Files
workinf_Blender_Wasm/tools/web/check-vdb-native-pipeline.mjs
2026-08-14 18:08:29 -04:00

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`);