core: Store data in `BitmapData` instead of `Bitmap`

This makes `Bitmap` delegate to `BitmapData` for
all of the bitmap-related information (handle, width, and height).
As a result, we now unconditionally store a `BitmapData` in `Bitmap`.

As a result, swapping the underling `BitmapData` instance will
automatically change the properties (and rendered image) of a `Bitmap`.

This required some refactoring in the render backends in order to
get access to a `BitmapHandle` through `BitmapData`.
This commit is contained in:
Aaron Hill 2022-10-19 15:30:23 -05:00 committed by kmeisthax
parent 41df7fdc59
commit 10491a1be9
9 changed files with 205 additions and 237 deletions

View File

@ -253,10 +253,6 @@ fn attach_bitmap<'gc>(
.coerce_to_i32(activation)?
.wrapping_add(AVM_DEPTH_BIAS);
let bitmap_handle = bitmap_data
.write(activation.context.gc_context)
.bitmap_handle(activation.context.renderer);
// TODO: Implement pixel snapping
let _pixel_snapping = args
.get(2)
@ -268,29 +264,20 @@ fn attach_bitmap<'gc>(
.unwrap_or(&Value::Undefined)
.as_bool(activation.swf_version());
if let Some(bitmap_handle) = bitmap_handle {
//TODO: do attached BitmapDatas have character ids?
let display_object = Bitmap::new_with_bitmap_data(
&mut activation.context,
0,
Some(bitmap_handle),
bitmap_data.read().width() as u16,
bitmap_data.read().height() as u16,
Some(bitmap_data),
smoothing,
);
movie_clip.replace_at_depth(
&mut activation.context,
display_object.into(),
depth,
);
display_object.post_instantiation(
&mut activation.context,
None,
Instantiator::Avm1,
true,
);
}
//TODO: do attached BitmapDatas have character ids?
let display_object = Bitmap::new_with_bitmap_data(
&mut activation.context,
0,
bitmap_data,
smoothing,
);
movie_clip.replace_at_depth(&mut activation.context, display_object.into(), depth);
display_object.post_instantiation(
&mut activation.context,
None,
Instantiator::Avm1,
true,
);
}
}
}

View File

