Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(webgpu): external webgpu surfaces / byow #21835

Merged
merged 14 commits into from
Jan 19, 2024
29 changes: 25 additions & 4 deletions ext/webgpu/02_surface.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import { core, primordials } from "ext:core/mod.js";
const ops = core.ops;
import * as webidl from "ext:deno_webidl/00_webidl.js";
import { createFilteredInspectProxy } from "ext:deno_console/01_console.js";
const { Symbol, SymbolFor, ObjectPrototypeIsPrototypeOf } = primordials;
const { Symbol, SymbolFor, ObjectPrototypeIsPrototypeOf, TypeError } =
primordials;
import { loadWebGPU, webgpu } from "ext:deno_webgpu/00_init.js";

const _surfaceRid = Symbol("[[surfaceRid]]");
Expand Down Expand Up @@ -157,8 +158,28 @@ function createCanvasContext(options) {
return canvasContext;
}

function presentGPUCanvasContext(ctx) {
ctx[_present]();
// External webgpu surfaces

// TODO(@littledivy): This will extend `OffscreenCanvas` when we add it.
class UnsafeWindowSurface {
#ctx;
#surfaceRid;

constructor(system, win, display) {
this.#surfaceRid = ops.op_webgpu_surface_create(system, win, display);
}

getContext(context) {
if (context !== "webgpu") {
throw new TypeError("Only 'webgpu' context is supported.");
}
this.#ctx = createCanvasContext({ surfaceRid: this.#surfaceRid });
return this.#ctx;
}

present() {
this.#ctx[_present]();
}
}

export { createCanvasContext, GPUCanvasContext, presentGPUCanvasContext };
export { GPUCanvasContext, UnsafeWindowSurface };
2 changes: 1 addition & 1 deletion ext/webgpu/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ 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 }
raw-window-handle = { workspace = true }

[target.'cfg(not(target_arch = "wasm32"))'.dependencies.wgpu-core]
workspace = true
Expand Down
127 changes: 127 additions & 0 deletions ext/webgpu/byow.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.

use deno_core::error::type_error;
use deno_core::error::AnyError;
use deno_core::op2;
use deno_core::OpState;
use deno_core::ResourceId;
use std::ffi::c_void;

use crate::surface::WebGpuSurface;

#[op2(fast)]
#[smi]
pub fn op_webgpu_surface_create(
state: &mut OpState,
#[string] system: &str,
p1: *const c_void,
p2: *const c_void,
) -> Result<ResourceId, AnyError> {
littledivy marked this conversation as resolved.
Show resolved Hide resolved
let instance = state.borrow::<super::Instance>();
// Security note:
//
// The `p1` and `p2` parameters are pointers to platform-specific window
// handles.
//
// The code below works under the assumption that:
//
// - handles can only be created by the FFI interface which
// enforces --allow-ffi.
//
// - `*const c_void` deserizalizes null and v8::External.
//
// - Only FFI can create v8::External.
littledivy marked this conversation as resolved.
Show resolved Hide resolved
if p1.is_null() {
return Err(type_error("Invalid parameters"));
}

let (win_handle, display_handle) = raw_window(system, p1, p2)?;
let surface =
instance.instance_create_surface(display_handle, win_handle, ());

let rid = state
.resource_table
.add(WebGpuSurface(instance.clone(), surface));
Ok(rid)
}

type RawHandles = (
raw_window_handle::RawWindowHandle,
raw_window_handle::RawDisplayHandle,
);

#[cfg(target_os = "macos")]
fn raw_window(
system: &str,
ns_window: *const c_void,
ns_view: *const c_void,
) -> Result<RawHandles, AnyError> {
if system != "cocoa" {
return Err(type_error("Invalid system on macOS"));
}

let win_handle = {
let mut handle = raw_window_handle::AppKitWindowHandle::empty();
handle.ns_window = ns_window as *mut c_void;
littledivy marked this conversation as resolved.
Show resolved Hide resolved
handle.ns_view = ns_view as *mut c_void;

raw_window_handle::RawWindowHandle::AppKit(handle)
};
let display_handle = raw_window_handle::RawDisplayHandle::AppKit(
raw_window_handle::AppKitDisplayHandle::empty(),
);
Ok((win_handle, display_handle))
}

