diff --git a/src/easeljs/display/Bitmap.js b/src/easeljs/display/Bitmap.js index db90d5fec..497828ce5 100644 --- a/src/easeljs/display/Bitmap.js +++ b/src/easeljs/display/Bitmap.js @@ -212,7 +212,10 @@ this.createjs = this.createjs||{}; **/ p.clone = function(node) { var image = this.image; - if(image && node){ image = image.cloneNode(); } + if(image && node){ + image = image.cloneNode(); + image.src = image.src; // IE cloneNode bug fix + } var o = new Bitmap(image); if (this.sourceRect) { o.sourceRect = this.sourceRect.clone(); } this._cloneProps(o); diff --git a/src/easeljs/display/Container.js b/src/easeljs/display/Container.js index 4810cfb21..092df39e3 100644 --- a/src/easeljs/display/Container.js +++ b/src/easeljs/display/Container.js @@ -705,4 +705,4 @@ this.createjs = this.createjs||{}; createjs.Container = createjs.promote(Container, "DisplayObject"); -}()); \ No newline at end of file +}()); diff --git a/src/easeljs/display/DisplayObject.js b/src/easeljs/display/DisplayObject.js index 190bbd6e2..9216d7783 100644 --- a/src/easeljs/display/DisplayObject.js +++ b/src/easeljs/display/DisplayObject.js @@ -153,7 +153,7 @@ this.createjs = this.createjs||{}; * If a cache is active, this returns the canvas that holds the image of this display object. See {{#crossLink "DisplayObject/cache:method"}}{{/crossLink}} * for more information. Use this to display the result of a cache. This will be a HTMLCanvasElement unless special cache rules have been deliberately enabled for this cache. * @property cacheCanvas - * @type {HTMLCanvasElement | Object} + * @type {HTMLCanvasElement | WebGLTexture | Object} * @default null * @readonly **/ @@ -457,6 +457,15 @@ this.createjs = this.createjs||{}; * @default 0 */ this._webGLRenderStyle = DisplayObject._StageGL_NONE; + + /** + * Storage for the calculated position of an object in StageGL. If not using StageGL, you can null it to save memory. + * @property _glMtx + * @protected + * @type {Matrix2D} + * @default null + */ + this._glMtx = new createjs.Matrix2D(); } var p = createjs.extend(DisplayObject, createjs.EventDispatcher); @@ -751,7 +760,7 @@ this.createjs = this.createjs||{}; **/ p.draw = function(ctx, ignoreCache) { var cache = this.bitmapCache; - if(cache && !ignoreCache) { + if (cache && !ignoreCache) { return cache.draw(ctx); } return false; @@ -826,8 +835,10 @@ this.createjs = this.createjs||{}; * @param {Object} [options=undefined] Specify additional parameters for the cache logic **/ p.cache = function(x, y, width, height, scale, options) { - if(!this.bitmapCache){ + if (!this.bitmapCache){ this.bitmapCache = new createjs.BitmapCache(); + } else { + this.bitmapCache._autoGenerated = false; } this.bitmapCache.define(this, x, y, width, height, scale, options); }; @@ -855,7 +866,7 @@ this.createjs = this.createjs||{}; * whatwg spec on compositing. **/ p.updateCache = function(compositeOperation) { - if(!this.bitmapCache) { + if (!this.bitmapCache) { throw "cache() must be called before updateCache()"; } this.bitmapCache.update(compositeOperation); @@ -866,7 +877,7 @@ this.createjs = this.createjs||{}; * @method uncache **/ p.uncache = function() { - if(this.bitmapCache) { + if (this.bitmapCache) { this.bitmapCache.release(); this.bitmapCache = undefined; } @@ -996,8 +1007,9 @@ this.createjs = this.createjs||{}; * @return {Matrix2D} A matrix representing this display object's transform. **/ p.getMatrix = function(matrix) { - var o = this, mtx = matrix&&matrix.identity() || new createjs.Matrix2D(); - return o.transformMatrix ? mtx.copy(o.transformMatrix) : mtx.appendTransform(o.x, o.y, o.scaleX, o.scaleY, o.rotation, o.skewX, o.skewY, o.regX, o.regY); + var o = this, mtx = matrix || new createjs.Matrix2D(); + return o.transformMatrix ? mtx.copy(o.transformMatrix) : + (mtx.identity() && mtx.appendTransform(o.x, o.y, o.scaleX, o.scaleY, o.rotation, o.skewX, o.skewY, o.regX, o.regY)); }; /** @@ -1254,6 +1266,7 @@ this.createjs = this.createjs||{}; o.hitArea = this.hitArea; o.cursor = this.cursor; o._bounds = this._bounds; + o._webGLRenderStyle = this._webGLRenderStyle; return o; }; diff --git a/src/easeljs/display/Stage.js b/src/easeljs/display/Stage.js index 380c1fbea..a6c675ccb 100644 --- a/src/easeljs/display/Stage.js +++ b/src/easeljs/display/Stage.js @@ -376,7 +376,24 @@ this.createjs = this.createjs||{}; ctx.restore(); this.dispatchEvent("drawend"); }; - + + /** + * Draws the stage into the specified context ignoring its visible, alpha, shadow, and transform. + * 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} ctx 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(ctx, ignoreCache) { + var result = this.Container_draw(ctx, ignoreCache); + this.canvas._invalid = true; + return result; + }; + /** * Propagates a tick event through the display list. This is automatically called by {{#crossLink "Stage/update"}}{{/crossLink}} * unless {{#crossLink "Stage/tickOnUpdate:property"}}{{/crossLink}} is set to false. diff --git a/src/easeljs/display/StageGL.js b/src/easeljs/display/StageGL.js index 414a5c35a..5522ee793 100644 --- a/src/easeljs/display/StageGL.js +++ b/src/easeljs/display/StageGL.js @@ -34,29 +34,39 @@ this.createjs = this.createjs||{}; /* * README IF EDITING: - * Terminology for developers: * + * - 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 a vector3 containing x,y,z * Index/Indices: used in groups of 3 to define a triangle, points to vertices 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 + * UV: 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. Multiple cards are batched for this reason * Program/Shader: For every vertex we run the Vertex shader. The results are used per pixel by the Fragment shader. When - * combined and paired these are a shader "program" - * Texture: WebGL representation of image data and associated extra information - * Slot: A space on the GPU into which textures can be loaded for use in a batch, using "ActiveTexture" switches texture slot. + * combined and paired these are a "shader program" + * Texture: WebGL representation of image data and associated extra information, separate from a DOM Image + * Slot: A space on the GPU into which textures can be loaded for use in a batch, i.e. using "ActiveTexture" switches texture slot. + * Render___: actual WebGL draw call + * Buffer: WebGL array data + * Cover: A card that covers the entire viewport + * + * - Notes: + * WebGL treats 0,0 as the bottom left, as such there's a lot of co-ordinate space flipping to make regular canvas + * numbers make sense to users and WebGL simultaneously. This extends to textures stored in memory too. If writing + * code that deals with x/y, be aware your y may be flipped. + * Older versions had distinct internal paths for filters and regular draws, these have been merged. + * Draws are slowly assembled out of found content. Overflowing things like shaders, object/texture count will cause + * an early draw before continuing. Lookout for the things that force a draw. Marked with <------------------------ */ (function () { "use strict"; /** - * A StageGL instance is the root level {{#crossLink "Container"}}{{/crossLink}} for an WebGL-optimized display list, - * which is used in place of the usual {{#crossLink "Stage"}}{{/crossLink}}. This class should behave identically to - * a {{#crossLink "Stage"}}{{/crossLink}} except for WebGL-specific functionality. + * A StageGL instance is the root level {{#crossLink "Container"}}{{/crossLink}} for a WebGL-optimized display list, + * which can be used in place of the usual {{#crossLink "Stage"}}{{/crossLink}}. This class should behave identically + * to a {{#crossLink "Stage"}}{{/crossLink}} except for WebGL-specific functionality. * * Each time the {{#crossLink "Stage/tick"}}{{/crossLink}} method is called, the display list is rendered to the * target <canvas/> instance, ignoring non-WebGL-compatible display objects. On devices and browsers that don't @@ -77,6 +87,7 @@ this.createjs = this.createjs||{}; * resize your canvas after making a StageGL instance, this will properly size the WebGL context stored in memory. * - Best performance in demanding scenarios will come from manual management of texture memory, but it is handled * automatically by default. See {{#crossLink "StageGL/releaseTexture"}}{{/crossLink}} for details. + * - Disable `directDraw` to get access to cacheless filters and composite oeprations! * *

Example

* This example creates a StageGL instance, adds a child to it, then uses the EaselJS {{#crossLink "Ticker"}}{{/crossLink}} @@ -94,41 +105,41 @@ this.createjs = this.createjs||{}; * stage.update(); * } * - *

Notes

- * - StageGL is not currently included in the minified version of EaselJS. - * - {{#crossLink "SpriteContainer"}}{{/crossLink}} (the previous approach to WebGL with EaselJS) has been deprecated. - * - Earlier versions of WebGL support in EaselJS (SpriteStage and SpriteContainer) had hard limitations on images - * per container, which have been solved. - * * @class StageGL * @extends Stage * @constructor * @param {HTMLCanvasElement | String | Object} canvas A canvas object that StageGL will render to, or the string id - * of a canvas object in the current DOM. + * of a canvas object in the current DOM. * @param {Object} options All the option parameters in a reference object, some are not supported by some browsers. * @param {Boolean} [options.preserveBuffer=false] If `true`, the canvas is NOT auto-cleared by WebGL (the spec - * discourages setting this to `true`). This is useful if you want persistent draw effects. + * discourages setting this to `true`). This is useful if you want persistent draw effects and has also fixed device + * specific bugs due to mis-timed clear commands. * @param {Boolean} [options.antialias=false] Specifies whether or not the browser's WebGL implementation should try - * to perform anti-aliasing. This will also enable linear pixel sampling on power-of-two textures (smoother images). + * to perform anti-aliasing. This will also enable linear pixel sampling on power-of-two textures (smoother images). * @param {Boolean} [options.transparent=false] If `true`, the canvas is transparent. This is very * expensive, and should be used with caution. - * @param {Boolean} [options.premultiply=false] Alters color handling. If `true`, this assumes the shader must - * account for pre-multiplied alpha. This can help avoid visual halo effects with some assets, but may also cause - * problems with other assets. - * @param {Integer} [options.autoPurge=1200] How often the system should automatically dump unused textures with - * `purgeTextures(autoPurge)` every `autoPurge/2` draws. See {{#crossLink "StageGL/purgeTextures"}}{{/crossLink}} for more - * information. + * @param {Boolean} [options.directDraw=true] If `true`, this will bypass intermediary render-textures when possible + * resulting in reduced memory and increased performance, this disables some features. Cache-less filters and some + * {{#crossLink "DisplayObject/compositeOperation:property"}}{{/crossLink}} values rely on this being false. + * @param (Boolean} [options.premultiply] @deprecated Upgraded colour & transparency handling have fixed the issue + * this flag was trying to solve rendering it unnecessary. + * @param {int} [options.autoPurge=1200] How often the system should automatically dump unused textures. Calls + * `purgeTextures(autoPurge)` every `autoPurge/2` draws. See {{#crossLink "StageGL/purgeTextures"}}{{/crossLink}} + * for more information on texture purging. + * @param {String|int} [options.clearColor=undefined] Automatically calls {{#crossLink "StageGL/setClearColor"}}{{/crossLink}} + * after init is complete, can be overridden and changed manually later. */ function StageGL(canvas, options) { this.Stage_constructor(canvas); + var transparent, antialias, preserveBuffer, autoPurge, directDraw; if (options !== undefined) { if (typeof options !== "object"){ throw("Invalid options object"); } - var premultiply = options.premultiply; - var transparent = options.transparent; - var antialias = options.antialias; - var preserveBuffer = options.preserveBuffer; - var autoPurge = options.autoPurge; + transparent = options.transparent; + antialias = options.antialias; + preserveBuffer = options.preserveBuffer; + autoPurge = options.autoPurge; + directDraw = options.directDraw; } // public properties: @@ -141,6 +152,18 @@ this.createjs = this.createjs||{}; */ this.vocalDebug = false; + /** + * Specifies whether this instance is slaved to a {{#crossLink "BitmapCache"}}{{/crossLink}} or draws independantly. + * Necessary to control texture outputs and behaviours when caching. StageGL cache outputs will only render + * properly for the StageGL that made them. See the {{#crossLink "cache"}}{{/crossLink}} function documentation + * for more information. Enabled by default when BitmapCache's `useGL` is true. + * NOTE: This property is mainly for internal use, though it may be useful for advanced uses. + * @property isCacheControlled + * @type {Boolean} + * @default false + */ + this.isCacheControlled = false; + // private properties: /** * Specifies whether or not the canvas is auto-cleared by WebGL. The WebGL spec discourages `true`. @@ -171,24 +194,24 @@ this.createjs = this.createjs||{}; */ this._transparent = transparent||false; - /** - * Specifies whether or not StageGL is handling colours as premultiplied alpha. - * @property _premultiply - * @protected - * @type {Boolean} - * @default false - */ - this._premultiply = premultiply||false; - /** * Internal value of {{#crossLink "StageGL/autoPurge"}}{{/crossLink}} * @property _autoPurge * @protected - * @type {Integer} + * @type {int} * @default null */ this._autoPurge = undefined; - this.autoPurge = autoPurge; //getter/setter handles setting the real value and validating + this.autoPurge = autoPurge; //getter/setter handles setting the real value and validating and documentation + + /** + * See directDraw + * @property _directDraw + * @protected + * @type {Boolean} + * @default false + */ + this._directDraw = directDraw === undefined ? true : false; /** * The width in px of the drawing surface saved in memory. @@ -227,6 +250,13 @@ this.createjs = this.createjs||{}; */ this._webGLContext = null; + /** + * Reduce API logic by allowing stage to behave as a renderTexture target, should always be null as null is canvas. + * @type {null} + * @protected + */ + this._frameBuffer = null; + /** * The color to use when the WebGL canvas has been cleared. May appear as a background color. Defaults to grey. * @property _clearColor @@ -239,12 +269,12 @@ this.createjs = this.createjs||{}; /** * 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 + * @property _maxBatchVertexCount * @protected * @type {Number} - * @default StageGL.DEFAULT_MAX_BATCH_SIZE (10000) + * @default StageGL.DEFAULT_MAX_BATCH_SIZE * StageGL.INDICIES_PER_CARD */ - this._maxCardsPerBatch = StageGL.DEFAULT_MAX_BATCH_SIZE; //TODO: write getter/setters for this + this._maxBatchVertexCount = StageGL.DEFAULT_MAX_BATCH_SIZE * StageGL.INDICIES_PER_CARD; /** * The shader program used to draw the current batch. @@ -255,6 +285,13 @@ this.createjs = this.createjs||{}; */ this._activeShader = null; + /** + * The non cover, per object shader used for most rendering actions. + * @type {WebGLProgram} + * @protected + */ + this._mainShader = null; + /** * The vertex position data for the current draw call. * @property _vertices @@ -274,7 +311,7 @@ this.createjs = this.createjs||{}; this._vertexPositionBuffer = null; /** - * The vertex U/V data for the current draw call. + * The vertex UV data for the current draw call. * @property _uvs * @protected * @type {Float32Array} @@ -327,6 +364,67 @@ this.createjs = this.createjs||{}; */ this._alphaBuffer = null; + /** + * One of the major render buffers used in composite blending drawing. Do not expect this to always be the same object. + * "What you're drawing to", object occasionally swaps with concat. + * @property _bufferTextureOutput + * @protected + * @type {WebGLTexture} + */ + this._bufferTextureOutput = null; + + /** + * One of the major render buffers used in composite blending drawing. Do not expect this to always be the same object. + * "What you've draw before now", object occasionally swaps with output. + * @property _bufferTextureConcat + * @protected + * @type {WebGLTexture} + */ + this._bufferTextureConcat = null; + + /** + * One of the major render buffers used in composite blending drawing. + * "Temporary mixing surface" + * @property _bufferTextureTemp + * @protected + * @type {WebGLTexture} + */ + this._bufferTextureTemp = null; + + /** + * The current render buffer being targeted, usually targets internal buffers, but may be set to cache's buffer during a cache render. + * @property _batchTextureOutput + * @protected + * @type {WebGLTexture | StageGL} + */ + this._batchTextureOutput = this; + + /** + * The current render buffer being targeted, usually targets internal buffers, but may be set to cache's buffer during a cache render. + * @property _batchTextureConcat + * @protected + * @type {WebGLTexture} + */ + this._batchTextureConcat = null; + + /** + * The current render buffer being targeted, usually targets internal buffers, but may be set to cache's buffer during a cache render. + * @property _batchTextureTemp + * @protected + * @type {WebGLTexture} + */ + this._batchTextureTemp = null; + + /** + * Internal library of the shaders that have been compiled and created along with their parameters. Should contain + * compiled `gl.ShaderProgram` and settings for `gl.blendFunc` and `gl.blendEquation`. Populated as requested. + * + * See {{#crossLink "StageGL/_updateRenderMode:method"}}{{/crossLink}} for exact details. + * @type {Object} + * @private + */ + this._builtShaders = {}; + /** * An index based lookup of every WebGL Texture currently in use. * @property _drawTexture @@ -361,30 +459,59 @@ this.createjs = this.createjs||{}; this._baseTextures = []; /** - * The number of concurrent textures the GPU can handle. This value is dynamically set from WebGL during initialization - * via `gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS)`. The WebGL spec states that the lowest guaranteed value is 8, - * but it could be higher. Do not set this value higher than the value returned by the GPU. Setting it lower will - * probably reduce performance, but may be advisable to reserve slots for custom filter work. - * NOTE: Can also act as a length for {{#crossLink "StageGL/_batchTextures:property"}}. - * @property _batchTextureCount + * Texture slots for a draw + * @property _gpuTextureCount * @protected - * @type {Number} - * @default 8 + * @type {uint} */ - this._batchTextureCount = 8; + this._gpuTextureCount = 8; /** - * The location at which the last texture was inserted into a GPU slot in {{#crossLink "StageGL/_batchTextures:property"}}{{/crossLink}}. - * Manual control of this variable can yield improvements in performance by intelligently replacing textures - * inside a batch to reduce texture re-load. It is impossible to write automated general use code, as it requires - * display list look ahead inspection and/or render foreknowledge. - * @property _lastTextureInsert + * Texture slots on the hardware + * @property _gpuTextureMax * @protected - * @type {Number} - * @default -1 + * @type {uint} + */ + this._gpuTextureMax = 8; + + /** + * Texture slots in a batch for User textures + * @property _batchTextureCount + * @protected + * @type {uint} + */ + this._batchTextureCount = 0; + + /** + * The location at which the last texture was inserted into a GPU slot */ this._lastTextureInsert = -1; + /** + * The current string name of the render mode being employed per Context2D spec. + * Must start invalid to trigger default shader into being built during init. + * https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/globalCompositeOperation + * @type {string} + * @private + */ + this._renderMode = ""; + + /** + * Flag indicating that the content being batched in `appendToBatch` must be drawn now and not follow batch logic. + * Used for effects that are compounding in nature and cannot be applied in a single pass. + * Should be enabled with extreme care due to massive performance implications. + * @type {boolean} + * @private + */ + this._immediateRender = false; + + /** + * Vertices drawn into the batch so far. + * @type {number} + * @private + */ + this._batchVertexCount = 0; + /** * The current batch being drawn, A batch consists of a call to `drawElements` on the GPU. Many of these calls * can occur per draw. @@ -413,15 +540,6 @@ this.createjs = this.createjs||{}; */ this._slotBlacklist = []; - /** - * Used to prevent nested draw calls from accidentally overwriting drawing information by tracking depth. - * @property _isDrawing - * @protected - * @type {Number} - * @default 0 - */ - this._isDrawing = 0; - /** * Used to ensure every canvas used as a texture source has a unique ID. * @property _lastTrackedCanvas @@ -431,17 +549,6 @@ this.createjs = this.createjs||{}; */ this._lastTrackedCanvas = -1; - /** - * Controls whether final rendering output of a {{#crossLink "cacheDraw"}}{{/crossLink}} is the canvas or a render - * texture. See the {{#crossLink "cache"}}{{/crossLink}} function modifications for full implications and discussion. - * @property isCacheControlled - * @protected - * @type {Boolean} - * @default false - * @todo LM: is this supposed to be _isCacheControlled since its private? - */ - this.isCacheControlled = false; - /** * Used to counter-position the object being cached so it aligns with the cache surface. Additionally ensures * that all rendering starts with a top level container. @@ -454,12 +561,18 @@ this.createjs = this.createjs||{}; // and begin this._initializeWebGL(); + + // these settings require everything to be ready + if (options !== undefined) { + options.clearColor !== undefined && this.setClearColor(options.clearColor); + options.premultiply !== undefined && (createjs.deprecate(null, "options.premultiply")()); + } } var p = createjs.extend(StageGL, createjs.Stage); // static methods: /** - * Calculate the U/V co-ordinate based info for sprite frames. Instead of pixel count it uses a 0-1 space. Also includes + * Calculate the UV co-ordinate based info for sprite frames. Instead of pixel count it uses a 0-1 space. Also includes * the ability to get info back for a specific frame, or only calculate that one frame. * * //generate UV rects for all entries @@ -482,28 +595,28 @@ this.createjs = this.createjs||{}; 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); + var start = (target !== -1 && onlyTarget)?(target):(0); + var end = (target !== -1 && onlyTarget)?(target+1):(spritesheet._frames.length); for (var i=start; i 0.0035) {" + // 1/255 = 0.0039, so ignore any value below 1 because it's probably noise - "gl_FragColor = vec4(color.rgb/color.a, color.a * alphaValue);" + - "} else {" + - "gl_FragColor = vec4(0.0, 0.0, 0.0, 0.0);" + - "}" - ); - - //TODO: DHG: a real particle shader - /** - * @property PARTICLE_VERTEX_BODY - * @todo - * @final - * @static - * @type {String} - * @readonly - */ - StageGL.PARTICLE_VERTEX_BODY = ( - StageGL.REGULAR_VERTEX_BODY - ); - /** - * @property PARTICLE_FRAGMENT_BODY - * @todo - * @final - * @static - * @type {String} - * @readonly - */ - StageGL.PARTICLE_FRAGMENT_BODY = ( - StageGL.REGULAR_FRAGMENT_BODY - ); /** * Portion of the shader that contains the "varying" properties required in both vertex and fragment shaders. The * cover shader is designed to be a simple vertex/uv only texture render that covers the render surface. Shader * code may contain templates that are replaced pre-compile. * @property COVER_VARYING_HEADER + * @protected * @static * @final * @type {String} * @readonly */ StageGL.COVER_VARYING_HEADER = ( - "precision mediump float;" + + "precision highp float;" + //this is usually essential for filter math - "varying highp vec2 vRenderCoord;" + - "varying highp vec2 vTextureCoord;" + "varying vec2 vTextureCoord;" ); /** @@ -777,6 +840,7 @@ this.createjs = this.createjs||{}; * simple vertex/uv only texture render that covers the render surface. Shader code may contain templates that are * replaced pre-compile. * @property COVER_VERTEX_HEADER + * @protected * @static * @final * @type {String} @@ -785,8 +849,7 @@ this.createjs = this.createjs||{}; StageGL.COVER_VERTEX_HEADER = ( StageGL.COVER_VARYING_HEADER + "attribute vec2 vertexPosition;" + - "attribute vec2 uvPosition;" + - "uniform float uUpright;" + "attribute vec2 uvPosition;" ); /** @@ -794,6 +857,7 @@ this.createjs = this.createjs||{}; * simple vertex/uv only texture render that covers the render surface. Shader code may contain templates that are * replaced pre-compile. * @property COVER_FRAGMENT_HEADER + * @protected * @static * @final * @type {String} @@ -808,6 +872,7 @@ this.createjs = this.createjs||{}; * Body of the vertex shader. The cover shader is designed to be a simple vertex/uv only texture render that covers * the render surface. Shader code may contain templates that are replaced pre-compile. * @property COVER_VERTEX_BODY + * @protected * @static * @final * @type {String} @@ -816,8 +881,7 @@ this.createjs = this.createjs||{}; StageGL.COVER_VERTEX_BODY = ( "void main(void) {" + "gl_Position = vec4(vertexPosition.x, vertexPosition.y, 0.0, 1.0);" + - "vRenderCoord = uvPosition;" + - "vTextureCoord = vec2(uvPosition.x, abs(uUpright - uvPosition.y));" + + "vTextureCoord = uvPosition;" + "}" ); @@ -825,6 +889,7 @@ this.createjs = this.createjs||{}; * Body of the fragment shader. The cover shader is designed to be a simple vertex/uv only texture render that * covers the render surface. Shader code may contain templates that are replaced pre-compile. * @property COVER_FRAGMENT_BODY + * @protected * @static * @final * @type {String} @@ -832,11 +897,299 @@ this.createjs = this.createjs||{}; */ StageGL.COVER_FRAGMENT_BODY = ( "void main(void) {" + - "vec4 color = texture2D(uSampler, vRenderCoord);" + - "gl_FragColor = color;" + + "gl_FragColor = texture2D(uSampler, vTextureCoord);" + "}" ); + /** + * The starting template of a cover fragment shader with simple blending equations + * @property BLEND_FRAGMENT_SIMPLE + * @protected + * @static + * @final + * @type {String} + * @readonly + */ + StageGL.BLEND_FRAGMENT_SIMPLE = ( + "uniform sampler2D uMixSampler;"+ + "void main(void) {" + + "vec4 src = texture2D(uMixSampler, vTextureCoord);" + + "vec4 dst = texture2D(uSampler, vTextureCoord);" + // note this is an open bracket on main! + ); + + /** + * The starting template of a cover fragment shader which has complex blending equations + * @property BLEND_FRAGMENT_COMPLEX + * @protected + * @static + * @final + * @type {String} + * @readonly + */ + StageGL.BLEND_FRAGMENT_COMPLEX = ( + StageGL.BLEND_FRAGMENT_SIMPLE + + "vec3 srcClr = min(src.rgb/src.a, 1.0);" + + "vec3 dstClr = min(dst.rgb/dst.a, 1.0);" + + + "float totalAlpha = min(1.0 - (1.0-dst.a) * (1.0-src.a), 1.0);" + + "float srcFactor = min(max(src.a - dst.a, 0.0) / totalAlpha, 1.0);" + + "float dstFactor = min(max(dst.a - src.a, 0.0) / totalAlpha, 1.0);" + + "float mixFactor = max(max(1.0 - srcFactor, 0.0) - dstFactor, 0.0);" + + + "gl_FragColor = vec4(" + + "(" + + "srcFactor * srcClr +" + + "dstFactor * dstClr +" + + "mixFactor * vec3(" + // this should be closed with the cap! + ); + + /** + * The closing portion of a template for a cover fragment shader which has complex blending equations + * @property BLEND_FRAGMENT_COMPLEX_CAP + * @protected + * @static + * @final + * @type {String} + * @readonly + */ + StageGL.BLEND_FRAGMENT_COMPLEX_CAP = ( + ")" + + ") * totalAlpha, totalAlpha" + + ");" + + "}" + ); + + /** + * A shader utility function, used to calculate the "overlay" blend of two elements + * @property BLEND_FRAGMENT_OVERLAY_UTIL + * @protected + * @static + * @final + * @type {String} + * @readonly + */ + StageGL.BLEND_FRAGMENT_OVERLAY_UTIL = ( + "float overlay(float a, float b) {" + + "if(a < 0.5) { return 2.0 * a * b; }" + + "return 1.0 - 2.0 * (1.0-a) * (1.0-b);" + + "}" + ); + + /** + * A collection of shader utility functions, used to calculate HSL math. Taken from W3C spec + * https://www.w3.org/TR/compositing-1/#blendingnonseparable + * @property BLEND_FRAGMENT_HSL_UTIL + * @protected + * @static + * @final + * @type {String} + * @readonly + */ + StageGL.BLEND_FRAGMENT_HSL_UTIL = ( + "float getLum(vec3 c) { return 0.299*c.r + 0.589*c.g + 0.109*c.b; }" + + "float getSat(vec3 c) { return max(max(c.r, c.g), c.b) - min(min(c.r, c.g), c.b); }" + + "vec3 clipHSL(vec3 c) {" + + "float lum = getLum(c);" + + "float n = min(min(c.r, c.g), c.b);" + + "float x = max(max(c.r, c.g), c.b);" + + "if(n < 0.0){ c = lum + (((c - lum) * lum) / (lum - n)); }" + + "if(x > 1.0){ c = lum + (((c - lum) * (1.0 - lum)) / (x - lum)); }" + + "return clamp(c, 0.0, 1.0);" + + "}" + + "vec3 setLum(vec3 c, float lum) {" + + "return clipHSL(c + (lum - getLum(c)));" + + "}" + + "vec3 setSat(vec3 c, float val) {" + + "vec3 result = vec3(0.0);" + + "float minVal = min(min(c.r, c.g), c.b);" + + "float maxVal = max(max(c.r, c.g), c.b);" + + "vec3 minMask = vec3(c.r == minVal, c.g == minVal, c.b == minVal);" + + "vec3 maxMask = vec3(c.r == maxVal, c.g == maxVal, c.b == maxVal);" + + "vec3 midMask = 1.0 - min(minMask+maxMask, 1.0);" + + "float midVal = (c*midMask).r + (c*midMask).g + (c*midMask).b;" + + "if(maxVal > minVal) {" + + "result = midMask * min( ((midVal - minVal) * val) / (maxVal - minVal), 1.0);" + + "result += maxMask * val;" + + "}" + + "return result;" + + "}" + ); + + /** + * The hash of supported blend modes and their properties + * @property BLEND_SOURCES + * @static + * @final + * @type {Object} + * @readonly + */ + StageGL.BLEND_SOURCES = { + "source-over": { // empty object verifies it as a blend mode, but default values handle actual settings + //eqRGB: "FUNC_ADD", eqA: "FUNC_ADD" + //srcRGB: "ONE", srcA: "ONE" + //dstRGB: "ONE_MINUS_SRC_ALPHA", dstA: "ONE_MINUS_SRC_ALPHA" + }, + "source-in": { + shader: (StageGL.BLEND_FRAGMENT_SIMPLE + + "gl_FragColor = vec4(src.rgb * dst.a, src.a * dst.a);" + + "}") + }, + "source-in_cheap": { + srcRGB: "DST_ALPHA", srcA: "ZERO", + dstRGB: "ZERO", dstA: "SRC_ALPHA" + }, + "source-out": { + shader: (StageGL.BLEND_FRAGMENT_SIMPLE + + "gl_FragColor = vec4(src.rgb * (1.0 - dst.a), src.a - dst.a);" + + "}") + }, + "source-out_cheap": { + eqA: "FUNC_SUBTRACT", + srcRGB: "ONE_MINUS_DST_ALPHA", srcA: "ONE", + dstRGB: "ZERO", dstA: "SRC_ALPHA" + }, + "source-atop": { + srcRGB: "DST_ALPHA", srcA: "ZERO", + dstRGB: "ONE_MINUS_SRC_ALPHA", dstA: "ONE" + }, + "destination-over": { + srcRGB: "ONE_MINUS_DST_ALPHA", srcA: "ONE_MINUS_DST_ALPHA", + dstRGB: "ONE", dstA: "ONE" + }, + "destination-in": { + shader: (StageGL.BLEND_FRAGMENT_SIMPLE + + "gl_FragColor = vec4(dst.rgb * src.a, src.a * dst.a);" + + "}") + }, + "destination-in_cheap": { + srcRGB: "ZERO", srcA: "DST_ALPHA", + dstRGB: "SRC_ALPHA", dstA: "ZERO" + }, + "destination-out": { + eqA: "FUNC_REVERSE_SUBTRACT", + srcRGB: "ZERO", srcA: "DST_ALPHA", + dstRGB: "ONE_MINUS_SRC_ALPHA", dstA: "ONE" + }, + "destination-atop": { + shader: (StageGL.BLEND_FRAGMENT_SIMPLE + + "gl_FragColor = vec4(dst.rgb * src.a + src.rgb * (1.0 - dst.a), src.a);" + + "}") + }, + "destination-atop_cheap": { + srcRGB: "ONE_MINUS_DST_ALPHA", srcA: "ONE", + dstRGB: "SRC_ALPHA", dstA: "ZERO" + }, + "copy": { + shader: (StageGL.BLEND_FRAGMENT_SIMPLE + + "gl_FragColor = vec4(src.rgb, src.a);" + + "}") + }, + "copy_cheap": { + dstRGB: "ZERO", dstA: "ZERO" + }, + "xor": { + shader: (StageGL.BLEND_FRAGMENT_SIMPLE + + "float omSRC = (1.0 - src.a);" + + "float omDST = (1.0 - dst.a);" + + "gl_FragColor = vec4(src.rgb * omDST + dst.rgb * omSRC, src.a * omDST + dst.a * omSRC);" + + "}") + }, + + "multiply": { // this has to be complex to handle retention of both dst and src in non mixed scenarios + shader: (StageGL.BLEND_FRAGMENT_COMPLEX + + "srcClr * dstClr" + + StageGL.BLEND_FRAGMENT_COMPLEX_CAP) + }, + "multiply_cheap": { // NEW, handles retention of src data incorrectly when no dst data present + srcRGB: "ONE_MINUS_DST_ALPHA", srcA: "ONE", + dstRGB: "SRC_COLOR", dstA: "ONE" + }, + "screen": { + srcRGB: "ONE", srcA: "ONE", + dstRGB: "ONE_MINUS_SRC_COLOR", dstA: "ONE_MINUS_SRC_ALPHA" + }, + "lighter": { + dstRGB: "ONE", dstA:"ONE" + }, + "lighten": { //WebGL 2.0 can optimize this + shader: (StageGL.BLEND_FRAGMENT_COMPLEX + + "max(srcClr, dstClr)" + + StageGL.BLEND_FRAGMENT_COMPLEX_CAP) + }, + "darken": { //WebGL 2.0 can optimize this + shader: (StageGL.BLEND_FRAGMENT_COMPLEX + + "min(srcClr, dstClr)" + + StageGL.BLEND_FRAGMENT_COMPLEX_CAP) + }, + + "overlay": { + shader: (StageGL.BLEND_FRAGMENT_OVERLAY_UTIL + StageGL.BLEND_FRAGMENT_COMPLEX + + "overlay(dstClr.r,srcClr.r), overlay(dstClr.g,srcClr.g), overlay(dstClr.b,srcClr.b)" + + StageGL.BLEND_FRAGMENT_COMPLEX_CAP) + }, + "hard-light": { + shader: (StageGL.BLEND_FRAGMENT_OVERLAY_UTIL + StageGL.BLEND_FRAGMENT_COMPLEX + + "overlay(srcClr.r,dstClr.r), overlay(srcClr.g,dstClr.g), overlay(srcClr.b,dstClr.b)" + + StageGL.BLEND_FRAGMENT_COMPLEX_CAP) + }, + "soft-light": { + shader: ( + "float softcurve(float a) {" + + "if(a > 0.25) { return sqrt(a); }" + + "return ((16.0 * a - 12.0) * a + 4.0) * a;" + + "}" + + "float softmix(float a, float b) {" + + "if(b <= 0.5) { return a - (1.0 - 2.0*b) * a * (1.0 - a); }" + + "return a + (2.0 * b - 1.0) * (softcurve(a) - a);" + + "}" + StageGL.BLEND_FRAGMENT_COMPLEX + + "softmix(dstClr.r,srcClr.r), softmix(dstClr.g,srcClr.g), softmix(dstClr.b,srcClr.b)" + + StageGL.BLEND_FRAGMENT_COMPLEX_CAP) + }, + "color-dodge": { + shader: (StageGL.BLEND_FRAGMENT_COMPLEX + + "clamp(dstClr / (1.0 - srcClr), 0.0, 1.0)" + + StageGL.BLEND_FRAGMENT_COMPLEX_CAP) + }, + "color-burn": { + shader: (StageGL.BLEND_FRAGMENT_COMPLEX + + "1.0 - clamp((1.0 - smoothstep(0.0035, 0.9955, dstClr)) / smoothstep(0.0035, 0.9955, srcClr), 0.0, 1.0)" + + StageGL.BLEND_FRAGMENT_COMPLEX_CAP) + }, + "difference": { // do this to match visible results in browsers + shader: (StageGL.BLEND_FRAGMENT_COMPLEX + + "abs(src.rgb - dstClr)" + + StageGL.BLEND_FRAGMENT_COMPLEX_CAP) + }, + "exclusion": { // do this to match visible results in browsers + shader: (StageGL.BLEND_FRAGMENT_COMPLEX + + "dstClr + src.rgb - 2.0 * src.rgb * dstClr" + + StageGL.BLEND_FRAGMENT_COMPLEX_CAP) + }, + + "hue": { + shader: (StageGL.BLEND_FRAGMENT_HSL_UTIL + StageGL.BLEND_FRAGMENT_COMPLEX + + "setLum(setSat(srcClr, getSat(dstClr)), getLum(dstClr))" + + StageGL.BLEND_FRAGMENT_COMPLEX_CAP) + }, + "saturation": { + shader: (StageGL.BLEND_FRAGMENT_HSL_UTIL + StageGL.BLEND_FRAGMENT_COMPLEX + + "setLum(setSat(dstClr, getSat(srcClr)), getLum(dstClr))" + + StageGL.BLEND_FRAGMENT_COMPLEX_CAP) + }, + "color": { + shader: (StageGL.BLEND_FRAGMENT_HSL_UTIL + StageGL.BLEND_FRAGMENT_COMPLEX + + "setLum(srcClr, getLum(dstClr))" + + StageGL.BLEND_FRAGMENT_COMPLEX_CAP) + }, + "luminosity": { + shader: (StageGL.BLEND_FRAGMENT_HSL_UTIL + StageGL.BLEND_FRAGMENT_COMPLEX + + "setLum(dstClr, getLum(srcClr))" + + StageGL.BLEND_FRAGMENT_COMPLEX_CAP) + } + }; + // events: /** * Dispatched each update immediately before the canvas is cleared and the display list is drawn to it. You can call @@ -856,7 +1209,7 @@ this.createjs = this.createjs||{}; p._set_autoPurge = function (value) { value = isNaN(value)?1200:value; - if (value != -1) { + if (value !== -1) { value = value<10?10:value; } this._autoPurge = value; @@ -878,10 +1231,12 @@ this.createjs = this.createjs||{}; /** * Specifies whether or not StageGL is automatically purging unused textures. Higher numbers purge less - * often. Values below 10 are upgraded to 10, and -1 disables this feature. + * often. Values below 10 are upgraded to 10, and -1 disables this feature. If you are not dynamically adding + * and removing new images this can be se9000t to -1, and should be for performance reasons. If you only add images, + * or add and remove the same images for the entire application, it is safe to turn off this feature. Alternately, + * manually manage removing textures yourself with {{#crossLink "StageGL/releaseTexture"}}{{/crossLink}} * @property autoPurge - * @protected - * @type {Integer} + * @type {int} * @default 1000 */ autoPurge: { get: p._get_autoPurge, set: p._set_autoPurge } @@ -904,30 +1259,34 @@ this.createjs = this.createjs||{}; // defaults and options var options = { - depth: false, // Disable the depth buffer as it isn't used. - alpha: this._transparent, // Make the canvas background transparent. - stencil: true, + depth: false, // nothing has depth + stencil: false, // while there's uses for this, we're not using any yet + premultipliedAlpha: this._transparent, // this is complicated, trust it + + alpha: this._transparent, antialias: this._antialias, - premultipliedAlpha: this._premultiply, // Assume the drawing buffer contains colors with premultiplied alpha. preserveDrawingBuffer: this._preserveBuffer }; var gl = this._webGLContext = this._fetchWebGLContext(this.canvas, options); if (!gl) { return null; } - this.updateSimultaneousTextureCount(gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS)); - this._maxTextureSlots = gl.getParameter(gl.MAX_COMBINED_TEXTURE_IMAGE_UNITS); - this._createBuffers(gl); - this._initTextures(gl); - gl.disable(gl.DEPTH_TEST); + gl.depthMask(false); 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, this._premultiply); - //gl.pixelStorei(gl.UNPACK_COLORSPACE_CONVERSION_WEBGL, gl.NONE); + gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true); + gl.clearColor(0.0, 0.0, 0.0, 0); + + this._createBuffers(); + this._initMaterials(); + this._updateRenderMode("source-over"); - this._webGLContext.clearColor(this._clearColor.r, this._clearColor.g, this._clearColor.b, this._clearColor.a); - this.updateViewport(this._viewportWidth || this.canvas.width, this._viewportHeight || this.canvas.height); + this.updateViewport(this.canvas.width, this.canvas.height); + if (!this._directDraw) { + this._bufferTextureOutput = this.getRenderBufferTexture(this._viewportWidth, this._viewportHeight); + } + + this.canvas._invalid = true; } } else { this._webGLContext = null; @@ -943,16 +1302,12 @@ this.createjs = this.createjs||{}; if (!this.canvas) { return; } if (this.tickOnUpdate) { this.tick(props); } this.dispatchEvent("drawstart"); - if (this.autoClear) { this.clear(); } if (this._webGLContext) { - // Use WebGL. - this._batchDraw(this, this._webGLContext); - if (this._autoPurge != -1 && !(this._drawID%((this._autoPurge/2)|0))) { - this.purgeTextures(this._autoPurge); - } + this.draw(this._webGLContext, false); } else { // Use 2D. + if (this.autoClear) { this.clear(); } var ctx = this.canvas.getContext("2d"); ctx.save(); this.updateContext(ctx); @@ -967,18 +1322,15 @@ this.createjs = this.createjs||{}; */ p.clear = function () { if (!this.canvas) { return; } - if (StageGL.isWebGLActive(this._webGLContext)) { - var gl = this._webGLContext; - var cc = this._clearColor; - var adjust = this._transparent ? cc.a : 1.0; - // Use WebGL settings; adjust for pre multiplied alpha appropriate to scenario - this._webGLContext.clearColor(cc.r * adjust, cc.g * adjust, cc.b * adjust, adjust); - gl.clear(gl.COLOR_BUFFER_BIT); - this._webGLContext.clearColor(cc.r, cc.g, cc.b, cc.a); - } else { - // Use 2D. + + var gl = this._webGLContext; + if (!StageGL.isWebGLActive(gl)) { // Use 2D. this.Stage_clear(); + return; } + + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + this._clearFrameBuffer(this._transparent ? this._clearColor.a : 1); }; /** @@ -994,13 +1346,52 @@ this.createjs = this.createjs||{}; * @return {Boolean} If the draw was handled by this function */ p.draw = function (context, ignoreCache) { - if (context === this._webGLContext && StageGL.isWebGLActive(this._webGLContext)) { - var gl = this._webGLContext; - this._batchDraw(this, gl, ignoreCache); - return true; - } else { + var gl = this._webGLContext; + // 2D context fallback + if (!(context === this._webGLContext && StageGL.isWebGLActive(gl))) { return this.Stage_draw(context, ignoreCache); } + + var storeBatchOutput = this._batchTextureOutput; + var storeBatchConcat = this._batchTextureConcat; + var storeBatchTemp = this._batchTextureTemp; + + // Use WebGL + this._batchVertexCount = 0; + this._drawID++; + + if (this._directDraw) { + this._batchTextureOutput = this; + if (this.autoClear) { this.clear(); } + } else { + this._batchTextureOutput = this._bufferTextureOutput; + this._batchTextureConcat = this._bufferTextureConcat; + this._batchTextureTemp = this._bufferTextureTemp; + } + + this._updateRenderMode("source-over"); + this._drawContent(this, ignoreCache); + + if (!this._directDraw) { + if (this.autoClear) { this.clear(); } + this.batchReason = "finalOutput"; + this._drawCover(null, this._batchTextureOutput); + } + + // batches may generate or swap around textures. To be sure we capture them, store them back into buffer + this._bufferTextureOutput = this._batchTextureOutput; + this._bufferTextureConcat = this._batchTextureConcat; + this._bufferTextureTemp = this._batchTextureTemp; + + this._batchTextureOutput = storeBatchOutput; + this._batchTextureConcat = storeBatchConcat; + this._batchTextureTemp = storeBatchTemp; + + if (this._autoPurge !== -1 && !(this._drawID%((this._autoPurge/2)|0))) { + this.purgeTextures(this._autoPurge); + } + + return true; }; /** @@ -1010,81 +1401,64 @@ this.createjs = this.createjs||{}; * @method cacheDraw * @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). - * @param {Array} filters The filters we're drawing into cache. * @param {BitmapCache} manager The BitmapCache instance looking after the cache * @return {Boolean} If the draw was handled by this function */ - p.cacheDraw = function (target, filters, manager) { - if (StageGL.isWebGLActive(this._webGLContext)) { - var gl = this._webGLContext; - this._cacheDraw(gl, target, filters, manager); - return true; - } else { + p.cacheDraw = function (target, manager) { + // 2D context fallback + if (!StageGL.isWebGLActive(this._webGLContext)) { return false; } - }; - /** - * Blocks, or frees a texture "slot" on the GPU. Can be useful if you are overflowing textures. When overflowing - * textures they are re-uploaded to the GPU every time they're encountered, this can be expensive with large textures. - * By blocking the slot you reduce available slots, potentially increasing draw calls, but mostly you prevent a - * texture being re-uploaded if it would have moved slots due to overflow. - * - * NOTE: This method is mainly for internal use, though it may be useful for advanced uses. - * For example, block the slot a background image is stored in so there is less re-loading of that image. - * @method protectTextureSlot - * @param {Number} id The slot to be affected - * @param {Boolean} [lock=false] Whether this slot is the one being locked. - */ - p.protectTextureSlot = function (id, lock) { - if (id > this._maxTextureSlots || id < 0) { - throw "Slot outside of acceptable range"; - } - this._slotBlacklist[id] = !!lock; - }; + var storeBatchOutput = this._batchTextureOutput; + var storeBatchConcat = this._batchTextureConcat; + var storeBatchTemp = this._batchTextureTemp; - /** - * Render textures can't draw into themselves so any item being used for renderTextures needs two to alternate between. - * This function creates, gets, and toggles the render surface between the two. - * - * NOTE: This method is mainly for internal use, though it may be useful for advanced uses. - * @method getTargetRenderTexture - * @param {DisplayObject} target The object associated with the render textures, usually a cached object. - * @param {Number} w The width to create the texture at. - * @param {Number} h The height to create the texture at. - * @return {Objet} - * @todo fill in return type - */ - p.getTargetRenderTexture = function (target, w, h) { - var result, toggle = false; - var gl = this._webGLContext; - if (target.__lastRT !== undefined && target.__lastRT === target.__rtA) { toggle = true; } - if (!toggle) { - if (target.__rtA === undefined) { - target.__rtA = this.getRenderBufferTexture(w, h); - } else { - if (w != target.__rtA._width || h != target.__rtA._height) { - this.resizeTexture(target.__rtA, w, h); - } - this.setTextureParams(gl); - } - result = target.__rtA; - } else { - if (target.__rtB === undefined) { - target.__rtB = this.getRenderBufferTexture(w, h); - } else { - if (w != target.__rtB._width || h != target.__rtB._height) { - this.resizeTexture(target.__rtB, w, h); - } - this.setTextureParams(gl); - } - result = target.__rtB; + var filterCount = manager._filterCount, filtersLeft = filterCount; + var backupWidth = this._viewportWidth, backupHeight = this._viewportHeight; + this.updateViewport(manager._drawWidth, manager._drawHeight); + + this._batchTextureOutput = (manager._filterCount%2) ? manager._bufferTextureConcat : manager._bufferTextureOutput; + this._batchTextureConcat = (manager._filterCount%2) ? manager._bufferTextureOutput : manager._bufferTextureConcat; + this._batchTextureTemp = manager._bufferTextureTemp; + + var container = this._cacheContainer; + container.children = [target]; + container.transformMatrix = this._alignTargetToCache(target, manager); + + this._updateRenderMode("source-over"); + this._drawContent(container, true); + + // re-align buffers with fake filter passes to solve certain error cases + if (this.isCacheControlled) { + // post filter pass to place content into output buffer + //TODO: add in directDraw support for cache controlled StageGLs + filterCount++; + filtersLeft++; + } else if (manager._cacheCanvas !== ((manager._filterCount%2) ? this._batchTextureConcat : this._batchTextureOutput)) { + // pre filter pass to align output, may of become misaligned due to composite operations + filtersLeft++; } - if (!result) { - throw "Problems creating render textures, known causes include using too much VRAM by not releasing WebGL texture instances"; + + while (filtersLeft) { //warning: pay attention to where filtersLeft is modified, this is a micro-optimization + var filter = manager._getGLFilter(filterCount - (filtersLeft--)); + var swap = this._batchTextureConcat; + this._batchTextureConcat = this._batchTextureOutput; + this._batchTextureOutput = (this.isCacheControlled && filtersLeft === 0) ? this : swap; + this.batchReason = "filterPass"; + this._drawCover(this._batchTextureOutput._frameBuffer, this._batchTextureConcat, filter); } - target.__lastRT = result; - return result; + + manager._bufferTextureOutput = this._batchTextureOutput; + manager._bufferTextureConcat = this._batchTextureConcat; + manager._bufferTextureTemp = this._batchTextureTemp; + + this._batchTextureOutput = storeBatchOutput; + this._batchTextureConcat = storeBatchConcat; + this._batchTextureTemp = storeBatchTemp; + + this.updateViewport(backupWidth, backupHeight); + return true; }; /** @@ -1098,10 +1472,10 @@ this.createjs = this.createjs||{}; * developer to ensure that a texture in use is not removed. * * Textures in use, or to be used again shortly, should not be removed. This is simply for performance reasons. - * Removing a texture in use will cause the texture to have to be re-uploaded slowing rendering. + * Removing a texture in use will cause the texture to end up being re-uploaded slowing rendering. * @method releaseTexture - * @param {DisplayObject | Texture | Image | Canvas} item An object that used the texture to be discarded. - * @param {Boolean} safe Should the release attempt to be "safe" and only delete this usage. + * @param {DisplayObject | WebGLTexture | Image | Canvas} item An object that used the texture to be discarded. + * @param {Boolean} [safe=false] Should the release attempt to be "safe" and only delete this usage. */ p.releaseTexture = function (item, safe) { var i, l; @@ -1110,7 +1484,7 @@ this.createjs = this.createjs||{}; // this is a container object if (item.children) { for (i = 0, l = item.children.length; i < l; i++) { - this.releaseTexture(item.children[i]); + this.releaseTexture(item.children[i], safe); } } @@ -1136,7 +1510,7 @@ this.createjs = this.createjs||{}; } else if (item._webGLRenderStyle === 1) { // this is a SpriteSheet, we can't tell which image we used from the list easily so remove them all! for (i = 0, l = item.spriteSheet._images.length; i < l; i++) { - this.releaseTexture(item.spriteSheet._images[i]); + this.releaseTexture(item.spriteSheet._images[i], safe); } return; } @@ -1194,50 +1568,13 @@ this.createjs = this.createjs||{}; } }; - /** - * Try to set the max textures the system can handle. It should default to the hardware maximum, and lower values - * may limit performance. Some devices have been known to mis-report their max textures, or you may need a standard - * baseline cross devices for testing. Barring the previous suggestions, there is little need to call this function - * as the library will automatically try to find the best value. - * - * NOTE: This method is mainly for internal use, though it may be useful for advanced uses. - * @method updateSimultaneousTextureCount - * @param {Number} [count=1] The number of textures intended for simultaneous loading. - */ - p.updateSimultaneousTextureCount = function (count) { - // TODO: DHG: make sure API works in all instances, may be some issues with buffers etc I haven't foreseen - var gl = this._webGLContext; - var success = false; - - if (count < 1 || isNaN(count)) { count = 1; } - this._batchTextureCount = count; - - 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, but * the render surface's instead. This is necessary after manually resizing the canvas element on the DOM to avoid a * up/down scaled render. * @method updateViewport - * @param {Integer} width The width of the render surface in pixels. - * @param {Integer} height The height of the render surface in pixels. + * @param {int} width The width of the render surface in pixels. + * @param {int} height The height of the render surface in pixels. */ p.updateViewport = function (width, height) { this._viewportWidth = width|0; @@ -1252,16 +1589,20 @@ this.createjs = this.createjs||{}; // additionally we offset into they Y so the polygons are inside the camera's "clipping" plane this._projectionMatrix = new Float32Array([ 2 / this._viewportWidth, 0, 0, 0, - 0, -2 / this._viewportHeight, 1, 0, + 0, -2 / this._viewportHeight, 0, 0, 0, 0, 1, 0, - -1, 1, 0.1, 0 + -1, 1, 0, 1 ]); - // create the flipped version for use with render texture flipping - // DHG: this would be a slice/clone but some platforms don't offer them for Float32Array - this._projectionMatrixFlip = new Float32Array([0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0]); - this._projectionMatrixFlip.set(this._projectionMatrix); - this._projectionMatrixFlip[5] *= -1; - this._projectionMatrixFlip[13] *= -1; + + if (this._bufferTextureOutput !== null) { + this.resizeTexture(this._bufferTextureOutput, this._viewportWidth, this._viewportHeight); + } + if (this._bufferTextureConcat !== null) { + this.resizeTexture(this._bufferTextureConcat, this._viewportWidth, this._viewportHeight); + } + if (this._bufferTextureTemp !== null) { + this.resizeTexture(this._bufferTextureTemp, this._viewportWidth, this._viewportHeight); + } } }; @@ -1287,8 +1628,7 @@ this.createjs = this.createjs||{}; } else { try { targetShader = this._fetchShaderProgram( - gl, "filter", - filter.VTX_SHADER_BODY, filter.FRAG_SHADER_BODY, + true, filter.VTX_SHADER_BODY, filter.FRAG_SHADER_BODY, filter.shaderParamSetup && filter.shaderParamSetup.bind(filter) ); filter._builtShader = targetShader; @@ -1330,10 +1670,12 @@ this.createjs = this.createjs||{}; * @protected * @method resizeTexture * @param {WebGLTexture} texture The GL Texture to be modified. - * @param {uint} [width=1] The width of the texture in pixels, defaults to 1 - * @param {uint} [height=1] The height of the texture in pixels, defaults to 1 + * @param {uint} width The width of the texture in pixels + * @param {uint} height The height of the texture in pixels */ p.resizeTexture = function (texture, width,height) { + if (texture.width === width && texture.height === height){ return; } + var gl = this._webGLContext; gl.bindTexture(gl.TEXTURE_2D, texture); gl.texImage2D( @@ -1345,6 +1687,8 @@ this.createjs = this.createjs||{}; gl.UNSIGNED_BYTE, // type of texture(pixel color depth) null // image data, we can do null because we're doing array data ); + + // set its width and height for spoofing as an image and tracking texture.width = width; texture.height = height; }; @@ -1352,24 +1696,22 @@ this.createjs = this.createjs||{}; /** * Returns a base texture (see {{#crossLink "StageGL/getBaseTexture"}}{{/crossLink}}) for details. Also includes an * attached and linked render buffer in `texture._frameBuffer`. RenderTextures can be thought of as an internal - * canvas on the GPU that can be drawn to. + * canvas on the GPU that can be drawn to. Being internal to the GPU they are much faster than "offscreen canvases". * @method getRenderBufferTexture * @param {Number} w The width of the texture in pixels. * @param {Number} h The height of the texture in pixels. - * @return {Texture} the basic texture instance with a render buffer property. + * @return {WebGLTexture} the basic texture instance with a render buffer property. */ p.getRenderBufferTexture = function (w, h) { var gl = this._webGLContext; - // get the texture var renderTexture = this.getBaseTexture(w, h); if (!renderTexture) { return null; } - // get the frame buffer var frameBuffer = gl.createFramebuffer(); if (!frameBuffer) { return null; } - // set its width and height for spoofing as an image + // set its width and height for spoofing as an image and tracking renderTexture.width = w; renderTexture.height = h; @@ -1427,16 +1769,16 @@ this.createjs = this.createjs||{}; p.setClearColor = function (color) { var r, g, b, a, output; - if (typeof color == "string") { - if (color.indexOf("#") == 0) { - if (color.length == 4) { + if (typeof color === "string") { + if (color.indexOf("#") === 0) { + if (color.length === 4) { color = "#" + color.charAt(1)+color.charAt(1) + color.charAt(2)+color.charAt(2) + color.charAt(3)+color.charAt(3) } r = Number("0x"+color.slice(1, 3))/255; g = Number("0x"+color.slice(3, 5))/255; b = Number("0x"+color.slice(5, 7))/255; a = Number("0x"+color.slice(7, 9))/255; - } else if (color.indexOf("rgba(") == 0) { + } else if (color.indexOf("rgba(") === 0) { output = color.slice(5, -1).split(","); r = Number(output[0])/255; g = Number(output[1])/255; @@ -1454,9 +1796,6 @@ this.createjs = this.createjs||{}; this._clearColor.g = g || 0; this._clearColor.b = b || 0; this._clearColor.a = a || 0; - - if (!this._webGLContext) { return; } - this._webGLContext.clearColor(this._clearColor.r, this._clearColor.g, this._clearColor.b, this._clearColor.a); }; /** @@ -1474,11 +1813,12 @@ this.createjs = this.createjs||{}; * @method _getSafeTexture * @param {uint} [w=1] The width of the texture in pixels, defaults to 1 * @param {uint} [h=1] The height of the texture in pixels, defaults to 1 + * @protected */ p._getSafeTexture = function (w, h) { var texture = this.getBaseTexture(w, h); - if(!texture) { + if (!texture) { var msg = "Problem creating texture, possible cause: using too much VRAM, please try releasing texture memory"; (console.error && console.error(msg)) || console.log(msg); @@ -1488,6 +1828,25 @@ this.createjs = this.createjs||{}; return texture; }; + /** + * Visually clear out the currently active FrameBuffer, does not rebind or adjust the frameBuffer in use. + * @method _getSafeTexture + * @param alpha + * @protected + */ + p._clearFrameBuffer = function (alpha) { + var gl = this._webGLContext; + var cc = this._clearColor; + + if (alpha > 0) { alpha = 1; } + if (alpha < 0) { alpha = 0; } + + // Use WebGL settings; adjust for pre multiplied alpha appropriate to scenario + gl.clearColor(cc.r * alpha, cc.g * alpha, cc.b * alpha, alpha); + gl.clear(gl.COLOR_BUFFER_BIT); + gl.clearColor(0, 0, 0, 0); + }; + /** * Sets up and returns the WebGL context for the canvas. May return undefined in error scenarios. These can include * situations where the canvas element already has a context, 2D or GL. @@ -1522,40 +1881,29 @@ this.createjs = this.createjs||{}; * filters. Once compiled, shaders are saved so. If the Shader code requires dynamic alterations re-run this function * to generate a new shader. * @method _fetchShaderProgram - * @param {WebGLRenderingContext} gl The canvas WebGL context object to draw into. - * @param {String} [shaderName="regular"] Working values: "regular", "override", and "filter". Which type of shader to build. + * @param {Boolean} coverShader Is this a per object shader or a cover shader * Filter and override both accept the custom params. Regular and override have all features. Filter is a special case reduced feature shader meant to be over-ridden. - * @param {String} [customVTX] Extra vertex shader information to replace a regular draw, see + * @param {String | undefined} [customVTX=undefined] Extra vertex shader information to replace a regular draw, see * {{#crossLink "StageGL/COVER_VERTEX_BODY"}}{{/crossLink}} for default and {{#crossLink "Filter"}}{{/crossLink}} for examples. - * @param {String} [customFRAG] Extra fragment shader information to replace a regular draw, see + * @param {String | undefined} [customFRAG=undefined] Extra fragment shader information to replace a regular draw, see * {{#crossLink "StageGL/COVER_FRAGMENT_BODY"}}{{/crossLink}} for default and {{#crossLink "Filter"}}{{/crossLink}} for examples. - * @param {Function} [shaderParamSetup] Function to run so custom shader parameters can get applied for the render. + * @param {Function | undefined} [shaderParamSetup=undefined] Function to run so custom shader parameters can get applied for the render. * @protected * @return {WebGLProgram} The compiled and linked shader */ - p._fetchShaderProgram = function (gl, shaderName, customVTX, customFRAG, shaderParamSetup) { + p._fetchShaderProgram = function (coverShader, customVTX, customFRAG, shaderParamSetup) { + var gl = this._webGLContext; + gl.useProgram(null); // safety to avoid collisions // build the correct shader string out of the right headers and bodies var targetFrag, targetVtx; - switch (shaderName) { - case "filter": - targetVtx = StageGL.COVER_VERTEX_HEADER + (customVTX || StageGL.COVER_VERTEX_BODY); - targetFrag = StageGL.COVER_FRAGMENT_HEADER + (customFRAG || StageGL.COVER_FRAGMENT_BODY); - break; - case "particle": //TODO - targetVtx = StageGL.REGULAR_VERTEX_HEADER + StageGL.PARTICLE_VERTEX_BODY; - targetFrag = StageGL.REGULAR_FRAGMENT_HEADER + StageGL.PARTICLE_FRAGMENT_BODY; - break; - case "override": - targetVtx = StageGL.REGULAR_VERTEX_HEADER + (customVTX || StageGL.REGULAR_VERTEX_BODY); - targetFrag = StageGL.REGULAR_FRAGMENT_HEADER + (customFRAG || StageGL.REGULAR_FRAGMENT_BODY); - break; - case "regular": - default: - targetVtx = StageGL.REGULAR_VERTEX_HEADER + StageGL.REGULAR_VERTEX_BODY; - targetFrag = StageGL.REGULAR_FRAGMENT_HEADER + StageGL.REGULAR_FRAGMENT_BODY; - break; + if (coverShader) { + targetVtx = StageGL.COVER_VERTEX_HEADER + (customVTX || StageGL.COVER_VERTEX_BODY); + targetFrag = StageGL.COVER_FRAGMENT_HEADER + (customFRAG || StageGL.COVER_FRAGMENT_BODY); + } else { + targetVtx = StageGL.REGULAR_VERTEX_HEADER + (customVTX || StageGL.REGULAR_VERTEX_BODY); + targetFrag = StageGL.REGULAR_FRAGMENT_HEADER + (customFRAG || StageGL.REGULAR_FRAGMENT_BODY); } // create the separate vars @@ -1567,7 +1915,6 @@ this.createjs = this.createjs||{}; gl.attachShader(shaderProgram, vertexShader); gl.attachShader(shaderProgram, fragmentShader); gl.linkProgram(shaderProgram); - shaderProgram._type = shaderName; // check compile status if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) { @@ -1577,58 +1924,44 @@ this.createjs = this.createjs||{}; // set up the parameters on the shader gl.useProgram(shaderProgram); - switch (shaderName) { - case "filter": - // 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); + // 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.samplerUniform = gl.getUniformLocation(shaderProgram, "uSampler"); - gl.uniform1i(shaderProgram.samplerUniform, 0); + shaderProgram.uvPositionAttribute = gl.getAttribLocation(shaderProgram, "uvPosition"); + gl.enableVertexAttribArray(shaderProgram.uvPositionAttribute); - shaderProgram.uprightUniform = gl.getUniformLocation(shaderProgram, "uUpright"); - gl.uniform1f(shaderProgram.uprightUniform, 0); + if (coverShader) { + 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 "override": - case "particle": - case "regular": - 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; - } + // if there's some custom attributes be sure to hook them up + if (shaderParamSetup) { + shaderParamSetup(gl, this, shaderProgram); + } + } else { + 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._gpuTextureCount; i++) { + samplers[i] = i; + } + shaderProgram.samplerData = samplers; - shaderProgram.samplerData = samplers; - shaderProgram.samplerUniform = gl.getUniformLocation(shaderProgram, "uSampler"); - gl.uniform1iv(shaderProgram.samplerUniform, samplers); + shaderProgram.samplerUniform = gl.getUniformLocation(shaderProgram, "uSampler"); + gl.uniform1iv(shaderProgram.samplerUniform, shaderProgram.samplerData); - shaderProgram.pMatrixUniform = gl.getUniformLocation(shaderProgram, "pMatrix"); - break; + shaderProgram.pMatrixUniform = gl.getUniformLocation(shaderProgram, "pMatrix"); } + shaderProgram._type = coverShader ? "cover" : "batch"; + gl.useProgram(this._activeShader); return shaderProgram; }; @@ -1643,17 +1976,20 @@ this.createjs = this.createjs||{}; * @protected */ p._createShader = function (gl, type, str) { - // inject the static number - str = str.replace(/\{\{count}}/g, this._batchTextureCount); + var textureCount = this._batchTextureCount; - // resolve issue with no dynamic samplers by creating correct samplers in if else chain - // TODO: WebGL 2.0 does not need this support - var insert = ""; - for (var i = 1; i= 0) { this._textureDictionary[texture._storeID] = undefined; for (var n in this._textureIDs) { - if (this._textureIDs[n] == texture._storeID) { delete this._textureIDs[n]; } + if (this._textureIDs[n] === texture._storeID) { delete this._textureIDs[n]; } } var data = texture._imageData; - for (var i=data.length-1; i>=0; i--) { data[i]._storeID = undefined; } + if (data) { + for (var i=data.length-1; i>=0; i--) { data[i]._storeID = undefined; } + } texture._imageData = texture._storeID = undefined; } @@ -1978,241 +2341,177 @@ this.createjs = this.createjs||{}; }; /** - * Store or restore current batch textures into a backup array - * @method _backupBatchTextures - * @param {Boolean} restore Perform a restore instead of a store. - * @param {Array} [target=this._backupTextures] Where to perform the backup, defaults to internal backup. + * Small utility function to keep internal API consistent and set the uniforms for a dual texture cover render + * @method _setCoverMixShaderParams + * @param {WebGLRenderingContext} gl The context where the drawing takes place + * @param stage unused + * @param shaderProgram unused * @protected */ - p._backupBatchTextures = function (restore, target) { - var gl = this._webGLContext; - - if (!this._backupTextures) { this._backupTextures = []; } - if (target === undefined) { target = this._backupTextures; } - - for (var i=0; i 0) { - this._drawBuffers(gl); + p._updateRenderMode = function (newMode) { + if ( newMode === null || newMode === undefined){ newMode = "source-over"; } + + var blendSrc = StageGL.BLEND_SOURCES[newMode]; + if (blendSrc === undefined) { + if (this.vocalDebug){ console.log("Unknown compositeOperation ["+ newMode +"], reverting to default"); } + blendSrc = StageGL.BLEND_SOURCES[newMode = "source-over"]; } - this._isDrawing++; - this._drawID++; - this.batchCardCount = 0; - this.depth = 0; + if (this._renderMode === newMode) { return; } - this._appendToBatchGroup(sceneGraph, gl, new createjs.Matrix2D(), this.alpha, ignoreCache); + var gl = this._webGLContext; + var shaderData = this._builtShaders[newMode]; + if (shaderData === undefined) { + try { + shaderData = this._builtShaders[newMode] = { + eqRGB: gl[blendSrc.eqRGB || "FUNC_ADD"], + srcRGB: gl[blendSrc.srcRGB || "ONE"], + dstRGB: gl[blendSrc.dstRGB || "ONE_MINUS_SRC_ALPHA"], + eqA: gl[blendSrc.eqA || "FUNC_ADD"], + srcA: gl[blendSrc.srcA || "ONE"], + dstA: gl[blendSrc.dstA || "ONE_MINUS_SRC_ALPHA"], + immediate: blendSrc.shader !== undefined, + shader: (blendSrc.shader || this._builtShaders["source-over"] === undefined) ? + this._fetchShaderProgram( + true, undefined, blendSrc.shader, + this._setCoverMixShaderParams + ) : this._builtShaders["source-over"].shader // re-use source-over when we don't need a new shader + }; + if (blendSrc.shader) { shaderData.shader._name = newMode; } + } catch (e) { + this._builtShaders[newMode] = undefined; + console && console.log("SHADER SWITCH FAILURE", e); + return; + } + } - this.batchReason = "drawFinish"; - this._drawBuffers(gl); // <-------------------------------------------------------- - this._isDrawing--; + if (shaderData.immediate && this._directDraw) { + if (this.vocalDebug) { console.log("Illegal compositeOperation ["+ newMode +"] due to StageGL.directDraw = true, reverting to default"); } + return; + } + + gl.bindFramebuffer(gl.FRAMEBUFFER, this._batchTextureOutput._frameBuffer); + + this.batchReason = "shaderSwap"; + this._renderBatch(); // <-------------------------------------------------------------------------------- + + this._renderMode = newMode; + this._immediateRender = shaderData.immediate; + gl.blendEquationSeparate(shaderData.eqRGB, shaderData.eqA); + gl.blendFuncSeparate(shaderData.srcRGB, shaderData.dstRGB, shaderData.srcA, shaderData.dstA); }; /** - * Perform the drawing process to fill a specific cache texture, including applying filters. - * @method _cacheDraw - * @param {DisplayObject} target The object we're drawing into the cache. For example, used for drawing the cache - * (to prevent it from simply drawing an existing cache back into itself). - * @param {Array} filters The filters we're drawing into cache. - * @param {BitmapCache} manager The BitmapCache instance looking after the cache + * Helper function for the standard content draw + * @method _drawContent + * @param {Stage | Container} content + * @param {Boolean} ignoreCache * @protected */ - p._cacheDraw = function (gl, target, filters, manager) { - /* - Implicitly there are 4 modes to this function: filtered-sameContext, filtered-uniqueContext, sameContext, uniqueContext. - Each situation must be handled slightly differently as 'sameContext' or 'uniqueContext' define how the output works, - one drawing directly into the main context and the other drawing into a stored renderTexture respectively. - When the draw is a 'filtered' draw, the filters are applied sequentially and will draw into saved textures repeatedly. - Once the final filter is done the final output is treated depending upon whether it is a same or unique context. - The internal complexity comes from reducing over-draw, shared code, and issues like textures needing to be flipped - sometimes when written to render textures. - */ - var renderTexture; - var shaderBackup = this._activeShader; - var blackListBackup = this._slotBlacklist; - var lastTextureSlot = this._maxTextureSlots-1; - var wBackup = this._viewportWidth, hBackup = this._viewportHeight; - - // protect the last slot so that we have somewhere to bind the renderTextures so it doesn't get upset - this.protectTextureSlot(lastTextureSlot, true); - - // create offset container for drawing item - var mtx = target.getMatrix(); - mtx = mtx.clone(); - mtx.scale(1/manager.scale, 1/manager.scale); - mtx = mtx.invert(); - mtx.translate(-manager.offX/manager.scale*target.scaleX, -manager.offY/manager.scale*target.scaleY); - var container = this._cacheContainer; - container.children = [target]; - container.transformMatrix = mtx; + p._drawContent = function (content, ignoreCache) { + var gl = this._webGLContext; - this._backupBatchTextures(false); + this._activeShader = this._mainShader; - if (filters && filters.length) { - this._drawFilters(target, filters, manager); - } else { - // is this for another stage or mine? - if (this.isCacheControlled) { - // draw item to canvas I -> C - gl.clear(gl.COLOR_BUFFER_BIT); - this._batchDraw(container, gl, true); - } else { - gl.activeTexture(gl.TEXTURE0 + lastTextureSlot); - target.cacheCanvas = this.getTargetRenderTexture(target, manager._drawWidth, manager._drawHeight); - renderTexture = target.cacheCanvas; - - // draw item to render texture I -> T - gl.bindFramebuffer(gl.FRAMEBUFFER, renderTexture._frameBuffer); - this.updateViewport(manager._drawWidth, manager._drawHeight); - this._projectionMatrix = this._projectionMatrixFlip; - gl.clear(gl.COLOR_BUFFER_BIT); - this._batchDraw(container, gl, true); - - gl.bindFramebuffer(gl.FRAMEBUFFER, null); - this.updateViewport(wBackup, hBackup); - } - } + gl.bindFramebuffer(gl.FRAMEBUFFER, this._batchTextureOutput._frameBuffer); + gl.clear(gl.COLOR_BUFFER_BIT); - this._backupBatchTextures(true); + this._appendToBatch(content, new createjs.Matrix2D(), this.alpha, ignoreCache); - this.protectTextureSlot(lastTextureSlot, false); - this._activeShader = shaderBackup; - this._slotBlacklist = blackListBackup; + this.batchReason = "contentEnd"; + this._renderBatch(); }; /** - * Sub portion of _cacheDraw, split off for readability. Do not call independently. - * @method _drawFilters - * @param {DisplayObject} target The object we're drawing with a filter. - * @param {Array} filters The filters we're drawing into cache. - * @param {BitmapCache} manager The BitmapCache instance looking after the cache + * Used to draw one or more textures potentially using a filter into the supplied buffer. + * Mostly used for caches, filters, and outputting final render frames. + * Draws `dst` into `out` after applying `srcFilter` depending on its current value. + * @method _drawCover + * @param {WebGLFramebuffer} out Buffer to draw the results into (null is the canvas element) + * @param {WebGLTexture} dst Base texture layer aka "destination" in image blending terminology + * @param {WebGLTexture | Filter} [srcFilter = undefined] Modification parameter for the draw. If a texture, the + * current _renderMode applies it as a "source" image. If a Filter, the filter is applied to the dst with its params. + * @protected */ - p._drawFilters = function (target, filters, manager) { + p._drawCover = function (out, dst, srcFilter) { var gl = this._webGLContext; - var renderTexture; - var lastTextureSlot = this._maxTextureSlots-1; - var wBackup = this._viewportWidth, hBackup = this._viewportHeight; - - var container = this._cacheContainer; - var filterCount = filters.length; - // 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 + lastTextureSlot); - renderTexture = this.getTargetRenderTexture(target, manager._drawWidth, manager._drawHeight); + gl.bindFramebuffer(gl.FRAMEBUFFER, out); + if (out !== null){ gl.clear(gl.COLOR_BUFFER_BIT); } - // draw item to render texture I -> T - gl.bindFramebuffer(gl.FRAMEBUFFER, renderTexture._frameBuffer); - this.updateViewport(manager._drawWidth, manager._drawHeight); - gl.clear(gl.COLOR_BUFFER_BIT); - this._batchDraw(container, gl, true); - - // 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.bindTexture(gl.TEXTURE_2D, dst); this.setTextureParams(gl); - var flipY = false; - var i = 0, filter = filters[i]; - do { // this is safe because we wouldn't be in apply filters without a filter count of at least 1 - - // swap to correct shader - this._activeShader = this.getFilterShader(filter); - if (!this._activeShader) { continue; } - - // now the old result is stored in slot 0, make a new render texture - gl.activeTexture(gl.TEXTURE0 + lastTextureSlot); - renderTexture = this.getTargetRenderTexture(target, manager._drawWidth, manager._drawHeight); - gl.bindFramebuffer(gl.FRAMEBUFFER, renderTexture._frameBuffer); - - // draw result to render texture R -> T - gl.viewport(0, 0, manager._drawWidth, manager._drawHeight); - gl.clear(gl.COLOR_BUFFER_BIT); - 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); - this.setTextureParams(gl); - - // use flipping to keep things upright, things already cancel out on a single filter - // this needs to be here as multiPass is not accurate to _this_ frame until after shader acquisition - if (filterCount > 1 || filters[0]._multiPass) { - flipY = !flipY; + if (srcFilter instanceof createjs.Filter) { + this._activeShader = this.getFilterShader(srcFilter); + } else { + if (srcFilter instanceof WebGLTexture) { + gl.activeTexture(gl.TEXTURE1); + gl.bindTexture(gl.TEXTURE_2D, srcFilter); + this.setTextureParams(gl); + } else if (srcFilter !== undefined && this.vocalDebug) { + console.log("Unknown data handed to function: ", srcFilter); } + this._activeShader = this._builtShaders[this._renderMode].shader; + } - // work through the multipass if it's there, otherwise move on - filter = filter._multiPass !== null ? filter._multiPass : filters[++i]; - } while (filter); - - // is this for another stage or mine - if (this.isCacheControlled) { - gl.bindFramebuffer(gl.FRAMEBUFFER, null); - this.updateViewport(wBackup, hBackup); + this._renderCover(); + }; - // draw result to canvas R -> C - this._activeShader = this.getFilterShader(this); - gl.clear(gl.COLOR_BUFFER_BIT); - this._drawCover(gl, flipY); + /** + * Returns a matrix that can be used to counter position the `target` so that it fits and scales to the `manager` + * @param {DisplayObject} target The object to be counter positioned + * @param {BitmapCache} manager The cache manager to be aligned to + * @returns {Matrix2D} The matrix that can be used used to counter position the container + * @method _alignTargetToCache + * @private + */ + p._alignTargetToCache = function(target, manager) { + if (manager._counterMatrix === null) { + manager._counterMatrix = target.getMatrix(); } else { - //TODO: DHG: this is less than ideal. A flipped initial render for this circumstance might help. Adjust the perspective matrix? - if (flipY) { - gl.activeTexture(gl.TEXTURE0 + lastTextureSlot); - renderTexture = this.getTargetRenderTexture(target, manager._drawWidth, manager._drawHeight); - gl.bindFramebuffer(gl.FRAMEBUFFER, renderTexture._frameBuffer); - - this._activeShader = this.getFilterShader(this); - gl.viewport(0, 0, manager._drawWidth, manager._drawHeight); - gl.clear(gl.COLOR_BUFFER_BIT); - this._drawCover(gl, !flipY); - } - gl.bindFramebuffer(gl.FRAMEBUFFER, null); - this.updateViewport(wBackup, hBackup); - - // make sure the last texture is the active thing to draw - target.cacheCanvas = renderTexture; + target.getMatrix(manager._counterMatrix) } + + var mtx = manager._counterMatrix; + mtx.scale(1/manager.scale, 1/manager.scale); + mtx = mtx.invert(); + mtx.translate(-manager.offX/manager.scale*target.scaleX, -manager.offY/manager.scale*target.scaleY); + + return mtx; }; /** * Add all the contents of a container to the pending buffers, called recursively on each container. This may * trigger a draw if a buffer runs out of space. This is the main workforce of the render loop. - * @method _appendToBatchGroup + * @method _appendToBatch * @param {Container} container The {{#crossLink "Container"}}{{/crossLink}} that contains everything to be drawn. - * @param {WebGLRenderingContext} gl The canvas WebGL context object to draw into. * @param {Matrix2D} concatMtx The effective (concatenated) transformation matrix when beginning this container * @param {Number} concatAlpha The effective (concatenated) alpha when beginning this container - * @param {Boolean} ignoreCache Don't use an element's cache during this draw + * @param {Boolean} [ignoreCache=false] Don't use an element's cache during this draw * @protected */ - p._appendToBatchGroup = function (container, gl, concatMtx, concatAlpha, ignoreCache) { + p._appendToBatch = function (container, concatMtx, concatAlpha, ignoreCache) { + var gl = this._webGLContext; + // sort out shared properties - if (!container._glMtx) { container._glMtx = new createjs.Matrix2D(); } var cMtx = container._glMtx; cMtx.copy(concatMtx); - if (container.transformMatrix) { + if (container.transformMatrix !== null) { cMtx.appendMatrix(container.transformMatrix); } else { cMtx.appendTransform( @@ -2223,6 +2522,11 @@ this.createjs = this.createjs||{}; ); } + var previousRenderMode = this._renderMode; + if (container.compositeOperation) { + this._updateRenderMode(container.compositeOperation); + } + // sub components of figuring out the position an object holds var subL, subT, subR, subB; @@ -2230,28 +2534,53 @@ this.createjs = this.createjs||{}; var l = container.children.length; for (var i = 0; i < l; i++) { var item = container.children[i]; + var useCache = (!ignoreCache && item.cacheCanvas) || false; - if (!(item.visible && concatAlpha)) { continue; } - if (!item.cacheCanvas || ignoreCache) { + if (!(item.visible && concatAlpha > 0.0035)) { continue; } + + if (useCache === false) { if (item._updateState){ item._updateState(); } - if (item.children) { - this._appendToBatchGroup(item, gl, cMtx, item.alpha * concatAlpha); - continue; + + if(!ignoreCache && item.cacheCanvas === null && item.filters !== null && item.filters.length) { + var bounds; + if (item.bitmapCache === null) { + bounds = item.getBounds(); + item.bitmapCache = new createjs.BitmapCache(); + item.bitmapCache._autoGenerated = true; + } + if (item.bitmapCache._autoGenerated) { + this.batchReason = "cachelessFilterInterupt"; + this._renderBatch(); // <---------------------------------------------------- + + var shaderBackup = this._activeShader; + bounds = bounds || item.getBounds(); + item.bitmapCache.define(item, bounds.x, bounds.y, bounds.width, bounds.height, 1, {useGL:this}); + useCache = item.bitmapCache._cacheCanvas; + + this._activeShader = shaderBackup; + gl.bindFramebuffer(gl.FRAMEBUFFER, this._batchTextureOutput._frameBuffer); + } } } + if (useCache === false && item.children) { + this._appendToBatch(item, cMtx, item.alpha * concatAlpha); + continue; + } + + if (item.compositeOperation !== null) { + this._updateRenderMode(item.compositeOperation); + } + // check for overflowing batch, if yes then force a render - // TODO: DHG: consider making this polygon count dependant for things like vector draws - if (this.batchCardCount+1 > this._maxCardsPerBatch) { + if (this._batchVertexCount + StageGL.INDICIES_PER_CARD > this._maxBatchVertexCount) { this.batchReason = "vertexOverflow"; - this._drawBuffers(gl); // <------------------------------------------------------------ - this.batchCardCount = 0; + this._renderBatch(); // <------------------------------------------------------------ } // keep track of concatenated position - if (!item._glMtx) { item._glMtx = new createjs.Matrix2D(); } var iMtx = item._glMtx; iMtx.copy(cMtx); if (item.transformMatrix) { @@ -2266,16 +2595,20 @@ this.createjs = this.createjs||{}; } var uvRect, texIndex, image, frame, texture, src; - var useCache = item.cacheCanvas && !ignoreCache; // get the image data, or abort if not present - if (item._webGLRenderStyle === 2 || useCache) { // BITMAP / Cached Canvas - image = (ignoreCache?false:item.cacheCanvas) || item.image; - } else if (item._webGLRenderStyle === 1) { // SPRITE - frame = item.spriteSheet.getFrame(item.currentFrame); //TODO: Faster way? + // BITMAP / Cached Canvas + if (item._webGLRenderStyle === 2 || useCache !== false) { + image = useCache === false ? item.image : useCache; + + // SPRITE + } else if (item._webGLRenderStyle === 1) { + frame = item.spriteSheet.getFrame(item.currentFrame); if (frame === null) { continue; } image = frame.image; - } else { // MISC (DOM objects render themselves later) + + // MISC (DOM objects render themselves later) + } else { continue; } if (!image) { continue; } @@ -2306,15 +2639,16 @@ this.createjs = this.createjs||{}; texIndex = texture._activeIndex; image._drawID = this._drawID; - if (item._webGLRenderStyle === 2 || useCache) { // BITMAP / Cached Canvas - if (!useCache && item.sourceRect) { + // BITMAP / Cached Canvas + if (item._webGLRenderStyle === 2 || useCache !== false) { + if (useCache === false && item.sourceRect) { // calculate uvs if (!item._uvRect) { item._uvRect = {}; } src = item.sourceRect; uvRect = item._uvRect; - uvRect.t = (src.y)/image.height; + uvRect.t = 1 - ((src.y)/image.height); uvRect.l = (src.x)/image.width; - uvRect.b = (src.y + src.height)/image.height; + uvRect.b = 1 - ((src.y + src.height)/image.height); uvRect.r = (src.x + src.width)/image.width; // calculate vertices @@ -2324,16 +2658,18 @@ this.createjs = this.createjs||{}; // calculate uvs uvRect = StageGL.UV_RECT; // calculate vertices - if (useCache) { + if (useCache === false) { + subL = 0; subT = 0; + subR = image.width+subL; subB = image.height+subT; + } else { src = item.bitmapCache; subL = src.x+(src._filterOffX/src.scale); subT = src.y+(src._filterOffY/src.scale); subR = (src._drawWidth/src.scale)+subL; subB = (src._drawHeight/src.scale)+subT; - } else { - subL = 0; subT = 0; - subR = image.width+subL; subB = image.height+subT; } } - } else if (item._webGLRenderStyle === 1) { // SPRITE + + // SPRITE + } else if (item._webGLRenderStyle === 1) { var rect = frame.rect; // calculate uvs @@ -2348,8 +2684,8 @@ this.createjs = this.createjs||{}; } // These must be calculated here else a forced draw might happen after they're set - var offV1 = this.batchCardCount*StageGL.INDICIES_PER_CARD; // offset for 1 component vectors - var offV2 = offV1*2; // offset for 2 component vectors + var offV1 = this._batchVertexCount; // offset for 1 component vectors + var offV2 = offV1*2; // offset for 2 component vectors //DHG: See Matrix2D.transformPoint for why this math specifically // apply vertices @@ -2374,21 +2710,67 @@ this.createjs = this.createjs||{}; // apply alpha alphas[offV1] = alphas[offV1+1] = alphas[offV1+2] = alphas[offV1+3] = alphas[offV1+4] = alphas[offV1+5] = item.alpha * concatAlpha; - this.batchCardCount++; + this._batchVertexCount += StageGL.INDICIES_PER_CARD; + + if (this._immediateRender) { + this._immediateBatchRender(); + } + } + + if (this._renderMode !== previousRenderMode) { + this._updateRenderMode(previousRenderMode); } }; + /** + * The shader or effect needs to be drawn immediately, sub function of `_appendToBatch` + * @method _immediateBatchRender + * @protected + */ + p._immediateBatchRender = function() { + var gl = this._webGLContext; + + if (this._batchTextureConcat === null){ + this._batchTextureConcat = this.getRenderBufferTexture(this._viewportWidth, this._viewportHeight); + } else { + this.resizeTexture(this._batchTextureConcat, this._viewportWidth, this._viewportHeight); + gl.bindFramebuffer(gl.FRAMEBUFFER, this._batchTextureConcat._frameBuffer); + gl.clear(gl.COLOR_BUFFER_BIT); + } + if (this._batchTextureTemp === null){ + this._batchTextureTemp = this.getRenderBufferTexture(this._viewportWidth, this._viewportHeight); + gl.bindFramebuffer(gl.FRAMEBUFFER, this._batchTextureTemp._frameBuffer); + } else { + this.resizeTexture(this._batchTextureTemp, this._viewportWidth, this._viewportHeight); + gl.bindFramebuffer(gl.FRAMEBUFFER, this._batchTextureTemp._frameBuffer); + gl.clear(gl.COLOR_BUFFER_BIT); + } + + var swap = this._batchTextureOutput; + this._batchTextureOutput = this._batchTextureConcat; + this._batchTextureConcat = swap; + + this._activeShader = this._mainShader; + this.batchReason = "immediatePrep"; + this._renderBatch();//<----------------------------------------------------------------------------------------- + + this.batchReason = "immediateResults"; + this._drawCover(this._batchTextureOutput._frameBuffer, this._batchTextureConcat, this._batchTextureTemp); + + gl.bindFramebuffer(gl.FRAMEBUFFER, this._batchTextureOutput._frameBuffer); + }; + /** * Draws all the currently defined cards in the buffer to the render surface. - * @method _drawBuffers - * @param {WebGLRenderingContext} gl The canvas WebGL context object to draw into. + * @method _renderBatch * @protected */ - p._drawBuffers = function (gl) { - if (this.batchCardCount <= 0) { return; } // prevents error logs on stages filled with un-renederable content. + p._renderBatch = function () { + if (this._batchVertexCount <= 0) { return; } // prevents error logs on stages filled with un-renederable content. + var gl = this._webGLContext; if (this.vocalDebug) { - console.log("Draw["+ this._drawID +":"+ this._batchID +"] : "+ this.batchReason); + console.log("Batch["+ this._drawID +":"+ this._batchID +"] : "+ this.batchReason); } var shaderProgram = this._activeShader; var vertexPositionBuffer = this._vertexPositionBuffer; @@ -2423,31 +2805,27 @@ this.createjs = this.createjs||{}; this.setTextureParams(gl, texture.isPOT); } - gl.drawArrays(gl.TRIANGLES, 0, this.batchCardCount*StageGL.INDICIES_PER_CARD); + gl.drawArrays(gl.TRIANGLES, 0, this._batchVertexCount); + + this._batchVertexCount = 0; this._batchID++; }; /** - * Draws a card that covers the entire render surface. Mainly used for filters. - * @method _drawBuffers - * @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 the `y` space sometimes requires flipping in the render. + * Draws a card that covers the entire render surface. Mainly used for filters and composite operations. + * @method _renderCover * @protected */ - p._drawCover = function (gl, flipY) { - if (this._isDrawing > 0) { - this._drawBuffers(gl); - } + p._renderCover = function () { + var gl = this._webGLContext; if (this.vocalDebug) { - console.log("Draw["+ this._drawID +":"+ this._batchID +"] : "+ "Cover"); + console.log("Cover["+ this._drawID +":"+ this._batchID +"] : "+ this.batchReason); } 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); @@ -2455,12 +2833,12 @@ this.createjs = this.createjs||{}; gl.bufferSubData(gl.ARRAY_BUFFER, 0, StageGL.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?StageGL.COVER_UV_FLIP:StageGL.COVER_UV); + gl.bufferSubData(gl.ARRAY_BUFFER, 0, StageGL.COVER_UV); gl.uniform1i(shaderProgram.samplerUniform, 0); - gl.uniform1f(shaderProgram.uprightUniform, flipY?0:1); gl.drawArrays(gl.TRIANGLES, 0, StageGL.INDICIES_PER_CARD); + this._batchID++; // while this isn't a batch, this fixes issues with expected textures in expected places }; createjs.StageGL = createjs.promote(StageGL, "Stage"); diff --git a/src/easeljs/filters/AberrationFilter.js b/src/easeljs/filters/AberrationFilter.js new file mode 100644 index 000000000..9907d64a4 --- /dev/null +++ b/src/easeljs/filters/AberrationFilter.js @@ -0,0 +1,179 @@ +/* + * AberrationFilter + * 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 + */ + +(function() { + "use strict"; + + +// constructor: + /** + * Separates and pushes each of the colour channels apart. I.E. shift the red channel slightly left. + * Allows specifying the direction and the ammount it affects each channel. Great for computer glitches and VCR like + * effects. + * + * See {{#crossLink "Filter"}}{{/crossLink}} for an more information on applying filters. + * @class AberrationFilter + * @extends Filter + * @constructor + * @param {Number} [xDir=0] Movement in x at a multiplier of 1, specified in pixels. + * @param {Number} [yDir=0] Movement in y at a multiplier of 1, specified in pixels. + * @param {Number} [redMultiplier=0] Multiplier for the movement of the Red channel. Negative values allowed. + * @param {Number} [greenMultiplier=0] Multiplier for the movement of the Green channel. Negative values allowed. + * @param {Number} [blueMultiplier=0] Multiplier for the movement of the Blue channel. Negative values allowed. + * @param {Number} [originalMix=0] Amount of original image to keep, 0-1. + * @param {Boolean} [alphaMax=false] Calculate combined alpha using maximum alpha available. Creates a stronger image. + **/ + function AberrationFilter(xDir, yDir, redMultiplier, greenMultiplier, blueMultiplier, originalMix, alphaMax) { + this.Filter_constructor(); + + // public properties: + this.xDir = Number(xDir) || 0; + this.yDir = Number(yDir) || 0; + + this.redMultiplier = Number(redMultiplier) || 0; + this.greenMultiplier = Number(greenMultiplier) || 0; + this.blueMultiplier = Number(blueMultiplier) || 0; + + this.originalMix = Math.min(Math.max(originalMix, 0), 1) || 0; + this._alphaMax = Boolean(alphaMax); + + this.FRAG_SHADER_BODY = ( + "uniform vec2 uColorDirection;" + + "uniform vec3 uColorMultiplier;" + + "uniform vec2 uExtraProps;" + + + "void main(void) {" + + "vec4 sample = texture2D(" + + "uSampler, " + + "vTextureCoord" + + ");" + + "vec4 rSample = texture2D(" + + "uSampler, " + + "vTextureCoord + (uColorDirection * uColorMultiplier.r)" + + ");" + + "vec4 gSample = texture2D(" + + "uSampler, " + + "vTextureCoord + (uColorDirection * uColorMultiplier.g)" + + ");" + + "vec4 bSample = texture2D(" + + "uSampler, " + + "vTextureCoord + (uColorDirection * uColorMultiplier.b)" + + ");" + + + "vec4 result = vec4(" + + "rSample.r*rSample.a, " + + "gSample.g*gSample.a, " + + "bSample.b*bSample.a, " + + (alphaMax ? + "max(rSample.a, max(gSample.a, max(bSample.a, sample.a)))" : + "(rSample.a + gSample.a + bSample.a) / 3.0" + ) + + ");" + + "gl_FragColor = mix(result, sample, uExtraProps[0]*sample.a);" + + "}" + ); + + } + var p = createjs.extend(AberrationFilter, createjs.Filter); + + +// public methods: + p.shaderParamSetup = function(gl, stage, shaderProgram) { + + gl.uniform2f( + gl.getUniformLocation(shaderProgram, "uColorDirection"), + this.xDir*(1/stage._viewportWidth), this.yDir*(1/stage._viewportHeight) + ); + + gl.uniform3f( + gl.getUniformLocation(shaderProgram, "uColorMultiplier"), + -this.redMultiplier, + -this.greenMultiplier, + -this.blueMultiplier + ); + + gl.uniform2f( + gl.getUniformLocation(shaderProgram, "uExtraProps"), + this.originalMix, 0 + ); + }; + +// private methods: + p._applyFilter = function(imageData) { + var refPixels = imageData.data.slice(); + var outPixels = imageData.data; + var width = imageData.width; + var height = imageData.height; + var offset, pixel; + + for (var i=0; i= width) { redX = width-1; } + if (redY < 0) { redY = 0; } + if (redY >= height) { redY = height-1; } + + if (grnX < 0) { grnX = 0; } + if (grnX >= width) { grnX = width-1; } + if (grnY < 0) { grnY = 0; } + if (grnY >= height) { grnY = height-1; } + + if (bluX < 0) { bluX = 0; } + if (bluX >= width) { bluX = width-1; } + if (bluY < 0) { bluY = 0; } + if (bluY >= height) { bluY = height-1; } + + var redPixel = ((redY*width)+redX)*4; + var grnPixel = ((grnY*width)+grnX)*4; + var bluPixel = ((bluY*width)+bluX)*4; + + outPixels[pixel] = refPixels[redPixel]; + outPixels[pixel+1] = refPixels[grnPixel+1]; + outPixels[pixel+2] = refPixels[bluPixel+2]; + outPixels[pixel+3] = this._alphaMax ? + Math.max(refPixels[redPixel+3], refPixels[grnPixel+3], refPixels[bluPixel+3]) : + (refPixels[redPixel+3] + refPixels[grnPixel+3] + refPixels[bluPixel+3]) / 3; + } + } + + return true; + }; + + createjs.AberrationFilter = createjs.promote(AberrationFilter, "Filter"); +}()); diff --git a/src/easeljs/filters/AlphaMapFilter.js b/src/easeljs/filters/AlphaMapFilter.js index ee64231e8..70ccc285b 100644 --- a/src/easeljs/filters/AlphaMapFilter.js +++ b/src/easeljs/filters/AlphaMapFilter.js @@ -64,12 +64,16 @@ this.createjs = this.createjs || {}; * @class AlphaMapFilter * @extends Filter * @constructor - * @param {HTMLImageElement|HTMLCanvasElement} alphaMap The greyscale image (or canvas) to use as the alpha value for the + * @param {HTMLImageElement|HTMLCanvasElement|WebGLTexture} alphaMap The greyscale image (or canvas) to use as the alpha value for the * result. This should be exactly the same dimensions as the target. **/ function AlphaMapFilter(alphaMap) { this.Filter_constructor(); - + + if (!createjs.Filter.isValidImageSource(alphaMap)) { + throw "Must provide valid image source for alpha map, see Filter.isValidImageSource"; + } + // public properties: /** * The greyscale image (or canvas) to use as the alpha value for the result. This should be exactly the same @@ -79,35 +83,45 @@ this.createjs = this.createjs || {}; **/ this.alphaMap = alphaMap; - // private properties: /** - * @property _alphaMap + * @property _map * @protected * @type HTMLImageElement|HTMLCanvasElement **/ - this._alphaMap = null; + this._map = null; /** - * @property _mapData + * @property _mapCtx * @protected - * @type Uint8ClampedArray + * @type CanvasRenderingContext2D **/ - this._mapData = null; + this._mapCtx = null; + + /** + * @property _mapTexture + * @protected + * @type WebGLTexture + */ this._mapTexture = null; this.FRAG_SHADER_BODY = ( "uniform sampler2D uAlphaSampler;"+ "void main(void) {" + - "vec4 color = texture2D(uSampler, vRenderCoord);" + + "vec4 color = texture2D(uSampler, vTextureCoord);" + "vec4 alphaMap = texture2D(uAlphaSampler, vTextureCoord);" + // some image formats can have transparent white rgba(1,1,1, 0) when put on the GPU, this means we need a slight tweak // using ceil ensure that the colour will be used so long as it exists but pure transparency will be treated black - "gl_FragColor = vec4(color.rgb, color.a * (alphaMap.r * ceil(alphaMap.a)));" + + "float newAlpha = alphaMap.r * ceil(alphaMap.a);" + + "gl_FragColor = vec4(clamp(color.rgb/color.a, 0.0, 1.0) * newAlpha, newAlpha);" + "}" ); + + if(alphaMap instanceof WebGLTexture) { + this._mapTexture = alphaMap; + } } var p = createjs.extend(AlphaMapFilter, createjs.Filter); @@ -116,12 +130,14 @@ this.createjs = this.createjs || {}; /** docced in super class **/ p.shaderParamSetup = function(gl, stage, shaderProgram) { - if(!this._mapTexture) { this._mapTexture = gl.createTexture(); } + if(this._mapTexture === null) { this._mapTexture = gl.createTexture(); } gl.activeTexture(gl.TEXTURE1); gl.bindTexture(gl.TEXTURE_2D, this._mapTexture); stage.setTextureParams(gl); - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, this.alphaMap); + if (this.alphaMap !== this._mapTexture) { + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, this.alphaMap); + } gl.uniform1i( gl.getUniformLocation(shaderProgram, "uAlphaSampler"), @@ -133,8 +149,6 @@ this.createjs = this.createjs || {}; /** docced in super class **/ p.clone = function () { var o = new AlphaMapFilter(this.alphaMap); - o._alphaMap = this._alphaMap; - o._mapData = this._mapData; return o; }; @@ -146,15 +160,43 @@ this.createjs = this.createjs || {}; // private methods: /** docced in super class **/ - p._applyFilter = function (imageData) { - if (!this.alphaMap) { return true; } + p._applyFilter = function(imageData) { if (!this._prepAlphaMap()) { return false; } - - // TODO: update to support scenarios where the target has different dimensions. - var data = imageData.data; - var map = this._mapData; - for(var i=0, l=data.length; i= 1) { + if (this._bufferTextureConcat === null) { + this._bufferTextureConcat = this._stageGL.getRenderBufferTexture(this._drawWidth, this._drawHeight); + } else { + this._stageGL.resizeTexture(this._bufferTextureConcat, this._drawWidth, this._drawHeight); + } + } + } + this._filterOffX = filterBounds.x; this._filterOffY = filterBounds.y; this.offX = this.x*this.scale + this._filterOffX; @@ -355,24 +486,21 @@ this.createjs = this.createjs||{}; * @method release **/ p.release = function() { - if (this._webGLCache) { - // if it isn't cache controlled clean up after yourself - if (!this._webGLCache.isCacheControlled) { - if (this.__lastRT){ this.__lastRT = undefined; } - if (this.__rtA){ this._webGLCache._killTextureObject(this.__rtA); } - if (this.__rtB){ this._webGLCache._killTextureObject(this.__rtB); } - if (this.target && this.target.cacheCanvas){ this._webGLCache._killTextureObject(this.target.cacheCanvas); } - } + if (this._stageGL) { + if (this._bufferTextureOutput !== null){ this._stageGL._killTextureObject(this._bufferTextureOutput); } + if (this._bufferTextureConcat !== null){ this._stageGL._killTextureObject(this._bufferTextureConcat); } + if (this._bufferTextureTemp !== null){ this._stageGL._killTextureObject(this._bufferTextureTemp); } // set the context to none and let the garbage collector get the rest when the canvas itself gets removed - this._webGLCache = false; + this._stageGL = false; } else { var stage = this.target.stage; - if (stage instanceof createjs.StageGL){ - stage.releaseTexture(this.target.cacheCanvas); + if (stage instanceof createjs.StageGL) { + stage.releaseTexture(this._cacheCanvas); } } - this.target = this.target.cacheCanvas = null; + this.disabled = true; + this.target = this._cacheCanvas = null; this.cacheID = this._cacheDataURLID = this._cacheDataURL = undefined; this.width = this.height = this.x = this.y = this.offX = this.offY = 0; this.scale = 1; @@ -386,11 +514,11 @@ this.createjs = this.createjs||{}; * @return {String} The image data url for the cache. **/ p.getCacheDataURL = function() { - var cacheCanvas = this.target && this.target.cacheCanvas; + var cacheCanvas = this.target && this._cacheCanvas; if (!cacheCanvas) { return null; } - if (this.cacheID != this._cacheDataURLID) { + if (this.cacheID !== this._cacheDataURLID) { this._cacheDataURLID = this.cacheID; - this._cacheDataURL = cacheCanvas.toDataURL?cacheCanvas.toDataURL():null; // incase function is + this._cacheDataURL = cacheCanvas.toDataURL ? cacheCanvas.toDataURL() : null; } return this._cacheDataURL; }; @@ -402,8 +530,8 @@ this.createjs = this.createjs||{}; * @return {Boolean} Whether the draw was handled successfully. **/ p.draw = function(ctx) { - if(!this.target) { return false; } - ctx.drawImage(this.target.cacheCanvas, + if (!this.target) { return false; } + ctx.drawImage(this._cacheCanvas, this.x + (this._filterOffX/this.scale), this.y + (this._filterOffY/this.scale), this._drawWidth/this.scale, this._drawHeight/this.scale ); @@ -423,6 +551,20 @@ this.createjs = this.createjs||{}; ); }; + /** + * Fetch the correct filter in order, complicated by multipass filtering. + * @param {Number} lookup The filter in the list to return + */ + p._getGLFilter = function(lookup) { + if (this.target.filters === null || lookup < 0){ return undefined; } + var i = 0; + var result = this.target.filters[i]; + while (result && --lookup >= 0) { + result = result._multiPass ? result._multiPass : this.target.filters[++i]; + } + return result; + }; + // private methods: /** * Create or resize the invisible canvas/surface that is needed for the display object(s) to draw to, @@ -435,11 +577,12 @@ this.createjs = this.createjs||{}; var surface; if (!this._options || !this._options.useGL) { - surface = this.target.cacheCanvas; + surface = this._cacheCanvas; // create it if it's missing - if(!surface) { - surface = this.target.cacheCanvas = createjs.createCanvas?createjs.createCanvas():document.createElement("canvas"); + if (!surface) { + surface = this._cacheCanvas = createjs.createCanvas?createjs.createCanvas():document.createElement("canvas"); + this.disabled = this._disabled; } // now size it @@ -449,51 +592,43 @@ this.createjs = this.createjs||{}; } // create it if it's missing - if (!this._webGLCache) { + if (!this._stageGL) { if (this._options.useGL === "stage") { - if(!(this.target.stage && this.target.stage.isWebGL)){ + var targetStage = this.target.stage; + // use the stage that this object belongs on as the WebGL context + if (!(targetStage && targetStage.isWebGL)){ var error = "Cannot use 'stage' for cache because the object's parent stage is "; - error += this.target.stage ? "non WebGL." : "not set, please addChild to the correct stage."; + error += targetStage ? "non WebGL." : "not set, please addChild to the correct stage."; throw error; } - this.target.cacheCanvas = true; // will be replaced with RenderTexture, temporary positive value for old "isCached" checks - this._webGLCache = this.target.stage; + this._stageGL = targetStage; - } else if(this._options.useGL === "new") { - this.target.cacheCanvas = document.createElement("canvas"); // we can turn off autopurge because we wont be making textures here - this._webGLCache = new createjs.StageGL(this.target.cacheCanvas, {antialias: true, transparent: true, autoPurge: -1}); - this._webGLCache.isCacheControlled = true; // use this flag to control stage sizing and final output + } else if (this._options.useGL === "new") { + // create a new WebGL context to run this cache + this._cacheCanvas = document.createElement("canvas"); // low autopurge in case of filter swapping and low texture count + this._stageGL = new createjs.StageGL(this._cacheCanvas, {antialias: true, transparent: true, autoPurge: 10}); + if (!this._stageGL._webGLContext){ throw "GL Cache asked for but unavailable"; } + this._stageGL.isCacheControlled = true; // use this flag to control stage sizing and final output - } else if(this._options.useGL instanceof createjs.StageGL) { - this.target.cacheCanvas = true; // will be replaced with RenderTexture, temporary positive value for old "isCached" checks - this._webGLCache = this._options.useGL; - this._webGLCache.isCacheControlled = true; // use this flag to control stage sizing and final output + } else if (this._options.useGL instanceof createjs.StageGL) { + // use the provided WebGL context to run this cache, trust the user it works and is configured. + this._stageGL = this._options.useGL; } else { throw "Invalid option provided to useGL, expected ['stage', 'new', StageGL, undefined], got "+ this._options.useGL; } } - // now size render surfaces - surface = this.target.cacheCanvas; - var stageGL = this._webGLCache; + this.disabled = this._disabled; - // if we have a dedicated stage we've gotta size it + // if we have a dedicated stage we've got to size it + var stageGL = this._stageGL; if (stageGL.isCacheControlled) { + surface = this._cacheCanvas; surface.width = this._drawWidth; surface.height = this._drawHeight; stageGL.updateViewport(this._drawWidth, this._drawHeight); } - if (this.target.filters) { - // with filters we can't tell how many we'll need but the most we'll ever need is two, so make them now - stageGL.getTargetRenderTexture(this.target, this._drawWidth,this._drawHeight); - stageGL.getTargetRenderTexture(this.target, this._drawWidth,this._drawHeight); - } else { - // without filters then we only need one RenderTexture, and that's only if its not a dedicated stage - if (!stageGL.isCacheControlled) { - stageGL.getTargetRenderTexture(this.target, this._drawWidth,this._drawHeight); - } - } }; /** @@ -502,18 +637,12 @@ this.createjs = this.createjs||{}; * @protected **/ p._drawToCache = function(compositeOperation) { - var surface = this.target.cacheCanvas; + var surface = this._cacheCanvas; var target = this.target; - var webGL = this._webGLCache; - - if (webGL){ - webGL.cacheDraw(target, target.filters, this); - - // we may of swapped around which element the surface is, so we re-fetch it - surface = this.target.cacheCanvas; + var webGL = this._stageGL; - surface.width = this._drawWidth; - surface.height = this._drawHeight; + if (webGL) { + webGL.cacheDraw(target, this); } else { var ctx = surface.getContext("2d"); @@ -528,7 +657,6 @@ this.createjs = this.createjs||{}; target.draw(ctx, true); ctx.restore(); - if (target.filters && target.filters.length) { this._applyFilters(ctx); } @@ -551,14 +679,14 @@ this.createjs = this.createjs||{}; var i = 0, filter = filters[i]; do { // this is safe because we wouldn't be in apply filters without a filter count of at least 1 - if(filter.usesContext){ - if(data) { + if (filter.usesContext){ + if (data) { ctx.putImageData(data, 0,0); data = null; } filter.applyFilter(ctx, 0,0, w,h); } else { - if(!data) { + if (!data) { data = ctx.getImageData(0,0, w,h); } filter._applyFilter(data); @@ -569,7 +697,7 @@ this.createjs = this.createjs||{}; } while (filter); //done - if(data) { + if (data) { ctx.putImageData(data, 0,0); } }; diff --git a/src/easeljs/filters/BlurFilter.js b/src/easeljs/filters/BlurFilter.js index cc888925f..20bad1404 100644 --- a/src/easeljs/filters/BlurFilter.js +++ b/src/easeljs/filters/BlurFilter.js @@ -115,7 +115,7 @@ this.createjs = this.createjs||{}; "for(int i=0; i<{{blurX}}; i++) {" + "for(int j=0; j<{{blurY}}; j++) {" + - "sampleOffset = vRenderCoord + (textureOffset * vec2(float(i)-xAdj, float(j)-yAdj));" + + "sampleOffset = vTextureCoord + (textureOffset * vec2(float(i)-xAdj, float(j)-yAdj));" + "color += texture2D(uSampler, sampleOffset) * (xWeight[i] * yWeight[j]);" + "}" + "}" + @@ -163,7 +163,7 @@ this.createjs = this.createjs||{}; } return this._compiledShader; }; - p._setShader = function() { this._compiledShader; }; + p._setShader = function(value) { this._compiledShader = value; }; try { Object.defineProperties(p, { diff --git a/src/easeljs/filters/ColorFilter.js b/src/easeljs/filters/ColorFilter.js index 4fcfa491c..20a7f77ff 100644 --- a/src/easeljs/filters/ColorFilter.js +++ b/src/easeljs/filters/ColorFilter.js @@ -135,7 +135,7 @@ this.createjs = this.createjs||{}; "uniform vec4 uColorOffset;" + "void main(void) {" + - "vec4 color = texture2D(uSampler, vRenderCoord);" + + "vec4 color = texture2D(uSampler, vTextureCoord);" + "gl_FragColor = (color * uColorMultiplier) + uColorOffset;" + "}" diff --git a/src/easeljs/filters/ColorMatrix.js b/src/easeljs/filters/ColorMatrix.js index b6279172e..053325c52 100644 --- a/src/easeljs/filters/ColorMatrix.js +++ b/src/easeljs/filters/ColorMatrix.js @@ -107,6 +107,52 @@ this.createjs = this.createjs||{}; ColorMatrix.LENGTH = ColorMatrix.IDENTITY_MATRIX.length; +// static methods: + /** + * Create an instance of ColorMatrix using the Sepia preset + * @returns {ColorMatrix} + */ + ColorMatrix.createSepiaPreset = function() { + return (new ColorMatrix()).copy([ + 0.4977, 0.9828, 0.1322, 0.0000, 14, + 0.4977, 0.9828, 0.1322, 0.0000, -14, + 0.4977, 0.9828, 0.1322, 0.0000, -47, + 0.0000, 0.0000, 0.0000, 1.0000, 0, + 0, 0, 0, 0, 1 + ]); + }; + + /** + * Create an instance of ColorMatrix using an invert color preset + * @returns {ColorMatrix} + */ + ColorMatrix.createInvertPreset = function() { + return (new ColorMatrix()).copy([ + -1.0000, 0.0000, 0.0000, 0.0000, 255, + 0.0000, -1.0000, 0.0000, 0.0000, 255, + 0.0000, 0.0000, -1.0000, 0.0000, 255, + 0.0000, 0.0000, 0.0000, 1.0000, 0, + 0, 0, 0, 0, 1 + ]); + }; + + /** + * Create an instance of ColorMatrix using the Greyscale preset. + * Note: -100 saturation accounts for perceived brightness, the greyscale preset treats all channels equally. + * @returns {ColorMatrix} + */ + ColorMatrix.createGreyscalePreset = function() { + return (new ColorMatrix()).copy([ + 0.3333, 0.3334, 0.3333, 0.0000, 0, + 0.3333, 0.3334, 0.3333, 0.0000, 0, + 0.3333, 0.3334, 0.3333, 0.0000, 0, + 0.0000, 0.0000, 0.0000, 1.0000, 0, + 0, 0, 0, 0, 1 + ]); + }; + + //TODO: some "fun" filters + // public methods: /** * Resets the instance with the specified values. @@ -172,6 +218,24 @@ this.createjs = this.createjs||{}; return this; }; + /** + * Adjusts the colour offset of pixel color by adding the specified value to the red, green and blue channels. + * Positive values will make the image brighter, negative values will make it darker. + * @method adjustBrightness + * @param {Number} r A value between -255 & 255 that will be added to the Red channel. + * @param {Number} g A value between -255 & 255 that will be added to the Green channel. + * @param {Number} b A value between -255 & 255 that will be added to the Blue channel. + * @return {ColorMatrix} The ColorMatrix instance the method is called on (useful for chaining calls.) + * @chainable + **/ + p.adjustOffset = function(r,g,b) { + if (isNaN(r) || isNaN(g) || isNaN(b)) { return this; } + this[4] = this._cleanValue(this[4] + r,255); + this[9] = this._cleanValue(this[9] + g,255); + this[14] = this._cleanValue(this[14] + b,255); + return this; + }; + /** * Adjusts the contrast of pixel color. * Positive values will increase contrast, negative values will decrease contrast. @@ -313,7 +377,13 @@ this.createjs = this.createjs||{}; * @return {String} a string representation of the instance. **/ p.toString = function() { - return "[ColorMatrix]"; + var sz = ""; + sz += " "+ this[0].toFixed(4)+", "+this[1].toFixed(4)+", "+this[2].toFixed(4)+", "+this[3].toFixed(4)+", "+(this[4]|0)+",\n"; + sz += " "+ this[5].toFixed(4)+", "+this[6].toFixed(4)+", "+this[7].toFixed(4)+", "+this[8].toFixed(4)+", "+(this[9]|0)+",\n"; + sz += " "+ this[10].toFixed(4)+", "+this[11].toFixed(4)+", "+this[12].toFixed(4)+", "+this[13].toFixed(4)+", "+(this[14]|0)+",\n"; + sz += " "+ this[15].toFixed(4)+", "+this[16].toFixed(4)+", "+this[17].toFixed(4)+", "+this[18].toFixed(4)+", "+(this[19]|0)+",\n"; + sz += " "+ (this[20]|0)+", "+(this[21]|0)+", "+(this[22]|0)+", "+(this[23]|0)+", "+(this[24]|0)+"\n"; + return "[ColorMatrix] {\n"+ sz +"}"; }; @@ -367,6 +437,5 @@ this.createjs = this.createjs||{}; return matrix; }; - createjs.ColorMatrix = ColorMatrix; }()); diff --git a/src/easeljs/filters/ColorMatrixFilter.js b/src/easeljs/filters/ColorMatrixFilter.js index 6d189f58f..d88ce3cbd 100644 --- a/src/easeljs/filters/ColorMatrixFilter.js +++ b/src/easeljs/filters/ColorMatrixFilter.js @@ -56,6 +56,18 @@ this.createjs = this.createjs||{}; * * shape.cache(-50, -50, 100, 100); * + *

Example

+ * This example uses a preset to generate a sepia photograph effect + * + * var shape = new createjs.Shape().set({x:100,y:100}); + * shape.graphics.beginFill("#ff0000").drawCircle(0,0,50); + * + * shape.filters = [ + * new createjs.ColorMatrixFilter( createjs.ColorMatrix.createSepiaPreset() ) + * ]; + * + * shape.cache(-50, -50, 100, 100); + * * See {{#crossLink "Filter"}}{{/crossLink}} for an more information on applying filters. * @class ColorMatrixFilter * @constructor @@ -79,14 +91,15 @@ this.createjs = this.createjs||{}; "uniform vec4 uColorMatrixOffset;" + "void main(void) {" + - "vec4 color = texture2D(uSampler, vRenderCoord);" + + "vec4 color = texture2D(uSampler, vTextureCoord);" + "mat4 m = uColorMatrix;" + - "vec4 newColor = vec4(0,0,0,0);" + - "newColor.r = color.r*m[0][0] + color.g*m[0][1] + color.b*m[0][2] + color.a*m[0][3];" + - "newColor.g = color.r*m[1][0] + color.g*m[1][1] + color.b*m[1][2] + color.a*m[1][3];" + - "newColor.b = color.r*m[2][0] + color.g*m[2][1] + color.b*m[2][2] + color.a*m[2][3];" + - "newColor.a = color.r*m[3][0] + color.g*m[3][1] + color.b*m[3][2] + color.a*m[3][3];" + + "vec4 newColor = vec4(" + + "color.r*m[0][0] + color.g*m[0][1] + color.b*m[0][2] + color.a*m[0][3]," + + "color.r*m[1][0] + color.g*m[1][1] + color.b*m[1][2] + color.a*m[1][3]," + + "color.r*m[2][0] + color.g*m[2][1] + color.b*m[2][2] + color.a*m[2][3]," + + "color.r*m[3][0] + color.g*m[3][1] + color.b*m[3][2] + color.a*m[3][3]" + + ");" + "gl_FragColor = newColor + uColorMatrixOffset;" + "}" diff --git a/src/easeljs/filters/DisplacementFilter.js b/src/easeljs/filters/DisplacementFilter.js new file mode 100644 index 000000000..e07f057bb --- /dev/null +++ b/src/easeljs/filters/DisplacementFilter.js @@ -0,0 +1,211 @@ +/* + * DisplacementFilter + * 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 + */ + +// namespace: +this.createjs = this.createjs||{}; + +(function() { + "use strict"; + + +// constructor: + /** + * Distorts portions of the image, creates effects like computer glitches, bulge/pinch, and pond ripples. This filter + * Uses a reference image/canvas and interprets its r/g channels as a displacement map. A displacement map species + * how far from the original pixel the output pixel should be taken from. + * + * Painting a displacement map means understanding how the r/g channel are interpreted. The red channel changes + * how far in the x to sample, and the green changes how far in the y to sample. The maximum range is -distance to + * +distance ('distance' being the distance value on the filter instance). This maps to the 0-255 color space for + * each channel. This means that for an image to experience no change it should be painted #808000 as that is in the + * middle for both the x and the y displacements. + *
    + *
  • Red values smaller than 0x80 sample from further left, and larger than 0x80 sample form further right.
  • + *
  • Green values smaller than 0x80 sample from higher up, and larger than 0x80 sample form lower down.
  • + *
+ * The output vs input can take some getting used to so experiment! As a tip: when painting to a canvas that + * will act as displacement map, consider using the blend mode of "overlay" or "softlight". These blend modes + * will preserve mid tones (no displacement) while still mixing the high and low values as you might need per channel. + * + * See {{#crossLink "Filter"}}{{/crossLink}} for an more information on applying filters. + * @class DisplacementFilter + * @extends Filter + * @constructor + * @param {HTMLImageElement|HTMLCanvasElement|WebGLTexture} dudvMap The horizontal blur radius in pixels. + * @param {Number} [distance=0] The absolute value of the maximum possible displacement from the original pixel. + **/ + function DisplacementFilter(dudvMap, distance) { + this.Filter_constructor(); + + if (!createjs.Filter.isValidImageSource(dudvMap)) { + throw "Must provide valid image source for displacement map, see Filter.isValidImageSource"; + } + + // public properties: + /** + * The visual source to fetch the displacement map from. + * @property dudvMap + * @type {Image|HTMLCanvasElement} + **/ + this.dudvMap = dudvMap; + + /** + * The absolute value of the maximum shift in x/y possible. + * @property distance + * @default 128 + * @type {Image|HTMLCanvasElement} + **/ + this.distance = Number(distance); + if(isNaN(this.distance)) { this.distance = 128; } + + /** + * This is a template to generate the shader for {{#crossLink FRAG_SHADER_BODY}}{{/crossLink}} + */ + this.FRAG_SHADER_BODY = ( + "uniform sampler2D uDudvSampler;"+ + "uniform float fPower;" + + "uniform vec2 pixelAdjustment;" + + + "void main(void) {" + + "vec4 dudvValue = texture2D(uDudvSampler, vTextureCoord);" + + "vec2 sampleOffset = mix(vec2(0.0), dudvValue.rg-0.5, dudvValue.a) * (fPower*pixelAdjustment);" + + "gl_FragColor = texture2D(uSampler, vTextureCoord + sampleOffset);" + + "}" + ); + + if(dudvMap instanceof WebGLTexture) { + this._mapTexture = dudvMap; + } else if (dudvMap instanceof HTMLCanvasElement) { + this._dudvCanvas = dudvMap; + this._dudvCtx = dudvMap.getContext("2d"); + } else { + var canvas = this._dudvCanvas = createjs.createCanvas ? createjs.createCanvas() : document.createElement("canvas"); + canvas.width = dudvMap.width; + canvas.height = dudvMap.height; + (this._dudvCtx = canvas.getContext("2d")).drawImage(dudvMap, 0,0); + } + } + var p = createjs.extend(DisplacementFilter, createjs.Filter); + + +// public methods: + /** docced in super class **/ + p.shaderParamSetup = function(gl, stage, shaderProgram) { + if (!this._mapTexture) { this._mapTexture = gl.createTexture(); } + + gl.activeTexture(gl.TEXTURE1); + gl.bindTexture(gl.TEXTURE_2D, this._mapTexture); + stage.setTextureParams(gl); + if (this.dudvMap !== this._mapTexture) { + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, this.dudvMap); + } + + gl.uniform1i( + gl.getUniformLocation(shaderProgram, "uDudvSampler"), + 1 + ); + + gl.uniform1f( + gl.getUniformLocation(shaderProgram, "fPower"), + this.distance + ); + + gl.uniform2f( //this is correct as the color maps to -0.5,0.5. This compounds the pixel delta, thus 2/size + gl.getUniformLocation(shaderProgram, "pixelAdjustment"), + 2/stage._viewportWidth, -2/stage._viewportHeight + ); + }; + +// private methods: + /** docced in super class **/ + p._applyFilter = function(imageData) { + // as we're reaching across pixels we need an unmodified clone of the source + // slice/from/map/filter don't work correctly in IE11 and subarray creates a ref + var refArray, refArraySrc = imageData.data; + if (refArraySrc.slice !== undefined) { + refArray = refArraySrc.slice(); + } else { + refArray = new Uint8ClampedArray(refArraySrc.length); + refArray.set(refArraySrc); + } + + var outArray = imageData.data; + var width = imageData.width; + var height = imageData.height; + var rowOffset, pixelStart; + + var sampleData = this._dudvCtx.getImageData(0,0, this.dudvMap.width,this.dudvMap.height); + var sampleArray = sampleData.data; + var sampleWidth = sampleData.width; + var sampleHeight = sampleData.height; + var sampleRowOffset, samplePixelStart; + + var widthRatio = sampleWidth/width; + var heightRatio = sampleHeight/height; + var pxRange = 1/255; + + // performance optimizing lookup + var distance = this.distance*2; + + // the x and y need to stretch separately, nesting the for loops simplifies this logic even if the array is flat + for (var i=0; i width) { xDelta = width-j; } + if (i+yDelta < 0) { yDelta = -i; } + if (i+yDelta > height) { yDelta = height-i; } + + var targetPixelStart = (pixelStart + xDelta*4) + yDelta*4*width; + outArray[pixelStart] = refArray[targetPixelStart]; + outArray[pixelStart+1] = refArray[targetPixelStart+1]; + outArray[pixelStart+2] = refArray[targetPixelStart+2]; + outArray[pixelStart+3] = refArray[targetPixelStart+3]; + } + } + + return true; + }; + + createjs.DisplacementFilter = createjs.promote(DisplacementFilter, "Filter"); +}()); diff --git a/src/easeljs/filters/Filter.js b/src/easeljs/filters/Filter.js index 045993f8f..f4a3ea125 100644 --- a/src/easeljs/filters/Filter.js +++ b/src/easeljs/filters/Filter.js @@ -39,9 +39,11 @@ this.createjs = this.createjs||{}; // constructor: /** - * Base class that all filters should inherit from. Filters need to be applied to objects that have been cached using - * the {{#crossLink "DisplayObject/cache"}}{{/crossLink}} method. If an object changes, please cache it again, or use - * {{#crossLink "DisplayObject/updateCache"}}{{/crossLink}}. Note that the filters must be applied before caching. + * Base class that all filters should inherit from. Appli + * + * When on a regular Stage apply the Filters and then cache the object using the {{#crossLink "DisplayObject/cache"}}{{/crossLink}} method. + * When a cached object changes, please use {{#crossLink "DisplayObject/updateCache"}}{{/crossLink}}. + * When on a StageGL simply setting content in the `.filters` array will trigger an automatic and constantly updated cache. * *

Example

* @@ -55,13 +57,20 @@ this.createjs = this.createjs||{}; * margins that need to be applied in order to fully display the filter. For example, the {{#crossLink "BlurFilter"}}{{/crossLink}} * will cause an object to feather outwards, resulting in a margin around the shape. * + * Any filter that consumes an external image stretches the image to cover the cached bounds. If this is an undesired + * visual result, then use an intermediary cache to properly size and layout your data before passing it to a filter. + * + * *

EaselJS Filters

* EaselJS comes with a number of pre-built filters: - *
  • {{#crossLink "AlphaMapFilter"}}{{/crossLink}} : Map a greyscale image to the alpha channel of a display object
  • - *
  • {{#crossLink "AlphaMaskFilter"}}{{/crossLink}}: Map an image's alpha channel to the alpha channel of a display object
  • - *
  • {{#crossLink "BlurFilter"}}{{/crossLink}}: Apply vertical and horizontal blur to a display object
  • - *
  • {{#crossLink "ColorFilter"}}{{/crossLink}}: Color transform a display object
  • - *
  • {{#crossLink "ColorMatrixFilter"}}{{/crossLink}}: Transform an image using a {{#crossLink "ColorMatrix"}}{{/crossLink}}
  • + *
      + *
    • {{#crossLink "AberrationFilter"}}{{/crossLink}} : Shift the RGB components separately along a given vector
    • + *
    • {{#crossLink "AlphaMapFilter"}}{{/crossLink}} : Map a greyscale image to the alpha channel of a display object
    • + *
    • {{#crossLink "AlphaMaskFilter"}}{{/crossLink}}: Map an image's alpha channel to the alpha channel of a display object
    • + *
    • {{#crossLink "BlurFilter"}}{{/crossLink}}: Apply vertical and horizontal blur to a display object
    • + *
    • {{#crossLink "ColorFilter"}}{{/crossLink}}: Color transform a display object
    • + *
    • {{#crossLink "ColorMatrixFilter"}}{{/crossLink}}: Transform an image using a {{#crossLink "ColorMatrix"}}{{/crossLink}}
    • + *
    • {{#crossLink "DisplacementFilter"}}{{/crossLink}}: Create localized distortions in supplied display object
    • *
    * * @class Filter @@ -89,7 +98,6 @@ this.createjs = this.createjs||{}; * Pre-processed template shader code. It will be parsed before being fed in into the shader compiler. * This should be based upon StageGL.SHADER_VERTEX_BODY_REGULAR * @property VTX_SHADER - * @virtual * @type {String} * @readonly */ @@ -99,7 +107,6 @@ this.createjs = this.createjs||{}; * Pre-processed template shader code. It will be parsed before being fed in into the shader compiler. * This should be based upon StageGL.SHADER_FRAGMENT_BODY_REGULAR * @property FRAG_SHADER - * @virtual * @type {String} * @readonly */ @@ -107,6 +114,30 @@ this.createjs = this.createjs||{}; } var p = Filter.prototype; +// static methods: + /** + * Check to see if an image source being provided is one that is valid. + *

    Valid Sources:

    + *
      + *
    • Image Object
    • + *
    • HTML Canvas Element
    • + *
    • `.cacheCanvas` on an object with the same stage
    • + *
    + * WebGLTextures CANNOT be shared between multiple WebGL contexts. This means the only safe source for a WebGLTexture + * is an object cached using the same StageGL as the object trying to use it in a filter. This function does not + * enforce that restriction, as it is difficult or expensive to detect. The render will crash or fail to load the + * image data if the rule isn't followed. + * @param {HTMLImageElement|HTMLCanvasElement|WebGLTexture} src The element to check for validity + * @return Boolean Whether the source is valid + */ + Filter.isValidImageSource = function(src) { + return Boolean(src) && ( + src instanceof Image || + src instanceof WebGLTexture || + src instanceof HTMLCanvasElement + ); + }; + // public methods: /** * Provides padding values for this filter. That is, how much the filter will extend the visual bounds of an object it is applied to. @@ -136,23 +167,19 @@ this.createjs = this.createjs||{}; * @param {Number} y The y position to use for the source rect. * @param {Number} width The width to use for the source rect. * @param {Number} height The height to use for the source rect. - * @param {CanvasRenderingContext2D} [targetCtx] The 2D context to draw the result to. Defaults to the context passed to ctx. - * @param {Number} [targetX] The x position to draw the result to. Defaults to the value passed to x. - * @param {Number} [targetY] The y position to draw the result to. Defaults to the value passed to y. + * @param {CanvasRenderingContext2D} [targetCtx=ctx] The 2D context to draw the result to. Defaults to the context passed to ctx. * @return {Boolean} If the filter was applied successfully. **/ - p.applyFilter = function(ctx, x, y, width, height, targetCtx, targetX, targetY) { + p.applyFilter = function(ctx, x, y, width, height, targetCtx) { // this is the default behaviour because most filters access pixel data. It is overridden when not needed. targetCtx = targetCtx || ctx; - if (targetX == null) { targetX = x; } - if (targetY == null) { targetY = y; } try { var imageData = ctx.getImageData(x, y, width, height); } catch (e) { return false; } if (this._applyFilter(imageData)) { - targetCtx.putImageData(imageData, targetX, targetY); + targetCtx.putImageData(imageData, x, y); return true; } return false; diff --git a/src/easeljs/utils/WebGLInspector.js b/src/easeljs/utils/WebGLInspector.js index 60c42e85c..09ae74c2f 100644 --- a/src/easeljs/utils/WebGLInspector.js +++ b/src/easeljs/utils/WebGLInspector.js @@ -43,26 +43,10 @@ this.createjs = this.createjs||{}; * @constructor * @param {StageGL} stage The default stage to use when none is supplied. */ - function WebGLInspector(stage) { - this.EventDispatcher_constructor(); - - // public properties: - - // private properties: - /** - * The internal reference to the default stage this Inspector is for. - * @property _stage - * @protected - * @type {StageGL} - */ - this._stage = stage; - - // and begin - this._initializeWebGLInspector(); - } + function WebGLInspector(stage) {} var p = createjs.extend(WebGLInspector, createjs.EventDispatcher); -// static: +// properties: /** * Alternate output for debugging situations where "console" is not available, i.e. Mobile or remote debugging. * Expects object with a "log" function that takes any number of params. @@ -72,84 +56,101 @@ this.createjs = this.createjs||{}; * @static * @protected */ - p.alternateOutput = undefined; + WebGLInspector.alternateOutput = undefined; -// getter / setters: - -// ctor: /** - * @method _initializeWebGL - * @protected + * Default stage to assume when non provided + * @type {StageGL} + * @private */ - p._initializeWebGLInspector = function() {}; + WebGLInspector.stage = undefined; // public methods: + /** + * Utility to call the right logging + * @params * + */ + WebGLInspector.log = function() { + (WebGLInspector.alternateOutput ? WebGLInspector.alternateOutput.log : console.log).apply(this, arguments); + }; + /** * Perform all of the logging reports at once. * @method log - * @param {StageGL} [stage=this._stage] The stage to log information for. + * @param {StageGL} [stage=WebGLInspector.stage] The stage to log information for. */ - p.log = function(stage) { - if(!stage){ stage = this._stage; } + WebGLInspector.logAll = function(stage) { + if (!stage){ stage = WebGLInspector.stage; } - console.log("Batches Per Draw", (stage._batchID/stage._drawID).toFixed(4)); - this.logContextInfo(stage._webGLContext); - this.logDepth(stage.children, ""); - this.logTextureFill(stage); + WebGLInspector.log("Batches Per Draw", (stage._batchID/stage._drawID).toFixed(4)); + WebGLInspector.logContextInfo(stage._webGLContext); + WebGLInspector.logDepth(stage.children, ""); + WebGLInspector.logTextureFill(stage); }; /** - * Replace the stage's Draw command with an empty draw command. This is useful for testing performance, and ignoring - * rendering. - * @method toggleGPUDraw - * @param {StageGL} [stage=this._stage] The stage to log information for. - * @param {Boolean} enabled Force enabled. If left undefined, it will toggle. + * Replace the stage's Draw command with a new draw command. This is useful for: + *
      + *
    • Testing performance, with no render cost. See `WebGLInspector.drawEmpty`
    • + *
    • Troubleshooting and tracking loaded textures. See `WebGLInspector.drawTexOnBuffer`
    • + *
    • Misc feature or troubleshooting injection
    • + *
    + * @method replaceRenderBatchCall + * @param {StageGL} [stage=WebGLInspector.stage] The stage to log information for. + * @param {Function} newFunc . */ - p.toggleGPUDraw = function(stage, enabled) { - if(!stage){ stage = this._stage; } + WebGLInspector.replaceRenderBatchCall = function(stage, newFunc) { + if (!stage){ stage = WebGLInspector.stage; } - if(enabled === undefined) { - enabled = !!stage._drawBuffers_; + if (newFunc === undefined && stage._renderBatch_) { + stage._renderBatch = stage._renderBatch_; + stage._renderBatch_ = undefined; + } else { + if (stage._renderBatch_ === undefined) { + stage._renderBatch_ = stage._renderBatch; + } + stage._renderBatch = newFunc; } + }; - if(enabled) { - if(stage._drawBuffers_) { - stage._drawBuffers = stage._drawBuffers_; - stage._drawBuffers_ = undefined; - } + /** + * Identical to replaceRenderBatchCall, but affects the Cover command. + * @method replaceRenderCoverCall + * @param {StageGL} [stage=WebGLInspector.stage] The stage to log information for. + * @param {Function} newFunc . + */ + WebGLInspector.replaceRenderCoverCall = function(stage, newFunc) { + if (!stage){ stage = WebGLInspector.stage; } + + if (newFunc === undefined && stage._renderCover_) { + stage._renderCover = stage._renderCover_; + stage._renderCover_ = undefined; } else { - stage._drawBuffers_ = stage._drawBuffers; - stage._drawBuffers = function(gl) { - if(this.vocalDebug) { - var output = "BlankDraw["+ this._drawID +":"+ this._batchID +"] : "+ this.batchReason; - this.alternateOutput?this.alternateOutput.log(output):console.log(output); - } - }; + if (stage._renderCover_ === undefined) { + stage._renderCover_ = stage._renderCover; + } + stage._renderCover = newFunc; } }; /** * Recursively walk the entire display tree, log the attached items, and display it in a tree view. * @method logDepth - * @param {Array} [children=this._stage.children] The children array to walk through. + * @param {Array} [children=WebGLInspector.stage.children] The children array to walk through. * @param {String} prepend What to prepend to this output from this point onwards. * @param {Function} customLog Which logging function to use, mainly for filtering or formatting output. * Fallback hierarchy is customLog -> alternateOutput -> console.log. */ - p.logDepth = function(children, prepend, customLog) { - if(!children){ children = this._stage.children; } - if(!prepend){ prepend = ""; } + WebGLInspector.logDepth = function(children, prepend, customLog) { + if (!children){ children = WebGLInspector.stage.children; } + if (!prepend){ prepend = ""; } var l = children.length; - for(var i=0; i