From 7904c3d4f0d82924f94b30cce11f80d0305361b9 Mon Sep 17 00:00:00 2001 From: Nathan Adams Date: Sun, 18 Dec 2022 15:10:07 +0100 Subject: [PATCH] wgpu: Implement all blend modes --- render/wgpu/shaders/blend.wgsl | 89 ++++++++++++++++++++++++++++++++++ render/wgpu/src/commands.rs | 38 ++++++++------- render/wgpu/src/descriptors.rs | 34 +++++++++++++ render/wgpu/src/layouts.rs | 45 +++++++++++++++++ render/wgpu/src/pipelines.rs | 24 ++++++++- render/wgpu/src/shaders.rs | 3 ++ render/wgpu/src/surface.rs | 8 ++- 7 files changed, 222 insertions(+), 19 deletions(-) create mode 100644 render/wgpu/shaders/blend.wgsl diff --git a/render/wgpu/shaders/blend.wgsl b/render/wgpu/shaders/blend.wgsl new file mode 100644 index 000000000..4f5fba328 --- /dev/null +++ b/render/wgpu/shaders/blend.wgsl @@ -0,0 +1,89 @@ +/// Shader used for drawing a pending framebuffer onto a parent framebuffer + +struct VertexOutput { + @builtin(position) position: vec4, + @location(0) uv: vec2, +}; + +struct BlendOptions { + mode: i32, + _padding1: f32, + _padding2: f32, + _padding3: f32, +} + +@group(2) @binding(0) var parent_texture: texture_2d; +@group(2) @binding(1) var current_texture: texture_2d; +@group(2) @binding(2) var texture_sampler: sampler; +@group(2) @binding(3) var blend: BlendOptions; + +@vertex +fn main_vertex(in: VertexInput) -> VertexOutput { + let pos = globals.view_matrix * transforms.world_matrix * vec4(in.position.x, in.position.y, 1.0, 1.0); + let uv = vec2((pos.x + 1.0) / 2.0, -((pos.y - 1.0) / 2.0)); + return VertexOutput(pos, uv); +} + +fn blend_func(src: vec3, dst: vec3) -> vec3 { + switch (blend.mode) { + default: { + return src; + } + case 1: { // Multiply + return src * dst; + } + case 2: { // Screen + return (dst + src) - (dst * src); + } + case 3: { // Lighten + return max(dst, src); + } + case 4: { // Darken + return min(dst, src); + } + case 5: { // Difference + return abs(dst - src); + } + case 8: { // Invert + return 1.0 - dst; + } + case 11: { // Overlay + var out = src; + if (dst.r <= 0.5) { out.r = (2.0 * src.r * dst.r); } else { out.r = (1.0 - 2.0 * (1.0 - dst.r) * (1.0 - src.r)); } + if (dst.g <= 0.5) { out.g = (2.0 * src.g * dst.g); } else { out.g = (1.0 - 2.0 * (1.0 - dst.g) * (1.0 - src.g)); } + if (dst.b <= 0.5) { out.b = (2.0 * src.b * dst.b); } else { out.b = (1.0 - 2.0 * (1.0 - dst.b) * (1.0 - src.b)); } + return out; + } + case 12: { // Hardlight + var out = src; + if (src.r <= 0.5) { out.r = (2.0 * src.r * dst.r); } else { out.r = (1.0 - 2.0 * (1.0 - dst.r) * (1.0 - src.r)); } + if (src.g <= 0.5) { out.g = (2.0 * src.g * dst.g); } else { out.g = (1.0 - 2.0 * (1.0 - dst.g) * (1.0 - src.g)); } + if (src.b <= 0.5) { out.b = (2.0 * src.b * dst.b); } else { out.b = (1.0 - 2.0 * (1.0 - dst.b) * (1.0 - src.b)); } + return out; + } + } +} + +@fragment +fn main_fragment(in: VertexOutput) -> @location(0) vec4 { + // dst is the parent pixel we're blending onto - it is either 0 or 1 alpha. + var dst: vec4 = textureSample(parent_texture, texture_sampler, in.uv); + // src is the pixel that we want to apply - it may have any alpha. + var src: vec4 = textureSample(current_texture, texture_sampler, in.uv); + + if (src.a > 0.0) { + if (blend.mode == 6) { // Add + return vec4(src.rgb + dst.rgb, 1.0); + } else if (blend.mode == 7) { // Subtract + return vec4(dst.rgb - src.rgb, 1.0); + } else if (blend.mode == 9) { // Alpha + return vec4(dst.rgb * src.a, src.a * dst.a); + } else if (blend.mode == 10) { // Erase + return vec4(dst.rgb * (1.0 - src.a), (1.0 - src.a) * dst.a); + } else { + return vec4(src.rgb * (1.0 - dst.a) + dst.rgb * (1.0 - src.a) + src.a * dst.a * blend_func(src.rgb / src.a, dst.rgb/ dst.a), src.a + dst.a * (1.0 - src.a)); + } + } else { + return dst; + } +} diff --git a/render/wgpu/src/commands.rs b/render/wgpu/src/commands.rs index fe830204b..16480b9da 100644 --- a/render/wgpu/src/commands.rs +++ b/render/wgpu/src/commands.rs @@ -1,8 +1,8 @@ use crate::mesh::{DrawType, Mesh}; use crate::surface::{BlendBuffer, DepthBuffer, FrameBuffer, ResolveBuffer, TextureBuffers}; use crate::{ - as_texture, ColorAdjustments, Descriptors, Globals, MaskState, Pipelines, TextureTransforms, - Transforms, UniformBuffer, + as_texture, ColorAdjustments, Descriptors, Globals, MaskState, Pipelines, Transforms, + UniformBuffer, }; use ruffle_render::backend::ShapeHandle; use ruffle_render::bitmap::BitmapHandle; @@ -226,6 +226,11 @@ impl<'pass, 'frame: 'pass, 'global: 'frame> CommandRenderer<'pass, 'frame, 'glob mask_state = renderer.mask_state; } Chunk::Blend(commands, blend_mode) => { + let parent = match blend_mode { + BlendMode::Alpha | BlendMode::Erase => nearest_layer, + _ => target, + }; + target.update_blend_buffer(&mut draw_encoder); let frame_buffer = texture_buffers.take_frame_buffer(&descriptors); @@ -258,25 +263,17 @@ impl<'pass, 'frame: 'pass, 'global: 'frame> CommandRenderer<'pass, 'frame, 'glob false, ); - let bitmap_bind_group = + let blend_bind_group = descriptors .device .create_bind_group(&wgpu::BindGroupDescriptor { label: None, - layout: &descriptors.bind_layouts.bitmap, + layout: &descriptors.bind_layouts.blend, entries: &[ wgpu::BindGroupEntry { binding: 0, - resource: wgpu::BindingResource::Buffer( - wgpu::BufferBinding { - buffer: &descriptors.quad.texture_transforms, - offset: 0, - size: wgpu::BufferSize::new(std::mem::size_of::< - TextureTransforms, - >( - ) - as u64), - }, + resource: wgpu::BindingResource::TextureView( + parent.blend_buffer.view(), ), }, wgpu::BindGroupEntry { @@ -288,11 +285,18 @@ impl<'pass, 'frame: 'pass, 'global: 'frame> CommandRenderer<'pass, 'frame, 'glob wgpu::BindGroupEntry { binding: 2, resource: wgpu::BindingResource::Sampler( - &descriptors.bitmap_samplers.get_sampler(false, false), + descriptors.bitmap_samplers.get_sampler(false, false), ), }, + wgpu::BindGroupEntry { + binding: 3, + resource: descriptors + .blend_buffer(blend_mode) + .as_entire_binding(), + }, ], }); + let mut render_pass = draw_encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: None, @@ -314,10 +318,10 @@ impl<'pass, 'frame: 'pass, 'global: 'frame> CommandRenderer<'pass, 'frame, 'glob } } - render_pass.set_pipeline(pipelines.bitmap.pipeline_for(mask_state)); + render_pass.set_pipeline(pipelines.blend.pipeline_for(mask_state)); render_pass.set_bind_group(1, texture_buffers.whole_frame_bind_group(), &[0]); - render_pass.set_bind_group(2, &bitmap_bind_group, &[]); + render_pass.set_bind_group(2, &blend_bind_group, &[]); render_pass.set_vertex_buffer(0, descriptors.quad.vertices.slice(..)); render_pass.set_index_buffer( diff --git a/render/wgpu/src/descriptors.rs b/render/wgpu/src/descriptors.rs index 7db421ebd..03e3082b6 100644 --- a/render/wgpu/src/descriptors.rs +++ b/render/wgpu/src/descriptors.rs @@ -4,6 +4,9 @@ use crate::shaders::Shaders; use crate::{create_buffer_with_data, BitmapSamplers, Pipelines, TextureTransforms, Vertex}; use fnv::FnvHashMap; use std::sync::{Arc, Mutex}; +use swf::BlendMode; + +const MAX_BLEND_MODES: usize = 13; pub struct Descriptors { pub adapter: wgpu::Adapter, @@ -13,6 +16,7 @@ pub struct Descriptors { pub bitmap_samplers: BitmapSamplers, pub bind_layouts: BindLayouts, pub quad: Quad, + blend_buffers: [wgpu::Buffer; MAX_BLEND_MODES], copy_pipeline: Mutex>>, copy_srgb_pipeline: Mutex>>, shaders: Shaders, @@ -27,6 +31,15 @@ impl Descriptors { let shaders = Shaders::new(&device); let quad = Quad::new(&device); + let blend_buffers = core::array::from_fn(|blend_id| { + create_buffer_with_data( + &device, + bytemuck::cast_slice(&[blend_id, 0, 0, 0]), + wgpu::BufferUsages::UNIFORM, + create_debug_label!("Blend mode {:?}", blend_id), + ) + }); + Self { adapter, device, @@ -35,6 +48,7 @@ impl Descriptors { bitmap_samplers, bind_layouts, quad, + blend_buffers, copy_pipeline: Default::default(), copy_srgb_pipeline: Default::default(), shaders, @@ -182,6 +196,26 @@ impl Descriptors { }) .clone() } + + pub fn blend_buffer(&self, blend_mode: BlendMode) -> &wgpu::Buffer { + let index = match blend_mode { + BlendMode::Normal => 0, + BlendMode::Layer => 0, + BlendMode::Multiply => 1, + BlendMode::Screen => 2, + BlendMode::Lighten => 3, + BlendMode::Darken => 4, + BlendMode::Difference => 5, + BlendMode::Add => 6, + BlendMode::Subtract => 7, + BlendMode::Invert => 8, + BlendMode::Alpha => 9, + BlendMode::Erase => 10, + BlendMode::Overlay => 11, + BlendMode::HardLight => 12, + }; + &self.blend_buffers[index] + } } pub struct Quad { diff --git a/render/wgpu/src/layouts.rs b/render/wgpu/src/layouts.rs index 4be3247bc..caa412c43 100644 --- a/render/wgpu/src/layouts.rs +++ b/render/wgpu/src/layouts.rs @@ -4,6 +4,7 @@ pub struct BindLayouts { pub transforms: wgpu::BindGroupLayout, pub bitmap: wgpu::BindGroupLayout, pub gradient: wgpu::BindGroupLayout, + pub blend: wgpu::BindGroupLayout, } impl BindLayouts { @@ -71,6 +72,49 @@ impl BindLayouts { label: bitmap_bind_layout_label.as_deref(), }); + let blend_bind_layout_label = create_debug_label!("Blend bind group layout"); + let blend = 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: false }, + view_dimension: wgpu::TextureViewDimension::D2, + }, + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 1, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Texture { + multisampled: false, + sample_type: wgpu::TextureSampleType::Float { filterable: false }, + view_dimension: wgpu::TextureViewDimension::D2, + }, + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 2, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::NonFiltering), + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 3, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }, + ], + label: blend_bind_layout_label.as_deref(), + }); + let gradient_bind_layout_label = create_debug_label!("Gradient shape bind group"); let gradient = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { entries: &[ @@ -107,6 +151,7 @@ impl BindLayouts { transforms, bitmap, gradient, + blend, } } } diff --git a/render/wgpu/src/pipelines.rs b/render/wgpu/src/pipelines.rs index 6227261e0..4389abf2a 100644 --- a/render/wgpu/src/pipelines.rs +++ b/render/wgpu/src/pipelines.rs @@ -23,6 +23,7 @@ pub struct Pipelines { pub color: ShapePipeline, pub bitmap: ShapePipeline, pub gradient: ShapePipeline, + pub blend: ShapePipeline, } impl ShapePipeline { @@ -64,6 +65,7 @@ impl Pipelines { msaa_sample_count, &VERTEX_BUFFERS_DESCRIPTION, &[&bind_layouts.globals, &bind_layouts.transforms], + wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING, ); let bitmap_pipelines = create_shape_pipeline( @@ -78,6 +80,7 @@ impl Pipelines { &bind_layouts.transforms, &bind_layouts.bitmap, ], + wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING, ); let gradient_pipelines = create_shape_pipeline( @@ -92,12 +95,29 @@ impl Pipelines { &bind_layouts.transforms, &bind_layouts.gradient, ], + wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING, + ); + + let blend_pipeline = create_shape_pipeline( + "Blend", + device, + format, + &shaders.blend_shader, + msaa_sample_count, + &VERTEX_BUFFERS_DESCRIPTION, + &[ + &bind_layouts.globals, + &bind_layouts.transforms, + &bind_layouts.blend, + ], + wgpu::BlendState::REPLACE, ); Self { color: color_pipelines, bitmap: bitmap_pipelines, gradient: gradient_pipelines, + blend: blend_pipeline, } } } @@ -145,6 +165,7 @@ fn create_pipeline_descriptor<'a>( } } +#[allow(clippy::too_many_arguments)] fn create_shape_pipeline( name: &'static str, device: &wgpu::Device, @@ -153,6 +174,7 @@ fn create_shape_pipeline( msaa_sample_count: u32, vertex_buffers_layout: &[wgpu::VertexBufferLayout<'_>], bind_group_layouts: &[&wgpu::BindGroupLayout], + blend: wgpu::BlendState, ) -> ShapePipeline { let pipeline_layout_label = create_debug_label!("{} shape pipeline layout", name); let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { @@ -181,7 +203,7 @@ fn create_shape_pipeline( }), &[Some(wgpu::ColorTargetState { format, - blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING), + blend: Some(blend), write_mask, })], vertex_buffers_layout, diff --git a/render/wgpu/src/shaders.rs b/render/wgpu/src/shaders.rs index c2a3e447b..79cdadb97 100644 --- a/render/wgpu/src/shaders.rs +++ b/render/wgpu/src/shaders.rs @@ -5,6 +5,7 @@ pub struct Shaders { pub gradient_shader: wgpu::ShaderModule, pub copy_srgb_shader: wgpu::ShaderModule, pub copy_shader: wgpu::ShaderModule, + pub blend_shader: wgpu::ShaderModule, } impl Shaders { @@ -23,6 +24,7 @@ impl Shaders { include_str!("../shaders/copy_srgb.wgsl"), ); let copy_shader = create_shader(device, "copy", include_str!("../shaders/copy.wgsl")); + let blend_shader = create_shader(device, "blend", include_str!("../shaders/blend.wgsl")); Self { color_shader, @@ -30,6 +32,7 @@ impl Shaders { gradient_shader, copy_srgb_shader, copy_shader, + blend_shader, } } } diff --git a/render/wgpu/src/surface.rs b/render/wgpu/src/surface.rs index 1f84167fd..df3d3c278 100644 --- a/render/wgpu/src/surface.rs +++ b/render/wgpu/src/surface.rs @@ -98,6 +98,7 @@ impl FrameBuffer { #[derive(Debug)] pub struct BlendBuffer { texture: wgpu::Texture, + view: wgpu::TextureView, } impl BlendBuffer { @@ -117,8 +118,13 @@ impl BlendBuffer { format, usage, }); + let view = texture.create_view(&Default::default()); - Self { texture } + Self { texture, view } + } + + pub fn view(&self) -> &wgpu::TextureView { + &self.view } pub fn texture(&self) -> &wgpu::Texture {