feat:: External webgpu surfaces / BYOW (#21835)

This PR contains the implementation of the External webgpu surfaces /
BYOW proposal. BYOW stands for "Bring your own window".

Closes #21713 

Adds `Deno.UnsafeWindowSurface` ( `--unstable-webgpu` API) to the `Deno`
namespace:

```typescript
class UnsafeWindowSurface {
  constructor(
    system: "cocoa" | "x11" | "win32",
    winHandle: Deno.PointerValue,
    displayHandle: Deno.PointerValue | null
  );
  
  getContext(type: "webgpu"): GPUCanvasContext;

  present(): void;
}
```

For the initial pass, I've opted to support the three major windowing
systems. The parameters correspond to the table below:
| system                      | winHandle  | displayHandle |
| -----------------     | ----------   | ------- |
| "cocoa" (macOS)    | `NSView*`       | - |
| "win32" (Windows) | `HWND`         | `HINSTANCE` |
| "x11" (Linux)            | Xlib `Window` | Xlib `Display*` |

Ecosystem support:

- [x] deno_sdl2 (sdl2) -
[mod.ts#L1209](7e177bc652/mod.ts (L1209))
- [x] dwm (glfw) - https://github.com/deno-windowing/dwm/issues/29
- [ ] pane (winit)

<details>
<summary>Example</summary>

```typescript
// A simple clear screen pass, colors based on mouse position.

import { EventType, WindowBuilder } from "https://deno.land/x/sdl2@0.7.0/mod.ts";

const window = new WindowBuilder("sdl2 + deno + webgpu", 640, 480).build();
const [system, windowHandle, displayHandle] = window.rawHandle();

const adapter = await navigator.gpu.requestAdapter();
const device = await adapter.requestDevice();

const context = Deno.createWindowSurface(system, windowHandle, displayHandle);

context.configure({
  device: device,
  format: "bgra8unorm",
  height: 480,
  width: 640,
});

let r = 0.0;
let g = 0.0;
let b = 0.0;

for (const event of window.events()) {
  if (event.type === EventType.Quit) {
    break;
  } else if (event.type === EventType.Draw) {
    const textureView = context.getCurrentTexture().createView();

    const renderPassDescriptor: GPURenderPassDescriptor = {
      colorAttachments: [
        {
          view: textureView,
          clearValue: { r, g, b, a: 1.0 },
          loadOp: "clear",
          storeOp: "store",
        },
      ],
    };

    const commandEncoder = device.createCommandEncoder();
    const passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor);
    passEncoder.end();

    device.queue.submit([commandEncoder.finish()]);
    Deno.presentGPUCanvasContext(context);
  }

  if (event.type === EventType.MouseMotion) {
    r = event.x / 640;
    g = event.y / 480;
    b = 1.0 - r - g;
  }
}
```

You can find more examples in the linked tracking issue.

</details>

---------

Signed-off-by: Divy Srivastava <dj.srivastava23@gmail.com>
This commit is contained in:
Divy Srivastava 2024-01-19 22:49:14 +05:30 committed by GitHub
parent 47232f8a41
commit 40febd9dd1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 161 additions and 6 deletions

View file

@ -16,6 +16,7 @@ const {
ObjectPrototypeIsPrototypeOf,
Symbol,
SymbolFor,
TypeError,
} = primordials;
import * as webidl from "ext:deno_webidl/00_webidl.js";
@ -166,8 +167,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 };

View file

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

127
ext/webgpu/byow.rs Normal file
View file

@ -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> {
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 export v8::External to user code.
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;
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;
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))
}

View file

@ -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;
@ -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"],

View file

@ -29,6 +29,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,
@ -222,7 +223,9 @@ denoNsUnstableById[unstableIds.net] = {
// denoNsUnstableById[unstableIds.unsafeProto] = {}
// denoNsUnstableById[unstableIds.webgpu] = {}
denoNsUnstableById[unstableIds.webgpu] = {
UnsafeWindowSurface: webgpuSurface.UnsafeWindowSurface,
};
// denoNsUnstableById[unstableIds.workerOptions] = {}
@ -242,6 +245,7 @@ const denoNsUnstable = {
UnsafePointer: ffi.UnsafePointer,
UnsafePointerView: ffi.UnsafePointerView,
UnsafeFnPointer: ffi.UnsafeFnPointer,
UnsafeWindowSurface: webgpuSurface.UnsafeWindowSurface,
flock: fs.flock,
flockSync: fs.flockSync,
funlock: fs.funlock,