Skip to content

Commit 525bf37

Browse files
committed
feat(native): C#-controlled backend selection via GetAvailableBackends/SetBackend
- Add GetAvailableBackends() (else-if chain, D3D11 first to avoid false D3D12) - Add SetBackend(int) for C# to select renderer before Init() - Remove auto-detection from MainThread; use g_SelectedBackend set by C# - Managed.cs: detect backend, dispatch to ImGuiRenderer (D3D) or ImGUIGLRenderer (GL) - NativeApi: add P/Invoke for GetAvailableBackends and SetBackend - Fix D3D12 false-positive on D3D11 devices (kiero D3D12 locate succeeds on WDDM 2.0+)
1 parent a2d5073 commit 525bf37

6 files changed

Lines changed: 81 additions & 35 deletions

File tree

‎.gitignore‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ project.lock.json
5050
# test project (exclude entire dir)
5151
TestAndroidProject/
5252
TestAndroidProject2/
53-
/dx12app/
53+
tests
5454

5555
# Runtime data (mods, etc — user-managed)
5656
/Runtime/

‎StArray.ModManager.Windows/Managed.cs‎

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using StArray.ModManager.Manager;
88
using StArray.ModManager.Runtime;
99
using StArray.ModManager.Windows.UI;
10+
using StArray.ModManager.Windows.Native;
1011

1112
namespace StArray.ModManager.Windows;
1213

@@ -70,9 +71,20 @@ public static int Entry(int argc, IntPtr argv)
7071
Logger.Info($"{nameof(Managed)}-Benchmark", $"Path resolve: {Benchmark.End():F3}s");
7172
ModManagerUI modManagerUI = new(new ModLoader(modsPath), managedDir);
7273

73-
// Use DX12 renderer — hooks IDXGISwapChain::Present via vtable + MinHook
74-
ImGuiRenderer.OnRender += modManagerUI.Render;
75-
ImGuiRenderer.Install();
74+
// Detect backend and pick renderer
75+
int mask = NativeApi.GetAvailableBackends();
76+
Logger.Info("Backend", $"GetAvailableBackends=0x{mask:X2} (1=D3D12 2=D3D11 4=D3D9 8=GL 16=VK)");
77+
78+
if ((mask & 2) != 0) // D3D11
79+
{ NativeApi.SetBackend(1); ImGuiRenderer.OnRender += modManagerUI.Render; ImGuiRenderer.Install(); }
80+
else if ((mask & 1) != 0) // D3D12
81+
{ NativeApi.SetBackend(0); ImGuiRenderer.OnRender += modManagerUI.Render; ImGuiRenderer.Install(); }
82+
else if ((mask & 4) != 0) // D3D9
83+
{ NativeApi.SetBackend(2); ImGuiRenderer.OnRender += modManagerUI.Render; ImGuiRenderer.Install(); }
84+
else if ((mask & 8) != 0) // OpenGL
85+
{ ImGUIGLRenderer.OnRender += modManagerUI.Render; ImGUIGLRenderer.Install(); }
86+
else
87+
{ Logger.Error("Backend", $"No supported backend"); return 1; }
7688
totalSw.Stop();
7789
Logger.Info($"{nameof(Managed)}-Benchmark", $"=== Startup total: {totalSw.Elapsed.TotalSeconds:F3}s ===");
7890
return 0;

‎StArray.ModManager.Windows/Native/main.cpp‎

Lines changed: 56 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ bool ShowMenu = false, ImGui_Initialised = false;
1010
HWND g_GameWindow = nullptr;
1111
WNDPROC g_OriginalWndProc = nullptr;
1212
static HMODULE g_Module = nullptr;
13+
static int g_SelectedBackend = -1; // set by C# via SetBackend()
1314

