ruffle/render/wgpu/src/pipelines.rs

413 lines
14 KiB
Rust

use crate::blend::{ComplexBlend, TrivialBlend};
use crate::layouts::BindLayouts;
use crate::shaders::Shaders;
use crate::{MaskState, PosColorVertex, PosVertex};
use enum_map::{enum_map, Enum, EnumMap};
use std::collections::HashMap;
use wgpu::{vertex_attr_array, BlendState, PrimitiveTopology};
pub const VERTEX_BUFFERS_DESCRIPTION_POS: [wgpu::VertexBufferLayout; 1] =
[wgpu::VertexBufferLayout {
array_stride: std::mem::size_of::<PosVertex>() as u64,
step_mode: wgpu::VertexStepMode::Vertex,
attributes: &vertex_attr_array![
0 => Float32x2,
],
}];
pub const VERTEX_BUFFERS_DESCRIPTION_COLOR: [wgpu::VertexBufferLayout; 1] =
[wgpu::VertexBufferLayout {
array_stride: std::mem::size_of::<PosColorVertex>() as u64,
step_mode: wgpu::VertexStepMode::Vertex,
attributes: &vertex_attr_array![
0 => Float32x2,
1 => Float32x4,
],
}];
#[derive(Debug)]
pub struct ShapePipeline {
pub pipelines: EnumMap<MaskState, wgpu::RenderPipeline>,
stencilless: wgpu::RenderPipeline,
}
#[derive(Debug)]
pub struct Pipelines {
pub color: ShapePipeline,
pub lines: ShapePipeline,
/// Renders a bitmap without any blending, and does
/// not write to the alpha channel. This is used for
/// drawing a finished Stage3D buffer onto the background.
pub bitmap_opaque: wgpu::RenderPipeline,
/// Like `bitmap_opaque`, but with a no-op `DepthStencilState`.
/// This is used when we're inside a `RenderPass` that is
/// using a stencil buffer, but we don't want to write to it
/// or use it in any way.
pub bitmap_opaque_dummy_stencil: wgpu::RenderPipeline,
pub bitmap: EnumMap<TrivialBlend, ShapePipeline>,
pub gradients: ShapePipeline,
pub complex_blends: EnumMap<ComplexBlend, ShapePipeline>,
}
impl ShapePipeline {
pub fn pipeline_for(&self, mask_state: MaskState) -> &wgpu::RenderPipeline {
&self.pipelines[mask_state]
}
pub fn stencilless_pipeline(&self) -> &wgpu::RenderPipeline {
&self.stencilless
}
/// Builds of a nested `EnumMap` that maps a `MaskState` to
/// a `RenderPipeline`. The provided callback is used to construct the `RenderPipeline`
/// for each possible `MaskState`.
fn build(
stencilless: wgpu::RenderPipeline,
mut f: impl FnMut(MaskState) -> wgpu::RenderPipeline,
) -> Self {
let mask_array: [wgpu::RenderPipeline; MaskState::LENGTH] = (0..MaskState::LENGTH)
.map(|mask_enum| {
let mask_state = MaskState::from_usize(mask_enum);
f(mask_state)
})
.collect::<Vec<_>>()
.try_into()
.unwrap();
ShapePipeline {
pipelines: EnumMap::from_array(mask_array),
stencilless,
}
}
}
impl Pipelines {
pub fn new(
device: &wgpu::Device,
shaders: &Shaders,
format: wgpu::TextureFormat,
msaa_sample_count: u32,
bind_layouts: &BindLayouts,
) -> Self {
let colort_bindings = vec![&bind_layouts.globals, &bind_layouts.transforms];
let color_pipelines = create_shape_pipeline(
"Color",
device,
format,
&shaders.color_shader,
msaa_sample_count,
&VERTEX_BUFFERS_DESCRIPTION_COLOR,
&colort_bindings,
BlendState::PREMULTIPLIED_ALPHA_BLENDING,
&[],
PrimitiveTopology::TriangleList,
);
let lines_pipelines = create_shape_pipeline(
"Lines",
device,
format,
&shaders.color_shader,
msaa_sample_count,
&VERTEX_BUFFERS_DESCRIPTION_COLOR,
&colort_bindings,
BlendState::PREMULTIPLIED_ALPHA_BLENDING,
&[],
PrimitiveTopology::LineStrip,
);
let gradient_bindings = vec![
&bind_layouts.globals,
&bind_layouts.transforms,
&bind_layouts.gradient,
];
let gradient_pipeline = create_shape_pipeline(
"Gradient",
device,
format,
&shaders.gradient_shader,
msaa_sample_count,
&VERTEX_BUFFERS_DESCRIPTION_POS,
&gradient_bindings,
BlendState::PREMULTIPLIED_ALPHA_BLENDING,
&[],
PrimitiveTopology::TriangleList,
);
let complex_blend_bindings = vec![
&bind_layouts.globals,
&bind_layouts.transforms,
&bind_layouts.blend,
];
let complex_blend_pipelines = enum_map! {
blend => create_shape_pipeline(
&format!("Complex Blend: {blend:?}"),
device,
format,
&shaders.blend_shaders[blend],
msaa_sample_count,
&VERTEX_BUFFERS_DESCRIPTION_POS,
&complex_blend_bindings,
BlendState::REPLACE,
&[],
PrimitiveTopology::TriangleList,
)
};
let bitmap_blend_bindings = vec![
&bind_layouts.globals,
&bind_layouts.transforms,
&bind_layouts.bitmap,
];
let bitmap_pipelines: [ShapePipeline; TrivialBlend::LENGTH] = (0..TrivialBlend::LENGTH)
.map(|blend| {
let blend = TrivialBlend::from_usize(blend);
let name = format!("Bitmap ({blend:?})");
create_shape_pipeline(
&name,
device,
format,
&shaders.bitmap_shader,
msaa_sample_count,
&VERTEX_BUFFERS_DESCRIPTION_POS,
&bitmap_blend_bindings,
blend.blend_state(),
&[],
PrimitiveTopology::TriangleList,
)
})
.collect::<Vec<_>>()
.try_into()
.unwrap();
let bitmap_opaque_pipeline_layout_label =
create_debug_label!("Opaque bitmap pipeline layout");
let bitmap_opaque_pipeline_layout =
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: bitmap_opaque_pipeline_layout_label.as_deref(),
bind_group_layouts: &bitmap_blend_bindings,
push_constant_ranges: &[],
});
let bitmap_opaque = device.create_render_pipeline(&create_pipeline_descriptor(
create_debug_label!("Bitmap opaque copy").as_deref(),
&shaders.bitmap_shader,
&shaders.bitmap_shader,
&bitmap_opaque_pipeline_layout,
None,
&[Some(wgpu::ColorTargetState {
format,
blend: Some(BlendState::REPLACE),
write_mask: wgpu::ColorWrites::COLOR,
})],
&VERTEX_BUFFERS_DESCRIPTION_POS,
msaa_sample_count,
&[("late_saturate".to_owned(), 1.0)].into(),
PrimitiveTopology::TriangleList,
));
let bitmap_opaque_dummy_depth = device.create_render_pipeline(&create_pipeline_descriptor(
create_debug_label!("Bitmap opaque copy").as_deref(),
&shaders.bitmap_shader,
&shaders.bitmap_shader,
&bitmap_opaque_pipeline_layout,
Some(wgpu::DepthStencilState {
format: wgpu::TextureFormat::Stencil8,
depth_write_enabled: false,
depth_compare: wgpu::CompareFunction::Always,
stencil: wgpu::StencilState {
front: wgpu::StencilFaceState::IGNORE,
back: wgpu::StencilFaceState::IGNORE,
read_mask: 0,
write_mask: 0,
},
bias: wgpu::DepthBiasState::default(),
}),
&[Some(wgpu::ColorTargetState {
format,
blend: Some(BlendState::REPLACE),
write_mask: wgpu::ColorWrites::COLOR,
})],
&VERTEX_BUFFERS_DESCRIPTION_POS,
msaa_sample_count,
&Default::default(),
PrimitiveTopology::TriangleList,
));
Self {
color: color_pipelines,
lines: lines_pipelines,
bitmap: EnumMap::from_array(bitmap_pipelines),
bitmap_opaque,
bitmap_opaque_dummy_stencil: bitmap_opaque_dummy_depth,
gradients: gradient_pipeline,
complex_blends: complex_blend_pipelines,
}
}
}
#[allow(clippy::too_many_arguments)]
fn create_pipeline_descriptor<'a>(
label: Option<&'a str>,
vertex_shader: &'a wgpu::ShaderModule,
fragment_shader: &'a wgpu::ShaderModule,
pipeline_layout: &'a wgpu::PipelineLayout,
depth_stencil_state: Option<wgpu::DepthStencilState>,
color_target_state: &'a [Option<wgpu::ColorTargetState>],
vertex_buffer_layout: &'a [wgpu::VertexBufferLayout<'a>],
msaa_sample_count: u32,
fragment_constants: &'a HashMap<String, f64>,
primitive_topology: PrimitiveTopology,
) -> wgpu::RenderPipelineDescriptor<'a> {
wgpu::RenderPipelineDescriptor {
label,
layout: Some(pipeline_layout),
vertex: wgpu::VertexState {
module: vertex_shader,
entry_point: "main_vertex",
buffers: vertex_buffer_layout,
compilation_options: Default::default(),
},
fragment: Some(wgpu::FragmentState {
module: fragment_shader,
entry_point: "main_fragment",
targets: color_target_state,
compilation_options: wgpu::PipelineCompilationOptions {
constants: fragment_constants,
..Default::default()
},
}),
primitive: wgpu::PrimitiveState {
topology: primitive_topology,
strip_index_format: None,
front_face: wgpu::FrontFace::Ccw,
cull_mode: None,
polygon_mode: wgpu::PolygonMode::default(),
unclipped_depth: false,
conservative: false,
},
depth_stencil: depth_stencil_state,
multisample: wgpu::MultisampleState {
count: msaa_sample_count,
mask: !0,
alpha_to_coverage_enabled: false,
},
multiview: None,
}
}
#[allow(clippy::too_many_arguments)]
fn create_shape_pipeline(
name: &str,
device: &wgpu::Device,
format: wgpu::TextureFormat,
shader: &wgpu::ShaderModule,
msaa_sample_count: u32,
vertex_buffers_layout: &[wgpu::VertexBufferLayout<'_>],
bind_group_layouts: &[&wgpu::BindGroupLayout],
blend: BlendState,
push_constant_ranges: &[wgpu::PushConstantRange],
primitive_topology: PrimitiveTopology,
) -> ShapePipeline {
let pipeline_layout_label = create_debug_label!("{} shape pipeline layout", name);
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: pipeline_layout_label.as_deref(),
bind_group_layouts,
push_constant_ranges,
});
let mask_render_state = |mask_name, stencil_state, write_mask| {
device.create_render_pipeline(&create_pipeline_descriptor(
create_debug_label!("{} pipeline {}", name, mask_name).as_deref(),
shader,
shader,
&pipeline_layout,
Some(wgpu::DepthStencilState {
format: wgpu::TextureFormat::Stencil8,
depth_write_enabled: false,
depth_compare: wgpu::CompareFunction::Always,
stencil: wgpu::StencilState {
front: stencil_state,
back: stencil_state,
read_mask: !0,
write_mask: !0,
},
bias: Default::default(),
}),
&[Some(wgpu::ColorTargetState {
format,
blend: Some(blend),
write_mask,
})],
vertex_buffers_layout,
msaa_sample_count,
&Default::default(),
primitive_topology,
))
};
ShapePipeline::build(
device.create_render_pipeline(&create_pipeline_descriptor(
create_debug_label!("{} stencilless pipeline", name).as_deref(),
shader,
shader,
&pipeline_layout,
None,
&[Some(wgpu::ColorTargetState {
format,
blend: Some(blend),
write_mask: wgpu::ColorWrites::ALL,
})],
vertex_buffers_layout,
msaa_sample_count,
&Default::default(),
primitive_topology,
)),
|mask_state| match mask_state {
MaskState::NoMask => mask_render_state(
"no mask",
wgpu::StencilFaceState {
compare: wgpu::CompareFunction::Always,
fail_op: wgpu::StencilOperation::Keep,
depth_fail_op: wgpu::StencilOperation::Keep,
pass_op: wgpu::StencilOperation::Keep,
},
wgpu::ColorWrites::ALL,
),
MaskState::DrawMaskStencil => mask_render_state(
"draw mask stencil",
wgpu::StencilFaceState {
compare: wgpu::CompareFunction::Equal,
fail_op: wgpu::StencilOperation::Keep,
depth_fail_op: wgpu::StencilOperation::Keep,
pass_op: wgpu::StencilOperation::IncrementClamp,
},
wgpu::ColorWrites::empty(),
),
MaskState::DrawMaskedContent => mask_render_state(
"draw masked content",
wgpu::StencilFaceState {
compare: wgpu::CompareFunction::Equal,
fail_op: wgpu::StencilOperation::Keep,
depth_fail_op: wgpu::StencilOperation::Keep,
pass_op: wgpu::StencilOperation::Keep,
},
wgpu::ColorWrites::ALL,
),
MaskState::ClearMaskStencil => mask_render_state(
"clear mask stencil",
wgpu::StencilFaceState {
compare: wgpu::CompareFunction::Equal,
fail_op: wgpu::StencilOperation::Keep,
depth_fail_op: wgpu::StencilOperation::Keep,
pass_op: wgpu::StencilOperation::DecrementClamp,
},
wgpu::ColorWrites::empty(),
),
},
)
}