From 53b648529639662ce0cea455166be352441a06de Mon Sep 17 00:00:00 2001 From: "Enzo Persillet (Tutez)" Date: Sun, 26 Jul 2026 06:15:12 +0200 Subject: [PATCH 1/2] Add opt-in hardware cache for Animate bitmaps --- .github/workflows/main.yml | 1 + README.md | 16 ++ .../exporters/animate/AnimateBitmapSymbol.hx | 18 +- src/swf/exporters/animate/AnimateLibrary.hx | 122 +++++++++++ .../exporters/animate/AnimateShapeSymbol.hx | 86 ++++++-- tests/src/TestMain.hx | 1 + tests/src/tests/AnimateBitmapSymbolTest.hx | 190 ++++++++++++++++++ 7 files changed, 420 insertions(+), 14 deletions(-) create mode 100644 tests/src/tests/AnimateBitmapSymbolTest.hx diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 5e2e928..01d29ea 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -125,6 +125,7 @@ jobs: working-directory: tests run: | haxelib run openfl test neko + haxelib run openfl test neko -D swf_hardware_bitmap_cache unit-test-hashlink: strategy: diff --git a/README.md b/README.md index 03e0e6a..89011fe 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,22 @@ There are three primary code paths within the library: This library can be called automatically by the OpenFL/Lime command-line tools to process `` tags, or it can be used on the command-line to process SWF files into Animate ZIP files. +Hardware-only Animate bitmap cache +================================== + +Native hardware-rendered projects may define `swf_hardware_bitmap_cache` to +share each Animate bitmap asset between bitmap instances. After its texture is +uploaded, the decoded CPU image is released. Bitmap-filled shapes are probed +against OpenFL's hardware graphics compatibility rules. Hardware-compatible +shapes use the same cache, while shapes that fall back to software rendering +keep one shared readable `BitmapData`. Shapes used directly by scale9 grids are +also kept readable. + +This mode is opt-in because cached bitmap assets used by direct `Bitmap` +instances become non-readable. Pixel read/write APIs and software-renderer +fallback are not supported for those bitmaps, and their textures cannot be +restored after a graphics context is lost. + Usage ===== diff --git a/src/swf/exporters/animate/AnimateBitmapSymbol.hx b/src/swf/exporters/animate/AnimateBitmapSymbol.hx index 3b5e06e..424eec7 100644 --- a/src/swf/exporters/animate/AnimateBitmapSymbol.hx +++ b/src/swf/exporters/animate/AnimateBitmapSymbol.hx @@ -22,9 +22,25 @@ class AnimateBitmapSymbol extends AnimateSymbol private override function __createObject(library:AnimateLibrary):Bitmap { #if lime - return new Bitmap(BitmapData.fromImage(library.getImage(path)), PixelSnapping.AUTO, smooth != false); + return new Bitmap(__createBitmapData(library), PixelSnapping.AUTO, smooth != false); #else return null; #end } + + private function __createBitmapData(library:AnimateLibrary):BitmapData + { + #if (lime && !flash && swf_hardware_bitmap_cache) + return library.__getHardwareBitmapData(this); + #else + return __createBitmapDataUncached(library); + #end + } + + @:allow(swf.exporters.animate.AnimateLibrary) + private function __createBitmapDataUncached(library:AnimateLibrary):BitmapData + { + var image = library.getImage(path); + return image != null ? BitmapData.fromImage(image) : null; + } } diff --git a/src/swf/exporters/animate/AnimateLibrary.hx b/src/swf/exporters/animate/AnimateLibrary.hx index 9aed6ed..e54cc05 100644 --- a/src/swf/exporters/animate/AnimateLibrary.hx +++ b/src/swf/exporters/animate/AnimateLibrary.hx @@ -5,6 +5,7 @@ import lime.graphics.Image; import lime.graphics.ImageChannel; import lime.math.Vector2; import swf.exporters.core.FilterType; +import openfl.display.BitmapData; import openfl.display.MovieClip; import openfl.events.Event; import openfl.events.IOErrorEvent; @@ -59,6 +60,10 @@ import openfl.filters.GlowFilter; private var bitmapClassNames:Map; private var bitmapSymbols:Array; private var frameRate:Float; + #if (lime && !flash && swf_hardware_bitmap_cache) + private var hardwareBitmapData:Map; + private var readableShapeBitmapData:Map; + #end private var id:String; private var instanceID:String; private var preloading:Bool; @@ -79,6 +84,10 @@ import openfl.filters.GlowFilter; alphaCheck = new Map(); bitmapClassNames = new Map(); + #if (lime && !flash && swf_hardware_bitmap_cache) + hardwareBitmapData = new Map(); + readableShapeBitmapData = new Map(); + #end #if (ios || tvos) rootPath = "assets/"; @@ -142,6 +151,111 @@ import openfl.filters.GlowFilter; return instances.get(uuid); } + #if (lime && !flash && swf_hardware_bitmap_cache) + @:allow(swf.exporters.animate.AnimateBitmapSymbol) + private function __getHardwareBitmapData(symbol:AnimateBitmapSymbol):BitmapData + { + var key = __getBitmapDataKey(symbol); + var bitmapData = hardwareBitmapData.get(key); + + if (bitmapData == null) + { + bitmapData = symbol.__createBitmapDataUncached(this); + + if (bitmapData != null) + { + bitmapData.disposeImage(); + hardwareBitmapData.set(key, bitmapData); + + if (symbol.path != null) + { + cachedImages.remove(symbol.path); + alphaCheck.remove(symbol.path); + } + + if (symbol.alpha != null) + { + cachedImages.remove(symbol.alpha); + } + } + } + + return bitmapData; + } + + @:allow(swf.exporters.animate.AnimateShapeSymbol) + private function __getReadableShapeBitmapData(symbol:AnimateBitmapSymbol):BitmapData + { + var key = __getBitmapDataKey(symbol); + var bitmapData = readableShapeBitmapData.get(key); + + if (bitmapData == null) + { + bitmapData = symbol.__createBitmapDataUncached(this); + + if (bitmapData != null) + { + readableShapeBitmapData.set(key, bitmapData); + } + } + + return bitmapData; + } + + private function __getBitmapDataKey(symbol:AnimateBitmapSymbol):String + { + return symbol.path != null + ? "path:" + symbol.path + "|alpha:" + (symbol.alpha != null ? symbol.alpha : "") + : "symbol:" + symbol.id; + } + + private function __markScale9GridShapesReadable():Void + { + for (symbol in symbols) + { + var spriteSymbol:AnimateSpriteSymbol; + #if (haxe_ver >= 4.2) + if (!Std.isOfType(symbol, AnimateSpriteSymbol)) + #else + if (!Std.is(symbol, AnimateSpriteSymbol)) + #end + { + continue; + } + else + { + spriteSymbol = cast symbol; + } + + if (spriteSymbol.scale9Grid == null) + { + continue; + } + + for (frame in spriteSymbol.frames) + { + if (frame.objects == null) + { + continue; + } + + for (frameObject in frame.objects) + { + var childSymbol = symbols.get(frameObject.symbol); + #if (haxe_ver >= 4.2) + if (Std.isOfType(childSymbol, AnimateShapeSymbol)) + #else + if (Std.is(childSymbol, AnimateShapeSymbol)) + #end + { + cast(childSymbol, AnimateShapeSymbol).requiresReadableBitmapData = true; + } + } + } + } + } + #end + #if lime public override function getImage(id:String):Image { @@ -306,6 +420,10 @@ import openfl.filters.GlowFilter; } } + #if (lime && !flash && swf_hardware_bitmap_cache) + __markScale9GridShapesReadable(); + #end + // SWFLite.instances.set(instanceID, swf); __load().onProgress(promise.progress).onError(promise.error).onComplete(function(_) @@ -448,6 +566,10 @@ import openfl.filters.GlowFilter; public override function unload():Void { instances.remove(uuid); + #if (lime && !flash && swf_hardware_bitmap_cache) + hardwareBitmapData = new Map(); + readableShapeBitmapData = new Map(); + #end if (symbols == null) return; // if (swf == null) return; diff --git a/src/swf/exporters/animate/AnimateShapeSymbol.hx b/src/swf/exporters/animate/AnimateShapeSymbol.hx index 7f2d1c9..e090046 100644 --- a/src/swf/exporters/animate/AnimateShapeSymbol.hx +++ b/src/swf/exporters/animate/AnimateShapeSymbol.hx @@ -8,6 +8,9 @@ import openfl.display.JointStyle; import openfl.display.LineScaleMode; import openfl.display.Shape; import openfl.display.SpreadMethod; +#if (lime && !flash && swf_hardware_bitmap_cache) +import openfl.display._internal.Context3DGraphics; +#end #if !openfl_debug @:fileXml('tags="haxe,release"') @@ -20,11 +23,20 @@ import openfl.display.SpreadMethod; @:access(openfl.display.JointStyle) @:access(openfl.display.LineScaleMode) @:access(openfl.display.SpreadMethod) +#if (lime && !flash && swf_hardware_bitmap_cache) +@:access(openfl.display._internal.Context3DGraphics) +#end class AnimateShapeSymbol extends AnimateSymbol { public var commands:Array; public var rendered:Shape; + #if (lime && !flash && swf_hardware_bitmap_cache) + private static var __probeBitmapData:BitmapData; + private var hardwareCompatible:Null; + private var requiresReadableBitmapData:Bool; + #end + public function new() { super(); @@ -41,6 +53,31 @@ class AnimateShapeSymbol extends AnimateSymbol return shape; } + var hardwareBitmapFills = false; + #if (lime && !flash && swf_hardware_bitmap_cache) + if (!requiresReadableBitmapData) + { + if (hardwareCompatible == null) + { + var probe = new Shape(); + __renderCommands(probe.graphics, library, false, true); + hardwareCompatible = Context3DGraphics.isCompatible(probe.graphics); + } + hardwareBitmapFills = hardwareCompatible == true; + } + #end + + __renderCommands(graphics, library, hardwareBitmapFills, false); + + commands = null; + rendered = new Shape(); + rendered.graphics.copyFrom(shape.graphics); + + return shape; + } + + private function __renderCommands(graphics:openfl.display.Graphics, library:AnimateLibrary, hardwareBitmapFills:Bool, probeBitmapFills:Bool):Void + { for (command in commands) { switch (command) @@ -49,15 +86,7 @@ class AnimateShapeSymbol extends AnimateSymbol graphics.beginFill(color, alpha); case BeginBitmapFill(bitmapID, matrix, repeat, smooth): - #if lime - var bitmapSymbol:AnimateBitmapSymbol = cast library.symbols.get(bitmapID); - var bitmap = library.getImage(bitmapSymbol.path); - - if (bitmap != null) - { - graphics.beginBitmapFill(BitmapData.fromImage(bitmap), matrix, repeat, smooth); - } - #end + __beginBitmapFill(graphics, library, bitmapID, matrix, repeat, smooth, hardwareBitmapFills, probeBitmapFills); case BeginGradientFill(fillType, colors, alphas, ratios, matrix, spreadMethod, interpolationMethod, focalPointRatio): #if flash @@ -90,11 +119,42 @@ class AnimateShapeSymbol extends AnimateSymbol graphics.moveTo(x, y); } } + } - commands = null; - rendered = new Shape(); - rendered.graphics.copyFrom(shape.graphics); + private static function __beginBitmapFill(graphics:openfl.display.Graphics, library:AnimateLibrary, bitmapID:Int, matrix:openfl.geom.Matrix, repeat:Bool, + smooth:Bool, hardwareBitmapFill:Bool, probeBitmapFill:Bool):Void + { + #if lime + var bitmapSymbol:AnimateBitmapSymbol = cast library.symbols.get(bitmapID); + #if (!flash && swf_hardware_bitmap_cache) + var bitmapData:BitmapData; + if (probeBitmapFill) + { + if (__probeBitmapData == null) + { + __probeBitmapData = new BitmapData(1, 1, true, 0xFFFFFFFF); + } + bitmapData = __probeBitmapData; + } + else if (hardwareBitmapFill) + { + bitmapData = library.__getHardwareBitmapData(bitmapSymbol); + } + else + { + // Shapes that fall back to Cairo, including scale9Grid shapes, + // still need CPU pixels. + bitmapData = library.__getReadableShapeBitmapData(bitmapSymbol); + } + #else + var bitmap = library.getImage(bitmapSymbol.path); + var bitmapData = bitmap != null ? BitmapData.fromImage(bitmap) : null; + #end - return shape; + if (bitmapData != null) + { + graphics.beginBitmapFill(bitmapData, matrix, repeat, smooth); + } + #end } } diff --git a/tests/src/TestMain.hx b/tests/src/TestMain.hx index 99caed6..835f63c 100644 --- a/tests/src/TestMain.hx +++ b/tests/src/TestMain.hx @@ -9,6 +9,7 @@ class TestMain extends Sprite super(); var runner = new Runner(); + runner.addCase(new tests.AnimateBitmapSymbolTest()); runner.addCase(new tests.ShapesTest()); Report.create(runner); diff --git a/tests/src/tests/AnimateBitmapSymbolTest.hx b/tests/src/tests/AnimateBitmapSymbolTest.hx new file mode 100644 index 0000000..8d9941a --- /dev/null +++ b/tests/src/tests/AnimateBitmapSymbolTest.hx @@ -0,0 +1,190 @@ +package tests; + +import lime.graphics.Image; +import openfl.display.BitmapData; +import openfl.geom.Rectangle; +import swf.exporters.animate.AnimateBitmapSymbol; +import swf.exporters.animate.AnimateFrame; +import swf.exporters.animate.AnimateFrameObject; +import swf.exporters.animate.AnimateLibrary; +import swf.exporters.animate.AnimateShapeCommand; +import swf.exporters.animate.AnimateShapeSymbol; +import swf.exporters.animate.AnimateSpriteSymbol; +import utest.Assert; +import utest.Test; + +@:access(lime.utils.AssetLibrary) +@:access(openfl.display.BitmapData) +@:access(openfl.display.Graphics) +@:access(openfl.display._internal.DrawCommandBuffer) +@:access(swf.exporters.animate.AnimateBitmapSymbol) +@:access(swf.exporters.animate.AnimateLibrary) +@:access(swf.exporters.animate.AnimateShapeSymbol) +class AnimateBitmapSymbolTest extends Test +{ + public function testHardwareBitmapCacheIsOptIn():Void + { + var library = new AnimateLibrary("bitmap-test", "bitmap-test"); + library.bitmapSymbols = []; + var image = new Image(null, 0, 0, 32, 32, 0xFF336699); + var symbol = new AnimateBitmapSymbol(); + symbol.id = 1; + symbol.path = "bitmap.png"; + library.cachedImages.set(symbol.path, image); + + var first = symbol.__createBitmapData(library); + var second = symbol.__createBitmapData(library); + + Assert.notNull(first); + Assert.notNull(second); + + #if (lime && !flash && swf_hardware_bitmap_cache) + Assert.isTrue(first == second); + Assert.isFalse(first.readable); + Assert.isFalse(library.cachedImages.exists(symbol.path)); + Assert.isTrue(first.image == image); + #else + Assert.isFalse(first == second); + Assert.isTrue(first.readable); + Assert.isTrue(second.readable); + Assert.isTrue(library.cachedImages.exists(symbol.path)); + #end + } + + public function testHardwareCompatibleShapeBitmapFillsUseHardwareCache():Void + { + var library = new AnimateLibrary("shape-bitmap-test", "shape-bitmap-test"); + library.bitmapSymbols = []; + library.symbols = new Map(); + var image = new Image(null, 0, 0, 32, 32, 0xFF336699); + var bitmapSymbol = new AnimateBitmapSymbol(); + bitmapSymbol.id = 1; + bitmapSymbol.path = "shape-bitmap.png"; + library.cachedImages.set(bitmapSymbol.path, image); + library.symbols.set(bitmapSymbol.id, bitmapSymbol); + + var shapeSymbol = new AnimateShapeSymbol(); + shapeSymbol.commands = [ + BeginBitmapFill(bitmapSymbol.id, null, true, true), + MoveTo(0, 0), + LineTo(32, 0), + LineTo(32, 32), + LineTo(0, 32), + EndFill + ]; + + var shape = shapeSymbol.__createObject(library); + var bitmapData:BitmapData = cast shape.graphics.__commands.o[0]; + Assert.notNull(bitmapData); + + #if (lime && !flash && swf_hardware_bitmap_cache) + Assert.isFalse(bitmapData.readable); + var secondShape = shapeSymbol.__createObject(library); + var secondShapeBitmapData:BitmapData = cast secondShape.graphics.__commands.o[0]; + Assert.isTrue(bitmapData == secondShapeBitmapData); + + var directBitmapData = bitmapSymbol.__createBitmapData(library); + Assert.isFalse(directBitmapData.readable); + Assert.isTrue(bitmapData == directBitmapData); + Assert.isFalse(library.cachedImages.exists(bitmapSymbol.path)); + #else + Assert.isTrue(bitmapData.readable); + Assert.isFalse(bitmapData == bitmapSymbol.__createBitmapData(library)); + Assert.isTrue(library.cachedImages.exists(bitmapSymbol.path)); + #end + } + + public function testSoftwareShapeBitmapFillsStayReadableAndShared():Void + { + var library = new AnimateLibrary("software-shape-bitmap-test", "software-shape-bitmap-test"); + library.bitmapSymbols = []; + library.symbols = new Map(); + var image = new Image(null, 0, 0, 32, 32, 0xFF336699); + var bitmapSymbol = new AnimateBitmapSymbol(); + bitmapSymbol.id = 1; + bitmapSymbol.path = "software-shape-bitmap.png"; + library.cachedImages.set(bitmapSymbol.path, image); + library.symbols.set(bitmapSymbol.id, bitmapSymbol); + + var shapeSymbol = new AnimateShapeSymbol(); + shapeSymbol.commands = [ + BeginBitmapFill(bitmapSymbol.id, null, true, true), + MoveTo(0, 0), + LineTo(32, 0), + LineTo(32, 32), + LineTo(0, 32), + EndFill, + BeginGradientFill(0, [0x000000, 0xFFFFFF], [1.0, 1.0], [0, 255], null, 0, 0, 0), + MoveTo(0, 0), + LineTo(1, 0), + LineTo(1, 1), + LineTo(0, 1), + EndFill + ]; + + var shape = shapeSymbol.__createObject(library); + var bitmapData:BitmapData = cast shape.graphics.__commands.o[0]; + Assert.notNull(bitmapData); + + #if (lime && !flash && swf_hardware_bitmap_cache) + Assert.isTrue(bitmapData.readable); + var secondShape = shapeSymbol.__createObject(library); + var secondShapeBitmapData:BitmapData = cast secondShape.graphics.__commands.o[0]; + Assert.isTrue(bitmapData == secondShapeBitmapData); + Assert.isTrue(bitmapData.image == image); + + var directBitmapData = bitmapSymbol.__createBitmapData(library); + Assert.isFalse(directBitmapData.readable); + Assert.isFalse(bitmapData == directBitmapData); + Assert.isFalse(library.cachedImages.exists(bitmapSymbol.path)); + #else + Assert.isTrue(bitmapData.readable); + #end + } + + public function testScale9GridShapeBitmapFillsStayReadable():Void + { + #if (lime && !flash && swf_hardware_bitmap_cache) + var library = new AnimateLibrary("scale9-shape-bitmap-test", "scale9-shape-bitmap-test"); + library.bitmapSymbols = []; + library.symbols = new Map(); + var image = new Image(null, 0, 0, 32, 32, 0xFF336699); + var bitmapSymbol = new AnimateBitmapSymbol(); + bitmapSymbol.id = 1; + bitmapSymbol.path = "scale9-shape-bitmap.png"; + library.cachedImages.set(bitmapSymbol.path, image); + library.symbols.set(bitmapSymbol.id, bitmapSymbol); + + var shapeSymbol = new AnimateShapeSymbol(); + shapeSymbol.id = 2; + shapeSymbol.commands = [ + BeginBitmapFill(bitmapSymbol.id, null, true, true), + MoveTo(0, 0), + LineTo(32, 0), + LineTo(32, 32), + LineTo(0, 32), + EndFill + ]; + library.symbols.set(shapeSymbol.id, shapeSymbol); + + var frameObject = new AnimateFrameObject(); + frameObject.symbol = shapeSymbol.id; + var frame = new AnimateFrame(); + frame.objects = [frameObject]; + var spriteSymbol = new AnimateSpriteSymbol(); + spriteSymbol.id = 3; + spriteSymbol.scale9Grid = new Rectangle(8, 8, 16, 16); + spriteSymbol.frames = [frame]; + library.symbols.set(spriteSymbol.id, spriteSymbol); + + library.__markScale9GridShapesReadable(); + var shape = shapeSymbol.__createObject(library); + var bitmapData:BitmapData = cast shape.graphics.__commands.o[0]; + + Assert.isTrue(bitmapData.readable); + Assert.isTrue(bitmapData.image == image); + #else + Assert.isTrue(true); + #end + } +} From 8d1b2b1579c6f7dcf42a015f748561af08c71c3d Mon Sep 17 00:00:00 2001 From: "Enzo Persillet (Tutez)" Date: Sun, 26 Jul 2026 19:04:24 +0200 Subject: [PATCH 2/2] Preserve readable data for shared Animate bitmaps --- src/swf/exporters/animate/AnimateLibrary.hx | 33 +++++++++++ .../exporters/animate/AnimateShapeSymbol.hx | 47 +++++++++++---- tests/src/tests/AnimateBitmapSymbolTest.hx | 58 ++++++++++++++++++- 3 files changed, 125 insertions(+), 13 deletions(-) diff --git a/src/swf/exporters/animate/AnimateLibrary.hx b/src/swf/exporters/animate/AnimateLibrary.hx index e54cc05..2ab7bdd 100644 --- a/src/swf/exporters/animate/AnimateLibrary.hx +++ b/src/swf/exporters/animate/AnimateLibrary.hx @@ -62,6 +62,7 @@ import openfl.filters.GlowFilter; private var frameRate:Float; #if (lime && !flash && swf_hardware_bitmap_cache) private var hardwareBitmapData:Map; + private var readableBitmapDataKeys:Map; private var readableShapeBitmapData:Map; #end private var id:String; @@ -86,6 +87,7 @@ import openfl.filters.GlowFilter; bitmapClassNames = new Map(); #if (lime && !flash && swf_hardware_bitmap_cache) hardwareBitmapData = new Map(); + readableBitmapDataKeys = new Map(); readableShapeBitmapData = new Map(); #end @@ -156,6 +158,11 @@ import openfl.filters.GlowFilter; private function __getHardwareBitmapData(symbol:AnimateBitmapSymbol):BitmapData { var key = __getBitmapDataKey(symbol); + if (readableBitmapDataKeys.exists(key)) + { + return __getReadableShapeBitmapData(symbol); + } + var bitmapData = hardwareBitmapData.get(key); if (bitmapData == null) @@ -183,6 +190,15 @@ import openfl.filters.GlowFilter; return bitmapData; } + @:allow(swf.exporters.animate.AnimateShapeSymbol) + private function __markBitmapDataReadable(symbol:AnimateBitmapSymbol):Void + { + if (symbol != null) + { + readableBitmapDataKeys.set(__getBitmapDataKey(symbol), true); + } + } + @:allow(swf.exporters.animate.AnimateShapeSymbol) private function __getReadableShapeBitmapData(symbol:AnimateBitmapSymbol):BitmapData { @@ -254,6 +270,21 @@ import openfl.filters.GlowFilter; } } } + + private function __prepareBitmapCaches():Void + { + for (symbol in symbols) + { + #if (haxe_ver >= 4.2) + if (Std.isOfType(symbol, AnimateShapeSymbol)) + #else + if (Std.is(symbol, AnimateShapeSymbol)) + #end + { + cast(symbol, AnimateShapeSymbol).__prepareBitmapCache(this); + } + } + } #end #if lime @@ -422,6 +453,7 @@ import openfl.filters.GlowFilter; #if (lime && !flash && swf_hardware_bitmap_cache) __markScale9GridShapesReadable(); + __prepareBitmapCaches(); #end // SWFLite.instances.set(instanceID, swf); @@ -568,6 +600,7 @@ import openfl.filters.GlowFilter; instances.remove(uuid); #if (lime && !flash && swf_hardware_bitmap_cache) hardwareBitmapData = new Map(); + readableBitmapDataKeys = new Map(); readableShapeBitmapData = new Map(); #end if (symbols == null) return; diff --git a/src/swf/exporters/animate/AnimateShapeSymbol.hx b/src/swf/exporters/animate/AnimateShapeSymbol.hx index e090046..62c7c24 100644 --- a/src/swf/exporters/animate/AnimateShapeSymbol.hx +++ b/src/swf/exporters/animate/AnimateShapeSymbol.hx @@ -55,16 +55,8 @@ class AnimateShapeSymbol extends AnimateSymbol var hardwareBitmapFills = false; #if (lime && !flash && swf_hardware_bitmap_cache) - if (!requiresReadableBitmapData) - { - if (hardwareCompatible == null) - { - var probe = new Shape(); - __renderCommands(probe.graphics, library, false, true); - hardwareCompatible = Context3DGraphics.isCompatible(probe.graphics); - } - hardwareBitmapFills = hardwareCompatible == true; - } + __prepareBitmapCache(library); + hardwareBitmapFills = !requiresReadableBitmapData && hardwareCompatible == true; #end __renderCommands(graphics, library, hardwareBitmapFills, false); @@ -76,6 +68,41 @@ class AnimateShapeSymbol extends AnimateSymbol return shape; } + #if (lime && !flash && swf_hardware_bitmap_cache) + @:allow(swf.exporters.animate.AnimateLibrary) + private function __prepareBitmapCache(library:AnimateLibrary):Void + { + if (hardwareCompatible == null) + { + var probe = new Shape(); + __renderCommands(probe.graphics, library, false, true); + hardwareCompatible = Context3DGraphics.isCompatible(probe.graphics); + } + + if (requiresReadableBitmapData || hardwareCompatible != true) + { + __markBitmapFillsReadable(library); + } + } + + private function __markBitmapFillsReadable(library:AnimateLibrary):Void + { + if (commands != null) + { + for (command in commands) + { + switch (command) + { + case BeginBitmapFill(bitmapID, _, _, _): + library.__markBitmapDataReadable(cast library.symbols.get(bitmapID)); + + default: + } + } + } + } + #end + private function __renderCommands(graphics:openfl.display.Graphics, library:AnimateLibrary, hardwareBitmapFills:Bool, probeBitmapFills:Bool):Void { for (command in commands) diff --git a/tests/src/tests/AnimateBitmapSymbolTest.hx b/tests/src/tests/AnimateBitmapSymbolTest.hx index 8d9941a..185489b 100644 --- a/tests/src/tests/AnimateBitmapSymbolTest.hx +++ b/tests/src/tests/AnimateBitmapSymbolTest.hx @@ -134,14 +134,66 @@ class AnimateBitmapSymbolTest extends Test Assert.isTrue(bitmapData.image == image); var directBitmapData = bitmapSymbol.__createBitmapData(library); - Assert.isFalse(directBitmapData.readable); - Assert.isFalse(bitmapData == directBitmapData); - Assert.isFalse(library.cachedImages.exists(bitmapSymbol.path)); + Assert.isTrue(directBitmapData.readable); + Assert.isTrue(bitmapData == directBitmapData); + Assert.isTrue(library.cachedImages.exists(bitmapSymbol.path)); #else Assert.isTrue(bitmapData.readable); #end } + public function testBitmapSharedByHardwareAndSoftwareShapesStaysReadable():Void + { + #if (lime && !flash && swf_hardware_bitmap_cache) + var library = new AnimateLibrary("shared-shape-bitmap-test", "shared-shape-bitmap-test"); + library.bitmapSymbols = []; + library.symbols = new Map(); + var image = new Image(null, 0, 0, 32, 32, 0xFF336699); + var bitmapSymbol = new AnimateBitmapSymbol(); + bitmapSymbol.id = 1; + bitmapSymbol.path = "shared-shape-bitmap.png"; + library.cachedImages.set(bitmapSymbol.path, image); + library.symbols.set(bitmapSymbol.id, bitmapSymbol); + + var hardwareShapeSymbol = new AnimateShapeSymbol(); + hardwareShapeSymbol.id = 2; + hardwareShapeSymbol.commands = [ + BeginBitmapFill(bitmapSymbol.id, null, true, true), + MoveTo(0, 0), + LineTo(32, 0), + LineTo(32, 32), + LineTo(0, 32), + EndFill + ]; + library.symbols.set(hardwareShapeSymbol.id, hardwareShapeSymbol); + + var softwareShapeSymbol = new AnimateShapeSymbol(); + softwareShapeSymbol.id = 3; + softwareShapeSymbol.commands = [ + BeginBitmapFill(bitmapSymbol.id, null, true, true), + BeginGradientFill(0, [0x000000, 0xFFFFFF], [1.0, 1.0], [0, 255], null, 0, 0, 0), + EndFill + ]; + library.symbols.set(softwareShapeSymbol.id, softwareShapeSymbol); + + // Cache preparation must make the result independent of which shape is + // instantiated first. + library.__prepareBitmapCaches(); + + var hardwareShape = hardwareShapeSymbol.__createObject(library); + var hardwareBitmapData:BitmapData = cast hardwareShape.graphics.__commands.o[0]; + var softwareShape = softwareShapeSymbol.__createObject(library); + var softwareBitmapData:BitmapData = cast softwareShape.graphics.__commands.o[0]; + + Assert.isTrue(hardwareBitmapData.readable); + Assert.isTrue(hardwareBitmapData == softwareBitmapData); + Assert.isTrue(hardwareBitmapData.image == image); + Assert.isTrue(library.cachedImages.exists(bitmapSymbol.path)); + #else + Assert.isTrue(true); + #end + } + public function testScale9GridShapeBitmapFillsStayReadable():Void { #if (lime && !flash && swf_hardware_bitmap_cache)