Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions emcc.py
Original file line number Diff line number Diff line change
Expand Up @@ -1112,6 +1112,13 @@ def check(input_file):
assert not shared.Settings.WASM, 'LEGACY_VM_SUPPORT is only supported for asm.js, and not wasm. Build with -s WASM=0'
shared.Settings.POLYFILL_OLD_MATH_FUNCTIONS = 1
shared.Settings.WORKAROUND_IOS_9_RIGHT_SHIFT_BUG = 1
shared.Settings.WORKAROUND_OLD_WEBGL_UNIFORM_UPLOAD_IGNORED_OFFSET_BUG = 1

# Silently drop any individual backwards compatibility emulation flags that are known never to occur on browsers that support WebAssembly.
if shared.Settings.WASM:
shared.Settings.POLYFILL_OLD_MATH_FUNCTIONS = 0
shared.Settings.WORKAROUND_IOS_9_RIGHT_SHIFT_BUG = 0
shared.Settings.WORKAROUND_OLD_WEBGL_UNIFORM_UPLOAD_IGNORED_OFFSET_BUG = 0

if shared.Settings.SPLIT_MEMORY:
if shared.Settings.WASM:
Expand Down
44 changes: 44 additions & 0 deletions src/library_gl.js
Original file line number Diff line number Diff line change
Expand Up @@ -719,6 +719,29 @@ var LibraryGL = {
context.supportsWebGL2EntryPoints = (context.version >= 2) && (getChromeVersion() === false || getChromeVersion() >= 58);
#endif

#if WORKAROUND_OLD_WEBGL_UNIFORM_UPLOAD_IGNORED_OFFSET_BUG
context.cannotHandleOffsetsInUniformArrayViews = (function(g) {
try {
var p = g.createProgram(); // Note: we do not delete this program so it stays part of the context we created, but that is ok - it does not do anything and we want to keep this detection size minimal.
function b(c, t) {
var s = g.createShader(t);
g.shaderSource(s, c);
g.compileShader(s);
return s;
}
g.attachShader(p, b("attribute vec4 p;void main(){gl_Position=p;}", g.VERTEX_SHADER));
g.attachShader(p, b("precision lowp float;uniform vec4 u;void main(){gl_FragColor=u;}", g.FRAGMENT_SHADER));
g.linkProgram(p);
var h = new Float32Array(8);
h[4] = 1;
g.useProgram(p);
var l = g.getUniformLocation(p, "u");
g.uniform4fv(l, h.subarray(4, 8)); // Uploading a 4-vector GL uniform from last four elements of array [0,0,0,0,1,0,0,0], i.e. uploading vec4=(1,0,0,0) at offset=4.
return !g.getUniform(p, l)[0]; // in proper WebGL we expect to read back the vector we just uploaded: (1,0,0,0). On buggy browser would instead have uploaded offset=0 of above array, i.e. vec4=(0,0,0,0)
} catch(e) { return false; } // If we get an exception, we assume we got some other error, and do not trigger this workaround.
})();
#endif

// Store the created context object so that we can access the context given a canvas without having to pass the parameters again.
if (ctx.canvas) ctx.canvas.GLctxObject = context;
GL.contexts[handle] = context;
Expand Down Expand Up @@ -3191,6 +3214,9 @@ var LibraryGL = {
}
} else {
view = {{{ makeHEAPView('F32', 'value', 'value+count*4') }}};
#if WORKAROUND_OLD_WEBGL_UNIFORM_UPLOAD_IGNORED_OFFSET_BUG
Copy link
Member

@kripken kripken Nov 6, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this needed for every single F32 heap view?

How about creating a helper function to avoid all the code duplication, makeGLHEAPView (adding "GL" in the name, and it adds this check)?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, it is needed only specifically for these views that it was added to. I would not want to craft any abstractions for this. If I added a makeGLHEAPView() (which lives in core src/parseTools.js) that we'd have to maintain just for this case, then someone would wonder why only parts of src/library_gl.js used makeGLHEAPView() and others used regular makeHEAPView() and it might leak to being used everywhere. I'd prefer to do this as KISS as possible, not building any complexity on top of this.

if (GL.currentContext.cannotHandleOffsetsInUniformArrayViews) view = new Float32Array(view);
#endif
}
GLctx.uniform1fv(GL.uniforms[location], view);
},
Expand Down Expand Up @@ -3219,6 +3245,9 @@ var LibraryGL = {
}
} else {
view = {{{ makeHEAPView('F32', 'value', 'value+count*8') }}};
#if WORKAROUND_OLD_WEBGL_UNIFORM_UPLOAD_IGNORED_OFFSET_BUG
if (GL.currentContext.cannotHandleOffsetsInUniformArrayViews) view = new Float32Array(view);
#endif
}
GLctx.uniform2fv(GL.uniforms[location], view);
},
Expand Down Expand Up @@ -3248,6 +3277,9 @@ var LibraryGL = {
}
} else {
view = {{{ makeHEAPView('F32', 'value', 'value+count*12') }}};
#if WORKAROUND_OLD_WEBGL_UNIFORM_UPLOAD_IGNORED_OFFSET_BUG
if (GL.currentContext.cannotHandleOffsetsInUniformArrayViews) view = new Float32Array(view);
#endif
}
GLctx.uniform3fv(GL.uniforms[location], view);
},
Expand Down Expand Up @@ -3278,6 +3310,9 @@ var LibraryGL = {
}
} else {
view = {{{ makeHEAPView('F32', 'value', 'value+count*16') }}};
#if WORKAROUND_OLD_WEBGL_UNIFORM_UPLOAD_IGNORED_OFFSET_BUG
if (GL.currentContext.cannotHandleOffsetsInUniformArrayViews) view = new Float32Array(view);
#endif
}
GLctx.uniform4fv(GL.uniforms[location], view);
},
Expand Down Expand Up @@ -3394,6 +3429,9 @@ var LibraryGL = {
}
} else {
view = {{{ makeHEAPView('F32', 'value', 'value+count*16') }}};
#if WORKAROUND_OLD_WEBGL_UNIFORM_UPLOAD_IGNORED_OFFSET_BUG
if (GL.currentContext.cannotHandleOffsetsInUniformArrayViews) view = new Float32Array(view);
#endif
}
GLctx.uniformMatrix2fv(GL.uniforms[location], !!transpose, view);
},
Expand Down Expand Up @@ -3429,6 +3467,9 @@ var LibraryGL = {
}
} else {
view = {{{ makeHEAPView('F32', 'value', 'value+count*36') }}};
#if WORKAROUND_OLD_WEBGL_UNIFORM_UPLOAD_IGNORED_OFFSET_BUG
if (GL.currentContext.cannotHandleOffsetsInUniformArrayViews) view = new Float32Array(view);
#endif
}
GLctx.uniformMatrix3fv(GL.uniforms[location], !!transpose, view);
},
Expand Down Expand Up @@ -3471,6 +3512,9 @@ var LibraryGL = {
}
} else {
view = {{{ makeHEAPView('F32', 'value', 'value+count*64') }}};
#if WORKAROUND_OLD_WEBGL_UNIFORM_UPLOAD_IGNORED_OFFSET_BUG
if (GL.currentContext.cannotHandleOffsetsInUniformArrayViews) view = new Float32Array(view);
#endif
}
GLctx.uniformMatrix4fv(GL.uniforms[location], !!transpose, view);
},
Expand Down
7 changes: 7 additions & 0 deletions src/settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,12 @@ var FULL_ES2 = 0;
// from the output.
var GL_EMULATE_GLES_VERSION_STRING_FORMAT = 1;

// Some old Android WeChat (Chromium 37?) browser has a WebGL bug that it ignores
// the offset of a typed array view pointing to an ArrayBuffer. Set this to
// 1 to enable a polyfill that works around the issue when it appears. This
// bug is only relevant to WebGL 1, the affected browsers do not support WebGL 2.
var WORKAROUND_OLD_WEBGL_UNIFORM_UPLOAD_IGNORED_OFFSET_BUG = 0;

// Enables WebGL2 native functions. This mode will also create a WebGL2
// context by default if no version is specified.
var USE_WEBGL2 = 0;
Expand Down Expand Up @@ -452,6 +458,7 @@ var POLYFILL_OLD_MATH_FUNCTIONS = 0;
// browsers and shell environments. Specifically:
// * Add polyfilling for Math.clz32, Math.trunc, Math.imul, Math.fround. (-s POLYFILL_OLD_MATH_FUNCTIONS=1)
// * Work around iOS 9 right shift bug (-s WORKAROUND_IOS_9_RIGHT_SHIFT_BUG=1)
// * Work around old Chromium WebGL 1 bug (-s WORKAROUND_OLD_WEBGL_UNIFORM_UPLOAD_IGNORED_OFFSET_BUG=1)
// * Disable WebAssembly. (Must be paired with -s WASM=0)
// You can also configure the above options individually.
var LEGACY_VM_SUPPORT = 0;
Expand Down
5 changes: 5 additions & 0 deletions tests/test_browser.py
Original file line number Diff line number Diff line change
Expand Up @@ -3864,6 +3864,11 @@ def test_webgl_from_client_side_memory_without_default_enabled_extensions(self):
def test_webgl_offscreen_framebuffer(self):
self.btest('webgl_draw_triangle.c', '0', args=['-lGL', '-s', 'OFFSCREEN_FRAMEBUFFER=1', '-DEXPLICIT_SWAP=1'])

