Skip to content

Commit

Permalink
chore(core): Convert luma object to singleton (#2083)
Browse files Browse the repository at this point in the history
  • Loading branch information
ibgreen committed Apr 29, 2024
1 parent f1d0645 commit fb299aa
Show file tree
Hide file tree
Showing 5 changed files with 70 additions and 95 deletions.
3 changes: 0 additions & 3 deletions modules/core/src/adapter/device.ts
Expand Up @@ -2,7 +2,6 @@
// SPDX-License-Identifier: MIT
// Copyright (c) vis.gl contributors

import {VERSION} from '../init';
import {StatsManager, lumaStats} from '../utils/stats-manager';
import {log} from '../utils/log';
import {uid} from '../utils/uid';
Expand Down Expand Up @@ -300,8 +299,6 @@ export abstract class Device {
return 'Device';
}

static VERSION = VERSION;

constructor(props: DeviceProps) {
this.props = {...Device.defaultProps, ...props};
this.id = this.props.id || uid(this[Symbol.toStringTag].toLowerCase());
Expand Down
105 changes: 69 additions & 36 deletions modules/core/src/adapter/luma.ts
Expand Up @@ -10,11 +10,16 @@ import {StatsManager} from '../utils/stats-manager';
import {lumaStats} from '../utils/stats-manager';
import {log} from '../utils/log';

declare global {
// eslint-disable-next-line no-var
var luma: Luma;
}

const STARTUP_MESSAGE = 'set luma.log.level=1 (or higher) to trace rendering';

const ERROR_MESSAGE =
'No matching device found. Ensure `@luma.gl/webgl` and/or `@luma.gl/webgpu` modules are imported.';

const preregisteredAdapters = new Map<string, Adapter>();

/** Properties for creating a new device */
export type CreateDeviceProps = DeviceProps & {
/** Selects the type of device. `best-available` uses webgpu if available, then webgl. */
Expand All @@ -25,7 +30,8 @@ export type CreateDeviceProps = DeviceProps & {
/** 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;
handle: unknown; // WebGL2RenderingContext | GPUDevice | null;
/** List of adapters. Will also search any pre-registered adapterss */
adapters?: Adapter[];
};

Expand All @@ -34,37 +40,67 @@ export type AttachDeviceProps = DeviceProps & {
* Register WebGPU and/or WebGL adapters (controls application bundle size)
* Run-time selection of the first available Device
*/
export class luma {
export class Luma {
static defaultProps: Required<CreateDeviceProps> = {
...Device.defaultProps,
type: 'best-available',
adapters: undefined!
};

/** Global stats for all adapters */
static stats: StatsManager = lumaStats;
/** Global stats for all devices */
readonly stats: StatsManager = lumaStats;

/**
* Global log
*
* Assign luma.log.level in console to control logging: \
* 0: none, 1: minimal, 2: verbose, 3: attribute/uniforms, 4: gl logs
* luma.log.break[], set to gl funcs, luma.log.profile[] set to model names`;
*/
readonly log: Log = log;

/** Version of luma.gl */
readonly VERSION: string =
// Version detection using build plugin
// @ts-expect-error no-undef
typeof __VERSION__ !== 'undefined' ? __VERSION__ : 'running from source';

protected preregisteredAdapters = new Map<string, Adapter>();

constructor() {
if (globalThis.luma) {
if (globalThis.luma.VERSION !== this.VERSION) {
log.error(`Found luma.gl ${globalThis.luma.VERSION} while initialzing ${this.VERSION}`)();
log.error(`'yarn why @luma.gl/core' can help identify the source of the conflict`)();
throw new Error(`luma.gl - multiple versions detected: see console log`);
}

log.error('This version of luma.gl has already been initialized')();
}

/** Global log */
static log: Log = log;
log.log(1, `${this.VERSION} - ${STARTUP_MESSAGE}`)();

static registerAdapters(adapters: Adapter[]): void {
globalThis.luma = this;
}

registerAdapters(adapters: Adapter[]): void {
for (const deviceClass of adapters) {
preregisteredAdapters.set(deviceClass.type, deviceClass);
this.preregisteredAdapters.set(deviceClass.type, deviceClass);
}
}

/** Get type strings for supported Devices */
static getSupportedAdapters(adapters: Adapter[] = []): string[] {
const adapterMap = getAdapterMap(adapters);
getSupportedAdapters(adapters: Adapter[] = []): string[] {
const adapterMap = this.getAdapterMap(adapters);
return Array.from(adapterMap)
.map(([, adapter]) => adapter)
.filter(adapter => adapter.isSupported?.())
.map(adapter => adapter.type);
}

/** Get type strings for best available Device */
static getBestAvailableAdapter(adapters: Adapter[] = []): 'webgpu' | 'webgl' | null {
const adapterMap = getAdapterMap(adapters);
getBestAvailableAdapter(adapters: Adapter[] = []): 'webgpu' | 'webgl' | null {
const adapterMap = this.getAdapterMap(adapters);
if (adapterMap.get('webgpu')?.isSupported?.()) {
return 'webgpu';
}
Expand All @@ -74,27 +110,27 @@ export class luma {
return null;
}

static setDefaultDeviceProps(props: CreateDeviceProps): void {
Object.assign(luma.defaultProps, props);
setDefaultDeviceProps(props: CreateDeviceProps): void {
Object.assign(Luma.defaultProps, props);
}

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

// Should be handled by attach device
// if (props.gl) {
// props.type = 'webgl';
// }

const adapterMap = getAdapterMap(props.adapters);
const adapterMap = this.getAdapterMap(props.adapters);

let type: string = props.type || '';
if (type === 'best-available') {
type = luma.getBestAvailableAdapter(props.adapters) || type;
type = this.getBestAvailableAdapter(props.adapters) || type;
}

const adapters = getAdapterMap(props.adapters) || adapterMap;
const adapters = this.getAdapterMap(props.adapters) || adapterMap;

const adapter = adapters.get(type);
const device = await adapter?.create?.(props);
Expand All @@ -106,8 +142,8 @@ export class luma {
}

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

// WebGL
let type = '';
Expand Down Expand Up @@ -142,7 +178,7 @@ export class luma {
* 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 {
enforceWebGL2(enforce: boolean = true): void {
const prototype = HTMLCanvasElement.prototype as any;
if (!enforce && prototype.originalGetContext) {
// Reset the original getContext function
Expand All @@ -168,7 +204,7 @@ export class luma {

/** Convert a list of adapters to a map */
protected getAdapterMap(adapters: Adapter[] = []): Map<string, Adapter> {
const map = new Map(preregisteredAdapters);
const map = new Map(this.preregisteredAdapters);
for (const adapter of adapters) {
map.set(adapter.type, adapter);
}
Expand All @@ -178,23 +214,20 @@ export class luma {
// DEPRECATED

/** @deprecated Use registerAdapters */
static registerDevices(deviceClasses: any[]): void {
registerDevices(deviceClasses: any[]): void {
log.warn('luma.registerDevices() is deprecated, use luma.registerAdapters() instead');
for (const deviceClass of deviceClasses) {
const adapter = deviceClass.adapter as Adapter;
if (adapter) {
preregisteredAdapters.set(adapter.type, adapter);
this.preregisteredAdapters.set(adapter.type, adapter);
}
}
}
}

/** Convert a list of adapters to a map */
function getAdapterMap(adapters: Adapter[] = []): Map<string, Adapter> {
const map = new Map<string, Adapter>(preregisteredAdapters);
for (const deviceClass of adapters) {
// assert(deviceClass.type && deviceClass.isSupported && deviceClass.create);
map.set(deviceClass.type, deviceClass);
}
return map;
}
/**
* Entry point to the luma.gl GPU abstraction
* Register WebGPU and/or WebGL adapters (controls application bundle size)
* Run-time selection of the first available Device
*/
export const luma = new Luma();
2 changes: 0 additions & 2 deletions modules/core/src/index.ts
Expand Up @@ -2,8 +2,6 @@
// SPDX-License-Identifier: MIT
// Copyright (c) vis.gl contributors

export {VERSION} from './init';

// MAIN API ACCESS POINT
export {luma} from './adapter/luma';

Expand Down
53 changes: 0 additions & 53 deletions modules/core/src/init.ts

This file was deleted.

2 changes: 1 addition & 1 deletion modules/webgl/src/context/debug/spector.ts
Expand Up @@ -58,7 +58,7 @@ export function initializeSpectorJS(props: SpectorProps): Spector | null {
const {Spector} = globalThis.SPECTOR as any;
spector = new Spector();
if (globalThis.luma) {
globalThis.luma.spector = spector;
(globalThis.luma as any).spector = spector;
}
}

Expand Down

0 comments on commit fb299aa

Please sign in to comment.