forked from ocornut/imgui
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Split emscripten only sections off Added callbacks for getting device & adapter Added multi viewport code Added swapchain creation function Added temporary canvas resize on creation until ocornut#6751 is merged in (Refreshing will resize the canvas)
- Loading branch information
Showing
2 changed files
with
334 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,329 @@ | ||
// 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/) | ||
|
||
// Learn about Dear ImGui: | ||
// - FAQ https://dearimgui.com/faq | ||
// - Getting Started https://dearimgui.com/getting-started | ||
// - Documentation https://dearimgui.com/docs (same as your local docs/ folder). | ||
// - Introduction, links and more at the top of imgui.cpp | ||
|
||
#include "imgui.h" | ||
#include "imgui_impl_glfw.h" | ||
#include "imgui_impl_wgpu.h" | ||
#include <stdio.h> | ||
#ifdef __EMSCRIPTEN__ | ||
#include <emscripten.h> | ||
#include <emscripten/html5.h> | ||
#include <emscripten/html5_webgpu.h> | ||
#else | ||
#include <webgpu/webgpu_glfw.h> | ||
#endif | ||
#include <GLFW/glfw3.h> | ||
#include <webgpu/webgpu.h> | ||
#include <webgpu/webgpu_cpp.h> | ||
|
||
// Global WebGPU required states | ||
static wgpu::Instance wgpu_instance = nullptr; | ||
static wgpu::Surface wgpu_surface = nullptr; | ||
static wgpu::Device wgpu_device = nullptr; | ||
static WGPUTextureFormat wgpu_preferred_fmt = WGPUTextureFormat_BGRA8Unorm; | ||
static WGPUSwapChain wgpu_swap_chain = nullptr; | ||
static int wgpu_swap_chain_width = 1280; | ||
static int wgpu_swap_chain_height = 720; | ||
|
||
// Forward declarations | ||
static void MainLoopStep(void* window); | ||
static void InitWGPU(void (*callback)(wgpu::Device)); | ||
static bool CreateWGPUSurface(GLFWwindow* window); | ||
static void CreateSwapChain(int width, int height); | ||
static void print_glfw_error(int error, const char* description); | ||
static void print_wgpu_error(WGPUErrorType error_type, const char* message, void*); | ||
static void RequestDeviceCallback(WGPURequestDeviceStatus status, WGPUDevice cDevice, const char* message, void* userdata); | ||
static void RequestAdapterCallback(WGPURequestAdapterStatus status, WGPUAdapter cAdapter,const char* message, void* userdata); | ||
// ------ Remove pending https://github.com/ocornut/imgui/pull/6751 | ||
// glfwGetFramebufferSize not reporting changes in size | ||
#if defined(__EMSCRIPTEN__) | ||
EM_JS(int, canvas_get_width, (), { | ||
return document.getElementById('canvas').width; | ||
}); | ||
|
||
EM_JS(int, canvas_get_height, (), { | ||
return document.getElementById('canvas').height; | ||
}); | ||
#endif | ||
// ------------------------------- | ||
|
||
// Main code | ||
int main(int, char**) | ||
{ | ||
InitWGPU([](wgpu::Device device) { | ||
wgpu_device = device; | ||
|
||
glfwSetErrorCallback(print_glfw_error); | ||
if (!glfwInit()) | ||
exit(1); | ||
|
||
#if defined(__EMSCRIPTEN__) | ||
wgpu_swap_chain_width = canvas_get_width(); | ||
wgpu_swap_chain_height = canvas_get_height(); | ||
#endif | ||
// 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(wgpu_swap_chain_width, wgpu_swap_chain_height, "Dear ImGui GLFW+WebGPU example", nullptr, nullptr); | ||
if (!window) | ||
{ | ||
glfwTerminate(); | ||
exit(1); | ||
} | ||
|
||
// Initialize the WebGPU surface | ||
if (!CreateWGPUSurface(window)) | ||
{ | ||
if (window) | ||
glfwDestroyWindow(window); | ||
glfwTerminate(); | ||
exit(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 | ||
io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable; // Enable Multiviewport for docking branch | ||
|
||
// 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. | ||
#if defined(__EMSCRIPTEN__) | ||
io.IniFilename = nullptr; | ||
#endif | ||
|
||
// Setup Dear ImGui style | ||
ImGui::StyleColorsDark(); | ||
//ImGui::StyleColorsLight(); | ||
|
||
// Setup Platform/Renderer backends | ||
ImGui_ImplGlfw_InitForOther(window, true); | ||
ImGui_ImplWGPU_Init(wgpu_device.Get(), 3, wgpu_preferred_fmt, WGPUTextureFormat_Undefined, wgpu_instance.Get()); | ||
|
||
CreateSwapChain(wgpu_swap_chain_width, wgpu_swap_chain_height); | ||
|
||
// 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 a nullptr. 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. | ||
// - Use '#define IMGUI_ENABLE_FREETYPE' in your imconfig file to use Freetype for higher quality font rendering. | ||
// - 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/segoeui.ttf", 18.0f); | ||
io.Fonts->AddFontFromFileTTF("fonts/DroidSans.ttf", 16.0f); | ||
//io.Fonts->AddFontFromFileTTF("fonts/Roboto-Medium.ttf", 16.0f); | ||
//io.Fonts->AddFontFromFileTTF("fonts/Cousine-Regular.ttf", 15.0f); | ||
//io.Fonts->AddFontFromFileTTF("fonts/ProggyTiny.ttf", 10.0f); | ||
//ImFont* font = io.Fonts->AddFontFromFileTTF("fonts/ArialUni.ttf", 18.0f, nullptr, io.Fonts->GetGlyphRangesJapanese()); | ||
//IM_ASSERT(font != nullptr); | ||
#endif | ||
|
||
// This function will directly return and exit the main function. | ||
// Make sure that no required objects get cleaned up. | ||
// This way we can use the browsers 'requestAnimationFrame' to control the rendering. | ||
#if defined(__EMSCRIPTEN__) | ||
emscripten_set_main_loop_arg(MainLoopStep, window, 0, false); | ||
#else | ||
while (!glfwWindowShouldClose(window)) { | ||
MainLoopStep(window); | ||
wgpuSwapChainPresent(wgpu_swap_chain); | ||
if (ImGui::GetIO().ConfigFlags & ImGuiConfigFlags_ViewportsEnable) | ||
{ | ||
ImGui::UpdatePlatformWindows(); | ||
ImGui::RenderPlatformWindowsDefault(); | ||
} | ||
} | ||
#endif | ||
}); | ||
|
||
return 0; | ||
} | ||
|
||
static void RequestDeviceCallback(WGPURequestDeviceStatus status, WGPUDevice cDevice, const char* message, void* userdata) | ||
{ | ||
wgpu::Device device = wgpu::Device::Acquire(cDevice); | ||
wgpuDeviceSetUncapturedErrorCallback(cDevice, print_wgpu_error, nullptr); | ||
reinterpret_cast<void (*)(wgpu::Device)>(userdata)(device); | ||
} | ||
|
||
static void RequestAdapterCallback(WGPURequestAdapterStatus status, WGPUAdapter cAdapter, const char* message, void* userdata) | ||
{ | ||
if (status != WGPURequestAdapterStatus_Success) { | ||
exit(0); | ||
} | ||
|
||
wgpu::Adapter adapter = wgpu::Adapter::Acquire(cAdapter); | ||
|
||
adapter.RequestDevice(nullptr, RequestDeviceCallback, userdata); | ||
} | ||
|
||
static void InitWGPU(void (*callback)(wgpu::Device)) | ||
{ | ||
wgpu_instance = wgpu::Instance::Acquire(wgpuCreateInstance(nullptr)); | ||
wgpu_instance.RequestAdapter(nullptr, RequestAdapterCallback, reinterpret_cast<void*>(callback)); | ||
} | ||
|
||
static bool CreateWGPUSurface(GLFWwindow* window) | ||
{ | ||
// Use C++ wrapper due to misbehavior in Emscripten. | ||
// Some offset computation for wgpuInstanceCreateSurface in JavaScript | ||
// seem to be inline with struct alignments in the C++ structure | ||
#if defined(__EMSCRIPTEN__) | ||
wgpu::SurfaceDescriptorFromCanvasHTMLSelector html_surface_desc = {}; | ||
html_surface_desc.selector = "#canvas"; | ||
|
||
wgpu::SurfaceDescriptor surfaceDesc = {}; | ||
surfaceDesc.nextInChain = &html_surface_desc; | ||
|
||
wgpu_surface = wgpu_instance.CreateSurface(&surfaceDesc); | ||
#else | ||
wgpu_surface = wgpu::glfw::CreateSurfaceForWindow(wgpu_instance, window); | ||
#endif | ||
|
||
return wgpu_surface != nullptr; | ||
} | ||
|
||
static void CreateSwapChain(int width, int height) | ||
{ | ||
if (wgpu_swap_chain) { | ||
wgpuSwapChainRelease(wgpu_swap_chain); | ||
wgpu_swap_chain = nullptr; | ||
} | ||
|
||
wgpu_swap_chain_width = width; | ||
wgpu_swap_chain_height = height; | ||
|
||
WGPUSwapChainDescriptor swapDescriptor = {}; | ||
swapDescriptor.usage = WGPUTextureUsage_RenderAttachment; | ||
swapDescriptor.format = wgpu_preferred_fmt; | ||
swapDescriptor.width = wgpu_swap_chain_width; | ||
swapDescriptor.height = wgpu_swap_chain_height; | ||
swapDescriptor.presentMode = WGPUPresentMode_Fifo; | ||
|
||
wgpu_swap_chain = wgpuDeviceCreateSwapChain(wgpu_device.Get(), wgpu_surface.Get(), &swapDescriptor); | ||
} | ||
|
||
static void MainLoopStep(void* window) | ||
{ | ||
ImGuiIO& io = ImGui::GetIO(); | ||
|
||
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(); | ||
CreateSwapChain(width, height); | ||
ImGui_ImplWGPU_CreateDeviceObjects(); | ||
} | ||
|
||
// Start the Dear ImGui frame | ||
ImGui_ImplWGPU_NewFrame(); | ||
ImGui_ImplGlfw_NewFrame(); | ||
ImGui::NewFrame(); | ||
|
||
// Our state | ||
// (we use static, which essentially makes the variable globals, as a convenience to keep the example code easy to follow) | ||
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); | ||
|
||
// 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 create 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 / io.Framerate, io.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(); | ||
} | ||
|
||
// Rendering | ||
ImGui::Render(); | ||
|
||
WGPURenderPassColorAttachment color_attachments = {}; | ||
color_attachments.loadOp = WGPULoadOp_Clear; | ||
color_attachments.storeOp = WGPUStoreOp_Store; | ||
color_attachments.clearValue = { clear_color.x * clear_color.w, clear_color.y * clear_color.w, clear_color.z * clear_color.w, clear_color.w }; | ||
color_attachments.view = 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.Get(), &enc_desc); | ||
|
||
WGPURenderPassEncoder pass = wgpuCommandEncoderBeginRenderPass(encoder, &render_pass_desc); | ||
ImGui_ImplWGPU_RenderDrawData(ImGui::GetDrawData(), pass); | ||
wgpuRenderPassEncoderEnd(pass); | ||
|
||
WGPUCommandBufferDescriptor cmd_buffer_desc = {}; | ||
WGPUCommandBuffer cmd_buffer = wgpuCommandEncoderFinish(encoder, &cmd_buffer_desc); | ||
WGPUQueue queue = wgpuDeviceGetQueue(wgpu_device.Get()); | ||
wgpuQueueSubmit(queue, 1, &cmd_buffer); | ||
} | ||
|
||
static void print_glfw_error(int error, const char* description) | ||
{ | ||
printf("GLFW Error %d: %s\n", error, description); | ||
} | ||
|
||
static 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); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters