Keep a pixel frame worn through a shader alive while it is drawn
Changing a palette colour of pixel art flooded the console with Pixi warnings: "[BindGroup] a 'textureSource' was destroyed while still bound to a shader", and the same for its sampler. #293 had stopped this for a drawing, but a frame of pixel art is worn as a cut of the sheet. The cut has a source of its own, so the sheet looked unbound, was freed at once and took the cut with it under the shader. - The check whether a replaced picture is still in use gets its key too. TextureLibrary then asks the picture and every cut taken from it. - A sheet with a bound cut waits like a bound picture did since #293, and both go together once the shader has taken the new frame. Found on the way: - The project the report came from has a sprite wearing a frame of an eleven-frame pixel art through a shader asset. - A nine-sliced sprite wearing the frame showed no warning, even without the change. The first try of the e2e check used it. Tests: - Gate on melektaus-atx: 236 files, 3577 tests (+1) - e2e 48: 45 checks (+1, sum 1741). 48l-iii puts a shader on the object wearing the second frame, changes a palette colour three times and counts the warnings. - Counter-checks bite: without the cuts in the check the unit test fails, and 48l-iii counts 6 warnings for 3 changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
c74b6b1cab
commit
3f57cf731d
5 files changed
+84
-6
No files matched your search
@@ -36,7 +36,9 @@ export class TextureLibrary extends FileLibrary<Texture> {
|
||||
this.dropCropsOf(key);
|
||||
texture.destroy(true);
|
||||
},
|
||||
boundToShader,
|
||||
// A frame of pixel art is worn as a cut, which has a source of its own: the sheet itself is
|
||||
// then bound nowhere, and freeing it destroyed the cut under the shader.
|
||||
(texture, key) => boundToShader(texture) || this.cropsOf(key).some(boundToShader),
|
||||
// After the redraw that asked: every mesh it synced wears the new picture by then.
|
||||
(retry) => queueMicrotask(retry),
|
||||
);
|
||||
@@ -152,7 +154,12 @@ export class TextureLibrary extends FileLibrary<Texture> {
|
||||
return Assets.load<Texture>(url);
|
||||
}
|
||||
|
||||
/** Cuts taken from a picture that has moved on to a newer revision; the key is their prefix. */
|
||||
/** The cuts taken from one picture; the file is the prefix of their key. */
|
||||
private cropsOf(file: string): Texture[] {
|
||||
return [...this.crops].filter(([key]) => key.startsWith(`${file}|`)).map(([, texture]) => texture);
|
||||
}
|
||||
|
||||
/** Cuts taken from a picture that has moved on to a newer revision. */
|
||||
private dropCropsOf(file: string): void {
|
||||
for (const [key, texture] of this.crops)
|
||||
if (key.startsWith(`${file}|`)) {
|
||||
|
||||
@@ -34,7 +34,7 @@ export class GeneratedPictures<T> {
|
||||
*/
|
||||
constructor(
|
||||
private readonly free: (picture: T, key: string) => void,
|
||||
private readonly inUse: (picture: T) => boolean = () => false,
|
||||
private readonly inUse: (picture: T, key: string) => boolean = () => false,
|
||||
private readonly soon: (retry: () => void) => void = () => {},
|
||||
) {}
|
||||
|
||||
@@ -80,7 +80,7 @@ export class GeneratedPictures<T> {
|
||||
|
||||
private freeWaiting(): void {
|
||||
this.waiting = this.waiting.filter((held) => {
|
||||
if (this.inUse(held.picture)) return true;
|
||||
if (this.inUse(held.picture, held.key)) return true;
|
||||
this.free(held.picture, held.key);
|
||||
return false;
|
||||
});
|
||||
@@ -95,7 +95,7 @@ export class GeneratedPictures<T> {
|
||||
const held = this.kept.get(key);
|
||||
if (!held) return;
|
||||
this.kept.delete(key);
|
||||
if (!this.inUse(held.picture)) return this.free(held.picture, key);
|
||||
if (!this.inUse(held.picture, key)) return this.free(held.picture, key);
|
||||
this.waiting.push({ key, picture: held.picture });
|
||||
if (this.asked) return;
|
||||
this.asked = true;
|
||||
|
||||
Reference in new issue
Block a user