API Docs for:
Show:

File: ../src/render/renderFrameContext.js

///@INFO: BASE
/**	RenderFrameContext
*	This class is used when you want to render the scene not to the screen but to some texture for postprocessing
*	It helps to create the textures and bind them easily, add extra buffers or show it on the screen.
*	Check the FrameFX and CameraFX components to see it in action.
*   Dependencies: LS.Renderer (writes there only)
*
* @class RenderFrameContext
* @namespace LS
* @constructor
*/
function RenderFrameContext( o )
{
	this.width = 0; //0 means the same size as the viewport, negative numbers mean reducing the texture in half N times
	this.height = 0; //0 means the same size as the viewport
	this.precision = RenderFrameContext.DEFAULT_PRECISION; //LOW_PRECISION uses a byte, MEDIUM uses a half_float, HIGH uses a float, or directly the texture type (p.e gl.UNSIGNED_SHORT_4_4_4_4 )
	this.filter_texture = true; //magFilter: in case the texture is shown, do you want to see it pixelated?
	this.format = GL.RGB; //how many color channels, or directly the texture internalformat 
	this.use_depth_texture = true; //store the depth in a texture
	this.use_stencil_buffer = false; //add an stencil buffer (cannot be read as a texture in webgl)
	this.num_extra_textures = 0; //number of extra textures in case we want to render to several buffers
	this.name = null; //if a name is provided all the textures will be stored in the LS.ResourcesManager

	this.generate_mipmaps = false; //try to generate mipmaps if possible (only when the texture is power of two)
	this.adjust_aspect = false; //when the size doesnt match the canvas size it could look distorted, settings this to true will fix the problem
	this.clone_after_unbind = false; //clones the textures after unbinding it. Used when the texture will be in the 3D scene

	this._fbo = null;
	this._color_texture = null;
	this._depth_texture = null;
	this._textures = []; //all color textures (the first will be _color_texture)
	this._cloned_textures = null; //in case we set the clone_after_unbind to true

	this._version = 1; //to detect changes
	this._minFilter = gl.NEAREST;

	if(o)
		this.configure(o);
}

RenderFrameContext.current = null;
RenderFrameContext.stack = [];

RenderFrameContext.DEFAULT_PRECISION = 0; //selected by the renderer
RenderFrameContext.LOW_PRECISION = 1; //byte
RenderFrameContext.MEDIUM_PRECISION = 2; //half_float or float
RenderFrameContext.HIGH_PRECISION = 3; //float

RenderFrameContext.DEFAULT_PRECISION_WEBGL_TYPE = GL.UNSIGNED_BYTE;

RenderFrameContext["@width"] = { type: "number", step: 1, precision: 0 };
RenderFrameContext["@height"] = { type: "number", step: 1, precision: 0 };

//definitions for the GUI
RenderFrameContext["@precision"] = { widget: "combo", values: { 
	"default": RenderFrameContext.DEFAULT_PRECISION, 
	"low": RenderFrameContext.LOW_PRECISION,
	"medium": RenderFrameContext.MEDIUM_PRECISION,
	"high": RenderFrameContext.HIGH_PRECISION
	}
};

RenderFrameContext["@format"] = { widget: "combo", values: { 
		"RGB": GL.RGB,
		"RGBA": GL.RGBA
	}
};

RenderFrameContext["@num_extra_textures"] = { type: "number", step: 1, min: 0, max: 4, precision: 0 };
RenderFrameContext["@name"] = { type: "string" };

RenderFrameContext.prototype.clear = function()
{
	if(this.name)
	{
		for(var i = 0; i < this._textures.length; ++i)
			delete LS.ResourcesManager.textures[ this.name + (i > 1 ? i : "") ];
		if(this._depth_texture)
			delete LS.ResourcesManager.textures[ this.name + "_depth"];
	}

	this._fbo = null;
	this._textures = [];
	this._color_texture = null;
	this._depth_textures = null;
}

