bun-webgpu
Version:
Native WebGPU implementation for Bun runtime
1,651 lines (1,633 loc) • 185 kB
JavaScript
// @bun
// src/ffi.ts
import { dlopen, suffix, FFIType } from "bun:ffi";
import { existsSync } from "fs";
import { createRequire } from "module";
var require2 = createRequire(import.meta.url);
function findLibrary() {
try {
const isWindows = process.platform === "win32";
const libraryName = isWindows ? "webgpu_wrapper" : "libwebgpu_wrapper";
const targetLibPath = require2.resolve(`bun-webgpu-${process.platform}-${process.arch}/${libraryName}.${suffix}`);
if (existsSync(targetLibPath)) {
return targetLibPath;
}
} catch {}
throw new Error(`bun-webgpu is not supported on the current platform: ${process.platform}-${process.arch}`);
}
function _loadLibrary(libPath) {
const resolvedPath = libPath || findLibrary();
const { symbols } = dlopen(resolvedPath, {
zwgpuCreateInstance: {
args: [FFIType.pointer],
returns: FFIType.pointer
},
zwgpuInstanceCreateSurface: {
args: [FFIType.pointer, FFIType.pointer],
returns: FFIType.pointer
},
zwgpuInstanceProcessEvents: {
args: [FFIType.pointer],
returns: FFIType.void
},
zwgpuInstanceRequestAdapter: {
args: [
FFIType.pointer,
FFIType.pointer,
FFIType.pointer
],
returns: FFIType.u64
},
zwgpuInstanceWaitAny: {
args: [
FFIType.pointer,
FFIType.u64,
FFIType.pointer,
FFIType.u64
],
returns: FFIType.u32
},
zwgpuInstanceGetWGSLLanguageFeatures: {
args: [FFIType.pointer, FFIType.pointer],
returns: FFIType.u32
},
zwgpuInstanceRelease: {
args: [FFIType.pointer],
returns: FFIType.void
},
zwgpuInstanceAddRef: {
args: [FFIType.pointer],
returns: FFIType.void
},
zwgpuAdapterCreateDevice: {
args: [
FFIType.pointer,
FFIType.pointer
],
returns: FFIType.pointer
},
zwgpuAdapterGetInfo: {
args: [FFIType.pointer, FFIType.pointer],
returns: FFIType.u32
},
zwgpuAdapterRequestDevice: {
args: [
FFIType.pointer,
FFIType.pointer,
FFIType.pointer
],
returns: FFIType.u64
},
zwgpuAdapterRelease: {
args: [FFIType.pointer],
returns: FFIType.void
},
zwgpuAdapterGetFeatures: {
args: [FFIType.pointer, FFIType.pointer],
returns: FFIType.void
},
zwgpuAdapterGetLimits: {
args: [FFIType.pointer, FFIType.pointer],
returns: FFIType.u32
},
zwgpuDeviceGetAdapterInfo: {
args: [FFIType.pointer, FFIType.pointer],
returns: FFIType.u32
},
zwgpuDeviceCreateBuffer: {
args: [FFIType.pointer, FFIType.pointer],
returns: FFIType.pointer
},
zwgpuDeviceCreateTexture: {
args: [FFIType.pointer, FFIType.pointer],
returns: FFIType.pointer
},
zwgpuDeviceCreateSampler: {
args: [FFIType.pointer, FFIType.pointer],
returns: FFIType.pointer
},
zwgpuDeviceCreateShaderModule: {
args: [FFIType.pointer, FFIType.pointer],
returns: FFIType.pointer
},
zwgpuDeviceCreateBindGroupLayout: {
args: [FFIType.pointer, FFIType.pointer],
returns: FFIType.pointer
},
zwgpuDeviceCreateBindGroup: {
args: [FFIType.pointer, FFIType.pointer],
returns: FFIType.pointer
},
zwgpuDeviceCreatePipelineLayout: {
args: [FFIType.pointer, FFIType.pointer],
returns: FFIType.pointer
},
zwgpuDeviceCreateRenderPipeline: {
args: [FFIType.pointer, FFIType.pointer],
returns: FFIType.pointer
},
zwgpuDeviceCreateComputePipeline: {
args: [FFIType.pointer, FFIType.pointer],
returns: FFIType.pointer
},
zwgpuDeviceCreateRenderBundleEncoder: {
args: [FFIType.pointer, FFIType.pointer],
returns: FFIType.pointer
},
zwgpuDeviceCreateCommandEncoder: {
args: [FFIType.pointer, FFIType.pointer],
returns: FFIType.pointer
},
zwgpuDeviceCreateQuerySet: {
args: [FFIType.pointer, FFIType.pointer],
returns: FFIType.pointer
},
zwgpuDeviceGetQueue: {
args: [FFIType.pointer],
returns: FFIType.pointer
},
zwgpuDeviceGetLimits: {
args: [FFIType.pointer, FFIType.pointer],
returns: FFIType.u32
},
zwgpuDeviceHasFeature: {
args: [FFIType.pointer, FFIType.u32],
returns: FFIType.bool
},
zwgpuDeviceGetFeatures: {
args: [FFIType.pointer, FFIType.pointer],
returns: FFIType.void
},
zwgpuDevicePushErrorScope: {
args: [FFIType.pointer, FFIType.u32],
returns: FFIType.void
},
zwgpuDevicePopErrorScope: {
args: [FFIType.pointer, FFIType.pointer],
returns: FFIType.u64
},
zwgpuDeviceTick: {
args: [FFIType.pointer],
returns: FFIType.void
},
zwgpuDeviceInjectError: {
args: [FFIType.pointer, FFIType.u32, FFIType.pointer],
returns: FFIType.void
},
zwgpuDeviceCreateComputePipelineAsync: {
args: [
FFIType.pointer,
FFIType.pointer,
FFIType.pointer
],
returns: FFIType.u64
},
zwgpuDeviceCreateRenderPipelineAsync: {
args: [
FFIType.pointer,
FFIType.pointer,
FFIType.pointer
],
returns: FFIType.u64
},
zwgpuDeviceDestroy: {
args: [FFIType.pointer],
returns: FFIType.void
},
zwgpuDeviceRelease: {
args: [FFIType.pointer],
returns: FFIType.void
},
zwgpuBufferGetMappedRange: {
args: [FFIType.pointer, FFIType.u64, FFIType.u64],
returns: FFIType.ptr
},
zwgpuBufferGetConstMappedRange: {
args: [FFIType.pointer, FFIType.u64, FFIType.u64],
returns: FFIType.ptr
},
zwgpuBufferUnmap: {
args: [FFIType.pointer],
returns: FFIType.void
},
zwgpuBufferMapAsync: {
args: [
FFIType.pointer,
FFIType.u64,
FFIType.u64,
FFIType.u64,
FFIType.pointer
],
returns: FFIType.u64
},
zwgpuBufferDestroy: {
args: [FFIType.pointer],
returns: FFIType.void
},
zwgpuBufferRelease: {
args: [FFIType.pointer],
returns: FFIType.void
},
zwgpuTextureCreateView: {
args: [FFIType.pointer, FFIType.pointer],
returns: FFIType.pointer
},
zwgpuTextureDestroy: {
args: [FFIType.pointer],
returns: FFIType.void
},
zwgpuTextureRelease: {
args: [FFIType.pointer],
returns: FFIType.void
},
zwgpuTextureViewRelease: {
args: [FFIType.pointer],
returns: FFIType.void
},
zwgpuSamplerRelease: {
args: [FFIType.pointer],
returns: FFIType.void
},
zwgpuShaderModuleGetCompilationInfo: {
args: [FFIType.pointer, FFIType.pointer],
returns: FFIType.u64
},
zwgpuShaderModuleRelease: {
args: [FFIType.pointer],
returns: FFIType.void
},
zwgpuBindGroupLayoutRelease: {
args: [FFIType.pointer],
returns: FFIType.void
},
zwgpuBindGroupRelease: {
args: [FFIType.pointer],
returns: FFIType.void
},
zwgpuPipelineLayoutRelease: {
args: [FFIType.pointer],
returns: FFIType.void
},
zwgpuQuerySetDestroy: {
args: [FFIType.pointer],
returns: FFIType.void
},
zwgpuQuerySetRelease: {
args: [FFIType.pointer],
returns: FFIType.void
},
zwgpuRenderPipelineRelease: {
args: [FFIType.pointer],
returns: FFIType.void
},
zwgpuRenderPipelineGetBindGroupLayout: {
args: [FFIType.pointer, FFIType.u32],
returns: FFIType.pointer
},
zwgpuComputePipelineRelease: {
args: [FFIType.pointer],
returns: FFIType.void
},
zwgpuComputePipelineGetBindGroupLayout: {
args: [FFIType.pointer, FFIType.u32],
returns: FFIType.pointer
},
zwgpuCommandEncoderBeginRenderPass: {
args: [FFIType.pointer, FFIType.pointer],
returns: FFIType.pointer
},
zwgpuCommandEncoderBeginComputePass: {
args: [FFIType.pointer, FFIType.pointer],
returns: FFIType.pointer
},
zwgpuCommandEncoderClearBuffer: {
args: [
FFIType.pointer,
FFIType.pointer,
FFIType.u64,
FFIType.u64
],
returns: FFIType.void
},
zwgpuCommandEncoderCopyBufferToBuffer: {
args: [
FFIType.pointer,
FFIType.pointer,
FFIType.u64,
FFIType.pointer,
FFIType.u64,
FFIType.u64
],
returns: FFIType.void
},
zwgpuCommandEncoderCopyBufferToTexture: {
args: [
FFIType.pointer,
FFIType.pointer,
FFIType.pointer,
FFIType.pointer
],
returns: FFIType.void
},
zwgpuCommandEncoderCopyTextureToBuffer: {
args: [
FFIType.pointer,
FFIType.pointer,
FFIType.pointer,
FFIType.pointer
],
returns: FFIType.void
},
zwgpuCommandEncoderCopyTextureToTexture: {
args: [
FFIType.pointer,
FFIType.pointer,
FFIType.pointer,
FFIType.pointer
],
returns: FFIType.void
},
zwgpuCommandEncoderResolveQuerySet: {
args: [
FFIType.pointer,
FFIType.pointer,
FFIType.u32,
FFIType.u32,
FFIType.pointer,
FFIType.u64
],
returns: FFIType.void
},
zwgpuCommandEncoderFinish: {
args: [FFIType.pointer, FFIType.pointer],
returns: FFIType.pointer
},
zwgpuCommandEncoderRelease: {
args: [FFIType.pointer],
returns: FFIType.void
},
zwgpuCommandEncoderPushDebugGroup: {
args: [FFIType.pointer, FFIType.pointer],
returns: FFIType.void
},
zwgpuCommandEncoderPopDebugGroup: {
args: [FFIType.pointer],
returns: FFIType.void
},
zwgpuCommandEncoderInsertDebugMarker: {
args: [FFIType.pointer, FFIType.pointer],
returns: FFIType.void
},
zwgpuRenderPassEncoderSetScissorRect: {
args: [
FFIType.pointer,
FFIType.u32,
FFIType.u32,
FFIType.u32,
FFIType.u32
],
returns: FFIType.void
},
zwgpuRenderPassEncoderSetViewport: {
args: [
FFIType.pointer,
FFIType.f32,
FFIType.f32,
FFIType.f32,
FFIType.f32,
FFIType.f32,
FFIType.f32
],
returns: FFIType.void
},
zwgpuRenderPassEncoderSetBlendConstant: {
args: [
FFIType.pointer,
FFIType.pointer
],
returns: FFIType.void
},
zwgpuRenderPassEncoderSetStencilReference: {
args: [
FFIType.pointer,
FFIType.u32
],
returns: FFIType.void
},
zwgpuRenderPassEncoderSetPipeline: {
args: [FFIType.pointer, FFIType.pointer],
returns: FFIType.void
},
zwgpuRenderPassEncoderSetBindGroup: {
args: [
FFIType.pointer,
FFIType.u32,
FFIType.pointer,
FFIType.u64,
FFIType.pointer
],
returns: FFIType.void
},
zwgpuRenderPassEncoderSetVertexBuffer: {
args: [
FFIType.pointer,
FFIType.u32,
FFIType.pointer,
FFIType.u64,
FFIType.u64
],
returns: FFIType.void
},
zwgpuRenderPassEncoderSetIndexBuffer: {
args: [
FFIType.pointer,
FFIType.pointer,
FFIType.u32,
FFIType.u64,
FFIType.u64
],
returns: FFIType.void
},
zwgpuRenderPassEncoderDraw: {
args: [
FFIType.pointer,
FFIType.u32,
FFIType.u32,
FFIType.u32,
FFIType.u32
],
returns: FFIType.void
},
zwgpuRenderPassEncoderDrawIndexed: {
args: [
FFIType.pointer,
FFIType.u32,
FFIType.u32,
FFIType.u32,
FFIType.i32,
FFIType.u32
],
returns: FFIType.void
},
zwgpuRenderPassEncoderDrawIndirect: {
args: [
FFIType.pointer,
FFIType.pointer,
FFIType.u64
],
returns: FFIType.void
},
zwgpuRenderPassEncoderDrawIndexedIndirect: {
args: [
FFIType.pointer,
FFIType.pointer,
FFIType.u64
],
returns: FFIType.void
},
zwgpuRenderPassEncoderExecuteBundles: {
args: [
FFIType.pointer,
FFIType.u64,
FFIType.pointer
],
returns: FFIType.void
},
zwgpuRenderPassEncoderEnd: {
args: [FFIType.pointer],
returns: FFIType.void
},
zwgpuRenderPassEncoderRelease: {
args: [FFIType.pointer],
returns: FFIType.void
},
zwgpuRenderPassEncoderPushDebugGroup: {
args: [FFIType.pointer, FFIType.pointer],
returns: FFIType.void
},
zwgpuRenderPassEncoderPopDebugGroup: {
args: [FFIType.pointer],
returns: FFIType.void
},
zwgpuRenderPassEncoderInsertDebugMarker: {
args: [FFIType.pointer, FFIType.pointer],
returns: FFIType.void
},
zwgpuRenderPassEncoderBeginOcclusionQuery: {
args: [
FFIType.pointer,
FFIType.u32
],
returns: FFIType.void
},
zwgpuRenderPassEncoderEndOcclusionQuery: {
args: [
FFIType.pointer
],
returns: FFIType.void
},
zwgpuComputePassEncoderSetPipeline: {
args: [FFIType.pointer, FFIType.pointer],
returns: FFIType.void
},
zwgpuComputePassEncoderSetBindGroup: {
args: [
FFIType.pointer,
FFIType.u32,
FFIType.pointer,
FFIType.u64,
FFIType.pointer
],
returns: FFIType.void
},
zwgpuComputePassEncoderDispatchWorkgroups: {
args: [
FFIType.pointer,
FFIType.u32,
FFIType.u32,
FFIType.u32
],
returns: FFIType.void
},
zwgpuComputePassEncoderDispatchWorkgroupsIndirect: {
args: [
FFIType.pointer,
FFIType.pointer,
FFIType.u64
],
returns: FFIType.void
},
zwgpuComputePassEncoderEnd: {
args: [FFIType.pointer],
returns: FFIType.void
},
zwgpuComputePassEncoderRelease: {
args: [FFIType.pointer],
returns: FFIType.void
},
zwgpuComputePassEncoderPushDebugGroup: {
args: [FFIType.pointer, FFIType.pointer],
returns: FFIType.void
},
zwgpuComputePassEncoderPopDebugGroup: {
args: [FFIType.pointer],
returns: FFIType.void
},
zwgpuComputePassEncoderInsertDebugMarker: {
args: [FFIType.pointer, FFIType.pointer],
returns: FFIType.void
},
zwgpuCommandBufferRelease: {
args: [FFIType.pointer],
returns: FFIType.void
},
zwgpuQueueSubmit: {
args: [
FFIType.pointer,
FFIType.u64,
FFIType.pointer
],
returns: FFIType.void
},
zwgpuQueueWriteBuffer: {
args: [
FFIType.pointer,
FFIType.pointer,
FFIType.u64,
FFIType.ptr,
FFIType.u64
],
returns: FFIType.void
},
zwgpuQueueWriteTexture: {
args: [
FFIType.pointer,
FFIType.pointer,
FFIType.ptr,
FFIType.u64,
FFIType.pointer,
FFIType.pointer
],
returns: FFIType.void
},
zwgpuQueueOnSubmittedWorkDone: {
args: [
FFIType.pointer,
FFIType.pointer
],
returns: FFIType.u64
},
zwgpuQueueRelease: {
args: [FFIType.pointer],
returns: FFIType.void
},
zwgpuSurfaceConfigure: {
args: [FFIType.pointer, FFIType.pointer],
returns: FFIType.void
},
zwgpuSurfaceUnconfigure: {
args: [FFIType.pointer],
returns: FFIType.void
},
zwgpuSurfaceGetCurrentTexture: {
args: [FFIType.pointer, FFIType.pointer],
returns: FFIType.void
},
zwgpuSurfacePresent: {
args: [FFIType.pointer],
returns: FFIType.void
},
zwgpuSurfaceRelease: {
args: [FFIType.pointer],
returns: FFIType.void
},
zwgpuAdapterInfoFreeMembers: {
args: [FFIType.pointer],
returns: FFIType.void
},
zwgpuSurfaceCapabilitiesFreeMembers: {
args: [FFIType.pointer],
returns: FFIType.void
},
zwgpuSupportedFeaturesFreeMembers: {
args: [FFIType.pointer],
returns: FFIType.void
},
zwgpuSharedBufferMemoryEndAccessStateFreeMembers: {
args: [FFIType.pointer],
returns: FFIType.void
},
zwgpuSharedTextureMemoryEndAccessStateFreeMembers: {
args: [FFIType.pointer],
returns: FFIType.void
},
zwgpuSupportedWGSLLanguageFeaturesFreeMembers: {
args: [FFIType.pointer],
returns: FFIType.void
},
zwgpuRenderBundleRelease: {
args: [FFIType.pointer],
returns: FFIType.void
},
zwgpuRenderBundleEncoderDraw: {
args: [
FFIType.pointer,
FFIType.u32,
FFIType.u32,
FFIType.u32,
FFIType.u32
],
returns: FFIType.void
},
zwgpuRenderBundleEncoderDrawIndexed: {
args: [
FFIType.pointer,
FFIType.u32,
FFIType.u32,
FFIType.u32,
FFIType.i32,
FFIType.u32
],
returns: FFIType.void
},
zwgpuRenderBundleEncoderDrawIndirect: {
args: [
FFIType.pointer,
FFIType.pointer,
FFIType.u64
],
returns: FFIType.void
},
zwgpuRenderBundleEncoderDrawIndexedIndirect: {
args: [
FFIType.pointer,
FFIType.pointer,
FFIType.u64
],
returns: FFIType.void
},
zwgpuRenderBundleEncoderFinish: {
args: [FFIType.pointer, FFIType.pointer],
returns: FFIType.pointer
},
zwgpuRenderBundleEncoderSetBindGroup: {
args: [
FFIType.pointer,
FFIType.u32,
FFIType.pointer,
FFIType.u64,
FFIType.pointer
],
returns: FFIType.void
},
zwgpuRenderBundleEncoderSetIndexBuffer: {
args: [
FFIType.pointer,
FFIType.pointer,
FFIType.u32,
FFIType.u64,
FFIType.u64
],
returns: FFIType.void
},
zwgpuRenderBundleEncoderSetPipeline: {
args: [FFIType.pointer, FFIType.pointer],
returns: FFIType.void
},
zwgpuRenderBundleEncoderSetVertexBuffer: {
args: [
FFIType.pointer,
FFIType.u32,
FFIType.pointer,
FFIType.u64,
FFIType.u64
],
returns: FFIType.void
},
zwgpuRenderBundleEncoderRelease: {
args: [FFIType.pointer],
returns: FFIType.void
},
zwgpuRenderBundleEncoderPushDebugGroup: {
args: [FFIType.pointer, FFIType.pointer],
returns: FFIType.void
},
zwgpuRenderBundleEncoderPopDebugGroup: {
args: [FFIType.pointer],
returns: FFIType.void
},
zwgpuRenderBundleEncoderInsertDebugMarker: {
args: [FFIType.pointer, FFIType.pointer],
returns: FFIType.void
}
});
return symbols;
}
function loadLibrary(libPath) {
const rawSymbols = _loadLibrary(libPath);
const normalizedSymbols = Object.keys(rawSymbols).reduce((acc, key) => {
const newKey = key.replace(/^zw/, "w");
acc[newKey] = rawSymbols[key];
return acc;
}, {});
const FFI_SYMBOLS = process.env.DEBUG === "true" || process.env.TRACE_WEBGPU === "true" ? convertToDebugSymbols(normalizedSymbols) : normalizedSymbols;
return FFI_SYMBOLS;
}
function convertToDebugSymbols(symbols) {
const debugSymbols = {};
if (process.env.DEBUG === "true") {
Object.entries(symbols).forEach(([key, value]) => {
if (typeof value === "function") {
debugSymbols[key] = (...args) => {
console.log(`${key}(${args.map((arg) => String(arg)).join(", ")})`);
const result = value(...args);
console.log(`${key} returned:`, String(result));
return result;
};
} else {
debugSymbols[key] = value;
}
});
}
if (process.env.TRACE_WEBGPU === "true") {
const traceSymbols = {};
Object.entries(symbols).forEach(([key, value]) => {
if (typeof value === "function") {
traceSymbols[key] = [];
debugSymbols[key] = (...args) => {
const start = performance.now();
const result = value(...args);
const end = performance.now();
traceSymbols[key].push(end - start);
return result;
};
} else {
debugSymbols[key] = value;
}
});
process.on("exit", () => {
const allStats = [];
for (const [key, timings] of Object.entries(traceSymbols)) {
if (!Array.isArray(timings) || timings.length === 0) {
continue;
}
const sortedTimings = [...timings].sort((a, b) => a - b);
const count = sortedTimings.length;
const total = sortedTimings.reduce((acc, t) => acc + t, 0);
const average = total / count;
const min = sortedTimings[0];
const max = sortedTimings[count - 1];
const medianIndex = Math.floor(count / 2);
const p90Index = Math.floor(count * 0.9);
const p99Index = Math.floor(count * 0.99);
const median = sortedTimings[medianIndex];
const p90 = sortedTimings[Math.min(p90Index, count - 1)];
const p99 = sortedTimings[Math.min(p99Index, count - 1)];
allStats.push({
name: key,
count,
total,
average,
min,
max,
median,
p90,
p99
});
}
allStats.sort((a, b) => b.total - a.total);
console.log(`
--- WebGPU FFI Call Performance ---`);
console.log("Sorted by total time spent (descending)");
console.log("-------------------------------------------------------------------------------------------------------------------------");
if (allStats.length === 0) {
console.log("No trace data collected or all symbols had zero calls.");
} else {
const nameHeader = "Symbol";
const callsHeader = "Calls";
const totalHeader = "Total (ms)";
const avgHeader = "Avg (ms)";
const minHeader = "Min (ms)";
const maxHeader = "Max (ms)";
const medHeader = "Med (ms)";
const p90Header = "P90 (ms)";
const p99Header = "P99 (ms)";
const nameWidth = Math.max(nameHeader.length, ...allStats.map((s) => s.name.length));
const countWidth = Math.max(callsHeader.length, ...allStats.map((s) => String(s.count).length));
const totalWidth = Math.max(totalHeader.length, ...allStats.map((s) => s.total.toFixed(2).length));
const avgWidth = Math.max(avgHeader.length, ...allStats.map((s) => s.average.toFixed(2).length));
const minWidth = Math.max(minHeader.length, ...allStats.map((s) => s.min.toFixed(2).length));
const maxWidth = Math.max(maxHeader.length, ...allStats.map((s) => s.max.toFixed(2).length));
const medianWidth = Math.max(medHeader.length, ...allStats.map((s) => s.median.toFixed(2).length));
const p90Width = Math.max(p90Header.length, ...allStats.map((s) => s.p90.toFixed(2).length));
const p99Width = Math.max(p99Header.length, ...allStats.map((s) => s.p99.toFixed(2).length));
console.log(`${nameHeader.padEnd(nameWidth)} | ` + `${callsHeader.padStart(countWidth)} | ` + `${totalHeader.padStart(totalWidth)} | ` + `${avgHeader.padStart(avgWidth)} | ` + `${minHeader.padStart(minWidth)} | ` + `${maxHeader.padStart(maxWidth)} | ` + `${medHeader.padStart(medianWidth)} | ` + `${p90Header.padStart(p90Width)} | ` + `${p99Header.padStart(p99Width)}`);
console.log(`${"-".repeat(nameWidth)}-+-${"-".repeat(countWidth)}-+-${"-".repeat(totalWidth)}-+-${"-".repeat(avgWidth)}-+-${"-".repeat(minWidth)}-+-${"-".repeat(maxWidth)}-+-${"-".repeat(medianWidth)}-+-${"-".repeat(p90Width)}-+-${"-".repeat(p99Width)}`);
allStats.forEach((stat) => {
console.log(`${stat.name.padEnd(nameWidth)} | ` + `${String(stat.count).padStart(countWidth)} | ` + `${stat.total.toFixed(2).padStart(totalWidth)} | ` + `${stat.average.toFixed(2).padStart(avgWidth)} | ` + `${stat.min.toFixed(2).padStart(minWidth)} | ` + `${stat.max.toFixed(2).padStart(maxWidth)} | ` + `${stat.median.toFixed(2).padStart(medianWidth)} | ` + `${stat.p90.toFixed(2).padStart(p90Width)} | ` + `${stat.p99.toFixed(2).padStart(p99Width)}`);
});
}
console.log("-------------------------------------------------------------------------------------------------------------------------");
});
}
return debugSymbols;
}
// src/GPU.ts
import { JSCallback as JSCallback5, toArrayBuffer as toArrayBuffer6, ptr as ptr11, FFIType as FFIType6 } from "bun:ffi";
// src/GPUAdapter.ts
import { FFIType as FFIType5, JSCallback as JSCallback4, ptr as ptr10 } from "bun:ffi";
// src/GPUDevice.ts
import { FFIType as FFIType4, JSCallback as JSCallback3, ptr as ptr9 } from "bun:ffi";
// src/structs_def.ts
import { toArrayBuffer as toArrayBuffer3 } from "bun:ffi";
// src/shared.ts
import { toArrayBuffer } from "bun:ffi";
// src/buffer_pool.ts
class BufferPool {
buffers;
blockSize;
freeBlocks = [];
minBlocks;
maxBlocks;
currentBlocks;
allocatedCount = 0;
bufferToBlockIndex = new WeakMap;
constructor(minBlocks, maxBlocks, blockSize) {
if (minBlocks <= 0 || maxBlocks <= 0 || blockSize <= 0) {
throw new Error("Min blocks, max blocks, and block size must be positive");
}
if (minBlocks > maxBlocks) {
throw new Error("Min blocks cannot be greater than max blocks");
}
this.minBlocks = minBlocks;
this.maxBlocks = maxBlocks;
this.blockSize = blockSize;
this.currentBlocks = minBlocks;
this.buffers = [];
this.initializePool();
}
initializePool() {
this.freeBlocks = [];
for (let i = 0;i < this.minBlocks; i++) {
this.buffers.push(new ArrayBuffer(this.blockSize));
this.freeBlocks.push(i);
}
}
expandPool() {
if (this.currentBlocks >= this.maxBlocks) {
return false;
}
const oldBlocks = this.currentBlocks;
const newBlocks = Math.min(this.maxBlocks, this.currentBlocks * 2);
for (let i = oldBlocks;i < newBlocks; i++) {
this.buffers.push(new ArrayBuffer(this.blockSize));
this.freeBlocks.push(i);
}
this.currentBlocks = newBlocks;
return true;
}
request() {
if (this.freeBlocks.length === 0) {
if (!this.expandPool()) {
throw new Error("BufferPool out of memory: no free blocks available");
}
}
const blockIndex = this.freeBlocks.pop();
if (blockIndex < 0 || blockIndex >= this.buffers.length) {
throw new Error("Invalid block index");
}
this.allocatedCount++;
const buffer = this.buffers[blockIndex];
this.bufferToBlockIndex.set(buffer, blockIndex);
return { __type: "BlockBuffer", buffer, index: blockIndex };
}
release(buffer) {
const blockIndex = this.bufferToBlockIndex.get(buffer);
if (blockIndex === undefined) {
throw new Error("ArrayBuffer was not allocated from this allocator or already freed");
}
this.bufferToBlockIndex.delete(buffer);
this.freeBlocks.push(blockIndex);
this.allocatedCount--;
}
releaseBlock(blockIndex) {
if (blockIndex < 0 || blockIndex >= this.currentBlocks) {
throw new Error("Block index out of range");
}
const buffer = this.buffers[blockIndex];
if (!this.bufferToBlockIndex.has(buffer)) {
throw new Error(`Block ${blockIndex} was not allocated or already freed`);
}
this.bufferToBlockIndex.delete(buffer);
this.freeBlocks.push(blockIndex);
this.allocatedCount--;
}
getBuffer(blockIndex) {
if (blockIndex < 0 || blockIndex >= this.currentBlocks) {
throw new Error("Block index out of range");
}
return this.buffers[blockIndex];
}
reset() {
this.allocatedCount = 0;
this.bufferToBlockIndex = new WeakMap;
this.currentBlocks = this.minBlocks;
this.buffers = [];
this.initializePool();
}
get totalBlockCount() {
return this.currentBlocks;
}
get maxBlockCount() {
return this.maxBlocks;
}
get minBlockCount() {
return this.minBlocks;
}
get allocatedBlockCount() {
return this.allocatedCount;
}
get freeBlockCount() {
return this.freeBlocks.length;
}
get hasAvailableBlocks() {
return this.freeBlocks.length > 0 || this.currentBlocks < this.maxBlocks;
}
get utilizationRatio() {
return this.currentBlocks > 0 ? this.allocatedCount / this.currentBlocks : 0;
}
}
// src/shared.ts
var AsyncStatus = {
Success: 1,
CallbackCancelled: 2,
Error: 3,
Aborted: 4,
Force32: 2147483647
};
var WGPUErrorType = {
"no-error": 1,
validation: 2,
"out-of-memory": 3,
internal: 4,
unknown: 5,
"force-32": 2147483647
};
var idBufferPool = new BufferPool(64, 1024, 8);
function packUserDataId(id) {
const blockBuffer = idBufferPool.request();
const userDataBuffer = new Uint32Array(blockBuffer.buffer);
userDataBuffer[0] = id;
userDataBuffer[1] = blockBuffer.index;
return blockBuffer.buffer;
}
function unpackUserDataId(userDataPtr) {
const userDataBuffer = toArrayBuffer(userDataPtr, 0, 8);
const userDataView = new Uint32Array(userDataBuffer);
const id = userDataView[0];
const index = userDataView[1];
idBufferPool.releaseBlock(index);
return id;
}
class GPUAdapterInfoImpl {
__brand = "GPUAdapterInfo";
vendor = "";
architecture = "";
device = "";
description = "";
subgroupMinSize = 0;
subgroupMaxSize = 0;
isFallbackAdapter = false;
constructor() {
throw new TypeError("Illegal constructor");
}
}
function normalizeIdentifier(input) {
if (!input || input.trim() === "") {
return "";
}
return input.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
}
function decodeCallbackMessage(messagePtr, messageSize) {
if (!messagePtr || messageSize === 0n || messageSize === 0) {
return "[empty message]";
}
let arrayBuffer = null;
arrayBuffer = messageSize ? toArrayBuffer(messagePtr, 0, Number(messageSize)) : toArrayBuffer(messagePtr);
let message = "Could not decode error message";
if (arrayBuffer instanceof Error) {
message = arrayBuffer.message;
} else {
message = Buffer.from(arrayBuffer).toString();
}
return message;
}
var DEFAULT_SUPPORTED_LIMITS = Object.freeze({
maxTextureDimension1D: 8192,
maxTextureDimension2D: 8192,
maxTextureDimension3D: 2048,
maxTextureArrayLayers: 256,
maxBindGroups: 4,
maxBindGroupsPlusVertexBuffers: 24,
maxBindingsPerBindGroup: 1000,
maxStorageBuffersInFragmentStage: 8,
maxStorageBuffersInVertexStage: 8,
maxStorageTexturesInFragmentStage: 4,
maxStorageTexturesInVertexStage: 4,
maxDynamicUniformBuffersPerPipelineLayout: 8,
maxDynamicStorageBuffersPerPipelineLayout: 4,
maxSampledTexturesPerShaderStage: 16,
maxSamplersPerShaderStage: 16,
maxStorageBuffersPerShaderStage: 8,
maxStorageTexturesPerShaderStage: 4,
maxUniformBuffersPerShaderStage: 12,
maxUniformBufferBindingSize: 65536,
maxStorageBufferBindingSize: 134217728,
minUniformBufferOffsetAlignment: 256,
minStorageBufferOffsetAlignment: 256,
maxVertexBuffers: 8,
maxBufferSize: 268435456,
maxVertexAttributes: 16,
maxVertexBufferArrayStride: 2048,
maxInterStageShaderComponents: 4294967295,
maxInterStageShaderVariables: 16,
maxColorAttachments: 8,
maxColorAttachmentBytesPerSample: 32,
maxComputeWorkgroupStorageSize: 16384,
maxComputeInvocationsPerWorkgroup: 256,
maxComputeWorkgroupSizeX: 256,
maxComputeWorkgroupSizeY: 256,
maxComputeWorkgroupSizeZ: 64,
maxComputeWorkgroupsPerDimension: 65535,
maxImmediateSize: 0
});
class GPUSupportedLimitsImpl {
__brand = "GPUSupportedLimits";
maxTextureDimension1D = 8192;
maxTextureDimension2D = 8192;
maxTextureDimension3D = 2048;
maxTextureArrayLayers = 256;
maxBindGroups = 4;
maxBindGroupsPlusVertexBuffers = 24;
maxBindingsPerBindGroup = 1000;
maxStorageBuffersInFragmentStage = 8;
maxStorageBuffersInVertexStage = 8;
maxStorageTexturesInFragmentStage = 4;
maxStorageTexturesInVertexStage = 4;
maxDynamicUniformBuffersPerPipelineLayout = 8;
maxDynamicStorageBuffersPerPipelineLayout = 4;
maxSampledTexturesPerShaderStage = 16;
maxSamplersPerShaderStage = 16;
maxStorageBuffersPerShaderStage = 8;
maxStorageTexturesPerShaderStage = 4;
maxUniformBuffersPerShaderStage = 12;
maxUniformBufferBindingSize = 65536;
maxStorageBufferBindingSize = 134217728;
minUniformBufferOffsetAlignment = 256;
minStorageBufferOffsetAlignment = 256;
maxVertexBuffers = 8;
maxBufferSize = 268435456;
maxVertexAttributes = 16;
maxVertexBufferArrayStride = 2048;
maxInterStageShaderComponents = 4294967295;
maxInterStageShaderVariables = 16;
maxColorAttachments = 8;
maxColorAttachmentBytesPerSample = 32;
maxComputeWorkgroupStorageSize = 16384;
maxComputeInvocationsPerWorkgroup = 256;
maxComputeWorkgroupSizeX = 256;
maxComputeWorkgroupSizeY = 256;
maxComputeWorkgroupSizeZ = 64;
maxComputeWorkgroupsPerDimension = 65535;
constructor() {
throw new TypeError("Illegal constructor");
}
}
// src/utils/error.ts
function fatalError(...args) {
const message = args.join(" ");
console.error("FATAL ERROR:", message);
throw new Error(message);
}
class OperationError extends Error {
constructor(message) {
super(message);
this.name = "OperationError";
}
}
class GPUErrorImpl extends Error {
constructor(message) {
super(message);
this.name = "GPUError";
}
}
class GPUOutOfMemoryError extends Error {
constructor(message) {
super(message);
this.name = "GPUOutOfMemoryError";
}
}
class GPUInternalError extends Error {
constructor(message) {
super(message);
this.name = "GPUInternalError";
}
}
class GPUValidationError extends Error {
constructor(message) {
super(message);
this.name = "GPUValidationError";
}
}
class GPUPipelineErrorImpl extends DOMException {
reason;
__brand = "GPUPipelineError";
constructor(message, options) {
const parts = message.split(`
`);
const errorMessage = parts[0];
const stack = parts.slice(1).join(`
`);
super(errorMessage, "GPUPipelineError");
this.reason = options.reason;
this.stack = stack;
}
}
class AbortError2 extends Error {
constructor(message) {
super(message);
this.name = "AbortError";
}
}
function createWGPUError(type, message) {
switch (type) {
case WGPUErrorType["out-of-memory"]:
return new GPUOutOfMemoryError(message);
case WGPUErrorType.internal:
return new GPUInternalError(message);
case WGPUErrorType.validation:
return new GPUValidationError(message);
default:
return new GPUErrorImpl(message);
}
}
// src/structs_ffi.ts
import { ptr, toArrayBuffer as toArrayBuffer2 } from "bun:ffi";
var pointerSize = process.arch === "x64" || process.arch === "arm64" ? 8 : 4;
var typeSizes = {
u8: 1,
bool_u8: 1,
bool_u32: 4,
u16: 2,
i16: 2,
u32: 4,
u64: 8,
f32: 4,
f64: 8,
pointer: pointerSize,
i32: 4
};
var primitiveKeys = Object.keys(typeSizes);
function isPrimitiveType(type) {
return typeof type === "string" && primitiveKeys.includes(type);
}
var typeAlignments = { ...typeSizes };
var typeGetters = {
u8: (view, offset) => view.getUint8(offset),
bool_u8: (view, offset) => Boolean(view.getUint8(offset)),
bool_u32: (view, offset) => Boolean(view.getUint32(offset, true)),
u16: (view, offset) => view.getUint16(offset, true),
i16: (view, offset) => view.getInt16(offset, true),
u32: (view, offset) => view.getUint32(offset, true),
u64: (view, offset) => view.getBigUint64(offset, true),
f32: (view, offset) => view.getFloat32(offset, true),
f64: (view, offset) => view.getFloat64(offset, true),
i32: (view, offset) => view.getInt32(offset, true),
pointer: (view, offset) => pointerSize === 8 ? view.getBigUint64(offset, true) : BigInt(view.getUint32(offset, true))
};
function objectPtr() {
return {
__type: "objectPointer"
};
}
function isObjectPointerDef(type) {
return typeof type === "object" && type !== null && type.__type === "objectPointer";
}
function allocStruct(structDef, options) {
const buffer = new ArrayBuffer(structDef.size);
const view = new DataView(buffer);
const result = { buffer, view };
const { pack: pointerPacker } = primitivePackers("pointer");
if (options?.lengths) {
const subBuffers = {};
for (const [arrayFieldName, length] of Object.entries(options.lengths)) {
const arrayMeta = structDef.arrayFields.get(arrayFieldName);
if (!arrayMeta) {
throw new Error(`Field '${arrayFieldName}' is not an array field with a lengthOf field`);
}
const subBuffer = new ArrayBuffer(length * arrayMeta.elementSize);
subBuffers[arrayFieldName] = subBuffer;
const pointer = length > 0 ? ptr(subBuffer) : null;
pointerPacker(view, arrayMeta.arrayOffset, pointer);
arrayMeta.lengthPack(view, arrayMeta.lengthOffset, length);
}
if (Object.keys(subBuffers).length > 0) {
result.subBuffers = subBuffers;
}
}
return result;
}
function alignOffset(offset, align) {
return offset + (align - 1) & ~(align - 1);
}
function enumTypeError(value) {
throw new TypeError(`Invalid enum value: ${value}`);
}
function defineEnum(mapping, base = "u32") {
const reverse = Object.fromEntries(Object.entries(mapping).map(([k, v]) => [v, k]));
return {
__type: "enum",
type: base,
to(value) {
return typeof value === "number" ? value : mapping[value] ?? enumTypeError(String(value));
},
from(value) {
return reverse[value] ?? enumTypeError(String(value));
},
enum: mapping
};
}
function isEnum(type) {
return typeof type === "object" && type.__type === "enum";
}
function isStruct(type) {
return typeof type === "object" && type.__type === "struct";
}
function primitivePackers(type) {
let pack;
let unpack;
switch (type) {
case "u8":
pack = (view, off, val) => view.setUint8(off, val);
unpack = (view, off) => view.getUint8(off);
break;
case "bool_u8":
pack = (view, off, val) => view.setUint8(off, val ? 1 : 0);
unpack = (view, off) => Boolean(view.getUint8(off));
break;
case "bool_u32":
pack = (view, off, val) => view.setUint32(off, val ? 1 : 0, true);
unpack = (view, off) => Boolean(view.getUint32(off, true));
break;
case "u16":
pack = (view, off, val) => view.setUint16(off, val, true);
unpack = (view, off) => view.getUint16(off, true);
break;
case "i16":
pack = (view, off, val) => view.setInt16(off, val, true);
unpack = (view, off) => view.getInt16(off, true);
break;
case "u32":
pack = (view, off, val) => view.setUint32(off, val, true);
unpack = (view, off) => view.getUint32(off, true);
break;
case "i32":
pack = (view, off, val) => view.setInt32(off, val, true);
unpack = (view, off) => view.getInt32(off, true);
break;
case "u64":
pack = (view, off, val) => view.setBigUint64(off, BigInt(val), true);
unpack = (view, off) => view.getBigUint64(off, true);
break;
case "f32":
pack = (view, off, val) => view.setFloat32(off, val, true);
unpack = (view, off) => view.getFloat32(off, true);
break;
case "f64":
pack = (view, off, val) => view.setFloat64(off, val, true);
unpack = (view, off) => view.getFloat64(off, true);
break;
case "pointer":
pack = (view, off, val) => {
pointerSize === 8 ? view.setBigUint64(off, val ? BigInt(val) : 0n, true) : view.setUint32(off, val ? Number(val) : 0, true);
};
unpack = (view, off) => {
const bint = pointerSize === 8 ? view.getBigUint64(off, true) : BigInt(view.getUint32(off, true));
return Number(bint);
};
break;
default:
fatalError(`Unsupported primitive type: ${type}`);
}
return { pack, unpack };
}
var { pack: pointerPacker, unpack: pointerUnpacker } = primitivePackers("pointer");
function packObjectArray(val) {
const buffer = new ArrayBuffer(val.length * pointerSize);
const bufferView = new DataView(buffer);
for (let i = 0;i < val.length; i++) {
const instance = val[i];
const ptrValue = instance?.ptr ?? null;
pointerPacker(bufferView, i * pointerSize, ptrValue);
}
return bufferView;
}
var encoder = new TextEncoder;
function defineStruct(fields, structDefOptions) {
let offset = 0;
let maxAlign = 1;
const layout = [];
const lengthOfFields = {};
const lengthOfRequested = [];
const arrayFieldsMetadata = {};
for (const [name, typeOrStruct, options = {}] of fields) {
if (options.condition && !options.condition()) {
continue;
}
let size = 0, align = 0;
let pack;
let unpack;
let needsLengthOf = false;
let lengthOfDef = null;
if (isPrimitiveType(typeOrStruct)) {
size = typeSizes[typeOrStruct];
align = typeAlignments[typeOrStruct];
({ pack, unpack } = primitivePackers(typeOrStruct));
} else if (typeof typeOrStruct === "string" && typeOrStruct === "cstring") {
size = pointerSize;
align = pointerSize;
pack = (view, off, val) => {
const bufPtr = val ? ptr(encoder.encode(val + "\x00")) : null;
pointerPacker(view, off, bufPtr);
};
unpack = (view, off) => {
const ptrVal = pointerUnpacker(view, off);
return ptrVal;
};
} else if (typeof typeOrStruct === "string" && typeOrStruct === "char*") {
size = pointerSize;
align = pointerSize;
pack = (view, off, val) => {
const bufPtr = val ? ptr(encoder.encode(val)) : null;
pointerPacker(view, off, bufPtr);
};
unpack = (view, off) => {
const ptrVal = pointerUnpacker(view, off);
return ptrVal;
};
} else if (isEnum(typeOrStruct)) {
const base = typeOrStruct.type;
size = typeSizes[base];
align = typeAlignments[base];
const { pack: packEnum } = primitivePackers(base);
pack = (view, off, val) => {
const num = typeOrStruct.to(val);
packEnum(view, off, num);
};
unpack = (view, off) => {
const raw = typeGetters[base](view, off);
return typeOrStruct.from(raw);
};
} else if (isStruct(typeOrStruct)) {
if (options.asPointer === true) {
size = pointerSize;
align = pointerSize;
pack = (view, off, val, obj, options2) => {
if (!val) {
pointerPacker(view, off, null);
return;
}
const nestedBuf = typeOrStruct.pack(val, options2);
pointerPacker(view, off, ptr(nestedBuf));
};
unpack = (view, off) => {
throw new Error("Not implemented yet");
};
} else {
size = typeOrStruct.size;
align = typeOrStruct.align;
pack = (view, off, val, obj, options2) => {
const nestedBuf = typeOrStruct.pack(val, options2);
const nestedView = new Uint8Array(nestedBuf);
const dView = new Uint8Array(view.buffer);
dView.set(nestedView, off);
};
unpack = (view, off) => {
const slice = view.buffer.slice(off, off + size);
return typeOrStruct.unpack(slice);
};
}
} else if (isObjectPointerDef(typeOrStruct)) {
size = pointerSize;
align = pointerSize;
pack = (view, off, value) => {
const ptrValue = value?.ptr ?? null;
if (ptrValue === undefined) {
console.warn(`Field '${name}' expected object with '.ptr' property, but got undefined pointer value from:`, value);
pointerPacker(view, off, null);
} else {
pointerPacker(view, off, ptrValue);
}
};
unpack = (view, off) => {
return pointerUnpacker(view, off);
};
} else if (Array.isArray(typeOrStruct) && typeOrStruct.length === 1 && typeOrStruct[0] !== undefined) {
const [def] = typeOrStruct;
size = pointerSize;
align = pointerSize;
let arrayElementSize;
if (isEnum(def)) {
arrayElementSize = typeSizes[def.type];
pack = (view, off, val, obj) => {
if (!val || val.length === 0) {
pointerPacker(view, off, null);
return;
}
const buffer = new ArrayBuffer(val.length * arrayElementSize);
const bufferView = new DataView(buffer);
for (let i = 0;i < val.length; i++) {
const num = def.to(val[i]);
bufferView.setUint32(i * arrayElementSize, num, true);
}
pointerPacker(view, off, ptr(buffer));
};
unpack = null;
needsLengthOf = true;
lengthOfDef = def;
} else if (isStruct(def)) {
arrayElementSize = def.size;
pack = (view, off, val, obj, options2) => {
if (!val || val.length === 0) {
pointerPacker(view, off, null);
return;
}
const buffer = new ArrayBuffer(val.length * arrayElementSize);
const bufferView = new DataView(buffer);
for (let i = 0;i < val.length; i++) {
def.packInto(val[i], bufferView, i * arrayElementSize, options2);
}
pointerPacker(view, off, ptr(buffer));
};
unpack = (view, off) => {
throw new Error("Not implemented yet");
};
} else if (isPrimitiveType(def)) {
arrayElementSize = typeSizes[def];
const { pack: primitivePack } = primitivePackers(def);
pack = (view, off, val) => {
if (!val || val.length === 0) {
pointerPacker(view, off, null);
return;
}
const buffer = new ArrayBuffer(val.length * arrayElementSize);
const bufferView = new DataView(buffer);
for (let i = 0;i < val.length; i++) {
primitivePack(bufferView, i * arrayElementSize, val[i]);
}
pointerPacker(view, off, ptr(buffer));
};
unpack = null;
} else if (isObjectPointerDef(def)) {
arrayElementSize = pointerSize;
pack = (view, off, val) => {
if (!val || val.length === 0) {
pointerPacker(view, off, null);
return;
}
const packedView = packObjectArray(val);
pointerPacker(view, off, ptr(packedView.buffer));
};
unpack = () => {
throw new Error("not implemented yet");
};
} else {
throw new Error(`Unsupported array element type for ${name}: ${JSON.stringify(def)}`);
}
const lengthOfField = Object.values(lengthOfFields).find((f) => f.lengthOf === name);
if (lengthOfField && isPrimitiveType(lengthOfField.type)) {
const { pack: lengthPack } = primitivePackers(lengthOfField.type);
arrayFieldsMetadata[name] = {
elementSize: arrayElementSize,
arrayOffset: offset,
lengthOffset: lengthOfField.offset,
lengthPack
};
}
} else {
throw new Error(`Unsupported field type for ${name}: ${JSON.stringify(typeOrStruct)}`);
}
offset = alignOffset(offset, align);
if (options.unpackTransform) {
const originalUnpack = unpack;
unpack = (view, off) => options.unpackTransform(originalUnpack(view, off));
}
if (options.packTransform) {
const originalPack = pack;
pack = (view, off, val, obj, packOptions) => originalPack(view, off, options.packTransform(val), obj, packOptions);
}
if (options.optional) {
const originalPack = pack;
if (isStruct(typeOrStruct) && !options.asPointer) {
pack = (view, off, val, obj, packOptions) => {
if (val || options.mapOptionalInline) {
originalPack(view, off, val, obj, packOptions);
}
};
} else {
pack = (view, off, val, obj, packOptions) => originalPack(view, off, val ?? 0, obj, packOptions);
}
}
if (options.lengthOf) {
const originalPack = pack;
pack = (view, off, val, obj, packOptions) => originalPack(view, off, obj[options.lengthOf] ? obj[options.lengthOf].length : 0, obj, packOptions);
}
let validateFunctions;
if (options.validate) {
validateFunctions = Array.isArray(options.validate) ? options.validate : [options.validate];
}
const layoutField = {
name,
offset,
size,
align,
validate: validateFunctions,
optional: !!options.optional || !!options.lengthOf || options.default !== undefined,
default: options.default,
pack,
unpack,
type: typeOrStruct,
lengthOf: options.lengthOf
};
layout.push(layoutField);
if (options.lengthOf) {
lengthOfFields[options.lengthOf] = layoutField;
}
if (needsLengthOf) {
if (!lengthOfDef)
fatalError(`Internal error: needsLengthOf=true but lengthOfDef is null for ${name}`);
lengthOfRequested.push({ requester: layoutField, def: lengthOfDef });
}
offset += size;
maxAlign = Math.max(maxAlign, align);
}
for (const { requester, def } of lengthOfRequested) {
if (isPrimitiveType(def)) {
continue;
}
const lengthOfField = lengthOfFields[requester.name];
if (!lengthOfField) {
throw new Error(`lengthOf field not found for array field ${requester.name}`);
}
const elemSize = def.type === "u32" ? 4 : 8;
requester.unpack = (view, off) => {
const result = [];
const length = lengthOfField.unpack(view, lengthOfField.offset);
const ptrAddress = pointerUnpacker(view, off);
if (ptrAddress === 0n && length > 0) {
throw new Error(`Array field ${requester.name} has null pointer but length ${length}.`);
}
if (ptrAddress === 0n || length === 0) {
return [];
}
const buffer = toArra