#pragma once #include #include #include #include #include #include #include #include #include "unique_value.h" #include class SDLContext { public: SDLContext(); enum class CrtFilterType { none, autoselect, scanlines, scanlines_2, rgb_matrix_2, rgb_matrix_3 }; struct VideoConfig { int window_width; int window_height; CrtFilterType crt_filter; int composer; const char *scale_quality; bool fullscreen; int aspect_x; int aspect_y; float cursor_size; }; struct AudioConfig { const char *audioDevice; }; enum class JoystickButton : char{ disabled, enter, space, ctrl, lclick, rclick, ctrl_lclick, escape, up, down, left, right, turn_left, turn_right, merge, split1, split2, split3, split4, split5, split6, map, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, backspace, mod_key }; static JoystickButton button_from_string(std::string_view s); struct JoystickConfig { bool enabled; bool swap_axis; short walk_deadzone; short cursor_deadzone; JoystickButton buttons[32]; JoystickButton buttons_mod[32]; }; struct AudioInfo { int freq; }; void init_video(const VideoConfig &config, const char *title); void set_window_icon(const void *icon_data, size_t icon_size); void configure_controller(const JoystickConfig &cfg); void close_video(); AudioInfo init_audio(const AudioConfig &config, SDL_AudioCallback cb, void *cb_ctx); void pause_audio(bool pause); void close_audio(); void present_rect(uint16_t *pixels, unsigned int pitch, unsigned int x, unsigned int y, unsigned int xs,unsigned ys); void swap_render_buffers(); void swap_display_buffers(); void show_blend_transition(const SDL_Rect &wrkarea, const SDL_Rect &prev, const SDL_Rect &next, float phase); void show_slide_transition(const SDL_Rect &visible_from, const SDL_Rect &visible_where, const SDL_Rect &hidden_from, const SDL_Rect &hidden_where); void set_quit_callback(std::function fn); MS_EVENT getMsEvent() { std::lock_guard _(_mx); MS_EVENT out = ms_event; ms_event.event = 0; ms_event.event_type = 0; return out; } bool get_shift_key_state() const { return _key_shift; } bool get_control_key_state() const { return _key_control; } bool get_capslock_state() const { return _key_capslock; } bool is_keyboard_ready() const; std::uint16_t pop_keyboard_code() ; bool is_quit_requested() const { return _quit_requested; } void cancel_quit_request() { _quit_requested = false; } void show_mouse_cursor(const unsigned short *ms_hi_format, SDL_Point finger); void hide_mouse_cursor(); void load_sprite(int sprite_id, const unsigned short *hi_image); void place_sprite(int sprite_id, int x, int y); void scale_sprite(int sprite_id, int x, int y, int w, int h); void hide_sprite(int sprite_id); void sprite_set_zindex(int sprite_id, int zindex); void unload_sprite(int sprite); void enable_crt_filter(bool enable); bool is_crt_enabled() const; bool is_joystick_used() const; bool is_joystick_enabled() const; protected: struct SDL_Deleter { void operator()(SDL_Window *); void operator()(SDL_Renderer *); void operator()(SDL_Surface *); void operator()(SDL_Texture *); }; struct BlendTransitionReq { SDL_Rect wrkarea; SDL_Rect prev; SDL_Rect next; float phase; }; struct SlideTransitionReq { SDL_Rect visible_from; SDL_Rect visible_where; SDL_Rect hidden_from; SDL_Rect hidden_where; }; enum class DisplayRequest { update, swap_render_buffers, swap_visible_buffers, blend_transition, slide_transition, show_mouse_cursor, //< loads mouse cursor and shows it hide_mouse_cursor, //< clears mouse cursor sprite_load, sprite_unload, sprite_place, sprite_scale, sprite_zindex, sprite_hide }; struct SDL_Audio_Deleter { void operator()(SDL_AudioDeviceID x); }; struct Sprite { int id = {}; int zindex = {}; std::unique_ptr _txtr = {}; SDL_Rect _rect = {}; bool shown = false; }; using SpriteList = std::vector; MS_EVENT ms_event; mutable std::mutex _mx; int aspect_x = 4; int aspect_y = 3; CrtFilterType _crt_filter= CrtFilterType::autoselect; bool _enable_crt = true; std::function _quit_callback; JoystickConfig _jcontrol_map; bool _jcontrol_mod_key = false; bool _jcontrol_used = false; std::unique_ptr _window; std::unique_ptr _renderer; std::unique_ptr _texture; std::unique_ptr _texture2; std::unique_ptr _crt_effect; std::unique_ptr _mouse; unique_value _audio; SDL_Texture *_visible_texture; SDL_Texture *_hidden_texture; bool _fullscreen_mode = false; bool _present = false; std::atomic _key_control = false; std::atomic _key_shift = false; std::atomic _key_capslock = false; std::atomic _quit_requested = false; std::vector _display_update_queue; using QueueIter = const char *; std::queue _keyboard_queue; SDL_Rect _mouse_rect; SDL_Point _mouse_finger; float _mouse_size; SpriteList _sprites; int axis1_cooldown = -1; int axis2_cooldown = -1; int check_axis_dir(int &cooldown, int value); Uint32 _update_request_event; Uint32 _refresh_request; std::jthread _render_thread; void event_loop(std::stop_token stp); void update_screen(bool force_refresh = false); template requires(std::is_trivially_copy_constructible_v) void push_item(const T &item); void push_item(const std::string_view &item); void push_update_msg(const SDL_Rect &rc, const uint16_t *data, int pitch); void push_swap_buffers(); template requires(std::is_trivially_copy_constructible_v) void pop_item(QueueIter &iter, T &item); std::string_view pop_data(QueueIter &iter, std::size_t size); SDL_Rect get_window_aspect_rect() const; static SDL_Rect to_window_rect(const SDL_Rect &winrc, const SDL_Rect &source_rect) ; static SDL_Point to_window_point(const SDL_Rect &win_rec, const SDL_Point &pt) ; static SDL_Point to_source_point(const SDL_Rect &win_rec, const SDL_Point &win_pt) ; static SDL_Rect transition_rect(const SDL_Rect &beg, const SDL_Rect &end, float phase); static int transition_int(int beg, int end, float phase); void generateCRTTexture(SDL_Renderer* renderer, SDL_Texture** texture, int width, int height, CrtFilterType type); void signal_push(); void refresh_screen(); std::optional blend_transition; std::optional slide_transition; void push_hi_image(const unsigned short *image); void update_zindex(); void joystick_handle(); void generate_j_event(int button, char up); static int adjust_deadzone(int v, short deadzone); };