Skip to content

Commit

Permalink
chore(core): luma.ts cleanup
Browse files Browse the repository at this point in the history
  • Loading branch information
ibgreen committed Apr 28, 2024
1 parent cb258af commit 8b9cfc9
Show file tree
Hide file tree
Showing 4 changed files with 189 additions and 157 deletions.
56 changes: 44 additions & 12 deletions docs/api-reference/core/luma.md
Expand Up @@ -96,30 +96,46 @@ const webgpuDevice = luma.createDevice({
});
```

## Methods
## Types

### `luma.registerDevices()`
### `CreateDeviceProps`

```typescript
luma.registerDevices(devices: (typeof Device)[]): void;
Properties for creating a new device

```ts
type CreateDeviceProps = DeviceProps & {
/** Selects the type of device. `best-available` uses webgpu if available, then webgl. */
type?: 'webgl' | 'webgpu' | 'unknown' | 'best-available';
/** List of device types. Will also search any pre-registered device backends */
devices?: DeviceFactory[];
}
```
Registers one or more devices (device constructors) so that they can be used
to create `Device` instances against that GPU backend. The registered device types
will be available to `luma.createDevice()` and `luma.attachDevice()` calls.
### `AttachDeviceProps`
`luma.registerDevices()` enables separation of the application code that
registers GPU backends from the application code that creates devices.
Properties for attaching an existing WebGL context or WebGPU device to a new luma Device.
```ts
export type AttachDeviceProps = DeviceProps & {
/** Externally created WebGL context or WebGPU device */
handle: WebGL2RenderingContext | GPUDevice | null;
/** List of device types. Will also search any pre-registered device backends */
devices?: DeviceFactory[];
};
```

## Methods

### `luma.createDevice()`

```typescript
luma.createDevice({type, ...DeviceProps});
luma.createDevice({type, devices, ...deviceProps}: CreateDeviceProps);
```

To create a Device instance, the application calls `luma.createDevice()`.

- `type`: `'webgl' \| 'webgpu' \| 'best-available'`
- `devices`: list of `Device` backend classes. Can be omitted if `luma.registerDevices()` has been called.

Unless a device `type` is specified a `Device` will be created using the `'best-available'` adapter.
luma.gl favors WebGPU over WebGL devices, whenever WebGPU is available.
Expand All @@ -131,14 +147,30 @@ Note: A device type is available if:
### `luma.attachDevice()`

```ts
luma.attachDevice(handle: WebGL2RenderingContext | GPUDevice, devices: unknown[]);
luma.attachDevice({handle: WebGL2RenderingContext | GPUDevice, devices, ...}: AttachDeviceProps);
```

A luma.gl Device can be attached to an externally created `WebGL2RenderingContext` or `GPUDevice`.
This allows applications to use the luma.gl API to "interleave" rendering with other GPU libraries.

If you need to attach a luma.gl `Device` to a WebGL 1 `WebGLRenderingContext`, see `luma.enforceWebGL2()`.
- `handle` - The externally created `WebGL2RenderingContext` or `GPUDevice` that should be attached to a luma `Device`.
- `devices` - list of `Device` backend classes. Can be omitted if `luma.registerDevices()` has been called.

Note that while you cannot directly attach a luma.gl `Device` to a WebGL 1 `WebGLRenderingContext`, you may be able to work around it using `luma.enforceWebGL2()`.

### `luma.registerDevices()`

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

Registers one or more devices (device constructors) so that they can be used
to create `Device` instances against that GPU backend. The registered device types
will be available to `luma.createDevice()` and `luma.attachDevice()` calls.

`luma.registerDevices()` enables separation of the application code that
registers GPU backends from the application code that creates devices,
so that device types do not have to be provided at `Device` create or attach time.

### `luma.enforceWebGL2()`

Expand Down
11 changes: 11 additions & 0 deletions modules/core/src/adapter/device.ts
Expand Up @@ -241,6 +241,17 @@ export type DeviceProps = {
_factoryDestroyPolicy?: 'unused' | 'never';
};

/**
* Create and attach devices for a specific backend. Currently static methods on each device
*/
export interface DeviceFactory {
// new (props: DeviceProps): Device; Constructor isn't used
type: string;
isSupported(): boolean;
create(props: DeviceProps): Promise<Device>;
attach?(handle: unknown): Device;
}

