Optimize viewer shaders and camera controls; sample GPU profiles asynchronously
Amp-Thread-ID: https://ampcode.com/threads/T-01a0b382-1e79-712b-abd1-d29201c9bb5e Co-authored-by: Heaust Azure <heaust.azure@gmail.com>
This commit is contained in:
@@ -0,0 +1,122 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
import { Worker } from "node:worker_threads";
|
||||
import { SharedProfile } from "./profile.js";
|
||||
import { YawnCore } from "./index.js";
|
||||
|
||||
test("shared profiles replace unread samples and exclude overlapping reads/writes", () => {
|
||||
const writer = new SharedProfile(new SharedArrayBuffer(512));
|
||||
const reader = new SharedProfile(writer.buffer);
|
||||
assert.equal(reader.read(), undefined);
|
||||
writer.write(JSON.stringify({ frame: 1, name: "GPU → SAB" }));
|
||||
writer.write(JSON.stringify({ frame: 2, name: "light + opaque" }));
|
||||
assert.deepEqual(reader.read(), { frame: 2, name: "light + opaque" });
|
||||
assert.equal(reader.read(), undefined);
|
||||
Atomics.store(writer.header, 0, 1);
|
||||
assert.equal(writer.write('{"frame":3}'), false);
|
||||
assert.equal(reader.read(), undefined);
|
||||
Atomics.store(writer.header, 0, 0);
|
||||
assert.equal(writer.write(" ".repeat(513)), false);
|
||||
assert.equal(writer.write('{"frame":4}'), true);
|
||||
writer.clear();
|
||||
assert.equal(reader.read(), undefined);
|
||||
});
|
||||
|
||||
test("concurrent shared profile publication never mixes samples", async () => {
|
||||
const reader = new SharedProfile();
|
||||
const done = new Int32Array(new SharedArrayBuffer(4));
|
||||
const worker = new Worker(
|
||||
`
|
||||
const {workerData} = require('node:worker_threads');
|
||||
(async () => {
|
||||
const {SharedProfile} = await import(workerData.module);
|
||||
const writer = new SharedProfile(workerData.buffer);
|
||||
for (let frame = 1; frame <= 2000; frame++) {
|
||||
const json = JSON.stringify({frame, values: Array(128).fill(frame * 3 + 7), name: 'GPU → SAB'});
|
||||
while (!writer.write(json)) await new Promise(r => setImmediate(r));
|
||||
}
|
||||
Atomics.store(new Int32Array(workerData.done), 0, 1);
|
||||
})();
|
||||
`,
|
||||
{
|
||||
eval: true,
|
||||
workerData: {
|
||||
module: new URL("./profile.js", import.meta.url).href,
|
||||
buffer: reader.buffer,
|
||||
done: done.buffer,
|
||||
},
|
||||
},
|
||||
);
|
||||
const exit = new Promise((resolve, reject) => {
|
||||
worker.once("error", reject);
|
||||
worker.once("exit", (code) =>
|
||||
code ? reject(new Error(`Worker exit ${code}`)) : resolve(),
|
||||
);
|
||||
});
|
||||
try {
|
||||
let last = 0;
|
||||
while (!Atomics.load(done, 0) || Atomics.load(reader.header, 0) === 2) {
|
||||
const sample = reader.read();
|
||||
if (sample) {
|
||||
assert.ok(sample.frame > last);
|
||||
assert.deepEqual(sample.values, Array(128).fill(sample.frame * 3 + 7));
|
||||
assert.equal(sample.name, "GPU → SAB");
|
||||
last = sample.frame;
|
||||
}
|
||||
await new Promise((r) => setImmediate(r));
|
||||
}
|
||||
await exit;
|
||||
assert.equal(last, 2000);
|
||||
} finally {
|
||||
await worker.terminate();
|
||||
}
|
||||
});
|
||||
|
||||
test("onProfile reads SAB without result messages and stops on unsubscribe/dispose", async (t) => {
|
||||
t.mock.timers.enable({ apis: ["setInterval"] });
|
||||
const messages = [];
|
||||
let receive;
|
||||
let writer;
|
||||
const worker = {
|
||||
addEventListener(type, listener) {
|
||||
if (type === "message") receive = listener;
|
||||
},
|
||||
postMessage(message) {
|
||||
messages.push(message.type);
|
||||
writer = new SharedProfile(message.profileBuffer);
|
||||
queueMicrotask(() =>
|
||||
receive({
|
||||
data: {
|
||||
request: message.request,
|
||||
result: {
|
||||
buffer: new SharedArrayBuffer(64),
|
||||
features: [],
|
||||
rows: [],
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
},
|
||||
terminate() {},
|
||||
};
|
||||
const core = new YawnCore({}, { workerFactory: () => worker });
|
||||
await core.ready;
|
||||
const samples = [];
|
||||
const stop = core.onProfile((sample) => samples.push(sample));
|
||||
writer.write('{"frame":9,"milliseconds":1.25}');
|
||||
t.mock.timers.tick(99);
|
||||
assert.equal(samples.length, 0);
|
||||
t.mock.timers.tick(1);
|
||||
assert.deepEqual(samples, [{ frame: 9, milliseconds: 1.25 }]);
|
||||
t.mock.timers.tick(100);
|
||||
assert.equal(samples.length, 1);
|
||||
assert.deepEqual(messages, ["init"]);
|
||||
stop();
|
||||
writer.write('{"frame":10}');
|
||||
t.mock.timers.tick(100);
|
||||
assert.equal(samples.length, 1);
|
||||
core.onProfile((sample) => samples.push(sample));
|
||||
core.dispose();
|
||||
t.mock.timers.tick(100);
|
||||
assert.equal(samples.length, 1);
|
||||
});
|
||||
Reference in New Issue
Block a user