| /* |
| * Copyright 2023 Rive |
| */ |
| |
| #include "rive/renderer/webgpu/render_context_webgpu_impl.hpp" |
| |
| #include "shaders/constants.glsl" |
| |
| #include <string> |
| |
| #include "generated/shaders/spirv/blit_texture_as_draw_filtered.vert.h" |
| #include "generated/shaders/spirv/blit_texture_as_draw_filtered.frag.h" |
| #include "generated/shaders/spirv/color_ramp.vert.h" |
| #include "generated/shaders/spirv/color_ramp.frag.h" |
| #include "generated/shaders/spirv/tessellate.vert.h" |
| #include "generated/shaders/spirv/tessellate.frag.h" |
| #include "generated/shaders/spirv/render_atlas.vert.h" |
| #include "generated/shaders/spirv/render_atlas_fill.frag.h" |
| #include "generated/shaders/spirv/render_atlas_stroke.frag.h" |
| #include "generated/shaders/spirv/draw_path.webgpu_vert.h" |
| #include "generated/shaders/spirv/draw_path.webgpu_frag.h" |
| #include "generated/shaders/spirv/draw_interior_triangles.webgpu_vert.h" |
| #include "generated/shaders/spirv/draw_interior_triangles.webgpu_frag.h" |
| #include "generated/shaders/spirv/draw_atlas_blit.webgpu_vert.h" |
| #include "generated/shaders/spirv/draw_atlas_blit.webgpu_frag.h" |
| #include "generated/shaders/spirv/draw_image_mesh.vert.h" |
| #include "generated/shaders/spirv/draw_image_mesh.webgpu_frag.h" |
| |
| #ifdef RIVE_DAWN |
| #include <dawn/webgpu_cpp.h> |
| #endif |
| |
| #ifdef RIVE_WEBGPU |
| #include <webgpu/webgpu_cpp.h> |
| #include <emscripten.h> |
| #include <emscripten/html5.h> |
| #if RIVE_WEBGPU == 1 |
| #include "webgpu_compat.h" |
| #endif |
| #endif |
| |
| #if (defined(RIVE_WEBGPU) && RIVE_WEBGPU > 1) || defined(RIVE_DAWN) |
| #define WGPU_STRING_VIEW(s) \ |
| { \ |
| .data = s, \ |
| .length = strlen(s), \ |
| } |
| |
| namespace wgpu |
| { |
| using ImageCopyBuffer = TexelCopyBufferInfo; |
| using ImageCopyTexture = TexelCopyTextureInfo; |
| using TextureDataLayout = TexelCopyBufferLayout; |
| }; // namespace wgpu |
| #endif |
| |
| constexpr static auto RIVE_FRONT_FACE = wgpu::FrontFace::CW; |
| |
| #ifdef RIVE_WAGYU |
| #include <webgpu/webgpu_wagyu.h> |
| |
| #include <sstream> |
| |
| #include "generated/shaders/glsl.glsl.hpp" |
| #include "generated/shaders/constants.glsl.hpp" |
| #include "generated/shaders/common.glsl.hpp" |
| #include "generated/shaders/color_ramp.glsl.hpp" |
| #include "generated/shaders/bezier_utils.glsl.hpp" |
| #include "generated/shaders/tessellate.glsl.hpp" |
| #include "generated/shaders/render_atlas.glsl.hpp" |
| #include "generated/shaders/advanced_blend.glsl.hpp" |
| #include "generated/shaders/draw_path.glsl.hpp" |
| #include "generated/shaders/draw_path_common.glsl.hpp" |
| #include "generated/shaders/draw_image_mesh.glsl.hpp" |
| |
| // When compiling "glslRaw" shaders, the WebGPU driver will automatically |
| // search for a uniform with this name and update its value when draw commands |
| // have a base instance. |
| constexpr static char BASE_INSTANCE_UNIFORM_NAME[] = "nrdp_BaseInstance"; |
| |
| EM_JS(int, gl_max_vertex_shader_storage_blocks, (), { |
| const version = globalThis.nrdp ?.version || navigator.getNrdpVersion(); |
| return version.libraries.opengl.options.limits |
| .GL_MAX_VERTEX_SHADER_STORAGE_BLOCKS; |
| }); |
| |
| wgpu::ShaderModule compile_shader_module_wagyu(wgpu::Device device, |
| const char* source, |
| WGPUWagyuShaderLanguage language) |
| { |
| WGPUChainedStruct* chainedShaderModuleDescriptor = nullptr; |
| WGPUWagyuShaderModuleDescriptor wagyuDesc = |
| WGPU_WAGYU_SHADER_MODULE_DESCRIPTOR_INIT; |
| wagyuDesc.codeSize = strlen(source); |
| wagyuDesc.code = source; |
| wagyuDesc.language = language; |
| chainedShaderModuleDescriptor = &wagyuDesc.chain; |
| |
| WGPUShaderModuleDescriptor descriptor = WGPU_SHADER_MODULE_DESCRIPTOR_INIT; |
| descriptor.nextInChain = chainedShaderModuleDescriptor; |
| auto ret = wgpuDeviceCreateShaderModule(device.Get(), &descriptor); |
| return wgpu::ShaderModule::Acquire(ret); |
| } |
| #endif |
| |
| static wgpu::ShaderModule compile_shader_module_spirv(wgpu::Device device, |
| const uint32_t* code, |
| uint32_t codeSize) |
| { |
| wgpu::ShaderModuleSPIRVDescriptor spirvDesc; |
| spirvDesc.code = code; |
| spirvDesc.codeSize = codeSize; |
| wgpu::ShaderModuleDescriptor descriptor; |
| descriptor.nextInChain = &spirvDesc; |
| return device.CreateShaderModule(&descriptor); |
| } |
| |
| namespace rive::gpu |
| { |
| #ifdef RIVE_WAGYU |
| // Draws emulated render-pass load/store actions for |
| // EXT_shader_pixel_local_storage. |
| class RenderContextWebGPUImpl::LoadStoreEXTPipeline |
| { |
| public: |
| LoadStoreEXTPipeline(RenderContextWebGPUImpl* 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 << "#define " GLSL_FRAGMENT " true\n"; |
| glsl << "#define " GLSL_ENABLE_CLIPPING " true\n"; |
| BuildLoadStoreEXTGLSL(glsl, actions); |
| fragmentShader = |
| compile_shader_module_wagyu(context->m_device, |
| glsl.str().c_str(), |
| WGPUWagyuShaderLanguage_GLSLRAW); |
| |
| 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 = RIVE_FRONT_FACE, |
| .cullMode = wgpu::CullMode::None, |
| }, |
| .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; |
| wgpu::RenderPipeline m_renderPipeline; |
| }; |
| #endif |
| |
| // Renders color ramps to the gradient texture. |
| class RenderContextWebGPUImpl::ColorRampPipeline |
| { |
| public: |
| ColorRampPipeline(RenderContextWebGPUImpl* impl) |
| { |
| const wgpu::Device device = impl->device(); |
| |
| wgpu::BindGroupLayoutDescriptor colorRampBindingsDesc = { |
| .entryCount = COLOR_RAMP_BINDINGS_COUNT, |
| .entries = impl->m_perFlushBindingLayouts.data(), |
| }; |
| |
| m_bindGroupLayout = |
| device.CreateBindGroupLayout(&colorRampBindingsDesc); |
| |
| wgpu::PipelineLayoutDescriptor pipelineLayoutDesc = { |
| .bindGroupLayoutCount = 1, |
| .bindGroupLayouts = &m_bindGroupLayout, |
| }; |
| |
| wgpu::PipelineLayout pipelineLayout = |
| device.CreatePipelineLayout(&pipelineLayoutDesc); |
| |
| wgpu::ShaderModule vertexShader, fragmentShader; |
| #ifdef RIVE_WAGYU |
| if (impl->m_capabilities.backendType == wgpu::BackendType::OpenGLES) |
| { |
| // Rive shaders tend to be long and prone to vendor bugs in the |
| // compiler. Instead of SPIRV, send down the raw Rive GLSL sources, |
| // which have various workarounds for known issues and are tested |
| // regularly. |
| std::ostringstream glsl; |
| glsl << "#define " << GLSL_POST_INVERT_Y << " true\n"; |
| glsl << glsl::glsl << "\n"; |
| glsl << glsl::constants << "\n"; |
| glsl << glsl::common << "\n"; |
| glsl << glsl::color_ramp << "\n"; |
| |
| std::ostringstream vertexGLSL; |
| vertexGLSL << "#version 310 es\n"; |
| vertexGLSL << "#define " GLSL_VERTEX " true\n"; |
| vertexGLSL << glsl.str(); |
| vertexShader = |
| compile_shader_module_wagyu(device, |
| vertexGLSL.str().c_str(), |
| WGPUWagyuShaderLanguage_GLSLRAW); |
| |
| std::ostringstream fragmentGLSL; |
| fragmentGLSL << "#version 310 es\n"; |
| fragmentGLSL << "#define " GLSL_FRAGMENT " true\n"; |
| fragmentGLSL << glsl.str(); |
| fragmentShader = |
| compile_shader_module_wagyu(device, |
| fragmentGLSL.str().c_str(), |
| WGPUWagyuShaderLanguage_GLSLRAW); |
| } |
| else |
| #endif |
| { |
| vertexShader = |
| compile_shader_module_spirv(device, |
| color_ramp_vert, |
| std::size(color_ramp_vert)); |
| fragmentShader = |
| compile_shader_module_spirv(device, |
| color_ramp_frag, |
| std::size(color_ramp_frag)); |
| } |
| |
| wgpu::VertexAttribute attrs[] = { |
| { |
| .format = wgpu::VertexFormat::Uint32x4, |
| .offset = 0, |
| .shaderLocation = 0, |
| }, |
| }; |
| |
| wgpu::VertexBufferLayout vertexBufferLayout = { |
| .attributeCount = std::size(attrs), |
| .attributes = attrs, |
| }; |
| // arrayStride and stepMode are defined in different orders in webgpu1 |
| // vs webgpu2, so can't be in the designated initializer. |
| vertexBufferLayout.arrayStride = sizeof(gpu::GradientSpan); |
| vertexBufferLayout.stepMode = wgpu::VertexStepMode::Instance; |
| |
| 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 = RIVE_FRONT_FACE, |
| .cullMode = wgpu::CullMode::None, |
| }, |
| .fragment = &fragmentState, |
| }; |
| |
| m_renderPipeline = device.CreateRenderPipeline(&desc); |
| } |
| |
| const wgpu::BindGroupLayout& bindGroupLayout() const |
| { |
| return m_bindGroupLayout; |
| } |
| wgpu::RenderPipeline renderPipeline() const { return m_renderPipeline; } |
| |
| private: |
| wgpu::BindGroupLayout m_bindGroupLayout; |
| wgpu::RenderPipeline m_renderPipeline; |
| }; |
| |
| // Renders tessellated vertices to the tessellation texture. |
| class RenderContextWebGPUImpl::TessellatePipeline |
| { |
| public: |
| TessellatePipeline(RenderContextWebGPUImpl* impl) |
| { |
| const wgpu::Device device = impl->device(); |
| |
| wgpu::BindGroupLayoutDescriptor perFlushBindingsDesc = { |
| .entryCount = TESS_BINDINGS_COUNT, |
| .entries = impl->m_perFlushBindingLayouts.data(), |
| }; |
| |
| m_perFlushBindingsLayout = |
| device.CreateBindGroupLayout(&perFlushBindingsDesc); |
| |
| wgpu::BindGroupLayout layouts[] = { |
| m_perFlushBindingsLayout, |
| impl->m_emptyBindingsLayout, |
| impl->m_drawBindGroupLayouts[IMMUTABLE_SAMPLER_BINDINGS_SET], |
| }; |
| static_assert(IMMUTABLE_SAMPLER_BINDINGS_SET == 2); |
| |
| wgpu::PipelineLayoutDescriptor pipelineLayoutDesc = { |
| .bindGroupLayoutCount = std::size(layouts), |
| .bindGroupLayouts = layouts, |
| }; |
| |
| wgpu::PipelineLayout pipelineLayout = |
| device.CreatePipelineLayout(&pipelineLayoutDesc); |
| |
| wgpu::ShaderModule vertexShader, fragmentShader; |
| #ifdef RIVE_WAGYU |
| if (impl->m_capabilities.backendType == wgpu::BackendType::OpenGLES) |
| { |
| // Rive shaders tend to be long and prone to vendor bugs in the |
| // compiler. Instead of SPIRV, send down the raw Rive GLSL sources, |
| // which have various workarounds for known issues and are tested |
| // regularly. |
| std::ostringstream glsl; |
| if (impl->m_capabilities.polyfillVertexStorageBuffers) |
| { |
| glsl << "#define " GLSL_DISABLE_SHADER_STORAGE_BUFFERS |
| " true\n"; |
| } |
| glsl << "#define " << GLSL_POST_INVERT_Y << " true\n"; |
| glsl << glsl::glsl << "\n"; |
| glsl << glsl::constants << "\n"; |
| glsl << glsl::common << "\n"; |
| glsl << glsl::bezier_utils << "\n"; |
| glsl << glsl::tessellate << "\n"; |
| |
| std::ostringstream vertexGLSL; |
| vertexGLSL << "#version 310 es\n"; |
| vertexGLSL << "#define " GLSL_VERTEX " true\n"; |
| vertexGLSL << glsl.str(); |
| vertexShader = |
| compile_shader_module_wagyu(device, |
| vertexGLSL.str().c_str(), |
| WGPUWagyuShaderLanguage_GLSLRAW); |
| |
| std::ostringstream fragmentGLSL; |
| fragmentGLSL << "#version 310 es\n"; |
| fragmentGLSL << "#define " GLSL_FRAGMENT " true\n"; |
| fragmentGLSL << glsl.str(); |
| fragmentShader = |
| compile_shader_module_wagyu(device, |
| fragmentGLSL.str().c_str(), |
| WGPUWagyuShaderLanguage_GLSLRAW); |
| } |
| else |
| #endif |
| { |
| vertexShader = |
| compile_shader_module_spirv(device, |
| tessellate_vert, |
| std::size(tessellate_vert)); |
| fragmentShader = |
| compile_shader_module_spirv(device, |
| tessellate_frag, |
| std::size(tessellate_frag)); |
| } |
| |
| 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 = { |
| .attributeCount = std::size(attrs), |
| .attributes = attrs, |
| }; |
| // arrayStride and stepMode are defined in different orders in webgpu1 |
| // vs webgpu2, so can't be in the designated initializer. |
| vertexBufferLayout.arrayStride = sizeof(gpu::TessVertexSpan); |
| vertexBufferLayout.stepMode = wgpu::VertexStepMode::Instance; |
| |
| 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 = RIVE_FRONT_FACE, |
| .cullMode = wgpu::CullMode::None, |
| }, |
| .fragment = &fragmentState, |
| }; |
| |
| m_renderPipeline = device.CreateRenderPipeline(&desc); |
| } |
| |
| wgpu::BindGroupLayout perFlushBindingsLayout() const |
| { |
| return m_perFlushBindingsLayout; |
| } |
| wgpu::RenderPipeline renderPipeline() const { return m_renderPipeline; } |
| |
| private: |
| wgpu::BindGroupLayout m_perFlushBindingsLayout; |
| wgpu::RenderPipeline m_renderPipeline; |
| }; |
| |
| // Renders tessellated vertices to the tessellation texture. |
| class RenderContextWebGPUImpl::AtlasPipeline |
| { |
| public: |
| AtlasPipeline(RenderContextWebGPUImpl* impl) |
| { |
| const wgpu::Device device = impl->device(); |
| |
| wgpu::BindGroupLayoutDescriptor perFlushBindingsDesc = { |
| .entryCount = ATLAS_BINDINGS_COUNT, |
| .entries = impl->m_perFlushBindingLayouts.data(), |
| }; |
| |
| m_perFlushBindingsLayout = |
| device.CreateBindGroupLayout(&perFlushBindingsDesc); |
| |
| wgpu::BindGroupLayout layouts[] = { |
| m_perFlushBindingsLayout, |
| impl->m_emptyBindingsLayout, |
| impl->m_drawBindGroupLayouts[IMMUTABLE_SAMPLER_BINDINGS_SET], |
| }; |
| static_assert(IMMUTABLE_SAMPLER_BINDINGS_SET == 2); |
| |
| wgpu::PipelineLayoutDescriptor pipelineLayoutDesc = { |
| .bindGroupLayoutCount = std::size(layouts), |
| .bindGroupLayouts = layouts, |
| }; |
| |
| wgpu::PipelineLayout pipelineLayout = |
| device.CreatePipelineLayout(&pipelineLayoutDesc); |
| |
| wgpu::ShaderModule vertexShader; |
| wgpu::ShaderModule fillFragmentShader, strokeFragmentShader; |
| #ifdef RIVE_WAGYU |
| if (impl->m_capabilities.backendType == wgpu::BackendType::OpenGLES) |
| { |
| // Rive shaders tend to be long and prone to vendor bugs in the |
| // compiler. Instead of SPIRV, send down the raw Rive GLSL sources, |
| // which have various workarounds for known issues and are tested |
| // regularly. |
| std::ostringstream glsl; |
| glsl << "#define " << GLSL_DRAW_PATH << " true\n"; |
| glsl << "#define " << GLSL_ENABLE_FEATHER << " true\n"; |
| glsl << "#define " << GLSL_ENABLE_INSTANCE_INDEX << " true\n"; |
| glsl << "#define " << GLSL_BASE_INSTANCE_UNIFORM_NAME << ' ' |
| << BASE_INSTANCE_UNIFORM_NAME << '\n'; |
| glsl << "#define " << GLSL_POST_INVERT_Y << " true\n"; |
| if (impl->m_capabilities.polyfillVertexStorageBuffers) |
| { |
| glsl << "#define " GLSL_DISABLE_SHADER_STORAGE_BUFFERS |
| " true\n"; |
| } |
| glsl << glsl::glsl << '\n'; |
| glsl << glsl::constants << '\n'; |
| glsl << glsl::common << '\n'; |
| glsl << glsl::draw_path_common << '\n'; |
| glsl << glsl::render_atlas << '\n'; |
| |
| std::ostringstream vertexGLSL; |
| vertexGLSL << "#version 310 es\n"; |
| vertexGLSL << "#pragma shader_stage(vertex)\n"; |
| vertexGLSL << "#define " GLSL_VERTEX " true\n"; |
| vertexGLSL << glsl.str(); |
| vertexShader = |
| compile_shader_module_wagyu(device, |
| vertexGLSL.str().c_str(), |
| WGPUWagyuShaderLanguage_GLSLRAW); |
| |
| std::ostringstream fillGLSL; |
| fillGLSL << "#version 310 es\n"; |
| fillGLSL << "#pragma shader_stage(fragment)\n"; |
| fillGLSL << "#define " GLSL_FRAGMENT " true\n"; |
| fillGLSL << "#define " GLSL_ATLAS_FEATHERED_FILL " true\n"; |
| fillGLSL << glsl.str(); |
| fillFragmentShader = |
| compile_shader_module_wagyu(device, |
| fillGLSL.str().c_str(), |
| WGPUWagyuShaderLanguage_GLSLRAW); |
| |
| std::ostringstream strokeGLSL; |
| strokeGLSL << "#version 310 es\n"; |
| strokeGLSL << "#pragma shader_stage(fragment)\n"; |
| strokeGLSL << "#define " GLSL_FRAGMENT " true\n"; |
| strokeGLSL << "#define " GLSL_ATLAS_FEATHERED_STROKE " true\n"; |
| strokeGLSL << glsl.str(); |
| strokeFragmentShader = |
| compile_shader_module_wagyu(device, |
| strokeGLSL.str().c_str(), |
| WGPUWagyuShaderLanguage_GLSLRAW); |
| } |
| else |
| #endif |
| { |
| vertexShader = |
| compile_shader_module_spirv(device, |
| render_atlas_vert, |
| std::size(render_atlas_vert)); |
| fillFragmentShader = |
| compile_shader_module_spirv(device, |
| render_atlas_fill_frag, |
| std::size(render_atlas_fill_frag)); |
| strokeFragmentShader = compile_shader_module_spirv( |
| device, |
| render_atlas_stroke_frag, |
| std::size(render_atlas_stroke_frag)); |
| } |
| |
| wgpu::VertexAttribute attrs[] = { |
| { |
| .format = wgpu::VertexFormat::Float32x4, |
| .offset = 0, |
| .shaderLocation = 0, |
| }, |
| { |
| .format = wgpu::VertexFormat::Float32x4, |
| .offset = 4 * sizeof(float), |
| .shaderLocation = 1, |
| }, |
| }; |
| |
| wgpu::VertexBufferLayout vertexBufferLayout = { |
| .attributeCount = std::size(attrs), |
| .attributes = attrs, |
| }; |
| // arrayStride and stepMode are defined in different orders in webgpu1 |
| // vs webgpu2, so can't be in the designated initializer. |
| vertexBufferLayout.arrayStride = sizeof(gpu::PatchVertex); |
| vertexBufferLayout.stepMode = wgpu::VertexStepMode::Vertex; |
| |
| wgpu::BlendState blendState = { |
| .color = { |
| .operation = wgpu::BlendOperation::Add, |
| .srcFactor = wgpu::BlendFactor::One, |
| .dstFactor = wgpu::BlendFactor::One, |
| }}; |
| |
| wgpu::ColorTargetState colorTargetState = { |
| .format = wgpu::TextureFormat::R16Float, |
| .blend = &blendState, |
| }; |
| |
| wgpu::FragmentState fragmentState = { |
| .module = fillFragmentShader, |
| .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 = RIVE_FRONT_FACE, |
| .cullMode = wgpu::CullMode::Back, |
| }, |
| .fragment = &fragmentState, |
| }; |
| |
| m_fillPipeline = device.CreateRenderPipeline(&desc); |
| |
| blendState.color.operation = wgpu::BlendOperation::Max; |
| fragmentState.module = strokeFragmentShader; |
| m_strokePipeline = device.CreateRenderPipeline(&desc); |
| } |
| |
| wgpu::BindGroupLayout perFlushBindingsLayout() const |
| { |
| return m_perFlushBindingsLayout; |
| } |
| wgpu::RenderPipeline fillPipeline() const { return m_fillPipeline; } |
| wgpu::RenderPipeline strokePipeline() const { return m_strokePipeline; } |
| |
| private: |
| wgpu::BindGroupLayout m_perFlushBindingsLayout; |
| wgpu::RenderPipeline m_fillPipeline; |
| wgpu::RenderPipeline m_strokePipeline; |
| }; |
| |
| // Draw paths and image meshes using the gradient and tessellation textures. |
| class RenderContextWebGPUImpl::DrawPipeline |
| { |
| public: |
| DrawPipeline(RenderContextWebGPUImpl* context, |
| DrawType drawType, |
| gpu::ShaderFeatures shaderFeatures, |
| bool targetIsGLFBO0) |
| { |
| wgpu::ShaderModule vertexShader, fragmentShader; |
| #ifdef RIVE_WAGYU |
| PixelLocalStorageType plsType = context->m_capabilities.plsType; |
| if (context->m_capabilities.backendType == |
| wgpu::BackendType::OpenGLES || |
| plsType == PixelLocalStorageType:: |
| VK_EXT_rasterization_order_attachment_access) |
| { |
| WGPUWagyuShaderLanguage language; |
| const char* versionString; |
| std::ostringstream glsl; |
| auto addDefine = [&glsl](const char* name) { |
| glsl << "#define " << name << " true\n"; |
| }; |
| if (context->m_capabilities.backendType == |
| wgpu::BackendType::OpenGLES) |
| { |
| language = WGPUWagyuShaderLanguage_GLSLRAW; |
| versionString = "#version 310 es"; |
| if (!targetIsGLFBO0) |
| { |
| addDefine(GLSL_POST_INVERT_Y); |
| } |
| glsl << "#define " << GLSL_BASE_INSTANCE_UNIFORM_NAME << ' ' |
| << BASE_INSTANCE_UNIFORM_NAME << '\n'; |
| } |
| else |
| { |
| language = WGPUWagyuShaderLanguage_GLSL; |
| versionString = "#version 460"; |
| addDefine(GLSL_TARGET_VULKAN); |
| } |
| if (plsType == |
| PixelLocalStorageType::GL_EXT_shader_pixel_local_storage) |
| { |
| glsl << "#ifdef GL_EXT_shader_pixel_local_storage\n"; |
| addDefine(GLSL_PLS_IMPL_EXT_NATIVE); |
| glsl << "#else\n"; |
| // If we are being compiled by SPIRV transpiler for |
| // introspection, GL_EXT_shader_pixel_local_storage will not be |
| // defined. |
| glsl << "#extension GL_EXT_samplerless_texture_functions : " |
| "enable\n"; |
| addDefine(GLSL_PLS_IMPL_NONE); |
| glsl << "#endif\n"; |
| } |
| else |
| { |
| glsl << "#extension GL_EXT_samplerless_texture_functions : " |
| "enable\n"; |
| addDefine( |
| plsType == PixelLocalStorageType:: |
| VK_EXT_rasterization_order_attachment_access |
| ? GLSL_PLS_IMPL_SUBPASS_LOAD |
| : GLSL_PLS_IMPL_NONE); |
| } |
| if (context->m_capabilities.polyfillVertexStorageBuffers) |
| { |
| addDefine(GLSL_DISABLE_SHADER_STORAGE_BUFFERS); |
| } |
| switch (drawType) |
| { |
| case DrawType::midpointFanPatches: |
| case DrawType::midpointFanCenterAAPatches: |
| case DrawType::outerCurvePatches: |
| addDefine(GLSL_ENABLE_INSTANCE_INDEX); |
| break; |
| case DrawType::atlasBlit: |
| addDefine(GLSL_ATLAS_BLIT); |
| [[fallthrough]]; |
| case DrawType::interiorTriangulation: |
| addDefine(GLSL_DRAW_INTERIOR_TRIANGLES); |
| break; |
| case DrawType::imageRect: |
| addDefine(GLSL_DRAW_IMAGE); |
| addDefine(GLSL_DRAW_IMAGE_RECT); |
| RIVE_UNREACHABLE(); |
| break; |
| case DrawType::imageMesh: |
| addDefine(GLSL_DRAW_IMAGE); |
| addDefine(GLSL_DRAW_IMAGE_MESH); |
| break; |
| case DrawType::renderPassInitialize: |
| addDefine(GLSL_DRAW_RENDER_TARGET_UPDATE_BOUNDS); |
| addDefine(GLSL_INITIALIZE_PLS); |
| RIVE_UNREACHABLE(); |
| break; |
| case DrawType::renderPassResolve: |
| addDefine(GLSL_DRAW_RENDER_TARGET_UPDATE_BOUNDS); |
| addDefine(GLSL_RESOLVE_PLS); |
| RIVE_UNREACHABLE(); |
| break; |
| case DrawType::msaaStrokes: |
| case DrawType::msaaMidpointFanBorrowedCoverage: |
| case DrawType::msaaMidpointFans: |
| case DrawType::msaaMidpointFanStencilReset: |
| case DrawType::msaaMidpointFanPathsStencil: |
| case DrawType::msaaMidpointFanPathsCover: |
| case DrawType::msaaOuterCubics: |
| case DrawType::msaaStencilClipReset: |
| RIVE_UNREACHABLE(); |
| break; |
| } |
| for (size_t i = 0; i < gpu::kShaderFeatureCount; ++i) |
| { |
| ShaderFeatures feature = static_cast<ShaderFeatures>(1 << i); |
| if (shaderFeatures & feature) |
| { |
| addDefine(GetShaderFeatureGLSLName(feature)); |
| } |
| } |
| glsl << gpu::glsl::glsl << '\n'; |
| glsl << gpu::glsl::constants << '\n'; |
| glsl << gpu::glsl::common << '\n'; |
| if (shaderFeatures & ShaderFeatures::ENABLE_ADVANCED_BLEND) |
| { |
| glsl << gpu::glsl::advanced_blend << '\n'; |
| } |
| glsl << "#define " << GLSL_OPTIONALLY_FLAT; |
| if (!context->platformFeatures().avoidFlatVaryings) |
| { |
| glsl << " flat"; |
| } |
| glsl << '\n'; |
| switch (drawType) |
| { |
| case DrawType::midpointFanPatches: |
| case DrawType::midpointFanCenterAAPatches: |
| case DrawType::outerCurvePatches: |
| addDefine(GLSL_DRAW_PATH); |
| glsl << gpu::glsl::draw_path_common << '\n'; |
| glsl << gpu::glsl::draw_path << '\n'; |
| break; |
| case DrawType::interiorTriangulation: |
| case DrawType::atlasBlit: |
| glsl << gpu::glsl::draw_path_common << '\n'; |
| glsl << gpu::glsl::draw_path << '\n'; |
| break; |
| case DrawType::imageMesh: |
| glsl << gpu::glsl::draw_image_mesh << '\n'; |
| break; |
| case DrawType::imageRect: |
| case DrawType::msaaStrokes: |
| case DrawType::msaaMidpointFanBorrowedCoverage: |
| case DrawType::msaaMidpointFans: |
| case DrawType::msaaMidpointFanStencilReset: |
| case DrawType::msaaMidpointFanPathsStencil: |
| case DrawType::msaaMidpointFanPathsCover: |
| case DrawType::msaaOuterCubics: |
| case DrawType::msaaStencilClipReset: |
| case DrawType::renderPassInitialize: |
| case DrawType::renderPassResolve: |
| RIVE_UNREACHABLE(); |
| break; |
| } |
| |
| std::ostringstream vertexGLSL; |
| vertexGLSL << versionString << "\n"; |
| vertexGLSL << "#pragma shader_stage(vertex)\n"; |
| vertexGLSL << "#define " GLSL_VERTEX " true\n"; |
| vertexGLSL << glsl.str(); |
| vertexShader = compile_shader_module_wagyu(context->m_device, |
| vertexGLSL.str().c_str(), |
| language); |
| |
| std::ostringstream fragmentGLSL; |
| fragmentGLSL << versionString << "\n"; |
| fragmentGLSL << "#pragma shader_stage(fragment)\n"; |
| fragmentGLSL << "#define " GLSL_FRAGMENT " true\n"; |
| fragmentGLSL << glsl.str(); |
| fragmentShader = |
| compile_shader_module_wagyu(context->m_device, |
| fragmentGLSL.str().c_str(), |
| language); |
| } |
| else |
| #endif |
| { |
| switch (drawType) |
| { |
| case DrawType::midpointFanPatches: |
| case DrawType::midpointFanCenterAAPatches: |
| case DrawType::outerCurvePatches: |
| vertexShader = compile_shader_module_spirv( |
| context->m_device, |
| draw_path_webgpu_vert, |
| std::size(draw_path_webgpu_vert)); |
| fragmentShader = compile_shader_module_spirv( |
| context->m_device, |
| draw_path_webgpu_frag, |
| std::size(draw_path_webgpu_frag)); |
| break; |
| case DrawType::interiorTriangulation: |
| vertexShader = compile_shader_module_spirv( |
| context->m_device, |
| draw_interior_triangles_webgpu_vert, |
| std::size(draw_interior_triangles_webgpu_vert)); |
| fragmentShader = compile_shader_module_spirv( |
| context->m_device, |
| draw_interior_triangles_webgpu_frag, |
| std::size(draw_interior_triangles_webgpu_frag)); |
| break; |
| case DrawType::atlasBlit: |
| vertexShader = compile_shader_module_spirv( |
| context->m_device, |
| draw_atlas_blit_webgpu_vert, |
| std::size(draw_atlas_blit_webgpu_vert)); |
| fragmentShader = compile_shader_module_spirv( |
| context->m_device, |
| draw_atlas_blit_webgpu_frag, |
| std::size(draw_atlas_blit_webgpu_frag)); |
| break; |
| case DrawType::imageRect: |
| RIVE_UNREACHABLE(); |
| case DrawType::imageMesh: |
| vertexShader = compile_shader_module_spirv( |
| context->m_device, |
| draw_image_mesh_vert, |
| std::size(draw_image_mesh_vert)); |
| fragmentShader = compile_shader_module_spirv( |
| context->m_device, |
| draw_image_mesh_webgpu_frag, |
| std::size(draw_image_mesh_webgpu_frag)); |
| break; |
| case DrawType::msaaStrokes: |
| case DrawType::msaaMidpointFanBorrowedCoverage: |
| case DrawType::msaaMidpointFans: |
| case DrawType::msaaMidpointFanStencilReset: |
| case DrawType::msaaMidpointFanPathsStencil: |
| case DrawType::msaaMidpointFanPathsCover: |
| case DrawType::msaaOuterCubics: |
| case DrawType::msaaStencilClipReset: |
| case DrawType::renderPassInitialize: |
| case DrawType::renderPassResolve: |
| RIVE_UNREACHABLE(); |
| } |
| } |
| |
| for (auto framebufferFormat : |
| {wgpu::TextureFormat::BGRA8Unorm, wgpu::TextureFormat::RGBA8Unorm}) |
| { |
| int pipelineIdx = RenderPipelineIdx(framebufferFormat); |
| m_renderPipelines[pipelineIdx] = |
| context->makeDrawPipeline(drawType, |
| framebufferFormat, |
| vertexShader, |
| fragmentShader); |
| } |
| } |
| |
| 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; |
| } |
| |
| wgpu::RenderPipeline m_renderPipelines[2]; |
| }; |
| |
| RenderContextWebGPUImpl::RenderContextWebGPUImpl( |
| wgpu::Adapter adapter, |
| wgpu::Device device, |
| wgpu::Queue queue, |
| const ContextOptions& contextOptions) : |
| m_device(device), m_queue(queue), m_contextOptions(contextOptions) |
| { |
| // All backends currently use raster ordered shaders. |
| // TODO: update this flag once we have msaa and atomic modes. |
| m_platformFeatures.supportsRasterOrdering = true; |
| m_platformFeatures.clipSpaceBottomUp = true; |
| m_platformFeatures.framebufferBottomUp = false; |
| |
| #ifdef RIVE_WAGYU |
| m_capabilities.backendType = static_cast<wgpu::BackendType>( |
| wgpuWagyuAdapterGetBackend(adapter.Get())); |
| WGPUWagyuStringArray extensions = WGPU_WAGYU_STRING_ARRAY_INIT; |
| if (m_capabilities.backendType == wgpu::BackendType::Vulkan) |
| { |
| wgpuWagyuDeviceGetExtensions(device.Get(), &extensions); |
| } |
| else if (m_capabilities.backendType == wgpu::BackendType::OpenGLES) |
| { |
| wgpuWagyuAdapterGetExtensions(adapter.Get(), &extensions); |
| } |
| for (size_t i = 0; i < extensions.stringCount; ++i) |
| { |
| if (m_capabilities.backendType == wgpu::BackendType::Vulkan) |
| { |
| if (!strcmp(extensions.strings[i].data, |
| "VK_EXT_rasterization_order_attachment_access")) |
| { |
| m_capabilities.plsType = PixelLocalStorageType:: |
| VK_EXT_rasterization_order_attachment_access; |
| } |
| } |
| else if (m_capabilities.backendType == wgpu::BackendType::OpenGLES) |
| { |
| if (!strcmp(extensions.strings[i].data, |
| "GL_EXT_shader_pixel_local_storage")) |
| { |
| m_capabilities.plsType = |
| PixelLocalStorageType::GL_EXT_shader_pixel_local_storage; |
| } |
| } |
| } |
| if (m_capabilities.backendType == wgpu::BackendType::OpenGLES && |
| gl_max_vertex_shader_storage_blocks() < 4) |
| { |
| // Rive requires 4 storage buffers in the vertex shader. Polyfill them |
| // if the hardware doesn't support this. |
| m_capabilities.polyfillVertexStorageBuffers = true; |
| } |
| #endif |
| } |
| |
| static wgpu::AddressMode webgpu_address_mode(rive::ImageWrap wrap) |
| { |
| switch (wrap) |
| { |
| case rive::ImageWrap::clamp: |
| return wgpu::AddressMode::ClampToEdge; |
| case rive::ImageWrap::repeat: |
| return wgpu::AddressMode::Repeat; |
| case rive::ImageWrap::mirror: |
| return wgpu::AddressMode::MirrorRepeat; |
| } |
| |
| RIVE_UNREACHABLE(); |
| } |
| |
| static wgpu::FilterMode webgpu_filter_mode(rive::ImageFilter filter) |
| { |
| switch (filter) |
| { |
| case rive::ImageFilter::trilinear: |
| return wgpu::FilterMode::Linear; |
| case rive::ImageFilter::nearest: |
| return wgpu::FilterMode::Nearest; |
| } |
| |
| RIVE_UNREACHABLE(); |
| } |
| |
| static wgpu::MipmapFilterMode webgpu_mipmap_filter_mode( |
| rive::ImageFilter filter) |
| { |
| switch (filter) |
| { |
| case rive::ImageFilter::trilinear: |
| return wgpu::MipmapFilterMode::Linear; |
| case rive::ImageFilter::nearest: |
| return wgpu::MipmapFilterMode::Nearest; |
| } |
| |
| RIVE_UNREACHABLE(); |
| } |
| |
| void RenderContextWebGPUImpl::initGPUObjects() |
| { |
| m_perFlushBindingLayouts = {{ |
| { |
| .binding = FLUSH_UNIFORM_BUFFER_IDX, |
| .visibility = |
| wgpu::ShaderStage::Vertex | wgpu::ShaderStage::Fragment, |
| .buffer = |
| { |
| .type = wgpu::BufferBindingType::Uniform, |
| }, |
| }, |
| #ifdef RIVE_WAGYU |
| m_capabilities.polyfillVertexStorageBuffers ? |
| wgpu::BindGroupLayoutEntry{ |
| .binding = PATH_BUFFER_IDX, |
| .visibility = wgpu::ShaderStage::Vertex, |
| .texture = |
| { |
| .sampleType = wgpu::TextureSampleType::Uint, |
| .viewDimension = wgpu::TextureViewDimension::e2D, |
| }, |
| } : |
| #endif |
| wgpu::BindGroupLayoutEntry{ |
| .binding = PATH_BUFFER_IDX, |
| .visibility = wgpu::ShaderStage::Vertex, |
| .buffer = |
| { |
| .type = wgpu::BufferBindingType::ReadOnlyStorage, |
| }, |
| }, |
| #ifdef RIVE_WAGYU |
| m_capabilities.polyfillVertexStorageBuffers ? |
| wgpu::BindGroupLayoutEntry{ |
| .binding = PAINT_BUFFER_IDX, |
| .visibility = wgpu::ShaderStage::Vertex, |
| .texture = |
| { |
| .sampleType = wgpu::TextureSampleType::Uint, |
| .viewDimension = wgpu::TextureViewDimension::e2D, |
| }, |
| } : |
| #endif |
| wgpu::BindGroupLayoutEntry{ |
| .binding = PAINT_BUFFER_IDX, |
| .visibility = wgpu::ShaderStage::Vertex, |
| .buffer = |
| { |
| .type = wgpu::BufferBindingType::ReadOnlyStorage, |
| }, |
| }, |
| #ifdef RIVE_WAGYU |
| m_capabilities.polyfillVertexStorageBuffers ? |
| wgpu::BindGroupLayoutEntry{ |
| .binding = PAINT_AUX_BUFFER_IDX, |
| .visibility = wgpu::ShaderStage::Vertex, |
| .texture = |
| { |
| .sampleType = wgpu::TextureSampleType::UnfilterableFloat, |
| .viewDimension = wgpu::TextureViewDimension::e2D, |
| }, |
| } : |
| #endif |
| wgpu::BindGroupLayoutEntry{ |
| .binding = PAINT_AUX_BUFFER_IDX, |
| .visibility = wgpu::ShaderStage::Vertex, |
| .buffer = |
| { |
| .type = wgpu::BufferBindingType::ReadOnlyStorage, |
| }, |
| }, |
| #ifdef RIVE_WAGYU |
| m_capabilities.polyfillVertexStorageBuffers ? |
| wgpu::BindGroupLayoutEntry{ |
| .binding = CONTOUR_BUFFER_IDX, |
| .visibility = wgpu::ShaderStage::Vertex, |
| .texture = |
| { |
| .sampleType = wgpu::TextureSampleType::Uint, |
| .viewDimension = wgpu::TextureViewDimension::e2D, |
| }, |
| } : |
| #endif |
| wgpu::BindGroupLayoutEntry{ |
| .binding = CONTOUR_BUFFER_IDX, |
| .visibility = wgpu::ShaderStage::Vertex, |
| .buffer = |
| { |
| .type = wgpu::BufferBindingType::ReadOnlyStorage, |
| }, |
| }, |
| { |
| .binding = FEATHER_TEXTURE_IDX, |
| .visibility = |
| wgpu::ShaderStage::Vertex | wgpu::ShaderStage::Fragment, |
| .texture = |
| { |
| .sampleType = wgpu::TextureSampleType::Float, |
| .viewDimension = wgpu::TextureViewDimension::e2D, |
| }, |
| }, |
| { |
| .binding = TESS_VERTEX_TEXTURE_IDX, |
| .visibility = wgpu::ShaderStage::Vertex, |
| .texture = |
| { |
| .sampleType = wgpu::TextureSampleType::Uint, |
| .viewDimension = wgpu::TextureViewDimension::e2D, |
| }, |
| }, |
| { |
| .binding = ATLAS_TEXTURE_IDX, |
| .visibility = wgpu::ShaderStage::Fragment, |
| .texture = |
| { |
| .sampleType = wgpu::TextureSampleType::Float, |
| .viewDimension = wgpu::TextureViewDimension::e2D, |
| }, |
| }, |
| { |
| .binding = GRAD_TEXTURE_IDX, |
| .visibility = wgpu::ShaderStage::Fragment, |
| .texture = |
| { |
| .sampleType = wgpu::TextureSampleType::Float, |
| .viewDimension = wgpu::TextureViewDimension::e2D, |
| }, |
| }, |
| { |
| .binding = IMAGE_DRAW_UNIFORM_BUFFER_IDX, |
| .visibility = |
| wgpu::ShaderStage::Vertex | wgpu::ShaderStage::Fragment, |
| .buffer = |
| { |
| .type = wgpu::BufferBindingType::Uniform, |
| .hasDynamicOffset = true, |
| .minBindingSize = sizeof(gpu::ImageDrawUniforms), |
| }, |
| }, |
| }}; |
| static_assert(DRAW_BINDINGS_COUNT == 10); |
| static_assert(sizeof(m_perFlushBindingLayouts) == |
| DRAW_BINDINGS_COUNT * sizeof(wgpu::BindGroupLayoutEntry)); |
| |
| wgpu::BindGroupLayoutDescriptor perFlushBindingsDesc = { |
| .entryCount = DRAW_BINDINGS_COUNT, |
| .entries = m_perFlushBindingLayouts.data(), |
| }; |
| |
| m_drawBindGroupLayouts[PER_FLUSH_BINDINGS_SET] = |
| m_device.CreateBindGroupLayout(&perFlushBindingsDesc); |
| |
| wgpu::BindGroupLayoutEntry perDrawBindingLayouts[] = { |
| { |
| .binding = IMAGE_TEXTURE_IDX, |
| .visibility = wgpu::ShaderStage::Fragment, |
| .texture = |
| { |
| .sampleType = wgpu::TextureSampleType::Float, |
| .viewDimension = wgpu::TextureViewDimension::e2D, |
| }, |
| }, |
| { |
| .binding = IMAGE_SAMPLER_IDX, |
| .visibility = wgpu::ShaderStage::Fragment, |
| .sampler = {.type = wgpu::SamplerBindingType::Filtering}, |
| }, |
| }; |
| |
| wgpu::BindGroupLayoutDescriptor perDrawBindingsDesc = { |
| .entryCount = std::size(perDrawBindingLayouts), |
| .entries = perDrawBindingLayouts, |
| }; |
| |
| m_drawBindGroupLayouts[PER_DRAW_BINDINGS_SET] = |
| m_device.CreateBindGroupLayout(&perDrawBindingsDesc); |
| |
| wgpu::BindGroupLayoutEntry drawBindingSamplerLayouts[] = { |
| { |
| .binding = GRAD_TEXTURE_IDX, |
| .visibility = wgpu::ShaderStage::Fragment, |
| .sampler = |
| { |
| .type = wgpu::SamplerBindingType::Filtering, |
| }, |
| }, |
| { |
| .binding = FEATHER_TEXTURE_IDX, |
| .visibility = |
| wgpu::ShaderStage::Vertex | wgpu::ShaderStage::Fragment, |
| .sampler = |
| { |
| .type = wgpu::SamplerBindingType::Filtering, |
| }, |
| }, |
| { |
| .binding = ATLAS_TEXTURE_IDX, |
| .visibility = wgpu::ShaderStage::Fragment, |
| .sampler = |
| { |
| .type = wgpu::SamplerBindingType::Filtering, |
| }, |
| }, |
| }; |
| |
| wgpu::BindGroupLayoutDescriptor samplerBindingsDesc = { |
| .entryCount = std::size(drawBindingSamplerLayouts), |
| .entries = drawBindingSamplerLayouts, |
| }; |
| |
| m_drawBindGroupLayouts[IMMUTABLE_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); |
| |
| for (size_t i = 0; i < ImageSampler::MAX_SAMPLER_PERMUTATIONS; ++i) |
| { |
| ImageWrap wrapX = ImageSampler::GetWrapXOptionFromKey(i); |
| ImageWrap wrapY = ImageSampler::GetWrapYOptionFromKey(i); |
| ImageFilter filter = ImageSampler::GetFilterOptionFromKey(i); |
| wgpu::FilterMode minMagFilter = webgpu_filter_mode(filter); |
| |
| wgpu::SamplerDescriptor samplerDesc = { |
| .addressModeU = webgpu_address_mode(wrapX), |
| .addressModeV = webgpu_address_mode(wrapY), |
| .magFilter = minMagFilter, |
| .minFilter = minMagFilter, |
| .mipmapFilter = webgpu_mipmap_filter_mode(filter), |
| }; |
| |
| m_imageSamplers[i] = m_device.CreateSampler(&samplerDesc); |
| } |
| |
| wgpu::BindGroupEntry samplerBindingEntries[] = { |
| { |
| .binding = GRAD_TEXTURE_IDX, |
| .sampler = m_linearSampler, |
| }, |
| { |
| .binding = FEATHER_TEXTURE_IDX, |
| .sampler = m_linearSampler, |
| }, |
| { |
| .binding = ATLAS_TEXTURE_IDX, |
| .sampler = m_linearSampler, |
| }, |
| }; |
| |
| wgpu::BindGroupDescriptor samplerBindGroupDesc = { |
| .layout = m_drawBindGroupLayouts[IMMUTABLE_SAMPLER_BINDINGS_SET], |
| .entryCount = std::size(samplerBindingEntries), |
| .entries = samplerBindingEntries, |
| }; |
| |
| m_samplerBindings = m_device.CreateBindGroup(&samplerBindGroupDesc); |
| |
| #ifdef RIVE_WAGYU |
| bool needsInputAttachmentBindings = |
| m_capabilities.plsType == |
| PixelLocalStorageType::VK_EXT_rasterization_order_attachment_access; |
| if (needsInputAttachmentBindings) |
| { |
| WGPUWagyuInputTextureBindingLayout inputAttachmentLayout = |
| WGPU_WAGYU_INPUT_TEXTURE_BINDING_LAYOUT_INIT; |
| inputAttachmentLayout.viewDimension = WGPUTextureViewDimension_2D; |
| |
| WGPUBindGroupLayoutEntry inputAttachments[4]; |
| memset(inputAttachments, 0, sizeof(inputAttachments)); |
| for (uint32_t i = 0; i < std::size(inputAttachments); ++i) |
| { |
| inputAttachments[i].nextInChain = &inputAttachmentLayout.chain; |
| inputAttachments[i].binding = i; |
| inputAttachments[i].visibility = WGPUShaderStage_Fragment; |
| } |
| |
| WGPUBindGroupLayoutDescriptor inputAttachmentsLayoutDesc = |
| WGPU_BIND_GROUP_LAYOUT_DESCRIPTOR_INIT; |
| inputAttachmentsLayoutDesc.entryCount = std::size(inputAttachments), |
| inputAttachmentsLayoutDesc.entries = inputAttachments, |
| |
| m_drawBindGroupLayouts[PLS_TEXTURE_BINDINGS_SET] = |
| wgpu::BindGroupLayout::Acquire( |
| wgpuDeviceCreateBindGroupLayout(m_device.Get(), |
| &inputAttachmentsLayoutDesc)); |
| } |
| #endif |
| |
| wgpu::PipelineLayoutDescriptor drawPipelineLayoutDesc = { |
| .bindGroupLayoutCount = static_cast<size_t>( |
| #ifdef RIVE_WAGYU |
| needsInputAttachmentBindings ? BINDINGS_SET_COUNT : |
| #endif |
| BINDINGS_SET_COUNT - 1), |
| .bindGroupLayouts = m_drawBindGroupLayouts, |
| }; |
| static_assert(PLS_TEXTURE_BINDINGS_SET == BINDINGS_SET_COUNT - 1); |
| |
| m_drawPipelineLayout = |
| m_device.CreatePipelineLayout(&drawPipelineLayoutDesc); |
| |
| wgpu::BindGroupLayoutDescriptor emptyBindingsDesc = {}; |
| m_emptyBindingsLayout = m_device.CreateBindGroupLayout(&emptyBindingsDesc); |
| |
| #ifdef RIVE_WAGYU |
| if (m_capabilities.plsType == |
| PixelLocalStorageType::GL_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 << "#define " GLSL_VERTEX " true\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 " true\n"; |
| BuildLoadStoreEXTGLSL(glsl, LoadStoreActionsEXT::none); |
| m_loadStoreEXTVertexShader = |
| compile_shader_module_wagyu(m_device, |
| glsl.str().c_str(), |
| WGPUWagyuShaderLanguage_GLSLRAW); |
| m_loadStoreEXTUniforms = makeUniformBufferRing(sizeof(float) * 4); |
| } |
| #endif |
| |
| wgpu::BufferDescriptor tessSpanIndexBufferDesc = { |
| .usage = wgpu::BufferUsage::Index, |
| .size = sizeof(gpu::kTessSpanIndices), |
| .mappedAtCreation = true, |
| }; |
| m_tessSpanIndexBuffer = m_device.CreateBuffer(&tessSpanIndexBufferDesc); |
| memcpy(m_tessSpanIndexBuffer.GetMappedRange(), |
| gpu::kTessSpanIndices, |
| sizeof(gpu::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 featherTextureDesc = { |
| .usage = |
| wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::CopyDst, |
| .dimension = wgpu::TextureDimension::e2D, |
| .size = {gpu::GAUSSIAN_TABLE_SIZE, FEATHER_TEXTURE_1D_ARRAY_LENGTH}, |
| .format = wgpu::TextureFormat::R16Float, |
| }; |
| |
| m_featherTexture = m_device.CreateTexture(&featherTextureDesc); |
| wgpu::ImageCopyTexture dest = {.texture = m_featherTexture}; |
| wgpu::TextureDataLayout layout = { |
| .bytesPerRow = sizeof(gpu::g_gaussianIntegralTableF16), |
| }; |
| wgpu::Extent3D extent = { |
| .width = gpu::GAUSSIAN_TABLE_SIZE, |
| .height = 1, |
| }; |
| m_queue.WriteTexture(&dest, |
| gpu::g_gaussianIntegralTableF16, |
| sizeof(gpu::g_gaussianIntegralTableF16), |
| &layout, |
| &extent); |
| dest.origin.y = 1; |
| m_queue.WriteTexture(&dest, |
| gpu::g_inverseGaussianIntegralTableF16, |
| sizeof(gpu::g_inverseGaussianIntegralTableF16), |
| &layout, |
| &extent); |
| m_featherTextureView = m_featherTexture.CreateView(); |
| |
| 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(); |
| |
| m_colorRampPipeline = std::make_unique<ColorRampPipeline>(this); |
| m_tessellatePipeline = std::make_unique<TessellatePipeline>(this); |
| m_atlasPipeline = std::make_unique<AtlasPipeline>(this); |
| } |
| |
| RenderContextWebGPUImpl::~RenderContextWebGPUImpl() {} |
| |
| RenderTargetWebGPU::RenderTargetWebGPU( |
| wgpu::Device device, |
| const RenderContextWebGPUImpl::Capabilities& capabilities, |
| wgpu::TextureFormat framebufferFormat, |
| uint32_t width, |
| uint32_t height) : |
| RenderTarget(width, height), |
| m_framebufferFormat(framebufferFormat), |
| m_targetTextureView{} // Will be configured later by setTargetTexture(). |
| { |
| #ifdef RIVE_WAGYU |
| // EXT_shader_pixel_local_storage doesn't need to allocate textures for |
| // clip, scratch, and coverage. These are instead kept in explicit tiled PLS |
| // memory. |
| if (capabilities.plsType == RenderContextWebGPUImpl::PixelLocalStorageType:: |
| GL_EXT_shader_pixel_local_storage) |
| { |
| return; |
| } |
| #endif |
| |
| wgpu::TextureDescriptor desc = { |
| .usage = wgpu::TextureUsage::RenderAttachment, |
| .size = {static_cast<uint32_t>(width), static_cast<uint32_t>(height)}, |
| }; |
| #ifdef RIVE_WAGYU |
| if (capabilities.plsType == |
| RenderContextWebGPUImpl::PixelLocalStorageType:: |
| VK_EXT_rasterization_order_attachment_access) |
| { |
| desc.usage |= static_cast<wgpu::TextureUsage>( |
| WGPUTextureUsage_WagyuInputAttachment | |
| WGPUTextureUsage_WagyuTransientAttachment); |
| } |
| #endif |
| |
| desc.format = wgpu::TextureFormat::R32Uint; |
| m_coverageTexture = device.CreateTexture(&desc); |
| m_clipTexture = device.CreateTexture(&desc); |
| |
| desc.format = m_framebufferFormat; |
| m_scratchColorTexture = device.CreateTexture(&desc); |
| |
| m_coverageTextureView = m_coverageTexture.CreateView(); |
| m_clipTextureView = m_clipTexture.CreateView(); |
| m_scratchColorTextureView = m_scratchColorTexture.CreateView(); |
| } |
| |
| void RenderTargetWebGPU::setTargetTextureView(wgpu::TextureView textureView, |
| wgpu::Texture texture) |
| { |
| m_targetTexture = texture; |
| m_targetTextureView = textureView; |
| } |
| |
| rcp<RenderTargetWebGPU> RenderContextWebGPUImpl::makeRenderTarget( |
| wgpu::TextureFormat framebufferFormat, |
| uint32_t width, |
| uint32_t height) |
| { |
| return rcp(new RenderTargetWebGPU(m_device, |
| m_capabilities, |
| framebufferFormat, |
| width, |
| height)); |
| } |
| |
| 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 : gpu::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) % gpu::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 |
| { |
| m_queue.WriteBuffer(m_buffers[m_submittedBufferIdx], |
| 0, |
| m_stagingBuffer.get(), |
| sizeInBytes()); |
| } |
| } |
| |
| private: |
| const wgpu::Device m_device; |
| const wgpu::Queue m_queue; |
| wgpu::Buffer m_buffers[gpu::kBufferRingSize]; |
| int m_submittedBufferIdx = -1; |
| std::unique_ptr<uint8_t[]> m_stagingBuffer; |
| }; |
| |
| rcp<RenderBuffer> RenderContextWebGPUImpl::makeRenderBuffer( |
| RenderBufferType type, |
| RenderBufferFlags flags, |
| size_t sizeInBytes) |
| { |
| return make_rcp<RenderBufferWebGPUImpl>(m_device, |
| m_queue, |
| type, |
| flags, |
| sizeInBytes); |
| } |
| |
| #ifndef RIVE_WAGYU |
| // Blits texture-to-texture using a draw command. |
| class RenderContextWebGPUImpl::BlitTextureAsDrawPipeline |
| { |
| public: |
| BlitTextureAsDrawPipeline(RenderContextWebGPUImpl* impl) |
| { |
| const wgpu::Device device = impl->device(); |
| |
| wgpu::BindGroupLayoutEntry bindingEntries[] = { |
| { |
| .binding = IMAGE_TEXTURE_IDX, |
| .visibility = wgpu::ShaderStage::Fragment, |
| .texture = |
| { |
| .sampleType = wgpu::TextureSampleType::Float, |
| .viewDimension = wgpu::TextureViewDimension::e2D, |
| }, |
| }, |
| { |
| .binding = IMAGE_SAMPLER_IDX, |
| .visibility = wgpu::ShaderStage::Fragment, |
| .sampler = |
| { |
| .type = wgpu::SamplerBindingType::Filtering, |
| }, |
| }, |
| }; |
| |
| wgpu::BindGroupLayoutDescriptor bindingsDesc = { |
| .entryCount = std::size(bindingEntries), |
| .entries = bindingEntries, |
| }; |
| |
| m_perDrawBindGroupLayout = device.CreateBindGroupLayout(&bindingsDesc); |
| |
| wgpu::BindGroupLayout layouts[] = { |
| impl->m_emptyBindingsLayout, |
| m_perDrawBindGroupLayout, |
| }; |
| static_assert(PER_DRAW_BINDINGS_SET == 1); |
| |
| wgpu::PipelineLayoutDescriptor pipelineLayoutDesc = { |
| .bindGroupLayoutCount = std::size(layouts), |
| .bindGroupLayouts = layouts, |
| }; |
| |
| wgpu::PipelineLayout pipelineLayout = |
| device.CreatePipelineLayout(&pipelineLayoutDesc); |
| |
| wgpu::ShaderModule vertexShader = compile_shader_module_spirv( |
| device, |
| blit_texture_as_draw_filtered_vert, |
| std::size(blit_texture_as_draw_filtered_vert)); |
| |
| wgpu::ShaderModule fragmentShader = compile_shader_module_spirv( |
| device, |
| blit_texture_as_draw_filtered_frag, |
| std::size(blit_texture_as_draw_filtered_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", |
| }, |
| .primitive = |
| { |
| .topology = wgpu::PrimitiveTopology::TriangleStrip, |
| }, |
| .fragment = &fragmentState, |
| }; |
| |
| m_renderPipeline = device.CreateRenderPipeline(&desc); |
| } |
| |
| const wgpu::BindGroupLayout& perDrawBindGroupLayout() const |
| { |
| return m_perDrawBindGroupLayout; |
| } |
| wgpu::RenderPipeline renderPipeline() const { return m_renderPipeline; } |
| |
| private: |
| wgpu::BindGroupLayout m_perDrawBindGroupLayout; |
| wgpu::RenderPipeline m_renderPipeline; |
| }; |
| #endif |
| |
| void RenderContextWebGPUImpl::generateMipmaps(wgpu::Texture texture) |
| { |
| wgpu::CommandEncoder mipEncoder = m_device.CreateCommandEncoder(); |
| |
| #ifdef RIVE_WAGYU |
| wgpuWagyuCommandEncoderGenerateMipmap(mipEncoder.Get(), texture.Get()); |
| #else |
| // Generate the mipmaps manually by drawing each layer. |
| if (m_blitTextureAsDrawPipeline == nullptr) |
| { |
| m_blitTextureAsDrawPipeline = |
| std::make_unique<BlitTextureAsDrawPipeline>(this); |
| } |
| |
| wgpu::TextureViewDescriptor textureViewDesc = { |
| .baseMipLevel = 0, |
| .mipLevelCount = 1, |
| }; |
| |
| wgpu::TextureView dstView, srcView = texture.CreateView(&textureViewDesc); |
| |
| for (uint32_t level = 1; level < texture.GetMipLevelCount(); |
| ++level, srcView = std::move(dstView)) |
| { |
| textureViewDesc.baseMipLevel = level; |
| dstView = texture.CreateView(&textureViewDesc); |
| |
| wgpu::RenderPassColorAttachment attachment = { |
| .view = dstView, |
| .loadOp = wgpu::LoadOp::Clear, |
| .storeOp = wgpu::StoreOp::Store, |
| .clearValue = {}, |
| }; |
| |
| wgpu::RenderPassDescriptor mipPassDesc = { |
| .colorAttachmentCount = 1, |
| .colorAttachments = &attachment, |
| }; |
| |
| wgpu::RenderPassEncoder mipPass = |
| mipEncoder.BeginRenderPass(&mipPassDesc); |
| |
| wgpu::BindGroupEntry bindingEntries[] = { |
| { |
| .binding = IMAGE_TEXTURE_IDX, |
| .textureView = srcView, |
| }, |
| { |
| .binding = IMAGE_SAMPLER_IDX, |
| .sampler = m_linearSampler, |
| }, |
| }; |
| |
| wgpu::BindGroupDescriptor bindGroupDesc = { |
| .layout = m_blitTextureAsDrawPipeline->perDrawBindGroupLayout(), |
| .entryCount = std::size(bindingEntries), |
| .entries = bindingEntries, |
| }; |
| |
| wgpu::BindGroup bindings = m_device.CreateBindGroup(&bindGroupDesc); |
| mipPass.SetBindGroup(PER_DRAW_BINDINGS_SET, bindings); |
| |
| mipPass.SetPipeline(m_blitTextureAsDrawPipeline->renderPipeline()); |
| mipPass.Draw(4); |
| mipPass.End(); |
| } |
| #endif |
| |
| wgpu::CommandBuffer commands = mipEncoder.Finish(); |
| m_queue.Submit(1, &commands); |
| } |
| |
| rcp<Texture> RenderContextWebGPUImpl::makeImageTexture( |
| uint32_t width, |
| uint32_t height, |
| uint32_t mipLevelCount, |
| const uint8_t imageDataRGBAPremul[]) |
| { |
| wgpu::TextureDescriptor textureDesc = { |
| .usage = |
| wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::CopyDst, |
| .dimension = wgpu::TextureDimension::e2D, |
| .size = {width, height}, |
| .format = wgpu::TextureFormat::RGBA8Unorm, |
| .mipLevelCount = mipLevelCount, |
| }; |
| if (mipLevelCount > 1) |
| { |
| #ifdef RIVE_WAGYU |
| // Wagyu generates mipmaps with copies. |
| textureDesc.usage |= wgpu::TextureUsage::CopySrc; |
| #else |
| // Unextended WebGPU implements mipmaps with draws. |
| textureDesc.usage |= wgpu::TextureUsage::RenderAttachment; |
| #endif |
| } |
| |
| wgpu::Texture texture = m_device.CreateTexture(&textureDesc); |
| |
| wgpu::ImageCopyTexture dest = {.texture = texture}; |
| wgpu::TextureDataLayout layout = {.bytesPerRow = width * 4}; |
| wgpu::Extent3D extent = {width, height}; |
| m_queue.WriteTexture(&dest, |
| imageDataRGBAPremul, |
| height * width * 4, |
| &layout, |
| &extent); |
| |
| if (mipLevelCount > 1) |
| { |
| generateMipmaps(texture); |
| } |
| |
| return make_rcp<TextureWebGPUImpl>(width, height, std::move(texture)); |
| } |
| |
| 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 capacityInBytesUnRounded, |
| wgpu::BufferUsage usage) : |
| // Storage buffers must be multiples of 4 in size. |
| BufferRing(math::round_up_to_multiple_of<4>( |
| std::max<size_t>(capacityInBytesUnRounded, 1))), |
| m_queue(queue) |
| { |
| wgpu::BufferDescriptor desc = { |
| .usage = wgpu::BufferUsage::CopyDst | usage, |
| .size = capacityInBytes(), |
| }; |
| for (int i = 0; i < gpu::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 |
| { |
| m_queue.WriteBuffer(m_buffers[bufferIdx], |
| 0, |
| 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( |
| gpu::StorageBufferStructure bufferStructure) |
| { |
| switch (bufferStructure) |
| { |
| case gpu::StorageBufferStructure::uint32x4: |
| return wgpu::TextureFormat::RGBA32Uint; |
| case gpu::StorageBufferStructure::uint32x2: |
| return wgpu::TextureFormat::RG32Uint; |
| case gpu::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, |
| gpu::StorageBufferStructure bufferStructure) : |
| BufferWebGPU( |
| device, |
| queue, |
| gpu::StorageTextureBufferSize(capacityInBytes, bufferStructure), |
| wgpu::BufferUsage::CopySrc), |
| m_bufferStructure(bufferStructure) |
| { |
| // Create a texture to mirror the buffer contents. |
| auto [textureWidth, textureHeight] = |
| gpu::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] = |
| gpu::StorageTextureSize(bindingSizeInBytes, m_bufferStructure); |
| wgpu::ImageCopyBuffer srcBuffer = { |
| .layout = |
| { |
| .offset = offsetSizeInBytes, |
| .bytesPerRow = (STORAGE_TEXTURE_WIDTH * |
| gpu::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> RenderContextWebGPUImpl::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> RenderContextWebGPUImpl::makeStorageBufferRing( |
| size_t capacityInBytes, |
| gpu::StorageBufferStructure bufferStructure) |
| { |
| #ifdef RIVE_WAGYU |
| if (m_capabilities.polyfillVertexStorageBuffers) |
| { |
| return std::make_unique<StorageTextureBufferWebGPU>(m_device, |
| m_queue, |
| capacityInBytes, |
| bufferStructure); |
| } |
| else |
| #endif |
| { |
| return std::make_unique<BufferWebGPU>(m_device, |
| m_queue, |
| capacityInBytes, |
| wgpu::BufferUsage::Storage); |
| } |
| } |
| |
| std::unique_ptr<BufferRing> RenderContextWebGPUImpl::makeVertexBufferRing( |
| size_t capacityInBytes) |
| { |
| return std::make_unique<BufferWebGPU>(m_device, |
| m_queue, |
| capacityInBytes, |
| wgpu::BufferUsage::Vertex); |
| } |
| |
| void RenderContextWebGPUImpl::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, |
| .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 RenderContextWebGPUImpl::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(); |
| } |
| |
| void RenderContextWebGPUImpl::resizeAtlasTexture(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::R16Float, |
| }; |
| |
| m_atlasTexture = m_device.CreateTexture(&desc); |
| m_atlasTextureView = m_atlasTexture.CreateView(); |
| } |
| |
| wgpu::RenderPipeline RenderContextWebGPUImpl::makeDrawPipeline( |
| rive::gpu::DrawType drawType, |
| wgpu::TextureFormat framebufferFormat, |
| wgpu::ShaderModule vertexShader, |
| wgpu::ShaderModule fragmentShader) |
| { |
| std::vector<WGPUVertexAttribute> attrs; |
| std::vector<WGPUVertexBufferLayout> vertexBufferLayouts; |
| switch (drawType) |
| { |
| case DrawType::midpointFanPatches: |
| case DrawType::midpointFanCenterAAPatches: |
| case DrawType::outerCurvePatches: |
| { |
| attrs = { |
| WGPUVertexAttribute{ |
| .format = WGPUVertexFormat_Float32x4, |
| .offset = 0, |
| .shaderLocation = 0, |
| }, |
| WGPUVertexAttribute{ |
| .format = WGPUVertexFormat_Float32x4, |
| .offset = 4 * sizeof(float), |
| .shaderLocation = 1, |
| }, |
| }; |
| |
| vertexBufferLayouts = {WGPU_VERTEX_BUFFER_LAYOUT_INIT}; |
| vertexBufferLayouts[0].attributeCount = std::size(attrs); |
| vertexBufferLayouts[0].attributes = attrs.data(); |
| vertexBufferLayouts[0].arrayStride = sizeof(gpu::PatchVertex); |
| vertexBufferLayouts[0].stepMode = WGPUVertexStepMode_Vertex; |
| break; |
| } |
| case DrawType::interiorTriangulation: |
| case DrawType::atlasBlit: |
| { |
| attrs = { |
| WGPUVertexAttribute{ |
| .format = WGPUVertexFormat_Float32x3, |
| .offset = 0, |
| .shaderLocation = 0, |
| }, |
| }; |
| |
| vertexBufferLayouts = {WGPU_VERTEX_BUFFER_LAYOUT_INIT}; |
| vertexBufferLayouts[0].attributeCount = std::size(attrs); |
| vertexBufferLayouts[0].attributes = attrs.data(); |
| vertexBufferLayouts[0].arrayStride = sizeof(gpu::TriangleVertex); |
| vertexBufferLayouts[0].stepMode = WGPUVertexStepMode_Vertex; |
| break; |
| } |
| case DrawType::imageRect: |
| RIVE_UNREACHABLE(); |
| case DrawType::imageMesh: |
| attrs = { |
| WGPUVertexAttribute{ |
| .format = WGPUVertexFormat_Float32x2, |
| .offset = 0, |
| .shaderLocation = 0, |
| }, |
| WGPUVertexAttribute{ |
| .format = WGPUVertexFormat_Float32x2, |
| .offset = 0, |
| .shaderLocation = 1, |
| }, |
| }; |
| |
| vertexBufferLayouts = {WGPU_VERTEX_BUFFER_LAYOUT_INIT, |
| WGPU_VERTEX_BUFFER_LAYOUT_INIT}; |
| |
| vertexBufferLayouts[0].attributeCount = 1; |
| vertexBufferLayouts[0].attributes = &attrs[0]; |
| vertexBufferLayouts[0].arrayStride = sizeof(float) * 2; |
| vertexBufferLayouts[0].stepMode = WGPUVertexStepMode_Vertex; |
| |
| vertexBufferLayouts[1].attributeCount = 1; |
| vertexBufferLayouts[1].attributes = &attrs[1]; |
| vertexBufferLayouts[1].arrayStride = sizeof(float) * 2; |
| vertexBufferLayouts[1].stepMode = WGPUVertexStepMode_Vertex; |
| break; |
| case DrawType::msaaStrokes: |
| case DrawType::msaaMidpointFanBorrowedCoverage: |
| case DrawType::msaaMidpointFans: |
| case DrawType::msaaMidpointFanStencilReset: |
| case DrawType::msaaMidpointFanPathsStencil: |
| case DrawType::msaaMidpointFanPathsCover: |
| case DrawType::msaaOuterCubics: |
| case DrawType::msaaStencilClipReset: |
| case DrawType::renderPassInitialize: |
| case DrawType::renderPassResolve: |
| RIVE_UNREACHABLE(); |
| } |
| |
| WGPUBlendState srcOverBlend = { |
| .color = |
| { |
| .operation = WGPUBlendOperation_Add, |
| .srcFactor = WGPUBlendFactor_One, |
| .dstFactor = WGPUBlendFactor_OneMinusSrcAlpha, |
| }, |
| .alpha = |
| { |
| .operation = WGPUBlendOperation_Add, |
| .srcFactor = WGPUBlendFactor_One, |
| .dstFactor = WGPUBlendFactor_OneMinusSrcAlpha, |
| }, |
| }; |
| |
| WGPUChainedStruct* extraColorTargetState = nullptr; |
| #ifdef RIVE_WAGYU |
| WGPUWagyuColorTargetState wagyuColorTargetState = |
| WGPU_WAGYU_COLOR_TARGET_STATE_INIT; |
| if (m_capabilities.plsType == |
| PixelLocalStorageType::VK_EXT_rasterization_order_attachment_access) |
| { |
| // WGPUWagyu needs us to tell it when color attachments are also used as |
| // input attachments. |
| wagyuColorTargetState.usedAsInput = WGPUOptionalBool_True; |
| extraColorTargetState = &wagyuColorTargetState.chain; |
| } |
| #endif |
| WGPUColorTargetState colorAttachments[PLS_PLANE_COUNT] = { |
| { |
| .nextInChain = extraColorTargetState, |
| .format = static_cast<WGPUTextureFormat>(framebufferFormat), |
| .blend = |
| #ifdef RIVE_WAGYU |
| m_capabilities.plsType != PixelLocalStorageType::none |
| ? nullptr |
| : |
| #endif |
| &srcOverBlend, |
| .writeMask = WGPUColorWriteMask_All, |
| }, |
| { |
| .nextInChain = extraColorTargetState, |
| .format = WGPUTextureFormat_R32Uint, |
| .writeMask = WGPUColorWriteMask_All, |
| }, |
| { |
| .nextInChain = extraColorTargetState, |
| .format = static_cast<WGPUTextureFormat>(framebufferFormat), |
| .writeMask = WGPUColorWriteMask_All, |
| }, |
| { |
| .nextInChain = extraColorTargetState, |
| .format = WGPUTextureFormat_R32Uint, |
| .writeMask = WGPUColorWriteMask_All, |
| }, |
| }; |
| static_assert(COLOR_PLANE_IDX == 0); |
| static_assert(CLIP_PLANE_IDX == 1); |
| static_assert(SCRATCH_COLOR_PLANE_IDX == 2); |
| static_assert(COVERAGE_PLANE_IDX == 3); |
| static_assert(PLS_PLANE_COUNT == 4); |
| |
| WGPUFragmentState fragmentState = { |
| .module = fragmentShader.Get(), |
| .entryPoint = WGPU_STRING_VIEW("main"), |
| .targetCount = static_cast<size_t>( |
| #ifdef RIVE_WAGYU |
| m_capabilities.plsType == |
| PixelLocalStorageType::GL_EXT_shader_pixel_local_storage |
| ? 1 |
| : |
| #endif |
| std::size(colorAttachments)), |
| .targets = colorAttachments, |
| }; |
| |
| #ifdef RIVE_WAGYU |
| WGPUWagyuInputAttachmentState inputAttachments[PLS_PLANE_COUNT]; |
| WGPUWagyuFragmentState wagyuFragmentState = WGPU_WAGYU_FRAGMENT_STATE_INIT; |
| if (m_capabilities.plsType == |
| PixelLocalStorageType::VK_EXT_rasterization_order_attachment_access) |
| { |
| for (size_t i = 0; i < PLS_PLANE_COUNT; ++i) |
| { |
| inputAttachments[i] = WGPU_WAGYU_INPUT_ATTACHMENT_STATE_INIT; |
| inputAttachments[i].format = colorAttachments[i].format; |
| inputAttachments[i].usedAsColor = WGPUOptionalBool_True; |
| } |
| wagyuFragmentState.inputCount = std::size(inputAttachments); |
| wagyuFragmentState.inputs = inputAttachments; |
| wagyuFragmentState.featureFlags = |
| WGPUWagyuFragmentStateFeaturesFlags_RasterizationOrderAttachmentAccess; |
| fragmentState.nextInChain = &wagyuFragmentState.chain; |
| } |
| #endif |
| |
| WGPURenderPipelineDescriptor desc = { |
| .layout = m_drawPipelineLayout.Get(), |
| .vertex = |
| { |
| .module = vertexShader.Get(), |
| .entryPoint = WGPU_STRING_VIEW("main"), |
| .bufferCount = std::size(vertexBufferLayouts), |
| .buffers = vertexBufferLayouts.data(), |
| }, |
| .primitive = |
| { |
| .topology = WGPUPrimitiveTopology_TriangleList, |
| .frontFace = static_cast<WGPUFrontFace>(RIVE_FRONT_FACE), |
| .cullMode = DrawTypeIsImageDraw(drawType) ? WGPUCullMode_None |
| : WGPUCullMode_Back, |
| }, |
| .multisample = |
| { |
| .count = 1, |
| .mask = 0xffffffff, |
| }, |
| .fragment = &fragmentState, |
| }; |
| |
| return wgpu::RenderPipeline::Acquire( |
| wgpuDeviceCreateRenderPipeline(m_device.Get(), &desc)); |
| } |
| |
| wgpu::RenderPassEncoder RenderContextWebGPUImpl::makePLSRenderPass( |
| wgpu::CommandEncoder encoder, |
| const RenderTargetWebGPU* renderTarget, |
| wgpu::LoadOp loadOp, |
| const wgpu::Color& clearColor) |
| { |
| WGPURenderPassColorAttachment plsAttachments[4] = { |
| { |
| // framebuffer |
| .view = renderTarget->m_targetTextureView.Get(), |
| .depthSlice = WGPU_DEPTH_SLICE_UNDEFINED, |
| .loadOp = static_cast<WGPULoadOp>(loadOp), |
| .storeOp = WGPUStoreOp_Store, |
| .clearValue = |
| {clearColor.r, clearColor.g, clearColor.b, clearColor.a}, |
| }, |
| { |
| // clip |
| .view = renderTarget->m_clipTextureView.Get(), |
| .depthSlice = WGPU_DEPTH_SLICE_UNDEFINED, |
| .loadOp = WGPULoadOp_Clear, |
| .storeOp = WGPUStoreOp_Discard, |
| .clearValue = {}, |
| }, |
| { |
| // scratchColor |
| .view = renderTarget->m_scratchColorTextureView.Get(), |
| .depthSlice = WGPU_DEPTH_SLICE_UNDEFINED, |
| .loadOp = WGPULoadOp_Clear, |
| .storeOp = WGPUStoreOp_Discard, |
| .clearValue = {}, |
| }, |
| { |
| // coverage |
| .view = renderTarget->m_coverageTextureView.Get(), |
| .depthSlice = WGPU_DEPTH_SLICE_UNDEFINED, |
| .loadOp = WGPULoadOp_Clear, |
| .storeOp = WGPUStoreOp_Discard, |
| .clearValue = {}, |
| }, |
| }; |
| static_assert(COLOR_PLANE_IDX == 0); |
| static_assert(CLIP_PLANE_IDX == 1); |
| static_assert(SCRATCH_COLOR_PLANE_IDX == 2); |
| static_assert(COVERAGE_PLANE_IDX == 3); |
| |
| WGPURenderPassDescriptor passDesc = { |
| .colorAttachmentCount = static_cast<size_t>( |
| #ifdef RIVE_WAGYU |
| m_capabilities.plsType == |
| PixelLocalStorageType::GL_EXT_shader_pixel_local_storage |
| ? 1 |
| : |
| #endif |
| 4), |
| .colorAttachments = plsAttachments, |
| }; |
| |
| #ifdef RIVE_WAGYU |
| WGPUColor targetClearColor = {clearColor.r, |
| clearColor.g, |
| clearColor.b, |
| clearColor.a}; |
| static WGPUColor blackClearColor = {}; |
| WGPUWagyuRenderPassInputAttachment inputAttachments[] = { |
| { |
| .view = renderTarget->m_targetTextureView.Get(), |
| .clearValue = &targetClearColor, |
| .loadOp = static_cast<WGPULoadOp>(loadOp), |
| .storeOp = WGPUStoreOp_Store, |
| }, |
| { |
| .view = renderTarget->m_clipTextureView.Get(), |
| .clearValue = &blackClearColor, |
| .loadOp = WGPULoadOp_Clear, |
| .storeOp = WGPUStoreOp_Discard, |
| |
| }, |
| { |
| .view = renderTarget->m_scratchColorTextureView.Get(), |
| .clearValue = &blackClearColor, |
| .loadOp = WGPULoadOp_Clear, |
| .storeOp = WGPUStoreOp_Discard, |
| |
| }, |
| { |
| .view = renderTarget->m_coverageTextureView.Get(), |
| .clearValue = &blackClearColor, |
| .loadOp = WGPULoadOp_Clear, |
| .storeOp = WGPUStoreOp_Discard, |
| }}; |
| static_assert(COLOR_PLANE_IDX == 0); |
| static_assert(CLIP_PLANE_IDX == 1); |
| static_assert(SCRATCH_COLOR_PLANE_IDX == 2); |
| static_assert(COVERAGE_PLANE_IDX == 3); |
| static_assert(PLS_PLANE_COUNT == std::size(inputAttachments)); |
| |
| WGPUWagyuRenderPassDescriptor wagyuRenderPassDescriptor = |
| WGPU_WAGYU_RENDER_PASS_DESCRIPTOR_INIT; |
| if (m_capabilities.plsType == |
| PixelLocalStorageType::VK_EXT_rasterization_order_attachment_access) |
| { |
| wagyuRenderPassDescriptor.inputAttachmentCount = |
| std::size(inputAttachments); |
| wagyuRenderPassDescriptor.inputAttachments = inputAttachments; |
| passDesc.nextInChain = &wagyuRenderPassDescriptor.chain; |
| } |
| else if (m_capabilities.plsType == |
| PixelLocalStorageType::GL_EXT_shader_pixel_local_storage) |
| { |
| wagyuRenderPassDescriptor.pixelLocalStorageEnabled = |
| WGPUOptionalBool_True; |
| passDesc.nextInChain = &wagyuRenderPassDescriptor.chain; |
| } |
| #endif |
| |
| return wgpu::RenderPassEncoder::Acquire( |
| wgpuCommandEncoderBeginRenderPass(encoder.Get(), &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 RenderContextWebGPUImpl::flush(const FlushDescriptor& desc) |
| { |
| auto* renderTarget = |
| static_cast<const RenderTargetWebGPU*>(desc.renderTarget); |
| |
| wgpu::CommandEncoder encoder; |
| { |
| auto wgpuEncoder = |
| static_cast<WGPUCommandEncoder>(desc.externalCommandBuffer); |
| assert(wgpuEncoder != nullptr); |
| #if (defined(RIVE_WEBGPU) && RIVE_WEBGPU > 1) || defined(RIVE_DAWN) |
| wgpuCommandEncoderAddRef(wgpuEncoder); |
| #else |
| wgpuCommandEncoderReference(wgpuEncoder); |
| #endif |
| encoder = wgpu::CommandEncoder::Acquire(wgpuEncoder); |
| } |
| |
| #ifdef RIVE_WAGYU |
| // If storage buffers are disabled, copy their contents to textures. |
| if (m_capabilities.polyfillVertexStorageBuffers) |
| { |
| if (desc.pathCount > 0) |
| { |
| update_webgpu_storage_texture<gpu::PathData>(pathBufferRing(), |
| desc.pathCount, |
| desc.firstPath, |
| encoder); |
| update_webgpu_storage_texture<gpu::PaintData>(paintBufferRing(), |
| desc.pathCount, |
| desc.firstPaint, |
| encoder); |
| update_webgpu_storage_texture<gpu::PaintAuxData>( |
| paintAuxBufferRing(), |
| desc.pathCount, |
| desc.firstPaintAux, |
| encoder); |
| } |
| if (desc.contourCount > 0) |
| { |
| update_webgpu_storage_texture<gpu::ContourData>(contourBufferRing(), |
| desc.contourCount, |
| desc.firstContour, |
| encoder); |
| } |
| } |
| #endif |
| |
| wgpu::BindGroupEntry perFlushBindingEntries[DRAW_BINDINGS_COUNT] = { |
| { |
| .binding = FLUSH_UNIFORM_BUFFER_IDX, |
| .buffer = webgpu_buffer(flushUniformBufferRing()), |
| .offset = desc.flushUniformDataOffsetInBytes, |
| }, |
| #ifdef RIVE_WAGYU |
| m_capabilities.polyfillVertexStorageBuffers |
| ? wgpu::BindGroupEntry{.binding = PATH_BUFFER_IDX, |
| .textureView = webgpu_storage_texture_view( |
| pathBufferRing())} |
| : |
| #endif |
| wgpu::BindGroupEntry{ |
| .binding = PATH_BUFFER_IDX, |
| .buffer = webgpu_buffer(pathBufferRing()), |
| .offset = desc.firstPath * sizeof(gpu::PathData), |
| }, |
| #ifdef RIVE_WAGYU |
| m_capabilities.polyfillVertexStorageBuffers ? |
| wgpu::BindGroupEntry{ |
| .binding = PAINT_BUFFER_IDX, |
| .textureView = webgpu_storage_texture_view(paintBufferRing()), |
| } : |
| #endif |
| wgpu::BindGroupEntry{ |
| .binding = PAINT_BUFFER_IDX, |
| .buffer = webgpu_buffer(paintBufferRing()), |
| .offset = desc.firstPaint * sizeof(gpu::PaintData), |
| }, |
| #ifdef RIVE_WAGYU |
| m_capabilities.polyfillVertexStorageBuffers ? |
| wgpu::BindGroupEntry{ |
| .binding = PAINT_AUX_BUFFER_IDX, |
| .textureView = webgpu_storage_texture_view(paintAuxBufferRing()), |
| } : |
| #endif |
| wgpu::BindGroupEntry{ |
| .binding = PAINT_AUX_BUFFER_IDX, |
| .buffer = webgpu_buffer(paintAuxBufferRing()), |
| .offset = desc.firstPaintAux * sizeof(gpu::PaintAuxData), |
| }, |
| #ifdef RIVE_WAGYU |
| m_capabilities.polyfillVertexStorageBuffers ? |
| wgpu::BindGroupEntry{ |
| .binding = CONTOUR_BUFFER_IDX, |
| .textureView = webgpu_storage_texture_view(contourBufferRing()), |
| } : |
| #endif |
| wgpu::BindGroupEntry{ |
| .binding = CONTOUR_BUFFER_IDX, |
| .buffer = webgpu_buffer(contourBufferRing()), |
| .offset = desc.firstContour * sizeof(gpu::ContourData), |
| }, |
| { |
| .binding = FEATHER_TEXTURE_IDX, |
| .textureView = m_featherTextureView, |
| }, |
| { |
| .binding = TESS_VERTEX_TEXTURE_IDX, |
| .textureView = m_tessVertexTextureView, |
| }, |
| { |
| .binding = ATLAS_TEXTURE_IDX, |
| .textureView = m_atlasTextureView, |
| }, |
| { |
| .binding = GRAD_TEXTURE_IDX, |
| .textureView = m_gradientTextureView, |
| }, |
| { |
| .binding = IMAGE_DRAW_UNIFORM_BUFFER_IDX, |
| .buffer = webgpu_buffer(imageDrawUniformBufferRing()), |
| .size = sizeof(gpu::ImageDrawUniforms), |
| }, |
| }; |
| |
| // Render the complex color ramps to the gradient texture. |
| if (desc.gradDataHeight > 0) |
| { |
| wgpu::BindGroupDescriptor colorRampBindGroupDesc = { |
| .layout = m_colorRampPipeline->bindGroupLayout(), |
| .entryCount = COLOR_RAMP_BINDINGS_COUNT, |
| .entries = perFlushBindingEntries, |
| }; |
| |
| 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, |
| 0.f, |
| gpu::kGradTextureWidth, |
| static_cast<float>(desc.gradDataHeight), |
| 0.0, |
| 1.0); |
| gradPass.SetPipeline(m_colorRampPipeline->renderPipeline()); |
| gradPass.SetVertexBuffer(0, |
| webgpu_buffer(gradSpanBufferRing()), |
| desc.firstGradSpan * |
| sizeof(gpu::GradientSpan)); |
| gradPass.SetBindGroup( |
| 0, |
| m_device.CreateBindGroup(&colorRampBindGroupDesc)); |
| gradPass.Draw(gpu::GRAD_SPAN_TRI_STRIP_VERTEX_COUNT, |
| desc.gradSpanCount, |
| 0, |
| 0); |
| gradPass.End(); |
| } |
| |
| // Tessellate all curves into vertices in the tessellation texture. |
| if (desc.tessVertexSpanCount > 0) |
| { |
| wgpu::BindGroupDescriptor tessBindGroupDesc = { |
| .layout = m_tessellatePipeline->perFlushBindingsLayout(), |
| .entryCount = TESS_BINDINGS_COUNT, |
| .entries = perFlushBindingEntries, |
| }; |
| |
| 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, |
| gpu::kTessTextureWidth, |
| static_cast<float>(desc.tessDataHeight), |
| 0.0, |
| 1.0); |
| tessPass.SetPipeline(m_tessellatePipeline->renderPipeline()); |
| tessPass.SetVertexBuffer(0, |
| webgpu_buffer(tessSpanBufferRing()), |
| desc.firstTessVertexSpan * |
| sizeof(gpu::TessVertexSpan)); |
| tessPass.SetIndexBuffer(m_tessSpanIndexBuffer, |
| wgpu::IndexFormat::Uint16); |
| tessPass.SetBindGroup(PER_FLUSH_BINDINGS_SET, |
| m_device.CreateBindGroup(&tessBindGroupDesc)); |
| tessPass.SetBindGroup(IMMUTABLE_SAMPLER_BINDINGS_SET, |
| m_samplerBindings); |
| tessPass.DrawIndexed(std::size(gpu::kTessSpanIndices), |
| desc.tessVertexSpanCount, |
| 0, |
| 0, |
| 0); |
| tessPass.End(); |
| } |
| |
| // Render the atlas if we have any offscreen feathers. |
| if ((desc.atlasFillBatchCount | desc.atlasStrokeBatchCount) != 0) |
| { |
| wgpu::BindGroupDescriptor atlasBindGroupDesc = { |
| .layout = m_atlasPipeline->perFlushBindingsLayout(), |
| .entryCount = ATLAS_BINDINGS_COUNT, |
| .entries = perFlushBindingEntries, |
| }; |
| |
| wgpu::RenderPassColorAttachment attachment{ |
| .view = m_atlasTextureView, |
| .loadOp = wgpu::LoadOp::Clear, |
| .storeOp = wgpu::StoreOp::Store, |
| .clearValue = {0, 0, 0, 0}, |
| }; |
| |
| wgpu::RenderPassDescriptor atlasPassDesc = { |
| .colorAttachmentCount = 1, |
| .colorAttachments = &attachment, |
| }; |
| |
| wgpu::RenderPassEncoder atlasPass = |
| encoder.BeginRenderPass(&atlasPassDesc); |
| atlasPass.SetViewport(0.f, |
| 0.f, |
| desc.atlasContentWidth, |
| desc.atlasContentHeight, |
| 0.0, |
| 1.0); |
| atlasPass.SetVertexBuffer(0, m_pathPatchVertexBuffer); |
| atlasPass.SetIndexBuffer(m_pathPatchIndexBuffer, |
| wgpu::IndexFormat::Uint16); |
| atlasPass.SetBindGroup(PER_FLUSH_BINDINGS_SET, |
| m_device.CreateBindGroup(&atlasBindGroupDesc)); |
| atlasPass.SetBindGroup(IMMUTABLE_SAMPLER_BINDINGS_SET, |
| m_samplerBindings); |
| |
| if (desc.atlasFillBatchCount != 0) |
| { |
| atlasPass.SetPipeline(m_atlasPipeline->fillPipeline()); |
| for (size_t i = 0; i < desc.atlasFillBatchCount; ++i) |
| { |
| const gpu::AtlasDrawBatch& fillBatch = desc.atlasFillBatches[i]; |
| atlasPass.SetScissorRect(fillBatch.scissor.left, |
| fillBatch.scissor.top, |
| fillBatch.scissor.width(), |
| fillBatch.scissor.height()); |
| atlasPass.DrawIndexed(gpu::kMidpointFanCenterAAPatchIndexCount, |
| fillBatch.patchCount, |
| gpu::kMidpointFanCenterAAPatchBaseIndex, |
| 0, |
| fillBatch.basePatch); |
| } |
| } |
| |
| if (desc.atlasStrokeBatchCount != 0) |
| { |
| atlasPass.SetPipeline(m_atlasPipeline->strokePipeline()); |
| for (size_t i = 0; i < desc.atlasStrokeBatchCount; ++i) |
| { |
| const gpu::AtlasDrawBatch& strokeBatch = |
| desc.atlasStrokeBatches[i]; |
| atlasPass.SetScissorRect(strokeBatch.scissor.left, |
| strokeBatch.scissor.top, |
| strokeBatch.scissor.width(), |
| strokeBatch.scissor.height()); |
| atlasPass.DrawIndexed(gpu::kMidpointFanPatchBorderIndexCount, |
| strokeBatch.patchCount, |
| gpu::kMidpointFanPatchBaseIndex, |
| 0, |
| strokeBatch.basePatch); |
| } |
| } |
| |
| atlasPass.End(); |
| } |
| |
| wgpu::LoadOp loadOp; |
| wgpu::Color clearColor; |
| if (desc.colorLoadAction == LoadAction::clear) |
| { |
| loadOp = wgpu::LoadOp::Clear; |
| float cc[4]; |
| UnpackColorToRGBA32FPremul(desc.colorClearValue, cc); |
| clearColor = {cc[0], cc[1], cc[2], cc[3]}; |
| } |
| else |
| { |
| loadOp = wgpu::LoadOp::Load; |
| } |
| |
| wgpu::RenderPassEncoder drawPass = |
| makePLSRenderPass(encoder, renderTarget, loadOp, clearColor); |
| drawPass.SetViewport(0.f, |
| 0.f, |
| renderTarget->width(), |
| renderTarget->height(), |
| 0.0, |
| 1.0); |
| |
| #ifdef RIVE_WAGYU |
| bool needsInputAttachmentBindings = |
| m_capabilities.plsType == |
| PixelLocalStorageType::VK_EXT_rasterization_order_attachment_access; |
| if (needsInputAttachmentBindings) |
| { |
| wgpu::BindGroupEntry plsTextureBindingEntries[] = { |
| { |
| .binding = COLOR_PLANE_IDX, |
| .textureView = renderTarget->m_targetTextureView, |
| }, |
| { |
| .binding = CLIP_PLANE_IDX, |
| .textureView = renderTarget->m_clipTextureView, |
| }, |
| { |
| .binding = SCRATCH_COLOR_PLANE_IDX, |
| .textureView = renderTarget->m_scratchColorTextureView, |
| }, |
| { |
| .binding = COVERAGE_PLANE_IDX, |
| .textureView = renderTarget->m_coverageTextureView, |
| }, |
| }; |
| |
| 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); |
| } |
| #endif |
| |
| #ifdef RIVE_WAGYU |
| if (m_capabilities.plsType == |
| PixelLocalStorageType::GL_EXT_shader_pixel_local_storage) |
| { |
| // Draw the load action for EXT_shader_pixel_local_storage. |
| std::array<float, 4> clearColor; |
| LoadStoreActionsEXT loadActions = |
| gpu::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); |
| } |
| #endif |
| |
| drawPass.SetBindGroup(IMMUTABLE_SAMPLER_BINDINGS_SET, m_samplerBindings); |
| |
| wgpu::BindGroupDescriptor perFlushBindGroupDesc = { |
| .layout = m_drawBindGroupLayouts[PER_FLUSH_BINDINGS_SET], |
| .entryCount = std::size(perFlushBindingEntries), |
| .entries = perFlushBindingEntries, |
| }; |
| |
| wgpu::BindGroup perFlushBindings = |
| m_device.CreateBindGroup(&perFlushBindGroupDesc); |
| |
| // Execute the DrawList. |
| bool needsNewBindings = true; |
| for (const DrawBatch& batch : *desc.drawList) |
| { |
| DrawType drawType = batch.drawType; |
| |
| // Bind the appropriate image texture, if any. |
| wgpu::TextureView imageTextureView = m_nullImagePaintTextureView; |
| uint8_t imageSamplerKey = ImageSampler::LINEAR_CLAMP_SAMPLER_KEY; |
| if (auto imageTexture = |
| static_cast<const TextureWebGPUImpl*>(batch.imageTexture)) |
| { |
| imageTextureView = imageTexture->textureView(); |
| imageSamplerKey = batch.imageSampler.asKey(); |
| needsNewBindings = true; |
| } |
| |
| if (needsNewBindings || |
| // Image draws always re-bind because they update the dynamic offset |
| // to their uniforms. |
| drawType == DrawType::imageRect || drawType == DrawType::imageMesh) |
| { |
| drawPass.SetBindGroup(PER_FLUSH_BINDINGS_SET, |
| perFlushBindings, |
| 1, |
| &batch.imageDrawDataOffset); |
| |
| wgpu::BindGroupEntry perDrawBindingEntries[] = { |
| { |
| .binding = IMAGE_TEXTURE_IDX, |
| .textureView = imageTextureView, |
| }, |
| { |
| .binding = IMAGE_SAMPLER_IDX, |
| .sampler = m_imageSamplers[imageSamplerKey], |
| }, |
| }; |
| |
| wgpu::BindGroupDescriptor perDrawBindGroupDesc = { |
| .layout = m_drawBindGroupLayouts[PER_DRAW_BINDINGS_SET], |
| .entryCount = std::size(perDrawBindingEntries), |
| .entries = perDrawBindingEntries, |
| }; |
| |
| wgpu::BindGroup perDrawBindings = |
| m_device.CreateBindGroup(&perDrawBindGroupDesc); |
| drawPass.SetBindGroup(PER_DRAW_BINDINGS_SET, |
| perDrawBindings, |
| 0, |
| nullptr); |
| } |
| |
| // Setup the pipeline for this specific drawType and shaderFeatures. |
| bool targetIsGLFBO0 = false; |
| #ifdef RIVE_WAGYU |
| if (m_capabilities.backendType == wgpu::BackendType::OpenGLES) |
| { |
| targetIsGLFBO0 = wgpuWagyuTextureIsSwapchain( |
| renderTarget->m_targetTexture.Get()); |
| } |
| #endif |
| uint32_t webgpuShaderKey = |
| (gpu::ShaderUniqueKey(drawType, |
| batch.shaderFeatures, |
| gpu::InterlockMode::rasterOrdering, |
| gpu::ShaderMiscFlags::none) |
| << 1) | |
| static_cast<uint32_t>(targetIsGLFBO0); |
| const DrawPipeline& drawPipeline = |
| m_drawPipelines |
| .try_emplace(webgpuShaderKey, |
| this, |
| drawType, |
| batch.shaderFeatures, |
| targetIsGLFBO0) |
| .first->second; |
| drawPass.SetPipeline( |
| drawPipeline.renderPipeline(renderTarget->framebufferFormat())); |
| |
| switch (drawType) |
| { |
| case DrawType::midpointFanPatches: |
| case DrawType::midpointFanCenterAAPatches: |
| 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(gpu::PatchIndexCount(drawType), |
| batch.elementCount, |
| gpu::PatchBaseIndex(drawType), |
| 0, |
| batch.baseElement); |
| break; |
| } |
| case DrawType::interiorTriangulation: |
| case DrawType::atlasBlit: |
| { |
| 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::msaaStrokes: |
| case DrawType::msaaMidpointFanBorrowedCoverage: |
| case DrawType::msaaMidpointFans: |
| case DrawType::msaaMidpointFanStencilReset: |
| case DrawType::msaaMidpointFanPathsStencil: |
| case DrawType::msaaMidpointFanPathsCover: |
| case DrawType::msaaOuterCubics: |
| case DrawType::msaaStencilClipReset: |
| case DrawType::renderPassInitialize: |
| case DrawType::renderPassResolve: |
| RIVE_UNREACHABLE(); |
| } |
| } |
| |
| #ifdef RIVE_WAGYU |
| if (m_capabilities.plsType == |
| PixelLocalStorageType::GL_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); |
| } |
| #endif |
| |
| drawPass.End(); |
| } |
| |
| std::unique_ptr<RenderContext> RenderContextWebGPUImpl::MakeContext( |
| wgpu::Adapter adapter, |
| wgpu::Device device, |
| wgpu::Queue queue, |
| const ContextOptions& contextOptions) |
| { |
| auto impl = std::unique_ptr<RenderContextWebGPUImpl>( |
| new RenderContextWebGPUImpl(adapter, device, queue, contextOptions)); |
| impl->initGPUObjects(); |
| return std::make_unique<RenderContext>(std::move(impl)); |
| } |
| } // namespace rive::gpu |