render: Fix masking issues on wgpu/webgl backends

Change the usage of the stencil buffer to avoid running out of
stencil bits when too many nested masks are active.

This also cleans things up on wgpu which requires us to make
pipeline states in advice; now we only need a few stencil states
for masking as opposed to hundreds.
This commit is contained in:
Mike Welsh 2020-10-14 11:25:55 -07:00
parent 8ba5dbfa49
commit 4558be948e
9 changed files with 622 additions and 478 deletions

28
Cargo.lock generated
View File

@ -81,6 +81,12 @@ dependencies = [
"num-traits 0.2.12",
]
[[package]]
name = "array-macro"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06e97b4e522f9e55523001238ac59d13a8603af57f69980de5d8de4bbbe8ada6"
[[package]]
name = "arrayref"
version = "0.3.6"
@ -909,6 +915,27 @@ version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
[[package]]
name = "enum-map"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f81f09b9cb18af2ea1da2688a1d6b1762b4f938d7495bb034bce48d4c608043"
dependencies = [
"array-macro",
"enum-map-derive",
]
[[package]]
name = "enum-map-derive"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e57001dfb2532f5a103ff869656887fae9a8defa7d236f3e39d2ee86ed629ad7"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "enumset"
version = "1.0.1"
@ -2595,6 +2622,7 @@ version = "0.1.0"
dependencies = [
"bytemuck",
"clap",
"enum-map",
"futures",
"image",
"jpeg-decoder",

View File

@ -40,6 +40,7 @@ pub trait RenderBackend: Downcast {
fn draw_letterbox(&mut self, letterbox: Letterbox);
fn push_mask(&mut self);
fn activate_mask(&mut self);
fn deactivate_mask(&mut self);
fn pop_mask(&mut self);
}
impl_downcast!(RenderBackend);
@ -143,6 +144,7 @@ impl RenderBackend for NullRenderer {
fn draw_letterbox(&mut self, _letterbox: Letterbox) {}
fn push_mask(&mut self) {}
fn activate_mask(&mut self) {}
fn deactivate_mask(&mut self) {}
fn pop_mask(&mut self) {}
}

View File

@ -1150,18 +1150,22 @@ pub fn render_children<'gc>(
children: &std::collections::BTreeMap<Depth, DisplayObject<'gc>>,
) {
let mut clip_depth = 0;
let mut clip_depth_stack = vec![];
let mut clip_depth_stack: Vec<(Depth, DisplayObject<'_>)> = vec![];
for (&depth, &child) in children {
// Check if we need to pop off a mask.
// This must be a while loop because multiple masks can be popped
// at the same dpeth.
while clip_depth > 0 && depth >= clip_depth {
// Clear the mask stencil and pop the mask.
let (prev_clip_depth, clip_child) = clip_depth_stack.pop().unwrap();
clip_depth = prev_clip_depth;
context.renderer.deactivate_mask();
clip_child.render(context);
context.renderer.pop_mask();
clip_depth = clip_depth_stack.pop().unwrap();
}
if child.clip_depth() > 0 && child.allow_as_mask() {
// Push and render the mask.
clip_depth_stack.push(clip_depth);
clip_depth_stack.push((clip_depth, child));
clip_depth = child.clip_depth();
context.renderer.push_mask();
child.render(context);
@ -1172,9 +1176,11 @@ pub fn render_children<'gc>(
}
}
while !clip_depth_stack.is_empty() {
// Pop any remaining masks.
for (_, clip_child) in clip_depth_stack.into_iter().rev() {
context.renderer.deactivate_mask();
clip_child.render(context);
context.renderer.pop_mask();
clip_depth_stack.pop();
}
}

View File

@ -1029,6 +1029,11 @@ impl<'gc> TDisplayObject<'gc> for EditText<'gc> {
self.render_layout_box(context, layout_box);
}
context.renderer.deactivate_mask();
context.renderer.draw_rect(
Color::from_rgb(0, 0xff),
&(context.transform_stack.transform().matrix * mask),
);
context.renderer.pop_mask();
context.transform_stack.pop();

View File

@ -31,6 +31,7 @@ pub struct WebCanvasRenderBackend {
viewport_height: u32,
use_color_transform_hack: bool,
pixelated_property_value: &'static str,
deactivating_mask: bool,
}
/// Canvas-drawable shape data extracted from an SWF file.
@ -213,6 +214,7 @@ impl WebCanvasRenderBackend {
viewport_width: 0,
viewport_height: 0,
use_color_transform_hack: is_firefox,
deactivating_mask: false,
// For rendering non-smoothed bitmaps.
// crisp-edges works in Firefox, pixelated works in Chrome (and others)?
@ -551,6 +553,8 @@ impl RenderBackend for WebCanvasRenderBackend {
self.context.set_fill_style(&color.into());
self.context
.fill_rect(0.0, 0.0, width.into(), height.into());
self.deactivating_mask = false;
}
fn end_frame(&mut self) {
@ -558,6 +562,10 @@ impl RenderBackend for WebCanvasRenderBackend {
}
fn render_bitmap(&mut self, bitmap: BitmapHandle, transform: &Transform) {
if self.deactivating_mask {
return;
}
self.set_transform(&transform.matrix);
self.set_color_filter(transform);
if let Some(bitmap) = self.bitmaps.get(bitmap.0) {
@ -569,6 +577,10 @@ impl RenderBackend for WebCanvasRenderBackend {
}
fn render_shape(&mut self, shape: ShapeHandle, transform: &Transform) {
if self.deactivating_mask {
return;
}
self.set_transform(&transform.matrix);
if let Some(shape) = self.shapes.get(shape.0) {
for command in shape.0.iter() {
@ -629,6 +641,10 @@ impl RenderBackend for WebCanvasRenderBackend {
}
fn draw_rect(&mut self, color: Color, matrix: &Matrix) {
if self.deactivating_mask {
return;
}
self.set_transform(matrix);
self.clear_color_filter();
@ -685,7 +701,12 @@ impl RenderBackend for WebCanvasRenderBackend {
// We render the maskee clips to the second render target.
self.push_render_target();
}
fn deactivate_mask(&mut self) {
self.deactivating_mask = true;
}
fn pop_mask(&mut self) {
self.deactivating_mask = false;
let (maskee_canvas, maskee_context) = self.pop_render_target();
let (masker_canvas, _masker_context) = self.pop_render_target();

View File

@ -23,6 +23,14 @@ const GRADIENT_FRAGMENT_GLSL: &str = include_str!("../shaders/gradient.frag");
const BITMAP_FRAGMENT_GLSL: &str = include_str!("../shaders/bitmap.frag");
const NUM_VERTEX_ATTRIBUTES: u32 = 2;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum MaskState {
NoMask,
DrawMaskStencil,
DrawMaskedContent,
ClearMaskStencil,
}
pub struct WebGlRenderBackend {
/// WebGL1 context
gl: Gl,
@ -48,15 +56,11 @@ pub struct WebGlRenderBackend {
quad_shape: ShapeHandle,
mask_state: MaskState,
num_masks: u32,
num_masks_active: u32,
write_stencil_mask: u32,
test_stencil_mask: u32,
next_stencil_mask: u32,
mask_stack: Vec<(u32, u32)>,
mask_state_dirty: bool,
active_program: *const ShaderProgram,
mask_state_dirty: bool,
blend_func: (u32, u32),
mult_color: Option<[f32; 4]>,
add_color: Option<[f32; 4]>,
@ -191,15 +195,12 @@ impl WebGlRenderBackend {
viewport_width: 500.0,
viewport_height: 500.0,
view_matrix: [[0.0; 4]; 4],
mask_state: MaskState::NoMask,
num_masks: 0,
num_masks_active: 0,
write_stencil_mask: 0,
test_stencil_mask: 0,
next_stencil_mask: 1,
mask_stack: vec![],
mask_state_dirty: true,
active_program: std::ptr::null(),
mask_state_dirty: true,
blend_func: (Gl::SRC_ALPHA, Gl::ONE_MINUS_SRC_ALPHA),
mult_color: None,
add_color: None,
@ -636,29 +637,31 @@ impl WebGlRenderBackend {
fn set_stencil_state(&mut self) {
// Set stencil state for masking, if necessary.
if self.mask_state_dirty {
if self.num_masks > 0 {
self.gl.enable(Gl::STENCIL_TEST);
if self.num_masks_active < self.num_masks {
self.gl.stencil_mask(self.write_stencil_mask);
self.gl
.stencil_func(Gl::ALWAYS, self.write_stencil_mask as i32, 0xff);
self.gl.stencil_op(Gl::KEEP, Gl::KEEP, Gl::REPLACE);
self.gl.color_mask(false, false, false, false);
} else {
self.gl.stencil_mask(0xff);
self.gl.stencil_func(
Gl::EQUAL,
self.test_stencil_mask as i32,
self.test_stencil_mask,
);
self.gl.stencil_op(Gl::KEEP, Gl::KEEP, Gl::KEEP);
self.gl.color_mask(true, true, true, true);
}
} else {
match self.mask_state {
MaskState::NoMask => {
self.gl.disable(Gl::STENCIL_TEST);
self.gl.color_mask(true, true, true, true);
}
self.mask_state_dirty = false;
MaskState::DrawMaskStencil => {
self.gl.enable(Gl::STENCIL_TEST);
self.gl
.stencil_func(Gl::EQUAL, (self.num_masks - 1) as i32, 0xff);
self.gl.stencil_op(Gl::KEEP, Gl::KEEP, Gl::INCR);
self.gl.color_mask(false, false, false, false);
}
MaskState::DrawMaskedContent => {
self.gl.enable(Gl::STENCIL_TEST);
self.gl.stencil_func(Gl::EQUAL, self.num_masks as i32, 0xff);
self.gl.stencil_op(Gl::KEEP, Gl::KEEP, Gl::KEEP);
self.gl.color_mask(true, true, true, true);
}
MaskState::ClearMaskStencil => {
self.gl.enable(Gl::STENCIL_TEST);
self.gl.stencil_func(Gl::EQUAL, self.num_masks as i32, 0xff);
self.gl.stencil_op(Gl::KEEP, Gl::KEEP, Gl::DECR);
self.gl.color_mask(false, false, false, false);
}
}
}
}
@ -796,13 +799,9 @@ impl RenderBackend for WebGlRenderBackend {
}
fn begin_frame(&mut self, clear: Color) {
self.num_masks = 0;
self.num_masks_active = 0;
self.write_stencil_mask = 0;
self.test_stencil_mask = 0;
self.next_stencil_mask = 1;
self.active_program = std::ptr::null();
self.mask_state = MaskState::NoMask;
self.num_masks = 0;
self.mask_state_dirty = true;
self.mult_color = None;
@ -1212,48 +1211,35 @@ impl RenderBackend for WebGlRenderBackend {
}
fn push_mask(&mut self) {
// Desktop draws the masker to the stencil buffer, one bit per mask.
// Masks-within-masks are handled as a bitmask.
// This does unfortunately mean we are limited in the number of masks at once (usually 8 bits).
if self.next_stencil_mask >= 0x100 {
// If we've reached the limit of masks, clear the stencil buffer and start over.
// But this may not be correct if there is still a mask active (mask-within-mask).
if self.test_stencil_mask != 0 {
log::warn!(
"Too many masks active for stencil buffer; possibly incorrect rendering"
assert!(
self.mask_state == MaskState::NoMask || self.mask_state == MaskState::DrawMaskedContent
);
}
self.next_stencil_mask = 1;
self.gl.stencil_mask(0xff);
self.gl.clear_stencil(self.test_stencil_mask as i32);
self.gl.clear(Gl::STENCIL_BUFFER_BIT);
self.gl.clear_stencil(0);
}
self.num_masks += 1;
self.mask_stack
.push((self.write_stencil_mask, self.test_stencil_mask));
self.write_stencil_mask = self.next_stencil_mask;
self.test_stencil_mask |= self.next_stencil_mask;
self.next_stencil_mask <<= 1;
self.mask_state = MaskState::DrawMaskStencil;
self.mask_state_dirty = true;
}
fn activate_mask(&mut self) {
self.num_masks_active += 1;
assert!(self.num_masks > 0 && self.mask_state == MaskState::DrawMaskStencil);
self.mask_state = MaskState::DrawMaskedContent;
self.mask_state_dirty = true;
}
fn deactivate_mask(&mut self) {
assert!(self.num_masks > 0 && self.mask_state == MaskState::DrawMaskedContent);
self.mask_state = MaskState::ClearMaskStencil;
self.mask_state_dirty = true;
}
fn pop_mask(&mut self) {
if !self.mask_stack.is_empty() {
assert!(self.num_masks > 0 && self.mask_state == MaskState::ClearMaskStencil);
self.num_masks -= 1;
self.num_masks_active -= 1;
let (write, test) = self.mask_stack.pop().unwrap();
self.write_stencil_mask = write;
self.test_stencil_mask = test;
self.mask_state_dirty = true;
self.mask_state = if self.num_masks == 0 {
MaskState::NoMask
} else {
log::warn!("Mask stack underflow\n");
}
MaskState::DrawMaskedContent
};
self.mask_state_dirty = true;
}
}

View File

@ -16,6 +16,7 @@ futures = "0.3.6"
bytemuck = "1.4.1"
raw-window-handle = "0.3.3"
clap = { version = "3.0.0-beta.2", optional = true }
enum-map = "0.6.3"
[features]
render_debug_labels = []

View File

@ -24,6 +24,7 @@ use crate::utils::{
gradient_spread_mode_index, ruffle_path_to_lyon_path, swf_bitmap_to_gl_matrix,
swf_to_gl_matrix,
};
use enum_map::Enum;
use ruffle_core::color_transform::ColorTransform;
use std::mem::replace;
use std::rc::Rc;
@ -59,17 +60,21 @@ pub struct WgpuRenderBackend<T: RenderTarget> {
viewport_height: f32,
view_matrix: [[f32; 4]; 4],
textures: Vec<(swf::CharacterId, Texture)>,
mask_state: MaskState,
num_masks: u32,
num_masks_active: u32,
write_stencil_mask: u32,
test_stencil_mask: u32,
next_stencil_mask: u32,
mask_stack: Vec<(u32, u32)>,
quad_vbo: wgpu::Buffer,
quad_ibo: wgpu::Buffer,
quad_tex_transforms: wgpu::Buffer,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Enum)]
pub enum MaskState {
NoMask,
DrawMaskStencil,
DrawMaskedContent,
ClearMaskStencil,
}
#[repr(C)]
#[derive(Copy, Clone, Debug)]
struct Transforms {
@ -238,12 +243,10 @@ impl<T: RenderTarget> WgpuRenderBackend<T> {
viewport_height,
view_matrix,
textures: Vec::new(),
num_masks: 0,
num_masks_active: 0,
write_stencil_mask: 0,
test_stencil_mask: 0,
next_stencil_mask: 1,
mask_stack: Vec::new(),
mask_state: MaskState::NoMask,
quad_vbo,
quad_ibo,
quad_tex_transforms,
@ -829,11 +832,9 @@ impl<T: RenderTarget + 'static> RenderBackend for WgpuRenderBackend<T> {
None
}
};
self.mask_state = MaskState::NoMask;
self.num_masks = 0;
self.num_masks_active = 0;
self.write_stencil_mask = 0;
self.test_stencil_mask = 0;
self.next_stencil_mask = 1;
if let Some((frame_output, encoder)) = &mut self.current_frame {
let (color_attachment, resolve_target) = if self.msaa_sample_count >= 2 {
@ -1006,21 +1007,22 @@ impl<T: RenderTarget + 'static> RenderBackend for WgpuRenderBackend<T> {
}),
});
render_pass.set_pipeline(&self.pipelines.bitmap.pipeline_for(
self.num_masks,
self.num_masks_active,
self.test_stencil_mask,
self.write_stencil_mask,
));
render_pass.set_pipeline(&self.pipelines.bitmap.pipeline_for(self.mask_state));
render_pass.set_bind_group(0, &bind_group, &[]);
render_pass.set_vertex_buffer(0, self.quad_vbo.slice(..));
render_pass.set_index_buffer(self.quad_ibo.slice(..));
if self.num_masks_active < self.num_masks {
render_pass.set_stencil_reference(self.write_stencil_mask);
} else {
render_pass.set_stencil_reference(self.test_stencil_mask);
match self.mask_state {
MaskState::NoMask => (),
MaskState::DrawMaskStencil => {
debug_assert!(self.num_masks > 0);
render_pass.set_stencil_reference(self.num_masks - 1);
}
MaskState::DrawMaskedContent | MaskState::ClearMaskStencil => {
debug_assert!(self.num_masks > 0);
render_pass.set_stencil_reference(self.num_masks);
}
};
render_pass.draw_indexed(0..6, 0, 0..1);
}
@ -1115,28 +1117,14 @@ impl<T: RenderTarget + 'static> RenderBackend for WgpuRenderBackend<T> {
for draw in &mesh.draws {
match &draw.draw_type {
DrawType::Color => {
render_pass.set_pipeline(&self.pipelines.color.pipeline_for(
self.num_masks,
self.num_masks_active,
self.test_stencil_mask,
self.write_stencil_mask,
));
render_pass.set_pipeline(&self.pipelines.color.pipeline_for(self.mask_state));
}
DrawType::Gradient { .. } => {
render_pass.set_pipeline(&self.pipelines.gradient.pipeline_for(
self.num_masks,
self.num_masks_active,
self.test_stencil_mask,
self.write_stencil_mask,
));
render_pass
.set_pipeline(&self.pipelines.gradient.pipeline_for(self.mask_state));
}
DrawType::Bitmap { .. } => {
render_pass.set_pipeline(&self.pipelines.bitmap.pipeline_for(
self.num_masks,
self.num_masks_active,
self.test_stencil_mask,
self.write_stencil_mask,
));
render_pass.set_pipeline(&self.pipelines.bitmap.pipeline_for(self.mask_state));
}
}
@ -1144,11 +1132,17 @@ impl<T: RenderTarget + 'static> RenderBackend for WgpuRenderBackend<T> {
render_pass.set_vertex_buffer(0, draw.vertex_buffer.slice(..));
render_pass.set_index_buffer(draw.index_buffer.slice(..));
if self.num_masks_active < self.num_masks {
render_pass.set_stencil_reference(self.write_stencil_mask);
} else {
render_pass.set_stencil_reference(self.test_stencil_mask);
match self.mask_state {
MaskState::NoMask => (),
MaskState::DrawMaskStencil => {
debug_assert!(self.num_masks > 0);
render_pass.set_stencil_reference(self.num_masks - 1);
}
MaskState::DrawMaskedContent | MaskState::ClearMaskStencil => {
debug_assert!(self.num_masks > 0);
render_pass.set_stencil_reference(self.num_masks);
}
};
render_pass.draw_indexed(0..draw.index_count, 0, 0..1);
}
@ -1254,21 +1248,22 @@ impl<T: RenderTarget + 'static> RenderBackend for WgpuRenderBackend<T> {
}),
});
render_pass.set_pipeline(&self.pipelines.color.pipeline_for(
self.num_masks,
self.num_masks_active,
self.test_stencil_mask,
self.write_stencil_mask,
));
render_pass.set_pipeline(&self.pipelines.color.pipeline_for(self.mask_state));
render_pass.set_bind_group(0, &bind_group, &[]);
render_pass.set_vertex_buffer(0, self.quad_vbo.slice(..));
render_pass.set_index_buffer(self.quad_ibo.slice(..));
if self.num_masks_active < self.num_masks {
render_pass.set_stencil_reference(self.write_stencil_mask);
} else {
render_pass.set_stencil_reference(self.test_stencil_mask);
match self.mask_state {
MaskState::NoMask => (),
MaskState::DrawMaskStencil => {
debug_assert!(self.num_masks > 0);
render_pass.set_stencil_reference(self.num_masks - 1);
}
MaskState::DrawMaskedContent | MaskState::ClearMaskStencil => {
debug_assert!(self.num_masks > 0);
render_pass.set_stencil_reference(self.num_masks);
}
};
render_pass.draw_indexed(0..6, 0, 0..1);
}
@ -1362,69 +1357,31 @@ impl<T: RenderTarget + 'static> RenderBackend for WgpuRenderBackend<T> {
}
fn push_mask(&mut self) {
// Desktop draws the masker to the stencil buffer, one bit per mask.
// Masks-within-masks are handled as a bitmask.
// This does unfortunately mean we are limited in the number of masks at once (8 bits).
if self.next_stencil_mask >= 0x100 {
// If we've reached the limit of masks, clear the stencil buffer and start over.
// But this may not be correct if there is still a mask active (mask-within-mask).
if self.test_stencil_mask != 0 {
log::warn!(
"Too many masks active for stencil buffer; possibly incorrect rendering"
assert!(
self.mask_state == MaskState::NoMask || self.mask_state == MaskState::DrawMaskedContent
);
}
self.next_stencil_mask = 1;
if let Some((frame_output, encoder)) = &mut self.current_frame {
let (color_attachment, resolve_target) = if self.msaa_sample_count >= 2 {
(&self.frame_buffer_view, Some(frame_output.view()))
} else {
(frame_output.view(), None)
};
encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
color_attachments: &[wgpu::RenderPassColorAttachmentDescriptor {
attachment: color_attachment,
resolve_target,
ops: wgpu::Operations {
load: wgpu::LoadOp::Load,
store: true,
},
}],
depth_stencil_attachment: Some(
wgpu::RenderPassDepthStencilAttachmentDescriptor {
attachment: &self.depth_texture_view,
depth_ops: Some(wgpu::Operations {
load: wgpu::LoadOp::Load,
store: true,
}),
stencil_ops: Some(wgpu::Operations {
load: wgpu::LoadOp::Clear(self.test_stencil_mask),
store: true,
}),
},
),
});
}
}
self.num_masks += 1;
self.mask_stack
.push((self.write_stencil_mask, self.test_stencil_mask));
self.write_stencil_mask = self.next_stencil_mask;
self.test_stencil_mask |= self.next_stencil_mask;
self.next_stencil_mask <<= 1;
self.mask_state = MaskState::DrawMaskStencil;
}
fn activate_mask(&mut self) {
self.num_masks_active += 1;
assert!(self.num_masks > 0 && self.mask_state == MaskState::DrawMaskStencil);
self.mask_state = MaskState::DrawMaskedContent;
}
fn deactivate_mask(&mut self) {
assert!(self.num_masks > 0 && self.mask_state == MaskState::DrawMaskedContent);
self.mask_state = MaskState::ClearMaskStencil;
}
fn pop_mask(&mut self) {
if !self.mask_stack.is_empty() {
assert!(self.num_masks > 0 && self.mask_state == MaskState::ClearMaskStencil);
self.num_masks -= 1;
self.num_masks_active -= 1;
let (write, test) = self.mask_stack.pop().unwrap();
self.write_stencil_mask = write;
self.test_stencil_mask = test;
}
self.mask_state = if self.num_masks == 0 {
MaskState::NoMask
} else {
MaskState::DrawMaskedContent
};
}
}

View File

@ -1,10 +1,10 @@
use crate::{Error, GPUVertex};
use crate::{Error, GPUVertex, MaskState};
use enum_map::{enum_map, EnumMap};
use wgpu::vertex_attr_array;
#[derive(Debug)]
pub struct ShapePipeline {
pub write_mask_pipelines: Vec<wgpu::RenderPipeline>,
pub read_mask_pipelines: Vec<wgpu::RenderPipeline>,
pub mask_pipelines: EnumMap<MaskState, wgpu::RenderPipeline>,
pub bind_layout: wgpu::BindGroupLayout,
}
@ -16,18 +16,8 @@ pub struct Pipelines {
}
impl ShapePipeline {
pub fn pipeline_for(
&self,
num_masks: u32,
num_masks_active: u32,
read_mask: u32,
write_mask: u32,
) -> &wgpu::RenderPipeline {
if num_masks_active < num_masks {
&self.write_mask_pipelines[write_mask.trailing_zeros() as usize]
} else {
&self.read_mask_pipelines[read_mask as usize]
}
pub fn pipeline_for(&self, mask_state: MaskState) -> &wgpu::RenderPipeline {
&self.mask_pipelines[mask_state]
}
}
@ -162,13 +152,11 @@ fn create_color_pipelines(
push_constant_ranges: &[],
});
let mut write_mask_pipelines = Vec::new();
let mut read_mask_pipelines = Vec::new();
for i in 0..8 {
let label = create_debug_label!("Color pipeline write mask {}", i);
write_mask_pipelines.push(device.create_render_pipeline(&create_pipeline_descriptor(
label.as_deref(),
let mask_pipelines = enum_map! {
MaskState::NoMask => {
let (stencil, write_mask) = mask_render_state(MaskState::NoMask);
device.create_render_pipeline(&create_pipeline_descriptor(
create_debug_label!("Color pipeline no mask").as_deref(),
vertex_shader,
fragment_shader,
&pipeline_layout,
@ -176,22 +164,7 @@ fn create_color_pipelines(
format: wgpu::TextureFormat::Depth24PlusStencil8,
depth_write_enabled: true,
depth_compare: wgpu::CompareFunction::Always,
stencil: wgpu::StencilStateDescriptor {
front: wgpu::StencilStateFaceDescriptor {
compare: wgpu::CompareFunction::Always,
fail_op: wgpu::StencilOperation::Keep,
depth_fail_op: wgpu::StencilOperation::Keep,
pass_op: wgpu::StencilOperation::Replace,
},
back: wgpu::StencilStateFaceDescriptor {
compare: wgpu::CompareFunction::Always,
fail_op: wgpu::StencilOperation::Keep,
depth_fail_op: wgpu::StencilOperation::Keep,
pass_op: wgpu::StencilOperation::Replace,
},
read_mask: 0xff,
write_mask: 1 << i,
},
stencil,
}),
&[wgpu::ColorStateDescriptor {
format: wgpu::TextureFormat::Bgra8Unorm,
@ -205,17 +178,17 @@ fn create_color_pipelines(
dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
operation: wgpu::BlendOperation::Add,
},
write_mask: wgpu::ColorWrite::empty(),
write_mask,
}],
vertex_buffers_description,
msaa_sample_count,
)));
}
))
},
for i in 0..256 {
let label = create_debug_label!("Color pipeline read mask {}", i);
read_mask_pipelines.push(device.create_render_pipeline(&create_pipeline_descriptor(
label.as_deref(),
MaskState::DrawMaskStencil => {
let (stencil, write_mask) = mask_render_state(MaskState::DrawMaskStencil);
device.create_render_pipeline(&create_pipeline_descriptor(
create_debug_label!("Color pipeline draw mask stencil").as_deref(),
vertex_shader,
fragment_shader,
&pipeline_layout,
@ -223,22 +196,7 @@ fn create_color_pipelines(
format: wgpu::TextureFormat::Depth24PlusStencil8,
depth_write_enabled: true,
depth_compare: wgpu::CompareFunction::Always,
stencil: wgpu::StencilStateDescriptor {
front: wgpu::StencilStateFaceDescriptor {
compare: wgpu::CompareFunction::Equal,
fail_op: wgpu::StencilOperation::Keep,
depth_fail_op: wgpu::StencilOperation::Keep,
pass_op: wgpu::StencilOperation::Keep,
},
back: wgpu::StencilStateFaceDescriptor {
compare: wgpu::CompareFunction::Equal,
fail_op: wgpu::StencilOperation::Keep,
depth_fail_op: wgpu::StencilOperation::Keep,
pass_op: wgpu::StencilOperation::Keep,
},
read_mask: i,
write_mask: 0,
},
stencil,
}),
&[wgpu::ColorStateDescriptor {
format: wgpu::TextureFormat::Bgra8Unorm,
@ -252,16 +210,80 @@ fn create_color_pipelines(
dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
operation: wgpu::BlendOperation::Add,
},
write_mask: wgpu::ColorWrite::ALL,
write_mask,
}],
vertex_buffers_description,
msaa_sample_count,
)));
}
))
},
MaskState::DrawMaskedContent => {
let (stencil, write_mask) = mask_render_state(MaskState::DrawMaskedContent);
device.create_render_pipeline(&create_pipeline_descriptor(
create_debug_label!("Color pipeline draw masked content").as_deref(),
vertex_shader,
fragment_shader,
&pipeline_layout,
Some(wgpu::DepthStencilStateDescriptor {
format: wgpu::TextureFormat::Depth24PlusStencil8,
depth_write_enabled: true,
depth_compare: wgpu::CompareFunction::Always,
stencil,
}),
&[wgpu::ColorStateDescriptor {
format: wgpu::TextureFormat::Bgra8Unorm,
color_blend: wgpu::BlendDescriptor {
src_factor: wgpu::BlendFactor::SrcAlpha,
dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
operation: wgpu::BlendOperation::Add,
},
alpha_blend: wgpu::BlendDescriptor {
src_factor: wgpu::BlendFactor::SrcAlpha,
dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
operation: wgpu::BlendOperation::Add,
},
write_mask,
}],
vertex_buffers_description,
msaa_sample_count,
))
},
MaskState::ClearMaskStencil => {
let (stencil, write_mask) = mask_render_state(MaskState::ClearMaskStencil);
device.create_render_pipeline(&create_pipeline_descriptor(
create_debug_label!("Color pipeline clear mask stencil").as_deref(),
vertex_shader,
fragment_shader,
&pipeline_layout,
Some(wgpu::DepthStencilStateDescriptor {
format: wgpu::TextureFormat::Depth24PlusStencil8,
depth_write_enabled: true,
depth_compare: wgpu::CompareFunction::Always,
stencil,
}),
&[wgpu::ColorStateDescriptor {
format: wgpu::TextureFormat::Bgra8Unorm,
color_blend: wgpu::BlendDescriptor {
src_factor: wgpu::BlendFactor::SrcAlpha,
dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
operation: wgpu::BlendOperation::Add,
},
alpha_blend: wgpu::BlendDescriptor {
src_factor: wgpu::BlendFactor::SrcAlpha,
dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
operation: wgpu::BlendOperation::Add,
},
write_mask,
}],
vertex_buffers_description,
msaa_sample_count,
))
},
};
ShapePipeline {
write_mask_pipelines,
read_mask_pipelines,
mask_pipelines,
bind_layout,
}
}
@ -330,13 +352,11 @@ fn create_bitmap_pipeline(
push_constant_ranges: &[],
});
let mut write_mask_pipelines = Vec::new();
let mut read_mask_pipelines = Vec::new();
for i in 0..8 {
let label = create_debug_label!("Bitmap pipeline write mask {}", i);
write_mask_pipelines.push(device.create_render_pipeline(&create_pipeline_descriptor(
label.as_deref(),
let mask_pipelines = enum_map! {
MaskState::NoMask => {
let (stencil, write_mask) = mask_render_state(MaskState::NoMask);
device.create_render_pipeline(&create_pipeline_descriptor(
create_debug_label!("Bitmap pipeline no mask").as_deref(),
vertex_shader,
fragment_shader,
&pipeline_layout,
@ -344,22 +364,7 @@ fn create_bitmap_pipeline(
format: wgpu::TextureFormat::Depth24PlusStencil8,
depth_write_enabled: true,
depth_compare: wgpu::CompareFunction::Always,
stencil: wgpu::StencilStateDescriptor {
front: wgpu::StencilStateFaceDescriptor {
compare: wgpu::CompareFunction::Always,
fail_op: wgpu::StencilOperation::Keep,
depth_fail_op: wgpu::StencilOperation::Keep,
pass_op: wgpu::StencilOperation::Replace,
},
back: wgpu::StencilStateFaceDescriptor {
compare: wgpu::CompareFunction::Always,
fail_op: wgpu::StencilOperation::Keep,
depth_fail_op: wgpu::StencilOperation::Keep,
pass_op: wgpu::StencilOperation::Replace,
},
read_mask: 0xff,
write_mask: 1 << i,
},
stencil,
}),
&[wgpu::ColorStateDescriptor {
format: wgpu::TextureFormat::Bgra8Unorm,
@ -373,17 +378,17 @@ fn create_bitmap_pipeline(
dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
operation: wgpu::BlendOperation::Add,
},
write_mask: wgpu::ColorWrite::empty(),
write_mask,
}],
vertex_buffers_description,
msaa_sample_count,
)));
}
))
},
for i in 0..256 {
let label = create_debug_label!("Bitmap pipeline read mask {}", i);
read_mask_pipelines.push(device.create_render_pipeline(&create_pipeline_descriptor(
label.as_deref(),
MaskState::DrawMaskStencil => {
let (stencil, write_mask) = mask_render_state(MaskState::DrawMaskStencil);
device.create_render_pipeline(&create_pipeline_descriptor(
create_debug_label!("Bitmap pipeline draw mask stencil").as_deref(),
vertex_shader,
fragment_shader,
&pipeline_layout,
@ -391,27 +396,12 @@ fn create_bitmap_pipeline(
format: wgpu::TextureFormat::Depth24PlusStencil8,
depth_write_enabled: true,
depth_compare: wgpu::CompareFunction::Always,
stencil: wgpu::StencilStateDescriptor {
front: wgpu::StencilStateFaceDescriptor {
compare: wgpu::CompareFunction::Equal,
fail_op: wgpu::StencilOperation::Keep,
depth_fail_op: wgpu::StencilOperation::Keep,
pass_op: wgpu::StencilOperation::Keep,
},
back: wgpu::StencilStateFaceDescriptor {
compare: wgpu::CompareFunction::Equal,
fail_op: wgpu::StencilOperation::Keep,
depth_fail_op: wgpu::StencilOperation::Keep,
pass_op: wgpu::StencilOperation::Keep,
},
read_mask: i,
write_mask: 0,
},
stencil,
}),
&[wgpu::ColorStateDescriptor {
format: wgpu::TextureFormat::Bgra8Unorm,
color_blend: wgpu::BlendDescriptor {
src_factor: wgpu::BlendFactor::One,
src_factor: wgpu::BlendFactor::SrcAlpha,
dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
operation: wgpu::BlendOperation::Add,
},
@ -420,16 +410,80 @@ fn create_bitmap_pipeline(
dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
operation: wgpu::BlendOperation::Add,
},
write_mask: wgpu::ColorWrite::ALL,
write_mask,
}],
vertex_buffers_description,
msaa_sample_count,
)));
))
},
MaskState::DrawMaskedContent => {
let (stencil, write_mask) = mask_render_state(MaskState::DrawMaskedContent);
device.create_render_pipeline(&create_pipeline_descriptor(
create_debug_label!("Bitmap pipeline draw masked content").as_deref(),
vertex_shader,
fragment_shader,
&pipeline_layout,
Some(wgpu::DepthStencilStateDescriptor {
format: wgpu::TextureFormat::Depth24PlusStencil8,
depth_write_enabled: true,
depth_compare: wgpu::CompareFunction::Equal,
stencil,
}),
&[wgpu::ColorStateDescriptor {
format: wgpu::TextureFormat::Bgra8Unorm,
color_blend: wgpu::BlendDescriptor {
src_factor: wgpu::BlendFactor::SrcAlpha,
dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
operation: wgpu::BlendOperation::Add,
},
alpha_blend: wgpu::BlendDescriptor {
src_factor: wgpu::BlendFactor::SrcAlpha,
dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
operation: wgpu::BlendOperation::Add,
},
write_mask,
}],
vertex_buffers_description,
msaa_sample_count,
))
},
MaskState::ClearMaskStencil => {
let (stencil, write_mask) = mask_render_state(MaskState::ClearMaskStencil);
device.create_render_pipeline(&create_pipeline_descriptor(
create_debug_label!("Bitmap pipeline clear mask stencil").as_deref(),
vertex_shader,
fragment_shader,
&pipeline_layout,
Some(wgpu::DepthStencilStateDescriptor {
format: wgpu::TextureFormat::Depth24PlusStencil8,
depth_write_enabled: true,
depth_compare: wgpu::CompareFunction::Always,
stencil,
}),
&[wgpu::ColorStateDescriptor {
format: wgpu::TextureFormat::Bgra8Unorm,
color_blend: wgpu::BlendDescriptor {
src_factor: wgpu::BlendFactor::SrcAlpha,
dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
operation: wgpu::BlendOperation::Add,
},
alpha_blend: wgpu::BlendDescriptor {
src_factor: wgpu::BlendFactor::SrcAlpha,
dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
operation: wgpu::BlendOperation::Add,
},
write_mask,
}],
vertex_buffers_description,
msaa_sample_count,
))
}
};
ShapePipeline {
write_mask_pipelines,
read_mask_pipelines,
mask_pipelines,
bind_layout,
}
}
@ -492,13 +546,11 @@ fn create_gradient_pipeline(
push_constant_ranges: &[],
});
let mut write_mask_pipelines = Vec::new();
let mut read_mask_pipelines = Vec::new();
for i in 0..8 {
let label = create_debug_label!("Gradient pipeline write mask {}", i);
write_mask_pipelines.push(device.create_render_pipeline(&create_pipeline_descriptor(
label.as_deref(),
let mask_pipelines = enum_map! {
MaskState::NoMask => {
let (stencil, write_mask) = mask_render_state(MaskState::NoMask);
device.create_render_pipeline(&create_pipeline_descriptor(
create_debug_label!("Gradient pipeline no mask").as_deref(),
vertex_shader,
fragment_shader,
&pipeline_layout,
@ -506,22 +558,7 @@ fn create_gradient_pipeline(
format: wgpu::TextureFormat::Depth24PlusStencil8,
depth_write_enabled: true,
depth_compare: wgpu::CompareFunction::Always,
stencil: wgpu::StencilStateDescriptor {
front: wgpu::StencilStateFaceDescriptor {
compare: wgpu::CompareFunction::Always,
fail_op: wgpu::StencilOperation::Keep,
depth_fail_op: wgpu::StencilOperation::Keep,
pass_op: wgpu::StencilOperation::Replace,
},
back: wgpu::StencilStateFaceDescriptor {
compare: wgpu::CompareFunction::Always,
fail_op: wgpu::StencilOperation::Keep,
depth_fail_op: wgpu::StencilOperation::Keep,
pass_op: wgpu::StencilOperation::Replace,
},
read_mask: 0xff,
write_mask: 1 << i,
},
stencil,
}),
&[wgpu::ColorStateDescriptor {
format: wgpu::TextureFormat::Bgra8Unorm,
@ -535,17 +572,17 @@ fn create_gradient_pipeline(
dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
operation: wgpu::BlendOperation::Add,
},
write_mask: wgpu::ColorWrite::empty(),
write_mask,
}],
vertex_buffers_description,
msaa_sample_count,
)));
}
))
},
for i in 0..256 {
let label = create_debug_label!("Gradient pipeline read mask {}", i);
read_mask_pipelines.push(device.create_render_pipeline(&create_pipeline_descriptor(
label.as_deref(),
MaskState::DrawMaskStencil => {
let (stencil, write_mask) = mask_render_state(MaskState::DrawMaskStencil);
device.create_render_pipeline(&create_pipeline_descriptor(
create_debug_label!("Gradient pipeline draw mask stencil").as_deref(),
vertex_shader,
fragment_shader,
&pipeline_layout,
@ -553,22 +590,7 @@ fn create_gradient_pipeline(
format: wgpu::TextureFormat::Depth24PlusStencil8,
depth_write_enabled: true,
depth_compare: wgpu::CompareFunction::Always,
stencil: wgpu::StencilStateDescriptor {
front: wgpu::StencilStateFaceDescriptor {
compare: wgpu::CompareFunction::Equal,
fail_op: wgpu::StencilOperation::Keep,
depth_fail_op: wgpu::StencilOperation::Keep,
pass_op: wgpu::StencilOperation::Keep,
},
back: wgpu::StencilStateFaceDescriptor {
compare: wgpu::CompareFunction::Equal,
fail_op: wgpu::StencilOperation::Keep,
depth_fail_op: wgpu::StencilOperation::Keep,
pass_op: wgpu::StencilOperation::Keep,
},
read_mask: i,
write_mask: 0,
},
stencil,
}),
&[wgpu::ColorStateDescriptor {
format: wgpu::TextureFormat::Bgra8Unorm,
@ -582,16 +604,132 @@ fn create_gradient_pipeline(
dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
operation: wgpu::BlendOperation::Add,
},
write_mask: wgpu::ColorWrite::ALL,
write_mask,
}],
vertex_buffers_description,
msaa_sample_count,
)));
))
},
MaskState::DrawMaskedContent => {
let (stencil, write_mask) = mask_render_state(MaskState::DrawMaskedContent);
device.create_render_pipeline(&create_pipeline_descriptor(
create_debug_label!("Gradient pipeline draw masked content").as_deref(),
vertex_shader,
fragment_shader,
&pipeline_layout,
Some(wgpu::DepthStencilStateDescriptor {
format: wgpu::TextureFormat::Depth24PlusStencil8,
depth_write_enabled: true,
depth_compare: wgpu::CompareFunction::Equal,
stencil,
}),
&[wgpu::ColorStateDescriptor {
format: wgpu::TextureFormat::Bgra8Unorm,
color_blend: wgpu::BlendDescriptor {
src_factor: wgpu::BlendFactor::SrcAlpha,
dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
operation: wgpu::BlendOperation::Add,
},
alpha_blend: wgpu::BlendDescriptor {
src_factor: wgpu::BlendFactor::SrcAlpha,
dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
operation: wgpu::BlendOperation::Add,
},
write_mask,
}],
vertex_buffers_description,
msaa_sample_count,
))
},
MaskState::ClearMaskStencil => {
let (stencil, write_mask) = mask_render_state(MaskState::ClearMaskStencil);
device.create_render_pipeline(&create_pipeline_descriptor(
create_debug_label!("Gradient pipeline clear mask stencil").as_deref(),
vertex_shader,
fragment_shader,
&pipeline_layout,
Some(wgpu::DepthStencilStateDescriptor {
format: wgpu::TextureFormat::Depth24PlusStencil8,
depth_write_enabled: true,
depth_compare: wgpu::CompareFunction::Always,
stencil,
}),
&[wgpu::ColorStateDescriptor {
format: wgpu::TextureFormat::Bgra8Unorm,
color_blend: wgpu::BlendDescriptor {
src_factor: wgpu::BlendFactor::SrcAlpha,
dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
operation: wgpu::BlendOperation::Add,
},
alpha_blend: wgpu::BlendDescriptor {
src_factor: wgpu::BlendFactor::SrcAlpha,
dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
operation: wgpu::BlendOperation::Add,
},
write_mask,
}],
vertex_buffers_description,
msaa_sample_count,
))
}
};
ShapePipeline {
write_mask_pipelines,
read_mask_pipelines,
mask_pipelines,
bind_layout,
}
}
fn mask_render_state(state: MaskState) -> (wgpu::StencilStateDescriptor, wgpu::ColorWrite) {
let (stencil_state, color_write) = match state {
MaskState::NoMask => (
wgpu::StencilStateFaceDescriptor {
compare: wgpu::CompareFunction::Always,
fail_op: wgpu::StencilOperation::Keep,
depth_fail_op: wgpu::StencilOperation::Keep,
pass_op: wgpu::StencilOperation::Keep,
},
wgpu::ColorWrite::ALL,
),
MaskState::DrawMaskStencil => (
wgpu::StencilStateFaceDescriptor {
compare: wgpu::CompareFunction::Equal,
fail_op: wgpu::StencilOperation::Keep,
depth_fail_op: wgpu::StencilOperation::Keep,
pass_op: wgpu::StencilOperation::IncrementClamp,
},
wgpu::ColorWrite::empty(),
),
MaskState::DrawMaskedContent => (
wgpu::StencilStateFaceDescriptor {
compare: wgpu::CompareFunction::Equal,
fail_op: wgpu::StencilOperation::Keep,
depth_fail_op: wgpu::StencilOperation::Keep,
pass_op: wgpu::StencilOperation::Keep,
},
wgpu::ColorWrite::ALL,
),
MaskState::ClearMaskStencil => (
wgpu::StencilStateFaceDescriptor {
compare: wgpu::CompareFunction::Equal,
fail_op: wgpu::StencilOperation::Keep,
depth_fail_op: wgpu::StencilOperation::Keep,
pass_op: wgpu::StencilOperation::DecrementClamp,
},
wgpu::ColorWrite::empty(),
),
};
(
wgpu::StencilStateDescriptor {
front: stencil_state.clone(),
back: stencil_state,
read_mask: 0xff,
write_mask: 0xff,
},
color_write,
)
}