Render through a desynchronized WebGL context by default

Measured click-to-paint was 72-77 ms, of which the editor itself spends
under 4 ms; the rest is the finished frame waiting in the compositor
queue. A desynchronized context bypasses it (16-18 ms in the same probe).
Pixi has no option for it, so main.ts builds the WebGL2 context itself.
The mode may tear on some drivers; ?vsync falls back to Pixi's own
context.

__gm.lowLatency() reports the attributes the browser actually granted,
and a graph-step check pins that the mode stays on.

Also adds the todo note graph-node-collapse-width.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
rootandClaude Opus 5 committed 2026-09-11 11:54:19 +02:00
1 parent 13852fe4b2
commit 0dcb3b4432
5 files changed
+30 -2

No files matched your search

+3
View File
@@ -281,6 +281,9 @@ export function installDebugHook(app: Application, state: EditorState, editor: E
};
},
fps: () => editor.fps,
lowLatency: () =>
(app.renderer as unknown as { gl?: WebGL2RenderingContext }).gl?.getContextAttributes()
?.desynchronized === true,
tab: () => editor.tab,
showTab: (tab: CentreTab) => editor.showTab(tab),
};
+16 -1
View File
@@ -6,7 +6,22 @@ import { startEditor } from './editor/App';
import { mountPlayground } from './playground';
import { initUI, Theme, VStack } from './ui';
// `desynchronized` bypasses the compositor queue (headless click-to-paint 72 → 16 ms) but may
// tear; `?vsync` opts out. Pixi has no option for it, hence our own context.
function lowLatencyContext(): { canvas?: HTMLCanvasElement; context?: WebGL2RenderingContext } {
const canvas = document.createElement('canvas');
const context = canvas.getContext('webgl2', {
alpha: false,
antialias: true,
premultipliedAlpha: true,
stencil: true,
desynchronized: true,
});
return context ? { canvas, context } : {};
}
async function main(): Promise<void> {
const params = new URLSearchParams(location.search);
const app = new Application();
await app.init({
resizeTo: window,
@@ -15,6 +30,7 @@ async function main(): Promise<void> {
resolution: Math.min(2, window.devicePixelRatio || 1),
autoDensity: true,
preference: 'webgl',
...(params.has('vsync') ? {} : lowLatencyContext()),
});
document.body.appendChild(app.canvas);
@@ -25,7 +41,6 @@ async function main(): Promise<void> {
app.stage.hitArea = app.screen;
initUI(app, root, overlay);
const params = new URLSearchParams(location.search);
if (params.has('playground')) mountPlayground(app, root);
else await startEditor(app, root);