Shader library: fingerprint without node positions, a drag no longer recompiles (#65)
game-maker / suite (push) Successful in 9m5s
game-maker / suite (push) Successful in 9m5s
shaderFingerprint(graph) covers node id/type/params/ports and edge endpoints only; x/y, collapsed, folded and edge ids stay out. ShaderLibrary keys on it and counts compiles (__gm.shaderCompiles()). Measured before: one compile per preview step of a node drag (12 for a harness drag); after: 0, a param change exactly +1. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
016e83c9b5
commit
102a649a35
6 files changed
+82
-6
No files matched your search
@@ -47,6 +47,18 @@ export interface ShaderBuild {
|
||||
* becomes one local per output, in dependency order, and every Property a float uniform. The texel
|
||||
* arrives premultiplied, so the prelude unpremultiplies and the epilogue multiplies back.
|
||||
*/
|
||||
/**
|
||||
* What the fragment depends on and nothing else: node ids, types, params and declared ports, and
|
||||
* which port feeds which. Positions, collapsing and folding are the editor's; they stay out so a
|
||||
* moved node does not compile again (#65).
|
||||
*/
|
||||
export function shaderFingerprint(graph: Graph): string {
|
||||
return JSON.stringify({
|
||||
nodes: graph.nodes.map((n) => [n.id, n.type, n.params, n.ports ?? null]),
|
||||
edges: graph.edges.map((e) => [e.from.node, e.from.port, e.to.node, e.to.port]),
|
||||
});
|
||||
}
|
||||
|
||||
export function compileShader(graph: Graph): ShaderBuild {
|
||||
const problems: Record<string, string> = {};
|
||||
const problem = (node: GraphNode, text: string) => {
|
||||
|
||||
@@ -415,6 +415,8 @@ export function installDebugHook(app: Application, state: EditorState, editor: E
|
||||
},
|
||||
/** Holds the shader clock at `at` seconds, so a Time shader's pixels can be measured; null releases it. */
|
||||
shaderTime: (at: number | null) => shaderLibrary.freezeTime(at),
|
||||
/** Graphs compiled so far; a moved node must not add one (#65). */
|
||||
shaderCompiles: () => shaderLibrary.compiles,
|
||||
/** Which views redraw every tick because a Time shader is on screen. */
|
||||
animates: () => ({ scene: viewport.animates, game: gameView.animates }),
|
||||
lowLatency: () =>
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
import type { ShaderAsset } from '../core/model/Asset';
|
||||
import { findAssetOf, type Project } from '../core/model/Project';
|
||||
import { compileShader, type ShaderBuild } from '../core/shader/compile';
|
||||
import { compileShader, shaderFingerprint, type ShaderBuild } from '../core/shader/compile';
|
||||
import type { ShaderSource } from './shaders';
|
||||
|
||||
/**
|
||||
* Compiled shader graphs, one per asset, recompiled when the graph's content changes. A graph is
|
||||
* edited in place, so the cache keys on its serialized form rather than on its identity.
|
||||
* edited in place, so the cache keys on a fingerprint of what the fragment depends on.
|
||||
*/
|
||||
export class ShaderLibrary {
|
||||
private readonly builds = new Map<string, { fingerprint: string; build: ShaderBuild }>();
|
||||
/** How often a graph went through the compiler – what the checks read to prove a move did not. */
|
||||
compiles = 0;
|
||||
private readonly started = performance.now();
|
||||
/** Set from the test hook: the clock stands at this second until released. */
|
||||
private frozenAt: number | null = null;
|
||||
@@ -23,9 +25,10 @@ export class ShaderLibrary {
|
||||
}
|
||||
|
||||
buildFor(asset: ShaderAsset): ShaderBuild {
|
||||
const fingerprint = JSON.stringify(asset.graph);
|
||||
const fingerprint = shaderFingerprint(asset.graph);
|
||||
const cached = this.builds.get(asset.id);
|
||||
if (cached && cached.fingerprint === fingerprint) return cached.build;
|
||||
this.compiles += 1;
|
||||
const build = compileShader(asset.graph);
|
||||
this.builds.set(asset.id, { fingerprint, build });
|
||||
return build;
|
||||
|
||||
Reference in new issue
Block a user