diff --git a/core/src/avm1/globals/movie_clip.rs b/core/src/avm1/globals/movie_clip.rs index f05c5ce4f..24819d610 100644 --- a/core/src/avm1/globals/movie_clip.rs +++ b/core/src/avm1/globals/movie_clip.rs @@ -1608,6 +1608,12 @@ fn set_transform<'gc>( let color_transform = *clip.base().color_transform(); this.set_color_transform(activation.context.gc_context, color_transform); + if let Some(parent) = this.parent() { + // Self-transform changes are automatically handled, + // we only want to inform ancestors to avoid unnecessary invalidations for tx/ty + parent.invalidate_cached_bitmap(activation.context.gc_context); + } + this.set_transformed_by_script(activation.context.gc_context, true); } } diff --git a/core/src/avm1/globals/transform.rs b/core/src/avm1/globals/transform.rs index 02836ab69..931508197 100644 --- a/core/src/avm1/globals/transform.rs +++ b/core/src/avm1/globals/transform.rs @@ -97,6 +97,11 @@ fn method<'gc>( let matrix = object_to_matrix(object, activation)?; clip.set_matrix(activation.context.gc_context, matrix); clip.set_transformed_by_script(activation.context.gc_context, true); + if let Some(parent) = clip.parent() { + // Self-transform changes are automatically handled, + // we only want to inform ancestors to avoid unnecessary invalidations for tx/ty + parent.invalidate_cached_bitmap(activation.context.gc_context); + } } } Value::Undefined @@ -120,6 +125,7 @@ fn method<'gc>( activation.context.gc_context, color_transform.read().clone().into(), ); + clip.invalidate_cached_bitmap(activation.context.gc_context); clip.set_transformed_by_script(activation.context.gc_context, true); } } diff --git a/core/src/avm2/globals/flash/display/display_object.rs b/core/src/avm2/globals/flash/display/display_object.rs index 7bbeaa4ce..bb53245b3 100644 --- a/core/src/avm2/globals/flash/display/display_object.rs +++ b/core/src/avm2/globals/flash/display/display_object.rs @@ -731,6 +731,12 @@ pub fn set_transform<'gc>( let mut write = dobj.base_mut(activation.context.gc_context); write.set_matrix(matrix); write.set_color_transform(color_transform); + drop(write); + if let Some(parent) = dobj.parent() { + // Self-transform changes are automatically handled, + // we only want to inform ancestors to avoid unnecessary invalidations for tx/ty + parent.invalidate_cached_bitmap(activation.context.gc_context); + } } Ok(Value::Undefined) } diff --git a/core/src/avm2/globals/flash/geom/transform.rs b/core/src/avm2/globals/flash/geom/transform.rs index dcb0b2214..fc0082538 100644 --- a/core/src/avm2/globals/flash/geom/transform.rs +++ b/core/src/avm2/globals/flash/geom/transform.rs @@ -53,9 +53,9 @@ pub fn set_color_transform<'gc>( ) -> Result, Error<'gc>> { let this = this.unwrap(); let ct = object_to_color_transform(args.get_object(activation, 0, "value")?, activation)?; - get_display_object(this, activation)? - .base_mut(activation.context.gc_context) - .set_color_transform(ct); + let dobj = get_display_object(this, activation)?; + dobj.set_color_transform(activation.context.gc_context, ct); + dobj.invalidate_cached_bitmap(activation.context.gc_context); Ok(Value::Undefined) } @@ -76,9 +76,13 @@ pub fn set_matrix<'gc>( ) -> Result, Error<'gc>> { let this = this.unwrap(); let matrix = object_to_matrix(args.get_object(activation, 0, "value")?, activation)?; - get_display_object(this, activation)? - .base_mut(activation.context.gc_context) - .set_matrix(matrix); + let dobj = get_display_object(this, activation)?; + dobj.set_matrix(activation.context.gc_context, matrix); + if let Some(parent) = dobj.parent() { + // Self-transform changes are automatically handled, + // we only want to inform ancestors to avoid unnecessary invalidations for tx/ty + parent.invalidate_cached_bitmap(activation.context.gc_context); + } Ok(Value::Undefined) } diff --git a/core/src/display_object.rs b/core/src/display_object.rs index bcd4778f3..c7598ca5e 100644 --- a/core/src/display_object.rs +++ b/core/src/display_object.rs @@ -941,10 +941,16 @@ pub trait TDisplayObject<'gc>: self.base_mut(gc_context).set_place_frame(frame) } + /// Sets the matrix of this object. + /// This does NOT invalidate the cache, as it's often used with other operations. + /// It is the callers responsibility to do so. fn set_matrix(&self, gc_context: MutationContext<'gc, '_>, matrix: Matrix) { self.base_mut(gc_context).set_matrix(matrix); } + /// Sets the color transform of this object. + /// This does NOT invalidate the cache, as it's often used with other operations. + /// It is the callers responsibility to do so. fn set_color_transform( &self, gc_context: MutationContext<'gc, '_>, @@ -1184,8 +1190,13 @@ pub trait TDisplayObject<'gc>: /// Sets the opacity of this display object. /// 1 is fully opaque. /// Set by the `_alpha`/`alpha` ActionScript properties. + /// This invalidates any cacheAsBitmap automatically. fn set_alpha(&self, gc_context: MutationContext<'gc, '_>, value: f64) { - self.base_mut(gc_context).set_alpha(value) + self.base_mut(gc_context).set_alpha(value); + if let Some(parent) = self.parent() { + // Self-transform changes are automatically handled + parent.invalidate_cached_bitmap(gc_context); + } } fn name(&self) -> AvmString<'gc> { @@ -1765,9 +1776,15 @@ pub trait TDisplayObject<'gc>: if !self.transformed_by_script() { if let Some(matrix) = place_object.matrix { self.set_matrix(context.gc_context, matrix.into()); + if let Some(parent) = self.parent() { + // Self-transform changes are automatically handled, + // we only want to inform ancestors to avoid unnecessary invalidations for tx/ty + parent.invalidate_cached_bitmap(context.gc_context); + } } if let Some(color_transform) = &place_object.color_transform { self.set_color_transform(context.gc_context, *color_transform); + self.invalidate_cached_bitmap(context.gc_context); } if let Some(ratio) = place_object.ratio { if let Some(mut morph_shape) = self.as_morph_shape() { diff --git a/core/src/display_object/avm1_button.rs b/core/src/display_object/avm1_button.rs index 95497d5ed..9a4415825 100644 --- a/core/src/display_object/avm1_button.rs +++ b/core/src/display_object/avm1_button.rs @@ -191,6 +191,8 @@ impl<'gc> Avm1Button<'gc> { dispatch_removed_event(removed_child, context); } } + + self.invalidate_cached_bitmap(context.gc_context); } fn get_boolean_property( diff --git a/core/src/display_object/avm2_button.rs b/core/src/display_object/avm2_button.rs index 381866d01..6d3fdf21b 100644 --- a/core/src/display_object/avm2_button.rs +++ b/core/src/display_object/avm2_button.rs @@ -227,6 +227,8 @@ impl<'gc> Avm2Button<'gc> { } } + self.invalidate_cached_bitmap(context.gc_context); + // We manually call `construct_frame` for `child` and `state_sprite` - normally // this would be done in the `DisplayObject` constructor, but SimpleButton does // not have children in the normal DisplayObjectContainer sense. diff --git a/tests/tests/swfs/visual/cache_as_bitmap/nested_color_transform/expected.png b/tests/tests/swfs/visual/cache_as_bitmap/nested_color_transform/expected.png new file mode 100644 index 000000000..5d33ff451 Binary files /dev/null and b/tests/tests/swfs/visual/cache_as_bitmap/nested_color_transform/expected.png differ diff --git a/tests/tests/swfs/visual/cache_as_bitmap/nested_color_transform/output.txt b/tests/tests/swfs/visual/cache_as_bitmap/nested_color_transform/output.txt new file mode 100644 index 000000000..e69de29bb diff --git a/tests/tests/swfs/visual/cache_as_bitmap/nested_color_transform/test.fla b/tests/tests/swfs/visual/cache_as_bitmap/nested_color_transform/test.fla new file mode 100644 index 000000000..3cdef79cd Binary files /dev/null and b/tests/tests/swfs/visual/cache_as_bitmap/nested_color_transform/test.fla differ diff --git a/tests/tests/swfs/visual/cache_as_bitmap/nested_color_transform/test.swf b/tests/tests/swfs/visual/cache_as_bitmap/nested_color_transform/test.swf new file mode 100644 index 000000000..abec704bf Binary files /dev/null and b/tests/tests/swfs/visual/cache_as_bitmap/nested_color_transform/test.swf differ diff --git a/tests/tests/swfs/visual/cache_as_bitmap/nested_color_transform/test.toml b/tests/tests/swfs/visual/cache_as_bitmap/nested_color_transform/test.toml new file mode 100644 index 000000000..4a2532106 --- /dev/null +++ b/tests/tests/swfs/visual/cache_as_bitmap/nested_color_transform/test.toml @@ -0,0 +1,7 @@ +num_frames = 3 + +[image_comparison] +tolerance = 0 + +[player_options] +with_renderer = { optional = false, sample_count = 1 } diff --git a/tests/tests/swfs/visual/cache_as_bitmap/nested_matrix/expected.png b/tests/tests/swfs/visual/cache_as_bitmap/nested_matrix/expected.png new file mode 100644 index 000000000..081ddcba7 Binary files /dev/null and b/tests/tests/swfs/visual/cache_as_bitmap/nested_matrix/expected.png differ diff --git a/tests/tests/swfs/visual/cache_as_bitmap/nested_matrix/output.txt b/tests/tests/swfs/visual/cache_as_bitmap/nested_matrix/output.txt new file mode 100644 index 000000000..e69de29bb diff --git a/tests/tests/swfs/visual/cache_as_bitmap/nested_matrix/test.fla b/tests/tests/swfs/visual/cache_as_bitmap/nested_matrix/test.fla new file mode 100644 index 000000000..01918afed Binary files /dev/null and b/tests/tests/swfs/visual/cache_as_bitmap/nested_matrix/test.fla differ diff --git a/tests/tests/swfs/visual/cache_as_bitmap/nested_matrix/test.swf b/tests/tests/swfs/visual/cache_as_bitmap/nested_matrix/test.swf new file mode 100644 index 000000000..117a4660f Binary files /dev/null and b/tests/tests/swfs/visual/cache_as_bitmap/nested_matrix/test.swf differ diff --git a/tests/tests/swfs/visual/cache_as_bitmap/nested_matrix/test.toml b/tests/tests/swfs/visual/cache_as_bitmap/nested_matrix/test.toml new file mode 100644 index 000000000..4a2532106 --- /dev/null +++ b/tests/tests/swfs/visual/cache_as_bitmap/nested_matrix/test.toml @@ -0,0 +1,7 @@ +num_frames = 3 + +[image_comparison] +tolerance = 0 + +[player_options] +with_renderer = { optional = false, sample_count = 1 }