109 lines
7.4 KiB
JavaScript
109 lines
7.4 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import crypto from "node:crypto";
|
|
import fs from "node:fs";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
import { spawnSync } from "node:child_process";
|
|
import { createRequire } from "node:module";
|
|
import { fileURLToPath } from "node:url";
|
|
import ts from "../../web/node_modules/typescript/lib/typescript.js";
|
|
|
|
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
|
|
const resourceRoot = path.resolve(process.env.VDB_RESOURCE_ROOT ?? "/home/mes123456/resource-library/blender-web-vdb");
|
|
const converter = path.join(root, "build_vdb_tools", "vdb_to_nanovdb");
|
|
const generator = path.join(root, "build_vdb_tools", "vdb_fixture_generator");
|
|
const source = path.join(resourceRoot, "generated", "generated-smoke.vdb");
|
|
const storedBundle = path.join(resourceRoot, "nanovdb", "generated-smoke.nvdb");
|
|
const officialSource = path.join(resourceRoot, "official", "sphere.vdb");
|
|
const officialBundle = path.join(resourceRoot, "nanovdb", "official-sphere.nvdb");
|
|
const browserManifest = path.join(resourceRoot, "manifests", "generated-smoke.nanovdb.json");
|
|
const catalog = JSON.parse(fs.readFileSync(path.join(resourceRoot, "manifest.json"), "utf8"));
|
|
const evidence = JSON.parse(fs.readFileSync(path.join(root, "docs", "status", "vdb-native-evidence.json"), "utf8"));
|
|
const sha256 = (file) => crypto.createHash("sha256").update(fs.readFileSync(file)).digest("hex");
|
|
|
|
assert.match(fs.readFileSync(path.join(root, "build_web_blender6", "CMakeCache.txt"), "utf8"), /^WITH_OPENVDB:BOOL=OFF$/m);
|
|
assert.match(fs.readFileSync(path.join(root, "build_blender_5.2.0", "CMakeCache.txt"), "utf8"), /^WITH_OPENVDB:BOOL=ON$/m);
|
|
assert.equal(catalog.schemaVersion, 1);
|
|
assert.equal(catalog.toolchain.openVDBVersion, "13.0.0");
|
|
assert.equal(catalog.toolchain.converterSha256, sha256(converter));
|
|
assert.equal(evidence.schemaVersion, 1);
|
|
assert.equal(evidence.browserOpenVDB, false);
|
|
assert.equal(evidence.desktopOpenVDB, true);
|
|
assert.equal(evidence.serverJobConfigured, true);
|
|
assert.equal(evidence.opfsStreamingConfigured, true);
|
|
assert.equal(evidence.webgpuRendererConfigured, true);
|
|
assert.equal(evidence.mainVolumeRoundtripConfigured, true);
|
|
assert.equal(evidence.primaryViewportVolumeIntegrated, false);
|
|
assert.equal(evidence.releaseStatus, "BLOCKED");
|
|
assert.deepEqual(evidence.toolchain, catalog.toolchain);
|
|
assert.deepEqual(evidence.resources, catalog.entries);
|
|
for (const entry of catalog.entries) {
|
|
const file = path.join(resourceRoot, entry.path);
|
|
assert.equal(fs.statSync(file).size, entry.byteLength, `${entry.id} byte length changed`);
|
|
assert.equal(sha256(file), entry.sha256, `${entry.id} SHA-256 changed`);
|
|
}
|
|
assert.equal(catalog.entries.find((entry) => entry.id === "official-sphere")?.sha256, "bb884a9a38354e47191c4ece4a11d1e051594a910fde56501cfc51ff52b171ab");
|
|
|
|
const ldd = spawnSync("ldd", [converter], { encoding: "utf8" });
|
|
assert.equal(ldd.status, 0);
|
|
assert.match(ldd.stdout, /libopenvdb\.so\.13/);
|
|
const temporary = fs.mkdtempSync(path.join(os.tmpdir(), "vdb-native-"));
|
|
try {
|
|
const output = path.join(temporary, "smoke.nvdb");
|
|
const report = path.join(temporary, "smoke.json");
|
|
const conversion = spawnSync(converter, ["--input", source, "--output", output, "--report", report, "--grid", "density", "--grid", "temperature", "--grid", "color", "--grid", "velocity", "--quantization", "LOSSLESS"], { encoding: "utf8" });
|
|
assert.equal(conversion.status, 0, conversion.stderr);
|
|
assert.equal(sha256(output), sha256(storedBundle), "OpenVDB to NanoVDB conversion is not deterministic");
|
|
const parsedReport = JSON.parse(fs.readFileSync(report, "utf8"));
|
|
assert.equal(parsedReport.openVDBVersion, "13.0.0");
|
|
assert.equal(parsedReport.nanoVDBVersion, "32.9.0");
|
|
assert.deepEqual(parsedReport.grids.map((grid) => grid.name), ["color", "density", "temperature", "velocity"]);
|
|
assert.ok(parsedReport.grids.every((grid) => grid.byteOffset >= grid.segmentByteOffset && grid.byteOffset + grid.byteLength <= grid.segmentByteOffset + grid.segmentByteLength));
|
|
|
|
const generatedA = path.join(temporary, "generated-a");
|
|
const generatedB = path.join(temporary, "generated-b");
|
|
fs.mkdirSync(generatedA);
|
|
fs.mkdirSync(generatedB);
|
|
assert.equal(spawnSync(generator, [generatedA], { encoding: "utf8" }).status, 0);
|
|
assert.equal(spawnSync(generator, [generatedB], { encoding: "utf8" }).status, 0);
|
|
const semanticOutputs = [];
|
|
for (const [index, generated] of [generatedA, generatedB].entries()) {
|
|
const semanticOutput = path.join(temporary, `semantic-${index}.nvdb`);
|
|
const semanticReport = path.join(temporary, `semantic-${index}.json`);
|
|
const result = spawnSync(converter, ["--input", path.join(generated, "generated-smoke.vdb"), "--output", semanticOutput, "--report", semanticReport, "--grid", "density", "--grid", "temperature", "--grid", "color", "--grid", "velocity", "--quantization", "LOSSLESS"], { encoding: "utf8" });
|
|
assert.equal(result.status, 0, result.stderr);
|
|
semanticOutputs.push(semanticOutput);
|
|
}
|
|
assert.equal(sha256(semanticOutputs[0]), sha256(semanticOutputs[1]), "Semantically identical generated VDB grids produce different NanoVDB bundles");
|
|
assert.equal(sha256(semanticOutputs[0]), sha256(storedBundle));
|
|
|
|
const officialOutput = path.join(temporary, "official-sphere.nvdb");
|
|
const officialReport = path.join(temporary, "official-sphere.json");
|
|
const officialConversion = spawnSync(converter, ["--input", officialSource, "--output", officialOutput, "--report", officialReport, "--quantization", "LOSSLESS"], { encoding: "utf8" });
|
|
assert.equal(officialConversion.status, 0, officialConversion.stderr);
|
|
assert.equal(sha256(officialOutput), sha256(officialBundle), "Official sphere conversion is not deterministic");
|
|
const parsedOfficial = JSON.parse(fs.readFileSync(officialReport, "utf8"));
|
|
assert.deepEqual(parsedOfficial.grids.map((grid) => [grid.name, grid.gridClass]), [["ls_sphere", "LEVEL_SET"]]);
|
|
|
|
const malformed = spawnSync(converter, ["--input", path.join(resourceRoot, "generated", "generated-smoke-truncated.vdb"), "--output", path.join(temporary, "bad.nvdb"), "--report", path.join(temporary, "bad.json")], { encoding: "utf8" });
|
|
assert.notEqual(malformed.status, 0);
|
|
assert.match(malformed.stderr, /VDB_CONVERSION_FAILED/);
|
|
|
|
for (const name of ["asset-path", "capability-gates", "volume-vdb"]) {
|
|
const sourceText = fs.readFileSync(path.join(root, `web/protocol/${name}.ts`), "utf8");
|
|
let code = ts.transpileModule(sourceText, { compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2022 }, fileName: `${name}.ts` }).outputText;
|
|
code = code.replaceAll('require("./asset-path")', 'require("./asset-path.cjs")').replaceAll('require("./capability-gates")', 'require("./capability-gates.cjs")');
|
|
fs.writeFileSync(path.join(temporary, `${name}.cjs`), code);
|
|
}
|
|
const require = createRequire(import.meta.url);
|
|
const protocol = require(path.join(temporary, "volume-vdb.cjs"));
|
|
const validated = protocol.validateNanoVDBBundleManifest(JSON.parse(fs.readFileSync(browserManifest, "utf8")));
|
|
assert.equal(validated.bundleSha256, sha256(storedBundle));
|
|
assert.deepEqual(validated.grids.map((grid) => grid.semantic), ["COLOR", "DENSITY", "TEMPERATURE", "VELOCITY"]);
|
|
}
|
|
finally {
|
|
fs.rmSync(temporary, { recursive: true, force: true });
|
|
}
|
|
|
|
process.stdout.write(`vdb-native-pipeline-ok resources=${catalog.entries.length} converter=openvdb13-nanovdb32 conversion-deterministic=1 semantic-deterministic=1 official-deterministic=1 malformed=blocked browser-openvdb=off\n`);
|