From 57845b02c55444c3614672b3240da1ad981d18dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Tue, 26 Nov 2024 09:05:29 +0100 Subject: [PATCH] ImGui thin3d backend: Add texture binding support --- ext/imgui/imgui_impl_thin3d.cpp | 96 ++++++++++++++++++++++++--------- ext/imgui/imgui_impl_thin3d.h | 6 ++- 2 files changed, 74 insertions(+), 28 deletions(-) diff --git a/ext/imgui/imgui_impl_thin3d.cpp b/ext/imgui/imgui_impl_thin3d.cpp index 5eb8e57351..4cf9590c92 100644 --- a/ext/imgui/imgui_impl_thin3d.cpp +++ b/ext/imgui/imgui_impl_thin3d.cpp @@ -12,28 +12,52 @@ static Lin::Matrix4x4 g_drawMatrix; static ImFont *g_proportionalFont = nullptr; static ImFont *g_fixedFont = nullptr; -struct ImGui_ImplThin3d_Data { +struct RegisteredTexture { + bool isFramebuffer; + union { + Draw::Texture *texture; + Draw::Framebuffer *framebuffer; + }; +}; + +struct BackendData { Draw::SamplerState *fontSampler = nullptr; Draw::Texture *fontImage = nullptr; Draw::Pipeline *pipeline = nullptr; + std::vector tempTextures; }; +#define TEX_ID_OFFSET 256 + // Backend data stored in io.BackendRendererUserData to allow support for multiple Dear ImGui contexts // It is STRONGLY preferred that you use docking branch with multi-viewports (== single Dear ImGui context + multiple windows) instead of multiple Dear ImGui contexts. // FIXME: multi-context support is not tested and probably dysfunctional in this backend. -static ImGui_ImplThin3d_Data* ImGui_ImplThin3d_GetBackendData() { - return ImGui::GetCurrentContext() ? (ImGui_ImplThin3d_Data *)ImGui::GetIO().BackendRendererUserData : nullptr; +static BackendData *ImGui_ImplThin3d_GetBackendData() { + return ImGui::GetCurrentContext() ? (BackendData *)ImGui::GetIO().BackendRendererUserData : nullptr; } -static void ImGui_ImplThin3d_SetupRenderState(Draw::DrawContext *draw, ImDrawData* draw_data, Draw::Pipeline *pipeline, int fb_width, int fb_height) { - ImGui_ImplThin3d_Data* bd = ImGui_ImplThin3d_GetBackendData(); +static void BindTexture(Draw::DrawContext *draw, BackendData *bd, ImTextureID textureId) { + if (!textureId) { + draw->BindTexture(0, bd->fontImage); + } else { + size_t index = (size_t)textureId - TEX_ID_OFFSET; + _dbg_assert_(index < bd->tempTextures.size()); + if (bd->tempTextures[index].framebuffer) { + draw->BindFramebufferAsTexture(bd->tempTextures[index].framebuffer, 0, Draw::FBChannel::FB_COLOR_BIT, 0); + } else { + draw->BindTexture(0, bd->tempTextures[index].texture); + } + } +} + +static void ImGui_ImplThin3d_SetupRenderState(Draw::DrawContext *draw, ImDrawData* drawData, Draw::Pipeline *pipeline, int fb_width, int fb_height) { + BackendData *bd = ImGui_ImplThin3d_GetBackendData(); // Bind pipeline and texture draw->BindPipeline(pipeline); - draw->BindTexture(0, bd->fontImage); draw->BindSamplerStates(0, 1, &bd->fontSampler); - // Setup viewport: + // Setup viewport { Draw::Viewport viewport; viewport.TopLeftX = 0; @@ -46,10 +70,11 @@ static void ImGui_ImplThin3d_SetupRenderState(Draw::DrawContext *draw, ImDrawDat } // Setup scale and translation: - // Our visible imgui space lies from draw_data->DisplayPps (top left) to draw_data->DisplayPos+data_data->DisplaySize (bottom right). DisplayPos is (0,0) for single viewport apps. - // We currently ignore DisplayPos. + // Our visible imgui space lies from drawData->DisplayPps (top left) to drawData->DisplayPos + drawData->DisplaySize (bottom right). + // DisplayPos is (0,0) for single viewport apps. We currently ignore DisplayPos. + // We probably only need to do this at the start of the frame. { - Lin::Matrix4x4 mtx = ComputeOrthoMatrix(draw_data->DisplaySize.x, draw_data->DisplaySize.y, draw->GetDeviceCaps().coordConvention); + Lin::Matrix4x4 mtx = ComputeOrthoMatrix(drawData->DisplaySize.x, drawData->DisplaySize.y, draw->GetDeviceCaps().coordConvention); Draw::VsTexColUB ub{}; memcpy(ub.WorldViewProj, mtx.getReadPtr(), sizeof(Lin::Matrix4x4)); @@ -63,10 +88,11 @@ void ImGui_ImplThin3d_RenderDrawData(ImDrawData* draw_data, Draw::DrawContext *d // Avoid rendering when minimized, scale coordinates for retina displays (screen coordinates != framebuffer coordinates) int fb_width = (int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x); int fb_height = (int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y); - if (fb_width <= 0 || fb_height <= 0) + if (fb_width <= 0 || fb_height <= 0) { return; + } - ImGui_ImplThin3d_Data* bd = ImGui_ImplThin3d_GetBackendData(); + BackendData* bd = ImGui_ImplThin3d_GetBackendData(); // Setup desired Vulkan state ImGui_ImplThin3d_SetupRenderState(draw, draw_data, bd->pipeline, fb_width, fb_height); @@ -77,6 +103,8 @@ void ImGui_ImplThin3d_RenderDrawData(ImDrawData* draw_data, Draw::DrawContext *d _assert_(sizeof(ImDrawIdx) == 2); + ImTextureID prev = (ImTextureID)-1; + std::vector draws; // Render command lists for (int n = 0; n < draw_data->CmdListsCount; n++) { @@ -84,6 +112,10 @@ void ImGui_ImplThin3d_RenderDrawData(ImDrawData* draw_data, Draw::DrawContext *d draws.clear(); for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++) { const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i]; + if (pcmd->TextureId != prev) { + BindTexture(draw, bd, pcmd->TextureId); + prev = pcmd->TextureId; + } 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.) @@ -119,10 +151,13 @@ void ImGui_ImplThin3d_RenderDrawData(ImDrawData* draw_data, Draw::DrawContext *d } draw->SetScissorRect(0, 0, fb_width, fb_height); + + // Discard temp textures. + bd->tempTextures.clear(); } bool ImGui_ImplThin3d_CreateDeviceObjects(Draw::DrawContext *draw) { - ImGui_ImplThin3d_Data* bd = ImGui_ImplThin3d_GetBackendData(); + BackendData* bd = ImGui_ImplThin3d_GetBackendData(); if (!bd->fontSampler) { // Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling. @@ -138,7 +173,7 @@ bool ImGui_ImplThin3d_CreateDeviceObjects(Draw::DrawContext *draw) { } if (!bd->pipeline) { - ImGui_ImplThin3d_Data* bd = ImGui_ImplThin3d_GetBackendData(); + BackendData* bd = ImGui_ImplThin3d_GetBackendData(); using namespace Draw; @@ -179,7 +214,7 @@ bool ImGui_ImplThin3d_CreateDeviceObjects(Draw::DrawContext *draw) { if (!bd->fontImage) { ImGuiIO& io = ImGui::GetIO(); - ImGui_ImplThin3d_Data* bd = ImGui_ImplThin3d_GetBackendData(); + BackendData* bd = ImGui_ImplThin3d_GetBackendData(); unsigned char* pixels; int width, height; @@ -204,7 +239,7 @@ bool ImGui_ImplThin3d_CreateDeviceObjects(Draw::DrawContext *draw) { void ImGui_ImplThin3d_DestroyDeviceObjects() { ImGuiIO& io = ImGui::GetIO(); - ImGui_ImplThin3d_Data* bd = ImGui_ImplThin3d_GetBackendData(); + BackendData* bd = ImGui_ImplThin3d_GetBackendData(); if (bd->fontImage) { bd->fontImage->Release(); bd->fontImage = nullptr; @@ -236,7 +271,7 @@ bool ImGui_ImplThin3d_Init(Draw::DrawContext *draw, const uint8_t *ttf_font, siz IM_ASSERT(io.BackendRendererUserData == nullptr && "Already initialized a renderer backend!"); // Setup backend capabilities flags - ImGui_ImplThin3d_Data* bd = IM_NEW(ImGui_ImplThin3d_Data)(); + BackendData* bd = IM_NEW(BackendData)(); io.BackendRendererUserData = (void*)bd; io.BackendRendererName = "imgui_impl_thin3d"; io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes. @@ -253,7 +288,7 @@ void ImGui_PopFont() { } void ImGui_ImplThin3d_Shutdown() { - ImGui_ImplThin3d_Data* bd = ImGui_ImplThin3d_GetBackendData(); + BackendData* bd = ImGui_ImplThin3d_GetBackendData(); IM_ASSERT(bd != nullptr && "No renderer backend to shutdown, or already shutdown?"); ImGuiIO& io = ImGui::GetIO(); @@ -265,7 +300,7 @@ void ImGui_ImplThin3d_Shutdown() { } void ImGui_ImplThin3d_NewFrame(Draw::DrawContext *draw, Lin::Matrix4x4 drawMatrix) { - ImGui_ImplThin3d_Data* bd = ImGui_ImplThin3d_GetBackendData(); + BackendData* bd = ImGui_ImplThin3d_GetBackendData(); IM_ASSERT(bd != nullptr && "Context or backend not initialized! Did you call ImGui_ImplThin3d_Init()?"); // This one checks if objects already have been created, so ok to call every time. @@ -273,13 +308,22 @@ void ImGui_ImplThin3d_NewFrame(Draw::DrawContext *draw, Lin::Matrix4x4 drawMatri g_drawMatrix = drawMatrix; } -// Register a texture. No-op. -ImTextureID ImGui_ImplThin3d_AddTexture(Draw::Texture *texture) { - ImGui_ImplThin3d_Data* bd = ImGui_ImplThin3d_GetBackendData(); - return (void *)texture; +ImTextureID ImGui_ImplThin3d_AddTextureTemp(Draw::Texture *texture) { + BackendData* bd = ImGui_ImplThin3d_GetBackendData(); + + RegisteredTexture tex{ false }; + tex.texture = texture; + + bd->tempTextures.push_back(tex); + return (ImTextureID)(uint64_t)(TEX_ID_OFFSET + bd->tempTextures.size() - 1); } -// Unregister a texture. No-op. -Draw::Texture *ImGui_ImplThin3d_RemoveTexture(ImTextureID tex) { - return (Draw::Texture *)tex; +ImTextureID ImGui_ImplThin3d_AddFBAsTextureTemp(Draw::Framebuffer *framebuffer) { + BackendData* bd = ImGui_ImplThin3d_GetBackendData(); + + RegisteredTexture tex{ true }; + tex.framebuffer = framebuffer; + + bd->tempTextures.push_back(tex); + return (ImTextureID)(uint64_t)(TEX_ID_OFFSET + bd->tempTextures.size() - 1); } diff --git a/ext/imgui/imgui_impl_thin3d.h b/ext/imgui/imgui_impl_thin3d.h index 6c86a4589c..98cad60f64 100644 --- a/ext/imgui/imgui_impl_thin3d.h +++ b/ext/imgui/imgui_impl_thin3d.h @@ -41,8 +41,10 @@ IMGUI_IMPL_API void ImGui_ImplThin3d_RenderDrawData(ImDrawData* draw_dat IMGUI_IMPL_API bool ImGui_ImplThin3d_CreateDeviceObjects(Draw::DrawContext *draw); IMGUI_IMPL_API void ImGui_ImplThin3d_DestroyDeviceObjects(); -IMGUI_IMPL_API ImTextureID ImGui_ImplThin3d_AddTexture(Draw::Texture *texture); -IMGUI_IMPL_API Draw::Texture *ImGui_ImplThin3d_RemoveTexture(ImTextureID texture); +// These register a texture for imgui drawing, but just for the current frame. +// Textures are unregistered again in RenderDrawData. This is just simpler. +IMGUI_IMPL_API ImTextureID ImGui_ImplThin3d_AddTextureTemp(Draw::Texture *texture); +IMGUI_IMPL_API ImTextureID ImGui_ImplThin3d_AddFBAsTextureTemp(Draw::Framebuffer *framebuffer); void ImGui_PushFixedFont(); void ImGui_PopFont();