RenderFrameContext.prototype.configure = function(o)
{
	this.width = o.width || 0;
	this.height = o.height || 0;
	this.format = o.format || GL.RGBA;
	this.precision = o.precision || 0;
	this.filter_texture = !!o.filter_texture;
	this.adjust_aspect = !!o.adjust_aspect;
	this.use_depth_texture = !!o.use_depth_texture;
	this.use_stencil_buffer = !!o.use_stencil_buffer;
	this.num_extra_textures = o.num_extra_textures || 0;
	this.name = o.name;
	this.clone_after_unbind = !!o.clone_after_unbind;
}

RenderFrameContext.prototype.serialize = function()
{
	return {
		width: this.width,
		height:  this.height,
		filter_texture: this.filter_texture,
		precision:  this.precision,
		format: this.format,
		adjust_aspect: this.adjust_aspect,
		use_depth_texture:  this.use_depth_texture,
		use_stencil_buffer: this.use_stencil_buffer,
		num_extra_textures:  this.num_extra_textures,
		clone_after_unbind: this.clone_after_unbind,
		name: this.name
	};
}

RenderFrameContext.prototype.prepare = function( viewport_width, viewport_height )
{
	//compute the right size for the textures
	var final_width = this.width;
	var final_height = this.height;
	if(final_width == 0)
		final_width = viewport_width;
	else if(final_width < 0)
		final_width = viewport_width >> Math.abs( this.width ); //subsampling
	if(final_height == 0)
		final_height = viewport_height;
	else if(final_height < 0)
		final_height = viewport_height >> Math.abs( this.height ); //subsampling

	var format = this.format;
	var magFilter = this.filter_texture ? gl.LINEAR : gl.NEAREST ;
	var type = 0;

	var minFilter = gl.LINEAR;
	if(this.generate_mipmaps && GL.isPowerOfTwo(final_width) && GL.isPowerOfTwo(final_height) )
		minFilter = gl.LINEAR_MIPMAP_LINEAR;
	this._minFilter = minFilter;

	switch( this.precision )
	{
		case RenderFrameContext.LOW_PRECISION:
			type = gl.UNSIGNED_BYTE; break;
		case RenderFrameContext.MEDIUM_PRECISION:
			type = gl.HIGH_PRECISION_FORMAT; break; //gl.HIGH_PRECISION_FORMAT is HALF_FLOAT_OES, if not supported then is FLOAT, otherwise is UNSIGNED_BYTE
		case RenderFrameContext.HIGH_PRECISION:
			type = gl.FLOAT; break;
		case RenderFrameContext.DEFAULT_PRECISION:
			type = RenderFrameContext.DEFAULT_PRECISION_WEBGL_TYPE; break;
		default:
			type = this.precision; break; //used for custom formats
	}

	var textures = this._textures;

	//for the color: check that the texture size matches
	if( !this._color_texture || 
		this._color_texture.width != final_width || this._color_texture.height != final_height || 
		this._color_texture.type != type || this._color_texture.format != format || this._color_texture.minFilter != minFilter )
		this._color_texture = new GL.Texture( final_width, final_height, { minFilter: minFilter, magFilter: magFilter, format: format, type: type });
	else
		this._color_texture.setParameter( gl.TEXTURE_MAG_FILTER, magFilter );
	textures[0] = this._color_texture;

	//extra color texture (multibuffer rendering)
	var total_extra = Math.min( this.num_extra_textures, 4 );
	
	//extra buffers not supported in this webgl context
	if(gl.webgl_version == 1 && !gl.extensions["WEBGL_draw_buffers"])
		total_extra = 0;

	for(var i = 0; i < total_extra; ++i) //MAX is 4
	{
		var extra_texture = textures[1 + i];
		if( (!extra_texture || extra_texture.width != final_width || extra_texture.height != final_height || extra_texture.type != type || extra_texture.format != format || extra_texture.minFilter != minFilter) )
			extra_texture = new GL.Texture( final_width, final_height, { minFilter: minFilter, magFilter: magFilter, format: format, type: type });
		else
			extra_texture.setParameter( gl.TEXTURE_MAG_FILTER, magFilter );
		textures[1 + i] = extra_texture;
	}

	//for the depth
	var depth_format = gl.DEPTH_COMPONENT;
	var depth_type = gl.UNSIGNED_INT;

	if(this.use_stencil_buffer && gl.extensions.WEBGL_depth_texture)
	{
		depth_format = gl.DEPTH_STENCIL;
		depth_type = gl.extensions.WEBGL_depth_texture.UNSIGNED_INT_24_8_WEBGL;
	}

	if( this.use_depth_texture && 
		(!this._depth_texture || this._depth_texture.width != final_width || this._depth_texture.height != final_height || this._depth_texture.format != depth_format || this._depth_texture.type != depth_type ) && 
		gl.extensions["WEBGL_depth_texture"] )
		this._depth_texture = new GL.Texture( final_width, final_height, { filter: gl.NEAREST, format: depth_format, type: depth_type });
	else if( !this.use_depth_texture )
		this._depth_texture = null;

	//we will store some extra info in the depth texture for the near and far plane distances
	if(this._depth_texture)
	{
		if(!this._depth_texture.near_far_planes)
			this._depth_texture.near_far_planes = vec2.create();
	}

	//create FBO
	if( !this._fbo )
		this._fbo = new GL.FBO();

	//cut extra
	textures.length = 1 + total_extra;

	//assign textures (this will enable the FBO but it will restore the old one after finishing)
	this._fbo.stencil = this.use_stencil_buffer;
	this._fbo.setTextures( textures, this._depth_texture );
	this._version += 1;
}

