Skip to content

Commit

Permalink
chore(core): Clean up WebGLStateTracker (#2087)
Browse files Browse the repository at this point in the history
  • Loading branch information
ibgreen committed Apr 29, 2024
1 parent 450f308 commit 32d9fff
Show file tree
Hide file tree
Showing 8 changed files with 102 additions and 157 deletions.
18 changes: 8 additions & 10 deletions modules/webgl/src/adapter/converters/device-parameters.ts
Expand Up @@ -14,7 +14,6 @@ import type {
GLProvokingVertex,
GLStencilOp
} from '@luma.gl/constants';
import {pushContextState, popContextState} from '../../context/state-tracker/track-context-state';
import {setGLParameters} from '../../context/parameters/unified-parameter-api';
import {WebGLDevice} from '../webgl-device';

Expand All @@ -41,13 +40,13 @@ export function withDeviceAndGLParameters<T = unknown>(

// Wrap in a try-catch to ensure that parameters are restored on exceptions
const webglDevice = device as WebGLDevice;
pushContextState(webglDevice.gl);
webglDevice.pushState();
try {
setDeviceParameters(device, parameters);
setGLParameters(webglDevice.gl, glParameters);
return func(device);
} finally {
popContextState(webglDevice.gl);
webglDevice.popState();
}
}

Expand All @@ -72,12 +71,12 @@ export function withGLParameters<T = unknown>(

// Wrap in a try-catch to ensure that parameters are restored on exceptions
const webglDevice = device as WebGLDevice;
pushContextState(webglDevice.gl);
webglDevice.pushState();
try {
setGLParameters(webglDevice.gl, parameters);
return func(device);
} finally {
popContextState(webglDevice.gl);
webglDevice.popState();
}
}

Expand All @@ -99,15 +98,14 @@ export function withDeviceParameters<T = unknown>(
return func(device);
}

// Wrap in a try-catch to ensure that parameters are restored on exceptions
// @ts-expect-error
pushContextState(device.gl);
// Wrap in a try-catch to ensure that parameters are restored on exceptions'
const webglDevice = device as WebGLDevice;
webglDevice.pushState();
try {
setDeviceParameters(device, parameters);
return func(device);
} finally {
// @ts-expect-error
popContextState(device.gl);
webglDevice.popState();
}
}

Expand Down
5 changes: 2 additions & 3 deletions modules/webgl/src/adapter/resources/webgl-render-pass.ts
Expand Up @@ -8,7 +8,6 @@ import {WebGLDevice} from '../webgl-device';
import {GL, GLParameters} from '@luma.gl/constants';
import {withGLParameters} from '../../context/state-tracker/with-parameters';
import {setGLParameters} from '../../context/parameters/unified-parameter-api';
import {pushContextState, popContextState} from '../../context/state-tracker/track-context-state';
import {WEBGLQuerySet} from './webgl-query-set';

// Should collapse during minification
Expand All @@ -30,15 +29,15 @@ export class WEBGLRenderPass extends RenderPass {
this.device = device;

// TODO - do parameters (scissorRect) affect the clear operation?
pushContextState(this.device.gl);
this.device.pushState();
this.setParameters(this.props.parameters);

// Hack - for now WebGL draws in "immediate mode" (instead of queueing the operations)...
this.clear();
}

end(): void {
popContextState(this.device.gl);
this.device.popState();
// should add commands to CommandEncoder.
}

