Mask work

This commit is contained in:
Mike Welsh 2019-09-07 19:33:06 -07:00
parent 3c38405a71
commit 28f0ce3c83
8 changed files with 202 additions and 37 deletions

View File

@ -27,6 +27,9 @@ pub trait RenderBackend {
fn end_frame(&mut self); fn end_frame(&mut self);
fn draw_pause_overlay(&mut self); fn draw_pause_overlay(&mut self);
fn draw_letterbox(&mut self, letterbox: Letterbox); fn draw_letterbox(&mut self, letterbox: Letterbox);
fn push_mask(&mut self);
fn activate_mask(&mut self);
fn pop_mask(&mut self);
} }
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug)]
@ -80,6 +83,9 @@ impl RenderBackend for NullRenderer {
fn render_shape(&mut self, _shape: ShapeHandle, _transform: &Transform) {} fn render_shape(&mut self, _shape: ShapeHandle, _transform: &Transform) {}
fn draw_pause_overlay(&mut self) {} fn draw_pause_overlay(&mut self) {}
fn draw_letterbox(&mut self, _letterbox: Letterbox) {} fn draw_letterbox(&mut self, _letterbox: Letterbox) {}
fn push_mask(&mut self) {}
fn activate_mask(&mut self) {}
fn pop_mask(&mut self) {}
} }
pub fn glue_swf_jpeg_to_tables(jpeg_tables: &[u8], jpeg_data: &[u8]) -> Vec<u8> { pub fn glue_swf_jpeg_to_tables(jpeg_tables: &[u8], jpeg_data: &[u8]) -> Vec<u8> {

View File

@ -196,9 +196,7 @@ impl<'gc> DisplayObject<'gc> for Button<'gc> {
fn render(&self, context: &mut RenderContext<'_, 'gc>) { fn render(&self, context: &mut RenderContext<'_, 'gc>) {
context.transform_stack.push(self.transform()); context.transform_stack.push(self.transform());
for child in self.children.values() { crate::display_object::render_children(context, &self.children);
child.read().render(context);
}
context.transform_stack.pop(); context.transform_stack.pop();
} }

View File

@ -28,6 +28,9 @@ impl<'gc> Default for DisplayObjectBase<'gc> {
} }
impl<'gc> DisplayObject<'gc> for DisplayObjectBase<'gc> { impl<'gc> DisplayObject<'gc> for DisplayObjectBase<'gc> {
fn depth(&self) -> Depth {
self.depth
}
fn transform(&self) -> &Transform { fn transform(&self) -> &Transform {
&self.transform &self.transform
} }
@ -71,6 +74,7 @@ impl<'gc> DisplayObject<'gc> for DisplayObjectBase<'gc> {
} }
pub trait DisplayObject<'gc>: 'gc + Collect + Debug { pub trait DisplayObject<'gc>: 'gc + Collect + Debug {
fn depth(&self) -> Depth;
fn local_bounds(&self) -> BoundingBox { fn local_bounds(&self) -> BoundingBox {
BoundingBox::default() BoundingBox::default()
} }
@ -148,6 +152,9 @@ impl<'gc> Clone for Box<dyn DisplayObject<'gc>> {
macro_rules! impl_display_object { macro_rules! impl_display_object {
($field:ident) => { ($field:ident) => {
fn depth(&self) -> crate::prelude::Depth {
self.$field.depth()
}
fn transform(&self) -> &crate::transform::Transform { fn transform(&self) -> &crate::transform::Transform {
self.$field.transform() self.$field.transform()
} }
@ -190,6 +197,43 @@ macro_rules! impl_display_object {
}; };
} }
/// Renders the children of a display object, taking masking into account.
// TODO(Herschel): Move this into an IDisplayObject/IDisplayObjectContainer trait when
// we figure out inheritance
pub fn render_children<'gc>(
context: &mut RenderContext<'_, 'gc>,
children: &std::collections::BTreeMap<Depth, DisplayNode<'gc>>,
) {
let mut clip_depth = 0;
let mut clip_depth_stack = 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 {
context.renderer.pop_mask();
clip_depth = clip_depth_stack.pop().unwrap();
}
let child = child.read();
if child.clip_depth() > 0 {
// Push and render the mask.
clip_depth_stack.push(clip_depth);
clip_depth = child.clip_depth();
context.renderer.push_mask();
child.render(context);
context.renderer.activate_mask();
} else {
// Normal child.
child.render(context);
}
}
while !clip_depth_stack.is_empty() {
context.renderer.pop_mask();
clip_depth_stack.pop();
}
}
/// `DisplayNode` is the garbage-collected pointer between display objects. /// `DisplayNode` is the garbage-collected pointer between display objects.
/// TODO(Herschel): The extra Box here is necessary to hold the trait object inside a GC pointer, /// TODO(Herschel): The extra Box here is necessary to hold the trait object inside a GC pointer,
/// but this is an extra allocation... Can we avoid this, maybe with a DST? /// but this is an extra allocation... Can we avoid this, maybe with a DST?

View File

@ -330,11 +330,7 @@ impl<'gc> DisplayObject<'gc> for MovieClip<'gc> {
fn render(&self, context: &mut RenderContext<'_, 'gc>) { fn render(&self, context: &mut RenderContext<'_, 'gc>) {
context.transform_stack.push(self.transform()); context.transform_stack.push(self.transform());
crate::display_object::render_children(context, &self.children);
for child in self.children.values() {
child.read().render(context);
}
context.transform_stack.pop(); context.transform_stack.pop();
} }
@ -1008,6 +1004,9 @@ impl<'gc, 'a> MovieClip<'gc> {
character character
.write(context.gc_context) .write(context.gc_context)
.set_color_transform(prev_character.read().color_transform()); .set_color_transform(prev_character.read().color_transform());
character
.write(context.gc_context)
.set_clip_depth(prev_character.read().clip_depth());
} }
character character
} }

