diff --git a/docs/api-guide/gpu/gpu-initialization.md b/docs/api-guide/gpu/gpu-initialization.md index 9c5e17889e..9312ecf2d5 100644 --- a/docs/api-guide/gpu/gpu-initialization.md +++ b/docs/api-guide/gpu/gpu-initialization.md @@ -45,7 +45,7 @@ import {luma} from '@luma.gl/core'; import {webgpuAdapter} from '@luma.gl/webgpu'; luma.registerAdapters([webgpuAdapter]); -const device = await luma.createDevice({type: 'webgpu', canvas: ...}); +const device = await luma.createDevice({type: 'webgpu', canvasContext: {canvas: ...}}); ``` It is possible to register more than one device adapter to create an application @@ -65,5 +65,5 @@ import {WebGPUDevice} from '@luma.gl/webgpu'; luma.registerAdapters([WebGLDevice, WebGPUDevice]); -const webgpuDevice = luma.createDevice({type: 'best-available', canvas: ...}); +const webgpuDevice = luma.createDevice({type: 'best-available', canvasContext: {canvas: ...}}); ``` diff --git a/docs/api-reference/core/README.md b/docs/api-reference/core/README.md index 248a6618a9..8e59fa7881 100644 --- a/docs/api-reference/core/README.md +++ b/docs/api-reference/core/README.md @@ -14,7 +14,7 @@ import {luma} from '@luma.gl/core'; import {WebGPUAdapter} from '@luma.gl/webgpu'; luma.registerDevice([WebGPUAdapter]) -const device = await luma.createDevice({type: 'webgpu', canvas: ...}); +const device = await luma.createDevice({type: 'webgpu', canvasContext: ...}); ``` It is possible to register more than one device adapter to create an application @@ -26,7 +26,7 @@ import {luma} from '@luma.gl/core'; import {WebGPUAdapter} from '@luma.gl/webgpu'; import {WebGLAdapter} '@luma.gl/webgl'; -const webgpuDevice = luma.createDevice({type: 'best-available', canvas: ...}); +const webgpuDevice = luma.createDevice({type: 'best-available', canvasContext: ...}); ``` ## Creating GPU Resources diff --git a/docs/api-reference/core/canvas-context.md b/docs/api-reference/core/canvas-context.md index 3a92372cd1..88c620b0da 100644 --- a/docs/api-reference/core/canvas-context.md +++ b/docs/api-reference/core/canvas-context.md @@ -1,16 +1,9 @@ # CanvasContext A `CanvasContext` holds a connection between a GPU `Device` and an HTML `canvas` or `OffscreenCanvas` into which it can render. - -Canvas contexts are created using `device.createCanvasContext()`. Depending on options passed, this either: -- creates a new canvas element, or -- attaches the context to an existing canvas element -- -- (see [remarks](#remarks) below for WebGL limitations). - a `CanvasContext` handles the following responsibilities: -- Provides a `Framebuffer` representing the display, with freshly updated and resized textures for every render frame. On WebGPU it manages the "swap chain". +- A source of `Framebuffer`s that will render into the display. - Handles canvas resizing - Manages device pixel ratio (mapping between device and CSS pixels) @@ -19,8 +12,10 @@ a `CanvasContext` handles the following responsibilities: Use a device's default canvas context: ```typescript +const renderPass = device.beginRenderPass({}); +// or const renderPass = device.beginRenderPass({ - framebuffer: device.canvasContext.getFramebuffer() + framebuffer: device.getCanvasContext().getFramebuffer() }); ``` @@ -31,6 +26,11 @@ const canvasContext2 = device.createCanvasContext({canvas: ...}); const renderPass = device.beginRenderPass({ framebuffer: canvasContext2.getFramebuffer() }); + +const renderPass = device.beginRenderPass({ + framebuffer: canvasContext2.getFramebuffer() +}); + ``` ## Types @@ -46,8 +46,9 @@ const renderPass = device.beginRenderPass({ | `useDevicePixels?` | `boolean` \| `number` | Device pixels scale factor (`true` uses browser DPI) | | `autoResize?` | `boolean` | Whether to track resizes | | `visible?` | `boolean` | Visibility (only used if new canvas is created). | -| `colorSpace?` | `'srgb'` | WebGPU only https://www.w3.org/TR/webgpu/#canvas-configuration | -| `compositingAlphaMode?` | `'opaque'` \| `'premultiplied'` | WebGPU only https://www.w3.org/TR/webgpu/#canvas-configuration | +| `alphaMode?: string` | `'opaque'` | `'opaque' \| 'premultiplied'`. See [alphaMode](https://developer.mozilla.org/en-US/docs/Web/API/GPUCanvasContext/configure#alphamode). | +| `colorSpace?: 'string` | `'srgb'` | `'srgb' \| 'display-p3'`. See [colorSpace](https://developer.mozilla.org/en-US/docs/Web/API/GPUCanvasContext/configure#colorspace). | + ## Static Fields @@ -81,12 +82,20 @@ Whether the framebuffer backing this canvas context is sized using device pixels ### constructor :::info -A `CanvasContext` should not be constructed directly. Default canvas contexts are created when instantiating a `WebGPUDevice` or a `WebGLDevice`, and can be accessed through the `device.canvasContext` field. Additional canvas contexts can be explicitly created through `WebGPUDevice.createCanvasContext(...)`. +A `CanvasContext` should not be constructed directly. Default canvas contexts are created when instantiating a `WebGPUDevice` or a `WebGLDevice` by supplying the `canvasContext` property, and can be accessed through the `device.canvasContext` field. Additional canvas contexts can be explicitly created through `WebGPUDevice.createCanvasContext(...)`. ::: +On `Device` instances that support it (see [remarks](#remarks) below) additional canvas contexts are created using `device.createCanvasContext()`. Depending on options passed, this either: +- creates a new canvas element with the specified properties, +- or attaches the context to an existing canvas element + +### getCurrentFramebuffer(): Framebuffer + +Returns a framebuffer with properly resized current 'swap chain' textures. Rendering to this framebuffer will update the canvas associated with that `CanvasContext`. Note that a new `Framebuffer` must be requested on every redraw cycle. + ### `getDevicePixelResolution(): [number, number]` -T +TBA ### `getPixelSize(): [number, number]` @@ -94,18 +103,18 @@ Returns the size in pixels required to cover the canvas at the current device pi ### `resize(): void` -Resize the drawing surface. +Resize the drawing surface. Usually called after the window has been resized. Note that automatic resizing is performed as size changes to the underlying canvas object are detected. ```typescript -canvasContext.resize(options) +canvasContext.resize(options: {width: number, height: number; userDevicePixels}) ``` - - **width**: New drawing surface width. - - **height**: New drawing surface height. - - **useDevicePixels**: Whether to scale the drawing surface using the device pixel ratio. +- **width**: New drawing surface width. +- **height**: New drawing surface height. +- **useDevicePixels**: Whether to scale the drawing surface using the device pixel ratio. ## Remarks -- Note that a WebGPU `Device` can have multiple associated `CanvasContext` instances (or none, if only used for compute). -- However a WebGL `Device` always has exactly one `CanvasContext` and can only render into that single canvas. (This is a fundamental limitation of WebGL.) -- `useDevicePixels` can accept a custom ratio (Number), instead of `true` or `false`. This allows rendering to a much smaller or higher resolutions. When using high value (usually more than device pixel ratio), it is possible it can get clamped down outside of luma.gl's control due to system memory limitation, in such cases a warning will be logged to the browser console. +- A WebGPU `Device` can have multiple associated `CanvasContext` instances (or none, if only used for compute). +- A WebGL `Device` always has exactly one `CanvasContext` and can only render into that single canvas. (This is a fundamental limitation of the WebGL API.) +- `useDevicePixels` can accept a custom ratio (`number`), instead of `true` or `false`. This allows rendering to a smaller or higher resolutions. When using high value (usually more than device pixel ratio), it is possible it can get clamped down outside of luma.gl's control due to system memory limitation, in such cases a warning will be logged to the browser console. diff --git a/docs/api-reference/core/device.md b/docs/api-reference/core/device.md index d11d53382d..529d6e2953 100644 --- a/docs/api-reference/core/device.md +++ b/docs/api-reference/core/device.md @@ -18,20 +18,20 @@ with changes to enable a WebGL2 implementation. ## Usage -Create a new Device, auto creating a canvas and a new WebGL 2 context +Create a new `Device`, auto creating a canvas and a new WebGL 2 context. See [`luma.createDevice()`](./luma.md#lumacreatedevice). ```typescript import {Device} from '@luma.gl/core'; -const device = new luma.createDevice({type: 'webgl2'}); +const device = new luma.createDevice({type: 'webgl2', ...}); ``` -Attaching a Device to an externally created `WebGL2RenderingContext`. +Attaching a `Device` to an externally created `WebGL2RenderingContext`. ```typescript import {Device} from '@luma.gl/core'; import {Model} from '@luma.gl/engine'; -const gl = canvas.createContext('webgl2'); +const gl = canvas.getContext('webgl2', ...); const device = Device.attach(gl); const model = new Model(device, options); @@ -52,20 +52,62 @@ console.error(message); ### `DeviceProps` -| Parameter | Default | Description | -| ------------------------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------- | -| `type` | `'best-available'` | `'webgpu'`, `'webgl'`, `'best-available'` | -| `canvas` | N/A | A _string_ `id` of an existing HTML element or a _DOMElement_. If not provided, a new canvas will be created. | -| priority. | -| `debug?: boolean` | `false` | WebGL API calls will be logged to the console and WebGL errors will generate JavaScript exceptions. | -| `break?: string[]` | `[]` | Insert a break point (`debugger`) if one of the listed gl functions is called. | -| `alpha?: boolean` | `true` | Default render target has an alpha buffer. | -| `depth?: boolean` | `true` | Default render target has a depth buffer of at least `16` bits. | -| `stencil?` | `false` | Default render target has a stencil buffer of at least `8` bits. | -| `antialias?` | `true` | Boolean that indicates whether or not to perform anti-aliasing. | -| `premultipliedAlpha?` | `true` | Boolean that indicates that the page compositor will assume the drawing buffer contains colors with pre-multiplied alpha. | -| `preserveDrawingBuffer?` | `false` | Default render target buffers will preserve their values until cleared or overwritten. Useful for screen capture. | -| `failIfMajorPerformanceCaveat?` | `false` | Do not create if the system performance is low. | +:::tip +This object can also include all [`CanvasContextProps`][canvas-context-props] properties to configure how a new canvas is created. If a canvas is provided, these are ignored. +::: + +[canvas-context-props]: ./canvas-context.md#canvascontextprops +[webgl-attributes]: https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/getContext#contextattributes + +Specifies props to use when luma creates the device. + +| Parameter | Default | Description | +| --------------------------------------------------- | -------------------------------------------- | ------------------------------------------------------------------------------------------ | +| `id?: string` | `null` | Optional string id, mainly intended for debugging. | +| `canvasContext?: CanvasContextProps` | [CanvasContexProps][canvas-context] | Props used to create the default `CanvasContext` for the new `Device`. | +| `onError?: (error: Error) => unknown` | `log.error` | Error handling. | +| `powerPreference?: string` | `'high-performance'` | `'default' \| 'high-performance' \| 'low-power'` (WebGL). | +| `webgl?: WebGLContextAttributes` | [`WebGLContextAttributes`][webgl-attributes] | Attributes passed on to WebGL (`canvas.getContext('webgl2', props.webgl)` | +| `_requestMaxLimits?: boolean` | `true` | Ensures that the Device exposes the highest `DeviceLimits` supported by platform (WebGPU). | +| `_initializeFeatures?: boolean` | `true` | Initialize all `DeviceFeatures` on startup. 🧪 | +| `_disabledFeatures?: Record` | `{ 'compilation-status-async-webgl': true }` | Disable specific `DeviceFeatures`. 🧪 | +| `_factoryDestroyPolicy?: string` | `'unused'` | `'unused' \| 'never'` Never destroy cached shaders and pipelines. 🧪 | + +:::caution +🧪 denotes experimental feature. Expect API to change. +::: + +Specify WebGL debugging options to use when luma creates the WebGL context. + +| WebGL Debugging | Default | Description | +| ---------------------------------------------------------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------- | +| `debugShaders?`: `'errors'` \| `'warnings'` \| `'always'` \| `'never'` | `'error'` | Display shader source code with inline errors in the canvas. | +| `debugFramebuffers?: boolean` | `false` | Show small copy of the contents of updated Framebuffers in the canvas. | +| `debugWebGL?: boolean` | `false` | Initialize Khronos WebGLDeveloperTools. WebGL API calls will be logged to the console and WebGL errors will generate JavaScript exceptions. | +| `break?: string[]` | `[]` | Insert a break point (`debugger`) if one of the listed gl functions is called. | +| `debugSpectorJS?: boolean` | `false` | Initialize the SpectorJS WebGL debugger. | +| `debugSpectorJSUrl?: string` | N/A | SpectorJS URL. Override if CDN is down or different SpectorJS version is desired. | + +:::tip +Learn more about WebGL debugging in our [Debugging](../../developer-guide/debugging.md) guide. +::: + +#### WebGLContextAttributes + +For detailed control over WebGL context can specify what [`WebGLContextAttributes`][webgl-attributes] to use if luma creates the WebGL context. + +| `WebGLContextAttributes` | Default | Description | +| ---------------------------------------- | ------- | ------------------------------------------------------------------------------------------------------ | +| `webgl.preserveDrawingBuffers?: boolean` | `true` | Default render target buffers will preserve their values until overwritten. Useful for screen capture. | +| `webgl.alpha?: boolean` | `true` | Default render target has an alpha buffer. | +| `webgl.antialias?: boolean` | `true` | Boolean that indicates whether or not to perform anti-aliasing. | +| `webgl.depth?: boolean` | `true` | Default render target has a depth buffer of at least `16` bits. | +| `webgl.premultipliedAlpha?: boolean` | `true` | The page compositor will assume the drawing buffer contains colors with pre-multiplied alpha. | +| `webgl.stencil?: boolean` | `false` | Default render target has a stencil buffer of at least `8` bits. | +| `webgl.desynchronized?: boolean` | `false` | Hint to reduce latency by desynchronizing the canvas paint cycle from the event loop (WebGL). | +| `webgl.failIfMajorPerformanceCaveat?` | `false` | Do not create a Device if the system performance is low (WebGL). | + +Note that luma.gl v9.1 and onwards set `webgl.preserveDrawingBuffers` to `true` by default. This can be disabled for some memory savings and a minor performance boost on resource limited devices, such as mobile phones, at the cost of not being able to take screenshots or render to screen without clearing. ## Fields diff --git a/docs/api-reference/core/luma.md b/docs/api-reference/core/luma.md index 35d1f782f9..4441c706dc 100644 --- a/docs/api-reference/core/luma.md +++ b/docs/api-reference/core/luma.md @@ -22,13 +22,13 @@ Create a WebGL 2 context (throws if WebGL2 not supported) import {luma} from '@luma.gl/core'; import {webgl2Adapter} from '@luma.gl/webgl'; -const webgpuDevice = luma.createDevice({type: 'webgl', adapters: [webgl2Adapter], canvas: ...}); +const webgpuDevice = luma.createDevice({type: 'webgl', adapters: [webgl2Adapter], canvasContext: {canvas: ...}}); ``` ```typescript const webgpuDevice = luma.createDevice({ type: 'best-available', - canvas: ..., + canvasContext: {...}, adapters: [webgl2Adapter, WebGPUDevice] }); ``` @@ -41,7 +41,7 @@ Register the WebGL backend, then create a WebGL2 context, auto creating a canvas import {luma} from '@luma.gl/core'; import {webgl2Adapter} from '@luma.gl/webgl'; luma.registerAdapters([webgl2Adapter]); -const webglDevice = luma.createDevice({type: 'webgl', canvas: ...}); +const webglDevice = luma.createDevice({type: 'webgl', canvasContext: {canvas: ...}}); ``` It is possible to register more than one device to create an application @@ -52,7 +52,7 @@ import {luma} from '@luma.gl/core'; import {webgl2Adapter} from '@luma.gl/webgl'; import {webgpuDevice} from '@luma.gl/webgl'; luma.registerAdapters([webgl2Adapter, webgpuDevice]); -const device = luma.createDevice({type: 'best-available', canvas: ...}); +const device = luma.createDevice({type: 'best-available', canvasContext: {canvas: ...}}); ``` ## Registering Adapters @@ -72,7 +72,7 @@ import {luma} from '@luma.gl/core'; import {webgpuAdapter} from '@luma.gl/webgpu'; luma.registerAdapters([webgpuAdapter]); -const device = await luma.createDevice({type: 'webgpu', canvas: ...}); +const device = await luma.createDevice({type: 'webgpu', canvasContext: ...}); ``` Pre-register devices @@ -83,35 +83,35 @@ import {webgl2Adapter} from '@luma.gl/webgl'; import {webgpuAdapter} from '@luma.gl/webgpu'; luma.registerAdapters([webgl2Adapter, webgpuAdapter]); -const webgpuDevice = luma.createDevice({type: 'best-available', canvas: ...}); +const webgpuDevice = luma.createDevice({type: 'best-available', canvasContext: ...}); ``` ## Types ### `CreateDeviceProps` -Properties for creating a new device +Properties for creating a new device. See [`DeviceProps`](./device.md#deviceprops) for device specific options. ```ts -type CreateDeviceProps = DeviceProps & { +type CreateDeviceProps = { /** 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 */ adapters?: Adapter[]; -} +} & DeviceProps ``` ### `AttachDeviceProps` -Properties for attaching an existing WebGL context or WebGPU device to a new luma Device. +Properties for attaching an existing WebGL context or WebGPU device to a new luma Device. See [`DeviceProps`](./device.md#deviceprops) for device specific options. ```ts -export type AttachDeviceProps = DeviceProps & { +export type AttachDeviceProps = { /** Externally created WebGL context or WebGPU device */ handle: WebGL2RenderingContext | GPUDevice | null; /** List of device types. Will also search any pre-registered device backends */ adapters?: Adapter[]; -}; +} & DeviceProps; ``` ## Methods @@ -126,6 +126,7 @@ To create a Device instance, the application calls `luma.createDevice()`. - `type`: `'webgl' \| 'webgpu' \| 'best-available'` - `adapters`: list of `Adapter` instances providing support for different GPU backends. Can be omitted if `luma.registerAdapters()` has been called. +- `...deviceProps`: See [`DeviceProps`](./device.md#deviceprops) for device specific options. Unless a device `type` is specified a `Device` will be created using the `'best-available'` adapter. luma.gl favors WebGPU over WebGL adapters, whenever WebGPU is available. @@ -137,7 +138,7 @@ Note: A specific device type is available and supported if both of the following ### `luma.attachDevice()` ```ts -luma.attachDevice({handle: WebGL2RenderingContext | GPUDevice, adapters, ...}: AttachDeviceProps); +luma.attachDevice({handle: WebGL2RenderingContext | GPUDevice, adapters, ...deviceProps}: AttachDeviceProps); ``` A luma.gl Device can be attached to an externally created `WebGL2RenderingContext` or `GPUDevice`. @@ -145,6 +146,7 @@ This allows applications to use the luma.gl API to "interleave" rendering with o - `handle` - The externally created `WebGL2RenderingContext` or `GPUDevice` that should be attached to a luma `Device`. - `adapters` - list of `Device` backend classes. Can be omitted if `luma.registerAdapters()` has been called. +- `...deviceProps`: See [`DeviceProps`](./device.md#deviceprops) for device specific options. 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()`. diff --git a/docs/api-reference/engine/compute/computation.md b/docs/api-reference/engine/compute/computation.md index 02948aa9b0..3e153bdef1 100644 --- a/docs/api-reference/engine/compute/computation.md +++ b/docs/api-reference/engine/compute/computation.md @@ -62,7 +62,7 @@ Debug shader source (even when shader successful) // construct the model. const model = new Computation(device, { source: COMPUTE_SHADER, - debugShaders: true + debugShaders: 'always' }); ``` diff --git a/docs/api-reference/engine/model.md b/docs/api-reference/engine/model.md index 4436247f7f..f48a8cf721 100644 --- a/docs/api-reference/engine/model.md +++ b/docs/api-reference/engine/model.md @@ -75,7 +75,7 @@ Debug shader source (even when shader successful) const model = new Model(device, { vs: VERTEX_SHADER, fs: FRAGMENT_SHADER, - debugShaders: true + debugShaders: 'always' }); ``` @@ -102,7 +102,7 @@ Less commonly used properties: | `varyings?` | `string[]` | WebGL: Array of vertex shader output variables (used in TransformFeedback flow). | | `bufferMode?` | | WebGL: Mode for recording vertex shader outputs (used in TransformFeedback flow). | -`ModelProps` also include [`RenderPipelineProps`](/docs/api-reference/core/resources/render-pipeline.md), which are passed through to the `RenderPipeline` constructor, e.g: +`ModelProps` also include [`RenderPipelineProps`](/docs/api-reference/core/resources/render-pipeline), which are passed through to the `RenderPipeline` constructor, e.g: | Property | Type | Description | | ----------------- | -------------------------- | --------------------------------------------------------------------------------------- | diff --git a/docs/api-reference/webgl/README.md b/docs/api-reference/webgl/README.md index 408339ac81..461f09717e 100644 --- a/docs/api-reference/webgl/README.md +++ b/docs/api-reference/webgl/README.md @@ -4,34 +4,34 @@ This module contains the WebGL adapter for the "abstract" luma.gl API (`@luma.gl/core`). -Simply importing `@luma.gl/webgl` installs the adapter and enables WebGL devices to -be created using `luma.createDevice(...)`: +Importing `webgl2Adapter` from `@luma.gl/webgl` enables WebGL devices to +be created using `luma.createDevice(props)`. See [`CreateDeviceProps`](../core/luma#createdeviceprops) for WebGL property options. ```typescript import {luma} from '@luma.gl/core'; -import '@luma.gl/webgl'; // Installs the WebGLDevice adapter +import {webgl2Adapter}'@luma.gl/webgl'; -const device = await luma.createDevice({type: 'webgl', canvas: ...}); +const device = await luma.createDevice({adapters: [webgl2Adapter], canvasContext: {width: 800: height: 600}}); // Resources can now be created const buffer = device.createBuffer(...); ``` To use a luma.gl WebGL `Device` with raw WebGL calls, the application needs to access -the `WebGLRenderingContext`. The context is available on the `WebGLDevice` subclass: +the `WebGL2RenderingContext`. The context is available on the `WebGLDevice` subclass: ```typescript // @ts-expect-error -const gl = device.gl; +const gl = device.handle; ``` -With a bit more work, typescript users can retrieve the `WebGLRenderingContext` +With a bit more work, typescript users can retrieve the `WebGL2RenderingContext` without ignoring type errors: ```typescript import {cast} from '@luma.gl/core'; import {WebGLDevice} from '@luma.gl/webgl'; // Installs the WebGLDevice adapter -const webglDevice = cast(device); -const gl = webglDevice.gl; +const webglDevice = cast(device); +const gl = webglDevice.handle; ``` diff --git a/docs/api-reference/webgpu/README.md b/docs/api-reference/webgpu/README.md index 2a15336c9c..bd49ebf6b4 100644 --- a/docs/api-reference/webgpu/README.md +++ b/docs/api-reference/webgpu/README.md @@ -4,14 +4,26 @@ This module contains the WebGPU adapter for the "abstract" luma.gl API (`@luma.gl/core`). -Simply importing `@luma.gl/webgpu` installs the adapter and enables WebGPU devices to -be created using `luma.createDevice(...)`: +The `webgpuAdapter` imported from `@luma.gl/webgpu` enables WebGPU devices to +be created using `luma.createDevice(props)`: See [`CreateDeviceProps`](../core/luma#createdeviceprops) for WebGPU prop options. ```typescript import {luma} from '@luma.gl/core'; -import '@luma.gl/webgpu'; // Installs the WebGPUDevice adapter +import {webgpuAdapter}'@luma.gl/webgpu'; // Installs the WebGPUDevice adapter -const device = await luma.createDevice({type: 'webgpu', canvas: ...}); +const device = await luma.createDevice({adapters: [webgpuAdapter], canvasContext: {width: 800, height: 600}}); + +// Resources can now be created +const buffer = device.createBuffer(...); +``` + +If you are only interested in using WebGPU for compute and not for rendering (or if you want to manually create one or more `CanvasContext`s later), you can also create a WebGPU device without a `CanvasContext`: + +```typescript +import {luma} from '@luma.gl/core'; +import {webgpuAdapter}'@luma.gl/webgpu'; // Installs the WebGPUDevice adapter + +const device = await luma.createDevice({adapters: [webgpuAdapter]}); // Resources can now be created const buffer = device.createBuffer(...); diff --git a/docs/developer-guide/debugging.md b/docs/developer-guide/debugging.md index 90f51716f5..57f1574d51 100644 --- a/docs/developer-guide/debugging.md +++ b/docs/developer-guide/debugging.md @@ -1,5 +1,7 @@ # Debugging +## Why GPU Debugging can be hard + Debugging GPU code can be challenging. Standard CPU-side debugging tools like breakpoints and single stepping are not avaialble in GPU shaders. when shaders fail, the result is often a blank screen that does not provide much information about what went wrong. In addition, the error behind a failed render can be located in very different parts of the code: @@ -8,8 +10,9 @@ In addition, the error behind a failed render can be located in very different p - or in one of the many GPU pipeline settings - or in the way the APIs were called. -The good news is that luma.gl provides a number of facilities for debugging your GPU code, -to help you save time during development. These features include +## Debug Support Overview + +luma.gl provides a number of facilities for debugging your GPU code, to help you save time during development. These features include - All GPU objects have auto-populated but configurable `id` fields. - Configurable logging of GPU operations, with optional verbose logs that display all values being passed to each draw call. @@ -18,6 +21,37 @@ to help you save time during development. These features include - Spector.js integration - Khronos WebGL debug integration - Synchronous WebGL error capture (optional module). +## Debug flags + +The `luma.createDevice()` API accepts a number of debug parameters + +```ts +const device = luma.createDevice({ + debugFramebuffers: true, + debugWebGL: true, + debugSpectorJS: true, +}); +``` + +## Browser console debug API + +luma.gl exposes a global variable `luma.log` that can be manipulated in your browser dev tools console window to activate debugging. +A nice aspect of this system is that it keeps state when refreshing the browser page, meaning that you can change log level, refresh the browser tab and get logs while your program reinitializes. + +You can enable and disable debug features using the `luma.log.set` feature. In your browser console tab, type: + +```ts +luma.log.set('debug-webgl', true) +``` + +You can also control the amount of logging you get by changing `luma.log.level`: + +```ts +luma.log.level=1 +``` + + + ## id strings Most classes in luma.gl allow you to supply an optional `id` string to their constructors. @@ -30,16 +64,15 @@ const program = device.createRenderPipeline({id: 'pyramid-program', ...}); ``` Apart from providing a human-readable `id` field when inspecting objects in the debugger, -the `id`s that the application provides are used in multiple places: +the `id` is used in the following ways: -- luma.gl's built-in logging (see next section) often includes object `id`s. +- luma.gl's built-in logging (see next section) often includes the `id`s. - `id` is copied into the WebGPU object `label` field which is designed to support debugging. -- `id` is exposed to Specter.js (luma.gl sets the [`__SPECTOR_Metadata`](https://github.com/BabylonJS/Spector.js#custom-data field on WebGL object handles). +- `id` is exposed to the WebGL Spector.js library (when activated, luma.gl sets the [`__SPECTOR_Metadata`](https://github.com/BabylonJS/Spector.js#custom-data field on WebGL object handles). ## Logging -luma.gl logs its activities. - +luma.gl logs a number of activities which can be helpful to understanding what is happening. Set the global variable `luma.log.level` (this can be done in the browser console at any time) ```ts @@ -66,7 +99,6 @@ Note that luma.gl also injects and parses `glslify`-style `#define SHADER_NAME` Naming shaders directly in the shader code can help identify which shader is involved when debugging shader parsing errors occur. - ## Buffer data inspection `Buffer` objects contain a CPU side copy of the first few bytes of data in the `buffer.debugData` field. This field is refreshed whenever data is written or read from the CPU side, using `buffer.write()`, `buffer.readAsync()` etc and can be inspected in the debugger to inspect the contents of the buffer. @@ -95,8 +127,8 @@ The most flexible way to enable WebGL API tracing is by typing the following com Note that the developer tools module is loaded dynamically when a device is created with the debug flag set, so the developer tools can be activated in production code by opening the browser console and typing: -``` -luma.set('debug', true) +```ts +luma.set('debug-webgl', true) ``` then reload your browser tab. @@ -105,7 +137,7 @@ While usually not recommended, it is also possible to activate the developer too ```ts import {luma} from '@luma.gl/core'; -const device = luma.createDevice({type: 'webgl', debug: true}); +const device = luma.createDevice({type: 'webgl', {webgl: {debug: true}}); ``` > Warning: WebGL debug contexts impose a significant performance penalty (luma waits for the GPU after each WebGL call to check error codes) and should not be activated in production code. @@ -116,8 +148,8 @@ luma.gl integrates with [Spector.js](https://spector.babylonjs.com/), a powerful The most flexible way to enable Spector.js is by typing the following command into the browser developer tools console: -``` -luma.log.set('spector', true); +```ts +luma.log.set('debug-spectorjs', true); ``` And then restarting the application (e.g. via Command-R on MacOS), @@ -127,10 +159,10 @@ You can also enable spector when creating a device by adding the `spector: true To display Spector.js stats when loaded. -``` +```ts luma.spector.displayUI() ``` :::info Spector.js is dynamically loaded into your application, so there is no bundle size penalty. -::: \ No newline at end of file +::: diff --git a/docs/developer-guide/installing.md b/docs/developer-guide/installing.md index ff985a48a0..38cce8e045 100644 --- a/docs/developer-guide/installing.md +++ b/docs/developer-guide/installing.md @@ -24,7 +24,7 @@ yarn add @luma.gl/webgpu import {luma} from '@luma.gl/core'; import '@luma.gl/webgpu'; -const device = await luma.createDevice({type: 'webgpu', canvas: ...}); +const device = await luma.createDevice({type: 'webgpu', canvasContext: ...}); ``` It is possible to register more than one device adapter to create an application @@ -35,7 +35,7 @@ import {luma} from '@luma.gl/core'; import '@luma.gl/webgpu'; import '@luma.gl/webgl'; -const webgpuDevice = luma.createDevice({type: 'best-available', canvas: ...}); +const webgpuDevice = luma.createDevice({type: 'best-available', canvasContext: ...}); ``` ## A Typical Install diff --git a/docs/legacy/legacy-whats-new.md b/docs/legacy/legacy-whats-new.md index adad6d9766..37e75c6bf0 100644 --- a/docs/legacy/legacy-whats-new.md +++ b/docs/legacy/legacy-whats-new.md @@ -383,7 +383,7 @@ A new method `Model.transform` makes it easier to run basic transform feedback o #### Transform class (WebGL 2) -[`Transform`](/docs/api-reference/engine/transform) is now an officially supported luma.gl class. This new class provides an easy-to-use interface to Transform Feedback. This class hides complexity by internally creating and managing the supporing WebGL objects that are necessary to perform Transform Feedback operations. +`Transform` is now an officially supported luma.gl class. This new class provides an easy-to-use interface to Transform Feedback. This class hides complexity by internally creating and managing the supporing WebGL objects that are necessary to perform Transform Feedback operations. #### GLSL Transpilation @@ -428,7 +428,7 @@ Date: Apr 24, 2018 #### Transform class (WebGL 2, Experimental) -The new experimental [`Transform`](/docs/api-reference/engine/transform) class provides an easy-to-use interface to perform Transform Feedback operations. +The new experimental `Transform` class provides an easy-to-use interface to perform Transform Feedback operations. **Pixel Readback to GPU Buffers** (WebGL 2) - A new method `Framebuffer.readPixelsToBuffer` is added to asynchronously read pixel data into a `Buffer` object. This allows applications to reduce the CPU-GPU sync time by postponing transfer of data or to completely avoid GPU-CPU sync by using the pixel data in the GPU `Buffer` object directly as data source for another GPU draw or transform feedback operation. diff --git a/docs/legacy/porting-guide.md b/docs/legacy/porting-guide.md index 6312f38e90..69fbcdb050 100644 --- a/docs/legacy/porting-guide.md +++ b/docs/legacy/porting-guide.md @@ -59,12 +59,12 @@ In progress A subset of the new interfaces in the luma.gl v9 API: -| Interface | Description | -| --- | --- | -| `Adapter` | luma.gl exposes GPU capabilities on the device in the form of one or more as `Adapter`s. | -| `Device` | Manages resources, and the device’s GPUQueues, which execute commands. | +| Interface | Description | +| --------- | ------------------------------------------------------------------------------------------------------------------------------- | +| `Adapter` | luma.gl exposes GPU capabilities on the device in the form of one or more as `Adapter`s. | +| `Device` | Manages resources, and the device’s GPUQueues, which execute commands. | | `Buffer` | The physical resources backed by GPU memory. A `Device` may have its own memory with high-speed access to the processing units. | -| `Texture` | Like buffer, but supports random access | +| `Texture` | Like buffer, but supports random access | GPUCommandBuffer and GPURenderBundle are containers for user-recorded commands. | `Shader` | Compiled shader code. | `Sampler` | or GPUBindGroup, configure the way physical resources are used by the GPU. | @@ -181,15 +181,15 @@ The debug module has been removed. Debug functionality is now built-in (and dyna #### `AnimationLoop` -| v8 Prop or Method | v9 Replacement | Comment | -| ------------------------------- | ---------------------------------- | ------------------------------------------------ | +| v8 Prop or Method | v9 Replacement | Comment | +| ------------------------------- | ---------------------------------------- | ------------------------------------------------------------------------ | | Properties | -| `props.gl` | `props.device` | Now accepts a `Promise`. | -| `props.glOptions` | `luma.createDevice()` | -| `props.createFramebuffer` | N/A | You will need to create framebuffers explicitly. | -| `props.debug` | `luma.createDevice({debug: true})` | Device debug functionality is improved. | +| `props.gl` | `props.device` | Now accepts a `Promise`. | +| `props.glOptions` | `luma.createDevice()` | See [CreateDeviceProps](../api-reference/core/luma.md#createdeviceprops) | +| `props.createFramebuffer` | N/A | You will need to create framebuffers explicitly. | +| `props.debug` | `luma.createDevice({debugWebGL: true}})` | Device debug option naming improvements. | | Methods | -| `animationLoop.isContextLost()` | `device.lost`, `device.isLost()` | | +| `animationLoop.isContextLost()` | `device.lost`, `device.isLost()` | | ### `luma.gl/core` / `@luma.gl/webgl` @@ -214,7 +214,7 @@ The v8 luma.gl API was designed to allow apps to work directly with the `WebGLRe | `WebGL2RenderingContext` | `Device.gl` | Contexts are created (or wrapped) by the `Device` class. | | | `getWebGL2Context(gl)` | `device.gl2` | | | `assertWebGLContext(gl)` | N/A | A device will always hold a valid WebGL context | -| `assertWebGL2Context(gl)` | N/A | Not needed. WebGL devices now always use WebGL2. | +| `assertWebGL2Context(gl)` | N/A | Not needed. WebGL devices now always use WebGL2. | | | | | `createGLContext()` | `luma.createDevice()` | Will create a WebGL context if `WebGLDevice` is registered. | | `createGLContext({onContextLost})` | `device.lost` | `Promise` that lets the application `await` context loss. | @@ -244,10 +244,10 @@ The v8 luma.gl API was designed to allow apps to work directly with the `WebGLRe ### `@luma.gl/debug`** -| v8 Prop or Method | v9 Replacement | Comment | -| -------------------- | --------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | -| `makeDebugContext()` | `luma.createDevice({debug: true, type: 'webgl'})` | [Khronos WebGL developer tools][khronos_dev_tools] are dynamically loaded when needed. | -| Spector.js | `luma.createDevice({spector: true, type: 'webgl'})` | [Spector.js][spector] is pre-integrated. Te Spector.js library will be dynamically loaded when needed and the canvas is "captured". | +| v8 Prop or Method | v9 Replacement | Comment | +| -------------------- | ----------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | +| `makeDebugContext()` | `luma.createDevice({debugWebGL: true, type: 'webgl'})` | [Khronos WebGL developer tools][khronos_dev_tools] are dynamically loaded when needed. | +| Spector.js | `luma.createDevice({debugSpectorJS: true}, type: 'webgl'})` | [Spector.js][spector] is pre-integrated. The Spector.js library will be dynamically loaded when needed and the canvas is "captured". | [khronos_dev_tools]: https://github.com/KhronosGroup/WebGLDeveloperTools [spector]: https://spector.babylonjs.com/ @@ -261,7 +261,7 @@ Feature constants have been changed to match the WebGPU API and will need to be | luma.gl v8 `FEATURE` | v9 `DeviceFeature` | Comments | | ------------------------------------------- | -------------------------------------- | ----------------------------------------------------------------------------- | | **General WebGL Features** | | | -| `FEATURES.WEBGL2` | `webgl` | True for WebGL Contexts | +| `FEATURES.WEBGL2` | `webgl` | True for WebGL Contexts | | `FEATURES.TIMER_QUERY` | `timer-query-webgl` | [`Query`][/]ocs/api-reference/webgl/query) for asynchronous GPU timings | | `FEATURES.INSTANCED_RENDERING` | N/A (always true) | Instanced rendering (via instanced vertex attributes) | | `FEATURES.VERTEX_ARRAY_OBJECT` | N/A (always true) | `VertexArrayObjects` can be created | @@ -298,28 +298,28 @@ Feature constants have been changed to match the WebGPU API and will need to be The following table shows mappings from luma v8 WebGL parameters to luma v9 WebGPU style parameters. -| luma v8 / WebGL Parameter | v9 Parameter | Values | v9 Values | -| -------------------------------------------------------- | ---------------------------------- | -------------------------------------- | --------------------------------- | -| [polygonOffset][polygonoffset] | `depthBias`, `depthBiasSlopeScale` | -| [depthRange][depthrange] | N/A | -| [clearDepth][cleardepth] | | +| luma v8 / WebGL Parameter | v9 Parameter | Values | v9 Values | +| -------------------------------------------------------- | --------------------------------------------------------- | -------------------------------------- | --------------------------------- | +| [polygonOffset][polygonoffset] | `depthBias`, `depthBiasSlopeScale` | +| [depthRange][depthrange] | N/A | +| [clearDepth][cleardepth] | | | **Rasterization Parameters** | -| [`cullFace`][cullface] | `cullMode` | Which face to cull | **`'none'`**, `'front'`, `'back'` | -| [`frontFace`][frontface] | `frontFace` | Which triangle winding order is front | **`ccw`**, `cw` | -| `polygonOffset` | `depthBias` | Small depth offset for polygons | `float` | -| `polygonOffset` | `depthBiasSlopeScale` | Small depth factor for polygons | `float` | -| `polygonOffset` | `depthBiasClamp` | Max depth offset for polygons | `float` | +| [`cullFace`][cullface] | `cullMode` | Which face to cull | **`'none'`**, `'front'`, `'back'` | +| [`frontFace`][frontface] | `frontFace` | Which triangle winding order is front | **`ccw`**, `cw` | +| `polygonOffset` | `depthBias` | Small depth offset for polygons | `float` | +| `polygonOffset` | `depthBiasSlopeScale` | Small depth factor for polygons | `float` | +| `polygonOffset` | `depthBiasClamp` | Max depth offset for polygons | `float` | | **Stencil Parameters** | -| [`stencilMask`][stencilmask] / `GL.STENCIL_WRITEMASK` | `stencilReadMask` | Binary mask for reading stencil values | `number` (**`0xffffffff`**) | -| `stencilFunc` / `GL.STENCIL_VALUE_MASK` | `stencilWriteMask` | Binary mask for writing stencil values | `number` (**`0xffffffff`**) | -| `stencilFunc` / `GL.STENCIL_FUNC` | `stencilCompare` | How the mask is compared | **`always`**, `not-equal`, ... | -| [`stencilOp`][stencilop] / `GL.STENCIL_PASS_DEPTH_PASS` | `stencilPassOperation` | | **`'keep'`** | -| [`stencilOp`][stencilop] / `GL.STENCIL_PASS_DEPTH_FAIL` | `stencilDepthFailOperation` | | **`'keep'`** | -| [`stencilOp`][stencilop] / `GL.STENCIL_FAIL` | `stencilFailOperation` | | **`'keep'`** | -| [`stencilOpSeparate`][stencilopseparate] | | | | +| [`stencilMask`][stencilmask] / `GL.STENCIL_WRITEMASK` | `stencilReadMask` | Binary mask for reading stencil values | `number` (**`0xffffffff`**) | +| `stencilFunc` / `GL.STENCIL_VALUE_MASK` | `stencilWriteMask` | Binary mask for writing stencil values | `number` (**`0xffffffff`**) | +| `stencilFunc` / `GL.STENCIL_FUNC` | `stencilCompare` | How the mask is compared | **`always`**, `not-equal`, ... | +| [`stencilOp`][stencilop] / `GL.STENCIL_PASS_DEPTH_PASS` | `stencilPassOperation` | | **`'keep'`** | +| [`stencilOp`][stencilop] / `GL.STENCIL_PASS_DEPTH_FAIL` | `stencilDepthFailOperation` | | **`'keep'`** | +| [`stencilOp`][stencilop] / `GL.STENCIL_FAIL` | `stencilFailOperation` | | **`'keep'`** | +| [`stencilOpSeparate`][stencilopseparate] | | | | | **Sampler Parameters** | -| `magFilter` / `GL.TEXTURE_MAG_FILTER` * | `magFilter`, `mipmapFilter`, `lodMinClamp`, `lodMaxClamp` | | `'linear'`, `'nearest'`, ... | -| `minFilter` / `GL.TEXTURE_MIN_FILTER` * | `minFilter`, `mipmapFilter`, `lodMinClamp`, `lodMaxClamp` | | `'linear'`, `'nearest'`, ... | +| `magFilter` / `GL.TEXTURE_MAG_FILTER` * | `magFilter`, `mipmapFilter`, `lodMinClamp`, `lodMaxClamp` | | `'linear'`, `'nearest'`, ... | +| `minFilter` / `GL.TEXTURE_MIN_FILTER` * | `minFilter`, `mipmapFilter`, `lodMinClamp`, `lodMaxClamp` | | `'linear'`, `'nearest'`, ... | \* Note that the `magFilter`, `minFilter`, and `mipmapFilter` parameters accept only `'nearest'` and `'linear'` as values. To disable use of mipmaps on a texture that includes them, set `lodMaxClamp` to zero. diff --git a/docs/tutorials/transform-feedback.mdx b/docs/tutorials/transform-feedback.mdx index 433c02ee1a..ce2a17a853 100644 --- a/docs/tutorials/transform-feedback.mdx +++ b/docs/tutorials/transform-feedback.mdx @@ -14,7 +14,7 @@ The tutorial pages have not yet been updated for luma.gl v9. -In luma.gl, transform feedback is primarily exposed via the [Transform](/docs/api-reference/engine/transform) class, which simplifies usage by managing input and output buffers. We'll demonstrate its usage by setting up a simple animation that runs completely on the GPU. +In luma.gl, transform feedback is primarily exposed via the [BufferTransform](/docs/api-reference/engine/compute/buffer-transform) class, which simplifies usage by managing input and output buffers. We'll demonstrate its usage by setting up a simple animation that runs completely on the GPU. To start, we'll modify our imports to include `Transform` from **@luma.gl/engine**: diff --git a/docs/tutorials/transform.mdx b/docs/tutorials/transform.mdx index fe39796380..4fa0cf59c6 100644 --- a/docs/tutorials/transform.mdx +++ b/docs/tutorials/transform.mdx @@ -18,7 +18,7 @@ Transform feedback allows us to capture vertex shader results from one pass and use them in subsequent passes. It is a powerful tool that can be used to set up massively parrallelized animations or data transformations. Note that transform feedback can only be used with WebGL 2. -In luma.gl, transform feedback is primarily exposed via the [Transform](/docs/api-reference/engine/transform) class, which simplifies usage by managing input and output buffers. We'll demonstrate its usage by setting up a simple animation that runs completely on the GPU. +In luma.gl, transform feedback is primarily exposed via the [Transform](/docs/api-reference/engine/compute/buffer-transform) class, which simplifies usage by managing input and output buffers. We'll demonstrate its usage by setting up a simple animation that runs completely on the GPU. To start, we'll modify our imports to include `Transform` from **@luma.gl/engine**: diff --git a/docs/upgrade-guide.md b/docs/upgrade-guide.md index 86df9acdc6..d50c53cd9b 100644 --- a/docs/upgrade-guide.md +++ b/docs/upgrade-guide.md @@ -33,17 +33,19 @@ When initializing luma.gl, applications now import an `Adapter` singleton from e **@luma.gl/core** -| Updated API | Status | Replacement | Comment | -| ------------------------------ | ---------- | -------------------------------------- | --------------------------------------------------------------- | -| `luma.registerDevices()` | Deprecated | [`luma.registerAdapters()`][adapters]. | Adapters provide a cleaner way to work with GPU backends. | -| `Texture.props.data` (Promise) | Removed | `AsyncTexture` class | Textures no longer accept promises. | -| `Parameters.blend` | New | | Explicit activation of color blending | -| `triangle-fan-webgl` topology | Removed | `triangle-strip`. | Reorganize your geometries | -| `line-loop-webgl` topology | Removed | `line-list`. | Reorganize your geometries | -| `glsl` shader template string | Removed | `/* glsl */` comment | Enable syntax highlighting in vscode using before shader string | -| `depth24unorm-stencil8` | Removed | `depth24plus-stencil8` | The `TextureFormat` was removed from the WebGPU spec | -| `rgb8unorm-unsized` | Removed | `rgb8unorm` | No longer support unsized WebGL1 `TextureFormat` | -| `rgba8unorm-unsized` | Removed | `rgb8aunorm` | No longer support unsized WebGL1 `TextureFormat` | +| Updated API | Status | Replacement | Comment | +| ------------------------------ | ---------- | ----------------------------------------- | --------------------------------------------------------------- | +| `luma.registerDevices()` | Deprecated | [`luma.registerAdapters()`][adapters]. | Adapters provide a cleaner way to work with GPU backends. | +| `DeviceProps` for canvas | Moved | [`DeviceProps.canvasContext...`][canvas]. | Move canvas related props to `props.canvasContext: {}`. | +| `DeviceProps` for webgl | Moved | [`DeviceProps.webgl...`][webgl]. | Move canvas related props to `props.webgl: {}`. | +| `Texture.props.data` (Promise) | Removed | `AsyncTexture` class | Textures no longer accept promises. | +| `Parameters.blend` | New | | Explicit activation of color blending | +| `triangle-fan-webgl` topology | Removed | `triangle-strip`. | Reorganize your geometries | +| `line-loop-webgl` topology | Removed | `line-list`. | Reorganize your geometries | +| `glsl` shader template string | Removed | `/* glsl */` comment | Enable syntax highlighting in vscode using before shader string | +| `depth24unorm-stencil8` | Removed | `depth24plus-stencil8` | The `TextureFormat` was removed from the WebGPU spec | +| `rgb8unorm-unsized` | Removed | `rgb8unorm` | No longer support unsized WebGL1 `TextureFormat` | +| `rgba8unorm-unsized` | Removed | `rgb8aunorm` | No longer support unsized WebGL1 `TextureFormat` | [adapters]: /docs/api-reference/core/luma#lumaregisteradapters @@ -57,6 +59,11 @@ When initializing luma.gl, applications now import an `Adapter` singleton from e | `getDependencyGraph()` | Removed | `getShaderModuleDependencies(module)` . | Interact directly with the shader module | | `glsl` template string | Removed | `/* glsl */` comment | Enable syntax highlighting in vscode using comment | + +**@luma.gl/webgl** + +- `WebGLDeviceContext` - Note that luma.gl v9.1 and onwards set `DeviceProps.webgl.preserveDrawingBuffers` to `true` by default. This can be disabled for some memory savings and a minor performance boost on resource limited devices, such as mobile phones, at the cost of not being able to take screenshots or rendering to screen without clearing it. + ## Upgrading to v9.0 luma.gl v9 is a major modernization of the luma.gl API, with many breaking changes, so the upgrade notes for this release are unusually long. To facilitate porting to the v9 release we have also provided a diff --git a/modules/constants/src/webgl-constants.ts b/modules/constants/src/webgl-constants.ts index 9225205693..9c082e3735 100644 --- a/modules/constants/src/webgl-constants.ts +++ b/modules/constants/src/webgl-constants.ts @@ -11,6 +11,7 @@ * @privateRemarks Locally called `GLEnum` instead of `GL`, because `babel-plugin-inline-webl-constants` * both depends on and processes this module, but shouldn't replace these declarations. */ +// eslint-disable-next-line no-shadow enum GLEnum { // Clearing buffers // Constants passed to clear() to clear buffer masks. diff --git a/modules/core/src/adapter-utils/format-compiler-log.ts b/modules/core/src/adapter-utils/format-compiler-log.ts index b01a45cf29..3ea9b12fe0 100644 --- a/modules/core/src/adapter-utils/format-compiler-log.ts +++ b/modules/core/src/adapter-utils/format-compiler-log.ts @@ -67,8 +67,9 @@ ${numberedLines}${positionIndicator}${message.type.toUpperCase()}: ${message.mes `; } + const color = message.type === 'error' ? 'red' : '#8B4000'; // dark orange return options?.html - ? `
${message.type.toUpperCase()}: ${ + ? `
${message.type.toUpperCase()}: ${ message.message }
` : `${message.type.toUpperCase()}: ${message.message}`; diff --git a/modules/core/src/adapter/canvas-context.ts b/modules/core/src/adapter/canvas-context.ts index 544d239ef9..2a3b57e298 100644 --- a/modules/core/src/adapter/canvas-context.ts +++ b/modules/core/src/adapter/canvas-context.ts @@ -13,24 +13,24 @@ const isPageLoaded: () => boolean = () => isPage && document.readyState === 'com /** Properties for a CanvasContext */ export type CanvasContextProps = { - /** If canvas not supplied, will be created and added to the DOM. If string, will be looked up in the DOM */ + /** If a canvas not supplied, one will be created and added to the DOM. If a string, a canvas with that id will be looked up in the DOM */ canvas?: HTMLCanvasElement | OffscreenCanvas | string | null; - /** If new canvas is created, it will be created in the specified container, otherwise appended to body */ + /** If new canvas is created, it will be created in the specified container, otherwise is appended as a child of document.body */ container?: HTMLElement | string | null; - /** Width in pixels of the canvas */ + /** Width in pixels of the canvas - used when creating a new canvas */ width?: number; - /** Height in pixels of the canvas */ + /** Height in pixels of the canvas - used when creating a new canvas */ height?: number; + /** Visibility (only used if new canvas is created). */ + visible?: boolean; /** Whether to apply a device pixels scale factor (`true` uses browser DPI) */ useDevicePixels?: boolean | number; - /** Whether to track resizes (if not ) */ + /** Whether to track window resizes */ autoResize?: boolean; - /** Visibility (only used if new canvas is created). */ - visible?: boolean; - /** WebGPU only https://www.w3.org/TR/webgpu/#canvas-configuration */ - colorSpace?: 'srgb'; // GPUPredefinedColorSpace - /** WebGPU only https://www.w3.org/TR/webgpu/#canvas-configuration */ + /** https://developer.mozilla.org/en-US/docs/Web/API/GPUCanvasContext/configure#alphamode */ alphaMode?: 'opaque' | 'premultiplied'; + /** https://developer.mozilla.org/en-US/docs/Web/API/GPUCanvasContext/configure#colorspace */ + colorSpace?: 'srgb'; // GPUPredefinedColorSpace }; const DEFAULT_CANVAS_CONTEXT_PROPS: Required = { @@ -41,8 +41,8 @@ const DEFAULT_CANVAS_CONTEXT_PROPS: Required = { autoResize: true, container: null, visible: true, - colorSpace: 'srgb', - alphaMode: 'opaque' + alphaMode: 'opaque', + colorSpace: 'srgb' }; /** diff --git a/modules/core/src/adapter/device.ts b/modules/core/src/adapter/device.ts index e673b9e961..7a6e680207 100644 --- a/modules/core/src/adapter/device.ts +++ b/modules/core/src/adapter/device.ts @@ -194,52 +194,58 @@ type WebGLCompressedTextureFeatures = /** Device properties */ export type DeviceProps = { + /** string id for debugging. Stored on the object, used in logging and set on underlying GPU objects when feasible. */ id?: string; - - // Common parameters - canvas?: HTMLCanvasElement | OffscreenCanvas | string | null; // A canvas element or a canvas string id - container?: HTMLElement | string | null; - width?: number /** width is only used when creating a new canvas */; - height?: number /** height is only used when creating a new canvas */; - - /** Request a Device with the highest limits supported by platform. WebGPU: devices can be created with minimal limits. */ - requestMaxLimits?: boolean; - - // WebGLContext PARAMETERS - Can only be set on context creation... - // alpha?: boolean; // Default render target has an alpha buffer. - // depth?: boolean; // Default render target has a depth buffer of at least 16 bits. - // stencil?: boolean; // Default render target has a stencil buffer of at least 8 bits. - // antialias?: boolean; // Boolean that indicates whether or not to perform anti-aliasing. - // premultipliedAlpha?: boolean; // Boolean that indicates that the page compositor will assume the drawing buffer contains colors with pre-multiplied alpha. - // preserveDrawingBuffer?: boolean; // Default render target buffers will not be automatically cleared and will preserve their values until cleared or overwritten - // failIfMajorPerformanceCaveat?: boolean; // Do not create if the system performance is low. - + /** Properties for the default canvas context */ + canvasContext?: CanvasContextProps; + /** Control which type of GPU is preferred on systems with both integrated and discrete GPU. Defaults to "high-performance" / discrete GPU. */ + powerPreference?: 'default' | 'high-performance' | 'low-power'; + /** Hints that device creation should fail if no hardware GPU is available (if the system performance is "low"). */ + failIfMajorPerformanceCaveat?: boolean; /** Error handling */ onError?: (error: Error) => unknown; - // @deprecated Attach to existing context. Rename to handle? Use Device.attach? - gl?: WebGL2RenderingContext | null; + /** WebGL specific: Properties passed through to WebGL2RenderingContext creation: `canvas.getContext('webgl2', props.webgl)` */ + webgl?: WebGLContextProps; // DEBUG SETTINGS - /** WebGL: Instrument WebGL2RenderingContext (at the expense of performance) */ - debug?: boolean; - /** Break on WebGL functions matching these strings */ - break?: string[]; - - /** WebGL: Initialize the SpectorJS WebGL debugger */ - debugWithSpectorJS?: boolean; - /** SpectorJS URL. Override if CDN is down or different SpectorJS version is desired */ - spectorUrl?: string; - - // EXPERIMENTAL SETTINGS - /** Set to false to disable WebGL state management instrumentation: TODO- Unclear if still supported / useful */ - manageState?: boolean; - /** Initialize all features on startup */ - initalizeFeatures?: boolean; + + /** Show shader source in browser? The default is`'error'`, meaning that logs are shown when shader compilation has errors */ + debugShaders?: 'never' | 'errors' | 'warnings' | 'always'; + /** Renders a small version of updated Framebuffers into the primary canvas context. Can be set in console luma.log.set('debug-framebuffers', true) */ + debugFramebuffers?: boolean; + /** WebGL specific - Trace WebGL calls (instruments WebGL2RenderingContext at the expense of performance). Can be set in console luma.log.set('debug-webgl', true) */ + debugWebGL?: boolean; + /** WebGL specific - Initialize the SpectorJS WebGL debugger. Can be set in console luma.log.set('debug-spectorjs', true) */ + debugSpectorJS?: boolean; + /** WebGL specific - SpectorJS URL. Override if CDN is down or different SpectorJS version is desired. */ + debugSpectorJSUrl?: string; + + // EXPERIMENTAL SETTINGS - subject to change + + /** WebGPU specific - Request a Device with the highest limits supported by platform. On WebGPU devices can be created with minimal limits. */ + _requestMaxLimits?: boolean; /** Disable specific features */ - disabledFeatures?: Partial>; + _disabledFeatures?: Partial>; + /** WebGL specific - Initialize all features on startup */ + _initializeFeatures?: boolean; /** Never destroy cached shaders and pipelines */ _factoryDestroyPolicy?: 'unused' | 'never'; + + /** @deprecated Internal, Do not use directly! Use `luma.attachDevice()` to attach to pre-created contexts/devices. */ + _handle?: unknown; // WebGL2RenderingContext | GPUDevice | null; +}; + +/** WebGL independent copy of WebGLContextAttributes */ +type WebGLContextProps = { + alpha?: boolean; // indicates if the canvas contains an alpha buffer. + desynchronized?: boolean; // hints the user agent to reduce the latency by desynchronizing the canvas paint cycle from the event loop + antialias?: boolean; // indicates whether or not to perform anti-aliasing. + depth?: boolean; // indicates that the drawing buffer has a depth buffer of at least 16 bits. + failIfMajorPerformanceCaveat?: boolean; // indicates if a context will be created if the system performance is low or if no hardware GPU is available. + powerPreference?: 'default' | 'high-performance' | 'low-power'; + premultipliedAlpha?: boolean; // page compositor will assume the drawing buffer contains colors with pre-multiplied alpha. + preserveDrawingBuffer?: boolean; // buffers will not be cleared and will preserve their values until cleared or overwritten by the author. }; /** @@ -259,39 +265,32 @@ export interface DeviceFactory { export abstract class Device { static defaultProps: Required = { id: null!, - canvas: null, - container: null, - manageState: true, - width: 800, // width are height are only used by headless gl - height: 600, - requestMaxLimits: true, + powerPreference: 'high-performance', + failIfMajorPerformanceCaveat: false, + canvasContext: undefined!, // Callbacks onError: (error: Error) => log.error(error.message), - gl: null, - - // alpha: undefined, - // depth: undefined, - // stencil: undefined, - // antialias: undefined, - // premultipliedAlpha: undefined, - // preserveDrawingBuffer: undefined, - // failIfMajorPerformanceCaveat: undefined - - debug: Boolean(log.get('debug')), // Instrument context (at the expense of performance) - break: (log.get('break') as string[]) || [], - - // WebGL specific debugging - debugWithSpectorJS: undefined!, - spectorUrl: undefined!, - + _factoryDestroyPolicy: 'unused', // TODO - Change these after confirming things work as expected - initalizeFeatures: true, - disabledFeatures: { + _initializeFeatures: true, + _disabledFeatures: { 'compilation-status-async-webgl': true }, - _factoryDestroyPolicy: 'unused' + _requestMaxLimits: true, + + // WebGL specific + webgl: {}, + + debugShaders: log.get('debug-shaders') || undefined!, + debugFramebuffers: Boolean(log.get('debug-framebuffers')), + debugWebGL: Boolean(log.get('debug-webgl')), + debugSpectorJS: undefined!, // Note: log setting is queried by the spector.js code + debugSpectorJSUrl: undefined!, + + // INTERNAL + _handle: undefined! }; get [Symbol.toStringTag](): string { diff --git a/modules/core/src/adapter/luma.ts b/modules/core/src/adapter/luma.ts index e99a41dc06..a335ccca2f 100644 --- a/modules/core/src/adapter/luma.ts +++ b/modules/core/src/adapter/luma.ts @@ -20,19 +20,21 @@ const ERROR_MESSAGE = 'No matching device found. Ensure `@luma.gl/webgl` and/or `@luma.gl/webgpu` modules are imported.'; /** Properties for creating a new device */ -export type CreateDeviceProps = DeviceProps & { +export type CreateDeviceProps = { /** Selects the type of device. `best-available` uses webgpu if available, then webgl. */ type?: 'webgl' | 'webgpu' | 'unknown' | 'best-available'; + /** List of adapters. Will also search any pre-registered adapters */ adapters?: Adapter[]; -}; +} & DeviceProps; /** Properties for attaching an existing WebGL context or WebGPU device to a new luma Device */ -export type AttachDeviceProps = DeviceProps & { +export type AttachDeviceProps = { + type?: 'webgl' | 'webgpu' | 'unknown' | 'best-available'; /** Externally created WebGL context or WebGPU device */ handle: unknown; // WebGL2RenderingContext | GPUDevice | null; - /** List of adapters. Will also search any pre-registered adapterss */ + /** List of adapters. Will also search any pre-registered adapters */ adapters?: Adapter[]; -}; +} & DeviceProps; /** * Entry point to the luma.gl GPU abstraction diff --git a/modules/core/src/adapter/resources/shader.ts b/modules/core/src/adapter/resources/shader.ts index 64c093b5cf..7611537cff 100644 --- a/modules/core/src/adapter/resources/shader.ts +++ b/modules/core/src/adapter/resources/shader.ts @@ -23,8 +23,8 @@ export type ShaderProps = ResourceProps & { sourceMap?: string | null; /** Optional shader entry point (WebGPU only) */ entryPoint?: string; - /** Show shader source in browser? */ - debug?: 'never' | 'errors' | 'warnings' | 'always'; + /** Show shader source in browser? Overrides the device.props.debugShaders setting */ + debugShaders?: 'never' | 'errors' | 'warnings' | 'always'; }; /** @@ -39,7 +39,7 @@ export abstract class Shader extends Resource { source: '', sourceMap: null, entryPoint: 'main', - debug: 'errors' + debugShaders: undefined! }; override get [Symbol.toStringTag](): string { @@ -55,6 +55,7 @@ export abstract class Shader extends Resource { /** Create a new Shader instance */ constructor(device: Device, props: ShaderProps) { + props = {...props, debugShaders: props.debugShaders || device.props.debugShaders || 'errors'}; super(device, {id: getShaderIdFromProps(props), ...props}, Shader.defaultProps); this.stage = this.props.stage; this.source = this.props.source; @@ -78,7 +79,8 @@ export abstract class Shader extends Resource { // PORTABLE HELPERS /** In browser logging of errors */ - async debugShader(trigger = this.props.debug): Promise { + async debugShader(): Promise { + const trigger = this.props.debugShaders; switch (trigger) { case 'never': return; @@ -94,7 +96,7 @@ export abstract class Shader extends Resource { } const messages = await this.getCompilationInfo(); - if (this.props.debug === 'warnings' && messages?.length === 0) { + if (trigger === 'warnings' && messages?.length === 0) { return; } this._displayShaderLog(messages); diff --git a/modules/core/src/gpu-type-utils/decode-texture-format.ts b/modules/core/src/gpu-type-utils/decode-texture-format.ts index fbe7ab07ef..a469d7ee7f 100644 --- a/modules/core/src/gpu-type-utils/decode-texture-format.ts +++ b/modules/core/src/gpu-type-utils/decode-texture-format.ts @@ -11,7 +11,7 @@ const COMPRESSED_TEXTURE_FORMAT_PREFIXES = [ 'bc1', 'bc2', 'bc3', 'bc4', 'bc5', 'bc6', 'bc7', 'etc1', 'etc2', 'eac', 'atc', 'astc', 'pvrtc' ]; -const REGEX = /^(r|rg|rgb|rgba|bgra)([0-9]*)([a-z]*)(-srgb)?(-webgl)?$/; +const RGB_FORMAT_REGEX = /^(r|rg|rgb|rgba|bgra)([0-9]*)([a-z]*)(-srgb)?(-webgl)?$/; export type DecodedTextureFormat = { /** String describing which channels this texture has */ @@ -58,7 +58,7 @@ export function isTextureFormatCompressed(textureFormat: TextureFormat): boolean * Decodes a vertex format, returning type, components, byte length and flags (integer, signed, normalized) */ export function decodeTextureFormat(format: TextureFormat): DecodedTextureFormat { - const matches = REGEX.exec(format as string); + const matches = RGB_FORMAT_REGEX.exec(format as string); if (matches) { const [, channels, length, type, srgb, suffix] = matches; if (format) { diff --git a/modules/core/src/index.ts b/modules/core/src/index.ts index 222ec7597b..eba580362c 100644 --- a/modules/core/src/index.ts +++ b/modules/core/src/index.ts @@ -3,6 +3,7 @@ // Copyright (c) vis.gl contributors // MAIN API ACCESS POINT +export type {CreateDeviceProps} from './adapter/luma'; export {luma} from './adapter/luma'; // ADAPTER (DEVICE AND GPU RESOURCE INTERFACES) diff --git a/modules/core/src/portable/uniform-block.ts b/modules/core/src/portable/uniform-block.ts index 94e0f91bfb..dbb0d8bd5c 100644 --- a/modules/core/src/portable/uniform-block.ts +++ b/modules/core/src/portable/uniform-block.ts @@ -37,7 +37,7 @@ export class UniformBlock< // TODO - Extract uniform layout from the shaderLayout object if (props?.name && props?.shaderLayout) { const binding = props?.shaderLayout.bindings?.find( - binding => binding.type === 'uniform' && binding.name === props?.name + binding_ => binding_.type === 'uniform' && binding_.name === props?.name ); if (!binding) { throw new Error(props?.name); diff --git a/modules/core/src/portable/uniform-store.ts b/modules/core/src/portable/uniform-store.ts index d9e4be0430..faf9648c5e 100644 --- a/modules/core/src/portable/uniform-store.ts +++ b/modules/core/src/portable/uniform-store.ts @@ -153,7 +153,7 @@ export class UniformStore< /** Update one uniform buffer. Only updates if values have changed */ updateUniformBuffer(uniformBufferName: keyof TPropGroups): false | string { const uniformBlock = this.uniformBlocks.get(uniformBufferName); - const uniformBuffer = this.uniformBuffers.get(uniformBufferName); + let uniformBuffer = this.uniformBuffers.get(uniformBufferName); let reason: false | string = false; if (uniformBuffer && uniformBlock?.needsRedraw) { @@ -161,7 +161,7 @@ export class UniformStore< // This clears the needs redraw flag const uniformBufferData = this.getUniformBufferData(uniformBufferName); - const uniformBuffer = this.uniformBuffers.get(uniformBufferName); + uniformBuffer = this.uniformBuffers.get(uniformBufferName); uniformBuffer?.write(uniformBufferData); // logging - TODO - don't query the values unnecessarily diff --git a/modules/core/test/adapter/resources/command-encoder.spec.ts b/modules/core/test/adapter/resources/command-encoder.spec.ts index 84ea43e807..213777061a 100644 --- a/modules/core/test/adapter/resources/command-encoder.spec.ts +++ b/modules/core/test/adapter/resources/command-encoder.spec.ts @@ -127,7 +127,7 @@ test('CommandBuffer#copyTextureToBuffer', async t => { async function testCopyTextureToBuffer( t: Test, - device: Device, + device_: Device, options: CopyTextureToBufferFixture & {useFramebuffer?: boolean} ) { const {title, srcPixel, dstPixel, dstOffset = 0} = options; @@ -139,7 +139,7 @@ async function testCopyTextureToBuffer( let sourceTexture; - const colorTexture = device.createTexture({ + const colorTexture = device_.createTexture({ data: srcPixel, width: 1, height: 1, @@ -147,15 +147,15 @@ async function testCopyTextureToBuffer( mipmaps: false }); - const destinationBuffer = device.createBuffer({byteLength}); + const destinationBuffer = device_.createBuffer({byteLength}); if (options.useFramebuffer) { - sourceTexture = device.createFramebuffer({colorAttachments: [colorTexture]}); + sourceTexture = device_.createFramebuffer({colorAttachments: [colorTexture]}); } else { sourceTexture = colorTexture; } - const commandEncoder = device.createCommandEncoder(); + const commandEncoder = device_.createCommandEncoder(); commandEncoder.copyTextureToBuffer({ sourceTexture, width: 1, @@ -197,24 +197,24 @@ test('CommandEncoder#copyTextureToTexture', t => { function testCopyToTexture( t: Test, - device: Device, + device_: Device, options: {isSubCopy: boolean; sourceIsFramebuffer: boolean} ): void { // const byteLength = 6 * 4; // 6 floats const sourceColor = [255, 128, 64, 32]; - const sourceTexture = device.createTexture({ + const sourceTexture = device_.createTexture({ data: options.sourceIsFramebuffer ? null : new Uint8Array(sourceColor) }); const destinationTexture = sourceTexture.clone(); - const commandEncoder = device.createCommandEncoder(); + const commandEncoder = device_.createCommandEncoder(); commandEncoder.copyTextureToTexture({sourceTexture, destinationTexture}); commandEncoder.finish(); // Read data form destination texture - const color = device.readPixelsToArrayWebGL(destinationTexture); + const color = device_.readPixelsToArrayWebGL(destinationTexture); t.deepEqual(color, sourceColor, 'copyTextureToTexture() successful'); diff --git a/modules/core/test/adapter/resources/texture.spec.ts b/modules/core/test/adapter/resources/texture.spec.ts index 1ceef50657..d5f527a7da 100644 --- a/modules/core/test/adapter/resources/texture.spec.ts +++ b/modules/core/test/adapter/resources/texture.spec.ts @@ -150,7 +150,7 @@ function testFormatCreation(t, device: Device, withData: boolean = false) { continue; } - if (device.isTextureFormatSupported(format)) { + if (device.isTextureFormatSupported(format) && !device.isTextureFormatCompressed(format)) { try { const data = withData && !packed ? TEXTURE_DATA[dataType] || DEFAULT_TEXTURE_DATA : null; // TODO: for some reason mipmap generation failing for RGB32F format diff --git a/modules/engine/src/compute/computation.ts b/modules/engine/src/compute/computation.ts index d861b7237c..892d81cafc 100644 --- a/modules/engine/src/compute/computation.ts +++ b/modules/engine/src/compute/computation.ts @@ -288,7 +288,7 @@ export class Computation { id: `${this.id}-fragment`, stage: 'compute', source: this.source, - debug: this.props.debugShaders + debugShaders: this.props.debugShaders }); this.pipeline = this.pipelineFactory.createComputePipeline({ diff --git a/modules/engine/src/model/model.ts b/modules/engine/src/model/model.ts index 81642994ea..39596dea25 100644 --- a/modules/engine/src/model/model.ts +++ b/modules/engine/src/model/model.ts @@ -753,7 +753,7 @@ export class Model { id: `${this.id}-vertex`, stage: 'vertex', source: this.source || this.vs, - debug: this.props.debugShaders + debugShaders: this.props.debugShaders }); let fs: Shader | null = null; @@ -764,7 +764,7 @@ export class Model { id: `${this.id}-fragment`, stage: 'fragment', source: this.source || this.fs, - debug: this.props.debugShaders + debugShaders: this.props.debugShaders }); } @@ -834,7 +834,7 @@ export class Model { protected _drawCount = 0; _logFramebuffer(renderPass: RenderPass): void { - const debugFramebuffers = log.get('framebuffer'); + const debugFramebuffers = this.device.props.debugFramebuffers; this._drawCount++; // Update first 3 frames and then every 60 frames if (!debugFramebuffers) { diff --git a/modules/engine/src/modules/picking/picking-manager.ts b/modules/engine/src/modules/picking/picking-manager.ts index 36a9d9bc70..c2d9db6df8 100644 --- a/modules/engine/src/modules/picking/picking-manager.ts +++ b/modules/engine/src/modules/picking/picking-manager.ts @@ -67,7 +67,7 @@ export class PickingManager { sourceWidth: 1, sourceHeight: 1 }); - console.log(color255); + // console.log(color255); // Check if we have let highlightedObjectColor: Float32Array | null = new Float32Array(color255).map(x => x / 255); diff --git a/modules/engine/src/passes/shader-pass-renderer.ts b/modules/engine/src/passes/shader-pass-renderer.ts index 114c97572e..fef0632fdb 100644 --- a/modules/engine/src/passes/shader-pass-renderer.ts +++ b/modules/engine/src/passes/shader-pass-renderer.ts @@ -125,6 +125,7 @@ void main() { } first = false; + // eslint-disable-next-line no-shadow const sourceTexture = this.swapFramebuffers.current.colorAttachments[0].texture; const bindings = { diff --git a/modules/engine/test/compute/buffer-transform.spec.ts b/modules/engine/test/compute/buffer-transform.spec.ts index d2d22b7b6b..6bf0cfd858 100644 --- a/modules/engine/test/compute/buffer-transform.spec.ts +++ b/modules/engine/test/compute/buffer-transform.spec.ts @@ -45,12 +45,12 @@ test('BufferTransform#run', async t => { }); function createBufferTransform( - webglDevice: Device, + webglDevice_: Device, src?: Buffer, dst?: Buffer, vertexCount?: number ): BufferTransform { - return new BufferTransform(webglDevice, { + return new BufferTransform(webglDevice_, { vs: VS, fs: FS, vertexCount, diff --git a/modules/engine/test/compute/texture-transform.spec.ts b/modules/engine/test/compute/texture-transform.spec.ts index aa5a0d6482..3149d4cd06 100644 --- a/modules/engine/test/compute/texture-transform.spec.ts +++ b/modules/engine/test/compute/texture-transform.spec.ts @@ -123,20 +123,20 @@ test('TextureTransform#texture', async t => { }); async function readF32( - webglDevice: Device, + webglDevice_: Device, source: Texture, byteLength: number ): Promise { - const bytes = await readU8(webglDevice, source, byteLength); + const bytes = await readU8(webglDevice_, source, byteLength); return new Float32Array(bytes.buffer, bytes.byteOffset, bytes.byteLength / 4); } async function readU8( - webglDevice: Device, + webglDevice_: Device, sourceTexture: Texture, byteLength: number ): Promise { - const destinationBuffer = webglDevice.createBuffer({byteLength}); + const destinationBuffer = webglDevice_.createBuffer({byteLength}); try { const cmd = webglDevice.createCommandEncoder(); cmd.copyTextureToBuffer({sourceTexture, destinationBuffer}); diff --git a/modules/gltf/src/gltf/gl-utils.ts b/modules/gltf/src/gltf/gl-utils.ts index 554fc4b10c..ff5d36d9a2 100644 --- a/modules/gltf/src/gltf/gl-utils.ts +++ b/modules/gltf/src/gltf/gl-utils.ts @@ -7,6 +7,7 @@ import {PrimitiveTopology} from '@luma.gl/core'; // NOTE: Modules other than `@luma.gl/webgl` should not import `GL` from // `@luma.gl/constants`. Locally we use `GLEnum` instead of `GL` to avoid // conflicts with the `babel-plugin-inline-webgl-constants` plugin. +// eslint-disable-next-line no-shadow export enum GLEnum { POINTS = 0x0, LINES = 0x1, diff --git a/modules/gltf/src/pbr/parse-pbr-material.ts b/modules/gltf/src/pbr/parse-pbr-material.ts index f330568aa0..3051b97668 100644 --- a/modules/gltf/src/pbr/parse-pbr-material.ts +++ b/modules/gltf/src/pbr/parse-pbr-material.ts @@ -28,6 +28,7 @@ export type ParsedPBRMaterial = { // NOTE: Modules other than `@luma.gl/webgl` should not import `GL` from // `@luma.gl/constants`. Locally we use `GLEnum` instead of `GL` to avoid // conflicts with the `babel-plugin-inline-webgl-constants` plugin. +// eslint-disable-next-line no-shadow enum GLEnum { FUNC_ADD = 0x8006, ONE = 1, diff --git a/modules/shadertools/src/modules/lighting/lights/lighting.ts b/modules/shadertools/src/modules/lighting/lights/lighting.ts index eec6693ef9..c15d71c392 100644 --- a/modules/shadertools/src/modules/lighting/lights/lighting.ts +++ b/modules/shadertools/src/modules/lighting/lights/lighting.ts @@ -14,6 +14,7 @@ const MAX_LIGHTS = 5; const COLOR_FACTOR = 255.0; /** Shader type field for lights */ +// eslint-disable-next-line no-shadow export enum LIGHT_TYPE { POINT = 0, DIRECTIONAL = 1 diff --git a/modules/shadertools/test/gpu-test-utils.ts b/modules/shadertools/test/gpu-test-utils.ts index 21ddb95adc..3ff4d1666f 100644 --- a/modules/shadertools/test/gpu-test-utils.ts +++ b/modules/shadertools/test/gpu-test-utils.ts @@ -1,7 +1,5 @@ /* eslint-disable max-len, prefer-template, camelcase */ /* eslint-disable no-console */ -import {setGLParameters} from '@luma.gl/webgl'; -import {createTestContext} from '@luma.gl/test-utils'; // Utilities functions that to be moved to a common place for future tests @@ -36,19 +34,6 @@ function glErrorShouldBe(gl, glErrors, opt_msg) { } } -export function initializeGL(canvas) { - const gl = createTestContext(canvas); - setGLParameters(gl, { - viewport: [0, 0, canvas.width, canvas.height] - }); - setGLParameters(gl, { - clearColor: [0, 0, 0, 1], - clearDepth: 1 - }); - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - return gl; -} - export function initializeTexTarget(gl) { const framebuffer = gl.createFramebuffer(); gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer); diff --git a/modules/test-utils/src/create-test-device.ts b/modules/test-utils/src/create-test-device.ts index 61765c2ac6..e7bca99e4a 100644 --- a/modules/test-utils/src/create-test-device.ts +++ b/modules/test-utils/src/create-test-device.ts @@ -2,32 +2,25 @@ // SPDX-License-Identifier: MIT // Copyright (c) vis.gl contributors -import type {Device, DeviceProps} from '@luma.gl/core'; +import type {Device, CanvasContextProps} from '@luma.gl/core'; import {luma, log} from '@luma.gl/core'; import {webgl2Adapter, WebGLDevice} from '@luma.gl/webgl'; import {webgpuAdapter, WebGPUDevice} from '@luma.gl/webgpu'; -const CONTEXT_DEFAULTS: Partial = { +const DEFAULT_CANVAS_CONTEXT_PROPS: CanvasContextProps = { width: 1, - height: 1, - debug: true + height: 1 }; -/** Create a test WebGL context */ -export function createTestContext(opts: Record = {}): WebGL2RenderingContext | null { - const device = createTestDevice(opts); - return device && device.gl; -} - /** Create a test WebGLDevice */ -export function createTestDevice(props: DeviceProps = {}): WebGLDevice | null { +export function createTestDevice(): WebGLDevice | null { try { - props = {...CONTEXT_DEFAULTS, ...props, debug: true}; - // TODO - We dont use luma.createDevice since createTestDevice currently expect WebGL context to be created synchronously - return new WebGLDevice(props); + // TODO - We do not use luma.createDevice since createTestDevice currently expect WebGL context to be created synchronously + return new WebGLDevice({canvasContext: DEFAULT_CANVAS_CONTEXT_PROPS, debugWebGL: true}); } catch (error) { // eslint-disable-next-line no-console - console.error(`Failed to created device '${props.id}': ${(error as Error).message}`); + console.error(`Failed to created device: ${(error as Error).message}`); + debugger; // eslint-disable-line no-debugger return null; } } @@ -51,7 +44,8 @@ export async function getTestDevices(type?: 'webgl' | 'webgpu'): Promise { // 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. - if (typeof props.canvas === 'string') { + if (typeof props.canvasContext?.canvas === 'string') { await CanvasContext.pageLoaded; } diff --git a/modules/test-utils/src/null-device/null-device.ts b/modules/test-utils/src/null-device/null-device.ts index 237b93307b..ebf9096eac 100644 --- a/modules/test-utils/src/null-device/null-device.ts +++ b/modules/test-utils/src/null-device/null-device.ts @@ -48,7 +48,7 @@ export class NullDevice extends Device { return true; } readonly type = 'unknown'; - features: DeviceFeatures = new DeviceFeatures([], this.props.disabledFeatures); + features: DeviceFeatures = new DeviceFeatures([], this.props._disabledFeatures); limits: NullDeviceLimits = new NullDeviceLimits(); readonly info = NullDeviceInfo; @@ -58,7 +58,7 @@ export class NullDevice extends Device { constructor(props: DeviceProps) { super({...props, id: props.id || 'null-device'}); - this.canvasContext = new NullCanvasContext(this, props); + this.canvasContext = new NullCanvasContext(this, props.canvasContext); this.lost = new Promise(resolve => {}); this.canvasContext.resize(); } diff --git a/modules/webgl/src/adapter/converters/device-parameters.ts b/modules/webgl/src/adapter/converters/device-parameters.ts index 6eae24233a..0993e4e1cd 100644 --- a/modules/webgl/src/adapter/converters/device-parameters.ts +++ b/modules/webgl/src/adapter/converters/device-parameters.ts @@ -31,7 +31,7 @@ export function withDeviceAndGLParameters( device: Device, parameters: Parameters, glParameters: GLParameters, - func: (device?: Device) => T + func: (_?: Device) => T ): T { if (isObjectEmpty(parameters)) { // Avoid setting state if no parameters provided. Just call and return @@ -62,7 +62,7 @@ export function withDeviceAndGLParameters( export function withGLParameters( device: Device, parameters: GLParameters, - func: (device?: Device) => T + func: (_?: Device) => T ): T { if (isObjectEmpty(parameters)) { // Avoid setting state if no parameters provided. Just call and return @@ -91,7 +91,7 @@ export function withGLParameters( export function withDeviceParameters( device: Device, parameters: Parameters, - func: (device?: Device) => T + func: (_?: Device) => T ): T { if (isObjectEmpty(parameters)) { // Avoid setting state if no parameters provided. Just call and return diff --git a/modules/webgl/src/adapter/resources/webgl-render-pipeline.ts b/modules/webgl/src/adapter/resources/webgl-render-pipeline.ts index cad0bfb209..68b0778ce9 100644 --- a/modules/webgl/src/adapter/resources/webgl-render-pipeline.ts +++ b/modules/webgl/src/adapter/resources/webgl-render-pipeline.ts @@ -110,12 +110,12 @@ export class WEBGLRenderPipeline extends RenderPipeline { // and reference them as `app` from both GLSL and JS. // TODO - this is rather hacky - we could also remap the name directly in the shader layout. const binding = - this.shaderLayout.bindings.find(binding => binding.name === name) || - this.shaderLayout.bindings.find(binding => binding.name === `${name}Uniforms`); + this.shaderLayout.bindings.find(binding_ => binding_.name === name) || + this.shaderLayout.bindings.find(binding_ => binding_.name === `${name}Uniforms`); if (!binding) { const validBindings = this.shaderLayout.bindings - .map(binding => `"${binding.name}"`) + .map(binding_ => `"${binding_.name}"`) .join(', '); if (!options?.disableWarnings) { log.warn( diff --git a/modules/webgl/src/adapter/resources/webgl-shader.ts b/modules/webgl/src/adapter/resources/webgl-shader.ts index 7bc3b92016..50765a2bec 100644 --- a/modules/webgl/src/adapter/resources/webgl-shader.ts +++ b/modules/webgl/src/adapter/resources/webgl-shader.ts @@ -49,8 +49,8 @@ export class WEBGLShader extends Shader { } override getCompilationInfoSync(): readonly CompilerMessage[] { - const log = this.device.gl.getShaderInfoLog(this.handle); - return log ? parseShaderCompilerLog(log) : []; + const shaderLog = this.device.gl.getShaderInfoLog(this.handle); + return shaderLog ? parseShaderCompilerLog(shaderLog) : []; } override getTranslatedSource(): string | null { @@ -63,9 +63,7 @@ export class WEBGLShader extends Shader { /** Compile a shader and get compilation status */ protected async _compile(source: string): Promise { - const addGLSLVersion = (source: string) => - source.startsWith('#version ') ? source : `#version 300 es\n${source}`; - source = addGLSLVersion(source); + source = source.startsWith('#version ') ? source : `#version 300 es\n${source}`; const {gl} = this.device; gl.shaderSource(this.handle, source); diff --git a/modules/webgl/src/adapter/webgl-adapter.ts b/modules/webgl/src/adapter/webgl-adapter.ts index e5fce0e232..e5824cc735 100644 --- a/modules/webgl/src/adapter/webgl-adapter.ts +++ b/modules/webgl/src/adapter/webgl-adapter.ts @@ -52,7 +52,7 @@ export class WebGLAdapter extends Adapter { if (!isWebGL(gl)) { throw new Error('Invalid WebGL2RenderingContext'); } - return new WebGLDevice({gl: gl as WebGL2RenderingContext}); + return new WebGLDevice({_handle: gl as WebGL2RenderingContext}); } async create(props: DeviceProps = {}): Promise { @@ -61,17 +61,17 @@ export class WebGLAdapter extends Adapter { const promises: Promise[] = []; // Load webgl and spector debug scripts from CDN if requested - if (props.debug) { + if (props.debugWebGL) { promises.push(loadWebGLDeveloperTools()); } - if (props.debugWithSpectorJS) { + if (props.debugSpectorJS) { promises.push(loadSpectorJS(props)); } // 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. - if (typeof props.canvas === 'string') { + if (typeof props.canvasContext?.canvas === 'string') { promises.push(CanvasContext.pageLoaded); } diff --git a/modules/webgl/src/adapter/webgl-device.ts b/modules/webgl/src/adapter/webgl-device.ts index ea0a9f0e66..9b184e105a 100644 --- a/modules/webgl/src/adapter/webgl-device.ts +++ b/modules/webgl/src/adapter/webgl-device.ts @@ -12,28 +12,7 @@ import type { Texture, Framebuffer, VertexArray, - VertexArrayProps -} from '@luma.gl/core'; -import {Device, CanvasContext, log} from '@luma.gl/core'; -import type {GLExtensions} from '@luma.gl/constants'; -import {WebGLStateTracker} from '../context/state-tracker/webgl-state-tracker'; -import {createBrowserContext} from '../context/helpers/create-browser-context'; -import {getDeviceInfo} from './device-helpers/webgl-device-info'; -import {WebGLDeviceFeatures} from './device-helpers/webgl-device-features'; -import {WebGLDeviceLimits} from './device-helpers/webgl-device-limits'; -import {WebGLCanvasContext} from './webgl-canvas-context'; -import type {Spector} from '../context/debug/spector-types'; -import {initializeSpectorJS} from '../context/debug/spector'; -import {makeDebugContext} from '../context/debug/webgl-developer-tools'; -import { - isTextureFormatSupported, - isTextureFormatRenderable, - isTextureFormatFilterable -} from './converters/texture-formats'; -import {uid} from '../utils/uid'; - -// WebGL classes -import type { + VertexArrayProps, BufferProps, ShaderProps, // Sampler, @@ -55,6 +34,23 @@ import type { TransformFeedbackProps, QuerySetProps } from '@luma.gl/core'; +import {Device, CanvasContext, log} from '@luma.gl/core'; +import type {GLExtensions} from '@luma.gl/constants'; +import {WebGLStateTracker} from '../context/state-tracker/webgl-state-tracker'; +import {createBrowserContext} from '../context/helpers/create-browser-context'; +import {getDeviceInfo} from './device-helpers/webgl-device-info'; +import {WebGLDeviceFeatures} from './device-helpers/webgl-device-features'; +import {WebGLDeviceLimits} from './device-helpers/webgl-device-limits'; +import {WebGLCanvasContext} from './webgl-canvas-context'; +import type {Spector} from '../context/debug/spector-types'; +import {initializeSpectorJS} from '../context/debug/spector'; +import {makeDebugContext} from '../context/debug/webgl-developer-tools'; +import { + isTextureFormatSupported, + isTextureFormatRenderable, + isTextureFormatFilterable +} from './converters/texture-formats'; +import {uid} from '../utils/uid'; import {WEBGLBuffer} from './resources/webgl-buffer'; import {WEBGLShader} from './resources/webgl-shader'; @@ -122,33 +118,54 @@ export class WebGLDevice extends Device { // If attaching to an already attached context, return the attached device // @ts-expect-error device is attached to context - const device: WebGLDevice | undefined = props.gl?.device; + let device: WebGLDevice | undefined = props.canvasContext?.canvas?.gl?.device; if (device) { throw new Error(`WebGL context already attached to device ${device.id}`); } // Create and instrument context - const canvas = props.gl?.canvas || props.canvas; - this.canvasContext = new WebGLCanvasContext(this, {...props, canvas}); + this.canvasContext = new WebGLCanvasContext(this, props.canvasContext); this.lost = new Promise<{reason: 'destroyed'; message: string}>(resolve => { this._resolveContextLost = resolve; }); - this.handle = createBrowserContext(this.canvasContext.canvas, { - ...props, - onContextLost: (event: Event) => - this._resolveContextLost?.({ - reason: 'destroyed', - message: 'Entered sleep mode, or too many apps or browser tabs are using the GPU.' - }) - }); - this.gl = this.handle; + const webglContextAttributes: WebGLContextAttributes = {...props.webgl}; + // Copy props from CanvasContextProps + if (props.canvasContext?.alphaMode === 'premultiplied') { + webglContextAttributes.premultipliedAlpha = true; + } + if (props.powerPreference !== undefined) { + webglContextAttributes.powerPreference = props.powerPreference; + } - if (!this.handle) { + const gl = createBrowserContext( + this.canvasContext.canvas, + { + onContextLost: (event: Event) => + this._resolveContextLost?.({ + reason: 'destroyed', + message: 'Entered sleep mode, or too many apps or browser tabs are using the GPU.' + }), + // eslint-disable-next-line no-console + onContextRestored: (event: Event) => console.log('WebGL context restored') + }, + webglContextAttributes + ); + + if (!gl) { throw new Error('WebGL context creation failed'); } + // @ts-expect-error device is attached to context + device = gl.device; + if (device) { + throw new Error(`WebGL context already attached to device ${device.id}`); + } + + this.handle = gl; + this.gl = gl; + // Add spector debug instrumentation to context // We need to trust spector integration to decide if spector should be initialized // We also run spector instrumentation first, otherwise spector can clobber luma instrumentation. @@ -161,8 +178,12 @@ export class WebGLDevice extends Device { // initialize luma Device fields this.info = getDeviceInfo(this.gl, this._extensions); this.limits = new WebGLDeviceLimits(this.gl); - this.features = new WebGLDeviceFeatures(this.gl, this._extensions, this.props.disabledFeatures); - if (this.props.initalizeFeatures) { + this.features = new WebGLDeviceFeatures( + this.gl, + this._extensions, + this.props._disabledFeatures + ); + if (this.props._initializeFeatures) { this.features.initializeFeatures(); } @@ -175,8 +196,8 @@ export class WebGLDevice extends Device { glState.trackState(this.gl, {copyState: false}); // DEBUG contexts: Add luma debug instrumentation to the context, force log level to at least 1 - if (props.debug) { - this.gl = makeDebugContext(this.gl, {...props, throwOnError: true}); + if (props.debugWebGL) { + this.gl = makeDebugContext(this.gl, {...props}); this.debug = true; log.level = Math.max(log.level, 1); log.warn('WebGL debug mode activated. Performance reduced.')(); diff --git a/modules/webgl/src/context/debug/spector-types.ts b/modules/webgl/src/context/debug/spector-types.ts index 864bdfbce4..b186b4edc7 100644 --- a/modules/webgl/src/context/debug/spector-types.ts +++ b/modules/webgl/src/context/debug/spector-types.ts @@ -1,5 +1,5 @@ // Forked from https://github.com/BabylonJS/Spector.js/blob/master/dist/spector.d.ts -/* eslint-disable camelcase */ +/* eslint-disable camelcase, no-shadow */ interface IEvent { add(callback: (element: T) => void, context?: any): number; diff --git a/modules/webgl/src/context/debug/spector.ts b/modules/webgl/src/context/debug/spector.ts index 19b65b8367..1d3d0f95cc 100644 --- a/modules/webgl/src/context/debug/spector.ts +++ b/modules/webgl/src/context/debug/spector.ts @@ -5,14 +5,14 @@ import {log} from '@luma.gl/core'; import {loadScript} from '../../utils/load-script'; -import {Spector} from './spector-types'; +import type {Spector} from './spector-types'; /** Spector debug initialization options */ type SpectorProps = { - /** Whether spector is enabled */ - debugWithSpectorJS?: boolean; + /** Whether spector.js is enabled */ + debugSpectorJS?: boolean; /** URL to load spector script from. Typically a CDN URL */ - spectorUrl?: string; + debugSpectorJSUrl?: string; /** Canvas to monitor */ gl?: WebGL2RenderingContext; }; @@ -29,19 +29,19 @@ declare global { } export const DEFAULT_SPECTOR_PROPS: Required = { - debugWithSpectorJS: log.get('spector') || log.get('spectorjs'), + debugSpectorJS: log.get('debug-spectorjs'), // https://github.com/BabylonJS/Spector.js#basic-usage // https://forum.babylonjs.com/t/spectorcdn-is-temporarily-off/48241 // spectorUrl: 'https://spectorcdn.babylonjs.com/spector.bundle.js'; - spectorUrl: 'https://cdn.jsdelivr.net/npm/spectorjs@0.9.30/dist/spector.bundle.js', + debugSpectorJSUrl: 'https://cdn.jsdelivr.net/npm/spectorjs@0.9.30/dist/spector.bundle.js', gl: undefined! }; /** Loads spector from CDN if not already installed */ -export async function loadSpectorJS(props: {spectorUrl?: string}): Promise { +export async function loadSpectorJS(props: {debugSpectorJSUrl?: string}): Promise { if (!globalThis.SPECTOR) { try { - await loadScript(props.spectorUrl || DEFAULT_SPECTOR_PROPS.spectorUrl); + await loadScript(props.debugSpectorJSUrl || DEFAULT_SPECTOR_PROPS.debugSpectorJSUrl); } catch (error) { log.warn(String(error)); } @@ -50,14 +50,14 @@ export async function loadSpectorJS(props: {spectorUrl?: string}): Promise export function initializeSpectorJS(props: SpectorProps): Spector | null { props = {...DEFAULT_SPECTOR_PROPS, ...props}; - if (!props.debugWithSpectorJS) { + if (!props.debugSpectorJS) { return null; } if (!spector && globalThis.SPECTOR && !globalThis.luma?.spector) { log.probe(LOG_LEVEL, 'SPECTOR found and initialized. Start with `luma.spector.displayUI()`')(); - const {Spector} = globalThis.SPECTOR as any; - spector = new Spector(); + const {Spector: SpectorJS} = globalThis.SPECTOR as any; + spector = new SpectorJS(); if (globalThis.luma) { (globalThis.luma as any).spector = spector; } diff --git a/modules/webgl/src/context/debug/webgl-developer-tools.ts b/modules/webgl/src/context/debug/webgl-developer-tools.ts index 120f653987..a88f23737c 100644 --- a/modules/webgl/src/context/debug/webgl-developer-tools.ts +++ b/modules/webgl/src/context/debug/webgl-developer-tools.ts @@ -11,18 +11,9 @@ import {loadScript} from '../../utils/load-script'; const WEBGL_DEBUG_CDN_URL = 'https://unpkg.com/webgl-debug@2.0.1/index.js'; type DebugContextProps = { - debug?: boolean; - throwOnError?: boolean; - break?: string[]; + debugWebGL?: boolean; }; -// const DEFAULT_DEBUG_CONTEXT_PROPS: Required = { -// debug: true, -// throwOnError: false, -// break: [], -// webgl2: false, -// } - type ContextData = { realContext?: WebGL2RenderingContext; debugContext?: WebGL2RenderingContext; @@ -60,7 +51,7 @@ export function makeDebugContext( gl: WebGL2RenderingContext, props: DebugContextProps = {} ): WebGL2RenderingContext { - return props.debug ? getDebugContext(gl, props) : getRealContext(gl); + return props.debugWebGL ? getDebugContext(gl, props) : getRealContext(gl); } // Returns the real context from either of the real/debug contexts @@ -136,9 +127,7 @@ function onGLError(props: DebugContextProps, err, functionName: string, args: an const message = `${errorMessage} in gl.${functionName}(${functionArgs})`; log.error(message)(); debugger; // eslint-disable-line - if (props.throwOnError) { - throw new Error(message); - } + // throw new Error(message); } // Don't generate function string until it is needed @@ -153,26 +142,11 @@ function onValidateGLFunc( log.log(1, functionString)(); } - // If array of breakpoint strings supplied, check if any of them is contained in current GLEnum function - if (props.break && props.break.length > 0) { - functionString = functionString || getFunctionString(functionName, functionArgs); - const isBreakpoint = props.break.every( - (breakOn: string) => functionString.indexOf(breakOn) !== -1 - ); - if (isBreakpoint) { - debugger; // eslint-disable-line - } - } - for (const arg of functionArgs) { if (arg === undefined) { functionString = functionString || getFunctionString(functionName, functionArgs); - if (props.throwOnError) { - throw new Error(`Undefined argument: ${functionString}`); - } else { - log.error(`Undefined argument: ${functionString}`)(); - debugger; // eslint-disable-line - } + debugger; // eslint-disable-line + // throw new Error(`Undefined argument: ${functionString}`); } } } diff --git a/modules/webgl/src/context/helpers/create-browser-context.ts b/modules/webgl/src/context/helpers/create-browser-context.ts index 2670ecb3a8..dedbfac29e 100644 --- a/modules/webgl/src/context/helpers/create-browser-context.ts +++ b/modules/webgl/src/context/helpers/create-browser-context.ts @@ -5,37 +5,13 @@ /** * ContextProps * @param onContextLost - * @param onContextRestored - * - * BROWSER CONTEXT PARAMETERS - * @param debug Instrument context (at the expense of performance). - * @param alpha Default render target has an alpha buffer. - * @param depth Default render target has a depth buffer of at least 16 bits. - * @param stencil Default render target has a stencil buffer of at least 8 bits. - * @param antialias Boolean that indicates whether or not to perform anti-aliasing. - * @param premultipliedAlpha Boolean that indicates that the page compositor will assume the drawing buffer contains colors with pre-multiplied alpha. - * @param preserveDrawingBuffer Default render target buffers will not be automatically cleared and will preserve their values until cleared or overwritten - * @param failIfMajorPerformanceCaveat Do not create if the system performance is low. + * @param onContextRestored * */ type ContextProps = { - onContextLost?: (event: Event) => void; - onContextRestored?: (event: Event) => void; - alpha?: boolean; // indicates if the canvas contains an alpha buffer. - desynchronized?: boolean; // hints the user agent to reduce the latency by desynchronizing the canvas paint cycle from the event loop - antialias?: boolean; // indicates whether or not to perform anti-aliasing. - depth?: boolean; // indicates that the drawing buffer has a depth buffer of at least 16 bits. - failIfMajorPerformanceCaveat?: boolean; // indicates if a context will be created if the system performance is low or if no hardware GPU is available. - powerPreference?: 'default' | 'high-performance' | 'low-power'; - premultipliedAlpha?: boolean; // page compositor will assume the drawing buffer contains colors with pre-multiplied alpha. - preserveDrawingBuffer?: boolean; // buffers will not be cleared and will preserve their values until cleared or overwritten by the author. -}; - -const DEFAULT_CONTEXT_PROPS: ContextProps = { - powerPreference: 'high-performance', // After all, most apps are using WebGL for performance reasons - // eslint-disable-next-line no-console - onContextLost: () => console.error('WebGL context lost'), - // eslint-disable-next-line no-console - onContextRestored: () => console.info('WebGL context restored') + /** Called when a context is lost */ + onContextLost: (event: Event) => void; + /** Called when a context is restored */ + onContextRestored: (event: Event) => void; }; /** @@ -45,53 +21,51 @@ const DEFAULT_CONTEXT_PROPS: ContextProps = { */ export function createBrowserContext( canvas: HTMLCanvasElement | OffscreenCanvas, - props: ContextProps + props: ContextProps, + webglContextAttributes: WebGLContextAttributes ): WebGL2RenderingContext { - props = {...DEFAULT_CONTEXT_PROPS, ...props}; - // Try to extract any extra information about why context creation failed - let errorMessage = null; - const onCreateError = error => (errorMessage = error.statusMessage || errorMessage); - canvas.addEventListener('webglcontextcreationerror', onCreateError, false); - - // Create the desired context - let gl: WebGL2RenderingContext | null = null; + const errorMessage = null; + // const onCreateError = error => (errorMessage = error.statusMessage || errorMessage); - // props.failIfMajorPerformanceCaveat = true; + // Avoid multiple listeners? + // canvas.removeEventListener('webglcontextcreationerror', onCreateError, false); + // canvas.addEventListener('webglcontextcreationerror', onCreateError, false); - // We require webgl2 context - props.preserveDrawingBuffer = true; - gl ||= canvas.getContext('webgl2', props) as WebGL2RenderingContext; + const webglProps: WebGLContextAttributes = { + preserveDrawingBuffer: true, + // failIfMajorPerformanceCaveat: true, + ...webglContextAttributes + }; - // Software GPU - - // props.failIfMajorPerformanceCaveat = false; - - // if (!gl && props.webgl1) { - // gl = canvas.getContext('webgl', props); - // } + // Create the desired context + let gl: WebGL2RenderingContext | null = null; - // TODO are we removing this listener before giving it a chance to fire? - canvas.removeEventListener('webglcontextcreationerror', onCreateError, false); + // Create a webgl2 context + gl ||= canvas.getContext('webgl2', webglProps); + + // Creation failed with failIfMajorPerformanceCaveat - Try a Software GPU + if (!gl && !webglContextAttributes.failIfMajorPerformanceCaveat) { + webglProps.failIfMajorPerformanceCaveat = false; + gl = canvas.getContext('webgl', webglProps) as WebGL2RenderingContext; + // @ts-expect-error + gl.luma ||= {}; + // @ts-expect-error + gl.luma.softwareRenderer = true; + } if (!gl) { throw new Error(`Failed to create WebGL context: ${errorMessage || 'Unknown error'}`); } - if (props.onContextLost) { - // Carefully extract and wrap callbacks to prevent addEventListener from rebinding them. - const {onContextLost} = props; - canvas.addEventListener('webglcontextlost', (event: Event) => onContextLost(event), false); - } - if (props.onContextRestored) { - // Carefully extract and wrap callbacks to prevent addEventListener from rebinding them. - const {onContextRestored} = props; - canvas.addEventListener( - 'webglcontextrestored', - (event: Event) => onContextRestored(event), - false - ); - } + // Carefully extract and wrap callbacks to prevent addEventListener from rebinding them. + const {onContextLost, onContextRestored} = props; + canvas.addEventListener('webglcontextlost', (event: Event) => onContextLost(event), false); + canvas.addEventListener( + 'webglcontextrestored', + (event: Event) => onContextRestored(event), + false + ); return gl; } diff --git a/modules/webgl/test/adapter/webgl-canvas-context.spec.ts b/modules/webgl/test/adapter/webgl-canvas-context.spec.ts index ab6f0ddc68..8ef9fb013c 100644 --- a/modules/webgl/test/adapter/webgl-canvas-context.spec.ts +++ b/modules/webgl/test/adapter/webgl-canvas-context.spec.ts @@ -353,8 +353,8 @@ test('WebGLCanvasContext#cssToDeviceRatio', t => { }); /** Modify the canvas context to mock test conditions */ -function configureCanvasContext(canvasContext: CanvasContext, tc) { +function configureCanvasContext(canvasContext_: CanvasContext, tc) { // @ts-expect-error read only - canvasContext._canvasSizeInfo = tc._canvasSizeInfo; - canvasContext.getDrawingBufferSize = () => [tc.drawingBufferWidth, tc.drawingBufferHeight]; + canvasContext_._canvasSizeInfo = tc._canvasSizeInfo; + canvasContext_.getDrawingBufferSize = () => [tc.drawingBufferWidth, tc.drawingBufferHeight]; } diff --git a/modules/webgl/test/context/state-tracker/webgl-state-tracker.spec.ts b/modules/webgl/test/context/state-tracker/webgl-state-tracker.spec.ts index f5708f5e3b..83b73524e0 100644 --- a/modules/webgl/test/context/state-tracker/webgl-state-tracker.spec.ts +++ b/modules/webgl/test/context/state-tracker/webgl-state-tracker.spec.ts @@ -25,7 +25,7 @@ import {ENUM_STYLE_SETTINGS_SET1, ENUM_STYLE_SETTINGS_SET2} from './data/sample- // Settings test, don't reuse a context // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion -const device = createTestDevice({debug: true}) as WebGLDevice; +const device = createTestDevice() as WebGLDevice; test('WebGLStateTracker#imports', t => { t.ok(typeof WebGLStateTracker === 'function', 'WebGLStateTracker imported OK'); diff --git a/modules/webgpu/src/adapter/helpers/get-vertex-buffer-layout.ts b/modules/webgpu/src/adapter/helpers/get-vertex-buffer-layout.ts index cab8d31b00..b12a2a6e45 100644 --- a/modules/webgpu/src/adapter/helpers/get-vertex-buffer-layout.ts +++ b/modules/webgpu/src/adapter/helpers/get-vertex-buffer-layout.ts @@ -150,7 +150,7 @@ function findAttributeLayout( name: string, attributeNames: Set ): AttributeDeclaration | null { - const attribute = shaderLayout.attributes.find(attribute => attribute.name === name); + const attribute = shaderLayout.attributes.find(attribute_ => attribute_.name === name); if (!attribute) { log.warn(`Unknown attribute ${name}`)(); return null; diff --git a/modules/webgpu/src/adapter/resources/webgpu-render-pass.ts b/modules/webgpu/src/adapter/resources/webgpu-render-pass.ts index d2ae694824..af166bcbb4 100644 --- a/modules/webgpu/src/adapter/resources/webgpu-render-pass.ts +++ b/modules/webgpu/src/adapter/resources/webgpu-render-pass.ts @@ -32,10 +32,10 @@ export class WebGPURenderPass extends RenderPass { } if (device.features.has('timestamp-query')) { - const webgpuQuerySet = props.timestampQuerySet as WebGPUQuerySet; - renderPassDescriptor.timestampWrites = webgpuQuerySet + const webgpuTSQuerySet = props.timestampQuerySet as WebGPUQuerySet; + renderPassDescriptor.timestampWrites = webgpuTSQuerySet ? ({ - querySet: webgpuQuerySet.handle, + querySet: webgpuTSQuerySet.handle, beginningOfPassWriteIndex: props.beginTimestampIndex, endOfPassWriteIndex: props.endTimestampIndex } as GPUComputePassTimestampWrites) diff --git a/modules/webgpu/src/adapter/webgpu-adapter.ts b/modules/webgpu/src/adapter/webgpu-adapter.ts index 69d2865a40..73c644d7b5 100644 --- a/modules/webgpu/src/adapter/webgpu-adapter.ts +++ b/modules/webgpu/src/adapter/webgpu-adapter.ts @@ -44,7 +44,7 @@ export class WebGPUAdapter extends Adapter { const requiredFeatures: GPUFeatureName[] = []; const requiredLimits: Record = {}; - if (props.requestMaxLimits) { + if (props._requestMaxLimits) { // Require all features requiredFeatures.push(...(Array.from(adapter.features) as GPUFeatureName[])); @@ -69,7 +69,7 @@ export class WebGPUAdapter extends Adapter { log.probe(1, 'GPUDevice available')(); - if (typeof props.canvas === 'string') { + if (typeof props.canvasContext?.canvas === 'string') { await CanvasContext.pageLoaded; log.probe(1, 'DOM is loaded')(); } diff --git a/modules/webgpu/src/adapter/webgpu-device.ts b/modules/webgpu/src/adapter/webgpu-device.ts index 40ecb6f3bb..4932eed122 100644 --- a/modules/webgpu/src/adapter/webgpu-device.ts +++ b/modules/webgpu/src/adapter/webgpu-device.ts @@ -6,7 +6,6 @@ // / import type { - DeviceProps, DeviceInfo, DeviceLimits, DeviceFeature, @@ -28,7 +27,8 @@ import type { TransformFeedback, TransformFeedbackProps, QuerySet, - QuerySetProps + QuerySetProps, + DeviceProps } from '@luma.gl/core'; import {Device, DeviceFeatures} from '@luma.gl/core'; import {WebGPUBuffer} from './resources/webgpu-buffer'; @@ -101,14 +101,9 @@ export class WebGPUDevice extends Device { }); // Note: WebGPU devices can be created without a canvas, for compute shader purposes - // if (props.canvas) { - this.canvasContext = new WebGPUCanvasContext(this, this.adapter, { - canvas: props.canvas, - height: props.height, - width: props.width, - container: props.container - }); - // } + if (props.canvasContext) { + this.canvasContext = new WebGPUCanvasContext(this, this.adapter, props.canvasContext); + } } // TODO @@ -280,7 +275,7 @@ export class WebGPUDevice extends Device { features.add(feature); } - return new DeviceFeatures(Array.from(features), this.props.disabledFeatures); + return new DeviceFeatures(Array.from(features), this.props._disabledFeatures); } copyExternalImageToTexture(options: { diff --git a/test/apps/headless/test.js b/test/apps/headless/test.js index 9f71d1623f..7725c5ada5 100644 --- a/test/apps/headless/test.js +++ b/test/apps/headless/test.js @@ -8,7 +8,6 @@ export const gl = createHeadlessContext({ width: 1, height: 1, debug: true, - throwOnError: false }); const ext = gl.getExtension('EXT_disjoint_timer_query'); diff --git a/website/content/examples/tutorials/hello-triangle.mdx b/website/content/examples/tutorials/hello-triangle.mdx index aca3aafaa8..ac7dfd932e 100644 --- a/website/content/examples/tutorials/hello-triangle.mdx +++ b/website/content/examples/tutorials/hello-triangle.mdx @@ -1,7 +1,7 @@ import {DeviceTabs} from '@site/src/react-luma'; -import {HelloTriangleWebGPUExample} from '@site'; +import {HelloTriangleGeometryExample} from '@site'; # Triangle - + diff --git a/website/content/examples/tutorials/instanced-cubes.mdx b/website/content/examples/tutorials/instanced-cubes.mdx index 858673779d..eaf7d15fe6 100644 --- a/website/content/examples/tutorials/instanced-cubes.mdx +++ b/website/content/examples/tutorials/instanced-cubes.mdx @@ -1,7 +1,7 @@ import {DeviceTabs} from '@site/src/react-luma'; -import {InstancedCubesWebGPUExample} from '@site'; +import {InstancedCubesExample} from '@site'; # Instanced Cubes - + diff --git a/website/content/examples/tutorials/textured-cube.mdx b/website/content/examples/tutorials/textured-cube.mdx deleted file mode 100644 index ce7d0ad099..0000000000 --- a/website/content/examples/tutorials/textured-cube.mdx +++ /dev/null @@ -1,7 +0,0 @@ -import {DeviceTabs} from '@site/src/react-luma'; -import {TexturedCubeWebGPUExample} from '@site'; - -# Textured Cube - - - diff --git a/website/content/examples/tutorials/two-cubes.mdx b/website/content/examples/tutorials/two-cubes.mdx index 52fd2e2897..745e8c92ca 100644 --- a/website/content/examples/tutorials/two-cubes.mdx +++ b/website/content/examples/tutorials/two-cubes.mdx @@ -1,7 +1,7 @@ import {DeviceTabs} from '@site/src/react-luma'; -import {TwoCubesWebGPUExample} from '@site'; +import {TwoCubesExample} from '@site'; # Two Cubes - + diff --git a/website/index.ts b/website/index.ts index e9cf5d2ef7..5d4f87afb3 100644 --- a/website/index.ts +++ b/website/index.ts @@ -19,8 +19,7 @@ export { CubemapExample, Texture3DExample, - HelloTriangleWebGPUExample, - InstancedCubesWebGPUExample, - TexturedCubeWebGPUExample, - TwoCubesWebGPUExample + HelloTriangleGeometryExample, + InstancedCubesExample, + TwoCubesExample } from './src/examples/templates'; diff --git a/website/src/examples/templates.tsx b/website/src/examples/templates.tsx index 1bd0075951..26a8e71831 100644 --- a/website/src/examples/templates.tsx +++ b/website/src/examples/templates.tsx @@ -16,9 +16,12 @@ import InstancingApp from '../../../examples/showcase/instancing/app'; import PersistenceApp from '../../../examples/showcase/persistence/app'; // import WanderingApp from '../../../examples/showcase/wandering/app'; +import HelloTriangleGeometryApp from '../../../examples/tutorials/hello-triangle-geometry/app'; +import HelloTriangleApp from '../../../examples/tutorials/hello-triangle/app'; import HelloCubeApp from '../../../examples/tutorials/hello-cube/app'; +import TwoCubesApp from '../../../examples/tutorials/hello-two-cubes/app'; +import InstancedCubesApp from '../../../examples/tutorials/hello-instanced-cubes/app'; import HelloInstancingApp from '../../../examples/tutorials/hello-instancing/app'; -import HelloTriangleApp from '../../../examples/tutorials/hello-triangle-geometry/app'; import HelloGLTFApp from '../../../examples/tutorials/hello-gltf/app'; import LightingApp from '../../../examples/tutorials/lighting/app'; import ShaderHooksApp from '../../../examples/tutorials/shader-hooks/app'; @@ -26,13 +29,6 @@ import ShaderModulesApp from '../../../examples/tutorials/shader-modules/app'; import TransformFeedbackApp from '../../../examples/tutorials/transform-feedback/app'; import TransformApp from '../../../examples/tutorials/transform/app'; -// import AnimationLoop from '../../../examples/webgl/external-webgl-context/app'; - -import HelloTriangleWebGPUApp from '../../../examples/tutorials/hello-triangle/app'; -import InstancedCubesWebGPUApp from '../../../examples/tutorials/hello-instanced-cubes/app'; -import TexturedCubeWebGPUApp from '../../../examples/webgpu/textured-cube/app'; -import TwoCubesWebGPUApp from '../../../examples/tutorials/hello-two-cubes/app'; - const exampleConfig = {}; // API Examples @@ -237,38 +233,39 @@ export const TransformExample: React.FC = () => ( // WebGPU Examples -export const HelloTriangleWebGPUExample: React.FC = () => ( +export const HelloTriangleGeometryExample: React.FC = () => ( ); -export const InstancedCubesWebGPUExample: React.FC = () => ( +export const InstancedCubesExample: React.FC = () => ( ); -export const TexturedCubeWebGPUExample: React.FC = () => ( +export const TwoCubesExample: React.FC = () => ( ); -export const TwoCubesWebGPUExample: React.FC = () => ( - -); +// export const TexturedCubeExample: React.FC = () => ( +// +// ); + diff --git a/website/src/react-luma/components/luma-example.tsx b/website/src/react-luma/components/luma-example.tsx index fe23318da6..4d5f0fc5bf 100644 --- a/website/src/react-luma/components/luma-example.tsx +++ b/website/src/react-luma/components/luma-example.tsx @@ -88,8 +88,10 @@ export const LumaExample: FC = (props: LumaExampleProps) => { device = await luma.createDevice({ adapters: [webgl2Adapter, webgpuAdapter], type: deviceType, - canvas, - container: containerName + canvasContext: { + canvas, + container: containerName + } }); animationLoop = makeAnimationLoop(props.template as unknown as typeof AnimationLoopTemplate, { diff --git a/website/src/react-luma/store/device-store.tsx b/website/src/react-luma/store/device-store.tsx index fcd6b6d12f..94606ca38e 100644 --- a/website/src/react-luma/store/device-store.tsx +++ b/website/src/react-luma/store/device-store.tsx @@ -30,7 +30,9 @@ export async function createDevice(type: 'webgl' | 'webgpu'): Promise { adapters: [webgl2Adapter, webgpuAdapter], type, height: 0, - container: getCanvasContainer() + canvasContext: { + container: getCanvasContainer() + } }); return await cachedDevice[type]; } diff --git a/examples/webgpu/compute/app.ts b/wip/examples-wip/webgpu/compute/app.ts similarity index 100% rename from examples/webgpu/compute/app.ts rename to wip/examples-wip/webgpu/compute/app.ts diff --git a/examples/webgpu/computeboids/app.ts b/wip/examples-wip/webgpu/computeboids/app.ts similarity index 100% rename from examples/webgpu/computeboids/app.ts rename to wip/examples-wip/webgpu/computeboids/app.ts diff --git a/examples/webgpu/computeboids/index.html b/wip/examples-wip/webgpu/computeboids/index.html similarity index 100% rename from examples/webgpu/computeboids/index.html rename to wip/examples-wip/webgpu/computeboids/index.html diff --git a/examples/webgpu/computeboids/package.json b/wip/examples-wip/webgpu/computeboids/package.json similarity index 100% rename from examples/webgpu/computeboids/package.json rename to wip/examples-wip/webgpu/computeboids/package.json diff --git a/examples/webgpu/computeboids/sprites.wgsl b/wip/examples-wip/webgpu/computeboids/sprites.wgsl similarity index 100% rename from examples/webgpu/computeboids/sprites.wgsl rename to wip/examples-wip/webgpu/computeboids/sprites.wgsl diff --git a/examples/webgpu/computeboids/update-sprites.wgsl b/wip/examples-wip/webgpu/computeboids/update-sprites.wgsl similarity index 100% rename from examples/webgpu/computeboids/update-sprites.wgsl rename to wip/examples-wip/webgpu/computeboids/update-sprites.wgsl diff --git a/examples/webgpu/computeboids/vite.config.ts b/wip/examples-wip/webgpu/computeboids/vite.config.ts similarity index 100% rename from examples/webgpu/computeboids/vite.config.ts rename to wip/examples-wip/webgpu/computeboids/vite.config.ts diff --git a/examples/webgpu/textured-cube/app.ts b/wip/examples-wip/webgpu/textured-cube/app.ts similarity index 100% rename from examples/webgpu/textured-cube/app.ts rename to wip/examples-wip/webgpu/textured-cube/app.ts diff --git a/examples/webgpu/textured-cube/index.html b/wip/examples-wip/webgpu/textured-cube/index.html similarity index 100% rename from examples/webgpu/textured-cube/index.html rename to wip/examples-wip/webgpu/textured-cube/index.html diff --git a/examples/webgpu/textured-cube/package.json b/wip/examples-wip/webgpu/textured-cube/package.json similarity index 100% rename from examples/webgpu/textured-cube/package.json rename to wip/examples-wip/webgpu/textured-cube/package.json diff --git a/examples/webgpu/textured-cube/vis-logo.png b/wip/examples-wip/webgpu/textured-cube/vis-logo.png similarity index 100% rename from examples/webgpu/textured-cube/vis-logo.png rename to wip/examples-wip/webgpu/textured-cube/vis-logo.png diff --git a/examples/webgpu/textured-cube/vite.config.ts b/wip/examples-wip/webgpu/textured-cube/vite.config.ts similarity index 100% rename from examples/webgpu/textured-cube/vite.config.ts rename to wip/examples-wip/webgpu/textured-cube/vite.config.ts