Replaces manual H264/TCP stereo streaming with NVIDIA CloudXR for
higher-quality stereoscopic rendering and lower latency.
Changes:
- teleop_xr_agent.py: add --cloudxr flag (enables Isaac Sim XR mode,
disables manual StreamingManager)
- deps/cloudxr/: NVIDIA CloudXR.js SDK (Early Access) with Isaac Lab
teleop React web client
- deps/cloudxr/Dockerfile.wss.proxy: HAProxy WSS proxy for PICO 4 Ultra
HTTPS mode (routes wss://48322 → ws://49100)
- deps/cloudxr/isaac/webpack.dev.js: disable file watching to avoid
EMFILE errors with large node_modules
- deps/cloudxr/INSTALL.md: full setup guide
Usage:
# Start CloudXR Runtime + Isaac Lab
cd ~/IsaacLab && ./docker/container.py start \
--files docker-compose.cloudxr-runtime.patch.yaml \
--env-file .env.cloudxr-runtime
# Run teleop with CloudXR
~/IsaacLab/isaaclab.sh -p teleop_xr_agent.py \
--task Isaac-MindRobot-2i-DualArm-IK-Abs-v0 --cloudxr
# Serve web client
cd deps/cloudxr/isaac && npm run dev-server:https
2686 lines
84 KiB
TypeScript
2686 lines
84 KiB
TypeScript
/**
|
|
* Sentinel value to represent undefined GL state.
|
|
* This allows distinguishing between "not set" (GLUndefined) and "set to null" (null).
|
|
*/
|
|
export const GLUndefined = {} as const;
|
|
|
|
/**
|
|
* Type representing the GLUndefined sentinel value.
|
|
*/
|
|
export type GLUndefined = typeof GLUndefined;
|
|
|
|
/**
|
|
* Checks if a value is defined (not GLUndefined).
|
|
* @param val - The value to check
|
|
* @returns true if the value is defined, false if it's GLUndefined
|
|
*/
|
|
export function isDefined(val: any): boolean {
|
|
return val !== GLUndefined;
|
|
}
|
|
|
|
/**
|
|
* WebGL maximum array sizes
|
|
* These are conservative minimum values guaranteed by the WebGL spec
|
|
*/
|
|
export const GL_MAX_VERTEX_ATTRIBS = 16; // Minimum guaranteed by WebGL 2.0
|
|
export const GL_MAX_UNIFORM_BUFFER_BINDINGS = 36; // Minimum guaranteed by WebGL 2.0
|
|
export const GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS = 4; // Minimum guaranteed by WebGL 2.0
|
|
export const GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS = 32; // Minimum guaranteed by WebGL 2.0
|
|
export const GL_MAX_COLOR_ATTACHMENTS = 8; // Minimum guaranteed by WebGL 2.0 (typically 4, but commonly 8+)
|
|
|
|
/**
|
|
* Generic dictionary/array container for indexed state with cloning support.
|
|
* Used for storing WebGL state indexed by number keys.
|
|
* @template T - The type of values stored in the dictionary, must have a clone() method
|
|
*/
|
|
export class GLAttributeArray<T extends { clone(): T; equals(other: T): boolean }> {
|
|
[index: number]: T | GLUndefined;
|
|
private _size: number;
|
|
|
|
constructor(size: number) {
|
|
this._size = size;
|
|
for (let i = 0; i < size; i++) {
|
|
this[i] = GLUndefined;
|
|
}
|
|
}
|
|
|
|
get(index: number): T | GLUndefined {
|
|
return this[index];
|
|
}
|
|
|
|
set(index: number, value: T | GLUndefined): void {
|
|
this[index] = value;
|
|
}
|
|
|
|
clone(): GLAttributeArray<T> {
|
|
const cloned = new GLAttributeArray<T>(this._size);
|
|
for (let i = 0; i < this._size; i++) {
|
|
cloned[i] = this[i] !== GLUndefined ? (this[i] as T).clone() : this[i];
|
|
}
|
|
return cloned;
|
|
}
|
|
|
|
equals(other: GLAttributeArray<T>): boolean {
|
|
if (this._size !== other._size) return false;
|
|
for (let i = 0; i < this._size; i++) {
|
|
const thisVal = this[i];
|
|
const otherVal = other[i];
|
|
if (thisVal === GLUndefined && otherVal === GLUndefined) continue;
|
|
if (thisVal === GLUndefined || otherVal === GLUndefined) return false;
|
|
if (!(thisVal as T).equals(otherVal as T)) return false;
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Generic dictionary container for string-keyed state with cloning support.
|
|
* Used for storing WebGL state indexed by string keys (e.g., texture unit names).
|
|
* @template T - The type of values stored in the dictionary, must have a clone() method
|
|
*/
|
|
export class GLUnitMap<T extends { clone(): T; equals(other: T): boolean }> {
|
|
[key: string]: T | any;
|
|
|
|
get(key: string): T | GLUndefined {
|
|
return this[key] !== undefined ? this[key] : GLUndefined;
|
|
}
|
|
|
|
set(key: string, value: T): void {
|
|
this[key] = value;
|
|
}
|
|
|
|
clone(): GLUnitMap<T> {
|
|
const cloned = new GLUnitMap<T>();
|
|
for (const [key, value] of Object.entries(this)) {
|
|
if (value && typeof value === 'object' && 'clone' in value) {
|
|
cloned[key] = value.clone();
|
|
}
|
|
}
|
|
return cloned;
|
|
}
|
|
|
|
equals(other: GLUnitMap<T>): boolean {
|
|
const thisKeys = Object.keys(this);
|
|
const otherKeys = Object.keys(other);
|
|
if (thisKeys.length !== otherKeys.length) return false;
|
|
return thisKeys.every(key => {
|
|
const thisVal = this.get(key);
|
|
const otherVal = other.get(key);
|
|
if (thisVal === GLUndefined && otherVal === GLUndefined) return true;
|
|
if (thisVal === GLUndefined || otherVal === GLUndefined) return false;
|
|
return (thisVal as T).equals(otherVal as T);
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* WebGL State interfaces and state-only WebGL context
|
|
*
|
|
* This file contains the comprehensive WebGL state structure and a WebGLStateTracker
|
|
* class that mimics the WebGL2RenderingContext interface but only updates state
|
|
* without making actual WebGL calls.
|
|
*
|
|
* IMPORTANT: This tracker only tracks the state associated with DEFAULT/NULL objects.
|
|
*
|
|
* Per the WebGL 2.0 spec, state falls into three categories:
|
|
*
|
|
* 1. ALWAYS TRACKED (Global Context State):
|
|
* - Active texture unit (ACTIVE_TEXTURE)
|
|
* - Bound objects: VAO, program, framebuffer, renderbuffer, transform feedback
|
|
* - Buffer bindings: ARRAY_BUFFER, UNIFORM_BUFFER, etc. (except ELEMENT_ARRAY_BUFFER)
|
|
* - Texture bindings per unit (TEXTURE_BINDING_2D, etc.)
|
|
* - Viewport and scissor box
|
|
* - Clear colors (color, depth, stencil)
|
|
* - Enable/disable capabilities (BLEND, DEPTH_TEST, etc.)
|
|
* - Pixel store parameters (PACK_ALIGNMENT, UNPACK_FLIP_Y_WEBGL, etc.)
|
|
* - Blend state (equations, functions, color)
|
|
* - Depth state (func, range, mask)
|
|
* - Stencil state (func, ops, masks)
|
|
* - Color write mask
|
|
* - Cull face mode and front face
|
|
* - Line width
|
|
* - Polygon offset
|
|
* - Sample coverage
|
|
*
|
|
* 2. ONLY TRACKED WHEN DEFAULT VAO IS BOUND (Per-VAO State):
|
|
* - ELEMENT_ARRAY_BUFFER binding
|
|
* - Vertex attribute arrays (enabled/disabled per attribute index)
|
|
* - Vertex attribute pointers (buffer binding, size, type, stride, offset, normalized, divisor)
|
|
*
|
|
* IMPORTANT: ARRAY_BUFFER is NOT per-VAO! It's always global.
|
|
* However, the buffer associated with each vertex attribute (captured when
|
|
* vertexAttribPointer is called) IS per-VAO state.
|
|
*
|
|
* Note: When a non-default VAO is bound, we DON'T track these as we don't maintain
|
|
* per-VAO state. State updates to these will be silently accepted but not tracked.
|
|
*
|
|
* 3. ONLY TRACKED WHEN DEFAULT FRAMEBUFFER IS BOUND (Per-Framebuffer State):
|
|
* - Attachments (COLOR_ATTACHMENT0-15, DEPTH_ATTACHMENT, STENCIL_ATTACHMENT, DEPTH_STENCIL_ATTACHMENT)
|
|
* via framebufferTexture2D, framebufferRenderbuffer, framebufferTextureLayer
|
|
* - Draw buffers (drawBuffers) - which color attachments are written to
|
|
* - Read buffer (readBuffer) - which color attachment is read from
|
|
* Note: When a non-default framebuffer is bound, we DON'T track these as we don't
|
|
* maintain per-framebuffer state. State updates will be silently accepted but not tracked.
|
|
*
|
|
* 4. INDEXED BUFFER BINDINGS (Global Context State, Always Tracked):
|
|
* - Uniform buffer indexed bindings (bindBufferBase/Range with UNIFORM_BUFFER)
|
|
* - Transform feedback buffer indexed bindings (bindBufferBase/Range with TRANSFORM_FEEDBACK_BUFFER)
|
|
* Note: These are GLOBAL state, not per-object. bindBufferBase/Range updates BOTH
|
|
* the indexed binding and the generic binding.
|
|
*
|
|
* This is a deliberate simplification for tracking "current active context state" without
|
|
* the complexity of per-object state management.
|
|
*/
|
|
|
|
/**
|
|
* Comprehensive WebGL state structure
|
|
*/
|
|
export class WebGLTextureUnitState {
|
|
texture2D: WebGLTexture | null | GLUndefined = GLUndefined;
|
|
textureCubeMap: WebGLTexture | null | GLUndefined = GLUndefined;
|
|
texture3D: WebGLTexture | null | GLUndefined = GLUndefined;
|
|
texture2DArray: WebGLTexture | null | GLUndefined = GLUndefined;
|
|
|
|
clone(): WebGLTextureUnitState {
|
|
const cloned = new WebGLTextureUnitState();
|
|
cloned.texture2D = this.texture2D;
|
|
cloned.textureCubeMap = this.textureCubeMap;
|
|
cloned.texture3D = this.texture3D;
|
|
cloned.texture2DArray = this.texture2DArray;
|
|
return cloned;
|
|
}
|
|
|
|
equals(other: WebGLTextureUnitState): boolean {
|
|
return (
|
|
this.texture2D === other.texture2D &&
|
|
this.textureCubeMap === other.textureCubeMap &&
|
|
this.texture3D === other.texture3D &&
|
|
this.texture2DArray === other.texture2DArray
|
|
);
|
|
}
|
|
}
|
|
|
|
export class WebGLTextureState {
|
|
activeTexture: number | GLUndefined = GLUndefined;
|
|
textureUnits = new GLUnitMap<WebGLTextureUnitState>();
|
|
|
|
clone(): WebGLTextureState {
|
|
const cloned = new WebGLTextureState();
|
|
cloned.activeTexture = this.activeTexture;
|
|
cloned.textureUnits = this.textureUnits.clone();
|
|
return cloned;
|
|
}
|
|
|
|
equals(other: WebGLTextureState): boolean {
|
|
return (
|
|
this.activeTexture === other.activeTexture && this.textureUnits.equals(other.textureUnits)
|
|
);
|
|
}
|
|
}
|
|
|
|
export class WebGLIndexedBufferBinding {
|
|
buffer: WebGLBuffer | null | GLUndefined = GLUndefined;
|
|
offset: number | GLUndefined = GLUndefined;
|
|
size: number | GLUndefined = GLUndefined;
|
|
|
|
clone(): WebGLIndexedBufferBinding {
|
|
const cloned = new WebGLIndexedBufferBinding();
|
|
cloned.buffer = this.buffer;
|
|
cloned.offset = this.offset;
|
|
cloned.size = this.size;
|
|
return cloned;
|
|
}
|
|
|
|
equals(other: WebGLIndexedBufferBinding): boolean {
|
|
return this.buffer === other.buffer && this.offset === other.offset && this.size === other.size;
|
|
}
|
|
}
|
|
|
|
export class WebGLBufferState {
|
|
arrayBuffer: WebGLBuffer | null | GLUndefined = GLUndefined;
|
|
|
|
// Note: ELEMENT_ARRAY_BUFFER is NOT stored here!
|
|
// Per OpenGL ES 3.0 spec section 2.10, ELEMENT_ARRAY_BUFFER binding is per-VAO state.
|
|
// It is stored in WebGLPerVAOState.elementArrayBuffer instead.
|
|
|
|
// Generic bindings (also affected by bindBufferBase/Range)
|
|
uniformBuffer: WebGLBuffer | null | GLUndefined = GLUndefined;
|
|
transformFeedbackBuffer: WebGLBuffer | null | GLUndefined = GLUndefined;
|
|
|
|
// Indexed bindings (WebGL 2.0)
|
|
// These are global context state, but bindBufferBase/Range also updates the generic binding
|
|
uniformBufferBindings = new GLAttributeArray<WebGLIndexedBufferBinding>(
|
|
GL_MAX_UNIFORM_BUFFER_BINDINGS
|
|
);
|
|
transformFeedbackBufferBindings = new GLAttributeArray<WebGLIndexedBufferBinding>(
|
|
GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS
|
|
);
|
|
|
|
// Other global bindings
|
|
pixelPackBuffer: WebGLBuffer | null | GLUndefined = GLUndefined;
|
|
pixelUnpackBuffer: WebGLBuffer | null | GLUndefined = GLUndefined;
|
|
copyReadBuffer: WebGLBuffer | null | GLUndefined = GLUndefined;
|
|
copyWriteBuffer: WebGLBuffer | null | GLUndefined = GLUndefined;
|
|
|
|
clone(): WebGLBufferState {
|
|
const cloned = new WebGLBufferState();
|
|
cloned.arrayBuffer = this.arrayBuffer;
|
|
cloned.uniformBuffer = this.uniformBuffer;
|
|
cloned.transformFeedbackBuffer = this.transformFeedbackBuffer;
|
|
cloned.pixelPackBuffer = this.pixelPackBuffer;
|
|
cloned.pixelUnpackBuffer = this.pixelUnpackBuffer;
|
|
cloned.copyReadBuffer = this.copyReadBuffer;
|
|
cloned.copyWriteBuffer = this.copyWriteBuffer;
|
|
cloned.uniformBufferBindings = this.uniformBufferBindings.clone();
|
|
cloned.transformFeedbackBufferBindings = this.transformFeedbackBufferBindings.clone();
|
|
return cloned;
|
|
}
|
|
|
|
equals(other: WebGLBufferState): boolean {
|
|
return (
|
|
this.arrayBuffer === other.arrayBuffer &&
|
|
this.uniformBuffer === other.uniformBuffer &&
|
|
this.transformFeedbackBuffer === other.transformFeedbackBuffer &&
|
|
this.pixelPackBuffer === other.pixelPackBuffer &&
|
|
this.pixelUnpackBuffer === other.pixelUnpackBuffer &&
|
|
this.copyReadBuffer === other.copyReadBuffer &&
|
|
this.copyWriteBuffer === other.copyWriteBuffer &&
|
|
this.uniformBufferBindings.equals(other.uniformBufferBindings) &&
|
|
this.transformFeedbackBufferBindings.equals(other.transformFeedbackBufferBindings)
|
|
);
|
|
}
|
|
}
|
|
|
|
export class WebGLProgramState {
|
|
currentProgram: WebGLProgram | null | GLUndefined = GLUndefined;
|
|
|
|
clone(): WebGLProgramState {
|
|
const cloned = new WebGLProgramState();
|
|
cloned.currentProgram = this.currentProgram;
|
|
return cloned;
|
|
}
|
|
|
|
equals(other: WebGLProgramState): boolean {
|
|
return this.currentProgram === other.currentProgram;
|
|
}
|
|
}
|
|
|
|
export class WebGLFramebufferAttachment {
|
|
attachmentType: 'texture' | 'renderbuffer' | null | GLUndefined = GLUndefined;
|
|
texture: WebGLTexture | null | GLUndefined = GLUndefined;
|
|
renderbuffer: WebGLRenderbuffer | null | GLUndefined = GLUndefined;
|
|
textureLevel: number | GLUndefined = GLUndefined;
|
|
textureLayer: number | GLUndefined = GLUndefined; // For 3D textures or texture arrays
|
|
textureCubeFace: number | GLUndefined = GLUndefined; // For cubemap faces
|
|
|
|
clone(): WebGLFramebufferAttachment {
|
|
const cloned = new WebGLFramebufferAttachment();
|
|
cloned.attachmentType = this.attachmentType;
|
|
cloned.texture = this.texture;
|
|
cloned.renderbuffer = this.renderbuffer;
|
|
cloned.textureLevel = this.textureLevel;
|
|
cloned.textureLayer = this.textureLayer;
|
|
cloned.textureCubeFace = this.textureCubeFace;
|
|
return cloned;
|
|
}
|
|
|
|
equals(other: WebGLFramebufferAttachment): boolean {
|
|
return (
|
|
this.attachmentType === other.attachmentType &&
|
|
this.texture === other.texture &&
|
|
this.renderbuffer === other.renderbuffer &&
|
|
this.textureLevel === other.textureLevel &&
|
|
this.textureLayer === other.textureLayer &&
|
|
this.textureCubeFace === other.textureCubeFace
|
|
);
|
|
}
|
|
}
|
|
|
|
export class WebGLFramebufferAttachments {
|
|
// Color attachments (typically 0-7, but can query MAX_COLOR_ATTACHMENTS)
|
|
[key: string]: WebGLFramebufferAttachment; // e.g., "COLOR_ATTACHMENT0"
|
|
}
|
|
|
|
export class WebGLDefaultFramebufferState {
|
|
// Attachments for the default framebuffer (only valid when framebuffer === null)
|
|
colorAttachments: WebGLFramebufferAttachments = new WebGLFramebufferAttachments();
|
|
depthAttachment: WebGLFramebufferAttachment | null | GLUndefined = GLUndefined;
|
|
stencilAttachment: WebGLFramebufferAttachment | null | GLUndefined = GLUndefined;
|
|
depthStencilAttachment: WebGLFramebufferAttachment | null | GLUndefined = GLUndefined;
|
|
// Draw buffers array (which color attachments are written to)
|
|
drawBuffers: number[] | GLUndefined = GLUndefined;
|
|
// Read buffer (which color attachment is read from)
|
|
readBuffer: number | GLUndefined = GLUndefined;
|
|
|
|
clone(): WebGLDefaultFramebufferState {
|
|
const cloned = new WebGLDefaultFramebufferState();
|
|
// Clone color attachments
|
|
for (const [key, value] of Object.entries(this.colorAttachments)) {
|
|
if (value instanceof WebGLFramebufferAttachment) {
|
|
cloned.colorAttachments[key] = value.clone();
|
|
}
|
|
}
|
|
cloned.depthAttachment = this.depthAttachment;
|
|
cloned.stencilAttachment = this.stencilAttachment;
|
|
cloned.depthStencilAttachment = this.depthStencilAttachment;
|
|
cloned.drawBuffers = Array.isArray(this.drawBuffers) ? [...this.drawBuffers] : this.drawBuffers;
|
|
cloned.readBuffer = this.readBuffer;
|
|
return cloned;
|
|
}
|
|
|
|
equals(other: WebGLDefaultFramebufferState): boolean {
|
|
// Compare color attachments
|
|
const thisKeys = Object.keys(this.colorAttachments);
|
|
const otherKeys = Object.keys(other.colorAttachments);
|
|
if (thisKeys.length !== otherKeys.length) return false;
|
|
const colorAttachmentsEqual = thisKeys.every(key => {
|
|
const thisAttach = this.colorAttachments[key];
|
|
const otherAttach = other.colorAttachments[key];
|
|
if (!thisAttach || !otherAttach) return thisAttach === otherAttach;
|
|
return thisAttach.equals(otherAttach);
|
|
});
|
|
|
|
// Compare draw buffers
|
|
const drawBuffersEqual =
|
|
this.drawBuffers === other.drawBuffers ||
|
|
(Array.isArray(this.drawBuffers) &&
|
|
Array.isArray(other.drawBuffers) &&
|
|
this.drawBuffers.length === other.drawBuffers.length &&
|
|
this.drawBuffers.every((v, i) => v === (other.drawBuffers as number[])[i]));
|
|
|
|
return (
|
|
colorAttachmentsEqual &&
|
|
this.depthAttachment === other.depthAttachment &&
|
|
this.stencilAttachment === other.stencilAttachment &&
|
|
this.depthStencilAttachment === other.depthStencilAttachment &&
|
|
drawBuffersEqual &&
|
|
this.readBuffer === other.readBuffer
|
|
);
|
|
}
|
|
}
|
|
|
|
export class WebGLFramebufferState {
|
|
drawFramebuffer: WebGLFramebuffer | null | GLUndefined = GLUndefined;
|
|
readFramebuffer: WebGLFramebuffer | null | GLUndefined = GLUndefined;
|
|
framebuffer: WebGLFramebuffer | null | GLUndefined = GLUndefined;
|
|
// State for the default framebuffer (only tracked when framebuffer === null)
|
|
defaultFramebufferState: WebGLDefaultFramebufferState = new WebGLDefaultFramebufferState();
|
|
|
|
clone(): WebGLFramebufferState {
|
|
const cloned = new WebGLFramebufferState();
|
|
cloned.drawFramebuffer = this.drawFramebuffer;
|
|
cloned.readFramebuffer = this.readFramebuffer;
|
|
cloned.framebuffer = this.framebuffer;
|
|
cloned.defaultFramebufferState = this.defaultFramebufferState.clone();
|
|
return cloned;
|
|
}
|
|
|
|
equals(other: WebGLFramebufferState): boolean {
|
|
return (
|
|
this.drawFramebuffer === other.drawFramebuffer &&
|
|
this.readFramebuffer === other.readFramebuffer &&
|
|
this.framebuffer === other.framebuffer &&
|
|
this.defaultFramebufferState.equals(other.defaultFramebufferState)
|
|
);
|
|
}
|
|
}
|
|
|
|
export class WebGLVertexAttribState {
|
|
enabled: boolean | GLUndefined = GLUndefined;
|
|
buffer: WebGLBuffer | null | GLUndefined = GLUndefined;
|
|
size: number | GLUndefined = GLUndefined;
|
|
type: number | GLUndefined = GLUndefined;
|
|
normalized: boolean | GLUndefined = GLUndefined;
|
|
stride: number | GLUndefined = GLUndefined;
|
|
offset: number | GLUndefined = GLUndefined;
|
|
divisor: number | GLUndefined = GLUndefined;
|
|
|
|
clone(): WebGLVertexAttribState {
|
|
const cloned = new WebGLVertexAttribState();
|
|
cloned.enabled = this.enabled;
|
|
cloned.buffer = this.buffer;
|
|
cloned.size = this.size;
|
|
cloned.type = this.type;
|
|
cloned.normalized = this.normalized;
|
|
cloned.stride = this.stride;
|
|
cloned.offset = this.offset;
|
|
cloned.divisor = this.divisor;
|
|
return cloned;
|
|
}
|
|
|
|
equals(other: WebGLVertexAttribState): boolean {
|
|
return (
|
|
this.enabled === other.enabled &&
|
|
this.buffer === other.buffer &&
|
|
this.size === other.size &&
|
|
this.type === other.type &&
|
|
this.normalized === other.normalized &&
|
|
this.stride === other.stride &&
|
|
this.offset === other.offset &&
|
|
this.divisor === other.divisor
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Per-VAO state - according to OpenGL ES 3.0 spec section 2.10,
|
|
* ELEMENT_ARRAY_BUFFER binding is part of VAO state
|
|
*/
|
|
export class WebGLPerVAOState {
|
|
// ELEMENT_ARRAY_BUFFER binding is per-VAO (spec: section 2.10, table 6.2)
|
|
elementArrayBuffer: WebGLBuffer | null | GLUndefined = GLUndefined;
|
|
// Vertex attribute state is also per-VAO
|
|
attributes = new GLAttributeArray<WebGLVertexAttribState>(GL_MAX_VERTEX_ATTRIBS);
|
|
|
|
clone(): WebGLPerVAOState {
|
|
const cloned = new WebGLPerVAOState();
|
|
cloned.elementArrayBuffer = this.elementArrayBuffer;
|
|
cloned.attributes = this.attributes.clone();
|
|
return cloned;
|
|
}
|
|
|
|
equals(other: WebGLPerVAOState): boolean {
|
|
return (
|
|
this.elementArrayBuffer === other.elementArrayBuffer &&
|
|
this.attributes.equals(other.attributes)
|
|
);
|
|
}
|
|
}
|
|
|
|
export class WebGLVertexArrayState {
|
|
// Currently bound VAO (null = default VAO)
|
|
vertexArrayObject: WebGLVertexArrayObject | null | GLUndefined = GLUndefined;
|
|
|
|
// Per-VAO state storage
|
|
// Key: VAO object (null represents default VAO)
|
|
// Value: Per-VAO state including ELEMENT_ARRAY_BUFFER and attributes
|
|
vaoStates: Map<WebGLVertexArrayObject | null, WebGLPerVAOState> = new Map();
|
|
|
|
clone(): WebGLVertexArrayState {
|
|
const cloned = new WebGLVertexArrayState();
|
|
cloned.vertexArrayObject = this.vertexArrayObject;
|
|
|
|
// Clone all VAO states
|
|
for (const [vao, state] of this.vaoStates.entries()) {
|
|
cloned.vaoStates.set(vao, state.clone());
|
|
}
|
|
|
|
return cloned;
|
|
}
|
|
|
|
equals(other: WebGLVertexArrayState): boolean {
|
|
if (this.vertexArrayObject !== other.vertexArrayObject) return false;
|
|
if (this.vaoStates.size !== other.vaoStates.size) return false;
|
|
|
|
// Compare all VAO states
|
|
for (const [vao, state] of this.vaoStates.entries()) {
|
|
const otherState = other.vaoStates.get(vao);
|
|
if (!otherState || !state.equals(otherState)) return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Get or create the state for the currently bound VAO
|
|
*/
|
|
getCurrentVAOState(): WebGLPerVAOState {
|
|
const vao = this.vertexArrayObject === GLUndefined ? null : this.vertexArrayObject;
|
|
|
|
if (!this.vaoStates.has(vao)) {
|
|
this.vaoStates.set(vao, new WebGLPerVAOState());
|
|
}
|
|
|
|
return this.vaoStates.get(vao)!;
|
|
}
|
|
}
|
|
|
|
export class WebGLViewportState {
|
|
viewport: Int32Array | GLUndefined = GLUndefined;
|
|
scissorBox: Int32Array | GLUndefined = GLUndefined;
|
|
|
|
clone(): WebGLViewportState {
|
|
const cloned = new WebGLViewportState();
|
|
cloned.viewport =
|
|
this.viewport !== GLUndefined ? new Int32Array(this.viewport as Int32Array) : this.viewport;
|
|
cloned.scissorBox =
|
|
this.scissorBox !== GLUndefined
|
|
? new Int32Array(this.scissorBox as Int32Array)
|
|
: this.scissorBox;
|
|
return cloned;
|
|
}
|
|
|
|
equals(other: WebGLViewportState): boolean {
|
|
const viewportEqual =
|
|
this.viewport === other.viewport ||
|
|
(this.viewport !== GLUndefined &&
|
|
other.viewport !== GLUndefined &&
|
|
(this.viewport as Int32Array).every((v, i) => v === (other.viewport as Int32Array)[i]));
|
|
const scissorEqual =
|
|
this.scissorBox === other.scissorBox ||
|
|
(this.scissorBox !== GLUndefined &&
|
|
other.scissorBox !== GLUndefined &&
|
|
(this.scissorBox as Int32Array).every((v, i) => v === (other.scissorBox as Int32Array)[i]));
|
|
return viewportEqual && scissorEqual;
|
|
}
|
|
}
|
|
|
|
export class WebGLClearState {
|
|
colorClearValue: Float32Array | GLUndefined = GLUndefined;
|
|
depthClearValue: number | GLUndefined = GLUndefined;
|
|
stencilClearValue: number | GLUndefined = GLUndefined;
|
|
|
|
clone(): WebGLClearState {
|
|
const cloned = new WebGLClearState();
|
|
cloned.colorClearValue =
|
|
this.colorClearValue !== GLUndefined
|
|
? new Float32Array(this.colorClearValue as Float32Array)
|
|
: this.colorClearValue;
|
|
cloned.depthClearValue = this.depthClearValue;
|
|
cloned.stencilClearValue = this.stencilClearValue;
|
|
return cloned;
|
|
}
|
|
|
|
equals(other: WebGLClearState): boolean {
|
|
const colorEqual =
|
|
this.colorClearValue === other.colorClearValue ||
|
|
(this.colorClearValue !== GLUndefined &&
|
|
other.colorClearValue !== GLUndefined &&
|
|
(this.colorClearValue as Float32Array).every(
|
|
(v, i) => v === (other.colorClearValue as Float32Array)[i]
|
|
));
|
|
return (
|
|
colorEqual &&
|
|
this.depthClearValue === other.depthClearValue &&
|
|
this.stencilClearValue === other.stencilClearValue
|
|
);
|
|
}
|
|
}
|
|
|
|
export class WebGLCapabilityState {
|
|
blend: boolean | GLUndefined = GLUndefined;
|
|
cullFace: boolean | GLUndefined = GLUndefined;
|
|
depthTest: boolean | GLUndefined = GLUndefined;
|
|
dither: boolean | GLUndefined = GLUndefined;
|
|
polygonOffsetFill: boolean | GLUndefined = GLUndefined;
|
|
sampleAlphaToCoverage: boolean | GLUndefined = GLUndefined;
|
|
sampleCoverage: boolean | GLUndefined = GLUndefined;
|
|
scissorTest: boolean | GLUndefined = GLUndefined;
|
|
stencilTest: boolean | GLUndefined = GLUndefined;
|
|
rasterDiscard: boolean | GLUndefined = GLUndefined;
|
|
|
|
clone(): WebGLCapabilityState {
|
|
const cloned = new WebGLCapabilityState();
|
|
Object.assign(cloned, this);
|
|
return cloned;
|
|
}
|
|
|
|
equals(other: WebGLCapabilityState): boolean {
|
|
return (
|
|
this.blend === other.blend &&
|
|
this.cullFace === other.cullFace &&
|
|
this.depthTest === other.depthTest &&
|
|
this.dither === other.dither &&
|
|
this.polygonOffsetFill === other.polygonOffsetFill &&
|
|
this.sampleAlphaToCoverage === other.sampleAlphaToCoverage &&
|
|
this.sampleCoverage === other.sampleCoverage &&
|
|
this.scissorTest === other.scissorTest &&
|
|
this.stencilTest === other.stencilTest &&
|
|
this.rasterDiscard === other.rasterDiscard
|
|
);
|
|
}
|
|
}
|
|
|
|
export class WebGLPixelStoreState {
|
|
packAlignment: number | GLUndefined = GLUndefined;
|
|
unpackAlignment: number | GLUndefined = GLUndefined;
|
|
unpackFlipY: boolean | GLUndefined = GLUndefined;
|
|
unpackPremultiplyAlpha: boolean | GLUndefined = GLUndefined;
|
|
packRowLength: number | GLUndefined = GLUndefined;
|
|
packSkipPixels: number | GLUndefined = GLUndefined;
|
|
packSkipRows: number | GLUndefined = GLUndefined;
|
|
unpackRowLength: number | GLUndefined = GLUndefined;
|
|
unpackImageHeight: number | GLUndefined = GLUndefined;
|
|
unpackSkipPixels: number | GLUndefined = GLUndefined;
|
|
unpackSkipRows: number | GLUndefined = GLUndefined;
|
|
unpackSkipImages: number | GLUndefined = GLUndefined;
|
|
|
|
clone(): WebGLPixelStoreState {
|
|
const cloned = new WebGLPixelStoreState();
|
|
Object.assign(cloned, this);
|
|
return cloned;
|
|
}
|
|
|
|
equals(other: WebGLPixelStoreState): boolean {
|
|
return (
|
|
this.packAlignment === other.packAlignment &&
|
|
this.unpackAlignment === other.unpackAlignment &&
|
|
this.unpackFlipY === other.unpackFlipY &&
|
|
this.unpackPremultiplyAlpha === other.unpackPremultiplyAlpha &&
|
|
this.packRowLength === other.packRowLength &&
|
|
this.packSkipPixels === other.packSkipPixels &&
|
|
this.packSkipRows === other.packSkipRows &&
|
|
this.unpackRowLength === other.unpackRowLength &&
|
|
this.unpackImageHeight === other.unpackImageHeight &&
|
|
this.unpackSkipPixels === other.unpackSkipPixels &&
|
|
this.unpackSkipRows === other.unpackSkipRows &&
|
|
this.unpackSkipImages === other.unpackSkipImages
|
|
);
|
|
}
|
|
}
|
|
|
|
export class WebGLBlendState {
|
|
blendEquationRgb: number | GLUndefined = GLUndefined;
|
|
blendEquationAlpha: number | GLUndefined = GLUndefined;
|
|
blendSrcRgb: number | GLUndefined = GLUndefined;
|
|
blendDstRgb: number | GLUndefined = GLUndefined;
|
|
blendSrcAlpha: number | GLUndefined = GLUndefined;
|
|
blendDstAlpha: number | GLUndefined = GLUndefined;
|
|
blendColor: Float32Array | GLUndefined = GLUndefined;
|
|
|
|
clone(): WebGLBlendState {
|
|
const cloned = new WebGLBlendState();
|
|
cloned.blendEquationRgb = this.blendEquationRgb;
|
|
cloned.blendEquationAlpha = this.blendEquationAlpha;
|
|
cloned.blendSrcRgb = this.blendSrcRgb;
|
|
cloned.blendDstRgb = this.blendDstRgb;
|
|
cloned.blendSrcAlpha = this.blendSrcAlpha;
|
|
cloned.blendDstAlpha = this.blendDstAlpha;
|
|
cloned.blendColor =
|
|
this.blendColor !== GLUndefined
|
|
? new Float32Array(this.blendColor as Float32Array)
|
|
: this.blendColor;
|
|
return cloned;
|
|
}
|
|
|
|
equals(other: WebGLBlendState): boolean {
|
|
const blendColorEqual =
|
|
this.blendColor === other.blendColor ||
|
|
(this.blendColor !== GLUndefined &&
|
|
other.blendColor !== GLUndefined &&
|
|
(this.blendColor as Float32Array).every(
|
|
(v, i) => v === (other.blendColor as Float32Array)[i]
|
|
));
|
|
return (
|
|
this.blendEquationRgb === other.blendEquationRgb &&
|
|
this.blendEquationAlpha === other.blendEquationAlpha &&
|
|
this.blendSrcRgb === other.blendSrcRgb &&
|
|
this.blendDstRgb === other.blendDstRgb &&
|
|
this.blendSrcAlpha === other.blendSrcAlpha &&
|
|
this.blendDstAlpha === other.blendDstAlpha &&
|
|
blendColorEqual
|
|
);
|
|
}
|
|
}
|
|
|
|
export class WebGLDepthState {
|
|
depthFunc: number | GLUndefined = GLUndefined;
|
|
depthRange: Float32Array | GLUndefined = GLUndefined;
|
|
depthWritemask: boolean | GLUndefined = GLUndefined;
|
|
|
|
clone(): WebGLDepthState {
|
|
const cloned = new WebGLDepthState();
|
|
cloned.depthFunc = this.depthFunc;
|
|
cloned.depthRange =
|
|
this.depthRange !== GLUndefined
|
|
? new Float32Array(this.depthRange as Float32Array)
|
|
: this.depthRange;
|
|
cloned.depthWritemask = this.depthWritemask;
|
|
return cloned;
|
|
}
|
|
|
|
equals(other: WebGLDepthState): boolean {
|
|
const depthRangeEqual =
|
|
this.depthRange === other.depthRange ||
|
|
(this.depthRange !== GLUndefined &&
|
|
other.depthRange !== GLUndefined &&
|
|
(this.depthRange as Float32Array).every(
|
|
(v, i) => v === (other.depthRange as Float32Array)[i]
|
|
));
|
|
return (
|
|
this.depthFunc === other.depthFunc &&
|
|
depthRangeEqual &&
|
|
this.depthWritemask === other.depthWritemask
|
|
);
|
|
}
|
|
}
|
|
|
|
export class WebGLStencilState {
|
|
stencilFunc: number | GLUndefined = GLUndefined;
|
|
stencilRef: number | GLUndefined = GLUndefined;
|
|
stencilValueMask: number | GLUndefined = GLUndefined;
|
|
stencilWritemask: number | GLUndefined = GLUndefined;
|
|
stencilFail: number | GLUndefined = GLUndefined;
|
|
stencilPassDepthFail: number | GLUndefined = GLUndefined;
|
|
stencilPassDepthPass: number | GLUndefined = GLUndefined;
|
|
stencilBackFunc: number | GLUndefined = GLUndefined;
|
|
stencilBackRef: number | GLUndefined = GLUndefined;
|
|
stencilBackValueMask: number | GLUndefined = GLUndefined;
|
|
stencilBackWritemask: number | GLUndefined = GLUndefined;
|
|
stencilBackFail: number | GLUndefined = GLUndefined;
|
|
stencilBackPassDepthFail: number | GLUndefined = GLUndefined;
|
|
stencilBackPassDepthPass: number | GLUndefined = GLUndefined;
|
|
|
|
clone(): WebGLStencilState {
|
|
const cloned = new WebGLStencilState();
|
|
Object.assign(cloned, this);
|
|
return cloned;
|
|
}
|
|
|
|
equals(other: WebGLStencilState): boolean {
|
|
return (
|
|
this.stencilFunc === other.stencilFunc &&
|
|
this.stencilRef === other.stencilRef &&
|
|
this.stencilValueMask === other.stencilValueMask &&
|
|
this.stencilWritemask === other.stencilWritemask &&
|
|
this.stencilFail === other.stencilFail &&
|
|
this.stencilPassDepthFail === other.stencilPassDepthFail &&
|
|
this.stencilPassDepthPass === other.stencilPassDepthPass &&
|
|
this.stencilBackFunc === other.stencilBackFunc &&
|
|
this.stencilBackRef === other.stencilBackRef &&
|
|
this.stencilBackValueMask === other.stencilBackValueMask &&
|
|
this.stencilBackWritemask === other.stencilBackWritemask &&
|
|
this.stencilBackFail === other.stencilBackFail &&
|
|
this.stencilBackPassDepthFail === other.stencilBackPassDepthFail &&
|
|
this.stencilBackPassDepthPass === other.stencilBackPassDepthPass
|
|
);
|
|
}
|
|
}
|
|
|
|
export class WebGLColorState {
|
|
colorWritemask: boolean[] | GLUndefined = GLUndefined;
|
|
|
|
clone(): WebGLColorState {
|
|
const cloned = new WebGLColorState();
|
|
cloned.colorWritemask =
|
|
this.colorWritemask !== GLUndefined
|
|
? [...(this.colorWritemask as boolean[])]
|
|
: this.colorWritemask;
|
|
return cloned;
|
|
}
|
|
|
|
equals(other: WebGLColorState): boolean {
|
|
return (
|
|
this.colorWritemask === other.colorWritemask ||
|
|
(this.colorWritemask !== GLUndefined &&
|
|
other.colorWritemask !== GLUndefined &&
|
|
(this.colorWritemask as boolean[]).every(
|
|
(v, i) => v === (other.colorWritemask as boolean[])[i]
|
|
))
|
|
);
|
|
}
|
|
}
|
|
|
|
export class WebGLCullingState {
|
|
cullFaceMode: number | GLUndefined = GLUndefined;
|
|
frontFace: number | GLUndefined = GLUndefined;
|
|
|
|
clone(): WebGLCullingState {
|
|
const cloned = new WebGLCullingState();
|
|
Object.assign(cloned, this);
|
|
return cloned;
|
|
}
|
|
|
|
equals(other: WebGLCullingState): boolean {
|
|
return this.cullFaceMode === other.cullFaceMode && this.frontFace === other.frontFace;
|
|
}
|
|
}
|
|
|
|
export class WebGLLineState {
|
|
lineWidth: number | GLUndefined = GLUndefined;
|
|
|
|
clone(): WebGLLineState {
|
|
const cloned = new WebGLLineState();
|
|
cloned.lineWidth = this.lineWidth;
|
|
return cloned;
|
|
}
|
|
|
|
equals(other: WebGLLineState): boolean {
|
|
return this.lineWidth === other.lineWidth;
|
|
}
|
|
}
|
|
|
|
export class WebGLPolygonOffsetState {
|
|
polygonOffsetFactor: number | GLUndefined = GLUndefined;
|
|
polygonOffsetUnits: number | GLUndefined = GLUndefined;
|
|
|
|
clone(): WebGLPolygonOffsetState {
|
|
const cloned = new WebGLPolygonOffsetState();
|
|
Object.assign(cloned, this);
|
|
return cloned;
|
|
}
|
|
|
|
equals(other: WebGLPolygonOffsetState): boolean {
|
|
return (
|
|
this.polygonOffsetFactor === other.polygonOffsetFactor &&
|
|
this.polygonOffsetUnits === other.polygonOffsetUnits
|
|
);
|
|
}
|
|
}
|
|
|
|
export class WebGLSampleState {
|
|
sampleCoverageValue: number | GLUndefined = GLUndefined;
|
|
sampleCoverageInvert: boolean | GLUndefined = GLUndefined;
|
|
|
|
clone(): WebGLSampleState {
|
|
const cloned = new WebGLSampleState();
|
|
Object.assign(cloned, this);
|
|
return cloned;
|
|
}
|
|
|
|
equals(other: WebGLSampleState): boolean {
|
|
return (
|
|
this.sampleCoverageValue === other.sampleCoverageValue &&
|
|
this.sampleCoverageInvert === other.sampleCoverageInvert
|
|
);
|
|
}
|
|
}
|
|
|
|
export class WebGLTransformFeedbackState {
|
|
transformFeedback: WebGLTransformFeedback | null | GLUndefined = GLUndefined;
|
|
transformFeedbackActive: boolean | GLUndefined = GLUndefined;
|
|
transformFeedbackPaused: boolean | GLUndefined = GLUndefined;
|
|
|
|
clone(): WebGLTransformFeedbackState {
|
|
const cloned = new WebGLTransformFeedbackState();
|
|
Object.assign(cloned, this);
|
|
return cloned;
|
|
}
|
|
|
|
equals(other: WebGLTransformFeedbackState): boolean {
|
|
return (
|
|
this.transformFeedback === other.transformFeedback &&
|
|
this.transformFeedbackActive === other.transformFeedbackActive &&
|
|
this.transformFeedbackPaused === other.transformFeedbackPaused
|
|
);
|
|
}
|
|
}
|
|
|
|
export class WebGLRenderbufferState {
|
|
renderbuffer: WebGLRenderbuffer | null | GLUndefined = GLUndefined;
|
|
|
|
clone(): WebGLRenderbufferState {
|
|
const cloned = new WebGLRenderbufferState();
|
|
cloned.renderbuffer = this.renderbuffer;
|
|
return cloned;
|
|
}
|
|
|
|
equals(other: WebGLRenderbufferState): boolean {
|
|
return this.renderbuffer === other.renderbuffer;
|
|
}
|
|
}
|
|
|
|
export class WebGLSamplerState {
|
|
// Sampler bindings per texture unit (WebGL 2.0)
|
|
samplerBindings: { [unit: number]: WebGLSampler | null | GLUndefined } = {};
|
|
|
|
clone(): WebGLSamplerState {
|
|
const cloned = new WebGLSamplerState();
|
|
// Shallow copy is fine since samplerBindings contains WebGL objects (not cloneable state objects)
|
|
Object.assign(cloned.samplerBindings, this.samplerBindings);
|
|
return cloned;
|
|
}
|
|
|
|
equals(other: WebGLSamplerState): boolean {
|
|
const thisKeys = Object.keys(this.samplerBindings);
|
|
const otherKeys = Object.keys(other.samplerBindings);
|
|
if (thisKeys.length !== otherKeys.length) return false;
|
|
return thisKeys.every(
|
|
key => this.samplerBindings[Number(key)] === other.samplerBindings[Number(key)]
|
|
);
|
|
}
|
|
}
|
|
|
|
export class WebGLQueryState {
|
|
// Active queries per target (WebGL 2.0)
|
|
// Only one query can be active per target at a time
|
|
currentOcclusionQuery: WebGLQuery | null | GLUndefined = GLUndefined;
|
|
currentTransformFeedbackPrimitivesWritten: WebGLQuery | null | GLUndefined = GLUndefined;
|
|
currentAnySamplesPassed: WebGLQuery | null | GLUndefined = GLUndefined;
|
|
currentAnySamplesPassedConservative: WebGLQuery | null | GLUndefined = GLUndefined;
|
|
|
|
clone(): WebGLQueryState {
|
|
const cloned = new WebGLQueryState();
|
|
Object.assign(cloned, this);
|
|
return cloned;
|
|
}
|
|
|
|
equals(other: WebGLQueryState): boolean {
|
|
return (
|
|
this.currentOcclusionQuery === other.currentOcclusionQuery &&
|
|
this.currentTransformFeedbackPrimitivesWritten ===
|
|
other.currentTransformFeedbackPrimitivesWritten &&
|
|
this.currentAnySamplesPassed === other.currentAnySamplesPassed &&
|
|
this.currentAnySamplesPassedConservative === other.currentAnySamplesPassedConservative
|
|
);
|
|
}
|
|
}
|
|
|
|
export class WebGLState {
|
|
// Texture state
|
|
textures?: WebGLTextureState;
|
|
|
|
// Buffer state
|
|
buffers?: WebGLBufferState;
|
|
|
|
// Program state
|
|
programs?: WebGLProgramState;
|
|
|
|
// Framebuffer state
|
|
framebuffers?: WebGLFramebufferState;
|
|
|
|
// Vertex array state
|
|
vertexArrays?: WebGLVertexArrayState;
|
|
|
|
// Viewport and scissor
|
|
viewport?: WebGLViewportState;
|
|
|
|
// Clear values
|
|
clear?: WebGLClearState;
|
|
|
|
// Capabilities (enable/disable state)
|
|
capabilities?: WebGLCapabilityState;
|
|
|
|
// Pixel store parameters
|
|
pixelStore?: WebGLPixelStoreState;
|
|
|
|
// Blend state
|
|
blend?: WebGLBlendState;
|
|
|
|
// Depth state
|
|
depth?: WebGLDepthState;
|
|
|
|
// Stencil state
|
|
stencil?: WebGLStencilState;
|
|
|
|
// Color state
|
|
color?: WebGLColorState;
|
|
|
|
// Face culling
|
|
culling?: WebGLCullingState;
|
|
|
|
// Line rendering
|
|
line?: WebGLLineState;
|
|
|
|
// Polygon offset
|
|
polygonOffset?: WebGLPolygonOffsetState;
|
|
|
|
// Sample coverage
|
|
sample?: WebGLSampleState;
|
|
|
|
// Transform feedback (WebGL 2.0)
|
|
transformFeedback?: WebGLTransformFeedbackState;
|
|
|
|
// Renderbuffer
|
|
renderbuffer?: WebGLRenderbufferState;
|
|
|
|
// Sampler state (WebGL 2.0)
|
|
samplers?: WebGLSamplerState;
|
|
|
|
// Query state (WebGL 2.0)
|
|
queries?: WebGLQueryState;
|
|
|
|
// Buffer lifecycle tracking (for validation without GPU calls)
|
|
validBuffers = new Set<WebGLBuffer>();
|
|
|
|
readonly constants = new WebGLConstants();
|
|
|
|
// Lazy getters for state components
|
|
getTexturesState(): WebGLTextureState {
|
|
if (!this.textures) {
|
|
this.textures = new WebGLTextureState();
|
|
this.textures.activeTexture = this.constants.TEXTURE0;
|
|
}
|
|
return this.textures;
|
|
}
|
|
|
|
getOrCreateTextureUnit(unit: string): WebGLTextureUnitState {
|
|
const textures = this.getTexturesState();
|
|
if (!textures.textureUnits[unit]) {
|
|
textures.textureUnits[unit] = new WebGLTextureUnitState();
|
|
}
|
|
return textures.textureUnits[unit];
|
|
}
|
|
|
|
getBuffersState(): WebGLBufferState {
|
|
if (!this.buffers) {
|
|
this.buffers = new WebGLBufferState();
|
|
}
|
|
return this.buffers;
|
|
}
|
|
|
|
getProgramsState(): WebGLProgramState {
|
|
if (!this.programs) {
|
|
this.programs = new WebGLProgramState();
|
|
}
|
|
return this.programs;
|
|
}
|
|
|
|
getFramebuffersState(): WebGLFramebufferState {
|
|
if (!this.framebuffers) {
|
|
this.framebuffers = new WebGLFramebufferState();
|
|
this.framebuffers.defaultFramebufferState.drawBuffers = [this.constants.BACK || 0x0405]; // Default to BACK
|
|
this.framebuffers.defaultFramebufferState.readBuffer = this.constants.BACK || 0x0405;
|
|
}
|
|
return this.framebuffers;
|
|
}
|
|
|
|
getVertexArraysState(): WebGLVertexArrayState {
|
|
if (!this.vertexArrays) {
|
|
this.vertexArrays = new WebGLVertexArrayState();
|
|
}
|
|
return this.vertexArrays;
|
|
}
|
|
|
|
getOrCreateVertexAttrib(index: number): WebGLVertexAttribState {
|
|
const vertexArrays = this.getVertexArraysState();
|
|
const vaoState = vertexArrays.getCurrentVAOState();
|
|
const existing = vaoState.attributes.get(index);
|
|
if (!existing || existing === GLUndefined) {
|
|
const newAttrib = new WebGLVertexAttribState();
|
|
vaoState.attributes.set(index, newAttrib);
|
|
return newAttrib;
|
|
}
|
|
return existing as WebGLVertexAttribState;
|
|
}
|
|
|
|
getViewportState(): WebGLViewportState {
|
|
if (!this.viewport) {
|
|
this.viewport = new WebGLViewportState();
|
|
}
|
|
return this.viewport;
|
|
}
|
|
|
|
getClearState(): WebGLClearState {
|
|
if (!this.clear) {
|
|
this.clear = new WebGLClearState();
|
|
}
|
|
return this.clear;
|
|
}
|
|
|
|
getCapabilitiesState(): WebGLCapabilityState {
|
|
if (!this.capabilities) {
|
|
this.capabilities = new WebGLCapabilityState();
|
|
}
|
|
return this.capabilities;
|
|
}
|
|
|
|
getPixelStoreState(): WebGLPixelStoreState {
|
|
if (!this.pixelStore) {
|
|
this.pixelStore = new WebGLPixelStoreState();
|
|
}
|
|
return this.pixelStore;
|
|
}
|
|
|
|
getBlendState(): WebGLBlendState {
|
|
if (!this.blend) {
|
|
this.blend = new WebGLBlendState();
|
|
}
|
|
return this.blend;
|
|
}
|
|
|
|
getDepthState(): WebGLDepthState {
|
|
if (!this.depth) {
|
|
this.depth = new WebGLDepthState();
|
|
}
|
|
return this.depth;
|
|
}
|
|
|
|
getStencilState(): WebGLStencilState {
|
|
if (!this.stencil) {
|
|
this.stencil = new WebGLStencilState();
|
|
}
|
|
return this.stencil;
|
|
}
|
|
|
|
getColorState(): WebGLColorState {
|
|
if (!this.color) {
|
|
this.color = new WebGLColorState();
|
|
}
|
|
return this.color;
|
|
}
|
|
|
|
getCullingState(): WebGLCullingState {
|
|
if (!this.culling) {
|
|
this.culling = new WebGLCullingState();
|
|
}
|
|
return this.culling;
|
|
}
|
|
|
|
getLineState(): WebGLLineState {
|
|
if (!this.line) {
|
|
this.line = new WebGLLineState();
|
|
}
|
|
return this.line;
|
|
}
|
|
|
|
getPolygonOffsetState(): WebGLPolygonOffsetState {
|
|
if (!this.polygonOffset) {
|
|
this.polygonOffset = new WebGLPolygonOffsetState();
|
|
}
|
|
return this.polygonOffset;
|
|
}
|
|
|
|
getSampleState(): WebGLSampleState {
|
|
if (!this.sample) {
|
|
this.sample = new WebGLSampleState();
|
|
}
|
|
return this.sample;
|
|
}
|
|
|
|
getTransformFeedbackState(): WebGLTransformFeedbackState {
|
|
if (!this.transformFeedback) {
|
|
this.transformFeedback = new WebGLTransformFeedbackState();
|
|
}
|
|
return this.transformFeedback;
|
|
}
|
|
|
|
getRenderbufferState(): WebGLRenderbufferState {
|
|
if (!this.renderbuffer) {
|
|
this.renderbuffer = new WebGLRenderbufferState();
|
|
}
|
|
return this.renderbuffer;
|
|
}
|
|
|
|
getSamplersState(): WebGLSamplerState {
|
|
if (!this.samplers) {
|
|
this.samplers = new WebGLSamplerState();
|
|
}
|
|
return this.samplers;
|
|
}
|
|
|
|
getQueriesState(): WebGLQueryState {
|
|
if (!this.queries) {
|
|
this.queries = new WebGLQueryState();
|
|
}
|
|
return this.queries;
|
|
}
|
|
|
|
// Public state accessors
|
|
getState(): WebGLState {
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Clone the current state into a new WebGLState object
|
|
* This creates a deep clone of the state properties
|
|
* Note: WebGL objects (buffers, textures, etc.) are not cloned, only references
|
|
*/
|
|
clone(): WebGLState {
|
|
const cloned = new WebGLState();
|
|
|
|
// Clone each state property using their clone methods
|
|
if (this.buffers) cloned.buffers = this.buffers.clone();
|
|
if (this.vertexArrays) cloned.vertexArrays = this.vertexArrays.clone();
|
|
if (this.textures) cloned.textures = this.textures.clone();
|
|
if (this.samplers) cloned.samplers = this.samplers.clone();
|
|
if (this.programs) cloned.programs = this.programs.clone();
|
|
if (this.framebuffers) cloned.framebuffers = this.framebuffers.clone();
|
|
if (this.renderbuffer) cloned.renderbuffer = this.renderbuffer.clone();
|
|
if (this.transformFeedback) cloned.transformFeedback = this.transformFeedback.clone();
|
|
if (this.viewport) cloned.viewport = this.viewport.clone();
|
|
if (this.capabilities) cloned.capabilities = this.capabilities.clone();
|
|
if (this.clear) cloned.clear = this.clear.clone();
|
|
if (this.blend) cloned.blend = this.blend.clone();
|
|
if (this.depth) cloned.depth = this.depth.clone();
|
|
if (this.stencil) cloned.stencil = this.stencil.clone();
|
|
if (this.color) cloned.color = this.color.clone();
|
|
if (this.culling) cloned.culling = this.culling.clone();
|
|
if (this.line) cloned.line = this.line.clone();
|
|
if (this.polygonOffset) cloned.polygonOffset = this.polygonOffset.clone();
|
|
if (this.sample) cloned.sample = this.sample.clone();
|
|
if (this.pixelStore) cloned.pixelStore = this.pixelStore.clone();
|
|
if (this.queries) cloned.queries = this.queries.clone();
|
|
|
|
// Clone validBuffers Set
|
|
cloned.validBuffers = new Set(this.validBuffers);
|
|
|
|
return cloned;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* WebGLStateTracker - A state-only WebGL context that mimics WebGL2RenderingContext
|
|
*
|
|
* This class provides the same interface as WebGL2RenderingContext but only updates
|
|
* internal state without making actual WebGL calls. Useful for testing, debugging,
|
|
* and state tracking without GPU dependencies.
|
|
*/
|
|
/**
|
|
* WebGL Constants
|
|
*/
|
|
export class WebGLConstants {
|
|
// Buffer targets
|
|
readonly ARRAY_BUFFER = 0x8892;
|
|
readonly ELEMENT_ARRAY_BUFFER = 0x8893;
|
|
readonly UNIFORM_BUFFER = 0x8a11;
|
|
readonly TRANSFORM_FEEDBACK_BUFFER = 0x8c8e;
|
|
readonly PIXEL_PACK_BUFFER = 0x88eb;
|
|
readonly PIXEL_UNPACK_BUFFER = 0x88ec;
|
|
readonly COPY_READ_BUFFER = 0x8f36;
|
|
readonly COPY_WRITE_BUFFER = 0x8f37;
|
|
|
|
// Texture targets and units
|
|
readonly TEXTURE0 = 0x84c0;
|
|
readonly TEXTURE_2D = 0x0de1;
|
|
readonly TEXTURE_CUBE_MAP = 0x8513;
|
|
readonly TEXTURE_3D = 0x806f;
|
|
readonly TEXTURE_2D_ARRAY = 0x8c1a;
|
|
|
|
// Framebuffer targets
|
|
readonly FRAMEBUFFER = 0x8d40;
|
|
readonly DRAW_FRAMEBUFFER = 0x8ca9;
|
|
readonly READ_FRAMEBUFFER = 0x8ca8;
|
|
|
|
// Capabilities
|
|
readonly BLEND = 0x0be2;
|
|
readonly CULL_FACE = 0x0b44;
|
|
readonly DEPTH_TEST = 0x0b71;
|
|
readonly DITHER = 0x0bd0;
|
|
readonly POLYGON_OFFSET_FILL = 0x8037;
|
|
readonly SAMPLE_ALPHA_TO_COVERAGE = 0x809e;
|
|
readonly SAMPLE_COVERAGE = 0x80a0;
|
|
readonly SCISSOR_TEST = 0x0c11;
|
|
readonly STENCIL_TEST = 0x0b90;
|
|
readonly RASTERIZER_DISCARD = 0x8c89;
|
|
|
|
// Blend equations
|
|
readonly FUNC_ADD = 0x8006;
|
|
readonly FUNC_SUBTRACT = 0x800a;
|
|
|
|
// Blend functions
|
|
readonly ZERO = 0;
|
|
readonly ONE = 1;
|
|
readonly SRC_ALPHA = 0x0302;
|
|
readonly ONE_MINUS_SRC_ALPHA = 0x0303;
|
|
|
|
// Depth functions
|
|
readonly NEVER = 0x0200;
|
|
readonly LESS = 0x0201;
|
|
readonly EQUAL = 0x0202;
|
|
readonly LEQUAL = 0x0203;
|
|
|
|
// Stencil operations
|
|
readonly KEEP = 0x1e00;
|
|
readonly REPLACE = 0x1e01;
|
|
readonly INCR = 0x1e02;
|
|
|
|
// Stencil functions
|
|
readonly ALWAYS = 0x0207;
|
|
|
|
// Culling
|
|
readonly FRONT = 0x0404;
|
|
readonly BACK = 0x0405;
|
|
readonly FRONT_AND_BACK = 0x0408;
|
|
readonly CW = 0x0900;
|
|
readonly CCW = 0x0901;
|
|
|
|
// Pixel store parameters
|
|
readonly PACK_ALIGNMENT = 0x0d05;
|
|
readonly UNPACK_ALIGNMENT = 0x0cf5;
|
|
readonly UNPACK_FLIP_Y_WEBGL = 0x9240;
|
|
readonly UNPACK_PREMULTIPLY_ALPHA_WEBGL = 0x9241;
|
|
readonly PACK_ROW_LENGTH = 0x0d02;
|
|
readonly PACK_SKIP_PIXELS = 0x0d04;
|
|
readonly PACK_SKIP_ROWS = 0x0d03;
|
|
readonly UNPACK_ROW_LENGTH = 0x0cf2;
|
|
readonly UNPACK_IMAGE_HEIGHT = 0x806e;
|
|
readonly UNPACK_SKIP_PIXELS = 0x0cf4;
|
|
readonly UNPACK_SKIP_ROWS = 0x0cf3;
|
|
readonly UNPACK_SKIP_IMAGES = 0x806d;
|
|
|
|
// Framebuffer attachments
|
|
readonly COLOR_ATTACHMENT0 = 0x8ce0;
|
|
readonly DEPTH_ATTACHMENT = 0x8d00;
|
|
readonly STENCIL_ATTACHMENT = 0x8d20;
|
|
readonly DEPTH_STENCIL_ATTACHMENT = 0x821a;
|
|
|
|
// Query targets
|
|
readonly ANY_SAMPLES_PASSED = 0x8c2f;
|
|
readonly ANY_SAMPLES_PASSED_CONSERVATIVE = 0x8d6a;
|
|
readonly TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN = 0x8c88;
|
|
|
|
// Data types
|
|
readonly BYTE = 0x1400;
|
|
readonly UNSIGNED_BYTE = 0x1401;
|
|
readonly SHORT = 0x1402;
|
|
readonly UNSIGNED_SHORT = 0x1403;
|
|
readonly INT = 0x1404;
|
|
readonly UNSIGNED_INT = 0x1405;
|
|
readonly FLOAT = 0x1406;
|
|
|
|
// Renderbuffer
|
|
readonly RENDERBUFFER = 0x8d41;
|
|
|
|
// Transform feedback
|
|
readonly TRANSFORM_FEEDBACK = 0x8e22;
|
|
|
|
// Texture parameters
|
|
readonly TEXTURE_MIN_FILTER = 0x2801;
|
|
readonly LINEAR = 0x2601;
|
|
|
|
// Draw modes
|
|
readonly TRIANGLES = 0x0004;
|
|
readonly POINTS = 0x0000;
|
|
|
|
// Buffer usage
|
|
readonly STATIC_DRAW = 0x88e4;
|
|
}
|
|
|
|
export class WebGLStateTracker {
|
|
private state: WebGLState = new WebGLState();
|
|
readonly constants = this.state.constants;
|
|
|
|
constructor() {
|
|
// State is created in field initializer
|
|
}
|
|
|
|
// Buffer lifecycle tracking methods
|
|
createBuffer(buffer: WebGLBuffer): void {
|
|
this.state.validBuffers.add(buffer);
|
|
}
|
|
|
|
deleteBuffer(buffer: WebGLBuffer): void {
|
|
this.state.validBuffers.delete(buffer);
|
|
}
|
|
|
|
isValidBuffer(buffer: WebGLBuffer | null): boolean {
|
|
if (!buffer) return false;
|
|
return this.state.validBuffers.has(buffer);
|
|
}
|
|
|
|
// Buffer binding methods
|
|
bindBuffer(target: number, buffer: WebGLBuffer | null): void {
|
|
const buffers = this.state.getBuffersState();
|
|
switch (target) {
|
|
case this.constants.ARRAY_BUFFER:
|
|
buffers.arrayBuffer = buffer;
|
|
break;
|
|
case this.constants.ELEMENT_ARRAY_BUFFER:
|
|
{
|
|
// ELEMENT_ARRAY_BUFFER is per-VAO state (OpenGL ES 3.0 spec section 2.10)
|
|
// Store it in the currently bound VAO's state
|
|
const vertexArrays = this.state.getVertexArraysState();
|
|
const vaoState = vertexArrays.getCurrentVAOState();
|
|
vaoState.elementArrayBuffer = buffer;
|
|
}
|
|
break;
|
|
case this.constants.UNIFORM_BUFFER:
|
|
buffers.uniformBuffer = buffer;
|
|
break;
|
|
case this.constants.TRANSFORM_FEEDBACK_BUFFER:
|
|
buffers.transformFeedbackBuffer = buffer;
|
|
break;
|
|
case this.constants.PIXEL_PACK_BUFFER:
|
|
buffers.pixelPackBuffer = buffer;
|
|
break;
|
|
case this.constants.PIXEL_UNPACK_BUFFER:
|
|
buffers.pixelUnpackBuffer = buffer;
|
|
break;
|
|
case this.constants.COPY_READ_BUFFER:
|
|
buffers.copyReadBuffer = buffer;
|
|
break;
|
|
case this.constants.COPY_WRITE_BUFFER:
|
|
buffers.copyWriteBuffer = buffer;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Vertex Array Object methods
|
|
bindVertexArray(vao: WebGLVertexArrayObject | null): void {
|
|
const vertexArrays = this.state.getVertexArraysState();
|
|
vertexArrays.vertexArrayObject = vao;
|
|
|
|
// Ensure a vaoStates entry exists for this VAO
|
|
// This allows us to reliably detect if a VAO was deleted (missing from vaoStates)
|
|
if (!vertexArrays.vaoStates.has(vao)) {
|
|
vertexArrays.vaoStates.set(vao, new WebGLPerVAOState());
|
|
}
|
|
|
|
// Note: When switching VAOs, the ELEMENT_ARRAY_BUFFER binding is automatically
|
|
// switched to the new VAO's ELEMENT_ARRAY_BUFFER (per OpenGL ES 3.0 spec section 2.10)
|
|
// This happens automatically via the per-VAO state mechanism.
|
|
}
|
|
|
|
deleteVertexArray(vao: WebGLVertexArrayObject | null): void {
|
|
if (!vao) return;
|
|
|
|
const vertexArrays = this.state.getVertexArraysState();
|
|
|
|
// If the deleted VAO is currently bound, bind to null (default VAO)
|
|
// Per WebGL spec: "If the deleted object is currently bound, the binding reverts to 0"
|
|
if (vertexArrays.vertexArrayObject === vao) {
|
|
vertexArrays.vertexArrayObject = null;
|
|
}
|
|
|
|
// Remove the VAO state to prevent memory leaks
|
|
vertexArrays.vaoStates.delete(vao);
|
|
}
|
|
|
|
// Vertex attribute methods (per-VAO state - OpenGL ES 3.0 spec section 2.10)
|
|
// Now tracked for ALL VAOs, not just default
|
|
enableVertexAttribArray(index: number): void {
|
|
const attrib = this.state.getOrCreateVertexAttrib(index);
|
|
attrib.enabled = true;
|
|
}
|
|
|
|
disableVertexAttribArray(index: number): void {
|
|
const attrib = this.state.getOrCreateVertexAttrib(index);
|
|
attrib.enabled = false;
|
|
}
|
|
|
|
vertexAttribPointer(
|
|
index: number,
|
|
size: number,
|
|
type: number,
|
|
normalized: boolean,
|
|
stride: number,
|
|
offset: number
|
|
): void {
|
|
const attrib = this.state.getOrCreateVertexAttrib(index);
|
|
// Capture the current ARRAY_BUFFER binding
|
|
const buffers = this.state.buffers;
|
|
attrib.buffer = buffers?.arrayBuffer || GLUndefined;
|
|
attrib.size = size;
|
|
attrib.type = type;
|
|
attrib.normalized = normalized;
|
|
attrib.stride = stride;
|
|
attrib.offset = offset;
|
|
}
|
|
|
|
vertexAttribIPointer(
|
|
index: number,
|
|
size: number,
|
|
type: number,
|
|
stride: number,
|
|
offset: number
|
|
): void {
|
|
// Same as vertexAttribPointer but for integer attributes
|
|
const attrib = this.state.getOrCreateVertexAttrib(index);
|
|
const buffers = this.state.buffers;
|
|
attrib.buffer = buffers?.arrayBuffer || GLUndefined;
|
|
attrib.size = size;
|
|
attrib.type = type;
|
|
attrib.normalized = false; // Integer attributes are never normalized
|
|
attrib.stride = stride;
|
|
attrib.offset = offset;
|
|
}
|
|
|
|
vertexAttribDivisor(index: number, divisor: number): void {
|
|
const attrib = this.state.getOrCreateVertexAttrib(index);
|
|
attrib.divisor = divisor;
|
|
}
|
|
|
|
// Texture methods
|
|
activeTexture(texture: number): void {
|
|
const textures = this.state.getTexturesState();
|
|
const unitIndex = texture - this.constants.TEXTURE0;
|
|
|
|
// Validate unit index is within valid range
|
|
if (unitIndex < 0 || unitIndex >= GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS) {
|
|
console.warn(`Invalid activeTexture value: ${texture} (unit index: ${unitIndex})`);
|
|
return;
|
|
}
|
|
|
|
textures.activeTexture = texture;
|
|
}
|
|
|
|
bindTexture(target: number, texture: WebGLTexture | null): void {
|
|
const textures = this.state.getTexturesState();
|
|
const activeTextureValue =
|
|
textures.activeTexture === GLUndefined
|
|
? this.constants.TEXTURE0
|
|
: (textures.activeTexture as number);
|
|
const unitIndex = activeTextureValue - this.constants.TEXTURE0;
|
|
|
|
// Validate unit index is within valid range
|
|
if (unitIndex < 0 || unitIndex >= GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS) {
|
|
console.warn(
|
|
`Invalid texture unit index: ${unitIndex} (activeTexture: ${activeTextureValue})`
|
|
);
|
|
return;
|
|
}
|
|
|
|
const unitKey = `TEXTURE${unitIndex}`;
|
|
|
|
if (!textures.textureUnits[unitKey]) {
|
|
textures.textureUnits[unitKey] = new WebGLTextureUnitState();
|
|
}
|
|
|
|
switch (target) {
|
|
case this.constants.TEXTURE_2D:
|
|
textures.textureUnits[unitKey].texture2D = texture;
|
|
break;
|
|
case this.constants.TEXTURE_CUBE_MAP:
|
|
textures.textureUnits[unitKey].textureCubeMap = texture;
|
|
break;
|
|
case this.constants.TEXTURE_3D:
|
|
textures.textureUnits[unitKey].texture3D = texture;
|
|
break;
|
|
case this.constants.TEXTURE_2D_ARRAY:
|
|
textures.textureUnits[unitKey].texture2DArray = texture;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Program methods
|
|
useProgram(program: WebGLProgram | null): void {
|
|
const programs = this.state.getProgramsState();
|
|
programs.currentProgram = program;
|
|
}
|
|
|
|
// Framebuffer methods
|
|
bindFramebuffer(target: number, framebuffer: WebGLFramebuffer | null): void {
|
|
const framebuffers = this.state.getFramebuffersState();
|
|
switch (target) {
|
|
case this.constants.FRAMEBUFFER:
|
|
// FRAMEBUFFER target binds to both DRAW_FRAMEBUFFER and READ_FRAMEBUFFER
|
|
framebuffers.framebuffer = framebuffer;
|
|
framebuffers.drawFramebuffer = framebuffer;
|
|
framebuffers.readFramebuffer = framebuffer;
|
|
break;
|
|
case this.constants.DRAW_FRAMEBUFFER:
|
|
framebuffers.drawFramebuffer = framebuffer;
|
|
break;
|
|
case this.constants.READ_FRAMEBUFFER:
|
|
framebuffers.readFramebuffer = framebuffer;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Capability methods
|
|
enable(cap: number): void {
|
|
const capabilities = this.state.getCapabilitiesState();
|
|
switch (cap) {
|
|
case this.constants.BLEND:
|
|
capabilities.blend = true;
|
|
break;
|
|
case this.constants.CULL_FACE:
|
|
capabilities.cullFace = true;
|
|
break;
|
|
case this.constants.DEPTH_TEST:
|
|
capabilities.depthTest = true;
|
|
break;
|
|
case this.constants.DITHER:
|
|
capabilities.dither = true;
|
|
break;
|
|
case this.constants.POLYGON_OFFSET_FILL:
|
|
capabilities.polygonOffsetFill = true;
|
|
break;
|
|
case this.constants.SAMPLE_ALPHA_TO_COVERAGE:
|
|
capabilities.sampleAlphaToCoverage = true;
|
|
break;
|
|
case this.constants.SAMPLE_COVERAGE:
|
|
capabilities.sampleCoverage = true;
|
|
break;
|
|
case this.constants.SCISSOR_TEST:
|
|
capabilities.scissorTest = true;
|
|
break;
|
|
case this.constants.STENCIL_TEST:
|
|
capabilities.stencilTest = true;
|
|
break;
|
|
case this.constants.RASTERIZER_DISCARD:
|
|
capabilities.rasterDiscard = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
disable(cap: number): void {
|
|
const capabilities = this.state.getCapabilitiesState();
|
|
switch (cap) {
|
|
case this.constants.BLEND:
|
|
capabilities.blend = false;
|
|
break;
|
|
case this.constants.CULL_FACE:
|
|
capabilities.cullFace = false;
|
|
break;
|
|
case this.constants.DEPTH_TEST:
|
|
capabilities.depthTest = false;
|
|
break;
|
|
case this.constants.DITHER:
|
|
capabilities.dither = false;
|
|
break;
|
|
case this.constants.POLYGON_OFFSET_FILL:
|
|
capabilities.polygonOffsetFill = false;
|
|
break;
|
|
case this.constants.SAMPLE_ALPHA_TO_COVERAGE:
|
|
capabilities.sampleAlphaToCoverage = false;
|
|
break;
|
|
case this.constants.SAMPLE_COVERAGE:
|
|
capabilities.sampleCoverage = false;
|
|
break;
|
|
case this.constants.SCISSOR_TEST:
|
|
capabilities.scissorTest = false;
|
|
break;
|
|
case this.constants.STENCIL_TEST:
|
|
capabilities.stencilTest = false;
|
|
break;
|
|
case this.constants.RASTERIZER_DISCARD:
|
|
capabilities.rasterDiscard = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Viewport methods
|
|
viewport(x: number, y: number, width: number, height: number): void {
|
|
const viewportState = this.state.getViewportState();
|
|
if (!isDefined(viewportState.viewport)) {
|
|
viewportState.viewport = new Int32Array(4);
|
|
}
|
|
const arr = viewportState.viewport as Int32Array;
|
|
arr[0] = x;
|
|
arr[1] = y;
|
|
arr[2] = width;
|
|
arr[3] = height;
|
|
}
|
|
|
|
scissor(x: number, y: number, width: number, height: number): void {
|
|
const viewportState = this.state.getViewportState();
|
|
if (!isDefined(viewportState.scissorBox)) {
|
|
viewportState.scissorBox = new Int32Array(4);
|
|
}
|
|
const arr = viewportState.scissorBox as Int32Array;
|
|
arr[0] = x;
|
|
arr[1] = y;
|
|
arr[2] = width;
|
|
arr[3] = height;
|
|
}
|
|
|
|
// Clear methods
|
|
clearColor(red: number, green: number, blue: number, alpha: number): void {
|
|
const clear = this.state.getClearState();
|
|
if (!isDefined(clear.colorClearValue)) {
|
|
clear.colorClearValue = new Float32Array(4);
|
|
}
|
|
const arr = clear.colorClearValue as Float32Array;
|
|
arr[0] = red;
|
|
arr[1] = green;
|
|
arr[2] = blue;
|
|
arr[3] = alpha;
|
|
}
|
|
|
|
clearDepth(depth: number): void {
|
|
const clear = this.state.getClearState();
|
|
clear.depthClearValue = depth;
|
|
}
|
|
|
|
clearStencil(stencil: number): void {
|
|
const clear = this.state.getClearState();
|
|
clear.stencilClearValue = stencil;
|
|
}
|
|
|
|
// Blend state methods
|
|
blendColor(red: number, green: number, blue: number, alpha: number): void {
|
|
const blend = this.state.getBlendState();
|
|
if (!isDefined(blend.blendColor)) {
|
|
blend.blendColor = new Float32Array(4);
|
|
}
|
|
const arr = blend.blendColor as Float32Array;
|
|
arr[0] = red;
|
|
arr[1] = green;
|
|
arr[2] = blue;
|
|
arr[3] = alpha;
|
|
}
|
|
|
|
blendEquation(mode: number): void {
|
|
const blend = this.state.getBlendState();
|
|
blend.blendEquationRgb = mode;
|
|
blend.blendEquationAlpha = mode;
|
|
}
|
|
|
|
blendEquationSeparate(modeRGB: number, modeAlpha: number): void {
|
|
const blend = this.state.getBlendState();
|
|
blend.blendEquationRgb = modeRGB;
|
|
blend.blendEquationAlpha = modeAlpha;
|
|
}
|
|
|
|
blendFunc(sfactor: number, dfactor: number): void {
|
|
const blend = this.state.getBlendState();
|
|
blend.blendSrcRgb = sfactor;
|
|
blend.blendDstRgb = dfactor;
|
|
blend.blendSrcAlpha = sfactor;
|
|
blend.blendDstAlpha = dfactor;
|
|
}
|
|
|
|
blendFuncSeparate(srcRGB: number, dstRGB: number, srcAlpha: number, dstAlpha: number): void {
|
|
const blend = this.state.getBlendState();
|
|
blend.blendSrcRgb = srcRGB;
|
|
blend.blendDstRgb = dstRGB;
|
|
blend.blendSrcAlpha = srcAlpha;
|
|
blend.blendDstAlpha = dstAlpha;
|
|
}
|
|
|
|
// Depth state methods
|
|
depthFunc(func: number): void {
|
|
const depth = this.state.getDepthState();
|
|
depth.depthFunc = func;
|
|
}
|
|
|
|
depthMask(flag: boolean): void {
|
|
const depth = this.state.getDepthState();
|
|
depth.depthWritemask = flag;
|
|
}
|
|
|
|
depthRange(zNear: number, zFar: number): void {
|
|
const depth = this.state.getDepthState();
|
|
if (!isDefined(depth.depthRange)) {
|
|
depth.depthRange = new Float32Array(2);
|
|
}
|
|
const arr = depth.depthRange as Float32Array;
|
|
arr[0] = zNear;
|
|
arr[1] = zFar;
|
|
}
|
|
|
|
// Stencil state methods
|
|
stencilFunc(func: number, ref: number, mask: number): void {
|
|
const stencil = this.state.getStencilState();
|
|
stencil.stencilFunc = func;
|
|
stencil.stencilRef = ref;
|
|
stencil.stencilValueMask = mask;
|
|
stencil.stencilBackFunc = func;
|
|
stencil.stencilBackRef = ref;
|
|
stencil.stencilBackValueMask = mask;
|
|
}
|
|
|
|
stencilFuncSeparate(face: number, func: number, ref: number, mask: number): void {
|
|
const stencil = this.state.getStencilState();
|
|
if (face === this.constants.FRONT || face === this.constants.FRONT_AND_BACK) {
|
|
stencil.stencilFunc = func;
|
|
stencil.stencilRef = ref;
|
|
stencil.stencilValueMask = mask;
|
|
}
|
|
if (face === this.constants.BACK || face === this.constants.FRONT_AND_BACK) {
|
|
stencil.stencilBackFunc = func;
|
|
stencil.stencilBackRef = ref;
|
|
stencil.stencilBackValueMask = mask;
|
|
}
|
|
}
|
|
|
|
stencilMask(mask: number): void {
|
|
const stencil = this.state.getStencilState();
|
|
stencil.stencilWritemask = mask;
|
|
stencil.stencilBackWritemask = mask;
|
|
}
|
|
|
|
stencilMaskSeparate(face: number, mask: number): void {
|
|
const stencil = this.state.getStencilState();
|
|
if (face === this.constants.FRONT || face === this.constants.FRONT_AND_BACK) {
|
|
stencil.stencilWritemask = mask;
|
|
}
|
|
if (face === this.constants.BACK || face === this.constants.FRONT_AND_BACK) {
|
|
stencil.stencilBackWritemask = mask;
|
|
}
|
|
}
|
|
|
|
stencilOp(fail: number, zfail: number, zpass: number): void {
|
|
const stencil = this.state.getStencilState();
|
|
stencil.stencilFail = fail;
|
|
stencil.stencilPassDepthFail = zfail;
|
|
stencil.stencilPassDepthPass = zpass;
|
|
stencil.stencilBackFail = fail;
|
|
stencil.stencilBackPassDepthFail = zfail;
|
|
stencil.stencilBackPassDepthPass = zpass;
|
|
}
|
|
|
|
stencilOpSeparate(face: number, fail: number, zfail: number, zpass: number): void {
|
|
const stencil = this.state.getStencilState();
|
|
if (face === this.constants.FRONT || face === this.constants.FRONT_AND_BACK) {
|
|
stencil.stencilFail = fail;
|
|
stencil.stencilPassDepthFail = zfail;
|
|
stencil.stencilPassDepthPass = zpass;
|
|
}
|
|
if (face === this.constants.BACK || face === this.constants.FRONT_AND_BACK) {
|
|
stencil.stencilBackFail = fail;
|
|
stencil.stencilBackPassDepthFail = zfail;
|
|
stencil.stencilBackPassDepthPass = zpass;
|
|
}
|
|
}
|
|
|
|
// Color state methods
|
|
colorMask(red: boolean, green: boolean, blue: boolean, alpha: boolean): void {
|
|
const color = this.state.getColorState();
|
|
color.colorWritemask = [red, green, blue, alpha];
|
|
}
|
|
|
|
// Culling state methods
|
|
cullFace(mode: number): void {
|
|
const culling = this.state.getCullingState();
|
|
culling.cullFaceMode = mode;
|
|
}
|
|
|
|
frontFace(mode: number): void {
|
|
const culling = this.state.getCullingState();
|
|
culling.frontFace = mode;
|
|
}
|
|
|
|
// Line width
|
|
lineWidth(width: number): void {
|
|
const line = this.state.getLineState();
|
|
line.lineWidth = width;
|
|
}
|
|
|
|
// Polygon offset
|
|
polygonOffset(factor: number, units: number): void {
|
|
const polygonOffset = this.state.getPolygonOffsetState();
|
|
polygonOffset.polygonOffsetFactor = factor;
|
|
polygonOffset.polygonOffsetUnits = units;
|
|
}
|
|
|
|
// Sample coverage
|
|
sampleCoverage(value: number, invert: boolean): void {
|
|
const sample = this.state.getSampleState();
|
|
sample.sampleCoverageValue = value;
|
|
sample.sampleCoverageInvert = invert;
|
|
}
|
|
|
|
// Pixel store methods
|
|
pixelStorei(pname: number, param: number): void {
|
|
const pixelStore = this.state.getPixelStoreState();
|
|
switch (pname) {
|
|
case this.constants.PACK_ALIGNMENT:
|
|
pixelStore.packAlignment = param;
|
|
break;
|
|
case this.constants.UNPACK_ALIGNMENT:
|
|
pixelStore.unpackAlignment = param;
|
|
break;
|
|
case this.constants.UNPACK_FLIP_Y_WEBGL:
|
|
pixelStore.unpackFlipY = !!param;
|
|
break;
|
|
case this.constants.UNPACK_PREMULTIPLY_ALPHA_WEBGL:
|
|
pixelStore.unpackPremultiplyAlpha = !!param;
|
|
break;
|
|
case this.constants.PACK_ROW_LENGTH:
|
|
pixelStore.packRowLength = param;
|
|
break;
|
|
case this.constants.PACK_SKIP_PIXELS:
|
|
pixelStore.packSkipPixels = param;
|
|
break;
|
|
case this.constants.PACK_SKIP_ROWS:
|
|
pixelStore.packSkipRows = param;
|
|
break;
|
|
case this.constants.UNPACK_ROW_LENGTH:
|
|
pixelStore.unpackRowLength = param;
|
|
break;
|
|
case this.constants.UNPACK_IMAGE_HEIGHT:
|
|
pixelStore.unpackImageHeight = param;
|
|
break;
|
|
case this.constants.UNPACK_SKIP_PIXELS:
|
|
pixelStore.unpackSkipPixels = param;
|
|
break;
|
|
case this.constants.UNPACK_SKIP_ROWS:
|
|
pixelStore.unpackSkipRows = param;
|
|
break;
|
|
case this.constants.UNPACK_SKIP_IMAGES:
|
|
pixelStore.unpackSkipImages = param;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Renderbuffer methods
|
|
bindRenderbuffer(target: number, renderbuffer: WebGLRenderbuffer | null): void {
|
|
const renderbufferState = this.state.getRenderbufferState();
|
|
renderbufferState.renderbuffer = renderbuffer;
|
|
}
|
|
|
|
// Transform feedback methods
|
|
bindTransformFeedback(target: number, transformFeedback: WebGLTransformFeedback | null): void {
|
|
const tfState = this.state.getTransformFeedbackState();
|
|
tfState.transformFeedback = transformFeedback;
|
|
}
|
|
|
|
beginTransformFeedback(primitiveMode: number): void {
|
|
const tfState = this.state.getTransformFeedbackState();
|
|
tfState.transformFeedbackActive = true;
|
|
tfState.transformFeedbackPaused = false;
|
|
}
|
|
|
|
endTransformFeedback(): void {
|
|
const tfState = this.state.getTransformFeedbackState();
|
|
tfState.transformFeedbackActive = false;
|
|
tfState.transformFeedbackPaused = false;
|
|
}
|
|
|
|
pauseTransformFeedback(): void {
|
|
const tfState = this.state.getTransformFeedbackState();
|
|
if (tfState.transformFeedbackActive) {
|
|
tfState.transformFeedbackPaused = true;
|
|
}
|
|
}
|
|
|
|
resumeTransformFeedback(): void {
|
|
const tfState = this.state.getTransformFeedbackState();
|
|
if (tfState.transformFeedbackActive) {
|
|
tfState.transformFeedbackPaused = false;
|
|
}
|
|
}
|
|
|
|
// Buffer binding range methods
|
|
bindBufferBase(target: number, index: number, buffer: WebGLBuffer | null): void {
|
|
const buffers = this.state.getBuffersState();
|
|
// Per WebGL spec: bindBufferBase updates BOTH the indexed binding AND the generic binding
|
|
switch (target) {
|
|
case this.constants.UNIFORM_BUFFER:
|
|
buffers.uniformBuffer = buffer;
|
|
const uniformBinding = new WebGLIndexedBufferBinding();
|
|
uniformBinding.buffer = buffer;
|
|
uniformBinding.offset = 0;
|
|
uniformBinding.size = 0; // 0 means entire buffer
|
|
buffers.uniformBufferBindings.set(index, uniformBinding);
|
|
break;
|
|
case this.constants.TRANSFORM_FEEDBACK_BUFFER:
|
|
buffers.transformFeedbackBuffer = buffer;
|
|
const tfBinding = new WebGLIndexedBufferBinding();
|
|
tfBinding.buffer = buffer;
|
|
tfBinding.offset = 0;
|
|
tfBinding.size = 0; // 0 means entire buffer
|
|
buffers.transformFeedbackBufferBindings.set(index, tfBinding);
|
|
break;
|
|
}
|
|
}
|
|
|
|
bindBufferRange(
|
|
target: number,
|
|
index: number,
|
|
buffer: WebGLBuffer | null,
|
|
offset: number,
|
|
size: number
|
|
): void {
|
|
const buffers = this.state.getBuffersState();
|
|
// Per WebGL spec: bindBufferRange updates BOTH the indexed binding AND the generic binding
|
|
switch (target) {
|
|
case this.constants.UNIFORM_BUFFER:
|
|
buffers.uniformBuffer = buffer;
|
|
const uniformBinding = new WebGLIndexedBufferBinding();
|
|
uniformBinding.buffer = buffer;
|
|
uniformBinding.offset = offset;
|
|
uniformBinding.size = size;
|
|
buffers.uniformBufferBindings.set(index, uniformBinding);
|
|
break;
|
|
case this.constants.TRANSFORM_FEEDBACK_BUFFER:
|
|
buffers.transformFeedbackBuffer = buffer;
|
|
const transformBinding = new WebGLIndexedBufferBinding();
|
|
transformBinding.buffer = buffer;
|
|
transformBinding.offset = offset;
|
|
transformBinding.size = size;
|
|
buffers.transformFeedbackBufferBindings.set(index, transformBinding);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Framebuffer attachment methods (per-framebuffer state, only tracked for default FB)
|
|
framebufferTexture2D(
|
|
target: number,
|
|
attachment: number,
|
|
textarget: number,
|
|
texture: WebGLTexture | null,
|
|
level: number
|
|
): void {
|
|
const framebuffers = this.state.framebuffers;
|
|
const isDefaultFB =
|
|
!framebuffers ||
|
|
(target === this.constants.FRAMEBUFFER && framebuffers.framebuffer === null) ||
|
|
(target === this.constants.DRAW_FRAMEBUFFER && framebuffers.drawFramebuffer === null);
|
|
|
|
if (isDefaultFB) {
|
|
const fbState = this.state.getFramebuffersState().defaultFramebufferState;
|
|
const attachmentObj = new WebGLFramebufferAttachment();
|
|
attachmentObj.attachmentType = texture ? 'texture' : GLUndefined;
|
|
attachmentObj.texture = texture || GLUndefined;
|
|
attachmentObj.renderbuffer = GLUndefined;
|
|
attachmentObj.textureLevel = level;
|
|
attachmentObj.textureLayer = 0;
|
|
attachmentObj.textureCubeFace = textarget;
|
|
|
|
this.setFramebufferAttachment(fbState, attachment, attachmentObj);
|
|
}
|
|
// else: Non-default framebuffer - don't track
|
|
}
|
|
|
|
framebufferRenderbuffer(
|
|
target: number,
|
|
attachment: number,
|
|
renderbuffertarget: number,
|
|
renderbuffer: WebGLRenderbuffer | null
|
|
): void {
|
|
const framebuffers = this.state.framebuffers;
|
|
const isDefaultFB =
|
|
!framebuffers ||
|
|
(target === this.constants.FRAMEBUFFER && framebuffers.framebuffer === null) ||
|
|
(target === this.constants.DRAW_FRAMEBUFFER && framebuffers.drawFramebuffer === null);
|
|
|
|
if (isDefaultFB) {
|
|
const fbState = this.state.getFramebuffersState().defaultFramebufferState;
|
|
const attachmentObj = new WebGLFramebufferAttachment();
|
|
attachmentObj.attachmentType = renderbuffer ? 'renderbuffer' : GLUndefined;
|
|
attachmentObj.texture = GLUndefined;
|
|
attachmentObj.renderbuffer = renderbuffer || GLUndefined;
|
|
attachmentObj.textureLevel = 0;
|
|
attachmentObj.textureLayer = 0;
|
|
attachmentObj.textureCubeFace = 0;
|
|
|
|
this.setFramebufferAttachment(fbState, attachment, attachmentObj);
|
|
}
|
|
// else: Non-default framebuffer - don't track
|
|
}
|
|
|
|
framebufferTextureLayer(
|
|
target: number,
|
|
attachment: number,
|
|
texture: WebGLTexture | null,
|
|
level: number,
|
|
layer: number
|
|
): void {
|
|
const framebuffers = this.state.framebuffers;
|
|
const isDefaultFB =
|
|
!framebuffers ||
|
|
(target === this.constants.FRAMEBUFFER && framebuffers.framebuffer === null) ||
|
|
(target === this.constants.DRAW_FRAMEBUFFER && framebuffers.drawFramebuffer === null);
|
|
|
|
if (isDefaultFB) {
|
|
const fbState = this.state.getFramebuffersState().defaultFramebufferState;
|
|
const attachmentObj = new WebGLFramebufferAttachment();
|
|
attachmentObj.attachmentType = texture ? 'texture' : GLUndefined;
|
|
attachmentObj.texture = texture || GLUndefined;
|
|
attachmentObj.renderbuffer = GLUndefined;
|
|
attachmentObj.textureLevel = level;
|
|
attachmentObj.textureLayer = layer;
|
|
attachmentObj.textureCubeFace = 0;
|
|
|
|
this.setFramebufferAttachment(fbState, attachment, attachmentObj);
|
|
}
|
|
// else: Non-default framebuffer - don't track
|
|
}
|
|
|
|
private setFramebufferAttachment(
|
|
fbState: WebGLDefaultFramebufferState,
|
|
attachment: number,
|
|
attachmentObj: WebGLFramebufferAttachment
|
|
): void {
|
|
if (attachment === this.constants.DEPTH_ATTACHMENT) {
|
|
fbState.depthAttachment = attachmentObj;
|
|
} else if (attachment === this.constants.STENCIL_ATTACHMENT) {
|
|
fbState.stencilAttachment = attachmentObj;
|
|
} else if (attachment === this.constants.DEPTH_STENCIL_ATTACHMENT) {
|
|
fbState.depthStencilAttachment = attachmentObj;
|
|
} else if (
|
|
attachment >= this.constants.COLOR_ATTACHMENT0 &&
|
|
attachment <= this.constants.COLOR_ATTACHMENT0 + 15
|
|
) {
|
|
const index = attachment - this.constants.COLOR_ATTACHMENT0;
|
|
fbState.colorAttachments[`COLOR_ATTACHMENT${index}`] = attachmentObj;
|
|
}
|
|
}
|
|
|
|
// Draw buffer methods (per-framebuffer state, only tracked for default FB)
|
|
drawBuffers(buffers: number[]): void {
|
|
const framebuffers = this.state.framebuffers;
|
|
const isDefaultFB = !framebuffers || framebuffers.drawFramebuffer === null;
|
|
|
|
if (isDefaultFB) {
|
|
const fbState = this.state.getFramebuffersState().defaultFramebufferState;
|
|
fbState.drawBuffers = [...buffers];
|
|
}
|
|
// else: Non-default framebuffer - don't track
|
|
}
|
|
|
|
readBuffer(mode: number): void {
|
|
const framebuffers = this.state.framebuffers;
|
|
const isDefaultFB = !framebuffers || framebuffers.readFramebuffer === null;
|
|
|
|
if (isDefaultFB) {
|
|
const fbState = this.state.getFramebuffersState().defaultFramebufferState;
|
|
fbState.readBuffer = mode;
|
|
}
|
|
// else: Non-default framebuffer - don't track
|
|
}
|
|
|
|
// Hint method
|
|
hint(target: number, mode: number): void {
|
|
// Hints don't typically affect state in a way we need to track
|
|
// This is a no-op but maintains interface compatibility
|
|
}
|
|
|
|
// No-op state changing methods that don't affect our tracked state
|
|
// but are part of the WebGL2RenderingContext interface
|
|
|
|
// Buffer data methods (don't affect binding state)
|
|
bufferData(
|
|
target: number,
|
|
sizeOrData: number | ArrayBufferView | ArrayBuffer | null,
|
|
usage: number,
|
|
srcOffset?: number,
|
|
length?: number
|
|
): void {
|
|
// Data operations don't change binding state
|
|
}
|
|
|
|
bufferSubData(
|
|
target: number,
|
|
dstByteOffset: number,
|
|
srcData: ArrayBufferView | ArrayBuffer,
|
|
srcOffset?: number,
|
|
length?: number
|
|
): void {
|
|
// Data operations don't change binding state
|
|
}
|
|
|
|
copyBufferSubData(
|
|
readTarget: number,
|
|
writeTarget: number,
|
|
readOffset: number,
|
|
writeOffset: number,
|
|
size: number
|
|
): void {
|
|
// Copy operations don't change binding state
|
|
}
|
|
|
|
// Texture parameter methods (don't affect binding state)
|
|
texParameterf(target: number, pname: number, param: number): void {
|
|
// Parameter changes don't affect binding state
|
|
}
|
|
|
|
texParameteri(target: number, pname: number, param: number): void {
|
|
// Parameter changes don't affect binding state
|
|
}
|
|
|
|
// Texture data methods (don't affect binding state)
|
|
texImage2D(...args: any[]): void {
|
|
// Data operations don't change binding state
|
|
}
|
|
|
|
texSubImage2D(...args: any[]): void {
|
|
// Data operations don't change binding state
|
|
}
|
|
|
|
texImage3D(...args: any[]): void {
|
|
// Data operations don't change binding state
|
|
}
|
|
|
|
texSubImage3D(...args: any[]): void {
|
|
// Data operations don't change binding state
|
|
}
|
|
|
|
compressedTexImage2D(...args: any[]): void {
|
|
// Data operations don't change binding state
|
|
}
|
|
|
|
compressedTexSubImage2D(...args: any[]): void {
|
|
// Data operations don't change binding state
|
|
}
|
|
|
|
compressedTexImage3D(...args: any[]): void {
|
|
// Data operations don't change binding state
|
|
}
|
|
|
|
compressedTexSubImage3D(...args: any[]): void {
|
|
// Data operations don't change binding state
|
|
}
|
|
|
|
texStorage2D(
|
|
target: number,
|
|
levels: number,
|
|
internalformat: number,
|
|
width: number,
|
|
height: number
|
|
): void {
|
|
// Storage allocation doesn't change binding state
|
|
}
|
|
|
|
texStorage3D(
|
|
target: number,
|
|
levels: number,
|
|
internalformat: number,
|
|
width: number,
|
|
height: number,
|
|
depth: number
|
|
): void {
|
|
// Storage allocation doesn't change binding state
|
|
}
|
|
|
|
copyTexImage2D(
|
|
target: number,
|
|
level: number,
|
|
internalformat: number,
|
|
x: number,
|
|
y: number,
|
|
width: number,
|
|
height: number,
|
|
border: number
|
|
): void {
|
|
// Copy operations don't change binding state
|
|
}
|
|
|
|
copyTexSubImage2D(
|
|
target: number,
|
|
level: number,
|
|
xoffset: number,
|
|
yoffset: number,
|
|
x: number,
|
|
y: number,
|
|
width: number,
|
|
height: number
|
|
): void {
|
|
// Copy operations don't change binding state
|
|
}
|
|
|
|
copyTexSubImage3D(
|
|
target: number,
|
|
level: number,
|
|
xoffset: number,
|
|
yoffset: number,
|
|
zoffset: number,
|
|
x: number,
|
|
y: number,
|
|
width: number,
|
|
height: number
|
|
): void {
|
|
// Copy operations don't change binding state
|
|
}
|
|
|
|
generateMipmap(target: number): void {
|
|
// Mipmap generation doesn't change binding state
|
|
}
|
|
|
|
// Framebuffer methods (attachment doesn't affect binding state)
|
|
invalidateFramebuffer(target: number, attachments: number[]): void {
|
|
// Invalidation doesn't change binding state
|
|
}
|
|
|
|
invalidateSubFramebuffer(
|
|
target: number,
|
|
attachments: number[],
|
|
x: number,
|
|
y: number,
|
|
width: number,
|
|
height: number
|
|
): void {
|
|
// Invalidation doesn't change binding state
|
|
}
|
|
|
|
// Renderbuffer storage (doesn't affect binding state)
|
|
renderbufferStorage(target: number, internalformat: number, width: number, height: number): void {
|
|
// Storage allocation doesn't change binding state
|
|
}
|
|
|
|
renderbufferStorageMultisample(
|
|
target: number,
|
|
samples: number,
|
|
internalformat: number,
|
|
width: number,
|
|
height: number
|
|
): void {
|
|
// Storage allocation doesn't change binding state
|
|
}
|
|
|
|
// Sampler methods (WebGL 2.0)
|
|
bindSampler(unit: number, sampler: WebGLSampler | null): void {
|
|
const samplers = this.state.getSamplersState();
|
|
samplers.samplerBindings[unit] = sampler;
|
|
}
|
|
|
|
samplerParameteri(sampler: WebGLSampler, pname: number, param: number): void {
|
|
// Sampler parameters don't affect binding state
|
|
}
|
|
|
|
samplerParameterf(sampler: WebGLSampler, pname: number, param: number): void {
|
|
// Sampler parameters don't affect binding state
|
|
}
|
|
|
|
// Query methods (WebGL 2.0)
|
|
beginQuery(target: number, query: WebGLQuery): void {
|
|
const queries = this.state.getQueriesState();
|
|
switch (target) {
|
|
case this.constants.ANY_SAMPLES_PASSED:
|
|
queries.currentAnySamplesPassed = query;
|
|
break;
|
|
case this.constants.ANY_SAMPLES_PASSED_CONSERVATIVE:
|
|
queries.currentAnySamplesPassedConservative = query;
|
|
break;
|
|
case this.constants.TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN:
|
|
queries.currentTransformFeedbackPrimitivesWritten = query;
|
|
break;
|
|
// SAMPLES_PASSED is WebGL 1.0 extension, we track as occlusion query
|
|
default:
|
|
queries.currentOcclusionQuery = query;
|
|
break;
|
|
}
|
|
}
|
|
|
|
endQuery(target: number): void {
|
|
const queries = this.state.getQueriesState();
|
|
switch (target) {
|
|
case this.constants.ANY_SAMPLES_PASSED:
|
|
queries.currentAnySamplesPassed = null;
|
|
break;
|
|
case this.constants.ANY_SAMPLES_PASSED_CONSERVATIVE:
|
|
queries.currentAnySamplesPassedConservative = null;
|
|
break;
|
|
case this.constants.TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN:
|
|
queries.currentTransformFeedbackPrimitivesWritten = null;
|
|
break;
|
|
default:
|
|
queries.currentOcclusionQuery = null;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Generic vertex attribute setters (don't affect binding state)
|
|
vertexAttrib1f(index: number, x: number): void {
|
|
// Generic vertex attributes don't affect binding state
|
|
}
|
|
|
|
vertexAttrib2f(index: number, x: number, y: number): void {
|
|
// Generic vertex attributes don't affect binding state
|
|
}
|
|
|
|
vertexAttrib3f(index: number, x: number, y: number, z: number): void {
|
|
// Generic vertex attributes don't affect binding state
|
|
}
|
|
|
|
vertexAttrib4f(index: number, x: number, y: number, z: number, w: number): void {
|
|
// Generic vertex attributes don't affect binding state
|
|
}
|
|
|
|
vertexAttribI4i(index: number, x: number, y: number, z: number, w: number): void {
|
|
// Generic vertex attributes don't affect binding state
|
|
}
|
|
|
|
vertexAttribI4ui(index: number, x: number, y: number, z: number, w: number): void {
|
|
// Generic vertex attributes don't affect binding state
|
|
}
|
|
|
|
vertexAttrib1fv(index: number, values: Float32Array | number[]): void {
|
|
// Generic vertex attributes don't affect binding state
|
|
}
|
|
|
|
vertexAttrib2fv(index: number, values: Float32Array | number[]): void {
|
|
// Generic vertex attributes don't affect binding state
|
|
}
|
|
|
|
vertexAttrib3fv(index: number, values: Float32Array | number[]): void {
|
|
// Generic vertex attributes don't affect binding state
|
|
}
|
|
|
|
vertexAttrib4fv(index: number, values: Float32Array | number[]): void {
|
|
// Generic vertex attributes don't affect binding state
|
|
}
|
|
|
|
vertexAttribI4iv(index: number, values: Int32Array | number[]): void {
|
|
// Generic vertex attributes don't affect binding state
|
|
}
|
|
|
|
vertexAttribI4uiv(index: number, values: Uint32Array | number[]): void {
|
|
// Generic vertex attributes don't affect binding state
|
|
}
|
|
|
|
// Uniform methods (don't affect binding state, only change uniform values)
|
|
uniform1f(location: WebGLUniformLocation | null, x: number): void {}
|
|
uniform2f(location: WebGLUniformLocation | null, x: number, y: number): void {}
|
|
uniform3f(location: WebGLUniformLocation | null, x: number, y: number, z: number): void {}
|
|
uniform4f(
|
|
location: WebGLUniformLocation | null,
|
|
x: number,
|
|
y: number,
|
|
z: number,
|
|
w: number
|
|
): void {}
|
|
|
|
uniform1i(location: WebGLUniformLocation | null, x: number): void {}
|
|
uniform2i(location: WebGLUniformLocation | null, x: number, y: number): void {}
|
|
uniform3i(location: WebGLUniformLocation | null, x: number, y: number, z: number): void {}
|
|
uniform4i(
|
|
location: WebGLUniformLocation | null,
|
|
x: number,
|
|
y: number,
|
|
z: number,
|
|
w: number
|
|
): void {}
|
|
|
|
uniform1ui(location: WebGLUniformLocation | null, x: number): void {}
|
|
uniform2ui(location: WebGLUniformLocation | null, x: number, y: number): void {}
|
|
uniform3ui(location: WebGLUniformLocation | null, x: number, y: number, z: number): void {}
|
|
uniform4ui(
|
|
location: WebGLUniformLocation | null,
|
|
x: number,
|
|
y: number,
|
|
z: number,
|
|
w: number
|
|
): void {}
|
|
|
|
uniform1fv(
|
|
location: WebGLUniformLocation | null,
|
|
data: Float32Array | number[],
|
|
srcOffset?: number,
|
|
srcLength?: number
|
|
): void {}
|
|
uniform2fv(
|
|
location: WebGLUniformLocation | null,
|
|
data: Float32Array | number[],
|
|
srcOffset?: number,
|
|
srcLength?: number
|
|
): void {}
|
|
uniform3fv(
|
|
location: WebGLUniformLocation | null,
|
|
data: Float32Array | number[],
|
|
srcOffset?: number,
|
|
srcLength?: number
|
|
): void {}
|
|
uniform4fv(
|
|
location: WebGLUniformLocation | null,
|
|
data: Float32Array | number[],
|
|
srcOffset?: number,
|
|
srcLength?: number
|
|
): void {}
|
|
|
|
uniform1iv(
|
|
location: WebGLUniformLocation | null,
|
|
data: Int32Array | number[],
|
|
srcOffset?: number,
|
|
srcLength?: number
|
|
): void {}
|
|
uniform2iv(
|
|
location: WebGLUniformLocation | null,
|
|
data: Int32Array | number[],
|
|
srcOffset?: number,
|
|
srcLength?: number
|
|
): void {}
|
|
uniform3iv(
|
|
location: WebGLUniformLocation | null,
|
|
data: Int32Array | number[],
|
|
srcOffset?: number,
|
|
srcLength?: number
|
|
): void {}
|
|
uniform4iv(
|
|
location: WebGLUniformLocation | null,
|
|
data: Int32Array | number[],
|
|
srcOffset?: number,
|
|
srcLength?: number
|
|
): void {}
|
|
|
|
uniform1uiv(
|
|
location: WebGLUniformLocation | null,
|
|
data: Uint32Array | number[],
|
|
srcOffset?: number,
|
|
srcLength?: number
|
|
): void {}
|
|
uniform2uiv(
|
|
location: WebGLUniformLocation | null,
|
|
data: Uint32Array | number[],
|
|
srcOffset?: number,
|
|
srcLength?: number
|
|
): void {}
|
|
uniform3uiv(
|
|
location: WebGLUniformLocation | null,
|
|
data: Uint32Array | number[],
|
|
srcOffset?: number,
|
|
srcLength?: number
|
|
): void {}
|
|
uniform4uiv(
|
|
location: WebGLUniformLocation | null,
|
|
data: Uint32Array | number[],
|
|
srcOffset?: number,
|
|
srcLength?: number
|
|
): void {}
|
|
|
|
uniformMatrix2fv(
|
|
location: WebGLUniformLocation | null,
|
|
transpose: boolean,
|
|
data: Float32Array | number[],
|
|
srcOffset?: number,
|
|
srcLength?: number
|
|
): void {}
|
|
uniformMatrix3fv(
|
|
location: WebGLUniformLocation | null,
|
|
transpose: boolean,
|
|
data: Float32Array | number[],
|
|
srcOffset?: number,
|
|
srcLength?: number
|
|
): void {}
|
|
uniformMatrix4fv(
|
|
location: WebGLUniformLocation | null,
|
|
transpose: boolean,
|
|
data: Float32Array | number[],
|
|
srcOffset?: number,
|
|
srcLength?: number
|
|
): void {}
|
|
uniformMatrix2x3fv(
|
|
location: WebGLUniformLocation | null,
|
|
transpose: boolean,
|
|
data: Float32Array | number[],
|
|
srcOffset?: number,
|
|
srcLength?: number
|
|
): void {}
|
|
uniformMatrix3x2fv(
|
|
location: WebGLUniformLocation | null,
|
|
transpose: boolean,
|
|
data: Float32Array | number[],
|
|
srcOffset?: number,
|
|
srcLength?: number
|
|
): void {}
|
|
uniformMatrix2x4fv(
|
|
location: WebGLUniformLocation | null,
|
|
transpose: boolean,
|
|
data: Float32Array | number[],
|
|
srcOffset?: number,
|
|
srcLength?: number
|
|
): void {}
|
|
uniformMatrix4x2fv(
|
|
location: WebGLUniformLocation | null,
|
|
transpose: boolean,
|
|
data: Float32Array | number[],
|
|
srcOffset?: number,
|
|
srcLength?: number
|
|
): void {}
|
|
uniformMatrix3x4fv(
|
|
location: WebGLUniformLocation | null,
|
|
transpose: boolean,
|
|
data: Float32Array | number[],
|
|
srcOffset?: number,
|
|
srcLength?: number
|
|
): void {}
|
|
uniformMatrix4x3fv(
|
|
location: WebGLUniformLocation | null,
|
|
transpose: boolean,
|
|
data: Float32Array | number[],
|
|
srcOffset?: number,
|
|
srcLength?: number
|
|
): void {}
|
|
|
|
// Drawing methods (don't affect state, only render)
|
|
clear(mask: number): void {}
|
|
drawArrays(mode: number, first: number, count: number): void {}
|
|
drawElements(mode: number, count: number, type: number, offset: number): void {}
|
|
drawArraysInstanced(mode: number, first: number, count: number, instanceCount: number): void {}
|
|
drawElementsInstanced(
|
|
mode: number,
|
|
count: number,
|
|
type: number,
|
|
offset: number,
|
|
instanceCount: number
|
|
): void {}
|
|
drawRangeElements(
|
|
mode: number,
|
|
start: number,
|
|
end: number,
|
|
count: number,
|
|
type: number,
|
|
offset: number
|
|
): void {}
|
|
|
|
// Read pixels (doesn't change state)
|
|
readPixels(...args: any[]): void {}
|
|
|
|
// Blit framebuffer (doesn't change binding state)
|
|
blitFramebuffer(
|
|
srcX0: number,
|
|
srcY0: number,
|
|
srcX1: number,
|
|
srcY1: number,
|
|
dstX0: number,
|
|
dstY0: number,
|
|
dstX1: number,
|
|
dstY1: number,
|
|
mask: number,
|
|
filter: number
|
|
): void {}
|
|
|
|
// Flush/Finish (doesn't change state)
|
|
flush(): void {}
|
|
finish(): void {}
|
|
|
|
// State access methods - returns a clone to prevent external mutation
|
|
getState(): WebGLState {
|
|
return this.state.clone();
|
|
}
|
|
|
|
// Public getters that return state without lazy creation (tests use these)
|
|
getBufferState(): WebGLBufferState | undefined {
|
|
return this.state.buffers;
|
|
}
|
|
|
|
getVertexArrayState(): WebGLVertexArrayState | undefined {
|
|
return this.state.vertexArrays;
|
|
}
|
|
|
|
getTextureState(): WebGLTextureState | undefined {
|
|
return this.state.textures;
|
|
}
|
|
|
|
getProgramState(): WebGLProgramState | undefined {
|
|
return this.state.programs;
|
|
}
|
|
|
|
getFramebufferState(): WebGLFramebufferState | undefined {
|
|
return this.state.framebuffers;
|
|
}
|
|
|
|
getCapabilityState(): WebGLCapabilityState | undefined {
|
|
return this.state.capabilities;
|
|
}
|
|
|
|
getSamplerState(): WebGLSamplerState | undefined {
|
|
return this.state.samplers;
|
|
}
|
|
|
|
getQueryState(): WebGLQueryState | undefined {
|
|
return this.state.queries;
|
|
}
|
|
|
|
getStencilState(): WebGLStencilState | undefined {
|
|
return this.state.stencil;
|
|
}
|
|
|
|
getPixelStoreState(): WebGLPixelStoreState | undefined {
|
|
return this.state.pixelStore;
|
|
}
|
|
|
|
getTransformFeedbackState(): WebGLTransformFeedbackState | undefined {
|
|
return this.state.transformFeedback;
|
|
}
|
|
|
|
// Reset state to defaults (clears all lazily created state)
|
|
reset(): void {
|
|
this.state = new WebGLState();
|
|
}
|
|
}
|