/*
* SpriteStage
* Visit http://createjs.com/ for documentation, updates and examples.
*
* Copyright (c) 2010 gskinner.com, inc.
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
/**
* @module EaselJS
*/
this.createjs = this.createjs||{};
(function() {
"use strict";
/**
* README IF EDITING:
* Terminology for developers:
*
* Vertex: a point that help defines a shape, 3 per triangle. Usually has an x,y,z but can have more/less info.
* Vertex Property: a piece of information attached to the vertex like x,y,z
* Index/Indecies: used in groups of 3 to define a triangle, points to vertecies by their index in an array (some render modes do not use these)
* Card: a group of 2 triangles used to display a rectangular image
* U/V: common names for the [0-1] texture co-ordinates on an image
* Batch: a single call to the renderer, best done as little as possible so multiple cards are put into a single batch
* Buffer: WebGL array data
* Program/Shader: For every vertex we run the Vertex shader, this information is then passed to a paired Fragment shader. When combined and paired these are a shader "program"
* Texture: WebGL representation of image data and associated extra information
*
**/
/**
* A Sprite Stage is the root level {{#crossLink "Container"}}{{/crossLink}} for an webGL optimized display list. Each time its {{#crossLink "Stage/tick"}}{{/crossLink}}
* method is called, it will render its display list to its target canvas ignoring non webGL compatible Display Objects.
* On devices or browsers that don't support WebGL, content will automatically be rendered via canvas 2D.
*
* Complications:
* - Only Sprite, Container, BitmapText, Bitmap, and DOMElement are rendered when added to the display list.
* - To display something SpriteStage cannot normally render, cache the object. A cached object is the same to the renderer as a new image regardless of its contents.
* - Images are wrapped as a webGL texture, graphics cards have a limit to concurrent textures, too many textures will slow performance. Ironically meaning caching may slow WebGL.
* - If new images are continually added and removed from the display list it will leak memory due to WebGL Texture wrappers being made.
//TODO: add in a hook so that people can easily clear old texture memory
* - Clone an image node (DOM/Canvas Element) to re-use it between multiple SpriteStage instances, the GPU texture loading and tracking is not advanced enough yet.
* - You must call updateViewport if you resize your canvas after making a SpriteStage, this will properly size the 3D context stored in memory, this won't affect the DOM.
*
* <h4>How to use Example</h4>
* This example creates a sprite stage, adds a child to it, then uses {{#crossLink "Ticker"}}{{/crossLink}} to update the child
* and redraw the stage using {{#crossLink "SpriteStage/update"}}{{/crossLink}}.
*
* var stage = new createjs.SpriteStage("canvasElementId", false, false);
* stage.updateViewport(800, 600);
* var image = new createjs.Bitmap("imagePath.png");
* stage.addChild(image);
* createjs.Ticker.addEventListener("tick", handleTick);
* function handleTick(event) {
* image.x += 10;
* stage.update();
* }
*
* <strong>Note:</strong> SpriteStage is not included in the minified version of EaselJS.
* <strong>Note:</strong> SpriteContainer was required by previous versions but is deprecated.
* <strong>Note:</strong> Previous versions had hard limitations about images per container etc, these have been removed.
*
* @class SpriteStage
* @extends Stage
* @constructor
* @param {HTMLCanvasElement | String | Object} canvas A canvas object that the SpriteStage will render to, or the string id
* of a canvas object in the current document.
* @param {Boolean} preserveDrawingBuffer If true, the canvas is NOT auto-cleared by WebGL (spec discourages true). Useful if you want to use p.autoClear = false.
* @param {Boolean} antialias Specifies whether or not the browser's WebGL implementation should try to perform antialiasing.
**/
function SpriteStage(canvas, preserveDrawingBuffer, antialias) {
this.Stage_constructor(canvas);
// public properties:
///////////////////////////////////////////////////////
/**
* Console log potential issues and problems, this is designed to have -minimal- performance impact so
* if you're looking for more extensive debugging information this may be inadequate.
* @property vocalDebug
* @type {Boolean}
* @default false
*/
this.vocalDebug = false;
// private properties:
///////////////////////////////////////////////////////
/**
* Used when the canvas context is created, requires context re-creation to update.
* Specifies whether or not the canvas is auto-cleared by WebGL. Spec discourages true.
* If true, the canvas is NOT auto-cleared by WebGL. WebGL replacement for `autoClear = false`.
* @property _preserveDrawingBuffer
* @protected
* @type {Boolean}
* @default false
**/
this._preserveDrawingBuffer = preserveDrawingBuffer||false; //TODO: DHG: look at turning this into autoClear directly
/**
* Specifies whether or not the browser's WebGL implementation should try to perform antialiasing.
* @property _antialias
* @protected
* @type {Boolean}
* @default false
**/
this._antialias = antialias||false; //TODO: DHG: ensure this does something
/**
* The width of the drawing surface used in memory.
* @property _viewportWidth
* @protected
* @type {Number}
* @default 0
**/
this._viewportWidth = 0;
/**
* The height of the drawing surface used in memory.
* @property _viewportHeight
* @protected
* @type {Number}
* @default 0
**/
this._viewportHeight = 0;
/**
* A 2D projection matrix used to convert WebGL's viewspace into canvas co-ordinates.
* Regular canvas display uses a Top-Left = 0,0 where WebGL uses a Center 0,0 Top-Right 1,1 system.
* @property _projectionMatrix
* @protected
* @type {Float32Array}
* @default null
**/
this._projectionMatrix = null;
/**
* The current WebGL canvas context. Often shorthanded to just "gl" in many parts of the code.
* @property _webGLContext
* @protected
* @type {WebGLRenderingContext}
* @default null
**/
this._webGLContext = null;
/**
* The color to use when the WebGL canvas has been cleared.
* @property _clearColor
* @protected
* @type {Object}
* @default black
**/
this._clearColor = { r: 0.0, g: 0.0, b: 0.0, a: 0.0 }; //TODO: formalize this approach into regular canvases
/**
* The maximum number of cards (aka a single sprite) that can be drawn in one draw call.
* Use getter/setters to modify otherwise internal buffers may be incorrect sizes.
* @property _maxCardsPerBatch
* @protected
* @type {Number}
* @default SpriteStage.DEFAULT_MAX_BATCH_SIZE
**/
this._maxCardsPerBatch = SpriteStage.DEFAULT_MAX_BATCH_SIZE; //TODO: write getter/setters for this
/**
* The shader program used to draw the current batch.
* @property _activeShader
* @protected
* @type {WebGLProgram}
* @default null
**/
this._activeShader = null;
/**
* The vertex position data for the current draw call.
* @property _vertices
* @protected
* @type {Float32Array}
* @default null
**/
this._vertices = null;
/**
* The WebGL buffer attached to _vertecies.
* @property _vertexPositionBuffer
* @protected
* @type {WebGLBuffer}
* @default null
**/
this._vertexPositionBuffer = null;
/**
* The vertices data for the current draw call.
* @property _uvs
* @protected
* @type {Float32Array}
* @default null
**/
this._uvs = null;
/**
* The WebGL buffer attached to _uvs.
* @property _uvPositionBuffer
* @protected
* @type {WebGLBuffer}
* @default null
**/
this._uvPositionBuffer = null;
/**
* The vertices data for the current draw call.
* @property _indecies
* @protected
* @type {Float32Array}
* @default null
**/
this._indecies = null;
/**
* The WebGL buffer attached to _indecies.
* @property _textureIndexBuffer
* @protected
* @type {WebGLBuffer}
* @default null
**/
this._textureIndexBuffer = null;
/**
* The vertices data for the current draw call.
* @property _alphas
* @protected
* @type {Float32Array}
* @default null
**/
this._alphas = null;
/**
* The WebGL buffer attached to _alphas.
* @property _alphaBuffer
* @protected
* @type {WebGLBuffer}
* @default null
**/
this._alphaBuffer = null;
/**
* An index based lookup of every WebGL Texture currently in use.
* @property _drawTexture
* @protected
* @type {Array}
* @default null
**/
this._textureDictionary = [];
/**
* A string based lookup hash of what index a texture is stored at in the dictionary.
* The lookup string is often the src url.
* @property _textureIDs
* @protected
* @type {Object}
* @default null
**/
this._textureIDs = {};
/**
* An array of all the textures currently loaded into the GPU, index in array matches GPU index.
* @property _batchTextures
* @protected
* @type {Array}
* @default null
*/
this._batchTextures = [];
/**
* How many concurrent textures the gpu can handle. Dynamically Get this value from WebGL during initilization.
* Spec states 8 is lowest guaranteed value but it could be higher.
* Do not set higher than the value returned by the GPU, and setting it lower will potentially reduce performance.
* gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS)
* Can also act as a length for _batchTextures
* @property _batchTextureCount
* @protected
* @type {Number}
* @default 8
*/
this._batchTextureCount = 8;
/**
* Location at which the last texture was inserted into a GPU slot in _batchTextures.
* Manual control of this variable could yield improvements in performance by intelligently replacing textures inside a batch.
* Impossible to write automated general use code for as it requires display list inspection/foreknowledge to attempt due to content knowledge.
* @protected
* @type {Number}
* @default -1
*/
this._lastTextureInsert = -1;
/**
* Location at which the last texture was inserted into the texture dictionary.
* Do not confuse with _lastTextureInsert location, this variable is for ensuring unique ids and unrelated.
* @protected
* @type {Number}
* @default -1
*/
this._lastTextureID = -1;
/**
* Current batch being drawn, a batch consists of a call to "drawElements" on the GPU. mnay may occur per draw.
* @protected
* @type {Number}
* @default 0
*/
this._batchID = 0;
/**
* Current draw being performed, may contain multiple batches. Comparing to _batchID can reveal batching efficiency.
* @protected
* @type {Number}
* @default 0
*/
this._drawID = 0;
this._isDrawing = 0;
this._lastTrackedCanvas = 0;
this.isCacheControlled = false;
this._cacheContainer = new createjs.Container();
// and begin
this._initializeWebGL();
}
var p = createjs.extend(SpriteStage, createjs.Stage);
// constants:
///////////////////////////////////////////////////////
/**
* The number of properties defined per vertex.
* x, y, textureU, textureV, textureIndex, alpha
* @property NUM_VERTEX_PROPERTIES
* @static
* @final
* @type {Number}
* @readonly
**/
SpriteStage.VERTEX_PROPERTY_COUNT = 6;
/**
* The number of traingle indicies it takes to form a Card. 3 per triangles, 2 triangles.
* @property NUM_VERTEX_PROPERTIES
* @static
* @final
* @type {Number}
* @readonly
**/
SpriteStage.INDICIES_PER_CARD = 6;
/**
* Default value for the maximum number of cards we want to process in a batch.
* See WEBGL_MAX_INDEX_NUM for a hard limit.
* @property DEFAULT_MAX_BATCH_SIZE
* @static
* @final
* @type {Number}
* @readonly
**/
SpriteStage.DEFAULT_MAX_BATCH_SIZE = 8000;
/**
* The maximum size WebGL allows for element index numbers: 16 bit unsigned integer.
* It takes 6 indcies to make a unique card
* @property MAX_INDEX_SIZE
* @static
* @final
* @type {Number}
* @readonly
**/
SpriteStage.WEBGL_MAX_INDEX_NUM = Math.pow(2, 16);
/**
* Default U/V rect for dealing with full coverage from an image source.
* @property UV_RECT
* @static
* @final
* @type {Object}
* @readonly
*/
SpriteStage.UV_RECT = {t:0, l:0, b:1, r:1};
SpriteStage.COVER_UV = new Float32Array([
0, 0, //TL
1, 0, //TR
0, 1, //BL
1, 0, //TR
1, 1, //BR
0, 1 //BL
]);
SpriteStage.COVER_UV_FLIP = new Float32Array([
0, 1, //TL
1, 1, //TR
0, 0, //BL
1, 1, //TR
1, 0, //BR
0, 0 //BL
]);
SpriteStage.COVER_VERT = new Float32Array([
-1, 1, //TL
1, 1, //TR
-1, -1, //BL
1, 1, //TR
1, -1, //BR
-1, -1 //BL
]);
SpriteStage.SHADER_VARYING_HEADER = (
"precision mediump float;" +
"varying vec2 vTextureCoord;" +
"varying lowp float indexPicker;" +
"varying lowp float alphaValue;"
);
SpriteStage.SHADER_VERTEX_HEADER = (
SpriteStage.SHADER_VARYING_HEADER +
"attribute vec2 vertexPosition;" +
"attribute vec2 uvPosition;" +
"attribute lowp float textureIndex;" +
"attribute lowp float objectAlpha;" +
"uniform mat4 pMatrix;"
);
SpriteStage.SHADER_FRAGMENT_HEADER = (
SpriteStage.SHADER_VARYING_HEADER +
"uniform sampler2D uSampler[{{count}}];"
);
SpriteStage.SHADER_VERTEX_BODY_REGULAR = (
"void main(void) {" +
//DHG TODO: why won't this work? Must be something wrong with the hand built matrix see js... bypass for now
//vertexPosition, round if flag
//"gl_Position = pMatrix * vec4(vertexPosition.x, vertexPosition.y, 0.0, 1.0);" +
"gl_Position = vec4("+
"(vertexPosition.x * pMatrix[0][0]) + pMatrix[3][0]," +
"(vertexPosition.y * pMatrix[1][1]) + pMatrix[3][1]," +
"pMatrix[3][2]," +
"1.0" +
");" +
"alphaValue = objectAlpha;" +
"indexPicker = textureIndex;" +
"vTextureCoord = uvPosition;" +
"}"
);
SpriteStage.SHADER_FRAGMENT_BODY_REGULAR = (
"void main(void) {" +
"int src = int(indexPicker);" +
"vec4 color = vec4(1.0, 0.0, 0.0, 1.0);" +
"if(src == 0) {" +
"color = texture2D(uSampler[0], vTextureCoord);" +
"{{alternates}}" +
"}" +
"gl_FragColor = vec4(color.rgb, color.a * alphaValue);" +
"}"
);
SpriteStage.SHADER_VERTEX_BODY_PARTICLE = (
SpriteStage.SHADER_VERTEX_BODY_REGULAR //TODO: DHG: a real particle shader
);
SpriteStage.SHADER_FRAGMENT_BODY_PARTICLE = (
SpriteStage.SHADER_FRAGMENT_BODY_REGULAR //TODO: DHG: a real particle shader
);
SpriteStage.COVER_VARYING_HEADER = (
"precision mediump float;" +
"varying highp vec2 vTextureCoord;"
);
SpriteStage.COVER_VERTEX_HEADER = (
SpriteStage.COVER_VARYING_HEADER +
"attribute vec2 vertexPosition;" +
"attribute vec2 uvPosition;"
);
SpriteStage.COVER_FRAGMENT_HEADER = (
SpriteStage.COVER_VARYING_HEADER +
"uniform sampler2D uSampler;"
);
SpriteStage.COVER_VERTEX_BODY_REGULAR = (
"void main(void) {" +
"gl_Position = vec4(vertexPosition.x, vertexPosition.y, 0.0, 1.0);" +
"vTextureCoord = uvPosition;" +
"}"
);
SpriteStage.COVER_FRAGMENT_BODY_REGULAR = (
"void main(void) {" +
"vec4 color = texture2D(uSampler, vTextureCoord);" +
"gl_FragColor = color;" +
"}"
);
// getter / setters:
///////////////////////////////////////////////////////
/**
* Indicates whether WebGL is being used for rendering. For example, this would be false if WebGL is not
* supported in the browser.
* @readonly
* @property isWebGL
* @type {Boolean}
**/
p._get_isWebGL = function() {
return !!this._webGLContext;
};
/**
* Indicates whether WebGL is being used for rendering. For example, this would be false if WebGL is not
* supported in the browser.
* @readonly
* @property contextWebGL
* @type {WebGLRenderingContext}
**/
p._get_contextWebGL = function() {
return this._webGLContext;
};
try {
Object.defineProperties(p, {
isWebGL: { get: p._get_isWebGL }
});
} catch (e) {} // TODO: use Log
// ctor:
///////////////////////////////////////////////////////
/**
*
* @method _initializeWebGL
* @protected
*/
p._initializeWebGL = function() {
if (this.canvas) {
if (!this._webGLContext || this._webGLContext.canvas !== this.canvas) {
// A context hasn't been defined yet,
// OR the defined context belongs to a different canvas, so reinitialize.
this._createWebGL();
}
} else {
this._webGLContext = null;
}
return this._webGLContext;
};
/**
*
* @method _createWebGL
* @protected
*/
p._createWebGL = function() {
// defaults and options
var options = {
//depth: false, // Disable the depth buffer as it isn't used.
alpha: false, // Make the canvas background transparent.
stencil: true,
antialias: this._antialias,
preserveDrawingBuffer: this._preserveDrawingBuffer,
premultipliedAlpha: true // Assume the drawing buffer contains colors with premultiplied alpha.
};
var gl = this._webGLContext = this._fetchWebGLContext(this.canvas, options);
this.updateSimultaneousTextureCount(gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS));
this._createBuffers(gl);
this._initTextures(gl);
//gl.clearColor(0.25, 0.25, 0.25, 1.0);
//gl.disable(gl.DEPTH_TEST);
gl.enable(gl.BLEND);
gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
this.updateViewport(this._viewportWidth || this.canvas.width, this._viewportHeight || this.canvas.height);
};
// public methods:
///////////////////////////////////////////////////////
/** docced in super class **/
p.update = function(props) {
//DHG TODO: test context swapping and re-acqusition
if (!this.canvas) { return; }
if (this.tickOnUpdate) { this.tick(props); }
this.dispatchEvent("drawstart"); // TODO: make cancellable?
if (this._webGLContext) {
// Use WebGL.
this._batchDraw(this, this._webGLContext);
} else {
// Use 2D.
if (this.autoClear) { this.clear(); }
var ctx = this.canvas.getContext("2d");
ctx.save();
this.updateContext(ctx);
this.draw(ctx, false);
ctx.restore();
}
this.dispatchEvent("drawend");
};
/**
* Clears the target canvas. Useful if {{#crossLink "Stage/autoClear:property"}}{{/crossLink}} is set to `false`.
* @method clear
**/
p.clear = function() {
if (!this.canvas) { return; }
if (this.isWebGLActive(this._webGLContext)) {
var gl = this._webGLContext;
// Use WebGL.
gl.clear(gl.COLOR_BUFFER_BIT);
} else {
// Use 2D.
var ctx = this.canvas.getContext("2d");
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.clearRect(0, 0, this.canvas.width + 1, this.canvas.height + 1);
}
};
p.isWebGLActive = function(ctx) {
return ctx &&
ctx instanceof WebGLRenderingContext &&
typeof WebGLRenderingContext !== 'undefined';
};
/**
* Draws the stage into the specified context (using WebGL) ignoring its shadow.
* If WebGL is not supported in the browser, it will default to a 2D context.
* Returns true if the draw was handled (useful for overriding functionality).
*
* NOTE: This method is mainly for internal use, though it may be useful for advanced uses.
* @method draw
* @param {CanvasRenderingContext2D} gl The canvas 2D context object to draw into.
* @param {Boolean} [ignoreCache=false] Indicates whether the draw operation should ignore any current cache.
* For example, used for drawing the cache (to prevent it from simply drawing an existing cache back
* into itself).
**/
p.draw = function(gl, ignoreCache) {
if (this.isWebGLActive(gl)) {
this._batchDraw(this, gl, ignoreCache);
return true;
} else {
return this.Stage_draw(gl, ignoreCache);
}
};
/**
* Draws the stage into the specified context (using WebGL) ignoring its shadow.
* If WebGL is not supported in the browser, it will default to a 2D context.
* Returns true if the draw was handled (useful for overriding functionality).
*
* NOTE: This method is mainly for internal use, though it may be useful for advanced uses.
* @method filterDraw
* @param {DisplayObject} target The object we're drawing into cache.
* For example, used for drawing the cache (to prevent it from simply drawing an existing cache back
* into itself).
**/
p.cacheDraw = function(target, filters) {
var gl = this._webGLContext;
this._shaderBackup = this._activeShader;
// create offset container for drawing item
var mtx = target.getMatrix();
mtx = mtx.clone().invert();
var container = this._cacheContainer;
container.children = [target];
container.transformMatrix = mtx;
var filterCount = filters.length;
if(filterCount) {
// we don't know which texture slot we're dealing with previously and we need one out of the way
// once we're using that slot activate it so when we make and bind our RenderTexture it's safe there
gl.activeTexture(gl.TEXTURE0+(this._batchTextureCount-1));
var renderTexture = this.getRenderBufferTexture(target._cacheWidth, target._cacheHeight);
// draw item to render texture I -> T
gl.bindFramebuffer(gl.FRAMEBUFFER, renderTexture._frameBuffer);
this._batchDraw(container, gl, true);
// TODO: because I'm using the last texture slot already a regular draw could cause issues depending upon content, find some way of blacklisting the last slot for insert into batch
// bind the result texture to slot 0 as all filters and cover draws assume original content is in slot 0
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, renderTexture);
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);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
var flipY = false;
// apply each filter in order, but remember to toggle used texture and used
for(var i=0; i<filterCount; i++) {
var filter = filters[i];
// now the old result is stored in slot 0, make a new render texture
gl.activeTexture(gl.TEXTURE0+(this._batchTextureCount-1));
renderTexture = this.getRenderBufferTexture(target._cacheWidth, target._cacheHeight);
gl.bindFramebuffer(gl.FRAMEBUFFER, renderTexture._frameBuffer);
// swap to correct shader
this._activeShader = this.getFilterShader(gl, filter);
if(!this._activeShader) { continue; }
// draw result to render texture R -> T
this._drawCover(gl, flipY);
// bind the result texture to slot 0 as all filters and cover draws assume original content is in slot 0
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, renderTexture);
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);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
// use flipping to keep things upright, things already cancel out on a single filter
if(filterCount > 1) {
flipY = !flipY;
//gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, flipY);
}
}
// is this for another stage or mine
if(this.isCacheControlled) {
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
// draw result to canvas R -> C
this._activeShader = this.getFilterShader(gl, null);
this._drawCover(gl, flipY);
} else {
//TODO: DHG: this is less than ideal a flipped inital render for this circumstance might help, adjust the perspective matrix?
if(flipY) {
gl.activeTexture(gl.TEXTURE0+(this._batchTextureCount-1));
renderTexture = this.getRenderBufferTexture(target._cacheWidth, target._cacheHeight);
gl.bindFramebuffer(gl.FRAMEBUFFER, renderTexture._frameBuffer);
this._activeShader = this.getFilterShader(gl, null);
this._drawCover(gl, !flipY);
}
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
// make sure the last texture is the active thing to draw
target.cacheCanvas = renderTexture;
}
} else {
// is this for another stage or mine
if(this.isCacheControlled) {
// draw item to canvas I -> C
this._batchDraw(container, gl, true);
} else {
// we don't know which texture slot we're dealing with previously and we need one out of the way
// once we're using that slot activate it so when we make and bind our RenderTexture it's safe there
gl.activeTexture(gl.TEXTURE0+(this._batchTextureCount-1));
var renderTexture = this.getRenderBufferTexture(target._cacheWidth, target._cacheHeight);
// draw item to render texture I -> T
gl.bindFramebuffer(gl.FRAMEBUFFER, renderTexture._frameBuffer);
this._batchDraw(container, gl, true);
// TODO: because I'm using the last texture slot already a regular draw could cause issues depending upon content, find some way of blacklisting the last slot for insert into batch
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
// make sure the last texture is the active thing to draw
target.cacheCanvas = renderTexture;
}
}
this._activeShader = this._shaderBackup;
};
/**
* Try to set the max textures the system can handle, should default to the hardware max.
* @method updateViewport
* @param {Number} count
*/
p.updateSimultaneousTextureCount = function(count) {
//TODO: DHG: make sure API works in all instances, may be some issues with buffers etc I haven't forseen
var gl = this._webGLContext;
var success = false;
this._batchTextureCount = count;
if(this._batchTextureCount < 1){ this._batchTextureCount = 1; }
while(!success) {
try{
this._activeShader = this._fetchShaderProgram(gl);
success = true;
} catch(e) {
if(this._batchTextureCount == 1){
throw("Cannot compile shader "+ e);
}
this._batchTextureCount -= 4;
if(this._batchTextureCount < 1){ this._batchTextureCount = 1; }
if(this.vocalDebug){
console.log("Reducing desired texture count due to errors: " + this._batchTextureCount);
}
}
}
};
/**
* Update the WebGL viewport. Note that this does NOT update the canvas element's width/height.
* @method updateViewport
* @param {Number} width Integer pixel size of render surface.
* @param {Number} height Integer pixel size of render surface.
**/
p.updateViewport = function (width, height) {
this._viewportWidth = width;
this._viewportHeight = height;
var gl = this._webGLContext;
if (gl) {
gl.viewport(0, 0, this._viewportWidth, this._viewportHeight);
// openGL works with a -1,1 space on its screen. It also follows Y-Up
// we need to flip the y, scale and then translate the co-ordinates to match this
// additionally we offset into they Y so the polygons are inside the camera's "clipping" plane
this._projectionMatrix = new Float32Array([
2 / width, 0, 0, 0,
0, -2 / height, 1, 0,
0, 0, 1, 0,
-1, 1, 0.1, 0
]);
}
};
p.getFilterShader = function(gl, filter) {
filter = filter || {};
var targetShader = this._activeShader;
//try{
targetShader = this._fetchShaderProgram(
gl, "custom",
filter.VTX_SHADER_BODY, filter.FRAG_SHADER_BODY,
filter.shaderParamSetup && filter.shaderParamSetup.bind(filter)
);
//} catch (e) {
// console.log("SHADER SWITCH FAILURE", e);
//}
return targetShader;
};
/**
* Clears an image's texture to free it up for garbage collection.
* @method clearImageTexture
* @param {HTMLImageElement} image
**/
p.clearImageTexture = function(image) {
image.__easeljs_texture = null;
};
/**
* Returns a string representation of this object.
* @method toString
* @return {String} a string representation of the instance.
**/
p.toString = function() {
return "[SpriteStage (name="+ this.name +")]";
};
/**
* Returns a base texture for use without forgetting any initilization
* @method getBaseTexture
* @param {HTMLImageElement} w The width of the texture, defaults to 1
* @param {HTMLImageElement} h The height of the texture, defaults to 1
* @param {HTMLImageElement} data in a Uint8Array width*height*4(rgba) long, defaults to a single pixel.
* @return {Texture} the basic texture instance.
**/
p.getBaseTexture = function(w, h, data) {
var width = w || 1;
var height = h || 1;
if(data === undefined){ data = new Uint8Array([0.1, 0.2, 0.3, 1.0]); }
var gl = this._webGLContext;
var texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texImage2D(
gl.TEXTURE_2D, // target
0, // level of detail
gl.RGBA, // internalformat
width, height, 0, // width, height, border (only for array/null sourced textures)
gl.RGBA, // format (match internal format)
gl.UNSIGNED_BYTE, // type of texture(pixel color depth)
data // image data
);
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);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
return texture;
};
/**
* Returns a base texture (see getBaseTexture) with an attached render buffer in texture._frameBuffer
* @method getBaseTexture
* @param {HTMLImageElement} w The width of the texture
* @param {HTMLImageElement} h The height of the texture
* @return {Texture} the basic texture instance.
**/
p.getRenderBufferTexture = function(w, h) {
var gl = this._webGLContext;
// get the texture and set its width and height for spoofing as an image
var renderTexture = this.getBaseTexture(w, h, null);
renderTexture.width = w;
renderTexture.height = h;
// get the frame buffer
var frameBuffer = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, frameBuffer);
// attach frame buffer to texture and provide cross links to look up each other
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, renderTexture, 0);
frameBuffer._renderTexture = renderTexture;
renderTexture._frameBuffer = frameBuffer;
// flag as an unstored texture, trying to store and maintain these would be complex due to
// issues like them being swapped aorund, plus tracking them in stored textures hold no benefits
renderTexture._storeID = -1;
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
return renderTexture;
};
/**
* Calculate the U/V co-ordinate based info for sprite frames. Instead of pixels a 0-1 space.
* Also includes the ability to get info back for a specific frame or only calculate that one frame.
* @param {SpriteSheet} spritesheet The spritesheet to process
* @param {frame} target The frame we're most worried about
* @param {Boolean} onlyTarget The DOM canvas element to attach to
* @method buildUVRects
* @return {UVRect} the target frame or a generic frame
*/
p.buildUVRects = function(spritesheet, target, onlyTarget) {
if(!spritesheet || !spritesheet._frames){ return result; }
if(target === undefined) { target = -1; }
if(onlyTarget === undefined) { onlyTarget = false; }
var start = (target != -1 && onlyTarget)?(target):(0);
var end = (target != -1 && onlyTarget)?(target+1):(spritesheet._frames.length);
for(var i=start; i<end; i++) {
var f = spritesheet._frames[i];
if(f.uvRect) { continue; }
if(f.image.width <= 0 || f.image.height <= 0) { continue; }
var r = f.rect;
f.uvRect = {
t: r.y / f.image.height, l: r.x / f.image.width,
b: (r.y + r.height) / f.image.height, r: (r.x + r.width) / f.image.width
};
}
return spritesheet._frames[(target != -1)?(target):(0)].uvRect || {t:0, l:0, b:1, r:1};
};
// private methods:
///////////////////////////////////////////////////////
/**
* Sets up and returns the webgl context for the canvas
* @param {Canvas} canvas The DOM canvas element to attach to
* @param {Object} options The options to be handed into the WebGL object, see WebGL spec
* @method _fetchWebGLContext
* @protected
*/
p._fetchWebGLContext = function(canvas, options) {
var gl;
try {
gl = canvas.getContext("webgl", options) || canvas.getContext("experimental-webgl", options);
} catch (e) {
// don't do anything in catch null will handle it and we may get false positives given the || operation
}
if (!gl) {
alert("Could not initialize WebGL");
} else {
gl.viewportWidth = canvas.width;
gl.viewportHeight = canvas.height;
}
return gl;
};
/**
* Create the completed Sahder Program from the vertex and fragment shaders
* @param {WebGLRenderingContext} gl
* @method _fetchShaderProgram
* @protected
*/
p._fetchShaderProgram = function(gl, shader, customVTX, customFRAG, shaderParamSetup) {
gl.useProgram(null); //saftey to avoid collisions
var targetFrag, targetVtx;
switch(shader) {
case "custom":
targetVtx = SpriteStage.COVER_VERTEX_HEADER;
targetFrag = SpriteStage.COVER_FRAGMENT_HEADER;
targetVtx += customVTX || SpriteStage.COVER_VERTEX_BODY_REGULAR;
targetFrag += customFRAG || SpriteStage.COVER_FRAGMENT_BODY_REGULAR;
break;
case "particle":
targetVtx = SpriteStage.SHADER_VERTEX_HEADER;
targetFrag = SpriteStage.SHADER_FRAGMENT_HEADER;
targetVtx += SpriteStage.SHADER_VERTEX_BODY_PARTICLE;
targetFrag += SpriteStage.SHADER_FRAGMENT_BODY_PARTICLE;
break;
default:
targetVtx = SpriteStage.SHADER_VERTEX_HEADER;
targetFrag = SpriteStage.SHADER_FRAGMENT_HEADER;
targetVtx += SpriteStage.SHADER_VERTEX_BODY_REGULAR;
targetFrag += SpriteStage.SHADER_FRAGMENT_BODY_REGULAR;
break;
}
//DHG might need to pre-process shader code so get the result
var vertexShader = this._createShader(gl, gl.VERTEX_SHADER, targetVtx);
var fragmentShader = this._createShader(gl, gl.FRAGMENT_SHADER, targetFrag);
var shaderProgram = gl.createProgram();
gl.attachShader(shaderProgram, vertexShader);
gl.attachShader(shaderProgram, fragmentShader);
gl.linkProgram(shaderProgram);
if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
gl.useProgram(this._activeShader);
throw(gl.getProgramInfoLog(shaderProgram));
}
gl.useProgram(shaderProgram);
switch(shader) {
case "custom":
shaderProgram.vertexPositionAttribute = gl.getAttribLocation(shaderProgram, "vertexPosition");
gl.enableVertexAttribArray(shaderProgram.vertexPositionAttribute);
shaderProgram.uvPositionAttribute = gl.getAttribLocation(shaderProgram, "uvPosition");
gl.enableVertexAttribArray(shaderProgram.uvPositionAttribute);
shaderProgram.samplerUniform = gl.getUniformLocation(shaderProgram, "uSampler");
gl.uniform1i(shaderProgram.samplerUniform, 0);
// if there's some custom attributes be sure to hook them up
if(shaderParamSetup) {
shaderParamSetup(gl, this, shaderProgram);
}
break;
case "particle":
default:
// get the places in memory the shader is stored so we can feed information into them
// then save it off on the shader because it's so tied to the shader itself
shaderProgram.vertexPositionAttribute = gl.getAttribLocation(shaderProgram, "vertexPosition");
gl.enableVertexAttribArray(shaderProgram.vertexPositionAttribute);
shaderProgram.uvPositionAttribute = gl.getAttribLocation(shaderProgram, "uvPosition");
gl.enableVertexAttribArray(shaderProgram.uvPositionAttribute);
shaderProgram.textureIndexAttribute = gl.getAttribLocation(shaderProgram, "textureIndex");
gl.enableVertexAttribArray(shaderProgram.textureIndexAttribute);
shaderProgram.alphaAttribute = gl.getAttribLocation(shaderProgram, "objectAlpha");
gl.enableVertexAttribArray(shaderProgram.alphaAttribute);
var samplers = [];
for(var i = 0; i < this._batchTextureCount; i++) {
samplers[i] = i;
}
shaderProgram.pMatrixUniform = gl.getUniformLocation(shaderProgram, "pMatrix");
shaderProgram.samplerData = samplers;
shaderProgram.samplerUniform = gl.getUniformLocation(shaderProgram, "uSampler");
gl.uniform1iv(shaderProgram.samplerUniform, samplers);
break;
}
gl.useProgram(this._activeShader);
return shaderProgram;
};
/**
* Creates a shader from the specified string.
* @method _createShader
* @param {WebGLRenderingContext} gl
* @param {Number} type The type of shader to create. gl.VERTEX_SHADER | gl.FRAGMENT_SHADER
* @param {String} str The definition for the shader.
* @return {WebGLShader}
* @protected
**/
p._createShader = function(gl, type, str) {
// inject the static number
str = str.replace("{{count}}", this._batchTextureCount);
// add in arbitrary line count
var insert = "";
for(var i=1; i<this._batchTextureCount; i++) {
insert += "} else if(src == "+ i +") { color = texture2D(uSampler["+ i +"], vTextureCoord);";
}
str = str.replace("{{alternates}}", insert);
var shader = gl.createShader(type);
gl.shaderSource(shader, str);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
throw(gl.getShaderInfoLog(shader));
return null;
}
return shader;
};
/**
* Sets up the necessary vertices and indices buffers.
* @method _createBuffers
* @param {WebGLRenderingContext} gl
* @protected
**/
p._createBuffers = function(gl) {
var groupCount = this._maxCardsPerBatch * SpriteStage.INDICIES_PER_CARD;
var groupSize, i;
// DHG, all buffers are created using this pattern
// create a webGL buffer
// attach it to WebGL
// figure out how many parts it has to an entry
// fill it with empty data to reserve the memory
// attach the empty data to the GPU
// track the sizes on the buffer object
/* DHG: a single buffer may be optimal in some situations and would be approached like this
DHG: currently not implemented due to lack of need
var vertexBuffer = this._vertexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
groupSize = 2 + 2 + 1 + 1; //x/y, u/v, index, alpha
var vertexData = this._vertexData = new Float32Array(groupCount * groupSize);
for(i=0; i<vertexData.length; i+=groupSize) {
vertexData[i+0] = vertexData[i+1] = 0;
vertexData[i+2] = vertexData[i+3] = 0.5;
vertexData[i+4] = 0;
vertexData[i+5] = 1;
}
vertexBuffer.itemSize = groupSize;
vertexBuffer.numItems = groupCount;
*/
// the actual position information
var vertexPositionBuffer = this._vertexPositionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexPositionBuffer);
groupSize = 2;
var vertices = this._vertices = new Float32Array(groupCount * groupSize);
for(i=0; i<vertices.length; i+=groupSize) { vertices[i+0] = vertices[i+1] = 0; }
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.DYNAMIC_DRAW);
vertexPositionBuffer.itemSize = groupSize;
vertexPositionBuffer.numItems = groupCount;
// where on the texture it gets its information
var uvPositionBuffer = this._uvPositionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, uvPositionBuffer);
groupSize = 2;
var uvs = this._uvs = new Float32Array(groupCount * groupSize);
for(i=0; i<uvs.length; i+=groupSize) { uvs[i+0] = uvs[i+1] = 0; }
gl.bufferData(gl.ARRAY_BUFFER, uvs, gl.DYNAMIC_DRAW);
uvPositionBuffer.itemSize = groupSize;
uvPositionBuffer.numItems = groupCount;
// what texture it should use
var textureIndexBuffer = this._textureIndexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, textureIndexBuffer);
groupSize = 1;
var indecies = this._indecies = new Float32Array(groupCount * groupSize);
for(i=0; i<indecies.length; i++) { indecies[i] = 0; }
gl.bufferData(gl.ARRAY_BUFFER, indecies, gl.DYNAMIC_DRAW);
textureIndexBuffer.itemSize = groupSize;
textureIndexBuffer.numItems = groupCount;
// what alpha it should have
var alphaBuffer = this._alphaBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, alphaBuffer);
groupSize = 1;
var alphas = this._alphas = new Float32Array(groupCount * groupSize);
for(i=0; i<alphas.length; i++) { alphas[i] = 1; }
gl.bufferData(gl.ARRAY_BUFFER, alphas, gl.DYNAMIC_DRAW);
alphaBuffer.itemSize = groupSize;
alphaBuffer.numItems = groupCount;
};
/**
* Do all the setup work for loading textures.
* @method _initTextures
* @param {WebGLRenderingContext} gl
* @protected
*/
p._initTextures = function(gl) {
//TODO: DHG: add a cleanup routine in here in case this happens mid stream
// reset counters
this._lastTextureInsert = -1;
this._lastTextureID = -1;
// clear containers
this._textureDictionary = [];
this._textureIDs = {};
this._batchTextures = [];
// fill in blanks as it helps the renderer be stable while textures are loading and reduces need for saftey code
for(var i=0; i<this._batchTextureCount;i++) {
this._batchTextures[i] = this.getBaseTexture();
}
};
/**
* Load a specific texture, accounting for potential delay as it might not be preloaded
* @method _loadTextureImage
* @param {WebGLRenderingContext} gl
* @param {Image} image Actual image to be loaded
* @protected
*/
p._loadTextureImage = function(gl, image) {
var index = ++this._lastTextureID;
var src = image.src;
if(!src){
// one time canvas property setup
image._isCanvas = true;
src = image.src = "canvas_" + this._lastTrackedCanvas++;
}
var storeID = this._textureIDs[src];
if(storeID === undefined) {
storeID = this._textureDictionary.length;
this._textureIDs[src] = storeID;
}
if(this._textureDictionary[storeID] === undefined){
this._textureDictionary[storeID] = this.getBaseTexture();
}
var texture = this._textureDictionary[storeID];
texture._batchID = this._batchID;
texture._storeID = storeID;
texture._imageData = image;
this._insertTextureInBatch(gl, texture);
image._storeID = storeID;
if(image.complete || image.naturalWidth || image._isCanvas) { // is it already loaded
this._updateTextureImageData(gl, image);
} else {
image.onload = this._updateTextureImageData.bind(this, gl, image); //TODO: DHG: EventListener instead of callback
}
return texture;
};
/**
* Neccesary to upload the actual image data to the gpu. Without this the texture will be blank.
* @param {WebGLRenderingContext} gl
* @param {Image | Canvas} image The image data to be uploaded
* @method _updateTextureImageData
*/
p._updateTextureImageData = function(gl, image) {
var texture = this._textureDictionary[image._storeID];
gl.activeTexture(gl.TEXTURE0 + texture._activeIndex);
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
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);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
texture._w = image.width;
texture._h = image.height;
if(this.vocalDebug) {
// the bitwise & is intentional, cheap exponent 2 check
if((image.width & image.width-1) || (image.height & image.height-1)) {
console.warn("NPOT(Non Power of Two) Texture: "+ image.src);
}
if(image.width > gl.MAX_TEXTURE_SIZE || image.height > gl.MAX_TEXTURE_SIZE){
console.error("Oversized Texture: "+ image.width+"x"+image.height +" vs "+ gl.MAX_TEXTURE_SIZE +"max");
}
}
};
/**
* Begin the drawing process
* @param {WebGLRenderingContext} gl
* @param {Stage || Container} sceneGraph Container object with all that needs to rendered, prefferably a stage
* @method _batchDraw
*/
p._batchDraw = function(sceneGraph, gl, ignoreCache) {
if(this._isDrawing > 0) {
this._drawToGPU(gl);
}
this._isDrawing++;
this._drawID++;
this.batchCardCount = 0;
this.depth = 0;
var mtx = new createjs.Matrix2D();
this._appendToBatchGroup(sceneGraph, gl, mtx, 1, ignoreCache); //TODO: DHG: isn't there a global alpha or something?
this.batchReason = "drawFinish";
this._drawToGPU(gl); // <--------------------------------------------------------
this._isDrawing--;
};
/**
* Draws all the currently defined boxes to the GPU.
* @method _drawToGPU
* @param {WebGLRenderingContext} gl The canvas WebGL context object to draw into.
* @param {Boolean} flipY Covers are used for things like RenderTextures and because of 3D vs Canvas space this can end up meaning y sometimes requires flipping in the render
* @protected
**/
p._drawCover = function(gl, flipY) {
if(this._isDrawing > 0) {
this._drawToGPU(gl);
}
if(this.vocalDebug) {
console.log("Draw["+ this._drawID +":"+ this._batchID +"] : "+ "Cover");
}
var shaderProgram = this._activeShader;
var vertexPositionBuffer = this._vertexPositionBuffer;
var uvPositionBuffer = this._uvPositionBuffer;
gl.clear(gl.COLOR_BUFFER_BIT);
gl.useProgram(shaderProgram);
gl.bindBuffer(gl.ARRAY_BUFFER, vertexPositionBuffer);
gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, vertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);
gl.bufferSubData(gl.ARRAY_BUFFER, 0, SpriteStage.COVER_VERT);
gl.bindBuffer(gl.ARRAY_BUFFER, uvPositionBuffer);
gl.vertexAttribPointer(shaderProgram.uvPositionAttribute, uvPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);
gl.bufferSubData(gl.ARRAY_BUFFER, 0, flipY?SpriteStage.COVER_UV_FLIP:SpriteStage.COVER_UV);
gl.uniform1i(shaderProgram.samplerUniform, 0);
gl.drawArrays(gl.TRIANGLES, 0, SpriteStage.INDICIES_PER_CARD);
};
/**
* Add all the contents of a container to the display list, called recurssivley on each
* @param {Container} container
* @param {WebGLRenderingContext} gl
* @param {Matrix2D} concatMtx Cumulative offset so far
* @method _appendToBatchGroup
*/
p._appendToBatchGroup = function(container, gl, concatMtx, concatAlpha, ignoreCache) {
// sort out shared properties
if(!container._glMtx) { container._glMtx = new createjs.Matrix2D(); }
var cMtx = container._glMtx;
cMtx.copy(concatMtx);
if(container.transformMatrix) {
cMtx.appendMatrix(
container.transformMatrix
);
} else {
cMtx.appendTransform(
container.x, container.y,
container.scaleX, container.scaleY,
container.rotation, container.skewX, container.skewY,
container.regX, container.regY
);
}
//concatAlpha *= container.alpha; //TODO: DHG: Probably not needed.
//var tlX = 0, tlY = 0, trX = 0, trY = 0, blX = 0, blY = 0, brX = 0, brY = 0;
var subL, subT, subR, subB;
// actually apply its data to the buffers
for(var i = 0, l = container.children.length; i < l; i++) { //TODO: DHG: store off length
var item = container.children[i];
if(!(item.visible && concatAlpha)) { continue; }
if(!item.cacheCanvas || ignoreCache) {
if(item._webGLRenderStyle === 3) { // BITMAP TEXT SETUP
item._updateText(); //TODO: DHG: Make this a more generic API like a "pre webgl render" function
}
if(item.children) { // CONTAINER
this._appendToBatchGroup(item, gl, cMtx, item.alpha * concatAlpha);
continue;
}
}
// check for overflowing batch, if yes then force a render
if(this.batchCardCount+1 > this._maxCardsPerBatch) { //TODO: DHG: consider making this polygon count dependant for things like vector draws
this.batchReason = "vertexOverflow";
this._drawToGPU(gl); // <------------------------------------------------------------
this.batchCardCount = 0;
}
// actually apply its data to the buffers
if(!item._glMtx) { item._glMtx = new createjs.Matrix2D(); }
var iMtx = item._glMtx;
iMtx.copy(cMtx);
if(item.transformMatrix) {
iMtx.appendMatrix(
item.transformMatrix
);
} else {
iMtx.appendTransform(
item.x, item.y,
item.scaleX, item.scaleY,
item.rotation, item.skewX, item.skewY,
item.regX, item.regY
);
}
var uvRect, texIndex;
var uvs = this._uvs;
var vertices = this._vertices;
var texI = this._indecies;
var alphas = this._alphas;
var offset = this.batchCardCount*SpriteStage.INDICIES_PER_CARD*2; //TODO: DHG: you can do better
var loc = (offset/2)|0;
if(item._webGLRenderStyle === 2 || (item.cacheCanvas && !ignoreCache)) { // BITMAP / Cached Canvas
var image = (ignoreCache?false:item.cacheCanvas) || item.image;
// calculate texture
var texture;
if(image._storeID === undefined) {
// this texture is new to us so load it and add it to the batch
texture = this._loadTextureImage(gl, image);
this._insertTextureInBatch(gl, texture);
} else {
// fetch the texture
if(image._storeID < 0) {
// if it isn't a stored texture it's a render texture so the image object is the texture
texture = image;
} else {
texture = this._textureDictionary[image._storeID];
}
// put it in the batch if needed
if(texture._batchID !== this._batchID) {
this._insertTextureInBatch(gl, texture);
}
}
texIndex = texture._activeIndex;
if(item.sourceRect) {
// calculate uvs
if(!item._uvRect) { item._uvRect = {}; }
var src = item.sourceRect;
uvRect = item._uvRect;
uvRect.t = (src.x)/image.width;
uvRect.l = (src.y)/image.height;
uvRect.b = (src.x + src.width)/image.width;
uvRect.r = (src.y + src.height)/image.height;
// calculate vertices
subL = -item.regX; subT = -item.regY;
subR = src.width+subL; subB = src.height+subT;
} else {
// calculate uvs
// calculate vertices
if(item.cacheCanvas) {
uvRect = SpriteStage.UV_RECT;
subL = item._cacheOffsetX; subT = item._cacheOffsetY;
} else {
uvRect = SpriteStage.UV_RECT;
subL = (-item.regX); subT = (-item.regY);
}
subR = image.width+subL; subB = image.height+subT;
}
} else if(item._webGLRenderStyle === 1) { // SPRITE
var frame = item.spriteSheet.getFrame(item.currentFrame);
var rect = frame.rect;
var image = frame.image;
// calculate uvs
uvRect = frame.uvRect;
if(!uvRect) {
uvRect = this.buildUVRects(item.spriteSheet, item.currentFrame, false);
}
// calculate texture
var texture;
if(image._storeID === undefined) {
// this texture is new to us so load it and add it to the batch
texture = this._loadTextureImage(gl, image);
this._insertTextureInBatch(gl, texture);
} else {
// fetch the texture
if(image._storeID < 0) {
// if it isn't a stored texture it's a render texture so the image object is the texture
texture = image;
} else {
texture = this._textureDictionary[image._storeID];
}
// put it in the batch if needed
if(texture._batchID !== this._batchID) {
this._insertTextureInBatch(gl, texture);
}
}
texIndex = texture._activeIndex;
// calculate vertices
subL = -frame.regX; subT = -frame.regY;
subR = rect.width-frame.regX; subB = rect.height-frame.regY;
} else { // MISC (DOM objects render themselves later)
continue;
}
//DHG: See Matrix2D.transformPoint for why this math specifically
// apply vertices //TODO: DHG: optimize?
vertices[offset] = subL *iMtx.a + subT *iMtx.c +iMtx.tx; vertices[offset+1] = subL *iMtx.b + subT *iMtx.d +iMtx.ty;
vertices[offset+2] = subL *iMtx.a + subB *iMtx.c +iMtx.tx; vertices[offset+3] = subL *iMtx.b + subB *iMtx.d +iMtx.ty;
vertices[offset+4] = subR *iMtx.a + subT *iMtx.c +iMtx.tx; vertices[offset+5] = subR *iMtx.b + subT *iMtx.d +iMtx.ty;
vertices[offset+6] = subL *iMtx.a + subB *iMtx.c +iMtx.tx; vertices[offset+7] = subL *iMtx.b + subB *iMtx.d +iMtx.ty;
vertices[offset+8] = subR *iMtx.a + subT *iMtx.c +iMtx.tx; vertices[offset+9] = subR *iMtx.b + subT *iMtx.d +iMtx.ty;
vertices[offset+10] = subR *iMtx.a + subB *iMtx.c +iMtx.tx; vertices[offset+11] = subR *iMtx.b + subB *iMtx.d +iMtx.ty;
// apply uvs
uvs[offset] = uvRect.l; uvs[offset+1] = uvRect.t;
uvs[offset+2] = uvRect.l; uvs[offset+3] = uvRect.b;
uvs[offset+4] = uvRect.r; uvs[offset+5] = uvRect.t;
uvs[offset+6] = uvRect.l; uvs[offset+7] = uvRect.b;
uvs[offset+8] = uvRect.r; uvs[offset+9] = uvRect.t;
uvs[offset+10] = uvRect.r; uvs[offset+11] = uvRect.b;
// apply texture
texI[loc] = texI[loc+1] = texI[loc+2] = texI[loc+3] = texI[loc+4] = texI[loc+5] = texIndex;
// apply alpha
alphas[loc] = alphas[loc+1] = alphas[loc+2] = alphas[loc+3] = alphas[loc+4] = alphas[loc+5] = item.alpha * concatAlpha;
this.batchCardCount++;
}
};
/**
* Draws all the currently defined boxes to the GPU.
* @method _drawToGPU
* @param {WebGLRenderingContext} gl The canvas WebGL context object to draw into.
* @protected
**/
p._drawToGPU = function(gl) {
//return;
if(this.vocalDebug) {
console.log("Draw["+ this._drawID +":"+ this._batchID +"] : "+ this.batchReason);
}
var shaderProgram = this._activeShader;
var vertexPositionBuffer = this._vertexPositionBuffer;
var textureIndexBuffer = this._textureIndexBuffer;
var uvPositionBuffer = this._uvPositionBuffer;
var alphaBuffer = this._alphaBuffer;
gl.useProgram(shaderProgram);
gl.bindBuffer(gl.ARRAY_BUFFER, vertexPositionBuffer);
gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, vertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);
gl.bufferSubData(gl.ARRAY_BUFFER, 0, this._vertices);
gl.bindBuffer(gl.ARRAY_BUFFER, textureIndexBuffer);
gl.vertexAttribPointer(shaderProgram.textureIndexAttribute, textureIndexBuffer.itemSize, gl.FLOAT, false, 0, 0);
gl.bufferSubData(gl.ARRAY_BUFFER, 0, this._indecies);
gl.bindBuffer(gl.ARRAY_BUFFER, uvPositionBuffer);
gl.vertexAttribPointer(shaderProgram.uvPositionAttribute, uvPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);
gl.bufferSubData(gl.ARRAY_BUFFER, 0, this._uvs);
gl.bindBuffer(gl.ARRAY_BUFFER, alphaBuffer);
gl.vertexAttribPointer(shaderProgram.alphaAttribute, alphaBuffer.itemSize, gl.FLOAT, false, 0, 0);
gl.bufferSubData(gl.ARRAY_BUFFER, 0, this._alphas);
gl.uniformMatrix4fv(shaderProgram.pMatrixUniform, gl.FALSE, this._projectionMatrix);
//gl.uniformMatrix1i(shaderProgram.flagUniform, gl.FALSE, this.flags);
for (var j = 0; j < this._batchTextureCount; j++) {
var texture = this._batchTextures[j];
gl.activeTexture(gl.TEXTURE0 + j);
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
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);
}
gl.drawArrays(gl.TRIANGLES, 0, this.batchCardCount*SpriteStage.INDICIES_PER_CARD);
this._batchID++;
};
/**
* Adds the texture to a spot in the current batch, forcing a draw if no spots are free.
* @method _insertTextureInBatch
* @param {WebGLRenderingContext} gl The canvas WebGL context object to draw into.
* @param {WebGLTexture} gl The canvas WebGL context object to draw into.
* @protected
*/
p._insertTextureInBatch = function(gl, texture) {
// if it wasn't used last batch
if(this._batchTextures[texture._activeIndex] !== texture) {
// we've got to find it a a spot.
var found = -1;
var start = (this._lastTextureInsert+1) % this._batchTextureCount;
var look = start;
do {
if(this._batchTextures[look]._batchID != this._batchID) {
found = look;
break;
}
look = (look+1) % this._batchTextureCount;
} while(look !== start);
// we couldn't find anywhere for it go, meaning we're maxed out
if(found === -1) {
this.batchReason = "textureOverflow";
this._drawToGPU(gl); // <--------------------------------------------------------
this.batchCardCount = 0;
found = start;
}
// lets put it into that spot
this._batchTextures[found] = texture;
texture._activeIndex = found;
gl.activeTexture(gl.TEXTURE0 + found);
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
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);
this._lastTextureInsert = found;
} else {
var image = texture._imageData;
if(image._invalid) {
this._updateTextureImageData(gl, image);
}
}
texture._drawID = this._drawID;
texture._batchID = this._batchID;
};
// Injections
///////////////////////////////////////////////////////
/**
* We need to modify other classes, do this during our class initialization
*/
(function _injectRenderStyles() {
// Set which classes are compatible with SpriteStage. The order is important!!!
// Reflect any changes to the drawing loop
var candidates = [createjs.Sprite, createjs.Bitmap, createjs.BitmapText, createjs.DOMElement];
candidates.forEach(function(_class, index) {
_class.prototype._webGLRenderStyle = index + 1;
});
//createjs.Container.prototype._webGLRenderStyle = createjs.SpriteContainer.prototype._webGLRenderStyle;
})();
createjs.SpriteStage = createjs.promote(SpriteStage, "Stage");
}());