Loading/unloading assets

he …

i’m working on a generative piece where i’m using tons of textures for pbr materials. i managed to write a preloader that picks all textures used in a bunch of pbr materials from the asset registry when i reset my generated scene. i’m now looking into vram optimization and run into a problem.

i’m using asset.unload() to get rid of the textures when i do my reset. vram gets reduced as i hoped it would but sometimes i get the error ‘attempting to use a texture that has been destroyed.’ resulting in a black material in some places.

i think that i can resolve the issue if i just knew the name of the texture(s) that cause the error. if i check console and open the error details i won’t find any reference. is there a way to track it down?

Hi @rtz23,

Are you reloading back the textures that have been manually unloaded, when they are required to be rendered again?

Any asset that you manually unload you will have to load it in time when it’s required again:

this.app.assets.load(asset);

he @Leonidas,
absolutly. this is why i was mentioning the preloader. it does load() the textures i need. i cycle through all material slots of all materials which will be shown in the scene upfront find them in registry and then i .load() them. after the reset i use the array i have to unload them. i also tryed to tag them and use this.app.assets.findByTag() to rule out removing any texture i don’t want to be unloaded (like some custom opacity maps).

found this thread meanwhile. that guy mentions a similar thing when textures are shared between his scenes. it may be possible the textures that fail are used in the scene before i reset it.

Yes, good point, it may be related to that. If you are able to share a sample project that reproduces the issue we may be able to see if there is an engine but in there, or find a workaround.

i’ll check if i can reproduce it but the project is super complex - working on it for almost 2 years now. i’m not sure if it is reproduceable at all. but i’ll give it a try. could also lead me to an answer trying to encapsulate it. i’d give you acces but i don’t think you’ll be able do decipher what’s going on there at all :wink: … i’ll share a sample project if i get it done!

1 Like

Unfortunately, it doesn’t have that information at the material level as I believe it is referencing the texture resource directly. Maybe you could track what assets are unloaded from the asset registry event as that would capture the last X textures and narrow the search down?

1 Like

Edit: Looks like the unload event is on the asset itself: https://developer.playcanvas.com/en/api/pc.Asset.html#event:unload

2 Likes

@yaustar thanks … i’ll give that a try… though i believe it may be the same problem as in the other thread. gonna try to reproduce the problem in a sample project now.

@Leonidas @yaustar

https://playcanvas.com/project/776049

so this is in a nutshell what i’m doing. i have an attributes array with my textures. upfront i randomize which texture will be used. then i find it in the registry and load it. callback sets a function to switch the textures (i clone the material because i need different iterations of it). and when i reset the scene i unload all the textures i have loaded before.

i don’t get the error here though sometimes one of the spheres stays black as i described it.

In that example it is possible to unload a texture one ball was using before but may have been needed by another ball now hence the error. That’s what’s causing your error as one material is trying to use a texture that another ball has unloaded.

@yaustar

you are right. i need to check if that is also the case in my original project. it may well be. but if i remember correctly i load the spheres in a sequence one after another. and the unload (should) not happen while any sphere is loading its textures. writing this i can think of a case where that might actually happen. i’m gonna look into it… thank you so far…

@yaustar

so i’ve spent some time staring at lists of textures and tracing variables. i can confirm that all my materials and textures are loading in a sequence so they don’t interfere. the unloading can only happen if every texture is loaded. actually loading/unloading works out in most of the cases. but somewhere along the way i lose some textures still.

i found out though that there is a case where multiple entites request to load the same previously unloaded texture simultanously. can that to your knowledge cause a problem?

Shouldn’t be an issue AFAIK.

If you are 100% sure that a material is not trying to use an unloaded texture, the only other thing I can think of is that a texture may be failing to load (do you get any errors messages in dev console about network errors?)

Is the issue reproducible on the same texture/material/time in the app?

I’m wondering if there is some way to trace the error message back to the material :thinking:

@yaustar

