scratch0-5 / ffi /opengl.js
soiz1's picture
Upload folder using huggingface_hub
8f3f8db verified
// 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();