API Docs for:
Show:

File: ../src/fbo.js

/** 
* FBO for FrameBufferObjects, FBOs are used to store the render inside one or several textures 
* Supports multibuffer and depthbuffer texture, useful for deferred rendering
* @namespace GL
* @class FBO
* @param {Array} color_textures an array containing the color textures, if not supplied a render buffer will be used
* @param {GL.Texture} depth_texture the depth texture, if not supplied a render buffer will be used
* @param {Bool} stencil create a stencil buffer?
* @constructor
*/
function FBO( textures, depth_texture, stencil, gl )
{
	gl = gl || global.gl;
	this.gl = gl;
	this._context_id = gl.context_id; 

	if(textures && textures.constructor !== Array)
		throw("FBO textures must be an Array");

	this.handler = null;
	this.width = -1;
	this.height = -1;
	this.color_textures = [];
	this.depth_texture = null;
	this.stencil = !!stencil;

	this._stencil_enabled = false;
	this._num_binded_textures = 0;

	//assign textures
	if((textures && textures.length) || depth_texture)
		this.setTextures( textures, depth_texture );

	//save state
	this._old_fbo_handler = null;
	this._old_viewport = new Float32Array(4);
}

GL.FBO = FBO;

/**
* Changes the textures binded to this FBO
* @method setTextures
* @param {Array} color_textures an array containing the color textures, if not supplied a render buffer will be used
* @param {GL.Texture} depth_texture the depth texture, if not supplied a render buffer will be used
* @param {Boolean} skip_disable it doenst try to go back to the previous FBO enabled in case there was one
*/
FBO.prototype.setTextures = function( color_textures, depth_texture, skip_disable )
{
	//test depth
	if( depth_texture && depth_texture.constructor === GL.Texture )
	{
		if( depth_texture.format !== GL.DEPTH_COMPONENT && 
			depth_texture.format !== GL.DEPTH_STENCIL && 
			depth_texture.format !== GL.DEPTH_COMPONENT16 && 
			depth_texture.format !== GL.DEPTH_COMPONENT24 &&
			depth_texture.format !== GL.DEPTH_COMPONENT32F )
			throw("FBO Depth texture must be of format: gl.DEPTH_COMPONENT, gl.DEPTH_STENCIL or gl.DEPTH_COMPONENT16/24/32F (only in webgl2)");

		if( depth_texture.type != GL.UNSIGNED_SHORT && 
			depth_texture.type != GL.UNSIGNED_INT && 
			depth_texture.type != GL.UNSIGNED_INT_24_8_WEBGL &&
			depth_texture.type != GL.FLOAT)
			throw("FBO Depth texture must be of type: gl.UNSIGNED_SHORT, gl.UNSIGNED_INT, gl.UNSIGNED_INT_24_8_WEBGL");
	}

	//test if is already binded
	var same = this.depth_texture == depth_texture;
	if( same && color_textures )
	{
		if( color_textures.constructor !== Array )
			throw("FBO: color_textures parameter must be an array containing all the textures to be binded in the color");
		if( color_textures.length == this.color_textures.length )
		{
			for(var i = 0; i < color_textures.length; ++i)
				if( color_textures[i] != this.color_textures[i] )
				{
					same = false;
					break;
				}
		}
		else
			same = false;
	}

	if(this._stencil_enabled !== this.stencil)
		same = false;
		
	if(same)
		return;

	//copy textures in place
	this.color_textures.length = color_textures ? color_textures.length : 0;
	if(color_textures)
		for(var i = 0; i < color_textures.length; ++i)
			this.color_textures[i] = color_textures[i];
	this.depth_texture = depth_texture;

	//update GPU FBO
	this.update( skip_disable );
}

