413 lines
14 KiB
Rust
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(),
|
|
),
|
|
},
|
|
)
|
|
}
|