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.