From dd1e89b441b6ec9863c6c85306302d47e38aba22 Mon Sep 17 00:00:00 2001 From: Marino von Wattenwyl Date: Mon, 30 Oct 2023 16:51:39 +0100 Subject: [PATCH 01/31] [WebGPU] Set up CMake to download wgpu --- apps/CMakeLists.txt | 6 ++++- apps/webgpu/CMakeLists.txt | 50 ++++++++++++++++++++++++++++++++++++++ apps/webgpu/main.cpp | 18 ++++++++++++++ 3 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 apps/webgpu/CMakeLists.txt create mode 100644 apps/webgpu/main.cpp diff --git a/apps/CMakeLists.txt b/apps/CMakeLists.txt index 73765b36..d4889f7a 100644 --- a/apps/CMakeLists.txt +++ b/apps/CMakeLists.txt @@ -9,4 +9,8 @@ else() add_subdirectory(app_demo_imgui) add_subdirectory(app_demo_node) add_subdirectory(app_demo_slproject) -endif() + + if (SL_BUILD_WEBGPU_DEMO) + add_subdirectory(webgpu) + endif () +endif() \ No newline at end of file diff --git a/apps/webgpu/CMakeLists.txt b/apps/webgpu/CMakeLists.txt new file mode 100644 index 00000000..9b74fd34 --- /dev/null +++ b/apps/webgpu/CMakeLists.txt @@ -0,0 +1,50 @@ +if (SYSTEM_NAME_UPPER MATCHES "WINDOWS") + set(WGPU_OS "windows") +elseif (SYSTEM_NAME_UPPER MATCHES "DARWIN") + set(WGPU_OS "macos") +elseif (SYSTEM_NAME_UPPER MATCHES "LINUX") + set(WGPU_OS "linux") +else () + message(FATAL_ERROR "[WebGPU] System '${SYSTEM_NAME_UPPER}' is not supported") +endif () + +if (CMAKE_SYSTEM_PROCESSOR MATCHES "AMD64" OR CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64") + set(WGPU_ARCH "x86_64") +elseif (CMAKE_SYSTEM_PROCESSOR MATCHES "arm64") + set(WGPU_ARCH "arm64") +else () + message(FATAL_ERROR "[WebGPU] Architecture '${CMAKE_SYSTEM_PROCESSOR}' is not supported") +endif () + +set(WGPU_VERSION "v0.18.0.1") +set(WGPU_DIR "${SL_PROJECT_ROOT}/externals/prebuilt/wgpu_${WGPU_VERSION}") +set(WGPU_ZIP "wgpu-${WGPU_OS}-${WGPU_ARCH}-release.zip") + +if (NOT EXISTS "${WGPU_DIR}") + set(BASE_URL "https://github.com/gfx-rs/wgpu-native/releases/download") + set(DOWNLOAD_URL "${BASE_URL}/${WGPU_VERSION}/${WGPU_ZIP}") + + message(STATUS "[WebGPU] Downloading ${WGPU_ZIP}...") + file(DOWNLOAD "${DOWNLOAD_URL}" "${WGPU_DIR}/${WGPU_ZIP}") + message(STATUS "[WebGPU] Download complete") + + execute_process(COMMAND ${CMAKE_COMMAND} -E tar xzf "${WGPU_ZIP}" WORKING_DIRECTORY "${WGPU_DIR}") + message(STATUS "[WebGPU] Files extracted") + + file(REMOVE "${WGPU_DIR}/commit-sha" "${WGPU_DIR}/${WGPU_ZIP}") + message(STATUS "[WebGPU] Directory cleaned up") +else () + message(STATUS "[WebGPU] Directory existing") +endif () + +add_library(wgpu STATIC IMPORTED) +set_target_properties(wgpu PROPERTIES + IMPORTED_LOCATION "${WGPU_DIR}/wgpu_native.lib" + INTERFACE_INCLUDE_DIRECTORIES "${WGPU_DIR}") + +if (SYSTEM_NAME_UPPER MATCHES "WINDOWS") + target_link_libraries(wgpu INTERFACE "ws2_32" "userenv" "advapi32" "bcrypt" "d3dcompiler" "ntdll" "opengl32") +endif () + +add_executable(webgpu-demo main.cpp) +target_link_libraries(webgpu-demo wgpu) diff --git a/apps/webgpu/main.cpp b/apps/webgpu/main.cpp new file mode 100644 index 00000000..c014c1dc --- /dev/null +++ b/apps/webgpu/main.cpp @@ -0,0 +1,18 @@ +#include + +#include + +int main(int argc, const char* argv[]) { + WGPUInstance instance = wgpuCreateInstance(nullptr); + if (instance) { + std::cout << "[WebGPU] Instance created" << std::endl; + } else { + std::cerr << "[WebGPU] Failed to create instance" << std::endl; + return 1; + } + + wgpuInstanceRelease(instance); + std::cerr << "[WebGPU] Resources released" << std::endl; + + return 0; +} \ No newline at end of file From 50b2f28340db2020c6cb1013132a817bb0d6f6f3 Mon Sep 17 00:00:00 2001 From: Marino von Wattenwyl Date: Thu, 2 Nov 2023 17:16:02 +0100 Subject: [PATCH 02/31] [WebGPU] Add triangle demo for Windows --- CMakeLists.txt | 2 + apps/webgpu/CMakeLists.txt | 2 +- apps/webgpu/main.cpp | 318 ++++++++++++++++++++++++++++++++++++- 3 files changed, 314 insertions(+), 8 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2018324f..f03e78da 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -49,6 +49,7 @@ option(SL_BUILD_WAI "Specifies if the WAI library should be built" ON) option(SL_BUILD_APPS "Specifies if sample apps should be built" ON) option(SL_BUILD_EXERCISES "Specifies if exercise apps should be built" ON) option(SL_BUILD_VULKAN_APPS "Specifies if vulkan apps should be built" OFF) +option(SL_BUILD_WEBGPU_DEMO "Specifies if WebGPU demo should be built" ON) option(SL_BUILD_WITH_OPTIX "Specifies if Optix renderer should be built" OFF) option(SL_BUILD_WITH_KTX "Specifies if Kronos Texture library (ktx) should be used" ON) option(SL_BUILD_WITH_OPENSSL "Specifies if OpenSSL should be used" ON) @@ -71,6 +72,7 @@ message(STATUS "SL_BUILD_WAI: ${SL_BUILD_WAI}") message(STATUS "SL_BUILD_APPS: ${SL_BUILD_APPS}") message(STATUS "SL_BUILD_EXERCISES: ${SL_BUILD_EXERCISES}") message(STATUS "SL_BUILD_VULKAN_APPS: ${SL_BUILD_VULKAN_APPS}") +message(STATUS "SL_BUILD_WEBGPU_DEMO: ${SL_BUILD_WEBGPU_DEMO}") message(STATUS "SL_BUILD_WITH_OPTIX: ${SL_BUILD_WITH_OPTIX}") message(STATUS "SL_BUILD_WITH_KTX: ${SL_BUILD_WITH_KTX}") message(STATUS "SL_BUILD_WITH_OPENSSL: ${SL_BUILD_WITH_OPENSSL}") diff --git a/apps/webgpu/CMakeLists.txt b/apps/webgpu/CMakeLists.txt index 9b74fd34..97bfde63 100644 --- a/apps/webgpu/CMakeLists.txt +++ b/apps/webgpu/CMakeLists.txt @@ -47,4 +47,4 @@ if (SYSTEM_NAME_UPPER MATCHES "WINDOWS") endif () add_executable(webgpu-demo main.cpp) -target_link_libraries(webgpu-demo wgpu) +target_link_libraries(webgpu-demo PRIVATE wgpu glfw3dll) diff --git a/apps/webgpu/main.cpp b/apps/webgpu/main.cpp index c014c1dc..b23d48cb 100644 --- a/apps/webgpu/main.cpp +++ b/apps/webgpu/main.cpp @@ -1,18 +1,322 @@ +#if defined(_WIN32) +# define SYSTEM_WINDOWS +# define GLFW_EXPOSE_NATIVE_WIN32 +# define WIN32_LEAN_AND_MEAN +# include +#elif defined(__linux__) +# define SYSTEM_LINUX +# define GLFW_EXPOSE_NATIVE_X11 +#elif defined(__APPLE__) +# define SYSTEM_DARWIN +# define GLFW_EXPOSE_NATIVE_COCOA +#endif + #include +#include +#include #include +#include + +#define WEBGPU_DEMO_LOG(msg) std::cout << (msg) << std::endl + +#define WEBGPU_DEMO_CHECK(condition, errorMsg) \ + if (!(condition)) \ + { \ + std::cerr << (errorMsg) << std::endl; \ + std::exit(1); \ + } + +void handleAdapterRequest(WGPURequestAdapterStatus status, + WGPUAdapter adapter, + char const* message, + void* userdata) +{ + WEBGPU_DEMO_CHECK(status == WGPURequestAdapterStatus_Success, + "[WebGPU] Failed to acquire adapter: " + std::string(message)); + WEBGPU_DEMO_LOG("[WebGPU] Adapter acquired"); + + WGPUAdapter* outAdapter = (WGPUAdapter*)userdata; + *outAdapter = adapter; +} + +void handleDeviceRequest(WGPURequestDeviceStatus status, + WGPUDevice device, + char const* message, + void* userdata) +{ + WEBGPU_DEMO_CHECK(status == WGPURequestDeviceStatus_Success, + "[WebGPU] Failed to acquire device: " + std::string(message)); + WEBGPU_DEMO_LOG("[WebGPU] Device acquired"); + + WGPUDevice* outDevice = (WGPUDevice*)userdata; + *outDevice = device; +} + +int main(int argc, const char* argv[]) +{ + // === Initialize GLFW === + + WEBGPU_DEMO_CHECK(glfwInit(), "[GLFW] Failed to initialize"); + WEBGPU_DEMO_LOG("[GLFW] Initialized"); + + // === Create the GLFW window === + + // Prevent GLFW from creating an OpenGL context as the underlying graphics API probably won't be OpenGL. + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + + // Create a non-resizable window as we currently don't recreate surfaces when the window size changes. + glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); + + GLFWwindow* window = glfwCreateWindow(1280, 720, "WebGPU Demo", nullptr, nullptr); + WEBGPU_DEMO_CHECK(window, "[GLFW] Window created"); + + // === Create a WebGPU instance === + // The instance is the root interface to WebGPU through which we create all other WebGPU resources. -int main(int argc, const char* argv[]) { WGPUInstance instance = wgpuCreateInstance(nullptr); - if (instance) { - std::cout << "[WebGPU] Instance created" << std::endl; - } else { - std::cerr << "[WebGPU] Failed to create instance" << std::endl; - return 1; + WEBGPU_DEMO_CHECK(instance, "[WebGPU] Failed to create instance"); + WEBGPU_DEMO_LOG("[WebGPU] Instance created"); + + // === Acquire a WebGPU adapter === + // An adapter provides information about the capabilities of the GPU. + + WGPURequestAdapterOptions adapterOptions = {}; + + WGPUAdapter adapter; + wgpuInstanceRequestAdapter(instance, &adapterOptions, handleAdapterRequest, &adapter); + + // === Acquire a WebGPU device === + // A device provides access to a GPU and is created from an adapter. + + WGPUDeviceDescriptor deviceDesc = {}; + deviceDesc.label = "Demo Device"; + deviceDesc.defaultQueue.label = "Demo Queue"; + + WGPUDevice device; + wgpuAdapterRequestDevice(adapter, &deviceDesc, handleDeviceRequest, &device); + + // === Acquire a WebGPU queue === + // The queue is where the commands for the GPU are submitted to. + + WGPUQueue queue = wgpuDeviceGetQueue(device); + WEBGPU_DEMO_CHECK(queue, "[WebGPU] Failed to acquire queue"); + WEBGPU_DEMO_LOG("[WebGPU] Queue acquired"); + + // === Create a WebGPU surface === + // The surface is where our rendered images will be presented to. + // It is created from window handles on most platforms and from a canvas in the browser. + +#ifdef SYSTEM_WINDOWS + WGPUSurfaceDescriptorFromWindowsHWND nativeSurfaceDesc = {}; + nativeSurfaceDesc.chain.sType = WGPUSType_SurfaceDescriptorFromWindowsHWND; + nativeSurfaceDesc.hinstance = GetModuleHandle(nullptr); + nativeSurfaceDesc.hwnd = glfwGetWin32Window(window); +#endif + + WGPUSurfaceDescriptor surfaceDesc = {}; + surfaceDesc.label = "Demo Surface"; + surfaceDesc.nextInChain = (const WGPUChainedStruct*)&nativeSurfaceDesc; + + WGPUSurface surface = wgpuInstanceCreateSurface(instance, &surfaceDesc); + WEBGPU_DEMO_CHECK(surface, "[WebGPU] Failed to create surface"); + WEBGPU_DEMO_LOG("[WebGPU] Surface created"); + + // === Configure the surface === + // The surface needs to be configured before images can be presented. + + // Query the surface capabilities from the adapter. + WGPUSurfaceCapabilities surfaceCapabilities; + wgpuSurfaceGetCapabilities(surface, adapter, &surfaceCapabilities); + + // Get the window size from the GLFW window. + int surfaceWidth; + int surfaceHeight; + glfwGetWindowSize(window, &surfaceWidth, &surfaceHeight); + + WGPUSurfaceConfiguration surfaceConfig = {}; + surfaceConfig.device = device; + surfaceConfig.usage = WGPUTextureUsage_RenderAttachment; + surfaceConfig.format = surfaceCapabilities.formats[0]; + surfaceConfig.presentMode = WGPUPresentMode_Fifo; + surfaceConfig.alphaMode = surfaceCapabilities.alphaModes[0]; + surfaceConfig.width = surfaceWidth; + surfaceConfig.height = surfaceHeight; + wgpuSurfaceConfigure(surface, &surfaceConfig); + WEBGPU_DEMO_LOG("[WebGPU] Surface configured"); + + // === Create the shader module === + // Create a shader module for use in our render pipeline. + // Shaders are written in a WebGPU-specific language called WGSL (WebGPU shader language). + // Shader modules can be used in multiple pipeline stages by specifying different entry points. + + const char* shaderSource = R"( + @vertex + fn vs_main(@builtin(vertex_index) in_vertex_index: u32) -> @builtin(position) vec4f { + if (in_vertex_index == 0u) { + return vec4f(-0.5, -0.5, 0.0, 1.0); + } else if (in_vertex_index == 1u) { + return vec4f(0.5, -0.5, 0.0, 1.0); + } else { + return vec4f(0.0, 0.5, 0.0, 1.0); + } + } + + @fragment + fn fs_main() -> @location(0) vec4f { + return vec4f(0.2, 0.2, 1.0, 1.0); + } + )"; + + WGPUShaderModuleWGSLDescriptor shaderModuleWGSLDesc = {}; + shaderModuleWGSLDesc.chain.sType = WGPUSType_ShaderModuleWGSLDescriptor; + shaderModuleWGSLDesc.code = shaderSource; + + WGPUShaderModuleDescriptor shaderModuleDesc = {}; + shaderModuleDesc.label = "Hello Triangle Shader"; + shaderModuleDesc.nextInChain = (const WGPUChainedStruct*)&shaderModuleWGSLDesc; + + WGPUShaderModule shaderModule = wgpuDeviceCreateShaderModule(device, &shaderModuleDesc); + WEBGPU_DEMO_CHECK(shaderModule, "[WebGPU] Failed to create shader module"); + WEBGPU_DEMO_LOG("[WebGPU] Shader module created"); + + // === Create the WebGPU render pipeline === + // The render pipeline specifies the configuration for the fixed-function stages as well as + // the shaders for the programmable stages of the hardware pipeline. + + // Configuration for the vertex shader stage + WGPUVertexState vertexState = {}; + vertexState.module = shaderModule; + vertexState.entryPoint = "vs_main"; + + // Configuration for the primitive assembly and rasterization stages + WGPUPrimitiveState primitiveState = {}; + primitiveState.topology = WGPUPrimitiveTopology_TriangleList; + primitiveState.stripIndexFormat = WGPUIndexFormat_Undefined; + primitiveState.frontFace = WGPUFrontFace_CCW; + primitiveState.cullMode = WGPUCullMode_None; + + // Configuration for multisampling + WGPUMultisampleState multisampleState = {}; + multisampleState.count = 1; + multisampleState.mask = WGPUColorWriteMask_All; + + // Configuration for the fragment shader stage + WGPUColorTargetState colorTargetState = {}; + colorTargetState.format = surfaceCapabilities.formats[0]; + colorTargetState.writeMask = WGPUColorWriteMask_All; + WGPUFragmentState fragmentState = {}; + fragmentState.module = shaderModule; + fragmentState.entryPoint = "fs_main"; + fragmentState.targetCount = 1; + fragmentState.targets = &colorTargetState; + + // Configuration for the entire pipeline + WGPURenderPipelineDescriptor pipelineDesc = {}; + pipelineDesc.label = "Hello Triangle Pipeline"; + pipelineDesc.vertex = vertexState; + pipelineDesc.primitive = primitiveState; + pipelineDesc.multisample = multisampleState; + pipelineDesc.fragment = &fragmentState; + + WGPURenderPipeline pipeline = wgpuDeviceCreateRenderPipeline(device, &pipelineDesc); + WEBGPU_DEMO_CHECK(pipeline, "[WebGPU] Failed to create render pipeline"); + WEBGPU_DEMO_LOG("[WebGPU] Render pipeline created"); + + // === Render loop === + + while (!glfwWindowShouldClose(window)) + { + // === Create a WebGPU texture view === + // The texture view is where we render our image into. + + // Get a texture from the surface to render into. + WGPUSurfaceTexture surfaceTexture; + wgpuSurfaceGetCurrentTexture(surface, &surfaceTexture); + WEBGPU_DEMO_CHECK(surfaceTexture.status == WGPUSurfaceGetCurrentTextureStatus_Success, "[WebGPU] Failed to acquire current surface texture"); + + // Create a view into the texture to specify where and how to modify the texture. + WGPUTextureView view = wgpuTextureCreateView(surfaceTexture.texture, nullptr); + + // === Create a WebGPU command encoder === + // The encoder encodes the commands for the GPU into a command buffer. + + WGPUCommandEncoderDescriptor cmdEncoderDesc = {}; + cmdEncoderDesc.label = " Hello Triangle Command Encoder"; + + WGPUCommandEncoder cmdEncoder = wgpuDeviceCreateCommandEncoder(device, &cmdEncoderDesc); + WEBGPU_DEMO_CHECK(cmdEncoder, "[WebGPU] Failed to create command encoder"); + + // === Create a WebGPU render pass === + // The render pass specifies what attachments to use while rendering. + // A color attachment specifies what view to render into and what to do with the texture before and after + // rendering. We clear the texture before rendering and store the results after rendering. + + WGPURenderPassColorAttachment colorAttachment = {}; + colorAttachment.view = view; + colorAttachment.loadOp = WGPULoadOp_Clear; + colorAttachment.storeOp = WGPUStoreOp_Store; + colorAttachment.clearValue.r = 0.3; + colorAttachment.clearValue.g = 0.0; + colorAttachment.clearValue.b = 0.2; + colorAttachment.clearValue.a = 1.0; + + WGPURenderPassDescriptor renderPassDesc = {}; + renderPassDesc.label = "Hello Triangle Render Pass"; + renderPassDesc.colorAttachmentCount = 1; + renderPassDesc.colorAttachments = &colorAttachment; + + // === Encode the commands === + // The commands to begin a render pass, bind a pipeline, draw the triangle and end the render pass + // are encoded into a buffer. + + WGPURenderPassEncoder renderPassEncoder = wgpuCommandEncoderBeginRenderPass(cmdEncoder, &renderPassDesc); + wgpuRenderPassEncoderSetPipeline(renderPassEncoder, pipeline); + wgpuRenderPassEncoderDraw(renderPassEncoder, 3, 1, 0, 0); + wgpuRenderPassEncoderEnd(renderPassEncoder); + + // === Get the command buffer === + // The command encoder is finished to get the commands for the GPU to execute in a command buffer. + + WGPUCommandBufferDescriptor cmdBufferDesc = {}; + cmdBufferDesc.label = "Hello Triangle Command Buffer"; + + WGPUCommandBuffer cmdBuffer = wgpuCommandEncoderFinish(cmdEncoder, &cmdBufferDesc); + + // === Submit the command buffer to the GPU === + // The work for the GPU is submitted through the queue and executed. + wgpuQueueSubmit(queue, 1, &cmdBuffer); + + // === Present the surface === + // This presents our rendered texture to the screen. + wgpuSurfacePresent(surface); + + // === Clean up resources === + wgpuCommandBufferRelease(cmdBuffer); + wgpuRenderPassEncoderRelease(renderPassEncoder); + wgpuCommandEncoderRelease(cmdEncoder); + wgpuTextureViewRelease(view); + wgpuTextureRelease(surfaceTexture.texture); + + glfwPollEvents(); } + // === Release all WebGPU resources === + + wgpuRenderPipelineRelease(pipeline); + wgpuShaderModuleRelease(shaderModule); + wgpuSurfaceRelease(surface); + wgpuQueueRelease(queue); + wgpuDeviceRelease(device); + wgpuAdapterRelease(adapter); wgpuInstanceRelease(instance); - std::cerr << "[WebGPU] Resources released" << std::endl; + WEBGPU_DEMO_LOG("[WebGPU] Resources released"); + + // === Destroy the window and terminate GLFW === + + glfwDestroyWindow(window); + glfwTerminate(); + WEBGPU_DEMO_LOG("[GLFW] Window closed and terminated"); return 0; } \ No newline at end of file From 0f20c06451244c5d5c5f1bcd4084370cb5e57204 Mon Sep 17 00:00:00 2001 From: Marino von Wattenwyl Date: Fri, 3 Nov 2023 10:40:30 +0100 Subject: [PATCH 03/31] [Externals] Add Python build script for GLFW --- externals/prebuild_scripts/build_glfw.py | 112 +++++++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 externals/prebuild_scripts/build_glfw.py diff --git a/externals/prebuild_scripts/build_glfw.py b/externals/prebuild_scripts/build_glfw.py new file mode 100644 index 00000000..4ec469f4 --- /dev/null +++ b/externals/prebuild_scripts/build_glfw.py @@ -0,0 +1,112 @@ +#!/usr/bin/python3 + +import platform +import subprocess +import os +import multiprocessing +import shutil +from pathlib import Path + + +BUILD_DIR = Path("build") +INSTALL_DIR = Path("install") + +SYSTEM_WIN64 = "win64" +SYSTEM_LINUX = "linux" +SYSTEM_MAC64 = "mac64" +SYSTEM_MACARM64 = "macArm64" + + +is_windows = platform.system() == "Windows" +is_linux = platform.system() == "Linux" +is_darwin = platform.system() == "Darwin" + + +def build(build_type): + build_dir = BUILD_DIR / build_type + install_dir = INSTALL_DIR / build_type + + # Convert the first letter to uppercase for CMake. + cmake_build_type = build_type[0].upper() + build_type[1:] + + config_command = [ + "cmake", + "-B" + str(build_dir), + "-DCMAKE_INSTALL_PREFIX=" + str(install_dir), + "-DGLFW_BUILD_EXAMPLES=OFF", + "-DGLFW_BUILD_TESTS=OFF", + "-DGLFW_BUILD_DOCS=OFF" + ] + + if not is_windows: + config_command.append("-DCMAKE_BUILD_TYPE=" + cmake_build_type) + + build_command = [ + "cmake", + "--build", build_dir, + "--target", "install", + "-j" + str(multiprocessing.cpu_count()) + ] + + if is_windows: + build_command.extend(["--config", cmake_build_type]) + + subprocess.run(config_command) + subprocess.run(build_command) + + destination = output_dir / build_type + destination.mkdir() + + if is_windows: + shutil.copy(install_dir / "lib" / "glfw3.lib", destination) + else: + shutil.copy(install_dir / "lib" / "libglfw3.a", destination) + + +print(f"\n=== Building GLFW ===\n") +version = input("Version: ") + +if is_windows: + system = SYSTEM_WIN64 + print("System: " + system) +elif is_linux: + system = SYSTEM_LINUX + print("System: " + system) +elif is_darwin: + system = input(f"System ({SYSTEM_MAC64}/{SYSTEM_MACARM64}): ") + assert system in (SYSTEM_MAC64, SYSTEM_MACARM64) + +name = system + "_glfw_" + version + +prebuilt_dir = Path(os.curdir).absolute().parent / "prebuilt" +output_dir = Path(prebuilt_dir, name) +print("Output directory: " + str(output_dir)) + +zip_filename = name + ".zip" +print("Zip file: " + zip_filename) + +if not os.path.isdir("glfw"): + print("\n=== Cloning GLFW ===\n") + subprocess.run(["git", "clone", "https://github.com/glfw/glfw.git"]) + +os.chdir("glfw") + +print(f"\n=== Checking out version {version} ===\n") +subprocess.run(["git", "checkout", version]) +subprocess.run(["git", "pull", "origin", version]) + +print(f"\n=== Preparing output directory ===\n") + +shutil.rmtree(output_dir, ignore_errors=True) +output_dir.mkdir() + +print("Directory created") + +print(f"\n=== Building debug version ===\n") +build("debug") +print(f"\n=== Building release version ===\n") +build("release") + +shutil.copytree(INSTALL_DIR / "debug" / "include", output_dir / "include") +shutil.copy("LICENSE.md", output_dir) +shutil.copy("README.md", output_dir) \ No newline at end of file From 3b5ec616362aff4e71444d9c20ce3e260284aa0d Mon Sep 17 00:00:00 2001 From: Marino von Wattenwyl Date: Fri, 3 Nov 2023 10:53:37 +0100 Subject: [PATCH 04/31] [Externals] Add Windows support for build_glfw.py --- externals/prebuild_scripts/build_glfw.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/externals/prebuild_scripts/build_glfw.py b/externals/prebuild_scripts/build_glfw.py index 4ec469f4..e3036d0e 100644 --- a/externals/prebuild_scripts/build_glfw.py +++ b/externals/prebuild_scripts/build_glfw.py @@ -82,9 +82,6 @@ def build(build_type): output_dir = Path(prebuilt_dir, name) print("Output directory: " + str(output_dir)) -zip_filename = name + ".zip" -print("Zip file: " + zip_filename) - if not os.path.isdir("glfw"): print("\n=== Cloning GLFW ===\n") subprocess.run(["git", "clone", "https://github.com/glfw/glfw.git"]) @@ -109,4 +106,13 @@ def build(build_type): shutil.copytree(INSTALL_DIR / "debug" / "include", output_dir / "include") shutil.copy("LICENSE.md", output_dir) -shutil.copy("README.md", output_dir) \ No newline at end of file +shutil.copy("README.md", output_dir) + +print("\n=== Creating archive === \n") + +os.chdir(os.pardir) +shutil.make_archive(name, "zip", output_dir.parent, output_dir.name) +print("Archive created") +print("Path: " + str(Path(name + ".zip").absolute())) + +print("\n") \ No newline at end of file From 03c0999ba27b285ed6da91f17fc843e73b5fa43e Mon Sep 17 00:00:00 2001 From: Marino von Wattenwyl Date: Fri, 3 Nov 2023 10:59:37 +0100 Subject: [PATCH 05/31] [CMake] Upgrade GLFW to 3.3.8 on Windows and macOS --- cmake/DownloadPrebuilts.cmake | 47 ++++++++++++++++------------------- 1 file changed, 21 insertions(+), 26 deletions(-) diff --git a/cmake/DownloadPrebuilts.cmake b/cmake/DownloadPrebuilts.cmake index 8858fcb8..9e8d3dfc 100644 --- a/cmake/DownloadPrebuilts.cmake +++ b/cmake/DownloadPrebuilts.cmake @@ -446,22 +446,19 @@ elseif ("${SYSTEM_NAME_UPPER}" STREQUAL "WINDOWS") #---------------------------- # GLFW for Windows # #################### - set(glfw_VERSION "3.3.2") + set(glfw_VERSION "3.3.8") set(glfw_PREBUILT_DIR "win64_glfw_${glfw_VERSION}") set(glfw_DIR "${PREBUILT_PATH}/${glfw_PREBUILT_DIR}") - set(glfw_INCLUDE_DIR "${glfw_DIR}/include") - set(glfw_LINK_DIR "${glfw_DIR}/lib-vc2019") - add_library(glfw3dll SHARED IMPORTED) - set_target_properties(glfw3dll PROPERTIES - IMPORTED_IMPLIB "${glfw_LINK_DIR}/glfw3dll.lib" - IMPORTED_LOCATION "${glfw_LINK_DIR}/glfw3.dll" - INTERFACE_INCLUDE_DIRECTORIES "${glfw_INCLUDE_DIR}" + add_library(glfw3 STATIC IMPORTED) + set_target_properties(glfw3 PROPERTIES + IMPORTED_LOCATION "${glfw_DIR}/release/glfw3.lib" + IMPORTED_LOCATION_DEBUG "${glfw_DIR}/debug/glfw3.lib" + INTERFACE_INCLUDE_DIRECTORIES "${glfw_DIR}/include" ) - set(glfw_LIBS glfw3dll) + set(glfw_LIBS glfw3) download_lib("${glfw_PREBUILT_DIR}") - copy_dlls("${glfw_LIBS}") ################### # KTX for Windows # @@ -643,19 +640,18 @@ elseif ("${SYSTEM_NAME_UPPER}" STREQUAL "DARWIN" AND # GLFW for MacOS-x86_64 # ######################### - set(glfw_VERSION "3.3.2") + set(glfw_VERSION "3.3.8") set(glfw_PREBUILT_DIR "mac64_glfw_${glfw_VERSION}") set(glfw_DIR "${PREBUILT_PATH}/${glfw_PREBUILT_DIR}") - set(glfw_INCLUDE_DIR "${glfw_DIR}/include") - add_library(glfw SHARED IMPORTED) - set_target_properties(glfw PROPERTIES - IMPORTED_LOCATION "${glfw_DIR}/Release/libglfw.3.dylib" - INTERFACE_INCLUDE_DIRECTORIES "${glfw_INCLUDE_DIR}") - set(glfw_LIBS glfw) + add_library(glfw3 STATIC IMPORTED) + set_target_properties(glfw3 PROPERTIES + IMPORTED_LOCATION "${glfw_DIR}/release/libglfw3.a" + IMPORTED_LOCATION_DEBUG "${glfw_DIR}/debug/libglfw3.a" + INTERFACE_INCLUDE_DIRECTORIES "${glfw_DIR}/include") + set(glfw_LIBS glfw3) download_lib("${glfw_PREBUILT_DIR}") - copy_dylibs("${glfw_LIBS}") ######################## # KTX for MacOS-x86_64 # @@ -824,19 +820,18 @@ elseif ("${SYSTEM_NAME_UPPER}" STREQUAL "DARWIN" AND # GLFW for MacOS-arm64 # ######################## - set(glfw_VERSION "3.3.2") + set(glfw_VERSION "3.3.8") set(glfw_PREBUILT_DIR "macArm64_glfw_${glfw_VERSION}") set(glfw_DIR "${PREBUILT_PATH}/${glfw_PREBUILT_DIR}") - set(glfw_INCLUDE_DIR "${glfw_DIR}/include") - add_library(glfw SHARED IMPORTED) - set_target_properties(glfw PROPERTIES - IMPORTED_LOCATION "${glfw_DIR}/Release/libglfw.3.3.dylib" - INTERFACE_INCLUDE_DIRECTORIES "${glfw_INCLUDE_DIR}") - set(glfw_LIBS glfw) + add_library(glfw3 STATIC IMPORTED) + set_target_properties(glfw3 PROPERTIES + IMPORTED_LOCATION "${glfw_DIR}/release/libglfw3.a" + IMPORTED_LOCATION_DEBUG "${glfw_DIR}/debug/libglfw3.a" + INTERFACE_INCLUDE_DIRECTORIES "${glfw_DIR}/include") + set(glfw_LIBS glfw3) download_lib("${glfw_PREBUILT_DIR}") - copy_dylibs("${glfw_LIBS}") ####################### # KTX for MacOS-arm64 # From ba333c3da4a0bda9f772f6f3b730ca8a4d7265bd Mon Sep 17 00:00:00 2001 From: Marino von Wattenwyl Date: Fri, 3 Nov 2023 11:00:53 +0100 Subject: [PATCH 06/31] [WebGPU] Fix GLFW linking --- apps/webgpu/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/webgpu/CMakeLists.txt b/apps/webgpu/CMakeLists.txt index 97bfde63..40b48567 100644 --- a/apps/webgpu/CMakeLists.txt +++ b/apps/webgpu/CMakeLists.txt @@ -47,4 +47,4 @@ if (SYSTEM_NAME_UPPER MATCHES "WINDOWS") endif () add_executable(webgpu-demo main.cpp) -target_link_libraries(webgpu-demo PRIVATE wgpu glfw3dll) +target_link_libraries(webgpu-demo PRIVATE wgpu ${glfw_LIBS}) From 3e91462dad04e440f38159b7e8e97c83bdd67196 Mon Sep 17 00:00:00 2001 From: Marino von Wattenwyl Date: Fri, 3 Nov 2023 11:04:19 +0100 Subject: [PATCH 07/31] [Externals] Add support for macOS cross-compilation to build_glfw.py --- externals/prebuild_scripts/build_glfw.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/externals/prebuild_scripts/build_glfw.py b/externals/prebuild_scripts/build_glfw.py index e3036d0e..1f66519a 100644 --- a/externals/prebuild_scripts/build_glfw.py +++ b/externals/prebuild_scripts/build_glfw.py @@ -40,6 +40,11 @@ def build(build_type): if not is_windows: config_command.append("-DCMAKE_BUILD_TYPE=" + cmake_build_type) + + if system == SYSTEM_MAC64: + config_command.append("-DCMAKE_OSX_ARCHITECTURES=x86_64") + elif system == SYSTEM_MACARM64: + config_command.append("-DCMAKE_OSX_ARCHITECTURES=arm64") build_command = [ "cmake", From 29c1a76bd393901c0b6b16bf46e38c1a0759df79 Mon Sep 17 00:00:00 2001 From: Marino von Wattenwyl Date: Fri, 3 Nov 2023 11:12:21 +0100 Subject: [PATCH 08/31] [WebGPU] Add support for Metal surfaces --- apps/webgpu/CMakeLists.txt | 17 +++++++++++++---- apps/webgpu/main.cpp | 24 ++++++++++++++++++++++-- 2 files changed, 35 insertions(+), 6 deletions(-) diff --git a/apps/webgpu/CMakeLists.txt b/apps/webgpu/CMakeLists.txt index 40b48567..7eb61dd6 100644 --- a/apps/webgpu/CMakeLists.txt +++ b/apps/webgpu/CMakeLists.txt @@ -38,13 +38,22 @@ else () endif () add_library(wgpu STATIC IMPORTED) -set_target_properties(wgpu PROPERTIES - IMPORTED_LOCATION "${WGPU_DIR}/wgpu_native.lib" - INTERFACE_INCLUDE_DIRECTORIES "${WGPU_DIR}") if (SYSTEM_NAME_UPPER MATCHES "WINDOWS") - target_link_libraries(wgpu INTERFACE "ws2_32" "userenv" "advapi32" "bcrypt" "d3dcompiler" "ntdll" "opengl32") + target_link_libraries(wgpu INTERFACE "ws2_32" "userenv" "bcrypt" "d3dcompiler" "ntdll" "opengl32") + set_target_properties(wgpu PROPERTIES + IMPORTED_LOCATION "${WGPU_DIR}/libwgpu_native.lib" + INTERFACE_INCLUDE_DIRECTORIES "${WGPU_DIR}") +elseif (SYSTEM_NAME_UPPER MATCHES "DARWIN") + target_link_libraries(wgpu INTERFACE "-framework CoreFoundation" "-framework QuartzCore" "-framework Metal") + set_target_properties(wgpu PROPERTIES + IMPORTED_LOCATION "${WGPU_DIR}/libwgpu_native.a" + INTERFACE_INCLUDE_DIRECTORIES "${WGPU_DIR}") endif () add_executable(webgpu-demo main.cpp) target_link_libraries(webgpu-demo PRIVATE wgpu ${glfw_LIBS}) + +if (SYSTEM_NAME_UPPER MATCHES "DARWIN") + target_link_libraries(webgpu-demo PRIVATE "-framework Cocoa" "-framework IOKit") +endif () \ No newline at end of file diff --git a/apps/webgpu/main.cpp b/apps/webgpu/main.cpp index b23d48cb..b835cf79 100644 --- a/apps/webgpu/main.cpp +++ b/apps/webgpu/main.cpp @@ -9,10 +9,17 @@ #elif defined(__APPLE__) # define SYSTEM_DARWIN # define GLFW_EXPOSE_NATIVE_COCOA +# define OBJC_OLD_DISPATCH_PROTOTYPES 1 +# include +# include +# include +# include #endif #include + #include +#define GLFW_NATIVE_INCLUDE_NONE #include #include @@ -107,11 +114,24 @@ int main(int argc, const char* argv[]) // The surface is where our rendered images will be presented to. // It is created from window handles on most platforms and from a canvas in the browser. -#ifdef SYSTEM_WINDOWS +#if defined(SYSTEM_WINDOWS) WGPUSurfaceDescriptorFromWindowsHWND nativeSurfaceDesc = {}; nativeSurfaceDesc.chain.sType = WGPUSType_SurfaceDescriptorFromWindowsHWND; nativeSurfaceDesc.hinstance = GetModuleHandle(nullptr); nativeSurfaceDesc.hwnd = glfwGetWin32Window(window); +#elif defined(SYSTEM_DARWIN) + id cocoaWindow = glfwGetCocoaWindow(window); + + id contentView = objc_msgSend(cocoaWindow, sel_registerName("contentView")); + objc_msgSend(contentView, sel_getUid("setWantsLayer:"), 1); + + objc_class *metalLayerClass = objc_getClass("CAMetalLayer"); + id metalLayer = objc_msgSend((id)metalLayerClass, sel_getUid("layer")); + objc_msgSend(contentView, sel_registerName("setLayer:"), metalLayer); + + WGPUSurfaceDescriptorFromMetalLayer nativeSurfaceDesc = {}; + nativeSurfaceDesc.chain.sType = WGPUSType_SurfaceDescriptorFromMetalLayer; + nativeSurfaceDesc.layer = metalLayer; #endif WGPUSurfaceDescriptor surfaceDesc = {}; @@ -286,7 +306,7 @@ int main(int argc, const char* argv[]) // === Submit the command buffer to the GPU === // The work for the GPU is submitted through the queue and executed. wgpuQueueSubmit(queue, 1, &cmdBuffer); - + // === Present the surface === // This presents our rendered texture to the screen. wgpuSurfacePresent(surface); From a81d07795739a7d28f8bada8af21290e03ff7c6f Mon Sep 17 00:00:00 2001 From: Marino von Wattenwyl Date: Fri, 3 Nov 2023 11:17:03 +0100 Subject: [PATCH 09/31] [WebGPU] Add support for Xlib surfaces --- apps/webgpu/main.cpp | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/apps/webgpu/main.cpp b/apps/webgpu/main.cpp index b835cf79..7d48d42e 100644 --- a/apps/webgpu/main.cpp +++ b/apps/webgpu/main.cpp @@ -9,6 +9,8 @@ #elif defined(__APPLE__) # define SYSTEM_DARWIN # define GLFW_EXPOSE_NATIVE_COCOA +# define GLFW_NATIVE_INCLUDE_NONE + # define OBJC_OLD_DISPATCH_PROTOTYPES 1 # include # include @@ -17,9 +19,7 @@ #endif #include - #include -#define GLFW_NATIVE_INCLUDE_NONE #include #include @@ -119,19 +119,24 @@ int main(int argc, const char* argv[]) nativeSurfaceDesc.chain.sType = WGPUSType_SurfaceDescriptorFromWindowsHWND; nativeSurfaceDesc.hinstance = GetModuleHandle(nullptr); nativeSurfaceDesc.hwnd = glfwGetWin32Window(window); +#elif defined(SYSTEM_LINUX) + WGPUSurfaceDescriptorFromXlibWindow nativeSurfaceDesc = {}; + nativeSurfaceDesc.chain.sType = WGPUSType_SurfaceDescriptorFromXlibWindow; + nativeSurfaceDesc.display = glfwGetX11Display(); + nativeSurfaceDesc.window = glfwGetX11Window(window); #elif defined(SYSTEM_DARWIN) id cocoaWindow = glfwGetCocoaWindow(window); id contentView = objc_msgSend(cocoaWindow, sel_registerName("contentView")); objc_msgSend(contentView, sel_getUid("setWantsLayer:"), 1); - objc_class *metalLayerClass = objc_getClass("CAMetalLayer"); - id metalLayer = objc_msgSend((id)metalLayerClass, sel_getUid("layer")); + objc_class* metalLayerClass = objc_getClass("CAMetalLayer"); + id metalLayer = objc_msgSend((id)metalLayerClass, sel_getUid("layer")); objc_msgSend(contentView, sel_registerName("setLayer:"), metalLayer); WGPUSurfaceDescriptorFromMetalLayer nativeSurfaceDesc = {}; - nativeSurfaceDesc.chain.sType = WGPUSType_SurfaceDescriptorFromMetalLayer; - nativeSurfaceDesc.layer = metalLayer; + nativeSurfaceDesc.chain.sType = WGPUSType_SurfaceDescriptorFromMetalLayer; + nativeSurfaceDesc.layer = metalLayer; #endif WGPUSurfaceDescriptor surfaceDesc = {}; From fef422b6521a71477a7d3bea5efb102409de73ed Mon Sep 17 00:00:00 2001 From: Marino von Wattenwyl Date: Fri, 3 Nov 2023 11:18:27 +0100 Subject: [PATCH 10/31] [WebGPU] Add Linux libraries to CMake --- apps/webgpu/CMakeLists.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/webgpu/CMakeLists.txt b/apps/webgpu/CMakeLists.txt index 7eb61dd6..55b477a3 100644 --- a/apps/webgpu/CMakeLists.txt +++ b/apps/webgpu/CMakeLists.txt @@ -44,6 +44,10 @@ if (SYSTEM_NAME_UPPER MATCHES "WINDOWS") set_target_properties(wgpu PROPERTIES IMPORTED_LOCATION "${WGPU_DIR}/libwgpu_native.lib" INTERFACE_INCLUDE_DIRECTORIES "${WGPU_DIR}") +elseif (SYSTEM_NAME_UPPER MATCHES "LINUX") + set_target_properties(wgpu PROPERTIES + IMPORTED_LOCATION "${WGPU_DIR}/libwgpu_native.a" + INTERFACE_INCLUDE_DIRECTORIES "${WGPU_DIR}") elseif (SYSTEM_NAME_UPPER MATCHES "DARWIN") target_link_libraries(wgpu INTERFACE "-framework CoreFoundation" "-framework QuartzCore" "-framework Metal") set_target_properties(wgpu PROPERTIES From c7370487a25a28a7525eb0fa5cf8437fd987c3b1 Mon Sep 17 00:00:00 2001 From: Marino von Wattenwyl Date: Fri, 3 Nov 2023 11:31:54 +0100 Subject: [PATCH 11/31] [WebGPU] Link X11 on Linux --- apps/webgpu/CMakeLists.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/webgpu/CMakeLists.txt b/apps/webgpu/CMakeLists.txt index 55b477a3..4bc31d6b 100644 --- a/apps/webgpu/CMakeLists.txt +++ b/apps/webgpu/CMakeLists.txt @@ -58,6 +58,8 @@ endif () add_executable(webgpu-demo main.cpp) target_link_libraries(webgpu-demo PRIVATE wgpu ${glfw_LIBS}) -if (SYSTEM_NAME_UPPER MATCHES "DARWIN") +if (SYSTEM_NAME_UPPER MATCHES "LINUX") + target_link_libraries(webgpu-demo PRIVATE "X11") +elseif (SYSTEM_NAME_UPPER MATCHES "DARWIN") target_link_libraries(webgpu-demo PRIVATE "-framework Cocoa" "-framework IOKit") endif () \ No newline at end of file From 9c340b69d909b8ef410193a9f94b5631d642a13f Mon Sep 17 00:00:00 2001 From: Marino von Wattenwyl Date: Fri, 3 Nov 2023 13:33:34 +0100 Subject: [PATCH 12/31] [WebGPU] Render demo using vertex and index buffers --- apps/webgpu/CMakeLists.txt | 2 +- apps/webgpu/main.cpp | 111 ++++++++++++++++++++++++++++++++----- 2 files changed, 98 insertions(+), 15 deletions(-) diff --git a/apps/webgpu/CMakeLists.txt b/apps/webgpu/CMakeLists.txt index 4bc31d6b..cf777bb2 100644 --- a/apps/webgpu/CMakeLists.txt +++ b/apps/webgpu/CMakeLists.txt @@ -42,7 +42,7 @@ add_library(wgpu STATIC IMPORTED) if (SYSTEM_NAME_UPPER MATCHES "WINDOWS") target_link_libraries(wgpu INTERFACE "ws2_32" "userenv" "bcrypt" "d3dcompiler" "ntdll" "opengl32") set_target_properties(wgpu PROPERTIES - IMPORTED_LOCATION "${WGPU_DIR}/libwgpu_native.lib" + IMPORTED_LOCATION "${WGPU_DIR}/wgpu_native.lib" INTERFACE_INCLUDE_DIRECTORIES "${WGPU_DIR}") elseif (SYSTEM_NAME_UPPER MATCHES "LINUX") set_target_properties(wgpu PROPERTIES diff --git a/apps/webgpu/main.cpp b/apps/webgpu/main.cpp index 7d48d42e..5bf45c94 100644 --- a/apps/webgpu/main.cpp +++ b/apps/webgpu/main.cpp @@ -23,7 +23,9 @@ #include #include +#include #include +#include #define WEBGPU_DEMO_LOG(msg) std::cout << (msg) << std::endl @@ -93,11 +95,26 @@ int main(int argc, const char* argv[]) WGPUAdapter adapter; wgpuInstanceRequestAdapter(instance, &adapterOptions, handleAdapterRequest, &adapter); + WGPUSupportedLimits adapterLimits; + wgpuAdapterGetLimits(adapter, &adapterLimits); + // === Acquire a WebGPU device === // A device provides access to a GPU and is created from an adapter. + // We specify the capabilites that we require our device to have in requiredLimits. + // We cannot access more resources than specified in the required limits, + // which is how WebGPU prevents code from working on one machine and not on another. + + WGPURequiredLimits requiredLimits = {}; + requiredLimits.limits.maxVertexAttributes = 1; + requiredLimits.limits.maxVertexBuffers = 1; + requiredLimits.limits.maxBufferSize = 4ull * 2ull * sizeof(float); + requiredLimits.limits.maxVertexBufferArrayStride = 2ull * sizeof(float); + requiredLimits.limits.minStorageBufferOffsetAlignment = adapterLimits.limits.minStorageBufferOffsetAlignment; + requiredLimits.limits.minUniformBufferOffsetAlignment = adapterLimits.limits.minUniformBufferOffsetAlignment; WGPUDeviceDescriptor deviceDesc = {}; deviceDesc.label = "Demo Device"; + deviceDesc.requiredLimits = &requiredLimits; deviceDesc.defaultQueue.label = "Demo Queue"; WGPUDevice device; @@ -170,6 +187,58 @@ int main(int argc, const char* argv[]) wgpuSurfaceConfigure(surface, &surfaceConfig); WEBGPU_DEMO_LOG("[WebGPU] Surface configured"); + // === Create the WebGPU vertex buffer === + // The vertex buffer contains the input data for the shader. + + // clang-format off + std::vector vertexData = + { + -0.5, 0.5, // top-left corner + -0.5, -0.5, // bottom-left corner + 0.5, 0.5, // top-right corner + 0.5, -0.5, // bottom-right corner + }; + // clang-format on + + unsigned vertexCount = vertexData.size() / 2; + unsigned vertexDataSize = vertexData.size() * sizeof(float); + + WGPUBufferDescriptor vertexBufferDesc = {}; + vertexBufferDesc.label = "Demo Vertex Buffer"; + vertexBufferDesc.size = vertexDataSize; + vertexBufferDesc.usage = WGPUBufferUsage_CopyDst | WGPUBufferUsage_Vertex; + + WGPUBuffer vertexBuffer = wgpuDeviceCreateBuffer(device, &vertexBufferDesc); + WEBGPU_DEMO_CHECK(vertexBuffer, "[WebGPU] Failed to create vertex buffer"); + WEBGPU_DEMO_LOG("[WebGPU] Vertex buffer created"); + + // Upload the data to the GPU. + wgpuQueueWriteBuffer(queue, vertexBuffer, 0, vertexData.data(), vertexDataSize); + + // === Create the WebGPU index buffer === + + // clang-format off + std::vector indexData = + { + 0, 1, 2, + 2, 1, 3, + }; + // clang-format on + + unsigned indexCount = indexData.size(); + unsigned indexDataSize = indexData.size() * sizeof(std::uint16_t); + + WGPUBufferDescriptor indexBufferDesc = {}; + indexBufferDesc.label = "Demo Index Buffer"; + indexBufferDesc.size = indexDataSize; + indexBufferDesc.usage = WGPUBufferUsage_CopyDst | WGPUBufferUsage_Index; + + WGPUBuffer indexBuffer = wgpuDeviceCreateBuffer(device, &indexBufferDesc); + WEBGPU_DEMO_CHECK(indexBuffer, "[WebGPU] Failed to create index buffer"); + WEBGPU_DEMO_LOG("[WebGPU] Index buffer created"); + + wgpuQueueWriteBuffer(queue, indexBuffer, 0, indexData.data(), indexDataSize); + // === Create the shader module === // Create a shader module for use in our render pipeline. // Shaders are written in a WebGPU-specific language called WGSL (WebGPU shader language). @@ -177,14 +246,8 @@ int main(int argc, const char* argv[]) const char* shaderSource = R"( @vertex - fn vs_main(@builtin(vertex_index) in_vertex_index: u32) -> @builtin(position) vec4f { - if (in_vertex_index == 0u) { - return vec4f(-0.5, -0.5, 0.0, 1.0); - } else if (in_vertex_index == 1u) { - return vec4f(0.5, -0.5, 0.0, 1.0); - } else { - return vec4f(0.0, 0.5, 0.0, 1.0); - } + fn vs_main(@location(0) in_vertex_position: vec2f) -> @builtin(position) vec4f { + return vec4f(in_vertex_position, 0.0, 1.0); } @fragment @@ -198,7 +261,7 @@ int main(int argc, const char* argv[]) shaderModuleWGSLDesc.code = shaderSource; WGPUShaderModuleDescriptor shaderModuleDesc = {}; - shaderModuleDesc.label = "Hello Triangle Shader"; + shaderModuleDesc.label = "Demo Shader"; shaderModuleDesc.nextInChain = (const WGPUChainedStruct*)&shaderModuleWGSLDesc; WGPUShaderModule shaderModule = wgpuDeviceCreateShaderModule(device, &shaderModuleDesc); @@ -209,10 +272,25 @@ int main(int argc, const char* argv[]) // The render pipeline specifies the configuration for the fixed-function stages as well as // the shaders for the programmable stages of the hardware pipeline. + // Description of the vertex attribute for the vertex buffer layout + WGPUVertexAttribute vertexAttribute = {}; + vertexAttribute.format = WGPUVertexFormat_Float32x2; + vertexAttribute.offset = 0; + vertexAttribute.shaderLocation = 0; + + // Description of the vertex buffer layout for the vertex shader stage + WGPUVertexBufferLayout vertexBufferLayout = {}; + vertexBufferLayout.arrayStride = 2ull * sizeof(float); + vertexBufferLayout.stepMode = WGPUVertexStepMode_Vertex; + vertexBufferLayout.attributeCount = 1; + vertexBufferLayout.attributes = &vertexAttribute; + // Configuration for the vertex shader stage WGPUVertexState vertexState = {}; vertexState.module = shaderModule; vertexState.entryPoint = "vs_main"; + vertexState.bufferCount = 1; + vertexState.buffers = &vertexBufferLayout; // Configuration for the primitive assembly and rasterization stages WGPUPrimitiveState primitiveState = {}; @@ -238,7 +316,7 @@ int main(int argc, const char* argv[]) // Configuration for the entire pipeline WGPURenderPipelineDescriptor pipelineDesc = {}; - pipelineDesc.label = "Hello Triangle Pipeline"; + pipelineDesc.label = "Demo Pipeline"; pipelineDesc.vertex = vertexState; pipelineDesc.primitive = primitiveState; pipelineDesc.multisample = multisampleState; @@ -252,6 +330,7 @@ int main(int argc, const char* argv[]) while (!glfwWindowShouldClose(window)) { + // === Create a WebGPU texture view === // The texture view is where we render our image into. @@ -267,7 +346,7 @@ int main(int argc, const char* argv[]) // The encoder encodes the commands for the GPU into a command buffer. WGPUCommandEncoderDescriptor cmdEncoderDesc = {}; - cmdEncoderDesc.label = " Hello Triangle Command Encoder"; + cmdEncoderDesc.label = "Demo Command Encoder"; WGPUCommandEncoder cmdEncoder = wgpuDeviceCreateCommandEncoder(device, &cmdEncoderDesc); WEBGPU_DEMO_CHECK(cmdEncoder, "[WebGPU] Failed to create command encoder"); @@ -287,7 +366,7 @@ int main(int argc, const char* argv[]) colorAttachment.clearValue.a = 1.0; WGPURenderPassDescriptor renderPassDesc = {}; - renderPassDesc.label = "Hello Triangle Render Pass"; + renderPassDesc.label = "Demo Render Pass"; renderPassDesc.colorAttachmentCount = 1; renderPassDesc.colorAttachments = &colorAttachment; @@ -297,14 +376,16 @@ int main(int argc, const char* argv[]) WGPURenderPassEncoder renderPassEncoder = wgpuCommandEncoderBeginRenderPass(cmdEncoder, &renderPassDesc); wgpuRenderPassEncoderSetPipeline(renderPassEncoder, pipeline); - wgpuRenderPassEncoderDraw(renderPassEncoder, 3, 1, 0, 0); + wgpuRenderPassEncoderSetVertexBuffer(renderPassEncoder, 0, vertexBuffer, 0, vertexDataSize); + wgpuRenderPassEncoderSetIndexBuffer(renderPassEncoder, indexBuffer, WGPUIndexFormat_Uint16, 0, indexDataSize); + wgpuRenderPassEncoderDrawIndexed(renderPassEncoder, indexCount, 1, 0, 0, 0); wgpuRenderPassEncoderEnd(renderPassEncoder); // === Get the command buffer === // The command encoder is finished to get the commands for the GPU to execute in a command buffer. WGPUCommandBufferDescriptor cmdBufferDesc = {}; - cmdBufferDesc.label = "Hello Triangle Command Buffer"; + cmdBufferDesc.label = "Demo Command Buffer"; WGPUCommandBuffer cmdBuffer = wgpuCommandEncoderFinish(cmdEncoder, &cmdBufferDesc); @@ -330,6 +411,8 @@ int main(int argc, const char* argv[]) wgpuRenderPipelineRelease(pipeline); wgpuShaderModuleRelease(shaderModule); + wgpuBufferDestroy(vertexBuffer); + wgpuBufferRelease(vertexBuffer); wgpuSurfaceRelease(surface); wgpuQueueRelease(queue); wgpuDeviceRelease(device); From 0794f766ec7eaa9b73a7a47d3658613b3c77efc9 Mon Sep 17 00:00:00 2001 From: Marino von Wattenwyl Date: Fri, 3 Nov 2023 13:43:49 +0100 Subject: [PATCH 13/31] [WebGPU] Add surface re-configuring to demo --- apps/webgpu/main.cpp | 65 ++++++++++++++++++++++++++++++++++---------- 1 file changed, 51 insertions(+), 14 deletions(-) diff --git a/apps/webgpu/main.cpp b/apps/webgpu/main.cpp index 5bf45c94..96a672d3 100644 --- a/apps/webgpu/main.cpp +++ b/apps/webgpu/main.cpp @@ -74,9 +74,6 @@ int main(int argc, const char* argv[]) // Prevent GLFW from creating an OpenGL context as the underlying graphics API probably won't be OpenGL. glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); - // Create a non-resizable window as we currently don't recreate surfaces when the window size changes. - glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); - GLFWwindow* window = glfwCreateWindow(1280, 720, "WebGPU Demo", nullptr, nullptr); WEBGPU_DEMO_CHECK(window, "[GLFW] Window created"); @@ -200,7 +197,7 @@ int main(int argc, const char* argv[]) }; // clang-format on - unsigned vertexCount = vertexData.size() / 2; + unsigned vertexCount = vertexData.size() / 2; unsigned vertexDataSize = vertexData.size() * sizeof(float); WGPUBufferDescriptor vertexBufferDesc = {}; @@ -216,7 +213,7 @@ int main(int argc, const char* argv[]) wgpuQueueWriteBuffer(queue, vertexBuffer, 0, vertexData.data(), vertexDataSize); // === Create the WebGPU index buffer === - + // clang-format off std::vector indexData = { @@ -225,7 +222,7 @@ int main(int argc, const char* argv[]) }; // clang-format on - unsigned indexCount = indexData.size(); + unsigned indexCount = indexData.size(); unsigned indexDataSize = indexData.size() * sizeof(std::uint16_t); WGPUBufferDescriptor indexBufferDesc = {}; @@ -274,16 +271,16 @@ int main(int argc, const char* argv[]) // Description of the vertex attribute for the vertex buffer layout WGPUVertexAttribute vertexAttribute = {}; - vertexAttribute.format = WGPUVertexFormat_Float32x2; - vertexAttribute.offset = 0; - vertexAttribute.shaderLocation = 0; + vertexAttribute.format = WGPUVertexFormat_Float32x2; + vertexAttribute.offset = 0; + vertexAttribute.shaderLocation = 0; // Description of the vertex buffer layout for the vertex shader stage WGPUVertexBufferLayout vertexBufferLayout = {}; - vertexBufferLayout.arrayStride = 2ull * sizeof(float); - vertexBufferLayout.stepMode = WGPUVertexStepMode_Vertex; - vertexBufferLayout.attributeCount = 1; - vertexBufferLayout.attributes = &vertexAttribute; + vertexBufferLayout.arrayStride = 2ull * sizeof(float); + vertexBufferLayout.stepMode = WGPUVertexStepMode_Vertex; + vertexBufferLayout.attributeCount = 1; + vertexBufferLayout.attributes = &vertexAttribute; // Configuration for the vertex shader stage WGPUVertexState vertexState = {}; @@ -337,7 +334,47 @@ int main(int argc, const char* argv[]) // Get a texture from the surface to render into. WGPUSurfaceTexture surfaceTexture; wgpuSurfaceGetCurrentTexture(surface, &surfaceTexture); - WEBGPU_DEMO_CHECK(surfaceTexture.status == WGPUSurfaceGetCurrentTextureStatus_Success, "[WebGPU] Failed to acquire current surface texture"); + + // The surface might change over time. + // For example, the window might be resized or minimized. + // We have to check the status and adapt to it. + switch (surfaceTexture.status) + { + case WGPUSurfaceGetCurrentTextureStatus_Success: + // Everything is ok. + // TODO: check for a suboptimal texture and re-configure it if needed. + break; + + case WGPUSurfaceGetCurrentTextureStatus_Timeout: + case WGPUSurfaceGetCurrentTextureStatus_Outdated: + case WGPUSurfaceGetCurrentTextureStatus_Lost: + // The surface needs to be re-configured. + + // Get the window size from the GLFW window. + glfwGetWindowSize(window, &surfaceWidth, &surfaceHeight); + + // The surface size might be zero if the window is minimized. + if (surfaceWidth != 0 && surfaceHeight != 0) + { + WEBGPU_DEMO_LOG("[WebGPU] Re-configuring surface"); + surfaceConfig.width = surfaceWidth; + surfaceConfig.height = surfaceHeight; + wgpuSurfaceConfigure(surface, &surfaceConfig); + } + + // Skip this frame. + glfwPollEvents(); + continue; + + case WGPUSurfaceGetCurrentTextureStatus_OutOfMemory: + case WGPUSurfaceGetCurrentTextureStatus_DeviceLost: + // An error occured. + WEBGPU_DEMO_CHECK(false, "[WebGPU] Failed to acquire current surface texture"); + break; + + case WGPUSurfaceGetCurrentTextureStatus_Force32: + break; + } // Create a view into the texture to specify where and how to modify the texture. WGPUTextureView view = wgpuTextureCreateView(surfaceTexture.texture, nullptr); From 1cf61b7d645de46b1aa79d91e0fdf1cee0d20271 Mon Sep 17 00:00:00 2001 From: Marino von Wattenwyl Date: Fri, 3 Nov 2023 14:29:57 +0100 Subject: [PATCH 14/31] [WebGPU] Add uniforms to demo --- apps/webgpu/main.cpp | 109 ++++++++++++++++++++++++++++++++++++++----- 1 file changed, 98 insertions(+), 11 deletions(-) diff --git a/apps/webgpu/main.cpp b/apps/webgpu/main.cpp index 96a672d3..c433a0f0 100644 --- a/apps/webgpu/main.cpp +++ b/apps/webgpu/main.cpp @@ -102,10 +102,14 @@ int main(int argc, const char* argv[]) // which is how WebGPU prevents code from working on one machine and not on another. WGPURequiredLimits requiredLimits = {}; - requiredLimits.limits.maxVertexAttributes = 1; - requiredLimits.limits.maxVertexBuffers = 1; + requiredLimits.limits.maxVertexAttributes = 1u; + requiredLimits.limits.maxVertexBuffers = 1u; requiredLimits.limits.maxBufferSize = 4ull * 2ull * sizeof(float); - requiredLimits.limits.maxVertexBufferArrayStride = 2ull * sizeof(float); + requiredLimits.limits.maxVertexBufferArrayStride = 2u * sizeof(float); + requiredLimits.limits.maxInterStageShaderComponents = 2u; + requiredLimits.limits.maxBindGroups = 1u; + requiredLimits.limits.maxUniformBuffersPerShaderStage = 1u; + requiredLimits.limits.maxUniformBufferBindingSize = 16ull * 4ull; requiredLimits.limits.minStorageBufferOffsetAlignment = adapterLimits.limits.minStorageBufferOffsetAlignment; requiredLimits.limits.minUniformBufferOffsetAlignment = adapterLimits.limits.minUniformBufferOffsetAlignment; @@ -184,7 +188,7 @@ int main(int argc, const char* argv[]) wgpuSurfaceConfigure(surface, &surfaceConfig); WEBGPU_DEMO_LOG("[WebGPU] Surface configured"); - // === Create the WebGPU vertex buffer === + // === Create the vertex buffer === // The vertex buffer contains the input data for the shader. // clang-format off @@ -212,7 +216,7 @@ int main(int argc, const char* argv[]) // Upload the data to the GPU. wgpuQueueWriteBuffer(queue, vertexBuffer, 0, vertexData.data(), vertexDataSize); - // === Create the WebGPU index buffer === + // === Create the index buffer === // clang-format off std::vector indexData = @@ -236,20 +240,46 @@ int main(int argc, const char* argv[]) wgpuQueueWriteBuffer(queue, indexBuffer, 0, indexData.data(), indexDataSize); + // === Create the uniform buffer === + + WGPUBufferDescriptor uniformBufferDesc = {}; + uniformBufferDesc.label = "Demo Uniform Buffer"; + uniformBufferDesc.size = sizeof(float); + uniformBufferDesc.usage = WGPUBufferUsage_CopyDst | WGPUBufferUsage_Uniform; + + WGPUBuffer uniformBuffer = wgpuDeviceCreateBuffer(device, &uniformBufferDesc); + WEBGPU_DEMO_CHECK(indexBuffer, "[WebGPU] Failed to create uniform buffer"); + WEBGPU_DEMO_LOG("[WebGPU] Uniform buffer created"); + + float uniformBufferData = 0.0f; + wgpuQueueWriteBuffer(queue, uniformBuffer, 0, &uniformBufferData, sizeof(float)); + // === Create the shader module === // Create a shader module for use in our render pipeline. // Shaders are written in a WebGPU-specific language called WGSL (WebGPU shader language). // Shader modules can be used in multiple pipeline stages by specifying different entry points. const char* shaderSource = R"( + @group(0) @binding(0) var u_time: f32; + + struct VertexOutput { + @builtin(position) position: vec4f, + @location(0) texture_coords: vec2f, + } + @vertex - fn vs_main(@location(0) in_vertex_position: vec2f) -> @builtin(position) vec4f { - return vec4f(in_vertex_position, 0.0, 1.0); + fn vs_main(@location(0) in_vertex_position: vec2f) -> VertexOutput { + var offset = vec2(0.5 * sin(u_time), 0.5 * cos(u_time)); + + var out: VertexOutput; + out.position = vec4f(in_vertex_position + offset, 0.0, 1.0); + out.texture_coords = in_vertex_position + vec2(0.5, 0.5); + return out; } @fragment - fn fs_main() -> @location(0) vec4f { - return vec4f(0.2, 0.2, 1.0, 1.0); + fn fs_main(in: VertexOutput) -> @location(0) vec4f { + return vec4f(in.texture_coords, 1.0, 1.0); } )"; @@ -265,7 +295,52 @@ int main(int argc, const char* argv[]) WEBGPU_DEMO_CHECK(shaderModule, "[WebGPU] Failed to create shader module"); WEBGPU_DEMO_LOG("[WebGPU] Shader module created"); - // === Create the WebGPU render pipeline === + // === Create the bind group layout === + // Bind groups contain binding layouts that describe how uniforms are passed to shaders. + + WGPUBindGroupLayoutEntry bindingLayout = {}; + bindingLayout.binding = 0; + bindingLayout.visibility = WGPUShaderStage_Vertex; + bindingLayout.buffer.type = WGPUBufferBindingType_Uniform; + bindingLayout.buffer.minBindingSize = sizeof(float); + + WGPUBindGroupLayoutDescriptor bindGroupLayoutDesc = {}; + bindGroupLayoutDesc.label = "Demo Bind Group Layout"; + bindGroupLayoutDesc.entryCount = 1; + bindGroupLayoutDesc.entries = &bindingLayout; + WGPUBindGroupLayout bindGroupLayout = wgpuDeviceCreateBindGroupLayout(device, &bindGroupLayoutDesc); + WEBGPU_DEMO_CHECK(bindGroupLayout, "[WebGPU] Failed to create bind group layout"); + WEBGPU_DEMO_LOG("[WebGPU] Bind group layout created"); + + // === Create the bind group === + // The bind group actually binds the buffer to the uniform. + + WGPUBindGroupEntry binding = {}; + binding.binding = 0; + binding.buffer = uniformBuffer; + binding.offset = 0; + binding.size = sizeof(float); + + WGPUBindGroupDescriptor bindGroupDesc = {}; + bindGroupDesc.layout = bindGroupLayout; + bindGroupDesc.entryCount = bindGroupLayoutDesc.entryCount; + bindGroupDesc.entries = &binding; + WGPUBindGroup bindGroup = wgpuDeviceCreateBindGroup(device, &bindGroupDesc); + WEBGPU_DEMO_CHECK(bindGroup, "[WebGPU] Failed to create bind group"); + WEBGPU_DEMO_LOG("[WebGPU] Bind group created"); + + // === Create the pipeline layout === + // The pipeline layout specifies the bind groups the pipeline uses. + + WGPUPipelineLayoutDescriptor pipelineLayoutDesc = {}; + pipelineLayoutDesc.label = "Demo Pipeline Layout"; + pipelineLayoutDesc.bindGroupLayoutCount = 1; + pipelineLayoutDesc.bindGroupLayouts = &bindGroupLayout; + WGPUPipelineLayout pipelineLayout = wgpuDeviceCreatePipelineLayout(device, &pipelineLayoutDesc); + WEBGPU_DEMO_CHECK(pipelineLayout, "[WebGPU] Failed to create pipeline layout"); + WEBGPU_DEMO_LOG("[WebGPU] Pipeline layout created"); + + // === Create the render pipeline === // The render pipeline specifies the configuration for the fixed-function stages as well as // the shaders for the programmable stages of the hardware pipeline. @@ -314,6 +389,7 @@ int main(int argc, const char* argv[]) // Configuration for the entire pipeline WGPURenderPipelineDescriptor pipelineDesc = {}; pipelineDesc.label = "Demo Pipeline"; + pipelineDesc.layout = pipelineLayout; pipelineDesc.vertex = vertexState; pipelineDesc.primitive = primitiveState; pipelineDesc.multisample = multisampleState; @@ -327,7 +403,6 @@ int main(int argc, const char* argv[]) while (!glfwWindowShouldClose(window)) { - // === Create a WebGPU texture view === // The texture view is where we render our image into. @@ -379,6 +454,10 @@ int main(int argc, const char* argv[]) // Create a view into the texture to specify where and how to modify the texture. WGPUTextureView view = wgpuTextureCreateView(surfaceTexture.texture, nullptr); + // === Update the uniform === + float time = static_cast(glfwGetTime()); + wgpuQueueWriteBuffer(queue, uniformBuffer, 0, &time, sizeof(float)); + // === Create a WebGPU command encoder === // The encoder encodes the commands for the GPU into a command buffer. @@ -415,6 +494,7 @@ int main(int argc, const char* argv[]) wgpuRenderPassEncoderSetPipeline(renderPassEncoder, pipeline); wgpuRenderPassEncoderSetVertexBuffer(renderPassEncoder, 0, vertexBuffer, 0, vertexDataSize); wgpuRenderPassEncoderSetIndexBuffer(renderPassEncoder, indexBuffer, WGPUIndexFormat_Uint16, 0, indexDataSize); + wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 0, bindGroup, 0, nullptr); wgpuRenderPassEncoderDrawIndexed(renderPassEncoder, indexCount, 1, 0, 0, 0); wgpuRenderPassEncoderEnd(renderPassEncoder); @@ -447,7 +527,14 @@ int main(int argc, const char* argv[]) // === Release all WebGPU resources === wgpuRenderPipelineRelease(pipeline); + wgpuPipelineLayoutRelease(pipelineLayout); + wgpuBindGroupRelease(bindGroup); + wgpuBindGroupLayoutRelease(bindGroupLayout); wgpuShaderModuleRelease(shaderModule); + wgpuBufferDestroy(uniformBuffer); + wgpuBufferRelease(uniformBuffer); + wgpuBufferDestroy(indexBuffer); + wgpuBufferRelease(indexBuffer); wgpuBufferDestroy(vertexBuffer); wgpuBufferRelease(vertexBuffer); wgpuSurfaceRelease(surface); From 7f7bd944f2bf3a0569d6896addfe62f97df3a7a5 Mon Sep 17 00:00:00 2001 From: Marino von Wattenwyl Date: Fri, 3 Nov 2023 16:13:41 +0100 Subject: [PATCH 15/31] [WebGPU] Add texture to demo --- apps/webgpu/CMakeLists.txt | 2 +- apps/webgpu/main.cpp | 142 +++++++++++++++++++++++++++++-------- 2 files changed, 115 insertions(+), 29 deletions(-) diff --git a/apps/webgpu/CMakeLists.txt b/apps/webgpu/CMakeLists.txt index cf777bb2..373f3bec 100644 --- a/apps/webgpu/CMakeLists.txt +++ b/apps/webgpu/CMakeLists.txt @@ -56,7 +56,7 @@ elseif (SYSTEM_NAME_UPPER MATCHES "DARWIN") endif () add_executable(webgpu-demo main.cpp) -target_link_libraries(webgpu-demo PRIVATE wgpu ${glfw_LIBS}) +target_link_libraries(webgpu-demo PRIVATE wgpu sl_cv ${glfw_LIBS}) if (SYSTEM_NAME_UPPER MATCHES "LINUX") target_link_libraries(webgpu-demo PRIVATE "X11") diff --git a/apps/webgpu/main.cpp b/apps/webgpu/main.cpp index c433a0f0..5a8f2286 100644 --- a/apps/webgpu/main.cpp +++ b/apps/webgpu/main.cpp @@ -21,6 +21,8 @@ #include #include #include +#include +#include #include #include @@ -101,17 +103,22 @@ int main(int argc, const char* argv[]) // We cannot access more resources than specified in the required limits, // which is how WebGPU prevents code from working on one machine and not on another. - WGPURequiredLimits requiredLimits = {}; - requiredLimits.limits.maxVertexAttributes = 1u; - requiredLimits.limits.maxVertexBuffers = 1u; - requiredLimits.limits.maxBufferSize = 4ull * 2ull * sizeof(float); - requiredLimits.limits.maxVertexBufferArrayStride = 2u * sizeof(float); - requiredLimits.limits.maxInterStageShaderComponents = 2u; - requiredLimits.limits.maxBindGroups = 1u; - requiredLimits.limits.maxUniformBuffersPerShaderStage = 1u; - requiredLimits.limits.maxUniformBufferBindingSize = 16ull * 4ull; - requiredLimits.limits.minStorageBufferOffsetAlignment = adapterLimits.limits.minStorageBufferOffsetAlignment; - requiredLimits.limits.minUniformBufferOffsetAlignment = adapterLimits.limits.minUniformBufferOffsetAlignment; + WGPURequiredLimits requiredLimits = {}; + requiredLimits.limits.maxVertexAttributes = 1u; + requiredLimits.limits.maxVertexBuffers = 1u; + requiredLimits.limits.maxBufferSize = 4ull * 2ull * sizeof(float); + requiredLimits.limits.maxVertexBufferArrayStride = 2u * sizeof(float); + requiredLimits.limits.maxInterStageShaderComponents = 2u; + requiredLimits.limits.maxBindGroups = 2u; + requiredLimits.limits.maxBindingsPerBindGroup = 2u; + requiredLimits.limits.maxUniformBuffersPerShaderStage = 1u; + requiredLimits.limits.maxUniformBufferBindingSize = 16ull * 4ull; + requiredLimits.limits.maxSampledTexturesPerShaderStage = 1u; + requiredLimits.limits.maxTextureDimension1D = 2048; + requiredLimits.limits.maxTextureDimension2D = 2048; + requiredLimits.limits.maxTextureArrayLayers = 1; + requiredLimits.limits.minStorageBufferOffsetAlignment = adapterLimits.limits.minStorageBufferOffsetAlignment; + requiredLimits.limits.minUniformBufferOffsetAlignment = adapterLimits.limits.minUniformBufferOffsetAlignment; WGPUDeviceDescriptor deviceDesc = {}; deviceDesc.label = "Demo Device"; @@ -254,6 +261,65 @@ int main(int argc, const char* argv[]) float uniformBufferData = 0.0f; wgpuQueueWriteBuffer(queue, uniformBuffer, 0, &uniformBufferData, sizeof(float)); + // === Create the texture === + + // unsigned pixelDataSize = 256 * 256 * 4; + // std::vector pixelData(pixelDataSize, 100); + + cv::Mat image = cv::imread(std::string(SL_PROJECT_ROOT) + "/data/images/textures/brickwall0512_C.jpg"); + cv::cvtColor(image, image, cv::COLOR_BGR2RGBA); + + unsigned imageWidth = image.cols; + unsigned imageHeight = image.rows; + unsigned pixelDataSize = 4 * imageWidth * imageHeight; + + WGPUTextureDescriptor textureDesc = {}; + textureDesc.label = "Demo Texture"; + textureDesc.usage = WGPUTextureUsage_CopyDst | WGPUTextureUsage_TextureBinding; + textureDesc.dimension = WGPUTextureDimension_2D; + textureDesc.size.width = imageWidth; + textureDesc.size.height = imageHeight; + textureDesc.size.depthOrArrayLayers = 1; + textureDesc.format = WGPUTextureFormat_RGBA8UnormSrgb; + textureDesc.mipLevelCount = 1; + textureDesc.sampleCount = 1; + + WGPUTexture texture = wgpuDeviceCreateTexture(device, &textureDesc); + WEBGPU_DEMO_CHECK(texture, "[WebGPU] Failed to create texture"); + WEBGPU_DEMO_LOG("[WebGPU] Texture created"); + + // Where do we copyu the pixel data to? + WGPUImageCopyTexture destination = {}; + destination.texture = texture; + destination.mipLevel = 0; + destination.origin.x = 0; + destination.origin.y = 0; + destination.origin.z = 0; + destination.aspect = WGPUTextureAspect_All; + + // Where do we copy the pixel data from? + WGPUTextureDataLayout pixelDataLayout = {}; + pixelDataLayout.offset = 0; + pixelDataLayout.bytesPerRow = 4 * textureDesc.size.width; + pixelDataLayout.rowsPerImage = textureDesc.size.height; + + // Upload the data to the GPU. + wgpuQueueWriteTexture(queue, &destination, image.data, pixelDataSize, &pixelDataLayout, &textureDesc.size); + + // === Create a texture view into the texture === + WGPUTextureViewDescriptor textureViewDesc = {}; + textureViewDesc.aspect = WGPUTextureAspect_All; + textureViewDesc.baseArrayLayer = 0; + textureViewDesc.arrayLayerCount = 1; + textureViewDesc.baseMipLevel = 0; + textureViewDesc.mipLevelCount = 1; + textureViewDesc.dimension = WGPUTextureViewDimension_2D; + textureViewDesc.format = textureDesc.format; + + WGPUTextureView textureView = wgpuTextureCreateView(texture, &textureViewDesc); + WEBGPU_DEMO_CHECK(textureView, "[WebGPU] Failed to create texture view"); + WEBGPU_DEMO_LOG("[WebGPU] Texture view created"); + // === Create the shader module === // Create a shader module for use in our render pipeline. // Shaders are written in a WebGPU-specific language called WGSL (WebGPU shader language). @@ -261,6 +327,7 @@ int main(int argc, const char* argv[]) const char* shaderSource = R"( @group(0) @binding(0) var u_time: f32; + @group(0) @binding(1) var texture: texture_2d; struct VertexOutput { @builtin(position) position: vec4f, @@ -279,7 +346,7 @@ int main(int argc, const char* argv[]) @fragment fn fs_main(in: VertexOutput) -> @location(0) vec4f { - return vec4f(in.texture_coords, 1.0, 1.0); + return textureLoad(texture, vec2i(i32(512.0 * in.texture_coords.x), i32(512.0 * in.texture_coords.y)), 0).rgba; } )"; @@ -296,35 +363,51 @@ int main(int argc, const char* argv[]) WEBGPU_DEMO_LOG("[WebGPU] Shader module created"); // === Create the bind group layout === - // Bind groups contain binding layouts that describe how uniforms are passed to shaders. + // Bind groups contain binding layouts that describe how uniforms and textures are passed to shaders. + + // Entry for the uniform + WGPUBindGroupLayoutEntry uniformLayout = {}; + uniformLayout.binding = 0; + uniformLayout.visibility = WGPUShaderStage_Vertex; + uniformLayout.buffer.type = WGPUBufferBindingType_Uniform; + uniformLayout.buffer.minBindingSize = sizeof(float); - WGPUBindGroupLayoutEntry bindingLayout = {}; - bindingLayout.binding = 0; - bindingLayout.visibility = WGPUShaderStage_Vertex; - bindingLayout.buffer.type = WGPUBufferBindingType_Uniform; - bindingLayout.buffer.minBindingSize = sizeof(float); + // Entry for the texture + WGPUBindGroupLayoutEntry textureLayout = {}; + textureLayout.binding = 1; + textureLayout.visibility = WGPUShaderStage_Fragment; + textureLayout.texture.sampleType = WGPUTextureSampleType_Float; + textureLayout.texture.viewDimension = WGPUTextureViewDimension_2D; + + std::vector bindGroupLayoutEntries = {uniformLayout, textureLayout}; WGPUBindGroupLayoutDescriptor bindGroupLayoutDesc = {}; bindGroupLayoutDesc.label = "Demo Bind Group Layout"; - bindGroupLayoutDesc.entryCount = 1; - bindGroupLayoutDesc.entries = &bindingLayout; + bindGroupLayoutDesc.entryCount = bindGroupLayoutEntries.size(); + bindGroupLayoutDesc.entries = bindGroupLayoutEntries.data(); WGPUBindGroupLayout bindGroupLayout = wgpuDeviceCreateBindGroupLayout(device, &bindGroupLayoutDesc); WEBGPU_DEMO_CHECK(bindGroupLayout, "[WebGPU] Failed to create bind group layout"); WEBGPU_DEMO_LOG("[WebGPU] Bind group layout created"); // === Create the bind group === - // The bind group actually binds the buffer to the uniform. + // The bind group actually binds the buffer to uniforms and textures. + + WGPUBindGroupEntry uniformBinding = {}; + uniformBinding.binding = 0; + uniformBinding.buffer = uniformBuffer; + uniformBinding.offset = 0; + uniformBinding.size = sizeof(float); + + WGPUBindGroupEntry textureBinding = {}; + textureBinding.binding = 1; + textureBinding.textureView = textureView; - WGPUBindGroupEntry binding = {}; - binding.binding = 0; - binding.buffer = uniformBuffer; - binding.offset = 0; - binding.size = sizeof(float); + std::vector bindGroupEntries = {uniformBinding, textureBinding}; WGPUBindGroupDescriptor bindGroupDesc = {}; bindGroupDesc.layout = bindGroupLayout; - bindGroupDesc.entryCount = bindGroupLayoutDesc.entryCount; - bindGroupDesc.entries = &binding; + bindGroupDesc.entryCount = bindGroupEntries.size(); + bindGroupDesc.entries = bindGroupEntries.data(); WGPUBindGroup bindGroup = wgpuDeviceCreateBindGroup(device, &bindGroupDesc); WEBGPU_DEMO_CHECK(bindGroup, "[WebGPU] Failed to create bind group"); WEBGPU_DEMO_LOG("[WebGPU] Bind group created"); @@ -531,6 +614,9 @@ int main(int argc, const char* argv[]) wgpuBindGroupRelease(bindGroup); wgpuBindGroupLayoutRelease(bindGroupLayout); wgpuShaderModuleRelease(shaderModule); + wgpuTextureViewRelease(textureView); + wgpuTextureDestroy(texture); + wgpuTextureRelease(texture); wgpuBufferDestroy(uniformBuffer); wgpuBufferRelease(uniformBuffer); wgpuBufferDestroy(indexBuffer); From 7339609d7410f7a1baa7efb4ec5d1898e52a8f34 Mon Sep 17 00:00:00 2001 From: Marino von Wattenwyl Date: Fri, 3 Nov 2023 16:15:12 +0100 Subject: [PATCH 16/31] [WebGPU] Use mailbox present mode in demo --- apps/webgpu/main.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/apps/webgpu/main.cpp b/apps/webgpu/main.cpp index 5a8f2286..ccad5570 100644 --- a/apps/webgpu/main.cpp +++ b/apps/webgpu/main.cpp @@ -188,7 +188,7 @@ int main(int argc, const char* argv[]) surfaceConfig.device = device; surfaceConfig.usage = WGPUTextureUsage_RenderAttachment; surfaceConfig.format = surfaceCapabilities.formats[0]; - surfaceConfig.presentMode = WGPUPresentMode_Fifo; + surfaceConfig.presentMode = WGPUPresentMode_Mailbox; surfaceConfig.alphaMode = surfaceCapabilities.alphaModes[0]; surfaceConfig.width = surfaceWidth; surfaceConfig.height = surfaceHeight; @@ -263,9 +263,6 @@ int main(int argc, const char* argv[]) // === Create the texture === - // unsigned pixelDataSize = 256 * 256 * 4; - // std::vector pixelData(pixelDataSize, 100); - cv::Mat image = cv::imread(std::string(SL_PROJECT_ROOT) + "/data/images/textures/brickwall0512_C.jpg"); cv::cvtColor(image, image, cv::COLOR_BGR2RGBA); From 6fb69e03e7f748f121b6dd492986a4ebea145847 Mon Sep 17 00:00:00 2001 From: Marino von Wattenwyl Date: Mon, 6 Nov 2023 11:36:16 +0100 Subject: [PATCH 17/31] [WebGPU] Add 3d cube to demo --- apps/webgpu/CMakeLists.txt | 2 +- apps/webgpu/main.cpp | 335 ++++++++++++++++++++++++++++++------- 2 files changed, 272 insertions(+), 65 deletions(-) diff --git a/apps/webgpu/CMakeLists.txt b/apps/webgpu/CMakeLists.txt index 373f3bec..0c747f93 100644 --- a/apps/webgpu/CMakeLists.txt +++ b/apps/webgpu/CMakeLists.txt @@ -56,7 +56,7 @@ elseif (SYSTEM_NAME_UPPER MATCHES "DARWIN") endif () add_executable(webgpu-demo main.cpp) -target_link_libraries(webgpu-demo PRIVATE wgpu sl_cv ${glfw_LIBS}) +target_link_libraries(webgpu-demo PRIVATE wgpu sl_cv sl_math ${glfw_LIBS}) if (SYSTEM_NAME_UPPER MATCHES "LINUX") target_link_libraries(webgpu-demo PRIVATE "X11") diff --git a/apps/webgpu/main.cpp b/apps/webgpu/main.cpp index ccad5570..0a57aef4 100644 --- a/apps/webgpu/main.cpp +++ b/apps/webgpu/main.cpp @@ -23,11 +23,13 @@ #include #include #include +#include #include #include #include #include +#include #define WEBGPU_DEMO_LOG(msg) std::cout << (msg) << std::endl @@ -38,6 +40,15 @@ std::exit(1); \ } +struct alignas(16) ShaderUniformData +{ + float projectionMatrix[16]; + float viewMatrix[16]; + float modelMatrix[16]; +}; + +static_assert(sizeof(ShaderUniformData) % 16 == 0, "uniform data size must be a multiple of 16"); + void handleAdapterRequest(WGPURequestAdapterStatus status, WGPUAdapter adapter, char const* message, @@ -104,18 +115,19 @@ int main(int argc, const char* argv[]) // which is how WebGPU prevents code from working on one machine and not on another. WGPURequiredLimits requiredLimits = {}; - requiredLimits.limits.maxVertexAttributes = 1u; + requiredLimits.limits.maxVertexAttributes = 2u; requiredLimits.limits.maxVertexBuffers = 1u; - requiredLimits.limits.maxBufferSize = 4ull * 2ull * sizeof(float); - requiredLimits.limits.maxVertexBufferArrayStride = 2u * sizeof(float); + requiredLimits.limits.maxBufferSize = 1024ull; + requiredLimits.limits.maxVertexBufferArrayStride = 5u * sizeof(float); requiredLimits.limits.maxInterStageShaderComponents = 2u; - requiredLimits.limits.maxBindGroups = 2u; - requiredLimits.limits.maxBindingsPerBindGroup = 2u; + requiredLimits.limits.maxBindGroups = 1u; + requiredLimits.limits.maxBindingsPerBindGroup = 3u; requiredLimits.limits.maxUniformBuffersPerShaderStage = 1u; - requiredLimits.limits.maxUniformBufferBindingSize = 16ull * 4ull; + requiredLimits.limits.maxUniformBufferBindingSize = 512ull; requiredLimits.limits.maxSampledTexturesPerShaderStage = 1u; - requiredLimits.limits.maxTextureDimension1D = 2048; - requiredLimits.limits.maxTextureDimension2D = 2048; + requiredLimits.limits.maxSamplersPerShaderStage = 1u; + requiredLimits.limits.maxTextureDimension1D = 4096; + requiredLimits.limits.maxTextureDimension2D = 4096; requiredLimits.limits.maxTextureArrayLayers = 1; requiredLimits.limits.minStorageBufferOffsetAlignment = adapterLimits.limits.minStorageBufferOffsetAlignment; requiredLimits.limits.minUniformBufferOffsetAlignment = adapterLimits.limits.minUniformBufferOffsetAlignment; @@ -201,14 +213,57 @@ int main(int argc, const char* argv[]) // clang-format off std::vector vertexData = { - -0.5, 0.5, // top-left corner - -0.5, -0.5, // bottom-left corner - 0.5, 0.5, // top-right corner - 0.5, -0.5, // bottom-right corner + // left + -0.5, 0.5, -0.5, 0.0f, 0.0f, + -0.5, -0.5, -0.5, 0.0f, 1.0f, + -0.5, 0.5, 0.5, 1.0f, 0.0f, + -0.5, 0.5, 0.5, 1.0f, 0.0f, + -0.5, -0.5, -0.5, 0.0f, 1.0f, + -0.5, -0.5, 0.5, 1.0f, 1.0f, + + // right + 0.5, 0.5, 0.5, 0.0f, 0.0f, + 0.5, -0.5, 0.5, 0.0f, 1.0f, + 0.5, 0.5, -0.5, 1.0f, 0.0f, + 0.5, 0.5, -0.5, 1.0f, 0.0f, + 0.5, -0.5, 0.5, 0.0f, 1.0f, + 0.5, -0.5, -0.5, 1.0f, 1.0f, + + // bottom + -0.5, -0.5, 0.5, 0.0f, 0.0f, + -0.5, -0.5, -0.5, 0.0f, 1.0f, + 0.5, -0.5, 0.5, 1.0f, 0.0f, + 0.5, -0.5, 0.5, 1.0f, 0.0f, + -0.5, -0.5, -0.5, 0.0f, 1.0f, + 0.5, -0.5, -0.5, 1.0f, 1.0f, + + // top + -0.5, 0.5, -0.5, 0.0f, 0.0f, + -0.5, 0.5, 0.5, 0.0f, 1.0f, + 0.5, 0.5, -0.5, 1.0f, 0.0f, + 0.5, 0.5, -0.5, 1.0f, 0.0f, + -0.5, 0.5, 0.5, 0.0f, 1.0f, + 0.5, 0.5, 0.5, 1.0f, 1.0f, + + // back + 0.5, 0.5, -0.5, 0.0f, 0.0f, + 0.5, -0.5, -0.5, 0.0f, 1.0f, + -0.5, 0.5, -0.5, 1.0f, 0.0f, + -0.5, 0.5, -0.5, 1.0f, 0.0f, + 0.5, -0.5, -0.5, 0.0f, 1.0f, + -0.5, -0.5, -0.5, 1.0f, 1.0f, + + // front + -0.5, 0.5, 0.5, 0.0f, 0.0f, + -0.5, -0.5, 0.5, 0.0f, 1.0f, + 0.5, 0.5, 0.5, 1.0f, 0.0f, + 0.5, 0.5, 0.5, 1.0f, 0.0f, + -0.5, -0.5, 0.5, 0.0f, 1.0f, + 0.5, -0.5, 0.5, 1.0f, 1.0f, }; // clang-format on - unsigned vertexCount = vertexData.size() / 2; + unsigned vertexCount = vertexData.size() / 5; unsigned vertexDataSize = vertexData.size() * sizeof(float); WGPUBufferDescriptor vertexBufferDesc = {}; @@ -225,13 +280,9 @@ int main(int argc, const char* argv[]) // === Create the index buffer === - // clang-format off - std::vector indexData = - { - 0, 1, 2, - 2, 1, 3, - }; - // clang-format on + std::vector indexData = {}; + for (std::uint16_t index = 0; index < 36; index++) + indexData.push_back(index); unsigned indexCount = indexData.size(); unsigned indexDataSize = indexData.size() * sizeof(std::uint16_t); @@ -251,23 +302,56 @@ int main(int argc, const char* argv[]) WGPUBufferDescriptor uniformBufferDesc = {}; uniformBufferDesc.label = "Demo Uniform Buffer"; - uniformBufferDesc.size = sizeof(float); + uniformBufferDesc.size = sizeof(ShaderUniformData); uniformBufferDesc.usage = WGPUBufferUsage_CopyDst | WGPUBufferUsage_Uniform; WGPUBuffer uniformBuffer = wgpuDeviceCreateBuffer(device, &uniformBufferDesc); WEBGPU_DEMO_CHECK(indexBuffer, "[WebGPU] Failed to create uniform buffer"); WEBGPU_DEMO_LOG("[WebGPU] Uniform buffer created"); - float uniformBufferData = 0.0f; - wgpuQueueWriteBuffer(queue, uniformBuffer, 0, &uniformBufferData, sizeof(float)); + ShaderUniformData uniformData = {}; + wgpuQueueWriteBuffer(queue, uniformBuffer, 0, &uniformData, sizeof(ShaderUniformData)); + + // === Create the depth texture === + + WGPUTextureFormat depthTextureFormat = WGPUTextureFormat_Depth24Plus; + + WGPUTextureDescriptor depthTextureDesc = {}; + depthTextureDesc.dimension = WGPUTextureDimension_2D; + depthTextureDesc.format = depthTextureFormat; + depthTextureDesc.mipLevelCount = 1; + depthTextureDesc.sampleCount = 1; + depthTextureDesc.size.width = surfaceWidth; + depthTextureDesc.size.height = surfaceHeight; + depthTextureDesc.size.depthOrArrayLayers = 1; + depthTextureDesc.usage = WGPUTextureUsage_RenderAttachment; + depthTextureDesc.viewFormatCount = 1; + depthTextureDesc.viewFormats = &depthTextureFormat; + + WGPUTexture depthTexture = wgpuDeviceCreateTexture(device, &depthTextureDesc); + WEBGPU_DEMO_CHECK(depthTexture, "[WebGPU] Failed to create depth texture"); + WEBGPU_DEMO_LOG("[WebGPU] Depth texture created"); + + WGPUTextureViewDescriptor depthTextureViewDesc = {}; + depthTextureViewDesc.aspect = WGPUTextureAspect_DepthOnly; + depthTextureViewDesc.baseArrayLayer = 0; + depthTextureViewDesc.arrayLayerCount = 1; + depthTextureViewDesc.baseMipLevel = 0; + depthTextureViewDesc.mipLevelCount = 1; + depthTextureViewDesc.dimension = WGPUTextureViewDimension_2D; + depthTextureViewDesc.format = depthTextureFormat; + + WGPUTextureView depthTextureView = wgpuTextureCreateView(depthTexture, &depthTextureViewDesc); + WEBGPU_DEMO_CHECK(depthTextureView, "[WebGPU] Failed to create depth texture view"); + WEBGPU_DEMO_LOG("[WebGPU] Depth texture view created"); // === Create the texture === cv::Mat image = cv::imread(std::string(SL_PROJECT_ROOT) + "/data/images/textures/brickwall0512_C.jpg"); cv::cvtColor(image, image, cv::COLOR_BGR2RGBA); - unsigned imageWidth = image.cols; - unsigned imageHeight = image.rows; + unsigned imageWidth = image.cols; + unsigned imageHeight = image.rows; unsigned pixelDataSize = 4 * imageWidth * imageHeight; WGPUTextureDescriptor textureDesc = {}; @@ -317,33 +401,67 @@ int main(int argc, const char* argv[]) WEBGPU_DEMO_CHECK(textureView, "[WebGPU] Failed to create texture view"); WEBGPU_DEMO_LOG("[WebGPU] Texture view created"); + // === Create the texture sampler === + // The sampler is used to look up values of the texture in the shader. + // We could look up values directly without a sampler, but with a sampler we + // get access to features like interpolation and mipmapping. + + WGPUSamplerDescriptor samplerDesc = {}; + samplerDesc.addressModeU = WGPUAddressMode_ClampToEdge; + samplerDesc.addressModeV = WGPUAddressMode_ClampToEdge; + samplerDesc.addressModeW = WGPUAddressMode_ClampToEdge; + samplerDesc.magFilter = WGPUFilterMode_Linear; + samplerDesc.minFilter = WGPUFilterMode_Linear; + samplerDesc.mipmapFilter = WGPUMipmapFilterMode_Linear; + samplerDesc.lodMinClamp = 0.0f; + samplerDesc.lodMaxClamp = 1.0f; + samplerDesc.compare = WGPUCompareFunction_Undefined; + samplerDesc.maxAnisotropy = 1; + + WGPUSampler sampler = wgpuDeviceCreateSampler(device, &samplerDesc); + WEBGPU_DEMO_CHECK(sampler, "[WebGPU] Failed to create sampler"); + WEBGPU_DEMO_LOG("[WebGPU] Sampler created"); + // === Create the shader module === // Create a shader module for use in our render pipeline. // Shaders are written in a WebGPU-specific language called WGSL (WebGPU shader language). // Shader modules can be used in multiple pipeline stages by specifying different entry points. const char* shaderSource = R"( - @group(0) @binding(0) var u_time: f32; - @group(0) @binding(1) var texture: texture_2d; + struct VertexInput { + @location(0) position: vec3f, + @location(1) uvs: vec2f, + }; + + struct Uniforms { + projectionMatrix: mat4x4f, + viewMatrix: mat4x4f, + modelMatrix: mat4x4f, + }; struct VertexOutput { @builtin(position) position: vec4f, - @location(0) texture_coords: vec2f, + @location(0) uvs: vec2f, } + + @group(0) @binding(0) var uniforms: Uniforms; + @group(0) @binding(1) var texture: texture_2d; + @group(0) @binding(2) var textureSampler: sampler; @vertex - fn vs_main(@location(0) in_vertex_position: vec2f) -> VertexOutput { - var offset = vec2(0.5 * sin(u_time), 0.5 * cos(u_time)); - + fn vs_main(in: VertexInput) -> VertexOutput { + var localPos = vec4f(in.position, 1.0); + var worldPos = uniforms.modelMatrix * localPos; + var out: VertexOutput; - out.position = vec4f(in_vertex_position + offset, 0.0, 1.0); - out.texture_coords = in_vertex_position + vec2(0.5, 0.5); + out.position = uniforms.projectionMatrix * uniforms.viewMatrix * worldPos; + out.uvs = in.uvs; return out; } @fragment fn fs_main(in: VertexOutput) -> @location(0) vec4f { - return textureLoad(texture, vec2i(i32(512.0 * in.texture_coords.x), i32(512.0 * in.texture_coords.y)), 0).rgba; + return textureSample(texture, textureSampler, in.uvs).rgba; } )"; @@ -363,20 +481,28 @@ int main(int argc, const char* argv[]) // Bind groups contain binding layouts that describe how uniforms and textures are passed to shaders. // Entry for the uniform - WGPUBindGroupLayoutEntry uniformLayout = {}; - uniformLayout.binding = 0; - uniformLayout.visibility = WGPUShaderStage_Vertex; - uniformLayout.buffer.type = WGPUBufferBindingType_Uniform; - uniformLayout.buffer.minBindingSize = sizeof(float); + WGPUBindGroupLayoutEntry uniformBindLayout = {}; + uniformBindLayout.binding = 0; + uniformBindLayout.visibility = WGPUShaderStage_Vertex; + uniformBindLayout.buffer.type = WGPUBufferBindingType_Uniform; + uniformBindLayout.buffer.minBindingSize = sizeof(ShaderUniformData); // Entry for the texture - WGPUBindGroupLayoutEntry textureLayout = {}; - textureLayout.binding = 1; - textureLayout.visibility = WGPUShaderStage_Fragment; - textureLayout.texture.sampleType = WGPUTextureSampleType_Float; - textureLayout.texture.viewDimension = WGPUTextureViewDimension_2D; - - std::vector bindGroupLayoutEntries = {uniformLayout, textureLayout}; + WGPUBindGroupLayoutEntry textureBindLayout = {}; + textureBindLayout.binding = 1; + textureBindLayout.visibility = WGPUShaderStage_Fragment; + textureBindLayout.texture.sampleType = WGPUTextureSampleType_Float; + textureBindLayout.texture.viewDimension = WGPUTextureViewDimension_2D; + + // Entry for the sampler + WGPUBindGroupLayoutEntry samplerBindLayout = {}; + samplerBindLayout.binding = 2; + samplerBindLayout.visibility = WGPUShaderStage_Fragment; + samplerBindLayout.sampler.type = WGPUSamplerBindingType_Filtering; + + std::vector bindGroupLayoutEntries = {uniformBindLayout, + textureBindLayout, + samplerBindLayout}; WGPUBindGroupLayoutDescriptor bindGroupLayoutDesc = {}; bindGroupLayoutDesc.label = "Demo Bind Group Layout"; @@ -390,16 +516,22 @@ int main(int argc, const char* argv[]) // The bind group actually binds the buffer to uniforms and textures. WGPUBindGroupEntry uniformBinding = {}; - uniformBinding.binding = 0; - uniformBinding.buffer = uniformBuffer; - uniformBinding.offset = 0; - uniformBinding.size = sizeof(float); + uniformBinding.binding = 0; + uniformBinding.buffer = uniformBuffer; + uniformBinding.offset = 0; + uniformBinding.size = sizeof(ShaderUniformData); WGPUBindGroupEntry textureBinding = {}; - textureBinding.binding = 1; - textureBinding.textureView = textureView; + textureBinding.binding = 1; + textureBinding.textureView = textureView; + + WGPUBindGroupEntry samplerBinding = {}; + samplerBinding.binding = 2; + samplerBinding.sampler = sampler; - std::vector bindGroupEntries = {uniformBinding, textureBinding}; + std::vector bindGroupEntries = {uniformBinding, + textureBinding, + samplerBinding}; WGPUBindGroupDescriptor bindGroupDesc = {}; bindGroupDesc.layout = bindGroupLayout; @@ -424,18 +556,25 @@ int main(int argc, const char* argv[]) // The render pipeline specifies the configuration for the fixed-function stages as well as // the shaders for the programmable stages of the hardware pipeline. - // Description of the vertex attribute for the vertex buffer layout - WGPUVertexAttribute vertexAttribute = {}; - vertexAttribute.format = WGPUVertexFormat_Float32x2; - vertexAttribute.offset = 0; - vertexAttribute.shaderLocation = 0; + // Description of the vertex attributes for the vertex buffer layout + WGPUVertexAttribute positionAttribute = {}; + positionAttribute.format = WGPUVertexFormat_Float32x3; + positionAttribute.offset = 0; + positionAttribute.shaderLocation = 0; + + WGPUVertexAttribute uvsAttribute = {}; + uvsAttribute.format = WGPUVertexFormat_Float32x2; + uvsAttribute.offset = 3 * sizeof(float); + uvsAttribute.shaderLocation = 1; + + std::vector vertexAttributes = {positionAttribute, uvsAttribute}; // Description of the vertex buffer layout for the vertex shader stage WGPUVertexBufferLayout vertexBufferLayout = {}; - vertexBufferLayout.arrayStride = 2ull * sizeof(float); + vertexBufferLayout.arrayStride = 5ull * sizeof(float); vertexBufferLayout.stepMode = WGPUVertexStepMode_Vertex; - vertexBufferLayout.attributeCount = 1; - vertexBufferLayout.attributes = &vertexAttribute; + vertexBufferLayout.attributeCount = vertexAttributes.size(); + vertexBufferLayout.attributes = vertexAttributes.data(); // Configuration for the vertex shader stage WGPUVertexState vertexState = {}; @@ -451,6 +590,22 @@ int main(int argc, const char* argv[]) primitiveState.frontFace = WGPUFrontFace_CCW; primitiveState.cullMode = WGPUCullMode_None; + // Configuration for depth testing and stencil buffer + WGPUDepthStencilState depthStencilState = {}; + depthStencilState.format = depthTextureFormat; + depthStencilState.depthWriteEnabled = true; + depthStencilState.depthCompare = WGPUCompareFunction_Less; + depthStencilState.stencilReadMask = 0; + depthStencilState.stencilWriteMask = 0; + depthStencilState.stencilFront.compare = WGPUCompareFunction_Always; + depthStencilState.stencilFront.failOp = WGPUStencilOperation_Keep; + depthStencilState.stencilFront.depthFailOp = WGPUStencilOperation_Keep; + depthStencilState.stencilFront.passOp = WGPUStencilOperation_Keep; + depthStencilState.stencilBack.compare = WGPUCompareFunction_Always; + depthStencilState.stencilBack.failOp = WGPUStencilOperation_Keep; + depthStencilState.stencilBack.depthFailOp = WGPUStencilOperation_Keep; + depthStencilState.stencilBack.passOp = WGPUStencilOperation_Keep; + // Configuration for multisampling WGPUMultisampleState multisampleState = {}; multisampleState.count = 1; @@ -472,6 +627,7 @@ int main(int argc, const char* argv[]) pipelineDesc.layout = pipelineLayout; pipelineDesc.vertex = vertexState; pipelineDesc.primitive = primitiveState; + pipelineDesc.depthStencil = &depthStencilState; pipelineDesc.multisample = multisampleState; pipelineDesc.fragment = &fragmentState; @@ -479,6 +635,8 @@ int main(int argc, const char* argv[]) WEBGPU_DEMO_CHECK(pipeline, "[WebGPU] Failed to create render pipeline"); WEBGPU_DEMO_LOG("[WebGPU] Render pipeline created"); + float rotation = 0.0f; + // === Render loop === while (!glfwWindowShouldClose(window)) @@ -515,6 +673,23 @@ int main(int argc, const char* argv[]) surfaceConfig.width = surfaceWidth; surfaceConfig.height = surfaceHeight; wgpuSurfaceConfigure(surface, &surfaceConfig); + + // Recreate the depth texture. + + wgpuTextureViewRelease(textureView); + wgpuTextureDestroy(depthTexture); + wgpuTextureRelease(depthTexture); + + depthTextureDesc.size.width = surfaceWidth; + depthTextureDesc.size.height = surfaceHeight; + + depthTexture = wgpuDeviceCreateTexture(device, &depthTextureDesc); + WEBGPU_DEMO_CHECK(depthTexture, "[WebGPU] Failed to re-create depth texture"); + WEBGPU_DEMO_LOG("[WebGPU] Depth texture re-created"); + + depthTextureView = wgpuTextureCreateView(depthTexture, &depthTextureViewDesc); + WEBGPU_DEMO_CHECK(depthTextureView, "[WebGPU] Failed to re-create depth texture view"); + WEBGPU_DEMO_LOG("[WebGPU] Depth texture view re-created"); } // Skip this frame. @@ -534,9 +709,24 @@ int main(int argc, const char* argv[]) // Create a view into the texture to specify where and how to modify the texture. WGPUTextureView view = wgpuTextureCreateView(surfaceTexture.texture, nullptr); - // === Update the uniform === - float time = static_cast(glfwGetTime()); - wgpuQueueWriteBuffer(queue, uniformBuffer, 0, &time, sizeof(float)); + // === Prepare uniform data === + float aspectRatio = static_cast(surfaceWidth) / static_cast(surfaceHeight); + + SLMat4f projectionMatrix; + projectionMatrix.perspective(70.0f, aspectRatio, 0.1, 1000.0f); + + SLMat4f viewMatrix; + viewMatrix.translate(0.0f, 0.0f, -2.0f); + + SLMat4f modelMatrix; + modelMatrix.rotation(90.0f * static_cast(glfwGetTime()), SLVec3f::AXISY); + + // === Update uniforms === + ShaderUniformData uniformData = {}; + std::memcpy(uniformData.projectionMatrix, projectionMatrix.m(), sizeof(uniformData.projectionMatrix)); + std::memcpy(uniformData.viewMatrix, viewMatrix.m(), sizeof(uniformData.viewMatrix)); + std::memcpy(uniformData.modelMatrix, modelMatrix.m(), sizeof(uniformData.modelMatrix)); + wgpuQueueWriteBuffer(queue, uniformBuffer, 0, &uniformData, sizeof(ShaderUniformData)); // === Create a WebGPU command encoder === // The encoder encodes the commands for the GPU into a command buffer. @@ -551,6 +741,7 @@ int main(int argc, const char* argv[]) // The render pass specifies what attachments to use while rendering. // A color attachment specifies what view to render into and what to do with the texture before and after // rendering. We clear the texture before rendering and store the results after rendering. + // The depth attachment specifies what depth texture to use. WGPURenderPassColorAttachment colorAttachment = {}; colorAttachment.view = view; @@ -561,10 +752,22 @@ int main(int argc, const char* argv[]) colorAttachment.clearValue.b = 0.2; colorAttachment.clearValue.a = 1.0; + WGPURenderPassDepthStencilAttachment depthStencilAttachment = {}; + depthStencilAttachment.view = depthTextureView; + depthStencilAttachment.depthLoadOp = WGPULoadOp_Clear; + depthStencilAttachment.depthStoreOp = WGPUStoreOp_Store; + depthStencilAttachment.depthClearValue = 1.0f; + depthStencilAttachment.depthReadOnly = false; + depthStencilAttachment.stencilLoadOp = WGPULoadOp_Clear; + depthStencilAttachment.stencilStoreOp = WGPUStoreOp_Store; + depthStencilAttachment.stencilClearValue = 0.0f; + depthStencilAttachment.stencilReadOnly = true; + WGPURenderPassDescriptor renderPassDesc = {}; renderPassDesc.label = "Demo Render Pass"; renderPassDesc.colorAttachmentCount = 1; renderPassDesc.colorAttachments = &colorAttachment; + renderPassDesc.depthStencilAttachment = &depthStencilAttachment; // === Encode the commands === // The commands to begin a render pass, bind a pipeline, draw the triangle and end the render pass @@ -611,9 +814,13 @@ int main(int argc, const char* argv[]) wgpuBindGroupRelease(bindGroup); wgpuBindGroupLayoutRelease(bindGroupLayout); wgpuShaderModuleRelease(shaderModule); + wgpuSamplerRelease(sampler); wgpuTextureViewRelease(textureView); wgpuTextureDestroy(texture); wgpuTextureRelease(texture); + wgpuTextureViewRelease(depthTextureView); + wgpuTextureDestroy(depthTexture); + wgpuTextureRelease(depthTexture); wgpuBufferDestroy(uniformBuffer); wgpuBufferRelease(uniformBuffer); wgpuBufferDestroy(indexBuffer); From 7aa30d574dcbff1bd35524c4154ba340b0e18128 Mon Sep 17 00:00:00 2001 From: Marino von Wattenwyl Date: Mon, 6 Nov 2023 13:11:40 +0100 Subject: [PATCH 18/31] [WebGPU] Add mipmapping to demo --- apps/webgpu/main.cpp | 87 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 77 insertions(+), 10 deletions(-) diff --git a/apps/webgpu/main.cpp b/apps/webgpu/main.cpp index 0a57aef4..3173eeeb 100644 --- a/apps/webgpu/main.cpp +++ b/apps/webgpu/main.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include @@ -362,7 +363,7 @@ int main(int argc, const char* argv[]) textureDesc.size.height = imageHeight; textureDesc.size.depthOrArrayLayers = 1; textureDesc.format = WGPUTextureFormat_RGBA8UnormSrgb; - textureDesc.mipLevelCount = 1; + textureDesc.mipLevelCount = 4; textureDesc.sampleCount = 1; WGPUTexture texture = wgpuDeviceCreateTexture(device, &textureDesc); @@ -384,8 +385,51 @@ int main(int argc, const char* argv[]) pixelDataLayout.bytesPerRow = 4 * textureDesc.size.width; pixelDataLayout.rowsPerImage = textureDesc.size.height; - // Upload the data to the GPU. - wgpuQueueWriteTexture(queue, &destination, image.data, pixelDataSize, &pixelDataLayout, &textureDesc.size); + // Generate mip levels. + + WGPUExtent3D mipLevelSize; + mipLevelSize.width = textureDesc.size.width; + mipLevelSize.height = textureDesc.size.height; + mipLevelSize.depthOrArrayLayers = 1; + + for (unsigned mipLevel = 0; mipLevel < textureDesc.mipLevelCount; mipLevel++) + { + // === Test colors === + // + // std::uint64_t mipLevelColors[] = + // { + // 0xFF0000FF, + // 0xFFFF00FF, + // 0x00FF00FF, + // 0x0000FFFF + // }; + // + // for (unsigned y = 0; y < mipLevelSize.height; y++) + // { + // for (unsigned x = 0; x < mipLevelSize.width; x++) + // { + // unsigned pixelIndex = x + y * mipLevelSize.width; + // std::memcpy(&mipLevelData[4ull * pixelIndex], &mipLevelColors[mipLevel], 4); + // } + // } + + cv::Mat mipLevelImage; + cv::Size cvSize(static_cast(mipLevelSize.width), static_cast(mipLevelSize.height)); + cv::resize(image, mipLevelImage, cvSize); + + std::size_t mipLevelBytes = 4ull * mipLevelSize.width * mipLevelSize.height; + + destination.mipLevel = mipLevel; + pixelDataLayout.bytesPerRow = 4 * mipLevelSize.width; + pixelDataLayout.rowsPerImage = mipLevelSize.height; + + // Upload the data to the GPU. + wgpuQueueWriteTexture(queue, &destination, mipLevelImage.data, mipLevelBytes, &pixelDataLayout, &mipLevelSize); + + // Scale the image down for the next mip level. + mipLevelSize.width /= 2; + mipLevelSize.height /= 2; + } // === Create a texture view into the texture === WGPUTextureViewDescriptor textureViewDesc = {}; @@ -393,7 +437,7 @@ int main(int argc, const char* argv[]) textureViewDesc.baseArrayLayer = 0; textureViewDesc.arrayLayerCount = 1; textureViewDesc.baseMipLevel = 0; - textureViewDesc.mipLevelCount = 1; + textureViewDesc.mipLevelCount = textureDesc.mipLevelCount; textureViewDesc.dimension = WGPUTextureViewDimension_2D; textureViewDesc.format = textureDesc.format; @@ -414,9 +458,9 @@ int main(int argc, const char* argv[]) samplerDesc.minFilter = WGPUFilterMode_Linear; samplerDesc.mipmapFilter = WGPUMipmapFilterMode_Linear; samplerDesc.lodMinClamp = 0.0f; - samplerDesc.lodMaxClamp = 1.0f; + samplerDesc.lodMaxClamp = static_cast(textureDesc.mipLevelCount); samplerDesc.compare = WGPUCompareFunction_Undefined; - samplerDesc.maxAnisotropy = 1; + samplerDesc.maxAnisotropy = 1.0f; WGPUSampler sampler = wgpuDeviceCreateSampler(device, &samplerDesc); WEBGPU_DEMO_CHECK(sampler, "[WebGPU] Failed to create sampler"); @@ -635,12 +679,38 @@ int main(int argc, const char* argv[]) WEBGPU_DEMO_CHECK(pipeline, "[WebGPU] Failed to create render pipeline"); WEBGPU_DEMO_LOG("[WebGPU] Render pipeline created"); - float rotation = 0.0f; + SLMat4f modelMatrix; + + double lastCursorX = 0.0f; + double lastCursorY = 0.0f; // === Render loop === while (!glfwWindowShouldClose(window)) { + // === Update cube model matrix === + + double cursorX; + double cursorY; + glfwGetCursorPos(window, &cursorX, &cursorY); + + SLMat4f rotationX; + SLMat4f rotationY; + + if (glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_1) == GLFW_PRESS) + { + float deltaRotX = 0.5f * static_cast(cursorY - lastCursorY); + float deltaRotY = 0.5f * static_cast(cursorX - lastCursorX); + + rotationX.rotate(deltaRotX, SLVec3f::AXISX); + rotationY.rotate(deltaRotY, SLVec3f::AXISY); + } + + modelMatrix = rotationY * rotationX * modelMatrix; + + lastCursorX = cursorX; + lastCursorY = cursorY; + // === Create a WebGPU texture view === // The texture view is where we render our image into. @@ -718,9 +788,6 @@ int main(int argc, const char* argv[]) SLMat4f viewMatrix; viewMatrix.translate(0.0f, 0.0f, -2.0f); - SLMat4f modelMatrix; - modelMatrix.rotation(90.0f * static_cast(glfwGetTime()), SLVec3f::AXISY); - // === Update uniforms === ShaderUniformData uniformData = {}; std::memcpy(uniformData.projectionMatrix, projectionMatrix.m(), sizeof(uniformData.projectionMatrix)); From e6ad617ba7abce5a73c27bdbee96e91e017a378c Mon Sep 17 00:00:00 2001 From: Marino von Wattenwyl Date: Mon, 6 Nov 2023 13:14:05 +0100 Subject: [PATCH 19/31] [WebGPU] Re-configure suboptimal surfaces in demo --- apps/webgpu/main.cpp | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/apps/webgpu/main.cpp b/apps/webgpu/main.cpp index 3173eeeb..4939f53e 100644 --- a/apps/webgpu/main.cpp +++ b/apps/webgpu/main.cpp @@ -725,7 +725,20 @@ int main(int argc, const char* argv[]) { case WGPUSurfaceGetCurrentTextureStatus_Success: // Everything is ok. - // TODO: check for a suboptimal texture and re-configure it if needed. + + // Check for a suboptimal texture and re-configure it if needed. + if (surfaceTexture.suboptimal) + { + WEBGPU_DEMO_LOG("[WebGPU] Re-configuring currently suboptimal surface"); + surfaceConfig.width = surfaceWidth; + surfaceConfig.height = surfaceHeight; + wgpuSurfaceConfigure(surface, &surfaceConfig); + + // Skip this frame. + glfwPollEvents(); + continue; + } + break; case WGPUSurfaceGetCurrentTextureStatus_Timeout: From 2f3e6c40b9d462b362f68e19671c69cd55fcbfca Mon Sep 17 00:00:00 2001 From: Marino von Wattenwyl Date: Mon, 6 Nov 2023 15:14:33 +0100 Subject: [PATCH 20/31] [WebGPU] Use new Objective-C runtime declarations in demo --- apps/webgpu/main.cpp | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/apps/webgpu/main.cpp b/apps/webgpu/main.cpp index 4939f53e..2b63ec6b 100644 --- a/apps/webgpu/main.cpp +++ b/apps/webgpu/main.cpp @@ -11,7 +11,6 @@ # define GLFW_EXPOSE_NATIVE_COCOA # define GLFW_NATIVE_INCLUDE_NONE -# define OBJC_OLD_DISPATCH_PROTOTYPES 1 # include # include # include @@ -165,12 +164,18 @@ int main(int argc, const char* argv[]) #elif defined(SYSTEM_DARWIN) id cocoaWindow = glfwGetCocoaWindow(window); - id contentView = objc_msgSend(cocoaWindow, sel_registerName("contentView")); - objc_msgSend(contentView, sel_getUid("setWantsLayer:"), 1); + // We call the Objective-C runtime directly to get the Metal surface. + + // We need to cast 'objc_msgSend' to appropiate function pointer types before calling them. + auto* sendMessageReturnId = (id(*)(id, ...))objc_msgSend; + auto* sendMessageReturnNothing = (void (*)(id, ...))objc_msgSend; + + id contentView = sendMessageReturnId(cocoaWindow, sel_getUid("contentView")); + sendMessageReturnNothing(contentView, sel_getUid("setWantsLayer:"), 1); objc_class* metalLayerClass = objc_getClass("CAMetalLayer"); - id metalLayer = objc_msgSend((id)metalLayerClass, sel_getUid("layer")); - objc_msgSend(contentView, sel_registerName("setLayer:"), metalLayer); + id metalLayer = sendMessageReturnId((id)metalLayerClass, sel_getUid("layer")); + sendMessageReturnNothing(contentView, sel_registerName("setLayer:"), metalLayer); WGPUSurfaceDescriptorFromMetalLayer nativeSurfaceDesc = {}; nativeSurfaceDesc.chain.sType = WGPUSType_SurfaceDescriptorFromMetalLayer; @@ -388,8 +393,8 @@ int main(int argc, const char* argv[]) // Generate mip levels. WGPUExtent3D mipLevelSize; - mipLevelSize.width = textureDesc.size.width; - mipLevelSize.height = textureDesc.size.height; + mipLevelSize.width = textureDesc.size.width; + mipLevelSize.height = textureDesc.size.height; mipLevelSize.depthOrArrayLayers = 1; for (unsigned mipLevel = 0; mipLevel < textureDesc.mipLevelCount; mipLevel++) @@ -403,17 +408,17 @@ int main(int argc, const char* argv[]) // 0x00FF00FF, // 0x0000FFFF // }; - // + // // for (unsigned y = 0; y < mipLevelSize.height; y++) // { // for (unsigned x = 0; x < mipLevelSize.width; x++) // { // unsigned pixelIndex = x + y * mipLevelSize.width; // std::memcpy(&mipLevelData[4ull * pixelIndex], &mipLevelColors[mipLevel], 4); - // } + // } // } - cv::Mat mipLevelImage; + cv::Mat mipLevelImage; cv::Size cvSize(static_cast(mipLevelSize.width), static_cast(mipLevelSize.height)); cv::resize(image, mipLevelImage, cvSize); @@ -725,7 +730,7 @@ int main(int argc, const char* argv[]) { case WGPUSurfaceGetCurrentTextureStatus_Success: // Everything is ok. - + // Check for a suboptimal texture and re-configure it if needed. if (surfaceTexture.suboptimal) { From f35025373c1d8dad65fc386854d65070b368daf1 Mon Sep 17 00:00:00 2001 From: Marino von Wattenwyl Date: Mon, 6 Nov 2023 15:17:47 +0100 Subject: [PATCH 21/31] [WebGPU] Use FIFO present mode in demo --- apps/webgpu/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/webgpu/main.cpp b/apps/webgpu/main.cpp index 2b63ec6b..efc71100 100644 --- a/apps/webgpu/main.cpp +++ b/apps/webgpu/main.cpp @@ -206,7 +206,7 @@ int main(int argc, const char* argv[]) surfaceConfig.device = device; surfaceConfig.usage = WGPUTextureUsage_RenderAttachment; surfaceConfig.format = surfaceCapabilities.formats[0]; - surfaceConfig.presentMode = WGPUPresentMode_Mailbox; + surfaceConfig.presentMode = WGPUPresentMode_Fifo; surfaceConfig.alphaMode = surfaceCapabilities.alphaModes[0]; surfaceConfig.width = surfaceWidth; surfaceConfig.height = surfaceHeight; From f981ed4a78190c2413655a6c9ae29f5f7b311295 Mon Sep 17 00:00:00 2001 From: Marino von Wattenwyl Date: Mon, 6 Nov 2023 16:33:40 +0100 Subject: [PATCH 22/31] [WebGPU] Move Metal layer creation into Objective-C file --- apps/webgpu/CMakeLists.txt | 7 ++++++- apps/webgpu/main.cpp | 27 +++++---------------------- apps/webgpu/metal_layer.mm | 11 +++++++++++ 3 files changed, 22 insertions(+), 23 deletions(-) create mode 100644 apps/webgpu/metal_layer.mm diff --git a/apps/webgpu/CMakeLists.txt b/apps/webgpu/CMakeLists.txt index 0c747f93..fff48076 100644 --- a/apps/webgpu/CMakeLists.txt +++ b/apps/webgpu/CMakeLists.txt @@ -55,7 +55,12 @@ elseif (SYSTEM_NAME_UPPER MATCHES "DARWIN") INTERFACE_INCLUDE_DIRECTORIES "${WGPU_DIR}") endif () -add_executable(webgpu-demo main.cpp) +set(TARGET_SPECIFIC_SOURCES) +if (SYSTEM_NAME_UPPER MATCHES "DARWIN") + set(TARGET_SPECIFIC_SOURCES metal_layer.mm) +endif () + +add_executable(webgpu-demo main.cpp ${TARGET_SPECIFIC_SOURCES}) target_link_libraries(webgpu-demo PRIVATE wgpu sl_cv sl_math ${glfw_LIBS}) if (SYSTEM_NAME_UPPER MATCHES "LINUX") diff --git a/apps/webgpu/main.cpp b/apps/webgpu/main.cpp index efc71100..062c56e6 100644 --- a/apps/webgpu/main.cpp +++ b/apps/webgpu/main.cpp @@ -9,12 +9,6 @@ #elif defined(__APPLE__) # define SYSTEM_DARWIN # define GLFW_EXPOSE_NATIVE_COCOA -# define GLFW_NATIVE_INCLUDE_NONE - -# include -# include -# include -# include #endif #include @@ -49,6 +43,10 @@ struct alignas(16) ShaderUniformData static_assert(sizeof(ShaderUniformData) % 16 == 0, "uniform data size must be a multiple of 16"); +#ifdef SYSTEM_DARWIN +extern "C" void* createMetalLayer(void* window); +#endif + void handleAdapterRequest(WGPURequestAdapterStatus status, WGPUAdapter adapter, char const* message, @@ -162,24 +160,9 @@ int main(int argc, const char* argv[]) nativeSurfaceDesc.display = glfwGetX11Display(); nativeSurfaceDesc.window = glfwGetX11Window(window); #elif defined(SYSTEM_DARWIN) - id cocoaWindow = glfwGetCocoaWindow(window); - - // We call the Objective-C runtime directly to get the Metal surface. - - // We need to cast 'objc_msgSend' to appropiate function pointer types before calling them. - auto* sendMessageReturnId = (id(*)(id, ...))objc_msgSend; - auto* sendMessageReturnNothing = (void (*)(id, ...))objc_msgSend; - - id contentView = sendMessageReturnId(cocoaWindow, sel_getUid("contentView")); - sendMessageReturnNothing(contentView, sel_getUid("setWantsLayer:"), 1); - - objc_class* metalLayerClass = objc_getClass("CAMetalLayer"); - id metalLayer = sendMessageReturnId((id)metalLayerClass, sel_getUid("layer")); - sendMessageReturnNothing(contentView, sel_registerName("setLayer:"), metalLayer); - WGPUSurfaceDescriptorFromMetalLayer nativeSurfaceDesc = {}; nativeSurfaceDesc.chain.sType = WGPUSType_SurfaceDescriptorFromMetalLayer; - nativeSurfaceDesc.layer = metalLayer; + nativeSurfaceDesc.layer = createMetalLayer(glfwGetCocoaWindow(window)); #endif WGPUSurfaceDescriptor surfaceDesc = {}; diff --git a/apps/webgpu/metal_layer.mm b/apps/webgpu/metal_layer.mm new file mode 100644 index 00000000..a7a832ca --- /dev/null +++ b/apps/webgpu/metal_layer.mm @@ -0,0 +1,11 @@ +#include +#include + +extern "C" id createMetalLayer(NSWindow* window) +{ + [window.contentView setWantsLayer:YES]; + id metalLayer = [CAMetalLayer layer]; + [window.contentView setLayer:metalLayer]; + + return metalLayer; +} \ No newline at end of file From 2e2c6775e4c15f7d0b73e5dff62a5790ac33bfce Mon Sep 17 00:00:00 2001 From: Marcus Hudritsch Date: Mon, 6 Nov 2023 18:36:51 +0100 Subject: [PATCH 23/31] Cosmetics --- apps/webgpu/main.cpp | 92 ++++++++++++++++++++++---------------------- 1 file changed, 46 insertions(+), 46 deletions(-) diff --git a/apps/webgpu/main.cpp b/apps/webgpu/main.cpp index 062c56e6..f66e72ce 100644 --- a/apps/webgpu/main.cpp +++ b/apps/webgpu/main.cpp @@ -202,53 +202,53 @@ int main(int argc, const char* argv[]) // clang-format off std::vector vertexData = { - // left - -0.5, 0.5, -0.5, 0.0f, 0.0f, - -0.5, -0.5, -0.5, 0.0f, 1.0f, - -0.5, 0.5, 0.5, 1.0f, 0.0f, - -0.5, 0.5, 0.5, 1.0f, 0.0f, - -0.5, -0.5, -0.5, 0.0f, 1.0f, - -0.5, -0.5, 0.5, 1.0f, 1.0f, - - // right - 0.5, 0.5, 0.5, 0.0f, 0.0f, - 0.5, -0.5, 0.5, 0.0f, 1.0f, - 0.5, 0.5, -0.5, 1.0f, 0.0f, - 0.5, 0.5, -0.5, 1.0f, 0.0f, - 0.5, -0.5, 0.5, 0.0f, 1.0f, - 0.5, -0.5, -0.5, 1.0f, 1.0f, - - // bottom - -0.5, -0.5, 0.5, 0.0f, 0.0f, - -0.5, -0.5, -0.5, 0.0f, 1.0f, - 0.5, -0.5, 0.5, 1.0f, 0.0f, - 0.5, -0.5, 0.5, 1.0f, 0.0f, - -0.5, -0.5, -0.5, 0.0f, 1.0f, - 0.5, -0.5, -0.5, 1.0f, 1.0f, - - // top + // left -0.5, 0.5, -0.5, 0.0f, 0.0f, - -0.5, 0.5, 0.5, 0.0f, 1.0f, - 0.5, 0.5, -0.5, 1.0f, 0.0f, - 0.5, 0.5, -0.5, 1.0f, 0.0f, - -0.5, 0.5, 0.5, 0.0f, 1.0f, - 0.5, 0.5, 0.5, 1.0f, 1.0f, - - // back - 0.5, 0.5, -0.5, 0.0f, 0.0f, - 0.5, -0.5, -0.5, 0.0f, 1.0f, - -0.5, 0.5, -0.5, 1.0f, 0.0f, - -0.5, 0.5, -0.5, 1.0f, 0.0f, - 0.5, -0.5, -0.5, 0.0f, 1.0f, - -0.5, -0.5, -0.5, 1.0f, 1.0f, - - // front - -0.5, 0.5, 0.5, 0.0f, 0.0f, - -0.5, -0.5, 0.5, 0.0f, 1.0f, - 0.5, 0.5, 0.5, 1.0f, 0.0f, - 0.5, 0.5, 0.5, 1.0f, 0.0f, - -0.5, -0.5, 0.5, 0.0f, 1.0f, - 0.5, -0.5, 0.5, 1.0f, 1.0f, + -0.5, -0.5, -0.5, 0.0f, 1.0f, + -0.5, 0.5, 0.5, 1.0f, 0.0f, + -0.5, 0.5, 0.5, 1.0f, 0.0f, + -0.5, -0.5, -0.5, 0.0f, 1.0f, + -0.5, -0.5, 0.5, 1.0f, 1.0f, + + // right + 0.5, 0.5, 0.5, 0.0f, 0.0f, + 0.5, -0.5, 0.5, 0.0f, 1.0f, + 0.5, 0.5, -0.5, 1.0f, 0.0f, + 0.5, 0.5, -0.5, 1.0f, 0.0f, + 0.5, -0.5, 0.5, 0.0f, 1.0f, + 0.5, -0.5, -0.5, 1.0f, 1.0f, + + // bottom + -0.5, -0.5, 0.5, 0.0f, 0.0f, + -0.5, -0.5, -0.5, 0.0f, 1.0f, + 0.5, -0.5, 0.5, 1.0f, 0.0f, + 0.5, -0.5, 0.5, 1.0f, 0.0f, + -0.5, -0.5, -0.5, 0.0f, 1.0f, + 0.5, -0.5, -0.5, 1.0f, 1.0f, + + // top + -0.5, 0.5, -0.5, 0.0f, 0.0f, + -0.5, 0.5, 0.5, 0.0f, 1.0f, + 0.5, 0.5, -0.5, 1.0f, 0.0f, + 0.5, 0.5, -0.5, 1.0f, 0.0f, + -0.5, 0.5, 0.5, 0.0f, 1.0f, + 0.5, 0.5, 0.5, 1.0f, 1.0f, + + // back + 0.5, 0.5, -0.5, 0.0f, 0.0f, + 0.5, -0.5, -0.5, 0.0f, 1.0f, + -0.5, 0.5, -0.5, 1.0f, 0.0f, + -0.5, 0.5, -0.5, 1.0f, 0.0f, + 0.5, -0.5, -0.5, 0.0f, 1.0f, + -0.5, -0.5, -0.5, 1.0f, 1.0f, + + // front + -0.5, 0.5, 0.5, 0.0f, 0.0f, + -0.5, -0.5, 0.5, 0.0f, 1.0f, + 0.5, 0.5, 0.5, 1.0f, 0.0f, + 0.5, 0.5, 0.5, 1.0f, 0.0f, + -0.5, -0.5, 0.5, 0.0f, 1.0f, + 0.5, -0.5, 0.5, 1.0f, 1.0f, }; // clang-format on From cfa20a95ffedad9a58e26acb7760620492421cb3 Mon Sep 17 00:00:00 2001 From: Marcus Hudritsch Date: Mon, 6 Nov 2023 18:43:54 +0100 Subject: [PATCH 24/31] Fixed Warning --- apps/webgpu/main.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/webgpu/main.cpp b/apps/webgpu/main.cpp index f66e72ce..e87ddc83 100644 --- a/apps/webgpu/main.cpp +++ b/apps/webgpu/main.cpp @@ -7,7 +7,9 @@ # define SYSTEM_LINUX # define GLFW_EXPOSE_NATIVE_X11 #elif defined(__APPLE__) -# define SYSTEM_DARWIN +# ifndef SYSTEM_DARWIN +# define SYSTEM_DARWIN +# endif # define GLFW_EXPOSE_NATIVE_COCOA #endif From 61583d619de36d06785e3e39185c3206a808e616 Mon Sep 17 00:00:00 2001 From: Marino von Wattenwyl Date: Thu, 9 Nov 2023 09:48:14 +0100 Subject: [PATCH 25/31] [WebGPU] Update demo --- apps/webgpu/main.cpp | 173 ++++++++++++++++++++++++------------------- 1 file changed, 95 insertions(+), 78 deletions(-) diff --git a/apps/webgpu/main.cpp b/apps/webgpu/main.cpp index 062c56e6..eb231299 100644 --- a/apps/webgpu/main.cpp +++ b/apps/webgpu/main.cpp @@ -34,6 +34,14 @@ std::exit(1); \ } +struct AppData +{ + WGPUDevice device = nullptr; + WGPUSurface surface = nullptr; + + bool requireSurfaceReconfiguration = false; +}; + struct alignas(16) ShaderUniformData { float projectionMatrix[16]; @@ -47,6 +55,12 @@ static_assert(sizeof(ShaderUniformData) % 16 == 0, "uniform data size must be a extern "C" void* createMetalLayer(void* window); #endif +void onWindowResized(GLFWwindow* window, int width, int height) +{ + AppData& app = *((AppData*)glfwGetWindowUserPointer(window)); + app.requireSurfaceReconfiguration = true; +} + void handleAdapterRequest(WGPURequestAdapterStatus status, WGPUAdapter adapter, char const* message, @@ -75,6 +89,8 @@ void handleDeviceRequest(WGPURequestDeviceStatus status, int main(int argc, const char* argv[]) { + AppData app; + // === Initialize GLFW === WEBGPU_DEMO_CHECK(glfwInit(), "[GLFW] Failed to initialize"); @@ -86,7 +102,11 @@ int main(int argc, const char* argv[]) glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); GLFWwindow* window = glfwCreateWindow(1280, 720, "WebGPU Demo", nullptr, nullptr); - WEBGPU_DEMO_CHECK(window, "[GLFW] Window created"); + WEBGPU_DEMO_CHECK(window, "[GLFW] Failed to create window"); + WEBGPU_DEMO_LOG("[GLFW] Window created"); + + glfwSetWindowUserPointer(window, &app); + glfwSetWindowSizeCallback(window, onWindowResized); // === Create a WebGPU instance === // The instance is the root interface to WebGPU through which we create all other WebGPU resources. @@ -135,13 +155,12 @@ int main(int argc, const char* argv[]) deviceDesc.requiredLimits = &requiredLimits; deviceDesc.defaultQueue.label = "Demo Queue"; - WGPUDevice device; - wgpuAdapterRequestDevice(adapter, &deviceDesc, handleDeviceRequest, &device); + wgpuAdapterRequestDevice(adapter, &deviceDesc, handleDeviceRequest, &app.device); // === Acquire a WebGPU queue === // The queue is where the commands for the GPU are submitted to. - WGPUQueue queue = wgpuDeviceGetQueue(device); + WGPUQueue queue = wgpuDeviceGetQueue(app.device); WEBGPU_DEMO_CHECK(queue, "[WebGPU] Failed to acquire queue"); WEBGPU_DEMO_LOG("[WebGPU] Queue acquired"); @@ -169,8 +188,8 @@ int main(int argc, const char* argv[]) surfaceDesc.label = "Demo Surface"; surfaceDesc.nextInChain = (const WGPUChainedStruct*)&nativeSurfaceDesc; - WGPUSurface surface = wgpuInstanceCreateSurface(instance, &surfaceDesc); - WEBGPU_DEMO_CHECK(surface, "[WebGPU] Failed to create surface"); + app.surface = wgpuInstanceCreateSurface(instance, &surfaceDesc); + WEBGPU_DEMO_CHECK(app.surface, "[WebGPU] Failed to create surface"); WEBGPU_DEMO_LOG("[WebGPU] Surface created"); // === Configure the surface === @@ -178,7 +197,7 @@ int main(int argc, const char* argv[]) // Query the surface capabilities from the adapter. WGPUSurfaceCapabilities surfaceCapabilities; - wgpuSurfaceGetCapabilities(surface, adapter, &surfaceCapabilities); + wgpuSurfaceGetCapabilities(app.surface, adapter, &surfaceCapabilities); // Get the window size from the GLFW window. int surfaceWidth; @@ -186,14 +205,14 @@ int main(int argc, const char* argv[]) glfwGetWindowSize(window, &surfaceWidth, &surfaceHeight); WGPUSurfaceConfiguration surfaceConfig = {}; - surfaceConfig.device = device; + surfaceConfig.device = app.device; surfaceConfig.usage = WGPUTextureUsage_RenderAttachment; surfaceConfig.format = surfaceCapabilities.formats[0]; surfaceConfig.presentMode = WGPUPresentMode_Fifo; surfaceConfig.alphaMode = surfaceCapabilities.alphaModes[0]; surfaceConfig.width = surfaceWidth; surfaceConfig.height = surfaceHeight; - wgpuSurfaceConfigure(surface, &surfaceConfig); + wgpuSurfaceConfigure(app.surface, &surfaceConfig); WEBGPU_DEMO_LOG("[WebGPU] Surface configured"); // === Create the vertex buffer === @@ -260,7 +279,7 @@ int main(int argc, const char* argv[]) vertexBufferDesc.size = vertexDataSize; vertexBufferDesc.usage = WGPUBufferUsage_CopyDst | WGPUBufferUsage_Vertex; - WGPUBuffer vertexBuffer = wgpuDeviceCreateBuffer(device, &vertexBufferDesc); + WGPUBuffer vertexBuffer = wgpuDeviceCreateBuffer(app.device, &vertexBufferDesc); WEBGPU_DEMO_CHECK(vertexBuffer, "[WebGPU] Failed to create vertex buffer"); WEBGPU_DEMO_LOG("[WebGPU] Vertex buffer created"); @@ -281,7 +300,7 @@ int main(int argc, const char* argv[]) indexBufferDesc.size = indexDataSize; indexBufferDesc.usage = WGPUBufferUsage_CopyDst | WGPUBufferUsage_Index; - WGPUBuffer indexBuffer = wgpuDeviceCreateBuffer(device, &indexBufferDesc); + WGPUBuffer indexBuffer = wgpuDeviceCreateBuffer(app.device, &indexBufferDesc); WEBGPU_DEMO_CHECK(indexBuffer, "[WebGPU] Failed to create index buffer"); WEBGPU_DEMO_LOG("[WebGPU] Index buffer created"); @@ -294,7 +313,7 @@ int main(int argc, const char* argv[]) uniformBufferDesc.size = sizeof(ShaderUniformData); uniformBufferDesc.usage = WGPUBufferUsage_CopyDst | WGPUBufferUsage_Uniform; - WGPUBuffer uniformBuffer = wgpuDeviceCreateBuffer(device, &uniformBufferDesc); + WGPUBuffer uniformBuffer = wgpuDeviceCreateBuffer(app.device, &uniformBufferDesc); WEBGPU_DEMO_CHECK(indexBuffer, "[WebGPU] Failed to create uniform buffer"); WEBGPU_DEMO_LOG("[WebGPU] Uniform buffer created"); @@ -317,7 +336,7 @@ int main(int argc, const char* argv[]) depthTextureDesc.viewFormatCount = 1; depthTextureDesc.viewFormats = &depthTextureFormat; - WGPUTexture depthTexture = wgpuDeviceCreateTexture(device, &depthTextureDesc); + WGPUTexture depthTexture = wgpuDeviceCreateTexture(app.device, &depthTextureDesc); WEBGPU_DEMO_CHECK(depthTexture, "[WebGPU] Failed to create depth texture"); WEBGPU_DEMO_LOG("[WebGPU] Depth texture created"); @@ -354,7 +373,7 @@ int main(int argc, const char* argv[]) textureDesc.mipLevelCount = 4; textureDesc.sampleCount = 1; - WGPUTexture texture = wgpuDeviceCreateTexture(device, &textureDesc); + WGPUTexture texture = wgpuDeviceCreateTexture(app.device, &textureDesc); WEBGPU_DEMO_CHECK(texture, "[WebGPU] Failed to create texture"); WEBGPU_DEMO_LOG("[WebGPU] Texture created"); @@ -450,7 +469,7 @@ int main(int argc, const char* argv[]) samplerDesc.compare = WGPUCompareFunction_Undefined; samplerDesc.maxAnisotropy = 1.0f; - WGPUSampler sampler = wgpuDeviceCreateSampler(device, &samplerDesc); + WGPUSampler sampler = wgpuDeviceCreateSampler(app.device, &samplerDesc); WEBGPU_DEMO_CHECK(sampler, "[WebGPU] Failed to create sampler"); WEBGPU_DEMO_LOG("[WebGPU] Sampler created"); @@ -505,7 +524,7 @@ int main(int argc, const char* argv[]) shaderModuleDesc.label = "Demo Shader"; shaderModuleDesc.nextInChain = (const WGPUChainedStruct*)&shaderModuleWGSLDesc; - WGPUShaderModule shaderModule = wgpuDeviceCreateShaderModule(device, &shaderModuleDesc); + WGPUShaderModule shaderModule = wgpuDeviceCreateShaderModule(app.device, &shaderModuleDesc); WEBGPU_DEMO_CHECK(shaderModule, "[WebGPU] Failed to create shader module"); WEBGPU_DEMO_LOG("[WebGPU] Shader module created"); @@ -540,7 +559,7 @@ int main(int argc, const char* argv[]) bindGroupLayoutDesc.label = "Demo Bind Group Layout"; bindGroupLayoutDesc.entryCount = bindGroupLayoutEntries.size(); bindGroupLayoutDesc.entries = bindGroupLayoutEntries.data(); - WGPUBindGroupLayout bindGroupLayout = wgpuDeviceCreateBindGroupLayout(device, &bindGroupLayoutDesc); + WGPUBindGroupLayout bindGroupLayout = wgpuDeviceCreateBindGroupLayout(app.device, &bindGroupLayoutDesc); WEBGPU_DEMO_CHECK(bindGroupLayout, "[WebGPU] Failed to create bind group layout"); WEBGPU_DEMO_LOG("[WebGPU] Bind group layout created"); @@ -569,7 +588,7 @@ int main(int argc, const char* argv[]) bindGroupDesc.layout = bindGroupLayout; bindGroupDesc.entryCount = bindGroupEntries.size(); bindGroupDesc.entries = bindGroupEntries.data(); - WGPUBindGroup bindGroup = wgpuDeviceCreateBindGroup(device, &bindGroupDesc); + WGPUBindGroup bindGroup = wgpuDeviceCreateBindGroup(app.device, &bindGroupDesc); WEBGPU_DEMO_CHECK(bindGroup, "[WebGPU] Failed to create bind group"); WEBGPU_DEMO_LOG("[WebGPU] Bind group created"); @@ -580,7 +599,7 @@ int main(int argc, const char* argv[]) pipelineLayoutDesc.label = "Demo Pipeline Layout"; pipelineLayoutDesc.bindGroupLayoutCount = 1; pipelineLayoutDesc.bindGroupLayouts = &bindGroupLayout; - WGPUPipelineLayout pipelineLayout = wgpuDeviceCreatePipelineLayout(device, &pipelineLayoutDesc); + WGPUPipelineLayout pipelineLayout = wgpuDeviceCreatePipelineLayout(app.device, &pipelineLayoutDesc); WEBGPU_DEMO_CHECK(pipelineLayout, "[WebGPU] Failed to create pipeline layout"); WEBGPU_DEMO_LOG("[WebGPU] Pipeline layout created"); @@ -663,11 +682,12 @@ int main(int argc, const char* argv[]) pipelineDesc.multisample = multisampleState; pipelineDesc.fragment = &fragmentState; - WGPURenderPipeline pipeline = wgpuDeviceCreateRenderPipeline(device, &pipelineDesc); + WGPURenderPipeline pipeline = wgpuDeviceCreateRenderPipeline(app.device, &pipelineDesc); WEBGPU_DEMO_CHECK(pipeline, "[WebGPU] Failed to create render pipeline"); WEBGPU_DEMO_LOG("[WebGPU] Render pipeline created"); - SLMat4f modelMatrix; + float camRotX = 0.0f; + float camRotY = 0.0f; double lastCursorX = 0.0f; double lastCursorY = 0.0f; @@ -682,29 +702,59 @@ int main(int argc, const char* argv[]) double cursorY; glfwGetCursorPos(window, &cursorX, &cursorY); - SLMat4f rotationX; - SLMat4f rotationY; - if (glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_1) == GLFW_PRESS) { - float deltaRotX = 0.5f * static_cast(cursorY - lastCursorY); - float deltaRotY = 0.5f * static_cast(cursorX - lastCursorX); - - rotationX.rotate(deltaRotX, SLVec3f::AXISX); - rotationY.rotate(deltaRotY, SLVec3f::AXISY); + camRotX -= 0.25f * static_cast(cursorY - lastCursorY); + camRotY -= 0.25f * static_cast(cursorX - lastCursorX); } - modelMatrix = rotationY * rotationX * modelMatrix; - lastCursorX = cursorX; lastCursorY = cursorY; // === Create a WebGPU texture view === // The texture view is where we render our image into. + if (app.requireSurfaceReconfiguration) + { + app.requireSurfaceReconfiguration = false; + + // Get the window size from the GLFW window. + glfwGetWindowSize(window, &surfaceWidth, &surfaceHeight); + + // The surface size might be zero if the window is minimized. + if (surfaceWidth != 0 && surfaceHeight != 0) + { + WEBGPU_DEMO_LOG("[WebGPU] Re-configuring surface"); + surfaceConfig.width = surfaceWidth; + surfaceConfig.height = surfaceHeight; + wgpuSurfaceConfigure(app.surface, &surfaceConfig); + + // Recreate the depth texture. + + wgpuTextureViewRelease(textureView); + wgpuTextureDestroy(depthTexture); + wgpuTextureRelease(depthTexture); + + depthTextureDesc.size.width = surfaceWidth; + depthTextureDesc.size.height = surfaceHeight; + + depthTexture = wgpuDeviceCreateTexture(app.device, &depthTextureDesc); + WEBGPU_DEMO_CHECK(depthTexture, "[WebGPU] Failed to re-create depth texture"); + WEBGPU_DEMO_LOG("[WebGPU] Depth texture re-created"); + + depthTextureView = wgpuTextureCreateView(depthTexture, &depthTextureViewDesc); + WEBGPU_DEMO_CHECK(depthTextureView, "[WebGPU] Failed to re-create depth texture view"); + WEBGPU_DEMO_LOG("[WebGPU] Depth texture view re-created"); + } + + // Skip this frame. + glfwPollEvents(); + continue; + } + // Get a texture from the surface to render into. WGPUSurfaceTexture surfaceTexture; - wgpuSurfaceGetCurrentTexture(surface, &surfaceTexture); + wgpuSurfaceGetCurrentTexture(app.surface, &surfaceTexture); // The surface might change over time. // For example, the window might be resized or minimized. @@ -712,18 +762,11 @@ int main(int argc, const char* argv[]) switch (surfaceTexture.status) { case WGPUSurfaceGetCurrentTextureStatus_Success: - // Everything is ok. - - // Check for a suboptimal texture and re-configure it if needed. + // Everything is ok, but still check for a suboptimal texture and re-configure it if needed. if (surfaceTexture.suboptimal) { WEBGPU_DEMO_LOG("[WebGPU] Re-configuring currently suboptimal surface"); - surfaceConfig.width = surfaceWidth; - surfaceConfig.height = surfaceHeight; - wgpuSurfaceConfigure(surface, &surfaceConfig); - - // Skip this frame. - glfwPollEvents(); + app.requireSurfaceReconfiguration = true; continue; } @@ -733,38 +776,7 @@ int main(int argc, const char* argv[]) case WGPUSurfaceGetCurrentTextureStatus_Outdated: case WGPUSurfaceGetCurrentTextureStatus_Lost: // The surface needs to be re-configured. - - // Get the window size from the GLFW window. - glfwGetWindowSize(window, &surfaceWidth, &surfaceHeight); - - // The surface size might be zero if the window is minimized. - if (surfaceWidth != 0 && surfaceHeight != 0) - { - WEBGPU_DEMO_LOG("[WebGPU] Re-configuring surface"); - surfaceConfig.width = surfaceWidth; - surfaceConfig.height = surfaceHeight; - wgpuSurfaceConfigure(surface, &surfaceConfig); - - // Recreate the depth texture. - - wgpuTextureViewRelease(textureView); - wgpuTextureDestroy(depthTexture); - wgpuTextureRelease(depthTexture); - - depthTextureDesc.size.width = surfaceWidth; - depthTextureDesc.size.height = surfaceHeight; - - depthTexture = wgpuDeviceCreateTexture(device, &depthTextureDesc); - WEBGPU_DEMO_CHECK(depthTexture, "[WebGPU] Failed to re-create depth texture"); - WEBGPU_DEMO_LOG("[WebGPU] Depth texture re-created"); - - depthTextureView = wgpuTextureCreateView(depthTexture, &depthTextureViewDesc); - WEBGPU_DEMO_CHECK(depthTextureView, "[WebGPU] Failed to re-create depth texture view"); - WEBGPU_DEMO_LOG("[WebGPU] Depth texture view re-created"); - } - - // Skip this frame. - glfwPollEvents(); + app.requireSurfaceReconfiguration = true; continue; case WGPUSurfaceGetCurrentTextureStatus_OutOfMemory: @@ -783,11 +795,16 @@ int main(int argc, const char* argv[]) // === Prepare uniform data === float aspectRatio = static_cast(surfaceWidth) / static_cast(surfaceHeight); + SLMat4f modelMatrix; + SLMat4f projectionMatrix; projectionMatrix.perspective(70.0f, aspectRatio, 0.1, 1000.0f); SLMat4f viewMatrix; - viewMatrix.translate(0.0f, 0.0f, -2.0f); + viewMatrix.rotate(camRotY, SLVec3f::AXISY); + viewMatrix.rotate(camRotX, SLVec3f::AXISX); + viewMatrix.translate(0.0f, 0.0f, 2.0f); + viewMatrix.invert(); // === Update uniforms === ShaderUniformData uniformData = {}; @@ -802,7 +819,7 @@ int main(int argc, const char* argv[]) WGPUCommandEncoderDescriptor cmdEncoderDesc = {}; cmdEncoderDesc.label = "Demo Command Encoder"; - WGPUCommandEncoder cmdEncoder = wgpuDeviceCreateCommandEncoder(device, &cmdEncoderDesc); + WGPUCommandEncoder cmdEncoder = wgpuDeviceCreateCommandEncoder(app.device, &cmdEncoderDesc); WEBGPU_DEMO_CHECK(cmdEncoder, "[WebGPU] Failed to create command encoder"); // === Create a WebGPU render pass === @@ -863,7 +880,7 @@ int main(int argc, const char* argv[]) // === Present the surface === // This presents our rendered texture to the screen. - wgpuSurfacePresent(surface); + wgpuSurfacePresent(app.surface); // === Clean up resources === wgpuCommandBufferRelease(cmdBuffer); @@ -895,9 +912,9 @@ int main(int argc, const char* argv[]) wgpuBufferRelease(indexBuffer); wgpuBufferDestroy(vertexBuffer); wgpuBufferRelease(vertexBuffer); - wgpuSurfaceRelease(surface); + wgpuSurfaceRelease(app.surface); wgpuQueueRelease(queue); - wgpuDeviceRelease(device); + wgpuDeviceRelease(app.device); wgpuAdapterRelease(adapter); wgpuInstanceRelease(instance); WEBGPU_DEMO_LOG("[WebGPU] Resources released"); From efc509cbfca68cb2c8e28dd1e7f0bcfd7030dd99 Mon Sep 17 00:00:00 2001 From: Marino von Wattenwyl Date: Thu, 9 Nov 2023 10:57:59 +0100 Subject: [PATCH 26/31] [WebGPU] Improve surface re-configuring in demo --- apps/webgpu/main.cpp | 719 +++++++++++++++++++++++-------------------- 1 file changed, 383 insertions(+), 336 deletions(-) diff --git a/apps/webgpu/main.cpp b/apps/webgpu/main.cpp index e4c2999a..316b3fa5 100644 --- a/apps/webgpu/main.cpp +++ b/apps/webgpu/main.cpp @@ -38,10 +38,43 @@ struct AppData { - WGPUDevice device = nullptr; - WGPUSurface surface = nullptr; + GLFWwindow* window = nullptr; + int surfaceWidth = 0; + int surfaceHeight = 0; + + WGPUInstance instance = nullptr; + WGPUAdapter adapter = nullptr; + WGPUDevice device = nullptr; + WGPUQueue queue = nullptr; + WGPUSurface surface = nullptr; + WGPUTexture depthTexture = nullptr; + WGPUTextureView depthTextureView = nullptr; + WGPUBuffer vertexBuffer = nullptr; + WGPUBuffer indexBuffer = nullptr; + WGPUBuffer uniformBuffer = nullptr; + WGPUTexture texture = nullptr; + WGPUTextureView textureView = nullptr; + WGPUSampler sampler = nullptr; + WGPUShaderModule shaderModule = nullptr; + WGPUBindGroupLayout bindGroupLayout = nullptr; + WGPUBindGroup bindGroup = nullptr; + WGPUPipelineLayout pipelineLayout = nullptr; + WGPURenderPipeline pipeline = nullptr; + + WGPUSurfaceConfiguration surfaceConfig; + WGPUTextureFormat depthTextureFormat; + WGPUTextureDescriptor depthTextureDesc; + WGPUTextureViewDescriptor depthTextureViewDesc; + + unsigned vertexDataSize; + unsigned indexCount; + unsigned indexDataSize; bool requireSurfaceReconfiguration = false; + + float camRotX = 0.0f; + float camRotY = 0.0f; + float camZ = 2.0f; }; struct alignas(16) ShaderUniformData @@ -57,10 +90,207 @@ static_assert(sizeof(ShaderUniformData) % 16 == 0, "uniform data size must be a extern "C" void* createMetalLayer(void* window); #endif -void onWindowResized(GLFWwindow* window, int width, int height) +void reconfigureSurface(AppData& app) +{ + // Get the window size from the GLFW window. + glfwGetWindowSize(app.window, &app.surfaceWidth, &app.surfaceHeight); + + // The surface size might be zero if the window is minimized. + if (app.surfaceWidth == 0 || app.surfaceHeight == 0) + return; + + WEBGPU_DEMO_LOG("[WebGPU] Re-configuring surface"); + app.surfaceConfig.width = app.surfaceWidth; + app.surfaceConfig.height = app.surfaceHeight; + wgpuSurfaceConfigure(app.surface, &app.surfaceConfig); + + // Recreate the depth texture. + + wgpuTextureViewRelease(app.depthTextureView); + wgpuTextureDestroy(app.depthTexture); + wgpuTextureRelease(app.depthTexture); + + app.depthTextureDesc.size.width = app.surfaceWidth; + app.depthTextureDesc.size.height = app.surfaceHeight; + + app.depthTexture = wgpuDeviceCreateTexture(app.device, &app.depthTextureDesc); + WEBGPU_DEMO_CHECK(app.depthTexture, "[WebGPU] Failed to re-create depth texture"); + WEBGPU_DEMO_LOG("[WebGPU] Depth texture re-created"); + + app.depthTextureView = wgpuTextureCreateView(app.depthTexture, &app.depthTextureViewDesc); + WEBGPU_DEMO_CHECK(app.depthTextureView, "[WebGPU] Failed to re-create depth texture view"); + WEBGPU_DEMO_LOG("[WebGPU] Depth texture view re-created"); +} + +void onPaint(AppData& app) +{ + // Get a texture from the surface to render into. + WGPUSurfaceTexture surfaceTexture; + wgpuSurfaceGetCurrentTexture(app.surface, &surfaceTexture); + + // The surface might change over time. + // For example, the window might be resized or minimized. + // We have to check the status and adapt to it. + switch (surfaceTexture.status) + { + case WGPUSurfaceGetCurrentTextureStatus_Success: + // Everything is ok, but still check for a suboptimal texture and re-configure it if needed. + if (surfaceTexture.suboptimal) + { + WEBGPU_DEMO_LOG("[WebGPU] Re-configuring currently suboptimal surface"); + reconfigureSurface(app); + wgpuSurfaceGetCurrentTexture(app.surface, &surfaceTexture); + } + + break; + + case WGPUSurfaceGetCurrentTextureStatus_Timeout: + case WGPUSurfaceGetCurrentTextureStatus_Outdated: + case WGPUSurfaceGetCurrentTextureStatus_Lost: + // The surface needs to be re-configured. + reconfigureSurface(app); + wgpuSurfaceGetCurrentTexture(app.surface, &surfaceTexture); + break; + + case WGPUSurfaceGetCurrentTextureStatus_OutOfMemory: + case WGPUSurfaceGetCurrentTextureStatus_DeviceLost: + // An error occured. + WEBGPU_DEMO_CHECK(false, "[WebGPU] Failed to acquire current surface texture"); + break; + + case WGPUSurfaceGetCurrentTextureStatus_Force32: + break; + } + + // === Create a WebGPU texture view === + // The texture view is where we render our image into. + + // Create a view into the texture to specify where and how to modify the texture. + WGPUTextureView view = wgpuTextureCreateView(surfaceTexture.texture, nullptr); + + // === Prepare uniform data === + float aspectRatio = static_cast(app.surfaceWidth) / static_cast(app.surfaceHeight); + + SLMat4f modelMatrix; + + SLMat4f projectionMatrix; + projectionMatrix.perspective(70.0f, aspectRatio, 0.1, 1000.0f); + + SLMat4f viewMatrix; + viewMatrix.rotate(app.camRotY, SLVec3f::AXISY); + viewMatrix.rotate(app.camRotX, SLVec3f::AXISX); + viewMatrix.translate(0.0f, 0.0f, 2.0f); + viewMatrix.invert(); + + // === Update uniforms === + ShaderUniformData uniformData = {}; + std::memcpy(uniformData.projectionMatrix, projectionMatrix.m(), sizeof(uniformData.projectionMatrix)); + std::memcpy(uniformData.viewMatrix, viewMatrix.m(), sizeof(uniformData.viewMatrix)); + std::memcpy(uniformData.modelMatrix, modelMatrix.m(), sizeof(uniformData.modelMatrix)); + wgpuQueueWriteBuffer(app.queue, app.uniformBuffer, 0, &uniformData, sizeof(ShaderUniformData)); + + // === Create a WebGPU command encoder === + // The encoder encodes the commands for the GPU into a command buffer. + + WGPUCommandEncoderDescriptor cmdEncoderDesc = {}; + cmdEncoderDesc.label = "Demo Command Encoder"; + + WGPUCommandEncoder cmdEncoder = wgpuDeviceCreateCommandEncoder(app.device, &cmdEncoderDesc); + WEBGPU_DEMO_CHECK(cmdEncoder, "[WebGPU] Failed to create command encoder"); + + // === Create a WebGPU render pass === + // The render pass specifies what attachments to use while rendering. + // A color attachment specifies what view to render into and what to do with the texture before and after + // rendering. We clear the texture before rendering and store the results after rendering. + // The depth attachment specifies what depth texture to use. + + WGPURenderPassColorAttachment colorAttachment = {}; + colorAttachment.view = view; + colorAttachment.loadOp = WGPULoadOp_Clear; + colorAttachment.storeOp = WGPUStoreOp_Store; + colorAttachment.clearValue.r = 0.3; + colorAttachment.clearValue.g = 0.0; + colorAttachment.clearValue.b = 0.2; + colorAttachment.clearValue.a = 1.0; + + WGPURenderPassDepthStencilAttachment depthStencilAttachment = {}; + depthStencilAttachment.view = app.depthTextureView; + depthStencilAttachment.depthLoadOp = WGPULoadOp_Clear; + depthStencilAttachment.depthStoreOp = WGPUStoreOp_Store; + depthStencilAttachment.depthClearValue = 1.0f; + depthStencilAttachment.depthReadOnly = false; + depthStencilAttachment.stencilLoadOp = WGPULoadOp_Clear; + depthStencilAttachment.stencilStoreOp = WGPUStoreOp_Store; + depthStencilAttachment.stencilClearValue = 0.0f; + depthStencilAttachment.stencilReadOnly = true; + + WGPURenderPassDescriptor renderPassDesc = {}; + renderPassDesc.label = "Demo Render Pass"; + renderPassDesc.colorAttachmentCount = 1; + renderPassDesc.colorAttachments = &colorAttachment; + renderPassDesc.depthStencilAttachment = &depthStencilAttachment; + + // === Encode the commands === + // The commands to begin a render pass, bind a pipeline, draw the triangle and end the render pass + // are encoded into a buffer. + + WGPURenderPassEncoder renderPassEncoder = wgpuCommandEncoderBeginRenderPass(cmdEncoder, &renderPassDesc); + wgpuRenderPassEncoderSetPipeline(renderPassEncoder, app.pipeline); + wgpuRenderPassEncoderSetVertexBuffer(renderPassEncoder, 0, app.vertexBuffer, 0, app.vertexDataSize); + wgpuRenderPassEncoderSetIndexBuffer(renderPassEncoder, app.indexBuffer, WGPUIndexFormat_Uint16, 0, app.indexDataSize); + wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 0, app.bindGroup, 0, nullptr); + wgpuRenderPassEncoderDrawIndexed(renderPassEncoder, app.indexCount, 1, 0, 0, 0); + wgpuRenderPassEncoderEnd(renderPassEncoder); + + // === Get the command buffer === + // The command encoder is finished to get the commands for the GPU to execute in a command buffer. + + WGPUCommandBufferDescriptor cmdBufferDesc = {}; + cmdBufferDesc.label = "Demo Command Buffer"; + + WGPUCommandBuffer cmdBuffer = wgpuCommandEncoderFinish(cmdEncoder, &cmdBufferDesc); + + // === Submit the command buffer to the GPU === + // The work for the GPU is submitted through the queue and executed. + wgpuQueueSubmit(app.queue, 1, &cmdBuffer); + + // === Present the surface === + // This presents our rendered texture to the screen. + wgpuSurfacePresent(app.surface); + + // === Clean up resources === + wgpuCommandBufferRelease(cmdBuffer); + wgpuRenderPassEncoderRelease(renderPassEncoder); + wgpuCommandEncoderRelease(cmdEncoder); + wgpuTextureViewRelease(view); + wgpuTextureRelease(surfaceTexture.texture); +} + +void onResize(GLFWwindow* window, int width, int height) { AppData& app = *((AppData*)glfwGetWindowUserPointer(window)); - app.requireSurfaceReconfiguration = true; + reconfigureSurface(app); + onPaint(app); +} + +void initGLFW(AppData& app) +{ + // === Initialize GLFW === + + WEBGPU_DEMO_CHECK(glfwInit(), "[GLFW] Failed to initialize"); + WEBGPU_DEMO_LOG("[GLFW] Initialized"); + + // === Create the GLFW window === + + // Prevent GLFW from creating an OpenGL context as the underlying graphics API probably won't be OpenGL. + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + + app.window = glfwCreateWindow(1280, 720, "WebGPU Demo", nullptr, nullptr); + WEBGPU_DEMO_CHECK(app.window, "[GLFW] Failed to create window"); + WEBGPU_DEMO_LOG("[GLFW] Window created"); + + glfwSetWindowUserPointer(app.window, &app); + glfwSetFramebufferSizeCallback(app.window, onResize); } void handleAdapterRequest(WGPURequestAdapterStatus status, @@ -89,32 +319,13 @@ void handleDeviceRequest(WGPURequestDeviceStatus status, *outDevice = device; } -int main(int argc, const char* argv[]) +void initWebGPU(AppData& app) { - AppData app; - - // === Initialize GLFW === - - WEBGPU_DEMO_CHECK(glfwInit(), "[GLFW] Failed to initialize"); - WEBGPU_DEMO_LOG("[GLFW] Initialized"); - - // === Create the GLFW window === - - // Prevent GLFW from creating an OpenGL context as the underlying graphics API probably won't be OpenGL. - glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); - - GLFWwindow* window = glfwCreateWindow(1280, 720, "WebGPU Demo", nullptr, nullptr); - WEBGPU_DEMO_CHECK(window, "[GLFW] Failed to create window"); - WEBGPU_DEMO_LOG("[GLFW] Window created"); - - glfwSetWindowUserPointer(window, &app); - glfwSetWindowSizeCallback(window, onWindowResized); - // === Create a WebGPU instance === // The instance is the root interface to WebGPU through which we create all other WebGPU resources. - WGPUInstance instance = wgpuCreateInstance(nullptr); - WEBGPU_DEMO_CHECK(instance, "[WebGPU] Failed to create instance"); + app.instance = wgpuCreateInstance(nullptr); + WEBGPU_DEMO_CHECK(app.instance, "[WebGPU] Failed to create instance"); WEBGPU_DEMO_LOG("[WebGPU] Instance created"); // === Acquire a WebGPU adapter === @@ -122,11 +333,10 @@ int main(int argc, const char* argv[]) WGPURequestAdapterOptions adapterOptions = {}; - WGPUAdapter adapter; - wgpuInstanceRequestAdapter(instance, &adapterOptions, handleAdapterRequest, &adapter); + wgpuInstanceRequestAdapter(app.instance, &adapterOptions, handleAdapterRequest, &app.adapter); WGPUSupportedLimits adapterLimits; - wgpuAdapterGetLimits(adapter, &adapterLimits); + wgpuAdapterGetLimits(app.adapter, &adapterLimits); // === Acquire a WebGPU device === // A device provides access to a GPU and is created from an adapter. @@ -157,13 +367,13 @@ int main(int argc, const char* argv[]) deviceDesc.requiredLimits = &requiredLimits; deviceDesc.defaultQueue.label = "Demo Queue"; - wgpuAdapterRequestDevice(adapter, &deviceDesc, handleDeviceRequest, &app.device); + wgpuAdapterRequestDevice(app.adapter, &deviceDesc, handleDeviceRequest, &app.device); // === Acquire a WebGPU queue === // The queue is where the commands for the GPU are submitted to. - WGPUQueue queue = wgpuDeviceGetQueue(app.device); - WEBGPU_DEMO_CHECK(queue, "[WebGPU] Failed to acquire queue"); + app.queue = wgpuDeviceGetQueue(app.device); + WEBGPU_DEMO_CHECK(app.queue, "[WebGPU] Failed to acquire queue"); WEBGPU_DEMO_LOG("[WebGPU] Queue acquired"); // === Create a WebGPU surface === @@ -174,23 +384,23 @@ int main(int argc, const char* argv[]) WGPUSurfaceDescriptorFromWindowsHWND nativeSurfaceDesc = {}; nativeSurfaceDesc.chain.sType = WGPUSType_SurfaceDescriptorFromWindowsHWND; nativeSurfaceDesc.hinstance = GetModuleHandle(nullptr); - nativeSurfaceDesc.hwnd = glfwGetWin32Window(window); + nativeSurfaceDesc.hwnd = glfwGetWin32Window(app.window); #elif defined(SYSTEM_LINUX) WGPUSurfaceDescriptorFromXlibWindow nativeSurfaceDesc = {}; nativeSurfaceDesc.chain.sType = WGPUSType_SurfaceDescriptorFromXlibWindow; nativeSurfaceDesc.display = glfwGetX11Display(); - nativeSurfaceDesc.window = glfwGetX11Window(window); + nativeSurfaceDesc.window = glfwGetX11Window(app.window); #elif defined(SYSTEM_DARWIN) WGPUSurfaceDescriptorFromMetalLayer nativeSurfaceDesc = {}; nativeSurfaceDesc.chain.sType = WGPUSType_SurfaceDescriptorFromMetalLayer; - nativeSurfaceDesc.layer = createMetalLayer(glfwGetCocoaWindow(window)); + nativeSurfaceDesc.layer = createMetalLayer(glfwGetCocoaWindow(app.window)); #endif WGPUSurfaceDescriptor surfaceDesc = {}; surfaceDesc.label = "Demo Surface"; surfaceDesc.nextInChain = (const WGPUChainedStruct*)&nativeSurfaceDesc; - app.surface = wgpuInstanceCreateSurface(instance, &surfaceDesc); + app.surface = wgpuInstanceCreateSurface(app.instance, &surfaceDesc); WEBGPU_DEMO_CHECK(app.surface, "[WebGPU] Failed to create surface"); WEBGPU_DEMO_LOG("[WebGPU] Surface created"); @@ -199,22 +409,20 @@ int main(int argc, const char* argv[]) // Query the surface capabilities from the adapter. WGPUSurfaceCapabilities surfaceCapabilities; - wgpuSurfaceGetCapabilities(app.surface, adapter, &surfaceCapabilities); + wgpuSurfaceGetCapabilities(app.surface, app.adapter, &surfaceCapabilities); // Get the window size from the GLFW window. - int surfaceWidth; - int surfaceHeight; - glfwGetWindowSize(window, &surfaceWidth, &surfaceHeight); - - WGPUSurfaceConfiguration surfaceConfig = {}; - surfaceConfig.device = app.device; - surfaceConfig.usage = WGPUTextureUsage_RenderAttachment; - surfaceConfig.format = surfaceCapabilities.formats[0]; - surfaceConfig.presentMode = WGPUPresentMode_Fifo; - surfaceConfig.alphaMode = surfaceCapabilities.alphaModes[0]; - surfaceConfig.width = surfaceWidth; - surfaceConfig.height = surfaceHeight; - wgpuSurfaceConfigure(app.surface, &surfaceConfig); + glfwGetFramebufferSize(app.window, &app.surfaceWidth, &app.surfaceHeight); + + app.surfaceConfig = {}; + app.surfaceConfig.device = app.device; + app.surfaceConfig.usage = WGPUTextureUsage_RenderAttachment; + app.surfaceConfig.format = surfaceCapabilities.formats[0]; + app.surfaceConfig.presentMode = WGPUPresentMode_Fifo; + app.surfaceConfig.alphaMode = surfaceCapabilities.alphaModes[0]; + app.surfaceConfig.width = app.surfaceWidth; + app.surfaceConfig.height = app.surfaceHeight; + wgpuSurfaceConfigure(app.surface, &app.surfaceConfig); WEBGPU_DEMO_LOG("[WebGPU] Surface configured"); // === Create the vertex buffer === @@ -273,20 +481,20 @@ int main(int argc, const char* argv[]) }; // clang-format on - unsigned vertexCount = vertexData.size() / 5; - unsigned vertexDataSize = vertexData.size() * sizeof(float); + unsigned vertexCount = vertexData.size() / 5; + app.vertexDataSize = vertexData.size() * sizeof(float); WGPUBufferDescriptor vertexBufferDesc = {}; vertexBufferDesc.label = "Demo Vertex Buffer"; - vertexBufferDesc.size = vertexDataSize; + vertexBufferDesc.size = app.vertexDataSize; vertexBufferDesc.usage = WGPUBufferUsage_CopyDst | WGPUBufferUsage_Vertex; - WGPUBuffer vertexBuffer = wgpuDeviceCreateBuffer(app.device, &vertexBufferDesc); - WEBGPU_DEMO_CHECK(vertexBuffer, "[WebGPU] Failed to create vertex buffer"); + app.vertexBuffer = wgpuDeviceCreateBuffer(app.device, &vertexBufferDesc); + WEBGPU_DEMO_CHECK(app.vertexBuffer, "[WebGPU] Failed to create vertex buffer"); WEBGPU_DEMO_LOG("[WebGPU] Vertex buffer created"); // Upload the data to the GPU. - wgpuQueueWriteBuffer(queue, vertexBuffer, 0, vertexData.data(), vertexDataSize); + wgpuQueueWriteBuffer(app.queue, app.vertexBuffer, 0, vertexData.data(), app.vertexDataSize); // === Create the index buffer === @@ -294,19 +502,19 @@ int main(int argc, const char* argv[]) for (std::uint16_t index = 0; index < 36; index++) indexData.push_back(index); - unsigned indexCount = indexData.size(); - unsigned indexDataSize = indexData.size() * sizeof(std::uint16_t); + app.indexCount = indexData.size(); + app.indexDataSize = indexData.size() * sizeof(std::uint16_t); WGPUBufferDescriptor indexBufferDesc = {}; indexBufferDesc.label = "Demo Index Buffer"; - indexBufferDesc.size = indexDataSize; + indexBufferDesc.size = app.indexDataSize; indexBufferDesc.usage = WGPUBufferUsage_CopyDst | WGPUBufferUsage_Index; - WGPUBuffer indexBuffer = wgpuDeviceCreateBuffer(app.device, &indexBufferDesc); - WEBGPU_DEMO_CHECK(indexBuffer, "[WebGPU] Failed to create index buffer"); + app.indexBuffer = wgpuDeviceCreateBuffer(app.device, &indexBufferDesc); + WEBGPU_DEMO_CHECK(app.indexBuffer, "[WebGPU] Failed to create index buffer"); WEBGPU_DEMO_LOG("[WebGPU] Index buffer created"); - wgpuQueueWriteBuffer(queue, indexBuffer, 0, indexData.data(), indexDataSize); + wgpuQueueWriteBuffer(app.queue, app.indexBuffer, 0, indexData.data(), app.indexDataSize); // === Create the uniform buffer === @@ -315,44 +523,44 @@ int main(int argc, const char* argv[]) uniformBufferDesc.size = sizeof(ShaderUniformData); uniformBufferDesc.usage = WGPUBufferUsage_CopyDst | WGPUBufferUsage_Uniform; - WGPUBuffer uniformBuffer = wgpuDeviceCreateBuffer(app.device, &uniformBufferDesc); - WEBGPU_DEMO_CHECK(indexBuffer, "[WebGPU] Failed to create uniform buffer"); + app.uniformBuffer = wgpuDeviceCreateBuffer(app.device, &uniformBufferDesc); + WEBGPU_DEMO_CHECK(app.indexBuffer, "[WebGPU] Failed to create uniform buffer"); WEBGPU_DEMO_LOG("[WebGPU] Uniform buffer created"); ShaderUniformData uniformData = {}; - wgpuQueueWriteBuffer(queue, uniformBuffer, 0, &uniformData, sizeof(ShaderUniformData)); + wgpuQueueWriteBuffer(app.queue, app.uniformBuffer, 0, &uniformData, sizeof(ShaderUniformData)); // === Create the depth texture === - WGPUTextureFormat depthTextureFormat = WGPUTextureFormat_Depth24Plus; - - WGPUTextureDescriptor depthTextureDesc = {}; - depthTextureDesc.dimension = WGPUTextureDimension_2D; - depthTextureDesc.format = depthTextureFormat; - depthTextureDesc.mipLevelCount = 1; - depthTextureDesc.sampleCount = 1; - depthTextureDesc.size.width = surfaceWidth; - depthTextureDesc.size.height = surfaceHeight; - depthTextureDesc.size.depthOrArrayLayers = 1; - depthTextureDesc.usage = WGPUTextureUsage_RenderAttachment; - depthTextureDesc.viewFormatCount = 1; - depthTextureDesc.viewFormats = &depthTextureFormat; - - WGPUTexture depthTexture = wgpuDeviceCreateTexture(app.device, &depthTextureDesc); - WEBGPU_DEMO_CHECK(depthTexture, "[WebGPU] Failed to create depth texture"); + app.depthTextureFormat = WGPUTextureFormat_Depth24Plus; + + app.depthTextureDesc = {}; + app.depthTextureDesc.dimension = WGPUTextureDimension_2D; + app.depthTextureDesc.format = app.depthTextureFormat; + app.depthTextureDesc.mipLevelCount = 1; + app.depthTextureDesc.sampleCount = 1; + app.depthTextureDesc.size.width = app.surfaceWidth; + app.depthTextureDesc.size.height = app.surfaceHeight; + app.depthTextureDesc.size.depthOrArrayLayers = 1; + app.depthTextureDesc.usage = WGPUTextureUsage_RenderAttachment; + app.depthTextureDesc.viewFormatCount = 1; + app.depthTextureDesc.viewFormats = &app.depthTextureFormat; + + app.depthTexture = wgpuDeviceCreateTexture(app.device, &app.depthTextureDesc); + WEBGPU_DEMO_CHECK(app.depthTexture, "[WebGPU] Failed to create depth texture"); WEBGPU_DEMO_LOG("[WebGPU] Depth texture created"); - WGPUTextureViewDescriptor depthTextureViewDesc = {}; - depthTextureViewDesc.aspect = WGPUTextureAspect_DepthOnly; - depthTextureViewDesc.baseArrayLayer = 0; - depthTextureViewDesc.arrayLayerCount = 1; - depthTextureViewDesc.baseMipLevel = 0; - depthTextureViewDesc.mipLevelCount = 1; - depthTextureViewDesc.dimension = WGPUTextureViewDimension_2D; - depthTextureViewDesc.format = depthTextureFormat; - - WGPUTextureView depthTextureView = wgpuTextureCreateView(depthTexture, &depthTextureViewDesc); - WEBGPU_DEMO_CHECK(depthTextureView, "[WebGPU] Failed to create depth texture view"); + app.depthTextureViewDesc = {}; + app.depthTextureViewDesc.aspect = WGPUTextureAspect_DepthOnly; + app.depthTextureViewDesc.baseArrayLayer = 0; + app.depthTextureViewDesc.arrayLayerCount = 1; + app.depthTextureViewDesc.baseMipLevel = 0; + app.depthTextureViewDesc.mipLevelCount = 1; + app.depthTextureViewDesc.dimension = WGPUTextureViewDimension_2D; + app.depthTextureViewDesc.format = app.depthTextureFormat; + + app.depthTextureView = wgpuTextureCreateView(app.depthTexture, &app.depthTextureViewDesc); + WEBGPU_DEMO_CHECK(app.depthTextureView, "[WebGPU] Failed to create depth texture view"); WEBGPU_DEMO_LOG("[WebGPU] Depth texture view created"); // === Create the texture === @@ -375,13 +583,13 @@ int main(int argc, const char* argv[]) textureDesc.mipLevelCount = 4; textureDesc.sampleCount = 1; - WGPUTexture texture = wgpuDeviceCreateTexture(app.device, &textureDesc); - WEBGPU_DEMO_CHECK(texture, "[WebGPU] Failed to create texture"); + app.texture = wgpuDeviceCreateTexture(app.device, &textureDesc); + WEBGPU_DEMO_CHECK(app.texture, "[WebGPU] Failed to create texture"); WEBGPU_DEMO_LOG("[WebGPU] Texture created"); // Where do we copyu the pixel data to? WGPUImageCopyTexture destination = {}; - destination.texture = texture; + destination.texture = app.texture; destination.mipLevel = 0; destination.origin.x = 0; destination.origin.y = 0; @@ -433,7 +641,7 @@ int main(int argc, const char* argv[]) pixelDataLayout.rowsPerImage = mipLevelSize.height; // Upload the data to the GPU. - wgpuQueueWriteTexture(queue, &destination, mipLevelImage.data, mipLevelBytes, &pixelDataLayout, &mipLevelSize); + wgpuQueueWriteTexture(app.queue, &destination, mipLevelImage.data, mipLevelBytes, &pixelDataLayout, &mipLevelSize); // Scale the image down for the next mip level. mipLevelSize.width /= 2; @@ -450,8 +658,8 @@ int main(int argc, const char* argv[]) textureViewDesc.dimension = WGPUTextureViewDimension_2D; textureViewDesc.format = textureDesc.format; - WGPUTextureView textureView = wgpuTextureCreateView(texture, &textureViewDesc); - WEBGPU_DEMO_CHECK(textureView, "[WebGPU] Failed to create texture view"); + app.textureView = wgpuTextureCreateView(app.texture, &textureViewDesc); + WEBGPU_DEMO_CHECK(app.textureView, "[WebGPU] Failed to create texture view"); WEBGPU_DEMO_LOG("[WebGPU] Texture view created"); // === Create the texture sampler === @@ -471,8 +679,8 @@ int main(int argc, const char* argv[]) samplerDesc.compare = WGPUCompareFunction_Undefined; samplerDesc.maxAnisotropy = 1.0f; - WGPUSampler sampler = wgpuDeviceCreateSampler(app.device, &samplerDesc); - WEBGPU_DEMO_CHECK(sampler, "[WebGPU] Failed to create sampler"); + app.sampler = wgpuDeviceCreateSampler(app.device, &samplerDesc); + WEBGPU_DEMO_CHECK(app.sampler, "[WebGPU] Failed to create sampler"); WEBGPU_DEMO_LOG("[WebGPU] Sampler created"); // === Create the shader module === @@ -526,8 +734,8 @@ int main(int argc, const char* argv[]) shaderModuleDesc.label = "Demo Shader"; shaderModuleDesc.nextInChain = (const WGPUChainedStruct*)&shaderModuleWGSLDesc; - WGPUShaderModule shaderModule = wgpuDeviceCreateShaderModule(app.device, &shaderModuleDesc); - WEBGPU_DEMO_CHECK(shaderModule, "[WebGPU] Failed to create shader module"); + app.shaderModule = wgpuDeviceCreateShaderModule(app.device, &shaderModuleDesc); + WEBGPU_DEMO_CHECK(app.shaderModule, "[WebGPU] Failed to create shader module"); WEBGPU_DEMO_LOG("[WebGPU] Shader module created"); // === Create the bind group layout === @@ -561,8 +769,9 @@ int main(int argc, const char* argv[]) bindGroupLayoutDesc.label = "Demo Bind Group Layout"; bindGroupLayoutDesc.entryCount = bindGroupLayoutEntries.size(); bindGroupLayoutDesc.entries = bindGroupLayoutEntries.data(); - WGPUBindGroupLayout bindGroupLayout = wgpuDeviceCreateBindGroupLayout(app.device, &bindGroupLayoutDesc); - WEBGPU_DEMO_CHECK(bindGroupLayout, "[WebGPU] Failed to create bind group layout"); + + app.bindGroupLayout = wgpuDeviceCreateBindGroupLayout(app.device, &bindGroupLayoutDesc); + WEBGPU_DEMO_CHECK(app.bindGroupLayout, "[WebGPU] Failed to create bind group layout"); WEBGPU_DEMO_LOG("[WebGPU] Bind group layout created"); // === Create the bind group === @@ -570,28 +779,28 @@ int main(int argc, const char* argv[]) WGPUBindGroupEntry uniformBinding = {}; uniformBinding.binding = 0; - uniformBinding.buffer = uniformBuffer; + uniformBinding.buffer = app.uniformBuffer; uniformBinding.offset = 0; uniformBinding.size = sizeof(ShaderUniformData); WGPUBindGroupEntry textureBinding = {}; textureBinding.binding = 1; - textureBinding.textureView = textureView; + textureBinding.textureView = app.textureView; WGPUBindGroupEntry samplerBinding = {}; samplerBinding.binding = 2; - samplerBinding.sampler = sampler; + samplerBinding.sampler = app.sampler; std::vector bindGroupEntries = {uniformBinding, textureBinding, samplerBinding}; WGPUBindGroupDescriptor bindGroupDesc = {}; - bindGroupDesc.layout = bindGroupLayout; + bindGroupDesc.layout = app.bindGroupLayout; bindGroupDesc.entryCount = bindGroupEntries.size(); bindGroupDesc.entries = bindGroupEntries.data(); - WGPUBindGroup bindGroup = wgpuDeviceCreateBindGroup(app.device, &bindGroupDesc); - WEBGPU_DEMO_CHECK(bindGroup, "[WebGPU] Failed to create bind group"); + app.bindGroup = wgpuDeviceCreateBindGroup(app.device, &bindGroupDesc); + WEBGPU_DEMO_CHECK(app.bindGroup, "[WebGPU] Failed to create bind group"); WEBGPU_DEMO_LOG("[WebGPU] Bind group created"); // === Create the pipeline layout === @@ -600,9 +809,10 @@ int main(int argc, const char* argv[]) WGPUPipelineLayoutDescriptor pipelineLayoutDesc = {}; pipelineLayoutDesc.label = "Demo Pipeline Layout"; pipelineLayoutDesc.bindGroupLayoutCount = 1; - pipelineLayoutDesc.bindGroupLayouts = &bindGroupLayout; - WGPUPipelineLayout pipelineLayout = wgpuDeviceCreatePipelineLayout(app.device, &pipelineLayoutDesc); - WEBGPU_DEMO_CHECK(pipelineLayout, "[WebGPU] Failed to create pipeline layout"); + pipelineLayoutDesc.bindGroupLayouts = &app.bindGroupLayout; + + app.pipelineLayout = wgpuDeviceCreatePipelineLayout(app.device, &pipelineLayoutDesc); + WEBGPU_DEMO_CHECK(app.pipelineLayout, "[WebGPU] Failed to create pipeline layout"); WEBGPU_DEMO_LOG("[WebGPU] Pipeline layout created"); // === Create the render pipeline === @@ -631,7 +841,7 @@ int main(int argc, const char* argv[]) // Configuration for the vertex shader stage WGPUVertexState vertexState = {}; - vertexState.module = shaderModule; + vertexState.module = app.shaderModule; vertexState.entryPoint = "vs_main"; vertexState.bufferCount = 1; vertexState.buffers = &vertexBufferLayout; @@ -645,7 +855,7 @@ int main(int argc, const char* argv[]) // Configuration for depth testing and stencil buffer WGPUDepthStencilState depthStencilState = {}; - depthStencilState.format = depthTextureFormat; + depthStencilState.format = app.depthTextureFormat; depthStencilState.depthWriteEnabled = true; depthStencilState.depthCompare = WGPUCompareFunction_Less; depthStencilState.stencilReadMask = 0; @@ -669,7 +879,7 @@ int main(int argc, const char* argv[]) colorTargetState.format = surfaceCapabilities.formats[0]; colorTargetState.writeMask = WGPUColorWriteMask_All; WGPUFragmentState fragmentState = {}; - fragmentState.module = shaderModule; + fragmentState.module = app.shaderModule; fragmentState.entryPoint = "fs_main"; fragmentState.targetCount = 1; fragmentState.targets = &colorTargetState; @@ -677,255 +887,92 @@ int main(int argc, const char* argv[]) // Configuration for the entire pipeline WGPURenderPipelineDescriptor pipelineDesc = {}; pipelineDesc.label = "Demo Pipeline"; - pipelineDesc.layout = pipelineLayout; + pipelineDesc.layout = app.pipelineLayout; pipelineDesc.vertex = vertexState; pipelineDesc.primitive = primitiveState; pipelineDesc.depthStencil = &depthStencilState; pipelineDesc.multisample = multisampleState; pipelineDesc.fragment = &fragmentState; - WGPURenderPipeline pipeline = wgpuDeviceCreateRenderPipeline(app.device, &pipelineDesc); - WEBGPU_DEMO_CHECK(pipeline, "[WebGPU] Failed to create render pipeline"); + app.pipeline = wgpuDeviceCreateRenderPipeline(app.device, &pipelineDesc); + WEBGPU_DEMO_CHECK(app.pipeline, "[WebGPU] Failed to create render pipeline"); WEBGPU_DEMO_LOG("[WebGPU] Render pipeline created"); +} - float camRotX = 0.0f; - float camRotY = 0.0f; +void deinitWebGPU(AppData& app) +{ + // === Release all WebGPU resources === + + wgpuRenderPipelineRelease(app.pipeline); + wgpuPipelineLayoutRelease(app.pipelineLayout); + wgpuBindGroupRelease(app.bindGroup); + wgpuBindGroupLayoutRelease(app.bindGroupLayout); + wgpuShaderModuleRelease(app.shaderModule); + wgpuSamplerRelease(app.sampler); + wgpuTextureViewRelease(app.textureView); + wgpuTextureDestroy(app.texture); + wgpuTextureRelease(app.texture); + wgpuTextureViewRelease(app.depthTextureView); + wgpuTextureDestroy(app.depthTexture); + wgpuTextureRelease(app.depthTexture); + wgpuBufferDestroy(app.uniformBuffer); + wgpuBufferRelease(app.uniformBuffer); + wgpuBufferDestroy(app.indexBuffer); + wgpuBufferRelease(app.indexBuffer); + wgpuBufferDestroy(app.vertexBuffer); + wgpuBufferRelease(app.vertexBuffer); + wgpuSurfaceRelease(app.surface); + wgpuQueueRelease(app.queue); + wgpuDeviceRelease(app.device); + wgpuAdapterRelease(app.adapter); + wgpuInstanceRelease(app.instance); + + WEBGPU_DEMO_LOG("[WebGPU] Resources released"); +} + +void deinitGLFW(AppData& app) +{ + // === Destroy the window and terminate GLFW === + + glfwDestroyWindow(app.window); + glfwTerminate(); + WEBGPU_DEMO_LOG("[GLFW] Window closed and terminated"); +} + +int main(int argc, const char* argv[]) +{ + AppData app; + initGLFW(app); + initWebGPU(app); double lastCursorX = 0.0f; double lastCursorY = 0.0f; // === Render loop === - while (!glfwWindowShouldClose(window)) + while (!glfwWindowShouldClose(app.window)) { // === Update cube model matrix === double cursorX; double cursorY; - glfwGetCursorPos(window, &cursorX, &cursorY); + glfwGetCursorPos(app.window, &cursorX, &cursorY); - if (glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_1) == GLFW_PRESS) + if (glfwGetMouseButton(app.window, GLFW_MOUSE_BUTTON_1) == GLFW_PRESS) { - camRotX -= 0.25f * static_cast(cursorY - lastCursorY); - camRotY -= 0.25f * static_cast(cursorX - lastCursorX); + app.camRotX -= 0.25f * static_cast(cursorY - lastCursorY); + app.camRotY -= 0.25f * static_cast(cursorX - lastCursorX); } lastCursorX = cursorX; lastCursorY = cursorY; - // === Create a WebGPU texture view === - // The texture view is where we render our image into. - - if (app.requireSurfaceReconfiguration) - { - app.requireSurfaceReconfiguration = false; - - // Get the window size from the GLFW window. - glfwGetWindowSize(window, &surfaceWidth, &surfaceHeight); - - // The surface size might be zero if the window is minimized. - if (surfaceWidth != 0 && surfaceHeight != 0) - { - WEBGPU_DEMO_LOG("[WebGPU] Re-configuring surface"); - surfaceConfig.width = surfaceWidth; - surfaceConfig.height = surfaceHeight; - wgpuSurfaceConfigure(app.surface, &surfaceConfig); - - // Recreate the depth texture. - - wgpuTextureViewRelease(textureView); - wgpuTextureDestroy(depthTexture); - wgpuTextureRelease(depthTexture); - - depthTextureDesc.size.width = surfaceWidth; - depthTextureDesc.size.height = surfaceHeight; - - depthTexture = wgpuDeviceCreateTexture(app.device, &depthTextureDesc); - WEBGPU_DEMO_CHECK(depthTexture, "[WebGPU] Failed to re-create depth texture"); - WEBGPU_DEMO_LOG("[WebGPU] Depth texture re-created"); - - depthTextureView = wgpuTextureCreateView(depthTexture, &depthTextureViewDesc); - WEBGPU_DEMO_CHECK(depthTextureView, "[WebGPU] Failed to re-create depth texture view"); - WEBGPU_DEMO_LOG("[WebGPU] Depth texture view re-created"); - } - - // Skip this frame. - glfwPollEvents(); - continue; - } - - // Get a texture from the surface to render into. - WGPUSurfaceTexture surfaceTexture; - wgpuSurfaceGetCurrentTexture(app.surface, &surfaceTexture); - - // The surface might change over time. - // For example, the window might be resized or minimized. - // We have to check the status and adapt to it. - switch (surfaceTexture.status) - { - case WGPUSurfaceGetCurrentTextureStatus_Success: - // Everything is ok, but still check for a suboptimal texture and re-configure it if needed. - if (surfaceTexture.suboptimal) - { - WEBGPU_DEMO_LOG("[WebGPU] Re-configuring currently suboptimal surface"); - app.requireSurfaceReconfiguration = true; - continue; - } - - break; - - case WGPUSurfaceGetCurrentTextureStatus_Timeout: - case WGPUSurfaceGetCurrentTextureStatus_Outdated: - case WGPUSurfaceGetCurrentTextureStatus_Lost: - // The surface needs to be re-configured. - app.requireSurfaceReconfiguration = true; - continue; - - case WGPUSurfaceGetCurrentTextureStatus_OutOfMemory: - case WGPUSurfaceGetCurrentTextureStatus_DeviceLost: - // An error occured. - WEBGPU_DEMO_CHECK(false, "[WebGPU] Failed to acquire current surface texture"); - break; - - case WGPUSurfaceGetCurrentTextureStatus_Force32: - break; - } - - // Create a view into the texture to specify where and how to modify the texture. - WGPUTextureView view = wgpuTextureCreateView(surfaceTexture.texture, nullptr); - - // === Prepare uniform data === - float aspectRatio = static_cast(surfaceWidth) / static_cast(surfaceHeight); - - SLMat4f modelMatrix; - - SLMat4f projectionMatrix; - projectionMatrix.perspective(70.0f, aspectRatio, 0.1, 1000.0f); - - SLMat4f viewMatrix; - viewMatrix.rotate(camRotY, SLVec3f::AXISY); - viewMatrix.rotate(camRotX, SLVec3f::AXISX); - viewMatrix.translate(0.0f, 0.0f, 2.0f); - viewMatrix.invert(); - - // === Update uniforms === - ShaderUniformData uniformData = {}; - std::memcpy(uniformData.projectionMatrix, projectionMatrix.m(), sizeof(uniformData.projectionMatrix)); - std::memcpy(uniformData.viewMatrix, viewMatrix.m(), sizeof(uniformData.viewMatrix)); - std::memcpy(uniformData.modelMatrix, modelMatrix.m(), sizeof(uniformData.modelMatrix)); - wgpuQueueWriteBuffer(queue, uniformBuffer, 0, &uniformData, sizeof(ShaderUniformData)); - - // === Create a WebGPU command encoder === - // The encoder encodes the commands for the GPU into a command buffer. - - WGPUCommandEncoderDescriptor cmdEncoderDesc = {}; - cmdEncoderDesc.label = "Demo Command Encoder"; - - WGPUCommandEncoder cmdEncoder = wgpuDeviceCreateCommandEncoder(app.device, &cmdEncoderDesc); - WEBGPU_DEMO_CHECK(cmdEncoder, "[WebGPU] Failed to create command encoder"); - - // === Create a WebGPU render pass === - // The render pass specifies what attachments to use while rendering. - // A color attachment specifies what view to render into and what to do with the texture before and after - // rendering. We clear the texture before rendering and store the results after rendering. - // The depth attachment specifies what depth texture to use. - - WGPURenderPassColorAttachment colorAttachment = {}; - colorAttachment.view = view; - colorAttachment.loadOp = WGPULoadOp_Clear; - colorAttachment.storeOp = WGPUStoreOp_Store; - colorAttachment.clearValue.r = 0.3; - colorAttachment.clearValue.g = 0.0; - colorAttachment.clearValue.b = 0.2; - colorAttachment.clearValue.a = 1.0; - - WGPURenderPassDepthStencilAttachment depthStencilAttachment = {}; - depthStencilAttachment.view = depthTextureView; - depthStencilAttachment.depthLoadOp = WGPULoadOp_Clear; - depthStencilAttachment.depthStoreOp = WGPUStoreOp_Store; - depthStencilAttachment.depthClearValue = 1.0f; - depthStencilAttachment.depthReadOnly = false; - depthStencilAttachment.stencilLoadOp = WGPULoadOp_Clear; - depthStencilAttachment.stencilStoreOp = WGPUStoreOp_Store; - depthStencilAttachment.stencilClearValue = 0.0f; - depthStencilAttachment.stencilReadOnly = true; - - WGPURenderPassDescriptor renderPassDesc = {}; - renderPassDesc.label = "Demo Render Pass"; - renderPassDesc.colorAttachmentCount = 1; - renderPassDesc.colorAttachments = &colorAttachment; - renderPassDesc.depthStencilAttachment = &depthStencilAttachment; - - // === Encode the commands === - // The commands to begin a render pass, bind a pipeline, draw the triangle and end the render pass - // are encoded into a buffer. - - WGPURenderPassEncoder renderPassEncoder = wgpuCommandEncoderBeginRenderPass(cmdEncoder, &renderPassDesc); - wgpuRenderPassEncoderSetPipeline(renderPassEncoder, pipeline); - wgpuRenderPassEncoderSetVertexBuffer(renderPassEncoder, 0, vertexBuffer, 0, vertexDataSize); - wgpuRenderPassEncoderSetIndexBuffer(renderPassEncoder, indexBuffer, WGPUIndexFormat_Uint16, 0, indexDataSize); - wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 0, bindGroup, 0, nullptr); - wgpuRenderPassEncoderDrawIndexed(renderPassEncoder, indexCount, 1, 0, 0, 0); - wgpuRenderPassEncoderEnd(renderPassEncoder); - - // === Get the command buffer === - // The command encoder is finished to get the commands for the GPU to execute in a command buffer. - - WGPUCommandBufferDescriptor cmdBufferDesc = {}; - cmdBufferDesc.label = "Demo Command Buffer"; - - WGPUCommandBuffer cmdBuffer = wgpuCommandEncoderFinish(cmdEncoder, &cmdBufferDesc); - - // === Submit the command buffer to the GPU === - // The work for the GPU is submitted through the queue and executed. - wgpuQueueSubmit(queue, 1, &cmdBuffer); - - // === Present the surface === - // This presents our rendered texture to the screen. - wgpuSurfacePresent(app.surface); - - // === Clean up resources === - wgpuCommandBufferRelease(cmdBuffer); - wgpuRenderPassEncoderRelease(renderPassEncoder); - wgpuCommandEncoderRelease(cmdEncoder); - wgpuTextureViewRelease(view); - wgpuTextureRelease(surfaceTexture.texture); - + onPaint(app); glfwPollEvents(); } - // === Release all WebGPU resources === - - wgpuRenderPipelineRelease(pipeline); - wgpuPipelineLayoutRelease(pipelineLayout); - wgpuBindGroupRelease(bindGroup); - wgpuBindGroupLayoutRelease(bindGroupLayout); - wgpuShaderModuleRelease(shaderModule); - wgpuSamplerRelease(sampler); - wgpuTextureViewRelease(textureView); - wgpuTextureDestroy(texture); - wgpuTextureRelease(texture); - wgpuTextureViewRelease(depthTextureView); - wgpuTextureDestroy(depthTexture); - wgpuTextureRelease(depthTexture); - wgpuBufferDestroy(uniformBuffer); - wgpuBufferRelease(uniformBuffer); - wgpuBufferDestroy(indexBuffer); - wgpuBufferRelease(indexBuffer); - wgpuBufferDestroy(vertexBuffer); - wgpuBufferRelease(vertexBuffer); - wgpuSurfaceRelease(app.surface); - wgpuQueueRelease(queue); - wgpuDeviceRelease(app.device); - wgpuAdapterRelease(adapter); - wgpuInstanceRelease(instance); - WEBGPU_DEMO_LOG("[WebGPU] Resources released"); - - // === Destroy the window and terminate GLFW === - - glfwDestroyWindow(window); - glfwTerminate(); - WEBGPU_DEMO_LOG("[GLFW] Window closed and terminated"); + deinitWebGPU(app); + deinitGLFW(app); return 0; } \ No newline at end of file From d3d5336a3ce729f49e616925a7a4e7fad2775cb9 Mon Sep 17 00:00:00 2001 From: Marino von Wattenwyl Date: Thu, 9 Nov 2023 12:23:50 +0100 Subject: [PATCH 27/31] [WebGPU] Add normal mapping to demo --- apps/webgpu/main.cpp | 138 ++++++++++++++++++++++++++----------------- 1 file changed, 84 insertions(+), 54 deletions(-) diff --git a/apps/webgpu/main.cpp b/apps/webgpu/main.cpp index 316b3fa5..b52d794d 100644 --- a/apps/webgpu/main.cpp +++ b/apps/webgpu/main.cpp @@ -77,6 +77,18 @@ struct AppData float camZ = 2.0f; }; +struct VertexData +{ + float positionX; + float positionY; + float positionZ; + float normalX; + float normalY; + float normalZ; + float uvX; + float uvY; +}; + struct alignas(16) ShaderUniformData { float projectionMatrix[16]; @@ -124,6 +136,9 @@ void reconfigureSurface(AppData& app) void onPaint(AppData& app) { + if (app.surfaceWidth == 0 || app.surfaceHeight == 0) + return; + // Get a texture from the surface to render into. WGPUSurfaceTexture surfaceTexture; wgpuSurfaceGetCurrentTexture(app.surface, &surfaceTexture); @@ -345,11 +360,11 @@ void initWebGPU(AppData& app) // which is how WebGPU prevents code from working on one machine and not on another. WGPURequiredLimits requiredLimits = {}; - requiredLimits.limits.maxVertexAttributes = 2u; + requiredLimits.limits.maxVertexAttributes = 3u; requiredLimits.limits.maxVertexBuffers = 1u; - requiredLimits.limits.maxBufferSize = 1024ull; - requiredLimits.limits.maxVertexBufferArrayStride = 5u * sizeof(float); - requiredLimits.limits.maxInterStageShaderComponents = 2u; + requiredLimits.limits.maxBufferSize = 2048ull; + requiredLimits.limits.maxVertexBufferArrayStride = sizeof(VertexData); + requiredLimits.limits.maxInterStageShaderComponents = 5u; requiredLimits.limits.maxBindGroups = 1u; requiredLimits.limits.maxBindingsPerBindGroup = 3u; requiredLimits.limits.maxUniformBuffersPerShaderStage = 1u; @@ -429,60 +444,60 @@ void initWebGPU(AppData& app) // The vertex buffer contains the input data for the shader. // clang-format off - std::vector vertexData = + std::vector vertexData = { // left - -0.5, 0.5, -0.5, 0.0f, 0.0f, - -0.5, -0.5, -0.5, 0.0f, 1.0f, - -0.5, 0.5, 0.5, 1.0f, 0.0f, - -0.5, 0.5, 0.5, 1.0f, 0.0f, - -0.5, -0.5, -0.5, 0.0f, 1.0f, - -0.5, -0.5, 0.5, 1.0f, 1.0f, + {-0.5, 0.5, -0.5, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f}, + {-0.5, -0.5, -0.5, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f}, + {-0.5, 0.5, 0.5, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f}, + {-0.5, 0.5, 0.5, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f}, + {-0.5, -0.5, -0.5, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f}, + {-0.5, -0.5, 0.5, -1.0f, 0.0f, 0.0f, 1.0f, 1.0f}, // right - 0.5, 0.5, 0.5, 0.0f, 0.0f, - 0.5, -0.5, 0.5, 0.0f, 1.0f, - 0.5, 0.5, -0.5, 1.0f, 0.0f, - 0.5, 0.5, -0.5, 1.0f, 0.0f, - 0.5, -0.5, 0.5, 0.0f, 1.0f, - 0.5, -0.5, -0.5, 1.0f, 1.0f, + { 0.5, 0.5, 0.5, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f}, + { 0.5, -0.5, 0.5, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f}, + { 0.5, 0.5, -0.5, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f}, + { 0.5, 0.5, -0.5, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f}, + { 0.5, -0.5, 0.5, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f}, + { 0.5, -0.5, -0.5, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f}, // bottom - -0.5, -0.5, 0.5, 0.0f, 0.0f, - -0.5, -0.5, -0.5, 0.0f, 1.0f, - 0.5, -0.5, 0.5, 1.0f, 0.0f, - 0.5, -0.5, 0.5, 1.0f, 0.0f, - -0.5, -0.5, -0.5, 0.0f, 1.0f, - 0.5, -0.5, -0.5, 1.0f, 1.0f, + {-0.5, -0.5, 0.5, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f}, + {-0.5, -0.5, -0.5, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f}, + { 0.5, -0.5, 0.5, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f}, + { 0.5, -0.5, 0.5, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f}, + {-0.5, -0.5, -0.5, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f}, + { 0.5, -0.5, -0.5, 0.0f, -1.0f, 0.0f, 1.0f, 1.0f}, // top - -0.5, 0.5, -0.5, 0.0f, 0.0f, - -0.5, 0.5, 0.5, 0.0f, 1.0f, - 0.5, 0.5, -0.5, 1.0f, 0.0f, - 0.5, 0.5, -0.5, 1.0f, 0.0f, - -0.5, 0.5, 0.5, 0.0f, 1.0f, - 0.5, 0.5, 0.5, 1.0f, 1.0f, + {-0.5, 0.5, -0.5, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f}, + {-0.5, 0.5, 0.5, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f}, + { 0.5, 0.5, -0.5, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f}, + { 0.5, 0.5, -0.5, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f}, + {-0.5, 0.5, 0.5, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f}, + { 0.5, 0.5, 0.5, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f}, // back - 0.5, 0.5, -0.5, 0.0f, 0.0f, - 0.5, -0.5, -0.5, 0.0f, 1.0f, - -0.5, 0.5, -0.5, 1.0f, 0.0f, - -0.5, 0.5, -0.5, 1.0f, 0.0f, - 0.5, -0.5, -0.5, 0.0f, 1.0f, - -0.5, -0.5, -0.5, 1.0f, 1.0f, + { 0.5, 0.5, -0.5, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f}, + { 0.5, -0.5, -0.5, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f}, + {-0.5, 0.5, -0.5, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f}, + {-0.5, 0.5, -0.5, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f}, + { 0.5, -0.5, -0.5, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f}, + {-0.5, -0.5, -0.5, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f}, // front - -0.5, 0.5, 0.5, 0.0f, 0.0f, - -0.5, -0.5, 0.5, 0.0f, 1.0f, - 0.5, 0.5, 0.5, 1.0f, 0.0f, - 0.5, 0.5, 0.5, 1.0f, 0.0f, - -0.5, -0.5, 0.5, 0.0f, 1.0f, - 0.5, -0.5, 0.5, 1.0f, 1.0f, + {-0.5, 0.5, 0.5, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f}, + {-0.5, -0.5, 0.5, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f}, + { 0.5, 0.5, 0.5, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f}, + { 0.5, 0.5, 0.5, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f}, + {-0.5, -0.5, 0.5, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f}, + { 0.5, -0.5, 0.5, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f}, }; // clang-format on - unsigned vertexCount = vertexData.size() / 5; - app.vertexDataSize = vertexData.size() * sizeof(float); + unsigned vertexCount = vertexData.size(); + app.vertexDataSize = vertexData.size() * sizeof(VertexData); WGPUBufferDescriptor vertexBufferDesc = {}; vertexBufferDesc.label = "Demo Vertex Buffer"; @@ -587,7 +602,7 @@ void initWebGPU(AppData& app) WEBGPU_DEMO_CHECK(app.texture, "[WebGPU] Failed to create texture"); WEBGPU_DEMO_LOG("[WebGPU] Texture created"); - // Where do we copyu the pixel data to? + // Where do we copy the pixel data to? WGPUImageCopyTexture destination = {}; destination.texture = app.texture; destination.mipLevel = 0; @@ -691,7 +706,8 @@ void initWebGPU(AppData& app) const char* shaderSource = R"( struct VertexInput { @location(0) position: vec3f, - @location(1) uvs: vec2f, + @location(1) normal: vec3f, + @location(2) uv: vec2f, }; struct Uniforms { @@ -702,9 +718,12 @@ void initWebGPU(AppData& app) struct VertexOutput { @builtin(position) position: vec4f, - @location(0) uvs: vec2f, + @location(0) worldNormal: vec3f, + @location(1) uv: vec2f, } + const LIGHT_DIR = vec3f(4.0, -8.0, 1.0); + @group(0) @binding(0) var uniforms: Uniforms; @group(0) @binding(1) var texture: texture_2d; @group(0) @binding(2) var textureSampler: sampler; @@ -716,13 +735,19 @@ void initWebGPU(AppData& app) var out: VertexOutput; out.position = uniforms.projectionMatrix * uniforms.viewMatrix * worldPos; - out.uvs = in.uvs; + out.worldNormal = in.normal; + out.uv = in.uv; return out; } @fragment fn fs_main(in: VertexOutput) -> @location(0) vec4f { - return textureSample(texture, textureSampler, in.uvs).rgba; + var baseColor = textureSample(texture, textureSampler, in.uv); + + var diffuse = dot(-normalize(in.worldNormal), normalize(LIGHT_DIR)); + diffuse = diffuse * 0.5 + 0.5; + + return vec4(baseColor.rgb * diffuse, baseColor.a); } )"; @@ -825,16 +850,21 @@ void initWebGPU(AppData& app) positionAttribute.offset = 0; positionAttribute.shaderLocation = 0; - WGPUVertexAttribute uvsAttribute = {}; - uvsAttribute.format = WGPUVertexFormat_Float32x2; - uvsAttribute.offset = 3 * sizeof(float); - uvsAttribute.shaderLocation = 1; + WGPUVertexAttribute normalAttribute = {}; + normalAttribute.format = WGPUVertexFormat_Float32x3; + normalAttribute.offset = 3 * sizeof(float); + normalAttribute.shaderLocation = 1; + + WGPUVertexAttribute uvAttribute = {}; + uvAttribute.format = WGPUVertexFormat_Float32x2; + uvAttribute.offset = 6 * sizeof(float); + uvAttribute.shaderLocation = 2; - std::vector vertexAttributes = {positionAttribute, uvsAttribute}; + std::vector vertexAttributes = {positionAttribute, normalAttribute, uvAttribute}; // Description of the vertex buffer layout for the vertex shader stage WGPUVertexBufferLayout vertexBufferLayout = {}; - vertexBufferLayout.arrayStride = 5ull * sizeof(float); + vertexBufferLayout.arrayStride = sizeof(VertexData); vertexBufferLayout.stepMode = WGPUVertexStepMode_Vertex; vertexBufferLayout.attributeCount = vertexAttributes.size(); vertexBufferLayout.attributes = vertexAttributes.data(); From c4963b915493524809a92683611977c9a72cde3e Mon Sep 17 00:00:00 2001 From: Marcus Hudritsch Date: Thu, 9 Nov 2023 15:21:59 +0100 Subject: [PATCH 28/31] Update main.cpp --- apps/webgpu/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/webgpu/main.cpp b/apps/webgpu/main.cpp index e4c2999a..7e154334 100644 --- a/apps/webgpu/main.cpp +++ b/apps/webgpu/main.cpp @@ -130,7 +130,7 @@ int main(int argc, const char* argv[]) // === Acquire a WebGPU device === // A device provides access to a GPU and is created from an adapter. - // We specify the capabilites that we require our device to have in requiredLimits. + // We specify the capabilities that we require our device to have in requiredLimits. // We cannot access more resources than specified in the required limits, // which is how WebGPU prevents code from working on one machine and not on another. From f0420edab9e946498d63b2a5be4836eac7d49cfa Mon Sep 17 00:00:00 2001 From: Marino von Wattenwyl Date: Mon, 13 Nov 2023 10:44:52 +0100 Subject: [PATCH 29/31] [WebGPU] Document WebGPU concepts --- apps/webgpu/main.cpp | 200 ++++++++++++++++++++++++++++++++----------- 1 file changed, 151 insertions(+), 49 deletions(-) diff --git a/apps/webgpu/main.cpp b/apps/webgpu/main.cpp index 71c488b1..93f9b41c 100644 --- a/apps/webgpu/main.cpp +++ b/apps/webgpu/main.cpp @@ -28,7 +28,7 @@ #include #define WEBGPU_DEMO_LOG(msg) std::cout << (msg) << std::endl - + #define WEBGPU_DEMO_CHECK(condition, errorMsg) \ if (!(condition)) \ { \ @@ -36,7 +36,134 @@ std::exit(1); \ } -struct AppData +/* +To interact with WebGPU, we create objects with a call to wgpu*Create*. This function normally takes the parent +object as a parameter and a WGPU*Descriptor that contains the specification for the object. The returned object +is released after usage with a call to wgpuRelease*. An object can have a label that is used when reporting errors. + +WebGPU concepts: + - WGPUInstance + The core interface through which all other objects are created. + + - WGPUAdapter + Represents a physical device and is used to query its capabilities and limits. + + - WGPUDevice + Represents a logical device we interact with. It is created by specifying the capabilities we require + and fails if the adapter does not support them. We cannot create more resources than specified in the + limits at creation. This is done to prevent a function from working on one device but not on another. + + - WGPUQueue + The queue is where we submit the commands for the GPU to. Everything that is executed on the GPU goes + through the queue. Examples are executing rendering or compute pipelines, writing to or reading from buffers. + + - WGPUSurface + The surface we draw onto. How it is acquired depends on the OS so we have to manually convert window handles + from the different platforms to surfaces. The surface has to be reconfigured every time the window size + changes. + + - WGPUBuffer + Represents a chunk of memory on the GPU. They can be used for example as vertex buffers, uniforms buffers or + storage buffers. Buffers are created with a fixed size and usage flags that specify e.g. whether we can copy + to this buffer or whether it is used as an index buffer. The memory for the buffer is automatically allocated + at creation but has to be deallocated manually using wgpuBufferDestroy. wgpuQueueWriteBuffer is used to upload + buffer data to the GPU. + + - WGPUTexture + Represents pixel data in the memory of the GPU. It is similar to a buffer in that memory for it is allocated at + creation, that it has usage flags and that the memory is deallocated using wgpuTextureDestroy. Data is uploaded + using wgpuQueueWriteTexture. A texture additionally has a size, a format, a mip level count and a sample count. + Textures can be used in shaders or as a render attachment (e.g. the depth buffer). Mip maps have to be created + manually. + + - WGPUTextureView + To use a texture, a texture view has to be created. It specifies which part of the texture is accessed + (which base array layer, how many array layers, which base mip level, which format, ...). + + - WGPUTextureSampler + To read a texture in a shader, a sampler is commonly used. Textures can also be accessed directly without a + sampler by specifying texel coordinates, which is more like reading the data from a buffer. To get access to + features like filtering, mipmapping or clamping, a sampler is used. + + - WGPURenderPipeline + Represents a configuration of the GPU pipeline. It specifies completely how input data is transformed into + output pixels by settings the configuration for the GPU pipeline stages. + + The WebGPU render pipeline model looks like this: + 1. vertex fetch + 2. vertex shader + 3. primitive assembly + 4. rasterization + 5. fragment shader + 6. stencil test and write + 7. depth test and write + 8. blending + 9. write to attachments + + The descriptor for this structure contains the following configurations: + + - layout: WGPUPipelineLayout + - vertex: WGPUVertexState + Configuration for the vertex fetch and vertex shader stages. + Specifies the shader module to run as well as the buffers and constants used. + A vertex buffer is specified using a WGPUVertexBufferLayout structure that contains a list of + attributes (WGPUVertexAttribute) along with the stride and step mode (per vertex or per instance). + Attributes are specified through a format, an offset in the data and a location in the shader. + The location is a number hard-coded for the attribute in the shader code. + - primitive: WGPUPrimitiveState + Configuration for the primitive assembly and rasterization stages. + Specifies the topology (triangles, triangle strips, lines, ...), what index format + is used, how the face orientation of triangles is defined and the cull mode. + - depthStencil: WGPUDepthStencilState + Configuration for the depth test and stencil test stages. + Specifies the format of the depth/stencil buffer and how depth and stencil testing are performed. + - multisample: WGPUMultisampleState + Configuration for multisampling. + Specifies the number of samples per pixel as well as additional parameters for muiltisampling. + - fragment: WGPUFragmentState + Configuration for the fragment shader and blending stages. + Specifies the shader module to run as well as the buffers and constants used. + This state also specifies a list of color targets that are rendered into which contains + additional configuration such as the color attachement format and the blending mode. + + - WGPUShaderModule + Represents a shader on the GPU. It is created by specifying code in either the WebGPU shading language (WGSL) + or the SPIR-V format (not support on the Web). This code is then compiled by the WebGPU implementation to a + backend-specific shader format such as SPIR-V for Vulkan or MSL for Metal. A shader can have multiple entry + points, making it possible to use one shader module for both the vertex shader and fragment shader stage. + + - WGPUBindGroup + A list of resources bound in a shader. Resources can be buffers, samplers or texture views. Each bind group + entry contains a binding (a unique number assigned to the resource in the shader code), the buffer, sampler or + texture view bound, an offset and a size. Multiple bind groups can be set per render pass. + + - WGPUBindGroupLayout + A list of layouts for bind groups. A bind group references its layout and they both have to have the same + number of entries. The entries describe the binding, which shader stages can access it as well as additional + info depending on the type. For example, buffer bind group layout entries specify whether they are a uniform + buffer, a storage buffer or read-only storage. + + - WGPUPipelineLayout + Specifies the bind groups used in the pipeline. + + - WGPUCommandEncoder + Work for the GPU has to be recorded into a command buffer. This is done using a command encoder. We call + functions on the encoder (wgpuCommandEncoder*) to encode the commands into a buffer that can be accessed by + calling wgpuCommandEncoderFinish. + + - WGPUCommandBuffer + When all the GPU commands are recorded, wgpuCommandEncoderFinish is called on the queue which returns a + command buffer. This buffer can then be submitted to the GPU using wgpuQueueSubmit. + + - WGPURenderPassEncoder + Specifies how a render pipeline is executed. It encodes the pipeline used along with the required vertex + buffers, index buffers, bind groups, drawing commands, etc. Accessing these render commands is done using a + specialized object called a render pass encoder. It is created from a command encoder using + wgpuCommandEncoderBeginRenderPass. + +*/ + +struct App { GLFWwindow* window = nullptr; int surfaceWidth = 0; @@ -47,11 +174,11 @@ struct AppData WGPUDevice device = nullptr; WGPUQueue queue = nullptr; WGPUSurface surface = nullptr; - WGPUTexture depthTexture = nullptr; - WGPUTextureView depthTextureView = nullptr; WGPUBuffer vertexBuffer = nullptr; WGPUBuffer indexBuffer = nullptr; WGPUBuffer uniformBuffer = nullptr; + WGPUTexture depthTexture = nullptr; + WGPUTextureView depthTextureView = nullptr; WGPUTexture texture = nullptr; WGPUTextureView textureView = nullptr; WGPUSampler sampler = nullptr; @@ -70,8 +197,6 @@ struct AppData unsigned indexCount; unsigned indexDataSize; - bool requireSurfaceReconfiguration = false; - float camRotX = 0.0f; float camRotY = 0.0f; float camZ = 2.0f; @@ -102,7 +227,7 @@ static_assert(sizeof(ShaderUniformData) % 16 == 0, "uniform data size must be a extern "C" void* createMetalLayer(void* window); #endif -void reconfigureSurface(AppData& app) +void reconfigureSurface(App& app) { // Get the window size from the GLFW window. glfwGetWindowSize(app.window, &app.surfaceWidth, &app.surfaceHeight); @@ -134,7 +259,7 @@ void reconfigureSurface(AppData& app) WEBGPU_DEMO_LOG("[WebGPU] Depth texture view re-created"); } -void onPaint(AppData& app) +void onPaint(App& app) { if (app.surfaceWidth == 0 || app.surfaceHeight == 0) return; @@ -283,12 +408,12 @@ void onPaint(AppData& app) void onResize(GLFWwindow* window, int width, int height) { - AppData& app = *((AppData*)glfwGetWindowUserPointer(window)); + App& app = *((App*)glfwGetWindowUserPointer(window)); reconfigureSurface(app); onPaint(app); } -void initGLFW(AppData& app) +void initGLFW(App& app) { // === Initialize GLFW === @@ -334,7 +459,7 @@ void handleDeviceRequest(WGPURequestDeviceStatus status, *outDevice = device; } -void initWebGPU(AppData& app) +void initWebGPU(App& app) { // === Create a WebGPU instance === // The instance is the root interface to WebGPU through which we create all other WebGPU resources. @@ -450,48 +575,36 @@ void initWebGPU(AppData& app) {-0.5, 0.5, -0.5, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f}, {-0.5, -0.5, -0.5, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f}, {-0.5, 0.5, 0.5, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f}, - {-0.5, 0.5, 0.5, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f}, - {-0.5, -0.5, -0.5, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f}, {-0.5, -0.5, 0.5, -1.0f, 0.0f, 0.0f, 1.0f, 1.0f}, // right { 0.5, 0.5, 0.5, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f}, { 0.5, -0.5, 0.5, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f}, { 0.5, 0.5, -0.5, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f}, - { 0.5, 0.5, -0.5, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f}, - { 0.5, -0.5, 0.5, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f}, { 0.5, -0.5, -0.5, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f}, // bottom {-0.5, -0.5, 0.5, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f}, {-0.5, -0.5, -0.5, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f}, { 0.5, -0.5, 0.5, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f}, - { 0.5, -0.5, 0.5, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f}, - {-0.5, -0.5, -0.5, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f}, { 0.5, -0.5, -0.5, 0.0f, -1.0f, 0.0f, 1.0f, 1.0f}, // top {-0.5, 0.5, -0.5, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f}, {-0.5, 0.5, 0.5, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f}, { 0.5, 0.5, -0.5, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f}, - { 0.5, 0.5, -0.5, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f}, - {-0.5, 0.5, 0.5, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f}, { 0.5, 0.5, 0.5, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f}, // back { 0.5, 0.5, -0.5, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f}, { 0.5, -0.5, -0.5, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f}, {-0.5, 0.5, -0.5, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f}, - {-0.5, 0.5, -0.5, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f}, - { 0.5, -0.5, -0.5, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f}, {-0.5, -0.5, -0.5, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f}, // front {-0.5, 0.5, 0.5, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f}, {-0.5, -0.5, 0.5, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f}, { 0.5, 0.5, 0.5, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f}, - { 0.5, 0.5, 0.5, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f}, - {-0.5, -0.5, 0.5, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f}, { 0.5, -0.5, 0.5, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f}, }; // clang-format on @@ -513,9 +626,17 @@ void initWebGPU(AppData& app) // === Create the index buffer === - std::vector indexData = {}; - for (std::uint16_t index = 0; index < 36; index++) - indexData.push_back(index); + // clang-format off + std::vector indexData = + { + 0, 1, 2, 2, 1, 3, // left + 4, 5, 6, 6, 5, 7, // right + 8, 9, 10, 10, 9, 11, // bottom + 12, 13, 14, 14, 13, 15, // top + 16, 17, 18, 18, 17, 19, // back + 20, 21, 22, 22, 21, 23, // front + }; + // clang-format on app.indexCount = indexData.size(); app.indexDataSize = indexData.size() * sizeof(std::uint16_t); @@ -626,25 +747,6 @@ void initWebGPU(AppData& app) for (unsigned mipLevel = 0; mipLevel < textureDesc.mipLevelCount; mipLevel++) { - // === Test colors === - // - // std::uint64_t mipLevelColors[] = - // { - // 0xFF0000FF, - // 0xFFFF00FF, - // 0x00FF00FF, - // 0x0000FFFF - // }; - // - // for (unsigned y = 0; y < mipLevelSize.height; y++) - // { - // for (unsigned x = 0; x < mipLevelSize.width; x++) - // { - // unsigned pixelIndex = x + y * mipLevelSize.width; - // std::memcpy(&mipLevelData[4ull * pixelIndex], &mipLevelColors[mipLevel], 4); - // } - // } - cv::Mat mipLevelImage; cv::Size cvSize(static_cast(mipLevelSize.width), static_cast(mipLevelSize.height)); cv::resize(image, mipLevelImage, cvSize); @@ -881,7 +983,7 @@ void initWebGPU(AppData& app) primitiveState.topology = WGPUPrimitiveTopology_TriangleList; primitiveState.stripIndexFormat = WGPUIndexFormat_Undefined; primitiveState.frontFace = WGPUFrontFace_CCW; - primitiveState.cullMode = WGPUCullMode_None; + primitiveState.cullMode = WGPUCullMode_Back; // Configuration for depth testing and stencil buffer WGPUDepthStencilState depthStencilState = {}; @@ -929,7 +1031,7 @@ void initWebGPU(AppData& app) WEBGPU_DEMO_LOG("[WebGPU] Render pipeline created"); } -void deinitWebGPU(AppData& app) +void deinitWebGPU(App& app) { // === Release all WebGPU resources === @@ -960,7 +1062,7 @@ void deinitWebGPU(AppData& app) WEBGPU_DEMO_LOG("[WebGPU] Resources released"); } -void deinitGLFW(AppData& app) +void deinitGLFW(App& app) { // === Destroy the window and terminate GLFW === @@ -971,7 +1073,7 @@ void deinitGLFW(AppData& app) int main(int argc, const char* argv[]) { - AppData app; + App app; initGLFW(app); initWebGPU(app); From 79b7ebee5061c4b39a60d5cded7cebec37050e84 Mon Sep 17 00:00:00 2001 From: Marino von Wattenwyl Date: Thu, 16 Nov 2023 11:30:14 +0100 Subject: [PATCH 30/31] [WebGPU] Update demo docs --- apps/webgpu/CMakeLists.txt | 2 +- apps/webgpu/{main.cpp => webgpu_demo.cpp} | 139 ++++++++++++++++------ 2 files changed, 104 insertions(+), 37 deletions(-) rename apps/webgpu/{main.cpp => webgpu_demo.cpp} (92%) diff --git a/apps/webgpu/CMakeLists.txt b/apps/webgpu/CMakeLists.txt index fff48076..564ea90b 100644 --- a/apps/webgpu/CMakeLists.txt +++ b/apps/webgpu/CMakeLists.txt @@ -60,7 +60,7 @@ if (SYSTEM_NAME_UPPER MATCHES "DARWIN") set(TARGET_SPECIFIC_SOURCES metal_layer.mm) endif () -add_executable(webgpu-demo main.cpp ${TARGET_SPECIFIC_SOURCES}) +add_executable(webgpu-demo webgpu_demo.cpp ${TARGET_SPECIFIC_SOURCES}) target_link_libraries(webgpu-demo PRIVATE wgpu sl_cv sl_math ${glfw_LIBS}) if (SYSTEM_NAME_UPPER MATCHES "LINUX") diff --git a/apps/webgpu/main.cpp b/apps/webgpu/webgpu_demo.cpp similarity index 92% rename from apps/webgpu/main.cpp rename to apps/webgpu/webgpu_demo.cpp index 93f9b41c..64a97a4a 100644 --- a/apps/webgpu/main.cpp +++ b/apps/webgpu/webgpu_demo.cpp @@ -1,47 +1,45 @@ -#if defined(_WIN32) -# define SYSTEM_WINDOWS -# define GLFW_EXPOSE_NATIVE_WIN32 -# define WIN32_LEAN_AND_MEAN -# include -#elif defined(__linux__) -# define SYSTEM_LINUX -# define GLFW_EXPOSE_NATIVE_X11 -#elif defined(__APPLE__) -# ifndef SYSTEM_DARWIN -# define SYSTEM_DARWIN -# endif -# define GLFW_EXPOSE_NATIVE_COCOA -#endif +/* -#include -#include -#include -#include -#include -#include -#include +-- Overview -#include -#include -#include -#include -#include +WebGPU is a graphics API standard developed by the World Wide Web Consortium (W3C). It is an abstraction layer +over modern graphics APIs like Vulkan, D3D12 and Metal and thus uses modern concepts such as queues, command buffers +or pipelines. WebGPU is supposed to be 'safe' API and is generally simpler to use than Vulkan. Both graphics and +compute functionality is available. -#define WEBGPU_DEMO_LOG(msg) std::cout << (msg) << std::endl - -#define WEBGPU_DEMO_CHECK(condition, errorMsg) \ - if (!(condition)) \ - { \ - std::cerr << (errorMsg) << std::endl; \ - std::exit(1); \ - } +The primary target for WebGPU was the Web as a replacement for the old WebGL API. This means that +the specification is written for a JavaScript API. However, Google and Mozilla have decided to provide their in-browser +implementations as native libraries, so we can use WebGPU in native apps written in C, C++ or Rust. The implementers +have agreed on a common interface for their libraries in form of a header called `webgpu.h` +(https://github.com/webgpu-native/webgpu-headers/). -/* -To interact with WebGPU, we create objects with a call to wgpu*Create*. This function normally takes the parent +There are currently three implementations of this header: + - wgpu-native: Mozilla's implementation for Firefox, written in Rust + - Dawn: Google's implementation for Chromium, written in C++ + - Emscripten: Translates the webgpu.h calls to JavaScript calls in the browser + +WebGPU uses its own shader language called WGSL (WebGPU Shader Language). This is the only shader language supported +in the browsers even though the native implementations also support SPIR-V. + +To make porting to WebGPU easier, the two browser vendors provide tools for translating other shader languages to WGSL: + - Naga: Mozilla's shader translator, can be used only as an executable + - Tint: Google's shader translator, can be used as both a C++ library and an executable + +This demo uses the wgpu-native implementation because Mozilla releases pre-built binaries that can easily be +downloaded from CMake. Since wgpu-native uses the same interface header as Dawn, it would also be possible to +link Dawn instead of wgpu-native, but we would have to build and distribute it ourselves. + + +-- Usage + +WebGPU follows a stateless design as opposed to OpenGL, where much state has to be set globally before making a +draw call. To use WebGPU, we create objects with a call to wgpu*Create*. This function generally takes the parent object as a parameter and a WGPU*Descriptor that contains the specification for the object. The returned object is released after usage with a call to wgpuRelease*. An object can have a label that is used when reporting errors. +We can now call functions on the object to interact with them. WebGPU concepts: + - WGPUInstance The core interface through which all other objects are created. @@ -161,8 +159,77 @@ WebGPU concepts: specialized object called a render pass encoder. It is created from a command encoder using wgpuCommandEncoderBeginRenderPass. + +-- WebGPU vs. Vulkan + +Here's a list of things I've noticed are handled differently from Vulkan (as of 2023-11-26): + + - There is no multithreading + Unlike Vukan, WebGPU is currently single-threaded and doesn't allow encoding command buffers on + multiple threads. + Status: https://gpuweb.github.io/gpuweb/explainer/#multithreading + + - There is only one queue + Vulkan allows you to create multiple queues from different families. For example, you can create a + graphics and a compute queue and submit commands to them that are processed in parallel on some GPUs. + In WebGPU, there is only one queue that is acquired using wgpuDeviceGetQueue. + Discussion: https://github.com/gpuweb/gpuweb/issues/1065 + + - There is no manual memory allocation + In WebGPU, buffers take care of memory allocation internally. Awesome! + + - There is no swap chain + The swap chain configuration is part of the surface configuration. + + - Error handling is very different + In Vulkan, there is by default no validation and programs just silently crash and burn. It is possible + to enable validation layers that print errors with references to the spec. The reason for this is that + Vulkan wants to avoid validation overhead in release builds. In WebGPU, validation seems mostly up to the + implementations. We currently use the wgpu-native implementation, which seems to catch all errors and prints + most of the time a nice error message with a human-readable error message including the labels of the + problematic objects, suggests fixes and even generates a stack trace. I'm not sure what the overhead of + this validation is, but I excpect there to be an option to turn it off in the future. + */ +#if defined(_WIN32) +# define SYSTEM_WINDOWS +# define GLFW_EXPOSE_NATIVE_WIN32 +# define WIN32_LEAN_AND_MEAN +# include +#elif defined(__linux__) +# define SYSTEM_LINUX +# define GLFW_EXPOSE_NATIVE_X11 +#elif defined(__APPLE__) +# ifndef SYSTEM_DARWIN +# define SYSTEM_DARWIN +# endif +# define GLFW_EXPOSE_NATIVE_COCOA +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#define WEBGPU_DEMO_LOG(msg) std::cout << (msg) << std::endl + +#define WEBGPU_DEMO_CHECK(condition, errorMsg) \ + if (!(condition)) \ + { \ + std::cerr << (errorMsg) << std::endl; \ + std::exit(1); \ + } + struct App { GLFWwindow* window = nullptr; From f6c3c9fc4d35cc828c682a9ad9cb98e84344e641 Mon Sep 17 00:00:00 2001 From: Marcus Hudritsch Date: Mon, 27 Nov 2023 14:29:46 +0100 Subject: [PATCH 31/31] Cosmetics Crashes on MacOS on wgpuAdapterGetLimits --- apps/webgpu/webgpu_demo.cpp | 41 +++++++++++++++++++++---------------- 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/apps/webgpu/webgpu_demo.cpp b/apps/webgpu/webgpu_demo.cpp index 64a97a4a..d8059660 100644 --- a/apps/webgpu/webgpu_demo.cpp +++ b/apps/webgpu/webgpu_demo.cpp @@ -42,47 +42,47 @@ WebGPU concepts: - WGPUInstance The core interface through which all other objects are created. - + - WGPUAdapter Represents a physical device and is used to query its capabilities and limits. - + - WGPUDevice Represents a logical device we interact with. It is created by specifying the capabilities we require and fails if the adapter does not support them. We cannot create more resources than specified in the limits at creation. This is done to prevent a function from working on one device but not on another. - + - WGPUQueue The queue is where we submit the commands for the GPU to. Everything that is executed on the GPU goes through the queue. Examples are executing rendering or compute pipelines, writing to or reading from buffers. - + - WGPUSurface The surface we draw onto. How it is acquired depends on the OS so we have to manually convert window handles from the different platforms to surfaces. The surface has to be reconfigured every time the window size changes. - + - WGPUBuffer Represents a chunk of memory on the GPU. They can be used for example as vertex buffers, uniforms buffers or storage buffers. Buffers are created with a fixed size and usage flags that specify e.g. whether we can copy to this buffer or whether it is used as an index buffer. The memory for the buffer is automatically allocated at creation but has to be deallocated manually using wgpuBufferDestroy. wgpuQueueWriteBuffer is used to upload buffer data to the GPU. - + - WGPUTexture Represents pixel data in the memory of the GPU. It is similar to a buffer in that memory for it is allocated at creation, that it has usage flags and that the memory is deallocated using wgpuTextureDestroy. Data is uploaded using wgpuQueueWriteTexture. A texture additionally has a size, a format, a mip level count and a sample count. Textures can be used in shaders or as a render attachment (e.g. the depth buffer). Mip maps have to be created manually. - + - WGPUTextureView To use a texture, a texture view has to be created. It specifies which part of the texture is accessed (which base array layer, how many array layers, which base mip level, which format, ...). - + - WGPUTextureSampler To read a texture in a shader, a sampler is commonly used. Textures can also be accessed directly without a sampler by specifying texel coordinates, which is more like reading the data from a buffer. To get access to features like filtering, mipmapping or clamping, a sampler is used. - + - WGPURenderPipeline Represents a configuration of the GPU pipeline. It specifies completely how input data is transformed into output pixels by settings the configuration for the GPU pipeline stages. @@ -107,14 +107,14 @@ WebGPU concepts: A vertex buffer is specified using a WGPUVertexBufferLayout structure that contains a list of attributes (WGPUVertexAttribute) along with the stride and step mode (per vertex or per instance). Attributes are specified through a format, an offset in the data and a location in the shader. - The location is a number hard-coded for the attribute in the shader code. + The location is a number hard-coded for the attribute in the shader code. - primitive: WGPUPrimitiveState Configuration for the primitive assembly and rasterization stages. Specifies the topology (triangles, triangle strips, lines, ...), what index format is used, how the face orientation of triangles is defined and the cull mode. - depthStencil: WGPUDepthStencilState Configuration for the depth test and stencil test stages. - Specifies the format of the depth/stencil buffer and how depth and stencil testing are performed. + Specifies the format of the depth/stencil buffer and how depth and stencil testing are performed. - multisample: WGPUMultisampleState Configuration for multisampling. Specifies the number of samples per pixel as well as additional parameters for muiltisampling. @@ -139,15 +139,15 @@ WebGPU concepts: A list of layouts for bind groups. A bind group references its layout and they both have to have the same number of entries. The entries describe the binding, which shader stages can access it as well as additional info depending on the type. For example, buffer bind group layout entries specify whether they are a uniform - buffer, a storage buffer or read-only storage. + buffer, a storage buffer or read-only storage. - WGPUPipelineLayout Specifies the bind groups used in the pipeline. - + - WGPUCommandEncoder Work for the GPU has to be recorded into a command buffer. This is done using a command encoder. We call functions on the encoder (wgpuCommandEncoder*) to encode the commands into a buffer that can be accessed by - calling wgpuCommandEncoderFinish. + calling wgpuCommandEncoderFinish. - WGPUCommandBuffer When all the GPU commands are recorded, wgpuCommandEncoderFinish is called on the queue which returns a @@ -157,7 +157,7 @@ WebGPU concepts: Specifies how a render pipeline is executed. It encodes the pipeline used along with the required vertex buffers, index buffers, bind groups, drawing commands, etc. Accessing these render commands is done using a specialized object called a render pass encoder. It is created from a command encoder using - wgpuCommandEncoderBeginRenderPass. + wgpuCommandEncoderBeginRenderPass. -- WebGPU vs. Vulkan @@ -188,7 +188,7 @@ Here's a list of things I've noticed are handled differently from Vulkan (as of implementations. We currently use the wgpu-native implementation, which seems to catch all errors and prints most of the time a nice error message with a human-readable error message including the labels of the problematic objects, suggests fixes and even generates a stack trace. I'm not sure what the overhead of - this validation is, but I excpect there to be an option to turn it off in the future. + this validation is, but I excpect there to be an option to turn it off in the future. */ @@ -222,7 +222,7 @@ Here's a list of things I've noticed are handled differently from Vulkan (as of #include #define WEBGPU_DEMO_LOG(msg) std::cout << (msg) << std::endl - + #define WEBGPU_DEMO_CHECK(condition, errorMsg) \ if (!(condition)) \ { \ @@ -540,7 +540,12 @@ void initWebGPU(App& app) WGPURequestAdapterOptions adapterOptions = {}; - wgpuInstanceRequestAdapter(app.instance, &adapterOptions, handleAdapterRequest, &app.adapter); + wgpuInstanceRequestAdapter(app.instance, + &adapterOptions, + handleAdapterRequest, + &app.adapter); + WEBGPU_DEMO_CHECK(app.adapter, "[WebGPU] Failed to create adapter"); + WEBGPU_DEMO_LOG("[WebGPU] Adapter created"); WGPUSupportedLimits adapterLimits; wgpuAdapterGetLimits(app.adapter, &adapterLimits);