Expand Down
19 changes: 7 additions & 12 deletions modules/webgl/src/adapter/webgl-device.ts
Expand Up @@ -7,11 +7,7 @@ import type {DeviceProps, DeviceInfo, CanvasContextProps, TextureFormat} from '@
import type {Buffer, Texture, Framebuffer, VertexArray, VertexArrayProps} from '@luma.gl/core';
import {Device, CanvasContext, log} from '@luma.gl/core';
import type {GLExtensions} from '@luma.gl/constants';
import {
popContextState,
pushContextState,
trackContextState
} from '../context/state-tracker/track-context-state';
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';
Expand Down Expand Up @@ -231,13 +227,10 @@ ${device.info.vendor}, ${device.info.renderer} for canvas: ${device.canvasContex
this.canvasContext.resize();

// Install context state tracking
// @ts-expect-error - hidden parameters
const {enable = true, copyState = false} = props;
trackContextState(this.gl, {
enable,
copyState,
const glState = new WebGLStateTracker(this.gl, {
log: (...args: any[]) => log.log(1, ...args)()
});
glState.trackState(this.gl, {copyState: false});

// DEBUG contexts: Add debug instrumentation to the context, force log level to at least 1
if (props.debug) {
Expand Down Expand Up @@ -461,12 +454,14 @@ ${device.info.vendor}, ${device.info.renderer} for canvas: ${device.canvasContex

/** Save current WebGL context state onto an internal stack */
pushState(): void {
pushContextState(this.gl);
const webglState = WebGLStateTracker.get(this.gl);
webglState.push();
}

/** Restores previously saved context state */
popState(): void {
popContextState(this.gl);
const webglState = WebGLStateTracker.get(this.gl);
webglState.pop();
}

/**
Expand Down
Expand Up @@ -2,37 +2,43 @@
// SPDX-License-Identifier: MIT
// Copyright (c) vis.gl contributors

// Support for listening to context state changes and intercepting state queries
// NOTE: this system does not handle buffer bindings
import {setGLParameters, getGLParameters} from '../parameters/unified-parameter-api';
import {deepArrayEqual} from './deep-array-equal';
import {
GL_PARAMETER_DEFAULTS,
GL_HOOKED_SETTERS,
NON_CACHE_PARAMETERS
} from '../parameters/webgl-parameter-tables';
import {setGLParameters, getGLParameters} from '../parameters/unified-parameter-api';
import {deepArrayEqual} from './deep-array-equal';

// HELPER CLASS - GLState
// HELPER CLASS - WebGLStateTracker

/**
* Support for listening to context state changes and intercepting state queries
* NOTE: this system does not handle buffer bindings
*/
export class WebGLStateTracker {
static get(gl: WebGL2RenderingContext): WebGLStateTracker {
// @ts-expect-error
return gl.state as WebGLStateTracker;
}

/* eslint-disable no-shadow */
class GLState {
gl: WebGL2RenderingContext;
program: unknown = null;
stateStack: object[] = [];
enable = true;
cache: Record<string, any>;
cache: Record<string, any> = null!;
log;

protected initialized = false;

constructor(
gl: WebGL2RenderingContext,
{
copyState = false, // Copy cache from params (slow) or initialize from WebGL defaults (fast)
log = () => {} // Logging function, called when gl parameter change calls are actually issued
} = {}
props?: {
log; // Logging function, called when gl parameter change calls are actually issued
}
) {
this.gl = gl;
this.cache = copyState ? getGLParameters(gl) : Object.assign({}, GL_PARAMETER_DEFAULTS);
this.log = log;
this.log = props?.log || (() => {});

this._updateCache = this._updateCache.bind(this);
Object.seal(this);
Expand All @@ -51,6 +57,38 @@ class GLState {
this.stateStack.pop();
}

/**
* Initialize WebGL state caching on a context
* can be called multiple times to enable/disable
*
* @note After calling this function, context state will be cached
* .push() and .pop() will be available for saving,
* temporarily modifying, and then restoring state.
*/
trackState(gl: WebGL2RenderingContext, options?: {copyState?: boolean}): void {
this.cache = options.copyState ? getGLParameters(gl) : Object.assign({}, GL_PARAMETER_DEFAULTS);

if (this.initialized) {
throw new Error('WebGLStateTracker');
}
this.initialized = true;

// @ts-expect-error
this.gl.state = this;

installProgramSpy(gl);

// intercept all setter functions in the table
for (const key in GL_HOOKED_SETTERS) {
const setter = GL_HOOKED_SETTERS[key];
installSetterSpy(gl, key, setter);
}

// intercept all getter functions in the table
installGetterOverride(gl, 'getParameter');
installGetterOverride(gl, 'isEnabled');
}

/**
// interceptor for context set functions - update our cache and our stack
// values (Object) - the key values for this setter
Expand Down Expand Up @@ -89,83 +127,6 @@ class GLState {
}
}

function getContextState(gl: WebGL2RenderingContext): GLState {
// @ts-expect-error
return gl.state as GLState;
}

// PUBLIC API

/**
* Initialize WebGL state caching on a context
* can be called multiple times to enable/disable
*
* @note After calling this function, context state will be cached
* gl.state.push() and gl.state.pop() will be available for saving,
* temporarily modifying, and then restoring state.
*/
export function trackContextState(
gl: WebGL2RenderingContext,
options?: {
enable?: boolean;
copyState?: boolean;
log?: any;
}
): WebGL2RenderingContext {
const {enable = true, copyState} = options || {};
// assert(copyState !== undefined);
// @ts-expect-error
if (!gl.state) {
// @ts-ignore
// const {polyfillContext} = global_;
// if (polyfillContext) {
// polyfillContext(gl);
// }

// Create a state cache
// @ts-expect-error
gl.state = new GLState(gl, {copyState});

installProgramSpy(gl);

// intercept all setter functions in the table
for (const key in GL_HOOKED_SETTERS) {
const setter = GL_HOOKED_SETTERS[key];
installSetterSpy(gl, key, setter);
}

// intercept all getter functions in the table
installGetterOverride(gl, 'getParameter');
installGetterOverride(gl, 'isEnabled');
}

const glState = getContextState(gl);
glState.enable = enable;

return gl;
}

/**
* Saves current WebGL context state onto an internal per-context stack
*/
export function pushContextState(gl: WebGL2RenderingContext): void {
let glState = getContextState(gl);
if (!glState) {
trackContextState(gl, {copyState: false});
glState = getContextState(gl);
}
glState.push();
}

/**
* Restores previously saved WebGL context state
*/
export function popContextState(gl: WebGL2RenderingContext): void {
const glState = getContextState(gl);
// assert(glState);
glState.pop();
}

// HELPER FUNCTIONS - INSTALL GET/SET INTERCEPTORS (SPYS) ON THE CONTEXT

/**
Expand All @@ -185,7 +146,7 @@ function installGetterOverride(gl: WebGL2RenderingContext, functionName: string)
return originalGetterFunc(pname);
}

const glState = getContextState(gl);
const glState = WebGLStateTracker.get(gl);
if (!(pname in glState.cache)) {
// WebGL limits are not prepopulated in the cache, call the original getter when first queried.
glState.cache[pname] = originalGetterFunc(pname);
Expand Down Expand Up @@ -229,7 +190,7 @@ function installSetterSpy(gl: WebGL2RenderingContext, functionName: string, sett
gl[functionName] = function set(...params) {
// Update the value
// Call the setter with the state cache and the params so that it can store the parameters
const glState = getContextState(gl);
const glState = WebGLStateTracker.get(gl);
// eslint-disable-next-line @typescript-eslint/unbound-method
const {valueChanged, oldValue} = setter(glState._updateCache, ...params);

Expand Down Expand Up @@ -257,7 +218,7 @@ function installProgramSpy(gl: WebGL2RenderingContext): void {
const originalUseProgram = gl.useProgram.bind(gl);

gl.useProgram = function useProgramLuma(handle) {
const glState = getContextState(gl);
const glState = WebGLStateTracker.get(gl);
if (glState.program !== handle) {
originalUseProgram(handle);
glState.program = handle;
Expand Down
9 changes: 5 additions & 4 deletions modules/webgl/src/context/state-tracker/with-parameters.ts
Expand Up @@ -3,7 +3,7 @@
// Copyright (c) vis.gl contributors

import {GLParameters, setGLParameters} from '../parameters/unified-parameter-api';
import {pushContextState, popContextState} from './track-context-state';
import {WebGLStateTracker} from './webgl-state-tracker';

/**
* Execute a function with a set of temporary WebGL parameter overrides
Expand All @@ -25,7 +25,8 @@ export function withGLParameters(

const {nocatch = true} = parameters;

pushContextState(gl);
const webglState = WebGLStateTracker.get(gl);
webglState.push();
setGLParameters(gl, parameters);

// Setup is done, call the function
Expand All @@ -34,13 +35,13 @@ export function withGLParameters(
if (nocatch) {
// Avoid try catch to minimize stack size impact for safe execution paths
value = func(gl);
popContextState(gl);
webglState.pop();
} else {
// Wrap in a try-catch to ensure that parameters are restored on exceptions
try {
value = func(gl);
} finally {
popContextState(gl);
webglState.pop();
}
}

Expand Down
7 changes: 1 addition & 6 deletions modules/webgl/src/index.ts
Expand Up @@ -43,17 +43,12 @@ export {setDeviceParameters, withDeviceParameters} from './adapter/converters/de

// HELPERS - EXPERIMENTAL
export {getShaderLayout} from './adapter/helpers/get-shader-layout';
export {WebGLStateTracker} from './context/state-tracker/webgl-state-tracker';

// TEST EXPORTS
export {TEXTURE_FORMATS as _TEXTURE_FORMATS} from './adapter/converters/texture-formats';

// DEPRECATED TEST EXPORTS
// State tracking
export {
trackContextState,
pushContextState,
popContextState
} from './context/state-tracker/track-context-state';

export {
resetGLParameters,
Expand Down

0 comments on commit 32d9fff

Please sign in to comment.