aboutsummaryrefslogtreecommitdiff
path: root/src/library_gl.js
diff options
context:
space:
mode:
Diffstat (limited to 'src/library_gl.js')
-rw-r--r--src/library_gl.js554
1 files changed, 417 insertions, 137 deletions
diff --git a/src/library_gl.js b/src/library_gl.js
index 9f88260a..f3cea384 100644
--- a/src/library_gl.js
+++ b/src/library_gl.js
@@ -4,6 +4,7 @@
*/
var LibraryGL = {
+ $GL__postset: 'GL.init()',
$GL: {
#if GL_DEBUG
debug: true,
@@ -21,6 +22,10 @@ var LibraryGL = {
packAlignment: 4, // default alignment is 4 bytes
unpackAlignment: 4, // default alignment is 4 bytes
+ init: function() {
+ Browser.moduleContextCreatedCallbacks.push(GL.initExtensions);
+ },
+
// 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) {
@@ -100,6 +105,79 @@ var LibraryGL = {
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
+ }
+ },
+
+ initExtensions: function() {
+ if (GL.initExtensions.done) return;
+ GL.initExtensions.done = true;
+
+ 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');
}
},
@@ -273,96 +351,41 @@ var LibraryGL = {
}
},
- glCompressedTexImage2D: function(target, level, internalformat, width, height, border, imageSize, data) {
+ 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);
+ Module.ctx['compressedTexImage2D'](target, level, internalFormat, width, height, border, data);
},
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);
+ Module.ctx['compressedTexSubImage2D'](target, level, xoffset, yoffset, width, height, data);
},
- glTexImage2D: function(target, level, internalformat, width, height, border, format, type, pixels) {
+ glTexImage2D: function(target, level, internalFormat, width, height, border, format, type, pixels) {
if (pixels) {
- 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 + ') passed to glTexImage2D';
- }
- 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;
- default:
- throw 'Invalid type (' + type + ') passed to glTexImage2D';
- }
- var bytes = GL.computeImageSize(width, height, sizePerPixel, GL.unpackAlignment);
- pixels = {{{ makeHEAPView('U8', 'pixels', 'pixels+bytes') }}};
+ 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);
+ Module.ctx.texImage2D(target, level, internalFormat, width, height, border, format, type, pixels);
},
glTexSubImage2D: function(target, level, xoffset, yoffset, width, height, format, type, pixels) {
if (pixels) {
- 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 + ') passed to glTexSubImage2D';
- }
- 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;
- default:
- throw 'Invalid type (' + type + ') passed to glTexSubImage2D';
- }
- var bytes = GL.computeImageSize(width, height, sizePerPixel, GL.unpackAlignment);
- pixels = {{{ makeHEAPView('U8', 'pixels', 'pixels+bytes') }}};
+ var data = GL.getTexPixelData(type, format, width, height, pixels, -1);
+ pixels = data.pixels;
} else {
pixels = null;
}
@@ -891,28 +914,62 @@ var LibraryGL = {
$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,
+
init: function() {
+ 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');
- // XXX some of these ignored capabilities may lead to incorrect rendering, if we do not emulate them in shaders
- var ignoredCapabilities = {
- 0x0DE1: 1, // GL_TEXTURE_2D
- 0x0B20: 1, // GL_LINE_SMOOTH
- 0x0B60: 1, // GL_FOG
- 0x8513: 1, // GL_TEXTURE_CUBE_MAP
- 0x0BA1: 1, // GL_NORMALIZE
- 0x0C60: 1, // GL_TEXTURE_GEN_S
- 0x0C61: 1 // GL_TEXTURE_GEN_T
+ // 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
};
_glEnable = function(cap) {
- if (cap in ignoredCapabilities) return;
+ // 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 in validCapabilities)) {
+ return;
+ }
Module.ctx.enable(cap);
};
_glDisable = function(cap) {
- if (cap in ignoredCapabilities) return;
+ if (GL.immediate.lastRenderer) GL.immediate.lastRenderer.cleanup();
+ if (cap == 0x0B60 /* GL_FOG */) {
+ GLEmulation.fogEnabled = false;
+ return;
+ } else if (!(cap in validCapabilities)) {
+ return;
+ }
Module.ctx.disable(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 glGetIntegerv = _glGetIntegerv;
_glGetIntegerv = function(pname, params) {
@@ -942,7 +999,11 @@ var LibraryGL = {
_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'), 'i8', ALLOC_NORMAL);
+ 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_);
};
@@ -986,7 +1047,7 @@ var LibraryGL = {
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][0], u_modelView[1][0], u_modelView[2][0], u_modelView[3][0])'); // XXX extremely inefficient
+ 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');
@@ -1034,9 +1095,10 @@ var LibraryGL = {
source = 'attribute vec3 a_normal; \n' +
source.replace(/gl_Normal/g, 'a_normal');
}
+ // fog
if (source.indexOf('gl_FogFragCoord') >= 0) {
- source = 'varying float v_fogCoord; \n' +
- source.replace(/gl_FogFragCoord/g, 'v_fogCoord');
+ 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++) {
@@ -1049,7 +1111,26 @@ var LibraryGL = {
if (source.indexOf('gl_Color') >= 0) {
source = 'varying vec4 v_color; \n' + source.replace(/gl_Color/g, 'v_color');
}
- source = source.replace(/gl_Fog.color/g, 'vec4(0.0)'); // XXX TODO
+ 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
@@ -1105,6 +1186,21 @@ var LibraryGL = {
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);
@@ -1134,15 +1230,27 @@ var LibraryGL = {
} 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
- {{{ makeSetValue('params', '0', '0', 'float') }}};
+ HEAPF32.set(GLEmulation.fogColor, params >> 2);
} else if (pname == 0x0B63) { // GL_FOG_START
- {{{ makeSetValue('params', '0', '0', 'float') }}};
+ {{{ makeSetValue('params', '0', 'GLEmulation.fogStart', 'float') }}};
} else if (pname == 0x0B64) { // GL_FOG_END
- {{{ makeSetValue('params', '0', '0', 'float') }}};
+ {{{ 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);
+ };
},
getProcAddress: function(name) {
@@ -1269,12 +1377,16 @@ var LibraryGL = {
rendererCache: {},
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, // ""
// The following data structures are used for OpenGL Immediate Mode matrix routines.
matrix: {},
matrixStack: {},
currentMatrix: 'm', // default is modelview
tempMatrix: null,
+ matricesModified: false,
// Clientside attributes
VERTEX: 0,
@@ -1324,6 +1436,7 @@ var LibraryGL = {
tempBufferIndexLookup: null,
tempVertexBuffers: null,
tempIndexBuffers: null,
+ tempQuadIndexBuffer: null,
generateTempBuffers: function() {
function ceilPower2(x) {
@@ -1357,6 +1470,30 @@ var LibraryGL = {
last = size;
}
}
+
+ // GL_QUAD indexes can be precalculated
+ this.tempQuadIndexBuffer = Module.ctx.createBuffer();
+ Module.ctx.bindBuffer(Module.ctx.ELEMENT_ARRAY_BUFFER, this.tempQuadIndexBuffer);
+ var numIndexes = this.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);
},
// Renderers
@@ -1394,7 +1531,14 @@ var LibraryGL = {
if (!cacheItem[attribute.type]) cacheItem[attribute.type] = {};
cacheItem = cacheItem[attribute.type];
}
- if (GL.currProgram) {
+ if (GLEmulation.fogEnabled) {
+ var fogParam = GLEmulation.fogMode;
+ } else {
+ var fogParam = 0; // all valid modes are non-zero
+ }
+ if (!cacheItem[fogParam]) cacheItem[fogParam] = {};
+ cacheItem = cacheItem[fogParam];
+ if (GL.currProgram) { // Note the order here; this one is last, and optional
if (!cacheItem[GL.currProgram]) cacheItem[GL.currProgram] = {};
cacheItem = cacheItem[GL.currProgram];
}
@@ -1446,20 +1590,43 @@ var LibraryGL = {
}
this.program = GL.programs[GL.currProgram];
} 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.
this.vertexShader = Module.ctx.createShader(Module.ctx.VERTEX_SHADER);
var zero = positionSize == 2 ? '0, ' : '';
+ 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;
+ }
+ }
Module.ctx.shaderSource(this.vertexShader, 'attribute vec' + positionSize + ' a_position; \n' +
'attribute vec2 a_texCoord0; \n' +
(hasTextures ? 'varying vec2 v_texCoord; \n' : '') +
'varying vec4 v_color; \n' +
(colorSize ? 'attribute vec4 a_color; \n': 'uniform vec4 u_color; \n') +
+ (GLEmulation.fogEnabled ? 'varying float v_fogFragCoord; \n' : '') +
'uniform mat4 u_modelView; \n' +
'uniform mat4 u_projection; \n' +
'void main() \n' +
'{ \n' +
- ' gl_Position = u_projection * (u_modelView * vec4(a_position, ' + zero + '1.0)); \n' +
+ ' vec4 ecPosition = (u_modelView * vec4(a_position, ' + zero + '1.0)); \n' + // eye-coordinate position
+ ' gl_Position = u_projection * ecPosition; \n' +
(hasTextures ? 'v_texCoord = a_texCoord0; \n' : '') +
(colorSize ? 'v_color = a_color; \n' : 'v_color = u_color; \n') +
+ (GLEmulation.fogEnabled ? 'v_fogFragCoord = abs(ecPosition.z);\n' : '') +
'} \n');
Module.ctx.compileShader(this.vertexShader);
@@ -1468,16 +1635,30 @@ var LibraryGL = {
'varying vec2 v_texCoord; \n' +
'uniform sampler2D u_texture; \n' +
'varying vec4 v_color; \n' +
+ (GLEmulation.fogEnabled ? (
+ 'varying float v_fogFragCoord; \n' +
+ 'uniform vec4 u_fogColor; \n' +
+ 'uniform float u_fogEnd; \n' +
+ 'uniform float u_fogScale; \n' +
+ 'uniform float u_fogDensity; \n' +
+ 'float ffog(in float ecDistance) { \n' +
+ fogFormula +
+ ' fog = clamp(fog, 0.0, 1.0); \n' +
+ ' return fog; \n' +
+ '} \n'
+ ) : '') +
'void main() \n' +
'{ \n' +
(hasTextures ? 'gl_FragColor = v_color * texture2D( u_texture, v_texCoord );\n' :
'gl_FragColor = v_color;\n') +
+ (GLEmulation.fogEnabled ? 'gl_FragColor = vec4(mix(u_fogColor.rgb, gl_FragColor.rgb, ffog(v_fogFragCoord)), gl_FragColor.a); \n' : '') +
'} \n');
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);
}
@@ -1507,9 +1688,55 @@ var LibraryGL = {
this.hasNormal = normalSize > 0 && this.normalLocation >= 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.immediate.MAX_TEMP_BUFFER_SIZE, 'too much vertex data');
+ arrayBuffer = GL.immediate.tempVertexBuffers[GL.immediate.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 this if you
+ // have odd glitches (by setting canSkip always to 0, or even cleaning up the renderer right
+ // after rendering)
+ var lastRenderer = GL.immediate.lastRenderer;
+ var canSkip = this == lastRenderer &&
+ arrayBuffer == GL.immediate.lastArrayBuffer &&
+ (GL.currProgram || this.program) == GL.immediate.lastProgram &&
+ !GL.immediate.matricesModified;
+ if (!canSkip && lastRenderer) lastRenderer.cleanup();
+ if (!GL.currArrayBuffer) {
+ // Bind the array buffer and upload data after cleaning up the previous renderer
+ if (arrayBuffer != GL.immediate.lastArrayBuffer) {
+ Module.ctx.bindBuffer(Module.ctx.ARRAY_BUFFER, arrayBuffer);
+ }
+ Module.ctx.bufferSubData(Module.ctx.ARRAY_BUFFER, start, GL.immediate.vertexData.subarray(start >> 2, end >> 2));
+ }
+ if (canSkip) return;
+ GL.immediate.lastRenderer = this;
+ GL.immediate.lastArrayBuffer = arrayBuffer;
+ GL.immediate.lastProgram = GL.currProgram || this.program;
+ GL.immediate.matricesModified = false;
+
+ if (!GL.currProgram) {
+ Module.ctx.useProgram(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']);
@@ -1550,6 +1777,12 @@ var LibraryGL = {
Module.ctx.bindTexture(Module.ctx.TEXTURE_2D, texture);
Module.ctx.uniform1i(this.textureLocation, 0);
}
+ 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() {
@@ -1567,6 +1800,17 @@ var LibraryGL = {
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);
+ }
+
+ GL.immediate.lastRenderer = null;
+ GL.immediate.lastArrayBuffer = null;
+ GL.immediate.lastProgram = null;
+ GL.immediate.matricesModified = true;
}
};
ret.init();
@@ -1601,7 +1845,9 @@ var LibraryGL = {
// 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();
}
@@ -1748,43 +1994,19 @@ var LibraryGL = {
emulatedElementArrayBuffer = true;
}
} else if (GL.immediate.mode > 6) { // above GL_TRIANGLE_FAN are the non-GL ES modes
- if (GL.immediate.mode == 7) { // GL_QUADS
- var numQuads = numVertexes / 4;
- assert(numQuads % 1 == 0);
- for (var i = 0; i < numQuads; i++) {
- var start = i*4;
- GL.immediate.indexData[numIndexes++] = start;
- GL.immediate.indexData[numIndexes++] = start+1;
- GL.immediate.indexData[numIndexes++] = start+2;
- GL.immediate.indexData[numIndexes++] = start;
- GL.immediate.indexData[numIndexes++] = start+2;
- GL.immediate.indexData[numIndexes++] = start+3;
- }
- } else {
- throw 'unsupported immediate mode ' + GL.immediate.mode;
- }
- assert(numIndexes << 1 <= GL.immediate.MAX_TEMP_BUFFER_SIZE, 'too many immediate mode indexes (b)');
- var indexBuffer = GL.immediate.tempIndexBuffers[GL.immediate.tempBufferIndexLookup[numIndexes << 1]];
- Module.ctx.bindBuffer(Module.ctx.ELEMENT_ARRAY_BUFFER, indexBuffer);
- Module.ctx.bufferSubData(Module.ctx.ELEMENT_ARRAY_BUFFER, 0, this.indexData.subarray(0, numIndexes));
+ 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.immediate.MAX_TEMP_BUFFER_SIZE, 'too many immediate mode indexes (b)');
+ Module.ctx.bindBuffer(Module.ctx.ELEMENT_ARRAY_BUFFER, this.tempQuadIndexBuffer);
emulatedElementArrayBuffer = true;
}
- if (!GL.currArrayBuffer) {
- Module.ctx.bindBuffer(Module.ctx.ARRAY_BUFFER, this.vertexObject);
- var start = GL.immediate.firstVertex*GL.immediate.stride;
- var end = GL.immediate.lastVertex*GL.immediate.stride;
- assert(end <= GL.immediate.MAX_TEMP_BUFFER_SIZE, 'too much vertex data');
- var vertexBuffer = GL.immediate.tempVertexBuffers[GL.immediate.tempBufferIndexLookup[end]];
- Module.ctx.bindBuffer(Module.ctx.ARRAY_BUFFER, vertexBuffer);
- Module.ctx.bufferSubData(Module.ctx.ARRAY_BUFFER, start, this.vertexData.subarray(start >> 2, end >> 2));
- }
-
- // Render
- if (!GL.currProgram) {
- Module.ctx.useProgram(renderer.program);
- }
-
renderer.prepare();
if (numIndexes) {
@@ -1793,17 +2015,9 @@ var LibraryGL = {
Module.ctx.drawArrays(GL.immediate.mode, startIndex, numVertexes);
}
- renderer.cleanup();
-
- if (!GL.currArrayBuffer) {
- Module.ctx.bindBuffer(Module.ctx.ARRAY_BUFFER, null);
- }
if (emulatedElementArrayBuffer) {
Module.ctx.bindBuffer(Module.ctx.ELEMENT_ARRAY_BUFFER, GL.buffers[GL.currElementArrayBuffer] || null);
}
- if (!GL.currProgram) {
- Module.ctx.useProgram(null);
- }
}
},
@@ -1886,7 +2100,7 @@ var LibraryGL = {
},
glColor4us__deps: ['glColor4f'],
glColor4us: function(r, g, b, a) {
- _glColor4f((r&65525)/65535, (g&65525)/65535, (b&65525)/65535, (a&65525)/65535);
+ _glColor4f((r&65535)/65535, (g&65535)/65535, (b&65535)/65535, (a&65535)/65535);
},
glColor4ui__deps: ['glColor4f'],
glColor4ui: function(r, g, b, a) {
@@ -1931,10 +2145,58 @@ var LibraryGL = {
_glColor4f({{{ makeGetValue('p', '0', 'float') }}}, {{{ makeGetValue('p', '4', 'float') }}}, {{{ makeGetValue('p', '8', 'float') }}}, {{{ makeGetValue('p', '12', 'float') }}});
},
- glFogf: function(){}, // TODO
- glFogi: function(){}, // TODO
- glFogx: function(){}, // TODO
- glFogfv: function(){}, // TODO
+ 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
@@ -2011,20 +2273,24 @@ var LibraryGL = {
},
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]);
},
@@ -2032,30 +2298,36 @@ var LibraryGL = {
#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);
@@ -2063,6 +2335,7 @@ var LibraryGL = {
},
glMultTransposeMatrixf: function(matrix) {
+ GL.immediate.matricesMod