diff options
author | crowlKats <13135287+crowlKats@users.noreply.github.com> | 2021-03-01 11:31:13 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-03-01 11:31:13 +0100 |
commit | 7cd14f97c9300357475e3e461fa57cbb7ec5bfec (patch) | |
tree | 39eb11e8a9c53001ffe814f5aac3ec5e37de6357 /op_crates/webgpu | |
parent | dbdbe7a1cf0d56df85305eb3638bc177d8a0216f (diff) |
feat: WebGPU API (#7977)
Co-authored-by: Luca Casonato <lucacasonato@yahoo.com>
Diffstat (limited to 'op_crates/webgpu')
-rw-r--r-- | op_crates/webgpu/01_webgpu.js | 5048 | ||||
-rw-r--r-- | op_crates/webgpu/02_idl_types.js | 1800 | ||||
-rw-r--r-- | op_crates/webgpu/Cargo.toml | 21 | ||||
-rw-r--r-- | op_crates/webgpu/README.md | 35 | ||||
-rw-r--r-- | op_crates/webgpu/binding.rs | 362 | ||||
-rw-r--r-- | op_crates/webgpu/buffer.rs | 243 | ||||
-rw-r--r-- | op_crates/webgpu/bundle.rs | 465 | ||||
-rw-r--r-- | op_crates/webgpu/command_encoder.rs | 734 | ||||
-rw-r--r-- | op_crates/webgpu/compute_pass.rs | 365 | ||||
-rw-r--r-- | op_crates/webgpu/error.rs | 252 | ||||
-rw-r--r-- | op_crates/webgpu/lib.deno_webgpu.d.ts | 1126 | ||||
-rw-r--r-- | op_crates/webgpu/lib.rs | 541 | ||||
-rw-r--r-- | op_crates/webgpu/pipeline.rs | 643 | ||||
-rw-r--r-- | op_crates/webgpu/queue.rs | 157 | ||||
-rw-r--r-- | op_crates/webgpu/render_pass.rs | 676 | ||||
-rw-r--r-- | op_crates/webgpu/sampler.rs | 129 | ||||
-rw-r--r-- | op_crates/webgpu/shader.rs | 77 | ||||
-rw-r--r-- | op_crates/webgpu/texture.rs | 256 | ||||
-rw-r--r-- | op_crates/webgpu/webgpu.idl | 1023 |
19 files changed, 13953 insertions, 0 deletions
diff --git a/op_crates/webgpu/01_webgpu.js b/op_crates/webgpu/01_webgpu.js new file mode 100644 index 000000000..7a078619e --- /dev/null +++ b/op_crates/webgpu/01_webgpu.js @@ -0,0 +1,5048 @@ +// 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; + + /** + * @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 (Array.isArray(data)) { + return { + width: data[0], + height: data[1], + depth: data[2], + }; + } else { + return data; + } + } + + /** + * @param {number[] | GPUOrigin3DDict} data + * @returns {GPUOrigin3DDict} + */ + function normalizeGPUOrigin3D(data) { + if (Array.isArray(data)) { + return { + x: data[0], + y: data[1], + z: data[2], + }; + } else { + return data; + } + } + + /** + * @param {number[] | GPUColor} data + * @returns {GPUColor} + */ + function normalizeGPUColor(data) { + if (Array.isArray(data)) { + return { + r: data[0], + g: data[1], + b: data[2], + a: data[3], + }; + } else { + return data; + } + } + + class GPUOutOfMemoryError extends Error { + constructor() { + super(); + } + } + + class GPUValidationError extends Error { + /** @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.jsonOpAsync( + "op_webgpu_request_adapter", + { ...options }, + ); + + if (err) { + return null; + } else { + return createGPUAdapter(data.name, data); + } + } + + [Symbol.for("Deno.customInspect")](inspect) { + return `${this.constructor.name} ${inspect({})}`; + } + } + + const _name = Symbol("[[name]]"); + const _adapter = Symbol("[[adapter]]"); + const _cleanup = Symbol("[[cleanup]]"); + + /** + * @typedef InnerGPUAdapter + * @property {number} rid + * @property {GPUAdapterFeatures} features + * @property {GPUAdapterLimits} limits + */ + + /** + * @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: createGPUAdapterFeatures(inner.features), + limits: createGPUAdapterLimits(inner.limits), + }; + return adapter; + } + + class GPUAdapter { + /** @type {string} */ + [_name]; + /** @type {InnerGPUAdapter} */ + [_adapter]; + + /** @returns {string} */ + get name() { + webidl.assertBranded(this, GPUAdapter); + return this[_name]; + } + /** @returns {GPUAdapterFeatures} */ + get features() { + webidl.assertBranded(this, GPUAdapter); + return this[_adapter].features; + } + /** @returns {GPUAdapterLimits} */ + get limits() { + webidl.assertBranded(this, GPUAdapter); + return this[_adapter].limits; + } + + 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 nonGuaranteedFeatures = descriptor.nonGuaranteedFeatures ?? []; + for (const feature of nonGuaranteedFeatures) { + if (!this[_adapter].features.has(feature)) { + throw new TypeError( + `${prefix}: nonGuaranteedFeatures must be a subset of the adapter features.`, + ); + } + } + const nonGuaranteedLimits = descriptor.nonGuaranteedLimits ?? []; + // TODO(lucacasonato): validate nonGuaranteedLimits + + const { rid, features, limits } = await core.jsonOpAsync( + "op_webgpu_request_device", + { + adapterRid: this[_adapter].rid, + labe: descriptor.label, + nonGuaranteedFeatures, + nonGuaranteedLimits, + }, + ); + + const inner = new InnerGPUDevice({ + rid, + adapter: this, + features: Object.freeze(features), + limits: Object.freeze(limits), + }); + return createGPUDevice( + descriptor.label ?? null, + inner, + createGPUQueue(descriptor.label ?? null, inner), + ); + } + + [Symbol.for("Deno.customInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ + name: this.name, + features: this.features, + limits: this.limits, + }) + }`; + } + } + + const _limits = Symbol("[[limits]]"); + + function createGPUAdapterLimits(features) { + /** @type {GPUAdapterLimits} */ + const adapterFeatures = webidl.createBranded(GPUAdapterLimits); + 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} maxVertexBuffers + * @property {number} maxVertexAttributes + * @property {number} maxVertexBufferArrayStride + */ + + class GPUAdapterLimits { + /** @type {InnerAdapterLimits} */ + [_limits]; + constructor() { + webidl.illegalConstructor(); + } + + get maxTextureDimension1D() { + throw new TypeError("Not yet implemented"); + } + get maxTextureDimension2D() { + throw new TypeError("Not yet implemented"); + } + get maxTextureDimension3D() { + throw new TypeError("Not yet implemented"); + } + get maxTextureArrayLayers() { + throw new TypeError("Not yet implemented"); + } + get maxBindGroups() { + webidl.assertBranded(this, GPUAdapterLimits); + return this[_limits].maxBindGroups; + } + get maxDynamicUniformBuffersPerPipelineLayout() { + webidl.assertBranded(this, GPUAdapterLimits); + return this[_limits].maxDynamicUniformBuffersPerPipelineLayout; + } + get maxDynamicStorageBuffersPerPipelineLayout() { + webidl.assertBranded(this, GPUAdapterLimits); + return this[_limits].maxDynamicStorageBuffersPerPipelineLayout; + } + get maxSampledTexturesPerShaderStage() { + webidl.assertBranded(this, GPUAdapterLimits); + return this[_limits].maxSampledTexturesPerShaderStage; + } + get maxSamplersPerShaderStage() { + webidl.assertBranded(this, GPUAdapterLimits); + return this[_limits].maxSamplersPerShaderStage; + } + get maxStorageBuffersPerShaderStage() { + webidl.assertBranded(this, GPUAdapterLimits); + return this[_limits].maxStorageBuffersPerShaderStage; + } + get maxStorageTexturesPerShaderStage() { + webidl.assertBranded(this, GPUAdapterLimits); + return this[_limits].maxStorageTexturesPerShaderStage; + } + get maxUniformBuffersPerShaderStage() { + webidl.assertBranded(this, GPUAdapterLimits); + return this[_limits].maxUniformBuffersPerShaderStage; + } + get maxUniformBufferBindingSize() { + webidl.assertBranded(this, GPUAdapterLimits); + return this[_limits].maxUniformBufferBindingSize; + } + get maxStorageBufferBindingSize() { + throw new TypeError("Not yet implemented"); + } + get maxVertexBuffers() { + throw new TypeError("Not yet implemented"); + } + get maxVertexAttributes() { + throw new TypeError("Not yet implemented"); + } + get maxVertexBufferArrayStride() { + throw new TypeError("Not yet implemented"); + } + + [Symbol.for("Deno.customInspect")](inspect) { + return `${this.constructor.name} ${inspect(this[_limits])}`; + } + } + + const _features = Symbol("[[features]]"); + + function createGPUAdapterFeatures(features) { + /** @type {GPUAdapterFeatures} */ + const adapterFeatures = webidl.createBranded(GPUAdapterFeatures); + adapterFeatures[_features] = new Set(features); + return adapterFeatures; + } + + class GPUAdapterFeatures { + /** @type {Set<string>} */ + [_features]; + + constructor() { + webidl.illegalConstructor(); + } + + /** @return {IterableIterator<[string, string]>} */ + entries() { + webidl.assertBranded(this, GPUAdapterFeatures); + return this[_features].entries(); + } + + /** @return {void} */ + forEach(callbackfn, thisArg) { + webidl.assertBranded(this, GPUAdapterFeatures); + this[_features].forEach(callbackfn, thisArg); + } + + /** @return {boolean} */ + has(value) { + webidl.assertBranded(this, GPUAdapterFeatures); + return this[_features].has(value); + } + + /** @return {IterableIterator<string>} */ + keys() { + webidl.assertBranded(this, GPUAdapterFeatures); + return this[_features].keys(); + } + + /** @return {IterableIterator<string>} */ + values() { + webidl.assertBranded(this, GPUAdapterFeatures); + return this[_features].values(); + } + + /** @return {number} */ + get size() { + webidl.assertBranded(this, GPUAdapterFeatures); + return this[_features].size; + } + + [Symbol.iterator]() { + webidl.assertBranded(this, GPUAdapterFeatures); + return this[_features][Symbol.iterator](); + } + + [Symbol.for("Deno.customInspect")](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]; + } + + [Symbol.for("Deno.customInspect")](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; + Object.defineProperty(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 {GPUError | undefined} error + */ + + /** + * @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) { + this.resources.push(new WeakRef(resource)); + } + + /** @param {{ type: string, value: string | null } | undefined} err */ + pushError(err) { + if (err) { + switch (err.type) { + case "lost": + this.isLost = true; + this.resolveLost( + createGPUDeviceLostInfo(undefined, "device was lost"), + ); + break; + case "validation": + case "out-of-memory": + for ( + let i = this.errorScopeStack.length - 1; + i >= 0; + i-- + ) { + const scope = this.errorScopeStack[i]; + if (scope.filter == err.type) { + if (!scope.error) { + switch (err.type) { + case "validation": + scope.error = new GPUValidationError( + err.value ?? "validation error", + ); + break; + case "out-of-memory": + scope.error = new GPUOutOfMemoryError(); + break; + } + } + return; + } + } + // TODO(lucacasonato): emit a UncapturedErrorEvent + break; + } + } + } + } + + /** + * @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 = resources.pop()?.deref(); + if (resource) { + resource[_cleanup](); + } + } + const rid = device.rid; + if (rid !== undefined) { + core.close(rid); + /** @type {number | undefined} */ + device.rid = undefined; + } + } + + get adapter() { + webidl.assertBranded(this, GPUDevice); + return this[_device].adapter; + } + 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.jsonOpSync("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.jsonOpSync("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.jsonOpSync("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.jsonOpSync( + "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 = descriptor.bindGroupLayouts.map( + (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.jsonOpSync("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 = descriptor.entries.map((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.jsonOpSync("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.jsonOpSync( + "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.jsonOpSync( + "op_webgpu_create_compute_pipeline", + { + deviceRid: device.rid, + label: descriptor.label, + layout, + compute: { + module, + entryPoint: descriptor.compute.entryPoint, + }, + }, + ); + 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.jsonOpSync("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 Promise.resolve(this.createComputePipeline(descriptor)); + } + + createRenderPipelineAsync(descriptor) { + // TODO(lucacasonato): this should be real async + return Promise.resolve(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.jsonOpSync("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.jsonOpSync( + "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.jsonOpSync("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 Promise.resolve(true); + } + if (device.rid === undefined) { + return Promise.resolve(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" }); + device.errorScopeStack.push({ filter, error: undefined }); + } + + /** + * @returns {Promise<GPUError | null>} + */ + // deno-lint-ignore require-await + async popErrorScope() { + webidl.assertBranded(this, GPUDevice); + const prefix = "Failed to execute 'pushErrorScope' on 'GPUDevice'"; + const device = assertDevice(this, { prefix, context: "this" }); + if (device.isLost) { + throw new DOMException("Device has been lost.", "OperationError"); + } + const scope = device.errorScopeStack.pop(); + if (!scope) { + throw new DOMException( + "There are no error scopes on that stack.", + "OperationError", + ); + } + return scope.error ?? null; + } + + [Symbol.for("Deno.customInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ + adapter: this.adapter, + 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 = commandBuffers.map((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.jsonOpSync("op_webgpu_queue_submit", { + queueRid: device.rid, + commandBuffers: commandBufferRids, + }); + device.pushError(err); + } + + onSubmittedWorkDone() { + webidl.assertBranded(this, GPUQueue); + return Promise.resolve(); + } + + /** + * @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.jsonOpSync( + "op_webgpu_write_buffer", + { + queueRid: device.rid, + buffer: bufferRid, + bufferOffset, + dataOffset, + size, + }, + new Uint8Array(ArrayBuffer.isView(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.jsonOpSync( + "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(ArrayBuffer.isView(data) ? data.buffer : data), + ); + device.pushError(err); + } + + copyImageBitmapToTexture(_source, _destination, _copySize) { + throw new Error("Not yet implemented"); + } + + [Symbol.for("Deno.customInspect")](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 = mappedRanges.pop(); + 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 = Math.max(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 { err } = await core.jsonOpAsync( + "op_webgpu_buffer_get_map_async", + { + bufferRid, + deviceRid: device.rid, + mode, + offset, + size: rangeSize, + }, + ); + device.pushError(err); + 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", + }); + size = size === undefined + ? undefined + : 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 = Math.max(0, this[_size] - offset); + } else { + rangeSize = this[_size]; + } + if (this[_state] !== "mapped" && this[_state] !== "mapped at creation") { + throw new DOMException( + `${prefix}: buffer is not mapped.`, + "OperationError", + ); + } + 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", + ); + } + const mappingRange = this[_mappingRange]; + if (!mappingRange) { + throw new DOMException(`${prefix}: invalid state.`, "OperationError"); + } + if (offset < mappingRange[0]) { + throw new DOMException( + `${prefix}: offset is out of bounds.`, + "OperationError", + ); + } + if ((offset + rangeSize) > mappingRange[1]) { + throw new DOMException( + `${prefix}: offset is out of bounds.`, + "OperationError", + ); + } + 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.jsonOpSync( + "op_webgpu_buffer_get_mapped_range", + { + bufferRid, + offset: offset - mappingRange[0], + size: rangeSize, + }, + new Uint8Array(buffer), + ); + + mappedRanges.push([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.jsonOpSync("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](); + } + + [Symbol.for("Deno.customInspect")](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 = views.pop()?.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.jsonOpSync("op_webgpu_create_texture_view", { + textureRid, + ...descriptor, + }); + device.pushError(err); + + const textureView = createGPUTextureView( + descriptor.label ?? null, + this, + rid, + ); + this[_views].push(new WeakRef(textureView)); + return textureView; + } + + destroy() { + webidl.assertBranded(this, GPUTexture); + this[_cleanup](); + } + + [Symbol.for("Deno.customInspect")](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(); + } + + [Symbol.for("Deno.customInspect")](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(); + } + + [Symbol.for("Deno.customInspect")](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(); + } + + [Symbol.for("Deno.customInspect")](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(); + } + + [Symbol.for("Deno.customInspect")](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(); + } + + [Symbol.for("Deno.customInspect")](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"); + } + + [Symbol.for("Deno.customInspect")](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, GPURenderPipeline); + 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.jsonOpSync( + "op_webgpu_compute_pipeline_get_bind_group_layout", + { computePipelineRid, index }, + ); + device.pushError(err); + + const bindGroupLayout = createGPUBindGroupLayout( + label, + device, + rid, + ); + device.trackResource((bindGroupLayout)); + return bindGroupLayout; + } + + [Symbol.for("Deno.customInspect")](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.jsonOpSync( + "op_webgpu_render_pipeline_get_bind_group_layout", + { renderPipelineRid, index }, + ); + device.pushError(err); + + const bindGroupLayout = createGPUBindGroupLayout( + label, + device, + rid, + ); + device.trackResource((bindGroupLayout)); + return bindGroupLayout; + } + + [Symbol.for("Deno.customInspect")](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 = encoders.pop()?.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 = descriptor.colorAttachments.map( + (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.jsonOpSync( + "op_webgpu_command_encoder_begin_render_pass", + { + commandEncoderRid, + ...descriptor, + colorAttachments, + depthStencilAttachment, + }, + ); + + const renderPassEncoder = createGPURenderPassEncoder( + descriptor.label ?? null, + this, + rid, + ); + this[_encoders].push(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.jsonOpSync( + "op_webgpu_command_encoder_begin_compute_pass", + { + commandEncoderRid, + ...descriptor, + }, + ); + + const computePassEncoder = createGPUComputePassEncoder( + descriptor.label ?? null, + this, + rid, + ); + this[_encoders].push(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.jsonOpSync( + "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.jsonOpSync( + "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.jsonOpSync( + "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.jsonOpSync( + "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.jsonOpSync( + "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.jsonOpSync( + "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.jsonOpSync( + "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.jsonOpSync( + "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.jsonOpSync( + "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.jsonOpSync("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; + } + + [Symbol.for("Deno.customInspect")](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.jsonOpSync("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.jsonOpSync("op_webgpu_render_pass_set_scissor_rect", { + renderPassRid, + x, + y, + width, + height, + }); + } + + /** + * @param {GPUColor} color + */ + setBlendColor(color) { + webidl.assertBranded(this, GPURenderPassEncoder); + const prefix = + "Failed to execute 'setBlendColor' 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.jsonOpSync("op_webgpu_render_pass_set_blend_color", { + 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.jsonOpSync("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.jsonOpSync("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.jsonOpSync("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.jsonOpSync("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 = bundles.map((bundle, i) => { + const context = `bundle ${i + 1}`; + const rid = assertResource(bundle, { prefix, context }); + assertDeviceMatch(device, bundle, { + prefix, + resourceContext: context, + selfContext: "this", + }); + return rid; + }); + core.jsonOpSync("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.jsonOpSync("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.jsonOpSync( + "op_webgpu_render_pass_set_bind_group", + { + renderPassRid, + index, + bindGroup: bindGroupRid, + dynamicOffsetsDataStart, + dynamicOffsetsDataLength, + }, + dynamicOffsetsData, + ); + } else { + dynamicOffsetsData ??= []; + core.jsonOpSync("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.jsonOpSync("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.jsonOpSync("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.jsonOpSync("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.jsonOpSync("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 = 0) { + 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", + }); + 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.jsonOpSync("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 = 0) { + 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", + }); + 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.jsonOpSync("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.jsonOpSync("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.jsonOpSync("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.jsonOpSync("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.jsonOpSync("op_webgpu_render_pass_draw_indexed_indirect", { + renderPassRid, + indirectBuffer: indirectBufferRid, + indirectOffset, + }); + } + + [Symbol.for("Deno.customInspect")](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.jsonOpSync("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.jsonOpSync("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.jsonOpSync("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.jsonOpSync( + "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.jsonOpSync("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.jsonOpSync("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.jsonOpSync("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.jsonOpSync( + "op_webgpu_compute_pass_set_bind_group", + { + computePassRid, + index, + bindGroup: bindGroupRid, + dynamicOffsetsDataStart, + dynamicOffsetsDataLength, + }, + dynamicOffsetsData, + ); + } else { + dynamicOffsetsData ??= []; + core.jsonOpSync("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.jsonOpSync("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.jsonOpSync("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.jsonOpSync("op_webgpu_compute_pass_insert_debug_marker", { + computePassRid, + markerLabel, + }); + } + + [Symbol.for("Deno.customInspect")](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"); + } + + [Symbol.for("Deno.customInspect")](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.jsonOpSync( + "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.jsonOpSync( + "op_webgpu_render_bundle_encoder_set_bind_group", + { + renderBundleEncoderRid, + index, + bindGroup: bindGroupRid, + dynamicOffsetsDataStart, + dynamicOffsetsDataLength, + }, + dynamicOffsetsData, + ); + } else { + dynamicOffsetsData ??= []; + core.jsonOpSync("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.jsonOpSync("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.jsonOpSync("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.jsonOpSync("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.jsonOpSync("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.jsonOpSync("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.jsonOpSync("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.jsonOpSync("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.jsonOpSync("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.jsonOpSync("op_webgpu_render_bundle_encoder_draw_indirect", { + renderBundleEncoderRid, + indirectBuffer: indirectBufferRid, + indirectOffset, + }); + } + + drawIndexedIndirect(_indirectBuffer, _indirectOffset) { + throw new Error("Not yet implemented"); + } + + [Symbol.for("Deno.customInspect")](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(); + } + + [Symbol.for("Deno.customInspect")](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](); + } + + [Symbol.for("Deno.customInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ + label: this.label, + }) + }`; + } + } + GPUObjectBaseMixin("GPUQuerySet", GPUQuerySet); + + window.__bootstrap.webgpu = { + gpu: webidl.createBranded(GPU), + GPU, + GPUAdapter, + GPUAdapterLimits, + GPUAdapterFeatures, + 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/op_crates/webgpu/02_idl_types.js b/op_crates/webgpu/02_idl_types.js new file mode 100644 index 000000000..68c3e3247 --- /dev/null +++ b/op_crates/webgpu/02_idl_types.js @@ -0,0 +1,1800 @@ +// 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, + GPUAdapterLimits, + GPUAdapterFeatures, + 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; + + // 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: GPUAdapterLimits + webidl.converters.GPUAdapterLimits = webidl.createInterfaceConverter( + "GPUAdapterLimits", + GPUAdapterLimits, + ); + + // INTERFACE: GPUAdapterFeatures + webidl.converters.GPUAdapterFeatures = webidl.createInterfaceConverter( + "GPUAdapterFeatures", + GPUAdapterFeatures, + ); + + // 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"], + }, + ]; + 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", + ], + ); + + // DICTIONARY: GPUDeviceDescriptor + const dictMembersGPUDeviceDescriptor = [ + { + key: "nonGuaranteedFeatures", + converter: webidl.createSequenceConverter( + webidl.converters["GPUFeatureName"], + ), + defaultValue: [], + }, + { + key: "nonGuaranteedLimits", + converter: webidl.converters.any, + defaultValue: {}, + }, + ]; + 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: GPUExtent3D + webidl.converters["GPUExtent3D"] = webidl.converters.any; + + // TYPEDEF: GPUIntegerCoordinate + webidl.converters["GPUIntegerCoordinate"] = (V, opts) => + webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }); + + // TYPEDEF: GPUSize32 + webidl.converters["GPUSize32"] = (V, opts) => + webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }); + + // 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, + ); + + // TYPEDEF: GPUBindingResource + webidl.converters["GPUBindingResource"] = webidl.converters.any; + + // 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, + ); + + // 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, + ); + + // 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, + ); + + // 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, + // ); + + // INTERFACE: GPUShaderModule + webidl.converters.GPUShaderModule = webidl.createInterfaceConverter( + "GPUShaderModule", + GPUShaderModule, + ); + + // DICTIONARY: GPUShaderModuleDescriptor + const dictMembersGPUShaderModuleDescriptor = [ + { key: "code", converter: webidl.converters["USVString"], required: true }, + { key: "sourceMap", converter: webidl.converters["object"] }, + ]; + webidl.converters["GPUShaderModuleDescriptor"] = webidl + .createDictionaryConverter( + "GPUShaderModuleDescriptor", + dictMembersGPUObjectDescriptorBase, + dictMembersGPUShaderModuleDescriptor, + ); + + // DICTIONARY: GPUPipelineDescriptorBase + const dictMembersGPUPipelineDescriptorBase = [ + { key: "layout", converter: webidl.converters["GPUPipelineLayout"] }, + ]; + webidl.converters["GPUPipelineDescriptorBase"] = webidl + .createDictionaryConverter( + "GPUPipelineDescriptorBase", + dictMembersGPUObjectDescriptorBase, + dictMembersGPUPipelineDescriptorBase, + ); + + // DICTIONARY: GPUProgrammableStage + const dictMembersGPUProgrammableStage = [ + { + key: "module", + converter: webidl.converters["GPUShaderModule"], + required: true, + }, + { + key: "entryPoint", + converter: webidl.converters["USVString"], + required: true, + }, + ]; + 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", + [ + "uchar2", + "uchar4", + "char2", + "char4", + "uchar2norm", + "uchar4norm", + "char2norm", + "char4norm", + "ushort2", + "ushort4", + "short2", + "short4", + "ushort2norm", + "ushort4norm", + "short2norm", + "short4norm", + "half2", + "half4", + "float", + "float2", + "float3", + "float4", + "uint", + "uint2", + "uint3", + "uint4", + "int", + "int2", + "int3", + "int4", + ], + ); + + // 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"], + ), + ), + defaultValue: [], + }, + ]; + 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", + }, + ]; + 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"], + defaultValue: {}, + }, + { + key: "stencilBack", + converter: webidl.converters["GPUStencilFaceState"], + defaultValue: {}, + }, + { + 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, + }, + { + key: "clampDepth", + converter: webidl.converters["boolean"], + defaultValue: false, + }, + ]; + 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-color", + "one-minus-src-color", + "src-alpha", + "one-minus-src-alpha", + "dst-color", + "one-minus-dst-color", + "dst-alpha", + "one-minus-dst-alpha", + "src-alpha-saturated", + "blend-color", + "one-minus-blend-color", + ], + ); + + // 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"], + defaultValue: {}, + }, + { + key: "depthStencil", + converter: webidl.converters["GPUDepthStencilState"], + }, + { + key: "multisample", + converter: webidl.converters["GPUMultisampleState"], + defaultValue: {}, + }, + { 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, + ); + + // 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, + ); + + // TYPEDEF: GPUOrigin3D + webidl.converters["GPUOrigin3D"] = webidl.converters.any; + + // 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"], + defaultValue: {}, + }, + { + key: "aspect", + converter: webidl.converters["GPUTextureAspect"], + defaultValue: "all", + }, + ]; + webidl.converters["GPUImageCopyTexture"] = webidl.createDictionaryConverter( + "GPUImageCopyTexture", + dictMembersGPUImageCopyTexture, + ); + + // 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: GPUStoreOp + webidl.converters["GPUStoreOp"] = webidl.createEnumConverter("GPUStoreOp", [ + "store", + "clear", + ]); + + // DICTIONARY: GPURenderPassColorAttachment + const dictMembersGPURenderPassColorAttachment = [ + { + key: "view", + converter: webidl.converters["GPUTextureView"], + required: true, + }, + { key: "resolveTarget", converter: webidl.converters["GPUTextureView"] }, + { key: "loadValue", converter: webidl.converters.any, required: true }, + { + key: "storeOp", + converter: webidl.converters["GPUStoreOp"], + defaultValue: "store", + }, + ]; + 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, required: true }, + { + key: "depthStoreOp", + converter: webidl.converters["GPUStoreOp"], + required: true, + }, + { + key: "depthReadOnly", + converter: webidl.converters["boolean"], + defaultValue: false, + }, + { + key: "stencilLoadValue", + converter: webidl.converters.any, + 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, + ); + + // ENUM: GPULoadOp + webidl.converters["GPULoadOp"] = webidl.createEnumConverter("GPULoadOp", [ + "load", + ]); + + // INTERFACE: GPURenderBundle + webidl.converters.GPURenderBundle = webidl.createInterfaceConverter( + "GPURenderBundle", + 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"], + ), + defaultValue: [], + }, + ]; + 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; + + // // 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"]; + + // 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"] = webidl.converters.any; + + // 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"] = webidl.converters.any; + + // 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, + ); + + // DICTIONARY: GPUExtent3DDict + const dictMembersGPUExtent3DDict = [ + { + key: "width", + converter: webidl.converters["GPUIntegerCoordinate"], + defaultValue: 1, + }, + { + key: "height", + converter: webidl.converters["GPUIntegerCoordinate"], + defaultValue: 1, + }, + { + key: "depthOrArrayLayers", + converter: webidl.converters["GPUIntegerCoordinate"], + defaultValue: 1, + }, + ]; + webidl.converters["GPUExtent3DDict"] = webidl.createDictionaryConverter( + "GPUExtent3DDict", + dictMembersGPUExtent3DDict, + ); + + webidl.converters["sequence<GPURenderBundle>"] = webidl + .createSequenceConverter(webidl.converters["GPURenderBundle"]); + webidl.converters["sequence<GPUCommandBuffer>"] = webidl + .createSequenceConverter(webidl.converters["GPUCommandBuffer"]); + webidl.converters["UVString?"] = webidl.createNullableConverter( + webidl.converters.USVString, + ); +})(this); diff --git a/op_crates/webgpu/Cargo.toml b/op_crates/webgpu/Cargo.toml new file mode 100644 index 000000000..92d9e01f3 --- /dev/null +++ b/op_crates/webgpu/Cargo.toml @@ -0,0 +1,21 @@ +# Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. + +[package] +name = "deno_webgpu" +version = "0.1.0" +edition = "2018" +description = "provides webgpu Web API to deno_core" +authors = ["the Deno authors"] +license = "MIT" +readme = "README.md" +repository = "https://github.com/denoland/deno" + +[lib] +path = "lib.rs" + +[dependencies] +deno_core = { version = "0.79.0", path = "../../core" } +tokio = { version = "1.1.1", features = ["full"] } +serde = { version = "1.0.123", features = ["derive"] } +wgpu-core = { version = "0.7.0", features = ["trace"] } +wgpu-types = "0.7.0" diff --git a/op_crates/webgpu/README.md b/op_crates/webgpu/README.md new file mode 100644 index 000000000..2f915dcbb --- /dev/null +++ b/op_crates/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/op_crates/webgpu/binding.rs b/op_crates/webgpu/binding.rs new file mode 100644 index 000000000..b93b223cf --- /dev/null +++ b/op_crates/webgpu/binding.rs @@ -0,0 +1,362 @@ +// 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::serde_json::json; +use deno_core::serde_json::Value; +use deno_core::ZeroCopyBuf; +use deno_core::{OpState, Resource}; +use serde::Deserialize; +use std::borrow::Cow; + +use super::error::WebGPUError; + +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: u32, + label: Option<String>, + entries: Vec<GPUBindGroupLayoutEntry>, +} + +pub fn op_webgpu_create_bind_group_layout( + state: &mut OpState, + args: CreateBindGroupLayoutArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result<Value, 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: false, + 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), + }; + + let (bind_group_layout, maybe_err) = gfx_select!(device => instance.device_create_bind_group_layout( + device, + &descriptor, + std::marker::PhantomData + )); + + let rid = state + .resource_table + .add(WebGPUBindGroupLayout(bind_group_layout)); + + Ok(json!({ + "rid": rid, + "err": maybe_err.map(WebGPUError::from) + })) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CreatePipelineLayoutArgs { + device_rid: u32, + label: Option<String>, + bind_group_layouts: Vec<u32>, +} + +pub fn op_webgpu_create_pipeline_layout( + state: &mut OpState, + args: CreatePipelineLayoutArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result<Value, 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(), + }; + + let (pipeline_layout, maybe_err) = gfx_select!(device => instance.device_create_pipeline_layout( + device, + &descriptor, + std::marker::PhantomData + )); + + let rid = state + .resource_table + .add(super::pipeline::WebGPUPipelineLayout(pipeline_layout)); + + Ok(json!({ + "rid": rid, + "err": maybe_err.map(WebGPUError::from) + })) +} + +#[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: u32, + label: Option<String>, + layout: u32, + entries: Vec<GPUBindGroupEntry>, +} + +pub fn op_webgpu_create_bind_group( + state: &mut OpState, + args: CreateBindGroupArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result<Value, 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), + }; + + let (bind_group, maybe_err) = gfx_select!(device => instance.device_create_bind_group( + device, + &descriptor, + std::marker::PhantomData + )); + + let rid = state.resource_table.add(WebGPUBindGroup(bind_group)); + + Ok(json!({ + "rid": rid, + "err": maybe_err.map(WebGPUError::from) + })) +} diff --git a/op_crates/webgpu/buffer.rs b/op_crates/webgpu/buffer.rs new file mode 100644 index 000000000..8ff9f3115 --- /dev/null +++ b/op_crates/webgpu/buffer.rs @@ -0,0 +1,243 @@ +// 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::futures::channel::oneshot; +use deno_core::serde_json::json; +use deno_core::serde_json::Value; +use deno_core::OpState; +use deno_core::ZeroCopyBuf; +use deno_core::{BufVec, Resource}; +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::WebGPUError; + +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: u32, + label: Option<String>, + size: u64, + usage: u32, + mapped_at_creation: Option<bool>, +} + +pub fn op_webgpu_create_buffer( + state: &mut OpState, + args: CreateBufferArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result<Value, 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).unwrap(), + mapped_at_creation: args.mapped_at_creation.unwrap_or(false), + }; + + let (buffer, maybe_err) = gfx_select!(device => instance.device_create_buffer( + device, + &descriptor, + std::marker::PhantomData + )); + + let rid = state.resource_table.add(WebGPUBuffer(buffer)); + + Ok(json!({ + "rid": rid, + "err": maybe_err.map(WebGPUError::from) + })) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct BufferGetMapAsyncArgs { + buffer_rid: u32, + device_rid: u32, + mode: u32, + offset: u64, + size: u64, +} + +pub async fn op_webgpu_buffer_get_map_async( + state: Rc<RefCell<OpState>>, + args: BufferGetMapAsyncArgs, + _bufs: BufVec, +) -> Result<Value, 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 + 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, + } + ))?; + } + + 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(json!({})) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct BufferGetMappedRangeArgs { + buffer_rid: u32, + offset: u64, + size: u64, +} + +pub fn op_webgpu_buffer_get_mapped_range( + state: &mut OpState, + args: BufferGetMappedRangeArgs, + zero_copy: &mut [ZeroCopyBuf], +) -> Result<Value, AnyError> { + 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 = gfx_select!(buffer => instance.buffer_get_mapped_range( + buffer, + args.offset, + std::num::NonZeroU64::new(args.size) + )) + .map_err(|e| DOMExceptionOperationError::new(&e.to_string()))?; + + let slice = unsafe { + std::slice::from_raw_parts_mut(slice_pointer, args.size as usize) + }; + zero_copy[0].copy_from_slice(slice); + + let rid = state + .resource_table + .add(WebGPUBufferMapped(slice_pointer, args.size as usize)); + + Ok(json!({ + "rid": rid, + })) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct BufferUnmapArgs { + buffer_rid: u32, + mapped_rid: u32, +} + +pub fn op_webgpu_buffer_unmap( + state: &mut OpState, + args: BufferUnmapArgs, + zero_copy: &mut [ZeroCopyBuf], +) -> Result<Value, 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.get(0) { + let slice = unsafe { std::slice::from_raw_parts_mut(slice_pointer, size) }; + slice.copy_from_slice(&buffer); + } + + let maybe_err = gfx_select!(buffer => instance.buffer_unmap(buffer)).err(); + + Ok(json!({ "err": maybe_err.map(WebGPUError::from) })) +} diff --git a/op_crates/webgpu/bundle.rs b/op_crates/webgpu/bundle.rs new file mode 100644 index 000000000..7043b0a9e --- /dev/null +++ b/op_crates/webgpu/bundle.rs @@ -0,0 +1,465 @@ +// 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::serde_json::json; +use deno_core::serde_json::Value; +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::WebGPUError; +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: u32, + 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, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result<Value, 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(json!({ + "rid": rid, + "err": maybe_err.map(WebGPUError::from), + })) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderBundleEncoderFinishArgs { + render_bundle_encoder_rid: u32, + label: Option<String>, +} + +pub fn op_webgpu_render_bundle_encoder_finish( + state: &mut OpState, + args: RenderBundleEncoderFinishArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result<Value, 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>(); + + let (render_bundle, maybe_err) = gfx_select!(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 + )); + + let rid = state.resource_table.add(WebGPURenderBundle(render_bundle)); + + Ok(json!({ + "rid": rid, + "err": maybe_err.map(WebGPUError::from) + })) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderBundleEncoderSetBindGroupArgs { + render_bundle_encoder_rid: u32, + 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: &mut [ZeroCopyBuf], +) -> Result<Value, 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 (prefix, data, suffix) = unsafe { zero_copy[0].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(json!({})) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderBundleEncoderPushDebugGroupArgs { + render_bundle_encoder_rid: u32, + group_label: String, +} + +pub fn op_webgpu_render_bundle_encoder_push_debug_group( + state: &mut OpState, + args: RenderBundleEncoderPushDebugGroupArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result<Value, 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(json!({})) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderBundleEncoderPopDebugGroupArgs { + render_bundle_encoder_rid: u32, +} + +pub fn op_webgpu_render_bundle_encoder_pop_debug_group( + state: &mut OpState, + args: RenderBundleEncoderPopDebugGroupArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result<Value, AnyError> { + let render_bundle_encoder_resource = state + .resource_table + .get::<WebGPURenderBundleEncoder>(args.render_bundle_encoder_rid) + .ok_or_else(bad_resource_id)?; + + unsafe { + wgpu_core::command::bundle_ffi::wgpu_render_bundle_pop_debug_group( + &mut render_bundle_encoder_resource.0.borrow_mut(), + ); + } + + Ok(json!({})) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderBundleEncoderInsertDebugMarkerArgs { + render_bundle_encoder_rid: u32, + marker_label: String, +} + +pub fn op_webgpu_render_bundle_encoder_insert_debug_marker( + state: &mut OpState, + args: RenderBundleEncoderInsertDebugMarkerArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result<Value, 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(json!({})) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderBundleEncoderSetPipelineArgs { + render_bundle_encoder_rid: u32, + pipeline: u32, +} + +pub fn op_webgpu_render_bundle_encoder_set_pipeline( + state: &mut OpState, + args: RenderBundleEncoderSetPipelineArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result<Value, 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(json!({})) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderBundleEncoderSetIndexBufferArgs { + render_bundle_encoder_rid: u32, + buffer: u32, + index_format: String, + offset: u64, + size: u64, +} + +pub fn op_webgpu_render_bundle_encoder_set_index_buffer( + state: &mut OpState, + args: RenderBundleEncoderSetIndexBufferArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result<Value, 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(json!({})) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderBundleEncoderSetVertexBufferArgs { + render_bundle_encoder_rid: u32, + slot: u32, + buffer: u32, + offset: u64, + size: u64, +} + +pub fn op_webgpu_render_bundle_encoder_set_vertex_buffer( + state: &mut OpState, + args: RenderBundleEncoderSetVertexBufferArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result<Value, 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(json!({})) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderBundleEncoderDrawArgs { + render_bundle_encoder_rid: u32, + 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, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result<Value, 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(json!({})) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderBundleEncoderDrawIndexedArgs { + render_bundle_encoder_rid: u32, + 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, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result<Value, 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(json!({})) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderBundleEncoderDrawIndirectArgs { + render_bundle_encoder_rid: u32, + indirect_buffer: u32, + indirect_offset: u64, +} + +pub fn op_webgpu_render_bundle_encoder_draw_indirect( + state: &mut OpState, + args: RenderBundleEncoderDrawIndirectArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result<Value, 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(json!({})) +} diff --git a/op_crates/webgpu/command_encoder.rs b/op_crates/webgpu/command_encoder.rs new file mode 100644 index 000000000..b91f677ee --- /dev/null +++ b/op_crates/webgpu/command_encoder.rs @@ -0,0 +1,734 @@ +// 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::serde_json::json; +use deno_core::serde_json::Value; +use deno_core::ZeroCopyBuf; +use deno_core::{OpState, Resource}; +use serde::Deserialize; +use std::borrow::Cow; +use std::cell::RefCell; + +use super::error::WebGPUError; + +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, + "clear" => wgpu_core::command::StoreOp::Clear, + _ => unreachable!(), + } +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CreateCommandEncoderArgs { + device_rid: u32, + label: Option<String>, + _measure_execution_time: Option<bool>, // not yet implemented +} + +pub fn op_webgpu_create_command_encoder( + state: &mut OpState, + args: CreateCommandEncoderArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result<Value, 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), + }; + + let (command_encoder, maybe_err) = gfx_select!(device => instance.device_create_command_encoder( + device, + &descriptor, + std::marker::PhantomData + )); + + let rid = state + .resource_table + .add(WebGPUCommandEncoder(command_encoder)); + + Ok(json!({ + "rid": rid, + "err": maybe_err.map(WebGPUError::from), + })) +} + +#[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: u32, + 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, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result<Value, 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::ColorAttachmentDescriptor { + attachment: 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::DepthStencilAttachmentDescriptor { + attachment: 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(json!({ + "rid": rid, + })) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CommandEncoderBeginComputePassArgs { + command_encoder_rid: u32, + label: Option<String>, +} + +pub fn op_webgpu_command_encoder_begin_compute_pass( + state: &mut OpState, + args: CommandEncoderBeginComputePassArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result<Value, 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(json!({ + "rid": rid, + })) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CommandEncoderCopyBufferToBufferArgs { + command_encoder_rid: u32, + 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, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result<Value, 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; + + let maybe_err = gfx_select!(command_encoder => instance.command_encoder_copy_buffer_to_buffer( + command_encoder, + source_buffer, + args.source_offset, + destination_buffer, + args.destination_offset, + args.size + )).err(); + + Ok(json!({ "err": maybe_err.map(WebGPUError::from) })) +} + +#[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: u32, + source: GPUImageCopyBuffer, + destination: GPUImageCopyTexture, + copy_size: super::texture::GPUExtent3D, +} + +pub fn op_webgpu_command_encoder_copy_buffer_to_texture( + state: &mut OpState, + args: CommandEncoderCopyBufferToTextureArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result<Value, 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::BufferCopyView { + buffer: source_buffer_resource.0, + layout: wgpu_types::TextureDataLayout { + offset: args.source.offset.unwrap_or(0), + bytes_per_row: args.source.bytes_per_row.unwrap_or(0), + rows_per_image: args.source.rows_per_image.unwrap_or(0), + }, + }; + let destination = wgpu_core::command::TextureCopyView { + 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), + }), + }; + let maybe_err = gfx_select!(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: args.copy_size.depth.unwrap_or(1), + } + )).err(); + + Ok(json!({ "err": maybe_err.map(WebGPUError::from) })) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CommandEncoderCopyTextureToBufferArgs { + command_encoder_rid: u32, + source: GPUImageCopyTexture, + destination: GPUImageCopyBuffer, + copy_size: super::texture::GPUExtent3D, +} + +pub fn op_webgpu_command_encoder_copy_texture_to_buffer( + state: &mut OpState, + args: CommandEncoderCopyTextureToBufferArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result<Value, 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::TextureCopyView { + 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::BufferCopyView { + buffer: destination_buffer_resource.0, + layout: wgpu_types::TextureDataLayout { + offset: args.destination.offset.unwrap_or(0), + bytes_per_row: args.destination.bytes_per_row.unwrap_or(0), + rows_per_image: args.destination.rows_per_image.unwrap_or(0), + }, + }; + let maybe_err = gfx_select!(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: args.copy_size.depth.unwrap_or(1), + } + )).err(); + + Ok(json!({ "err": maybe_err.map(WebGPUError::from) })) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CommandEncoderCopyTextureToTextureArgs { + command_encoder_rid: u32, + source: GPUImageCopyTexture, + destination: GPUImageCopyTexture, + copy_size: super::texture::GPUExtent3D, +} + +pub fn op_webgpu_command_encoder_copy_texture_to_texture( + state: &mut OpState, + args: CommandEncoderCopyTextureToTextureArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result<Value, 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::TextureCopyView { + 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::TextureCopyView { + 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), + }), + }; + let maybe_err = gfx_select!(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: args.copy_size.depth.unwrap_or(1), + } + )).err(); + + Ok(json!({ "err": maybe_err.map(WebGPUError::from) })) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CommandEncoderPushDebugGroupArgs { + command_encoder_rid: u32, + group_label: String, +} + +pub fn op_webgpu_command_encoder_push_debug_group( + state: &mut OpState, + args: CommandEncoderPushDebugGroupArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result<Value, 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 maybe_err = gfx_select!(command_encoder => instance + .command_encoder_push_debug_group(command_encoder, &args.group_label)) + .err(); + + Ok(json!({ "err": maybe_err.map(WebGPUError::from) })) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CommandEncoderPopDebugGroupArgs { + command_encoder_rid: u32, +} + +pub fn op_webgpu_command_encoder_pop_debug_group( + state: &mut OpState, + args: CommandEncoderPopDebugGroupArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result<Value, 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 maybe_err = gfx_select!(command_encoder => instance.command_encoder_pop_debug_group(command_encoder)).err(); + + Ok(json!({ "err": maybe_err.map(WebGPUError::from) })) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CommandEncoderInsertDebugMarkerArgs { + command_encoder_rid: u32, + marker_label: String, +} + +pub fn op_webgpu_command_encoder_insert_debug_marker( + state: &mut OpState, + args: CommandEncoderInsertDebugMarkerArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result<Value, 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 maybe_err = gfx_select!(command_encoder => instance.command_encoder_insert_debug_marker( + command_encoder, + &args.marker_label + )).err(); + + Ok(json!({ "err": maybe_err.map(WebGPUError::from) })) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CommandEncoderWriteTimestampArgs { + command_encoder_rid: u32, + query_set: u32, + query_index: u32, +} + +pub fn op_webgpu_command_encoder_write_timestamp( + state: &mut OpState, + args: CommandEncoderWriteTimestampArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result<Value, 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 maybe_err = + gfx_select!(command_encoder => instance.command_encoder_write_timestamp( + command_encoder, + query_set_resource.0, + args.query_index + )) + .err(); + + Ok(json!({ "err": maybe_err.map(WebGPUError::from) })) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CommandEncoderResolveQuerySetArgs { + command_encoder_rid: u32, + 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, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result<Value, 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)?; + + let maybe_err = + gfx_select!(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 + )) + .err(); + + Ok(json!({ "err": maybe_err.map(WebGPUError::from) })) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CommandEncoderFinishArgs { + command_encoder_rid: u32, + label: Option<String>, +} + +pub fn op_webgpu_command_encoder_finish( + state: &mut OpState, + args: CommandEncoderFinishArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result<Value, 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), + }; + + let (command_buffer, maybe_err) = gfx_select!(command_encoder => instance.command_encoder_finish( + command_encoder, + &descriptor + )); + + let rid = state + .resource_table + .add(WebGPUCommandBuffer(command_buffer)); + + Ok(json!({ + "rid": rid, + "err": maybe_err.map(WebGPUError::from) + })) +} diff --git a/op_crates/webgpu/compute_pass.rs b/op_crates/webgpu/compute_pass.rs new file mode 100644 index 000000000..711bdea97 --- /dev/null +++ b/op_crates/webgpu/compute_pass.rs @@ -0,0 +1,365 @@ +// 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::serde_json::json; +use deno_core::serde_json::Value; +use deno_core::ZeroCopyBuf; +use deno_core::{OpState, Resource}; +use serde::Deserialize; +use std::borrow::Cow; +use std::cell::RefCell; + +use super::error::WebGPUError; + +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: u32, + pipeline: u32, +} + +pub fn op_webgpu_compute_pass_set_pipeline( + state: &mut OpState, + args: ComputePassSetPipelineArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result<Value, 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(json!({})) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ComputePassDispatchArgs { + compute_pass_rid: u32, + x: u32, + y: u32, + z: u32, +} + +pub fn op_webgpu_compute_pass_dispatch( + state: &mut OpState, + args: ComputePassDispatchArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result<Value, 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(json!({})) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ComputePassDispatchIndirectArgs { + compute_pass_rid: u32, + indirect_buffer: u32, + indirect_offset: u64, +} + +pub fn op_webgpu_compute_pass_dispatch_indirect( + state: &mut OpState, + args: ComputePassDispatchIndirectArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result<Value, 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(json!({})) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ComputePassBeginPipelineStatisticsQueryArgs { + compute_pass_rid: u32, + query_set: u32, + query_index: u32, +} + +pub fn op_webgpu_compute_pass_begin_pipeline_statistics_query( + state: &mut OpState, + args: ComputePassBeginPipelineStatisticsQueryArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result<Value, 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)?; + + unsafe { + 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(json!({})) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ComputePassEndPipelineStatisticsQueryArgs { + compute_pass_rid: u32, +} + +pub fn op_webgpu_compute_pass_end_pipeline_statistics_query( + state: &mut OpState, + args: ComputePassEndPipelineStatisticsQueryArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result<Value, AnyError> { + 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_end_pipeline_statistics_query( + &mut compute_pass_resource.0.borrow_mut(), + ); + } + + Ok(json!({})) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ComputePassWriteTimestampArgs { + compute_pass_rid: u32, + query_set: u32, + query_index: u32, +} + +pub fn op_webgpu_compute_pass_write_timestamp( + state: &mut OpState, + args: ComputePassWriteTimestampArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result<Value, 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)?; + + unsafe { + 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(json!({})) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ComputePassEndPassArgs { + command_encoder_rid: u32, + compute_pass_rid: u32, +} + +pub fn op_webgpu_compute_pass_end_pass( + state: &mut OpState, + args: ComputePassEndPassArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result<Value, 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>(); + + let maybe_err = + gfx_select!(command_encoder => instance.command_encoder_run_compute_pass( + command_encoder, + compute_pass + )) + .err(); + + Ok(json!({ "err": maybe_err.map(WebGPUError::from) })) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ComputePassSetBindGroupArgs { + compute_pass_rid: u32, + 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: &mut [ZeroCopyBuf], +) -> Result<Value, 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 (prefix, data, suffix) = zero_copy[0].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(json!({})) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ComputePassPushDebugGroupArgs { + compute_pass_rid: u32, + group_label: String, +} + +pub fn op_webgpu_compute_pass_push_debug_group( + state: &mut OpState, + args: ComputePassPushDebugGroupArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result<Value, 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(json!({})) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ComputePassPopDebugGroupArgs { + compute_pass_rid: u32, +} + +pub fn op_webgpu_compute_pass_pop_debug_group( + state: &mut OpState, + args: ComputePassPopDebugGroupArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result<Value, 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(json!({})) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ComputePassInsertDebugMarkerArgs { + compute_pass_rid: u32, + marker_label: String, +} + +pub fn op_webgpu_compute_pass_insert_debug_marker( + state: &mut OpState, + args: ComputePassInsertDebugMarkerArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result<Value, 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(json!({})) +} diff --git a/op_crates/webgpu/error.rs b/op_crates/webgpu/error.rs new file mode 100644 index 000000000..e793dabfa --- /dev/null +++ b/op_crates/webgpu/error.rs @@ -0,0 +1,252 @@ +use deno_core::error::AnyError; +use serde::Serialize; +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)] +#[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/op_crates/webgpu/lib.deno_webgpu.d.ts b/op_crates/webgpu/lib.deno_webgpu.d.ts new file mode 100644 index 000000000..cd2680b3d --- /dev/null +++ b/op_crates/webgpu/lib.deno_webgpu.d.ts @@ -0,0 +1,1126 @@ +// 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 GPUAdapterLimits { + 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; + maxVertexBuffers?: number; + maxVertexAttributes?: number; + maxVertexBufferArrayStride?: number; +} + +declare class GPUAdapterFeatures { + 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; +} + +declare type GPUPowerPreference = "low-power" | "high-performance"; + +declare class GPUAdapter { + readonly name: string; + readonly features: GPUAdapterFeatures; + readonly limits: GPUAdapterLimits; + + requestDevice(descriptor?: GPUDeviceDescriptor): Promise<GPUDevice | null>; +} + +declare interface GPUDeviceDescriptor extends GPUObjectDescriptorBase { + nonGuaranteedFeatures?: GPUFeatureName[]; + nonGuaranteedLimits?: 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 adapter: GPUAdapter; + 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; + 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; +} + +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-color" + | "one-minus-src-color" + | "src-alpha" + | "one-minus-src-alpha" + | "dst-color" + | "one-minus-dst-color" + | "dst-alpha" + | "one-minus-dst-alpha" + | "src-alpha-saturated" + | "blend-color" + | "one-minus-blend-color"; + +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; + + clampDepth?: boolean; +} + +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 = + | "uchar2" + | "uchar4" + | "char2" + | "char4" + | "uchar2norm" + | "uchar4norm" + | "char2norm" + | "char4norm" + | "ushort2" + | "ushort4" + | "short2" + | "short4" + | "ushort2norm" + | "ushort4norm" + | "short2norm" + | "short4norm" + | "half2" + | "half4" + | "float" + | "float2" + | "float3" + | "float4" + | "uint" + | "uint2" + | "uint3" + | "uint4" + | "int" + | "int2" + | "int3" + | "int4"; + +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; + + setBlendColor(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" | "clear"; + +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; + depth?: number; +} + +declare type GPUExtent3D = number[] | GPUExtent3DDict; diff --git a/op_crates/webgpu/lib.rs b/op_crates/webgpu/lib.rs new file mode 100644 index 000000000..2f1769daf --- /dev/null +++ b/op_crates/webgpu/lib.rs @@ -0,0 +1,541 @@ +// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. + +#![deny(warnings)] + +use deno_core::error::AnyError; +use deno_core::error::{bad_resource_id, not_supported}; +use deno_core::serde_json::json; +use deno_core::serde_json::Value; +use deno_core::OpState; +use deno_core::ZeroCopyBuf; +use deno_core::{BufVec, Resource}; +use serde::Deserialize; +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::WebGPUError; + +#[macro_use] +mod macros { + macro_rules! gfx_select { + ($id:expr => $global:ident.$method:ident( $($param:expr),* )) => { + match $id.backend() { + #[cfg(all(not(target_arch = "wasm32"), not(any(target_os = "ios", target_os = "macos"))))] + wgpu_types::Backend::Vulkan => $global.$method::<wgpu_core::backend::Vulkan>( $($param),* ), + #[cfg(all(not(target_arch = "wasm32"), any(target_os = "ios", target_os = "macos")))] + wgpu_types::Backend::Metal => $global.$method::<wgpu_core::backend::Metal>( $($param),* ), + #[cfg(all(not(target_arch = "wasm32"), windows))] + wgpu_types::Backend::Dx12 => $global.$method::<wgpu_core::backend::Dx12>( $($param),* ), + #[cfg(all(not(target_arch = "wasm32"), windows))] + wgpu_types::Backend::Dx11 => $global.$method::<wgpu_core::backend::Dx11>( $($param),* ), + #[cfg(any(target_arch = "wasm32", all(unix, not(any(target_os = "ios", target_os = "macos")))))] + wgpu_types::Backend::Gl => $global.$method::<wgpu_core::backend::Gl>( $($param),+ ), + other => panic!("Unexpected backend {:?}", other), + } + }; + } +} + +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() + } +} + +/// Execute this crates' JS source files. +pub fn init(isolate: &mut deno_core::JsRuntime) { + let files = vec![ + ( + "deno:op_crates/webgpu/01_webgpu.js", + include_str!("01_webgpu.js"), + ), + ( + "deno:op_crates/webgpu/02_idl_types.js", + include_str!("02_idl_types.js"), + ), + ]; + for (url, source_code) in files { + isolate.execute(url, source_code).unwrap(); + } +} + +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<&str> { + let mut return_features: Vec<&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>, +} + +pub async fn op_webgpu_request_adapter( + state: Rc<RefCell<OpState>>, + args: RequestAdapterArgs, + _bufs: BufVec, +) -> Result<Value, AnyError> { + let mut state = state.borrow_mut(); + check_unstable(&state, "navigator.gpu.requestAdapter"); + let instance = 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(), + }, + 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(json!({ + "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 limits = json!({ + "maxBindGroups": adapter_limits.max_bind_groups, + "maxDynamicUniformBuffersPerPipelineLayout": adapter_limits.max_dynamic_uniform_buffers_per_pipeline_layout, + "maxDynamicStorageBuffersPerPipelineLayout": adapter_limits.max_dynamic_storage_buffers_per_pipeline_layout, + "maxSampledTexturesPerShaderStage": adapter_limits.max_sampled_textures_per_shader_stage, + "maxSamplersPerShaderStage": adapter_limits.max_samplers_per_shader_stage, + "maxStorageBuffersPerShaderStage": adapter_limits.max_storage_buffers_per_shader_stage, + "maxStorageTexturesPerShaderStage": adapter_limits.max_storage_textures_per_shader_stage, + "maxUniformBuffersPerShaderStage": adapter_limits.max_uniform_buffers_per_shader_stage, + "maxUniformBufferBindingSize": adapter_limits.max_uniform_buffer_binding_size + }); + + let rid = state.resource_table.add(WebGPUAdapter(adapter)); + + Ok(json!({ + "rid": rid, + "name": name, + "features": features, + "limits": limits + })) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct GPULimits { + _max_texture_dimension1d: Option<u32>, + _max_texture_dimension2d: Option<u32>, + _max_texture_dimension3d: 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>, + _max_vertex_buffers: Option<u32>, + _max_vertex_attributes: Option<u32>, + _max_vertex_buffer_array_stride: Option<u32>, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RequestDeviceArgs { + adapter_rid: u32, + label: Option<String>, + non_guaranteed_features: Option<Vec<String>>, + non_guaranteed_limits: Option<GPULimits>, +} + +pub async fn op_webgpu_request_device( + state: Rc<RefCell<OpState>>, + args: RequestDeviceArgs, + _bufs: BufVec, +) -> Result<Value, 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.non_guaranteed_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 + .non_guaranteed_limits + .map_or(Default::default(), |limits| wgpu_types::Limits { + 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_push_constant_size: 0, + }), + }; + + 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 json_limits = json!({ + "maxBindGroups": limits.max_bind_groups, + "maxDynamicUniformBuffersPerPipelineLayout": limits.max_dynamic_uniform_buffers_per_pipeline_layout, + "maxDynamicStorageBuffersPerPipelineLayout": limits.max_dynamic_storage_buffers_per_pipeline_layout, + "maxSampledTexturesPerShaderStage": limits.max_sampled_textures_per_shader_stage, + "maxSamplersPerShaderStage": limits.max_samplers_per_shader_stage, + "maxStorageBuffersPerShaderStage": limits.max_storage_buffers_per_shader_stage, + "maxStorageTexturesPerShaderStage": limits.max_storage_textures_per_shader_stage, + "maxUniformBuffersPerShaderStage": limits.max_uniform_buffers_per_shader_stage, + "maxUniformBufferBindingSize": limits.max_uniform_buffer_binding_size, + }); + + let rid = state.resource_table.add(WebGPUDevice(device)); + + Ok(json!({ + "rid": rid, + "features": features, + "limits": json_limits, + })) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CreateQuerySetArgs { + device_rid: u32, + _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, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result<Value, 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, + }; + + let (query_set, maybe_err) = gfx_select!(device => instance.device_create_query_set( + device, + &descriptor, + std::marker::PhantomData + )); + + let rid = state.resource_table.add(WebGPUQuerySet(query_set)); + + Ok(json!({ + "rid": rid, + "err": maybe_err.map(WebGPUError::from), + })) +} diff --git a/op_crates/webgpu/pipeline.rs b/op_crates/webgpu/pipeline.rs new file mode 100644 index 000000000..04493b18a --- /dev/null +++ b/op_crates/webgpu/pipeline.rs @@ -0,0 +1,643 @@ +// 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::serde_json::json; +use deno_core::serde_json::Value; +use deno_core::ZeroCopyBuf; +use deno_core::{OpState, Resource}; +use serde::Deserialize; +use std::borrow::Cow; + +use super::error::WebGPUError; + +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-color" => wgpu_types::BlendFactor::SrcColor, + "one-minus-src-color" => wgpu_types::BlendFactor::OneMinusSrcColor, + "src-alpha" => wgpu_types::BlendFactor::SrcAlpha, + "one-minus-src-alpha" => wgpu_types::BlendFactor::OneMinusSrcAlpha, + "dst-color" => wgpu_types::BlendFactor::DstColor, + "one-minus-dst-color" => wgpu_types::BlendFactor::OneMinusDstColor, + "dst-alpha" => wgpu_types::BlendFactor::DstAlpha, + "one-minus-dst-alpha" => wgpu_types::BlendFactor::OneMinusDstAlpha, + "src-alpha-saturated" => wgpu_types::BlendFactor::SrcAlphaSaturated, + "blend-color" => wgpu_types::BlendFactor::BlendColor, + "one-minus-blend-color" => wgpu_types::BlendFactor::OneMinusBlendColor, + _ => unreachable!(), + } +} + +fn serialize_blend_component( + blend: GPUBlendComponent, +) -> wgpu_types::BlendState { + wgpu_types::BlendState { + 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, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CreateComputePipelineArgs { + device_rid: u32, + label: Option<String>, + layout: Option<u32>, + compute: GPUProgrammableStage, +} + +pub fn op_webgpu_create_compute_pipeline( + state: &mut OpState, + args: CreateComputePipelineArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result<Value, 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), + }, + }; + 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(json!({ + "rid": rid, + "err": maybe_err.map(WebGPUError::from), + })) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ComputePipelineGetBindGroupLayoutArgs { + compute_pipeline_rid: u32, + index: u32, +} + +pub fn op_webgpu_compute_pipeline_get_bind_group_layout( + state: &mut OpState, + args: ComputePipelineGetBindGroupLayoutArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result<Value, 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(json!({ + "rid": rid, + "label": 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>, +} + +#[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>, + clamp_depth: Option<bool>, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct GPUVertexAttribute { + format: String, + offset: u64, + shader_location: u32, +} + +#[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: u32, + 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, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result<Value, 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 { + if let Some(buffer) = buffer { + 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 + .iter() + .map(|attribute| wgpu_types::VertexAttribute { + format: match attribute.format.as_str() { + "uchar2" => wgpu_types::VertexFormat::Uchar2, + "uchar4" => wgpu_types::VertexFormat::Uchar4, + "char2" => wgpu_types::VertexFormat::Char2, + "char4" => wgpu_types::VertexFormat::Char4, + "uchar2norm" => wgpu_types::VertexFormat::Uchar2Norm, + "uchar4norm" => wgpu_types::VertexFormat::Uchar4, + "char2norm" => wgpu_types::VertexFormat::Char2Norm, + "char4norm" => wgpu_types::VertexFormat::Char4Norm, + "ushort2" => wgpu_types::VertexFormat::Ushort2, + "ushort4" => wgpu_types::VertexFormat::Ushort4, + "short2" => wgpu_types::VertexFormat::Short2, + "short4" => wgpu_types::VertexFormat::Short4, + "ushort2norm" => wgpu_types::VertexFormat::Ushort2Norm, + "ushort4norm" => wgpu_types::VertexFormat::Ushort4Norm, + "short2norm" => wgpu_types::VertexFormat::Short2Norm, + "short4norm" => wgpu_types::VertexFormat::Short4Norm, + "half2" => wgpu_types::VertexFormat::Half2, + "half4" => wgpu_types::VertexFormat::Half4, + "float" => wgpu_types::VertexFormat::Float, + "float2" => wgpu_types::VertexFormat::Float2, + "float3" => wgpu_types::VertexFormat::Float3, + "float4" => wgpu_types::VertexFormat::Float4, + "uint" => wgpu_types::VertexFormat::Uint, + "uint2" => wgpu_types::VertexFormat::Uint2, + "uint3" => wgpu_types::VertexFormat::Uint3, + "uint4" => wgpu_types::VertexFormat::Uint4, + "int" => wgpu_types::VertexFormat::Int, + "int2" => wgpu_types::VertexFormat::Int2, + "int3" => wgpu_types::VertexFormat::Int3, + "int4" => wgpu_types::VertexFormat::Int4, + _ => unreachable!(), + }, + 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" => wgpu_types::CullMode::None, + "front" => wgpu_types::CullMode::Front, + "back" => wgpu_types::CullMode::Back, + _ => unreachable!(), + }, + None => wgpu_types::CullMode::None, + }, + polygon_mode: Default::default(), // native-only + } + }), + 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), + }, + clamp_depth: depth_stencil.clamp_depth.unwrap_or(false), + } + }), + 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 + .iter() + .map(|target| { + let blends = target.blend.clone().map(|blend| { + ( + serialize_blend_component(blend.alpha), + serialize_blend_component(blend.color), + ) + }); + + wgpu_types::ColorTargetState { + format: super::texture::serialize_texture_format( + &target.format, + ) + .unwrap(), + alpha_blend: blends + .clone() + .map_or(Default::default(), |states| states.0), + color_blend: blends + .map_or(Default::default(), |states| states.1), + 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(json!({ + "rid": rid, + "err": maybe_err.map(WebGPUError::from) + })) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderPipelineGetBindGroupLayoutArgs { + render_pipeline_rid: u32, + index: u32, +} + +pub fn op_webgpu_render_pipeline_get_bind_group_layout( + state: &mut OpState, + args: RenderPipelineGetBindGroupLayoutArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result<Value, 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(json!({ + "rid": rid, + "label": label, + "err": maybe_err.map(WebGPUError::from), + })) +} diff --git a/op_crates/webgpu/queue.rs b/op_crates/webgpu/queue.rs new file mode 100644 index 000000000..c141a3c45 --- /dev/null +++ b/op_crates/webgpu/queue.rs @@ -0,0 +1,157 @@ +// 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::serde_json::json; +use deno_core::serde_json::Value; +use deno_core::OpState; +use deno_core::ZeroCopyBuf; +use serde::Deserialize; + +use super::error::WebGPUError; + +type WebGPUQueue = super::WebGPUDevice; + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct QueueSubmitArgs { + queue_rid: u32, + command_buffers: Vec<u32>, +} + +pub fn op_webgpu_queue_submit( + state: &mut OpState, + args: QueueSubmitArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result<Value, 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(json!({ "err": maybe_err.map(WebGPUError::from) })) +} + +#[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: u32, + buffer: u32, + buffer_offset: u64, + data_offset: usize, + size: Option<usize>, +} + +pub fn op_webgpu_write_buffer( + state: &mut OpState, + args: QueueWriteBufferArgs, + zero_copy: &mut [ZeroCopyBuf], +) -> Result<Value, AnyError> { + 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[0][args.data_offset..(args.data_offset + size)], + None => &zero_copy[0][args.data_offset..], + }; + let maybe_err = gfx_select!(queue => instance.queue_write_buffer( + queue, + buffer, + args.buffer_offset, + data + )) + .err(); + + Ok(json!({ "err": maybe_err.map(WebGPUError::from) })) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct QueueWriteTextureArgs { + queue_rid: u32, + 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: &mut [ZeroCopyBuf], +) -> Result<Value, AnyError> { + 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::TextureCopyView { + 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::TextureDataLayout { + offset: args.data_layout.offset.unwrap_or(0), + bytes_per_row: args.data_layout.bytes_per_row.unwrap_or(0), + rows_per_image: args.data_layout.rows_per_image.unwrap_or(0), + }; + + let maybe_err = gfx_select!(queue => instance.queue_write_texture( + queue, + &destination, + &*zero_copy[0], + &data_layout, + &wgpu_types::Extent3d { + width: args.size.width.unwrap_or(1), + height: args.size.height.unwrap_or(1), + depth: args.size.depth.unwrap_or(1), + } + )) + .err(); + + Ok(json!({ "err": maybe_err.map(WebGPUError::from) })) +} diff --git a/op_crates/webgpu/render_pass.rs b/op_crates/webgpu/render_pass.rs new file mode 100644 index 000000000..c9018726b --- /dev/null +++ b/op_crates/webgpu/render_pass.rs @@ -0,0 +1,676 @@ +// 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::serde_json::json; +use deno_core::serde_json::Value; +use deno_core::ZeroCopyBuf; +use deno_core::{OpState, Resource}; +use serde::Deserialize; +use std::borrow::Cow; +use std::cell::RefCell; + +use super::error::WebGPUError; + +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: u32, + 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, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result<Value, 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(json!({})) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderPassSetScissorRectArgs { + render_pass_rid: u32, + x: u32, + y: u32, + width: u32, + height: u32, +} + +pub fn op_webgpu_render_pass_set_scissor_rect( + state: &mut OpState, + args: RenderPassSetScissorRectArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result<Value, 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(json!({})) +} + +#[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 RenderPassSetBlendColorArgs { + render_pass_rid: u32, + color: GPUColor, +} + +pub fn op_webgpu_render_pass_set_blend_color( + state: &mut OpState, + args: RenderPassSetBlendColorArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result<Value, 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_color( + &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(json!({})) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderPassSetStencilReferenceArgs { + render_pass_rid: u32, + reference: u32, +} + +pub fn op_webgpu_render_pass_set_stencil_reference( + state: &mut OpState, + args: RenderPassSetStencilReferenceArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result<Value, 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(json!({})) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderPassBeginPipelineStatisticsQueryArgs { + render_pass_rid: u32, + query_set: u32, + query_index: u32, +} + +pub fn op_webgpu_render_pass_begin_pipeline_statistics_query( + state: &mut OpState, + args: RenderPassBeginPipelineStatisticsQueryArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result<Value, 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)?; + + unsafe { + 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(json!({})) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderPassEndPipelineStatisticsQueryArgs { + render_pass_rid: u32, +} + +pub fn op_webgpu_render_pass_end_pipeline_statistics_query( + state: &mut OpState, + args: RenderPassEndPipelineStatisticsQueryArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result<Value, AnyError> { + 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_end_pipeline_statistics_query( + &mut render_pass_resource.0.borrow_mut(), + ); + } + + Ok(json!({})) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderPassWriteTimestampArgs { + render_pass_rid: u32, + query_set: u32, + query_index: u32, +} + +pub fn op_webgpu_render_pass_write_timestamp( + state: &mut OpState, + args: RenderPassWriteTimestampArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result<Value, 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)?; + + unsafe { + 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(json!({})) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderPassExecuteBundlesArgs { + render_pass_rid: u32, + bundles: Vec<u32>, +} + +pub fn op_webgpu_render_pass_execute_bundles( + state: &mut OpState, + args: RenderPassExecuteBundlesArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result<Value, 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(json!({})) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderPassEndPassArgs { + command_encoder_rid: u32, + render_pass_rid: u32, +} + +pub fn op_webgpu_render_pass_end_pass( + state: &mut OpState, + args: RenderPassEndPassArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result<Value, 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>(); + + let maybe_err = gfx_select!(command_encoder => instance.command_encoder_run_render_pass(command_encoder, render_pass)).err(); + + Ok(json!({ "err": maybe_err.map(WebGPUError::from) })) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderPassSetBindGroupArgs { + render_pass_rid: u32, + 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: &mut [ZeroCopyBuf], +) -> Result<Value, 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 (prefix, data, suffix) = unsafe { zero_copy[0].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(json!({})) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderPassPushDebugGroupArgs { + render_pass_rid: u32, + group_label: String, +} + +pub fn op_webgpu_render_pass_push_debug_group( + state: &mut OpState, + args: RenderPassPushDebugGroupArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result<Value, 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(json!({})) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderPassPopDebugGroupArgs { + render_pass_rid: u32, +} + +pub fn op_webgpu_render_pass_pop_debug_group( + state: &mut OpState, + args: RenderPassPopDebugGroupArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result<Value, 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(json!({})) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderPassInsertDebugMarkerArgs { + render_pass_rid: u32, + marker_label: String, +} + +pub fn op_webgpu_render_pass_insert_debug_marker( + state: &mut OpState, + args: RenderPassInsertDebugMarkerArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result<Value, 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(json!({})) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderPassSetPipelineArgs { + render_pass_rid: u32, + pipeline: u32, +} + +pub fn op_webgpu_render_pass_set_pipeline( + state: &mut OpState, + args: RenderPassSetPipelineArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result<Value, 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(json!({})) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderPassSetIndexBufferArgs { + render_pass_rid: u32, + buffer: u32, + index_format: String, + offset: u64, + size: u64, +} + +pub fn op_webgpu_render_pass_set_index_buffer( + state: &mut OpState, + args: RenderPassSetIndexBufferArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result<Value, 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)?; + + render_pass_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(json!({})) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderPassSetVertexBufferArgs { + render_pass_rid: u32, + slot: u32, + buffer: u32, + offset: u64, + size: u64, +} + +pub fn op_webgpu_render_pass_set_vertex_buffer( + state: &mut OpState, + args: RenderPassSetVertexBufferArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result<Value, 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)?; + + 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, + std::num::NonZeroU64::new(args.size), + ); + + Ok(json!({})) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderPassDrawArgs { + render_pass_rid: u32, + vertex_count: u32, + instance_count: u32, + first_vertex: u32, + first_instance: u32, +} + +pub fn op_webgpu_render_pass_draw( + state: &mut OpState, + args: RenderPassDrawArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result<Value, 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(json!({})) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderPassDrawIndexedArgs { + render_pass_rid: u32, + 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, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result<Value, 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(json!({})) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderPassDrawIndirectArgs { + render_pass_rid: u32, + indirect_buffer: u32, + indirect_offset: u64, +} + +pub fn op_webgpu_render_pass_draw_indirect( + state: &mut OpState, + args: RenderPassDrawIndirectArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result<Value, 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(json!({})) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderPassDrawIndexedIndirectArgs { + render_pass_rid: u32, + indirect_buffer: u32, + indirect_offset: u64, +} + +pub fn op_webgpu_render_pass_draw_indexed_indirect( + state: &mut OpState, + args: RenderPassDrawIndexedIndirectArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result<Value, 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(json!({})) +} diff --git a/op_crates/webgpu/sampler.rs b/op_crates/webgpu/sampler.rs new file mode 100644 index 000000000..48974a328 --- /dev/null +++ b/op_crates/webgpu/sampler.rs @@ -0,0 +1,129 @@ +// 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::serde_json::json; +use deno_core::serde_json::Value; +use deno_core::ZeroCopyBuf; +use deno_core::{OpState, Resource}; +use serde::Deserialize; +use std::borrow::Cow; + +use super::error::WebGPUError; + +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: u32, + 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, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result<Value, 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 + }; + + let (sampler, maybe_err) = gfx_select!(device => instance.device_create_sampler( + device, + &descriptor, + std::marker::PhantomData + )); + + let rid = state.resource_table.add(WebGPUSampler(sampler)); + + Ok(json!({ + "rid": rid, + "err": maybe_err.map(WebGPUError::from) + })) +} diff --git a/op_crates/webgpu/shader.rs b/op_crates/webgpu/shader.rs new file mode 100644 index 000000000..25adf9b95 --- /dev/null +++ b/op_crates/webgpu/shader.rs @@ -0,0 +1,77 @@ +// 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::serde_json::json; +use deno_core::serde_json::Value; +use deno_core::ZeroCopyBuf; +use deno_core::{OpState, Resource}; +use serde::Deserialize; +use std::borrow::Cow; + +use super::error::WebGPUError; + +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: u32, + 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: &mut [ZeroCopyBuf], +) -> Result<Value, 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 { + let (prefix, data, suffix) = zero_copy[0].align_to::<u32>(); + assert!(prefix.is_empty()); + assert!(suffix.is_empty()); + data + })), + }; + + let mut flags = wgpu_types::ShaderFlags::default(); + flags.set(wgpu_types::ShaderFlags::VALIDATION, true); + #[cfg(all(target_os = "macos", target_arch = "x86_64"))] + flags.set(wgpu_types::ShaderFlags::EXPERIMENTAL_TRANSLATION, true); + + let descriptor = wgpu_core::pipeline::ShaderModuleDescriptor { + label: args.label.map(Cow::from), + flags, + }; + + let (shader_module, maybe_err) = gfx_select!(device => instance.device_create_shader_module( + device, + &descriptor, + source, + std::marker::PhantomData + )); + + let rid = state.resource_table.add(WebGPUShaderModule(shader_module)); + + Ok(json!({ + "rid": rid, + "err": maybe_err.map(WebGPUError::from) + })) +} diff --git a/op_crates/webgpu/texture.rs b/op_crates/webgpu/texture.rs new file mode 100644 index 000000000..94de28fed --- /dev/null +++ b/op_crates/webgpu/texture.rs @@ -0,0 +1,256 @@ +// 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::serde_json::json; +use deno_core::serde_json::Value; +use deno_core::ZeroCopyBuf; +use deno_core::{OpState, Resource}; +use serde::Deserialize; +use std::borrow::Cow; + +use super::error::WebGPUError; +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: Option<u32>, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CreateTextureArgs { + device_rid: u32, + 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, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result<Value, 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: args.size.depth.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(), + }; + + let (texture, maybe_err) = gfx_select!(device => instance.device_create_texture( + device, + &descriptor, + std::marker::PhantomData + )); + + let rid = state.resource_table.add(WebGPUTexture(texture)); + + Ok(json!({ + "rid": rid, + "err": maybe_err.map(WebGPUError::from) + })) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CreateTextureViewArgs { + texture_rid: u32, + 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, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result<Value, 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)), + 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), + 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), + ), + }; + + let (texture_view, maybe_err) = gfx_select!(texture => instance.texture_create_view( + texture, + &descriptor, + std::marker::PhantomData + )); + + let rid = state.resource_table.add(WebGPUTextureView(texture_view)); + + Ok(json!({ + "rid": rid, + "err": maybe_err.map(WebGPUError::from) + })) +} diff --git a/op_crates/webgpu/webgpu.idl b/op_crates/webgpu/webgpu.idl new file mode 100644 index 000000000..598df7efb --- /dev/null +++ b/op_crates/webgpu/webgpu.idl @@ -0,0 +1,1023 @@ +interface mixin GPUObjectBase { + attribute USVString? label; +}; + +dictionary GPUObjectDescriptorBase { + USVString label; +}; + +interface GPUAdapterLimits { + readonly attribute GPUSize32 maxTextureDimension1D; + readonly attribute GPUSize32 maxTextureDimension2D; + readonly attribute GPUSize32 maxTextureDimension3D; + readonly attribute GPUSize32 maxTextureArrayLayers; + readonly attribute GPUSize32 maxBindGroups; + readonly attribute GPUSize32 maxDynamicUniformBuffersPerPipelineLayout; + readonly attribute GPUSize32 maxDynamicStorageBuffersPerPipelineLayout; + readonly attribute GPUSize32 maxSampledTexturesPerShaderStage; + readonly attribute GPUSize32 maxSamplersPerShaderStage; + readonly attribute GPUSize32 maxStorageBuffersPerShaderStage; + readonly attribute GPUSize32 maxStorageTexturesPerShaderStage; + readonly attribute GPUSize32 maxUniformBuffersPerShaderStage; + readonly attribute GPUSize32 maxUniformBufferBindingSize; + readonly attribute GPUSize32 maxStorageBufferBindingSize; + readonly attribute GPUSize32 maxVertexBuffers; + readonly attribute GPUSize32 maxVertexAttributes; + readonly attribute GPUSize32 maxVertexBufferArrayStride; +}; + +interface GPUAdapterFeatures { + readonly setlike<GPUFeatureName>; +}; + +[Exposed=Window] +partial interface Navigator { + [SameObject] readonly attribute GPU gpu; +}; + +[Exposed=DedicatedWorker] +partial interface WorkerNavigator { + [SameObject] readonly attribute GPU gpu; +}; + +[Exposed=(Window, DedicatedWorker)] +interface GPU { + Promise<GPUAdapter?> requestAdapter(optional GPURequestAdapterOptions options = {}); +}; + +dictionary GPURequestAdapterOptions { + GPUPowerPreference powerPreference; +}; + +enum GPUPowerPreference { + "low-power", + "high-performance" +}; + +interface GPUAdapter { + readonly attribute DOMString name; + [SameObject] readonly attribute GPUAdapterFeatures features; + [SameObject] readonly attribute GPUAdapterLimits limits; + + Promise<GPUDevice?> requestDevice(optional GPUDeviceDescriptor descriptor = {}); +}; + +dictionary GPUDeviceDescriptor : GPUObjectDescriptorBase { + sequence<GPUFeatureName> nonGuaranteedFeatures = []; + record<DOMString, GPUSize32> nonGuaranteedLimits = {}; +}; + +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 GPUAdapter adapter; + readonly attribute FrozenArray<GPUFeatureName> features; + readonly attribute object 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; + +[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; +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; +interface GPUMapMode { + const GPUFlagsConstant READ = 0x0001; + const GPUFlagsConstant WRITE = 0x0002; +}; + +[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; +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; +}; + +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", +}; + +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" +}; + +[Serializable] +interface GPUBindGroupLayout { +}; +GPUBindGroupLayout includes GPUObjectBase; + +dictionary GPUBindGroupLayoutDescriptor : GPUObjectDescriptorBase { + required sequence<GPUBindGroupLayoutEntry> entries; +}; + +typedef [EnforceRange] unsigned long GPUShaderStageFlags; +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"; +}; + +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; +}; + +[Serializable] +interface GPUPipelineLayout { +}; +GPUPipelineLayout includes GPUObjectBase; + +dictionary GPUPipelineLayoutDescriptor : GPUObjectDescriptorBase { + required sequence<GPUBindGroupLayout> bindGroupLayouts; +}; + +enum GPUCompilationMessageType { + "error", + "warning", + "info" +}; + +[Serializable] +interface GPUCompilationMessage { + readonly attribute DOMString message; + readonly attribute GPUCompilationMessageType type; + readonly attribute unsigned long long lineNum; + readonly attribute unsigned long long linePos; +}; + +[Serializable] +interface GPUCompilationInfo { + readonly attribute FrozenArray<GPUCompilationMessage> messages; +}; + +[Serializable] +interface GPUShaderModule { + Promise<GPUCompilationInfo> compilationInfo(); +}; +GPUShaderModule includes GPUObjectBase; + +dictionary GPUShaderModuleDescriptor : GPUObjectDescriptorBase { + required USVString code; + object sourceMap; +}; + +dictionary GPUPipelineDescriptorBase : GPUObjectDescriptorBase { + GPUPipelineLayout layout; +}; + +interface mixin GPUPipelineBase { + GPUBindGroupLayout getBindGroupLayout(unsigned long index); +}; + +dictionary GPUProgrammableStage { + required GPUShaderModule module; + required USVString entryPoint; +}; + +[Serializable] +interface GPUComputePipeline { +}; +GPUComputePipeline includes GPUObjectBase; +GPUComputePipeline includes GPUPipelineBase; + +dictionary GPUComputePipelineDescriptor : GPUPipelineDescriptorBase { + required GPUProgrammableStage compute; +}; + +[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"; +}; + +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; +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-color", + "one-minus-src-color", + "src-alpha", + "one-minus-src-alpha", + "dst-color", + "one-minus-dst-color", + "dst-alpha", + "one-minus-dst-alpha", + "src-alpha-saturated", + "blend-color", + "one-minus-blend-color" +}; + +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; + + // Enable depth clamping (requires "depth-clamping" feature) + boolean clampDepth = false; +}; + +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 { + "uchar2", + "uchar4", + "char2", + "char4", + "uchar2norm", + "uchar4norm", + "char2norm", + "char4norm", + "ushort2", + "ushort4", + "short2", + "short4", + "ushort2norm", + "ushort4norm", + "short2norm", + "short4norm", + "half2", + "half4", + "float", + "float2", + "float3", + "float4", + "uint", + "uint2", + "uint3", + "uint4", + "int", + "int2", + "int3", + "int4" +}; + +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; +}; + +interface GPUCommandBuffer { + readonly attribute Promise<double> executionTime; +}; +GPUCommandBuffer includes GPUObjectBase; + +dictionary GPUCommandBufferDescriptor : GPUObjectDescriptorBase { +}; + +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); +}; + +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 = 0); + undefined setVertexBuffer(GPUIndex32 slot, GPUBuffer buffer, optional GPUSize64 offset = 0, optional GPUSize64 size = 0); + + 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); +}; + +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 setBlendColor(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; + GPUStoreOp storeOp = "store"; +}; + +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", + "clear" +}; + +interface GPURenderBundle { +}; +GPURenderBundle includes GPUObjectBase; + +dictionary GPURenderBundleDescriptor : GPUObjectDescriptorBase { +}; + +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; +}; + +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; + +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", +}; + +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" +}; + +interface GPUOutOfMemoryError { + constructor(); +}; + +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 { + GPUIntegerCoordinate width = 1; + GPUIntegerCoordinate height = 1; + GPUIntegerCoordinate depthOrArrayLayers = 1; +}; +typedef (sequence<GPUIntegerCoordinate> or GPUExtent3DDict) GPUExtent3D; |