Skip to content

Commit

Permalink
Merge 055d0b2 into a27c2cd
Browse files Browse the repository at this point in the history
  • Loading branch information
ibgreen committed Mar 7, 2024
2 parents a27c2cd + 055d0b2 commit 4f46655
Show file tree
Hide file tree
Showing 8 changed files with 160 additions and 77 deletions.
82 changes: 47 additions & 35 deletions docs/api-reference/core/luma.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,7 @@ using the registered backends.
The returned [`Device`](/docs/api-reference/core/device) instances provides luma.gl applications
with further access to the GPU.

## luma.registerDevices

```typescript
luma.registerDevices(devices: Device[]): void;
```

Registers one or more devices so that they can be used to create `Device` instances against
that GPU backend.
## Device Registration

```typescript
import {luma} from '@luma.gl/core';
Expand All @@ -27,26 +20,10 @@ luma.registerDevices([WebGLDevice, WebGPUDevice]);
It is possible to register more than one device to create an application
that can work in both WebGL and WebGPU environments.

```
The `@luma.gl/core` module defines abstract API interfaces such as `Device`, `Buffer` etc and is not usable on its own.

One or more GPU backend modules must be also be imported from a corresponding GPU API backend module (`@luma.gl/webgl` and/or `@luma.gl/webgpu`) and then registered with luma.gl.

## luma.createDevice
```typescript
luma.createDevice({type, ...DeviceProps});
```

To enable of this, the application create a `Device` using the `'best-available'` adapter.

luma.gl favors WebGPU over WebGL devices, whenever WebGPU is available.

:::note
At least one backend must be imported and registered with `luma.registerDevices()` for `luma.createDevice()` calls to succeed.
:::

## Usage

Create a WebGL2 context, auto creating a canvas
Expand All @@ -69,19 +46,18 @@ luma.registerDevices([WebGLDevice]);
const webgpuDevice = luma.createDevice({type: 'webgl', canvas: ...});
```



## Registering Device Backends


To create a WebGPU device:
Install device modules

```sh
yarn add @luma.gl/core
yarn add @luma.gl/webgl
yarn add @luma.gl/webgpu
```

To create a WebGPU device:

```typescript
import {luma} from '@luma.gl/core';
import {WebGPUDevice} from '@luma.gl/webgpu';
Expand All @@ -90,19 +66,55 @@ luma.registerDevices([WebGPUDevice]);
const device = await luma.createDevice({type: 'webgpu', canvas: ...});
```


```sh
yarn add @luma.gl/core
yarn add @luma.gl/webgl
yarn add @luma.gl/webgpu
```
Pre-register devices

```typescript
import {luma} from '@luma.gl/core';
import {WebGLDevice} from '@luma.gl/webgl';
import {WebGPUDevice} from '@luma.gl/webgpu';

luma.registerDevices([WebGLDevice, WebGPUDevice]);

