| /* | |
| * Copyright 2023 Rive | |
| */ | |
| #include "rive/pls/webgpu/pls_render_context_webgpu_impl.hpp" | |
| #include "pls_render_context_webgpu_vulkan.hpp" | |
| #include "rive/pls/pls_image.hpp" | |
| #include "shaders/constants.glsl" | |
| #include "shaders/out/generated/spirv/color_ramp.vert.h" | |
| #include "shaders/out/generated/spirv/color_ramp.frag.h" | |
| #include "shaders/out/generated/spirv/tessellate.vert.h" | |
| #include "shaders/out/generated/spirv/tessellate.frag.h" | |
| #include "shaders/out/generated/spirv/draw_path.vert.h" | |
| #include "shaders/out/generated/spirv/draw_path.frag.h" | |
| #include "shaders/out/generated/spirv/draw_interior_triangles.vert.h" | |
| #include "shaders/out/generated/spirv/draw_interior_triangles.frag.h" | |
| #include "shaders/out/generated/spirv/draw_image_mesh.vert.h" | |
| #include "shaders/out/generated/spirv/draw_image_mesh.frag.h" | |
| #include "shaders/out/generated/glsl.glsl.hpp" | |
| #include "shaders/out/generated/constants.glsl.hpp" | |
| #include "shaders/out/generated/common.glsl.hpp" | |
| #include "shaders/out/generated/tessellate.glsl.hpp" | |
| #include "shaders/out/generated/advanced_blend.glsl.hpp" | |
| #include "shaders/out/generated/draw_path.glsl.hpp" | |
| #include "shaders/out/generated/draw_path_common.glsl.hpp" | |
| #include "shaders/out/generated/draw_image_mesh.glsl.hpp" | |
| #include <sstream> | |
| #include <string> | |
| #ifdef RIVE_DAWN | |
| #include <dawn/webgpu_cpp.h> | |
| static void enable_shader_pixel_local_storage_ext(wgpu::RenderPassEncoder, bool enabled) | |
| { | |
| RIVE_UNREACHABLE(); | |
| } | |
| static void write_texture(wgpu::Queue queue, | |
| wgpu::Texture texture, | |
| uint32_t bytesPerRow, | |
| uint32_t width, | |
| uint32_t height, | |
| const void* data, | |
| size_t dataSize) | |
| { | |
| wgpu::ImageCopyTexture dest = { | |
| .texture = texture, | |
| .mipLevel = 0, | |
| }; | |
| wgpu::TextureDataLayout layout = { | |
| .bytesPerRow = bytesPerRow, | |
| }; | |
| wgpu::Extent3D extent = { | |
| .width = width, | |
| .height = height, | |
| }; | |
| queue.WriteTexture(&dest, data, dataSize, &layout, &extent); | |
| } | |
| static void write_buffer(wgpu::Queue queue, wgpu::Buffer buffer, const void* data, size_t dataSize) | |
| { | |
| queue.WriteBuffer(buffer, 0, data, dataSize); | |
| } | |
| #endif | |
| #ifdef RIVE_WEBGPU | |
| #include <webgpu/webgpu_cpp.h> | |
| #include <emscripten.h> | |
| #include <emscripten/html5_webgpu.h> | |
| EM_JS(void, enable_shader_pixel_local_storage_ext_js, (int renderPass, bool enabled), { | |
| renderPass = JsValStore.get(renderPass); | |
| renderPass.setShaderPixelLocalStorageEnabled(Boolean(enabled)); | |
| }); | |
| static void enable_shader_pixel_local_storage_ext(wgpu::RenderPassEncoder renderPass, bool enabled) | |
| { | |
| enable_shader_pixel_local_storage_ext_js( | |
| emscripten_webgpu_export_render_pass_encoder(renderPass.Get()), | |
| enabled); | |
| } | |
| EM_JS(void, | |
| write_texture_js, | |
| (int queue, | |
| int texture, | |
| uint32_t bytesPerRow, | |
| uint32_t width, | |
| uint32_t height, | |
| uintptr_t indexU8, | |
| size_t dataSize), | |
| { | |
| queue = JsValStore.get(queue); | |
| texture = JsValStore.get(texture); | |
| // Copy data off the WASM heap before sending it to WebGPU bindings. | |
| const data = new Uint8Array(dataSize); | |
| data.set(Module.HEAPU8.subarray(indexU8, indexU8 + dataSize)); | |
| queue.writeTexture({texture}, | |
| data, | |
| {bytesPerRow : bytesPerRow}, | |
| {width : width, height : height}); | |
| }); | |
| static void write_texture(wgpu::Queue queue, | |
| wgpu::Texture texture, | |
| uint32_t bytesPerRow, | |
| uint32_t width, | |
| uint32_t height, | |
| const void* data, | |
| size_t dataSize) | |
| { | |
| write_texture_js(emscripten_webgpu_export_queue(queue.Get()), | |
| emscripten_webgpu_export_texture(texture.Get()), | |
| bytesPerRow, | |
| width, | |
| height, | |
| reinterpret_cast<uintptr_t>(data), | |
| dataSize); | |
| } | |
| EM_JS(void, write_buffer_js, (int queue, int buffer, uintptr_t indexU8, size_t dataSize), { | |
| queue = JsValStore.get(queue); | |
| buffer = JsValStore.get(buffer); | |
| // Copy data off the WASM heap before sending it to WebGPU bindings. | |
| const data = new Uint8Array(dataSize); | |
| data.set(Module.HEAPU8.subarray(indexU8, indexU8 + dataSize)); | |
| queue.writeBuffer(buffer, 0, data, 0, dataSize); | |
| }); | |
| static void write_buffer(wgpu::Queue queue, wgpu::Buffer buffer, const void* data, size_t dataSize) | |
| { | |
| write_buffer_js(emscripten_webgpu_export_queue(queue.Get()), | |
| emscripten_webgpu_export_buffer(buffer.Get()), | |
| reinterpret_cast<uintptr_t>(data), | |
| dataSize); | |
| } | |
| #endif | |
| namespace rive::pls | |
| { | |
| // Draws emulated render-pass load/store actions for EXT_shader_pixel_local_storage. | |
| class PLSRenderContextWebGPUImpl::LoadStoreEXTPipeline | |
| { | |
| public: | |
| LoadStoreEXTPipeline(PLSRenderContextWebGPUImpl* context, | |
| LoadStoreActionsEXT actions, | |
| wgpu::TextureFormat framebufferFormat) : | |
| m_framebufferFormat(framebufferFormat) | |
| { | |
| wgpu::PipelineLayoutDescriptor pipelineLayoutDesc; | |
| if (actions & LoadStoreActionsEXT::clearColor) | |
| { | |
| // Create a uniform buffer binding for the clear color. | |
| wgpu::BindGroupLayoutEntry bindingLayouts[] = { | |
| { | |
| .binding = 0, | |
| .visibility = wgpu::ShaderStage::Fragment, | |
| .buffer = | |
| { | |
| .type = wgpu::BufferBindingType::Uniform, | |
| }, | |
| }, | |
| }; | |
| wgpu::BindGroupLayoutDescriptor bindingsDesc = { | |
| .entryCount = std::size(bindingLayouts), | |
| .entries = bindingLayouts, | |
| }; | |
| m_bindGroupLayout = context->m_device.CreateBindGroupLayout(&bindingsDesc); | |
| pipelineLayoutDesc = { | |
| .bindGroupLayoutCount = 1, | |
| .bindGroupLayouts = &m_bindGroupLayout, | |
| }; | |
| } | |
| else | |
| { | |
| pipelineLayoutDesc = { | |
| .bindGroupLayoutCount = 0, | |
| .bindGroupLayouts = nullptr, | |
| }; | |
| } | |
| wgpu::PipelineLayout pipelineLayout = | |
| context->m_device.CreatePipelineLayout(&pipelineLayoutDesc); | |
| wgpu::ShaderModule fragmentShader; | |
| std::ostringstream glsl; | |
| glsl << "#version 310 es\n"; | |
| glsl << "#pragma shader_stage(fragment)\n"; | |
| glsl << "#define " GLSL_FRAGMENT "\n"; | |
| glsl << "#define " GLSL_ENABLE_CLIPPING "\n"; | |
| BuildLoadStoreEXTGLSL(glsl, actions); | |
| fragmentShader = m_fragmentShaderHandle.compileShaderModule(context->m_device, | |
| glsl.str().c_str(), | |
| "glsl-raw"); | |
| wgpu::ColorTargetState colorTargetState = { | |
| .format = framebufferFormat, | |
| }; | |
| wgpu::FragmentState fragmentState = { | |
| .module = fragmentShader, | |
| .entryPoint = "main", | |
| .targetCount = 1, | |
| .targets = &colorTargetState, | |
| }; | |
| wgpu::RenderPipelineDescriptor desc = { | |
| .layout = pipelineLayout, | |
| .vertex = | |
| { | |
| .module = context->m_loadStoreEXTVertexShader, | |
| .entryPoint = "main", | |
| .bufferCount = 0, | |
| .buffers = nullptr, | |
| }, | |
| .primitive = | |
| { | |
| .topology = wgpu::PrimitiveTopology::TriangleStrip, | |
| .frontFace = context->m_frontFaceForOnScreenDraws, | |
| .cullMode = wgpu::CullMode::Back, | |
| }, | |
| .fragment = &fragmentState, | |
| }; | |
| m_renderPipeline = context->m_device.CreateRenderPipeline(&desc); | |
| } | |
| const wgpu::BindGroupLayout& bindGroupLayout() const | |
| { | |
| assert(m_bindGroupLayout); // We only have a bind group if there is a clear color. | |
| return m_bindGroupLayout; | |
| } | |
| wgpu::RenderPipeline renderPipeline(wgpu::TextureFormat framebufferFormat) const | |
| { | |
| assert(framebufferFormat == m_framebufferFormat); | |
| return m_renderPipeline; | |
| } | |
| private: | |
| const wgpu::TextureFormat m_framebufferFormat RIVE_MAYBE_UNUSED; | |
| wgpu::BindGroupLayout m_bindGroupLayout; | |
| EmJsHandle m_fragmentShaderHandle; | |
| wgpu::RenderPipeline m_renderPipeline; | |
| }; | |
| // Renders color ramps to the gradient texture. | |
| class PLSRenderContextWebGPUImpl::ColorRampPipeline | |
| { | |
| public: | |
| ColorRampPipeline(wgpu::Device device) | |
| { | |
| wgpu::BindGroupLayoutEntry bindingLayouts[] = { | |
| { | |
| .binding = FLUSH_UNIFORM_BUFFER_IDX, | |
| .visibility = wgpu::ShaderStage::Vertex, | |
| .buffer = | |
| { | |
| .type = wgpu::BufferBindingType::Uniform, | |
| }, | |
| }, | |
| }; | |
| wgpu::BindGroupLayoutDescriptor bindingsDesc = { | |
| .entryCount = std::size(bindingLayouts), | |
| .entries = bindingLayouts, | |
| }; | |
| m_bindGroupLayout = device.CreateBindGroupLayout(&bindingsDesc); | |
| wgpu::PipelineLayoutDescriptor pipelineLayoutDesc = { | |
| .bindGroupLayoutCount = 1, | |
| .bindGroupLayouts = &m_bindGroupLayout, | |
| }; | |
| wgpu::PipelineLayout pipelineLayout = device.CreatePipelineLayout(&pipelineLayoutDesc); | |
| wgpu::ShaderModule vertexShader = | |
| m_vertexShaderHandle.compileSPIRVShaderModule(device, | |
| color_ramp_vert, | |
| std::size(color_ramp_vert)); | |
| wgpu::VertexAttribute attrs[] = { | |
| { | |
| .format = wgpu::VertexFormat::Uint32x4, | |
| .offset = 0, | |
| .shaderLocation = 0, | |
| }, | |
| }; | |
| wgpu::VertexBufferLayout vertexBufferLayout = { | |
| .arrayStride = sizeof(pls::GradientSpan), | |
| .stepMode = wgpu::VertexStepMode::Instance, | |
| .attributeCount = std::size(attrs), | |
| .attributes = attrs, | |
| }; | |
| wgpu::ShaderModule fragmentShader = | |
| m_fragmentShaderHandle.compileSPIRVShaderModule(device, | |
| color_ramp_frag, | |
| std::size(color_ramp_frag)); | |
| wgpu::ColorTargetState colorTargetState = { | |
| .format = wgpu::TextureFormat::RGBA8Unorm, | |
| }; | |
| wgpu::FragmentState fragmentState = { | |
| .module = fragmentShader, | |
| .entryPoint = "main", | |
| .targetCount = 1, | |
| .targets = &colorTargetState, | |
| }; | |
| wgpu::RenderPipelineDescriptor desc = { | |
| .layout = pipelineLayout, | |
| .vertex = | |
| { | |
| .module = vertexShader, | |
| .entryPoint = "main", | |
| .bufferCount = 1, | |
| .buffers = &vertexBufferLayout, | |
| }, | |
| .primitive = | |
| { | |
| .topology = wgpu::PrimitiveTopology::TriangleStrip, | |
| .frontFace = kFrontFaceForOffscreenDraws, | |
| .cullMode = wgpu::CullMode::Back, | |
| }, | |
| .fragment = &fragmentState, | |
| }; | |
| m_renderPipeline = device.CreateRenderPipeline(&desc); | |
| } | |
| const wgpu::BindGroupLayout& bindGroupLayout() const { return m_bindGroupLayout; } | |
| const wgpu::RenderPipeline& renderPipeline() const { return m_renderPipeline; } | |
| private: | |
| wgpu::BindGroupLayout m_bindGroupLayout; | |
| EmJsHandle m_vertexShaderHandle; | |
| EmJsHandle m_fragmentShaderHandle; | |
| wgpu::RenderPipeline m_renderPipeline; | |
| }; | |
| // Renders tessellated vertices to the tessellation texture. | |
| class PLSRenderContextWebGPUImpl::TessellatePipeline | |
| { | |
| public: | |
| TessellatePipeline(wgpu::Device device, const ContextOptions& contextOptions) | |
| { | |
| wgpu::BindGroupLayoutEntry bindingLayouts[] = { | |
| contextOptions.disableStorageBuffers ? | |
| wgpu::BindGroupLayoutEntry{ | |
| .binding = PATH_BUFFER_IDX, | |
| .visibility = wgpu::ShaderStage::Vertex, | |
| .texture = | |
| { | |
| .sampleType = wgpu::TextureSampleType::Uint, | |
| .viewDimension = wgpu::TextureViewDimension::e2D, | |
| }, | |
| } : | |
| wgpu::BindGroupLayoutEntry{ | |
| .binding = PATH_BUFFER_IDX, | |
| .visibility = wgpu::ShaderStage::Vertex, | |
| .buffer = | |
| { | |
| .type = wgpu::BufferBindingType::ReadOnlyStorage, | |
| }, | |
| }, | |
| contextOptions.disableStorageBuffers ? | |
| wgpu::BindGroupLayoutEntry{ | |
| .binding = CONTOUR_BUFFER_IDX, | |
| .visibility = wgpu::ShaderStage::Vertex, | |
| .texture = | |
| { | |
| .sampleType = wgpu::TextureSampleType::Uint, | |
| .viewDimension = wgpu::TextureViewDimension::e2D, | |
| }, | |
| } : | |
| wgpu::BindGroupLayoutEntry{ | |
| .binding = CONTOUR_BUFFER_IDX, | |
| .visibility = wgpu::ShaderStage::Vertex, | |
| .buffer = | |
| { | |
| .type = wgpu::BufferBindingType::ReadOnlyStorage, | |
| }, | |
| }, | |
| { | |
| .binding = FLUSH_UNIFORM_BUFFER_IDX, | |
| .visibility = wgpu::ShaderStage::Vertex, | |
| .buffer = | |
| { | |
| .type = wgpu::BufferBindingType::Uniform, | |
| }, | |
| }, | |
| }; | |
| wgpu::BindGroupLayoutDescriptor bindingsDesc = { | |
| .entryCount = std::size(bindingLayouts), | |
| .entries = bindingLayouts, | |
| }; | |
| m_bindGroupLayout = device.CreateBindGroupLayout(&bindingsDesc); | |
| wgpu::PipelineLayoutDescriptor pipelineLayoutDesc = { | |
| .bindGroupLayoutCount = 1, | |
| .bindGroupLayouts = &m_bindGroupLayout, | |
| }; | |
| wgpu::PipelineLayout pipelineLayout = device.CreatePipelineLayout(&pipelineLayoutDesc); | |
| wgpu::ShaderModule vertexShader; | |
| if (contextOptions.disableStorageBuffers) | |
| { | |
| // The built-in SPIRV does not #define DISABLE_SHADER_STORAGE_BUFFERS. Recompile the | |
| // tessellation shader with storage buffers disabled. | |
| std::ostringstream vertexGLSL; | |
| vertexGLSL << "#version 460\n"; | |
| vertexGLSL << "#pragma shader_stage(vertex)\n"; | |
| vertexGLSL << "#define " GLSL_VERTEX "\n"; | |
| vertexGLSL << "#define " GLSL_DISABLE_SHADER_STORAGE_BUFFERS "\n"; | |
| vertexGLSL << "#define " GLSL_TARGET_VULKAN "\n"; | |
| vertexGLSL << "#extension GL_EXT_samplerless_texture_functions : enable\n"; | |
| vertexGLSL << glsl::glsl << "\n"; | |
| vertexGLSL << glsl::constants << "\n"; | |
| vertexGLSL << glsl::common << "\n"; | |
| vertexGLSL << glsl::tessellate << "\n"; | |
| vertexShader = | |
| m_vertexShaderHandle.compileShaderModule(device, vertexGLSL.str().c_str(), "glsl"); | |
| } | |
| else | |
| { | |
| vertexShader = | |
| m_vertexShaderHandle.compileSPIRVShaderModule(device, | |
| tessellate_vert, | |
| std::size(tessellate_vert)); | |
| } | |
| wgpu::VertexAttribute attrs[] = { | |
| { | |
| .format = wgpu::VertexFormat::Float32x4, | |
| .offset = 0, | |
| .shaderLocation = 0, | |
| }, | |
| { | |
| .format = wgpu::VertexFormat::Float32x4, | |
| .offset = 4 * sizeof(float), | |
| .shaderLocation = 1, | |
| }, | |
| { | |
| .format = wgpu::VertexFormat::Float32x4, | |
| .offset = 8 * sizeof(float), | |
| .shaderLocation = 2, | |
| }, | |
| { | |
| .format = wgpu::VertexFormat::Uint32x4, | |
| .offset = 12 * sizeof(float), | |
| .shaderLocation = 3, | |
| }, | |
| }; | |
| wgpu::VertexBufferLayout vertexBufferLayout = { | |
| .arrayStride = sizeof(pls::TessVertexSpan), | |
| .stepMode = wgpu::VertexStepMode::Instance, | |
| .attributeCount = std::size(attrs), | |
| .attributes = attrs, | |
| }; | |
| wgpu::ShaderModule fragmentShader = | |
| m_fragmentShaderHandle.compileSPIRVShaderModule(device, | |
| tessellate_frag, | |
| std::size(tessellate_frag)); | |
| wgpu::ColorTargetState colorTargetState = { | |
| .format = wgpu::TextureFormat::RGBA32Uint, | |
| }; | |
| wgpu::FragmentState fragmentState = { | |
| .module = fragmentShader, | |
| .entryPoint = "main", | |
| .targetCount = 1, | |
| .targets = &colorTargetState, | |
| }; | |
| wgpu::RenderPipelineDescriptor desc = { | |
| .layout = pipelineLayout, | |
| .vertex = | |
| { | |
| .module = vertexShader, | |
| .entryPoint = "main", | |
| .bufferCount = 1, | |
| .buffers = &vertexBufferLayout, | |
| }, | |
| .primitive = | |
| { | |
| .topology = wgpu::PrimitiveTopology::TriangleList, | |
| .frontFace = kFrontFaceForOffscreenDraws, | |
| .cullMode = wgpu::CullMode::Back, | |
| }, | |
| .fragment = &fragmentState, | |
| }; | |
| m_renderPipeline = device.CreateRenderPipeline(&desc); | |
| } | |
| const wgpu::BindGroupLayout& bindGroupLayout() const { return m_bindGroupLayout; } | |
| const wgpu::RenderPipeline renderPipeline() const { return m_renderPipeline; } | |
| private: | |
| wgpu::BindGroupLayout m_bindGroupLayout; | |
| EmJsHandle m_vertexShaderHandle; | |
| EmJsHandle m_fragmentShaderHandle; | |
| wgpu::RenderPipeline m_renderPipeline; | |
| }; | |
| // Draw paths and image meshes using the gradient and tessellation textures. | |
| class PLSRenderContextWebGPUImpl::DrawPipeline | |
| { | |
| public: | |
| DrawPipeline(PLSRenderContextWebGPUImpl* context, | |
| DrawType drawType, | |
| pls::ShaderFeatures shaderFeatures, | |
| const ContextOptions& contextOptions) | |
| { | |
| PixelLocalStorageType plsType = context->m_contextOptions.plsType; | |
| wgpu::ShaderModule vertexShader, fragmentShader; | |
| if (plsType == PixelLocalStorageType::subpassLoad || | |
| plsType == PixelLocalStorageType::EXT_shader_pixel_local_storage || | |
| contextOptions.disableStorageBuffers) | |
| { | |
| const char* language; | |
| const char* versionString; | |
| if (plsType == PixelLocalStorageType::EXT_shader_pixel_local_storage) | |
| { | |
| language = "glsl-raw"; | |
| versionString = "#version 310 es"; | |
| } | |
| else | |
| { | |
| language = "glsl"; | |
| versionString = "#version 460"; | |
| } | |
| std::ostringstream glsl; | |
| auto addDefine = [&glsl](const char* name) { glsl << "#define " << name << "\n"; }; | |
| if (plsType == PixelLocalStorageType::EXT_shader_pixel_local_storage) | |
| { | |
| glsl << "#ifdef GL_EXT_shader_pixel_local_storage\n"; | |
| addDefine(GLSL_PLS_IMPL_EXT_NATIVE); | |
| glsl << "#else\n"; | |
| glsl << "#extension GL_EXT_samplerless_texture_functions : enable\n"; | |
| addDefine(GLSL_TARGET_VULKAN); | |
| // If we are being compiled by SPIRV transpiler for introspection, | |
| // GL_EXT_shader_pixel_local_storage will not be defined. | |
| addDefine(GLSL_PLS_IMPL_NONE); | |
| glsl << "#endif\n"; | |
| } | |
| else | |
| { | |
| glsl << "#extension GL_EXT_samplerless_texture_functions : enable\n"; | |
| addDefine(GLSL_TARGET_VULKAN); | |
| addDefine(plsType == PixelLocalStorageType::subpassLoad ? GLSL_PLS_IMPL_SUBPASS_LOAD | |
| : GLSL_PLS_IMPL_NONE); | |
| } | |
| if (contextOptions.disableStorageBuffers) | |
| { | |
| addDefine(GLSL_DISABLE_SHADER_STORAGE_BUFFERS); | |
| } | |
| switch (drawType) | |
| { | |
| case DrawType::midpointFanPatches: | |
| case DrawType::outerCurvePatches: | |
| addDefine(GLSL_ENABLE_INSTANCE_INDEX); | |
| if (plsType == PixelLocalStorageType::EXT_shader_pixel_local_storage) | |
| { | |
| // The WebGPU layer automatically searches for a uniform named | |
| // "SPIRV_Cross_BaseInstance" and manages it for us. | |
| addDefine(GLSL_ENABLE_SPIRV_CROSS_BASE_INSTANCE); | |
| } | |
| break; | |
| case DrawType::interiorTriangulation: | |
| addDefine(GLSL_DRAW_INTERIOR_TRIANGLES); | |
| break; | |
| case DrawType::imageRect: | |
| RIVE_UNREACHABLE(); | |
| case DrawType::imageMesh: | |
| break; | |
| case DrawType::plsAtomicInitialize: | |
| case DrawType::plsAtomicResolve: | |
| case DrawType::stencilClipReset: | |
| RIVE_UNREACHABLE(); | |
| } | |
| for (size_t i = 0; i < pls::kShaderFeatureCount; ++i) | |
| { | |
| ShaderFeatures feature = static_cast<ShaderFeatures>(1 << i); | |
| if (shaderFeatures & feature) | |
| { | |
| addDefine(GetShaderFeatureGLSLName(feature)); | |
| } | |
| } | |
| glsl << pls::glsl::glsl << '\n'; | |
| glsl << pls::glsl::constants << '\n'; | |
| glsl << pls::glsl::common << '\n'; | |
| if (shaderFeatures & ShaderFeatures::ENABLE_ADVANCED_BLEND) | |
| { | |
| glsl << pls::glsl::advanced_blend << '\n'; | |
| } | |
| if (context->platformFeatures().avoidFlatVaryings) | |
| { | |
| addDefine(GLSL_OPTIONALLY_FLAT); | |
| } | |
| else | |
| { | |
| glsl << "#define " GLSL_OPTIONALLY_FLAT " flat\n"; | |
| } | |
| switch (drawType) | |
| { | |
| case DrawType::midpointFanPatches: | |
| case DrawType::outerCurvePatches: | |
| addDefine(GLSL_DRAW_PATH); | |
| glsl << pls::glsl::draw_path_common << '\n'; | |
| glsl << pls::glsl::draw_path << '\n'; | |
| break; | |
| case DrawType::interiorTriangulation: | |
| addDefine(GLSL_DRAW_INTERIOR_TRIANGLES); | |
| glsl << pls::glsl::draw_path_common << '\n'; | |
| glsl << pls::glsl::draw_path << '\n'; | |
| break; | |
| case DrawType::imageRect: | |
| addDefine(GLSL_DRAW_IMAGE); | |
| addDefine(GLSL_DRAW_IMAGE_RECT); | |
| RIVE_UNREACHABLE(); | |
| case DrawType::imageMesh: | |
| addDefine(GLSL_DRAW_IMAGE); | |
| addDefine(GLSL_DRAW_IMAGE_MESH); | |
| glsl << pls::glsl::draw_image_mesh << '\n'; | |
| break; | |
| case DrawType::plsAtomicInitialize: | |
| addDefine(GLSL_DRAW_RENDER_TARGET_UPDATE_BOUNDS); | |
| addDefine(GLSL_INITIALIZE_PLS); | |
| RIVE_UNREACHABLE(); | |
| case DrawType::plsAtomicResolve: | |
| addDefine(GLSL_DRAW_RENDER_TARGET_UPDATE_BOUNDS); | |
| addDefine(GLSL_RESOLVE_PLS); | |
| RIVE_UNREACHABLE(); | |
| case DrawType::stencilClipReset: | |
| RIVE_UNREACHABLE(); | |
| } | |
| std::ostringstream vertexGLSL; | |
| vertexGLSL << versionString << "\n"; | |
| vertexGLSL << "#pragma shader_stage(vertex)\n"; | |
| vertexGLSL << "#define " GLSL_VERTEX "\n"; | |
| vertexGLSL << glsl.str(); | |
| vertexShader = m_vertexShaderHandle.compileShaderModule(context->m_device, | |
| vertexGLSL.str().c_str(), | |
| language); | |
| std::ostringstream fragmentGLSL; | |
| fragmentGLSL << versionString << "\n"; | |
| fragmentGLSL << "#pragma shader_stage(fragment)\n"; | |
| fragmentGLSL << "#define " GLSL_FRAGMENT "\n"; | |
| fragmentGLSL << glsl.str(); | |
| fragmentShader = m_fragmentShaderHandle.compileShaderModule(context->m_device, | |
| fragmentGLSL.str().c_str(), | |
| language); | |
| } | |
| else | |
| { | |
| switch (drawType) | |
| { | |
| case DrawType::midpointFanPatches: | |
| case DrawType::outerCurvePatches: | |
| vertexShader = | |
| m_vertexShaderHandle.compileSPIRVShaderModule(context->m_device, | |
| draw_path_vert, | |
| std::size(draw_path_vert)); | |
| fragmentShader = | |
| m_fragmentShaderHandle.compileSPIRVShaderModule(context->m_device, | |
| draw_path_frag, | |
| std::size(draw_path_frag)); | |
| break; | |
| case DrawType::interiorTriangulation: | |
| vertexShader = m_vertexShaderHandle.compileSPIRVShaderModule( | |
| context->m_device, | |
| draw_interior_triangles_vert, | |
| std::size(draw_interior_triangles_vert)); | |
| fragmentShader = m_fragmentShaderHandle.compileSPIRVShaderModule( | |
| context->m_device, | |
| draw_interior_triangles_frag, | |
| std::size(draw_interior_triangles_frag)); | |
| break; | |
| case DrawType::imageRect: | |
| RIVE_UNREACHABLE(); | |
| case DrawType::imageMesh: | |
| vertexShader = m_vertexShaderHandle.compileSPIRVShaderModule( | |
| context->m_device, | |
| draw_image_mesh_vert, | |
| std::size(draw_image_mesh_vert)); | |
| fragmentShader = m_fragmentShaderHandle.compileSPIRVShaderModule( | |
| context->m_device, | |
| draw_image_mesh_frag, | |
| std::size(draw_image_mesh_frag)); | |
| break; | |
| case DrawType::plsAtomicInitialize: | |
| case DrawType::plsAtomicResolve: | |
| case DrawType::stencilClipReset: | |
| RIVE_UNREACHABLE(); | |
| } | |
| } | |
| for (auto framebufferFormat : | |
| {wgpu::TextureFormat::BGRA8Unorm, wgpu::TextureFormat::RGBA8Unorm}) | |
| { | |
| int pipelineIdx = RenderPipelineIdx(framebufferFormat); | |
| m_renderPipelines[pipelineIdx] = | |
| context->makePLSDrawPipeline(drawType, | |
| framebufferFormat, | |
| vertexShader, | |
| fragmentShader, | |
| &m_renderPipelineHandles[pipelineIdx]); | |
| } | |
| } | |
| const wgpu::RenderPipeline renderPipeline(wgpu::TextureFormat framebufferFormat) const | |
| { | |
| return m_renderPipelines[RenderPipelineIdx(framebufferFormat)]; | |
| } | |
| private: | |
| static int RenderPipelineIdx(wgpu::TextureFormat framebufferFormat) | |
| { | |
| assert(framebufferFormat == wgpu::TextureFormat::BGRA8Unorm || | |
| framebufferFormat == wgpu::TextureFormat::RGBA8Unorm); | |
| return framebufferFormat == wgpu::TextureFormat::BGRA8Unorm ? 1 : 0; | |
| } | |
| EmJsHandle m_vertexShaderHandle; | |
| EmJsHandle m_fragmentShaderHandle; | |
| wgpu::RenderPipeline m_renderPipelines[2]; | |
| EmJsHandle m_renderPipelineHandles[2]; | |
| }; | |
| PLSRenderContextWebGPUImpl::PLSRenderContextWebGPUImpl( | |
| wgpu::Device device, | |
| wgpu::Queue queue, | |
| const ContextOptions& contextOptions, | |
| const PlatformFeatures& baselinePlatformFeatures) : | |
| m_device(device), | |
| m_queue(queue), | |
| m_contextOptions(contextOptions), | |
| m_frontFaceForOnScreenDraws(wgpu::FrontFace::CW), | |
| m_colorRampPipeline(std::make_unique<ColorRampPipeline>(m_device)), | |
| m_tessellatePipeline(std::make_unique<TessellatePipeline>(m_device, m_contextOptions)) | |
| { | |
| m_platformFeatures = baselinePlatformFeatures; | |
| m_platformFeatures.invertOffscreenY = true; | |
| if (m_contextOptions.plsType == PixelLocalStorageType::EXT_shader_pixel_local_storage && | |
| baselinePlatformFeatures.uninvertOnScreenY) | |
| { | |
| // We will use "glsl-raw" in order to access EXT_shader_pixel_local_storage, so the WebGPU | |
| // layer won't actually get a chance to negate Y like it thinks it will. | |
| m_platformFeatures.uninvertOnScreenY = false; | |
| // PLS always expects CW, but in this case, we need to specify CCW. This is because the | |
| // WebGPU layer thinks it's going to negate Y in our shader, and will therefore also flip | |
| // our frontFace. However, since we will use raw-glsl shaders, the WebGPU layer won't | |
| // actually get a chance to negate Y like it thinks it will. Therefore, we emit the wrong | |
| // frontFace, in anticipation of it getting flipped into the correct frontFace on its way to | |
| // the driver. | |
| m_frontFaceForOnScreenDraws = wgpu::FrontFace::CCW; | |
| } | |
| } | |
| void PLSRenderContextWebGPUImpl::initGPUObjects() | |
| { | |
| wgpu::BindGroupLayoutEntry drawBindingLayouts[] = { | |
| { | |
| .binding = TESS_VERTEX_TEXTURE_IDX, | |
| .visibility = wgpu::ShaderStage::Vertex, | |
| .texture = | |
| { | |
| .sampleType = wgpu::TextureSampleType::Uint, | |
| .viewDimension = wgpu::TextureViewDimension::e2D, | |
| }, | |
| }, | |
| { | |
| .binding = GRAD_TEXTURE_IDX, | |
| .visibility = wgpu::ShaderStage::Fragment, | |
| .texture = | |
| { | |
| .sampleType = wgpu::TextureSampleType::Float, | |
| .viewDimension = wgpu::TextureViewDimension::e2D, | |
| }, | |
| }, | |
| { | |
| .binding = IMAGE_TEXTURE_IDX, | |
| .visibility = wgpu::ShaderStage::Fragment, | |
| .texture = | |
| { | |
| .sampleType = wgpu::TextureSampleType::Float, | |
| .viewDimension = wgpu::TextureViewDimension::e2D, | |
| }, | |
| }, | |
| m_contextOptions.disableStorageBuffers ? | |
| wgpu::BindGroupLayoutEntry{ | |
| .binding = PATH_BUFFER_IDX, | |
| .visibility = wgpu::ShaderStage::Vertex, | |
| .texture = | |
| { | |
| .sampleType = wgpu::TextureSampleType::Uint, | |
| .viewDimension = wgpu::TextureViewDimension::e2D, | |
| }, | |
| } : | |
| wgpu::BindGroupLayoutEntry{ | |
| .binding = PATH_BUFFER_IDX, | |
| .visibility = wgpu::ShaderStage::Vertex, | |
| .buffer = | |
| { | |
| .type = wgpu::BufferBindingType::ReadOnlyStorage, | |
| }, | |
| }, | |
| m_contextOptions.disableStorageBuffers ? | |
| wgpu::BindGroupLayoutEntry{ | |
| .binding = PAINT_BUFFER_IDX, | |
| .visibility = wgpu::ShaderStage::Vertex, | |
| .texture = | |
| { | |
| .sampleType = wgpu::TextureSampleType::Uint, | |
| .viewDimension = wgpu::TextureViewDimension::e2D, | |
| }, | |
| } : | |
| wgpu::BindGroupLayoutEntry{ | |
| .binding = PAINT_BUFFER_IDX, | |
| .visibility = wgpu::ShaderStage::Vertex, | |
| .buffer = | |
| { | |
| .type = wgpu::BufferBindingType::ReadOnlyStorage, | |
| }, | |
| }, | |
| m_contextOptions.disableStorageBuffers ? | |
| wgpu::BindGroupLayoutEntry{ | |
| .binding = PAINT_AUX_BUFFER_IDX, | |
| .visibility = wgpu::ShaderStage::Vertex, | |
| .texture = | |
| { | |
| .sampleType = wgpu::TextureSampleType::UnfilterableFloat, | |
| .viewDimension = wgpu::TextureViewDimension::e2D, | |
| }, | |
| } : | |
| wgpu::BindGroupLayoutEntry{ | |
| .binding = PAINT_AUX_BUFFER_IDX, | |
| .visibility = wgpu::ShaderStage::Vertex, | |
| .buffer = | |
| { | |
| .type = wgpu::BufferBindingType::ReadOnlyStorage, | |
| }, | |
| }, | |
| m_contextOptions.disableStorageBuffers ? | |
| wgpu::BindGroupLayoutEntry{ | |
| .binding = CONTOUR_BUFFER_IDX, | |
| .visibility = wgpu::ShaderStage::Vertex, | |
| .texture = | |
| { | |
| .sampleType = wgpu::TextureSampleType::Uint, | |
| .viewDimension = wgpu::TextureViewDimension::e2D, | |
| }, | |
| } : | |
| wgpu::BindGroupLayoutEntry{ | |
| .binding = CONTOUR_BUFFER_IDX, | |
| .visibility = wgpu::ShaderStage::Vertex, | |
| .buffer = | |
| { | |
| .type = wgpu::BufferBindingType::ReadOnlyStorage, | |
| }, | |
| }, | |
| { | |
| .binding = FLUSH_UNIFORM_BUFFER_IDX, | |
| .visibility = wgpu::ShaderStage::Vertex, | |
| .buffer = | |
| { | |
| .type = wgpu::BufferBindingType::Uniform, | |
| }, | |
| }, | |
| { | |
| .binding = IMAGE_DRAW_UNIFORM_BUFFER_IDX, | |
| .visibility = wgpu::ShaderStage::Vertex | wgpu::ShaderStage::Fragment, | |
| .buffer = | |
| { | |
| .type = wgpu::BufferBindingType::Uniform, | |
| .hasDynamicOffset = true, | |
| .minBindingSize = sizeof(pls::ImageDrawUniforms), | |
| }, | |
| }, | |
| }; | |
| wgpu::BindGroupLayoutDescriptor drawBindingsDesc = { | |
| .entryCount = std::size(drawBindingLayouts), | |
| .entries = drawBindingLayouts, | |
| }; | |
| m_drawBindGroupLayouts[0] = m_device.CreateBindGroupLayout(&drawBindingsDesc); | |
| wgpu::BindGroupLayoutEntry drawBindingSamplerLayouts[] = { | |
| { | |
| .binding = GRAD_TEXTURE_IDX, | |
| .visibility = wgpu::ShaderStage::Fragment, | |
| .sampler = | |
| { | |
| .type = wgpu::SamplerBindingType::Filtering, | |
| }, | |
| }, | |
| { | |
| .binding = IMAGE_TEXTURE_IDX, | |
| .visibility = wgpu::ShaderStage::Fragment, | |
| .sampler = | |
| { | |
| .type = wgpu::SamplerBindingType::Filtering, | |
| }, | |
| }, | |
| }; | |
| wgpu::BindGroupLayoutDescriptor samplerBindingsDesc = { | |
| .entryCount = std::size(drawBindingSamplerLayouts), | |
| .entries = drawBindingSamplerLayouts, | |
| }; | |
| m_drawBindGroupLayouts[SAMPLER_BINDINGS_SET] = | |
| m_device.CreateBindGroupLayout(&samplerBindingsDesc); | |
| wgpu::SamplerDescriptor linearSamplerDesc = { | |
| .addressModeU = wgpu::AddressMode::ClampToEdge, | |
| .addressModeV = wgpu::AddressMode::ClampToEdge, | |
| .magFilter = wgpu::FilterMode::Linear, | |
| .minFilter = wgpu::FilterMode::Linear, | |
| .mipmapFilter = wgpu::MipmapFilterMode::Nearest, | |
| }; | |
| m_linearSampler = m_device.CreateSampler(&linearSamplerDesc); | |
| wgpu::SamplerDescriptor mipmapSamplerDesc = { | |
| .addressModeU = wgpu::AddressMode::ClampToEdge, | |
| .addressModeV = wgpu::AddressMode::ClampToEdge, | |
| .magFilter = wgpu::FilterMode::Linear, | |
| .minFilter = wgpu::FilterMode::Linear, | |
| .mipmapFilter = wgpu::MipmapFilterMode::Nearest, | |
| }; | |
| m_mipmapSampler = m_device.CreateSampler(&mipmapSamplerDesc); | |
| wgpu::BindGroupEntry samplerBindingEntries[] = { | |
| { | |
| .binding = GRAD_TEXTURE_IDX, | |
| .sampler = m_linearSampler, | |
| }, | |
| { | |
| .binding = IMAGE_TEXTURE_IDX, | |
| .sampler = m_mipmapSampler, | |
| }, | |
| }; | |
| wgpu::BindGroupDescriptor samplerBindGroupDesc = { | |
| .layout = m_drawBindGroupLayouts[SAMPLER_BINDINGS_SET], | |
| .entryCount = std::size(samplerBindingEntries), | |
| .entries = samplerBindingEntries, | |
| }; | |
| m_samplerBindings = m_device.CreateBindGroup(&samplerBindGroupDesc); | |
| bool needsPLSTextureBindings = m_contextOptions.plsType == PixelLocalStorageType::subpassLoad; | |
| if (needsPLSTextureBindings) | |
| { | |
| m_drawBindGroupLayouts[PLS_TEXTURE_BINDINGS_SET] = initPLSTextureBindGroup(); | |
| } | |
| wgpu::PipelineLayoutDescriptor drawPipelineLayoutDesc = { | |
| .bindGroupLayoutCount = static_cast<size_t>(needsPLSTextureBindings ? 3 : 2), | |
| .bindGroupLayouts = m_drawBindGroupLayouts, | |
| }; | |
| m_drawPipelineLayout = m_device.CreatePipelineLayout(&drawPipelineLayoutDesc); | |
| if (m_contextOptions.plsType == PixelLocalStorageType::EXT_shader_pixel_local_storage) | |
| { | |
| // We have to manually implement load/store operations from a shader when using | |
| // EXT_shader_pixel_local_storage. | |
| std::ostringstream glsl; | |
| glsl << "#version 310 es\n"; | |
| glsl << "#pragma shader_stage(vertex)\n"; | |
| glsl << "#define " GLSL_VERTEX "\n"; | |
| // If we are being compiled by SPIRV transpiler for introspection, use gl_VertexIndex | |
| // instead of gl_VertexID. | |
| glsl << "#ifndef GL_EXT_shader_pixel_local_storage\n"; | |
| glsl << "#define gl_VertexID gl_VertexIndex\n"; | |
| glsl << "#endif\n"; | |
| glsl << "#define " GLSL_ENABLE_CLIPPING "\n"; | |
| BuildLoadStoreEXTGLSL(glsl, LoadStoreActionsEXT::none); | |
| m_loadStoreEXTVertexShader = | |
| m_loadStoreEXTVertexShaderHandle.compileShaderModule(m_device, | |
| glsl.str().c_str(), | |
| "glsl-raw"); | |
| m_loadStoreEXTUniforms = makeUniformBufferRing(sizeof(float) * 4); | |
| } | |
| wgpu::BufferDescriptor tessSpanIndexBufferDesc = { | |
| .usage = wgpu::BufferUsage::Index, | |
| .size = sizeof(pls::kTessSpanIndices), | |
| .mappedAtCreation = true, | |
| }; | |
| m_tessSpanIndexBuffer = m_device.CreateBuffer(&tessSpanIndexBufferDesc); | |
| memcpy(m_tessSpanIndexBuffer.GetMappedRange(), | |
| pls::kTessSpanIndices, | |
| sizeof(pls::kTessSpanIndices)); | |
| m_tessSpanIndexBuffer.Unmap(); | |
| wgpu::BufferDescriptor patchBufferDesc = { | |
| .usage = wgpu::BufferUsage::Vertex, | |
| .size = kPatchVertexBufferCount * sizeof(PatchVertex), | |
| .mappedAtCreation = true, | |
| }; | |
| m_pathPatchVertexBuffer = m_device.CreateBuffer(&patchBufferDesc); | |
| patchBufferDesc.size = (kPatchIndexBufferCount * sizeof(uint16_t)); | |
| // WebGPU buffer sizes must be multiples of 4. | |
| patchBufferDesc.size = math::round_up_to_multiple_of<4>(patchBufferDesc.size); | |
| patchBufferDesc.usage = wgpu::BufferUsage::Index; | |
| m_pathPatchIndexBuffer = m_device.CreateBuffer(&patchBufferDesc); | |
| GeneratePatchBufferData( | |
| reinterpret_cast<PatchVertex*>(m_pathPatchVertexBuffer.GetMappedRange()), | |
| reinterpret_cast<uint16_t*>(m_pathPatchIndexBuffer.GetMappedRange())); | |
| m_pathPatchVertexBuffer.Unmap(); | |
| m_pathPatchIndexBuffer.Unmap(); | |
| wgpu::TextureDescriptor nullImagePaintTextureDesc = { | |
| .usage = wgpu::TextureUsage::TextureBinding, | |
| .dimension = wgpu::TextureDimension::e2D, | |
| .size = {1, 1}, | |
| .format = wgpu::TextureFormat::RGBA8Unorm, | |
| }; | |
| m_nullImagePaintTexture = m_device.CreateTexture(&nullImagePaintTextureDesc); | |
| m_nullImagePaintTextureView = m_nullImagePaintTexture.CreateView(); | |
| } | |
| PLSRenderContextWebGPUImpl::~PLSRenderContextWebGPUImpl() {} | |
| PLSRenderTargetWebGPU::PLSRenderTargetWebGPU(wgpu::Device device, | |
| wgpu::TextureFormat framebufferFormat, | |
| uint32_t width, | |
| uint32_t height, | |
| wgpu::TextureUsage additionalTextureFlags) : | |
| PLSRenderTarget(width, height), m_framebufferFormat(framebufferFormat) | |
| { | |
| wgpu::TextureDescriptor desc = { | |
| .usage = wgpu::TextureUsage::RenderAttachment | additionalTextureFlags, | |
| .size = {static_cast<uint32_t>(width), static_cast<uint32_t>(height)}, | |
| }; | |
| desc.format = wgpu::TextureFormat::R32Uint; | |
| m_coverageTexture = device.CreateTexture(&desc); | |
| m_clipTexture = device.CreateTexture(&desc); | |
| desc.format = m_framebufferFormat; | |
| m_originalDstColorTexture = device.CreateTexture(&desc); | |
| m_targetTextureView = {}; // Will be configured later by setTargetTexture(). | |
| m_coverageTextureView = m_coverageTexture.CreateView(); | |
| m_clipTextureView = m_clipTexture.CreateView(); | |
| m_originalDstColorTextureView = m_originalDstColorTexture.CreateView(); | |
| } | |
| void PLSRenderTargetWebGPU::setTargetTextureView(wgpu::TextureView textureView) | |
| { | |
| m_targetTextureView = textureView; | |
| } | |
| rcp<PLSRenderTargetWebGPU> PLSRenderContextWebGPUImpl::makeRenderTarget( | |
| wgpu::TextureFormat framebufferFormat, | |
| uint32_t width, | |
| uint32_t height) | |
| { | |
| return rcp(new PLSRenderTargetWebGPU(m_device, | |
| framebufferFormat, | |
| width, | |
| height, | |
| wgpu::TextureUsage::None)); | |
| } | |
| class RenderBufferWebGPUImpl : public RenderBuffer | |
| { | |
| public: | |
| RenderBufferWebGPUImpl(wgpu::Device device, | |
| wgpu::Queue queue, | |
| RenderBufferType renderBufferType, | |
| RenderBufferFlags renderBufferFlags, | |
| size_t sizeInBytes) : | |
| RenderBuffer(renderBufferType, renderBufferFlags, sizeInBytes), | |
| m_device(device), | |
| m_queue(queue) | |
| { | |
| bool mappedOnceAtInitialization = flags() & RenderBufferFlags::mappedOnceAtInitialization; | |
| int bufferCount = mappedOnceAtInitialization ? 1 : pls::kBufferRingSize; | |
| wgpu::BufferDescriptor desc = { | |
| .usage = type() == RenderBufferType::index ? wgpu::BufferUsage::Index | |
| : wgpu::BufferUsage::Vertex, | |
| // WebGPU buffer sizes must be multiples of 4. | |
| .size = math::round_up_to_multiple_of<4>(sizeInBytes), | |
| .mappedAtCreation = mappedOnceAtInitialization, | |
| }; | |
| if (!mappedOnceAtInitialization) | |
| { | |
| desc.usage |= wgpu::BufferUsage::CopyDst; | |
| } | |
| for (int i = 0; i < bufferCount; ++i) | |
| { | |
| m_buffers[i] = device.CreateBuffer(&desc); | |
| } | |
| } | |
| wgpu::Buffer submittedBuffer() const { return m_buffers[m_submittedBufferIdx]; } | |
| protected: | |
| void* onMap() override | |
| { | |
| m_submittedBufferIdx = (m_submittedBufferIdx + 1) % pls::kBufferRingSize; | |
| assert(m_buffers[m_submittedBufferIdx] != nullptr); | |
| if (flags() & RenderBufferFlags::mappedOnceAtInitialization) | |
| { | |
| return m_buffers[m_submittedBufferIdx].GetMappedRange(); | |
| } | |
| else | |
| { | |
| if (m_stagingBuffer == nullptr) | |
| { | |
| m_stagingBuffer.reset(new uint8_t[sizeInBytes()]); | |
| } | |
| return m_stagingBuffer.get(); | |
| } | |
| } | |
| void onUnmap() override | |
| { | |
| if (flags() & RenderBufferFlags::mappedOnceAtInitialization) | |
| { | |
| m_buffers[m_submittedBufferIdx].Unmap(); | |
| } | |
| else | |
| { | |
| write_buffer(m_queue, | |
| m_buffers[m_submittedBufferIdx], | |
| m_stagingBuffer.get(), | |
| sizeInBytes()); | |
| } | |
| } | |
| private: | |
| const wgpu::Device m_device; | |
| const wgpu::Queue m_queue; | |
| wgpu::Buffer m_buffers[pls::kBufferRingSize]; | |
| int m_submittedBufferIdx = -1; | |
| std::unique_ptr<uint8_t[]> m_stagingBuffer; | |
| }; | |
| rcp<RenderBuffer> PLSRenderContextWebGPUImpl::makeRenderBuffer(RenderBufferType type, | |
| RenderBufferFlags flags, | |
| size_t sizeInBytes) | |
| { | |
| return make_rcp<RenderBufferWebGPUImpl>(m_device, m_queue, type, flags, sizeInBytes); | |
| } | |
| class PLSTextureWebGPUImpl : public PLSTexture | |
| { | |
| public: | |
| PLSTextureWebGPUImpl(wgpu::Device device, | |
| wgpu::Queue queue, | |
| uint32_t width, | |
| uint32_t height, | |
| uint32_t mipLevelCount, | |
| const uint8_t imageDataRGBA[]) : | |
| PLSTexture(width, height) | |
| { | |
| wgpu::TextureDescriptor desc = { | |
| .usage = wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::CopyDst, | |
| .dimension = wgpu::TextureDimension::e2D, | |
| .size = {width, height}, | |
| .format = wgpu::TextureFormat::RGBA8Unorm, | |
| // TODO: implement mipmap generation. | |
| .mipLevelCount = 1, // mipLevelCount, | |
| }; | |
| m_texture = device.CreateTexture(&desc); | |
| m_textureView = m_texture.CreateView(); | |
| // Specify the top-level image in the mipmap chain. | |
| // TODO: implement mipmap generation. | |
| write_texture(queue, | |
| m_texture, | |
| width * 4, | |
| width, | |
| height, | |
| imageDataRGBA, | |
| height * width * 4); | |
| } | |
| wgpu::TextureView textureView() const { return m_textureView; } | |
| private: | |
| wgpu::Texture m_texture; | |
| wgpu::TextureView m_textureView; | |
| }; | |
| rcp<PLSTexture> PLSRenderContextWebGPUImpl::makeImageTexture(uint32_t width, | |
| uint32_t height, | |
| uint32_t mipLevelCount, | |
| const uint8_t imageDataRGBA[]) | |
| { | |
| return make_rcp<PLSTextureWebGPUImpl>(m_device, | |
| m_queue, | |
| width, | |
| height, | |
| mipLevelCount, | |
| imageDataRGBA); | |
| } | |
| class BufferWebGPU : public BufferRing | |
| { | |
| public: | |
| static std::unique_ptr<BufferWebGPU> Make(wgpu::Device device, | |
| wgpu::Queue queue, | |
| size_t capacityInBytes, | |
| wgpu::BufferUsage usage) | |
| { | |
| return std::make_unique<BufferWebGPU>(device, queue, capacityInBytes, usage); | |
| } | |
| BufferWebGPU(wgpu::Device device, | |
| wgpu::Queue queue, | |
| size_t capacityInBytes, | |
| wgpu::BufferUsage usage) : | |
| BufferRing(std::max<size_t>(capacityInBytes, 1)), m_queue(queue) | |
| { | |
| wgpu::BufferDescriptor desc = { | |
| .usage = wgpu::BufferUsage::CopyDst | usage, | |
| .size = capacityInBytes, | |
| }; | |
| for (int i = 0; i < pls::kBufferRingSize; ++i) | |
| { | |
| m_buffers[i] = device.CreateBuffer(&desc); | |
| } | |
| } | |
| wgpu::Buffer submittedBuffer() const { return m_buffers[submittedBufferIdx()]; } | |
| protected: | |
| void* onMapBuffer(int bufferIdx, size_t mapSizeInBytes) override { return shadowBuffer(); } | |
| void onUnmapAndSubmitBuffer(int bufferIdx, size_t mapSizeInBytes) override | |
| { | |
| write_buffer(m_queue, m_buffers[bufferIdx], shadowBuffer(), mapSizeInBytes); | |
| } | |
| const wgpu::Queue m_queue; | |
| wgpu::Buffer m_buffers[kBufferRingSize]; | |
| }; | |
| // GL TextureFormat to use for a texture that polyfills a storage buffer. | |
| static wgpu::TextureFormat storage_texture_format(pls::StorageBufferStructure bufferStructure) | |
| { | |
| switch (bufferStructure) | |
| { | |
| case pls::StorageBufferStructure::uint32x4: | |
| return wgpu::TextureFormat::RGBA32Uint; | |
| case pls::StorageBufferStructure::uint32x2: | |
| return wgpu::TextureFormat::RG32Uint; | |
| case pls::StorageBufferStructure::float32x4: | |
| return wgpu::TextureFormat::RGBA32Float; | |
| } | |
| RIVE_UNREACHABLE(); | |
| } | |
| // Buffer ring with a texture used to polyfill storage buffers when they are disabled. | |
| class StorageTextureBufferWebGPU : public BufferWebGPU | |
| { | |
| public: | |
| StorageTextureBufferWebGPU(wgpu::Device device, | |
| wgpu::Queue queue, | |
| size_t capacityInBytes, | |
| pls::StorageBufferStructure bufferStructure) : | |
| BufferWebGPU(device, | |
| queue, | |
| pls::StorageTextureBufferSize(capacityInBytes, bufferStructure), | |
| wgpu::BufferUsage::CopySrc), | |
| m_bufferStructure(bufferStructure) | |
| { | |
| // Create a texture to mirror the buffer contents. | |
| auto [textureWidth, textureHeight] = | |
| pls::StorageTextureSize(this->capacityInBytes(), bufferStructure); | |
| wgpu::TextureDescriptor desc{ | |
| .usage = wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::CopyDst, | |
| .size = {textureWidth, textureHeight}, | |
| .format = storage_texture_format(bufferStructure), | |
| }; | |
| m_texture = device.CreateTexture(&desc); | |
| m_textureView = m_texture.CreateView(); | |
| } | |
| void updateTextureFromBuffer(size_t bindingSizeInBytes, | |
| size_t offsetSizeInBytes, | |
| wgpu::CommandEncoder encoder) const | |
| { | |
| auto [updateWidth, updateHeight] = | |
| pls::StorageTextureSize(bindingSizeInBytes, m_bufferStructure); | |
| wgpu::ImageCopyBuffer srcBuffer = { | |
| .layout = | |
| { | |
| .offset = offsetSizeInBytes, | |
| .bytesPerRow = (STORAGE_TEXTURE_WIDTH * | |
| pls::StorageBufferElementSizeInBytes(m_bufferStructure)), | |
| }, | |
| .buffer = submittedBuffer(), | |
| }; | |
| wgpu::ImageCopyTexture dstTexture = { | |
| .texture = m_texture, | |
| .origin = {0, 0, 0}, | |
| }; | |
| wgpu::Extent3D copySize = { | |
| .width = updateWidth, | |
| .height = updateHeight, | |
| }; | |
| encoder.CopyBufferToTexture(&srcBuffer, &dstTexture, ©Size); | |
| } | |
| wgpu::TextureView textureView() const { return m_textureView; } | |
| private: | |
| const StorageBufferStructure m_bufferStructure; | |
| wgpu::Texture m_texture; | |
| wgpu::TextureView m_textureView; | |
| }; | |
| std::unique_ptr<BufferRing> PLSRenderContextWebGPUImpl::makeUniformBufferRing( | |
| size_t capacityInBytes) | |
| { | |
| // Uniform blocks must be multiples of 256 bytes in size. | |
| capacityInBytes = std::max<size_t>(capacityInBytes, 256); | |
| assert(capacityInBytes % 256 == 0); | |
| return std::make_unique<BufferWebGPU>(m_device, | |
| m_queue, | |
| capacityInBytes, | |
| wgpu::BufferUsage::Uniform); | |
| } | |
| std::unique_ptr<BufferRing> PLSRenderContextWebGPUImpl::makeStorageBufferRing( | |
| size_t capacityInBytes, | |
| pls::StorageBufferStructure bufferStructure) | |
| { | |
| if (m_contextOptions.disableStorageBuffers) | |
| { | |
| return std::make_unique<StorageTextureBufferWebGPU>(m_device, | |
| m_queue, | |
| capacityInBytes, | |
| bufferStructure); | |
| } | |
| else | |
| { | |
| return std::make_unique<BufferWebGPU>(m_device, | |
| m_queue, | |
| capacityInBytes, | |
| wgpu::BufferUsage::Storage); | |
| } | |
| } | |
| std::unique_ptr<BufferRing> PLSRenderContextWebGPUImpl::makeVertexBufferRing(size_t capacityInBytes) | |
| { | |
| return std::make_unique<BufferWebGPU>(m_device, | |
| m_queue, | |
| capacityInBytes, | |
| wgpu::BufferUsage::Vertex); | |
| } | |
| std::unique_ptr<BufferRing> PLSRenderContextWebGPUImpl::makeTextureTransferBufferRing( | |
| size_t capacityInBytes) | |
| { | |
| return std::make_unique<BufferWebGPU>(m_device, | |
| m_queue, | |
| capacityInBytes, | |
| wgpu::BufferUsage::CopySrc); | |
| } | |
| void PLSRenderContextWebGPUImpl::resizeGradientTexture(uint32_t width, uint32_t height) | |
| { | |
| width = std::max(width, 1u); | |
| height = std::max(height, 1u); | |
| wgpu::TextureDescriptor desc{ | |
| .usage = wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::TextureBinding | | |
| wgpu::TextureUsage::CopyDst, | |
| .size = {static_cast<uint32_t>(width), static_cast<uint32_t>(height)}, | |
| .format = wgpu::TextureFormat::RGBA8Unorm, | |
| }; | |
| m_gradientTexture = m_device.CreateTexture(&desc); | |
| m_gradientTextureView = m_gradientTexture.CreateView(); | |
| } | |
| void PLSRenderContextWebGPUImpl::resizeTessellationTexture(uint32_t width, uint32_t height) | |
| { | |
| width = std::max(width, 1u); | |
| height = std::max(height, 1u); | |
| wgpu::TextureDescriptor desc{ | |
| .usage = wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::TextureBinding, | |
| .size = {static_cast<uint32_t>(width), static_cast<uint32_t>(height)}, | |
| .format = wgpu::TextureFormat::RGBA32Uint, | |
| }; | |
| m_tessVertexTexture = m_device.CreateTexture(&desc); | |
| m_tessVertexTextureView = m_tessVertexTexture.CreateView(); | |
| } | |
| wgpu::RenderPipeline PLSRenderContextWebGPUImpl::makePLSDrawPipeline( | |
| rive::pls::DrawType drawType, | |
| wgpu::TextureFormat framebufferFormat, | |
| wgpu::ShaderModule vertexShader, | |
| wgpu::ShaderModule fragmentShader, | |
| EmJsHandle* pipelineJSHandleIfNeeded) | |
| { | |
| std::vector<wgpu::VertexAttribute> attrs; | |
| std::vector<wgpu::VertexBufferLayout> vertexBufferLayouts; | |
| switch (drawType) | |
| { | |
| case DrawType::midpointFanPatches: | |
| case DrawType::outerCurvePatches: | |
| { | |
| attrs = { | |
| { | |
| .format = wgpu::VertexFormat::Float32x4, | |
| .offset = 0, | |
| .shaderLocation = 0, | |
| }, | |
| { | |
| .format = wgpu::VertexFormat::Float32x4, | |
| .offset = 4 * sizeof(float), | |
| .shaderLocation = 1, | |
| }, | |
| }; | |
| vertexBufferLayouts = { | |
| { | |
| .arrayStride = sizeof(pls::PatchVertex), | |
| .stepMode = wgpu::VertexStepMode::Vertex, | |
| .attributeCount = std::size(attrs), | |
| .attributes = attrs.data(), | |
| }, | |
| }; | |
| break; | |
| } | |
| case DrawType::interiorTriangulation: | |
| { | |
| attrs = { | |
| { | |
| .format = wgpu::VertexFormat::Float32x3, | |
| .offset = 0, | |
| .shaderLocation = 0, | |
| }, | |
| }; | |
| vertexBufferLayouts = { | |
| { | |
| .arrayStride = sizeof(pls::TriangleVertex), | |
| .stepMode = wgpu::VertexStepMode::Vertex, | |
| .attributeCount = std::size(attrs), | |
| .attributes = attrs.data(), | |
| }, | |
| }; | |
| break; | |
| } | |
| case DrawType::imageRect: | |
| RIVE_UNREACHABLE(); | |
| case DrawType::imageMesh: | |
| attrs = { | |
| { | |
| .format = wgpu::VertexFormat::Float32x2, | |
| .offset = 0, | |
| .shaderLocation = 0, | |
| }, | |
| { | |
| .format = wgpu::VertexFormat::Float32x2, | |
| .offset = 0, | |
| .shaderLocation = 1, | |
| }, | |
| }; | |
| vertexBufferLayouts = { | |
| { | |
| .arrayStride = sizeof(float) * 2, | |
| .stepMode = wgpu::VertexStepMode::Vertex, | |
| .attributeCount = 1, | |
| .attributes = &attrs[0], | |
| }, | |
| { | |
| .arrayStride = sizeof(float) * 2, | |
| .stepMode = wgpu::VertexStepMode::Vertex, | |
| .attributeCount = 1, | |
| .attributes = &attrs[1], | |
| }, | |
| }; | |
| break; | |
| case DrawType::plsAtomicInitialize: | |
| case DrawType::plsAtomicResolve: | |
| case DrawType::stencilClipReset: | |
| RIVE_UNREACHABLE(); | |
| } | |
| wgpu::ColorTargetState colorTargets[] = { | |
| {.format = framebufferFormat}, | |
| {.format = wgpu::TextureFormat::R32Uint}, | |
| {.format = wgpu::TextureFormat::R32Uint}, | |
| {.format = framebufferFormat}, | |
| }; | |
| static_assert(FRAMEBUFFER_PLANE_IDX == 0); | |
| static_assert(COVERAGE_PLANE_IDX == 1); | |
| static_assert(CLIP_PLANE_IDX == 2); | |
| static_assert(ORIGINAL_DST_COLOR_PLANE_IDX == 3); | |
| wgpu::FragmentState fragmentState = { | |
| .module = fragmentShader, | |
| .entryPoint = "main", | |
| .targetCount = static_cast<size_t>( | |
| m_contextOptions.plsType == PixelLocalStorageType::EXT_shader_pixel_local_storage ? 1 | |
| : 4), | |
| .targets = colorTargets, | |
| }; | |
| wgpu::RenderPipelineDescriptor desc = { | |
| .layout = m_drawPipelineLayout, | |
| .vertex = | |
| { | |
| .module = vertexShader, | |
| .entryPoint = "main", | |
| .bufferCount = std::size(vertexBufferLayouts), | |
| .buffers = vertexBufferLayouts.data(), | |
| }, | |
| .primitive = | |
| { | |
| .topology = wgpu::PrimitiveTopology::TriangleList, | |
| .frontFace = m_frontFaceForOnScreenDraws, | |
| .cullMode = | |
| drawType != DrawType::imageMesh ? wgpu::CullMode::Back : wgpu::CullMode::None, | |
| }, | |
| .fragment = &fragmentState, | |
| }; | |
| return m_device.CreateRenderPipeline(&desc); | |
| } | |
| wgpu::RenderPassEncoder PLSRenderContextWebGPUImpl::makePLSRenderPass( | |
| wgpu::CommandEncoder encoder, | |
| const PLSRenderTargetWebGPU* renderTarget, | |
| wgpu::LoadOp loadOp, | |
| const wgpu::Color& clearColor, | |
| EmJsHandle* renderPassJSHandleIfNeeded) | |
| { | |
| wgpu::RenderPassColorAttachment plsAttachments[4] = { | |
| { | |
| // framebuffer | |
| .view = renderTarget->m_targetTextureView, | |
| .loadOp = loadOp, | |
| .storeOp = wgpu::StoreOp::Store, | |
| .clearValue = clearColor, | |
| }, | |
| { | |
| // coverage | |
| .view = renderTarget->m_coverageTextureView, | |
| .loadOp = wgpu::LoadOp::Clear, | |
| .storeOp = wgpu::StoreOp::Discard, | |
| .clearValue = {}, | |
| }, | |
| { | |
| // clip | |
| .view = renderTarget->m_clipTextureView, | |
| .loadOp = wgpu::LoadOp::Clear, | |
| .storeOp = wgpu::StoreOp::Discard, | |
| .clearValue = {}, | |
| }, | |
| { | |
| // originalDstColor | |
| .view = renderTarget->m_originalDstColorTextureView, | |
| .loadOp = wgpu::LoadOp::Clear, | |
| .storeOp = wgpu::StoreOp::Discard, | |
| .clearValue = {}, | |
| }, | |
| }; | |
| static_assert(FRAMEBUFFER_PLANE_IDX == 0); | |
| static_assert(COVERAGE_PLANE_IDX == 1); | |
| static_assert(CLIP_PLANE_IDX == 2); | |
| static_assert(ORIGINAL_DST_COLOR_PLANE_IDX == 3); | |
| wgpu::RenderPassDescriptor passDesc = { | |
| .colorAttachmentCount = static_cast<size_t>( | |
| m_contextOptions.plsType == PixelLocalStorageType::EXT_shader_pixel_local_storage ? 1 | |
| : 4), | |
| .colorAttachments = plsAttachments, | |
| }; | |
| return encoder.BeginRenderPass(&passDesc); | |
| } | |
| static wgpu::Buffer webgpu_buffer(const BufferRing* bufferRing) | |
| { | |
| assert(bufferRing != nullptr); | |
| return static_cast<const BufferWebGPU*>(bufferRing)->submittedBuffer(); | |
| } | |
| template <typename HighLevelStruct> | |
| void update_webgpu_storage_texture(const BufferRing* bufferRing, | |
| size_t bindingCount, | |
| size_t firstElement, | |
| wgpu::CommandEncoder encoder) | |
| { | |
| assert(bufferRing != nullptr); | |
| auto storageTextureBuffer = static_cast<const StorageTextureBufferWebGPU*>(bufferRing); | |
| storageTextureBuffer->updateTextureFromBuffer(bindingCount * sizeof(HighLevelStruct), | |
| firstElement * sizeof(HighLevelStruct), | |
| encoder); | |
| } | |
| wgpu::TextureView webgpu_storage_texture_view(const BufferRing* bufferRing) | |
| { | |
| assert(bufferRing != nullptr); | |
| return static_cast<const StorageTextureBufferWebGPU*>(bufferRing)->textureView(); | |
| } | |
| void PLSRenderContextWebGPUImpl::flush(const FlushDescriptor& desc) | |
| { | |
| auto* renderTarget = static_cast<const PLSRenderTargetWebGPU*>(desc.renderTarget); | |
| wgpu::CommandEncoder encoder = m_device.CreateCommandEncoder(); | |
| // If storage buffers are disabled, copy their contents to textures. | |
| if (m_contextOptions.disableStorageBuffers) | |
| { | |
| if (desc.pathCount > 0) | |
| { | |
| update_webgpu_storage_texture<pls::PathData>(pathBufferRing(), | |
| desc.pathCount, | |
| desc.firstPath, | |
| encoder); | |
| update_webgpu_storage_texture<pls::PaintData>(paintBufferRing(), | |
| desc.pathCount, | |
| desc.firstPaint, | |
| encoder); | |
| update_webgpu_storage_texture<pls::PaintAuxData>(paintAuxBufferRing(), | |
| desc.pathCount, | |
| desc.firstPaintAux, | |
| encoder); | |
| } | |
| if (desc.contourCount > 0) | |
| { | |
| update_webgpu_storage_texture<pls::ContourData>(contourBufferRing(), | |
| desc.contourCount, | |
| desc.firstContour, | |
| encoder); | |
| } | |
| } | |
| // Render the complex color ramps to the gradient texture. | |
| if (desc.complexGradSpanCount > 0) | |
| { | |
| wgpu::BindGroupEntry bindingEntries[] = { | |
| { | |
| .binding = FLUSH_UNIFORM_BUFFER_IDX, | |
| .buffer = webgpu_buffer(flushUniformBufferRing()), | |
| .offset = desc.flushUniformDataOffsetInBytes, | |
| }, | |
| }; | |
| wgpu::BindGroupDescriptor bindGroupDesc = { | |
| .layout = m_colorRampPipeline->bindGroupLayout(), | |
| .entryCount = std::size(bindingEntries), | |
| .entries = bindingEntries, | |
| }; | |
| wgpu::BindGroup bindings = m_device.CreateBindGroup(&bindGroupDesc); | |
| wgpu::RenderPassColorAttachment attachment = { | |
| .view = m_gradientTextureView, | |
| .loadOp = wgpu::LoadOp::Clear, | |
| .storeOp = wgpu::StoreOp::Store, | |
| .clearValue = {}, | |
| }; | |
| wgpu::RenderPassDescriptor gradPassDesc = { | |
| .colorAttachmentCount = 1, | |
| .colorAttachments = &attachment, | |
| }; | |
| wgpu::RenderPassEncoder gradPass = encoder.BeginRenderPass(&gradPassDesc); | |
| gradPass.SetViewport(0.f, | |
| static_cast<double>(desc.complexGradRowsTop), | |
| pls::kGradTextureWidth, | |
| static_cast<float>(desc.complexGradRowsHeight), | |
| 0.0, | |
| 1.0); | |
| gradPass.SetPipeline(m_colorRampPipeline->renderPipeline()); | |
| gradPass.SetVertexBuffer(0, webgpu_buffer(gradSpanBufferRing())); | |
| gradPass.SetBindGroup(0, bindings); | |
| gradPass.Draw(4, desc.complexGradSpanCount, 0, desc.firstComplexGradSpan); | |
| gradPass.End(); | |
| } | |
| // Copy the simple color ramps to the gradient texture. | |
| if (desc.simpleGradTexelsHeight > 0) | |
| { | |
| wgpu::ImageCopyBuffer srcBuffer = { | |
| .layout = | |
| { | |
| .offset = desc.simpleGradDataOffsetInBytes, | |
| .bytesPerRow = pls::kGradTextureWidth * 4, | |
| }, | |
| .buffer = webgpu_buffer(simpleColorRampsBufferRing()), | |
| }; | |
| wgpu::ImageCopyTexture dstTexture = { | |
| .texture = m_gradientTexture, | |
| .origin = {0, 0, 0}, | |
| }; | |
| wgpu::Extent3D copySize = { | |
| .width = desc.simpleGradTexelsWidth, | |
| .height = desc.simpleGradTexelsHeight, | |
| }; | |
| encoder.CopyBufferToTexture(&srcBuffer, &dstTexture, ©Size); | |
| } | |
| // Tessellate all curves into vertices in the tessellation texture. | |
| if (desc.tessVertexSpanCount > 0) | |
| { | |
| wgpu::BindGroupEntry bindingEntries[] = { | |
| m_contextOptions.disableStorageBuffers ? | |
| wgpu::BindGroupEntry{ | |
| .binding = PATH_BUFFER_IDX, | |
| .textureView = webgpu_storage_texture_view(pathBufferRing()) | |
| } : | |
| wgpu::BindGroupEntry{ | |
| .binding = PATH_BUFFER_IDX, | |
| .buffer = webgpu_buffer(pathBufferRing()), | |
| .offset = desc.firstPath * sizeof(pls::PathData), | |
| }, | |
| m_contextOptions.disableStorageBuffers ? | |
| wgpu::BindGroupEntry{ | |
| .binding = CONTOUR_BUFFER_IDX, | |
| .textureView = webgpu_storage_texture_view(contourBufferRing()), | |
| } : | |
| wgpu::BindGroupEntry{ | |
| .binding = CONTOUR_BUFFER_IDX, | |
| .buffer = webgpu_buffer(contourBufferRing()), | |
| .offset = desc.firstContour * sizeof(pls::ContourData), | |
| }, | |
| { | |
| .binding = FLUSH_UNIFORM_BUFFER_IDX, | |
| .buffer = webgpu_buffer(flushUniformBufferRing()), | |
| .offset = desc.flushUniformDataOffsetInBytes, | |
| }, | |
| }; | |
| wgpu::BindGroupDescriptor bindGroupDesc = { | |
| .layout = m_tessellatePipeline->bindGroupLayout(), | |
| .entryCount = std::size(bindingEntries), | |
| .entries = bindingEntries, | |
| }; | |
| wgpu::BindGroup bindings = m_device.CreateBindGroup(&bindGroupDesc); | |
| wgpu::RenderPassColorAttachment attachment{ | |
| .view = m_tessVertexTextureView, | |
| .loadOp = wgpu::LoadOp::Clear, | |
| .storeOp = wgpu::StoreOp::Store, | |
| .clearValue = {}, | |
| }; | |
| wgpu::RenderPassDescriptor tessPassDesc = { | |
| .colorAttachmentCount = 1, | |
| .colorAttachments = &attachment, | |
| }; | |
| wgpu::RenderPassEncoder tessPass = encoder.BeginRenderPass(&tessPassDesc); | |
| tessPass.SetViewport(0.f, | |
| 0.f, | |
| pls::kTessTextureWidth, | |
| static_cast<float>(desc.tessDataHeight), | |
| 0.0, | |
| 1.0); | |
| tessPass.SetPipeline(m_tessellatePipeline->renderPipeline()); | |
| tessPass.SetVertexBuffer(0, webgpu_buffer(tessSpanBufferRing())); | |
| tessPass.SetIndexBuffer(m_tessSpanIndexBuffer, wgpu::IndexFormat::Uint16); | |
| tessPass.SetBindGroup(0, bindings); | |
| tessPass.DrawIndexed(std::size(pls::kTessSpanIndices), | |
| desc.tessVertexSpanCount, | |
| 0, | |
| 0, | |
| desc.firstTessVertexSpan); | |
| tessPass.End(); | |
| } | |
| wgpu::LoadOp loadOp; | |
| wgpu::Color clearColor; | |
| if (desc.colorLoadAction == LoadAction::clear) | |
| { | |
| loadOp = wgpu::LoadOp::Clear; | |
| float cc[4]; | |
| UnpackColorToRGBA32F(desc.clearColor, cc); | |
| clearColor = {cc[0], cc[1], cc[2], cc[3]}; | |
| } | |
| else | |
| { | |
| loadOp = wgpu::LoadOp::Load; | |
| } | |
| EmJsHandle drawPassJSHandle; | |
| wgpu::RenderPassEncoder drawPass = | |
| makePLSRenderPass(encoder, renderTarget, loadOp, clearColor, &drawPassJSHandle); | |
| drawPass.SetViewport(0.f, 0.f, renderTarget->width(), renderTarget->height(), 0.0, 1.0); | |
| bool needsPLSTextureBindings = m_contextOptions.plsType == PixelLocalStorageType::subpassLoad; | |
| if (needsPLSTextureBindings) | |
| { | |
| wgpu::BindGroupEntry plsTextureBindingEntries[] = { | |
| { | |
| .binding = FRAMEBUFFER_PLANE_IDX, | |
| .textureView = renderTarget->m_targetTextureView, | |
| }, | |
| { | |
| .binding = COVERAGE_PLANE_IDX, | |
| .textureView = renderTarget->m_coverageTextureView, | |
| }, | |
| { | |
| .binding = CLIP_PLANE_IDX, | |
| .textureView = renderTarget->m_clipTextureView, | |
| }, | |
| { | |
| .binding = ORIGINAL_DST_COLOR_PLANE_IDX, | |
| .textureView = renderTarget->m_originalDstColorTextureView, | |
| }, | |
| }; | |
| wgpu::BindGroupDescriptor plsTextureBindingsGroupDesc = { | |
| .layout = m_drawBindGroupLayouts[PLS_TEXTURE_BINDINGS_SET], | |
| .entryCount = std::size(plsTextureBindingEntries), | |
| .entries = plsTextureBindingEntries, | |
| }; | |
| wgpu::BindGroup plsTextureBindings = m_device.CreateBindGroup(&plsTextureBindingsGroupDesc); | |
| drawPass.SetBindGroup(PLS_TEXTURE_BINDINGS_SET, plsTextureBindings); | |
| } | |
| if (m_contextOptions.plsType == PixelLocalStorageType::EXT_shader_pixel_local_storage) | |
| { | |
| enable_shader_pixel_local_storage_ext(drawPass, true); | |
| // Draw the load action for EXT_shader_pixel_local_storage. | |
| std::array<float, 4> clearColor; | |
| LoadStoreActionsEXT loadActions = pls::BuildLoadActionsEXT(desc, &clearColor); | |
| const LoadStoreEXTPipeline& loadPipeline = | |
| m_loadStoreEXTPipelines | |
| .try_emplace(loadActions, this, loadActions, renderTarget->framebufferFormat()) | |
| .first->second; | |
| if (loadActions & LoadStoreActionsEXT::clearColor) | |
| { | |
| void* uniformData = m_loadStoreEXTUniforms->mapBuffer(sizeof(clearColor)); | |
| memcpy(uniformData, clearColor.data(), sizeof(clearColor)); | |
| m_loadStoreEXTUniforms->unmapAndSubmitBuffer(); | |
| wgpu::BindGroupEntry uniformBindingEntries[] = { | |
| { | |
| .binding = 0, | |
| .buffer = webgpu_buffer(m_loadStoreEXTUniforms.get()), | |
| }, | |
| }; | |
| wgpu::BindGroupDescriptor uniformBindGroupDesc = { | |
| .layout = loadPipeline.bindGroupLayout(), | |
| .entryCount = std::size(uniformBindingEntries), | |
| .entries = uniformBindingEntries, | |
| }; | |
| wgpu::BindGroup uniformBindings = m_device.CreateBindGroup(&uniformBindGroupDesc); | |
| drawPass.SetBindGroup(0, uniformBindings); | |
| } | |
| drawPass.SetPipeline(loadPipeline.renderPipeline(renderTarget->framebufferFormat())); | |
| drawPass.Draw(4); | |
| } | |
| drawPass.SetBindGroup(SAMPLER_BINDINGS_SET, m_samplerBindings); | |
| // Execute the DrawList. | |
| wgpu::TextureView currentImageTextureView = m_nullImagePaintTextureView; | |
| wgpu::BindGroup bindings; | |
| bool needsNewBindings = true; | |
| for (const DrawBatch& batch : *desc.drawList) | |
| { | |
| if (batch.elementCount == 0) | |
| { | |
| continue; | |
| } | |
| DrawType drawType = batch.drawType; | |
| // Bind the appropriate image texture, if any. | |
| if (auto imageTexture = static_cast<const PLSTextureWebGPUImpl*>(batch.imageTexture)) | |
| { | |
| currentImageTextureView = imageTexture->textureView(); | |
| needsNewBindings = true; | |
| } | |
| if (needsNewBindings) | |
| { | |
| wgpu::BindGroupEntry bindingEntries[] = { | |
| { | |
| .binding = TESS_VERTEX_TEXTURE_IDX, | |
| .textureView = m_tessVertexTextureView, | |
| }, | |
| { | |
| .binding = GRAD_TEXTURE_IDX, | |
| .textureView = m_gradientTextureView, | |
| }, | |
| { | |
| .binding = IMAGE_TEXTURE_IDX, | |
| .textureView = currentImageTextureView, | |
| }, | |
| m_contextOptions.disableStorageBuffers ? | |
| wgpu::BindGroupEntry{ | |
| .binding = PATH_BUFFER_IDX, | |
| .textureView = webgpu_storage_texture_view(pathBufferRing()) | |
| } : | |
| wgpu::BindGroupEntry{ | |
| .binding = PATH_BUFFER_IDX, | |
| .buffer = webgpu_buffer(pathBufferRing()), | |
| .offset = desc.firstPath * sizeof(pls::PathData), | |
| }, | |
| m_contextOptions.disableStorageBuffers ? | |
| wgpu::BindGroupEntry{ | |
| .binding = PAINT_BUFFER_IDX, | |
| .textureView = webgpu_storage_texture_view(paintBufferRing()), | |
| } : | |
| wgpu::BindGroupEntry{ | |
| .binding = PAINT_BUFFER_IDX, | |
| .buffer = webgpu_buffer(paintBufferRing()), | |
| .offset = desc.firstPaint * sizeof(pls::PaintData), | |
| }, | |
| m_contextOptions.disableStorageBuffers ? | |
| wgpu::BindGroupEntry{ | |
| .binding = PAINT_AUX_BUFFER_IDX, | |
| .textureView = webgpu_storage_texture_view(paintAuxBufferRing()), | |
| } : | |
| wgpu::BindGroupEntry{ | |
| .binding = PAINT_AUX_BUFFER_IDX, | |
| .buffer = webgpu_buffer(paintAuxBufferRing()), | |
| .offset = desc.firstPaintAux * sizeof(pls::PaintAuxData), | |
| }, | |
| m_contextOptions.disableStorageBuffers ? | |
| wgpu::BindGroupEntry{ | |
| .binding = CONTOUR_BUFFER_IDX, | |
| .textureView = webgpu_storage_texture_view(contourBufferRing()), | |
| } : | |
| wgpu::BindGroupEntry{ | |
| .binding = CONTOUR_BUFFER_IDX, | |
| .buffer = webgpu_buffer(contourBufferRing()), | |
| .offset = desc.firstContour * sizeof(pls::ContourData), | |
| }, | |
| { | |
| .binding = FLUSH_UNIFORM_BUFFER_IDX, | |
| .buffer = webgpu_buffer(flushUniformBufferRing()), | |
| .offset = desc.flushUniformDataOffsetInBytes, | |
| }, | |
| { | |
| .binding = IMAGE_DRAW_UNIFORM_BUFFER_IDX, | |
| .buffer = webgpu_buffer(imageDrawUniformBufferRing()), | |
| .size = sizeof(pls::ImageDrawUniforms), | |
| }, | |
| }; | |
| wgpu::BindGroupDescriptor bindGroupDesc = { | |
| .layout = m_drawBindGroupLayouts[0], | |
| .entryCount = std::size(bindingEntries), | |
| .entries = bindingEntries, | |
| }; | |
| bindings = m_device.CreateBindGroup(&bindGroupDesc); | |
| } | |
| if (needsNewBindings || | |
| // Image draws always re-bind because they update the dynamic offset to their uniforms. | |
| drawType == DrawType::imageRect || drawType == DrawType::imageMesh) | |
| { | |
| drawPass.SetBindGroup(0, bindings, 1, &batch.imageDrawDataOffset); | |
| needsNewBindings = false; | |
| } | |
| // Setup the pipeline for this specific drawType and shaderFeatures. | |
| const DrawPipeline& drawPipeline = | |
| m_drawPipelines | |
| .try_emplace(pls::ShaderUniqueKey(drawType, | |
| batch.shaderFeatures, | |
| pls::InterlockMode::rasterOrdering, | |
| pls::ShaderMiscFlags::none), | |
| this, | |
| drawType, | |
| batch.shaderFeatures, | |
| m_contextOptions) | |
| .first->second; | |
| drawPass.SetPipeline(drawPipeline.renderPipeline(renderTarget->framebufferFormat())); | |
| switch (drawType) | |
| { | |
| case DrawType::midpointFanPatches: | |
| case DrawType::outerCurvePatches: | |
| { | |
| // Draw PLS patches that connect the tessellation vertices. | |
| drawPass.SetVertexBuffer(0, m_pathPatchVertexBuffer); | |
| drawPass.SetIndexBuffer(m_pathPatchIndexBuffer, wgpu::IndexFormat::Uint16); | |
| drawPass.DrawIndexed(pls::PatchIndexCount(drawType), | |
| batch.elementCount, | |
| pls::PatchBaseIndex(drawType), | |
| 0, | |
| batch.baseElement); | |
| break; | |
| } | |
| case DrawType::interiorTriangulation: | |
| { | |
| drawPass.SetVertexBuffer(0, webgpu_buffer(triangleBufferRing())); | |
| drawPass.Draw(batch.elementCount, 1, batch.baseElement); | |
| break; | |
| } | |
| case DrawType::imageRect: | |
| RIVE_UNREACHABLE(); | |
| case DrawType::imageMesh: | |
| { | |
| auto vertexBuffer = static_cast<const RenderBufferWebGPUImpl*>(batch.vertexBuffer); | |
| auto uvBuffer = static_cast<const RenderBufferWebGPUImpl*>(batch.uvBuffer); | |
| auto indexBuffer = static_cast<const RenderBufferWebGPUImpl*>(batch.indexBuffer); | |
| drawPass.SetVertexBuffer(0, vertexBuffer->submittedBuffer()); | |
| drawPass.SetVertexBuffer(1, uvBuffer->submittedBuffer()); | |
| drawPass.SetIndexBuffer(indexBuffer->submittedBuffer(), wgpu::IndexFormat::Uint16); | |
| drawPass.DrawIndexed(batch.elementCount, 1, batch.baseElement); | |
| break; | |
| } | |
| case DrawType::plsAtomicInitialize: | |
| case DrawType::plsAtomicResolve: | |
| case DrawType::stencilClipReset: | |
| RIVE_UNREACHABLE(); | |
| } | |
| } | |
| if (m_contextOptions.plsType == PixelLocalStorageType::EXT_shader_pixel_local_storage) | |
| { | |
| // Draw the store action for EXT_shader_pixel_local_storage. | |
| LoadStoreActionsEXT actions = LoadStoreActionsEXT::storeColor; | |
| auto it = m_loadStoreEXTPipelines.try_emplace(actions, | |
| this, | |
| actions, | |
| renderTarget->framebufferFormat()); | |
| LoadStoreEXTPipeline* storePipeline = &it.first->second; | |
| drawPass.SetPipeline(storePipeline->renderPipeline(renderTarget->framebufferFormat())); | |
| drawPass.Draw(4); | |
| enable_shader_pixel_local_storage_ext(drawPass, false); | |
| } | |
| drawPass.End(); | |
| wgpu::CommandBuffer commands = encoder.Finish(); | |
| m_queue.Submit(1, &commands); | |
| } | |
| std::unique_ptr<PLSRenderContext> PLSRenderContextWebGPUImpl::MakeContext( | |
| wgpu::Device device, | |
| wgpu::Queue queue, | |
| const ContextOptions& contextOptions, | |
| const pls::PlatformFeatures& baselinePlatformFeatures) | |
| { | |
| std::unique_ptr<PLSRenderContextWebGPUImpl> impl; | |
| switch (contextOptions.plsType) | |
| { | |
| case PixelLocalStorageType::subpassLoad: | |
| #ifdef RIVE_WEBGPU | |
| impl = std::unique_ptr<PLSRenderContextWebGPUImpl>( | |
| new PLSRenderContextWebGPUVulkan(device, | |
| queue, | |
| contextOptions, | |
| baselinePlatformFeatures)); | |
| break; | |
| #endif | |
| case PixelLocalStorageType::EXT_shader_pixel_local_storage: | |
| case PixelLocalStorageType::none: | |
| impl = std::unique_ptr<PLSRenderContextWebGPUImpl>( | |
| new PLSRenderContextWebGPUImpl(device, | |
| queue, | |
| contextOptions, | |
| baselinePlatformFeatures)); | |
| break; | |
| } | |
| impl->initGPUObjects(); | |
| return std::make_unique<PLSRenderContext>(std::move(impl)); | |
| } | |
| } // namespace rive::pls |