From ee0037c6b0a58547e67fb854126c9c2f952100f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Sat, 26 Aug 2017 17:50:32 +0300 Subject: [PATCH 1/4] Implement -s OFFSCREEN_FRAMEBUFFER=1 option which allows a proxying based fallback to doing WebGL from a pthread when OffscreenCanvas is not available, as well as doing explicitSwapControl semantics for a WebGL context. --- src/library_gl.js | 167 ++++++++++++++++++++++++++++++ src/library_glfw.js | 5 + src/library_glut.js | 5 + src/library_html5.js | 28 ++++- src/library_sdl.js | 5 + src/settings.js | 1 + src/struct_info.json | 3 +- system/include/emscripten/html5.h | 1 + 8 files changed, 211 insertions(+), 4 deletions(-) diff --git a/src/library_gl.js b/src/library_gl.js index abf82dd171286..eefed7aa97832 100644 --- a/src/library_gl.js +++ b/src/library_gl.js @@ -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 @@ -542,6 +548,146 @@ var LibraryGL = { return context; }, +#if OFFSCREEN_FRAMEBUFFER + // 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 = { @@ -567,6 +713,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; }, @@ -3861,7 +4019,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', diff --git a/src/library_glfw.js b/src/library_glfw.js index e1e1788bed078..2153e337cd7de 100644 --- a/src/library_glfw.js +++ b/src/library_glfw.js @@ -996,6 +996,11 @@ 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. + contextAttributes.renderViaOffscreenBackBuffer = true; + contextAttributes.preserveDrawingBuffer = true; +#endif Module.ctx = Browser.createContext(Module['canvas'], true, true, contextAttributes); } diff --git a/src/library_glut.js b/src/library_glut.js index e2f4c22939679..3169f490da7d9 100644 --- a/src/library_glut.js +++ b/src/library_glut.js @@ -565,6 +565,11 @@ 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. + contextAttributes.renderViaOffscreenBackBuffer = true; + contextAttributes.preserveDrawingBuffer = true; +#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 */; }, diff --git a/src/library_html5.js b/src/library_html5.js index 9509a7280e42c..12b3442b811be 100644 --- a/src/library_html5.js +++ b/src/library_html5.js @@ -1831,12 +1831,21 @@ 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; + 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!'); + } +#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; @@ -1844,11 +1853,14 @@ var LibraryJSEvents = { } } #else +#else // !OFFSCREENCANVAS_SUPPORT +#if !OFFSCREEN_FRAMEBUFFER 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!'); + 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!'); return 0; } -#endif +#endif // ~!OFFSCREEN_FRAMEBUFFER +#endif // ~!OFFSCREENCANVAS_SUPPORT var contextHandle = GL.createContext(canvas, contextAttributes); return contextHandle; @@ -1881,6 +1893,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!'); diff --git a/src/library_sdl.js b/src/library_sdl.js index cbcf70096cb7c..533af0d62bcee 100644 --- a/src/library_sdl.js +++ b/src/library_sdl.js @@ -395,6 +395,11 @@ 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. + webGLContextAttributes.renderViaOffscreenBackBuffer = true; + webGLContextAttributes.preserveDrawingBuffer = true; +#endif var ctx = Browser.createContext(canvas, is_SDL_OPENGL, usePageCanvas, webGLContextAttributes); SDL.surfaces[surf] = { diff --git a/src/settings.js b/src/settings.js index 853fcceaf428f..b856ce8978837 100644 --- a/src/settings.js +++ b/src/settings.js @@ -905,6 +905,7 @@ var TEXTDECODER = 1; // Is enabled, use the JavaScript TextDecoder API for strin var OFFSCREENCANVAS_SUPPORT = 0; // If set to 1, enables support for transferring canvases to pthreads and creating WebGL contexts in them, // as well as explicit swap control for GL contexts. This needs browser support for the OffscreenCanvas // specification. +var OFFSCREEN_FRAMEBUFFER = 0; // If set to 1, enables rendering to an offscreen render target first, and then finally flipping on to the screen. var FETCH_DEBUG = 0; // If nonzero, prints out debugging information in library_fetch.js diff --git a/src/struct_info.json b/src/struct_info.json index 97a02378e21df..c3db819795b68 100644 --- a/src/struct_info.json +++ b/src/struct_info.json @@ -1390,7 +1390,8 @@ "majorVersion", "minorVersion", "enableExtensionsByDefault", - "explicitSwapControl" + "explicitSwapControl", + "renderViaOffscreenBackBuffer" ], "EmscriptenFullscreenStrategy": [ "scaleMode", diff --git a/system/include/emscripten/html5.h b/system/include/emscripten/html5.h index faccfd41234de..559e3c9291562 100644 --- a/system/include/emscripten/html5.h +++ b/system/include/emscripten/html5.h @@ -414,6 +414,7 @@ typedef struct EmscriptenWebGLContextAttributes { EM_BOOL enableExtensionsByDefault; EM_BOOL explicitSwapControl; + EM_BOOL renderViaOffscreenBackBuffer; } EmscriptenWebGLContextAttributes; extern void emscripten_webgl_init_context_attributes(EmscriptenWebGLContextAttributes *attributes); From bdbf5c489a2ae994c1537e0f51c5d02098f23ace Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Tue, 6 Feb 2018 00:14:57 +0200 Subject: [PATCH 2/4] OFFSCREEN_FRAMEBUFFER swapping support --- src/library_browser.js | 20 +++++++++++++++++--- src/library_html5.js | 3 +++ src/settings.js | 11 ++++++++++- 3 files changed, 30 insertions(+), 4 deletions(-) diff --git a/src/library_browser.js b/src/library_browser.js index bc1b75a00e4ec..d3dee3c67c452 100644 --- a/src/library_browser.js +++ b/src/library_browser.js @@ -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; @@ -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 diff --git a/src/library_html5.js b/src/library_html5.js index 12b3442b811be..e3ff3a9326cc6 100644 --- a/src/library_html5.js +++ b/src/library_html5.js @@ -1954,6 +1954,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') }}}; }, diff --git a/src/settings.js b/src/settings.js index b856ce8978837..1770447fcb9fb 100644 --- a/src/settings.js +++ b/src/settings.js @@ -905,7 +905,16 @@ var TEXTDECODER = 1; // Is enabled, use the JavaScript TextDecoder API for strin var OFFSCREENCANVAS_SUPPORT = 0; // If set to 1, enables support for transferring canvases to pthreads and creating WebGL contexts in them, // as well as explicit swap control for GL contexts. This needs browser support for the OffscreenCanvas // specification. -var OFFSCREEN_FRAMEBUFFER = 0; // If set to 1, enables rendering to an offscreen render target first, and then finally flipping on to the screen. +var OFFSCREEN_FRAMEBUFFER = 0; // If set to 1, enables support for WebGL contexts to render to an offscreen render target, to avoid + // the implicit swap behavior of WebGL where exiting any event callback would automatically perform a "flip" + // to present rendered content on screen. When an Emscripten GL context has Offscreen Framebuffer enabled, a single + // frame can be composited from multiple event callbacks, and the swap function emscripten_webgl_commit_frame() + // is then explicitly called to present the rendered content on screen. + // The OffscreenCanvas feature also enables explicit GL frame swapping support, and also, + // -s OFFSCREEN_FRAMEBUFFER=1 feature can be used to polyfill support for accessing WebGL in multiple + // threads in the absence of OffscreenCanvas support in browser, at the cost of some performance and latency. + // OffscreenCanvas and Offscreen Framebuffer support can be enabled at the same time, and allows one to + // utilize OffscreenCanvas where available, and to fall back to Offscreen Framebuffer otherwise. var FETCH_DEBUG = 0; // If nonzero, prints out debugging information in library_fetch.js From 0a3719ba982b98e0879352f03722709590779295 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Tue, 13 Feb 2018 14:51:26 +0200 Subject: [PATCH 3/4] Add GL.enableOffscreenFramebufferAttributes() function to initialize context attributes for Offscreen Framebuffer use --- src/library_gl.js | 5 +++++ src/library_glfw.js | 3 +-- src/library_glut.js | 3 +-- src/library_sdl.js | 3 +-- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/library_gl.js b/src/library_gl.js index eefed7aa97832..edd20a4c61994 100644 --- a/src/library_gl.js +++ b/src/library_gl.js @@ -549,6 +549,11 @@ var LibraryGL = { }, #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. diff --git a/src/library_glfw.js b/src/library_glfw.js index 2153e337cd7de..082bf100cc336 100644 --- a/src/library_glfw.js +++ b/src/library_glfw.js @@ -998,8 +998,7 @@ var LibraryGLFW = { } #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. - contextAttributes.renderViaOffscreenBackBuffer = true; - contextAttributes.preserveDrawingBuffer = true; + GL.enableOffscreenFramebufferAttributes(contextAttributes); #endif Module.ctx = Browser.createContext(Module['canvas'], true, true, contextAttributes); } diff --git a/src/library_glut.js b/src/library_glut.js index 3169f490da7d9..98920ae77bd40 100644 --- a/src/library_glut.js +++ b/src/library_glut.js @@ -567,8 +567,7 @@ var LibraryGLUT = { }; #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. - contextAttributes.renderViaOffscreenBackBuffer = true; - contextAttributes.preserveDrawingBuffer = true; + 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 */; diff --git a/src/library_sdl.js b/src/library_sdl.js index 533af0d62bcee..204b42448924d 100644 --- a/src/library_sdl.js +++ b/src/library_sdl.js @@ -397,8 +397,7 @@ var LibrarySDL = { #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. - webGLContextAttributes.renderViaOffscreenBackBuffer = true; - webGLContextAttributes.preserveDrawingBuffer = true; + GL.enableOffscreenFramebufferAttributes(webGLContextAttributes); #endif var ctx = Browser.createContext(canvas, is_SDL_OPENGL, usePageCanvas, webGLContextAttributes); From 02aa29fe18b06c4f265b38f11c828dee8ec8557b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Fri, 13 Jul 2018 17:21:18 +0300 Subject: [PATCH 4/4] Make WebGL context attribute explicitSwapControl imply renderViaOffscreenBackBuffer when OffscreenCanvas is not supported. Add testing for OFFSCREEN_FRAMEBUFFER mode. Add documentation for OFFSCREEN_FRAMEBUFFER. --- site/source/docs/api_reference/html5.h.rst | 23 ++++- src/library_html5.js | 16 +++- system/include/emscripten/html5.h | 2 +- tests/test_browser.py | 5 ++ tests/webgl_draw_triangle.c | 98 ++++++++++++++++++++++ 5 files changed, 140 insertions(+), 4 deletions(-) create mode 100644 tests/webgl_draw_triangle.c diff --git a/site/source/docs/api_reference/html5.h.rst b/site/source/docs/api_reference/html5.h.rst index 98abd158f918f..4eb37b25b15ec 100644 --- a/site/source/docs/api_reference/html5.h.rst +++ b/site/source/docs/api_reference/html5.h.rst @@ -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 ------------------ @@ -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. diff --git a/src/library_html5.js b/src/library_html5.js index e3ff3a9326cc6..d9eb5571f5f44 100644 --- a/src/library_html5.js +++ b/src/library_html5.js @@ -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; @@ -1836,7 +1837,9 @@ var LibraryJSEvents = { #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 @@ -1852,14 +1855,23 @@ var LibraryJSEvents = { canvas = GL.offscreenCanvases[canvas.id]; } } -#else #else // !OFFSCREENCANVAS_SUPPORT -#if !OFFSCREEN_FRAMEBUFFER +#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']) { +#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 // ~!OFFSCREEN_FRAMEBUFFER + #endif // ~!OFFSCREENCANVAS_SUPPORT var contextHandle = GL.createContext(canvas, contextAttributes); diff --git a/system/include/emscripten/html5.h b/system/include/emscripten/html5.h index 559e3c9291562..a77f7d9b4f58d 100644 --- a/system/include/emscripten/html5.h +++ b/system/include/emscripten/html5.h @@ -437,7 +437,7 @@ extern EMSCRIPTEN_RESULT emscripten_set_webglcontextrestored_callback(const char extern EM_BOOL emscripten_is_webgl_context_lost(const char *target); -extern EMSCRIPTEN_RESULT emscripten_webgl_commit_frame(); +extern EMSCRIPTEN_RESULT emscripten_webgl_commit_frame(void); extern EMSCRIPTEN_RESULT emscripten_set_canvas_element_size(const char *target, int width, int height); extern EMSCRIPTEN_RESULT emscripten_get_canvas_element_size(const char *target, int *width, int *height); diff --git a/tests/test_browser.py b/tests/test_browser.py index 739be2baa20e6..07540bbedee0c 100644 --- a/tests/test_browser.py +++ b/tests/test_browser.py @@ -3731,6 +3731,11 @@ def test_webgl_offscreen_canvas_in_mainthread_after_pthread(self): for args in [[], ['-DTEST_MAIN_THREAD_EXPLICIT_COMMIT']]: self.btest('gl_in_mainthread_after_pthread.cpp', expected='0', args=args+['-s', 'USE_PTHREADS=1', '-s', 'PTHREAD_POOL_SIZE=2', '-s', 'OFFSCREENCANVAS_SUPPORT=1', '-lGL']) + # Tests that -s OFFSCREEN_FRAMEBUFFER=1 rendering works. + @requires_graphics_hardware + def test_webgl_offscreen_framebuffer(self): + self.btest('webgl_draw_triangle.c', '0', args=['-lGL', '-s', 'OFFSCREEN_FRAMEBUFFER=1', '-DEXPLICIT_SWAP=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). diff --git a/tests/webgl_draw_triangle.c b/tests/webgl_draw_triangle.c new file mode 100644 index 0000000000000..22991a6adcee0 --- /dev/null +++ b/tests/webgl_draw_triangle.c @@ -0,0 +1,98 @@ +#include +#include +#include +#include +#include + +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 + + 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;" + "void main() {" + "gl_FragColor = color;" + "}"; + GLuint fs = compile_shader(GL_FRAGMENT_SHADER, fragment_shader); + + GLuint program = create_program(vs, fs); + glUseProgram(program); + + GLuint vbo; + glGenBuffers(1, &vbo); + glBindBuffer(GL_ARRAY_BUFFER, vbo); + 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, + }; + 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); + glEnableVertexAttribArray(0); + glEnableVertexAttribArray(1); + + 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 +}