Skip to content

Commit

Permalink
Merge pull request #5581 from juj/offscreen_framebuffer
Browse files Browse the repository at this point in the history
Multithreading 14/N: OFFSCREEN_FRAMEBUFFER
  • Loading branch information
juj authored Aug 12, 2018
2 parents 4470ad8 + 02aa29f commit 89d4ae2
Show file tree
Hide file tree
Showing 12 changed files with 380 additions and 9 deletions.
23 changes: 22 additions & 1 deletion site/source/docs/api_reference/html5.h.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1949,6 +1949,20 @@ Struct
If ``true``, all GLES2-compatible non-performance-impacting WebGL extensions will automatically be enabled for you after the context has been created. If ``false``, no extensions are enabled by default, and you need to manually call :c:func:`emscripten_webgl_enable_extension` to enable each extension that you want to use. Default value: ``true``.
.. c:member:: EM_BOOL explicitSwapControl
By default, when ``explicitSwapControl`` is in its default state ``false``, rendered WebGL content is implicitly presented (displayed to the user) on the canvas when the event handler that renders with WebGL returns back to the browser event loop. If ``explicitSwapControl`` is set to ``true``, rendered content will not be displayed on screen automatically when event handler function finishes, but the control of swapping is given to the user to manage, via the ``emscripten_webgl_commit_frame()`` function.
In order to be able to set ``explicitSwapControl==true``, support for it must explicitly be enabled either 1) via adding the ``-s OFFSCREEN_FRAMEBUFFER=1`` Emscripten linker flag, and enabling ``renderViaOffscreenBackBuffer==1``, or 2) via adding the the linker flag ``-s OFFSCREENCANVAS_SUPPORT=1``, and running in a browser that supports OffscreenCanvas.
.. c:member:: EM_BOOL renderViaOffscreenBackBuffer
If ``true``, an extra intermediate backbuffer (offscreen render target) is allocated to the created WebGL context, and rendering occurs to this backbuffer instead of directly onto the WebGL "default backbuffer". This is required to be enabled if 1) ``explicitSwapControl==true`` and the browser does not support OffscreenCanvas, 2) when performing WebGL rendering in a worker thread and the browser does not support OffscreenCanvas, and 3) when performing WebGL context accesses from multiple threads simultaneously (independent of whether OffscreenCanvas is supported or not).
Because supporting offscreen framebuffer adds some amount of extra code to the compiled output, support for it must explicitly be enabled via the ``-s OFFSCREEN_FRAMEBUFFER=1`` Emscripten linker flag. When building simultaneously with both ``-s OFFSCREEN_FRAMEBUFFER=1`` and ``-s OFFSCREENCANVAS_SUPPORT=1`` linker flags enabled, offscreen backbuffer can be used as a polyfill-like compatibility fallback to enable rendering WebGL from a pthread when the browser does not support the OffscreenCanvas API.
Callback functions
------------------
Expand Down Expand Up @@ -2044,7 +2058,14 @@ Functions
:rtype: |EMSCRIPTEN_WEBGL_CONTEXT_HANDLE|
.. c:function:: EMSCRIPTEN_RESULT emscripten_webgl_commit_frame()
Presents ("swaps") the content rendered on the currently active WebGL context to be visible on the canvas. This function is available on WebGL contexts that were created with the ``explicitSwapControl==true`` context creation attribute. If ``explicitSwapControl==false``, then the rendered content is displayed on the screen "implicitly" when yielding back to the browser from the calling event handler.
:returns: :c:data:`EMSCRIPTEN_RESULT_SUCCESS`, or one of the other result values, denoting a reason for failure.
:rtype: |EMSCRIPTEN_RESULT|
.. c:function:: EMSCRIPTEN_RESULT emscripten_webgl_get_drawing_buffer_size(EMSCRIPTEN_WEBGL_CONTEXT_HANDLE context, int *width, int *height)
Gets the ``drawingBufferWidth`` and ``drawingBufferHeight`` of the specified WebGL context.
Expand Down
20 changes: 17 additions & 3 deletions src/library_browser.js
Original file line number Diff line number Diff line change
Expand Up @@ -1115,7 +1115,11 @@ var LibraryBrowser = {
},

