UNPKG

bun-webgpu

Version:

Native WebGPU implementation for Bun runtime

1,651 lines (1,633 loc) 185 kB
// @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