Browse Source

Backends: SDLGPU3: added ImGuiBackendFlags_RendererHasTextures support.

# Conflicts:
#	backends/imgui_impl_sdlgpu3.cpp
#	backends/imgui_impl_sdlgpu3.h
ocornut 9 months ago
parent
commit
16fe666e36
2 changed files with 129 additions and 85 deletions
  1. 123 82
      backends/imgui_impl_sdlgpu3.cpp
  2. 6 3
      backends/imgui_impl_sdlgpu3.h

+ 123 - 82
backends/imgui_impl_sdlgpu3.cpp

@@ -3,7 +3,8 @@
 
 // Implemented features:
 //  [X] Renderer: User texture binding. Use simply cast a reference to your SDL_GPUTextureSamplerBinding to ImTextureID.
-//  [X] Renderer: Large meshes support (64k+ vertices) with 16-bit indices.
+//  [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset).
+//  [X] Renderer: Texture updates support for dynamic font system (ImGuiBackendFlags_RendererHasTextures).
 
 // The aim of imgui_impl_sdlgpu3.h/.cpp is to be usable in your engine without any modification.
 // IF YOU FEEL YOU NEED TO MAKE ANY CHANGE TO THIS CODE, please share them and your feedback at https://github.com/ocornut/imgui/
@@ -21,6 +22,7 @@
 //   Calling the function is MANDATORY, otherwise the ImGui will not upload neither the vertex nor the index buffer for the GPU. See imgui_impl_sdlgpu3.cpp for more info.
 
 // CHANGELOG
+//  2025-06-11: Added support for ImGuiBackendFlags_RendererHasTextures, for dynamic font atlas. Removed ImGui_ImplSDLGPU3_CreateFontsTexture() and ImGui_ImplSDLGPU3_DestroyFontsTexture().
 //  2025-04-28: Added support for special ImDrawCallback_ResetRenderState callback to reset render state.
 //  2025-03-30: Made ImGui_ImplSDLGPU3_PrepareDrawData() reuse GPU Transfer Buffers which were unusually slow to recreate every frame. Much faster now.
 //  2025-03-21: Fixed typo in function name Imgui_ImplSDLGPU3_PrepareDrawData() -> ImGui_ImplSDLGPU3_PrepareDrawData().
@@ -33,6 +35,11 @@
 #include "imgui_impl_sdlgpu3_shaders.h"
 
 // SDL_GPU Data
+struct ImGui_ImplSDLGPU3_Texture
+{
+    SDL_GPUTexture*              Texture = nullptr;
+    SDL_GPUTextureSamplerBinding TextureSamplerBinding = { nullptr, nullptr };
+};
 
 // Reusable buffers used for rendering 1 current in-flight frame, for ImGui_ImplSDLGPU3_RenderDrawData()
 struct ImGui_ImplSDLGPU3_FrameData
@@ -50,14 +57,12 @@ struct ImGui_ImplSDLGPU3_Data
     ImGui_ImplSDLGPU3_InitInfo   InitInfo;
 
     // Graphics pipeline & shaders
-    SDL_GPUShader*               VertexShader   = nullptr;
-    SDL_GPUShader*               FragmentShader = nullptr;
-    SDL_GPUGraphicsPipeline*     Pipeline       = nullptr;
-
-    // Font data
-    SDL_GPUSampler*              FontSampler = nullptr;
-    SDL_GPUTexture*              FontTexture = nullptr;
-    SDL_GPUTextureSamplerBinding FontBinding = { nullptr, nullptr };
+    SDL_GPUShader*               VertexShader           = nullptr;
+    SDL_GPUShader*               FragmentShader         = nullptr;
+    SDL_GPUGraphicsPipeline*     Pipeline               = nullptr;
+    SDL_GPUSampler*              TexSampler             = nullptr;
+    SDL_GPUTransferBuffer*       TexTransferBuffer      = nullptr;
+    uint32_t                     TexTransferBufferSize  = 0;
 
     // Frame data for main window
     ImGui_ImplSDLGPU3_FrameData  MainWindowFrameData;