#[cfg(target_os = "windows")]
fn raw_window(
system: &str,
window: *const c_void,
hinstance: *const c_void,
) -> Result<RawHandles, AnyError> {
use raw_window_handle::WindowsDisplayHandle;
if system != "win32" {
return Err(type_error("Invalid system on Windows"));
}

let win_handle = {
use raw_window_handle::Win32WindowHandle;

let mut handle = Win32WindowHandle::empty();
handle.hwnd = window as *mut c_void;
littledivy marked this conversation as resolved.
Show resolved Hide resolved
handle.hinstance = hinstance as *mut c_void;

raw_window_handle::RawWindowHandle::Win32(handle)
};

let display_handle =
raw_window_handle::RawDisplayHandle::Windows(WindowsDisplayHandle::empty());
Ok((win_handle, display_handle))
}

#[cfg(target_os = "linux")]
fn raw_window(
system: &str,
window: *const c_void,
display: *const c_void,
) -> Result<RawHandles, AnyError> {
if system != "x11" {
return Err(type_error("Invalid system on Linux"));
}

let win_handle = {
let mut handle = raw_window_handle::XlibWindowHandle::empty();
handle.window = window as *mut c_void as _;

raw_window_handle::RawWindowHandle::Xlib(handle)
};

let display_handle = {
let mut handle = raw_window_handle::XlibDisplayHandle::empty();
handle.display = display as *mut c_void;

raw_window_handle::RawDisplayHandle::Xlib(handle)
};

Ok((win_handle, display_handle))
}
5 changes: 4 additions & 1 deletion ext/webgpu/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ mod macros {
pub mod binding;
pub mod buffer;
pub mod bundle;
pub mod byow;
pub mod command_encoder;
pub mod compute_pass;
pub mod error;
Expand Down Expand Up @@ -214,7 +215,9 @@ deno_core::extension!(
// surface
surface::op_webgpu_surface_configure,
surface::op_webgpu_surface_get_current_texture,
surface::op_webgpu_surface_present
surface::op_webgpu_surface_present,
// byow
byow::op_webgpu_surface_create,
],
esm = ["00_init.js", "02_surface.js"],
lazy_loaded_esm = ["01_webgpu.js"],
Expand Down
6 changes: 5 additions & 1 deletion runtime/js/90_deno_ns.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import * as tty from "ext:runtime/40_tty.js";
import * as httpRuntime from "ext:runtime/40_http.js";
import * as kv from "ext:deno_kv/01_db.ts";
import * as cron from "ext:deno_cron/01_cron.ts";
import * as webgpuSurface from "ext:deno_webgpu/02_surface.js";

const denoNs = {
metrics: core.metrics,
Expand Down Expand Up @@ -217,7 +218,9 @@ denoNsUnstableById[unstableIds.net] = {

// denoNsUnstableById[unstableIds.unsafeProto] = {}

// denoNsUnstableById[unstableIds.webgpu] = {}
denoNsUnstableById[unstableIds.webgpu] = {
UnsafeWindowSurface: webgpuSurface.UnsafeWindowSurface,
};

// denoNsUnstableById[unstableIds.workerOptions] = {}

Expand All @@ -237,6 +240,7 @@ const denoNsUnstable = {
UnsafePointer: ffi.UnsafePointer,
UnsafePointerView: ffi.UnsafePointerView,
UnsafeFnPointer: ffi.UnsafeFnPointer,
UnsafeWindowSurface: webgpuSurface.UnsafeWindowSurface,
flock: fs.flock,
flockSync: fs.flockSync,
funlock: fs.funlock,
Expand Down