import type { ErrorCode } from "./error"; export const NANOVDB_RENDER_GOLDEN_SCHEMA_VERSION = 1 as const; export const NANOVDB_GOLDEN_MISMATCH_CODE = "NANOVDB_GOLDEN_MISMATCH" as const satisfies ErrorCode; export interface NanoVDBRenderGoldenThresholdsIR { maxChannelError: number; meanAbsoluteError: number; rmsError: number; alphaCoverageDeltaRatio: number; } export interface NanoVDBRenderGoldenComparisonIR { schemaVersion: typeof NANOVDB_RENDER_GOLDEN_SCHEMA_VERSION; status: "READY" | "BLOCKED"; pixelCount: number; comparedChannels: number; maxChannelError: number; meanAbsoluteError: number; rmsError: number; referenceAlphaPixels: number; actualAlphaPixels: number; alphaCoverageDeltaRatio: number; thresholds: NanoVDBRenderGoldenThresholdsIR; errorCode: typeof NANOVDB_GOLDEN_MISMATCH_CODE | null; } function validateThresholds(value: NanoVDBRenderGoldenThresholdsIR): NanoVDBRenderGoldenThresholdsIR { if ( !Number.isInteger(value.maxChannelError) || value.maxChannelError < 0 || value.maxChannelError > 255 || !Number.isFinite(value.meanAbsoluteError) || value.meanAbsoluteError < 0 || value.meanAbsoluteError > 255 || !Number.isFinite(value.rmsError) || value.rmsError < 0 || value.rmsError > 255 || !Number.isFinite(value.alphaCoverageDeltaRatio) || value.alphaCoverageDeltaRatio < 0 || value.alphaCoverageDeltaRatio > 1 ) { throw new Error("NANOVDB_INVALID_ARGUMENT: render golden thresholds are invalid"); } return { ...value }; } export function compareNanoVDBRenderGolden( reference: Uint8Array, actual: Uint8Array, thresholdsValue: NanoVDBRenderGoldenThresholdsIR, ): NanoVDBRenderGoldenComparisonIR { if ( !(reference instanceof Uint8Array) || !(actual instanceof Uint8Array) || reference.byteLength === 0 || reference.byteLength !== actual.byteLength || reference.byteLength % 4 !== 0 ) { throw new Error("NANOVDB_INVALID_ARGUMENT: render golden images must be equal non-empty RGBA8 buffers"); } const thresholds = validateThresholds(thresholdsValue); let maximum = 0; let absoluteTotal = 0; let squaredTotal = 0; let referenceAlphaPixels = 0; let actualAlphaPixels = 0; for (let index = 0; index < reference.byteLength; index++) { const difference = Math.abs(reference[index] - actual[index]); maximum = Math.max(maximum, difference); absoluteTotal += difference; squaredTotal += difference * difference; if ((index & 3) === 3) { if (reference[index] > 0) referenceAlphaPixels++; if (actual[index] > 0) actualAlphaPixels++; } } const pixelCount = reference.byteLength / 4; const meanAbsoluteError = absoluteTotal / reference.byteLength; const rmsError = Math.sqrt(squaredTotal / reference.byteLength); const alphaCoverageDeltaRatio = Math.abs(referenceAlphaPixels - actualAlphaPixels) / pixelCount; const matches = maximum <= thresholds.maxChannelError && meanAbsoluteError <= thresholds.meanAbsoluteError && rmsError <= thresholds.rmsError && alphaCoverageDeltaRatio <= thresholds.alphaCoverageDeltaRatio; return { schemaVersion: NANOVDB_RENDER_GOLDEN_SCHEMA_VERSION, status: matches ? "READY" : "BLOCKED", pixelCount, comparedChannels: reference.byteLength, maxChannelError: maximum, meanAbsoluteError, rmsError, referenceAlphaPixels, actualAlphaPixels, alphaCoverageDeltaRatio, thresholds, errorCode: matches ? null : NANOVDB_GOLDEN_MISMATCH_CODE, }; }