overlay: implement dx11 backend (#707)

## Link to GitHub Issue or related Pull Request, if one exists
Fixes #134 

## Description of change
Add ImGui DX11 backend implementation, and integrate into the spice
overlay.

The tricky part is that some of the Unity games:

1. Launch multiple windows, and
2. Can be resized (even full screen games launch at a certain resolution
and then expand to fit desktop resolution)
3. lazy load DX11 DLLs

This PR also adds screenshot functionality, but screen resize is not
implemented.

## Testing

Seems to be working for most games. Need to do final tests for:

- [x] CCJ (attract movie plays as well)
- [x] Busou Shinki (title movie plays)
- [x] MFG
- [x] QuizKnock
- [x] Polaris Chord
- [x] check dx9 for regression
This commit is contained in:
bicarus
2026-05-29 00:47:28 -07:00
committed by GitHub
parent a977cba772
commit b3d8d0aea9
18 changed files with 2044 additions and 4 deletions
+15
View File
@@ -41,6 +41,8 @@ static INT64 g_TicksPerSecond = 0;
static ImGuiMouseCursor g_LastMouseCursor = ImGuiMouseCursor_COUNT;
static double g_LastMouseMovement = 0.f;
static bool g_MouseCursorAutoHide = false;
static float g_DisplaySizeOverrideW = 0.0f;
static float g_DisplaySizeOverrideH = 0.0f;
constexpr size_t VKEY_MAX = 255;
static std::array<bool, VKEY_MAX> g_KeysDown;
@@ -179,12 +181,25 @@ void ImGui_ImplSpice_Shutdown() {
void ImGui_ImplSpice_UpdateDisplaySize() {
// backends that render to a surface different from the window's client
// area (e.g. dx11 swapchain backbuffer) can override the size here so
// ImGui's viewport matches the actual render target.
if (g_DisplaySizeOverrideW > 0.0f && g_DisplaySizeOverrideH > 0.0f) {
ImGui::GetIO().DisplaySize = ImVec2(g_DisplaySizeOverrideW, g_DisplaySizeOverrideH);
return;
}
// get display size
RECT rect;
::GetClientRect(g_hWnd, &rect);
ImGui::GetIO().DisplaySize = ImVec2((float)(rect.right - rect.left), (float)(rect.bottom - rect.top));
}
void ImGui_ImplSpice_SetDisplaySizeOverride(float w, float h) {
g_DisplaySizeOverrideW = w;
g_DisplaySizeOverrideH = h;
}
bool ImGui_ImplSpice_UpdateMouseCursor() {
// check if cursor should be changed
+1
View File
@@ -6,5 +6,6 @@
IMGUI_IMPL_API bool ImGui_ImplSpice_Init(HWND hWnd);
IMGUI_IMPL_API void ImGui_ImplSpice_Shutdown();
IMGUI_IMPL_API void ImGui_ImplSpice_UpdateDisplaySize();
IMGUI_IMPL_API void ImGui_ImplSpice_SetDisplaySizeOverride(float w, float h);
IMGUI_IMPL_API bool ImGui_ImplSpice_UpdateMouseCursor();
IMGUI_IMPL_API void ImGui_ImplSpice_NewFrame();
+120 -2
View File
@@ -15,10 +15,19 @@
#include "build/resource.h"
#include "external/imgui/backends/imgui_impl_dx9.h"
#ifdef SPICE_D3D11
#include "external/imgui/backends/imgui_impl_dx11.h"
#include "hooks/graphics/backends/d3d11/d3d11_backend.h"
#endif
#include "overlay/imgui/impl_spice.h"
#include "overlay/imgui/impl_sw.h"
#include "overlay/notifications.h"
#ifdef SPICE_D3D11
#include <d3d11.h>
#include <dxgi.h>
#endif
#include "window.h"
#ifdef SPICE64
#include "windows/camera_control.h"
@@ -105,6 +114,21 @@ void overlay::create_d3d9(HWND hWnd, IDirect3D9 *d3d, IDirect3DDevice9 *device)
}
}
#ifdef SPICE_D3D11
void overlay::create_d3d11(HWND hWnd, ID3D11Device *device, ID3D11DeviceContext *context,
IDXGISwapChain *swapchain) {
if (!overlay::ENABLED) {
return;
}
const std::lock_guard<std::mutex> lock(OVERLAY_MUTEX);
if (!overlay::OVERLAY) {
overlay::OVERLAY = std::make_unique<overlay::SpiceOverlay>(hWnd, device, context, swapchain);
}
}
#endif
void overlay::create_software(HWND hWnd) {
if (!overlay::ENABLED) {
return;
@@ -165,6 +189,27 @@ overlay::SpiceOverlay::SpiceOverlay(HWND hWnd)
this->init();
}
#ifdef SPICE_D3D11
overlay::SpiceOverlay::SpiceOverlay(HWND hWnd, ID3D11Device *d3d11_device,
ID3D11DeviceContext *d3d11_context,
IDXGISwapChain *d3d11_swapchain)
: renderer(OverlayRenderer::D3D11),
hWnd(hWnd),
d3d11_device(d3d11_device),
d3d11_context(d3d11_context),
d3d11_swapchain(d3d11_swapchain) {
log_info("overlay", "initializing (D3D11)");
// increment reference counts
this->d3d11_device->AddRef();
this->d3d11_context->AddRef();
this->d3d11_swapchain->AddRef();
// init
this->init();
}
#endif
void overlay::SpiceOverlay::init() {
// init imgui
@@ -317,6 +362,11 @@ void overlay::SpiceOverlay::init() {
case OverlayRenderer::D3D9:
ImGui_ImplDX9_Init(this->device);
break;
#ifdef SPICE_D3D11
case OverlayRenderer::D3D11:
ImGui_ImplDX11_Init(this->d3d11_device, this->d3d11_context);
break;
#endif
case OverlayRenderer::SOFTWARE:
imgui_sw::bind_imgui_painting();
break;
@@ -327,6 +377,11 @@ void overlay::SpiceOverlay::init() {
case OverlayRenderer::D3D9:
ImGui_ImplDX9_NewFrame();
break;
#ifdef SPICE_D3D11
case OverlayRenderer::D3D11:
ImGui_ImplDX11_NewFrame();
break;
#endif
case OverlayRenderer::SOFTWARE:
break;
}
@@ -449,6 +504,21 @@ overlay::SpiceOverlay::~SpiceOverlay() {
this->d3d->Release();
break;
#ifdef SPICE_D3D11
case OverlayRenderer::D3D11:
if (this->d3d11_rtv) {
this->d3d11_rtv->Release();
this->d3d11_rtv = nullptr;
}
ImGui_ImplDX11_Shutdown();
// drop references
this->d3d11_swapchain->Release();
this->d3d11_context->Release();
this->d3d11_device->Release();
break;
#endif
case OverlayRenderer::SOFTWARE:
imgui_sw::unbind_imgui_painting();
break;
@@ -484,6 +554,15 @@ void overlay::SpiceOverlay::new_frame() {
case OverlayRenderer::D3D9:
ImGui_ImplDX9_NewFrame();
break;
#ifdef SPICE_D3D11
case OverlayRenderer::D3D11:
// refresh DisplaySize each frame; the dx11 swapchain hook pushes
// the current backbuffer dimensions into impl_spice via the
// size override so ImGui's viewport tracks ResizeBuffers calls.
ImGui_ImplSpice_UpdateDisplaySize();
ImGui_ImplDX11_NewFrame();
break;
#endif
case OverlayRenderer::SOFTWARE:
ImGui_ImplSpice_UpdateDisplaySize();
break;
@@ -526,6 +605,12 @@ void overlay::SpiceOverlay::render() {
case OverlayRenderer::D3D9:
ImGui_ImplDX9_RenderDrawData(ImGui::GetDrawData());
break;
#ifdef SPICE_D3D11
case OverlayRenderer::D3D11:
overlay::d3d11::render(this->d3d11_device, this->d3d11_context,
this->d3d11_swapchain, &this->d3d11_rtv);
break;
#endif
case OverlayRenderer::SOFTWARE: {
// get display metrics
@@ -705,11 +790,44 @@ bool overlay::SpiceOverlay::hotkeys_triggered() {
}
void overlay::SpiceOverlay::reset_invalidate() {
ImGui_ImplDX9_InvalidateDeviceObjects();
if (!overlay::OVERLAY) {
return;
}
switch (overlay::OVERLAY->renderer) {
case OverlayRenderer::D3D9:
ImGui_ImplDX9_InvalidateDeviceObjects();
break;
#ifdef SPICE_D3D11
case OverlayRenderer::D3D11:
// for DX11 a ResizeBuffers only invalidates the backbuffer RTV; the imgui
// device objects (shaders, buffers, textures) remain valid.
if (overlay::OVERLAY->d3d11_rtv) {
overlay::OVERLAY->d3d11_rtv->Release();
overlay::OVERLAY->d3d11_rtv = nullptr;
}
break;
#endif
case OverlayRenderer::SOFTWARE:
break;
}
}
void overlay::SpiceOverlay::reset_recreate() {
ImGui_ImplDX9_CreateDeviceObjects();
if (!overlay::OVERLAY) {
return;
}
switch (overlay::OVERLAY->renderer) {
case OverlayRenderer::D3D9:
ImGui_ImplDX9_CreateDeviceObjects();
break;
#ifdef SPICE_D3D11
case OverlayRenderer::D3D11:
// RTV is lazily recreated on the next render()
break;
#endif
case OverlayRenderer::SOFTWARE:
break;
}
}
void overlay::SpiceOverlay::input_char(unsigned int c) {
+35
View File
@@ -10,11 +10,22 @@
#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,
};
@@ -62,6 +73,10 @@ namespace overlay {
Window *window_log = nullptr;
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();
@@ -92,6 +107,14 @@ namespace overlay {
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;
}
@@ -124,6 +147,14 @@ namespace overlay {
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<uint32_t> pixel_data;
size_t pixel_data_width = 0;
@@ -156,6 +187,10 @@ namespace overlay {
// 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);
}