From 4f6bac784072675a1eceff68da7cd0c86d796c71 Mon Sep 17 00:00:00 2001 From: Nathan Adams Date: Tue, 27 Jun 2023 21:47:47 +0200 Subject: [PATCH] wgpu: Refactor out filters into their own files --- render/wgpu/src/descriptors.rs | 8 +- render/wgpu/src/filters.rs | 75 +++++++ render/wgpu/src/filters/blur.rs | 234 ++++++++++++++++++++++ render/wgpu/src/filters/color_matrix.rs | 186 +++++++++++++++++ render/wgpu/src/layouts.rs | 73 ------- render/wgpu/src/lib.rs | 8 +- render/wgpu/src/pipelines.rs | 91 +-------- render/wgpu/src/surface.rs | 254 +----------------------- render/wgpu/src/utils.rs | 49 +++++ 9 files changed, 556 insertions(+), 422 deletions(-) create mode 100644 render/wgpu/src/filters.rs create mode 100644 render/wgpu/src/filters/blur.rs create mode 100644 render/wgpu/src/filters/color_matrix.rs diff --git a/render/wgpu/src/descriptors.rs b/render/wgpu/src/descriptors.rs index d54f57747..37ae26d1f 100644 --- a/render/wgpu/src/descriptors.rs +++ b/render/wgpu/src/descriptors.rs @@ -1,8 +1,9 @@ +use crate::filters::{FilterVertex, Filters}; use crate::layouts::BindLayouts; use crate::pipelines::VERTEX_BUFFERS_DESCRIPTION_POS; use crate::shaders::Shaders; use crate::{ - create_buffer_with_data, BitmapSamplers, FilterVertex, Pipelines, PosColorVertex, PosVertex, + create_buffer_with_data, BitmapSamplers, Pipelines, PosColorVertex, PosVertex, TextureTransforms, Transforms, DEFAULT_COLOR_ADJUSTMENTS, }; use fnv::FnvHashMap; @@ -20,9 +21,10 @@ pub struct Descriptors { pub quad: Quad, copy_pipeline: Mutex>>, copy_srgb_pipeline: Mutex>>, - shaders: Shaders, + pub shaders: Shaders, pipelines: Mutex>>, pub default_color_bind_group: wgpu::BindGroup, + pub filters: Filters, } impl Debug for Descriptors { @@ -52,6 +54,7 @@ impl Descriptors { resource: default_color_transform.as_entire_binding(), }], }); + let filters = Filters::new(&device); Self { adapter, @@ -66,6 +69,7 @@ impl Descriptors { shaders, pipelines: Default::default(), default_color_bind_group, + filters, } } diff --git a/render/wgpu/src/filters.rs b/render/wgpu/src/filters.rs new file mode 100644 index 000000000..9871e842b --- /dev/null +++ b/render/wgpu/src/filters.rs @@ -0,0 +1,75 @@ +mod blur; +mod color_matrix; + +use crate::filters::blur::BlurFilter; +use crate::filters::color_matrix::ColorMatrixFilter; +use bytemuck::{Pod, Zeroable}; +use wgpu::util::DeviceExt; +use wgpu::vertex_attr_array; + +pub struct Filters { + pub blur: BlurFilter, + pub color_matrix: ColorMatrixFilter, +} + +impl Filters { + pub fn new(device: &wgpu::Device) -> Self { + Self { + blur: BlurFilter::new(device), + color_matrix: ColorMatrixFilter::new(device), + } + } +} + +#[repr(C)] +#[derive(Copy, Clone, Debug, Pod, Zeroable)] +pub struct FilterVertex { + pub position: [f32; 2], + pub uv: [f32; 2], +} + +pub const VERTEX_BUFFERS_DESCRIPTION_FILTERS: [wgpu::VertexBufferLayout; 1] = + [wgpu::VertexBufferLayout { + array_stride: std::mem::size_of::() as u64, + step_mode: wgpu::VertexStepMode::Vertex, + attributes: &vertex_attr_array![ + 0 => Float32x2, + 1 => Float32x2, + ], + }]; + +pub fn create_filter_vertices( + device: &wgpu::Device, + source_texture: &wgpu::Texture, + source_point: (u32, u32), + source_size: (u32, u32), +) -> wgpu::Buffer { + let source_width = source_texture.width() as f32; + let source_height = source_texture.height() as f32; + let left = source_point.0; + let top = source_point.1; + let right = left + source_size.0; + let bottom = top + source_size.1; + device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: create_debug_label!("Filter vertices").as_deref(), + contents: bytemuck::cast_slice(&[ + FilterVertex { + position: [0.0, 0.0], + uv: [left as f32 / source_width, top as f32 / source_height], + }, + FilterVertex { + position: [1.0, 0.0], + uv: [right as f32 / source_width, top as f32 / source_height], + }, + FilterVertex { + position: [1.0, 1.0], + uv: [right as f32 / source_width, bottom as f32 / source_height], + }, + FilterVertex { + position: [0.0, 1.0], + uv: [left as f32 / source_width, bottom as f32 / source_height], + }, + ]), + usage: wgpu::BufferUsages::VERTEX, + }) +} diff --git a/render/wgpu/src/filters/blur.rs b/render/wgpu/src/filters/blur.rs new file mode 100644 index 000000000..61b2deb46 --- /dev/null +++ b/render/wgpu/src/filters/blur.rs @@ -0,0 +1,234 @@ +use crate::backend::RenderTargetMode; +use crate::buffer_pool::TexturePool; +use crate::descriptors::Descriptors; +use crate::filters::{create_filter_vertices, VERTEX_BUFFERS_DESCRIPTION_FILTERS}; +use crate::surface::target::CommandTarget; +use crate::utils::SampleCountMap; +use std::sync::OnceLock; +use swf::BlurFilter as BlurFilterArgs; +use wgpu::util::DeviceExt; + +pub struct BlurFilter { + bind_group_layout: wgpu::BindGroupLayout, + pipeline_layout: wgpu::PipelineLayout, + pipelines: SampleCountMap>, +} + +impl BlurFilter { + pub fn new(device: &wgpu::Device) -> Self { + let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + entries: &[ + wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Texture { + multisampled: false, + sample_type: wgpu::TextureSampleType::Float { filterable: true }, + view_dimension: wgpu::TextureViewDimension::D2, + }, + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 1, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 2, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: wgpu::BufferSize::new( + std::mem::size_of::<[f32; 4]>() as u64 + ), + }, + count: None, + }, + ], + label: create_debug_label!("Blur filter binds").as_deref(), + }); + + let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: None, + bind_group_layouts: &[&bind_group_layout], + push_constant_ranges: &[], + }); + + Self { + pipelines: Default::default(), + pipeline_layout, + bind_group_layout, + } + } + + fn pipeline(&self, descriptors: &Descriptors, msaa_sample_count: u32) -> &wgpu::RenderPipeline { + self.pipelines.get_or_init(msaa_sample_count, || { + let label = create_debug_label!("Blur Filter ({} msaa)", msaa_sample_count); + descriptors + .device + .create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: label.as_deref(), + layout: Some(&self.pipeline_layout), + vertex: wgpu::VertexState { + module: &descriptors.shaders.blur_filter, + entry_point: "main_vertex", + buffers: &VERTEX_BUFFERS_DESCRIPTION_FILTERS, + }, + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleList, + strip_index_format: None, + front_face: wgpu::FrontFace::Ccw, + cull_mode: None, + polygon_mode: wgpu::PolygonMode::default(), + unclipped_depth: false, + conservative: false, + }, + depth_stencil: None, + multisample: wgpu::MultisampleState { + count: msaa_sample_count, + mask: !0, + alpha_to_coverage_enabled: false, + }, + fragment: Some(wgpu::FragmentState { + module: &descriptors.shaders.blur_filter, + entry_point: "main_fragment", + targets: &[Some(wgpu::TextureFormat::Rgba8Unorm.into())], + }), + multiview: None, + }) + }) + } + + #[allow(clippy::too_many_arguments)] + pub fn apply( + &self, + descriptors: &Descriptors, + texture_pool: &mut TexturePool, + draw_encoder: &mut wgpu::CommandEncoder, + source_texture: &wgpu::Texture, + source_point: (u32, u32), + source_size: (u32, u32), + filter: &BlurFilterArgs, + ) -> CommandTarget { + let sample_count = source_texture.sample_count(); + let format = source_texture.format(); + let pipeline = self.pipeline(descriptors, sample_count); + + // FIXME - this should be larger than the source texture. Figure out exactly how much larger + let targets = [ + CommandTarget::new( + descriptors, + texture_pool, + wgpu::Extent3d { + width: source_size.0, + height: source_size.1, + depth_or_array_layers: 1, + }, + format, + sample_count, + RenderTargetMode::FreshWithColor(wgpu::Color::TRANSPARENT), + draw_encoder, + ), + CommandTarget::new( + descriptors, + texture_pool, + wgpu::Extent3d { + width: source_size.0, + height: source_size.1, + depth_or_array_layers: 1, + }, + format, + sample_count, + RenderTargetMode::FreshWithColor(wgpu::Color::TRANSPARENT), + draw_encoder, + ), + ]; + + // TODO: Vertices should be per pass, and each pass needs diff sizes + let vertices = create_filter_vertices( + &descriptors.device, + source_texture, + source_point, + source_size, + ); + + let source_view = source_texture.create_view(&Default::default()); + for i in 0..2 { + let blur_x = (filter.blur_x.to_f32() - 1.0).max(0.0); + let blur_y = (filter.blur_y.to_f32() - 1.0).max(0.0); + let current = &targets[i % 2]; + let (previous_view, previous_vertices, previous_width, previous_height) = if i == 0 { + ( + &source_view, + vertices.slice(..), + source_texture.width() as f32, + source_texture.height() as f32, + ) + } else { + let previous = &targets[(i - 1) % 2]; + ( + previous.color_view(), + descriptors.quad.filter_vertices.slice(..), + previous.width() as f32, + previous.height() as f32, + ) + }; + let buffer = descriptors + .device + .create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: create_debug_label!("Filter arguments").as_deref(), + contents: bytemuck::cast_slice(&[ + blur_x * ((i as u32) % 2) as f32, + blur_y * (((i as u32) % 2) + 1) as f32, + previous_width, + previous_height, + ]), + usage: wgpu::BufferUsages::UNIFORM, + }); + let filter_group = descriptors + .device + .create_bind_group(&wgpu::BindGroupDescriptor { + label: create_debug_label!("Filter group").as_deref(), + layout: &self.bind_group_layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::TextureView(previous_view), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::Sampler( + descriptors.bitmap_samplers.get_sampler(false, true), + ), + }, + wgpu::BindGroupEntry { + binding: 2, + resource: buffer.as_entire_binding(), + }, + ], + }); + let mut render_pass = draw_encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: create_debug_label!("Blur filter").as_deref(), + color_attachments: &[current.color_attachments()], + depth_stencil_attachment: None, + }); + render_pass.set_pipeline(pipeline); + + render_pass.set_bind_group(0, &filter_group, &[]); + + render_pass.set_vertex_buffer(0, previous_vertices); + render_pass.set_index_buffer( + descriptors.quad.indices.slice(..), + wgpu::IndexFormat::Uint32, + ); + render_pass.draw_indexed(0..6, 0, 0..1); + } + + targets + .into_iter() + .last() + .expect("Targets should not be empty") + } +} diff --git a/render/wgpu/src/filters/color_matrix.rs b/render/wgpu/src/filters/color_matrix.rs new file mode 100644 index 000000000..5f0aa11fe --- /dev/null +++ b/render/wgpu/src/filters/color_matrix.rs @@ -0,0 +1,186 @@ +use crate::backend::RenderTargetMode; +use crate::buffer_pool::TexturePool; +use crate::descriptors::Descriptors; +use crate::filters::{create_filter_vertices, VERTEX_BUFFERS_DESCRIPTION_FILTERS}; +use crate::surface::target::CommandTarget; +use crate::utils::SampleCountMap; +use std::sync::OnceLock; +use swf::ColorMatrixFilter as ColorMatrixFilterArgs; +use wgpu::util::DeviceExt; + +pub struct ColorMatrixFilter { + bind_group_layout: wgpu::BindGroupLayout, + pipeline_layout: wgpu::PipelineLayout, + pipelines: SampleCountMap>, +} + +impl ColorMatrixFilter { + pub fn new(device: &wgpu::Device) -> Self { + let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + entries: &[ + wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Texture { + multisampled: false, + sample_type: wgpu::TextureSampleType::Float { filterable: true }, + view_dimension: wgpu::TextureViewDimension::D2, + }, + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 1, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 2, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: wgpu::BufferSize::new( + std::mem::size_of::<[f32; 20]>() as u64 + ), + }, + count: None, + }, + ], + label: create_debug_label!("Color matrix filter binds").as_deref(), + }); + + let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: None, + bind_group_layouts: &[&bind_group_layout], + push_constant_ranges: &[], + }); + + Self { + pipelines: Default::default(), + pipeline_layout, + bind_group_layout, + } + } + + fn pipeline(&self, descriptors: &Descriptors, msaa_sample_count: u32) -> &wgpu::RenderPipeline { + self.pipelines.get_or_init(msaa_sample_count, || { + let label = create_debug_label!("Color Matrix Filter ({} msaa)", msaa_sample_count); + descriptors + .device + .create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: label.as_deref(), + layout: Some(&self.pipeline_layout), + vertex: wgpu::VertexState { + module: &descriptors.shaders.color_matrix_filter, + entry_point: "main_vertex", + buffers: &VERTEX_BUFFERS_DESCRIPTION_FILTERS, + }, + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleList, + strip_index_format: None, + front_face: wgpu::FrontFace::Ccw, + cull_mode: None, + polygon_mode: wgpu::PolygonMode::default(), + unclipped_depth: false, + conservative: false, + }, + depth_stencil: None, + multisample: wgpu::MultisampleState { + count: msaa_sample_count, + mask: !0, + alpha_to_coverage_enabled: false, + }, + fragment: Some(wgpu::FragmentState { + module: &descriptors.shaders.color_matrix_filter, + entry_point: "main_fragment", + targets: &[Some(wgpu::TextureFormat::Rgba8Unorm.into())], + }), + multiview: None, + }) + }) + } + + #[allow(clippy::too_many_arguments)] + pub fn apply( + &self, + descriptors: &Descriptors, + texture_pool: &mut TexturePool, + draw_encoder: &mut wgpu::CommandEncoder, + source_texture: &wgpu::Texture, + source_point: (u32, u32), + source_size: (u32, u32), + filter: &ColorMatrixFilterArgs, + ) -> CommandTarget { + let sample_count = source_texture.sample_count(); + let format = source_texture.format(); + let pipeline = self.pipeline(descriptors, sample_count); + + let target = CommandTarget::new( + descriptors, + texture_pool, + wgpu::Extent3d { + width: source_size.0, + height: source_size.1, + depth_or_array_layers: 1, + }, + format, + sample_count, + RenderTargetMode::FreshWithColor(wgpu::Color::TRANSPARENT), + draw_encoder, + ); + let source_view = source_texture.create_view(&Default::default()); + let buffer = descriptors + .device + .create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: create_debug_label!("Filter arguments").as_deref(), + contents: bytemuck::cast_slice(&filter.matrix), + usage: wgpu::BufferUsages::UNIFORM, + }); + let vertices = create_filter_vertices( + &descriptors.device, + source_texture, + source_point, + source_size, + ); + let filter_group = descriptors + .device + .create_bind_group(&wgpu::BindGroupDescriptor { + label: create_debug_label!("Filter group").as_deref(), + layout: &self.bind_group_layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::TextureView(&source_view), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::Sampler( + descriptors.bitmap_samplers.get_sampler(false, false), + ), + }, + wgpu::BindGroupEntry { + binding: 2, + resource: buffer.as_entire_binding(), + }, + ], + }); + let mut render_pass = draw_encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: create_debug_label!("Color matrix filter").as_deref(), + color_attachments: &[target.color_attachments()], + depth_stencil_attachment: None, + }); + render_pass.set_pipeline(pipeline); + + render_pass.set_bind_group(0, &filter_group, &[]); + + render_pass.set_vertex_buffer(0, vertices.slice(..)); + render_pass.set_index_buffer( + descriptors.quad.indices.slice(..), + wgpu::IndexFormat::Uint32, + ); + render_pass.draw_indexed(0..6, 0, 0..1); + drop(render_pass); + target + } +} diff --git a/render/wgpu/src/layouts.rs b/render/wgpu/src/layouts.rs index 88cb43ec8..bb13d6d09 100644 --- a/render/wgpu/src/layouts.rs +++ b/render/wgpu/src/layouts.rs @@ -9,8 +9,6 @@ pub struct BindLayouts { pub bitmap: wgpu::BindGroupLayout, pub gradient: wgpu::BindGroupLayout, pub blend: wgpu::BindGroupLayout, - pub color_matrix_filter: wgpu::BindGroupLayout, - pub blur_filter: wgpu::BindGroupLayout, } impl BindLayouts { @@ -180,75 +178,6 @@ impl BindLayouts { label: gradient_bind_layout_label.as_deref(), }); - let color_matrix_filter = - device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - entries: &[ - wgpu::BindGroupLayoutEntry { - binding: 0, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Texture { - multisampled: false, - sample_type: wgpu::TextureSampleType::Float { filterable: true }, - view_dimension: wgpu::TextureViewDimension::D2, - }, - count: None, - }, - wgpu::BindGroupLayoutEntry { - binding: 1, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), - count: None, - }, - wgpu::BindGroupLayoutEntry { - binding: 2, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Buffer { - ty: wgpu::BufferBindingType::Uniform, - has_dynamic_offset: false, - min_binding_size: wgpu::BufferSize::new( - std::mem::size_of::<[f32; 20]>() as u64, - ), - }, - count: None, - }, - ], - label: create_debug_label!("Color matrix filter binds").as_deref(), - }); - - let blur_filter = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - entries: &[ - wgpu::BindGroupLayoutEntry { - binding: 0, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Texture { - multisampled: false, - sample_type: wgpu::TextureSampleType::Float { filterable: true }, - view_dimension: wgpu::TextureViewDimension::D2, - }, - count: None, - }, - wgpu::BindGroupLayoutEntry { - binding: 1, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), - count: None, - }, - wgpu::BindGroupLayoutEntry { - binding: 2, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Buffer { - ty: wgpu::BufferBindingType::Uniform, - has_dynamic_offset: false, - min_binding_size: wgpu::BufferSize::new( - std::mem::size_of::<[f32; 4]>() as u64 - ), - }, - count: None, - }, - ], - label: create_debug_label!("Blur filter binds").as_deref(), - }); - Self { globals, transforms, @@ -256,8 +185,6 @@ impl BindLayouts { bitmap, gradient, blend, - color_matrix_filter, - blur_filter, } } } diff --git a/render/wgpu/src/lib.rs b/render/wgpu/src/lib.rs index d2f22331e..6514e3246 100644 --- a/render/wgpu/src/lib.rs +++ b/render/wgpu/src/lib.rs @@ -42,6 +42,7 @@ mod buffer_pool; #[cfg(feature = "clap")] pub mod clap; pub mod descriptors; +mod filters; mod layouts; mod mesh; mod shaders; @@ -136,13 +137,6 @@ impl From for PosColorVertex { } } -#[repr(C)] -#[derive(Copy, Clone, Debug, Pod, Zeroable)] -struct FilterVertex { - position: [f32; 2], - uv: [f32; 2], -} - #[repr(C)] #[derive(Copy, Clone, Debug, Pod, Zeroable)] struct GradientUniforms { diff --git a/render/wgpu/src/pipelines.rs b/render/wgpu/src/pipelines.rs index c1e2a3e34..d656c5f0e 100644 --- a/render/wgpu/src/pipelines.rs +++ b/render/wgpu/src/pipelines.rs @@ -1,7 +1,7 @@ use crate::blend::{ComplexBlend, TrivialBlend}; use crate::layouts::BindLayouts; use crate::shaders::Shaders; -use crate::{FilterVertex, MaskState, PosColorVertex, PosVertex, PushConstants, Transforms}; +use crate::{MaskState, PosColorVertex, PosVertex, PushConstants, Transforms}; use enum_map::{enum_map, Enum, EnumMap}; use std::mem; use wgpu::{vertex_attr_array, BlendState}; @@ -15,16 +15,6 @@ pub const VERTEX_BUFFERS_DESCRIPTION_POS: [wgpu::VertexBufferLayout; 1] = ], }]; -pub const VERTEX_BUFFERS_DESCRIPTION_FILTERS: [wgpu::VertexBufferLayout; 1] = - [wgpu::VertexBufferLayout { - array_stride: std::mem::size_of::() as u64, - step_mode: wgpu::VertexStepMode::Vertex, - attributes: &vertex_attr_array![ - 0 => Float32x2, - 1 => Float32x2, - ], - }]; - pub const VERTEX_BUFFERS_DESCRIPTION_COLOR: [wgpu::VertexBufferLayout; 1] = [wgpu::VertexBufferLayout { array_stride: std::mem::size_of::() as u64, @@ -56,8 +46,6 @@ pub struct Pipelines { pub bitmap: EnumMap, pub gradients: ShapePipeline, pub complex_blends: EnumMap, - pub color_matrix_filter: wgpu::RenderPipeline, - pub blur_filter: wgpu::RenderPipeline, } impl ShapePipeline { @@ -267,81 +255,6 @@ impl Pipelines { msaa_sample_count, )); - let color_matrix_filter_layout = - device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { - label: None, - bind_group_layouts: &[&bind_layouts.color_matrix_filter], - push_constant_ranges: &[], - }); - - let color_matrix_filter = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { - label: create_debug_label!("Color Matrix Filter").as_deref(), - layout: Some(&color_matrix_filter_layout), - vertex: wgpu::VertexState { - module: &shaders.color_matrix_filter, - entry_point: "main_vertex", - buffers: &VERTEX_BUFFERS_DESCRIPTION_FILTERS, - }, - primitive: wgpu::PrimitiveState { - topology: wgpu::PrimitiveTopology::TriangleList, - strip_index_format: None, - front_face: wgpu::FrontFace::Ccw, - cull_mode: None, - polygon_mode: wgpu::PolygonMode::default(), - unclipped_depth: false, - conservative: false, - }, - depth_stencil: None, - multisample: wgpu::MultisampleState { - count: msaa_sample_count, - mask: !0, - alpha_to_coverage_enabled: false, - }, - fragment: Some(wgpu::FragmentState { - module: &shaders.color_matrix_filter, - entry_point: "main_fragment", - targets: &[Some(format.into())], - }), - multiview: None, - }); - - let blur_filter_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { - label: None, - bind_group_layouts: &[&bind_layouts.blur_filter], - push_constant_ranges: &[], - }); - - let blur_filter = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { - label: create_debug_label!("Blur Filter").as_deref(), - layout: Some(&blur_filter_layout), - vertex: wgpu::VertexState { - module: &shaders.blur_filter, - entry_point: "main_vertex", - buffers: &VERTEX_BUFFERS_DESCRIPTION_FILTERS, - }, - primitive: wgpu::PrimitiveState { - topology: wgpu::PrimitiveTopology::TriangleList, - strip_index_format: None, - front_face: wgpu::FrontFace::Ccw, - cull_mode: None, - polygon_mode: wgpu::PolygonMode::default(), - unclipped_depth: false, - conservative: false, - }, - depth_stencil: None, - multisample: wgpu::MultisampleState { - count: msaa_sample_count, - mask: !0, - alpha_to_coverage_enabled: false, - }, - fragment: Some(wgpu::FragmentState { - module: &shaders.blur_filter, - entry_point: "main_fragment", - targets: &[Some(format.into())], - }), - multiview: None, - }); - Self { color: color_pipelines, bitmap: EnumMap::from_array(bitmap_pipelines), @@ -349,8 +262,6 @@ impl Pipelines { bitmap_opaque_dummy_depth, gradients: gradient_pipeline, complex_blends: complex_blend_pipelines, - color_matrix_filter, - blur_filter, } } } diff --git a/render/wgpu/src/surface.rs b/render/wgpu/src/surface.rs index 91d67d8f0..0a98eda5a 100644 --- a/render/wgpu/src/surface.rs +++ b/render/wgpu/src/surface.rs @@ -7,17 +7,13 @@ use crate::buffer_pool::TexturePool; use crate::mesh::Mesh; use crate::surface::commands::{chunk_blends, Chunk, CommandRenderer}; use crate::utils::{remove_srgb, supported_sample_count}; -use crate::{ - ColorAdjustments, Descriptors, FilterVertex, MaskState, Pipelines, Transforms, UniformBuffer, -}; +use crate::{ColorAdjustments, Descriptors, MaskState, Pipelines, Transforms, UniformBuffer}; use ruffle_render::commands::CommandList; use ruffle_render::filters::Filter; use ruffle_render::quality::StageQuality; use std::sync::Arc; -use swf::{BlurFilter, ColorMatrixFilter}; use target::CommandTarget; use tracing::instrument; -use wgpu::util::DeviceExt; use crate::utils::run_copy_pipeline; @@ -351,7 +347,7 @@ impl Surface { filter: Filter, ) -> CommandTarget { let target = match filter { - Filter::ColorMatrixFilter(filter) => self.apply_color_matrix( + Filter::ColorMatrixFilter(filter) => descriptors.filters.color_matrix.apply( descriptors, texture_pool, draw_encoder, @@ -360,7 +356,7 @@ impl Surface { source_size, &filter, ), - Filter::BlurFilter(filter) => self.apply_blur( + Filter::BlurFilter(filter) => descriptors.filters.blur.apply( descriptors, texture_pool, draw_encoder, @@ -372,7 +368,7 @@ impl Surface { _ => { tracing::warn!("Unsupported filter {filter:?}"); // Apply a default color matrix - it's essentially a blit - self.apply_color_matrix( + descriptors.filters.color_matrix.apply( descriptors, texture_pool, draw_encoder, @@ -390,246 +386,4 @@ impl Surface { target.ensure_cleared(draw_encoder); target } - - #[allow(clippy::too_many_arguments)] - pub fn apply_color_matrix( - &self, - descriptors: &Descriptors, - texture_pool: &mut TexturePool, - draw_encoder: &mut wgpu::CommandEncoder, - source_texture: &wgpu::Texture, - source_point: (u32, u32), - source_size: (u32, u32), - filter: &ColorMatrixFilter, - ) -> CommandTarget { - let target = CommandTarget::new( - descriptors, - texture_pool, - wgpu::Extent3d { - width: source_size.0, - height: source_size.1, - depth_or_array_layers: 1, - }, - self.format, - self.sample_count, - RenderTargetMode::FreshWithColor(wgpu::Color::TRANSPARENT), - draw_encoder, - ); - let source_view = source_texture.create_view(&Default::default()); - let buffer = descriptors - .device - .create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: create_debug_label!("Filter arguments").as_deref(), - contents: bytemuck::cast_slice(&filter.matrix), - usage: wgpu::BufferUsages::UNIFORM, - }); - let vertices = create_filter_vertices( - &descriptors.device, - source_texture, - source_point, - source_size, - ); - let filter_group = descriptors - .device - .create_bind_group(&wgpu::BindGroupDescriptor { - label: create_debug_label!("Filter group").as_deref(), - layout: &descriptors.bind_layouts.color_matrix_filter, - entries: &[ - wgpu::BindGroupEntry { - binding: 0, - resource: wgpu::BindingResource::TextureView(&source_view), - }, - wgpu::BindGroupEntry { - binding: 1, - resource: wgpu::BindingResource::Sampler( - descriptors.bitmap_samplers.get_sampler(false, false), - ), - }, - wgpu::BindGroupEntry { - binding: 2, - resource: buffer.as_entire_binding(), - }, - ], - }); - let mut render_pass = draw_encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: create_debug_label!("Color matrix filter").as_deref(), - color_attachments: &[target.color_attachments()], - depth_stencil_attachment: None, - }); - render_pass.set_pipeline(&self.pipelines.color_matrix_filter); - - render_pass.set_bind_group(0, &filter_group, &[]); - - render_pass.set_vertex_buffer(0, vertices.slice(..)); - render_pass.set_index_buffer( - descriptors.quad.indices.slice(..), - wgpu::IndexFormat::Uint32, - ); - render_pass.draw_indexed(0..6, 0, 0..1); - drop(render_pass); - target - } - - #[allow(clippy::too_many_arguments)] - pub fn apply_blur( - &self, - descriptors: &Descriptors, - texture_pool: &mut TexturePool, - draw_encoder: &mut wgpu::CommandEncoder, - source_texture: &wgpu::Texture, - source_point: (u32, u32), - source_size: (u32, u32), - filter: &BlurFilter, - ) -> CommandTarget { - // FIXME - this should be larger than the source texture. Figure out exactly how much larger - let targets = [ - CommandTarget::new( - descriptors, - texture_pool, - wgpu::Extent3d { - width: source_size.0, - height: source_size.1, - depth_or_array_layers: 1, - }, - self.format, - self.sample_count, - RenderTargetMode::FreshWithColor(wgpu::Color::TRANSPARENT), - draw_encoder, - ), - CommandTarget::new( - descriptors, - texture_pool, - wgpu::Extent3d { - width: source_size.0, - height: source_size.1, - depth_or_array_layers: 1, - }, - self.format, - self.sample_count, - RenderTargetMode::FreshWithColor(wgpu::Color::TRANSPARENT), - draw_encoder, - ), - ]; - - // TODO: Vertices should be per pass, and each pass needs diff sizes - let vertices = create_filter_vertices( - &descriptors.device, - source_texture, - source_point, - source_size, - ); - - let source_view = source_texture.create_view(&Default::default()); - for i in 0..2 { - let blur_x = (filter.blur_x.to_f32() - 1.0).max(0.0); - let blur_y = (filter.blur_y.to_f32() - 1.0).max(0.0); - let current = &targets[i % 2]; - let (previous_view, previous_vertices, previous_width, previous_height) = if i == 0 { - ( - &source_view, - vertices.slice(..), - source_texture.width() as f32, - source_texture.height() as f32, - ) - } else { - let previous = &targets[(i - 1) % 2]; - ( - previous.color_view(), - descriptors.quad.filter_vertices.slice(..), - previous.width() as f32, - previous.height() as f32, - ) - }; - let buffer = descriptors - .device - .create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: create_debug_label!("Filter arguments").as_deref(), - contents: bytemuck::cast_slice(&[ - blur_x * ((i as u32) % 2) as f32, - blur_y * (((i as u32) % 2) + 1) as f32, - previous_width, - previous_height, - ]), - usage: wgpu::BufferUsages::UNIFORM, - }); - let filter_group = descriptors - .device - .create_bind_group(&wgpu::BindGroupDescriptor { - label: create_debug_label!("Filter group").as_deref(), - layout: &descriptors.bind_layouts.blur_filter, - entries: &[ - wgpu::BindGroupEntry { - binding: 0, - resource: wgpu::BindingResource::TextureView(previous_view), - }, - wgpu::BindGroupEntry { - binding: 1, - resource: wgpu::BindingResource::Sampler( - descriptors.bitmap_samplers.get_sampler(false, true), - ), - }, - wgpu::BindGroupEntry { - binding: 2, - resource: buffer.as_entire_binding(), - }, - ], - }); - let mut render_pass = draw_encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: create_debug_label!("Blur filter").as_deref(), - color_attachments: &[current.color_attachments()], - depth_stencil_attachment: None, - }); - render_pass.set_pipeline(&self.pipelines.blur_filter); - - render_pass.set_bind_group(0, &filter_group, &[]); - - render_pass.set_vertex_buffer(0, previous_vertices); - render_pass.set_index_buffer( - descriptors.quad.indices.slice(..), - wgpu::IndexFormat::Uint32, - ); - render_pass.draw_indexed(0..6, 0, 0..1); - } - - targets - .into_iter() - .last() - .expect("Targets should not be empty") - } -} - -fn create_filter_vertices( - device: &wgpu::Device, - source_texture: &wgpu::Texture, - source_point: (u32, u32), - source_size: (u32, u32), -) -> wgpu::Buffer { - let source_width = source_texture.width() as f32; - let source_height = source_texture.height() as f32; - let left = source_point.0; - let top = source_point.1; - let right = left + source_size.0; - let bottom = top + source_size.1; - device.create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: create_debug_label!("Filter vertices").as_deref(), - contents: bytemuck::cast_slice(&[ - FilterVertex { - position: [0.0, 0.0], - uv: [left as f32 / source_width, top as f32 / source_height], - }, - FilterVertex { - position: [1.0, 0.0], - uv: [right as f32 / source_width, top as f32 / source_height], - }, - FilterVertex { - position: [1.0, 1.0], - uv: [right as f32 / source_width, bottom as f32 / source_height], - }, - FilterVertex { - position: [0.0, 1.0], - uv: [left as f32 / source_width, bottom as f32 / source_height], - }, - ]), - usage: wgpu::BufferUsages::VERTEX, - }) } diff --git a/render/wgpu/src/utils.rs b/render/wgpu/src/utils.rs index 3a59b4b28..ae8ced8f3 100644 --- a/render/wgpu/src/utils.rs +++ b/render/wgpu/src/utils.rs @@ -288,3 +288,52 @@ pub fn run_copy_pipeline( render_pass.draw_indexed(0..6, 0, 0..1); drop(render_pass); } + +pub struct SampleCountMap { + one: T, + two: T, + four: T, + eight: T, + sixteen: T, +} + +impl Default for SampleCountMap { + fn default() -> Self { + SampleCountMap { + one: Default::default(), + two: Default::default(), + four: Default::default(), + eight: Default::default(), + sixteen: Default::default(), + } + } +} + +impl SampleCountMap { + pub fn get(&self, sample_count: u32) -> &T { + match sample_count { + 1 => &self.one, + 2 => &self.two, + 4 => &self.four, + 8 => &self.eight, + 16 => &self.sixteen, + _ => unreachable!("Sample counts must be powers of two between 1..=16"), + } + } +} + +impl SampleCountMap> { + pub fn get_or_init(&self, sample_count: u32, init: F) -> &T + where + F: FnOnce() -> T, + { + match sample_count { + 1 => self.one.get_or_init(init), + 2 => self.two.get_or_init(init), + 4 => self.four.get_or_init(init), + 8 => self.eight.get_or_init(init), + 16 => self.sixteen.get_or_init(init), + _ => unreachable!("Sample counts must be powers of two between 1..=16"), + } + } +}