1415
static int g_WndProcMsgCount = 0;
1516
LRESULT APIENTRY WndProc(HWND h, UINT m, WPARAM w, LPARAM l) {
@@ -20,13 +21,13 @@ LRESULT APIENTRY WndProc(HWND h, UINT m, WPARAM w, LPARAM l) {
2021
HRESULT APIENTRY hkPresent(IDXGISwapChain* sc, UINT sync, UINT flags) {
2122
if (!ImGui_Initialised) {
2223
if (imgui_callbacks.init_callback == nullptr) return oPresent(sc, sync, flags);
23-
DEBUG_LOG("hkPresent: first frame init, backend=%d", g_KieroBackend);
24+
DEBUG_LOG("hkPresent: first frame init, backend=%d", g_SelectedBackend);
2425
{
2526
DXGI_SWAP_CHAIN_DESC sd; sc->GetDesc(&sd);
2627
g_GameWindow = sd.OutputWindow;
2728
DEBUG_LOG("hkPresent: OutputWindow=%p (%dx%d)", g_GameWindow, sd.BufferDesc.Width, sd.BufferDesc.Height);
2829
}
29-
switch (g_KieroBackend) {
30+
switch (g_SelectedBackend) {
3031
case 2: DX9::Init(sc); break;
3132
case 1: DX11::Init(sc); break;
3233
case 0: DX12::Init(sc); break;
@@ -36,7 +37,7 @@ HRESULT APIENTRY hkPresent(IDXGISwapChain* sc, UINT sync, UINT flags) {
3637
ImGui_ImplWin32_Init(g_GameWindow);
3738
DEBUG_LOG("hkPresent: GL/VK init via C#, no native backend");
3839
break;
39-
default: DEBUG_LOG("hkPresent: unknown backend=%d, skipping init", g_KieroBackend); break;
40+
default: DEBUG_LOG("hkPresent: unknown backend=%d, skipping init", g_SelectedBackend); break;
4041
}
4142
g_OriginalWndProc = (WNDPROC)SetWindowLongPtr(g_GameWindow, GWLP_WNDPROC, (__int3264)(LONG_PTR)WndProc);
4243
ImGui_Initialised = true;
@@ -60,7 +61,7 @@ HRESULT APIENTRY hkPresent(IDXGISwapChain* sc, UINT sync, UINT flags) {
6061
io->DisplaySize.x = (float)sd.BufferDesc.Width; io->DisplaySize.y = (float)sd.BufferDesc.Height; }
6162
ImGui_ImplWin32_NewFrame();
6263

63-
switch (g_KieroBackend) {
64+
switch (g_SelectedBackend) {
6465
case 4: case 3: // VK/GL: C# render only
6566
if (imgui_callbacks.render_callback) imgui_callbacks.render_callback(); break;
6667
case 2: DX9::Render(sc); break;
@@ -86,43 +87,69 @@ DWORD WINAPI MainThread(LPVOID) {
8687
Sleep(100);
8788
}
8889

89-
// ---- kiero2 backend detection (explicit locate, no macros) ----
90+
if (g_SelectedBackend < 0 || g_SelectedBackend > 4) {
91+
DEBUG_LOG("MainThread: invalid backend=%d, no hooks installed", g_SelectedBackend);
92+
return 1;
93+
}
94+
9095
MH_Initialize();
91-
{
92-
kiero::D3D12Output d3d12;
93-
kiero::D3D11Output d3d11;
94-
kiero::D3D9Output d3d9;
95-
kiero::OpenGLOutput gl;
96-
kiero::VulkanOutput vk;
97-
98-
if (kiero::locate<kiero::Implementation_D3D12>(nullptr, &d3d12) == kiero::Error_Nil)
99-
{ g_KieroBackend = 0; g_KieroD3D12 = d3d12; DEBUG_LOG("MainThread: backend=D3D12"); }
100-
else if (kiero::locate<kiero::Implementation_D3D11>(nullptr, &d3d11) == kiero::Error_Nil)
101-
{ g_KieroBackend = 1; g_KieroD3D11 = d3d11; DEBUG_LOG("MainThread: backend=D3D11"); }
102-
else if (kiero::locate<kiero::Implementation_D3D9>(nullptr, &d3d9) == kiero::Error_Nil)
103-
{ g_KieroBackend = 2; g_KieroD3D9 = d3d9; DEBUG_LOG("MainThread: backend=D3D9"); }
104-
else if (kiero::locate<kiero::Implementation_OpenGL>(nullptr, &gl) == kiero::Error_Nil)
105-
{ g_KieroBackend = 3; g_KieroOpenGL = gl; DEBUG_LOG("MainThread: backend=OpenGL"); }
106-
else if (kiero::locate<kiero::Implementation_Vulkan>(nullptr, &vk) == kiero::Error_Nil)
107-
{ g_KieroBackend = 4; g_KieroVulkan = vk; DEBUG_LOG("MainThread: backend=Vulkan"); }
108-
else {
109-
DEBUG_LOG("MainThread: no backend detected!");
110-
return 1;
111-
}
96+
DEBUG_LOG("MainThread: backend=%d (0=D3D12 1=D3D11 2=D3D9 3=GL 4=VK)", g_SelectedBackend);
97+
98+
// GL/VK: no DXGI swapchain to hook — call init directly, C# handles everything
99+
if (g_SelectedBackend >= 3) {
100+
if (imgui_callbacks.init_callback) imgui_callbacks.init_callback();
101+
DEBUG_LOG("MainThread: GL/VK backend, C# handles rendering, no native hooks");
102+
return 0;
112103
}
113-
DEBUG_LOG("MainThread: backend=%d (0=D3D12 1=D3D11 2=D3D9 3=GL 4=VK)", g_KieroBackend);
114-
if (g_KieroBackend == 0) {
104+
105+
if (g_SelectedBackend == 0) {
115106
void* t = D3D12_CQ(10);
116107
DEBUG_LOG("MainThread: D3D12 CQ[10]=%p (ExecuteCommandLists)", t);
117108
if (t) { MH_CreateHook(t, (LPVOID)hkExecuteCommandLists, (void**)&oExecuteCommandLists); MH_EnableHook(t); }
118109
}
119-
void* prTarget = g_KieroBackend == 0 ? D3D12_SWAP(8) : g_KieroBackend == 1 ? D3D11_SWAP(8) : D3D9_DEV(17);
110+
void* prTarget = g_SelectedBackend == 0 ? D3D12_SWAP(8) : g_SelectedBackend == 1 ? D3D11_SWAP(8) : D3D9_DEV(17);
120111
DEBUG_LOG("MainThread: Present target=%p, installing hook...", prTarget);
121112
if (prTarget) { MH_CreateHook(prTarget, (LPVOID)hkPresent, (void**)&oPresent); MH_EnableHook(prTarget); }
122113
DEBUG_LOG("MainThread: hooks installed, oPresent=%p", oPresent);
123114
return 0;
124115
}
125116

117+
// ---- C# API ----
118+
119+
extern "C" __declspec(dllexport) int __cdecl GetAvailableBackends() {
120+
int mask = 0;
121+
kiero::D3D12Output d3d12;
122+
kiero::D3D11Output d3d11;
123+
kiero::D3D9Output d3d9;
124+
kiero::OpenGLOutput gl;
125+
kiero::VulkanOutput vk;
126+
127+
// else-if chain: first match wins (D3D12 locate may false-positive on D3D11 devices)
128+
if (kiero::locate<kiero::Implementation_D3D11>(nullptr, &d3d11) == kiero::Error_Nil)
129+
{ mask = 2; g_KieroD3D11 = d3d11; DEBUG_LOG("GetAvailableBackends: D3D11"); }
130+
else if (kiero::locate<kiero::Implementation_D3D12>(nullptr, &d3d12) == kiero::Error_Nil)
131+
{ mask = 1; g_KieroD3D12 = d3d12; DEBUG_LOG("GetAvailableBackends: D3D12"); }
132+
else if (kiero::locate<kiero::Implementation_D3D9>(nullptr, &d3d9) == kiero::Error_Nil)
133+
{ mask = 4; g_KieroD3D9 = d3d9; DEBUG_LOG("GetAvailableBackends: D3D9"); }
134+
else if (kiero::locate<kiero::Implementation_OpenGL>(nullptr, &gl) == kiero::Error_Nil)
135+
{ mask = 8; g_KieroOpenGL = gl; DEBUG_LOG("GetAvailableBackends: OpenGL"); }
136+
else if (kiero::locate<kiero::Implementation_Vulkan>(nullptr, &vk) == kiero::Error_Nil)
137+
{ mask = 16; g_KieroVulkan = vk; DEBUG_LOG("GetAvailableBackends: Vulkan"); }
138+
139+
DEBUG_LOG("GetAvailableBackends: mask=0x%02X", mask);
140+
return mask;
141+
}
142+
143+
extern "C" __declspec(dllexport) int __cdecl SetBackend(int backend) {
144+
if (backend < 0 || backend > 4) {
145+
DEBUG_LOG("SetBackend: invalid backend=%d", backend);
146+
return 1;
147+
}
148+
g_SelectedBackend = backend;
149+
DEBUG_LOG("SetBackend: backend=%d", backend);
150+
return 0;
151+
}
152+
126153
extern "C" __declspec(dllexport) int __cdecl Init(ImGuiInitCallbackFn ic, ImGuiShutdownCallbackFn sc, ImGuiRenderCallbackFn rc) {
127154
DEBUG_LOG("Init: bridge mode, init_cb=%p shutdown_cb=%p render_cb=%p", ic, sc, rc);
128155
imgui_callbacks = { ic, sc, rc }; g_BridgeMode = true;

‎StArray.ModManager.Windows/Native/main.h‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,6 @@ static kiero::D3D11Output g_KieroD3D11;
105105
static kiero::D3D12Output g_KieroD3D12;
106106
static kiero::OpenGLOutput g_KieroOpenGL;
107107
static kiero::VulkanOutput g_KieroVulkan;
108-
static int g_KieroBackend = -1; // 0=D3D12 1=D3D11 2=D3D9 3=OpenGL 4=Vulkan
109108

110109
// Per-backend vtable accessors
111110
inline void* D3D12_DEV(int i) { return g_KieroD3D12.device_methods[i]; }

‎StArray.ModManager.Windows/NativeInterop/Dx12Native.cs‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,12 @@ public static class NativeApi
1717

1818
[DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl, EntryPoint = "Init")]
1919
public static extern int Init(IntPtr init, IntPtr shutdown, IntPtr render);
20+
21+
/// <summary>Detect available graphics backends. Returns bitmask: 1=D3D12, 2=D3D11, 4=D3D9, 8=GL, 16=VK.</summary>
22+
[DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl, EntryPoint = "GetAvailableBackends")]
23+
public static extern int GetAvailableBackends();
24+
25+
/// <summary>Select which backend to hook. 0=D3D12, 1=D3D11, 2=D3D9, 3=GL, 4=VK. Call before Init().</summary>
26+
[DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl, EntryPoint = "SetBackend")]
27+
public static extern int SetBackend(int backend);
2028
}

‎StArray.ModManager.Windows/UI/ImGuiRenderer.cs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ bool InstallInstance()
4343
Marshal.GetFunctionPointerForDelegate(_sInit),
4444
Marshal.GetFunctionPointerForDelegate(_sShutdown),
4545
Marshal.GetFunctionPointerForDelegate(_sRender));
46-
W($"[NH] NativeInit={r} backend=auto");
46+
W($"[NH] NativeInit={r}");
4747

4848
if (s_pending != null) { _onRender += s_pending; s_pending = null; }
4949
_ok = true; W("[Renderer] Ready (multi-backend)");

0 commit comments

Comments
 (0)