Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WebGL2 MSAA RTs and RenderTarget refactor #877

Merged
merged 7 commits into from Mar 8, 2017
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
170 changes: 125 additions & 45 deletions src/graphics/device.js
Expand Up @@ -877,6 +877,30 @@ pc.extend(pc, function () {
}
},

_checkFbo: function () {
// Ensure all is well
var gl = this.gl;
var status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
switch (status) {
case gl.FRAMEBUFFER_INCOMPLETE_ATTACHMENT:
console.error("ERROR: FRAMEBUFFER_INCOMPLETE_ATTACHMENT");
break;
case gl.FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT:
console.error("ERROR: FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT");
break;
case gl.FRAMEBUFFER_INCOMPLETE_DIMENSIONS:
console.error("ERROR: FRAMEBUFFER_INCOMPLETE_DIMENSIONS");
break;
case gl.FRAMEBUFFER_UNSUPPORTED:
console.error("ERROR: FRAMEBUFFER_UNSUPPORTED");
break;
case gl.FRAMEBUFFER_COMPLETE:
break;
default:
break;
}
},

/**
* @function
* @name pc.GraphicsDevice#updateBegin
Expand Down Expand Up @@ -905,61 +929,112 @@ pc.extend(pc, function () {
});
// #endif

// Set RT's device
target._device = this;

// ##### Create main FBO #####
target._glFrameBuffer = gl.createFramebuffer();
this.setFramebuffer(target._glFrameBuffer);

// --- Init the provided color buffer (optional) ---
var colorBuffer = target._colorBuffer;
if (!colorBuffer._glTextureId) {
// Clamp the render buffer size to the maximum supported by the device
colorBuffer._width = Math.min(colorBuffer.width, this.maxRenderBufferSize);
colorBuffer._height = Math.min(colorBuffer.height, this.maxRenderBufferSize);

this.setTexture(colorBuffer, 0);
if (colorBuffer) {
if (!colorBuffer._glTextureId) {
// Clamp the render buffer size to the maximum supported by the device
colorBuffer._width = Math.min(colorBuffer.width, this.maxRenderBufferSize);
colorBuffer._height = Math.min(colorBuffer.height, this.maxRenderBufferSize);
this.setTexture(colorBuffer, 0);
}
// Attach the color buffer
gl.framebufferTexture2D(
gl.FRAMEBUFFER,
gl.COLOR_ATTACHMENT0,
colorBuffer._cubemap ? gl.TEXTURE_CUBE_MAP_POSITIVE_X + target._face : gl.TEXTURE_2D,
colorBuffer._glTextureId,
0
);
}

gl.framebufferTexture2D(
gl.FRAMEBUFFER,
gl.COLOR_ATTACHMENT0,
colorBuffer._cubemap ? gl.TEXTURE_CUBE_MAP_POSITIVE_X + target._face : gl.TEXTURE_2D,
colorBuffer._glTextureId,
0
);

if (target._depth) {
if (!target._glDepthBuffer) {
target._glDepthBuffer = gl.createRenderbuffer();
var depthBuffer = target._depthBuffer;
if (depthBuffer && this.webgl2) {
// --- Init the provided depth/stencil buffer (optional, WebGL2 only) ---
if (!depthBuffer._glTextureId) {
// Clamp the render buffer size to the maximum supported by the device
depthBuffer._width = Math.min(depthBuffer.width, this.maxRenderBufferSize);
depthBuffer._height = Math.min(depthBuffer.height, this.maxRenderBufferSize);
this.setTexture(depthBuffer, 0);
}

gl.bindRenderbuffer(gl.RENDERBUFFER, target._glDepthBuffer);
// Attach
if (target._stencil) {
gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, target.width, target.height);
gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, target._glDepthBuffer);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT,
depthBuffer._cubemap ? gl.TEXTURE_CUBE_MAP_POSITIVE_X + target._face : gl.TEXTURE_2D,
target._depthBuffer._glTextureId, 0);
} else {
gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, target.width, target.height);
gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, target._glDepthBuffer);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT,
depthBuffer._cubemap ? gl.TEXTURE_CUBE_MAP_POSITIVE_X + target._face : gl.TEXTURE_2D,
target._depthBuffer._glTextureId, 0);
}
} else if (target._depth) {
// --- Init a new depth/stencil buffer (optional) ---
// if this is a MSAA RT, and no buffer to resolve to, skip creating non-MSAA depth
var willRenderMsaa = target._samples > 1 && this.webgl2;
if (!willRenderMsaa) {
if (!target._glDepthBuffer) {
target._glDepthBuffer = gl.createRenderbuffer();
}
gl.bindRenderbuffer(gl.RENDERBUFFER, target._glDepthBuffer);
if (target._stencil) {
gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, target.width, target.height);
gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, target._glDepthBuffer);
} else {
gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, target.width, target.height);
gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, target._glDepthBuffer);
}
gl.bindRenderbuffer(gl.RENDERBUFFER, null);
}
gl.bindRenderbuffer(gl.RENDERBUFFER, null);
}

// Ensure all is well
var status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
switch (status) {
case gl.FRAMEBUFFER_INCOMPLETE_ATTACHMENT:
console.error("ERROR: FRAMEBUFFER_INCOMPLETE_ATTACHMENT");
break;
case gl.FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT:
console.error("ERROR: FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT");
break;
case gl.FRAMEBUFFER_INCOMPLETE_DIMENSIONS:
console.error("ERROR: FRAMEBUFFER_INCOMPLETE_DIMENSIONS");
break;
case gl.FRAMEBUFFER_UNSUPPORTED:
console.error("ERROR: FRAMEBUFFER_UNSUPPORTED");
break;
case gl.FRAMEBUFFER_COMPLETE:
break;
default:
break;
// #ifdef DEBUG
this._checkFbo();
// #endif

// ##### Create MSAA FBO (WebGL2 only) #####
if (this.webgl2 && target._samples > 1) {

// Use previous FBO for resolves
target._glResolveFrameBuffer = target._glFrameBuffer;

// Actual FBO will be MSAA
target._glFrameBuffer = gl.createFramebuffer();
this.setFramebuffer(target._glFrameBuffer);

// Create an optional MSAA color buffer
if (colorBuffer) {
if (!target._glMsaaColorBuffer) {
target._glMsaaColorBuffer = gl.createRenderbuffer();
}
gl.bindRenderbuffer(gl.RENDERBUFFER, target._glMsaaColorBuffer);
gl.renderbufferStorageMultisample(gl.RENDERBUFFER, target._samples, colorBuffer._glInternalFormat, target.width, target.height);
gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, target._glMsaaColorBuffer);
}

// Optionally add a MSAA depth/stencil buffer
if (target._depth) {
if (!target._glMsaaDepthBuffer) {
target._glMsaaDepthBuffer = gl.createRenderbuffer();
}
gl.bindRenderbuffer(gl.RENDERBUFFER, target._glMsaaDepthBuffer);
if (target._stencil) {
gl.renderbufferStorageMultisample(gl.RENDERBUFFER, target._samples, gl.DEPTH24_STENCIL8, target.width, target.height);
gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, target._glMsaaDepthBuffer);
} else {
gl.renderbufferStorageMultisample(gl.RENDERBUFFER, target._samples, gl.DEPTH_COMPONENT32F, target.width, target.height);
gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, target._glMsaaDepthBuffer);
}
}
// #ifdef DEBUG
this._checkFbo();
// #endif
}

// #ifdef PROFILER
Expand Down Expand Up @@ -992,14 +1067,19 @@ pc.extend(pc, function () {
var target = this.renderTarget;
if (target) {
// Switch rendering back to the back buffer
this.setFramebuffer(null);
//this.setFramebuffer(null); // disabled - not needed?

// If the active render target is auto-mipmapped, generate its mip chain
var colorBuffer = target._colorBuffer;
if (colorBuffer._glTextureId && colorBuffer.mipmaps && colorBuffer._pot) {
if (colorBuffer && colorBuffer._glTextureId && colorBuffer.mipmaps && colorBuffer._pot) {
gl.bindTexture(colorBuffer._glTarget, colorBuffer._glTextureId);
gl.generateMipmap(colorBuffer._glTarget);
}

// Resolve MSAA if needed
if (this.webgl2 && target._samples > 1 && target.autoResolve) {
target.resolve();
}
}
},

Expand Down
126 changes: 113 additions & 13 deletions src/graphics/render-target.js
Expand Up @@ -9,14 +9,17 @@ pc.extend(pc, function () {
/**
* @name pc.RenderTarget
* @class A render target is a rectangular rendering surface.
* @description Creates a new render target.
* @param {pc.GraphicsDevice} graphicsDevice The graphics device used to manage this frame buffer.
* @param {pc.Texture} colorBuffer The texture that this render target will treat as a rendering surface.
* @description Creates a new render target. A color buffer or a depth buffer must be set.
* @param {Object} options Object for passing optional arguments.
* @param {Boolean} options.depth True if the render target is to include a depth buffer and false otherwise (default is true).
* @param {Boolean} options.stencil True if the render target is to include a stencil buffer and false otherwise (default is false). Requires depth buffer.
* @param {pc.Texture} [options.colorBuffer] The texture that this render target will treat as a rendering surface.
* @param {Boolean} [options.depth] If set to true, depth buffer will be created. Defaults to true. Ignored if depthBuffer is defined.
* @param {Boolean} [options.stencil] If set to true, depth buffer will include stencil. Defaults to false. Ignored if depthBuffer is defined or depth is false.
* @param {pc.Texture} [options.depthBuffer] The texture that this render target will treat as a depth/stencil surface (WebGL2 only). If set, the 'depth' and 'stencil' properties are ignored.
* Texture must have pc.PIXELFORMAT_DEPTH or PIXELFORMAT_DEPTHSTENCIL format.
* @param {Number} [options.samples] Number of hardware anti-aliasing samples (WebGL2 only). Default is 1.
* @param {Boolean} [options.autoResolve] If samples > 1, enables or disables automatic MSAA resolve after rendering to this RT (see pc.RenderTarget#resolve). Defaults to true;
* Defaults to true.
* @param {Number} options.face If the colorBuffer parameter is a cubemap, use this option to specify the
* @param {Number} [options.face] If the colorBuffer parameter is a cubemap, use this option to specify the
* face of the cubemap to render to. Can be:
* <ul>
* <li>pc.CUBEFACE_POSX</li>
Expand All @@ -41,17 +44,50 @@ pc.extend(pc, function () {
* // Set the render target on an entity's camera component
* entity.camera.renderTarget = renderTarget;
*/
var RenderTarget = function (graphicsDevice, colorBuffer, options) {
this._device = graphicsDevice;
this._colorBuffer = colorBuffer;
var RenderTarget = function (options, _arg2, _arg3) {

if (options instanceof pc.GraphicsDevice) {
// old constructor
this._colorBuffer = _arg2;
options = _arg3;
} else {
// new constructor
this._colorBuffer = options.colorBuffer;
}

this._glFrameBuffer = null;
this._glDepthBuffer = null;

// Process optional arguments
options = (options !== undefined) ? options : defaultOptions;
this._depthBuffer = options.depthBuffer;
this._face = (options.face !== undefined) ? options.face : 0;
this._depth = (options.depth !== undefined) ? options.depth : true;
this._stencil = (options.stencil !== undefined) ? options.stencil : false;

if (this._depthBuffer) {
var format = this._depthBuffer._format;
if (format === pc.PIXELFORMAT_DEPTH) {
this._depth = true;
this._stencil = false;
} else if (format === pc.PIXELFORMAT_DEPTHSTENCIL) {
this._depth = true;
this._stencil = true;
} else {
// #ifdef DEBUG
console.warn('Incorrect depthBuffer format. Must be pc.PIXELFORMAT_DEPTH or pc.PIXELFORMAT_DEPTHSTENCIL');
// #endif
this._depth = false;
this._stencil = false;
}
} else {
this._depth = (options.depth !== undefined) ? options.depth : true;
this._stencil = (options.stencil !== undefined) ? options.stencil : false;
}

this._samples = (options.samples !== undefined) ? options.samples : 1;
this.autoResolve = (options.autoResolve !== undefined) ? options.autoResolve : true;
this._glResolveFrameBuffer = null;
this._glMsaaColorBuffer = null;
this._glMsaaDepthBuffer = null;
};

RenderTarget.prototype = {
Expand All @@ -61,6 +97,7 @@ pc.extend(pc, function () {
* @description Frees resources associated with this render target.
*/
destroy: function () {
if (!this._device) return;
var gl = this._device.gl;

if (this._glFrameBuffer) {
Expand All @@ -72,6 +109,48 @@ pc.extend(pc, function () {
gl.deleteRenderbuffer(this._glDepthBuffer);
this._glDepthBuffer = null;
}

if (this._glResolveFrameBuffer) {
gl.deleteFramebuffer(this._glResolveFrameBuffer);
this._glResolveFrameBuffer = null;
}

if (this._glMsaaColorBuffer) {
gl.deleteRenderbuffer(this._glMsaaColorBuffer);
this._glMsaaColorBuffer = null;
}

if (this._glMsaaDepthBuffer) {
gl.deleteRenderbuffer(this._glMsaaDepthBuffer);
this._glMsaaDepthBuffer = null;
}
},

/**
* @function
* @name pc.RenderTarget#resolve
* @description If samples > 1, resolves the anti-aliased render target (WebGL2 only).
* When you're rendering to an anti-aliased render target, pixels aren't written directly to the readable texture.
* Instead, they're first written to a MSAA buffer, where each sample for each pixel is stored independently.
* In order to read the results, you first need to 'resolve' the buffer - to average all samples and create a simple texture with one color per pixel.
* This function performs this averaging and updates the colorBuffer and the depthBuffer.
* If autoResolve is set to true, the resolve will happen after every rendering to this render target, otherwise you can do it manually,
* during the app update or inside a pc.Command.
*/
resolve: function (color, depth) {
if (!this._device) return;
if (!this._device.webgl2) return;
var gl = this._device.gl;

if (color === undefined) color = true;
if (depth === undefined && this._depthBuffer) depth = true;

gl.bindFramebuffer(gl.READ_FRAMEBUFFER, this._glFrameBuffer);
gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, this._glResolveFrameBuffer);
gl.blitFramebuffer( 0, 0, this.width, this.height,
0, 0, this.width, this.height,
(color ? gl.COLOR_BUFFER_BIT : 0) | (depth ? gl.DEPTH_BUFFER_BIT : 0),
gl.NEAREST);
}
};

Expand All @@ -87,6 +166,19 @@ pc.extend(pc, function () {
}
});

/**
* @readonly
* @name pc.RenderTarget#depthBuffer
* @type pc.Texture
* @description Depth buffer set up on the render target. Only available, if depthBuffer was set in constructor.
* Not available, if depth property was used instead.
*/
Object.defineProperty(RenderTarget.prototype, 'depthBuffer', {
get: function() {
return this._depthBuffer;
}
});

/**
* @readonly
* @name pc.RenderTarget#face
Expand Down Expand Up @@ -116,7 +208,11 @@ pc.extend(pc, function () {
*/
Object.defineProperty(RenderTarget.prototype, 'width', {
get: function() {
return this._colorBuffer.width;
if (this._colorBuffer) {
return this._colorBuffer.width;
} else {
return this._depthBuffer.width;
}
}
});

Expand All @@ -128,7 +224,11 @@ pc.extend(pc, function () {
*/
Object.defineProperty(RenderTarget.prototype, 'height', {
get: function() {
return this._colorBuffer.height;
if (this._colorBuffer) {
return this._colorBuffer.height;
} else {
return this._depthBuffer.height;
}
}
});

Expand Down