/**
* Updates the FBO with the new set of textures and buffers
* @method update
* @param {Boolean} skip_disable it doenst try to go back to the previous FBO enabled in case there was one
*/
FBO.prototype.update = function( skip_disable )
{
	//save state to restore afterwards
	this._old_fbo_handler = gl.getParameter( gl.FRAMEBUFFER_BINDING );

	if(!this.handler)
		this.handler = gl.createFramebuffer();

	var w = -1,
		h = -1,
		type = null;

	var color_textures = this.color_textures;
	var depth_texture = this.depth_texture;

	//compute the W and H (and check they have the same size)
	if(color_textures && color_textures.length)
		for(var i = 0; i < color_textures.length; i++)
		{
			var t = color_textures[i];
			if(t.constructor !== GL.Texture)
				throw("FBO can only bind instances of GL.Texture");
			if(w == -1) 
				w = t.width;
			else if(w != t.width)
				throw("Cannot bind textures with different dimensions");
			if(h == -1) 
				h = t.height;
			else if(h != t.height)
				throw("Cannot bind textures with different dimensions");
			if(type == null) //first one defines the type
				type = t.type;
			else if (type != t.type)
				throw("Cannot bind textures to a FBO with different pixel formats");
			if (t.texture_type != gl.TEXTURE_2D)
				throw("Cannot bind a Cubemap to a FBO");
		}
	else
	{
		w = depth_texture.width;
		h = depth_texture.height;
	}

	this.width = w;
	this.height = h;

	gl.bindFramebuffer( gl.FRAMEBUFFER, this.handler );

	//draw_buffers allow to have more than one color texture binded in a FBO
	var ext = gl.extensions["WEBGL_draw_buffers"];
	if( gl.webgl_version == 1 && !ext && color_textures && color_textures.length > 1)
		throw("Rendering to several textures not supported by your browser");

	var target = gl.webgl_version == 1 ? gl.FRAMEBUFFER : gl.DRAW_FRAMEBUFFER;

	gl.framebufferRenderbuffer( target, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, null );
	gl.framebufferRenderbuffer( target, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, null );
	//detach color too?

	//bind a buffer for the depth
	if( depth_texture && depth_texture.constructor === GL.Texture )
	{
		if(gl.webgl_version == 1 && !gl.extensions["WEBGL_depth_texture"] )
			throw("Rendering to depth texture not supported by your browser");

		if(this.stencil && depth_texture.format !== gl.DEPTH_STENCIL )
			console.warn("Stencil cannot be enabled if there is a depth texture with a DEPTH_STENCIL format");

		if( depth_texture.format == gl.DEPTH_STENCIL )
			gl.framebufferTexture2D( target, gl.DEPTH_STENCIL_ATTACHMENT, gl.TEXTURE_2D, depth_texture.handler, 0);
		else
			gl.framebufferTexture2D( target, gl.DEPTH_ATTACHMENT, gl.TEXTURE_2D, depth_texture.handler, 0);
	}
	else //create a renderbuffer to store depth
	{
		var depth_renderbuffer = null;
		
		//allows to reuse a renderbuffer between FBOs
		if( depth_texture && depth_texture.constructor === WebGLRenderbuffer && depth_texture.width == w && depth_texture.height == h ) 
			depth_renderbuffer = this._depth_renderbuffer = depth_texture;
		else
		{
			//create one
			depth_renderbuffer = this._depth_renderbuffer = this._depth_renderbuffer || gl.createRenderbuffer();
			depth_renderbuffer.width = w;
			depth_renderbuffer.height = h;
		}
		
		gl.bindRenderbuffer( gl.RENDERBUFFER, depth_renderbuffer );
		if(this.stencil)
		{
			gl.renderbufferStorage( gl.RENDERBUFFER, gl.DEPTH_STENCIL, w, h );
			gl.framebufferRenderbuffer( target, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, depth_renderbuffer );
		}
		else
		{
			gl.renderbufferStorage( gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, w, h );
			gl.framebufferRenderbuffer( target, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, depth_renderbuffer );
		}
	}

	//bind buffers for the colors
	if(color_textures && color_textures.length)
	{
		this.order = []; //draw_buffers request the use of an array with the order of the attachments
		for(var i = 0; i < color_textures.length; i++)
		{
			var t = color_textures[i];

			//not a bug, gl.COLOR_ATTACHMENT0 + i because COLOR_ATTACHMENT is sequential numbers
			gl.framebufferTexture2D( target, gl.COLOR_ATTACHMENT0 + i, gl.TEXTURE_2D, t.handler, 0 );
			this.order.push( gl.COLOR_ATTACHMENT0 + i );
		}
	}
	else //create renderbuffer to store color
	{
		var color_renderbuffer = this._color_renderbuffer = this._color_renderbuffer || gl.createRenderbuffer();
		color_renderbuffer.width = w;
		color_renderbuffer.height = h;
		gl.bindRenderbuffer( gl.RENDERBUFFER, color_renderbuffer );
		gl.renderbufferStorage( gl.RENDERBUFFER, gl.RGBA4, w, h );
		gl.framebufferRenderbuffer( target, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, color_renderbuffer );
	}

	//detach old ones (only if is reusing a FBO with a different set of textures)
	var num = color_textures ? color_textures.length : 0;
	for(var i = num; i < this._num_binded_textures; ++i)
		gl.framebufferTexture2D( target, gl.COLOR_ATTACHMENT0 + i, gl.TEXTURE_2D, null, 0);
	this._num_binded_textures = num;

	this._stencil_enabled = this.stencil;

	/* does not work, must be used with the depth_stencil
	if(this.stencil && !depth_texture)
	{
		var stencil_buffer = this._stencil_buffer = this._stencil_buffer || gl.createRenderbuffer();
		stencil_buffer.width = w;
		stencil_buffer.height = h;
		gl.bindRenderbuffer( gl.RENDERBUFFER, stencil_buffer );
		gl.renderbufferStorage( gl.RENDERBUFFER, gl.STENCIL_INDEX8, w, h);
		gl.framebufferRenderbuffer( gl.FRAMEBUFFER, gl.STENCIL_ATTACHMENT, gl.RENDERBUFFER, stencil_buffer );
		this._stencil_enabled = true;
	}
	else
	{
		this._stencil_buffer = null;
		this._stencil_enabled = false;
	}
	*/

	//when using more than one texture you need to use the multidraw extension
	if(color_textures && color_textures.length > 1)
	{
		if( ext )
			ext.drawBuffersWEBGL( this.order );
		else
			gl.drawBuffers( this.order );
	}

	//check completion
	var complete = gl.checkFramebufferStatus( target );
	if(complete !== gl.FRAMEBUFFER_COMPLETE)
		throw("FBO not complete: " + complete);

	//restore state
	gl.bindTexture(gl.TEXTURE_2D, null);
	gl.bindRenderbuffer(gl.RENDERBUFFER, null);
	if(!skip_disable)
		gl.bindFramebuffer( target, this._old_fbo_handler );
}