/**
* Called to bind the rendering to this context, from now on all the render will be stored in the textures inside
*
* @method enable
*/
RenderFrameContext.prototype.enable = function( render_settings, viewport, camera )
{
	viewport = viewport || gl.viewport_data;

	//create FBO and textures (pass width and height of current viewport)
	this.prepare( viewport[2], viewport[3] );

	if(!this._fbo)
		throw("No FBO created in RenderFrameContext");

	//enable FBO
	RenderFrameContext.enableFBO( this._fbo, this.adjust_aspect );

	if(LS.RenderFrameContext.current)
		RenderFrameContext.stack.push( LS.RenderFrameContext.current );
	LS.RenderFrameContext.current = this;

	//set depth info inside the texture
	camera = camera || LS.Renderer._current_camera;
	if(this._depth_texture && camera)
	{
		this._depth_texture.near_far_planes[0] = camera.near;
		this._depth_texture.near_far_planes[1] = camera.far;
	}
}

//we cannot read and write in the same buffer, so we need to clone the textures
//done from... ?
RenderFrameContext.prototype.cloneBuffers = function()
{
	//we do not call this._fbo.unbind because it will set the previous FBO
	gl.bindFramebuffer( gl.FRAMEBUFFER, null );

	///for every color texture
	if( this._textures.length )
	{
		if(!this._cloned_textures)
			this._cloned_textures = [];
		var textures = this._textures;
		this._cloned_textures.length = textures.length;
		for(var i = 0; i < textures.length; ++i)
		{
			var texture = textures[i];
			var cloned_texture = this._cloned_textures[i];
			if( !cloned_texture || cloned_texture.hasSameSize( texture[i] ) || !cloned_texture.hasSameProperties( texture ) )
				cloned_texture = this._cloned_textures[i] = new GL.Texture( texture.width, texture.height, texture.getProperties() );
			texture.copyTo( cloned_texture );
			if(i == 0)
				LS.ResourcesManager.textures[":color_buffer" ] = cloned_texture;
		}
	}

	//for depth
	if( this._depth_texture )
	{
		var depth = this._depth_texture;
		if(!this._cloned_depth_texture || this._cloned_depth_texture.width != depth.width || this._cloned_depth_texture.height != depth.height || !this._cloned_depth_texture.hasSameProperties( depth ) )
			this._cloned_depth_texture = new GL.Texture( depth.width, depth.height, depth.getProperties() );

		depth.copyTo( this._cloned_depth_texture );
		LS.ResourcesManager.textures[":depth_buffer" ] = this._cloned_depth_texture;
	}

	//rebind FBO
	gl.bindFramebuffer( gl.FRAMEBUFFER, this._fbo.handler );
}

