From 16fe666e364785c0a9b534ea18aa5fa62ab06a36 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 16 Jan 2025 12:32:56 +0100 Subject: [PATCH] Backends: SDLGPU3: added ImGuiBackendFlags_RendererHasTextures support. # Conflicts: # backends/imgui_impl_sdlgpu3.cpp # backends/imgui_impl_sdlgpu3.h --- backends/imgui_impl_sdlgpu3.cpp | 203 +++++++++++++++++++------------- backends/imgui_impl_sdlgpu3.h | 9 +- 2 files changed, 128 insertions(+), 84 deletions(-) diff --git a/backends/imgui_impl_sdlgpu3.cpp b/backends/imgui_impl_sdlgpu3.cpp index 4bcc5bdfc..92515602b 100644 --- a/backends/imgui_impl_sdlgpu3.cpp +++ b/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(); - } + // 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)(); - unsigned char* pixels; - int width, height; - io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); - uint32_t upload_size = width * height * 4 * sizeof(char); - - // 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"); + + // Store identifiers + tex->SetTexID((ImTextureID)(intptr_t)&backend_tex->TextureSamplerBinding); + tex->BackendUserData = backend_tex; } - // Assign the texture to the TextureSamplerBinding - bd->FontBinding.texture = bd->FontTexture; - - // 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; + ImGui_ImplSDLGPU3_Texture* backend_tex = (ImGui_ImplSDLGPU3_Texture*)tex->BackendUserData; + IM_ASSERT(tex->Format == ImTextureFormat_RGBA32); - 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"); + // 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; - void* texture_ptr = SDL_MapGPUTransferBuffer(v->Device, transferbuffer, false); - memcpy(texture_ptr, pixels, upload_size); - SDL_UnmapGPUTransferBuffer(v->Device, transferbuffer); + // 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"); + } + + // 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); - } + // 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); + } - // Store our identifier - io.Fonts->SetTexID((ImTextureID)&bd->FontBinding); -} - -// 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 diff --git a/backends/imgui_impl_sdlgpu3.h b/backends/imgui_impl_sdlgpu3.h index c6d5a8e5d..380855b29 100644 --- a/backends/imgui_impl_sdlgpu3.h +++ b/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