|
| 1 | +// Reference |
| 2 | +// Francois: https://github.com/beaufortfrancois |
| 3 | +// GitHub: https://github.com/beaufortfrancois/webgpu-cross-platform-app |
| 4 | +// Reference: https://github.com/beaufortfrancois/webgpu-cross-platform-app/blob/main/main.cpp |
| 5 | + |
| 6 | +#include <iostream> |
| 7 | + |
| 8 | +/** |
| 9 | + * GLFW: cross-platform window and input library. |
| 10 | + * Used here mainly to create a window and get a native handle for WebGPU. |
| 11 | + */ |
| 12 | +#include <GLFW/glfw3.h> |
| 13 | + |
| 14 | +/** |
| 15 | + * Emscripten/WebAssembly support. |
| 16 | + * Only needed when building for browser/WebAssembly targets. |
| 17 | + */ |
| 18 | +#if defined(__EMSCRIPTEN__) |
| 19 | +#include <emscripten/emscripten.h> |
| 20 | +#endif |
| 21 | + |
| 22 | +/** |
| 23 | + * Optional Dawn utility header. |
| 24 | + * Provides functions to print human-readable info for WebGPU objects. |
| 25 | + * Useful for debugging or logging GPU info. |
| 26 | + */ |
| 27 | +#include <dawn/webgpu_cpp_print.h> |
| 28 | + |
| 29 | +/** |
| 30 | + * WebGPU C++ RAII wrapper. |
| 31 | + * Simplifies using the low-level C API in a safe, idiomatic C++ style. |
| 32 | + */ |
| 33 | +#include <webgpu/webgpu_cpp.h> |
| 34 | + |
| 35 | +/** |
| 36 | + * Helper to integrate WebGPU with GLFW windows. |
| 37 | + * Abstracts platform-specific surface creation. |
| 38 | + */ |
| 39 | +#include <webgpu/webgpu_glfw.h> |
| 40 | + |
| 41 | +/** |
| 42 | + * Core WebGPU objects. |
| 43 | + */ |
| 44 | + |
| 45 | +/** |
| 46 | + * Entry point to the WebGPU API. |
| 47 | + * - Manages adapters, surfaces, and asynchronous GPU events. |
| 48 | + * - All GPU operations (device creation, surface setup, etc.) start from the Instance. |
| 49 | + */ |
| 50 | +wgpu::Instance instance; |
| 51 | + |
| 52 | +/** |
| 53 | + * Represents a physical GPU in the system. |
| 54 | + * - Could be a discrete GPU (high-performance) or integrated GPU (low-power). |
| 55 | + * - Provides information about supported features, limits, and formats. |
| 56 | + * - Used as a basis to create a logical GPU device. |
| 57 | + */ |
| 58 | +wgpu::Adapter adapter; |
| 59 | + |
| 60 | +/** |
| 61 | + * Logical GPU object used to issue rendering commands. |
| 62 | + * - Created from the Adapter. |
| 63 | + * - Responsible for creating GPU resources like buffers, textures, and pipelines. |
| 64 | + * - Handles command submission, validation, and error reporting. |
| 65 | + * - Central object for recording and executing GPU work. |
| 66 | + */ |
| 67 | +wgpu::Device device; |
| 68 | + |
| 69 | +/** |
| 70 | + * Describes the GPU rendering pipeline. |
| 71 | + * - Combines vertex and fragment shaders, input layouts, rasterization, and blending state. |
| 72 | + * - Defines how vertex data is processed and how fragments are shaded. |
| 73 | + * - Can be reused for multiple draw calls. |
| 74 | + */ |
| 75 | +wgpu::RenderPipeline pipeline; |
| 76 | + |
| 77 | +/** |
| 78 | + * Surface for rendering, typically linked to a window or canvas. |
| 79 | + * - Represents the target for presenting rendered frames. |
| 80 | + * - Format specifies the color texture layout (e.g., RGBA8Unorm). |
| 81 | + * - Used to acquire textures for the swap chain. |
| 82 | + */ |
| 83 | +wgpu::Surface surface; |
| 84 | +wgpu::TextureFormat format; |
| 85 | + |
| 86 | +// Window dimensions. |
| 87 | +const uint32_t kWidth = 512; |
| 88 | +const uint32_t kHeight = 512; |
| 89 | + |
| 90 | +/** |
| 91 | + * Configure the rendering surface (swap chain) based on GPU capabilities. |
| 92 | + * Uses double/triple buffering to avoid flicker and tearing. |
| 93 | + */ |
| 94 | +void ConfigureSurface() { |
| 95 | + wgpu::SurfaceCapabilities capabilities; |
| 96 | + surface.GetCapabilities(adapter, &capabilities); |
| 97 | + format = capabilities.formats[0]; |
| 98 | + wgpu::SurfaceConfiguration config{ |
| 99 | + .device = device, |
| 100 | + .format = format, |
| 101 | + .width = kWidth, |
| 102 | + .height = kHeight, |
| 103 | + .presentMode = wgpu::PresentMode::Fifo, // V-sync |
| 104 | + }; |
| 105 | + surface.Configure(&config); |
| 106 | +} |
| 107 | + |
| 108 | +/** |
| 109 | + * Initialize core WebGPU objects: Instance, Adapter, and Device. |
| 110 | + * |
| 111 | + * - Instance: the entry point to the WebGPU API. Manages adapters, surfaces, and async events. |
| 112 | + * - Adapter: represents a physical GPU (discrete or integrated) and its capabilities. |
| 113 | + * - Device: logical GPU used to create resources, pipelines, and issue rendering commands. |
| 114 | + */ |
| 115 | +void Init() { |
| 116 | + /** |
| 117 | + * Create a WebGPU Instance. |
| 118 | + * |
| 119 | + * - `timedWaitAnyEnable = true` allows synchronous waiting on multiple asynchronous operations. |
| 120 | + * - Instance is required before requesting an Adapter or creating a Surface. |
| 121 | + */ |
| 122 | + wgpu::InstanceDescriptor instanceDesc{ |
| 123 | + .capabilities = { |
| 124 | + .timedWaitAnyEnable = true, |
| 125 | + }, |
| 126 | + }; |
| 127 | + instance = wgpu::CreateInstance(&instanceDesc); |
| 128 | + |
| 129 | + /** |
| 130 | + * Request a GPU adapter asynchronously but wait synchronously. |
| 131 | + * |
| 132 | + * - Adapter represents the actual physical GPU the device will use. |
| 133 | + * - We check for success and exit if no adapter is found. |
| 134 | + */ |
| 135 | + wgpu::Future f1 = instance.RequestAdapter( |
| 136 | + nullptr, |
| 137 | + wgpu::CallbackMode::WaitAnyOnly, |
| 138 | + [](wgpu::RequestAdapterStatus status, wgpu::Adapter a, wgpu::StringView message) { |
| 139 | + if (status != wgpu::RequestAdapterStatus::Success) { |
| 140 | + std::cout << "RequestAdapter: " << message << "\n"; |
| 141 | + exit(0); |
| 142 | + } |
| 143 | + adapter = std::move(a); |
| 144 | + }); |
| 145 | + instance.WaitAny(f1, UINT64_MAX); |
| 146 | + |
| 147 | + /** |
| 148 | + * Request a logical GPU device from the adapter. |
| 149 | + * |
| 150 | + * - Device is required to create GPU resources and pipelines. |
| 151 | + * - Set an uncaptured error callback to log any runtime GPU errors. |
| 152 | + * - Wait synchronously for device creation to complete. |
| 153 | + */ |
| 154 | + wgpu::DeviceDescriptor desc{}; |
| 155 | + desc.SetUncapturedErrorCallback( |
| 156 | + [](const wgpu::Device&, wgpu::ErrorType errorType, wgpu::StringView message) { |
| 157 | + std::cout << "Error: " << errorType << " - message: " << message << "\n"; |
| 158 | + }); |
| 159 | + wgpu::Future f2 = adapter.RequestDevice( |
| 160 | + &desc, |
| 161 | + wgpu::CallbackMode::WaitAnyOnly, |
| 162 | + [](wgpu::RequestDeviceStatus status, wgpu::Device d, wgpu::StringView message) { |
| 163 | + if (status != wgpu::RequestDeviceStatus::Success) { |
| 164 | + std::cout << "RequestDevice: " << message << "\n"; |
| 165 | + exit(0); |
| 166 | + } |
| 167 | + device = std::move(d); |
| 168 | + }); |
| 169 | + instance.WaitAny(f2, UINT64_MAX); |
| 170 | +} |
| 171 | + |
| 172 | +/** |
| 173 | + * Embedded WGSL shader code as a raw string. |
| 174 | + * |
| 175 | + * - Vertex shader (`vertexMain`): outputs positions of a single triangle. |
| 176 | + * - Fragment shader (`fragmentMain`): outputs a solid magenta color. |
| 177 | + * |
| 178 | + * Notes: |
| 179 | + * - WGSL is the shading language used in WebGPU. |
| 180 | + * - Raw string literal (R"( ... )") avoids escaping quotes or newlines. |
| 181 | + */ |
| 182 | +const char shaderCode[] = R"( |
| 183 | + @vertex fn vertexMain(@builtin(vertex_index) i : u32) -> @builtin(position) vec4f { |
| 184 | + const pos = array(vec2f(0,1), vec2f(-1,-1), vec2f(1,-1)); |
| 185 | + return vec4f(pos[i],0,1); |
| 186 | + } |
| 187 | + @fragment fn fragmentMain() -> @location(0) vec4f { |
| 188 | + return vec4f(1,0,0,1); |
| 189 | + } |
| 190 | +)"; |
| 191 | + |
| 192 | +/** |
| 193 | + * Create a render pipeline: encapsulates all GPU state needed to draw. |
| 194 | + * |
| 195 | + * A render pipeline in WebGPU defines: |
| 196 | + * - Vertex processing: how vertices are transformed and passed to the fragment stage. |
| 197 | + * - Fragment processing: how pixel colors are computed and written to the framebuffer. |
| 198 | + * - Output formats, blending, rasterization, and other GPU state. |
| 199 | + * |
| 200 | + * This function sets up a simple pipeline using the embedded WGSL shader. |
| 201 | + */ |
| 202 | +void CreateRenderPipeline() { |
| 203 | + wgpu::ShaderSourceWGSL wgsl{{ |
| 204 | + .code = shaderCode, |
| 205 | + }}; |
| 206 | + wgpu::ShaderModuleDescriptor shaderModuleDescriptor{ |
| 207 | + .nextInChain = &wgsl, |
| 208 | + }; |
| 209 | + wgpu::ShaderModule shaderModule = device.CreateShaderModule(&shaderModuleDescriptor); |
| 210 | + wgpu::ColorTargetState colorTargetState{ |
| 211 | + .format = format, |
| 212 | + }; |
| 213 | + wgpu::FragmentState fragmentState{ |
| 214 | + .module = shaderModule, |
| 215 | + .targetCount = 1, |
| 216 | + .targets = &colorTargetState, |
| 217 | + }; |
| 218 | + wgpu::RenderPipelineDescriptor descriptor{ |
| 219 | + .vertex = { |
| 220 | + .module = shaderModule, |
| 221 | + }, |
| 222 | + .fragment = &fragmentState, |
| 223 | + }; |
| 224 | + pipeline = device.CreateRenderPipeline(&descriptor); |
| 225 | +} |
| 226 | + |
| 227 | +/** |
| 228 | + * Render a single frame. |
| 229 | + * |
| 230 | + * Steps: |
| 231 | + * 1. Acquire the next available texture from the surface to render into. |
| 232 | + * 2. Set up a render pass describing how the GPU should render to that texture. |
| 233 | + * 3. Record drawing commands (vertex/fragment pipeline operations) into a command encoder. |
| 234 | + * 4. Submit the recorded commands to the GPU queue for execution. |
| 235 | + * |
| 236 | + * This function is typically called every frame in the main loop. |
| 237 | + */ |
| 238 | +void Render() { |
| 239 | + wgpu::SurfaceTexture surfaceTexture; |
| 240 | + surface.GetCurrentTexture(&surfaceTexture); |
| 241 | + |
| 242 | + wgpu::RenderPassColorAttachment attachment{ |
| 243 | + .view = surfaceTexture.texture.CreateView(), |
| 244 | + .loadOp = wgpu::LoadOp::Clear, |
| 245 | + .storeOp = wgpu::StoreOp::Store, |
| 246 | + }; |
| 247 | + wgpu::RenderPassDescriptor renderpass{ |
| 248 | + .colorAttachmentCount = 1, |
| 249 | + .colorAttachments = &attachment, |
| 250 | + }; |
| 251 | + |
| 252 | + wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); |
| 253 | + wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderpass); |
| 254 | + pass.SetPipeline(pipeline); |
| 255 | + pass.Draw(3); // Single triangle |
| 256 | + pass.End(); |
| 257 | + |
| 258 | + wgpu::CommandBuffer commands = encoder.Finish(); |
| 259 | + device.GetQueue().Submit(1, &commands); |
| 260 | +} |
| 261 | + |
| 262 | +/** Setup graphics objects: configure surface and create pipeline */ |
| 263 | +void InitGraphics() { |
| 264 | + ConfigureSurface(); |
| 265 | + CreateRenderPipeline(); |
| 266 | +} |
| 267 | + |
| 268 | +/** |
| 269 | + * Start application: initialize GLFW, create window and surface, enter render loop. |
| 270 | + */ |
| 271 | +void Start() { |
| 272 | + if (!glfwInit()) |
| 273 | + return; |
| 274 | + |
| 275 | + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); // WebGPU only |
| 276 | + GLFWwindow* window = glfwCreateWindow(kWidth, kHeight, "WebGPU window", nullptr, nullptr); |
| 277 | + |
| 278 | + surface = wgpu::glfw::CreateSurfaceForWindow(instance, window); |
| 279 | + InitGraphics(); |
| 280 | + |
| 281 | +#if defined(__EMSCRIPTEN__) |
| 282 | + emscripten_set_main_loop(Render, 0, false); |
| 283 | +#else |
| 284 | + while (!glfwWindowShouldClose(window)) { |
| 285 | + glfwPollEvents(); |
| 286 | + Render(); |
| 287 | + surface.Present(); |
| 288 | + instance.ProcessEvents(); |
| 289 | + } |
| 290 | +#endif |
| 291 | +} |
| 292 | + |
| 293 | +/** Program entry point */ |
| 294 | +int main() { |
| 295 | + Init(); |
| 296 | + Start(); |
| 297 | +} |
0 commit comments