Release Healer Man 0.1.7
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
import { open } from "node:fs/promises";
|
||||
|
||||
const GLB_MAGIC = 0x46546c67;
|
||||
const JSON_CHUNK_TYPE = 0x4e4f534a;
|
||||
const GPU_INSTANCING_EXTENSION = "EXT_mesh_gpu_instancing";
|
||||
|
||||
function primitiveCount(json, meshIndex) {
|
||||
return json.meshes?.[meshIndex]?.primitives?.length ?? 0;
|
||||
}
|
||||
|
||||
function gpuInstanceCount(json, node) {
|
||||
const attributes = node.extensions?.[GPU_INSTANCING_EXTENSION]?.attributes;
|
||||
if (!attributes || typeof attributes !== "object") return 0;
|
||||
return Math.max(
|
||||
0,
|
||||
...Object.values(attributes).map((accessorIndex) => (
|
||||
json.accessors?.[accessorIndex]?.count ?? 0
|
||||
)),
|
||||
);
|
||||
}
|
||||
|
||||
export function analyzeGlbJson(json) {
|
||||
const repeatedMeshes = new Map();
|
||||
let estimatedDrawCalls = 0;
|
||||
let expandedPrimitiveInstances = 0;
|
||||
let meshNodes = 0;
|
||||
let gpuInstanceBatches = 0;
|
||||
let gpuInstances = 0;
|
||||
|
||||
for (const node of json.nodes ?? []) {
|
||||
if (!Number.isInteger(node.mesh)) continue;
|
||||
meshNodes += 1;
|
||||
const primitives = primitiveCount(json, node.mesh);
|
||||
const instances = gpuInstanceCount(json, node);
|
||||
estimatedDrawCalls += primitives;
|
||||
expandedPrimitiveInstances += primitives * Math.max(1, instances);
|
||||
if (instances > 0) {
|
||||
gpuInstanceBatches += 1;
|
||||
gpuInstances += instances;
|
||||
continue;
|
||||
}
|
||||
repeatedMeshes.set(node.mesh, (repeatedMeshes.get(node.mesh) ?? 0) + 1);
|
||||
}
|
||||
|
||||
let eligibleRepeatedMeshes = 0;
|
||||
let eligibleRepeatedNodes = 0;
|
||||
let unbatchedDrawCallSavings = 0;
|
||||
for (const [meshIndex, count] of repeatedMeshes) {
|
||||
if (count < 3) continue;
|
||||
eligibleRepeatedMeshes += 1;
|
||||
eligibleRepeatedNodes += count;
|
||||
unbatchedDrawCallSavings += (count - 1) * primitiveCount(json, meshIndex);
|
||||
}
|
||||
|
||||
const materials = json.materials ?? [];
|
||||
return {
|
||||
nodes: json.nodes?.length ?? 0,
|
||||
meshNodes,
|
||||
meshes: json.meshes?.length ?? 0,
|
||||
materials: materials.length,
|
||||
textures: json.textures?.length ?? 0,
|
||||
doubleSidedMaterials: materials.filter((material) => material.doubleSided === true).length,
|
||||
blendedMaterials: materials.filter((material) => material.alphaMode === "BLEND").length,
|
||||
estimatedDrawCalls,
|
||||
expandedPrimitiveInstances,
|
||||
gpuInstanceBatches,
|
||||
gpuInstances,
|
||||
eligibleRepeatedMeshes,
|
||||
eligibleRepeatedNodes,
|
||||
unbatchedDrawCallSavings,
|
||||
usesGpuInstancing: (json.extensionsUsed ?? []).includes(GPU_INSTANCING_EXTENSION),
|
||||
};
|
||||
}
|
||||
|
||||
export async function readGlbJson(file) {
|
||||
const handle = await open(file, "r");
|
||||
try {
|
||||
const header = Buffer.alloc(20);
|
||||
const { bytesRead } = await handle.read(header, 0, header.length, 0);
|
||||
if (bytesRead !== header.length
|
||||
|| header.readUInt32LE(0) !== GLB_MAGIC
|
||||
|| header.readUInt32LE(4) !== 2
|
||||
|| header.readUInt32LE(16) !== JSON_CHUNK_TYPE) {
|
||||
throw new Error(`${file} is not a supported GLB 2.0 file.`);
|
||||
}
|
||||
const jsonLength = header.readUInt32LE(12);
|
||||
const jsonBuffer = Buffer.alloc(jsonLength);
|
||||
const jsonRead = await handle.read(jsonBuffer, 0, jsonLength, 20);
|
||||
if (jsonRead.bytesRead !== jsonLength) throw new Error(`${file} has a truncated JSON chunk.`);
|
||||
return JSON.parse(jsonBuffer.toString("utf8"));
|
||||
} finally {
|
||||
await handle.close();
|
||||
}
|
||||
}
|
||||
|
||||
export async function analyzeGlbFile(file) {
|
||||
return analyzeGlbJson(await readGlbJson(file));
|
||||
}
|
||||
Reference in New Issue
Block a user