var math = require('../../../math'),
utils = require('../../../utils'),
CONST = require('../../../../const'),
// StencilManager = require('../managers/StencilManager'),
StencilMaskStack = require('./StencilMaskStack');
/**
* @author Mat Groves http://matgroves.com/ @Doormat23
*/
/**
* @class
* @param gl {WebGLRenderingContext} the current WebGL drawing context
* @param width {number} the horizontal range of the filter
* @param height {number} the vertical range of the filter
* @param scaleMode {number} See {@link SCALE_MODES} for possible values
* @param resolution {number} the current resolution
* @param root {boolean} Whether this object is the root element or not
*/
var RenderTarget = function(gl, width, height, scaleMode, resolution, root) {
// TODO Resolution could go here ( eg low res blurs )
/**
* The current WebGL drawing context.
*
* @member {WebGLRenderingContext}
*/
this.gl = gl;
// next time to create a frame buffer and texture
/**
* A frame buffer
*
* @member {WebGLFrameBuffer}
*/
this.frameBuffer = null;
/**
* The texture
*
* @member {Texture}
*/
this.texture = null;
/**
* The size of the object as a rectangle
*
* @member {Rectangle}
*/
this.size = new math.Rectangle(0, 0, 1, 1);
/**
* The current resolution
*
* @member {number}
*/
this.resolution = resolution || CONST.RESOLUTION;
/**
* The projection matrix
*
* @member {Matrix}
*/
this.projectionMatrix = new math.Matrix();
/**
* The object's transform
*
* @member {Matrix}
*/
this.transform = null;
/**
* The frame.
*
* @member {Rectangle}
*/
this.frame = null;
/**
* The stencil buffer stores masking data for the render target
*
* @member {WebGLRenderBuffer}
*/
this.stencilBuffer = null;
/**
* The data structure for the stencil masks
*
* @member {StencilMaskStack}
*/
this.stencilMaskStack = new StencilMaskStack();
/**
* Stores filter data for the render target
*
* @member {object[]}
*/
this.filterStack = [
{
renderTarget:this,
filter:[],
bounds:this.size,
},
];
/**
* The scale mode.
*
* @member {number}
* @default SCALE_MODES.DEFAULT
* @see SCALE_MODES
*/
this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT;
/**
* Whether this object is the root element or not
*
* @member {boolean}
*/
this.root = root;
if (!this.root) {
// this.flipY = true;
this.frameBuffer = gl.createFramebuffer();
/*
A frame buffer needs a target to render to..
create a texture and bind it attach it to the framebuffer..
*/
this.texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, this.texture);
// set the scale properties of the texture..
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, scaleMode === CONST.SCALE_MODES.LINEAR ? gl.LINEAR : gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, scaleMode === CONST.SCALE_MODES.LINEAR ? gl.LINEAR : gl.NEAREST);
// check to see if the texture is a power of two!
var isPowerOfTwo = utils.isPowerOfTwo(width, height);
// TODO for 99% of use cases if a texture is power of two we should tile the texture...
if (!isPowerOfTwo) {
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);
}
else {
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT);
}
gl.bindFramebuffer(gl.FRAMEBUFFER, this.frameBuffer);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.texture, 0);
}
this.resize(width, height);
};
RenderTarget.prototype.constructor = RenderTarget;
module.exports = RenderTarget;
/**
* Clears the filter texture.
*
* @param [bind=false] {boolean} Should we bind our framebuffer before clearing?
*/
RenderTarget.prototype.clear = function(bind) {
var gl = this.gl;
if (bind) {
gl.bindFramebuffer(gl.FRAMEBUFFER, this.frameBuffer);
}
gl.clearColor(0,0,0,0);
gl.clear(gl.COLOR_BUFFER_BIT);
};
/**
* Binds the stencil buffer.
*
*/
RenderTarget.prototype.attachStencilBuffer = function() {
if (this.stencilBuffer) {
return;
}
/**
* The stencil buffer is used for masking in pixi
* lets create one and then add attach it to the framebuffer..
*/
if (!this.root) {
var gl = this.gl;
this.stencilBuffer = gl.createRenderbuffer();
gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencilBuffer);
gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, this.stencilBuffer);
gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, this.size.width * this.resolution , this.size.height * this.resolution);
}
};
/**
* Binds the buffers and initialises the viewport.
*
*/
RenderTarget.prototype.activate = function() {
// TOOD refactor usage of frame..
var gl = this.gl;
gl.bindFramebuffer(gl.FRAMEBUFFER, this.frameBuffer);
var projectionFrame = this.frame || this.size;
// TODO add a dirty flag to this of a setter for the frame?
this.calculateProjection(projectionFrame);
if (this.transform) {
this.projectionMatrix.append(this.transform);
}
gl.viewport(0,0, projectionFrame.width * this.resolution, projectionFrame.height * this.resolution);
};
/**
* Updates the projection matrix based on a projection frame (which is a rectangle)
*
*/
RenderTarget.prototype.calculateProjection = function(projectionFrame) {
var pm = this.projectionMatrix;
pm.identity();
if (!this.root) {
pm.a = 1 / projectionFrame.width * 2;
pm.d = 1 / projectionFrame.height * 2;
pm.tx = -1 - projectionFrame.x * pm.a;
pm.ty = -1 - projectionFrame.y * pm.d;
}
else {
pm.a = 1 / projectionFrame.width * 2;
pm.d = -1 / projectionFrame.height * 2;
pm.tx = -1 - projectionFrame.x * pm.a;
pm.ty = 1 - projectionFrame.y * pm.d;
}
};
/**
* Resizes the texture to the specified width and height
*
* @param width {Number} the new width of the texture
* @param height {Number} the new height of the texture
*/
RenderTarget.prototype.resize = function(width, height) {
width = width | 0;
height = height | 0;
if (this.size.width === width && this.size.height === height) {
return;
}
this.size.width = width;
this.size.height = height;
if (!this.root) {
var gl = this.gl;
gl.bindTexture(gl.TEXTURE_2D, this.texture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width * this.resolution, height * this.resolution , 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
if (this.stencilBuffer) {
// update the stencil buffer width and height
gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencilBuffer);
gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, width * this.resolution, height * this.resolution);
}
}
var projectionFrame = this.frame || this.size;
this.calculateProjection(projectionFrame);
};
/**
* Destroys the render target.
*
*/
RenderTarget.prototype.destroy = function() {
var gl = this.gl;
gl.deleteRenderbuffer(this.stencilBuffer);
gl.deleteFramebuffer(this.frameBuffer);
gl.deleteTexture(this.texture);
this.frameBuffer = null;
this.texture = null;
};