From 8058188b8f0c81a5ecf5096cb422d9248e5d47a1 Mon Sep 17 00:00:00 2001 From: Erik Onarheim Date: Sun, 5 May 2024 10:22:44 -0500 Subject: [PATCH] fix: [#3047] Animation glitch cause by uninitialized state (#3048) Closes #3047 After the performance update to `ImageRenderer` in v0.29.2 there was uninitialized shared state that leaks between draw calls, which `Raster`s are especially susceptible to based on the call pattern. --- CHANGELOG.md | 1 + sandbox/tests/polygon-rendering/index.html | 12 +++ sandbox/tests/polygon-rendering/index.ts | 72 ++++++++++++++++++ sandbox/tests/polygon-rendering/test.png | Bin 0 -> 511 bytes .../Context/image-renderer/image-renderer.ts | 2 + src/spec/ExcaliburGraphicsContextSpec.ts | 55 +++++++++++++ .../ExcaliburGraphicsContextSpec/rasters.png | Bin 0 -> 10043 bytes .../ExcaliburGraphicsContextSpec/test.png | Bin 0 -> 511 bytes 8 files changed, 142 insertions(+) create mode 100644 sandbox/tests/polygon-rendering/index.html create mode 100644 sandbox/tests/polygon-rendering/index.ts create mode 100644 sandbox/tests/polygon-rendering/test.png create mode 100644 src/spec/images/ExcaliburGraphicsContextSpec/rasters.png create mode 100644 src/spec/images/ExcaliburGraphicsContextSpec/test.png diff --git a/CHANGELOG.md b/CHANGELOG.md index a7480c548..4eff4a35e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Fixed +- Fixed animation glitch caused by uninitialized state in `ImageRenderer` - Fixed issue where `ex.Loader.suppressPlayButton = true` did not work. Only using the `ex.Engine({suppressPlayButton: true})` worked ### Updates diff --git a/sandbox/tests/polygon-rendering/index.html b/sandbox/tests/polygon-rendering/index.html new file mode 100644 index 000000000..df44e5868 --- /dev/null +++ b/sandbox/tests/polygon-rendering/index.html @@ -0,0 +1,12 @@ + + + + + + Polygon Animation Rendering + + + + + + diff --git a/sandbox/tests/polygon-rendering/index.ts b/sandbox/tests/polygon-rendering/index.ts new file mode 100644 index 000000000..1555de855 --- /dev/null +++ b/sandbox/tests/polygon-rendering/index.ts @@ -0,0 +1,72 @@ +var engine = new ex.Engine({ + width: 500, + height: 400 +}); + +var image = new ex.ImageSource('test.png'); +var spriteSheet = ex.SpriteSheet.fromImageSource({ + image: image, + grid: { + rows: 1, + columns: 4, + spriteWidth: 100, + spriteHeight: 100 + } +}); + +// Set up the moving object +// var mover = new ex.Actor({ x: 100, y: 0 }); +// mover.anchor = ex.vec(0, 0); +// mover.actions.repeatForever((ctx) => ctx.moveTo(ex.vec(100, 300), 50).moveTo(ex.vec(100, 0), 50)); +// mover.graphics.add('up', spriteSheet.sprites[0]); + +var down = ex.Animation.fromSpriteSheet(spriteSheet, [0, 1, 2, 3], 500); + +// mover.graphics.add('down', down); +// mover.onPreUpdate = () => { +// if (mover.vel.y < 0) { +// mover.graphics.use('up'); +// } +// if (mover.vel.y > 0) { +// mover.graphics.use('down'); +// } +// }; + +// set up the hexagon object +// var static1 = new ex.Actor({ x: 350, y: 100 }); +var polygon1 = new ex.Polygon({ + points: [ex.vec(50, 0), ex.vec(150, 0), ex.vec(200, 86), ex.vec(150, 172), ex.vec(50, 172), ex.vec(0, 86)], + color: ex.Color.fromRGB(0, 255, 0) +}); +// static1.graphics.use(polygon1); + +// set up the rectangle object +// var static2 = new ex.Actor({ x: 300, y: 300 }); +var rect = new ex.Rectangle({ width: 100, height: 100, color: ex.Color.fromRGB(255, 0, 0) }); +// static2.graphics.use(rect); + +// set up the circle object +// var static3 = new ex.Actor({ x: 400, y: 300 }); +var circle = new ex.Circle({ radius: 50, color: ex.Color.fromRGB(0, 0, 255) }); +// static3.graphics.use(circle); + +var loader = new ex.Loader([image]); + +engine.currentScene.onPostDraw = (ctx) => { + down.draw(ctx, 100, 100); + polygon1.draw(ctx, 350, 100); + rect.draw(ctx, 300, 300); + circle.draw(ctx, 400, 300); + + down.tick(500, 1); + down.draw(ctx, 100, 100); + polygon1.draw(ctx, 350, 100); + rect.draw(ctx, 300, 300); + circle.draw(ctx, 400, 300); +}; + +// engine.add(mover); +// engine.add(static1); +// engine.add(static2); +// engine.add(static3); +engine.start(loader); diff --git a/sandbox/tests/polygon-rendering/test.png b/sandbox/tests/polygon-rendering/test.png new file mode 100644 index 0000000000000000000000000000000000000000..9316a4135e65c5302ab9e5a4da44343084431582 GIT binary patch literal 511 zcmeAS@N?(olHy`uVBq!ia0y~yV4MJCr*NFSZ?7KYJYc}X z;;8-i`m$Eki@SkN|@aBcp=gC`HvE bsASx(!g^HmuZjdP@)$f_{an^LB{Ts5h!RS~ literal 0 HcmV?d00001 diff --git a/src/engine/Graphics/Context/image-renderer/image-renderer.ts b/src/engine/Graphics/Context/image-renderer/image-renderer.ts index 31e42e526..595837185 100644 --- a/src/engine/Graphics/Context/image-renderer/image-renderer.ts +++ b/src/engine/Graphics/Context/image-renderer/image-renderer.ts @@ -222,6 +222,8 @@ export class ImageRenderer implements RendererPlugin { let width = maybeImageWidth || swidth || 0; let height = maybeImageHeight || sheight || 0; + this._view[0] = 0; + this._view[1] = 0; this._view[2] = swidth ?? maybeImageWidth ?? 0; this._view[3] = sheight ?? maybeImageHeight ?? 0; this._dest[0] = sx ?? 1; diff --git a/src/spec/ExcaliburGraphicsContextSpec.ts b/src/spec/ExcaliburGraphicsContextSpec.ts index 831b5c69a..e25b1e8c8 100644 --- a/src/spec/ExcaliburGraphicsContextSpec.ts +++ b/src/spec/ExcaliburGraphicsContextSpec.ts @@ -1053,5 +1053,60 @@ describe('The ExcaliburGraphicsContext', () => { }).not.toThrow(); sut.dispose(); }); + + it('can handle rendering rasters in succession', async () => { + // arrange + const image = new ex.ImageSource('src/spec/images/ExcaliburGraphicsContextSpec/test.png'); + await image.load(); + const spriteSheet = ex.SpriteSheet.fromImageSource({ + image: image, + grid: { + rows: 1, + columns: 4, + spriteWidth: 100, + spriteHeight: 100 + } + }); + + const down = ex.Animation.fromSpriteSheet(spriteSheet, [0, 1, 2, 3], 500); + const polygon = new ex.Polygon({ + points: [ex.vec(50, 0), ex.vec(150, 0), ex.vec(200, 86), ex.vec(150, 172), ex.vec(50, 172), ex.vec(0, 86)], + color: ex.Color.fromRGB(0, 255, 0) + }); + const rect = new ex.Rectangle({ width: 100, height: 100, color: ex.Color.fromRGB(255, 0, 0) }); + const circle = new ex.Circle({ radius: 50, color: ex.Color.fromRGB(0, 0, 255) }); + + const canvasElement = testCanvasElement; + canvasElement.width = 500; + canvasElement.height = 400; + const sut = new ex.ExcaliburGraphicsContextWebGL({ + canvasElement: canvasElement, + context: testContext, + enableTransparency: false, + snapToPixel: true, + backgroundColor: ex.Color.White + }); + + // act + + sut.clear(); + + down.draw(sut, 100, 100); + polygon.draw(sut, 350, 100); + rect.draw(sut, 300, 300); + circle.draw(sut, 400, 300); + + down.tick(500, 1); + down.draw(sut, 100, 100); + polygon.draw(sut, 350, 100); + rect.draw(sut, 300, 300); + circle.draw(sut, 400, 300); + + sut.flush(); + + // assert + await expectAsync(canvasElement).toEqualImage('src/spec/images/ExcaliburGraphicsContextSpec/rasters.png'); + sut.dispose(); + }); }); }); diff --git a/src/spec/images/ExcaliburGraphicsContextSpec/rasters.png b/src/spec/images/ExcaliburGraphicsContextSpec/rasters.png new file mode 100644 index 0000000000000000000000000000000000000000..c5fa369301e0cd90e27a31bb2456e96a9954bf5d GIT binary patch literal 10043 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 OnHX9c6zE<2?f(HBk<3m2 literal 0 HcmV?d00001 diff --git a/src/spec/images/ExcaliburGraphicsContextSpec/test.png b/src/spec/images/ExcaliburGraphicsContextSpec/test.png new file mode 100644 index 0000000000000000000000000000000000000000..9316a4135e65c5302ab9e5a4da44343084431582 GIT binary patch literal 511 zcmeAS@N?(olHy`uVBq!ia0y~yV4MJCr*NFSZ?7KYJYc}X z;;8-i`m$Eki@SkN|@aBcp=gC`HvE bsASx(!g^HmuZjdP@)$f_{an^LB{Ts5h!RS~ literal 0 HcmV?d00001