diff --git a/core/src/bitmap/operations.rs b/core/src/bitmap/operations.rs index b0f8ec44cf6c..e699cbf19e81 100644 --- a/core/src/bitmap/operations.rs +++ b/core/src/bitmap/operations.rs @@ -1526,7 +1526,13 @@ pub fn draw<'gc>( library: context.library, transform_stack: &mut transform_stack, is_offscreen: true, - use_bitmap_cache: false, + + // Use CAB only when drawing with the same quality, because this draw + // will update caches. When drawing with a different quality, we would + // either (1) update caches for regular draws with this temporary + // quality, or (2) draw cached bitmaps with the regular quality. + // TODO Support CAB draw with different quality and remove this param. + use_bitmap_cache: context.stage.quality() == quality, stage: context.stage, }; @@ -1597,13 +1603,10 @@ pub fn draw<'gc>( dirty_region.union(old); } - assert!( - cache_draws.is_empty(), - "BitmapData.draw() should not use cacheAsBitmap" - ); - let image = context - .renderer - .render_offscreen(handle, commands, quality, dirty_region); + let image = + context + .renderer + .render_offscreen(handle, commands, quality, dirty_region, cache_draws); match image { Some(sync_handle) => { diff --git a/render/canvas/src/lib.rs b/render/canvas/src/lib.rs index 8e2706d82bcb..9de21ffa793a 100644 --- a/render/canvas/src/lib.rs +++ b/render/canvas/src/lib.rs @@ -511,6 +511,7 @@ impl RenderBackend for WebCanvasRenderBackend { _commands: CommandList, _quality: StageQuality, _bounds: PixelRegion, + _cache_entries: Vec, ) -> Option> { None } diff --git a/render/src/backend.rs b/render/src/backend.rs index 47b2b975e5fa..70fe1971bee8 100644 --- a/render/src/backend.rs +++ b/render/src/backend.rs @@ -41,6 +41,7 @@ pub trait RenderBackend: Any { commands: CommandList, quality: StageQuality, bounds: PixelRegion, + cache_entries: Vec, ) -> Option>; /// Applies the given filter with a `BitmapHandle` source onto a destination `BitmapHandle`. diff --git a/render/src/backend/null.rs b/render/src/backend/null.rs index d84504059175..2ba615260f38 100644 --- a/render/src/backend/null.rs +++ b/render/src/backend/null.rs @@ -68,6 +68,7 @@ impl RenderBackend for NullRenderer { _commands: CommandList, _quality: StageQuality, _bounds: PixelRegion, + _cache_entries: Vec, ) -> Option> { None } diff --git a/render/webgl/src/lib.rs b/render/webgl/src/lib.rs index 902add071d6b..af279a187655 100644 --- a/render/webgl/src/lib.rs +++ b/render/webgl/src/lib.rs @@ -997,6 +997,7 @@ impl RenderBackend for WebGlRenderBackend { _commands: CommandList, _quality: StageQuality, _bounds: PixelRegion, + _cache_entries: Vec, ) -> Option> { None } diff --git a/render/wgpu/src/backend.rs b/render/wgpu/src/backend.rs index d10be0ce8073..885218c982a9 100644 --- a/render/wgpu/src/backend.rs +++ b/render/wgpu/src/backend.rs @@ -368,6 +368,85 @@ impl WgpuRenderBackend { }) => unreachable!("Buffer must be Borrowed as it was set to be Borrowed earlier"), } } + + fn draw_cache(&mut self, cache_entries: Vec) { + for entry in cache_entries { + let texture = as_texture(&entry.handle); + let mut surface = Surface::new( + &self.descriptors, + self.surface.quality(), + texture.texture.width(), + texture.texture.height(), + wgpu::TextureFormat::Rgba8Unorm, + ); + if entry.filters.is_empty() { + surface.draw_commands( + RenderTargetMode::ExistingWithColor( + texture.texture.clone(), + wgpu::Color { + r: f64::from(entry.clear.r) / 255.0, + g: f64::from(entry.clear.g) / 255.0, + b: f64::from(entry.clear.b) / 255.0, + a: f64::from(entry.clear.a) / 255.0, + }, + ), + &self.descriptors, + &self.meshes, + entry.commands, + &mut self.active_frame.staging_belt, + &self.dynamic_transforms, + &mut self.active_frame.command_encoder, + LayerRef::None, + &mut self.offscreen_texture_pool, + ); + } else { + // We're relying on there being no impotent filters here, + // so that we can safely start by using the actual CAB texture. + // It's guaranteed that at least one filter would have used it and moved the target to something else, + // letting us safely copy back to it later. + let mut target = surface.draw_commands( + RenderTargetMode::ExistingWithColor( + texture.texture.clone(), + wgpu::Color { + r: f64::from(entry.clear.r) / 255.0, + g: f64::from(entry.clear.g) / 255.0, + b: f64::from(entry.clear.b) / 255.0, + a: f64::from(entry.clear.a) / 255.0, + }, + ), + &self.descriptors, + &self.meshes, + entry.commands, + &mut self.active_frame.staging_belt, + &self.dynamic_transforms, + &mut self.active_frame.command_encoder, + LayerRef::None, + &mut self.offscreen_texture_pool, + ); + for filter in entry.filters { + target = self.descriptors.filters.apply( + &self.descriptors, + &mut self.active_frame.command_encoder, + &mut self.offscreen_texture_pool, + &mut self.active_frame.staging_belt, + FilterSource::for_entire_texture(target.color_texture()), + filter, + ); + } + run_copy_pipeline( + &self.descriptors, + target.color_texture().format(), + texture.texture.format(), + &texture.texture.create_view(&Default::default()), + target.color_view(), + target.whole_frame_bind_group(&self.descriptors), + target.globals(), + target.color_texture().sample_count(), + &mut self.active_frame.command_encoder, + ); + } + } + } } impl RenderBackend for WgpuRenderBackend { @@ -499,82 +578,7 @@ impl RenderBackend for WgpuRenderBackend { } }; - for entry in cache_entries { - let texture = as_texture(&entry.handle); - let mut surface = Surface::new( - &self.descriptors, - self.surface.quality(), - texture.texture.width(), - texture.texture.height(), - wgpu::TextureFormat::Rgba8Unorm, - ); - if entry.filters.is_empty() { - surface.draw_commands( - RenderTargetMode::ExistingWithColor( - texture.texture.clone(), - wgpu::Color { - r: f64::from(entry.clear.r) / 255.0, - g: f64::from(entry.clear.g) / 255.0, - b: f64::from(entry.clear.b) / 255.0, - a: f64::from(entry.clear.a) / 255.0, - }, - ), - &self.descriptors, - &self.meshes, - entry.commands, - &mut self.active_frame.staging_belt, - &self.dynamic_transforms, - &mut self.active_frame.command_encoder, - LayerRef::None, - &mut self.offscreen_texture_pool, - ); - } else { - // We're relying on there being no impotent filters here, - // so that we can safely start by using the actual CAB texture. - // It's guaranteed that at least one filter would have used it and moved the target to something else, - // letting us safely copy back to it later. - let mut target = surface.draw_commands( - RenderTargetMode::ExistingWithColor( - texture.texture.clone(), - wgpu::Color { - r: f64::from(entry.clear.r) / 255.0, - g: f64::from(entry.clear.g) / 255.0, - b: f64::from(entry.clear.b) / 255.0, - a: f64::from(entry.clear.a) / 255.0, - }, - ), - &self.descriptors, - &self.meshes, - entry.commands, - &mut self.active_frame.staging_belt, - &self.dynamic_transforms, - &mut self.active_frame.command_encoder, - LayerRef::None, - &mut self.offscreen_texture_pool, - ); - for filter in entry.filters { - target = self.descriptors.filters.apply( - &self.descriptors, - &mut self.active_frame.command_encoder, - &mut self.offscreen_texture_pool, - &mut self.active_frame.staging_belt, - FilterSource::for_entire_texture(target.color_texture()), - filter, - ); - } - run_copy_pipeline( - &self.descriptors, - target.color_texture().format(), - texture.texture.format(), - &texture.texture.create_view(&Default::default()), - target.color_view(), - target.whole_frame_bind_group(&self.descriptors), - target.globals(), - target.color_texture().sample_count(), - &mut self.active_frame.command_encoder, - ); - } - } + self.draw_cache(cache_entries); self.surface.draw_commands_and_copy_to( frame_output.view(), @@ -710,6 +714,7 @@ impl RenderBackend for WgpuRenderBackend { commands: CommandList, quality: StageQuality, bounds: PixelRegion, + cache_entries: Vec, ) -> Option> { let texture = as_texture(&handle); @@ -730,6 +735,8 @@ impl RenderBackend for WgpuRenderBackend { .get_next_texture() .expect("TextureTargetFrame.get_next_texture is infallible"); + self.draw_cache(cache_entries); + let mut surface = Surface::new( &self.descriptors, quality, diff --git a/tests/tests/swfs/avm2/away3d_advanced_shallow_water_demo/output.expected.png b/tests/tests/swfs/avm2/away3d_advanced_shallow_water_demo/output.expected.png index 10fd22d3e9ee..2b3304c3c2e8 100644 Binary files a/tests/tests/swfs/avm2/away3d_advanced_shallow_water_demo/output.expected.png and b/tests/tests/swfs/avm2/away3d_advanced_shallow_water_demo/output.expected.png differ diff --git a/tests/tests/swfs/avm2/away3d_advanced_shallow_water_demo/test.toml b/tests/tests/swfs/avm2/away3d_advanced_shallow_water_demo/test.toml index af1368fe01b4..de80e816e7d1 100644 --- a/tests/tests/swfs/avm2/away3d_advanced_shallow_water_demo/test.toml +++ b/tests/tests/swfs/avm2/away3d_advanced_shallow_water_demo/test.toml @@ -2,7 +2,7 @@ num_ticks = 20 [image_comparisons.output] tolerance = 30 -max_outliers = 400 +max_outliers = 500 [player_options] with_renderer = { optional = false, sample_count = 1 } diff --git a/tests/tests/swfs/avm2/bitmapdata_draw_cab_quality/Test.as b/tests/tests/swfs/avm2/bitmapdata_draw_cab_quality/Test.as new file mode 100644 index 000000000000..f223d011bded --- /dev/null +++ b/tests/tests/swfs/avm2/bitmapdata_draw_cab_quality/Test.as @@ -0,0 +1,39 @@ +package { +import flash.display.*; +import flash.geom.*; +import flash.filters.*; +import flash.utils.*; + +[SWF(width="40", height="40")] +public class Test extends MovieClip { + public function Test() { + var s:Sprite = new Sprite(); + s.graphics.beginFill(0xFF00FF); + s.graphics.drawTriangles(Vector.([ + 0, 0, + 0, 20, + 20, 20 + ]), Vector.([ + 0, 1, 2 + ])); + s.graphics.endFill(); + s.cacheAsBitmap = true; + + var bd:BitmapData = new BitmapData(40, 20); + bd.fillRect(new Rectangle(0,0,40,20), 0xFF000000); + bd.drawWithQuality( + s, + new Matrix(1, 0, 0, 1, 20, 0), + new ColorTransform(0, 1, 1), + "difference", + new Rectangle(0, 10, 40, 10), + false, + "low" + ); + var b:Bitmap = new Bitmap(bd); + b.y = 20; + addChild(b); + addChild(s); + } +} +} diff --git a/tests/tests/swfs/avm2/bitmapdata_draw_cab_quality/output.expected.png b/tests/tests/swfs/avm2/bitmapdata_draw_cab_quality/output.expected.png new file mode 100644 index 000000000000..b253119c7cef Binary files /dev/null and b/tests/tests/swfs/avm2/bitmapdata_draw_cab_quality/output.expected.png differ diff --git a/tests/tests/swfs/avm2/bitmapdata_draw_cab_quality/output.txt b/tests/tests/swfs/avm2/bitmapdata_draw_cab_quality/output.txt new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/tests/tests/swfs/avm2/bitmapdata_draw_cab_quality/test.swf b/tests/tests/swfs/avm2/bitmapdata_draw_cab_quality/test.swf new file mode 100644 index 000000000000..e2a403abcf99 Binary files /dev/null and b/tests/tests/swfs/avm2/bitmapdata_draw_cab_quality/test.swf differ diff --git a/tests/tests/swfs/avm2/bitmapdata_draw_cab_quality/test.toml b/tests/tests/swfs/avm2/bitmapdata_draw_cab_quality/test.toml new file mode 100644 index 000000000000..7e9df1dc6fb6 --- /dev/null +++ b/tests/tests/swfs/avm2/bitmapdata_draw_cab_quality/test.toml @@ -0,0 +1,10 @@ +num_ticks = 1 + +[image_comparisons.output] +# Outliers come from the top triangle due to MSAA, +# the bottom one should produce a lot more in case something's wrong. +max_outliers = 80 + +[player_options] +with_renderer = { optional = false, sample_count = 4 } +viewport_dimensions = { width = 160, height = 160, scale_factor = 1 } diff --git a/tests/tests/swfs/avm2/bitmapdata_draw_filters/test.toml b/tests/tests/swfs/avm2/bitmapdata_draw_filters/test.toml index 0d8096bcf008..7c8574cf4c61 100644 --- a/tests/tests/swfs/avm2/bitmapdata_draw_filters/test.toml +++ b/tests/tests/swfs/avm2/bitmapdata_draw_filters/test.toml @@ -1,8 +1,5 @@ num_ticks = 1 -# FIXME Ruffle does not use CAB in BitmapData.draw -known_failure = true - [image_comparisons.output] tolerance = 0