// Runs natively in pthread, no __proxy needed.
#if OFFSCREEN_FRAMEBUFFER
emscripten_set_main_loop__deps: ['emscripten_set_main_loop_timing', 'emscripten_get_now', 'emscripten_webgl_commit_frame'],
#else
emscripten_set_main_loop__deps: ['emscripten_set_main_loop_timing', 'emscripten_get_now'],
#endif
emscripten_set_main_loop: function(func, fps, simulateInfiniteLoop, arg, noSetTiming) {
Module['noExitRuntime'] = true;

Expand Down Expand Up @@ -1183,10 +1187,20 @@ var LibraryBrowser = {
GL.newRenderingFrameStarted();
#endif

#if USE_PTHREADS
#if OFFSCREEN_FRAMEBUFFER
// If the current GL context is a proxied regular WebGL context, and was initialized with implicit swap mode on the main thread, and we are on the parent thread,
// perform the swap on behalf of the user.
if (typeof GL !== 'undefined' && GL.currentContext && GLctxIsOnParentThread) {
var explicitSwapControl = {{{ makeGetValue('GL.currentContext', 0, 'i32') }}};
if (!explicitSwapControl) _emscripten_webgl_commit_frame();
}
#endif
#endif

#if OFFSCREENCANVAS_SUPPORT
// If the current GL context is an OffscreenCanvas, but it was initialized with implicit swap mode, perform the swap
// in behalf of the user.
if (typeof GL !== 'undefined' && GL.currentContext && !GL.currentContext.attributes.explicitSwapControl && GL.currentContext.GLctx.commit) {
// If the current GL context is an OffscreenCanvas, but it was initialized with implicit swap mode, perform the swap on behalf of the user.
if (typeof GL !== 'undefined' && GL.currentContext && !GLctxIsOnParentThread && !GL.currentContext.attributes.explicitSwapControl && GL.currentContext.GLctx.commit) {
GL.currentContext.GLctx.commit();
}
#endif
Expand Down
172 changes: 172 additions & 0 deletions src/library_gl.js
Original file line number Diff line number Diff line change
Expand Up @@ -497,6 +497,12 @@ var LibraryGL = {
webGLContextAttributes['minorVersion'] = 0;
}

#if OFFSCREEN_FRAMEBUFFER
// In proxied operation mode, rAF()/setTimeout() functions do not delimit frame boundaries, so can't have WebGL implementation
// try to detect when it's ok to discard contents of the rendered backbuffer.
if (webGLContextAttributes['renderViaOffscreenBackBuffer']) webGLContextAttributes['preserveDrawingBuffer'] = true;
#endif

#if GL_TESTING
webGLContextAttributes['preserveDrawingBuffer'] = true;
#endif
Expand Down Expand Up @@ -542,6 +548,151 @@ var LibraryGL = {
return context;
},

#if OFFSCREEN_FRAMEBUFFER
enableOffscreenFramebufferAttributes: function(webGLContextAttributes) {
webGLContextAttributes.renderViaOffscreenBackBuffer = true;
webGLContextAttributes.preserveDrawingBuffer = true;
},

// If WebGL is being proxied from a pthread to the main thread, we can't directly render to the WebGL default back buffer
// because of WebGL's implicit swap behavior. Therefore in such modes, create an offscreen render target surface to
// which rendering is performed to, and finally flipped to the main screen.
createOffscreenFramebuffer: function(context) {
var gl = context.GLctx;

// Create FBO
var fbo = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
context.defaultFbo = fbo;

// Create render targets to the FBO
context.defaultColorTarget = gl.createTexture();
context.defaultDepthTarget = gl.createRenderbuffer();
GL.resizeOffscreenFramebuffer(context); // Size them up correctly (use the same mechanism when resizing on demand)

gl.bindTexture(gl.TEXTURE_2D, context.defaultColorTarget);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.canvas.width, gl.canvas.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, context.defaultColorTarget, 0);
gl.bindTexture(gl.TEXTURE_2D, null);

// Create depth render target to the FBO
var depthTarget = gl.createRenderbuffer();
gl.bindRenderbuffer(gl.RENDERBUFFER, context.defaultDepthTarget);
gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, gl.canvas.width, gl.canvas.height);
gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, context.defaultDepthTarget);
gl.bindRenderbuffer(gl.RENDERBUFFER, null);

// Create blitter
var vertices = [
-1, -1,
-1, 1,
1, -1,
1, 1
];
var vb = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vb);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
gl.bindBuffer(gl.ARRAY_BUFFER, null);
context.blitVB = vb;

var vsCode =
'attribute vec2 pos;' +
'varying lowp vec2 tex;' +
'void main() { tex = pos * 0.5 + vec2(0.5,0.5); gl_Position = vec4(pos, 0.0, 1.0); }';
var vs = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vs, vsCode);
gl.compileShader(vs);

