From 2252b1209aa012d4be94e4c151e56884bd3067fe Mon Sep 17 00:00:00 2001 From: Leo Kettmeir Date: Mon, 9 Jan 2023 16:44:20 +0100 Subject: [PATCH] feat(deno): surface support (#3265) --- Cargo.lock | 1 + deno_webgpu/Cargo.toml | 4 + deno_webgpu/src/01_webgpu.js | 20 +++- deno_webgpu/src/03_surface.js | 135 ++++++++++++++++++++++++ deno_webgpu/src/04_surface_idl_types.js | 77 ++++++++++++++ deno_webgpu/src/error.rs | 9 ++ deno_webgpu/src/lib.rs | 21 +++- deno_webgpu/src/surface.rs | 130 +++++++++++++++++++++++ deno_webgpu/webgpu.idl | 25 ++++- wgpu-types/src/lib.rs | 1 + 10 files changed, 408 insertions(+), 15 deletions(-) create mode 100644 deno_webgpu/src/03_surface.js create mode 100644 deno_webgpu/src/04_surface_idl_types.js create mode 100644 deno_webgpu/src/surface.rs diff --git a/Cargo.lock b/Cargo.lock index cb01f676ed..981c1ca445 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -628,6 +628,7 @@ name = "deno_webgpu" version = "0.81.0" dependencies = [ "deno_core", + "raw-window-handle 0.5.0", "serde", "tokio", "wgpu-core", diff --git a/deno_webgpu/Cargo.toml b/deno_webgpu/Cargo.toml index 4a47a9418e..628adbd6c3 100644 --- a/deno_webgpu/Cargo.toml +++ b/deno_webgpu/Cargo.toml @@ -10,11 +10,15 @@ readme = "README.md" repository.workspace = true description = "WebGPU implementation for Deno" +[features] +surface = ["wgpu-core/raw-window-handle", "dep:raw-window-handle"] + [dependencies] deno_core.workspace = true serde = { workspace = true, features = ["derive"] } tokio = { workspace = true, features = ["full"] } wgpu-types = { workspace = true, features = ["trace", "replay", "serde"] } +raw-window-handle = { workspace = true, optional = true } [dependencies.wgpu-core] workspace = true diff --git a/deno_webgpu/src/01_webgpu.js b/deno_webgpu/src/01_webgpu.js index 373fe58a8b..d857bd2b42 100644 --- a/deno_webgpu/src/01_webgpu.js +++ b/deno_webgpu/src/01_webgpu.js @@ -188,10 +188,13 @@ } } + const illegalConstructorKey = Symbol("illegalConstructorKey"); class GPUError extends Error { - constructor() { + constructor(key = null) { super(); - webidl.illegalConstructor(); + if (key !== illegalConstructorKey) { + webidl.illegalConstructor(); + } } [_message]; @@ -212,7 +215,9 @@ prefix, context: "Argument 1", }); - super(message); + super(illegalConstructorKey); + this[webidl.brand] = webidl.brand; + this[_message] = message; } } const GPUValidationErrorPrototype = GPUValidationError.prototype; @@ -226,7 +231,9 @@ prefix, context: "Argument 1", }); - super(message); + super(illegalConstructorKey); + this[webidl.brand] = webidl.brand; + this[_message] = message; } } const GPUOutOfMemoryErrorPrototype = GPUOutOfMemoryError.prototype; @@ -347,7 +354,7 @@ rid, adapter: this, features: createGPUSupportedFeatures(features), - limits: createGPUSupportedFeatures(limits), + limits: createGPUSupportedLimits(limits), }); return createGPUDevice( descriptor.label, @@ -5251,6 +5258,9 @@ const GPUQuerySetPrototype = GPUQuerySet.prototype; window.__bootstrap.webgpu = { + _device, + assertDevice, + createGPUTexture, gpu: webidl.createBranded(GPU), GPU, GPUAdapter, diff --git a/deno_webgpu/src/03_surface.js b/deno_webgpu/src/03_surface.js new file mode 100644 index 0000000000..bd3da8cd37 --- /dev/null +++ b/deno_webgpu/src/03_surface.js @@ -0,0 +1,135 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. + +// @ts-check +/// +/// +/// +/// + +"use strict"; + +((window) => { + const core = window.Deno.core; + const ops = core.ops; + const webidl = window.__bootstrap.webidl; + const { Symbol } = window.__bootstrap.primordials; + const { _device, assertDevice, createGPUTexture } = window.__bootstrap.webgpu; + + const _surfaceRid = Symbol("[[surfaceRid]]"); + const _configuration = Symbol("[[configuration]]"); + const _canvas = Symbol("[[canvas]]"); + const _currentTexture = Symbol("[[currentTexture]]"); + class GPUCanvasContext { + /** @type {number} */ + [_surfaceRid]; + /** @type {InnerGPUDevice} */ + [_device]; + [_configuration]; + [_canvas]; + /** @type {GPUTexture | undefined} */ + [_currentTexture]; + + get canvas() { + webidl.assertBranded(this, GPUCanvasContextPrototype); + return this[_canvas]; + } + + constructor() { + webidl.illegalConstructor(); + } + + configure(configuration) { + webidl.assertBranded(this, GPUCanvasContextPrototype); + const prefix = "Failed to execute 'configure' on 'GPUCanvasContext'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + configuration = webidl.converters.GPUCanvasConfiguration(configuration, { + prefix, + context: "Argument 1", + }); + + this[_device] = configuration.device[_device]; + this[_configuration] = configuration; + const device = assertDevice(this, { prefix, context: "configuration.device" }); + + const { err } = ops.op_webgpu_surface_configure({ + surfaceRid: this[_surfaceRid], + deviceRid: device.rid, + format: configuration.format, + usage: configuration.usage, + width: configuration.width, + height: configuration.height, + alphaMode: configuration.alphaMode, + }); + + device.pushError(err); + } + + unconfigure() { + webidl.assertBranded(this, GPUCanvasContextPrototype); + + this[_configuration] = null; + this[_device] = null; + } + + getCurrentTexture() { + webidl.assertBranded(this, GPUCanvasContextPrototype); + const prefix = "Failed to execute 'getCurrentTexture' on 'GPUCanvasContext'"; + + if (this[_configuration] === null) { + throw new DOMException("context is not configured.", "InvalidStateError"); + } + + const device = assertDevice(this, { prefix, context: "this" }); + + if (this[_currentTexture]) { + return this[_currentTexture]; + } + + const { rid } = ops.op_webgpu_surface_get_current_texture(device.rid, this[_surfaceRid]); + + const texture = createGPUTexture( + { + size: { + width: this[_configuration].width, + height: this[_configuration].height, + depthOrArrayLayers: 1, + }, + mipLevelCount: 1, + sampleCount: 1, + dimension: "2d", + format: this[_configuration].format, + usage: this[_configuration].usage, + }, + device, + rid, + ); + device.trackResource(texture); + this[_currentTexture] = texture; + return texture; + } + + // Extended from spec. Required to present the texture; browser don't need this. + present() { + webidl.assertBranded(this, GPUCanvasContextPrototype); + const prefix = "Failed to execute 'present' on 'GPUCanvasContext'"; + const device = assertDevice(this[_currentTexture], { prefix, context: "this" }); + ops.op_webgpu_surface_present(device.rid, this[_surfaceRid]); + this[_currentTexture].destroy(); + this[_currentTexture] = undefined; + } + } + const GPUCanvasContextPrototype = GPUCanvasContext.prototype; + + function createCanvasContext(options) { + const canvasContext = webidl.createBranded(GPUCanvasContext); + canvasContext[_surfaceRid] = options.surfaceRid; + canvasContext[_canvas] = options.canvas; + return canvasContext; + } + + window.__bootstrap.webgpu = { + ...window.__bootstrap.webgpu, + GPUCanvasContext, + createCanvasContext, + }; +})(this); diff --git a/deno_webgpu/src/04_surface_idl_types.js b/deno_webgpu/src/04_surface_idl_types.js new file mode 100644 index 0000000000..942c2a5b2b --- /dev/null +++ b/deno_webgpu/src/04_surface_idl_types.js @@ -0,0 +1,77 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. + +// @ts-check +/// +/// +/// +/// + +"use strict"; + +((window) => { + const webidl = window.__bootstrap.webidl; + const { GPUTextureUsage } = window.__bootstrap.webgpu; + + // ENUM: GPUCanvasAlphaMode + webidl.converters["GPUCanvasAlphaMode"] = webidl.createEnumConverter( + "GPUCanvasAlphaMode", + [ + "opaque", + "premultiplied", + ], + ); + + // NON-SPEC: ENUM: GPUPresentMode + webidl.converters["GPUPresentMode"] = webidl.createEnumConverter( + "GPUPresentMode", + [ + "autoVsync", + "autoNoVsync", + "fifo", + "fifoRelaxed", + "immediate", + "mailbox", + ], + ); + + // DICT: GPUCanvasConfiguration + const dictMembersGPUCanvasConfiguration = [ + { key: "device", converter: webidl.converters.GPUDevice, required: true }, + { + key: "format", + converter: webidl.converters.GPUTextureFormat, + required: true, + }, + { + key: "usage", + converter: webidl.converters["GPUTextureUsageFlags"], + defaultValue: GPUTextureUsage.RENDER_ATTACHMENT, + }, + { + key: "alphaMode", + converter: webidl.converters["GPUCanvasAlphaMode"], + defaultValue: "opaque", + }, + + // Extended from spec + { + key: "presentMode", + converter: webidl.converters["GPUPresentMode"], + }, + { + key: "width", + converter: webidl.converters["long"], + required: true, + }, + { + key: "height", + converter: webidl.converters["long"], + required: true, + }, + ]; + webidl.converters["GPUCanvasConfiguration"] = webidl + .createDictionaryConverter( + "GPUCanvasConfiguration", + dictMembersGPUCanvasConfiguration, + ); +})(this); diff --git a/deno_webgpu/src/error.rs b/deno_webgpu/src/error.rs index 541fa9d908..f9903579f9 100644 --- a/deno_webgpu/src/error.rs +++ b/deno_webgpu/src/error.rs @@ -23,6 +23,8 @@ use wgpu_core::device::DeviceError; use wgpu_core::pipeline::CreateComputePipelineError; use wgpu_core::pipeline::CreateRenderPipelineError; use wgpu_core::pipeline::CreateShaderModuleError; +#[cfg(feature = "surface")] +use wgpu_core::present::ConfigureSurfaceError; use wgpu_core::resource::BufferAccessError; use wgpu_core::resource::CreateBufferError; use wgpu_core::resource::CreateQuerySetError; @@ -275,6 +277,13 @@ impl From for WebGpuError { } } +#[cfg(feature = "surface")] +impl From for WebGpuError { + fn from(err: ConfigureSurfaceError) -> Self { + WebGpuError::Validation(fmt_err(&err)) + } +} + #[derive(Debug)] pub struct DomExceptionOperationError { pub msg: String, diff --git a/deno_webgpu/src/lib.rs b/deno_webgpu/src/lib.rs index 287e340920..bdd64b3c5f 100644 --- a/deno_webgpu/src/lib.rs +++ b/deno_webgpu/src/lib.rs @@ -27,13 +27,22 @@ mod macros { macro_rules! gfx_select { ($id:expr => $global:ident.$method:ident( $($param:expr),* )) => { match $id.backend() { - #[cfg(not(target_os = "macos"))] + #[cfg(any( + all(not(target_arch = "wasm32"), not(target_os = "ios"), not(target_os = "macos")), + feature = "vulkan-portability" + ))] wgpu_types::Backend::Vulkan => $global.$method::( $($param),* ), - #[cfg(target_os = "macos")] + #[cfg(all(not(target_arch = "wasm32"), any(target_os = "ios", target_os = "macos")))] wgpu_types::Backend::Metal => $global.$method::( $($param),* ), - #[cfg(windows)] + #[cfg(all(not(target_arch = "wasm32"), windows))] wgpu_types::Backend::Dx12 => $global.$method::( $($param),* ), - #[cfg(all(unix, not(target_os = "macos")))] + #[cfg(all(not(target_arch = "wasm32"), windows))] + wgpu_types::Backend::Dx11 => $global.$method::( $($param),* ), + #[cfg(any( + all(unix, not(target_os = "macos"), not(target_os = "ios")), + feature = "angle", + target_arch = "wasm32" + ))] wgpu_types::Backend::Gl => $global.$method::( $($param),+ ), other => panic!("Unexpected backend {:?}", other), } @@ -67,6 +76,8 @@ pub mod queue; pub mod render_pass; pub mod sampler; pub mod shader; +#[cfg(feature = "surface")] +pub mod surface; pub mod texture; pub struct Unstable(pub bool); @@ -82,7 +93,7 @@ fn check_unstable(state: &OpState, api_name: &str) { } } -type Instance = wgpu_core::hub::Global; +pub type Instance = wgpu_core::hub::Global; struct WebGpuAdapter(wgpu_core::id::AdapterId); impl Resource for WebGpuAdapter { diff --git a/deno_webgpu/src/surface.rs b/deno_webgpu/src/surface.rs new file mode 100644 index 0000000000..8a476667d4 --- /dev/null +++ b/deno_webgpu/src/surface.rs @@ -0,0 +1,130 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. + +use super::WebGpuResult; +use deno_core::error::AnyError; +use deno_core::include_js_files; +use deno_core::op; +use deno_core::Extension; +use deno_core::OpState; +use deno_core::Resource; +use deno_core::ResourceId; +use serde::Deserialize; +use std::borrow::Cow; +use wgpu_types::SurfaceStatus; + +pub fn init_surface(unstable: bool) -> Extension { + Extension::builder() + .js(include_js_files!( + prefix "deno:deno_webgpu", + "03_surface.js", + "04_surface_idl_types.js", + )) + .ops(vec![ + op_webgpu_surface_configure::decl(), + op_webgpu_surface_get_current_texture::decl(), + op_webgpu_surface_present::decl(), + ]) + .state(move |state| { + // TODO: check & possibly streamline this + // Unstable might be able to be OpMiddleware + // let unstable_checker = state.borrow::(); + // let unstable = unstable_checker.unstable; + state.put(super::Unstable(unstable)); + Ok(()) + }) + .build() +} + +pub struct WebGpuSurface(pub wgpu_core::id::SurfaceId); +impl Resource for WebGpuSurface { + fn name(&self) -> Cow { + "webGPUSurface".into() + } +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SurfaceConfigureArgs { + surface_rid: ResourceId, + device_rid: ResourceId, + format: wgpu_types::TextureFormat, + usage: u32, + width: u32, + height: u32, + present_mode: Option, + alpha_mode: wgpu_types::CompositeAlphaMode, +} + +#[op] +pub fn op_webgpu_surface_configure( + state: &mut OpState, + args: SurfaceConfigureArgs, +) -> Result { + let instance = state.borrow::(); + let device_resource = state + .resource_table + .get::(args.device_rid)?; + let device = device_resource.0; + let surface_resource = state + .resource_table + .get::(args.surface_rid)?; + let surface = surface_resource.0; + + let conf = wgpu_types::SurfaceConfiguration { + usage: wgpu_types::TextureUsages::from_bits_truncate(args.usage), + format: args.format, + width: args.width, + height: args.height, + present_mode: args.present_mode.unwrap_or_default(), + alpha_mode: args.alpha_mode, + }; + + let err = gfx_select!(device => instance.surface_configure(surface, device, &conf)); + + Ok(WebGpuResult::maybe_err(err)) +} + +#[op] +pub fn op_webgpu_surface_get_current_texture( + state: &mut OpState, + device_rid: ResourceId, + surface_rid: ResourceId, +) -> Result { + let instance = state.borrow::(); + let device_resource = state + .resource_table + .get::(device_rid)?; + let device = device_resource.0; + let surface_resource = state.resource_table.get::(surface_rid)?; + let surface = surface_resource.0; + + let output = gfx_select!(device => instance.surface_get_current_texture(surface, ()))?; + + match output.status { + SurfaceStatus::Good | SurfaceStatus::Suboptimal => { + let id = output.texture_id.unwrap(); + let rid = state.resource_table.add(crate::texture::WebGpuTexture(id)); + Ok(WebGpuResult::rid(rid)) + } + _ => Err(AnyError::msg("Invalid Surface Status")), + } +} + +#[op] +pub fn op_webgpu_surface_present( + state: &mut OpState, + device_rid: ResourceId, + surface_rid: ResourceId, +) -> Result<(), AnyError> { + let instance = state.borrow::(); + let device_resource = state + .resource_table + .get::(device_rid)?; + let device = device_resource.0; + let surface_resource = state.resource_table.get::(surface_rid)?; + let surface = surface_resource.0; + + let _ = gfx_select!(device => instance.surface_present(surface))?; + + Ok(()) +} diff --git a/deno_webgpu/webgpu.idl b/deno_webgpu/webgpu.idl index 30b99a67c8..7d5da0a878 100644 --- a/deno_webgpu/webgpu.idl +++ b/deno_webgpu/webgpu.idl @@ -374,11 +374,6 @@ interface GPUExternalTexture { }; GPUExternalTexture includes GPUObjectBase; -dictionary GPUExternalTextureDescriptor : GPUObjectDescriptorBase { - required HTMLVideoElement source; - PredefinedColorSpace colorSpace = "srgb"; -}; - [Exposed=(Window, DedicatedWorker), SecureContext] interface GPUSampler { }; @@ -1068,6 +1063,26 @@ enum GPUPipelineStatisticName { "compute-shader-invocations", }; +[Exposed=(Window, DedicatedWorker), SecureContext] +interface GPUCanvasContext { + undefined configure(GPUCanvasConfiguration configuration); + undefined unconfigure(); + + GPUTexture getCurrentTexture(); +}; + +enum GPUCanvasAlphaMode { + "opaque", + "premultiplied" +}; + +dictionary GPUCanvasConfiguration { + required GPUDevice device; + required GPUTextureFormat format; + GPUTextureUsageFlags usage = 0x10; // GPUTextureUsage.RENDER_ATTACHMENT + GPUCanvasAlphaMode alphaMode = "opaque"; +}; + enum GPUDeviceLostReason { "destroyed" }; diff --git a/wgpu-types/src/lib.rs b/wgpu-types/src/lib.rs index 373be2d75e..681256e938 100644 --- a/wgpu-types/src/lib.rs +++ b/wgpu-types/src/lib.rs @@ -3847,6 +3847,7 @@ pub enum PresentMode { #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[cfg_attr(feature = "trace", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] pub enum CompositeAlphaMode { /// Chooses either `Opaque` or `Inherit` automatically,depending on the /// `alpha_mode` that the current surface can support.