# Tests that -s WORKAROUND_OLD_WEBGL_UNIFORM_UPLOAD_IGNORED_OFFSET_BUG=1 rendering works.
@requires_graphics_hardware
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this actually test that code path - does it force the polyfill on somehow?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It just tests that code compiles with that flag enabled. I don't think it is worth spending time to maintaining a test infrastructure where we would enforce the path to kick in.

def test_webgl_workaround_webgl_uniform_upload_bug(self):
self.btest('webgl_draw_triangle_with_uniform_color.c', '0', args=['-lGL', '-s', 'WORKAROUND_OLD_WEBGL_UNIFORM_UPLOAD_IGNORED_OFFSET_BUG=1'])

# Tests the feature that shell html page can preallocate the typed array and place it to Module.buffer before loading the script page.
# In this build mode, the -s TOTAL_MEMORY=xxx option will be ignored.
# Preallocating the buffer in this was is asm.js only (wasm needs a Memory).
Expand Down
118 changes: 118 additions & 0 deletions tests/webgl_draw_triangle_with_uniform_color.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
/*
* Copyright 2018 The Emscripten Authors. All rights reserved.
* Emscripten is available under two separate licenses, the MIT license and the
* University of Illinois/NCSA Open Source License. Both these licenses can be
* found in the LICENSE file.
*/

#include <stdio.h>
#include <stdlib.h>
#include <emscripten/emscripten.h>
#include <emscripten/html5.h>
#include <GLES2/gl2.h>

GLuint compile_shader(GLenum shaderType, const char *src)
{
GLuint shader = glCreateShader(shaderType);
glShaderSource(shader, 1, &src, NULL);
glCompileShader(shader);

GLint isCompiled = 0;
glGetShaderiv(shader, GL_COMPILE_STATUS, &isCompiled);
if (!isCompiled)
{
GLint maxLength = 0;
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &maxLength);
char *buf = (char*)malloc(maxLength+1);
glGetShaderInfoLog(shader, maxLength, &maxLength, buf);
printf("%s\n", buf);
free(buf);
return 0;
}

return shader;
}

GLuint create_program(GLuint vertexShader, GLuint fragmentShader)
{
GLuint program = glCreateProgram();
glAttachShader(program, vertexShader);
glAttachShader(program, fragmentShader);
glBindAttribLocation(program, 0, "apos");
glBindAttribLocation(program, 1, "acolor");
glLinkProgram(program);
return program;
}

int main()
{
EmscriptenWebGLContextAttributes attr;
emscripten_webgl_init_context_attributes(&attr);
#ifdef EXPLICIT_SWAP
attr.explicitSwapControl = 1;
#endif
#ifdef DRAW_FROM_CLIENT_MEMORY
// This test verifies that drawing from client-side memory when enableExtensionsByDefault==false works.
attr.enableExtensionsByDefault = 0;
#endif

EMSCRIPTEN_WEBGL_CONTEXT_HANDLE ctx = emscripten_webgl_create_context("#canvas", &attr);
emscripten_webgl_make_context_current(ctx);

static const char vertex_shader[] =
"attribute vec4 apos;"
"attribute vec4 acolor;"
"varying vec4 color;"
"void main() {"
"color = acolor;"
"gl_Position = apos;"
"}";
GLuint vs = compile_shader(GL_VERTEX_SHADER, vertex_shader);

static const char fragment_shader[] =
"precision lowp float;"
"varying vec4 color;"
"uniform vec4 color2;"
"void main() {"
"gl_FragColor = color*color2;"
"}";
GLuint fs = compile_shader(GL_FRAGMENT_SHADER, fragment_shader);

GLuint program = create_program(vs, fs);
glUseProgram(program);

static const float pos_and_color[] = {
// x, y, r, g, b
-0.6f, -0.6f, 1, 0, 0,
0.6f, -0.6f, 0, 1, 0,
0.f, 0.6f, 0, 0, 1,
};

#ifdef DRAW_FROM_CLIENT_MEMORY
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 20, pos_and_color);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 20, (void*)(pos_and_color+2));
#else
GLuint vbo;
glGenBuffers(1, &vbo);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(pos_and_color), pos_and_color, GL_STATIC_DRAW);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 20, 0);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 20, (void*)8);
#endif
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);

float color2[4] = { 0.0f, 1.f, 0.0f, 1.0f };
glUniform4fv(glGetUniformLocation(program, "color2"), 1, color2);
glClearColor(0.3f,0.3f,0.3f,1);
glClear(GL_COLOR_BUFFER_BIT);
glDrawArrays(GL_TRIANGLES, 0, 3);

#ifdef EXPLICIT_SWAP
emscripten_webgl_commit_frame();
#endif

#ifdef REPORT_RESULT
REPORT_RESULT(0);
#endif
}