393 lines
13 KiB
JavaScript
393 lines
13 KiB
JavaScript
import Base from './core/Base';
|
|
import Texture from './Texture';
|
|
import TextureCube from './TextureCube';
|
|
import glenum from './core/glenum';
|
|
import Cache from './core/Cache';
|
|
|
|
var KEY_FRAMEBUFFER = 'framebuffer';
|
|
var KEY_RENDERBUFFER = 'renderbuffer';
|
|
var KEY_RENDERBUFFER_WIDTH = KEY_RENDERBUFFER + '_width';
|
|
var KEY_RENDERBUFFER_HEIGHT = KEY_RENDERBUFFER + '_height';
|
|
var KEY_RENDERBUFFER_ATTACHED = KEY_RENDERBUFFER + '_attached';
|
|
var KEY_DEPTHTEXTURE_ATTACHED = 'depthtexture_attached';
|
|
|
|
var GL_FRAMEBUFFER = glenum.FRAMEBUFFER;
|
|
var GL_RENDERBUFFER = glenum.RENDERBUFFER;
|
|
var GL_DEPTH_ATTACHMENT = glenum.DEPTH_ATTACHMENT;
|
|
var GL_COLOR_ATTACHMENT0 = glenum.COLOR_ATTACHMENT0;
|
|
/**
|
|
* @constructor clay.FrameBuffer
|
|
* @extends clay.core.Base
|
|
*/
|
|
var FrameBuffer = Base.extend(
|
|
/** @lends clay.FrameBuffer# */
|
|
{
|
|
/**
|
|
* If use depth buffer
|
|
* @type {boolean}
|
|
*/
|
|
depthBuffer: true,
|
|
|
|
/**
|
|
* @type {Object}
|
|
*/
|
|
viewport: null,
|
|
|
|
_width: 0,
|
|
_height: 0,
|
|
|
|
_textures: null,
|
|
|
|
_boundRenderer: null,
|
|
}, function () {
|
|
// Use cache
|
|
this._cache = new Cache();
|
|
|
|
this._textures = {};
|
|
},
|
|
|
|
/**@lends clay.FrameBuffer.prototype. */
|
|
{
|
|
/**
|
|
* Get attached texture width
|
|
* {number}
|
|
*/
|
|
// FIXME Can't use before #bind
|
|
getTextureWidth: function () {
|
|
return this._width;
|
|
},
|
|
|
|
/**
|
|
* Get attached texture height
|
|
* {number}
|
|
*/
|
|
getTextureHeight: function () {
|
|
return this._height;
|
|
},
|
|
|
|
/**
|
|
* Bind the framebuffer to given renderer before rendering
|
|
* @param {clay.Renderer} renderer
|
|
*/
|
|
bind: function (renderer) {
|
|
|
|
if (renderer.__currentFrameBuffer) {
|
|
// Already bound
|
|
if (renderer.__currentFrameBuffer === this) {
|
|
return;
|
|
}
|
|
|
|
console.warn('Renderer already bound with another framebuffer. Unbind it first');
|
|
}
|
|
renderer.__currentFrameBuffer = this;
|
|
|
|
var _gl = renderer.gl;
|
|
|
|
_gl.bindFramebuffer(GL_FRAMEBUFFER, this._getFrameBufferGL(renderer));
|
|
this._boundRenderer = renderer;
|
|
var cache = this._cache;
|
|
|
|
cache.put('viewport', renderer.viewport);
|
|
|
|
var hasTextureAttached = false;
|
|
var width;
|
|
var height;
|
|
for (var attachment in this._textures) {
|
|
hasTextureAttached = true;
|
|
var obj = this._textures[attachment];
|
|
if (obj) {
|
|
// TODO Do width, height checking, make sure size are same
|
|
width = obj.texture.width;
|
|
height = obj.texture.height;
|
|
// Attach textures
|
|
this._doAttach(renderer, obj.texture, attachment, obj.target);
|
|
}
|
|
}
|
|
|
|
this._width = width;
|
|
this._height = height;
|
|
|
|
if (!hasTextureAttached && this.depthBuffer) {
|
|
console.error('Must attach texture before bind, or renderbuffer may have incorrect width and height.')
|
|
}
|
|
|
|
if (this.viewport) {
|
|
renderer.setViewport(this.viewport);
|
|
}
|
|
else {
|
|
renderer.setViewport(0, 0, width, height, 1);
|
|
}
|
|
|
|
var attachedTextures = cache.get('attached_textures');
|
|
if (attachedTextures) {
|
|
for (var attachment in attachedTextures) {
|
|
if (!this._textures[attachment]) {
|
|
var target = attachedTextures[attachment];
|
|
this._doDetach(_gl, attachment, target);
|
|
}
|
|
}
|
|
}
|
|
if (!cache.get(KEY_DEPTHTEXTURE_ATTACHED) && this.depthBuffer) {
|
|
// Create a new render buffer
|
|
if (cache.miss(KEY_RENDERBUFFER)) {
|
|
cache.put(KEY_RENDERBUFFER, _gl.createRenderbuffer());
|
|
}
|
|
var renderbuffer = cache.get(KEY_RENDERBUFFER);
|
|
|
|
if (width !== cache.get(KEY_RENDERBUFFER_WIDTH)
|
|
|| height !== cache.get(KEY_RENDERBUFFER_HEIGHT)) {
|
|
_gl.bindRenderbuffer(GL_RENDERBUFFER, renderbuffer);
|
|
_gl.renderbufferStorage(GL_RENDERBUFFER, _gl.DEPTH_COMPONENT16, width, height);
|
|
cache.put(KEY_RENDERBUFFER_WIDTH, width);
|
|
cache.put(KEY_RENDERBUFFER_HEIGHT, height);
|
|
_gl.bindRenderbuffer(GL_RENDERBUFFER, null);
|
|
}
|
|
if (!cache.get(KEY_RENDERBUFFER_ATTACHED)) {
|
|
_gl.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, renderbuffer);
|
|
cache.put(KEY_RENDERBUFFER_ATTACHED, true);
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Unbind the frame buffer after rendering
|
|
* @param {clay.Renderer} renderer
|
|
*/
|
|
unbind: function (renderer) {
|
|
// Remove status record on renderer
|
|
renderer.__currentFrameBuffer = null;
|
|
|
|
var _gl = renderer.gl;
|
|
|
|
_gl.bindFramebuffer(GL_FRAMEBUFFER, null);
|
|
this._boundRenderer = null;
|
|
|
|
this._cache.use(renderer.__uid__);
|
|
var viewport = this._cache.get('viewport');
|
|
// Reset viewport;
|
|
if (viewport) {
|
|
renderer.setViewport(viewport);
|
|
}
|
|
|
|
this.updateMipmap(renderer);
|
|
},
|
|
|
|
// Because the data of texture is changed over time,
|
|
// Here update the mipmaps of texture each time after rendered;
|
|
updateMipmap: function (renderer) {
|
|
var _gl = renderer.gl;
|
|
for (var attachment in this._textures) {
|
|
var obj = this._textures[attachment];
|
|
if (obj) {
|
|
var texture = obj.texture;
|
|
// FIXME some texture format can't generate mipmap
|
|
if (!texture.NPOT && texture.useMipmap
|
|
&& texture.minFilter === Texture.LINEAR_MIPMAP_LINEAR) {
|
|
var target = texture.textureType === 'textureCube' ? glenum.TEXTURE_CUBE_MAP : glenum.TEXTURE_2D;
|
|
_gl.bindTexture(target, texture.getWebGLTexture(renderer));
|
|
_gl.generateMipmap(target);
|
|
_gl.bindTexture(target, null);
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
|
|
// 0x8CD5, 36053, FRAMEBUFFER_COMPLETE
|
|
// 0x8CD6, 36054, FRAMEBUFFER_INCOMPLETE_ATTACHMENT
|
|
// 0x8CD7, 36055, FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT
|
|
// 0x8CD9, 36057, FRAMEBUFFER_INCOMPLETE_DIMENSIONS
|
|
// 0x8CDD, 36061, FRAMEBUFFER_UNSUPPORTED
|
|
checkStatus: function (_gl) {
|
|
return _gl.checkFramebufferStatus(GL_FRAMEBUFFER);
|
|
},
|
|
|
|
_getFrameBufferGL: function (renderer) {
|
|
var cache = this._cache;
|
|
cache.use(renderer.__uid__);
|
|
|
|
if (cache.miss(KEY_FRAMEBUFFER)) {
|
|
cache.put(KEY_FRAMEBUFFER, renderer.gl.createFramebuffer());
|
|
}
|
|
|
|
return cache.get(KEY_FRAMEBUFFER);
|
|
},
|
|
|
|
/**
|
|
* Attach a texture(RTT) to the framebuffer
|
|
* @param {clay.Texture} texture
|
|
* @param {number} [attachment=gl.COLOR_ATTACHMENT0]
|
|
* @param {number} [target=gl.TEXTURE_2D]
|
|
*/
|
|
attach: function (texture, attachment, target) {
|
|
|
|
if (!texture.width) {
|
|
throw new Error('The texture attached to color buffer is not a valid.');
|
|
}
|
|
// TODO width and height check
|
|
|
|
// If the depth_texture extension is enabled, developers
|
|
// Can attach a depth texture to the depth buffer
|
|
// http://blog.tojicode.com/2012/07/using-webgldepthtexture.html
|
|
attachment = attachment || GL_COLOR_ATTACHMENT0;
|
|
target = target || glenum.TEXTURE_2D;
|
|
|
|
var boundRenderer = this._boundRenderer;
|
|
var _gl = boundRenderer && boundRenderer.gl;
|
|
var attachedTextures;
|
|
|
|
if (_gl) {
|
|
var cache = this._cache;
|
|
cache.use(boundRenderer.__uid__);
|
|
attachedTextures = cache.get('attached_textures');
|
|
}
|
|
|
|
// Check if texture attached
|
|
var previous = this._textures[attachment];
|
|
if (previous && previous.target === target
|
|
&& previous.texture === texture
|
|
&& (attachedTextures && attachedTextures[attachment] != null)
|
|
) {
|
|
return;
|
|
}
|
|
|
|
var canAttach = true;
|
|
if (boundRenderer) {
|
|
canAttach = this._doAttach(boundRenderer, texture, attachment, target);
|
|
// Set viewport again incase attached to different size textures.
|
|
if (!this.viewport) {
|
|
boundRenderer.setViewport(0, 0, texture.width, texture.height, 1);
|
|
}
|
|
}
|
|
|
|
if (canAttach) {
|
|
this._textures[attachment] = this._textures[attachment] || {};
|
|
this._textures[attachment].texture = texture;
|
|
this._textures[attachment].target = target;
|
|
}
|
|
},
|
|
|
|
_doAttach: function (renderer, texture, attachment, target) {
|
|
var _gl = renderer.gl;
|
|
// Make sure texture is always updated
|
|
// Because texture width or height may be changed and in this we can't be notified
|
|
// FIXME awkward;
|
|
var webglTexture = texture.getWebGLTexture(renderer);
|
|
// Assume cache has been used.
|
|
var attachedTextures = this._cache.get('attached_textures');
|
|
if (attachedTextures && attachedTextures[attachment]) {
|
|
var obj = attachedTextures[attachment];
|
|
// Check if texture and target not changed
|
|
if (obj.texture === texture && obj.target === target) {
|
|
return;
|
|
}
|
|
}
|
|
attachment = +attachment;
|
|
|
|
var canAttach = true;
|
|
if (attachment === GL_DEPTH_ATTACHMENT || attachment === glenum.DEPTH_STENCIL_ATTACHMENT) {
|
|
var extension = renderer.getGLExtension('WEBGL_depth_texture');
|
|
|
|
if (!extension) {
|
|
console.error('Depth texture is not supported by the browser');
|
|
canAttach = false;
|
|
}
|
|
if (texture.format !== glenum.DEPTH_COMPONENT
|
|
&& texture.format !== glenum.DEPTH_STENCIL
|
|
) {
|
|
console.error('The texture attached to depth buffer is not a valid.');
|
|
canAttach = false;
|
|
}
|
|
|
|
// Dispose render buffer created previous
|
|
if (canAttach) {
|
|
var renderbuffer = this._cache.get(KEY_RENDERBUFFER);
|
|
if (renderbuffer) {
|
|
_gl.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, null);
|
|
_gl.deleteRenderbuffer(renderbuffer);
|
|
this._cache.put(KEY_RENDERBUFFER, false);
|
|
}
|
|
|
|
this._cache.put(KEY_RENDERBUFFER_ATTACHED, false);
|
|
this._cache.put(KEY_DEPTHTEXTURE_ATTACHED, true);
|
|
}
|
|
}
|
|
|
|
// Mipmap level can only be 0
|
|
_gl.framebufferTexture2D(GL_FRAMEBUFFER, attachment, target, webglTexture, 0);
|
|
|
|
if (!attachedTextures) {
|
|
attachedTextures = {};
|
|
this._cache.put('attached_textures', attachedTextures);
|
|
}
|
|
attachedTextures[attachment] = attachedTextures[attachment] || {};
|
|
attachedTextures[attachment].texture = texture;
|
|
attachedTextures[attachment].target = target;
|
|
|
|
return canAttach;
|
|
},
|
|
|
|
_doDetach: function (_gl, attachment, target) {
|
|
// Detach a texture from framebuffer
|
|
// https://github.com/KhronosGroup/WebGL/blob/master/conformance-suites/1.0.0/conformance/framebuffer-test.html#L145
|
|
_gl.framebufferTexture2D(GL_FRAMEBUFFER, attachment, target, null, 0);
|
|
|
|
// Assume cache has been used.
|
|
var attachedTextures = this._cache.get('attached_textures');
|
|
if (attachedTextures && attachedTextures[attachment]) {
|
|
attachedTextures[attachment] = null;
|
|
}
|
|
|
|
if (attachment === GL_DEPTH_ATTACHMENT || attachment === glenum.DEPTH_STENCIL_ATTACHMENT) {
|
|
this._cache.put(KEY_DEPTHTEXTURE_ATTACHED, false);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Detach a texture
|
|
* @param {number} [attachment=gl.COLOR_ATTACHMENT0]
|
|
* @param {number} [target=gl.TEXTURE_2D]
|
|
*/
|
|
detach: function (attachment, target) {
|
|
// TODO depth extension check ?
|
|
this._textures[attachment] = null;
|
|
if (this._boundRenderer) {
|
|
var cache = this._cache;
|
|
cache.use(this._boundRenderer.__uid__);
|
|
this._doDetach(this._boundRenderer.gl, attachment, target);
|
|
}
|
|
},
|
|
/**
|
|
* Dispose
|
|
* @param {WebGLRenderingContext} _gl
|
|
*/
|
|
dispose: function (renderer) {
|
|
|
|
var _gl = renderer.gl;
|
|
var cache = this._cache;
|
|
|
|
cache.use(renderer.__uid__);
|
|
|
|
var renderBuffer = cache.get(KEY_RENDERBUFFER);
|
|
if (renderBuffer) {
|
|
_gl.deleteRenderbuffer(renderBuffer);
|
|
}
|
|
var frameBuffer = cache.get(KEY_FRAMEBUFFER);
|
|
if (frameBuffer) {
|
|
_gl.deleteFramebuffer(frameBuffer);
|
|
}
|
|
cache.deleteContext(renderer.__uid__);
|
|
|
|
// Clear cache for reusing
|
|
this._textures = {};
|
|
|
|
}
|
|
});
|
|
|
|
FrameBuffer.DEPTH_ATTACHMENT = GL_DEPTH_ATTACHMENT;
|
|
FrameBuffer.COLOR_ATTACHMENT0 = GL_COLOR_ATTACHMENT0;
|
|
FrameBuffer.STENCIL_ATTACHMENT = glenum.STENCIL_ATTACHMENT;
|
|
FrameBuffer.DEPTH_STENCIL_ATTACHMENT = glenum.DEPTH_STENCIL_ATTACHMENT;
|
|
|
|
export default FrameBuffer;
|