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::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(

View File

@ -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<FnvHashMap<wgpu::TextureFormat, Arc<wgpu::RenderPipeline>>>,
copy_srgb_pipeline: Mutex<FnvHashMap<wgpu::TextureFormat, Arc<wgpu::RenderPipeline>>>,
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 {

View File

@ -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,
}
}
}

View File

@ -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,

View File

@ -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,
}
}
}

View File

@ -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 {