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 |
47 | extern 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 |
51 | struct 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 | |
63 | struct FrameResources |
64 | { |
65 | WGPUBuffer IndexBuffer; |
66 | WGPUBuffer VertexBuffer; |
67 | ImDrawIdx* IndexBufferHost; |
68 | ImDrawVert* VertexBufferHost; |
69 | int IndexBufferSize; |
70 | int VertexBufferSize; |
71 | }; |
72 | |
73 | struct Uniforms |
74 | { |
75 | float MVP[4][4]; |
76 | float Gamma; |
77 | }; |
78 | |
79 | struct 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. |
96 | static 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 | |
105 | static const char __shader_vert_wgsl[] = R"( |
106 | struct VertexInput { |
107 | @location(0) position: vec2<f32>, |
108 | @location(1) uv: vec2<f32>, |
109 | @location(2) color: vec4<f32>, |
110 | }; |
111 | |
112 | struct VertexOutput { |
113 | @builtin(position) position: vec4<f32>, |
114 | @location(0) color: vec4<f32>, |
115 | @location(1) uv: vec2<f32>, |
116 | }; |
117 | |
118 | struct Uniforms { |
119 | mvp: mat4x4<f32>, |
120 | gamma: f32, |
121 | }; |
122 | |
123 | @group(0) @binding(0) var<uniform> uniforms: Uniforms; |
124 | |
125 | @vertex |
126 | fn 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 | |
135 | static const char __shader_frag_wgsl[] = R"( |
136 | struct VertexOutput { |
137 | @builtin(position) position: vec4<f32>, |
138 | @location(0) color: vec4<f32>, |
139 | @location(1) uv: vec2<f32>, |
140 | }; |
141 | |
142 | struct 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 |
152 | fn 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 | |
159 | static void SafeRelease(ImDrawIdx*& res) |
160 | { |
161 | if (res) |
162 | delete[] res; |
163 | res = nullptr; |
164 | } |
165 | static void SafeRelease(ImDrawVert*& res) |
166 | { |
167 | if (res) |
168 | delete[] res; |
169 | res = nullptr; |
170 | } |
171 | static void SafeRelease(WGPUBindGroupLayout& res) |
172 | { |
173 | if (res) |
174 | wgpuBindGroupLayoutRelease(res); |
175 | res = nullptr; |
176 | } |
177 | static void SafeRelease(WGPUBindGroup& res) |
178 | { |
179 | if (res) |
180 | wgpuBindGroupRelease(res); |
181 | res = nullptr; |
182 | } |
183 | static void SafeRelease(WGPUBuffer& res) |
184 | { |
185 | if (res) |
186 | wgpuBufferRelease(res); |
187 | res = nullptr; |
188 | } |
189 | static void SafeRelease(WGPUPipelineLayout& res) |
190 | { |
191 | if (res) |
192 | wgpuPipelineLayoutRelease(res); |
193 | res = nullptr; |
194 | } |
195 | static void SafeRelease(WGPURenderPipeline& res) |
196 | { |
197 | if (res) |
198 | wgpuRenderPipelineRelease(res); |
199 | res = nullptr; |
200 | } |
201 | static void SafeRelease(WGPUSampler& res) |
202 | { |
203 | if (res) |
204 | wgpuSamplerRelease(res); |
205 | res = nullptr; |
206 | } |
207 | static void SafeRelease(WGPUShaderModule& res) |
208 | { |
209 | if (res) |
210 | wgpuShaderModuleRelease(res); |
211 | res = nullptr; |
212 | } |
213 | static void SafeRelease(WGPUTextureView& res) |
214 | { |
215 | if (res) |
216 | wgpuTextureViewRelease(res); |
217 | res = nullptr; |
218 | } |
219 | static void SafeRelease(WGPUTexture& res) |
220 | { |
221 | if (res) |
222 | wgpuTextureRelease(res); |
223 | res = nullptr; |
224 | } |
225 | |
226 | static 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 | |
237 | static 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 | |
245 | static 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 | |
262 | static 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 | |
274 | static 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) |
342 | void 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 | |
485 | static 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 | |
553 | static 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 | |
567 | bool 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 | |
710 | void 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 | |
726 | bool 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 | |
770 | void 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 | |
790 | void 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 | |