// This is an OpenGL implementation for SqueakJS using WebGL. // It is very much incomplete and currently only implements // the subset of OpenGL that is used by Croquet Jasmine, // but could be extended to support more. // The functions are invoked via FFI, which takes care of // converting the arguments and return values between JS // and Smalltalk. // The OpenGL context is global and created by B3DAcceleratorPlugin. // Context switching is done by B3DAcceleratorPlugin.makeCurrent(). // helpful constant lookup: // https://javagl.github.io/GLConstantsTranslator/GLConstantsTranslator.html // TODO // [X] implement draw arrays // [X] implement draw elements // [ ] implement vertex buffer objects // [X] implement material + lighting // [X] implement clip planes // [X] implement fog // [ ] implement tex coord gen // [ ] fix glBitmap for size other than 640x480 (also, make pixel perfect) // [ ] optimize list compilation glBegin/glEnd // [ ] implement light attenuation // [ ] implement spot lights // [ ] implement color material // [ ] emulate glLineWidth (WebGL usually only supports 1px lines) // e.g. using https://wwwtyro.net/2019/11/18/instanced-lines.html // [ ] full OpenGL 1.0 support // [ ] full OpenGL 1.1 support // [ ] full OpenGL 1.2 support // [ ] full OpenGL 1.3 support // [ ] full OpenGL 1.4 support // [ ] full OpenGL 1.5 support // [ ] some extensions? // OpenGL constants (many missing in WebGL) var GL; function OpenGL() { "use strict"; var DEBUG = 0; // 0 = off // 1 = some (errors, warnings) // 2 = lots (function calls) // 3 = all (call details) var identity = new Float32Array([ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, ]); // Primitive attributes for glBegin/glEnd var flagCounter = 0; var HAS_NORMAL = 1 << flagCounter++; var HAS_COLOR = 1 << flagCounter++; var HAS_TEXCOORD = 1 << flagCounter++; // additional flags for selecting shader var USE_TEXTURE = 1 << flagCounter++; var USE_ALPHA_TEST = 1 << flagCounter++; var USE_POINT_SIZE = 1 << flagCounter++; var NUM_LIGHTS_MASK = (1 << flagCounter++) // 3 bits for number of lights (0-7) + (1 << flagCounter++) + (1 << flagCounter++); var NUM_CLIP_PLANES_MASK = (1 << flagCounter++) // 3 bits for number of clip planes (0-5) + (1 << flagCounter++) + (1 << flagCounter++); var FOG_MASK = (1 << flagCounter++) // 2 bits for fog mode (off, linear, exp, exp2) + (1 << flagCounter++); // this math is silly but fun ... var NUM_LIGHTS_SHIFT = Math.floor(Math.log2(NUM_LIGHTS_MASK)) - 2; var MAX_LIGHTS = (NUM_LIGHTS_MASK >> NUM_LIGHTS_SHIFT) + 1; var ANY_LIGHTS = (MAX_LIGHTS-1) << NUM_LIGHTS_SHIFT; if (ANY_LIGHTS !== NUM_LIGHTS_MASK) throw Error("OpenGL: bad NUM_LIGHTS_MASK"); var NUM_CLIP_PLANES_SHIFT = Math.floor(Math.log2(NUM_CLIP_PLANES_MASK)) - 2; var MAX_CLIP_PLANES = (NUM_CLIP_PLANES_MASK >> NUM_CLIP_PLANES_SHIFT) + 1; var ANY_CLIP_PLANES = (MAX_CLIP_PLANES-1) << NUM_CLIP_PLANES_SHIFT; if (ANY_CLIP_PLANES !== NUM_CLIP_PLANES_MASK) throw Error("OpenGL: bad NUM_CLIP_PLANES_MASK"); var FOG_SHIFT = Math.floor(Math.log2(FOG_MASK)) - 1; var MAX_FOG = (FOG_MASK >> FOG_SHIFT) + 1; var ANY_FOG = (MAX_FOG-1) << FOG_SHIFT; if (ANY_FOG !== FOG_MASK) throw Error("OpenGL: bad ANY_FOG"); var NO_FOG = 0; var LINEAR_FOG = 1; var EXP_FOG = 2; var EXP2_FOG = 3; var gl; // the emulated OpenGL state var webgl; // the actual WebGL context return { getModuleName: function() { return 'libGL.so (SqueakJS)'; }, setInterpreter: function(anInterpreterProxy) { this.vm = anInterpreterProxy.vm; this.ffi = this.vm.primHandler; return true; }, ffiFunctionNotFoundHandler: function(name, args) { this.vm.warnOnce("OpenGL: UNIMPLEMENTED (missing) " + name); if (DEBUG > 0) debugger; return null; // do not fail but return nil }, initialiseModule: function() { DEBUG > 1 && console.log("OpenGL: initialiseModule"); if (!GL) initGLConstants(); // connect to B3DAcceleratorPlugin to get WebGL context var modules = SqueakJS.vm.primHandler.loadedModules; var B3DAcceleratorPlugin = modules['B3DAcceleratorPlugin']; if (!B3DAcceleratorPlugin) throw Error("OpenGL: B3DAcceleratorPlugin not loaded"); this.GL = GL; B3DAcceleratorPlugin.setOpenGL(this); // will call makeCurrent() }, makeCurrent: function(renderer) { if (webgl === renderer.webgl) return; // already current DEBUG > 1 && console.log("OpenGL: makeCurrent", renderer); webgl = renderer.webgl; gl = renderer.opengl; if (!gl) renderer.opengl = this.initGL(); }, initGL: function() { DEBUG > 0 && console.log("OpenGL: initGL"); // if webgl-lint is loaded, configure it const ext = webgl.getExtension('GMAN_debug_helper'); if (ext) ext.setConfiguration({ throwOnError: false, }); // if Spector script is loaded, capture WebGL calls if (typeof SPECTOR !== "undefined") { var spector = new SPECTOR.Spector(); spector.captureContext(webgl); spector.displayUI(); } // initialize emulated OpenGL state gl = { alphaTest: false, alphaFunc: null, alphaRef: 0, extensions: "GL_ARB_texture_non_power_of_two GL_SGIS_generate_mipmap GL_ARB_transpose_matrix", color: new Float32Array(4), normal: new Float32Array([0, 0, 1]), texCoord: new Float32Array(2), primitive: null, // for glBegin/glEnd primitiveAttrs: 0, // for glVertex clipPlanes: [], // clip plane equations clientState: {}, // enabled arrays by attr fogMode: GL.EXP, // fog mode fogEnabled: false, // fog enabled fogDensity: 1, // fog density fogStart: 0, // fog start fogEnd: 1, // fog end fogColor: new Float32Array([0, 0, 0, 0]), // fog color fogHint: GL.DONT_CARE, // fog hint shaders: {}, // shader programs by attr/flags matrixMode: 0, // current matrix mode matrices: {}, // matrix stacks by mode matrix: null, // current matrix (matrices[mode][0]) lightingEnabled: false, lights: [], // light states lightModelAmbient: null, // scene ambient color material: null, // material state textureIdGen: 0, // texture id generator textures: {}, // webgl texture objects by id texture: null, // texture textureEnabled: false, // texture enabled textureEnvMode: GL.MODULATE, // texture environment mode listIdGen: 0, // display list id generator lists: {}, // display lists by id list: null, // current display list listMode: 0, // current display list mode listBase: 0, // base for glCallLists pixelStoreUnpackRowLength: 0, pixelStoreUnpackSkipRows: 0, pixelStoreUnpackSkipPixels: 0, rasterPos: new Float32Array(4), rasterColor: new Float32Array([1, 1, 1, 1]), bitmapTexture: null, // texture for glBitmap bitmapVertexBuffer: null, // vertex buffer for glBitmap bitmapShader: { // shader program for glBitmap program: null, locations: {}, }, // shader for glBitmap vendor: "Codefrau", renderer: "SqueakJS", version: "1.0", viewport: new Int32Array([0, 0, 0, 0]), depthRange: new Float32Array([0, 1]), }; // set initial state gl.matrices[GL.MODELVIEW] = [new Float32Array(identity)]; gl.matrices[GL.PROJECTION] = [new Float32Array(identity)]; gl.matrices[GL.TEXTURE] = [new Float32Array(identity)]; gl.matrixMode = GL.MODELVIEW; gl.matrix = gl.matrices[gl.matrixMode][0]; gl.color.set([1, 1, 1, 1]); for (var i = 0; i < MAX_CLIP_PLANES; i++) { gl.clipPlanes[i] = { enabled: false, equation: new Float32Array([0, 0, 0, 0]), }; } for (var i = 0; i < MAX_LIGHTS; i++) { gl.lights[i] = { enabled: false, ambient: new Float32Array([0, 0, 0, 1]), diffuse: new Float32Array(i === 0 ? [1, 1, 1, 1] : [0, 0, 0, 1]), specular: new Float32Array(i === 0 ? [1, 1, 1, 1] : [0, 0, 0, 1]), position: new Float32Array([0, 0, 1, 0]), }; } gl.lightModelAmbient = new Float32Array([0.2, 0.2, 0.2, 1]); gl.material = { ambient: new Float32Array([0.2, 0.2, 0.2, 1]), diffuse: new Float32Array([0.8, 0.8, 0.8, 1]), specular: new Float32Array([0, 0, 0, 1]), emission: new Float32Array([0, 0, 0, 1]), shininess: 0, }; var clientStates = ["vertexArray", "normalArray", "colorArray", "textureCoordArray"]; for (var i = 0; i < clientStates.length; i++) { var attr = clientStates[i]; gl.clientState[attr] = { enabled: false, size: 0, type: GL.FLOAT, stride: 0, pointer: null, // binding: null, TODO: support VBOs } } return gl; }, destroyGL: function(renderer) { DEBUG > 0 && console.log("OpenGL: destroyGL"); // TODO: delete textures, arrays, shaders? renderer.opengl = null; webgl = null; gl = null; }, // FFI functions get JS args, return JS result addToList: function(name, args) { if (!gl.list) return false; gl.list.commands.push({name: name, args: args}); if (gl.listMode === GL.COMPILE) { DEBUG > 1 && console.log("[COMPILE]", name, args); return true; } return false; }, glAlphaFunc: function(func, ref) { if (gl.listMode && this.addToList("glAlphaFunc", [func, ref])) return; DEBUG > 1 && console.log("glAlphaFunc", GL_Symbol(func), ref); gl.alphaFunc = func; gl.alphaRef = ref; }, glBegin: function(mode) { if (gl.listMode && this.addToList("glBegin", [mode])) return; DEBUG > 1 && console.log("glBegin", GL_Symbol(mode, 'POINTS')); gl.primitive = { mode: mode, vertices: [], vertexSize: 0, vertexAttrs: 0, } gl.primitiveAttrs = 0; }, glBindTexture: function(target, texture) { if (gl.listMode && this.addToList("glBindTexture", [target, texture])) return; DEBUG > 1 && console.log("glBindTexture", GL_Symbol(target), texture); var textureObj = gl.textures[texture]; if (!textureObj) throw Error("OpenGL: texture not found"); webgl.bindTexture(target, textureObj); gl.texture = textureObj; }, glBitmap: function(width, height, xorig, yorig, xmove, ymove, bitmap) { // bitmap is supposed to be declared as "GLubyte*" per OpenGL spec, // which the FFI would convert to Uint8Array for us. However, the // image FFI declaration uses "void*", probably because it makes no // difference in C, a pointer is a pointer. In JS, we get an // ArrayBuffer for "void*" so we need to convert it to Uint8Array // ourselves. if (!bitmap.buffer) bitmap = new Uint8Array(bitmap); if (gl.listMode && this.addToList("glBitmap", [width, height, xorig, yorig, xmove, ymove, bitmap])) return; DEBUG > 1 && console.log("glBitmap", width, height, xorig, yorig, xmove, ymove, bitmap); if (width > 0 && height > 0) { // we need to convert the 1-bit deep bitmap to a 1-byte // per pixel texture in ALPHA format, with the bitmap // mapping 0-bits to transparent, 1-bits to opaque, // and then draw it as a textured quad covering the viewport var texels = new Uint8Array(width * height); var bytesPerRow = Math.ceil(width / 32) * 4; for (var y = 0; y < height; y++) { var byteIndex = y * bytesPerRow; var bitIndex = 7; for (var x = 0; x < width; x++) { var bit = bitmap[byteIndex] & (1 << bitIndex); if (bit) texels[y * width + x] = 255; bitIndex--; if (bitIndex < 0) { byteIndex++; bitIndex = 7; } } } // debug: print bitmap // s=''; for (y = height -1 ; y >= 0; y--) { for (x = 0; x < width; x++) s += texels[y * width + x] ? '⬛️' : '⬜️'; s+='\n'}; console.log(s) var texture = gl.bitmapTexture; if (!texture) { texture = gl.bitmapTexture = webgl.createTexture(); webgl.bindTexture(webgl.TEXTURE_2D, texture); webgl.texParameteri(webgl.TEXTURE_2D, webgl.TEXTURE_MIN_FILTER, webgl.LINEAR); webgl.texParameteri(webgl.TEXTURE_2D, webgl.TEXTURE_MAG_FILTER, webgl.LINEAR); webgl.texParameteri(webgl.TEXTURE_2D, webgl.TEXTURE_WRAP_S, webgl.CLAMP_TO_EDGE); webgl.texParameteri(webgl.TEXTURE_2D, webgl.TEXTURE_WRAP_T, webgl.CLAMP_TO_EDGE); } else { webgl.bindTexture(webgl.TEXTURE_2D, texture); } webgl.pixelStorei(webgl.UNPACK_ALIGNMENT, 1); webgl.texImage2D(webgl.TEXTURE_2D, 0, webgl.ALPHA, width, height, 0, webgl.ALPHA, webgl.UNSIGNED_BYTE, texels); webgl.pixelStorei(webgl.UNPACK_ALIGNMENT, 4); webgl.disable(webgl.CULL_FACE); webgl.disable(webgl.DEPTH_TEST); webgl.disable(webgl.BLEND); webgl.colorMask(true, true, true, true); webgl.viewport(0, 0, webgl.drawingBufferWidth, webgl.drawingBufferHeight); var vertexBuffer = gl.bitmapVertexBuffer; if (!vertexBuffer) { var vertices = new Float32Array([ 0, 0, 1, 0, 0, 1, 1, 1, ]); vertexBuffer = gl.bitmapVertexBuffer = webgl.createBuffer(); webgl.bindBuffer(webgl.ARRAY_BUFFER, vertexBuffer); webgl.bufferData(webgl.ARRAY_BUFFER, vertices, webgl.STATIC_DRAW); } else { webgl.bindBuffer(webgl.ARRAY_BUFFER, vertexBuffer); } var shader = gl.bitmapShader; if (!shader.program) { shader.program = webgl.createProgram(); var vs = webgl.createShader(webgl.VERTEX_SHADER); webgl.shaderSource(vs, ` attribute vec2 a_position; uniform vec3 u_raster; uniform vec2 u_rasterOffset; uniform vec2 u_rasterScale; uniform vec2 u_translate; uniform vec2 u_scale; varying vec2 v_texcoord; void main() { vec2 raster = u_raster.xy * u_rasterScale + u_rasterOffset; vec2 pos = raster + a_position * u_scale + u_translate; gl_Position = vec4(pos, u_raster.z, 1); v_texcoord = a_position; } `); webgl.compileShader(vs); if (!webgl.getShaderParameter(vs, webgl.COMPILE_STATUS)) { console.error("OpenGL: vertex shader compile error: " + webgl.getShaderInfoLog(vs)); debugger; return; } var fs = webgl.createShader(webgl.FRAGMENT_SHADER); webgl.shaderSource(fs, ` precision mediump float; uniform sampler2D u_texture; uniform vec4 u_color; varying vec2 v_texcoord; void main() { float alpha = texture2D(u_texture, v_texcoord).a; if (alpha < 0.5) discard; gl_FragColor = u_color; } `); webgl.compileShader(fs); if (!webgl.getShaderParameter(fs, webgl.COMPILE_STATUS)) { console.error("OpenGL: fragment shader compile error: " + webgl.getShaderInfoLog(fs)); debugger; return; } webgl.attachShader(shader.program, vs); webgl.attachShader(shader.program, fs); webgl.linkProgram(shader.program); if (!webgl.getProgramParameter(shader.program, webgl.LINK_STATUS)) { console.error("OpenGL: shader link error: " + webgl.getProgramInfoLog(shader.program)); debugger return; } shader.locations = { a_position: webgl.getAttribLocation(shader.program, "a_position"), u_texture: webgl.getUniformLocation(shader.program, "u_texture"), u_color: webgl.getUniformLocation(shader.program, "u_color"), u_raster: webgl.getUniformLocation(shader.program, "u_raster"), u_rasterOffset: webgl.getUniformLocation(shader.program, "u_rasterOffset"), u_rasterScale: webgl.getUniformLocation(shader.program, "u_rasterScale"), u_translate: webgl.getUniformLocation(shader.program, "u_translate"), u_scale: webgl.getUniformLocation(shader.program, "u_scale"), }; } webgl.useProgram(shader.program); webgl.enableVertexAttribArray(shader.locations.a_position); webgl.vertexAttribPointer(shader.locations.a_position, 2, webgl.FLOAT, false, 0, 0); webgl.uniform1i(shader.locations.u_texture, 0); webgl.uniform4fv(shader.locations.u_color, gl.rasterColor); var w = webgl.drawingBufferWidth; var h = webgl.drawingBufferHeight; // this still isn't pixel perfect. Appears to work best for 640x480 // but not when changing the extent?! Weird. Also, some letters are still // cut off (like "m"). if (!this.bitmapRasterScale) this.bitmapRasterScale = [2/w, 2/h]; if (!this.bitmapScale) this.bitmapScale = [2*width/w, 2*height/h]; if (!this.bitmapTranslate) this.bitmapTranslate = [0, 0]; if (!this.bitmapRasterOffset) this.bitmapRasterOffset = [-1, -1]; // the properties above are written to allow intereactive debugging webgl.uniform3f(shader.locations.u_raster, gl.rasterPos[0] - xorig, gl.rasterPos[1] - yorig, gl.rasterPos[2]); webgl.uniform2fv(shader.locations.u_rasterOffset, this.bitmapRasterOffset); webgl.uniform2fv(shader.locations.u_rasterScale, this.bitmapRasterScale); webgl.uniform2fv(shader.locations.u_translate, this.bitmapTranslate); webgl.uniform2fv(shader.locations.u_scale, this.bitmapScale); webgl.drawArrays(webgl.TRIANGLE_STRIP, 0, 4); webgl.disableVertexAttribArray(shader.locations.a_position); webgl.bindBuffer(webgl.ARRAY_BUFFER, null); webgl.useProgram(null); webgl.bindTexture(webgl.TEXTURE_2D, null); webgl.enable(webgl.CULL_FACE); webgl.enable(webgl.DEPTH_TEST); webgl.enable(webgl.BLEND); } gl.rasterPos[0] += xmove; gl.rasterPos[1] += ymove; }, glBlendFunc: function(sfactor, dfactor) { if (gl.listMode && this.addToList("glBlendFunc", [sfactor, dfactor])) return; DEBUG > 1 && console.log("glBlendFunc", GL_Symbol(sfactor), GL_Symbol(dfactor)); webgl.blendFunc(sfactor, dfactor); }, glCallList: function(list) { if (gl.listMode && this.addToList("glCallList", [list])) return; DEBUG > 1 && console.log("glCallList", list, "START"); this.executeList(list); DEBUG > 1 && console.log("glCallList", list, "DONE"); }, glCallLists: function(n, type, lists) { if (gl.listMode && this.addToList("glCallLists", [n, type, lists])) return; DEBUG > 1 && console.log("glCallLists", n, GL_Symbol(type), lists); var array; switch (type) { case GL.BYTE: array = new Int8Array(lists); break; case GL.UNSIGNED_BYTE: array = new Uint8Array(lists); break; case GL.INT: array = new Int32Array(lists); break; case GL.UNSIGNED_INT: array = new Uint32Array(lists); break; default: if (DEBUG) console.log("UNIMPLEMENTED glCallLists type", GL_Symbol(type)) else this.vm.warnOnce("OpenGL: UNIMPLEMENTED glCallLists type " + GL_Symbol(type)); return; } for (var i = 0; i < n; i++) { var list = gl.listBase + array[i]; this.executeList(list); } }, glClear: function(mask) { if (gl.listMode && this.addToList("glClear", [mask])) return; var maskString = ""; if (mask & webgl.COLOR_BUFFER_BIT) maskString += " COLOR"; if (mask & webgl.DEPTH_BUFFER_BIT) maskString += " DEPTH"; if (mask & webgl.STENCIL_BUFFER_BIT) maskString += " STENCIL"; DEBUG > 1 && console.log("glClear"+ maskString); webgl.clear(mask); // B3DAcceleratorPlugin will call vm.breakNow() // to emulate double buffering (which will return // control to the browser which will flush the canvas). // We discourage breaking until then to avoid flicker // glClear is a good place for that since it's usually // called at least once per frame this.vm.breakAfter(500); }, glClearColor: function(red, green, blue, alpha) { if (gl.listMode && this.addToList("glClearColor", [red, green, blue, alpha])) return; DEBUG > 1 && console.log("glClearColor", red, green, blue, alpha); webgl.clearColor(red, green, blue, alpha); }, glColor3f: function(red, green, blue) { if (gl.listMode && this.addToList("glColor3f", [red, green, blue])) return; DEBUG > 1 && console.log("glColor3f", red, green, blue); gl.color[0] = red; gl.color[1] = green; gl.color[2] = blue; gl.color[3] = 1; gl.primitiveAttrs |= HAS_COLOR; }, glColor3fv: function(v) { if (gl.listMode && this.addToList("glColor3fv", [v.slice()])) return; DEBUG > 1 && console.log("glColor3fv", Array.from(v)); gl.color.set(v); gl.color[3] = 1; gl.primitiveAttrs |= HAS_COLOR; }, glColor4d: function(red, green, blue, alpha) { if (gl.listMode && this.addToList("glColor4d", [red, green, blue, alpha])) return; DEBUG > 1 && console.log("glColor4d", red, green, blue, alpha); gl.color[0] = red; gl.color[1] = green; gl.color[2] = blue; gl.color[3] = alpha; gl.primitiveAttrs |= HAS_COLOR; }, glColor4f: function(red, green, blue, alpha) { if (gl.listMode && this.addToList("glColor4f", [red, green, blue, alpha])) return; DEBUG > 1 && console.log("glColor4f", red, green, blue, alpha); gl.color[0] = red; gl.color[1] = green; gl.color[2] = blue; gl.color[3] = alpha; gl.primitiveAttrs |= HAS_COLOR; }, glColor4fv: function(v) { if (gl.listMode && this.addToList("glColor4fv", [v.slice()])) return; DEBUG > 1 && console.log("glColor4fv", Array.from(v)); gl.color.set(v); gl.primitiveAttrs |= HAS_COLOR; }, glColorPointer: function(size, type, stride, pointer) { if (gl.listMode && this.addToList("glColorPointer", [size, type, stride, pointer])) return; DEBUG > 1 && console.log("glColorPointer", size, GL_Symbol(type), stride, pointer); gl.clientState.colorArray.size = size; gl.clientState.colorArray.type = type; gl.clientState.colorArray.stride = stride; gl.clientState.colorArray.pointer = pointer; }, glColorMask: function(red, green, blue, alpha) { if (gl.listMode && this.addToList("glColorMask", [red, green, blue, alpha])) return; DEBUG > 1 && console.log("glColorMask", red, green, blue, alpha); webgl.colorMask(red, green, blue, alpha); }, glClipPlane: function(plane, equation) { if (gl.listMode && this.addToList("glClipPlane", [plane, equation])) return; DEBUG > 1 && console.log("glClipPlane", GL_Symbol(plane), Array.from(equation)); var clipPlane = gl.clipPlanes[plane - GL.CLIP_PLANE0]; // multiply by inverse of modelview matrix var m = new Float32Array(16); invertMatrix(gl.matrices[GL.MODELVIEW][0], m); transposeMatrix(m); multVec4(m, equation, clipPlane.equation); }, glDeleteLists: function(list, range) { DEBUG > 1 && console.log("glDeleteLists", list, range); for (var i = 0; i < range; i++) { delete gl.lists[list + i]; } }, glDeleteTextures: function(n, textures) { DEBUG > 1 && console.log("glDeleteTextures", n, Array.from(textures)); for (var i = 0; i < n; i++) { var id = textures[i]; var texture = gl.textures[id]; if (texture) { webgl.deleteTexture(texture); if (gl.texture === texture) gl.texture = null; } delete gl.textures[id]; } }, glDepthFunc: function(func) { if (gl.listMode && this.addToList("glDepthFunc", [func])) return; DEBUG > 1 && console.log("glDepthFunc", GL_Symbol(func)); webgl.depthFunc(func); }, glDepthMask: function(flag) { if (gl.listMode && this.addToList("glDepthMask", [flag])) return; DEBUG > 1 && console.log("glDepthMask", flag); webgl.depthMask(flag); }, glDepthRange: function(zNear, zFar) { if (gl.listMode && this.addToList("glDepthRange", [zNear, zFar])) return; DEBUG > 1 && console.log("glDepthRange", zNear, zFar); webgl.depthRange(zNear, zFar); }, glDisable: function(cap) { if (gl.listMode && this.addToList("glDisable", [cap])) return; switch (cap) { case GL.ALPHA_TEST: DEBUG > 1 && console.log("glDisable GL_ALPHA_TEST"); gl.alphaTest = false; break; case webgl.BLEND: DEBUG > 1 && console.log("glDisable GL_BLEND"); webgl.disable(webgl.BLEND); break; case GL.CLIP_PLANE0: case GL.CLIP_PLANE1: case GL.CLIP_PLANE2: case GL.CLIP_PLANE3: case GL.CLIP_PLANE4: case GL.CLIP_PLANE5: case GL.CLIP_PLANE6: case GL.CLIP_PLANE7: DEBUG > 1 && console.log("glDisable GL_CLIP_PLANE" + (cap - GL.CLIP_PLANE0)); gl.clipPlanes[cap - GL.CLIP_PLANE0].enabled = false; break; case webgl.CULL_FACE: DEBUG > 1 && console.log("glDisable GL.CULL_FACE"); webgl.disable(webgl.CULL_FACE); break; case webgl.DEPTH_TEST: DEBUG > 1 && console.log("glDisable GL_DEPTH_TEST"); webgl.disable(webgl.DEPTH_TEST); break; case GL.FOG: DEBUG > 1 && console.log("glDisable GL_FOG"); gl.fogEnabled = false; break; case GL.NORMALIZE: DEBUG > 1 && console.log("glDisable GL_NORMALIZE"); // we always normalize normals break; case GL.LIGHT0: case GL.LIGHT1: case GL.LIGHT2: case GL.LIGHT3: case GL.LIGHT4: case GL.LIGHT5: case GL.LIGHT6: case GL.LIGHT7: DEBUG > 1 && console.log("glDisable GL_LIGHT" + (cap - GL.LIGHT0)); gl.lights[cap - GL.LIGHT0].enabled = false; break; case GL.LIGHTING: DEBUG > 1 && console.log("glDisable GL_LIGHTING"); gl.lightingEnabled = false; break; case webgl.POLYGON_OFFSET_FILL: DEBUG > 1 && console.log("glDisable GL_POLYGON_OFFSET_FILL"); webgl.disable(webgl.POLYGON_OFFSET_FILL); break; case webgl.STENCIL_TEST: DEBUG > 1 && console.log("glDisable GL_STENCIL_TEST"); webgl.disable(webgl.STENCIL_TEST); break; case webgl.TEXTURE_2D: DEBUG > 1 && console.log("glDisable GL_TEXTURE_2D"); gl.textureEnabled = false; break; case GL.TEXTURE_GEN_S: case GL.TEXTURE_GEN_T: case GL.TEXTURE_GEN_R: case GL.TEXTURE_GEN_Q: if (DEBUG) console.log("UNIMPLEMENTED glDisable GL_TEXTURE_GEN_" + (cap - GL.TEXTURE_GEN_S)); else this.vm.warnOnce("OpenGL: UNIMPLEMENTED glDisable GL_TEXTURE_GEN_" + (cap - GL.TEXTURE_GEN_S)); break; default: if (DEBUG) console.log("UNIMPLEMENTED glDisable", GL_Symbol(cap)); else this.vm.warnOnce("OpenGL: UNIMPLEMENTED glDisable " + GL_Symbol(cap)); } }, glDisableClientState: function(cap) { if (gl.listMode && this.addToList("glDisableClientState", [cap])) return; switch (cap) { case GL.VERTEX_ARRAY: DEBUG > 1 && console.log("glDisableClientState GL_VERTEX_ARRAY"); gl.clientState.vertexArray.enabled = false; return; case GL.NORMAL_ARRAY: DEBUG > 1 && console.log("glDisableClientState GL_NORMAL_ARRAY"); gl.clientState.normalArray.enabled = false; return; case GL.COLOR_ARRAY: DEBUG > 1 && console.log("glDisableClientState GL_COLOR_ARRAY"); gl.clientState.colorArray.enabled = false; return; case GL.TEXTURE_COORD_ARRAY: DEBUG > 1 && console.log("glDisableClientState GL_TEXTURE_COORD_ARRAY"); gl.clientState.textureCoordArray.enabled = false; return; default: if (DEBUG) console.log("UNIMPLEMENTED glDisableClientState", GL_Symbol(cap)); else this.vm.warnOnce("OpenGL: UNIMPLEMENTED glDisableClientState " + GL_Symbol(cap)); } }, glDrawArrays: function(mode, first, count) { if (gl.listMode && this.addToList("glDrawArrays", [mode, first, count])) return; var geometryFlags = 0; if (gl.clientState.normalArray.enabled) geometryFlags |= HAS_NORMAL; if (gl.clientState.colorArray.enabled) geometryFlags |= HAS_COLOR; if (gl.clientState.textureCoordArray.enabled) geometryFlags |= HAS_TEXCOORD; if (mode === GL.POINTS) geometryFlags |= USE_POINT_SIZE; var shader = this.getShader(geometryFlags); if (!shader.program) { if (DEBUG) console.warn("UNIMPLEMENTED glDrawArrays shader: " + shader.label); else this.vm.warnOnce("OpenGL: UNIMPLEMENTED glDrawArrays shader: " + shader.label); return; } var vertexArray = gl.clientState.vertexArray; if (!vertexArray.enabled || !vertexArray.pointer) { DEBUG > 0 && console.warn("glDrawArrays: GL_VERTEX_ARRAY incomplete, skipping"); return; } DEBUG > 1 && console.log("glDrawArrays", GL_Symbol(mode, 'POINTS'), first, count, shader.label); webgl.useProgram(shader.program); this.setShaderUniforms(shader); var loc = shader.locations; var vertexBuffer = webgl.createBuffer(); webgl.bindBuffer(webgl.ARRAY_BUFFER, vertexBuffer); webgl.bufferData(webgl.ARRAY_BUFFER, vertexArray.pointer, webgl.DYNAMIC_DRAW); webgl.vertexAttribPointer(loc['aPosition'], vertexArray.size, vertexArray.type, false, vertexArray.stride, 0); webgl.enableVertexAttribArray(loc['aPosition']); var normalBuffer; if (loc['aNormal'] >= 0) { var normalArray = gl.clientState.normalArray; normalBuffer = webgl.createBuffer(); webgl.bindBuffer(webgl.ARRAY_BUFFER, normalBuffer); webgl.bufferData(webgl.ARRAY_BUFFER, normalArray.pointer, webgl.DYNAMIC_DRAW); webgl.vertexAttribPointer(loc['aNormal'], normalArray.size, normalArray.type, false, normalArray.stride, 0); webgl.enableVertexAttribArray(loc['aNormal']); } var colorBuffer; if (loc['aColor'] >= 0) { var colorArray = gl.clientState.colorArray; colorBuffer = webgl.createBuffer(); webgl.bindBuffer(webgl.ARRAY_BUFFER, colorBuffer); webgl.bufferData(webgl.ARRAY_BUFFER, colorArray.pointer, webgl.DYNAMIC_DRAW); webgl.vertexAttribPointer(loc['aColor'], colorArray.size, colorArray.type, false, colorArray.stride, 0); webgl.enableVertexAttribArray(loc['aColor']); } var texCoordBuffer; if (loc['aTexCoord'] >= 0) { var texCoordArray = gl.clientState.textureCoordArray; texCoordBuffer = webgl.createBuffer(); webgl.bindBuffer(webgl.ARRAY_BUFFER, texCoordBuffer); webgl.bufferData(webgl.ARRAY_BUFFER, texCoordArray.pointer, webgl.DYNAMIC_DRAW); webgl.vertexAttribPointer(loc['aTexCoord'], texCoordArray.size, texCoordArray.type, false, texCoordArray.stride, 0); webgl.enableVertexAttribArray(loc['aTexCoord']); } webgl.drawArrays(mode, first, count); webgl.useProgram(null); webgl.bindBuffer(webgl.ARRAY_BUFFER, null); webgl.disableVertexAttribArray(loc['aPosition']); webgl.deleteBuffer(vertexBuffer); if (normalBuffer) { webgl.disableVertexAttribArray(loc['aNormal']); webgl.deleteBuffer(normalBuffer); } if (colorBuffer) { webgl.disableVertexAttribArray(loc['aColor']); webgl.deleteBuffer(colorBuffer); } if (texCoordBuffer) { webgl.disableVertexAttribArray(loc['aTexCoord']); webgl.deleteBuffer(texCoordBuffer); } }, glDrawElements: function(mode, count, type, indicesPtr) { if (gl.listMode && this.addToList("glDrawElements", [mode, count, type, indicesPtr])) return; var indices; switch (type) { case GL.UNSIGNED_BYTE: indices = new Uint8Array(indicesPtr); break; case GL.UNSIGNED_SHORT: indices = new Uint16Array(indicesPtr); break; case GL.UNSIGNED_INT: // not directly supported by WebGL without OES_element_index_uint var indices32 = new Uint32Array(indicesPtr); var max = Math.max.apply(null, indices32); if (max > 0xFFFF) console.warn("OpenGL: glDrawElements with indices > 65535 not supported, truncating", max); if (max <= 0xFF) { indices = new Uint8Array(count); type = GL.UNSIGNED_BYTE; } else { indices = new Uint16Array(count); type = GL.UNSIGNED_SHORT; } for (var i = 0; i < count; i++) indices[i] = indices32[i]; break; default: if (DEBUG) console.log("UNIMPLEMENTED glDrawElements type", GL_Symbol(type)); else this.vm.warnOnce("OpenGL: UNIMPLEMENTED glDrawElements type " + GL_Symbol(type)); return; } // convert quads to triangles if (mode === GL.QUADS) { var arrayClass = indices.constructor; var newIndices = new arrayClass(count * 6 / 4); var j = 0; for (var i = 0; i < count; i += 4) { newIndices[j++] = indices[i]; newIndices[j++] = indices[i + 1]; newIndices[j++] = indices[i + 2]; newIndices[j++] = indices[i]; newIndices[j++] = indices[i + 2]; newIndices[j++] = indices[i + 3]; } indices = newIndices; count = newIndices.length; mode = GL.TRIANGLES; } var geometryFlags = 0; if (gl.clientState.normalArray.enabled) geometryFlags |= HAS_NORMAL; if (gl.clientState.colorArray.enabled) geometryFlags |= HAS_COLOR; if (gl.clientState.textureCoordArray.enabled) geometryFlags |= HAS_TEXCOORD; if (mode === GL.POINTS) geometryFlags |= USE_POINT_SIZE; var shader = this.getShader(geometryFlags); if (!shader.program) { if (DEBUG) console.warn("UNIMPLEMENTED glDrawElements shader: " + shader.label); else this.vm.warnOnce("OpenGL: UNIMPLEMENTED glDrawElements shader: " + shader.label); return; } var vertexArray = gl.clientState.vertexArray; if (!vertexArray.enabled || !vertexArray.pointer) { DEBUG > 0 && console.warn("glDrawElements: GL_VERTEX_ARRAY incomplete, skipping"); return; } DEBUG > 1 && console.log("glDrawElements", GL_Symbol(mode, 'POINTS'), count, GL_Symbol(type), shader.label, Array.from(indices)); webgl.useProgram(shader.program); this.setShaderUniforms(shader); var loc = shader.locations; var vertexBuffer = webgl.createBuffer(); webgl.bindBuffer(webgl.ARRAY_BUFFER, vertexBuffer); webgl.bufferData(webgl.ARRAY_BUFFER, vertexArray.pointer, webgl.DYNAMIC_DRAW); webgl.vertexAttribPointer(loc['aPosition'], vertexArray.size, vertexArray.type, false, vertexArray.stride, 0); webgl.enableVertexAttribArray(loc['aPosition']); var normalBuffer; if (loc['aNormal'] >= 0) { var normalArray = gl.clientState.normalArray; normalBuffer = webgl.createBuffer(); webgl.bindBuffer(webgl.ARRAY_BUFFER, normalBuffer); webgl.bufferData(webgl.ARRAY_BUFFER, normalArray.pointer, webgl.DYNAMIC_DRAW); webgl.vertexAttribPointer(loc['aNormal'], normalArray.size, normalArray.type, false, normalArray.stride, 0); webgl.enableVertexAttribArray(loc['aNormal']); } var colorBuffer; if (loc['aColor'] >= 0) { var colorArray = gl.clientState.colorArray; colorBuffer = webgl.createBuffer(); webgl.bindBuffer(webgl.ARRAY_BUFFER, colorBuffer); webgl.bufferData(webgl.ARRAY_BUFFER, colorArray.pointer, webgl.DYNAMIC_DRAW); webgl.vertexAttribPointer(loc['aColor'], colorArray.size, colorArray.type, false, colorArray.stride, 0); webgl.enableVertexAttribArray(loc['aColor']); } var texCoordBuffer; if (loc['aTexCoord'] >= 0) { var texCoordArray = gl.clientState.textureCoordArray; texCoordBuffer = webgl.createBuffer(); webgl.bindBuffer(webgl.ARRAY_BUFFER, texCoordBuffer); webgl.bufferData(webgl.ARRAY_BUFFER, texCoordArray.pointer, webgl.DYNAMIC_DRAW); webgl.vertexAttribPointer(loc['aTexCoord'], texCoordArray.size, texCoordArray.type, false, texCoordArray.stride, 0); webgl.enableVertexAttribArray(loc['aTexCoord']); } var indexBuffer = webgl.createBuffer(); webgl.bindBuffer(webgl.ELEMENT_ARRAY_BUFFER, indexBuffer); webgl.bufferData(webgl.ELEMENT_ARRAY_BUFFER, indices, webgl.DYNAMIC_DRAW); webgl.drawElements(mode, indices.length, type, 0); webgl.useProgram(null); webgl.bindBuffer(webgl.ELEMENT_ARRAY_BUFFER, null); webgl.bindBuffer(webgl.ARRAY_BUFFER, null); webgl.deleteBuffer(indexBuffer); webgl.disableVertexAttribArray(loc['aPosition']); webgl.deleteBuffer(vertexBuffer); if (normalBuffer) { webgl.disableVertexAttribArray(loc['aNormal']); webgl.deleteBuffer(normalBuffer); } if (colorBuffer) { webgl.disableVertexAttribArray(loc['aColor']); webgl.deleteBuffer(colorBuffer); } if (texCoordBuffer) { webgl.disableVertexAttribArray(loc['aTexCoord']); webgl.deleteBuffer(texCoordBuffer); } }, glEnable: function(cap) { if (gl.listMode && this.addToList("glEnable", [cap])) return; switch (cap) { case GL.ALPHA_TEST: DEBUG > 1 && console.log("glEnable GL_ALPHA_TEST"); gl.alphaTest = true; break; case webgl.BLEND: DEBUG > 1 && console.log("glEnable GL_BLEND"); webgl.enable(webgl.BLEND); break; case GL.CLIP_PLANE0: case GL.CLIP_PLANE1: case GL.CLIP_PLANE2: case GL.CLIP_PLANE3: case GL.CLIP_PLANE4: case GL.CLIP_PLANE5: case GL.CLIP_PLANE6: case GL.CLIP_PLANE7: DEBUG > 1 && console.log("glEnable GL_CLIP_PLANE" + (cap - GL.CLIP_PLANE0)); gl.clipPlanes[cap - GL.CLIP_PLANE0].enabled = true; break; case webgl.CULL_FACE: DEBUG > 1 && console.log("glEnable GL_CULL_FACE"); webgl.enable(webgl.CULL_FACE); break; case webgl.DEPTH_TEST: DEBUG > 1 && console.log("glEnable GL_DEPTH_TEST"); webgl.enable(webgl.DEPTH_TEST); break; case GL.FOG: DEBUG > 1 && console.log("glEnable GL_FOG"); gl.fogEnabled = true; break; case GL.NORMALIZE: DEBUG > 1 && console.log("glEnable GL_NORMALIZE"); // we always normalize normals break; case GL.LIGHT0: case GL.LIGHT1: case GL.LIGHT2: case GL.LIGHT3: case GL.LIGHT4: case GL.LIGHT5: case GL.LIGHT6: case GL.LIGHT7: DEBUG > 1 && console.log("glEnable GL_LIGHT" + (cap - GL.LIGHT0)); gl.lights[cap - GL.LIGHT0].enabled = true; break; case GL.LIGHTING: DEBUG > 1 && console.log("glEnable GL_LIGHTING"); gl.lightingEnabled = true; break; case webgl.POLYGON_OFFSET_FILL: DEBUG > 1 && console.log("glEnable GL_POLYGON_OFFSET_FILL"); webgl.enable(webgl.POLYGON_OFFSET_FILL); break; case webgl.STENCIL_TEST: DEBUG > 1 && console.log("glEnable GL_STENCIL_TEST"); webgl.enable(webgl.STENCIL_TEST); break; case webgl.TEXTURE_2D: DEBUG > 1 && console.log("glEnable GL_TEXTURE_2D"); gl.textureEnabled = true; break; case GL.TEXTURE_GEN_S: case GL.TEXTURE_GEN_T: case GL.TEXTURE_GEN_R: case GL.TEXTURE_GEN_Q: if (DEBUG) console.log("UNIMPLEMENTED glEnable GL_" + GL_Symbol(cap, "TEXTURE_GEN_S")); else this.vm.warnOnce("OpenGL: UNIMPLEMENTED glEnable GL_" + GL_Symbol(cap, "TEXTURE_GEN_S")); break; default: if (DEBUG) console.log("UNIMPLEMENTED glEnable", GL_Symbol(cap)); else this.vm.warnOnce("OpenGL: UNIMPLEMENTED glEnable " + GL_Symbol(cap)); } }, glEnableClientState: function(cap) { if (gl.listMode && this.addToList("glEnableClientState", [cap])) return; switch (cap) { case GL.VERTEX_ARRAY: DEBUG > 1 && console.log("glEnableClientState GL_VERTEX_ARRAY"); gl.clientState.vertexArray.enabled = true; return; case GL.NORMAL_ARRAY: DEBUG > 1 && console.log("glEnableClientState GL_NORMAL_ARRAY"); gl.clientState.normalArray.enabled = true; return; case GL.COLOR_ARRAY: DEBUG > 1 && console.log("glEnableClientState GL_COLOR_ARRAY"); gl.clientState.colorArray.enabled = true; return; case GL.TEXTURE_COORD_ARRAY: DEBUG > 1 && console.log("glEnableClientState GL_TEXTURE_COORD_ARRAY"); gl.clientState.textureCoordArray.enabled = true; return; default: if (DEBUG) console.log("UNIMPLEMENTED glEnableClientState", GL_Symbol(cap)); else this.vm.warnOnce("OpenGL: UNIMPLEMENTED glEnableClientState " + GL_Symbol(cap)); } }, glFog: function(pname, param) { if (gl.listMode && this.addToList("glFog", [pname, param])) return; switch (pname) { case GL.FOG_MODE: gl.fogMode = param; DEBUG > 1 && console.log("glFog GL_FOG_MODE", GL_Symbol(param)); break; case GL.FOG_DENSITY: DEBUG > 1 && console.log("glFog GL_FOG_DENSITY", param); gl.fogDensity = param; break; case GL.FOG_START: DEBUG > 1 && console.log("glFog GL_FOG_START", param); gl.fogStart = param; break; case GL.FOG_END: DEBUG > 1 && console.log("glFog GL_FOG_END", param); gl.fogEnd = param; break; case GL.FOG_COLOR: DEBUG > 1 && console.log("glFog GL_FOG_COLOR", Array.from(param)); gl.fogColor.set(param); break; default: if (DEBUG) console.log("UNIMPLEMENTED glFog", GL_Symbol(pname), param); else this.vm.warnOnce("OpenGL: UNIMPLEMENTED glFog " + GL_Symbol(pname)); } }, glFogi: function(pname, param) { this.glFog(pname, param); }, glFogf: function(pname, param) { this.glFog(pname, param); }, glFogiv: function(pname, params) { // FOG_COLOR integer values are mapped linearly such that the most positive representable value maps to 1.0, // and the most negative representable value maps to -1.0 this.glFog(pname, pname === GL.FOG_COLOR ? params.map(function(x) { return (x + 0.5) / (0x7FFFFFFF + 0.5); }) : params[0]); }, glFogfv: function(pname, params) { this.glFog(pname, pname === GL.FOG_COLOR ? params : params[0]); }, getShader: function(geometryFlags) { // geometryFlags: HAS_TEXCOORD, HAS_NORMAL, HAS_COLOR, USE_POINT_SIZE var shaderFlags = geometryFlags; if (gl.textureEnabled && gl.texture) shaderFlags |= USE_TEXTURE; if (gl.alphaTest) shaderFlags |= USE_ALPHA_TEST; // UNIMPLEMENTED if (gl.fogEnabled) switch (gl.fogMode) { case GL.EXP: shaderFlags |= EXP_FOG << FOG_SHIFT; break; case GL.EXP2: shaderFlags |= EXP2_FOG << FOG_SHIFT; break; case GL.LINEAR: shaderFlags |= LINEAR_FOG << FOG_SHIFT; break; } var numLights = 0; if (gl.lightingEnabled) { for (var i = 0; i < MAX_LIGHTS; i++) { if (gl.lights[i].enabled) numLights++; } shaderFlags |= numLights << NUM_LIGHTS_SHIFT; } var numClipPlanes = 0; for (var i = 0; i < MAX_CLIP_PLANES; i++) { if (gl.clipPlanes[i].enabled) { numClipPlanes++; } } shaderFlags |= numClipPlanes << NUM_CLIP_PLANES_SHIFT; // create shader program var shader = gl.shaders[shaderFlags]; if (!shader) { var flagString = "[POSITION"; if (shaderFlags & HAS_NORMAL) flagString += ", NORMAL"; if (shaderFlags & HAS_COLOR) flagString += ", COLOR"; if (shaderFlags & HAS_TEXCOORD) flagString += ", TEXCOORD"; flagString += "]"; if (shaderFlags & USE_TEXTURE) flagString += ", TEXTURE"; if (shaderFlags & ANY_LIGHTS) { flagString += ", "+ numLights +" LIGHT"; if (numLights !== 1) flagString += "S"; } if (shaderFlags & ANY_CLIP_PLANES) { flagString += ", "+ numClipPlanes +" CLIP_PLANE"; if (numClipPlanes !== 1) flagString += "S"; } if (shaderFlags & USE_ALPHA_TEST) flagString += ", ALPHA_TEST"; if (shaderFlags & USE_POINT_SIZE) flagString += ", POINT_SIZE"; if (shaderFlags & ANY_FOG) flagString += ", FOG"; shader = gl.shaders[shaderFlags] = { flags: shaderFlags, label: flagString, program: null, locations: null, vsource: null, // for debugging fsource: null, // for debugging }; var implemented = HAS_TEXCOORD | HAS_NORMAL | HAS_COLOR | USE_TEXTURE | NUM_LIGHTS_MASK | NUM_CLIP_PLANES_MASK | USE_POINT_SIZE | FOG_MASK; if (shaderFlags & ~implemented) return shader; var program = webgl.createProgram() var vs = webgl.createShader(webgl.VERTEX_SHADER); shader.vsource = this.vertexShaderSource(shaderFlags); webgl.shaderSource(vs, shader.vsource); webgl.compileShader(vs); if (!webgl.getShaderParameter(vs, webgl.COMPILE_STATUS)) { console.error("OpenGL: vertex shader compile error: " + webgl.getShaderInfoLog(vs)); debugger; return shader; } var fs = webgl.createShader(webgl.FRAGMENT_SHADER); shader.fsource = this.fragmentShaderSource(shaderFlags); webgl.shaderSource(fs, shader.fsource); webgl.compileShader(fs); if (!webgl.getShaderParameter(fs, webgl.COMPILE_STATUS)) { console.error("OpenGL: fragment shader compile error: " + webgl.getShaderInfoLog(fs)); debugger; return shader; } webgl.attachShader(program, vs); webgl.attachShader(program, fs); webgl.linkProgram(program); if (!webgl.getProgramParameter(program, webgl.LINK_STATUS)) { console.error("OpenGL: shader link error: " + webgl.getProgramInfoLog(program)); debugger return shader; } shader.program = program; shader.locations = this.getLocations(program, shaderFlags); } return shader; }, setShaderUniforms: function(shader) { var loc = shader.locations; DEBUG > 2 && console.log("uModelView", Array.from(gl.matrices[GL.MODELVIEW][0])); webgl.uniformMatrix4fv(loc['uModelView'], false, gl.matrices[GL.MODELVIEW][0]); DEBUG > 2 && console.log("uProjection", Array.from(gl.matrices[GL.PROJECTION][0])); webgl.uniformMatrix4fv(loc['uProjection'], false, gl.matrices[GL.PROJECTION][0]); if (loc['uNormalMatrix']) { var normalMatrix = asNormalMatrix(gl.matrices[GL.MODELVIEW][0]); DEBUG > 2 && console.log("uNormalMatrix", Array.from(normalMatrix)); webgl.uniformMatrix3fv(loc['uNormalMatrix'], false, normalMatrix); } if (loc['uNormal']) { DEBUG > 2 && console.log("uNormal", Array.from(gl.normal)); webgl.uniform3fv(loc['uNormal'], gl.normal); } if (loc['uColor']) { var color = gl.color; if (gl.textureEnvMode === GL.REPLACE) color = [1, 1, 1, 1]; // HACK DEBUG > 2 && console.log("uColor", Array.from(color)); webgl.uniform4fv(loc['uColor'], color); } if (loc['uTextureMatrix']) { DEBUG > 2 && console.log("uTextureMatrix", Array.from(gl.matrices[GL.TEXTURE][0])); webgl.uniformMatrix4fv(loc['uTextureMatrix'], false, gl.matrices[GL.TEXTURE][0]); } if (loc['uTexCoord']) { DEBUG > 2 && console.log("uTexCoord", Array.from(gl.texCoord)); webgl.uniform2fv(loc['uTexCoord'], gl.texCoord); } if (loc['uSampler']) { DEBUG > 2 && console.log("uSampler", gl.texture); webgl.activeTexture(webgl.TEXTURE0); webgl.bindTexture(webgl.TEXTURE_2D, gl.texture); webgl.uniform1i(loc['uSampler'], 0); } if (loc['uPointSize']) { DEBUG > 2 && console.log("uPointSize", gl.pointSize); webgl.uniform1f(loc['uPointSize'], gl.pointSize); } var numLights = (shader.flags & NUM_LIGHTS_MASK) >> NUM_LIGHTS_SHIFT; if (numLights > 0) { DEBUG > 2 && console.log("uLightModelAmbient", Array.from(gl.lightModelAmbient)); webgl.uniform4fv(loc['uLightModelAmbient'], gl.lightModelAmbient); DEBUG > 2 && console.log("uMaterialAmbient", Array.from(gl.material.ambient)); webgl.uniform4fv(loc['uMaterialAmbient'], gl.material.ambient); DEBUG > 2 && console.log("uMaterialDiffuse", Array.from(gl.material.diffuse)); webgl.uniform4fv(loc['uMaterialDiffuse'], gl.material.diffuse); DEBUG > 2 && console.log("uMaterialSpecular", Array.from(gl.material.specular)); webgl.uniform4fv(loc['uMaterialSpecular'], gl.material.specular); DEBUG > 2 && console.log("uMaterialEmission", Array.from(gl.material.emission)); webgl.uniform4fv(loc['uMaterialEmission'], gl.material.emission); DEBUG > 2 && console.log("uMaterialShininess", gl.material.shininess); webgl.uniform1f(loc['uMaterialShininess'], gl.material.shininess); var index = 0; for (var i = 0; i < MAX_LIGHTS; i++) { var light = gl.lights[i]; if (!light.enabled) continue; DEBUG > 2 && console.log("uLights[" + index + "].ambient", Array.from(light.ambient)); webgl.uniform4fv(loc['uLights'][index].ambient, light.ambient); DEBUG > 2 && console.log("uLights[" + index + "].diffuse", Array.from(light.diffuse)); webgl.uniform4fv(loc['uLights'][index].diffuse, light.diffuse); DEBUG > 2 && console.log("uLights[" + index + "].specular", Array.from(light.specular)); webgl.uniform4fv(loc['uLights'][index].specular, light.specular); DEBUG > 2 && console.log("uLights[" + index + "].position", Array.from(light.position)); webgl.uniform4fv(loc['uLights'][index].position, light.position); index++; } } var numClipPlanes = (shader.flags & NUM_CLIP_PLANES_MASK) >> NUM_CLIP_PLANES_SHIFT; if (numClipPlanes > 0) { var index = 0; for (var i = 0; i < MAX_CLIP_PLANES; i++) { var clipPlane = gl.clipPlanes[i]; if (!clipPlane.enabled) continue; DEBUG > 2 && console.log("uClipPlanes[" + index + "]", Array.from(clipPlane.equation)); webgl.uniform4fv(loc['uClipPlanes'][index], clipPlane.equation); index++; } } if (loc['uFogColor']) { DEBUG > 2 && console.log("uFogColor", Array.from(gl.fogColor)); webgl.uniform4fv(loc['uFogColor'], gl.fogColor); } if (loc['uFogEnd']) { DEBUG > 2 && console.log("uFogEnd", gl.fogEnd); webgl.uniform1f(loc['uFogEnd'], gl.fogEnd); } if (loc['uFogRange']) { DEBUG > 2 && console.log("uFogRange", gl.fogEnd - gl.fogStart); webgl.uniform1f(loc['uFogRange'], gl.fogEnd - gl.fogStart); } if (loc['uFogDensity']) { DEBUG > 2 && console.log("uFogDensity", gl.fogDensity); webgl.uniform1f(loc['uFogDensity'], gl.fogDensity); } }, glEnd: function() { if (gl.listMode && this.addToList("glEnd", [])) return; var primitive = gl.primitive; gl.primitive = null; // select shader var geometryFlags = primitive.vertexAttrs; if (primitive.mode === GL.POINTS) geometryFlags |= USE_POINT_SIZE; var shader = this.getShader(geometryFlags); if (!shader.program) { if (DEBUG) console.warn("UNIMPLEMENTED glEnd shader: " + shader.label); else this.vm.warnOnce("OpenGL: UNIMPLEMENTED glEnd shader: " + shader.label); return; } // create interleaved vertex buffer var vertices = primitive.vertices; var size = primitive.vertexSize; var data = new Float32Array(vertices.length * size); for (var i = 0, offset = 0; i < vertices.length; i++, offset += size) { data.set(vertices[i], offset); } var vertexBuffer = webgl.createBuffer(); webgl.bindBuffer(webgl.ARRAY_BUFFER, vertexBuffer); webgl.bufferData(webgl.ARRAY_BUFFER, data, webgl.DYNAMIC_DRAW); // set mode depending on primitive mode // and create index buffer if needed var mode; var indices; switch (primitive.mode) { // supported by WebGL, no index buffer needed case webgl.POINTS: case webgl.LINES: case webgl.LINE_LOOP: case webgl.LINE_STRIP: case webgl.TRIANGLES: case webgl.TRIANGLE_STRIP: case webgl.TRIANGLE_FAN: DEBUG > 1 && console.log("glEnd " + GL_Symbol(primitive.mode, "POINTS") + ":" + shader.label); mode = primitive.mode; break; // not supported by WebGL, emulate case GL.QUADS: // use triangles and an index buffer to // duplicate vertices as v0-v1-v2, v2-v1-v3 // we assume that all attributes are floats DEBUG > 1 && console.log("glEnd GL_QUADS:" + shader.label); indices = vertices.length > 256 ? new Uint16Array(vertices.length * 3 / 2) : new Uint8Array(vertices.length * 3 / 2); var offset = 0; for (var i = 0; i < vertices.length; i += 4) { indices[offset++] = i; indices[offset++] = i+1; indices[offset++] = i+2; indices[offset++] = i; indices[offset++] = i+2; indices[offset++] = i+3; } mode = webgl.TRIANGLES; break; case GL.QUAD_STRIP: if (DEBUG) console.log("UNIMPLEMENTED glEnd GL_QUAD_STRIP:" + shader.label); else this.vm.warnOnce("OpenGL: UNIMPLEMENTED glEnd GL_QUAD_STRIP"); return; case GL.POLYGON: // use triangle fan, which works for convex polygons DEBUG > 1 && console.log("glEnd GL_POLYGON:" + shader.label); mode = webgl.TRIANGLE_FAN; break; default: if (DEBUG) console.log("UNIMPLEMENTED glEnd", primitive.mode); else this.vm.warnOnce("OpenGL: UNIMPLEMENTED glEnd " + primitive.mode); return; } var indexBuffer; if (indices) { indexBuffer = webgl.createBuffer(); webgl.bindBuffer(webgl.ELEMENT_ARRAY_BUFFER, indexBuffer); webgl.bufferData(webgl.ELEMENT_ARRAY_BUFFER, indices, webgl.DYNAMIC_DRAW); } // set up uniforms and vertex attributes var stride = size * 4; var offset = 0; webgl.useProgram(shader.program); this.setShaderUniforms(shader); var loc = shader.locations; DEBUG > 2 && console.log("aPosition: @" + offset + "/" + stride); webgl.vertexAttribPointer(loc['aPosition'], 3, webgl.FLOAT, false, stride, offset); webgl.enableVertexAttribArray(loc['aPosition']); offset += 12; if (loc['aNormal'] >= 0) { DEBUG > 2 && console.log("aNormal: @" + offset + "/" + stride); webgl.vertexAttribPointer(loc['aNormal'], 3, webgl.FLOAT, false, stride, offset); webgl.enableVertexAttribArray(loc['aNormal']); } if (geometryFlags & HAS_NORMAL) offset += 12; if (loc['aColor'] >= 0) { DEBUG > 2 && console.log("aColor: @" + offset + "/" + stride); webgl.vertexAttribPointer(loc['aColor'], 4, webgl.FLOAT, false, stride, offset); webgl.enableVertexAttribArray(loc['aColor']); } if (geometryFlags & HAS_COLOR) offset += 16; if (loc['aTexCoord'] >= 0) { DEBUG > 2 && console.log("aTexCoord: @" + offset + "/" + stride); webgl.vertexAttribPointer(loc['aTexCoord'], 2, webgl.FLOAT, false, stride, offset); webgl.enableVertexAttribArray(loc['aTexCoord']); } if (geometryFlags & HAS_TEXCOORD) offset += 8; // draw if (indexBuffer) { DEBUG > 1 && console.log("glEnd: draw indexed vertices", GL_Symbol(mode, 'POINTS'), Array.from(indices), vertices.map(function(v) { return ""+v; })); webgl.drawElements(mode, indices.length, vertices.length > 256 ? webgl.UNSIGNED_SHORT : webgl.UNSIGNED_BYTE, 0); } else { DEBUG > 1 && console.log("glEnd: draw vertices", GL_Symbol(mode, 'POINTS'), 0, vertices.map(function(v) { return ""+v; })); webgl.drawArrays(mode, 0, vertices.length); } webgl.useProgram(null); webgl.disableVertexAttribArray(loc['aPosition']); if (loc['aNormal'] >= 0) webgl.disableVertexAttribArray(loc['aNormal']); if (loc['aColor'] >= 0) webgl.disableVertexAttribArray(loc['aColor']); if (loc['aTexCoord'] >= 0) webgl.disableVertexAttribArray(loc['aTexCoord']); webgl.bindBuffer(webgl.ARRAY_BUFFER, null); webgl.deleteBuffer(vertexBuffer); if (indexBuffer) { webgl.deleteBuffer(indexBuffer); webgl.bindBuffer(webgl.ELEMENT_ARRAY_BUFFER, null); } }, glEndList: function() { DEBUG > 1 && console.log("glEndList"); var list = gl.list; gl.list = null; gl.lists[list.id] = list; gl.listMode = 0; }, glFinish: function() { DEBUG > 1 && console.log("glFinish"); webgl.finish(); }, glFlush: function() { DEBUG > 1 && console.log("glFlush"); webgl.flush(); }, glFrontFace: function(mode) { if (gl.listMode && this.addToList("glFrontFace", [mode])) return; DEBUG > 1 && console.log("glFrontFace", GL_Symbol(mode)); webgl.frontFace(mode); }, glFrustum: function(left, right, bottom, top, zNear, zFar) { if (gl.listMode && this.addToList("glFrustum", [left, right, bottom, top, zNear, zFar])) return; DEBUG > 1 && console.log("glFrustum", left, right, bottom, top, zNear, zFar); var m = gl.matrix; m[0] = 2 * zNear / (right - left); m[1] = 0; m[2] = 0; m[3] = 0; m[4] = 0; m[5] = 2 * zNear / (top - bottom); m[6] = 0; m[7] = 0; m[8] = (right + left) / (right - left); m[9] = (top + bottom) / (top - bottom); m[10] = -(zFar + zNear) / (zFar - zNear); m[11] = -1; m[12] = 0; m[13] = 0; m[14] = -(2 * zFar * zNear) / (zFar - zNear); m[15] = 0; }, glGenLists: function(range) { DEBUG > 1 && console.log("glGenLists", range); var firstId = 0; if (range > 0) { firstId = gl.listIdGen + 1; gl.listIdGen += range; } return firstId; }, glGenTextures: function(n, textures) { for (var i = 0; i < n; i++) { var id = ++gl.textureIdGen; gl.textures[id] = webgl.createTexture(); textures[i] = id; } DEBUG > 1 && console.log("glGenTextures", n, Array.from(textures)); }, glGetFloatv: function(pname, params) { switch (pname) { case GL.MODELVIEW_MATRIX: DEBUG > 1 && console.log("glGetFloatv GL_MODELVIEW_MATRIX"); params.set(gl.matrices[GL.MODELVIEW][0]); break; case GL.PROJECTION_MATRIX: DEBUG > 1 && console.log("glGetFloatv GL_PROJECTION_MATRIX"); params.set(gl.matrices[GL.PROJECTION][0]); break; case GL.TEXTURE_MATRIX: DEBUG > 1 && console.log("glGetFloatv GL_TEXTURE_MATRIX"); params.set(gl.matrices[GL.TEXTURE][0]); break; default: if (DEBUG) console.log("UNIMPLEMENTED glGetFloatv", GL_Symbol(pname)); else this.vm.warnOnce("OpenGL: UNIMPLEMENTED glGetFloatv " + GL_Symbol(pname)); } }, glGetIntegerv(name, params) { switch (name) { case GL.LIST_INDEX: DEBUG > 1 && console.log("glGetIntegerv GL_LIST_INDEX"); params[0] = gl.list ? gl.list.id : 0; break; default: if (DEBUG) console.log("UNIMPLEMENTED glGetIntegerv", GL_Symbol(name)); else this.vm.warnOnce("OpenGL: UNIMPLEMENTED glGetIntegerv " + GL_Symbol(name)); } }, glGetString: function(name) { switch (name) { case GL.VERSION: DEBUG > 1 && console.log("glGetString GL_VERSION"); return gl.version; case GL.VENDOR: DEBUG > 1 && console.log("glGetString GL_VENDOR"); return gl.vendor; case GL.RENDERER: DEBUG > 1 && console.log("glGetString GL_RENDERER"); return gl.renderer; case GL.EXTENSIONS: DEBUG > 1 && console.log("glGetString GL_EXTENSIONS"); return gl.extensions; default: if (DEBUG) console.log("UNIMPLEMENTED glGetString", name); else this.vm.warnOnce("OpenGL: UNIMPLEMENTED glGetString " + name); } return ""; }, glGetTexLevelParameteriv: function(target, level, pname, params) { switch (pname) { case GL.TEXTURE_COMPRESSED: return false; default: if (DEBUG) console.log("UNIMPLEMENTED glGetTexLevelParameteriv", GL_Symbol(target), level, GL_Symbol(pname), params); else this.vm.warnOnce("OpenGL: UNIMPLEMENTED glGetTexLevelParameteriv " + GL_Symbol(target) + " " + GL_Symbol(pname)); } }, glHint: function(target, mode) { if (gl.listMode && this.addToList("glHint", [target, mode])) return; switch (target) { case webgl.GENERATE_MIPMAP_HINT: DEBUG > 1 && console.log("glHint GL_GENERATE_MIPMAP_HINT", GL_Symbol(mode)); webgl.hint(target, mode); break; } // webgl doesn't support any other hints DEBUG > 1 && console.log("IGNORING glHint", GL_Symbol(target), GL_Symbol(mode)); }, glIsEnabled: function(cap) { switch (cap) { case GL.LIGHTING: DEBUG > 1 && console.log("glIsEnabled GL_LIGHTING"); return gl.lightingEnabled; default: if (DEBUG) console.log("UNIMPLEMENTED glIsEnabled", cap); else this.vm.warnOnce("OpenGL: UNIMPLEMENTED glIsEnabled " + cap); } return false; }, glIsTexture: function(texture) { DEBUG > 1 && console.log("glIsTexture", texture); return !!gl.textures[texture]; }, glLightf: function(light, pname, param) { if (gl.listMode && this.addToList("glLightf", [light, pname, param])) return; var i = light - GL.LIGHT0; switch (pname) { case GL.SPOT_CUTOFF: if (param === 180) { DEBUG > 1 && console.log("glLightf", i, "GL_SPOT_CUTOFF", param); return; } // fall through if not default default: if (DEBUG) console.log("UNIMPLEMENTED glLightf", i, GL_Symbol(pname), param); else this.vm.warnOnce("OpenGL: UNIMPLEMENTED glLightf " + GL_Symbol(pname)); } }, glLightfv: function(light, pname, param) { if (gl.listMode && this.addToList("glLightfv", [light, pname, param])) return; var i = light - GL.LIGHT0; switch (pname) { case GL.AMBIENT: DEBUG > 1 && console.log("glLightfv", i, "GL_AMBIENT", Array.from(param)); gl.lights[i].ambient = param; break; case GL.DIFFUSE: DEBUG > 1 && console.log("glLightfv", i, "GL_DIFFUSE", Array.from(param)); gl.lights[i].diffuse = param; break; case GL.SPECULAR: DEBUG > 1 && console.log("glLightfv", i, "GL_SPECULAR", Array.from(param)); gl.lights[i].specular = param; break; case GL.POSITION: if (param[3] === 0) { transformDirection(gl.matrices[GL.MODELVIEW][0], param, gl.lights[i].position); } else { transformPoint(gl.matrices[GL.MODELVIEW][0], param, gl.lights[i].position); } DEBUG > 1 && console.log("glLightfv", i, "GL_POSITION", Array.from(param), "=>", Array.from(gl.lights[i].position)); break; default: if (DEBUG) console.log("UNIMPLEMENTED glLightfv", i, GL_Symbol(pname), Array.from(param)); else this.vm.warnOnce("OpenGL: UNIMPLEMENTED glLightfv " + GL_Symbol(pname)); } }, glLightModelfv: function(pname, params) { if (gl.listMode && this.addToList("glLightModelfv", [pname, params])) return; switch (pname) { case GL.LIGHT_MODEL_AMBIENT: DEBUG > 1 && console.log("glLightModelfv GL_LIGHT_MODEL_AMBIENT", Array.from(params)); gl.lightModelAmbient = params; break; default: if (DEBUG) console.log("UNIMPLEMENTED glLightModelfv", GL_Symbol(pname), Array.from(params)); else this.vm.warnOnce("OpenGL: UNIMPLEMENTED glLightModelfv " + GL_Symbol(pname)); } }, glLineWidth: function(width) { if (gl.listMode && this.addToList("glLineWidth", [width])) return; DEBUG > 1 && console.log("glLineWidth", width); webgl.lineWidth(width); }, glListBase: function(base) { DEBUG > 1 && console.log("glListBase", base); gl.listBase = base; }, glLoadIdentity: function() { if (gl.listMode && this.addToList("glLoadIdentity", [])) return; DEBUG > 1 && console.log("glLoadIdentity"); gl.matrix.set(identity); }, glLoadMatrixf: function(m) { if (gl.listMode && this.addToList("glLoadMatrixf", [m])) return; gl.matrix.set(m); DEBUG > 1 && console.log("glLoadMatrixf", GL_Symbol(gl.matrixMode), Array.from(m)); }, glLoadTransposeMatrixf: function(m) { if (gl.listMode && this.addToList("glLoadTransposeMatrixf", [m])) return; var t = new Float32Array(m); transposeMatrix(t); gl.matrix.set(t); DEBUG > 1 && console.log("glLoadTransposeMatrixf", GL_Symbol(gl.matrixMode), Array.from(m)); }, glMaterialf: function(face, pname, param) { if (gl.listMode && this.addToList("glMaterialf", [face, pname, param])) return; switch (pname) { case GL.SHININESS: DEBUG > 1 && console.log("glMaterialf GL_SHININESS", param); gl.material.shininess = param; break; default: if (DEBUG) console.log("UNIMPLEMENTED glMaterialf", GL_Symbol(pname), param); else this.vm.warnOnce("OpenGL: UNIMPLEMENTED glMaterialf " + GL_Symbol(pname)); } }, glMaterialfv: function(face, pname, param) { if (gl.listMode && this.addToList("glMaterialfv", [face, pname, param])) return; switch (pname) { case GL.AMBIENT: DEBUG > 1 && console.log("glMaterialfv GL_AMBIENT", Array.from(param)); gl.material.ambient = param; break; case GL.DIFFUSE: DEBUG > 1 && console.log("glMaterialfv GL_DIFFUSE", Array.from(param)); gl.material.diffuse = param; break; case GL.SPECULAR: DEBUG > 1 && console.log("glMaterialfv GL_SPECULAR", Array.from(param)); gl.material.specular = param; break; case GL.EMISSION: DEBUG > 1 && console.log("glMaterialfv GL_EMISSION", Array.from(param)); gl.material.emission = param; break; case GL.SHININESS: DEBUG > 1 && console.log("glMaterialfv GL_SHININESS", Array.from(param)); gl.material.shininess = param[0]; break; case GL.AMBIENT_AND_DIFFUSE: DEBUG > 1 && console.log("glMaterialfv GL_AMBIENT_AND_DIFFUSE", Array.from(param)); gl.material.ambient = param; gl.material.diffuse = param; break; default: if (DEBUG) console.log("UNIMPLEMENTED glMaterialfv", GL_Symbol(pname), Array.from(param)); else this.vm.warnOnce("OpenGL: UNIMPLEMENTED glMaterialfv " + GL_Symbol(pname)); } }, glMatrixMode: function(mode) { if (gl.listMode && this.addToList("glMatrixMode", [mode])) return; if (mode !== GL.MODELVIEW && mode !== GL.PROJECTION && mode !== GL.TEXTURE) { if (DEBUG) console.warn("UNIMPLEMENTED glMatrixMode", GL_Symbol(mode)); else this.vm.warnOnce("OpenGL: UNIMPLEMENTED glMatrixMode " + GL_Symbol(mode)); } else DEBUG > 1 && console.log("glMatrixMode", GL_Symbol(mode)); gl.matrixMode = mode; if (!gl.matrices[mode]) gl.matrices[mode] = [new Float32Array(identity)]; gl.matrix = gl.matrices[mode][0]; }, glMultMatrixf: function(m) { if (gl.listMode && this.addToList("glMultMatrixf", [m])) return; multMatrix(gl.matrix, m); DEBUG > 1 && console.log("glMultMatrixf", GL_Symbol(gl.matrixMode), Array.from(m), "=>", Array.from(gl.matrix)); }, glMultTransposeMatrixf: function(m) { if (gl.listMode && this.addToList("glMultTransposeMatrixf", [m])) return; var t = new Float32Array(m); transposeMatrix(t); multMatrix(gl.matrix, t); DEBUG > 1 && console.log("glMultTransposeMatrixf", GL_Symbol(gl.matrixMode), Array.from(m), "=>", Array.from(gl.matrix)); }, glNewList: function(list, mode) { DEBUG > 1 && console.log("glNewList", list, GL_Symbol(mode)); var newList = { id: list, commands: [], }; gl.list = newList; gl.listMode = mode; }, glNormal3f: function(nx, ny, nz) { if (gl.listMode && this.addToList("glNormal3f", [nx, ny, nz])) return; DEBUG > 1 && console.log("glNormal3f", nx, ny, nz); gl.normal[0] = nx; gl.normal[1] = ny; gl.normal[2] = nz; gl.primitiveAttrs |= HAS_NORMAL; }, glNormal3fv: function(v) { if (gl.listMode && this.addToList("glNormal3fv", [v.slice()])) return; DEBUG > 1 && console.log("glNormal3fv", Array.from(v)); gl.normal.set(v); gl.primitiveAttrs |= HAS_NORMAL; }, glNormalPointer: function(type, stride, pointer) { if (gl.listMode && this.addToList("glNormalPointer", [type, stride, pointer])) return; DEBUG > 1 && console.log("glNormalPointer", GL_Symbol(type), stride, pointer); gl.clientState.normalArray.size = 3; gl.clientState.normalArray.type = type; gl.clientState.normalArray.stride = stride; gl.clientState.normalArray.pointer = pointer; }, glPixelStorei: function(pname, param) { switch (pname) { case webgl.UNPACK_ALIGNMENT: DEBUG > 1 && console.log("glPixelStorei GL_UNPACK_ALIGNMENT", param); //@webgl.pixelStorei(webgl.UNPACK_ALIGNMENT, param); break; case GL.UNPACK_LSB_FIRST: if (param !== 0) console.log("UNIMPLEMENTED glPixelStorei GL_UNPACK_LSB_FIRST", param); break; case GL.UNPACK_ROW_LENGTH: DEBUG > 1 && console.log("glPixelStorei GL_UNPACK_ROW_LENGTH", param); gl.pixelStoreUnpackRowLength = param; break; case GL.UNPACK_SKIP_ROWS: DEBUG > 1 && console.log("glPixelStorei GL_UNPACK_SKIP_ROWS", param); gl.pixelStoreUnpackSkipRows = param; break; case GL.UNPACK_SKIP_PIXELS: DEBUG > 1 && console.log("glPixelStorei GL_UNPACK_SKIP_PIXELS", param); gl.pixelStoreUnpackSkipPixels = param; break; default: if (DEBUG) console.log("UNIMPLEMENTED glPixelStorei", pname, param); else this.vm.warnOnce("OpenGL: UNIMPLEMENTED glPixelStorei " + pname); } }, glPolygonOffset: function(factor, units) { if (gl.listMode && this.addToList("glPolygonOffset", [factor, units])) return; DEBUG > 1 && console.log("glPolygonOffset", factor, units); webgl.polygonOffset(factor, units); }, glPushAttrib: function(mask) { if (gl.listMode && this.addToList("glPushAttrib", [mask])) return; if (DEBUG > 0) { var maskString = ''; if (mask === GL.ALL_ATTRIB_BITS) { maskString = 'GL_ALL_ATTRIB_BITS'; } else { if (mask & GL.CURRENT_BIT) { maskString += 'GL_CURRENT_BIT '; mask &= ~GL.CURRENT_BIT; } if (mask & GL.ENABLE_BIT) { maskString += 'GL_ENABLE_BIT '; mask &= ~GL.ENABLE_BIT; } if (mask & GL.LIGHTING_BIT) { maskString += 'GL_LIGHTING_BIT '; mask &= ~GL.LIGHTING_BIT; } if (mask & GL.COLOR_BUFFER_BIT) { maskString += 'GL_COLOR_BUFFER_BIT '; mask &= ~GL.COLOR_BUFFER_BIT; } if (mask & GL.DEPTH_BUFFER_BIT) { maskString += 'GL_DEPTH_BUFFER_BIT '; mask &= ~GL.DEPTH_BUFFER_BIT; } if (mask & GL.POLYGON_BIT) { maskString += 'GL_POLYGON_BIT '; mask &= ~GL.POLYGON_BIT; } if (mask & GL.TEXTURE_BIT) { maskString += 'GL_TEXTURE_BIT '; mask &= ~GL.TEXTURE_BIT; } if (mask) for (var i = 0; i < 32; i++) { if (mask & (1 << i)) maskString += " " + (1 << i); } } console.log("UNIMPLEMENTED glPushAttrib", maskString); } else this.vm.warnOnce("OpenGL: UNIMPLEMENTED glPushAttrib"); }, glPushMatrix: function() { if (gl.listMode && this.addToList("glPushMatrix", [])) return; gl.matrix = new Float32Array(gl.matrix); gl.matrices[gl.matrixMode].unshift(gl.matrix); DEBUG > 1 && console.log("glPushMatrix", GL_Symbol(gl.matrixMode), "=>", Array.from(gl.matrix)); }, glPointSize: function(size) { if (gl.listMode && this.addToList("glPointSize", [size])) return; DEBUG > 1 && console.log("glPointSize", size); gl.pointSize = size; }, glPopAttrib: function() { if (gl.listMode && this.addToList("glPopAttrib", [])) return; if (DEBUG) console.log("UNIMPLEMENTED glPopAttrib"); else this.vm.warnOnce("OpenGL: UNIMPLEMENTED glPopAttrib"); }, glPopMatrix: function() { if (gl.listMode && this.addToList("glPopMatrix", [])) return; if (gl.matrices[gl.matrixMode].length <= 1) return DEBUG > 0 && console.warn("OpenGL: matrix stack underflow"); gl.matrices[gl.matrixMode].shift(); gl.matrix = gl.matrices[gl.matrixMode][0]; DEBUG > 1 && console.log("glPopMatrix", GL_Symbol(gl.matrixMode), "=>", Array.from(gl.matrix)); }, glRasterPos3f: function(x, y, z) { if (gl.listMode && this.addToList("glRasterPos3f", [x, y, z])) return; DEBUG > 1 && console.log("glRasterPos3f", x, y, z); gl.rasterPos[0] = x; gl.rasterPos[1] = y; gl.rasterPos[2] = z; gl.rasterPos[3] = 1; // transform point via modelview and projection matrices var m = gl.matrices[GL.PROJECTION][0]; transformPoint(m, gl.rasterPos, gl.rasterPos); m = gl.matrices[GL.MODELVIEW][0]; transformPoint(m, gl.rasterPos, gl.rasterPos); // transform to window coordinates gl.rasterPos[0] = (gl.rasterPos[0] * 0.5 + 0.5) * gl.viewport[2] + gl.viewport[0]; gl.rasterPos[1] = (gl.rasterPos[1] * 0.5 + 0.5) * gl.viewport[3] + gl.viewport[1]; gl.rasterPos[2] = (gl.rasterPos[2] * 0.5 + 0.5) * (gl.depthRange[1] - gl.depthRange[0]) + gl.depthRange[0]; // remember raster color gl.rasterColor.set(gl.color); }, glReadPixels: function(x, y, width, height, format, type, pixels) { if (gl.listMode && this.addToList("glReadPixels", [x, y, width, height, format, type, pixels])) return; var swizzle = false; switch (format) { case webgl.RGBA: break; case GL.BGRA: format = webgl.RGBA; swizzle = true; break; default: if (DEBUG) console.warn("UNIMPLEMENTED glReadPixels format " + GL_Symbol(format)); else this.vm.warnOnce("OpenGL: UNIMPLEMENTED glReadPixels format " + GL_Symbol(format)); return; } switch (type) { case webgl.UNSIGNED_BYTE: pixels = new Uint8Array(pixels); break; default: if (DEBUG) console.warn("UNIMPLEMENTED glReadPixels type " + GL_Symbol(type)); else this.vm.warnOnce("OpenGL: UNIMPLEMENTED glReadPixels type " + GL_Symbol(type)); return; } DEBUG > 1 && console.log("glReadPixels", x, y, width, height, GL_Symbol(format), GL_Symbol(type), pixels); webgl.readPixels(x, y, width, height, format, type, pixels); if (swizzle) { for (var i = 0; i < pixels.length; i += 4) { var r = pixels[i]; var b = pixels[i+2]; pixels[i] = b; pixels[i+2] = r; } } }, glTranslated: function(x, y, z) { if (gl.listMode && this.addToList("glTranslated", [x, y, z])) return; translateMatrix(gl.matrix, x, y, z); DEBUG > 1 && console.log("glTranslated", GL_Symbol(gl.matrixMode), x, y, z, "=>", Array.from(gl.matrix)); }, glTranslatef: function(x, y, z) { if (gl.listMode && this.addToList("glTranslatef", [x, y, z])) return; translateMatrix(gl.matrix, x, y, z); DEBUG > 1 && console.log("glTranslatef", GL_Symbol(gl.matrixMode), x, y, z, "=>", Array.from(gl.matrix)); }, glRotatef: function(angle, x, y, z) { if (gl.listMode && this.addToList("glRotatef", [angle, x, y, z])) return; rotateMatrix(gl.matrix, angle, x, y, z); DEBUG > 1 && console.log("glRotatef", GL_Symbol(gl.matrixMode), angle, x, y, z, "=>", Array.from(gl.matrix)); }, glScalef: function(x, y, z) { if (gl.listMode && this.addToList("glScalef", [x, y, z])) return; scaleMatrix(gl.matrix, x, y, z); DEBUG > 1 && console.log("glScalef", GL_Symbol(gl.matrixMode), x, y, z, "=>", Array.from(gl.matrix)); }, glScaled: function(x, y, z) { if (gl.listMode && this.addToList("glScaled", [x, y, z])) return; scaleMatrix(gl.matrix, x, y, z); DEBUG > 1 && console.log("glScaled", GL_Symbol(gl.matrixMode), x, y, z, "=>", Array.from(gl.matrix)); }, glShadeModel: function(mode) { if (gl.listMode && this.addToList("glShadeModel", [mode])) return; if (DEBUG) console.log("UNIMPLEMENTED glShadeModel", GL_Symbol(mode)); else this.vm.warnOnce("OpenGL: UNIMPLEMENTED glShadeModel " + GL_Symbol(mode)); }, glStencilFunc: function(func, ref, mask) { if (gl.listMode && this.addToList("glStencilFunc", [func, ref, mask])) return; DEBUG > 1 && console.log("glStencilFunc", GL_Symbol(func), ref, '0x'+(mask>>>0).toString(16)); webgl.stencilFunc(func, ref, mask); }, glStencilOp: function(fail, zfail, zpass) { if (gl.listMode && this.addToList("glStencilOp", [fail, zfail, zpass])) return; DEBUG > 1 && console.log("glStencilOp", GL_Symbol(fail), GL_Symbol(zfail), GL_Symbol(zpass)); webgl.stencilOp(fail, zfail, zpass); }, glTexEnv: function(target, pname, param) { if (gl.listMode && this.addToList("glTexEnv", [target, pname, param])) return; switch (pname) { case GL.TEXTURE_ENV_MODE: switch (param) { case GL.MODULATE: // Modulate means multiply the texture color with the fragment color // which is what our fragment shader does by default DEBUG > 1 && console.log("glTexEnv", GL_Symbol(target), "GL_TEXTURE_ENV_MODE", GL_Symbol(param)); gl.textureEnvMode = param; return; case GL.REPLACE: // Replace means use the texture color as the fragment color // which we emulate by forcing the uniform color to white DEBUG > 1 && console.log("glTexEnv", GL_Symbol(target), "GL_TEXTURE_ENV_MODE", GL_Symbol(param)); gl.textureEnvMode = param; return; default: if (DEBUG) console.log("UNIMPLEMENTED glTexEnv", GL_Symbol(target), "GL_TEXTURE_ENV_MODE", GL_Symbol(param)); else this.vm.warnOnce("OpenGL: UNIMPLEMENTED glTexEnv " + GL_Symbol(target) + " GL_TEXTURE_ENV_MODE " + GL_Symbol(param)); } break; default: if (DEBUG) console.log("UNIMPLEMENTED glTexEnv", GL_Symbol(target), GL_Symbol(pname), GL_Symbol(param)); else this.vm.warnOnce("OpenGL: UNIMPLEMENTED glTexEnv " + GL_Symbol(target) + " " + GL_Symbol(pname)); } }, glTexEnvi: function(target, pname, param) { this.glTexEnv(target, pname, param); }, glTexEnvf: function(target, pname, param) { this.glTexEnv(target, pname, param); }, glTexGen: function(coord, pname, param) { if (gl.listMode && this.addToList("glTexGen", [coord, pname, param])) return; if (DEBUG) console.log("UNIMPLEMENTED glTexGen", GL_Symbol(coord, "S"), GL_Symbol(pname), GL_Symbol(param)); else this.vm.warnOnce("OpenGL: UNIMPLEMENTED glTexGen " + GL_Symbol(pname)); }, glTexGeni: function(coord, pname, param) { this.glTexGen(coord, pname, param); }, glTexGenf: function(coord, pname, param) { this.glTexGen(coord, pname, param); }, glTexGend: function(coord, pname, params) { this.glTexGen(coord, pname, param); }, glTexGenfv: function(coord, pname, params) { if (gl.listMode && this.addToList("glTexGenfv", [coord, pname, params])) return; if (DEBUG) console.log("UNIMPLEMENTED glTexGenfv", GL_Symbol(coord, "S"), GL_Symbol(pname), Array.from(params)); else this.vm.warnOnce("OpenGL: UNIMPLEMENTED glTexGenfv " + GL_Symbol(pname)); }, glTexImage2D: function(target, level, internalformat, width, height, border, format, type, pixels) { if (gl.listMode && this.addToList("glTexImage2D", [target, level, internalformat, width, height, border, format, type, pixels])) return; DEBUG > 1 && console.log("glTexImage2D", GL_Symbol(target), level, GL_Symbol(internalformat), width, height, border, GL_Symbol(format), GL_Symbol(type), pixels); gl.texture.width = width; gl.texture.height = height; // WebGL does not support GL_UNPACK_ROW_LENGTH, GL_UNPACK_SKIP_ROWS, GL_UNPACK_SKIP_PIXELS if (gl.pixelStoreUnpackRowLength !== 0 && gl.pixelStoreUnpackRowLength !== gl.texture.width) { if (DEBUG) console.warn("UNIMPLEMENTED glTexImage2D GL_UNPACK_ROW_LENGTH " + gl.pixelStoreUnpackRowLength); else this.vm.warnOnce("OpenGL: UNIMPLEMENTED glTexImage2D GL_UNPACK_ROW_LENGTH"); } if (gl.pixelStoreUnpackSkipRows !== 0) { if (DEBUG) console.warn("UNIMPLEMENTED glTexImage2D GL_UNPACK_SKIP_ROWS " + gl.pixelStoreUnpackSkipRows); else this.vm.warnOnce("OpenGL: UNIMPLEMENTED glTexImage2D GL_UNPACK_SKIP_ROWS"); } if (gl.pixelStoreUnpackSkipPixels !== 0) { if (DEBUG) console.warn("UNIMPLEMENTED glTexImage2D GL_UNPACK_SKIP_PIXELS " + gl.pixelStoreUnpackSkipPixels); else this.vm.warnOnce("OpenGL: UNIMPLEMENTED glTexImage2D GL_UNPACK_SKIP_PIXELS"); } // WebGL only supports GL_RGBA switch (format) { case webgl.RGBA: if (DEBUG) console.warn("glTexImage2D GL_RGBA: need to not swizzle BGRA"); else this.vm.warnOnce("OpenGL: UNIMPLEMENTED glTexImage2D format GL_RGBA"); break; case GL.BGRA: format = webgl.RGBA; break; default: if (DEBUG) console.warn("UNIMPLEMENTED glTexImage2D format " + GL_Symbol(format)); else this.vm.warnOnce("OpenGL: UNIMPLEMENTED glTexImage2D format " + GL_Symbol(format)); return; } // pixels are coming in via FFI as void* (ArrayBuffer) // convert to appropriate typed array // in OpenGL, pixels can be null to allocate texture storage if (!pixels) pixels = new ArrayBuffer(width * height * 4); switch (type) { case webgl.UNSIGNED_BYTE: pixels = new Uint8Array(pixels); gl.texture.pixels = pixels; // for debugging break; default: if (DEBUG) console.warn("UNIMPLEMENTED glTexImage2D type " + GL_Symbol(type)); else this.vm.warnOnce("OpenGL: UNIMPLEMENTED glTexImage2D type " + GL_Symbol(type)); return; } webgl.texImage2D(target, level, internalformat, width, height, border, format, type, pixels); if (gl.texture.generateMipmapSGIS) webgl.generateMipmap(target); }, debugTexture: function(texture) { if (texture === false) { var canvas = document.getElementById("texDebug"); if (canvas) canvas.remove(); return; } if (!texture) texture = gl.texture; var pixels = texture.pixels; var width = texture.width; var height = texture.height; var data = new Uint8Array(pixels); var canvas = document.getElementById("texDebug"); if (!canvas) { canvas = document.createElement('canvas'); canvas.id = "texDebug"; canvas.style.position = "absolute"; canvas.style.zIndex = 1000; document.body.appendChild(canvas); } canvas.width = width; canvas.height = height; var ctx = canvas.getContext('2d'); var imageData = ctx.createImageData(width, height); imageData.data.set(data); ctx.putImageData(imageData, 0, 0); }, glTexCoord2d: function(s, t) { if (gl.listMode && this.addToList("glTexCoord2d", [s, t])) return; DEBUG > 1 && console.log("glTexCoord2d", s, t); gl.texCoord[0] = s; gl.texCoord[1] = t; gl.primitiveAttrs |= HAS_TEXCOORD; }, glTexCoord2f: function(s, t) { if (gl.listMode && this.addToList("glTexCoord2f", [s, t])) return; DEBUG > 1 && console.log("glTexCoord2f", s, t); gl.texCoord[0] = s; gl.texCoord[1] = t; gl.primitiveAttrs |= HAS_TEXCOORD; }, glTexCoord2fv: function(v) { if (gl.listMode && this.addToList("glTexCoord2fv", [v.slice()])) return; DEBUG > 1 && console.log("glTexCoord2fv", Array.from(v)); gl.texCoord.set(v); gl.primitiveAttrs |= HAS_TEXCOORD; }, glTexCoordPointer: function(size, type, stride, pointer) { if (gl.listMode && this.addToList("glTexCoordPointer", [size, type, stride, pointer])) return; DEBUG > 1 && console.log("glTexCoordPointer", size, GL_Symbol(type), stride, pointer); gl.clientState.textureCoordArray.size = size; gl.clientState.textureCoordArray.type = type; gl.clientState.textureCoordArray.stride = stride; gl.clientState.textureCoordArray.pointer = pointer; }, glTexParameteri: function(target, pname, param) { if (gl.listMode && this.addToList("glTexParameteri", [target, pname, param])) return; DEBUG > 1 && console.log("glTexParameteri", GL_Symbol(target), GL_Symbol(pname), GL_Symbol(param)); if (pname === GL.GENERATE_MIPMAP_SGIS) { // WebGL does not support GL_GENERATE_MIPMAP_SGIS. Emulate it gl.texture.generateMipmapSGIS = param; // if an image has been uploaded already, generate mipmaps now if (param && gl.texture.width > 1) webgl.generateMipmap(target); return; } webgl.texParameteri(target, pname, param); }, glTexSubImage2D: function(target, level, xoffset, yoffset, width, height, format, type, pixels) { if (gl.listMode && this.addToList("glTexSubImage2D", [target, level, xoffset, yoffset, width, height, format, type, pixels])) return; DEBUG > 1 && console.log("glTexSubImage2D", GL_Symbol(target), level, xoffset, yoffset, width, height, GL_Symbol(format), GL_Symbol(type), pixels); // WebGL does not support GL_UNPACK_ROW_LENGTH, GL_UNPACK_SKIP_ROWS, GL_UNPACK_SKIP_PIXELS // emulate GL_UNPACK_SKIP_ROWS var pixelsOffset = gl.pixelStoreUnpackSkipRows * gl.texture.width; // to be multiplied by pixel size below // assume GL_UNPACK_ROW_LENGTH is full width (which is the case when uploading part of a bitmap in Squeak) if (gl.pixelStoreUnpackRowLength !== 0 && gl.pixelStoreUnpackRowLength !== gl.texture.width) { if (DEBUG) console.warn("UNIMPLEMENTED glTexSubImage2D GL_UNPACK_ROW_LENGTH " + gl.pixelStoreUnpackRowLength); else this.vm.warnOnce("OpenGL: UNIMPLEMENTED glTexSubImage2D GL_UNPACK_ROW_LENGTH"); } // WebGL does not support GL_UNPACK_SKIP_PIXELS to allow different width if (width !== gl.texture.width) { // we could either // 1. call texSubImage2D for each row // 2. copy subimage pixels into a new buffer // 3. call texSubImage2D for the whole width so we don't need to skip pixels // we choose 3. for now width = gl.texture.width; xoffset = 0; } // WebGL only supports RGB not BGR switch (format) { case webgl.RGBA: pixelsOffset *= 4; break; case GL.BGRA: pixelsOffset *= 4; format = webgl.RGBA; break; default: if (DEBUG) console.warn("UNIMPLEMENTED glTexSubImage2D format " + GL_Symbol(format)); else this.vm.warnOnce("OpenGL: UNIMPLEMENTED glTexSubImage2D format " + GL_Symbol(format)); return; } // pixels are coming in via FFI as void* (ArrayBuffer) // convert to appropriate typed array switch (type) { case webgl.UNSIGNED_BYTE: pixels = new Uint8Array(pixels, pixelsOffset); break; default: if (DEBUG) console.warn("UNIMPLEMENTED glTexSubImage2D type " + GL_Symbol(type)); else this.vm.warnOnce("OpenGL: UNIMPLEMENTED glTexSubImage2D type " + GL_Symbol(type)); return; } webgl.texSubImage2D(target, level, xoffset, yoffset, width, height, format, type, pixels); if (gl.texture.generateMipmapSGIS) webgl.generateMipmap(target); }, glVertex2f: function(x, y) { if (gl.listMode && this.addToList("glVertex2f", [x, y])) return; DEBUG > 1 && console.log("glVertex2f", x, y); var position = [x, y]; this.pushVertex(position); }, glVertex3f: function(x, y, z) { if (gl.listMode && this.addToList("glVertex3f", [x, y, z])) return; DEBUG > 1 && console.log("glVertex3f", x, y, z); var position = [x, y, z]; this.pushVertex(position); }, glVertex3fv: function(v) { if (gl.listMode && this.addToList("glVertex3fv", [v.slice()])) return; DEBUG > 1 && console.log("glVertex3fv", Array.from(v)); this.pushVertex(v); }, glVertex2i: function(x, y) { if (gl.listMode && this.addToList("glVertex2i", [x, y])) return; DEBUG > 1 && console.log("glVertex2i", x, y); var position = [x, y]; this.pushVertex(position); }, glVertexPointer: function(size, type, stride, pointer) { if (gl.listMode && this.addToList("glVertexPointer", [size, type, stride, pointer])) return; DEBUG > 1 && console.log("glVertexPointer", size, GL_Symbol(type), stride, pointer); gl.clientState.vertexArray.size = size; gl.clientState.vertexArray.type = type; gl.clientState.vertexArray.stride = stride; gl.clientState.vertexArray.pointer = pointer; }, glViewport: function(x, y, width, height) { if (gl.listMode && this.addToList("glViewport", [x, y, width, height])) return; DEBUG > 1 && console.log("glViewport", x, y, width, height); webgl.viewport(x, y, width, height); gl.viewport[0] = x; gl.viewport[1] = y; gl.viewport[2] = width; gl.viewport[3] = height; }, glXGetProcAddressARB: function(procName) { procName = Squeak.bytesAsString(procName); DEBUG > 1 && console.log("glXGetProcAddressARB", procName); var handle = this.ffi.ffiLookupFunc(this, procName); if (!handle) { if (DEBUG) console.warn("UNIMPLEMENTED EXT FUNC", procName); else this.vm.warnOnce("OpenGL: UNIMPLEMENTED EXT FUNC" + procName); } return handle; }, pushVertex: function(position) { var primitive = gl.primitive; if (!primitive) throw Error("OpenGL: glBegin not called"); if (!primitive.vertexSize) { var vertexSize = 3; if (gl.primitiveAttrs & HAS_NORMAL) vertexSize += 3; if (gl.primitiveAttrs & HAS_COLOR) vertexSize += 4; if (gl.primitiveAttrs & HAS_TEXCOORD) vertexSize += 2; primitive.vertexSize = vertexSize; primitive.vertexAttrs = gl.primitiveAttrs; } var vertex = new Float32Array(primitive.vertexSize); var offset = 0; vertex.set(position, offset); offset += 3; if (primitive.vertexAttrs & HAS_NORMAL) { vertex.set(gl.normal, offset); offset += 3; } if (primitive.vertexAttrs & HAS_COLOR) { vertex.set(gl.color, offset); offset += 4; } if (primitive.vertexAttrs & HAS_TEXCOORD) { vertex.set(gl.texCoord, offset); offset += 2; } primitive.vertices.push(vertex); }, // Shader source code // Note: we could take a look at Emscripten's glemu to add some more features // https://github.com/emscripten-core/emscripten/blob/cb99414efed02dc61d04315d3e3cf5ad3180e56f/src/library_glemu.js#L2170 // The structure is a bit different but applicable vertexShaderSource: function(shaderFlags) { var src = []; src.push("uniform mat4 uModelView;"); src.push("uniform mat4 uProjection;"); src.push("attribute vec3 aPosition;"); if (shaderFlags & HAS_COLOR) { src.push("attribute vec4 aColor;"); } else if (shaderFlags & ANY_LIGHTS) { src.push("uniform vec4 uColor;"); } if (shaderFlags & (HAS_COLOR | ANY_LIGHTS)) { src.push("varying vec4 vColor;"); } if (shaderFlags & HAS_TEXCOORD && shaderFlags & USE_TEXTURE) { src.push("uniform mat4 uTextureMatrix;"); src.push("attribute vec2 aTexCoord;"); src.push("varying vec2 vTexCoord;"); } if (shaderFlags & USE_POINT_SIZE) { src.push("uniform float uPointSize;"); } var numLights = (shaderFlags & NUM_LIGHTS_MASK) >> NUM_LIGHTS_SHIFT; if (numLights > 0) { if (shaderFlags & HAS_NORMAL) { src.push("attribute vec3 aNormal;"); } else { src.push("uniform vec3 uNormal;"); } src.push("uniform mat3 uNormalMatrix;"); src.push("uniform vec4 uLightModelAmbient;"); src.push("uniform vec4 uMaterialAmbient;"); src.push("uniform vec4 uMaterialDiffuse;"); src.push("uniform vec4 uMaterialSpecular;"); src.push("uniform vec4 uMaterialEmission;"); src.push("uniform float uMaterialShininess;"); src.push("struct Light {"); src.push(" vec4 position;"); src.push(" vec4 ambient;"); src.push(" vec4 diffuse;"); src.push(" vec4 specular;"); src.push("};"); src.push("uniform Light uLights[" + numLights + "];"); } var numClipPlanes = (shaderFlags & NUM_CLIP_PLANES_MASK) >> NUM_CLIP_PLANES_SHIFT; if (numClipPlanes > 0) { src.push("uniform vec4 uClipPlanes[" + numClipPlanes + "];"); src.push("varying float vClipDist[" + numClipPlanes + "];"); } var fog = (shaderFlags & FOG_MASK) >> FOG_SHIFT; if (fog !== NO_FOG) { if (fog === LINEAR_FOG) { src.push("uniform float uFogEnd;"); src.push("uniform float uFogRange;"); } else { src.push("uniform float uFogDensity;"); } src.push("varying float vFogDist;"); } src.push("void main(void) {"); if (shaderFlags & HAS_COLOR) { src.push(" vColor = aColor;"); } else if (shaderFlags & ANY_LIGHTS) { src.push(" vColor = uColor;"); } src.push(" vec4 position = uModelView * vec4(aPosition, 1.0);"); if (numLights > 0) { if (shaderFlags & HAS_NORMAL) { src.push(" vec3 normal = normalize(uNormalMatrix * aNormal);"); } else { src.push(" vec3 normal = normalize(uNormalMatrix * uNormal);"); } src.push(" vec4 lighting = uMaterialEmission;"); src.push(" lighting += uMaterialAmbient * uLightModelAmbient;"); src.push(" vec3 eyeDir = normalize(-position.xyz);"); src.push(" for (int i = 0; i < " + numLights + "; i++) {"); src.push(" Light light = uLights[i];"); src.push(" vec3 lightDir;"); src.push(" if (light.position.w == 0.0) {"); src.push(" lightDir = normalize(light.position.xyz);"); src.push(" } else {"); src.push(" lightDir = normalize(light.position.xyz - position.xyz);"); src.push(" }"); src.push(" float nDotL = max(dot(normal, lightDir), 0.0);"); src.push(" vec4 ambient = uMaterialAmbient * light.ambient;"); src.push(" vec4 diffuse = uMaterialDiffuse * light.diffuse * nDotL;"); src.push(" vec4 specular = vec4(0.0);"); src.push(" if (nDotL > 0.0) {"); src.push(" vec3 halfVector = normalize(lightDir + eyeDir);"); src.push(" float nDotHV = max(dot(normal, halfVector), 0.0);"); src.push(" specular = uMaterialSpecular * light.specular * pow(nDotHV, uMaterialShininess);"); src.push(" }"); src.push(" lighting += ambient + diffuse + specular;"); src.push(" }"); src.push(" vColor = clamp(lighting, 0.0, 1.0);"); src.push(" vColor.a = uMaterialDiffuse.a;"); } if (numClipPlanes > 0) { src.push(" for (int i = 0; i < " + numClipPlanes + "; i++) {"); src.push(" vClipDist[i] = dot(position, uClipPlanes[i]);"); src.push(" }"); } if (shaderFlags & HAS_TEXCOORD && shaderFlags & USE_TEXTURE) { src.push(" vec4 texCoord = uTextureMatrix * vec4(aTexCoord, 0.0, 1.0);"); src.push(" vTexCoord = texCoord.xy / texCoord.w;"); } if (fog !== NO_FOG) { src.push(" vFogDist = -position.z;"); } if (shaderFlags & USE_POINT_SIZE) { src.push(" gl_PointSize = uPointSize;"); } src.push(" gl_Position = uProjection * position;"); src.push("}"); var src = src.join("\n"); DEBUG > 1 && console.log(src); return src; }, fragmentShaderSource: function(shaderFlags) { var src = []; src.push("precision mediump float;"); var numClipPlanes = (shaderFlags & NUM_CLIP_PLANES_MASK) >> NUM_CLIP_PLANES_SHIFT; if (numClipPlanes > 0) { src.push("varying float vClipDist[" + numClipPlanes + "];"); } if (shaderFlags & (HAS_COLOR | ANY_LIGHTS)) { src.push("varying vec4 vColor;"); } else { src.push("uniform vec4 uColor;"); } if (shaderFlags & USE_TEXTURE) { if (shaderFlags & HAS_TEXCOORD) { src.push("varying vec2 vTexCoord;"); } else { src.push("uniform vec2 uTexCoord;"); } src.push("uniform sampler2D uSampler;"); } var fog = (shaderFlags & FOG_MASK) >> FOG_SHIFT; if (fog !== NO_FOG) { if (fog === LINEAR_FOG) { src.push("uniform float uFogEnd;"); src.push("uniform float uFogRange;"); } else { src.push("uniform float uFogDensity;"); } src.push("uniform vec4 uFogColor;"); src.push("varying float vFogDist;"); } src.push("void main(void) {"); if (numClipPlanes > 0) { src.push(" bool clipped = false;"); src.push(" for (int i = 0; i < " + numClipPlanes + "; i++) {"); src.push(" if (vClipDist[i] < 0.0) clipped = true;"); src.push(" }"); src.push(" if (clipped) discard;"); } if (shaderFlags & (HAS_COLOR | ANY_LIGHTS)) { src.push(" vec4 color = vColor;"); } else { src.push(" vec4 color = uColor;"); } if (shaderFlags & USE_TEXTURE) { if (shaderFlags & HAS_TEXCOORD) { src.push(" vec2 texCoord = vTexCoord;"); } else { src.push(" vec2 texCoord = uTexCoord;"); } src.push(" color *= texture2D(uSampler, texCoord).bgra;"); } if (fog !== NO_FOG) { switch (fog) { case LINEAR_FOG: src.push(" float fogAmount = (uFogEnd - vFogDist) / uFogRange;"); break; case EXP_FOG: src.push(" float fogAmount = exp(-uFogDensity * vFogDist);"); break; case EXP2_FOG: src.push(" float fogAmount = exp(-uFogDensity * uFogDensity * vFogDist * vFogDist);"); break; } src.push(" color.rgb = mix(uFogColor.rgb, color.rgb, clamp(fogAmount, 0.0, 1.0));"); } src.push(" gl_FragColor = color;"); src.push("}"); var src = src.join("\n"); DEBUG > 1 && console.log(src); return src; }, getLocations: function(program, shaderFlags) { var locations = {}; locations.uModelView = webgl.getUniformLocation(program, "uModelView"); locations.uProjection = webgl.getUniformLocation(program, "uProjection"); locations.aPosition = webgl.getAttribLocation(program, "aPosition"); if (shaderFlags & USE_TEXTURE) { if (shaderFlags & HAS_TEXCOORD) { locations.uTextureMatrix = webgl.getUniformLocation(program, "uTextureMatrix"); locations.aTexCoord = webgl.getAttribLocation(program, "aTexCoord"); } else { locations.uTexCoord = webgl.getUniformLocation(program, "uTexCoord"); } locations.uSampler = webgl.getUniformLocation(program, "uSampler"); } if (shaderFlags & HAS_COLOR) { locations.aColor = webgl.getAttribLocation(program, "aColor"); } else { locations.uColor = webgl.getUniformLocation(program, "uColor"); } if (shaderFlags & USE_POINT_SIZE) { locations.uPointSize = webgl.getUniformLocation(program, "uPointSize"); } var numLights = (shaderFlags & NUM_LIGHTS_MASK) >> NUM_LIGHTS_SHIFT; if (numLights > 0) { if (shaderFlags & HAS_NORMAL) { locations.aNormal = webgl.getAttribLocation(program, "aNormal"); } else{ locations.uNormal = webgl.getUniformLocation(program, "uNormal"); } locations.uNormalMatrix = webgl.getUniformLocation(program, "uNormalMatrix"); locations.uLightModelAmbient = webgl.getUniformLocation(program, "uLightModelAmbient"); locations.uMaterialAmbient = webgl.getUniformLocation(program, "uMaterialAmbient"); locations.uMaterialDiffuse = webgl.getUniformLocation(program, "uMaterialDiffuse"); locations.uMaterialSpecular = webgl.getUniformLocation(program, "uMaterialSpecular"); locations.uMaterialEmission = webgl.getUniformLocation(program, "uMaterialEmission"); locations.uMaterialShininess = webgl.getUniformLocation(program, "uMaterialShininess"); locations.uLights = []; for (var i = 0; i < numLights; i++) { var light = {}; light.position = webgl.getUniformLocation(program, "uLights[" + i + "].position"); light.ambient = webgl.getUniformLocation(program, "uLights[" + i + "].ambient"); light.diffuse = webgl.getUniformLocation(program, "uLights[" + i + "].diffuse"); light.specular = webgl.getUniformLocation(program, "uLights[" + i + "].specular"); locations.uLights.push(light); } } var numClipPlanes = (shaderFlags & NUM_CLIP_PLANES_MASK) >> NUM_CLIP_PLANES_SHIFT; if (numClipPlanes > 0) { locations.uClipPlanes = []; for (var i = 0; i < numClipPlanes; i++) { locations.uClipPlanes.push(webgl.getUniformLocation(program, "uClipPlanes[" + i + "]")); } } var fog = (shaderFlags & FOG_MASK) >> FOG_SHIFT; if (fog !== NO_FOG) { if (fog === LINEAR_FOG) { locations.uFogEnd = webgl.getUniformLocation(program, "uFogEnd"); locations.uFogRange = webgl.getUniformLocation(program, "uFogRange"); } else { locations.uFogDensity = webgl.getUniformLocation(program, "uFogDensity"); } locations.uFogColor = webgl.getUniformLocation(program, "uFogColor"); } DEBUG > 1 && console.log(locations); return locations; }, executeList: function(listId) { var list = gl.lists[listId]; if (!list) { DEBUG > 0 && console.warn("OpenGL: display list not found " + listId); return; } for (var i = 0; i < list.commands.length; i++) { var cmd = list.commands[i]; this[cmd.name].apply(this, cmd.args); } }, }; } function transformDirection(matrix, src, dst) { var x = src[0]; var y = src[1]; var z = src[2]; var rx = matrix[0] * x + matrix[4] * y + matrix[8] * z; var ry = matrix[1] * x + matrix[5] * y + matrix[9] * z; var rz = matrix[2] * x + matrix[6] * y + matrix[10] * z; dst[0] = rx; dst[1] = ry; dst[2] = rz; dst[3] = src[3]; } function transformPoint(matrix, src, dst) { var x = src[0]; var y = src[1]; var z = src[2]; var rx = matrix[0] * x + matrix[4] * y + matrix[8] * z + matrix[12]; var ry = matrix[1] * x + matrix[5] * y + matrix[9] * z + matrix[13]; var rz = matrix[2] * x + matrix[6] * y + matrix[10] * z + matrix[14]; var rw = matrix[3] * x + matrix[7] * y + matrix[11] * z + matrix[15]; if (rw === 1) { dst[0] = rx; dst[1] = ry; dst[2] = rz; } else { if (rw !== 0) rw = 1 / rw; dst[0] = rx * rw; dst[1] = ry * rw; dst[2] = rz * rw; } dst[3] = src[3]; } function multVec4(matrix, src, dst) { var x = src[0]; var y = src[1]; var z = src[2]; var w = src[3]; dst[0] = matrix[0] * x + matrix[4] * y + matrix[8] * z + matrix[12] * w; dst[1] = matrix[1] * x + matrix[5] * y + matrix[9] * z + matrix[13] * w; dst[2] = matrix[2] * x + matrix[6] * y + matrix[10] * z + matrix[14] * w; dst[3] = matrix[3] * x + matrix[7] * y + matrix[11] * z + matrix[15] * w; } function multMatrix(m1, m2) { var m00 = m1[0] * m2[0] + m1[4] * m2[1] + m1[8] * m2[2] + m1[12] * m2[3]; var m01 = m1[1] * m2[0] + m1[5] * m2[1] + m1[9] * m2[2] + m1[13] * m2[3]; var m02 = m1[2] * m2[0] + m1[6] * m2[1] + m1[10] * m2[2] + m1[14] * m2[3]; var m03 = m1[3] * m2[0] + m1[7] * m2[1] + m1[11] * m2[2] + m1[15] * m2[3]; var m10 = m1[0] * m2[4] + m1[4] * m2[5] + m1[8] * m2[6] + m1[12] * m2[7]; var m11 = m1[1] * m2[4] + m1[5] * m2[5] + m1[9] * m2[6] + m1[13] * m2[7]; var m12 = m1[2] * m2[4] + m1[6] * m2[5] + m1[10] * m2[6] + m1[14] * m2[7]; var m13 = m1[3] * m2[4] + m1[7] * m2[5] + m1[11] * m2[6] + m1[15] * m2[7]; var m20 = m1[0] * m2[8] + m1[4] * m2[9] + m1[8] * m2[10] + m1[12] * m2[11]; var m21 = m1[1] * m2[8] + m1[5] * m2[9] + m1[9] * m2[10] + m1[13] * m2[11]; var m22 = m1[2] * m2[8] + m1[6] * m2[9] + m1[10] * m2[10] + m1[14] * m2[11]; var m23 = m1[3] * m2[8] + m1[7] * m2[9] + m1[11] * m2[10] + m1[15] * m2[11]; var m30 = m1[0] * m2[12] + m1[4] * m2[13] + m1[8] * m2[14] + m1[12] * m2[15]; var m31 = m1[1] * m2[12] + m1[5] * m2[13] + m1[9] * m2[14] + m1[13] * m2[15]; var m32 = m1[2] * m2[12] + m1[6] * m2[13] + m1[10] * m2[14] + m1[14] * m2[15]; var m33 = m1[3] * m2[12] + m1[7] * m2[13] + m1[11] * m2[14] + m1[15] * m2[15]; m1[0] = m00; m1[1] = m01; m1[2] = m02; m1[3] = m03; m1[4] = m10; m1[5] = m11; m1[6] = m12; m1[7] = m13; m1[8] = m20; m1[9] = m21; m1[10] = m22; m1[11] = m23; m1[12] = m30; m1[13] = m31; m1[14] = m32; m1[15] = m33; } function translateMatrix(m, x, y, z) { m[12] += x * m[0] + y * m[4] + z * m[8]; m[13] += x * m[1] + y * m[5] + z * m[9]; m[14] += x * m[2] + y * m[6] + z * m[10]; m[15] += x * m[3] + y * m[7] + z * m[11]; } function rotateMatrix(m, angle, x, y, z) { // normalize axis vector var len = Math.sqrt(x * x + y * y + z * z); if (len === 0) return; if (len !== 1) { len = 1 / len; x *= len; y *= len; z *= len; } // create rotation matrix var degrees = Math.PI / 180; var c = Math.cos(angle * degrees); var s = Math.sin(angle * degrees); var t = 1 - c; var m00 = x * x * t + c; var m01 = x * y * t - z * s; var m02 = x * z * t + y * s; var m10 = y * x * t + z * s; var m11 = y * y * t + c; var m12 = y * z * t - x * s; var m20 = z * x * t - y * s; var m21 = z * y * t + x * s; var m22 = z * z * t + c; // multiply rotation matrix var m0 = m[0] * m00 + m[4] * m01 + m[8] * m02; var m1 = m[1] * m00 + m[5] * m01 + m[9] * m02; var m2 = m[2] * m00 + m[6] * m01 + m[10] * m02; var m3 = m[3] * m00 + m[7] * m01 + m[11] * m02; var m4 = m[0] * m10 + m[4] * m11 + m[8] * m12; var m5 = m[1] * m10 + m[5] * m11 + m[9] * m12; var m6 = m[2] * m10 + m[6] * m11 + m[10] * m12; var m7 = m[3] * m10 + m[7] * m11 + m[11] * m12; m[8] = m[0] * m20 + m[4] * m21 + m[8] * m22; m[9] = m[1] * m20 + m[5] * m21 + m[9] * m22; m[10] = m[2] * m20 + m[6] * m21 + m[10] * m22; m[11] = m[3] * m20 + m[7] * m21 + m[11] * m22; m[0] = m0; m[1] = m1; m[2] = m2; m[3] = m3; m[4] = m4; m[5] = m5; m[6] = m6; m[7] = m7; } function scaleMatrix(m, x, y, z) { m[0] *= x; m[1] *= x; m[2] *= x; m[3] *= x; m[4] *= y; m[5] *= y; m[6] *= y; m[7] *= y; m[8] *= z; m[9] *= z; m[10] *= z; m[11] *= z; } function transposeMatrix(m) { var m01 = m[1], m02 = m[2], m03 = m[3], m12 = m[6], m13 = m[7], m23 = m[11]; m[1] = m[4]; m[2] = m[8]; m[3] = m[12]; m[4] = m01; m[6] = m[9]; m[7] = m[13]; m[8] = m02; m[9] = m12; m[11] = m[14]; m[12] = m03; m[13] = m13; m[14] = m23; } function invertMatrix(src, dst) { if (!dst) dst = src; var m00 = src[0], m01 = src[1], m02 = src[2], m03 = src[3], m10 = src[4], m11 = src[5], m12 = src[6], m13 = src[7], m20 = src[8], m21 = src[9], m22 = src[10], m23 = src[11], m30 = src[12], m31 = src[13], m32 = src[14], m33 = src[15]; var t00 = m00 * m11 - m01 * m10, t01 = m00 * m12 - m02 * m10, t02 = m00 * m13 - m03 * m10, t03 = m01 * m12 - m02 * m11, t04 = m01 * m13 - m03 * m11, t05 = m02 * m13 - m03 * m12, t06 = m20 * m31 - m21 * m30, t07 = m20 * m32 - m22 * m30, t08 = m20 * m33 - m23 * m30, t09 = m21 * m32 - m22 * m31, t10 = m21 * m33 - m23 * m31, t11 = m22 * m33 - m23 * m32; var det = t00 * t11 - t01 * t10 + t02 * t09 + t03 * t08 - t04 * t07 + t05 * t06; if (det === 0) return; var invDet = 1 / det; dst[0] = (m11 * t11 - m12 * t10 + m13 * t09) * invDet; dst[1] = (-m01 * t11 + m02 * t10 - m03 * t09) * invDet; dst[2] = (m31 * t05 - m32 * t04 + m33 * t03) * invDet; dst[3] = (-m21 * t05 + m22 * t04 - m23 * t03) * invDet; dst[4] = (-m10 * t11 + m12 * t08 - m13 * t07) * invDet; dst[5] = (m00 * t11 - m02 * t08 + m03 * t07) * invDet; dst[6] = (-m30 * t05 + m32 * t02 - m33 * t01) * invDet; dst[7] = (m20 * t05 - m22 * t02 + m23 * t01) * invDet; dst[8] = (m10 * t10 - m11 * t08 + m13 * t06) * invDet; dst[9] = (-m00 * t10 + m01 * t08 - m03 * t06) * invDet; dst[10] = (m30 * t04 - m31 * t02 + m33 * t00) * invDet; dst[11] = (-m20 * t04 + m21 * t02 - m23 * t00) * invDet; dst[12] = (-m10 * t09 + m11 * t07 - m12 * t06) * invDet; dst[13] = (m00 * t09 - m01 * t07 + m02 * t06) * invDet; dst[14] = (-m30 * t03 + m31 * t01 - m32 * t00) * invDet; dst[15] = (m20 * t03 - m21 * t01 + m22 * t00) * invDet; } function asNormalMatrix(m) { // inverse transpose of upper-left 3x3 matrix var out = new Float32Array(9); var m11 = m[0], m21 = m[1], m31 = m[2], m12 = m[4], m22 = m[5], m32 = m[6], m13 = m[8], m23 = m[9], m33 = m[10]; var t11 = m33 * m22 - m32 * m23, t12 = m32 * m13 - m33 * m12, t13 = m23 * m12 - m22 * m13; var det = m11 * t11 + m21 * t12 + m31 * t13; if (det !== 0 ) { const s = 1 / det; // store in transposed order out[0] = t11 * s; out[3] = (m31 * m23 - m33 * m21) * s; out[6] = (m32 * m21 - m31 * m22) * s; out[1] = t12 * s; out[4] = (m33 * m11 - m31 * m13) * s; out[7] = (m31 * m12 - m32 * m11) * s; out[2] = t13 * s; out[5] = (m21 * m13 - m23 * m11) * s; out[8] = (m22 * m11 - m21 * m12) * s; } return out; } var GL_Symbols; // reverse mapping for debug printing function GL_Symbol(constant, rangeStart) { if (constant === undefined) { debugger; return /* should not happen */} if (rangeStart !== undefined) { // rangeStart is e.g. "FALSE" or "POINTS" which are both 0 var all = Object.keys(GL); // we're relying on insertion order here var start = all.indexOf(rangeStart); return all[start + constant - GL[rangeStart]]; } else return GL_Symbols[constant] || constant; } function initGLConstants() { GL = { DEPTH_BUFFER_BIT: 0x00000100, STENCIL_BUFFER_BIT: 0x00000400, COLOR_BUFFER_BIT: 0x00004000, FALSE: 0, TRUE: 1, POINTS: 0x0000, LINES: 0x0001, LINE_LOOP: 0x0002, LINE_STRIP: 0x0003, TRIANGLES: 0x0004, TRIANGLE_STRIP: 0x0005, TRIANGLE_FAN: 0x0006, QUADS: 0x0007, QUAD_STRIP: 0x0008, POLYGON: 0x0009, NEVER: 0x0200, LESS: 0x0201, EQUAL: 0x0202, LEQUAL: 0x0203, GREATER: 0x0204, NOTEQUAL: 0x0205, GEQUAL: 0x0206, ALWAYS: 0x0207, SRC_COLOR: 0x0300, ONE_MINUS_SRC_COLOR: 0x0301, SRC_ALPHA: 0x0302, ONE_MINUS_SRC_ALPHA: 0x0303, DST_ALPHA: 0x0304, ONE_MINUS_DST_ALPHA: 0x0305, DST_COLOR: 0x0306, ONE_MINUS_DST_COLOR: 0x0307, SRC_ALPHA_SATURATE: 0x0308, NONE: 0, FRONT_LEFT: 0x0400, FRONT_RIGHT: 0x0401, BACK_LEFT: 0x0402, BACK_RIGHT: 0x0403, FRONT: 0x0404, BACK: 0x0405, LEFT: 0x0406, RIGHT: 0x0407, FRONT_AND_BACK: 0x0408, NO_ERROR: 0, INVALID_ENUM: 0x0500, INVALID_VALUE: 0x0501, INVALID_OPERATION: 0x0502, OUT_OF_MEMORY: 0x0505, EXP: 0x0800, EXP2: 0x0801, CW: 0x0900, CCW: 0x0901, POINT_SIZE: 0x0B11, POINT_SIZE_RANGE: 0x0B12, POINT_SIZE_GRANULARITY: 0x0B13, LINE_SMOOTH: 0x0B20, LINE_WIDTH: 0x0B21, LINE_WIDTH_RANGE: 0x0B22, LINE_WIDTH_GRANULARITY: 0x0B23, LIST_INDEX: 0x0B33, LIGHTING: 0x0B50, ALPHA_TEST: 0x0BC0, POLYGON_MODE: 0x0B40, POLYGON_SMOOTH: 0x0B41, CULL_FACE: 0x0B44, CULL_FACE_MODE: 0x0B45, FRONT_FACE: 0x0B46, COLOR_MATERIAL: 0x0B57, FOG: 0x0B60, FOG_DENSITY: 0x0B62, FOG_START: 0x0B63, FOG_END: 0x0B64, FOG_MODE: 0x0B65, FOG_COLOR: 0x0B66, DEPTH_RANGE: 0x0B70, DEPTH_TEST: 0x0B71, DEPTH_WRITEMASK: 0x0B72, DEPTH_CLEAR_VALUE: 0x0B73, DEPTH_FUNC: 0x0B74, STENCIL_TEST: 0x0B90, STENCIL_CLEAR_VALUE: 0x0B91, STENCIL_FUNC: 0x0B92, STENCIL_VALUE_MASK: 0x0B93, STENCIL_FAIL: 0x0B94, STENCIL_PASS_DEPTH_FAIL: 0x0B95, STENCIL_PASS_DEPTH_PASS: 0x0B96, STENCIL_REF: 0x0B97, STENCIL_WRITEMASK: 0x0B98, MATRIX_MODE: 0x0BA0, NORMALIZE: 0x0BA1, VIEWPORT: 0x0BA2, MODELVIEW_STACK_DEPTH: 0x0BA3, PROJECTION_STACK_DEPTH: 0x0BA4, TEXTURE_STACK_DEPTH: 0x0BA5, MODELVIEW_MATRIX: 0x0BA6, PROJECTION_MATRIX: 0x0BA7, TEXTURE_MATRIX: 0x0BA8, ATTRIB_STACK_DEPTH: 0x0BB0, CLIENT_ATTRIB_STACK_DEPTH: 0x0BB1, ALPHA_TEST: 0x0BC0, ALPHA_TEST_FUNC: 0x0BC1, ALPHA_TEST_REF: 0x0BC2, DITHER: 0x0BD0, BLEND_DST: 0x0BE0, BLEND_SRC: 0x0BE1, BLEND: 0x0BE2, LOGIC_OP_MODE: 0x0BF0, DRAW_BUFFER: 0x0C01, READ_BUFFER: 0x0C02, SCISSOR_BOX: 0x0C10, SCISSOR_TEST: 0x0C11, COLOR_CLEAR_VALUE: 0x0C22, COLOR_WRITEMASK: 0x0C23, DOUBLEBUFFER: 0x0C32, STEREO: 0x0C33, PERSPECTIVE_CORRECTION_HINT: 0x0C50, LINE_SMOOTH_HINT: 0x0C52, POLYGON_SMOOTH_HINT: 0x0C53, FOG_HINT: 0x0C54, TEXTURE_GEN_S: 0x0C60, TEXTURE_GEN_T: 0x0C61, TEXTURE_GEN_R: 0x0C62, TEXTURE_GEN_Q: 0x0C63, PIXEL_MAP_I_TO_I: 0x0C70, PIXEL_MAP_S_TO_S: 0x0C71, PIXEL_MAP_I_TO_R: 0x0C72, PIXEL_MAP_I_TO_G: 0x0C73, PIXEL_MAP_I_TO_B: 0x0C74, PIXEL_MAP_I_TO_A: 0x0C75, PIXEL_MAP_R_TO_R: 0x0C76, PIXEL_MAP_G_TO_G: 0x0C77, PIXEL_MAP_B_TO_B: 0x0C78, PIXEL_MAP_A_TO_A: 0x0C79, PIXEL_MAP_I_TO_I_SIZE: 0x0CB0, PIXEL_MAP_S_TO_S_SIZE: 0x0CB1, PIXEL_MAP_I_TO_R_SIZE: 0x0CB2, PIXEL_MAP_I_TO_G_SIZE: 0x0CB3, PIXEL_MAP_I_TO_B_SIZE: 0x0CB4, PIXEL_MAP_I_TO_A_SIZE: 0x0CB5, PIXEL_MAP_R_TO_R_SIZE: 0x0CB6, PIXEL_MAP_G_TO_G_SIZE: 0x0CB7, PIXEL_MAP_B_TO_B_SIZE: 0x0CB8, PIXEL_MAP_A_TO_A_SIZE: 0x0CB9, UNPACK_SWAP_BYTES: 0x0CF0, UNPACK_LSB_FIRST: 0x0CF1, UNPACK_ROW_LENGTH: 0x0CF2, UNPACK_SKIP_ROWS: 0x0CF3, UNPACK_SKIP_PIXELS: 0x0CF4, UNPACK_ALIGNMENT: 0x0CF5, PACK_SWAP_BYTES: 0x0D00, PACK_LSB_FIRST: 0x0D01, PACK_ROW_LENGTH: 0x0D02, PACK_SKIP_ROWS: 0x0D03, PACK_SKIP_PIXELS: 0x0D04, PACK_ALIGNMENT: 0x0D05, MAX_TEXTURE_SIZE: 0x0D33, MAX_VIEWPORT_DIMS: 0x0D3A, SUBPIXEL_BITS: 0x0D50, TEXTURE_1D: 0x0DE0, TEXTURE_2D: 0x0DE1, TEXTURE_WIDTH: 0x1000, TEXTURE_HEIGHT: 0x1001, TEXTURE_BORDER_COLOR: 0x1004, DONT_CARE: 0x1100, FASTEST: 0x1101, NICEST: 0x1102, AMBIENT: 0x1200, DIFFUSE: 0x1201, SPECULAR: 0x1202, POSITION: 0x1203, SPOT_CUTOFF: 0x1206, COMPILE: 0x1300, COMPILE_AND_EXECUTE: 0x1301, BYTE: 0x1400, UNSIGNED_BYTE: 0x1401, SHORT: 0x1402, UNSIGNED_SHORT: 0x1403, INT: 0x1404, UNSIGNED_INT: 0x1405, FLOAT: 0x1406, STACK_OVERFLOW: 0x0503, STACK_UNDERFLOW: 0x0504, CLEAR: 0x1500, AND: 0x1501, AND_REVERSE: 0x1502, COPY: 0x1503, AND_INVERTED: 0x1504, NOOP: 0x1505, XOR: 0x1506, OR: 0x1507, NOR: 0x1508, EQUIV: 0x1509, INVERT: 0x150A, OR_REVERSE: 0x150B, COPY_INVERTED: 0x150C, OR_INVERTED: 0x150D, NAND: 0x150E, SET: 0x150F, EMISSION: 0x1600, SHININESS: 0x1601, AMBIENT_AND_DIFFUSE: 0x1602, COLOR_INDEXES: 0x1603, MODELVIEW: 0x1700, PROJECTION: 0x1701, TEXTURE: 0x1702, COLOR: 0x1800, DEPTH: 0x1801, STENCIL: 0x1802, STENCIL_INDEX: 0x1901, DEPTH_COMPONENT: 0x1902, RED: 0x1903, GREEN: 0x1904, BLUE: 0x1905, ALPHA: 0x1906, RGB: 0x1907, RGBA: 0x1908, POINT: 0x1B00, LINE: 0x1B01, FILL: 0x1B02, FLAT: 0x1D00, SMOOTH: 0x1D01, KEEP: 0x1E00, REPLACE: 0x1E01, INCR: 0x1E02, DECR: 0x1E03, VENDOR: 0x1F00, RENDERER: 0x1F01, VERSION: 0x1F02, EXTENSIONS: 0x1F03, S: 0x2000, T: 0x2001, R: 0x2002, Q: 0x2003, MODULATE: 0x2100, DECAL: 0x2101, TEXTURE_ENV_MODE: 0x2200, TEXTURE_ENV: 0x2300, EYE_LINEAR: 0x2400, OBJECT_LINEAR: 0x2401, SPHERE_MAP: 0x2402, TEXTURE_GEN_MODE: 0x2500, OBJECT_PLANE: 0x2501, EYE_PLANE: 0x2502, SPHERE_MAP: 0x2402, NEAREST: 0x2600, LINEAR: 0x2601, NEAREST_MIPMAP_NEAREST: 0x2700, LINEAR_MIPMAP_NEAREST: 0x2701, NEAREST_MIPMAP_LINEAR: 0x2702, LINEAR_MIPMAP_LINEAR: 0x2703, TEXTURE_MAG_FILTER: 0x2800, TEXTURE_MIN_FILTER: 0x2801, TEXTURE_WRAP_S: 0x2802, TEXTURE_WRAP_T: 0x2803, REPEAT: 0x2901, CLIP_PLANE0: 0x3000, CLIP_PLANE1: 0x3001, CLIP_PLANE2: 0x3002, CLIP_PLANE3: 0x3003, CLIP_PLANE4: 0x3004, CLIP_PLANE5: 0x3005, CLIP_PLANE6: 0x3006, CLIP_PLANE7: 0x3007, LIGHT0: 0x4000, LIGHT1: 0x4001, LIGHT2: 0x4002, LIGHT3: 0x4003, LIGHT4: 0x4004, LIGHT5: 0x4005, LIGHT6: 0x4006, LIGHT7: 0x4007, BGRA: 0x80E1, CLAMP_TO_EDGE: 0x812F, VERTEX_ARRAY: 0x8074, NORMAL_ARRAY: 0x8075, COLOR_ARRAY: 0x8076, INDEX_ARRAY: 0x8077, TEXTURE_COORD_ARRAY: 0x8078, GENERATE_MIPMAP_SGIS: 0x8191, GENERATE_MIPMAP_HINT_SGIS: 0x8192, TEXTURE_COMPRESSED: 0x86A1, CURRENT_BIT: 0x00001, POINT_BIT: 0x00002, LINE_BIT: 0x00004, POLYGON_BIT: 0x00008, POLYGON_STIPPLE_BIT: 0x00010, PIXEL_MODE_BIT: 0x00020, LIGHTING_BIT: 0x00040, FOG_BIT: 0x00080, DEPTH_BUFFER_BIT: 0x00100, ACCUM_BUFFER_BIT: 0x00200, STENCIL_BUFFER_BIT: 0x00400, VIEWPORT_BIT: 0x00800, TRANSFORM_BIT: 0x01000, ENABLE_BIT: 0x02000, COLOR_BUFFER_BIT: 0x04000, HINT_BIT: 0x08000, EVAL_BIT: 0x10000, LIST_BIT: 0x20000, TEXTURE_BIT: 0x40000, SCISSOR_BIT: 0x80000, ALL_ATTRIB_BITS: 0xFFFFF, ZERO: 0, ONE: 1, }; GL_Symbols = {}; for (var name in GL) { var value = GL[name]; GL_Symbols[value] = name; } } function registerOpenGL() { if (typeof Squeak === "object" && Squeak.registerExternalModule) { Squeak.registerExternalModule('libGL.so', OpenGL()); } else self.setTimeout(registerOpenGL, 100); }; registerOpenGL();