761 lines
28 KiB
Rust
761 lines
28 KiB
Rust
use crate::avm2::{Object as Avm2Object, Value as Avm2Value};
|
|
use crate::display_object::{DisplayObject, TDisplayObject};
|
|
use bitflags::bitflags;
|
|
use gc_arena::Collect;
|
|
use ruffle_render::backend::RenderBackend;
|
|
use ruffle_render::bitmap::{Bitmap, BitmapFormat, BitmapHandle, PixelRegion, SyncHandle};
|
|
use ruffle_wstr::WStr;
|
|
use std::fmt::Debug;
|
|
use std::ops::Range;
|
|
use swf::{Rectangle, Twips};
|
|
use tracing::instrument;
|
|
|
|
/// An implementation of the Lehmer/Park-Miller random number generator
|
|
/// Uses the fixed parameters m = 2,147,483,647 and a = 16,807
|
|
pub struct LehmerRng {
|
|
x: u32,
|
|
}
|
|
|
|
impl LehmerRng {
|
|
pub fn with_seed(seed: u32) -> Self {
|
|
Self { x: seed }
|
|
}
|
|
|
|
/// Generate the next value in the sequence via the following formula
|
|
/// X_(k+1) = a * X_k mod m
|
|
pub fn gen(&mut self) -> u32 {
|
|
self.x = ((self.x as u64).overflowing_mul(16_807).0 % 2_147_483_647) as u32;
|
|
self.x
|
|
}
|
|
|
|
pub fn gen_range(&mut self, rng: Range<u8>) -> u8 {
|
|
rng.start + (self.gen() % ((rng.end - rng.start) as u32 + 1)) as u8
|
|
}
|
|
}
|
|
|
|
/// This can represent both a premultiplied and an unmultiplied ARGB color value.
|
|
///
|
|
/// Note that most operations only make sense on one of these representations:
|
|
/// For example, blending on premultiplied values, and applying a `ColorTransform` on
|
|
/// unmultiplied values. Make sure to convert the color to the correct form beforehand.
|
|
// TODO: Maybe split the type into `PremultipliedColor(u32)` and
|
|
// `UnmultipliedColor(u32)`?
|
|
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Collect)]
|
|
#[collect(no_drop)]
|
|
pub struct Color(u32);
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub enum BitmapDataDrawError {
|
|
Unimplemented,
|
|
}
|
|
|
|
impl Color {
|
|
pub fn blue(&self) -> u8 {
|
|
(self.0 & 0xFF) as u8
|
|
}
|
|
|
|
pub fn green(&self) -> u8 {
|
|
((self.0 >> 8) & 0xFF) as u8
|
|
}
|
|
|
|
pub fn red(&self) -> u8 {
|
|
((self.0 >> 16) & 0xFF) as u8
|
|
}
|
|
|
|
pub fn alpha(&self) -> u8 {
|
|
((self.0 >> 24) & 0xFF) as u8
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn to_premultiplied_alpha(self, transparency: bool) -> Self {
|
|
// This has some accuracy issues with some alpha values
|
|
|
|
let old_alpha = if transparency { self.alpha() } else { 255 };
|
|
|
|
let a = old_alpha as u32;
|
|
let r = ((self.red() as u32 * a + 127) / 255) as u8;
|
|
let g = ((self.green() as u32 * a + 127) / 255) as u8;
|
|
let b = ((self.blue() as u32 * a + 127) / 255) as u8;
|
|
|
|
Self::argb(old_alpha, r, g, b)
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn to_un_multiplied_alpha(self) -> Self {
|
|
// We need to match Flash's results, and this lookup table was generated by brute force.
|
|
// For each alpha value, every value between 0..256^3 was tested to see if it produced the
|
|
// correct color value when reversing the premultiplication.
|
|
// Source code used to generate this table can be found at:
|
|
// https://gist.github.com/pdewacht/614b428cd42c2052dc0fd292516c9f9f
|
|
const FLASH_PREMUL_FACTOR: [u32; 256] = [
|
|
0, 16678912, 8339456, 5559638, 4169728, 3335783, 2779819, 2386603, 2086230, 1855488,
|
|
1667892, 1518251, 1391151, 1285234, 1193302, 1111928, 1043895, 981113, 927744, 879275,
|
|
834621, 795535, 759126, 726358, 695839, 668183, 642538, 618737, 596651, 576171, 555964,
|
|
538706, 522104, 506319, 490557, 477321, 464038, 451353, 439544, 428244, 417582, 407500,
|
|
397768, 388535, 379630, 371117, 363179, 355235, 348050, 340965, 334052, 327038, 321269,
|
|
315077, 309159, 303586, 298189, 293092, 287981, 283080, 278251, 273892, 269268, 265179,
|
|
261087, 256971, 253160, 249322, 245508, 242164, 238575, 235245, 231859, 228848, 225785,
|
|
222712, 219616, 216827, 213985, 211432, 208835, 206075, 203750, 201196, 198895, 196223,
|
|
194301, 191987, 189686, 187636, 185559, 183426, 181453, 179444, 177638, 175855, 174054,
|
|
171948, 170489, 168695, 166889, 165365, 163519, 162045, 160508, 158970, 157429, 156150,
|
|
154610, 153081, 151803, 150511, 148986, 147709, 146420, 145116, 143868, 142586, 141545,
|
|
140277, 139194, 137957, 136954, 135676, 134652, 133621, 132604, 131577, 130552, 129527,
|
|
128508, 127476, 126451, 125432, 124670, 123645, 122818, 121847, 121082, 120060, 119288,
|
|
118263, 117502, 116720, 115967, 115195, 114424, 113655, 112893, 112125, 111356, 110563,
|
|
109811, 109048, 108287, 107766, 107004, 106236, 105724, 104953, 104434, 103676, 102904,
|
|
102375, 101879, 101119, 100604, 99834, 99321, 98813, 98112, 97533, 97019, 96509, 95994,
|
|
95486, 94713, 94185, 93689, 93179, 92667, 92149, 91643, 91129, 90621, 90068, 89597,
|
|
89342, 88829, 88318, 87804, 87294, 87034, 86523, 85994, 85499, 85245, 84732, 84222,
|
|
83956, 83450, 82937, 82685, 82173, 81840, 81405, 80889, 80638, 80127, 79862, 79354,
|
|
79103, 78590, 78332, 78077, 77565, 77308, 76795, 76541, 76284, 75766, 75518, 75262,
|
|
74748, 74493, 74238, 73691, 73470, 73214, 72959, 72447, 72189, 71935, 71671, 71166,
|
|
70911, 70651, 70399, 70140, 69886, 69615, 69116, 68861, 68603, 68350, 68093, 67839,
|
|
67576, 67326, 67070, 66813, 66556, 66302, 66046, 65791, 65408,
|
|
];
|
|
|
|
let alpha_factor = FLASH_PREMUL_FACTOR[self.alpha() as usize];
|
|
let unmultiply = |c| ((c as u32 * alpha_factor + 0x8000) >> 16) as u8;
|
|
|
|
let r = unmultiply(self.red());
|
|
let g = unmultiply(self.green());
|
|
let b = unmultiply(self.blue());
|
|
|
|
Self::argb(self.alpha(), r, g, b)
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn argb(alpha: u8, red: u8, green: u8, blue: u8) -> Self {
|
|
Self(u32::from_le_bytes([blue, green, red, alpha]))
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn with_alpha(&self, alpha: u8) -> Self {
|
|
Self::argb(alpha, self.red(), self.green(), self.blue())
|
|
}
|
|
|
|
/// # Arguments
|
|
///
|
|
/// * `self` - Must be in premultiplied form.
|
|
/// * `source` - Must be in premultiplied form.
|
|
#[must_use]
|
|
pub fn blend_over(&self, source: &Self) -> Self {
|
|
let sa = source.alpha();
|
|
|
|
let r = source.red() + ((self.red() as u16 * (255 - sa as u16)) >> 8) as u8;
|
|
let g = source.green() + ((self.green() as u16 * (255 - sa as u16)) >> 8) as u8;
|
|
let b = source.blue() + ((self.blue() as u16 * (255 - sa as u16)) >> 8) as u8;
|
|
let a = source.alpha() + ((self.alpha() as u16 * (255 - sa as u16)) >> 8) as u8;
|
|
Self::argb(a, r, g, b)
|
|
}
|
|
}
|
|
|
|
impl std::fmt::Display for Color {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
f.write_str(&format!("{:#x}", self.0))
|
|
}
|
|
}
|
|
|
|
impl From<Color> for u32 {
|
|
fn from(c: Color) -> Self {
|
|
c.0
|
|
}
|
|
}
|
|
|
|
impl From<u32> for Color {
|
|
fn from(i: u32) -> Self {
|
|
Color(i)
|
|
}
|
|
}
|
|
|
|
impl From<swf::Color> for Color {
|
|
fn from(c: swf::Color) -> Self {
|
|
Self::argb(c.a, c.r, c.g, c.b)
|
|
}
|
|
}
|
|
|
|
impl From<Color> for swf::Color {
|
|
fn from(c: Color) -> Self {
|
|
let r = c.red();
|
|
let g = c.green();
|
|
let b = c.blue();
|
|
let a = c.alpha();
|
|
Self { r, g, b, a }
|
|
}
|
|
}
|
|
|
|
bitflags! {
|
|
pub struct ChannelOptions: u8 {
|
|
const RED = 1 << 0;
|
|
const GREEN = 1 << 1;
|
|
const BLUE = 1 << 2;
|
|
const ALPHA = 1 << 3;
|
|
const RGB = Self::RED.bits() | Self::GREEN.bits() | Self::BLUE.bits();
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Collect)]
|
|
#[collect(no_drop)]
|
|
pub struct BitmapData<'gc> {
|
|
/// The pixels in the bitmap, stored as a array of pre-multiplied ARGB colour values
|
|
pixels: Vec<Color>,
|
|
width: u32,
|
|
height: u32,
|
|
transparency: bool,
|
|
|
|
// Note that it's technically possible to have a BitmapData with zero width and height,
|
|
// (by embedding it in the SWF instead of using the BitmapData constructor),
|
|
// so we need a separate 'disposed' flag.
|
|
disposed: bool,
|
|
|
|
/// The bitmap handle for this data.
|
|
///
|
|
/// This is lazily initialized; a value of `None` indicates that
|
|
/// initialization has not yet happened.
|
|
#[collect(require_static)]
|
|
bitmap_handle: Option<BitmapHandle>,
|
|
|
|
/// The AVM2 side of this `BitmapData`.
|
|
///
|
|
/// AVM1 cannot retrieve `BitmapData` back from the display object tree, so
|
|
/// this does not need to hold an AVM1 object.
|
|
avm2_object: Option<Avm2Object<'gc>>,
|
|
|
|
dirty_state: DirtyState,
|
|
}
|
|
|
|
#[derive(Clone, Collect, Debug)]
|
|
#[collect(require_static)]
|
|
enum DirtyState {
|
|
// Both the CPU and GPU pixels are up to date. We do not need to wait for any syncs to complete
|
|
Clean,
|
|
|
|
// The CPU pixels have been modified, and need to be synced to the GPU via `update_dirty_texture`
|
|
CpuModified(PixelRegion),
|
|
|
|
// The GPU pixels have been modified, and need to be synced to the CPU via `BitmapDataWrapper::sync`
|
|
GpuModified(Box<dyn SyncHandle>, PixelRegion),
|
|
}
|
|
|
|
mod wrapper {
|
|
use crate::avm2::{Object as Avm2Object, Value as Avm2Value};
|
|
use crate::context::RenderContext;
|
|
use gc_arena::{Collect, GcCell, MutationContext};
|
|
use ruffle_render::backend::RenderBackend;
|
|
use ruffle_render::bitmap::{BitmapHandle, PixelRegion};
|
|
use ruffle_render::commands::CommandHandler;
|
|
use std::cell::Ref;
|
|
|
|
use super::{copy_pixels_to_bitmapdata, BitmapData, DirtyState};
|
|
|
|
/// A wrapper type that ensures that we always wait for a pending
|
|
/// GPU -> CPU sync to complete (using `sync_handle`) before accessing
|
|
/// the CPU-side pixels.
|
|
///
|
|
/// This is overly conservative - we perform a sync before allowing any access
|
|
/// to the underlying `BitmapData`, even if we wouldn't be accessing the pixels.
|
|
/// Implementing more fine-grained tracking turned out to be extremely invasive,
|
|
/// and made the code much less readable. This should be enough for the simple
|
|
/// case where ActionScript calls `BitmapData.draw`, and then doesn't interact
|
|
/// with the Bitmap/BitmapData object at all for some time.
|
|
///
|
|
/// There are three ways that this type gets used:
|
|
/// 1. Blocking on the current GPU->CPU sync via the `sync` method,
|
|
/// and obtainng a `GcCell<'gc, BitmapData<'gc>>` (or implicily through `as_bitmap_data`).
|
|
/// This is done for the vast majority of BitmapData AS2/AS3 methods, as they need to access CPU-side pixels.
|
|
/// 2. Ignoring the current GPU->CPU sync state. This is done by the `render` method defined on this type,
|
|
/// since rendering only uses GPU-side data, and ignores CPU-side pixels entirely.
|
|
/// 3. Explicitly cancelling any in-progress GPU->CPU sync via `overwrite_cpu_pixels_from_gpu`. This is
|
|
/// used by `BitmapData.draw` and `BitmapData.apply_filter`, since the new rendering result will completely
|
|
/// replace the current CPU-side pixels. This performs a CPU -> GPU sync, to ensure that the GPU side
|
|
/// is up to date before we overwrite the CPU-side pixels.
|
|
/// In the future, we could explore using this in additional
|
|
/// cases where we know that the entire CPU-side pixel array will be overwritten without being read
|
|
/// (e.g. `BitmapData.fillRect` with a rectangle covering the entire bitmap). However, `overwrite_cpu_pixels`
|
|
/// is always a performance optimization, and can always be safely replaced with `sync` (at the cost of worse performance)
|
|
///
|
|
/// Note that we also perform CPU-GPU syncs from `BitmapData.update_dirty_texture` when `dirty` is set.
|
|
/// `sync_handle` and `dirty` can never be set at the same time - we can only have one of them set, or none of them set.
|
|
#[derive(Copy, Clone, Collect, Debug)]
|
|
#[collect(no_drop)]
|
|
pub struct BitmapDataWrapper<'gc>(GcCell<'gc, BitmapData<'gc>>);
|
|
|
|
impl<'gc> BitmapDataWrapper<'gc> {
|
|
pub fn new(data: GcCell<'gc, BitmapData<'gc>>) -> Self {
|
|
BitmapDataWrapper(data)
|
|
}
|
|
|
|
// 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(mc: MutationContext<'gc, '_>) -> Self {
|
|
BitmapDataWrapper(GcCell::allocate(
|
|
mc,
|
|
BitmapData {
|
|
pixels: Vec::new(),
|
|
width: 0,
|
|
height: 0,
|
|
transparency: false,
|
|
disposed: true,
|
|
bitmap_handle: None,
|
|
avm2_object: None,
|
|
dirty_state: DirtyState::Clean,
|
|
},
|
|
))
|
|
}
|
|
|
|
// Provides access to the underlying `BitmapData`. If a GPU -> CPU sync
|
|
// is in progress, waits for it to complete
|
|
pub fn sync(&self) -> GcCell<'gc, BitmapData<'gc>> {
|
|
// SAFETY: The only field that can store gc pointers is `avm2_object`,
|
|
// which we don't update here. Ideally, we would refactor this so that
|
|
// `BitmapData` doesn't contain any gc pointers, allowing us to use a normal
|
|
// `RefCell` instead of a `GcCell`.
|
|
let mut write = unsafe { self.0.borrow_mut() };
|
|
match std::mem::replace(&mut write.dirty_state, DirtyState::Clean) {
|
|
DirtyState::GpuModified(sync_handle, bounds) => {
|
|
sync_handle
|
|
.retrieve_offscreen_texture(Box::new(|buffer, buffer_width| {
|
|
copy_pixels_to_bitmapdata(&mut write, buffer, buffer_width, bounds)
|
|
}))
|
|
.expect("Failed to sync BitmapData");
|
|
write.dirty_state = DirtyState::Clean
|
|
}
|
|
old_state => write.dirty_state = old_state,
|
|
}
|
|
self.0
|
|
}
|
|
|
|
/// Provides access to the underlying `BitmapHandle`.
|
|
/// If the CPU pixels are dirty, syncs them to the GPU.
|
|
/// If the GPU pixels are dirty, then handle is returned immediately
|
|
/// without waiting for the sync to complete, as a BitmapHandle can
|
|
/// only be used to access the GPU data. Unlike `overwrite_cpu_pixels_from_gpu`,
|
|
/// this does not cancel the GPU -> CPU sync.
|
|
pub fn bitmap_handle(
|
|
&self,
|
|
gc_context: MutationContext<'gc, '_>,
|
|
renderer: &mut dyn RenderBackend,
|
|
) -> BitmapHandle {
|
|
let mut bitmap_data = self.0.write(gc_context);
|
|
bitmap_data.update_dirty_texture(renderer);
|
|
bitmap_data.bitmap_handle(renderer).unwrap()
|
|
}
|
|
|
|
/// Provides access to the underlying `BitmapData`.
|
|
/// This should only be used when you will be overwriting the entire
|
|
/// `pixels` vec without reading from it. Cancels any in-progress GPU -> CPU sync.
|
|
/// This does not sync from cpu to gpu.
|
|
#[allow(clippy::type_complexity)]
|
|
pub fn overwrite_cpu_pixels_from_gpu(
|
|
&self,
|
|
mc: MutationContext<'gc, '_>,
|
|
) -> (GcCell<'gc, BitmapData<'gc>>, Option<PixelRegion>) {
|
|
let mut write = self.0.write(mc);
|
|
let dirty_rect = match write.dirty_state {
|
|
DirtyState::GpuModified(_, rect) => {
|
|
write.dirty_state = DirtyState::Clean;
|
|
Some(rect)
|
|
}
|
|
DirtyState::CpuModified(_) | DirtyState::Clean => None,
|
|
};
|
|
(self.0, dirty_rect)
|
|
}
|
|
|
|
/// Provides read access to the BitmapData pixels.
|
|
/// Only the provided region is guaranteed to be up-to-date.
|
|
/// It is an error to access any other pixels outside of that region.
|
|
pub fn read_area(&self, read_area: PixelRegion) -> Ref<'_, BitmapData<'gc>> {
|
|
let needs_update = if let DirtyState::GpuModified(_, area) = self.0.read().dirty_state {
|
|
area.intersects(read_area)
|
|
} else {
|
|
false
|
|
};
|
|
if needs_update {
|
|
self.sync();
|
|
}
|
|
self.0.read()
|
|
}
|
|
|
|
// These methods do not require a sync to complete, as they do not depend on the
|
|
// CPU-side pixels. They are implemented directly on `BitmapDataWrapper`, allowing
|
|
// callers to avoid calling sync()
|
|
|
|
pub fn height(&self) -> u32 {
|
|
self.0.read().height
|
|
}
|
|
|
|
pub fn width(&self) -> u32 {
|
|
self.0.read().width
|
|
}
|
|
|
|
pub fn object2(&self) -> Avm2Value<'gc> {
|
|
self.0.read().object2()
|
|
}
|
|
|
|
pub fn disposed(&self) -> bool {
|
|
self.0.read().disposed
|
|
}
|
|
|
|
pub fn transparency(&self) -> bool {
|
|
self.0.read().transparency
|
|
}
|
|
|
|
pub fn check_valid(
|
|
&self,
|
|
activation: &mut crate::avm2::Activation<'_, 'gc>,
|
|
) -> Result<(), crate::avm2::Error<'gc>> {
|
|
if self.disposed() {
|
|
return Err(crate::avm2::Error::AvmError(
|
|
crate::avm2::error::argument_error(
|
|
activation,
|
|
"Error #2015: Invalid BitmapData.",
|
|
2015,
|
|
)?,
|
|
));
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
pub fn dispose(&self, mc: MutationContext<'gc, '_>) {
|
|
self.0.write(mc).dispose();
|
|
}
|
|
|
|
pub fn init_object2(&self, mc: MutationContext<'gc, '_>, object: Avm2Object<'gc>) {
|
|
self.0.write(mc).avm2_object = Some(object)
|
|
}
|
|
|
|
pub fn render(&self, smoothing: bool, context: &mut RenderContext<'_, 'gc>) {
|
|
let mut inner_bitmap_data = self.0.write(context.gc_context);
|
|
if inner_bitmap_data.disposed() {
|
|
return;
|
|
}
|
|
|
|
// Note - we do a CPU -> GPU sync, but we do *not* do a GPU -> CPU sync
|
|
// (rendering is done on the GPU, so the CPU pixels don't need to be up-to-date).
|
|
inner_bitmap_data.update_dirty_texture(context.renderer);
|
|
let handle = inner_bitmap_data
|
|
.bitmap_handle(context.renderer)
|
|
.expect("Missing bitmap handle");
|
|
|
|
context
|
|
.commands
|
|
.render_bitmap(handle, context.transform_stack.transform(), smoothing);
|
|
}
|
|
|
|
pub fn can_read(&self, read_area: PixelRegion) -> bool {
|
|
if let DirtyState::GpuModified(_, area) = self.0.read().dirty_state {
|
|
!area.intersects(read_area)
|
|
} else {
|
|
true
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "egui")]
|
|
pub fn debug_sync_status(&self) -> std::borrow::Cow<'static, str> {
|
|
match self.0.read().dirty_state {
|
|
DirtyState::Clean => std::borrow::Cow::Borrowed("Clean"),
|
|
DirtyState::CpuModified(area) => std::borrow::Cow::Owned(format!(
|
|
"CPU modified from {}, {} to {}, {}",
|
|
area.x_min, area.y_min, area.x_max, area.y_max
|
|
)),
|
|
DirtyState::GpuModified(_, area) => std::borrow::Cow::Owned(format!(
|
|
"GPU modified from {}, {} to {}, {}",
|
|
area.x_min, area.y_min, area.x_max, area.y_max
|
|
)),
|
|
}
|
|
}
|
|
|
|
pub fn is_point_in_bounds(&self, x: i32, y: i32) -> bool {
|
|
x >= 0 && x < self.width() as i32 && y >= 0 && y < self.height() as i32
|
|
}
|
|
|
|
pub fn ptr_eq(&self, other: BitmapDataWrapper<'gc>) -> bool {
|
|
GcCell::ptr_eq(self.0, other.0)
|
|
}
|
|
}
|
|
}
|
|
|
|
pub use wrapper::BitmapDataWrapper;
|
|
|
|
impl std::fmt::Debug for BitmapData<'_> {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
f.debug_struct("BitmapData")
|
|
.field("dirty_state", &self.dirty_state)
|
|
.field("width", &self.width)
|
|
.field("height", &self.height)
|
|
.field("transparency", &self.transparency)
|
|
.field("disposed", &self.disposed)
|
|
.field("bitmap_handle", &self.bitmap_handle)
|
|
.finish()
|
|
}
|
|
}
|
|
|
|
impl<'gc> BitmapData<'gc> {
|
|
pub fn new(width: u32, height: u32, transparency: bool, fill_color: u32) -> Self {
|
|
Self {
|
|
pixels: vec![
|
|
Color(fill_color).to_premultiplied_alpha(transparency);
|
|
width as usize * height as usize
|
|
],
|
|
width,
|
|
height,
|
|
transparency,
|
|
disposed: false,
|
|
bitmap_handle: None,
|
|
avm2_object: None,
|
|
dirty_state: DirtyState::Clean,
|
|
}
|
|
}
|
|
|
|
pub fn new_with_pixels(
|
|
width: u32,
|
|
height: u32,
|
|
transparency: bool,
|
|
pixels: Vec<Color>,
|
|
) -> Self {
|
|
Self {
|
|
pixels,
|
|
width,
|
|
height,
|
|
transparency,
|
|
bitmap_handle: None,
|
|
avm2_object: None,
|
|
disposed: false,
|
|
dirty_state: DirtyState::Clean,
|
|
}
|
|
}
|
|
|
|
pub fn disposed(&self) -> bool {
|
|
self.disposed
|
|
}
|
|
|
|
pub fn dispose(&mut self) {
|
|
self.width = 0;
|
|
self.height = 0;
|
|
self.pixels.clear();
|
|
self.bitmap_handle = None;
|
|
// There's no longer a handle to update
|
|
self.dirty_state = DirtyState::Clean;
|
|
self.disposed = true;
|
|
}
|
|
|
|
pub fn bitmap_handle(&mut self, renderer: &mut dyn RenderBackend) -> Option<BitmapHandle> {
|
|
if self.bitmap_handle.is_none() {
|
|
let bitmap = Bitmap::new(
|
|
self.width(),
|
|
self.height(),
|
|
BitmapFormat::Rgba,
|
|
self.pixels_rgba(),
|
|
);
|
|
let bitmap_handle = renderer.register_bitmap(bitmap);
|
|
if let Err(e) = &bitmap_handle {
|
|
tracing::warn!("Failed to register raw bitmap for BitmapData: {:?}", e);
|
|
}
|
|
self.bitmap_handle = bitmap_handle.ok();
|
|
}
|
|
|
|
self.bitmap_handle.clone()
|
|
}
|
|
|
|
pub fn transparency(&self) -> bool {
|
|
self.transparency
|
|
}
|
|
|
|
pub fn set_gpu_dirty(&mut self, sync_handle: Box<dyn SyncHandle>, region: PixelRegion) {
|
|
self.dirty_state = DirtyState::GpuModified(sync_handle, region);
|
|
}
|
|
|
|
pub fn set_cpu_dirty(&mut self, region: PixelRegion) {
|
|
debug_assert!(region.x_max <= self.width);
|
|
debug_assert!(region.y_max <= self.height);
|
|
match &mut self.dirty_state {
|
|
DirtyState::CpuModified(old_region) => old_region.union(region),
|
|
DirtyState::Clean => self.dirty_state = DirtyState::CpuModified(region),
|
|
DirtyState::GpuModified(_, _) => {
|
|
panic!("Attempted to modify CPU dirty state while GPU sync is in progress!")
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn pixels(&self) -> &[Color] {
|
|
&self.pixels
|
|
}
|
|
|
|
pub fn pixels_rgba(&self) -> Vec<u8> {
|
|
// TODO: This could have been implemented as follows:
|
|
//
|
|
// self.pixels
|
|
// .iter()
|
|
// .flat_map(|p| [p.red(), p.green(), p.blue(), p.alpha()])
|
|
// .collect()
|
|
//
|
|
// But currently Rust emits suboptimal code in that case. For now we use
|
|
// `Vec::with_capacity` manually to avoid unnecessary re-allocations.
|
|
|
|
let mut output = Vec::with_capacity(self.pixels.len() * 4);
|
|
for p in &self.pixels {
|
|
output.extend_from_slice(&[p.red(), p.green(), p.blue(), p.alpha()])
|
|
}
|
|
output
|
|
}
|
|
|
|
pub fn width(&self) -> u32 {
|
|
self.width
|
|
}
|
|
pub fn height(&self) -> u32 {
|
|
self.height
|
|
}
|
|
|
|
pub fn is_point_in_bounds(&self, x: i32, y: i32) -> bool {
|
|
x >= 0 && x < self.width() as i32 && y >= 0 && y < self.height() as i32
|
|
}
|
|
|
|
#[inline]
|
|
pub fn set_pixel32_raw(&mut self, x: u32, y: u32, color: Color) {
|
|
self.pixels[(x + y * self.width) as usize] = color;
|
|
}
|
|
|
|
#[inline]
|
|
pub fn get_pixel32_raw(&self, x: u32, y: u32) -> Color {
|
|
self.pixels[(x + y * self.width()) as usize]
|
|
}
|
|
|
|
pub fn raw_pixels_mut(&mut self) -> &mut Vec<Color> {
|
|
&mut self.pixels
|
|
}
|
|
|
|
pub fn raw_pixels(&self) -> &[Color] {
|
|
&self.pixels
|
|
}
|
|
|
|
// Updates the data stored with our `BitmapHandle` if this `BitmapData`
|
|
// is dirty
|
|
pub fn update_dirty_texture(&mut self, renderer: &mut dyn RenderBackend) {
|
|
let handle = self.bitmap_handle(renderer).unwrap();
|
|
match &self.dirty_state {
|
|
DirtyState::CpuModified(region) => {
|
|
if let Err(e) = renderer.update_texture(
|
|
&handle,
|
|
Bitmap::new(
|
|
self.width(),
|
|
self.height(),
|
|
BitmapFormat::Rgba,
|
|
self.pixels_rgba(),
|
|
),
|
|
*region,
|
|
) {
|
|
tracing::error!("Failed to update dirty bitmap {:?}: {:?}", handle, e);
|
|
}
|
|
self.dirty_state = DirtyState::Clean;
|
|
}
|
|
DirtyState::Clean | DirtyState::GpuModified(_, _) => {}
|
|
}
|
|
}
|
|
|
|
pub fn object2(&self) -> Avm2Value<'gc> {
|
|
self.avm2_object
|
|
.map(|o| o.into())
|
|
.unwrap_or(Avm2Value::Null)
|
|
}
|
|
|
|
pub fn init_object2(&mut self, object: Avm2Object<'gc>) {
|
|
self.avm2_object = Some(object)
|
|
}
|
|
}
|
|
|
|
pub enum IBitmapDrawable<'gc> {
|
|
BitmapData(BitmapDataWrapper<'gc>),
|
|
DisplayObject(DisplayObject<'gc>),
|
|
}
|
|
|
|
impl IBitmapDrawable<'_> {
|
|
pub fn bounds(&self) -> Rectangle<Twips> {
|
|
match self {
|
|
IBitmapDrawable::BitmapData(bmd) => Rectangle {
|
|
x_min: Twips::ZERO,
|
|
x_max: Twips::from_pixels(bmd.width() as f64),
|
|
y_min: Twips::ZERO,
|
|
y_max: Twips::from_pixels(bmd.height() as f64),
|
|
},
|
|
IBitmapDrawable::DisplayObject(o) => o.bounds(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[instrument(level = "debug", skip_all)]
|
|
fn copy_pixels_to_bitmapdata(
|
|
write: &mut BitmapData,
|
|
buffer: &[u8],
|
|
buffer_width: u32,
|
|
area: PixelRegion,
|
|
) {
|
|
let buffer_width_pixels = buffer_width / 4;
|
|
|
|
for y in area.y_min..area.y_max {
|
|
for x in area.x_min..area.x_max {
|
|
// note: this order of conversions helps llvm realize the index is 4-byte-aligned
|
|
let ind = (((x - area.x_min) + (y - area.y_min) * buffer_width_pixels) as usize) * 4;
|
|
|
|
// TODO(mid): optimize this A LOT
|
|
let r = buffer[ind];
|
|
let g = buffer[ind + 1usize];
|
|
let b = buffer[ind + 2usize];
|
|
let a = if write.transparency() {
|
|
buffer[ind + 3usize]
|
|
} else {
|
|
255
|
|
};
|
|
|
|
// TODO(later): we might want to swap Color storage from argb to rgba, to make it cheaper
|
|
let nc = Color::argb(a, r, g, b);
|
|
|
|
// Ignore the original color entirely - the blending (including alpha)
|
|
// was done by the renderer when it wrote over the previous texture contents.
|
|
write.set_pixel32_raw(x, y, nc);
|
|
}
|
|
}
|
|
write.set_cpu_dirty(area);
|
|
}
|
|
|
|
#[derive(Copy, Clone, Debug)]
|
|
pub enum ThresholdOperation {
|
|
Equals,
|
|
NotEquals,
|
|
LessThan,
|
|
LessThanOrEquals,
|
|
GreaterThan,
|
|
GreaterThanOrEquals,
|
|
}
|
|
|
|
impl ThresholdOperation {
|
|
pub fn from_wstr(str: &WStr) -> Option<Self> {
|
|
if str == b"==" {
|
|
Some(Self::Equals)
|
|
} else if str == b"!=" {
|
|
Some(Self::NotEquals)
|
|
} else if str == b"<" {
|
|
Some(Self::LessThan)
|
|
} else if str == b"<=" {
|
|
Some(Self::LessThanOrEquals)
|
|
} else if str == b">" {
|
|
Some(Self::GreaterThan)
|
|
} else if str == b">=" {
|
|
Some(Self::GreaterThanOrEquals)
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
pub fn matches(&self, value: u32, masked_threshold: u32) -> bool {
|
|
match self {
|
|
ThresholdOperation::Equals => value == masked_threshold,
|
|
ThresholdOperation::NotEquals => value != masked_threshold,
|
|
ThresholdOperation::LessThan => value < masked_threshold,
|
|
ThresholdOperation::LessThanOrEquals => value <= masked_threshold,
|
|
ThresholdOperation::GreaterThan => value > masked_threshold,
|
|
ThresholdOperation::GreaterThanOrEquals => value >= masked_threshold,
|
|
}
|
|
}
|
|
}
|