/**
* WebGPU Device/WebGL context abstraction
*/
Expand Down
105 changes: 47 additions & 58 deletions modules/core/src/adapter/luma.ts
Expand Up @@ -4,25 +4,28 @@

import type {Log} from '@probe.gl/log';
import type {DeviceProps} from './device';
import {Device} from './device';
import {Device, DeviceFactory} from './device';
import {StatsManager} from '../utils/stats-manager';
import {lumaStats} from '../utils/stats-manager';
import {log} from '../utils/log';

const deviceMap = new Map<string, typeof Device>();
const ERROR_MESSAGE =
'No matching device found. Ensure `@luma.gl/webgl` and/or `@luma.gl/webgpu` modules are imported.';

const deviceMap = new Map<string, DeviceFactory>();

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

/** 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[];
devices?: DeviceFactory[];
};

/**
Expand All @@ -43,7 +46,7 @@ export class luma {
/** Global log */
static log: Log = log;

static registerDevices(deviceClasses: any[] /* : typeof Device */): void {
static registerDevices(deviceClasses: DeviceFactory[]): void {
for (const deviceClass of deviceClasses) {
deviceMap.set(deviceClass.type, deviceClass);
}
Expand All @@ -64,6 +67,18 @@ export class luma {
);
}

/** Get type strings for best available Device */
static getBestAvailableDeviceType(devices: DeviceFactory[] = []): 'webgpu' | 'webgl' | null {
const deviceMap = getDeviceMap(devices);
if (deviceMap.get('webgpu')?.isSupported?.()) {
return 'webgpu';
}
if (deviceMap.get('webgl')?.isSupported?.()) {
return 'webgl';
}
return null;
}

static setDefaultDeviceProps(props: CreateDeviceProps): void {
Object.assign(luma.defaultProps, props);
}
Expand All @@ -74,9 +89,10 @@ export class luma {

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

Expand All @@ -90,15 +106,14 @@ export class luma {

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

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

/** Creates a device. Asynchronously. */
Expand All @@ -110,46 +125,25 @@ export class luma {

const devices = getDeviceMap(props.devices) || deviceMap;

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

case 'webgl':
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':
WebGPUDevice = devices.get('webgpu') as any;
if (WebGPUDevice?.isSupported?.()) {
return await WebGPUDevice.create(props);
}
WebGLDevice = devices.get('webgl') as any;
if (WebGLDevice?.isSupported?.()) {
return await WebGLDevice.create(props);
}
break;
let type: string = props.type || '';
if (type === 'best-available') {
type = luma.getBestAvailableDeviceType(props.devices) || type;
}
throw new Error(
'No matching device found. Ensure `@luma.gl/webgl` and/or `@luma.gl/webgpu` modules are imported.'
);

const Device = devices.get(type);
const device = await Device?.create?.(props);
if (device) {
return device;
}

throw new Error(ERROR_MESSAGE);
}

/**
* Override `HTMLCanvasContext.getCanvas()` to always create WebGL2 contexts.
* Used when attaching luma to a context from an external library does not support creating WebGL2 contexts.
* (luma can only attach to WebGL2 contexts).
*/
static enforceWebGL2(enforce: boolean = true): void {
const prototype = HTMLCanvasElement.prototype as any;
if (!enforce && prototype.originalGetContext) {
Expand All @@ -175,13 +169,8 @@ export class luma {
}

/** Convert a list of devices to a map */
function getDeviceMap(
deviceClasses?: any[] /* : typeof Device */
): Map<string, typeof Device> | null {
if (!deviceClasses || deviceClasses?.length === 0) {
return null;
}
const map = new Map<string, typeof Device>();
function getDeviceMap(deviceClasses: DeviceFactory[] = []): Map<string, DeviceFactory> {
const map = new Map<string, DeviceFactory>(deviceMap);
for (const deviceClass of deviceClasses) {
// assert(deviceClass.type && deviceClass.isSupported && deviceClass.create);
map.set(deviceClass.type, deviceClass);
Expand Down

0 comments on commit 8b9cfc9

Please sign in to comment.