blob: 5e05eb514f812f2a3f91aace7d7339b118765155 [file] [log] [blame] [edit]
/*
* GL support. See https://github.com/kripken/emscripten/wiki/OpenGL-support
* for current status.
*/
var LibraryGL = {
$GL__postset: 'GL.init()',
$GL: {
#if GL_DEBUG
debug: true,
#endif
counter: 1, // 0 is reserved as 'null' in gl
buffers: [],
programs: [],
framebuffers: [],
renderbuffers: [],
textures: [],
uniforms: [],
shaders: [],
#if FULL_ES2
clientBuffers: [],
#endif
currArrayBuffer: 0,
currElementArrayBuffer: 0,
byteSizeByTypeRoot: 0x1400, // GL_BYTE
byteSizeByType: [
1, // GL_BYTE
1, // GL_UNSIGNED_BYTE
2, // GL_SHORT
2, // GL_UNSIGNED_SHORT
4, // GL_INT
4, // GL_UNSIGNED_INT
4, // GL_FLOAT
2, // GL_2_BYTES
3, // GL_3_BYTES
4, // GL_4_BYTES
8 // GL_DOUBLE
],
uniformTable: {}, // name => uniform ID. the uID must be identical until relinking, cannot create a new uID each call to glGetUniformLocation
packAlignment: 4, // default alignment is 4 bytes
unpackAlignment: 4, // default alignment is 4 bytes
init: function() {
Browser.moduleContextCreatedCallbacks.push(GL.initExtensions);
},
// Get a new ID for a texture/buffer/etc., while keeping the table dense and fast. Creation is farely rare so it is worth optimizing lookups later.
getNewId: function(table) {
var ret = GL.counter++;
for (var i = table.length; i < ret; i++) {
table[i] = null;
}
return ret;
},
// Mini temp buffer
MINI_TEMP_BUFFER_SIZE: 16,
miniTempBuffer: null,
miniTempBufferViews: [0], // index i has the view of size i+1
// Large temporary buffers
MAX_TEMP_BUFFER_SIZE: {{{ GL_MAX_TEMP_BUFFER_SIZE }}},
tempBufferIndexLookup: null,
tempVertexBuffers: null,
tempIndexBuffers: null,
tempQuadIndexBuffer: null,
generateTempBuffers: function(quads) {
GL.tempBufferIndexLookup = new Uint8Array(GL.MAX_TEMP_BUFFER_SIZE+1);
GL.tempVertexBuffers = [];
GL.tempIndexBuffers = [];
var last = -1, curr = -1;
var size = 1;
for (var i = 0; i <= GL.MAX_TEMP_BUFFER_SIZE; i++) {
if (i > size) {
size <<= 1;
}
if (size != last) {
curr++;
GL.tempVertexBuffers[curr] = Module.ctx.createBuffer();
Module.ctx.bindBuffer(Module.ctx.ARRAY_BUFFER, GL.tempVertexBuffers[curr]);
Module.ctx.bufferData(Module.ctx.ARRAY_BUFFER, size, Module.ctx.DYNAMIC_DRAW);
Module.ctx.bindBuffer(Module.ctx.ARRAY_BUFFER, null);
GL.tempIndexBuffers[curr] = Module.ctx.createBuffer();
Module.ctx.bindBuffer(Module.ctx.ELEMENT_ARRAY_BUFFER, GL.tempIndexBuffers[curr]);
Module.ctx.bufferData(Module.ctx.ELEMENT_ARRAY_BUFFER, size, Module.ctx.DYNAMIC_DRAW);
Module.ctx.bindBuffer(Module.ctx.ELEMENT_ARRAY_BUFFER, null);
last = size;
}
GL.tempBufferIndexLookup[i] = curr;
}
if (quads) {
// GL_QUAD indexes can be precalculated
GL.tempQuadIndexBuffer = Module.ctx.createBuffer();
Module.ctx.bindBuffer(Module.ctx.ELEMENT_ARRAY_BUFFER, GL.tempQuadIndexBuffer);
var numIndexes = GL.MAX_TEMP_BUFFER_SIZE >> 1;
var quadIndexes = new Uint16Array(numIndexes);
var i = 0, v = 0;
while (1) {
quadIndexes[i++] = v;
if (i >= numIndexes) break;
quadIndexes[i++] = v+1;
if (i >= numIndexes) break;
quadIndexes[i++] = v+2;
if (i >= numIndexes) break;
quadIndexes[i++] = v;
if (i >= numIndexes) break;
quadIndexes[i++] = v+2;
if (i >= numIndexes) break;
quadIndexes[i++] = v+3;
if (i >= numIndexes) break;
v += 4;
}
Module.ctx.bufferData(Module.ctx.ELEMENT_ARRAY_BUFFER, quadIndexes, Module.ctx.STATIC_DRAW);
Module.ctx.bindBuffer(Module.ctx.ELEMENT_ARRAY_BUFFER, null);
}
},
// Linear lookup in one of the tables (buffers, programs, etc.). TODO: consider using a weakmap to make this faster, if it matters
scan: function(table, object) {
for (var item in table) {
if (table[item] == object) return item;
}
return 0;
},
// Find a token in a shader source string
findToken: function(source, token) {
function isIdentChar(ch) {
if (ch >= 48 && ch <= 57) // 0-9
return true;
if (ch >= 65 && ch <= 90) // A-Z
return true;
if (ch >= 97 && ch <= 122) // a-z
return true;
return false;
}
var i = -1;
do {
i = source.indexOf(token, i + 1);
if (i < 0) {
break;
}
if (i > 0 && isIdentChar(source[i - 1])) {
continue;
}
i += token.length;
if (i < source.length - 1 && isIdentChar(source[i + 1])) {
continue;
}
return true;
} while (true);
return false;
},
getSource: function(shader, count, string, length) {
var source = '';
for (var i = 0; i < count; ++i) {
var frag;
if (length) {
var len = {{{ makeGetValue('length', 'i*4', 'i32') }}};
if (len < 0) {
frag = Pointer_stringify({{{ makeGetValue('string', 'i*4', 'i32') }}});
} else {
frag = Pointer_stringify({{{ makeGetValue('string', 'i*4', 'i32') }}}, len);
}
} else {
frag = Pointer_stringify({{{ makeGetValue('string', 'i*4', 'i32') }}});
}
source += frag;
}
// Let's see if we need to enable the standard derivatives extension
type = Module.ctx.getShaderParameter(GL.shaders[shader], 0x8B4F /* GL_SHADER_TYPE */);
if (type == 0x8B30 /* GL_FRAGMENT_SHADER */) {
if (GL.findToken(source, "dFdx") ||
GL.findToken(source, "dFdy") ||
GL.findToken(source, "fwidth")) {
source = "#extension GL_OES_standard_derivatives : enable\n" + source;
var extension = Module.ctx.getExtension("OES_standard_derivatives");
#if GL_DEBUG
if (!extension) {
Module.printErr("Shader attempts to use the standard derivatives extension which is not available.");
}
#endif
}
}
return source;
},
computeImageSize: function(width, height, sizePerPixel, alignment) {
function roundedToNextMultipleOf(x, y) {
return Math.floor((x + y - 1) / y) * y
}
var plainRowSize = width * sizePerPixel;
var alignedRowSize = roundedToNextMultipleOf(plainRowSize, alignment);
return (height <= 0) ? 0 :
((height - 1) * alignedRowSize + plainRowSize);
},
getTexPixelData: function(type, format, width, height, pixels, internalFormat) {
var sizePerPixel;
switch (type) {
case 0x1401 /* GL_UNSIGNED_BYTE */:
switch (format) {
case 0x1906 /* GL_ALPHA */:
case 0x1909 /* GL_LUMINANCE */:
sizePerPixel = 1;
break;
case 0x1907 /* GL_RGB */:
sizePerPixel = 3;
break;
case 0x1908 /* GL_RGBA */:
sizePerPixel = 4;
break;
case 0x190A /* GL_LUMINANCE_ALPHA */:
sizePerPixel = 2;
break;
default:
throw 'Invalid format (' + format + ')';
}
break;
case 0x8363 /* GL_UNSIGNED_SHORT_5_6_5 */:
case 0x8033 /* GL_UNSIGNED_SHORT_4_4_4_4 */:
case 0x8034 /* GL_UNSIGNED_SHORT_5_5_5_1 */:
sizePerPixel = 2;
break;
case 0x1406 /* GL_FLOAT */:
assert(GL.floatExt, 'Must have OES_texture_float to use float textures');
switch (format) {
case 0x1907 /* GL_RGB */:
sizePerPixel = 3*4;
break;
case 0x1908 /* GL_RGBA */:
sizePerPixel = 4*4;
break;
default:
throw 'Invalid format (' + format + ')';
}
internalFormat = Module.ctx.RGBA;
break;
default:
throw 'Invalid type (' + type + ')';
}
var bytes = GL.computeImageSize(width, height, sizePerPixel, GL.unpackAlignment);
if (type == 0x1401 /* GL_UNSIGNED_BYTE */) {
pixels = {{{ makeHEAPView('U8', 'pixels', 'pixels+bytes') }}};
} else if (type == 0x1406 /* GL_FLOAT */) {
pixels = {{{ makeHEAPView('F32', 'pixels', 'pixels+bytes') }}};
} else {
pixels = {{{ makeHEAPView('U16', 'pixels', 'pixels+bytes') }}};
}
return {
pixels: pixels,
internalFormat: internalFormat
}
},
#if FULL_ES2
calcBufLength: function(size, type, stride, count) {
if (stride > 0) {
return count * stride; // XXXvlad this is not exactly correct I don't think
}
var typeSize = GL.byteSizeByType[type - GL.byteSizeByTypeRoot];
return size * typeSize * count;
},
usedTempBuffers: [],
preDrawHandleClientVertexAttribBindings: function(count) {
GL.resetBufferBinding = false;
var used = GL.usedTempBuffers;
used.length = 0;
// TODO: initial pass to detect ranges we need to upload, might not need an upload per attrib
for (var i = 0; i < GL.maxVertexAttribs; ++i) {
var cb = GL.clientBuffers[i];
if (!cb.clientside || !cb.enabled) continue;
GL.resetBufferBinding = true;
var size = GL.calcBufLength(cb.size, cb.type, cb.stride, count);
var index = GL.tempBufferIndexLookup[size];
var buf;
do {
#if ASSERTIONS
assert(index < GL.tempVertexBuffers.length);
#endif
buf = GL.tempVertexBuffers[index++];
} while (used.indexOf(buf) >= 0);
used.push(buf);
Module.ctx.bindBuffer(Module.ctx.ARRAY_BUFFER, buf);
Module.ctx.bufferSubData(Module.ctx.ARRAY_BUFFER,
0,
HEAPU8.subarray(cb.ptr, cb.ptr + size));
Module.ctx.vertexAttribPointer(i, cb.size, cb.type, cb.normalized, cb.stride, 0);
}
},
postDrawHandleClientVertexAttribBindings: function() {
if (GL.resetBufferBinding) {
Module.ctx.bindBuffer(Module.ctx.ARRAY_BUFFER, GL.buffers[GL.currArrayBuffer]);
}
},
#endif
initExtensions: function() {
if (GL.initExtensions.done) return;
GL.initExtensions.done = true;
if (!Module.useWebGL) return; // an app might link both gl and 2d backends
GL.miniTempBuffer = new Float32Array(GL.MINI_TEMP_BUFFER_SIZE);
for (var i = 0; i < GL.MINI_TEMP_BUFFER_SIZE; i++) {
GL.miniTempBufferViews[i] = GL.miniTempBuffer.subarray(0, i+1);
}
GL.maxVertexAttribs = Module.ctx.getParameter(Module.ctx.MAX_VERTEX_ATTRIBS);
#if FULL_ES2
for (var i = 0; i < GL.maxVertexAttribs; i++) {
GL.clientBuffers[i] = { enabled: false, clientside: false, size: 0, type: 0, normalized: 0, stride: 0, ptr: 0 };
}
GL.generateTempBuffers();
#endif
GL.compressionExt = Module.ctx.getExtension('WEBGL_compressed_texture_s3tc') ||
Module.ctx.getExtension('MOZ_WEBGL_compressed_texture_s3tc') ||
Module.ctx.getExtension('WEBKIT_WEBGL_compressed_texture_s3tc');
GL.anisotropicExt = Module.ctx.getExtension('EXT_texture_filter_anisotropic') ||
Module.ctx.getExtension('MOZ_EXT_texture_filter_anisotropic') ||
Module.ctx.getExtension('WEBKIT_EXT_texture_filter_anisotropic');
GL.floatExt = Module.ctx.getExtension('OES_texture_float');
GL.elementIndexUintExt = Module.ctx.getExtension('OES_element_index_uint');
GL.standardDerivativesExt = Module.ctx.getExtension('OES_standard_derivatives');
}
},
glPixelStorei__sig: 'vii',
glPixelStorei: function(pname, param) {
if (pname == 0x0D05 /* GL_PACK_ALIGNMENT */) {
GL.packAlignment = param;
} else if (pname == 0x0cf5 /* GL_UNPACK_ALIGNMENT */) {
GL.unpackAlignment = param;
}
Module.ctx.pixelStorei(pname, param);
},
glGetString__sig: 'ii',
glGetString: function(name_) {
switch(name_) {
case 0x1F00 /* GL_VENDOR */:
case 0x1F01 /* GL_RENDERER */:
case 0x1F02 /* GL_VERSION */:
return allocate(intArrayFromString(Module.ctx.getParameter(name_)), 'i8', ALLOC_NORMAL);
case 0x1F03 /* GL_EXTENSIONS */:
return allocate(intArrayFromString(Module.ctx.getSupportedExtensions().join(' ')), 'i8', ALLOC_NORMAL);
case 0x8B8C /* GL_SHADING_LANGUAGE_VERSION */:
return allocate(intArrayFromString('OpenGL ES GLSL 1.00 (WebGL)'), 'i8', ALLOC_NORMAL);
default:
throw 'Failure: Invalid glGetString value: ' + name_;
}
},
glGetIntegerv__sig: 'vii',
glGetIntegerv: function(name_, p) {
switch(name_) { // Handle a few trivial GLES values
case 0x8DFA: // GL_SHADER_COMPILER
{{{ makeSetValue('p', '0', '1', 'i32') }}};
return;
case 0x8DF9: // GL_NUM_SHADER_BINARY_FORMATS
{{{ makeSetValue('p', '0', '0', 'i32') }}};
return;
}
var result = Module.ctx.getParameter(name_);
switch (typeof(result)) {
case "number":
{{{ makeSetValue('p', '0', 'result', 'i32') }}};
break;
case "boolean":
{{{ makeSetValue('p', '0', 'result ? 1 : 0', 'i8') }}};
break;
case "string":
throw 'Native code calling glGetIntegerv(' + name_ + ') on a name which returns a string!';
case "object":
if (result === null) {
{{{ makeSetValue('p', '0', '0', 'i32') }}};
} else if (result instanceof Float32Array ||
result instanceof Uint32Array ||
result instanceof Int32Array ||
result instanceof Array) {
for (var i = 0; i < result.length; ++i) {
{{{ makeSetValue('p', 'i*4', 'result[i]', 'i32') }}};
}
} else if (result instanceof WebGLBuffer) {
{{{ makeSetValue('p', '0', 'GL.scan(GL.buffers, result)', 'i32') }}};
} else if (result instanceof WebGLProgram) {
{{{ makeSetValue('p', '0', 'GL.scan(GL.programs, result)', 'i32') }}};
} else if (result instanceof WebGLFramebuffer) {
{{{ makeSetValue('p', '0', 'GL.scan(GL.framebuffers, result)', 'i32') }}};
} else if (result instanceof WebGLRenderbuffer) {
{{{ makeSetValue('p', '0', 'GL.scan(GL.renderbuffers, result)', 'i32') }}};
} else if (result instanceof WebGLTexture) {
{{{ makeSetValue('p', '0', 'GL.scan(GL.textures, result)', 'i32') }}};
} else {
throw 'Unknown object returned from WebGL getParameter';
}
break;
case "undefined":
throw 'Native code calling glGetIntegerv(' + name_ + ') and it returns undefined';
default:
throw 'Why did we hit the default case?';
}
},
glGetFloatv__sig: 'vii',
glGetFloatv: function(name_, p) {
var result = Module.ctx.getParameter(name_);
switch (typeof(result)) {
case "number":
{{{ makeSetValue('p', '0', 'result', 'float') }}};
break;
case "boolean":
{{{ makeSetValue('p', '0', 'result ? 1.0 : 0.0', 'float') }}};
break;
case "string":
{{{ makeSetValue('p', '0', '0', 'float') }}};
case "object":
if (result === null) {
throw 'Native code calling glGetFloatv(' + name_ + ') and it returns null';
} else if (result instanceof Float32Array ||
result instanceof Uint32Array ||
result instanceof Int32Array ||
result instanceof Array) {
for (var i = 0; i < result.length; ++i) {
{{{ makeSetValue('p', 'i*4', 'result[i]', 'float') }}};
}
} else if (result instanceof WebGLBuffer) {
{{{ makeSetValue('p', '0', 'GL.scan(GL.buffers, result)', 'float') }}};
} else if (result instanceof WebGLProgram) {
{{{ makeSetValue('p', '0', 'GL.scan(GL.programs, result)', 'float') }}};
} else if (result instanceof WebGLFramebuffer) {
{{{ makeSetValue('p', '0', 'GL.scan(GL.framebuffers, result)', 'float') }}};
} else if (result instanceof WebGLRenderbuffer) {
{{{ makeSetValue('p', '0', 'GL.scan(GL.renderbuffers, result)', 'float') }}};
} else if (result instanceof WebGLTexture) {
{{{ makeSetValue('p', '0', 'GL.scan(GL.textures, result)', 'float') }}};
} else {
throw 'Unknown object returned from WebGL getParameter';
}
break;
case "undefined":
throw 'Native code calling glGetFloatv(' + name_ + ') and it returns undefined';
default:
throw 'Why did we hit the default case?';
}
},
glGetBooleanv__sig: 'vii',
glGetBooleanv: function(name_, p) {
var result = Module.ctx.getParameter(name_);
switch (typeof(result)) {
case "number":
{{{ makeSetValue('p', '0', 'result != 0', 'i8') }}};
break;
case "boolean":
{{{ makeSetValue('p', '0', 'result != 0', 'i8') }}};
break;
case "string":
throw 'Native code calling glGetBooleanv(' + name_ + ') on a name which returns a string!';
case "object":
if (result === null) {
{{{ makeSetValue('p', '0', '0', 'i8') }}};
} else if (result instanceof Float32Array ||
result instanceof Uint32Array ||
result instanceof Int32Array ||
result instanceof Array) {
for (var i = 0; i < result.length; ++i) {
{{{ makeSetValue('p', 'i', 'result[i] != 0', 'i8') }}};
}
} else if (result instanceof WebGLBuffer ||
result instanceof WebGLProgram ||
result instanceof WebGLFramebuffer ||
result instanceof WebGLRenderbuffer ||
result instanceof WebGLTexture) {
{{{ makeSetValue('p', '0', '1', 'i8') }}}; // non-zero ID is always 1!
} else {
throw 'Unknown object returned from WebGL getParameter';
}
break;
case "undefined":
throw 'Unknown object returned from WebGL getParameter';
default:
throw 'Why did we hit the default case?';
}
},
glGenTextures__sig: 'vii',
glGenTextures: function(n, textures) {
for (var i = 0; i < n; i++) {
var id = GL.getNewId(GL.textures);
GL.textures[id] = Module.ctx.createTexture();
{{{ makeSetValue('textures', 'i*4', 'id', 'i32') }}};
}
},
glDeleteTextures__sig: 'vii',
glDeleteTextures: function(n, textures) {
for (var i = 0; i < n; i++) {
var id = {{{ makeGetValue('textures', 'i*4', 'i32') }}};
Module.ctx.deleteTexture(GL.textures[id]);
GL.textures[id] = null;
}
},
glCompressedTexImage2D__sig: 'viiiiiiii',
glCompressedTexImage2D: function(target, level, internalFormat, width, height, border, imageSize, data) {
assert(GL.compressionExt);
if (data) {
data = {{{ makeHEAPView('U8', 'data', 'data+imageSize') }}};
} else {
data = null;
}
Module.ctx['compressedTexImage2D'](target, level, internalFormat, width, height, border, data);
},
glCompressedTexSubImage2D__sig: 'viiiiiiiii',
glCompressedTexSubImage2D: function(target, level, xoffset, yoffset, width, height, format, imageSize, data) {
assert(GL.compressionExt);
if (data) {
data = {{{ makeHEAPView('U8', 'data', 'data+imageSize') }}};
} else {
data = null;
}
Module.ctx['compressedTexSubImage2D'](target, level, xoffset, yoffset, width, height, data);
},
glTexImage2D__sig: 'viiiiiiiii',
glTexImage2D: function(target, level, internalFormat, width, height, border, format, type, pixels) {
if (pixels) {
var data = GL.getTexPixelData(type, format, width, height, pixels, internalFormat);
pixels = data.pixels;
internalFormat = data.internalFormat;
} else {
pixels = null;
}
Module.ctx.texImage2D(target, level, internalFormat, width, height, border, format, type, pixels);
},
glTexSubImage2D__sig: 'viiiiiiiii',
glTexSubImage2D: function(target, level, xoffset, yoffset, width, height, format, type, pixels) {
if (pixels) {
var data = GL.getTexPixelData(type, format, width, height, pixels, -1);
pixels = data.pixels;
} else {
pixels = null;
}
Module.ctx.texSubImage2D(target, level, xoffset, yoffset, width, height, format, type, pixels);
},
glReadPixels__sig: 'viiiiiii',
glReadPixels: function(x, y, width, height, format, type, pixels) {
assert(type == 0x1401 /* GL_UNSIGNED_BYTE */);
var sizePerPixel;
switch (format) {
case 0x1907 /* GL_RGB */:
sizePerPixel = 3;
break;
case 0x1908 /* GL_RGBA */:
sizePerPixel = 4;
break;
default: throw 'unsupported glReadPixels format';
}
var totalSize = width*height*sizePerPixel;
Module.ctx.readPixels(x, y, width, height, format, type, HEAPU8.subarray(pixels, pixels + totalSize));
},
glBindTexture__sig: 'vii',
glBindTexture: function(target, texture) {
Module.ctx.bindTexture(target, texture ? GL.textures[texture] : null);
},
glGetTexParameterfv__sig: 'viii',
glGetTexParameterfv: function(target, pname, params) {
{{{ makeSetValue('params', '0', 'Module.getTexParameter(target, pname)', 'float') }}};
},
glGetTexParameteriv__sig: 'viii',
glGetTexParameteriv: function(target, pname, params) {
{{{ makeSetValue('params', '0', 'Module.getTexParameter(target, pname)', 'i32') }}};
},
glTexParameterfv__sig: 'viii',
glTexParameterfv: function(target, pname, params) {
var param = {{{ makeGetValue('params', '0', 'float') }}};
Module.ctx.texParameterf(target, pname, param);
},
glTexParameteriv__sig: 'viii',
glTexParameteriv: function(target, pname, params) {
var param = {{{ makeGetValue('params', '0', 'i32') }}};
Module.ctx.texParameteri(target, pname, param);
},
glIsTexture__sig: 'ii',
glIsTexture: function(texture) {
var texture = GL.textures[texture];
if (!texture) return 0;
return Module.ctx.isTexture(texture);
},
glGenBuffers__sig: 'vii',
glGenBuffers: function(n, buffers) {
for (var i = 0; i < n; i++) {
var id = GL.getNewId(GL.buffers);
GL.buffers[id] = Module.ctx.createBuffer();
{{{ makeSetValue('buffers', 'i*4', 'id', 'i32') }}};
}
},
glDeleteBuffers__sig: 'vii',
glDeleteBuffers: function(n, buffers) {
for (var i = 0; i < n; i++) {
var id = {{{ makeGetValue('buffers', 'i*4', 'i32') }}};
Module.ctx.deleteBuffer(GL.buffers[id]);
GL.buffers[id] = null;
if (id == GL.currArrayBuffer) GL.currArrayBuffer = 0;
if (id == GL.currElementArrayBuffer) GL.currElementArrayBuffer = 0;
}
},
glGetBufferParameteriv__sig: 'viii',
glGetBufferParameteriv: function(target, value, data) {
{{{ makeSetValue('data', '0', 'Module.ctx.getBufferParameter(target, value)', 'i32') }}};
},
glBufferData__sig: 'viiii',
glBufferData: function(target, size, data, usage) {
Module.ctx.bufferData(target, HEAPU8.subarray(data, data+size), usage);
},
glBufferSubData__sig: 'viiii',
glBufferSubData: function(target, offset, size, data) {
Module.ctx.bufferSubData(target, offset, HEAPU8.subarray(data, data+size));
},
glIsBuffer__sig: 'ii',
glIsBuffer: function(buffer) {
var b = GL.buffers[buffer];
if (!b) return 0;
return Module.ctx.isBuffer(b);
},
glGenRenderbuffers__sig: 'vii',
glGenRenderbuffers: function(n, renderbuffers) {
for (var i = 0; i < n; i++) {
var id = GL.getNewId(GL.renderbuffers);
GL.renderbuffers[id] = Module.ctx.createRenderbuffer();
{{{ makeSetValue('renderbuffers', 'i*4', 'id', 'i32') }}};
}
},
glDeleteRenderbuffers__sig: 'vii',
glDeleteRenderbuffers: function(n, renderbuffers) {
for (var i = 0; i < n; i++) {
var id = {{{ makeGetValue('renderbuffers', 'i*4', 'i32') }}};
Module.ctx.deleteRenderbuffer(GL.renderbuffers[id]);
GL.renderbuffers[id];
}
},
glBindRenderbuffer__sig: 'vii',
glBindRenderbuffer: function(target, renderbuffer) {
Module.ctx.bindRenderbuffer(target, renderbuffer ? GL.renderbuffers[renderbuffer] : null);
},
glGetRenderbufferParameteriv__sig: 'viii',
glGetRenderbufferParameteriv: function(target, pname, params) {
{{{ makeSetValue('params', '0', 'Module.ctx.getRenderbufferParameter(target, pname)', 'i32') }}};
},
glIsRenderbuffer__sig: 'ii',
glIsRenderbuffer: function(renderbuffer) {
var rb = GL.renderbuffers[renderbuffer];
if (!rb) return 0;
return Module.ctx.isRenderbuffer(rb);
},
glGetUniformfv__sig: 'viii',
glGetUniformfv: function(program, location, params) {
var data = Module.ctx.getUniform(GL.programs[program], GL.uniforms[location]);
if (typeof data == 'number') {
{{{ makeSetValue('params', '0', 'data', 'float') }}};
} else {
for (var i = 0; i < data.length; i++) {
{{{ makeSetValue('params', 'i', 'data[i]', 'float') }}};
}
}
},
glGetUniformiv__sig: 'viii',
glGetUniformiv: function(program, location, params) {
var data = Module.ctx.getUniform(GL.programs[program], GL.uniforms[location]);
if (typeof data == 'number' || typeof data == 'boolean') {
{{{ makeSetValue('params', '0', 'data', 'i32') }}};
} else {
for (var i = 0; i < data.length; i++) {
{{{ makeSetValue('params', 'i', 'data[i]', 'i32') }}};
}
}
},
glGetUniformLocation__sig: 'iii',
glGetUniformLocation: function(program, name) {
name = Pointer_stringify(name);
var ptable = GL.uniformTable[program];
if (!ptable) ptable = GL.uniformTable[program] = {};
var id = ptable[name];
if (id) return id;
var loc = Module.ctx.getUniformLocation(GL.programs[program], name);
if (!loc) return -1;
id = GL.getNewId(GL.uniforms);
GL.uniforms[id] = loc;
ptable[name] = id;
return id;
},
glGetVertexAttribfv__sig: 'viii',
glGetVertexAttribfv: function(index, pname, params) {
#if FULL_ES2
if (GL.clientBuffers[index].enabled) {
Module.printErr("glGetVertexAttribfv on client-side array: not supported, bad data returned");
}
#endif
var data = Module.ctx.getVertexAttrib(index, pname);
if (typeof data == 'number') {
{{{ makeSetValue('params', '0', 'data', 'float') }}};
} else {
for (var i = 0; i < data.length; i++) {
{{{ makeSetValue('params', 'i', 'data[i]', 'float') }}};
}
}
},
glGetVertexAttribiv__sig: 'viii',
glGetVertexAttribiv: function(index, pname, params) {
#if FULL_ES2
if (GL.clientBuffers[index].enabled) {
Module.printErr("glGetVertexAttribiv on client-side array: not supported, bad data returned");
}
#endif
var data = Module.ctx.getVertexAttrib(index, pname);
if (typeof data == 'number' || typeof data == 'boolean') {
{{{ makeSetValue('params', '0', 'data', 'i32') }}};
} else {
for (var i = 0; i < data.length; i++) {
{{{ makeSetValue('params', 'i', 'data[i]', 'i32') }}};
}
}
},
glGetVertexAttribPointerv__sig: 'viii',
glGetVertexAttribPointerv: function(index, pname, pointer) {
#if FULL_ES2
if (GL.clientBuffers[index].enabled) {
Module.printErr("glGetVertexAttribPointer on client-side array: not supported, bad data returned");
}
#endif
{{{ makeSetValue('pointer', '0', 'Module.ctx.getVertexAttribOffset(index, pname)', 'i32') }}};
},
glGetActiveUniform__sig: 'viiiiiii',
glGetActiveUniform: function(program, index, bufSize, length, size, type, name) {
program = GL.programs[program];
var info = Module.ctx.getActiveUniform(program, index);
var infoname = info.name.slice(0, Math.max(0, bufSize - 1));
writeStringToMemory(infoname, name);
if (length) {
{{{ makeSetValue('length', '0', 'infoname.length', 'i32') }}};
}
if (size) {
{{{ makeSetValue('size', '0', 'info.size', 'i32') }}};
}
if (type) {
{{{ makeSetValue('type', '0', 'info.type', 'i32') }}};
}
},
glUniform1f__sig: 'vif',
glUniform1f: function(location, v0) {
location = GL.uniforms[location];
Module.ctx.uniform1f(location, v0);
},
glUniform2f__sig: 'viff',
glUniform2f: function(location, v0, v1) {
location = GL.uniforms[location];
Module.ctx.uniform2f(location, v0, v1);
},
glUniform3f__sig: 'vifff',
glUniform3f: function(location, v0, v1, v2) {
location = GL.uniforms[location];
Module.ctx.uniform3f(location, v0, v1, v2);
},
glUniform4f__sig: 'viffff',
glUniform4f: function(location, v0, v1, v2, v3) {
location = GL.uniforms[location];
Module.ctx.uniform4f(location, v0, v1, v2, v3);
},
glUniform1i__sig: 'vii',
glUniform1i: function(location, v0) {
location = GL.uniforms[location];
Module.ctx.uniform1i(location, v0);
},
glUniform2i__sig: 'viii',
glUniform2i: function(location, v0, v1) {
location = GL.uniforms[location];
Module.ctx.uniform2i(location, v0, v1);
},
glUniform3i__sig: 'viiii',
glUniform3i: function(location, v0, v1, v2) {
location = GL.uniforms[location];
Module.ctx.uniform3i(location, v0, v1, v2);
},
glUniform4i__sig: 'viiiii',
glUniform4i: function(location, v0, v1, v2, v3) {
location = GL.uniforms[location];
Module.ctx.uniform4i(location, v0, v1, v2, v3);
},
glUniform1iv__sig: 'viii',
glUniform1iv: function(location, count, value) {
location = GL.uniforms[location];
value = {{{ makeHEAPView('32', 'value', 'value+count*4') }}};
Module.ctx.uniform1iv(location, value);
},
glUniform2iv__sig: 'viii',
glUniform2iv: function(location, count, value) {
location = GL.uniforms[location];
count *= 2;
value = {{{ makeHEAPView('32', 'value', 'value+count*4') }}};
Module.ctx.uniform2iv(location, value);
},
glUniform3iv__sig: 'viii',
glUniform3iv: function(location, count, value) {
location = GL.uniforms[location];
count *= 3;
value = {{{ makeHEAPView('32', 'value', 'value+count*4') }}};
Module.ctx.uniform3iv(location, value);
},
glUniform4iv__sig: 'viii',
glUniform4iv: function(location, count, value) {
location = GL.uniforms[location];
count *= 4;
value = {{{ makeHEAPView('32', 'value', 'value+count*4') }}};
Module.ctx.uniform4iv(location, value);
},
glUniform1fv__sig: 'viii',
glUniform1fv: function(location, count, value) {
location = GL.uniforms[location];
var view;
if (count == 1) {
// avoid allocation for the common case of uploading one uniform
view = GL.miniTempBufferViews[0];
view[0] = {{{ makeGetValue('value', '0', 'float') }}};
} else {
view = {{{ makeHEAPView('F32', 'value', 'value+count*4') }}};
}
Module.ctx.uniform1fv(location, view);
},
glUniform2fv__sig: 'viii',
glUniform2fv: function(location, count, value) {
location = GL.uniforms[location];
var view;
if (count == 1) {
// avoid allocation for the common case of uploading one uniform
view = GL.miniTempBufferViews[1];
view[0] = {{{ makeGetValue('value', '0', 'float') }}};
view[1] = {{{ makeGetValue('value', '4', 'float') }}};
} else {
view = {{{ makeHEAPView('F32', 'value', 'value+count*8') }}};
}
Module.ctx.uniform2fv(location, view);
},
glUniform3fv__sig: 'viii',
glUniform3fv: function(location, count, value) {
location = GL.uniforms[location];
var view;
if (count == 1) {
// avoid allocation for the common case of uploading one uniform
view = GL.miniTempBufferViews[2];
view[0] = {{{ makeGetValue('value', '0', 'float') }}};
view[1] = {{{ makeGetValue('value', '4', 'float') }}};
view[2] = {{{ makeGetValue('value', '8', 'float') }}};
} else {
view = {{{ makeHEAPView('F32', 'value', 'value+count*12') }}};
}
Module.ctx.uniform3fv(location, view);
},
glUniform4fv__sig: 'viii',
glUniform4fv: function(location, count, value) {
location = GL.uniforms[location];
var view;
if (count == 1) {
// avoid allocation for the common case of uploading one uniform
view = GL.miniTempBufferViews[3];
view[0] = {{{ makeGetValue('value', '0', 'float') }}};
view[1] = {{{ makeGetValue('value', '4', 'float') }}};
view[2] = {{{ makeGetValue('value', '8', 'float') }}};
view[3] = {{{ makeGetValue('value', '12', 'float') }}};
} else {
view = {{{ makeHEAPView('F32', 'value', 'value+count*16') }}};
}
Module.ctx.uniform4fv(location, view);
},
glUniformMatrix2fv__sig: 'viiii',
glUniformMatrix2fv: function(location, count, transpose, value) {
location = GL.uniforms[location];
var view;
if (count == 1) {
// avoid allocation for the common case of uploading one uniform matrix
view = GL.miniTempBufferViews[3];
for (var i = 0; i < 4; i++) {
view[i] = {{{ makeGetValue('value', 'i*4', 'float') }}};
}
} else {
view = {{{ makeHEAPView('F32', 'value', 'value+count*16') }}};
}
Module.ctx.uniformMatrix2fv(location, transpose, view);
},
glUniformMatrix3fv__sig: 'viiii',
glUniformMatrix3fv: function(location, count, transpose, value) {
location = GL.uniforms[location];
var view;
if (count == 1) {
// avoid allocation for the common case of uploading one uniform matrix
view = GL.miniTempBufferViews[8];
for (var i = 0; i < 9; i++) {
view[i] = {{{ makeGetValue('value', 'i*4', 'float') }}};
}
} else {
view = {{{ makeHEAPView('F32', 'value', 'value+count*36') }}};
}
Module.ctx.uniformMatrix3fv(location, transpose, view);
},
glUniformMatrix4fv__sig: 'viiii',
glUniformMatrix4fv: function(location, count, transpose, value) {
location = GL.uniforms[location];
var view;
if (count == 1) {
// avoid allocation for the common case of uploading one uniform matrix
view = GL.miniTempBufferViews[15];
for (var i = 0; i < 16; i++) {
view[i] = {{{ makeGetValue('value', 'i*4', 'float') }}};
}
} else {
view = {{{ makeHEAPView('F32', 'value', 'value+count*64') }}};
}
Module.ctx.uniformMatrix4fv(location, transpose, view);
},
glBindBuffer__sig: 'vii',
glBindBuffer: function(target, buffer) {
var bufferObj = buffer ? GL.buffers[buffer] : null;
if (target == Module.ctx.ARRAY_BUFFER) {
GL.currArrayBuffer = buffer;
} else if (target == Module.ctx.ELEMENT_ARRAY_BUFFER) {
GL.currElementArrayBuffer = buffer;
}
Module.ctx.bindBuffer(target, bufferObj);
},
glVertexAttrib1fv__sig: 'vii',
glVertexAttrib1fv: function(index, v) {
v = {{{ makeHEAPView('F32', 'v', 'v+' + (1*4)) }}};
Module.ctx.vertexAttrib1fv(index, v);
},
glVertexAttrib2fv__sig: 'vii',
glVertexAttrib2fv: function(index, v) {
v = {{{ makeHEAPView('F32', 'v', 'v+' + (2*4)) }}};
Module.ctx.vertexAttrib2fv(index, v);
},
glVertexAttrib3fv__sig: 'vii',
glVertexAttrib3fv: function(index, v) {
v = {{{ makeHEAPView('F32', 'v', 'v+' + (3*4)) }}};
Module.ctx.vertexAttrib3fv(index, v);
},
glVertexAttrib4fv__sig: 'vii',
glVertexAttrib4fv: function(index, v) {
v = {{{ makeHEAPView('F32', 'v', 'v+' + (4*4)) }}};
Module.ctx.vertexAttrib4fv(index, v);
},
glGetAttribLocation__sig: 'vii',
glGetAttribLocation: function(program, name) {
program = GL.programs[program];
name = Pointer_stringify(name);
return Module.ctx.getAttribLocation(program, name);
},
glGetActiveAttrib__sig: 'viiiiiii',
glGetActiveAttrib: function(program, index, bufSize, length, size, type, name) {
program = GL.programs[program];
var info = Module.ctx.getActiveAttrib(program, index);
var infoname = info.name.slice(0, Math.max(0, bufSize - 1));
writeStringToMemory(infoname, name);
if (length) {
{{{ makeSetValue('length', '0', 'infoname.length', 'i32') }}};
}
if (size) {
{{{ makeSetValue('size', '0', 'info.size', 'i32') }}};
}
if (type) {
{{{ makeSetValue('type', '0', 'info.type', 'i32') }}};
}
},
glCreateShader__sig: 'ii',
glCreateShader: function(shaderType) {
var id = GL.getNewId(GL.shaders);
GL.shaders[id] = Module.ctx.createShader(shaderType);
return id;
},
glDeleteShader__sig: 'vi',
glDeleteShader: function(shader) {
Module.ctx.deleteShader(GL.shaders[shader]);
GL.shaders[shader] = null;
},
glGetAttachedShaders__sig: 'viiii',
glGetAttachedShaders: function(program, maxCount, count, shaders) {
var result = Module.ctx.getAttachedShaders(GL.programs[program]);
var len = result.length;
if (len > maxCount) {
len = maxCount;
}
{{{ makeSetValue('count', '0', 'len', 'i32') }}};
for (var i = 0; i < len; ++i) {
{{{ makeSetValue('shaders', 'i*4', 'GL.shaders[result[i]]', 'i32') }}};
}
},
glShaderSource__sig: 'viiii',
glShaderSource: function(shader, count, string, length) {
var source = GL.getSource(shader, count, string, length);
Module.ctx.shaderSource(GL.shaders[shader], source);
},
glGetShaderSource__sig: 'viiii',
glGetShaderSource: function(shader, bufSize, length, source) {
var result = Module.ctx.getShaderSource(GL.shaders[shader]);
result = result.slice(0, Math.max(0, bufSize - 1));
writeStringToMemory(result, source);
if (length) {
{{{ makeSetValue('length', '0', 'result.length', 'i32') }}};
}
},
glCompileShader__sig: 'vi',
glCompileShader: function(shader) {
Module.ctx.compileShader(GL.shaders[shader]);
},
glGetShaderInfoLog__sig: 'viiii',
glGetShaderInfoLog: function(shader, maxLength, length, infoLog) {
var log = Module.ctx.getShaderInfoLog(GL.shaders[shader]);
// Work around a bug in Chromium which causes getShaderInfoLog to return null
if (!log) {
log = "";
}
log = log.substr(0, maxLength - 1);
writeStringToMemory(log, infoLog);
if (length) {
{{{ makeSetValue('length', '0', 'log.length', 'i32') }}}
}
},
glGetShaderiv__sig: 'viii',
glGetShaderiv : function(shader, pname, p) {
if (pname == 0x8B84) { // GL_INFO_LOG_LENGTH
{{{ makeSetValue('p', '0', 'Module.ctx.getShaderInfoLog(GL.shaders[shader]).length + 1', 'i32') }}};
} else {
{{{ makeSetValue('p', '0', 'Module.ctx.getShaderParameter(GL.shaders[shader], pname)', 'i32') }}};
}
},
glGetProgramiv__sig: 'viii',
glGetProgramiv : function(program, pname, p) {
if (pname == 0x8B84) { // GL_INFO_LOG_LENGTH
{{{ makeSetValue('p', '0', 'Module.ctx.getProgramInfoLog(GL.programs[program]).length + 1', 'i32') }}};
} else {
{{{ makeSetValue('p', '0', 'Module.ctx.getProgramParameter(GL.programs[program], pname)', 'i32') }}};
}
},
glIsShader__sig: 'ii',
glIsShader: function(shader) {
var s = GL.shaders[shader];
if (!s) return 0;
return Module.ctx.isShader(s);
},
glCreateProgram__sig: 'i',
glCreateProgram: function() {
var id = GL.getNewId(GL.programs);
GL.programs[id] = Module.ctx.createProgram();
return id;
},
glDeleteProgram__sig: 'vi',
glDeleteProgram: function(program) {
Module.ctx.deleteProgram(GL.programs[program]);
GL.programs[program] = null;
GL.uniformTable[program] = null;
},
glAttachShader__sig: 'vii',
glAttachShader: function(program, shader) {
Module.ctx.attachShader(GL.programs[program],
GL.shaders[shader]);
},
glDetachShader__sig: 'vii',
glDetachShader: function(program, shader) {
Module.ctx.detachShader(GL.programs[program],
GL.shaders[shader]);
},
glGetShaderPrecisionFormat: function(shaderType, precisionType, range, precision) {
var result = Module.ctx.getShaderPrecisionFormat(shaderType, precisionType);
{{{ makeSetValue('range', '0', 'result.rangeMin', 'i32') }}};
{{{ makeSetValue('range', '4', 'result.rangeMax', 'i32') }}};
{{{ makeSetValue('precision', '0', 'result.precision', 'i32') }}};
},
glLinkProgram__sig: 'vi',
glLinkProgram: function(program) {
Module.ctx.linkProgram(GL.programs[program]);
GL.uniformTable[program] = {}; // uniforms no longer keep the same names after linking
},
glGetProgramInfoLog__sig: 'viiii',
glGetProgramInfoLog: function(program, maxLength, length, infoLog) {
var log = Module.ctx.getProgramInfoLog(GL.programs[program]);
// Work around a bug in Chromium which causes getProgramInfoLog to return null
if (!log) {
log = "";
}
log = log.substr(0, maxLength - 1);
writeStringToMemory(log, infoLog);
if (length) {
{{{ makeSetValue('length', '0', 'log.length', 'i32') }}}
}
},
glUseProgram__sig: 'vi',
glUseProgram: function(program) {
Module.ctx.useProgram(program ? GL.programs[program] : null);
},
glValidateProgram__sig: 'vi',
glValidateProgram: function(program) {
Module.ctx.validateProgram(GL.programs[program]);
},
glIsProgram__sig: 'ii',
glIsProgram: function(program) {
var program = GL.programs[program];
if (!program) return 0;
return Module.ctx.isProgram(program);
},
glBindAttribLocation__sig: 'viii',
glBindAttribLocation: function(program, index, name) {
name = Pointer_stringify(name);
Module.ctx.bindAttribLocation(GL.programs[program], index, name);
},
glBindFramebuffer__sig: 'vii',
glBindFramebuffer: function(target, framebuffer) {
Module.ctx.bindFramebuffer(target, framebuffer ? GL.framebuffers[framebuffer] : null);
},
glGenFramebuffers__sig: 'vii',
glGenFramebuffers: function(n, ids) {
for (var i = 0; i < n; ++i) {
var id = GL.getNewId(GL.framebuffers);
GL.framebuffers[id] = Module.ctx.createFramebuffer();
{{{ makeSetValue('ids', 'i*4', 'id', 'i32') }}};
}
},
glDeleteFramebuffers__sig: 'vii',
glDeleteFramebuffers: function(n, framebuffers) {
for (var i = 0; i < n; ++i) {
var id = {{{ makeGetValue('framebuffers', 'i*4', 'i32') }}};
Module.ctx.deleteFramebuffer(GL.framebuffers[id]);
GL.framebuffers[id] = null;
}
},
glFramebufferRenderbuffer__sig: 'viiii',
glFramebufferRenderbuffer: function(target, attachment, renderbuffertarget, renderbuffer) {
Module.ctx.framebufferRenderbuffer(target, attachment, renderbuffertarget,
GL.renderbuffers[renderbuffer]);
},
glFramebufferTexture2D__sig: 'viiiii',
glFramebufferTexture2D: function(target, attachment, textarget, texture, level) {
Module.ctx.framebufferTexture2D(target, attachment, textarget,
GL.textures[texture], level);
},
glGetFramebufferAttachmentParameteriv__sig: 'viiii',
glGetFramebufferAttachmentParameteriv: function(target, attachment, pname, params) {
var result = Module.ctx.getFramebufferAttachmentParameter(target, attachment, pname);
{{{ makeSetValue('params', '0', 'params', 'i32') }}};
},
glIsFramebuffer__sig: 'ii',
glIsFramebuffer: function(framebuffer) {
var fb = GL.framebuffers[framebuffer];
if (!fb) return 0;
return Module.ctx.isFramebuffer(fb);
},
#if DISABLE_GL_EMULATION == 0
// GL emulation: provides misc. functionality not present in OpenGL ES 2.0 or WebGL
$GLEmulation__postset: 'GLEmulation.init();',
$GLEmulation: {
// Fog support. Partial, we assume shaders are used that implement fog. We just pass them uniforms
fogStart: 0,
fogEnd: 1,
fogDensity: 1.0,
fogColor: null,
fogMode: 0x0800, // GL_EXP
fogEnabled: false,
// VAO support
vaos: [],
currentVao: null,
enabledVertexAttribArrays: {}, // helps with vao cleanups
hasRunInit: false,
init: function() {
// Do not activate immediate/emulation code (e.g. replace glDrawElements) when in FULL_ES2 mode.
// We do not need full emulation, we instead emulate client-side arrays etc. in FULL_ES2 code in
// a straightforward manner, and avoid not having a bound buffer be ambiguous between es2 emulation
// code and legacy gl emulation code.
#if FULL_ES2
return;
#endif
if (GLEmulation.hasRunInit) {
return;
}
GLEmulation.hasRunInit = true;
GLEmulation.fogColor = new Float32Array(4);
// Add some emulation workarounds
Module.printErr('WARNING: using emscripten GL emulation. This is a collection of limited workarounds, do not expect it to work');
#if GL_UNSAFE_OPTS == 0
Module.printErr('WARNING: using emscripten GL emulation unsafe opts. If weirdness happens, try -s GL_UNSAFE_OPTS=0');
#endif
// XXX some of the capabilities we don't support may lead to incorrect rendering, if we do not emulate them in shaders
var validCapabilities = {
0x0B44: 1, // GL_CULL_FACE
0x0BE2: 1, // GL_BLEND
0x0BD0: 1, // GL_DITHER,
0x0B90: 1, // GL_STENCIL_TEST
0x0B71: 1, // GL_DEPTH_TEST
0x0C11: 1, // GL_SCISSOR_TEST
0x8037: 1, // GL_POLYGON_OFFSET_FILL
0x809E: 1, // GL_SAMPLE_ALPHA_TO_COVERAGE
0x80A0: 1 // GL_SAMPLE_COVERAGE
};
var glEnable = _glEnable;
_glEnable = function(cap) {
// Clean up the renderer on any change to the rendering state. The optimization of
// skipping renderer setup is aimed at the case of multiple glDraw* right after each other
if (GL.immediate.lastRenderer) GL.immediate.lastRenderer.cleanup();
if (cap == 0x0B60 /* GL_FOG */) {
GLEmulation.fogEnabled = true;
return;
} else if (cap == 0x0de1 /* GL_TEXTURE_2D */) {
// XXX not according to spec, and not in desktop GL, but works in some GLES1.x apparently, so support
// it by forwarding to glEnableClientState
/* Actually, let's not, for now. (This sounds exceedingly broken)
* This is in gl_ps_workaround2.c.
_glEnableClientState(cap);
*/
return;
} else if (!(cap in validCapabilities)) {
return;
}
glEnable(cap);
};
var glDisable = _glDisable;
_glDisable = function(cap) {
if (GL.immediate.lastRenderer) GL.immediate.lastRenderer.cleanup();
if (cap == 0x0B60 /* GL_FOG */) {
GLEmulation.fogEnabled = false;
return;
} else if (cap == 0x0de1 /* GL_TEXTURE_2D */) {
// XXX not according to spec, and not in desktop GL, but works in some GLES1.x apparently, so support
// it by forwarding to glDisableClientState
/* Actually, let's not, for now. (This sounds exceedingly broken)
* This is in gl_ps_workaround2.c.
_glDisableClientState(cap);
*/
return;
} else if (!(cap in validCapabilities)) {
return;
}
glDisable(cap);
};
_glIsEnabled = function(cap) {
if (cap == 0x0B60 /* GL_FOG */) {
return GLEmulation.fogEnabled ? 1 : 0;
} else if (!(cap in validCapabilities)) {
return 0;
}
return Module.ctx.isEnabled(cap);
};
var glGetBooleanv = _glGetBooleanv;
_glGetBooleanv = function(pname, p) {
var attrib = GLEmulation.getAttributeFromCapability(pname);
if (attrib !== null) {
var result = GL.immediate.enabledClientAttributes[attrib];
{{{ makeSetValue('p', '0', 'result === true ? 1 : 0', 'i8') }}};
return;
}
glGetBooleanv(pname, p);
};
var glGetIntegerv = _glGetIntegerv;
_glGetIntegerv = function(pname, params) {
switch (pname) {
case 0x84E2: pname = Module.ctx.MAX_TEXTURE_IMAGE_UNITS /* fake it */; break; // GL_MAX_TEXTURE_UNITS
case 0x8B4A: { // GL_MAX_VERTEX_UNIFORM_COMPONENTS_ARB
var result = Module.ctx.getParameter(Module.ctx.MAX_VERTEX_UNIFORM_VECTORS);
{{{ makeSetValue('params', '0', 'result*4', 'i32') }}}; // GLES gives num of 4-element vectors, GL wants individual components, so multiply
return;
}
case 0x8B49: { // GL_MAX_FRAGMENT_UNIFORM_COMPONENTS_ARB
var result = Module.ctx.getParameter(Module.ctx.MAX_FRAGMENT_UNIFORM_VECTORS);
{{{ makeSetValue('params', '0', 'result*4', 'i32') }}}; // GLES gives num of 4-element vectors, GL wants individual components, so multiply
return;
}
case 0x8B4B: { // GL_MAX_VARYING_FLOATS_ARB
var result = Module.ctx.getParameter(Module.ctx.MAX_VARYING_VECTORS);
{{{ makeSetValue('params', '0', 'result*4', 'i32') }}}; // GLES gives num of 4-element vectors, GL wants individual components, so multiply
return;
}
case 0x8871: pname = Module.ctx.MAX_COMBINED_TEXTURE_IMAGE_UNITS /* close enough */; break; // GL_MAX_TEXTURE_COORDS
case 0x807A: { // GL_VERTEX_ARRAY_SIZE
var attribute = GLImmediate.clientAttributes[GLImmediate.VERTEX];
{{{ makeSetValue('params', '0', 'attribute ? attribute.size : 0', 'i32') }}};
return;
}
case 0x807B: { // GL_VERTEX_ARRAY_TYPE
var attribute = GLImmediate.clientAttributes[GLImmediate.VERTEX];
{{{ makeSetValue('params', '0', 'attribute ? attribute.type : 0', 'i32') }}};
return;
}
case 0x807C: { // GL_VERTEX_ARRAY_STRIDE
var attribute = GLImmediate.clientAttributes[GLImmediate.VERTEX];
{{{ makeSetValue('params', '0', 'attribute ? attribute.stride : 0', 'i32') }}};
return;
}
case 0x8081: { // GL_COLOR_ARRAY_SIZE
var attribute = GLImmediate.clientAttributes[GLImmediate.COLOR];
{{{ makeSetValue('params', '0', 'attribute ? attribute.size : 0', 'i32') }}};
return;
}
case 0x8082: { // GL_COLOR_ARRAY_TYPE
var attribute = GLImmediate.clientAttributes[GLImmediate.COLOR];
{{{ makeSetValue('params', '0', 'attribute ? attribute.type : 0', 'i32') }}};
return;
}
case 0x8083: { // GL_COLOR_ARRAY_STRIDE
var attribute = GLImmediate.clientAttributes[GLImmediate.COLOR];
{{{ makeSetValue('params', '0', 'attribute ? attribute.stride : 0', 'i32') }}};
return;
}
case 0x8088: { // GL_TEXTURE_COORD_ARRAY_SIZE
var attribute = GLImmediate.clientAttributes[GLImmediate.TEXTURE0];
{{{ makeSetValue('params', '0', 'attribute ? attribute.size : 0', 'i32') }}};
return;
}
case 0x8089: { // GL_TEXTURE_COORD_ARRAY_TYPE
var attribute = GLImmediate.clientAttributes[GLImmediate.TEXTURE0];
{{{ makeSetValue('params', '0', 'attribute ? attribute.type : 0', 'i32') }}};
return;
}
case 0x808A: { // GL_TEXTURE_COORD_ARRAY_STRIDE
var attribute = GLImmediate.clientAttributes[GLImmediate.TEXTURE0];
{{{ makeSetValue('params', '0', 'attribute ? attribute.stride : 0', 'i32') }}};
return;
}
}
glGetIntegerv(pname, params);
};
var glGetString = _glGetString;
_glGetString = function(name_) {
switch(name_) {
case 0x1F03 /* GL_EXTENSIONS */: // Add various extensions that we can support
return allocate(intArrayFromString(Module.ctx.getSupportedExtensions().join(' ') +
' GL_EXT_texture_env_combine GL_ARB_texture_env_crossbar GL_ATI_texture_env_combine3 GL_NV_texture_env_combine4 GL_EXT_texture_env_dot3 GL_ARB_multitexture GL_ARB_vertex_buffer_object GL_EXT_framebuffer_object GL_ARB_vertex_program GL_ARB_fragment_program GL_ARB_shading_language_100 GL_ARB_shader_objects GL_ARB_vertex_shader GL_ARB_fragment_shader GL_ARB_texture_cube_map GL_EXT_draw_range_elements' +
(GL.compressionExt ? ' GL_ARB_texture_compression GL_EXT_texture_compression_s3tc' : '') +
(GL.anisotropicExt ? ' GL_EXT_texture_filter_anisotropic' : '')
), 'i8', ALLOC_NORMAL);
}
return glGetString(name_);
};
// Do some automatic rewriting to work around GLSL differences. Note that this must be done in
// tandem with the rest of the program, by itself it cannot suffice.
// Note that we need to remember shader types for this rewriting, saving sources makes it easier to debug.
GL.shaderInfos = {};
#if GL_DEBUG
GL.shaderSources = {};
GL.shaderOriginalSources = {};
#endif
var glCreateShader = _glCreateShader;
_glCreateShader = function(shaderType) {
var id = glCreateShader(shaderType);
GL.shaderInfos[id] = {
type: shaderType,
ftransform: false
};
return id;
};
var glShaderSource = _glShaderSource;
_glShaderSource = function(shader, count, string, length) {
var source = GL.getSource(shader, count, string, length);
#if GL_DEBUG
console.log("glShaderSource: Input: \n" + source);
GL.shaderOriginalSources[shader] = source;
#endif
// XXX We add attributes and uniforms to shaders. The program can ask for the # of them, and see the
// ones we generated, potentially confusing it? Perhaps we should hide them.
if (GL.shaderInfos[shader].type == Module.ctx.VERTEX_SHADER) {
// Replace ftransform() with explicit project/modelview transforms, and add position and matrix info.
var has_pm = source.search(/u_projection/) >= 0;
var has_mm = source.search(/u_modelView/) >= 0;
var has_pv = source.search(/a_position/) >= 0;
var need_pm = 0, need_mm = 0, need_pv = 0;
var old = source;
source = source.replace(/ftransform\(\)/g, '(u_projection * u_modelView * a_position)');
if (old != source) need_pm = need_mm = need_pv = 1;
old = source;
source = source.replace(/gl_ProjectionMatrix/g, 'u_projection');
if (old != source) need_pm = 1;
old = source;
source = source.replace(/gl_ModelViewMatrixTranspose\[2\]/g, 'vec4(u_modelView[0][2], u_modelView[1][2], u_modelView[2][2], u_modelView[3][2])'); // XXX extremely inefficient
if (old != source) need_mm = 1;
old = source;
source = source.replace(/gl_ModelViewMatrix/g, 'u_modelView');
if (old != source) need_mm = 1;
old = source;
source = source.replace(/gl_Vertex/g, 'a_position');
if (old != source) need_pv = 1;
old = source;
source = source.replace(/gl_ModelViewProjectionMatrix/g, '(u_projection * u_modelView)');
if (old != source) need_pm = need_mm = 1;
if (need_pv && !has_pv) source = 'attribute vec4 a_position; \n' + source;
if (need_mm && !has_mm) source = 'uniform mat4 u_modelView; \n' + source;
if (need_pm && !has_pm) source = 'uniform mat4 u_projection; \n' + source;
GL.shaderInfos[shader].ftransform = need_pm || need_mm || need_pv; // we will need to provide the fixed function stuff as attributes and uniforms
for (var i = 0; i < GL.immediate.MAX_TEXTURES; i++) {
// XXX To handle both regular texture mapping and cube mapping, we use vec4 for tex coordinates.
var old = source;
var need_vtc = source.search('v_texCoord' + i) == -1;
source = source.replace(new RegExp('gl_TexCoord\\[' + i + '\\]', 'g'), 'v_texCoord' + i)
.replace(new RegExp('gl_MultiTexCoord' + i, 'g'), 'a_texCoord' + i);
if (source != old) {
source = 'attribute vec4 a_texCoord' + i + '; \n' + source;
if (need_vtc) {
source = 'varying vec4 v_texCoord' + i + '; \n' + source;
}
}
old = source;
source = source.replace(new RegExp('gl_TextureMatrix\\[' + i + '\\]', 'g'), 'u_textureMatrix' + i);
if (source != old) {
source = 'uniform mat4 u_textureMatrix' + i + '; \n' + source;
}
}
if (source.indexOf('gl_FrontColor') >= 0) {
source = 'varying vec4 v_color; \n' +
source.replace(/gl_FrontColor/g, 'v_color');
}
if (source.indexOf('gl_Color') >= 0) {
source = 'attribute vec4 a_color; \n' +
source.replace(/gl_Color/g, 'a_color');
}
if (source.indexOf('gl_Normal') >= 0) {
source = 'attribute vec3 a_normal; \n' +
source.replace(/gl_Normal/g, 'a_normal');
}
// fog
if (source.indexOf('gl_FogFragCoord') >= 0) {
source = 'varying float v_fogFragCoord; \n' +
source.replace(/gl_FogFragCoord/g, 'v_fogFragCoord');
}
} else { // Fragment shader
for (var i = 0; i < GL.immediate.MAX_TEXTURES; i++) {
var old = source;
source = source.replace(new RegExp('gl_TexCoord\\[' + i + '\\]', 'g'), 'v_texCoord' + i);
if (source != old) {
source = 'varying vec4 v_texCoord' + i + '; \n' + source;
}
}
if (source.indexOf('gl_Color') >= 0) {
source = 'varying vec4 v_color; \n' + source.replace(/gl_Color/g, 'v_color');
}
if (source.indexOf('gl_Fog.color') >= 0) {
source = 'uniform vec4 u_fogColor; \n' +
source.replace(/gl_Fog.color/g, 'u_fogColor');
}
if (source.indexOf('gl_Fog.end') >= 0) {
source = 'uniform float u_fogEnd; \n' +
source.replace(/gl_Fog.end/g, 'u_fogEnd');
}
if (source.indexOf('gl_Fog.scale') >= 0) {
source = 'uniform float u_fogScale; \n' +
source.replace(/gl_Fog.scale/g, 'u_fogScale');
}
if (source.indexOf('gl_Fog.density') >= 0) {
source = 'uniform float u_fogDensity; \n' +
source.replace(/gl_Fog.density/g, 'u_fogDensity');
}
if (source.indexOf('gl_FogFragCoord') >= 0) {
source = 'varying float v_fogFragCoord; \n' +
source.replace(/gl_FogFragCoord/g, 'v_fogFragCoord');
}
source = 'precision mediump float;\n' + source;
}
#if GL_DEBUG
GL.shaderSources[shader] = source;
console.log("glShaderSource: Output: \n" + source);
#endif
Module.ctx.shaderSource(GL.shaders[shader], source);
};
var glCompileShader = _glCompileShader;
_glCompileShader = function(shader) {
Module.ctx.compileShader(GL.shaders[shader]);
if (!Module.ctx.getShaderParameter(GL.shaders[shader], Module.ctx.COMPILE_STATUS)) {
Module.printErr('Failed to compile shader: ' + Module.ctx.getShaderInfoLog(GL.shaders[shader]));
Module.printErr('Info: ' + JSON.stringify(GL.shaderInfos[shader]));
#if GL_DEBUG
Module.printErr('Original source: ' + GL.shaderOriginalSources[shader]);
Module.printErr('Source: ' + GL.shaderSources[shader]);
throw 'Shader compilation halt';
#else
Module.printErr('Enable GL_DEBUG to see shader source');
#endif
}
};
GL.programShaders = {};
var glAttachShader = _glAttachShader;
_glAttachShader = function(program, shader) {
if (!GL.programShaders[program]) GL.programShaders[program] = [];
GL.programShaders[program].push(shader);
glAttachShader(program, shader);
};
var glDetachShader = _glDetachShader;
_glDetachShader = function(program, shader) {
var programShader = GL.programShaders[program];
if (!programShader) {
Module.printErr('WARNING: _glDetachShader received invalid program: ' + program);
return;
}
var index = programShader.indexOf(shader);
programShader.splice(index, 1);
glDetachShader(program, shader);
};
var glUseProgram = _glUseProgram;
_glUseProgram = function(program) {
#if GL_DEBUG
if (GL.debug) {
Module.printErr('[using program with shaders]');
if (program) {
GL.programShaders[program].forEach(function(shader) {
Module.printErr(' shader ' + shader + ', original source: ' + GL.shaderOriginalSources[shader]);
Module.printErr(' Source: ' + GL.shaderSources[shader]);
});
}
}
#endif
GL.currProgram = program;
glUseProgram(program);
}
var glDeleteProgram = _glDeleteProgram;
_glDeleteProgram = function(program) {
glDeleteProgram(program);
if (program == GL.currProgram) GL.currProgram = 0;
};
// If attribute 0 was not bound, bind it to 0 for WebGL performance reasons. Track if 0 is free for that.
var zeroUsedPrograms = {};
var glBindAttribLocation = _glBindAttribLocation;
_glBindAttribLocation = function(program, index, name) {
if (index == 0) zeroUsedPrograms[program] = true;
glBindAttribLocation(program, index, name);
};
var glLinkProgram = _glLinkProgram;
_glLinkProgram = function(program) {
if (!(program in zeroUsedPrograms)) {
Module.ctx.bindAttribLocation(GL.programs[program], 0, 'a_position');
}
glLinkProgram(program);
};
var glBindBuffer = _glBindBuffer;
_glBindBuffer = function(target, buffer) {
glBindBuffer(target, buffer);
if (target == Module.ctx.ARRAY_BUFFER) {
if (GLEmulation.currentVao) {
assert(GLEmulation.currentVao.arrayBuffer == buffer || GLEmulation.currentVao.arrayBuffer == 0 || buffer == 0, 'TODO: support for multiple array buffers in vao');
GLEmulation.currentVao.arrayBuffer = buffer;
}
} else if (target == Module.ctx.ELEMENT_ARRAY_BUFFER) {
if (GLEmulation.currentVao) GLEmulation.currentVao.elementArrayBuffer = buffer;
}
};
var glGetFloatv = _glGetFloatv;
_glGetFloatv = function(pname, params) {
if (pname == 0x0BA6) { // GL_MODELVIEW_MATRIX
HEAPF32.set(GL.immediate.matrix['m'], params >> 2);
} else if (pname == 0x0BA7) { // GL_PROJECTION_MATRIX
HEAPF32.set(GL.immediate.matrix['p'], params >> 2);
} else if (pname == 0x0BA8) { // GL_TEXTURE_MATRIX
HEAPF32.set(GL.immediate.matrix['t' + GL.immediate.clientActiveTexture], params >> 2);
} else if (pname == 0x0B66) { // GL_FOG_COLOR
HEAPF32.set(GLEmulation.fogColor, params >> 2);
} else if (pname == 0x0B63) { // GL_FOG_START
{{{ makeSetValue('params', '0', 'GLEmulation.fogStart', 'float') }}};
} else if (pname == 0x0B64) { // GL_FOG_END
{{{ makeSetValue('params', '0', 'GLEmulation.fogEnd', 'float') }}};
} else if (pname == 0x0B62) { // GL_FOG_DENSITY
{{{ makeSetValue('params', '0', 'GLEmulation.fogDensity', 'float') }}};
} else if (pname == 0x0B65) { // GL_FOG_MODE
{{{ makeSetValue('params', '0', 'GLEmulation.fogMode', 'float') }}};
} else {
glGetFloatv(pname, params);
}
};
var glHint = _glHint;
_glHint = function(target, mode) {
if (target == 0x84EF) { // GL_TEXTURE_COMPRESSION_HINT
return;
}
glHint(target, mode);
};
var glEnableVertexAttribArray = _glEnableVertexAttribArray;
_glEnableVertexAttribArray = function(index) {
glEnableVertexAttribArray(index);
GLEmulation.enabledVertexAttribArrays[index] = 1;
if (GLEmulation.currentVao) GLEmulation.currentVao.enabledVertexAttribArrays[index] = 1;
};
var glDisableVertexAttribArray = _glDisableVertexAttribArray;
_glDisableVertexAttribArray = function(index) {
glDisableVertexAttribArray(index);
delete GLEmulation.enabledVertexAttribArrays[index];
if (GLEmulation.currentVao) delete GLEmulation.currentVao.enabledVertexAttribArrays[index];
};
var glVertexAttribPointer = _glVertexAttribPointer;
_glVertexAttribPointer = function(index, size, type, normalized, stride, pointer) {
glVertexAttribPointer(index, size, type, normalized, stride, pointer);
if (GLEmulation.currentVao) { // TODO: avoid object creation here? likely not hot though
GLEmulation.currentVao.vertexAttribPointers[index] = [index, size, type, normalized, stride, pointer];
}
};
},
getAttributeFromCapability: function(cap) {
var attrib = null;
switch (cap) {
case 0x0de1: // GL_TEXTURE_2D - XXX not according to spec, and not in desktop GL, but works in some GLES1.x apparently, so support it
#if ASSERTIONS
abort("GL_TEXTURE_2D is not a spec-defined capability for gl{Enable,Disable}ClientState.");
#endif
// Fall through:
case 0x8078: // GL_TEXTURE_COORD_ARRAY
attrib = GL.immediate.TEXTURE0 + GL.immediate.clientActiveTexture; break;
case 0x8074: // GL_VERTEX_ARRAY
attrib = GL.immediate.VERTEX; break;
case 0x8075: // GL_NORMAL_ARRAY
attrib = GL.immediate.NORMAL; break;
case 0x8076: // GL_COLOR_ARRAY
attrib = GL.immediate.COLOR; break;
}
return attrib;
},
getProcAddress: function(name) {
name = name.replace('EXT', '').replace('ARB', '');
// Do the translation carefully because of closure
var ret = 0;
switch (name) {
case 'glCreateShaderObject': case 'glCreateShader': ret = {{{ Functions.getIndex('_glCreateShader', true) }}}; break;
case 'glCreateProgramObject': case 'glCreateProgram': ret = {{{ Functions.getIndex('_glCreateProgram', true) }}}; break;
case 'glAttachObject': case 'glAttachShader': ret = {{{ Functions.getIndex('_glAttachShader', true) }}}; break;
case 'glUseProgramObject': case 'glUseProgram': ret = {{{ Functions.getIndex('_glUseProgram', true) }}}; break;
case 'glDetachObject': case 'glDetachShader': ret = {{{ Functions.getIndex('_glDetachShader', true) }}}; break;
case 'glDeleteObject': ret = {{{ Functions.getIndex('_glDeleteObject', true) }}}; break;
case 'glGetObjectParameteriv': ret = {{{ Functions.getIndex('_glGetObjectParameteriv', true) }}}; break;
case 'glGetInfoLog': ret = {{{ Functions.getIndex('_glGetInfoLog', true) }}}; break;
case 'glBindProgram': ret = {{{ Functions.getIndex('_glBindProgram', true) }}}; break;
case 'glDrawRangeElements': ret = {{{ Functions.getIndex('_glDrawRangeElements', true) }}}; break;
case 'glShaderSource': ret = {{{ Functions.getIndex('_glShaderSource', true) }}}; break;
case 'glCompileShader': ret = {{{ Functions.getIndex('_glCompileShader', true) }}}; break;
case 'glLinkProgram': ret = {{{ Functions.getIndex('_glLinkProgram', true) }}}; break;
case 'glGetUniformLocation': ret = {{{ Functions.getIndex('_glGetUniformLocation', true) }}}; break;
case 'glUniform1f': ret = {{{ Functions.getIndex('_glUniform1f', true) }}}; break;
case 'glUniform2f': ret = {{{ Functions.getIndex('_glUniform2f', true) }}}; break;
case 'glUniform3f': ret = {{{ Functions.getIndex('_glUniform3f', true) }}}; break;
case 'glUniform4f': ret = {{{ Functions.getIndex('_glUniform4f', true) }}}; break;
case 'glUniform1fv': ret = {{{ Functions.getIndex('_glUniform1fv', true) }}}; break;
case 'glUniform2fv': ret = {{{ Functions.getIndex('_glUniform2fv', true) }}}; break;
case 'glUniform3fv': ret = {{{ Functions.getIndex('_glUniform3fv', true) }}}; break;
case 'glUniform4fv': ret = {{{ Functions.getIndex('_glUniform4fv', true) }}}; break;
case 'glUniform1i': ret = {{{ Functions.getIndex('_glUniform1i', true) }}}; break;
case 'glUniform2i': ret = {{{ Functions.getIndex('_glUniform2i', true) }}}; break;
case 'glUniform3i': ret = {{{ Functions.getIndex('_glUniform3i', true) }}}; break;
case 'glUniform4i': ret = {{{ Functions.getIndex('_glUniform4i', true) }}}; break;
case 'glUniform1iv': ret = {{{ Functions.getIndex('_glUniform1iv', true) }}}; break;
case 'glUniform2iv': ret = {{{ Functions.getIndex('_glUniform2iv', true) }}}; break;
case 'glUniform3iv': ret = {{{ Functions.getIndex('_glUniform3iv', true) }}}; break;
case 'glUniform4iv': ret = {{{ Functions.getIndex('_glUniform4iv', true) }}}; break;
case 'glBindAttribLocation': ret = {{{ Functions.getIndex('_glBindAttribLocation', true) }}}; break;
case 'glGetActiveUniform': ret = {{{ Functions.getIndex('_glGetActiveUniform', true) }}}; break;
case 'glGenBuffers': ret = {{{ Functions.getIndex('_glGenBuffers', true) }}}; break;
case 'glBindBuffer': ret = {{{ Functions.getIndex('_glBindBuffer', true) }}}; break;
case 'glBufferData': ret = {{{ Functions.getIndex('_glBufferData', true) }}}; break;
case 'glBufferSubData': ret = {{{ Functions.getIndex('_glBufferSubData', true) }}}; break;
case 'glDeleteBuffers': ret = {{{ Functions.getIndex('_glDeleteBuffers', true) }}}; break;
case 'glActiveTexture': ret = {{{ Functions.getIndex('_glActiveTexture', true) }}}; break;
case 'glClientActiveTexture': ret = {{{ Functions.getIndex('_glClientActiveTexture', true) }}}; break;
case 'glGetProgramiv': ret = {{{ Functions.getIndex('_glGetProgramiv', true) }}}; break;
case 'glEnableVertexAttribArray': ret = {{{ Functions.getIndex('_glEnableVertexAttribArray', true) }}}; break;
case 'glDisableVertexAttribArray': ret = {{{ Functions.getIndex('_glDisableVertexAttribArray', true) }}}; break;
case 'glVertexAttribPointer': ret = {{{ Functions.getIndex('_glVertexAttribPointer', true) }}}; break;
case 'glVertexAttrib1f': ret = {{{ Functions.getIndex('_glVertexAttrib1f', true) }}}; break;
case 'glVertexAttrib2f': ret = {{{ Functions.getIndex('_glVertexAttrib2f', true) }}}; break;
case 'glVertexAttrib3f': ret = {{{ Functions.getIndex('_glVertexAttrib3f', true) }}}; break;
case 'glVertexAttrib4f': ret = {{{ Functions.getIndex('_glVertexAttrib4f', true) }}}; break;
case 'glVertexAttrib1fv': ret = {{{ Functions.getIndex('_glVertexAttrib1fv', true) }}}; break;
case 'glVertexAttrib2fv': ret = {{{ Functions.getIndex('_glVertexAttrib2fv', true) }}}; break;
case 'glVertexAttrib3fv': ret = {{{ Functions.getIndex('_glVertexAttrib3fv', true) }}}; break;
case 'glVertexAttrib4fv': ret = {{{ Functions.getIndex('_glVertexAttrib4fv', true) }}}; break;
case 'glGetVertexAttribfv': ret = {{{ Functions.getIndex('_glGetVertexAttribfv', true) }}}; break;
case 'glGetVertexAttribiv': ret = {{{ Functions.getIndex('_glGetVertexAttribiv', true) }}}; break;
case 'glGetVertexAttribPointerv': ret = {{{ Functions.getIndex('_glGetVertexAttribPointerv', true) }}}; break;
case 'glGetAttribLocation': ret = {{{ Functions.getIndex('_glGetAttribLocation', true) }}}; break;
case 'glGetActiveAttrib': ret = {{{ Functions.getIndex('_glGetActiveAttrib', true) }}}; break;
case 'glBindRenderbuffer': ret = {{{ Functions.getIndex('_glBindRenderbuffer', true) }}}; break;
case 'glDeleteRenderbuffers': ret = {{{ Functions.getIndex('_glDeleteRenderbuffers', true) }}}; break;
case 'glGenRenderbuffers': ret = {{{ Functions.getIndex('_glGenRenderbuffers', true) }}}; break;
case 'glCompressedTexImage2D': ret = {{{ Functions.getIndex('_glCompressedTexImage2D', true) }}}; break;
case 'glCompressedTexSubImage2D': ret = {{{ Functions.getIndex('_glCompressedTexSubImage2D', true) }}}; break;
case 'glBindFramebuffer': ret = {{{ Functions.getIndex('_glBindFramebuffer', true) }}}; break;
case 'glGenFramebuffers': ret = {{{ Functions.getIndex('_glGenFramebuffers', true) }}}; break;
case 'glDeleteFramebuffers': ret = {{{ Functions.getIndex('_glDeleteFramebuffers', true) }}}; break;
case 'glFramebufferRenderbuffer': ret = {{{ Functions.getIndex('_glFramebufferRenderbuffer', true) }}}; break;
case 'glFramebufferTexture2D': ret = {{{ Functions.getIndex('_glFramebufferTexture2D', true) }}}; break;
case 'glGetFramebufferAttachmentParameteriv': ret = {{{ Functions.getIndex('_glGetFramebufferAttachmentParameteriv', true) }}}; break;
case 'glIsFramebuffer': ret = {{{ Functions.getIndex('_glIsFramebuffer', true) }}}; break;
case 'glCheckFramebufferStatus': ret = {{{ Functions.getIndex('_glCheckFramebufferStatus', true) }}}; break;
case 'glRenderbufferStorage': ret = {{{ Functions.getIndex('_glRenderbufferStorage', true) }}}; break;
case 'glGenVertexArrays': ret = {{{ Functions.getIndex('_glGenVertexArrays', true) }}}; break;
case 'glDeleteVertexArrays': ret = {{{ Functions.getIndex('_glDeleteVertexArrays', true) }}}; break;
case 'glBindVertexArray': ret = {{{ Functions.getIndex('_glBindVertexArray', true) }}}; break;
case 'glGetString': ret = {{{ Functions.getIndex('_glGetString', true) }}}; break;
case 'glBindTexture': ret = {{{ Functions.getIndex('_glBindTexture', true) }}}; break;
case 'glGetBufferParameteriv': ret = {{{ Functions.getIndex('_glGetBufferParameteriv', true) }}}; break;
case 'glIsBuffer': ret = {{{ Functions.getIndex('_glIsBuffer', true) }}}; break;
case 'glDeleteShader': ret = {{{ Functions.getIndex('_glDeleteShader', true) }}}; break;
case 'glUniformMatrix2fv': ret = {{{ Functions.getIndex('_glUniformMatrix2fv', true) }}}; break;
case 'glUniformMatrix3fv': ret = {{{ Functions.getIndex('_glUniformMatrix3fv', true) }}}; break;
case 'glUniformMatrix4fv': ret = {{{ Functions.getIndex('_glUniformMatrix4fv', true) }}}; break;
case 'glIsRenderbuffer': ret = {{{ Functions.getIndex('_glIsRenderbuffer', true) }}}; break;
case 'glBlendEquation': ret = {{{ Functions.getIndex('_glBlendEquation', true) }}}; break;
case 'glBlendFunc': ret = {{{ Functions.getIndex('_glBlendFunc', true) }}}; break;
case 'glBlendFuncSeparate': ret = {{{ Functions.getIndex('_glBlendFuncSeparate', true) }}}; break;
case 'glBlendEquationSeparate': ret = {{{ Functions.getIndex('_glBlendEquationSeparate', true) }}}; break;
case 'glDepthRangef': ret = {{{ Functions.getIndex('_glDepthRangef', true) }}}; break;
case 'glClear': ret = {{{ Functions.getIndex('_glClear', true) }}}; break;
case 'glGenerateMipmap': ret = {{{ Functions.getIndex('_glGenerateMipmap', true) }}}; break;
case 'glBlendColor': ret = {{{ Functions.getIndex('_glBlendColor', true) }}}; break;
case 'glClearDepthf': ret = {{{ Functions.getIndex('_glClearDepthf', true) }}}; break;
case 'glDeleteProgram': ret = {{{ Functions.getIndex('_glDeleteProgram', true) }}}; break;
case 'glUniformMatrix3fv': ret = {{{ Functions.getIndex('_glUniformMatrix3fv', true) }}}; break;
case 'glClearColor': ret = {{{ Functions.getIndex('_glClearColor', true) }}}; break;
case 'glGetRenderbufferParameteriv': ret = {{{ Functions.getIndex('_glGetRenderbufferParameteriv', true) }}}; break;
case 'glGetShaderInfoLog': ret = {{{ Functions.getIndex('_glGetShaderInfoLog', true) }}}; break;
case 'glUniformMatrix4fv': ret = {{{ Functions.getIndex('_glUniformMatrix4fv', true) }}}; break;
case 'glClearStencil': ret = {{{ Functions.getIndex('_glClearStencil', true) }}}; break;
case 'glGetProgramInfoLog': ret = {{{ Functions.getIndex('_glGetProgramInfoLog', true) }}}; break;
case 'glGetUniformfv': ret = {{{ Functions.getIndex('_glGetUniformfv', true) }}}; break;
case 'glStencilFuncSeparate': ret = {{{ Functions.getIndex('_glStencilFuncSeparate', true) }}}; break;
case 'glSampleCoverage': ret = {{{ Functions.getIndex('_glSampleCoverage', true) }}}; break;
case 'glColorMask': ret = {{{ Functions.getIndex('_glColorMask', true) }}}; break;
case 'glGetShaderiv': ret = {{{ Functions.getIndex('_glGetShaderiv', true) }}}; break;
case 'glGetUniformiv': ret = {{{ Functions.getIndex('_glGetUniformiv', true) }}}; break;
case 'glCopyTexSubImage2D': ret = {{{ Functions.getIndex('_glCopyTexSubImage2D', true) }}}; break;
case 'glDetachShader': ret = {{{ Functions.getIndex('_glDetachShader', true) }}}; break;
case 'glGetShaderSource': ret = {{{ Functions.getIndex('_glGetShaderSource', true) }}}; break;
case 'glDeleteTextures': ret = {{{ Functions.getIndex('_glDeleteTextures', true) }}}; break;
case 'glGetAttachedShaders': ret = {{{ Functions.getIndex('_glGetAttachedShaders', true) }}}; break;
case 'glValidateProgram': ret = {{{ Functions.getIndex('_glValidateProgram', true) }}}; break;
case 'glDepthFunc': ret = {{{ Functions.getIndex('_glDepthFunc', true) }}}; break;
case 'glIsShader': ret = {{{ Functions.getIndex('_glIsShader', true) }}}; break;
case 'glDepthMask': ret = {{{ Functions.getIndex('_glDepthMask', true) }}}; break;
case 'glStencilMaskSeparate': ret = {{{ Functions.getIndex('_glStencilMaskSeparate', true) }}}; break;
case 'glIsProgram': ret = {{{ Functions.getIndex('_glIsProgram', true) }}}; break;
case 'glDisable': ret = {{{ Functions.getIndex('_glDisable', true) }}}; break;
case 'glStencilOpSeparate': ret = {{{ Functions.getIndex('_glStencilOpSeparate', true) }}}; break;
case 'glDrawArrays': ret = {{{ Functions.getIndex('_glDrawArrays', true) }}}; break;
case 'glDrawElements': ret = {{{ Functions.getIndex('_glDrawElements', true) }}}; break;
case 'glEnable': ret = {{{ Functions.getIndex('_glEnable', true) }}}; break;
case 'glFinish': ret = {{{ Functions.getIndex('_glFinish', true) }}}; break;
case 'glFlush': ret = {{{ Functions.getIndex('_glFlush', true) }}}; break;
case 'glFrontFace': ret = {{{ Functions.getIndex('_glFrontFace', true) }}}; break;
case 'glCullFace': ret = {{{ Functions.getIndex('_glCullFace', true) }}}; break;
case 'glGenTextures': ret = {{{ Functions.getIndex('_glGenTextures', true) }}}; break;
case 'glGetError': ret = {{{ Functions.getIndex('_glGetError', true) }}}; break;
case 'glGetIntegerv': ret = {{{ Functions.getIndex('_glGetIntegerv', true) }}}; break;
case 'glGetBooleanv': ret = {{{ Functions.getIndex('_glGetBooleanv', true) }}}; break;
case 'glGetFloatv': ret = {{{ Functions.getIndex('_glGetFloatv', true) }}}; break;
case 'glHint': ret = {{{ Functions.getIndex('_glHint', true) }}}; break;
case 'glIsTexture': ret = {{{ Functions.getIndex('_glIsTexture', true) }}}; break;
case 'glPixelStorei': ret = {{{ Functions.getIndex('_glPixelStorei', true) }}}; break;
case 'glReadPixels': ret = {{{ Functions.getIndex('_glReadPixels', true) }}}; break;
case 'glScissor': ret = {{{ Functions.getIndex('_glScissor', true) }}}; break;
case 'glStencilFunc': ret = {{{ Functions.getIndex('_glStencilFunc', true) }}}; break;
case 'glStencilMask': ret = {{{ Functions.getIndex('_glStencilMask', true) }}}; break;
case 'glStencilOp': ret = {{{ Functions.getIndex('_glStencilOp', true) }}}; break;
case 'glTexImage2D': ret = {{{ Functions.getIndex('_glTexImage2D', true) }}}; break;
case 'glTexParameterf': ret = {{{ Functions.getIndex('_glTexParameterf', true) }}}; break;
case 'glTexParameterfv': ret = {{{ Functions.getIndex('_glTexParameterfv', true) }}}; break;
case 'glTexParameteri': ret = {{{ Functions.getIndex('_glTexParameteri', true) }}}; break;
case 'glTexParameteriv': ret = {{{ Functions.getIndex('_glTexParameteriv', true) }}}; break;
case 'glGetTexParameterfv': ret = {{{ Functions.getIndex('_glGetTexParameterfv', true) }}}; break;
case 'glGetTexParameteriv': ret = {{{ Functions.getIndex('_glGetTexParameteriv', true) }}}; break;
case 'glTexSubImage2D': ret = {{{ Functions.getIndex('_glTexSubImage2D', true) }}}; break;
case 'glCopyTexImage2D': ret = {{{ Functions.getIndex('_glCopyTexImage2D', true) }}}; break;
case 'glViewport': ret = {{{ Functions.getIndex('_glViewport', true) }}}; break;
case 'glIsEnabled': ret = {{{ Functions.getIndex('_glIsEnabled', true) }}}; break;
case 'glLineWidth': ret = {{{ Functions.getIndex('_glLineWidth', true) }}}; break;
case 'glPolygonOffset': ret = {{{ Functions.getIndex('_glPolygonOffset', true) }}}; break;
case 'glReleaseShaderCompiler': ret = {{{ Functions.getIndex('_glReleaseShaderCompiler', true) }}}; break;
case 'glGetShaderPrecisionFormat': ret = {{{ Functions.getIndex('_glGetShaderPrecisionFormat', true) }}}; break;
case 'glShaderBinary': ret = {{{ Functions.getIndex('_glShaderBinary', true) }}}; break;
}
if (!ret) Module.printErr('WARNING: getProcAddress failed for ' + name);
return ret;
}
},
glGetShaderPrecisionFormat__sig: 'v',
glGetShaderPrecisionFormat: function() { throw 'glGetShaderPrecisionFormat: TODO' },
glShaderBinary__sig: 'v',
glShaderBinary: function() { throw 'glShaderBinary: TODO' },
glDeleteObject__sig: 'vi',
glDeleteObject: function(id) {
if (GL.programs[id]) {
_glDeleteProgram(id);
} else if (GL.shaders[id]) {
_glDeleteShader(id);
} else {
Module.printErr('WARNING: deleteObject received invalid id: ' + id);
}
},
glReleaseShaderCompiler__sig: 'v',
glReleaseShaderCompiler: function() {
// NOP (as allowed by GLES 2.0 spec)
},
glGetObjectParameteriv__sig: 'viii',
glGetObjectParameteriv: function(id, type, result) {
if (GL.programs[id]) {
if (type == 0x8B84) { // GL_OBJECT_INFO_LOG_LENGTH_ARB
{{{ makeSetValue('result', '0', 'Module.ctx.getProgramInfoLog(GL.programs[id]).length', 'i32') }}};
return;
}
_glGetProgramiv(id, type, result);
} else if (GL.shaders[id]) {
if (type == 0x8B84) { // GL_OBJECT_INFO_LOG_LENGTH_ARB
{{{ makeSetValue('result', '0', 'Module.ctx.getShaderInfoLog(GL.shaders[id]).length', 'i32') }}};
return;
} else if (type == 0x8B88) { // GL_OBJECT_SHADER_SOURCE_LENGTH_ARB
{{{ makeSetValue('result', '0', 'Module.ctx.getShaderSource(GL.shaders[id]).length', 'i32') }}};
return;
}
_glGetShaderiv(id, type, result);
} else {
Module.printErr('WARNING: getObjectParameteriv received invalid id: ' + id);
}
},
glGetInfoLog__sig: 'viiii',
glGetInfoLog: function(id, maxLength, length, infoLog) {
if (GL.programs[id]) {
_glGetProgramInfoLog(id, maxLength, length, infoLog);
} else if (GL.shaders[id]) {
_glGetShaderInfoLog(id, maxLength, length, infoLog);
} else {
Module.printErr('WARNING: getObjectParameteriv received invalid id: ' + id);
}
},
glBindProgram__sig: 'vii',
glBindProgram: function(type, id) {
assert(id == 0);
},
glGetPointerv: function(name, p) {
var attribute;
switch(name) {
case 0x808E: // GL_VERTEX_ARRAY_POINTER
attribute = GLImmediate.clientAttributes[GLImmediate.VERTEX]; break;
case 0x8090: // GL_COLOR_ARRAY_POINTER
attribute = GLImmediate.clientAttributes[GLImmediate.COLOR]; break;
case 0x8092: // GL_TEXTURE_COORD_ARRAY_POINTER
attribute = GLImmediate.clientAttributes[GLImmediate.TEXTURE0]; break;
default: throw 'TODO: glGetPointerv for ' + name;
}
{{{ makeSetValue('p', '0', 'attribute ? attribute.pointer : 0', 'i32') }}};
},
// GL Immediate mode
// See comment in GLEmulation.init()
#if FULL_ES2 == 0
$GLImmediate__postset: 'GL.immediate.setupFuncs(); Browser.moduleContextCreatedCallbacks.push(function() { GL.immediate.init() });',
#endif
$GLImmediate__deps: ['$Browser', '$GL', '$GLEmulation'],
$GLImmediate: {
MapTreeLib: null,
spawnMapTreeLib: function() {
/* A naive implementation of a map backed by an array, and accessed by
* naive iteration along the array. (hashmap with only one bucket)
*/
function CNaiveListMap() {
var list = [];
this.insert = function(key, val) {
if (this.contains(key|0)) return false;
list.push([key, val]);
return true;
};
var __contains_i;
this.contains = function(key) {
for (__contains_i = 0; __contains_i < list.length; ++__contains_i) {
if (list[__contains_i][0] === key) return true;
}
return false;
};
var __get_i;
this.get = function(key) {
for (__get_i = 0; __get_i < list.length; ++__get_i) {
if (list[__get_i][0] === key) return list[__get_i][1];
}
return undefined;
};
};
/* A tree of map nodes.
Uses `KeyView`s to allow descending the tree without garbage.
Example: {
// Create our map object.
var map = new ObjTreeMap();
// Grab the static keyView for the map.
var keyView = map.GetStaticKeyView();
// Let's make a map for:
// root: <undefined>
// 1: <undefined>
// 2: <undefined>
// 5: "Three, sir!"
// 3: "Three!"
// Note how we can chain together `Reset` and `Next` to
// easily descend based on multiple key fragments.
keyView.Reset().Next(1).Next(2).Next(5).Set("Three, sir!");
keyView.Reset().Next(1).Next(2).Next(3).Set("Three!");
}
*/
function CMapTree() {
function CNLNode() {
var map = new CNaiveListMap();
this.child = function(keyFrag) {
if (!map.contains(keyFrag|0)) {
map.insert(keyFrag|0, new CNLNode());
}
return map.get(keyFrag|0);
};
this.value = undefined;
this.get = function() {
return this.value;
};
this.set = function(val) {
this.value = val;
};
}
function CKeyView(root) {
var cur;
this.reset = function() {
cur = root;
return this;
};
this.reset();
this.next = function(keyFrag) {
cur = cur.child(keyFrag);
return this;
};
this.get = function() {
return cur.get();
};
this.set = function(val) {
cur.set(val);
};
};
var root;
var staticKeyView;
this.createKeyView = function() {
return new CKeyView(root);
}
this.clear = function() {
root = new CNLNode();
staticKeyView = this.createKeyView();
};
this.clear();
this.getStaticKeyView = function() {
staticKeyView.reset();
return staticKeyView;
};
};
// Exports:
return {
create: function() {
return new CMapTree();
},
};
},
TexEnvJIT: null,
spawnTexEnvJIT: function() {
// GL defs:
var GL_TEXTURE0 = 0x84C0;
var GL_TEXTURE_1D = 0x0DE0;
var GL_TEXTURE_2D = 0x0DE1;
var GL_TEXTURE_3D = 0x806f;
var GL_TEXTURE_CUBE_MAP = 0x8513;
var GL_TEXTURE_ENV = 0x2300;
var GL_TEXTURE_ENV_MODE = 0x2200;
var GL_TEXTURE_ENV_COLOR = 0x2201;
var GL_TEXTURE_CUBE_MAP_POSITIVE_X = 0x8515;
var GL_TEXTURE_CUBE_MAP_NEGATIVE_X = 0x8516;
var GL_TEXTURE_CUBE_MAP_POSITIVE_Y = 0x8517;
var GL_TEXTURE_CUBE_MAP_NEGATIVE_Y = 0x8518;
var GL_TEXTURE_CUBE_MAP_POSITIVE_Z = 0x8519;
var GL_TEXTURE_CUBE_MAP_NEGATIVE_Z = 0x851A;
var GL_SRC0_RGB = 0x8580;
var GL_SRC1_RGB = 0x8581;
var GL_SRC2_RGB = 0x8582;
var GL_SRC0_ALPHA = 0x8588;
var GL_SRC1_ALPHA = 0x8589;
var GL_SRC2_ALPHA = 0x858A;
var GL_OPERAND0_RGB = 0x8590;
var GL_OPERAND1_RGB = 0x8591;
var GL_OPERAND2_RGB = 0x8592;
var GL_OPERAND0_ALPHA = 0x8598;
var GL_OPERAND1_ALPHA = 0x8599;
var GL_OPERAND2_ALPHA = 0x859A;
var GL_COMBINE_RGB = 0x8571;
var GL_COMBINE_ALPHA = 0x8572;
var GL_RGB_SCALE = 0x8573;
var GL_ALPHA_SCALE = 0x0D1C;
// env.mode
var GL_ADD = 0x0104;
var GL_BLEND = 0x0BE2;
var GL_REPLACE = 0x1E01;
var GL_MODULATE = 0x2100;
var GL_DECAL = 0x2101;
var GL_COMBINE = 0x8570;
// env.color/alphaCombiner
//var GL_ADD = 0x0104;
//var GL_REPLACE = 0x1E01;
//var GL_MODULATE = 0x2100;
var GL_SUBTRACT = 0x84E7;
var GL_INTERPOLATE = 0x8575;
// env.color/alphaSrc
var GL_TEXTURE = 0x1702;
var GL_CONSTANT = 0x8576;
var GL_PRIMARY_COLOR = 0x8577;
var GL_PREVIOUS = 0x8578;
// env.color/alphaOp
var GL_SRC_COLOR = 0x0300;
var GL_ONE_MINUS_SRC_COLOR = 0x0301;
var GL_SRC_ALPHA = 0x0302;
var GL_ONE_MINUS_SRC_ALPHA = 0x0303;
var GL_RGB = 0x1907;
var GL_RGBA = 0x1908;
// Our defs:
var TEXENVJIT_NAMESPACE_PREFIX = "tej_";
// Not actually constant, as they can be changed between JIT passes:
var TEX_UNIT_UNIFORM_PREFIX = "uTexUnit";
var TEX_COORD_VARYING_PREFIX = "vTexCoord";
var PRIM_COLOR_VARYING = "vPrimColor";
var TEX_MATRIX_UNIFORM_PREFIX = "uTexMatrix";
// Static vars:
var s_texUnits = null; //[];
var s_activeTexture = 0;
var s_requiredTexUnitsForPass = [];
// Static funcs:
function abort(info) {
assert(false, "[TexEnvJIT] ABORT: " + info);
}
function abort_noSupport(info) {
abort("No support: " + info);
}
function abort_sanity(info) {
abort("Sanity failure: " + info);
}
function genTexUnitSampleExpr(texUnitID) {
var texUnit = s_texUnits[texUnitID];
var texType = texUnit.getTexType();
var func = null;
switch (texType) {
case GL_TEXTURE_1D:
func = "texture2D";
break;
case GL_TEXTURE_2D:
func = "texture2D";
break;
case GL_TEXTURE_3D:
return abort_noSupport("No support for 3D textures.");
case GL_TEXTURE_CUBE_MAP:
func = "textureCube";
break;
default:
return abort_sanity("Unknown texType: 0x" + texType.toString(16));
}
var texCoordExpr = TEX_COORD_VARYING_PREFIX + texUnitID;
if (TEX_MATRIX_UNIFORM_PREFIX != null) {
texCoordExpr = "(" + TEX_MATRIX_UNIFORM_PREFIX + texUnitID + " * " + texCoordExpr + ")";
}
return func + "(" + TEX_UNIT_UNIFORM_PREFIX + texUnitID + ", " + texCoordExpr + ".xy)";
}
function getTypeFromCombineOp(op) {
switch (op) {
case GL_SRC_COLOR:
case GL_ONE_MINUS_SRC_COLOR:
return "vec3";
case GL_SRC_ALPHA:
case GL_ONE_MINUS_SRC_ALPHA:
return "float";
}
return Abort_NoSupport("Unsupported combiner op: 0x" + op.toString(16));
}
function getCurTexUnit() {
return s_texUnits[s_activeTexture];
}
function genCombinerSourceExpr(texUnitID, constantExpr, previousVar,
src, op)
{
var srcExpr = null;
switch (src) {
case GL_TEXTURE:
srcExpr = genTexUnitSampleExpr(texUnitID);
break;
case GL_CONSTANT:
srcExpr = constantExpr;
break;
case GL_PRIMARY_COLOR:
srcExpr = PRIM_COLOR_VARYING;
break;
case GL_PREVIOUS:
srcExpr = previousVar;
break;
default:
return abort_noSupport("Unsupported combiner src: 0x" + src.toString(16));
}
var expr = null;
switch (op) {
case GL_SRC_COLOR:
expr = srcExpr + ".rgb";
break;
case GL_ONE_MINUS_SRC_COLOR:
expr = "(vec3(1.0) - " + srcExpr + ".rgb)";
break;
case GL_SRC_ALPHA:
expr = srcExpr + ".a";
break;
case GL_ONE_MINUS_SRC_ALPHA:
expr = "(1.0 - " + srcExpr + ".a)";
break;
default:
return abort_noSupport("Unsupported combiner op: 0x" + op.toString(16));
}
return expr;
}
function valToFloatLiteral(val) {
if (val == Math.round(val)) return val + '.0';
return val;
}
// Classes:
function CTexEnv() {
this.mode = GL_MODULATE;
this.colorCombiner = GL_MODULATE;
this.alphaCombiner = GL_MODULATE;
this.colorScale = 1;
this.alphaScale = 1;
this.envColor = [0, 0, 0, 0];
this.colorSrc = [
GL_TEXTURE,
GL_PREVIOUS,
GL_CONSTANT
];
this.alphaSrc = [
GL_TEXTURE,
GL_PREVIOUS,
GL_CONSTANT
];
this.colorOp = [
GL_SRC_COLOR,
GL_SRC_COLOR,
GL_SRC_ALPHA
];
this.alphaOp = [
GL_SRC_ALPHA,
GL_SRC_ALPHA,
GL_SRC_ALPHA
];
this.traverseState = function(keyView) {
keyView.next(this.mode);
keyView.next(this.colorCombiner);
keyView.next(this.alphaCombiner);
keyView.next(this.colorCombiner);
keyView.next(this.alphaScale);
keyView.next(this.envColor[0]);
keyView.next(this.envColor[1]);
keyView.next(this.envColor[2]);
keyView.next(this.envColor[3]);
keyView.next(this.colorSrc[0]);
keyView.next(this.colorSrc[1]);
keyView.next(this.colorSrc[2]);
keyView.next(this.alphaSrc[0]);
keyView.next(this.alphaSrc[1]);
keyView.next(this.alphaSrc[2]);
keyView.next(this.colorOp[0]);
keyView.next(this.colorOp[1]);
keyView.next(this.colorOp[2]);
keyView.next(this.alphaOp[0]);
keyView.next(this.alphaOp[1]);
keyView.next(this.alphaOp[2]);
};
}
function CTexUnit() {
this.env = new CTexEnv();
this.enabled_tex1D = false;
this.enabled_tex2D = false;
this.enabled_tex3D = false;
this.enabled_texCube = false;
this.traverseState = function(keyView) {
var texUnitType = this.getTexType();
keyView.next(texUnitType);
if (!texUnitType) return;
this.env.traverseState(keyView);
};
};
// Class impls:
CTexUnit.prototype.enabled = function() {
return this.getTexType() != 0;
}
CTexUnit.prototype.genPassLines = function(passOutputVar, passInputVar, texUnitID) {
if (!this.enabled()) {
return ["vec4 " + passOutputVar + " = " + passInputVar + ";"];
}
return this.env.genPassLines(passOutputVar, passInputVar, texUnitID);
}
CTexUnit.prototype.getTexType = function() {
if (this.enabled_texCube) {
return GL_TEXTURE_CUBE_MAP;
} else if (this.enabled_tex3D) {
return GL_TEXTURE_3D;
} else if (this.enabled_tex2D) {
return GL_TEXTURE_2D;
} else if (this.enabled_tex1D) {
return GL_TEXTURE_1D;
}
return 0;
}
CTexEnv.prototype.genPassLines = function(passOutputVar, passInputVar, texUnitID) {
switch (this.mode) {
case GL_REPLACE: {
/* RGB:
* Cv = Cs
* Av = Ap // Note how this is different, and that we'll
* need to track the bound texture internalFormat
* to get this right.
*
* RGBA:
* Cv = Cs
* Av = As
*/
return [
"vec4 " + passOutputVar + " = " + genTexUnitSampleExpr(texUnitID) + ";",
];
}
case GL_ADD: {
/* RGBA:
* Cv = Cp + Cs
* Av = ApAs
*/
var prefix = TEXENVJIT_NAMESPACE_PREFIX + 'env' + texUnitID + "_";
var texVar = prefix + "tex";
var colorVar = prefix + "color";
var alphaVar = prefix + "alpha";
return [
"vec4 " + texVar + " = " + genTexUnitSampleExpr(texUnitID) + ";",
"vec3 " + colorVar + " = " + passInputVar + ".rgb + " + texVar + ".rgb;",
"float " + alphaVar + " = " + passInputVar + ".a * " + texVar + ".a;",
"vec4 " + passOutputVar + " = vec4(" + colorVar + ", " + alphaVar + ");",
];
}
case GL_MODULATE: {
/* RGBA:
* Cv = CpCs
* Av = ApAs
*/
var line = [
"vec4 " + passOutputVar,
" = ",
passInputVar,
" * ",
genTexUnitSampleExpr(texUnitID),
";",
];
return [line.join("")];
}
case GL_DECAL: {
/* RGBA:
* Cv = Cp(1 - As) + CsAs
* Av = Ap
*/
var prefix = TEXENVJIT_NAMESPACE_PREFIX + 'env' + texUnitID + "_";
var texVar = prefix + "tex";
var colorVar = prefix + "color";
var alphaVar = prefix + "alpha";
return [
"vec4 " + texVar + " = " + genTexUnitSampleExpr(texUnitID) + ";",
[
"vec3 " + colorVar + " = ",
passInputVar + ".rgb * (1.0 - " + texVar + ".a)",
" + ",
texVar + ".rgb * " + texVar + ".a",
";"
].join(""),
"float " + alphaVar + " = " + passInputVar + ".a;",
"vec4 " + passOutputVar + " = vec4(" + colorVar + ", " + alphaVar + ");",
];
}
case GL_BLEND: {
/* RGBA:
* Cv = Cp(1 - Cs) + CcCs
* Av = As
*/
var prefix = TEXENVJIT_NAMESPACE_PREFIX + 'env' + texUnitID + "_";
var texVar = prefix + "tex";
var colorVar = prefix + "color";
var alphaVar = prefix + "alpha";
return [
"vec4 " + texVar + " = " + genTexUnitSampleExpr(texUnitID) + ";",
[
"vec3 " + colorVar + " = ",
passInputVar + ".rgb * (1.0 - " + texVar + ".rgb)",
" + ",
PRIM_COLOR_VARYING + ".rgb * " + texVar + ".rgb",
";"
].join(""),
"float " + alphaVar + " = " + texVar + ".a;",
"vec4 " + passOutputVar + " = vec4(" + colorVar + ", " + alphaVar + ");",
];
}
case GL_COMBINE: {
var prefix = TEXENVJIT_NAMESPACE_PREFIX + 'env' + texUnitID + "_";
var colorVar = prefix + "color";
var alphaVar = prefix + "alpha";
var colorLines = this.genCombinerLines(true, colorVar,
passInputVar, texUnitID,
this.colorCombiner, this.colorSrc, this.colorOp);
var alphaLines = this.genCombinerLines(false, alphaVar,
passInputVar, texUnitID,
this.alphaCombiner, this.alphaSrc, this.alphaOp);
var line = [
"vec4 " + passOutputVar,
" = ",
"vec4(",
colorVar + " * " + valToFloatLiteral(this.colorScale),
", ",
alphaVar + " * " + valToFloatLiteral(this.alphaScale),
")",
";",
].join("");
return [].concat(colorLines, alphaLines, [line]);
}
}
return Abort_NoSupport("Unsupported TexEnv mode: 0x" + this.mode.toString(16));
}
CTexEnv.prototype.genCombinerLines = function(isColor, outputVar,
passInputVar, texUnitID,
combiner, srcArr, opArr)
{
var argsNeeded = null;
switch (combiner) {
case GL_REPLACE:
argsNeeded = 1;
break;
case GL_MODULATE:
case GL_ADD:
case GL_SUBTRACT:
argsNeeded = 2;
break;
case GL_INTERPOLATE:
argsNeeded = 3;
break;
default:
return abort_noSupport("Unsupported combiner: 0x" + combiner.toString(16));
}
var constantExpr = [
"vec4(",
valToFloatLiteral(this.envColor[0]),
", ",
valToFloatLiteral(this.envColor[1]),
", ",
valToFloatLiteral(this.envColor[2]),
", ",
valToFloatLiteral(this.envColor[3]),
")",
].join("");
var src0Expr = (argsNeeded >= 1) ? genCombinerSourceExpr(texUnitID, constantExpr, passInputVar, srcArr[0], opArr[0])
: null;
var src1Expr = (argsNeeded >= 2) ? genCombinerSourceExpr(texUnitID, constantExpr, passInputVar, srcArr[1], opArr[1])
: null;
var src2Expr = (argsNeeded >= 3) ? genCombinerSourceExpr(texUnitID, constantExpr, passInputVar, srcArr[2], opArr[2])
: null;
var outputType = isColor ? "vec3" : "float";
var lines = null;
switch (combiner) {
case GL_REPLACE: {
var line = [
outputType + " " + outputVar,
" = ",
src0Expr,
";",
];
lines = [line.join("")];
break;
}
case GL_MODULATE: {
var line = [
outputType + " " + outputVar + " = ",
src0Expr + " * " + src1Expr,
";",
];
lines = [line.join("")];
break;
}
case GL_ADD: {
var line = [
outputType + " " + outputVar + " = ",
src0Expr + " + " + src1Expr,
";",
];
lines = [line.join("")];
break;
}
case GL_SUBTRACT: {
var line = [
outputType + " " + outputVar + " = ",
src0Expr + " - " + src1Expr,
";",
];
lines = [line.join("")];
break;
}
case GL_INTERPOLATE: {
var prefix = TEXENVJIT_NAMESPACE_PREFIX + 'env' + texUnitID + "_";
var arg2Var = prefix + "colorSrc2";
var arg2Line = getTypeFromCombineOp(this.colorOp[2]) + " " + arg2Var + " = " + src2Expr + ";";
var line = [
outputType + " " + outputVar,
" = ",
src0Expr + " * " + arg2Var,
" + ",
src1Expr + " * (1.0 - " + arg2Var + ")",
";",
];
lines = [
arg2Line,
line.join(""),
];
break;
}
default:
return abort_sanity("Unmatched TexEnv.colorCombiner?");
}
return lines;
}
return {
// Exports:
init: function(gl, specifiedMaxTextureImageUnits) {
var maxTexUnits = 0;
if (specifiedMaxTextureImageUnits) {
maxTexUnits = specifiedMaxTextureImageUnits;
} else if (gl) {
maxTexUnits = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS);
}
assert(maxTexUnits > 0);
s_texUnits = [];
for (var i = 0; i < maxTexUnits; i++) {
s_texUnits.push(new CTexUnit());
}
},
setGLSLVars: function(uTexUnitPrefix, vTexCoordPrefix, vPrimColor, uTexMatrixPrefix) {
TEX_UNIT_UNIFORM_PREFIX = uTexUnitPrefix;
TEX_COORD_VARYING_PREFIX = vTexCoordPrefix;
PRIM_COLOR_VARYING = vPrimColor;
TEX_MATRIX_UNIFORM_PREFIX = uTexMatrixPrefix;
},
genAllPassLines: function(resultDest, indentSize) {
indentSize = indentSize || 0;
s_requiredTexUnitsForPass.length = 0; // Clear the list.
var lines = [];
var lastPassVar = PRIM_COLOR_VARYING;
for (var i = 0; i < s_texUnits.length; i++) {
if (!s_texUnits[i].enabled()) continue;
s_requiredTexUnitsForPass.push(i);
var prefix = TEXENVJIT_NAMESPACE_PREFIX + 'env' + i + "_";
var passOutputVar = prefix + "result";
var newLines = s_texUnits[i].genPassLines(passOutputVar, lastPassVar, i);
lines = lines.concat(newLines, [""]);
lastPassVar = passOutputVar;
}
lines.push(resultDest + " = " + lastPassVar + ";");
var indent = "";
for (var i = 0; i < indentSize; i++) indent += " ";
var output = indent + lines.join("\n" + indent);
return output;
},
getUsedTexUnitList: function() {
return s_requiredTexUnitsForPass;
},
traverseState: function(keyView) {
for (var i = 0; i < s_texUnits.length; i++) {
var texUnit = s_texUnits[i];
var enabled = texUnit.enabled();
keyView.next(enabled);
if (enabled) {
texUnit.traverseState(keyView);
}
}
},
getTexUnitType: function(texUnitID) {
assert(texUnitID >= 0 &&
texUnitID < s_texUnits.length);
return s_texUnits[texUnitID].getTexType();
},
// Hooks:
hook_activeTexture: function(texture) {
s_activeTexture = texture - GL_TEXTURE0;
},
hook_enable: function(cap) {
var cur = getCurTexUnit();
switch (cap) {
case GL_TEXTURE_1D:
cur.enabled_tex1D = true;
break;
case GL_TEXTURE_2D:
cur.enabled_tex2D = true;
break;
case GL_TEXTURE_3D:
cur.enabled_tex3D = true;
break;
case GL_TEXTURE_CUBE_MAP:
cur.enabled_texCube = true;
break;
}
},
hook_disable: function(cap) {
var cur = getCurTexUnit();
switch (cap) {
case GL_TEXTURE_1D:
cur.enabled_tex1D = false;
break;
case GL_TEXTURE_2D:
cur.enabled_tex2D = false;
break;
case GL_TEXTURE_3D:
cur.enabled_tex3D = false;
break;
case GL_TEXTURE_CUBE_MAP:
cur.enabled_texCube = false;
break;
}
},
hook_texEnvf: function(target, pname, param) {
if (target != GL_TEXTURE_ENV)
return;
var env = getCurTexUnit().env;
switch (pname) {
case GL_RGB_SCALE:
env.colorScale = param;
break;
case GL_ALPHA_SCALE:
env.alphaScale = param;
break;
default:
Module.printErr('WARNING: Unhandled `pname` in call to `glTexEnvf`.');
}
},
hook_texEnvi: function(target, pname, param) {
if (target != GL_TEXTURE_ENV)
return;
var env = getCurTexUnit().env;
switch (pname) {
case GL_TEXTURE_ENV_MODE:
env.mode = param;
break;
case GL_COMBINE_RGB:
env.colorCombiner = param;
break;
case GL_COMBINE_ALPHA:
env.alphaCombiner = param;
break;
case GL_SRC0_RGB:
env.colorSrc[0] = param;
break;
case GL_SRC1_RGB:
env.colorSrc[1] = param;
break;
case GL_SRC2_RGB:
env.colorSrc[2] = param;
break;
case GL_SRC0_ALPHA:
env.alphaSrc[0] = param;
break;
case GL_SRC1_ALPHA:
env.alphaSrc[1] = param;
break;
case GL_SRC2_ALPHA:
env.alphaSrc[2] = param;
break;
case GL_OPERAND0_RGB:
env.colorOp[0] = param;
break;
case GL_OPERAND1_RGB:
env.colorOp[1] = param;
break;
case GL_OPERAND2_RGB:
env.colorOp[2] = param;
break;
case GL_OPERAND0_ALPHA:
env.alphaOp[0] = param;
break;
case GL_OPERAND1_ALPHA:
env.alphaOp[1] = param;
break;
case GL_OPERAND2_ALPHA:
env.alphaOp[2] = param;
break;
case GL_RGB_SCALE:
env.colorScale = param;
break;
case GL_ALPHA_SCALE:
env.alphaScale = param;
break;
default:
Module.printErr('WARNING: Unhandled `pname` in call to `glTexEnvi`.');
}
},
hook_texEnvfv: function(target, pname, params) {
if (target != GL_TEXTURE_ENV) return;
var env = getCurTexUnit().env;
switch (pname) {
case GL_TEXTURE_ENV_COLOR: {
for (var i = 0; i < 4; i++) {
var param = {{{ makeGetValue('params', 'i*4', 'float') }}};
env.envColor[i] = param;
}
break
}
default:
Module.printErr('WARNING: Unhandled `pname` in call to `glTexEnvfv`.');
}
},
};
},
// Vertex and index data
vertexData: null, // current vertex data. either tempData (glBegin etc.) or a view into the heap (gl*Pointer). Default view is F32
vertexDataU8: null, // U8 view
tempData: null,
indexData: null,
vertexCounter: 0,
mode: -1,
rendererCache: null,
rendererComponents: [], // small cache for calls inside glBegin/end. counts how many times the element was seen
rendererComponentPointer: 0, // next place to start a glBegin/end component
lastRenderer: null, // used to avoid cleaning up and re-preparing the same renderer
lastArrayBuffer: null, // used in conjunction with lastRenderer
lastProgram: null, // ""
lastStride: -1, // ""
// The following data structures are used for OpenGL Immediate Mode matrix routines.
matrix: {},
matrixStack: {},
currentMatrix: 'm', // default is modelview
tempMatrix: null,
matricesModified: false,
useTextureMatrix: false,
// Clientside attributes
VERTEX: 0,
NORMAL: 1,
COLOR: 2,
TEXTURE0: 3,
TEXTURE1: 4,
TEXTURE2: 5,
TEXTURE3: 6,
TEXTURE4: 7,
TEXTURE5: 8,
TEXTURE6: 9,
NUM_ATTRIBUTES: 10, // Overwritten in init().
MAX_TEXTURES: 7, // Overwritten in init().
totalEnabledClientAttributes: 0,
enabledClientAttributes: [0, 0],
clientAttributes: [], // raw data, including possible unneeded ones
liveClientAttributes: [], // the ones actually alive in the current computation, sorted
modifiedClientAttributes: false,
clientActiveTexture: 0,
clientColor: null,
usedTexUnitList: [],
fixedFunctionProgram: null,
setClientAttribute: function(name, size, type, stride, pointer) {
var attrib = this.clientAttributes[name];
if (!attrib) {
for (var i = 0; i <= name; i++) { // keep flat
if (!this.clientAttributes[i]) {
this.clientAttributes[i] = {
name: name,
size: size,
type: type,
stride: stride,
pointer: pointer,
offset: 0
};
}
}
} else {
attrib.name = name;
attrib.size = size;
attrib.type = type;
attrib.stride = stride;
attrib.pointer = pointer;
attrib.offset = 0;
}
this.modifiedClientAttributes = true;
},
// Renderers
addRendererComponent: function(name, size, type) {
if (!this.rendererComponents[name]) {
this.rendererComponents[name] = 1;
#if ASSERTIONS
if (this.enabledClientAttributes[name]) {
console.log("Warning: glTexCoord used after EnableClientState for TEXTURE_COORD_ARRAY for TEXTURE0. Disabling TEXTURE_COORD_ARRAY...");
}
#endif
this.enabledClientAttributes[name] = true;
this.setClientAttribute(name, size, type, 0, this.rendererComponentPointer);
this.rendererComponentPointer += size * GL.byteSizeByType[type - GL.byteSizeByTypeRoot];
} else {
this.rendererComponents[name]++;
}
},
disableBeginEndClientAttributes: function() {
for (var i = 0; i < this.NUM_ATTRIBUTES; i++) {
if (this.rendererComponents[i]) this.enabledClientAttributes[i] = false;
}
},
getRenderer: function() {
// return a renderer object given the liveClientAttributes
// we maintain a cache of renderers, optimized to not generate garbage
var attributes = GL.immediate.liveClientAttributes;
var cacheMap = GL.immediate.rendererCache;
var temp;
var keyView = cacheMap.getStaticKeyView().reset();
// By attrib state:
for (var i = 0; i < attributes.length; i++) {
var attribute = attributes[i];
keyView.next(attribute.name).next(attribute.size).next(attribute.type);
}
// By fog state:
var fogParam = 0;
if (GLEmulation.fogEnabled) {
switch (GLEmulation.fogMode) {
case 0x0801: // GL_EXP2
fogParam = 1;
break;
case 0x2601: // GL_LINEAR
fogParam = 2;
break;
default: // default to GL_EXP
fogParam = 3;
break;
}
}
keyView.next(fogParam);
// By cur program:
keyView.next(GL.currProgram);
if (!GL.currProgram) {
GL.immediate.TexEnvJIT.traverseState(keyView);
}
// If we don't already have it, create it.
if (!keyView.get()) {
#if GL_DEBUG
Module.printErr('generating renderer for ' + JSON.stringify(attributes));
#endif
keyView.set(this.createRenderer());
}
return keyView.get();
},
createRenderer: function(renderer) {
var useCurrProgram = !!GL.currProgram;
var hasTextures = false, textureSizes = [], textureTypes = [];
for (var i = 0; i < GL.immediate.MAX_TEXTURES; i++) {
var texAttribName = GL.immediate.TEXTURE0 + i;
if (!GL.immediate.enabledClientAttributes[texAttribName])
continue;
if (!useCurrProgram) {
assert(GL.immediate.TexEnvJIT.getTexUnitType(i) != 0, "GL_TEXTURE" + i + " coords are supplied, but that texture unit is disabled in the fixed-function pipeline.");
}
textureSizes[i] = GL.immediate.clientAttributes[texAttribName].size;
textureTypes[i] = GL.immediate.clientAttributes[texAttribName].type;
hasTextures = true;
}
var positionSize = GL.immediate.clientAttributes[GL.immediate.VERTEX].size;
var positionType = GL.immediate.clientAttributes[GL.immediate.VERTEX].type;
var colorSize = 0, colorType;
if (GL.immediate.enabledClientAttributes[GL.immediate.COLOR]) {
colorSize = GL.immediate.clientAttributes[GL.immediate.COLOR].size;
colorType = GL.immediate.clientAttributes[GL.immediate.COLOR].type;
}
var normalSize = 0, normalType;
if (GL.immediate.enabledClientAttributes[GL.immediate.NORMAL]) {
normalSize = GL.immediate.clientAttributes[GL.immediate.NORMAL].size;
normalType = GL.immediate.clientAttributes[GL.immediate.NORMAL].type;
}
var ret = {
init: function() {
// For fixed-function shader generation.
var uTexUnitPrefix = 'u_texUnit';
var aTexCoordPrefix = 'a_texCoord';
var vTexCoordPrefix = 'v_texCoord';
var vPrimColor = 'v_color';
var uTexMatrixPrefix = GL.immediate.useTextureMatrix ? 'u_textureMatrix' : null;
if (useCurrProgram) {
if (GL.shaderInfos[GL.programShaders[GL.currProgram][0]].type == Module.ctx.VERTEX_SHADER) {
this.vertexShader = GL.shaders[GL.programShaders[GL.currProgram][0]];
this.fragmentShader = GL.shaders[GL.programShaders[GL.currProgram][1]];
} else {
this.vertexShader = GL.shaders[GL.programShaders[GL.currProgram][1]];
this.fragmentShader = GL.shaders[GL.programShaders[GL.currProgram][0]];
}
this.program = GL.programs[GL.currProgram];
this.usedTexUnitList = [];
} else {
// IMPORTANT NOTE: If you parameterize the shader source based on any runtime values
// in order to create the least expensive shader possible based on the features being
// used, you should also update the code in the beginning of getRenderer to make sure
// that you cache the renderer based on the said parameters.
if (GLEmulation.fogEnabled) {
switch (GLEmulation.fogMode) {
case 0x0801: // GL_EXP2
// fog = exp(-(gl_Fog.density * gl_FogFragCoord)^2)
var fogFormula = ' float fog = exp(-u_fogDensity * u_fogDensity * ecDistance * ecDistance); \n';
break;
case 0x2601: // GL_LINEAR
// fog = (gl_Fog.end - gl_FogFragCoord) * gl_fog.scale
var fogFormula = ' float fog = (u_fogEnd - ecDistance) * u_fogScale; \n';
break;
default: // default to GL_EXP
// fog = exp(-gl_Fog.density * gl_FogFragCoord)
var fogFormula = ' float fog = exp(-u_fogDensity * ecDistance); \n';
break;
}
}
GL.immediate.TexEnvJIT.setGLSLVars(uTexUnitPrefix, vTexCoordPrefix, vPrimColor, uTexMatrixPrefix);
var fsTexEnvPass = GL.immediate.TexEnvJIT.genAllPassLines('gl_FragColor', 2);
var texUnitAttribList = '';
var texUnitVaryingList = '';
var texUnitUniformList = '';
var vsTexCoordInits = '';
this.usedTexUnitList = GL.immediate.TexEnvJIT.getUsedTexUnitList();
for (var i = 0; i < this.usedTexUnitList.length; i++) {
var texUnit = this.usedTexUnitList[i];
texUnitAttribList += 'attribute vec4 ' + aTexCoordPrefix + texUnit + ';\n';
texUnitVaryingList += 'varying vec4 ' + vTexCoordPrefix + texUnit + ';\n';
texUnitUniformList += 'uniform sampler2D ' + uTexUnitPrefix + texUnit + ';\n';
vsTexCoordInits += ' ' + vTexCoordPrefix + texUnit + ' = ' + aTexCoordPrefix + texUnit + ';\n';
if (GL.immediate.useTextureMatrix) {
texUnitUniformList += 'uniform mat4 ' + uTexMatrixPrefix + texUnit + ';\n';
}
}
var vsFogVaryingInit = null;
if (GLEmulation.fogEnabled) {
vsFogVaryingInit = ' v_fogFragCoord = abs(ecPosition.z);\n';
}
var vsSource = [
'attribute vec4 a_position;',
'attribute vec4 a_color;',
'varying vec4 v_color;',
texUnitAttribList,
texUnitVaryingList,
(GLEmulation.fogEnabled ? 'varying float v_fogFragCoord;' : null),
'uniform mat4 u_modelView;',
'uniform mat4 u_projection;',
'void main()',
'{',
' vec4 ecPosition = u_modelView * a_position;', // eye-coordinate position
' gl_Position = u_projection * ecPosition;',
' v_color = a_color;',
vsTexCoordInits,
vsFogVaryingInit,
'}',
''
].join('\n').replace(/\n\n+/g, '\n');
this.vertexShader = Module.ctx.createShader(Module.ctx.VERTEX_SHADER);
Module.ctx.shaderSource(this.vertexShader, vsSource);
Module.ctx.compileShader(this.vertexShader);
var fogHeaderIfNeeded = null;
if (GLEmulation.fogEnabled) {
fogHeaderIfNeeded = [
'',
'varying float v_fogFragCoord; ',
'uniform vec4 u_fogColor; ',
'uniform float u_fogEnd; ',
'uniform float u_fogScale; ',
'uniform float u_fogDensity; ',
'float ffog(in float ecDistance) { ',
fogFormula,
' fog = clamp(fog, 0.0, 1.0); ',
' return fog; ',
'}',
'',
].join("\n");
}
var fogPass = null;
if (GLEmulation.fogEnabled) {
fogPass = 'gl_FragColor = vec4(mix(u_fogColor.rgb, gl_FragColor.rgb, ffog(v_fogFragCoord)), gl_FragColor.a);\n';
}
var fsSource = [
'precision mediump float;',
texUnitVaryingList,
texUnitUniformList,
'varying vec4 v_color;',
fogHeaderIfNeeded,
'void main()',
'{',
fsTexEnvPass,
fogPass,
'}',
''
].join("\n").replace(/\n\n+/g, '\n');
this.fragmentShader = Module.ctx.createShader(Module.ctx.FRAGMENT_SHADER);
Module.ctx.shaderSource(this.fragmentShader, fsSource);
Module.ctx.compileShader(this.fragmentShader);
this.program = Module.ctx.createProgram();
Module.ctx.attachShader(this.program, this.vertexShader);
Module.ctx.attachShader(this.program, this.fragmentShader);
Module.ctx.bindAttribLocation(this.program, 0, 'a_position');
Module.ctx.linkProgram(this.program);
}
this.positionLocation = Module.ctx.getAttribLocation(this.program, 'a_position');
this.texCoordLocations = [];
for (var i = 0; i < GL.immediate.MAX_TEXTURES; i++) {
if (!GL.immediate.enabledClientAttributes[GL.immediate.TEXTURE0 + i]) {
this.texCoordLocations[i] = -1;
continue;
}
if (useCurrProgram) {
this.texCoordLocations[i] = Module.ctx.getAttribLocation(this.program, 'a_texCoord' + i);
} else {
this.texCoordLocations[i] = Module.ctx.getAttribLocation(this.program, aTexCoordPrefix + i);
}
}
if (!useCurrProgram) {
// Temporarily switch to the program so we can set our sampler uniforms early.
var prevBoundProg = Module.ctx.getParameter(Module.ctx.CURRENT_PROGRAM);
Module.ctx.useProgram(this.program);
{
for (var i = 0; i < this.usedTexUnitList.length; i++) {
var texUnitID = this.usedTexUnitList[i];
var texSamplerLoc = Module.ctx.getUniformLocation(this.program, uTexUnitPrefix + texUnitID);
Module.ctx.uniform1i(texSamplerLoc, texUnitID);
}
}
Module.ctx.useProgram(prevBoundProg);
}
this.textureMatrixLocations = [];
for (var i = 0; i < GL.immediate.MAX_TEXTURES; i++) {
this.textureMatrixLocations[i] = Module.ctx.getUniformLocation(this.program, 'u_textureMatrix' + i);
}
this.colorLocation = Module.ctx.getAttribLocation(this.program, 'a_color');
this.normalLocation = Module.ctx.getAttribLocation(this.program, 'a_normal');
this.modelViewLocation = Module.ctx.getUniformLocation(this.program, 'u_modelView');
this.projectionLocation = Module.ctx.getUniformLocation(this.program, 'u_projection');
this.hasTextures = hasTextures;
this.hasNormal = normalSize > 0 && this.normalLocation >= 0;
this.hasColor = (this.colorLocation === 0) || this.colorLocation > 0;
this.floatType = Module.ctx.FLOAT; // minor optimization
this.fogColorLocation = Module.ctx.getUniformLocation(this.program, 'u_fogColor');
this.fogEndLocation = Module.ctx.getUniformLocation(this.program, 'u_fogEnd');
this.fogScaleLocation = Module.ctx.getUniformLocation(this.program, 'u_fogScale');
this.fogDensityLocation = Module.ctx.getUniformLocation(this.program, 'u_fogDensity');
this.hasFog = !!(this.fogColorLocation || this.fogEndLocation ||
this.fogScaleLocation || this.fogDensityLocation);
},
prepare: function() {
// Calculate the array buffer
var arrayBuffer;
if (!GL.currArrayBuffer) {
var start = GL.immediate.firstVertex*GL.immediate.stride;
var end = GL.immediate.lastVertex*GL.immediate.stride;
assert(end <= GL.MAX_TEMP_BUFFER_SIZE, 'too much vertex data');
arrayBuffer = GL.tempVertexBuffers[GL.tempBufferIndexLookup[end]];
// TODO: consider using the last buffer we bound, if it was larger. downside is larger buffer, but we might avoid rebinding and preparing
} else {
arrayBuffer = GL.currArrayBuffer;
}
// If the array buffer is unchanged and the renderer as well, then we can avoid all the work here
// XXX We use some heuristics here, and this may not work in all cases. Try disabling GL_UNSAFE_OPTS if you
// have odd glitches
#if GL_UNSAFE_OPTS
var lastRenderer = GL.immediate.lastRenderer;
var canSkip = this == lastRenderer &&
arrayBuffer == GL.immediate.lastArrayBuffer &&
(GL.currProgram || this.program) == GL.immediate.lastProgram &&
GL.immediate.stride == GL.immediate.lastStride &&
!GL.immediate.matricesModified;
if (!canSkip && lastRenderer) lastRenderer.cleanup();
#endif
if (!GL.currArrayBuffer) {
// Bind the array buffer and upload data after cleaning up the previous renderer
#if GL_UNSAFE_OPTS
// Potentially unsafe, since lastArrayBuffer might not reflect the true array buffer in code that mixes immediate/non-immediate
if (arrayBuffer != GL.immediate.lastArrayBuffer) {
#endif
Module.ctx.bindBuffer(Module.ctx.ARRAY_BUFFER, arrayBuffer);
#if GL_UNSAFE_OPTS
}
#endif
Module.ctx.bufferSubData(Module.ctx.ARRAY_BUFFER, start, GL.immediate.vertexData.subarray(start >> 2, end >> 2));
}
#if GL_UNSAFE_OPTS
if (canSkip) return;
GL.immediate.lastRenderer = this;
GL.immediate.lastArrayBuffer = arrayBuffer;
GL.immediate.lastProgram = GL.currProgram || this.program;
GL.immediate.lastStride == GL.immediate.stride;
GL.immediate.matricesModified = false;
#endif
if (!GL.currProgram) {
Module.ctx.useProgram(this.program);
GL.immediate.fixedFunctionProgram = this.program;
}
if (this.modelViewLocation) Module.ctx.uniformMatrix4fv(this.modelViewLocation, false, GL.immediate.matrix['m']);
if (this.projectionLocation) Module.ctx.uniformMatrix4fv(this.projectionLocation, false, GL.immediate.matrix['p']);
var clientAttributes = GL.immediate.clientAttributes;
Module.ctx.vertexAttribPointer(this.positionLocation, positionSize, positionType, false,
GL.immediate.stride, clientAttributes[GL.immediate.VERTEX].offset);
Module.ctx.enableVertexAttribArray(this.positionLocation);
if (this.hasTextures) {
//for (var i = 0; i < this.usedTexUnitList.length; i++) {
// var texUnitID = this.usedTexUnitList[i];
for (var i = 0; i < GL.immediate.MAX_TEXTURES; i++) {
var texUnitID = i;
var attribLoc = this.texCoordLocations[texUnitID];
if (attribLoc === undefined || attribLoc < 0) continue;
if (texUnitID < textureSizes.length && textureSizes[texUnitID]) {
Module.ctx.vertexAttribPointer(attribLoc, textureSizes[texUnitID], textureTypes[texUnitID], false,
GL.immediate.stride, GL.immediate.clientAttributes[GL.immediate.TEXTURE0 + texUnitID].offset);
Module.ctx.enableVertexAttribArray(attribLoc);
} else {
// These two might be dangerous, but let's try them.
Module.ctx.vertexAttrib4f(attribLoc, 0, 0, 0, 1);
Module.ctx.disableVertexAttribArray(attribLoc);
}
}
for (var i = 0; i < GL.immediate.MAX_TEXTURES; i++) {
if (this.textureMatrixLocations[i]) { // XXX might we need this even without the condition we are currently in?
Module.ctx.uniformMatrix4fv(this.textureMatrixLocations[i], false, GL.immediate.matrix['t' + i]);
}
}
}
if (colorSize) {
Module.ctx.vertexAttribPointer(this.colorLocation, colorSize, colorType, true,
GL.immediate.stride, clientAttributes[GL.immediate.COLOR].offset);
Module.ctx.enableVertexAttribArray(this.colorLocation);
} else if (this.hasColor) {
Module.ctx.disableVertexAttribArray(this.colorLocation);
Module.ctx.vertexAttrib4fv(this.colorLocation, GL.immediate.clientColor);
}
if (this.hasNormal) {
Module.ctx.vertexAttribPointer(this.normalLocation, normalSize, normalType, true,
GL.immediate.stride, clientAttributes[GL.immediate.NORMAL].offset);
Module.ctx.enableVertexAttribArray(this.normalLocation);
}
if (this.hasFog) {
if (this.fogColorLocation) Module.ctx.uniform4fv(this.fogColorLocation, GLEmulation.fogColor);
if (this.fogEndLocation) Module.ctx.uniform1f(this.fogEndLocation, GLEmulation.fogEnd);
if (this.fogScaleLocation) Module.ctx.uniform1f(this.fogScaleLocation, 1/(GLEmulation.fogEnd - GLEmulation.fogStart));
if (this.fogDensityLocation) Module.ctx.uniform1f(this.fogDensityLocation, GLEmulation.fogDensity);
}
},
cleanup: function() {
Module.ctx.disableVertexAttribArray(this.positionLocation);
if (this.hasTextures) {
for (var i = 0; i < textureSizes.length; i++) {
if (textureSizes[i] && this.texCoordLocations[i] >= 0) {
Module.ctx.disableVertexAttribArray(this.texCoordLocations[i]);
}
}
}
if (this.hasColor) {
Module.ctx.disableVertexAttribArray(this.colorLocation);
}
if (this.hasNormal) {
Module.ctx.disableVertexAttribArray(this.normalLocation);
}
if (!GL.currProgram) {
Module.ctx.useProgram(null);
}
if (!GL.currArrayBuffer) {
Module.ctx.bindBuffer(Module.ctx.ARRAY_BUFFER, null);
}
#if GL_UNSAFE_OPTS
GL.immediate.lastRenderer = null;
GL.immediate.lastArrayBuffer = null;
GL.immediate.lastProgram = null;
#endif
GL.immediate.matricesModified = true;
}
};
ret.init();
return ret;
},
setupFuncs: function() {
// Replace some functions with immediate-mode aware versions. If there are no client
// attributes enabled, and we use webgl-friendly modes (no GL_QUADS), then no need
// for emulation
_glDrawArrays = function(mode, first, count) {
if (GL.immediate.totalEnabledClientAttributes == 0 && mode <= 6) {
Module.ctx.drawArrays(mode, first, count);
return;
}
GL.immediate.prepareClientAttributes(count, false);
GL.immediate.mode = mode;
if (!GL.currArrayBuffer) {
GL.immediate.vertexData = {{{ makeHEAPView('F32', 'GL.immediate.vertexPointer', 'GL.immediate.vertexPointer + (first+count)*GL.immediate.stride') }}}; // XXX assuming float
GL.immediate.firstVertex = first;
GL.immediate.lastVertex = first + count;
}
GL.immediate.flush(null, first);
GL.immediate.mode = -1;
};
_glDrawElements = function(mode, count, type, indices, start, end) { // start, end are given if we come from glDrawRangeElements
if (GL.immediate.totalEnabledClientAttributes == 0 && mode <= 6 && GL.currElementArrayBuffer) {
Module.ctx.drawElements(mode, count, type, indices);
return;
}
if (!GL.currElementArrayBuffer) {
assert(type == Module.ctx.UNSIGNED_SHORT); // We can only emulate buffers of this kind, for now
}
#if ASSERTIONS
console.log("DrawElements doesn't actually prepareClientAttributes properly.");
#endif
GL.immediate.prepareClientAttributes(count, false);
GL.immediate.mode = mode;
if (!GL.currArrayBuffer) {
GL.immediate.firstVertex = end ? start : TOTAL_MEMORY; // if we don't know the start, set an invalid value and we will calculate it later from the indices
GL.immediate.lastVertex = end ? end+1 : 0;
GL.immediate.vertexData = {{{ makeHEAPView('F32', 'GL.immediate.vertexPointer', '(end ? GL.immediate.vertexPointer + (end+1)*GL.immediate.stride : TOTAL_MEMORY)') }}}; // XXX assuming float
}
GL.immediate.flush(count, 0, indices);
GL.immediate.mode = -1;
};
// TexEnv stuff needs to be prepared early, so do it here.
// init() is too late for -O2, since it freezes the GL functions
// by that point.
GL.immediate.MapTreeLib = GL.immediate.spawnMapTreeLib();
GL.immediate.spawnMapTreeLib = null;
GL.immediate.TexEnvJIT = GL.immediate.spawnTexEnvJIT();
GL.immediate.spawnTexEnvJIT = null;
GL.immediate.setupHooks();
},
setupHooks: function() {
if (!GLEmulation.hasRunInit) {
GLEmulation.init();
}
var glActiveTexture = _glActiveTexture;
_glActiveTexture = function(texture) {
GL.immediate.TexEnvJIT.hook_activeTexture(texture);
glActiveTexture(texture);
};
var glEnable = _glEnable;
_glEnable = function(cap) {
GL.immediate.TexEnvJIT.hook_enable(cap);
glEnable(cap);
};
var glDisable = _glDisable;
_glDisable = function(cap) {
GL.immediate.TexEnvJIT.hook_disable(cap);
glDisable(cap);
};
var glTexEnvf = (typeof(_glTexEnvf) != 'undefined') ? _glTexEnvf : function(){};
_glTexEnvf = function(target, pname, param) {
GL.immediate.TexEnvJIT.hook_texEnvf(target, pname, param);
// Don't call old func, since we are the implementor.
//glTexEnvf(target, pname, param);
};
var glTexEnvi = (typeof(_glTexEnvi) != 'undefined') ? _glTexEnvi : function(){};
_glTexEnvi = function(target, pname, param) {
GL.immediate.TexEnvJIT.hook_texEnvi(target, pname, param);
// Don't call old func, since we are the implementor.
//glTexEnvi(target, pname, param);
};
var glTexEnvfv = (typeof(_glTexEnvfv) != 'undefined') ? _glTexEnvfv : function(){};
_glTexEnvfv = function(target, pname, param) {
GL.immediate.TexEnvJIT.hook_texEnvfv(target, pname, param);
// Don't call old func, since we are the implementor.
//glTexEnvfv(target, pname, param);
};
var glGetIntegerv = _glGetIntegerv;
_glGetIntegerv = function(pname, params) {
switch (pname) {
case 0x8B8D: { // GL_CURRENT_PROGRAM
// Just query directly so we're working with WebGL objects.
var cur = Module.ctx.getParameter(Module.ctx.CURRENT_PROGRAM);
if (cur == GL.immediate.fixedFunctionProgram) {
// Pretend we're not using a program.
{{{ makeSetValue('params', '0', '0', 'i32') }}};
return;
}
break;
}
}
glGetIntegerv(pname, params);
};
},
// Main functions
initted: false,
init: function() {
Module.printErr('WARNING: using emscripten GL immediate mode emulation. This is very limited in what it supports');
GL.immediate.initted = true;
if (!Module.useWebGL) return; // a 2D canvas may be currently used TODO: make sure we are actually called in that case
this.TexEnvJIT.init(Module.ctx);
GL.immediate.MAX_TEXTURES = Module.ctx.getParameter(Module.ctx.MAX_TEXTURE_IMAGE_UNITS);
GL.immediate.NUM_ATTRIBUTES = GL.immediate.TEXTURE0 + GL.immediate.MAX_TEXTURES;
GL.immediate.clientAttributes = [];
for (var i = 0; i < GL.immediate.NUM_ATTRIBUTES; i++) {
GL.immediate.clientAttributes.push({});
}
this.matrixStack['m'] = [];
this.matrixStack['p'] = [];
for (var i = 0; i < GL.immediate.MAX_TEXTURES; i++) {
this.matrixStack['t' + i] = [];
}
// Initialize matrix library
GL.immediate.matrix['m'] = GL.immediate.matrix.lib.mat4.create();
GL.immediate.matrix.lib.mat4.identity(GL.immediate.matrix['m']);
GL.immediate.matrix['p'] = GL.immediate.matrix.lib.mat4.create();
GL.immediate.matrix.lib.mat4.identity(GL.immediate.matrix['p']);
for (var i = 0; i < GL.immediate.MAX_TEXTURES; i++) {
GL.immediate.matrix['t' + i] = GL.immediate.matrix.lib.mat4.create();
}
// Renderer cache
this.rendererCache = this.MapTreeLib.create();
// Buffers for data
this.tempData = new Float32Array(GL.MAX_TEMP_BUFFER_SIZE >> 2);
this.indexData = new Uint16Array(GL.MAX_TEMP_BUFFER_SIZE >> 1);
this.vertexDataU8 = new Uint8Array(this.tempData.buffer);
GL.generateTempBuffers(true);
this.clientColor = new Float32Array([1, 1, 1, 1]);
},
// Prepares and analyzes client attributes.
// Modifies liveClientAttributes, stride, vertexPointer, vertexCounter
// count: number of elements we will draw
// beginEnd: whether we are drawing the results of a begin/end block
prepareClientAttributes: function(count, beginEnd) {
// If no client attributes were modified since we were last called, do nothing. Note that this
// does not work for glBegin/End, where we generate renderer components dynamically and then
// disable them ourselves, but it does help with glDrawElements/Arrays.
if (!this.modifiedClientAttributes) {
return;
}
this.modifiedClientAttributes = false;
var stride = 0, start;
var attributes = GL.immediate.liveClientAttributes;
attributes.length = 0;
for (var i = 0; i < GL.immediate.NUM_ATTRIBUTES; i++) {
if (GL.immediate.enabledClientAttributes[i]) attributes.push(GL.immediate.clientAttributes[i]);
}
attributes.sort(function(x, y) { return !x ? (!y ? 0 : 1) : (!y ? -1 : (x.pointer - y.pointer)) });
start = GL.currArrayBuffer ? 0 : attributes[0].pointer;
var multiStrides = false;
for (var i = 0; i < attributes.length; i++) {
var attribute = attributes[i];
if (!attribute) break;
if (stride != 0 && stride != attribute.stride) multiStrides = true;
if (attribute.stride) stride = attribute.stride;
}
if (multiStrides) stride = 0; // we will need to restride
var bytes = 0; // total size in bytes
if (!stride && !beginEnd) {
// beginEnd can not have stride in the attributes, that is fine. otherwise,
// no stride means that all attributes are in fact packed. to keep the rest of
// our emulation code simple, we perform unpacking/restriding here. this adds overhead, so
// it is a good idea to not hit this!
#if ASSERTIONS
Runtime.warnOnce('Unpacking/restriding attributes, this is slow and dangerous');
#endif
if (!GL.immediate.restrideBuffer) GL.immediate.restrideBuffer = _malloc(GL.MAX_TEMP_BUFFER_SIZE);
start = GL.immediate.restrideBuffer;
#if ASSERTIONS
assert(start % 4 == 0);
#endif
// calculate restrided offsets and total size
for (var i = 0; i < attributes.length; i++) {
var attribute = attributes[i];
if (!attribute) break;
var size = attribute.size * GL.byteSizeByType[attribute.type - GL.byteSizeByTypeRoot];
if (size % 4 != 0) size += 4 - (size % 4); // align everything
attribute.offset = bytes;
bytes += size;
}
#if ASSERTIONS
assert(count*bytes <= GL.MAX_TEMP_BUFFER_SIZE);
#endif
// copy out the data (we need to know the stride for that, and define attribute.pointer
for (var i = 0; i < attributes.length; i++) {
var attribute = attributes[i];
if (!attribute) break;
var size4 = Math.floor((attribute.size * GL.byteSizeByType[attribute.type - GL.byteSizeByTypeRoot])/4);
for (var j = 0; j < count; j++) {
for (var k = 0; k < size4; k++) { // copy in chunks of 4 bytes, our alignment makes this possible
HEAP32[((start + attribute.offset + bytes*j)>>2) + k] = HEAP32[(attribute.pointer>>2) + j*size4 + k];
}
}
attribute.pointer = start + attribute.offset;
}
} else {
// normal situation, everything is strided and in the same buffer
for (var i = 0; i < attributes.length; i++) {
var attribute = attributes[i];
if (!attribute) break;
attribute.offset = attribute.pointer - start;
if (attribute.offset > bytes) { // ensure we start where we should
assert((attribute.offset - bytes)%4 == 0); // XXX assuming 4-alignment
bytes += attribute.offset - bytes;
}
bytes += attribute.size * GL.byteSizeByType[attribute.type - GL.byteSizeByTypeRoot];
if (bytes % 4 != 0) bytes += 4 - (bytes % 4); // XXX assuming 4-alignment
}
assert(beginEnd || bytes <= stride); // if not begin-end, explicit stride should make sense with total byte size
if (bytes < stride) { // ensure the size is that of the stride
bytes = stride;
}
}
GL.immediate.stride = bytes;
if (!beginEnd) {
bytes *= count;
if (!GL.currArrayBuffer) {
GL.immediate.vertexPointer = start;
}
GL.immediate.vertexCounter = bytes / 4; // XXX assuming float
}
},
flush: function(numProvidedIndexes, startIndex, ptr) {
#if ASSERTIONS
assert(numProvidedIndexes >= 0 || !numProvidedIndexes);
#endif
startIndex = startIndex || 0;
ptr = ptr || 0;
var renderer = this.getRenderer();
// Generate index data in a format suitable for GLES 2.0/WebGL
var numVertexes = 4 * this.vertexCounter / GL.immediate.stride;
assert(numVertexes % 1 == 0, "`numVertexes` must be an integer.");
var emulatedElementArrayBuffer = false;
var numIndexes = 0;
if (numProvidedIndexes) {
numIndexes = numProvidedIndexes;
if (!GL.currArrayBuffer && GL.immediate.firstVertex > GL.immediate.lastVertex) {
// Figure out the first and last vertex from the index data
assert(!GL.currElementArrayBuffer); // If we are going to upload array buffer data, we need to find which range to
// upload based on the indices. If they are in a buffer on the GPU, that is very
// inconvenient! So if you do not have an array buffer, you should also not have
// an element array buffer. But best is to use both buffers!
for (var i = 0; i < numProvidedIndexes; i++) {
var currIndex = {{{ makeGetValue('ptr', 'i*2', 'i16', null, 1) }}};
GL.immediate.firstVertex = Math.min(GL.immediate.firstVertex, currIndex);
GL.immediate.lastVertex = Math.max(GL.immediate.lastVertex, currIndex+1);
}
}
if (!GL.currElementArrayBuffer) {
// If no element array buffer is bound, then indices is a literal pointer to clientside data
assert(numProvidedIndexes << 1 <= GL.MAX_TEMP_BUFFER_SIZE, 'too many immediate mode indexes (a)');
var indexBuffer = GL.tempIndexBuffers[GL.tempBufferIndexLookup[numProvidedIndexes << 1]];
Module.ctx.bindBuffer(Module.ctx.ELEMENT_ARRAY_BUFFER, indexBuffer);
Module.ctx.bufferSubData(Module.ctx.ELEMENT_ARRAY_BUFFER, 0, {{{ makeHEAPView('U16', 'ptr', 'ptr + (numProvidedIndexes << 1)') }}});
ptr = 0;
emulatedElementArrayBuffer = true;
}
} else if (GL.immediate.mode > 6) { // above GL_TRIANGLE_FAN are the non-GL ES modes
if (GL.immediate.mode != 7) throw 'unsupported immediate mode ' + GL.immediate.mode; // GL_QUADS
// GL.immediate.firstVertex is the first vertex we want. Quad indexes are in the pattern
// 0 1 2, 0 2 3, 4 5 6, 4 6 7, so we need to look at index firstVertex * 1.5 to see it.
// Then since indexes are 2 bytes each, that means 3
assert(GL.immediate.firstVertex % 4 == 0);
ptr = GL.immediate.firstVertex*3;
var numQuads = numVertexes / 4;
numIndexes = numQuads * 6; // 0 1 2, 0 2 3 pattern
assert(ptr + (numIndexes << 1) <= GL.MAX_TEMP_BUFFER_SIZE, 'too many immediate mode indexes (b)');
Module.ctx.bindBuffer(Module.ctx.ELEMENT_ARRAY_BUFFER, GL.tempQuadIndexBuffer);
emulatedElementArrayBuffer = true;
}
renderer.prepare();
if (numIndexes) {
Module.ctx.drawElements(Module.ctx.TRIANGLES, numIndexes, Module.ctx.UNSIGNED_SHORT, ptr);
} else {
Module.ctx.drawArrays(GL.immediate.mode, startIndex, numVertexes);
}
if (emulatedElementArrayBuffer) {
Module.ctx.bindBuffer(Module.ctx.ELEMENT_ARRAY_BUFFER, GL.buffers[GL.currElementArrayBuffer] || null);
}
#if GL_UNSAFE_OPTS == 0
renderer.cleanup();
#endif
}
},
$GLImmediateSetup__deps: ['$GLImmediate', function() { return 'GL.immediate = GLImmediate; GL.immediate.matrix.lib = ' + read('gl-matrix.js') + ';\n' }],
$GLImmediateSetup: {},
glBegin__deps: ['$GLImmediateSetup'],
glBegin: function(mode) {
// Push the old state:
GL.immediate.enabledClientAttributes_preBegin = GL.immediate.enabledClientAttributes;
GL.immediate.enabledClientAttributes = [];
GL.immediate.clientAttributes_preBegin = GL.immediate.clientAttributes;
GL.immediate.clientAttributes = []
for (var i = 0; i < GL.immediate.clientAttributes_preBegin.length; i++) {
GL.immediate.clientAttributes.push({});
}
GL.immediate.mode = mode;
GL.immediate.vertexCounter = 0;
var components = GL.immediate.rendererComponents = [];
for (var i = 0; i < GL.immediate.NUM_ATTRIBUTES; i++) {
components[i] = 0;
}
GL.immediate.rendererComponentPointer = 0;
GL.immediate.vertexData = GL.immediate.tempData;
},
glEnd: function() {
GL.immediate.prepareClientAttributes(GL.immediate.rendererComponents[GL.immediate.VERTEX], true);
GL.immediate.firstVertex = 0;
GL.immediate.lastVertex = GL.immediate.vertexCounter / (GL.immediate.stride >> 2);
GL.immediate.flush();
GL.immediate.disableBeginEndClientAttributes();
GL.immediate.mode = -1;
// Pop the old state:
GL.immediate.enabledClientAttributes = GL.immediate.enabledClientAttributes_preBegin;
GL.immediate.clientAttributes = GL.immediate.clientAttributes_preBegin;
GL.immediate.modifiedClientAttributes = true;
},
glVertex3f: function(x, y, z) {
#if ASSERTIONS
assert(GL.immediate.mode >= 0); // must be in begin/end
#endif
GL.immediate.vertexData[GL.immediate.vertexCounter++] = x;
GL.immediate.vertexData[GL.immediate.vertexCounter++] = y;
GL.immediate.vertexData[GL.immediate.vertexCounter++] = z || 0;
#if ASSERTIONS
assert(GL.immediate.vertexCounter << 2 < GL.MAX_TEMP_BUFFER_SIZE);
#endif
GL.immediate.addRendererComponent(GL.immediate.VERTEX, 3, Module.ctx.FLOAT);
},
glVertex2f: 'glVertex3f',
glVertex3fv__deps: ['glVertex3f'],
glVertex3fv: function(p) {
_glVertex3f({{{ makeGetValue('p', '0', 'float') }}}, {{{ makeGetValue('p', '4', 'float') }}}, {{{ makeGetValue('p', '8', 'float') }}});
},
glVertex2fv__deps: ['glVertex3f'],
glVertex2fv: function(p) {
_glVertex3f({{{ makeGetValue('p', '0', 'float') }}}, {{{ makeGetValue('p', '4', 'float') }}}, 0);
},
glVertex3i: 'glVertex3f',
glVertex2i: 'glVertex3f',
glTexCoord2i: function(u, v) {
#if ASSERTIONS
assert(GL.immediate.mode >= 0); // must be in begin/end
#endif
GL.immediate.vertexData[GL.immediate.vertexCounter++] = u;
GL.immediate.vertexData[GL.immediate.vertexCounter++] = v;
GL.immediate.addRendererComponent(GL.immediate.TEXTURE0, 2, Module.ctx.FLOAT);
},
glTexCoord2f: 'glTexCoord2i',
glTexCoord2fv__deps: ['glTexCoord2i'],
glTexCoord2fv: function(v) {
_glTexCoord2i({{{ makeGetValue('v', '0', 'float') }}}, {{{ makeGetValue('v', '4', 'float') }}});
},
glTexCoord4f: function() { throw 'glTexCoord4f: TODO' },
glColor4f: function(r, g, b, a) {
r = Math.max(Math.min(r, 1), 0);
g = Math.max(Math.min(g, 1), 0);
b = Math.max(Math.min(b, 1), 0);
a = Math.max(Math.min(a, 1), 0);
// TODO: make ub the default, not f, save a few mathops
if (GL.immediate.mode >= 0) {
var start = GL.immediate.vertexCounter << 2;
GL.immediate.vertexDataU8[start + 0] = r * 255;
GL.immediate.vertexDataU8[start + 1] = g * 255;
GL.immediate.vertexDataU8[start + 2] = b * 255;
GL.immediate.vertexDataU8[start + 3] = a * 255;
GL.immediate.vertexCounter++;
GL.immediate.addRendererComponent(GL.immediate.COLOR, 4, Module.ctx.UNSIGNED_BYTE);
} else {
GL.immediate.clientColor[0] = r;
GL.immediate.clientColor[1] = g;
GL.immediate.clientColor[2] = b;
GL.immediate.clientColor[3] = a;
}
},
glColor4d: 'glColor4f',
glColor4ub__deps: ['glColor4f'],
glColor4ub: function(r, g, b, a) {
_glColor4f((r&255)/255, (g&255)/255, (b&255)/255, (a&255)/255);
},
glColor4us__deps: ['glColor4f'],
glColor4us: function(r, g, b, a) {
_glColor4f((r&65535)/65535, (g&65535)/65535, (b&65535)/65535, (a&65535)/65535);
},
glColor4ui__deps: ['glColor4f'],
glColor4ui: function(r, g, b, a) {
_glColor4f((r>>>0)/4294967295, (g>>>0)/4294967295, (b>>>0)/4294967295, (a>>>0)/4294967295);
},
glColor3f__deps: ['glColor4f'],
glColor3f: function(r, g, b) {
_glColor4f(r, g, b, 1);
},
glColor3d: 'glColor3f',
glColor3ub__deps: ['glColor4ub'],
glColor3ub: function(r, g, b) {
_glColor4ub(r, g, b, 255);
},
glColor3us__deps: ['glColor4us'],
glColor3us: function(r, g, b) {
_glColor4us(r, g, b, 65535);
},
glColor3ui__deps: ['glColor4ui'],
glColor3ui: function(r, g, b) {
_glColor4ui(r, g, b, 4294967295);
},
glColor3ubv__deps: ['glColor3ub'],
glColor3ubv: function(p) {
_glColor3ub({{{ makeGetValue('p', '0', 'i8') }}}, {{{ makeGetValue('p', '1', 'i8') }}}, {{{ makeGetValue('p', '2', 'i8') }}});
},
glColor3usv__deps: ['glColor3us'],
glColor3usv: function(p) {
_glColor3us({{{ makeGetValue('p', '0', 'i16') }}}, {{{ makeGetValue('p', '2', 'i16') }}}, {{{ makeGetValue('p', '4', 'i16') }}});
},
glColor3uiv__deps: ['glColor3ui'],
glColor3uiv: function(p) {
_glColor3ui({{{ makeGetValue('p', '0', 'i32') }}}, {{{ makeGetValue('p', '4', 'i32') }}}, {{{ makeGetValue('p', '8', 'i32') }}});
},
glColor3fv__deps: ['glColor3f'],
glColor3fv: function(p) {
_glColor3f({{{ makeGetValue('p', '0', 'float') }}}, {{{ makeGetValue('p', '4', 'float') }}}, {{{ makeGetValue('p', '8', 'float') }}});
},
glColor4fv__deps: ['glColor4f'],
glColor4fv: function(p) {
_glColor4f({{{ makeGetValue('p', '0', 'float') }}}, {{{ makeGetValue('p', '4', 'float') }}}, {{{ makeGetValue('p', '8', 'float') }}}, {{{ makeGetValue('p', '12', 'float') }}});
},
glColor4ubv: function() { throw 'glColor4ubv not implemented' },
glFogf: function(pname, param) { // partial support, TODO
switch(pname) {
case 0x0B63: // GL_FOG_START
GLEmulation.fogStart = param; break;
case 0x0B64: // GL_FOG_END
GLEmulation.fogEnd = param; break;
case 0x0B62: // GL_FOG_DENSITY
GLEmulation.fogDensity = param; break;
case 0x0B65: // GL_FOG_MODE
switch (param) {
case 0x0801: // GL_EXP2
case 0x2601: // GL_LINEAR
GLEmulation.fogMode = param; break;
default: // default to GL_EXP
GLEmulation.fogMode = 0x0800 /* GL_EXP */; break;
}
break;
}
},
glFogi__deps: ['glFogf'],
glFogi: function(pname, param) {
return _glFogf(pname, param);
},
glFogfv__deps: ['glFogf'],
glFogfv: function(pname, param) { // partial support, TODO
switch(pname) {
case 0x0B66: // GL_FOG_COLOR
GLEmulation.fogColor[0] = {{{ makeGetValue('param', '0', 'float') }}};
GLEmulation.fogColor[1] = {{{ makeGetValue('param', '4', 'float') }}};
GLEmulation.fogColor[2] = {{{ makeGetValue('param', '8', 'float') }}};
GLEmulation.fogColor[3] = {{{ makeGetValue('param', '12', 'float') }}};
break;
case 0x0B63: // GL_FOG_START
case 0x0B64: // GL_FOG_END
_glFogf(pname, {{{ makeGetValue('param', '0', 'float') }}}); break;
}
},
glFogiv__deps: ['glFogf'],
glFogiv: function(pname, param) {
switch(pname) {
case 0x0B66: // GL_FOG_COLOR
GLEmulation.fogColor[0] = ({{{ makeGetValue('param', '0', 'i32') }}}/2147483647)/2.0+0.5;
GLEmulation.fogColor[1] = ({{{ makeGetValue('param', '4', 'i32') }}}/2147483647)/2.0+0.5;
GLEmulation.fogColor[2] = ({{{ makeGetValue('param', '8', 'i32') }}}/2147483647)/2.0+0.5;
GLEmulation.fogColor[3] = ({{{ makeGetValue('param', '12', 'i32') }}}/2147483647)/2.0+0.5;
break;
default:
_glFogf(pname, {{{ makeGetValue('param', '0', 'i32') }}}); break;
}
},
glFogx: 'glFogi',
glFogxv: 'glFogiv',
glPolygonMode: function(){}, // TODO
glAlphaFunc: function(){}, // TODO
glNormal3f: function(){}, // TODO
// Additional non-GLES rendering calls
glDrawRangeElements__sig: 'viiiiii',
glDrawRangeElements: function(mode, start, end, count, type, indices) {
_glDrawElements(mode, count, type, indices, start, end);
},
// ClientState/gl*Pointer
glEnableClientState: function(cap, disable) {
var attrib = GLEmulation.getAttributeFromCapability(cap);
if (attrib === null) {
#if ASSERTIONS
Module.printErr('WARNING: unhandled clientstate: ' + cap);
#endif
return;
}
if (disable && GL.immediate.enabledClientAttributes[attrib]) {
GL.immediate.enabledClientAttributes[attrib] = false;
GL.immediate.totalEnabledClientAttributes--;
if (GLEmulation.currentVao) delete GLEmulation.currentVao.enabledClientStates[cap];
} else if (!disable && !GL.immediate.enabledClientAttributes[attrib]) {
GL.immediate.enabledClientAttributes[attrib] = true;
GL.immediate.totalEnabledClientAttributes++;
if (GLEmulation.currentVao) GLEmulation.currentVao.enabledClientStates[cap] = 1;
}
GL.immediate.modifiedClientAttributes = true;
},
glDisableClientState: function(cap) {
_glEnableClientState(cap, 1);
},
glVertexPointer__deps: ['$GLEmulation'], // if any pointers are used, glVertexPointer must be, and if it is, then we need emulation
glVertexPointer: function(size, type, stride, pointer) {
GL.immediate.setClientAttribute(GL.immediate.VERTEX, size, type, stride, pointer);
},
glTexCoordPointer: function(size, type, stride, pointer) {
GL.immediate.setClientAttribute(GL.immediate.TEXTURE0 + GL.immediate.clientActiveTexture, size, type, stride, pointer);
},
glNormalPointer: function(type, stride, pointer) {
GL.immediate.setClientAttribute(GL.immediate.NORMAL, 3, type, stride, pointer);
},
glColorPointer: function(size, type, stride, pointer) {
GL.immediate.setClientAttribute(GL.immediate.COLOR, size, type, stride, pointer);
},
glClientActiveTexture__sig: 'vi',
glClientActiveTexture: function(texture) {
GL.immediate.clientActiveTexture = texture - 0x84C0; // GL_TEXTURE0
},
// Vertex array object (VAO) support. TODO: when the WebGL extension is popular, use that and remove this code and GL.vaos
glGenVertexArrays__deps: ['$GLEmulation'],
glGenVertexArrays__sig: 'vii',
glGenVertexArrays: function(n, vaos) {
for (var i = 0; i < n; i++) {
var id = GL.getNewId(GLEmulation.vaos);
GLEmulation.vaos[id] = {
id: id,
arrayBuffer: 0,
elementArrayBuffer: 0,
enabledVertexAttribArrays: {},
vertexAttribPointers: {},
enabledClientStates: {},
};
{{{ makeSetValue('vaos', 'i*4', 'id', 'i32') }}};
}
},
glDeleteVertexArrays__sig: 'vii',
glDeleteVertexArrays: function(n, vaos) {
for (var i = 0; i < n; i++) {
var id = {{{ makeGetValue('vaos', 'i*4', 'i32') }}};
GLEmulation.vaos[id] = null;
if (GLEmulation.currentVao && GLEmulation.currentVao.id == id) GLEmulation.currentVao = null;
}
},
glBindVertexArray__sig: 'vi',
glBindVertexArray: function(vao) {
// undo vao-related things, wipe the slate clean, both for vao of 0 or an actual vao
GLEmulation.currentVao = null; // make sure the commands we run here are not recorded
if (GL.immediate.lastRenderer) GL.immediate.lastRenderer.cleanup();
_glBindBuffer(Module.ctx.ARRAY_BUFFER, 0); // XXX if one was there before we were bound?
_glBindBuffer(Module.ctx.ELEMENT_ARRAY_BUFFER, 0);
for (var vaa in GLEmulation.enabledVertexAttribArrays) {
Module.ctx.disableVertexAttribArray(vaa);
}
GLEmulation.enabledVertexAttribArrays = {};
GL.immediate.enabledClientAttributes = [0, 0];
GL.immediate.totalEnabledClientAttributes = 0;
GL.immediate.modifiedClientAttributes = true;
if (vao) {
// replay vao
var info = GLEmulation.vaos[vao];
_glBindBuffer(Module.ctx.ARRAY_BUFFER, info.arrayBuffer); // XXX overwrite current binding?
_glBindBuffer(Module.ctx.ELEMENT_ARRAY_BUFFER, info.elementArrayBuffer);
for (var vaa in info.enabledVertexAttribArrays) {
_glEnableVertexAttribArray(vaa);
}
for (var vaa in info.vertexAttribPointers) {
_glVertexAttribPointer.apply(null, info.vertexAttribPointers[vaa]);
}
for (var attrib in info.enabledClientStates) {
_glEnableClientState(attrib|0);
}
GLEmulation.currentVao = info; // set currentVao last, so the commands we ran here were not recorded
}
},
// OpenGL Immediate Mode matrix routines.
// Note that in the future we might make these available only in certain modes.
glMatrixMode__deps: ['$GL', '$GLImmediateSetup', '$GLEmulation'], // emulation is not strictly needed, this is a workaround
glMatrixMode: function(mode) {
if (mode == 0x1700 /* GL_MODELVIEW */) {
GL.immediate.currentMatrix = 'm';
} else if (mode == 0x1701 /* GL_PROJECTION */) {
GL.immediate.currentMatrix = 'p';
} else if (mode == 0x1702) { // GL_TEXTURE
GL.immediate.useTextureMatrix = true;
GL.immediate.currentMatrix = 't' + GL.immediate.clientActiveTexture;
} else {
throw "Wrong mode " + mode + " passed to glMatrixMode";
}
},
glPushMatrix: function() {
GL.immediate.matricesModified = true;
GL.immediate.matrixStack[GL.immediate.currentMatrix].push(
Array.prototype.slice.call(GL.immediate.matrix[GL.immediate.currentMatrix]));
},
glPopMatrix: function() {
GL.immediate.matricesModified = true;
GL.immediate.matrix[GL.immediate.currentMatrix] = GL.immediate.matrixStack[GL.immediate.currentMatrix].pop();
},
glLoadIdentity__deps: ['$GL', '$GLImmediateSetup'],
glLoadIdentity: function() {
GL.immediate.matricesModified = true;
GL.immediate.matrix.lib.mat4.identity(GL.immediate.matrix[GL.immediate.currentMatrix]);
},
glLoadMatrixd: function(matrix) {
GL.immediate.matricesModified = true;
GL.immediate.matrix.lib.mat4.set({{{ makeHEAPView('F64', 'matrix', 'matrix+' + (16*8)) }}}, GL.immediate.matrix[GL.immediate.currentMatrix]);
},
glLoadMatrixf: function(matrix) {
#if GL_DEBUG
if (GL.debug) Module.printErr('glLoadMatrixf receiving: ' + Array.prototype.slice.call(HEAPF32.subarray(matrix >> 2, (matrix >> 2) + 16)));
#endif
GL.immediate.matricesModified = true;
GL.immediate.matrix.lib.mat4.set({{{ makeHEAPView('F32', 'matrix', 'matrix+' + (16*4)) }}}, GL.immediate.matrix[GL.immediate.currentMatrix]);
},
glLoadTransposeMatrixd: function(matrix) {
GL.immediate.matricesModified = true;
GL.immediate.matrix.lib.mat4.set({{{ makeHEAPView('F64', 'matrix', 'matrix+' + (16*8)) }}}, GL.immediate.matrix[GL.immediate.currentMatrix]);
GL.immediate.matrix.lib.mat4.transpose(GL.immediate.matrix[GL.immediate.currentMatrix]);
},
glLoadTransposeMatrixf: function(matrix) {
GL.immediate.matricesModified = true;
GL.immediate.matrix.lib.mat4.set({{{ makeHEAPView('F32', 'matrix', 'matrix+' + (16*4)) }}}, GL.immediate.matrix[GL.immediate.currentMatrix]);
GL.immediate.matrix.lib.mat4.transpose(GL.immediate.matrix[GL.immediate.currentMatrix]);
},
glMultMatrixd: function(matrix) {
GL.immediate.matricesModified = true;
GL.immediate.matrix.lib.mat4.multiply(GL.immediate.matrix[GL.immediate.currentMatrix],
{{{ makeHEAPView('F64', 'matrix', 'matrix+' + (16*8)) }}});
},
glMultMatrixf: function(matrix) {
GL.immediate.matricesModified = true;
GL.immediate.matrix.lib.mat4.multiply(GL.immediate.matrix[GL.immediate.currentMatrix],
{{{ makeHEAPView('F32', 'matrix', 'matrix+' + (16*4)) }}});
},
glMultTransposeMatrixd: function(matrix) {
GL.immediate.matricesModified = true;
var colMajor = GL.immediate.matrix.lib.mat4.create();
GL.immediate.matrix.lib.mat4.set({{{ makeHEAPView('F64', 'matrix', 'matrix+' + (16*8)) }}}, colMajor);
GL.immediate.matrix.lib.mat4.transpose(colMajor);
GL.immediate.matrix.lib.mat4.multiply(GL.immediate.matrix[GL.immediate.currentMatrix], colMajor);
},
glMultTransposeMatrixf: function(matrix) {
GL.immediate.matricesModified = true;
var colMajor = GL.immediate.matrix.lib.mat4.create();
GL.immediate.matrix.lib.mat4.set({{{ makeHEAPView('F32', 'matrix', 'matrix+' + (16*4)) }}}, colMajor);
GL.immediate.matrix.lib.mat4.transpose(colMajor);
GL.immediate.matrix.lib.mat4.multiply(GL.immediate.matrix[GL.immediate.currentMatrix], colMajor);
},
glFrustum: function(left, right, bottom, top_, nearVal, farVal) {
GL.immediate.matricesModified = true;
GL.immediate.matrix.lib.mat4.multiply(GL.immediate.matrix[GL.immediate.currentMatrix],
GL.immediate.matrix.lib.mat4.frustum(left, right, bottom, top_, nearVal, farVal));
},
glFrustumf: 'glFrustum',
glOrtho: function(left, right, bottom, top_, nearVal, farVal) {
GL.immediate.matricesModified = true;
GL.immediate.matrix.lib.mat4.multiply(GL.immediate.matrix[GL.immediate.currentMatrix],
GL.immediate.matrix.lib.mat4.ortho(left, right, bottom, top_, nearVal, farVal));
},
glOrthof: 'glOrtho',
glScaled: function(x, y, z) {
GL.immediate.matricesModified = true;
GL.immediate.matrix.lib.mat4.scale(GL.immediate.matrix[GL.immediate.currentMatrix], [x, y, z]);
},
glScalef: 'glScaled',
glTranslated: function(x, y, z) {
GL.immediate.matricesModified = true;
GL.immediate.matrix.lib.mat4.translate(GL.immediate.matrix[GL.immediate.currentMatrix], [x, y, z]);
},
glTranslatef: 'glTranslated',
glRotated: function(angle, x, y, z) {
GL.immediate.matricesModified = true;
GL.immediate.matrix.lib.mat4.rotate(GL.immediate.matrix[GL.immediate.currentMatrix], angle*Math.PI/180, [x, y, z]);
},
glRotatef: 'glRotated',
glDrawBuffer: function() { throw 'glDrawBuffer: TODO' },
glReadBuffer: function() { throw 'glReadBuffer: TODO' },
glLightfv: function() { throw 'glLightfv: TODO' },
glLightModelfv: function() { throw 'glLightModelfv: TODO' },
glMaterialfv: function() { throw 'glMaterialfv: TODO' },
glTexGeni: function() { throw 'glTexGeni: TODO' },
glTexGenfv: function() { throw 'glTexGenfv: TODO' },
glTexEnvi: function() { Runtime.warnOnce('glTexEnvi: TODO') },
glTexEnvf: function() { Runtime.warnOnce('glTexEnvf: TODO') },
glTexEnvfv: function() { Runtime.warnOnce('glTexEnvfv: TODO') },
glTexImage1D: function() { throw 'glTexImage1D: TODO' },
glTexCoord3f: function() { throw 'glTexCoord3f: TODO' },
glGetTexLevelParameteriv: function() { throw 'glGetTexLevelParameteriv: TODO' },
glShadeModel: function() { Runtime.warnOnce('TODO: glShadeModel') },
// Open GLES1.1 compatibility
glGenFramebuffersOES : 'glGenFramebuffers',
glGenRenderbuffersOES : 'glGenRenderbuffers',
glBindFramebufferOES : 'glBindFramebuffer',
glBindRenderbufferOES : 'glBindRenderbuffer',
glGetRenderbufferParameterivOES : 'glGetRenderbufferParameteriv',
glFramebufferRenderbufferOES : 'glFramebufferRenderbuffer',
glRenderbufferStorageOES : 'glRenderbufferStorage',
glCheckFramebufferStatusOES : 'glCheckFramebufferStatus',
glDeleteFramebuffersOES : 'glDeleteFramebuffers',
glDeleteRenderbuffersOES : 'glDeleteRenderbuffers',
glGenVertexArraysOES: 'glGenVertexArrays',
glDeleteVertexArraysOES: 'glDeleteVertexArrays',
glBindVertexArrayOES: 'glBindVertexArray',
glFramebufferTexture2DOES: 'glFramebufferTexture2D',
#endif // DISABLE_GL_EMULATION == 0
// GLU
gluPerspective: function(fov, aspect, near, far) {
GL.immediate.matricesModified = true;
GL.immediate.matrix[GL.immediate.currentMatrix] =
GL.immediate.matrix.lib.mat4.perspective(fov, aspect, near, far,
GL.immediate.matrix[GL.immediate.currentMatrix]);
},
gluLookAt: function(ex, ey, ez, cx, cy, cz, ux, uy, uz) {
GL.immediate.matricesModified = true;
GL.immediate.matrix.lib.mat4.lookAt(GL.immediate.matrix[GL.immediate.currentMatrix], [ex, ey, ez],
[cx, cy, cz], [ux, uy, uz]);
},
gluProject: function(objX, objY, objZ, model, proj, view, winX, winY, winZ) {
// The algorithm for this functions comes from Mesa
var inVec = new Float32Array(4);
var outVec = new Float32Array(4);
GL.immediate.matrix.lib.mat4.multiplyVec4({{{ makeHEAPView('F64', 'model', 'model+' + (16*8)) }}},
[objX, objY, objZ, 1.0], outVec);
GL.immediate.matrix.lib.mat4.multiplyVec4({{{ makeHEAPView('F64', 'proj', 'proj+' + (16*8)) }}},
outVec, inVec);
if (inVec[3] == 0.0) {
return 0 /* GL_FALSE */;
}
inVec[0] /= inVec[3];
inVec[1] /= inVec[3];
inVec[2] /= inVec[3];
// Map x, y and z to range 0-1 */
inVec[0] = inVec[0] * 0.5 + 0.5;
inVec[1] = inVec[1] * 0.5 + 0.5;
inVec[2] = inVec[2] * 0.5 + 0.5;
// Map x, y to viewport
inVec[0] = inVec[0] * {{{ makeGetValue('view', 2*4, 'i32') }}} + {{{ makeGetValue('view', 0*4, 'i32') }}};
inVec[1] = inVec[1] * {{{ makeGetValue('view', 3*4, 'i32') }}} + {{{ makeGetValue('view', 1*4, 'i32') }}};
{{{ makeSetValue('winX', '0', 'inVec[0]', 'double') }}};
{{{ makeSetValue('winY', '0', 'inVec[1]', 'double') }}};
{{{ makeSetValue('winZ', '0', 'inVec[2]', 'double') }}};
return 1 /* GL_TRUE */;
},
gluUnProject: function(winX, winY, winZ, model, proj, view, objX, objY, objZ) {
var result = GL.immediate.matrix.lib.mat4.unproject([winX, winY, winZ],
{{{ makeHEAPView('F64', 'model', 'model+' + (16*8)) }}},
{{{ makeHEAPView('F64', 'proj', 'proj+' + (16*8)) }}},
{{{ makeHEAPView('32', 'view', 'view+' + (4*4)) }}});
if (result === null) {
return 0 /* GL_FALSE */;
}
{{{ makeSetValue('objX', '0', 'result[0]', 'double') }}};
{{{ makeSetValue('objY', '0', 'result[1]', 'double') }}};
{{{ makeSetValue('objZ', '0', 'result[2]', 'double') }}};
return 1 /* GL_TRUE */;
},
gluOrtho2D: function(left, right, bottom, top) {
_glOrtho(left, right, bottom, top, -1, 1);
},
// GLES2 emulation
glVertexAttribPointer__sig: 'viiiiii',
glVertexAttribPointer: function(index, size, type, normalized, stride, ptr) {
#if FULL_ES2
var cb = GL.clientBuffers[index];
#if ASSERTIONS
assert(cb, index);
#endif
if (!GL.currArrayBuffer) {
cb.size = size;
cb.type = type;
cb.normalized = normalized;
cb.stride = stride;
cb.ptr = ptr;
cb.clientside = true;
return;
}
cb.clientside = false;
#endif
Module.ctx.vertexAttribPointer(index, size, type, normalized, stride, ptr);
},
glEnableVertexAttribArray__sig: 'vi',
glEnableVertexAttribArray: function(index) {
#if FULL_ES2
var cb = GL.clientBuffers[index];
#if ASSERTIONS
assert(cb, index);
#endif
cb.enabled = true;
#endif
Module.ctx.enableVertexAttribArray(index);
},
glDisableVertexAttribArray__sig: 'vi',
glDisableVertexAttribArray: function(index) {
#if FULL_ES2
var cb = GL.clientBuffers[index];
#if ASSERTIONS
assert(cb, index);
#endif
cb.enabled = false;
#endif
Module.ctx.disableVertexAttribArray(index);
},
glDrawArrays__sig: 'viii',
glDrawArrays: function(mode, first, count) {
#if FULL_ES2
// bind any client-side buffers
GL.preDrawHandleClientVertexAttribBindings(first + count);
#endif
Module.ctx.drawArrays(mode, first, count);
#if FULL_ES2
GL.postDrawHandleClientVertexAttribBindings();
#endif
},
glDrawElements__sig: 'viiii',
glDrawElements: function(mode, count, type, indices) {
#if FULL_ES2
var buf;
if (!GL.currElementArrayBuffer) {
var size = GL.calcBufLength(1, type, 0, count);
buf = GL.tempIndexBuffers[GL.tempBufferIndexLookup[size]];
Module.ctx.bindBuffer(Module.ctx.ELEMENT_ARRAY_BUFFER, buf);
Module.ctx.bufferSubData(Module.ctx.ELEMENT_ARRAY_BUFFER,
0,
HEAPU8.subarray(indices, indices + size));
// the index is now 0
indices = 0;
}
// bind any client-side buffers
GL.preDrawHandleClientVertexAttribBindings(count);
#endif
Module.ctx.drawElements(mode, count, type, indices);
#if FULL_ES2
GL.postDrawHandleClientVertexAttribBindings(count);
if (!GL.currElementArrayBuffer) {
Module.ctx.bindBuffer(Module.ctx.ELEMENT_ARRAY_BUFFER, null);
}
#endif
},
// signatures of simple pass-through functions, see later
glActiveTexture__sig: 'vi',
glCheckFramebufferStatus__sig: 'ii',
glRenderbufferStorage__sig: 'viiii',
glClearStencil__sig: 'vi',
glStencilFunc__sig: 'viii',
glLineWidth__sig: 'vi',
glBlendEquation__sig: 'vi',
glBlendEquationSeparate__sig: 'vii',
glVertexAttrib1f__sig: 'vii',
glVertexAttrib2f__sig: 'viii',
glVertexAttrib3f__sig: 'viiii',
glVertexAttrib4f__sig: 'viiiii',
glCullFace__sig: 'vi',
glBlendFunc__sig: 'vii',
glBlendFuncSeparate__sig: 'viiii',
glPolygonOffset__sig: 'vii',
glColorMask__sig: 'viiii',
glStencilOp__sig: 'viii',
glStencilOpSeparate__sig: 'viiii',
glGenerateMipmap__sig: 'vi',
glHint__sig: 'vii',
glDepthMask__sig: 'vi',
glViewport__sig: 'viiii',
glDepthFunc__sig: 'vi',
glStencilMask__sig: 'vi',
glStencilMaskSeparate__sig: 'vii',
glClearDepthf__sig: 'vi',
glFinish__sig: 'v',
glFlush__sig: 'v',
glClearColor__sig: 'viiii',
glIsEnabled__sig: 'ii',
glGetError__sig: 'i',
glFrontFace__sig: 'vi',
glSampleCoverage__sig: 'vi',
};
// Simple pass-through functions. Starred ones have return values. [X] ones have X in the C name but not in the JS name
[[0, 'getError* finish flush'],
[1, 'clearDepth clearDepth[f] depthFunc enable disable frontFace cullFace clear lineWidth clearStencil depthMask stencilMask checkFramebufferStatus* generateMipmap activeTexture blendEquation sampleCoverage isEnabled*'],
[2, 'blendFunc blendEquationSeparate depthRange depthRange[f] stencilMaskSeparate hint polygonOffset vertexAttrib1f'],
[3, 'texParameteri texParameterf vertexAttrib2f stencilFunc stencilOp'],
[4, 'viewport clearColor scissor vertexAttrib3f colorMask renderbufferStorage blendFuncSeparate blendColor stencilFuncSeparate stencilOpSeparate'],
[5, 'vertexAttrib4f'],
[8, 'copyTexImage2D copyTexSubImage2D']].forEach(function(data) {
var num = data[0];
var names = data[1];
var args = range(num).map(function(i) { return 'x' + i }).join(', ');
var plainStub = '(function(' + args + ') { Module.ctx.NAME(' + args + ') })';
var returnStub = '(function(' + args + ') { return Module.ctx.NAME(' + args + ') })';
var sigEnd = range(num).map(function() { return 'i' }).join('');
names.split(' ').forEach(function(name) {
var stub = plainStub;
var sig;
if (name[name.length-1] == '*') {
name = name.substr(0, name.length-1);
stub = returnStub;
sig = 'i' + sigEnd;
} else {
sig = 'v' + sigEnd;
}
var cName = name;
if (name.indexOf('[') >= 0) {
cName = name.replace('[', '').replace(']', '');
name = cName.substr(0, cName.length-1);
}
var cName = 'gl' + cName[0].toUpperCase() + cName.substr(1);
assert(!(cName in LibraryGL), "Cannot reimplement the existing function " + cName);
LibraryGL[cName] = eval(stub.replace('NAME', name));
if (!LibraryGL[cName + '__sig']) LibraryGL[cName + '__sig'] = sig;
});
});
autoAddDeps(LibraryGL, '$GL');
if (!DISABLE_GL_EMULATION) {
// Emulation requires everything else, potentially
LibraryGL.$GLEmulation__deps = LibraryGL.$GLEmulation__deps.slice(0); // the __deps object is shared
// Create glEmulation_NAME versions of all indexable GL calls, to allow C to implement functions with the same name,
// but still allow the library version to be called via a function pointer (from SDL_GetProcAddress)
for (var func in Functions.getIndex.tentative) {
if (func in Functions.implementedFunctions) {
var funcShort = func.substr(1);
var fullName = '_glRelocate' + func;
var fullNameShort = fullName.substr(1);
LibraryGL[fullNameShort] = LibraryGL[funcShort];
LibraryGL[fullNameShort + '__sig'] = LibraryGL[funcShort + '__sig'];
}
}
var glFuncs = [];
for (var item in LibraryGL) {
if (item != '$GLEmulation' && item.substr(-6) != '__deps' && item.substr(-9) != '__postset' && item.substr(-5) != '__sig' && item.substr(0, 2) == 'gl') {
glFuncs.push(item);
}
}
LibraryGL.$GLEmulation__deps = LibraryGL.$GLEmulation__deps.concat(glFuncs);
LibraryGL.$GLEmulation__deps.push(function() {
for (var func in Functions.getIndex.tentative) {
var fullName;
if (func in Functions.implementedFunctions) {
fullName = '_glRelocate' + func;
} else {
fullName = func;
}
Functions.getIndex(fullName);
Functions.unimplementedFunctions[fullName] = LibraryGL[fullName.substr(1) + '__sig'];
}
});
if (FORCE_GL_EMULATION) {
LibraryGL.glDrawElements__deps = LibraryGL.glDrawElements__deps.concat('$GLEmulation');
LibraryGL.glDrawArrays__deps = LibraryGL.glDrawArrays__deps.concat('$GLEmulation');
}
}
mergeInto(LibraryManager.library, LibraryGL);