Browse Source

Backends, Examples: Added support for WebGPU and corresponding example (#3632)

(Squashed 11 commits)
Basil Fierz 4 years ago
parent
commit
5853fbd68b

+ 7 - 0
.github/workflows/build.yml

@@ -400,6 +400,13 @@ jobs:
         popd
         popd
         make -C examples/example_emscripten_opengl3
         make -C examples/example_emscripten_opengl3
 
 
+    - name: Build example_emscripten_wgpu
+      run: |
+        pushd emsdk-master
+        source ./emsdk_env.sh
+        popd
+        make -C examples/example_emscripten_wgpu
+
   Discord-CI:
   Discord-CI:
     runs-on: ubuntu-18.04
     runs-on: ubuntu-18.04
     if: always()
     if: always()

+ 783 - 0
backends/imgui_impl_wgpu.cpp

@@ -0,0 +1,783 @@
+// dear imgui: Renderer for WebGPU
+// This needs to be used along with a Platform Binding (e.g. GLFW)
+
+// Implemented features:
+//  [X] Renderer: User texture binding. Use 'WGPUTextureView' as ImTextureID. Read the FAQ about ImTextureID!
+//  [X] Renderer: Support for large meshes (64k+ vertices) with 16-bit indices.
+
+// You can copy and use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
+// If you are new to Dear ImGui, read documentation from the docs/ folder + read the top of imgui.cpp.
+// Read online: https://github.com/ocornut/imgui/tree/master/docs
+
+// CHANGELOG
+// (minor and older changes stripped away, please see git history for details)
+
+#include "imgui.h"
+#include "imgui_impl_wgpu.h"
+
+// CRT
+#include <limits.h>
+
+// WebGPU
+#include <webgpu/webgpu.h>
+
+// ImGui prototypes
+ImGuiID ImHashData(const void* data_p, size_t data_size, ImU32 seed = 0);
+
+// WebGPU data
+static WGPUDevice                      g_wgpuDevice = NULL;
+static WGPUTextureFormat               g_renderTargetFormat = WGPUTextureFormat_Undefined;
+static WGPURenderPipeline              g_pipelineState = NULL;
+
+struct RenderResources
+{
+    // Font texture
+    WGPUTexture FontTexture;
+
+    // Texture view for font texture
+    WGPUTextureView FontTextureView;
+
+    // Sampler for the font texture
+    WGPUSampler Sampler;
+
+    // Shader uniforms
+    WGPUBuffer Uniforms;
+
+    // Resources bind-group to bind the common resources to pipeline
+    WGPUBindGroup CommonBindGroup;
+
+    // Bind group layout for image textures
+    WGPUBindGroupLayout ImageBindGroupLayout;
+
+    // Resources bind-group to bind the font/image resources to pipeline
+    ImGuiStorage ImageBindGroups;
+
+    // Default font-resource of ImGui
+    WGPUBindGroup ImageBindGroup;
+};
+static RenderResources g_resources;
+
+struct FrameResources
+{
+    WGPUBuffer  IndexBuffer;
+    WGPUBuffer  VertexBuffer;
+    ImDrawIdx*  IndexBufferHost;
+    ImDrawVert* VertexBufferHost;
+    int         IndexBufferSize;
+    int         VertexBufferSize;
+};
+static FrameResources*  g_pFrameResources = NULL;
+static unsigned int     g_numFramesInFlight = 0;
+static unsigned int     g_frameIndex = UINT_MAX;
+
+struct Uniforms
+{
+    float MVP[4][4];
+};
+
+//-----------------------------------------------------------------------------
+// SHADERS
+//-----------------------------------------------------------------------------
+
+// glsl_shader.vert, compiled with:
+// # glslangValidator -V -x -o glsl_shader.vert.u32 glsl_shader.vert
+/*
+#version 450 core
+layout(location = 0) in vec2 aPos;
+layout(location = 1) in vec2 aUV;
+layout(location = 2) in vec4 aColor;
+layout(set=0, binding = 0) uniform transform { mat4 mvp; };
+
+out gl_PerVertex { vec4 gl_Position; };
+layout(location = 0) out struct { vec4 Color; vec2 UV; } Out;
+
+void main()
+{
+    Out.Color = aColor;
+    Out.UV = aUV;
+    gl_Position = mvp * vec4(aPos, 0, 1);
+}
+*/
+static uint32_t __glsl_shader_vert_spv[] =
+{
+    0x07230203,0x00010000,0x00080007,0x0000002c,0x00000000,0x00020011,0x00000001,0x0006000b,
+    0x00000001,0x4c534c47,0x6474732e,0x3035342e,0x00000000,0x0003000e,0x00000000,0x00000001,
+    0x000a000f,0x00000000,0x00000004,0x6e69616d,0x00000000,0x0000000b,0x0000000f,0x00000015,
+    0x0000001b,0x00000023,0x00030003,0x00000002,0x000001c2,0x00040005,0x00000004,0x6e69616d,
+    0x00000000,0x00030005,0x00000009,0x00000000,0x00050006,0x00000009,0x00000000,0x6f6c6f43,
+    0x00000072,0x00040006,0x00000009,0x00000001,0x00005655,0x00030005,0x0000000b,0x0074754f,
+    0x00040005,0x0000000f,0x6c6f4361,0x0000726f,0x00030005,0x00000015,0x00565561,0x00060005,
+    0x00000019,0x505f6c67,0x65567265,0x78657472,0x00000000,0x00060006,0x00000019,0x00000000,
+    0x505f6c67,0x7469736f,0x006e6f69,0x00030005,0x0000001b,0x00000000,0x00050005,0x0000001d,
+    0x6e617274,0x726f6673,0x0000006d,0x00040006,0x0000001d,0x00000000,0x0070766d,0x00030005,
+    0x0000001f,0x00000000,0x00040005,0x00000023,0x736f5061,0x00000000,0x00040047,0x0000000b,
+    0x0000001e,0x00000000,0x00040047,0x0000000f,0x0000001e,0x00000002,0x00040047,0x00000015,
+    0x0000001e,0x00000001,0x00050048,0x00000019,0x00000000,0x0000000b,0x00000000,0x00030047,
+    0x00000019,0x00000002,0x00040048,0x0000001d,0x00000000,0x00000005,0x00050048,0x0000001d,
+    0x00000000,0x00000023,0x00000000,0x00050048,0x0000001d,0x00000000,0x00000007,0x00000010,
+    0x00030047,0x0000001d,0x00000002,0x00040047,0x0000001f,0x00000022,0x00000000,0x00040047,
+    0x0000001f,0x00000021,0x00000000,0x00040047,0x00000023,0x0000001e,0x00000000,0x00020013,
+    0x00000002,0x00030021,0x00000003,0x00000002,0x00030016,0x00000006,0x00000020,0x00040017,
+    0x00000007,0x00000006,0x00000004,0x00040017,0x00000008,0x00000006,0x00000002,0x0004001e,
+    0x00000009,0x00000007,0x00000008,0x00040020,0x0000000a,0x00000003,0x00000009,0x0004003b,
+    0x0000000a,0x0000000b,0x00000003,0x00040015,0x0000000c,0x00000020,0x00000001,0x0004002b,
+    0x0000000c,0x0000000d,0x00000000,0x00040020,0x0000000e,0x00000001,0x00000007,0x0004003b,
+    0x0000000e,0x0000000f,0x00000001,0x00040020,0x00000011,0x00000003,0x00000007,0x0004002b,
+    0x0000000c,0x00000013,0x00000001,0x00040020,0x00000014,0x00000001,0x00000008,0x0004003b,
+    0x00000014,0x00000015,0x00000001,0x00040020,0x00000017,0x00000003,0x00000008,0x0003001e,
+    0x00000019,0x00000007,0x00040020,0x0000001a,0x00000003,0x00000019,0x0004003b,0x0000001a,
+    0x0000001b,0x00000003,0x00040018,0x0000001c,0x00000007,0x00000004,0x0003001e,0x0000001d,
+    0x0000001c,0x00040020,0x0000001e,0x00000002,0x0000001d,0x0004003b,0x0000001e,0x0000001f,
+    0x00000002,0x00040020,0x00000020,0x00000002,0x0000001c,0x0004003b,0x00000014,0x00000023,
+    0x00000001,0x0004002b,0x00000006,0x00000025,0x00000000,0x0004002b,0x00000006,0x00000026,
+    0x3f800000,0x00050036,0x00000002,0x00000004,0x00000000,0x00000003,0x000200f8,0x00000005,
+    0x0004003d,0x00000007,0x00000010,0x0000000f,0x00050041,0x00000011,0x00000012,0x0000000b,
+    0x0000000d,0x0003003e,0x00000012,0x00000010,0x0004003d,0x00000008,0x00000016,0x00000015,
+    0x00050041,0x00000017,0x00000018,0x0000000b,0x00000013,0x0003003e,0x00000018,0x00000016,
+    0x00050041,0x00000020,0x00000021,0x0000001f,0x0000000d,0x0004003d,0x0000001c,0x00000022,
+    0x00000021,0x0004003d,0x00000008,0x00000024,0x00000023,0x00050051,0x00000006,0x00000027,
+    0x00000024,0x00000000,0x00050051,0x00000006,0x00000028,0x00000024,0x00000001,0x00070050,
+    0x00000007,0x00000029,0x00000027,0x00000028,0x00000025,0x00000026,0x00050091,0x00000007,
+    0x0000002a,0x00000022,0x00000029,0x00050041,0x00000011,0x0000002b,0x0000001b,0x0000000d,
+    0x0003003e,0x0000002b,0x0000002a,0x000100fd,0x00010038
+};
+
+// glsl_shader.frag, compiled with:
+// # glslangValidator -V -x -o glsl_shader.frag.u32 glsl_shader.frag
+/*
+#version 450 core
+layout(location = 0) out vec4 fColor;
+layout(set=0, binding=1) uniform sampler s;
+layout(set=1, binding=0) uniform texture2D t;
+layout(location = 0) in struct { vec4 Color; vec2 UV; } In;
+void main()
+{
+    fColor = In.Color * texture(sampler2D(t, s), In.UV.st);
+}
+*/
+static uint32_t __glsl_shader_frag_spv[] =
+{
+    0x07230203,0x00010000,0x00080007,0x00000023,0x00000000,0x00020011,0x00000001,0x0006000b,
+    0x00000001,0x4c534c47,0x6474732e,0x3035342e,0x00000000,0x0003000e,0x00000000,0x00000001,
+    0x0007000f,0x00000004,0x00000004,0x6e69616d,0x00000000,0x00000009,0x0000000d,0x00030010,
+    0x00000004,0x00000007,0x00030003,0x00000002,0x000001c2,0x00040005,0x00000004,0x6e69616d,
+    0x00000000,0x00040005,0x00000009,0x6c6f4366,0x0000726f,0x00030005,0x0000000b,0x00000000,
+    0x00050006,0x0000000b,0x00000000,0x6f6c6f43,0x00000072,0x00040006,0x0000000b,0x00000001,
+    0x00005655,0x00030005,0x0000000d,0x00006e49,0x00030005,0x00000015,0x00000074,0x00030005,
+    0x00000019,0x00000073,0x00040047,0x00000009,0x0000001e,0x00000000,0x00040047,0x0000000d,
+    0x0000001e,0x00000000,0x00040047,0x00000015,0x00000022,0x00000001,0x00040047,0x00000015,
+    0x00000021,0x00000000,0x00040047,0x00000019,0x00000022,0x00000000,0x00040047,0x00000019,
+    0x00000021,0x00000001,0x00020013,0x00000002,0x00030021,0x00000003,0x00000002,0x00030016,
+    0x00000006,0x00000020,0x00040017,0x00000007,0x00000006,0x00000004,0x00040020,0x00000008,
+    0x00000003,0x00000007,0x0004003b,0x00000008,0x00000009,0x00000003,0x00040017,0x0000000a,
+    0x00000006,0x00000002,0x0004001e,0x0000000b,0x00000007,0x0000000a,0x00040020,0x0000000c,
+    0x00000001,0x0000000b,0x0004003b,0x0000000c,0x0000000d,0x00000001,0x00040015,0x0000000e,
+    0x00000020,0x00000001,0x0004002b,0x0000000e,0x0000000f,0x00000000,0x00040020,0x00000010,
+    0x00000001,0x00000007,0x00090019,0x00000013,0x00000006,0x00000001,0x00000000,0x00000000,
+    0x00000000,0x00000001,0x00000000,0x00040020,0x00000014,0x00000000,0x00000013,0x0004003b,
+    0x00000014,0x00000015,0x00000000,0x0002001a,0x00000017,0x00040020,0x00000018,0x00000000,
+    0x00000017,0x0004003b,0x00000018,0x00000019,0x00000000,0x0003001b,0x0000001b,0x00000013,
+    0x0004002b,0x0000000e,0x0000001d,0x00000001,0x00040020,0x0000001e,0x00000001,0x0000000a,
+    0x00050036,0x00000002,0x00000004,0x00000000,0x00000003,0x000200f8,0x00000005,0x00050041,
+    0x00000010,0x00000011,0x0000000d,0x0000000f,0x0004003d,0x00000007,0x00000012,0x00000011,
+    0x0004003d,0x00000013,0x00000016,0x00000015,0x0004003d,0x00000017,0x0000001a,0x00000019,
+    0x00050056,0x0000001b,0x0000001c,0x00000016,0x0000001a,0x00050041,0x0000001e,0x0000001f,
+    0x0000000d,0x0000001d,0x0004003d,0x0000000a,0x00000020,0x0000001f,0x00050057,0x00000007,
+    0x00000021,0x0000001c,0x00000020,0x00050085,0x00000007,0x00000022,0x00000012,0x00000021,
+    0x0003003e,0x00000009,0x00000022,0x000100fd,0x00010038
+};
+
+static void SafeRelease(ImDrawIdx*& res)
+{
+    if (res)
+        delete[] res;
+    res = NULL;
+}
+static void SafeRelease(ImDrawVert*& res)
+{
+    if (res)
+        delete[] res;
+    res = NULL;
+}
+static void SafeRelease(WGPUBindGroupLayout& res)
+{
+    if (res)
+        wgpuBindGroupLayoutRelease(res);
+    res = NULL;
+}
+static void SafeRelease(WGPUBindGroup& res)
+{
+    if (res)
+        wgpuBindGroupRelease(res);
+    res = NULL;
+}
+static void SafeRelease(WGPUBuffer& res)
+{
+    if (res)
+        wgpuBufferRelease(res);
+    res = NULL;
+}
+static void SafeRelease(WGPURenderPipeline& res)
+{
+    if (res)
+        wgpuRenderPipelineRelease(res);
+    res = NULL;
+}
+static void SafeRelease(WGPUSampler& res)
+{
+    if (res)
+        wgpuSamplerRelease(res);
+    res = NULL;
+}
+static void SafeRelease(WGPUShaderModule& res)
+{
+    if (res)
+        wgpuShaderModuleRelease(res);
+    res = NULL;
+}
+static void SafeRelease(WGPUTextureView& res)
+{
+    if (res)
+        wgpuTextureViewRelease(res);
+    res = NULL;
+}
+static void SafeRelease(WGPUTexture& res)
+{
+    if (res)
+        wgpuTextureRelease(res);
+    res = NULL;
+}
+
+static void SafeRelease(RenderResources& res)
+{
+    SafeRelease(res.FontTexture);
+    SafeRelease(res.FontTextureView);
+    SafeRelease(res.Sampler);
+    SafeRelease(res.Uniforms);
+    SafeRelease(res.CommonBindGroup);
+    SafeRelease(res.ImageBindGroupLayout);
+    SafeRelease(res.ImageBindGroup);
+};
+
+static void SafeRelease(FrameResources& res)
+{
+    SafeRelease(res.IndexBuffer);
+    SafeRelease(res.VertexBuffer);
+    SafeRelease(res.IndexBufferHost);
+    SafeRelease(res.VertexBufferHost);
+}
+
+static WGPUProgrammableStageDescriptor ImGui_ImplWGPU_CreateShaderModule(uint32_t* binary_data, uint32_t binary_data_size)
+{
+    WGPUShaderModuleSPIRVDescriptor spirv_desc = {};
+    spirv_desc.chain.sType = WGPUSType_ShaderModuleSPIRVDescriptor;
+    spirv_desc.codeSize = binary_data_size;
+    spirv_desc.code = binary_data;
+
+    WGPUShaderModuleDescriptor desc;
+    desc.nextInChain = reinterpret_cast<WGPUChainedStruct*>(&spirv_desc);
+
+    WGPUProgrammableStageDescriptor stage_desc = {};
+    stage_desc.module = wgpuDeviceCreateShaderModule(g_wgpuDevice, &desc);
+    stage_desc.entryPoint = "main";
+    return stage_desc;
+}
+
+static WGPUBindGroup ImGui_ImplWGPU_CreateImageBindGroup(WGPUBindGroupLayout layout, WGPUTextureView texture)
+{
+    WGPUBindGroupEntry image_bg_entries[] = {
+        { 0, 0, 0, 0, 0, texture },
+    };
+
+    WGPUBindGroupDescriptor image_bg_descriptor = {};
+    image_bg_descriptor.layout = layout;
+    image_bg_descriptor.entryCount = sizeof(image_bg_entries) / sizeof(WGPUBindGroupEntry);
+    image_bg_descriptor.entries = image_bg_entries;
+    return wgpuDeviceCreateBindGroup(g_wgpuDevice, &image_bg_descriptor);
+}
+
+static void ImGui_ImplWGPU_SetupRenderState(ImDrawData* draw_data, WGPURenderPassEncoder ctx, FrameResources* fr)
+{
+    // Setup orthographic projection matrix into our constant buffer
+    // Our visible imgui space lies from draw_data->DisplayPos (top left) to draw_data->DisplayPos+data_data->DisplaySize (bottom right).
+    {
+        float L = draw_data->DisplayPos.x;
+        float R = draw_data->DisplayPos.x + draw_data->DisplaySize.x;
+        float T = draw_data->DisplayPos.y;
+        float B = draw_data->DisplayPos.y + draw_data->DisplaySize.y;
+        float mvp[4][4] =
+        {
+            { 2.0f/(R-L),   0.0f,           0.0f,       0.0f },
+            { 0.0f,         2.0f/(T-B),     0.0f,       0.0f },
+            { 0.0f,         0.0f,           0.5f,       0.0f },
+            { (R+L)/(L-R),  (T+B)/(B-T),    0.5f,       1.0f },
+        };
+        wgpuQueueWriteBuffer(wgpuDeviceGetDefaultQueue(g_wgpuDevice), g_resources.Uniforms, 0, mvp, sizeof(mvp));
+    }
+
+    // Setup viewport
+    wgpuRenderPassEncoderSetViewport(ctx, 0, 0, draw_data->DisplaySize.x, draw_data->DisplaySize.y, 0, 1);
+
+    // Bind shader and vertex buffers
+    unsigned int stride = sizeof(ImDrawVert);
+    unsigned int offset = 0;
+    wgpuRenderPassEncoderSetVertexBuffer(ctx, 0, fr->VertexBuffer, offset, fr->VertexBufferSize * stride);
+    wgpuRenderPassEncoderSetIndexBuffer(ctx, fr->IndexBuffer, sizeof(ImDrawIdx) == 2 ? WGPUIndexFormat_Uint16 : WGPUIndexFormat_Uint32, 0, fr->IndexBufferSize * sizeof(ImDrawIdx));
+    wgpuRenderPassEncoderSetPipeline(ctx, g_pipelineState);
+    wgpuRenderPassEncoderSetBindGroup(ctx, 0, g_resources.CommonBindGroup, 0, NULL);
+
+    // Setup blend factor
+    WGPUColor blend_color = { 0.f, 0.f, 0.f, 0.f };
+    wgpuRenderPassEncoderSetBlendColor(ctx, &blend_color);
+}
+
+// Render function
+// (this used to be set in io.RenderDrawListsFn and called by ImGui::Render(), but you can now call this directly from your main loop)
+void ImGui_ImplWGPU_RenderDrawData(ImDrawData* draw_data, WGPURenderPassEncoder pass_encoder)
+{
+    // Avoid rendering when minimized
+    if (draw_data->DisplaySize.x <= 0.0f || draw_data->DisplaySize.y <= 0.0f)
+        return;
+
+    // FIXME: I'm assuming that this only gets called once per frame!
+    // If not, we can't just re-allocate the IB or VB, we'll have to do a proper allocator.
+    g_frameIndex = g_frameIndex + 1;
+    FrameResources* fr = &g_pFrameResources[g_frameIndex % g_numFramesInFlight];
+
+    // Create and grow vertex/index buffers if needed
+    if (fr->VertexBuffer == NULL || fr->VertexBufferSize < draw_data->TotalVtxCount)
+    {
+        SafeRelease(fr->VertexBuffer);
+        SafeRelease(fr->VertexBufferHost);
+        fr->VertexBufferSize = draw_data->TotalVtxCount + 5000;
+
+        WGPUBufferDescriptor vb_desc = {
+            nullptr,
+            "IMGUI Vertex buffer",
+            WGPUBufferUsage_CopyDst | WGPUBufferUsage_Vertex,
+            fr->VertexBufferSize * sizeof(ImDrawVert),
+            false
+        };
+        fr->VertexBuffer = wgpuDeviceCreateBuffer(g_wgpuDevice, &vb_desc);
+        if (!fr->VertexBuffer)
+            return;
+
+        fr->VertexBufferHost = new ImDrawVert[fr->VertexBufferSize];
+    }
+    if (fr->IndexBuffer == NULL || fr->IndexBufferSize < draw_data->TotalIdxCount)
+    {
+        SafeRelease(fr->IndexBuffer);
+        SafeRelease(fr->IndexBufferHost);
+        fr->IndexBufferSize = draw_data->TotalIdxCount + 10000;
+
+        WGPUBufferDescriptor ib_desc = {
+            nullptr,
+            "IMGUI Index buffer",
+            WGPUBufferUsage_CopyDst | WGPUBufferUsage_Index,
+            fr->IndexBufferSize * sizeof(ImDrawIdx),
+            false
+        };
+        fr->IndexBuffer = wgpuDeviceCreateBuffer(g_wgpuDevice, &ib_desc);
+        if (!fr->IndexBuffer)
+            return;
+
+        fr->IndexBufferHost = new ImDrawIdx[fr->IndexBufferSize];
+    }
+
+    // Upload vertex/index data into a single contiguous GPU buffer
+    ImDrawVert* vtx_dst = (ImDrawVert*)fr->VertexBufferHost;
+    ImDrawIdx* idx_dst = (ImDrawIdx*)fr->IndexBufferHost;
+    for (int n = 0; n < draw_data->CmdListsCount; n++)
+    {
+        const ImDrawList* cmd_list = draw_data->CmdLists[n];
+        memcpy(vtx_dst, cmd_list->VtxBuffer.Data, cmd_list->VtxBuffer.Size * sizeof(ImDrawVert));
+        memcpy(idx_dst, cmd_list->IdxBuffer.Data, cmd_list->IdxBuffer.Size * sizeof(ImDrawIdx));
+        vtx_dst += cmd_list->VtxBuffer.Size;
+        idx_dst += cmd_list->IdxBuffer.Size;
+    }
+    int64_t vb_write_size = ((char*)vtx_dst - (char*)fr->VertexBufferHost + 3) & ~3;
+    int64_t ib_write_size = ((char*)idx_dst - (char*)fr->IndexBufferHost  + 3) & ~3;
+    wgpuQueueWriteBuffer(wgpuDeviceGetDefaultQueue(g_wgpuDevice), fr->VertexBuffer, 0, fr->VertexBufferHost, vb_write_size);
+    wgpuQueueWriteBuffer(wgpuDeviceGetDefaultQueue(g_wgpuDevice), fr->IndexBuffer,  0, fr->IndexBufferHost,  ib_write_size);
+
+    // Setup desired render state
+    ImGui_ImplWGPU_SetupRenderState(draw_data, pass_encoder, fr);
+
+    // Render command lists
+    // (Because we merged all buffers into a single one, we maintain our own offset into them)
+    int global_vtx_offset = 0;
+    int global_idx_offset = 0;
+    ImVec2 clip_off = draw_data->DisplayPos;
+    for (int n = 0; n < draw_data->CmdListsCount; n++)
+    {
+        const ImDrawList* cmd_list = draw_data->CmdLists[n];
+        for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++)
+        {
+            const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i];
+            if (pcmd->UserCallback != NULL)
+            {
+                // User callback, registered via ImDrawList::AddCallback()
+                // (ImDrawCallback_ResetRenderState is a special callback value used by the user to request the renderer to reset render state.)
+                if (pcmd->UserCallback == ImDrawCallback_ResetRenderState)
+                    ImGui_ImplWGPU_SetupRenderState(draw_data, pass_encoder, fr);
+                else
+                    pcmd->UserCallback(cmd_list, pcmd);
+            }
+            else
+            {
+                // Bind custom texture
+                auto bind_group = g_resources.ImageBindGroups.GetVoidPtr(ImHashData(&pcmd->TextureId, sizeof(ImTextureID)));
+                if (bind_group) {
+                    wgpuRenderPassEncoderSetBindGroup(pass_encoder, 1, (WGPUBindGroup)bind_group, 0, NULL);
+                }
+                else {
+                    WGPUBindGroup image_bind_group = ImGui_ImplWGPU_CreateImageBindGroup(g_resources.ImageBindGroupLayout, (WGPUTextureView) pcmd->TextureId);
+                    g_resources.ImageBindGroups.SetVoidPtr(ImHashData(&pcmd->TextureId, sizeof(ImTextureID)), image_bind_group);
+
+                    wgpuRenderPassEncoderSetBindGroup(pass_encoder, 1, image_bind_group, 0, NULL);
+                }
+
+                // Apply Scissor, Bind texture, Draw
+                uint32_t clip_rect[4];
+                clip_rect[0] = static_cast<uint32_t>(pcmd->ClipRect.x - clip_off.x);
+                clip_rect[1] = static_cast<uint32_t>(pcmd->ClipRect.y - clip_off.y);
+                clip_rect[2] = static_cast<uint32_t>(pcmd->ClipRect.z - clip_off.x);
+                clip_rect[3] = static_cast<uint32_t>(pcmd->ClipRect.w - clip_off.y);
+                wgpuRenderPassEncoderSetScissorRect(pass_encoder, clip_rect[0], clip_rect[1], clip_rect[2] - clip_rect[0], clip_rect[3] - clip_rect[1]);
+                wgpuRenderPassEncoderDrawIndexed(pass_encoder, pcmd->ElemCount, 1, pcmd->IdxOffset + global_idx_offset, pcmd->VtxOffset + global_vtx_offset, 0);
+            }
+        }
+        global_idx_offset += cmd_list->IdxBuffer.Size;
+        global_vtx_offset += cmd_list->VtxBuffer.Size;
+    }
+}
+
+static WGPUBuffer ImGui_ImplWGPU_CreateBufferFromData(const WGPUDevice& device, const void* data, uint64_t size, WGPUBufferUsage usage)
+{
+    WGPUBufferDescriptor descriptor = {};
+    descriptor.size = size;
+    descriptor.usage = usage | WGPUBufferUsage_CopyDst;
+    WGPUBuffer buffer = wgpuDeviceCreateBuffer(device, &descriptor);
+
+    WGPUQueue queue = wgpuDeviceGetDefaultQueue(g_wgpuDevice);
+    wgpuQueueWriteBuffer(queue, buffer, 0, data, size);
+    return buffer;
+}
+
+
+static void ImGui_ImplWGPU_CreateFontsTexture()
+{
+    // Build texture atlas
+    ImGuiIO& io = ImGui::GetIO();
+    unsigned char* pixels;
+    int width, height, size_pp;
+    io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height, &size_pp);
+
+    // Upload texture to graphics system
+    {
+        WGPUTextureDescriptor tex_desc = {};
+        tex_desc.label = "IMGUI Font Texture";
+        tex_desc.dimension = WGPUTextureDimension_2D;
+        tex_desc.size.width = width;
+        tex_desc.size.height = height;
+        tex_desc.size.depth = 1;
+        tex_desc.sampleCount = 1;
+        tex_desc.format = WGPUTextureFormat_RGBA8Unorm;
+        tex_desc.mipLevelCount = 1;
+        tex_desc.usage = WGPUTextureUsage_CopyDst | WGPUTextureUsage_Sampled;
+        g_resources.FontTexture = wgpuDeviceCreateTexture(g_wgpuDevice, &tex_desc);
+
+        WGPUTextureViewDescriptor tex_view_desc = {};
+        tex_view_desc.format = WGPUTextureFormat_RGBA8Unorm;
+        tex_view_desc.dimension = WGPUTextureViewDimension_2D;
+        tex_view_desc.baseMipLevel = 0;
+        tex_view_desc.mipLevelCount = 1;
+        tex_view_desc.baseArrayLayer = 0;
+        tex_view_desc.arrayLayerCount = 1;
+        tex_view_desc.aspect = WGPUTextureAspect_All;
+        g_resources.FontTextureView = wgpuTextureCreateView(g_resources.FontTexture, &tex_view_desc);
+    }
+
+    // Upload texture data
+    {
+        WGPUBuffer staging_buffer = ImGui_ImplWGPU_CreateBufferFromData(g_wgpuDevice, pixels, static_cast<uint32_t>(width * size_pp * height), WGPUBufferUsage_CopySrc);
+
+        WGPUBufferCopyView bufferCopyView = {};
+        bufferCopyView.buffer = staging_buffer;
+        bufferCopyView.layout.offset = 0;
+        bufferCopyView.layout.bytesPerRow = width * size_pp;
+        bufferCopyView.layout.rowsPerImage = height;
+
+        WGPUTextureCopyView textureCopyView = {};
+        textureCopyView.texture = g_resources.FontTexture;
+        textureCopyView.mipLevel = 0;
+        textureCopyView.origin = { 0, 0, 0 };
+#ifndef __EMSCRIPTEN__
+        textureCopyView.aspect = WGPUTextureAspect_All;
+#endif
+
+        WGPUExtent3D copySize = { static_cast<uint32_t>(width), static_cast<uint32_t>(height), 1 };
+
+        WGPUCommandEncoderDescriptor enc_desc = {};
+        WGPUCommandEncoder encoder = wgpuDeviceCreateCommandEncoder(g_wgpuDevice, &enc_desc);
+        wgpuCommandEncoderCopyBufferToTexture(encoder, &bufferCopyView, &textureCopyView, &copySize);
+        WGPUCommandBufferDescriptor cmd_buf_desc = {};
+        WGPUCommandBuffer copy = wgpuCommandEncoderFinish(encoder, &cmd_buf_desc);
+        WGPUQueue queue = wgpuDeviceGetDefaultQueue(g_wgpuDevice);
+        wgpuQueueSubmit(queue, 1, &copy);
+
+        wgpuCommandEncoderRelease(encoder);
+        wgpuBufferRelease(staging_buffer);
+    }
+
+    // Create the associated sampler
+    {
+        WGPUSamplerDescriptor sampler_desc = {};
+        sampler_desc.minFilter = WGPUFilterMode_Linear;
+        sampler_desc.magFilter = WGPUFilterMode_Linear;
+        sampler_desc.mipmapFilter = WGPUFilterMode_Linear;
+        sampler_desc.addressModeU = WGPUAddressMode_Repeat;
+        sampler_desc.addressModeV = WGPUAddressMode_Repeat;
+        sampler_desc.addressModeW = WGPUAddressMode_Repeat;
+        g_resources.Sampler = wgpuDeviceCreateSampler(g_wgpuDevice, &sampler_desc);
+    }
+
+    // Store our identifier
+    static_assert(sizeof(ImTextureID) >= sizeof(g_resources.FontTexture), "Can't pack descriptor handle into TexID, 32-bit not supported yet.");
+    io.Fonts->TexID = (ImTextureID)g_resources.FontTextureView;
+}
+
+static void ImGui_ImplWGPU_CreateUniformBuffer()
+{
+    WGPUBufferDescriptor ub_desc = {
+        nullptr,
+        "IMGUI Uniform buffer",
+        WGPUBufferUsage_CopyDst | WGPUBufferUsage_Uniform,
+        sizeof(Uniforms),
+        false
+    };
+    g_resources.Uniforms = wgpuDeviceCreateBuffer(g_wgpuDevice, &ub_desc);
+}
+
+bool ImGui_ImplWGPU_CreateDeviceObjects()
+{
+    if (!g_wgpuDevice)
+        return false;
+    if (g_pipelineState)
+        ImGui_ImplWGPU_InvalidateDeviceObjects();
+
+    // Create render pipeline
+    WGPURenderPipelineDescriptor graphics_pipeline_desc = {};
+
+    graphics_pipeline_desc.primitiveTopology = WGPUPrimitiveTopology_TriangleList;
+    graphics_pipeline_desc.sampleCount = 1;
+    graphics_pipeline_desc.sampleMask = UINT_MAX;
+
+
+    WGPUBindGroupLayoutEntry common_bg_layout_entries[2] = {};
+    common_bg_layout_entries[0].binding = 0;
+    common_bg_layout_entries[0].visibility = WGPUShaderStage_Vertex;
+    common_bg_layout_entries[0].type = WGPUBindingType_UniformBuffer;
+    common_bg_layout_entries[1].binding = 1;
+    common_bg_layout_entries[1].visibility = WGPUShaderStage_Fragment;
+    common_bg_layout_entries[1].type = WGPUBindingType_Sampler;
+
+    WGPUBindGroupLayoutEntry image_bg_layout_entries[1] = {};
+    image_bg_layout_entries[0].binding = 0;
+    image_bg_layout_entries[0].visibility = WGPUShaderStage_Fragment;
+    image_bg_layout_entries[0].type = WGPUBindingType_SampledTexture;
+
+    WGPUBindGroupLayoutDescriptor common_bg_layout_desc = {};
+    common_bg_layout_desc.entryCount = 2;
+    common_bg_layout_desc.entries = common_bg_layout_entries;
+
+    WGPUBindGroupLayoutDescriptor image_bg_layout_desc = {};
+    image_bg_layout_desc.entryCount = 1;
+    image_bg_layout_desc.entries = image_bg_layout_entries;
+
+    WGPUBindGroupLayout bg_layouts[2];
+    bg_layouts[0] = wgpuDeviceCreateBindGroupLayout(g_wgpuDevice, &common_bg_layout_desc);
+    bg_layouts[1] = wgpuDeviceCreateBindGroupLayout(g_wgpuDevice, &image_bg_layout_desc);
+
+    WGPUPipelineLayoutDescriptor layout_desc = {};
+    layout_desc.bindGroupLayoutCount = 2;
+    layout_desc.bindGroupLayouts = bg_layouts;
+    graphics_pipeline_desc.layout = wgpuDeviceCreatePipelineLayout(g_wgpuDevice, &layout_desc);
+
+    // Create the vertex shader
+    WGPUProgrammableStageDescriptor vertex_shader_desc = ImGui_ImplWGPU_CreateShaderModule(__glsl_shader_vert_spv, sizeof(__glsl_shader_vert_spv) / sizeof(uint32_t));
+    graphics_pipeline_desc.vertexStage = vertex_shader_desc;
+
+    // Vertex input configuration
+    WGPUVertexAttributeDescriptor attribute_binding_desc[] = {
+        { WGPUVertexFormat_Float2,     (uint64_t)IM_OFFSETOF(ImDrawVert, pos), 0 },
+        { WGPUVertexFormat_Float2,     (uint64_t)IM_OFFSETOF(ImDrawVert, uv),  1 },
+        { WGPUVertexFormat_UChar4Norm, (uint64_t)IM_OFFSETOF(ImDrawVert, col), 2 },
+    };
+
+    WGPUVertexBufferLayoutDescriptor buffer_binding_desc;
+    buffer_binding_desc.arrayStride = sizeof(ImDrawVert);
+    buffer_binding_desc.stepMode = WGPUInputStepMode_Vertex;
+    buffer_binding_desc.attributeCount = 3;
+    buffer_binding_desc.attributes = attribute_binding_desc;
+
+    WGPUVertexStateDescriptor vertex_state_desc = {};
+    vertex_state_desc.indexFormat = WGPUIndexFormat_Undefined;
+    vertex_state_desc.vertexBufferCount = 1;
+    vertex_state_desc.vertexBuffers = &buffer_binding_desc;
+
+    graphics_pipeline_desc.vertexState = &vertex_state_desc;
+
+    // Create the pixel shader
+    WGPUProgrammableStageDescriptor pixel_shader_desc = ImGui_ImplWGPU_CreateShaderModule(__glsl_shader_frag_spv, sizeof(__glsl_shader_frag_spv) / sizeof(uint32_t));
+    graphics_pipeline_desc.fragmentStage = &pixel_shader_desc;
+
+    // Create the blending setup
+    WGPUColorStateDescriptor color_state = {};
+    {
+        color_state.format = g_renderTargetFormat;
+        color_state.alphaBlend.operation = WGPUBlendOperation_Add;
+        color_state.alphaBlend.srcFactor = WGPUBlendFactor_OneMinusSrcAlpha;
+        color_state.alphaBlend.dstFactor = WGPUBlendFactor_Zero;
+        color_state.colorBlend.operation = WGPUBlendOperation_Add;
+        color_state.colorBlend.srcFactor = WGPUBlendFactor_SrcAlpha;
+        color_state.colorBlend.dstFactor = WGPUBlendFactor_OneMinusSrcAlpha;
+        color_state.writeMask = WGPUColorWriteMask_All;
+
+        graphics_pipeline_desc.colorStateCount = 1;
+        graphics_pipeline_desc.colorStates = &color_state;
+        graphics_pipeline_desc.alphaToCoverageEnabled = false;
+    }
+
+    // Create the rasterizer state
+    WGPURasterizationStateDescriptor raster_desc = {};
+    {
+        raster_desc.cullMode = WGPUCullMode_None;
+        raster_desc.frontFace = WGPUFrontFace_CW;
+        raster_desc.depthBias = 0;
+        raster_desc.depthBiasClamp = 0;
+        raster_desc.depthBiasSlopeScale = 0;
+        graphics_pipeline_desc.rasterizationState = &raster_desc;
+    }
+
+    // Create depth-stencil State
+    WGPUDepthStencilStateDescriptor depth_desc = {};
+    {
+        // Configure disabled state
+        depth_desc.format = WGPUTextureFormat_Undefined;
+
+        depth_desc.depthWriteEnabled = true;
+        depth_desc.depthCompare = WGPUCompareFunction_Always;
+        depth_desc.stencilReadMask = 0;
+        depth_desc.stencilWriteMask = 0;
+        depth_desc.stencilBack.compare = WGPUCompareFunction_Always;
+        depth_desc.stencilBack.failOp = WGPUStencilOperation_Keep;
+        depth_desc.stencilBack.depthFailOp = WGPUStencilOperation_Keep;
+        depth_desc.stencilBack.passOp = WGPUStencilOperation_Keep;
+        depth_desc.stencilFront.compare = WGPUCompareFunction_Always;
+        depth_desc.stencilFront.failOp = WGPUStencilOperation_Keep;
+        depth_desc.stencilFront.depthFailOp = WGPUStencilOperation_Keep;
+        depth_desc.stencilFront.passOp = WGPUStencilOperation_Keep;
+
+        // No depth buffer corresponds to no configuration
+        graphics_pipeline_desc.depthStencilState = nullptr;
+    }
+
+    g_pipelineState = wgpuDeviceCreateRenderPipeline(g_wgpuDevice, &graphics_pipeline_desc);
+
+    ImGui_ImplWGPU_CreateFontsTexture();
+    ImGui_ImplWGPU_CreateUniformBuffer();
+
+    // Create resource bind group
+    WGPUBindGroupEntry common_bg_entries[] = {
+        { 0, g_resources.Uniforms, 0, sizeof(Uniforms), 0, 0 },
+        { 1, 0, 0, 0, g_resources.Sampler, 0 },
+    };
+
+    WGPUBindGroupDescriptor common_bg_descriptor = {};
+    common_bg_descriptor.layout = bg_layouts[0];
+    common_bg_descriptor.entryCount = sizeof(common_bg_entries) / sizeof(WGPUBindGroupEntry);
+    common_bg_descriptor.entries = common_bg_entries;
+    g_resources.CommonBindGroup = wgpuDeviceCreateBindGroup(g_wgpuDevice, &common_bg_descriptor);
+    g_resources.ImageBindGroupLayout = bg_layouts[1];
+
+    WGPUBindGroup image_bind_group = ImGui_ImplWGPU_CreateImageBindGroup(bg_layouts[1], g_resources.FontTextureView);
+    g_resources.ImageBindGroup = image_bind_group;
+    g_resources.ImageBindGroups.SetVoidPtr(ImHashData(&g_resources.FontTextureView, sizeof(ImTextureID)), image_bind_group);
+
+    SafeRelease(vertex_shader_desc.module);
+    SafeRelease(pixel_shader_desc.module);
+    SafeRelease(bg_layouts[0]);
+
+    return true;
+}
+
+void ImGui_ImplWGPU_InvalidateDeviceObjects()
+{
+    if (!g_wgpuDevice)
+        return;
+
+    SafeRelease(g_pipelineState);
+    SafeRelease(g_resources);
+
+    ImGuiIO& io = ImGui::GetIO();
+    io.Fonts->TexID = NULL; // We copied g_pFontTextureView to io.Fonts->TexID so let's clear that as well.
+
+    for (unsigned int i = 0; i < g_numFramesInFlight; i++)
+    {
+        SafeRelease(g_pFrameResources[i]);
+    }
+}
+
+bool ImGui_ImplWGPU_Init(WGPUDevice device, int num_frames_in_flight, WGPUTextureFormat rt_format)
+{
+    // Setup back-end capabilities flags
+    ImGuiIO& io = ImGui::GetIO();
+    io.BackendRendererName = "imgui_impl_webgpu";
+    io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset;  // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes.
+
+    g_wgpuDevice = device;
+    g_renderTargetFormat = rt_format;
+    g_pFrameResources = new FrameResources[num_frames_in_flight];
+    g_numFramesInFlight = num_frames_in_flight;
+    g_frameIndex = UINT_MAX;
+
+    g_resources.FontTexture = NULL;
+    g_resources.FontTextureView = NULL;
+    g_resources.Sampler = NULL;
+    g_resources.Uniforms = NULL;
+    g_resources.CommonBindGroup = NULL;
+    g_resources.ImageBindGroupLayout = NULL;
+    g_resources.ImageBindGroups.Data.reserve(100);
+    g_resources.ImageBindGroup = NULL;
+
+    // Create buffers with a default size (they will later be grown as needed)
+    for (int i = 0; i < num_frames_in_flight; i++)
+    {
+        FrameResources* fr = &g_pFrameResources[i];
+        fr->IndexBuffer = NULL;
+        fr->VertexBuffer = NULL;
+        fr->IndexBufferHost = NULL;
+        fr->VertexBufferHost = NULL;
+        fr->IndexBufferSize = 10000;
+        fr->VertexBufferSize = 5000;
+    }
+
+    return true;
+}
+
+void ImGui_ImplWGPU_Shutdown()
+{
+    ImGui_ImplWGPU_InvalidateDeviceObjects();
+    delete[] g_pFrameResources;
+    g_pFrameResources = NULL;
+    g_wgpuDevice = NULL;
+    g_numFramesInFlight = 0;
+    g_frameIndex = UINT_MAX;
+}
+
+void ImGui_ImplWGPU_NewFrame()
+{
+    if (!g_pipelineState)
+        ImGui_ImplWGPU_CreateDeviceObjects();
+}

