wgpu: Implement all blend modes

This commit is contained in:
Nathan Adams 2022-12-18 15:10:07 +01:00
parent f9333e2626
commit 7904c3d4f0
7 changed files with 222 additions and 19 deletions

View File

@ -0,0 +1,89 @@
/// Shader used for drawing a pending framebuffer onto a parent framebuffer
struct VertexOutput {
@builtin(position) position: vec4<f32>,
@location(0) uv: vec2<f32>,
};
struct BlendOptions {
mode: i32,
_padding1: f32,
_padding2: f32,
_padding3: f32,
}
@group(2) @binding(0) var parent_texture: texture_2d<f32>;
@group(2) @binding(1) var current_texture: texture_2d<f32>;
@group(2) @binding(2) var texture_sampler: sampler;
@group(2) @binding(3) var<uniform> blend: BlendOptions;
@vertex
fn main_vertex(in: VertexInput) -> VertexOutput {
let pos = globals.view_matrix * transforms.world_matrix * vec4<f32>(in.position.x, in.position.y, 1.0, 1.0);
let uv = vec2<f32>((pos.x + 1.0) / 2.0, -((pos.y - 1.0) / 2.0));
return VertexOutput(pos, uv);
}
fn blend_func(src: vec3<f32>, dst: vec3<f32>) -> vec3<f32> {
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<f32> {
// dst is the parent pixel we're blending onto - it is either 0 or 1 alpha.
var dst: vec4<f32> = textureSample(parent_texture, texture_sampler, in.uv);
// src is the pixel that we want to apply - it may have any alpha.
var src: vec4<f32> = textureSample(current_texture, texture_sampler, in.uv);
if (src.a > 0.0) {
if (blend.mode == 6) { // Add
return vec4<f32>(src.rgb + dst.rgb, 1.0);
} else if (blend.mode == 7) { // Subtract
return vec4<f32>(dst.rgb - src.rgb, 1.0);
} else if (blend.mode == 9) { // Alpha
return vec4<f32>(dst.rgb * src.a, src.a * dst.a);
} else if (blend.mode == 10) { // Erase
return vec4<f32>(dst.rgb * (1.0 - src.a), (1.0 - src.a) * dst.a);
} else {
return vec4<f32>(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;
}
}

View File

@ -1,8 +1,8 @@
use crate::mesh::{DrawType, Mesh}; use crate::mesh::{DrawType, Mesh};
use crate::surface::{BlendBuffer, DepthBuffer, FrameBuffer, ResolveBuffer, TextureBuffers}; use crate::surface::{BlendBuffer, DepthBuffer, FrameBuffer, ResolveBuffer, TextureBuffers};
use crate::{ use crate::{
as_texture, ColorAdjustments, Descriptors, Globals, MaskState, Pipelines, TextureTransforms, as_texture, ColorAdjustments, Descriptors, Globals, MaskState, Pipelines, Transforms,
Transforms, UniformBuffer, UniformBuffer,
}; };
use ruffle_render::backend::ShapeHandle; use ruffle_render::backend::ShapeHandle;
use ruffle_render::bitmap::BitmapHandle; use ruffle_render::bitmap::BitmapHandle;
@ -226,6 +226,11 @@ impl<'pass, 'frame: 'pass, 'global: 'frame> CommandRenderer<'pass, 'frame, 'glob
mask_state = renderer.mask_state; mask_state = renderer.mask_state;
} }
Chunk::Blend(commands, blend_mode) => { Chunk::Blend(commands, blend_mode) => {
let parent = match blend_mode {
BlendMode::Alpha | BlendMode::Erase => nearest_layer,
_ => target,
};
target.update_blend_buffer(&mut draw_encoder); target.update_blend_buffer(&mut draw_encoder);
let frame_buffer = texture_buffers.take_frame_buffer(&descriptors); let frame_buffer = texture_buffers.take_frame_buffer(&descriptors);
@ -258,25 +263,17 @@ impl<'pass, 'frame: 'pass, 'global: 'frame> CommandRenderer<'pass, 'frame, 'glob
false, false,
); );
let bitmap_bind_group = let blend_bind_group =
descriptors descriptors
.device .device
.create_bind_group(&wgpu::BindGroupDescriptor { .create_bind_group(&wgpu::BindGroupDescriptor {
label: None, label: None,
layout: &descriptors.bind_layouts.bitmap, layout: &descriptors.bind_layouts.blend,
entries: &[ entries: &[
wgpu::BindGroupEntry { wgpu::BindGroupEntry {
binding: 0, binding: 0,
resource: wgpu::BindingResource::Buffer( resource: wgpu::BindingResource::TextureView(
wgpu::BufferBinding { parent.blend_buffer.view(),
buffer: &descriptors.quad.texture_transforms,
offset: 0,
size: wgpu::BufferSize::new(std::mem::size_of::<
TextureTransforms,
>(
)
as u64),
},
), ),
}, },
wgpu::BindGroupEntry { wgpu::BindGroupEntry {
@ -288,11 +285,18 @@ impl<'pass, 'frame: 'pass, 'global: 'frame> CommandRenderer<'pass, 'frame, 'glob
wgpu::BindGroupEntry { wgpu::BindGroupEntry {
binding: 2, binding: 2,
resource: wgpu::BindingResource::Sampler( 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 = let mut render_pass =
draw_encoder.begin_render_pass(&wgpu::RenderPassDescriptor { draw_encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: None, 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(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_vertex_buffer(0, descriptors.quad.vertices.slice(..));
render_pass.set_index_buffer( render_pass.set_index_buffer(

View File

@ -4,6 +4,9 @@ use crate::shaders::Shaders;
use crate::{create_buffer_with_data, BitmapSamplers, Pipelines, TextureTransforms, Vertex}; use crate::{create_buffer_with_data, BitmapSamplers, Pipelines, TextureTransforms, Vertex};
use fnv::FnvHashMap; use fnv::FnvHashMap;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use swf::BlendMode;
const MAX_BLEND_MODES: usize = 13;
pub struct Descriptors { pub struct Descriptors {
pub adapter: wgpu::Adapter, pub adapter: wgpu::Adapter,
@ -13,6 +16,7 @@ pub struct Descriptors {
pub bitmap_samplers: BitmapSamplers, pub bitmap_samplers: BitmapSamplers,
pub bind_layouts: BindLayouts, pub bind_layouts: BindLayouts,
pub quad: Quad, pub quad: Quad,
blend_buffers: [wgpu::Buffer; MAX_BLEND_MODES],
copy_pipeline: Mutex<FnvHashMap<wgpu::TextureFormat, Arc<wgpu::RenderPipeline>>>, copy_pipeline: Mutex<FnvHashMap<wgpu::TextureFormat, Arc<wgpu::RenderPipeline>>>,
copy_srgb_pipeline: Mutex<FnvHashMap<wgpu::TextureFormat, Arc<wgpu::RenderPipeline>>>, copy_srgb_pipeline: Mutex<FnvHashMap<wgpu::TextureFormat, Arc<wgpu::RenderPipeline>>>,
shaders: Shaders, shaders: Shaders,
@ -27,6 +31,15 @@ impl Descriptors {
let shaders = Shaders::new(&device); let shaders = Shaders::new(&device);
let quad = Quad::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 { Self {
adapter, adapter,
device, device,
@ -35,6 +48,7 @@ impl Descriptors {
bitmap_samplers, bitmap_samplers,
bind_layouts, bind_layouts,
quad, quad,
blend_buffers,
copy_pipeline: Default::default(), copy_pipeline: Default::default(),
copy_srgb_pipeline: Default::default(), copy_srgb_pipeline: Default::default(),
shaders, shaders,
@ -182,6 +196,26 @@ impl Descriptors {
}) })
.clone() .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 { pub struct Quad {

View File

@ -4,6 +4,7 @@ pub struct BindLayouts {
pub transforms: wgpu::BindGroupLayout, pub transforms: wgpu::BindGroupLayout,
pub bitmap: wgpu::BindGroupLayout, pub bitmap: wgpu::BindGroupLayout,
pub gradient: wgpu::BindGroupLayout, pub gradient: wgpu::BindGroupLayout,
pub blend: wgpu::BindGroupLayout,
} }
impl BindLayouts { impl BindLayouts {
@ -71,6 +72,49 @@ impl BindLayouts {
label: bitmap_bind_layout_label.as_deref(), 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_bind_layout_label = create_debug_label!("Gradient shape bind group");
let gradient = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { let gradient = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
entries: &[ entries: &[
@ -107,6 +151,7 @@ impl BindLayouts {
transforms, transforms,
bitmap, bitmap,
gradient, gradient,
blend,
} }
} }
} }

View File

@ -23,6 +23,7 @@ pub struct Pipelines {
pub color: ShapePipeline, pub color: ShapePipeline,
pub bitmap: ShapePipeline, pub bitmap: ShapePipeline,
pub gradient: ShapePipeline, pub gradient: ShapePipeline,
pub blend: ShapePipeline,
} }
impl ShapePipeline { impl ShapePipeline {
@ -64,6 +65,7 @@ impl Pipelines {
msaa_sample_count, msaa_sample_count,
&VERTEX_BUFFERS_DESCRIPTION, &VERTEX_BUFFERS_DESCRIPTION,
&[&bind_layouts.globals, &bind_layouts.transforms], &[&bind_layouts.globals, &bind_layouts.transforms],
wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING,
); );
let bitmap_pipelines = create_shape_pipeline( let bitmap_pipelines = create_shape_pipeline(
@ -78,6 +80,7 @@ impl Pipelines {
&bind_layouts.transforms, &bind_layouts.transforms,
&bind_layouts.bitmap, &bind_layouts.bitmap,
], ],
wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING,
); );
let gradient_pipelines = create_shape_pipeline( let gradient_pipelines = create_shape_pipeline(
@ -92,12 +95,29 @@ impl Pipelines {
&bind_layouts.transforms, &bind_layouts.transforms,
&bind_layouts.gradient, &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 { Self {
color: color_pipelines, color: color_pipelines,
bitmap: bitmap_pipelines, bitmap: bitmap_pipelines,
gradient: gradient_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( fn create_shape_pipeline(
name: &'static str, name: &'static str,
device: &wgpu::Device, device: &wgpu::Device,
@ -153,6 +174,7 @@ fn create_shape_pipeline(
msaa_sample_count: u32, msaa_sample_count: u32,
vertex_buffers_layout: &[wgpu::VertexBufferLayout<'_>], vertex_buffers_layout: &[wgpu::VertexBufferLayout<'_>],
bind_group_layouts: &[&wgpu::BindGroupLayout], bind_group_layouts: &[&wgpu::BindGroupLayout],
blend: wgpu::BlendState,
) -> ShapePipeline { ) -> ShapePipeline {
let pipeline_layout_label = create_debug_label!("{} shape pipeline layout", name); let pipeline_layout_label = create_debug_label!("{} shape pipeline layout", name);
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
@ -181,7 +203,7 @@ fn create_shape_pipeline(
}), }),
&[Some(wgpu::ColorTargetState { &[Some(wgpu::ColorTargetState {
format, format,
blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING), blend: Some(blend),
write_mask, write_mask,
})], })],
vertex_buffers_layout, vertex_buffers_layout,

View File

@ -5,6 +5,7 @@ pub struct Shaders {
pub gradient_shader: wgpu::ShaderModule, pub gradient_shader: wgpu::ShaderModule,
pub copy_srgb_shader: wgpu::ShaderModule, pub copy_srgb_shader: wgpu::ShaderModule,
pub copy_shader: wgpu::ShaderModule, pub copy_shader: wgpu::ShaderModule,
pub blend_shader: wgpu::ShaderModule,
} }
impl Shaders { impl Shaders {
@ -23,6 +24,7 @@ impl Shaders {
include_str!("../shaders/copy_srgb.wgsl"), include_str!("../shaders/copy_srgb.wgsl"),
); );
let copy_shader = create_shader(device, "copy", include_str!("../shaders/copy.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 { Self {
color_shader, color_shader,
@ -30,6 +32,7 @@ impl Shaders {
gradient_shader, gradient_shader,
copy_srgb_shader, copy_srgb_shader,
copy_shader, copy_shader,
blend_shader,
} }
} }
} }

View File

@ -98,6 +98,7 @@ impl FrameBuffer {
#[derive(Debug)] #[derive(Debug)]
pub struct BlendBuffer { pub struct BlendBuffer {
texture: wgpu::Texture, texture: wgpu::Texture,
view: wgpu::TextureView,
} }
impl BlendBuffer { impl BlendBuffer {
@ -117,8 +118,13 @@ impl BlendBuffer {
format, format,
usage, 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 { pub fn texture(&self) -> &wgpu::Texture {