core: Move draw from BitmapData to bitmap_data_operations

This commit is contained in:
Nathan Adams 2023-03-24 15:34:44 +01:00
parent 57648b6380
commit e4caedcc12
4 changed files with 141 additions and 158 deletions

View File

@ -548,11 +548,10 @@ pub fn draw<'gc>(
// Do this last, so that we only call `overwrite_cpu_pixels_from_gpu` // Do this last, so that we only call `overwrite_cpu_pixels_from_gpu`
// if we're actually going to draw something. // if we're actually going to draw something.
let (bmd, dirty_area) = bitmap_data let quality = activation.context.stage.quality();
.bitmap_data_wrapper() match bitmap_data_operations::draw(
.overwrite_cpu_pixels_from_gpu(&mut activation.context); &mut activation.context,
let mut write = bmd.write(activation.context.gc_context); bitmap_data.bitmap_data_wrapper(),
match write.draw(
source, source,
Transform { Transform {
matrix, matrix,
@ -561,9 +560,7 @@ pub fn draw<'gc>(
smoothing, smoothing,
blend_mode, blend_mode,
None, None,
activation.context.stage.quality(), quality,
&mut activation.context,
dirty_area,
) { ) {
Ok(()) => {} Ok(()) => {}
Err(BitmapDataDrawError::Unimplemented) => { Err(BitmapDataDrawError::Unimplemented) => {

View File

@ -828,23 +828,22 @@ pub fn draw<'gc>(
return Err(format!("BitmapData.draw: unexpected source {source:?}").into()); 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 // If the bitmapdata is invalid, it's fine to return early, since the pixels
// are inaccessible // are inaccessible
bitmap_data.read().check_valid(activation)?; bitmap_data.check_valid(activation)?;
match bitmap_data.write(activation.context.gc_context).draw(
// 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, source,
transform, transform,
smoothing, smoothing,
blend_mode, blend_mode,
clip_rect, clip_rect,
activation.context.stage.quality(), quality,
&mut activation.context,
dirty_area,
) { ) {
Ok(()) => {} Ok(()) => {}
Err(BitmapDataDrawError::Unimplemented) => { Err(BitmapDataDrawError::Unimplemented) => {
@ -862,10 +861,6 @@ pub fn draw_with_quality<'gc>(
args: &[Value<'gc>], args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> { ) -> Result<Value<'gc>, Error<'gc>> {
if let Some(bitmap_data) = this.and_then(|this| this.as_bitmap_data_wrapper()) { 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 transform = Transform::default();
let mut blend_mode = BlendMode::Normal; 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 smoothing = args.get_bool(5);
let source = args.get_object(activation, 0, "source")?; let source = args.get_object(activation, 0, "source")?;
@ -929,21 +923,21 @@ pub fn draw_with_quality<'gc>(
activation.context.stage.quality() activation.context.stage.quality()
}; };
match bitmap_data.draw( match bitmap_data_operations::draw(
&mut activation.context,
bitmap_data,
source, source,
transform, transform,
smoothing, smoothing,
blend_mode, blend_mode,
clip_rect, clip_rect,
quality, quality,
&mut activation.context,
dirty_area,
) { ) {
Ok(()) => {} Ok(()) => {}
Err(BitmapDataDrawError::Unimplemented) => { Err(BitmapDataDrawError::Unimplemented) => {
return Err("Render backend does not support BitmapData.draw".into()); return Err("Render backend does not support BitmapData.draw".into());
} }
} };
} }
Ok(Value::Undefined) Ok(Value::Undefined)
} }

View File

@ -1,20 +1,13 @@
use crate::avm2::{Error, Object as Avm2Object, Value as Avm2Value}; use crate::avm2::{Error, Object as Avm2Object, Value as Avm2Value};
use crate::context::RenderContext; use crate::display_object::{DisplayObject, TDisplayObject};
use crate::context::UpdateContext;
use crate::display_object::DisplayObject;
use crate::display_object::TDisplayObject;
use bitflags::bitflags; use bitflags::bitflags;
use core::fmt; use core::fmt;
use gc_arena::Collect; use gc_arena::Collect;
use ruffle_render::backend::RenderBackend; use ruffle_render::backend::RenderBackend;
use ruffle_render::bitmap::{Bitmap, BitmapFormat, BitmapHandle, PixelRegion, SyncHandle}; 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 ruffle_wstr::WStr;
use std::ops::Range; use std::ops::Range;
use swf::{BlendMode, Rectangle, Twips}; use swf::Twips;
use tracing::instrument; use tracing::instrument;
/// An implementation of the Lehmer/Park-Miller random number generator /// 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>) { pub fn init_object2(&mut self, object: Avm2Object<'gc>) {
self.avm2_object = Some(object) 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<Rectangle<Twips>>,
quality: StageQuality,
context: &mut UpdateContext<'_, 'gc>,
include_dirty_area: Option<PixelRegion>,
) -> 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> { pub enum IBitmapDrawable<'gc> {

View File

@ -1,12 +1,18 @@
use crate::bitmap::bitmap_data::{ 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::bitmap::turbulence::Turbulence;
use crate::context::UpdateContext; use crate::context::{RenderContext, UpdateContext};
use crate::display_object::TDisplayObject;
use gc_arena::GcCell; use gc_arena::GcCell;
use ruffle_render::bitmap::PixelRegion; use ruffle_render::bitmap::PixelRegion;
use ruffle_render::commands::{CommandHandler, CommandList};
use ruffle_render::filters::Filter; 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. /// 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 /// 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<Rectangle<Twips>>,
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),
}
}