From cfd32501b68439811513d1999adfddfc322f16f5 Mon Sep 17 00:00:00 2001 From: John Stefanelli Date: Sat, 23 Aug 2025 16:58:07 +0200 Subject: [PATCH] Initial commit --- .gitignore | 4 + CMakeLists.txt | 17 +++ assets/index.html | 30 +++++ assets/shaders/shaders.slang | 28 ++++ src/main.cpp | 250 +++++++++++++++++++++++++++++++++++ 5 files changed, 329 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 assets/index.html create mode 100644 assets/shaders/shaders.slang create mode 100644 src/main.cpp diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e6756e5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.cache/ +build/ +tmp/ +.clangd diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..0d19460 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,17 @@ +cmake_minimum_required(VERSION 4.0) + +project(wgpu-wasm LANGUAGES C CXX) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED TRUE) + +add_custom_target(${CMAKE_PROJECT_NAME}_html COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/assets/index.html ${CMAKE_CURRENT_BINARY_DIR}/index.html + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/assets/index.html) + +add_executable(${CMAKE_PROJECT_NAME} src/main.cpp) + +add_dependencies(${CMAKE_PROJECT_NAME} ${CMAKE_PROJECT_NAME}_html) + +target_compile_options(${CMAKE_PROJECT_NAME} PRIVATE $<$:/W4 /WX> $<$>:-Wall -Wextra -Wpedantic -Werror>) +target_compile_options(${CMAKE_PROJECT_NAME} PRIVATE --use-port=emdawnwebgpu) +target_link_options(${CMAKE_PROJECT_NAME} PRIVATE --use-port=emdawnwebgpu) diff --git a/assets/index.html b/assets/index.html new file mode 100644 index 0000000..c0b4619 --- /dev/null +++ b/assets/index.html @@ -0,0 +1,30 @@ + + + + WGPU ASM Test + + + + + + + + diff --git a/assets/shaders/shaders.slang b/assets/shaders/shaders.slang new file mode 100644 index 0000000..2b8e659 --- /dev/null +++ b/assets/shaders/shaders.slang @@ -0,0 +1,28 @@ + +struct STDVertex { + float3 pos; + float4 col; +}; + +struct VertexOut { + float4 pos : SV_Position; + float4 col; +}; + + +struct STDCamera { + float4x4 mvp; + float3 position; + float3 direction; +}; + +[shader("vertex")] +VertexOut vsMain(uint vertex_id: SV_VertexID, uint instance_id: SV_InstanceID, StructuredBuffer vertices, uniform StructuredBuffer camera) { + return VertexOut(mul(float4(vertices[vertex_id].pos, 1.0), camera[0].mvp), vertices[vertex_id].col); +} + +[shader("pixel")] +float4 fsMain(VertexOut fsIn){ + return fsIn.col; +} + diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..dbf0d39 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,250 @@ +#include "webgpu/webgpu_cpp.h" +#ifdef EMSCRIPTEN +#include "emscripten/emscripten.h" +#endif +#include + +enum LoadStatus { + None, + RequestedAdapter, + GotAdapter, + RequestedDevice, + GotDevice, + Ready, + Error +}; + +LoadStatus load_status; + +wgpu::Instance gpu_instance; +wgpu::Adapter gpu_adapter; +wgpu::Device gpu_device; +wgpu::Surface gpu_surface; +wgpu::TextureFormat target_format; +wgpu::Texture target_texture; +wgpu::Extent3D target_size = {}; + +void request_adapter() { + std::cout << "[WGPU] request_adapter()" << std::endl; + gpu_instance = wgpu::CreateInstance(); + + load_status = RequestedAdapter; + + wgpu::RequestAdapterOptions options = {}; + + gpu_instance.RequestAdapter(&options, wgpu::CallbackMode::AllowProcessEvents, [](wgpu::RequestAdapterStatus status, wgpu::Adapter adapter, std::string_view error) -> void { + if (status != wgpu::RequestAdapterStatus::Success) { + std::cout << "[WGPU] RequestAdapter error: " << error << std::endl; + load_status = Error; + return; + } + gpu_adapter = adapter; + load_status = GotAdapter; + }); +} + +void request_device() { + std::cout << "[WGPU] request_device()" << std::endl; + load_status = RequestedDevice; + + wgpu::DeviceDescriptor descriptor = {}; + + gpu_adapter.RequestDevice(&descriptor, wgpu::CallbackMode::AllowProcessEvents, [](wgpu::RequestDeviceStatus status, wgpu::Device device, std::string_view error) -> void { + if (status != wgpu::RequestDeviceStatus::Success) { + std::cout << "[WGPU] RequestDevice error: " << error << std::endl; + load_status = Error; + return; + } + + gpu_device = device; + load_status = GotDevice; + }); +} + +void configure_surface() { + if (!gpu_surface) { + + wgpu::SurfaceDescriptor surface_descriptor = {}; + #ifdef EMSCRIPTEN + wgpu::EmscriptenSurfaceSourceCanvasHTMLSelector selector = {}; + selector.sType = wgpu::SType::EmscriptenSurfaceSourceCanvasHTMLSelector; + selector.selector = "#wgpu-canvas"; + surface_descriptor.nextInChain = &selector; + #endif + + gpu_surface = gpu_instance.CreateSurface(&surface_descriptor); + } else { + gpu_surface.Unconfigure(); + } + + wgpu::SurfaceCapabilities surface_caps = {}; + + gpu_surface.GetCapabilities(gpu_adapter, &surface_caps); + + target_format = surface_caps.formatCount > 0 ? surface_caps.formats[0] : wgpu::TextureFormat::RGBA8Unorm; + + wgpu::SurfaceConfiguration surface_config = {}; + surface_config.device = gpu_device; + surface_config.alphaMode = surface_caps.alphaModeCount > 0 ? surface_caps.alphaModes[0] : wgpu::CompositeAlphaMode::Premultiplied; + surface_config.presentMode = surface_caps.presentModeCount > 0 ? surface_caps.presentModes[0] : wgpu::PresentMode::Undefined; + surface_config.usage = wgpu::TextureUsage::CopyDst; + surface_config.width = target_size.width; + surface_config.height = target_size.height; + surface_config.viewFormatCount = 0; + surface_config.format = target_format; + + gpu_surface.Configure(&surface_config); + + wgpu::TextureDescriptor target_texture_desc = {}; + target_texture_desc.format = target_format; + target_texture_desc.usage = wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::RenderAttachment; + target_texture_desc.dimension = wgpu::TextureDimension::e2D; + target_texture_desc.mipLevelCount = 1; + target_texture_desc.sampleCount = 1; + target_texture_desc.size.width = target_size.width; + target_texture_desc.size.height = target_size.height;; + target_texture_desc.size.depthOrArrayLayers = 1; + + if (target_texture) { + target_texture.Destroy(); + } + + target_texture = gpu_device.CreateTexture(&target_texture_desc); +} + +void load_resources() { + std::cout << "[WGPU] load_resources()" << std::endl; + + target_size.width = 1280; + target_size.height = 720; + configure_surface(); + + load_status = Ready; +} + +void load() { + switch(load_status) { + case LoadStatus::None: + request_adapter(); + return; + case LoadStatus::GotAdapter: + request_device(); + return; + case LoadStatus::GotDevice: + load_resources(); + return; + case LoadStatus::Error: + std::cerr << "[WGPU] An error occurred during load. Aborting." << std::endl; + emscripten_cancel_main_loop(); + break; + default: + return; + } +} + +#ifdef EMSCRIPTEN +EM_JS(int, get_page_width, (), { + return document.documentElement.offsetWidth; +}); + +EM_JS(int, get_page_height, (), { + return document.documentElement.offsetHeight; +}); +EM_JS(void, resize_canvas, (const char* canvas_id, int width, int height), { + const canvas = document.getElementById(canvas_id); + + if (!canvas) { + return; + } + + canvas.width = width; + canvas.height = height; +}); + +#endif + +void main_loop() { + if (load_status != LoadStatus::Ready) { + load(); + gpu_instance.ProcessEvents(); + return; + } + +#ifdef EMSCRIPTEN + auto page_width = static_cast(get_page_width()); + auto page_height = static_cast(get_page_height()); + + if (page_width != target_size.width || page_height != target_size.height) { + std::cout << "[WGPU] Resizing to: " << page_width << "x" << page_height << std::endl; + target_size.width = page_width; + target_size.height = page_height; + resize_canvas("wgpu-canvas", page_width, page_height); + configure_surface(); + } + +#endif + + wgpu::SurfaceTexture display_texture = {}; + + gpu_surface.GetCurrentTexture(&display_texture); + + if (display_texture.status != wgpu::SurfaceGetCurrentTextureStatus::SuccessOptimal &&display_texture.status != wgpu::SurfaceGetCurrentTextureStatus::SuccessSuboptimal) { + return; + } + + auto command_encoder = gpu_device.CreateCommandEncoder(); + + wgpu::RenderPassColorAttachment target_attachment = {}; + target_attachment.clearValue.r = 0.0f; + target_attachment.clearValue.g = 0.0f; + target_attachment.clearValue.b = 1.0f; + target_attachment.clearValue.a = 1.0f; + target_attachment.loadOp = wgpu::LoadOp::Clear; + target_attachment.storeOp = wgpu::StoreOp::Store; + target_attachment.view = target_texture.CreateView(); + + wgpu::RenderPassDescriptor pass_desc = {}; + pass_desc.colorAttachmentCount = 1; + pass_desc.colorAttachments = &target_attachment; + + auto pass = command_encoder.BeginRenderPass(&pass_desc); + + pass.End(); + + wgpu::TexelCopyTextureInfo src_info = {}; + src_info.texture = target_texture; + src_info.aspect = wgpu::TextureAspect::All; + src_info.mipLevel = 0; + + wgpu::TexelCopyTextureInfo dst_info = {}; + dst_info.texture = display_texture.texture; + dst_info.aspect = wgpu::TextureAspect::All; + dst_info.mipLevel = 0; + + wgpu::Extent3D copy_size = {}; + copy_size.width = target_size.width; + copy_size.height = target_size.height; + copy_size.depthOrArrayLayers = 1; + + command_encoder.CopyTextureToTexture(&src_info, &dst_info, ©_size); + + auto cmd_buffer = command_encoder.Finish(); + + gpu_device.GetQueue().Submit(1, &cmd_buffer); + +#ifndef EMSCRIPTEN + //TODO: Run gpu_srface.Present() when queued work is done +#endif + gpu_instance.ProcessEvents(); +} + +int main(int, char**) { +#ifdef EMSCRIPTEN + emscripten_set_main_loop(main_loop, 0, true); +#else + while(true) { + main_loop(); + } +#endif + return 0; +}