diff --git a/core/src/backend/render.rs b/core/src/backend/render.rs index 4ba3a9036..fcc723d6d 100644 --- a/core/src/backend/render.rs +++ b/core/src/backend/render.rs @@ -27,6 +27,9 @@ pub trait RenderBackend { fn end_frame(&mut self); fn draw_pause_overlay(&mut self); 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)] @@ -80,6 +83,9 @@ impl RenderBackend for NullRenderer { fn render_shape(&mut self, _shape: ShapeHandle, _transform: &Transform) {} fn draw_pause_overlay(&mut self) {} 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 { diff --git a/core/src/button.rs b/core/src/button.rs index 79bfb2469..1c2c505fc 100644 --- a/core/src/button.rs +++ b/core/src/button.rs @@ -196,9 +196,7 @@ impl<'gc> DisplayObject<'gc> for Button<'gc> { fn render(&self, context: &mut RenderContext<'_, 'gc>) { context.transform_stack.push(self.transform()); - for child in self.children.values() { - child.read().render(context); - } + crate::display_object::render_children(context, &self.children); context.transform_stack.pop(); } diff --git a/core/src/display_object.rs b/core/src/display_object.rs index 5ab26cc28..d29fcf0fe 100644 --- a/core/src/display_object.rs +++ b/core/src/display_object.rs @@ -28,6 +28,9 @@ impl<'gc> Default for DisplayObjectBase<'gc> { } impl<'gc> DisplayObject<'gc> for DisplayObjectBase<'gc> { + fn depth(&self) -> Depth { + self.depth + } fn transform(&self) -> &Transform { &self.transform } @@ -71,6 +74,7 @@ impl<'gc> DisplayObject<'gc> for DisplayObjectBase<'gc> { } pub trait DisplayObject<'gc>: 'gc + Collect + Debug { + fn depth(&self) -> Depth; fn local_bounds(&self) -> BoundingBox { BoundingBox::default() } @@ -148,6 +152,9 @@ impl<'gc> Clone for Box> { macro_rules! impl_display_object { ($field:ident) => { + fn depth(&self) -> crate::prelude::Depth { + self.$field.depth() + } fn transform(&self) -> &crate::transform::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>, +) { + 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. /// 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? diff --git a/core/src/movie_clip.rs b/core/src/movie_clip.rs index fef4dba63..7562bb66e 100644 --- a/core/src/movie_clip.rs +++ b/core/src/movie_clip.rs @@ -330,11 +330,7 @@ impl<'gc> DisplayObject<'gc> for MovieClip<'gc> { fn render(&self, context: &mut RenderContext<'_, 'gc>) { context.transform_stack.push(self.transform()); - - for child in self.children.values() { - child.read().render(context); - } - + crate::display_object::render_children(context, &self.children); context.transform_stack.pop(); } @@ -1008,6 +1004,9 @@ impl<'gc, 'a> MovieClip<'gc> { character .write(context.gc_context) .set_color_transform(prev_character.read().color_transform()); + character + .write(context.gc_context) + .set_clip_depth(prev_character.read().clip_depth()); } character } diff --git a/core/src/player.rs b/core/src/player.rs index 96aa6aced..e48c1ed18 100644 --- a/core/src/player.rs +++ b/core/src/player.rs @@ -431,6 +431,7 @@ impl Player { library: gc_root.library.read(), transform_stack, view_bounds, + clip_depth_stack: vec![], }; 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 transform_stack: &'a mut TransformStack, pub view_bounds: BoundingBox, + pub clip_depth_stack: Vec, } diff --git a/desktop/src/main.rs b/desktop/src/main.rs index 650fca21d..9a8587928 100644 --- a/desktop/src/main.rs +++ b/desktop/src/main.rs @@ -40,6 +40,7 @@ fn run_player(input_path: PathBuf) -> Result<(), Box> { .with_vsync(true) .with_multisampling(4) .with_srgb(true) + .with_stencil_buffer(8) .build_windowed(window_builder, &events_loop)?; let audio = audio::RodioAudioBackend::new()?; let renderer = GliumRenderBackend::new(windowed_context)?; diff --git a/desktop/src/render.rs b/desktop/src/render.rs index 8f8bbadc2..3c79e5e39 100644 --- a/desktop/src/render.rs +++ b/desktop/src/render.rs @@ -22,6 +22,12 @@ pub struct GliumRenderBackend { bitmap_shader_program: glium::Program, meshes: Vec, 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_height: f32, view_matrix: [[f32; 4]; 4], @@ -87,6 +93,12 @@ impl GliumRenderBackend { viewport_width: 500.0, viewport_height: 500.0, 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(); Ok(renderer) @@ -104,9 +116,6 @@ impl GliumRenderBackend { let mut mesh = Mesh { draws: vec![] }; - //let mut vertices: Vec = vec![]; - //let mut indices: Vec = vec![]; - let mut fill_tess = FillTessellator::new(); let mut stroke_tess = StrokeTessellator::new(); let mut lyon_mesh: VertexBuffers<_, u32> = VertexBuffers::new(); @@ -571,22 +580,29 @@ impl RenderBackend for GliumRenderBackend { fn begin_frame(&mut self) { assert!(self.target.is_none()); 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) { assert!(self.target.is_some()); - let target = self.target.take().unwrap(); target.finish().unwrap(); } fn clear(&mut self, color: Color) { let target = self.target.as_mut().unwrap(); - target.clear_color_srgb( - f32::from(color.r) / 255.0, - f32::from(color.g) / 255.0, - f32::from(color.b) / 255.0, - f32::from(color.a) / 255.0, + target.clear_color_srgb_and_stencil( + ( + f32::from(color.r) / 255.0, + f32::from(color.g) / 255.0, + f32::from(color.b) / 255.0, + f32::from(color.a) / 255.0, + ), + 0, ); } @@ -621,16 +637,27 @@ impl RenderBackend for GliumRenderBackend { 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 { match &draw.draw_type { DrawType::Color => { + draw_parameters.blend = color_blend(); + target .draw( &draw.vertex_buffer, &draw.index_buffer, &self.shader_program, &uniform! { view_matrix: self.view_matrix, world_matrix: world_matrix, mult_color: mult_color, add_color: add_color }, - &color_draw_parameters() + &draw_parameters ) .unwrap(); } @@ -643,13 +670,15 @@ impl RenderBackend for GliumRenderBackend { gradient: gradient_uniforms.clone(), }; + draw_parameters.blend = color_blend(); + target .draw( &draw.vertex_buffer, &draw.index_buffer, &self.gradient_shader_program, &uniforms, - &color_draw_parameters(), + &draw_parameters, ) .unwrap(); } @@ -697,13 +726,15 @@ impl RenderBackend for GliumRenderBackend { texture, }; + draw_parameters.blend = bitmap_blend(); + target .draw( &draw.vertex_buffer, &draw.index_buffer, &self.bitmap_shader_program, &uniforms, - &bitmap_draw_parameters(), + &draw_parameters, ) .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 { @@ -1124,30 +1193,72 @@ 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]] } +/// 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. #[inline] -fn color_draw_parameters() -> DrawParameters<'static> { - DrawParameters { - blend: glium::Blend::alpha_blending(), - ..Default::default() - } +fn color_blend() -> glium::Blend { + glium::Blend::alpha_blending() } /// Returns the drawing parameters for bitmaps with pre-multipled alpha. #[inline] -fn bitmap_draw_parameters() -> DrawParameters<'static> { +fn bitmap_blend() -> glium::Blend { use glium::{BlendingFunction, LinearBlendingFactor}; - DrawParameters { - blend: glium::Blend { - color: BlendingFunction::Addition { - source: LinearBlendingFactor::One, - destination: LinearBlendingFactor::OneMinusSourceAlpha, - }, - alpha: BlendingFunction::Addition { - source: LinearBlendingFactor::SourceAlpha, - destination: LinearBlendingFactor::OneMinusSourceAlpha, - }, - ..Default::default() + glium::Blend { + color: BlendingFunction::Addition { + source: LinearBlendingFactor::One, + destination: LinearBlendingFactor::OneMinusSourceAlpha, + }, + alpha: BlendingFunction::Addition { + source: LinearBlendingFactor::SourceAlpha, + destination: LinearBlendingFactor::OneMinusSourceAlpha, }, ..Default::default() } diff --git a/web/src/render.rs b/web/src/render.rs index 023a03965..1849ca9a8 100644 --- a/web/src/render.rs +++ b/web/src/render.rs @@ -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(