@luma.gl/webgl
Advanced tools
Comparing version 9.1.0-alpha.15 to 9.1.0-alpha.16
@@ -12,3 +12,3 @@ import type { CompareFunction } from '@luma.gl/core'; | ||
*/ | ||
export declare function withDeviceAndGLParameters<T = unknown>(device: Device, parameters: Parameters, glParameters: GLParameters, func: (device?: Device) => T): T; | ||
export declare function withDeviceAndGLParameters<T = unknown>(device: Device, parameters: Parameters, glParameters: GLParameters, func: (_?: Device) => T): T; | ||
/** | ||
@@ -23,3 +23,3 @@ * Execute a function with a set of temporary WebGL parameter overrides | ||
*/ | ||
export declare function withGLParameters<T = unknown>(device: Device, parameters: GLParameters, func: (device?: Device) => T): T; | ||
export declare function withGLParameters<T = unknown>(device: Device, parameters: GLParameters, func: (_?: Device) => T): T; | ||
/** | ||
@@ -33,3 +33,3 @@ * Execute a function with a set of temporary WebGL parameter overrides | ||
*/ | ||
export declare function withDeviceParameters<T = unknown>(device: Device, parameters: Parameters, func: (device?: Device) => T): T; | ||
export declare function withDeviceParameters<T = unknown>(device: Device, parameters: Parameters, func: (_?: Device) => T): T; | ||
/** Set WebGPU Style Parameters */ | ||
@@ -36,0 +36,0 @@ export declare function setDeviceParameters(device: Device, parameters: Parameters): void; |
@@ -308,2 +308,23 @@ import type { ExternalImage } from '@luma.gl/core'; | ||
*/ | ||
export type ReadPixelsToArrayOptions = { | ||
sourceX?: number; | ||
sourceY?: number; | ||
sourceFormat?: number; | ||
sourceAttachment?: number; | ||
target?: Uint8Array | Uint16Array | Float32Array; | ||
sourceWidth?: number; | ||
sourceHeight?: number; | ||
sourceDepth?: number; | ||
sourceType?: number; | ||
}; | ||
export type ReadPixelsToBufferOptions = { | ||
sourceX?: number; | ||
sourceY?: number; | ||
sourceFormat?: number; | ||
target?: Buffer; | ||
targetByteOffset?: number; | ||
sourceWidth?: number; | ||
sourceHeight?: number; | ||
sourceType?: number; | ||
}; | ||
/** | ||
@@ -320,13 +341,3 @@ * Copies data from a type or a Texture object into ArrayBuffer object. | ||
*/ | ||
export declare function readPixelsToArray(source: Framebuffer | Texture, options?: { | ||
sourceX?: number; | ||
sourceY?: number; | ||
sourceFormat?: number; | ||
sourceAttachment?: number; | ||
target?: Uint8Array | Uint16Array | Float32Array; | ||
sourceWidth?: number; | ||
sourceHeight?: number; | ||
sourceDepth?: number; | ||
sourceType?: number; | ||
}): Uint8Array | Uint16Array | Float32Array; | ||
export declare function readPixelsToArray(source: Framebuffer | Texture, options?: ReadPixelsToArrayOptions): Uint8Array | Uint16Array | Float32Array; | ||
/** | ||
@@ -339,12 +350,3 @@ * Copies data from a Framebuffer or a Texture object into a Buffer object. | ||
*/ | ||
export declare function readPixelsToBuffer(source: Framebuffer | Texture, options?: { | ||
sourceX?: number; | ||
sourceY?: number; | ||
sourceFormat?: number; | ||
target?: Buffer; | ||
targetByteOffset?: number; | ||
sourceWidth?: number; | ||
sourceHeight?: number; | ||
sourceType?: number; | ||
}): WEBGLBuffer; | ||
export declare function readPixelsToBuffer(source: Framebuffer | Texture, options?: ReadPixelsToBufferOptions): WEBGLBuffer; | ||
/** | ||
@@ -354,3 +356,3 @@ * Copy a rectangle from a Framebuffer or Texture object into a texture (at an offset) | ||
*/ | ||
export declare function copyToTexture(source: Framebuffer | Texture, target: Texture | GL, options?: { | ||
export declare function copyToTexture(sourceTexture: Framebuffer | Texture, destinationTexture: Texture | GL, options?: { | ||
sourceX?: number; | ||
@@ -357,0 +359,0 @@ sourceY?: number; |
@@ -159,221 +159,3 @@ // luma.gl | ||
} | ||
// texImage methods | ||
/** | ||
* Clear a texture mip level. | ||
* Wrapper for the messy WebGL texture API | ||
* | ||
export function clearMipLevel(gl: WebGL2RenderingContext, options: WebGLSetTextureOptions): void { | ||
const {dimension, width, height, depth = 0, level = 0} = options; | ||
const {glInternalFormat, glFormat, glType, compressed} = options; | ||
const glTarget = getWebGLCubeFaceTarget(options.glTarget, dimension, depth); | ||
switch (dimension) { | ||
case '2d-array': | ||
case '3d': | ||
if (compressed) { | ||
// prettier-ignore | ||
gl.compressedTexImage3D(glTarget, level, glInternalFormat, width, height, depth, BORDER, null); | ||
} else { | ||
// prettier-ignore | ||
gl.texImage3D( glTarget, level, glInternalFormat, width, height, depth, BORDER, glFormat, glType, null); | ||
} | ||
break; | ||
case '2d': | ||
case 'cube': | ||
if (compressed) { | ||
// prettier-ignore | ||
gl.compressedTexImage2D(glTarget, level, glInternalFormat, width, height, BORDER, null); | ||
} else { | ||
// prettier-ignore | ||
gl.texImage2D(glTarget, level, glInternalFormat, width, height, BORDER, glFormat, glType, null); | ||
} | ||
break; | ||
default: | ||
throw new Error(dimension); | ||
} | ||
} | ||
/** | ||
* Set a texture mip level to the contents of an external image. | ||
* Wrapper for the messy WebGL texture API | ||
* @note Corresponds to WebGPU device.queue.copyExternalImageToTexture() | ||
* | ||
export function setMipLevelFromExternalImage( | ||
gl: WebGL2RenderingContext, | ||
image: ExternalImage, | ||
options: WebGLSetTextureOptions | ||
): void { | ||
const {dimension, width, height, depth = 0, level = 0} = options; | ||
const {glInternalFormat, glType} = options; | ||
const glTarget = getWebGLCubeFaceTarget(options.glTarget, dimension, depth); | ||
// TODO - we can't change texture width (due to WebGPU limitations) - | ||
// and the width/heigh of an external image is implicit, so why do we need to extract it? | ||
// So what width height do we supply? The image size or the texture size? | ||
// const {width, height} = Texture.getExternalImageSize(image); | ||
switch (dimension) { | ||
case '2d-array': | ||
case '3d': | ||
// prettier-ignore | ||
gl.texImage3D(glTarget, level, glInternalFormat, width, height, depth, BORDER, glInternalFormat, glType, image); | ||
break; | ||
case '2d': | ||
case 'cube': | ||
// prettier-ignore | ||
gl.texImage2D(glTarget, level, glInternalFormat, width, height, BORDER, glInternalFormat, glType, image); | ||
break; | ||
default: | ||
throw new Error(dimension); | ||
} | ||
} | ||
/** | ||
* Set a texture mip level from CPU memory | ||
* Wrapper for the messy WebGL texture API | ||
* @note Not available (directly) in WebGPU | ||
* | ||
export function setMipLevelFromTypedArray( | ||
gl: WebGL2RenderingContext, | ||
data: TypedArray, | ||
parameters: {}, | ||
options: { | ||
dimension: '1d' | '2d' | '2d-array' | 'cube' | 'cube-array' | '3d'; | ||
height: number; | ||
width: number; | ||
depth?: number; | ||
level?: number; | ||
offset?: number; | ||
glTarget: GLTextureTarget; | ||
glInternalFormat: GL; | ||
glFormat: GL; | ||
glType: GL; | ||
compressed?: boolean; | ||
} | ||
): void { | ||
const {dimension, width, height, depth = 0, level = 0, offset = 0} = options; | ||
const {glInternalFormat, glFormat, glType, compressed} = options; | ||
const glTarget = getWebGLCubeFaceTarget(options.glTarget, dimension, depth); | ||
withGLParameters(gl, parameters, () => { | ||
switch (dimension) { | ||
case '2d-array': | ||
case '3d': | ||
if (compressed) { | ||
// prettier-ignore | ||
gl.compressedTexImage3D(glTarget, level, glInternalFormat, width, height, depth, BORDER, data); | ||
} else { | ||
// prettier-ignore | ||
gl.texImage3D( glTarget, level, glInternalFormat, width, height, depth, BORDER, glFormat, glType, data); | ||
} | ||
break; | ||
case '2d': | ||
if (compressed) { | ||
// prettier-ignore | ||
gl.compressedTexImage2D(glTarget, level, glInternalFormat, width, height, BORDER, data); | ||
} else { | ||
// prettier-ignore | ||
gl.texImage2D( glTarget, level, glInternalFormat, width, height, BORDER, glFormat, glType, data, offset); | ||
} | ||
break; | ||
default: | ||
throw new Error(dimension); | ||
} | ||
}); | ||
} | ||
/** | ||
* Set a texture level from CPU memory | ||
* @note Not available (directly) in WebGPU | ||
_setMipLevelFromTypedArray( | ||
depth: number, | ||
level: number, | ||
data: TextureLevelData, | ||
offset = 0, | ||
parameters | ||
): void { | ||
withGLParameters(this.gl, parameters, () => { | ||
switch (this.props.dimension) { | ||
case '2d-array': | ||
case '3d': | ||
if (this.compressed) { | ||
// prettier-ignore | ||
this.device.gl.compressedTexImage3D(this.glTarget, level, this.glInternalFormat, data.width, data.height, depth, BORDER, data.data); | ||
} else { | ||
// prettier-ignore | ||
this.gl.texImage3D( this.glTarget, level, this.glInternalFormat, this.width, this.height, depth, BORDER, this.glFormat, this.glType, data.data); | ||
} | ||
break; | ||
case '2d': | ||
if (this.compressed) { | ||
// prettier-ignore | ||
this.device.gl.compressedTexImage2D(this.glTarget, level, this.glInternalFormat, data.width, data.height, BORDER, data.data); | ||
} else { | ||
// prettier-ignore | ||
this.device.gl.texImage2D( this.glTarget, level, this.glInternalFormat, this.width, this.height, BORDER, this.glFormat, this.glType, data.data, offset); | ||
} | ||
break; | ||
default: | ||
throw new Error(this.props.dimension); | ||
} | ||
}); | ||
} | ||
* Set a texture level from a GPU buffer | ||
* | ||
export function setMipLevelFromGPUBuffer( | ||
gl: WebGL2RenderingContext, | ||
buffer: Buffer, | ||
options: WebGLSetTextureOptions | ||
): void { | ||
const {dimension, width, height, depth = 0, level = 0, byteOffset = 0} = options; | ||
const {glInternalFormat, glFormat, glType, compressed} = options; | ||
const glTarget = getWebGLCubeFaceTarget(options.glTarget, dimension, depth); | ||
const webglBuffer = buffer as WEBGLBuffer; | ||
const imageSize = buffer.byteLength; | ||
// In WebGL the source buffer is not a parameter. Instead it needs to be bound to a special bind point | ||
gl.bindBuffer(GL.PIXEL_UNPACK_BUFFER, webglBuffer.handle); | ||
switch (dimension) { | ||
case '2d-array': | ||
case '3d': | ||
if (compressed) { | ||
// prettier-ignore | ||
gl.compressedTexImage3D(glTarget, level, glInternalFormat, width, height, depth, BORDER, imageSize, byteOffset); | ||
} else { | ||
// prettier-ignore | ||
gl.texImage3D(glTarget, level, glInternalFormat, width, height, depth, BORDER, glFormat, glType, byteOffset); | ||
} | ||
break; | ||
case '2d': | ||
if (compressed) { | ||
// prettier-ignore | ||
gl.compressedTexImage2D(glTarget, level, glInternalFormat, width, height, BORDER, imageSize, byteOffset); | ||
} else { | ||
// prettier-ignore | ||
gl.texImage2D(glTarget, level, glInternalFormat, width, height, BORDER, glFormat, glType, byteOffset); | ||
} | ||
break; | ||
default: | ||
throw new Error(dimension); | ||
} | ||
gl.bindBuffer(GL.PIXEL_UNPACK_BUFFER, null); | ||
} | ||
*/ | ||
/** | ||
* Copies data from a type or a Texture object into ArrayBuffer object. | ||
@@ -452,7 +234,7 @@ * App can provide targetPixelArray or have it auto allocated by this method | ||
commandEncoder.copyTextureToBuffer({ | ||
source: source, | ||
sourceTexture: source, | ||
width: sourceWidth, | ||
height: sourceHeight, | ||
origin: [sourceX, sourceY], | ||
destination: webglBufferTarget, | ||
destinationBuffer: webglBufferTarget, | ||
byteOffset: targetByteOffset | ||
@@ -471,3 +253,3 @@ }); | ||
// eslint-disable-next-line complexity, max-statements | ||
export function copyToTexture(source, target, options) { | ||
export function copyToTexture(sourceTexture, destinationTexture, options) { | ||
const { sourceX = 0, sourceY = 0, | ||
@@ -479,3 +261,3 @@ // attachment = GL.COLOR_ATTACHMENT0, // TODO - support gl.readBuffer | ||
} = options || {}; | ||
const { framebuffer, deleteFramebuffer } = getFramebuffer(source); | ||
const { framebuffer, deleteFramebuffer } = getFramebuffer(sourceTexture); | ||
// assert(framebuffer); | ||
@@ -496,4 +278,4 @@ const webglFramebuffer = framebuffer; | ||
let textureTarget; | ||
if (target instanceof WEBGLTexture) { | ||
texture = target; | ||
if (destinationTexture instanceof WEBGLTexture) { | ||
texture = destinationTexture; | ||
width = Number.isFinite(width) ? width : texture.width; | ||
@@ -500,0 +282,0 @@ height = Number.isFinite(height) ? height : texture.height; |
@@ -35,4 +35,4 @@ // luma.gl | ||
function _copyBufferToBuffer(device, options) { | ||
const source = options.source; | ||
const destination = options.destination; | ||
const source = options.sourceBuffer; | ||
const destination = options.destinationBuffer; | ||
// {In WebGL2 we can p}erform the copy on the GPU | ||
@@ -60,3 +60,3 @@ // Use GL.COPY_READ_BUFFER+GL.COPY_WRITE_BUFFER avoid disturbing other targets and locking type | ||
/** Texture to copy to/from. */ | ||
source, | ||
sourceTexture, | ||
/** Mip-map level of the texture to copy to/from. (Default 0) */ | ||
@@ -67,9 +67,9 @@ mipLevel = 0, | ||
/** Width to copy */ | ||
width = options.source.width, | ||
width = options.sourceTexture.width, | ||
/** Height to copy */ | ||
height = options.source.height, depthOrArrayLayers = 0, | ||
height = options.sourceTexture.height, depthOrArrayLayers = 0, | ||
/** Defines the origin of the copy - the minimum corner of the texture sub-region to copy to/from. */ | ||
origin = [0, 0], | ||
/** Destination buffer */ | ||
destination, | ||
destinationBuffer, | ||
/** Offset, in bytes, from the beginning of the buffer to the start of the image data (default 0) */ | ||
@@ -90,3 +90,3 @@ byteOffset = 0, | ||
if (aspect !== 'all') { | ||
throw new Error('not supported'); | ||
throw new Error('aspect not supported in WebGL'); | ||
} | ||
@@ -98,6 +98,6 @@ // TODO - mipLevels are set when attaching texture to framebuffer | ||
// Asynchronous read (PIXEL_PACK_BUFFER) is WebGL2 only feature | ||
const { framebuffer, destroyFramebuffer } = getFramebuffer(source); | ||
const { framebuffer, destroyFramebuffer } = getFramebuffer(sourceTexture); | ||
let prevHandle; | ||
try { | ||
const webglBuffer = destination; | ||
const webglBuffer = destinationBuffer; | ||
const sourceWidth = width || framebuffer.width; | ||
@@ -156,3 +156,3 @@ const sourceHeight = height || framebuffer.height; | ||
/** Texture to copy to/from. */ | ||
source, | ||
sourceTexture, | ||
/** Mip-map level of the texture to copy to (Default 0) */ | ||
@@ -167,3 +167,3 @@ destinationMipLevel = 0, | ||
/** Texture to copy to/from. */ | ||
destination | ||
destinationTexture | ||
/** Mip-map level of the texture to copy to/from. (Default 0) */ | ||
@@ -176,6 +176,6 @@ // destinationMipLevel = options.mipLevel, | ||
} = options; | ||
let { width = options.destination.width, height = options.destination.height | ||
let { width = options.destinationTexture.width, height = options.destinationTexture.height | ||
// depthOrArrayLayers = 0 | ||
} = options; | ||
const { framebuffer, destroyFramebuffer } = getFramebuffer(source); | ||
const { framebuffer, destroyFramebuffer } = getFramebuffer(sourceTexture); | ||
const [sourceX, sourceY] = origin; | ||
@@ -189,4 +189,4 @@ const [destinationX, destinationY, destinationZ] = destinationOrigin; | ||
let textureTarget; | ||
if (destination instanceof WEBGLTexture) { | ||
texture = destination; | ||
if (destinationTexture instanceof WEBGLTexture) { | ||
texture = destinationTexture; | ||
width = Number.isFinite(width) ? width : texture.width; | ||
@@ -193,0 +193,0 @@ height = Number.isFinite(height) ? height : texture.height; |
@@ -57,3 +57,3 @@ import type { RenderPipelineProps, RenderPipelineParameters, PrimitiveTopology, ShaderLayout, UniformValue, Binding, RenderPass, VertexArray } from '@luma.gl/core'; | ||
/** Report link status. First, check for shader compilation failures if linking fails */ | ||
_reportLinkStatus(status: 'success' | 'linking' | 'validation'): void; | ||
_reportLinkStatus(status: 'success' | 'linking' | 'validation'): Promise<void>; | ||
/** | ||
@@ -60,0 +60,0 @@ * Get the shader compilation status |
@@ -81,7 +81,7 @@ // luma.gl | ||
// 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`); | ||
const binding = 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(', '); | ||
@@ -224,3 +224,3 @@ if (!options?.disableWarnings) { | ||
/** Report link status. First, check for shader compilation failures if linking fails */ | ||
_reportLinkStatus(status) { | ||
async _reportLinkStatus(status) { | ||
switch (status) { | ||
@@ -231,11 +231,24 @@ case 'success': | ||
// First check for shader compilation failures if linking fails | ||
if (this.vs.compilationStatus === 'error') { | ||
this.vs.debugShader(); | ||
throw new Error(`Error during compilation of shader ${this.vs.id}`); | ||
switch (this.vs.compilationStatus) { | ||
case 'error': | ||
this.vs.debugShader(); | ||
throw new Error(`Error during compilation of shader ${this.vs.id}`); | ||
case 'pending': | ||
this.vs.asyncCompilationStatus.then(() => this.vs.debugShader()); | ||
break; | ||
case 'success': | ||
break; | ||
} | ||
if (this.fs?.compilationStatus === 'error') { | ||
this.fs.debugShader(); | ||
throw new Error(`Error during compilation of shader ${this.fs.id}`); | ||
switch (this.fs?.compilationStatus) { | ||
case 'error': | ||
this.fs.debugShader(); | ||
throw new Error(`Error during compilation of shader ${this.fs.id}`); | ||
case 'pending': | ||
this.fs.asyncCompilationStatus.then(() => this.fs.debugShader()); | ||
break; | ||
case 'success': | ||
break; | ||
} | ||
throw new Error(`Error during ${status}: ${this.device.gl.getProgramInfoLog(this.handle)}`); | ||
const linkErrorLog = this.device.gl.getProgramInfoLog(this.handle); | ||
throw new Error(`Error during ${status}: ${linkErrorLog}`); | ||
} | ||
@@ -242,0 +255,0 @@ } |
@@ -11,2 +11,3 @@ import { Shader, ShaderProps, CompilerMessage } from '@luma.gl/core'; | ||
destroy(): void; | ||
get asyncCompilationStatus(): Promise<'pending' | 'success' | 'error'>; | ||
getCompilationInfo(): Promise<readonly CompilerMessage[]>; | ||
@@ -13,0 +14,0 @@ getCompilationInfoSync(): readonly CompilerMessage[]; |
@@ -36,2 +36,5 @@ // luma.gl | ||
} | ||
get asyncCompilationStatus() { | ||
return this._waitForCompilationComplete().then(() => this.compilationStatus); | ||
} | ||
async getCompilationInfo() { | ||
@@ -42,4 +45,4 @@ await this._waitForCompilationComplete(); | ||
getCompilationInfoSync() { | ||
const log = this.device.gl.getShaderInfoLog(this.handle); | ||
return log ? parseShaderCompilerLog(log) : []; | ||
const shaderLog = this.device.gl.getShaderInfoLog(this.handle); | ||
return shaderLog ? parseShaderCompilerLog(shaderLog) : []; | ||
} | ||
@@ -54,4 +57,3 @@ getTranslatedSource() { | ||
async _compile(source) { | ||
const addGLSLVersion = (source) => 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; | ||
@@ -58,0 +60,0 @@ gl.shaderSource(this.handle, source); |
// luma.gl | ||
// SPDX-License-Identifier: MIT | ||
// Copyright (c) vis.gl contributors | ||
import { Adapter, Device, CanvasContext, log } from '@luma.gl/core'; | ||
import { Adapter, Device, log } from '@luma.gl/core'; | ||
import { WebGLDevice } from "./webgl-device.js"; | ||
@@ -46,3 +46,3 @@ import { enforceWebGL2 } from "../context/polyfills/polyfill-webgl1-extensions.js"; | ||
} | ||
return new WebGLDevice({ gl: gl }); | ||
return new WebGLDevice({ _handle: gl }); | ||
} | ||
@@ -53,13 +53,8 @@ async create(props = {}) { | ||
// 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') { | ||
promises.push(CanvasContext.pageLoaded); | ||
} | ||
// Wait for all the loads to settle before creating the context. | ||
@@ -73,3 +68,2 @@ // The Device.create() functions are async, so in contrast to the constructor, we can `await` here. | ||
} | ||
log.probe(LOG_LEVEL + 1, 'DOM is loaded')(); | ||
const device = new WebGLDevice(props); | ||
@@ -76,0 +70,0 @@ // Log some debug info about the newly created context |
import type { TypedArray } from '@math.gl/types'; | ||
import type { DeviceProps, DeviceInfo, CanvasContextProps, TextureFormat, Buffer, Texture, Framebuffer, VertexArray, VertexArrayProps } from '@luma.gl/core'; | ||
import type { DeviceProps, DeviceInfo, CanvasContextProps, TextureFormat, Buffer, Texture, Framebuffer, VertexArray, VertexArrayProps, BufferProps, ShaderProps, SamplerProps, TextureProps, ExternalTexture, ExternalTextureProps, FramebufferProps, RenderPipelineProps, ComputePipeline, ComputePipelineProps, RenderPassProps, ComputePass, ComputePassProps, CommandEncoderProps, TransformFeedbackProps, QuerySetProps } from '@luma.gl/core'; | ||
import { Device, CanvasContext } from '@luma.gl/core'; | ||
@@ -9,3 +9,2 @@ import type { GLExtensions } from '@luma.gl/constants'; | ||
import type { Spector } from "../context/debug/spector-types.js"; | ||
import type { BufferProps, ShaderProps, SamplerProps, TextureProps, ExternalTexture, ExternalTextureProps, FramebufferProps, RenderPipelineProps, ComputePipeline, ComputePipelineProps, RenderPassProps, ComputePass, ComputePassProps, CommandEncoderProps, TransformFeedbackProps, QuerySetProps } from '@luma.gl/core'; | ||
import { WEBGLBuffer } from "./resources/webgl-buffer.js"; | ||
@@ -12,0 +11,0 @@ import { WEBGLShader } from "./resources/webgl-shader.js"; |
@@ -61,5 +61,10 @@ // luma.gl | ||
super({ ...props, id: props.id || uid('webgl-device') }); | ||
// WebGL requires a canvas to be created before creating the context | ||
if (!props.createCanvasContext) { | ||
throw new Error('WebGLDevice requires props.createCanvasContext to be set'); | ||
} | ||
const canvasContextProps = props.createCanvasContext === true ? {} : props.createCanvasContext; | ||
// If attaching to an already attached context, return the attached device | ||
// @ts-expect-error device is attached to context | ||
const device = props.gl?.device; | ||
let device = canvasContextProps.canvas?.gl?.device; | ||
if (device) { | ||
@@ -69,18 +74,32 @@ 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, canvasContextProps); | ||
this.lost = new Promise(resolve => { | ||
this._resolveContextLost = resolve; | ||
}); | ||
this.handle = createBrowserContext(this.canvasContext.canvas, { | ||
...props, | ||
const webglContextAttributes = { ...props.webgl }; | ||
// Copy props from CanvasContextProps | ||
if (canvasContextProps.alphaMode === 'premultiplied') { | ||
webglContextAttributes.premultipliedAlpha = true; | ||
} | ||
if (props.powerPreference !== undefined) { | ||
webglContextAttributes.powerPreference = props.powerPreference; | ||
} | ||
const gl = createBrowserContext(this.canvasContext.canvas, { | ||
onContextLost: (event) => this._resolveContextLost?.({ | ||
reason: 'destroyed', | ||
message: 'Entered sleep mode, or too many apps or browser tabs are using the GPU.' | ||
}) | ||
}); | ||
this.gl = this.handle; | ||
if (!this.handle) { | ||
}), | ||
// eslint-disable-next-line no-console | ||
onContextRestored: (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 | ||
@@ -96,4 +115,4 @@ // We need to trust spector integration to decide if spector should be initialized | ||
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(); | ||
@@ -108,4 +127,4 @@ } | ||
// 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; | ||
@@ -138,3 +157,3 @@ log.level = Math.max(log.level, 1); | ||
createBuffer(props) { | ||
const newProps = this._getBufferProps(props); | ||
const newProps = this._normalizeBufferProps(props); | ||
return new WEBGLBuffer(this, newProps); | ||
@@ -141,0 +160,0 @@ } |
// Forked from https://github.com/BabylonJS/Spector.js/blob/master/dist/spector.d.ts | ||
/* eslint-disable camelcase */ | ||
/* eslint-disable camelcase, no-shadow */ | ||
var LogLevel; | ||
@@ -4,0 +4,0 @@ (function (LogLevel) { |
@@ -1,8 +0,8 @@ | ||
import { Spector } from "./spector-types.js"; | ||
import type { Spector } from "./spector-types.js"; | ||
/** 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 */ | ||
@@ -17,3 +17,3 @@ gl?: WebGL2RenderingContext; | ||
export declare function loadSpectorJS(props: { | ||
spectorUrl?: string; | ||
debugSpectorJSUrl?: string; | ||
}): Promise<void>; | ||
@@ -20,0 +20,0 @@ export declare function initializeSpectorJS(props: SpectorProps): Spector | null; |
@@ -10,7 +10,7 @@ // luma.gl | ||
export const DEFAULT_SPECTOR_PROPS = { | ||
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 | ||
@@ -22,3 +22,3 @@ }; | ||
try { | ||
await loadScript(props.spectorUrl || DEFAULT_SPECTOR_PROPS.spectorUrl); | ||
await loadScript(props.debugSpectorJSUrl || DEFAULT_SPECTOR_PROPS.debugSpectorJSUrl); | ||
} | ||
@@ -32,3 +32,3 @@ catch (error) { | ||
props = { ...DEFAULT_SPECTOR_PROPS, ...props }; | ||
if (!props.debugWithSpectorJS) { | ||
if (!props.debugSpectorJS) { | ||
return null; | ||
@@ -38,4 +38,4 @@ } | ||
log.probe(LOG_LEVEL, 'SPECTOR found and initialized. Start with `luma.spector.displayUI()`')(); | ||
const { Spector } = globalThis.SPECTOR; | ||
spector = new Spector(); | ||
const { Spector: SpectorJS } = globalThis.SPECTOR; | ||
spector = new SpectorJS(); | ||
if (globalThis.luma) { | ||
@@ -42,0 +42,0 @@ globalThis.luma.spector = spector; |
type DebugContextProps = { | ||
debug?: boolean; | ||
throwOnError?: boolean; | ||
break?: string[]; | ||
debugWebGL?: boolean; | ||
}; | ||
@@ -6,0 +4,0 @@ declare global { |
@@ -32,3 +32,3 @@ // luma.gl | ||
export function makeDebugContext(gl, props = {}) { | ||
return props.debug ? getDebugContext(gl, props) : getRealContext(gl); | ||
return props.debugWebGL ? getDebugContext(gl, props) : getRealContext(gl); | ||
} | ||
@@ -92,5 +92,3 @@ // Returns the real context from either of the real/debug contexts | ||
debugger; // eslint-disable-line | ||
if (props.throwOnError) { | ||
throw new Error(message); | ||
} | ||
// throw new Error(message); | ||
} | ||
@@ -104,22 +102,9 @@ // Don't generate function string until it is needed | ||
} | ||
// 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) => 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}`); | ||
} | ||
} | ||
} |
/** | ||
* 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; | ||
desynchronized?: boolean; | ||
antialias?: boolean; | ||
depth?: boolean; | ||
failIfMajorPerformanceCaveat?: boolean; | ||
powerPreference?: 'default' | 'high-performance' | 'low-power'; | ||
premultipliedAlpha?: boolean; | ||
preserveDrawingBuffer?: boolean; | ||
/** Called when a context is lost */ | ||
onContextLost: (event: Event) => void; | ||
/** Called when a context is restored */ | ||
onContextRestored: (event: Event) => void; | ||
}; | ||
@@ -33,4 +17,4 @@ /** | ||
*/ | ||
export declare function createBrowserContext(canvas: HTMLCanvasElement | OffscreenCanvas, props: ContextProps): WebGL2RenderingContext; | ||
export declare function createBrowserContext(canvas: HTMLCanvasElement | OffscreenCanvas, props: ContextProps, webglContextAttributes: WebGLContextAttributes): WebGL2RenderingContext; | ||
export {}; | ||
//# sourceMappingURL=create-browser-context.d.ts.map |
// luma.gl | ||
// SPDX-License-Identifier: MIT | ||
// Copyright (c) vis.gl contributors | ||
const DEFAULT_CONTEXT_PROPS = { | ||
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') | ||
}; | ||
/** | ||
@@ -16,33 +9,34 @@ * Create a WebGL context for a canvas | ||
*/ | ||
export function createBrowserContext(canvas, props) { | ||
props = { ...DEFAULT_CONTEXT_PROPS, ...props }; | ||
export function createBrowserContext(canvas, props, webglContextAttributes) { | ||
// 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); | ||
const errorMessage = null; | ||
// const onCreateError = error => (errorMessage = error.statusMessage || errorMessage); | ||
// Avoid multiple listeners? | ||
// canvas.removeEventListener('webglcontextcreationerror', onCreateError, false); | ||
// canvas.addEventListener('webglcontextcreationerror', onCreateError, false); | ||
const webglProps = { | ||
preserveDrawingBuffer: true, | ||
// failIfMajorPerformanceCaveat: true, | ||
...webglContextAttributes | ||
}; | ||
// Create the desired context | ||
let gl = null; | ||
// props.failIfMajorPerformanceCaveat = true; | ||
// We require webgl2 context | ||
gl ||= canvas.getContext('webgl2', props); | ||
// Software GPU | ||
// props.failIfMajorPerformanceCaveat = false; | ||
// if (!gl && props.webgl1) { | ||
// gl = canvas.getContext('webgl', props); | ||
// } | ||
// 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); | ||
// @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) => onContextLost(event), false); | ||
} | ||
if (props.onContextRestored) { | ||
// Carefully extract and wrap callbacks to prevent addEventListener from rebinding them. | ||
const { onContextRestored } = props; | ||
canvas.addEventListener('webglcontextrestored', (event) => onContextRestored(event), false); | ||
} | ||
// Carefully extract and wrap callbacks to prevent addEventListener from rebinding them. | ||
const { onContextLost, onContextRestored } = props; | ||
canvas.addEventListener('webglcontextlost', (event) => onContextLost(event), false); | ||
canvas.addEventListener('webglcontextrestored', (event) => onContextRestored(event), false); | ||
return gl; | ||
@@ -49,0 +43,0 @@ } |
{ | ||
"name": "@luma.gl/webgl", | ||
"version": "9.1.0-alpha.15", | ||
"version": "9.1.0-alpha.16", | ||
"description": "WebGL2 adapter for the luma.gl core API", | ||
@@ -46,7 +46,7 @@ "type": "module", | ||
"dependencies": { | ||
"@luma.gl/constants": "9.1.0-alpha.15", | ||
"@luma.gl/constants": "9.1.0-alpha.16", | ||
"@math.gl/types": "4.1.0-alpha.3", | ||
"@probe.gl/env": "^4.0.8" | ||
}, | ||
"gitHead": "41af576ca655cb749a5567cf903f9e9242793c77" | ||
"gitHead": "39eec40d12c826548b636c057fdb8572adfe611f" | ||
} |
@@ -34,3 +34,3 @@ // luma.gl | ||
glParameters: GLParameters, | ||
func: (device?: Device) => T | ||
func: (_?: Device) => T | ||
): T { | ||
@@ -66,3 +66,3 @@ if (isObjectEmpty(parameters)) { | ||
parameters: GLParameters, | ||
func: (device?: Device) => T | ||
func: (_?: Device) => T | ||
): T { | ||
@@ -96,3 +96,3 @@ if (isObjectEmpty(parameters)) { | ||
parameters: Parameters, | ||
func: (device?: Device) => T | ||
func: (_?: Device) => T | ||
): T { | ||
@@ -99,0 +99,0 @@ if (isObjectEmpty(parameters)) { |
@@ -493,3 +493,27 @@ // luma.gl | ||
*/ | ||
export type ReadPixelsToArrayOptions = { | ||
sourceX?: number; | ||
sourceY?: number; | ||
sourceFormat?: number; | ||
sourceAttachment?: number; | ||
target?: Uint8Array | Uint16Array | Float32Array; | ||
// following parameters are auto deduced if not provided | ||
sourceWidth?: number; | ||
sourceHeight?: number; | ||
sourceDepth?: number; | ||
sourceType?: number; | ||
}; | ||
export type ReadPixelsToBufferOptions = { | ||
sourceX?: number; | ||
sourceY?: number; | ||
sourceFormat?: number; | ||
target?: Buffer; // A new Buffer object is created when not provided. | ||
targetByteOffset?: number; // byte offset in buffer object | ||
// following parameters are auto deduced if not provided | ||
sourceWidth?: number; | ||
sourceHeight?: number; | ||
sourceType?: number; | ||
}; | ||
/** | ||
@@ -508,14 +532,3 @@ * Copies data from a type or a Texture object into ArrayBuffer object. | ||
source: Framebuffer | Texture, | ||
options?: { | ||
sourceX?: number; | ||
sourceY?: number; | ||
sourceFormat?: number; | ||
sourceAttachment?: number; | ||
target?: Uint8Array | Uint16Array | Float32Array; | ||
// following parameters are auto deduced if not provided | ||
sourceWidth?: number; | ||
sourceHeight?: number; | ||
sourceDepth?: number; | ||
sourceType?: number; | ||
} | ||
options?: ReadPixelsToArrayOptions | ||
): Uint8Array | Uint16Array | Float32Array { | ||
@@ -581,13 +594,3 @@ const { | ||
source: Framebuffer | Texture, | ||
options?: { | ||
sourceX?: number; | ||
sourceY?: number; | ||
sourceFormat?: number; | ||
target?: Buffer; // A new Buffer object is created when not provided. | ||
targetByteOffset?: number; // byte offset in buffer object | ||
// following parameters are auto deduced if not provided | ||
sourceWidth?: number; | ||
sourceHeight?: number; | ||
sourceType?: number; | ||
} | ||
options?: ReadPixelsToBufferOptions | ||
): WEBGLBuffer { | ||
@@ -626,7 +629,7 @@ const { | ||
commandEncoder.copyTextureToBuffer({ | ||
source: source as Texture, | ||
sourceTexture: source as Texture, | ||
width: sourceWidth, | ||
height: sourceHeight, | ||
origin: [sourceX, sourceY], | ||
destination: webglBufferTarget, | ||
destinationBuffer: webglBufferTarget, | ||
byteOffset: targetByteOffset | ||
@@ -649,4 +652,4 @@ }); | ||
export function copyToTexture( | ||
source: Framebuffer | Texture, | ||
target: Texture | GL, | ||
sourceTexture: Framebuffer | Texture, | ||
destinationTexture: Texture | GL, | ||
options?: { | ||
@@ -681,3 +684,3 @@ sourceX?: number; | ||
const {framebuffer, deleteFramebuffer} = getFramebuffer(source); | ||
const {framebuffer, deleteFramebuffer} = getFramebuffer(sourceTexture); | ||
// assert(framebuffer); | ||
@@ -699,4 +702,4 @@ const webglFramebuffer = framebuffer; | ||
let textureTarget: GL; | ||
if (target instanceof WEBGLTexture) { | ||
texture = target; | ||
if (destinationTexture instanceof WEBGLTexture) { | ||
texture = destinationTexture; | ||
width = Number.isFinite(width) ? width : texture.width; | ||
@@ -703,0 +706,0 @@ height = Number.isFinite(height) ? height : texture.height; |
@@ -76,4 +76,4 @@ // luma.gl | ||
function _copyBufferToBuffer(device: WebGLDevice, options: CopyBufferToBufferOptions): void { | ||
const source = options.source as WEBGLBuffer; | ||
const destination = options.destination as WEBGLBuffer; | ||
const source = options.sourceBuffer as WEBGLBuffer; | ||
const destination = options.destinationBuffer as WEBGLBuffer; | ||
@@ -110,3 +110,3 @@ // {In WebGL2 we can p}erform the copy on the GPU | ||
/** Texture to copy to/from. */ | ||
source, | ||
sourceTexture, | ||
/** Mip-map level of the texture to copy to/from. (Default 0) */ | ||
@@ -118,5 +118,5 @@ mipLevel = 0, | ||
/** Width to copy */ | ||
width = options.source.width, | ||
width = options.sourceTexture.width, | ||
/** Height to copy */ | ||
height = options.source.height, | ||
height = options.sourceTexture.height, | ||
depthOrArrayLayers = 0, | ||
@@ -127,3 +127,3 @@ /** Defines the origin of the copy - the minimum corner of the texture sub-region to copy to/from. */ | ||
/** Destination buffer */ | ||
destination, | ||
destinationBuffer, | ||
/** Offset, in bytes, from the beginning of the buffer to the start of the image data (default 0) */ | ||
@@ -146,3 +146,3 @@ byteOffset = 0, | ||
if (aspect !== 'all') { | ||
throw new Error('not supported'); | ||
throw new Error('aspect not supported in WebGL'); | ||
} | ||
@@ -156,6 +156,6 @@ | ||
// Asynchronous read (PIXEL_PACK_BUFFER) is WebGL2 only feature | ||
const {framebuffer, destroyFramebuffer} = getFramebuffer(source); | ||
const {framebuffer, destroyFramebuffer} = getFramebuffer(sourceTexture); | ||
let prevHandle: WebGLFramebuffer | null | undefined; | ||
try { | ||
const webglBuffer = destination as WEBGLBuffer; | ||
const webglBuffer = destinationBuffer as WEBGLBuffer; | ||
const sourceWidth = width || framebuffer.width; | ||
@@ -229,3 +229,3 @@ const sourceHeight = height || framebuffer.height; | ||
/** Texture to copy to/from. */ | ||
source, | ||
sourceTexture, | ||
/** Mip-map level of the texture to copy to (Default 0) */ | ||
@@ -242,3 +242,3 @@ destinationMipLevel = 0, | ||
/** Texture to copy to/from. */ | ||
destination | ||
destinationTexture | ||
/** Mip-map level of the texture to copy to/from. (Default 0) */ | ||
@@ -253,8 +253,8 @@ // destinationMipLevel = options.mipLevel, | ||
let { | ||
width = options.destination.width, | ||
height = options.destination.height | ||
width = options.destinationTexture.width, | ||
height = options.destinationTexture.height | ||
// depthOrArrayLayers = 0 | ||
} = options; | ||
const {framebuffer, destroyFramebuffer} = getFramebuffer(source); | ||
const {framebuffer, destroyFramebuffer} = getFramebuffer(sourceTexture); | ||
const [sourceX, sourceY] = origin; | ||
@@ -273,4 +273,4 @@ const [destinationX, destinationY, destinationZ] = destinationOrigin; | ||
let textureTarget: GL; | ||
if (destination instanceof WEBGLTexture) { | ||
texture = destination; | ||
if (destinationTexture instanceof WEBGLTexture) { | ||
texture = destinationTexture; | ||
width = Number.isFinite(width) ? width : texture.width; | ||
@@ -277,0 +277,0 @@ height = Number.isFinite(height) ? height : texture.height; |
@@ -113,8 +113,8 @@ // luma.gl | ||
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(', '); | ||
@@ -321,3 +321,3 @@ if (!options?.disableWarnings) { | ||
/** Report link status. First, check for shader compilation failures if linking fails */ | ||
_reportLinkStatus(status: 'success' | 'linking' | 'validation') { | ||
async _reportLinkStatus(status: 'success' | 'linking' | 'validation'): Promise<void> { | ||
switch (status) { | ||
@@ -329,11 +329,26 @@ case 'success': | ||
// First check for shader compilation failures if linking fails | ||
if (this.vs.compilationStatus === 'error') { | ||
this.vs.debugShader(); | ||
throw new Error(`Error during compilation of shader ${this.vs.id}`); | ||
switch (this.vs.compilationStatus) { | ||
case 'error': | ||
this.vs.debugShader(); | ||
throw new Error(`Error during compilation of shader ${this.vs.id}`); | ||
case 'pending': | ||
this.vs.asyncCompilationStatus.then(() => this.vs.debugShader()); | ||
break; | ||
case 'success': | ||
break; | ||
} | ||
if (this.fs?.compilationStatus === 'error') { | ||
this.fs.debugShader(); | ||
throw new Error(`Error during compilation of shader ${this.fs.id}`); | ||
switch (this.fs?.compilationStatus) { | ||
case 'error': | ||
this.fs.debugShader(); | ||
throw new Error(`Error during compilation of shader ${this.fs.id}`); | ||
case 'pending': | ||
this.fs.asyncCompilationStatus.then(() => this.fs.debugShader()); | ||
break; | ||
case 'success': | ||
break; | ||
} | ||
throw new Error(`Error during ${status}: ${this.device.gl.getProgramInfoLog(this.handle)}`); | ||
const linkErrorLog = this.device.gl.getProgramInfoLog(this.handle); | ||
throw new Error(`Error during ${status}: ${linkErrorLog}`); | ||
} | ||
@@ -340,0 +355,0 @@ } |
@@ -42,2 +42,6 @@ // luma.gl | ||
get asyncCompilationStatus(): Promise<'pending' | 'success' | 'error'> { | ||
return this._waitForCompilationComplete().then(() => this.compilationStatus); | ||
} | ||
override async getCompilationInfo(): Promise<readonly CompilerMessage[]> { | ||
@@ -49,4 +53,4 @@ await this._waitForCompilationComplete(); | ||
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) : []; | ||
} | ||
@@ -64,5 +68,3 @@ | ||
protected async _compile(source: string): Promise<void> { | ||
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}`; | ||
@@ -69,0 +71,0 @@ const {gl} = this.device; |
@@ -5,3 +5,3 @@ // luma.gl | ||
import {Adapter, Device, DeviceProps, CanvasContext, log} from '@luma.gl/core'; | ||
import {Adapter, Device, DeviceProps, log} from '@luma.gl/core'; | ||
import {WebGLDevice} from './webgl-device'; | ||
@@ -56,3 +56,3 @@ import {enforceWebGL2} from '../context/polyfills/polyfill-webgl1-extensions'; | ||
} | ||
return new WebGLDevice({gl: gl as WebGL2RenderingContext}); | ||
return new WebGLDevice({_handle: gl as WebGL2RenderingContext}); | ||
} | ||
@@ -66,16 +66,10 @@ | ||
// 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') { | ||
promises.push(CanvasContext.pageLoaded); | ||
} | ||
// Wait for all the loads to settle before creating the context. | ||
@@ -90,4 +84,2 @@ // The Device.create() functions are async, so in contrast to the constructor, we can `await` here. | ||
log.probe(LOG_LEVEL + 1, 'DOM is loaded')(); | ||
const device = new WebGLDevice(props); | ||
@@ -94,0 +86,0 @@ |
@@ -15,24 +15,3 @@ // luma.gl | ||
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, | ||
@@ -59,2 +38,19 @@ ShaderProps, | ||
} 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'; | ||
@@ -125,5 +121,11 @@ import {WEBGLBuffer} from './resources/webgl-buffer'; | ||
// WebGL requires a canvas to be created before creating the context | ||
if (!props.createCanvasContext) { | ||
throw new Error('WebGLDevice requires props.createCanvasContext to be set'); | ||
} | ||
const canvasContextProps = props.createCanvasContext === true ? {} : props.createCanvasContext; | ||
// 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 = canvasContextProps.canvas?.gl?.device; | ||
if (device) { | ||
@@ -134,4 +136,3 @@ 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, canvasContextProps); | ||
@@ -142,16 +143,38 @@ this.lost = new Promise<{reason: 'destroyed'; message: string}>(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 (canvasContextProps.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 | ||
@@ -169,4 +192,8 @@ // We need to trust spector integration to decide if spector should be initialized | ||
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(); | ||
@@ -184,4 +211,4 @@ } | ||
// 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; | ||
@@ -222,3 +249,3 @@ log.level = Math.max(log.level, 1); | ||
createBuffer(props: BufferProps | ArrayBuffer | ArrayBufferView): WEBGLBuffer { | ||
const newProps = this._getBufferProps(props); | ||
const newProps = this._normalizeBufferProps(props); | ||
return new WEBGLBuffer(this, newProps); | ||
@@ -225,0 +252,0 @@ } |
// Forked from https://github.com/BabylonJS/Spector.js/blob/master/dist/spector.d.ts | ||
/* eslint-disable camelcase */ | ||
/* eslint-disable camelcase, no-shadow */ | ||
@@ -4,0 +4,0 @@ interface IEvent<T> { |
@@ -8,10 +8,10 @@ // luma.gl | ||
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 */ | ||
@@ -33,7 +33,7 @@ gl?: WebGL2RenderingContext; | ||
export const DEFAULT_SPECTOR_PROPS: Required<SpectorProps> = { | ||
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! | ||
@@ -43,6 +43,6 @@ }; | ||
/** Loads spector from CDN if not already installed */ | ||
export async function loadSpectorJS(props: {spectorUrl?: string}): Promise<void> { | ||
export async function loadSpectorJS(props: {debugSpectorJSUrl?: string}): Promise<void> { | ||
if (!globalThis.SPECTOR) { | ||
try { | ||
await loadScript(props.spectorUrl || DEFAULT_SPECTOR_PROPS.spectorUrl); | ||
await loadScript(props.debugSpectorJSUrl || DEFAULT_SPECTOR_PROPS.debugSpectorJSUrl); | ||
} catch (error) { | ||
@@ -56,3 +56,3 @@ log.warn(String(error)); | ||
props = {...DEFAULT_SPECTOR_PROPS, ...props}; | ||
if (!props.debugWithSpectorJS) { | ||
if (!props.debugSpectorJS) { | ||
return null; | ||
@@ -63,4 +63,4 @@ } | ||
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) { | ||
@@ -67,0 +67,0 @@ (globalThis.luma as any).spector = spector; |
@@ -14,14 +14,5 @@ // luma.gl | ||
type DebugContextProps = { | ||
debug?: boolean; | ||
throwOnError?: boolean; | ||
break?: string[]; | ||
debugWebGL?: boolean; | ||
}; | ||
// const DEFAULT_DEBUG_CONTEXT_PROPS: Required<DebugContextProps> = { | ||
// debug: true, | ||
// throwOnError: false, | ||
// break: [], | ||
// webgl2: false, | ||
// } | ||
type ContextData = { | ||
@@ -64,3 +55,3 @@ realContext?: WebGL2RenderingContext; | ||
): WebGL2RenderingContext { | ||
return props.debug ? getDebugContext(gl, props) : getRealContext(gl); | ||
return props.debugWebGL ? getDebugContext(gl, props) : getRealContext(gl); | ||
} | ||
@@ -141,5 +132,3 @@ | ||
debugger; // eslint-disable-line | ||
if (props.throwOnError) { | ||
throw new Error(message); | ||
} | ||
// throw new Error(message); | ||
} | ||
@@ -159,24 +148,9 @@ | ||
// 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}`); | ||
} | ||
} | ||
} |
@@ -8,35 +8,11 @@ // luma.gl | ||
* @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. | ||
/** Called when a context is lost */ | ||
onContextLost: (event: Event) => void; | ||
/** Called when a context is restored */ | ||
onContextRestored: (event: Event) => void; | ||
}; | ||
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') | ||
}; | ||
/** | ||
@@ -49,30 +25,35 @@ * Create a WebGL context for a canvas | ||
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); | ||
const errorMessage = null; | ||
// const onCreateError = error => (errorMessage = error.statusMessage || errorMessage); | ||
// Avoid multiple listeners? | ||
// canvas.removeEventListener('webglcontextcreationerror', onCreateError, false); | ||
// canvas.addEventListener('webglcontextcreationerror', onCreateError, false); | ||
const webglProps: WebGLContextAttributes = { | ||
preserveDrawingBuffer: true, | ||
// failIfMajorPerformanceCaveat: true, | ||
...webglContextAttributes | ||
}; | ||
// Create the desired context | ||
let gl: WebGL2RenderingContext | null = null; | ||
// props.failIfMajorPerformanceCaveat = true; | ||
// Create a webgl2 context | ||
gl ||= canvas.getContext('webgl2', webglProps); | ||
// We require webgl2 context | ||
gl ||= canvas.getContext('webgl2', props) as WebGL2RenderingContext; | ||
// 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; | ||
} | ||
// Software GPU | ||
// props.failIfMajorPerformanceCaveat = false; | ||
// if (!gl && props.webgl1) { | ||
// gl = canvas.getContext('webgl', props); | ||
// } | ||
// TODO are we removing this listener before giving it a chance to fire? | ||
canvas.removeEventListener('webglcontextcreationerror', onCreateError, false); | ||
if (!gl) { | ||
@@ -82,16 +63,10 @@ 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 | ||
); | ||
@@ -98,0 +73,0 @@ return gl; |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
1859655
32557
+ Added@luma.gl/constants@9.1.0-alpha.16(transitive)
- Removed@luma.gl/constants@9.1.0-alpha.15(transitive)