@@ -154,6 +159,13 @@ void ImGui_ImplSDLGPU3_PrepareDrawData(ImDrawData* draw_data, SDL_GPUCommandBuff
     if (fb_width <= 0 || fb_height <= 0 || draw_data->TotalVtxCount <= 0)
         return;
 
+    // Catch up with texture updates. Most of the times, the list will have 1 element with an OK status, aka nothing to do.
+    // (This almost always points to ImGui::GetPlatformIO().Textures[] but is part of ImDrawData to allow overriding or disabling texture updates).
+    if (draw_data->Textures != nullptr)
+        for (ImTextureData* tex : *draw_data->Textures)
+            if (tex->Status != ImTextureStatus_OK)
+                ImGui_ImplSDLGPU3_UpdateTexture(tex);
+
     ImGui_ImplSDLGPU3_Data* bd = ImGui_ImplSDLGPU3_GetBackendData();
     ImGui_ImplSDLGPU3_InitInfo* v = &bd->InitInfo;
     ImGui_ImplSDLGPU3_FrameData* fd = &bd->MainWindowFrameData;
@@ -281,91 +293,117 @@ void ImGui_ImplSDLGPU3_RenderDrawData(ImDrawData* draw_data, SDL_GPUCommandBuffe
     SDL_SetGPUScissor(render_pass, &scissor_rect);
 }
 
-void ImGui_ImplSDLGPU3_CreateFontsTexture()
+static void ImGui_ImplSDLGPU3_DestroyTexture(ImTextureData* tex)
+{
+    ImGui_ImplSDLGPU3_Data* bd = ImGui_ImplSDLGPU3_GetBackendData();
+    ImGui_ImplSDLGPU3_Texture* backend_tex = (ImGui_ImplSDLGPU3_Texture*)tex->BackendUserData;
+    if (backend_tex == nullptr)
+        return;
+    SDL_GPUTextureSamplerBinding* binding = (SDL_GPUTextureSamplerBinding*)(intptr_t)tex->BackendUserData;
+    IM_ASSERT(backend_tex->Texture == binding->texture);
+    SDL_ReleaseGPUTexture(bd->InitInfo.Device, backend_tex->Texture);
+    IM_DELETE(backend_tex);
+
+    // Clear identifiers and mark as destroyed (in order to allow e.g. calling InvalidateDeviceObjects while running)
+    tex->SetTexID(ImTextureID_Invalid);
+    tex->SetStatus(ImTextureStatus_Destroyed);
+    tex->BackendUserData = nullptr;
+}
+
+void ImGui_ImplSDLGPU3_UpdateTexture(ImTextureData* tex)
 {
-    ImGuiIO& io = ImGui::GetIO();
     ImGui_ImplSDLGPU3_Data* bd = ImGui_ImplSDLGPU3_GetBackendData();
     ImGui_ImplSDLGPU3_InitInfo* v = &bd->InitInfo;
 
-    // Destroy existing texture (if any)
-    if (bd->FontTexture)
+    if (tex->Status == ImTextureStatus_WantCreate)
     {
-        SDL_WaitForGPUIdle(v->Device);
-        ImGui_ImplSDLGPU3_DestroyFontsTexture();
-    }
-
-    unsigned char* pixels;
-    int width, height;
-    io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height);
-    uint32_t upload_size = width * height * 4 * sizeof(char);
+        // Create and upload new texture to graphics system
+        //IMGUI_DEBUG_LOG("UpdateTexture #%03d: WantCreate %dx%d\n", tex->UniqueID, tex->Width, tex->Height);
+        IM_ASSERT(tex->TexID == ImTextureID_Invalid && tex->BackendUserData == nullptr);
+        IM_ASSERT(tex->Format == ImTextureFormat_RGBA32);
+        ImGui_ImplSDLGPU3_Texture* backend_tex = IM_NEW(ImGui_ImplSDLGPU3_Texture)();
 
-    // Create the Image:
-    {
+        // Create texture
         SDL_GPUTextureCreateInfo texture_info = {};
-        texture_info.type   = SDL_GPU_TEXTURETYPE_2D;
+        texture_info.type = SDL_GPU_TEXTURETYPE_2D;
         texture_info.format = SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM;
         texture_info.usage = SDL_GPU_TEXTUREUSAGE_SAMPLER;
-        texture_info.width  = width;
-        texture_info.height = height;
+        texture_info.width = tex->Width;
+        texture_info.height = tex->Height;
         texture_info.layer_count_or_depth = 1;
         texture_info.num_levels = 1;
         texture_info.sample_count = SDL_GPU_SAMPLECOUNT_1;
 
-        bd->FontTexture = SDL_CreateGPUTexture(v->Device, &texture_info);
-        IM_ASSERT(bd->FontTexture && "Failed to create font texture, call SDL_GetError() for more info");
-    }
+        backend_tex->Texture = SDL_CreateGPUTexture(v->Device, &texture_info);
+        backend_tex->TextureSamplerBinding.texture = backend_tex->Texture;
+        backend_tex->TextureSamplerBinding.sampler = bd->TexSampler;
+        IM_ASSERT(backend_tex->Texture && "Failed to create font texture, call SDL_GetError() for more info");
 
-    // Assign the texture to the TextureSamplerBinding
-    bd->FontBinding.texture = bd->FontTexture;
+        // Store identifiers
+        tex->SetTexID((ImTextureID)(intptr_t)&backend_tex->TextureSamplerBinding);
+        tex->BackendUserData = backend_tex;
+    }
 
