Skip to content

Commit 571dae9

Browse files
committed
Backends: WGPU: added ImGuiBackendFlags_RendererHasTextures support. (#8465)
1 parent b178fd4 commit 571dae9

File tree

3 files changed

+86
-54
lines changed

3 files changed

+86
-54
lines changed

backends/imgui_impl_wgpu.cpp

Lines changed: 81 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,7 @@
66
// [X] Renderer: User texture binding. Use 'WGPUTextureView' as ImTextureID. Read the FAQ about ImTextureID/ImTextureRef!
77
// [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset).
88
// [X] Renderer: Expose selected render state for draw callbacks to use. Access in '(ImGui_ImplXXXX_RenderState*)GetPlatformIO().Renderer_RenderState'.
9-
// Missing features or Issues:
10-
// [ ] Renderer: Missing texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures).
9+
// [X] Renderer: Texture updates support for dynamic font system (ImGuiBackendFlags_RendererHasTextures).
1110

1211
// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
1312
// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
@@ -19,6 +18,7 @@
1918

2019
// CHANGELOG
2120
// (minor and older changes stripped away, please see git history for details)
21+
// 2025-06-12: Added support for ImGuiBackendFlags_RendererHasTextures, for dynamic font atlas. (#8465)
2222
// 2025-02-26: Recreate image bind groups during render. (#8426, #8046, #7765, #8027) + Update for latest webgpu-native changes.
2323
// 2024-10-14: Update Dawn support for change of string usages. (#8082, #8083)
2424
// 2024-10-07: Expose selected render state in ImGui_ImplWGPU_RenderState, which you can access in 'void* platform_io.Renderer_RenderState' during draw callbacks.
@@ -73,11 +73,15 @@ extern ImGuiID ImHashData(const void* data_p, size_t data_size, ImU32 seed);
7373
#define MEMALIGN(_SIZE,_ALIGN) (((_SIZE) + ((_ALIGN) - 1)) & ~((_ALIGN) - 1)) // Memory align (copied from IM_ALIGN() macro).
7474

7575
// WebGPU data
76+
struct ImGui_ImplWGPU_Texture
77+
{
78+
WGPUTexture Texture = nullptr;
79+
WGPUTextureView TextureView = nullptr;
80+
};
81+
7682
struct RenderResources
7783
{
78-
WGPUTexture FontTexture = nullptr; // Font texture
79-
WGPUTextureView FontTextureView = nullptr; // Texture view for font texture
80-
WGPUSampler Sampler = nullptr; // Sampler for the font texture
84+
WGPUSampler Sampler = nullptr; // Sampler for textures
8185
WGPUBuffer Uniforms = nullptr; // Shader uniforms
8286
WGPUBindGroup CommonBindGroup = nullptr; // Resources bind-group to bind the common resources to pipeline
8387
ImGuiStorage ImageBindGroups; // Resources bind-group to bind the font/image resources to pipeline (this is a key->value map)
@@ -234,23 +238,8 @@ static void SafeRelease(WGPUShaderModule& res)
234238
wgpuShaderModuleRelease(res);
235239
res = nullptr;
236240
}
237-
static void SafeRelease(WGPUTextureView& res)
238-
{
239-
if (res)
240-
wgpuTextureViewRelease(res);
241-
res = nullptr;
242-
}
243-
static void SafeRelease(WGPUTexture& res)
244-
{
245-
if (res)
246-
wgpuTextureRelease(res);
247-
res = nullptr;
248-
}
249-
250241
static void SafeRelease(RenderResources& res)
251242
{
252-
SafeRelease(res.FontTexture);
253-
SafeRelease(res.FontTextureView);
254243
SafeRelease(res.Sampler);
255244
SafeRelease(res.Uniforms);
256245
SafeRelease(res.CommonBindGroup);
@@ -381,6 +370,13 @@ void ImGui_ImplWGPU_RenderDrawData(ImDrawData* draw_data, WGPURenderPassEncoder
381370
if (fb_width <= 0 || fb_height <= 0 || draw_data->CmdListsCount == 0)
382371
return;
383372

373+
// Catch up with texture updates. Most of the times, the list will have 1 element with an OK status, aka nothing to do.
374+
// (This almost always points to ImGui::GetPlatformIO().Textures[] but is part of ImDrawData to allow overriding or disabling texture updates).
375+
if (draw_data->Textures != nullptr)
376+
for (ImTextureData* tex : *draw_data->Textures)
377+
if (tex->Status != ImTextureStatus_OK)
378+
ImGui_ImplWGPU_UpdateTexture(tex);
379+
384380
// FIXME: Assuming that this only gets called once per frame!
385381
// If not, we can't just re-allocate the IB or VB, we'll have to do a proper allocator.
386382
ImGui_ImplWGPU_Data* bd = ImGui_ImplWGPU_GetBackendData();
@@ -536,33 +532,52 @@ void ImGui_ImplWGPU_RenderDrawData(ImDrawData* draw_data, WGPURenderPassEncoder
536532
platform_io.Renderer_RenderState = nullptr;
537533
}
538534

539-
static void ImGui_ImplWGPU_CreateFontsTexture()
535+
static void ImGui_ImplWGPU_DestroyTexture(ImTextureData* tex)
540536
{
541-
// Build texture atlas
542-
ImGui_ImplWGPU_Data* bd = ImGui_ImplWGPU_GetBackendData();
543-
ImGuiIO& io = ImGui::GetIO();
544-
unsigned char* pixels;
545-
int width, height, size_pp;
546-
io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height, &size_pp);
537+
ImGui_ImplWGPU_Texture* backend_tex = (ImGui_ImplWGPU_Texture*)tex->BackendUserData;
538+
if (backend_tex == nullptr)
539+
return;
547540

548-
// Upload texture to graphics system
541+
IM_ASSERT(backend_tex->TextureView == (WGPUTextureView)(intptr_t)tex->TexID);
542+
wgpuTextureViewRelease(backend_tex->TextureView);
543+
wgpuTextureRelease(backend_tex->Texture);
544+
IM_DELETE(backend_tex);
545+
546+
// Clear identifiers and mark as destroyed (in order to allow e.g. calling InvalidateDeviceObjects while running)
547+
tex->SetTexID(ImTextureID_Invalid);
548+
tex->SetStatus(ImTextureStatus_Destroyed);
549+
tex->BackendUserData = nullptr;
550+
}
551+
552+
void ImGui_ImplWGPU_UpdateTexture(ImTextureData* tex)
553+
{
554+
ImGui_ImplWGPU_Data* bd = ImGui_ImplWGPU_GetBackendData();
555+
if (tex->Status == ImTextureStatus_WantCreate)
549556
{
557+
// Create and upload new texture to graphics system
558+
//IMGUI_DEBUG_LOG("UpdateTexture #%03d: WantCreate %dx%d\n", tex->UniqueID, tex->Width, tex->Height);
559+
IM_ASSERT(tex->TexID == ImTextureID_Invalid && tex->BackendUserData == nullptr);
560+
IM_ASSERT(tex->Format == ImTextureFormat_RGBA32);
561+
ImGui_ImplWGPU_Texture* backend_tex = IM_NEW(ImGui_ImplWGPU_Texture)();
562+
563+
// Create texture
550564
WGPUTextureDescriptor tex_desc = {};
551565
#if defined(IMGUI_IMPL_WEBGPU_BACKEND_DAWN) || defined(IMGUI_IMPL_WEBGPU_BACKEND_WGPU)
552-
tex_desc.label = { "Dear ImGui Font Texture", WGPU_STRLEN };
566+
tex_desc.label = { "Dear ImGui Texture", WGPU_STRLEN };
553567
#else
554-
tex_desc.label = "Dear ImGui Font Texture";
568+
tex_desc.label = "Dear ImGui Texture";
555569
#endif
556570
tex_desc.dimension = WGPUTextureDimension_2D;
557-
tex_desc.size.width = width;
558-
tex_desc.size.height = height;
571+
tex_desc.size.width = tex->Width;
572+
tex_desc.size.height = tex->Height;
559573
tex_desc.size.depthOrArrayLayers = 1;
560574
tex_desc.sampleCount = 1;
561575
tex_desc.format = WGPUTextureFormat_RGBA8Unorm;
562576
tex_desc.mipLevelCount = 1;
563577
tex_desc.usage = WGPUTextureUsage_CopyDst | WGPUTextureUsage_TextureBinding;
564-
bd->renderResources.FontTexture = wgpuDeviceCreateTexture(bd->wgpuDevice, &tex_desc);
578+
backend_tex->Texture = wgpuDeviceCreateTexture(bd->wgpuDevice, &tex_desc);
565579

580+
// Create texture view
566581
WGPUTextureViewDescriptor tex_view_desc = {};
567582
tex_view_desc.format = WGPUTextureFormat_RGBA8Unorm;
568583
tex_view_desc.dimension = WGPUTextureViewDimension_2D;
@@ -571,35 +586,50 @@ static void ImGui_ImplWGPU_CreateFontsTexture()
571586
tex_view_desc.baseArrayLayer = 0;
572587
tex_view_desc.arrayLayerCount = 1;
573588
tex_view_desc.aspect = WGPUTextureAspect_All;
574-
bd->renderResources.FontTextureView = wgpuTextureCreateView(bd->renderResources.FontTexture, &tex_view_desc);
589+
backend_tex->TextureView = wgpuTextureCreateView(backend_tex->Texture, &tex_view_desc);
590+
591+
// Store identifiers
592+
tex->SetTexID((ImTextureID)(intptr_t)backend_tex->TextureView);
593+
tex->BackendUserData = backend_tex;
594+
// We don't set tex->Status to ImTextureStatus_OK to let the code fallthrough below.
575595
}
576596

577-
// Upload texture data
597+
if (tex->Status == ImTextureStatus_WantCreate || tex->Status == ImTextureStatus_WantUpdates)
578598
{
599+
ImGui_ImplWGPU_Texture* backend_tex = (ImGui_ImplWGPU_Texture*)tex->BackendUserData;
600+
IM_ASSERT(tex->Format == ImTextureFormat_RGBA32);
601+
602+
// We could use the smaller rect on _WantCreate but using the full rect allows us to clear the texture.
603+
const int upload_x = (tex->Status == ImTextureStatus_WantCreate) ? 0 : tex->UpdateRect.x;
604+
const int upload_y = (tex->Status == ImTextureStatus_WantCreate) ? 0 : tex->UpdateRect.y;
605+
const int upload_w = (tex->Status == ImTextureStatus_WantCreate) ? tex->Width : tex->UpdateRect.w;
606+
const int upload_h = (tex->Status == ImTextureStatus_WantCreate) ? tex->Height : tex->UpdateRect.h;
607+
608+
// Update full texture or selected blocks. We only ever write to textures regions which have never been used before!
609+
// This backend choose to use tex->UpdateRect but you can use tex->Updates[] to upload individual regions.
579610
#if defined(IMGUI_IMPL_WEBGPU_BACKEND_DAWN) || defined(IMGUI_IMPL_WEBGPU_BACKEND_WGPU)
580611
WGPUTexelCopyTextureInfo dst_view = {};
581612
#else
582613
WGPUImageCopyTexture dst_view = {};
583614
#endif
584-
dst_view.texture = bd->renderResources.FontTexture;
615+
dst_view.texture = backend_tex->Texture;
585616
dst_view.mipLevel = 0;
586-
dst_view.origin = { 0, 0, 0 };
617+
dst_view.origin = { (uint32_t)upload_x, (uint32_t)upload_y, 0 };
587618
dst_view.aspect = WGPUTextureAspect_All;
588619
#if defined(IMGUI_IMPL_WEBGPU_BACKEND_DAWN) || defined(IMGUI_IMPL_WEBGPU_BACKEND_WGPU)
589620
WGPUTexelCopyBufferLayout layout = {};
590621
#else
591622
WGPUTextureDataLayout layout = {};
592623
#endif
593624
layout.offset = 0;
594-
layout.bytesPerRow = width * size_pp;
595-
layout.rowsPerImage = height;
596-
WGPUExtent3D size = { (uint32_t)width, (uint32_t)height, 1 };
597-
wgpuQueueWriteTexture(bd->defaultQueue, &dst_view, pixels, (uint32_t)(width * size_pp * height), &layout, &size);
625+
layout.bytesPerRow = tex->Width * tex->BytesPerPixel;
626+
layout.rowsPerImage = upload_h;
627+
WGPUExtent3D write_size = { (uint32_t)upload_w, (uint32_t)upload_h, 1 };
628+
wgpuQueueWriteTexture(bd->defaultQueue, &dst_view, tex->GetPixelsAt(upload_x, upload_y), (uint32_t)(tex->Width * upload_h * tex->BytesPerPixel), &layout, &write_size);
629+
tex->SetStatus(ImTextureStatus_OK);
598630
}
599-
600-
// Store our identifier
601-
static_assert(sizeof(ImTextureID) >= sizeof(bd->renderResources.FontTexture), "Can't pack descriptor handle into TexID, 32-bit not supported yet.");
602-
io.Fonts->SetTexID((ImTextureID)bd->renderResources.FontTextureView);
631+
if (tex->Status == ImTextureStatus_WantDestroy && tex->UnusedFrames > 0)
632+
ImGui_ImplWGPU_DestroyTexture(tex);
603633
}
604634

605635
static void ImGui_ImplWGPU_CreateUniformBuffer()
@@ -743,7 +773,6 @@ bool ImGui_ImplWGPU_CreateDeviceObjects()
743773

744774
bd->pipelineState = wgpuDeviceCreateRenderPipeline(bd->wgpuDevice, &graphics_pipeline_desc);
745775

746-
ImGui_ImplWGPU_CreateFontsTexture();
747776
ImGui_ImplWGPU_CreateUniformBuffer();
748777

749778
// Create sampler
@@ -788,8 +817,10 @@ void ImGui_ImplWGPU_InvalidateDeviceObjects()
788817
SafeRelease(bd->pipelineState);
789818
SafeRelease(bd->renderResources);
790819

791-
ImGuiIO& io = ImGui::GetIO();
792-
io.Fonts->SetTexID(0); // We copied g_pFontTextureView to io.Fonts->TexID so let's clear that as well.
820+
// Destroy all textures
821+
for (ImTextureData* tex : ImGui::GetPlatformIO().Textures)
822+
if (tex->RefCount == 1)
823+
ImGui_ImplWGPU_DestroyTexture(tex);
793824

794825
for (unsigned int i = 0; i < bd->numFramesInFlight; i++)
795826
SafeRelease(bd->pFrameResources[i]);
@@ -814,6 +845,7 @@ bool ImGui_ImplWGPU_Init(ImGui_ImplWGPU_InitInfo* init_info)
814845
io.BackendRendererName = "imgui_impl_webgpu";
815846
#endif
816847
io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes.
848+
io.BackendFlags |= ImGuiBackendFlags_RendererHasTextures; // We can honor ImGuiPlatformIO::Textures[] requests during render.
817849

818850
bd->initInfo = *init_info;
819851
bd->wgpuDevice = init_info->Device;
@@ -823,8 +855,6 @@ bool ImGui_ImplWGPU_Init(ImGui_ImplWGPU_InitInfo* init_info)
823855
bd->numFramesInFlight = init_info->NumFramesInFlight;
824856
bd->frameIndex = UINT_MAX;
825857

826-
bd->renderResources.FontTexture = nullptr;
827-
bd->renderResources.FontTextureView = nullptr;
828858
bd->renderResources.Sampler = nullptr;
829859
bd->renderResources.Uniforms = nullptr;
830860
bd->renderResources.CommonBindGroup = nullptr;
@@ -863,7 +893,7 @@ void ImGui_ImplWGPU_Shutdown()
863893

864894
io.BackendRendererName = nullptr;
865895
io.BackendRendererUserData = nullptr;
866-
io.BackendFlags &= ~ImGuiBackendFlags_RendererHasVtxOffset;
896+
io.BackendFlags &= ~(ImGuiBackendFlags_RendererHasVtxOffset | ImGuiBackendFlags_RendererHasTextures);
867897
IM_DELETE(bd);
868898
}
869899

backends/imgui_impl_wgpu.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,7 @@
1313
// [X] Renderer: User texture binding. Use 'WGPUTextureView' as ImTextureID. Read the FAQ about ImTextureID/ImTextureRef!
1414
// [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset).
1515
// [X] Renderer: Expose selected render state for draw callbacks to use. Access in '(ImGui_ImplXXXX_RenderState*)GetPlatformIO().Renderer_RenderState'.
16-
// Missing features or Issues:
17-
// [ ] Renderer: Missing texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures).
16+
// [X] Renderer: Texture updates support for dynamic font system (ImGuiBackendFlags_RendererHasTextures).
1817

1918
// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
2019
// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
@@ -57,6 +56,9 @@ IMGUI_IMPL_API void ImGui_ImplWGPU_RenderDrawData(ImDrawData* draw_data, WGPURen
5756
IMGUI_IMPL_API bool ImGui_ImplWGPU_CreateDeviceObjects();
5857
IMGUI_IMPL_API void ImGui_ImplWGPU_InvalidateDeviceObjects();
5958

59+
// (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.
60+
IMGUI_IMPL_API void ImGui_ImplWGPU_UpdateTexture(ImTextureData* tex);
61+
6062
// [BETA] Selected render state data shared with callbacks.
6163
// This is temporarily stored in GetPlatformIO().Renderer_RenderState during the ImGui_ImplWGPU_RenderDrawData() call.
6264
// (Please open an issue if you feel you need access to more data)

docs/CHANGELOG.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -363,7 +363,7 @@ Other changes:
363363
- Misc: added extra operators to ImVec4 in IMGUI_DEFINE_MATH_OPERATORS block. (#8510) [@gan74]
364364
- Demo: changed default framed item width to use Min(GetFontSize() * 12, GetContentRegionAvail().x * 0.40f).
365365
- Backends:
366-
- Backends: DX9/DX10/DX11/DX12, Vulkan, OpenGL2/3, Metal, SDLGPU3, SDLRenderer2/3, Allegro5:
366+
- Backends: DX9/DX10/DX11/DX12, Vulkan, OpenGL2/3, Metal, SDLGPU3, SDLRenderer2/3, WebGPU, Allegro5:
367367
- Added ImGuiBackendFlags_RendererHasTextures support. (#8465, #3761, #3471)
368368
[@ocornut, @ShironekoBen, @thedmd]
369369
- Added ImGui_ImplXXXX_UpdateTexture(ImTextureData* tex) functions for all backend.

0 commit comments

Comments
 (0)