diff options
author | Ryan Dahl <ry@tinyclouds.org> | 2021-08-11 12:27:05 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-08-11 12:27:05 +0200 |
commit | a0285e2eb88f6254f6494b0ecd1878db3a3b2a58 (patch) | |
tree | 90671b004537e20f9493fd3277ffd21d30b39a0e /ext/webgpu | |
parent | 3a6994115176781b3a93d70794b1b81bc95e42b4 (diff) |
Rename extensions/ directory to ext/ (#11643)
Diffstat (limited to 'ext/webgpu')
-rw-r--r-- | ext/webgpu/01_webgpu.js | 5140 | ||||
-rw-r--r-- | ext/webgpu/02_idl_types.js | 1957 | ||||
-rw-r--r-- | ext/webgpu/Cargo.toml | 21 | ||||
-rw-r--r-- | ext/webgpu/README.md | 35 | ||||
-rw-r--r-- | ext/webgpu/binding.rs | 335 | ||||
-rw-r--r-- | ext/webgpu/buffer.rs | 241 | ||||
-rw-r--r-- | ext/webgpu/bundle.rs | 454 | ||||
-rw-r--r-- | ext/webgpu/command_encoder.rs | 692 | ||||
-rw-r--r-- | ext/webgpu/compute_pass.rs | 356 | ||||
-rw-r--r-- | ext/webgpu/error.rs | 294 | ||||
-rw-r--r-- | ext/webgpu/lib.deno_webgpu.d.ts | 1131 | ||||
-rw-r--r-- | ext/webgpu/lib.rs | 928 | ||||
-rw-r--r-- | ext/webgpu/pipeline.rs | 686 | ||||
-rw-r--r-- | ext/webgpu/queue.rs | 160 | ||||
-rw-r--r-- | ext/webgpu/render_pass.rs | 688 | ||||
-rw-r--r-- | ext/webgpu/sampler.rs | 120 | ||||
-rw-r--r-- | ext/webgpu/shader.rs | 70 | ||||
-rw-r--r-- | ext/webgpu/texture.rs | 244 | ||||
-rw-r--r-- | ext/webgpu/webgpu.idl | 1057 |
19 files changed, 14609 insertions, 0 deletions
diff --git a/ext/webgpu/01_webgpu.js b/ext/webgpu/01_webgpu.js new file mode 100644 index 000000000..50d4d6eba --- /dev/null +++ b/ext/webgpu/01_webgpu.js @@ -0,0 +1,5140 @@ +// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. + +// @ts-check +/// <reference path="../../core/lib.deno_core.d.ts" /> +/// <reference path="../web/internal.d.ts" /> +/// <reference path="../web/lib.deno_web.d.ts" /> +/// <reference path="./lib.deno_webgpu.d.ts" /> + +"use strict"; + +((window) => { + const core = window.Deno.core; + const webidl = window.__bootstrap.webidl; + const eventTarget = window.__bootstrap.eventTarget; + const { DOMException } = window.__bootstrap.domException; + const { + ArrayBuffer, + ArrayBufferIsView, + ArrayIsArray, + ArrayPrototypeFilter, + ArrayPrototypeMap, + ArrayPrototypePop, + ArrayPrototypePush, + Error, + MathMax, + ObjectDefineProperty, + ObjectFreeze, + Promise, + PromiseAll, + PromisePrototypeCatch, + PromisePrototypeThen, + PromiseReject, + PromiseResolve, + Set, + SetPrototypeEntries, + SetPrototypeForEach, + SetPrototypeHas, + SetPrototypeKeys, + SetPrototypeValues, + Symbol, + SymbolFor, + SymbolIterator, + TypeError, + Uint32Array, + Uint8Array, + } = window.__bootstrap.primordials; + + /** + * @param {any} self + * @param {{prefix: string, context: string}} opts + * @returns {InnerGPUDevice & {rid: number}} + */ + function assertDevice(self, { prefix, context }) { + const device = self[_device]; + const deviceRid = device?.rid; + if (deviceRid === undefined) { + throw new DOMException( + `${prefix}: ${context} references an invalid or destroyed device.`, + "OperationError", + ); + } + return device; + } + + /** + * @param {InnerGPUDevice} self + * @param {any} resource + * @param {{prefix: string, resourceContext: string, selfContext: string}} opts + * @returns {InnerGPUDevice & {rid: number}} + */ + function assertDeviceMatch( + self, + resource, + { prefix, resourceContext, selfContext }, + ) { + const resourceDevice = assertDevice(resource, { + prefix, + context: resourceContext, + }); + if (resourceDevice.rid !== self.rid) { + throw new DOMException( + `${prefix}: ${resourceContext} belongs to a diffent device than ${selfContext}.`, + "OperationError", + ); + } + return { ...resourceDevice, rid: resourceDevice.rid }; + } + + /** + * @param {any} self + * @param {{prefix: string, context: string}} opts + * @returns {number} + */ + function assertResource(self, { prefix, context }) { + const rid = self[_rid]; + if (rid === undefined) { + throw new DOMException( + `${prefix}: ${context} an invalid or destroyed resource.`, + "OperationError", + ); + } + return rid; + } + + /** + * @param {number[] | GPUExtent3DDict} data + * @returns {GPUExtent3DDict} + */ + function normalizeGPUExtent3D(data) { + if (ArrayIsArray(data)) { + return { + width: data[0], + height: data[1], + depthOrArrayLayers: data[2], + }; + } else { + return data; + } + } + + /** + * @param {number[] | GPUOrigin3DDict} data + * @returns {GPUOrigin3DDict} + */ + function normalizeGPUOrigin3D(data) { + if (ArrayIsArray(data)) { + return { + x: data[0], + y: data[1], + z: data[2], + }; + } else { + return data; + } + } + + /** + * @param {number[] | GPUColor} data + * @returns {GPUColor} + */ + function normalizeGPUColor(data) { + if (ArrayIsArray(data)) { + return { + r: data[0], + g: data[1], + b: data[2], + a: data[3], + }; + } else { + return data; + } + } + + class GPUOutOfMemoryError extends Error { + name = "GPUOutOfMemoryError"; + constructor() { + super("device out of memory"); + } + } + + class GPUValidationError extends Error { + name = "GPUValidationError"; + /** @param {string} message */ + constructor(message) { + const prefix = "Failed to construct 'GPUValidationError'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + message = webidl.converters.DOMString(message, { + prefix, + context: "Argument 1", + }); + super(message); + } + } + + class GPU { + [webidl.brand] = webidl.brand; + + constructor() { + webidl.illegalConstructor(); + } + + /** + * @param {GPURequestAdapterOptions} options + */ + async requestAdapter(options = {}) { + webidl.assertBranded(this, GPU); + options = webidl.converters.GPURequestAdapterOptions(options, { + prefix: "Failed to execute 'requestAdapter' on 'GPU'", + context: "Argument 1", + }); + + const { err, ...data } = await core.opAsync( + "op_webgpu_request_adapter", + { ...options }, + ); + + if (err) { + return null; + } else { + return createGPUAdapter(data.name, data); + } + } + + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return `${this.constructor.name} ${inspect({})}`; + } + } + + const _name = Symbol("[[name]]"); + const _adapter = Symbol("[[adapter]]"); + const _cleanup = Symbol("[[cleanup]]"); + + /** + * @typedef InnerGPUAdapter + * @property {number} rid + * @property {GPUSupportedFeatures} features + * @property {GPUSupportedLimits} limits + * @property {boolean} isSoftware + */ + + /** + * @param {string} name + * @param {InnerGPUAdapter} inner + * @returns {GPUAdapter} + */ + function createGPUAdapter(name, inner) { + /** @type {GPUAdapter} */ + const adapter = webidl.createBranded(GPUAdapter); + adapter[_name] = name; + adapter[_adapter] = { + ...inner, + features: createGPUSupportedFeatures(inner.features), + limits: createGPUSupportedLimits(inner.limits), + }; + return adapter; + } + + class GPUAdapter { + /** @type {string} */ + [_name]; + /** @type {InnerGPUAdapter} */ + [_adapter]; + + /** @returns {string} */ + get name() { + webidl.assertBranded(this, GPUAdapter); + return this[_name]; + } + /** @returns {GPUSupportedFeatures} */ + get features() { + webidl.assertBranded(this, GPUAdapter); + return this[_adapter].features; + } + /** @returns {GPUSupportedLimits} */ + get limits() { + webidl.assertBranded(this, GPUAdapter); + return this[_adapter].limits; + } + /** @returns {boolean} */ + get isSoftware() { + return this[_adapter].isSoftware; + } + + constructor() { + webidl.illegalConstructor(); + } + + /** + * @param {GPUDeviceDescriptor} descriptor + * @returns {Promise<GPUDevice>} + */ + async requestDevice(descriptor = {}) { + webidl.assertBranded(this, GPUAdapter); + const prefix = "Failed to execute 'requestDevice' on 'GPUAdapter'"; + descriptor = webidl.converters.GPUDeviceDescriptor(descriptor, { + prefix, + context: "Argument 1", + }); + const requiredFeatures = descriptor.requiredFeatures ?? []; + for (const feature of requiredFeatures) { + if (!SetPrototypeHas(this[_adapter].features[_features], feature)) { + throw new TypeError( + `${prefix}: nonGuaranteedFeatures must be a subset of the adapter features.`, + ); + } + } + const requiredLimits = descriptor.requiredLimits; + // TODO(lucacasonato): validate requiredLimits + + const { rid, features, limits } = await core.opAsync( + "op_webgpu_request_device", + { + adapterRid: this[_adapter].rid, + labe: descriptor.label, + requiredFeatures, + requiredLimits, + }, + ); + + const inner = new InnerGPUDevice({ + rid, + adapter: this, + features: ObjectFreeze(features), + limits: ObjectFreeze(limits), + }); + return createGPUDevice( + descriptor.label ?? null, + inner, + createGPUQueue(descriptor.label ?? null, inner), + ); + } + + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ + name: this.name, + features: this.features, + limits: this.limits, + }) + }`; + } + } + + const _limits = Symbol("[[limits]]"); + + function createGPUSupportedLimits(features) { + /** @type {GPUSupportedLimits} */ + const adapterFeatures = webidl.createBranded(GPUSupportedLimits); + adapterFeatures[_limits] = features; + return adapterFeatures; + } + + /** + * @typedef InnerAdapterLimits + * @property {number} maxTextureDimension1D + * @property {number} maxTextureDimension2D + * @property {number} maxTextureDimension3D + * @property {number} maxTextureArrayLayers + * @property {number} maxBindGroups + * @property {number} maxDynamicUniformBuffersPerPipelineLayout + * @property {number} maxDynamicStorageBuffersPerPipelineLayout + * @property {number} maxSampledTexturesPerShaderStage + * @property {number} maxSamplersPerShaderStage + * @property {number} maxStorageBuffersPerShaderStage + * @property {number} maxStorageTexturesPerShaderStage + * @property {number} maxUniformBuffersPerShaderStage + * @property {number} maxUniformBufferBindingSize + * @property {number} maxStorageBufferBindingSize + * @property {number} minUniformBufferOffsetAlignment + * @property {number} minStorageBufferOffsetAlignment + * @property {number} maxVertexBuffers + * @property {number} maxVertexAttributes + * @property {number} maxVertexBufferArrayStride + * @property {number} maxInterStageShaderComponents + * @property {number} maxComputeWorkgroupStorageSize + * @property {number} maxComputeWorkgroupInvocations + * @property {number} maxComputePerDimensionDispatchSize + */ + + class GPUSupportedLimits { + /** @type {InnerAdapterLimits} */ + [_limits]; + constructor() { + webidl.illegalConstructor(); + } + + get maxTextureDimension1D() { + webidl.assertBranded(this, GPUSupportedLimits); + return this[_limits].maxTextureDimension1D; + } + get maxTextureDimension2D() { + webidl.assertBranded(this, GPUSupportedLimits); + return this[_limits].maxTextureDimension2D; + } + get maxTextureDimension3D() { + webidl.assertBranded(this, GPUSupportedLimits); + return this[_limits].maxTextureDimension3D; + } + get maxTextureArrayLayers() { + webidl.assertBranded(this, GPUSupportedLimits); + return this[_limits].maxTextureArrayLayers; + } + get maxBindGroups() { + webidl.assertBranded(this, GPUSupportedLimits); + return this[_limits].maxBindGroups; + } + get maxDynamicUniformBuffersPerPipelineLayout() { + webidl.assertBranded(this, GPUSupportedLimits); + return this[_limits].maxDynamicUniformBuffersPerPipelineLayout; + } + get maxDynamicStorageBuffersPerPipelineLayout() { + webidl.assertBranded(this, GPUSupportedLimits); + return this[_limits].maxDynamicStorageBuffersPerPipelineLayout; + } + get maxSampledTexturesPerShaderStage() { + webidl.assertBranded(this, GPUSupportedLimits); + return this[_limits].maxSampledTexturesPerShaderStage; + } + get maxSamplersPerShaderStage() { + webidl.assertBranded(this, GPUSupportedLimits); + return this[_limits].maxSamplersPerShaderStage; + } + get maxStorageBuffersPerShaderStage() { + webidl.assertBranded(this, GPUSupportedLimits); + return this[_limits].maxStorageBuffersPerShaderStage; + } + get maxStorageTexturesPerShaderStage() { + webidl.assertBranded(this, GPUSupportedLimits); + return this[_limits].maxStorageTexturesPerShaderStage; + } + get maxUniformBuffersPerShaderStage() { + webidl.assertBranded(this, GPUSupportedLimits); + return this[_limits].maxUniformBuffersPerShaderStage; + } + get maxUniformBufferBindingSize() { + webidl.assertBranded(this, GPUSupportedLimits); + return this[_limits].maxUniformBufferBindingSize; + } + get maxStorageBufferBindingSize() { + webidl.assertBranded(this, GPUSupportedLimits); + return this[_limits].maxStorageBufferBindingSize; + } + get minUniformBufferOffsetAlignment() { + webidl.assertBranded(this, GPUSupportedLimits); + return this[_limits].minUniformBufferOffsetAlignment; + } + get minStorageBufferOffsetAlignment() { + webidl.assertBranded(this, GPUSupportedLimits); + return this[_limits].minStorageBufferOffsetAlignment; + } + get maxVertexBuffers() { + webidl.assertBranded(this, GPUSupportedLimits); + return this[_limits].maxVertexBuffers; + } + get maxVertexAttributes() { + webidl.assertBranded(this, GPUSupportedLimits); + return this[_limits].maxVertexAttributes; + } + get maxVertexBufferArrayStride() { + webidl.assertBranded(this, GPUSupportedLimits); + return this[_limits].maxVertexBufferArrayStride; + } + get maxInterStageShaderComponents() { + webidl.assertBranded(this, GPUSupportedLimits); + return this[_limits].maxInterStageShaderComponents; + } + get maxComputeWorkgroupStorageSize() { + webidl.assertBranded(this, GPUSupportedLimits); + return this[_limits].maxComputeWorkgroupStorageSize; + } + get maxComputeWorkgroupInvocations() { + webidl.assertBranded(this, GPUSupportedLimits); + return this[_limits].maxComputeWorkgroupInvocations; + } + get maxComputePerDimensionDispatchSize() { + webidl.assertBranded(this, GPUSupportedLimits); + return this[_limits].maxComputePerDimensionDispatchSize; + } + + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return `${this.constructor.name} ${inspect(this[_limits])}`; + } + } + + const _features = Symbol("[[features]]"); + + function createGPUSupportedFeatures(features) { + /** @type {GPUSupportedFeatures} */ + const adapterFeatures = webidl.createBranded(GPUSupportedFeatures); + adapterFeatures[_features] = new Set(features); + return adapterFeatures; + } + + class GPUSupportedFeatures { + /** @type {Set<string>} */ + [_features]; + + constructor() { + webidl.illegalConstructor(); + } + + /** @return {IterableIterator<[string, string]>} */ + entries() { + webidl.assertBranded(this, GPUSupportedFeatures); + return SetPrototypeEntries(this[_features]); + } + + /** @return {void} */ + forEach(callbackfn, thisArg) { + webidl.assertBranded(this, GPUSupportedFeatures); + SetPrototypeForEach(this[_features], callbackfn, thisArg); + } + + /** @return {boolean} */ + has(value) { + webidl.assertBranded(this, GPUSupportedFeatures); + return SetPrototypeHas(this[_features], value); + } + + /** @return {IterableIterator<string>} */ + keys() { + webidl.assertBranded(this, GPUSupportedFeatures); + return SetPrototypeKeys(this[_features]); + } + + /** @return {IterableIterator<string>} */ + values() { + webidl.assertBranded(this, GPUSupportedFeatures); + return SetPrototypeValues(this[_features]); + } + + /** @return {number} */ + get size() { + webidl.assertBranded(this, GPUSupportedFeatures); + return this[_features].size; + } + + [SymbolIterator]() { + webidl.assertBranded(this, GPUSupportedFeatures); + return this[_features][SymbolIterator](); + } + + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return `${this.constructor.name} ${inspect([...this.values()])}`; + } + } + + const _reason = Symbol("[[reason]]"); + const _message = Symbol("[[message]]"); + + /** + * + * @param {string | undefined} reason + * @param {string} message + * @returns {GPUDeviceLostInfo} + */ + function createGPUDeviceLostInfo(reason, message) { + /** @type {GPUDeviceLostInfo} */ + const deviceLostInfo = webidl.createBranded(GPUDeviceLostInfo); + deviceLostInfo[_reason] = reason; + deviceLostInfo[_message] = message; + return deviceLostInfo; + } + + class GPUDeviceLostInfo { + /** @type {string | undefined} */ + [_reason]; + /** @type {string} */ + [_message]; + + constructor() { + webidl.illegalConstructor(); + } + + get reason() { + webidl.assertBranded(this, GPUDeviceLostInfo); + return this[_reason]; + } + get message() { + webidl.assertBranded(this, GPUDeviceLostInfo); + return this[_message]; + } + + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ reason: this[_reason], message: this[_message] }) + }`; + } + } + + const _label = Symbol("[[label]]"); + + /** + * @param {string} name + * @param {any} type + */ + function GPUObjectBaseMixin(name, type) { + type.prototype[_label] = null; + ObjectDefineProperty(type.prototype, "label", { + /** + * @return {string | null} + */ + get() { + webidl.assertBranded(this, type); + return this[_label]; + }, + /** + * @param {string | null} label + */ + set(label) { + webidl.assertBranded(this, type); + label = webidl.converters["UVString?"](label, { + prefix: `Failed to set 'label' on '${name}'`, + context: "Argument 1", + }); + this[_label] = label; + }, + }); + } + + const _device = Symbol("[[device]]"); + const _queue = Symbol("[[queue]]"); + + /** + * @typedef ErrorScope + * @property {string} filter + * @property {Promise<void>[]} operations + */ + + /** + * @typedef InnerGPUDeviceOptions + * @property {GPUAdapter} adapter + * @property {number | undefined} rid + * @property {GPUFeatureName[]} features + * @property {object} limits + */ + + class InnerGPUDevice { + /** @type {GPUAdapter} */ + adapter; + /** @type {number | undefined} */ + rid; + /** @type {GPUFeatureName[]} */ + features; + /** @type {object} */ + limits; + /** @type {WeakRef<any>[]} */ + resources; + /** @type {boolean} */ + isLost; + /** @type {Promise<GPUDeviceLostInfo>} */ + lost; + /** @type {(info: GPUDeviceLostInfo) => void} */ + resolveLost; + /** @type {ErrorScope[]} */ + errorScopeStack; + + /** + * @param {InnerGPUDeviceOptions} options + */ + constructor(options) { + this.adapter = options.adapter; + this.rid = options.rid; + this.features = options.features; + this.limits = options.limits; + this.resources = []; + this.isLost = false; + this.resolveLost = () => {}; + this.lost = new Promise((resolve) => { + this.resolveLost = resolve; + }); + this.errorScopeStack = []; + } + + /** @param {any} resource */ + trackResource(resource) { + ArrayPrototypePush(this.resources, new WeakRef(resource)); + } + + /** @param {{ type: string, value: string | null } | undefined} err */ + pushError(err) { + this.pushErrorPromise(PromiseResolve(err)); + } + + /** @param {Promise<{ type: string, value: string | null } | undefined>} promise */ + pushErrorPromise(promise) { + const operation = PromisePrototypeThen(promise, (err) => { + if (err) { + switch (err.type) { + case "lost": + this.isLost = true; + this.resolveLost( + createGPUDeviceLostInfo(undefined, "device was lost"), + ); + break; + case "validation": + return PromiseReject( + new GPUValidationError(err.value ?? "validation error"), + ); + case "out-of-memory": + return PromiseReject(new GPUOutOfMemoryError()); + } + } + }); + + const validationStack = ArrayPrototypeFilter( + this.errorScopeStack, + ({ filter }) => filter == "validation", + ); + const validationScope = validationStack[validationStack.length - 1]; + const validationFilteredPromise = PromisePrototypeCatch( + operation, + (err) => { + if (err instanceof GPUValidationError) return PromiseReject(err); + return PromiseResolve(); + }, + ); + if (validationScope) { + ArrayPrototypePush( + validationScope.operations, + validationFilteredPromise, + ); + } else { + PromisePrototypeCatch(validationFilteredPromise, () => { + // TODO(lucacasonato): emit an UncapturedErrorEvent + }); + } + // prevent uncaptured promise rejections + PromisePrototypeCatch(validationFilteredPromise, (_err) => {}); + + const oomStack = ArrayPrototypeFilter( + this.errorScopeStack, + ({ filter }) => filter == "out-of-memory", + ); + const oomScope = oomStack[oomStack.length - 1]; + const oomFilteredPromise = PromisePrototypeCatch(operation, (err) => { + if (err instanceof GPUOutOfMemoryError) return PromiseReject(err); + return PromiseResolve(); + }); + if (oomScope) { + ArrayPrototypePush(oomScope.operations, oomFilteredPromise); + } else { + PromisePrototypeCatch(oomFilteredPromise, () => { + // TODO(lucacasonato): emit an UncapturedErrorEvent + }); + } + // prevent uncaptured promise rejections + PromisePrototypeCatch(oomFilteredPromise, (_err) => {}); + } + } + + /** + * @param {string | null} label + * @param {InnerGPUDevice} inner + * @param {GPUQueue} queue + * @returns {GPUDevice} + */ + function createGPUDevice(label, inner, queue) { + /** @type {GPUDevice} */ + const device = webidl.createBranded(GPUDevice); + device[_label] = label; + device[_device] = inner; + device[_queue] = queue; + return device; + } + + // TODO(@crowlKats): https://gpuweb.github.io/gpuweb/#errors-and-debugging + class GPUDevice extends eventTarget.EventTarget { + /** @type {InnerGPUDevice} */ + [_device]; + + /** @type {GPUQueue} */ + [_queue]; + + [_cleanup]() { + const device = this[_device]; + const resources = device.resources; + while (resources.length > 0) { + const resource = ArrayPrototypePop(resources)?.deref(); + if (resource) { + resource[_cleanup](); + } + } + const rid = device.rid; + if (rid !== undefined) { + core.close(rid); + /** @type {number | undefined} */ + device.rid = undefined; + } + } + + get features() { + webidl.assertBranded(this, GPUDevice); + return this[_device].features; + } + get limits() { + webidl.assertBranded(this, GPUDevice); + return this[_device].limits; + } + get queue() { + webidl.assertBranded(this, GPUDevice); + return this[_queue]; + } + + constructor() { + webidl.illegalConstructor(); + super(); + } + + destroy() { + webidl.assertBranded(this, GPUDevice); + this[_cleanup](); + } + + /** + * @param {GPUBufferDescriptor} descriptor + * @returns {GPUBuffer} + */ + createBuffer(descriptor) { + webidl.assertBranded(this, GPUDevice); + const prefix = "Failed to execute 'createBuffer' on 'GPUDevice'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + descriptor = webidl.converters.GPUBufferDescriptor(descriptor, { + prefix, + context: "Argument 1", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const { rid, err } = core.opSync("op_webgpu_create_buffer", { + deviceRid: device.rid, + ...descriptor, + }); + device.pushError(err); + /** @type {CreateGPUBufferOptions} */ + let options; + if (descriptor.mappedAtCreation) { + options = { + mapping: new ArrayBuffer(descriptor.size), + mappingRange: [0, descriptor.size], + mappedRanges: [], + state: "mapped at creation", + }; + } else { + options = { + mapping: null, + mappedRanges: null, + mappingRange: null, + state: "unmapped", + }; + } + const buffer = createGPUBuffer( + descriptor.label ?? null, + device, + rid, + descriptor.size, + descriptor.usage, + options, + ); + device.trackResource((buffer)); + return buffer; + } + + /** + * @param {GPUTextureDescriptor} descriptor + * @returns {GPUTexture} + */ + createTexture(descriptor) { + webidl.assertBranded(this, GPUDevice); + const prefix = "Failed to execute 'createTexture' on 'GPUDevice'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + descriptor = webidl.converters.GPUTextureDescriptor(descriptor, { + prefix, + context: "Argument 1", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const { rid, err } = core.opSync("op_webgpu_create_texture", { + deviceRid: device.rid, + ...descriptor, + size: normalizeGPUExtent3D(descriptor.size), + }); + device.pushError(err); + + const texture = createGPUTexture( + descriptor.label ?? null, + device, + rid, + ); + device.trackResource((texture)); + return texture; + } + + /** + * @param {GPUSamplerDescriptor} descriptor + * @returns {GPUSampler} + */ + createSampler(descriptor = {}) { + webidl.assertBranded(this, GPUDevice); + const prefix = "Failed to execute 'createSampler' on 'GPUDevice'"; + descriptor = webidl.converters.GPUSamplerDescriptor(descriptor, { + prefix, + context: "Argument 1", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const { rid, err } = core.opSync("op_webgpu_create_sampler", { + deviceRid: device.rid, + ...descriptor, + }); + device.pushError(err); + + const sampler = createGPUSampler( + descriptor.label ?? null, + device, + rid, + ); + device.trackResource((sampler)); + return sampler; + } + + /** + * @param {GPUBindGroupLayoutDescriptor} descriptor + * @returns {GPUBindGroupLayout} + */ + createBindGroupLayout(descriptor) { + webidl.assertBranded(this, GPUDevice); + const prefix = "Failed to execute 'createBindGroupLayout' on 'GPUDevice'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + descriptor = webidl.converters.GPUBindGroupLayoutDescriptor(descriptor, { + prefix, + context: "Argument 1", + }); + const device = assertDevice(this, { prefix, context: "this" }); + for (const entry of descriptor.entries) { + let i = 0; + if (entry.buffer) i++; + if (entry.sampler) i++; + if (entry.texture) i++; + if (entry.storageTexture) i++; + + if (i !== 1) { + throw new Error(); // TODO(@crowlKats): correct error + } + } + + const { rid, err } = core.opSync( + "op_webgpu_create_bind_group_layout", + { + deviceRid: device.rid, + ...descriptor, + }, + ); + device.pushError(err); + + const bindGroupLayout = createGPUBindGroupLayout( + descriptor.label ?? null, + device, + rid, + ); + device.trackResource((bindGroupLayout)); + return bindGroupLayout; + } + + /** + * @param {GPUPipelineLayoutDescriptor} descriptor + * @returns {GPUPipelineLayout} + */ + createPipelineLayout(descriptor) { + webidl.assertBranded(this, GPUDevice); + const prefix = "Failed to execute 'createPipelineLayout' on 'GPUDevice'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + descriptor = webidl.converters.GPUPipelineLayoutDescriptor(descriptor, { + prefix, + context: "Argument 1", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const bindGroupLayouts = ArrayPrototypeMap( + descriptor.bindGroupLayouts, + (layout, i) => { + const context = `bind group layout ${i + 1}`; + const rid = assertResource(layout, { prefix, context }); + assertDeviceMatch(device, layout, { + prefix, + selfContext: "this", + resourceContext: context, + }); + return rid; + }, + ); + const { rid, err } = core.opSync("op_webgpu_create_pipeline_layout", { + deviceRid: device.rid, + label: descriptor.label, + bindGroupLayouts, + }); + device.pushError(err); + + const pipelineLayout = createGPUPipelineLayout( + descriptor.label ?? null, + device, + rid, + ); + device.trackResource((pipelineLayout)); + return pipelineLayout; + } + + /** + * @param {GPUBindGroupDescriptor} descriptor + * @returns {GPUBindGroup} + */ + createBindGroup(descriptor) { + webidl.assertBranded(this, GPUDevice); + const prefix = "Failed to execute 'createBindGroup' on 'GPUDevice'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + descriptor = webidl.converters.GPUBindGroupDescriptor(descriptor, { + prefix, + context: "Argument 1", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const layout = assertResource(descriptor.layout, { + prefix, + context: "layout", + }); + assertDeviceMatch(device, descriptor.layout, { + prefix, + resourceContext: "layout", + selfContext: "this", + }); + const entries = ArrayPrototypeMap(descriptor.entries, (entry, i) => { + const context = `entry ${i + 1}`; + const resource = entry.resource; + if (resource instanceof GPUSampler) { + const rid = assertResource(resource, { + prefix, + context, + }); + assertDeviceMatch(device, resource, { + prefix, + resourceContext: context, + selfContext: "this", + }); + return { + binding: entry.binding, + kind: "GPUSampler", + resource: rid, + }; + } else if (resource instanceof GPUTextureView) { + const rid = assertResource(resource, { + prefix, + context, + }); + assertResource(resource[_texture], { + prefix, + context, + }); + assertDeviceMatch(device, resource[_texture], { + prefix, + resourceContext: context, + selfContext: "this", + }); + return { + binding: entry.binding, + kind: "GPUTextureView", + resource: rid, + }; + } else { + const rid = assertResource(resource.buffer, { prefix, context }); + assertDeviceMatch(device, resource.buffer, { + prefix, + resourceContext: context, + selfContext: "this", + }); + return { + binding: entry.binding, + kind: "GPUBufferBinding", + resource: rid, + offset: entry.resource.offset, + size: entry.resource.size, + }; + } + }); + + const { rid, err } = core.opSync("op_webgpu_create_bind_group", { + deviceRid: device.rid, + label: descriptor.label, + layout, + entries, + }); + device.pushError(err); + + const bindGroup = createGPUBindGroup( + descriptor.label ?? null, + device, + rid, + ); + device.trackResource((bindGroup)); + return bindGroup; + } + + /** + * @param {GPUShaderModuleDescriptor} descriptor + */ + createShaderModule(descriptor) { + webidl.assertBranded(this, GPUDevice); + const prefix = "Failed to execute 'createShaderModule' on 'GPUDevice'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + descriptor = webidl.converters.GPUShaderModuleDescriptor(descriptor, { + prefix, + context: "Argument 1", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const { rid, err } = core.opSync( + "op_webgpu_create_shader_module", + { + deviceRid: device.rid, + label: descriptor.label, + code: (typeof descriptor.code === "string") + ? descriptor.code + : undefined, + sourceMap: descriptor.sourceMap, + }, + ...(descriptor.code instanceof Uint32Array + ? [new Uint8Array(descriptor.code.buffer)] + : []), + ); + device.pushError(err); + + const shaderModule = createGPUShaderModule( + descriptor.label ?? null, + device, + rid, + ); + device.trackResource((shaderModule)); + return shaderModule; + } + + /** + * @param {GPUComputePipelineDescriptor} descriptor + * @returns {GPUComputePipeline} + */ + createComputePipeline(descriptor) { + webidl.assertBranded(this, GPUDevice); + const prefix = "Failed to execute 'createComputePipeline' on 'GPUDevice'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + descriptor = webidl.converters.GPUComputePipelineDescriptor(descriptor, { + prefix, + context: "Argument 1", + }); + const device = assertDevice(this, { prefix, context: "this" }); + let layout = undefined; + if (descriptor.layout) { + const context = "layout"; + layout = assertResource(descriptor.layout, { prefix, context }); + assertDeviceMatch(device, descriptor.layout, { + prefix, + resourceContext: context, + selfContext: "this", + }); + } + const module = assertResource(descriptor.compute.module, { + prefix, + context: "compute shader module", + }); + assertDeviceMatch(device, descriptor.compute.module, { + prefix, + resourceContext: "compute shader module", + selfContext: "this", + }); + + const { rid, err } = core.opSync( + "op_webgpu_create_compute_pipeline", + { + deviceRid: device.rid, + label: descriptor.label, + layout, + compute: { + module, + entryPoint: descriptor.compute.entryPoint, + constants: descriptor.compute.constants, + }, + }, + ); + device.pushError(err); + + const computePipeline = createGPUComputePipeline( + descriptor.label ?? null, + device, + rid, + ); + device.trackResource((computePipeline)); + return computePipeline; + } + + /** + * @param {GPURenderPipelineDescriptor} descriptor + * @returns {GPURenderPipeline} + */ + createRenderPipeline(descriptor) { + webidl.assertBranded(this, GPUDevice); + const prefix = "Failed to execute 'createRenderPipeline' on 'GPUDevice'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + descriptor = webidl.converters.GPURenderPipelineDescriptor(descriptor, { + prefix, + context: "Argument 1", + }); + const device = assertDevice(this, { prefix, context: "this" }); + let layout = undefined; + if (descriptor.layout) { + const context = "layout"; + layout = assertResource(descriptor.layout, { prefix, context }); + assertDeviceMatch(device, descriptor.layout, { + prefix, + resourceContext: context, + selfContext: "this", + }); + } + const module = assertResource(descriptor.vertex.module, { + prefix, + context: "vertex shader module", + }); + assertDeviceMatch(device, descriptor.vertex.module, { + prefix, + resourceContext: "vertex shader module", + selfContext: "this", + }); + let fragment = undefined; + if (descriptor.fragment) { + const module = assertResource(descriptor.fragment.module, { + prefix, + context: "fragment shader module", + }); + assertDeviceMatch(device, descriptor.fragment.module, { + prefix, + resourceContext: "fragment shader module", + selfContext: "this", + }); + fragment = { + module, + entryPoint: descriptor.fragment.entryPoint, + targets: descriptor.fragment.targets, + }; + } + + const { rid, err } = core.opSync("op_webgpu_create_render_pipeline", { + deviceRid: device.rid, + label: descriptor.label, + layout, + vertex: { + module, + entryPoint: descriptor.vertex.entryPoint, + buffers: descriptor.vertex.buffers, + }, + primitive: descriptor.primitive, + depthStencil: descriptor.depthStencil, + multisample: descriptor.multisample, + fragment, + }); + device.pushError(err); + + const renderPipeline = createGPURenderPipeline( + descriptor.label ?? null, + device, + rid, + ); + device.trackResource((renderPipeline)); + return renderPipeline; + } + + createComputePipelineAsync(descriptor) { + // TODO(lucacasonato): this should be real async + return PromiseResolve(this.createComputePipeline(descriptor)); + } + + createRenderPipelineAsync(descriptor) { + // TODO(lucacasonato): this should be real async + return PromiseResolve(this.createRenderPipeline(descriptor)); + } + + /** + * @param {GPUCommandEncoderDescriptor} descriptor + * @returns {GPUCommandEncoder} + */ + createCommandEncoder(descriptor = {}) { + webidl.assertBranded(this, GPUDevice); + const prefix = "Failed to execute 'createCommandEncoder' on 'GPUDevice'"; + descriptor = webidl.converters.GPUCommandEncoderDescriptor(descriptor, { + prefix, + context: "Argument 1", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const { rid, err } = core.opSync("op_webgpu_create_command_encoder", { + deviceRid: device.rid, + ...descriptor, + }); + device.pushError(err); + + const commandEncoder = createGPUCommandEncoder( + descriptor.label ?? null, + device, + rid, + ); + device.trackResource((commandEncoder)); + return commandEncoder; + } + + /** + * @param {GPURenderBundleEncoderDescriptor} descriptor + * @returns {GPURenderBundleEncoder} + */ + createRenderBundleEncoder(descriptor) { + webidl.assertBranded(this, GPUDevice); + const prefix = + "Failed to execute 'createRenderBundleEncoder' on 'GPUDevice'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + descriptor = webidl.converters.GPURenderBundleEncoderDescriptor( + descriptor, + { + prefix, + context: "Argument 1", + }, + ); + const device = assertDevice(this, { prefix, context: "this" }); + const { rid, err } = core.opSync( + "op_webgpu_create_render_bundle_encoder", + { + deviceRid: device.rid, + ...descriptor, + }, + ); + device.pushError(err); + + const renderBundleEncoder = createGPURenderBundleEncoder( + descriptor.label ?? null, + device, + rid, + ); + device.trackResource((renderBundleEncoder)); + return renderBundleEncoder; + } + + /** + * @param {GPUQuerySetDescriptor} descriptor + * @returns {GPUQuerySet} + */ + createQuerySet(descriptor) { + webidl.assertBranded(this, GPUDevice); + const prefix = "Failed to execute 'createQuerySet' on 'GPUDevice'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + descriptor = webidl.converters.GPUQuerySetDescriptor( + descriptor, + { + prefix, + context: "Argument 1", + }, + ); + const device = assertDevice(this, { prefix, context: "this" }); + const { rid, err } = core.opSync("op_webgpu_create_query_set", { + deviceRid: device.rid, + ...descriptor, + }); + device.pushError(err); + + const querySet = createGPUQuerySet( + descriptor.label ?? null, + device, + rid, + descriptor, + ); + device.trackResource((querySet)); + return querySet; + } + + get lost() { + webidl.assertBranded(this, GPUDevice); + const device = this[_device]; + if (!device) { + return PromiseResolve(true); + } + if (device.rid === undefined) { + return PromiseResolve(true); + } + return device.lost; + } + + /** + * @param {GPUErrorFilter} filter + */ + pushErrorScope(filter) { + webidl.assertBranded(this, GPUDevice); + const prefix = "Failed to execute 'pushErrorScope' on 'GPUDevice'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + filter = webidl.converters.GPUErrorFilter(filter, { + prefix, + context: "Argument 1", + }); + const device = assertDevice(this, { prefix, context: "this" }); + ArrayPrototypePush(device.errorScopeStack, { filter, operations: [] }); + } + + /** + * @returns {Promise<GPUError | null>} + */ + // deno-lint-ignore require-await + async popErrorScope() { + webidl.assertBranded(this, GPUDevice); + const prefix = "Failed to execute 'popErrorScope' on 'GPUDevice'"; + const device = assertDevice(this, { prefix, context: "this" }); + if (device.isLost) { + throw new DOMException("Device has been lost.", "OperationError"); + } + const scope = ArrayPrototypePop(device.errorScopeStack); + if (!scope) { + throw new DOMException( + "There are no error scopes on the error scope stack.", + "OperationError", + ); + } + const operations = PromiseAll(scope.operations); + return PromisePrototypeThen( + operations, + () => PromiseResolve(null), + (err) => PromiseResolve(err), + ); + } + + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ + features: this.features, + label: this.label, + limits: this.limits, + queue: this.queue, + }) + }`; + } + } + GPUObjectBaseMixin("GPUDevice", GPUDevice); + + /** + * @param {string | null} label + * @param {InnerGPUDevice} device + * @returns {GPUQueue} + */ + function createGPUQueue(label, device) { + /** @type {GPUQueue} */ + const queue = webidl.createBranded(GPUQueue); + queue[_label] = label; + queue[_device] = device; + return queue; + } + + class GPUQueue { + /** @type {InnerGPUDevice} */ + [_device]; + + constructor() { + webidl.illegalConstructor(); + } + + /** + * @param {GPUCommandBuffer[]} commandBuffers + */ + submit(commandBuffers) { + webidl.assertBranded(this, GPUQueue); + const prefix = "Failed to execute 'submit' on 'GPUQueue'"; + webidl.requiredArguments(arguments.length, 1, { + prefix, + }); + commandBuffers = webidl.converters["sequence<GPUCommandBuffer>"]( + commandBuffers, + { prefix, context: "Argument 1" }, + ); + const device = assertDevice(this, { prefix, context: "this" }); + const commandBufferRids = ArrayPrototypeMap( + commandBuffers, + (buffer, i) => { + const context = `command buffer ${i + 1}`; + const rid = assertResource(buffer, { prefix, context }); + assertDeviceMatch(device, buffer, { + prefix, + selfContext: "this", + resourceContext: context, + }); + return rid; + }, + ); + const { err } = core.opSync("op_webgpu_queue_submit", { + queueRid: device.rid, + commandBuffers: commandBufferRids, + }); + device.pushError(err); + } + + onSubmittedWorkDone() { + webidl.assertBranded(this, GPUQueue); + return PromiseResolve(); + } + + /** + * @param {GPUBuffer} buffer + * @param {number} bufferOffset + * @param {BufferSource} data + * @param {number} [dataOffset] + * @param {number} [size] + */ + writeBuffer(buffer, bufferOffset, data, dataOffset = 0, size) { + webidl.assertBranded(this, GPUQueue); + const prefix = "Failed to execute 'writeBuffer' on 'GPUQueue'"; + webidl.requiredArguments(arguments.length, 3, { prefix }); + buffer = webidl.converters["GPUBuffer"](buffer, { + prefix, + context: "Argument 1", + }); + bufferOffset = webidl.converters["GPUSize64"](bufferOffset, { + prefix, + context: "Argument 2", + }); + data = webidl.converters.BufferSource(data, { + prefix, + context: "Argument 3", + }); + dataOffset = webidl.converters["GPUSize64"](dataOffset, { + prefix, + context: "Argument 4", + }); + size = size === undefined + ? undefined + : webidl.converters["GPUSize64"](size, { + prefix, + context: "Argument 5", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const bufferRid = assertResource(buffer, { + prefix, + context: "Argument 1", + }); + assertDeviceMatch(device, buffer, { + prefix, + selfContext: "this", + resourceContext: "Argument 1", + }); + const { err } = core.opSync( + "op_webgpu_write_buffer", + { + queueRid: device.rid, + buffer: bufferRid, + bufferOffset, + dataOffset, + size, + }, + new Uint8Array(ArrayBufferIsView(data) ? data.buffer : data), + ); + device.pushError(err); + } + + /** + * @param {GPUImageCopyTexture} destination + * @param {BufferSource} data + * @param {GPUImageDataLayout} dataLayout + * @param {GPUExtent3D} size + */ + writeTexture(destination, data, dataLayout, size) { + webidl.assertBranded(this, GPUQueue); + const prefix = "Failed to execute 'writeTexture' on 'GPUQueue'"; + webidl.requiredArguments(arguments.length, 4, { prefix }); + destination = webidl.converters.GPUImageCopyTexture(destination, { + prefix, + context: "Argument 1", + }); + data = webidl.converters.BufferSource(data, { + prefix, + context: "Argument 2", + }); + dataLayout = webidl.converters.GPUImageDataLayout(dataLayout, { + prefix, + context: "Argument 3", + }); + size = webidl.converters.GPUExtent3D(size, { + prefix, + context: "Argument 4", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const textureRid = assertResource(destination.texture, { + prefix, + context: "texture", + }); + assertDeviceMatch(device, destination.texture, { + prefix, + selfContext: "this", + resourceContext: "texture", + }); + const { err } = core.opSync( + "op_webgpu_write_texture", + { + queueRid: device.rid, + destination: { + texture: textureRid, + mipLevel: destination.mipLevel, + origin: destination.origin + ? normalizeGPUOrigin3D(destination.origin) + : undefined, + }, + dataLayout, + size: normalizeGPUExtent3D(size), + }, + new Uint8Array(ArrayBufferIsView(data) ? data.buffer : data), + ); + device.pushError(err); + } + + copyImageBitmapToTexture(_source, _destination, _copySize) { + throw new Error("Not yet implemented"); + } + + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ + label: this.label, + }) + }`; + } + } + GPUObjectBaseMixin("GPUQueue", GPUQueue); + + const _rid = Symbol("[[rid]]"); + + const _size = Symbol("[[size]]"); + const _usage = Symbol("[[usage]]"); + const _state = Symbol("[[state]]"); + const _mappingRange = Symbol("[[mapping_range]]"); + const _mappedRanges = Symbol("[[mapped_ranges]]"); + const _mapMode = Symbol("[[map_mode]]"); + + /** + * @typedef CreateGPUBufferOptions + * @property {ArrayBuffer | null} mapping + * @property {number[] | null} mappingRange + * @property {[ArrayBuffer, number, number][] | null} mappedRanges + * @property {"mapped" | "mapped at creation" | "mapped pending" | "unmapped" | "destroy" } state + */ + + /** + * @param {string | null} label + * @param {InnerGPUDevice} device + * @param {number} rid + * @param {number} size + * @param {number} usage + * @param {CreateGPUBufferOptions} options + * @returns {GPUBuffer} + */ + function createGPUBuffer(label, device, rid, size, usage, options) { + /** @type {GPUBuffer} */ + const buffer = webidl.createBranded(GPUBuffer); + buffer[_label] = label; + buffer[_device] = device; + buffer[_rid] = rid; + buffer[_size] = size; + buffer[_usage] = usage; + buffer[_mappingRange] = options.mappingRange; + buffer[_mappedRanges] = options.mappedRanges; + buffer[_state] = options.state; + return buffer; + } + + class GPUBuffer { + /** @type {InnerGPUDevice} */ + [_device]; + + /** @type {number} */ + [_rid]; + + /** @type {number} */ + [_size]; + + /** @type {number} */ + [_usage]; + + /** @type {"mapped" | "mapped at creation" | "mapped pending" | "unmapped" | "destroy"} */ + [_state]; + + /** @type {[number, number] | null} */ + [_mappingRange]; + + /** @type {[ArrayBuffer, number, number][] | null} */ + [_mappedRanges]; + + /** @type {number} */ + [_mapMode]; + + [_cleanup]() { + const mappedRanges = this[_mappedRanges]; + if (mappedRanges) { + while (mappedRanges.length > 0) { + const mappedRange = ArrayPrototypePop(mappedRanges); + if (mappedRange !== undefined) { + core.close(mappedRange[1]); + } + } + } + const rid = this[_rid]; + if (rid !== undefined) { + core.close(rid); + /** @type {number | undefined} */ + this[_rid] = undefined; + } + this[_state] = "destroy"; + } + + constructor() { + webidl.illegalConstructor(); + } + + /** + * @param {number} mode + * @param {number} offset + * @param {number} [size] + */ + async mapAsync(mode, offset = 0, size) { + webidl.assertBranded(this, GPUBuffer); + const prefix = "Failed to execute 'mapAsync' on 'GPUBuffer'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + mode = webidl.converters.GPUMapModeFlags(mode, { + prefix, + context: "Argument 1", + }); + offset = webidl.converters.GPUSize64(offset, { + prefix, + context: "Argument 2", + }); + size = size === undefined + ? undefined + : webidl.converters.GPUSize64(size, { + prefix, + context: "Argument 3", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const bufferRid = assertResource(this, { prefix, context: "this" }); + /** @type {number} */ + let rangeSize; + if (size === undefined) { + rangeSize = MathMax(0, this[_size] - offset); + } else { + rangeSize = this[_size]; + } + if ((offset % 8) !== 0) { + throw new DOMException( + `${prefix}: offset must be a multiple of 8.`, + "OperationError", + ); + } + if ((rangeSize % 4) !== 0) { + throw new DOMException( + `${prefix}: rangeSize must be a multiple of 4.`, + "OperationError", + ); + } + if ((offset + rangeSize) > this[_size]) { + throw new DOMException( + `${prefix}: offset + rangeSize must be less than or equal to buffer size.`, + "OperationError", + ); + } + if (this[_state] !== "unmapped") { + throw new DOMException( + `${prefix}: GPUBuffer is not currently unmapped.`, + "OperationError", + ); + } + const readMode = (mode & 0x0001) === 0x0001; + const writeMode = (mode & 0x0002) === 0x0002; + if ((readMode && writeMode) || (!readMode && !writeMode)) { + throw new DOMException( + `${prefix}: exactly one of READ or WRITE map mode must be set.`, + "OperationError", + ); + } + if (readMode && !((this[_usage] && 0x0001) === 0x0001)) { + throw new DOMException( + `${prefix}: READ map mode not valid because buffer does not have MAP_READ usage.`, + "OperationError", + ); + } + if (writeMode && !((this[_usage] && 0x0002) === 0x0002)) { + throw new DOMException( + `${prefix}: WRITE map mode not valid because buffer does not have MAP_WRITE usage.`, + "OperationError", + ); + } + + this[_mapMode] = mode; + this[_state] = "mapping pending"; + const promise = PromisePrototypeThen( + core.opAsync( + "op_webgpu_buffer_get_map_async", + { + bufferRid, + deviceRid: device.rid, + mode, + offset, + size: rangeSize, + }, + ), + ({ err }) => err, + ); + device.pushErrorPromise(promise); + const err = await promise; + if (err) { + throw new DOMException("validation error occured", "OperationError"); + } + this[_state] = "mapped"; + this[_mappingRange] = [offset, offset + rangeSize]; + /** @type {[ArrayBuffer, number, number][] | null} */ + this[_mappedRanges] = []; + } + + /** + * @param {number} offset + * @param {number} size + */ + getMappedRange(offset = 0, size) { + webidl.assertBranded(this, GPUBuffer); + const prefix = "Failed to execute 'getMappedRange' on 'GPUBuffer'"; + offset = webidl.converters.GPUSize64(offset, { + prefix, + context: "Argument 1", + }); + if (size !== undefined) { + size = webidl.converters.GPUSize64(size, { + prefix, + context: "Argument 2", + }); + } + assertDevice(this, { prefix, context: "this" }); + const bufferRid = assertResource(this, { prefix, context: "this" }); + /** @type {number} */ + let rangeSize; + if (size === undefined) { + rangeSize = MathMax(0, this[_size] - offset); + } else { + rangeSize = size; + } + + const mappedRanges = this[_mappedRanges]; + if (!mappedRanges) { + throw new DOMException(`${prefix}: invalid state.`, "OperationError"); + } + for (const [buffer, _rid, start] of mappedRanges) { + // TODO(lucacasonato): is this logic correct? + const end = start + buffer.byteLength; + if ( + (start >= offset && start < (offset + rangeSize)) || + (end >= offset && end < (offset + rangeSize)) + ) { + throw new DOMException( + `${prefix}: requested buffer overlaps with another mapped range.`, + "OperationError", + ); + } + } + + const buffer = new ArrayBuffer(rangeSize); + const { rid } = core.opSync( + "op_webgpu_buffer_get_mapped_range", + { + bufferRid, + offset, + size, + }, + new Uint8Array(buffer), + ); + + ArrayPrototypePush(mappedRanges, [buffer, rid, offset]); + + return buffer; + } + + unmap() { + webidl.assertBranded(this, GPUBuffer); + const prefix = "Failed to execute 'unmap' on 'GPUBuffer'"; + const device = assertDevice(this, { prefix, context: "this" }); + const bufferRid = assertResource(this, { prefix, context: "this" }); + if (this[_state] === "unmapped" || this[_state] === "destroyed") { + throw new DOMException( + `${prefix}: buffer is not ready to be unmapped.`, + "OperationError", + ); + } + if (this[_state] === "mapping pending") { + // TODO(lucacasonato): this is not spec compliant. + throw new DOMException( + `${prefix}: can not unmap while mapping. This is a Deno limitation.`, + "OperationError", + ); + } else if ( + this[_state] === "mapped" || this[_state] === "mapped at creation" + ) { + /** @type {boolean} */ + let write = false; + if (this[_state] === "mapped at creation") { + write = true; + } else if (this[_state] === "mapped") { + const mapMode = this[_mapMode]; + if (mapMode === undefined) { + throw new DOMException( + `${prefix}: invalid state.`, + "OperationError", + ); + } + if ((mapMode & 0x0002) === 0x0002) { + write = true; + } + } + + const mappedRanges = this[_mappedRanges]; + if (!mappedRanges) { + throw new DOMException(`${prefix}: invalid state.`, "OperationError"); + } + for (const [buffer, mappedRid] of mappedRanges) { + const { err } = core.opSync("op_webgpu_buffer_unmap", { + bufferRid, + mappedRid, + }, ...(write ? [new Uint8Array(buffer)] : [])); + device.pushError(err); + if (err) return; + } + this[_mappingRange] = null; + this[_mappedRanges] = null; + } + + this[_state] = "unmapped"; + } + + destroy() { + webidl.assertBranded(this, GPUBuffer); + this[_cleanup](); + } + + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ + label: this.label, + }) + }`; + } + } + GPUObjectBaseMixin("GPUBuffer", GPUBuffer); + + class GPUBufferUsage { + constructor() { + webidl.illegalConstructor(); + } + + static get MAP_READ() { + return 0x0001; + } + static get MAP_WRITE() { + return 0x0002; + } + static get COPY_SRC() { + return 0x0004; + } + static get COPY_DST() { + return 0x0008; + } + static get INDEX() { + return 0x0010; + } + static get VERTEX() { + return 0x0020; + } + static get UNIFORM() { + return 0x0040; + } + static get STORAGE() { + return 0x0080; + } + static get INDIRECT() { + return 0x0100; + } + static get QUERY_RESOLVE() { + return 0x0200; + } + } + + class GPUMapMode { + constructor() { + webidl.illegalConstructor(); + } + + static get READ() { + return 0x0001; + } + static get WRITE() { + return 0x0002; + } + } + + const _views = Symbol("[[views]]"); + + /** + * @param {string | null} label + * @param {InnerGPUDevice} device + * @param {number} rid + * @returns {GPUTexture} + */ + function createGPUTexture(label, device, rid) { + /** @type {GPUTexture} */ + const texture = webidl.createBranded(GPUTexture); + texture[_label] = label; + texture[_device] = device; + texture[_rid] = rid; + texture[_views] = []; + return texture; + } + + class GPUTexture { + /** @type {InnerGPUDevice} */ + [_device]; + /** @type {number | undefined} */ + [_rid]; + /** @type {WeakRef<GPUTextureView>[]} */ + [_views]; + + [_cleanup]() { + const views = this[_views]; + while (views.length > 0) { + const view = ArrayPrototypePop(views)?.deref(); + if (view) { + view[_cleanup](); + } + } + const rid = this[_rid]; + if (rid !== undefined) { + core.close(rid); + /** @type {number | undefined} */ + this[_rid] = undefined; + } + } + + constructor() { + webidl.illegalConstructor(); + } + + /** + * @param {GPUTextureViewDescriptor} descriptor + */ + createView(descriptor = {}) { + webidl.assertBranded(this, GPUTexture); + const prefix = "Failed to execute 'createView' on 'GPUTexture'"; + webidl.requiredArguments(arguments.length, 0, { prefix }); + descriptor = webidl.converters.GPUTextureViewDescriptor(descriptor, { + prefix, + context: "Argument 1", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const textureRid = assertResource(this, { prefix, context: "this" }); + const { rid, err } = core.opSync("op_webgpu_create_texture_view", { + textureRid, + ...descriptor, + }); + device.pushError(err); + + const textureView = createGPUTextureView( + descriptor.label ?? null, + this, + rid, + ); + ArrayPrototypePush(this[_views], new WeakRef(textureView)); + return textureView; + } + + destroy() { + webidl.assertBranded(this, GPUTexture); + this[_cleanup](); + } + + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ + label: this.label, + }) + }`; + } + } + GPUObjectBaseMixin("GPUTexture", GPUTexture); + + class GPUTextureUsage { + constructor() { + webidl.illegalConstructor(); + } + + static get COPY_SRC() { + return 0x01; + } + static get COPY_DST() { + return 0x02; + } + static get SAMPLED() { + return 0x04; + } + static get STORAGE() { + return 0x08; + } + static get RENDER_ATTACHMENT() { + return 0x10; + } + } + + const _texture = Symbol("[[texture]]"); + + /** + * @param {string | null} label + * @param {GPUTexture} texture + * @param {number} rid + * @returns {GPUTextureView} + */ + function createGPUTextureView(label, texture, rid) { + /** @type {GPUTextureView} */ + const textureView = webidl.createBranded(GPUTextureView); + textureView[_label] = label; + textureView[_texture] = texture; + textureView[_rid] = rid; + return textureView; + } + class GPUTextureView { + /** @type {GPUTexture} */ + [_texture]; + /** @type {number | undefined} */ + [_rid]; + + [_cleanup]() { + const rid = this[_rid]; + if (rid !== undefined) { + core.close(rid); + /** @type {number | undefined} */ + this[_rid] = undefined; + } + } + + constructor() { + webidl.illegalConstructor(); + } + + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ + label: this.label, + }) + }`; + } + } + GPUObjectBaseMixin("GPUTextureView", GPUTextureView); + + /** + * @param {string | null} label + * @param {InnerGPUDevice} device + * @param {number} rid + * @returns {GPUSampler} + */ + function createGPUSampler(label, device, rid) { + /** @type {GPUSampler} */ + const sampler = webidl.createBranded(GPUSampler); + sampler[_label] = label; + sampler[_device] = device; + sampler[_rid] = rid; + return sampler; + } + class GPUSampler { + /** @type {InnerGPUDevice} */ + [_device]; + /** @type {number | undefined} */ + [_rid]; + + [_cleanup]() { + const rid = this[_rid]; + if (rid !== undefined) { + core.close(rid); + /** @type {number | undefined} */ + this[_rid] = undefined; + } + } + + constructor() { + webidl.illegalConstructor(); + } + + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ + label: this.label, + }) + }`; + } + } + GPUObjectBaseMixin("GPUSampler", GPUSampler); + + /** + * @param {string | null} label + * @param {InnerGPUDevice} device + * @param {number} rid + * @returns {GPUBindGroupLayout} + */ + function createGPUBindGroupLayout(label, device, rid) { + /** @type {GPUBindGroupLayout} */ + const bindGroupLayout = webidl.createBranded(GPUBindGroupLayout); + bindGroupLayout[_label] = label; + bindGroupLayout[_device] = device; + bindGroupLayout[_rid] = rid; + return bindGroupLayout; + } + class GPUBindGroupLayout { + /** @type {InnerGPUDevice} */ + [_device]; + /** @type {number | undefined} */ + [_rid]; + + [_cleanup]() { + const rid = this[_rid]; + if (rid !== undefined) { + core.close(rid); + /** @type {number | undefined} */ + this[_rid] = undefined; + } + } + + constructor() { + webidl.illegalConstructor(); + } + + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ + label: this.label, + }) + }`; + } + } + GPUObjectBaseMixin("GPUBindGroupLayout", GPUBindGroupLayout); + + /** + * @param {string | null} label + * @param {InnerGPUDevice} device + * @param {number} rid + * @returns {GPUPipelineLayout} + */ + function createGPUPipelineLayout(label, device, rid) { + /** @type {GPUPipelineLayout} */ + const pipelineLayout = webidl.createBranded(GPUPipelineLayout); + pipelineLayout[_label] = label; + pipelineLayout[_device] = device; + pipelineLayout[_rid] = rid; + return pipelineLayout; + } + class GPUPipelineLayout { + /** @type {InnerGPUDevice} */ + [_device]; + /** @type {number | undefined} */ + [_rid]; + + [_cleanup]() { + const rid = this[_rid]; + if (rid !== undefined) { + core.close(rid); + /** @type {number | undefined} */ + this[_rid] = undefined; + } + } + + constructor() { + webidl.illegalConstructor(); + } + + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ + label: this.label, + }) + }`; + } + } + GPUObjectBaseMixin("GPUPipelineLayout", GPUPipelineLayout); + + /** + * @param {string | null} label + * @param {InnerGPUDevice} device + * @param {number} rid + * @returns {GPUBindGroup} + */ + function createGPUBindGroup(label, device, rid) { + /** @type {GPUBindGroup} */ + const bindGroup = webidl.createBranded(GPUBindGroup); + bindGroup[_label] = label; + bindGroup[_device] = device; + bindGroup[_rid] = rid; + return bindGroup; + } + class GPUBindGroup { + /** @type {InnerGPUDevice} */ + [_device]; + /** @type {number | undefined} */ + [_rid]; + + [_cleanup]() { + const rid = this[_rid]; + if (rid !== undefined) { + core.close(rid); + /** @type {number | undefined} */ + this[_rid] = undefined; + } + } + + constructor() { + webidl.illegalConstructor(); + } + + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ + label: this.label, + }) + }`; + } + } + GPUObjectBaseMixin("GPUBindGroup", GPUBindGroup); + + /** + * @param {string | null} label + * @param {InnerGPUDevice} device + * @param {number} rid + * @returns {GPUShaderModule} + */ + function createGPUShaderModule(label, device, rid) { + /** @type {GPUShaderModule} */ + const bindGroup = webidl.createBranded(GPUShaderModule); + bindGroup[_label] = label; + bindGroup[_device] = device; + bindGroup[_rid] = rid; + return bindGroup; + } + class GPUShaderModule { + /** @type {InnerGPUDevice} */ + [_device]; + /** @type {number | undefined} */ + [_rid]; + + [_cleanup]() { + const rid = this[_rid]; + if (rid !== undefined) { + core.close(rid); + /** @type {number | undefined} */ + this[_rid] = undefined; + } + } + + constructor() { + webidl.illegalConstructor(); + } + + compilationInfo() { + throw new Error("Not yet implemented"); + } + + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ + label: this.label, + }) + }`; + } + } + GPUObjectBaseMixin("GPUShaderModule", GPUShaderModule); + + class GPUShaderStage { + constructor() { + webidl.illegalConstructor(); + } + + static get VERTEX() { + return 0x1; + } + + static get FRAGMENT() { + return 0x2; + } + + static get COMPUTE() { + return 0x4; + } + } + + /** + * @param {string | null} label + * @param {InnerGPUDevice} device + * @param {number} rid + * @returns {GPUComputePipeline} + */ + function createGPUComputePipeline(label, device, rid) { + /** @type {GPUComputePipeline} */ + const pipeline = webidl.createBranded(GPUComputePipeline); + pipeline[_label] = label; + pipeline[_device] = device; + pipeline[_rid] = rid; + return pipeline; + } + class GPUComputePipeline { + /** @type {InnerGPUDevice} */ + [_device]; + /** @type {number | undefined} */ + [_rid]; + + [_cleanup]() { + const rid = this[_rid]; + if (rid !== undefined) { + core.close(rid); + /** @type {number | undefined} */ + this[_rid] = undefined; + } + } + + constructor() { + webidl.illegalConstructor(); + } + + /** + * @param {number} index + * @returns {GPUBindGroupLayout} + */ + getBindGroupLayout(index) { + webidl.assertBranded(this, GPUComputePipeline); + const prefix = + "Failed to execute 'getBindGroupLayout' on 'GPUComputePipeline'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + index = webidl.converters["unsigned long"](index, { + prefix, + context: "Argument 1", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const computePipelineRid = assertResource(this, { + prefix, + context: "this", + }); + const { rid, label, err } = core.opSync( + "op_webgpu_compute_pipeline_get_bind_group_layout", + { computePipelineRid, index }, + ); + device.pushError(err); + + const bindGroupLayout = createGPUBindGroupLayout( + label, + device, + rid, + ); + device.trackResource((bindGroupLayout)); + return bindGroupLayout; + } + + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ + label: this.label, + }) + }`; + } + } + GPUObjectBaseMixin("GPUComputePipeline", GPUComputePipeline); + + /** + * @param {string | null} label + * @param {InnerGPUDevice} device + * @param {number} rid + * @returns {GPURenderPipeline} + */ + function createGPURenderPipeline(label, device, rid) { + /** @type {GPURenderPipeline} */ + const pipeline = webidl.createBranded(GPURenderPipeline); + pipeline[_label] = label; + pipeline[_device] = device; + pipeline[_rid] = rid; + return pipeline; + } + class GPURenderPipeline { + /** @type {InnerGPUDevice} */ + [_device]; + /** @type {number | undefined} */ + [_rid]; + + [_cleanup]() { + const rid = this[_rid]; + if (rid !== undefined) { + core.close(rid); + /** @type {number | undefined} */ + this[_rid] = undefined; + } + } + + constructor() { + webidl.illegalConstructor(); + } + + /** + * @param {number} index + */ + getBindGroupLayout(index) { + webidl.assertBranded(this, GPURenderPipeline); + const prefix = + "Failed to execute 'getBindGroupLayout' on 'GPURenderPipeline'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + index = webidl.converters["unsigned long"](index, { + prefix, + context: "Argument 1", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const renderPipelineRid = assertResource(this, { + prefix, + context: "this", + }); + const { rid, label, err } = core.opSync( + "op_webgpu_render_pipeline_get_bind_group_layout", + { renderPipelineRid, index }, + ); + device.pushError(err); + + const bindGroupLayout = createGPUBindGroupLayout( + label, + device, + rid, + ); + device.trackResource((bindGroupLayout)); + return bindGroupLayout; + } + + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ + label: this.label, + }) + }`; + } + } + GPUObjectBaseMixin("GPURenderPipeline", GPURenderPipeline); + + class GPUColorWrite { + constructor() { + webidl.illegalConstructor(); + } + + static get RED() { + return 0x1; + } + static get GREEN() { + return 0x2; + } + static get BLUE() { + return 0x4; + } + static get ALPHA() { + return 0x8; + } + static get ALL() { + return 0xF; + } + } + + const _encoders = Symbol("[[encoders]]"); + + /** + * @param {string | null} label + * @param {InnerGPUDevice} device + * @param {number} rid + * @returns {GPUCommandEncoder} + */ + function createGPUCommandEncoder(label, device, rid) { + /** @type {GPUCommandEncoder} */ + const encoder = webidl.createBranded(GPUCommandEncoder); + encoder[_label] = label; + encoder[_device] = device; + encoder[_rid] = rid; + encoder[_encoders] = []; + return encoder; + } + class GPUCommandEncoder { + /** @type {InnerGPUDevice} */ + [_device]; + /** @type {number | undefined} */ + [_rid]; + /** @type {WeakRef<GPURenderPassEncoder | GPUComputePassEncoder>[]} */ + [_encoders]; + + [_cleanup]() { + const encoders = this[_encoders]; + while (encoders.length > 0) { + const encoder = ArrayPrototypePop(encoders)?.deref(); + if (encoder) { + encoder[_cleanup](); + } + } + const rid = this[_rid]; + if (rid !== undefined) { + core.close(rid); + /** @type {number | undefined} */ + this[_rid] = undefined; + } + } + + constructor() { + webidl.illegalConstructor(); + } + + /** + * @param {GPURenderPassDescriptor} descriptor + * @return {GPURenderPassEncoder} + */ + beginRenderPass(descriptor) { + webidl.assertBranded(this, GPUCommandEncoder); + const prefix = + "Failed to execute 'beginRenderPass' on 'GPUCommandEncoder'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + descriptor = webidl.converters.GPURenderPassDescriptor(descriptor, { + prefix, + context: "Argument 1", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const commandEncoderRid = assertResource(this, { + prefix, + context: "this", + }); + + if (this[_rid] === undefined) { + throw new DOMException( + "Failed to execute 'beginRenderPass' on 'GPUCommandEncoder': already consumed", + "OperationError", + ); + } + + let depthStencilAttachment; + if (descriptor.depthStencilAttachment) { + const view = assertResource(descriptor.depthStencilAttachment.view, { + prefix, + context: "texture view for depth stencil attachment", + }); + assertDeviceMatch( + device, + descriptor.depthStencilAttachment.view[_texture], + { + prefix, + resourceContext: "texture view for depth stencil attachment", + selfContext: "this", + }, + ); + + depthStencilAttachment = { + ...descriptor.depthStencilAttachment, + view, + }; + + if ( + typeof descriptor.depthStencilAttachment.depthLoadValue === "string" + ) { + depthStencilAttachment.depthLoadOp = + descriptor.depthStencilAttachment.depthLoadValue; + } else { + depthStencilAttachment.depthLoadOp = "clear"; + depthStencilAttachment.depthLoadValue = + descriptor.depthStencilAttachment.depthLoadValue; + } + + if ( + typeof descriptor.depthStencilAttachment.stencilLoadValue === "string" + ) { + depthStencilAttachment.stencilLoadOp = + descriptor.depthStencilAttachment.stencilLoadValue; + depthStencilAttachment.stencilLoadValue = undefined; + } else { + depthStencilAttachment.stencilLoadOp = "clear"; + depthStencilAttachment.stencilLoadValue = + descriptor.depthStencilAttachment.stencilLoadValue; + } + } + const colorAttachments = ArrayPrototypeMap( + descriptor.colorAttachments, + (colorAttachment, i) => { + const context = `color attachment ${i + 1}`; + const view = assertResource(colorAttachment.view, { + prefix, + context: `texture view for ${context}`, + }); + assertResource(colorAttachment.view[_texture], { + prefix, + context: `texture backing texture view for ${context}`, + }); + assertDeviceMatch( + device, + colorAttachment.view[_texture], + { + prefix, + resourceContext: `texture view for ${context}`, + selfContext: "this", + }, + ); + let resolveTarget; + if (colorAttachment.resolveTarget) { + resolveTarget = assertResource( + colorAttachment.resolveTarget, + { + prefix, + context: `resolve target texture view for ${context}`, + }, + ); + assertResource(colorAttachment.resolveTarget[_texture], { + prefix, + context: + `texture backing resolve target texture view for ${context}`, + }); + assertDeviceMatch( + device, + colorAttachment.resolveTarget[_texture], + { + prefix, + resourceContext: `resolve target texture view for ${context}`, + selfContext: "this", + }, + ); + } + const attachment = { + view: view, + resolveTarget, + storeOp: colorAttachment.storeOp, + }; + + if (typeof colorAttachment.loadValue === "string") { + attachment.loadOp = colorAttachment.loadValue; + } else { + attachment.loadOp = "clear"; + attachment.loadValue = normalizeGPUColor( + colorAttachment.loadValue, + ); + } + + return attachment; + }, + ); + + const { rid } = core.opSync( + "op_webgpu_command_encoder_begin_render_pass", + { + commandEncoderRid, + ...descriptor, + colorAttachments, + depthStencilAttachment, + }, + ); + + const renderPassEncoder = createGPURenderPassEncoder( + descriptor.label ?? null, + this, + rid, + ); + ArrayPrototypePush(this[_encoders], new WeakRef(renderPassEncoder)); + return renderPassEncoder; + } + + /** + * @param {GPUComputePassDescriptor} descriptor + */ + beginComputePass(descriptor = {}) { + webidl.assertBranded(this, GPUCommandEncoder); + const prefix = + "Failed to execute 'beginComputePass' on 'GPUCommandEncoder'"; + descriptor = webidl.converters.GPUComputePassDescriptor(descriptor, { + prefix, + context: "Argument 1", + }); + + assertDevice(this, { prefix, context: "this" }); + const commandEncoderRid = assertResource(this, { + prefix, + context: "this", + }); + + const { rid } = core.opSync( + "op_webgpu_command_encoder_begin_compute_pass", + { + commandEncoderRid, + ...descriptor, + }, + ); + + const computePassEncoder = createGPUComputePassEncoder( + descriptor.label ?? null, + this, + rid, + ); + ArrayPrototypePush(this[_encoders], new WeakRef(computePassEncoder)); + return computePassEncoder; + } + + /** + * @param {GPUBuffer} source + * @param {number} sourceOffset + * @param {GPUBuffer} destination + * @param {number} destinationOffset + * @param {number} size + */ + copyBufferToBuffer( + source, + sourceOffset, + destination, + destinationOffset, + size, + ) { + webidl.assertBranded(this, GPUCommandEncoder); + const prefix = + "Failed to execute 'copyBufferToBuffer' on 'GPUCommandEncoder'"; + webidl.requiredArguments(arguments.length, 5, { prefix }); + source = webidl.converters.GPUBuffer(source, { + prefix, + context: "Argument 1", + }); + sourceOffset = webidl.converters.GPUSize64(sourceOffset, { + prefix, + context: "Argument 2", + }); + destination = webidl.converters.GPUBuffer(destination, { + prefix, + context: "Argument 3", + }); + destinationOffset = webidl.converters.GPUSize64(destinationOffset, { + prefix, + context: "Argument 4", + }); + size = webidl.converters.GPUSize64(size, { + prefix, + context: "Argument 5", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const commandEncoderRid = assertResource(this, { + prefix, + context: "this", + }); + const sourceRid = assertResource(source, { + prefix, + context: "Argument 1", + }); + assertDeviceMatch(device, source, { + prefix, + resourceContext: "Argument 1", + selfContext: "this", + }); + const destinationRid = assertResource(destination, { + prefix, + context: "Argument 3", + }); + assertDeviceMatch(device, destination, { + prefix, + resourceContext: "Argument 3", + selfContext: "this", + }); + + const { err } = core.opSync( + "op_webgpu_command_encoder_copy_buffer_to_buffer", + { + commandEncoderRid, + source: sourceRid, + sourceOffset, + destination: destinationRid, + destinationOffset, + size, + }, + ); + device.pushError(err); + } + + /** + * @param {GPUImageCopyBuffer} source + * @param {GPUImageCopyTexture} destination + * @param {GPUExtent3D} copySize + */ + copyBufferToTexture(source, destination, copySize) { + webidl.assertBranded(this, GPUCommandEncoder); + const prefix = + "Failed to execute 'copyBufferToTexture' on 'GPUCommandEncoder'"; + webidl.requiredArguments(arguments.length, 3, { prefix }); + source = webidl.converters.GPUImageCopyBuffer(source, { + prefix, + context: "Argument 1", + }); + destination = webidl.converters.GPUImageCopyTexture(destination, { + prefix, + context: "Argument 2", + }); + copySize = webidl.converters.GPUExtent3D(copySize, { + prefix, + context: "Argument 3", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const commandEncoderRid = assertResource(this, { + prefix, + context: "this", + }); + const sourceBufferRid = assertResource(source.buffer, { + prefix, + context: "source in Argument 1", + }); + assertDeviceMatch(device, source.buffer, { + prefix, + resourceContext: "source in Argument 1", + selfContext: "this", + }); + const destinationTextureRid = assertResource(destination.texture, { + prefix, + context: "texture in Argument 2", + }); + assertDeviceMatch(device, destination.texture, { + prefix, + resourceContext: "texture in Argument 2", + selfContext: "this", + }); + + const { err } = core.opSync( + "op_webgpu_command_encoder_copy_buffer_to_texture", + { + commandEncoderRid, + source: { + ...source, + buffer: sourceBufferRid, + }, + destination: { + texture: destinationTextureRid, + mipLevel: destination.mipLevel, + origin: destination.origin + ? normalizeGPUOrigin3D(destination.origin) + : undefined, + }, + copySize: normalizeGPUExtent3D(copySize), + }, + ); + device.pushError(err); + } + + /** + * @param {GPUImageCopyTexture} source + * @param {GPUImageCopyBuffer} destination + * @param {GPUExtent3D} copySize + */ + copyTextureToBuffer(source, destination, copySize) { + webidl.assertBranded(this, GPUCommandEncoder); + const prefix = + "Failed to execute 'copyTextureToBuffer' on 'GPUCommandEncoder'"; + webidl.requiredArguments(arguments.length, 3, { prefix }); + source = webidl.converters.GPUImageCopyTexture(source, { + prefix, + context: "Argument 1", + }); + destination = webidl.converters.GPUImageCopyBuffer(destination, { + prefix, + context: "Argument 2", + }); + copySize = webidl.converters.GPUExtent3D(copySize, { + prefix, + context: "Argument 3", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const commandEncoderRid = assertResource(this, { + prefix, + context: "this", + }); + const sourceTextureRid = assertResource(source.texture, { + prefix, + context: "texture in Argument 1", + }); + assertDeviceMatch(device, source.texture, { + prefix, + resourceContext: "texture in Argument 1", + selfContext: "this", + }); + const destinationBufferRid = assertResource(destination.buffer, { + prefix, + context: "buffer in Argument 2", + }); + assertDeviceMatch(device, destination.buffer, { + prefix, + resourceContext: "buffer in Argument 2", + selfContext: "this", + }); + const { err } = core.opSync( + "op_webgpu_command_encoder_copy_texture_to_buffer", + { + commandEncoderRid, + source: { + texture: sourceTextureRid, + mipLevel: source.mipLevel, + origin: source.origin + ? normalizeGPUOrigin3D(source.origin) + : undefined, + }, + destination: { + ...destination, + buffer: destinationBufferRid, + }, + copySize: normalizeGPUExtent3D(copySize), + }, + ); + device.pushError(err); + } + + /** + * @param {GPUImageCopyTexture} source + * @param {GPUImageCopyTexture} destination + * @param {GPUExtent3D} copySize + */ + copyTextureToTexture(source, destination, copySize) { + webidl.assertBranded(this, GPUCommandEncoder); + const prefix = + "Failed to execute 'copyTextureToTexture' on 'GPUCommandEncoder'"; + webidl.requiredArguments(arguments.length, 3, { prefix }); + source = webidl.converters.GPUImageCopyTexture(source, { + prefix, + context: "Argument 1", + }); + destination = webidl.converters.GPUImageCopyTexture(destination, { + prefix, + context: "Argument 2", + }); + copySize = webidl.converters.GPUExtent3D(copySize, { + prefix, + context: "Argument 3", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const commandEncoderRid = assertResource(this, { + prefix, + context: "this", + }); + const sourceTextureRid = assertResource(source.texture, { + prefix, + context: "texture in Argument 1", + }); + assertDeviceMatch(device, source.texture, { + prefix, + resourceContext: "texture in Argument 1", + selfContext: "this", + }); + const destinationTextureRid = assertResource(destination.texture, { + prefix, + context: "texture in Argument 2", + }); + assertDeviceMatch(device, destination.texture, { + prefix, + resourceContext: "texture in Argument 2", + selfContext: "this", + }); + const { err } = core.opSync( + "op_webgpu_command_encoder_copy_texture_to_texture", + { + commandEncoderRid, + source: { + texture: sourceTextureRid, + mipLevel: source.mipLevel, + origin: source.origin + ? normalizeGPUOrigin3D(source.origin) + : undefined, + }, + destination: { + texture: destinationTextureRid, + mipLevel: destination.mipLevel, + origin: destination.origin + ? normalizeGPUOrigin3D(destination.origin) + : undefined, + }, + copySize: normalizeGPUExtent3D(copySize), + }, + ); + device.pushError(err); + } + + /** + * @param {string} groupLabel + */ + pushDebugGroup(groupLabel) { + webidl.assertBranded(this, GPUCommandEncoder); + const prefix = + "Failed to execute 'pushDebugGroup' on 'GPUCommandEncoder'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + groupLabel = webidl.converters.USVString(groupLabel, { + prefix, + context: "Argument 1", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const commandEncoderRid = assertResource(this, { + prefix, + context: "this", + }); + const { err } = core.opSync( + "op_webgpu_command_encoder_push_debug_group", + { + commandEncoderRid, + groupLabel, + }, + ); + device.pushError(err); + } + + popDebugGroup() { + webidl.assertBranded(this, GPUCommandEncoder); + const prefix = "Failed to execute 'popDebugGroup' on 'GPUCommandEncoder'"; + const device = assertDevice(this, { prefix, context: "this" }); + const commandEncoderRid = assertResource(this, { + prefix, + context: "this", + }); + const { err } = core.opSync( + "op_webgpu_command_encoder_pop_debug_group", + { + commandEncoderRid, + }, + ); + device.pushError(err); + } + + /** + * @param {string} markerLabel + */ + insertDebugMarker(markerLabel) { + webidl.assertBranded(this, GPUCommandEncoder); + const prefix = + "Failed to execute 'insertDebugMarker' on 'GPUCommandEncoder'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + markerLabel = webidl.converters.USVString(markerLabel, { + prefix, + context: "Argument 1", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const commandEncoderRid = assertResource(this, { + prefix, + context: "this", + }); + const { err } = core.opSync( + "op_webgpu_command_encoder_insert_debug_marker", + { + commandEncoderRid, + markerLabel, + }, + ); + device.pushError(err); + } + + /** + * @param {GPUQuerySet} querySet + * @param {number} queryIndex + */ + writeTimestamp(querySet, queryIndex) { + webidl.assertBranded(this, GPUCommandEncoder); + const prefix = + "Failed to execute 'writeTimestamp' on 'GPUCommandEncoder'"; + webidl.requiredArguments(arguments.length, 2, { prefix }); + querySet = webidl.converters.GPUQuerySet(querySet, { + prefix, + context: "Argument 1", + }); + queryIndex = webidl.converters.GPUSize32(queryIndex, { + prefix, + context: "Argument 2", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const commandEncoderRid = assertResource(this, { + prefix, + context: "this", + }); + const querySetRid = assertResource(querySet, { + prefix, + context: "Argument 1", + }); + assertDeviceMatch(device, querySet, { + prefix, + resourceContext: "Argument 1", + selfContext: "this", + }); + const { err } = core.opSync( + "op_webgpu_command_encoder_write_timestamp", + { + commandEncoderRid, + querySet: querySetRid, + queryIndex, + }, + ); + device.pushError(err); + } + + /** + * @param {GPUQuerySet} querySet + * @param {number} firstQuery + * @param {number} queryCount + * @param {GPUBuffer} destination + * @param {number} destinationOffset + */ + resolveQuerySet( + querySet, + firstQuery, + queryCount, + destination, + destinationOffset, + ) { + webidl.assertBranded(this, GPUCommandEncoder); + const prefix = + "Failed to execute 'resolveQuerySet' on 'GPUCommandEncoder'"; + webidl.requiredArguments(arguments.length, 5, { prefix }); + querySet = webidl.converters.GPUQuerySet(querySet, { + prefix, + context: "Argument 1", + }); + firstQuery = webidl.converters.GPUSize32(firstQuery, { + prefix, + context: "Argument 2", + }); + queryCount = webidl.converters.GPUSize32(queryCount, { + prefix, + context: "Argument 3", + }); + destination = webidl.converters.GPUQuerySet(destination, { + prefix, + context: "Argument 4", + }); + destinationOffset = webidl.converters.GPUSize64(destinationOffset, { + prefix, + context: "Argument 5", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const commandEncoderRid = assertResource(this, { + prefix, + context: "this", + }); + const querySetRid = assertResource(querySet, { + prefix, + context: "Argument 1", + }); + assertDeviceMatch(device, querySet, { + prefix, + resourceContext: "Argument 1", + selfContext: "this", + }); + const destinationRid = assertResource(destination, { + prefix, + context: "Argument 3", + }); + assertDeviceMatch(device, destination, { + prefix, + resourceContext: "Argument 3", + selfContext: "this", + }); + const { err } = core.opSync( + "op_webgpu_command_encoder_resolve_query_set", + { + commandEncoderRid, + querySet: querySetRid, + firstQuery, + queryCount, + destination: destinationRid, + destinationOffset, + }, + ); + device.pushError(err); + } + + /** + * @param {GPUCommandBufferDescriptor} descriptor + * @returns {GPUCommandBuffer} + */ + finish(descriptor = {}) { + webidl.assertBranded(this, GPUCommandEncoder); + const prefix = "Failed to execute 'finish' on 'GPUCommandEncoder'"; + descriptor = webidl.converters.GPUCommandBufferDescriptor(descriptor, { + prefix, + context: "Argument 1", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const commandEncoderRid = assertResource(this, { + prefix, + context: "this", + }); + const { rid, err } = core.opSync("op_webgpu_command_encoder_finish", { + commandEncoderRid, + ...descriptor, + }); + device.pushError(err); + /** @type {number | undefined} */ + this[_rid] = undefined; + + const commandBuffer = createGPUCommandBuffer( + descriptor.label ?? null, + device, + rid, + ); + device.trackResource((commandBuffer)); + return commandBuffer; + } + + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ + label: this.label, + }) + }`; + } + } + GPUObjectBaseMixin("GPUCommandEncoder", GPUCommandEncoder); + + const _encoder = Symbol("[[encoder]]"); + + /** + * @param {string | null} label + * @param {GPUCommandEncoder} encoder + * @param {number} rid + * @returns {GPURenderPassEncoder} + */ + function createGPURenderPassEncoder(label, encoder, rid) { + /** @type {GPURenderPassEncoder} */ + const passEncoder = webidl.createBranded(GPURenderPassEncoder); + passEncoder[_label] = label; + passEncoder[_encoder] = encoder; + passEncoder[_rid] = rid; + return passEncoder; + } + + class GPURenderPassEncoder { + /** @type {GPUCommandEncoder} */ + [_encoder]; + /** @type {number | undefined} */ + [_rid]; + + [_cleanup]() { + const rid = this[_rid]; + if (rid !== undefined) { + core.close(rid); + /** @type {number | undefined} */ + this[_rid] = undefined; + } + } + + constructor() { + webidl.illegalConstructor(); + } + + /** + * @param {number} x + * @param {number} y + * @param {number} width + * @param {number} height + * @param {number} minDepth + * @param {number} maxDepth + */ + setViewport(x, y, width, height, minDepth, maxDepth) { + webidl.assertBranded(this, GPURenderPassEncoder); + const prefix = + "Failed to execute 'setViewport' on 'GPUComputePassEncoder'"; + webidl.requiredArguments(arguments.length, 6, { prefix }); + x = webidl.converters.float(x, { prefix, context: "Argument 1" }); + y = webidl.converters.float(y, { prefix, context: "Argument 2" }); + width = webidl.converters.float(width, { prefix, context: "Argument 3" }); + height = webidl.converters.float(height, { + prefix, + context: "Argument 4", + }); + minDepth = webidl.converters.float(minDepth, { + prefix, + context: "Argument 5", + }); + maxDepth = webidl.converters.float(maxDepth, { + prefix, + context: "Argument 6", + }); + assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const renderPassRid = assertResource(this, { prefix, context: "this" }); + core.opSync("op_webgpu_render_pass_set_viewport", { + renderPassRid, + x, + y, + width, + height, + minDepth, + maxDepth, + }); + } + + /** + * + * @param {number} x + * @param {number} y + * @param {number} width + * @param {number} height + */ + setScissorRect(x, y, width, height) { + webidl.assertBranded(this, GPURenderPassEncoder); + const prefix = + "Failed to execute 'setScissorRect' on 'GPUComputePassEncoder'"; + webidl.requiredArguments(arguments.length, 4, { prefix }); + x = webidl.converters.GPUIntegerCoordinate(x, { + prefix, + context: "Argument 1", + }); + y = webidl.converters.GPUIntegerCoordinate(y, { + prefix, + context: "Argument 2", + }); + width = webidl.converters.GPUIntegerCoordinate(width, { + prefix, + context: "Argument 3", + }); + height = webidl.converters.GPUIntegerCoordinate(height, { + prefix, + context: "Argument 4", + }); + assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const renderPassRid = assertResource(this, { prefix, context: "this" }); + core.opSync("op_webgpu_render_pass_set_scissor_rect", { + renderPassRid, + x, + y, + width, + height, + }); + } + + /** + * @param {GPUColor} color + */ + setBlendConstant(color) { + webidl.assertBranded(this, GPURenderPassEncoder); + const prefix = + "Failed to execute 'setBlendConstant' on 'GPUComputePassEncoder'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + color = webidl.converters.GPUColor(color, { + prefix, + context: "Argument 1", + }); + assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const renderPassRid = assertResource(this, { prefix, context: "this" }); + core.opSync("op_webgpu_render_pass_set_blend_constant", { + renderPassRid, + color: normalizeGPUColor(color), + }); + } + + /** + * @param {number} reference + */ + setStencilReference(reference) { + webidl.assertBranded(this, GPURenderPassEncoder); + const prefix = + "Failed to execute 'setStencilReference' on 'GPUComputePassEncoder'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + reference = webidl.converters.GPUStencilValue(reference, { + prefix, + context: "Argument 1", + }); + assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const renderPassRid = assertResource(this, { prefix, context: "this" }); + core.opSync("op_webgpu_render_pass_set_stencil_reference", { + renderPassRid, + reference, + }); + } + + beginOcclusionQuery(_queryIndex) { + throw new Error("Not yet implemented"); + } + + endOcclusionQuery() { + throw new Error("Not yet implemented"); + } + + /** + * @param {GPUQuerySet} querySet + * @param {number} queryIndex + */ + beginPipelineStatisticsQuery(querySet, queryIndex) { + webidl.assertBranded(this, GPURenderPassEncoder); + const prefix = + "Failed to execute 'beginPipelineStatisticsQuery' on 'GPURenderPassEncoder'"; + webidl.requiredArguments(arguments.length, 2, { prefix }); + querySet = webidl.converters.GPUQuerySet(querySet, { + prefix, + context: "Argument 1", + }); + queryIndex = webidl.converters.GPUSize32(queryIndex, { + prefix, + context: "Argument 2", + }); + const device = assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const renderPassRid = assertResource(this, { prefix, context: "this" }); + const querySetRid = assertResource(querySet, { + prefix, + context: "Argument 1", + }); + assertDeviceMatch(device, querySet, { + prefix, + resourceContext: "Argument 1", + selfContext: "this", + }); + core.opSync("op_webgpu_render_pass_begin_pipeline_statistics_query", { + renderPassRid, + querySet: querySetRid, + queryIndex, + }); + } + + endPipelineStatisticsQuery() { + webidl.assertBranded(this, GPURenderPassEncoder); + const prefix = + "Failed to execute 'endPipelineStatisticsQuery' on 'GPURenderPassEncoder'"; + assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const renderPassRid = assertResource(this, { prefix, context: "this" }); + core.opSync("op_webgpu_render_pass_end_pipeline_statistics_query", { + renderPassRid, + }); + } + + /** + * @param {GPUQuerySet} querySet + * @param {number} queryIndex + */ + writeTimestamp(querySet, queryIndex) { + webidl.assertBranded(this, GPURenderPassEncoder); + const prefix = + "Failed to execute 'writeTimestamp' on 'GPURenderPassEncoder'"; + webidl.requiredArguments(arguments.length, 2, { prefix }); + querySet = webidl.converters.GPUQuerySet(querySet, { + prefix, + context: "Argument 1", + }); + queryIndex = webidl.converters.GPUSize32(queryIndex, { + prefix, + context: "Argument 2", + }); + const device = assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const renderPassRid = assertResource(this, { prefix, context: "this" }); + const querySetRid = assertResource(querySet, { + prefix, + context: "Argument 1", + }); + assertDeviceMatch(device, querySet, { + prefix, + resourceContext: "Argument 1", + selfContext: "this", + }); + core.opSync("op_webgpu_render_pass_write_timestamp", { + renderPassRid, + querySet: querySetRid, + queryIndex, + }); + } + + /** + * @param {GPURenderBundle[]} bundles + */ + executeBundles(bundles) { + webidl.assertBranded(this, GPURenderPassEncoder); + const prefix = + "Failed to execute 'executeBundles' on 'GPURenderPassEncoder'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + bundles = webidl.converters["sequence<GPURenderBundle>"](bundles, { + prefix, + context: "Argument 1", + }); + const device = assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const renderPassRid = assertResource(this, { prefix, context: "this" }); + const bundleRids = ArrayPrototypeMap(bundles, (bundle, i) => { + const context = `bundle ${i + 1}`; + const rid = assertResource(bundle, { prefix, context }); + assertDeviceMatch(device, bundle, { + prefix, + resourceContext: context, + selfContext: "this", + }); + return rid; + }); + core.opSync("op_webgpu_render_pass_execute_bundles", { + renderPassRid, + bundles: bundleRids, + }); + } + + endPass() { + webidl.assertBranded(this, GPURenderPassEncoder); + const prefix = "Failed to execute 'endPass' on 'GPURenderPassEncoder'"; + const device = assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const commandEncoderRid = assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const renderPassRid = assertResource(this, { prefix, context: "this" }); + const { err } = core.opSync("op_webgpu_render_pass_end_pass", { + commandEncoderRid, + renderPassRid, + }); + device.pushError(err); + this[_rid] = undefined; + } + + // TODO(lucacasonato): has an overload + setBindGroup( + index, + bindGroup, + dynamicOffsetsData, + dynamicOffsetsDataStart, + dynamicOffsetsDataLength, + ) { + webidl.assertBranded(this, GPURenderPassEncoder); + const prefix = + "Failed to execute 'setBindGroup' on 'GPURenderPassEncoder'"; + const device = assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const renderPassRid = assertResource(this, { prefix, context: "this" }); + const bindGroupRid = assertResource(bindGroup, { + prefix, + context: "Argument 2", + }); + assertDeviceMatch(device, bindGroup, { + prefix, + resourceContext: "Argument 2", + selfContext: "this", + }); + if (dynamicOffsetsData instanceof Uint32Array) { + core.opSync( + "op_webgpu_render_pass_set_bind_group", + { + renderPassRid, + index, + bindGroup: bindGroupRid, + dynamicOffsetsDataStart, + dynamicOffsetsDataLength, + }, + dynamicOffsetsData, + ); + } else { + dynamicOffsetsData ??= []; + core.opSync("op_webgpu_render_pass_set_bind_group", { + renderPassRid, + index, + bindGroup: bindGroupRid, + dynamicOffsetsData, + dynamicOffsetsDataStart: 0, + dynamicOffsetsDataLength: dynamicOffsetsData.length, + }); + } + } + + /** + * @param {string} groupLabel + */ + pushDebugGroup(groupLabel) { + webidl.assertBranded(this, GPURenderPassEncoder); + const prefix = + "Failed to execute 'pushDebugGroup' on 'GPURenderPassEncoder'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + groupLabel = webidl.converters.USVString(groupLabel, { + prefix, + context: "Argument 1", + }); + assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const renderPassRid = assertResource(this, { prefix, context: "this" }); + core.opSync("op_webgpu_render_pass_push_debug_group", { + renderPassRid, + groupLabel, + }); + } + + popDebugGroup() { + webidl.assertBranded(this, GPURenderPassEncoder); + const prefix = + "Failed to execute 'popDebugGroup' on 'GPURenderPassEncoder'"; + assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const renderPassRid = assertResource(this, { prefix, context: "this" }); + core.opSync("op_webgpu_render_pass_pop_debug_group", { + renderPassRid, + }); + } + + /** + * @param {string} markerLabel + */ + insertDebugMarker(markerLabel) { + webidl.assertBranded(this, GPURenderPassEncoder); + const prefix = + "Failed to execute 'insertDebugMarker' on 'GPURenderPassEncoder'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + markerLabel = webidl.converters.USVString(markerLabel, { + prefix, + context: "Argument 1", + }); + assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const renderPassRid = assertResource(this, { prefix, context: "this" }); + core.opSync("op_webgpu_render_pass_insert_debug_marker", { + renderPassRid, + markerLabel, + }); + } + + /** + * @param {GPURenderPipeline} pipeline + */ + setPipeline(pipeline) { + webidl.assertBranded(this, GPURenderPassEncoder); + const prefix = + "Failed to execute 'setPipeline' on 'GPURenderPassEncoder'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + pipeline = webidl.converters.GPURenderPipeline(pipeline, { + prefix, + context: "Argument 1", + }); + const device = assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const renderPassRid = assertResource(this, { prefix, context: "this" }); + const pipelineRid = assertResource(pipeline, { + prefix, + context: "Argument 1", + }); + assertDeviceMatch(device, pipeline, { + prefix, + resourceContext: "Argument 1", + selfContext: "this", + }); + core.opSync("op_webgpu_render_pass_set_pipeline", { + renderPassRid, + pipeline: pipelineRid, + }); + } + + /** + * @param {GPUBuffer} buffer + * @param {GPUIndexFormat} indexFormat + * @param {number} offset + * @param {number} size + */ + setIndexBuffer(buffer, indexFormat, offset = 0, size) { + webidl.assertBranded(this, GPURenderPassEncoder); + const prefix = + "Failed to execute 'setIndexBuffer' on 'GPURenderPassEncoder'"; + webidl.requiredArguments(arguments.length, 2, { prefix }); + buffer = webidl.converters.GPUBuffer(buffer, { + prefix, + context: "Argument 1", + }); + indexFormat = webidl.converters.GPUIndexFormat(indexFormat, { + prefix, + context: "Argument 2", + }); + offset = webidl.converters.GPUSize64(offset, { + prefix, + context: "Argument 3", + }); + if (size !== undefined) { + size = webidl.converters.GPUSize64(size, { + prefix, + context: "Argument 4", + }); + } + const device = assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const renderPassRid = assertResource(this, { prefix, context: "this" }); + const bufferRid = assertResource(buffer, { + prefix, + context: "Argument 1", + }); + assertDeviceMatch(device, buffer, { + prefix, + resourceContext: "Argument 1", + selfContext: "this", + }); + core.opSync("op_webgpu_render_pass_set_index_buffer", { + renderPassRid, + buffer: bufferRid, + indexFormat, + offset, + size, + }); + } + + /** + * @param {number} slot + * @param {GPUBuffer} buffer + * @param {number} offset + * @param {number} size + */ + setVertexBuffer(slot, buffer, offset = 0, size) { + webidl.assertBranded(this, GPURenderPassEncoder); + const prefix = + "Failed to execute 'setVertexBuffer' on 'GPURenderPassEncoder'"; + webidl.requiredArguments(arguments.length, 2, { prefix }); + slot = webidl.converters.GPUSize32(slot, { + prefix, + context: "Argument 2", + }); + buffer = webidl.converters.GPUBuffer(buffer, { + prefix, + context: "Argument 2", + }); + offset = webidl.converters.GPUSize64(offset, { + prefix, + context: "Argument 3", + }); + if (size !== undefined) { + size = webidl.converters.GPUSize64(size, { + prefix, + context: "Argument 4", + }); + } + const device = assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const renderPassRid = assertResource(this, { prefix, context: "this" }); + const bufferRid = assertResource(buffer, { + prefix, + context: "Argument 2", + }); + assertDeviceMatch(device, buffer, { + prefix, + resourceContext: "Argument 2", + selfContext: "this", + }); + core.opSync("op_webgpu_render_pass_set_vertex_buffer", { + renderPassRid, + slot, + buffer: bufferRid, + offset, + size, + }); + } + + /** + * @param {number} vertexCount + * @param {number} instanceCount + * @param {number} firstVertex + * @param {number} firstInstance + */ + draw(vertexCount, instanceCount = 1, firstVertex = 0, firstInstance = 0) { + webidl.assertBranded(this, GPURenderPassEncoder); + const prefix = "Failed to execute 'draw' on 'GPURenderPassEncoder'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + vertexCount = webidl.converters.GPUSize32(vertexCount, { + prefix, + context: "Argument 1", + }); + instanceCount = webidl.converters.GPUSize32(instanceCount, { + prefix, + context: "Argument 2", + }); + firstVertex = webidl.converters.GPUSize32(firstVertex, { + prefix, + context: "Argument 3", + }); + firstInstance = webidl.converters.GPUSize32(firstInstance, { + prefix, + context: "Argument 4", + }); + assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const renderPassRid = assertResource(this, { prefix, context: "this" }); + core.opSync("op_webgpu_render_pass_draw", { + renderPassRid, + vertexCount, + instanceCount, + firstVertex, + firstInstance, + }); + } + + /** + * @param {number} indexCount + * @param {number} instanceCount + * @param {number} firstIndex + * @param {number} baseVertex + * @param {number} firstInstance + */ + drawIndexed( + indexCount, + instanceCount = 1, + firstIndex = 0, + baseVertex = 0, + firstInstance = 0, + ) { + webidl.assertBranded(this, GPURenderPassEncoder); + const prefix = + "Failed to execute 'drawIndexed' on 'GPURenderPassEncoder'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + indexCount = webidl.converters.GPUSize32(indexCount, { + prefix, + context: "Argument 1", + }); + instanceCount = webidl.converters.GPUSize32(instanceCount, { + prefix, + context: "Argument 2", + }); + firstIndex = webidl.converters.GPUSize32(firstIndex, { + prefix, + context: "Argument 3", + }); + baseVertex = webidl.converters.GPUSignedOffset32(baseVertex, { + prefix, + context: "Argument 4", + }); + firstInstance = webidl.converters.GPUSize32(firstInstance, { + prefix, + context: "Argument 5", + }); + assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const renderPassRid = assertResource(this, { prefix, context: "this" }); + core.opSync("op_webgpu_render_pass_draw_indexed", { + renderPassRid, + indexCount, + instanceCount, + firstIndex, + baseVertex, + firstInstance, + }); + } + + /** + * @param {GPUBuffer} indirectBuffer + * @param {number} indirectOffset + */ + drawIndirect(indirectBuffer, indirectOffset) { + webidl.assertBranded(this, GPURenderPassEncoder); + const prefix = + "Failed to execute 'drawIndirect' on 'GPURenderPassEncoder'"; + webidl.requiredArguments(arguments.length, 2, { prefix }); + indirectBuffer = webidl.converters.GPUBuffer(indirectBuffer, { + prefix, + context: "Argument 1", + }); + indirectOffset = webidl.converters.GPUSize64(indirectOffset, { + prefix, + context: "Argument 2", + }); + const device = assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const renderPassRid = assertResource(this, { prefix, context: "this" }); + const indirectBufferRid = assertResource(indirectBuffer, { + prefix, + context: "Argument 1", + }); + assertDeviceMatch(device, indirectBuffer, { + prefix, + resourceContext: "Argument 1", + selfContext: "this", + }); + core.opSync("op_webgpu_render_pass_draw_indirect", { + renderPassRid, + indirectBuffer: indirectBufferRid, + indirectOffset, + }); + } + + /** + * @param {GPUBuffer} indirectBuffer + * @param {number} indirectOffset + */ + drawIndexedIndirect(indirectBuffer, indirectOffset) { + webidl.assertBranded(this, GPURenderPassEncoder); + const prefix = + "Failed to execute 'drawIndirect' on 'GPURenderPassEncoder'"; + webidl.requiredArguments(arguments.length, 2, { prefix }); + indirectBuffer = webidl.converters.GPUBuffer(indirectBuffer, { + prefix, + context: "Argument 1", + }); + indirectOffset = webidl.converters.GPUSize64(indirectOffset, { + prefix, + context: "Argument 2", + }); + const device = assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const renderPassRid = assertResource(this, { prefix, context: "this" }); + const indirectBufferRid = assertResource(indirectBuffer, { + prefix, + context: "Argument 1", + }); + assertDeviceMatch(device, indirectBuffer, { + prefix, + resourceContext: "Argument 1", + selfContext: "this", + }); + core.opSync("op_webgpu_render_pass_draw_indexed_indirect", { + renderPassRid, + indirectBuffer: indirectBufferRid, + indirectOffset, + }); + } + + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ + label: this.label, + }) + }`; + } + } + GPUObjectBaseMixin("GPURenderPassEncoder", GPURenderPassEncoder); + + /** + * @param {string | null} label + * @param {GPUCommandEncoder} encoder + * @param {number} rid + * @returns {GPUComputePassEncoder} + */ + function createGPUComputePassEncoder(label, encoder, rid) { + /** @type {GPUComputePassEncoder} */ + const computePassEncoder = webidl.createBranded(GPUComputePassEncoder); + computePassEncoder[_label] = label; + computePassEncoder[_encoder] = encoder; + computePassEncoder[_rid] = rid; + return computePassEncoder; + } + + class GPUComputePassEncoder { + /** @type {GPUCommandEncoder} */ + [_encoder]; + + /** @type {number | undefined} */ + [_rid]; + + [_cleanup]() { + const rid = this[_rid]; + if (rid !== undefined) { + core.close(rid); + /** @type {number | undefined} */ + this[_rid] = undefined; + } + } + + constructor() { + webidl.illegalConstructor(); + } + + /** + * @param {GPUComputePipeline} pipeline + */ + setPipeline(pipeline) { + webidl.assertBranded(this, GPUComputePassEncoder); + const prefix = + "Failed to execute 'setPipeline' on 'GPUComputePassEncoder'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + pipeline = webidl.converters.GPUComputePipeline(pipeline, { + prefix, + context: "Argument 1", + }); + const device = assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const computePassRid = assertResource(this, { prefix, context: "this" }); + const pipelineRid = assertResource(pipeline, { + prefix, + context: "Argument 1", + }); + assertDeviceMatch(device, pipeline, { + prefix, + resourceContext: "Argument 1", + selfContext: "this", + }); + core.opSync("op_webgpu_compute_pass_set_pipeline", { + computePassRid, + pipeline: pipelineRid, + }); + } + + /** + * @param {number} x + * @param {number} y + * @param {number} z + */ + dispatch(x, y = 1, z = 1) { + webidl.assertBranded(this, GPUComputePassEncoder); + const prefix = "Failed to execute 'dispatch' on 'GPUComputePassEncoder'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + x = webidl.converters.GPUSize32(x, { prefix, context: "Argument 1" }); + y = webidl.converters.GPUSize32(y, { prefix, context: "Argument 2" }); + z = webidl.converters.GPUSize32(z, { prefix, context: "Argument 3" }); + assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const computePassRid = assertResource(this, { prefix, context: "this" }); + core.opSync("op_webgpu_compute_pass_dispatch", { + computePassRid, + x, + y, + z, + }); + } + + /** + * @param {GPUBuffer} indirectBuffer + * @param {number} indirectOffset + */ + dispatchIndirect(indirectBuffer, indirectOffset) { + webidl.assertBranded(this, GPUComputePassEncoder); + const prefix = + "Failed to execute 'dispatchIndirect' on 'GPUComputePassEncoder'"; + webidl.requiredArguments(arguments.length, 2, { prefix }); + indirectBuffer = webidl.converters.GPUBuffer(indirectBuffer, { + prefix, + context: "Argument 1", + }); + indirectOffset = webidl.converters.GPUSize64(indirectOffset, { + prefix, + context: "Argument 2", + }); + const device = assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const computePassRid = assertResource(this, { prefix, context: "this" }); + const indirectBufferRid = assertResource(indirectBuffer, { + prefix, + context: "Argument 1", + }); + assertDeviceMatch(device, indirectBuffer, { + prefix, + resourceContext: "Argument 1", + selfContext: "this", + }); + core.opSync("op_webgpu_compute_pass_dispatch_indirect", { + computePassRid: computePassRid, + indirectBuffer: indirectBufferRid, + indirectOffset, + }); + } + + /** + * @param {GPUQuerySet} querySet + * @param {number} queryIndex + */ + beginPipelineStatisticsQuery(querySet, queryIndex) { + webidl.assertBranded(this, GPUComputePassEncoder); + const prefix = + "Failed to execute 'beginPipelineStatisticsQuery' on 'GPUComputePassEncoder'"; + webidl.requiredArguments(arguments.length, 2, { prefix }); + querySet = webidl.converters.GPUQuerySet(querySet, { + prefix, + context: "Argument 1", + }); + queryIndex = webidl.converters.GPUSize32(queryIndex, { + prefix, + context: "Argument 2", + }); + const device = assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const computePassRid = assertResource(this, { prefix, context: "this" }); + const querySetRid = assertResource(querySet, { + prefix, + context: "Argument 1", + }); + assertDeviceMatch(device, querySet, { + prefix, + resourceContext: "Argument 1", + selfContext: "this", + }); + core.opSync( + "op_webgpu_compute_pass_begin_pipeline_statistics_query", + { + computePassRid, + querySet: querySetRid, + queryIndex, + }, + ); + } + + endPipelineStatisticsQuery() { + webidl.assertBranded(this, GPUComputePassEncoder); + const prefix = + "Failed to execute 'endPipelineStatisticsQuery' on 'GPUComputePassEncoder'"; + assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const computePassRid = assertResource(this, { prefix, context: "this" }); + core.opSync("op_webgpu_compute_pass_end_pipeline_statistics_query", { + computePassRid, + }); + } + + /** + * @param {GPUQuerySet} querySet + * @param {number} queryIndex + */ + writeTimestamp(querySet, queryIndex) { + webidl.assertBranded(this, GPUComputePassEncoder); + const prefix = + "Failed to execute 'writeTimestamp' on 'GPUComputePassEncoder'"; + webidl.requiredArguments(arguments.length, 2, { prefix }); + querySet = webidl.converters.GPUQuerySet(querySet, { + prefix, + context: "Argument 1", + }); + queryIndex = webidl.converters.GPUSize32(queryIndex, { + prefix, + context: "Argument 2", + }); + const device = assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const computePassRid = assertResource(this, { prefix, context: "this" }); + const querySetRid = assertResource(querySet, { + prefix, + context: "Argument 1", + }); + assertDeviceMatch(device, querySet, { + prefix, + resourceContext: "Argument 1", + selfContext: "this", + }); + core.opSync("op_webgpu_compute_pass_write_timestamp", { + computePassRid, + querySet: querySetRid, + queryIndex, + }); + } + + endPass() { + webidl.assertBranded(this, GPUComputePassEncoder); + const prefix = "Failed to execute 'endPass' on 'GPUComputePassEncoder'"; + const device = assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const commandEncoderRid = assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const computePassRid = assertResource(this, { prefix, context: "this" }); + const { err } = core.opSync("op_webgpu_compute_pass_end_pass", { + commandEncoderRid, + computePassRid, + }); + device.pushError(err); + this[_rid] = undefined; + } + + // TODO(lucacasonato): has an overload + setBindGroup( + index, + bindGroup, + dynamicOffsetsData, + dynamicOffsetsDataStart, + dynamicOffsetsDataLength, + ) { + webidl.assertBranded(this, GPUComputePassEncoder); + const prefix = + "Failed to execute 'setBindGroup' on 'GPUComputePassEncoder'"; + const device = assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const computePassRid = assertResource(this, { prefix, context: "this" }); + const bindGroupRid = assertResource(bindGroup, { + prefix, + context: "Argument 2", + }); + assertDeviceMatch(device, bindGroup, { + prefix, + resourceContext: "Argument 2", + selfContext: "this", + }); + if (dynamicOffsetsData instanceof Uint32Array) { + core.opSync( + "op_webgpu_compute_pass_set_bind_group", + { + computePassRid, + index, + bindGroup: bindGroupRid, + dynamicOffsetsDataStart, + dynamicOffsetsDataLength, + }, + dynamicOffsetsData, + ); + } else { + dynamicOffsetsData ??= []; + core.opSync("op_webgpu_compute_pass_set_bind_group", { + computePassRid, + index, + bindGroup: bindGroupRid, + dynamicOffsetsData, + dynamicOffsetsDataStart: 0, + dynamicOffsetsDataLength: dynamicOffsetsData.length, + }); + } + } + + /** + * @param {string} groupLabel + */ + pushDebugGroup(groupLabel) { + webidl.assertBranded(this, GPUComputePassEncoder); + const prefix = + "Failed to execute 'pushDebugGroup' on 'GPUComputePassEncoder'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + groupLabel = webidl.converters.USVString(groupLabel, { + prefix, + context: "Argument 1", + }); + assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const computePassRid = assertResource(this, { prefix, context: "this" }); + core.opSync("op_webgpu_compute_pass_push_debug_group", { + computePassRid, + groupLabel, + }); + } + + popDebugGroup() { + webidl.assertBranded(this, GPUComputePassEncoder); + const prefix = + "Failed to execute 'popDebugGroup' on 'GPUComputePassEncoder'"; + assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const computePassRid = assertResource(this, { prefix, context: "this" }); + core.opSync("op_webgpu_compute_pass_pop_debug_group", { + computePassRid, + }); + } + + /** + * @param {string} markerLabel + */ + insertDebugMarker(markerLabel) { + webidl.assertBranded(this, GPUComputePassEncoder); + const prefix = + "Failed to execute 'insertDebugMarker' on 'GPUComputePassEncoder'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + markerLabel = webidl.converters.USVString(markerLabel, { + prefix, + context: "Argument 1", + }); + assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const computePassRid = assertResource(this, { prefix, context: "this" }); + core.opSync("op_webgpu_compute_pass_insert_debug_marker", { + computePassRid, + markerLabel, + }); + } + + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ + label: this.label, + }) + }`; + } + } + GPUObjectBaseMixin("GPUComputePassEncoder", GPUComputePassEncoder); + + /** + * @param {string | null} label + * @param {InnerGPUDevice} device + * @param {number} rid + * @returns {GPUCommandBuffer} + */ + function createGPUCommandBuffer(label, device, rid) { + /** @type {GPUCommandBuffer} */ + const commandBuffer = webidl.createBranded(GPUCommandBuffer); + commandBuffer[_label] = label; + commandBuffer[_device] = device; + commandBuffer[_rid] = rid; + return commandBuffer; + } + + class GPUCommandBuffer { + /** @type {InnerGPUDevice} */ + [_device]; + /** @type {number | undefined} */ + [_rid]; + + [_cleanup]() { + const rid = this[_rid]; + if (rid !== undefined) { + core.close(rid); + /** @type {number | undefined} */ + this[_rid] = undefined; + } + } + + constructor() { + webidl.illegalConstructor(); + } + + get executionTime() { + throw new Error("Not yet implemented"); + } + + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ + label: this.label, + // TODO(crowlKats): executionTime + }) + }`; + } + } + GPUObjectBaseMixin("GPUCommandBuffer", GPUCommandBuffer); + + /** + * @param {string | null} label + * @param {InnerGPUDevice} device + * @param {number} rid + * @returns {GPURenderBundleEncoder} + */ + function createGPURenderBundleEncoder(label, device, rid) { + /** @type {GPURenderBundleEncoder} */ + const bundleEncoder = webidl.createBranded(GPURenderBundleEncoder); + bundleEncoder[_label] = label; + bundleEncoder[_device] = device; + bundleEncoder[_rid] = rid; + return bundleEncoder; + } + + class GPURenderBundleEncoder { + /** @type {InnerGPUDevice} */ + [_device]; + /** @type {number | undefined} */ + [_rid]; + + [_cleanup]() { + const rid = this[_rid]; + if (rid !== undefined) { + core.close(rid); + /** @type {number | undefined} */ + this[_rid] = undefined; + } + } + + constructor() { + webidl.illegalConstructor(); + } + + /** + * @param {GPURenderBundleDescriptor} descriptor + */ + finish(descriptor = {}) { + webidl.assertBranded(this, GPURenderBundleEncoder); + const prefix = "Failed to execute 'finish' on 'GPURenderBundleEncoder'"; + descriptor = webidl.converters.GPURenderBundleDescriptor(descriptor, { + prefix, + context: "Argument 1", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const renderBundleEncoderRid = assertResource(this, { + prefix, + context: "this", + }); + const { rid, err } = core.opSync( + "op_webgpu_render_bundle_encoder_finish", + { + renderBundleEncoderRid, + ...descriptor, + }, + ); + device.pushError(err); + this[_rid] = undefined; + + const renderBundle = createGPURenderBundle( + descriptor.label ?? null, + device, + rid, + ); + device.trackResource((renderBundle)); + return renderBundle; + } + + // TODO(lucacasonato): has an overload + setBindGroup( + index, + bindGroup, + dynamicOffsetsData, + dynamicOffsetsDataStart, + dynamicOffsetsDataLength, + ) { + webidl.assertBranded(this, GPURenderBundleEncoder); + const prefix = + "Failed to execute 'setBindGroup' on 'GPURenderBundleEncoder'"; + const device = assertDevice(this, { prefix, context: "this" }); + const renderBundleEncoderRid = assertResource(this, { + prefix, + context: "this", + }); + const bindGroupRid = assertResource(bindGroup, { + prefix, + context: "Argument 2", + }); + assertDeviceMatch(device, bindGroup, { + prefix, + resourceContext: "Argument 2", + selfContext: "this", + }); + if (dynamicOffsetsData instanceof Uint32Array) { + core.opSync( + "op_webgpu_render_bundle_encoder_set_bind_group", + { + renderBundleEncoderRid, + index, + bindGroup: bindGroupRid, + dynamicOffsetsDataStart, + dynamicOffsetsDataLength, + }, + dynamicOffsetsData, + ); + } else { + dynamicOffsetsData ??= []; + core.opSync("op_webgpu_render_bundle_encoder_set_bind_group", { + renderBundleEncoderRid, + index, + bindGroup: bindGroupRid, + dynamicOffsetsData, + dynamicOffsetsDataStart: 0, + dynamicOffsetsDataLength: dynamicOffsetsData.length, + }); + } + } + + /** + * @param {string} groupLabel + */ + pushDebugGroup(groupLabel) { + webidl.assertBranded(this, GPURenderBundleEncoder); + const prefix = + "Failed to execute 'pushDebugGroup' on 'GPURenderBundleEncoder'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + groupLabel = webidl.converters.USVString(groupLabel, { + prefix, + context: "Argument 1", + }); + assertDevice(this, { prefix, context: "this" }); + const renderBundleEncoderRid = assertResource(this, { + prefix, + context: "this", + }); + core.opSync("op_webgpu_render_bundle_encoder_push_debug_group", { + renderBundleEncoderRid, + groupLabel, + }); + } + + popDebugGroup() { + webidl.assertBranded(this, GPURenderBundleEncoder); + const prefix = + "Failed to execute 'popDebugGroup' on 'GPURenderBundleEncoder'"; + assertDevice(this, { prefix, context: "this" }); + const renderBundleEncoderRid = assertResource(this, { + prefix, + context: "this", + }); + core.opSync("op_webgpu_render_bundle_encoder_pop_debug_group", { + renderBundleEncoderRid, + }); + } + + /** + * @param {string} markerLabel + */ + insertDebugMarker(markerLabel) { + webidl.assertBranded(this, GPURenderBundleEncoder); + const prefix = + "Failed to execute 'insertDebugMarker' on 'GPURenderBundleEncoder'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + markerLabel = webidl.converters.USVString(markerLabel, { + prefix, + context: "Argument 1", + }); + assertDevice(this, { prefix, context: "this" }); + const renderBundleEncoderRid = assertResource(this, { + prefix, + context: "this", + }); + core.opSync("op_webgpu_render_bundle_encoder_push_debug_group", { + renderBundleEncoderRid, + markerLabel, + }); + } + + /** + * @param {GPURenderPipeline} pipeline + */ + setPipeline(pipeline) { + webidl.assertBranded(this, GPURenderBundleEncoder); + const prefix = + "Failed to execute 'setPipeline' on 'GPURenderBundleEncoder'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + pipeline = webidl.converters.GPURenderPipeline(pipeline, { + prefix, + context: "Argument 1", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const renderBundleEncoderRid = assertResource(this, { + prefix, + context: "this", + }); + const pipelineRid = assertResource(pipeline, { + prefix, + context: "Argument 1", + }); + assertDeviceMatch(device, pipeline, { + prefix, + resourceContext: "Argument 1", + selfContext: "this", + }); + core.opSync("op_webgpu_render_bundle_encoder_set_pipeline", { + renderBundleEncoderRid, + pipeline: pipelineRid, + }); + } + + /** + * @param {GPUBuffer} buffer + * @param {GPUIndexFormat} indexFormat + * @param {number} offset + * @param {number} size + */ + setIndexBuffer(buffer, indexFormat, offset = 0, size = 0) { + webidl.assertBranded(this, GPURenderBundleEncoder); + const prefix = + "Failed to execute 'setIndexBuffer' on 'GPURenderBundleEncoder'"; + webidl.requiredArguments(arguments.length, 2, { prefix }); + buffer = webidl.converters.GPUBuffer(buffer, { + prefix, + context: "Argument 1", + }); + indexFormat = webidl.converters.GPUIndexFormat(indexFormat, { + prefix, + context: "Argument 2", + }); + offset = webidl.converters.GPUSize64(offset, { + prefix, + context: "Argument 3", + }); + size = webidl.converters.GPUSize64(size, { + prefix, + context: "Argument 4", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const renderBundleEncoderRid = assertResource(this, { + prefix, + context: "this", + }); + const bufferRid = assertResource(buffer, { + prefix, + context: "Argument 1", + }); + assertDeviceMatch(device, buffer, { + prefix, + resourceContext: "Argument 1", + selfContext: "this", + }); + core.opSync("op_webgpu_render_bundle_encoder_set_index_buffer", { + renderBundleEncoderRid, + buffer: bufferRid, + indexFormat, + offset, + size, + }); + } + + /** + * @param {number} slot + * @param {GPUBuffer} buffer + * @param {number} offset + * @param {number} size + */ + setVertexBuffer(slot, buffer, offset = 0, size = 0) { + webidl.assertBranded(this, GPURenderBundleEncoder); + const prefix = + "Failed to execute 'setVertexBuffer' on 'GPURenderBundleEncoder'"; + webidl.requiredArguments(arguments.length, 2, { prefix }); + slot = webidl.converters.GPUSize32(slot, { + prefix, + context: "Argument 2", + }); + buffer = webidl.converters.GPUBuffer(buffer, { + prefix, + context: "Argument 2", + }); + offset = webidl.converters.GPUSize64(offset, { + prefix, + context: "Argument 3", + }); + size = webidl.converters.GPUSize64(size, { + prefix, + context: "Argument 4", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const renderBundleEncoderRid = assertResource(this, { + prefix, + context: "this", + }); + const bufferRid = assertResource(buffer, { + prefix, + context: "Argument 2", + }); + assertDeviceMatch(device, buffer, { + prefix, + resourceContext: "Argument 2", + selfContext: "this", + }); + core.opSync("op_webgpu_render_bundle_encoder_set_vertex_buffer", { + renderBundleEncoderRid, + slot, + buffer: bufferRid, + offset, + size, + }); + } + + /** + * @param {number} vertexCount + * @param {number} instanceCount + * @param {number} firstVertex + * @param {number} firstInstance + */ + draw(vertexCount, instanceCount = 1, firstVertex = 0, firstInstance = 0) { + webidl.assertBranded(this, GPURenderBundleEncoder); + const prefix = "Failed to execute 'draw' on 'GPURenderBundleEncoder'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + vertexCount = webidl.converters.GPUSize32(vertexCount, { + prefix, + context: "Argument 1", + }); + instanceCount = webidl.converters.GPUSize32(instanceCount, { + prefix, + context: "Argument 2", + }); + firstVertex = webidl.converters.GPUSize32(firstVertex, { + prefix, + context: "Argument 3", + }); + firstInstance = webidl.converters.GPUSize32(firstInstance, { + prefix, + context: "Argument 4", + }); + assertDevice(this, { prefix, context: "this" }); + const renderBundleEncoderRid = assertResource(this, { + prefix, + context: "this", + }); + core.opSync("op_webgpu_render_bundle_encoder_draw", { + renderBundleEncoderRid, + vertexCount, + instanceCount, + firstVertex, + firstInstance, + }); + } + + /** + * @param {number} indexCount + * @param {number} instanceCount + * @param {number} firstIndex + * @param {number} baseVertex + * @param {number} firstInstance + */ + drawIndexed( + indexCount, + instanceCount = 1, + firstIndex = 0, + baseVertex = 0, + firstInstance = 0, + ) { + webidl.assertBranded(this, GPURenderBundleEncoder); + const prefix = + "Failed to execute 'drawIndexed' on 'GPURenderBundleEncoder'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + indexCount = webidl.converters.GPUSize32(indexCount, { + prefix, + context: "Argument 1", + }); + instanceCount = webidl.converters.GPUSize32(instanceCount, { + prefix, + context: "Argument 2", + }); + firstIndex = webidl.converters.GPUSize32(firstIndex, { + prefix, + context: "Argument 3", + }); + baseVertex = webidl.converters.GPUSignedOffset32(baseVertex, { + prefix, + context: "Argument 4", + }); + firstInstance = webidl.converters.GPUSize32(firstInstance, { + prefix, + context: "Argument 5", + }); + assertDevice(this, { prefix, context: "this" }); + const renderBundleEncoderRid = assertResource(this, { + prefix, + context: "this", + }); + core.opSync("op_webgpu_render_bundle_encoder_draw_indexed", { + renderBundleEncoderRid, + indexCount, + instanceCount, + firstIndex, + baseVertex, + firstInstance, + }); + } + + /** + * @param {GPUBuffer} indirectBuffer + * @param {number} indirectOffset + */ + drawIndirect(indirectBuffer, indirectOffset) { + webidl.assertBranded(this, GPURenderBundleEncoder); + const prefix = + "Failed to execute 'drawIndirect' on 'GPURenderBundleEncoder'"; + webidl.requiredArguments(arguments.length, 2, { prefix }); + indirectBuffer = webidl.converters.GPUBuffer(indirectBuffer, { + prefix, + context: "Argument 1", + }); + indirectOffset = webidl.converters.GPUSize64(indirectOffset, { + prefix, + context: "Argument 2", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const renderBundleEncoderRid = assertResource(this, { + prefix, + context: "this", + }); + const indirectBufferRid = assertResource(indirectBuffer, { + prefix, + context: "Argument 1", + }); + assertDeviceMatch(device, indirectBuffer, { + prefix, + resourceContext: "Argument 1", + selfContext: "this", + }); + core.opSync("op_webgpu_render_bundle_encoder_draw_indirect", { + renderBundleEncoderRid, + indirectBuffer: indirectBufferRid, + indirectOffset, + }); + } + + drawIndexedIndirect(_indirectBuffer, _indirectOffset) { + throw new Error("Not yet implemented"); + } + + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ + label: this.label, + }) + }`; + } + } + GPUObjectBaseMixin("GPURenderBundleEncoder", GPURenderBundleEncoder); + + /** + * @param {string | null} label + * @param {InnerGPUDevice} device + * @param {number} rid + * @returns {GPURenderBundle} + */ + function createGPURenderBundle(label, device, rid) { + /** @type {GPURenderBundle} */ + const bundle = webidl.createBranded(GPURenderBundle); + bundle[_label] = label; + bundle[_device] = device; + bundle[_rid] = rid; + return bundle; + } + + class GPURenderBundle { + /** @type {InnerGPUDevice} */ + [_device]; + /** @type {number | undefined} */ + [_rid]; + + [_cleanup]() { + const rid = this[_rid]; + if (rid !== undefined) { + core.close(rid); + /** @type {number | undefined} */ + this[_rid] = undefined; + } + } + + constructor() { + webidl.illegalConstructor(); + } + + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ + label: this.label, + }) + }`; + } + } + GPUObjectBaseMixin("GPURenderBundle", GPURenderBundle); + + const _descriptor = Symbol("[[descriptor]]"); + + /** + * @param {string | null} label + * @param {InnerGPUDevice} device + * @param {number} rid + * @returns {GPUQuerySet} + */ + function createGPUQuerySet(label, device, rid, descriptor) { + /** @type {GPUQuerySet} */ + const queue = webidl.createBranded(GPUQuerySet); + queue[_label] = label; + queue[_device] = device; + queue[_rid] = rid; + queue[_descriptor] = descriptor; + return queue; + } + + class GPUQuerySet { + /** @type {InnerGPUDevice} */ + [_device]; + /** @type {number | undefined} */ + [_rid]; + /** @type {GPUQuerySetDescriptor} */ + [_descriptor]; + + [_cleanup]() { + const rid = this[_rid]; + if (rid !== undefined) { + core.close(rid); + /** @type {number | undefined} */ + this[_rid] = undefined; + } + } + + constructor() { + webidl.illegalConstructor(); + } + + destroy() { + webidl.assertBranded(this, GPUQuerySet); + this[_cleanup](); + } + + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ + label: this.label, + }) + }`; + } + } + GPUObjectBaseMixin("GPUQuerySet", GPUQuerySet); + + window.__bootstrap.webgpu = { + gpu: webidl.createBranded(GPU), + GPU, + GPUAdapter, + GPUSupportedLimits, + GPUSupportedFeatures, + GPUDevice, + GPUQueue, + GPUBuffer, + GPUBufferUsage, + GPUMapMode, + GPUTextureUsage, + GPUTexture, + GPUTextureView, + GPUSampler, + GPUBindGroupLayout, + GPUPipelineLayout, + GPUBindGroup, + GPUShaderModule, + GPUShaderStage, + GPUComputePipeline, + GPURenderPipeline, + GPUColorWrite, + GPUCommandEncoder, + GPURenderPassEncoder, + GPUComputePassEncoder, + GPUCommandBuffer, + GPURenderBundleEncoder, + GPURenderBundle, + GPUQuerySet, + GPUOutOfMemoryError, + GPUValidationError, + }; +})(this); diff --git a/ext/webgpu/02_idl_types.js b/ext/webgpu/02_idl_types.js new file mode 100644 index 000000000..9102b8d7a --- /dev/null +++ b/ext/webgpu/02_idl_types.js @@ -0,0 +1,1957 @@ +// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. + +// @ts-check +/// <reference path="../web/internal.d.ts" /> + +"use strict"; + +((window) => { + const webidl = window.__bootstrap.webidl; + const { + GPU, + GPUAdapter, + GPUSupportedLimits, + GPUSupportedFeatures, + GPUDevice, + GPUQueue, + GPUBuffer, + GPUBufferUsage, + GPUMapMode, + GPUTextureUsage, + GPUTexture, + GPUTextureView, + GPUSampler, + GPUBindGroupLayout, + GPUPipelineLayout, + GPUBindGroup, + GPUShaderModule, + GPUShaderStage, + GPUComputePipeline, + GPURenderPipeline, + GPUColorWrite, + GPUCommandEncoder, + GPURenderPassEncoder, + GPUComputePassEncoder, + GPUCommandBuffer, + GPURenderBundleEncoder, + GPURenderBundle, + GPUQuerySet, + GPUOutOfMemoryError, + GPUValidationError, + } = window.__bootstrap.webgpu; + const { + SymbolIterator, + TypeError, + Uint32Array, + } = window.__bootstrap.primordials; + + // This needs to be initalized after all of the base classes are implmented, + // otherwise their converters might not be available yet. + // DICTIONARY: GPUObjectDescriptorBase + const dictMembersGPUObjectDescriptorBase = [ + { key: "label", converter: webidl.converters["USVString"] }, + ]; + webidl.converters["GPUObjectDescriptorBase"] = webidl + .createDictionaryConverter( + "GPUObjectDescriptorBase", + dictMembersGPUObjectDescriptorBase, + ); + + // INTERFACE: GPUSupportedLimits + webidl.converters.GPUSupportedLimits = webidl.createInterfaceConverter( + "GPUSupportedLimits", + GPUSupportedLimits, + ); + + // INTERFACE: GPUSupportedFeatures + webidl.converters.GPUSupportedFeatures = webidl.createInterfaceConverter( + "GPUSupportedFeatures", + GPUSupportedFeatures, + ); + + // ENUM: GPUPredefinedColorSpace + webidl.converters.GPUPredefinedColorSpace = webidl.createEnumConverter( + "GPUPredefinedColorSpace", + ["srgb"], + ); + + // INTERFACE: GPU + webidl.converters.GPU = webidl.createInterfaceConverter("GPU", GPU); + + // ENUM: GPUPowerPreference + webidl.converters["GPUPowerPreference"] = webidl.createEnumConverter( + "GPUPowerPreference", + [ + "low-power", + "high-performance", + ], + ); + + // DICTIONARY: GPURequestAdapterOptions + const dictMembersGPURequestAdapterOptions = [ + { + key: "powerPreference", + converter: webidl.converters["GPUPowerPreference"], + }, + { + key: "forceSoftware", + converter: webidl.converters.boolean, + defaultValue: false, + }, + ]; + webidl.converters["GPURequestAdapterOptions"] = webidl + .createDictionaryConverter( + "GPURequestAdapterOptions", + dictMembersGPURequestAdapterOptions, + ); + + // INTERFACE: GPUAdapter + webidl.converters.GPUAdapter = webidl.createInterfaceConverter( + "GPUAdapter", + GPUAdapter, + ); + + // ENUM: GPUFeatureName + webidl.converters["GPUFeatureName"] = webidl.createEnumConverter( + "GPUFeatureName", + [ + "depth-clamping", + "depth24unorm-stencil8", + "depth32float-stencil8", + "pipeline-statistics-query", + "texture-compression-bc", + "timestamp-query", + // extended from spec + "mappable-primary-buffers", + "sampled-texture-binding-array", + "sampled-texture-array-dynamic-indexing", + "sampled-texture-array-non-uniform-indexing", + "unsized-binding-array", + "multi-draw-indirect", + "multi-draw-indirect-count", + "push-constants", + "address-mode-clamp-to-border", + "non-fill-polygon-mode", + "texture-compression-etc2", + "texture-compression-astc-ldr", + "texture-adapter-specific-format-features", + "shader-float64", + "vertex-attribute-64bit", + ], + ); + + // TYPEDEF: GPUSize32 + webidl.converters["GPUSize32"] = (V, opts) => + webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }); + + // DICTIONARY: GPUDeviceDescriptor + const dictMembersGPUDeviceDescriptor = [ + { + key: "nonGuaranteedFeatures", + converter: webidl.createSequenceConverter( + webidl.converters["GPUFeatureName"], + ), + get defaultValue() { + return []; + }, + }, + { + key: "nonGuaranteedLimits", + converter: webidl.createRecordConverter( + webidl.converters["DOMString"], + webidl.converters["GPUSize32"], + ), + get defaultValue() { + return {}; + }, + }, + ]; + webidl.converters["GPUDeviceDescriptor"] = webidl.createDictionaryConverter( + "GPUDeviceDescriptor", + dictMembersGPUObjectDescriptorBase, + dictMembersGPUDeviceDescriptor, + ); + + // INTERFACE: GPUDevice + webidl.converters.GPUDevice = webidl.createInterfaceConverter( + "GPUDevice", + GPUDevice, + ); + + // INTERFACE: GPUBuffer + webidl.converters.GPUBuffer = webidl.createInterfaceConverter( + "GPUBuffer", + GPUBuffer, + ); + + // TYPEDEF: GPUSize64 + webidl.converters["GPUSize64"] = (V, opts) => + webidl.converters["unsigned long long"](V, { ...opts, enforceRange: true }); + + // TYPEDEF: GPUBufferUsageFlags + webidl.converters["GPUBufferUsageFlags"] = (V, opts) => + webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }); + + // DICTIONARY: GPUBufferDescriptor + const dictMembersGPUBufferDescriptor = [ + { key: "size", converter: webidl.converters["GPUSize64"], required: true }, + { + key: "usage", + converter: webidl.converters["GPUBufferUsageFlags"], + required: true, + }, + { + key: "mappedAtCreation", + converter: webidl.converters["boolean"], + defaultValue: false, + }, + ]; + webidl.converters["GPUBufferDescriptor"] = webidl.createDictionaryConverter( + "GPUBufferDescriptor", + dictMembersGPUObjectDescriptorBase, + dictMembersGPUBufferDescriptor, + ); + + // INTERFACE: GPUBufferUsage + webidl.converters.GPUBufferUsage = webidl.createInterfaceConverter( + "GPUBufferUsage", + GPUBufferUsage, + ); + + // TYPEDEF: GPUMapModeFlags + webidl.converters["GPUMapModeFlags"] = (V, opts) => + webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }); + + // INTERFACE: GPUMapMode + webidl.converters.GPUMapMode = webidl.createInterfaceConverter( + "GPUMapMode", + GPUMapMode, + ); + + // INTERFACE: GPUTexture + webidl.converters.GPUTexture = webidl.createInterfaceConverter( + "GPUTexture", + GPUTexture, + ); + + // TYPEDEF: GPUIntegerCoordinate + webidl.converters["GPUIntegerCoordinate"] = (V, opts) => + webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }); + webidl.converters["sequence<GPUIntegerCoordinate>"] = webidl + .createSequenceConverter(webidl.converters["GPUIntegerCoordinate"]); + + // DICTIONARY: GPUExtent3DDict + const dictMembersGPUExtent3DDict = [ + { + key: "width", + converter: webidl.converters["GPUIntegerCoordinate"], + required: true, + }, + { + key: "height", + converter: webidl.converters["GPUIntegerCoordinate"], + defaultValue: 1, + }, + { + key: "depthOrArrayLayers", + converter: webidl.converters["GPUIntegerCoordinate"], + defaultValue: 1, + }, + ]; + webidl.converters["GPUExtent3DDict"] = webidl.createDictionaryConverter( + "GPUExtent3DDict", + dictMembersGPUExtent3DDict, + ); + + // TYPEDEF: GPUExtent3D + webidl.converters["GPUExtent3D"] = (V, opts) => { + // Union for (sequence<GPUIntegerCoordinate> or GPUExtent3DDict) + if (V === null || V === undefined) { + return webidl.converters["GPUExtent3DDict"](V, opts); + } + if (typeof V === "object") { + const method = V[SymbolIterator]; + if (method !== undefined) { + return webidl.converters["sequence<GPUIntegerCoordinate>"](V, opts); + } + return webidl.converters["GPUExtent3DDict"](V, opts); + } + throw webidl.makeException( + TypeError, + "can not be converted to sequence<GPUIntegerCoordinate> or GPUExtent3DDict.", + opts, + ); + }; + + // ENUM: GPUTextureDimension + webidl.converters["GPUTextureDimension"] = webidl.createEnumConverter( + "GPUTextureDimension", + [ + "1d", + "2d", + "3d", + ], + ); + + // ENUM: GPUTextureFormat + webidl.converters["GPUTextureFormat"] = webidl.createEnumConverter( + "GPUTextureFormat", + [ + "r8unorm", + "r8snorm", + "r8uint", + "r8sint", + "r16uint", + "r16sint", + "r16float", + "rg8unorm", + "rg8snorm", + "rg8uint", + "rg8sint", + "r32uint", + "r32sint", + "r32float", + "rg16uint", + "rg16sint", + "rg16float", + "rgba8unorm", + "rgba8unorm-srgb", + "rgba8snorm", + "rgba8uint", + "rgba8sint", + "bgra8unorm", + "bgra8unorm-srgb", + "rgb9e5ufloat", + "rgb10a2unorm", + "rg11b10ufloat", + "rg32uint", + "rg32sint", + "rg32float", + "rgba16uint", + "rgba16sint", + "rgba16float", + "rgba32uint", + "rgba32sint", + "rgba32float", + "stencil8", + "depth16unorm", + "depth24plus", + "depth24plus-stencil8", + "depth32float", + "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", + "depth24unorm-stencil8", + "depth32float-stencil8", + ], + ); + + // TYPEDEF: GPUTextureUsageFlags + webidl.converters["GPUTextureUsageFlags"] = (V, opts) => + webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }); + + // DICTIONARY: GPUTextureDescriptor + const dictMembersGPUTextureDescriptor = [ + { + key: "size", + converter: webidl.converters["GPUExtent3D"], + required: true, + }, + { + key: "mipLevelCount", + converter: webidl.converters["GPUIntegerCoordinate"], + defaultValue: 1, + }, + { + key: "sampleCount", + converter: webidl.converters["GPUSize32"], + defaultValue: 1, + }, + { + key: "dimension", + converter: webidl.converters["GPUTextureDimension"], + defaultValue: "2d", + }, + { + key: "format", + converter: webidl.converters["GPUTextureFormat"], + required: true, + }, + { + key: "usage", + converter: webidl.converters["GPUTextureUsageFlags"], + required: true, + }, + ]; + webidl.converters["GPUTextureDescriptor"] = webidl.createDictionaryConverter( + "GPUTextureDescriptor", + dictMembersGPUObjectDescriptorBase, + dictMembersGPUTextureDescriptor, + ); + + // INTERFACE: GPUTextureUsage + webidl.converters.GPUTextureUsage = webidl.createInterfaceConverter( + "GPUTextureUsage", + GPUTextureUsage, + ); + + // INTERFACE: GPUTextureView + webidl.converters.GPUTextureView = webidl.createInterfaceConverter( + "GPUTextureView", + GPUTextureView, + ); + + // ENUM: GPUTextureViewDimension + webidl.converters["GPUTextureViewDimension"] = webidl.createEnumConverter( + "GPUTextureViewDimension", + [ + "1d", + "2d", + "2d-array", + "cube", + "cube-array", + "3d", + ], + ); + + // ENUM: GPUTextureAspect + webidl.converters["GPUTextureAspect"] = webidl.createEnumConverter( + "GPUTextureAspect", + [ + "all", + "stencil-only", + "depth-only", + ], + ); + + // DICTIONARY: GPUTextureViewDescriptor + const dictMembersGPUTextureViewDescriptor = [ + { key: "format", converter: webidl.converters["GPUTextureFormat"] }, + { + key: "dimension", + converter: webidl.converters["GPUTextureViewDimension"], + }, + { + key: "aspect", + converter: webidl.converters["GPUTextureAspect"], + defaultValue: "all", + }, + { + key: "baseMipLevel", + converter: webidl.converters["GPUIntegerCoordinate"], + defaultValue: 0, + }, + { + key: "mipLevelCount", + converter: webidl.converters["GPUIntegerCoordinate"], + }, + { + key: "baseArrayLayer", + converter: webidl.converters["GPUIntegerCoordinate"], + defaultValue: 0, + }, + { + key: "arrayLayerCount", + converter: webidl.converters["GPUIntegerCoordinate"], + }, + ]; + webidl.converters["GPUTextureViewDescriptor"] = webidl + .createDictionaryConverter( + "GPUTextureViewDescriptor", + dictMembersGPUObjectDescriptorBase, + dictMembersGPUTextureViewDescriptor, + ); + + // INTERFACE: GPUSampler + webidl.converters.GPUSampler = webidl.createInterfaceConverter( + "GPUSampler", + GPUSampler, + ); + + // ENUM: GPUAddressMode + webidl.converters["GPUAddressMode"] = webidl.createEnumConverter( + "GPUAddressMode", + [ + "clamp-to-edge", + "repeat", + "mirror-repeat", + ], + ); + + // ENUM: GPUFilterMode + webidl.converters["GPUFilterMode"] = webidl.createEnumConverter( + "GPUFilterMode", + [ + "nearest", + "linear", + ], + ); + + // ENUM: GPUCompareFunction + webidl.converters["GPUCompareFunction"] = webidl.createEnumConverter( + "GPUCompareFunction", + [ + "never", + "less", + "equal", + "less-equal", + "greater", + "not-equal", + "greater-equal", + "always", + ], + ); + + // DICTIONARY: GPUSamplerDescriptor + const dictMembersGPUSamplerDescriptor = [ + { + key: "addressModeU", + converter: webidl.converters["GPUAddressMode"], + defaultValue: "clamp-to-edge", + }, + { + key: "addressModeV", + converter: webidl.converters["GPUAddressMode"], + defaultValue: "clamp-to-edge", + }, + { + key: "addressModeW", + converter: webidl.converters["GPUAddressMode"], + defaultValue: "clamp-to-edge", + }, + { + key: "magFilter", + converter: webidl.converters["GPUFilterMode"], + defaultValue: "nearest", + }, + { + key: "minFilter", + converter: webidl.converters["GPUFilterMode"], + defaultValue: "nearest", + }, + { + key: "mipmapFilter", + converter: webidl.converters["GPUFilterMode"], + defaultValue: "nearest", + }, + { + key: "lodMinClamp", + converter: webidl.converters["float"], + defaultValue: 0, + }, + { + key: "lodMaxClamp", + converter: webidl.converters["float"], + defaultValue: 0xffffffff, + }, + { key: "compare", converter: webidl.converters["GPUCompareFunction"] }, + { + key: "maxAnisotropy", + converter: (V, opts) => + webidl.converters["unsigned short"](V, { ...opts, clamp: true }), + defaultValue: 1, + }, + ]; + webidl.converters["GPUSamplerDescriptor"] = webidl.createDictionaryConverter( + "GPUSamplerDescriptor", + dictMembersGPUObjectDescriptorBase, + dictMembersGPUSamplerDescriptor, + ); + + // INTERFACE: GPUBindGroupLayout + webidl.converters.GPUBindGroupLayout = webidl.createInterfaceConverter( + "GPUBindGroupLayout", + GPUBindGroupLayout, + ); + + // TYPEDEF: GPUIndex32 + webidl.converters["GPUIndex32"] = (V, opts) => + webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }); + + // TYPEDEF: GPUShaderStageFlags + webidl.converters["GPUShaderStageFlags"] = (V, opts) => + webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }); + + // ENUM: GPUBufferBindingType + webidl.converters["GPUBufferBindingType"] = webidl.createEnumConverter( + "GPUBufferBindingType", + [ + "uniform", + "storage", + "read-only-storage", + ], + ); + + // DICTIONARY: GPUBufferBindingLayout + const dictMembersGPUBufferBindingLayout = [ + { + key: "type", + converter: webidl.converters["GPUBufferBindingType"], + defaultValue: "uniform", + }, + { + key: "hasDynamicOffset", + converter: webidl.converters["boolean"], + defaultValue: false, + }, + { + key: "minBindingSize", + converter: webidl.converters["GPUSize64"], + defaultValue: 0, + }, + ]; + webidl.converters["GPUBufferBindingLayout"] = webidl + .createDictionaryConverter( + "GPUBufferBindingLayout", + dictMembersGPUBufferBindingLayout, + ); + + // ENUM: GPUSamplerBindingType + webidl.converters["GPUSamplerBindingType"] = webidl.createEnumConverter( + "GPUSamplerBindingType", + [ + "filtering", + "non-filtering", + "comparison", + ], + ); + + // DICTIONARY: GPUSamplerBindingLayout + const dictMembersGPUSamplerBindingLayout = [ + { + key: "type", + converter: webidl.converters["GPUSamplerBindingType"], + defaultValue: "filtering", + }, + ]; + webidl.converters["GPUSamplerBindingLayout"] = webidl + .createDictionaryConverter( + "GPUSamplerBindingLayout", + dictMembersGPUSamplerBindingLayout, + ); + + // ENUM: GPUTextureSampleType + webidl.converters["GPUTextureSampleType"] = webidl.createEnumConverter( + "GPUTextureSampleType", + [ + "float", + "unfilterable-float", + "depth", + "sint", + "uint", + ], + ); + + // DICTIONARY: GPUTextureBindingLayout + const dictMembersGPUTextureBindingLayout = [ + { + key: "sampleType", + converter: webidl.converters["GPUTextureSampleType"], + defaultValue: "float", + }, + { + key: "viewDimension", + converter: webidl.converters["GPUTextureViewDimension"], + defaultValue: "2d", + }, + { + key: "multisampled", + converter: webidl.converters["boolean"], + defaultValue: false, + }, + ]; + webidl.converters["GPUTextureBindingLayout"] = webidl + .createDictionaryConverter( + "GPUTextureBindingLayout", + dictMembersGPUTextureBindingLayout, + ); + + // ENUM: GPUStorageTextureAccess + webidl.converters["GPUStorageTextureAccess"] = webidl.createEnumConverter( + "GPUStorageTextureAccess", + [ + "read-only", + "write-only", + ], + ); + + // DICTIONARY: GPUStorageTextureBindingLayout + const dictMembersGPUStorageTextureBindingLayout = [ + { + key: "access", + converter: webidl.converters["GPUStorageTextureAccess"], + required: true, + }, + { + key: "format", + converter: webidl.converters["GPUTextureFormat"], + required: true, + }, + { + key: "viewDimension", + converter: webidl.converters["GPUTextureViewDimension"], + defaultValue: "2d", + }, + ]; + webidl.converters["GPUStorageTextureBindingLayout"] = webidl + .createDictionaryConverter( + "GPUStorageTextureBindingLayout", + dictMembersGPUStorageTextureBindingLayout, + ); + + // DICTIONARY: GPUBindGroupLayoutEntry + const dictMembersGPUBindGroupLayoutEntry = [ + { + key: "binding", + converter: webidl.converters["GPUIndex32"], + required: true, + }, + { + key: "visibility", + converter: webidl.converters["GPUShaderStageFlags"], + required: true, + }, + { key: "buffer", converter: webidl.converters["GPUBufferBindingLayout"] }, + { key: "sampler", converter: webidl.converters["GPUSamplerBindingLayout"] }, + { key: "texture", converter: webidl.converters["GPUTextureBindingLayout"] }, + { + key: "storageTexture", + converter: webidl.converters["GPUStorageTextureBindingLayout"], + }, + ]; + webidl.converters["GPUBindGroupLayoutEntry"] = webidl + .createDictionaryConverter( + "GPUBindGroupLayoutEntry", + dictMembersGPUBindGroupLayoutEntry, + ); + + // DICTIONARY: GPUBindGroupLayoutDescriptor + const dictMembersGPUBindGroupLayoutDescriptor = [ + { + key: "entries", + converter: webidl.createSequenceConverter( + webidl.converters["GPUBindGroupLayoutEntry"], + ), + required: true, + }, + ]; + webidl.converters["GPUBindGroupLayoutDescriptor"] = webidl + .createDictionaryConverter( + "GPUBindGroupLayoutDescriptor", + dictMembersGPUObjectDescriptorBase, + dictMembersGPUBindGroupLayoutDescriptor, + ); + + // INTERFACE: GPUShaderStage + webidl.converters.GPUShaderStage = webidl.createInterfaceConverter( + "GPUShaderStage", + GPUShaderStage, + ); + + // INTERFACE: GPUBindGroup + webidl.converters.GPUBindGroup = webidl.createInterfaceConverter( + "GPUBindGroup", + GPUBindGroup, + ); + + // DICTIONARY: GPUBufferBinding + const dictMembersGPUBufferBinding = [ + { + key: "buffer", + converter: webidl.converters["GPUBuffer"], + required: true, + }, + { + key: "offset", + converter: webidl.converters["GPUSize64"], + defaultValue: 0, + }, + { key: "size", converter: webidl.converters["GPUSize64"] }, + ]; + webidl.converters["GPUBufferBinding"] = webidl.createDictionaryConverter( + "GPUBufferBinding", + dictMembersGPUBufferBinding, + ); + + // TYPEDEF: GPUBindingResource + webidl.converters["GPUBindingResource"] = + webidl.converters.any /** put union here! **/; + + // DICTIONARY: GPUBindGroupEntry + const dictMembersGPUBindGroupEntry = [ + { + key: "binding", + converter: webidl.converters["GPUIndex32"], + required: true, + }, + { + key: "resource", + converter: webidl.converters["GPUBindingResource"], + required: true, + }, + ]; + webidl.converters["GPUBindGroupEntry"] = webidl.createDictionaryConverter( + "GPUBindGroupEntry", + dictMembersGPUBindGroupEntry, + ); + + // DICTIONARY: GPUBindGroupDescriptor + const dictMembersGPUBindGroupDescriptor = [ + { + key: "layout", + converter: webidl.converters["GPUBindGroupLayout"], + required: true, + }, + { + key: "entries", + converter: webidl.createSequenceConverter( + webidl.converters["GPUBindGroupEntry"], + ), + required: true, + }, + ]; + webidl.converters["GPUBindGroupDescriptor"] = webidl + .createDictionaryConverter( + "GPUBindGroupDescriptor", + dictMembersGPUObjectDescriptorBase, + dictMembersGPUBindGroupDescriptor, + ); + + // INTERFACE: GPUPipelineLayout + webidl.converters.GPUPipelineLayout = webidl.createInterfaceConverter( + "GPUPipelineLayout", + GPUPipelineLayout, + ); + + // DICTIONARY: GPUPipelineLayoutDescriptor + const dictMembersGPUPipelineLayoutDescriptor = [ + { + key: "bindGroupLayouts", + converter: webidl.createSequenceConverter( + webidl.converters["GPUBindGroupLayout"], + ), + required: true, + }, + ]; + webidl.converters["GPUPipelineLayoutDescriptor"] = webidl + .createDictionaryConverter( + "GPUPipelineLayoutDescriptor", + dictMembersGPUObjectDescriptorBase, + dictMembersGPUPipelineLayoutDescriptor, + ); + + // INTERFACE: GPUShaderModule + webidl.converters.GPUShaderModule = webidl.createInterfaceConverter( + "GPUShaderModule", + GPUShaderModule, + ); + + // DICTIONARY: GPUShaderModuleDescriptor + const dictMembersGPUShaderModuleDescriptor = [ + { + key: "code", + converter: (V, opts) => { + if (V instanceof Uint32Array) { + return webidl.converters["Uint32Array"](V, opts); + } + if (typeof V === "string") { + return webidl.converters["DOMString"](V, opts); + } + throw webidl.makeException( + TypeError, + "can not be converted to Uint32Array or DOMString.", + opts, + ); + }, + required: true, + }, + { key: "sourceMap", converter: webidl.converters["object"] }, + ]; + webidl.converters["GPUShaderModuleDescriptor"] = webidl + .createDictionaryConverter( + "GPUShaderModuleDescriptor", + dictMembersGPUObjectDescriptorBase, + dictMembersGPUShaderModuleDescriptor, + ); + + // // ENUM: GPUCompilationMessageType + // webidl.converters["GPUCompilationMessageType"] = webidl.createEnumConverter( + // "GPUCompilationMessageType", + // [ + // "error", + // "warning", + // "info", + // ], + // ); + + // // INTERFACE: GPUCompilationMessage + // webidl.converters.GPUCompilationMessage = webidl.createInterfaceConverter( + // "GPUCompilationMessage", + // GPUCompilationMessage, + // ); + + // // INTERFACE: GPUCompilationInfo + // webidl.converters.GPUCompilationInfo = webidl.createInterfaceConverter( + // "GPUCompilationInfo", + // GPUCompilationInfo, + // ); + + // DICTIONARY: GPUPipelineDescriptorBase + const dictMembersGPUPipelineDescriptorBase = [ + { key: "layout", converter: webidl.converters["GPUPipelineLayout"] }, + ]; + webidl.converters["GPUPipelineDescriptorBase"] = webidl + .createDictionaryConverter( + "GPUPipelineDescriptorBase", + dictMembersGPUObjectDescriptorBase, + dictMembersGPUPipelineDescriptorBase, + ); + + // TYPEDEF: GPUPipelineConstantValue + webidl.converters.GPUPipelineConstantValue = webidl.converters.double; + + webidl.converters["record<USVString, GPUPipelineConstantValue>"] = webidl + .createRecordConverter( + webidl.converters.USVString, + webidl.converters.GPUPipelineConstantValue, + ); + + // DICTIONARY: GPUProgrammableStage + const dictMembersGPUProgrammableStage = [ + { + key: "module", + converter: webidl.converters["GPUShaderModule"], + required: true, + }, + { + key: "entryPoint", + converter: webidl.converters["USVString"], + required: true, + }, + { + key: "constants", + converter: + webidl.converters["record<USVString, GPUPipelineConstantValue>"], + }, + ]; + webidl.converters["GPUProgrammableStage"] = webidl.createDictionaryConverter( + "GPUProgrammableStage", + dictMembersGPUProgrammableStage, + ); + + // INTERFACE: GPUComputePipeline + webidl.converters.GPUComputePipeline = webidl.createInterfaceConverter( + "GPUComputePipeline", + GPUComputePipeline, + ); + + // DICTIONARY: GPUComputePipelineDescriptor + const dictMembersGPUComputePipelineDescriptor = [ + { + key: "compute", + converter: webidl.converters["GPUProgrammableStage"], + required: true, + }, + ]; + webidl.converters["GPUComputePipelineDescriptor"] = webidl + .createDictionaryConverter( + "GPUComputePipelineDescriptor", + dictMembersGPUPipelineDescriptorBase, + dictMembersGPUComputePipelineDescriptor, + ); + + // INTERFACE: GPURenderPipeline + webidl.converters.GPURenderPipeline = webidl.createInterfaceConverter( + "GPURenderPipeline", + GPURenderPipeline, + ); + + // ENUM: GPUInputStepMode + webidl.converters["GPUInputStepMode"] = webidl.createEnumConverter( + "GPUInputStepMode", + [ + "vertex", + "instance", + ], + ); + + // ENUM: GPUVertexFormat + webidl.converters["GPUVertexFormat"] = webidl.createEnumConverter( + "GPUVertexFormat", + [ + "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", + ], + ); + + // DICTIONARY: GPUVertexAttribute + const dictMembersGPUVertexAttribute = [ + { + key: "format", + converter: webidl.converters["GPUVertexFormat"], + required: true, + }, + { + key: "offset", + converter: webidl.converters["GPUSize64"], + required: true, + }, + { + key: "shaderLocation", + converter: webidl.converters["GPUIndex32"], + required: true, + }, + ]; + webidl.converters["GPUVertexAttribute"] = webidl.createDictionaryConverter( + "GPUVertexAttribute", + dictMembersGPUVertexAttribute, + ); + + // DICTIONARY: GPUVertexBufferLayout + const dictMembersGPUVertexBufferLayout = [ + { + key: "arrayStride", + converter: webidl.converters["GPUSize64"], + required: true, + }, + { + key: "stepMode", + converter: webidl.converters["GPUInputStepMode"], + defaultValue: "vertex", + }, + { + key: "attributes", + converter: webidl.createSequenceConverter( + webidl.converters["GPUVertexAttribute"], + ), + required: true, + }, + ]; + webidl.converters["GPUVertexBufferLayout"] = webidl.createDictionaryConverter( + "GPUVertexBufferLayout", + dictMembersGPUVertexBufferLayout, + ); + + // DICTIONARY: GPUVertexState + const dictMembersGPUVertexState = [ + { + key: "buffers", + converter: webidl.createSequenceConverter( + webidl.createNullableConverter( + webidl.converters["GPUVertexBufferLayout"], + ), + ), + get defaultValue() { + return []; + }, + }, + ]; + webidl.converters["GPUVertexState"] = webidl.createDictionaryConverter( + "GPUVertexState", + dictMembersGPUProgrammableStage, + dictMembersGPUVertexState, + ); + + // ENUM: GPUPrimitiveTopology + webidl.converters["GPUPrimitiveTopology"] = webidl.createEnumConverter( + "GPUPrimitiveTopology", + [ + "point-list", + "line-list", + "line-strip", + "triangle-list", + "triangle-strip", + ], + ); + + // ENUM: GPUIndexFormat + webidl.converters["GPUIndexFormat"] = webidl.createEnumConverter( + "GPUIndexFormat", + [ + "uint16", + "uint32", + ], + ); + + // ENUM: GPUFrontFace + webidl.converters["GPUFrontFace"] = webidl.createEnumConverter( + "GPUFrontFace", + [ + "ccw", + "cw", + ], + ); + + // ENUM: GPUCullMode + webidl.converters["GPUCullMode"] = webidl.createEnumConverter("GPUCullMode", [ + "none", + "front", + "back", + ]); + + // DICTIONARY: GPUPrimitiveState + const dictMembersGPUPrimitiveState = [ + { + key: "topology", + converter: webidl.converters["GPUPrimitiveTopology"], + defaultValue: "triangle-list", + }, + { key: "stripIndexFormat", converter: webidl.converters["GPUIndexFormat"] }, + { + key: "frontFace", + converter: webidl.converters["GPUFrontFace"], + defaultValue: "ccw", + }, + { + key: "cullMode", + converter: webidl.converters["GPUCullMode"], + defaultValue: "none", + }, + { + key: "clampDepth", + converter: webidl.converters["boolean"], + defaultValue: false, + }, + ]; + webidl.converters["GPUPrimitiveState"] = webidl.createDictionaryConverter( + "GPUPrimitiveState", + dictMembersGPUPrimitiveState, + ); + + // ENUM: GPUStencilOperation + webidl.converters["GPUStencilOperation"] = webidl.createEnumConverter( + "GPUStencilOperation", + [ + "keep", + "zero", + "replace", + "invert", + "increment-clamp", + "decrement-clamp", + "increment-wrap", + "decrement-wrap", + ], + ); + + // DICTIONARY: GPUStencilFaceState + const dictMembersGPUStencilFaceState = [ + { + key: "compare", + converter: webidl.converters["GPUCompareFunction"], + defaultValue: "always", + }, + { + key: "failOp", + converter: webidl.converters["GPUStencilOperation"], + defaultValue: "keep", + }, + { + key: "depthFailOp", + converter: webidl.converters["GPUStencilOperation"], + defaultValue: "keep", + }, + { + key: "passOp", + converter: webidl.converters["GPUStencilOperation"], + defaultValue: "keep", + }, + ]; + webidl.converters["GPUStencilFaceState"] = webidl.createDictionaryConverter( + "GPUStencilFaceState", + dictMembersGPUStencilFaceState, + ); + + // TYPEDEF: GPUStencilValue + webidl.converters["GPUStencilValue"] = (V, opts) => + webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }); + + // TYPEDEF: GPUDepthBias + webidl.converters["GPUDepthBias"] = (V, opts) => + webidl.converters["long"](V, { ...opts, enforceRange: true }); + + // DICTIONARY: GPUDepthStencilState + const dictMembersGPUDepthStencilState = [ + { + key: "format", + converter: webidl.converters["GPUTextureFormat"], + required: true, + }, + { + key: "depthWriteEnabled", + converter: webidl.converters["boolean"], + defaultValue: false, + }, + { + key: "depthCompare", + converter: webidl.converters["GPUCompareFunction"], + defaultValue: "always", + }, + { + key: "stencilFront", + converter: webidl.converters["GPUStencilFaceState"], + get defaultValue() { + return {}; + }, + }, + { + key: "stencilBack", + converter: webidl.converters["GPUStencilFaceState"], + get defaultValue() { + return {}; + }, + }, + { + key: "stencilReadMask", + converter: webidl.converters["GPUStencilValue"], + defaultValue: 0xFFFFFFFF, + }, + { + key: "stencilWriteMask", + converter: webidl.converters["GPUStencilValue"], + defaultValue: 0xFFFFFFFF, + }, + { + key: "depthBias", + converter: webidl.converters["GPUDepthBias"], + defaultValue: 0, + }, + { + key: "depthBiasSlopeScale", + converter: webidl.converters["float"], + defaultValue: 0, + }, + { + key: "depthBiasClamp", + converter: webidl.converters["float"], + defaultValue: 0, + }, + ]; + webidl.converters["GPUDepthStencilState"] = webidl.createDictionaryConverter( + "GPUDepthStencilState", + dictMembersGPUDepthStencilState, + ); + + // TYPEDEF: GPUSampleMask + webidl.converters["GPUSampleMask"] = (V, opts) => + webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }); + + // DICTIONARY: GPUMultisampleState + const dictMembersGPUMultisampleState = [ + { + key: "count", + converter: webidl.converters["GPUSize32"], + defaultValue: 1, + }, + { + key: "mask", + converter: webidl.converters["GPUSampleMask"], + defaultValue: 0xFFFFFFFF, + }, + { + key: "alphaToCoverageEnabled", + converter: webidl.converters["boolean"], + defaultValue: false, + }, + ]; + webidl.converters["GPUMultisampleState"] = webidl.createDictionaryConverter( + "GPUMultisampleState", + dictMembersGPUMultisampleState, + ); + + // ENUM: GPUBlendFactor + webidl.converters["GPUBlendFactor"] = webidl.createEnumConverter( + "GPUBlendFactor", + [ + "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", + ], + ); + + // ENUM: GPUBlendOperation + webidl.converters["GPUBlendOperation"] = webidl.createEnumConverter( + "GPUBlendOperation", + [ + "add", + "subtract", + "reverse-subtract", + "min", + "max", + ], + ); + + // DICTIONARY: GPUBlendComponent + const dictMembersGPUBlendComponent = [ + { + key: "srcFactor", + converter: webidl.converters["GPUBlendFactor"], + defaultValue: "one", + }, + { + key: "dstFactor", + converter: webidl.converters["GPUBlendFactor"], + defaultValue: "zero", + }, + { + key: "operation", + converter: webidl.converters["GPUBlendOperation"], + defaultValue: "add", + }, + ]; + webidl.converters["GPUBlendComponent"] = webidl.createDictionaryConverter( + "GPUBlendComponent", + dictMembersGPUBlendComponent, + ); + + // DICTIONARY: GPUBlendState + const dictMembersGPUBlendState = [ + { + key: "color", + converter: webidl.converters["GPUBlendComponent"], + required: true, + }, + { + key: "alpha", + converter: webidl.converters["GPUBlendComponent"], + required: true, + }, + ]; + webidl.converters["GPUBlendState"] = webidl.createDictionaryConverter( + "GPUBlendState", + dictMembersGPUBlendState, + ); + + // TYPEDEF: GPUColorWriteFlags + webidl.converters["GPUColorWriteFlags"] = (V, opts) => + webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }); + + // DICTIONARY: GPUColorTargetState + const dictMembersGPUColorTargetState = [ + { + key: "format", + converter: webidl.converters["GPUTextureFormat"], + required: true, + }, + { key: "blend", converter: webidl.converters["GPUBlendState"] }, + { + key: "writeMask", + converter: webidl.converters["GPUColorWriteFlags"], + defaultValue: 0xF, + }, + ]; + webidl.converters["GPUColorTargetState"] = webidl.createDictionaryConverter( + "GPUColorTargetState", + dictMembersGPUColorTargetState, + ); + + // DICTIONARY: GPUFragmentState + const dictMembersGPUFragmentState = [ + { + key: "targets", + converter: webidl.createSequenceConverter( + webidl.converters["GPUColorTargetState"], + ), + required: true, + }, + ]; + webidl.converters["GPUFragmentState"] = webidl.createDictionaryConverter( + "GPUFragmentState", + dictMembersGPUProgrammableStage, + dictMembersGPUFragmentState, + ); + + // DICTIONARY: GPURenderPipelineDescriptor + const dictMembersGPURenderPipelineDescriptor = [ + { + key: "vertex", + converter: webidl.converters["GPUVertexState"], + required: true, + }, + { + key: "primitive", + converter: webidl.converters["GPUPrimitiveState"], + get defaultValue() { + return {}; + }, + }, + { + key: "depthStencil", + converter: webidl.converters["GPUDepthStencilState"], + }, + { + key: "multisample", + converter: webidl.converters["GPUMultisampleState"], + get defaultValue() { + return {}; + }, + }, + { key: "fragment", converter: webidl.converters["GPUFragmentState"] }, + ]; + webidl.converters["GPURenderPipelineDescriptor"] = webidl + .createDictionaryConverter( + "GPURenderPipelineDescriptor", + dictMembersGPUPipelineDescriptorBase, + dictMembersGPURenderPipelineDescriptor, + ); + + // INTERFACE: GPUColorWrite + webidl.converters.GPUColorWrite = webidl.createInterfaceConverter( + "GPUColorWrite", + GPUColorWrite, + ); + + // INTERFACE: GPUCommandBuffer + webidl.converters.GPUCommandBuffer = webidl.createInterfaceConverter( + "GPUCommandBuffer", + GPUCommandBuffer, + ); + webidl.converters["sequence<GPUCommandBuffer>"] = webidl + .createSequenceConverter(webidl.converters["GPUCommandBuffer"]); + + // DICTIONARY: GPUCommandBufferDescriptor + const dictMembersGPUCommandBufferDescriptor = []; + webidl.converters["GPUCommandBufferDescriptor"] = webidl + .createDictionaryConverter( + "GPUCommandBufferDescriptor", + dictMembersGPUObjectDescriptorBase, + dictMembersGPUCommandBufferDescriptor, + ); + + // INTERFACE: GPUCommandEncoder + webidl.converters.GPUCommandEncoder = webidl.createInterfaceConverter( + "GPUCommandEncoder", + GPUCommandEncoder, + ); + + // DICTIONARY: GPUCommandEncoderDescriptor + const dictMembersGPUCommandEncoderDescriptor = [ + { + key: "measureExecutionTime", + converter: webidl.converters["boolean"], + defaultValue: false, + }, + ]; + webidl.converters["GPUCommandEncoderDescriptor"] = webidl + .createDictionaryConverter( + "GPUCommandEncoderDescriptor", + dictMembersGPUObjectDescriptorBase, + dictMembersGPUCommandEncoderDescriptor, + ); + + // DICTIONARY: GPUImageDataLayout + const dictMembersGPUImageDataLayout = [ + { + key: "offset", + converter: webidl.converters["GPUSize64"], + defaultValue: 0, + }, + { key: "bytesPerRow", converter: webidl.converters["GPUSize32"] }, + { key: "rowsPerImage", converter: webidl.converters["GPUSize32"] }, + ]; + webidl.converters["GPUImageDataLayout"] = webidl.createDictionaryConverter( + "GPUImageDataLayout", + dictMembersGPUImageDataLayout, + ); + + // DICTIONARY: GPUImageCopyBuffer + const dictMembersGPUImageCopyBuffer = [ + { + key: "buffer", + converter: webidl.converters["GPUBuffer"], + required: true, + }, + ]; + webidl.converters["GPUImageCopyBuffer"] = webidl.createDictionaryConverter( + "GPUImageCopyBuffer", + dictMembersGPUImageDataLayout, + dictMembersGPUImageCopyBuffer, + ); + + // DICTIONARY: GPUOrigin3DDict + const dictMembersGPUOrigin3DDict = [ + { + key: "x", + converter: webidl.converters["GPUIntegerCoordinate"], + defaultValue: 0, + }, + { + key: "y", + converter: webidl.converters["GPUIntegerCoordinate"], + defaultValue: 0, + }, + { + key: "z", + converter: webidl.converters["GPUIntegerCoordinate"], + defaultValue: 0, + }, + ]; + webidl.converters["GPUOrigin3DDict"] = webidl.createDictionaryConverter( + "GPUOrigin3DDict", + dictMembersGPUOrigin3DDict, + ); + + // TYPEDEF: GPUOrigin3D + webidl.converters["GPUOrigin3D"] = (V, opts) => { + // Union for (sequence<GPUIntegerCoordinate> or GPUOrigin3DDict) + if (V === null || V === undefined) { + return webidl.converters["GPUOrigin3DDict"](V, opts); + } + if (typeof V === "object") { + const method = V[SymbolIterator]; + if (method !== undefined) { + return webidl.converters["sequence<GPUIntegerCoordinate>"](V, opts); + } + return webidl.converters["GPUOrigin3DDict"](V, opts); + } + throw webidl.makeException( + TypeError, + "can not be converted to sequence<GPUIntegerCoordinate> or GPUOrigin3DDict.", + opts, + ); + }; + + // DICTIONARY: GPUImageCopyTexture + const dictMembersGPUImageCopyTexture = [ + { + key: "texture", + converter: webidl.converters["GPUTexture"], + required: true, + }, + { + key: "mipLevel", + converter: webidl.converters["GPUIntegerCoordinate"], + defaultValue: 0, + }, + { + key: "origin", + converter: webidl.converters["GPUOrigin3D"], + get defaultValue() { + return {}; + }, + }, + { + key: "aspect", + converter: webidl.converters["GPUTextureAspect"], + defaultValue: "all", + }, + ]; + webidl.converters["GPUImageCopyTexture"] = webidl.createDictionaryConverter( + "GPUImageCopyTexture", + dictMembersGPUImageCopyTexture, + ); + + // DICTIONARY: GPUOrigin2DDict + const dictMembersGPUOrigin2DDict = [ + { + key: "x", + converter: webidl.converters["GPUIntegerCoordinate"], + defaultValue: 0, + }, + { + key: "y", + converter: webidl.converters["GPUIntegerCoordinate"], + defaultValue: 0, + }, + ]; + webidl.converters["GPUOrigin2DDict"] = webidl.createDictionaryConverter( + "GPUOrigin2DDict", + dictMembersGPUOrigin2DDict, + ); + + // TYPEDEF: GPUOrigin2D + webidl.converters["GPUOrigin2D"] = (V, opts) => { + // Union for (sequence<GPUIntegerCoordinate> or GPUOrigin2DDict) + if (V === null || V === undefined) { + return webidl.converters["GPUOrigin2DDict"](V, opts); + } + if (typeof V === "object") { + const method = V[SymbolIterator]; + if (method !== undefined) { + return webidl.converters["sequence<GPUIntegerCoordinate>"](V, opts); + } + return webidl.converters["GPUOrigin2DDict"](V, opts); + } + throw webidl.makeException( + TypeError, + "can not be converted to sequence<GPUIntegerCoordinate> or GPUOrigin2DDict.", + opts, + ); + }; + + // INTERFACE: GPUComputePassEncoder + webidl.converters.GPUComputePassEncoder = webidl.createInterfaceConverter( + "GPUComputePassEncoder", + GPUComputePassEncoder, + ); + + // DICTIONARY: GPUComputePassDescriptor + const dictMembersGPUComputePassDescriptor = []; + webidl.converters["GPUComputePassDescriptor"] = webidl + .createDictionaryConverter( + "GPUComputePassDescriptor", + dictMembersGPUObjectDescriptorBase, + dictMembersGPUComputePassDescriptor, + ); + + // INTERFACE: GPURenderPassEncoder + webidl.converters.GPURenderPassEncoder = webidl.createInterfaceConverter( + "GPURenderPassEncoder", + GPURenderPassEncoder, + ); + + // ENUM: GPULoadOp + webidl.converters["GPULoadOp"] = webidl.createEnumConverter("GPULoadOp", [ + "load", + ]); + + // DICTIONARY: GPUColorDict + const dictMembersGPUColorDict = [ + { key: "r", converter: webidl.converters["double"], required: true }, + { key: "g", converter: webidl.converters["double"], required: true }, + { key: "b", converter: webidl.converters["double"], required: true }, + { key: "a", converter: webidl.converters["double"], required: true }, + ]; + webidl.converters["GPUColorDict"] = webidl.createDictionaryConverter( + "GPUColorDict", + dictMembersGPUColorDict, + ); + + // TYPEDEF: GPUColor + webidl.converters["GPUColor"] = (V, opts) => { + // Union for (sequence<double> or GPUColorDict) + if (V === null || V === undefined) { + return webidl.converters["GPUColorDict"](V, opts); + } + if (typeof V === "object") { + const method = V[SymbolIterator]; + if (method !== undefined) { + return webidl.converters["sequence<double>"](V, opts); + } + return webidl.converters["GPUColorDict"](V, opts); + } + throw webidl.makeException( + TypeError, + "can not be converted to sequence<double> or GPUColorDict.", + opts, + ); + }; + + // ENUM: GPUStoreOp + webidl.converters["GPUStoreOp"] = webidl.createEnumConverter("GPUStoreOp", [ + "store", + "discard", + ]); + + // DICTIONARY: GPURenderPassColorAttachment + const dictMembersGPURenderPassColorAttachment = [ + { + key: "view", + converter: webidl.converters["GPUTextureView"], + required: true, + }, + { key: "resolveTarget", converter: webidl.converters["GPUTextureView"] }, + { + key: "loadValue", + converter: webidl.converters.any, /** put union here! **/ + required: true, + }, + { + key: "storeOp", + converter: webidl.converters["GPUStoreOp"], + required: true, + }, + ]; + webidl.converters["GPURenderPassColorAttachment"] = webidl + .createDictionaryConverter( + "GPURenderPassColorAttachment", + dictMembersGPURenderPassColorAttachment, + ); + + // DICTIONARY: GPURenderPassDepthStencilAttachment + const dictMembersGPURenderPassDepthStencilAttachment = [ + { + key: "view", + converter: webidl.converters["GPUTextureView"], + required: true, + }, + { + key: "depthLoadValue", + converter: webidl.converters.any, /** put union here! **/ + required: true, + }, + { + key: "depthStoreOp", + converter: webidl.converters["GPUStoreOp"], + required: true, + }, + { + key: "depthReadOnly", + converter: webidl.converters["boolean"], + defaultValue: false, + }, + { + key: "stencilLoadValue", + converter: webidl.converters.any, /** put union here! **/ + required: true, + }, + { + key: "stencilStoreOp", + converter: webidl.converters["GPUStoreOp"], + required: true, + }, + { + key: "stencilReadOnly", + converter: webidl.converters["boolean"], + defaultValue: false, + }, + ]; + webidl.converters["GPURenderPassDepthStencilAttachment"] = webidl + .createDictionaryConverter( + "GPURenderPassDepthStencilAttachment", + dictMembersGPURenderPassDepthStencilAttachment, + ); + + // INTERFACE: GPUQuerySet + webidl.converters.GPUQuerySet = webidl.createInterfaceConverter( + "GPUQuerySet", + GPUQuerySet, + ); + + // DICTIONARY: GPURenderPassDescriptor + const dictMembersGPURenderPassDescriptor = [ + { + key: "colorAttachments", + converter: webidl.createSequenceConverter( + webidl.converters["GPURenderPassColorAttachment"], + ), + required: true, + }, + { + key: "depthStencilAttachment", + converter: webidl.converters["GPURenderPassDepthStencilAttachment"], + }, + { key: "occlusionQuerySet", converter: webidl.converters["GPUQuerySet"] }, + ]; + webidl.converters["GPURenderPassDescriptor"] = webidl + .createDictionaryConverter( + "GPURenderPassDescriptor", + dictMembersGPUObjectDescriptorBase, + dictMembersGPURenderPassDescriptor, + ); + + // INTERFACE: GPURenderBundle + webidl.converters.GPURenderBundle = webidl.createInterfaceConverter( + "GPURenderBundle", + GPURenderBundle, + ); + webidl.converters["sequence<GPURenderBundle>"] = webidl + .createSequenceConverter(webidl.converters["GPURenderBundle"]); + + // DICTIONARY: GPURenderBundleDescriptor + const dictMembersGPURenderBundleDescriptor = []; + webidl.converters["GPURenderBundleDescriptor"] = webidl + .createDictionaryConverter( + "GPURenderBundleDescriptor", + dictMembersGPUObjectDescriptorBase, + dictMembersGPURenderBundleDescriptor, + ); + + // INTERFACE: GPURenderBundleEncoder + webidl.converters.GPURenderBundleEncoder = webidl.createInterfaceConverter( + "GPURenderBundleEncoder", + GPURenderBundleEncoder, + ); + + // DICTIONARY: GPURenderBundleEncoderDescriptor + const dictMembersGPURenderBundleEncoderDescriptor = [ + { + key: "colorFormats", + converter: webidl.createSequenceConverter( + webidl.converters["GPUTextureFormat"], + ), + required: true, + }, + { + key: "depthStencilFormat", + converter: webidl.converters["GPUTextureFormat"], + }, + { + key: "sampleCount", + converter: webidl.converters["GPUSize32"], + defaultValue: 1, + }, + ]; + webidl.converters["GPURenderBundleEncoderDescriptor"] = webidl + .createDictionaryConverter( + "GPURenderBundleEncoderDescriptor", + dictMembersGPUObjectDescriptorBase, + dictMembersGPURenderBundleEncoderDescriptor, + ); + + // INTERFACE: GPUQueue + webidl.converters.GPUQueue = webidl.createInterfaceConverter( + "GPUQueue", + GPUQueue, + ); + + // ENUM: GPUQueryType + webidl.converters["GPUQueryType"] = webidl.createEnumConverter( + "GPUQueryType", + [ + "occlusion", + "pipeline-statistics", + "timestamp", + ], + ); + + // ENUM: GPUPipelineStatisticName + webidl.converters["GPUPipelineStatisticName"] = webidl.createEnumConverter( + "GPUPipelineStatisticName", + [ + "vertex-shader-invocations", + "clipper-invocations", + "clipper-primitives-out", + "fragment-shader-invocations", + "compute-shader-invocations", + ], + ); + + // DICTIONARY: GPUQuerySetDescriptor + const dictMembersGPUQuerySetDescriptor = [ + { + key: "type", + converter: webidl.converters["GPUQueryType"], + required: true, + }, + { key: "count", converter: webidl.converters["GPUSize32"], required: true }, + { + key: "pipelineStatistics", + converter: webidl.createSequenceConverter( + webidl.converters["GPUPipelineStatisticName"], + ), + get defaultValue() { + return []; + }, + }, + ]; + webidl.converters["GPUQuerySetDescriptor"] = webidl.createDictionaryConverter( + "GPUQuerySetDescriptor", + dictMembersGPUObjectDescriptorBase, + dictMembersGPUQuerySetDescriptor, + ); + + // ENUM: GPUDeviceLostReason + webidl.converters["GPUDeviceLostReason"] = webidl.createEnumConverter( + "GPUDeviceLostReason", + [ + "destroyed", + ], + ); + + // // INTERFACE: GPUDeviceLostInfo + // webidl.converters.GPUDeviceLostInfo = webidl.createInterfaceConverter( + // "GPUDeviceLostInfo", + // GPUDeviceLostInfo, + // ); + + // ENUM: GPUErrorFilter + webidl.converters["GPUErrorFilter"] = webidl.createEnumConverter( + "GPUErrorFilter", + [ + "out-of-memory", + "validation", + ], + ); + + // INTERFACE: GPUOutOfMemoryError + webidl.converters.GPUOutOfMemoryError = webidl.createInterfaceConverter( + "GPUOutOfMemoryError", + GPUOutOfMemoryError, + ); + + // INTERFACE: GPUValidationError + webidl.converters.GPUValidationError = webidl.createInterfaceConverter( + "GPUValidationError", + GPUValidationError, + ); + + // TYPEDEF: GPUError + webidl.converters["GPUError"] = webidl.converters.any /** put union here! **/; + + // // INTERFACE: GPUUncapturedErrorEvent + // webidl.converters.GPUUncapturedErrorEvent = webidl.createInterfaceConverter( + // "GPUUncapturedErrorEvent", + // GPUUncapturedErrorEvent, + // ); + + // DICTIONARY: GPUUncapturedErrorEventInit + const dictMembersGPUUncapturedErrorEventInit = [ + { key: "error", converter: webidl.converters["GPUError"], required: true }, + ]; + webidl.converters["GPUUncapturedErrorEventInit"] = webidl + .createDictionaryConverter( + "GPUUncapturedErrorEventInit", + // dictMembersEventInit, + dictMembersGPUUncapturedErrorEventInit, + ); + + // TYPEDEF: GPUBufferDynamicOffset + webidl.converters["GPUBufferDynamicOffset"] = (V, opts) => + webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }); + + // TYPEDEF: GPUSignedOffset32 + webidl.converters["GPUSignedOffset32"] = (V, opts) => + webidl.converters["long"](V, { ...opts, enforceRange: true }); + + // TYPEDEF: GPUFlagsConstant + webidl.converters["GPUFlagsConstant"] = webidl.converters["unsigned long"]; +})(this); diff --git a/ext/webgpu/Cargo.toml b/ext/webgpu/Cargo.toml new file mode 100644 index 000000000..1e1b36a41 --- /dev/null +++ b/ext/webgpu/Cargo.toml @@ -0,0 +1,21 @@ +# Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. + +[package] +name = "deno_webgpu" +version = "0.15.0" +authors = ["the Deno authors"] +edition = "2018" +license = "MIT" +readme = "README.md" +repository = "https://github.com/denoland/deno" +description = "WebGPU implementation for Deno" + +[lib] +path = "lib.rs" + +[dependencies] +deno_core = { version = "0.96.0", path = "../../core" } +serde = { version = "1.0.126", features = ["derive"] } +tokio = { version = "1.8.1", features = ["full"] } +wgpu-core = { version = "0.9.0", features = ["trace"] } +wgpu-types = "0.9.0" diff --git a/ext/webgpu/README.md b/ext/webgpu/README.md new file mode 100644 index 000000000..2f915dcbb --- /dev/null +++ b/ext/webgpu/README.md @@ -0,0 +1,35 @@ +# deno_webgpu + +This op crate implements the WebGPU API as defined in +https://gpuweb.github.io/gpuweb/ in Deno. The implementation targets the spec +draft as of February 22, 2021. The spec is still very much in flux. This op +crate tries to stay up to date with the spec, but is constrained by the features +implemented in our GPU backend library [wgpu](https://github.com/gfx-rs/wgpu). + +The spec is still very bare bones, and is still missing many details. As the +spec becomes more concrete, we will implement to follow the spec more closely. + +In addition, setting the `DENO_WEBGPU_TRACE` environmental variable will output +a +[wgpu trace](https://github.com/gfx-rs/wgpu/wiki/Debugging-wgpu-Applications#tracing-infrastructure) +to the specified directory. + +For testing this op crate will make use of the WebGPU conformance tests suite, +running through our WPT runner. This will be used to validate implementation +conformance. + +GitHub CI doesn't run with GPUs, so testing relies on software like DX WARP & +Vulkan lavapipe. Currently only using DX WARP works, so tests are only run on +Windows. + +## Links + +Specification: https://gpuweb.github.io/gpuweb/ + +Design documents: https://github.com/gpuweb/gpuweb/tree/main/design + +Conformance tests suite: https://github.com/gpuweb/cts + +WebGPU examples for Deno: https://github.com/crowlKats/webgpu-examples + +wgpu-users matrix channel: https://matrix.to/#/#wgpu-users:matrix.org diff --git a/ext/webgpu/binding.rs b/ext/webgpu/binding.rs new file mode 100644 index 000000000..d20eb97d9 --- /dev/null +++ b/ext/webgpu/binding.rs @@ -0,0 +1,335 @@ +// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. + +use deno_core::error::bad_resource_id; +use deno_core::error::AnyError; +use deno_core::ResourceId; +use deno_core::{OpState, Resource}; +use serde::Deserialize; +use std::borrow::Cow; + +use super::error::WebGpuResult; + +pub(crate) struct WebGpuBindGroupLayout( + pub(crate) wgpu_core::id::BindGroupLayoutId, +); +impl Resource for WebGpuBindGroupLayout { + fn name(&self) -> Cow<str> { + "webGPUBindGroupLayout".into() + } +} + +pub(crate) struct WebGpuBindGroup(pub(crate) wgpu_core::id::BindGroupId); +impl Resource for WebGpuBindGroup { + fn name(&self) -> Cow<str> { + "webGPUBindGroup".into() + } +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct GpuBufferBindingLayout { + #[serde(rename = "type")] + kind: Option<String>, + has_dynamic_offset: Option<bool>, + min_binding_size: Option<u64>, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct GpuSamplerBindingLayout { + #[serde(rename = "type")] + kind: Option<String>, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct GpuTextureBindingLayout { + sample_type: Option<String>, + view_dimension: Option<String>, + multisampled: Option<bool>, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct GpuStorageTextureBindingLayout { + access: String, + format: String, + view_dimension: Option<String>, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct GpuBindGroupLayoutEntry { + binding: u32, + visibility: u32, + buffer: Option<GpuBufferBindingLayout>, + sampler: Option<GpuSamplerBindingLayout>, + texture: Option<GpuTextureBindingLayout>, + storage_texture: Option<GpuStorageTextureBindingLayout>, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CreateBindGroupLayoutArgs { + device_rid: ResourceId, + label: Option<String>, + entries: Vec<GpuBindGroupLayoutEntry>, +} + +pub fn op_webgpu_create_bind_group_layout( + state: &mut OpState, + args: CreateBindGroupLayoutArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let instance = state.borrow::<super::Instance>(); + let device_resource = state + .resource_table + .get::<super::WebGpuDevice>(args.device_rid) + .ok_or_else(bad_resource_id)?; + let device = device_resource.0; + + let mut entries = vec![]; + + for entry in &args.entries { + entries.push(wgpu_types::BindGroupLayoutEntry { + binding: entry.binding, + visibility: wgpu_types::ShaderStage::from_bits(entry.visibility).unwrap(), + ty: if let Some(buffer) = &entry.buffer { + wgpu_types::BindingType::Buffer { + ty: match &buffer.kind { + Some(kind) => match kind.as_str() { + "uniform" => wgpu_types::BufferBindingType::Uniform, + "storage" => { + wgpu_types::BufferBindingType::Storage { read_only: false } + } + "read-only-storage" => { + wgpu_types::BufferBindingType::Storage { read_only: true } + } + _ => unreachable!(), + }, + None => wgpu_types::BufferBindingType::Uniform, + }, + has_dynamic_offset: buffer.has_dynamic_offset.unwrap_or(false), + min_binding_size: if let Some(min_binding_size) = + buffer.min_binding_size + { + std::num::NonZeroU64::new(min_binding_size) + } else { + None + }, + } + } else if let Some(sampler) = &entry.sampler { + match &sampler.kind { + Some(kind) => match kind.as_str() { + "filtering" => wgpu_types::BindingType::Sampler { + filtering: true, + comparison: false, + }, + "non-filtering" => wgpu_types::BindingType::Sampler { + filtering: false, + comparison: false, + }, + "comparison" => wgpu_types::BindingType::Sampler { + filtering: true, + comparison: true, + }, + _ => unreachable!(), + }, + None => wgpu_types::BindingType::Sampler { + filtering: true, + comparison: false, + }, + } + } else if let Some(texture) = &entry.texture { + wgpu_types::BindingType::Texture { + sample_type: match &texture.sample_type { + Some(sample_type) => match sample_type.as_str() { + "float" => { + wgpu_types::TextureSampleType::Float { filterable: true } + } + "unfilterable-float" => { + wgpu_types::TextureSampleType::Float { filterable: false } + } + "depth" => wgpu_types::TextureSampleType::Depth, + "sint" => wgpu_types::TextureSampleType::Sint, + "uint" => wgpu_types::TextureSampleType::Uint, + _ => unreachable!(), + }, + None => wgpu_types::TextureSampleType::Float { filterable: true }, + }, + view_dimension: match &texture.view_dimension { + Some(view_dimension) => { + super::texture::serialize_dimension(view_dimension) + } + None => wgpu_types::TextureViewDimension::D2, + }, + multisampled: texture.multisampled.unwrap_or(false), + } + } else if let Some(storage_texture) = &entry.storage_texture { + wgpu_types::BindingType::StorageTexture { + access: match storage_texture.access.as_str() { + "read-only" => wgpu_types::StorageTextureAccess::ReadOnly, + "write-only" => wgpu_types::StorageTextureAccess::WriteOnly, + _ => unreachable!(), + }, + format: super::texture::serialize_texture_format( + &storage_texture.format, + )?, + view_dimension: match &storage_texture.view_dimension { + Some(view_dimension) => { + super::texture::serialize_dimension(view_dimension) + } + None => wgpu_types::TextureViewDimension::D2, + }, + } + } else { + unreachable!() + }, + count: None, // native-only + }); + } + + let descriptor = wgpu_core::binding_model::BindGroupLayoutDescriptor { + label: args.label.map(Cow::from), + entries: Cow::from(entries), + }; + + gfx_put!(device => instance.device_create_bind_group_layout( + device, + &descriptor, + std::marker::PhantomData + ) => state, WebGpuBindGroupLayout) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CreatePipelineLayoutArgs { + device_rid: ResourceId, + label: Option<String>, + bind_group_layouts: Vec<u32>, +} + +pub fn op_webgpu_create_pipeline_layout( + state: &mut OpState, + args: CreatePipelineLayoutArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let instance = state.borrow::<super::Instance>(); + let device_resource = state + .resource_table + .get::<super::WebGpuDevice>(args.device_rid) + .ok_or_else(bad_resource_id)?; + let device = device_resource.0; + + let mut bind_group_layouts = vec![]; + + for rid in &args.bind_group_layouts { + let bind_group_layout = state + .resource_table + .get::<WebGpuBindGroupLayout>(*rid) + .ok_or_else(bad_resource_id)?; + bind_group_layouts.push(bind_group_layout.0); + } + + let descriptor = wgpu_core::binding_model::PipelineLayoutDescriptor { + label: args.label.map(Cow::from), + bind_group_layouts: Cow::from(bind_group_layouts), + push_constant_ranges: Default::default(), + }; + + gfx_put!(device => instance.device_create_pipeline_layout( + device, + &descriptor, + std::marker::PhantomData + ) => state, super::pipeline::WebGpuPipelineLayout) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct GpuBindGroupEntry { + binding: u32, + kind: String, + resource: u32, + offset: Option<u64>, + size: Option<u64>, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CreateBindGroupArgs { + device_rid: ResourceId, + label: Option<String>, + layout: u32, + entries: Vec<GpuBindGroupEntry>, +} + +pub fn op_webgpu_create_bind_group( + state: &mut OpState, + args: CreateBindGroupArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let instance = state.borrow::<super::Instance>(); + let device_resource = state + .resource_table + .get::<super::WebGpuDevice>(args.device_rid) + .ok_or_else(bad_resource_id)?; + let device = device_resource.0; + + let mut entries = vec![]; + + for entry in &args.entries { + let e = wgpu_core::binding_model::BindGroupEntry { + binding: entry.binding, + resource: match entry.kind.as_str() { + "GPUSampler" => { + let sampler_resource = state + .resource_table + .get::<super::sampler::WebGpuSampler>(entry.resource) + .ok_or_else(bad_resource_id)?; + wgpu_core::binding_model::BindingResource::Sampler(sampler_resource.0) + } + "GPUTextureView" => { + let texture_view_resource = state + .resource_table + .get::<super::texture::WebGpuTextureView>(entry.resource) + .ok_or_else(bad_resource_id)?; + wgpu_core::binding_model::BindingResource::TextureView( + texture_view_resource.0, + ) + } + "GPUBufferBinding" => { + let buffer_resource = state + .resource_table + .get::<super::buffer::WebGpuBuffer>(entry.resource) + .ok_or_else(bad_resource_id)?; + wgpu_core::binding_model::BindingResource::Buffer( + wgpu_core::binding_model::BufferBinding { + buffer_id: buffer_resource.0, + offset: entry.offset.unwrap_or(0), + size: std::num::NonZeroU64::new(entry.size.unwrap_or(0)), + }, + ) + } + _ => unreachable!(), + }, + }; + entries.push(e); + } + + let bind_group_layout = state + .resource_table + .get::<WebGpuBindGroupLayout>(args.layout) + .ok_or_else(bad_resource_id)?; + + let descriptor = wgpu_core::binding_model::BindGroupDescriptor { + label: args.label.map(Cow::from), + layout: bind_group_layout.0, + entries: Cow::from(entries), + }; + + gfx_put!(device => instance.device_create_bind_group( + device, + &descriptor, + std::marker::PhantomData + ) => state, WebGpuBindGroup) +} diff --git a/ext/webgpu/buffer.rs b/ext/webgpu/buffer.rs new file mode 100644 index 000000000..6980b6348 --- /dev/null +++ b/ext/webgpu/buffer.rs @@ -0,0 +1,241 @@ +// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. + +use deno_core::error::bad_resource_id; +use deno_core::error::null_opbuf; +use deno_core::error::type_error; +use deno_core::error::AnyError; +use deno_core::futures::channel::oneshot; +use deno_core::OpState; +use deno_core::Resource; +use deno_core::ResourceId; +use deno_core::ZeroCopyBuf; +use serde::Deserialize; +use std::borrow::Cow; +use std::cell::RefCell; +use std::rc::Rc; +use std::time::Duration; + +use super::error::DomExceptionOperationError; +use super::error::WebGpuResult; + +pub(crate) struct WebGpuBuffer(pub(crate) wgpu_core::id::BufferId); +impl Resource for WebGpuBuffer { + fn name(&self) -> Cow<str> { + "webGPUBuffer".into() + } +} + +struct WebGpuBufferMapped(*mut u8, usize); +impl Resource for WebGpuBufferMapped { + fn name(&self) -> Cow<str> { + "webGPUBufferMapped".into() + } +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CreateBufferArgs { + device_rid: ResourceId, + label: Option<String>, + size: u64, + usage: u32, + mapped_at_creation: Option<bool>, +} + +pub fn op_webgpu_create_buffer( + state: &mut OpState, + args: CreateBufferArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let instance = state.borrow::<super::Instance>(); + let device_resource = state + .resource_table + .get::<super::WebGpuDevice>(args.device_rid) + .ok_or_else(bad_resource_id)?; + let device = device_resource.0; + + let descriptor = wgpu_core::resource::BufferDescriptor { + label: args.label.map(Cow::from), + size: args.size, + usage: wgpu_types::BufferUsage::from_bits(args.usage) + .ok_or_else(|| type_error("usage is not valid"))?, + mapped_at_creation: args.mapped_at_creation.unwrap_or(false), + }; + + gfx_put!(device => instance.device_create_buffer( + device, + &descriptor, + std::marker::PhantomData + ) => state, WebGpuBuffer) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct BufferGetMapAsyncArgs { + buffer_rid: ResourceId, + device_rid: ResourceId, + mode: u32, + offset: u64, + size: u64, +} + +pub async fn op_webgpu_buffer_get_map_async( + state: Rc<RefCell<OpState>>, + args: BufferGetMapAsyncArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let (sender, receiver) = oneshot::channel::<Result<(), AnyError>>(); + + let device; + { + let state_ = state.borrow(); + let instance = state_.borrow::<super::Instance>(); + let buffer_resource = state_ + .resource_table + .get::<WebGpuBuffer>(args.buffer_rid) + .ok_or_else(bad_resource_id)?; + let buffer = buffer_resource.0; + let device_resource = state_ + .resource_table + .get::<super::WebGpuDevice>(args.device_rid) + .ok_or_else(bad_resource_id)?; + device = device_resource.0; + + let boxed_sender = Box::new(sender); + let sender_ptr = Box::into_raw(boxed_sender) as *mut u8; + + extern "C" fn buffer_map_future_wrapper( + status: wgpu_core::resource::BufferMapAsyncStatus, + user_data: *mut u8, + ) { + let sender_ptr = user_data as *mut oneshot::Sender<Result<(), AnyError>>; + let boxed_sender = unsafe { Box::from_raw(sender_ptr) }; + boxed_sender + .send(match status { + wgpu_core::resource::BufferMapAsyncStatus::Success => Ok(()), + _ => unreachable!(), // TODO + }) + .unwrap(); + } + + // TODO(lucacasonato): error handling + let maybe_err = gfx_select!(buffer => instance.buffer_map_async( + buffer, + args.offset..(args.offset + args.size), + wgpu_core::resource::BufferMapOperation { + host: match args.mode { + 1 => wgpu_core::device::HostMap::Read, + 2 => wgpu_core::device::HostMap::Write, + _ => unreachable!(), + }, + callback: buffer_map_future_wrapper, + user_data: sender_ptr, + } + )) + .err(); + + if maybe_err.is_some() { + return Ok(WebGpuResult::maybe_err(maybe_err)); + } + } + + let done = Rc::new(RefCell::new(false)); + let done_ = done.clone(); + let device_poll_fut = async move { + while !*done.borrow() { + { + let state = state.borrow(); + let instance = state.borrow::<super::Instance>(); + gfx_select!(device => instance.device_poll(device, false)).unwrap() + } + tokio::time::sleep(Duration::from_millis(10)).await; + } + Ok::<(), AnyError>(()) + }; + + let receiver_fut = async move { + receiver.await??; + let mut done = done_.borrow_mut(); + *done = true; + Ok::<(), AnyError>(()) + }; + + tokio::try_join!(device_poll_fut, receiver_fut)?; + + Ok(WebGpuResult::empty()) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct BufferGetMappedRangeArgs { + buffer_rid: ResourceId, + offset: u64, + size: Option<u64>, +} + +pub fn op_webgpu_buffer_get_mapped_range( + state: &mut OpState, + args: BufferGetMappedRangeArgs, + zero_copy: Option<ZeroCopyBuf>, +) -> Result<WebGpuResult, AnyError> { + let mut zero_copy = zero_copy.ok_or_else(null_opbuf)?; + let instance = state.borrow::<super::Instance>(); + let buffer_resource = state + .resource_table + .get::<WebGpuBuffer>(args.buffer_rid) + .ok_or_else(bad_resource_id)?; + let buffer = buffer_resource.0; + + let (slice_pointer, range_size) = + gfx_select!(buffer => instance.buffer_get_mapped_range( + buffer, + args.offset, + args.size + )) + .map_err(|e| DomExceptionOperationError::new(&e.to_string()))?; + + let slice = unsafe { + std::slice::from_raw_parts_mut(slice_pointer, range_size as usize) + }; + zero_copy.copy_from_slice(slice); + + let rid = state + .resource_table + .add(WebGpuBufferMapped(slice_pointer, range_size as usize)); + + Ok(WebGpuResult::rid(rid)) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct BufferUnmapArgs { + buffer_rid: ResourceId, + mapped_rid: ResourceId, +} + +pub fn op_webgpu_buffer_unmap( + state: &mut OpState, + args: BufferUnmapArgs, + zero_copy: Option<ZeroCopyBuf>, +) -> Result<WebGpuResult, AnyError> { + let mapped_resource = state + .resource_table + .take::<WebGpuBufferMapped>(args.mapped_rid) + .ok_or_else(bad_resource_id)?; + let instance = state.borrow::<super::Instance>(); + let buffer_resource = state + .resource_table + .get::<WebGpuBuffer>(args.buffer_rid) + .ok_or_else(bad_resource_id)?; + let buffer = buffer_resource.0; + + let slice_pointer = mapped_resource.0; + let size = mapped_resource.1; + + if let Some(buffer) = zero_copy { + let slice = unsafe { std::slice::from_raw_parts_mut(slice_pointer, size) }; + slice.copy_from_slice(&buffer); + } + + gfx_ok!(buffer => instance.buffer_unmap(buffer)) +} diff --git a/ext/webgpu/bundle.rs b/ext/webgpu/bundle.rs new file mode 100644 index 000000000..b0c161b04 --- /dev/null +++ b/ext/webgpu/bundle.rs @@ -0,0 +1,454 @@ +// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. + +use deno_core::error::bad_resource_id; +use deno_core::error::null_opbuf; +use deno_core::error::AnyError; +use deno_core::ResourceId; +use deno_core::ZeroCopyBuf; +use deno_core::{OpState, Resource}; +use serde::Deserialize; +use std::borrow::Cow; +use std::cell::RefCell; +use std::rc::Rc; + +use super::error::WebGpuResult; +use super::texture::serialize_texture_format; + +struct WebGpuRenderBundleEncoder( + RefCell<wgpu_core::command::RenderBundleEncoder>, +); +impl Resource for WebGpuRenderBundleEncoder { + fn name(&self) -> Cow<str> { + "webGPURenderBundleEncoder".into() + } +} + +pub(crate) struct WebGpuRenderBundle(pub(crate) wgpu_core::id::RenderBundleId); +impl Resource for WebGpuRenderBundle { + fn name(&self) -> Cow<str> { + "webGPURenderBundle".into() + } +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CreateRenderBundleEncoderArgs { + device_rid: ResourceId, + label: Option<String>, + color_formats: Vec<String>, + depth_stencil_format: Option<String>, + sample_count: Option<u32>, +} + +pub fn op_webgpu_create_render_bundle_encoder( + state: &mut OpState, + args: CreateRenderBundleEncoderArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let device_resource = state + .resource_table + .get::<super::WebGpuDevice>(args.device_rid) + .ok_or_else(bad_resource_id)?; + let device = device_resource.0; + + let mut color_formats = vec![]; + + for format in &args.color_formats { + color_formats.push(serialize_texture_format(format)?); + } + + let descriptor = wgpu_core::command::RenderBundleEncoderDescriptor { + label: args.label.map(Cow::from), + color_formats: Cow::from(color_formats), + depth_stencil_format: args + .depth_stencil_format + .map(|s| serialize_texture_format(&s)) + .transpose()?, + sample_count: args.sample_count.unwrap_or(1), + }; + + let res = + wgpu_core::command::RenderBundleEncoder::new(&descriptor, device, None); + let (render_bundle_encoder, maybe_err) = match res { + Ok(encoder) => (encoder, None), + Err(e) => ( + wgpu_core::command::RenderBundleEncoder::dummy(device), + Some(e), + ), + }; + + let rid = state + .resource_table + .add(WebGpuRenderBundleEncoder(RefCell::new( + render_bundle_encoder, + ))); + + Ok(WebGpuResult::rid_err(rid, maybe_err)) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderBundleEncoderFinishArgs { + render_bundle_encoder_rid: ResourceId, + label: Option<String>, +} + +pub fn op_webgpu_render_bundle_encoder_finish( + state: &mut OpState, + args: RenderBundleEncoderFinishArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let render_bundle_encoder_resource = state + .resource_table + .take::<WebGpuRenderBundleEncoder>(args.render_bundle_encoder_rid) + .ok_or_else(bad_resource_id)?; + let render_bundle_encoder = Rc::try_unwrap(render_bundle_encoder_resource) + .ok() + .expect("unwrapping render_bundle_encoder_resource should succeed") + .0 + .into_inner(); + let instance = state.borrow::<super::Instance>(); + + gfx_put!(render_bundle_encoder.parent() => instance.render_bundle_encoder_finish( + render_bundle_encoder, + &wgpu_core::command::RenderBundleDescriptor { + label: args.label.map(Cow::from), + }, + std::marker::PhantomData + ) => state, WebGpuRenderBundle) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderBundleEncoderSetBindGroupArgs { + render_bundle_encoder_rid: ResourceId, + index: u32, + bind_group: u32, + dynamic_offsets_data: Option<Vec<u32>>, + dynamic_offsets_data_start: usize, + dynamic_offsets_data_length: usize, +} + +pub fn op_webgpu_render_bundle_encoder_set_bind_group( + state: &mut OpState, + args: RenderBundleEncoderSetBindGroupArgs, + zero_copy: Option<ZeroCopyBuf>, +) -> Result<WebGpuResult, AnyError> { + let bind_group_resource = state + .resource_table + .get::<super::binding::WebGpuBindGroup>(args.bind_group) + .ok_or_else(bad_resource_id)?; + let render_bundle_encoder_resource = state + .resource_table + .get::<WebGpuRenderBundleEncoder>(args.render_bundle_encoder_rid) + .ok_or_else(bad_resource_id)?; + + // I know this might look like it can be easily deduplicated, but it can not + // be due to the lifetime of the args.dynamic_offsets_data slice. Because we + // need to use a raw pointer here the slice can be freed before the pointer + // is used in wgpu_render_pass_set_bind_group. See + // https://matrix.to/#/!XFRnMvAfptAHthwBCx:matrix.org/$HgrlhD-Me1DwsGb8UdMu2Hqubgks8s7ILwWRwigOUAg + match args.dynamic_offsets_data { + Some(data) => unsafe { + wgpu_core::command::bundle_ffi::wgpu_render_bundle_set_bind_group( + &mut render_bundle_encoder_resource.0.borrow_mut(), + args.index, + bind_group_resource.0, + data.as_slice().as_ptr(), + args.dynamic_offsets_data_length, + ); + }, + None => { + let zero_copy = zero_copy.ok_or_else(null_opbuf)?; + let (prefix, data, suffix) = unsafe { zero_copy.align_to::<u32>() }; + assert!(prefix.is_empty()); + assert!(suffix.is_empty()); + unsafe { + wgpu_core::command::bundle_ffi::wgpu_render_bundle_set_bind_group( + &mut render_bundle_encoder_resource.0.borrow_mut(), + args.index, + bind_group_resource.0, + data[args.dynamic_offsets_data_start..].as_ptr(), + args.dynamic_offsets_data_length, + ); + } + } + }; + + Ok(WebGpuResult::empty()) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderBundleEncoderPushDebugGroupArgs { + render_bundle_encoder_rid: ResourceId, + group_label: String, +} + +pub fn op_webgpu_render_bundle_encoder_push_debug_group( + state: &mut OpState, + args: RenderBundleEncoderPushDebugGroupArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let render_bundle_encoder_resource = state + .resource_table + .get::<WebGpuRenderBundleEncoder>(args.render_bundle_encoder_rid) + .ok_or_else(bad_resource_id)?; + + unsafe { + let label = std::ffi::CString::new(args.group_label).unwrap(); + wgpu_core::command::bundle_ffi::wgpu_render_bundle_push_debug_group( + &mut render_bundle_encoder_resource.0.borrow_mut(), + label.as_ptr(), + ); + } + + Ok(WebGpuResult::empty()) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderBundleEncoderPopDebugGroupArgs { + render_bundle_encoder_rid: ResourceId, +} + +pub fn op_webgpu_render_bundle_encoder_pop_debug_group( + state: &mut OpState, + args: RenderBundleEncoderPopDebugGroupArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let render_bundle_encoder_resource = state + .resource_table + .get::<WebGpuRenderBundleEncoder>(args.render_bundle_encoder_rid) + .ok_or_else(bad_resource_id)?; + + wgpu_core::command::bundle_ffi::wgpu_render_bundle_pop_debug_group( + &mut render_bundle_encoder_resource.0.borrow_mut(), + ); + + Ok(WebGpuResult::empty()) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderBundleEncoderInsertDebugMarkerArgs { + render_bundle_encoder_rid: ResourceId, + marker_label: String, +} + +pub fn op_webgpu_render_bundle_encoder_insert_debug_marker( + state: &mut OpState, + args: RenderBundleEncoderInsertDebugMarkerArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let render_bundle_encoder_resource = state + .resource_table + .get::<WebGpuRenderBundleEncoder>(args.render_bundle_encoder_rid) + .ok_or_else(bad_resource_id)?; + + unsafe { + let label = std::ffi::CString::new(args.marker_label).unwrap(); + wgpu_core::command::bundle_ffi::wgpu_render_bundle_insert_debug_marker( + &mut render_bundle_encoder_resource.0.borrow_mut(), + label.as_ptr(), + ); + } + + Ok(WebGpuResult::empty()) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderBundleEncoderSetPipelineArgs { + render_bundle_encoder_rid: ResourceId, + pipeline: u32, +} + +pub fn op_webgpu_render_bundle_encoder_set_pipeline( + state: &mut OpState, + args: RenderBundleEncoderSetPipelineArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let render_pipeline_resource = state + .resource_table + .get::<super::pipeline::WebGpuRenderPipeline>(args.pipeline) + .ok_or_else(bad_resource_id)?; + let render_bundle_encoder_resource = state + .resource_table + .get::<WebGpuRenderBundleEncoder>(args.render_bundle_encoder_rid) + .ok_or_else(bad_resource_id)?; + + wgpu_core::command::bundle_ffi::wgpu_render_bundle_set_pipeline( + &mut render_bundle_encoder_resource.0.borrow_mut(), + render_pipeline_resource.0, + ); + + Ok(WebGpuResult::empty()) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderBundleEncoderSetIndexBufferArgs { + render_bundle_encoder_rid: ResourceId, + buffer: u32, + index_format: String, + offset: u64, + size: u64, +} + +pub fn op_webgpu_render_bundle_encoder_set_index_buffer( + state: &mut OpState, + args: RenderBundleEncoderSetIndexBufferArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let buffer_resource = state + .resource_table + .get::<super::buffer::WebGpuBuffer>(args.buffer) + .ok_or_else(bad_resource_id)?; + let render_bundle_encoder_resource = state + .resource_table + .get::<WebGpuRenderBundleEncoder>(args.render_bundle_encoder_rid) + .ok_or_else(bad_resource_id)?; + + render_bundle_encoder_resource + .0 + .borrow_mut() + .set_index_buffer( + buffer_resource.0, + super::pipeline::serialize_index_format(args.index_format), + args.offset, + std::num::NonZeroU64::new(args.size), + ); + + Ok(WebGpuResult::empty()) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderBundleEncoderSetVertexBufferArgs { + render_bundle_encoder_rid: ResourceId, + slot: u32, + buffer: u32, + offset: u64, + size: u64, +} + +pub fn op_webgpu_render_bundle_encoder_set_vertex_buffer( + state: &mut OpState, + args: RenderBundleEncoderSetVertexBufferArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let buffer_resource = state + .resource_table + .get::<super::buffer::WebGpuBuffer>(args.buffer) + .ok_or_else(bad_resource_id)?; + let render_bundle_encoder_resource = state + .resource_table + .get::<WebGpuRenderBundleEncoder>(args.render_bundle_encoder_rid) + .ok_or_else(bad_resource_id)?; + + wgpu_core::command::bundle_ffi::wgpu_render_bundle_set_vertex_buffer( + &mut render_bundle_encoder_resource.0.borrow_mut(), + args.slot, + buffer_resource.0, + args.offset, + std::num::NonZeroU64::new(args.size), + ); + + Ok(WebGpuResult::empty()) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderBundleEncoderDrawArgs { + render_bundle_encoder_rid: ResourceId, + vertex_count: u32, + instance_count: u32, + first_vertex: u32, + first_instance: u32, +} + +pub fn op_webgpu_render_bundle_encoder_draw( + state: &mut OpState, + args: RenderBundleEncoderDrawArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let render_bundle_encoder_resource = state + .resource_table + .get::<WebGpuRenderBundleEncoder>(args.render_bundle_encoder_rid) + .ok_or_else(bad_resource_id)?; + + wgpu_core::command::bundle_ffi::wgpu_render_bundle_draw( + &mut render_bundle_encoder_resource.0.borrow_mut(), + args.vertex_count, + args.instance_count, + args.first_vertex, + args.first_instance, + ); + + Ok(WebGpuResult::empty()) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderBundleEncoderDrawIndexedArgs { + render_bundle_encoder_rid: ResourceId, + index_count: u32, + instance_count: u32, + first_index: u32, + base_vertex: i32, + first_instance: u32, +} + +pub fn op_webgpu_render_bundle_encoder_draw_indexed( + state: &mut OpState, + args: RenderBundleEncoderDrawIndexedArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let render_bundle_encoder_resource = state + .resource_table + .get::<WebGpuRenderBundleEncoder>(args.render_bundle_encoder_rid) + .ok_or_else(bad_resource_id)?; + + wgpu_core::command::bundle_ffi::wgpu_render_bundle_draw_indexed( + &mut render_bundle_encoder_resource.0.borrow_mut(), + args.index_count, + args.instance_count, + args.first_index, + args.base_vertex, + args.first_instance, + ); + + Ok(WebGpuResult::empty()) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderBundleEncoderDrawIndirectArgs { + render_bundle_encoder_rid: ResourceId, + indirect_buffer: u32, + indirect_offset: u64, +} + +pub fn op_webgpu_render_bundle_encoder_draw_indirect( + state: &mut OpState, + args: RenderBundleEncoderDrawIndirectArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let buffer_resource = state + .resource_table + .get::<super::buffer::WebGpuBuffer>(args.indirect_buffer) + .ok_or_else(bad_resource_id)?; + let render_bundle_encoder_resource = state + .resource_table + .get::<WebGpuRenderBundleEncoder>(args.render_bundle_encoder_rid) + .ok_or_else(bad_resource_id)?; + + wgpu_core::command::bundle_ffi::wgpu_render_bundle_draw_indirect( + &mut render_bundle_encoder_resource.0.borrow_mut(), + buffer_resource.0, + args.indirect_offset, + ); + + Ok(WebGpuResult::empty()) +} diff --git a/ext/webgpu/command_encoder.rs b/ext/webgpu/command_encoder.rs new file mode 100644 index 000000000..6821a2954 --- /dev/null +++ b/ext/webgpu/command_encoder.rs @@ -0,0 +1,692 @@ +// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. + +use deno_core::error::bad_resource_id; +use deno_core::error::AnyError; +use deno_core::ResourceId; +use deno_core::{OpState, Resource}; +use serde::Deserialize; +use std::borrow::Cow; +use std::cell::RefCell; +use std::num::NonZeroU32; + +use super::error::WebGpuResult; + +pub(crate) struct WebGpuCommandEncoder( + pub(crate) wgpu_core::id::CommandEncoderId, +); +impl Resource for WebGpuCommandEncoder { + fn name(&self) -> Cow<str> { + "webGPUCommandEncoder".into() + } +} + +pub(crate) struct WebGpuCommandBuffer( + pub(crate) wgpu_core::id::CommandBufferId, +); +impl Resource for WebGpuCommandBuffer { + fn name(&self) -> Cow<str> { + "webGPUCommandBuffer".into() + } +} + +fn serialize_store_op(store_op: String) -> wgpu_core::command::StoreOp { + match store_op.as_str() { + "store" => wgpu_core::command::StoreOp::Store, + "discard" => wgpu_core::command::StoreOp::Clear, + _ => unreachable!(), + } +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CreateCommandEncoderArgs { + device_rid: ResourceId, + label: Option<String>, + _measure_execution_time: Option<bool>, // not yet implemented +} + +pub fn op_webgpu_create_command_encoder( + state: &mut OpState, + args: CreateCommandEncoderArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let instance = state.borrow::<super::Instance>(); + let device_resource = state + .resource_table + .get::<super::WebGpuDevice>(args.device_rid) + .ok_or_else(bad_resource_id)?; + let device = device_resource.0; + + let descriptor = wgpu_types::CommandEncoderDescriptor { + label: args.label.map(Cow::from), + }; + + gfx_put!(device => instance.device_create_command_encoder( + device, + &descriptor, + std::marker::PhantomData + ) => state, WebGpuCommandEncoder) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct GpuRenderPassColorAttachment { + view: u32, + resolve_target: Option<u32>, + load_op: String, + load_value: Option<super::render_pass::GpuColor>, + store_op: Option<String>, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct GpuRenderPassDepthStencilAttachment { + view: u32, + depth_load_op: String, + depth_load_value: Option<f32>, + depth_store_op: String, + depth_read_only: Option<bool>, + stencil_load_op: String, + stencil_load_value: Option<u32>, + stencil_store_op: String, + stencil_read_only: Option<bool>, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CommandEncoderBeginRenderPassArgs { + command_encoder_rid: ResourceId, + label: Option<String>, + color_attachments: Vec<GpuRenderPassColorAttachment>, + depth_stencil_attachment: Option<GpuRenderPassDepthStencilAttachment>, + _occlusion_query_set: Option<u32>, // not yet implemented +} + +pub fn op_webgpu_command_encoder_begin_render_pass( + state: &mut OpState, + args: CommandEncoderBeginRenderPassArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let command_encoder_resource = state + .resource_table + .get::<WebGpuCommandEncoder>(args.command_encoder_rid) + .ok_or_else(bad_resource_id)?; + + let mut color_attachments = vec![]; + + for color_attachment in args.color_attachments { + let texture_view_resource = state + .resource_table + .get::<super::texture::WebGpuTextureView>(color_attachment.view) + .ok_or_else(bad_resource_id)?; + + let attachment = wgpu_core::command::RenderPassColorAttachment { + view: texture_view_resource.0, + resolve_target: color_attachment + .resolve_target + .map(|rid| { + state + .resource_table + .get::<super::texture::WebGpuTextureView>(rid) + .ok_or_else(bad_resource_id) + }) + .transpose()? + .map(|texture| texture.0), + channel: match color_attachment.load_op.as_str() { + "load" => wgpu_core::command::PassChannel { + load_op: wgpu_core::command::LoadOp::Load, + store_op: color_attachment + .store_op + .map_or(wgpu_core::command::StoreOp::Store, serialize_store_op), + clear_value: Default::default(), + read_only: false, + }, + "clear" => { + let color = color_attachment.load_value.unwrap(); + wgpu_core::command::PassChannel { + load_op: wgpu_core::command::LoadOp::Clear, + store_op: color_attachment + .store_op + .map_or(wgpu_core::command::StoreOp::Store, serialize_store_op), + clear_value: wgpu_types::Color { + r: color.r, + g: color.g, + b: color.b, + a: color.a, + }, + read_only: false, + } + } + _ => unreachable!(), + }, + }; + + color_attachments.push(attachment) + } + + let mut depth_stencil_attachment = None; + + if let Some(attachment) = args.depth_stencil_attachment { + let texture_view_resource = state + .resource_table + .get::<super::texture::WebGpuTextureView>(attachment.view) + .ok_or_else(bad_resource_id)?; + + depth_stencil_attachment = + Some(wgpu_core::command::RenderPassDepthStencilAttachment { + view: texture_view_resource.0, + depth: match attachment.depth_load_op.as_str() { + "load" => wgpu_core::command::PassChannel { + load_op: wgpu_core::command::LoadOp::Load, + store_op: serialize_store_op(attachment.depth_store_op), + clear_value: 0.0, + read_only: attachment.depth_read_only.unwrap_or(false), + }, + "clear" => wgpu_core::command::PassChannel { + load_op: wgpu_core::command::LoadOp::Clear, + store_op: serialize_store_op(attachment.depth_store_op), + clear_value: attachment.depth_load_value.unwrap(), + read_only: attachment.depth_read_only.unwrap_or(false), + }, + _ => unreachable!(), + }, + stencil: match attachment.stencil_load_op.as_str() { + "load" => wgpu_core::command::PassChannel { + load_op: wgpu_core::command::LoadOp::Load, + store_op: serialize_store_op(attachment.stencil_store_op), + clear_value: 0, + read_only: attachment.stencil_read_only.unwrap_or(false), + }, + "clear" => wgpu_core::command::PassChannel { + load_op: wgpu_core::command::LoadOp::Clear, + store_op: serialize_store_op(attachment.stencil_store_op), + clear_value: attachment.stencil_load_value.unwrap(), + read_only: attachment.stencil_read_only.unwrap_or(false), + }, + _ => unreachable!(), + }, + }); + } + + let descriptor = wgpu_core::command::RenderPassDescriptor { + label: args.label.map(Cow::from), + color_attachments: Cow::from(color_attachments), + depth_stencil_attachment: depth_stencil_attachment.as_ref(), + }; + + let render_pass = wgpu_core::command::RenderPass::new( + command_encoder_resource.0, + &descriptor, + ); + + let rid = state + .resource_table + .add(super::render_pass::WebGpuRenderPass(RefCell::new( + render_pass, + ))); + + Ok(WebGpuResult::rid(rid)) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CommandEncoderBeginComputePassArgs { + command_encoder_rid: ResourceId, + label: Option<String>, +} + +pub fn op_webgpu_command_encoder_begin_compute_pass( + state: &mut OpState, + args: CommandEncoderBeginComputePassArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let command_encoder_resource = state + .resource_table + .get::<WebGpuCommandEncoder>(args.command_encoder_rid) + .ok_or_else(bad_resource_id)?; + + let descriptor = wgpu_core::command::ComputePassDescriptor { + label: args.label.map(Cow::from), + }; + + let compute_pass = wgpu_core::command::ComputePass::new( + command_encoder_resource.0, + &descriptor, + ); + + let rid = state + .resource_table + .add(super::compute_pass::WebGpuComputePass(RefCell::new( + compute_pass, + ))); + + Ok(WebGpuResult::rid(rid)) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CommandEncoderCopyBufferToBufferArgs { + command_encoder_rid: ResourceId, + source: u32, + source_offset: u64, + destination: u32, + destination_offset: u64, + size: u64, +} + +pub fn op_webgpu_command_encoder_copy_buffer_to_buffer( + state: &mut OpState, + args: CommandEncoderCopyBufferToBufferArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let instance = state.borrow::<super::Instance>(); + let command_encoder_resource = state + .resource_table + .get::<WebGpuCommandEncoder>(args.command_encoder_rid) + .ok_or_else(bad_resource_id)?; + let command_encoder = command_encoder_resource.0; + let source_buffer_resource = state + .resource_table + .get::<super::buffer::WebGpuBuffer>(args.source) + .ok_or_else(bad_resource_id)?; + let source_buffer = source_buffer_resource.0; + let destination_buffer_resource = state + .resource_table + .get::<super::buffer::WebGpuBuffer>(args.destination) + .ok_or_else(bad_resource_id)?; + let destination_buffer = destination_buffer_resource.0; + + gfx_ok!(command_encoder => instance.command_encoder_copy_buffer_to_buffer( + command_encoder, + source_buffer, + args.source_offset, + destination_buffer, + args.destination_offset, + args.size + )) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct GpuImageCopyBuffer { + buffer: u32, + offset: Option<u64>, + bytes_per_row: Option<u32>, + rows_per_image: Option<u32>, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct GpuOrigin3D { + pub x: Option<u32>, + pub y: Option<u32>, + pub z: Option<u32>, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct GpuImageCopyTexture { + pub texture: u32, + pub mip_level: Option<u32>, + pub origin: Option<GpuOrigin3D>, + pub _aspect: Option<String>, // not yet implemented +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CommandEncoderCopyBufferToTextureArgs { + command_encoder_rid: ResourceId, + source: GpuImageCopyBuffer, + destination: GpuImageCopyTexture, + copy_size: super::texture::GpuExtent3D, +} + +pub fn op_webgpu_command_encoder_copy_buffer_to_texture( + state: &mut OpState, + args: CommandEncoderCopyBufferToTextureArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let instance = state.borrow::<super::Instance>(); + let command_encoder_resource = state + .resource_table + .get::<WebGpuCommandEncoder>(args.command_encoder_rid) + .ok_or_else(bad_resource_id)?; + let command_encoder = command_encoder_resource.0; + let source_buffer_resource = state + .resource_table + .get::<super::buffer::WebGpuBuffer>(args.source.buffer) + .ok_or_else(bad_resource_id)?; + let destination_texture_resource = state + .resource_table + .get::<super::texture::WebGpuTexture>(args.destination.texture) + .ok_or_else(bad_resource_id)?; + + let source = wgpu_core::command::ImageCopyBuffer { + buffer: source_buffer_resource.0, + layout: wgpu_types::ImageDataLayout { + offset: args.source.offset.unwrap_or(0), + bytes_per_row: NonZeroU32::new(args.source.bytes_per_row.unwrap_or(0)), + rows_per_image: NonZeroU32::new(args.source.rows_per_image.unwrap_or(0)), + }, + }; + let destination = wgpu_core::command::ImageCopyTexture { + texture: destination_texture_resource.0, + mip_level: args.destination.mip_level.unwrap_or(0), + origin: args + .destination + .origin + .map_or(Default::default(), |origin| wgpu_types::Origin3d { + x: origin.x.unwrap_or(0), + y: origin.y.unwrap_or(0), + z: origin.z.unwrap_or(0), + }), + }; + gfx_ok!(command_encoder => instance.command_encoder_copy_buffer_to_texture( + command_encoder, + &source, + &destination, + &wgpu_types::Extent3d { + width: args.copy_size.width.unwrap_or(1), + height: args.copy_size.height.unwrap_or(1), + depth_or_array_layers: args.copy_size.depth_or_array_layers.unwrap_or(1), + } + )) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CommandEncoderCopyTextureToBufferArgs { + command_encoder_rid: ResourceId, + source: GpuImageCopyTexture, + destination: GpuImageCopyBuffer, + copy_size: super::texture::GpuExtent3D, +} + +pub fn op_webgpu_command_encoder_copy_texture_to_buffer( + state: &mut OpState, + args: CommandEncoderCopyTextureToBufferArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let instance = state.borrow::<super::Instance>(); + let command_encoder_resource = state + .resource_table + .get::<WebGpuCommandEncoder>(args.command_encoder_rid) + .ok_or_else(bad_resource_id)?; + let command_encoder = command_encoder_resource.0; + let source_texture_resource = state + .resource_table + .get::<super::texture::WebGpuTexture>(args.source.texture) + .ok_or_else(bad_resource_id)?; + let destination_buffer_resource = state + .resource_table + .get::<super::buffer::WebGpuBuffer>(args.destination.buffer) + .ok_or_else(bad_resource_id)?; + + let source = wgpu_core::command::ImageCopyTexture { + texture: source_texture_resource.0, + mip_level: args.source.mip_level.unwrap_or(0), + origin: args.source.origin.map_or(Default::default(), |origin| { + wgpu_types::Origin3d { + x: origin.x.unwrap_or(0), + y: origin.y.unwrap_or(0), + z: origin.z.unwrap_or(0), + } + }), + }; + let destination = wgpu_core::command::ImageCopyBuffer { + buffer: destination_buffer_resource.0, + layout: wgpu_types::ImageDataLayout { + offset: args.destination.offset.unwrap_or(0), + bytes_per_row: NonZeroU32::new( + args.destination.bytes_per_row.unwrap_or(0), + ), + rows_per_image: NonZeroU32::new( + args.destination.rows_per_image.unwrap_or(0), + ), + }, + }; + gfx_ok!(command_encoder => instance.command_encoder_copy_texture_to_buffer( + command_encoder, + &source, + &destination, + &wgpu_types::Extent3d { + width: args.copy_size.width.unwrap_or(1), + height: args.copy_size.height.unwrap_or(1), + depth_or_array_layers: args.copy_size.depth_or_array_layers.unwrap_or(1), + } + )) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CommandEncoderCopyTextureToTextureArgs { + command_encoder_rid: ResourceId, + source: GpuImageCopyTexture, + destination: GpuImageCopyTexture, + copy_size: super::texture::GpuExtent3D, +} + +pub fn op_webgpu_command_encoder_copy_texture_to_texture( + state: &mut OpState, + args: CommandEncoderCopyTextureToTextureArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let instance = state.borrow::<super::Instance>(); + let command_encoder_resource = state + .resource_table + .get::<WebGpuCommandEncoder>(args.command_encoder_rid) + .ok_or_else(bad_resource_id)?; + let command_encoder = command_encoder_resource.0; + let source_texture_resource = state + .resource_table + .get::<super::texture::WebGpuTexture>(args.source.texture) + .ok_or_else(bad_resource_id)?; + let destination_texture_resource = state + .resource_table + .get::<super::texture::WebGpuTexture>(args.destination.texture) + .ok_or_else(bad_resource_id)?; + + let source = wgpu_core::command::ImageCopyTexture { + texture: source_texture_resource.0, + mip_level: args.source.mip_level.unwrap_or(0), + origin: args.source.origin.map_or(Default::default(), |origin| { + wgpu_types::Origin3d { + x: origin.x.unwrap_or(0), + y: origin.y.unwrap_or(0), + z: origin.z.unwrap_or(0), + } + }), + }; + let destination = wgpu_core::command::ImageCopyTexture { + texture: destination_texture_resource.0, + mip_level: args.destination.mip_level.unwrap_or(0), + origin: args + .destination + .origin + .map_or(Default::default(), |origin| wgpu_types::Origin3d { + x: origin.x.unwrap_or(0), + y: origin.y.unwrap_or(0), + z: origin.z.unwrap_or(0), + }), + }; + gfx_ok!(command_encoder => instance.command_encoder_copy_texture_to_texture( + command_encoder, + &source, + &destination, + &wgpu_types::Extent3d { + width: args.copy_size.width.unwrap_or(1), + height: args.copy_size.height.unwrap_or(1), + depth_or_array_layers: args.copy_size.depth_or_array_layers.unwrap_or(1), + } + )) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CommandEncoderPushDebugGroupArgs { + command_encoder_rid: ResourceId, + group_label: String, +} + +pub fn op_webgpu_command_encoder_push_debug_group( + state: &mut OpState, + args: CommandEncoderPushDebugGroupArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let instance = state.borrow::<super::Instance>(); + let command_encoder_resource = state + .resource_table + .get::<WebGpuCommandEncoder>(args.command_encoder_rid) + .ok_or_else(bad_resource_id)?; + let command_encoder = command_encoder_resource.0; + + gfx_ok!(command_encoder => instance + .command_encoder_push_debug_group(command_encoder, &args.group_label)) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CommandEncoderPopDebugGroupArgs { + command_encoder_rid: ResourceId, +} + +pub fn op_webgpu_command_encoder_pop_debug_group( + state: &mut OpState, + args: CommandEncoderPopDebugGroupArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let instance = state.borrow::<super::Instance>(); + let command_encoder_resource = state + .resource_table + .get::<WebGpuCommandEncoder>(args.command_encoder_rid) + .ok_or_else(bad_resource_id)?; + let command_encoder = command_encoder_resource.0; + + gfx_ok!(command_encoder => instance.command_encoder_pop_debug_group(command_encoder)) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CommandEncoderInsertDebugMarkerArgs { + command_encoder_rid: ResourceId, + marker_label: String, +} + +pub fn op_webgpu_command_encoder_insert_debug_marker( + state: &mut OpState, + args: CommandEncoderInsertDebugMarkerArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let instance = state.borrow::<super::Instance>(); + let command_encoder_resource = state + .resource_table + .get::<WebGpuCommandEncoder>(args.command_encoder_rid) + .ok_or_else(bad_resource_id)?; + let command_encoder = command_encoder_resource.0; + + gfx_ok!(command_encoder => instance.command_encoder_insert_debug_marker( + command_encoder, + &args.marker_label + )) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CommandEncoderWriteTimestampArgs { + command_encoder_rid: ResourceId, + query_set: u32, + query_index: u32, +} + +pub fn op_webgpu_command_encoder_write_timestamp( + state: &mut OpState, + args: CommandEncoderWriteTimestampArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let instance = state.borrow::<super::Instance>(); + let command_encoder_resource = state + .resource_table + .get::<WebGpuCommandEncoder>(args.command_encoder_rid) + .ok_or_else(bad_resource_id)?; + let command_encoder = command_encoder_resource.0; + let query_set_resource = state + .resource_table + .get::<super::WebGpuQuerySet>(args.query_set) + .ok_or_else(bad_resource_id)?; + + gfx_ok!(command_encoder => instance.command_encoder_write_timestamp( + command_encoder, + query_set_resource.0, + args.query_index + )) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CommandEncoderResolveQuerySetArgs { + command_encoder_rid: ResourceId, + query_set: u32, + first_query: u32, + query_count: u32, + destination: u32, + destination_offset: u64, +} + +pub fn op_webgpu_command_encoder_resolve_query_set( + state: &mut OpState, + args: CommandEncoderResolveQuerySetArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let instance = state.borrow::<super::Instance>(); + let command_encoder_resource = state + .resource_table + .get::<WebGpuCommandEncoder>(args.command_encoder_rid) + .ok_or_else(bad_resource_id)?; + let command_encoder = command_encoder_resource.0; + let query_set_resource = state + .resource_table + .get::<super::WebGpuQuerySet>(args.query_set) + .ok_or_else(bad_resource_id)?; + let destination_resource = state + .resource_table + .get::<super::buffer::WebGpuBuffer>(args.destination) + .ok_or_else(bad_resource_id)?; + + gfx_ok!(command_encoder => instance.command_encoder_resolve_query_set( + command_encoder, + query_set_resource.0, + args.first_query, + args.query_count, + destination_resource.0, + args.destination_offset + )) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CommandEncoderFinishArgs { + command_encoder_rid: ResourceId, + label: Option<String>, +} + +pub fn op_webgpu_command_encoder_finish( + state: &mut OpState, + args: CommandEncoderFinishArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let command_encoder_resource = state + .resource_table + .take::<WebGpuCommandEncoder>(args.command_encoder_rid) + .ok_or_else(bad_resource_id)?; + let command_encoder = command_encoder_resource.0; + let instance = state.borrow::<super::Instance>(); + + let descriptor = wgpu_types::CommandBufferDescriptor { + label: args.label.map(Cow::from), + }; + + gfx_put!(command_encoder => instance.command_encoder_finish( + command_encoder, + &descriptor + ) => state, WebGpuCommandBuffer) +} diff --git a/ext/webgpu/compute_pass.rs b/ext/webgpu/compute_pass.rs new file mode 100644 index 000000000..6971dbe5c --- /dev/null +++ b/ext/webgpu/compute_pass.rs @@ -0,0 +1,356 @@ +// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. + +use deno_core::error::bad_resource_id; +use deno_core::error::null_opbuf; +use deno_core::error::AnyError; +use deno_core::ResourceId; +use deno_core::ZeroCopyBuf; +use deno_core::{OpState, Resource}; +use serde::Deserialize; +use std::borrow::Cow; +use std::cell::RefCell; + +use super::error::WebGpuResult; + +pub(crate) struct WebGpuComputePass( + pub(crate) RefCell<wgpu_core::command::ComputePass>, +); +impl Resource for WebGpuComputePass { + fn name(&self) -> Cow<str> { + "webGPUComputePass".into() + } +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ComputePassSetPipelineArgs { + compute_pass_rid: ResourceId, + pipeline: u32, +} + +pub fn op_webgpu_compute_pass_set_pipeline( + state: &mut OpState, + args: ComputePassSetPipelineArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let compute_pipeline_resource = state + .resource_table + .get::<super::pipeline::WebGpuComputePipeline>(args.pipeline) + .ok_or_else(bad_resource_id)?; + let compute_pass_resource = state + .resource_table + .get::<WebGpuComputePass>(args.compute_pass_rid) + .ok_or_else(bad_resource_id)?; + + wgpu_core::command::compute_ffi::wgpu_compute_pass_set_pipeline( + &mut compute_pass_resource.0.borrow_mut(), + compute_pipeline_resource.0, + ); + + Ok(WebGpuResult::empty()) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ComputePassDispatchArgs { + compute_pass_rid: ResourceId, + x: u32, + y: u32, + z: u32, +} + +pub fn op_webgpu_compute_pass_dispatch( + state: &mut OpState, + args: ComputePassDispatchArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let compute_pass_resource = state + .resource_table + .get::<WebGpuComputePass>(args.compute_pass_rid) + .ok_or_else(bad_resource_id)?; + + wgpu_core::command::compute_ffi::wgpu_compute_pass_dispatch( + &mut compute_pass_resource.0.borrow_mut(), + args.x, + args.y, + args.z, + ); + + Ok(WebGpuResult::empty()) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ComputePassDispatchIndirectArgs { + compute_pass_rid: ResourceId, + indirect_buffer: u32, + indirect_offset: u64, +} + +pub fn op_webgpu_compute_pass_dispatch_indirect( + state: &mut OpState, + args: ComputePassDispatchIndirectArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let buffer_resource = state + .resource_table + .get::<super::buffer::WebGpuBuffer>(args.indirect_buffer) + .ok_or_else(bad_resource_id)?; + let compute_pass_resource = state + .resource_table + .get::<WebGpuComputePass>(args.compute_pass_rid) + .ok_or_else(bad_resource_id)?; + + wgpu_core::command::compute_ffi::wgpu_compute_pass_dispatch_indirect( + &mut compute_pass_resource.0.borrow_mut(), + buffer_resource.0, + args.indirect_offset, + ); + + Ok(WebGpuResult::empty()) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ComputePassBeginPipelineStatisticsQueryArgs { + compute_pass_rid: ResourceId, + query_set: u32, + query_index: u32, +} + +pub fn op_webgpu_compute_pass_begin_pipeline_statistics_query( + state: &mut OpState, + args: ComputePassBeginPipelineStatisticsQueryArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let compute_pass_resource = state + .resource_table + .get::<WebGpuComputePass>(args.compute_pass_rid) + .ok_or_else(bad_resource_id)?; + let query_set_resource = state + .resource_table + .get::<super::WebGpuQuerySet>(args.query_set) + .ok_or_else(bad_resource_id)?; + + wgpu_core::command::compute_ffi::wgpu_compute_pass_begin_pipeline_statistics_query( + &mut compute_pass_resource.0.borrow_mut(), + query_set_resource.0, + args.query_index, + ); + + Ok(WebGpuResult::empty()) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ComputePassEndPipelineStatisticsQueryArgs { + compute_pass_rid: ResourceId, +} + +pub fn op_webgpu_compute_pass_end_pipeline_statistics_query( + state: &mut OpState, + args: ComputePassEndPipelineStatisticsQueryArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let compute_pass_resource = state + .resource_table + .get::<WebGpuComputePass>(args.compute_pass_rid) + .ok_or_else(bad_resource_id)?; + + wgpu_core::command::compute_ffi::wgpu_compute_pass_end_pipeline_statistics_query( + &mut compute_pass_resource.0.borrow_mut(), + ); + + Ok(WebGpuResult::empty()) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ComputePassWriteTimestampArgs { + compute_pass_rid: ResourceId, + query_set: u32, + query_index: u32, +} + +pub fn op_webgpu_compute_pass_write_timestamp( + state: &mut OpState, + args: ComputePassWriteTimestampArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let compute_pass_resource = state + .resource_table + .get::<WebGpuComputePass>(args.compute_pass_rid) + .ok_or_else(bad_resource_id)?; + let query_set_resource = state + .resource_table + .get::<super::WebGpuQuerySet>(args.query_set) + .ok_or_else(bad_resource_id)?; + + wgpu_core::command::compute_ffi::wgpu_compute_pass_write_timestamp( + &mut compute_pass_resource.0.borrow_mut(), + query_set_resource.0, + args.query_index, + ); + + Ok(WebGpuResult::empty()) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ComputePassEndPassArgs { + command_encoder_rid: ResourceId, + compute_pass_rid: ResourceId, +} + +pub fn op_webgpu_compute_pass_end_pass( + state: &mut OpState, + args: ComputePassEndPassArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let command_encoder_resource = state + .resource_table + .get::<super::command_encoder::WebGpuCommandEncoder>( + args.command_encoder_rid, + ) + .ok_or_else(bad_resource_id)?; + let command_encoder = command_encoder_resource.0; + let compute_pass_resource = state + .resource_table + .take::<WebGpuComputePass>(args.compute_pass_rid) + .ok_or_else(bad_resource_id)?; + let compute_pass = &compute_pass_resource.0.borrow(); + let instance = state.borrow::<super::Instance>(); + + gfx_ok!(command_encoder => instance.command_encoder_run_compute_pass( + command_encoder, + compute_pass + )) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ComputePassSetBindGroupArgs { + compute_pass_rid: ResourceId, + index: u32, + bind_group: u32, + dynamic_offsets_data: Option<Vec<u32>>, + dynamic_offsets_data_start: usize, + dynamic_offsets_data_length: usize, +} + +pub fn op_webgpu_compute_pass_set_bind_group( + state: &mut OpState, + args: ComputePassSetBindGroupArgs, + zero_copy: Option<ZeroCopyBuf>, +) -> Result<WebGpuResult, AnyError> { + let bind_group_resource = state + .resource_table + .get::<super::binding::WebGpuBindGroup>(args.bind_group) + .ok_or_else(bad_resource_id)?; + let compute_pass_resource = state + .resource_table + .get::<WebGpuComputePass>(args.compute_pass_rid) + .ok_or_else(bad_resource_id)?; + + unsafe { + wgpu_core::command::compute_ffi::wgpu_compute_pass_set_bind_group( + &mut compute_pass_resource.0.borrow_mut(), + args.index, + bind_group_resource.0, + match args.dynamic_offsets_data { + Some(data) => data.as_ptr(), + None => { + let zero_copy = zero_copy.ok_or_else(null_opbuf)?; + let (prefix, data, suffix) = zero_copy.align_to::<u32>(); + assert!(prefix.is_empty()); + assert!(suffix.is_empty()); + data[args.dynamic_offsets_data_start..].as_ptr() + } + }, + args.dynamic_offsets_data_length, + ); + } + + Ok(WebGpuResult::empty()) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ComputePassPushDebugGroupArgs { + compute_pass_rid: ResourceId, + group_label: String, +} + +pub fn op_webgpu_compute_pass_push_debug_group( + state: &mut OpState, + args: ComputePassPushDebugGroupArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let compute_pass_resource = state + .resource_table + .get::<WebGpuComputePass>(args.compute_pass_rid) + .ok_or_else(bad_resource_id)?; + + unsafe { + let label = std::ffi::CString::new(args.group_label).unwrap(); + wgpu_core::command::compute_ffi::wgpu_compute_pass_push_debug_group( + &mut compute_pass_resource.0.borrow_mut(), + label.as_ptr(), + 0, // wgpu#975 + ); + } + + Ok(WebGpuResult::empty()) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ComputePassPopDebugGroupArgs { + compute_pass_rid: ResourceId, +} + +pub fn op_webgpu_compute_pass_pop_debug_group( + state: &mut OpState, + args: ComputePassPopDebugGroupArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let compute_pass_resource = state + .resource_table + .get::<WebGpuComputePass>(args.compute_pass_rid) + .ok_or_else(bad_resource_id)?; + + wgpu_core::command::compute_ffi::wgpu_compute_pass_pop_debug_group( + &mut compute_pass_resource.0.borrow_mut(), + ); + + Ok(WebGpuResult::empty()) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ComputePassInsertDebugMarkerArgs { + compute_pass_rid: ResourceId, + marker_label: String, +} + +pub fn op_webgpu_compute_pass_insert_debug_marker( + state: &mut OpState, + args: ComputePassInsertDebugMarkerArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let compute_pass_resource = state + .resource_table + .get::<WebGpuComputePass>(args.compute_pass_rid) + .ok_or_else(bad_resource_id)?; + + unsafe { + let label = std::ffi::CString::new(args.marker_label).unwrap(); + wgpu_core::command::compute_ffi::wgpu_compute_pass_insert_debug_marker( + &mut compute_pass_resource.0.borrow_mut(), + label.as_ptr(), + 0, // wgpu#975 + ); + } + + Ok(WebGpuResult::empty()) +} diff --git a/ext/webgpu/error.rs b/ext/webgpu/error.rs new file mode 100644 index 000000000..57e2e675f --- /dev/null +++ b/ext/webgpu/error.rs @@ -0,0 +1,294 @@ +// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. +use deno_core::error::AnyError; +use deno_core::ResourceId; +use serde::Serialize; +use std::convert::From; +use std::fmt; +use wgpu_core::binding_model::CreateBindGroupError; +use wgpu_core::binding_model::CreateBindGroupLayoutError; +use wgpu_core::binding_model::CreatePipelineLayoutError; +use wgpu_core::binding_model::GetBindGroupLayoutError; +use wgpu_core::command::CommandAllocatorError; +use wgpu_core::command::CommandEncoderError; +use wgpu_core::command::ComputePassError; +use wgpu_core::command::CopyError; +use wgpu_core::command::CreateRenderBundleError; +use wgpu_core::command::QueryError; +use wgpu_core::command::RenderBundleError; +use wgpu_core::command::RenderPassError; +use wgpu_core::device::queue::QueueSubmitError; +use wgpu_core::device::queue::QueueWriteError; +use wgpu_core::device::DeviceError; +use wgpu_core::pipeline::CreateComputePipelineError; +use wgpu_core::pipeline::CreateRenderPipelineError; +use wgpu_core::pipeline::CreateShaderModuleError; +use wgpu_core::resource::BufferAccessError; +use wgpu_core::resource::CreateBufferError; +use wgpu_core::resource::CreateQuerySetError; +use wgpu_core::resource::CreateSamplerError; +use wgpu_core::resource::CreateTextureError; +use wgpu_core::resource::CreateTextureViewError; + +#[derive(Serialize)] +pub struct WebGpuResult { + pub rid: Option<ResourceId>, + pub err: Option<WebGpuError>, +} + +impl WebGpuResult { + pub fn rid(rid: ResourceId) -> Self { + Self { + rid: Some(rid), + err: None, + } + } + + pub fn rid_err<T: Into<WebGpuError>>( + rid: ResourceId, + err: Option<T>, + ) -> Self { + Self { + rid: Some(rid), + err: err.map(|e| e.into()), + } + } + + pub fn maybe_err<T: Into<WebGpuError>>(err: Option<T>) -> Self { + Self { + rid: None, + err: err.map(|e| e.into()), + } + } + + pub fn empty() -> Self { + Self { + rid: None, + err: None, + } + } +} + +#[derive(Serialize)] +#[serde(tag = "type", content = "value")] +#[serde(rename_all = "kebab-case")] +pub enum WebGpuError { + Lost, + OutOfMemory, + Validation(String), +} + +impl From<CreateBufferError> for WebGpuError { + fn from(err: CreateBufferError) -> Self { + match err { + CreateBufferError::Device(err) => err.into(), + CreateBufferError::AccessError(err) => err.into(), + err => WebGpuError::Validation(err.to_string()), + } + } +} + +impl From<DeviceError> for WebGpuError { + fn from(err: DeviceError) -> Self { + match err { + DeviceError::Lost => WebGpuError::Lost, + DeviceError::OutOfMemory => WebGpuError::OutOfMemory, + DeviceError::Invalid => WebGpuError::Validation(err.to_string()), + } + } +} + +impl From<BufferAccessError> for WebGpuError { + fn from(err: BufferAccessError) -> Self { + match err { + BufferAccessError::Device(err) => err.into(), + err => WebGpuError::Validation(err.to_string()), + } + } +} + +impl From<CreateBindGroupLayoutError> for WebGpuError { + fn from(err: CreateBindGroupLayoutError) -> Self { + match err { + CreateBindGroupLayoutError::Device(err) => err.into(), + err => WebGpuError::Validation(err.to_string()), + } + } +} + +impl From<CreatePipelineLayoutError> for WebGpuError { + fn from(err: CreatePipelineLayoutError) -> Self { + match err { + CreatePipelineLayoutError::Device(err) => err.into(), + err => WebGpuError::Validation(err.to_string()), + } + } +} + +impl From<CreateBindGroupError> for WebGpuError { + fn from(err: CreateBindGroupError) -> Self { + match err { + CreateBindGroupError::Device(err) => err.into(), + err => WebGpuError::Validation(err.to_string()), + } + } +} + +impl From<RenderBundleError> for WebGpuError { + fn from(err: RenderBundleError) -> Self { + WebGpuError::Validation(err.to_string()) + } +} + +impl From<CreateRenderBundleError> for WebGpuError { + fn from(err: CreateRenderBundleError) -> Self { + WebGpuError::Validation(err.to_string()) + } +} + +impl From<CommandAllocatorError> for WebGpuError { + fn from(err: CommandAllocatorError) -> Self { + match err { + CommandAllocatorError::Device(err) => err.into(), + } + } +} + +impl From<CopyError> for WebGpuError { + fn from(err: CopyError) -> Self { + WebGpuError::Validation(err.to_string()) + } +} + +impl From<CommandEncoderError> for WebGpuError { + fn from(err: CommandEncoderError) -> Self { + WebGpuError::Validation(err.to_string()) + } +} + +impl From<QueryError> for WebGpuError { + fn from(err: QueryError) -> Self { + WebGpuError::Validation(err.to_string()) + } +} + +impl From<ComputePassError> for WebGpuError { + fn from(err: ComputePassError) -> Self { + WebGpuError::Validation(err.to_string()) + } +} + +impl From<CreateComputePipelineError> for WebGpuError { + fn from(err: CreateComputePipelineError) -> Self { + match err { + CreateComputePipelineError::Device(err) => err.into(), + err => WebGpuError::Validation(err.to_string()), + } + } +} + +impl From<GetBindGroupLayoutError> for WebGpuError { + fn from(err: GetBindGroupLayoutError) -> Self { + WebGpuError::Validation(err.to_string()) + } +} + +impl From<CreateRenderPipelineError> for WebGpuError { + fn from(err: CreateRenderPipelineError) -> Self { + match err { + CreateRenderPipelineError::Device(err) => err.into(), + err => WebGpuError::Validation(err.to_string()), + } + } +} + +impl From<RenderPassError> for WebGpuError { + fn from(err: RenderPassError) -> Self { + WebGpuError::Validation(err.to_string()) + } +} + +impl From<CreateSamplerError> for WebGpuError { + fn from(err: CreateSamplerError) -> Self { + match err { + CreateSamplerError::Device(err) => err.into(), + err => WebGpuError::Validation(err.to_string()), + } + } +} + +impl From<CreateShaderModuleError> for WebGpuError { + fn from(err: CreateShaderModuleError) -> Self { + match err { + CreateShaderModuleError::Device(err) => err.into(), + err => WebGpuError::Validation(err.to_string()), + } + } +} + +impl From<CreateTextureError> for WebGpuError { + fn from(err: CreateTextureError) -> Self { + match err { + CreateTextureError::Device(err) => err.into(), + err => WebGpuError::Validation(err.to_string()), + } + } +} + +impl From<CreateTextureViewError> for WebGpuError { + fn from(err: CreateTextureViewError) -> Self { + WebGpuError::Validation(err.to_string()) + } +} + +impl From<CreateQuerySetError> for WebGpuError { + fn from(err: CreateQuerySetError) -> Self { + match err { + CreateQuerySetError::Device(err) => err.into(), + err => WebGpuError::Validation(err.to_string()), + } + } +} + +impl From<QueueSubmitError> for WebGpuError { + fn from(err: QueueSubmitError) -> Self { + match err { + QueueSubmitError::Queue(err) => err.into(), + err => WebGpuError::Validation(err.to_string()), + } + } +} + +impl From<QueueWriteError> for WebGpuError { + fn from(err: QueueWriteError) -> Self { + match err { + QueueWriteError::Queue(err) => err.into(), + err => WebGpuError::Validation(err.to_string()), + } + } +} + +#[derive(Debug)] +pub struct DomExceptionOperationError { + pub msg: String, +} + +impl DomExceptionOperationError { + pub fn new(msg: &str) -> Self { + DomExceptionOperationError { + msg: msg.to_string(), + } + } +} + +impl fmt::Display for DomExceptionOperationError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.pad(&self.msg) + } +} + +impl std::error::Error for DomExceptionOperationError {} + +pub fn get_error_class_name(e: &AnyError) -> Option<&'static str> { + e.downcast_ref::<DomExceptionOperationError>() + .map(|_| "DOMExceptionOperationError") +} diff --git a/ext/webgpu/lib.deno_webgpu.d.ts b/ext/webgpu/lib.deno_webgpu.d.ts new file mode 100644 index 000000000..9f7a31cb7 --- /dev/null +++ b/ext/webgpu/lib.deno_webgpu.d.ts @@ -0,0 +1,1131 @@ +// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. + +// deno-lint-ignore-file no-explicit-any no-empty-interface + +/// <reference no-default-lib="true" /> +/// <reference lib="esnext" /> + +// 8cc98b6f10b7f354473a08c3773bb1de839845b9 + +interface GPUObjectBase { + label: string | null; +} + +declare interface GPUObjectDescriptorBase { + label?: string; +} + +declare class GPUSupportedLimits { + maxTextureDimension1D?: number; + maxTextureDimension2D?: number; + maxTextureDimension3D?: number; + maxTextureArrayLayers?: number; + maxBindGroups?: number; + maxDynamicUniformBuffersPerPipelineLayout?: number; + maxDynamicStorageBuffersPerPipelineLayout?: number; + maxSampledTexturesPerShaderStage?: number; + maxSamplersPerShaderStage?: number; + maxStorageBuffersPerShaderStage?: number; + maxStorageTexturesPerShaderStage?: number; + maxUniformBuffersPerShaderStage?: number; + maxUniformBufferBindingSize?: number; + maxStorageBufferBindingSize?: number; + minUniformBufferOffsetAlignment?: number; + minStorageBufferOffsetAlignment?: number; + maxVertexBuffers?: number; + maxVertexAttributes?: number; + maxVertexBufferArrayStride?: number; + maxInterStageShaderComponents?: number; + maxComputeWorkgroupStorageSize?: number; + maxComputeWorkgroupInvocations?: number; + maxComputePerDimensionDispatchSize?: number; +} + +declare class GPUSupportedFeatures { + forEach( + callbackfn: ( + value: GPUFeatureName, + value2: GPUFeatureName, + set: Set<GPUFeatureName>, + ) => void, + thisArg?: any, + ): void; + has(value: GPUFeatureName): boolean; + size: number; + [ + Symbol + .iterator + ](): IterableIterator<GPUFeatureName>; + entries(): IterableIterator<[GPUFeatureName, GPUFeatureName]>; + keys(): IterableIterator<GPUFeatureName>; + values(): IterableIterator<GPUFeatureName>; +} + +declare class GPU { + requestAdapter( + options?: GPURequestAdapterOptions, + ): Promise<GPUAdapter | null>; +} + +declare interface GPURequestAdapterOptions { + powerPreference?: GPUPowerPreference; + forceSoftware?: boolean; +} + +declare type GPUPowerPreference = "low-power" | "high-performance"; + +declare class GPUAdapter { + readonly name: string; + readonly features: GPUSupportedFeatures; + readonly limits: GPUSupportedLimits; + readonly isSoftware: boolean; + + requestDevice(descriptor?: GPUDeviceDescriptor): Promise<GPUDevice>; +} + +declare interface GPUDeviceDescriptor extends GPUObjectDescriptorBase { + requiredFeatures?: GPUFeatureName[]; + requiredLimits?: Record<string, number>; +} + +declare type GPUFeatureName = + | "depth-clamping" + | "depth24unorm-stencil8" + | "depth32float-stencil8" + | "pipeline-statistics-query" + | "texture-compression-bc" + | "timestamp-query" + // extended from spec + | "mappable-primary-buffers" + | "sampled-texture-binding-array" + | "sampled-texture-array-dynamic-indexing" + | "sampled-texture-array-non-uniform-indexing" + | "unsized-binding-array" + | "multi-draw-indirect" + | "multi-draw-indirect-count" + | "push-constants" + | "address-mode-clamp-to-border" + | "non-fill-polygon-mode" + | "texture-compression-etc2" + | "texture-compression-astc-ldr" + | "texture-adapter-specific-format-features" + | "shader-float64" + | "vertex-attribute-64bit"; + +declare class GPUDevice extends EventTarget implements GPUObjectBase { + label: string | null; + + readonly lost: Promise<GPUDeviceLostInfo>; + pushErrorScope(filter: GPUErrorFilter): undefined; + popErrorScope(): Promise<GPUError | null>; + onuncapturederror: + | ((this: GPUDevice, ev: GPUUncapturedErrorEvent) => any) + | null; + + readonly features: ReadonlyArray<GPUFeatureName>; + readonly limits: Record<string, number>; + readonly queue: GPUQueue; + + destroy(): undefined; + + createBuffer(descriptor: GPUBufferDescriptor): GPUBuffer; + createTexture(descriptor: GPUTextureDescriptor): GPUTexture; + createSampler(descriptor?: GPUSamplerDescriptor): GPUSampler; + + createBindGroupLayout( + descriptor: GPUBindGroupLayoutDescriptor, + ): GPUBindGroupLayout; + createPipelineLayout( + descriptor: GPUPipelineLayoutDescriptor, + ): GPUPipelineLayout; + createBindGroup(descriptor: GPUBindGroupDescriptor): GPUBindGroup; + + createShaderModule(descriptor: GPUShaderModuleDescriptor): GPUShaderModule; + createComputePipeline( + descriptor: GPUComputePipelineDescriptor, + ): GPUComputePipeline; + createRenderPipeline( + descriptor: GPURenderPipelineDescriptor, + ): GPURenderPipeline; + createComputePipelineAsync( + descriptor: GPUComputePipelineDescriptor, + ): Promise<GPUComputePipeline>; + createRenderPipelineAsync( + descriptor: GPURenderPipelineDescriptor, + ): Promise<GPURenderPipeline>; + + createCommandEncoder( + descriptor?: GPUCommandEncoderDescriptor, + ): GPUCommandEncoder; + createRenderBundleEncoder( + descriptor: GPURenderBundleEncoderDescriptor, + ): GPURenderBundleEncoder; + + createQuerySet(descriptor: GPUQuerySetDescriptor): GPUQuerySet; +} + +declare class GPUBuffer implements GPUObjectBase { + label: string | null; + + mapAsync( + mode: GPUMapModeFlags, + offset?: number, + size?: number, + ): Promise<undefined>; + getMappedRange(offset?: number, size?: number): ArrayBuffer; + unmap(): undefined; + + destroy(): undefined; +} + +declare interface GPUBufferDescriptor extends GPUObjectDescriptorBase { + size: number; + usage: GPUBufferUsageFlags; + mappedAtCreation?: boolean; +} + +declare type GPUBufferUsageFlags = number; +declare class GPUBufferUsage { + static MAP_READ: 0x0001; + static MAP_WRITE: 0x0002; + static COPY_SRC: 0x0004; + static COPY_DST: 0x0008; + static INDEX: 0x0010; + static VERTEX: 0x0020; + static UNIFORM: 0x0040; + static STORAGE: 0x0080; + static INDIRECT: 0x0100; + static QUERY_RESOLVE: 0x0200; +} + +declare type GPUMapModeFlags = number; +declare class GPUMapMode { + static READ: 0x0001; + static WRITE: 0x0002; +} + +declare class GPUTexture implements GPUObjectBase { + label: string | null; + + createView(descriptor?: GPUTextureViewDescriptor): GPUTextureView; + destroy(): undefined; +} + +declare interface GPUTextureDescriptor extends GPUObjectDescriptorBase { + size: GPUExtent3D; + mipLevelCount?: number; + sampleCount?: number; + dimension?: GPUTextureDimension; + format: GPUTextureFormat; + usage: GPUTextureUsageFlags; +} + +declare type GPUTextureDimension = "1d" | "2d" | "3d"; + +declare type GPUTextureUsageFlags = number; +declare class GPUTextureUsage { + static COPY_SRC: 0x01; + static COPY_DST: 0x02; + static SAMPLED: 0x04; + static STORAGE: 0x08; + static RENDER_ATTACHMENT: 0x10; +} + +declare class GPUTextureView implements GPUObjectBase { + label: string | null; +} + +declare interface GPUTextureViewDescriptor extends GPUObjectDescriptorBase { + format?: GPUTextureFormat; + dimension?: GPUTextureViewDimension; + aspect?: GPUTextureAspect; + baseMipLevel?: number; + mipLevelCount?: number; + baseArrayLayer?: number; + arrayLayerCount?: number; +} + +declare type GPUTextureViewDimension = + | "1d" + | "2d" + | "2d-array" + | "cube" + | "cube-array" + | "3d"; + +declare type GPUTextureAspect = "all" | "stencil-only" | "depth-only"; + +declare type GPUTextureFormat = + | "r8unorm" + | "r8snorm" + | "r8uint" + | "r8sint" + | "r16uint" + | "r16sint" + | "r16float" + | "rg8unorm" + | "rg8snorm" + | "rg8uint" + | "rg8sint" + | "r32uint" + | "r32sint" + | "r32float" + | "rg16uint" + | "rg16sint" + | "rg16float" + | "rgba8unorm" + | "rgba8unorm-srgb" + | "rgba8snorm" + | "rgba8uint" + | "rgba8sint" + | "bgra8unorm" + | "bgra8unorm-srgb" + | "rgb9e5ufloat" + | "rgb10a2unorm" + | "rg11b10ufloat" + | "rg32uint" + | "rg32sint" + | "rg32float" + | "rgba16uint" + | "rgba16sint" + | "rgba16float" + | "rgba32uint" + | "rgba32sint" + | "rgba32float" + | "stencil8" + | "depth16unorm" + | "depth24plus" + | "depth24plus-stencil8" + | "depth32float" + | "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" + | "depth24unorm-stencil8" + | "depth32float-stencil8"; + +declare class GPUSampler implements GPUObjectBase { + label: string | null; +} + +declare interface GPUSamplerDescriptor extends GPUObjectDescriptorBase { + addressModeU?: GPUAddressMode; + addressModeV?: GPUAddressMode; + addressModeW?: GPUAddressMode; + magFilter?: GPUFilterMode; + minFilter?: GPUFilterMode; + mipmapFilter?: GPUFilterMode; + lodMinClamp?: number; + lodMaxClamp?: number; + compare?: GPUCompareFunction; + maxAnisotropy?: number; +} + +declare type GPUAddressMode = "clamp-to-edge" | "repeat" | "mirror-repeat"; + +declare type GPUFilterMode = "nearest" | "linear"; + +declare type GPUCompareFunction = + | "never" + | "less" + | "equal" + | "less-equal" + | "greater" + | "not-equal" + | "greater-equal" + | "always"; + +declare class GPUBindGroupLayout implements GPUObjectBase { + label: string | null; +} + +declare interface GPUBindGroupLayoutDescriptor extends GPUObjectDescriptorBase { + entries: GPUBindGroupLayoutEntry[]; +} + +declare interface GPUBindGroupLayoutEntry { + binding: number; + visibility: GPUShaderStageFlags; + + buffer?: GPUBufferBindingLayout; + sampler?: GPUSamplerBindingLayout; + texture?: GPUTextureBindingLayout; + storageTexture?: GPUStorageTextureBindingLayout; +} + +declare type GPUShaderStageFlags = number; +declare class GPUShaderStage { + static VERTEX: 0x1; + static FRAGMENT: 0x2; + static COMPUTE: 0x4; +} + +declare interface GPUBufferBindingLayout { + type?: GPUBufferBindingType; + hasDynamicOffset?: boolean; + minBindingSize?: number; +} + +declare type GPUBufferBindingType = "uniform" | "storage" | "read-only-storage"; + +declare interface GPUSamplerBindingLayout { + type?: GPUSamplerBindingType; +} + +declare type GPUSamplerBindingType = + | "filtering" + | "non-filtering" + | "comparison"; + +declare interface GPUTextureBindingLayout { + sampleType?: GPUTextureSampleType; + viewDimension?: GPUTextureViewDimension; + multisampled?: boolean; +} + +declare type GPUTextureSampleType = + | "float" + | "unfilterable-float" + | "depth" + | "sint" + | "uint"; + +declare interface GPUTextureBindingLayout { + sampleType?: GPUTextureSampleType; + viewDimension?: GPUTextureViewDimension; + multisampled?: boolean; +} + +declare type GPUStorageTextureAccess = "read-only" | "write-only"; + +declare interface GPUStorageTextureBindingLayout { + access: GPUStorageTextureAccess; + format: GPUTextureFormat; + viewDimension?: GPUTextureViewDimension; +} + +declare class GPUBindGroup implements GPUObjectBase { + label: string | null; +} + +declare interface GPUBindGroupDescriptor extends GPUObjectDescriptorBase { + layout: GPUBindGroupLayout; + entries: GPUBindGroupEntry[]; +} + +declare type GPUBindingResource = + | GPUSampler + | GPUTextureView + | GPUBufferBinding; + +declare interface GPUBindGroupEntry { + binding: number; + resource: GPUBindingResource; +} + +declare interface GPUBufferBinding { + buffer: GPUBuffer; + offset?: number; + size?: number; +} + +declare class GPUPipelineLayout implements GPUObjectBase { + label: string | null; +} + +declare interface GPUPipelineLayoutDescriptor extends GPUObjectDescriptorBase { + bindGroupLayouts: GPUBindGroupLayout[]; +} + +declare type GPUCompilationMessageType = "error" | "warning" | "info"; + +declare interface GPUCompilationMessage { + readonly message: string; + readonly type: GPUCompilationMessageType; + readonly lineNum: number; + readonly linePos: number; +} + +declare interface GPUCompilationInfo { + readonly messages: ReadonlyArray<GPUCompilationMessage>; +} + +declare class GPUShaderModule implements GPUObjectBase { + label: string | null; + + compilationInfo(): Promise<GPUCompilationInfo>; +} + +declare interface GPUShaderModuleDescriptor extends GPUObjectDescriptorBase { + code: string | Uint32Array; + sourceMap?: any; +} + +declare interface GPUPipelineDescriptorBase extends GPUObjectDescriptorBase { + layout?: GPUPipelineLayout; +} + +declare interface GPUPipelineBase { + getBindGroupLayout(index: number): GPUBindGroupLayout; +} + +declare interface GPUProgrammableStage { + module: GPUShaderModule; + entryPoint: string; +} + +declare class GPUComputePipeline implements GPUObjectBase, GPUPipelineBase { + label: string | null; + + getBindGroupLayout(index: number): GPUBindGroupLayout; +} + +declare interface GPUComputePipelineDescriptor + extends GPUPipelineDescriptorBase { + compute: GPUProgrammableStage; +} + +declare class GPURenderPipeline implements GPUObjectBase, GPUPipelineBase { + label: string | null; + + getBindGroupLayout(index: number): GPUBindGroupLayout; +} + +declare interface GPURenderPipelineDescriptor + extends GPUPipelineDescriptorBase { + vertex: GPUVertexState; + primitive?: GPUPrimitiveState; + depthStencil?: GPUDepthStencilState; + multisample?: GPUMultisampleState; + fragment?: GPUFragmentState; +} + +declare type GPUPrimitiveTopology = + | "point-list" + | "line-list" + | "line-strip" + | "triangle-list" + | "triangle-strip"; + +declare interface GPUPrimitiveState { + topology?: GPUPrimitiveTopology; + stripIndexFormat?: GPUIndexFormat; + frontFace?: GPUFrontFace; + cullMode?: GPUCullMode; + clampDepth?: boolean; +} + +declare type GPUFrontFace = "ccw" | "cw"; + +declare type GPUCullMode = "none" | "front" | "back"; + +declare interface GPUMultisampleState { + count?: number; + mask?: number; + alphaToCoverageEnabled?: boolean; +} + +declare interface GPUFragmentState extends GPUProgrammableStage { + targets: GPUColorTargetState[]; +} + +declare interface GPUColorTargetState { + format: GPUTextureFormat; + + blend?: GPUBlendState; + writeMask?: GPUColorWriteFlags; +} + +declare interface GPUBlendState { + color: GPUBlendComponent; + alpha: GPUBlendComponent; +} + +declare type GPUColorWriteFlags = number; +declare class GPUColorWrite { + static RED: 0x1; + static GREEN: 0x2; + static BLUE: 0x4; + static ALPHA: 0x8; + static ALL: 0xF; +} + +declare interface GPUBlendComponent { + srcFactor: GPUBlendFactor; + dstFactor: GPUBlendFactor; + operation: GPUBlendOperation; +} + +declare type GPUBlendFactor = + | "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"; + +declare type GPUBlendOperation = + | "add" + | "subtract" + | "reverse-subtract" + | "min" + | "max"; + +declare interface GPUDepthStencilState { + format: GPUTextureFormat; + + depthWriteEnabled?: boolean; + depthCompare?: GPUCompareFunction; + + stencilFront?: GPUStencilFaceState; + stencilBack?: GPUStencilFaceState; + + stencilReadMask?: number; + stencilWriteMask?: number; + + depthBias?: number; + depthBiasSlopeScale?: number; + depthBiasClamp?: number; +} + +declare interface GPUStencilFaceState { + compare?: GPUCompareFunction; + failOp?: GPUStencilOperation; + depthFailOp?: GPUStencilOperation; + passOp?: GPUStencilOperation; +} + +declare type GPUStencilOperation = + | "keep" + | "zero" + | "replace" + | "invert" + | "increment-clamp" + | "decrement-clamp" + | "increment-wrap" + | "decrement-wrap"; + +declare type GPUIndexFormat = "uint16" | "uint32"; + +declare type GPUVertexFormat = + | "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"; +declare type GPUInputStepMode = "vertex" | "instance"; + +declare interface GPUVertexState extends GPUProgrammableStage { + buffers?: (GPUVertexBufferLayout | null)[]; +} + +declare interface GPUVertexBufferLayout { + arrayStride: number; + stepMode?: GPUInputStepMode; + attributes: GPUVertexAttribute[]; +} + +declare interface GPUVertexAttribute { + format: GPUVertexFormat; + offset: number; + + shaderLocation: number; +} + +declare class GPUCommandBuffer implements GPUObjectBase { + label: string | null; + + readonly executionTime: Promise<number>; +} + +declare interface GPUCommandBufferDescriptor extends GPUObjectDescriptorBase {} + +declare class GPUCommandEncoder implements GPUObjectBase { + label: string | null; + + beginRenderPass(descriptor: GPURenderPassDescriptor): GPURenderPassEncoder; + beginComputePass( + descriptor?: GPUComputePassDescriptor, + ): GPUComputePassEncoder; + + copyBufferToBuffer( + source: GPUBuffer, + sourceOffset: number, + destination: GPUBuffer, + destinationOffset: number, + size: number, + ): undefined; + + copyBufferToTexture( + source: GPUImageCopyBuffer, + destination: GPUImageCopyTexture, + copySize: GPUExtent3D, + ): undefined; + + copyTextureToBuffer( + source: GPUImageCopyTexture, + destination: GPUImageCopyBuffer, + copySize: GPUExtent3D, + ): undefined; + + copyTextureToTexture( + source: GPUImageCopyTexture, + destination: GPUImageCopyTexture, + copySize: GPUExtent3D, + ): undefined; + + pushDebugGroup(groupLabel: string): undefined; + popDebugGroup(): undefined; + insertDebugMarker(markerLabel: string): undefined; + + writeTimestamp(querySet: GPUQuerySet, queryIndex: number): undefined; + + resolveQuerySet( + querySet: GPUQuerySet, + firstQuery: number, + queryCount: number, + destination: GPUBuffer, + destinationOffset: number, + ): undefined; + + finish(descriptor?: GPUCommandBufferDescriptor): GPUCommandBuffer; +} + +declare interface GPUCommandEncoderDescriptor extends GPUObjectDescriptorBase { + measureExecutionTime?: boolean; +} + +declare interface GPUImageDataLayout { + offset?: number; + bytesPerRow?: number; + rowsPerImage?: number; +} + +declare interface GPUImageCopyBuffer extends GPUImageDataLayout { + buffer: GPUBuffer; +} + +declare interface GPUImageCopyTexture { + texture: GPUTexture; + mipLevel?: number; + origin?: GPUOrigin3D; + aspect?: GPUTextureAspect; +} + +interface GPUProgrammablePassEncoder { + setBindGroup( + index: number, + bindGroup: GPUBindGroup, + dynamicOffsets?: number[], + ): undefined; + + setBindGroup( + index: number, + bindGroup: GPUBindGroup, + dynamicOffsetsData: Uint32Array, + dynamicOffsetsDataStart: number, + dynamicOffsetsDataLength: number, + ): undefined; + + pushDebugGroup(groupLabel: string): undefined; + popDebugGroup(): undefined; + insertDebugMarker(markerLabel: string): undefined; +} + +declare class GPUComputePassEncoder + implements GPUObjectBase, GPUProgrammablePassEncoder { + label: string | null; + setBindGroup( + index: number, + bindGroup: GPUBindGroup, + dynamicOffsets?: number[], + ): undefined; + setBindGroup( + index: number, + bindGroup: GPUBindGroup, + dynamicOffsetsData: Uint32Array, + dynamicOffsetsDataStart: number, + dynamicOffsetsDataLength: number, + ): undefined; + pushDebugGroup(groupLabel: string): undefined; + popDebugGroup(): undefined; + insertDebugMarker(markerLabel: string): undefined; + setPipeline(pipeline: GPUComputePipeline): undefined; + dispatch(x: number, y?: number, z?: number): undefined; + dispatchIndirect( + indirectBuffer: GPUBuffer, + indirectOffset: number, + ): undefined; + + beginPipelineStatisticsQuery( + querySet: GPUQuerySet, + queryIndex: number, + ): undefined; + endPipelineStatisticsQuery(): undefined; + + writeTimestamp(querySet: GPUQuerySet, queryIndex: number): undefined; + + endPass(): undefined; +} + +declare interface GPUComputePassDescriptor extends GPUObjectDescriptorBase {} + +interface GPURenderEncoderBase { + setPipeline(pipeline: GPURenderPipeline): undefined; + + setIndexBuffer( + buffer: GPUBuffer, + indexFormat: GPUIndexFormat, + offset?: number, + size?: number, + ): undefined; + setVertexBuffer( + slot: number, + buffer: GPUBuffer, + offset?: number, + size?: number, + ): undefined; + + draw( + vertexCount: number, + instanceCount?: number, + firstVertex?: number, + firstInstance?: number, + ): undefined; + drawIndexed( + indexCount: number, + instanceCount?: number, + firstIndex?: number, + baseVertex?: number, + firstInstance?: number, + ): undefined; + + drawIndirect(indirectBuffer: GPUBuffer, indirectOffset: number): undefined; + drawIndexedIndirect( + indirectBuffer: GPUBuffer, + indirectOffset: number, + ): undefined; +} + +declare class GPURenderPassEncoder + implements GPUObjectBase, GPUProgrammablePassEncoder, GPURenderEncoderBase { + label: string | null; + setBindGroup( + index: number, + bindGroup: GPUBindGroup, + dynamicOffsets?: number[], + ): undefined; + setBindGroup( + index: number, + bindGroup: GPUBindGroup, + dynamicOffsetsData: Uint32Array, + dynamicOffsetsDataStart: number, + dynamicOffsetsDataLength: number, + ): undefined; + pushDebugGroup(groupLabel: string): undefined; + popDebugGroup(): undefined; + insertDebugMarker(markerLabel: string): undefined; + setPipeline(pipeline: GPURenderPipeline): undefined; + setIndexBuffer( + buffer: GPUBuffer, + indexFormat: GPUIndexFormat, + offset?: number, + size?: number, + ): undefined; + setVertexBuffer( + slot: number, + buffer: GPUBuffer, + offset?: number, + size?: number, + ): undefined; + draw( + vertexCount: number, + instanceCount?: number, + firstVertex?: number, + firstInstance?: number, + ): undefined; + drawIndexed( + indexCount: number, + instanceCount?: number, + firstIndex?: number, + baseVertex?: number, + firstInstance?: number, + ): undefined; + drawIndirect(indirectBuffer: GPUBuffer, indirectOffset: number): undefined; + drawIndexedIndirect( + indirectBuffer: GPUBuffer, + indirectOffset: number, + ): undefined; + + setViewport( + x: number, + y: number, + width: number, + height: number, + minDepth: number, + maxDepth: number, + ): undefined; + + setScissorRect( + x: number, + y: number, + width: number, + height: number, + ): undefined; + + setBlendConstant(color: GPUColor): undefined; + setStencilReference(reference: number): undefined; + + beginOcclusionQuery(queryIndex: number): undefined; + endOcclusionQuery(): undefined; + + beginPipelineStatisticsQuery( + querySet: GPUQuerySet, + queryIndex: number, + ): undefined; + endPipelineStatisticsQuery(): undefined; + + writeTimestamp(querySet: GPUQuerySet, queryIndex: number): undefined; + + executeBundles(bundles: GPURenderBundle[]): undefined; + endPass(): undefined; +} + +declare interface GPURenderPassDescriptor extends GPUObjectDescriptorBase { + colorAttachments: GPURenderPassColorAttachment[]; + depthStencilAttachment?: GPURenderPassDepthStencilAttachment; + occlusionQuerySet?: GPUQuerySet; +} + +declare interface GPURenderPassColorAttachment { + view: GPUTextureView; + resolveTarget?: GPUTextureView; + + loadValue: GPULoadOp | GPUColor; + storeOp?: GPUStoreOp; +} + +declare interface GPURenderPassDepthStencilAttachment { + view: GPUTextureView; + + depthLoadValue: GPULoadOp | number; + depthStoreOp: GPUStoreOp; + depthReadOnly?: boolean; + + stencilLoadValue: GPULoadOp | number; + stencilStoreOp: GPUStoreOp; + stencilReadOnly?: boolean; +} + +declare type GPULoadOp = "load"; + +declare type GPUStoreOp = "store" | "discard"; + +declare class GPURenderBundle implements GPUObjectBase { + label: string | null; +} + +declare interface GPURenderBundleDescriptor extends GPUObjectDescriptorBase {} + +declare class GPURenderBundleEncoder + implements GPUObjectBase, GPUProgrammablePassEncoder, GPURenderEncoderBase { + label: string | null; + draw( + vertexCount: number, + instanceCount?: number, + firstVertex?: number, + firstInstance?: number, + ): undefined; + drawIndexed( + indexCount: number, + instanceCount?: number, + firstIndex?: number, + baseVertex?: number, + firstInstance?: number, + ): undefined; + drawIndexedIndirect( + indirectBuffer: GPUBuffer, + indirectOffset: number, + ): undefined; + drawIndirect(indirectBuffer: GPUBuffer, indirectOffset: number): undefined; + insertDebugMarker(markerLabel: string): undefined; + popDebugGroup(): undefined; + pushDebugGroup(groupLabel: string): undefined; + setBindGroup( + index: number, + bindGroup: GPUBindGroup, + dynamicOffsets?: number[], + ): undefined; + setBindGroup( + index: number, + bindGroup: GPUBindGroup, + dynamicOffsetsData: Uint32Array, + dynamicOffsetsDataStart: number, + dynamicOffsetsDataLength: number, + ): undefined; + setIndexBuffer( + buffer: GPUBuffer, + indexFormat: GPUIndexFormat, + offset?: number, + size?: number, + ): undefined; + setPipeline(pipeline: GPURenderPipeline): undefined; + setVertexBuffer( + slot: number, + buffer: GPUBuffer, + offset?: number, + size?: number, + ): undefined; + + finish(descriptor?: GPURenderBundleDescriptor): GPURenderBundle; +} + +declare interface GPURenderBundleEncoderDescriptor + extends GPUObjectDescriptorBase { + colorFormats: GPUTextureFormat[]; + depthStencilFormat?: GPUTextureFormat; + sampleCount?: number; +} + +declare class GPUQueue implements GPUObjectBase { + label: string | null; + + submit(commandBuffers: GPUCommandBuffer[]): undefined; + + onSubmittedWorkDone(): Promise<undefined>; + + writeBuffer( + buffer: GPUBuffer, + bufferOffset: number, + data: BufferSource, + dataOffset?: number, + size?: number, + ): undefined; + + writeTexture( + destination: GPUImageCopyTexture, + data: BufferSource, + dataLayout: GPUImageDataLayout, + size: GPUExtent3D, + ): undefined; +} + +declare class GPUQuerySet implements GPUObjectBase { + label: string | null; + + destroy(): undefined; +} + +declare interface GPUQuerySetDescriptor extends GPUObjectDescriptorBase { + type: GPUQueryType; + count: number; + pipelineStatistics?: GPUPipelineStatisticName[]; +} + +declare type GPUQueryType = "occlusion" | "pipeline-statistics" | "timestamp"; + +declare type GPUPipelineStatisticName = + | "vertex-shader-invocations" + | "clipper-invocations" + | "clipper-primitives-out" + | "fragment-shader-invocations" + | "compute-shader-invocations"; + +declare type GPUDeviceLostReason = "destroyed"; + +declare interface GPUDeviceLostInfo { + readonly reason: GPUDeviceLostReason | undefined; + readonly message: string; +} + +declare type GPUErrorFilter = "out-of-memory" | "validation"; + +declare class GPUOutOfMemoryError { + constructor(); +} + +declare class GPUValidationError { + constructor(message: string); + readonly message: string; +} + +declare type GPUError = GPUOutOfMemoryError | GPUValidationError; + +declare class GPUUncapturedErrorEvent extends Event { + constructor( + type: string, + gpuUncapturedErrorEventInitDict: GPUUncapturedErrorEventInit, + ); + readonly error: GPUError; +} + +declare interface GPUUncapturedErrorEventInit extends EventInit { + error?: GPUError; +} + +declare interface GPUColorDict { + r: number; + g: number; + b: number; + a: number; +} + +declare type GPUColor = number[] | GPUColorDict; + +declare interface GPUOrigin3DDict { + x?: number; + y?: number; + z?: number; +} + +declare type GPUOrigin3D = number[] | GPUOrigin3DDict; + +declare interface GPUExtent3DDict { + width: number; + height?: number; + depthOrArrayLayers?: number; +} + +declare type GPUExtent3D = number[] | GPUExtent3DDict; diff --git a/ext/webgpu/lib.rs b/ext/webgpu/lib.rs new file mode 100644 index 000000000..9a39ea4a2 --- /dev/null +++ b/ext/webgpu/lib.rs @@ -0,0 +1,928 @@ +// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. + +use deno_core::error::AnyError; +use deno_core::error::{bad_resource_id, not_supported}; +use deno_core::include_js_files; +use deno_core::op_async; +use deno_core::op_sync; +use deno_core::Extension; +use deno_core::OpFn; +use deno_core::OpState; +use deno_core::Resource; +use deno_core::ResourceId; +use serde::Deserialize; +use serde::Serialize; +use std::borrow::Cow; +use std::cell::RefCell; +use std::path::PathBuf; +use std::rc::Rc; +pub use wgpu_core; +pub use wgpu_types; + +use error::DomExceptionOperationError; +use error::WebGpuResult; + +#[macro_use] +mod macros { + macro_rules! gfx_select { + ($id:expr => $global:ident.$method:ident( $($param:expr),* )) => { + match $id.backend() { + #[cfg(not(target_os = "macos"))] + wgpu_types::Backend::Vulkan => $global.$method::<wgpu_core::backend::Vulkan>( $($param),* ), + #[cfg(target_os = "macos")] + wgpu_types::Backend::Metal => $global.$method::<wgpu_core::backend::Metal>( $($param),* ), + #[cfg(windows)] + wgpu_types::Backend::Dx12 => $global.$method::<wgpu_core::backend::Dx12>( $($param),* ), + #[cfg(windows)] + wgpu_types::Backend::Dx11 => $global.$method::<wgpu_core::backend::Dx11>( $($param),* ), + #[cfg(all(unix, not(target_os = "macos")))] + wgpu_types::Backend::Gl => $global.$method::<wgpu_core::backend::Gl>( $($param),+ ), + other => panic!("Unexpected backend {:?}", other), + } + }; + } + + macro_rules! gfx_put { + ($id:expr => $global:ident.$method:ident( $($param:expr),* ) => $state:expr, $rc:expr) => {{ + let (val, maybe_err) = gfx_select!($id => $global.$method($($param),*)); + let rid = $state.resource_table.add($rc(val)); + Ok(WebGpuResult::rid_err(rid, maybe_err)) + }}; + } + + macro_rules! gfx_ok { + ($id:expr => $global:ident.$method:ident( $($param:expr),* )) => {{ + let maybe_err = gfx_select!($id => $global.$method($($param),*)).err(); + Ok(WebGpuResult::maybe_err(maybe_err)) + }}; + } +} + +pub mod binding; +pub mod buffer; +pub mod bundle; +pub mod command_encoder; +pub mod compute_pass; +pub mod error; +pub mod pipeline; +pub mod queue; +pub mod render_pass; +pub mod sampler; +pub mod shader; +pub mod texture; + +pub struct Unstable(pub bool); + +fn check_unstable(state: &OpState, api_name: &str) { + let unstable = state.borrow::<Unstable>(); + + if !unstable.0 { + eprintln!( + "Unstable API '{}'. The --unstable flag must be provided.", + api_name + ); + std::process::exit(70); + } +} + +type Instance = wgpu_core::hub::Global<wgpu_core::hub::IdentityManagerFactory>; + +struct WebGpuAdapter(wgpu_core::id::AdapterId); +impl Resource for WebGpuAdapter { + fn name(&self) -> Cow<str> { + "webGPUAdapter".into() + } +} + +struct WebGpuDevice(wgpu_core::id::DeviceId); +impl Resource for WebGpuDevice { + fn name(&self) -> Cow<str> { + "webGPUDevice".into() + } +} + +struct WebGpuQuerySet(wgpu_core::id::QuerySetId); +impl Resource for WebGpuQuerySet { + fn name(&self) -> Cow<str> { + "webGPUQuerySet".into() + } +} + +pub fn init(unstable: bool) -> Extension { + Extension::builder() + .js(include_js_files!( + prefix "deno:ext/webgpu", + "01_webgpu.js", + "02_idl_types.js", + )) + .ops(declare_webgpu_ops()) + .state(move |state| { + // TODO: check & possibly streamline this + // Unstable might be able to be OpMiddleware + // let unstable_checker = state.borrow::<super::UnstableChecker>(); + // let unstable = unstable_checker.unstable; + state.put(Unstable(unstable)); + Ok(()) + }) + .build() +} + +pub fn get_declaration() -> PathBuf { + PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("lib.deno_webgpu.d.ts") +} + +fn deserialize_features(features: &wgpu_types::Features) -> Vec<&'static str> { + let mut return_features: Vec<&'static str> = vec![]; + + if features.contains(wgpu_types::Features::DEPTH_CLAMPING) { + return_features.push("depth-clamping"); + } + if features.contains(wgpu_types::Features::PIPELINE_STATISTICS_QUERY) { + return_features.push("pipeline-statistics-query"); + } + if features.contains(wgpu_types::Features::TEXTURE_COMPRESSION_BC) { + return_features.push("texture-compression-bc"); + } + if features.contains(wgpu_types::Features::TIMESTAMP_QUERY) { + return_features.push("timestamp-query"); + } + + // extended from spec + if features.contains(wgpu_types::Features::MAPPABLE_PRIMARY_BUFFERS) { + return_features.push("mappable-primary-buffers"); + } + if features.contains(wgpu_types::Features::SAMPLED_TEXTURE_BINDING_ARRAY) { + return_features.push("sampled-texture-binding-array"); + } + if features + .contains(wgpu_types::Features::SAMPLED_TEXTURE_ARRAY_DYNAMIC_INDEXING) + { + return_features.push("sampled-texture-array-dynamic-indexing"); + } + if features + .contains(wgpu_types::Features::SAMPLED_TEXTURE_ARRAY_NON_UNIFORM_INDEXING) + { + return_features.push("sampled-texture-array-non-uniform-indexing"); + } + if features.contains(wgpu_types::Features::UNSIZED_BINDING_ARRAY) { + return_features.push("unsized-binding-array"); + } + if features.contains(wgpu_types::Features::MULTI_DRAW_INDIRECT) { + return_features.push("multi-draw-indirect"); + } + if features.contains(wgpu_types::Features::MULTI_DRAW_INDIRECT_COUNT) { + return_features.push("multi-draw-indirect-count"); + } + if features.contains(wgpu_types::Features::PUSH_CONSTANTS) { + return_features.push("push-constants"); + } + if features.contains(wgpu_types::Features::ADDRESS_MODE_CLAMP_TO_BORDER) { + return_features.push("address-mode-clamp-to-border"); + } + if features.contains(wgpu_types::Features::NON_FILL_POLYGON_MODE) { + return_features.push("non-fill-polygon-mode"); + } + if features.contains(wgpu_types::Features::TEXTURE_COMPRESSION_ETC2) { + return_features.push("texture-compression-etc2"); + } + if features.contains(wgpu_types::Features::TEXTURE_COMPRESSION_ASTC_LDR) { + return_features.push("texture-compression-astc-ldr"); + } + if features + .contains(wgpu_types::Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES) + { + return_features.push("texture-adapter-specific-format-features"); + } + if features.contains(wgpu_types::Features::SHADER_FLOAT64) { + return_features.push("shader-float64"); + } + if features.contains(wgpu_types::Features::VERTEX_ATTRIBUTE_64BIT) { + return_features.push("vertex-attribute-64bit"); + } + + return_features +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RequestAdapterArgs { + power_preference: Option<String>, +} + +#[derive(Serialize)] +#[serde(untagged)] +pub enum GpuAdapterDeviceOrErr { + Error { err: String }, + Features(GpuAdapterDevice), +} + +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +pub struct GpuAdapterDevice { + rid: ResourceId, + name: Option<String>, + limits: wgpu_types::Limits, + features: Vec<&'static str>, + is_software: bool, +} + +pub async fn op_webgpu_request_adapter( + state: Rc<RefCell<OpState>>, + args: RequestAdapterArgs, + _: (), +) -> Result<GpuAdapterDeviceOrErr, AnyError> { + let mut state = state.borrow_mut(); + check_unstable(&state, "navigator.gpu.requestAdapter"); + let instance = if let Some(instance) = state.try_borrow::<Instance>() { + instance + } else { + state.put(wgpu_core::hub::Global::new( + "webgpu", + wgpu_core::hub::IdentityManagerFactory, + wgpu_types::BackendBit::PRIMARY, + )); + state.borrow::<Instance>() + }; + + let descriptor = wgpu_core::instance::RequestAdapterOptions { + power_preference: match args.power_preference { + Some(power_preference) => match power_preference.as_str() { + "low-power" => wgpu_types::PowerPreference::LowPower, + "high-performance" => wgpu_types::PowerPreference::HighPerformance, + _ => unreachable!(), + }, + None => Default::default(), + }, + // TODO(lucacasonato): respect forceSoftware + compatible_surface: None, // windowless + }; + let res = instance.request_adapter( + &descriptor, + wgpu_core::instance::AdapterInputs::Mask( + wgpu_types::BackendBit::PRIMARY, + |_| std::marker::PhantomData, + ), + ); + + let adapter = match res { + Ok(adapter) => adapter, + Err(err) => { + return Ok(GpuAdapterDeviceOrErr::Error { + err: err.to_string(), + }) + } + }; + let name = gfx_select!(adapter => instance.adapter_get_info(adapter))?.name; + let adapter_features = + gfx_select!(adapter => instance.adapter_features(adapter))?; + let features = deserialize_features(&adapter_features); + let adapter_limits = + gfx_select!(adapter => instance.adapter_limits(adapter))?; + + let rid = state.resource_table.add(WebGpuAdapter(adapter)); + + Ok(GpuAdapterDeviceOrErr::Features(GpuAdapterDevice { + rid, + name: Some(name), + features, + limits: adapter_limits, + is_software: false, + })) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct GpuLimits { + max_texture_dimension_1d: Option<u32>, + max_texture_dimension_2d: Option<u32>, + max_texture_dimension_3d: Option<u32>, + max_texture_array_layers: Option<u32>, + max_bind_groups: Option<u32>, + max_dynamic_uniform_buffers_per_pipeline_layout: Option<u32>, + max_dynamic_storage_buffers_per_pipeline_layout: Option<u32>, + max_sampled_textures_per_shader_stage: Option<u32>, + max_samplers_per_shader_stage: Option<u32>, + max_storage_buffers_per_shader_stage: Option<u32>, + max_storage_textures_per_shader_stage: Option<u32>, + max_uniform_buffers_per_shader_stage: Option<u32>, + max_uniform_buffer_binding_size: Option<u32>, + max_storage_buffer_binding_size: Option<u32>, + // min_uniform_buffer_offset_alignment: Option<u32>, + // min_storage_buffer_offset_alignment: Option<u32>, + max_vertex_buffers: Option<u32>, + max_vertex_attributes: Option<u32>, + max_vertex_buffer_array_stride: Option<u32>, + // max_inter_stage_shader_components: Option<u32>, + // max_compute_workgroup_storage_size: Option<u32>, + // max_compute_workgroup_invocations: Option<u32>, + // max_compute_per_dimension_dispatch_size: Option<u32>, +} + +impl From<GpuLimits> for wgpu_types::Limits { + fn from(limits: GpuLimits) -> wgpu_types::Limits { + wgpu_types::Limits { + max_texture_dimension_1d: limits.max_texture_dimension_1d.unwrap_or(8192), + max_texture_dimension_2d: limits.max_texture_dimension_2d.unwrap_or(8192), + max_texture_dimension_3d: limits.max_texture_dimension_3d.unwrap_or(2048), + max_texture_array_layers: limits.max_texture_array_layers.unwrap_or(2048), + max_bind_groups: limits.max_bind_groups.unwrap_or(4), + max_dynamic_uniform_buffers_per_pipeline_layout: limits + .max_dynamic_uniform_buffers_per_pipeline_layout + .unwrap_or(8), + max_dynamic_storage_buffers_per_pipeline_layout: limits + .max_dynamic_storage_buffers_per_pipeline_layout + .unwrap_or(4), + max_sampled_textures_per_shader_stage: limits + .max_sampled_textures_per_shader_stage + .unwrap_or(16), + max_samplers_per_shader_stage: limits + .max_samplers_per_shader_stage + .unwrap_or(16), + max_storage_buffers_per_shader_stage: limits + .max_storage_buffers_per_shader_stage + .unwrap_or(4), + max_storage_textures_per_shader_stage: limits + .max_storage_textures_per_shader_stage + .unwrap_or(4), + max_uniform_buffers_per_shader_stage: limits + .max_uniform_buffers_per_shader_stage + .unwrap_or(12), + max_uniform_buffer_binding_size: limits + .max_uniform_buffer_binding_size + .unwrap_or(16384), + max_storage_buffer_binding_size: limits + .max_storage_buffer_binding_size + .unwrap_or(134217728), + // min_uniform_buffer_offset_alignment: limits + // .min_uniform_buffer_offset_alignment + // .unwrap_or(default), + // min_storage_buffer_offset_alignment: limits + // .min_storage_buffer_offset_alignment + // .unwrap_or(default), + max_vertex_buffers: limits.max_vertex_buffers.unwrap_or(8), + max_vertex_attributes: limits.max_vertex_attributes.unwrap_or(16), + max_vertex_buffer_array_stride: limits + .max_vertex_buffer_array_stride + .unwrap_or(2048), + // max_inter_stage_shader_components: limits + // .max_inter_stage_shader_components + // .unwrap_or(default), + // max_compute_workgroup_storage_size: limits + // .max_compute_workgroup_storage_size + // .unwrap_or(default), + // max_compute_workgroup_invocations: limits + // .max_compute_workgroup_invocations + // .unwrap_or(default), + // max_compute_per_dimension_dispatch_size: limits + // .max_compute_per_dimension_dispatch_size + // .unwrap_or(default), + max_push_constant_size: 0, + } + } +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RequestDeviceArgs { + adapter_rid: ResourceId, + label: Option<String>, + required_features: Option<Vec<String>>, + required_limits: Option<GpuLimits>, +} + +pub async fn op_webgpu_request_device( + state: Rc<RefCell<OpState>>, + args: RequestDeviceArgs, + _: (), +) -> Result<GpuAdapterDevice, AnyError> { + let mut state = state.borrow_mut(); + let adapter_resource = state + .resource_table + .get::<WebGpuAdapter>(args.adapter_rid) + .ok_or_else(bad_resource_id)?; + let adapter = adapter_resource.0; + let instance = state.borrow::<Instance>(); + + let mut features: wgpu_types::Features = wgpu_types::Features::empty(); + + if let Some(passed_features) = args.required_features { + if passed_features.contains(&"depth-clamping".to_string()) { + features.set(wgpu_types::Features::DEPTH_CLAMPING, true); + } + if passed_features.contains(&"pipeline-statistics-query".to_string()) { + features.set(wgpu_types::Features::PIPELINE_STATISTICS_QUERY, true); + } + if passed_features.contains(&"texture-compression-bc".to_string()) { + features.set(wgpu_types::Features::TEXTURE_COMPRESSION_BC, true); + } + if passed_features.contains(&"timestamp-query".to_string()) { + features.set(wgpu_types::Features::TIMESTAMP_QUERY, true); + } + + // extended from spec + if passed_features.contains(&"mappable-primary-buffers".to_string()) { + features.set(wgpu_types::Features::MAPPABLE_PRIMARY_BUFFERS, true); + } + if passed_features.contains(&"sampled-texture-binding-array".to_string()) { + features.set(wgpu_types::Features::SAMPLED_TEXTURE_BINDING_ARRAY, true); + } + if passed_features + .contains(&"sampled-texture-array-dynamic-indexing".to_string()) + { + features.set( + wgpu_types::Features::SAMPLED_TEXTURE_ARRAY_DYNAMIC_INDEXING, + true, + ); + } + if passed_features + .contains(&"sampled-texture-array-non-uniform-indexing".to_string()) + { + features.set( + wgpu_types::Features::SAMPLED_TEXTURE_ARRAY_NON_UNIFORM_INDEXING, + true, + ); + } + if passed_features.contains(&"unsized-binding-array".to_string()) { + features.set(wgpu_types::Features::UNSIZED_BINDING_ARRAY, true); + } + if passed_features.contains(&"multi-draw-indirect".to_string()) { + features.set(wgpu_types::Features::MULTI_DRAW_INDIRECT, true); + } + if passed_features.contains(&"multi-draw-indirect-count".to_string()) { + features.set(wgpu_types::Features::MULTI_DRAW_INDIRECT_COUNT, true); + } + if passed_features.contains(&"push-constants".to_string()) { + features.set(wgpu_types::Features::PUSH_CONSTANTS, true); + } + if passed_features.contains(&"address-mode-clamp-to-border".to_string()) { + features.set(wgpu_types::Features::ADDRESS_MODE_CLAMP_TO_BORDER, true); + } + if passed_features.contains(&"non-fill-polygon-mode".to_string()) { + features.set(wgpu_types::Features::NON_FILL_POLYGON_MODE, true); + } + if passed_features.contains(&"texture-compression-etc2".to_string()) { + features.set(wgpu_types::Features::TEXTURE_COMPRESSION_ETC2, true); + } + if passed_features.contains(&"texture-compression-astc-ldr".to_string()) { + features.set(wgpu_types::Features::TEXTURE_COMPRESSION_ASTC_LDR, true); + } + if passed_features + .contains(&"texture-adapter-specific-format-features".to_string()) + { + features.set( + wgpu_types::Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES, + true, + ); + } + if passed_features.contains(&"shader-float64".to_string()) { + features.set(wgpu_types::Features::SHADER_FLOAT64, true); + } + if passed_features.contains(&"vertex-attribute-64bit".to_string()) { + features.set(wgpu_types::Features::VERTEX_ATTRIBUTE_64BIT, true); + } + } + + let descriptor = wgpu_types::DeviceDescriptor { + label: args.label.map(Cow::from), + features, + limits: args + .required_limits + .map_or(wgpu_types::Limits::default(), Into::into), + }; + + let (device, maybe_err) = gfx_select!(adapter => instance.adapter_request_device( + adapter, + &descriptor, + std::env::var("DENO_WEBGPU_TRACE").ok().as_ref().map(std::path::Path::new), + std::marker::PhantomData + )); + if let Some(err) = maybe_err { + return Err(DomExceptionOperationError::new(&err.to_string()).into()); + } + + let device_features = + gfx_select!(device => instance.device_features(device))?; + let features = deserialize_features(&device_features); + let limits = gfx_select!(device => instance.device_limits(device))?; + + let rid = state.resource_table.add(WebGpuDevice(device)); + + Ok(GpuAdapterDevice { + rid, + name: None, + features, + limits, + // TODO(lucacasonato): report correctly from wgpu + is_software: false, + }) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CreateQuerySetArgs { + device_rid: ResourceId, + _label: Option<String>, // not yet implemented + #[serde(rename = "type")] + kind: String, + count: u32, + pipeline_statistics: Option<Vec<String>>, +} + +pub fn op_webgpu_create_query_set( + state: &mut OpState, + args: CreateQuerySetArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let device_resource = state + .resource_table + .get::<WebGpuDevice>(args.device_rid) + .ok_or_else(bad_resource_id)?; + let device = device_resource.0; + let instance = &state.borrow::<Instance>(); + + let descriptor = wgpu_types::QuerySetDescriptor { + ty: match args.kind.as_str() { + "pipeline-statistics" => { + let mut pipeline_statistics_names = + wgpu_types::PipelineStatisticsTypes::empty(); + + if let Some(pipeline_statistics) = args.pipeline_statistics { + if pipeline_statistics + .contains(&"vertex-shader-invocations".to_string()) + { + pipeline_statistics_names.set( + wgpu_types::PipelineStatisticsTypes::VERTEX_SHADER_INVOCATIONS, + true, + ); + } + if pipeline_statistics.contains(&"clipper-invocations".to_string()) { + pipeline_statistics_names.set( + wgpu_types::PipelineStatisticsTypes::CLIPPER_INVOCATIONS, + true, + ); + } + if pipeline_statistics.contains(&"clipper-primitives-out".to_string()) + { + pipeline_statistics_names.set( + wgpu_types::PipelineStatisticsTypes::CLIPPER_PRIMITIVES_OUT, + true, + ); + } + if pipeline_statistics + .contains(&"fragment-shader-invocations".to_string()) + { + pipeline_statistics_names.set( + wgpu_types::PipelineStatisticsTypes::FRAGMENT_SHADER_INVOCATIONS, + true, + ); + } + if pipeline_statistics + .contains(&"compute-shader-invocations".to_string()) + { + pipeline_statistics_names.set( + wgpu_types::PipelineStatisticsTypes::COMPUTE_SHADER_INVOCATIONS, + true, + ); + } + }; + + wgpu_types::QueryType::PipelineStatistics(pipeline_statistics_names) + } + "occlusion" => return Err(not_supported()), + "timestamp" => wgpu_types::QueryType::Timestamp, + _ => unreachable!(), + }, + count: args.count, + }; + + gfx_put!(device => instance.device_create_query_set( + device, + &descriptor, + std::marker::PhantomData + ) => state, WebGpuQuerySet) +} + +fn declare_webgpu_ops() -> Vec<(&'static str, Box<OpFn>)> { + vec![ + // Request device/adapter + ( + "op_webgpu_request_adapter", + op_async(op_webgpu_request_adapter), + ), + ( + "op_webgpu_request_device", + op_async(op_webgpu_request_device), + ), + // Query Set + ( + "op_webgpu_create_query_set", + op_sync(op_webgpu_create_query_set), + ), + // buffer + ( + "op_webgpu_create_buffer", + op_sync(buffer::op_webgpu_create_buffer), + ), + ( + "op_webgpu_buffer_get_mapped_range", + op_sync(buffer::op_webgpu_buffer_get_mapped_range), + ), + ( + "op_webgpu_buffer_unmap", + op_sync(buffer::op_webgpu_buffer_unmap), + ), + // buffer async + ( + "op_webgpu_buffer_get_map_async", + op_async(buffer::op_webgpu_buffer_get_map_async), + ), + // remaining sync ops + + // texture + ( + "op_webgpu_create_texture", + op_sync(texture::op_webgpu_create_texture), + ), + ( + "op_webgpu_create_texture_view", + op_sync(texture::op_webgpu_create_texture_view), + ), + // sampler + ( + "op_webgpu_create_sampler", + op_sync(sampler::op_webgpu_create_sampler), + ), + // binding + ( + "op_webgpu_create_bind_group_layout", + op_sync(binding::op_webgpu_create_bind_group_layout), + ), + ( + "op_webgpu_create_pipeline_layout", + op_sync(binding::op_webgpu_create_pipeline_layout), + ), + ( + "op_webgpu_create_bind_group", + op_sync(binding::op_webgpu_create_bind_group), + ), + // pipeline + ( + "op_webgpu_create_compute_pipeline", + op_sync(pipeline::op_webgpu_create_compute_pipeline), + ), + ( + "op_webgpu_compute_pipeline_get_bind_group_layout", + op_sync(pipeline::op_webgpu_compute_pipeline_get_bind_group_layout), + ), + ( + "op_webgpu_create_render_pipeline", + op_sync(pipeline::op_webgpu_create_render_pipeline), + ), + ( + "op_webgpu_render_pipeline_get_bind_group_layout", + op_sync(pipeline::op_webgpu_render_pipeline_get_bind_group_layout), + ), + // command_encoder + ( + "op_webgpu_create_command_encoder", + op_sync(command_encoder::op_webgpu_create_command_encoder), + ), + ( + "op_webgpu_command_encoder_begin_render_pass", + op_sync(command_encoder::op_webgpu_command_encoder_begin_render_pass), + ), + ( + "op_webgpu_command_encoder_begin_compute_pass", + op_sync(command_encoder::op_webgpu_command_encoder_begin_compute_pass), + ), + ( + "op_webgpu_command_encoder_copy_buffer_to_buffer", + op_sync(command_encoder::op_webgpu_command_encoder_copy_buffer_to_buffer), + ), + ( + "op_webgpu_command_encoder_copy_buffer_to_texture", + op_sync( + command_encoder::op_webgpu_command_encoder_copy_buffer_to_texture, + ), + ), + ( + "op_webgpu_command_encoder_copy_texture_to_buffer", + op_sync( + command_encoder::op_webgpu_command_encoder_copy_texture_to_buffer, + ), + ), + ( + "op_webgpu_command_encoder_copy_texture_to_texture", + op_sync( + command_encoder::op_webgpu_command_encoder_copy_texture_to_texture, + ), + ), + ( + "op_webgpu_command_encoder_push_debug_group", + op_sync(command_encoder::op_webgpu_command_encoder_push_debug_group), + ), + ( + "op_webgpu_command_encoder_pop_debug_group", + op_sync(command_encoder::op_webgpu_command_encoder_pop_debug_group), + ), + ( + "op_webgpu_command_encoder_insert_debug_marker", + op_sync(command_encoder::op_webgpu_command_encoder_insert_debug_marker), + ), + ( + "op_webgpu_command_encoder_write_timestamp", + op_sync(command_encoder::op_webgpu_command_encoder_write_timestamp), + ), + ( + "op_webgpu_command_encoder_resolve_query_set", + op_sync(command_encoder::op_webgpu_command_encoder_resolve_query_set), + ), + ( + "op_webgpu_command_encoder_finish", + op_sync(command_encoder::op_webgpu_command_encoder_finish), + ), + // render_pass + ( + "op_webgpu_render_pass_set_viewport", + op_sync(render_pass::op_webgpu_render_pass_set_viewport), + ), + ( + "op_webgpu_render_pass_set_scissor_rect", + op_sync(render_pass::op_webgpu_render_pass_set_scissor_rect), + ), + ( + "op_webgpu_render_pass_set_blend_constant", + op_sync(render_pass::op_webgpu_render_pass_set_blend_constant), + ), + ( + "op_webgpu_render_pass_set_stencil_reference", + op_sync(render_pass::op_webgpu_render_pass_set_stencil_reference), + ), + ( + "op_webgpu_render_pass_begin_pipeline_statistics_query", + op_sync( + render_pass::op_webgpu_render_pass_begin_pipeline_statistics_query, + ), + ), + ( + "op_webgpu_render_pass_end_pipeline_statistics_query", + op_sync(render_pass::op_webgpu_render_pass_end_pipeline_statistics_query), + ), + ( + "op_webgpu_render_pass_write_timestamp", + op_sync(render_pass::op_webgpu_render_pass_write_timestamp), + ), + ( + "op_webgpu_render_pass_execute_bundles", + op_sync(render_pass::op_webgpu_render_pass_execute_bundles), + ), + ( + "op_webgpu_render_pass_end_pass", + op_sync(render_pass::op_webgpu_render_pass_end_pass), + ), + ( + "op_webgpu_render_pass_set_bind_group", + op_sync(render_pass::op_webgpu_render_pass_set_bind_group), + ), + ( + "op_webgpu_render_pass_push_debug_group", + op_sync(render_pass::op_webgpu_render_pass_push_debug_group), + ), + ( + "op_webgpu_render_pass_pop_debug_group", + op_sync(render_pass::op_webgpu_render_pass_pop_debug_group), + ), + ( + "op_webgpu_render_pass_insert_debug_marker", + op_sync(render_pass::op_webgpu_render_pass_insert_debug_marker), + ), + ( + "op_webgpu_render_pass_set_pipeline", + op_sync(render_pass::op_webgpu_render_pass_set_pipeline), + ), + ( + "op_webgpu_render_pass_set_index_buffer", + op_sync(render_pass::op_webgpu_render_pass_set_index_buffer), + ), + ( + "op_webgpu_render_pass_set_vertex_buffer", + op_sync(render_pass::op_webgpu_render_pass_set_vertex_buffer), + ), + ( + "op_webgpu_render_pass_draw", + op_sync(render_pass::op_webgpu_render_pass_draw), + ), + ( + "op_webgpu_render_pass_draw_indexed", + op_sync(render_pass::op_webgpu_render_pass_draw_indexed), + ), + ( + "op_webgpu_render_pass_draw_indirect", + op_sync(render_pass::op_webgpu_render_pass_draw_indirect), + ), + ( + "op_webgpu_render_pass_draw_indexed_indirect", + op_sync(render_pass::op_webgpu_render_pass_draw_indexed_indirect), + ), + // compute_pass + ( + "op_webgpu_compute_pass_set_pipeline", + op_sync(compute_pass::op_webgpu_compute_pass_set_pipeline), + ), + ( + "op_webgpu_compute_pass_dispatch", + op_sync(compute_pass::op_webgpu_compute_pass_dispatch), + ), + ( + "op_webgpu_compute_pass_dispatch_indirect", + op_sync(compute_pass::op_webgpu_compute_pass_dispatch_indirect), + ), + ( + "op_webgpu_compute_pass_end_pass", + op_sync(compute_pass::op_webgpu_compute_pass_end_pass), + ), + ( + "op_webgpu_compute_pass_set_bind_group", + op_sync(compute_pass::op_webgpu_compute_pass_set_bind_group), + ), + ( + "op_webgpu_compute_pass_push_debug_group", + op_sync(compute_pass::op_webgpu_compute_pass_push_debug_group), + ), + ( + "op_webgpu_compute_pass_pop_debug_group", + op_sync(compute_pass::op_webgpu_compute_pass_pop_debug_group), + ), + ( + "op_webgpu_compute_pass_insert_debug_marker", + op_sync(compute_pass::op_webgpu_compute_pass_insert_debug_marker), + ), + // bundle + ( + "op_webgpu_create_render_bundle_encoder", + op_sync(bundle::op_webgpu_create_render_bundle_encoder), + ), + ( + "op_webgpu_render_bundle_encoder_finish", + op_sync(bundle::op_webgpu_render_bundle_encoder_finish), + ), + ( + "op_webgpu_render_bundle_encoder_set_bind_group", + op_sync(bundle::op_webgpu_render_bundle_encoder_set_bind_group), + ), + ( + "op_webgpu_render_bundle_encoder_push_debug_group", + op_sync(bundle::op_webgpu_render_bundle_encoder_push_debug_group), + ), + ( + "op_webgpu_render_bundle_encoder_pop_debug_group", + op_sync(bundle::op_webgpu_render_bundle_encoder_pop_debug_group), + ), + ( + "op_webgpu_render_bundle_encoder_insert_debug_marker", + op_sync(bundle::op_webgpu_render_bundle_encoder_insert_debug_marker), + ), + ( + "op_webgpu_render_bundle_encoder_set_pipeline", + op_sync(bundle::op_webgpu_render_bundle_encoder_set_pipeline), + ), + ( + "op_webgpu_render_bundle_encoder_set_index_buffer", + op_sync(bundle::op_webgpu_render_bundle_encoder_set_index_buffer), + ), + ( + "op_webgpu_render_bundle_encoder_set_vertex_buffer", + op_sync(bundle::op_webgpu_render_bundle_encoder_set_vertex_buffer), + ), + ( + "op_webgpu_render_bundle_encoder_draw", + op_sync(bundle::op_webgpu_render_bundle_encoder_draw), + ), + ( + "op_webgpu_render_bundle_encoder_draw_indexed", + op_sync(bundle::op_webgpu_render_bundle_encoder_draw_indexed), + ), + ( + "op_webgpu_render_bundle_encoder_draw_indirect", + op_sync(bundle::op_webgpu_render_bundle_encoder_draw_indirect), + ), + // queue + ( + "op_webgpu_queue_submit", + op_sync(queue::op_webgpu_queue_submit), + ), + ( + "op_webgpu_write_buffer", + op_sync(queue::op_webgpu_write_buffer), + ), + ( + "op_webgpu_write_texture", + op_sync(queue::op_webgpu_write_texture), + ), + // shader + ( + "op_webgpu_create_shader_module", + op_sync(shader::op_webgpu_create_shader_module), + ), + ] +} diff --git a/ext/webgpu/pipeline.rs b/ext/webgpu/pipeline.rs new file mode 100644 index 000000000..6d11179a4 --- /dev/null +++ b/ext/webgpu/pipeline.rs @@ -0,0 +1,686 @@ +// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. + +use deno_core::error::bad_resource_id; +use deno_core::error::AnyError; +use deno_core::ResourceId; +use deno_core::{OpState, Resource}; +use serde::Deserialize; +use serde::Serialize; +use std::borrow::Cow; + +use super::error::{WebGpuError, WebGpuResult}; + +pub(crate) struct WebGpuPipelineLayout( + pub(crate) wgpu_core::id::PipelineLayoutId, +); +impl Resource for WebGpuPipelineLayout { + fn name(&self) -> Cow<str> { + "webGPUPipelineLayout".into() + } +} + +pub(crate) struct WebGpuComputePipeline( + pub(crate) wgpu_core::id::ComputePipelineId, +); +impl Resource for WebGpuComputePipeline { + fn name(&self) -> Cow<str> { + "webGPUComputePipeline".into() + } +} + +pub(crate) struct WebGpuRenderPipeline( + pub(crate) wgpu_core::id::RenderPipelineId, +); +impl Resource for WebGpuRenderPipeline { + fn name(&self) -> Cow<str> { + "webGPURenderPipeline".into() + } +} + +pub fn serialize_index_format(format: String) -> wgpu_types::IndexFormat { + match format.as_str() { + "uint16" => wgpu_types::IndexFormat::Uint16, + "uint32" => wgpu_types::IndexFormat::Uint32, + _ => unreachable!(), + } +} + +fn serialize_stencil_operation( + operation: &str, +) -> wgpu_types::StencilOperation { + match operation { + "keep" => wgpu_types::StencilOperation::Keep, + "zero" => wgpu_types::StencilOperation::Zero, + "replace" => wgpu_types::StencilOperation::Replace, + "invert" => wgpu_types::StencilOperation::Invert, + "increment-clamp" => wgpu_types::StencilOperation::IncrementClamp, + "decrement-clamp" => wgpu_types::StencilOperation::DecrementClamp, + "increment-wrap" => wgpu_types::StencilOperation::IncrementWrap, + "decrement-wrap" => wgpu_types::StencilOperation::DecrementWrap, + _ => unreachable!(), + } +} + +fn serialize_stencil_face_state( + state: GpuStencilFaceState, +) -> wgpu_types::StencilFaceState { + wgpu_types::StencilFaceState { + compare: state + .compare + .as_ref() + .map_or(wgpu_types::CompareFunction::Always, |op| { + super::sampler::serialize_compare_function(op) + }), + fail_op: state + .fail_op + .as_ref() + .map_or(wgpu_types::StencilOperation::Keep, |op| { + serialize_stencil_operation(op) + }), + depth_fail_op: state + .depth_fail_op + .as_ref() + .map_or(wgpu_types::StencilOperation::Keep, |op| { + serialize_stencil_operation(op) + }), + pass_op: state + .pass_op + .as_ref() + .map_or(wgpu_types::StencilOperation::Keep, |op| { + serialize_stencil_operation(op) + }), + } +} + +fn serialize_blend_factor(blend_factor: &str) -> wgpu_types::BlendFactor { + match blend_factor { + "zero" => wgpu_types::BlendFactor::Zero, + "one" => wgpu_types::BlendFactor::One, + "src" => wgpu_types::BlendFactor::Src, + "one-minus-src" => wgpu_types::BlendFactor::OneMinusSrc, + "src-alpha" => wgpu_types::BlendFactor::SrcAlpha, + "one-minus-src-alpha" => wgpu_types::BlendFactor::OneMinusSrcAlpha, + "dst" => wgpu_types::BlendFactor::Dst, + "one-minus-dst" => wgpu_types::BlendFactor::OneMinusDst, + "dst-alpha" => wgpu_types::BlendFactor::DstAlpha, + "one-minus-dst-alpha" => wgpu_types::BlendFactor::OneMinusDstAlpha, + "src-alpha-saturated" => wgpu_types::BlendFactor::SrcAlphaSaturated, + "constant" => wgpu_types::BlendFactor::Constant, + "one-minus-constant" => wgpu_types::BlendFactor::OneMinusConstant, + _ => unreachable!(), + } +} + +fn serialize_blend_state(state: GpuBlendState) -> wgpu_types::BlendState { + wgpu_types::BlendState { + alpha: serialize_blend_component(state.alpha), + color: serialize_blend_component(state.color), + } +} + +fn serialize_blend_component( + blend: GpuBlendComponent, +) -> wgpu_types::BlendComponent { + wgpu_types::BlendComponent { + src_factor: blend + .src_factor + .as_ref() + .map_or(wgpu_types::BlendFactor::One, |factor| { + serialize_blend_factor(factor) + }), + dst_factor: blend + .dst_factor + .as_ref() + .map_or(wgpu_types::BlendFactor::Zero, |factor| { + serialize_blend_factor(factor) + }), + operation: match &blend.operation { + Some(operation) => match operation.as_str() { + "add" => wgpu_types::BlendOperation::Add, + "subtract" => wgpu_types::BlendOperation::Subtract, + "reverse-subtract" => wgpu_types::BlendOperation::ReverseSubtract, + "min" => wgpu_types::BlendOperation::Min, + "max" => wgpu_types::BlendOperation::Max, + _ => unreachable!(), + }, + None => wgpu_types::BlendOperation::Add, + }, + } +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct GpuProgrammableStage { + module: u32, + entry_point: String, + // constants: HashMap<String, GPUPipelineConstantValue> +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CreateComputePipelineArgs { + device_rid: ResourceId, + label: Option<String>, + layout: Option<u32>, + compute: GpuProgrammableStage, +} + +pub fn op_webgpu_create_compute_pipeline( + state: &mut OpState, + args: CreateComputePipelineArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let instance = state.borrow::<super::Instance>(); + let device_resource = state + .resource_table + .get::<super::WebGpuDevice>(args.device_rid) + .ok_or_else(bad_resource_id)?; + let device = device_resource.0; + + let pipeline_layout = if let Some(rid) = args.layout { + let id = state + .resource_table + .get::<WebGpuPipelineLayout>(rid) + .ok_or_else(bad_resource_id)?; + Some(id.0) + } else { + None + }; + + let compute_shader_module_resource = state + .resource_table + .get::<super::shader::WebGpuShaderModule>(args.compute.module) + .ok_or_else(bad_resource_id)?; + + let descriptor = wgpu_core::pipeline::ComputePipelineDescriptor { + label: args.label.map(Cow::from), + layout: pipeline_layout, + stage: wgpu_core::pipeline::ProgrammableStageDescriptor { + module: compute_shader_module_resource.0, + entry_point: Cow::from(args.compute.entry_point), + // TODO(lucacasonato): support args.compute.constants + }, + }; + let implicit_pipelines = match args.layout { + Some(_) => None, + None => Some(wgpu_core::device::ImplicitPipelineIds { + root_id: std::marker::PhantomData, + group_ids: &[std::marker::PhantomData; wgpu_core::MAX_BIND_GROUPS], + }), + }; + + let (compute_pipeline, maybe_err) = gfx_select!(device => instance.device_create_compute_pipeline( + device, + &descriptor, + std::marker::PhantomData, + implicit_pipelines + )); + + let rid = state + .resource_table + .add(WebGpuComputePipeline(compute_pipeline)); + + Ok(WebGpuResult::rid_err(rid, maybe_err)) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ComputePipelineGetBindGroupLayoutArgs { + compute_pipeline_rid: ResourceId, + index: u32, +} + +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +pub struct PipelineLayout { + rid: ResourceId, + label: String, + err: Option<WebGpuError>, +} + +pub fn op_webgpu_compute_pipeline_get_bind_group_layout( + state: &mut OpState, + args: ComputePipelineGetBindGroupLayoutArgs, + _: (), +) -> Result<PipelineLayout, AnyError> { + let instance = state.borrow::<super::Instance>(); + let compute_pipeline_resource = state + .resource_table + .get::<WebGpuComputePipeline>(args.compute_pipeline_rid) + .ok_or_else(bad_resource_id)?; + let compute_pipeline = compute_pipeline_resource.0; + + let (bind_group_layout, maybe_err) = gfx_select!(compute_pipeline => instance.compute_pipeline_get_bind_group_layout(compute_pipeline, args.index, std::marker::PhantomData)); + + let label = gfx_select!(bind_group_layout => instance.bind_group_layout_label(bind_group_layout)); + + let rid = state + .resource_table + .add(super::binding::WebGpuBindGroupLayout(bind_group_layout)); + + Ok(PipelineLayout { + rid, + label, + err: maybe_err.map(WebGpuError::from), + }) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct GpuPrimitiveState { + topology: Option<String>, + strip_index_format: Option<String>, + front_face: Option<String>, + cull_mode: Option<String>, + clamp_depth: bool, +} + +#[derive(Deserialize, Clone)] +#[serde(rename_all = "camelCase")] +struct GpuBlendComponent { + src_factor: Option<String>, + dst_factor: Option<String>, + operation: Option<String>, +} + +#[derive(Deserialize, Clone)] +#[serde(rename_all = "camelCase")] +struct GpuBlendState { + color: GpuBlendComponent, + alpha: GpuBlendComponent, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct GpuColorTargetState { + format: String, + blend: Option<GpuBlendState>, + write_mask: Option<u32>, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct GpuStencilFaceState { + compare: Option<String>, + fail_op: Option<String>, + depth_fail_op: Option<String>, + pass_op: Option<String>, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct GpuDepthStencilState { + format: String, + depth_write_enabled: Option<bool>, + depth_compare: Option<String>, + stencil_front: Option<GpuStencilFaceState>, + stencil_back: Option<GpuStencilFaceState>, + stencil_read_mask: Option<u32>, + stencil_write_mask: Option<u32>, + depth_bias: Option<i32>, + depth_bias_slope_scale: Option<f32>, + depth_bias_clamp: Option<f32>, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct GpuVertexAttribute { + format: GpuVertexFormat, + offset: u64, + shader_location: u32, +} + +#[derive(Deserialize)] +#[serde(rename_all = "lowercase")] +enum GpuVertexFormat { + 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, + Float64, + Float64x2, + Float64x3, + Float64x4, +} + +impl From<GpuVertexFormat> for wgpu_types::VertexFormat { + fn from(vf: GpuVertexFormat) -> wgpu_types::VertexFormat { + use wgpu_types::VertexFormat; + match vf { + GpuVertexFormat::Uint8x2 => VertexFormat::Uint8x2, + GpuVertexFormat::Uint8x4 => VertexFormat::Uint8x4, + GpuVertexFormat::Sint8x2 => VertexFormat::Sint8x2, + GpuVertexFormat::Sint8x4 => VertexFormat::Sint8x4, + GpuVertexFormat::Unorm8x2 => VertexFormat::Unorm8x2, + GpuVertexFormat::Unorm8x4 => VertexFormat::Unorm8x4, + GpuVertexFormat::Snorm8x2 => VertexFormat::Snorm8x2, + GpuVertexFormat::Snorm8x4 => VertexFormat::Snorm8x4, + GpuVertexFormat::Uint16x2 => VertexFormat::Uint16x2, + GpuVertexFormat::Uint16x4 => VertexFormat::Uint16x4, + GpuVertexFormat::Sint16x2 => VertexFormat::Sint16x2, + GpuVertexFormat::Sint16x4 => VertexFormat::Sint16x4, + GpuVertexFormat::Unorm16x2 => VertexFormat::Unorm16x2, + GpuVertexFormat::Unorm16x4 => VertexFormat::Unorm16x4, + GpuVertexFormat::Snorm16x2 => VertexFormat::Snorm16x2, + GpuVertexFormat::Snorm16x4 => VertexFormat::Snorm16x4, + GpuVertexFormat::Float16x2 => VertexFormat::Float16x2, + GpuVertexFormat::Float16x4 => VertexFormat::Float16x4, + GpuVertexFormat::Float32 => VertexFormat::Float32, + GpuVertexFormat::Float32x2 => VertexFormat::Float32x2, + GpuVertexFormat::Float32x3 => VertexFormat::Float32x3, + GpuVertexFormat::Float32x4 => VertexFormat::Float32x4, + GpuVertexFormat::Uint32 => VertexFormat::Uint32, + GpuVertexFormat::Uint32x2 => VertexFormat::Uint32x2, + GpuVertexFormat::Uint32x3 => VertexFormat::Uint32x3, + GpuVertexFormat::Uint32x4 => VertexFormat::Uint32x4, + GpuVertexFormat::Sint32 => VertexFormat::Sint32, + GpuVertexFormat::Sint32x2 => VertexFormat::Sint32x2, + GpuVertexFormat::Sint32x3 => VertexFormat::Sint32x3, + GpuVertexFormat::Sint32x4 => VertexFormat::Sint32x4, + GpuVertexFormat::Float64 => VertexFormat::Float64, + GpuVertexFormat::Float64x2 => VertexFormat::Float64x2, + GpuVertexFormat::Float64x3 => VertexFormat::Float64x3, + GpuVertexFormat::Float64x4 => VertexFormat::Float64x4, + } + } +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct GpuVertexBufferLayout { + array_stride: u64, + step_mode: Option<String>, + attributes: Vec<GpuVertexAttribute>, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct GpuVertexState { + module: u32, + entry_point: String, + buffers: Option<Vec<Option<GpuVertexBufferLayout>>>, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct GpuMultisampleState { + count: Option<u32>, + mask: Option<u64>, // against spec, but future proof + alpha_to_coverage_enabled: Option<bool>, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct GpuFragmentState { + targets: Vec<GpuColorTargetState>, + module: u32, + entry_point: String, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CreateRenderPipelineArgs { + device_rid: ResourceId, + label: Option<String>, + layout: Option<u32>, + vertex: GpuVertexState, + primitive: Option<GpuPrimitiveState>, + depth_stencil: Option<GpuDepthStencilState>, + multisample: Option<GpuMultisampleState>, + fragment: Option<GpuFragmentState>, +} + +pub fn op_webgpu_create_render_pipeline( + state: &mut OpState, + args: CreateRenderPipelineArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let instance = state.borrow::<super::Instance>(); + let device_resource = state + .resource_table + .get::<super::WebGpuDevice>(args.device_rid) + .ok_or_else(bad_resource_id)?; + let device = device_resource.0; + + let layout = if let Some(rid) = args.layout { + let pipeline_layout_resource = state + .resource_table + .get::<WebGpuPipelineLayout>(rid) + .ok_or_else(bad_resource_id)?; + Some(pipeline_layout_resource.0) + } else { + None + }; + + let vertex_shader_module_resource = state + .resource_table + .get::<super::shader::WebGpuShaderModule>(args.vertex.module) + .ok_or_else(bad_resource_id)?; + + let descriptor = wgpu_core::pipeline::RenderPipelineDescriptor { + label: args.label.map(Cow::from), + layout, + vertex: wgpu_core::pipeline::VertexState { + stage: wgpu_core::pipeline::ProgrammableStageDescriptor { + module: vertex_shader_module_resource.0, + entry_point: Cow::from(args.vertex.entry_point), + }, + buffers: Cow::from(if let Some(buffers) = args.vertex.buffers { + let mut return_buffers = vec![]; + for buffer in buffers.into_iter().flatten() { + return_buffers.push(wgpu_core::pipeline::VertexBufferLayout { + array_stride: buffer.array_stride, + step_mode: match buffer.step_mode { + Some(step_mode) => match step_mode.as_str() { + "vertex" => wgpu_types::InputStepMode::Vertex, + "instance" => wgpu_types::InputStepMode::Instance, + _ => unreachable!(), + }, + None => wgpu_types::InputStepMode::Vertex, + }, + attributes: Cow::from( + buffer + .attributes + .into_iter() + .map(|attribute| wgpu_types::VertexAttribute { + format: attribute.format.into(), + offset: attribute.offset, + shader_location: attribute.shader_location, + }) + .collect::<Vec<wgpu_types::VertexAttribute>>(), + ), + }); + } + return_buffers + } else { + vec![] + }), + }, + primitive: args.primitive.map_or(Default::default(), |primitive| { + wgpu_types::PrimitiveState { + topology: match primitive.topology { + Some(topology) => match topology.as_str() { + "point-list" => wgpu_types::PrimitiveTopology::PointList, + "line-list" => wgpu_types::PrimitiveTopology::LineList, + "line-strip" => wgpu_types::PrimitiveTopology::LineStrip, + "triangle-list" => wgpu_types::PrimitiveTopology::TriangleList, + "triangle-strip" => wgpu_types::PrimitiveTopology::TriangleStrip, + _ => unreachable!(), + }, + None => wgpu_types::PrimitiveTopology::TriangleList, + }, + strip_index_format: primitive + .strip_index_format + .map(serialize_index_format), + front_face: match primitive.front_face { + Some(front_face) => match front_face.as_str() { + "ccw" => wgpu_types::FrontFace::Ccw, + "cw" => wgpu_types::FrontFace::Cw, + _ => unreachable!(), + }, + None => wgpu_types::FrontFace::Ccw, + }, + cull_mode: match primitive.cull_mode { + Some(cull_mode) => match cull_mode.as_str() { + "none" => None, + "front" => Some(wgpu_types::Face::Front), + "back" => Some(wgpu_types::Face::Back), + _ => unreachable!(), + }, + None => None, + }, + polygon_mode: Default::default(), // native-only + conservative: false, // native-only + clamp_depth: primitive.clamp_depth, + } + }), + depth_stencil: args.depth_stencil.map(|depth_stencil| { + wgpu_types::DepthStencilState { + format: super::texture::serialize_texture_format(&depth_stencil.format) + .unwrap(), + depth_write_enabled: depth_stencil.depth_write_enabled.unwrap_or(false), + depth_compare: match depth_stencil.depth_compare { + Some(depth_compare) => { + super::sampler::serialize_compare_function(&depth_compare) + } + None => wgpu_types::CompareFunction::Always, + }, + stencil: wgpu_types::StencilState { + front: depth_stencil + .stencil_front + .map_or(Default::default(), serialize_stencil_face_state), + back: depth_stencil + .stencil_back + .map_or(Default::default(), serialize_stencil_face_state), + read_mask: depth_stencil.stencil_read_mask.unwrap_or(0xFFFFFFFF), + write_mask: depth_stencil.stencil_write_mask.unwrap_or(0xFFFFFFFF), + }, + bias: wgpu_types::DepthBiasState { + constant: depth_stencil.depth_bias.unwrap_or(0), + slope_scale: depth_stencil.depth_bias_slope_scale.unwrap_or(0.0), + clamp: depth_stencil.depth_bias_clamp.unwrap_or(0.0), + }, + } + }), + multisample: args.multisample.map_or(Default::default(), |multisample| { + wgpu_types::MultisampleState { + count: multisample.count.unwrap_or(1), + mask: multisample.mask.unwrap_or(0xFFFFFFFF), + alpha_to_coverage_enabled: multisample + .alpha_to_coverage_enabled + .unwrap_or(false), + } + }), + fragment: args.fragment.map(|fragment| { + let fragment_shader_module_resource = state + .resource_table + .get::<super::shader::WebGpuShaderModule>(fragment.module) + .ok_or_else(bad_resource_id) + .unwrap(); + + wgpu_core::pipeline::FragmentState { + stage: wgpu_core::pipeline::ProgrammableStageDescriptor { + module: fragment_shader_module_resource.0, + entry_point: Cow::from(fragment.entry_point), + }, + targets: Cow::from( + fragment + .targets + .into_iter() + .map(|target| wgpu_types::ColorTargetState { + format: super::texture::serialize_texture_format(&target.format) + .unwrap(), + blend: target.blend.map(serialize_blend_state), + write_mask: target + .write_mask + .map_or(Default::default(), |mask| { + wgpu_types::ColorWrite::from_bits(mask).unwrap() + }), + }) + .collect::<Vec<wgpu_types::ColorTargetState>>(), + ), + } + }), + }; + + let implicit_pipelines = match args.layout { + Some(_) => None, + None => Some(wgpu_core::device::ImplicitPipelineIds { + root_id: std::marker::PhantomData, + group_ids: &[std::marker::PhantomData; wgpu_core::MAX_BIND_GROUPS], + }), + }; + + let (render_pipeline, maybe_err) = gfx_select!(device => instance.device_create_render_pipeline( + device, + &descriptor, + std::marker::PhantomData, + implicit_pipelines + )); + + let rid = state + .resource_table + .add(WebGpuRenderPipeline(render_pipeline)); + + Ok(WebGpuResult::rid_err(rid, maybe_err)) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderPipelineGetBindGroupLayoutArgs { + render_pipeline_rid: ResourceId, + index: u32, +} + +pub fn op_webgpu_render_pipeline_get_bind_group_layout( + state: &mut OpState, + args: RenderPipelineGetBindGroupLayoutArgs, + _: (), +) -> Result<PipelineLayout, AnyError> { + let instance = state.borrow::<super::Instance>(); + let render_pipeline_resource = state + .resource_table + .get::<WebGpuRenderPipeline>(args.render_pipeline_rid) + .ok_or_else(bad_resource_id)?; + let render_pipeline = render_pipeline_resource.0; + + let (bind_group_layout, maybe_err) = gfx_select!(render_pipeline => instance.render_pipeline_get_bind_group_layout(render_pipeline, args.index, std::marker::PhantomData)); + + let label = gfx_select!(bind_group_layout => instance.bind_group_layout_label(bind_group_layout)); + + let rid = state + .resource_table + .add(super::binding::WebGpuBindGroupLayout(bind_group_layout)); + + Ok(PipelineLayout { + rid, + label, + err: maybe_err.map(WebGpuError::from), + }) +} diff --git a/ext/webgpu/queue.rs b/ext/webgpu/queue.rs new file mode 100644 index 000000000..876c2a207 --- /dev/null +++ b/ext/webgpu/queue.rs @@ -0,0 +1,160 @@ +// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. + +use std::num::NonZeroU32; + +use deno_core::error::bad_resource_id; +use deno_core::error::null_opbuf; +use deno_core::error::AnyError; +use deno_core::OpState; +use deno_core::ResourceId; +use deno_core::ZeroCopyBuf; +use serde::Deserialize; + +use super::error::WebGpuResult; + +type WebGpuQueue = super::WebGpuDevice; + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct QueueSubmitArgs { + queue_rid: ResourceId, + command_buffers: Vec<u32>, +} + +pub fn op_webgpu_queue_submit( + state: &mut OpState, + args: QueueSubmitArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let instance = state.borrow::<super::Instance>(); + let queue_resource = state + .resource_table + .get::<WebGpuQueue>(args.queue_rid) + .ok_or_else(bad_resource_id)?; + let queue = queue_resource.0; + + let mut ids = vec![]; + + for rid in args.command_buffers { + let buffer_resource = state + .resource_table + .get::<super::command_encoder::WebGpuCommandBuffer>(rid) + .ok_or_else(bad_resource_id)?; + ids.push(buffer_resource.0); + } + + let maybe_err = + gfx_select!(queue => instance.queue_submit(queue, &ids)).err(); + + Ok(WebGpuResult::maybe_err(maybe_err)) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct GpuImageDataLayout { + offset: Option<u64>, + bytes_per_row: Option<u32>, + rows_per_image: Option<u32>, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct QueueWriteBufferArgs { + queue_rid: ResourceId, + buffer: u32, + buffer_offset: u64, + data_offset: usize, + size: Option<usize>, +} + +pub fn op_webgpu_write_buffer( + state: &mut OpState, + args: QueueWriteBufferArgs, + zero_copy: Option<ZeroCopyBuf>, +) -> Result<WebGpuResult, AnyError> { + let zero_copy = zero_copy.ok_or_else(null_opbuf)?; + let instance = state.borrow::<super::Instance>(); + let buffer_resource = state + .resource_table + .get::<super::buffer::WebGpuBuffer>(args.buffer) + .ok_or_else(bad_resource_id)?; + let buffer = buffer_resource.0; + let queue_resource = state + .resource_table + .get::<WebGpuQueue>(args.queue_rid) + .ok_or_else(bad_resource_id)?; + let queue = queue_resource.0; + + let data = match args.size { + Some(size) => &zero_copy[args.data_offset..(args.data_offset + size)], + None => &zero_copy[args.data_offset..], + }; + let maybe_err = gfx_select!(queue => instance.queue_write_buffer( + queue, + buffer, + args.buffer_offset, + data + )) + .err(); + + Ok(WebGpuResult::maybe_err(maybe_err)) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct QueueWriteTextureArgs { + queue_rid: ResourceId, + destination: super::command_encoder::GpuImageCopyTexture, + data_layout: GpuImageDataLayout, + size: super::texture::GpuExtent3D, +} + +pub fn op_webgpu_write_texture( + state: &mut OpState, + args: QueueWriteTextureArgs, + zero_copy: Option<ZeroCopyBuf>, +) -> Result<WebGpuResult, AnyError> { + let zero_copy = zero_copy.ok_or_else(null_opbuf)?; + let instance = state.borrow::<super::Instance>(); + let texture_resource = state + .resource_table + .get::<super::texture::WebGpuTexture>(args.destination.texture) + .ok_or_else(bad_resource_id)?; + let queue_resource = state + .resource_table + .get::<WebGpuQueue>(args.queue_rid) + .ok_or_else(bad_resource_id)?; + let queue = queue_resource.0; + + let destination = wgpu_core::command::ImageCopyTexture { + texture: texture_resource.0, + mip_level: args.destination.mip_level.unwrap_or(0), + origin: args + .destination + .origin + .map_or(Default::default(), |origin| wgpu_types::Origin3d { + x: origin.x.unwrap_or(0), + y: origin.y.unwrap_or(0), + z: origin.z.unwrap_or(0), + }), + }; + let data_layout = wgpu_types::ImageDataLayout { + offset: args.data_layout.offset.unwrap_or(0), + bytes_per_row: NonZeroU32::new(args.data_layout.bytes_per_row.unwrap_or(0)), + rows_per_image: NonZeroU32::new( + args.data_layout.rows_per_image.unwrap_or(0), + ), + }; + + gfx_ok!(queue => instance.queue_write_texture( + queue, + &destination, + &*zero_copy, + &data_layout, + &wgpu_types::Extent3d { + width: args.size.width.unwrap_or(1), + height: args.size.height.unwrap_or(1), + depth_or_array_layers: args.size.depth_or_array_layers.unwrap_or(1), + } + )) +} diff --git a/ext/webgpu/render_pass.rs b/ext/webgpu/render_pass.rs new file mode 100644 index 000000000..81f2e6640 --- /dev/null +++ b/ext/webgpu/render_pass.rs @@ -0,0 +1,688 @@ +// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. + +use deno_core::error::bad_resource_id; +use deno_core::error::null_opbuf; +use deno_core::error::type_error; +use deno_core::error::AnyError; +use deno_core::ResourceId; +use deno_core::ZeroCopyBuf; +use deno_core::{OpState, Resource}; +use serde::Deserialize; +use std::borrow::Cow; +use std::cell::RefCell; + +use super::error::WebGpuResult; + +pub(crate) struct WebGpuRenderPass( + pub(crate) RefCell<wgpu_core::command::RenderPass>, +); +impl Resource for WebGpuRenderPass { + fn name(&self) -> Cow<str> { + "webGPURenderPass".into() + } +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderPassSetViewportArgs { + render_pass_rid: ResourceId, + x: f32, + y: f32, + width: f32, + height: f32, + min_depth: f32, + max_depth: f32, +} + +pub fn op_webgpu_render_pass_set_viewport( + state: &mut OpState, + args: RenderPassSetViewportArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let render_pass_resource = state + .resource_table + .get::<WebGpuRenderPass>(args.render_pass_rid) + .ok_or_else(bad_resource_id)?; + + wgpu_core::command::render_ffi::wgpu_render_pass_set_viewport( + &mut render_pass_resource.0.borrow_mut(), + args.x, + args.y, + args.width, + args.height, + args.min_depth, + args.max_depth, + ); + + Ok(WebGpuResult::empty()) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderPassSetScissorRectArgs { + render_pass_rid: ResourceId, + x: u32, + y: u32, + width: u32, + height: u32, +} + +pub fn op_webgpu_render_pass_set_scissor_rect( + state: &mut OpState, + args: RenderPassSetScissorRectArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let render_pass_resource = state + .resource_table + .get::<WebGpuRenderPass>(args.render_pass_rid) + .ok_or_else(bad_resource_id)?; + + wgpu_core::command::render_ffi::wgpu_render_pass_set_scissor_rect( + &mut render_pass_resource.0.borrow_mut(), + args.x, + args.y, + args.width, + args.height, + ); + + Ok(WebGpuResult::empty()) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct GpuColor { + pub r: f64, + pub g: f64, + pub b: f64, + pub a: f64, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderPassSetBlendConstantArgs { + render_pass_rid: ResourceId, + color: GpuColor, +} + +pub fn op_webgpu_render_pass_set_blend_constant( + state: &mut OpState, + args: RenderPassSetBlendConstantArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let render_pass_resource = state + .resource_table + .get::<WebGpuRenderPass>(args.render_pass_rid) + .ok_or_else(bad_resource_id)?; + + wgpu_core::command::render_ffi::wgpu_render_pass_set_blend_constant( + &mut render_pass_resource.0.borrow_mut(), + &wgpu_types::Color { + r: args.color.r, + g: args.color.g, + b: args.color.b, + a: args.color.a, + }, + ); + + Ok(WebGpuResult::empty()) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderPassSetStencilReferenceArgs { + render_pass_rid: ResourceId, + reference: u32, +} + +pub fn op_webgpu_render_pass_set_stencil_reference( + state: &mut OpState, + args: RenderPassSetStencilReferenceArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let render_pass_resource = state + .resource_table + .get::<WebGpuRenderPass>(args.render_pass_rid) + .ok_or_else(bad_resource_id)?; + + wgpu_core::command::render_ffi::wgpu_render_pass_set_stencil_reference( + &mut render_pass_resource.0.borrow_mut(), + args.reference, + ); + + Ok(WebGpuResult::empty()) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderPassBeginPipelineStatisticsQueryArgs { + render_pass_rid: ResourceId, + query_set: u32, + query_index: u32, +} + +pub fn op_webgpu_render_pass_begin_pipeline_statistics_query( + state: &mut OpState, + args: RenderPassBeginPipelineStatisticsQueryArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let render_pass_resource = state + .resource_table + .get::<WebGpuRenderPass>(args.render_pass_rid) + .ok_or_else(bad_resource_id)?; + let query_set_resource = state + .resource_table + .get::<super::WebGpuQuerySet>(args.query_set) + .ok_or_else(bad_resource_id)?; + + wgpu_core::command::render_ffi::wgpu_render_pass_begin_pipeline_statistics_query( + &mut render_pass_resource.0.borrow_mut(), + query_set_resource.0, + args.query_index, + ); + + Ok(WebGpuResult::empty()) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderPassEndPipelineStatisticsQueryArgs { + render_pass_rid: ResourceId, +} + +pub fn op_webgpu_render_pass_end_pipeline_statistics_query( + state: &mut OpState, + args: RenderPassEndPipelineStatisticsQueryArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let render_pass_resource = state + .resource_table + .get::<WebGpuRenderPass>(args.render_pass_rid) + .ok_or_else(bad_resource_id)?; + + wgpu_core::command::render_ffi::wgpu_render_pass_end_pipeline_statistics_query( + &mut render_pass_resource.0.borrow_mut(), + ); + + Ok(WebGpuResult::empty()) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderPassWriteTimestampArgs { + render_pass_rid: ResourceId, + query_set: u32, + query_index: u32, +} + +pub fn op_webgpu_render_pass_write_timestamp( + state: &mut OpState, + args: RenderPassWriteTimestampArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let render_pass_resource = state + .resource_table + .get::<WebGpuRenderPass>(args.render_pass_rid) + .ok_or_else(bad_resource_id)?; + let query_set_resource = state + .resource_table + .get::<super::WebGpuQuerySet>(args.query_set) + .ok_or_else(bad_resource_id)?; + + wgpu_core::command::render_ffi::wgpu_render_pass_write_timestamp( + &mut render_pass_resource.0.borrow_mut(), + query_set_resource.0, + args.query_index, + ); + + Ok(WebGpuResult::empty()) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderPassExecuteBundlesArgs { + render_pass_rid: ResourceId, + bundles: Vec<u32>, +} + +pub fn op_webgpu_render_pass_execute_bundles( + state: &mut OpState, + args: RenderPassExecuteBundlesArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let mut render_bundle_ids = vec![]; + + for rid in &args.bundles { + let render_bundle_resource = state + .resource_table + .get::<super::bundle::WebGpuRenderBundle>(*rid) + .ok_or_else(bad_resource_id)?; + render_bundle_ids.push(render_bundle_resource.0); + } + + let render_pass_resource = state + .resource_table + .get::<WebGpuRenderPass>(args.render_pass_rid) + .ok_or_else(bad_resource_id)?; + + unsafe { + wgpu_core::command::render_ffi::wgpu_render_pass_execute_bundles( + &mut render_pass_resource.0.borrow_mut(), + render_bundle_ids.as_ptr(), + args.bundles.len(), + ); + } + + Ok(WebGpuResult::empty()) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderPassEndPassArgs { + command_encoder_rid: ResourceId, + render_pass_rid: ResourceId, +} + +pub fn op_webgpu_render_pass_end_pass( + state: &mut OpState, + args: RenderPassEndPassArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let command_encoder_resource = state + .resource_table + .get::<super::command_encoder::WebGpuCommandEncoder>( + args.command_encoder_rid, + ) + .ok_or_else(bad_resource_id)?; + let command_encoder = command_encoder_resource.0; + let render_pass_resource = state + .resource_table + .take::<WebGpuRenderPass>(args.render_pass_rid) + .ok_or_else(bad_resource_id)?; + let render_pass = &render_pass_resource.0.borrow(); + let instance = state.borrow::<super::Instance>(); + + gfx_ok!(command_encoder => instance.command_encoder_run_render_pass(command_encoder, render_pass)) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderPassSetBindGroupArgs { + render_pass_rid: ResourceId, + index: u32, + bind_group: u32, + dynamic_offsets_data: Option<Vec<u32>>, + dynamic_offsets_data_start: usize, + dynamic_offsets_data_length: usize, +} + +pub fn op_webgpu_render_pass_set_bind_group( + state: &mut OpState, + args: RenderPassSetBindGroupArgs, + zero_copy: Option<ZeroCopyBuf>, +) -> Result<WebGpuResult, AnyError> { + let bind_group_resource = state + .resource_table + .get::<super::binding::WebGpuBindGroup>(args.bind_group) + .ok_or_else(bad_resource_id)?; + let render_pass_resource = state + .resource_table + .get::<WebGpuRenderPass>(args.render_pass_rid) + .ok_or_else(bad_resource_id)?; + + // I know this might look like it can be easily deduplicated, but it can not + // be due to the lifetime of the args.dynamic_offsets_data slice. Because we + // need to use a raw pointer here the slice can be freed before the pointer + // is used in wgpu_render_pass_set_bind_group. See + // https://matrix.to/#/!XFRnMvAfptAHthwBCx:matrix.org/$HgrlhD-Me1DwsGb8UdMu2Hqubgks8s7ILwWRwigOUAg + match args.dynamic_offsets_data { + Some(data) => unsafe { + wgpu_core::command::render_ffi::wgpu_render_pass_set_bind_group( + &mut render_pass_resource.0.borrow_mut(), + args.index, + bind_group_resource.0, + data.as_slice().as_ptr(), + args.dynamic_offsets_data_length, + ); + }, + None => { + let zero_copy = zero_copy.ok_or_else(null_opbuf)?; + let (prefix, data, suffix) = unsafe { zero_copy.align_to::<u32>() }; + assert!(prefix.is_empty()); + assert!(suffix.is_empty()); + unsafe { + wgpu_core::command::render_ffi::wgpu_render_pass_set_bind_group( + &mut render_pass_resource.0.borrow_mut(), + args.index, + bind_group_resource.0, + data[args.dynamic_offsets_data_start..].as_ptr(), + args.dynamic_offsets_data_length, + ); + } + } + }; + + Ok(WebGpuResult::empty()) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderPassPushDebugGroupArgs { + render_pass_rid: ResourceId, + group_label: String, +} + +pub fn op_webgpu_render_pass_push_debug_group( + state: &mut OpState, + args: RenderPassPushDebugGroupArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let render_pass_resource = state + .resource_table + .get::<WebGpuRenderPass>(args.render_pass_rid) + .ok_or_else(bad_resource_id)?; + + unsafe { + let label = std::ffi::CString::new(args.group_label).unwrap(); + wgpu_core::command::render_ffi::wgpu_render_pass_push_debug_group( + &mut render_pass_resource.0.borrow_mut(), + label.as_ptr(), + 0, // wgpu#975 + ); + } + + Ok(WebGpuResult::empty()) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderPassPopDebugGroupArgs { + render_pass_rid: ResourceId, +} + +pub fn op_webgpu_render_pass_pop_debug_group( + state: &mut OpState, + args: RenderPassPopDebugGroupArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let render_pass_resource = state + .resource_table + .get::<WebGpuRenderPass>(args.render_pass_rid) + .ok_or_else(bad_resource_id)?; + + wgpu_core::command::render_ffi::wgpu_render_pass_pop_debug_group( + &mut render_pass_resource.0.borrow_mut(), + ); + + Ok(WebGpuResult::empty()) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderPassInsertDebugMarkerArgs { + render_pass_rid: ResourceId, + marker_label: String, +} + +pub fn op_webgpu_render_pass_insert_debug_marker( + state: &mut OpState, + args: RenderPassInsertDebugMarkerArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let render_pass_resource = state + .resource_table + .get::<WebGpuRenderPass>(args.render_pass_rid) + .ok_or_else(bad_resource_id)?; + + unsafe { + let label = std::ffi::CString::new(args.marker_label).unwrap(); + wgpu_core::command::render_ffi::wgpu_render_pass_insert_debug_marker( + &mut render_pass_resource.0.borrow_mut(), + label.as_ptr(), + 0, // wgpu#975 + ); + } + + Ok(WebGpuResult::empty()) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderPassSetPipelineArgs { + render_pass_rid: ResourceId, + pipeline: u32, +} + +pub fn op_webgpu_render_pass_set_pipeline( + state: &mut OpState, + args: RenderPassSetPipelineArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let render_pipeline_resource = state + .resource_table + .get::<super::pipeline::WebGpuRenderPipeline>(args.pipeline) + .ok_or_else(bad_resource_id)?; + let render_pass_resource = state + .resource_table + .get::<WebGpuRenderPass>(args.render_pass_rid) + .ok_or_else(bad_resource_id)?; + + wgpu_core::command::render_ffi::wgpu_render_pass_set_pipeline( + &mut render_pass_resource.0.borrow_mut(), + render_pipeline_resource.0, + ); + + Ok(WebGpuResult::empty()) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderPassSetIndexBufferArgs { + render_pass_rid: ResourceId, + buffer: u32, + index_format: String, + offset: u64, + size: Option<u64>, +} + +pub fn op_webgpu_render_pass_set_index_buffer( + state: &mut OpState, + args: RenderPassSetIndexBufferArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let buffer_resource = state + .resource_table + .get::<super::buffer::WebGpuBuffer>(args.buffer) + .ok_or_else(bad_resource_id)?; + let render_pass_resource = state + .resource_table + .get::<WebGpuRenderPass>(args.render_pass_rid) + .ok_or_else(bad_resource_id)?; + + let size = if let Some(size) = args.size { + Some( + std::num::NonZeroU64::new(size) + .ok_or_else(|| type_error("size must be larger than 0"))?, + ) + } else { + None + }; + + render_pass_resource.0.borrow_mut().set_index_buffer( + buffer_resource.0, + super::pipeline::serialize_index_format(args.index_format), + args.offset, + size, + ); + + Ok(WebGpuResult::empty()) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderPassSetVertexBufferArgs { + render_pass_rid: ResourceId, + slot: u32, + buffer: u32, + offset: u64, + size: Option<u64>, +} + +pub fn op_webgpu_render_pass_set_vertex_buffer( + state: &mut OpState, + args: RenderPassSetVertexBufferArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let buffer_resource = state + .resource_table + .get::<super::buffer::WebGpuBuffer>(args.buffer) + .ok_or_else(bad_resource_id)?; + let render_pass_resource = state + .resource_table + .get::<WebGpuRenderPass>(args.render_pass_rid) + .ok_or_else(bad_resource_id)?; + + let size = if let Some(size) = args.size { + Some( + std::num::NonZeroU64::new(size) + .ok_or_else(|| type_error("size must be larger than 0"))?, + ) + } else { + None + }; + + wgpu_core::command::render_ffi::wgpu_render_pass_set_vertex_buffer( + &mut render_pass_resource.0.borrow_mut(), + args.slot, + buffer_resource.0, + args.offset, + size, + ); + + Ok(WebGpuResult::empty()) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderPassDrawArgs { + render_pass_rid: ResourceId, + vertex_count: u32, + instance_count: u32, + first_vertex: u32, + first_instance: u32, +} + +pub fn op_webgpu_render_pass_draw( + state: &mut OpState, + args: RenderPassDrawArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let render_pass_resource = state + .resource_table + .get::<WebGpuRenderPass>(args.render_pass_rid) + .ok_or_else(bad_resource_id)?; + + wgpu_core::command::render_ffi::wgpu_render_pass_draw( + &mut render_pass_resource.0.borrow_mut(), + args.vertex_count, + args.instance_count, + args.first_vertex, + args.first_instance, + ); + + Ok(WebGpuResult::empty()) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderPassDrawIndexedArgs { + render_pass_rid: ResourceId, + index_count: u32, + instance_count: u32, + first_index: u32, + base_vertex: i32, + first_instance: u32, +} + +pub fn op_webgpu_render_pass_draw_indexed( + state: &mut OpState, + args: RenderPassDrawIndexedArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let render_pass_resource = state + .resource_table + .get::<WebGpuRenderPass>(args.render_pass_rid) + .ok_or_else(bad_resource_id)?; + + wgpu_core::command::render_ffi::wgpu_render_pass_draw_indexed( + &mut render_pass_resource.0.borrow_mut(), + args.index_count, + args.instance_count, + args.first_index, + args.base_vertex, + args.first_instance, + ); + + Ok(WebGpuResult::empty()) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderPassDrawIndirectArgs { + render_pass_rid: ResourceId, + indirect_buffer: u32, + indirect_offset: u64, +} + +pub fn op_webgpu_render_pass_draw_indirect( + state: &mut OpState, + args: RenderPassDrawIndirectArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let buffer_resource = state + .resource_table + .get::<super::buffer::WebGpuBuffer>(args.indirect_buffer) + .ok_or_else(bad_resource_id)?; + let render_pass_resource = state + .resource_table + .get::<WebGpuRenderPass>(args.render_pass_rid) + .ok_or_else(bad_resource_id)?; + + wgpu_core::command::render_ffi::wgpu_render_pass_draw_indirect( + &mut render_pass_resource.0.borrow_mut(), + buffer_resource.0, + args.indirect_offset, + ); + + Ok(WebGpuResult::empty()) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderPassDrawIndexedIndirectArgs { + render_pass_rid: ResourceId, + indirect_buffer: u32, + indirect_offset: u64, +} + +pub fn op_webgpu_render_pass_draw_indexed_indirect( + state: &mut OpState, + args: RenderPassDrawIndexedIndirectArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let buffer_resource = state + .resource_table + .get::<super::buffer::WebGpuBuffer>(args.indirect_buffer) + .ok_or_else(bad_resource_id)?; + let render_pass_resource = state + .resource_table + .get::<WebGpuRenderPass>(args.render_pass_rid) + .ok_or_else(bad_resource_id)?; + + wgpu_core::command::render_ffi::wgpu_render_pass_draw_indexed_indirect( + &mut render_pass_resource.0.borrow_mut(), + buffer_resource.0, + args.indirect_offset, + ); + + Ok(WebGpuResult::empty()) +} diff --git a/ext/webgpu/sampler.rs b/ext/webgpu/sampler.rs new file mode 100644 index 000000000..7921c4151 --- /dev/null +++ b/ext/webgpu/sampler.rs @@ -0,0 +1,120 @@ +// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. + +use deno_core::error::bad_resource_id; +use deno_core::error::AnyError; +use deno_core::ResourceId; +use deno_core::{OpState, Resource}; +use serde::Deserialize; +use std::borrow::Cow; + +use super::error::WebGpuResult; + +pub(crate) struct WebGpuSampler(pub(crate) wgpu_core::id::SamplerId); +impl Resource for WebGpuSampler { + fn name(&self) -> Cow<str> { + "webGPUSampler".into() + } +} + +fn serialize_address_mode( + address_mode: Option<String>, +) -> wgpu_types::AddressMode { + match address_mode { + Some(address_mode) => match address_mode.as_str() { + "clamp-to-edge" => wgpu_types::AddressMode::ClampToEdge, + "repeat" => wgpu_types::AddressMode::Repeat, + "mirror-repeat" => wgpu_types::AddressMode::MirrorRepeat, + _ => unreachable!(), + }, + None => wgpu_types::AddressMode::ClampToEdge, + } +} + +fn serialize_filter_mode( + filter_mode: Option<String>, +) -> wgpu_types::FilterMode { + match filter_mode { + Some(filter_mode) => match filter_mode.as_str() { + "nearest" => wgpu_types::FilterMode::Nearest, + "linear" => wgpu_types::FilterMode::Linear, + _ => unreachable!(), + }, + None => wgpu_types::FilterMode::Nearest, + } +} + +pub fn serialize_compare_function( + compare: &str, +) -> wgpu_types::CompareFunction { + match compare { + "never" => wgpu_types::CompareFunction::Never, + "less" => wgpu_types::CompareFunction::Less, + "equal" => wgpu_types::CompareFunction::Equal, + "less-equal" => wgpu_types::CompareFunction::LessEqual, + "greater" => wgpu_types::CompareFunction::Greater, + "not-equal" => wgpu_types::CompareFunction::NotEqual, + "greater-equal" => wgpu_types::CompareFunction::GreaterEqual, + "always" => wgpu_types::CompareFunction::Always, + _ => unreachable!(), + } +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CreateSamplerArgs { + device_rid: ResourceId, + label: Option<String>, + address_mode_u: Option<String>, + address_mode_v: Option<String>, + address_mode_w: Option<String>, + mag_filter: Option<String>, + min_filter: Option<String>, + mipmap_filter: Option<String>, + lod_min_clamp: Option<f32>, + lod_max_clamp: Option<f32>, + compare: Option<String>, + max_anisotropy: Option<u8>, +} + +pub fn op_webgpu_create_sampler( + state: &mut OpState, + args: CreateSamplerArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let instance = state.borrow::<super::Instance>(); + let device_resource = state + .resource_table + .get::<super::WebGpuDevice>(args.device_rid) + .ok_or_else(bad_resource_id)?; + let device = device_resource.0; + + let descriptor = wgpu_core::resource::SamplerDescriptor { + label: args.label.map(Cow::from), + address_modes: [ + serialize_address_mode(args.address_mode_u), + serialize_address_mode(args.address_mode_v), + serialize_address_mode(args.address_mode_w), + ], + mag_filter: serialize_filter_mode(args.mag_filter), + min_filter: serialize_filter_mode(args.min_filter), + mipmap_filter: serialize_filter_mode(args.mipmap_filter), + lod_min_clamp: args.lod_min_clamp.unwrap_or(0.0), + lod_max_clamp: args.lod_max_clamp.unwrap_or( + wgpu_core::resource::SamplerDescriptor::default().lod_max_clamp, + ), + compare: args + .compare + .as_ref() + .map(|compare| serialize_compare_function(compare)), + anisotropy_clamp: std::num::NonZeroU8::new( + args.max_anisotropy.unwrap_or(0), + ), + border_color: None, // native-only + }; + + gfx_put!(device => instance.device_create_sampler( + device, + &descriptor, + std::marker::PhantomData + ) => state, WebGpuSampler) +} diff --git a/ext/webgpu/shader.rs b/ext/webgpu/shader.rs new file mode 100644 index 000000000..f48411969 --- /dev/null +++ b/ext/webgpu/shader.rs @@ -0,0 +1,70 @@ +// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. + +use deno_core::error::bad_resource_id; +use deno_core::error::null_opbuf; +use deno_core::error::AnyError; +use deno_core::ResourceId; +use deno_core::ZeroCopyBuf; +use deno_core::{OpState, Resource}; +use serde::Deserialize; +use std::borrow::Cow; + +use super::error::WebGpuResult; + +pub(crate) struct WebGpuShaderModule(pub(crate) wgpu_core::id::ShaderModuleId); +impl Resource for WebGpuShaderModule { + fn name(&self) -> Cow<str> { + "webGPUShaderModule".into() + } +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CreateShaderModuleArgs { + device_rid: ResourceId, + label: Option<String>, + code: Option<String>, + _source_map: Option<()>, // not yet implemented +} + +pub fn op_webgpu_create_shader_module( + state: &mut OpState, + args: CreateShaderModuleArgs, + zero_copy: Option<ZeroCopyBuf>, +) -> Result<WebGpuResult, AnyError> { + let instance = state.borrow::<super::Instance>(); + let device_resource = state + .resource_table + .get::<super::WebGpuDevice>(args.device_rid) + .ok_or_else(bad_resource_id)?; + let device = device_resource.0; + + let source = match args.code { + Some(code) => { + wgpu_core::pipeline::ShaderModuleSource::Wgsl(Cow::from(code)) + } + None => wgpu_core::pipeline::ShaderModuleSource::SpirV(Cow::from(unsafe { + match &zero_copy { + Some(zero_copy) => { + let (prefix, data, suffix) = zero_copy.align_to::<u32>(); + assert!(prefix.is_empty()); + assert!(suffix.is_empty()); + data + } + None => return Err(null_opbuf()), + } + })), + }; + + let descriptor = wgpu_core::pipeline::ShaderModuleDescriptor { + label: args.label.map(Cow::from), + flags: wgpu_types::ShaderFlags::all(), + }; + + gfx_put!(device => instance.device_create_shader_module( + device, + &descriptor, + source, + std::marker::PhantomData + ) => state, WebGpuShaderModule) +} diff --git a/ext/webgpu/texture.rs b/ext/webgpu/texture.rs new file mode 100644 index 000000000..587ac46a7 --- /dev/null +++ b/ext/webgpu/texture.rs @@ -0,0 +1,244 @@ +// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. + +use deno_core::error::AnyError; +use deno_core::error::{bad_resource_id, not_supported}; +use deno_core::ResourceId; +use deno_core::{OpState, Resource}; +use serde::Deserialize; +use std::borrow::Cow; + +use super::error::WebGpuResult; +pub(crate) struct WebGpuTexture(pub(crate) wgpu_core::id::TextureId); +impl Resource for WebGpuTexture { + fn name(&self) -> Cow<str> { + "webGPUTexture".into() + } +} + +pub(crate) struct WebGpuTextureView(pub(crate) wgpu_core::id::TextureViewId); +impl Resource for WebGpuTextureView { + fn name(&self) -> Cow<str> { + "webGPUTextureView".into() + } +} + +pub fn serialize_texture_format( + format: &str, +) -> Result<wgpu_types::TextureFormat, AnyError> { + Ok(match format { + // 8-bit formats + "r8unorm" => wgpu_types::TextureFormat::R8Unorm, + "r8snorm" => wgpu_types::TextureFormat::R8Snorm, + "r8uint" => wgpu_types::TextureFormat::R8Uint, + "r8sint" => wgpu_types::TextureFormat::R8Sint, + + // 16-bit formats + "r16uint" => wgpu_types::TextureFormat::R16Uint, + "r16sint" => wgpu_types::TextureFormat::R16Sint, + "r16float" => wgpu_types::TextureFormat::R16Float, + "rg8unorm" => wgpu_types::TextureFormat::Rg8Unorm, + "rg8snorm" => wgpu_types::TextureFormat::Rg8Snorm, + "rg8uint" => wgpu_types::TextureFormat::Rg8Uint, + "rg8sint" => wgpu_types::TextureFormat::Rg8Sint, + + // 32-bit formats + "r32uint" => wgpu_types::TextureFormat::R32Uint, + "r32sint" => wgpu_types::TextureFormat::R32Sint, + "r32float" => wgpu_types::TextureFormat::R32Float, + "rg16uint" => wgpu_types::TextureFormat::Rg16Uint, + "rg16sint" => wgpu_types::TextureFormat::Rg16Sint, + "rg16float" => wgpu_types::TextureFormat::Rg16Float, + "rgba8unorm" => wgpu_types::TextureFormat::Rgba8Unorm, + "rgba8unorm-srgb" => wgpu_types::TextureFormat::Rgba8UnormSrgb, + "rgba8snorm" => wgpu_types::TextureFormat::Rgba8Snorm, + "rgba8uint" => wgpu_types::TextureFormat::Rgba8Uint, + "rgba8sint" => wgpu_types::TextureFormat::Rgba8Sint, + "bgra8unorm" => wgpu_types::TextureFormat::Bgra8Unorm, + "bgra8unorm-srgb" => wgpu_types::TextureFormat::Bgra8UnormSrgb, + // Packed 32-bit formats + "rgb9e5ufloat" => return Err(not_supported()), // wgpu#967 + "rgb10a2unorm" => wgpu_types::TextureFormat::Rgb10a2Unorm, + "rg11b10ufloat" => wgpu_types::TextureFormat::Rg11b10Float, + + // 64-bit formats + "rg32uint" => wgpu_types::TextureFormat::Rg32Uint, + "rg32sint" => wgpu_types::TextureFormat::Rg32Sint, + "rg32float" => wgpu_types::TextureFormat::Rg32Float, + "rgba16uint" => wgpu_types::TextureFormat::Rgba16Uint, + "rgba16sint" => wgpu_types::TextureFormat::Rgba16Sint, + "rgba16float" => wgpu_types::TextureFormat::Rgba16Float, + + // 128-bit formats + "rgba32uint" => wgpu_types::TextureFormat::Rgba32Uint, + "rgba32sint" => wgpu_types::TextureFormat::Rgba32Sint, + "rgba32float" => wgpu_types::TextureFormat::Rgba32Float, + + // Depth and stencil formats + "stencil8" => return Err(not_supported()), // wgpu#967 + "depth16unorm" => return Err(not_supported()), // wgpu#967 + "depth24plus" => wgpu_types::TextureFormat::Depth24Plus, + "depth24plus-stencil8" => wgpu_types::TextureFormat::Depth24PlusStencil8, + "depth32float" => wgpu_types::TextureFormat::Depth32Float, + + // BC compressed formats usable if "texture-compression-bc" is both + // supported by the device/user agent and enabled in requestDevice. + "bc1-rgba-unorm" => wgpu_types::TextureFormat::Bc1RgbaUnorm, + "bc1-rgba-unorm-srgb" => wgpu_types::TextureFormat::Bc1RgbaUnormSrgb, + "bc2-rgba-unorm" => wgpu_types::TextureFormat::Bc2RgbaUnorm, + "bc2-rgba-unorm-srgb" => wgpu_types::TextureFormat::Bc2RgbaUnormSrgb, + "bc3-rgba-unorm" => wgpu_types::TextureFormat::Bc3RgbaUnorm, + "bc3-rgba-unorm-srgb" => wgpu_types::TextureFormat::Bc3RgbaUnormSrgb, + "bc4-r-unorm" => wgpu_types::TextureFormat::Bc4RUnorm, + "bc4-r-snorm" => wgpu_types::TextureFormat::Bc4RSnorm, + "bc5-rg-unorm" => wgpu_types::TextureFormat::Bc5RgUnorm, + "bc5-rg-snorm" => wgpu_types::TextureFormat::Bc5RgSnorm, + "bc6h-rgb-ufloat" => wgpu_types::TextureFormat::Bc6hRgbUfloat, + "bc6h-rgb-float" => wgpu_types::TextureFormat::Bc6hRgbSfloat, // wgpu#967 + "bc7-rgba-unorm" => wgpu_types::TextureFormat::Bc7RgbaUnorm, + "bc7-rgba-unorm-srgb" => wgpu_types::TextureFormat::Bc7RgbaUnormSrgb, + + // "depth24unorm-stencil8" extension + "depth24unorm-stencil8" => return Err(not_supported()), // wgpu#967 + + // "depth32float-stencil8" extension + "depth32float-stencil8" => return Err(not_supported()), // wgpu#967 + _ => unreachable!(), + }) +} + +pub fn serialize_dimension( + dimension: &str, +) -> wgpu_types::TextureViewDimension { + match dimension { + "1d" => wgpu_types::TextureViewDimension::D1, + "2d" => wgpu_types::TextureViewDimension::D2, + "2d-array" => wgpu_types::TextureViewDimension::D2Array, + "cube" => wgpu_types::TextureViewDimension::Cube, + "cube-array" => wgpu_types::TextureViewDimension::CubeArray, + "3d" => wgpu_types::TextureViewDimension::D3, + _ => unreachable!(), + } +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct GpuExtent3D { + pub width: Option<u32>, + pub height: Option<u32>, + pub depth_or_array_layers: Option<u32>, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CreateTextureArgs { + device_rid: ResourceId, + label: Option<String>, + size: GpuExtent3D, + mip_level_count: Option<u32>, + sample_count: Option<u32>, + dimension: Option<String>, + format: String, + usage: u32, +} + +pub fn op_webgpu_create_texture( + state: &mut OpState, + args: CreateTextureArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let instance = state.borrow::<super::Instance>(); + let device_resource = state + .resource_table + .get::<super::WebGpuDevice>(args.device_rid) + .ok_or_else(bad_resource_id)?; + let device = device_resource.0; + + let descriptor = wgpu_core::resource::TextureDescriptor { + label: args.label.map(Cow::from), + size: wgpu_types::Extent3d { + width: args.size.width.unwrap_or(1), + height: args.size.height.unwrap_or(1), + depth_or_array_layers: args.size.depth_or_array_layers.unwrap_or(1), + }, + mip_level_count: args.mip_level_count.unwrap_or(1), + sample_count: args.sample_count.unwrap_or(1), + dimension: match args.dimension { + Some(dimension) => match dimension.as_str() { + "1d" => wgpu_types::TextureDimension::D1, + "2d" => wgpu_types::TextureDimension::D2, + "3d" => wgpu_types::TextureDimension::D3, + _ => unreachable!(), + }, + None => wgpu_types::TextureDimension::D2, + }, + format: serialize_texture_format(&args.format)?, + usage: wgpu_types::TextureUsage::from_bits(args.usage).unwrap(), + }; + + gfx_put!(device => instance.device_create_texture( + device, + &descriptor, + std::marker::PhantomData + ) => state, WebGpuTexture) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CreateTextureViewArgs { + texture_rid: ResourceId, + label: Option<String>, + format: Option<String>, + dimension: Option<String>, + aspect: Option<String>, + base_mip_level: Option<u32>, + mip_level_count: Option<u32>, + base_array_layer: Option<u32>, + array_layer_count: Option<u32>, +} + +pub fn op_webgpu_create_texture_view( + state: &mut OpState, + args: CreateTextureViewArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let instance = state.borrow::<super::Instance>(); + let texture_resource = state + .resource_table + .get::<WebGpuTexture>(args.texture_rid) + .ok_or_else(bad_resource_id)?; + let texture = texture_resource.0; + + let descriptor = wgpu_core::resource::TextureViewDescriptor { + label: args.label.map(Cow::from), + format: args + .format + .map(|s| serialize_texture_format(&s)) + .transpose()?, + dimension: args.dimension.map(|s| serialize_dimension(&s)), + range: wgpu_types::ImageSubresourceRange { + aspect: match args.aspect { + Some(aspect) => match aspect.as_str() { + "all" => wgpu_types::TextureAspect::All, + "stencil-only" => wgpu_types::TextureAspect::StencilOnly, + "depth-only" => wgpu_types::TextureAspect::DepthOnly, + _ => unreachable!(), + }, + None => wgpu_types::TextureAspect::All, + }, + base_mip_level: args.base_mip_level.unwrap_or(0), + mip_level_count: std::num::NonZeroU32::new( + args.mip_level_count.unwrap_or(0), + ), + base_array_layer: args.base_array_layer.unwrap_or(0), + array_layer_count: std::num::NonZeroU32::new( + args.array_layer_count.unwrap_or(0), + ), + }, + }; + + gfx_put!(texture => instance.texture_create_view( + texture, + &descriptor, + std::marker::PhantomData + ) => state, WebGpuTextureView) +} diff --git a/ext/webgpu/webgpu.idl b/ext/webgpu/webgpu.idl new file mode 100644 index 000000000..b2a536f2f --- /dev/null +++ b/ext/webgpu/webgpu.idl @@ -0,0 +1,1057 @@ +interface mixin GPUObjectBase { + attribute USVString? label; +}; + +dictionary GPUObjectDescriptorBase { + USVString label; +}; + +[Exposed=Window] +interface GPUSupportedLimits { + readonly attribute unsigned long maxTextureDimension1D; + readonly attribute unsigned long maxTextureDimension2D; + readonly attribute unsigned long maxTextureDimension3D; + readonly attribute unsigned long maxTextureArrayLayers; + readonly attribute unsigned long maxBindGroups; + readonly attribute unsigned long maxDynamicUniformBuffersPerPipelineLayout; + readonly attribute unsigned long maxDynamicStorageBuffersPerPipelineLayout; + readonly attribute unsigned long maxSampledTexturesPerShaderStage; + readonly attribute unsigned long maxSamplersPerShaderStage; + readonly attribute unsigned long maxStorageBuffersPerShaderStage; + readonly attribute unsigned long maxStorageTexturesPerShaderStage; + readonly attribute unsigned long maxUniformBuffersPerShaderStage; + readonly attribute unsigned long maxUniformBufferBindingSize; + readonly attribute unsigned long maxStorageBufferBindingSize; + readonly attribute unsigned long minUniformBufferOffsetAlignment; + readonly attribute unsigned long minStorageBufferOffsetAlignment; + readonly attribute unsigned long maxVertexBuffers; + readonly attribute unsigned long maxVertexAttributes; + readonly attribute unsigned long maxVertexBufferArrayStride; + readonly attribute unsigned long maxInterStageShaderComponents; + readonly attribute unsigned long maxComputeWorkgroupStorageSize; + readonly attribute unsigned long maxComputeWorkgroupInvocations; + readonly attribute unsigned long maxComputePerDimensionDispatchSize; +}; + +[Exposed=Window] +interface GPUSupportedFeatures { + readonly setlike<DOMString>; +}; + +enum GPUPredefinedColorSpace { + "srgb", +}; + +interface mixin NavigatorGPU { + [SameObject] readonly attribute GPU gpu; +}; +Navigator includes NavigatorGPU; +WorkerNavigator includes NavigatorGPU; + +[Exposed=(Window, DedicatedWorker)] +interface GPU { + Promise<GPUAdapter?> requestAdapter(optional GPURequestAdapterOptions options = {}); +}; + +dictionary GPURequestAdapterOptions { + GPUPowerPreference powerPreference; + boolean forceSoftware = false; +}; + +enum GPUPowerPreference { + "low-power", + "high-performance" +}; + +[Exposed=Window] +interface GPUAdapter { + readonly attribute DOMString name; + [SameObject] readonly attribute GPUSupportedFeatures features; + [SameObject] readonly attribute GPUSupportedLimits limits; + readonly attribute boolean isSoftware; + + Promise<GPUDevice> requestDevice(optional GPUDeviceDescriptor descriptor = {}); +}; + +dictionary GPUDeviceDescriptor : GPUObjectDescriptorBase { + sequence<GPUFeatureName> requiredFeatures = []; + record<DOMString, GPUSize32> requiredLimits = {}; +}; + +enum GPUFeatureName { + "depth-clamping", + "depth24unorm-stencil8", + "depth32float-stencil8", + "pipeline-statistics-query", + "texture-compression-bc", + "timestamp-query", +}; + +[Exposed=(Window, DedicatedWorker), Serializable] +interface GPUDevice : EventTarget { + [SameObject] readonly attribute GPUSupportedFeatures features; + [SameObject] readonly attribute GPUSupportedLimits limits; + + [SameObject] readonly attribute GPUQueue queue; + + undefined destroy(); + + GPUBuffer createBuffer(GPUBufferDescriptor descriptor); + GPUTexture createTexture(GPUTextureDescriptor descriptor); + GPUSampler createSampler(optional GPUSamplerDescriptor descriptor = {}); + + GPUBindGroupLayout createBindGroupLayout(GPUBindGroupLayoutDescriptor descriptor); + GPUPipelineLayout createPipelineLayout(GPUPipelineLayoutDescriptor descriptor); + GPUBindGroup createBindGroup(GPUBindGroupDescriptor descriptor); + + GPUShaderModule createShaderModule(GPUShaderModuleDescriptor descriptor); + GPUComputePipeline createComputePipeline(GPUComputePipelineDescriptor descriptor); + GPURenderPipeline createRenderPipeline(GPURenderPipelineDescriptor descriptor); + Promise<GPUComputePipeline> createComputePipelineAsync(GPUComputePipelineDescriptor descriptor); + Promise<GPURenderPipeline> createRenderPipelineAsync(GPURenderPipelineDescriptor descriptor); + + GPUCommandEncoder createCommandEncoder(optional GPUCommandEncoderDescriptor descriptor = {}); + GPURenderBundleEncoder createRenderBundleEncoder(GPURenderBundleEncoderDescriptor descriptor); + + GPUQuerySet createQuerySet(GPUQuerySetDescriptor descriptor); +}; +GPUDevice includes GPUObjectBase; + +[Exposed=Window, Serializable] +interface GPUBuffer { + Promise<undefined> mapAsync(GPUMapModeFlags mode, optional GPUSize64 offset = 0, optional GPUSize64 size); + ArrayBuffer getMappedRange(optional GPUSize64 offset = 0, optional GPUSize64 size); + undefined unmap(); + + undefined destroy(); +}; +GPUBuffer includes GPUObjectBase; + +dictionary GPUBufferDescriptor : GPUObjectDescriptorBase { + required GPUSize64 size; + required GPUBufferUsageFlags usage; + boolean mappedAtCreation = false; +}; + +typedef [EnforceRange] unsigned long GPUBufferUsageFlags; +[Exposed=Window] +interface GPUBufferUsage { + const GPUFlagsConstant MAP_READ = 0x0001; + const GPUFlagsConstant MAP_WRITE = 0x0002; + const GPUFlagsConstant COPY_SRC = 0x0004; + const GPUFlagsConstant COPY_DST = 0x0008; + const GPUFlagsConstant INDEX = 0x0010; + const GPUFlagsConstant VERTEX = 0x0020; + const GPUFlagsConstant UNIFORM = 0x0040; + const GPUFlagsConstant STORAGE = 0x0080; + const GPUFlagsConstant INDIRECT = 0x0100; + const GPUFlagsConstant QUERY_RESOLVE = 0x0200; +}; + +typedef [EnforceRange] unsigned long GPUMapModeFlags; +[Exposed=Window] +interface GPUMapMode { + const GPUFlagsConstant READ = 0x0001; + const GPUFlagsConstant WRITE = 0x0002; +}; + +[Exposed=Window, Serializable] +interface GPUTexture { + GPUTextureView createView(optional GPUTextureViewDescriptor descriptor = {}); + + undefined destroy(); +}; +GPUTexture includes GPUObjectBase; + +dictionary GPUTextureDescriptor : GPUObjectDescriptorBase { + required GPUExtent3D size; + GPUIntegerCoordinate mipLevelCount = 1; + GPUSize32 sampleCount = 1; + GPUTextureDimension dimension = "2d"; + required GPUTextureFormat format; + required GPUTextureUsageFlags usage; +}; + +enum GPUTextureDimension { + "1d", + "2d", + "3d", +}; + +typedef [EnforceRange] unsigned long GPUTextureUsageFlags; +[Exposed=Window] +interface GPUTextureUsage { + const GPUFlagsConstant COPY_SRC = 0x01; + const GPUFlagsConstant COPY_DST = 0x02; + const GPUFlagsConstant SAMPLED = 0x04; + const GPUFlagsConstant STORAGE = 0x08; + const GPUFlagsConstant RENDER_ATTACHMENT = 0x10; +}; + +[Exposed=Window] +interface GPUTextureView { +}; +GPUTextureView includes GPUObjectBase; + +dictionary GPUTextureViewDescriptor : GPUObjectDescriptorBase { + GPUTextureFormat format; + GPUTextureViewDimension dimension; + GPUTextureAspect aspect = "all"; + GPUIntegerCoordinate baseMipLevel = 0; + GPUIntegerCoordinate mipLevelCount; + GPUIntegerCoordinate baseArrayLayer = 0; + GPUIntegerCoordinate arrayLayerCount; +}; + +enum GPUTextureViewDimension { + "1d", + "2d", + "2d-array", + "cube", + "cube-array", + "3d" +}; + +enum GPUTextureAspect { + "all", + "stencil-only", + "depth-only" +}; + +enum GPUTextureFormat { + // 8-bit formats + "r8unorm", + "r8snorm", + "r8uint", + "r8sint", + + // 16-bit formats + "r16uint", + "r16sint", + "r16float", + "rg8unorm", + "rg8snorm", + "rg8uint", + "rg8sint", + + // 32-bit formats + "r32uint", + "r32sint", + "r32float", + "rg16uint", + "rg16sint", + "rg16float", + "rgba8unorm", + "rgba8unorm-srgb", + "rgba8snorm", + "rgba8uint", + "rgba8sint", + "bgra8unorm", + "bgra8unorm-srgb", + // Packed 32-bit formats + "rgb9e5ufloat", + "rgb10a2unorm", + "rg11b10ufloat", + + // 64-bit formats + "rg32uint", + "rg32sint", + "rg32float", + "rgba16uint", + "rgba16sint", + "rgba16float", + + // 128-bit formats + "rgba32uint", + "rgba32sint", + "rgba32float", + + // Depth and stencil formats + "stencil8", + "depth16unorm", + "depth24plus", + "depth24plus-stencil8", + "depth32float", + + // BC compressed formats usable if "texture-compression-bc" is both + // supported by the device/user agent and enabled in requestDevice. + "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", + + // "depth24unorm-stencil8" feature + "depth24unorm-stencil8", + + // "depth32float-stencil8" feature + "depth32float-stencil8", +}; + +[Exposed=Window] +interface GPUSampler { +}; +GPUSampler includes GPUObjectBase; + +dictionary GPUSamplerDescriptor : GPUObjectDescriptorBase { + GPUAddressMode addressModeU = "clamp-to-edge"; + GPUAddressMode addressModeV = "clamp-to-edge"; + GPUAddressMode addressModeW = "clamp-to-edge"; + GPUFilterMode magFilter = "nearest"; + GPUFilterMode minFilter = "nearest"; + GPUFilterMode mipmapFilter = "nearest"; + float lodMinClamp = 0; + float lodMaxClamp = 0xffffffff; // TODO: What should this be? Was Number.MAX_VALUE. + GPUCompareFunction compare; + [Clamp] unsigned short maxAnisotropy = 1; +}; + +enum GPUAddressMode { + "clamp-to-edge", + "repeat", + "mirror-repeat" +}; + +enum GPUFilterMode { + "nearest", + "linear" +}; + +enum GPUCompareFunction { + "never", + "less", + "equal", + "less-equal", + "greater", + "not-equal", + "greater-equal", + "always" +}; + +[Exposed=Window, Serializable] +interface GPUBindGroupLayout { +}; +GPUBindGroupLayout includes GPUObjectBase; + +dictionary GPUBindGroupLayoutDescriptor : GPUObjectDescriptorBase { + required sequence<GPUBindGroupLayoutEntry> entries; +}; + +typedef [EnforceRange] unsigned long GPUShaderStageFlags; +[Exposed=Window] +interface GPUShaderStage { + const GPUFlagsConstant VERTEX = 0x1; + const GPUFlagsConstant FRAGMENT = 0x2; + const GPUFlagsConstant COMPUTE = 0x4; +}; + +dictionary GPUBindGroupLayoutEntry { + required GPUIndex32 binding; + required GPUShaderStageFlags visibility; + + GPUBufferBindingLayout buffer; + GPUSamplerBindingLayout sampler; + GPUTextureBindingLayout texture; + GPUStorageTextureBindingLayout storageTexture; +}; + +enum GPUBufferBindingType { + "uniform", + "storage", + "read-only-storage", +}; + +dictionary GPUBufferBindingLayout { + GPUBufferBindingType type = "uniform"; + boolean hasDynamicOffset = false; + GPUSize64 minBindingSize = 0; +}; + +enum GPUSamplerBindingType { + "filtering", + "non-filtering", + "comparison", +}; + +dictionary GPUSamplerBindingLayout { + GPUSamplerBindingType type = "filtering"; +}; + +enum GPUTextureSampleType { + "float", + "unfilterable-float", + "depth", + "sint", + "uint", +}; + +dictionary GPUTextureBindingLayout { + GPUTextureSampleType sampleType = "float"; + GPUTextureViewDimension viewDimension = "2d"; + boolean multisampled = false; +}; + +enum GPUStorageTextureAccess { + "read-only", + "write-only", +}; + +dictionary GPUStorageTextureBindingLayout { + required GPUStorageTextureAccess access; + required GPUTextureFormat format; + GPUTextureViewDimension viewDimension = "2d"; +}; + +[Exposed=Window] +interface GPUBindGroup { +}; +GPUBindGroup includes GPUObjectBase; + +dictionary GPUBindGroupDescriptor : GPUObjectDescriptorBase { + required GPUBindGroupLayout layout; + required sequence<GPUBindGroupEntry> entries; +}; + +typedef (GPUSampler or GPUTextureView or GPUBufferBinding) GPUBindingResource; + +dictionary GPUBindGroupEntry { + required GPUIndex32 binding; + required GPUBindingResource resource; +}; + +dictionary GPUBufferBinding { + required GPUBuffer buffer; + GPUSize64 offset = 0; + GPUSize64 size; +}; + +[Exposed=Window, Serializable] +interface GPUPipelineLayout { +}; +GPUPipelineLayout includes GPUObjectBase; + +dictionary GPUPipelineLayoutDescriptor : GPUObjectDescriptorBase { + required sequence<GPUBindGroupLayout> bindGroupLayouts; +}; + +[Exposed=Window, Serializable] +interface GPUShaderModule { + Promise<GPUCompilationInfo> compilationInfo(); +}; +GPUShaderModule includes GPUObjectBase; + +dictionary GPUShaderModuleDescriptor : GPUObjectDescriptorBase { + required USVString code; + object sourceMap; +}; + +enum GPUCompilationMessageType { + "error", + "warning", + "info" +}; + +[Exposed=Window, Serializable] +interface GPUCompilationMessage { + readonly attribute DOMString message; + readonly attribute GPUCompilationMessageType type; + readonly attribute unsigned long long lineNum; + readonly attribute unsigned long long linePos; + readonly attribute unsigned long long offset; + readonly attribute unsigned long long length; +}; + +[Exposed=Window, Serializable] +interface GPUCompilationInfo { + readonly attribute FrozenArray<GPUCompilationMessage> messages; +}; + +dictionary GPUPipelineDescriptorBase : GPUObjectDescriptorBase { + GPUPipelineLayout layout; +}; + +interface mixin GPUPipelineBase { + GPUBindGroupLayout getBindGroupLayout(unsigned long index); +}; + +dictionary GPUProgrammableStage { + required GPUShaderModule module; + required USVString entryPoint; + record<USVString, GPUPipelineConstantValue> constants; +}; + +typedef double GPUPipelineConstantValue; // May represent WGSL’s bool, f32, i32, u32. + +[Exposed=Window, Serializable] +interface GPUComputePipeline { +}; +GPUComputePipeline includes GPUObjectBase; +GPUComputePipeline includes GPUPipelineBase; + +dictionary GPUComputePipelineDescriptor : GPUPipelineDescriptorBase { + required GPUProgrammableStage compute; +}; + +[Exposed=Window, Serializable] +interface GPURenderPipeline { +}; +GPURenderPipeline includes GPUObjectBase; +GPURenderPipeline includes GPUPipelineBase; + +dictionary GPURenderPipelineDescriptor : GPUPipelineDescriptorBase { + required GPUVertexState vertex; + GPUPrimitiveState primitive = {}; + GPUDepthStencilState depthStencil; + GPUMultisampleState multisample = {}; + GPUFragmentState fragment; +}; + +enum GPUPrimitiveTopology { + "point-list", + "line-list", + "line-strip", + "triangle-list", + "triangle-strip" +}; + +dictionary GPUPrimitiveState { + GPUPrimitiveTopology topology = "triangle-list"; + GPUIndexFormat stripIndexFormat; + GPUFrontFace frontFace = "ccw"; + GPUCullMode cullMode = "none"; + + // Enable depth clamping (requires "depth-clamping" feature) + boolean clampDepth = false; +}; + +enum GPUFrontFace { + "ccw", + "cw" +}; + +enum GPUCullMode { + "none", + "front", + "back" +}; + +dictionary GPUMultisampleState { + GPUSize32 count = 1; + GPUSampleMask mask = 0xFFFFFFFF; + boolean alphaToCoverageEnabled = false; +}; + +dictionary GPUFragmentState: GPUProgrammableStage { + required sequence<GPUColorTargetState> targets; +}; + +dictionary GPUColorTargetState { + required GPUTextureFormat format; + + GPUBlendState blend; + GPUColorWriteFlags writeMask = 0xF; // GPUColorWrite.ALL +}; + +dictionary GPUBlendState { + required GPUBlendComponent color; + required GPUBlendComponent alpha; +}; + +typedef [EnforceRange] unsigned long GPUColorWriteFlags; +[Exposed=Window] +interface GPUColorWrite { + const GPUFlagsConstant RED = 0x1; + const GPUFlagsConstant GREEN = 0x2; + const GPUFlagsConstant BLUE = 0x4; + const GPUFlagsConstant ALPHA = 0x8; + const GPUFlagsConstant ALL = 0xF; +}; + +dictionary GPUBlendComponent { + GPUBlendFactor srcFactor = "one"; + GPUBlendFactor dstFactor = "zero"; + GPUBlendOperation operation = "add"; +}; + +enum GPUBlendFactor { + "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" +}; + +enum GPUBlendOperation { + "add", + "subtract", + "reverse-subtract", + "min", + "max" +}; + +dictionary GPUDepthStencilState { + required GPUTextureFormat format; + + boolean depthWriteEnabled = false; + GPUCompareFunction depthCompare = "always"; + + GPUStencilFaceState stencilFront = {}; + GPUStencilFaceState stencilBack = {}; + + GPUStencilValue stencilReadMask = 0xFFFFFFFF; + GPUStencilValue stencilWriteMask = 0xFFFFFFFF; + + GPUDepthBias depthBias = 0; + float depthBiasSlopeScale = 0; + float depthBiasClamp = 0; +}; + +dictionary GPUStencilFaceState { + GPUCompareFunction compare = "always"; + GPUStencilOperation failOp = "keep"; + GPUStencilOperation depthFailOp = "keep"; + GPUStencilOperation passOp = "keep"; +}; + +enum GPUStencilOperation { + "keep", + "zero", + "replace", + "invert", + "increment-clamp", + "decrement-clamp", + "increment-wrap", + "decrement-wrap" +}; + +enum GPUIndexFormat { + "uint16", + "uint32" +}; + +enum GPUVertexFormat { + "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", +}; + +enum GPUInputStepMode { + "vertex", + "instance" +}; + +dictionary GPUVertexState: GPUProgrammableStage { + sequence<GPUVertexBufferLayout?> buffers = []; +}; + +dictionary GPUVertexBufferLayout { + required GPUSize64 arrayStride; + GPUInputStepMode stepMode = "vertex"; + required sequence<GPUVertexAttribute> attributes; +}; + +dictionary GPUVertexAttribute { + required GPUVertexFormat format; + required GPUSize64 offset; + + required GPUIndex32 shaderLocation; +}; + +[Exposed=Window] +interface GPUCommandBuffer { + readonly attribute Promise<double> executionTime; +}; +GPUCommandBuffer includes GPUObjectBase; + +dictionary GPUCommandBufferDescriptor : GPUObjectDescriptorBase { +}; + +[Exposed=Window] +interface GPUCommandEncoder { + GPURenderPassEncoder beginRenderPass(GPURenderPassDescriptor descriptor); + GPUComputePassEncoder beginComputePass(optional GPUComputePassDescriptor descriptor = {}); + + undefined copyBufferToBuffer( + GPUBuffer source, + GPUSize64 sourceOffset, + GPUBuffer destination, + GPUSize64 destinationOffset, + GPUSize64 size); + + undefined copyBufferToTexture( + GPUImageCopyBuffer source, + GPUImageCopyTexture destination, + GPUExtent3D copySize); + + undefined copyTextureToBuffer( + GPUImageCopyTexture source, + GPUImageCopyBuffer destination, + GPUExtent3D copySize); + + undefined copyTextureToTexture( + GPUImageCopyTexture source, + GPUImageCopyTexture destination, + GPUExtent3D copySize); + + undefined pushDebugGroup(USVString groupLabel); + undefined popDebugGroup(); + undefined insertDebugMarker(USVString markerLabel); + + undefined writeTimestamp(GPUQuerySet querySet, GPUSize32 queryIndex); + + undefined resolveQuerySet( + GPUQuerySet querySet, + GPUSize32 firstQuery, + GPUSize32 queryCount, + GPUBuffer destination, + GPUSize64 destinationOffset); + + GPUCommandBuffer finish(optional GPUCommandBufferDescriptor descriptor = {}); +}; +GPUCommandEncoder includes GPUObjectBase; + +dictionary GPUCommandEncoderDescriptor : GPUObjectDescriptorBase { + boolean measureExecutionTime = false; + + // TODO: reusability flag? +}; + +dictionary GPUImageDataLayout { + GPUSize64 offset = 0; + GPUSize32 bytesPerRow; + GPUSize32 rowsPerImage; +}; + +dictionary GPUImageCopyBuffer : GPUImageDataLayout { + required GPUBuffer buffer; +}; + +dictionary GPUImageCopyTexture { + required GPUTexture texture; + GPUIntegerCoordinate mipLevel = 0; + GPUOrigin3D origin = {}; + GPUTextureAspect aspect = "all"; +}; + +interface mixin GPUProgrammablePassEncoder { + undefined setBindGroup(GPUIndex32 index, GPUBindGroup bindGroup, + optional sequence<GPUBufferDynamicOffset> dynamicOffsets = []); + + undefined setBindGroup(GPUIndex32 index, GPUBindGroup bindGroup, + Uint32Array dynamicOffsetsData, + GPUSize64 dynamicOffsetsDataStart, + GPUSize32 dynamicOffsetsDataLength); + + undefined pushDebugGroup(USVString groupLabel); + undefined popDebugGroup(); + undefined insertDebugMarker(USVString markerLabel); +}; + +[Exposed=Window] +interface GPUComputePassEncoder { + undefined setPipeline(GPUComputePipeline pipeline); + undefined dispatch(GPUSize32 x, optional GPUSize32 y = 1, optional GPUSize32 z = 1); + undefined dispatchIndirect(GPUBuffer indirectBuffer, GPUSize64 indirectOffset); + + undefined beginPipelineStatisticsQuery(GPUQuerySet querySet, GPUSize32 queryIndex); + undefined endPipelineStatisticsQuery(); + + undefined writeTimestamp(GPUQuerySet querySet, GPUSize32 queryIndex); + + undefined endPass(); +}; +GPUComputePassEncoder includes GPUObjectBase; +GPUComputePassEncoder includes GPUProgrammablePassEncoder; + +dictionary GPUComputePassDescriptor : GPUObjectDescriptorBase { +}; + +interface mixin GPURenderEncoderBase { + undefined setPipeline(GPURenderPipeline pipeline); + + undefined setIndexBuffer(GPUBuffer buffer, GPUIndexFormat indexFormat, optional GPUSize64 offset = 0, optional GPUSize64 size); + undefined setVertexBuffer(GPUIndex32 slot, GPUBuffer buffer, optional GPUSize64 offset = 0, optional GPUSize64 size); + + undefined draw(GPUSize32 vertexCount, optional GPUSize32 instanceCount = 1, + optional GPUSize32 firstVertex = 0, optional GPUSize32 firstInstance = 0); + undefined drawIndexed(GPUSize32 indexCount, optional GPUSize32 instanceCount = 1, + optional GPUSize32 firstIndex = 0, + optional GPUSignedOffset32 baseVertex = 0, + optional GPUSize32 firstInstance = 0); + + undefined drawIndirect(GPUBuffer indirectBuffer, GPUSize64 indirectOffset); + undefined drawIndexedIndirect(GPUBuffer indirectBuffer, GPUSize64 indirectOffset); +}; + +[Exposed=Window] +interface GPURenderPassEncoder { + undefined setViewport(float x, float y, + float width, float height, + float minDepth, float maxDepth); + + undefined setScissorRect(GPUIntegerCoordinate x, GPUIntegerCoordinate y, + GPUIntegerCoordinate width, GPUIntegerCoordinate height); + + undefined setBlendConstant(GPUColor color); + undefined setStencilReference(GPUStencilValue reference); + + undefined beginOcclusionQuery(GPUSize32 queryIndex); + undefined endOcclusionQuery(); + + undefined beginPipelineStatisticsQuery(GPUQuerySet querySet, GPUSize32 queryIndex); + undefined endPipelineStatisticsQuery(); + + undefined writeTimestamp(GPUQuerySet querySet, GPUSize32 queryIndex); + + undefined executeBundles(sequence<GPURenderBundle> bundles); + undefined endPass(); +}; +GPURenderPassEncoder includes GPUObjectBase; +GPURenderPassEncoder includes GPUProgrammablePassEncoder; +GPURenderPassEncoder includes GPURenderEncoderBase; + +dictionary GPURenderPassDescriptor : GPUObjectDescriptorBase { + required sequence<GPURenderPassColorAttachment> colorAttachments; + GPURenderPassDepthStencilAttachment depthStencilAttachment; + GPUQuerySet occlusionQuerySet; +}; + +dictionary GPURenderPassColorAttachment { + required GPUTextureView view; + GPUTextureView resolveTarget; + + required (GPULoadOp or GPUColor) loadValue; + required GPUStoreOp storeOp; +}; + +dictionary GPURenderPassDepthStencilAttachment { + required GPUTextureView view; + + required (GPULoadOp or float) depthLoadValue; + required GPUStoreOp depthStoreOp; + boolean depthReadOnly = false; + + required (GPULoadOp or GPUStencilValue) stencilLoadValue; + required GPUStoreOp stencilStoreOp; + boolean stencilReadOnly = false; +}; + +enum GPULoadOp { + "load" +}; + +enum GPUStoreOp { + "store", + "discard" +}; + +[Exposed=Window] +interface GPURenderBundle { +}; +GPURenderBundle includes GPUObjectBase; + +dictionary GPURenderBundleDescriptor : GPUObjectDescriptorBase { +}; + +[Exposed=Window] +interface GPURenderBundleEncoder { + GPURenderBundle finish(optional GPURenderBundleDescriptor descriptor = {}); +}; +GPURenderBundleEncoder includes GPUObjectBase; +GPURenderBundleEncoder includes GPUProgrammablePassEncoder; +GPURenderBundleEncoder includes GPURenderEncoderBase; + +dictionary GPURenderBundleEncoderDescriptor : GPUObjectDescriptorBase { + required sequence<GPUTextureFormat> colorFormats; + GPUTextureFormat depthStencilFormat; + GPUSize32 sampleCount = 1; +}; + +[Exposed=Window] +interface GPUQueue { + undefined submit(sequence<GPUCommandBuffer> commandBuffers); + + Promise<undefined> onSubmittedWorkDone(); + + undefined writeBuffer( + GPUBuffer buffer, + GPUSize64 bufferOffset, + [AllowShared] BufferSource data, + optional GPUSize64 dataOffset = 0, + optional GPUSize64 size); + + undefined writeTexture( + GPUImageCopyTexture destination, + [AllowShared] BufferSource data, + GPUImageDataLayout dataLayout, + GPUExtent3D size); +}; +GPUQueue includes GPUObjectBase; + +[Exposed=Window] +interface GPUQuerySet { + undefined destroy(); +}; +GPUQuerySet includes GPUObjectBase; + +dictionary GPUQuerySetDescriptor : GPUObjectDescriptorBase { + required GPUQueryType type; + required GPUSize32 count; + sequence<GPUPipelineStatisticName> pipelineStatistics = []; +}; + +enum GPUQueryType { + "occlusion", + "pipeline-statistics", + "timestamp" +}; + +enum GPUPipelineStatisticName { + "vertex-shader-invocations", + "clipper-invocations", + "clipper-primitives-out", + "fragment-shader-invocations", + "compute-shader-invocations" +}; + +enum GPUDeviceLostReason { + "destroyed", +}; + +[Exposed=Window] +interface GPUDeviceLostInfo { + readonly attribute (GPUDeviceLostReason or undefined) reason; + readonly attribute DOMString message; +}; + +partial interface GPUDevice { + readonly attribute Promise<GPUDeviceLostInfo> lost; +}; + +enum GPUErrorFilter { + "out-of-memory", + "validation" +}; + +[Exposed=Window] +interface GPUOutOfMemoryError { + constructor(); +}; + +[Exposed=Window] +interface GPUValidationError { + constructor(DOMString message); + readonly attribute DOMString message; +}; + +typedef (GPUOutOfMemoryError or GPUValidationError) GPUError; + +partial interface GPUDevice { + undefined pushErrorScope(GPUErrorFilter filter); + Promise<GPUError?> popErrorScope(); +}; + +[ + Exposed=(Window, DedicatedWorker) +] +interface GPUUncapturedErrorEvent : Event { + constructor( + DOMString type, + GPUUncapturedErrorEventInit gpuUncapturedErrorEventInitDict + ); + [SameObject] readonly attribute GPUError error; +}; + +dictionary GPUUncapturedErrorEventInit : EventInit { + required GPUError error; +}; + +partial interface GPUDevice { + [Exposed=(Window, DedicatedWorker)] + attribute EventHandler onuncapturederror; +}; + +typedef [EnforceRange] unsigned long GPUBufferDynamicOffset; +typedef [EnforceRange] unsigned long GPUStencilValue; +typedef [EnforceRange] unsigned long GPUSampleMask; +typedef [EnforceRange] long GPUDepthBias; + +typedef [EnforceRange] unsigned long long GPUSize64; +typedef [EnforceRange] unsigned long GPUIntegerCoordinate; +typedef [EnforceRange] unsigned long GPUIndex32; +typedef [EnforceRange] unsigned long GPUSize32; +typedef [EnforceRange] long GPUSignedOffset32; + +typedef unsigned long GPUFlagsConstant; + +dictionary GPUColorDict { + required double r; + required double g; + required double b; + required double a; +}; +typedef (sequence<double> or GPUColorDict) GPUColor; + +dictionary GPUOrigin2DDict { + GPUIntegerCoordinate x = 0; + GPUIntegerCoordinate y = 0; +}; +typedef (sequence<GPUIntegerCoordinate> or GPUOrigin2DDict) GPUOrigin2D; + +dictionary GPUOrigin3DDict { + GPUIntegerCoordinate x = 0; + GPUIntegerCoordinate y = 0; + GPUIntegerCoordinate z = 0; +}; +typedef (sequence<GPUIntegerCoordinate> or GPUOrigin3DDict) GPUOrigin3D; + +dictionary GPUExtent3DDict { + required GPUIntegerCoordinate width; + GPUIntegerCoordinate height = 1; + GPUIntegerCoordinate depthOrArrayLayers = 1; +}; +typedef (sequence<GPUIntegerCoordinate> or GPUExtent3DDict) GPUExtent3D; |