he … going to checkout everything again today :slight_smile: … so i do get network errors (more than it used to) … it happens sometimes when i press play. but after a reload i don’t see any network errors anymore nor do i get it in the situation when the materials stay black.

the problem is not reproduceable for the same textures. allthough i don’t know the exact names given the error message i do see that it varies among very different textures.

as i pointed out the project is very complex, messy and was created over a long ammount of time. it is absolutly possible that i miss something since i’m throwing around arrays with dozens of textures, meshes, pbr materials and what not like i’ve gone totally crazy.

i need to point out that i’m not a coder. so this feels like show off my dirty underwear but this is how i load the textures:

SuperSecret.prototype.loadTextures = function() {

    self = this;
    this.materialLoadCounter = 0;
    var tAssets = new Array(0);
    var aCount = 0;

    //materials base,bio1,bio2,caps & civ have beend picked out of a material array previously but not loaded yet

    this.texturePool.push(this.base.data.aoMap, this.base.data.diffuseMap, this.base.data.emissiveMap, this.base.data.normalMap, this.base.data.opacityMap, this.base.data.heightMap);
    this.texturePool.push(this.bio1.data.aoMap, this.bio1.data.diffuseMap, this.bio1.data.emissiveMap, this.bio1.data.normalMap, this.bio1.data.opacityMap, this.bio1.data.heightMap);
    this.texturePool.push(this.bio2.data.aoMap, this.bio2.data.diffuseMap, this.bio2.data.emissiveMap, this.bio2.data.normalMap, this.bio2.data.opacityMap, this.bio2.data.heightMap);
    this.texturePool.push(this.caps.data.aoMap, this.caps.data.diffuseMap, this.caps.data.emissiveMap, this.caps.data.normalMap, this.caps.data.opacityMap, this.caps.data.heightMap);
    this.texturePool.push(this.civ.data.diffuseMap);

    //not entirely sure why i did this loop in the first place - it cleanses the 'null' i get from empty texture slots
    //think i could not use the type material.data returns to locate textureassets with .find() hence .get()

    this.texturePool.forEach(function(tp) {
        var tex = this.app.assets.get(tp);
        if (tex !== undefined) {
            tAssets.push(tex);
        }
    });

    //this loop loads the textures by their id's that have been pushed from texturePool[] into tAssets[]  
    //with a callback to loadMaterials() which does what it is named after

    tAssets.forEach(function(ta) {
        ta.ready(function() {
            aCount++;
            if (aCount == tAssets.length) {
                self.loadMaterials();
            }
        });

        //loading. this is where i get the error. adding conditions !ta.loading && !ta.loaded prevents the error message
        //but seemingly results in black materials / missingn textures

        if (!ta.loading && !ta.loaded) {
            this.app.assets.load(ta);
        }
    });

    //i only have a vague idea why i needed this though doesn't fire anymore.    

    if (tAssets.length === 0) {
        self.loadMaterials();
    }

};

this is how i unload textures from vram:

    //this happens in a different script as a part of a reset process and can only be triggered after loading is finished
    //i just remove all the textures wich i have tagged carefully in editor - doublechecked for correct tagging several times now

    var delAssets = [];
    delAssets = this.app.assets.findByTag('dynamic');

    for(del = 0; del < delAssets.length; del++){
        delAssets[del].unload();
    }

not sure if you can make something out of it but if you see something stupid or obvious let me know. without unloading the textures the whole thing works out just fine.

to give you a bit more context - i’m loading a bunch of spheres which get different materials radomly. i used asset registry/load so i can load the textures when they are needed so the app loads fast. every sphere also has a neat preloader. this is why everything is going in sequence. i avoid to fire load requests for all assets i need at the same time but one after another.

tldr - it’s complicated :wink:

Nothing there that looks too crazy.

How are assets tagged to be ‘dynamic’?

Check the project network settings in the project settings. If it’s 0, set it to 5 and see if that helps?

