wgpu: Refactor out filters into their own files

This commit is contained in:
Nathan Adams 2023-06-27 21:47:47 +02:00
parent ad8457b54d
commit 4f6bac7840
9 changed files with 556 additions and 422 deletions

View File

@ -1,8 +1,9 @@
use crate::filters::{FilterVertex, Filters};
use crate::layouts::BindLayouts; use crate::layouts::BindLayouts;
use crate::pipelines::VERTEX_BUFFERS_DESCRIPTION_POS; use crate::pipelines::VERTEX_BUFFERS_DESCRIPTION_POS;
use crate::shaders::Shaders; use crate::shaders::Shaders;
use crate::{ use crate::{
create_buffer_with_data, BitmapSamplers, FilterVertex, Pipelines, PosColorVertex, PosVertex, create_buffer_with_data, BitmapSamplers, Pipelines, PosColorVertex, PosVertex,
TextureTransforms, Transforms, DEFAULT_COLOR_ADJUSTMENTS, TextureTransforms, Transforms, DEFAULT_COLOR_ADJUSTMENTS,
}; };
use fnv::FnvHashMap; use fnv::FnvHashMap;
@ -20,9 +21,10 @@ pub struct Descriptors {
pub quad: Quad, pub quad: Quad,
copy_pipeline: Mutex<FnvHashMap<(u32, wgpu::TextureFormat), Arc<wgpu::RenderPipeline>>>, copy_pipeline: Mutex<FnvHashMap<(u32, wgpu::TextureFormat), Arc<wgpu::RenderPipeline>>>,
copy_srgb_pipeline: Mutex<FnvHashMap<(u32, wgpu::TextureFormat), Arc<wgpu::RenderPipeline>>>, copy_srgb_pipeline: Mutex<FnvHashMap<(u32, wgpu::TextureFormat), Arc<wgpu::RenderPipeline>>>,
shaders: Shaders, pub shaders: Shaders,
pipelines: Mutex<FnvHashMap<(u32, wgpu::TextureFormat), Arc<Pipelines>>>, pipelines: Mutex<FnvHashMap<(u32, wgpu::TextureFormat), Arc<Pipelines>>>,
pub default_color_bind_group: wgpu::BindGroup, pub default_color_bind_group: wgpu::BindGroup,
pub filters: Filters,
} }
impl Debug for Descriptors { impl Debug for Descriptors {
@ -52,6 +54,7 @@ impl Descriptors {
resource: default_color_transform.as_entire_binding(), resource: default_color_transform.as_entire_binding(),
}], }],
}); });
let filters = Filters::new(&device);
Self { Self {
adapter, adapter,
@ -66,6 +69,7 @@ impl Descriptors {
shaders, shaders,
pipelines: Default::default(), pipelines: Default::default(),
default_color_bind_group, default_color_bind_group,
filters,
} }
} }

View File

@ -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::<FilterVertex>() 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,
})
}

View File

@ -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<OnceLock<wgpu::RenderPipeline>>,
}
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")
}
}

View File

@ -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<OnceLock<wgpu::RenderPipeline>>,
}
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
}
}

View File

@ -9,8 +9,6 @@ pub struct BindLayouts {
pub bitmap: wgpu::BindGroupLayout, pub bitmap: wgpu::BindGroupLayout,
pub gradient: wgpu::BindGroupLayout, pub gradient: wgpu::BindGroupLayout,
pub blend: wgpu::BindGroupLayout, pub blend: wgpu::BindGroupLayout,
pub color_matrix_filter: wgpu::BindGroupLayout,
pub blur_filter: wgpu::BindGroupLayout,
} }
impl BindLayouts { impl BindLayouts {
@ -180,75 +178,6 @@ impl BindLayouts {
label: gradient_bind_layout_label.as_deref(), 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 { Self {
globals, globals,
transforms, transforms,
@ -256,8 +185,6 @@ impl BindLayouts {
bitmap, bitmap,
gradient, gradient,
blend, blend,
color_matrix_filter,
blur_filter,
} }
} }
} }

View File

