Skip to content

Commit 2fede38

Browse files
author
David
committed
Updated StageGL image & texture association logic. Now handles multiple images with a shared texture better during the clean up routines. Less false positives during autoPurge.
1 parent f9057d2 commit 2fede38

1 file changed

Lines changed: 122 additions & 81 deletions

File tree

src/easeljs/display/StageGL.js

Lines changed: 122 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -429,7 +429,7 @@ this.createjs = this.createjs||{};
429429
* @type {Number}
430430
* @default 0
431431
*/
432-
this._lastTrackedCanvas = 0;
432+
this._lastTrackedCanvas = -1;
433433

434434
/**
435435
* Controls whether final rendering output of a {{#crossLink "cacheDraw"}}{{/crossLink}} is the canvas or a render
@@ -1100,9 +1100,10 @@ this.createjs = this.createjs||{};
11001100
* Textures in use, or to be used again shortly, should not be removed. This is simply for performance reasons.
11011101
* Removing a texture in use will cause the texture to have to be re-uploaded slowing rendering.
11021102
* @method releaseTexture
1103-
* @param {DisplayObject | Texture | Image | Canvas} item An object that used the texture to be discarded.
1103+
* @param {DisplayObject | Texture | Image | Canvas} item An object that used the texture to be discarded.
1104+
* @param {Boolean} safe Should the release attempt to be "safe" and only delete this usage.
11041105
*/
1105-
p.releaseTexture = function (item) {
1106+
p.releaseTexture = function (item, safe) {
11061107
var i, l;
11071108
if (!item) { return; }
11081109

@@ -1149,31 +1150,47 @@ this.createjs = this.createjs||{};
11491150
}
11501151

11511152
// remove it
1152-
this._killTextureObject(this._textureDictionary[foundImage._storeID]);
1153-
foundImage._storeID = undefined;
1153+
var texture = this._textureDictionary[foundImage._storeID];
1154+
if (safe) {
1155+
var data = texture._imageData;
1156+
var index = data.indexOf(foundImage);
1157+
if (index >= 0) { data.splice(index, 1); }
1158+
foundImage._storeID = undefined;
1159+
if (data.length === 0) { this._killTextureObject(texture); }
1160+
} else {
1161+
this._killTextureObject(texture);
1162+
}
11541163
};
11551164

