183 lines
7.7 KiB
JavaScript
183 lines
7.7 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import fs from "node:fs";
|
|
import path from "node:path";
|
|
import test from "node:test";
|
|
import ts from "typescript";
|
|
|
|
const repoRoot = path.resolve(import.meta.dirname, "../../..");
|
|
const sourcePath = path.join(repoRoot, "web/protocol/nanovdb-page-feedback.ts");
|
|
const transpiled = ts.transpileModule(fs.readFileSync(sourcePath, "utf8"), {
|
|
compilerOptions: { module: ts.ModuleKind.ES2022, target: ts.ScriptTarget.ES2022 },
|
|
fileName: sourcePath,
|
|
reportDiagnostics: true,
|
|
});
|
|
assert.deepEqual(transpiled.diagnostics, []);
|
|
const moduleUrl = "data:text/javascript;base64," + Buffer.from(transpiled.outputText).toString("base64");
|
|
const feedback = await import(moduleUrl);
|
|
|
|
test("M8-01 freezes the bounded GPU feedback word layout", () => {
|
|
const buffer = feedback.createNanoVDBPageFeedbackBuffer();
|
|
const words = new Uint32Array(buffer);
|
|
assert.equal(buffer.byteLength, (feedback.NANOVDB_PAGE_FEEDBACK_HEADER_WORDS + feedback.NANOVDB_PAGE_FEEDBACK_DEFAULT_CAPACITY) * 4);
|
|
assert.deepEqual([...words.slice(0, 4)], [1, 1024, 0, 0]);
|
|
assert.ok([...words.slice(4)].every((word) => word === 0xffffffff));
|
|
assert.deepEqual(feedback.NANOVDB_PAGE_FEEDBACK_WORD, { schemaVersion: 0, capacity: 1, count: 2, overflow: 3, pageIds: 4 });
|
|
assert.match(feedback.NANOVDB_PAGE_FEEDBACK_WGSL, /count: atomic<u32>/);
|
|
assert.match(feedback.NANOVDB_PAGE_FEEDBACK_WGSL, /overflow: atomic<u32>/);
|
|
assert.match(feedback.NANOVDB_PAGE_FEEDBACK_WGSL, /page_ids: array<atomic<u32>>/);
|
|
});
|
|
|
|
test("M8-02 records unique page IDs with a physical-capacity bound", () => {
|
|
assert.match(feedback.NANOVDB_PAGE_FEEDBACK_RECORD_WGSL, /arrayLength\(&nanovdb_page_feedback\.page_ids\)/);
|
|
assert.match(feedback.NANOVDB_PAGE_FEEDBACK_RECORD_WGSL, /min\(nanovdb_page_feedback\.capacity, physical_capacity\)/);
|
|
assert.match(feedback.NANOVDB_PAGE_FEEDBACK_RECORD_WGSL, /slot < capacity/);
|
|
assert.match(feedback.NANOVDB_PAGE_FEEDBACK_RECORD_WGSL, /atomicCompareExchangeWeak/);
|
|
assert.match(feedback.NANOVDB_PAGE_FEEDBACK_RECORD_WGSL, /claim\.old_value == page_id/);
|
|
assert.match(feedback.NANOVDB_PAGE_FEEDBACK_RECORD_WGSL, /atomicStore\(&nanovdb_page_feedback\.overflow, 1u\)/);
|
|
});
|
|
|
|
test("M8-03 sorts, deduplicates and binds CPU feedback to a render revision", () => {
|
|
const buffer = feedback.createNanoVDBPageFeedbackBuffer(4);
|
|
const words = new Uint32Array(buffer);
|
|
words[feedback.NANOVDB_PAGE_FEEDBACK_WORD.count] = 4;
|
|
words.set([7, 2, 7, 5], feedback.NANOVDB_PAGE_FEEDBACK_WORD.pageIds);
|
|
assert.deepEqual(feedback.parseNanoVDBPageFeedbackBatch(buffer, 8, 42), {
|
|
schemaVersion: 1,
|
|
renderRevision: 42,
|
|
attemptedCount: 4,
|
|
gpuStoredCount: 4,
|
|
uniqueCount: 3,
|
|
pageIds: [2, 5, 7],
|
|
status: "READY",
|
|
errorCode: null,
|
|
});
|
|
assert.throws(
|
|
() => feedback.parseNanoVDBPageFeedbackBatch(buffer, 8, -1),
|
|
(error) => error.code === "INVALID_ARGUMENT",
|
|
);
|
|
assert.throws(
|
|
() => feedback.parseNanoVDBPageFeedbackBatch(buffer, 8, 1.5),
|
|
(error) => error.code === "INVALID_ARGUMENT",
|
|
);
|
|
});
|
|
|
|
test("M8-04 rejects stale frame feedback before page I/O", async () => {
|
|
const buffer = feedback.createNanoVDBPageFeedbackBuffer(4);
|
|
const words = new Uint32Array(buffer);
|
|
words[feedback.NANOVDB_PAGE_FEEDBACK_WORD.count] = 3;
|
|
words.set([7, 2, 5], feedback.NANOVDB_PAGE_FEEDBACK_WORD.pageIds);
|
|
const batch = feedback.parseNanoVDBPageFeedbackBatch(buffer, 8, 42);
|
|
const requests = [];
|
|
const requestPage = async (pageId, renderRevision) => {
|
|
requests.push({ pageId, renderRevision });
|
|
};
|
|
|
|
assert.deepEqual(await feedback.dispatchNanoVDBPageFeedbackBatch(batch, 43, requestPage), {
|
|
schemaVersion: 1,
|
|
renderRevision: 42,
|
|
currentRenderRevision: 43,
|
|
status: "STALE",
|
|
requestedPageIds: [],
|
|
requestedCount: 0,
|
|
errorCode: "REVISION_CONFLICT",
|
|
});
|
|
assert.deepEqual(await feedback.dispatchNanoVDBPageFeedbackBatch(batch, 41, requestPage), {
|
|
schemaVersion: 1,
|
|
renderRevision: 42,
|
|
currentRenderRevision: 41,
|
|
status: "STALE",
|
|
requestedPageIds: [],
|
|
requestedCount: 0,
|
|
errorCode: "REVISION_CONFLICT",
|
|
});
|
|
assert.deepEqual(requests, []);
|
|
|
|
assert.deepEqual(await feedback.dispatchNanoVDBPageFeedbackBatch(batch, 42, requestPage), {
|
|
schemaVersion: 1,
|
|
renderRevision: 42,
|
|
currentRenderRevision: 42,
|
|
status: "ACCEPTED",
|
|
requestedPageIds: [2, 5, 7],
|
|
requestedCount: 3,
|
|
errorCode: null,
|
|
});
|
|
assert.deepEqual(requests, [
|
|
{ pageId: 2, renderRevision: 42 },
|
|
{ pageId: 5, renderRevision: 42 },
|
|
{ pageId: 7, renderRevision: 42 },
|
|
]);
|
|
await assert.rejects(
|
|
feedback.dispatchNanoVDBPageFeedbackBatch(batch, -1, requestPage),
|
|
(error) => error.code === "INVALID_ARGUMENT",
|
|
);
|
|
});
|
|
|
|
test("M8-01 parses ready and overflow feedback with a stable overflow code", () => {
|
|
const readyBuffer = feedback.createNanoVDBPageFeedbackBuffer(4);
|
|
const readyWords = new Uint32Array(readyBuffer);
|
|
readyWords[feedback.NANOVDB_PAGE_FEEDBACK_WORD.count] = 3;
|
|
readyWords.set([7, 2, 5], feedback.NANOVDB_PAGE_FEEDBACK_WORD.pageIds);
|
|
assert.deepEqual(feedback.parseNanoVDBPageFeedbackBuffer(readyBuffer, 8), {
|
|
schemaVersion: 1,
|
|
capacity: 4,
|
|
attemptedCount: 3,
|
|
storedCount: 3,
|
|
pageIds: [7, 2, 5],
|
|
status: "READY",
|
|
errorCode: null,
|
|
});
|
|
|
|
const overflowBuffer = feedback.createNanoVDBPageFeedbackBuffer(2);
|
|
const overflowWords = new Uint32Array(overflowBuffer);
|
|
overflowWords[feedback.NANOVDB_PAGE_FEEDBACK_WORD.count] = 4;
|
|
overflowWords[feedback.NANOVDB_PAGE_FEEDBACK_WORD.overflow] = 1;
|
|
overflowWords.set([3, 4], feedback.NANOVDB_PAGE_FEEDBACK_WORD.pageIds);
|
|
assert.deepEqual(feedback.parseNanoVDBPageFeedbackBuffer(overflowBuffer, 8), {
|
|
schemaVersion: 1,
|
|
capacity: 2,
|
|
attemptedCount: 4,
|
|
storedCount: 2,
|
|
pageIds: [3, 4],
|
|
status: "OVERFLOW",
|
|
errorCode: "NANOVDB_PAGE_FEEDBACK_OVERFLOW",
|
|
});
|
|
});
|
|
|
|
test("M8-01 rejects layout drift, invalid pages and inconsistent overflow", () => {
|
|
assert.throws(() => feedback.createNanoVDBPageFeedbackBuffer(0), (error) => error.code === "INVALID_ARGUMENT");
|
|
assert.throws(() => feedback.createNanoVDBPageFeedbackBuffer(8193), (error) => error.code === "INVALID_ARGUMENT");
|
|
|
|
const wrongSchema = feedback.createNanoVDBPageFeedbackBuffer(2);
|
|
new Uint32Array(wrongSchema)[0] = 2;
|
|
assert.throws(() => feedback.parseNanoVDBPageFeedbackBuffer(wrongSchema, 8), (error) => error.code === "PROTOCOL_MISMATCH");
|
|
|
|
const wrongCapacity = feedback.createNanoVDBPageFeedbackBuffer(2);
|
|
new Uint32Array(wrongCapacity)[1] = 8193;
|
|
assert.throws(() => feedback.parseNanoVDBPageFeedbackBuffer(wrongCapacity, 8), (error) => error.code === "PROTOCOL_MISMATCH");
|
|
|
|
const wrongSize = feedback.createNanoVDBPageFeedbackBuffer(2).slice(0, 20);
|
|
assert.throws(() => feedback.parseNanoVDBPageFeedbackBuffer(wrongSize, 8), (error) => error.code === "PROTOCOL_MISMATCH");
|
|
|
|
const inconsistent = feedback.createNanoVDBPageFeedbackBuffer(2);
|
|
const inconsistentWords = new Uint32Array(inconsistent);
|
|
inconsistentWords[2] = 3;
|
|
inconsistentWords.set([1, 2], 4);
|
|
assert.throws(() => feedback.parseNanoVDBPageFeedbackBuffer(inconsistent, 8), (error) => error.code === "PROTOCOL_MISMATCH");
|
|
|
|
const invalidPage = feedback.createNanoVDBPageFeedbackBuffer(2);
|
|
const invalidPageWords = new Uint32Array(invalidPage);
|
|
invalidPageWords[2] = 1;
|
|
invalidPageWords[4] = 8;
|
|
assert.throws(() => feedback.parseNanoVDBPageFeedbackBuffer(invalidPage, 8), (error) => error.code === "PROTOCOL_MISMATCH");
|
|
});
|
|
|
|
test("M8-01 reset clears atomic words and stale page IDs", () => {
|
|
const buffer = feedback.createNanoVDBPageFeedbackBuffer(3);
|
|
const words = new Uint32Array(buffer);
|
|
words[2] = 5;
|
|
words[3] = 1;
|
|
words.set([1, 2, 3], 4);
|
|
feedback.resetNanoVDBPageFeedbackBuffer(buffer);
|
|
assert.deepEqual([...words], [1, 3, 0, 0, 0xffffffff, 0xffffffff, 0xffffffff]);
|
|
});
|