const webgpuDevice = luma.createDevice({type: 'best-available', canvas: ...});
```

Provide devices to createDevice

```typescript
const webgpuDevice = luma.createDevice({
type: 'best-available',
canvas: ...,
devices: [WebGLDevice, WebGPUDevice]
});
```

## Methods

### `luma.registerDevices()`

```typescript
luma.registerDevices(devices: Device[]): void;
```

Registers one or more devices so that they can be used to create `Device` instances against
that GPU backend. They will be available to `luma.createDevice()` and `luma.attachDevice()` calls.
Enables separation of the code that registers backends from the code that creates devices.

### `luma.createDevice()`

```typescript
luma.createDevice({type, ...DeviceProps});
```

To enable of this, the application create a `Device` using the `'best-available'` adapter.

luma.gl favors WebGPU over WebGL devices, whenever WebGPU is available.

### `luma.attachDevice()`

```ts
luma.attachDevice(handle: WebGLRenderingContext | GPUDevice, devices: unknown[])
```

## Remarks

- At least one backend must be imported and registered with `luma.registerDevices()` for `luma.createDevice()` or `luma.attachDevice()` calls to succeed (unless `Device` implementations are supplied to those calls).
4 changes: 4 additions & 0 deletions docs/whats-new.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ Target Date: Q2 2024

- Production quality (non-experimental) WebGPU backend.

**@luma.gl/core**

- new [`luma.attachDevice()`](/docs/api-reference/core/luma#attachdevice) API - A `Device` can now be [attached to `WebGL2RenderingContext` or `GPUDevice`] without importing `WebGLDevice`.

## Version 9.0

Target Date: Feb 2024
Expand Down
106 changes: 83 additions & 23 deletions modules/core/src/adapter/luma.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,20 @@ import {StatsManager} from '../utils/stats-manager';
import {lumaStats} from '../utils/stats-manager';
import {log} from '../utils/log';

const deviceList = new Map<string, typeof Device>();
let deviceMap = new Map<string, typeof Device>();

/** Properties for creating a new device */
export type CreateDeviceProps = DeviceProps & {
/** Select type of device */
type?: 'webgl' | 'webgpu' | 'best-available';
/** Selects the type of device. `best-available` uses webgpu if available, then webgl. */
type?: 'webgl' | 'webgpu' | 'unknown' | 'best-available';
devices?: any[];
};

/** Properties for attaching an existing WebGL context or WebGPU device to a new luma Device */
export type AttachDeviceProps = DeviceProps & {
/** Externally created WebGL context or WebGPU device */
handle: WebGL2RenderingContext; // | GPUDevice;
devices?: any[];
};

/**
Expand All @@ -24,7 +33,8 @@ export type CreateDeviceProps = DeviceProps & {
export class luma {
static defaultProps: Required<CreateDeviceProps> = {
...Device.defaultProps,
type: 'best-available'
type: 'best-available',
devices: undefined!
};

/** Global stats for all devices */
Expand All @@ -34,20 +44,17 @@ export class luma {
static log: Log = log;

static registerDevices(deviceClasses: any[] /* : typeof Device */): void {
for (const deviceClass of deviceClasses) {
// assert(deviceClass.type && deviceClass.isSupported && deviceClass.create);
deviceList.set(deviceClass.type, deviceClass);
}
deviceMap = getDeviceMap(deviceClasses);
}

static getAvailableDevices(): string[] {
// @ts-expect-error
return Array.from(deviceList).map(Device => Device.type);
return Array.from(deviceMap).map(Device => Device.type);
}

static getSupportedDevices(): string[] {
return (
Array.from(deviceList)
Array.from(deviceMap)
// @ts-expect-error
.filter(Device => Device.isSupported())
// @ts-expect-error
Expand All @@ -59,35 +66,78 @@ export class luma {
Object.assign(luma.defaultProps, props);
}

/** Attach to an existing GPU API handle (WebGL2RenderingContext or GPUDevice). */
static async attachDevice(props: AttachDeviceProps): Promise<Device> {
const devices = getDeviceMap(props.devices) || deviceMap;

// WebGL
if (props.handle instanceof WebGL2RenderingContext) {
const WebGLDevice = devices.get('webgl') as any;
if (WebGLDevice) {
return (await WebGLDevice.attach(props.handle)) as Device;
}
}

// TODO - WebGPU does not yet have a stable API
// if (props.handle instanceof GPUDevice) {
// const WebGPUDevice = devices.get('webgpu') as any;
// if (WebGPUDevice) {
// return (await WebGPUDevice.attach(props.handle)) as Device;
// }
// }

// null
if (props.handle === null) {
const UnknownDevice = devices.get('unknown') as any;
if (UnknownDevice) {
return (await UnknownDevice.attach(null)) as Device;
}
}

throw new Error(
'Failed to attach device. Ensure `@luma.gl/webgl` and/or `@luma.gl/webgpu` modules are imported.'
);
}

/** Creates a device. Asynchronously. */
static async createDevice(props: CreateDeviceProps = {}): Promise<Device> {
props = {...luma.defaultProps, ...props};
if (props.gl) {
props.type = 'webgl';
}

let DeviceClass: any;
const devices = getDeviceMap(props.devices) || deviceMap;

switch (props.type) {
case 'webgpu':
DeviceClass = deviceList.get('webgpu');
if (DeviceClass) {
return await DeviceClass.create(props);
let WebGPUDevice = devices.get('webgpu') as any;
if (WebGPUDevice) {
return await WebGPUDevice.create(props);
}
break;

case 'webgl':
DeviceClass = deviceList.get('webgl');
if (DeviceClass) {
return await DeviceClass.create(props);
let WebGLDevice = devices.get('webgl') as any;
if (WebGLDevice) {
return await WebGLDevice.create(props);
}
break;

case 'unknown':
const UnknownDevice = devices.get('unknown') as any;
if (UnknownDevice) {
return await UnknownDevice.create(props);
}
break;

case 'best-available':
DeviceClass = deviceList.get('webgpu');
if (DeviceClass && DeviceClass.isSupported()) {
return await DeviceClass.create(props);
WebGPUDevice = devices.get('webgpu') as any;
if (WebGPUDevice?.isSupported?.()) {
return await WebGPUDevice.create(props);
}
DeviceClass = deviceList.get('webgl');
if (DeviceClass && DeviceClass.isSupported()) {
return await DeviceClass.create(props);
WebGLDevice = devices.get('webgl');
if (WebGLDevice?.isSupported?.()) {
return await WebGLDevice.create(props);
}
break;
}
Expand All @@ -96,3 +146,13 @@ export class luma {
);
}
}

/** Convert a list of devices to a map */
function getDeviceMap(deviceClasses: any[] /* : typeof Device */): Map<string, typeof Device> {
const map = new Map<string, typeof Device>();
for (const deviceClass of deviceClasses) {
// assert(deviceClass.type && deviceClass.isSupported && deviceClass.create);
map.set(deviceClass.type, deviceClass);
}
return map;
}
15 changes: 15 additions & 0 deletions modules/core/test/adapter/luma.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// luma.gl
// SPDX-License-Identifier: MIT
// Copyright (c) vis.gl contributors

import test from 'tape-promise/tape';
import {NullDevice} from '@luma.gl/test-utils';
import {luma} from '@luma.gl/core';

test('luma#attachDevice', async t => {
const device = await luma.attachDevice({handle: null, devices: [NullDevice]});
t.equal(device.type, 'unknown', 'info.vendor ok');
t.equal(device.info.vendor, 'no one', 'info.vendor ok');
t.equal(device.info.renderer, 'none', 'info.renderer ok');
t.end();
});
1 change: 1 addition & 0 deletions modules/core/test/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import './adapter/helpers/parse-shader-compiler-log.spec';

import './adapter/device.spec';
import './adapter/canvas-context.spec';
import './adapter/luma.spec';

// Resources
import './adapter/texture-formats.spec';
Expand Down
10 changes: 5 additions & 5 deletions modules/test-utils/src/null-device/null-device-info.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@

import type {DeviceInfo} from '@luma.gl/core';

export const NullDeviceInfo: DeviceInfo = {
type: 'webgl',
export const NullDeviceInfo = {
type: 'unknown',
gpu: 'software',
gpuType: 'unknown',
gpuBackend: 'unknown',
vendor: '',
vendor: 'no one',
renderer: 'none',
version: '1.0',
shadingLanguage: 'glsl' as const,
shadingLanguage: 'glsl',
shadingLanguageVersion: 300
} as const;
} as const satisfies DeviceInfo;
4 changes: 4 additions & 0 deletions modules/test-utils/src/null-device/null-device.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@ export class NullDevice extends Device {
readonly canvasContext: NullCanvasContext;
readonly lost: Promise<{reason: 'destroyed'; message: string}>;

static attach(handle: null): NullDevice {
return new NullDevice({});
}

static async create(props: DeviceProps = {}): Promise<NullDevice> {
// Wait for page to load: if canvas is a string we need to query the DOM for the canvas element.
// We only wait when props.canvas is string to avoids setting the global page onload callback unless necessary.
Expand Down
15 changes: 1 addition & 14 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3284,19 +3284,6 @@ __metadata:
languageName: unknown
linkType: soft

"@luma.gl/core-tests@workspace:modules/core-tests":
version: 0.0.0-use.local
resolution: "@luma.gl/core-tests@workspace:modules/core-tests"
dependencies:
"@luma.gl/core": "npm:9.0.0-beta.7"
"@luma.gl/engine": "npm:9.0.0-beta.7"
"@luma.gl/shadertools": "npm:9.0.0-beta.7"
"@luma.gl/test-utils": "npm:9.0.0-beta.7"
"@luma.gl/webgl": "npm:9.0.0-beta.7"
"@luma.gl/webgpu": "npm:9.0.0-beta.7"
languageName: unknown
linkType: soft

"@luma.gl/core@npm:9.0.0-beta.7, @luma.gl/core@workspace:modules/core":
version: 0.0.0-use.local
resolution: "@luma.gl/core@workspace:modules/core"
Expand Down Expand Up @@ -3347,7 +3334,7 @@ __metadata:
languageName: unknown
linkType: soft

"@luma.gl/test-utils@npm:9.0.0-beta.7, @luma.gl/test-utils@workspace:modules/test-utils":
"@luma.gl/test-utils@workspace:modules/test-utils":
version: 0.0.0-use.local
resolution: "@luma.gl/test-utils@workspace:modules/test-utils"
dependencies:
Expand Down

0 comments on commit 4f46655

Please sign in to comment.