Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

"cleanGLObjectsUponModifyOrDelete" field for X3DTextureNode #609

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
20 changes: 19 additions & 1 deletion src/Cache.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ x3dom.Cache = function () {
};

/**
* Returns a Texture 2D
* Returns the existing texture identified by "url" or generates and returns
* a new texture to be hereafter identified by the key "url."
*/
x3dom.Cache.prototype.getTexture2D = function (gl, doc, url, bgnd, crossOrigin, scale, genMipMaps) {
var textureIdentifier = url;
Expand All @@ -28,6 +29,13 @@ x3dom.Cache.prototype.getTexture2D = function (gl, doc, url, bgnd, crossOrigin,
return this.textures[textureIdentifier];
};

/**
* Returns the texture identified by "url" or returns undefined if there is no such texture.
*/
x3dom.Cache.prototype.getTexture2DByUrl = function ( url ) {
return this.textures[url];
}

/**
* Returns a Texture 2D
*/
Expand All @@ -41,6 +49,16 @@ x3dom.Cache.prototype.getTexture2DByDEF = function (gl, nameSpace, def) {
return this.textures[textureIdentifier];
};

/**
* Deletes the texture identified by "url" or does nothing if there is no such texture.
*/
x3dom.Cache.prototype.deleteTexture2DByUrl = function ( gl, url ) {
if( this.textures[url] !== undefined ) {
gl.deleteTexture( this.textures[url] );
delete this.textures[url];
}
};

/**
* Returns a Cube Texture
*/
Expand Down
47 changes: 45 additions & 2 deletions src/Texture.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ x3dom.Texture = function (gl, doc, cache, node) {
this.ready = false;

this.dashtexture = false;
this.lastUrlUsedForTextureCreation = null;

var tex = this.node;
var suffix = "mpd";
Expand Down Expand Up @@ -127,6 +128,35 @@ x3dom.Texture.prototype.update = function()
}
};

/**
* Invoke gl.deleteTexture on the texture handle, thus freeing up some video memory.
* @pre This instance has been properly set up before and contains a handle to a valid OpenGL texture.
* @pre The texture was created by using a URL. In other words, this.lastUrlUsedForTextureCreation is defined.
* @post This instance is no longer valid, and should be either updated or deleted.
*/
x3dom.Texture.prototype.cleanGLObjects = function()
{
if ( x3dom.isa( this.node, x3dom.nodeTypes.ImageTexture ) ) {
var textureUrl = this.lastUrlUsedForTextureCreation;
if( textureUrl === null ) {
x3dom.debug.logError( 'cleanGLObjects cannot delete texture by url since lastUrlUsedForTextureCreation is null' );
} else {
var textureHandle = this.cache.getTexture2DByUrl( textureUrl );
if( textureHandle === undefined || textureHandle !== this.texture ) {
// TODO: Implement cleanup logic for the case where this.cache is
// not holding the texture handle.
x3dom.debug.logError( 'cleanGLObjects not defined for case where this.cache does not contain this.texture' );
} else {
this.cache.deleteTexture2DByUrl( this.gl, textureUrl );
this.texture = undefined; // Make debugging easier.
}
}
} else {
// TODO: Implement for other node types, like MovieTexture and so on.
x3dom.debug.logError( 'cleanGLObjects not defined for this kind of texture node!' );
}
};

