From e4caedcc12cdfaac163651ca46e90da0e935a1a2 Mon Sep 17 00:00:00 2001 From: Nathan Adams Date: Fri, 24 Mar 2023 15:34:44 +0100 Subject: [PATCH] core: Move draw from BitmapData to bitmap_data_operations --- core/src/avm1/globals/bitmap_data.rs | 13 +- .../avm2/globals/flash/display/bitmap_data.rs | 32 ++--- core/src/bitmap/bitmap_data.rs | 130 +----------------- core/src/bitmap/bitmap_data_operations.rs | 124 ++++++++++++++++- 4 files changed, 141 insertions(+), 158 deletions(-) diff --git a/core/src/avm1/globals/bitmap_data.rs b/core/src/avm1/globals/bitmap_data.rs index 05ba58f94..c008a3ce1 100644 --- a/core/src/avm1/globals/bitmap_data.rs +++ b/core/src/avm1/globals/bitmap_data.rs @@ -548,11 +548,10 @@ pub fn draw<'gc>( // Do this last, so that we only call `overwrite_cpu_pixels_from_gpu` // if we're actually going to draw something. - let (bmd, dirty_area) = bitmap_data - .bitmap_data_wrapper() - .overwrite_cpu_pixels_from_gpu(&mut activation.context); - let mut write = bmd.write(activation.context.gc_context); - match write.draw( + let quality = activation.context.stage.quality(); + match bitmap_data_operations::draw( + &mut activation.context, + bitmap_data.bitmap_data_wrapper(), source, Transform { matrix, @@ -561,9 +560,7 @@ pub fn draw<'gc>( smoothing, blend_mode, None, - activation.context.stage.quality(), - &mut activation.context, - dirty_area, + quality, ) { Ok(()) => {} Err(BitmapDataDrawError::Unimplemented) => { diff --git a/core/src/avm2/globals/flash/display/bitmap_data.rs b/core/src/avm2/globals/flash/display/bitmap_data.rs index 550b9f457..c936c0df2 100644 --- a/core/src/avm2/globals/flash/display/bitmap_data.rs +++ b/core/src/avm2/globals/flash/display/bitmap_data.rs @@ -828,23 +828,22 @@ pub fn draw<'gc>( return Err(format!("BitmapData.draw: unexpected source {source:?}").into()); }; - // Drawing onto a BitmapData doesn't use any of the CPU-side pixels - // Do this last, so that we only call `overwrite_cpu_pixels_from_gpu` - // if we're actually going to draw something. - let (bitmap_data, dirty_area) = - bitmap_data.overwrite_cpu_pixels_from_gpu(&mut activation.context); // If the bitmapdata is invalid, it's fine to return early, since the pixels // are inaccessible - bitmap_data.read().check_valid(activation)?; - match bitmap_data.write(activation.context.gc_context).draw( + bitmap_data.check_valid(activation)?; + + // Do this last, so that we only call `overwrite_cpu_pixels_from_gpu` + // if we're actually going to draw something. + let quality = activation.context.stage.quality(); + match bitmap_data_operations::draw( + &mut activation.context, + bitmap_data, source, transform, smoothing, blend_mode, clip_rect, - activation.context.stage.quality(), - &mut activation.context, - dirty_area, + quality, ) { Ok(()) => {} Err(BitmapDataDrawError::Unimplemented) => { @@ -862,10 +861,6 @@ pub fn draw_with_quality<'gc>( args: &[Value<'gc>], ) -> Result, Error<'gc>> { if let Some(bitmap_data) = this.and_then(|this| this.as_bitmap_data_wrapper()) { - // Drawing onto a BitmapData doesn't use any of the CPU-side pixels - let (bitmap_data, dirty_area) = - bitmap_data.overwrite_cpu_pixels_from_gpu(&mut activation.context); - bitmap_data.read().check_valid(activation)?; let mut transform = Transform::default(); let mut blend_mode = BlendMode::Normal; @@ -900,7 +895,6 @@ pub fn draw_with_quality<'gc>( )?); } - let mut bitmap_data = bitmap_data.write(activation.context.gc_context); let smoothing = args.get_bool(5); let source = args.get_object(activation, 0, "source")?; @@ -929,21 +923,21 @@ pub fn draw_with_quality<'gc>( activation.context.stage.quality() }; - match bitmap_data.draw( + match bitmap_data_operations::draw( + &mut activation.context, + bitmap_data, source, transform, smoothing, blend_mode, clip_rect, quality, - &mut activation.context, - dirty_area, ) { Ok(()) => {} Err(BitmapDataDrawError::Unimplemented) => { return Err("Render backend does not support BitmapData.draw".into()); } - } + }; } Ok(Value::Undefined) } diff --git a/core/src/bitmap/bitmap_data.rs b/core/src/bitmap/bitmap_data.rs index 0f1f63eff..1363cf587 100644 --- a/core/src/bitmap/bitmap_data.rs +++ b/core/src/bitmap/bitmap_data.rs @@ -1,20 +1,13 @@ use crate::avm2::{Error, Object as Avm2Object, Value as Avm2Value}; -use crate::context::RenderContext; -use crate::context::UpdateContext; -use crate::display_object::DisplayObject; -use crate::display_object::TDisplayObject; +use crate::display_object::{DisplayObject, TDisplayObject}; use bitflags::bitflags; use core::fmt; use gc_arena::Collect; use ruffle_render::backend::RenderBackend; use ruffle_render::bitmap::{Bitmap, BitmapFormat, BitmapHandle, PixelRegion, SyncHandle}; -use ruffle_render::commands::{CommandHandler, CommandList}; -use ruffle_render::matrix::Matrix; -use ruffle_render::quality::StageQuality; -use ruffle_render::transform::Transform; use ruffle_wstr::WStr; use std::ops::Range; -use swf::{BlendMode, Rectangle, Twips}; +use swf::Twips; use tracing::instrument; /// An implementation of the Lehmer/Park-Miller random number generator @@ -658,125 +651,6 @@ impl<'gc> BitmapData<'gc> { pub fn init_object2(&mut self, object: Avm2Object<'gc>) { self.avm2_object = Some(object) } - - #[allow(clippy::too_many_arguments)] - pub fn draw( - &mut self, - mut source: IBitmapDrawable<'gc>, - transform: Transform, - smoothing: bool, - blend_mode: BlendMode, - clip_rect: Option>, - quality: StageQuality, - context: &mut UpdateContext<'_, 'gc>, - include_dirty_area: Option, - ) -> Result<(), BitmapDataDrawError> { - let bitmapdata_width = self.width(); - let bitmapdata_height = self.height(); - - let mut transform_stack = ruffle_render::transform::TransformStack::new(); - transform_stack.push(&transform); - let handle = self.bitmap_handle(context.renderer).unwrap(); - - // Calculate the maximum potential area that this draw call will affect - let matrix = transform_stack.transform().matrix; - let bounds = source.bounds(); - - let mut dirty_region = - PixelRegion::encompassing_twips(matrix * bounds.0, matrix * bounds.1); - dirty_region.clamp(bitmapdata_width, bitmapdata_height); - - // If we have another dirty area to preserve, expand this to include it - if let Some(old) = include_dirty_area { - dirty_region.union(old); - } - - let mut render_context = RenderContext { - renderer: context.renderer, - commands: CommandList::new(), - gc_context: context.gc_context, - library: context.library, - transform_stack: &mut transform_stack, - is_offscreen: true, - stage: context.stage, - allow_mask: true, - }; - - // Make the screen opacity match the opacity of this bitmap - - let clip_mat = clip_rect.map(|clip_rect| { - // Note - we do *not* apply the matrix to the clip rect, - // to match Flash's behavior. - let clip_mat = Matrix { - a: (clip_rect.x_max - clip_rect.x_min).to_pixels() as f32, - b: 0.0, - c: 0.0, - d: (clip_rect.y_max - clip_rect.y_min).to_pixels() as f32, - tx: clip_rect.x_min, - ty: clip_rect.y_min, - }; - - render_context.commands.push_mask(); - // The color doesn't matter, as this is a mask. - render_context - .commands - .draw_rect(swf::Color::BLACK, clip_mat); - render_context.commands.activate_mask(); - - clip_mat - }); - - match &mut source { - IBitmapDrawable::BitmapData(data) => { - data.render(smoothing, &mut render_context); - } - IBitmapDrawable::DisplayObject(object) => { - // Note that we do *not* use `render_base`, - // as we want to ignore the object's mask and normal transform - object.render_self(&mut render_context); - } - } - - if let Some(clip_mat) = clip_mat { - // Draw the rectangle again after deactivating the mask, - // to reset the stencil buffer. - render_context.commands.deactivate_mask(); - render_context - .commands - .draw_rect(swf::Color::BLACK, clip_mat); - render_context.commands.pop_mask(); - } - - self.update_dirty_texture(render_context.renderer); - - let commands = if blend_mode == BlendMode::Normal { - render_context.commands - } else { - let mut commands = CommandList::new(); - commands.blend(render_context.commands, blend_mode); - commands - }; - - let image = context - .renderer - .render_offscreen(handle, commands, quality, dirty_region); - - match image { - Some(sync_handle) => { - match self.dirty_state { - DirtyState::Clean => { - self.dirty_state = DirtyState::GpuModified(sync_handle, dirty_region) - } - DirtyState::CpuModified(_) | DirtyState::GpuModified(_, _) => panic!( - "Called BitmapData.render while already dirty: {:?}", - self.dirty_state - ), - } - Ok(()) - } - None => Err(BitmapDataDrawError::Unimplemented), - } - } } pub enum IBitmapDrawable<'gc> { diff --git a/core/src/bitmap/bitmap_data_operations.rs b/core/src/bitmap/bitmap_data_operations.rs index 45e6a9e97..cd095e427 100644 --- a/core/src/bitmap/bitmap_data_operations.rs +++ b/core/src/bitmap/bitmap_data_operations.rs @@ -1,12 +1,18 @@ use crate::bitmap::bitmap_data::{ - BitmapData, BitmapDataWrapper, ChannelOptions, Color, LehmerRng, ThresholdOperation, + BitmapData, BitmapDataDrawError, BitmapDataWrapper, ChannelOptions, Color, IBitmapDrawable, + LehmerRng, ThresholdOperation, }; use crate::bitmap::turbulence::Turbulence; -use crate::context::UpdateContext; +use crate::context::{RenderContext, UpdateContext}; +use crate::display_object::TDisplayObject; use gc_arena::GcCell; use ruffle_render::bitmap::PixelRegion; +use ruffle_render::commands::{CommandHandler, CommandList}; use ruffle_render::filters::Filter; -use swf::{ColorTransform, Fixed8}; +use ruffle_render::matrix::Matrix; +use ruffle_render::quality::StageQuality; +use ruffle_render::transform::Transform; +use swf::{BlendMode, ColorTransform, Fixed8, Rectangle, Twips}; /// AVM1 and AVM2 have a shared set of operations they can perform on BitmapDatas. /// Instead of directly manipulating the BitmapData in each place, they should call @@ -1186,3 +1192,115 @@ pub fn apply_filter<'gc>( } } } + +#[allow(clippy::too_many_arguments)] +pub fn draw<'gc>( + context: &mut UpdateContext<'_, 'gc>, + target: BitmapDataWrapper<'gc>, + mut source: IBitmapDrawable<'gc>, + transform: Transform, + smoothing: bool, + blend_mode: BlendMode, + clip_rect: Option>, + quality: StageQuality, +) -> Result<(), BitmapDataDrawError> { + let (target, include_dirty_area) = target.overwrite_cpu_pixels_from_gpu(context); + let mut write = target.write(context.gc_context); + + let bitmapdata_width = write.width(); + let bitmapdata_height = write.height(); + + let mut transform_stack = ruffle_render::transform::TransformStack::new(); + transform_stack.push(&transform); + let handle = write.bitmap_handle(context.renderer).unwrap(); + + // Calculate the maximum potential area that this draw call will affect + let matrix = transform_stack.transform().matrix; + let bounds = source.bounds(); + + let mut dirty_region = PixelRegion::encompassing_twips(matrix * bounds.0, matrix * bounds.1); + dirty_region.clamp(bitmapdata_width, bitmapdata_height); + + // If we have another dirty area to preserve, expand this to include it + if let Some(old) = include_dirty_area { + dirty_region.union(old); + } + + let mut render_context = RenderContext { + renderer: context.renderer, + commands: CommandList::new(), + gc_context: context.gc_context, + library: context.library, + transform_stack: &mut transform_stack, + is_offscreen: true, + stage: context.stage, + allow_mask: true, + }; + + // Make the screen opacity match the opacity of this bitmap + + let clip_mat = clip_rect.map(|clip_rect| { + // Note - we do *not* apply the matrix to the clip rect, + // to match Flash's behavior. + let clip_mat = Matrix { + a: (clip_rect.x_max - clip_rect.x_min).to_pixels() as f32, + b: 0.0, + c: 0.0, + d: (clip_rect.y_max - clip_rect.y_min).to_pixels() as f32, + tx: clip_rect.x_min, + ty: clip_rect.y_min, + }; + + render_context.commands.push_mask(); + // The color doesn't matter, as this is a mask. + render_context + .commands + .draw_rect(swf::Color::BLACK, clip_mat); + render_context.commands.activate_mask(); + + clip_mat + }); + + match &mut source { + IBitmapDrawable::BitmapData(data) => { + data.render(smoothing, &mut render_context); + } + IBitmapDrawable::DisplayObject(object) => { + // Note that we do *not* use `render_base`, + // as we want to ignore the object's mask and normal transform + object.render_self(&mut render_context); + } + } + + if let Some(clip_mat) = clip_mat { + // Draw the rectangle again after deactivating the mask, + // to reset the stencil buffer. + render_context.commands.deactivate_mask(); + render_context + .commands + .draw_rect(swf::Color::BLACK, clip_mat); + render_context.commands.pop_mask(); + } + + write.update_dirty_texture(render_context.renderer); + + let commands = if blend_mode == BlendMode::Normal { + render_context.commands + } else { + let mut commands = CommandList::new(); + commands.blend(render_context.commands, blend_mode); + commands + }; + + let image = context + .renderer + .render_offscreen(handle, commands, quality, dirty_region); + + match image { + Some(sync_handle) => { + write.set_gpu_dirty(sync_handle, dirty_region); + Ok(()) + } + None => Err(BitmapDataDrawError::Unimplemented), + } +}