Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(core): Clean up WebGLStateTracker #2087

Merged
merged 4 commits into from
Apr 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 8 additions & 10 deletions modules/webgl/src/adapter/converters/device-parameters.ts
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Loading
Loading