From 67724bfc7163689d85b7deb92b4bd7effc49669f Mon Sep 17 00:00:00 2001 From: David Wendt Date: Fri, 16 Apr 2021 17:55:57 -0400 Subject: [PATCH] core: Move most of the player rendering code into the `Stage`'s render method. --- core/src/avm1/object/script_object.rs | 2 +- core/src/avm1/test_utils.rs | 2 +- core/src/context.rs | 16 ++- core/src/display_object.rs | 64 +++++---- core/src/display_object/movie_clip.rs | 6 +- core/src/display_object/stage.rs | 176 ++++++++++++++++++++++- core/src/player.rs | 192 ++++++-------------------- 7 files changed, 268 insertions(+), 190 deletions(-) diff --git a/core/src/avm1/object/script_object.rs b/core/src/avm1/object/script_object.rs index 1db11478f..b2d6429b9 100644 --- a/core/src/avm1/object/script_object.rs +++ b/core/src/avm1/object/script_object.rs @@ -877,7 +877,6 @@ mod tests { audio: &mut NullAudioBackend::new(), audio_manager: &mut AudioManager::new(), ui: &mut NullUiBackend::new(), - background_color: &mut None, library: &mut Library::empty(gc_context), navigator: &mut NullNavigatorBackend::new(), renderer: &mut NullRenderer::new(), @@ -888,6 +887,7 @@ mod tests { mouse_position: &(Twips::zero(), Twips::zero()), drag_object: &mut None, stage_size: (Twips::from_pixels(550.0), Twips::from_pixels(400.0)), + viewport_size: (Twips::from_pixels(550.0), Twips::from_pixels(400.0)), player: None, load_manager: &mut LoadManager::new(), system: &mut SystemProperties::default(), diff --git a/core/src/avm1/test_utils.rs b/core/src/avm1/test_utils.rs index de21c95b6..3d290ce08 100644 --- a/core/src/avm1/test_utils.rs +++ b/core/src/avm1/test_utils.rs @@ -52,7 +52,6 @@ where audio: &mut NullAudioBackend::new(), ui: &mut NullUiBackend::new(), action_queue: &mut ActionQueue::new(), - background_color: &mut None, library: &mut Library::empty(gc_context), navigator: &mut NullNavigatorBackend::new(), renderer: &mut NullRenderer::new(), @@ -63,6 +62,7 @@ where mouse_position: &(Twips::zero(), Twips::zero()), drag_object: &mut None, stage_size: (Twips::from_pixels(550.0), Twips::from_pixels(400.0)), + viewport_size: (Twips::from_pixels(550.0), Twips::from_pixels(400.0)), player: None, load_manager: &mut LoadManager::new(), system: &mut SystemProperties::default(), diff --git a/core/src/context.rs b/core/src/context.rs index 286294f8b..bcf210bf2 100644 --- a/core/src/context.rs +++ b/core/src/context.rs @@ -38,10 +38,6 @@ pub struct UpdateContext<'a, 'gc, 'gc_context> { /// Display objects and actions can push actions onto the queue. pub action_queue: &'a mut ActionQueue<'gc>, - /// The background color of the Stage. Changed by the `SetBackgroundColor` SWF tag. - /// TODO: Move this into a `Stage` display object. - pub background_color: &'a mut Option, - /// The mutation context to allocate and mutate `GcCell` types. pub gc_context: MutationContext<'gc, 'gc_context>, @@ -107,6 +103,9 @@ pub struct UpdateContext<'a, 'gc, 'gc_context> { /// The dimensions of the stage. pub stage_size: (Twips, Twips), + /// The dimensions of the stage's containing viewport. + pub viewport_size: (Twips, Twips), + /// Weak reference to the player. /// /// Recipients of an update context may upgrade the reference to ensure @@ -250,7 +249,6 @@ impl<'a, 'gc, 'gc_context> UpdateContext<'a, 'gc, 'gc_context> { { UpdateContext { action_queue: self.action_queue, - background_color: self.background_color, gc_context: self.gc_context, library: self.library, player_version: self.player_version, @@ -271,6 +269,7 @@ impl<'a, 'gc, 'gc_context> UpdateContext<'a, 'gc, 'gc_context> { mouse_position: self.mouse_position, drag_object: self.drag_object, stage_size: self.stage_size, + viewport_size: self.viewport_size, player: self.player.clone(), load_manager: self.load_manager, system: self.system, @@ -370,11 +369,18 @@ pub struct RenderContext<'a, 'gc> { /// The renderer, used by the display objects to draw themselves. pub renderer: &'a mut dyn RenderBackend, + /// The UI backend, used to detect user interactions. + pub ui: &'a mut dyn UiBackend, + /// The library, which provides access to fonts and other definitions when rendering. pub library: &'a Library<'gc>, /// The transform stack controls the matrix and color transform as we traverse the display hierarchy. pub transform_stack: &'a mut TransformStack, + + /// The dimensions of the stage's containing viewport. + pub viewport_bounds: (Twips, Twips), + /// The bounds of the current viewport in twips. Used for culling. pub view_bounds: BoundingBox, diff --git a/core/src/display_object.rs b/core/src/display_object.rs index 7f5f7cd83..f819823b3 100644 --- a/core/src/display_object.rs +++ b/core/src/display_object.rs @@ -427,6 +427,39 @@ impl<'gc> DisplayObjectBase<'gc> { } } +pub fn render_base<'gc>(this: DisplayObject<'gc>, context: &mut RenderContext<'_, 'gc>) { + if this.maskee().is_some() { + return; + } + context.transform_stack.push(&*this.transform()); + + let mask = this.masker(); + let mut mask_transform = crate::transform::Transform::default(); + if let Some(m) = mask { + mask_transform.matrix = this.global_to_local_matrix(); + mask_transform.matrix *= m.local_to_global_matrix(); + context.renderer.push_mask(); + context.allow_mask = false; + context.transform_stack.push(&mask_transform); + m.render_self(context); + context.transform_stack.pop(); + context.allow_mask = true; + context.renderer.activate_mask(); + } + this.render_self(context); + if let Some(m) = mask { + context.renderer.deactivate_mask(); + context.allow_mask = false; + context.transform_stack.push(&mask_transform); + m.render_self(context); + context.transform_stack.pop(); + context.allow_mask = true; + context.renderer.pop_mask(); + } + + context.transform_stack.pop(); +} + #[enum_trait_object( #[derive(Clone, Collect, Debug, Copy)] #[collect(no_drop)] @@ -933,36 +966,7 @@ pub trait TDisplayObject<'gc>: fn render_self(&self, _context: &mut RenderContext<'_, 'gc>) {} fn render(&self, context: &mut RenderContext<'_, 'gc>) { - if self.maskee().is_some() { - return; - } - context.transform_stack.push(&*self.transform()); - - let mask = self.masker(); - let mut mask_transform = crate::transform::Transform::default(); - if let Some(m) = mask { - mask_transform.matrix = self.global_to_local_matrix(); - mask_transform.matrix *= m.local_to_global_matrix(); - context.renderer.push_mask(); - context.allow_mask = false; - context.transform_stack.push(&mask_transform); - m.render_self(context); - context.transform_stack.pop(); - context.allow_mask = true; - context.renderer.activate_mask(); - } - self.render_self(context); - if let Some(m) = mask { - context.renderer.deactivate_mask(); - context.allow_mask = false; - context.transform_stack.push(&mask_transform); - m.render_self(context); - context.transform_stack.pop(); - context.allow_mask = true; - context.renderer.pop_mask(); - } - - context.transform_stack.pop(); + render_base((*self).into(), context) } fn unload(&self, context: &mut UpdateContext<'_, 'gc, '_>) { diff --git a/core/src/display_object/movie_clip.rs b/core/src/display_object/movie_clip.rs index fc4789771..3eb8dc98f 100644 --- a/core/src/display_object/movie_clip.rs +++ b/core/src/display_object/movie_clip.rs @@ -3197,8 +3197,10 @@ impl<'gc, 'a> MovieClip<'gc> { // Also note that a loaded child SWF could change background color only // if parent SWF is missing SetBackgroundColor tag. let background_color = reader.read_rgb()?; - if context.background_color.is_none() { - *context.background_color = Some(background_color); + if context.stage.background_color().is_none() { + context + .stage + .set_background_color(context.gc_context, Some(background_color)); } Ok(()) } diff --git a/core/src/display_object/stage.rs b/core/src/display_object/stage.rs index d38d4fa14..b5961f39f 100644 --- a/core/src/display_object/stage.rs +++ b/core/src/display_object/stage.rs @@ -1,10 +1,13 @@ //! Root stage impl -use crate::context::UpdateContext; +use crate::backend::ui::UiBackend; +use crate::collect::CollectWrapper; +use crate::config::Letterbox; +use crate::context::{RenderContext, UpdateContext}; use crate::display_object::container::{ ChildContainer, DisplayObjectContainer, TDisplayObjectContainer, }; -use crate::display_object::{DisplayObject, DisplayObjectBase, TDisplayObject}; +use crate::display_object::{render_base, DisplayObject, DisplayObjectBase, TDisplayObject}; use crate::prelude::*; use crate::types::{Degrees, Percent}; use gc_arena::{Collect, GcCell, MutationContext}; @@ -29,6 +32,17 @@ pub struct StageData<'gc> { /// /// Stage children are exposed to AVM1 as `_level*n*` on all stage objects. child: ChildContainer<'gc>, + + /// The stage background. + /// + /// If the background color is not specified, it should be white. + background_color: CollectWrapper>, + + /// Determines how player content is resized to fit the stage. + letterbox: Letterbox, + + /// The bounds of the current viewport in twips, used for culling. + view_bounds: BoundingBox, } impl<'gc> Stage<'gc> { @@ -38,9 +52,149 @@ impl<'gc> Stage<'gc> { StageData { base: Default::default(), child: Default::default(), + background_color: CollectWrapper(None), + letterbox: Letterbox::Fullscreen, + view_bounds: Default::default(), }, )) } + + pub fn background_color(self) -> Option { + self.0.read().background_color.0.clone() + } + + pub fn set_background_color(self, gc_context: MutationContext<'gc, '_>, color: Option) { + self.0.write(gc_context).background_color.0 = color; + } + + pub fn inverse_view_matrix(self) -> Matrix { + let mut inverse_view_matrix = *(self.matrix()); + inverse_view_matrix.invert(); + + inverse_view_matrix + } + + pub fn letterbox(self) -> Letterbox { + self.0.read().letterbox + } + + pub fn set_letterbox(self, gc_context: MutationContext<'gc, '_>, letterbox: Letterbox) { + self.0.write(gc_context).letterbox = letterbox + } + + /// Determine if we should letterbox the stage content. + fn should_letterbox(self, ui: &mut dyn UiBackend) -> bool { + let letterbox = self.letterbox(); + + letterbox == Letterbox::On || (letterbox == Letterbox::Fullscreen && ui.is_fullscreen()) + } + + /// Update the stage's transform matrix in response to a root movie change. + pub fn build_matrices(&mut self, context: &mut UpdateContext<'_, 'gc, '_>) { + // Create view matrix to scale stage into viewport area. + let (movie_width, movie_height) = ( + context.stage_size.0.to_pixels(), + context.stage_size.1.to_pixels(), + ); + let (viewport_width, viewport_height) = ( + context.viewport_size.0.to_pixels(), + context.viewport_size.1.to_pixels(), + ); + let movie_aspect = movie_width / movie_height; + let viewport_aspect = viewport_width / viewport_height; + let (scale, margin_width, margin_height) = if viewport_aspect > movie_aspect { + let scale = viewport_height / movie_height; + (scale, (viewport_width - movie_width * scale) / 2.0, 0.0) + } else { + let scale = viewport_width / movie_width; + (scale, 0.0, (viewport_height - movie_height * scale) / 2.0) + }; + *self.matrix_mut(context.gc_context) = Matrix { + a: scale as f32, + b: 0.0, + c: 0.0, + d: scale as f32, + tx: Twips::from_pixels(margin_width), + ty: Twips::from_pixels(margin_height), + }; + + self.0.write(context.gc_context).view_bounds = if self.should_letterbox(context.ui) { + // No letterbox: movie area + BoundingBox { + x_min: Twips::zero(), + y_min: Twips::zero(), + x_max: Twips::from_pixels(movie_width), + y_max: Twips::from_pixels(movie_height), + valid: true, + } + } else { + // No letterbox: full visible stage area + let margin_width = margin_width / scale; + let margin_height = margin_height / scale; + BoundingBox { + x_min: Twips::from_pixels(-margin_width), + y_min: Twips::from_pixels(-margin_height), + x_max: Twips::from_pixels(movie_width + margin_width), + y_max: Twips::from_pixels(movie_height + margin_height), + valid: true, + } + }; + } + + /// Draw the stage's letterbox. + fn draw_letterbox(&self, context: &mut RenderContext<'_, 'gc>) { + let black = Color::from_rgb(0, 255); + let viewport_width = context.viewport_bounds.0.to_pixels() as f32; + let viewport_height = context.viewport_bounds.1.to_pixels() as f32; + + let view_matrix = self.matrix(); + + let margin_width = view_matrix.tx.to_pixels() as f32; + let margin_height = view_matrix.ty.to_pixels() as f32; + if margin_height > 0.0 { + context.renderer.draw_rect( + black.clone(), + &Matrix::create_box( + viewport_width, + margin_height, + 0.0, + Twips::default(), + Twips::default(), + ), + ); + context.renderer.draw_rect( + black, + &Matrix::create_box( + viewport_width, + margin_height, + 0.0, + Twips::default(), + Twips::from_pixels((viewport_height - margin_height) as f64), + ), + ); + } else if margin_width > 0.0 { + context.renderer.draw_rect( + black.clone(), + &Matrix::create_box( + margin_width, + viewport_height, + 0.0, + Twips::default(), + Twips::default(), + ), + ); + context.renderer.draw_rect( + black, + &Matrix::create_box( + margin_width, + viewport_height, + 0.0, + Twips::from_pixels((viewport_width - margin_width) as f64), + Twips::default(), + ), + ); + } + } } impl<'gc> TDisplayObject<'gc> for Stage<'gc> { @@ -61,6 +215,24 @@ impl<'gc> TDisplayObject<'gc> for Stage<'gc> { fn as_stage(&self) -> Option> { Some(*self) } + + fn render(&self, context: &mut RenderContext<'_, 'gc>) { + let background_color = self + .background_color() + .unwrap_or_else(|| Color::from_rgb(0xffffff, 255)); + let view_bounds = self.0.read().view_bounds.clone(); + context.view_bounds = view_bounds; + + context.renderer.begin_frame(background_color); + + render_base((*self).into(), context); + + if self.should_letterbox(context.ui) { + self.draw_letterbox(context); + } + + context.renderer.end_frame(); + } } impl<'gc> TDisplayObjectContainer<'gc> for Stage<'gc> { diff --git a/core/src/player.rs b/core/src/player.rs index 7dd8f5474..69958e950 100644 --- a/core/src/player.rs +++ b/core/src/player.rs @@ -174,14 +174,10 @@ pub struct Player { video: Video, transform_stack: TransformStack, - view_matrix: Matrix, - inverse_view_matrix: Matrix, - view_bounds: BoundingBox, rng: SmallRng, gc_arena: GcArena, - background_color: Option, frame_rate: f64, @@ -199,7 +195,6 @@ pub struct Player { viewport_height: u32, movie_width: u32, movie_height: u32, - letterbox: Letterbox, mouse_pos: (Twips, Twips), is_mouse_down: bool, @@ -261,11 +256,7 @@ impl Player { is_playing: false, needs_render: true, - background_color: None, transform_stack: TransformStack::new(), - view_matrix: Default::default(), - inverse_view_matrix: Default::default(), - view_bounds: Default::default(), rng: SmallRng::seed_from_u64(chrono::Utc::now().timestamp_millis() as u64), @@ -300,7 +291,6 @@ impl Player { movie_height, viewport_width: movie_width, viewport_height: movie_height, - letterbox: Letterbox::Fullscreen, mouse_pos: (Twips::zero(), Twips::zero()), is_mouse_down: false, @@ -334,10 +324,14 @@ impl Player { ); context.stage.replace_at_depth(context, fake_root.into(), 0); - Avm2::load_player_globals(context) + let result = Avm2::load_player_globals(context); + + let mut stage = context.stage; + stage.build_matrices(context); + + result })?; - player.build_matrices(); player.audio.set_frame_rate(frame_rate); let player_box = Arc::new(Mutex::new(player)); let mut player_lock = player_box.lock().unwrap(); @@ -447,9 +441,11 @@ impl Player { AvmString::new(activation.context.gc_context, version_string).into(), Attribute::empty(), ); + + let mut stage = activation.context.stage; + stage.build_matrices(&mut activation.context); }); - self.build_matrices(); self.preload(); self.audio.set_frame_rate(self.frame_rate); } @@ -580,25 +576,26 @@ impl Player { self.needs_render } - pub fn background_color(&self) -> Option { - self.background_color.clone() + pub fn background_color(&mut self) -> Option { + self.mutate_with_update_context(|context| context.stage.background_color()) } pub fn set_background_color(&mut self, color: Option) { - self.background_color = color + self.mutate_with_update_context(|context| { + context + .stage + .set_background_color(context.gc_context, color) + }) } - pub fn letterbox(&self) -> Letterbox { - self.letterbox + pub fn letterbox(&mut self) -> Letterbox { + self.mutate_with_update_context(|context| context.stage.letterbox()) } pub fn set_letterbox(&mut self, letterbox: Letterbox) { - self.letterbox = letterbox - } - - fn should_letterbox(&self) -> bool { - self.letterbox == Letterbox::On - || (self.letterbox == Letterbox::Fullscreen && self.ui.is_fullscreen()) + self.mutate_with_update_context(|context| { + context.stage.set_letterbox(context.gc_context, letterbox) + }) } pub fn warn_on_unsupported_content(&self) -> bool { @@ -624,11 +621,17 @@ impl Player { pub fn set_viewport_dimensions(&mut self, width: u32, height: u32) { self.viewport_width = width; self.viewport_height = height; - self.build_matrices(); + + self.mutate_with_update_context(|context| { + let mut stage = context.stage; + stage.build_matrices(context); + }) } pub fn handle_event(&mut self, event: PlayerEvent) { let mut needs_render = self.needs_render; + let inverse_view_matrix = + self.mutate_with_update_context(|context| context.stage.inverse_view_matrix()); if cfg!(feature = "avm_debug") { if let PlayerEvent::KeyDown { @@ -695,8 +698,7 @@ impl Player { | PlayerEvent::MouseDown { x, y } | PlayerEvent::MouseUp { x, y } = event { - self.mouse_pos = - self.inverse_view_matrix * (Twips::from_pixels(x), Twips::from_pixels(y)); + self.mouse_pos = inverse_view_matrix * (Twips::from_pixels(x), Twips::from_pixels(y)); if self.update_roll_over() { needs_render = true; } @@ -984,40 +986,30 @@ impl Player { } pub fn render(&mut self) { - let background_color = self - .background_color - .clone() - .unwrap_or_else(|| Color::from_rgb(0xffffff, 255)); - self.renderer.begin_frame(background_color); + let (renderer, ui, transform_stack) = + (&mut self.renderer, &mut self.ui, &mut self.transform_stack); - let (renderer, transform_stack) = (&mut self.renderer, &mut self.transform_stack); + let viewport_bounds = ( + Twips::from_pixels(self.viewport_height as f64), + Twips::from_pixels(self.viewport_width as f64), + ); - transform_stack.push(&crate::transform::Transform { - matrix: self.view_matrix, - ..Default::default() - }); - - let view_bounds = self.view_bounds.clone(); self.gc_arena.mutate(|_gc_context, gc_root| { let root_data = gc_root.0.read(); let mut render_context = RenderContext { renderer: renderer.deref_mut(), + ui: ui.deref_mut(), library: &root_data.library, transform_stack, - view_bounds, + viewport_bounds, + view_bounds: Default::default(), // filled in by stage clip_depth_stack: vec![], allow_mask: true, }; root_data.stage.render(&mut render_context); }); - transform_stack.pop(); - if self.should_letterbox() { - self.draw_letterbox(); - } - - self.renderer.end_frame(); self.needs_render = false; } @@ -1182,54 +1174,6 @@ impl Player { } } - fn build_matrices(&mut self) { - // Create view matrix to scale stage into viewport area. - let (movie_width, movie_height) = (self.movie_width as f32, self.movie_height as f32); - let (viewport_width, viewport_height) = - (self.viewport_width as f32, self.viewport_height as f32); - let movie_aspect = movie_width / movie_height; - let viewport_aspect = viewport_width / viewport_height; - let (scale, margin_width, margin_height) = if viewport_aspect > movie_aspect { - let scale = viewport_height / movie_height; - (scale, (viewport_width - movie_width * scale) / 2.0, 0.0) - } else { - let scale = viewport_width / movie_width; - (scale, 0.0, (viewport_height - movie_height * scale) / 2.0) - }; - self.view_matrix = Matrix { - a: scale, - b: 0.0, - c: 0.0, - d: scale, - tx: Twips::from_pixels(margin_width.into()), - ty: Twips::from_pixels(margin_height.into()), - }; - self.inverse_view_matrix = self.view_matrix; - self.inverse_view_matrix.invert(); - - self.view_bounds = if self.should_letterbox() { - // No letterbox: movie area - BoundingBox { - x_min: Twips::zero(), - y_min: Twips::zero(), - x_max: Twips::from_pixels(f64::from(self.movie_width)), - y_max: Twips::from_pixels(f64::from(self.movie_height)), - valid: true, - } - } else { - // No letterbox: full visible stage area - let margin_width = f64::from(margin_width / scale); - let margin_height = f64::from(margin_height / scale); - BoundingBox { - x_min: Twips::from_pixels(-margin_width), - y_min: Twips::from_pixels(-margin_height), - x_max: Twips::from_pixels(f64::from(self.movie_width) + margin_width), - y_max: Twips::from_pixels(f64::from(self.movie_height) + margin_height), - valid: true, - } - }; - } - /// Runs the closure `f` with an `UpdateContext`. /// This takes cares of populating the `UpdateContext` struct, avoiding borrow issues. fn mutate_with_update_context(&mut self, f: F) -> R @@ -1241,7 +1185,6 @@ impl Player { let ( player_version, swf, - background_color, renderer, audio, navigator, @@ -1250,6 +1193,8 @@ impl Player { mouse_position, stage_width, stage_height, + viewport_width, + viewport_height, player, system_properties, instance_counter, @@ -1264,7 +1209,6 @@ impl Player { ) = ( self.player_version, &self.swf, - &mut self.background_color, self.renderer.deref_mut(), self.audio.deref_mut(), self.navigator.deref_mut(), @@ -1273,6 +1217,8 @@ impl Player { &self.mouse_pos, Twips::from_pixels(self.movie_width.into()), Twips::from_pixels(self.movie_height.into()), + Twips::from_pixels(self.viewport_width.into()), + Twips::from_pixels(self.viewport_height.into()), self.self_reference.clone(), &mut self.system, &mut self.instance_counter, @@ -1309,7 +1255,6 @@ impl Player { player_version, swf, library, - background_color, rng, renderer, audio, @@ -1322,6 +1267,7 @@ impl Player { mouse_position, drag_object, stage_size: (stage_width, stage_height), + viewport_size: (viewport_width, viewport_height), player, load_manager, system: system_properties, @@ -1464,58 +1410,6 @@ impl Player { pub fn set_max_execution_duration(&mut self, max_execution_duration: Duration) { self.max_execution_duration = max_execution_duration } - - fn draw_letterbox(&mut self) { - let black = Color::from_rgb(0, 255); - let viewport_width = self.viewport_width as f32; - let viewport_height = self.viewport_height as f32; - - let margin_width = self.view_matrix.tx.to_pixels() as f32; - let margin_height = self.view_matrix.ty.to_pixels() as f32; - if margin_height > 0.0 { - self.renderer.draw_rect( - black.clone(), - &Matrix::create_box( - viewport_width, - margin_height, - 0.0, - Twips::default(), - Twips::default(), - ), - ); - self.renderer.draw_rect( - black, - &Matrix::create_box( - viewport_width, - margin_height, - 0.0, - Twips::default(), - Twips::from_pixels((viewport_height - margin_height) as f64), - ), - ); - } else if margin_width > 0.0 { - self.renderer.draw_rect( - black.clone(), - &Matrix::create_box( - margin_width, - viewport_height, - 0.0, - Twips::default(), - Twips::default(), - ), - ); - self.renderer.draw_rect( - black, - &Matrix::create_box( - margin_width, - viewport_height, - 0.0, - Twips::from_pixels((viewport_width - margin_width) as f64), - Twips::default(), - ), - ); - } - } } #[derive(Collect)]