diff --git a/Core/CMakeLists.txt b/Core/CMakeLists.txt index ccf2513e5e6..5d69b54edd7 100644 --- a/Core/CMakeLists.txt +++ b/Core/CMakeLists.txt @@ -24,6 +24,7 @@ target_link_libraries(corei_always INTERFACE corei_libraries_include resources ) + target_link_libraries(corei_always_no_pch INTERFACE core_config core_utility_no_pch diff --git a/Core/Libraries/CMakeLists.txt b/Core/Libraries/CMakeLists.txt index 1658bfbb9ee..51d2caf42a6 100644 --- a/Core/Libraries/CMakeLists.txt +++ b/Core/Libraries/CMakeLists.txt @@ -10,3 +10,8 @@ add_subdirectory(Source/debug) add_subdirectory(Source/EABrowserDispatch) add_subdirectory(Source/EABrowserEngine) add_subdirectory(Source/Compression) + +# Imgui library +if (RTS_BUILD_OPTION_IMGUI) + add_subdirectory(Source/ImGui) +endif () \ No newline at end of file diff --git a/Core/Libraries/Source/ImGui/CMakeLists.txt b/Core/Libraries/Source/ImGui/CMakeLists.txt new file mode 100644 index 00000000000..e29db8aa60d --- /dev/null +++ b/Core/Libraries/Source/ImGui/CMakeLists.txt @@ -0,0 +1,62 @@ +FetchContent_Declare( + imgui + GIT_REPOSITORY https://github.com/ocornut/imgui.git + GIT_TAG 791ad9b82db44ada9fedb3e26b2d900974ac0959 + SYSTEM +) + +FetchContent_MakeAvailable(imgui) + +# Main IMGUI sources we are going to need +set(IMGUI_BASE_SRCS + "${imgui_SOURCE_DIR}/imgui.cpp" + "${imgui_SOURCE_DIR}/imgui_draw.cpp" + "${imgui_SOURCE_DIR}/imgui_tables.cpp" + "${imgui_SOURCE_DIR}/imgui_widgets.cpp" + "${imgui_SOURCE_DIR}/imgui_demo.cpp" +) + +# Main Win32 DX8 specific sources we are going to need we can specify more if we need extra platforms +set(IMGUI_WIN32_DX8_ALL_SRCS + "${IMGUI_BASE_SRCS}" + "${imgui_SOURCE_DIR}/backends/imgui_impl_win32.cpp" + "${CMAKE_SOURCE_DIR}/Core/Libraries/Source/ImGui/dx8_backend/imgui_impl_dx8.cpp" +) + +# All Include directories +set(IMGUI_INCLUDE_DIRS + "${imgui_SOURCE_DIR}" + "${imgui_SOURCE_DIR}/backends" + # DX8 override. Remove the following once we are using a standard backend + "${CMAKE_SOURCE_DIR}/Core/Libraries/Source/ImGui/dx8_backend" +) + +# start target build section +# we currently have a hard dependency on dx8 and win32 api +if (WIN32) + # for now we only need it in debug configurations + if ( NOT ( (CMAKE_BUILD_TYPE STREQUAL "Debug") OR RTS_BUILD_OPTION_DEBUG ) ) + MESSAGE(FATAL_ERROR "ImGui is currently only available in Debug build modes") + endif () + + MESSAGE(STATUS "Enabling ImGui") + + add_library(lib_imgui STATIC ${IMGUI_WIN32_DX8_ALL_SRCS} + "${CMAKE_CURRENT_LIST_DIR}/wrapper/ImGuiFrameManager.cpp" + ) + target_include_directories(lib_imgui + PUBLIC ${IMGUI_INCLUDE_DIRS} + PUBLIC "${CMAKE_CURRENT_LIST_DIR}/wrapper") + target_link_libraries(lib_imgui PRIVATE d3d8lib) + + # use our own imconfig.h + target_compile_definitions(lib_imgui + PRIVATE IMGUI_DISABLE_DEFAULT_IMCONFIG + PRIVATE IMGUI_USER_CONFIG="${CMAKE_CURRENT_LIST_DIR}/imconfig.h" + INTERFACE RTS_HAS_IMGUI + ) +else () + # currently only WIN32 DX is supported + MESSAGE(FATAL_ERROR "Non-Windows platforms currently not supported for ImGui") +endif () +# end target build section diff --git a/Core/Libraries/Source/ImGui/dx8_backend/imgui_impl_dx8.cpp b/Core/Libraries/Source/ImGui/dx8_backend/imgui_impl_dx8.cpp new file mode 100644 index 00000000000..accaec963c4 --- /dev/null +++ b/Core/Libraries/Source/ImGui/dx8_backend/imgui_impl_dx8.cpp @@ -0,0 +1,626 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2014 - 2025 Omar Cornut (Based on dx9 ImGui Backend) + * Copyright (c) 2025 Meigyoku-Thmn and others + * Copyright (c) 2026 TheSuperHackers + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/** + * @file imgui_impl_dx8.cpp + * @brief DirectX8 renderer backend for Dear ImGui, reverse-engineered from the official DX9 backend + */ + +// dear imgui: Renderer Backend for DirectX8 +// This needs to be used along with a Platform Backend (e.g. Win32) + +// Implemented features: +// [X] Renderer: User texture binding. Use 'LPDIRECT3DTEXTURE8' as ImTextureID. Read the FAQ about ImTextureID! +// [X] Renderer: Large meshes support (64k+ vertices) with 16-bit indices. + +// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. +// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build +// the backends you need. Learn about Dear ImGui: +// - FAQ https://dearimgui.com/faq +// - Getting Started https://dearimgui.com/getting-started +// - Documentation https://dearimgui.com/docs (same as your local docs/ folder). +// - Introduction, links and more at the top of imgui.cpp + +#include "imgui_impl_dx8.h" +#include + +// DirectX +#include "d3d8.h" + +// DirectX data +struct ImGui_ImplDX8_Data +{ + LPDIRECT3DDEVICE8 pd3dDevice; + LPDIRECT3DVERTEXBUFFER8 pVB; + LPDIRECT3DINDEXBUFFER8 pIB; + LPDIRECT3DVERTEXBUFFER8 maskVB; + LPDIRECT3DINDEXBUFFER8 maskIB; + LPDIRECT3DTEXTURE8 FontTexture; + int VertexBufferSize; + int IndexBufferSize; + IDirect3DSurface8 *DepthBuffer; + IDirect3DSurface8 *realDepthStencilBuffer; + + ImGui_ImplDX8_Data() + { + memset((void *)this, 0, sizeof(*this)); + VertexBufferSize = 5000; + IndexBufferSize = 10000; + } +}; + +struct CUSTOMVERTEX +{ + float pos[3]; + D3DCOLOR col; + float uv[2]; +}; +#define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZ | D3DFVF_DIFFUSE | D3DFVF_TEX1) + +#ifdef IMGUI_USE_BGRA_PACKED_COLOR +#define IMGUI_COL_TO_DX8_ARGB(_COL) (_COL) +#else +#define IMGUI_COL_TO_DX8_ARGB(_COL) (((_COL) & 0xFF00FF00) | (((_COL) & 0xFF0000) >> 16) | (((_COL) & 0xFF) << 16)) +#endif + +ImGui_ImplDX8_Data *ImGui_ImplDX8_GetBackendData() +{ + return ImGui::GetCurrentContext() ? (ImGui_ImplDX8_Data *)ImGui::GetIO().BackendRendererUserData : nullptr; +} + +// Functions +void ImGui_ImplDX8_SetupRenderState(ImDrawData *draw_data) +{ + ImGui_ImplDX8_Data *bd = ImGui_ImplDX8_GetBackendData(); + + IDirect3DSurface8 *pSurface{}; + D3DSURFACE_DESC d3dSize{}; + if (SUCCEEDED(bd->pd3dDevice->GetRenderTarget(&pSurface)) && SUCCEEDED(pSurface->GetDesc(&d3dSize))) + { + // Setup viewport + D3DVIEWPORT8 vp{}; + vp.X = vp.Y = 0; + vp.Width = d3dSize.Width; + vp.Height = d3dSize.Height; + vp.MinZ = 0.0f; + vp.MaxZ = 1.0f; + bd->pd3dDevice->SetViewport(&vp); + } + if (pSurface) + { + pSurface->Release(); + pSurface = nullptr; + } + + // Setup render state: fixed-pipeline, alpha-blending, no face culling, no depth testing, shade mode (for gradient), + // bilinear sampling. Check result to prevent crash when restoring depth buffer with SetRenderTarget + if (bd->pd3dDevice->GetDepthStencilSurface(&bd->realDepthStencilBuffer) != D3D_OK) + { + bd->realDepthStencilBuffer = nullptr; + } + bd->pd3dDevice->SetRenderTarget(nullptr, bd->DepthBuffer); + bd->pd3dDevice->SetPixelShader(0); + bd->pd3dDevice->SetVertexShader(D3DFVF_CUSTOMVERTEX); + bd->pd3dDevice->SetRenderState(D3DRS_FILLMODE, D3DFILL_SOLID); + bd->pd3dDevice->SetRenderState(D3DRS_SHADEMODE, D3DSHADE_GOURAUD); + bd->pd3dDevice->SetRenderState(D3DRS_ZWRITEENABLE, FALSE); + bd->pd3dDevice->SetRenderState(D3DRS_ALPHATESTENABLE, FALSE); + bd->pd3dDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE); + bd->pd3dDevice->SetRenderState(D3DRS_ZENABLE, FALSE); + bd->pd3dDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE); + bd->pd3dDevice->SetRenderState(D3DRS_BLENDOP, D3DBLENDOP_ADD); + bd->pd3dDevice->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA); + bd->pd3dDevice->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA); + bd->pd3dDevice->SetRenderState(D3DRS_FOGENABLE, FALSE); + bd->pd3dDevice->SetRenderState(D3DRS_RANGEFOGENABLE, FALSE); + bd->pd3dDevice->SetRenderState(D3DRS_SPECULARENABLE, FALSE); + bd->pd3dDevice->SetRenderState(D3DRS_CLIPPING, TRUE); + bd->pd3dDevice->SetRenderState(D3DRS_LIGHTING, FALSE); + bd->pd3dDevice->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE); + bd->pd3dDevice->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE); + bd->pd3dDevice->SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_DIFFUSE); + bd->pd3dDevice->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_MODULATE); + bd->pd3dDevice->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE); + bd->pd3dDevice->SetTextureStageState(0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE); + bd->pd3dDevice->SetTextureStageState(1, D3DTSS_COLOROP, D3DTOP_DISABLE); + bd->pd3dDevice->SetTextureStageState(1, D3DTSS_ALPHAOP, D3DTOP_DISABLE); + bd->pd3dDevice->SetTextureStageState(0, D3DTSS_MINFILTER, D3DTEXF_LINEAR); + bd->pd3dDevice->SetTextureStageState(0, D3DTSS_MAGFILTER, D3DTEXF_LINEAR); + // This below is needed for the entire Touhou series from Touhou 6! + bd->pd3dDevice->SetTextureStageState(0, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_DISABLE); + + // Setup orthographic projection matrix + // Our visible imgui space lies from draw_data->DisplayPos (top left) to + // draw_data->DisplayPos+data_data->DisplaySize (bottom right). DisplayPos is (0,0) for single viewport apps. Being + // agnostic of whether or can be used, we aren't relying on + // D3DXMatrixIdentity()/D3DXMatrixOrthoOffCenterLH() or + // DirectX::XMMatrixIdentity()/DirectX::XMMatrixOrthographicOffCenterLH() + { + float L = draw_data->DisplayPos.x + 0.5f; + float R = draw_data->DisplayPos.x + d3dSize.Width + 0.5f; + float T = draw_data->DisplayPos.y + 0.5f; + float B = draw_data->DisplayPos.y + d3dSize.Height + 0.5f; + D3DMATRIX mat_identity = {{{ + 1.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 1.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 1.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 1.0f, + }}}; + D3DMATRIX mat_projection = {{{ + 2.0f / (R - L), + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 2.0f / (T - B), + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.5f, + 0.0f, + (L + R) / (L - R), + (T + B) / (B - T), + 0.5f, + 1.0f, + }}}; + bd->pd3dDevice->SetTransform(D3DTS_WORLD, &mat_identity); + bd->pd3dDevice->SetTransform(D3DTS_VIEW, &mat_identity); + bd->pd3dDevice->SetTransform(D3DTS_PROJECTION, &mat_projection); + } +} + +void build_mask_vbuffer(const RECT *rect) +{ + ImGui_ImplDX8_Data *bd = ImGui_ImplDX8_GetBackendData(); + CUSTOMVERTEX *vtx_dst{}; + // Check Lock result to prevent null pointer dereference + if (bd->maskVB->Lock(0, (UINT)(6 * sizeof(CUSTOMVERTEX)), (BYTE **)&vtx_dst, 0) != D3D_OK) + { + return; + } + vtx_dst[0].pos[0] = (float)rect->left; + vtx_dst[0].pos[1] = (float)rect->bottom; + vtx_dst[0].pos[2] = 0; + vtx_dst[1].pos[0] = (float)rect->left; + vtx_dst[1].pos[1] = (float)rect->top; + vtx_dst[1].pos[2] = 0; + vtx_dst[2].pos[0] = (float)rect->right; + vtx_dst[2].pos[1] = (float)rect->top; + vtx_dst[2].pos[2] = 0; + vtx_dst[3].pos[0] = (float)rect->left; + vtx_dst[3].pos[1] = (float)rect->bottom; + vtx_dst[3].pos[2] = 0; + vtx_dst[4].pos[0] = (float)rect->right; + vtx_dst[4].pos[1] = (float)rect->top; + vtx_dst[4].pos[2] = 0; + vtx_dst[5].pos[0] = (float)rect->right; + vtx_dst[5].pos[1] = (float)rect->bottom; + vtx_dst[5].pos[2] = 0; + vtx_dst[0].col = 0xFFFFFFFF; + vtx_dst[1].col = 0xFFFFFFFF; + vtx_dst[2].col = 0xFFFFFFFF; + vtx_dst[3].col = 0xFFFFFFFF; + vtx_dst[4].col = 0xFFFFFFFF; + vtx_dst[5].col = 0xFFFFFFFF; + vtx_dst[0].uv[0] = 0; + vtx_dst[0].uv[1] = 0; + vtx_dst[1].uv[0] = 0; + vtx_dst[1].uv[1] = 0; + vtx_dst[2].uv[0] = 0; + vtx_dst[2].uv[1] = 0; + vtx_dst[3].uv[0] = 0; + vtx_dst[3].uv[1] = 0; + vtx_dst[4].uv[0] = 0; + vtx_dst[4].uv[1] = 0; + vtx_dst[5].uv[0] = 0; + vtx_dst[5].uv[1] = 0; + bd->maskVB->Unlock(); +} + +// Render function. +void ImGui_ImplDX8_RenderDrawData(ImDrawData *draw_data) +{ + // Avoid rendering when minimized + if (draw_data->DisplaySize.x <= 0.0f || draw_data->DisplaySize.y <= 0.0f) + return; + + // Create and grow buffers if needed + ImGui_ImplDX8_Data *bd = ImGui_ImplDX8_GetBackendData(); + if (!bd->pVB || bd->VertexBufferSize < draw_data->TotalVtxCount) + { + if (bd->pVB) + { + bd->pVB->Release(); + bd->pVB = nullptr; + } + bd->VertexBufferSize = draw_data->TotalVtxCount + 5000; + if (bd->pd3dDevice->CreateVertexBuffer(bd->VertexBufferSize * sizeof(CUSTOMVERTEX), + D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY, D3DFVF_CUSTOMVERTEX, + D3DPOOL_DEFAULT, &bd->pVB) < 0) + return; + } + if (!bd->pIB || bd->IndexBufferSize < draw_data->TotalIdxCount) + { + if (bd->pIB) + { + bd->pIB->Release(); + bd->pIB = nullptr; + } + bd->IndexBufferSize = draw_data->TotalIdxCount + 10000; + if (bd->pd3dDevice->CreateIndexBuffer( + bd->IndexBufferSize * sizeof(ImDrawIdx), D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY, + sizeof(ImDrawIdx) == 2 ? D3DFMT_INDEX16 : D3DFMT_INDEX32, D3DPOOL_DEFAULT, &bd->pIB) < 0) + return; + } + + if (!bd->maskVB && !bd->maskIB) + { + if (bd->pd3dDevice->CreateVertexBuffer(6 * sizeof(CUSTOMVERTEX), D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY, + D3DFVF_CUSTOMVERTEX, D3DPOOL_DEFAULT, &bd->maskVB) < 0) + return; + if (bd->pd3dDevice->CreateIndexBuffer(6, D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY, + sizeof(ImDrawIdx) == 2 ? D3DFMT_INDEX16 : D3DFMT_INDEX32, D3DPOOL_DEFAULT, + &bd->maskIB) < 0) + return; + ImDrawIdx *idx_dst{}; + // Check Lock result to prevent null pointer dereference + if (bd->maskIB->Lock(0, 6 * sizeof(ImDrawIdx), (BYTE **)&idx_dst, D3DLOCK_DISCARD) != D3D_OK) + { + return; + } + idx_dst[0] = 0; + idx_dst[1] = 1; + idx_dst[2] = 2; + idx_dst[3] = 0; + idx_dst[4] = 2; + idx_dst[5] = 3; + bd->maskIB->Unlock(); + } + + // Backup the DX8 state + DWORD d3d8_state_block; + if (bd->pd3dDevice->CreateStateBlock(D3DSBT_ALL, &d3d8_state_block) < 0) + return; + if (bd->pd3dDevice->CaptureStateBlock(d3d8_state_block) < 0) + { + bd->pd3dDevice->DeleteStateBlock(d3d8_state_block); + return; + } + + // Backup the DX8 transform + D3DMATRIX last_world, last_view, last_projection; + bd->pd3dDevice->GetTransform(D3DTS_WORLD, &last_world); + bd->pd3dDevice->GetTransform(D3DTS_VIEW, &last_view); + bd->pd3dDevice->GetTransform(D3DTS_PROJECTION, &last_projection); + + // Allocate buffers + CUSTOMVERTEX *vtx_dst{}; + ImDrawIdx *idx_dst{}; + if (bd->pVB->Lock(0, (UINT)(draw_data->TotalVtxCount * sizeof(CUSTOMVERTEX)), (BYTE **)&vtx_dst, D3DLOCK_DISCARD) < + 0) + { + bd->pd3dDevice->DeleteStateBlock(d3d8_state_block); + return; + } + if (bd->pIB->Lock(0, (UINT)(draw_data->TotalIdxCount * sizeof(ImDrawIdx)), (BYTE **)&idx_dst, D3DLOCK_DISCARD) < 0) + { + bd->pVB->Unlock(); + bd->pd3dDevice->DeleteStateBlock(d3d8_state_block); + return; + } + + // Copy and convert all vertices into a single contiguous buffer, convert colors to DX8 default format. + for (int n = 0; n < draw_data->CmdListsCount; n++) + { + const ImDrawList *cmd_list = draw_data->CmdLists[n]; + const ImDrawVert *vtx_src = cmd_list->VtxBuffer.Data; + for (int i = 0; i < cmd_list->VtxBuffer.Size; i++) + { + vtx_dst->pos[0] = vtx_src->pos.x; + vtx_dst->pos[1] = vtx_src->pos.y; + vtx_dst->pos[2] = 0.0f; + vtx_dst->col = IMGUI_COL_TO_DX8_ARGB(vtx_src->col); + vtx_dst->uv[0] = vtx_src->uv.x; + vtx_dst->uv[1] = vtx_src->uv.y; + vtx_dst++; + vtx_src++; + } + memcpy(idx_dst, cmd_list->IdxBuffer.Data, cmd_list->IdxBuffer.Size * sizeof(ImDrawIdx)); + idx_dst += cmd_list->IdxBuffer.Size; + } + bd->pVB->Unlock(); + bd->pIB->Unlock(); + bd->pd3dDevice->SetStreamSource(0, bd->pVB, sizeof(CUSTOMVERTEX)); + bd->pd3dDevice->SetIndices(bd->pIB, 0); + bd->pd3dDevice->SetVertexShader(D3DFVF_CUSTOMVERTEX); + + // Setup desired DX state + ImGui_ImplDX8_SetupRenderState(draw_data); + + // Render command lists + // (Because we merged all buffers into a single one, we maintain our own offset into them) + int global_vtx_offset = 0; + int global_idx_offset = 0; + ImVec2 clip_off = draw_data->DisplayPos; + for (int n = 0; n < draw_data->CmdListsCount; n++) + { + const ImDrawList *cmd_list = draw_data->CmdLists[n]; + for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++) + { + const ImDrawCmd *pcmd = &cmd_list->CmdBuffer[cmd_i]; + if (pcmd->UserCallback != nullptr) + { + // User callback, registered via ImDrawList::AddCallback() + // (ImDrawCallback_ResetRenderState is a special callback value used by the user to request the renderer + // to reset render state.) + if (pcmd->UserCallback == ImDrawCallback_ResetRenderState) + ImGui_ImplDX8_SetupRenderState(draw_data); + else + pcmd->UserCallback(cmd_list, pcmd); + } + else + { + // Project clipping rectangles into framebuffer space + ImVec2 clip_min(pcmd->ClipRect.x - clip_off.x, pcmd->ClipRect.y - clip_off.y); + ImVec2 clip_max(pcmd->ClipRect.z - clip_off.x, pcmd->ClipRect.w - clip_off.y); + if (clip_max.x <= clip_min.x || clip_max.y <= clip_min.y) + continue; + + // Apply clipping rectangle, Bind texture, Draw + const RECT r = {(LONG)clip_min.x, (LONG)clip_min.y, (LONG)clip_max.x, (LONG)clip_max.y}; + const LPDIRECT3DTEXTURE8 texture = (LPDIRECT3DTEXTURE8)pcmd->GetTexID(); + bd->pd3dDevice->SetTexture(0, texture); + build_mask_vbuffer(&r); + bd->pd3dDevice->SetRenderState(D3DRS_COLORWRITEENABLE, 0); + bd->pd3dDevice->SetRenderState(D3DRS_ZENABLE, true); + bd->pd3dDevice->SetRenderState(D3DRS_STENCILENABLE, true); + bd->pd3dDevice->SetRenderState(D3DRS_STENCILWRITEMASK, 0xFF); + bd->pd3dDevice->SetRenderState(D3DRS_STENCILMASK, 0xFF); + bd->pd3dDevice->SetRenderState(D3DRS_STENCILFUNC, D3DCMP_ALWAYS); + bd->pd3dDevice->SetRenderState(D3DRS_ZFUNC, D3DCMP_ALWAYS); + bd->pd3dDevice->SetRenderState(D3DRS_STENCILPASS, D3DSTENCILOP_REPLACE); + bd->pd3dDevice->SetRenderState(D3DRS_STENCILREF, 0xFF); + bd->pd3dDevice->Clear(0, nullptr, D3DCLEAR_STENCIL, 0, 1.0f, 0); + bd->pd3dDevice->SetStreamSource(0, bd->maskVB, sizeof(CUSTOMVERTEX)); + bd->pd3dDevice->SetIndices(bd->maskIB, 0); + bd->pd3dDevice->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, 0, 4, 0, 2); + bd->pd3dDevice->DrawPrimitive(D3DPT_TRIANGLELIST, 0, 2); + bd->pd3dDevice->SetStreamSource(0, bd->pVB, sizeof(CUSTOMVERTEX)); + bd->pd3dDevice->SetIndices(bd->pIB, global_vtx_offset); + bd->pd3dDevice->SetRenderState(D3DRS_COLORWRITEENABLE, 0xF); + bd->pd3dDevice->SetRenderState(D3DRS_ZENABLE, false); + bd->pd3dDevice->SetRenderState(D3DRS_STENCILENABLE, true); + bd->pd3dDevice->SetRenderState(D3DRS_STENCILWRITEMASK, 0); + bd->pd3dDevice->SetRenderState(D3DRS_STENCILMASK, 0xFF); + bd->pd3dDevice->SetRenderState(D3DRS_STENCILFUNC, D3DCMP_EQUAL); + bd->pd3dDevice->SetRenderState(D3DRS_STENCILFAIL, D3DSTENCILOP_KEEP); + bd->pd3dDevice->SetRenderState(D3DRS_STENCILPASS, D3DSTENCILOP_KEEP); + bd->pd3dDevice->SetRenderState(D3DRS_STENCILREF, 0xFF); + bd->pd3dDevice->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, 0, (UINT)cmd_list->VtxBuffer.Size, + pcmd->IdxOffset + global_idx_offset, pcmd->ElemCount / 3); + } + } + global_idx_offset += cmd_list->IdxBuffer.Size; + global_vtx_offset += cmd_list->VtxBuffer.Size; + } + + // Restore the DX8 transform + bd->pd3dDevice->SetTransform(D3DTS_WORLD, &last_world); + bd->pd3dDevice->SetTransform(D3DTS_VIEW, &last_view); + bd->pd3dDevice->SetTransform(D3DTS_PROJECTION, &last_projection); + + // Restore the DX8 state + bd->pd3dDevice->SetRenderTarget(nullptr, bd->realDepthStencilBuffer); + if (bd->realDepthStencilBuffer) + { + bd->realDepthStencilBuffer->Release(); + bd->realDepthStencilBuffer = nullptr; + } + bd->pd3dDevice->ApplyStateBlock(d3d8_state_block); + bd->pd3dDevice->DeleteStateBlock(d3d8_state_block); +} + +bool ImGui_ImplDX8_Init(IDirect3DDevice8 *device) +{ + ImGuiIO &io = ImGui::GetIO(); + IM_ASSERT(io.BackendRendererUserData == nullptr && "Already initialized a renderer backend!"); + + // Setup backend capabilities flags + ImGui_ImplDX8_Data *bd = IM_NEW(ImGui_ImplDX8_Data)(); + io.BackendRendererUserData = (void *)bd; + io.BackendRendererName = "imgui_impl_dx8"; + io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing + // for large meshes. + + bd->pd3dDevice = device; + bd->pd3dDevice->AddRef(); + + return true; +} + +void ImGui_ImplDX8_Shutdown() +{ + ImGui_ImplDX8_Data *bd = ImGui_ImplDX8_GetBackendData(); + IM_ASSERT(bd != nullptr && "No renderer backend to shutdown, or already shutdown?"); + ImGuiIO &io = ImGui::GetIO(); + + ImGui_ImplDX8_InvalidateDeviceObjects(); + if (bd->pd3dDevice) + bd->pd3dDevice->Release(); + io.BackendRendererName = nullptr; + io.BackendRendererUserData = nullptr; + io.BackendFlags &= ~ImGuiBackendFlags_RendererHasVtxOffset; + IM_DELETE(bd); +} + +bool ImGui_ImplDX8_CreateFontsTexture() +{ + // Build texture atlas + ImGuiIO &io = ImGui::GetIO(); + ImGui_ImplDX8_Data *bd = ImGui_ImplDX8_GetBackendData(); + unsigned char *pixels; + int width, height, bytes_per_pixel; + io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height, &bytes_per_pixel); + + // Convert RGBA32 to BGRA32 +#ifndef IMGUI_USE_BGRA_PACKED_COLOR + if (io.Fonts->TexPixelsUseColors) + { + ImU32 *dst_start = (ImU32 *)ImGui::MemAlloc((size_t)width * height * bytes_per_pixel); + for (ImU32 *src = (ImU32 *)pixels, *dst = dst_start, *dst_end = dst_start + (size_t)width * height; + dst < dst_end; src++, dst++) + *dst = IMGUI_COL_TO_DX8_ARGB(*src); + pixels = (unsigned char *)dst_start; + } +#endif + + // Upload texture to graphics system + bd->FontTexture = nullptr; + if (bd->pd3dDevice->CreateTexture(width, height, 1, D3DUSAGE_DYNAMIC, D3DFMT_A8R8G8B8, D3DPOOL_DEFAULT, + &bd->FontTexture) < 0) + return false; + D3DLOCKED_RECT tex_locked_rect; + if (bd->FontTexture->LockRect(0, &tex_locked_rect, nullptr, 0) != D3D_OK) + return false; + for (int y = 0; y < height; y++) + memcpy((unsigned char *)tex_locked_rect.pBits + (size_t)tex_locked_rect.Pitch * y, + pixels + (size_t)width * bytes_per_pixel * y, (size_t)width * bytes_per_pixel); + bd->FontTexture->UnlockRect(0); + + // Store our identifier + io.Fonts->SetTexID((ImTextureID)bd->FontTexture); + +#ifndef IMGUI_USE_BGRA_PACKED_COLOR + if (io.Fonts->TexPixelsUseColors) + ImGui::MemFree(pixels); +#endif + + return true; +} + +bool ImGui_ImplD3D8_CreateDepthStencilBuffer() +{ + ImGui_ImplDX8_Data *bd = ImGui_ImplDX8_GetBackendData(); + if (bd->pd3dDevice == nullptr) + { + return false; + } + if (bd->DepthBuffer == nullptr) + { + IDirect3DSurface8 *realDepth; + D3DSURFACE_DESC sfcDesc; + + if (bd->pd3dDevice->GetDepthStencilSurface(&realDepth) != D3D_OK || !realDepth) + { + return false; + } + if (realDepth->GetDesc(&sfcDesc) != 0) + { + return false; + } + realDepth->Release(); + realDepth = nullptr; + if (bd->pd3dDevice->CreateDepthStencilSurface(sfcDesc.Width, sfcDesc.Height, D3DFMT_D24S8, D3DMULTISAMPLE_NONE, + &bd->DepthBuffer) != 0) + { + return false; + } + } + + return true; +} + +bool ImGui_ImplDX8_CreateDeviceObjects() +{ + ImGui_ImplDX8_Data *bd = ImGui_ImplDX8_GetBackendData(); + if (!bd || !bd->pd3dDevice) + return false; + if (!ImGui_ImplDX8_CreateFontsTexture()) + return false; + if (!ImGui_ImplD3D8_CreateDepthStencilBuffer()) + return false; + return true; +} + +void ImGui_ImplDX8_InvalidateDeviceObjects() +{ + ImGui_ImplDX8_Data *bd = ImGui_ImplDX8_GetBackendData(); + if (!bd || !bd->pd3dDevice) + return; + if (bd->pVB) + { + bd->pVB->Release(); + bd->pVB = nullptr; + } + if (bd->pIB) + { + bd->pIB->Release(); + bd->pIB = nullptr; + } + if (bd->maskVB) + { + bd->maskVB->Release(); + bd->maskVB = nullptr; + } + if (bd->maskIB) + { + bd->maskIB->Release(); + bd->maskIB = nullptr; + } + if (bd->DepthBuffer) + { + bd->DepthBuffer->Release(); + bd->DepthBuffer = nullptr; + } + if (bd->FontTexture) + { + bd->FontTexture->Release(); + bd->FontTexture = nullptr; + ImGui::GetIO().Fonts->SetTexID(0); + } // We copied bd->pFontTextureView to io.Fonts->TexID so let's clear that as well. +} + +void ImGui_ImplDX8_NewFrame() +{ + ImGui_ImplDX8_Data *bd = ImGui_ImplDX8_GetBackendData(); + IM_ASSERT(bd != nullptr && "Did you call ImGui_ImplDX8_Init()?"); + + if (!bd->FontTexture || !bd->DepthBuffer) + ImGui_ImplDX8_CreateDeviceObjects(); +} diff --git a/Core/Libraries/Source/ImGui/dx8_backend/imgui_impl_dx8.h b/Core/Libraries/Source/ImGui/dx8_backend/imgui_impl_dx8.h new file mode 100644 index 00000000000..b142abcf91c --- /dev/null +++ b/Core/Libraries/Source/ImGui/dx8_backend/imgui_impl_dx8.h @@ -0,0 +1,58 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2014 - 2025 Omar Cornut (Based on dx9 ImGui Backend) + * Copyright (c) 2025 Meigyoku-Thmn and others + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/** + * @file imgui_impl_dx8.h + * @brief DirectX8 renderer backend for Dear ImGui, reverse-engineered from the official DX9 backend + */ + +// dear imgui: Renderer Backend for DirectX8 +// This needs to be used along with a Platform Backend (e.g. Win32) + +// Implemented features: +// [X] Renderer: User texture binding. Use 'LPDIRECT3DTEXTURE8' as ImTextureID. Read the FAQ about ImTextureID! +// [X] Renderer: Large meshes support (64k+ vertices) with 16-bit indices. + +// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. +// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build +// the backends you need. Learn about Dear ImGui: +// - FAQ https://dearimgui.com/faq +// - Getting Started https://dearimgui.com/getting-started +// - Documentation https://dearimgui.com/docs (same as your local docs/ folder). +// - Introduction, links and more at the top of imgui.cpp + +#pragma once +#include // IMGUI_IMPL_API + +struct IDirect3DDevice8; + +IMGUI_IMPL_API bool ImGui_ImplDX8_Init(IDirect3DDevice8 *device); +IMGUI_IMPL_API void ImGui_ImplDX8_Shutdown(); +IMGUI_IMPL_API void ImGui_ImplDX8_NewFrame(); +IMGUI_IMPL_API void ImGui_ImplDX8_RenderDrawData(ImDrawData *draw_data); + +// Use if you want to reset your rendering device without losing Dear ImGui state. +IMGUI_IMPL_API bool ImGui_ImplDX8_CreateDeviceObjects(); +IMGUI_IMPL_API void ImGui_ImplDX8_InvalidateDeviceObjects(); diff --git a/Core/Libraries/Source/ImGui/imconfig.h b/Core/Libraries/Source/ImGui/imconfig.h new file mode 100644 index 00000000000..531b147f1f5 --- /dev/null +++ b/Core/Libraries/Source/ImGui/imconfig.h @@ -0,0 +1,147 @@ +//----------------------------------------------------------------------------- +// DEAR IMGUI COMPILE-TIME OPTIONS +// Runtime options (clipboard callbacks, enabling various features, etc.) can generally be set via the ImGuiIO structure. +// You can use ImGui::SetAllocatorFunctions() before calling ImGui::CreateContext() to rewire memory allocation functions. +//----------------------------------------------------------------------------- +// A) You may edit imconfig.h (and not overwrite it when updating Dear ImGui, or maintain a patch/rebased branch with your modifications to it) +// B) or '#define IMGUI_USER_CONFIG "my_imgui_config.h"' in your project and then add directives in your own file without touching this template. +//----------------------------------------------------------------------------- +// You need to make sure that configuration settings are defined consistently _everywhere_ Dear ImGui is used, which include the imgui*.cpp +// files but also _any_ of your code that uses Dear ImGui. This is because some compile-time options have an affect on data structures. +// Defining those options in imconfig.h will ensure every compilation unit gets to see the same data structure layouts. +// Call IMGUI_CHECKVERSION() from your .cpp file to verify that the data structures your files are using are matching the ones imgui.cpp is using. +//----------------------------------------------------------------------------- + +#pragma once + +//---- Define assertion handler. Defaults to calling assert(). +// - If your macro uses multiple statements, make sure is enclosed in a 'do { .. } while (0)' block so it can be used as a single statement. +// - Compiling with NDEBUG will usually strip out assert() to nothing, which is NOT recommended because we use asserts to notify of programmer mistakes. +//#define IM_ASSERT(_EXPR) MyAssert(_EXPR) +//#define IM_ASSERT(_EXPR) ((void)(_EXPR)) // Disable asserts + +//---- Define attributes of all API symbols declarations, e.g. for DLL under Windows +// Using Dear ImGui via a shared library is not recommended, because of function call overhead and because we don't guarantee backward nor forward ABI compatibility. +// - Windows DLL users: heaps and globals are not shared across DLL boundaries! You will need to call SetCurrentContext() + SetAllocatorFunctions() +// for each static/DLL boundary you are calling from. Read "Context and Memory Allocators" section of imgui.cpp for more details. +//#define IMGUI_API __declspec(dllexport) // MSVC Windows: DLL export +//#define IMGUI_API __declspec(dllimport) // MSVC Windows: DLL import +//#define IMGUI_API __attribute__((visibility("default"))) // GCC/Clang: override visibility when set is hidden + +//---- Don't define obsolete functions/enums/behaviors. Consider enabling from time to time after updating to clean your code of obsolete function/names. +//#define IMGUI_DISABLE_OBSOLETE_FUNCTIONS + +//---- Disable all of Dear ImGui or don't implement standard windows/tools. +// It is very strongly recommended to NOT disable the demo windows and debug tool during development. They are extremely useful in day to day work. Please read comments in imgui_demo.cpp. +//#define IMGUI_DISABLE // Disable everything: all headers and source files will be empty. +//#define IMGUI_DISABLE_DEMO_WINDOWS // Disable demo windows: ShowDemoWindow()/ShowStyleEditor() will be empty. +//#define IMGUI_DISABLE_DEBUG_TOOLS // Disable metrics/debugger and other debug tools: ShowMetricsWindow(), ShowDebugLogWindow() and ShowIDStackToolWindow() will be empty. + +//---- Don't implement some functions to reduce linkage requirements. +//#define IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS // [Win32] Don't implement default clipboard handler. Won't use and link with OpenClipboard/GetClipboardData/CloseClipboard etc. (user32.lib/.a, kernel32.lib/.a) +//#define IMGUI_ENABLE_WIN32_DEFAULT_IME_FUNCTIONS // [Win32] [Default with Visual Studio] Implement default IME handler (require imm32.lib/.a, auto-link for Visual Studio, -limm32 on command-line for MinGW) +//#define IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS // [Win32] [Default with non-Visual Studio compilers] Don't implement default IME handler (won't require imm32.lib/.a) +//#define IMGUI_DISABLE_WIN32_FUNCTIONS // [Win32] Won't use and link with any Win32 function (clipboard, IME). +//#define IMGUI_ENABLE_OSX_DEFAULT_CLIPBOARD_FUNCTIONS // [OSX] Implement default OSX clipboard handler (need to link with '-framework ApplicationServices', this is why this is not the default). +//#define IMGUI_DISABLE_DEFAULT_SHELL_FUNCTIONS // Don't implement default platform_io.Platform_OpenInShellFn() handler (Win32: ShellExecute(), require shell32.lib/.a, Mac/Linux: use system("")). +//#define IMGUI_DISABLE_DEFAULT_FORMAT_FUNCTIONS // Don't implement ImFormatString/ImFormatStringV so you can implement them yourself (e.g. if you don't want to link with vsnprintf) +//#define IMGUI_DISABLE_DEFAULT_MATH_FUNCTIONS // Don't implement ImFabs/ImSqrt/ImPow/ImFmod/ImCos/ImSin/ImAcos/ImAtan2 so you can implement them yourself. +//#define IMGUI_DISABLE_FILE_FUNCTIONS // Don't implement ImFileOpen/ImFileClose/ImFileRead/ImFileWrite and ImFileHandle at all (replace them with dummies) +//#define IMGUI_DISABLE_DEFAULT_FILE_FUNCTIONS // Don't implement ImFileOpen/ImFileClose/ImFileRead/ImFileWrite and ImFileHandle so you can implement them yourself if you don't want to link with fopen/fclose/fread/fwrite. This will also disable the LogToTTY() function. +//#define IMGUI_DISABLE_DEFAULT_ALLOCATORS // Don't implement default allocators calling malloc()/free() to avoid linking with them. You will need to call ImGui::SetAllocatorFunctions(). +//#define IMGUI_DISABLE_DEFAULT_FONT // Disable default embedded fonts (ProggyClean/ProggyVector), remove ~9 KB + ~17 KB from output binary. AddFontDefaultXXX() functions will assert. +//#define IMGUI_DISABLE_SSE // Disable use of SSE intrinsics even if available + +//---- Enable Test Engine / Automation features. +//#define IMGUI_ENABLE_TEST_ENGINE // Enable imgui_test_engine hooks. Generally set automatically by include "imgui_te_config.h", see Test Engine for details. + +//---- Include imgui_user.h at the end of imgui.h as a convenience +// May be convenient for some users to only explicitly include vanilla imgui.h and have extra stuff included. +//#define IMGUI_INCLUDE_IMGUI_USER_H +//#define IMGUI_USER_H_FILENAME "my_folder/my_imgui_user.h" + +//---- Pack vertex colors as BGRA8 instead of RGBA8 (to avoid converting from one to another). Need dedicated backend support. +//#define IMGUI_USE_BGRA_PACKED_COLOR + +//---- Use legacy CRC32-adler tables (used before 1.91.6), in order to preserve old .ini data that you cannot afford to invalidate. +//#define IMGUI_USE_LEGACY_CRC32_ADLER + +//---- Use 32-bit for ImWchar (default is 16-bit) to support Unicode planes 1-16. (e.g. point beyond 0xFFFF like emoticons, dingbats, symbols, shapes, ancient languages, etc...) +//#define IMGUI_USE_WCHAR32 + +//---- Avoid multiple STB libraries implementations, or redefine path/filenames to prioritize another version +// By default the embedded implementations are declared static and not available outside of Dear ImGui sources files. +//#define IMGUI_STB_TRUETYPE_FILENAME "my_folder/stb_truetype.h" +//#define IMGUI_STB_RECT_PACK_FILENAME "my_folder/stb_rect_pack.h" +//#define IMGUI_STB_SPRINTF_FILENAME "my_folder/stb_sprintf.h" // only used if IMGUI_USE_STB_SPRINTF is defined. +//#define IMGUI_DISABLE_STB_TRUETYPE_IMPLEMENTATION +//#define IMGUI_DISABLE_STB_RECT_PACK_IMPLEMENTATION +//#define IMGUI_DISABLE_STB_SPRINTF_IMPLEMENTATION // only disabled if IMGUI_USE_STB_SPRINTF is defined. + +//---- Use stb_sprintf.h for a faster implementation of vsnprintf instead of the one from libc (unless IMGUI_DISABLE_DEFAULT_FORMAT_FUNCTIONS is defined) +// Compatibility checks of arguments and formats done by clang and GCC will be disabled in order to support the extra formats provided by stb_sprintf.h. +//#define IMGUI_USE_STB_SPRINTF + +//---- Use FreeType to build and rasterize the font atlas (instead of stb_truetype which is embedded by default in Dear ImGui) +// Requires FreeType headers to be available in the include path. Requires program to be compiled with 'misc/freetype/imgui_freetype.cpp' (in this repository) + the FreeType library (not provided). +// Note that imgui_freetype.cpp may be used _without_ this define, if you manually call ImFontAtlas::SetFontLoader(). The define is simply a convenience. +// On Windows you may use vcpkg with 'vcpkg install freetype --triplet=x64-windows' + 'vcpkg integrate install'. +//#define IMGUI_ENABLE_FREETYPE + +//---- Use FreeType + plutosvg or lunasvg to render OpenType SVG fonts (SVGinOT) +// Only works in combination with IMGUI_ENABLE_FREETYPE. +// - plutosvg is currently easier to install, as e.g. it is part of vcpkg. It will support more fonts and may load them faster. See misc/freetype/README for instructions. +// - Both require headers to be available in the include path + program to be linked with the library code (not provided). +// - (note: lunasvg implementation is based on Freetype's rsvg-port.c which is licensed under CeCILL-C Free Software License Agreement) +//#define IMGUI_ENABLE_FREETYPE_PLUTOSVG +//#define IMGUI_ENABLE_FREETYPE_LUNASVG + +//---- Use stb_truetype to build and rasterize the font atlas (default) +// The only purpose of this define is if you want force compilation of the stb_truetype backend ALONG with the FreeType backend. +//#define IMGUI_ENABLE_STB_TRUETYPE + +//---- Define constructor and implicit cast operators to convert back<>forth between your math types and ImVec2/ImVec4. +// This will be inlined as part of ImVec2 and ImVec4 class declarations. +/* +#define IM_VEC2_CLASS_EXTRA \ + constexpr ImVec2(const MyVec2& f) : x(f.x), y(f.y) {} \ + operator MyVec2() const { return MyVec2(x,y); } + +#define IM_VEC4_CLASS_EXTRA \ + constexpr ImVec4(const MyVec4& f) : x(f.x), y(f.y), z(f.z), w(f.w) {} \ + operator MyVec4() const { return MyVec4(x,y,z,w); } +*/ +//---- ...Or use Dear ImGui's own very basic math operators. +//#define IMGUI_DEFINE_MATH_OPERATORS + +//---- Use 32-bit vertex indices (default is 16-bit) is one way to allow large meshes with more than 64K vertices. +// Your renderer backend will need to support it (most example renderer dx8_backend support both 16/32-bit indices). +// Another way to allow large meshes while keeping 16-bit indices is to handle ImDrawCmd::VtxOffset in your renderer. +// Read about ImGuiBackendFlags_RendererHasVtxOffset for details. +//#define ImDrawIdx unsigned int + +//---- Override ImDrawCallback signature (will need to modify renderer dx8_backend accordingly) +//struct ImDrawList; +//struct ImDrawCmd; +//typedef void (*MyImDrawCallback)(const ImDrawList* draw_list, const ImDrawCmd* cmd, void* my_renderer_user_data); +//#define ImDrawCallback MyImDrawCallback + +//---- Debug Tools: Macro to break in Debugger (we provide a default implementation of this in the codebase) +// (use 'Metrics->Tools->Item Picker' to pick widgets with the mouse and break into them for easy debugging.) +//#define IM_DEBUG_BREAK IM_ASSERT(0) +//#define IM_DEBUG_BREAK __debugbreak() + +//---- Debug Tools: Enable highlight ID conflicts _before_ hovering items. When io.ConfigDebugHighlightIdConflicts is set. +// (THIS WILL SLOW DOWN DEAR IMGUI. Only use occasionally and disable after use) +//#define IMGUI_DEBUG_HIGHLIGHT_ALL_ID_CONFLICTS + +//---- Debug Tools: Enable slower asserts +//#define IMGUI_DEBUG_PARANOID + +//---- Tip: You can add extra functions within the ImGui:: namespace from anywhere (e.g. your own sources/header files) +/* +namespace ImGui +{ + void MyFunction(const char* name, MyMatrix44* mtx); +} +*/ diff --git a/Core/Libraries/Source/ImGui/wrapper/ImGuiFrameManager.cpp b/Core/Libraries/Source/ImGui/wrapper/ImGuiFrameManager.cpp new file mode 100644 index 00000000000..bf98a968ace --- /dev/null +++ b/Core/Libraries/Source/ImGui/wrapper/ImGuiFrameManager.cpp @@ -0,0 +1,53 @@ +/** + * @file ImGuiFrameManager.cpp + * @brief Simple Frame manager for ImGui widgets + */ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2026 TheSuperHackers +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ +#include "ImGuiFrameManager.h" +#include "imgui.h" +#include "imgui_impl_dx8.h" +#include "imgui_impl_win32.h" + +bool ImGui::FrameManager::s_frameOpen = false; + +void ImGui::FrameManager::BeginFrame() +{ + if (s_frameOpen) + { + return; + } + + ImGui_ImplDX8_NewFrame(); + ImGui_ImplWin32_NewFrame(); + NewFrame(); + + s_frameOpen = true; +} + +void ImGui::FrameManager::EndFrame() +{ + if (!s_frameOpen) + { + return; + } + + Render(); + + s_frameOpen = false; +} diff --git a/Core/Libraries/Source/ImGui/wrapper/ImGuiFrameManager.h b/Core/Libraries/Source/ImGui/wrapper/ImGuiFrameManager.h new file mode 100644 index 00000000000..03ae1225149 --- /dev/null +++ b/Core/Libraries/Source/ImGui/wrapper/ImGuiFrameManager.h @@ -0,0 +1,38 @@ +/** + * @file ImGuiFrameManager.h + * @brief Simple Frame manager for ImGui widgets + */ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2026 TheSuperHackers +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#pragma once + +namespace ImGui +{ + +class FrameManager +{ + public: + static void BeginFrame(); + static void EndFrame(); // Includes Render() + + private: + static bool s_frameOpen; +}; + +} // namespace ImGui diff --git a/Generals/Code/GameEngine/CMakeLists.txt b/Generals/Code/GameEngine/CMakeLists.txt index f4ef0c45917..4c681d55c8a 100644 --- a/Generals/Code/GameEngine/CMakeLists.txt +++ b/Generals/Code/GameEngine/CMakeLists.txt @@ -1098,6 +1098,10 @@ target_link_libraries(g_gameengine PUBLIC g_wwvegas ) +if (RTS_BUILD_OPTION_IMGUI) + target_link_libraries(g_gameengine PRIVATE lib_imgui) +endif () + target_precompile_headers(g_gameengine PRIVATE [["Utility/CppMacros.h"]] # Must be first, to be removed when abandoning VC6 Include/Precompiled/PreRTS.h diff --git a/Generals/Code/GameEngine/Source/GameClient/GameClient.cpp b/Generals/Code/GameEngine/Source/GameClient/GameClient.cpp index b317344c680..adf577c1d3a 100644 --- a/Generals/Code/GameEngine/Source/GameClient/GameClient.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/GameClient.cpp @@ -82,6 +82,10 @@ #include "GameLogic/GhostObject.h" #include "GameLogic/Object.h" #include "GameLogic/ScriptEngine.h" // For TheScriptEngine - jkmcd +#ifdef RTS_HAS_IMGUI +#include "imgui.h" +#include "ImGuiFrameManager.h" +#endif #define DRAWABLE_HASH_SIZE 8192 @@ -484,11 +488,20 @@ void GameClient::registerDrawable( Drawable *draw ) /** ----------------------------------------------------------------------------------------------- * Redraw all views, update the GUI, play sound effects, etc. */ +// TheSuperHackers @feature jurassiclizard 16/01/2026 imgui integration (PR#2127) +// see details under WinMain.cpp (WndProc()) DECLARE_PERF_TIMER(GameClient_update) DECLARE_PERF_TIMER(GameClient_draw) void GameClient::update( void ) { USE_PERF_TIMER(GameClient_update) +#ifdef RTS_HAS_IMGUI + ImGui::FrameManager::BeginFrame(); + // Draw ImGui Demo Window + { + ImGui::ShowDemoWindow(); + } +#endif // create the FRAME_TICK message GameMessage *frameMsg = TheMessageStream->appendMessage( GameMessage::MSG_FRAME_TICK ); frameMsg->appendTimestampArgument( getFrame() ); @@ -581,6 +594,9 @@ void GameClient::update( void ) if(TheGlobalData->m_playIntro || TheGlobalData->m_afterIntro) { +#ifdef RTS_HAS_IMGUI + ImGui::FrameManager::EndFrame(); +#endif // redraw all views, update the GUI TheDisplay->DRAW(); TheDisplay->UPDATE(); @@ -689,6 +705,9 @@ void GameClient::update( void ) // need to draw the first frame, then don't draw again until TheGlobalData->m_noDraw if (TheGlobalData->m_noDraw > TheGameLogic->getFrame() && TheGameLogic->getFrame() > 0) { +#ifdef RTS_HAS_IMGUI + ImGui::FrameManager::EndFrame(); +#endif return; } #endif @@ -712,6 +731,9 @@ void GameClient::update( void ) TheDisplay->UPDATE(); } +#ifdef RTS_HAS_IMGUI + ImGui::FrameManager::EndFrame(); +#endif { USE_PERF_TIMER(GameClient_draw) diff --git a/Generals/Code/Libraries/Source/WWVegas/WW3D2/CMakeLists.txt b/Generals/Code/Libraries/Source/WWVegas/WW3D2/CMakeLists.txt index ada976f2a3c..2890c259f27 100644 --- a/Generals/Code/Libraries/Source/WWVegas/WW3D2/CMakeLists.txt +++ b/Generals/Code/Libraries/Source/WWVegas/WW3D2/CMakeLists.txt @@ -237,6 +237,10 @@ target_compile_definitions(g_ww3d2 PRIVATE $<$:WINVER=0x0500> ) +if (RTS_BUILD_OPTION_IMGUI) + target_link_libraries(g_ww3d2 PRIVATE lib_imgui) +endif () + target_precompile_headers(g_ww3d2 PRIVATE [["Utility/CppMacros.h"]] # Must be first, to be removed when abandoning VC6 dx8wrapper.h diff --git a/Generals/Code/Libraries/Source/WWVegas/WW3D2/dx8wrapper.cpp b/Generals/Code/Libraries/Source/WWVegas/WW3D2/dx8wrapper.cpp index 0e20057d1f8..7873c0f9008 100644 --- a/Generals/Code/Libraries/Source/WWVegas/WW3D2/dx8wrapper.cpp +++ b/Generals/Code/Libraries/Source/WWVegas/WW3D2/dx8wrapper.cpp @@ -82,6 +82,12 @@ #include "bound.h" #include "DbgHelpGuard.h" +#ifdef RTS_HAS_IMGUI +#include "imgui.h" +#include "imgui_impl_dx8.h" +#include "imgui_impl_win32.h" +#endif + const int DEFAULT_RESOLUTION_WIDTH = 640; const int DEFAULT_RESOLUTION_HEIGHT = 480; @@ -262,7 +268,8 @@ void MoveRectIntoOtherRect(const RECT& inner, const RECT& outer, int* x, int* y) *y += dy; } - +// TheSuperHackers @feature jurassiclizard 16/01/2026 imgui integration (PR#2127) +// see details under WinMain.cpp (WndProc()) bool DX8Wrapper::Init(void * hwnd, bool lite) { WWASSERT(!IsInitted); @@ -349,6 +356,11 @@ bool DX8Wrapper::Init(void * hwnd, bool lite) void DX8Wrapper::Shutdown(void) { +#ifdef RTS_HAS_IMGUI + ImGui_ImplDX8_Shutdown(); + ImGui_ImplWin32_Shutdown(); + ImGui::DestroyContext(); +#endif if (D3DDevice) { Set_Render_Target ((IDirect3DSurface8 *)nullptr); @@ -510,7 +522,6 @@ void DX8Wrapper::Do_Onetime_Device_Dependent_Shutdowns(void) } - bool DX8Wrapper::Create_Device(void) { WWASSERT(D3DDevice==nullptr); // for now, once you've created a device, you're stuck with it! @@ -588,7 +599,21 @@ bool DX8Wrapper::Create_Device(void) { return false; } - +#ifdef RTS_HAS_IMGUI + // Initialize ImGui + IMGUI_CHECKVERSION(); + ImGui::CreateContext(); + ImGuiIO& io = ImGui::GetIO(); + io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; + io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls + // Dark Style + ImGui::StyleColorsDark(); + + ImGui_ImplWin32_Init(_Hwnd); + ImGui_ImplDX8_Init(DX8Wrapper::_Get_D3D_Device8()); + io.Fonts->AddFontDefault(); + io.DisplaySize = ImVec2(ResolutionWidth,ResolutionHeight); +#endif dbgHelpGuard.deactivate(); /* @@ -603,6 +628,9 @@ bool DX8Wrapper::Reset_Device(bool reload_assets) WWDEBUG_SAY(("Resetting device.")); DX8_THREAD_ASSERT(); if ((IsInitted) && (D3DDevice != nullptr)) { +#ifdef RTS_HAS_IMGUI + ImGui_ImplDX8_InvalidateDeviceObjects(); +#endif // Release all non-MANAGED stuff WW3D::_Invalidate_Textures(); @@ -636,6 +664,9 @@ bool DX8Wrapper::Reset_Device(bool reload_assets) } Invalidate_Cached_Render_States(); Set_Default_Global_Render_States(); +#ifdef RTS_HAS_IMGUI + ImGui_ImplDX8_CreateDeviceObjects(); +#endif WWDEBUG_SAY(("Device reset completed")); return true; } @@ -1596,6 +1627,14 @@ void DX8Wrapper::Begin_Scene(void) void DX8Wrapper::End_Scene(bool flip_frames) { DX8_THREAD_ASSERT(); +#ifdef RTS_HAS_IMGUI + { + ImDrawData* data = ImGui::GetDrawData(); + if (data && data->CmdListsCount > 0) { + ImGui_ImplDX8_RenderDrawData(ImGui::GetDrawData()); + } + } +#endif DX8CALL(EndScene()); DX8WebBrowser::Render(0); diff --git a/Generals/Code/Main/CMakeLists.txt b/Generals/Code/Main/CMakeLists.txt index 9e14a4cbe75..b67c0cb8b25 100644 --- a/Generals/Code/Main/CMakeLists.txt +++ b/Generals/Code/Main/CMakeLists.txt @@ -7,6 +7,10 @@ else() set_target_properties(g_generals PROPERTIES OUTPUT_NAME generalsv) endif() +if (RTS_BUILD_OPTION_IMGUI) + target_link_libraries(g_generals PRIVATE lib_imgui) +endif () + target_link_libraries(g_generals PRIVATE binkstub comctl32 diff --git a/Generals/Code/Main/WinMain.cpp b/Generals/Code/Main/WinMain.cpp index 528b09dbdf5..133778b2ff8 100644 --- a/Generals/Code/Main/WinMain.cpp +++ b/Generals/Code/Main/WinMain.cpp @@ -67,7 +67,9 @@ #ifdef RTS_ENABLE_CRASHDUMP #include "Common/MiniDumper.h" #endif - +#ifdef RTS_HAS_IMGUI +#include "imgui.h" +#endif // GLOBALS //////////////////////////////////////////////////////////////////// HINSTANCE ApplicationHInstance = nullptr; ///< our application instance @@ -287,13 +289,29 @@ static const char *messageToString(unsigned int message) } #endif +// ThSuperHackers @feature jurassiclizard 16/01/2026 introduce ImGui framework (PR#2127) +// ImGui workflow: +// - WndProc: forwards input to ImGui via ImGui_ImplWin32_WndProcHandler() +// - DX8Wrapper: manages context/backend initialization and cleanup, +// - DX8Wrapper: handles device reset by invalidating/recreating device objects and End_Scene() and renders ImGui draw data after the main scene. +// - GameClient: GameClient::update() starts each frame with NewFrame() calls and builds UI (ShowDemoWindow), while ImGui::Render() is called before DRAW() operations and +// critically before early returns in RTS_DEBUG mode (frame stepping) to ensure frames are properly closed and the demo window displays. +// +// See GameClient::update(), DX8Wrapper::Create_Device(), DX8Wrapper::Init(), DX8Wrapper::Shutdown(), DX8Wrapper::End_Scene(), and WndProc() for implementation details. +#ifdef RTS_HAS_IMGUI +extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND, UINT, WPARAM, LPARAM); +#endif // WndProc ==================================================================== /** Window Procedure */ //============================================================================= LRESULT CALLBACK WndProc( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam ) { - +#ifdef RTS_HAS_IMGUI + if (ImGui_ImplWin32_WndProcHandler(hWnd, message, wParam, lParam)) { + return true; + } +#endif try { // First let the IME manager do it's stuff. diff --git a/Generals/Code/Tools/WorldBuilder/CMakeLists.txt b/Generals/Code/Tools/WorldBuilder/CMakeLists.txt index 263cc6fe32b..4f28f4758e3 100644 --- a/Generals/Code/Tools/WorldBuilder/CMakeLists.txt +++ b/Generals/Code/Tools/WorldBuilder/CMakeLists.txt @@ -212,6 +212,10 @@ target_precompile_headers(g_worldbuilder PRIVATE [["WWCommon.h"]] ) +if (RTS_BUILD_OPTION_IMGUI) + target_link_libraries(g_worldbuilder PRIVATE lib_imgui) +endif () + target_link_libraries(g_worldbuilder PRIVATE d3d8lib core_browserdispatch diff --git a/Generals/Code/Tools/WorldBuilder/include/wbview3d.h b/Generals/Code/Tools/WorldBuilder/include/wbview3d.h index 751eb8b2e68..7b09a8e5d14 100644 --- a/Generals/Code/Tools/WorldBuilder/include/wbview3d.h +++ b/Generals/Code/Tools/WorldBuilder/include/wbview3d.h @@ -78,7 +78,13 @@ class WbView3d : public WbView, public DX8_CleanupHook virtual void OnDraw(CDC* pDC); // overridden to draw this view //}}AFX_VIRTUAL -// Implementation +#ifdef RTS_HAS_IMGUI +// Function overrides needed for ImGui Mouse capture + virtual BOOL PreTranslateMessage(MSG* pMsg); + virtual LRESULT WindowProc(UINT message, WPARAM wParam, LPARAM lParam); +#endif + + // Implementation protected: virtual ~WbView3d(); #ifdef RTS_DEBUG diff --git a/Generals/Code/Tools/WorldBuilder/src/wbview3d.cpp b/Generals/Code/Tools/WorldBuilder/src/wbview3d.cpp index 97f571864f9..d063bfa4b21 100644 --- a/Generals/Code/Tools/WorldBuilder/src/wbview3d.cpp +++ b/Generals/Code/Tools/WorldBuilder/src/wbview3d.cpp @@ -95,6 +95,11 @@ #include +#ifdef RTS_HAS_IMGUI +#include +#include +#include "ImGuiFrameManager.h" +#endif // ---------------------------------------------------------------------------- // Misc. Forward Declarations @@ -779,7 +784,9 @@ void WbView3d::resetRenderObjects() if (TheW3DShadowManager) { TheW3DShadowManager->removeAllShadows(); } - +#ifdef RTS_HAS_IMGUI + ImGui_ImplDX8_InvalidateDeviceObjects(); +#endif SceneIterator *sceneIter = m_scene->Create_Iterator(); sceneIter->First(); while(!sceneIter->Is_Done()) { @@ -826,6 +833,10 @@ void WbView3d::resetRenderObjects() updateLights(); if (m_heightMapRenderObj) m_scene->Add_Render_Object(m_heightMapRenderObj); + +#ifdef RTS_HAS_IMGUI + ImGui_ImplDX8_CreateDeviceObjects(); +#endif } // ---------------------------------------------------------------------------- @@ -1995,10 +2006,20 @@ void WbView3d::redraw(void) } // ---------------------------------------------------------------------------- +// TheSuperHackers @feature jurassiclizard 16/01/2026 imgui integration (PR#2127) +// see details under WinMain.cpp (WndProc()) void WbView3d::render() { ++m_updateCount; +#ifdef RTS_HAS_IMGUI + { + ImGui::FrameManager::BeginFrame(); + ImGui::ShowDemoWindow(); + ImGui::FrameManager::EndFrame(); + } +#endif + if (WW3D::Begin_Render(true,true,Vector3(0.5f,0.5f,0.5f), TheWaterTransparency->m_minWaterOpacity) == WW3D_ERROR_OK) { @@ -2129,6 +2150,24 @@ void WbView3d::OnDraw(CDC* pDC) // Not used. See OnPaint. } +#ifdef RTS_HAS_IMGUI +extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND, UINT, WPARAM, LPARAM); +BOOL WbView3d::PreTranslateMessage(MSG *pMsg) +{ + if (ImGui_ImplWin32_WndProcHandler(m_hWnd, pMsg->message,pMsg->wParam, pMsg->lParam)) + { + return TRUE; + } + return WbView::PreTranslateMessage(pMsg); +} + +LRESULT WbView3d::WindowProc(UINT message, WPARAM wParam, LPARAM lParam) +{ + if (ImGui_ImplWin32_WndProcHandler(m_hWnd, message, wParam, lParam)) + return TRUE; + return WbView::WindowProc(message, wParam, lParam); +} +#endif // ---------------------------------------------------------------------------- // WbView3d diagnostics diff --git a/GeneralsMD/Code/GameEngine/CMakeLists.txt b/GeneralsMD/Code/GameEngine/CMakeLists.txt index 189ab821ffb..986a1b6fc84 100644 --- a/GeneralsMD/Code/GameEngine/CMakeLists.txt +++ b/GeneralsMD/Code/GameEngine/CMakeLists.txt @@ -1174,6 +1174,10 @@ target_link_libraries(z_gameengine PUBLIC z_wwvegas ) +if (RTS_BUILD_OPTION_IMGUI) + target_link_libraries(z_gameengine PRIVATE lib_imgui) +endif () + target_precompile_headers(z_gameengine PRIVATE [["Utility/CppMacros.h"]] # Must be first, to be removed when abandoning VC6 Include/Precompiled/PreRTS.h diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GameClient.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GameClient.cpp index 583721ff51e..b1b0a34397f 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GameClient.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GameClient.cpp @@ -32,6 +32,12 @@ #include "GameClient/GameClient.h" // USER INCLUDES ////////////////////////////////////////////////////////////// +#ifdef RTS_HAS_IMGUI +#include +#include "ImGuiFrameManager.h" +#include "dx8wrapper.h" +#endif + #include "Common/ActionManager.h" #include "Common/GameEngine.h" #include "Common/GameState.h" @@ -505,11 +511,20 @@ void GameClient::registerDrawable( Drawable *draw ) /** ----------------------------------------------------------------------------------------------- * Redraw all views, update the GUI, play sound effects, etc. */ +// TheSuperHackers @feature jurassiclizard 16/01/2026 imgui integration (PR#2127) +// see details under WinMain.cpp (WndProc()) DECLARE_PERF_TIMER(GameClient_update) DECLARE_PERF_TIMER(GameClient_draw) void GameClient::update( void ) { USE_PERF_TIMER(GameClient_update) +#ifdef RTS_HAS_IMGUI + ImGui::FrameManager::BeginFrame(); + // Draw ImGui Demo Window + { + ImGui::ShowDemoWindow(); + } +#endif // create the FRAME_TICK message GameMessage *frameMsg = TheMessageStream->appendMessage( GameMessage::MSG_FRAME_TICK ); frameMsg->appendTimestampArgument( getFrame() ); @@ -619,9 +634,13 @@ void GameClient::update( void ) if(TheGlobalData->m_playIntro || TheGlobalData->m_afterIntro) { +#ifdef RTS_HAS_IMGUI + ImGui::FrameManager::EndFrame(); +#endif // redraw all views, update the GUI TheDisplay->DRAW(); TheDisplay->UPDATE(); + return; } @@ -727,6 +746,9 @@ void GameClient::update( void ) // need to draw the first frame, then don't draw again until TheGlobalData->m_noDraw if (TheGlobalData->m_noDraw > TheGameLogic->getFrame() && TheGameLogic->getFrame() > 0) { +#ifdef RTS_HAS_IMGUI + ImGui::FrameManager::EndFrame(); +#endif return; } #endif @@ -750,6 +772,11 @@ void GameClient::update( void ) TheDisplay->UPDATE(); } + +#ifdef RTS_HAS_IMGUI + ImGui::FrameManager::EndFrame(); +#endif + { USE_PERF_TIMER(GameClient_draw) diff --git a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/CMakeLists.txt b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/CMakeLists.txt index 59617b6b451..1296f7f6501 100644 --- a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/CMakeLists.txt +++ b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/CMakeLists.txt @@ -253,6 +253,10 @@ target_precompile_headers(z_ww3d2 PRIVATE ) +if (RTS_BUILD_OPTION_IMGUI) + target_link_libraries(z_ww3d2 PRIVATE lib_imgui) +endif () + target_link_libraries(z_ww3d2 PRIVATE core_wwdebug corei_ww3d2 diff --git a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/dx8wrapper.cpp b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/dx8wrapper.cpp index c3f82d48ce1..29fac177c05 100644 --- a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/dx8wrapper.cpp +++ b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/dx8wrapper.cpp @@ -87,6 +87,12 @@ #include "shdlib.h" +#ifdef RTS_HAS_IMGUI +#include "imgui.h" +#include +#include "imgui_impl_dx8.h" +#endif + const int DEFAULT_RESOLUTION_WIDTH = 640; const int DEFAULT_RESOLUTION_HEIGHT = 480; const int DEFAULT_BIT_DEPTH = 32; @@ -355,6 +361,11 @@ bool DX8Wrapper::Init(void * hwnd, bool lite) void DX8Wrapper::Shutdown(void) { +#ifdef RTS_HAS_IMGUI + ImGui_ImplDX8_Shutdown(); + ImGui_ImplWin32_Shutdown(); + ImGui::DestroyContext(); +#endif if (D3DDevice) { Set_Render_Target ((IDirect3DSurface8 *)nullptr); @@ -418,6 +429,7 @@ void DX8Wrapper::Do_Onetime_Device_Dependent_Inits(void) ShatterSystem::Init(); TextureLoader::Init(); + Set_Default_Global_Render_States(); } @@ -520,7 +532,6 @@ void DX8Wrapper::Do_Onetime_Device_Dependent_Shutdowns(void) } - bool DX8Wrapper::Create_Device(void) { WWASSERT(D3DDevice==nullptr); // for now, once you've created a device, you're stuck with it! @@ -636,7 +647,21 @@ bool DX8Wrapper::Create_Device(void) return false; } } - +#ifdef RTS_HAS_IMGUI + // Initialize ImGui + IMGUI_CHECKVERSION(); + ImGui::CreateContext(); + ImGuiIO& io = ImGui::GetIO(); + io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; + io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls + // Dark Style + ImGui::StyleColorsDark(); + + ImGui_ImplWin32_Init(_Hwnd); + ImGui_ImplDX8_Init(DX8Wrapper::_Get_D3D_Device8()); + io.Fonts->AddFontDefault(); + io.DisplaySize = ImVec2(ResolutionWidth,ResolutionHeight); +#endif dbgHelpGuard.deactivate(); /* @@ -651,6 +676,9 @@ bool DX8Wrapper::Reset_Device(bool reload_assets) WWDEBUG_SAY(("Resetting device.")); DX8_THREAD_ASSERT(); if ((IsInitted) && (D3DDevice != nullptr)) { +#ifdef RTS_HAS_IMGUI + ImGui_ImplDX8_InvalidateDeviceObjects(); +#endif // Release all non-MANAGED stuff WW3D::_Invalidate_Textures(); @@ -692,6 +720,9 @@ bool DX8Wrapper::Reset_Device(bool reload_assets) Invalidate_Cached_Render_States(); Set_Default_Global_Render_States(); SHD_INIT_SHADERS; +#ifdef RTS_HAS_IMGUI + ImGui_ImplDX8_CreateDeviceObjects(); +#endif WWDEBUG_SAY(("Device reset completed")); return true; } @@ -1726,6 +1757,14 @@ void DX8Wrapper::Begin_Scene(void) void DX8Wrapper::End_Scene(bool flip_frames) { DX8_THREAD_ASSERT(); +#ifdef RTS_HAS_IMGUI + { + ImDrawData* data = ImGui::GetDrawData(); + if (data && data->CmdListsCount > 0 ) { + ImGui_ImplDX8_RenderDrawData(ImGui::GetDrawData()); + } + } +#endif DX8CALL(EndScene()); DX8WebBrowser::Render(0); diff --git a/GeneralsMD/Code/Main/CMakeLists.txt b/GeneralsMD/Code/Main/CMakeLists.txt index d6518f442ba..a07ef3b2fdb 100644 --- a/GeneralsMD/Code/Main/CMakeLists.txt +++ b/GeneralsMD/Code/Main/CMakeLists.txt @@ -7,6 +7,10 @@ else() set_target_properties(z_generals PROPERTIES OUTPUT_NAME generalszh) endif() +if (RTS_BUILD_OPTION_IMGUI) + target_link_libraries(z_generals PRIVATE lib_imgui) +endif () + target_link_libraries(z_generals PRIVATE binkstub comctl32 diff --git a/GeneralsMD/Code/Main/WinMain.cpp b/GeneralsMD/Code/Main/WinMain.cpp index c26688a081b..13102929933 100644 --- a/GeneralsMD/Code/Main/WinMain.cpp +++ b/GeneralsMD/Code/Main/WinMain.cpp @@ -67,6 +67,11 @@ #include "resource.h" #include + +#ifdef RTS_HAS_IMGUI +#include "imgui.h" +#endif + #ifdef RTS_ENABLE_CRASHDUMP #include "Common/MiniDumper.h" #endif @@ -290,13 +295,29 @@ static const char *messageToString(unsigned int message) } #endif +// ThSuperHackers @feature jurassiclizard 16/01/2026 introduce ImGui framework (PR#2127) +// ImGui workflow: +// - WndProc: forwards input to ImGui via ImGui_ImplWin32_WndProcHandler() +// - DX8Wrapper: manages context/backend initialization and cleanup, +// - DX8Wrapper: handles device reset by invalidating/recreating device objects and End_Scene() and renders ImGui draw data after the main scene. +// - GameClient: GameClient::update() starts each frame with NewFrame() calls and builds UI (ShowDemoWindow), while ImGui::Render() is called before DRAW() operations and +// critically before early returns in RTS_DEBUG mode (frame stepping) to ensure frames are properly closed and the demo window displays. +// +// See GameClient::update(), DX8Wrapper::Create_Device(), DX8Wrapper::Init(), DX8Wrapper::Shutdown(), DX8Wrapper::End_Scene(), and WndProc() for implementation details. +#ifdef RTS_HAS_IMGUI +extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND, UINT, WPARAM, LPARAM); +#endif // WndProc ==================================================================== /** Window Procedure */ //============================================================================= LRESULT CALLBACK WndProc( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam ) { - +#ifdef RTS_HAS_IMGUI + if (ImGui_ImplWin32_WndProcHandler(hWnd, message, wParam, lParam)) { + return true; + } +#endif try { // First let the IME manager do it's stuff. diff --git a/GeneralsMD/Code/Tools/WorldBuilder/CMakeLists.txt b/GeneralsMD/Code/Tools/WorldBuilder/CMakeLists.txt index c6e309238db..b465aee239f 100644 --- a/GeneralsMD/Code/Tools/WorldBuilder/CMakeLists.txt +++ b/GeneralsMD/Code/Tools/WorldBuilder/CMakeLists.txt @@ -217,6 +217,10 @@ target_precompile_headers(z_worldbuilder PRIVATE [["WWCommon.h"]] ) +if(RTS_BUILD_OPTION_IMGUI) + target_link_libraries(z_worldbuilder PRIVATE lib_imgui) +endif () + target_link_libraries(z_worldbuilder PRIVATE core_debug core_profile diff --git a/GeneralsMD/Code/Tools/WorldBuilder/include/wbview3d.h b/GeneralsMD/Code/Tools/WorldBuilder/include/wbview3d.h index f34426b6782..54b83328218 100644 --- a/GeneralsMD/Code/Tools/WorldBuilder/include/wbview3d.h +++ b/GeneralsMD/Code/Tools/WorldBuilder/include/wbview3d.h @@ -79,7 +79,12 @@ class WbView3d : public WbView, public DX8_CleanupHook virtual void OnDraw(CDC* pDC); // overridden to draw this view //}}AFX_VIRTUAL -// Implementation +#ifdef RTS_HAS_IMGUI +// Function overrides needed for ImGui Mouse capture + virtual BOOL PreTranslateMessage(MSG* pMsg); + virtual LRESULT WindowProc(UINT message, WPARAM wParam, LPARAM lParam); +#endif + // Implementation protected: virtual ~WbView3d(); #ifdef RTS_DEBUG diff --git a/GeneralsMD/Code/Tools/WorldBuilder/src/wbview3d.cpp b/GeneralsMD/Code/Tools/WorldBuilder/src/wbview3d.cpp index 5bc1fe6671d..cda001335bc 100644 --- a/GeneralsMD/Code/Tools/WorldBuilder/src/wbview3d.cpp +++ b/GeneralsMD/Code/Tools/WorldBuilder/src/wbview3d.cpp @@ -96,6 +96,11 @@ #include +#ifdef RTS_HAS_IMGUI +#include +#include +#include "ImGuiFrameManager.h" +#endif // ---------------------------------------------------------------------------- // Misc. Forward Declarations // ---------------------------------------------------------------------------- @@ -800,7 +805,9 @@ void WbView3d::resetRenderObjects() if (TheW3DShadowManager) { TheW3DShadowManager->removeAllShadows(); } - +#ifdef RTS_HAS_IMGUI + ImGui_ImplDX8_InvalidateDeviceObjects(); +#endif SceneIterator *sceneIter = m_scene->Create_Iterator(); sceneIter->First(); while(!sceneIter->Is_Done()) { @@ -850,6 +857,10 @@ void WbView3d::resetRenderObjects() m_heightMapRenderObj->removeAllTrees(); m_heightMapRenderObj->removeAllProps(); } + +#ifdef RTS_HAS_IMGUI + ImGui_ImplDX8_CreateDeviceObjects(); +#endif } // ---------------------------------------------------------------------------- @@ -2078,6 +2089,14 @@ void WbView3d::render() { ++m_updateCount; +#ifdef RTS_HAS_IMGUI + { + ImGui::FrameManager::BeginFrame(); + ImGui::ShowDemoWindow(); + ImGui::FrameManager::EndFrame(); + } +#endif + if (WW3D::Begin_Render(true,true,Vector3(0.5f,0.5f,0.5f), TheWaterTransparency->m_minWaterOpacity) == WW3D_ERROR_OK) { @@ -2212,6 +2231,24 @@ void WbView3d::OnDraw(CDC* pDC) // Not used. See OnPaint. } +#ifdef RTS_HAS_IMGUI +extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND, UINT, WPARAM, LPARAM); +BOOL WbView3d::PreTranslateMessage(MSG *pMsg) +{ + if (ImGui_ImplWin32_WndProcHandler(m_hWnd, pMsg->message,pMsg->wParam, pMsg->lParam)) + { + return TRUE; + } + return WbView::PreTranslateMessage(pMsg); +} + +LRESULT WbView3d::WindowProc(UINT message, WPARAM wParam, LPARAM lParam) +{ + if (ImGui_ImplWin32_WndProcHandler(m_hWnd, message, wParam, lParam)) + return TRUE; + return WbView::WindowProc(message, wParam, lParam); +} +#endif // ---------------------------------------------------------------------------- // WbView3d diagnostics diff --git a/cmake/config-build.cmake b/cmake/config-build.cmake index 6401c458be0..22ad009da50 100644 --- a/cmake/config-build.cmake +++ b/cmake/config-build.cmake @@ -8,6 +8,7 @@ option(RTS_BUILD_OPTION_DEBUG "Build code with the \"Debug\" configuration." OFF option(RTS_BUILD_OPTION_ASAN "Build code with Address Sanitizer." OFF) option(RTS_BUILD_OPTION_VC6_FULL_DEBUG "Build VC6 with full debug info." OFF) option(RTS_BUILD_OPTION_FFMPEG "Enable FFmpeg support" OFF) +option(RTS_BUILD_OPTION_IMGUI "Build with ImGui" OFF) if(NOT RTS_BUILD_ZEROHOUR AND NOT RTS_BUILD_GENERALS) set(RTS_BUILD_ZEROHOUR TRUE) @@ -23,6 +24,7 @@ add_feature_info(DebugBuild RTS_BUILD_OPTION_DEBUG "Building as a \"Debug\" buil add_feature_info(AddressSanitizer RTS_BUILD_OPTION_ASAN "Building with address sanitizer") add_feature_info(Vc6FullDebug RTS_BUILD_OPTION_VC6_FULL_DEBUG "Building VC6 with full debug info") add_feature_info(FFmpegSupport RTS_BUILD_OPTION_FFMPEG "Building with FFmpeg support") +add_feature_info(ImGuiSupport RTS_BUILD_OPTION_IMGUI "Building with ImGui") if(RTS_BUILD_ZEROHOUR) option(RTS_BUILD_ZEROHOUR_TOOLS "Build tools for Zero Hour" ON)