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:
1 parent
13852fe4b2
commit
0dcb3b4432
5 files changed
+30
-2
No files matched your search
@@ -549,6 +549,10 @@ selected and switched back on.
|
||||
## Stack
|
||||
|
||||
- **PixiJS v8** – rendering and pointer events. No game engine; runtime systems live in `src/runtime/`.
|
||||
`main.ts` creates the WebGL context itself with `desynchronized`, which Pixi has no option for:
|
||||
frames skip the compositor queue, so the UI keeps up with the pointer (click to picture measured
|
||||
72–77 ms before, 16–18 ms after). The price is possible tearing while things move fast;
|
||||
`?vsync` falls back to Pixi's own context. A browser without the mode simply ignores it.
|
||||
- **Own canvas widget toolkit** (`src/ui/`) – panels, buttons, lists, text/number/color fields,
|
||||
popups. The only lasting DOM element is a hidden `<input>` used as a keyboard bridge (IME,
|
||||
mobile keyboard, clipboard) while a canvas text field is focused; *Upload…* adds a short-lived
|
||||
@@ -640,7 +644,7 @@ npm run check # typecheck + lint + format:check + tests, what CI would ru
|
||||
# code is lost (tail's is reported), so a red run reads as green
|
||||
npm run build # writes dist/ and regenerates index.html
|
||||
export GM_REMOTE=user@host # optional: run the browser elsewhere; see below
|
||||
node tools/seed-e2e.mjs && node tools/e2e.mjs # 534 editor checks, screenshots in /tmp/gm-e2e-*.png
|
||||
node tools/seed-e2e.mjs && node tools/e2e.mjs # 535 editor checks, screenshots in /tmp/gm-e2e-*.png
|
||||
GM_STEPS=20 node tools/e2e.mjs # or 1-12, or 6,20 – a subset while working on one step
|
||||
./e2e.sh start [GM_STEPS] # the same run detached, see below
|
||||
./e2e.sh status # live progress, or the last run's result
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
man kann ja nun graph nodes zusammenklappen.
|
||||
hier soll auch die breite veringert werden auf die titeltext breite wenn möglich?
|
||||
@@ -253,6 +253,10 @@ export async function graph(t) {
|
||||
// Graph while playing shows the live highlight: hold ArrowRight → the exec edge is hot.
|
||||
const fpsEdit = await evaluate('__gm.fps()');
|
||||
check('fps readout is measured in edit mode', typeof fpsEdit === 'number' && fpsEdit > 0, `fps=${fpsEdit}`);
|
||||
check(
|
||||
'the canvas bypasses the compositor queue by default',
|
||||
(await evaluate('__gm.lowLatency()')) === true,
|
||||
);
|
||||
// Single evaluate: the readout refreshes twice a second, so read value and label together.
|
||||
check(
|
||||
'fps label is visible in the toolbar',
|
||||
|
||||
Reference in new issue
Block a user