1// dear imgui: Renderer for WebGPU
2// This needs to be used along with a Platform Binding (e.g. GLFW)
3// (Please note that WebGPU is currently experimental, will not run on non-beta browsers, and may break.)
4
5// Implemented features:
6// [X] Renderer: User texture binding. Use 'WGPUTextureView' as ImTextureID. Read the FAQ about ImTextureID!
7// [X] Renderer: Large meshes support (64k+ vertices) with 16-bit indices.
8// Missing features:
9// [ ] Renderer: Multi-viewport support (multiple windows). Not meaningful on the web.
10
11// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
12// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
13// Learn about Dear ImGui:
14// - FAQ https://dearimgui.com/faq
15// - Getting Started https://dearimgui.com/getting-started
16// - Documentation https://dearimgui.com/docs (same as your local docs/ folder).
17// - Introduction, links and more at the top of imgui.cpp
18
19// CHANGELOG
20// (minor and older changes stripped away, please see git history for details)
21// 2024-01-22: Added configurable PipelineMultisampleState struct. (#7240)
22// 2024-01-22: (Breaking) ImGui_ImplWGPU_Init() now takes a ImGui_ImplWGPU_InitInfo structure instead of variety of parameters, allowing for easier further changes.
23// 2024-01-22: Fixed pipeline layout leak. (#7245)
24// 2024-01-17: Explicitly fill all of WGPUDepthStencilState since standard removed defaults.
25// 2023-07-13: Use WGPUShaderModuleWGSLDescriptor's code instead of source. use WGPUMipmapFilterMode_Linear instead of WGPUFilterMode_Linear. (#6602)
26// 2023-04-11: Align buffer sizes. Use WGSL shaders instead of precompiled SPIR-V.
27// 2023-04-11: Reorganized backend to pull data from a single structure to facilitate usage with multiple-contexts (all g_XXXX access changed to bd->XXXX).
28// 2023-01-25: Revert automatic pipeline layout generation (see https://github.com/gpuweb/gpuweb/issues/2470)
29// 2022-11-24: Fixed validation error with default depth buffer settings.
30// 2022-11-10: Fixed rendering when a depth buffer is enabled. Added 'WGPUTextureFormat depth_format' parameter to ImGui_ImplWGPU_Init().
31// 2022-10-11: Using 'nullptr' instead of 'NULL' as per our switch to C++11.
32// 2021-11-29: Passing explicit buffer sizes to wgpuRenderPassEncoderSetVertexBuffer()/wgpuRenderPassEncoderSetIndexBuffer().
33// 2021-08-24: Fixed for latest specs.
34// 2021-05-24: Add support for draw_data->FramebufferScale.
35// 2021-05-19: Replaced direct access to ImDrawCmd::TextureId with a call to ImDrawCmd::GetTexID(). (will become a requirement)
36// 2021-05-16: Update to latest WebGPU specs (compatible with Emscripten 2.0.20 and Chrome Canary 92).
37// 2021-02-18: Change blending equation to preserve alpha in output buffer.
38// 2021-01-28: Initial version.
39
40#include "imgui.h"
41#ifndef IMGUI_DISABLE
42#include "imgui_impl_wgpu.h"
43#include <limits.h>
44#include <webgpu/webgpu.h>
45
46// Dear ImGui prototypes from imgui_internal.h
47extern ImGuiID ImHashData(const void* data_p, size_t data_size, ImU32 seed = 0);
48#define MEMALIGN(_SIZE,_ALIGN) (((_SIZE) + ((_ALIGN) - 1)) & ~((_ALIGN) - 1)) // Memory align (copied from IM_ALIGN() macro).
49
50// WebGPU data
51struct RenderResources
52{
53 WGPUTexture FontTexture = nullptr; // Font texture
54 WGPUTextureView FontTextureView = nullptr; // Texture view for font texture
55 WGPUSampler Sampler = nullptr; // Sampler for the font texture
56 WGPUBuffer Uniforms = nullptr; // Shader uniforms
57 WGPUBindGroup CommonBindGroup = nullptr; // Resources bind-group to bind the common resources to pipeline
58 ImGuiStorage ImageBindGroups; // Resources bind-group to bind the font/image resources to pipeline (this is a key->value map)
59 WGPUBindGroup ImageBindGroup = nullptr; // Default font-resource of Dear ImGui
60 WGPUBindGroupLayout ImageBindGroupLayout = nullptr; // Cache layout used for the image bind group. Avoids allocating unnecessary JS objects when working with WebASM
61};
62
63struct FrameResources
64{
65 WGPUBuffer IndexBuffer;
66 WGPUBuffer VertexBuffer;
67 ImDrawIdx* IndexBufferHost;
68 ImDrawVert* VertexBufferHost;
69 int IndexBufferSize;
70 int VertexBufferSize;
71};
72
73struct Uniforms
74{
75 float MVP[4][4];
76 float Gamma;
77};
78
79struct ImGui_ImplWGPU_Data
80{
81 ImGui_ImplWGPU_InitInfo initInfo;
82 WGPUDevice wgpuDevice = nullptr;
83 WGPUQueue defaultQueue = nullptr;
84 WGPUTextureFormat renderTargetFormat = WGPUTextureFormat_Undefined;
85 WGPUTextureFormat depthStencilFormat = WGPUTextureFormat_Undefined;
86 WGPURenderPipeline pipelineState = nullptr;
87
88 RenderResources renderResources;
89 FrameResources* pFrameResources = nullptr;
90 unsigned int numFramesInFlight = 0;
91 unsigned int frameIndex = UINT_MAX;
92};
93
94// Backend data stored in io.BackendRendererUserData to allow support for multiple Dear ImGui contexts
95// It is STRONGLY preferred that you use docking branch with multi-viewports (== single Dear ImGui context + multiple windows) instead of multiple Dear ImGui contexts.
96static ImGui_ImplWGPU_Data* ImGui_ImplWGPU_GetBackendData()
97{
98 return ImGui::GetCurrentContext() ? (ImGui_ImplWGPU_Data*)ImGui::GetIO().BackendRendererUserData : nullptr;
99}
100
101//-----------------------------------------------------------------------------
102// SHADERS
103//-----------------------------------------------------------------------------
104
105static const char __shader_vert_wgsl[] = R"(
106struct VertexInput {
107 @location(0) position: vec2<f32>,
108 @location(1) uv: vec2<f32>,
109 @location(2) color: vec4<f32>,
110};
111
112struct VertexOutput {
113 @builtin(position) position: vec4<f32>,
114 @location(0) color: vec4<f32>,
115 @location(1) uv: vec2<f32>,
116};
117
118struct Uniforms {
119 mvp: mat4x4<f32>,
120 gamma: f32,
121};
122
123@group(0) @binding(0) var<uniform> uniforms: Uniforms;
124
125@vertex
126fn main(in: VertexInput) -> VertexOutput {
127 var out: VertexOutput;
128 out.position = uniforms.mvp * vec4<f32>(in.position, 0.0, 1.0);
129 out.color = in.color;
130 out.uv = in.uv;
131 return out;
132}
133)";
134
135static const char __shader_frag_wgsl[] = R"(
136struct VertexOutput {
137 @builtin(position) position: vec4<f32>,
138 @location(0) color: vec4<f32>,
139 @location(1) uv: vec2<f32>,
140};
141
142struct Uniforms {
143 mvp: mat4x4<f32>,
144 gamma: f32,
145};
146
147@group(0) @binding(0) var<uniform> uniforms: Uniforms;
148@group(0) @binding(1) var s: sampler;
149@group(1) @binding(0) var t: texture_2d<f32>;
150
151@fragment
152fn main(in: VertexOutput) -> @location(0) vec4<f32> {
153 let color = in.color * textureSample(t, s, in.uv);
154 let corrected_color = pow(color.rgb, vec3<f32>(uniforms.gamma));
155 return vec4<f32>(corrected_color, color.a);
156}
157)";
158
159static void SafeRelease(ImDrawIdx*& res)
160{
161 if (res)
162 delete[] res;
163 res = nullptr;
164}
165static void SafeRelease(ImDrawVert*& res)
166{
167 if (res)
168 delete[] res;
169 res = nullptr;
170}
171static void SafeRelease(WGPUBindGroupLayout& res)
172{
173 if (res)
174 wgpuBindGroupLayoutRelease(res);
175 res = nullptr;
176}
177static void SafeRelease(WGPUBindGroup& res)
178{
179 if (res)
180 wgpuBindGroupRelease(res);
181 res = nullptr;
182}
183static void SafeRelease(WGPUBuffer& res)
184{
185 if (res)
186 wgpuBufferRelease(res);
187 res = nullptr;
188}
189static void SafeRelease(WGPUPipelineLayout& res)
190{
191 if (res)
192 wgpuPipelineLayoutRelease(res);
193 res = nullptr;
194}
195static void SafeRelease(WGPURenderPipeline& res)
196{
197 if (res)
198 wgpuRenderPipelineRelease(res);
199 res = nullptr;
200}
201static void SafeRelease(WGPUSampler& res)
202{
203 if (res)
204 wgpuSamplerRelease(res);
205 res = nullptr;
206}
207static void SafeRelease(WGPUShaderModule& res)
208{
209 if (res)
210 wgpuShaderModuleRelease(res);
211 res = nullptr;
212}
213static void SafeRelease(WGPUTextureView& res)
214{
215 if (res)
216 wgpuTextureViewRelease(res);
217 res = nullptr;
218}
219static void SafeRelease(WGPUTexture& res)
220{
221 if (res)
222 wgpuTextureRelease(res);
223 res = nullptr;
224}
225
226static void SafeRelease(RenderResources& res)
227{
228 SafeRelease(res.FontTexture);
229 SafeRelease(res.FontTextureView);
230 SafeRelease(res.Sampler);
231 SafeRelease(res.Uniforms);
232 SafeRelease(res.CommonBindGroup);
233 SafeRelease(res.ImageBindGroup);
234 SafeRelease(res.ImageBindGroupLayout);
235};
236
237static void SafeRelease(FrameResources& res)
238{
239 SafeRelease(res.IndexBuffer);
240 SafeRelease(res.VertexBuffer);
241 SafeRelease(res&: res.IndexBufferHost);
242 SafeRelease(res&: res.VertexBufferHost);
243}
244
245static WGPUProgrammableStageDescriptor ImGui_ImplWGPU_CreateShaderModule(const char* wgsl_source)
246{
247 ImGui_ImplWGPU_Data* bd = ImGui_ImplWGPU_GetBackendData();
248
249 WGPUShaderModuleWGSLDescriptor wgsl_desc = {};
250 wgsl_desc.chain.sType = WGPUSType_ShaderModuleWGSLDescriptor;
251 wgsl_desc.code = wgsl_source;
252
253 WGPUShaderModuleDescriptor desc = {};
254 desc.nextInChain = reinterpret_cast<WGPUChainedStruct*>(&wgsl_desc);
255
256 WGPUProgrammableStageDescriptor stage_desc = {};
257 stage_desc.module = wgpuDeviceCreateShaderModule(bd->wgpuDevice, &desc);
258 stage_desc.entryPoint = "main";
259 return stage_desc;
260}
261
262static WGPUBindGroup ImGui_ImplWGPU_CreateImageBindGroup(WGPUBindGroupLayout layout, WGPUTextureView texture)
263{
264 ImGui_ImplWGPU_Data* bd = ImGui_ImplWGPU_GetBackendData();
265 WGPUBindGroupEntry image_bg_entries[] = { { nullptr, 0, 0, 0, 0, 0, texture } };
266
267 WGPUBindGroupDescriptor image_bg_descriptor = {};
268 image_bg_descriptor.layout = layout;
269 image_bg_descriptor.entryCount = sizeof(image_bg_entries) / sizeof(WGPUBindGroupEntry);
270 image_bg_descriptor.entries = image_bg_entries;
271 return wgpuDeviceCreateBindGroup(bd->wgpuDevice, &image_bg_descriptor);
272}
273
274static void ImGui_ImplWGPU_SetupRenderState(ImDrawData* draw_data, WGPURenderPassEncoder ctx, FrameResources* fr)
275{
276 ImGui_ImplWGPU_Data* bd = ImGui_ImplWGPU_GetBackendData();
277
278 // Setup orthographic projection matrix into our constant buffer
279 // Our visible imgui space lies from draw_data->DisplayPos (top left) to draw_data->DisplayPos+data_data->DisplaySize (bottom right).
280 {
281 float L = draw_data->DisplayPos.x;
282 float R = draw_data->DisplayPos.x + draw_data->DisplaySize.x;
283 float T = draw_data->DisplayPos.y;
284 float B = draw_data->DisplayPos.y + draw_data->DisplaySize.y;
285 float mvp[4][4] =
286 {
287 { 2.0f/(R-L), 0.0f, 0.0f, 0.0f },
288 { 0.0f, 2.0f/(T-B), 0.0f, 0.0f },
289 { 0.0f, 0.0f, 0.5f, 0.0f },
290 { (R+L)/(L-R), (T+B)/(B-T), 0.5f, 1.0f },
291 };
292 wgpuQueueWriteBuffer(bd->defaultQueue, bd->renderResources.Uniforms, offsetof(Uniforms, MVP), mvp, sizeof(Uniforms::MVP));
293 float gamma;
294 switch (bd->renderTargetFormat)
295 {
296 case WGPUTextureFormat_ASTC10x10UnormSrgb:
297 case WGPUTextureFormat_ASTC10x5UnormSrgb:
298 case WGPUTextureFormat_ASTC10x6UnormSrgb:
299 case WGPUTextureFormat_ASTC10x8UnormSrgb:
300 case WGPUTextureFormat_ASTC12x10UnormSrgb:
301 case WGPUTextureFormat_ASTC12x12UnormSrgb:
302 case WGPUTextureFormat_ASTC4x4UnormSrgb:
303 case WGPUTextureFormat_ASTC5x5UnormSrgb:
304 case WGPUTextureFormat_ASTC6x5UnormSrgb:
305 case WGPUTextureFormat_ASTC6x6UnormSrgb:
306 case WGPUTextureFormat_ASTC8x5UnormSrgb:
307 case WGPUTextureFormat_ASTC8x6UnormSrgb:
308 case WGPUTextureFormat_ASTC8x8UnormSrgb:
309 case WGPUTextureFormat_BC1RGBAUnormSrgb:
310 case WGPUTextureFormat_BC2RGBAUnormSrgb:
311 case WGPUTextureFormat_BC3RGBAUnormSrgb:
312 case WGPUTextureFormat_BC7RGBAUnormSrgb:
313 case WGPUTextureFormat_BGRA8UnormSrgb:
314 case WGPUTextureFormat_ETC2RGB8A1UnormSrgb:
315 case WGPUTextureFormat_ETC2RGB8UnormSrgb:
316 case WGPUTextureFormat_ETC2RGBA8UnormSrgb:
317 case WGPUTextureFormat_RGBA8UnormSrgb:
318 gamma = 2.2f;
319 break;
320 default:
321 gamma = 1.0f;
322 }
323 wgpuQueueWriteBuffer(bd->defaultQueue, bd->renderResources.Uniforms, offsetof(Uniforms, Gamma), &gamma, sizeof(Uniforms::Gamma));
324 }
325
326 // Setup viewport
327 wgpuRenderPassEncoderSetViewport(ctx, 0, 0, draw_data->FramebufferScale.x * draw_data->DisplaySize.x, draw_data->FramebufferScale.y * draw_data->DisplaySize.y, 0, 1);
328
329 // Bind shader and vertex buffers
330 wgpuRenderPassEncoderSetVertexBuffer(ctx, 0, fr->VertexBuffer, 0, fr->VertexBufferSize * sizeof(ImDrawVert));
331 wgpuRenderPassEncoderSetIndexBuffer(ctx, fr->IndexBuffer, sizeof(ImDrawIdx) == 2 ? WGPUIndexFormat_Uint16 : WGPUIndexFormat_Uint32, 0, fr->IndexBufferSize * sizeof(ImDrawIdx));
332 wgpuRenderPassEncoderSetPipeline(ctx, bd->pipelineState);
333 wgpuRenderPassEncoderSetBindGroup(ctx, 0, bd->renderResources.CommonBindGroup, 0, nullptr);
334
335 // Setup blend factor
336 WGPUColor blend_color = { 0.f, 0.f, 0.f, 0.f };
337 wgpuRenderPassEncoderSetBlendConstant(ctx, &blend_color);
338}
339
340// Render function
341// (this used to be set in io.RenderDrawListsFn and called by ImGui::Render(), but you can now call this directly from your main loop)
342void ImGui_ImplWGPU_RenderDrawData(ImDrawData* draw_data, WGPURenderPassEncoder pass_encoder)
343{
344 // Avoid rendering when minimized
345 int fb_width = (int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x);
346 int fb_height = (int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y);
347 if (fb_width <= 0 || fb_height <= 0 || draw_data->CmdListsCount == 0)
348 return;
349
350 // FIXME: Assuming that this only gets called once per frame!
351 // If not, we can't just re-allocate the IB or VB, we'll have to do a proper allocator.
352 ImGui_ImplWGPU_Data* bd = ImGui_ImplWGPU_GetBackendData();
353 bd->frameIndex = bd->frameIndex + 1;
354 FrameResources* fr = &bd->pFrameResources[bd->frameIndex % bd->numFramesInFlight];
355
356 // Create and grow vertex/index buffers if needed
357 if (fr->VertexBuffer == nullptr || fr->VertexBufferSize < draw_data->TotalVtxCount)
358 {
359 if (fr->VertexBuffer)
360 {
361 wgpuBufferDestroy(fr->VertexBuffer);
362 wgpuBufferRelease(fr->VertexBuffer);
363 }
364 SafeRelease(res&: fr->VertexBufferHost);
365 fr->VertexBufferSize = draw_data->TotalVtxCount + 5000;
366
367 WGPUBufferDescriptor vb_desc =
368 {
369 nullptr,
370 "Dear ImGui Vertex buffer",
371 WGPUBufferUsage_CopyDst | WGPUBufferUsage_Vertex,
372 MEMALIGN(fr->VertexBufferSize * sizeof(ImDrawVert), 4),
373 false
374 };
375 fr->VertexBuffer = wgpuDeviceCreateBuffer(bd->wgpuDevice, &vb_desc);
376 if (!fr->VertexBuffer)
377 return;
378
379 fr->VertexBufferHost = new ImDrawVert[fr->VertexBufferSize];
380 }
381 if (fr->IndexBuffer == nullptr || fr->IndexBufferSize < draw_data->TotalIdxCount)
382 {
383 if (fr->IndexBuffer)
384 {
385 wgpuBufferDestroy(fr->IndexBuffer);
386 wgpuBufferRelease(fr->IndexBuffer);
387 }
388 SafeRelease(res&: fr->IndexBufferHost);
389 fr->IndexBufferSize = draw_data->TotalIdxCount + 10000;
390
391 WGPUBufferDescriptor ib_desc =
392 {
393 nullptr,
394 "Dear ImGui Index buffer",
395 WGPUBufferUsage_CopyDst | WGPUBufferUsage_Index,
396 MEMALIGN(fr->IndexBufferSize * sizeof(ImDrawIdx), 4),
397 false
398 };
399 fr->IndexBuffer = wgpuDeviceCreateBuffer(bd->wgpuDevice, &ib_desc);
400 if (!fr->IndexBuffer)
401 return;
402
403 fr->IndexBufferHost = new ImDrawIdx[fr->IndexBufferSize];
404 }
405
406 // Upload vertex/index data into a single contiguous GPU buffer
407 ImDrawVert* vtx_dst = (ImDrawVert*)fr->VertexBufferHost;
408 ImDrawIdx* idx_dst = (ImDrawIdx*)fr->IndexBufferHost;
409 for (int n = 0; n < draw_data->CmdListsCount; n++)
410 {
411 const ImDrawList* cmd_list = draw_data->CmdLists[n];
412 memcpy(dest: vtx_dst, src: cmd_list->VtxBuffer.Data, n: cmd_list->VtxBuffer.Size * sizeof(ImDrawVert));
413 memcpy(dest: idx_dst, src: cmd_list->IdxBuffer.Data, n: cmd_list->IdxBuffer.Size * sizeof(ImDrawIdx));
414 vtx_dst += cmd_list->VtxBuffer.Size;
415 idx_dst += cmd_list->IdxBuffer.Size;
416 }
417 int64_t vb_write_size = MEMALIGN((char*)vtx_dst - (char*)fr->VertexBufferHost, 4);
418 int64_t ib_write_size = MEMALIGN((char*)idx_dst - (char*)fr->IndexBufferHost, 4);
419 wgpuQueueWriteBuffer(bd->defaultQueue, fr->VertexBuffer, 0, fr->VertexBufferHost, vb_write_size);
420 wgpuQueueWriteBuffer(bd->defaultQueue, fr->IndexBuffer, 0, fr->IndexBufferHost, ib_write_size);
421
422 // Setup desired render state
423 ImGui_ImplWGPU_SetupRenderState(draw_data, pass_encoder, fr);
424
425 // Render command lists
426 // (Because we merged all buffers into a single one, we maintain our own offset into them)
427 int global_vtx_offset = 0;
428 int global_idx_offset = 0;
429 ImVec2 clip_scale = draw_data->FramebufferScale;
430 ImVec2 clip_off = draw_data->DisplayPos;
431 for (int n = 0; n < draw_data->CmdListsCount; n++)
432 {
433 const ImDrawList* cmd_list = draw_data->CmdLists[n];
434 for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++)
435 {
436 const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i];
437 if (pcmd->UserCallback != nullptr)
438 {
439 // User callback, registered via ImDrawList::AddCallback()
440 // (ImDrawCallback_ResetRenderState is a special callback value used by the user to request the renderer to reset render state.)
441 if (pcmd->UserCallback == ImDrawCallback_ResetRenderState)
442 ImGui_ImplWGPU_SetupRenderState(draw_data, pass_encoder, fr);
443 else
444 pcmd->UserCallback(cmd_list, pcmd);
445 }
446 else
447 {
448 // Bind custom texture
449 ImTextureID tex_id = pcmd->GetTexID();
450 ImGuiID tex_id_hash = ImHashData(data_p: &tex_id, data_size: sizeof(tex_id));
451 auto bind_group = bd->renderResources.ImageBindGroups.GetVoidPtr(tex_id_hash);
452 if (bind_group)
453 {
454 wgpuRenderPassEncoderSetBindGroup(pass_encoder, 1, (WGPUBindGroup)bind_group, 0, nullptr);
455 }
456 else
457 {
458 WGPUBindGroup image_bind_group = ImGui_ImplWGPU_CreateImageBindGroup(bd->renderResources.ImageBindGroupLayout, (WGPUTextureView)tex_id);
459 bd->renderResources.ImageBindGroups.SetVoidPtr(tex_id_hash, image_bind_group);
460 wgpuRenderPassEncoderSetBindGroup(pass_encoder, 1, image_bind_group, 0, nullptr);
461 }
462
463 // Project scissor/clipping rectangles into framebuffer space
464 ImVec2 clip_min((pcmd->ClipRect.x - clip_off.x) * clip_scale.x, (pcmd->ClipRect.y - clip_off.y) * clip_scale.y);
465 ImVec2 clip_max((pcmd->ClipRect.z - clip_off.x) * clip_scale.x, (pcmd->ClipRect.w - clip_off.y) * clip_scale.y);
466
467 // Clamp to viewport as wgpuRenderPassEncoderSetScissorRect() won't accept values that are off bounds
468 if (clip_min.x < 0.0f) { clip_min.x = 0.0f; }
469 if (clip_min.y < 0.0f) { clip_min.y = 0.0f; }
470 if (clip_max.x > fb_width) { clip_max.x = (float)fb_width; }
471 if (clip_max.y > fb_height) { clip_max.y = (float)fb_height; }
472 if (clip_max.x <= clip_min.x || clip_max.y <= clip_min.y)
473 continue;
474
475 // Apply scissor/clipping rectangle, Draw
476 wgpuRenderPassEncoderSetScissorRect(pass_encoder, (uint32_t)clip_min.x, (uint32_t)clip_min.y, (uint32_t)(clip_max.x - clip_min.x), (uint32_t)(clip_max.y - clip_min.y));
477 wgpuRenderPassEncoderDrawIndexed(pass_encoder, pcmd->ElemCount, 1, pcmd->IdxOffset + global_idx_offset, pcmd->VtxOffset + global_vtx_offset, 0);
478 }
479 }
480 global_idx_offset += cmd_list->IdxBuffer.Size;
481 global_vtx_offset += cmd_list->VtxBuffer.Size;
482 }
483}
484
485static void ImGui_ImplWGPU_CreateFontsTexture()
486{
487 // Build texture atlas
488 ImGui_ImplWGPU_Data* bd = ImGui_ImplWGPU_GetBackendData();
489 ImGuiIO& io = ImGui::GetIO();
490 unsigned char* pixels;
491 int width, height, size_pp;
492 io.Fonts->GetTexDataAsRGBA32(out_pixels: &pixels, out_width: &width, out_height: &height, out_bytes_per_pixel: &size_pp);
493
494 // Upload texture to graphics system
495 {
496 WGPUTextureDescriptor tex_desc = {};
497 tex_desc.label = "Dear ImGui Font Texture";
498 tex_desc.dimension = WGPUTextureDimension_2D;
499 tex_desc.size.width = width;
500 tex_desc.size.height = height;
501 tex_desc.size.depthOrArrayLayers = 1;
502 tex_desc.sampleCount = 1;
503 tex_desc.format = WGPUTextureFormat_RGBA8Unorm;
504 tex_desc.mipLevelCount = 1;
505 tex_desc.usage = WGPUTextureUsage_CopyDst | WGPUTextureUsage_TextureBinding;
506 bd->renderResources.FontTexture = wgpuDeviceCreateTexture(bd->wgpuDevice, &tex_desc);
507
508 WGPUTextureViewDescriptor tex_view_desc = {};
509 tex_view_desc.format = WGPUTextureFormat_RGBA8Unorm;
510 tex_view_desc.dimension = WGPUTextureViewDimension_2D;
511 tex_view_desc.baseMipLevel = 0;
512 tex_view_desc.mipLevelCount = 1;
513 tex_view_desc.baseArrayLayer = 0;
514 tex_view_desc.arrayLayerCount = 1;
515 tex_view_desc.aspect = WGPUTextureAspect_All;
516 bd->renderResources.FontTextureView = wgpuTextureCreateView(bd->renderResources.FontTexture, &tex_view_desc);
517 }
518
519 // Upload texture data
520 {
521 WGPUImageCopyTexture dst_view = {};
522 dst_view.texture = bd->renderResources.FontTexture;
523 dst_view.mipLevel = 0;
524 dst_view.origin = { 0, 0, 0 };
525 dst_view.aspect = WGPUTextureAspect_All;
526 WGPUTextureDataLayout layout = {};
527 layout.offset = 0;
528 layout.bytesPerRow = width * size_pp;
529 layout.rowsPerImage = height;
530 WGPUExtent3D size = { (uint32_t)width, (uint32_t)height, 1 };
531 wgpuQueueWriteTexture(bd->defaultQueue, &dst_view, pixels, (uint32_t)(width * size_pp * height), &layout, &size);
532 }
533
534 // Create the associated sampler
535 // (Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling)
536 {
537 WGPUSamplerDescriptor sampler_desc = {};
538 sampler_desc.minFilter = WGPUFilterMode_Linear;
539 sampler_desc.magFilter = WGPUFilterMode_Linear;
540 sampler_desc.mipmapFilter = WGPUMipmapFilterMode_Linear;
541 sampler_desc.addressModeU = WGPUAddressMode_Repeat;
542 sampler_desc.addressModeV = WGPUAddressMode_Repeat;
543 sampler_desc.addressModeW = WGPUAddressMode_Repeat;
544 sampler_desc.maxAnisotropy = 1;
545 bd->renderResources.Sampler = wgpuDeviceCreateSampler(bd->wgpuDevice, &sampler_desc);
546 }
547
548 // Store our identifier
549 static_assert(sizeof(ImTextureID) >= sizeof(bd->renderResources.FontTexture), "Can't pack descriptor handle into TexID, 32-bit not supported yet.");
550 io.Fonts->SetTexID((ImTextureID)bd->renderResources.FontTextureView);
551}
552
553static void ImGui_ImplWGPU_CreateUniformBuffer()
554{
555 ImGui_ImplWGPU_Data* bd = ImGui_ImplWGPU_GetBackendData();
556 WGPUBufferDescriptor ub_desc =
557 {
558 nullptr,
559 "Dear ImGui Uniform buffer",
560 WGPUBufferUsage_CopyDst | WGPUBufferUsage_Uniform,
561 MEMALIGN(sizeof(Uniforms), 16),
562 false
563 };
564 bd->renderResources.Uniforms = wgpuDeviceCreateBuffer(bd->wgpuDevice, &ub_desc);
565}
566
567bool ImGui_ImplWGPU_CreateDeviceObjects()
568{
569 ImGui_ImplWGPU_Data* bd = ImGui_ImplWGPU_GetBackendData();
570 if (!bd->wgpuDevice)
571 return false;
572 if (bd->pipelineState)
573 ImGui_ImplWGPU_InvalidateDeviceObjects();
574
575 // Create render pipeline
576 WGPURenderPipelineDescriptor graphics_pipeline_desc = {};
577 graphics_pipeline_desc.primitive.topology = WGPUPrimitiveTopology_TriangleList;
578 graphics_pipeline_desc.primitive.stripIndexFormat = WGPUIndexFormat_Undefined;
579 graphics_pipeline_desc.primitive.frontFace = WGPUFrontFace_CW;
580 graphics_pipeline_desc.primitive.cullMode = WGPUCullMode_None;
581 graphics_pipeline_desc.multisample = bd->initInfo.PipelineMultisampleState;
582
583 // Bind group layouts
584 WGPUBindGroupLayoutEntry common_bg_layout_entries[2] = {};
585 common_bg_layout_entries[0].binding = 0;
586 common_bg_layout_entries[0].visibility = WGPUShaderStage_Vertex | WGPUShaderStage_Fragment;
587 common_bg_layout_entries[0].buffer.type = WGPUBufferBindingType_Uniform;
588 common_bg_layout_entries[1].binding = 1;
589 common_bg_layout_entries[1].visibility = WGPUShaderStage_Fragment;
590 common_bg_layout_entries[1].sampler.type = WGPUSamplerBindingType_Filtering;
591
592 WGPUBindGroupLayoutEntry image_bg_layout_entries[1] = {};
593 image_bg_layout_entries[0].binding = 0;
594 image_bg_layout_entries[0].visibility = WGPUShaderStage_Fragment;
595 image_bg_layout_entries[0].texture.sampleType = WGPUTextureSampleType_Float;
596 image_bg_layout_entries[0].texture.viewDimension = WGPUTextureViewDimension_2D;
597
598 WGPUBindGroupLayoutDescriptor common_bg_layout_desc = {};
599 common_bg_layout_desc.entryCount = 2;
600 common_bg_layout_desc.entries = common_bg_layout_entries;
601
602 WGPUBindGroupLayoutDescriptor image_bg_layout_desc = {};
603 image_bg_layout_desc.entryCount = 1;
604 image_bg_layout_desc.entries = image_bg_layout_entries;
605
606 WGPUBindGroupLayout bg_layouts[2];
607 bg_layouts[0] = wgpuDeviceCreateBindGroupLayout(bd->wgpuDevice, &common_bg_layout_desc);
608 bg_layouts[1] = wgpuDeviceCreateBindGroupLayout(bd->wgpuDevice, &image_bg_layout_desc);
609
610 WGPUPipelineLayoutDescriptor layout_desc = {};
611 layout_desc.bindGroupLayoutCount = 2;
612 layout_desc.bindGroupLayouts = bg_layouts;
613 graphics_pipeline_desc.layout = wgpuDeviceCreatePipelineLayout(bd->wgpuDevice, &layout_desc);
614
615 // Create the vertex shader
616 WGPUProgrammableStageDescriptor vertex_shader_desc = ImGui_ImplWGPU_CreateShaderModule(__shader_vert_wgsl);
617 graphics_pipeline_desc.vertex.module = vertex_shader_desc.module;
618 graphics_pipeline_desc.vertex.entryPoint = vertex_shader_desc.entryPoint;
619
620 // Vertex input configuration
621 WGPUVertexAttribute attribute_desc[] =
622 {
623 { WGPUVertexFormat_Float32x2, (uint64_t)offsetof(ImDrawVert, pos), 0 },
624 { WGPUVertexFormat_Float32x2, (uint64_t)offsetof(ImDrawVert, uv), 1 },
625 { WGPUVertexFormat_Unorm8x4, (uint64_t)offsetof(ImDrawVert, col), 2 },
626 };
627
628 WGPUVertexBufferLayout buffer_layouts[1];
629 buffer_layouts[0].arrayStride = sizeof(ImDrawVert);
630 buffer_layouts[0].stepMode = WGPUVertexStepMode_Vertex;
631 buffer_layouts[0].attributeCount = 3;
632 buffer_layouts[0].attributes = attribute_desc;
633
634 graphics_pipeline_desc.vertex.bufferCount = 1;
635 graphics_pipeline_desc.vertex.buffers = buffer_layouts;
636
637 // Create the pixel shader
638 WGPUProgrammableStageDescriptor pixel_shader_desc = ImGui_ImplWGPU_CreateShaderModule(__shader_frag_wgsl);
639
640 // Create the blending setup
641 WGPUBlendState blend_state = {};
642 blend_state.alpha.operation = WGPUBlendOperation_Add;
643 blend_state.alpha.srcFactor = WGPUBlendFactor_One;
644 blend_state.alpha.dstFactor = WGPUBlendFactor_OneMinusSrcAlpha;
645 blend_state.color.operation = WGPUBlendOperation_Add;
646 blend_state.color.srcFactor = WGPUBlendFactor_SrcAlpha;
647 blend_state.color.dstFactor = WGPUBlendFactor_OneMinusSrcAlpha;
648
649 WGPUColorTargetState color_state = {};
650 color_state.format = bd->renderTargetFormat;
651 color_state.blend = &blend_state;
652 color_state.writeMask = WGPUColorWriteMask_All;
653
654 WGPUFragmentState fragment_state = {};
655 fragment_state.module = pixel_shader_desc.module;
656 fragment_state.entryPoint = pixel_shader_desc.entryPoint;
657 fragment_state.targetCount = 1;
658 fragment_state.targets = &color_state;
659
660 graphics_pipeline_desc.fragment = &fragment_state;
661
662 // Create depth-stencil State
663 WGPUDepthStencilState depth_stencil_state = {};
664 depth_stencil_state.format = bd->depthStencilFormat;
665 depth_stencil_state.depthWriteEnabled = false;
666 depth_stencil_state.depthCompare = WGPUCompareFunction_Always;
667 depth_stencil_state.stencilFront.compare = WGPUCompareFunction_Always;
668 depth_stencil_state.stencilFront.failOp = WGPUStencilOperation_Keep;
669 depth_stencil_state.stencilFront.depthFailOp = WGPUStencilOperation_Keep;
670 depth_stencil_state.stencilFront.passOp = WGPUStencilOperation_Keep;
671 depth_stencil_state.stencilBack.compare = WGPUCompareFunction_Always;
672 depth_stencil_state.stencilBack.failOp = WGPUStencilOperation_Keep;
673 depth_stencil_state.stencilBack.depthFailOp = WGPUStencilOperation_Keep;
674 depth_stencil_state.stencilBack.passOp = WGPUStencilOperation_Keep;
675
676 // Configure disabled depth-stencil state
677 graphics_pipeline_desc.depthStencil = (bd->depthStencilFormat == WGPUTextureFormat_Undefined) ? nullptr : &depth_stencil_state;
678
679 bd->pipelineState = wgpuDeviceCreateRenderPipeline(bd->wgpuDevice, &graphics_pipeline_desc);
680
681 ImGui_ImplWGPU_CreateFontsTexture();
682 ImGui_ImplWGPU_CreateUniformBuffer();
683
684 // Create resource bind group
685 WGPUBindGroupEntry common_bg_entries[] =
686 {
687 { nullptr, 0, bd->renderResources.Uniforms, 0, MEMALIGN(sizeof(Uniforms), 16), 0, 0 },
688 { nullptr, 1, 0, 0, 0, bd->renderResources.Sampler, 0 },
689 };
690
691 WGPUBindGroupDescriptor common_bg_descriptor = {};
692 common_bg_descriptor.layout = bg_layouts[0];
693 common_bg_descriptor.entryCount = sizeof(common_bg_entries) / sizeof(WGPUBindGroupEntry);
694 common_bg_descriptor.entries = common_bg_entries;
695 bd->renderResources.CommonBindGroup = wgpuDeviceCreateBindGroup(bd->wgpuDevice, &common_bg_descriptor);
696
697 WGPUBindGroup image_bind_group = ImGui_ImplWGPU_CreateImageBindGroup(bg_layouts[1], bd->renderResources.FontTextureView);
698 bd->renderResources.ImageBindGroup = image_bind_group;
699 bd->renderResources.ImageBindGroupLayout = bg_layouts[1];
700 bd->renderResources.ImageBindGroups.SetVoidPtr(ImHashData(&bd->renderResources.FontTextureView, sizeof(ImTextureID)), image_bind_group);
701
702 SafeRelease(vertex_shader_desc.module);
703 SafeRelease(pixel_shader_desc.module);
704 SafeRelease(graphics_pipeline_desc.layout);
705 SafeRelease(bg_layouts[0]);
706
707 return true;
708}
709
710void ImGui_ImplWGPU_InvalidateDeviceObjects()
711{
712 ImGui_ImplWGPU_Data* bd = ImGui_ImplWGPU_GetBackendData();
713 if (!bd->wgpuDevice)
714 return;
715
716 SafeRelease(bd->pipelineState);
717 SafeRelease(bd->renderResources);
718
719 ImGuiIO& io = ImGui::GetIO();
720 io.Fonts->SetTexID(0); // We copied g_pFontTextureView to io.Fonts->TexID so let's clear that as well.
721
722 for (unsigned int i = 0; i < bd->numFramesInFlight; i++)
723 SafeRelease(res&: bd->pFrameResources[i]);
724}
725
726bool ImGui_ImplWGPU_Init(ImGui_ImplWGPU_InitInfo* init_info)
727{
728 ImGuiIO& io = ImGui::GetIO();
729 IM_ASSERT(io.BackendRendererUserData == nullptr && "Already initialized a renderer backend!");
730
731 // Setup backend capabilities flags
732 ImGui_ImplWGPU_Data* bd = IM_NEW(ImGui_ImplWGPU_Data)();
733 io.BackendRendererUserData = (void*)bd;
734 io.BackendRendererName = "imgui_impl_webgpu";
735 io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes.
736
737 bd->initInfo = *init_info;
738 bd->wgpuDevice = init_info->Device;
739 bd->defaultQueue = wgpuDeviceGetQueue(bd->wgpuDevice);
740 bd->renderTargetFormat = init_info->RenderTargetFormat;
741 bd->depthStencilFormat = init_info->DepthStencilFormat;
742 bd->numFramesInFlight = init_info->NumFramesInFlight;
743 bd->frameIndex = UINT_MAX;
744
745 bd->renderResources.FontTexture = nullptr;
746 bd->renderResources.FontTextureView = nullptr;
747 bd->renderResources.Sampler = nullptr;
748 bd->renderResources.Uniforms = nullptr;
749 bd->renderResources.CommonBindGroup = nullptr;
750 bd->renderResources.ImageBindGroups.Data.reserve(100);
751 bd->renderResources.ImageBindGroup = nullptr;
752 bd->renderResources.ImageBindGroupLayout = nullptr;
753
754 // Create buffers with a default size (they will later be grown as needed)
755 bd->pFrameResources = new FrameResources[bd->numFramesInFlight];
756 for (int i = 0; i < bd->numFramesInFlight; i++)
757 {
758 FrameResources* fr = &bd->pFrameResources[i];
759 fr->IndexBuffer = nullptr;
760 fr->VertexBuffer = nullptr;
761 fr->IndexBufferHost = nullptr;
762 fr->VertexBufferHost = nullptr;
763 fr->IndexBufferSize = 10000;
764 fr->VertexBufferSize = 5000;
765 }
766
767 return true;
768}
769
770void ImGui_ImplWGPU_Shutdown()
771{
772 ImGui_ImplWGPU_Data* bd = ImGui_ImplWGPU_GetBackendData();
773 IM_ASSERT(bd != nullptr && "No renderer backend to shutdown, or already shutdown?");
774 ImGuiIO& io = ImGui::GetIO();
775
776 ImGui_ImplWGPU_InvalidateDeviceObjects();
777 delete[] bd->pFrameResources;
778 bd->pFrameResources = nullptr;
779 wgpuQueueRelease(bd->defaultQueue);
780 bd->wgpuDevice = nullptr;
781 bd->numFramesInFlight = 0;
782 bd->frameIndex = UINT_MAX;
783
784 io.BackendRendererName = nullptr;
785 io.BackendRendererUserData = nullptr;
786 io.BackendFlags &= ~ImGuiBackendFlags_RendererHasVtxOffset;
787 IM_DELETE(p: bd);
788}
789
790void ImGui_ImplWGPU_NewFrame()
791{
792 ImGui_ImplWGPU_Data* bd = ImGui_ImplWGPU_GetBackendData();
793 if (!bd->pipelineState)
794 ImGui_ImplWGPU_CreateDeviceObjects();
795}
796
797//-----------------------------------------------------------------------------
798
799#endif // #ifndef IMGUI_DISABLE
800

source code of imgui/backends/imgui_impl_wgpu.cpp