x3dom.Texture.prototype.setPixel = function(x, y, pixel, update)
{
var gl = this.gl;
Expand Down Expand Up @@ -395,16 +425,29 @@ x3dom.Texture.prototype.updateTexture = function()
}
else if (x3dom.isa(tex, x3dom.nodeTypes.X3DEnvironmentTextureNode))
{
this.texture = this.cache.getTextureCube(gl, doc, tex.getTexUrl(), false,
this.lastUrlUsedForTextureCreation = tex.getTexUrl();
this.texture = this.cache.getTextureCube( gl, doc, this.lastUrlUsedForTextureCreation, false,
tex._vf.crossOrigin, tex._vf.scale, this.genMipMaps);
}
else
{
this.texture = this.cache.getTexture2D(gl, doc, tex._nameSpace.getURL(tex._vf.url[0]),
this.lastUrlUsedForTextureCreation = this.getUrlForBasicTexture();
this.texture = this.cache.getTexture2D( gl, doc, this.lastUrlUsedForTextureCreation,
false, tex._vf.crossOrigin, tex._vf.scale, this.genMipMaps);
}
};

/**
* Returns the URL of the relevant texture, assuming the relevant node is not an
* X3DEnvironmentTextureMode or some other node which defines a special way of generating
* the texture's URL.
* @pre this.node is the sort for which basic URL-generation method is appropriate.
*/
x3dom.Texture.prototype.getUrlForBasicTexture = function()
{
return this.node._nameSpace.getURL( this.node._vf.url[0] );
};

x3dom.Texture.prototype.updateText = function()
{
var gl = this.gl;
Expand Down
119 changes: 84 additions & 35 deletions src/gfx_webgl.js
Original file line number Diff line number Diff line change
Expand Up @@ -179,23 +179,39 @@ x3dom.gfx_webgl = (function () {
return null;
}


/*****************************************************************************
* Setup GL objects for given shape
*****************************************************************************/
Context.prototype.setupShape = function (gl, drawable, viewarea) {
var q = 0, q6;
var textures, t;
var textureNodes, t;
var vertices, positionBuffer;
var texCoordBuffer, normalBuffer, colorBuffer;
var indicesBuffer, indexArray;

var shape = drawable.shape;
var geoNode = shape._cf.geometry.node;

if (shape._webgl !== undefined) {
var needFullReInit = false;

// Make a copy of shape._webgl.texture, which is essentially the collection of
// texture wrappers that this shape is in charge of. By making a copy, we can detect
// which textures have gone out of use, at which point we can delete them if we chose.
var oldTextureWrappers = [];
Array.forEach( shape._webgl.texture, function( oldTextureWrapper ) {
// Make a deep (enough) copy to support comparisons with true Texture instances
// and the deletion of an OpenGL texture, if necessary (the latter is
// why there is a copy of cache and gl in here).
var copyOfOldTextureWrapper = {};
copyOfOldTextureWrapper.node = oldTextureWrapper.node;
copyOfOldTextureWrapper.texture = oldTextureWrapper.texture;
copyOfOldTextureWrapper.lastUrlUsedForTextureCreation = oldTextureWrapper.lastUrlUsedForTextureCreation;
copyOfOldTextureWrapper.cache = oldTextureWrapper.cache;
copyOfOldTextureWrapper.gl = oldTextureWrapper.gl;
oldTextureWrappers.push( copyOfOldTextureWrapper );
});

// TODO; do same for texcoords etc.!
if (shape._dirty.colors === true &&
shape._webgl.shader.color === undefined && geoNode._mesh._colors[0].length) {
Expand All @@ -208,48 +224,47 @@ x3dom.gfx_webgl = (function () {
if (needFullReInit && shape._cleanupGLObjects) {
shape._cleanupGLObjects(true, false);
}

//Check for dirty Textures
// Check for dirty Textures (Have DOM elements been modified?)
if (shape._dirty.texture === true) {
//Check for Texture add or remove
if (shape._webgl.texture.length != shape.getTextures().length) {
//Delete old Textures
for (t = 0; t < shape._webgl.texture.length; ++t) {

// ImageTexture nodes, MovieTexture nodes, etc.
textureNodes = shape.getTextures();

// Check if Textures have been added or removed via adding/removing nodes.
if (shape._webgl.texture.length != textureNodes.length) {
// Delete old Textures (the wrappers, not the actual OpenGL texture objects).
for (t = 0; t < shape._webgl.texture.length; ++t) {
shape._webgl.texture.pop();
}

//Generate new Textures
textures = shape.getTextures();

for (t = 0; t < textures.length; ++t) {
shape._webgl.texture.push(new x3dom.Texture(gl, shape._nameSpace.doc, this.cache, textures[t]));
// Create new Texture wrappers.
for (t = 0; t < textureNodes.length; ++t) {
shape._webgl.texture.push(new x3dom.Texture(gl, shape._nameSpace.doc, this.cache, textureNodes[t]));
}

//Set dirty shader
shape._dirty.shader = true;

//Set dirty texture Coordinates
// Set dirty texture Coordinates
if (shape._webgl.shader.texcoord === undefined)
shape._dirty.texcoords = true;
}
else {
//If someone remove and append at the same time, texture count don't change
//and we have to check if all nodes the same as before
textures = shape.getTextures();

for (t = 0; t < textures.length; ++t) {
if (textures[t] === shape._webgl.texture[t].node) {
//only update the texture
// If client code performed an equal number of removes and appends, the texture count doesn't change
// and we have to check if all nodes the same as before
for (t = 0; t < textureNodes.length; ++t) {
if (textureNodes[t] === shape._webgl.texture[t].node) {
// Only update the texture
shape._webgl.texture[t].update();
}
else {
//Set texture to null for recreation
// Set texture to null for recreation
shape._webgl.texture[t].texture = null;

//Set new node
shape._webgl.texture[t].node = textures[t];
// Set new node
shape._webgl.texture[t].node = textureNodes[t];

//Update new node
// Update new node
shape._webgl.texture[t].update();
}
}
Expand Down Expand Up @@ -434,7 +449,32 @@ x3dom.gfx_webgl = (function () {
geoNode.unsetGeoDirty();
shape.unsetGeoDirty();
}


// Check whether there are any now-unused texture wrappers whose textures can now by cleaned up by
// the GL context.
Array.forEach( oldTextureWrappers, function( oldTextureWrapper ) {

// Check whether oldTextureWrapper is not a member of the updated
// texture wrappers (out of the updated texture wrappers, there is not
// a wrapper that wraps the same texture).
var textureNoLongerUsed = true;
Array.forEach( shape._webgl.texture, function( newTextureWrapper ) {
if( newTextureWrapper.texture === oldTextureWrapper.texture ) {
textureNoLongerUsed = false;
}
});

if( textureNoLongerUsed && oldTextureWrapper.node.cleanGLObjectsUponModifyOrDelete() ) {
// invoke gl.deleteTexture - would say
// 'oldTextureWrapper.cleanGLObjects()' except
// that oldTextureWrapper is not a true instance of Texture,
// just a copy of some of the fields from a Texture instance.
x3dom.Texture.prototype.cleanGLObjects.call( oldTextureWrapper );
}

});
oldTextureWrappers = [];

if (!needFullReInit) {
// we're done
return;
Expand All @@ -457,18 +497,27 @@ x3dom.gfx_webgl = (function () {
}
return;
}

// we're on init, thus reset all dirty flags
shape.unsetDirty();

// dynamically attach clean-up method for GL objects
// Dynamically attach clean-up method for GL objects
if (!shape._cleanupGLObjects)
{
shape._cleanupGLObjects = function (force, delGL)
{
// FIXME; what if complete tree is removed? Then _parentNodes.length may be greater 0.
if (this._webgl && ((arguments.length > 0 && force) || this._parentNodes.length == 0))
{
// Invoke gl.deleteTexture on those textures that are not supposed to hang around
// in memory when they go out of use.
Array.forEach( shape._webgl.texture, function( textureWrapper ) {
if( textureWrapper.node.cleanGLObjectsUponModifyOrDelete() ) {
// Here we are assuming that the Shape is going out of existence.
textureWrapper.cleanGLObjects();
}
});

var sp = this._webgl.shader;

for (var q = 0; q < this._webgl.positions.length; q++) {
Expand Down Expand Up @@ -539,12 +588,12 @@ x3dom.gfx_webgl = (function () {
externalGeometry: 0 // 0 : no EG, 1 : indexed EG, -1 : non-indexed EG
};

//Set Textures
textures = shape.getTextures();
for (t = 0; t < textures.length; ++t) {
shape._webgl.texture.push(new x3dom.Texture(gl, shape._nameSpace.doc, this.cache, textures[t]));
// Update texture wrappers
textureNodes = shape.getTextures();
for (t = 0; t < textureNodes.length; ++t) {
shape._webgl.texture.push(new x3dom.Texture(gl, shape._nameSpace.doc, this.cache, textureNodes[t]));
}

//Set Shader
//shape._webgl.shader = this.cache.getDynamicShader(gl, viewarea, shape);
//shape._webgl.shader = this.cache.getShaderByProperties(gl, drawable.properties);
Expand Down
33 changes: 29 additions & 4 deletions src/nodes/Texturing/X3DTextureNode.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,24 @@ x3dom.registerNodeType(
* @instance
*/
this.addField_SFNode('textureProperties', x3dom.nodeTypes.TextureProperties);

/**
* Specifies whether gl.deleteTexture() should be called on the current texture
* if this node gets deleted or it switches to representing a new texture image.
*
* Warning, this field should only be set to "true" if the relevant texture
* is used within one Shape node (preferably within a single X3DTextureNode inside
* that Shape). If another Shape references the same texture by using the same
* texture URL, then when a DOM modification to the first Shape causes the texture to be
* deleted, the second Shape might display incorrectly.
*
* @var {x3dom.fields.SFBool} cleanGLObjectsUponModifyOrDelete
* @memberof x3dom.nodeTypes.X3DTextureNode
* @initvalue false
* @field x3dom
* @instance
*/
this.addField_SFBool(ctx, 'cleanGLObjectsUponModifyOrDelete', false);

this._needPerFrameUpdate = false;
this._isCanvas = false;
Expand All @@ -106,6 +124,8 @@ x3dom.registerNodeType(

},
{
// TODO: This name is misleading, since calling invalidateGLObject() does not
// necessarily lead to a texture actually getting deleted.
invalidateGLObject: function ()
{
Array.forEach(this._parentNodes, function (app) {
Expand All @@ -115,7 +135,7 @@ x3dom.registerNodeType(
shape._dirty.texture = true;
}
else {
// Texture maybe in MultiTexture or CommonSurfaceShader
// Texture may be in MultiTexture or CommonSurfaceShader
Array.forEach(shape._parentNodes, function (realShape) {
if (x3dom.isa(realShape, x3dom.nodeTypes.X3DShapeNode)) {
realShape._dirty.texture = true;
Expand Down Expand Up @@ -165,15 +185,20 @@ x3dom.registerNodeType(
}
});
},

cleanGLObjectsUponModifyOrDelete : function()
{
return this._vf.cleanGLObjectsUponModifyOrDelete;
},

fieldChanged: function(fieldName)
{
if (fieldName == "url" || fieldName == "origChannelCount" ||
fieldName == "repeatS" || fieldName == "repeatT" ||
fieldName == "scale" || fieldName == "crossOrigin")
{
{
var that = this;

Array.forEach(this._parentNodes, function (app) {
if (x3dom.isa(app, x3dom.nodeTypes.X3DAppearanceNode)) {
app.nodeChanged();
Expand Down Expand Up @@ -202,7 +227,7 @@ x3dom.registerNodeType(
if(app._volumeDataParent){
app._volumeDataParent._dirty.texture = true;
}else{
//Texture maybe under a ComposedVolumeStyle
// Texture may be under a ComposedVolumeStyle
var volumeDataParent = app._parentNodes[0];
while(!x3dom.isa(volumeDataParent, x3dom.nodeTypes.X3DVolumeDataNode) && x3dom.isa(volumeDataParent, x3dom.nodeTypes.X3DNode)){
volumeDataParent = volumeDataParent._parentNodes[0];
Expand Down