+ 26 - 0
backends/imgui_impl_wgpu.h

@@ -0,0 +1,26 @@
+// dear imgui: Renderer for WebGPU
+// This needs to be used along with a Platform Binding (e.g. GLFW)
+
+// Implemented features:
+//  [X] Renderer: User texture binding. Use 'WGPUTextureView' as ImTextureID. Read the FAQ about ImTextureID!
+//  [X] Renderer: Support for large meshes (64k+ vertices) with 16-bit indices.
+
+// You can copy and use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
+// If you are new to Dear ImGui, read documentation from the docs/ folder + read the top of imgui.cpp.
+// Read online: https://github.com/ocornut/imgui/tree/master/docs
+
+#pragma once
+#include "imgui.h"      // IMGUI_IMPL_API
+
+// WebGPU
+#include <webgpu/webgpu.h>
+
+// cmd_list is the command list that the implementation will use to render imgui draw lists.
+IMGUI_IMPL_API bool ImGui_ImplWGPU_Init(WGPUDevice device, int num_frames_in_flight, WGPUTextureFormat rt_format);
+IMGUI_IMPL_API void ImGui_ImplWGPU_Shutdown();
+IMGUI_IMPL_API void ImGui_ImplWGPU_NewFrame();
+IMGUI_IMPL_API void ImGui_ImplWGPU_RenderDrawData(ImDrawData* draw_data, WGPURenderPassEncoder pass_encoder);
+
+// Use if you want to reset your rendering device without losing Dear ImGui state.
+IMGUI_IMPL_API void ImGui_ImplWGPU_InvalidateDeviceObjects();
+IMGUI_IMPL_API bool ImGui_ImplWGPU_CreateDeviceObjects();

