Skip to content

Commit

Permalink
Implement -s OFFSCREEN_FRAMEBUFFER=1 option which allows a proxying b…
Browse files Browse the repository at this point in the history
…ased fallback to doing WebGL from a pthread when OffscreenCanvas is not available, as well as doing explicitSwapControl semantics for a WebGL context.
  • Loading branch information
juj committed Jan 15, 2018
1 parent 9fc522a commit ca78adb
Show file tree
Hide file tree
Showing 8 changed files with 211 additions and 4 deletions.
167 changes: 167 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,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 = {
Expand All @@ -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;
},

Expand Down Expand Up @@ -3821,7 +3979,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
5 changes: 5 additions & 0 deletions src/library_glfw.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down
5 changes: 5 additions & 0 deletions src/library_glut.js
Original file line number Diff line number Diff line change
Expand Up @@ -564,6 +564,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 */;
},
Expand Down
28 changes: 25 additions & 3 deletions src/library_html5.js
Original file line number Diff line number Diff line change
Expand Up @@ -1831,24 +1831,36 @@ 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;
canvas = GL.offscreenCanvases[canvas.id];
}
}
#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;
Expand Down Expand Up @@ -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!');
Expand Down
5 changes: 5 additions & 0 deletions src/library_sdl.js
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,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] = {
Expand Down
1 change: 1 addition & 0 deletions src/settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -832,6 +832,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

Expand Down
3 changes: 2 additions & 1 deletion src/struct_info.json
Original file line number Diff line number Diff line change
Expand Up @@ -1386,7 +1386,8 @@
"majorVersion",
"minorVersion",
"enableExtensionsByDefault",
"explicitSwapControl"
"explicitSwapControl",
"renderViaOffscreenBackBuffer"
],
"EmscriptenFullscreenStrategy": [
"scaleMode",
Expand Down
1 change: 1 addition & 0 deletions system/include/emscripten/html5.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down

0 comments on commit ca78adb

Please sign in to comment.