-    // Create all the upload structures and upload:
+    if (tex->Status == ImTextureStatus_WantCreate || tex->Status == ImTextureStatus_WantUpdates)
     {
-        SDL_GPUTransferBufferCreateInfo transferbuffer_info = {};
-        transferbuffer_info.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD;
-        transferbuffer_info.size = upload_size;
-
-        SDL_GPUTransferBuffer* transferbuffer = SDL_CreateGPUTransferBuffer(v->Device, &transferbuffer_info);
-        IM_ASSERT(transferbuffer != nullptr && "Failed to create font transfer buffer, call SDL_GetError() for more information");
+        ImGui_ImplSDLGPU3_Texture* backend_tex = (ImGui_ImplSDLGPU3_Texture*)tex->BackendUserData;
+        IM_ASSERT(tex->Format == ImTextureFormat_RGBA32);
+
+        // Update full texture or selected blocks. We only ever write to textures regions which have never been used before!
+        // This backend choose to use tex->UpdateRect but you can use tex->Updates[] to upload individual regions.
+        // We could use the smaller rect on _WantCreate but using the full rect allows us to clear the texture.
+        const int upload_x = (tex->Status == ImTextureStatus_WantCreate) ? 0 : tex->UpdateRect.x;
+        const int upload_y = (tex->Status == ImTextureStatus_WantCreate) ? 0 : tex->UpdateRect.y;
+        const int upload_w = (tex->Status == ImTextureStatus_WantCreate) ? tex->Width : tex->UpdateRect.w;
+        const int upload_h = (tex->Status == ImTextureStatus_WantCreate) ? tex->Height : tex->UpdateRect.h;
+        uint32_t upload_pitch = upload_w * tex->BytesPerPixel;
+        uint32_t upload_size = upload_w * upload_h * tex->BytesPerPixel;
+
+        // Create transfer buffer
+        if (bd->TexTransferBufferSize < upload_size)
+        {
+            SDL_ReleaseGPUTransferBuffer(v->Device, bd->TexTransferBuffer);
+            SDL_GPUTransferBufferCreateInfo transferbuffer_info = {};
+            transferbuffer_info.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD;
+            transferbuffer_info.size = upload_size + 1024;
+            bd->TexTransferBufferSize = upload_size + 1024;
+            bd->TexTransferBuffer = SDL_CreateGPUTransferBuffer(v->Device, &transferbuffer_info);
+            IM_ASSERT(bd->TexTransferBuffer != nullptr && "Failed to create font transfer buffer, call SDL_GetError() for more information");
+        }
 
-        void* texture_ptr = SDL_MapGPUTransferBuffer(v->Device, transferbuffer, false);
-        memcpy(texture_ptr, pixels, upload_size);
-        SDL_UnmapGPUTransferBuffer(v->Device, transferbuffer);
+        // Copy to transfer buffer
+        {
+            void* texture_ptr = SDL_MapGPUTransferBuffer(v->Device, bd->TexTransferBuffer, false);
+            for (int y = 0; y < upload_h; y++)
+                memcpy((void*)((uintptr_t)texture_ptr + y * upload_pitch), tex->GetPixelsAt(upload_x, upload_y + y), upload_pitch);
+            SDL_UnmapGPUTransferBuffer(v->Device, bd->TexTransferBuffer);
+        }
 
         SDL_GPUTextureTransferInfo transfer_info = {};
         transfer_info.offset = 0;
-        transfer_info.transfer_buffer = transferbuffer;
+        transfer_info.transfer_buffer = bd->TexTransferBuffer;
 
         SDL_GPUTextureRegion texture_region = {};
-        texture_region.texture = bd->FontTexture;
-        texture_region.w = width;
-        texture_region.h = height;
+        texture_region.texture = backend_tex->Texture;
+        texture_region.x = (Uint32)upload_x;
+        texture_region.y = (Uint32)upload_y;
+        texture_region.w = (Uint32)upload_w;
+        texture_region.h = (Uint32)upload_h;
         texture_region.d = 1;
 
-        SDL_GPUCommandBuffer* cmd = SDL_AcquireGPUCommandBuffer(v->Device);
-        SDL_GPUCopyPass* copy_pass = SDL_BeginGPUCopyPass(cmd);
-        SDL_UploadToGPUTexture(copy_pass, &transfer_info, &texture_region, false);
-        SDL_EndGPUCopyPass(copy_pass);
-        SDL_SubmitGPUCommandBuffer(cmd);
-        SDL_ReleaseGPUTransferBuffer(v->Device, transferbuffer);
-    }
-
-    // Store our identifier
-    io.Fonts->SetTexID((ImTextureID)&bd->FontBinding);
-}
+        // Upload
+        {
+            SDL_GPUCommandBuffer* cmd = SDL_AcquireGPUCommandBuffer(v->Device);
+            SDL_GPUCopyPass* copy_pass = SDL_BeginGPUCopyPass(cmd);
+            SDL_UploadToGPUTexture(copy_pass, &transfer_info, &texture_region, false);
+            SDL_EndGPUCopyPass(copy_pass);
+            SDL_SubmitGPUCommandBuffer(cmd);
+        }
 
