UNPKG

webgl2

Version:

WebGL2 tools to derisk large GPU projects on the web beyond toys and demos.

1,420 lines (1,286 loc) 104 kB
// Thin forwarding WasmWebGL2RenderingContext and helpers // This module contains the class and small helpers that operate on the // WebAssembly instance. It is intentionally minimal: JS forwards calls to // WASM and reads last-error strings when needed. /** @typedef {number} u32 */ // Errno constants (must match src/webgl2_context.rs) export const ERR_OK = 0; export const ERR_INVALID_HANDLE = 1; export const ERR_OOM = 2; export const ERR_INVALID_ARGS = 3; export const ERR_NOT_IMPLEMENTED = 4; export const ERR_GL = 5; export const ERR_INTERNAL = 6; function getBPP(internalFormat) { switch (internalFormat) { case 0x822E: // R32F case 0x8236: // R32UI case 0x8235: // R32I return 4; case 0x8230: // RG32F case 0x823C: // RG32UI case 0x823B: // RG32I return 8; case 0x8814: // RGBA32F case 0x8D70: // RGBA32UI case 0x8D82: // RGBA32I case 0x8815: // RGB32F (often promoted to RGBA32F) case 0x8D71: // RGB32UI case 0x8D83: // RGB32I return 16; case 0x822D: // R16F case 0x8234: // R16UI case 0x8233: // R16I return 2; case 0x822F: // RG16F case 0x823A: // RG16UI case 0x8239: // RG16I return 4; case 0x881A: // RGBA16F case 0x8D76: // RGBA16UI case 0x8D88: // RGBA16I case 0x881B: // RGB16F case 0x8D77: // RGB16UI case 0x8D89: // RGB16I return 8; default: return 4; // Default to RGBA8 } } import { WasmWebGLTexture } from './webgl2_texture.js'; import { WasmWebGLShader, WasmWebGLProgram, WasmWebGLBuffer, WasmWebGLRenderbuffer, WasmWebGLFramebuffer, WasmWebGLVertexArrayObject, WasmWebGLQuery, WasmWebGLSampler, WasmWebGLSync, WasmWebGLTransformFeedback, WasmWebGLUniformLocation } from './webgl2_resources.js'; /** * @implements {WebGL2RenderingContext} */ export class WasmWebGL2RenderingContext { // Constants FRAGMENT_SHADER = 0x8B30; VERTEX_SHADER = 0x8B31; TRIANGLES = 0x0004; TRIANGLE_STRIP = 0x0005; COLOR_BUFFER_BIT = 0x00004000; DEPTH_BUFFER_BIT = 0x00000100; DEPTH_TEST = 0x0B71; STENCIL_TEST = 0x0B90; SCISSOR_TEST = 0x0C11; BLEND = 0x0BE2; CULL_FACE = 0x0B44; STENCIL_BUFFER_BIT = 0x00000400; COMPILE_STATUS = 0x8B81; LINK_STATUS = 0x8B82; DELETE_STATUS = 0x8B80; VALIDATE_STATUS = 0x8B83; ARRAY_BUFFER = 0x8892; ELEMENT_ARRAY_BUFFER = 0x8893; COPY_READ_BUFFER = 0x8F36; COPY_WRITE_BUFFER = 0x8F37; PIXEL_PACK_BUFFER = 0x88EB; PIXEL_UNPACK_BUFFER = 0x88EC; UNIFORM_BUFFER = 0x8A11; TRANSFORM_FEEDBACK_BUFFER = 0x8C8E; TRANSFORM_FEEDBACK_BUFFER_BINDING = 0x8C8F; TRANSFORM_FEEDBACK_BUFFER_START = 0x8C84; TRANSFORM_FEEDBACK_BUFFER_SIZE = 0x8C85; TRANSFORM_FEEDBACK_BINDING = 0x8E25; TRANSFORM_FEEDBACK_BUFFER_MODE = 0x8C7F; TRANSFORM_FEEDBACK_VARYINGS = 0x8C83; TRANSFORM_FEEDBACK_VARYING_MAX_LENGTH = 0x8C76; MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS = 0x8C8B; MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS = 0x8C8A; MAX_TRANSFORM_FEEDBACK_SEPARATE_COMPONENTS = 0x8C80; INTERLEAVED_ATTRIBS = 0x8C8C; SEPARATE_ATTRIBS = 0x8C8D; TRANSFORM_FEEDBACK_PAUSED = 0x8E23; TRANSFORM_FEEDBACK_ACTIVE = 0x8E24; TRANSFORM_FEEDBACK = 0x8E22; RASTERIZER_DISCARD = 0x8C89; STATIC_DRAW = 0x88E4; BYTE = 0x1400; UNSIGNED_BYTE = 0x1401; SHORT = 0x1402; UNSIGNED_SHORT = 0x1403; INT = 0x1404; UNSIGNED_INT = 0x1405; FLOAT = 0x1406; FLOAT_VEC2 = 0x8B50; FLOAT_VEC3 = 0x8B51; FLOAT_VEC4 = 0x8B52; INT_VEC2 = 0x8B53; INT_VEC3 = 0x8B54; INT_VEC4 = 0x8B55; BOOL = 0x8B56; BOOL_VEC2 = 0x8B57; BOOL_VEC3 = 0x8B58; BOOL_VEC4 = 0x8B59; FLOAT_MAT2 = 0x8B5A; FLOAT_MAT3 = 0x8B5B; FLOAT_MAT4 = 0x8B5C; SAMPLER_2D = 0x8B5E; SAMPLER_3D = 0x8B5F; SAMPLER_CUBE = 0x8B60; // Texture units (GL_TEXTURE0 .. GL_TEXTURE7) TEXTURE0 = 0x84C0; TEXTURE1 = 0x84C1; TEXTURE2 = 0x84C2; TEXTURE3 = 0x84C3; TEXTURE4 = 0x84C4; TEXTURE5 = 0x84C5; TEXTURE6 = 0x84C6; TEXTURE7 = 0x84C7; // Max combined texture image units MAX_COMBINED_TEXTURE_IMAGE_UNITS = 0x8B4D; ACTIVE_UNIFORMS = 0x8B86; ACTIVE_ATTRIBUTES = 0x8B89; VIEWPORT = 0x0BA2; COLOR_CLEAR_VALUE = 0x0C22; COLOR_WRITEMASK = 0x0C23; DEPTH_WRITEMASK = 0x0B72; STENCIL_WRITEMASK = 0x0B98; STENCIL_BACK_WRITEMASK = 0x8CA5; DEPTH_FUNC = 0x0B74; STENCIL_FUNC = 0x0B92; STENCIL_VALUE_MASK = 0x0B93; STENCIL_REF = 0x0B97; STENCIL_BACK_FUNC = 0x8800; STENCIL_BACK_VALUE_MASK = 0x8CA4; STENCIL_BACK_REF = 0x8CA3; STENCIL_FAIL = 0x0B94; STENCIL_PASS_DEPTH_FAIL = 0x0B95; STENCIL_PASS_DEPTH_PASS = 0x0B96; STENCIL_BACK_FAIL = 0x8801; STENCIL_BACK_PASS_DEPTH_FAIL = 0x8802; STENCIL_BACK_PASS_DEPTH_PASS = 0x8803; BUFFER_SIZE = 0x8764; MAX_VERTEX_ATTRIBS = 0x8869; NO_ERROR = 0; INVALID_ENUM = 0x0500; INVALID_VALUE = 0x0501; INVALID_OPERATION = 0x0502; OUT_OF_MEMORY = 0x0505; ZERO = 0; ONE = 1; CURRENT_VERTEX_ATTRIB = 0x8626; VERTEX_ATTRIB_ARRAY_ENABLED = 0x8622; VERTEX_ATTRIB_ARRAY_SIZE = 0x8623; VERTEX_ATTRIB_ARRAY_STRIDE = 0x8624; VERTEX_ATTRIB_ARRAY_TYPE = 0x8625; VERTEX_ATTRIB_ARRAY_NORMALIZED = 0x886A; VERTEX_ATTRIB_ARRAY_POINTER = 0x8645; VERTEX_ATTRIB_ARRAY_BUFFER_BINDING = 0x889F; VERTEX_ATTRIB_ARRAY_DIVISOR = 0x88FE; VERTEX_ATTRIB_ARRAY_INTEGER = 0x88FD; RENDERBUFFER = 0x8D41; FRAMEBUFFER = 0x8D40; READ_FRAMEBUFFER = 0x8CA8; DRAW_FRAMEBUFFER = 0x8CA9; FRAMEBUFFER_COMPLETE = 0x8CD5; FRAMEBUFFER_INCOMPLETE_ATTACHMENT = 0x8CD6; FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT = 0x8CD7; FRAMEBUFFER_INCOMPLETE_DIMENSIONS = 0x8CD9; FRAMEBUFFER_UNSUPPORTED = 0x8CDD; FRAMEBUFFER_INCOMPLETE_MULTISAMPLE = 0x8D56; RENDERBUFFER_SAMPLES = 0x8CAB; FRAMEBUFFER_UNDEFINED = 0x8219; MAX_DRAW_BUFFERS = 0x8824; DRAW_BUFFER0 = 0x8825; DRAW_BUFFER1 = 0x8826; DRAW_BUFFER2 = 0x8827; DRAW_BUFFER3 = 0x8828; DRAW_BUFFER4 = 0x8829; DRAW_BUFFER5 = 0x882A; DRAW_BUFFER6 = 0x882B; DRAW_BUFFER7 = 0x882C; MAX_COLOR_ATTACHMENTS = 0x8CDF; FRAMEBUFFER_BINDING = 0x8CA6; DRAW_FRAMEBUFFER_BINDING = 0x8CA6; READ_FRAMEBUFFER_BINDING = 0x8CAA; RENDERBUFFER_BINDING = 0x8CA7; DEPTH_COMPONENT16 = 0x81A5; DEPTH_STENCIL = 0x84F9; RGBA4 = 0x8056; RGB565 = 0x8D62; RGB5_A1 = 0x8057; RGBA8 = 0x8058; RGBA32F = 0x8814; RGB32F = 0x8815; RGBA16F = 0x881A; RGB16F = 0x881B; R8UI = 0x8232; RG8UI = 0x8238; RGB8UI = 0x8D7D; RGBA8UI = 0x8D7C; R16UI = 0x8234; RG16UI = 0x823A; RGB16UI = 0x8D77; RGBA16UI = 0x8D76; R32UI = 0x8236; RG32UI = 0x823C; RGB32UI = 0x8D71; RGBA32UI = 0x8D70; R8I = 0x8231; RG8I = 0x8237; RGB8I = 0x8D8F; RGBA8I = 0x8D8E; R16I = 0x8233; RG16I = 0x8239; RGB16I = 0x8D89; RGBA16I = 0x8D88; R32I = 0x8235; RG32I = 0x823B; RGB32I = 0x8D83; RGBA32I = 0x8D82; RED_INTEGER = 0x8D94; RG_INTEGER = 0x8228; RGB_INTEGER = 0x8D98; RGBA_INTEGER = 0x8D9E; R32F = 0x822E; RG32F = 0x8230; R16F = 0x822D; RG16F = 0x822F; STENCIL_INDEX8 = 0x8D48; COLOR_ATTACHMENT0 = 0x8CE0; COLOR_ATTACHMENT1 = 0x8CE1; COLOR_ATTACHMENT2 = 0x8CE2; COLOR_ATTACHMENT3 = 0x8CE3; COLOR_ATTACHMENT4 = 0x8CE4; COLOR_ATTACHMENT5 = 0x8CE5; COLOR_ATTACHMENT6 = 0x8CE6; COLOR_ATTACHMENT7 = 0x8CE7; DEPTH_ATTACHMENT = 0x8D00; STENCIL_ATTACHMENT = 0x8D20; DEPTH_STENCIL_ATTACHMENT = 0x821A; LESS = 0x0201; EQUAL = 0x0202; LEQUAL = 0x0203; GREATER = 0x0204; NOTEQUAL = 0x0205; GEQUAL = 0x0206; ALWAYS = 0x0207; NEVER = 0x0200; KEEP = 0x1E00; REPLACE = 0x1E01; INCR = 0x1E02; DECR = 0x1E03; INVERT = 0x150A; INCR_WRAP = 0x8507; DECR_WRAP = 0x8508; FRONT = 0x0404; BACK = 0x0405; FRONT_AND_BACK = 0x0408; TEXTURE_2D = 0x0DE1; TEXTURE_3D = 0x806F; TEXTURE_2D_ARRAY = 0x8C1A; TEXTURE_WRAP_S = 0x2802; TEXTURE_WRAP_T = 0x2803; TEXTURE_WRAP_R = 0x8072; TEXTURE_MAG_FILTER = 0x2800; TEXTURE_MIN_FILTER = 0x2801; RGBA = 0x1908; RED = 0x1903; RG = 0x8227; UNSIGNED_BYTE = 0x1401; FLOAT = 0x1406; NEAREST = 0x2600; LINEAR = 0x2601; NEAREST_MIPMAP_NEAREST = 0x2700; LINEAR_MIPMAP_NEAREST = 0x2701; NEAREST_MIPMAP_LINEAR = 0x2702; LINEAR_MIPMAP_LINEAR = 0x2703; REPEAT = 0x2901; CLAMP_TO_EDGE = 0x812F; MIRRORED_REPEAT = 0x8370; /** * @param {{ * instance: WebAssembly.Instance, * ctxHandle: number, * width: number, * height: number, * debugShaders: boolean, * sharedTable: any, * tableAllocator: any * }} options */ constructor({ instance, ctxHandle, width, height, debugShaders = false, sharedTable = null, tableAllocator = null, turboGlobals = null }) { this._instance = instance; this._ctxHandle = ctxHandle; this._destroyed = false; /** @type {import('./webgl2_resources.js').WasmWebGLProgram | null} */ this._currentProgram = null; // Explicit booleans for clarity this._debugShaders = !!debugShaders; this._drawingBufferWidth = width; this._drawingBufferHeight = height; this._sharedTable = sharedTable; this._tableAllocator = tableAllocator; // TODO: potentially retrieve those one demand from the main WASM module when shader WASM modules are initialised this._turboGlobals = turboGlobals; /** @type {Map<number, WasmWebGLTexture>} */ this._textureHandles = new Map(); /** @type {Map<number, WasmWebGLFramebuffer>} */ this._fbHandles = new Map(); /** @type {Map<number, WasmWebGLRenderbuffer>} */ this._rbHandles = new Map(); /** @type {Map<number, WasmWebGLBuffer>} */ this._bufferHandles = new Map(); /** @type {Map<number, WasmWebGLProgram>} */ this._programHandles = new Map(); /** @type {Map<number, WasmWebGLShader>} */ this._shaderHandles = new Map(); /** @type {Map<number, WasmWebGLVertexArrayObject>} */ this._vaoHandles = new Map(); /** @type {Map<number, WasmWebGLSampler>} */ this._samplerHandles = new Map(); /** @type {Map<number, WasmWebGLQuery>} */ this._queryHandles = new Map(); /** @type {Map<number, WasmWebGLSync>} */ this._syncHandles = new Map(); /** @type {Map<number, WasmWebGLTransformFeedback>} */ this._tfHandles = new Map(); WasmWebGL2RenderingContext._contexts.set(this._ctxHandle, this); } get drawingBufferWidth() { return this._drawingBufferWidth; } get drawingBufferHeight() { return this._drawingBufferHeight; } resize(width, height) { this._assertNotDestroyed(); const ex = this._instance.exports; if (!ex || typeof ex.wasm_ctx_resize !== 'function') { throw new Error('wasm_ctx_resize not found'); } const code = ex.wasm_ctx_resize(this._ctxHandle, width, height); _checkErr(code, this._instance); this._drawingBufferWidth = width; this._drawingBufferHeight = height; } // Set the viewport for rendering viewport(x, y, width, height) { this._assertNotDestroyed(); const ex = this._instance.exports; if (!ex || typeof ex.wasm_ctx_viewport !== 'function') { throw new Error('wasm_ctx_viewport not found'); } const code = ex.wasm_ctx_viewport(this._ctxHandle, x | 0, y | 0, width >>> 0, height >>> 0); _checkErr(code, this._instance); } /** @type {Map<number, WasmWebGL2RenderingContext>} */ static _contexts = new Map(); destroy() { if (this._destroyed) return; WasmWebGL2RenderingContext._contexts.delete(this._ctxHandle); const ex = this._instance.exports; if (ex && typeof ex.wasm_destroy_context === 'function') { const code = ex.wasm_destroy_context(this._ctxHandle); _checkErr(code, this._instance); } this._destroyed = true; } _assertNotDestroyed() { if (this._destroyed) throw new Error('context has been destroyed'); } createTexture() { this._assertNotDestroyed(); const ex = this._instance.exports; if (!ex || typeof ex.wasm_ctx_create_texture !== 'function') { throw new Error('wasm_ctx_create_texture not found'); } const handle = ex.wasm_ctx_create_texture(this._ctxHandle); if (handle === 0) { const msg = readErrorMessage(this._instance); throw new Error(`Failed to create texture: ${msg}`); } // Return a thin wrapper object representing the texture. const tex = new WasmWebGLTexture(this, handle); this._textureHandles.set(handle, tex); return tex; } deleteTexture(tex) { this._assertNotDestroyed(); const ex = this._instance.exports; if (!ex || typeof ex.wasm_ctx_delete_texture !== 'function') { throw new Error('wasm_ctx_delete_texture not found'); } const handle = tex && typeof tex === 'object' && typeof tex._handle === 'number' ? tex._handle : (tex >>> 0); const code = ex.wasm_ctx_delete_texture(this._ctxHandle, handle); _checkErr(code, this._instance); this._textureHandles.delete(handle); // If a wrapper object was passed, mark it as deleted. if (tex && typeof tex === 'object') { try { tex._handle = 0; tex._deleted = true; } catch (e) { /* ignore */ } } } bindTexture(target, tex) { this._assertNotDestroyed(); const ex = this._instance.exports; if (!ex || typeof ex.wasm_ctx_bind_texture !== 'function') { throw new Error('wasm_ctx_bind_texture not found'); } const handle = tex && typeof tex === 'object' && typeof tex._handle === 'number' ? tex._handle : (tex >>> 0); const code = ex.wasm_ctx_bind_texture(this._ctxHandle, target >>> 0, handle); _checkErr(code, this._instance); // Record bound texture in JS so we can map units to texture data for texel fetch this._boundTexture = handle; this._textureUnits = this._textureUnits || []; const unit = this._activeTextureUnit || 0; this._textureUnits[unit] = handle; } texImage2D(target, level, internalFormat, width, height, border, format, type_, pixels) { this._assertNotDestroyed(); const ex = this._instance.exports; if (!ex || typeof ex.wasm_ctx_tex_image_2d !== 'function') { throw new Error('wasm_ctx_tex_image_2d not found'); } let data = pixels; if (!data) { data = new Uint8Array(width * height * getBPP(internalFormat)); } else if (ArrayBuffer.isView(data)) { data = new Uint8Array(data.buffer, data.byteOffset, data.byteLength); } else if (data instanceof ArrayBuffer) { data = new Uint8Array(data); } const len = data.length; const ptr = ex.wasm_alloc(len); if (ptr === 0) throw new Error('Failed to allocate memory for pixel data'); try { const mem = new Uint8Array(ex.memory.buffer); mem.set(data, ptr); const code = ex.wasm_ctx_tex_image_2d( this._ctxHandle, target >>> 0, level >>> 0, internalFormat >>> 0, width >>> 0, height >>> 0, border >>> 0, format >>> 0, type_ >>> 0, ptr >>> 0, len >>> 0 ); _checkErr(code, this._instance); } finally { ex.wasm_free(ptr); } } texImage3D(target, level, internalFormat, width, height, depth, border, format, type_, pixels) { this._assertNotDestroyed(); const ex = this._instance.exports; if (!ex || typeof ex.wasm_ctx_tex_image_3d !== 'function') { throw new Error('wasm_ctx_tex_image_3d not found'); } let data = pixels; if (!data) { data = new Uint8Array(width * height * depth * getBPP(internalFormat)); } else if (ArrayBuffer.isView(data)) { data = new Uint8Array(data.buffer, data.byteOffset, data.byteLength); } else if (data instanceof ArrayBuffer) { data = new Uint8Array(data); } const len = data.length; const ptr = ex.wasm_alloc(len); if (ptr === 0) throw new Error('Failed to allocate memory for pixel data'); try { const mem = new Uint8Array(ex.memory.buffer); mem.set(data, ptr); const code = ex.wasm_ctx_tex_image_3d( this._ctxHandle, target >>> 0, level >>> 0, internalFormat >>> 0, width >>> 0, height >>> 0, depth >>> 0, border >>> 0, format >>> 0, type_ >>> 0, ptr >>> 0, len >>> 0 ); _checkErr(code, this._instance); } finally { ex.wasm_free(ptr); } } copyTexImage2D(target, level, internalFormat, x, y, width, height, border) { this._assertNotDestroyed(); const ex = this._instance.exports; if (!ex || typeof ex.wasm_ctx_copy_tex_image_2d !== 'function') { throw new Error('wasm_ctx_copy_tex_image_2d not found'); } const code = ex.wasm_ctx_copy_tex_image_2d( this._ctxHandle, target >>> 0, level >>> 0, internalFormat >>> 0, x | 0, y | 0, width >>> 0, height >>> 0, border >>> 0 ); _checkErr(code, this._instance); } createFramebuffer() { this._assertNotDestroyed(); const ex = this._instance.exports; if (!ex || typeof ex.wasm_ctx_create_framebuffer !== 'function') { throw new Error('wasm_ctx_create_framebuffer not found'); } const handle = ex.wasm_ctx_create_framebuffer(this._ctxHandle); if (handle === 0) { const msg = readErrorMessage(this._instance); throw new Error(`Failed to create framebuffer: ${msg}`); } const fb = new WasmWebGLFramebuffer(this, handle); this._fbHandles.set(handle, fb); return fb; } deleteFramebuffer(fb) { this._assertNotDestroyed(); const ex = this._instance.exports; if (!ex || typeof ex.wasm_ctx_delete_framebuffer !== 'function') { throw new Error('wasm_ctx_delete_framebuffer not found'); } const handle = fb && typeof fb === 'object' && typeof fb._handle === 'number' ? fb._handle : (fb >>> 0); const code = ex.wasm_ctx_delete_framebuffer(this._ctxHandle, handle); _checkErr(code, this._instance); this._fbHandles.delete(handle); if (fb && typeof fb === 'object') { try { fb._handle = 0; fb._deleted = true; } catch (e) { /* ignore */ } } } bindFramebuffer(target, fb) { this._assertNotDestroyed(); const ex = this._instance.exports; if (!ex || typeof ex.wasm_ctx_bind_framebuffer !== 'function') { throw new Error('wasm_ctx_bind_framebuffer not found'); } const handle = fb && typeof fb === 'object' && typeof fb._handle === 'number' ? fb._handle : (fb >>> 0); const code = ex.wasm_ctx_bind_framebuffer(this._ctxHandle, target >>> 0, handle); _checkErr(code, this._instance); } framebufferTexture2D(target, attachment, textarget, texture, level) { this._assertNotDestroyed(); const ex = this._instance.exports; if (!ex || typeof ex.wasm_ctx_framebuffer_texture2d !== 'function') { throw new Error('wasm_ctx_framebuffer_texture2d not found'); } const texHandle = texture && typeof texture === 'object' && typeof texture._handle === 'number' ? texture._handle : (texture >>> 0); const code = ex.wasm_ctx_framebuffer_texture2d( this._ctxHandle, target >>> 0, attachment >>> 0, textarget >>> 0, texHandle, level >>> 0 ); _checkErr(code, this._instance); } createRenderbuffer() { this._assertNotDestroyed(); const ex = this._instance.exports; if (!ex || typeof ex.wasm_ctx_create_renderbuffer !== 'function') { throw new Error('wasm_ctx_create_renderbuffer not found'); } const handle = ex.wasm_ctx_create_renderbuffer(this._ctxHandle); if (handle === 0) { const msg = readErrorMessage(this._instance); throw new Error(`Failed to create renderbuffer: ${msg}`); } const rb = new WasmWebGLRenderbuffer(this, handle); this._rbHandles.set(handle, rb); return rb; } bindRenderbuffer(target, renderbuffer) { this._assertNotDestroyed(); const ex = this._instance.exports; if (!ex || typeof ex.wasm_ctx_bind_renderbuffer !== 'function') { throw new Error('wasm_ctx_bind_renderbuffer not found'); } const rbHandle = renderbuffer && typeof renderbuffer === 'object' && typeof renderbuffer._handle === 'number' ? renderbuffer._handle : (renderbuffer >>> 0); const code = ex.wasm_ctx_bind_renderbuffer(this._ctxHandle, target >>> 0, rbHandle); _checkErr(code, this._instance); } deleteRenderbuffer(renderbuffer) { this._assertNotDestroyed(); const ex = this._instance.exports; if (!ex || typeof ex.wasm_ctx_delete_renderbuffer !== 'function') { throw new Error('wasm_ctx_delete_renderbuffer not found'); } const rbHandle = renderbuffer && typeof renderbuffer === 'object' && typeof renderbuffer._handle === 'number' ? renderbuffer._handle : (renderbuffer >>> 0); const code = ex.wasm_ctx_delete_renderbuffer(this._ctxHandle, rbHandle); _checkErr(code, this._instance); this._rbHandles.delete(rbHandle); if (renderbuffer && typeof renderbuffer === 'object') { try { renderbuffer._handle = 0; renderbuffer._deleted = true; } catch (e) { /* ignore */ } } } isRenderbuffer(rb) { this._assertNotDestroyed(); if (!rb || typeof rb !== 'object' || !(rb instanceof WasmWebGLRenderbuffer)) return false; if (rb._ctx !== this) return false; const ex = this._instance.exports; return ex.wasm_ctx_is_renderbuffer(this._ctxHandle, rb._handle) !== 0; } renderbufferStorage(target, internalFormat, width, height) { this._assertNotDestroyed(); const ex = this._instance.exports; if (!ex || typeof ex.wasm_ctx_renderbuffer_storage !== 'function') { throw new Error('wasm_ctx_renderbuffer_storage not found'); } const code = ex.wasm_ctx_renderbuffer_storage(this._ctxHandle, target >>> 0, internalFormat >>> 0, width | 0, height | 0); _checkErr(code, this._instance); } framebufferRenderbuffer(target, attachment, renderbuffertarget, renderbuffer) { this._assertNotDestroyed(); const ex = this._instance.exports; if (!ex || typeof ex.wasm_ctx_framebuffer_renderbuffer !== 'function') { throw new Error('wasm_ctx_framebuffer_renderbuffer not found'); } const rbHandle = renderbuffer && typeof renderbuffer === 'object' && typeof renderbuffer._handle === 'number' ? renderbuffer._handle : (renderbuffer >>> 0); const code = ex.wasm_ctx_framebuffer_renderbuffer( this._ctxHandle, target >>> 0, attachment >>> 0, renderbuffertarget >>> 0, rbHandle ); _checkErr(code, this._instance); } readPixels(x, y, width, height, format, type_, out) { this._assertNotDestroyed(); const ex = this._instance.exports; if (!ex || typeof ex.wasm_ctx_read_pixels !== 'function') { throw new Error('wasm_ctx_read_pixels not found'); } const ptr = ex.wasm_ctx_read_pixels( this._ctxHandle, x | 0, y | 0, width >>> 0, height >>> 0, format >>> 0, type_ >>> 0 ); if (ptr === 0) { const msg = readErrorMessage(this._instance); throw new Error(`readPixels failed: ${msg}`); } const dv = new DataView(ex.memory.buffer); const len = dv.getUint32(ptr - 16, true); if (!out || out.byteLength < len) { throw new Error(`output buffer too small (need ${len} bytes, have ${out ? out.byteLength : 0})`); } const src = new Uint8Array(ex.memory.buffer, ptr, len); const out_bytes = new Uint8Array(out.buffer, out.byteOffset, len); out_bytes.set(src); } // --- Stubs for unimplemented WebGL2 methods (forwarding API surface) --- // These are intentionally not implemented in the prototype. They allow // callers to detect missing functionality early with a uniform error. createShader(type) { this._assertNotDestroyed(); const ex = this._instance.exports; if (!ex || typeof ex.wasm_ctx_create_shader !== 'function') { throw new Error('wasm_ctx_create_shader not found'); } const handle = ex.wasm_ctx_create_shader(this._ctxHandle, type >>> 0); if (handle === 0) { const msg = readErrorMessage(this._instance); throw new Error(`Failed to create shader: ${msg}`); } return new WasmWebGLShader(this, handle); } shaderSource(shader, source) { this._assertNotDestroyed(); const ex = this._instance.exports; if (!ex || typeof ex.wasm_ctx_shader_source !== 'function') { throw new Error('wasm_ctx_shader_source not found'); } const shaderHandle = shader && typeof shader === 'object' && typeof shader._handle === 'number' ? shader._handle : (shader >>> 0); const sourceStr = String(source); const bytes = new TextEncoder().encode(sourceStr); const len = bytes.length; const ptr = ex.wasm_alloc(len); if (ptr === 0) throw new Error('Failed to allocate memory for shaderSource'); try { const mem = new Uint8Array(ex.memory.buffer); mem.set(bytes, ptr); const code = ex.wasm_ctx_shader_source(this._ctxHandle, shaderHandle, ptr, len); _checkErr(code, this._instance); } finally { ex.wasm_free(ptr); } } compileShader(shader) { this._assertNotDestroyed(); const ex = this._instance.exports; if (!ex || typeof ex.wasm_ctx_compile_shader !== 'function') { throw new Error('wasm_ctx_compile_shader not found'); } const shaderHandle = shader && typeof shader === 'object' && typeof shader._handle === 'number' ? shader._handle : (shader >>> 0); const code = ex.wasm_ctx_compile_shader(this._ctxHandle, shaderHandle); _checkErr(code, this._instance); } deleteShader(shader) { this._assertNotDestroyed(); const ex = this._instance.exports; if (!ex || typeof ex.wasm_ctx_delete_shader !== 'function') { throw new Error('wasm_ctx_delete_shader not found'); } const shaderHandle = shader && typeof shader === 'object' && typeof shader._handle === 'number' ? shader._handle : (shader >>> 0); const code = ex.wasm_ctx_delete_shader(this._ctxHandle, shaderHandle); _checkErr(code, this._instance); if (shader && typeof shader === 'object') { try { shader._handle = 0; shader._deleted = true; } catch (e) { /* ignore */ } } } createProgram() { this._assertNotDestroyed(); const ex = this._instance.exports; if (!ex || typeof ex.wasm_ctx_create_program !== 'function') { throw new Error('wasm_ctx_create_program not found'); } const handle = ex.wasm_ctx_create_program(this._ctxHandle); if (handle === 0) { const msg = readErrorMessage(this._instance); throw new Error(`Failed to create program: ${msg}`); } return new WasmWebGLProgram(this, handle); } attachShader(program, shader) { this._assertNotDestroyed(); const ex = this._instance.exports; if (!ex || typeof ex.wasm_ctx_attach_shader !== 'function') { throw new Error('wasm_ctx_attach_shader not found'); } const programHandle = program && typeof program === 'object' && typeof program._handle === 'number' ? program._handle : (program >>> 0); const shaderHandle = shader && typeof shader === 'object' && typeof shader._handle === 'number' ? shader._handle : (shader >>> 0); const code = ex.wasm_ctx_attach_shader(this._ctxHandle, programHandle, shaderHandle); _checkErr(code, this._instance); } detachShader(program, shader) { this._assertNotDestroyed(); throw new Error('not implemented'); } getActiveUniform(program, index) { this._assertNotDestroyed(); const ex = this._instance.exports; if (!ex || typeof ex.wasm_ctx_get_active_uniform !== 'function') { throw new Error('wasm_ctx_get_active_uniform not found'); } const programHandle = program && typeof program === 'object' && typeof program._handle === 'number' ? program._handle : (program >>> 0); const ptr = ex.wasm_ctx_get_active_uniform(this._ctxHandle, programHandle, index >>> 0); if (ptr === 0) return null; const dv = new DataView(ex.memory.buffer); const totalLen = dv.getUint32(ptr - 16, true); const size = dv.getInt32(ptr, true); const type_ = dv.getUint32(ptr + 4, true); const nameLen = totalLen - 8; const nameBytes = new Uint8Array(ex.memory.buffer, ptr + 8, nameLen); const name = new TextDecoder('utf-8').decode(nameBytes); return { name, size, type: type_ }; } getActiveAttrib(program, index) { this._assertNotDestroyed(); const ex = this._instance.exports; if (!ex || typeof ex.wasm_ctx_get_active_attrib !== 'function') { throw new Error('wasm_ctx_get_active_attrib not found'); } const programHandle = program && typeof program === 'object' && typeof program._handle === 'number' ? program._handle : (program >>> 0); const ptr = ex.wasm_ctx_get_active_attrib(this._ctxHandle, programHandle, index >>> 0); if (ptr === 0) return null; const dv = new DataView(ex.memory.buffer); const totalLen = dv.getUint32(ptr - 16, true); const size = dv.getInt32(ptr, true); const type_ = dv.getUint32(ptr + 4, true); const nameLen = totalLen - 8; const nameBytes = new Uint8Array(ex.memory.buffer, ptr + 8, nameLen); const name = new TextDecoder('utf-8').decode(nameBytes); return { name, size, type: type_ }; } linkProgram(program) { this._assertNotDestroyed(); const ex = this._instance.exports; if (!ex || typeof ex.wasm_ctx_link_program !== 'function') { throw new Error('wasm_ctx_link_program not found'); } const programHandle = program && typeof program === 'object' && typeof program._handle === 'number' ? program._handle : (program >>> 0); const code = ex.wasm_ctx_link_program(this._ctxHandle, programHandle); _checkErr(code, this._instance); // After linking, we need to instantiate the WASM modules on the host. if (program && typeof program === 'object') { const linkStatus = this.getProgramParameter(program, this.LINK_STATUS); if (linkStatus) { this._instantiateProgramShaders(program); } } } _instantiateProgramShaders(program) { const vsWasm = this.getProgramWasm(program, this.VERTEX_SHADER); const fsWasm = this.getProgramWasm(program, this.FRAGMENT_SHADER); if (!vsWasm || !fsWasm) { return; } // Allocate table slots for both shaders const vsIdx = this._tableAllocator ? this._tableAllocator.allocate() : null; const fsIdx = this._tableAllocator ? this._tableAllocator.allocate() : null; const createDebugEnv = (type, instanceRef) => { if (!this._debugShaders) return {}; const stubCode = this.getProgramDebugStub(program, type); if (!stubCode) return {}; // // Add sourceURL for debugging // const debugName = `shader_stub_program_${program._handle}_${type === this.VERTEX_SHADER ? 'vs' : 'fs'}.js`; // const codeWithUrl = stubCode + `\n//# sourceURL=${debugName}`; let stubFuncs; try { // Eval the stub array stubFuncs = (0, eval)(stubCode); } catch (e) { console.error("Failed to eval debug stub:", e); return {}; } return { debug_step: (line, funcIdx, resultPtr) => { if (line === 999999) { return; } const func = stubFuncs[line - 1]; if (func) { const ctx = { go: () => { // Trampoline logic would go here // For now we rely on WASM calling the function after debug_step returns } }; try { func.call(ctx); } catch (e) { console.error("Error in debug stub:", e); } } } }; }; let vsModule; vsModule = new WebAssembly.Module(vsWasm); const vsInstanceRef = { current: null }; const vsDebugEnv = createDebugEnv(this.VERTEX_SHADER, vsInstanceRef); const env = { memory: this._instance.exports.memory, __indirect_function_table: this._sharedTable, ACTIVE_ATTR_PTR: this._turboGlobals.ACTIVE_ATTR_PTR, ACTIVE_UNIFORM_PTR: this._turboGlobals.ACTIVE_UNIFORM_PTR, ACTIVE_VARYING_PTR: this._turboGlobals.ACTIVE_VARYING_PTR, ACTIVE_PRIVATE_PTR: this._turboGlobals.ACTIVE_PRIVATE_PTR, ACTIVE_TEXTURE_PTR: this._turboGlobals.ACTIVE_TEXTURE_PTR, ACTIVE_FRAME_SP: this._turboGlobals.ACTIVE_FRAME_SP, ...vsDebugEnv }; // Add math builtins from renderer (skipping host) const mathFuncs = [ 'gl_sin', 'gl_cos', 'gl_tan', 'gl_asin', 'gl_acos', 'gl_atan', 'gl_atan2', 'gl_exp', 'gl_exp2', 'gl_log', 'gl_log2', 'gl_pow', 'gl_sinh', 'gl_cosh', 'gl_tanh', 'gl_asinh', 'gl_acosh', 'gl_atanh', 'gl_inverse_mat2', 'gl_inverse_mat3', 'gl_debug4' ]; for (const name of mathFuncs) { if (this._instance.exports[name]) { env[name] = this._instance.exports[name]; } } program._vsInstance = new WebAssembly.Instance(vsModule, { env }); vsInstanceRef.current = program._vsInstance; // Register in table if (this._sharedTable && vsIdx !== null && program._vsInstance.exports.main) { this._sharedTable.set(vsIdx, program._vsInstance.exports.main); program._vsTableIndex = vsIdx; } let fsModule; // Dump WASM to disk when debug_shaders is enabled to aid diagnostics if (this._debugShaders) { try { require('fs').writeFileSync('test_debug/failing_fragment.wasm', Buffer.from(fsWasm)); } catch (e) { // Best-effort; ignore write failures in constrained environments } } if (this._debug) { console.log("Compiling FS Module..."); } fsModule = new WebAssembly.Module(fsWasm); const fsInstanceRef = { current: null }; const fsDebugEnv = createDebugEnv(this.FRAGMENT_SHADER, fsInstanceRef); const fsEnv = { memory: this._instance.exports.memory, __indirect_function_table: this._sharedTable, ACTIVE_ATTR_PTR: this._turboGlobals.ACTIVE_ATTR_PTR, ACTIVE_UNIFORM_PTR: this._turboGlobals.ACTIVE_UNIFORM_PTR, ACTIVE_VARYING_PTR: this._turboGlobals.ACTIVE_VARYING_PTR, ACTIVE_PRIVATE_PTR: this._turboGlobals.ACTIVE_PRIVATE_PTR, ACTIVE_TEXTURE_PTR: this._turboGlobals.ACTIVE_TEXTURE_PTR, ACTIVE_FRAME_SP: this._turboGlobals.ACTIVE_FRAME_SP, ...fsDebugEnv }; for (const name of mathFuncs) { if (this._instance.exports[name]) { fsEnv[name] = this._instance.exports[name]; } } if (!fsEnv.gl_debug4) fsEnv.gl_debug4 = (a, b, c, d) => {}; if (!fsEnv.gl_inverse_mat2) fsEnv.gl_inverse_mat2 = (in_ptr, out_ptr) => {}; if (!fsEnv.gl_inverse_mat3) fsEnv.gl_inverse_mat3 = (in_ptr, out_ptr) => {}; program._fsInstance = new WebAssembly.Instance(fsModule, { env: fsEnv }); fsInstanceRef.current = program._fsInstance; // Register in table if (this._sharedTable && fsIdx !== null && program._fsInstance.exports.main) { this._sharedTable.set(fsIdx, program._fsInstance.exports.main); program._fsTableIndex = fsIdx; } // Notify Rust of table indices (requires Phase 4) if (vsIdx !== null && fsIdx !== null) { const ex = this._instance.exports; if (ex.wasm_ctx_register_shader_indices) { ex.wasm_ctx_register_shader_indices( this._ctxHandle, program._handle, vsIdx, fsIdx ); } } } getProgramDebugStub(program, shaderType) { this._assertNotDestroyed(); const ex = this._instance.exports; if (!ex || typeof ex.wasm_ctx_get_program_debug_stub !== 'function') { return null; } const programHandle = program && typeof program === 'object' && typeof program._handle === 'number' ? program._handle : (program >>> 0); const len = ex.wasm_ctx_get_program_debug_stub(this._ctxHandle, programHandle, shaderType, 0, 0); if (len === 0) return null; const ptr = ex.wasm_alloc(len); if (ptr === 0) return null; try { const actualLen = ex.wasm_ctx_get_program_debug_stub(this._ctxHandle, programHandle, shaderType, ptr, len); const mem = new Uint8Array(ex.memory.buffer); const bytes = mem.subarray(ptr, ptr + actualLen); return new TextDecoder().decode(bytes); } finally { ex.wasm_free(ptr); } } deleteProgram(program) { this._assertNotDestroyed(); const ex = this._instance.exports; if (!ex || typeof ex.wasm_ctx_delete_program !== 'function') { throw new Error('wasm_ctx_delete_program not found'); } const programHandle = program && typeof program === 'object' && typeof program._handle === 'number' ? program._handle : (program >>> 0); // Table indices are now freed by Rust when the Program's refcount reaches zero. // This allows bound programs to remain valid even if deleted by the user, // as required by the WebGL specification. const code = ex.wasm_ctx_delete_program(this._ctxHandle, programHandle); _checkErr(code, this._instance); if (program && typeof program === 'object') { try { program._handle = 0; program._deleted = true; } catch (e) { /* ignore */ } } } useProgram(program) { this._assertNotDestroyed(); const ex = this._instance.exports; if (!ex || typeof ex.wasm_ctx_use_program !== 'function') { throw new Error('wasm_ctx_use_program not found'); } const programHandle = program && typeof program === 'object' && typeof program._handle === 'number' ? program._handle : (program >>> 0); const code = ex.wasm_ctx_use_program(this._ctxHandle, programHandle); _checkErr(code, this._instance); this._currentProgram = program; } getShaderParameter(shader, pname) { this._assertNotDestroyed(); const ex = this._instance.exports; if (!ex || typeof ex.wasm_ctx_get_shader_parameter !== 'function') { throw new Error('wasm_ctx_get_shader_parameter not found'); } const shaderHandle = shader && typeof shader === 'object' && typeof shader._handle === 'number' ? shader._handle : (shader >>> 0); const val = ex.wasm_ctx_get_shader_parameter(this._ctxHandle, shaderHandle, pname >>> 0); // WebGL returns boolean for status parameters if (pname === 0x8B81 /* COMPILE_STATUS */ || pname === 0x8B80 /* DELETE_STATUS */) { return !!val; } return val; } getProgramParameter(program, pname) { this._assertNotDestroyed(); const ex = this._instance.exports; if (!ex || typeof ex.wasm_ctx_get_program_parameter !== 'function') { throw new Error('wasm_ctx_get_program_parameter not found'); } const programHandle = program && typeof program === 'object' && typeof program._handle === 'number' ? program._handle : (program >>> 0); const val = ex.wasm_ctx_get_program_parameter(this._ctxHandle, programHandle, pname >>> 0); // WebGL returns boolean for status parameters if (pname === 0x8B82 /* LINK_STATUS */ || pname === 0x8B80 /* DELETE_STATUS */ || pname === 0x8B83 /* VALIDATE_STATUS */) { return !!val; } return val; } getShaderInfoLog(shader) { this._assertNotDestroyed(); const ex = this._instance.exports; if (!ex || typeof ex.wasm_ctx_get_shader_info_log !== 'function') { throw new Error('wasm_ctx_get_shader_info_log not found'); } const shaderHandle = shader && typeof shader === 'object' && typeof shader._handle === 'number' ? shader._handle : (shader >>> 0); const ptr = ex.wasm_ctx_get_shader_info_log(this._ctxHandle, shaderHandle); if (ptr === 0) return ""; const dv = new DataView(ex.memory.buffer); const len = dv.getUint32(ptr - 16, true); const mem = new Uint8Array(ex.memory.buffer); const bytes = mem.slice(ptr, ptr + len); return new TextDecoder().decode(bytes); } getProgramInfoLog(program) { this._assertNotDestroyed(); const ex = this._instance.exports; if (!ex || typeof ex.wasm_ctx_get_program_info_log !== 'function') { throw new Error('wasm_ctx_get_program_info_log not found'); } const programHandle = program && typeof program === 'object' && typeof program._handle === 'number' ? program._handle : (program >>> 0); const ptr = ex.wasm_ctx_get_program_info_log(this._ctxHandle, programHandle); if (ptr === 0) return ""; const dv = new DataView(ex.memory.buffer); const len = dv.getUint32(ptr - 16, true); const mem = new Uint8Array(ex.memory.buffer); const bytes = mem.slice(ptr, ptr + len); return new TextDecoder().decode(bytes); } getProgramWasm(program, shaderType) { this._assertNotDestroyed(); const ex = this._instance.exports; if (!ex || typeof ex.wasm_ctx_get_program_wasm_len !== 'function') { throw new Error('wasm_ctx_get_program_wasm_len not found'); } const programHandle = program && typeof program === 'object' && typeof program._handle === 'number' ? program._handle : (program >>> 0); const len = ex.wasm_ctx_get_program_wasm_len(this._ctxHandle, programHandle, shaderType); if (len === 0) return null; const ptr = ex.wasm_alloc(len); if (ptr === 0) throw new Error('Failed to allocate memory for getProgramWasm'); try { const actualLen = ex.wasm_ctx_get_program_wasm(this._ctxHandle, programHandle, shaderType, ptr, len); const mem = new Uint8Array(ex.memory.buffer); return new Uint8Array(mem.buffer, ptr, actualLen).slice(); } finally { ex.wasm_free(ptr); } } getAttribLocation(program, name) { this._assertNotDestroyed(); const ex = this._instance.exports; if (!ex || typeof ex.wasm_ctx_get_attrib_location !== 'function') { throw new Error('wasm_ctx_get_attrib_location not found'); } const programHandle = program && typeof program === 'object' && typeof program._handle === 'number' ? program._handle : (program >>> 0); const nameStr = String(name); const bytes = new TextEncoder().encode(nameStr); const len = bytes.length; const ptr = ex.wasm_alloc(len); if (ptr === 0) throw new Error('Failed to allocate memory for getAttribLocation'); try { const mem = new Uint8Array(ex.memory.buffer); mem.set(bytes, ptr); return ex.wasm_ctx_get_attrib_location(this._ctxHandle, programHandle, ptr, len); } finally { ex.wasm_free(ptr); } } bindAttribLocation(program, index, name) { this._assertNotDestroyed(); const ex = this._instance.exports; if (!ex || typeof ex.wasm_ctx_bind_attrib_location !== 'function') { throw new Error('wasm_ctx_bind_attrib_location not found'); } const programHandle = program && typeof program === 'object' && typeof program._handle === 'number' ? program._handle : (program >>> 0); const nameStr = String(name); const bytes = new TextEncoder().encode(nameStr); const len = bytes.length; const ptr = ex.wasm_alloc(len); if (ptr === 0) throw new Error('Failed to allocate memory for bindAttribLocation'); try { const mem = new Uint8Array(ex.memory.buffer); mem.set(bytes, ptr); const code = ex.wasm_ctx_bind_attrib_location(this._ctxHandle, programHandle, index >>> 0, ptr, len); _checkErr(code, this._instance); } finally { ex.wasm_free(ptr); } } enableVertexAttribArray(index) { this._assertNotDestroyed(); const ex = this._instance.exports; if (!ex || typeof ex.wasm_ctx_enable_vertex_attrib_array !== 'function') { throw new Error('wasm_ctx_enable_vertex_attrib_array not found'); } const code = ex.wasm_ctx_enable_vertex_attrib_array(this._ctxHandle, index >>> 0); _checkErr(code, this._instance); } disableVertexAttribArray(index) { this._assertNotDestroyed(); const ex = this._instance.exports; if (!ex || typeof ex.wasm_ctx_disable_vertex_attrib_array !== 'function') { throw new Error('wasm_ctx_disable_vertex_attrib_array not found'); } const code = ex.wasm_ctx_disable_vertex_attrib_array(this._ctxHandle, index >>> 0); _checkErr(code, this._instance); } vertexAttribPointer(index, size, type, normalized, stride, offset) { this._assertNotDestroyed(); const ex = this._instance.exports; if (!ex || typeof ex.wasm_ctx_vertex_attrib_pointer !== 'function') { throw new Error('wasm_ctx_vertex_attrib_pointer not found'); } const code = ex.wasm_ctx_vertex_attrib_pointer( this._ctxHandle, index >>> 0, size >>> 0, type >>> 0, normalized ? 1 : 0, stride >>> 0, offset >>> 0 ); if (code === 5) return; // ERR_GL _checkErr(code, this._instance); } vertexAttribIPointer(index, size, type, stride, offset) { this._assertNotDestroyed(); const ex = this._instance.exports; if (!ex || typeof ex.wasm_ctx_vertex_attrib_ipointer !== 'function') { throw new Error('wasm_ctx_vertex_attrib_ipointer not found'); } const code = ex.wasm_ctx_vertex_attrib_ipointer( this._ctxHandle, index >>> 0, size >>> 0, type >>> 0, stride >>> 0, offset >>> 0 ); if (code === 5) return; // ERR_GL _checkErr(code, this._instance); } vertexAttrib1f(index, v0) { this._assertNotDestroyed(); const ex = this._instance.exports; if (!ex || typeof ex.wasm_ctx_vertex_attrib1f !== 'function') { throw new Error('wasm_ctx_vertex_attrib1f not found'); } const code = ex.wasm_ctx_vertex_attrib1f(this._ctxHandle, index >>> 0, +v0); if (code === 5) return; // ERR_GL _checkErr(code, this._instance); } vertexAttrib2f(index, v0, v1) { this._assertNotDestroyed(); const ex = this._instance.exports; if (!ex || typeof ex.wasm_ctx_vertex_attrib2f !== 'function') { throw new Error('wasm_ctx_vertex_attrib2f not found'); } const code = ex.wasm_ctx_vertex_attrib2f(this._ctxHandle, index >>> 0, +v0, +v1); if (code === 5) return; // ERR_GL _checkErr(code, this._instance); } vertexAttrib3f(index, v0, v1, v2) { this._assertNotDestroyed(); const ex = this._instance.exports; if (!ex || typeof ex.wasm_ctx_vertex_attrib3f !== 'function') { throw new Error('wasm_ctx_vertex_attrib3f not found'); } const code = ex.wasm_ctx_vertex_attrib3f(this._ctxHandle, index >>> 0, +v0, +v1, +v2); if (code === 5) return; // ERR_GL _checkErr(code, this._instance); } vertexAttrib4f(index, v0, v1, v2, v3) { this._assertNotDestroyed(); const ex = this._instance.exports; if (!ex || typeof ex.wasm_ctx_vertex_attrib4f !== 'function') { throw new Error('wasm_ctx_vertex_attrib4f not found'); } const code = ex.wasm_ctx_vertex_attrib4f(this._ctxHandle, index >>> 0, +v0, +v1, +v2, +v3); if (code === 5) return; // ERR_GL _checkErr(code, this._instance); } vertexAttrib1fv(index, v) { if (v && v.length >= 1) { this.vertexAttrib1f(index, v[0]); } else { this._setError(0x0501); } } vertexAttrib2fv(index, v) { if (v && v.length >= 2) { this.vertexAttrib2f(index, v[0], v[1]); } else { this._setError(0x0501); } } vertexAttrib3fv(index, v) { if (v && v.length >= 3) { this.vertexAttrib3f(index, v[0], v[1], v[2]); } else { this._setError(0x0501); } } vertexAttrib4fv(index, v) { if (v && v.length >= 4) { this.vertexAttrib4f(index, v[0], v[1], v[2], v[3]); } else { this._setError(0x0501); } } vertexAttribI4i(index, v0, v1, v2, v3) { this._assertNotDestroyed(); const ex = this._instance.exports; if (!ex || typeof ex.wasm_ctx_vertex_attrib_i4i !== 'function') { throw new Error('wasm_ctx_vertex_attrib_i4i not found'); } const code = ex.wasm_ctx_vertex_attrib_i4i(this._ctxHandle, index >>> 0, v0 | 0, v1 | 0, v2 | 0, v3 | 0); if (code === 5) return; // ERR_GL _checkErr(code, this._instance); } vertexAttribI4ui(index, v0, v1, v2, v3) { this._assertNotDestroyed(); const ex = this._instance.exports; if (!ex || typeof ex.wasm_ctx_vertex_attrib_i4ui !== 'function') { throw new Error('wasm_ctx_vertex_attrib_i4ui not found'); } const code = ex.wasm_ctx_vertex_attrib_i4ui(this._ctxHandle, index >>> 0, v0 >>> 0, v1 >>> 0, v2 >>> 0, v3 >>> 0); if (code === 5) return; // ERR_GL _checkErr(code, this._instance); } vertexAttribI4iv(index, v) { if (v && v.length >= 4) { this.vertexAttribI4i(index, v[0], v[1], v[2], v[3]); } else { this._setError(0x0501); } } vertexAttribI4uiv(index, v) { if (v && v.length >= 4) { this.vertexAttribI4ui(index, v[0], v[1], v[2], v[3]); } else { this._setError(0x0501);