1#ifdef DTTR_MODS_ENABLED
16static bool initialized;
18static const float modding_badge_font_factor = 0.90f;
19static const float modding_badge_min_font_size = 6.0f;
20static const float modding_badge_default_font_size = 13.0f;
21static const float modding_badge_line_advance_factor = 0.86f;
22static const float modding_badge_bold_offset = 0.5f;
23static const ImVec4_c modding_badge_header_color = {1.0f, 1.0f, 1.0f, 1.0f};
24static const ImVec4_c modding_badge_seconds_color = {0.35f, 1.0f, 0.35f, 1.0f};
25static const ImVec4_c modding_badge_hot_reload_on_color = {0.35f, 1.0f, 0.35f, 1.0f};
26static const ImVec4_c modding_badge_hot_reload_off_color = {1.0f, 0.35f, 0.35f, 1.0f};
27static const char modding_badge_hot_reload_label[] =
"Hot Reload:";
29static float overlay_text_width(
const char *text) {
34 const ImVec2_c
size = igCalcTextSize(text,
NULL,
false, -1.0f);
38static float overlay_line_height() {
39 return igGetTextLineHeight();
42static float overlay_line_advance() {
43 return igGetTextLineHeight() * modding_badge_line_advance_factor;
46static float overlay_mod_gap() {
47 const ImGuiStyle *style = igGetStyle();
48 const float spacing = style ? style->ItemSpacing.x : 4.0f;
49 return spacing * 0.5f;
52static void draw_overlay_text_at(
53 ImDrawList *draw_list,
62 ImDrawList_AddText_Vec2(draw_list, pos,
color, text, text + strlen(text));
65static void draw_bold_overlay_text(
66 ImDrawList *draw_list,
71 draw_overlay_text_at(draw_list, pos,
color, text);
74 (ImVec2_c){pos.x + modding_badge_bold_offset, pos.y},
80static float hot_reload_width() {
82 return overlay_text_width(modding_badge_hot_reload_label) + overlay_mod_gap()
83 + overlay_text_width(
state);
86static void draw_hot_reload_header(
float width) {
87 ImDrawList *draw_list = igGetWindowDrawList();
93 const char *
state = hot_reload_enabled ?
"on" :
"off";
94 const ImVec4_c state_color = hot_reload_enabled ? modding_badge_hot_reload_on_color
95 : modding_badge_hot_reload_off_color;
96 const float gap = overlay_mod_gap();
97 const float label_width = overlay_text_width(modding_badge_hot_reload_label);
98 const ImVec2_c cursor = igGetCursorScreenPos();
99 const ImU32 label_color = igGetColorU32_Vec4(modding_badge_header_color);
100 const ImU32 state_color_u32 = igGetColorU32_Vec4(state_color);
102 draw_bold_overlay_text(draw_list, cursor, label_color, modding_badge_hot_reload_label);
103 draw_bold_overlay_text(
105 (ImVec2_c){cursor.x + label_width + gap, cursor.y},
110 igDummy((ImVec2_c){width, overlay_line_advance()});
113static void mod_overlay_elapsed_text(
char *out,
size_t out_size,
size_t mod_index) {
118 snprintf(out, out_size,
"(%lus)", elapsed_seconds);
121static void draw_mod_overlay_row(
127 ImDrawList *draw_list = igGetWindowDrawList();
132 const ImVec2_c cursor = igGetCursorScreenPos();
133 const ImU32 text_color = igGetColorU32_Col(ImGuiCol_Text, 1.0f);
134 const ImU32 seconds_color = igGetColorU32_Vec4(modding_badge_seconds_color);
135 const char *mod_name = name ? name :
"";
136 const float name_width = overlay_text_width(mod_name);
138 draw_bold_overlay_text(draw_list, cursor, text_color, mod_name);
139 draw_overlay_text_at(
141 (ImVec2_c){cursor.x + name_width + gap, cursor.y},
146 igDummy((ImVec2_c){total_width, overlay_line_advance()});
150static bool uses_sdl_gpu() {
155static const char *backend_name() {
156 return uses_sdl_gpu() ?
"SDL_GPU" :
"OpenGL";
160static bool has_draw_data(
const ImDrawData *draw_data) {
161 return draw_data && draw_data->CmdListsCount > 0;
165static void backend_init(
SDL_Window *sdl_window, SDL_GPUDevice *device) {
166 if (uses_sdl_gpu()) {
167 ImGui_ImplSDL3_InitForSDLGPU(sdl_window);
169 SDL_GPUTextureFormat swapchain_format = SDL_GetGPUSwapchainTextureFormat(
173 CImGui_ImplSDLGPU3_InitInfo info = {
175 .ColorTargetFormat = swapchain_format,
176 .MSAASamples = SDL_GPU_SAMPLECOUNT_1,
177 .SwapchainComposition = SDL_GPU_SWAPCHAINCOMPOSITION_SDR,
178 .PresentMode = SDL_GPU_PRESENTMODE_VSYNC,
181 cImGui_ImplSDLGPU3_Init(&info);
185 ImGui_ImplSDL3_InitForOpenGL(sdl_window,
NULL);
186 ImGui_ImplOpenGL3_Init(
"#version 330");
190static void backend_shutdown() {
191 if (uses_sdl_gpu()) {
192 cImGui_ImplSDLGPU3_Shutdown();
196 ImGui_ImplOpenGL3_Shutdown();
202 SDL_GPUDevice *device,
207 "ImGui overlay init requested while already initialized; cleaning up stale "
213 backend_type = backend;
217 igCreateContext(
NULL);
219 ImGuiIO *io = igGetIO_Nil();
220 io->ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
222 igStyleColorsDark(
NULL);
224 ImGuiStyle *style = igGetStyle();
226 style->WindowRounding = 4.0f;
229 backend_init(window, device);
231 DTTR_LOG_INFO(
"ImGui overlay initialized (backend: %s)", backend_name());
237#define DTTR_IMGUI_MAX_PENDING_EVENTS 256u
238static SDL_Event pending_events[DTTR_IMGUI_MAX_PENDING_EVENTS];
239static uint32_t pending_event_count;
240static bool pending_event_overflow_warned;
243static void flush_pending_events() {
244 for (uint32_t i = 0; i < pending_event_count; i++) {
245 ImGui_ImplSDL3_ProcessEvent(&pending_events[i]);
248 pending_event_count = 0;
258 ImGui_ImplSDL3_Shutdown();
259 igDestroyContext(
NULL);
262 pending_event_count = 0;
263 pending_event_overflow_warned =
false;
274 if (pending_event_count < DTTR_IMGUI_MAX_PENDING_EVENTS) {
275 pending_events[pending_event_count++] = *
event;
278 if (!pending_event_overflow_warned) {
279 pending_event_overflow_warned =
true;
281 "ImGui pending event buffer full; delivering events immediately"
285 ImGui_ImplSDL3_ProcessEvent(
event);
289 ImGuiIO *io = igGetIO_Nil();
290 switch (
event->type) {
291 case SDL_EVENT_MOUSE_MOTION:
292 case SDL_EVENT_MOUSE_BUTTON_DOWN:
293 case SDL_EVENT_MOUSE_BUTTON_UP:
294 case SDL_EVENT_MOUSE_WHEEL:
295 return io->WantCaptureMouse;
296 case SDL_EVENT_KEY_DOWN:
297 case SDL_EVENT_KEY_UP:
298 case SDL_EVENT_TEXT_INPUT:
299 return io->WantCaptureKeyboard;
306static void backend_new_frame() {
307 if (uses_sdl_gpu()) {
308 cImGui_ImplSDLGPU3_NewFrame();
310 ImGui_ImplOpenGL3_NewFrame();
315static void new_frame() {
316 flush_pending_events();
318 ImGui_ImplSDL3_NewFrame();
325static void new_frame_no_input(uint32_t
w, uint32_t
h) {
328 ImGuiIO *io = igGetIO_Nil();
329 io->DisplaySize = (ImVec2_c){(
float)
w, (
float)
h};
334static ImDrawData *render_game_frame(uint32_t
w, uint32_t
h) {
335 new_frame_no_input(
w,
h);
340 .scale = (
float)
h / 480.0f,
346 return igGetDrawData();
351 const float game_scale =
ctx->scale > 0.0f ?
ctx->scale : 1.0f;
353 const float margin = 6.0f * game_scale * desktop_scale;
354 const ImVec2_c pos = {
356 (
float)
ctx->game_y + margin,
359 const ImVec2_c pivot = {0.0f, 0.0f};
361 igSetNextWindowPos(pos, ImGuiCond_Always, pivot);
362 igPushStyleVar_Float(ImGuiStyleVar_WindowBorderSize, 0.0f);
363 igPushStyleVar_Float(ImGuiStyleVar_WindowRounding, 0.0f);
364 igPushStyleVar_Vec2(ImGuiStyleVar_WindowPadding, (ImVec2_c){0.0f, 0.0f});
366 const ImGuiWindowFlags
flags = ImGuiWindowFlags_NoDecoration
367 | ImGuiWindowFlags_NoBackground
368 | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoNav
369 | ImGuiWindowFlags_NoMove
370 | ImGuiWindowFlags_NoSavedSettings
371 | ImGuiWindowFlags_AlwaysAutoResize;
373 ImGuiIO *io = igGetIO_Nil();
374 ImFont *font = io ? io->FontDefault :
NULL;
381 const ImGuiStyle *style = igGetStyle();
382 const float style_font_size = style ? style->FontSizeBase : 0.0f;
384 const float font_size = font->LegacySize > 0.0f ? font->LegacySize
385 : modding_badge_default_font_size;
387 const float base_font_size = style_font_size > 0.0f ? style_font_size : font_size;
389 const float scaled_font_size = base_font_size * modding_badge_font_factor
392 const float badge_font_size = scaled_font_size > modding_badge_min_font_size
394 : modding_badge_min_font_size;
396 igPushFont(font, badge_font_size);
400 float rows_width = 0.0f;
401 const float gap = overlay_mod_gap();
403 for (
size_t i = 0; i < loaded_count; i++) {
405 mod_overlay_elapsed_text(seconds,
sizeof(seconds), i);
408 const float row_width = overlay_text_width(name ? name :
"") + gap
409 + overlay_text_width(seconds);
410 rows_width = fmaxf(rows_width, row_width);
413 const float text_width = fmaxf(hot_reload_width(), rows_width);
415 igSetNextWindowContentSize((ImVec2_c){text_width, 0.0f});
417 if (igBegin(
"##modding_overlay",
NULL,
flags)) {
418 draw_hot_reload_header(text_width);
420 for (
size_t i = 0; i < loaded_count; i++) {
422 mod_overlay_elapsed_text(seconds,
sizeof(seconds), i);
425 draw_mod_overlay_row(name, seconds, gap, text_width);
428 igDummy((ImVec2_c){text_width, overlay_line_height() - overlay_line_advance()});
440static ImDrawData *render_overlay_frame(
455 .scale = (
float)game_h / 480.0f,
461 draw_modding_overlay(&
ctx);
465 return igGetDrawData();
469static void current_display_size(uint32_t *width, uint32_t *height) {
470 ImGuiIO *io = igGetIO_Nil();
471 *width = (uint32_t)io->DisplaySize.x;
472 *height = (uint32_t)io->DisplaySize.y;
476static void submit_sdl3gpu(
477 ImDrawData *draw_data,
478 SDL_GPUCommandBuffer *cmd,
481 if (!has_draw_data(draw_data)) {
485 cImGui_ImplSDLGPU3_PrepareDrawData(draw_data, cmd);
487 SDL_GPUColorTargetInfo color_target = {
489 .load_op = SDL_GPU_LOADOP_LOAD,
490 .store_op = SDL_GPU_STOREOP_STORE,
493 SDL_GPURenderPass *pass = SDL_BeginGPURenderPass(cmd, &color_target, 1,
NULL);
498 cImGui_ImplSDLGPU3_RenderDrawData(draw_data, cmd, pass);
499 SDL_EndGPURenderPass(pass);
503static void submit_opengl(ImDrawData *draw_data) {
504 if (!has_draw_data(draw_data)) {
508 ImGui_ImplOpenGL3_RenderDrawData(draw_data);
513 SDL_GPUCommandBuffer *cmd,
514 SDL_GPUTexture *render_target,
522 submit_sdl3gpu(render_game_frame(
w,
h), cmd, render_target);
533 current_display_size(&width, &height);
534 submit_opengl(render_game_frame(width, height));
539 SDL_GPUCommandBuffer *cmd,
540 SDL_GPUTexture *swapchain_tex,
549 render_overlay_frame(swap_w, swap_h, game_x, game_y, game_w, game_h),
564 current_display_size(&width, &height);
565 submit_opengl(render_overlay_frame(width, height, game_x, game_y, game_w, game_h));
DTTR_Graphics_COM_Direct3DDevice7 DWORD block DTTR_Graphics_COM_Direct3DDevice7 DWORD block DTTR_Graphics_COM_Direct3DDevice7 void void void void DWORD f DTTR_Graphics_COM_Direct3DDevice7 DWORD idx float
const DTTR_BackendState * state
DTTR_Graphics_COM_Direct3DDevice7 void DWORD flags DWORD void DWORD DWORD color
DTTR_Graphics_COM_Direct3DDevice7 void DWORD flags DWORD void DWORD flags
DTTR_Graphics_COM_DirectDraw7 *self DWORD DWORD h
DTTR_Graphics_COM_DirectDraw7 *self DWORD w
void void DWORD HANDLE event
DTTR_Graphics_COM_DirectDrawSurface7 DWORD flags void NULL
DTTR_Graphics_COM_DirectDrawSurface7 DWORD DWORD void void DWORD flags DTTR_Graphics_COM_DirectDrawSurface7 void void *cb void * target
static float DTTR_ImGui_GetCurrentDesktopScale(const DTTR_ImGuiDesktopScaleState *state)
static bool DTTR_ImGui_ApplyWindowDesktopScale(DTTR_ImGuiDesktopScaleState *state, SDL_Window *window)
#define DTTR_LOG_WARN(...)
#define DTTR_LOG_INFO(...)
union SDL_Event SDL_Event
struct SDL_Window SDL_Window
void dttr_imgui_render_game_sdl3gpu(SDL_GPUCommandBuffer *cmd, SDL_GPUTexture *render_target, uint32_t w, uint32_t h)
void dttr_imgui_cleanup()
void dttr_imgui_render_sdl3gpu(SDL_GPUCommandBuffer *cmd, SDL_GPUTexture *swapchain_tex, uint32_t swap_w, uint32_t swap_h, uint32_t game_x, uint32_t game_y, uint32_t game_w, uint32_t game_h)
void dttr_imgui_render_opengl(uint32_t game_x, uint32_t game_y, uint32_t game_w, uint32_t game_h)
bool dttr_imgui_process_event(const SDL_Event *event)
void dttr_imgui_init(SDL_Window *window, SDL_GPUDevice *device, DTTR_BackendType backend)
void dttr_imgui_render_game_opengl()
void dttr_mods_render_game(const DTTR_Mods_RenderGameContext *ctx)
void dttr_mods_render(const DTTR_Mods_RenderContext *ctx)
void dttr_mods_imgui_end(const DTTR_Mods_RenderContext *ctx)
size_t dttr_mods_loaded_count()
void dttr_mods_imgui_begin(const DTTR_Mods_RenderContext *ctx)
bool dttr_mods_has_render_game()
DWORD dttr_mods_loaded_elapsed_ms(size_t index)
const char * dttr_mods_loaded_name(size_t index)
bool dttr_mods_hot_reload_enabled()