Files
mommysgoodpuppy 927136050d Speed up the decoupled renderer and skip unchanged scenes
Render thread (panorama, nice 0, load ~27 on 16 cores):
- Per-run UI panel slots reused across eyes, one per-frame panel data texture, and cached
  struct bytes for shader uniforms and draw matrices: renderRaythreeFrame 4.16 -> 2.27 ms.
- The worker skips publishing scenes whose fingerprint (overlaySceneFingerprint) matches the
  last one sent, with a 1 s keepalive. Anchored items now keep bit-identical local matrices
  while nothing moves: built from the local transform chain, or held through rounding noise
  where uikit derives matrixWorld from layout. Decode on the render thread 1.78 -> 0.04
  ms/frame when idle; render thread busy 6.4 -> 4.2 ms/frame.

Diagnostics: a frame timeline probe with schedstat in overlayRenderOwner, CPU profiling with an
interval, dropLateFrames, renderThreadNice, split presenter finish/wait/publish, a native
WaitFrameSync phase probe and a compositor frame-timing probe. asyncFrameWait,
projectionPacing and deferDecode are projection experiments that did not help.

Also includes the other agent's pending overlay scene buffer, wire and ovrdriver lab work.

Agent: Claude Code 2.1.296; model claude-opus-5-5; thread 941be8c7-aca1-4664-b284-7cc0eed69e21
2026-10-16 20:12:06 +03:00

60 lines
2.1 KiB
TypeScript

/** Cache CPU text data across independently decoded scene packets. */
type Geometry = {
positions: Float32Array;
uvs: Float32Array;
indices: Uint16Array | Uint32Array;
version: number;
};
type Text = { text: string; color: readonly number[]; geometry?: Geometry };
function sameBytes(a: ArrayBufferView, b: ArrayBufferView): boolean {
if (a === b) return true;
if (a.byteLength !== b.byteLength || a.constructor !== b.constructor) return false;
const aa = new Uint8Array(a.buffer, a.byteOffset, a.byteLength);
const bb = new Uint8Array(b.buffer, b.byteOffset, b.byteLength);
for (let i = 0; i < aa.length; i++) if (aa[i] !== bb[i]) return false;
return true;
}
/** Retains at most the latest scene's geometries; transport identity is irrelevant. */
export class UiTextGeometryInterner {
private previous: (Geometry | undefined)[] = [];
intern(texts: readonly Text[]): void {
const next: (Geometry | undefined)[] = [];
for (let i = 0; i < texts.length; i++) {
const source = texts[i].geometry, old = this.previous[i];
if (
source && old && source.version === old.version &&
sameBytes(source.positions, old.positions) && sameBytes(source.uvs, old.uvs) &&
sameBytes(source.indices, old.indices)
) {
texts[i].geometry = old;
}
next.push(texts[i].geometry);
}
this.previous = next;
}
}
type Content = { text: string; color: number[]; geometry?: Geometry; version?: number };
export function captureTextBatchContent(texts: readonly Text[]): Content[] {
return texts.map((t) => ({
text: t.text,
color: [...t.color],
geometry: t.geometry,
version: t.geometry?.version,
}));
}
/** World transforms are checked separately because fresh tracking mutates them in place. */
export function textBatchContentMatches(
texts: readonly Text[],
saved: readonly Content[],
): boolean {
return texts.length === saved.length && texts.every((t, i) => {
const old = saved[i];
return t.text === old.text && t.geometry === old.geometry &&
t.geometry?.version === old.version &&
t.color.length === old.color.length && t.color.every((v, j) => v === old.color[j]);
});
}