Skip to content

Commit

Permalink
wgpu: Implement all blend modes
Browse files Browse the repository at this point in the history
  • Loading branch information
Dinnerbone committed Dec 18, 2022
1 parent dace9f9 commit 54e2d32
Show file tree
Hide file tree
Showing 7 changed files with 222 additions and 19 deletions.
89 changes: 89 additions & 0 deletions render/wgpu/shaders/blend.wgsl
Original file line number Diff line number Diff line change
@@ -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;
}
}
38 changes: 21 additions & 17 deletions render/wgpu/src/commands.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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 {
Expand All @@ -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,
Expand All @@ -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(
Expand Down
34 changes: 34 additions & 0 deletions render/wgpu/src/descriptors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -35,6 +48,7 @@ impl Descriptors {
bitmap_samplers,
bind_layouts,
quad,
blend_buffers,
copy_pipeline: Default::default(),
copy_srgb_pipeline: Default::default(),
shaders,
Expand Down Expand Up @@ -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 {
Expand Down
45 changes: 45 additions & 0 deletions render/wgpu/src/layouts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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: &[
Expand Down Expand Up @@ -107,6 +151,7 @@ impl BindLayouts {
transforms,
bitmap,
gradient,
blend,
}
}
}
24 changes: 23 additions & 1 deletion render/wgpu/src/pipelines.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ pub struct Pipelines {
pub color: ShapePipeline,
pub bitmap: ShapePipeline,
pub gradient: ShapePipeline,
pub blend: ShapePipeline,
}

impl ShapePipeline {
Expand Down Expand Up @@ -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(
Expand All @@ -78,6 +80,7 @@ impl Pipelines {
&bind_layouts.transforms,
&bind_layouts.bitmap,
],
wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING,
);

let gradient_pipelines = create_shape_pipeline(
Expand All @@ -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,
}
}
}
Expand Down Expand Up @@ -145,6 +165,7 @@ fn create_pipeline_descriptor<'a>(
}
}

#[allow(clippy::too_many_arguments)]
fn create_shape_pipeline(
name: &'static str,
device: &wgpu::Device,
Expand All @@ -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 {
Expand Down Expand Up @@ -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,
Expand Down
Loading

0 comments on commit 54e2d32

Please sign in to comment.