Free a redrawn picture only once no shader names it (#293)
Editing a vector drawing made Pixi warn "destroyed while still bound
to a shader", twice with every change, once it had begun. It began
where an object wears the drawing through a shader asset. Each
change draws the picture anew and destroyed the one before it, but
the mesh of that object keeps its picture bound until it syncs. The
issue also asks for the button "Add shape" to go: a shape without
points cannot be given any, the Pen draws a new one.
- GeneratedPictures takes two more callbacks. inUse says that a
picture cannot be freed yet, soon runs a retry once whoever asked
is done.
- A replaced picture that is still in use waits in a list of its
own. It is freed right after the redraw that asked, as a rule. On
a later ask where its wearer was not part of that redraw.
- TextureLibrary tells a bound picture by the listeners for change
on its source. Under WebGL the bind groups are the only ones that
listen there. The retry is a microtask.
- The rule is asked for every drawn picture alike, whoever wears it.
- The inspector of a drawing and of a shape has no "Add shape" any
more. "Delete shapes" for several selected shapes stays.
Decided here:
- Under the list of shapes a second note says "The Pen draws a new
shape." while only the asset is selected.
Found on the way:
- Reproduced on a throwaway copy of the project the report came
from: 6 warnings for 3 changes with the shader override on the
object, none without it. After the change none, and no replaced
picture is left waiting.
- The fixture project of the tests never showed the warning. No
object there wore a drawing through a shader.
Tests:
- Gate on melektaus-atx: 211 files, 3219 tests (3209 + 10), exit 0
- e2e 47,48 on melektaus-atx: 113 checks, all pass
- e2e 28,30,31,39,40,47,48: 350 checks, all pass, run before the
wording of the new note changed
- The first gate was red: an existing test read the wording of the
note that had changed. Only three test files had been run after
the change.
- No full run and no smokes. The sum of 1668 (1667 + 1) is reckoned,
1667 is what CI counted for ec2fd90.
- Ten unit tests, five of them against the real bind group of Pixi
in tests/viewport/bound-pictures.test.ts
- One e2e check in block 47b-ii of module 47: a drawing worn through
a shader, three changes, no warning. The check on "Add shape" now
reads that there is no such button.
- Nine unit counter-probes and two browser counter-probes all fail
their tests. Without the change the new check counts 6 warnings.
- Not checked: a tilemap or a particle emitter holding a picture
bound the same way. Nothing was looked at after the build, the
new note included.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
ec2fd9084f
commit
9e869df063
11 files changed
+364
-46
No files matched your search
+59
-11
@@ -176,6 +176,46 @@ export async function vectorArt(t) {
|
||||
const repainted = await at(48, 200);
|
||||
check('the sprite shows the changed fill', colourDistance(repainted, REDRAWN) < 14, repainted);
|
||||
|
||||
// 47b-ii. The object wears the drawing through a shader asset (#293). Its shader names the picture
|
||||
// until the mesh syncs, which is after the new revision was drawn: destroyed there, Pixi
|
||||
// warned for the source and for its sampler with every single change.
|
||||
const boundLogFrom = t.consoleLog.length;
|
||||
await mutate(`
|
||||
Object.assign(__gm.state.activeScene.objects.find((o) => o.id === 'va-object').overrides,
|
||||
{ shader: 'adjust-colors', saturation: 0 })`);
|
||||
await settle();
|
||||
await sleep(300);
|
||||
const waiting = [];
|
||||
for (const fill of [0x3050a0, 0x70b030, REDRAWN.replace('#', '0x')]) {
|
||||
await mutate(`
|
||||
const art = __gm.state.project.assets.find((a) => a.id === 'va-art');
|
||||
art.layers[0].items[0].fill = ${fill}`);
|
||||
await settle();
|
||||
await sleep(200);
|
||||
waiting.push((await json('__gm.picturesWaiting()')).length);
|
||||
}
|
||||
const boundWarnings = t.consoleLog.slice(boundLogFrom).filter((line) => line.includes('BindGroup'));
|
||||
const afterShaded = await json("__gm.drawnPicture('va-art')");
|
||||
// Saturation 0 turns the yellow field grey: the shader is what draws the sprite here.
|
||||
const grey = await at(48, 200);
|
||||
const [red, green, blue] = [1, 3, 5].map((i) => parseInt(grey.slice(i, i + 2), 16));
|
||||
const shaded = Math.max(red, green, blue) - Math.min(red, green, blue) < 12;
|
||||
check(
|
||||
'a drawing worn through a shader is redrawn without Pixi warning of a destroyed texture',
|
||||
shaded &&
|
||||
boundWarnings.length === 0 &&
|
||||
// The replaced picture is freed once the shader took the new one: none is left lying.
|
||||
waiting.every((count) => count === 0) &&
|
||||
afterShaded.kept,
|
||||
JSON.stringify({ grey, warnings: boundWarnings.length, waiting, file: afterShaded?.file }),
|
||||
);
|
||||
await mutate(`
|
||||
const shadedObject = __gm.state.activeScene.objects.find((o) => o.id === 'va-object');
|
||||
delete shadedObject.overrides.shader;
|
||||
delete shadedObject.overrides.saturation`);
|
||||
await settle();
|
||||
await sleep(300);
|
||||
|
||||
// 47c. The tab: the pen draws a triangle and closes it.
|
||||
await evaluate("__gm.openDocument('asset:va-art')");
|
||||
await settle();
|
||||
@@ -424,18 +464,28 @@ export async function vectorArt(t) {
|
||||
JSON.stringify(await shapeIds()),
|
||||
);
|
||||
|
||||
// A spare shape for the two checks below: the duplicate mark makes one and selects it. The
|
||||
// inspector has no button that adds an empty shape any more (#293) – the Pen draws a new one.
|
||||
const before47e = (await shapeIds()).length;
|
||||
await clickRowButton('Add shape');
|
||||
await settle();
|
||||
const added = await shapeIds();
|
||||
const spareCopy = async () => {
|
||||
const known = await shapeIds();
|
||||
const mark = await json("__gm.vectorArt.shapeDuplicateAt('va-badge')");
|
||||
if (mark) await click(mark.x, mark.y);
|
||||
await settle();
|
||||
return (await shapeIds()).find((id) => !known.includes(id)) ?? null;
|
||||
};
|
||||
const added = await spareCopy();
|
||||
check(
|
||||
'Add shape puts an empty shape on top and selects it',
|
||||
added.length === before47e + 1 && (await json('__gm.vectorArt.selected()')) === added.at(-1),
|
||||
JSON.stringify({ added: added.length, selected: await json('__gm.vectorArt.selected()') }),
|
||||
'the inspector offers no Add shape button',
|
||||
added !== null &&
|
||||
(await json('__gm.vectorArt.selected()')) === added &&
|
||||
(await json("__gm.inspectorButton('Add shape')")) === null &&
|
||||
(await json("__gm.inspectorButton('To front')")) !== null,
|
||||
JSON.stringify({ added, selected: await json('__gm.vectorArt.selected()') }),
|
||||
);
|
||||
|
||||
// The cross on its row takes that shape out again, and the selection with it.
|
||||
const cross = await json(`__gm.vectorArt.shapeRemoveAt(${JSON.stringify(added.at(-1))})`);
|
||||
const cross = await json(`__gm.vectorArt.shapeRemoveAt(${JSON.stringify(added)})`);
|
||||
if (cross) await click(cross.x, cross.y);
|
||||
await settle();
|
||||
const afterCross = await shapeIds();
|
||||
@@ -443,16 +493,14 @@ export async function vectorArt(t) {
|
||||
'the cross on a shape row removes that shape and nothing else',
|
||||
cross !== null &&
|
||||
afterCross.length === before47e &&
|
||||
!afterCross.includes(added.at(-1)) &&
|
||||
!afterCross.includes(added) &&
|
||||
(await json('__gm.vectorArt.selected()')) === '',
|
||||
JSON.stringify({ cross, afterCross, selected: await json('__gm.vectorArt.selected()') }),
|
||||
);
|
||||
|
||||
// Delete takes a shape away however it was picked. A row of the list leaves nothing focused, so the
|
||||
// key reaches the editor's shortcuts rather than the canvas.
|
||||
await clickRowButton('Add shape');
|
||||
await settle();
|
||||
const spare = (await shapeIds()).at(-1);
|
||||
const spare = await spareCopy();
|
||||
const spareRow = await json(`__gm.vectorArt.shapeRowAt(${JSON.stringify(spare)})`);
|
||||
await click(spareRow.x + spareRow.w / 2, spareRow.y + spareRow.h / 2);
|
||||
await settle();
|
||||
|
||||
Reference in new issue
Block a user