11561165
/**
11571166
* Similar to {{#crossLink "releaseTexture"}}{{/crossLink}}, but this function differs by searching for textures to
11581167
* release. It works by assuming that it can purge any texture which was last used more than "count" draw calls ago.
1159-
* Because this process is unaware of the objects and whether they may be used on your stage, false positives can
1168+
* Because this process is unaware of the objects and whether they may be used later on your stage, false positives can
11601169
* occur. It is recommended to manually manage your memory with {{#crossLink "StageGL/releaseTexture"}}{{/crossLink}},
11611170
* however, there are many use cases where this is simpler and error-free. This process is also run by default under
11621171
* the hood to prevent leaks. To disable it see the {{#crossLink "StageGL/autoPurge:property"}}{{/crossLink}} property.
11631172
* @method purgeTextures
11641173
* @param {Number} [count=100] How many renders ago the texture was last used
11651174
*/
11661175
p.purgeTextures = function (count) {
1167-
if (count == undefined){ count = 100; }
1176+
if (!(count >= 0)){ count = 100; }
11681177

11691178
var dict = this._textureDictionary;
11701179
var l = dict.length;
1171-
for (var i= 0; i<l; i++) {
1172-
var item = dict[i];
1173-
if (!item) { continue; }
1174-
if (item._drawID + count <= this._drawID) { // use draw not batch as draw is more indicative of time
1175-
this._killTextureObject(item);
1180+
for (var i = 0; i<l; i++) {
1181+
var data, texture = dict[i];
1182+
if (!texture || !(data = texture._imageData)) { continue; }
1183+
1184+
for (var j = 0; j<data.length; j++) {
1185+
var item = data[j];
1186+
if (item._drawID + count <= this._drawID) {
1187+
item._storeID = undefined;
1188+
data.splice(j, 1);
1189+
j--;
1190+
}
11761191
}
1192+
1193+
if (!data.length) { this._killTextureObject(texture); }
11771194
}
11781195
};
11791196

@@ -1450,6 +1467,27 @@ this.createjs = this.createjs||{};
14501467
};
14511468

14521469
// private methods:
1470+
/**
1471+
* Returns a base texture that has no image or data loaded. Not intended for loading images. In some error cases,
1472+
* the texture creation will fail. This function differs from {{#crossLink "StageGL/getBaseTexture"}}{{/crossLink}}
1473+
* in that the failed textures will be replaced with a safe to render "nothing" texture.
1474+
* @method _getSafeTexture
1475+
* @param {uint} [w=1] The width of the texture in pixels, defaults to 1
1476+
* @param {uint} [h=1] The height of the texture in pixels, defaults to 1
1477+
*/
1478+
p._getSafeTexture = function (w, h) {
1479+
var texture = this.getBaseTexture(w, h);
1480+
1481+
if(!texture) {
1482+
msg = "Problem creating texture, possible cause: using too much VRAM, please try releasing texture memory";
1483+
(console.error && console.error(msg)) || console.log(msg);
1484+
1485+
texture = this._baseTextures[0];
1486+
}
1487+
1488+
return texture;
1489+
};
1490+
14531491
/**
14541492
* Sets up and returns the WebGL context for the canvas. May return undefined in error scenarios. These can include
14551493
* situations where the canvas element already has a context, 2D or GL.
@@ -1727,10 +1765,12 @@ this.createjs = this.createjs||{};
17271765

17281766
// fill in blanks as it helps the renderer be stable while textures are loading and reduces need for safety code
17291767
for (var i=0; i<this._batchTextureCount;i++) {
1730-
var tex = this.getBaseTexture();
1731-
this._baseTextures[i] = this._batchTextures[i] = tex;
1732-
if (!tex) {
1768+
var texture = this.getBaseTexture();
1769+
this._baseTextures[i] = this._batchTextures[i] = texture;
1770+
if (!texture) {
17331771
throw "Problems creating basic textures, known causes include using too much VRAM by not releasing WebGL texture instances";
1772+
} else {
1773+
texture._storeID = -1;
17341774
}
17351775
}
17361776
};
@@ -1739,56 +1779,49 @@ this.createjs = this.createjs||{};
17391779
* Load a specific texture, accounting for potential delay, as it might not be preloaded.
17401780
* @method _loadTextureImage
17411781
* @param {WebGLRenderingContext} gl
1742-
* @param {Image} image Actual image to be loaded
1782+
* @param {Image | Canvas} image Actual image to be loaded
17431783
* @return {WebGLTexture} The resulting Texture object
17441784
* @protected
17451785
*/
17461786
p._loadTextureImage = function (gl, image) {
1747-
var src = image.src;
1748-
1749-
if (!src) {
1750-
// one time canvas property setup
1751-
image._isCanvas = true;
1752-
src = image.src = "canvas_" + this._lastTrackedCanvas++;
1787+
var srcPath, texture, msg;
1788+
if (image instanceof Image && image.src) {
1789+
srcPath = image.src;
1790+
} else if (image instanceof HTMLCanvasElement) {
1791+
image._isCanvas = true; //canvases are already loaded and assumed unique so note that
1792+
srcPath = "canvas_" + (++this._lastTrackedCanvas);
1793+
} else {
1794+
msg = "Invalid image provided as source. Please ensure source is a correct DOM element.";
1795+
(console.error && console.error(msg, image)) || console.log(msg, image);
1796+
return;
17531797
}
17541798

1755-
// put the texture into our storage system
1756-
var storeID = this._textureIDs[src];
1799+
// create the texture lookup and texture
1800+
var storeID = this._textureIDs[srcPath];
17571801
if (storeID === undefined) {
1758-
storeID = this._textureIDs[src] = this._textureDictionary.length;
1759-
}
1760-
if (this._textureDictionary[storeID] === undefined) {
1761-
this._textureDictionary[storeID] = this.getBaseTexture();
1802+
this._textureIDs[srcPath] = storeID = this._textureDictionary.length;
1803+
image._storeID = storeID;
1804+
image._invalid = !image.isCanvas;
1805+
texture = this._getSafeTexture();
1806+
this._textureDictionary[storeID] = texture;
1807+
} else {
1808+
image._storeID = storeID;
1809+
texture = this._textureDictionary[storeID];
17621810
}
17631811

1764-
var texture = this._textureDictionary[storeID];
1765-
1766-
if (texture) {
1767-
// get texture params all set up
1768-
texture._batchID = this._batchID;
1812+
// allow the texture to track its references for cleanup, if it's not an error ref
1813+
if (texture._storeID != -1) {
17691814
texture._storeID = storeID;
1770-
texture._imageData = image;
1771-
this._insertTextureInBatch(gl, texture);
1772-
1773-
// get the data into the texture or wait for it to load
1774-
image._storeID = storeID;
1775-
if (image.complete || image.naturalWidth || image._isCanvas) { // is it already loaded
1776-
this._updateTextureImageData(gl, image);
1777-
} else {
1778-
image.addEventListener("load", this._updateTextureImageData.bind(this, gl, image));
1815+
if (texture._imageData) {
1816+
texture._imageData.push(image);
1817+
} else {
1818+
texture._imageData = [image];
17791819
}
1780-
} else {
1781-
// we really really should have a texture, try to recover the error by using a saved empty texture so we don't crash
1782-
var msg = "Problem creating desired texture, known causes include using too much VRAM by not releasing WebGL texture instances";
1783-
(console.error && console.error(msg)) || console.log(msg);
1784-
1785-
texture = this._baseTextures[0];
1786-
texture._batchID = this._batchID;
1787-
texture._storeID = -1;
1788-
texture._imageData = texture;
1789-
this._insertTextureInBatch(gl, texture);
17901820
}
17911821

1822+
// insert texture into batch
1823+
this._insertTextureInBatch(gl, texture);
1824+
17921825
return texture;
17931826
};
17941827

@@ -1801,6 +1834,11 @@ this.createjs = this.createjs||{};
18011834
* @protected
18021835
*/
18031836
p._updateTextureImageData = function (gl, image) {
1837+
// the image isn't loaded and isn't ready to be updated, because we don't set the invalid flag we should try again later
1838+
if (!(image.complete || image._isCanvas || image.naturalWidth)) {
1839+
return;
1840+
}
1841+
18041842
// the bitwise & is intentional, cheap exponent 2 check
18051843
var isNPOT = (image.width & image.width-1) || (image.height & image.height-1);
18061844
var texture = this._textureDictionary[image._storeID];
@@ -1831,8 +1869,8 @@ this.createjs = this.createjs||{};
18311869
texture._h = image.height;
18321870

18331871
if (this.vocalDebug) {
1834-
if (isNPOT) {
1835-
console.warn("NPOT(Non Power of Two) Texture: "+ image.src);
1872+
if (isNPOT && this._antialias) {
1873+
console.warn("NPOT(Non Power of Two) Texture w/ antialias on: "+ image.src);
18361874
}
18371875
if (image.width > gl.MAX_TEXTURE_SIZE || image.height > gl.MAX_TEXTURE_SIZE){
18381876
console && console.error("Oversized Texture: "+ image.width+"x"+image.height +" vs "+ gl.MAX_TEXTURE_SIZE +"max");
@@ -1848,8 +1886,7 @@ this.createjs = this.createjs||{};
18481886
* @protected
18491887
*/
18501888
p._insertTextureInBatch = function (gl, texture) {
1851-
// if it wasn't used last batch
1852-
if (this._batchTextures[texture._activeIndex] !== texture) {
1889+
if (this._batchTextures[texture._activeIndex] !== texture) { // if it wasn't used last batch
18531890
// we've got to find it a a spot.
18541891
var found = -1;
18551892
var start = (this._lastTextureInsert+1) % this._batchTextureCount;
@@ -1867,24 +1904,25 @@ this.createjs = this.createjs||{};
18671904
this.batchReason = "textureOverflow";
18681905
this._drawBuffers(gl); // <------------------------------------------------------------------------
18691906
this.batchCardCount = 0;
1870-
found = start;
1907+
found = start; //TODO: how do we optimize this to be smarter?
18711908
}
18721909

18731910
// lets put it into that spot
18741911
this._batchTextures[found] = texture;
18751912
texture._activeIndex = found;
1876-
var image = texture._imageData;
1877-
if (image && image._invalid && texture._drawID !== undefined) {
1913+
var image = texture._imageData && texture._imageData[0]; // first come first served, potentially problematic
1914+
if (image && image._invalid) {
18781915
this._updateTextureImageData(gl, image);
18791916
} else {
18801917
gl.activeTexture(gl.TEXTURE0 + found);
18811918
gl.bindTexture(gl.TEXTURE_2D, texture);
18821919
this.setTextureParams(gl);
18831920
}
18841921
this._lastTextureInsert = found;
1885-
} else {
1886-
var image = texture._imageData;
1887-
if (texture._storeID != undefined && image && image._invalid) {
1922+
1923+
} else if(texture._drawID !== this._drawID) { // hanging around from previous draws means the content might be out of date
1924+
var image = texture._imageData && texture._imageData[0];
1925+
if (image && image._invalid) {
18881926
this._updateTextureImageData(gl, image);
18891927
}
18901928
}
@@ -1898,40 +1936,41 @@ this.createjs = this.createjs||{};
18981936
* {{#crossLink "StageGL/releaseTexture"}}{{/crossLink}} instead as it will call this with the correct texture object(s).
18991937
* Note: Testing shows this may not happen immediately, have to wait frames for WebGL to have actually adjust memory.
19001938
* @method _killTextureObject
1901-
* @param {Texture} tex The texture to be cleaned out
1939+
* @param {Texture} texture The texture to be cleaned out
19021940
* @protected
19031941
*/
1904-
p._killTextureObject = function (tex) {
1905-
if (!tex) { return; }
1942+
p._killTextureObject = function (texture) {
1943+
if (!texture) { return; }
19061944
var gl = this._webGLContext;
19071945

19081946
// remove linkage
1909-
if (tex._storeID !== undefined && tex._storeID >= 0) {
1910-
this._textureDictionary[tex._storeID] = undefined;
1947+
if (texture._storeID !== undefined && texture._storeID >= 0) {
1948+
this._textureDictionary[texture._storeID] = undefined;
19111949
for (var n in this._textureIDs) {
1912-
if (this._textureIDs[n] == tex._storeID) { delete this._textureIDs[n]; }
1950+
if (this._textureIDs[n] == texture._storeID) { delete this._textureIDs[n]; }
19131951
}
1914-
if(tex._imageData) { tex._imageData._storeID = undefined; }
1915-
tex._imageData = tex._storeID = undefined;
1952+
var data = texture._imageData;
1953+
for (var i=data.length-1; i>=0; i--) { data[i]._storeID = undefined; }
1954+
texture._imageData = texture._storeID = undefined;
19161955
}
19171956

19181957
// make sure to drop it out of an active slot
1919-
if (tex._activeIndex !== undefined && this._batchTextures[tex._activeIndex] === tex) {
1920-
this._batchTextures[tex._activeIndex] = this._baseTextures[tex._activeIndex];
1958+
if (texture._activeIndex !== undefined && this._batchTextures[texture._activeIndex] === texture) {
1959+
this._batchTextures[texture._activeIndex] = this._baseTextures[texture._activeIndex];
19211960
}
19221961

19231962
// remove buffers if present
19241963
try {
1925-
if (tex._frameBuffer) { gl.deleteFramebuffer(tex._frameBuffer); }
1926-
tex._frameBuffer = undefined;
1964+
if (texture._frameBuffer) { gl.deleteFramebuffer(texture._frameBuffer); }
1965+
texture._frameBuffer = undefined;
19271966
} catch(e) {
19281967
/* suppress delete errors because it's already gone or didn't need deleting probably */
19291968
if (this.vocalDebug) { console.log(e); }
19301969
}
19311970

19321971
// remove entry
19331972
try {
1934-
gl.deleteTexture(tex);
1973+
gl.deleteTexture(texture);
19351974
} catch(e) {
19361975
/* suppress delete errors because it's already gone or didn't need deleting probably */
19371976
if (this.vocalDebug) { console.log(e); }
@@ -2229,32 +2268,33 @@ this.createjs = this.createjs||{};
22292268
var uvRect, texIndex, image, frame, texture, src;
22302269
var useCache = item.cacheCanvas && !ignoreCache;
22312270

2271+
// get the image data, or abort if not present
22322272
if (item._webGLRenderStyle === 2 || useCache) { // BITMAP / Cached Canvas
22332273
image = (ignoreCache?false:item.cacheCanvas) || item.image;
2234-
} else if (item._webGLRenderStyle === 1) { // SPRITE
2274+
} else if (item._webGLRenderStyle === 1) { // SPRITE
22352275
frame = item.spriteSheet.getFrame(item.currentFrame); //TODO: Faster way?
22362276
if (frame === null) { continue; }
22372277
image = frame.image;
2238-
} else { // MISC (DOM objects render themselves later)
2278+
} else { // MISC (DOM objects render themselves later)
22392279
continue;
22402280
}
2281+
if (!image) { continue; }
22412282

22422283
var uvs = this._uvs;
22432284
var vertices = this._vertices;
22442285
var texI = this._indices;
22452286
var alphas = this._alphas;
22462287

22472288
// calculate texture
2248-
if (!image) { continue; }
22492289
if (image._storeID === undefined) {
22502290
// this texture is new to us so load it and add it to the batch
22512291
texture = this._loadTextureImage(gl, image);
2252-
this._insertTextureInBatch(gl, texture);
22532292
} else {
22542293
// fetch the texture (render textures know how to look themselves up to simplify this logic)
22552294
texture = this._textureDictionary[image._storeID];
2256-
if (!texture){
2257-
if (this.vocalDebug){ console.log("Texture should not be looked up while not being stored."); }
2295+
2296+
if (!texture){ //TODO: this should really not occur but has due to bugs, hopefully this can be removed eventually
2297+
if (this.vocalDebug){ console.log("Image source should not be lookup a non existent texture, please report a bug."); }
22582298
continue;
22592299
}
22602300

@@ -2264,6 +2304,7 @@ this.createjs = this.createjs||{};
22642304
}
22652305
}
22662306
texIndex = texture._activeIndex;
2307+
image._drawID = this._drawID;
22672308

22682309
if (item._webGLRenderStyle === 2 || useCache) { // BITMAP / Cached Canvas
22692310
if (!useCache && item.sourceRect) {

0 commit comments

Comments
 (0)