-// You probably never need to call this, as it is called by ImGui_ImplSDLGPU3_CreateFontsTexture() and ImGui_ImplSDLGPU3_Shutdown().
-void ImGui_ImplSDLGPU3_DestroyFontsTexture()
-{
-    ImGuiIO& io = ImGui::GetIO();
-    ImGui_ImplSDLGPU3_Data* bd = ImGui_ImplSDLGPU3_GetBackendData();
-    ImGui_ImplSDLGPU3_InitInfo* v = &bd->InitInfo;
-    if (bd->FontTexture)
-    {
-        SDL_ReleaseGPUTexture(v->Device, bd->FontTexture);
-        bd->FontBinding.texture = nullptr;
-        bd->FontTexture = nullptr;
+        tex->SetStatus(ImTextureStatus_OK);
     }
-    io.Fonts->SetTexID(0);
+    if (tex->Status == ImTextureStatus_WantDestroy && tex->UnusedFrames > 0)
+        ImGui_ImplSDLGPU3_DestroyTexture(tex);
 }
 
 static void ImGui_ImplSDLGPU3_CreateShaders()
@@ -517,7 +555,9 @@ void ImGui_ImplSDLGPU3_CreateDeviceObjects()
     ImGui_ImplSDLGPU3_Data* bd = ImGui_ImplSDLGPU3_GetBackendData();
     ImGui_ImplSDLGPU3_InitInfo* v = &bd->InitInfo;
 
