Repository: ruffle-rs/ruffle (2026-03-26T16:53:32Z)

Issue:
Animated graphic doesn't invalidate parent AVM2Button's cached bitmap
### Describe the bug

Consider the Play button from #11907: [BurgeriaMenuButtons.zip](https://github.com/ruffle-rs/ruffle/files/11954572/BurgeriaMenuButtons.zip)
The idle/attract highlight animation has a noticeable pause in the middle:
![ruffle_BurgeriaPlayButton](https://github.com/ruffle-rs/ruffle/assets/71368227/9922d47f-5231-4ec0-a816-81b5328ce869)

Here is a simplified project with frames added to the animation to make it even more obvious: [BurgeriaPlayButtonNoBlend.zip](https://github.com/ruffle-rs/ruffle/files/13997160/BurgeriaPlayButtonNoBlend.zip)

Notice also that the pause does not happen in the SWF from #14858. This is because the problem only affects AVM2Button, not AVM1Button. The button has Cache as Bitmap enabled, and each frame of the animation should invalidate the parent button's bitmap cache, but this is not happening in AVM2Button.

### Expected behavior

The idle/attract animation should play smoothly:
![flashplayer_BurgeriaPlayButton](https://github.com/ruffle-rs/ruffle/assets/71368227/3c1cbaf6-0e46-4a6d-b119-caac840100f7)


### Content Location

Original project: [BurgeriaMenuButtons.zip](https://github.com/ruffle-rs/ruffle/files/11954572/BurgeriaMenuButtons.zip)
Simplified project: [BurgeriaPlayButtonNoBlend.zip](https://github.com/ruffle-rs/ruffle/files/13997160/BurgeriaPlayButtonNoBlend.zip)

### Affected platform

Desktop app

### Operating system

Windows 10

### Browser

_No response_

### Additional information

Possibly related to #14755.


Discussion:

Patch:
diff --git a/core/src/display_object/avm2_button.rs b/core/src/display_object/avm2_button.rs
index 3fc3610bce55..56277523bdbd 100644
--- a/core/src/display_object/avm2_button.rs
+++ b/core/src/display_object/avm2_button.rs
@@ -570,6 +570,7 @@ impl<'gc> TDisplayObject<'gc> for Avm2Button<'gc> {
         let current_state = self.get_state_child(self.0.state.get().into());
 
         if let Some(state) = current_state {
+            state.pre_render(context);
             state.render(context);
         }
     }


Test patch:
diff --git a/tests/tests/swfs/visual/cache_as_bitmap/avm2_button_state/output.expected.png b/tests/tests/swfs/visual/cache_as_bitmap/avm2_button_state/output.expected.png
new file mode 100644
index 000000000000..f12a6770efb7
Binary files /dev/null and b/tests/tests/swfs/visual/cache_as_bitmap/avm2_button_state/output.expected.png differ
diff --git a/tests/tests/swfs/visual/cache_as_bitmap/avm2_button_state/output.txt b/tests/tests/swfs/visual/cache_as_bitmap/avm2_button_state/output.txt
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/tests/tests/swfs/visual/cache_as_bitmap/avm2_button_state/test.swf b/tests/tests/swfs/visual/cache_as_bitmap/avm2_button_state/test.swf
new file mode 100644
index 000000000000..5ea0bc0cac97
Binary files /dev/null and b/tests/tests/swfs/visual/cache_as_bitmap/avm2_button_state/test.swf differ
diff --git a/tests/tests/swfs/visual/cache_as_bitmap/avm2_button_state/test.toml b/tests/tests/swfs/visual/cache_as_bitmap/avm2_button_state/test.toml
new file mode 100644
index 000000000000..aaff6724029c
--- /dev/null
+++ b/tests/tests/swfs/visual/cache_as_bitmap/avm2_button_state/test.toml
@@ -0,0 +1,8 @@
+num_frames = 3
+
+[image_comparisons.output]
+tolerance = 2
+max_outliers = 900
+
+[player_options]
+with_renderer = { optional = false, quality = "high" }