Shaders draw in the sprite's own pass: a Mesh with the graph's fragment, no filter (#77)
game-maker / suite (push) Successful in 8m24s

A sprite's picture is now a Mesh (unit quad, sibling of its Graphics node, which keeps
only the hit area); a worn shader replaces Pixi's fragment in that draw, so there is no
intermediate texture and no extra render pass. Measured on the HD 630: 400 shaded
sprites hold 60 fps (was 34), 800 hold 56 (was 20).

Two Pixi facts the code leans on: a mesh's texture is bound only for Pixi's own shader,
so GraphShader.wear hands it over; and MeshPipe caches whether a mesh batches and
re-checks it only in a frame without structure changes, so switching a shader on or
off calls mesh.unload().

The console panel mirrors console.group/groupCollapsed/groupEnd and fills the %s %d
%o %c directives, so Pixi's deprecation warning shows its head, not only its stack.
E2E 12 guards against the addChild-on-Graphics warning, 28 counts shaders instead of
filters.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
rootandClaude Fable 5.1 committed 2026-09-13 15:31:08 +02:00
1 parent 82effb2c03
commit 0e5e5ef925
15 files changed
+328 -88

No files matched your search

+15 -2
View File
@@ -1,4 +1,4 @@
import { type Application, Color, type ColorSource, type Container, Rectangle, Text } from 'pixi.js';
import { type Application, Color, type ColorSource, type Container, Mesh, Rectangle, Text } from 'pixi.js';
import { resolveObjects } from '../core/model/instance';
import { findObjectHolder } from '../core/model/Project';
import type { FrameRect } from '../core/model/texture';
@@ -171,6 +171,8 @@ export function installDebugHook(app: Application, state: EditorState, editor: E
};
(window as unknown as { __gm: unknown }).__gm = {
state,
/** The Pixi application, for probes that need the renderer or the stage. */
app,
sceneObjects: () => (state.activeScene ? objectsOf(state.activeScene.id) : []),
locate,
locateAll,
@@ -386,7 +388,7 @@ export function installDebugHook(app: Application, state: EditorState, editor: E
};
},
fps: () => editor.fps,
/** Visible nodes carrying a filter – one extra render pass each, which is what shader cost is made of. */
/** Visible nodes carrying a filter – one extra render pass each; since #77 a shader is none. */
filterCount: () => {
let n = 0;
const walk = (node: Container): void => {
@@ -397,6 +399,17 @@ export function installDebugHook(app: Application, state: EditorState, editor: E
walk(app.stage);
return n;
},
/** Visible sprite meshes drawing with a shader of their own – one draw call each, no pass (#77). */
shaderCount: () => {
let n = 0;
const walk = (node: Container): void => {
if (!node.visible) return;
if (node instanceof Mesh && node.shader) n += 1;
for (const child of node.children) walk(child);
};
walk(app.stage);
return n;
},
lowLatency: () =>
(app.renderer as unknown as { gl?: WebGL2RenderingContext }).gl?.getContextAttributes()
?.desynchronized === true,