-    if (!bd->FontSampler)
+    ImGui_ImplSDLGPU3_DestroyDeviceObjects();
+
+    if (bd->TexSampler == nullptr)
     {
         // Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling.
         SDL_GPUSamplerCreateInfo sampler_info = {};
@@ -534,13 +574,11 @@ void ImGui_ImplSDLGPU3_CreateDeviceObjects()
         sampler_info.max_anisotropy = 1.0f;
         sampler_info.enable_compare = false;
 
-        bd->FontSampler = SDL_CreateGPUSampler(v->Device, &sampler_info);
-        bd->FontBinding.sampler = bd->FontSampler;
-        IM_ASSERT(bd->FontSampler != nullptr && "Failed to create font sampler, call SDL_GetError() for more information");
+        bd->TexSampler = SDL_CreateGPUSampler(v->Device, &sampler_info);
+        IM_ASSERT(bd->TexSampler != nullptr && "Failed to create font sampler, call SDL_GetError() for more information");
     }
 
     ImGui_ImplSDLGPU3_CreateGraphicsPipeline();
-    ImGui_ImplSDLGPU3_CreateFontsTexture();
 }
 
 void ImGui_ImplSDLGPU3_DestroyFrameData()
@@ -564,12 +602,16 @@ void ImGui_ImplSDLGPU3_DestroyDeviceObjects()
     ImGui_ImplSDLGPU3_InitInfo* v = &bd->InitInfo;
 
     ImGui_ImplSDLGPU3_DestroyFrameData();
-    ImGui_ImplSDLGPU3_DestroyFontsTexture();
 
-    if (bd->VertexShader)   { SDL_ReleaseGPUShader(v->Device, bd->VertexShader); bd->VertexShader = nullptr;}
-    if (bd->FragmentShader) { SDL_ReleaseGPUShader(v->Device, bd->FragmentShader); bd->FragmentShader = nullptr;}
-    if (bd->FontSampler)    { SDL_ReleaseGPUSampler(v->Device, bd->FontSampler); bd->FontSampler = nullptr;}
-    if (bd->Pipeline)       { SDL_ReleaseGPUGraphicsPipeline(v->Device, bd->Pipeline); bd->Pipeline = nullptr;}
+    // Destroy all textures
+    for (ImTextureData* tex : ImGui::GetPlatformIO().Textures)
+        if (tex->RefCount == 1)
+            ImGui_ImplSDLGPU3_DestroyTexture(tex);
+    if (bd->TexTransferBuffer)  { SDL_ReleaseGPUTransferBuffer(v->Device, bd->TexTransferBuffer); bd->TexTransferBuffer = nullptr; }
+    if (bd->VertexShader)       { SDL_ReleaseGPUShader(v->Device, bd->VertexShader); bd->VertexShader = nullptr; }
+    if (bd->FragmentShader)     { SDL_ReleaseGPUShader(v->Device, bd->FragmentShader); bd->FragmentShader = nullptr; }
+    if (bd->TexSampler)         { SDL_ReleaseGPUSampler(v->Device, bd->TexSampler); bd->TexSampler = nullptr; }
+    if (bd->Pipeline)           { SDL_ReleaseGPUGraphicsPipeline(v->Device, bd->Pipeline); bd->Pipeline = nullptr; }
 }
 
 bool ImGui_ImplSDLGPU3_Init(ImGui_ImplSDLGPU3_InitInfo* info)
@@ -583,6 +625,7 @@ bool ImGui_ImplSDLGPU3_Init(ImGui_ImplSDLGPU3_InitInfo* info)
     io.BackendRendererUserData = (void*)bd;
     io.BackendRendererName = "imgui_impl_sdlgpu3";
     io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset;  // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes.
