[General] First triangle setup (not rendering yet)

This commit is contained in:
John Stefanelli 2025-08-31 13:02:24 +02:00
parent 6d66391e83
commit f1e3a25f3a
Signed by: jstefanelli
GPG key ID: 60EDE2437640D2AA
3 changed files with 315 additions and 6 deletions

View file

@ -2,8 +2,8 @@ cmake_minimum_required(VERSION 4.0)
project(wgpu-wasm LANGUAGES C CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED TRUE)
set(GLM_BUILD_LIBRARY FALSE)
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/contrib/glm)
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)
@ -11,9 +11,12 @@ add_custom_target(${CMAKE_PROJECT_NAME}_html COMMAND ${CMAKE_COMMAND} -E copy ${
add_executable(${CMAKE_PROJECT_NAME} src/main.cpp)
add_dependencies(${CMAKE_PROJECT_NAME} ${CMAKE_PROJECT_NAME}_html)
target_compile_features(${CMAKE_PROJECT_NAME} PRIVATE cxx_std_17)
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)
target_link_options(${CMAKE_PROJECT_NAME} PRIVATE --use-port=emdawnwebgpu -sASYNCIFY=1)
target_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE glm::glm)
target_compile_definitions(${CMAKE_PROJECT_NAME} PRIVATE -DGLM_FORCE_DEPTH_ZERO_TO_ONE -DGLM_FORCE_LEFT_HANDED)
function(compile_and_package_shader SHADER_NAME TARGET)
add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/shaders/${SHADER_NAME}.wgsl

View file

@ -17,8 +17,8 @@ struct STDCamera {
};
[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);
VertexOut vsMain(uint vertex_id: SV_VertexID, uint instance_id: SV_InstanceID, StructuredBuffer<STDVertex> vertices, uniform STDCamera camera) {
return VertexOut(mul(camera.mvp, float4(vertices[vertex_id].pos, 1.0)), vertices[vertex_id].col);
}
[shader("pixel")]

View file

@ -1,8 +1,22 @@
#include "glm/ext/matrix_clip_space.hpp"
#include "glm/trigonometric.hpp"
#include "webgpu/webgpu_cpp.h"
#ifdef EMSCRIPTEN
#include "emscripten/emscripten.h"
#endif
#define GLM_FORCE_ALIGNED_GENTYPES
#include <iostream>
#include <stdexcept>
#include <filesystem>
#include <fstream>
#include <iterator>
#include <glm/glm.hpp>
#ifdef EMSCRIPTEN
#define SHADERS_ROOT "/shaders/"
#else
#define SHADERS_ROOT "shaders/"
#endif
enum LoadStatus {
None,
@ -16,17 +30,66 @@ enum LoadStatus {
LoadStatus load_status;
struct STDVertex {
union {
glm::vec3 pos;
float __pos_raw[4];
};
glm::vec4 col;
};
std::array<STDVertex, 3> sample_triangle = {{
{
{ glm::vec3(-0.5f, -0.5f, -0.5f) },
glm::vec4(1.0f, 0.0f, 0.0f, 1.0f)
},
{
{ glm::vec3(0.5f, -0.5f, -0.5f) },
glm::vec4(0.0f, 1.0f, 0.0f, 1.0f)
},
{
{ glm::vec3(0.0f, 0.5f, -0.5f) },
glm::vec4(0.0f, 0.0f, 1.0f, 1.0f)
}
}};
struct STDCamera {
glm::mat4 mvp;
union {
glm::vec3 pos;
float __pos_raw[4];
};
union {
glm::vec3 dir;
float __dir_raw[4];
};
};
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::Texture target_depth_texture;
wgpu::Extent3D target_size = {};
wgpu::Buffer staging_buffer;
wgpu::Buffer vertex_buffer;
wgpu::Buffer uniform_buffer;
wgpu::ShaderModule basic_shader_module;
wgpu::BindGroupLayout basic_bind_group_layout;
wgpu::PipelineLayout basic_bind_layout;
wgpu::RenderPipeline basic_render_pipeline;
wgpu::BindGroup basic_bind_group;
void request_adapter() {
std::cout << "[WGPU] request_adapter()" << std::endl;
gpu_instance = wgpu::CreateInstance();
wgpu::InstanceDescriptor instance_desc{};
instance_desc.capabilities.timedWaitAnyEnable = true;
instance_desc.capabilities.timedWaitAnyMaxCount = 1;
gpu_instance = wgpu::CreateInstance(&instance_desc);
load_status = RequestedAdapter;
@ -41,6 +104,8 @@ void request_adapter() {
gpu_adapter = adapter;
load_status = GotAdapter;
});
//gpu_instance.WaitAny(f, -1);
}
void request_device() {
@ -110,6 +175,197 @@ void configure_surface() {
}
target_texture = gpu_device.CreateTexture(&target_texture_desc);
wgpu::TextureDescriptor depth_desc{};
depth_desc.format = wgpu::TextureFormat::Depth16Unorm;
depth_desc.usage = wgpu::TextureUsage::RenderAttachment;
depth_desc.dimension = wgpu::TextureDimension::e2D;
depth_desc.mipLevelCount = 1;
depth_desc.sampleCount = 1;
depth_desc.size.width = target_size.width;
depth_desc.size.height = target_size.height;
depth_desc.size.depthOrArrayLayers = 1;
if (target_depth_texture) {
target_depth_texture.Destroy();
}
target_depth_texture = gpu_device.CreateTexture(&depth_desc);
}
std::string load_shader_source(const std::string_view& shader_name) {
auto path = std::string(SHADERS_ROOT) + std::string(shader_name) + ".wgsl";
if (!std::filesystem::exists(path)) {
throw std::runtime_error("Shader not found.");
}
std::ifstream stream(path);
std::istream_iterator<char> start(stream), end{};
return std::string(start, end);
}
wgpu::ShaderModule load_shader(const std::string_view& shader_name) {
std::cout << "[WGPU] load_shader(\"" << shader_name << "\")" << std::endl;
auto source = load_shader_source(shader_name);
wgpu::ShaderSourceWGSL source_desc{};
source_desc.code = std::string_view(source);
source_desc.sType = wgpu::SType::ShaderSourceWGSL;
wgpu::ShaderModuleDescriptor descriptor{};
descriptor.nextInChain = &source_desc;
return gpu_device.CreateShaderModule(&descriptor);
}
void load_buffers() {
std::cout << "[WGPU] load_buffers()" << std::endl;
wgpu::BufferDescriptor staging_buffer_desc{};
staging_buffer_desc.size = sizeof(float) * 1024;
staging_buffer_desc.usage = wgpu::BufferUsage::MapWrite | wgpu::BufferUsage::CopySrc;
staging_buffer_desc.label = "STAGING_BUFER_1K";
staging_buffer = gpu_device.CreateBuffer(&staging_buffer_desc);
wgpu::BufferDescriptor vertex_buffer_desc{};
vertex_buffer_desc.size = sizeof(float) * 8 * 2048; //2048 STDVertex entries
vertex_buffer_desc.usage = wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopyDst;
vertex_buffer_desc.label = "BASIC_VERTEX_BUFFER";
vertex_buffer = gpu_device.CreateBuffer(&vertex_buffer_desc);
wgpu::BufferDescriptor uniform_buffer_desc{};
uniform_buffer_desc.size = sizeof(float) * 24;
uniform_buffer_desc.usage = wgpu::BufferUsage::Uniform | wgpu::BufferUsage::CopyDst;
uniform_buffer_desc.label = "CAMERA_BUFFER";
uniform_buffer = gpu_device.CreateBuffer(&uniform_buffer_desc);
}
wgpu::PipelineLayout load_basic_pipeline_layout() {
std::cout << "[WGPU] load_basic_pipeline_layout" << std::endl;
std::array<wgpu::BindGroupLayoutEntry, 2> layout_entries{};
layout_entries[0].binding = 1;
layout_entries[0].buffer.minBindingSize = sizeof(float) * 8 * 3; //At least 3 STDVertex entries
layout_entries[0].buffer.type = wgpu::BufferBindingType::ReadOnlyStorage;
layout_entries[1].binding = 0;
layout_entries[1].buffer.minBindingSize = sizeof(float) * 24; //1 STDCamera entry
layout_entries[1].buffer.type = wgpu::BufferBindingType::Uniform;
wgpu::BindGroupLayoutDescriptor group_desc{};
group_desc.entryCount = 2;
group_desc.entries = layout_entries.data();
basic_bind_group_layout = gpu_device.CreateBindGroupLayout(&group_desc);
wgpu::PipelineLayoutDescriptor layout_desc{};
layout_desc.bindGroupLayoutCount = 1;
layout_desc.bindGroupLayouts = &basic_bind_group_layout;
return gpu_device.CreatePipelineLayout(&layout_desc);
}
wgpu::RenderPipeline load_basic_render_pipeline() {
std::cout << "[WGPU] load_basic_render_pipeline()" << std::endl;
wgpu::BlendState fragment_blend{};
fragment_blend.alpha.operation = wgpu::BlendOperation::Add;
fragment_blend.alpha.srcFactor = wgpu::BlendFactor::SrcAlpha;
fragment_blend.alpha.dstFactor = wgpu::BlendFactor::OneMinusSrcAlpha;
fragment_blend.color.operation = wgpu::BlendOperation::Add;
fragment_blend.color.srcFactor = wgpu::BlendFactor::SrcAlpha;
fragment_blend.color.dstFactor = wgpu::BlendFactor::OneMinusSrcAlpha;
wgpu::ColorTargetState fragment_target{};
fragment_target.format = target_format;
fragment_target.blend = &fragment_blend;
fragment_target.writeMask = wgpu::ColorWriteMask::All;
wgpu::FragmentState fragment_state{};
fragment_state.module = basic_shader_module;
fragment_state.entryPoint = "fsMain";
fragment_state.constantCount = 0;
fragment_state.targetCount = 1;
fragment_state.targets = &fragment_target;
wgpu::DepthStencilState depth_stencil{};
depth_stencil.format = target_depth_texture.GetFormat();
depth_stencil.depthCompare = wgpu::CompareFunction::LessEqual;
depth_stencil.depthWriteEnabled = true;
wgpu::RenderPipelineDescriptor final_descriptor{};
final_descriptor.fragment = &fragment_state;
final_descriptor.vertex.module = basic_shader_module;
final_descriptor.vertex.entryPoint = "vsMain";
final_descriptor.vertex.constantCount = 0;
final_descriptor.vertex.bufferCount = 0;
final_descriptor.layout = basic_bind_layout;
final_descriptor.primitive.topology = wgpu::PrimitiveTopology::TriangleList;
final_descriptor.primitive.cullMode = wgpu::CullMode::Back;
final_descriptor.primitive.frontFace = wgpu::FrontFace::CCW;
//final_descriptor.primitive.stripIndexFormat = wgpu::IndexFormat::Uint16;
final_descriptor.depthStencil = &depth_stencil;
return gpu_device.CreateRenderPipeline(&final_descriptor);
}
wgpu::BindGroup load_basic_bind_group() {
std::array<wgpu::BindGroupEntry, 2> entries;
entries[0].size = vertex_buffer.GetSize();
entries[0].binding = 1;
entries[0].buffer = vertex_buffer;
entries[0].offset = 0;
entries[1].size = uniform_buffer.GetSize();
entries[1].binding = 0;
entries[1].buffer = uniform_buffer;
wgpu::BindGroupDescriptor final_desc{};
final_desc.layout = basic_bind_group_layout;
final_desc.entryCount = entries.size();
final_desc.entries = entries.data();
return gpu_device.CreateBindGroup(&final_desc);
}
void fill_buffers() {
std::cout << "[WGPU] fill_buffers()" << std::endl;
auto map = staging_buffer.MapAsync(wgpu::MapMode::Write, 0, (sizeof(STDVertex) * 3) + sizeof(STDCamera), wgpu::CallbackMode::WaitAnyOnly, [](wgpu::MapAsyncStatus status, std::string_view error) -> void {
if (status == wgpu::MapAsyncStatus::Success) {
std::cout << "[WGPU] Map Ok. " << std::endl;
staging_buffer.WriteMappedRange(0, sample_triangle.data(), sample_triangle.size() * sizeof(STDVertex));
STDCamera cam{};
//cam.mvp = glm::perspectiveFov(glm::radians(90.0f), 1280.0f, 720.0f, 0.001f, 100.0f);
cam.mvp = glm::mat4(1.0f);
staging_buffer.WriteMappedRange(sizeof(STDVertex) * 3, &cam, sizeof(STDCamera));
staging_buffer.Unmap();
} else {
std::cerr << "Mapping error: " << error << std::endl;
throw std::runtime_error("WAaaaaaAAAaa");
}
});
gpu_instance.WaitAny(map, -1);
auto enc = gpu_device.CreateCommandEncoder();
enc.CopyBufferToBuffer(staging_buffer, 0, vertex_buffer, 0, sizeof(STDVertex) * 3);
enc.CopyBufferToBuffer(staging_buffer, sizeof(STDVertex) * 3, uniform_buffer, 0, sizeof(STDCamera));
auto cmd = enc.Finish();
gpu_device.GetQueue().Submit(1, &cmd);
auto f = gpu_device.GetQueue().OnSubmittedWorkDone(wgpu::CallbackMode::WaitAnyOnly, [](wgpu::QueueWorkDoneStatus status) -> void {
if (status != wgpu::QueueWorkDoneStatus::Success) {
std::cout << "WAAAA x2" << std::endl;
}
});
gpu_instance.WaitAny(f, -1);
std::cout << "[WGPU] Copy Ok." << std::endl;
}
void load_resources() {
@ -118,6 +374,12 @@ void load_resources() {
target_size.width = 1280;
target_size.height = 720;
configure_surface();
basic_shader_module = load_shader("shaders");
load_buffers();
basic_bind_layout = load_basic_pipeline_layout();
basic_render_pipeline = load_basic_render_pipeline();
basic_bind_group = load_basic_bind_group();
fill_buffers();
load_status = Ready;
}
@ -161,6 +423,35 @@ EM_JS(void, resize_canvas, (const char* canvas_id, int width, int height), {
canvas.height = height;
});
void update_camera(int width, int height) {
auto map_future = staging_buffer.MapAsync(wgpu::MapMode::Write, 0, sizeof(STDCamera), wgpu::CallbackMode::WaitAnyOnly, [width, height](wgpu::MapAsyncStatus status, std::string_view error) -> void {
if (status != wgpu::MapAsyncStatus::Success) {
std::cerr << "[WGPU] Map failure: " << error << std::endl;
throw std::runtime_error("Staging buffer map failure");
}
STDCamera camera{};
camera.mvp = glm::perspectiveFov(glm::radians(90.0f), static_cast<float>(width), static_cast<float>(height), 0.001f, 100.0f);
camera.mvp = glm::mat4(1.0f);
staging_buffer.WriteMappedRange(0, &camera, sizeof(STDCamera));
staging_buffer.Unmap();
});
gpu_instance.WaitAny(map_future, -1);
auto enc = gpu_device.CreateCommandEncoder();
enc.CopyBufferToBuffer(staging_buffer, 0, uniform_buffer, 0, sizeof(STDCamera));
auto cmd = enc.Finish();
gpu_device.GetQueue().Submit(1, &cmd);
auto copy_future = gpu_device.GetQueue().OnSubmittedWorkDone(wgpu::CallbackMode::WaitAnyOnly, [](wgpu::QueueWorkDoneStatus) -> void {
});
gpu_instance.WaitAny(copy_future, -1);
}
#endif
void main_loop() {
@ -180,6 +471,9 @@ void main_loop() {
target_size.height = page_height;
resize_canvas("wgpu-canvas", page_width, page_height);
configure_surface();
if (staging_buffer && uniform_buffer) {
update_camera(page_width, page_height);
}
}
#endif
@ -203,11 +497,23 @@ void main_loop() {
target_attachment.storeOp = wgpu::StoreOp::Store;
target_attachment.view = target_texture.CreateView();
wgpu::RenderPassDepthStencilAttachment depth_attachment = {};
depth_attachment.view = target_depth_texture.CreateView();
depth_attachment.depthLoadOp = wgpu::LoadOp::Clear;
depth_attachment.depthStoreOp = wgpu::StoreOp::Store;
depth_attachment.depthClearValue = 1.0f;
wgpu::RenderPassDescriptor pass_desc = {};
pass_desc.colorAttachmentCount = 1;
pass_desc.colorAttachments = &target_attachment;
pass_desc.depthStencilAttachment = &depth_attachment;
auto pass = command_encoder.BeginRenderPass(&pass_desc);
pass.SetViewport(0, 0, page_width, page_height, 0.0f, 1.0f);
pass.SetPipeline(basic_render_pipeline);
pass.SetBindGroup(0, basic_bind_group);
pass.Draw(3);
pass.End();