1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2025-01-05 05:49:20 -05:00
denoland-deno/ext/webgpu/02_surface.js
Divy Srivastava 40febd9dd1
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>
2024-01-19 22:49:14 +05:30

194 lines
5.2 KiB
JavaScript

// Copyright 2018-2024 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" />
import { core, primordials } from "ext:core/mod.js";
const {
op_webgpu_surface_configure,
op_webgpu_surface_get_current_texture,
op_webgpu_surface_present,
} = core.ensureFastOps();
const {
ObjectPrototypeIsPrototypeOf,
Symbol,
SymbolFor,
TypeError,
} = primordials;
import * as webidl from "ext:deno_webidl/00_webidl.js";
import { createFilteredInspectProxy } from "ext:deno_console/01_console.js";
import { loadWebGPU, webgpu } from "ext:deno_webgpu/00_init.js";
const _surfaceRid = Symbol("[[surfaceRid]]");
const _configuration = Symbol("[[configuration]]");
const _canvas = Symbol("[[canvas]]");
const _currentTexture = Symbol("[[currentTexture]]");
const _present = Symbol("[[present]]");
class GPUCanvasContext {
/** @type {number} */
[_surfaceRid];
[_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",
});
const { _device, assertDevice } = webgpu;
this[_device] = configuration.device[_device];
this[_configuration] = configuration;
const device = assertDevice(this, {
prefix,
context: "configuration.device",
});
const { err } = op_webgpu_surface_configure({
surfaceRid: this[_surfaceRid],
deviceRid: device.rid,
format: configuration.format,
viewFormats: configuration.viewFormats,
usage: configuration.usage,
width: configuration.width,
height: configuration.height,
alphaMode: configuration.alphaMode,
});
device.pushError(err);
}
unconfigure() {
const { _device } = webgpu;
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 { createGPUTexture, assertDevice } = webgpu;
const device = assertDevice(this, { prefix, context: "this" });
if (this[_currentTexture]) {
return this[_currentTexture];
}
const { rid } = 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;
}
// Required to present the texture; browser don't need this.
[_present]() {
const { assertDevice } = webgpu;
webidl.assertBranded(this, GPUCanvasContextPrototype);
const prefix = "Failed to execute 'present' on 'GPUCanvasContext'";
const device = assertDevice(this[_currentTexture], {
prefix,
context: "this",
});
op_webgpu_surface_present(device.rid, this[_surfaceRid]);
this[_currentTexture].destroy();
this[_currentTexture] = undefined;
}
[SymbolFor("Deno.privateCustomInspect")](inspect, inspectOptions) {
return inspect(
createFilteredInspectProxy({
object: this,
evaluate: ObjectPrototypeIsPrototypeOf(GPUCanvasContextPrototype, this),
keys: [
"canvas",
],
}),
inspectOptions,
);
}
}
const GPUCanvasContextPrototype = GPUCanvasContext.prototype;
function createCanvasContext(options) {
// lazy load webgpu if needed
loadWebGPU();
const canvasContext = webidl.createBranded(GPUCanvasContext);
canvasContext[_surfaceRid] = options.surfaceRid;
canvasContext[_canvas] = options.canvas;
return canvasContext;
}
// 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 { GPUCanvasContext, UnsafeWindowSurface };