Side note: looks like self is used as a global variable instead of a local on.

Do var self = this

That in itself could be causing some issues.

@yaustar

network settings did not help. didn’t notice global assignment good point but didnt change anything either.

i tag the assets manually in the editor. the tag is just called ‘dynamic’ but is not created dynamically.

so if you say there’s nothing obvious it is at least a hint to consider alternative sources for my problem.

It’s one of those cases where it shouldn’t be a problem and haven’t encountered this issue myself.

Without being to replicate the issue, I can’t really suggest another solution as the error points to unloading a texture that is in use.

Other ways I can think on tracking the issue down is to track what material is causing the issue but iterating through all the material assets and checking if it is loaded and if all the maps are valid.

Is this issue easily reproducible?

If not, my first step would be to get it into a situation that you can replicate the issue on demand or very high possibility.

My guess is that material is being loaded with the texture it is referencing being loaded and there’s a mismatch between the two lists (the texture list and material list)

Another possibility is that an entity with a model asset that references other material assets is loaded. This causes the materials to be loaded and perhaps they are referencing textures that have been unloaded.

I would think about adding as much logging as possible and even modding or listening to events from the engine on loading and unloaded assets to track whats going on.

At a rough guess, without knowing your code, loadMaterials is loading a material asset that is referencing textures that the application hasn’t loaded.

You will have to start looking at which batch loading or unloading of assets is causing the issue and from there, systematic don’t unload half of them until you stop seeing the error.

This is why it’s crucial to be able reproduce the issue on will.

If it’s not possible, it points to a race/timing condition and may be more obvious if you use Chrome devtools to clear the cache and slow down the network speed.

I would be more tempted to make it a bit more straightforward and tag the textures and materials into groups rather than one giant single group.

Eg. ‘scene1’ for all materials and textures used in scene1. With some assets that are shared in multiple scenes, I would also check to make sure that the asset listed to be unloaded aren’t in the scene that is about to be loaded. It might also be good to use a loading scene inbetween the scene being unloaded and the scene being loaded. ie:

In ‘scene 1’
Load ‘loading scene’
Destroy entities in ‘scene 1’
Unload assets that are tagged ‘scene 1’ and also not tagged ‘scene 2’
Load assets tagged in ‘scene 2’
Load ‘scene 2’
Destroy entities in ‘loading scene’

@yaustar

i finally fixed it. it was actually a pretty boring variable type mismatch.

    this.texturePool.push(this.base.data.aoMap, this.base.data.diffuseMap, this.base.data.emissiveMap, this.base.data.normalMap, this.base.data.opacityMap, this.base.data.heightMap);
    this.texturePool.push(this.bio1.data.aoMap, this.bio1.data.diffuseMap, this.bio1.data.emissiveMap, this.bio1.data.normalMap, this.bio1.data.opacityMap, this.bio1.data.heightMap);
    this.texturePool.push(this.bio2.data.aoMap, this.bio2.data.diffuseMap, this.bio2.data.emissiveMap, this.bio2.data.normalMap, this.bio2.data.opacityMap, this.bio2.data.heightMap);
    this.texturePool.push(this.caps.data.aoMap, this.caps.data.diffuseMap, this.caps.data.emissiveMap, this.caps.data.normalMap, this.caps.data.opacityMap, this.caps.data.heightMap);
    this.texturePool.push(this.civ.data.diffuseMap);

i did not realize that this does not always return an asset ‘id’ but in some cases, if a texture had already been loaded previously by a different model AND been unloaded again, these may result in ‘Texture’. trying app.assets.get() then returned ‘undefined’ which lead to not loading these textures.

it all sems to comes down to the materials getting updated as soon as the corresponding textures are there. even if i did not load the materials themselves yet.

feeling relief.
thanks for your help!

1 Like

Is this the material resource? If yes, yep there’s a weird bit of legacy code that changes the type pending on if the asset is loaded or not :sweat: