diff --git a/CMakeLists.txt b/CMakeLists.txt index bdad092..9c5f395 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 $<$:/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) +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 diff --git a/assets/shaders/shaders.slang b/assets/shaders/shaders.slang index 2b8e659..55b5ffc 100644 --- a/assets/shaders/shaders.slang +++ b/assets/shaders/shaders.slang @@ -17,8 +17,8 @@ struct STDCamera { }; [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); +VertexOut vsMain(uint vertex_id: SV_VertexID, uint instance_id: SV_InstanceID, StructuredBuffer vertices, uniform STDCamera camera) { + return VertexOut(mul(camera.mvp, float4(vertices[vertex_id].pos, 1.0)), vertices[vertex_id].col); } [shader("pixel")] diff --git a/src/main.cpp b/src/main.cpp index dbf0d39..00e956b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -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 +#include +#include +#include +#include +#include + +#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 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 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 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 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(width), static_cast(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();