/**
* Called to stop rendering to this context
*
* @method disable
*/
RenderFrameContext.prototype.disable = function()
{
	//sets some global parameters for aspect and current RFC
	RenderFrameContext.disableFBO( this._fbo );

	//if we need to store the textures in the ResourcesManager
	if(this.name)
	{
		var textures = this._textures;
		for(var i = 0; i < textures.length; ++i)
		{
			var name = this.name + (i > 0 ? i : "");
			textures[i].filename = name;
			var final_texture = textures[i];

			//only clone main color if requested
			if( this.clone_after_unbind && i === 0 )
			{
				if( !this._cloned_texture || 
					this._cloned_texture.width !== final_texture.width || 
					this._cloned_texture.height !== final_texture.height ||
					this._cloned_texture.type !== final_texture.type )
					this._cloned_texture = final_texture.clone();
				else
					final_texture.copyTo( this._cloned_texture );
				final_texture = this._cloned_texture;
			}

			if( this._minFilter == gl.LINEAR_MIPMAP_LINEAR )
			{
				final_texture.bind(0);
				gl.generateMipmap(gl.TEXTURE_2D);
				final_texture.has_mipmaps = true;
			}

			LS.ResourcesManager.textures[ name ] = final_texture;
		}

		if(this._depth_texture)
		{
			var name = this.name + "_depth";
			this._depth_texture.filename = name;
			LS.ResourcesManager.textures[ name ] = this._depth_texture;
			//LS.ResourcesManager.textures[ ":depth" ] = this._depth_texture;
		}
	}

	if( RenderFrameContext.stack.length )
		LS.RenderFrameContext.current = RenderFrameContext.stack.pop();
	else
		LS.RenderFrameContext.current = null;
}

/**
* returns the texture containing the data rendered in this context
*
* @method getColorTexture
* @param {number} index the number of the texture (in case there is more than one)
* @return {GL.Texture} the texture
*/
RenderFrameContext.prototype.getColorTexture = function(num)
{
	return this._textures[ num || 0 ] || null;
}

/**
* returns the depth texture containing the depth data rendered in this context (in case the use_depth_texture is set to true)
*
* @method getDepthTexture
* @return {GL.Texture} the depth texture
*/
RenderFrameContext.prototype.getDepthTexture = function()
{
	return this._depth_texture || null;
}

/**
* Fills the textures with a flat color
* @method clearTextures
*/
RenderFrameContext.prototype.clearTextures = function()
{
	for(var i = 0; i < this._textures.length; ++i)
	{
		var texture = this._textures[i];
		if(!texture)
			continue;
		texture.fill([0,0,0,0]);
	}
}

//enables the FBO and sets every texture with a flag so it cannot be used during the rendering process
RenderFrameContext.enableFBO = function( fbo, adjust_aspect )
{
	fbo.bind( true ); //changes viewport to full FBO size (saves old)

	LS.Renderer._full_viewport.set( gl.viewport_data );
	if( adjust_aspect )
	{
		fbo._old_aspect = LS.Renderer.global_aspect;
		LS.Renderer.global_aspect = (gl.canvas.width / gl.canvas.height) / (fbo.color_textures[0].width / fbo.color_textures[0].height);
	}
	else
		delete fbo._old_aspect;
}

RenderFrameContext.disableFBO = function( fbo )
{
	fbo.unbind(); //restores viewport to old saved one
	LS.Renderer._full_viewport.set( fbo._old_viewport );
	if( fbo._old_aspect )
		LS.Renderer.global_aspect = fbo._old_aspect;
}


/**
* Render the context of the context to the viewport (allows to apply FXAA)
*
* @method show
* @param {boolean} use_antialiasing in case you want to render with FXAA antialiasing
*/
RenderFrameContext.prototype.show = function( use_antialiasing )
{
	var texture = this._color_texture;
	if(!use_antialiasing)
	{
		texture.toViewport();
		return;
	}

	var viewport = gl.getViewport();
	var shader = GL.Shader.getFXAAShader();
	var mesh = GL.Mesh.getScreenQuad();
	texture.bind(0);
	shader.uniforms( { u_texture:0, uViewportSize: viewport.subarray(2,4), u_iViewportSize: [1 / texture.width, 1 / texture.height]} ).draw( mesh );
}

//Resets the current WebGL fbo so it renders to the screen
RenderFrameContext.reset = function()
{
	gl.bindFramebuffer( gl.FRAMEBUFFER, null );
	LS.RenderFrameContext.current = null;
	LS.RenderFrameContext.stack.length = 0;
}


LS.RenderFrameContext = RenderFrameContext;