View File

@ -431,6 +431,7 @@ impl<Audio: AudioBackend, Renderer: RenderBackend> Player<Audio, Renderer> {
library: gc_root.library.read(), library: gc_root.library.read(),
transform_stack, transform_stack,
view_bounds, view_bounds,
clip_depth_stack: vec![],
}; };
gc_root.root.read().render(&mut render_context); gc_root.root.read().render(&mut render_context);
}); });
@ -543,4 +544,5 @@ pub struct RenderContext<'a, 'gc> {
pub library: std::cell::Ref<'a, Library<'gc>>, pub library: std::cell::Ref<'a, Library<'gc>>,
pub transform_stack: &'a mut TransformStack, pub transform_stack: &'a mut TransformStack,
pub view_bounds: BoundingBox, pub view_bounds: BoundingBox,
pub clip_depth_stack: Vec<Depth>,
} }

View File

@ -40,6 +40,7 @@ fn run_player(input_path: PathBuf) -> Result<(), Box<dyn std::error::Error>> {
.with_vsync(true) .with_vsync(true)
.with_multisampling(4) .with_multisampling(4)
.with_srgb(true) .with_srgb(true)
.with_stencil_buffer(8)
.build_windowed(window_builder, &events_loop)?; .build_windowed(window_builder, &events_loop)?;
let audio = audio::RodioAudioBackend::new()?; let audio = audio::RodioAudioBackend::new()?;
let renderer = GliumRenderBackend::new(windowed_context)?; let renderer = GliumRenderBackend::new(windowed_context)?;

View File

@ -22,6 +22,12 @@ pub struct GliumRenderBackend {
bitmap_shader_program: glium::Program, bitmap_shader_program: glium::Program,
meshes: Vec<Mesh>, meshes: Vec<Mesh>,
textures: Vec<(swf::CharacterId, Texture)>, textures: Vec<(swf::CharacterId, Texture)>,
num_masks: u32,
num_masks_active: u32,
write_stencil_mask: u32,
test_stencil_mask: u32,
next_stencil_mask: u32,
mask_stack: Vec<(u32, u32)>,
viewport_width: f32, viewport_width: f32,
viewport_height: f32, viewport_height: f32,
view_matrix: [[f32; 4]; 4], view_matrix: [[f32; 4]; 4],
@ -87,6 +93,12 @@ impl GliumRenderBackend {
viewport_width: 500.0, viewport_width: 500.0,
viewport_height: 500.0, viewport_height: 500.0,
view_matrix: [[0.0; 4]; 4], view_matrix: [[0.0; 4]; 4],
num_masks: 0,
num_masks_active: 0,
write_stencil_mask: 0,
test_stencil_mask: 0,
next_stencil_mask: 1,
mask_stack: vec![],
}; };
renderer.build_matrices(); renderer.build_matrices();
Ok(renderer) Ok(renderer)
@ -104,9 +116,6 @@ impl GliumRenderBackend {
let mut mesh = Mesh { draws: vec![] }; let mut mesh = Mesh { draws: vec![] };
//let mut vertices: Vec<Vertex> = vec![];
//let mut indices: Vec<u32> = vec![];
let mut fill_tess = FillTessellator::new(); let mut fill_tess = FillTessellator::new();
let mut stroke_tess = StrokeTessellator::new(); let mut stroke_tess = StrokeTessellator::new();
let mut lyon_mesh: VertexBuffers<_, u32> = VertexBuffers::new(); let mut lyon_mesh: VertexBuffers<_, u32> = VertexBuffers::new();
@ -571,22 +580,29 @@ impl RenderBackend for GliumRenderBackend {
fn begin_frame(&mut self) { fn begin_frame(&mut self) {
assert!(self.target.is_none()); assert!(self.target.is_none());
self.target = Some(self.display.draw()); self.target = Some(self.display.draw());
self.num_masks = 0;
self.num_masks_active = 0;
self.write_stencil_mask = 0;
self.test_stencil_mask = 0;
self.next_stencil_mask = 1;
} }
fn end_frame(&mut self) { fn end_frame(&mut self) {
assert!(self.target.is_some()); assert!(self.target.is_some());
let target = self.target.take().unwrap(); let target = self.target.take().unwrap();
target.finish().unwrap(); target.finish().unwrap();
} }
fn clear(&mut self, color: Color) { fn clear(&mut self, color: Color) {
let target = self.target.as_mut().unwrap(); let target = self.target.as_mut().unwrap();
target.clear_color_srgb( target.clear_color_srgb_and_stencil(
(
f32::from(color.r) / 255.0, f32::from(color.r) / 255.0,
f32::from(color.g) / 255.0, f32::from(color.g) / 255.0,
f32::from(color.b) / 255.0, f32::from(color.b) / 255.0,
f32::from(color.a) / 255.0, f32::from(color.a) / 255.0,
),
0,
); );
} }
@ -621,16 +637,27 @@ impl RenderBackend for GliumRenderBackend {
transform.color_transform.a_add, transform.color_transform.a_add,
]; ];
let mut draw_parameters = DrawParameters::default();
mask_draw_parameters(
&mut draw_parameters,
self.num_masks,
self.num_masks_active,
self.write_stencil_mask,
self.test_stencil_mask,
);
for draw in &mesh.draws { for draw in &mesh.draws {
match &draw.draw_type { match &draw.draw_type {
DrawType::Color => { DrawType::Color => {
draw_parameters.blend = color_blend();
target target
.draw( .draw(
&draw.vertex_buffer, &draw.vertex_buffer,
&draw.index_buffer, &draw.index_buffer,
&self.shader_program, &self.shader_program,
&uniform! { view_matrix: self.view_matrix, world_matrix: world_matrix, mult_color: mult_color, add_color: add_color }, &uniform! { view_matrix: self.view_matrix, world_matrix: world_matrix, mult_color: mult_color, add_color: add_color },
&color_draw_parameters() &draw_parameters
) )
.unwrap(); .unwrap();
} }
@ -643,13 +670,15 @@ impl RenderBackend for GliumRenderBackend {
gradient: gradient_uniforms.clone(), gradient: gradient_uniforms.clone(),
}; };
draw_parameters.blend = color_blend();
target target
.draw( .draw(
&draw.vertex_buffer, &draw.vertex_buffer,
&draw.index_buffer, &draw.index_buffer,
&self.gradient_shader_program, &self.gradient_shader_program,
&uniforms, &uniforms,
&color_draw_parameters(), &draw_parameters,
) )
.unwrap(); .unwrap();
} }
@ -697,13 +726,15 @@ impl RenderBackend for GliumRenderBackend {
texture, texture,
}; };
draw_parameters.blend = bitmap_blend();
target target
.draw( .draw(
&draw.vertex_buffer, &draw.vertex_buffer,
&draw.index_buffer, &draw.index_buffer,
&self.bitmap_shader_program, &self.bitmap_shader_program,
&uniforms, &uniforms,
&bitmap_draw_parameters(), &draw_parameters,
) )
.unwrap(); .unwrap();
} }
@ -772,6 +803,44 @@ impl RenderBackend for GliumRenderBackend {
} }
} }
} }
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 == 0 {
// 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).
let target = self.target.as_mut().unwrap();
if self.test_stencil_mask != 0 {
log::warn!(
"Too many masks active for stencil buffer; possibly inccorect rendering"
);
}
self.next_stencil_mask = 0;
target.clear_stencil(self.test_stencil_mask as i32);
}
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;
}
fn activate_mask(&mut self) {
self.num_masks_active += 1;
}
fn pop_mask(&mut self) {
if !self.mask_stack.is_empty() {
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;
} else {
log::warn!("Mask stack underflow\n");
}
}
} }
struct Texture { struct Texture {
@ -1124,21 +1193,65 @@ fn swf_bitmap_to_gl_matrix(m: swf::Matrix, bitmap_width: u32, bitmap_height: u32
[[a, d, 0.0], [b, e, 0.0], [c, f, 1.0]] [[a, d, 0.0], [b, e, 0.0], [c, f, 1.0]]
} }
/// Returns the drawing parameters for masking.
#[inline]
fn mask_draw_parameters(
params: &mut DrawParameters,
num_masks: u32,
num_masks_active: u32,
write_stencil_mask: u32,
test_stencil_mask: u32,
) {
use glium::draw_parameters::{Stencil, StencilOperation, StencilTest};
if num_masks > 0 {
let (value, test, pass_op, color_mask, write_mask) = if num_masks_active < num_masks {
(
write_stencil_mask as i32,
StencilTest::AlwaysPass,
StencilOperation::Replace,
(false, false, false, false),
write_stencil_mask,
)
} else {
(
test_stencil_mask as i32,
StencilTest::IfEqual {
mask: test_stencil_mask,
},
StencilOperation::Keep,
(true, true, true, true),
test_stencil_mask,
)
};
params.color_mask = color_mask;
params.stencil = Stencil {
test_clockwise: test,
reference_value_clockwise: value,
write_mask_clockwise: write_mask,
fail_operation_clockwise: StencilOperation::Keep,
pass_depth_fail_operation_clockwise: StencilOperation::Keep,
depth_pass_operation_clockwise: pass_op,
test_counter_clockwise: test,
reference_value_counter_clockwise: value,
write_mask_counter_clockwise: write_mask,
fail_operation_counter_clockwise: StencilOperation::Keep,
pass_depth_fail_operation_counter_clockwise: StencilOperation::Keep,
depth_pass_operation_counter_clockwise: pass_op,
};
}
}
/// Returns the drawing parameters for standard color/gradient fills. /// Returns the drawing parameters for standard color/gradient fills.
#[inline] #[inline]
fn color_draw_parameters() -> DrawParameters<'static> { fn color_blend() -> glium::Blend {
DrawParameters { glium::Blend::alpha_blending()
blend: glium::Blend::alpha_blending(),
..Default::default()
}
} }
/// Returns the drawing parameters for bitmaps with pre-multipled alpha. /// Returns the drawing parameters for bitmaps with pre-multipled alpha.
#[inline] #[inline]
fn bitmap_draw_parameters() -> DrawParameters<'static> { fn bitmap_blend() -> glium::Blend {
use glium::{BlendingFunction, LinearBlendingFactor}; use glium::{BlendingFunction, LinearBlendingFactor};
DrawParameters { glium::Blend {
blend: glium::Blend {
color: BlendingFunction::Addition { color: BlendingFunction::Addition {
source: LinearBlendingFactor::One, source: LinearBlendingFactor::One,
destination: LinearBlendingFactor::OneMinusSourceAlpha, destination: LinearBlendingFactor::OneMinusSourceAlpha,
@ -1148,7 +1261,5 @@ fn bitmap_draw_parameters() -> DrawParameters<'static> {
destination: LinearBlendingFactor::OneMinusSourceAlpha, destination: LinearBlendingFactor::OneMinusSourceAlpha,
}, },
..Default::default() ..Default::default()
},
..Default::default()
} }
} }

View File

@ -440,6 +440,10 @@ impl RenderBackend for WebCanvasRenderBackend {
} }
} }
} }
fn push_mask(&mut self) {}
fn activate_mask(&mut self) {}
fn pop_mask(&mut self) {}
} }
fn swf_shape_to_svg( fn swf_shape_to_svg(