+ 1 - 0
docs/BACKENDS.md

@@ -68,6 +68,7 @@ List of Renderer Backends:
     imgui_impl_opengl2.cpp    ; OpenGL 2 (legacy, fixed pipeline <- don't use with modern OpenGL context)
     imgui_impl_opengl2.cpp    ; OpenGL 2 (legacy, fixed pipeline <- don't use with modern OpenGL context)
     imgui_impl_opengl3.cpp    ; OpenGL 3/4, OpenGL ES 2, OpenGL ES 3 (modern programmable pipeline)
     imgui_impl_opengl3.cpp    ; OpenGL 3/4, OpenGL ES 2, OpenGL ES 3 (modern programmable pipeline)
     imgui_impl_vulkan.cpp     ; Vulkan
     imgui_impl_vulkan.cpp     ; Vulkan
+    imgui_impl_wgpu.cpp       ; WebGPU
 
 
 List of high-level Frameworks Backends (combining Platform + Renderer):
 List of high-level Frameworks Backends (combining Platform + Renderer):
 
 

+ 1 - 0
docs/CHANGELOG.txt

@@ -94,6 +94,7 @@ Breaking Changes:
   confusion with Tables API. Keep redirection enums (will obsolete). (#125, #513, #913, #1204, #1444, #2142, #2707)
   confusion with Tables API. Keep redirection enums (will obsolete). (#125, #513, #913, #1204, #1444, #2142, #2707)
 - Renamed io.ConfigWindowsMemoryCompactTimer to io.ConfigMemoryCompactTimer as the feature now applies
 - Renamed io.ConfigWindowsMemoryCompactTimer to io.ConfigMemoryCompactTimer as the feature now applies
   to other data structures. (#2636)
   to other data structures. (#2636)
+- Backends: WebGPU: Added backend for WebGPU support in imgui_impl_wgpu [@bfierz]
 
 
 Other Changes:
 Other Changes:
 
 

+ 4 - 0
docs/EXAMPLES.md

@@ -98,6 +98,10 @@ Emcripten + SDL2 + OpenGL3+/ES2/ES3 example. <BR>
 Note that other examples based on SDL or GLFW + OpenGL could easily be modified to work with Emscripten.
 Note that other examples based on SDL or GLFW + OpenGL could easily be modified to work with Emscripten.
 We provide this to make the Emscripten differences obvious, and have them not pollute all other examples.
 We provide this to make the Emscripten differences obvious, and have them not pollute all other examples.
 
 
+[example_emscripten_wgpu/](https://github.com/ocornut/imgui/blob/master/examples/example_emscripten_wgpu/) <BR>
+Emcripten + GLFW + WebGPU example. <BR>
+= main.cpp + imgui_impl_glfw.cpp + imgui_impl_wgpu.cpp
+
 [example_glfw_metal/](https://github.com/ocornut/imgui/blob/master/examples/example_glfw_metal/) <BR>
 [example_glfw_metal/](https://github.com/ocornut/imgui/blob/master/examples/example_glfw_metal/) <BR>
 GLFW (Mac) + Metal example. <BR>
 GLFW (Mac) + Metal example. <BR>
 = main.mm + imgui_impl_glfw.cpp + imgui_impl_metal.mm
 = main.mm + imgui_impl_glfw.cpp + imgui_impl_metal.mm

+ 80 - 0
examples/example_emscripten_wgpu/Makefile

@@ -0,0 +1,80 @@
+#
+# Makefile to use with emscripten
+# See https://emscripten.org/docs/getting_started/downloads.html
+# for installation instructions.
+#
+# This Makefile assumes you have loaded emscripten's environment.
+# (On Windows, you may need to execute emsdk_env.bat or encmdprompt.bat ahead)
+#
+# Running `make` will produce two files:
+#  - example_emscripten_wgpu.js
+#  - example_emscripten_wgpu.wasm
+#
+# All three are needed to run the demo.
+
+CC = emcc
+CXX = em++
+EXE = example_emscripten_wgpu.js
+IMGUI_DIR = ../..
+SOURCES = main.cpp
+SOURCES += $(IMGUI_DIR)/imgui.cpp $(IMGUI_DIR)/imgui_demo.cpp $(IMGUI_DIR)/imgui_draw.cpp $(IMGUI_DIR)/imgui_widgets.cpp
+SOURCES += $(IMGUI_DIR)/backends/imgui_impl_glfw.cpp $(IMGUI_DIR)/backends/imgui_impl_wgpu.cpp
+OBJS = $(addsuffix .o, $(basename $(notdir $(SOURCES))))
+UNAME_S := $(shell uname -s)
+
+##---------------------------------------------------------------------
+## EMSCRIPTEN OPTIONS
+##---------------------------------------------------------------------
+
+EMS += -s USE_GLFW=3 -s USE_WEBGPU=1 -s WASM=1
+EMS += -s ALLOW_MEMORY_GROWTH=1
+EMS += -s DISABLE_EXCEPTION_CATCHING=1 -s NO_EXIT_RUNTIME=0
+EMS += -s ASSERTIONS=1
+
+# Emscripten allows preloading a file or folder to be accessible at runtime.
+# The Makefile for this example project suggests embedding the misc/fonts/ folder into our application, it will then be accessible as "/fonts"
+# See documentation for more details: https://emscripten.org/docs/porting/files/packaging_files.html
+# (Default value is 0. Set to 1 to enable file-system and include the misc/fonts/ folder as part of the build.)
+USE_FILE_SYSTEM ?= 0
+ifeq ($(USE_FILE_SYSTEM), 0)
+EMS += -s NO_FILESYSTEM=1 -DIMGUI_DISABLE_FILE_FUNCTIONS
+endif
+ifeq ($(USE_FILE_SYSTEM), 1)
+LDFLAGS += --no-heap-copy --preload-file ../../misc/fonts@/fonts
+endif
+
+##---------------------------------------------------------------------
+## FINAL BUILD FLAGS
+##---------------------------------------------------------------------
+
+CPPFLAGS += -I$(IMGUI_DIR)/ -I$(IMGUI_DIR)/backends
+#CPPFLAGS += -g
+CPPFLAGS += -Wall -Wformat -Os
+CPPFLAGS += $(EMS)
+LIBS += $(EMS)
+#LDFLAGS += --shell-file shell_minimal.html
+
+##---------------------------------------------------------------------
+## BUILD RULES
+##---------------------------------------------------------------------
+
+%.o:%.cpp
+	$(CXX) $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $<
+
+%.o:../%.cpp
+	$(CXX) $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $<
+
+%.o:$(IMGUI_DIR)/%.cpp
+	$(CXX) $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $<
+
+%.o:$(IMGUI_DIR)/backends/%.cpp
+	$(CXX) $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $<
+
+all: $(EXE)
+	@echo Build complete for $(EXE)
+
+$(EXE): $(OBJS)
+	$(CXX) -o $@ $^ $(LIBS) $(LDFLAGS)
+
+clean:
+	rm -f $(EXE) $(OBJS) *.js *.wasm *.wasm.pre

+ 10 - 0
examples/example_emscripten_wgpu/README.md

@@ -0,0 +1,10 @@
+
+# How to Build
+
+- You need to install Emscripten from https://emscripten.org/docs/getting_started/downloads.html, and have the environment variables set, as described in https://emscripten.org/docs/getting_started/downloads.html#installation-instructions
+
+- Depending on your configuration, in Windows you may need to run `emsdk/emsdk_env.bat` in your console to access the Emscripten command-line tools.
+
+- Then build using `make` while in the `example_emscripten_wgpu/` directory.
+
+- Requires Emscripten 2.0.10 (December 2020) due to GLFW adaptations

+ 80 - 0
examples/example_emscripten_wgpu/example_emscripten_wgpu.html

@@ -0,0 +1,80 @@
+<!doctype html>
+<html lang="en-us">
+  <head>
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no"/>
+    <title>Dear ImGui Emscripten example</title>
+    <style>
+        body { margin: 0; background-color: black }
+        .emscripten {
+            position: absolute;
+            top: 0px;
+            left: 0px;
+            margin: 0px;
+            border: 0;
+            width: 100%;
+            height: 100%;
+            overflow: hidden;
+            display: block;
+            image-rendering: optimizeSpeed;
+            image-rendering: -moz-crisp-edges;
+            image-rendering: -o-crisp-edges;
+            image-rendering: -webkit-optimize-contrast;
+            image-rendering: optimize-contrast;
+            image-rendering: crisp-edges;
+            image-rendering: pixelated;
+            -ms-interpolation-mode: nearest-neighbor;
+        }
+    </style>
+  </head>
+  <body>
+    <canvas class="emscripten" id="canvas" oncontextmenu="event.preventDefault()"></canvas>
+    <script type='text/javascript'>
+      var Module;
+      (async () => {
+        Module = {
+          preRun: [],
+          postRun: [],
+          print: (function() {
+              return function(text) {
+                  text = Array.prototype.slice.call(arguments).join(' ');
+                  console.log(text);
+              };
+          })(),
+          printErr: function(text) {
+              text = Array.prototype.slice.call(arguments).join(' ');
+              console.error(text);
+          },
+          canvas: (function() {
+              var canvas = document.getElementById('canvas');
+              //canvas.addEventListener("webglcontextlost", function(e) { alert('FIXME: WebGL context lost, please reload the page'); e.preventDefault(); }, false);
+              return canvas;
+          })(),
+          setStatus: function(text) {
+              console.log("status: " + text);
+          },
+          monitorRunDependencies: function(left) {
+              // no run dependencies to log
+          }
+        };
+        window.onerror = function() {
+          console.log("onerror: " + event);
+        };
+
+      // Initialize the graphics adapter
+      {
+          const adapter = await navigator.gpu.requestAdapter();
+          const device = await adapter.requestDevice();
+          Module.preinitializedWebGPUDevice = device;
+      }
+
+      {
+          const js = document.createElement('script');
+          js.async = true;
+          js.src = "example_emscripten_wgpu.js";
+          document.body.appendChild(js);
+      }
+      })();
+    </script>
+  </body>
+</html>

+ 257 - 0
examples/example_emscripten_wgpu/main.cpp

@@ -0,0 +1,257 @@
+// Dear ImGui: standalone example application for Emscripten, using GLFW + WebGPU
+// (Emscripten is a C++-to-javascript compiler, used to publish executables for the web. See https://emscripten.org/)
+// If you are new to Dear ImGui, read documentation from the docs/ folder + read the top of imgui.cpp.
+// Read online: https://github.com/ocornut/imgui/tree/master/docs
+
+// This is mostly the same code as the SDL2 + OpenGL3 example, simply with the modifications needed to run on Emscripten.
+// It is possible to combine both code into a single source file that will compile properly on Desktop and using Emscripten.
+// See https://github.com/ocornut/imgui/pull/2492 as an example on how to do just that.
+
+#include "imgui.h"
+#include "imgui_impl_glfw.h"
+#include "imgui_impl_wgpu.h"
+#include <stdio.h>
+#include <emscripten.h>
+#include <emscripten/html5.h>
+#include <emscripten/html5_webgpu.h>
+#include <GLFW/glfw3.h>
+#include <webgpu/webgpu.h>
+#include <webgpu/webgpu_cpp.h>
+
+// Global WebGPU required states
+static WGPUDevice    wgpu_device = NULL;
+static WGPUSurface   wgpu_surface = NULL;
+static WGPUSwapChain wgpu_swap_chain = NULL;
+
+static int           wgpu_swap_chain_width = 0;
+static int           wgpu_swap_chain_height = 0;
+
+// States tracked across render frames
+static bool show_demo_window = true;
+static bool show_another_window = false;
+static ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f);
+
+// Forward declartions
+bool init_wgpu();
+void main_loop(void* window);
+void print_glfw_error(int error, const char* description);
+void print_wgpu_error(WGPUErrorType error_type, const char* message, void*);
+
+int main(int, char**)
+{
+    glfwSetErrorCallback(print_glfw_error);
+    if (!glfwInit())
+        return 1;
+
+    // Make sure GLFW does not initialize any graphics context.
+    // This needs to be done explicitly later
+    glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
+
+    GLFWwindow* window = glfwCreateWindow(1280, 720, "Dear ImGui GLFW+WebGPU example", NULL, NULL);
+    if (!window) {
+        glfwTerminate();
+        return 1;
+    }
+
+    // Initialize the WebGPU environment
+    if (!init_wgpu()) {
+        if (window)
+            glfwDestroyWindow(window);
+        glfwTerminate();
+        return 1;
+    }
+    glfwShowWindow(window);
+
+    // Setup Dear ImGui context
+    IMGUI_CHECKVERSION();
+    ImGui::CreateContext();
+    ImGuiIO& io = ImGui::GetIO(); (void)io;
+    //io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;     // Enable Keyboard Controls
+    //io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad;      // Enable Gamepad Controls
+
+    // For an Emscripten build we are disabling file-system access, so let's not attempt to do a fopen() of the imgui.ini file.
+    // You may manually call LoadIniSettingsFromMemory() to load settings from your own storage.
+    io.IniFilename = NULL;
+
+    // Setup Dear ImGui style
+    ImGui::StyleColorsDark();
+    //ImGui::StyleColorsClassic();
+
+    // Setup Platform/Renderer backends
+    ImGui_ImplGlfw_InitForVulkan(window, true);
+    ImGui_ImplWGPU_Init(wgpu_device, 3, WGPUTextureFormat_RGBA8Unorm);
+
+    // Load Fonts
+    // - If no fonts are loaded, dear imgui will use the default font. You can also load multiple fonts and use ImGui::PushFont()/PopFont() to select them.
+    // - AddFontFromFileTTF() will return the ImFont* so you can store it if you need to select the font among multiple.
+    // - If the file cannot be loaded, the function will return NULL. Please handle those errors in your application (e.g. use an assertion, or display an error and quit).
+    // - The fonts will be rasterized at a given size (w/ oversampling) and stored into a texture when calling ImFontAtlas::Build()/GetTexDataAsXXXX(), which ImGui_ImplXXXX_NewFrame below will call.
+    // - Read 'docs/FONTS.md' for more instructions and details.
+    // - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ !
+    // - Emscripten allows preloading a file or folder to be accessible at runtime. See Makefile for details.
+    //io.Fonts->AddFontDefault();
+#ifndef IMGUI_DISABLE_FILE_FUNCTIONS
+    io.Fonts->AddFontFromFileTTF("fonts/Roboto-Medium.ttf", 16.0f);
+    //io.Fonts->AddFontFromFileTTF("fonts/Cousine-Regular.ttf", 15.0f);
+    //io.Fonts->AddFontFromFileTTF("fonts/DroidSans.ttf", 16.0f);
+    //io.Fonts->AddFontFromFileTTF("fonts/ProggyTiny.ttf", 10.0f);
+    //ImFont* font = io.Fonts->AddFontFromFileTTF("fonts/ArialUni.ttf", 18.0f, NULL, io.Fonts->GetGlyphRangesJapanese());
+    //IM_ASSERT(font != NULL);
+#endif
+
+    // This function will directly return and extit he main function.
+    // Make sure that no required objects get cleaned up.
+    // This way we can use the browsers 'requestAnimationFrame' to control the rendering.
+    emscripten_set_main_loop_arg(main_loop, window, 0, false);
+
+    return 0;
+}
+
+bool init_wgpu()
+{
+    wgpu_device = emscripten_webgpu_get_device();
+    if (!wgpu_device)
+        return false;
+
+    wgpuDeviceSetUncapturedErrorCallback(wgpu_device, print_wgpu_error, nullptr);
+
+    // Use C++ wrapper due to malbehaviour in Emscripten.
+    // Some offset computation for wgpuInstanceCreateSurface in JavaScript
+    // seem to be inline with struct alignments in the C++ structure
+    wgpu::SurfaceDescriptorFromCanvasHTMLSelector html_surface_desc{};
+    html_surface_desc.selector = "#canvas";
+
+    wgpu::SurfaceDescriptor surface_desc{};
+    surface_desc.nextInChain = &html_surface_desc;
+
+    // Use 'null' instance
+    wgpu::Instance instance{};
+    wgpu_surface = instance.CreateSurface(&surface_desc).Release();
+
+    return true;
+}
+
+void main_loop(void* window)
+{
+    glfwPollEvents();
+
+    int width, height;
+    glfwGetFramebufferSize((GLFWwindow*) window, &width, &height);
+
+    // React to changes in screen size
+    if (width != wgpu_swap_chain_width && height != wgpu_swap_chain_height)
+    {
+        ImGui_ImplWGPU_InvalidateDeviceObjects();
+
+        if (wgpu_swap_chain)
+            wgpuSwapChainRelease(wgpu_swap_chain);
+
+        wgpu_swap_chain_width = width;
+        wgpu_swap_chain_height = height;
+
+        WGPUSwapChainDescriptor swap_chain_desc = {};
+        swap_chain_desc.usage = WGPUTextureUsage_OutputAttachment;
+        swap_chain_desc.format = WGPUTextureFormat_RGBA8Unorm;
+        swap_chain_desc.width = width;
+        swap_chain_desc.height = height;
+        swap_chain_desc.presentMode = WGPUPresentMode_Fifo;
+        wgpu_swap_chain = wgpuDeviceCreateSwapChain(wgpu_device, wgpu_surface, &swap_chain_desc);
+
+        ImGui_ImplWGPU_CreateDeviceObjects();
+    }
+
+    // Start the Dear ImGui frame
+    ImGui_ImplWGPU_NewFrame();
+    ImGui_ImplGlfw_NewFrame();
+    ImGui::NewFrame();
+
+    // 1. Show the big demo window (Most of the sample code is in ImGui::ShowDemoWindow()! You can browse its code to learn more about Dear ImGui!).
+    if (show_demo_window)
+        ImGui::ShowDemoWindow(&show_demo_window);
+
+    // 2. Show a simple window that we create ourselves. We use a Begin/End pair to created a named window.
+    {
+        static float f = 0.0f;
+        static int counter = 0;
+
+        ImGui::Begin("Hello, world!");                                // Create a window called "Hello, world!" and append into it.
+
+        ImGui::Text("This is some useful text.");                     // Display some text (you can use a format strings too)
+        ImGui::Checkbox("Demo Window", &show_demo_window);            // Edit bools storing our window open/close state
+        ImGui::Checkbox("Another Window", &show_another_window);
+
+        ImGui::SliderFloat("float", &f, 0.0f, 1.0f);                  // Edit 1 float using a slider from 0.0f to 1.0f
+        ImGui::ColorEdit3("clear color", (float*)&clear_color);       // Edit 3 floats representing a color
+
+        if (ImGui::Button("Button"))                                  // Buttons return true when clicked (most widgets return true when edited/activated)
+            counter++;
+        ImGui::SameLine();
+        ImGui::Text("counter = %d", counter);
+
+        ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / ImGui::GetIO().Framerate, ImGui::GetIO().Framerate);
+        ImGui::End();
+    }
+
+    // 3. Show another simple window.
+    if (show_another_window)
+    {
+        ImGui::Begin("Another Window", &show_another_window);         // Pass a pointer to our bool variable (the window will have a closing button that will clear the bool when clicked)
+        ImGui::Text("Hello from another window!");
+        if (ImGui::Button("Close Me"))
+            show_another_window = false;
+        ImGui::End();
+    }
+    
+    // Render the generated ImGui frame
+    ImGui::Render();
+
+    WGPURenderPassColorAttachmentDescriptor color_attachments = {};
+    color_attachments.loadOp = WGPULoadOp_Clear;
+    color_attachments.storeOp = WGPUStoreOp_Store;
+    color_attachments.clearColor = { clear_color.x, clear_color.y, clear_color.z, clear_color.w };
+    color_attachments.attachment = wgpuSwapChainGetCurrentTextureView(wgpu_swap_chain);
+    WGPURenderPassDescriptor render_pass_desc = {};
+    render_pass_desc.colorAttachmentCount = 1;
+    render_pass_desc.colorAttachments = &color_attachments;
+    render_pass_desc.depthStencilAttachment = nullptr;
+
+    WGPUCommandEncoderDescriptor enc_desc = {};
+    WGPUCommandEncoder encoder = wgpuDeviceCreateCommandEncoder(wgpu_device, &enc_desc);
+
+    WGPURenderPassEncoder pass = wgpuCommandEncoderBeginRenderPass(encoder, &render_pass_desc);
+    ImGui_ImplWGPU_RenderDrawData(ImGui::GetDrawData(), pass);
+    wgpuRenderPassEncoderEndPass(pass);
+
+    WGPUCommandBufferDescriptor cmd_buffer_desc = {};
+    WGPUCommandBuffer cmd_buffer = wgpuCommandEncoderFinish(encoder, &cmd_buffer_desc);
+    WGPUQueue queue = wgpuDeviceGetDefaultQueue(wgpu_device);
+    wgpuQueueSubmit(queue, 1, &cmd_buffer);
+}
+
+void print_glfw_error(int error, const char* description)
+{
+    printf("Glfw Error %d: %s\n", error, description);
+}
+
+void print_wgpu_error(WGPUErrorType error_type, const char* message, void*)
+{
+    const char* error_type_lbl = "";
+    switch (error_type) {
+    case WGPUErrorType_Validation:
+        error_type_lbl = "Validation";
+        break;
+    case WGPUErrorType_OutOfMemory:
+        error_type_lbl = "Out of memory";
+        break;
+    case WGPUErrorType_Unknown:
+        error_type_lbl = "Unknown";
+        break;
+    case WGPUErrorType_DeviceLost:
+        error_type_lbl = "Device lost";
+        break;
+    default:
+        error_type_lbl = "Unknown";
+    }
+
+    printf("%s error: %s\n", error_type_lbl, message);
+}