Advance M8-M11 parity workflows
This commit is contained in:
234
web/protocol/nanovdb-page-feedback.ts
Normal file
234
web/protocol/nanovdb-page-feedback.ts
Normal file
@@ -0,0 +1,234 @@
|
||||
import type { ErrorCode } from "./error";
|
||||
|
||||
export const NANOVDB_PAGE_FEEDBACK_SCHEMA_VERSION = 1 as const;
|
||||
export const NANOVDB_PAGE_FEEDBACK_HEADER_WORDS = 4;
|
||||
export const NANOVDB_PAGE_FEEDBACK_DEFAULT_CAPACITY = 1024;
|
||||
export const NANOVDB_PAGE_FEEDBACK_MAX_CAPACITY = 8192;
|
||||
export const NANOVDB_PAGE_FEEDBACK_EMPTY_PAGE_ID = 0xffffffff;
|
||||
export const NANOVDB_PAGE_FEEDBACK_OVERFLOW_CODE = "NANOVDB_PAGE_FEEDBACK_OVERFLOW" as const satisfies ErrorCode;
|
||||
|
||||
export const NANOVDB_PAGE_FEEDBACK_WORD = {
|
||||
schemaVersion: 0,
|
||||
capacity: 1,
|
||||
count: 2,
|
||||
overflow: 3,
|
||||
pageIds: NANOVDB_PAGE_FEEDBACK_HEADER_WORDS,
|
||||
} as const;
|
||||
|
||||
export const NANOVDB_PAGE_FEEDBACK_WGSL = /* wgsl */`
|
||||
struct NanoVDBPageFeedback {
|
||||
schema_version: u32,
|
||||
capacity: u32,
|
||||
count: atomic<u32>,
|
||||
overflow: atomic<u32>,
|
||||
page_ids: array<atomic<u32>>,
|
||||
}
|
||||
`;
|
||||
|
||||
export const NANOVDB_PAGE_FEEDBACK_RECORD_WGSL = /* wgsl */`
|
||||
fn nanovdb_record_page_fault(page_id: u32) {
|
||||
if (page_id == 0xffffffffu || nanovdb_page_feedback.schema_version != 1u) { return; }
|
||||
let physical_capacity = arrayLength(&nanovdb_page_feedback.page_ids);
|
||||
let capacity = min(nanovdb_page_feedback.capacity, physical_capacity);
|
||||
if (capacity == 0u) { return; }
|
||||
for (var slot = 0u; slot < capacity; slot += 1u) {
|
||||
loop {
|
||||
let current = atomicLoad(&nanovdb_page_feedback.page_ids[slot]);
|
||||
if (current == page_id) { return; }
|
||||
if (current != 0xffffffffu) { break; }
|
||||
let claim = atomicCompareExchangeWeak(&nanovdb_page_feedback.page_ids[slot], 0xffffffffu, page_id);
|
||||
if (claim.exchanged) {
|
||||
atomicAdd(&nanovdb_page_feedback.count, 1u);
|
||||
return;
|
||||
}
|
||||
if (claim.old_value == page_id) { return; }
|
||||
if (claim.old_value != 0xffffffffu) { break; }
|
||||
}
|
||||
}
|
||||
atomicStore(&nanovdb_page_feedback.overflow, 1u);
|
||||
atomicAdd(&nanovdb_page_feedback.count, 1u);
|
||||
}
|
||||
`;
|
||||
|
||||
export type NanoVDBPageFeedbackStatus = "READY" | "OVERFLOW";
|
||||
|
||||
export interface NanoVDBPageFeedbackResult {
|
||||
schemaVersion: typeof NANOVDB_PAGE_FEEDBACK_SCHEMA_VERSION;
|
||||
capacity: number;
|
||||
attemptedCount: number;
|
||||
storedCount: number;
|
||||
pageIds: number[];
|
||||
status: NanoVDBPageFeedbackStatus;
|
||||
errorCode: typeof NANOVDB_PAGE_FEEDBACK_OVERFLOW_CODE | null;
|
||||
}
|
||||
|
||||
export interface NanoVDBPageFeedbackBatch {
|
||||
schemaVersion: typeof NANOVDB_PAGE_FEEDBACK_SCHEMA_VERSION;
|
||||
renderRevision: number;
|
||||
attemptedCount: number;
|
||||
gpuStoredCount: number;
|
||||
uniqueCount: number;
|
||||
pageIds: number[];
|
||||
status: NanoVDBPageFeedbackStatus;
|
||||
errorCode: typeof NANOVDB_PAGE_FEEDBACK_OVERFLOW_CODE | null;
|
||||
}
|
||||
|
||||
export interface NanoVDBPageFeedbackDispatchResult {
|
||||
schemaVersion: typeof NANOVDB_PAGE_FEEDBACK_SCHEMA_VERSION;
|
||||
renderRevision: number;
|
||||
currentRenderRevision: number;
|
||||
status: "ACCEPTED" | "STALE";
|
||||
requestedPageIds: number[];
|
||||
requestedCount: number;
|
||||
errorCode: "REVISION_CONFLICT" | null;
|
||||
}
|
||||
|
||||
export type NanoVDBPageRequester = (pageId: number, renderRevision: number) => Promise<void> | void;
|
||||
|
||||
export class NanoVDBPageFeedbackError extends Error {
|
||||
constructor(public readonly code: "INVALID_ARGUMENT" | "PROTOCOL_MISMATCH", message: string) {
|
||||
super(`${code}: ${message}`);
|
||||
this.name = "NanoVDBPageFeedbackError";
|
||||
}
|
||||
}
|
||||
|
||||
function feedbackError(code: NanoVDBPageFeedbackError["code"], message: string): never {
|
||||
throw new NanoVDBPageFeedbackError(code, message);
|
||||
}
|
||||
|
||||
function validateCapacity(capacity: number, code: NanoVDBPageFeedbackError["code"] = "INVALID_ARGUMENT"): number {
|
||||
if (!Number.isSafeInteger(capacity) || capacity < 1 || capacity > NANOVDB_PAGE_FEEDBACK_MAX_CAPACITY) {
|
||||
feedbackError(code, "NanoVDB page feedback capacity is outside the bounded range");
|
||||
}
|
||||
return capacity;
|
||||
}
|
||||
|
||||
export function nanoVDBPageFeedbackByteLength(capacity = NANOVDB_PAGE_FEEDBACK_DEFAULT_CAPACITY): number {
|
||||
return (NANOVDB_PAGE_FEEDBACK_HEADER_WORDS + validateCapacity(capacity)) * Uint32Array.BYTES_PER_ELEMENT;
|
||||
}
|
||||
|
||||
export function createNanoVDBPageFeedbackBuffer(capacity = NANOVDB_PAGE_FEEDBACK_DEFAULT_CAPACITY): ArrayBuffer {
|
||||
const words = new Uint32Array(nanoVDBPageFeedbackByteLength(capacity) / Uint32Array.BYTES_PER_ELEMENT);
|
||||
words[NANOVDB_PAGE_FEEDBACK_WORD.schemaVersion] = NANOVDB_PAGE_FEEDBACK_SCHEMA_VERSION;
|
||||
words[NANOVDB_PAGE_FEEDBACK_WORD.capacity] = capacity;
|
||||
words.fill(NANOVDB_PAGE_FEEDBACK_EMPTY_PAGE_ID, NANOVDB_PAGE_FEEDBACK_WORD.pageIds);
|
||||
return words.buffer;
|
||||
}
|
||||
|
||||
export function resetNanoVDBPageFeedbackBuffer(buffer: ArrayBuffer): void {
|
||||
const words = feedbackWords(buffer);
|
||||
const capacity = validateHeader(words, buffer.byteLength);
|
||||
words[NANOVDB_PAGE_FEEDBACK_WORD.count] = 0;
|
||||
words[NANOVDB_PAGE_FEEDBACK_WORD.overflow] = 0;
|
||||
words.fill(NANOVDB_PAGE_FEEDBACK_EMPTY_PAGE_ID, NANOVDB_PAGE_FEEDBACK_WORD.pageIds, NANOVDB_PAGE_FEEDBACK_WORD.pageIds + capacity);
|
||||
}
|
||||
|
||||
export function parseNanoVDBPageFeedbackBuffer(buffer: ArrayBuffer, pageCount: number): NanoVDBPageFeedbackResult {
|
||||
if (!Number.isSafeInteger(pageCount) || pageCount < 1 || pageCount > NANOVDB_PAGE_FEEDBACK_MAX_CAPACITY) {
|
||||
feedbackError("INVALID_ARGUMENT", "NanoVDB virtual page count is outside the bounded range");
|
||||
}
|
||||
const words = feedbackWords(buffer);
|
||||
const capacity = validateHeader(words, buffer.byteLength);
|
||||
const attemptedCount = words[NANOVDB_PAGE_FEEDBACK_WORD.count];
|
||||
const overflow = words[NANOVDB_PAGE_FEEDBACK_WORD.overflow];
|
||||
if (overflow !== 0 && overflow !== 1) feedbackError("PROTOCOL_MISMATCH", "NanoVDB page feedback overflow flag is invalid");
|
||||
if ((attemptedCount > capacity) !== (overflow === 1)) {
|
||||
feedbackError("PROTOCOL_MISMATCH", "NanoVDB page feedback count and overflow flag disagree");
|
||||
}
|
||||
const storedCount = Math.min(attemptedCount, capacity);
|
||||
const pageIds = [...words.slice(NANOVDB_PAGE_FEEDBACK_WORD.pageIds, NANOVDB_PAGE_FEEDBACK_WORD.pageIds + storedCount)];
|
||||
if (pageIds.some((pageId) => pageId === NANOVDB_PAGE_FEEDBACK_EMPTY_PAGE_ID || pageId >= pageCount)) {
|
||||
feedbackError("PROTOCOL_MISMATCH", "NanoVDB page feedback contains an invalid virtual page ID");
|
||||
}
|
||||
const overflowed = overflow === 1;
|
||||
return {
|
||||
schemaVersion: NANOVDB_PAGE_FEEDBACK_SCHEMA_VERSION,
|
||||
capacity,
|
||||
attemptedCount,
|
||||
storedCount,
|
||||
pageIds,
|
||||
status: overflowed ? "OVERFLOW" : "READY",
|
||||
errorCode: overflowed ? NANOVDB_PAGE_FEEDBACK_OVERFLOW_CODE : null,
|
||||
};
|
||||
}
|
||||
|
||||
function bindNanoVDBPageFeedbackToRender(
|
||||
feedback: NanoVDBPageFeedbackResult,
|
||||
renderRevision: number,
|
||||
): NanoVDBPageFeedbackBatch {
|
||||
if (!Number.isSafeInteger(renderRevision) || renderRevision < 0) {
|
||||
feedbackError("INVALID_ARGUMENT", "NanoVDB feedback render revision is invalid");
|
||||
}
|
||||
const pageIds = [...new Set(feedback.pageIds)].sort((left, right) => left - right);
|
||||
return {
|
||||
schemaVersion: NANOVDB_PAGE_FEEDBACK_SCHEMA_VERSION,
|
||||
renderRevision,
|
||||
attemptedCount: feedback.attemptedCount,
|
||||
gpuStoredCount: feedback.storedCount,
|
||||
uniqueCount: pageIds.length,
|
||||
pageIds,
|
||||
status: feedback.status,
|
||||
errorCode: feedback.errorCode,
|
||||
};
|
||||
}
|
||||
|
||||
export function parseNanoVDBPageFeedbackBatch(
|
||||
buffer: ArrayBuffer,
|
||||
pageCount: number,
|
||||
renderRevision: number,
|
||||
): NanoVDBPageFeedbackBatch {
|
||||
return bindNanoVDBPageFeedbackToRender(parseNanoVDBPageFeedbackBuffer(buffer, pageCount), renderRevision);
|
||||
}
|
||||
|
||||
export async function dispatchNanoVDBPageFeedbackBatch(
|
||||
batch: NanoVDBPageFeedbackBatch,
|
||||
currentRenderRevision: number,
|
||||
requestPage: NanoVDBPageRequester,
|
||||
): Promise<NanoVDBPageFeedbackDispatchResult> {
|
||||
if (!Number.isSafeInteger(currentRenderRevision) || currentRenderRevision < 0) {
|
||||
feedbackError("INVALID_ARGUMENT", "NanoVDB current render revision is invalid");
|
||||
}
|
||||
if (batch.renderRevision !== currentRenderRevision) {
|
||||
return {
|
||||
schemaVersion: NANOVDB_PAGE_FEEDBACK_SCHEMA_VERSION,
|
||||
renderRevision: batch.renderRevision,
|
||||
currentRenderRevision,
|
||||
status: "STALE",
|
||||
requestedPageIds: [],
|
||||
requestedCount: 0,
|
||||
errorCode: "REVISION_CONFLICT",
|
||||
};
|
||||
}
|
||||
const requestedPageIds: number[] = [];
|
||||
for (const pageId of batch.pageIds) {
|
||||
await requestPage(pageId, batch.renderRevision);
|
||||
requestedPageIds.push(pageId);
|
||||
}
|
||||
return {
|
||||
schemaVersion: NANOVDB_PAGE_FEEDBACK_SCHEMA_VERSION,
|
||||
renderRevision: batch.renderRevision,
|
||||
currentRenderRevision,
|
||||
status: "ACCEPTED",
|
||||
requestedPageIds,
|
||||
requestedCount: requestedPageIds.length,
|
||||
errorCode: null,
|
||||
};
|
||||
}
|
||||
|
||||
function feedbackWords(buffer: ArrayBuffer): Uint32Array {
|
||||
if (!(buffer instanceof ArrayBuffer) || buffer.byteLength < NANOVDB_PAGE_FEEDBACK_HEADER_WORDS * Uint32Array.BYTES_PER_ELEMENT || buffer.byteLength % Uint32Array.BYTES_PER_ELEMENT !== 0) {
|
||||
feedbackError("PROTOCOL_MISMATCH", "NanoVDB page feedback buffer byte length is invalid");
|
||||
}
|
||||
return new Uint32Array(buffer);
|
||||
}
|
||||
|
||||
function validateHeader(words: Uint32Array, byteLength: number): number {
|
||||
if (words[NANOVDB_PAGE_FEEDBACK_WORD.schemaVersion] !== NANOVDB_PAGE_FEEDBACK_SCHEMA_VERSION) {
|
||||
feedbackError("PROTOCOL_MISMATCH", "NanoVDB page feedback schema version is unsupported");
|
||||
}
|
||||
const capacity = validateCapacity(words[NANOVDB_PAGE_FEEDBACK_WORD.capacity], "PROTOCOL_MISMATCH");
|
||||
if (byteLength !== nanoVDBPageFeedbackByteLength(capacity)) {
|
||||
feedbackError("PROTOCOL_MISMATCH", "NanoVDB page feedback buffer does not match its declared capacity");
|
||||
}
|
||||
return capacity;
|
||||
}
|
||||
Reference in New Issue
Block a user