mirror of
https://github.com/goodpuppies/petplay.git
synced 2026-10-23 11:00:51 +02:00
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
60 lines
2.1 KiB
TypeScript
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]);
|
|
});
|
|
}
|