@ -42,6 +42,7 @@ mod buffer_pool;
#[cfg(feature = "clap")] #[cfg(feature = "clap")]
pub mod clap; pub mod clap;
pub mod descriptors; pub mod descriptors;
mod filters;
mod layouts; mod layouts;
mod mesh; mod mesh;
mod shaders; mod shaders;
@ -136,13 +137,6 @@ impl From<TessVertex> for PosColorVertex {
} }
} }
#[repr(C)]
#[derive(Copy, Clone, Debug, Pod, Zeroable)]
struct FilterVertex {
position: [f32; 2],
uv: [f32; 2],
}
#[repr(C)] #[repr(C)]
#[derive(Copy, Clone, Debug, Pod, Zeroable)] #[derive(Copy, Clone, Debug, Pod, Zeroable)]
struct GradientUniforms { struct GradientUniforms {

View File

@ -1,7 +1,7 @@
use crate::blend::{ComplexBlend, TrivialBlend}; use crate::blend::{ComplexBlend, TrivialBlend};
use crate::layouts::BindLayouts; use crate::layouts::BindLayouts;
use crate::shaders::Shaders; 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 enum_map::{enum_map, Enum, EnumMap};
use std::mem; use std::mem;
use wgpu::{vertex_attr_array, BlendState}; 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::<FilterVertex>() as u64,
step_mode: wgpu::VertexStepMode::Vertex,
attributes: &vertex_attr_array![
0 => Float32x2,
1 => Float32x2,
],
}];
pub const VERTEX_BUFFERS_DESCRIPTION_COLOR: [wgpu::VertexBufferLayout; 1] = pub const VERTEX_BUFFERS_DESCRIPTION_COLOR: [wgpu::VertexBufferLayout; 1] =
[wgpu::VertexBufferLayout { [wgpu::VertexBufferLayout {
array_stride: std::mem::size_of::<PosColorVertex>() as u64, array_stride: std::mem::size_of::<PosColorVertex>() as u64,
@ -56,8 +46,6 @@ pub struct Pipelines {
pub bitmap: EnumMap<TrivialBlend, ShapePipeline>, pub bitmap: EnumMap<TrivialBlend, ShapePipeline>,
pub gradients: ShapePipeline, pub gradients: ShapePipeline,
pub complex_blends: EnumMap<ComplexBlend, ShapePipeline>, pub complex_blends: EnumMap<ComplexBlend, ShapePipeline>,
pub color_matrix_filter: wgpu::RenderPipeline,
pub blur_filter: wgpu::RenderPipeline,
} }
impl ShapePipeline { impl ShapePipeline {
@ -267,81 +255,6 @@ impl Pipelines {
msaa_sample_count, 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 { Self {
color: color_pipelines, color: color_pipelines,
bitmap: EnumMap::from_array(bitmap_pipelines), bitmap: EnumMap::from_array(bitmap_pipelines),
@ -349,8 +262,6 @@ impl Pipelines {
bitmap_opaque_dummy_depth, bitmap_opaque_dummy_depth,
gradients: gradient_pipeline, gradients: gradient_pipeline,
complex_blends: complex_blend_pipelines, complex_blends: complex_blend_pipelines,
color_matrix_filter,
blur_filter,
} }
} }
} }

View File

@ -7,17 +7,13 @@ use crate::buffer_pool::TexturePool;
use crate::mesh::Mesh; use crate::mesh::Mesh;
use crate::surface::commands::{chunk_blends, Chunk, CommandRenderer}; use crate::surface::commands::{chunk_blends, Chunk, CommandRenderer};
use crate::utils::{remove_srgb, supported_sample_count}; use crate::utils::{remove_srgb, supported_sample_count};
use crate::{ use crate::{ColorAdjustments, Descriptors, MaskState, Pipelines, Transforms, UniformBuffer};
ColorAdjustments, Descriptors, FilterVertex, MaskState, Pipelines, Transforms, UniformBuffer,
};
use ruffle_render::commands::CommandList; use ruffle_render::commands::CommandList;
use ruffle_render::filters::Filter; use ruffle_render::filters::Filter;
use ruffle_render::quality::StageQuality; use ruffle_render::quality::StageQuality;
use std::sync::Arc; use std::sync::Arc;
use swf::{BlurFilter, ColorMatrixFilter};
use target::CommandTarget; use target::CommandTarget;
use tracing::instrument; use tracing::instrument;
use wgpu::util::DeviceExt;
use crate::utils::run_copy_pipeline; use crate::utils::run_copy_pipeline;
@ -351,7 +347,7 @@ impl Surface {
filter: Filter, filter: Filter,
) -> CommandTarget { ) -> CommandTarget {
let target = match filter { let target = match filter {
Filter::ColorMatrixFilter(filter) => self.apply_color_matrix( Filter::ColorMatrixFilter(filter) => descriptors.filters.color_matrix.apply(
descriptors, descriptors,
texture_pool, texture_pool,
draw_encoder, draw_encoder,
@ -360,7 +356,7 @@ impl Surface {
source_size, source_size,
&filter, &filter,
), ),
Filter::BlurFilter(filter) => self.apply_blur( Filter::BlurFilter(filter) => descriptors.filters.blur.apply(
descriptors, descriptors,
texture_pool, texture_pool,
draw_encoder, draw_encoder,
@ -372,7 +368,7 @@ impl Surface {
_ => { _ => {
tracing::warn!("Unsupported filter {filter:?}"); tracing::warn!("Unsupported filter {filter:?}");
// Apply a default color matrix - it's essentially a blit // Apply a default color matrix - it's essentially a blit
self.apply_color_matrix( descriptors.filters.color_matrix.apply(
descriptors, descriptors,
texture_pool, texture_pool,
draw_encoder, draw_encoder,
@ -390,246 +386,4 @@ impl Surface {
target.ensure_cleared(draw_encoder); target.ensure_cleared(draw_encoder);
target 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,
})
} }

View File

@ -288,3 +288,52 @@ pub fn run_copy_pipeline(
render_pass.draw_indexed(0..6, 0, 0..1); render_pass.draw_indexed(0..6, 0, 0..1);
drop(render_pass); drop(render_pass);
} }
pub struct SampleCountMap<T> {
one: T,
two: T,
four: T,
eight: T,
sixteen: T,
}
impl<T: Default> Default for SampleCountMap<T> {
fn default() -> Self {
SampleCountMap {
one: Default::default(),
two: Default::default(),
four: Default::default(),
eight: Default::default(),
sixteen: Default::default(),
}
}
}
impl<T> SampleCountMap<T> {
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<T> SampleCountMap<std::sync::OnceLock<T>> {
pub fn get_or_init<F>(&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"),
}
}
}