#pragma once #include #include #include #include #include #include #include #include "external/imgui/imgui.h" #ifdef SPICE_D3D11 // forward decls for D3D11 (avoid pulling d3d11.h into every TU) struct ID3D11Device; struct ID3D11DeviceContext; struct ID3D11RenderTargetView; struct IDXGISwapChain; #endif namespace overlay { class Window; enum class OverlayRenderer { D3D9, #ifdef SPICE_D3D11 D3D11, #endif SOFTWARE, }; // corner of the screen the FPS / clock / timer window is anchored to enum class FpsLocation { TopLeft, TopRight, BottomLeft, BottomRight, }; // settings extern bool ENABLED; extern bool AUTO_SHOW_FPS; extern bool AUTO_SHOW_SUBSCREEN; extern bool AUTO_SHOW_IOPANEL; extern bool AUTO_SHOW_KEYPAD_P1; extern bool AUTO_SHOW_KEYPAD_P2; extern bool ENABLE_KEYBOARD_NAVIGATION; extern bool USE_WM_CHAR_FOR_IMGUI_CHAR_INPUT; extern FpsLocation FPS_LOCATION; extern bool SHOW_DEBUG_LOG_WINDOW; extern std::optional UI_SCALE_PERCENT; template float apply_scaling(T input) { if (!UI_SCALE_PERCENT.has_value()) { return input; } return static_cast(input) * UI_SCALE_PERCENT.value() / 100.f; } ImVec2 apply_scaling_to_vector(const ImVec2& input); ImVec2 apply_scaling_to_vector(float x, float y); class SpiceOverlay { public: D3DDEVICE_CREATION_PARAMETERS creation_parameters {}; D3DADAPTER_IDENTIFIER9 adapter_identifier {}; // windows Window *window_iopanel = nullptr; Window *window_config = nullptr; Window *window_keypad1 = nullptr; Window *window_keypad2 = nullptr; Window *window_cards = nullptr; Window *window_control = nullptr; Window *window_resize = nullptr; Window *window_camera = nullptr; Window *window_sub = nullptr; Window *window_log = nullptr; Window *window_obs = nullptr; // not part of `windows`: drawn/updated on the persistent layer (like // notifications), independent of the overlay's active state and input gates. std::unique_ptr window_fps; std::unique_ptr window_nostalgia_touch_piano; // not part of `windows`: the main menu / launcher. owned and drawn // separately from the overlay window layer; it drives the overlay's // active state while shown so its buttons still receive input. std::unique_ptr window_main_menu; explicit SpiceOverlay(HWND hWnd, IDirect3D9 *d3d, IDirect3DDevice9 *device); #ifdef SPICE_D3D11 explicit SpiceOverlay(HWND hWnd, ID3D11Device *d3d11_device, ID3D11DeviceContext *d3d11_context, IDXGISwapChain *d3d11_swapchain); #endif explicit SpiceOverlay(HWND hWnd); ~SpiceOverlay(); void window_add(Window *wnd); void new_frame(); void render(); // configurator-only (spicecfg D3D9): submit ImGui draw data inside BeginScene // after render() when the frame is being presented. In-game overlay draws in render(). void d3d9_render_draw(bool force_submit = false); void update(); void toggle_active(); void show_main_menu(); void set_active(bool active); bool get_active(); bool accepts_subscreen_mouse_input(); bool has_focus(); bool hotkeys_triggered(); static bool update_cursor(); static void reset_invalidate(); static void reset_recreate(); void input_char(unsigned int c); uint32_t *sw_get_pixel_data(int *width, int *height); inline bool uses_window(HWND hWnd) { return this->hWnd == hWnd; } inline bool uses_context(IDirect3D9 *other) { return this->d3d == other; } inline bool uses_device(IDirect3DDevice9 *other) { return this->device == other; } #ifdef SPICE_D3D11 inline bool uses_device(ID3D11Device *other) { return this->d3d11_device == other; } inline bool uses_swapchain(IDXGISwapChain *other) { return this->d3d11_swapchain == other; } #endif inline IDirect3DDevice9 *get_device() { return this->device; } inline HWND get_window() { return this->hWnd; } bool has_subscreen_touch_transform() { return (this->subscreen_touch_transform != nullptr); } bool transform_touch_point(LONG *x, LONG *y) { if (this->get_active() && this->subscreen_touch_transform) { return this->subscreen_touch_transform(x, y); } else { return true; } } void set_subscreen_touch_transform(const std::function &f) { this->subscreen_touch_transform = f; } // renderer OverlayRenderer renderer; float total_elapsed = 0.f; // set to true by the SOFTWARE renderer when the pixel buffer was updated // in the most recent render() call. Allows the configurator's WM_PAINT // driver to skip its full-window InvalidateRect on idle frames where the // ImGui draw data is bitwise-identical to the previous frame. bool sw_pixels_dirty = false; // configurator-only (spicecfg D3D9): set by render() when ImGui draw data // changed since the last submitted frame; configurator skips Clear/Present when false. bool d3d9_frame_dirty = false; private: HWND hWnd = nullptr; // D3D9 IDirect3D9 *d3d = nullptr; IDirect3DDevice9 *device = nullptr; #ifdef SPICE_D3D11 // D3D11 ID3D11Device *d3d11_device = nullptr; ID3D11DeviceContext *d3d11_context = nullptr; IDXGISwapChain *d3d11_swapchain = nullptr; ID3D11RenderTargetView *d3d11_rtv = nullptr; #endif // software std::vector pixel_data; size_t pixel_data_width = 0; size_t pixel_data_height = 0; uint64_t sw_last_draw_hash = 0; bool sw_has_last_draw_hash = false; uint64_t d3d9_last_draw_hash = 0; bool d3d9_has_last_draw_hash = false; int d3d9_last_display_w = 0; int d3d9_last_display_h = 0; std::vector> windows; std::function subscreen_touch_transform = nullptr; bool active = false; bool toggle_down = false; bool main_menu_down = false; bool fps_down = false; bool hotkey_toggle = false; bool hotkey_toggle_last = false; // true between new_frame()'s ImGui::NewFrame() and render()'s ImGui::Render(), // so render() never runs without a matching NewFrame. bool has_pending_frame = false; bool is_subscreen_overlay_visible(); void init(); void add_font(const char* font, ImFontConfig* config, const ImWchar* glyphs); }; // global extern std::mutex OVERLAY_MUTEX; extern std::unique_ptr OVERLAY; extern ImFont* DSEG_FONT; // synchronized helpers void create_d3d9(HWND hWnd, IDirect3D9 *d3d, IDirect3DDevice9 *device); #ifdef SPICE_D3D11 void create_d3d11(HWND hWnd, ID3D11Device *device, ID3D11DeviceContext *context, IDXGISwapChain *swapchain); #endif void create_software(HWND hWnd); void destroy(HWND hWnd = nullptr); }