@ -10,6 +10,7 @@ use crate::avm2::Error;
use crate::avm2::Multiname;
use crate::avm2::Namespace;
use crate::avm2::QName;
use crate::bitmap::bitmap_data::BitmapData;
use crate::character::Character;
use crate::display_object::{Bitmap, TDisplayObject};
@ -43,96 +44,80 @@ pub fn instance_init<'gc>(
.coerce_to_boolean();
if let Some(bitmap) = this.as_display_object().and_then(|dobj| dobj.as_bitmap()) {
if bitmap.bitmap_data().is_none() {
//We are being initialized by the movie. This means that we
//need to create bitmap data right away, since all AVM2 bitmaps
//hold bitmap data.
//We are being initialized by the movie. This means that we
//need to create bitmap data right away, since all AVM2 bitmaps
//hold bitmap data.
let bd_object = if let Some(bd_class) = bitmap.avm2_bitmapdata_class() {
bd_class.construct(activation, &[])?
} else if let Some(b_class) = bitmap.avm2_bitmap_class() {
// Instantiating Bitmap from a Flex-style bitmap asset.
// Contrary to the above comment, this code path DOES
// trigger from AVM2, since the DisplayObject instantiation
// logic does its job in this case.
if let Some((movie, symbol_id)) = activation
let bd_object = if let Some(bd_class) = bitmap.avm2_bitmapdata_class() {
bd_class.construct(activation, &[])?
} else if let Some(b_class) = bitmap.avm2_bitmap_class() {
// Instantiating Bitmap from a Flex-style bitmap asset.
// Contrary to the above comment, this code path DOES
// trigger from AVM2, since the DisplayObject instantiation
// logic does its job in this case.
if let Some((movie, symbol_id)) = activation
.context
.library
.avm2_class_registry()
.class_symbol(b_class)
{
if let Some(Character::Bitmap {
bitmap,
initial_data,
}) = activation
.context
.library
.avm2_class_registry()
.class_symbol(b_class)
.library_for_movie_mut(movie)
.character_by_id(symbol_id)
.cloned()
{
if let Some(Character::Bitmap {
bitmap,
initial_data,
}) = activation
.context
.library
.library_for_movie_mut(movie)
.character_by_id(symbol_id)
.cloned()
{
let new_bitmap_data = GcCell::allocate(
activation.context.gc_context,
BitmapData::default(),
);
let new_bitmap_data =
GcCell::allocate(activation.context.gc_context, BitmapData::default());
fill_bitmap_data_from_symbol(
activation,
bitmap,
new_bitmap_data,
initial_data,
);
BitmapDataObject::from_bitmap_data(
activation,
new_bitmap_data,
activation.context.avm2.classes().bitmapdata,
)?
} else {
//Class association not to a Bitmap
return Err("Attempted to instantiate Bitmap from timeline with symbol class associated to non-Bitmap!".into());
}
fill_bitmap_data_from_symbol(
activation,
bitmap,
new_bitmap_data,
initial_data,
);
BitmapDataObject::from_bitmap_data(
activation,
new_bitmap_data,
activation.context.avm2.classes().bitmapdata,
)?
} else {
//Class association not bidirectional
return Err("Cannot instantiate Bitmap from timeline without bidirectional symbol class association".into());
//Class association not to a Bitmap
return Err("Attempted to instantiate Bitmap from timeline with symbol class associated to non-Bitmap!".into());
}
} else {
// No class association
return Err(
"Cannot instantiate Bitmap from timeline without associated symbol class"
.into(),
);
};
//Class association not bidirectional
return Err("Cannot instantiate Bitmap from timeline without bidirectional symbol class association".into());
}
} else {
// No class association
return Err(
"Cannot instantiate Bitmap from timeline without associated symbol class"
.into(),
);
};
this.set_property(
&Multiname::public("bitmapData"),
bd_object.into(),
activation,
)?;
}
this.set_property(
&Multiname::public("bitmapData"),
bd_object.into(),
activation,
)?;
bitmap.set_smoothing(activation.context.gc_context, smoothing);
} else {
//We are being initialized by AVM2 (and aren't associated with a
//Bitmap subclass).
let bitmap_handle = if let Some(bd) = bitmap_data {
bd.write(activation.context.gc_context)
.bitmap_handle(activation.context.renderer)
} else {
None
};
let width = bitmap_data.map(|bd| bd.read().width()).unwrap_or(0) as u16;
let height = bitmap_data.map(|bd| bd.read().height()).unwrap_or(0) as u16;
let bitmap_data = bitmap_data.unwrap_or_else(|| {
GcCell::allocate(activation.context.gc_context, BitmapData::dummy())
});
let bitmap = Bitmap::new_with_bitmap_data(
&mut activation.context,
0,
bitmap_handle,
width,
height,
bitmap_data,
smoothing,
);
let bitmap =
Bitmap::new_with_bitmap_data(&mut activation.context, 0, bitmap_data, smoothing);
this.init_display_object(activation.context.gc_context, bitmap.into());
}
@ -160,10 +145,13 @@ pub fn bitmap_data<'gc>(
.and_then(|this| this.as_display_object())
.and_then(|dobj| dobj.as_bitmap())
{
return Ok(bitmap
.bitmap_data()
.map(|bd| bd.read().object2())
.unwrap_or(Value::Null));
let mut value = bitmap.bitmap_data().read().object2();
// AS3 expects an unset BitmapData to be null, not 'undefined'
if matches!(value, Value::Undefined) {
value = Value::Null;
}
return Ok(value);
}
Ok(Value::Undefined)
@ -179,13 +167,15 @@ pub fn set_bitmap_data<'gc>(
.and_then(|this| this.as_display_object())
.and_then(|dobj| dobj.as_bitmap())
{
let bitmap_data = args
.get(0)
.cloned()
.unwrap_or(Value::Null)
.as_object()
.and_then(|bd| bd.as_bitmap_data());
let bitmap_data = args.get(0).unwrap_or(&Value::Null);
let bitmap_data = if matches!(bitmap_data, Value::Null) {
GcCell::allocate(activation.context.gc_context, BitmapData::dummy())
} else {
bitmap_data
.coerce_to_object(activation)?
.as_bitmap_data()
.ok_or_else(|| Error::RustError("Argument was not a BitmapData".into()))?
};
bitmap.set_bitmap_data(&mut activation.context, bitmap_data);
}

