• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Dear ImGui: standalone example application for Emscripten, using GLFW + WebGPU
2 // (Emscripten is a C++-to-javascript compiler, used to publish executables for the web. See https://emscripten.org/)
3 // If you are new to Dear ImGui, read documentation from the docs/ folder + read the top of imgui.cpp.
4 // Read online: https://github.com/ocornut/imgui/tree/master/docs
5 
6 #include "imgui.h"
7 #include "imgui_impl_glfw.h"
8 #include "imgui_impl_wgpu.h"
9 #include <stdio.h>
10 #include <emscripten.h>
11 #include <emscripten/html5.h>
12 #include <emscripten/html5_webgpu.h>
13 #include <GLFW/glfw3.h>
14 #include <webgpu/webgpu.h>
15 #include <webgpu/webgpu_cpp.h>
16 
17 // Global WebGPU required states
18 static WGPUDevice    wgpu_device = NULL;
19 static WGPUSurface   wgpu_surface = NULL;
20 static WGPUSwapChain wgpu_swap_chain = NULL;
21 static int           wgpu_swap_chain_width = 0;
22 static int           wgpu_swap_chain_height = 0;
23 
24 // States tracked across render frames
25 static bool show_demo_window = true;
26 static bool show_another_window = false;
27 static ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f);
28 
29 // Forward declarations
30 static bool init_wgpu();
31 static void main_loop(void* window);
32 static void print_glfw_error(int error, const char* description);
33 static void print_wgpu_error(WGPUErrorType error_type, const char* message, void*);
34 
main(int,char **)35 int main(int, char**)
36 {
37     glfwSetErrorCallback(print_glfw_error);
38     if (!glfwInit())
39         return 1;
40 
41     // Make sure GLFW does not initialize any graphics context.
42     // This needs to be done explicitly later
43     glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
44 
45     GLFWwindow* window = glfwCreateWindow(1280, 720, "Dear ImGui GLFW+WebGPU example", NULL, NULL);
46     if (!window)
47     {
48         glfwTerminate();
49         return 1;
50     }
51 
52     // Initialize the WebGPU environment
53     if (!init_wgpu())
54     {
55         if (window)
56             glfwDestroyWindow(window);
57         glfwTerminate();
58         return 1;
59     }
60     glfwShowWindow(window);
61 
62     // Setup Dear ImGui context
63     IMGUI_CHECKVERSION();
64     ImGui::CreateContext();
65     ImGuiIO& io = ImGui::GetIO(); (void)io;
66     //io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;     // Enable Keyboard Controls
67     //io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad;      // Enable Gamepad Controls
68 
69     // For an Emscripten build we are disabling file-system access, so let's not attempt to do a fopen() of the imgui.ini file.
70     // You may manually call LoadIniSettingsFromMemory() to load settings from your own storage.
71     io.IniFilename = NULL;
72 
73     // Setup Dear ImGui style
74     ImGui::StyleColorsDark();
75     //ImGui::StyleColorsClassic();
76 
77     // Setup Platform/Renderer backends
78     ImGui_ImplGlfw_InitForOther(window, true);
79     ImGui_ImplWGPU_Init(wgpu_device, 3, WGPUTextureFormat_RGBA8Unorm);
80 
81     // Load Fonts
82     // - 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.
83     // - AddFontFromFileTTF() will return the ImFont* so you can store it if you need to select the font among multiple.
84     // - 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).
85     // - 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.
86     // - Read 'docs/FONTS.md' for more instructions and details.
87     // - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ !
88     // - Emscripten allows preloading a file or folder to be accessible at runtime. See Makefile for details.
89     //io.Fonts->AddFontDefault();
90 #ifndef IMGUI_DISABLE_FILE_FUNCTIONS
91     io.Fonts->AddFontFromFileTTF("fonts/Roboto-Medium.ttf", 16.0f);
92     //io.Fonts->AddFontFromFileTTF("fonts/Cousine-Regular.ttf", 15.0f);
93     //io.Fonts->AddFontFromFileTTF("fonts/DroidSans.ttf", 16.0f);
94     //io.Fonts->AddFontFromFileTTF("fonts/ProggyTiny.ttf", 10.0f);
95     //ImFont* font = io.Fonts->AddFontFromFileTTF("fonts/ArialUni.ttf", 18.0f, NULL, io.Fonts->GetGlyphRangesJapanese());
96     //IM_ASSERT(font != NULL);
97 #endif
98 
99     // This function will directly return and exit the main function.
100     // Make sure that no required objects get cleaned up.
101     // This way we can use the browsers 'requestAnimationFrame' to control the rendering.
102     emscripten_set_main_loop_arg(main_loop, window, 0, false);
103 
104     return 0;
105 }
106 
init_wgpu()107 static bool init_wgpu()
108 {
109     wgpu_device = emscripten_webgpu_get_device();
110     if (!wgpu_device)
111         return false;
112 
113     wgpuDeviceSetUncapturedErrorCallback(wgpu_device, print_wgpu_error, NULL);
114 
115     // Use C++ wrapper due to misbehavior in Emscripten.
116     // Some offset computation for wgpuInstanceCreateSurface in JavaScript
117     // seem to be inline with struct alignments in the C++ structure
118     wgpu::SurfaceDescriptorFromCanvasHTMLSelector html_surface_desc = {};
119     html_surface_desc.selector = "#canvas";
120 
121     wgpu::SurfaceDescriptor surface_desc = {};
122     surface_desc.nextInChain = &html_surface_desc;
123 
124     // Use 'null' instance
125     wgpu::Instance instance = {};
126     wgpu_surface = instance.CreateSurface(&surface_desc).Release();
127 
128     return true;
129 }
130 
main_loop(void * window)131 static void main_loop(void* window)
132 {
133     glfwPollEvents();
134 
135     int width, height;
136     glfwGetFramebufferSize((GLFWwindow*) window, &width, &height);
137 
138     // React to changes in screen size
139     if (width != wgpu_swap_chain_width && height != wgpu_swap_chain_height)
140     {
141         ImGui_ImplWGPU_InvalidateDeviceObjects();
142 
143         if (wgpu_swap_chain)
144             wgpuSwapChainRelease(wgpu_swap_chain);
145 
146         wgpu_swap_chain_width = width;
147         wgpu_swap_chain_height = height;
148 
149         WGPUSwapChainDescriptor swap_chain_desc = {};
150         swap_chain_desc.usage = WGPUTextureUsage_RenderAttachment;
151         swap_chain_desc.format = WGPUTextureFormat_RGBA8Unorm;
152         swap_chain_desc.width = width;
153         swap_chain_desc.height = height;
154         swap_chain_desc.presentMode = WGPUPresentMode_Fifo;
155         wgpu_swap_chain = wgpuDeviceCreateSwapChain(wgpu_device, wgpu_surface, &swap_chain_desc);
156 
157         ImGui_ImplWGPU_CreateDeviceObjects();
158     }
159 
160     // Start the Dear ImGui frame
161     ImGui_ImplWGPU_NewFrame();
162     ImGui_ImplGlfw_NewFrame();
163     ImGui::NewFrame();
164 
165     // 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!).
166     if (show_demo_window)
167         ImGui::ShowDemoWindow(&show_demo_window);
168 
169     // 2. Show a simple window that we create ourselves. We use a Begin/End pair to created a named window.
170     {
171         static float f = 0.0f;
172         static int counter = 0;
173 
174         ImGui::Begin("Hello, world!");                                // Create a window called "Hello, world!" and append into it.
175 
176         ImGui::Text("This is some useful text.");                     // Display some text (you can use a format strings too)
177         ImGui::Checkbox("Demo Window", &show_demo_window);            // Edit bools storing our window open/close state
178         ImGui::Checkbox("Another Window", &show_another_window);
179 
180         ImGui::SliderFloat("float", &f, 0.0f, 1.0f);                  // Edit 1 float using a slider from 0.0f to 1.0f
181         ImGui::ColorEdit3("clear color", (float*)&clear_color);       // Edit 3 floats representing a color
182 
183         if (ImGui::Button("Button"))                                  // Buttons return true when clicked (most widgets return true when edited/activated)
184             counter++;
185         ImGui::SameLine();
186         ImGui::Text("counter = %d", counter);
187 
188         ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / ImGui::GetIO().Framerate, ImGui::GetIO().Framerate);
189         ImGui::End();
190     }
191 
192     // 3. Show another simple window.
193     if (show_another_window)
194     {
195         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)
196         ImGui::Text("Hello from another window!");
197         if (ImGui::Button("Close Me"))
198             show_another_window = false;
199         ImGui::End();
200     }
201 
202     // Rendering
203     ImGui::Render();
204 
205     WGPURenderPassColorAttachment color_attachments = {};
206     color_attachments.loadOp = WGPULoadOp_Clear;
207     color_attachments.storeOp = WGPUStoreOp_Store;
208     color_attachments.clearColor = { clear_color.x * clear_color.w, clear_color.y * clear_color.w, clear_color.z * clear_color.w, clear_color.w };
209     color_attachments.view = wgpuSwapChainGetCurrentTextureView(wgpu_swap_chain);
210     WGPURenderPassDescriptor render_pass_desc = {};
211     render_pass_desc.colorAttachmentCount = 1;
212     render_pass_desc.colorAttachments = &color_attachments;
213     render_pass_desc.depthStencilAttachment = NULL;
214 
215     WGPUCommandEncoderDescriptor enc_desc = {};
216     WGPUCommandEncoder encoder = wgpuDeviceCreateCommandEncoder(wgpu_device, &enc_desc);
217 
218     WGPURenderPassEncoder pass = wgpuCommandEncoderBeginRenderPass(encoder, &render_pass_desc);
219     ImGui_ImplWGPU_RenderDrawData(ImGui::GetDrawData(), pass);
220     wgpuRenderPassEncoderEndPass(pass);
221 
222     WGPUCommandBufferDescriptor cmd_buffer_desc = {};
223     WGPUCommandBuffer cmd_buffer = wgpuCommandEncoderFinish(encoder, &cmd_buffer_desc);
224     WGPUQueue queue = wgpuDeviceGetQueue(wgpu_device);
225     wgpuQueueSubmit(queue, 1, &cmd_buffer);
226 }
227 
print_glfw_error(int error,const char * description)228 static void print_glfw_error(int error, const char* description)
229 {
230     printf("Glfw Error %d: %s\n", error, description);
231 }
232 
print_wgpu_error(WGPUErrorType error_type,const char * message,void *)233 static void print_wgpu_error(WGPUErrorType error_type, const char* message, void*)
234 {
235     const char* error_type_lbl = "";
236     switch (error_type)
237     {
238     case WGPUErrorType_Validation:  error_type_lbl = "Validation"; break;
239     case WGPUErrorType_OutOfMemory: error_type_lbl = "Out of memory"; break;
240     case WGPUErrorType_Unknown:     error_type_lbl = "Unknown"; break;
241     case WGPUErrorType_DeviceLost:  error_type_lbl = "Device lost"; break;
242     default:                        error_type_lbl = "Unknown";
243     }
244     printf("%s error: %s\n", error_type_lbl, message);
245 }
246