(function() { /** * Assumptions: * - Ability to allocate memory, set the context to allocate with using the global `wgpu.g_context` * - Exports a function table (for callbacks), added with `-extra-linker-flags:"--export-table"` */ class WebGPUInterface { /** * @param {WasmMemoryInterface} mem */ constructor(mem) { this.mem = mem; this.enums = { FeatureName: [undefined, "depth-clip-control", "depth32float-stencil8", "timestamp-query", "texture-compression-bc", "texture-compression-etc2", "texture-compression-astc", "indirect-first-instance", "shader-f16", "rg11b10ufloat-renderable", "bgra8unorm-storage", "float32-filterable", ], StoreOp: [undefined, "store", "discard", ], LoadOp: [undefined, "clear", "load", ], BufferBindingType: [undefined, "uniform", "storage", "read-only-storage", ], SamplerBindingType: [undefined, "filtering", "non-filtering", "comparison", ], TextureSampleType: [undefined, "float", "unfilterable-float", "depth", "sint", "uint", ], TextureViewDimension: [undefined, "1d", "2d", "2d-array", "cube", "cube-array", "3d", ], StorageTextureAccess: [undefined, "write-only", "read-only", "read-write", ], TextureFormat: [undefined, "r8unorm", "r8snorm", "r8uint", "r8sint", "r16uint", "r16sint", "r16float", "rg8unorm", "rg8snorm", "rg8uint", "rg8sint", "r32float", "r32uint", "r32sint", "rg16uint", "rg16sint", "rg16float", "rgba8unorm", "rgba8unorm-srgb", "rgba8snorm", "rgba8uint", "rgba8sint", "bgra8unorm", "bgra8unorm-srgb", "rgb10a2uint", "rgb10a2unorm", "rg11b10ufloat", "rgb9e5ufloat", "rg32float", "rg32uint", "rg32sint", "rgba16uint", "rgba16sint", "rgba16float", "rgba32float", "rgba32uint", "rgba32sint", "stencil8", "depth16unorm", "depth24plus", "depth24plus-stencil8", "depth32float", "depth32float-stencil8", "bc1-rgba-unorm", "bc1-rgba-unorm-srgb", "bc2-rgba-unorm", "bc2-rgba-unorm-srgb", "bc3-rgba-unorm", "bc3-rgba-unorm-srgb", "bc4-r-unorm", "bc4-r-snorm", "bc5-rg-unorm", "bc5-rg-snorm", "bc6h-rgb-ufloat", "bc6h-rgb-float", "bc7-rgba-unorm", "bc7-rgba-unorm-srgb", "etc2-rgb8unorm", "etc2-rgb8unorm-srgb", "etc2-rgb8a1unorm", "etc2-rgb8a1unorm-srgb", "etc2-rgba8unorm", "etc2-rgba8unorm-srgb", "eac-r11unorm", "eac-r11snorm", "eac-rg11unorm", "eac-rg11snorm", "astc-4x4-unorm", "astc-4x4-unorm-srgb", "astc-5x4-unorm", "astc-5x4-unorm-srgb", "astc-5x5-unorm", "astc-5x5-unorm-srgb", "astc-6x5-unorm", "astc-6x5-unorm-srgb", "astc-6x6-unorm", "astc-6x6-unorm-srgb", "astc-8x5-unorm", "astc-8x5-unorm-srgb", "astc-8x6-unorm", "astc-8x6-unorm-srgb", "astc-8x8-unorm", "astc-8x8-unorm-srgb", "astc-10x5-unorm", "astc-10x5-unorm-srgb", "astc-10x6-unorm", "astc-10x6-unorm-srgb", "astc-10x8-unorm", "astc-10x8-unorm-srgb", "astc-10x10-unorm", "astc-10x10-unorm-srgb", "astc-12x10-unorm", "astc-12x10-unorm-srgb", "astc-12x12-unorm", "astc-12x12-unorm-srgb", ], QueryType: ["occlusion", "timestamp", ], VertexStepMode: ["vertex", "instance", "vertex-buffer-not-used", ], VertexFormat: [undefined, "uint8x2", "uint8x4", "sint8x2", "sint8x4", "unorm8x2", "unorm8x4", "snorm8x2", "snorm8x4", "uint16x2", "uint16x4", "sint16x2", "sint16x4", "unorm16x2", "unorm16x4", "snorm16x2", "snorm16x4", "float16x2", "float16x4", "float32", "float32x2", "float32x3", "float32x4", "uint32", "uint32x2", "uint32x3", "uint32x4", "sint32", "sint32x2", "sint32x3", "sint32x4", ], PrimitiveTopology: ["point-list", "line-list", "line-strip", "triangle-list", "triangle-strip", ], IndexFormat: [undefined, "uint16", "uint32", ], FrontFace: ["ccw", "cw", ], CullMode: ["none", "front", "back", ], AddressMode: ["repeat", "mirror-repeat", "clamp-to-edge", ], FilterMode: ["nearest", "linear", ], MipmapFilterMode: ["nearest", "linear", ], CompareFunction: [undefined, "never", "less", "less-equal", "greater", "greater-equal", "equal", "not-equal", "always", ], TextureDimension: ["1d", "2d", "3d", ], ErrorType: ["no-error", "validation", "out-of-memory", "internal", "unknown", "device-lost", ], WGSLFeatureName: [undefined, "readonly_and_readwrite_storage_textures", "packed_4x8_integer_dot_product", "unrestricted_pointer_parameters", "pointer_composite_access", ], PowerPreference: [undefined, "low-power", "high-performance", ], CompositeAlphaMode: ["auto", "opaque", "premultiplied", "unpremultiplied", "inherit", ], StencilOperation: ["keep", "zero", "replace", "invert", "increment-clamp", "decrement-clamp", "increment-wrap", "decrement-wrap", ], BlendOperation: ["add", "subtract", "reverse-subtract", "min", "max", ], BlendFactor: ["zero", "one", "src", "one-minus-src", "src-alpha", "one-minus-src-alpha", "dst", "one-minus-dst", "dst-alpha", "one-minus-dst-alpha", "src-alpha-saturated", "constant", "one-minus-constant", ], PresentMode: ["fifo", "fifo-relaxed", "immediate", "mailbox", ], TextureAspect: ["all", "stencil-only", "depth-only"], DeviceLostReason: [undefined, "unknown", "destroyed"], }; /** @type {WebGPUObjectManager<{}>} */ this.instances = new WebGPUObjectManager("Instance", this.mem); /** @type {WebGPUObjectManager} */ this.adapters = new WebGPUObjectManager("Adapter", this.mem); /** @type {WebGPUObjectManager} */ this.bindGroups = new WebGPUObjectManager("BindGroup", this.mem); /** @type {WebGPUObjectManager} */ this.bindGroupLayouts = new WebGPUObjectManager("BindGroupLayout", this.mem); /** @type {WebGPUObjectManager<{ buffer: GPUBuffer, mapping: ?{ range: ArrayBuffer, ptr: number, size: number } }>} */ this.buffers = new WebGPUObjectManager("Buffer", this.mem); /** @type {WebGPUObjectManager} */ this.devices = new WebGPUObjectManager("Device", this.mem); /** @type {WebGPUObjectManager} */ this.commandBuffers = new WebGPUObjectManager("CommandBuffer", this.mem); /** @type {WebGPUObjectManager} */ this.commandEncoders = new WebGPUObjectManager("CommandEncoder", this.mem); /** @type {WebGPUObjectManager} */ this.computePassEncoders = new WebGPUObjectManager("ComputePassEncoder", this.mem); /** @type {WebGPUObjectManager} */ this.renderPassEncoders = new WebGPUObjectManager("RenderPassEncoder", this.mem); /** @type {WebGPUObjectManager} */ this.querySets = new WebGPUObjectManager("QuerySet", this.mem); /** @type {WebGPUObjectManager} */ this.computePipelines = new WebGPUObjectManager("ComputePipeline", this.mem); /** @type {WebGPUObjectManager} */ this.pipelineLayouts = new WebGPUObjectManager("PipelineLayout", this.mem); /** @type {WebGPUObjectManager} */ this.queues = new WebGPUObjectManager("Queue", this.mem); /** @type {WebGPUObjectManager} */ this.renderBundles = new WebGPUObjectManager("RenderBundle", this.mem); /** @type {WebGPUObjectManager} */ this.renderBundleEncoders = new WebGPUObjectManager("RenderBundleEncoder", this.mem); /** @type {WebGPUObjectManager} */ this.renderPipelines = new WebGPUObjectManager("RenderPipeline", this.mem); /** @type {WebGPUObjectManager} */ this.samplers = new WebGPUObjectManager("Sampler", this.mem); /** @type {WebGPUObjectManager} */ this.shaderModules = new WebGPUObjectManager("ShaderModule", this.mem); /** @type {WebGPUObjectManager} */ this.surfaces = new WebGPUObjectManager("Surface", this.mem); /** @type {WebGPUObjectManager} */ this.textures = new WebGPUObjectManager("Texture", this.mem); /** @type {WebGPUObjectManager} */ this.textureViews = new WebGPUObjectManager("TextureView", this.mem); } /** * @param {number|BigInt} src * @returns {number|BigInt} */ uint(src) { if (this.mem.intSize == 8) { return BigInt(src); } else if (this.mem.intSize == 4) { return src; } else { throw new Error("unreachable"); } } /** * @param {number|BigInt} src * @returns {number} */ unwrapBigInt(src) { if (typeof src == "number") { return src; } const MAX_SAFE_INTEGER = 9007199254740991n; if (typeof src != "bigint") { throw new TypeError(`unwrapBigInt got invalid param of type ${typeof src}`); } if (src > MAX_SAFE_INTEGER) { throw new Error(`unwrapBigInt precision would be lost converting ${src}`); } return Number(src); } /** * @param {boolean} condition * @param {string} message */ assert(condition, message = "assertion failure") { if (!condition) { throw new Error(message); } } /** * @template T * * @param {number} count * @param {number} start * @param {function(number): T} decoder * @param {number} stride * @returns {Array} */ array(count, start, decoder, stride) { if (count == 0) { return []; } this.assert(start != 0); const out = []; for (let i = 0; i < count; i += 1) { out.push(decoder.call(this, start)); start += stride; } return out; } /** * @param {string} name * @param {number} ptr * @returns {`GPU${name}`} */ enumeration(name, ptr) { const int = this.mem.loadI32(ptr); this.assert(this.enums[name], `Unknown enumeration "${name}"`); return this.enums[name][int]; } /** * @param {GPUSupportedFeatures} features * @param {number} ptr * @returns {BigInt|number} */ genericEnumerateFeatures(features, ptr) { const availableFeatures = []; this.enums.FeatureName.forEach((feature, value) => { if (!feature) { return; } if (features.has(feature)) { availableFeatures.push(value); } }); if (ptr != 0) { for (let i = 0; i < availableFeatures.length; i += 1) { this.mem.storeI32(ptr + (i * 4), availableFeatures[i]); } } return this.uint(availableFeatures.length); } /** * @param {GPUSupportedLimits} limits * @param {number} ptr */ genericGetLimits(limits, supportedLimitsPtr) { this.assert(supportedLimitsPtr != 0); const limitsPtr = supportedLimitsPtr + 8; this.mem.storeU32(limitsPtr + 0, limits.maxTextureDimension1D); this.mem.storeU32(limitsPtr + 4, limits.maxTextureDimension2D); this.mem.storeU32(limitsPtr + 8, limits.maxTextureDimension3D); this.mem.storeU32(limitsPtr + 12, limits.maxTextureArrayLayers); this.mem.storeU32(limitsPtr + 16, limits.maxBindGroups); this.mem.storeU32(limitsPtr + 20, limits.maxBindGroupsPlusVertexBuffers); this.mem.storeU32(limitsPtr + 24, limits.maxBindingsPerBindGroup); this.mem.storeU32(limitsPtr + 28, limits.maxDynamicUniformBuffersPerPipelineLayout); this.mem.storeU32(limitsPtr + 32, limits.maxDynamicStorageBuffersPerPipelineLayout); this.mem.storeU32(limitsPtr + 36, limits.maxSampledTexturesPerShaderStage); this.mem.storeU32(limitsPtr + 40, limits.maxSamplersPerShaderStage); this.mem.storeU32(limitsPtr + 44, limits.maxStorageBuffersPerShaderStage); this.mem.storeU32(limitsPtr + 48, limits.maxStorageTexturesPerShaderStage); this.mem.storeU32(limitsPtr + 52, limits.maxUniformBuffersPerShaderStage); this.mem.storeU64(limitsPtr + 56, limits.maxUniformBufferBindingSize); this.mem.storeU64(limitsPtr + 64, limits.maxStorageBufferBindingSize); this.mem.storeU32(limitsPtr + 72, limits.minUniformBufferOffsetAlignment); this.mem.storeU32(limitsPtr + 76, limits.minStorageBufferOffsetAlignment); this.mem.storeU32(limitsPtr + 80, limits.maxVertexBuffers); this.mem.storeU64(limitsPtr + 88, limits.maxBufferSize); this.mem.storeU32(limitsPtr + 96, limits.maxVertexAttributes); this.mem.storeU32(limitsPtr + 100, limits.maxVertexBufferArrayStride); this.mem.storeU32(limitsPtr + 104, limits.maxInterStageShaderComponents); this.mem.storeU32(limitsPtr + 108, limits.maxInterStageShaderVariables); this.mem.storeU32(limitsPtr + 112, limits.maxColorAttachments); this.mem.storeU32(limitsPtr + 116, limits.maxColorAttachmentBytesPerSample); this.mem.storeU32(limitsPtr + 120, limits.maxComputeWorkgroupStorageSize); this.mem.storeU32(limitsPtr + 124, limits.maxComputeInvocationsPerWorkgroup); this.mem.storeU32(limitsPtr + 128, limits.maxComputeWorkgroupSizeX); this.mem.storeU32(limitsPtr + 132, limits.maxComputeWorkgroupSizeY); this.mem.storeU32(limitsPtr + 136, limits.maxComputeWorkgroupSizeZ); this.mem.storeU32(limitsPtr + 140, limits.maxComputeWorkgroupsPerDimension); return true; } /** * @param {number} ptr * @returns {GPUFeatureName} */ FeatureNamePtr(ptr) { return this.FeatureName(this.mem.loadI32(ptr)); } /** * @param {number} featureInt * @returns {GPUFeatureName} */ FeatureName(featureInt) { return this.enums.FeatureName[featureInt]; } /** * @param {number} ptr * @returns {GPUSupportedLimits} */ RequiredLimitsPtr(ptr) { const start = this.mem.loadPtr(ptr); if (start == 0) { return undefined; } return this.Limits(start + 8); } /** * @param {number} start * @return {GPUSupportedLimits} */ Limits(start) { const limitU32 = (ptr) => { const value = this.mem.loadU32(ptr); if (value == 0xFFFFFFFF) { // LIMIT_32_UNDEFINED. return undefined; } return value; }; const limitU64 = (ptr) => { const part1 = this.mem.loadU32(ptr); const part2 = this.mem.loadU32(ptr + 4); if (part1 != 0xFFFFFFFF || part2 != 0xFFFFFFFF) { // LIMIT_64_UNDEFINED. return this.mem.loadU64(ptr); } return undefined; }; return { maxTextureDimension1D: limitU32(start + 0), maxTextureDimension2D: limitU32(start + 4), maxTextureDimension3D: limitU32(start + 8), maxTextureArrayLayers: limitU32(start + 12), maxBindGroups: limitU32(start + 16), maxBindGroupsPlusVertexBuffers: limitU32(start + 20), maxBindingsPerBindGroup: limitU32(start + 24), maxDynamicUniformBuffersPerPipelineLayout: limitU32(start + 28), maxDynamicStorageBuffersPerPipelineLayout: limitU32(start + 32), maxSampledTexturesPerShaderStage: limitU32(start + 36), maxSamplersPerShaderStage: limitU32(start + 40), maxStorageBuffersPerShaderStage: limitU32(start + 44), maxStorageTexturesPerShaderStage: limitU32(start + 48), maxUniformBuffersPerShaderStage: limitU32(start + 52), maxUniformBufferBindingSize: limitU64(start + 56), maxStorageBufferBindingSize: limitU64(start + 64), minUniformBufferOffsetAlignment: limitU32(start + 72), minStorageBufferOffsetAlignment: limitU32(start + 76), maxVertexBuffers: limitU32(start + 80), maxBufferSize: limitU64(start + 88), maxVertexAttributes: limitU32(start + 96), maxVertexBufferArrayStride: limitU32(start + 100), maxInterStageShaderComponents: limitU32(start + 104), maxInterStageShaderVariables: limitU32(start + 108), maxColorAttachments: limitU32(start + 112), maxColorAttachmentBytesPerSample: limitU32(start + 116), maxComputeWorkgroupStorageSize: limitU32(start + 120), maxComputeInvocationsPerWorkgroup: limitU32(start + 124), maxComputeWorkgroupSizeX: limitU32(start + 128), maxComputeWorkgroupSizeY: limitU32(start + 132), maxComputeWorkgroupSizeZ: limitU32(start + 136), maxComputeWorkgroupsPerDimension: limitU32(start + 140), }; } /** * @param {number} start * @returns {GPUQueueDescriptor} */ QueueDescriptor(start) { return { label: this.mem.loadCstring(start + 4), }; } /** * @param {number} ptr * @returns {GPUComputePassTimestampWrites} */ ComputePassTimestampWritesPtr(ptr) { const start = this.mem.loadPtr(ptr); if (start == 0) { return undefined; } return { querySet: this.querySets.get(this.mem.loadPtr(start + 0)), beginningOfPassWriteIndex: this.mem.loadU32(start + 4), endOfPassWriteIndex: this.mem.loadU32(start + 8), }; } /** * @param {number} start * @returns {GPURenderPassColorAttachment} */ RenderPassColorAttachment(start) { const viewIdx = this.mem.loadPtr(start + 4); const resolveTargetIdx = this.mem.loadPtr(start + 12); let depthSlice = this.mem.loadU32(start + 8); if (depthSlice == 0xFFFFFFFF) { // DEPTH_SLICE_UNDEFINED. depthSlice = undefined; } return { view: viewIdx > 0 ? this.textureViews.get(viewIdx) : undefined, resolveTarget: resolveTargetIdx > 0 ? this.textureViews.get(resolveTargetIdx) : undefined, depthSlice: depthSlice, loadOp: this.enumeration("LoadOp", start + 16), storeOp: this.enumeration("StoreOp", start + 20), clearValue: this.Color(start + 24), }; } /** * @param {number} start * @returns {GPUColor} */ Color(start) { return { r: this.mem.loadF64(start + 0), g: this.mem.loadF64(start + 8), b: this.mem.loadF64(start + 16), a: this.mem.loadF64(start + 24), }; } /** * @param {number} ptr * @returns {GPURenderPassDepthStencilAttachment} */ RenderPassDepthStencilAttachmentPtr(ptr) { const start = this.mem.loadPtr(ptr); if (start == 0) { return undefined; } return { view: this.textureViews.get(this.mem.loadPtr(start + 0)), depthLoadOp: this.enumeration("LoadOp", start + 4), depthStoreOp: this.enumeration("StoreOp", start + 8), depthClearValue: this.mem.loadF32(start + 12), depthReadOnly: this.mem.loadB32(start + 16), stencilLoadOp: this.enumeration("LoadOp", start + 20), stencilStoreOp: this.enumeration("StoreOp", start + 24), stencilClearValue: this.mem.loadF32(start + 28), stencilReadOnly: this.mem.loadB32(start + 32), }; } /** * @param {number} ptr * @returns {undefined|GPUQuerySet} */ QuerySet(ptr) { ptr = this.mem.loadPtr(ptr); if (ptr == 0) { return undefined; } return this.querySets.get(ptr); } /** * @param {number} ptr * @returns {GPURenderPassTimestampWrites} */ RenderPassTimestampWritesPtr(ptr) { return this.ComputePassTimestampWritesPtr(ptr); } /** * @param {number} start * @returns {GPUImageDataLayout} */ TextureDataLayout(start) { return { offset: this.mem.loadU64(start + 8), bytesPerRow: this.mem.loadU32(start + 16), rowsPerImage: this.mem.loadU32(start + 20), }; } /** * @param {number} start * @returns {GPUImageCopyBuffer} */ ImageCopyBuffer(start) { return { ...this.TextureDataLayout(start + 8), buffer: this.buffers.get(this.mem.loadPtr(start + 32)).buffer, }; } /** * @param {number} start * @returns {GPUImageCopyTexture} */ ImageCopyTexture(start) { return { texture: this.textures.get(this.mem.loadPtr(start + 4)), mipLevel: this.mem.loadU32(start + 8), origin: this.Origin3D(start + 12), aspect: this.enumeration("TextureAspect", start + 24), }; } /** * @param {number} start * @returns {GPUOrigin3D} */ Origin3D(start) { return { x: this.mem.loadU32(start + 0), y: this.mem.loadU32(start + 4), z: this.mem.loadU32(start + 8), }; } /** * @param {number} start * @returns {GPUExtent3D} */ Extent3D(start) { return { width: this.mem.loadU32(start + 0), height: this.mem.loadU32(start + 4), depthOrArrayLayers: this.mem.loadU32(start + 8), }; } /** * @param {number} start * @returns {GPUBindGroupEntry} */ BindGroupEntry(start) { const buffer = this.mem.loadPtr(start + 8); const sampler = this.mem.loadPtr(start + 32); const textureView = this.mem.loadPtr(start + 36); /** @type {GPUBindingResource} */ let resource; if (buffer > 0) { resource = { buffer: this.buffers.get(buffer).buffer, offset: this.mem.loadU64(start + 16), size: this.mem.loadU64(start + 24), } } else if (sampler > 0) { resource = this.samplers.get(sampler); } else if (textureView > 0) { resource = this.textureViews.get(textureView); } return { binding: this.mem.loadU32(start + 4), resource: resource, }; } /** * @param {number} start * @returns {GPUBindGroupLayoutEntry} */ BindGroupLayoutEntry(start) { const entry = { binding: this.mem.loadU32(start + 4), visibility: this.mem.loadU32(start + 8), buffer: this.BufferBindingLayout(start + 16), sampler: this.SamplerBindingLayout(start + 40), texture: this.TextureBindingLayout(start + 48), storageTexture: this.StorageTextureBindingLayout(start + 64), }; if (!entry.buffer.type) { entry.buffer = undefined; } if (!entry.sampler.type) { entry.sampler = undefined; } if (!entry.texture.sampleType) { entry.texture = undefined; } if (!entry.storageTexture.access) { entry.storageTexture = undefined; } return entry; } /** * @param {number} start * @returns {GPUBufferBindingLayout} */ BufferBindingLayout(start) { return { type: this.enumeration("BufferBindingType", start + 4), hasDynamicOffset: this.mem.loadB32(start + 8), minBindingSize: this.mem.loadU64(start + 16), }; } /** * @param {number} start * @returns {GPUSamplerBindingLayout} */ SamplerBindingLayout(start) { return { type: this.enumeration("SamplerBindingType", start + 4), }; } /** * @param {number} start * @returns {GPUTextureBindingLayout} */ TextureBindingLayout(start) { return { sampleType: this.enumeration("TextureSampleType", start + 4), viewDimension: this.enumeration("TextureViewDimension", start + 8), multisampled: this.mem.loadB32(start + 12), }; } /** * @param {number} start * @returns {GPUStorageTextureBindingLayout} */ StorageTextureBindingLayout(start) { return { access: this.enumeration("StorageTextureAccess", start + 4), format: this.enumeration("TextureFormat", start + 8), viewDimension: this.enumeration("TextureViewDimension", start + 12), }; } /** * @param {number} start * @returns {GPUProgrammableStage} */ ProgrammableStageDescriptor(start) { const constantsArray = this.array( this.mem.loadUint(start + 8 + this.mem.intSize), this.mem.loadPtr(start + 8 + this.mem.intSize*2), this.ConstantEntry, 16, ); return { module: this.shaderModules.get(this.mem.loadPtr(start + 4)), entryPoint: this.mem.loadCstring(start + 8), constants: constantsArray.reduce((prev, curr) => { prev[curr.key] = curr.value; return prev; }, {}), }; } /** * @param {number} start * @returns {{ key: string, value: number }} */ ConstantEntry(start) { return { key: this.mem.loadCstring(start + 4), value: this.mem.loadF64(start + 8), }; } /** * @param {number} start * @returns {GPUComputePipelineDescriptor} */ ComputePipelineDescriptor(start) { const layoutIdx = this.mem.loadPtr(start + 8) return { label: this.mem.loadCstring(start + 4), layout: layoutIdx > 0 ? this.pipelineLayouts.get(layoutIdx) : undefined, compute: this.ProgrammableStageDescriptor(start + 8 + this.mem.intSize), }; } /** * @param {number} start * @returns {GPUVertexState} */ VertexState(start) { let off = 8 + this.mem.intSize; const constantsArray = this.array( this.mem.loadUint(start + off), this.mem.loadPtr(start + off + this.mem.intSize), this.ConstantEntry, 16, ); off += this.mem.intSize * 2; return { module: this.shaderModules.get(this.mem.loadPtr(start + 4)), entryPoint: this.mem.loadCstring(start + 8), constants: constantsArray.reduce((prev, curr) => { prev[curr.key] = curr.value; return prev; }, {}), buffers: this.array( this.mem.loadUint(start + off), this.mem.loadPtr(start + off + this.mem.intSize), this.VertexBufferLayout, this.mem.intSize == 8 ? 32 : 24, ), }; } /** * @param {number} start * @returns {?GPUVertexBufferLayout} */ VertexBufferLayout(start) { const stepMode = this.enumeration("VertexStepMode", start + 8); if (stepMode == "vertex-buffer-not-used") { return null; } return { arrayStride: this.mem.loadU64(start + 0), stepMode: stepMode, attributes: this.array( this.mem.loadUint(start + 8 + this.mem.intSize), this.mem.loadPtr(start + 8 + this.mem.intSize*2), this.VertexAttribute, 24, ), }; } /** * @param {number} start * @returns {GPUVertexAttribute} */ VertexAttribute(start) { return { format: this.enumeration("VertexFormat", start + 0), offset: this.mem.loadU64(start + 8), shaderLocation: this.mem.loadU32(start + 16), }; } /** * @param {number} start * @returns {GPUPrimitiveState} */ PrimitiveState(start) { let unclippedDepth = undefined; const nextInChain = this.mem.loadPtr(start); if (nextInChain != 0) { const nextInChainType = this.mem.loadI32(nextInChain + 4); // PrimitiveDepthClipControl = 0x00000007, if (nextInChainType == 7) { unclippedDepth = this.mem.loadB32(nextInChain + 8); } } return { topology: this.enumeration("PrimitiveTopology", start + 4), stripIndexFormat: this.enumeration("IndexFormat", start + 8), frontFace: this.enumeration("FrontFace", start + 12), cullMode: this.enumeration("CullMode", start + 16), unclippedDepth: unclippedDepth, }; } /** * @param {number} start * @returns {GPURenderPipelineDescriptor} */ RenderPipelineDescriptor(start) { const layoutIdx = this.mem.loadPtr(start + 8); const offs = this.mem.intSize == 8 ? [64, 84, 88, 104] : [40, 60, 64, 80]; return { label: this.mem.loadCstring(start + 4), layout: layoutIdx > 0 ? this.pipelineLayouts.get(layoutIdx) : undefined, vertex: this.VertexState(start + 8 + this.mem.intSize), primitive: this.PrimitiveState(start + offs[0]), depthStencil: this.DepthStencilStatePtr(start + offs[1]), multisample: this.MultisampleState(start + offs[2]), fragment: this.FragmentStatePtr(start + offs[3]), }; } /** * @param {number} start * @returns {GPUShaderModuleCompilationHint} */ ShaderModuleCompilationHint(start) { return { entryPoint: this.mem.loadCstring(start + 4), layout: this.pipelineLayouts.get(this.mem.loadPtr(start + 8)), }; } /** * @param {number} ptr * @returns {?GPUDepthStencilState} */ DepthStencilStatePtr(ptr) { const start = this.mem.loadPtr(ptr); if (start == 0) { return undefined; } return { format: this.enumeration("TextureFormat", start + 4), depthWriteEnabled: this.mem.loadB32(start + 8), depthCompare: this.enumeration("CompareFunction", start + 12), stencilFront: this.StencilFaceState(start + 16), stencilBack: this.StencilFaceState(start + 32), stencilReadMask: this.mem.loadU32(start + 48), stencilWriteMask: this.mem.loadU32(start + 52), depthBias: this.mem.loadI32(start + 56), depthBiasSlopeScale: this.mem.loadF32(start + 60), depthBiasClamp: this.mem.loadF32(start + 64), }; } /** * @param {number} start * @returns {GPUStencilFaceState} */ StencilFaceState(start) { return { compare: this.enumeration("CompareFunction", start + 0), failOp: this.enumeration("StencilOperation", start + 4), depthFailOp: this.enumeration("StencilOperation", start + 8), passOp: this.enumeration("StencilOperation", start + 12), }; } /** * @param {number} start * @returns {GPUMultisampleState} */ MultisampleState(start) { return { count: this.mem.loadU32(start + 4), mask: this.mem.loadU32(start + 8), alphaToCoverageEnabled: this.mem.loadB32(start + 12), }; } /** * @param {number} ptr * @returns {?GPUFragmentState} */ FragmentStatePtr(ptr) { const start = this.mem.loadPtr(ptr); if (start == 0) { return undefined; } let off = 8 + this.mem.intSize; const constantsArray = this.array( this.mem.loadUint(start + off), this.mem.loadPtr(start + off + this.mem.intSize), this.ConstantEntry, 16, ); off += this.mem.intSize * 2; return { module: this.shaderModules.get(this.mem.loadPtr(start + 4)), entryPoint: this.mem.loadCstring(start + 8), constants: constantsArray.reduce((prev, curr) => { prev[curr.key] = curr.value; return prev; }, {}), targets: this.array( this.mem.loadUint(start + off), this.mem.loadPtr(start + off + this.mem.intSize), this.ColorTargetState, 16, ), }; } /** * @param {number} start * @returns {GPUColorTargetState} */ ColorTargetState(start) { return { format: this.enumeration("TextureFormat", start + 4), blend: this.BlendStatePtr(start + 8), writeMask: this.mem.loadU32(start + 12), }; } /** * @param {number} ptr * @returns {?GPUBlendState} */ BlendStatePtr(ptr) { const start = this.mem.loadPtr(ptr); if (start == 0) { return undefined; } return { color: this.BlendComponent(start + 0), alpha: this.BlendComponent(start + 12), }; } /** * @param {number} start * @returns {?GPUBlendComponent} */ BlendComponent(start) { return { operation: this.enumeration("BlendOperation", start + 0), srcFactor: this.enumeration("BlendFactor", start + 4), dstFactor: this.enumeration("BlendFactor", start + 8), }; } getInterface() { return { /** * @param {0|number} descriptorPtr * @returns {number} */ wgpuCreateInstance: (descriptorPtr) => { if (!navigator.gpu) { console.error("WebGPU is not supported by this browser"); return 0; } return this.instances.create({}); }, /** * @param {number} deviceIdx * @param {number} procNamePtr * @returns {number} */ wgpuGetProcAddress: (deviceIdx, procNamePtr) => { console.error(`unimplemented: wgpuGetProcAddress`); return 0; }, /* ---------------------- Adapter ---------------------- */ /** * @param {number} adapterIdx * @param {number} featuresPtr * @returns {number|BigInt} */ wgpuAdapterEnumerateFeatures: (adapterIdx, featuresPtr) => { const adapter = this.adapters.get(adapterIdx); return this.genericEnumerateFeatures(adapter.features, featuresPtr); }, /** * @param {number} adapterIdx * @param {number} supportedLimitsPtr * @returns {boolean} */ wgpuAdapterGetLimits: (adapterIdx, supportedLimitsPtr) => { const adapter = this.adapters.get(adapterIdx); return this.genericGetLimits(adapter.limits, supportedLimitsPtr); }, /** * @param {number} adapterIdx * @param {number} infoPtr */ wgpuAdapterGetInfo: (adapterIdx, infoPtr) => { this.assert(infoPtr != 0); // WebGPU backend. this.mem.storeI32(infoPtr + 20, 2); // Unknown adapter. this.mem.storeI32(infoPtr + 24, 3); // NOTE: I don't think getting the other fields in this struct is possible. // `adapter.requestAdapterInfo` is deprecated. }, /** * @param {number} infoPtr */ wgpuAdapterInfoFreeMembers: (infoPtr) => { // NOTE: nothing to free. }, /** * @param {number} adapterIdx * @param {number} featureInt * @returns {boolean} */ wgpuAdapterHasFeature: (adapterIdx, featureInt) => { const adapter = this.adapters.get(adapterIdx); return adapter.features.has(this.enums.FeatureName[featureInt]); }, /** * @param {number} adapterIdx * @param {0|number} descriptorPtr * @param {number} callbackPtr * @param {0|number} userdata */ wgpuAdapterRequestDevice: async (adapterIdx, descriptorPtr, callbackPtr, userdata) => { const adapter = this.adapters.get(adapterIdx); const callback = this.mem.exports.__indirect_function_table.get(callbackPtr); /** @type {GPUDeviceDescriptor} */ let descriptor; if (descriptorPtr != 0) { descriptor = { label: this.mem.loadCstring(descriptorPtr + 4), requiredFeatures: this.array( this.mem.loadUint(descriptorPtr + 8), this.mem.loadPtr(descriptorPtr + 8 + this.mem.intSize), this.FeatureNamePtr, 4, ), requiredLimits: this.RequiredLimitsPtr(descriptorPtr + 8 + this.mem.intSize + 4), defaultQueue: this.QueueDescriptor( descriptorPtr + 8 + this.mem.intSize + 4 + 4), }; } let device; let deviceIdx; try { device = await adapter.requestDevice(descriptor); deviceIdx = this.devices.create(device); // NOTE: don't callback here, any errors that happen later will then be caught by the catch here. } catch (e) { const messageLength = new TextEncoder().encode(e.message).length; const messageAddr = this.mem.exports.wgpu_alloc(messageLength + 1); this.mem.storeString(messageAddr, e.message); callback(1, null, messageAddr, userdata); this.mem.exports.wgpu_free(messageAddr); } let callbacksPtr = descriptorPtr + 24 + this.mem.intSize; const deviceLostCallbackPtr = this.mem.loadPtr(callbacksPtr); if (deviceLostCallbackPtr != 0) { const deviceLostUserData = this.mem.loadPtr(callbacksPtr) + 4; const deviceLostCallback = this.mem.exports.__indirect_function_table.get(deviceLostCallbackPtr); device.lost.then((info) => { const reason = this.enums.DeviceLostReason.indexOf(info.reason); const messageLength = new TextEncoder().encode(info.message).length; const messageAddr = this.mem.exports.wgpu_alloc(messageLength + 1); this.mem.storeString(messageAddr, info.message); deviceLostCallback(reason, messageAddr, deviceLostUserData); this.mem.exports.wgpu_free(messageAddr); }); } callbacksPtr += 8; // Skip over `nextInChain`. callbacksPtr += 4; const uncapturedErrorCallbackPtr = this.mem.loadPtr(callbacksPtr); if (uncapturedErrorCallbackPtr != 0) { const uncapturedErrorUserData = this.mem.loadPtr(callbacksPtr + 4); const uncapturedErrorCallback = this.mem.exports.__indirect_function_table.get(uncapturedErrorCallbackPtr); device.onuncapturederror = (ev) => { let status = 4; // Unknown if (ev.error instanceof GPUValidationError) { status = 1; // Validation } else if (ev.error instanceof GPUOutOfMemoryError) { status = 2; // OutOfMemory } else if (ev.error instanceof GPUInternalError) { status = 3; // Internal } const messageLength = new TextEncoder().encode(ev.error.message).length; const messageAddr = this.mem.exports.wgpu_alloc(messageLength + 1); this.mem.storeString(messageAddr, ev.error.message); uncapturedErrorCallback(status, messageAddr, uncapturedErrorUserData); this.mem.exports.wgpu_free(messageAddr); }; } callback(0, deviceIdx, null, userdata); }, ...this.adapters.interface(), /* ---------------------- BindGroup ---------------------- */ ...this.bindGroups.interface(true), /* ---------------------- BindGroupLayout ---------------------- */ ...this.bindGroupLayouts.interface(true), /* ---------------------- Buffer ---------------------- */ /** @param {number} bufferIdx */ wgpuBufferDestroy: (bufferIdx) => { const buffer = this.buffers.get(bufferIdx); buffer.buffer.destroy(); }, /** * @param {number} bufferIdx * @param {number|BigInt} offset * @param {number|BigInt} size * @returns {number} */ wgpuBufferGetMappedRange: (bufferIdx, offset, size) => { const buffer = this.buffers.get(bufferIdx); offset = this.unwrapBigInt(offset); size = this.unwrapBigInt(size); this.assert(!buffer.mapping, "buffer already mapped"); const range = buffer.buffer.getMappedRange(offset, size); const ptr = this.mem.exports.wgpu_alloc(range.byteLength); buffer.mapping = { range: range, ptr: ptr, size: range.byteLength }; return ptr; }, /** * @param {number} bufferIdx * @returns {BigInt} */ wgpuBufferGetSize: (bufferIdx) => { const buffer = this.buffers.get(bufferIdx); return BigInt(buffer.buffer.size); }, /** * @param {number} bufferIdx * @returns {number} */ wgpuBufferGetUsage: (bufferIdx) => { const buffer = this.buffers.get(bufferIdx); return buffer.buffer.usage; }, /** * @param {number} bufferIdx * @param {number} mode * @param {number|BigInt} offset * @param {number|BigInt} size * @param {number} callbackPtr * @param {0|number} userdata */ wgpuBufferMapAsync: async (bufferIdx, mode, offset, size, callbackPtr, userdata) => { const buffer = this.buffers.get(bufferIdx); const callback = this.mem.exports.__indirect_function_table.get(callbackPtr); offset = this.unwrapBigInt(offset); size = this.unwrapBigInt(size); if (buffer.buffer.mapState == "pending") { callback(this.enums.BufferMapAsyncStatus.MappingAlreadyPending, userdata); } else { let result; try { await buffer.buffer.mapAsync(mode, offset, size); result = 0; // Success. } catch(e) { console.warn(e); result = 2; // Unknown error. if (e instanceof DomException) { if (e.name == "OperationError") { result = 1; // Validation error. } } } callback(result, userdata); } }, /** * @param {number} bufferIdx * @param {number} labelPtr */ wgpuBufferSetLabel: (bufferIdx, labelPtr) => { const buffer = this.buffers.get(bufferIdx); buffer.buffer.label = this.mem.loadCstring(labelPtr); }, /** * @param {number} bufferIdx */ wgpuBufferUnmap: (bufferIdx) => { const buffer = this.buffers.get(bufferIdx); this.assert(buffer.mapping, "buffer not mapped"); const mapping = new Uint8Array(this.mem.memory.buffer, buffer.mapping.ptr, buffer.mapping.size); (new Uint8Array(buffer.mapping.range)).set(mapping); buffer.buffer.unmap(); this.mem.exports.wgpu_free(buffer.mapping.ptr); buffer.mapping = null; }, ...this.buffers.interface(), /* ---------------------- CommandBuffer ---------------------- */ ...this.commandBuffers.interface(true), /* ---------------------- CommandEncoder ---------------------- */ /** * @param {number} commandEncoderIdx * @param {0|number} descriptorPtr * @return {number} The compute pass encoder */ wgpuCommandEncoderBeginComputePass: (commandEncoderIdx, descriptorPtr) => { const commandEncoder = this.commandEncoders.get(commandEncoderIdx); /** @type {?GPUComputePassDescriptor} */ let descriptor; if (descriptorPtr != 0) { descriptor = { label: this.mem.loadCstring(descriptorPtr + 4), timestampWrites: this.ComputePassTimestampWritesPtr(descriptorPtr + 8), }; } const computePassEncoder = commandEncoder.beginComputePass(descriptor); return this.computePassEncoders.create(computePassEncoder); }, /** * @param {number} commandEncoderIdx * @param {number} descriptorPtr * @return {number} The render pass encoder */ wgpuCommandEncoderBeginRenderPass: (commandEncoderIdx, descriptorPtr) => { const commandEncoder = this.commandEncoders.get(commandEncoderIdx); this.assert(descriptorPtr != 0); let maxDrawCount = undefined; const nextInChain = this.mem.loadPtr(descriptorPtr); if (nextInChain != 0) { const nextInChainType = this.mem.loadI32(nextInChain + 4); // RenderPassDescriptorMaxDrawCount = 0x0000000F, if (nextInChainType == 0x0000000F) { maxDrawCount = this.mem.loadU64(nextInChain + 8); } } /** @type {GPURenderPassDescriptor} */ const descriptor = { label: this.mem.loadCstring(descriptorPtr + 4), colorAttachments: this.array( this.mem.loadUint(descriptorPtr + 8), this.mem.loadPtr(descriptorPtr + 8 + this.mem.intSize), this.RenderPassColorAttachment, 56, ), depthStencilAttachment: this.RenderPassDepthStencilAttachmentPtr(descriptorPtr + 8 + this.mem.intSize + 4), occlusionQuerySet: this.QuerySet(descriptorPtr + 8 + this.mem.intSize + 4 + 4), timestampWrites: this.RenderPassTimestampWritesPtr(descriptorPtr + 8 + this.mem.intSize + 4 + 4), maxDrawCount: maxDrawCount, }; const renderPassEncoder = commandEncoder.beginRenderPass(descriptor); return this.renderPassEncoders.create(renderPassEncoder); }, /** * @param {number} commandEncoderIdx * @param {number} bufferIdx * @param {BigInt} offset * @param {BigInt} size */ wgpuCommandEncoderClearBuffer: (commandEncoderIdx, bufferIdx, offset, size) => { const commandEncoder = this.commandEncoders.get(commandEncoderIdx); const buffer = this.buffers.get(bufferIdx); offset = this.unwrapBigInt(offset); size = this.unwrapBigInt(size); commandEncoder.clearBuffer(buffer.buffer, offset, size); }, /** * @param {number} commandEncoderIdx * @param {number} sourceIdx * @param {BigInt} sourceOffset * @param {number} destinationIdx * @param {BigInt} destinationOffset * @param {BigInt} size */ wgpuCommandEncoderCopyBufferToBuffer: (commandEncoderIdx, sourceIdx, sourceOffset, destinationIdx, destinationOffset, size) => { const commandEncoder = this.commandEncoders.get(commandEncoderIdx); const source = this.buffers.get(sourceIdx); const destination = this.buffers.get(destinationIdx); sourceOffset = this.unwrapBigInt(sourceOffset); destinationOffset = this.unwrapBigInt(destinationOffset); size = this.unwrapBigInt(size); commandEncoder.copyBufferToBuffer(source.buffer, sourceOffset, destination.buffer, destinationOffset, size); }, /** * @param {number} commandEncoderIdx * @param {number} sourcePtr * @param {number} destinationPtr * @param {number} copySizePtr */ wgpuCommandEncoderCopyBufferToTexture: (commandEncoderIdx, sourcePtr, destinationPtr, copySizePtr) => { const commandEncoder = this.commandEncoders.get(commandEncoderIdx); commandEncoder.copyBufferToTexture( this.ImageCopyBuffer(sourcePtr), this.ImageCopyTexture(destinationPtr), this.Extent3D(copySizePtr), ); }, /** * @param {number} commandEncoderIdx * @param {number} sourcePtr * @param {number} destinationPtr * @param {number} copySizePtr */ wgpuCommandEncoderCopyTextureToBuffer: (commandEncoderIdx, sourcePtr, destinationPtr, copySizePtr) => { const commandEncoder = this.commandEncoders.get(commandEncoderIdx); commandEncoder.copyTextureToBuffer( this.ImageCopyTexture(sourcePtr), this.ImageCopyBuffer(destinationPtr), this.Extent3D(copySizePtr), ); }, /** * @param {number} commandEncoderIdx * @param {number} sourcePtr * @param {number} destinationPtr * @param {number} copySizePtr */ wgpuCommandEncoderCopyTextureToTexture: (commandEncoderIdx, sourcePtr, destinationPtr, copySizePtr) => { const commandEncoder = this.commandEncoders.get(commandEncoderIdx); commandEncoder.copyTextureToTexture( this.ImageCopyTexture(sourcePtr), this.ImageCopyTexture(destinationPtr), this.Extent3D(copySizePtr), ); }, /** * @param {number} commandEncoderIdx * @param {0|number} descriptorPtr * @returns {number} The command buffer. */ wgpuCommandEncoderFinish: (commandEncoderIdx, descriptorPtr) => { const commandEncoder = this.commandEncoders.get(commandEncoderIdx); /** @type {undefined|GPUCommandBufferDescriptor} */ let descriptor; if (descriptorPtr != 0) { descriptor = { label: this.mem.loadCstring(descriptorPtr + 4), }; } const commandBuffer = commandEncoder.finish(descriptor); return this.commandBuffers.create(commandBuffer); }, /** * @param {number} commandEncoderIdx * @param {number} markerLabelPtr */ wgpuCommandEncoderInsertDebugMarker: (commandEncoderIdx, markerLabelPtr) => { const commandEncoder = this.commandEncoders.get(commandEncoderIdx); commandEncoder.insertDebugMarker(this.mem.loadCstring(markerLabelPtr)); }, /** * @param {number} commandEncoderIdx */ wgpuCommandEncoderPopDebugGroup: (commandEncoderIdx) => { const commandEncoder = this.commandEncoders.get(commandEncoderIdx); commandEncoder.popDebugGroup(); }, /** * @param {number} commandEncoderIdx * @param {number} markerLabelPtr */ wgpuCommandEncoderPushDebugGroup: (commandEncoderIdx, groupLabelPtr) => { const commandEncoder = this.commandEncoders.get(commandEncoderIdx); commandEncoder.pushDebugGroup(this.mem.loadCstring(groupLabelPtr)); }, /** * @param {number} commandEncoderIdx * @param {number} querySetIdx * @param {number} firstQuery * @param {number} queryCount * @param {number} destinationIdx * @param {BigInt} destinationOffset */ wgpuCommandEncoderResolveQuerySet: (commandEncoderIdx, querySetIdx, firstQuery, queryCount, destinationIdx, destinationOffset) => { const commandEncoder = this.commandEncoders.get(commandEncoderIdx); const querySet = this.querySets.get(querySetIdx); const destination = this.buffers.get(destinationIdx); destinationOffset = this.unwrapBigInt(destinationOffset); commandEncoder.resolveQuerySet(querySet, firstQuery, queryCount, destination.buffer, destinationOffset); }, /** * @param {number} commandEncoderIdx * @param {number} querySetIdx * @param {number} queryIndex */ wgpuCommandEncoderWriteTimestamp: (commandEncoderIdx, querySetIdx, queryIndex) => { const commandEncoder = this.commandEncoders.get(commandEncoderIdx); const querySet = this.querySets.get(querySetIdx); commandEncoder.writeTimestamp(querySet, queryIndex); }, ...this.commandEncoders.interface(true), /* ---------------------- ComputePassEncoder ---------------------- */ /** * @param {number} computePassEncoderIdx * @param {number} workgroupCountX * @param {number} workgroupCountY * @param {number} workgroupCountZ */ wgpuComputePassEncoderDispachWorkgroups: (computePassEncoderIdx, workgroupCountX, workgroupCountY, workgroupCountZ) => { const computePassEncoder = this.computePassEncoders.get(computePassEncoderIdx); computePassEncoder.dispatchWorkgroups(workgroupCountX, workgroupCountY, workgroupCountZ); }, /** * @param {number} computePassEncoderIdx * @param {number} indirectBufferIdx * @param {BigInt} indirectOffset */ wgpuComputePassEncoderDispachWorkgroupsIndirect: (computePassEncoderIdx, indirectBufferIdx, indirectOffset) => { const computePassEncoder = this.computePassEncoders.get(computePassEncoderIdx); const indirectBuffer = this.buffers.get(indirectBufferIdx); indirectOffset = this.unwrapBigInt(indirectOffset); computePassEncoder.dispatchWorkgroupsIndirect(indirectBuffer.buffer, indirectOffset); }, /** * @param {number} computePassEncoderIdx */ wgpuComputePassEncoderEnd: (computePassEncoderIdx) => { const computePassEncoder = this.computePassEncoders.get(computePassEncoderIdx); computePassEncoder.end(); }, /** * @param {number} computePassEncoderIdx * @param {number} markerLabelPtr */ wgpuComputePassEncoderInsertDebugMarker: (computePassEncoderIdx, markerLabelPtr) => { const computePassEncoder = this.computePassEncoders.get(computePassEncoderIdx); computePassEncoder.insertDebugMarker(this.mem.loadCstring(markerLabelPtr)); }, /** * @param {number} computePassEncoderIdx */ wgpuComputePassEncoderPopDebugGroup: (computePassEncoderIdx) => { const computePassEncoder = this.computePassEncoders.get(computePassEncoderIdx); computePassEncoder.popDebugGroup(); }, /** * @param {number} computePassEncoderIdx * @param {number} markerLabelPtr */ wgpuComputePassEncoderPushDebugGroup: (computePassEncoderIdx, groupLabelPtr) => { const computePassEncoder = this.computePassEncoders.get(computePassEncoderIdx); computePassEncoder.pushDebugGroup(this.mem.loadCstring(groupLabelPtr)); }, /** * @param {number} computePassEncoderIdx * @param {number} groupIndex * @param {0|number} groupIdx * @param {number|BigInt} dynamicOffsetCount * @param {number} dynamicOffsetsPtr */ wgpuComputePassEncoderSetBindGroup: (computePassEncoderIdx, groupIndex, groupIdx, dynamicOffsetCount, dynamicOffsetsPtr) => { const computePassEncoder = this.computePassEncoders.get(computePassEncoderIdx); dynamicOffsetCount = this.unwrapBigInt(dynamicOffsetCount); let bindGroup; if (groupIdx != 0) { bindGroup = this.bindGroups.get(groupIdx); } const dynamicOffsets = []; for (let i = 0; i < dynamicOffsetCount; i += 1) { dynamicOffsets.push(this.mem.loadU32(dynamicOffsetsPtr)); dynamicOffsetsPtr += 4; } computePassEncoder.setBindGroup(groupIndex, bindGroup, dynamicOffsets); }, /** * @param {number} computePassEncoderIdx * @param {number} pipelineIdx */ wgpuComputePassEncoderSetPipeline: (computePassEncoderIdx, pipelineIdx) => { const computePassEncoder = this.computePassEncoders.get(computePassEncoderIdx); const pipeline = this.computePipelines.get(pipelineIdx); computePassEncoder.setPipeline(pipeline); }, ...this.computePassEncoders.interface(true), /* ---------------------- ComputePipeline ---------------------- */ /** * @param {number} computePipelineIdx * @param {number} groupIndex * @returns {number} */ wgpuComputePipelineGetBindGroupLayout: (computePipelineIdx, groupIndex) => { const computePipeline = this.computePipelines.get(computePipelineIdx); const bindGroupLayout = computePipeline.getBindGroupLayout(groupIndex); return this.bindGroupLayouts.create(bindGroupLayout); }, ...this.computePipelines.interface(true), /* ---------------------- Device ---------------------- */ /** * @param {number} deviceIdx * @param {number} descriptorPtr * @returns {number} The bind group. */ wgpuDeviceCreateBindGroup: (deviceIdx, descriptorPtr) => { const device = this.devices.get(deviceIdx); this.assert(descriptorPtr != 0); /** @type {GPUBindGroupDescriptor} */ const descriptor = { label: this.mem.loadCstring(descriptorPtr + 4), layout: this.bindGroupLayouts.get(this.mem.loadPtr(descriptorPtr + 8)), entries: this.array( this.mem.loadUint(descriptorPtr + 8 + this.mem.intSize), this.mem.loadPtr(descriptorPtr + 8 + this.mem.intSize * 2), this.BindGroupEntry, 40, ), }; const bindGroup = device.createBindGroup(descriptor); return this.bindGroups.create(bindGroup); }, /** * @param {number} deviceIdx * @param {number} descriptorPtr * @returns {number} The bind group layout. */ wgpuDeviceCreateBindGroupLayout: (deviceIdx, descriptorPtr) => { const device = this.devices.get(deviceIdx); this.assert(descriptorPtr != 0); /** @type {GPUBindGroupLayoutDescriptor} */ const descriptor = { label: this.mem.loadCstring(descriptorPtr + 4), entries: this.array( this.mem.loadUint(descriptorPtr + 8), this.mem.loadPtr(descriptorPtr + 8 + this.mem.intSize), this.BindGroupLayoutEntry, 80, ), }; const bindGroupLayout = device.createBindGroupLayout(descriptor); return this.bindGroupLayouts.create(bindGroupLayout); }, /** * @param {number} deviceIdx * @param {number} descriptorPtr * @returns {number} The buffer. */ wgpuDeviceCreateBuffer: (deviceIdx, descriptorPtr) => { const device = this.devices.get(deviceIdx); this.assert(descriptorPtr != 0); /** @type {GPUBufferDescriptor} */ const descriptor = { label: this.mem.loadCstring(descriptorPtr + 4), usage: this.mem.loadU32(descriptorPtr + 8), size: this.mem.loadU64(descriptorPtr + 16), mappedAtCreation: this.mem.loadB32(descriptorPtr + 24), }; const buffer = device.createBuffer(descriptor); return this.buffers.create({buffer: buffer, mapping: null}); }, /** * @param {number} deviceIdx * @param {0|number} descriptorPtr * @returns {number} The command encoder. */ wgpuDeviceCreateCommandEncoder: (deviceIdx, descriptorPtr) => { const device = this.devices.get(deviceIdx); /** @type {GPUCommandEncoderDescriptor} */ let descriptor; if (descriptor != 0) { descriptor = { label: this.mem.loadCstring(descriptorPtr + 4), }; } const commandEncoder = device.createCommandEncoder(descriptor); return this.commandEncoders.create(commandEncoder); }, /** * @param {number} deviceIdx * @param {number} descriptorPtr * @returns {number} The compute pipeline. */ wgpuDeviceCreateComputePipeline: (deviceIdx, descriptorPtr) => { const device = this.devices.get(deviceIdx); this.assert(descriptorPtr != 0); const computePipeline = device.createComputePipeline(this.ComputePipelineDescriptor(descriptorPtr)); return this.computePipelines.create(computePipeline); }, /** * @param {number} deviceIdx * @param {number} descriptorPtr * @param {number} callbackPtr * @param {number} userdata */ wgpuDeviceCreateComputePipelineAsync: async (deviceIdx, descriptorPtr, callbackPtr, userdata) => { const device = this.devices.get(deviceIdx); const callback = this.mem.exports.__indirect_function_table.get(callbackPtr); this.assert(descriptorPtr != 0); let result; let resultIdx; try { const computePipeline = await device.createComputePipelineAsync(this.ComputePipelineDescriptor(descriptorPtr)); resultIdx = this.computePipelines.create(computePipeline); result = 0; /* Success */ // NOTE: don't callback here, any errors that happen later will then be caught by the catch here. } catch (e) { console.warn(e); result = 5; /* Unknown error */ } callback(result, resultIdx, null, userdata); }, /** * @param {number} deviceIdx * @param {number} descriptorPtr * @returns {number} The pipeline layout. */ wgpuDeviceCreatePipelineLayout: (deviceIdx, descriptorPtr) => { const device = this.devices.get(deviceIdx); this.assert(descriptorPtr != 0); /** @type {GPUPipelineLayoutDescriptor} */ const descriptor = { label: this.mem.loadCstring(descriptorPtr + 4), bindGroupLayouts: this.array( this.mem.loadUint(descriptorPtr + 8), this.mem.loadPtr(descriptorPtr + 8 + this.mem.intSize), (ptr) => this.bindGroupLayouts.get(this.mem.loadPtr(ptr)), 4, ), }; const pipelineLayout = device.createPipelineLayout(descriptor); return this.pipelineLayouts.create(pipelineLayout); }, /** * @param {number} deviceIdx * @param {number} descriptorPtr * @returns {number} The query set. */ wgpuDeviceCreateQuerySet: (deviceIdx, descriptorPtr) => { const device = this.devices.get(deviceIdx); this.assert(descriptorPtr != 0); /** @type {GPUQuerySetDescriptor} */ const descriptor = { label: this.mem.loadCstring(descriptorPtr + 4), type: this.QueryType(descriptorPtr + 8), count: this.mem.loadU32(descriptorPtr + 12), }; const querySet = device.createQuerySet(descriptor); return this.querySets.create(querySet); }, /** * @param {number} deviceIdx * @param {number} descriptorPtr * @returns {number} The query set. */ wgpuDeviceCreateRenderBundleEncoder: (deviceIdx, descriptorPtr) => { const device = this.devices.get(deviceIdx); this.assert(descriptorPtr != 0); /** @type {GPURenderBundleEncoderDescriptor} */ const descriptor = { label: this.mem.loadCstring(descriptorPtr + 4), colorFormats: this.array( this.mem.loadUint(descriptorPtr + 8), this.mem.loadPtr(descriptorPtr + 8 + this.mem.intSize), this.TextureFormat, 4, ), depthStencilFormat: this.enumeration("TextureFormat", descriptorPtr + 8 + this.mem.intSize + 4), sampleCount: this.mem.loadU32(descriptorPtr + 8 + this.mem.intSize + 8), depthReadOnly: this.mem.loadB32(descriptorPtr + 8 + this.mem.intSize + 12), stencilReadOnly: this.mem.loadB32(descriptorPtr + 8 + this.mem.intSize + 16), }; const renderBundleEncoder = device.createRenderBundleEncoder(descriptor); return this.renderBundleEncoders.create(renderBundleEncoder); }, /** * @param {number} deviceIdx * @param {number} descriptorPtr * @returns {number} The render pipeline. */ wgpuDeviceCreateRenderPipeline: (deviceIdx, descriptorPtr) => { const device = this.devices.get(deviceIdx); this.assert(descriptorPtr != 0); const descriptor = this.RenderPipelineDescriptor(descriptorPtr); const renderPipeline = device.createRenderPipeline(descriptor); return this.renderPipelines.create(renderPipeline); }, /** * @param {number} deviceIdx * @param {number} descriptorPtr * @param {number} callbackPtr * @param {number} userdata */ wgpuDeviceCreateRenderPipelineAsync: async (deviceIdx, descriptorPtr, callbackPtr, userdata) => { const device = this.devices.get(deviceIdx); const callback = this.mem.exports.__indirect_function_table.get(callbackPtr); this.assert(descriptorPtr != 0); let result; let resultIdx; try { const renderPipeline = await device.createRenderPipelineAsync(this.RenderPipelineDescriptor(descriptorPtr)); resultIdx = this.renderPipelines.create(renderPipeline); result = 0; /* Success */ // NOTE: don't callback here, any errors that happen later will then be caught by the catch here. } catch (e) { console.warn(e); result = 5; /* Unknown error */ } callback(result, resultIdx, null, userdata); }, /** * @param {number} deviceIdx * @param {0|number} descriptorPtr * @returns {number} The sampler. */ wgpuDeviceCreateSampler: (deviceIdx, descriptorPtr) => { const device = this.devices.get(deviceIdx); /** @type {?GPUSamplerDescriptor} */ let descriptor; if (descriptorPtr != 0) { descriptor = { label: this.mem.loadCstring(descriptorPtr + 4), addressModeU: this.enumeration("AddressMode", descriptorPtr + 8), addressModeV: this.enumeration("AddressMode", descriptorPtr + 12), addressModeW: this.enumeration("AddressMode", descriptorPtr + 16), magFilter: this.enumeration("FilterMode", descriptorPtr + 20), minFilter: this.enumeration("FilterMode", descriptorPtr + 24), mipMapFilter: this.enumeration("MipmapFilterMode", descriptorPtr + 28), lodMinClamp: this.mem.loadF32(descriptorPtr + 32), lodMaxClamp: this.mem.loadF32(descriptorPtr + 36), compare: this.enumeration("CompareFunction", descriptorPtr + 40), maxAnisotropy: this.mem.loadU16(descriptorPtr + 44), }; } const sampler = device.createSampler(descriptor); return this.samplers.create(sampler); }, /** * @param {number} deviceIdx * @param {number} descriptorPtr * @returns {number} The shader module. */ wgpuDeviceCreateShaderModule: (deviceIdx, descriptorPtr) => { const device = this.devices.get(deviceIdx); this.assert(descriptorPtr != 0); const nextInChain = this.mem.loadPtr(descriptorPtr); const nextInChainType = this.mem.loadI32(nextInChain + 4); // ShaderModuleWGSLDescriptor = 0x00000006, if (nextInChainType != 6) { throw new TypeError(`Descriptor type should be 'ShaderModuleWGSLDescriptor', got ${nextInChainType}`); } /** @type {GPUShaderModuleDescriptor} */ const descriptor = { label: this.mem.loadCstring(descriptorPtr + 4), code: this.mem.loadCstring(nextInChain + 8), compilationHints: this.array( this.mem.loadUint(descriptorPtr + 8), this.mem.loadPtr(descriptorPtr + 8 + this.mem.intSize), this.ShaderModuleCompilationHint, 12, ), }; const shaderModule = device.createShaderModule(descriptor); return this.shaderModules.create(shaderModule); }, /** * @param {number} deviceIdx * @param {number} descriptorPtr * @returns {number} The texture. */ wgpuDeviceCreateTexture: (deviceIdx, descriptorPtr) => { const device = this.devices.get(deviceIdx); this.assert(descriptorPtr != 0); /** @type {GPUTextureDescriptor} */ const descriptor = { label: this.mem.loadCstring(descriptorPtr + 4), usage: this.mem.loadU32(descriptorPtr + 8), dimension: this.enumeration("TextureDimension", descriptorPtr + 12), size: this.Extent3D(descriptorPtr + 16), format: this.enumeration("TextureFormat", descriptorPtr + 28), mipLevelCount: this.mem.loadU32(descriptorPtr + 32), sampleCount: this.mem.loadU32(descriptorPtr + 36), viewFormats: this.array( this.mem.loadUint(descriptorPtr + 40), this.mem.loadPtr(descriptorPtr + 40 + this.mem.intSize), (ptr) => this.enumeration("TextureFormat", ptr), 4, ), }; const texture = device.createTexture(descriptor); return this.textures.create(texture); }, /** * @param {number} deviceIdx */ wgpuDeviceDestroy: (deviceIdx) => { const device = this.devices.get(deviceIdx); device.destroy(); }, /** * @param {number} deviceIdx * @param {number} featuresPtr * @returns {number|BigInt} */ wgpuDeviceEnumerateFeatures: (deviceIdx, featuresPtr) => { const device = this.devices.get(deviceIdx); return this.genericEnumerateFeatures(device.features, featuresPtr); }, /** * @param {number} deviceIdx * @param {number} limitsPtr * @returns {boolean} */ wgpuDeviceGetLimits: (deviceIdx, limitsPtr) => { const device = this.devices.get(deviceIdx); return this.genericGetLimits(device.limits, limitsPtr); }, /** * @param {number} deviceIdx * @returns {number} */ wgpuDeviceGetQueue: (deviceIdx) => { const device = this.devices.get(deviceIdx); return this.queues.create(device.queue); }, /** * @param {number} deviceIdx * @param {number} featureInt * @returns {boolean} */ wgpuDeviceHasFeature: (deviceIdx, featureInt) => { const device = this.devices.get(deviceIdx); return device.features.has(this.enums.FeatureName[featureInt]); }, /** * @param {number} deviceIdx * @param {number} callbackPtr * @param {number} userdata */ wgpuDevicePopErrorScope: async (deviceIdx, callbackPtr, userdata) => { const device = this.devices.get(deviceIdx); const callback = this.mem.exports.__indirect_function_table.get(callbackPtr); const error = await device.popErrorScope(); if (!error) { callback(0, null, userdata); return; } console.warn(error); let status = 4; if (error instanceof GPUValidationError) { status = 1; } else if (error instanceof GPUOutOfMemoryError) { status = 2; } else if (error instanceof GPUInternalError) { status = 3; } callback(status, null, userdata); }, /** * @param {number} deviceIdx * @param {number} filterInt */ wgpuDevicePushErrorScope: (deviceIdx, filterInt) => { const device = this.devices.get(deviceIdx); device.pushErrorScope(this.enums.ErrorFilter[filterInt]); }, ...this.devices.interface(true), /* ---------------------- Instance ---------------------- */ /** * @param {number} instanceIdx * @param {number} descriptorPtr */ wgpuInstanceCreateSurface: (instanceIdx, descriptorPtr) => { this.assert(instanceIdx > 0); this.assert(descriptorPtr != 0); const nextInChain = this.mem.loadPtr(descriptorPtr); const nextInChainType = this.mem.loadI32(nextInChain + 4); // SurfaceDescriptorFromCanvasHTMLSelector = 0x00000004, if (nextInChainType != 4) { throw new TypeError(`Descriptor type should be 'SurfaceDescriptorFromCanvasHTMLSelector', got ${nextInChainType}`); } const selector = this.mem.loadCstring(nextInChain + 8); const surface = document.querySelector(selector); if (!surface) { throw new Error(`Selector '${selector}' did not match any element`); } if (!(surface instanceof HTMLCanvasElement)) { throw new Error('Selector matches an element that is not a canvas'); } return this.surfaces.create(surface); }, /** * @param {number} instanceIdx * @param {number} featureInt * @returns {boolean} */ wgpuInstanceHasWGSLLanguageFeature: (instanceIdx, featureInt) => { return navigator.gpu.wgslLanguageFeatures.has(this.enums.WGSLFeatureName[featureInt]); }, /** * @param {number} instanceIdx */ wgpuInstanceProcessEvents: (instanceIdx) => { console.warn("unimplemented: wgpuInstanceProcessEvents"); }, /** * @param {number} instanceIdx * @param {0|number} optionsPtr * @param {number} callbackPtr * @param {number} userdata */ wgpuInstanceRequestAdapter: async (instanceIdx, optionsPtr, callbackPtr, userdata) => { this.assert(instanceIdx > 0); const callback = this.mem.exports.__indirect_function_table.get(callbackPtr); /** @type {GPURequestAdapterOptions} */ let options; if (optionsPtr != 0) { options = { powerPreference: this.enumeration("PowerPreference", optionsPtr + 8), forceFallbackAdapter: this.mem.loadB32(optionsPtr + 16), }; } let adapterIdx; try { const adapter = await navigator.gpu.requestAdapter(options); adapterIdx = this.adapters.create(adapter); // NOTE: don't callback here, any errors that happen later will then be caught by the catch here. } catch(e) { console.warn(e); callback(2, null, null, userdata); } callback(0, adapterIdx, null, userdata); }, ...this.instances.interface(false), /* ---------------------- PipelineLayout ---------------------- */ ...this.pipelineLayouts.interface(true), /* ---------------------- QuerySet ---------------------- */ /** * @param {number} querySetIdx */ wgpuQuerySetDestroy: (querySetIdx) => { const querySet = this.querySets.get(querySetIdx); querySet.destroy(); }, /** * @param {number} querySetIdx * @returns {number} */ wgpuQuerySetGetCount: (querySetIdx) => { const querySet = this.querySets.get(querySetIdx); return querySet.count; }, /** * @param {number} querySetIdx * @returns {number} */ wgpuQuerySetGetType: (querySetIdx) => { const querySet = this.querySets.get(querySetIdx); return this.enums.QueryType.indexOf(querySet.type); }, ...this.querySets.interface(true), /* ---------------------- Queue ---------------------- */ /** * @param {number} queueIdx * @param {number} callbackPtr * @param {number} userdata */ wgpuQueueOnSubmittedWorkDone: async (queueIdx, callbackPtr, userdata) => { const queue = this.queues.get(queueIdx); const callback = this.mem.exports.__indirect_function_table.get(callbackPtr); let result; try { await queue.onSubmittedWorkDone(); result = 0; } catch(e) { console.warn(e); result = 1; } callback(result, userdata); }, /** * @param {number} queueIdx * @param {BigInt|number} commandCount * @param {number} commandsPtr */ wgpuQueueSubmit: (queueIdx, commandCount, commandsPtr) => { const queue = this.queues.get(queueIdx); const commands = this.array( this.unwrapBigInt(commandCount), commandsPtr, (ptr) => this.commandBuffers.get(this.mem.loadPtr(ptr)), 4, ); queue.submit(commands); }, /** * @param {number} queueIdx * @param {number} bufferIdx * @param {BigInt} bufferOffset * @param {number} dataPtr * @param {number|BigInt} size */ wgpuQueueWriteBuffer: (queueIdx, bufferIdx, bufferOffset, dataPtr, size) => { const queue = this.queues.get(queueIdx); const buffer = this.buffers.get(bufferIdx); bufferOffset = this.unwrapBigInt(bufferOffset); size = this.unwrapBigInt(size); queue.writeBuffer(buffer.buffer, bufferOffset, this.mem.loadBytes(dataPtr, size), 0, size); }, /** * @param {number} queueIdx * @param {number} destinationPtr * @param {number} dataPtr * @param {number|BigInt} dataSize * @param {number} dataLayoutPtr * @param {number} writeSizePtr */ wgpuQueueWriteTexture: (queueIdx, destinationPtr, dataPtr, dataSize, dataLayoutPtr, writeSizePtr) => { const queue = this.queues.get(queueIdx); const destination = this.ImageCopyTexture(destinationPtr); dataSize = this.unwrapBigInt(dataSize); const dataLayout = this.TextureDataLayout(dataLayoutPtr); const writeSize = this.Extent3D(writeSizePtr); queue.writeTexture(destination, this.mem.loadBytes(dataPtr, dataSize), dataLayout, writeSize); }, ...this.queues.interface(true), /* ---------------------- RenderBundle ---------------------- */ ...this.renderBundles.interface(true), /* ---------------------- RenderBundleEncoder ---------------------- */ /** * @param {number} renderBundleEncoderIdx * @param {number} vertexCount * @param {number} instanceCount * @param {number} firstVertex * @param {number} firstInstance */ wgpuRenderBundleEncoderDraw: (renderBundleEncoderIdx, vertexCount, instanceCount, firstVertex, firstInstance) => { const renderBundleEncoder = this.renderBundleEncoders.get(renderBundleEncoderIdx); renderBundleEncoder.draw(vertexCount, instanceCount, firstVertex, firstInstance); }, /** * @param {number} renderBundleEncoderIdx * @param {number} indexCount * @param {number} instanceCount * @param {number} firstIndex * @param {number} baseVertex * @param {number} firstInstance */ wgpuRenderBundleEncoderDrawIndexed: (renderBundleEncoderIdx, indexCount, instanceCount, firstIndex, baseVertex, firstInstance) => { const renderBundleEncoder = this.renderBundleEncoders.get(renderBundleEncoderIdx); renderBundleEncoder.drawIndexed(indexCount, instanceCount, firstIndex, baseVertex, firstInstance); }, /** * @param {number} renderBundleEncoderIdx * @param {number} indirectBufferIdx * @param {BigInt} indirectOffset */ wgpuRenderBundleEncoderDrawIndexedIndirect: (renderBundleEncoderIdx, indirectBufferIdx, indirectOffset) => { const renderBundleEncoder = this.renderBundleEncoders.get(renderBundleEncoderIdx); indirectOffset = this.unwrapBigInt(indirectOffset); const buffer = this.buffers.get(indirectBufferIdx); renderBundleEncoder.drawIndexedIndirect(buffer.buffer, indirectOffset); }, /** * @param {number} renderBundleEncoderIdx * @param {number} indirectBufferIdx * @param {BigInt} indirectOffset */ wgpuRenderBundleEncoderDrawIndirect: (renderBundleEncoderIdx, indirectBufferIdx, indirectOffset) => { const renderBundleEncoder = this.renderBundleEncoders.get(renderBundleEncoderIdx); indirectOffset = this.unwrapBigInt(indirectOffset); const buffer = this.buffers.get(indirectBufferIdx); renderBundleEncoder.drawIndirect(buffer.buffer, indirectOffset); }, /** * @param {number} renderBundleEncoderIdx * @param {0|number} descriptorPtr * @returns {number} */ wgpuRenderBundleEncoderFinish: (renderBundleEncoderIdx, descriptorPtr) => { const renderBundleEncoder = this.renderBundleEncoders.get(renderBundleEncoderIdx); /** @type {?GPURenderBundleDescriptor} */ let descriptor; if (descriptorPtr != 0) { descriptor = { label: this.mem.loadCstring(descriptorPtr + 4), }; } const renderBundle = renderBundleEncoder.finish(descriptor); return this.renderBundles.create(renderBundle); }, /** * @param {number} renderBundleEncoderIdx * @param {number} markerLabelPtr */ wgpuRenderBundleEncoderInsertDebugMarker: (renderBundleEncoderIdx, markerLabelPtr) => { const renderBundleEncoder = this.renderBundleEncoders.get(renderBundleEncoderIdx); this.assert(markerLabelPtr != 0); const markerLabel = this.mem.loadCstring(markerLabelPtr); renderBundleEncoder.insertDebugMarker(markerLabel); }, /** * @param {number} renderBundleEncoderIdx */ wgpuRenderBundleEncoderPopDebugGroup: (renderBundleEncoderIdx) => { const renderBundleEncoder = this.renderBundleEncoders.get(renderBundleEncoderIdx); renderBundleEncoder.popDebugGroup(); }, /** * @param {number} renderBundleEncoderIdx * @param {number} groupLabelPtr */ wgpuRenderBundleEncoderPushDebugGroup: (renderBundleEncoderIdx, groupLabelPtr) => { const renderBundleEncoder = this.renderBundleEncoders.get(renderBundleEncoderIdx); this.assert(groupLabelPtr!= 0); const groupLabel = this.mem.loadCstring(groupLabelPtr); renderBundleEncoder.pushDebugGroup(groupLabel); }, /** * @param {number} renderBundleEncoderIdx * @param {number} groupIndex * @param {0|number} groupIdx * @param {number|BigInt} dynamicOffsetCount * @param {number} dynamicOffsetsPtr */ wgpuRenderBundleEncoderSetBindGroup: (renderBundleEncoderIdx, groupIndex, groupIdx, dynamicOffsetCount, dynamicOffsetsPtr) => { const renderBundleEncoder = this.renderBundleEncoders.get(renderBundleEncoderIdx); let group; if (groupIdx > 0) { group = this.bindGroups.get(groupIdx); } dynamicOffsetCount = this.unwrapBigInt(dynamicOffsetCount); const dynamicOffsets = this.array(dynamicOffsetCount, dynamicOffsetsPtr, this.mem.loadU32, 4); renderBundleEncoder.setBindGroup(groupIndex, group, dynamicOffsets); }, /** * @param {number} renderBundleEncoderIdx * @param {number} bufferIdx * @param {number} formatInt * @param {BigInt} offset * @param {BigInt} size */ wgpuRenderBundleEncoderSetIndexBuffer: (renderBundleEncoderIdx, bufferIdx, formatInt, offset, size) => { const renderBundleEncoder = this.renderBundleEncoders.get(renderBundleEncoderIdx); const buffer = this.buffers.get(bufferIdx); const format = this.enums.IndexFormat[formatInt]; offset = this.unwrapBigInt(offset); size = this.unwrapBigInt(size); renderBundleEncoder.setIndexBuffer(buffer.buffer, format, offset, size); }, /** * @param {number} renderBundleEncoderIdx * @param {number} pipelineIdx */ wgpuRenderBundleEncoderSetPipeline: (renderBundleEncoderIdx, pipelineIdx) => { const renderBundleEncoder = this.renderBundleEncoders.get(renderBundleEncoderIdx); const pipeline = this.renderPipelines.get(pipelineIdx); renderBundleEncoder.setPipeline(pipeline); }, /** * @param {number} renderBundleEncoderIdx * @param {number} slot * @param {0|number} bufferIdx * @param {BigInt} offset * @param {BigInt} size */ wgpuRenderBundleEncoderSetVertexBuffer: (renderBundleEncoderIdx, slot, bufferIdx, offset, size) => { const renderBundleEncoder = this.renderBundleEncoders.get(renderBundleEncoderIdx); let buffer; if (bufferIdx > 0) { buffer = this.buffers.get(bufferIdx).buffer; } offset = this.unwrapBigInt(offset); size = this.unwrapBigInt(size); renderBundleEncoder.setVertexBuffer(slot, buffer, offset, size); }, ...this.renderBundleEncoders.interface(true), /* ---------------------- RenderPassEncoder ---------------------- */ /** * @param {number} renderPassEncoderIdx * @param {number} queryIndex */ wgpuRenderPassEncoderBeginOcclusionQuery: (renderPassEncoderIdx, queryIndex) => { const renderPassEncoder = this.renderPassEncoders.get(renderPassEncoderIdx); renderPassEncoder.beginOcclusionQuery(queryIndex); }, /** * @param {number} renderPassEncoderIdx * @param {number} vertexCount * @param {number} instanceCount * @param {number} firstVertex * @param {number} firstInstance */ wgpuRenderPassEncoderDraw: (renderPassEncoderIdx, vertexCount, instanceCount, firstVertex, firstInstance) => { const renderPassEncoder = this.renderPassEncoders.get(renderPassEncoderIdx); renderPassEncoder.draw(vertexCount, instanceCount, firstVertex, firstInstance); }, /** * @param {number} renderPassEncoderIdx * @param {number} indexCount * @param {number} instanceCount * @param {number} firstIndex * @param {number} baseVertex * @param {number} firstInstance */ wgpuRenderPassEncoderDrawIndexed: (renderPassEncoderIdx, indexCount, instanceCount, firstIndex, baseVertex, firstInstance) => { const renderPassEncoder = this.renderPassEncoders.get(renderPassEncoderIdx); renderPassEncoder.drawIndexed(indexCount, instanceCount, firstIndex, baseVertex, firstInstance); }, /** * @param {number} renderPassEncoderIdx * @param {number} indirectBufferIdx * @param {BigInt} indirectOffset */ wgpuRenderPassEncoderDrawIndexedIndirect: (renderPassEncoderIdx, indirectBufferIdx, indirectOffset) => { const renderPassEncoder = this.renderPassEncoders.get(renderPassEncoderIdx); const buffer = this.buffers.get(indirectBufferIdx); indirectOffset = this.unwrapBigInt(indirectOffset); renderPassEncoder.drawIndexedIndirect(buffer.buffer, indirectOffset); }, /** * @param {number} renderPassEncoderIdx * @param {number} indirectBufferIdx * @param {BigInt} indirectOffset */ wgpuRenderPassEncoderDrawIndirect: (renderPassEncoderIdx, indirectBufferIdx, indirectOffset) => { const renderPassEncoder = this.renderPassEncoders.get(renderPassEncoderIdx); const buffer = this.buffers.get(indirectBufferIdx); indirectOffset = this.unwrapBigInt(indirectOffset); renderPassEncoder.drawIndirect(buffer.buffer, indirectOffset); }, /** * @param {number} renderPassEncoderIdx */ wgpuRenderPassEncoderEnd: (renderPassEncoderIdx) => { const renderPassEncoder = this.renderPassEncoders.get(renderPassEncoderIdx); renderPassEncoder.end(); }, /** * @param {number} renderPassEncoderIdx */ wgpuRenderPassEncoderEndOcclusionQuery: (renderPassEncoderIdx) => { const renderPassEncoder = this.renderPassEncoders.get(renderPassEncoderIdx); renderPassEncoder.endOcclusionQuery(); }, /** * @param {number} renderPassEncoderIdx * @param {number|BigInt} bundleCount * @param {number} bundlesPtr */ wgpuRenderPassEncoderExecuteBundles: (renderPassEncoderIdx, bundleCount, bundlesPtr) => { const renderPassEncoder = this.renderPassEncoders.get(renderPassEncoderIdx); bundleCount = this.unwrapBigInt(bundleCount); const bundles = this.array( bundleCount, bundlesPtr, (ptr) => this.renderBundles.get(this.mem.loadPtr(ptr)), 4, ); renderPassEncoder.executeBundles(bundles); }, /** * @param {number} renderPassEncoderIdx * @param {number} markerLabelPtr */ wgpuRenderPassEncoderInsertDebugMarker: (renderPassEncoderIdx, markerLabelPtr) => { const renderPassEncoder = this.renderPassEncoders.get(renderPassEncoderIdx); const markerLabel = this.mem.loadCstring(markerLabelPtr); renderPassEncoder.insertDebugMarker(markerLabel); }, /** * @param {number} renderPassEncoderIdx */ wgpuRenderPassEncoderPopDebugGroup: (renderPassEncoderIdx) => { const renderPassEncoder = this.renderPassEncoders.get(renderPassEncoderIdx); renderPassEncoder.popDebugGroup(); }, /** * @param {number} renderPassEncoderIdx * @param {number} groupLabelPtr */ wgpuRenderPassEncoderPushDebugGroup: (renderPassEncoderIdx, groupLabelPtr) => { const renderPassEncoder = this.renderPassEncoders.get(renderPassEncoderIdx); const groupLabel = this.mem.loadCstring(groupLabelPtr); renderPassEncoder.pushDebugGroup(groupLabel); }, /** * @param {number} renderPassEncoderIdx * @param {number} groupIndex * @param {0|number} groupIdx * @param {number|BigInt} dynamicOffsetCount * @param {number} dynamicOffsetsPtr */ wgpuRenderPassEncoderSetBindGroup: (renderPassEncoderIdx, groupIndex, groupIdx, dynamicOffsetCount, dynamicOffsetsPtr) => { const renderPassEncoder = this.renderPassEncoders.get(renderPassEncoderIdx); let group; if (groupIdx > 0) { group = this.bindGroups.get(groupIdx); } dynamicOffsetCount = this.unwrapBigInt(dynamicOffsetCount); const dynamicOffsets = this.array(dynamicOffsetCount, dynamicOffsetsPtr, this.mem.loadU32, 4); renderPassEncoder.setBindGroup(groupIndex, group, dynamicOffsets); }, /** * @param {number} renderPassEncoderIdx * @param {number} bufferIdx * @param {number} formatInt * @param {BigInt} offset * @param {BigInt} size */ wgpuRenderPassEncoderSetIndexBuffer: (renderPassEncoderIdx, bufferIdx, formatInt, offset, size) => { const renderPassEncoder = this.renderPassEncoders.get(renderPassEncoderIdx); const buffer = this.buffers.get(bufferIdx); const format = this.enums.IndexFormat[formatInt]; offset = this.unwrapBigInt(offset); size = this.unwrapBigInt(size); renderPassEncoder.setIndexBuffer(buffer.buffer, format, offset, size); }, /** * @param {number} renderPassEncoderIdx * @param {number} pipelineIdx */ wgpuRenderPassEncoderSetPipeline: (renderPassEncoderIdx, pipelineIdx) => { const renderPassEncoder = this.renderPassEncoders.get(renderPassEncoderIdx); const pipeline = this.renderPipelines.get(pipelineIdx); renderPassEncoder.setPipeline(pipeline); }, /** * @param {number} renderPassEncoderIdx * @param {number} x * @param {number} y * @param {number} width * @param {number} height */ wgpuRenderPassEncoderSetScissorRect: (renderPassEncoderIdx, x, y, width, height) => { const renderPassEncoder = this.renderPassEncoders.get(renderPassEncoderIdx); renderPassEncoder.setScissorRect(x, y, width, height); }, /** * @param {number} renderPassEncoderIdx * @param {number} reference */ wgpuRenderPassEncoderSetStencilReference: (renderPassEncoderIdx, reference) => { const renderPassEncoder = this.renderPassEncoders.get(renderPassEncoderIdx); renderPassEncoder.setStencilReference(reference); }, /** * @param {number} renderPassEncoderIdx * @param {number} slot * @param {0|number} bufferIdx * @param {BigInt} offset * @param {BigInt} size */ wgpuRenderPassEncoderSetVertexBuffer: (renderPassEncoderIdx, slot, bufferIdx, offset, size) => { const renderPassEncoder = this.renderPassEncoders.get(renderPassEncoderIdx); let buffer; if (bufferIdx > 0) { buffer = this.buffers.get(bufferIdx).buffer; } offset = this.unwrapBigInt(offset); size = this.unwrapBigInt(size); renderPassEncoder.setVertexBuffer(slot, buffer, offset, size); }, /** * @param {number} renderPassEncoderIdx * @param {number} x * @param {number} y * @param {number} width * @param {number} height * @param {number} minDepth * @param {number} maxDepth */ wgpuRenderPassEncoderSetViewport: (renderPassEncoderIdx, x, y, width, height, minDepth, maxDepth) => { const renderPassEncoder = this.renderPassEncoders.get(renderPassEncoderIdx); renderPassEncoder.setViewport(x, y, width, height, minDepth, maxDepth); }, ...this.renderPassEncoders.interface(true), /* ---------------------- RenderPipeline ---------------------- */ /** * @param {number} renderPipelineIdx * @param {number} groupIndex * @returns {number} */ wgpuRenderPipelineGetBindGroupLayout: (renderPipelineIdx, groupIndex) => { const renderPipeline = this.renderPipelines.get(renderPipelineIdx); const bindGroupLayout = renderPipeline.getBindGroupLayout(groupIndex); return this.bindGroupLayouts.create(bindGroupLayout); }, ...this.renderPipelines.interface(true), /* ---------------------- Sampler ---------------------- */ ...this.samplers.interface(true), /* ---------------------- ShaderModule ---------------------- */ /** * @param {number} shaderModuleIdx * @param {number} callbackPtr * @param {number} userdata */ wgpuShaderModuleGetCompilationInfo: async (shaderModuleIdx, callbackPtr, userdata) => { const shaderModule = this.shaderModules.get(shaderModuleIdx); const callback = this.mem.exports.__indirect_function_table.get(callbackPtr); let status = 0; let retAddr = 0; const ptrsToFree = []; try { const compilationInfo = await shaderModule.getCompilationInfo(); const size = compilationInfo.messages.length * 72; const addr = this.mem.exports.wgpu_alloc(size); ptrsToFree.push(addr); compilationInfo.messages.forEach((message, i) => { const messageLength = new TextEncoder().encode(message.message).length; const messageAddr = this.mem.exports.wgpu_alloc(messageLength); ptrsToFree.push(messageAddr); this.mem.storeString(messageAddr, message.message); this.mem.storeI32(addr + (i * size) + 4); this.mem.storeI32(addr + (i * size) + 8, this.enums.CompilationMessageType.indexOf(message.type)); this.mem.storeU64(addr + (i * size) + 16, message.lineNum); this.mem.storeU64(addr + (i * size) + 24, message.linePos); this.mem.storeU64(addr + (i * size) + 32, message.offset); this.mem.storeU64(addr + (i * size) + 40, message.length); // TODO: UTF16 units. this.mem.storeU64(addr + (i * size) + 48, message.linePos); this.mem.storeU64(addr + (i * size) + 56, message.offset); this.mem.storeU64(addr + (i * size) + 64, message.length); }); retAddr = this.mem.exports.wgpu_alloc(3*this.mem.intSize); ptrsToFree.push(retAddr); this.mem.storeUint(retAddr + this.mem.intSize, compilationInfo.messages.length); this.mem.storeI32(retAddr + this.mem.intSize*2, addr); } catch (e) { console.warn(e); status = 1; } callback(status, retAddr, userdata); ptrsToFree.forEach(ptr => this.mem.exports.wgpu_free(ptr)); }, ...this.shaderModules.interface(true), /* ---------------------- Surface ---------------------- */ /** * @param {number} surfaceIdx * @param {number} configPtr */ wgpuSurfaceConfigure: (surfaceIdx, configPtr) => { const surface = this.surfaces.get(surfaceIdx); const context = surface.getContext('webgpu'); const widthOff = 16 + this.mem.intSize + 8; surface.width = this.mem.loadU32(configPtr + widthOff); surface.height = this.mem.loadU32(configPtr + widthOff + 4); /** @type {GPUCanvasConfiguration} */ const config = { device: this.devices.get(this.mem.loadPtr(configPtr + 4)), format: this.enumeration("TextureFormat", configPtr + 8), usage: this.mem.loadU32(configPtr + 12), viewFormats: this.array( this.mem.loadUint(configPtr + 16), this.mem.loadPtr(configPtr + 16 + this.mem.intSize), (ptr) => this.enumeration("TextureFormat", ptr), 4, ), alphaMode: this.enumeration("CompositeAlphaMode", configPtr + widthOff - 4), // // NOTE: present mode seems unused. presentMode: this.enumeration("PresentMode", configPtr + widthOff + 4), }; context.configure(config); }, /** * @param {number} surfaceIdx * @param {number} adapterIdx * @param {number} capabilitiesPtr */ wgpuSurfaceGetCapabilities: (surfaceIdx, adapterIdx, capabilitiesPtr) => { const formatStr = navigator.gpu.getPreferredCanvasFormat(); const format = this.enums.TextureFormat.indexOf(formatStr); this.mem.storeUint(capabilitiesPtr + 8, 1); const formatAddr = this.mem.exports.wgpu_alloc(4); this.mem.storeI32(formatAddr, format); this.mem.storeI32(capabilitiesPtr + 8 + this.mem.intSize, formatAddr); // NOTE: present modes don't seem to actually do anything in JS, we can just give back a default FIFO though. this.mem.storeUint(capabilitiesPtr + 8 + this.mem.intSize*2, 1); const presentModesAddr = this.mem.exports.wgpu_alloc(4); this.mem.storeI32(presentModesAddr, 0); this.mem.storeI32(capabilitiesPtr + 8 + this.mem.intSize*3, presentModesAddr); // Browser seems to support opaque (1) and premultiplied (2). this.mem.storeUint(capabilitiesPtr + 8 + this.mem.intSize*4, 2); const alphaModesAddr = this.mem.exports.wgpu_alloc(8); this.mem.storeI32(alphaModesAddr + 0, 1); // Opaque. this.mem.storeI32(alphaModesAddr + 4, 2); // premultiplied. this.mem.storeI32(capabilitiesPtr + 8 + this.mem.intSize*5, alphaModesAddr); }, /** * @param {number} surfaceIdx * @param {number} texturePtr */ wgpuSurfaceGetCurrentTexture: (surfaceIdx, texturePtr) => { const surface = this.surfaces.get(surfaceIdx); const context = surface.getContext('webgpu'); const texture = context.getCurrentTexture(); const textureIdx = this.textures.create(texture); this.mem.storeI32(texturePtr, textureIdx); // TODO: determine suboptimal and/or status. }, /** * @param {number} surfaceIdx */ wgpuSurfacePresent: (surfaceIdx) => { // NOTE: Not really anything to do here. }, /** * @param {number} surfaceIdx */ wgpuSurfaceUnconfigure: (surfaceIdx) => { const surface = this.surfaces.get(surfaceIdx); surface.getContext('webgpu').unconfigure(); }, ...this.surfaces.interface(true), /* ---------------------- SurfaceCapabilities ---------------------- */ /** * @param {number} surfaceCapabilitiesPtr */ wgpuSurfaceCapabilitiesFreeMembers: (surfaceCapabilitiesPtr) => { const formatsAddr = this.mem.loadI32(surfaceCapabilitiesPtr + this.mem.intSize*2); this.mem.exports.wgpu_free(formatsAddr); const presentModesAddr = this.mem.loadI32(surfaceCapabilitiesPtr + this.mem.intSize*4); this.mem.exports.wgpu_free(presentModesAddr); const alphaModesAddr = this.mem.loadI32(surfaceCapabilitiesPtr + this.mem.intSize*6); this.mem.exports.wgpu_free(alphaModesAddr); }, /* ---------------------- Texture ---------------------- */ /** * @param {number} textureIdx * @param {0|number} descriptorPtr * @returns {number} */ wgpuTextureCreateView: (textureIdx, descriptorPtr) => { const texture = this.textures.get(textureIdx); /** @type {?GPUTextureViewDescriptor} */ let descriptor; if (descriptorPtr != 0) { descriptor = { label: this.mem.loadCstring(descriptorPtr + 4), format: this.enumeration("TextureFormat", descriptorPtr + 8), dimension: this.enumeration("TextureViewDimension", descriptorPtr + 12), baseMipLevel: this.mem.loadU32(descriptorPtr + 16), mipLevelCount: this.mem.loadU32(descriptorPtr + 20), baseArrayLayer: this.mem.loadU32(descriptorPtr + 24), arrayLayerCount: this.mem.loadU32(descriptorPtr + 28), aspect: this.enumeration("TextureAspect", descriptorPtr + 32), }; if (descriptor.arrayLayerCount == 0xFFFFFFFF) { descriptor.arrayLayerCount = undefined; } if (descriptor.mipLevelCount == 0xFFFFFFFF) { descriptor.mipLevelCount = undefined; } } const textureView = texture.createView(descriptor); return this.textureViews.create(textureView); }, /** * @param {number} textureIdx */ wgpuTextureDestroy: (textureIdx) => { const texture = this.textures.get(textureIdx); texture.destroy(); }, /** * @param {number} textureIdx * @returns {number} */ wgpuTextureDepthOrArrayLayers: (textureIdx) => { const texture = this.textures.get(textureIdx); return texture.depthOrArrayLayers; }, /** * @param {number} textureIdx * @returns {number} */ wgpuTextureGetDimension: (textureIdx) => { const texture = this.textures.get(textureIdx); return this.enums.TextureDimension.indexOf(texture.dimension); }, /** * @param {number} textureIdx * @returns {number} */ wgpuTextureGetFormat: (textureIdx) => { const texture = this.textures.get(textureIdx); return this.enums.TextureFormat.indexOf(texture.format); }, /** * @param {number} textureIdx * @returns {number} */ wgpuTextureGetHeight: (textureIdx) => { const texture = this.textures.get(textureIdx); return texture.height; }, /** * @param {number} textureIdx * @returns {number} */ wgpuTextureGetMipLevelCount: (textureIdx) => { const texture = this.textures.get(textureIdx); return texture.mipLevelCount; }, /** * @param {number} textureIdx * @returns {number} */ wgpuTextureGetSampleCount: (textureIdx) => { const texture = this.textures.get(textureIdx); return texture.sampleCount; }, /** * @param {number} textureIdx * @returns {number} */ wgpuTextureGetUsage: (textureIdx) => { const texture = this.textures.get(textureIdx); return texture.usage; }, /** * @param {number} textureIdx * @returns {number} */ wgpuTextureGetWidth: (textureIdx) => { const texture = this.textures.get(textureIdx); return texture.width; }, ...this.textures.interface(true), /* ---------------------- TextureView ---------------------- */ ...this.textureViews.interface(true), }; } } /** @template T */ class WebGPUObjectManager { /** * @param {string} name * @param {WasmMemoryInterface} mem */ constructor(name, mem) { this.name = name; this.mem = mem; this.idx = 0; /** @type {Record} */ this.objects = {}; } /** * @param {T} object * @returns {number} */ create(object) { this.objects[this.idx] = { references: 1, object }; this.idx += 1; return this.idx; } /** * @param {number} idx * @returns {T} */ get(idx) { return this.objects[idx-1].object; } /** @param {number} idx */ release(idx) { this.objects[idx-1].references -= 1; if (this.objects[idx-1].references == 0) { delete this.objects[idx-1]; } } /** @param {number} idx */ reference(idx) { this.objects[idx-1].references += 1; } interface(withLabelSetter = false) { const inter = {}; inter[`wgpu${this.name}Reference`] = this.reference.bind(this); inter[`wgpu${this.name}Release`] = this.release.bind(this); if (withLabelSetter) { inter[`wgpu${this.name}SetLabel`] = (idx, labelPtr) => { const obj = this.get(idx); obj.label = this.mem.loadCstring(labelPtr); }; } return inter; } } window.odin = window.odin || {}; window.odin.WebGPUInterface = WebGPUInterface; })();