Initial commit

This commit is contained in:
John Stefanelli 2025-08-23 16:58:07 +02:00
commit cfd32501b6
Signed by: jstefanelli
GPG key ID: 60EDE2437640D2AA
5 changed files with 329 additions and 0 deletions

4
.gitignore vendored Normal file
View file

@ -0,0 +1,4 @@
.cache/
build/
tmp/
.clangd

17
CMakeLists.txt Normal file
View file

@ -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 $<$<CXX_COMPILER_ID:Msvc>:/W4 /WX> $<$<NOT:$<CXX_COMPILER_ID:Msvc>>:-Wall -Wextra -Wpedantic -Werror>)
target_compile_options(${CMAKE_PROJECT_NAME} PRIVATE --use-port=emdawnwebgpu)
target_link_options(${CMAKE_PROJECT_NAME} PRIVATE --use-port=emdawnwebgpu)

30
assets/index.html Normal file
View file

@ -0,0 +1,30 @@
<!DOCTYPE html>
<html>
<head>
<title>WGPU ASM Test</title>
<style>
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
html, body {
background-color: rgb(0.25, 0.25, 0.25);
width: 100vw;
height: 100vh;
overflow: hidden;
}
#wgpu-canvas {
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<canvas id="wgpu-canvas" width="1280" height="720">
</canvas>
<script defer src="wgpu-wasm.js"></script>
</body>
</html>

View file

@ -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<STDVertex> vertices, uniform StructuredBuffer<STDCamera> 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;
}

250
src/main.cpp Normal file
View file

@ -0,0 +1,250 @@
#include "webgpu/webgpu_cpp.h"
#ifdef EMSCRIPTEN
#include "emscripten/emscripten.h"
#endif
#include <iostream>
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<uint32_t>(get_page_width());
auto page_height = static_cast<uint32_t>(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, &copy_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;
}