var fsCode =
'varying lowp vec2 tex;' +
'uniform sampler2D sampler;' +
'void main() { gl_FragColor = texture2D(sampler, tex); }';
var fs = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fs, fsCode);
gl.compileShader(fs);

var blitProgram = gl.createProgram();
gl.attachShader(blitProgram, vs);
gl.attachShader(blitProgram, fs);
gl.linkProgram(blitProgram);
context.blitProgram = blitProgram;
context.blitPosLoc = gl.getAttribLocation(blitProgram, "pos");
gl.useProgram(blitProgram);
gl.uniform1i(gl.getUniformLocation(blitProgram, "sampler"), 0);
gl.useProgram(null);
},

resizeOffscreenFramebuffer: function(context) {
var gl = context.GLctx;

// Resize color buffer
if (context.defaultColorTarget) {
var prevTextureBinding = gl.getParameter(gl.TEXTURE_BINDING_2D);
gl.bindTexture(gl.TEXTURE_2D, context.defaultColorTarget);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.drawingBufferWidth, gl.drawingBufferHeight, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
gl.bindTexture(gl.TEXTURE_2D, prevTextureBinding);
}

// Resize depth buffer
if (context.defaultDepthTarget) {
var prevRenderBufferBinding = gl.getParameter(gl.RENDERBUFFER_BINDING);
gl.bindRenderbuffer(gl.RENDERBUFFER, context.defaultDepthTarget);
gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, gl.drawingBufferWidth, gl.drawingBufferHeight); // TODO: Read context creation parameters for what type of depth and stencil to use
gl.bindRenderbuffer(gl.RENDERBUFFER, prevRenderBufferBinding);
}
},

// Renders the contents of the offscreen render target onto the visible screen.
blitOffscreenFramebuffer: function(context) {
var gl = context.GLctx;

var prevFbo = gl.getParameter(gl.FRAMEBUFFER_BINDING);
gl.bindFramebuffer(gl.FRAMEBUFFER, null);

var prevProgram = gl.getParameter(gl.CURRENT_PROGRAM);
gl.useProgram(context.blitProgram);

var prevVB = gl.getParameter(gl.ARRAY_BUFFER_BINDING);
gl.bindBuffer(gl.ARRAY_BUFFER, context.blitVB);

gl.vertexAttribPointer(context.blitPosLoc, 2, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(context.blitPosLoc);

var prevActiveTexture = gl.getParameter(gl.ACTIVE_TEXTURE);
gl.activeTexture(gl.TEXTURE0);

var prevTextureBinding = gl.getParameter(gl.TEXTURE_BINDING_2D);
gl.bindTexture(gl.TEXTURE_2D, context.defaultColorTarget);

var prevBlend = gl.getParameter(gl.BLEND);
if (prevBlend) gl.disable(gl.BLEND);

var prevCullFace = gl.getParameter(gl.CULL_FACE);
if (prevCullFace) gl.disable(gl.CULL_FACE);

var prevDepthTest = gl.getParameter(gl.DEPTH_TEST);
if (prevDepthTest) gl.disable(gl.DEPTH_TEST);

gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);

if (prevDepthTest) gl.enable(gl.DEPTH_TEST);
if (prevCullFace) gl.enable(gl.CULL_FACE);
if (prevBlend) gl.enable(gl.BLEND);

gl.bindTexture(gl.TEXTURE_2D, prevTextureBinding);
gl.activeTexture(prevActiveTexture);
// prevEnableVertexAttribArray?
// prevVertexAttribPointer?
gl.bindBuffer(gl.ARRAY_BUFFER, prevVB);
gl.useProgram(prevProgram);
gl.bindFramebuffer(gl.FRAMEBUFFER, prevFbo);
},
#endif

