core: Make `BitmapHandle` hold a trait object instead of an id

`BitmapHandle` now holds `Arc<dyn BitmapHandleImpl>`.
This allows us to move all of the per-bitmap backend data into
`BitmapHandle`, instead of holding an id to a backend-specific
hashmap.

This fixes the memory leak issue with bitmaps. Once the AVM side of a
bitmap (`Bitmap`/`BitmapData`) gets garbage-collected, the
`BitmapHandle` will get dropped, freeing all of the GPU resources
assoicated with the bitmap.
This commit is contained in:
Aaron Hill 2022-09-30 16:25:54 -05:00
parent 0861153626
commit 1b3070ab85
25 changed files with 285 additions and 350 deletions

2
Cargo.lock generated
View File

@ -3489,6 +3489,7 @@ dependencies = [
name = "ruffle_render_canvas"
version = "0.1.0"
dependencies = [
"downcast-rs",
"fnv",
"gc-arena",
"js-sys",
@ -3505,6 +3506,7 @@ name = "ruffle_render_webgl"
version = "0.1.0"
dependencies = [
"bytemuck",
"downcast-rs",
"fnv",
"gc-arena",
"js-sys",

View File

@ -57,9 +57,7 @@ impl<'gc> BitmapDataObject<'gc> {
}
pub fn dispose(&self, context: &mut UpdateContext<'_, 'gc, '_>) {
self.bitmap_data()
.write(context.gc_context)
.dispose(context.renderer);
self.bitmap_data().write(context.gc_context).dispose();
}
}

View File

@ -818,9 +818,7 @@ pub fn dispose<'gc>(
if let Some(bitmap_data) = this.and_then(|this| this.as_bitmap_data()) {
// Don't check if we've already disposed this BitmapData - 'BitmapData.dispose()' can be called
// multiple times
bitmap_data
.write(activation.context.gc_context)
.dispose(activation.context.renderer);
bitmap_data.write(activation.context.gc_context).dispose();
}
Ok(Value::Undefined)
}

View File

@ -273,7 +273,7 @@ impl<'gc> Context3DObject<'gc> {
if context3d.should_render() {
let handle = context3d.bitmap_handle();
context.commands.render_bitmap(
handle,
&handle,
// FIXME - apply x and y translation from Stage3D
&Transform::default(),
false,

View File

@ -217,14 +217,11 @@ impl<'gc> BitmapData<'gc> {
self.disposed
}
pub fn dispose(&mut self, renderer: &mut dyn RenderBackend) {
pub fn dispose(&mut self) {
self.width = 0;
self.height = 0;
self.pixels.clear();
if let Some(handle) = self.bitmap_handle {
renderer.unregister_bitmap(handle);
self.bitmap_handle = None;
}
// There's no longer a handle to update
self.dirty = false;
self.disposed = true;
@ -245,7 +242,7 @@ impl<'gc> BitmapData<'gc> {
self.bitmap_handle = bitmap_handle.ok();
}
self.bitmap_handle
self.bitmap_handle.clone()
}
pub fn transparency(&self) -> bool {
@ -914,7 +911,7 @@ impl<'gc> BitmapData<'gc> {
let handle = self.bitmap_handle(context.renderer).unwrap();
if self.dirty() {
if let Err(e) = context.renderer.update_texture(
handle,
&handle,
self.width(),
self.height(),
self.pixels_rgba(),
@ -1028,7 +1025,7 @@ impl<'gc> BitmapData<'gc> {
bitmap_data.update_dirty_texture(&mut render_context);
let bitmap_handle = bitmap_data.bitmap_handle(render_context.renderer).unwrap();
render_context.commands.render_bitmap(
bitmap_handle,
&bitmap_handle,
render_context.transform_stack.transform(),
smoothing,
);

View File

@ -140,7 +140,7 @@ impl<'gc> Bitmap<'gc> {
#[allow(dead_code)]
pub fn bitmap_handle(self) -> Option<BitmapHandle> {
self.0.read().bitmap_handle
self.0.read().bitmap_handle.clone()
}
pub fn width(self) -> u16 {
@ -306,7 +306,7 @@ 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(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);
@ -316,7 +316,7 @@ impl<'gc> TDisplayObject<'gc> for Bitmap<'gc> {
}
context.commands.render_bitmap(
bitmap_handle,
&bitmap_handle,
context.transform_stack.transform(),
bitmap_data.smoothing,
);

View File

@ -467,7 +467,7 @@ impl<'gc> TDisplayObject<'gc> for Video<'gc> {
context
.commands
.render_bitmap(bitmap.handle, &transform, smoothing);
.render_bitmap(&bitmap.handle, &transform, smoothing);
} else {
log::warn!("Video has no decoded frame to render.");
}

View File

@ -409,7 +409,7 @@ impl BitmapSource for Drawing {
})
}
fn bitmap_handle(&self, id: u16, _backend: &mut dyn RenderBackend) -> Option<BitmapHandle> {
self.bitmaps.get(id as usize).map(|bm| bm.handle)
self.bitmaps.get(id as usize).map(|bm| bm.handle.clone())
}
}

View File

@ -14,6 +14,7 @@ fnv = "1.0.7"
ruffle_render = { path = "..", features = ["web"] }
gc-arena = { git = "https://github.com/ruffle-rs/gc-arena" }
swf = { path = "../../swf" }
downcast-rs = "1.2.0"
[dependencies.web-sys]
version = "0.3.60"

View File

@ -1,6 +1,8 @@
#![allow(clippy::uninlined_format_args)]
use fnv::FnvHashMap;
use std::sync::Arc;
use downcast_rs::Downcast;
use gc_arena::MutationContext;
use ruffle_render::backend::null::NullBitmapSource;
use ruffle_render::backend::{
@ -28,12 +30,10 @@ pub struct WebCanvasRenderBackend {
context: CanvasRenderingContext2d,
color_matrix: Element,
shapes: Vec<ShapeData>,
bitmaps: FnvHashMap<BitmapHandle, BitmapData>,
viewport_width: u32,
viewport_height: u32,
rect: Path2d,
mask_state: MaskState,
next_bitmap_handle: BitmapHandle,
blend_modes: Vec<BlendMode>,
// This is currnetly unused - we just store it to report
@ -135,6 +135,7 @@ struct CanvasBitmap {
}
#[allow(dead_code)]
#[derive(Debug)]
struct BitmapData {
bitmap: Bitmap,
image_data: ImageData,
@ -142,6 +143,12 @@ struct BitmapData {
context: CanvasRenderingContext2d,
}
impl ruffle_render::bitmap::BitmapHandleImpl for BitmapData {}
fn as_bitmap_data(handle: &BitmapHandle) -> &BitmapData {
handle.as_any().downcast_ref::<BitmapData>().unwrap()
}
impl BitmapData {
/// Puts the image data into a newly created <canvas>, and caches it.
fn new(bitmap: Bitmap) -> Result<Self, JsValue> {
@ -175,6 +182,19 @@ impl BitmapData {
context,
})
}
fn update_pixels(&self, bitmap: Bitmap) -> Result<(), JsValue> {
let bitmap = bitmap.to_rgba();
let image_data =
ImageData::new_with_u8_clamped_array(Clamped(bitmap.data()), bitmap.width())
.into_js_result()?;
self.canvas.set_width(bitmap.width());
self.canvas.set_height(bitmap.height());
self.context
.put_image_data(&image_data, 0.0, 0.0)
.into_js_result()?;
Ok(())
}
}
impl WebCanvasRenderBackend {
@ -269,13 +289,11 @@ impl WebCanvasRenderBackend {
color_matrix,
context,
shapes: vec![],
bitmaps: Default::default(),
viewport_width: 0,
viewport_height: 0,
viewport_scale_factor: 1.0,
rect,
mask_state: MaskState::DrawContent,
next_bitmap_handle: BitmapHandle(0),
blend_modes: vec![BlendMode::Normal],
};
Ok(renderer)
@ -440,31 +458,20 @@ impl RenderBackend for WebCanvasRenderBackend {
}
fn register_bitmap(&mut self, bitmap: Bitmap) -> Result<BitmapHandle, Error> {
let handle = self.next_bitmap_handle;
self.next_bitmap_handle = BitmapHandle(self.next_bitmap_handle.0 + 1);
let bitmap_data = BitmapData::new(bitmap).map_err(Error::JavascriptError)?;
self.bitmaps.insert(handle, bitmap_data);
Ok(handle)
}
fn unregister_bitmap(&mut self, bitmap: BitmapHandle) {
self.bitmaps.remove(&bitmap);
Ok(BitmapHandle(Arc::new(bitmap_data)))
}
fn update_texture(
&mut self,
handle: BitmapHandle,
handle: &BitmapHandle,
width: u32,
height: u32,
rgba: Vec<u8>,
) -> Result<(), Error> {
// TODO: Could be optimized to a single put_image_data call
// in case it is already stored as a canvas+context.
self.bitmaps.insert(
handle,
BitmapData::new(Bitmap::new(width, height, BitmapFormat::Rgba, rgba))
.map_err(Error::JavascriptError)?,
);
let data = as_bitmap_data(handle);
data.update_pixels(Bitmap::new(width, height, BitmapFormat::Rgba, rgba))
.map_err(Error::JavascriptError)?;
Ok(())
}
@ -481,8 +488,8 @@ impl RenderBackend for WebCanvasRenderBackend {
}
}
impl CommandHandler for WebCanvasRenderBackend {
fn render_bitmap(&mut self, bitmap: BitmapHandle, transform: &Transform, smoothing: bool) {
impl<'a> CommandHandler<'a> for WebCanvasRenderBackend {
fn render_bitmap(&mut self, bitmap: &BitmapHandle, transform: &Transform, smoothing: bool) {
if self.mask_state == MaskState::ClearMask {
return;
}
@ -491,11 +498,10 @@ impl CommandHandler for WebCanvasRenderBackend {
self.set_transform(&transform.matrix);
self.set_color_filter(transform);
if let Some(bitmap) = self.bitmaps.get(&bitmap) {
let bitmap = as_bitmap_data(bitmap);
let _ = self
.context
.draw_image_with_html_canvas_element(&bitmap.canvas, 0.0, 0.0);
}
self.clear_color_filter();
}
@ -1149,10 +1155,8 @@ fn create_bitmap_pattern(
bitmap_source: &dyn BitmapSource,
backend: &mut WebCanvasRenderBackend,
) -> Option<CanvasBitmap> {
if let Some(bitmap) = bitmap_source
.bitmap_handle(id, backend)
.and_then(|handle| backend.bitmaps.get(&handle))
{
if let Some(handle) = bitmap_source.bitmap_handle(id, backend) {
let bitmap = as_bitmap_data(&handle);
let repeat = if !is_repeating {
// NOTE: The WebGL backend does clamping in this case, just like
// Flash Player, but CanvasPattern has no such option...

View File

@ -47,12 +47,9 @@ pub trait RenderBackend: Downcast {
fn submit_frame(&mut self, clear: swf::Color, commands: CommandList);
fn register_bitmap(&mut self, bitmap: Bitmap) -> Result<BitmapHandle, Error>;
// Frees memory used by the bitmap. After this call, `handle` can no longer
// be used.
fn unregister_bitmap(&mut self, handle: BitmapHandle);
fn update_texture(
&mut self,
bitmap: BitmapHandle,
bitmap: &BitmapHandle,
width: u32,
height: u32,
rgba: Vec<u8>,

View File

@ -1,5 +1,7 @@
use std::sync::Arc;
use crate::backend::{RenderBackend, ShapeHandle, ViewportDimensions};
use crate::bitmap::{Bitmap, BitmapHandle, BitmapSize, BitmapSource};
use crate::bitmap::{Bitmap, BitmapHandle, BitmapHandleImpl, BitmapSize, BitmapSource};
use crate::commands::CommandList;
use crate::error::Error;
use crate::shape_utils::DistilledShape;
@ -28,6 +30,9 @@ impl NullRenderer {
Self { dimensions }
}
}
#[derive(Clone, Debug)]
struct NullBitmapHandle;
impl BitmapHandleImpl for NullBitmapHandle {}
impl RenderBackend for NullRenderer {
fn viewport_dimensions(&self) -> ViewportDimensions {
@ -66,13 +71,12 @@ impl RenderBackend for NullRenderer {
fn submit_frame(&mut self, _clear: Color, _commands: CommandList) {}
fn register_bitmap(&mut self, _bitmap: Bitmap) -> Result<BitmapHandle, Error> {
Ok(BitmapHandle(0))
Ok(BitmapHandle(Arc::new(NullBitmapHandle)))
}
fn unregister_bitmap(&mut self, _bitmap: BitmapHandle) {}
fn update_texture(
&mut self,
_bitmap: BitmapHandle,
_bitmap: &BitmapHandle,
_width: u32,
_height: u32,
_rgba: Vec<u8>,

View File

@ -1,8 +1,17 @@
use std::fmt::Debug;
use std::sync::Arc;
use downcast_rs::{impl_downcast, Downcast};
use crate::backend::RenderBackend;
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub struct BitmapHandle(pub usize);
#[derive(Clone, Debug)]
pub struct BitmapHandle(pub Arc<dyn BitmapHandleImpl>);
pub trait BitmapHandleImpl: Downcast + Debug {}
impl_downcast!(BitmapHandleImpl);
/// Info returned by the `register_bitmap` methods.
#[derive(Clone, Debug)]
pub struct BitmapInfo {
pub handle: BitmapHandle,

View File

@ -4,8 +4,8 @@ use crate::matrix::Matrix;
use crate::transform::Transform;
use swf::{BlendMode, Color};
pub trait CommandHandler {
fn render_bitmap(&mut self, bitmap: BitmapHandle, transform: &Transform, smoothing: bool);
pub trait CommandHandler<'a> {
fn render_bitmap(&mut self, bitmap: &'a BitmapHandle, transform: &Transform, smoothing: bool);
fn render_shape(&mut self, shape: ShapeHandle, transform: &Transform);
fn draw_rect(&mut self, color: Color, matrix: &Matrix);
fn push_mask(&mut self);
@ -25,33 +25,33 @@ impl CommandList {
Self::default()
}
pub fn execute(self, handler: &mut impl CommandHandler) {
for command in self.0 {
pub fn execute<'a>(&'a self, handler: &mut impl CommandHandler<'a>) {
for command in &self.0 {
match command {
Command::RenderBitmap {
bitmap,
transform,
smoothing,
} => handler.render_bitmap(bitmap, &transform, smoothing),
} => handler.render_bitmap(bitmap, &transform, *smoothing),
Command::RenderShape { shape, transform } => {
handler.render_shape(shape, &transform)
handler.render_shape(*shape, &transform)
}
Command::DrawRect { color, matrix } => handler.draw_rect(color, &matrix),
Command::DrawRect { color, matrix } => handler.draw_rect(color.clone(), &matrix),
Command::PushMask => handler.push_mask(),
Command::ActivateMask => handler.activate_mask(),
Command::DeactivateMask => handler.deactivate_mask(),
Command::PopMask => handler.pop_mask(),
Command::PushBlendMode(blend) => handler.push_blend_mode(blend),
Command::PushBlendMode(blend) => handler.push_blend_mode(*blend),
Command::PopBlendMode => handler.pop_blend_mode(),
}
}
}
}
impl CommandHandler for CommandList {
fn render_bitmap(&mut self, bitmap: BitmapHandle, transform: &Transform, smoothing: bool) {
impl<'a> CommandHandler<'a> for CommandList {
fn render_bitmap(&mut self, bitmap: &'a BitmapHandle, transform: &Transform, smoothing: bool) {
self.0.push(Command::RenderBitmap {
bitmap,
bitmap: bitmap.clone(),
transform: transform.clone(),
smoothing,
});

View File

@ -16,6 +16,7 @@ gc-arena = { git = "https://github.com/ruffle-rs/gc-arena" }
fnv = "1.0.7"
swf = { path = "../../swf" }
thiserror = "1.0"
downcast-rs = "1.2.0"
[dependencies.web-sys]
version = "0.3.60"

View File

@ -2,13 +2,14 @@
#![allow(clippy::uninlined_format_args)]
use bytemuck::{Pod, Zeroable};
use fnv::FnvHashMap;
use downcast_rs::Downcast;
use gc_arena::MutationContext;
use ruffle_render::backend::null::NullBitmapSource;
use ruffle_render::backend::{
Context3D, Context3DCommand, RenderBackend, ShapeHandle, ViewportDimensions,
};
use ruffle_render::bitmap::{Bitmap, BitmapFormat, BitmapHandle, BitmapSource};
use ruffle_render::bitmap::{Bitmap, BitmapFormat, BitmapHandle, BitmapHandleImpl, BitmapSource};
use ruffle_render::commands::{CommandHandler, CommandList};
use ruffle_render::error::Error as BitmapError;
use ruffle_render::shape_utils::DistilledShape;
@ -17,6 +18,7 @@ use ruffle_render::tessellator::{
};
use ruffle_render::transform::Transform;
use ruffle_web_common::{JsError, JsResult};
use std::sync::Arc;
use swf::{BlendMode, Color};
use thiserror::Error;
use wasm_bindgen::{JsCast, JsValue};
@ -140,14 +142,12 @@ pub struct WebGlRenderBackend {
renderbuffer_height: i32,
view_matrix: [[f32; 4]; 4],
bitmap_registry: FnvHashMap<BitmapHandle, RegistryData>,
next_bitmap_handle: BitmapHandle,
// This is currently unused - we just hold on to it
// to expose via `get_viewport_dimensions`
viewport_scale_factor: f64,
}
#[derive(Debug)]
struct RegistryData {
gl: Gl,
bitmap: Bitmap,
@ -160,6 +160,12 @@ impl Drop for RegistryData {
}
}
impl BitmapHandleImpl for RegistryData {}
fn as_registry_data(handle: &BitmapHandle) -> &RegistryData {
handle.as_any().downcast_ref::<RegistryData>().unwrap()
}
const MAX_GRADIENT_COLORS: usize = 15;
impl WebGlRenderBackend {
@ -311,8 +317,6 @@ impl WebGlRenderBackend {
blend_modes: vec![],
mult_color: None,
add_color: None,
bitmap_registry: Default::default(),
next_bitmap_handle: BitmapHandle(0),
viewport_scale_factor: 1.0,
};
@ -404,8 +408,7 @@ impl WebGlRenderBackend {
draw_type: if program.program == self.bitmap_program.program {
DrawType::Bitmap(BitmapDraw {
matrix: [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]],
handle: BitmapHandle(0),
handle: None,
is_smoothed: true,
is_repeating: false,
})
@ -668,7 +671,7 @@ impl WebGlRenderBackend {
TessDrawType::Bitmap(bitmap) => Draw {
draw_type: DrawType::Bitmap(BitmapDraw {
matrix: bitmap.matrix,
handle: bitmap_source.bitmap_handle(bitmap.bitmap_id, self).unwrap(),
handle: bitmap_source.bitmap_handle(bitmap.bitmap_id, self),
is_smoothed: bitmap.is_smoothed,
is_repeating: bitmap.is_repeating,
}),
@ -1024,36 +1027,21 @@ impl RenderBackend for WebGlRenderBackend {
self.gl
.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_MAG_FILTER, Gl::LINEAR as i32);
let handle = self.next_bitmap_handle;
self.next_bitmap_handle = BitmapHandle(self.next_bitmap_handle.0 + 1);
self.bitmap_registry.insert(
handle,
RegistryData {
Ok(BitmapHandle(Arc::new(RegistryData {
gl: self.gl.clone(),
bitmap,
texture,
},
);
Ok(handle)
}
fn unregister_bitmap(&mut self, bitmap: BitmapHandle) {
self.bitmap_registry.remove(&bitmap);
})))
}
fn update_texture(
&mut self,
handle: BitmapHandle,
handle: &BitmapHandle,
width: u32,
height: u32,
rgba: Vec<u8>,
) -> Result<(), BitmapError> {
let texture = if let Some(entry) = self.bitmap_registry.get(&handle) {
&entry.texture
} else {
return Err(BitmapError::UnknownHandle(handle));
};
let texture = &as_registry_data(handle).texture;
self.gl.bind_texture(Gl::TEXTURE_2D, Some(&texture));
@ -1088,15 +1076,14 @@ impl RenderBackend for WebGlRenderBackend {
}
}
impl CommandHandler for WebGlRenderBackend {
fn render_bitmap(&mut self, bitmap: BitmapHandle, transform: &Transform, smoothing: bool) {
impl<'a> CommandHandler<'a> for WebGlRenderBackend {
fn render_bitmap(&mut self, bitmap: &'a BitmapHandle, transform: &Transform, smoothing: bool) {
self.set_stencil_state();
if let Some(entry) = self.bitmap_registry.get(&bitmap) {
let entry = as_registry_data(bitmap);
// Adjust the quad draw to use the target bitmap.
let mesh = &self.meshes[self.bitmap_quad_shape.0];
let draw = &mesh.draws[0];
let bitmap_matrix = if let DrawType::Bitmap(BitmapDraw { matrix, .. }) = &draw.draw_type
{
let bitmap_matrix = if let DrawType::Bitmap(BitmapDraw { matrix, .. }) = &draw.draw_type {
matrix
} else {
unreachable!()
@ -1179,7 +1166,6 @@ impl CommandHandler for WebGlRenderBackend {
self.gl
.draw_elements_with_i32(Gl::TRIANGLES, draw.num_indices, Gl::UNSIGNED_INT, 0);
}
}
fn render_shape(&mut self, shape: ShapeHandle, transform: &Transform) {
let world_matrix = [
@ -1281,12 +1267,7 @@ impl CommandHandler for WebGlRenderBackend {
);
}
DrawType::Bitmap(bitmap) => {
let texture = if let Some(entry) = self.bitmap_registry.get(&bitmap.handle) {
&entry.texture
} else {
// Bitmap not registered
continue;
};
let texture = &as_registry_data(&bitmap.handle.as_ref().unwrap()).texture;
program.uniform_matrix3fv(
&self.gl,
@ -1485,7 +1466,7 @@ impl From<TessGradient> for Gradient {
#[derive(Clone, Debug)]
struct BitmapDraw {
matrix: [[f32; 3]; 3],
handle: BitmapHandle,
handle: Option<BitmapHandle>,
is_repeating: bool,
is_smoothed: bool,
}

View File

@ -5,10 +5,9 @@ use crate::target::RenderTargetFrame;
use crate::target::TextureTarget;
use crate::uniform_buffer::BufferStorage;
use crate::{
format_list, get_backend_names, BufferDimensions, Descriptors, Error, Globals, RenderTarget,
SwapChainTarget, Texture, TextureOffscreen, Transforms,
as_texture, format_list, get_backend_names, BufferDimensions, Descriptors, Error, Globals,
RenderTarget, SwapChainTarget, Texture, TextureOffscreen, Transforms,
};
use fnv::FnvHashMap;
use gc_arena::MutationContext;
use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle};
use ruffle_render::backend::{Context3D, Context3DCommand};
@ -34,8 +33,6 @@ pub struct WgpuRenderBackend<T: RenderTarget> {
surface: Surface,
meshes: Vec<Mesh>,
shape_tessellator: ShapeTessellator,
bitmap_registry: FnvHashMap<BitmapHandle, Texture>,
next_bitmap_handle: BitmapHandle,
// This is currently unused - we just store it to report in
// `get_viewport_dimensions`
viewport_scale_factor: f64,
@ -156,9 +153,6 @@ impl<T: RenderTarget> WgpuRenderBackend<T> {
surface,
meshes: Vec::new(),
shape_tessellator: ShapeTessellator::new(),
bitmap_registry: Default::default(),
next_bitmap_handle: BitmapHandle(0),
viewport_scale_factor: 1.0,
})
}
@ -221,19 +215,6 @@ impl<T: RenderTarget> WgpuRenderBackend<T> {
pub fn device(&self) -> &wgpu::Device {
&self.descriptors.device
}
pub fn bitmap_registry(&self) -> &FnvHashMap<BitmapHandle, Texture> {
&self.bitmap_registry
}
fn raw_register_bitmap(&mut self, data: Texture) -> BitmapHandle {
let handle = self.next_bitmap_handle;
self.next_bitmap_handle.0 += 1;
if self.bitmap_registry.insert(handle, data).is_some() {
panic!("Overwrote existing bitmap {:?}", handle);
}
handle
}
}
impl<T: RenderTarget + 'static> RenderBackend for WgpuRenderBackend<T> {
@ -283,14 +264,14 @@ impl<T: RenderTarget + 'static> RenderBackend for WgpuRenderBackend<T> {
usage: wgpu::TextureUsages::COPY_SRC,
});
let handle = self.raw_register_bitmap(Texture {
let handle = BitmapHandle(Arc::new(Texture {
bind_linear: Default::default(),
bind_nearest: Default::default(),
texture: dummy_texture,
texture_offscreen: None,
texture: Arc::new(dummy_texture),
texture_offscreen: Default::default(),
width: 0,
height: 0,
});
}));
Ok(Box::new(WgpuContext3D::new(
self.descriptors.clone(),
handle,
@ -307,14 +288,7 @@ impl<T: RenderTarget + 'static> RenderBackend for WgpuRenderBackend<T> {
.as_any_mut()
.downcast_mut::<WgpuContext3D>()
.unwrap();
if let Some(new_registry_data) = context.present(commands, mc) {
// Update the registry with the new texture, which will later
// be rendered by `Stage`
*self
.bitmap_registry
.get_mut(&context.bitmap_handle())
.unwrap() = new_registry_data;
}
context.present(commands, mc);
Ok(())
}
@ -385,7 +359,6 @@ impl<T: RenderTarget + 'static> RenderBackend for WgpuRenderBackend<T> {
&mut self.globals,
&mut self.uniform_buffers_storage,
&self.meshes,
&self.bitmap_registry,
commands,
);
@ -444,34 +417,26 @@ impl<T: RenderTarget + 'static> RenderBackend for WgpuRenderBackend<T> {
extent,
);
let handle = self.raw_register_bitmap(Texture {
texture,
let handle = BitmapHandle(Arc::new(Texture {
texture: Arc::new(texture),
bind_linear: Default::default(),
bind_nearest: Default::default(),
texture_offscreen: None,
width: extent.width,
height: extent.height,
});
texture_offscreen: Default::default(),
width: bitmap.width(),
height: bitmap.height(),
}));
Ok(handle)
}
fn unregister_bitmap(&mut self, handle: BitmapHandle) {
self.bitmap_registry.remove(&handle);
}
fn update_texture(
&mut self,
handle: BitmapHandle,
handle: &BitmapHandle,
width: u32,
height: u32,
rgba: Vec<u8>,
) -> Result<(), BitmapError> {
let texture = if let Some(entry) = self.bitmap_registry.get(&handle) {
&entry.texture
} else {
return Err(BitmapError::UnknownHandle(handle));
};
let texture = as_texture(handle);
let extent = wgpu::Extent3d {
width,
@ -481,7 +446,7 @@ impl<T: RenderTarget + 'static> RenderBackend for WgpuRenderBackend<T> {
self.descriptors.queue.write_texture(
wgpu::ImageCopyTexture {
texture,
texture: &texture.texture,
mip_level: 0,
origin: Default::default(),
aspect: wgpu::TextureAspect::All,
@ -505,16 +470,7 @@ impl<T: RenderTarget + 'static> RenderBackend for WgpuRenderBackend<T> {
height: u32,
commands: CommandList,
) -> Result<Bitmap, ruffle_render::error::Error> {
// We need ownership of `Texture` to access the non-`Clone`
// `wgpu` fields. At the end of this method, we re-insert
// `texture` into the map.
//
// This means that the target texture will be inaccessible
// while the callback `f` is a problem. This would only be
// an issue if a caller tried to render the target texture
// to itself, which probably isn't supported by Flash. If it
// is, then we could change `TextureTarget` to use an `Rc<wgpu::Texture>`
let mut texture = self.bitmap_registry.remove(&handle).unwrap();
let texture = as_texture(&handle);
let extent = wgpu::Extent3d {
width,
@ -531,7 +487,7 @@ impl<T: RenderTarget + 'static> RenderBackend for WgpuRenderBackend<T> {
// many buffers / depth textures rendered at once, we could
// try storing this data in an LRU cache, evicting entries
// as needed.
let mut texture_offscreen = texture.texture_offscreen.unwrap_or_else(|| {
let texture_offscreen = texture.texture_offscreen.get_or_init(|| {
let buffer_dimensions = BufferDimensions::new(width as usize, height as usize);
let buffer_label = create_debug_label!("Render target buffer");
let buffer = self
@ -545,7 +501,7 @@ impl<T: RenderTarget + 'static> RenderBackend for WgpuRenderBackend<T> {
mapped_at_creation: false,
});
TextureOffscreen {
buffer,
buffer: Arc::new(buffer),
buffer_dimensions,
surface: Surface::new(
&self.descriptors,
@ -559,10 +515,10 @@ impl<T: RenderTarget + 'static> RenderBackend for WgpuRenderBackend<T> {
let mut target = TextureTarget {
size: extent,
texture: texture.texture,
texture: texture.texture.clone(),
format: wgpu::TextureFormat::Rgba8Unorm,
buffer: texture_offscreen.buffer,
buffer_dimensions: texture_offscreen.buffer_dimensions,
buffer: texture_offscreen.buffer.clone(),
buffer_dimensions: texture_offscreen.buffer_dimensions.clone(),
};
let (old_width, old_height) = self.globals.resolution();
@ -579,7 +535,6 @@ impl<T: RenderTarget + 'static> RenderBackend for WgpuRenderBackend<T> {
&mut self.globals,
&mut self.uniform_buffers_storage,
&self.meshes,
&self.bitmap_registry,
commands,
);
target.submit(
@ -602,11 +557,6 @@ impl<T: RenderTarget + 'static> RenderBackend for WgpuRenderBackend<T> {
});
self.globals.set_resolution(old_width, old_height);
texture_offscreen.buffer = target.buffer;
texture_offscreen.buffer_dimensions = target.buffer_dimensions;
texture.texture_offscreen = Some(texture_offscreen);
texture.texture = target.texture;
self.bitmap_registry.insert(handle, texture);
Ok(image.unwrap())
}

View File

@ -1,8 +1,7 @@
use crate::frame::Frame;
use crate::mesh::{DrawType, Mesh};
use crate::pipelines::BlendMode as ActualBlendMode;
use crate::{ColorAdjustments, MaskState, Texture};
use fnv::FnvHashMap;
use crate::{as_texture, ColorAdjustments, MaskState};
use ruffle_render::backend::ShapeHandle;
use ruffle_render::bitmap::BitmapHandle;
use ruffle_render::commands::CommandHandler;
@ -11,7 +10,6 @@ use swf::{BlendMode, Color};
pub struct CommandRenderer<'a, 'b> {
frame: &'b mut Frame<'a>,
bitmap_registry: &'a FnvHashMap<BitmapHandle, Texture>,
meshes: &'a Vec<Mesh>,
quad_vertices: wgpu::BufferSlice<'a>,
quad_indices: wgpu::BufferSlice<'a>,
@ -23,13 +21,11 @@ impl<'a, 'b> CommandRenderer<'a, 'b> {
pub fn new(
frame: &'b mut Frame<'a>,
meshes: &'a Vec<Mesh>,
bitmap_registry: &'a FnvHashMap<BitmapHandle, Texture>,
quad_vertices: wgpu::BufferSlice<'a>,
quad_indices: wgpu::BufferSlice<'a>,
) -> Self {
Self {
frame,
bitmap_registry,
meshes,
quad_vertices,
quad_indices,
@ -39,9 +35,10 @@ impl<'a, 'b> CommandRenderer<'a, 'b> {
}
}
impl<'a, 'b> CommandHandler for CommandRenderer<'a, 'b> {
fn render_bitmap(&mut self, bitmap: BitmapHandle, transform: &Transform, smoothing: bool) {
if let Some(texture) = self.bitmap_registry.get(&bitmap) {
impl<'a, 'b> CommandHandler<'a> for CommandRenderer<'a, 'b> {
fn render_bitmap(&mut self, bitmap: &'a BitmapHandle, transform: &Transform, smoothing: bool) {
let texture = as_texture(bitmap);
self.frame.apply_transform(
&(transform.matrix
* ruffle_render::matrix::Matrix {
@ -57,7 +54,7 @@ impl<'a, 'b> CommandHandler for CommandRenderer<'a, 'b> {
&descriptors.device,
&descriptors.bind_layouts.bitmap,
&descriptors.quad,
bitmap,
bitmap.clone(),
&descriptors.bitmap_samplers,
);
@ -65,7 +62,6 @@ impl<'a, 'b> CommandHandler for CommandRenderer<'a, 'b> {
self.frame.draw(self.quad_vertices, self.quad_indices, 6);
}
}
fn render_shape(&mut self, shape: ShapeHandle, transform: &Transform) {
self.frame.apply_transform(

View File

@ -177,14 +177,11 @@ impl WgpuContext3D {
}
}
// Executes all of the given `commands` in response to a `Context3D.present` call.
// If we needed to re-create the target Texture due to a `ConfigureBackBuffer` command,
// then we return the new Texture.
// If we re-used the same Texture, then we return `None`.
pub(crate) fn present<'gc>(
&mut self,
commands: Vec<Context3DCommand<'gc>>,
mc: MutationContext<'gc, '_>,
) -> Option<Texture> {
) {
let mut render_command_encoder =
self.descriptors
.device
@ -216,10 +213,6 @@ impl WgpuContext3D {
// `clear_color`, which may be `None` even if we've seen a `Clear` command.
let mut seen_clear_command = false;
// If we execute any `ConfigureBackBuffer` commands, this will store the newly-create
// Texture.
let mut recreated_texture = None;
for command in &commands {
match command {
Context3DCommand::Clear {
@ -293,14 +286,14 @@ impl WgpuContext3D {
finish_render_pass!(render_pass);
self.texture_view = Some(wgpu_texture.create_view(&Default::default()));
recreated_texture = Some(Texture {
texture: wgpu_texture,
self.raw_texture_handle = BitmapHandle(Arc::new(Texture {
texture: Arc::new(wgpu_texture),
bind_linear: Default::default(),
bind_nearest: Default::default(),
texture_offscreen: None,
texture_offscreen: Default::default(),
width: *width,
height: *height,
});
}));
}
Context3DCommand::UploadToIndexBuffer {
buffer,
@ -514,8 +507,6 @@ impl WgpuContext3D {
self.buffer_staging_belt.recall();
self.compiled_pipeline = compiled_pipeline;
recreated_texture
}
}
@ -548,7 +539,7 @@ pub struct VertexAttributeInfo {
impl Context3D for WgpuContext3D {
fn bitmap_handle(&self) -> BitmapHandle {
self.raw_texture_handle
self.raw_texture_handle.clone()
}
fn should_render(&self) -> bool {
// If this is None, we haven't called configureBackBuffer yet.

View File

@ -1,5 +1,7 @@
#![allow(clippy::uninlined_format_args)]
use std::sync::Arc;
use crate::bitmaps::BitmapSamplers;
use crate::descriptors::Quad;
use crate::globals::Globals;
@ -13,7 +15,7 @@ use bytemuck::{Pod, Zeroable};
use descriptors::Descriptors;
use enum_map::Enum;
use once_cell::sync::OnceCell;
use ruffle_render::bitmap::BitmapHandle;
use ruffle_render::bitmap::{BitmapHandle, BitmapHandleImpl};
use ruffle_render::color_transform::ColorTransform;
use ruffle_render::tessellator::{Gradient as TessGradient, GradientType, Vertex as TessVertex};
pub use wgpu;
@ -41,6 +43,12 @@ mod mesh;
mod shaders;
mod surface;
impl BitmapHandleImpl for Texture {}
pub fn as_texture(handle: &BitmapHandle) -> &Texture {
handle.0.as_any().downcast_ref().unwrap()
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Enum)]
pub enum MaskState {
NoMask,
@ -186,10 +194,10 @@ impl From<TessGradient> for GradientStorage {
#[derive(Debug)]
pub struct Texture {
texture: wgpu::Texture,
texture: Arc<wgpu::Texture>,
bind_linear: OnceCell<BitmapBinds>,
bind_nearest: OnceCell<BitmapBinds>,
texture_offscreen: Option<TextureOffscreen>,
texture_offscreen: OnceCell<TextureOffscreen>,
width: u32,
height: u32,
}
@ -215,7 +223,7 @@ impl Texture {
samplers.get_sampler(false, smoothed),
&quad.texture_transforms,
self.texture.create_view(&Default::default()),
create_debug_label!("Bitmap {} bind group (smoothed: {})", handle.0, smoothed),
create_debug_label!("Bitmap {:?} bind group (smoothed: {})", handle.0, smoothed),
)
})
}
@ -223,7 +231,7 @@ impl Texture {
#[derive(Debug)]
struct TextureOffscreen {
buffer: wgpu::Buffer,
buffer: Arc<wgpu::Buffer>,
buffer_dimensions: BufferDimensions,
surface: Surface,
}

View File

@ -1,10 +1,11 @@
use crate::backend::WgpuRenderBackend;
use crate::target::RenderTarget;
use crate::{
create_buffer_with_data, Descriptors, GradientStorage, GradientUniforms, Texture,
as_texture, create_buffer_with_data, Descriptors, GradientStorage, GradientUniforms,
TextureTransforms, Vertex,
};
use ruffle_render::backend::RenderBackend;
use ruffle_render::bitmap::BitmapSource;
use ruffle_render::tessellator::{Bitmap, Draw as LyonDraw, DrawType as TessDrawType, Gradient};
use swf::CharacterId;
@ -32,7 +33,7 @@ impl Draw {
draw_id: usize,
) -> Self {
let vertices: Vec<_> = draw.vertices.into_iter().map(Vertex::from).collect();
let descriptors = backend.descriptors();
let descriptors = backend.descriptors().clone();
let vertex_buffer = create_buffer_with_data(
&descriptors.device,
bytemuck::cast_slice(&vertices),
@ -63,23 +64,20 @@ impl Draw {
num_indices: index_count,
num_mask_indices: draw.mask_index_count,
},
TessDrawType::Bitmap(bitmap) => {
let bitmap_handle = source.bitmap_handle(bitmap.bitmap_id, backend).unwrap();
let texture = &backend.bitmap_registry()[&bitmap_handle];
Draw {
TessDrawType::Bitmap(bitmap) => Draw {
draw_type: DrawType::bitmap(
&backend.descriptors(),
&descriptors,
bitmap,
texture,
shape_id,
draw_id,
source,
backend,
),
vertex_buffer,
index_buffer,
num_indices: index_count,
num_mask_indices: draw.mask_index_count,
}
}
},
}
}
}
@ -189,12 +187,14 @@ impl DrawType {
pub fn bitmap(
descriptors: &Descriptors,
bitmap: Bitmap,
texture: &Texture,
shape_id: CharacterId,
draw_id: usize,
source: &dyn BitmapSource,
backend: &mut dyn RenderBackend,
) -> Self {
let handle = source.bitmap_handle(bitmap.bitmap_id, backend).unwrap();
let texture = as_texture(&handle);
let texture_view = texture.texture.create_view(&Default::default());
let texture_transforms = create_texture_transforms(
&descriptors.device,
&bitmap.matrix,

View File

@ -7,11 +7,10 @@ use crate::surface::Surface::{Direct, DirectSrgb, Resolve, ResolveSrgb};
use crate::uniform_buffer::BufferStorage;
use crate::utils::remove_srgb;
use crate::{
create_buffer_with_data, ColorAdjustments, Descriptors, Globals, Pipelines, Texture,
TextureTransforms, Transforms, UniformBuffer,
create_buffer_with_data, ColorAdjustments, Descriptors, Globals, Pipelines, TextureTransforms,
Transforms, UniformBuffer,
};
use fnv::FnvHashMap;
use ruffle_render::bitmap::BitmapHandle;
use ruffle_render::commands::CommandList;
use std::sync::Arc;
@ -387,7 +386,6 @@ impl Surface {
globals: &mut Globals,
uniform_buffers_storage: &mut BufferStorage<Transforms>,
meshes: &Vec<Mesh>,
bitmap_registry: &FnvHashMap<BitmapHandle, Texture>,
commands: CommandList,
) -> Vec<wgpu::CommandBuffer> {
let label = create_debug_label!("Draw encoder");
@ -445,7 +443,6 @@ impl Surface {
commands.execute(&mut CommandRenderer::new(
&mut frame,
meshes,
bitmap_registry,
descriptors.quad.vertices.slice(..),
descriptors.quad.indices.slice(..),
));

View File

@ -2,6 +2,7 @@ use crate::utils::BufferDimensions;
use crate::Error;
use ruffle_render::utils::unmultiply_alpha_rgba;
use std::fmt::Debug;
use std::sync::Arc;
pub trait RenderTargetFrame: Debug {
fn into_view(self) -> wgpu::TextureView;
@ -136,9 +137,9 @@ impl RenderTarget for SwapChainTarget {
#[derive(Debug)]
pub struct TextureTarget {
pub size: wgpu::Extent3d,
pub texture: wgpu::Texture,
pub texture: Arc<wgpu::Texture>,
pub format: wgpu::TextureFormat,
pub buffer: wgpu::Buffer,
pub buffer: Arc<wgpu::Buffer>,
pub buffer_dimensions: BufferDimensions,
}
@ -197,9 +198,9 @@ impl TextureTarget {
});
Ok(Self {
size,
texture,
texture: Arc::new(texture),
format,
buffer,
buffer: Arc::new(buffer),
buffer_dimensions,
})
}

View File

@ -88,7 +88,7 @@ pub fn create_buffer_with_data(
}
// Based off wgpu example 'capture'
#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct BufferDimensions {
pub width: usize,
pub height: usize,

View File

@ -78,12 +78,12 @@ impl VideoBackend for SoftwareVideoBackend {
.ok_or(Error::VideoStreamIsNotRegistered)?;
let frame = stream.decoder.decode_frame(encoded_frame)?;
let handle = if let Some(bitmap) = stream.bitmap {
let handle = if let Some(bitmap) = stream.bitmap.clone() {
renderer.update_texture(
bitmap,
&bitmap,
frame.width.into(),
frame.height.into(),
frame.rgba.clone(),
frame.rgba,
)?;
bitmap
} else {
@ -95,7 +95,7 @@ impl VideoBackend for SoftwareVideoBackend {
);
renderer.register_bitmap(bitmap)?
};
stream.bitmap = Some(handle);
stream.bitmap = Some(handle.clone());
Ok(BitmapInfo {
handle,