/**
* Enables this FBO (from now on all the render will be stored in the textures attached to this FBO
* It stores the previous viewport to restore it afterwards, and changes it to full FBO size
* @method bind
* @param {boolean} keep_old keeps the previous FBO is one was attached to restore it afterwards
*/
FBO.prototype.bind = function( keep_old )
{
	if(!this.color_textures.length && !this.depth_texture)
		throw("FBO: no textures attached to FBO");
	this._old_viewport.set( gl.viewport_data );

	if(keep_old)
		this._old_fbo_handler = gl.getParameter( gl.FRAMEBUFFER_BINDING );
	else
		this._old_fbo_handler = null;

	if(this._old_fbo_handler != this.handler )
		gl.bindFramebuffer( gl.FRAMEBUFFER, this.handler );

	//mark them as in use in the FBO
	for(var i = 0; i < this.color_textures.length; ++i)
		this.color_textures[i]._in_current_fbo = true;
	if(this.depth_texture)
		this.depth_texture._in_current_fbo = true;

	gl.viewport( 0,0, this.width, this.height );
}

/**
* Disables this FBO, if it was binded with keep_old then the old FBO is enabled, otherwise it will render to the screen
* Restores viewport to previous
* @method unbind
*/
FBO.prototype.unbind = function()
{
	gl.bindFramebuffer( gl.FRAMEBUFFER, this._old_fbo_handler );
	this._old_fbo_handler = null;
	gl.setViewport( this._old_viewport );

	//mark the textures as no longer in use
	for(var i = 0; i < this.color_textures.length; ++i)
		this.color_textures[i]._in_current_fbo = false;
	if(this.depth_texture)
		this.depth_texture._in_current_fbo = false;
}

//binds another FBO without switch back to previous (faster)
FBO.prototype.switchTo = function( next_fbo )
{
	next_fbo._old_fbo_handler = this._old_fbo_handler;
	next_fbo._old_viewport.set( this._old_viewport );
	gl.bindFramebuffer( gl.FRAMEBUFFER, next_fbo.handler );
	this._old_fbo_handler = null;
	gl.viewport( 0,0, this.width, this.height );

	//mark the textures as no longer in use
	for(var i = 0; i < this.color_textures.length; ++i)
		this.color_textures[i]._in_current_fbo = false;
	if(this.depth_texture)
		this.depth_texture._in_current_fbo = false;

	//mark them as in use in the FBO
	for(var i = 0; i < next_fbo.color_textures.length; ++i)
		next_fbo.color_textures[i]._in_current_fbo = true;
	if(next_fbo.depth_texture)
		next_fbo.depth_texture._in_current_fbo = true;
}

FBO.prototype.delete = function()
{
	gl.deleteFramebuffer( this.handler );
	this.handler = null;
}