diff --git a/Cargo.lock b/Cargo.lock index cc745f8eb..f1fe0859b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3088,6 +3088,7 @@ dependencies = [ name = "ruffle_render_canvas" version = "0.1.0" dependencies = [ + "fnv", "js-sys", "log", "ruffle_core", @@ -3110,6 +3111,7 @@ name = "ruffle_render_webgl" version = "0.1.0" dependencies = [ "bytemuck", + "fnv", "js-sys", "log", "ruffle_core", @@ -3126,6 +3128,7 @@ dependencies = [ "bytemuck", "clap", "enum-map", + "fnv", "futures", "image", "log", diff --git a/core/src/avm1/globals/bitmap_data.rs b/core/src/avm1/globals/bitmap_data.rs index 0f7112d20..f0fefb7f9 100644 --- a/core/src/avm1/globals/bitmap_data.rs +++ b/core/src/avm1/globals/bitmap_data.rs @@ -405,7 +405,7 @@ pub fn dispose<'gc>( ) -> Result, Error<'gc>> { if let Some(bitmap_data) = this.as_bitmap_data_object() { if !bitmap_data.disposed() { - bitmap_data.dispose(activation.context.gc_context); + bitmap_data.dispose(&mut activation.context); return Ok(Value::Undefined); } } diff --git a/core/src/avm1/object/bitmap_data.rs b/core/src/avm1/object/bitmap_data.rs index 5e3bd2196..366fbf399 100644 --- a/core/src/avm1/object/bitmap_data.rs +++ b/core/src/avm1/object/bitmap_data.rs @@ -1,5 +1,6 @@ use crate::add_field_accessors; use crate::avm1::{Object, ScriptObject, TObject}; +use crate::context::UpdateContext; use crate::impl_custom_object; use gc_arena::{Collect, GcCell, MutationContext}; @@ -54,9 +55,11 @@ impl<'gc> BitmapDataObject<'gc> { )) } - pub fn dispose(&self, gc_context: MutationContext<'gc, '_>) { - self.bitmap_data().write(gc_context).dispose(); - self.0.write(gc_context).disposed = true; + pub fn dispose(&self, context: &mut UpdateContext<'_, 'gc, '_>) { + self.bitmap_data() + .write(context.gc_context) + .dispose(context.renderer); + self.0.write(context.gc_context).disposed = true; } } diff --git a/core/src/backend/render.rs b/core/src/backend/render.rs index 2a4d926a8..f4bab3d3f 100644 --- a/core/src/backend/render.rs +++ b/core/src/backend/render.rs @@ -86,6 +86,9 @@ pub trait RenderBackend: Downcast { fn get_bitmap_pixels(&mut self, bitmap: BitmapHandle) -> Option; fn register_bitmap(&mut self, bitmap: Bitmap) -> Result; + // Frees memory used by the bitmap. After this call, `handle` can no longer + // be used. + fn unregister_bitmap(&mut self, handle: BitmapHandle) -> Result<(), Error>; fn update_texture( &mut self, bitmap: BitmapHandle, @@ -177,6 +180,9 @@ impl RenderBackend for NullRenderer { fn register_bitmap(&mut self, _bitmap: Bitmap) -> Result { Ok(BitmapHandle(0)) } + fn unregister_bitmap(&mut self, _bitmap: BitmapHandle) -> Result<(), Error> { + Ok(()) + } fn update_texture( &mut self, diff --git a/core/src/bitmap/bitmap_data.rs b/core/src/bitmap/bitmap_data.rs index 6383ec71e..05868d926 100644 --- a/core/src/bitmap/bitmap_data.rs +++ b/core/src/bitmap/bitmap_data.rs @@ -168,11 +168,18 @@ impl<'gc> BitmapData<'gc> { self.dirty = true; } - pub fn dispose(&mut self) { + pub fn dispose(&mut self, renderer: &mut dyn RenderBackend) { self.width = 0; self.height = 0; self.pixels.clear(); - self.dirty = true; + if let Some(handle) = self.bitmap_handle { + if let Err(e) = renderer.unregister_bitmap(handle) { + log::warn!("Failed to unregister bitmap {:?}: {:?}", handle, e); + } + self.bitmap_handle = None; + } + // There's no longer a handle to update + self.dirty = false; } pub fn bitmap_handle(&mut self, renderer: &mut dyn RenderBackend) -> Option { diff --git a/render/canvas/Cargo.toml b/render/canvas/Cargo.toml index 128a3a34c..7d789d112 100644 --- a/render/canvas/Cargo.toml +++ b/render/canvas/Cargo.toml @@ -10,6 +10,7 @@ js-sys = "0.3.57" log = "0.4" ruffle_web_common = { path = "../../web/common" } wasm-bindgen = "=0.2.80" +fnv = "1.0.7" [dependencies.ruffle_core] path = "../../core" diff --git a/render/canvas/src/lib.rs b/render/canvas/src/lib.rs index 302d5df08..f5a81f7ce 100644 --- a/render/canvas/src/lib.rs +++ b/render/canvas/src/lib.rs @@ -1,3 +1,4 @@ +use fnv::FnvHashMap; use ruffle_core::backend::render::{ swf, Bitmap, BitmapFormat, BitmapHandle, BitmapSource, Color, NullBitmapSource, RenderBackend, ShapeHandle, Transform, @@ -21,11 +22,12 @@ pub struct WebCanvasRenderBackend { context: CanvasRenderingContext2d, color_matrix: Element, shapes: Vec, - bitmaps: Vec, + bitmaps: FnvHashMap, viewport_width: u32, viewport_height: u32, rect: Path2d, mask_state: MaskState, + next_bitmap_handle: BitmapHandle, } /// Canvas-drawable shape data extracted from an SWF file. @@ -237,11 +239,12 @@ impl WebCanvasRenderBackend { color_matrix, context, shapes: vec![], - bitmaps: vec![], + bitmaps: Default::default(), viewport_width: 0, viewport_height: 0, rect, mask_state: MaskState::DrawContent, + next_bitmap_handle: BitmapHandle(0), }; Ok(renderer) } @@ -369,7 +372,7 @@ impl RenderBackend for WebCanvasRenderBackend { self.set_transform(&transform.matrix); self.set_color_filter(transform); - if let Some(bitmap) = self.bitmaps.get(bitmap.0) { + if let Some(bitmap) = self.bitmaps.get(&bitmap) { let _ = self .context .draw_image_with_html_canvas_element(&bitmap.canvas, 0.0, 0.0); @@ -590,17 +593,23 @@ impl RenderBackend for WebCanvasRenderBackend { } fn get_bitmap_pixels(&mut self, bitmap: BitmapHandle) -> Option { - let bitmap = &self.bitmaps[bitmap.0]; + let bitmap = &self.bitmaps[&bitmap]; bitmap.get_pixels() } fn register_bitmap(&mut self, bitmap: Bitmap) -> Result { - let handle = BitmapHandle(self.bitmaps.len()); + let handle = self.next_bitmap_handle; + self.next_bitmap_handle = BitmapHandle(self.next_bitmap_handle.0 + 1); let bitmap_data = BitmapData::new(bitmap)?; - self.bitmaps.push(bitmap_data); + self.bitmaps.insert(handle, bitmap_data); Ok(handle) } + fn unregister_bitmap(&mut self, bitmap: BitmapHandle) -> Result<(), Error> { + self.bitmaps.remove(&bitmap); + Ok(()) + } + fn update_texture( &mut self, handle: BitmapHandle, @@ -610,8 +619,10 @@ impl RenderBackend for WebCanvasRenderBackend { ) -> Result { // TODO: Could be optimized to a single put_image_data call // in case it is already stored as a canvas+context. - self.bitmaps[handle.0] = - BitmapData::new(Bitmap::new(width, height, BitmapFormat::Rgba, rgba))?; + self.bitmaps.insert( + handle, + BitmapData::new(Bitmap::new(width, height, BitmapFormat::Rgba, rgba))?, + ); Ok(handle) } } @@ -647,7 +658,7 @@ fn draw_commands_to_path2d(commands: &[DrawCommand], is_closed: bool) -> Path2d fn swf_shape_to_canvas_commands( shape: &DistilledShape, bitmap_source: &dyn BitmapSource, - bitmaps: &[BitmapData], + bitmaps: &FnvHashMap, context: &CanvasRenderingContext2d, ) -> ShapeData { use ruffle_core::shape_utils::DrawPath; @@ -710,7 +721,7 @@ fn swf_shape_to_canvas_commands( } => { if let Some(bitmap) = bitmap_source .bitmap(*id) - .and_then(|bitmap| bitmaps.get(bitmap.handle.0)) + .and_then(|bitmap| bitmaps.get(&bitmap.handle)) { let repeat = if !*is_repeating { // NOTE: The WebGL backend does clamping in this case, just like diff --git a/render/webgl/Cargo.toml b/render/webgl/Cargo.toml index 98dde2fb8..6e272c666 100644 --- a/render/webgl/Cargo.toml +++ b/render/webgl/Cargo.toml @@ -12,6 +12,7 @@ ruffle_render_common_tess = { path = "../common_tess" } ruffle_web_common = { path = "../../web/common" } wasm-bindgen = "=0.2.80" bytemuck = { version = "1.9.1", features = ["derive"] } +fnv = "1.0.7" [dependencies.ruffle_core] path = "../../core" diff --git a/render/webgl/src/lib.rs b/render/webgl/src/lib.rs index 4235a8ae5..8ed5cc726 100644 --- a/render/webgl/src/lib.rs +++ b/render/webgl/src/lib.rs @@ -1,4 +1,5 @@ use bytemuck::{Pod, Zeroable}; +use fnv::FnvHashMap; use ruffle_core::backend::render::{ Bitmap, BitmapFormat, BitmapHandle, BitmapSource, Color, NullBitmapSource, RenderBackend, ShapeHandle, Transform, @@ -9,7 +10,6 @@ use ruffle_render_common_tess::{ Gradient as TessGradient, GradientType, ShapeTessellator, Vertex as TessVertex, }; use ruffle_web_common::JsResult; -use std::collections::HashMap; use wasm_bindgen::{JsCast, JsValue}; use web_sys::{ HtmlCanvasElement, OesVertexArrayObject, WebGl2RenderingContext as Gl2, WebGlBuffer, @@ -75,7 +75,6 @@ pub struct WebGlRenderBackend { shape_tessellator: ShapeTessellator, - textures: Vec, meshes: Vec, color_quad_shape: ShapeHandle, @@ -95,7 +94,13 @@ pub struct WebGlRenderBackend { renderbuffer_height: i32, view_matrix: [[f32; 4]; 4], - bitmap_registry: HashMap, + bitmap_registry: FnvHashMap, + next_bitmap_handle: BitmapHandle, +} + +struct RegistryData { + bitmap: Bitmap, + texture_wrapper: Texture, } const MAX_GRADIENT_COLORS: usize = 15; @@ -233,7 +238,6 @@ impl WebGlRenderBackend { meshes: vec![], color_quad_shape: ShapeHandle(0), bitmap_quad_shape: ShapeHandle(1), - textures: vec![], renderbuffer_width: 1, renderbuffer_height: 1, view_matrix: [[0.0; 4]; 4], @@ -247,7 +251,8 @@ impl WebGlRenderBackend { blend_func: (Gl::ONE, Gl::ONE_MINUS_SRC_ALPHA), mult_color: None, add_color: None, - bitmap_registry: HashMap::new(), + bitmap_registry: Default::default(), + next_bitmap_handle: BitmapHandle(0), }; let color_quad_mesh = renderer.build_quad_mesh(&renderer.color_program)?; @@ -821,7 +826,8 @@ impl RenderBackend for WebGlRenderBackend { fn render_bitmap(&mut self, bitmap: BitmapHandle, transform: &Transform, smoothing: bool) { self.set_stencil_state(); - if let Some(bitmap) = self.textures.get(bitmap.0) { + if let Some(entry) = self.bitmap_registry.get(&bitmap) { + let bitmap = &entry.texture_wrapper; let texture = &bitmap.texture; // Adjust the quad draw to use the target bitmap. let mesh = &self.meshes[self.bitmap_quad_shape.0]; @@ -1019,8 +1025,8 @@ impl RenderBackend for WebGlRenderBackend { ); } DrawType::Bitmap(bitmap) => { - let texture = if let Some(texture) = self.textures.get(bitmap.handle.0) { - texture + let texture = if let Some(entry) = self.bitmap_registry.get(&bitmap.handle) { + &entry.texture_wrapper } else { // Bitmap not registered continue; @@ -1168,7 +1174,7 @@ impl RenderBackend for WebGlRenderBackend { } fn get_bitmap_pixels(&mut self, bitmap: BitmapHandle) -> Option { - self.bitmap_registry.get(&bitmap).cloned() + self.bitmap_registry.get(&bitmap).map(|e| e.bitmap.clone()) } fn register_bitmap(&mut self, bitmap: Bitmap) -> Result { @@ -1203,20 +1209,30 @@ impl RenderBackend for WebGlRenderBackend { self.gl .tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_MAG_FILTER, Gl::LINEAR as i32); - let handle = BitmapHandle(self.textures.len()); + let handle = self.next_bitmap_handle; + self.next_bitmap_handle = BitmapHandle(self.next_bitmap_handle.0 + 1); let width = bitmap.width(); let height = bitmap.height(); - self.bitmap_registry.insert(handle, bitmap); - - self.textures.push(Texture { - width, - height, - texture, - }); + self.bitmap_registry.insert( + handle, + RegistryData { + bitmap, + texture_wrapper: Texture { + width, + height, + texture, + }, + }, + ); Ok(handle) } + fn unregister_bitmap(&mut self, bitmap: BitmapHandle) -> Result<(), Error> { + self.bitmap_registry.remove(&bitmap); + Ok(()) + } + fn update_texture( &mut self, handle: BitmapHandle, @@ -1224,8 +1240,8 @@ impl RenderBackend for WebGlRenderBackend { height: u32, rgba: Vec, ) -> Result { - let texture = if let Some(texture) = self.textures.get(handle.0) { - texture + let texture = if let Some(entry) = self.bitmap_registry.get(&handle) { + &entry.texture_wrapper } else { return Err("update_texture: Bitmap is not regsitered".into()); }; diff --git a/render/wgpu/Cargo.toml b/render/wgpu/Cargo.toml index 85eeecefd..0de2b6291 100644 --- a/render/wgpu/Cargo.toml +++ b/render/wgpu/Cargo.toml @@ -13,6 +13,7 @@ bytemuck = { version = "1.9.1", features = ["derive"] } raw-window-handle = "0.4" clap = { version = "3.2.2", features = ["derive"], optional = true } enum-map = "2.4.0" +fnv = "1.0.7" # desktop [target.'cfg(not(target_arch = "wasm32"))'.dependencies.futures] diff --git a/render/wgpu/src/lib.rs b/render/wgpu/src/lib.rs index 8afb445cb..cade06426 100644 --- a/render/wgpu/src/lib.rs +++ b/render/wgpu/src/lib.rs @@ -3,6 +3,7 @@ use crate::target::{RenderTarget, RenderTargetFrame, SwapChainTarget}; use crate::utils::{create_buffer_with_data, format_list, get_backend_names}; use bytemuck::{Pod, Zeroable}; use enum_map::Enum; +use fnv::FnvHashMap; use ruffle_core::backend::render::{ Bitmap, BitmapHandle, BitmapSource, Color, RenderBackend, ShapeHandle, Transform, }; @@ -32,7 +33,6 @@ pub mod clap; use crate::bitmaps::BitmapSamplers; use crate::globals::Globals; use crate::uniform_buffer::UniformBuffer; -use std::collections::HashMap; use std::path::Path; pub use wgpu; @@ -149,12 +149,17 @@ pub struct WgpuRenderBackend { meshes: Vec, mask_state: MaskState, shape_tessellator: ShapeTessellator, - textures: Vec, num_masks: u32, quad_vbo: wgpu::Buffer, quad_ibo: wgpu::Buffer, quad_tex_transforms: wgpu::Buffer, - bitmap_registry: HashMap, + bitmap_registry: FnvHashMap, + next_bitmap_handle: BitmapHandle, +} + +struct RegistryData { + bitmap: Bitmap, + texture_wrapper: Texture, } #[allow(dead_code)] @@ -481,7 +486,6 @@ impl WgpuRenderBackend { current_frame: None, meshes: Vec::new(), shape_tessellator: ShapeTessellator::new(), - textures: Vec::new(), num_masks: 0, mask_state: MaskState::NoMask, @@ -489,7 +493,8 @@ impl WgpuRenderBackend { quad_vbo, quad_ibo, quad_tex_transforms, - bitmap_registry: HashMap::new(), + bitmap_registry: Default::default(), + next_bitmap_handle: BitmapHandle(0), }) } @@ -658,8 +663,11 @@ impl WgpuRenderBackend { } } TessDrawType::Bitmap(bitmap) => { - let texture = self.textures.get(bitmap.bitmap.0).unwrap(); - let texture_view = texture.texture.create_view(&Default::default()); + let entry = self.bitmap_registry.get(&bitmap.bitmap).unwrap(); + let texture_view = entry + .texture_wrapper + .texture + .create_view(&Default::default()); // TODO: Extract to function? let mut texture_transform = [[0.0; 4]; 4]; @@ -952,7 +960,8 @@ impl RenderBackend for WgpuRenderBackend { } fn render_bitmap(&mut self, bitmap: BitmapHandle, transform: &Transform, smoothing: bool) { - if let Some(texture) = self.textures.get(bitmap.0) { + if let Some(entry) = self.bitmap_registry.get(&bitmap) { + let texture = &entry.texture_wrapper; let frame = if let Some(frame) = &mut self.current_frame { frame.get() } else { @@ -1320,7 +1329,7 @@ impl RenderBackend for WgpuRenderBackend { } fn get_bitmap_pixels(&mut self, bitmap: BitmapHandle) -> Option { - self.bitmap_registry.get(&bitmap).cloned() + self.bitmap_registry.get(&bitmap).map(|e| e.bitmap.clone()) } fn register_bitmap(&mut self, bitmap: Bitmap) -> Result { @@ -1361,7 +1370,8 @@ impl RenderBackend for WgpuRenderBackend { extent, ); - let handle = BitmapHandle(self.textures.len()); + let handle = self.next_bitmap_handle; + self.next_bitmap_handle = BitmapHandle(self.next_bitmap_handle.0 + 1); let width = bitmap.width(); let height = bitmap.height(); @@ -1391,17 +1401,27 @@ impl RenderBackend for WgpuRenderBackend { label: create_debug_label!("Bitmap {} bind group", handle.0).as_deref(), }); - self.bitmap_registry.insert(handle, bitmap); - self.textures.push(Texture { - width, - height, - texture, - bind_group, - }); + self.bitmap_registry.insert( + handle, + RegistryData { + bitmap, + texture_wrapper: Texture { + width, + height, + texture, + bind_group, + }, + }, + ); Ok(handle) } + fn unregister_bitmap(&mut self, handle: BitmapHandle) -> Result<(), Error> { + self.bitmap_registry.remove(&handle); + Ok(()) + } + fn update_texture( &mut self, handle: BitmapHandle, @@ -1409,8 +1429,8 @@ impl RenderBackend for WgpuRenderBackend { height: u32, rgba: Vec, ) -> Result { - let texture = if let Some(texture) = self.textures.get(handle.0) { - &texture.texture + let texture = if let Some(entry) = self.bitmap_registry.get(&handle) { + &entry.texture_wrapper.texture } else { return Err("update_texture: Bitmap not registered".into()); };