// // Created by Vicente Ferrari Smith on 06.03.26. // #include "renderer.h" #include #include "webgpu.h" #include "../graphics.h" #include #include extern WGPUInstance instance; extern Device wgpu_device; extern Queue wgpu_queue; Renderer::Renderer(GLFWwindow *window) { create_compute_pipeline(); create_render_pipeline(); // Number of floats in the buffers WGPUBufferDescriptor inputBufferDesc = WGPU_BUFFER_DESCRIPTOR_INIT; inputBufferDesc.label = toWgpuStringView("Input Buffer"); inputBufferDesc.size = elementCount * sizeof(float); inputBufferDesc.usage = WGPUBufferUsage_Storage; inputBufferDesc.mappedAtCreation = true; input_buffer = wgpuDeviceCreateBuffer(wgpu_device.device, &inputBufferDesc); WGPUBufferDescriptor outputBufferDesc = WGPU_BUFFER_DESCRIPTOR_INIT; outputBufferDesc.label = toWgpuStringView("Output Buffer"); outputBufferDesc.size = elementCount * sizeof(float); outputBufferDesc.usage = WGPUBufferUsage_Storage | WGPUBufferUsage_CopySrc; output_buffer = wgpuDeviceCreateBuffer(wgpu_device.device, &outputBufferDesc); WGPUBufferDescriptor stagingBufferDesc = WGPU_BUFFER_DESCRIPTOR_INIT; stagingBufferDesc.label = toWgpuStringView("Staging Buffer"); stagingBufferDesc.size = elementCount * sizeof(float); stagingBufferDesc.usage = WGPUBufferUsage_CopyDst | WGPUBufferUsage_MapRead; staging_buffer = wgpuDeviceCreateBuffer(wgpu_device.device, &stagingBufferDesc); float* inputBufferData = static_cast( wgpuBufferGetMappedRange(input_buffer, 0, WGPU_WHOLE_MAP_SIZE) ); // Write 0.0, 0.1, 0.2, 0.3, ... in inputBuffer for (size_t i = 0 ; i < elementCount ; ++i) { inputBufferData[i] = static_cast(i) * 0.1f; } wgpuBufferUnmap(input_buffer); } void Renderer::create_compute_pipeline() { std::string shader_source = read_entire_file("shader/compute.wgsl"); if (!shader_source.empty()) { WGPUShaderSourceWGSL wgslSourceDesc = WGPU_SHADER_SOURCE_WGSL_INIT; wgslSourceDesc.code = toWgpuStringView(shader_source); WGPUShaderModuleDescriptor moduleDesc = WGPU_SHADER_MODULE_DESCRIPTOR_INIT; moduleDesc.nextInChain = &wgslSourceDesc.chain; moduleDesc.label = toWgpuStringView("Our first compute shader"); WGPUShaderModule shaderModule = wgpuDeviceCreateShaderModule(wgpu_device.device, &moduleDesc); WGPUComputePipelineDescriptor desc = WGPU_COMPUTE_PIPELINE_DESCRIPTOR_INIT; desc.label = toWgpuStringView("Our simple pipeline"); desc.compute.module = shaderModule; desc.compute.entryPoint = toWgpuStringView("main"); compute_pipeline = wgpuDeviceCreateComputePipeline(wgpu_device.device, &desc); } else { std::println("Couldn't load compute shader source"); } } void Renderer::create_render_pipeline() { } void Renderer::submit_sprite(glm::vec2 pos, const sprite_t &sprite) { RenderCommand cmd {}; cmd.pipeline = PipelineType::TexturedQuad; cmd.key = { (uint16_t) pos.y, 0, (uint8_t) PipelineType::TexturedQuad }; const Texture &texture = texture_manager.textures[sprite.texture]; cmd.textured_quad = { .pos = pos, .scale = { sprite.scale.x, sprite.scale.y }, .uv0 = {0, 0}, .uv1 = {1, 1}, .colour = {1, 1, 1, 1}, .texture = texture.p_texture->texture, }; commands.push_back(cmd); // assert(started == true, "You can't submit without having started the renderer first."); // renderable : Renderable; // renderable.type = .Sprite; // // if sprite.window_space // renderable.projection_type = .ORTHOGRAPHIC_WINDOW; // else // renderable.projection_type = .ORTHOGRAPHIC_WORLD; // // renderable.pos = pos; // renderable.sprite.texture_sheet = sprite.texture_sheet; // renderable.sprite.texture_cell = sprite.texture_cell; // renderable.sprite.origin = sprite.origin; // renderable.sprite.scale = sprite.scale; // renderable.sprite.colour = sprite.colour; // renderable.sprite.alpha = alpha; // // array_add(*renderer.renderable_list, renderable); } void Renderer::begin_frame() { commands.clear(); } uint32_t divideAndCeil(uint32_t p, uint32_t q) { return (p + q - 1) / q; } void fetchBufferDataSync( WGPUInstance instance, WGPUBuffer bufferB, std::function processBufferData ) { // We copy here what used to be in main(): // Context passed to `onBufferBMapped` through theuserdata pointer: struct OnBufferBMappedContext { bool operationEnded = false; // Turned true as soon as the callback is invoked bool mappingIsSuccessful = false; // Turned true only if mapping succeeded }; // This function has the type WGPUBufferMapCallback as defined in webgpu.h auto onBufferBMapped = []( WGPUMapAsyncStatus status, struct WGPUStringView message, void* userdata1, void* /* userdata2 */ ) { OnBufferBMappedContext& context = *reinterpret_cast(userdata1); context.operationEnded = true; if (status == WGPUMapAsyncStatus_Success) { context.mappingIsSuccessful = true; } else { std::cout << "Could not map buffer B! Status: " << status << ", message: " << toStdStringView(message) << std::endl; } }; // We create an instance of the context shared with `onBufferBMapped` OnBufferBMappedContext context; // And we build the callback info: WGPUBufferMapCallbackInfo callbackInfo = WGPU_BUFFER_MAP_CALLBACK_INFO_INIT; callbackInfo.mode = WGPUCallbackMode_AllowProcessEvents; callbackInfo.callback = onBufferBMapped; callbackInfo.userdata1 = &context; // And finally we launch the asynchronous operation wgpuBufferMapAsync( bufferB, WGPUMapMode_Read, 0, // offset WGPU_WHOLE_MAP_SIZE, callbackInfo ); // Process events until the map operation ended wgpuInstanceProcessEvents(instance); while (!context.operationEnded) { sleepForMilliseconds(200); wgpuInstanceProcessEvents(instance); } if (context.mappingIsSuccessful) { const void* bufferData = wgpuBufferGetConstMappedRange(bufferB, 0, WGPU_WHOLE_MAP_SIZE); processBufferData(bufferData); } } void Renderer::end_frame(GLFWwindow *window) { std::vector bindGroupEntries(2, WGPU_BIND_GROUP_ENTRY_INIT); bindGroupEntries[0].binding = 0; bindGroupEntries[0].buffer = input_buffer; bindGroupEntries[1].binding = 1; bindGroupEntries[1].buffer = output_buffer; WGPUBindGroupDescriptor bindGroupDesc = WGPU_BIND_GROUP_DESCRIPTOR_INIT; bindGroupDesc.entries = bindGroupEntries.data(); bindGroupDesc.entryCount = bindGroupEntries.size(); bindGroupDesc.layout = wgpuComputePipelineGetBindGroupLayout(compute_pipeline, 0); WGPUBindGroup bindGroup = wgpuDeviceCreateBindGroup(wgpu_device.device, &bindGroupDesc); wgpuBindGroupLayoutRelease(bindGroupDesc.layout); WGPUCommandEncoderDescriptor encoderDesc = WGPU_COMMAND_ENCODER_DESCRIPTOR_INIT; encoderDesc.label = toWgpuStringView("My command encoder"); WGPUCommandEncoder encoder = wgpuDeviceCreateCommandEncoder(wgpu_device.device, &encoderDesc); WGPUComputePassEncoder computePass = wgpuCommandEncoderBeginComputePass(encoder, nullptr); wgpuComputePassEncoderSetPipeline(computePass, compute_pipeline); wgpuComputePassEncoderSetBindGroup(computePass, 0, bindGroup, 0, nullptr); uint32_t workgroupSizeX = 32; // the value specified in @workgroup_size(...) uint32_t workgroupCountX = divideAndCeil((uint32_t)elementCount, workgroupSizeX); // After the end of the compute pass, we copy the whole output buffer into the staging buffer wgpuComputePassEncoderDispatchWorkgroups(computePass, workgroupCountX, 1, 1); wgpuComputePassEncoderEnd(computePass); wgpuComputePassEncoderRelease(computePass); wgpuCommandEncoderCopyBufferToBuffer(encoder, output_buffer, 0, staging_buffer, 0, elementCount * sizeof(float)); WGPUCommandBufferDescriptor cmdBufferDescriptor = WGPU_COMMAND_BUFFER_DESCRIPTOR_INIT; cmdBufferDescriptor.label = toWgpuStringView("Command buffer"); WGPUCommandBuffer command = wgpuCommandEncoderFinish(encoder, &cmdBufferDescriptor); wgpuCommandEncoderRelease(encoder); // release encoder after it's finished // Finally submit the command queue std::cout << "Submitting command..." << std::endl; wgpuQueueSubmit(wgpu_queue.queue, 1, &command); wgpuCommandBufferRelease(command); std::cout << "Command submitted." << std::endl; // Removed fetchBufferDataSync(instance, staging_buffer, [&](const void* data) { const float* floatData = static_cast(data); std::cout << "Result: ["; for (size_t i = 0 ; i < elementCount ; ++i) { if (i > 0) std::cout << ", "; std::cout << floatData[i]; } std::cout << "]" << std::endl; }); // // Get the next target texture view // WGPUTextureView target_view = get_next_surface_view(); // if (!target_view) return; // no surface texture, we skip this frame // // WGPUCommandEncoderDescriptor encoderDesc = WGPU_COMMAND_ENCODER_DESCRIPTOR_INIT; // encoderDesc.label = toWgpuStringView("My command encoder"); // WGPUCommandEncoder encoder = wgpuDeviceCreateCommandEncoder(wgpu_device.device, &encoderDesc); // WGPURenderPassDescriptor renderPassDesc = WGPU_RENDER_PASS_DESCRIPTOR_INIT; // WGPURenderPassColorAttachment colorAttachment = WGPU_RENDER_PASS_COLOR_ATTACHMENT_INIT; // // colorAttachment.view = target_view; // colorAttachment.loadOp = WGPULoadOp_Clear; // colorAttachment.storeOp = WGPUStoreOp_Store; // colorAttachment.clearValue = WGPUColor{ 100.0 / 255.0, 149.0 / 255.0, 237.0 / 255.0, 1.0 }; // // renderPassDesc.colorAttachmentCount = 1; // renderPassDesc.colorAttachments = &colorAttachment; // // WGPURenderPassEncoder renderPass = wgpuCommandEncoderBeginRenderPass(encoder, &renderPassDesc); // // Use the render pass here (we do nothing with the render pass for now) // wgpuRenderPassEncoderEnd(renderPass); // wgpuRenderPassEncoderRelease(renderPass); // WGPUCommandBufferDescriptor cmdBufferDescriptor = WGPU_COMMAND_BUFFER_DESCRIPTOR_INIT; // cmdBufferDescriptor.label = toWgpuStringView("Command buffer"); // WGPUCommandBuffer command = wgpuCommandEncoderFinish(encoder, &cmdBufferDescriptor); // wgpuCommandEncoderRelease(encoder); // release encoder after it's finished // // // Finally submit the command queue // std::println("Submitting command..."); // wgpuQueueSubmit(wgpu_queue.queue, 1, &command); // wgpuCommandBufferRelease(command); // std::println("Command submitted."); // // // At the end of the frame // wgpuTextureViewRelease(target_view); // #ifndef __EMSCRIPTEN__ // wgpuSurfacePresent(wgpu_surface.surface); // #endif }