vello_gpu: Move wgpu pipeline creation into the pass modules
diff --git a/vello_gpu/src/render/wgpu/mod.rs b/vello_gpu/src/render/wgpu/mod.rs index 21eb8e2..70719ed 100644 --- a/vello_gpu/src/render/wgpu/mod.rs +++ b/vello_gpu/src/render/wgpu/mod.rs
@@ -599,7 +599,7 @@ u32::from(height), ); // Use atlas clear pipeline to render transparent pixels - render_pass.set_pipeline(&self.programs.atlas_clear_pipeline); + render_pass.set_pipeline(&self.programs.clear.atlas); // Draw fullscreen quad render_pass.draw(0..4, 0..1); }
diff --git a/vello_gpu/src/render/wgpu/passes/blend.rs b/vello_gpu/src/render/wgpu/passes/blend.rs index 0705bab..e2597f5 100644 --- a/vello_gpu/src/render/wgpu/passes/blend.rs +++ b/vello_gpu/src/render/wgpu/passes/blend.rs
@@ -4,6 +4,7 @@ //! The blend pass. use crate::render::wgpu::passes::copy::encode_copy_pass; +use crate::render::wgpu::passes::create_texture_op_pipeline; use crate::render::wgpu::passes::{Programs, RendererContext}; use crate::util::RangedSlice; use crate::{ @@ -14,9 +15,96 @@ use hashbrown::hash_map::Entry; use wgpu::{ BindGroup, BindGroupLayout, Device, RenderPassColorAttachment, RenderPassDescriptor, - TextureView, TextureViewDescriptor, util::DeviceExt, + RenderPipeline, TextureView, TextureViewDescriptor, util::DeviceExt, }; +/// Bind group layout and pipeline of the blend pass. +#[derive(Debug)] +pub(crate) struct BlendPipeline { + /// Blend bind group layout. + pub(crate) bind_group_layout: BindGroupLayout, + /// Blend pipeline. + pub(crate) pipeline: RenderPipeline, +} + +impl BlendPipeline { + pub(crate) fn new(device: &Device, pipeline_cache: Option<&wgpu::PipelineCache>) -> Self { + let blend_layer_bind_group_layout = + device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: Some("Blend Layer Textures Bind Group Layout"), + entries: &[ + wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Texture { + sample_type: wgpu::TextureSampleType::Float { filterable: false }, + view_dimension: wgpu::TextureViewDimension::D2, + multisampled: false, + }, + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 1, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Texture { + sample_type: wgpu::TextureSampleType::Float { filterable: false }, + view_dimension: wgpu::TextureViewDimension::D2, + multisampled: false, + }, + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 2, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Texture { + sample_type: wgpu::TextureSampleType::Uint, + view_dimension: wgpu::TextureViewDimension::D2, + multisampled: false, + }, + count: None, + }, + ], + }); + let blend_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: Some("Blend Shader"), + source: wgpu::ShaderSource::Wgsl(vello_gpu_shaders::wgsl::BLEND.into()), + }); + let blend_pipeline_layout = + device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("Blend Pipeline Layout"), + bind_group_layouts: &[Some(&blend_layer_bind_group_layout)], + immediate_size: 0, + }); + let blend_vertex_state = wgpu::VertexBufferLayout { + array_stride: size_of::<GpuBlendInstance>() as u64, + step_mode: wgpu::VertexStepMode::Instance, + attributes: &wgpu::vertex_attr_array![ + 0 => Uint32, + 1 => Uint32, + 2 => Uint32, + 3 => Uint32, + 4 => Uint32, + 5 => Uint32, + 6 => Uint32, + 7 => Uint32, + ], + }; + let blend_pipeline = create_texture_op_pipeline( + device, + "Blend Pipeline", + &blend_shader, + &blend_pipeline_layout, + &blend_vertex_state, + pipeline_cache, + ); + + Self { + bind_group_layout: blend_layer_bind_group_layout, + pipeline: blend_pipeline, + } + } +} + impl Programs { fn create_blend_layer_bind_group( device: &Device, @@ -62,7 +150,7 @@ .create_view(&TextureViewDescriptor::default()); entry.insert(Programs::create_blend_layer_bind_group( self.device, - &self.programs.blend_layer_bind_group_layout, + &self.programs.blend.bind_group_layout, resources.layer_views([ bindings.layer_id(TextureParity::Even), bindings.layer_id(TextureParity::Odd), @@ -116,7 +204,7 @@ timestamp_writes: None, multiview_mask: None, }); - render_pass.set_pipeline(&self.programs.blend_pipeline); + render_pass.set_pipeline(&self.programs.blend.pipeline); render_pass.set_bind_group(0, &*blend_layer_bind_group, &[]); render_pass.set_vertex_buffer(0, instance_buffer.slice(..)); render_pass.draw(0..4, 0..instance_count); @@ -133,7 +221,7 @@ encode_copy_pass( self.device, self.encoder, - &self.programs.copy_pipeline, + &self.programs.copy.pipeline, &self.scratch_buffers.copy_instances, &self.programs.resources.scratch_copy_bind_group, self.programs.resources.layer_view(bindings.target()),
diff --git a/vello_gpu/src/render/wgpu/passes/clear.rs b/vello_gpu/src/render/wgpu/passes/clear.rs index f3d34e2..0999fa3 100644 --- a/vello_gpu/src/render/wgpu/passes/clear.rs +++ b/vello_gpu/src/render/wgpu/passes/clear.rs
@@ -3,14 +3,136 @@ //! The clear pass. +use crate::render::wgpu::RenderTargetConfig; use crate::render::wgpu::passes::RendererContext; use crate::{ClearSettings, render::common::GpuClearInstance, target::DrawPassTarget}; use vello_common::color::{AlphaColor, Srgb}; use vello_common::geometry::RectU16; use wgpu::{ - CommandEncoder, RenderPassColorAttachment, RenderPassDescriptor, TextureView, util::DeviceExt, + ColorTargetState, ColorWrites, CommandEncoder, Device, PipelineCompilationOptions, + RenderPassColorAttachment, RenderPassDescriptor, RenderPipeline, TextureView, util::DeviceExt, }; +/// Pipelines for clearing rectangles of layer textures, the root target and atlas regions. +#[derive(Debug)] +pub(crate) struct ClearPipelines { + /// Layer-clear pipeline. + pub(crate) layer: RenderPipeline, + /// User-target rectangle-clear pipeline. + pub(crate) root: RenderPipeline, + /// Pipeline for clearing atlas regions. + pub(crate) atlas: RenderPipeline, +} + +impl ClearPipelines { + pub(crate) fn new( + device: &Device, + render_target_config: &RenderTargetConfig, + pipeline_cache: Option<&wgpu::PipelineCache>, + ) -> Self { + let clear_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: Some("Clear Shader"), + source: wgpu::ShaderSource::Wgsl(vello_gpu_shaders::wgsl::CLEAR.into()), + }); + + let clear_pipeline_layout = + device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("Clear Pipeline Layout"), + bind_group_layouts: &[], + immediate_size: 0, + }); + + let clear_vertex_state = wgpu::VertexBufferLayout { + array_stride: size_of::<GpuClearInstance>() as u64, + step_mode: wgpu::VertexStepMode::Instance, + attributes: &wgpu::vertex_attr_array![ + 0 => Uint32x2, + 1 => Uint32x2, + 2 => Uint32x2, + 3 => Uint32, + ], + }; + let create_clear_pipeline = |label, format| { + device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some(label), + layout: Some(&clear_pipeline_layout), + vertex: wgpu::VertexState { + module: &clear_shader, + entry_point: Some("vs_main"), + buffers: &[Some(clear_vertex_state.clone())], + compilation_options: PipelineCompilationOptions::default(), + }, + fragment: Some(wgpu::FragmentState { + module: &clear_shader, + entry_point: Some("fs_main"), + targets: &[Some(ColorTargetState { + format, + blend: None, + write_mask: ColorWrites::ALL, + })], + compilation_options: PipelineCompilationOptions::default(), + }), + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleStrip, + ..Default::default() + }, + depth_stencil: None, + multisample: wgpu::MultisampleState::default(), + multiview_mask: None, + cache: pipeline_cache, + }) + }; + let clear_pipeline = + create_clear_pipeline("Clear Pipeline", wgpu::TextureFormat::Rgba8Unorm); + let root_clear_pipeline = + create_clear_pipeline("Root Clear Pipeline", render_target_config.format); + + // Create atlas clear pipeline + let atlas_clear_pipeline_layout = + device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("Atlas Clear Pipeline Layout"), + bind_group_layouts: &[], + immediate_size: 0, + }); + // TODO: Change the atlas clear pipeline to use the existing layer clear mechanism. + let atlas_clear_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("Atlas Clear Pipeline"), + layout: Some(&atlas_clear_pipeline_layout), + vertex: wgpu::VertexState { + module: &clear_shader, + // Use a different vertex shader entry point + entry_point: Some("vs_main_fullscreen"), + buffers: &[], + compilation_options: PipelineCompilationOptions::default(), + }, + fragment: Some(wgpu::FragmentState { + module: &clear_shader, + entry_point: Some("fs_transparent"), + targets: &[Some(ColorTargetState { + format: wgpu::TextureFormat::Rgba8Unorm, + blend: None, + write_mask: ColorWrites::ALL, + })], + compilation_options: PipelineCompilationOptions::default(), + }), + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleStrip, + ..Default::default() + }, + depth_stencil: None, + multisample: wgpu::MultisampleState::default(), + multiview_mask: None, + cache: pipeline_cache, + }); + + Self { + layer: clear_pipeline, + root: root_clear_pipeline, + atlas: atlas_clear_pipeline, + } + } +} + impl RendererContext<'_> { pub(crate) fn clear_full_target( encoder: &mut CommandEncoder, @@ -57,13 +179,13 @@ self.programs.render_size.width, self.programs.render_size.height, ], - &self.programs.root_clear_pipeline, + &self.programs.clear.root, ), DrawPassTarget::Layer(_) => { let texture_size = self.texture_size(); ( [texture_size.width(), texture_size.height()], - &self.programs.clear_pipeline, + &self.programs.clear.layer, ) } };
diff --git a/vello_gpu/src/render/wgpu/passes/copy.rs b/vello_gpu/src/render/wgpu/passes/copy.rs index c81f572..58c6dac 100644 --- a/vello_gpu/src/render/wgpu/passes/copy.rs +++ b/vello_gpu/src/render/wgpu/passes/copy.rs
@@ -5,11 +5,72 @@ use crate::copy::GpuCopyInstance; use crate::render::wgpu::passes::Programs; +use crate::render::wgpu::passes::create_texture_op_pipeline; use wgpu::{ BindGroup, BindGroupLayout, CommandEncoder, Device, RenderPassColorAttachment, RenderPassDescriptor, RenderPipeline, TextureView, util::DeviceExt, }; +/// Bind group layout and pipeline of the copy pass. +#[derive(Debug)] +pub(crate) struct CopyPipeline { + /// Copy bind group layout. + pub(crate) bind_group_layout: BindGroupLayout, + /// Copy pipeline. + pub(crate) pipeline: RenderPipeline, +} + +impl CopyPipeline { + pub(crate) fn new(device: &Device, pipeline_cache: Option<&wgpu::PipelineCache>) -> Self { + let copy_bind_group_layout = + device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: Some("Copy Bind Group Layout"), + entries: &[wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Texture { + sample_type: wgpu::TextureSampleType::Float { filterable: false }, + view_dimension: wgpu::TextureViewDimension::D2, + multisampled: false, + }, + count: None, + }], + }); + let copy_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: Some("Copy Shader"), + source: wgpu::ShaderSource::Wgsl(vello_gpu_shaders::wgsl::COPY.into()), + }); + let copy_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("Copy Pipeline Layout"), + bind_group_layouts: &[Some(©_bind_group_layout)], + immediate_size: 0, + }); + let copy_vertex_state = wgpu::VertexBufferLayout { + array_stride: size_of::<GpuCopyInstance>() as u64, + step_mode: wgpu::VertexStepMode::Instance, + attributes: &wgpu::vertex_attr_array![ + 0 => Uint32, + 1 => Uint32, + 2 => Uint32, + 3 => Uint32, + ], + }; + let copy_pipeline = create_texture_op_pipeline( + device, + "Copy Pipeline", + ©_shader, + ©_pipeline_layout, + ©_vertex_state, + pipeline_cache, + ); + + Self { + bind_group_layout: copy_bind_group_layout, + pipeline: copy_pipeline, + } + } +} + impl Programs { pub(crate) fn create_copy_bind_group( device: &Device,
diff --git a/vello_gpu/src/render/wgpu/passes/filter.rs b/vello_gpu/src/render/wgpu/passes/filter.rs index a8f04e6..dbfe112 100644 --- a/vello_gpu/src/render/wgpu/passes/filter.rs +++ b/vello_gpu/src/render/wgpu/passes/filter.rs
@@ -12,11 +12,143 @@ }; use core::fmt::Debug; use wgpu::{ - BindGroup, BindGroupLayout, CommandEncoder, Device, Extent3d, RenderPassColorAttachment, - RenderPassDescriptor, RenderPipeline, Sampler, Texture, TextureView, TextureViewDescriptor, - util::DeviceExt, + BindGroup, BindGroupLayout, ColorTargetState, ColorWrites, CommandEncoder, Device, Extent3d, + PipelineCompilationOptions, RenderPassColorAttachment, RenderPassDescriptor, RenderPipeline, + Sampler, Texture, TextureView, TextureViewDescriptor, util::DeviceExt, }; +/// Bind group layouts, sampler and pipeline of the filter pass. +#[derive(Debug)] +pub(crate) struct FilterPipeline { + /// Bind group layout for filter data texture. + pub(crate) bind_group_layout: BindGroupLayout, + /// Filter input layouts. + pub(crate) input_bind_group_layouts: [BindGroupLayout; 2], + /// Filter sampler. + pub(crate) sampler: Sampler, + /// Pipeline for applying filter effects. + pub(crate) pipeline: RenderPipeline, +} + +impl FilterPipeline { + pub(crate) fn new(device: &Device, pipeline_cache: Option<&wgpu::PipelineCache>) -> Self { + let filter_texture_entry = wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Texture { + sample_type: wgpu::TextureSampleType::Float { filterable: true }, + view_dimension: wgpu::TextureViewDimension::D2, + multisampled: false, + }, + count: None, + }; + let filter_bind_group_layout = + device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: Some("Filter Bind Group Layout"), + entries: &[wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Texture { + sample_type: wgpu::TextureSampleType::Uint, + view_dimension: wgpu::TextureViewDimension::D2, + multisampled: false, + }, + count: None, + }], + }); + let filter_input_bind_group_layouts = [ + // Input texture and linear sampler. + device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: Some("Filter Input Bind Group Layout"), + entries: &[ + filter_texture_entry, + wgpu::BindGroupLayoutEntry { + binding: 1, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), + count: None, + }, + ], + }), + // The original layer texture. + device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: Some("Filter Original Texture Bind Group Layout"), + entries: &[filter_texture_entry], + }), + ]; + + let filter_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: Some("Filter Shader"), + source: wgpu::ShaderSource::Wgsl(vello_gpu_shaders::wgsl::FILTER.into()), + }); + let filter_pipeline_layout = + device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("Filter Pipeline Layout"), + bind_group_layouts: &[ + Some(&filter_bind_group_layout), + Some(&filter_input_bind_group_layouts[0]), + Some(&filter_input_bind_group_layouts[1]), + ], + immediate_size: 0, + }); + let filter_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("Filter Pipeline"), + layout: Some(&filter_pipeline_layout), + vertex: wgpu::VertexState { + module: &filter_shader, + entry_point: Some("vs_main"), + buffers: &[Some(wgpu::VertexBufferLayout { + array_stride: size_of::<FilterInstanceData>() as u64, + step_mode: wgpu::VertexStepMode::Instance, + attributes: &wgpu::vertex_attr_array![ + 0 => Uint32, + 1 => Uint32, + 2 => Uint32, + 3 => Uint32, + 4 => Uint32, + 5 => Uint32, + 6 => Uint32, + 7 => Uint32, + 8 => Uint32, + ], + })], + compilation_options: PipelineCompilationOptions::default(), + }, + fragment: Some(wgpu::FragmentState { + module: &filter_shader, + entry_point: Some("fs_main"), + targets: &[Some(ColorTargetState { + format: wgpu::TextureFormat::Rgba8Unorm, + blend: None, + write_mask: ColorWrites::ALL, + })], + compilation_options: PipelineCompilationOptions::default(), + }), + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleStrip, + ..Default::default() + }, + depth_stencil: None, + multisample: wgpu::MultisampleState::default(), + cache: pipeline_cache, + multiview_mask: None, + }); + let filter_sampler = device.create_sampler(&wgpu::SamplerDescriptor { + label: Some("Filter Linear Sampler"), + mag_filter: wgpu::FilterMode::Linear, + min_filter: wgpu::FilterMode::Linear, + ..Default::default() + }); + + Self { + bind_group_layout: filter_bind_group_layout, + input_bind_group_layouts: filter_input_bind_group_layouts, + sampler: filter_sampler, + pipeline: filter_pipeline, + } + } +} + #[derive(Debug)] pub(crate) struct FilterPairBindGroups { inputs: [BindGroup; 2], @@ -85,7 +217,7 @@ self.resources.filter_data_texture = filter_texture; self.resources.filter_base_bind_group = Self::create_filter_base_bind_group( device, - &self.filter_bind_group_layout, + &self.filter.bind_group_layout, &self .resources .filter_data_texture @@ -134,14 +266,14 @@ let inputs = layer_views.map(|view| { create_filter_input_bind_group( self.device, - &self.programs.filter_input_bind_group_layouts[0], - &self.programs.filter_sampler, + &self.programs.filter.input_bind_group_layouts[0], + &self.programs.filter.sampler, view, ) }); let copy_source = Programs::create_copy_bind_group( self.device, - &self.programs.copy_bind_group_layout, + &self.programs.copy.bind_group_layout, resources.layer_view(target), ); self.programs.filter_pair_bind_groups.insert( @@ -158,7 +290,7 @@ encode_copy_pass( self.device, self.encoder, - &self.programs.copy_pipeline, + &self.programs.copy.pipeline, copy_pass, &filter_pair_bind_groups.copy_source, resources.scratch_view(), @@ -173,7 +305,7 @@ self.device, self.encoder, resources, - &self.programs.filter_pipeline, + &self.programs.filter.pipeline, instances, &filter_pair_bind_groups.inputs[input.texture_parity.get_parity()], &resources.filter_original_bind_group,
diff --git a/vello_gpu/src/render/wgpu/passes/mod.rs b/vello_gpu/src/render/wgpu/passes/mod.rs index 3c12fc5..08e1e55 100644 --- a/vello_gpu/src/render/wgpu/passes/mod.rs +++ b/vello_gpu/src/render/wgpu/passes/mod.rs
@@ -12,20 +12,21 @@ use crate::draw::{EXTERNAL_TEXTURE_SLOT_COUNT, ExternalTextureBindings, ExternalTextureRun}; use crate::render::wgpu::RenderTargetConfig; use crate::render::wgpu::bindings::TextureBindings; -use crate::render::wgpu::passes::clear::clear_color; +use crate::render::wgpu::passes::blend::BlendPipeline; +use crate::render::wgpu::passes::clear::{ClearPipelines, clear_color}; +use crate::render::wgpu::passes::copy::CopyPipeline; use crate::render::wgpu::passes::filter::{ - FilterPairBindGroups, create_filter_original_texture_bind_group, + FilterPairBindGroups, FilterPipeline, create_filter_original_texture_bind_group, }; -use crate::render::wgpu::passes::strip::StripTargetKind; +use crate::render::wgpu::passes::strip::{StripPipelines, StripTargetKind}; use crate::render::wgpu::resources::{GpuResources, StripBufferArena, UploadStagingBelt}; use crate::util::RangedSlice; use crate::{ ClearSettings, GpuStrip, LayersConfig, RenderSize, TargetInit, - blend::{BlendStrip, GpuBlendInstance}, - copy::GpuCopyInstance, - filter::{FilterInstanceData, FilterPassPlan}, + blend::BlendStrip, + filter::FilterPassPlan, paint::ExternalSampler, - render::common::{GpuClearInstance, ScratchBuffers}, + render::common::ScratchBuffers, schedule::{Backend, round::BlendOp}, target::{ BlendPassBindings, DrawPassBindings, DrawPassTarget, FilterPassBindings, LayerTextureId, @@ -41,43 +42,31 @@ use vello_common::image_cache::ImageCache; use vello_common::multi_atlas::AtlasConfig; use wgpu::{ - BindGroup, BindGroupLayout, BlendState, ColorTargetState, ColorWrites, CommandEncoder, Device, + BindGroup, BindGroupLayout, ColorTargetState, ColorWrites, CommandEncoder, Device, PipelineCompilationOptions, RenderPipeline, Sampler, TextureView, TextureViewDescriptor, }; /// Defines the GPU resources and pipelines for rendering. #[derive(Debug)] pub(crate) struct Programs { - /// Intermediate strip pipeline. - intermediate_strip_pipeline: RenderPipeline, - /// Root alpha-strip pipeline. - alpha_strip_pipeline: RenderPipeline, - /// Root alpha-strip pipeline with depth testing. - depth_alpha_strip_pipeline: RenderPipeline, - /// Root opaque-strip pipeline. - opaque_strip_pipeline: RenderPipeline, + /// Bind group layouts and pipelines of the strip pass. + pub(crate) strip: StripPipelines, + /// Clear pipelines. + pub(crate) clear: ClearPipelines, + /// Bind group layouts, sampler and pipeline of the filter pass. + pub(crate) filter: FilterPipeline, + /// Bind group layout and pipeline of the blend pass. + pub(crate) blend: BlendPipeline, + /// Bind group layout and pipeline of the copy pass. + pub(crate) copy: CopyPipeline, /// Whether the depth buffer has been cleared this frame. pub(crate) depth_cleared_this_frame: bool, - /// Bind group layout for strip draws - strip_bind_group_layout: BindGroupLayout, /// Bind group layout for encoded paints pub(crate) encoded_paints_bind_group_layout: BindGroupLayout, /// Bind group layout for gradient texture pub(crate) gradient_bind_group_layout: BindGroupLayout, - /// Bind group layout for external textures. - external_texture_bind_group_layout: BindGroupLayout, - /// Bind group layout for filter data texture. - filter_bind_group_layout: BindGroupLayout, - /// Filter input layouts. - pub(crate) filter_input_bind_group_layouts: [BindGroupLayout; 2], - /// Filter sampler. - filter_sampler: Sampler, /// Native image samplers shared by external texture slots. external_samplers: HashMap<ExternalSampler, Sampler>, - /// Blend bind group layout. - blend_layer_bind_group_layout: BindGroupLayout, - /// Copy bind group layout. - pub(crate) copy_bind_group_layout: BindGroupLayout, /// Strip bind group cache. pub(crate) strip_layer_bind_groups: HashMap<(StripTargetKind, Option<LayerTextureId>), BindGroup>, @@ -85,24 +74,10 @@ pub(crate) blend_layer_bind_groups: HashMap<BlendPassBindings, BindGroup>, /// Filter bind group cache. pub(crate) filter_pair_bind_groups: HashMap<FilterPassBindings, FilterPairBindGroups>, - /// Pipeline for applying filter effects. - filter_pipeline: RenderPipeline, - /// Layer-clear pipeline. - clear_pipeline: RenderPipeline, - /// User-target rectangle-clear pipeline. - root_clear_pipeline: RenderPipeline, - /// Pipeline for clearing atlas regions. - pub(crate) atlas_clear_pipeline: RenderPipeline, - /// Blend pipeline. - blend_pipeline: RenderPipeline, - /// Copy pipeline. - copy_pipeline: RenderPipeline, /// GPU resources for rendering (created during prepare) pub(crate) resources: GpuResources, /// Arena holding all [`GpuStrip`] data. pub(crate) strips_arena: StripBufferArena, - /// Whether the external texture bind group has a `texture_external` binding. - wgpu_external_textures: bool, /// Shared staging memory for buffer and texture uploads. pub(crate) staging_belt: UploadStagingBelt, /// Dimensions of the rendering target @@ -124,557 +99,22 @@ resource_texture_dimension_2d: u32, pipeline_cache: Option<&wgpu::PipelineCache>, ) -> Self { - let strip_bind_group_layout = - device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - label: Some("Strip Bind Group Layout"), - entries: &[ - wgpu::BindGroupLayoutEntry { - binding: 0, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Texture { - sample_type: wgpu::TextureSampleType::Uint, - view_dimension: wgpu::TextureViewDimension::D2, - multisampled: false, - }, - count: None, - }, - wgpu::BindGroupLayoutEntry { - binding: 1, - visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Buffer { - ty: wgpu::BufferBindingType::Uniform, - has_dynamic_offset: false, - min_binding_size: None, - }, - count: None, - }, - wgpu::BindGroupLayoutEntry { - binding: 2, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Texture { - sample_type: wgpu::TextureSampleType::Float { filterable: false }, - view_dimension: wgpu::TextureViewDimension::D2, - multisampled: false, - }, - count: None, - }, - ], - }); - - #[cfg(feature = "wgpu_external_texture")] - let wgpu_external_textures = device.features().contains(wgpu::Features::EXTERNAL_TEXTURE); - #[cfg(not(feature = "wgpu_external_texture"))] - let wgpu_external_textures = false; - let external_texture_layout_entry = |binding| wgpu::BindGroupLayoutEntry { - binding, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Texture { - sample_type: wgpu::TextureSampleType::Float { filterable: true }, - view_dimension: wgpu::TextureViewDimension::D2, - multisampled: false, - }, - count: None, - }; - let external_sampler_layout_entry = |binding| wgpu::BindGroupLayoutEntry { - binding, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), - count: None, - }; - let mut external_texture_layout_entries = vec![ - external_texture_layout_entry(0), - external_sampler_layout_entry(1), - ]; - if wgpu_external_textures { - external_texture_layout_entries.push(wgpu::BindGroupLayoutEntry { - binding: 2, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::ExternalTexture, - count: None, - }); - } - let external_texture_bind_group_layout = - device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - label: Some("External Texture Bind Group Layout"), - entries: &external_texture_layout_entries, - }); - let encoded_paints_bind_group_layout = - device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - label: Some("Encoded Paints Bind Group Layout"), - entries: &[wgpu::BindGroupLayoutEntry { - binding: 0, - visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Texture { - sample_type: wgpu::TextureSampleType::Uint, - view_dimension: wgpu::TextureViewDimension::D2, - multisampled: false, - }, - count: None, - }], - }); - - let gradient_bind_group_layout = - device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - label: Some("Gradient Bind Group Layout"), - entries: &[wgpu::BindGroupLayoutEntry { - binding: 0, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Texture { - sample_type: wgpu::TextureSampleType::Float { filterable: true }, - view_dimension: wgpu::TextureViewDimension::D2, - multisampled: false, - }, - count: None, - }], - }); - - #[cfg(feature = "wgpu_external_texture")] - let strip_shader_source = if wgpu_external_textures { - vello_gpu_shaders::wgsl::RENDER_TEXTURE_EXTERNAL - } else { - vello_gpu_shaders::wgsl::RENDER - }; - #[cfg(not(feature = "wgpu_external_texture"))] - let strip_shader_source = vello_gpu_shaders::wgsl::RENDER; - let strip_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { - label: Some("Strip Shader"), - source: wgpu::ShaderSource::Wgsl(strip_shader_source.into()), - }); - - let clear_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { - label: Some("Clear Shader"), - source: wgpu::ShaderSource::Wgsl(vello_gpu_shaders::wgsl::CLEAR.into()), - }); - - let strip_pipeline_layout = - device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { - label: Some("Strip Pipeline Layout"), - bind_group_layouts: &[ - Some(&strip_bind_group_layout), - Some(&external_texture_bind_group_layout), - Some(&encoded_paints_bind_group_layout), - Some(&gradient_bind_group_layout), - ], - immediate_size: 0, - }); - - let clear_pipeline_layout = - device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { - label: Some("Clear Pipeline Layout"), - bind_group_layouts: &[], - immediate_size: 0, - }); - - let depth_format = wgpu::TextureFormat::Depth24Plus; - - let strip_vertex_state = wgpu::VertexBufferLayout { - array_stride: size_of::<GpuStrip>() as u64, - step_mode: wgpu::VertexStepMode::Instance, - attributes: &GpuStrip::vertex_attributes(), - }; - - let create_strip_pipeline = - |label, format, blend, depth_stencil: Option<wgpu::DepthStencilState>| { - device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { - label: Some(label), - layout: Some(&strip_pipeline_layout), - vertex: wgpu::VertexState { - module: &strip_shader, - entry_point: Some("vs_main"), - buffers: &[Some(strip_vertex_state.clone())], - compilation_options: PipelineCompilationOptions::default(), - }, - fragment: Some(wgpu::FragmentState { - module: &strip_shader, - entry_point: Some("fs_main"), - targets: &[Some(ColorTargetState { - format, - blend, - write_mask: ColorWrites::ALL, - })], - compilation_options: PipelineCompilationOptions::default(), - }), - primitive: wgpu::PrimitiveState { - topology: wgpu::PrimitiveTopology::TriangleStrip, - ..Default::default() - }, - depth_stencil, - multisample: wgpu::MultisampleState::default(), - multiview_mask: None, - cache: pipeline_cache, - }) - }; - - let depth_stencil = |depth_write_enabled| wgpu::DepthStencilState { - format: depth_format, - depth_write_enabled: Some(depth_write_enabled), - depth_compare: Some(wgpu::CompareFunction::LessEqual), - stencil: wgpu::StencilState::default(), - bias: wgpu::DepthBiasState::default(), - }; - - let intermediate_strip_pipeline = create_strip_pipeline( - "Strip Intermediate Pipeline", - wgpu::TextureFormat::Rgba8Unorm, - Some(BlendState::PREMULTIPLIED_ALPHA_BLENDING), - None, + Self::create_encoded_paints_bind_group_layout(device); + let gradient_bind_group_layout = Self::create_gradient_bind_group_layout(device); + let strip = StripPipelines::new( + device, + render_target_config, + &encoded_paints_bind_group_layout, + &gradient_bind_group_layout, + pipeline_cache, ); - - let alpha_strip_pipeline = create_strip_pipeline( - "Strip Alpha Pipeline", - render_target_config.format, - Some(BlendState::PREMULTIPLIED_ALPHA_BLENDING), - None, - ); - - let depth_alpha_strip_pipeline = create_strip_pipeline( - "Strip Depth Alpha Pipeline", - render_target_config.format, - Some(BlendState::PREMULTIPLIED_ALPHA_BLENDING), - Some(depth_stencil(false)), - ); - - let opaque_strip_pipeline = create_strip_pipeline( - "Strip Opaque Pipeline", - render_target_config.format, - None, - Some(depth_stencil(true)), - ); - - let clear_vertex_state = wgpu::VertexBufferLayout { - array_stride: size_of::<GpuClearInstance>() as u64, - step_mode: wgpu::VertexStepMode::Instance, - attributes: &wgpu::vertex_attr_array![ - 0 => Uint32x2, - 1 => Uint32x2, - 2 => Uint32x2, - 3 => Uint32, - ], - }; - let create_clear_pipeline = |label, format| { - device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { - label: Some(label), - layout: Some(&clear_pipeline_layout), - vertex: wgpu::VertexState { - module: &clear_shader, - entry_point: Some("vs_main"), - buffers: &[Some(clear_vertex_state.clone())], - compilation_options: PipelineCompilationOptions::default(), - }, - fragment: Some(wgpu::FragmentState { - module: &clear_shader, - entry_point: Some("fs_main"), - targets: &[Some(ColorTargetState { - format, - blend: None, - write_mask: ColorWrites::ALL, - })], - compilation_options: PipelineCompilationOptions::default(), - }), - primitive: wgpu::PrimitiveState { - topology: wgpu::PrimitiveTopology::TriangleStrip, - ..Default::default() - }, - depth_stencil: None, - multisample: wgpu::MultisampleState::default(), - multiview_mask: None, - cache: pipeline_cache, - }) - }; - let clear_pipeline = - create_clear_pipeline("Clear Pipeline", wgpu::TextureFormat::Rgba8Unorm); - let root_clear_pipeline = - create_clear_pipeline("Root Clear Pipeline", render_target_config.format); - - // Create atlas clear pipeline - let atlas_clear_pipeline_layout = - device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { - label: Some("Atlas Clear Pipeline Layout"), - bind_group_layouts: &[], - immediate_size: 0, - }); - // TODO: Change the atlas clear pipeline to use the existing layer clear mechanism. - let atlas_clear_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { - label: Some("Atlas Clear Pipeline"), - layout: Some(&atlas_clear_pipeline_layout), - vertex: wgpu::VertexState { - module: &clear_shader, - // Use a different vertex shader entry point - entry_point: Some("vs_main_fullscreen"), - buffers: &[], - compilation_options: PipelineCompilationOptions::default(), - }, - fragment: Some(wgpu::FragmentState { - module: &clear_shader, - entry_point: Some("fs_transparent"), - targets: &[Some(ColorTargetState { - format: wgpu::TextureFormat::Rgba8Unorm, - blend: None, - write_mask: ColorWrites::ALL, - })], - compilation_options: PipelineCompilationOptions::default(), - }), - primitive: wgpu::PrimitiveState { - topology: wgpu::PrimitiveTopology::TriangleStrip, - ..Default::default() - }, - depth_stencil: None, - multisample: wgpu::MultisampleState::default(), - multiview_mask: None, - cache: pipeline_cache, - }); - - let filter_texture_entry = wgpu::BindGroupLayoutEntry { - binding: 0, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Texture { - sample_type: wgpu::TextureSampleType::Float { filterable: true }, - view_dimension: wgpu::TextureViewDimension::D2, - multisampled: false, - }, - count: None, - }; - let filter_bind_group_layout = - device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - label: Some("Filter Bind Group Layout"), - entries: &[wgpu::BindGroupLayoutEntry { - binding: 0, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Texture { - sample_type: wgpu::TextureSampleType::Uint, - view_dimension: wgpu::TextureViewDimension::D2, - multisampled: false, - }, - count: None, - }], - }); - let filter_input_bind_group_layouts = [ - // Input texture and linear sampler. - device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - label: Some("Filter Input Bind Group Layout"), - entries: &[ - filter_texture_entry, - wgpu::BindGroupLayoutEntry { - binding: 1, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), - count: None, - }, - ], - }), - // The original layer texture. - device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - label: Some("Filter Original Texture Bind Group Layout"), - entries: &[filter_texture_entry], - }), - ]; - - let filter_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { - label: Some("Filter Shader"), - source: wgpu::ShaderSource::Wgsl(vello_gpu_shaders::wgsl::FILTER.into()), - }); - let filter_pipeline_layout = - device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { - label: Some("Filter Pipeline Layout"), - bind_group_layouts: &[ - Some(&filter_bind_group_layout), - Some(&filter_input_bind_group_layouts[0]), - Some(&filter_input_bind_group_layouts[1]), - ], - immediate_size: 0, - }); - let filter_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { - label: Some("Filter Pipeline"), - layout: Some(&filter_pipeline_layout), - vertex: wgpu::VertexState { - module: &filter_shader, - entry_point: Some("vs_main"), - buffers: &[Some(wgpu::VertexBufferLayout { - array_stride: size_of::<FilterInstanceData>() as u64, - step_mode: wgpu::VertexStepMode::Instance, - attributes: &wgpu::vertex_attr_array![ - 0 => Uint32, - 1 => Uint32, - 2 => Uint32, - 3 => Uint32, - 4 => Uint32, - 5 => Uint32, - 6 => Uint32, - 7 => Uint32, - 8 => Uint32, - ], - })], - compilation_options: PipelineCompilationOptions::default(), - }, - fragment: Some(wgpu::FragmentState { - module: &filter_shader, - entry_point: Some("fs_main"), - targets: &[Some(ColorTargetState { - format: wgpu::TextureFormat::Rgba8Unorm, - blend: None, - write_mask: ColorWrites::ALL, - })], - compilation_options: PipelineCompilationOptions::default(), - }), - primitive: wgpu::PrimitiveState { - topology: wgpu::PrimitiveTopology::TriangleStrip, - ..Default::default() - }, - depth_stencil: None, - multisample: wgpu::MultisampleState::default(), - cache: pipeline_cache, - multiview_mask: None, - }); - - let blend_layer_bind_group_layout = - device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - label: Some("Blend Layer Textures Bind Group Layout"), - entries: &[ - wgpu::BindGroupLayoutEntry { - binding: 0, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Texture { - sample_type: wgpu::TextureSampleType::Float { filterable: false }, - view_dimension: wgpu::TextureViewDimension::D2, - multisampled: false, - }, - count: None, - }, - wgpu::BindGroupLayoutEntry { - binding: 1, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Texture { - sample_type: wgpu::TextureSampleType::Float { filterable: false }, - view_dimension: wgpu::TextureViewDimension::D2, - multisampled: false, - }, - count: None, - }, - wgpu::BindGroupLayoutEntry { - binding: 2, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Texture { - sample_type: wgpu::TextureSampleType::Uint, - view_dimension: wgpu::TextureViewDimension::D2, - multisampled: false, - }, - count: None, - }, - ], - }); - - let copy_bind_group_layout = - device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - label: Some("Copy Bind Group Layout"), - entries: &[wgpu::BindGroupLayoutEntry { - binding: 0, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Texture { - sample_type: wgpu::TextureSampleType::Float { filterable: false }, - view_dimension: wgpu::TextureViewDimension::D2, - multisampled: false, - }, - count: None, - }], - }); - let blend_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { - label: Some("Blend Shader"), - source: wgpu::ShaderSource::Wgsl(vello_gpu_shaders::wgsl::BLEND.into()), - }); - let copy_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { - label: Some("Copy Shader"), - source: wgpu::ShaderSource::Wgsl(vello_gpu_shaders::wgsl::COPY.into()), - }); - let blend_pipeline_layout = - device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { - label: Some("Blend Pipeline Layout"), - bind_group_layouts: &[Some(&blend_layer_bind_group_layout)], - immediate_size: 0, - }); - let copy_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { - label: Some("Copy Pipeline Layout"), - bind_group_layouts: &[Some(©_bind_group_layout)], - immediate_size: 0, - }); - let blend_vertex_state = wgpu::VertexBufferLayout { - array_stride: size_of::<GpuBlendInstance>() as u64, - step_mode: wgpu::VertexStepMode::Instance, - attributes: &wgpu::vertex_attr_array![ - 0 => Uint32, - 1 => Uint32, - 2 => Uint32, - 3 => Uint32, - 4 => Uint32, - 5 => Uint32, - 6 => Uint32, - 7 => Uint32, - ], - }; - let copy_vertex_state = wgpu::VertexBufferLayout { - array_stride: size_of::<GpuCopyInstance>() as u64, - step_mode: wgpu::VertexStepMode::Instance, - attributes: &wgpu::vertex_attr_array![ - 0 => Uint32, - 1 => Uint32, - 2 => Uint32, - 3 => Uint32, - ], - }; - let create_texture_op_pipeline = - |label, - shader_module: &wgpu::ShaderModule, - layout, - vertex_state: &wgpu::VertexBufferLayout<'_>| { - device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { - label: Some(label), - layout: Some(layout), - vertex: wgpu::VertexState { - module: shader_module, - entry_point: Some("vs_main"), - buffers: &[Some(vertex_state.clone())], - compilation_options: PipelineCompilationOptions::default(), - }, - fragment: Some(wgpu::FragmentState { - module: shader_module, - entry_point: Some("fs_main"), - targets: &[Some(ColorTargetState { - format: wgpu::TextureFormat::Rgba8Unorm, - blend: None, - write_mask: ColorWrites::ALL, - })], - compilation_options: PipelineCompilationOptions::default(), - }), - primitive: wgpu::PrimitiveState { - topology: wgpu::PrimitiveTopology::TriangleStrip, - ..Default::default() - }, - depth_stencil: None, - multisample: wgpu::MultisampleState::default(), - multiview_mask: None, - cache: pipeline_cache, - }) - }; - let blend_pipeline = create_texture_op_pipeline( - "Blend Pipeline", - &blend_shader, - &blend_pipeline_layout, - &blend_vertex_state, - ); - let copy_pipeline = create_texture_op_pipeline( - "Copy Pipeline", - ©_shader, - ©_pipeline_layout, - ©_vertex_state, - ); + let clear = ClearPipelines::new(device, render_target_config, pipeline_cache); + let filter = FilterPipeline::new(device, pipeline_cache); + let blend = BlendPipeline::new(device, pipeline_cache); + let copy = CopyPipeline::new(device, pipeline_cache); let texture_size = layer_config.min_texture_size; - let filter_sampler = device.create_sampler(&wgpu::SamplerDescriptor { - label: Some("Filter Linear Sampler"), - mag_filter: wgpu::FilterMode::Linear, - min_filter: wgpu::FilterMode::Linear, - ..Default::default() - }); // The empty bind group needs a sampler even before any external image is drawn. let mut external_samplers = HashMap::new(); @@ -688,7 +128,7 @@ let placeholder_external_texture_view = Self::create_placeholder_external_texture(device); let filter_original_bind_group = create_filter_original_texture_bind_group( device, - &filter_input_bind_group_layouts[1], + &filter.input_bind_group_layouts[1], &placeholder_external_texture_view, ); let layer_config_buffer = Self::create_config_buffer( @@ -726,12 +166,14 @@ .collect(); let empty_external_texture_bind_group = Self::create_external_texture_bind_group( device, - &external_texture_bind_group_layout, + &strip.external_texture_bind_group_layout, [&placeholder_external_texture_view; EXTERNAL_TEXTURE_SLOT_COUNT], [&external_samplers[&ExternalSampler::DEFAULT]; EXTERNAL_TEXTURE_SLOT_COUNT], - wgpu_external_textures.then_some(wgpu::BindingResource::TextureView( - &placeholder_external_texture_view, - )), + strip + .wgpu_external_textures + .then_some(wgpu::BindingResource::TextureView( + &placeholder_external_texture_view, + )), ); const INITIAL_ENCODED_PAINTS_TEXTURE_HEIGHT: u32 = 1; @@ -777,12 +219,12 @@ ); let filter_base_bind_group = Self::create_filter_base_bind_group( device, - &filter_bind_group_layout, + &filter.bind_group_layout, &filter_data_texture.create_view(&TextureViewDescriptor::default()), ); let scratch_copy_bind_group = Self::create_copy_bind_group( device, - ©_bind_group_layout, + ©.bind_group_layout, &placeholder_external_texture_view, ); @@ -810,22 +252,16 @@ }; Self { - intermediate_strip_pipeline, - alpha_strip_pipeline, - depth_alpha_strip_pipeline, - opaque_strip_pipeline, + strip, + clear, + filter, + blend, + copy, depth_cleared_this_frame: false, - strip_bind_group_layout, encoded_paints_bind_group_layout, gradient_bind_group_layout, - external_texture_bind_group_layout, - filter_bind_group_layout, - filter_pipeline, - blend_pipeline, - copy_pipeline, resources, strips_arena: StripBufferArena::new(device), - wgpu_external_textures, staging_belt: UploadStagingBelt::new(device), encoded_paints_data, filter_data, @@ -834,14 +270,7 @@ height: render_target_config.height, }, scene_offset: (0, 0), - clear_pipeline, - root_clear_pipeline, - atlas_clear_pipeline, - filter_input_bind_group_layouts, - filter_sampler, external_samplers, - blend_layer_bind_group_layout, - copy_bind_group_layout, strip_layer_bind_groups: HashMap::new(), blend_layer_bind_groups: HashMap::new(), filter_pair_bind_groups: HashMap::new(), @@ -849,6 +278,45 @@ } } +/// Create a pipeline that draws instanced quads into an intermediate texture without blending. +fn create_texture_op_pipeline( + device: &Device, + label: &str, + shader_module: &wgpu::ShaderModule, + layout: &wgpu::PipelineLayout, + vertex_state: &wgpu::VertexBufferLayout<'_>, + pipeline_cache: Option<&wgpu::PipelineCache>, +) -> RenderPipeline { + device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some(label), + layout: Some(layout), + vertex: wgpu::VertexState { + module: shader_module, + entry_point: Some("vs_main"), + buffers: &[Some(vertex_state.clone())], + compilation_options: PipelineCompilationOptions::default(), + }, + fragment: Some(wgpu::FragmentState { + module: shader_module, + entry_point: Some("fs_main"), + targets: &[Some(ColorTargetState { + format: wgpu::TextureFormat::Rgba8Unorm, + blend: None, + write_mask: ColorWrites::ALL, + })], + compilation_options: PipelineCompilationOptions::default(), + }), + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleStrip, + ..Default::default() + }, + depth_stencil: None, + multisample: wgpu::MultisampleState::default(), + multiview_mask: None, + cache: pipeline_cache, + }) +} + /// A struct containing references to the many objects needed to get work /// scheduled onto the GPU. pub(crate) struct RendererContext<'a> {
diff --git a/vello_gpu/src/render/wgpu/passes/strip.rs b/vello_gpu/src/render/wgpu/passes/strip.rs index a01f1e6..9e34af9 100644 --- a/vello_gpu/src/render/wgpu/passes/strip.rs +++ b/vello_gpu/src/render/wgpu/passes/strip.rs
@@ -4,6 +4,7 @@ //! The strip pass. use crate::draw::{EXTERNAL_TEXTURE_SLOT_COUNT, ExternalTextureBindings, ExternalTextureRun}; +use crate::render::wgpu::RenderTargetConfig; use crate::render::wgpu::bindings::TextureBinding; use crate::render::wgpu::passes::{Programs, RendererContext}; use crate::util::RangedSlice; @@ -16,10 +17,226 @@ use core::{fmt::Debug, num::NonZeroU64, ops::Range}; use hashbrown::hash_map::Entry; use wgpu::{ - BindGroup, BindGroupLayout, Buffer, CommandEncoder, Device, RenderPassColorAttachment, - RenderPassDescriptor, Sampler, TextureView, TextureViewDescriptor, + BindGroup, BindGroupLayout, BlendState, Buffer, ColorTargetState, ColorWrites, CommandEncoder, + Device, PipelineCompilationOptions, RenderPassColorAttachment, RenderPassDescriptor, + RenderPipeline, Sampler, TextureView, TextureViewDescriptor, }; +/// Bind group layouts and pipelines of the strip pass. +#[derive(Debug)] +pub(crate) struct StripPipelines { + /// Bind group layout for strip draws + pub(crate) bind_group_layout: BindGroupLayout, + /// Bind group layout for external textures. + pub(crate) external_texture_bind_group_layout: BindGroupLayout, + /// Whether the external texture bind group has a `texture_external` binding. + pub(crate) wgpu_external_textures: bool, + /// Intermediate strip pipeline. + pub(crate) intermediate: RenderPipeline, + /// Root alpha-strip pipeline. + pub(crate) alpha: RenderPipeline, + /// Root alpha-strip pipeline with depth testing. + pub(crate) depth_alpha: RenderPipeline, + /// Root opaque-strip pipeline. + pub(crate) opaque: RenderPipeline, +} + +impl StripPipelines { + pub(crate) fn new( + device: &Device, + render_target_config: &RenderTargetConfig, + encoded_paints_bind_group_layout: &BindGroupLayout, + gradient_bind_group_layout: &BindGroupLayout, + pipeline_cache: Option<&wgpu::PipelineCache>, + ) -> Self { + let strip_bind_group_layout = + device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: Some("Strip Bind Group Layout"), + entries: &[ + wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Texture { + sample_type: wgpu::TextureSampleType::Uint, + view_dimension: wgpu::TextureViewDimension::D2, + multisampled: false, + }, + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 1, + visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 2, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Texture { + sample_type: wgpu::TextureSampleType::Float { filterable: false }, + view_dimension: wgpu::TextureViewDimension::D2, + multisampled: false, + }, + count: None, + }, + ], + }); + + #[cfg(feature = "wgpu_external_texture")] + let wgpu_external_textures = device.features().contains(wgpu::Features::EXTERNAL_TEXTURE); + #[cfg(not(feature = "wgpu_external_texture"))] + let wgpu_external_textures = false; + let external_texture_layout_entry = |binding| wgpu::BindGroupLayoutEntry { + binding, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Texture { + sample_type: wgpu::TextureSampleType::Float { filterable: true }, + view_dimension: wgpu::TextureViewDimension::D2, + multisampled: false, + }, + count: None, + }; + let external_sampler_layout_entry = |binding| wgpu::BindGroupLayoutEntry { + binding, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), + count: None, + }; + let mut external_texture_layout_entries = vec![ + external_texture_layout_entry(0), + external_sampler_layout_entry(1), + ]; + if wgpu_external_textures { + external_texture_layout_entries.push(wgpu::BindGroupLayoutEntry { + binding: 2, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::ExternalTexture, + count: None, + }); + } + let external_texture_bind_group_layout = + device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: Some("External Texture Bind Group Layout"), + entries: &external_texture_layout_entries, + }); + + #[cfg(feature = "wgpu_external_texture")] + let strip_shader_source = if wgpu_external_textures { + vello_gpu_shaders::wgsl::RENDER_TEXTURE_EXTERNAL + } else { + vello_gpu_shaders::wgsl::RENDER + }; + #[cfg(not(feature = "wgpu_external_texture"))] + let strip_shader_source = vello_gpu_shaders::wgsl::RENDER; + let strip_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: Some("Strip Shader"), + source: wgpu::ShaderSource::Wgsl(strip_shader_source.into()), + }); + + let strip_pipeline_layout = + device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("Strip Pipeline Layout"), + bind_group_layouts: &[ + Some(&strip_bind_group_layout), + Some(&external_texture_bind_group_layout), + Some(encoded_paints_bind_group_layout), + Some(gradient_bind_group_layout), + ], + immediate_size: 0, + }); + + let depth_format = wgpu::TextureFormat::Depth24Plus; + + let strip_vertex_state = wgpu::VertexBufferLayout { + array_stride: size_of::<GpuStrip>() as u64, + step_mode: wgpu::VertexStepMode::Instance, + attributes: &GpuStrip::vertex_attributes(), + }; + + let create_strip_pipeline = + |label, format, blend, depth_stencil: Option<wgpu::DepthStencilState>| { + device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some(label), + layout: Some(&strip_pipeline_layout), + vertex: wgpu::VertexState { + module: &strip_shader, + entry_point: Some("vs_main"), + buffers: &[Some(strip_vertex_state.clone())], + compilation_options: PipelineCompilationOptions::default(), + }, + fragment: Some(wgpu::FragmentState { + module: &strip_shader, + entry_point: Some("fs_main"), + targets: &[Some(ColorTargetState { + format, + blend, + write_mask: ColorWrites::ALL, + })], + compilation_options: PipelineCompilationOptions::default(), + }), + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleStrip, + ..Default::default() + }, + depth_stencil, + multisample: wgpu::MultisampleState::default(), + multiview_mask: None, + cache: pipeline_cache, + }) + }; + + let depth_stencil = |depth_write_enabled| wgpu::DepthStencilState { + format: depth_format, + depth_write_enabled: Some(depth_write_enabled), + depth_compare: Some(wgpu::CompareFunction::LessEqual), + stencil: wgpu::StencilState::default(), + bias: wgpu::DepthBiasState::default(), + }; + + let intermediate_strip_pipeline = create_strip_pipeline( + "Strip Intermediate Pipeline", + wgpu::TextureFormat::Rgba8Unorm, + Some(BlendState::PREMULTIPLIED_ALPHA_BLENDING), + None, + ); + + let alpha_strip_pipeline = create_strip_pipeline( + "Strip Alpha Pipeline", + render_target_config.format, + Some(BlendState::PREMULTIPLIED_ALPHA_BLENDING), + None, + ); + + let depth_alpha_strip_pipeline = create_strip_pipeline( + "Strip Depth Alpha Pipeline", + render_target_config.format, + Some(BlendState::PREMULTIPLIED_ALPHA_BLENDING), + Some(depth_stencil(false)), + ); + + let opaque_strip_pipeline = create_strip_pipeline( + "Strip Opaque Pipeline", + render_target_config.format, + None, + Some(depth_stencil(true)), + ); + + Self { + bind_group_layout: strip_bind_group_layout, + external_texture_bind_group_layout, + wgpu_external_textures, + intermediate: intermediate_strip_pipeline, + alpha: alpha_strip_pipeline, + depth_alpha: depth_alpha_strip_pipeline, + opaque: opaque_strip_pipeline, + } + } +} + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub(crate) enum StripTargetKind { Root, @@ -113,7 +330,8 @@ let (texture_view, wgpu_external_texture) = match binding { TextureBinding::TextureView(view) => ( view, - self.wgpu_external_textures + self.strip + .wgpu_external_textures .then_some(wgpu::BindingResource::TextureView(placeholder)), ), #[cfg(feature = "wgpu_external_texture")] @@ -124,7 +342,7 @@ }; Self::create_external_texture_bind_group( device, - &self.external_texture_bind_group_layout, + &self.strip.external_texture_bind_group_layout, [texture_view], samplers, wgpu_external_texture, @@ -270,7 +488,7 @@ .create_view(&TextureViewDescriptor::default()); let bind_group = Programs::create_strip_bind_group( self.device, - &self.programs.strip_bind_group_layout, + &self.programs.strip.bind_group_layout, &alphas_texture_view, config_buffer, child_layer_view, @@ -367,7 +585,7 @@ enable_opaque, "opaque strips require the final view depth attachment" ); - render_pass.set_pipeline(&self.programs.opaque_strip_pipeline); + render_pass.set_pipeline(&self.programs.strip.opaque); draw_strip_runs(&mut render_pass, 0, opaque_count); } @@ -375,13 +593,13 @@ // Alpha pass if matches!(target, DrawPassTarget::Root(RootTarget::UserSurface)) { let pipeline = if enable_opaque { - &self.programs.depth_alpha_strip_pipeline + &self.programs.strip.depth_alpha } else { - &self.programs.alpha_strip_pipeline + &self.programs.strip.alpha }; render_pass.set_pipeline(pipeline); } else { - render_pass.set_pipeline(&self.programs.intermediate_strip_pipeline); + render_pass.set_pipeline(&self.programs.strip.intermediate); } draw_strip_runs(&mut render_pass, opaque_count, alpha_count);
diff --git a/vello_gpu/src/render/wgpu/resources.rs b/vello_gpu/src/render/wgpu/resources.rs index 83e4261..37b8e35 100644 --- a/vello_gpu/src/render/wgpu/resources.rs +++ b/vello_gpu/src/render/wgpu/resources.rs
@@ -309,11 +309,11 @@ let scratch_view = self.resources.scratch_view(); let filter_original_bind_group = create_filter_original_texture_bind_group( device, - &self.filter_input_bind_group_layouts[1], + &self.filter.input_bind_group_layouts[1], scratch_view, ); let scratch_copy_bind_group = - Self::create_copy_bind_group(device, &self.copy_bind_group_layout, scratch_view); + Self::create_copy_bind_group(device, &self.copy.bind_group_layout, scratch_view); self.resources.filter_original_bind_group = filter_original_bind_group; self.resources.scratch_copy_bind_group = scratch_copy_bind_group; @@ -418,6 +418,38 @@ }) } + pub(crate) fn create_encoded_paints_bind_group_layout(device: &Device) -> BindGroupLayout { + device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: Some("Encoded Paints Bind Group Layout"), + entries: &[wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Texture { + sample_type: wgpu::TextureSampleType::Uint, + view_dimension: wgpu::TextureViewDimension::D2, + multisampled: false, + }, + count: None, + }], + }) + } + + pub(crate) fn create_gradient_bind_group_layout(device: &Device) -> BindGroupLayout { + device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: Some("Gradient Bind Group Layout"), + entries: &[wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Texture { + sample_type: wgpu::TextureSampleType::Float { filterable: true }, + view_dimension: wgpu::TextureViewDimension::D2, + multisampled: false, + }, + count: None, + }], + }) + } + pub(crate) fn create_encoded_paints_texture( device: &Device, width: u32,