| /* | |
| * Copyright 2023 Rive | |
| */ | |
| #ifdef RIVE_WEBGPU | |
| #include "pls_render_context_webgpu_vulkan.hpp" | |
| #include "shaders/constants.glsl" | |
| #include <webgpu/webgpu_cpp.h> | |
| #include <emscripten.h> | |
| #include <emscripten/html5_webgpu.h> | |
| namespace rive::pls | |
| { | |
| // Create a group for binding PLS textures as Vulkan input attachments. The "inputTexture" property | |
| // is nonstandard WebGPU. | |
| EM_JS(int, make_pls_input_attachment_texture_bind_group, (int device), { | |
| device = JsValStore.get(device); | |
| const plsBindBroupLayout = device.createBindGroupLayout({ | |
| entries : [ | |
| { | |
| binding : 0, | |
| visibility : GPUShaderStage.FRAGMENT, | |
| inputTexture : {viewDimension : "2d"}, | |
| }, | |
| { | |
| binding : 1, | |
| visibility : GPUShaderStage.FRAGMENT, | |
| inputTexture : {viewDimension : "2d"}, | |
| }, | |
| { | |
| binding : 2, | |
| visibility : GPUShaderStage.FRAGMENT, | |
| inputTexture : {viewDimension : "2d"}, | |
| }, | |
| { | |
| binding : 3, | |
| visibility : GPUShaderStage.FRAGMENT, | |
| inputTexture : {viewDimension : "2d"}, | |
| }, | |
| ] | |
| }); | |
| return JsValStore.add(plsBindBroupLayout); | |
| }); | |
| wgpu::BindGroupLayout PLSRenderContextWebGPUVulkan::initPLSTextureBindGroup() | |
| { | |
| m_plsTextureBindGroupJSHandle = EmJsHandle(make_pls_input_attachment_texture_bind_group( | |
| emscripten_webgpu_export_device(device().Get()))); | |
| return wgpu::BindGroupLayout::Acquire( | |
| emscripten_webgpu_import_bind_group_layout(m_plsTextureBindGroupJSHandle.get())); | |
| } | |
| // The "TRANSIENT_ATTACHMENT" and "INPUT_ATTACHMENT" flags are nonstandard WebGPU. | |
| EM_JS(int, texture_usage_transient_input_attachment, (), { | |
| return GPUTextureUsage.TRANSIENT_ATTACHMENT | GPUTextureUsage.INPUT_ATTACHMENT; | |
| }); | |
| rcp<PLSRenderTargetWebGPU> PLSRenderContextWebGPUVulkan::makeRenderTarget( | |
| wgpu::TextureFormat framebufferFormat, | |
| uint32_t width, | |
| uint32_t height) | |
| { | |
| return rcp(new PLSRenderTargetWebGPU( | |
| device(), | |
| framebufferFormat, | |
| width, | |
| height, | |
| static_cast<wgpu::TextureUsage>(texture_usage_transient_input_attachment()))); | |
| } | |
| // Create a standard PLS "draw" pipeline that uses Vulkan input attachments and | |
| // VK_EXT_rasterization_order_attachment_access for pixel local storage. The "inputs" and "features" | |
| // properties on GPUFragmentState, and the "usedAsInput" property on GPUColorTargetState are | |
| // nonstandard WebGPU. | |
| EM_JS(int, | |
| make_pls_draw_pipeline, | |
| (int device, | |
| rive::pls::DrawType drawType, | |
| wgpu::TextureFormat framebufferFormat, | |
| int vertexShader, | |
| int fragmentShader, | |
| int pipelineLayout, | |
| bool clockwiseFrontFace), | |
| { | |
| device = JsValStore.get(device); | |
| vertexShader = JsValStore.get(vertexShader); | |
| fragmentShader = JsValStore.get(fragmentShader); | |
| pipelineLayout = JsValStore.get(pipelineLayout); | |
| const RGBA8_UNORM = 0x00000012; | |
| const BGRA8_UNORM = 0x00000017; | |
| switch (framebufferFormat) | |
| { | |
| case RGBA8_UNORM: | |
| framebufferFormat = "rgba8unorm"; | |
| break; | |
| case BGRA8_UNORM: | |
| framebufferFormat = "bgra8unorm"; | |
| break; | |
| default: | |
| throw "unsupported framebuffer format " + framebufferFormat; | |
| } | |
| let vertexBufferLayout; | |
| switch (drawType) | |
| { | |
| case 0: | |
| case 1: | |
| attrs = [ | |
| { | |
| format : "float32x4", | |
| offset : 0, | |
| shaderLocation : 0, | |
| }, | |
| { | |
| format : "float32x4", | |
| offset : 4 * 4, | |
| shaderLocation : 1, | |
| }, | |
| ]; | |
| vertexBufferLayouts = [ | |
| { | |
| arrayStride : 4 * 8, | |
| stepMode : "vertex", | |
| attributes : attrs, | |
| }, | |
| ]; | |
| break; | |
| case 2: | |
| attrs = [ | |
| { | |
| format : "float32x3", | |
| offset : 0, | |
| shaderLocation : 0, | |
| }, | |
| ]; | |
| vertexBufferLayouts = [ | |
| { | |
| arrayStride : 4 * 3, | |
| stepMode : "vertex", | |
| attributes : attrs, | |
| }, | |
| ]; | |
| break; | |
| case 3: | |
| attrs = [ | |
| { | |
| format : "float32x2", | |
| offset : 0, | |
| shaderLocation : 0, | |
| }, | |
| { | |
| format : "float32x2", | |
| offset : 0, | |
| shaderLocation : 1, | |
| }, | |
| ]; | |
| vertexBufferLayouts = [ | |
| { | |
| arrayStride : 4 * 2, | |
| stepMode : "vertex", | |
| attributes : [attrs[0]], | |
| }, | |
| { | |
| arrayStride : 4 * 2, | |
| stepMode : "vertex", | |
| attributes : [attrs[1]], | |
| }, | |
| ]; | |
| break; | |
| } | |
| const pipelineDesc = { | |
| vertex : { | |
| module : vertexShader, | |
| entryPoint : "main", | |
| buffers : vertexBufferLayouts, | |
| }, | |
| fragment : { | |
| module : fragmentShader, | |
| entryPoint : "main", | |
| inputs : [ | |
| {format : framebufferFormat, usedAsColor : true}, | |
| {format : "r32uint", usedAsColor : true}, | |
| {format : "r32uint", usedAsColor : true}, | |
| {format : framebufferFormat, usedAsColor : true}, | |
| ], | |
| targets : [ | |
| {format : framebufferFormat, usedAsInput : true}, | |
| {format : "r32uint", usedAsInput : true}, | |
| {format : "r32uint", usedAsInput : true}, | |
| {format : framebufferFormat, usedAsInput : true}, | |
| ], | |
| features : GPUFragmentStateFeatures.RASTERIZATION_ORDER_ATTACHMENT_ACCESS, | |
| }, | |
| primitive : { | |
| topology : "triangle-list", | |
| frontFace : clockwiseFrontFace ? "cw" : "ccw", | |
| cullMode : drawType != 3 ? "back" : "none" | |
| }, | |
| layout : pipelineLayout | |
| }; | |
| const pipeline = device.createRenderPipeline(pipelineDesc); | |
| return JsValStore.add(pipeline); | |
| }); | |
| wgpu::RenderPipeline PLSRenderContextWebGPUVulkan::makePLSDrawPipeline( | |
| rive::pls::DrawType drawType, | |
| wgpu::TextureFormat framebufferFormat, | |
| wgpu::ShaderModule vertexShader, | |
| wgpu::ShaderModule fragmentShader, | |
| EmJsHandle* pipelineJSHandleIfNeeded) | |
| { | |
| *pipelineJSHandleIfNeeded = EmJsHandle( | |
| make_pls_draw_pipeline(emscripten_webgpu_export_device(device().Get()), | |
| drawType, | |
| framebufferFormat, | |
| emscripten_webgpu_export_shader_module(vertexShader.Get()), | |
| emscripten_webgpu_export_shader_module(fragmentShader.Get()), | |
| emscripten_webgpu_export_pipeline_layout(drawPipelineLayout().Get()), | |
| frontFaceForOnScreenDraws() == wgpu::FrontFace::CW)); | |
| return wgpu::RenderPipeline::Acquire( | |
| emscripten_webgpu_import_render_pipeline(pipelineJSHandleIfNeeded->get())); | |
| } | |
| // Create a standard PLS "draw" render pass that uses Vulkan input attachments for pixel local | |
| // storage. The "inputAttachments" property on GPURenderPassDescriptor is nonstandard WebGPU. | |
| EM_JS(int, | |
| make_pls_render_pass, | |
| (int encoder, | |
| int tex0, | |
| int tex1, | |
| int tex2, | |
| int tex3, | |
| wgpu::LoadOp loadOp, | |
| double r, | |
| double g, | |
| double b, | |
| double a), | |
| { | |
| encoder = JsValStore.get(encoder); | |
| tex0 = JsValStore.get(tex0); | |
| tex1 = JsValStore.get(tex1); | |
| tex2 = JsValStore.get(tex2); | |
| tex3 = JsValStore.get(tex3); | |
| const CLEAR = 1; | |
| const LOAD = 2; | |
| switch (loadOp) | |
| { | |
| case CLEAR: | |
| loadOp = "clear"; | |
| break; | |
| case LOAD: | |
| loadOp = "load"; | |
| break; | |
| default: | |
| throw "unsupported framebuffer format " + framebufferFormat; | |
| } | |
| const zero = {r : 0, g : 0, b : 0, a : 0}; | |
| const plsAttachments = [ | |
| { | |
| view : tex0, | |
| loadOp : loadOp, | |
| storeOp : "store", | |
| clearValue : {r : r, g : g, b : b, a : a}, | |
| }, | |
| { | |
| view : tex1, | |
| loadOp : "clear", | |
| storeOp : "discard", | |
| clearValue : zero, | |
| }, | |
| { | |
| view : tex2, | |
| loadOp : "clear", | |
| storeOp : "discard", | |
| clearValue : zero, | |
| }, | |
| { | |
| view : tex3, | |
| loadOp : "clear", | |
| storeOp : "discard", | |
| clearValue : zero, | |
| }, | |
| ]; | |
| const renderPass = encoder.beginRenderPass({ | |
| inputAttachments : plsAttachments, | |
| colorAttachments : plsAttachments, | |
| }); | |
| return JsValStore.add(renderPass); | |
| }); | |
| wgpu::RenderPassEncoder PLSRenderContextWebGPUVulkan::makePLSRenderPass( | |
| wgpu::CommandEncoder encoder, | |
| const PLSRenderTargetWebGPU* renderTarget, | |
| wgpu::LoadOp loadOp, | |
| const wgpu::Color& clearColor, | |
| EmJsHandle* renderPassJSHandleIfNeeded) | |
| { | |
| *renderPassJSHandleIfNeeded = EmJsHandle(make_pls_render_pass( | |
| emscripten_webgpu_export_command_encoder(encoder.Get()), | |
| emscripten_webgpu_export_texture_view(renderTarget->m_targetTextureView.Get()), | |
| emscripten_webgpu_export_texture_view(renderTarget->m_coverageTextureView.Get()), | |
| emscripten_webgpu_export_texture_view(renderTarget->m_clipTextureView.Get()), | |
| emscripten_webgpu_export_texture_view(renderTarget->m_originalDstColorTextureView.Get()), | |
| loadOp, | |
| clearColor.r, | |
| clearColor.g, | |
| clearColor.b, | |
| clearColor.a)); | |
| return wgpu::RenderPassEncoder::Acquire( | |
| emscripten_webgpu_import_render_pass_encoder(renderPassJSHandleIfNeeded->get())); | |
| } | |
| } // namespace rive::pls | |
| #endif |