+    io.BackendFlags |= ImGuiBackendFlags_RendererHasTextures;   // We can honor ImGuiPlatformIO::Textures[] requests during render.
 
     IM_ASSERT(info->Device != nullptr);
     IM_ASSERT(info->ColorTargetFormat != SDL_GPU_TEXTUREFORMAT_INVALID);
@@ -601,7 +644,7 @@ void ImGui_ImplSDLGPU3_Shutdown()
     ImGui_ImplSDLGPU3_DestroyDeviceObjects();
     io.BackendRendererName = nullptr;
     io.BackendRendererUserData = nullptr;
-    io.BackendFlags &= ~ImGuiBackendFlags_RendererHasVtxOffset;
+    io.BackendFlags &= ~(ImGuiBackendFlags_RendererHasVtxOffset | ImGuiBackendFlags_RendererHasTextures);
     IM_DELETE(bd);
 }
 
@@ -610,10 +653,8 @@ void ImGui_ImplSDLGPU3_NewFrame()
     ImGui_ImplSDLGPU3_Data* bd = ImGui_ImplSDLGPU3_GetBackendData();
     IM_ASSERT(bd != nullptr && "Context or backend not initialized! Did you call ImGui_ImplSDLGPU3_Init()?");
 
-    if (!bd->FontSampler)
+    if (!bd->TexSampler)
         ImGui_ImplSDLGPU3_CreateDeviceObjects();
-    if (!bd->FontTexture)
-        ImGui_ImplSDLGPU3_CreateFontsTexture();
 }
 
 #endif // #ifndef IMGUI_DISABLE

+ 6 - 3
backends/imgui_impl_sdlgpu3.h

@@ -3,7 +3,8 @@
 
 // Implemented features:
 //  [X] Renderer: User texture binding. Use simply cast a reference to your SDL_GPUTextureSamplerBinding to ImTextureID.
-//  [X] Renderer: Large meshes support (64k+ vertices) with 16-bit indices.
+//  [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset).
+//  [X] Renderer: Texture updates support for dynamic font system (ImGuiBackendFlags_RendererHasTextures).
 
 // The aim of imgui_impl_sdlgpu3.h/.cpp is to be usable in your engine without any modification.
 // IF YOU FEEL YOU NEED TO MAKE ANY CHANGE TO THIS CODE, please share them and your feedback at https://github.com/ocornut/imgui/
@@ -41,9 +42,11 @@ IMGUI_IMPL_API void     ImGui_ImplSDLGPU3_NewFrame();
 IMGUI_IMPL_API void     ImGui_ImplSDLGPU3_PrepareDrawData(ImDrawData* draw_data, SDL_GPUCommandBuffer* command_buffer);
 IMGUI_IMPL_API void     ImGui_ImplSDLGPU3_RenderDrawData(ImDrawData* draw_data, SDL_GPUCommandBuffer* command_buffer, SDL_GPURenderPass* render_pass, SDL_GPUGraphicsPipeline* pipeline = nullptr);
 
+// Use if you want to reset your rendering device without losing Dear ImGui state.
 IMGUI_IMPL_API void     ImGui_ImplSDLGPU3_CreateDeviceObjects();
 IMGUI_IMPL_API void     ImGui_ImplSDLGPU3_DestroyDeviceObjects();
-IMGUI_IMPL_API void     ImGui_ImplSDLGPU3_CreateFontsTexture();
-IMGUI_IMPL_API void     ImGui_ImplSDLGPU3_DestroyFontsTexture();
+
+// (Advanced) Use e.g. if you need to precisely control the timing of texture updates (e.g. for staged rendering), by setting ImDrawData::Textures = NULL to handle this manually.
+IMGUI_IMPL_API void     ImGui_ImplSDLGPU3_UpdateTexture(ImTextureData* tex);
 
 #endif // #ifndef IMGUI_DISABLE