View File

@ -200,6 +200,23 @@ impl fmt::Debug for BitmapData<'_> {
}
impl<'gc> BitmapData<'gc> {
// Creates a dummy BitmapData with no pixels or handle, marked as disposed.
// This is used for AS3 `Bitmap` instances without a corresponding AS3 `BitmapData` instance.
// Marking it as disposed skips rendering, and the unset `avm2_object` will cause this to
// be inaccessible to AS3 code.
pub fn dummy() -> Self {
BitmapData {
pixels: Vec::new(),
dirty: false,
width: 0,
height: 0,
transparency: false,
disposed: true,
bitmap_handle: None,
avm2_object: None,
}
}
pub fn init_pixels(&mut self, width: u32, height: u32, transparency: bool, fill_color: i32) {
self.width = width;
self.height = height;

View File

@ -10,8 +10,8 @@ use crate::display_object::{DisplayObjectBase, DisplayObjectPtr, TDisplayObject}
use crate::prelude::*;
use crate::vminterface::Instantiator;
use core::fmt;
use gc_arena::{Collect, Gc, GcCell, MutationContext};
use ruffle_render::bitmap::BitmapHandle;
use gc_arena::{Collect, GcCell, MutationContext};
use ruffle_render::bitmap::BitmapFormat;
use ruffle_render::commands::CommandHandler;
use std::cell::{Ref, RefMut};
@ -64,19 +64,10 @@ impl fmt::Debug for Bitmap<'_> {
#[collect(no_drop)]
pub struct BitmapData<'gc> {
base: DisplayObjectBase<'gc>,
static_data: Gc<'gc, BitmapStatic>,
id: CharacterId,
/// The current bitmap data object.
bitmap_data: Option<GcCell<'gc, crate::bitmap::bitmap_data::BitmapData<'gc>>>,
/// The current bitmap handle.
///
/// This needs to be cached separately from the associated bitmap data so
/// that it can be accessed without a mutation context.
///
/// If this is `None`, then the bitmap does not render anything.
#[collect(require_static)]
bitmap_handle: Option<BitmapHandle>,
bitmap_data: GcCell<'gc, crate::bitmap::bitmap_data::BitmapData<'gc>>,
/// Whether or not bitmap smoothing is enabled.
smoothing: bool,
@ -102,10 +93,7 @@ impl<'gc> Bitmap<'gc> {
pub fn new_with_bitmap_data(
context: &mut UpdateContext<'_, 'gc, '_>,
id: CharacterId,
bitmap_handle: Option<BitmapHandle>,
width: u16,
height: u16,
bitmap_data: Option<GcCell<'gc, crate::bitmap::bitmap_data::BitmapData<'gc>>>,
bitmap_data: GcCell<'gc, crate::bitmap::bitmap_data::BitmapData<'gc>>,
smoothing: bool,
) -> Self {
//NOTE: We do *not* solicit a handle from the `bitmap_data` at this
@ -115,9 +103,8 @@ impl<'gc> Bitmap<'gc> {
context.gc_context,
BitmapData {
base: Default::default(),
static_data: Gc::allocate(context.gc_context, BitmapStatic { id, width, height }),
id,
bitmap_data,
bitmap_handle,
smoothing,
avm2_object: None,
avm2_bitmap_class: BitmapClass::NoSubclass,
@ -131,45 +118,43 @@ impl<'gc> Bitmap<'gc> {
id: CharacterId,
bitmap: ruffle_render::bitmap::Bitmap,
) -> Result<Self, ruffle_render::error::Error> {
let width = bitmap.width() as u16;
let height = bitmap.height() as u16;
let bitmap_handle = context.renderer.register_bitmap(bitmap)?;
let bitmap_data = None;
let width = bitmap.width();
let height = bitmap.height();
let pixels: Vec<_> = bitmap
.as_colors()
.map(crate::bitmap::bitmap_data::Color::from)
.collect();
let mut bitmap_data = crate::bitmap::bitmap_data::BitmapData::default();
bitmap_data.set_pixels(
width,
height,
match bitmap.format() {
BitmapFormat::Rgba => true,
BitmapFormat::Rgb => false,
},
pixels,
);
let bitmap_data = GcCell::allocate(context.gc_context, bitmap_data);
let smoothing = true;
Ok(Self::new_with_bitmap_data(
context,
id,
Some(bitmap_handle),
width,
height,
bitmap_data,
smoothing,
))
}
#[allow(dead_code)]
pub fn bitmap_handle(self) -> Option<BitmapHandle> {
self.0.read().bitmap_handle.clone()
}
pub fn width(self) -> u16 {
let read = self.0.read();
read.bitmap_data
.map(|bd| bd.read().width() as u16)
.unwrap_or_else(|| read.static_data.width)
self.0.read().bitmap_data.read().width() as u16
}
pub fn height(self) -> u16 {
let read = self.0.read();
read.bitmap_data
.map(|bd| bd.read().height() as u16)
.unwrap_or_else(|| read.static_data.height)
self.0.read().bitmap_data.read().height() as u16
}
/// Retrieve the bitmap data associated with this `Bitmap`.
pub fn bitmap_data(self) -> Option<GcCell<'gc, crate::bitmap::bitmap_data::BitmapData<'gc>>> {
pub fn bitmap_data(self) -> GcCell<'gc, crate::bitmap::bitmap_data::BitmapData<'gc>> {
self.0.read().bitmap_data
}
@ -184,25 +169,9 @@ impl<'gc> Bitmap<'gc> {
pub fn set_bitmap_data(
self,
context: &mut UpdateContext<'_, 'gc, '_>,
bitmap_data: Option<GcCell<'gc, crate::bitmap::bitmap_data::BitmapData<'gc>>>,
bitmap_data: GcCell<'gc, crate::bitmap::bitmap_data::BitmapData<'gc>>,
) {
if let Some(bitmap_data) = bitmap_data {
let bitmap_handle = bitmap_data
.write(context.gc_context)
.bitmap_handle(context.renderer);
let mut write = self.0.write(context.gc_context);
write.bitmap_data = Some(bitmap_data);
if let Some(bitmap_handle) = bitmap_handle {
write.bitmap_handle = Some(bitmap_handle);
}
} else {
let mut write = self.0.write(context.gc_context);
write.bitmap_data = None;
write.bitmap_handle = None;
}
self.0.write(context.gc_context).bitmap_data = bitmap_data;
}
pub fn avm2_bitmapdata_class(self) -> Option<Avm2ClassObject<'gc>> {
@ -262,7 +231,7 @@ impl<'gc> TDisplayObject<'gc> for Bitmap<'gc> {
}
fn id(&self) -> CharacterId {
self.0.read().static_data.id
self.0.read().id
}
fn self_bounds(&self) -> BoundingBox {
@ -317,20 +286,24 @@ impl<'gc> TDisplayObject<'gc> for Bitmap<'gc> {
}
let bitmap_data = self.0.read();
if let Some(bitmap_handle) = &bitmap_data.bitmap_handle {
if let Some(inner_bitmap_data) = bitmap_data.bitmap_data {
if let Ok(mut bd) = inner_bitmap_data.try_write(context.gc_context) {
bd.update_dirty_texture(context);
} else {
return; // bail, this is caused by recursive render attempt. TODO: support this.
};
let inner_bitmap_data = bitmap_data.bitmap_data.try_write(context.gc_context);
if let Ok(mut inner_bitmap_data) = inner_bitmap_data {
if inner_bitmap_data.disposed() {
return;
}
inner_bitmap_data.update_dirty_texture(context);
let handle = inner_bitmap_data
.bitmap_handle(context.renderer)
.expect("Missing bitmap handle");
context.commands.render_bitmap(
&bitmap_handle,
&handle,
context.transform_stack.transform(),
bitmap_data.smoothing,
);
} else {
//this is caused by recursive render attempt. TODO: support this.
}
}
@ -350,12 +323,3 @@ impl<'gc> TDisplayObject<'gc> for Bitmap<'gc> {
Some(self)
}
}
/// Static data shared between all instances of a bitmap.
#[derive(Clone, Collect)]
#[collect(no_drop)]
struct BitmapStatic {
id: CharacterId,
width: u16,
height: u16,
}

View File

@ -5,6 +5,7 @@ use crate::avm2::{
use crate::context::{RenderContext, UpdateContext};
use crate::display_object::{DisplayObjectBase, DisplayObjectPtr, TDisplayObject};
use crate::drawing::Drawing;
use crate::library::MovieLibrarySource;
use crate::prelude::*;
use crate::tag_utils::SwfMovie;
use crate::vminterface::Instantiator;
@ -47,11 +48,13 @@ impl<'gc> Graphic<'gc> {
let static_data = GraphicStatic {
id: swf_shape.id,
bounds: (&swf_shape.shape_bounds).into(),
render_handle: Some(
context
.renderer
.register_shape((&swf_shape).into(), library),
),
render_handle: Some(context.renderer.register_shape(
(&swf_shape).into(),
&MovieLibrarySource {
library,
gc_context: context.gc_context,
},
)),
shape: swf_shape,
movie: Some(movie),
};

View File

@ -1,11 +1,11 @@
use crate::context::{RenderContext, UpdateContext};
use crate::display_object::{DisplayObjectBase, DisplayObjectPtr, TDisplayObject};
use crate::library::Library;
use crate::library::{Library, MovieLibrarySource};
use crate::prelude::*;
use crate::tag_utils::SwfMovie;
use core::fmt;
use gc_arena::{Collect, Gc, GcCell, MutationContext};
use ruffle_render::backend::{RenderBackend, ShapeHandle};
use ruffle_render::backend::ShapeHandle;
use ruffle_render::commands::CommandHandler;
use std::cell::{Ref, RefCell, RefMut};
use std::sync::Arc;
@ -102,7 +102,7 @@ impl<'gc> TDisplayObject<'gc> for MorphShape<'gc> {
let this = self.0.read();
let ratio = this.ratio;
let static_data = this.static_data;
let shape_handle = static_data.get_shape(context.renderer, context.library, ratio);
let shape_handle = static_data.get_shape(context, context.library, ratio);
context
.commands
.render_shape(shape_handle, context.transform_stack.transform());
@ -183,10 +183,10 @@ impl MorphShapeStatic {
/// Retrieves the `ShapeHandle` for the given ratio.
/// Lazily intializes and tessellates the shape if it does not yet exist.
fn get_shape(
fn get_shape<'gc>(
&self,
renderer: &'_ mut dyn RenderBackend,
library: &Library<'_>,
context: &mut RenderContext<'_, 'gc, '_>,
library: &Library<'gc>,
ratio: u16,
) -> ShapeHandle {
let mut frame = self.get_frame(ratio);
@ -194,7 +194,13 @@ impl MorphShapeStatic {
handle
} else {
let library = library.library_for_movie(self.movie.clone()).unwrap();
let handle = renderer.register_shape((&frame.shape).into(), library);
let handle = context.renderer.register_shape(
(&frame.shape).into(),
&MovieLibrarySource {
library,
gc_context: context.gc_context,
},
);
frame.shape_handle = Some(handle);
handle
}

View File

@ -2979,7 +2979,7 @@ impl<'gc, 'a> MovieClipData<'gc> {
) -> Result<(), Error> {
let define_bits_lossless = reader.read_define_bits_lossless(version)?;
let bitmap = ruffle_render::utils::decode_define_bits_lossless(&define_bits_lossless)?;
let initial_data: Vec<i32> = bitmap.clone().into();
let initial_data: Vec<i32> = bitmap.as_colors().collect();
let bitmap = Bitmap::new(context, define_bits_lossless.id, bitmap)?;
context
.library
@ -3103,7 +3103,7 @@ impl<'gc, 'a> MovieClipData<'gc> {
.jpeg_tables();
let jpeg_data = ruffle_render::utils::glue_tables_to_jpeg(jpeg_data, jpeg_tables);
let bitmap = ruffle_render::utils::decode_define_bits_jpeg(&jpeg_data, None)?;
let initial_data: Vec<i32> = bitmap.clone().into();
let initial_data: Vec<i32> = bitmap.as_colors().collect();
let bitmap = Bitmap::new(context, id, bitmap)?;
context
.library
@ -3127,7 +3127,7 @@ impl<'gc, 'a> MovieClipData<'gc> {
let id = reader.read_u16()?;
let jpeg_data = reader.read_slice_to_end();
let bitmap = ruffle_render::utils::decode_define_bits_jpeg(jpeg_data, None)?;
let initial_data: Vec<i32> = bitmap.clone().into();
let initial_data: Vec<i32> = bitmap.as_colors().collect();
let bitmap = Bitmap::new(context, id, bitmap)?;
context
.library
@ -3157,7 +3157,7 @@ impl<'gc, 'a> MovieClipData<'gc> {
let jpeg_data = reader.read_slice(jpeg_len)?;
let alpha_data = reader.read_slice_to_end();
let bitmap = ruffle_render::utils::decode_define_bits_jpeg(jpeg_data, Some(alpha_data))?;
let initial_data: Vec<i32> = bitmap.clone().into();
let initial_data: Vec<i32> = bitmap.as_colors().collect();
let bitmap = Bitmap::new(context, id, bitmap)?;
context
.library

View File

@ -2,6 +2,7 @@ use crate::avm1::PropertyMap as Avm1PropertyMap;
use crate::avm2::{ClassObject as Avm2ClassObject, Domain as Avm2Domain};
use crate::backend::audio::SoundHandle;
use crate::character::Character;
use crate::display_object::{Bitmap, Graphic, MorphShape, TDisplayObject, Text};
use crate::font::{Font, FontDescriptor};
use crate::prelude::*;
@ -11,6 +12,7 @@ use gc_arena::{Collect, MutationContext};
use ruffle_render::backend::RenderBackend;
use ruffle_render::bitmap::BitmapHandle;
use ruffle_render::utils::remove_invalid_jpeg_data;
use std::collections::HashMap;
use std::sync::{Arc, Weak};
use swf::CharacterId;
@ -319,17 +321,30 @@ impl<'gc> MovieLibrary<'gc> {
}
}
impl<'gc> ruffle_render::bitmap::BitmapSource for MovieLibrary<'gc> {
pub struct MovieLibrarySource<'a, 'gc, 'gc_context> {
pub library: &'a MovieLibrary<'gc>,
pub gc_context: MutationContext<'gc, 'gc_context>,
}
impl<'a, 'gc, 'gc_context> ruffle_render::bitmap::BitmapSource
for MovieLibrarySource<'a, 'gc, 'gc_context>
{
fn bitmap_size(&self, id: u16) -> Option<ruffle_render::bitmap::BitmapSize> {
self.get_bitmap(id)
self.library
.get_bitmap(id)
.map(|bitmap| ruffle_render::bitmap::BitmapSize {
width: bitmap.width(),
height: bitmap.height(),
})
}
fn bitmap_handle(&self, id: u16, _backend: &mut dyn RenderBackend) -> Option<BitmapHandle> {
self.get_bitmap(id)
.and_then(|bitmap| bitmap.bitmap_handle())
fn bitmap_handle(&self, id: u16, backend: &mut dyn RenderBackend) -> Option<BitmapHandle> {
self.library.get_bitmap(id).and_then(|bitmap| {
bitmap
.bitmap_data()
.write(self.gc_context)
.bitmap_handle(backend)
})
}
}

View File

@ -102,33 +102,19 @@ impl Bitmap {
pub fn data_mut(&mut self) -> &mut [u8] {
&mut self.data
}
}
impl From<Bitmap> for Vec<i32> {
fn from(bitmap: Bitmap) -> Self {
match bitmap.format {
BitmapFormat::Rgb => bitmap
.data
.chunks_exact(3)
.map(|chunk| {
let red = chunk[0];
let green = chunk[1];
let blue = chunk[2];
i32::from_le_bytes([blue, green, red, 0xFF])
})
.collect(),
BitmapFormat::Rgba => bitmap
.data
.chunks_exact(4)
.map(|chunk| {
let red = chunk[0];
let green = chunk[1];
let blue = chunk[2];
let alpha = chunk[3];
i32::from_le_bytes([blue, green, red, alpha])
})
.collect(),
}
pub fn as_colors(&self) -> impl Iterator<Item = i32> + '_ {
let chunks = match self.format {
BitmapFormat::Rgb => self.data.chunks_exact(3),
BitmapFormat::Rgba => self.data.chunks_exact(4),
};
chunks.map(|chunk| {
let red = chunk[0];
let green = chunk[1];
let blue = chunk[2];
let alpha = chunk.get(3).copied().unwrap_or(0xFF);
i32::from_le_bytes([blue, green, red, alpha])
})
}
}