feat(deno): surface support (#3265)

This commit is contained in:
Leo Kettmeir 2023-01-09 16:44:20 +01:00 committed by GitHub
parent ad4dac87fb
commit 2252b1209a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 408 additions and 15 deletions

1
Cargo.lock generated
View File

@ -628,6 +628,7 @@ name = "deno_webgpu"
version = "0.81.0"
dependencies = [
"deno_core",
"raw-window-handle 0.5.0",
"serde",
"tokio",
"wgpu-core",

View File

@ -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

View File

@ -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,

View File

@ -0,0 +1,135 @@
// Copyright 2018-2022 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 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);

View File

@ -0,0 +1,77 @@
// Copyright 2018-2022 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 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);

View File

@ -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<ClearError> for WebGpuError {
}
}
#[cfg(feature = "surface")]
impl From<ConfigureSurfaceError> for WebGpuError {
fn from(err: ConfigureSurfaceError) -> Self {
WebGpuError::Validation(fmt_err(&err))
}
}
#[derive(Debug)]
pub struct DomExceptionOperationError {
pub msg: String,

View File

@ -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::<wgpu_core::api::Vulkan>( $($param),* ),
#[cfg(target_os = "macos")]
#[cfg(all(not(target_arch = "wasm32"), any(target_os = "ios", target_os = "macos")))]
wgpu_types::Backend::Metal => $global.$method::<wgpu_core::api::Metal>( $($param),* ),
#[cfg(windows)]
#[cfg(all(not(target_arch = "wasm32"), windows))]
wgpu_types::Backend::Dx12 => $global.$method::<wgpu_core::api::Dx12>( $($param),* ),
#[cfg(all(unix, not(target_os = "macos")))]
#[cfg(all(not(target_arch = "wasm32"), windows))]
wgpu_types::Backend::Dx11 => $global.$method::<wgpu_core::api::Dx11>( $($param),* ),
#[cfg(any(
all(unix, not(target_os = "macos"), not(target_os = "ios")),
feature = "angle",
target_arch = "wasm32"
))]
wgpu_types::Backend::Gl => $global.$method::<wgpu_core::api::Gles>( $($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<wgpu_core::hub::IdentityManagerFactory>;
pub type Instance = wgpu_core::hub::Global<wgpu_core::hub::IdentityManagerFactory>;
struct WebGpuAdapter(wgpu_core::id::AdapterId);
impl Resource for WebGpuAdapter {

130
deno_webgpu/src/surface.rs Normal file
View File

@ -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::<super::UnstableChecker>();
// 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<str> {
"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<wgpu_types::PresentMode>,
alpha_mode: wgpu_types::CompositeAlphaMode,
}
#[op]
pub fn op_webgpu_surface_configure(
state: &mut OpState,
args: SurfaceConfigureArgs,
) -> Result<WebGpuResult, AnyError> {
let instance = state.borrow::<super::Instance>();
let device_resource = state
.resource_table
.get::<super::WebGpuDevice>(args.device_rid)?;
let device = device_resource.0;
let surface_resource = state
.resource_table
.get::<WebGpuSurface>(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<WebGpuResult, AnyError> {
let instance = state.borrow::<super::Instance>();
let device_resource = state
.resource_table
.get::<super::WebGpuDevice>(device_rid)?;
let device = device_resource.0;
let surface_resource = state.resource_table.get::<WebGpuSurface>(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::<super::Instance>();
let device_resource = state
.resource_table
.get::<super::WebGpuDevice>(device_rid)?;
let device = device_resource.0;
let surface_resource = state.resource_table.get::<WebGpuSurface>(surface_rid)?;
let surface = surface_resource.0;
let _ = gfx_select!(device => instance.surface_present(surface))?;
Ok(())
}

View File

@ -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"
};

View File

@ -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` automaticallydepending on the
/// `alpha_mode` that the current surface can support.