registerContext: function(ctx, webGLContextAttributes) {
var handle = GL.getNewId(GL.contexts);
var context = {
Expand All @@ -567,6 +718,18 @@ var LibraryGL = {
if (typeof webGLContextAttributes['enableExtensionsByDefault'] === 'undefined' || webGLContextAttributes['enableExtensionsByDefault']) {
GL.initExtensions(context);
}

if (webGLContextAttributes['renderViaOffscreenBackBuffer']) {
#if OFFSCREEN_FRAMEBUFFER
GL.createOffscreenFramebuffer(context);
#else
#if GL_DEBUG
Module.printErr('renderViaOffscreenBackBuffer=true specified in WebGL context creation attributes, pass linker flag -s OFFSCREEN_FRAMEBUFFER=1 to enable support!');
#endif
return 0;
#endif
}

return handle;
},

Expand Down Expand Up @@ -3865,7 +4028,16 @@ var LibraryGL = {
#if GL_ASSERTIONS
GL.validateGLObjectID(GL.framebuffers, framebuffer, 'glBindFramebuffer', 'framebuffer');
#endif

#if OFFSCREEN_FRAMEBUFFER
// defaultFbo may not be present if 'renderViaOffscreenBackBuffer' was not enabled during context creation time,
// i.e. setting -s OFFSCREEN_FRAMEBUFFER=1 at compilation time does not yet mandate that offscreen back buffer
// is being used, but that is ultimately decided at context creation time.
GLctx.bindFramebuffer(target, framebuffer ? GL.framebuffers[framebuffer] : GLctx.canvas.GLctxObject.defaultFbo);
#else
GLctx.bindFramebuffer(target, framebuffer ? GL.framebuffers[framebuffer] : null);
#endif

},

glGenFramebuffers__sig: 'vii',
Expand Down
4 changes: 4 additions & 0 deletions src/library_glfw.js
Original file line number Diff line number Diff line change
Expand Up @@ -996,6 +996,10 @@ var LibraryGLFW = {
stencil: (GLFW.hints[0x00021006] > 0), // GLFW_STENCIL_BITS
alpha: (GLFW.hints[0x00021004] > 0) // GLFW_ALPHA_BITS
}
#if OFFSCREEN_FRAMEBUFFER
// TODO: Make GLFW explicitly aware of whether it is being proxied or not, and set these to true only when proxying is being performed.
GL.enableOffscreenFramebufferAttributes(contextAttributes);
#endif
Module.ctx = Browser.createContext(Module['canvas'], true, true, contextAttributes);
}

Expand Down
4 changes: 4 additions & 0 deletions src/library_glut.js
Original file line number Diff line number Diff line change
Expand Up @@ -565,6 +565,10 @@ var LibraryGLUT = {
stencil: ((GLUT.initDisplayMode & 0x0020 /*GLUT_STENCIL*/) != 0),
alpha: ((GLUT.initDisplayMode & 0x0008 /*GLUT_ALPHA*/) != 0)
};
#if OFFSCREEN_FRAMEBUFFER
// TODO: Make glutCreateWindow explicitly aware of whether it is being proxied or not, and set these to true only when proxying is being performed.
GL.enableOffscreenFramebufferAttributes(contextAttributes);
#endif
Module.ctx = Browser.createContext(Module['canvas'], true, true, contextAttributes);
return Module.ctx ? 1 /* a new GLUT window ID for the created context */ : 0 /* failure */;
},
Expand Down
43 changes: 40 additions & 3 deletions src/library_html5.js
Original file line number Diff line number Diff line change
Expand Up @@ -1814,6 +1814,7 @@ var LibraryJSEvents = {
contextAttributes['minorVersion'] = {{{ makeGetValue('attributes', C_STRUCTS.EmscriptenWebGLContextAttributes.minorVersion, 'i32') }}};
var enableExtensionsByDefault = {{{ makeGetValue('attributes', C_STRUCTS.EmscriptenWebGLContextAttributes.enableExtensionsByDefault, 'i32') }}};
contextAttributes['explicitSwapControl'] = {{{ makeGetValue('attributes', C_STRUCTS.EmscriptenWebGLContextAttributes.explicitSwapControl, 'i32') }}};
contextAttributes['renderViaOffscreenBackBuffer'] = {{{ makeGetValue('attributes', C_STRUCTS.EmscriptenWebGLContextAttributes.renderViaOffscreenBackBuffer, 'i32') }}};

target = Pointer_stringify(target);
var canvas;
Expand All @@ -1831,24 +1832,47 @@ var LibraryJSEvents = {
#if OFFSCREENCANVAS_SUPPORT
if (contextAttributes['explicitSwapControl']) {
var supportsOffscreenCanvas = canvas.transferControlToOffscreen || (typeof OffscreenCanvas !== 'undefined' && canvas instanceof OffscreenCanvas);

if (!supportsOffscreenCanvas) {
#if OFFSCREEN_FRAMEBUFFER
if (!contextAttributes['renderViaOffscreenBackBuffer']) {
contextAttributes['renderViaOffscreenBackBuffer'] = true;
#if GL_DEBUG
console.error('emscripten_webgl_create_context: Performance warning, OffscreenCanvas is not supported but explicitSwapControl was requested, so force-enabling renderViaOffscreenBackBuffer=true to allow explicit swapping!');
#endif
}
#else
#if GL_DEBUG
console.error('emscripten_webgl_create_context failed: OffscreenCanvas is not supported!');
console.error('emscripten_webgl_create_context failed: OffscreenCanvas is not supported but explicitSwapControl was requested!');
#endif
return 0;
#endif
}

if (canvas.transferControlToOffscreen) {
GL.offscreenCanvases[canvas.id] = canvas.transferControlToOffscreen();
GL.offscreenCanvases[canvas.id].id = canvas.id;
canvas = GL.offscreenCanvases[canvas.id];
}
}
#else // !OFFSCREENCANVAS_SUPPORT
#if OFFSCREEN_FRAMEBUFFER
if (contextAttributes['explicitSwapControl'] && !contextAttributes['renderViaOffscreenBackBuffer']) {
contextAttributes['renderViaOffscreenBackBuffer'] = true;
#if GL_DEBUG
console.error('emscripten_webgl_create_context: Performance warning, not building with OffscreenCanvas support enabled but explicitSwapControl was requested, so force-enabling renderViaOffscreenBackBuffer=true to allow explicit swapping!');
#endif
}
#else
if (contextAttributes['explicitSwapControl']) {
console.error('emscripten_webgl_create_context failed: explicitSwapControl is not supported, please rebuild with -s OFFSCREENCANVAS_SUPPORT=1 to enable targeting the experimental OffscreenCanvas specification!');
#if GL_DEBUG
console.error('emscripten_webgl_create_context failed: explicitSwapControl is not supported, please rebuild with -s OFFSCREENCANVAS_SUPPORT=1 to enable targeting the experimental OffscreenCanvas specification, or rebuild with -s OFFSCREEN_FRAMEBUFFER=1 to emulate explicitSwapControl in the absence of OffscreenCanvas support!');
#endif
return 0;
}
#endif
#endif // ~!OFFSCREEN_FRAMEBUFFER

#endif // ~!OFFSCREENCANVAS_SUPPORT

var contextHandle = GL.createContext(canvas, contextAttributes);
return contextHandle;
Expand Down Expand Up @@ -1881,6 +1905,16 @@ var LibraryJSEvents = {
#endif
return {{{ cDefine('EMSCRIPTEN_RESULT_INVALID_TARGET') }}};
}

#if OFFSCREEN_FRAMEBUFFER
if (GL.currentContext.defaultFbo) {
GL.blitOffscreenFramebuffer(GL.currentContext);
#if GL_DEBUG
if (GL.currentContext.GLctx.commit) console.error('emscripten_webgl_commit_frame(): Offscreen framebuffer should never have gotten created when canvas is in OffscreenCanvas mode, since it is redundant and not necessary');
#endif
return {{{ cDefine('EMSCRIPTEN_RESULT_SUCCESS') }}};
}
#endif
if (!GL.currentContext.GLctx.commit) {
#if GL_DEBUG
console.error('emscripten_webgl_commit_frame() failed: OffscreenCanvas is not supported by the current GL context!');
Expand Down Expand Up @@ -1932,6 +1966,9 @@ var LibraryJSEvents = {

target.width = width;
target.height = height;
#if OFFSCREEN_FRAMEBUFFER
if (canvas.GLctxObject) GL.resizeOffscreenFramebuffer(canvas.GLctxObject);
#endif
return {{{ cDefine('EMSCRIPTEN_RESULT_SUCCESS') }}};
},

Expand Down
4 changes: 4 additions & 0 deletions src/library_sdl.js
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,10 @@ var LibrarySDL = {
alpha: (SDL.glAttributes[3 /*SDL_GL_ALPHA_SIZE*/] > 0)
};

#if OFFSCREEN_FRAMEBUFFER
// TODO: Make SDL explicitly aware of whether it is being proxied or not, and set these to true only when proxying is being performed.
GL.enableOffscreenFramebufferAttributes(webGLContextAttributes);
#endif
var ctx = Browser.createContext(canvas, is_SDL_OPENGL, usePageCanvas, webGLContextAttributes);

SDL.surfaces[surf] = {
Expand Down
Loading

0 comments on commit 89d4ae2

Please sign in to comment.