// frt_godot2.cc /* FRT - A Godot platform targeting single board computers Copyright (c) 2017-2022 Emanuele Fornara SPDX-License-Identifier: MIT */ #include "frt.h" #include "sdl2_adapter.h" #include "sdl2_godot_mapping.h" #include "dl/gles2.gen.h" #include "core/print_string.h" #include "drivers/unix/os_unix.h" #include "servers/visual_server.h" #include "servers/visual/visual_server_wrap_mt.h" #include "servers/visual/rasterizer.h" #include "servers/physics_server.h" #include "servers/audio/audio_server_sw.h" #include "servers/audio/sample_manager_sw.h" #include "servers/spatial_sound/spatial_sound_server_sw.h" #include "servers/spatial_sound_2d/spatial_sound_2d_server_sw.h" #include "servers/physics/physics_server_sw.h" #include "servers/physics_2d/physics_2d_server_sw.h" #include "servers/physics_2d/physics_2d_server_wrap_mt.h" #include "servers/visual/visual_server_raster.h" #include "drivers/gles2/rasterizer_gles2.h" #include "main/main.h" namespace frt { class AudioDriverSDL2 : public AudioDriverSW, public SampleProducer { private: Audio audio_; int mix_rate_; OutputFormat output_format_; public: AudioDriverSDL2() : audio_(this) { } public: // AudioDriverSW const char *get_name() const FRT_OVERRIDE { return "SDL2"; } Error init() FRT_OVERRIDE { mix_rate_ = GLOBAL_DEF("audio/mix_rate", 44100); output_format_ = OUTPUT_STEREO; const int latency = GLOBAL_DEF("audio/output_latency", 25); const int samples = closest_power_of_2(latency * mix_rate_ / 1000); return audio_.init(mix_rate_, samples) ? OK : ERR_CANT_OPEN; } int get_mix_rate() const FRT_OVERRIDE { return mix_rate_; } OutputFormat get_output_format() const FRT_OVERRIDE { return output_format_; } void start() FRT_OVERRIDE { audio_.start(); } void lock() FRT_OVERRIDE { audio_.lock(); } void unlock() FRT_OVERRIDE { audio_.unlock(); } void finish() FRT_OVERRIDE { audio_.finish(); } public: // SampleProducer void produce_samples(int n_of_frames, int32_t *frames) FRT_OVERRIDE { audio_server_process(n_of_frames, frames); } }; class Godot2_OS : public OS_Unix, public EventHandler { private: MainLoop *main_loop_; VideoMode video_mode_; bool quit_; OS_FRT os_; RasterizerGLES2 *rasterizer_; VisualServer *visual_server_; int event_id_; void init_video() { rasterizer_ = memnew(RasterizerGLES2); visual_server_ = memnew(VisualServerRaster(rasterizer_)); visual_server_->init(); } void cleanup_video() { visual_server_->finish(); memdelete(visual_server_); memdelete(rasterizer_); } AudioDriverSDL2 audio_driver_; AudioServerSW *audio_server_; SampleManagerMallocSW *sample_manager_; SpatialSoundServerSW *spatial_sound_server_; SpatialSound2DServerSW *spatial_sound_2d_server_; void init_audio() { const int driver_id = 0; AudioDriverManagerSW::add_driver(&audio_driver_); AudioDriverManagerSW::get_driver(driver_id)->set_singleton(); if (AudioDriverManagerSW::get_driver(driver_id)->init() != OK) fatal("audio driver failed to initialize."); sample_manager_ = memnew(SampleManagerMallocSW); audio_server_ = memnew(AudioServerSW(sample_manager_)); audio_server_->init(); spatial_sound_server_ = memnew(SpatialSoundServerSW); spatial_sound_server_->init(); spatial_sound_2d_server_ = memnew(SpatialSound2DServerSW); spatial_sound_2d_server_->init(); } void cleanup_audio() { spatial_sound_server_->finish(); memdelete(spatial_sound_server_); spatial_sound_2d_server_->finish(); memdelete(spatial_sound_2d_server_); memdelete(sample_manager_); audio_server_->finish(); memdelete(audio_server_); } PhysicsServer *physics_server_; Physics2DServer *physics_2d_server_; void init_physics() { physics_server_ = memnew(PhysicsServerSW); physics_server_->init(); physics_2d_server_ = memnew(Physics2DServerSW); physics_2d_server_->init(); } void cleanup_physics() { physics_2d_server_->finish(); memdelete(physics_2d_server_); physics_server_->finish(); memdelete(physics_server_); } InputDefault *input_; Point2 mouse_pos_; int mouse_state_; void init_input() { input_ = memnew(InputDefault); mouse_pos_ = Point2(-1, -1); mouse_state_ = 0; } void cleanup_input() { memdelete(input_); } void fill_modifier_state(::InputModifierState &st) { const InputModifierState *os_st = os_.get_modifier_state(); st.shift = os_st->shift; st.alt = os_st->alt; st.control = os_st->control; st.meta = os_st->meta; } public: Godot2_OS() : os_(this) { main_loop_ = 0; quit_ = false; event_id_ = 0; } void run() { if (main_loop_) { main_loop_->init(); while (!quit_ && !Main::iteration()) os_.dispatch_events(); main_loop_->finish(); } } public: // OS int get_video_driver_count() const FRT_OVERRIDE { return 1; } const char *get_video_driver_name(int driver) const FRT_OVERRIDE { return "GLES2"; } VideoMode get_default_video_mode() const FRT_OVERRIDE { return OS::VideoMode(960, 540, false); } void initialize(const VideoMode &desired, int video_driver, int audio_driver) FRT_OVERRIDE { video_mode_ = desired; os_.init(API_OpenGL_ES2, video_mode_.width, video_mode_.height, video_mode_.resizable, video_mode_.borderless_window, video_mode_.always_on_top); frt_resolve_symbols_gles2(get_proc_address); init_video(); init_audio(); init_physics(); init_input(); _ensure_data_dir(); } void set_main_loop(MainLoop *main_loop) FRT_OVERRIDE { main_loop_ = main_loop; input_->set_main_loop(main_loop); } void delete_main_loop() FRT_OVERRIDE { if (main_loop_) memdelete(main_loop_); main_loop_ = 0; } void finalize() FRT_OVERRIDE { delete_main_loop(); cleanup_input(); cleanup_physics(); cleanup_audio(); cleanup_video(); os_.cleanup(); } Point2 get_mouse_pos() const FRT_OVERRIDE { return mouse_pos_; } int get_mouse_button_state() const FRT_OVERRIDE { return mouse_state_; } void set_mouse_mode(OS::MouseMode mode) FRT_OVERRIDE { os_.set_mouse_mode(map_mouse_mode(mode)); } OS::MouseMode get_mouse_mode() const FRT_OVERRIDE { return map_mouse_os_mode(os_.get_mouse_mode()); } void set_window_title(const String &title) FRT_OVERRIDE { os_.set_title(title.utf8().get_data()); } void set_video_mode(const VideoMode &video_mode, int screen) FRT_OVERRIDE { } VideoMode get_video_mode(int screen = 0) const FRT_OVERRIDE { return video_mode_; } void get_fullscreen_mode_list(List *list, int screen) const FRT_OVERRIDE { } Size2 get_window_size() const FRT_OVERRIDE { return Size2(video_mode_.width, video_mode_.height); } void set_window_size(const Size2 size) FRT_OVERRIDE { ivec2 os_size = { (int)size.width, (int)size.height }; os_.set_size(os_size); video_mode_.width = os_size.x; video_mode_.height = os_size.y; } Point2 get_window_position() const FRT_OVERRIDE { ivec2 pos = os_.get_pos(); return Point2(pos.x, pos.y); } void set_window_position(const Point2 &pos) FRT_OVERRIDE { ivec2 os_pos = { (int)pos.width, (int)pos.height }; os_.set_pos(os_pos); } void set_window_fullscreen(bool enable) FRT_OVERRIDE { os_.set_fullscreen(enable); video_mode_.fullscreen = enable; } bool is_window_fullscreen() const FRT_OVERRIDE { return os_.is_fullscreen(); } void set_window_always_on_top(bool enable) FRT_OVERRIDE { os_.set_always_on_top(enable); video_mode_.always_on_top = enable; } bool is_window_always_on_top() const FRT_OVERRIDE { return os_.is_always_on_top(); } void set_window_resizable(bool enable) FRT_OVERRIDE { os_.set_resizable(enable); } bool is_window_resizable() const FRT_OVERRIDE { return os_.is_resizable(); } void set_window_maximized(bool enable) FRT_OVERRIDE { os_.set_maximized(enable); } bool is_window_maximized() const FRT_OVERRIDE { return os_.is_maximized(); } void set_window_minimized(bool enable) FRT_OVERRIDE { os_.set_minimized(enable); } bool is_window_minimized() const FRT_OVERRIDE { return os_.is_minimized(); } MainLoop *get_main_loop() const FRT_OVERRIDE { return main_loop_; } bool can_draw() const FRT_OVERRIDE { return true; } void set_cursor_shape(CursorShape shape) FRT_OVERRIDE { } void set_custom_mouse_cursor(const RES &cursor, CursorShape shape, const Vector2 &hotspot) FRT_OVERRIDE { } void make_rendering_thread() FRT_OVERRIDE { os_.make_current(); } void release_rendering_thread() FRT_OVERRIDE { os_.release_current(); } void swap_buffers() FRT_OVERRIDE { os_.swap_buffers(); } void set_use_vsync(bool enable) FRT_OVERRIDE { os_.set_use_vsync(enable); } bool is_vsync_enabled() const FRT_OVERRIDE { return os_.is_vsync_enabled(); } public: // EventHandler void handle_resize_event(ivec2 size) FRT_OVERRIDE { video_mode_.width = size.x; video_mode_.height = size.y; } void handle_key_event(int sdl2_code, int unicode, bool pressed) FRT_OVERRIDE { int code = map_key_sdl2_code(sdl2_code); InputEvent event; event.ID = ++event_id_; event.type = InputEvent::KEY; event.device = 0; fill_modifier_state(event.key.mod); event.key.pressed = pressed; event.key.scancode = code; event.key.unicode = unicode; event.key.echo = 0; input_->parse_input_event(event); } void handle_mouse_motion_event(ivec2 pos, ivec2 dpos) FRT_OVERRIDE { mouse_pos_.x = pos.x; mouse_pos_.y = pos.y; InputEvent event; event.ID = ++event_id_; event.type = InputEvent::MOUSE_MOTION; event.device = 0; fill_modifier_state(event.mouse_motion.mod); event.mouse_motion.button_mask = mouse_state_; event.mouse_motion.x = mouse_pos_.x; event.mouse_motion.y = mouse_pos_.y; input_->set_mouse_pos(mouse_pos_); event.mouse_motion.global_x = mouse_pos_.x; event.mouse_motion.global_y = mouse_pos_.y; event.mouse_motion.speed_x = input_->get_mouse_speed().x; event.mouse_motion.speed_y = input_->get_mouse_speed().y; event.mouse_motion.relative_x = dpos.x; event.mouse_motion.relative_y = dpos.y; input_->parse_input_event(event); } void handle_mouse_button_event(int os_button, bool pressed, bool doubleclick) FRT_OVERRIDE { int button = map_mouse_os_button(os_button); int bit = (1 << (button - 1)); if (pressed) mouse_state_ |= bit; else mouse_state_ &= ~bit; InputEvent event; event.ID = ++event_id_; event.type = InputEvent::MOUSE_BUTTON; event.device = 0; fill_modifier_state(event.mouse_button.mod); event.mouse_button.button_mask = mouse_state_; event.mouse_button.x = mouse_pos_.x; event.mouse_button.y = mouse_pos_.y; event.mouse_button.global_x = mouse_pos_.x; event.mouse_button.global_y = mouse_pos_.y; event.mouse_button.button_index = button; event.mouse_button.doubleclick = doubleclick; event.mouse_button.pressed = pressed; input_->parse_input_event(event); } void handle_js_status_event(int id, bool connected, const char *name, const char *guid) FRT_OVERRIDE { input_->joy_connection_changed(id, connected, name, guid); } void handle_js_button_event(int id, int button, bool pressed) FRT_OVERRIDE { event_id_ = input_->joy_button(event_id_, id, button, pressed ? 1 : 0); } void handle_js_axis_event(int id, int axis, float value) FRT_OVERRIDE { InputDefault::JoyAxis v = { -1, value }; event_id_ = input_->joy_axis(event_id_, id, axis, v); } void handle_js_hat_event(int id, int os_mask) FRT_OVERRIDE { int mask = map_hat_os_mask(os_mask); event_id_ = input_->joy_hat(event_id_, id, mask); } void handle_quit_event() FRT_OVERRIDE { quit_ = true; } }; } // namespace frt #include "frt_lib.h" extern "C" int frt_godot_main(int argc, char *argv[]) { frt::Godot2_OS os; Error err = Main::setup(argv[0], argc - 1, &argv[1]); if (err != OK) return 255; if (Main::start()) os.run(); Main::cleanup(); return os.get_exit_code(); }