Cloning materials creates many duplicate textures when not needed, and avoiding delays when changing materials

hi Guys , Im noticing some really high delay on one of my current projects and its usually happening when it has to change various materials and turn on or off some layers, all of this one after another , I dont know if this is the reason but i think it shouldnt be , cuz its not a big deal , even the model is not complex, it may have a lot of layers but this shouldnt be a problem either right ? , and I was looking at the inspector and I notice that the textures count is 4 times higher than the materials , might not be the problem but I find this a little resource wasting , so I think this could be improved, the thing is that I have a thrash can , and I clone it 4 times so you can configure 4 different cans , in order to do so I also cloned the materials , so when I change one of them the other cans remain with their own material , but the textures that they start with are all the same , the default one , so Im thinking is useless to have 4 different textures one for each can , when all 4 look exactly the same, so the 4 material clones should probably point to the same texture right ? they would still be 4 different materials and should not change all when you change one of them ,but they should all share the same texture. But there is no option when you re cloning materials for not cloning textures too, so i think this would be a really good thing to improve, I dont even know in which case you would need different textures for a cloned material , which in theory should have same textures as the original cuz is a clone, so yeah big waste of resources there cloning textures or let me know what you think about that.


this image explains it a little better, and yeah I know 2 of those cans have different label but that I did after cloning.

Also I know this might not be the kind of help I should be asking here, and I understand if you dont have the time or the patience to help me out , but still im gonna ask if anyone feels like helping , could anyone tell me what im doing wrong that when I choose a typical or put a signage on the can it takes a while just for doing that ? and this doesnt happen with other options like changing colors or top oppenings, those are like instantaneous changes, and i cant figure why, feeling like its everytime I make a material change, which i make using updateURL() , but this doesnt expain the delay either. can someone tell me how I could get rid of this delays ? make it more responsive , faster , smoother ,etc …
here is the link to that VALUTA Product Configurator , also if you wanna display the inspector you just have to click the letter “d” on your keyboard 10 times , that will show the scene inspector. if you need any other thing about the code in order to help me just tell me , i would gladly give you more info so you can help me with this.

The cloned version of the texture should be ok as it does not waste GPU and almost no CPU memory as the texture points to all the same data in our cache.

alright if you say its ok and it doesnt waste any resources then for me its ok. you got any idea on what could be causing the delay when changing textures ? or how can i make that more efficient ? i tried putting a progress bar too , but what happens is that when i click the button it freezes then loads the model and the progress bar is already full when its visible, its like there is no delay for the code but it is for the rendered result

anyway I still think you could skip that texture cloning part and just make reference to same texture right ? I mean it wouldnt hurt to just get rid of that unnecessary clonning, but thats up to you guys im just pointing things out.

Could you share a repro of where it is slow ? would be great to see it and understand what is happening.

hack the planet…!

Jokes aside , I dont personally use updateURL , but looking at your site and having developer tools open shows that it does use a network request each time and is the cause of the delay.

Even if you have previously loaded the texture and the browser uses the disc cache … there is still that delay.

I personally cache the Texture objects I create at runtime and when clicking on the same item again I avoid a network request and directly assign the cached object instead

1 Like

ahh damn so thats where I was failing then , thanks for pointing out that mistake, could you guide me on how to change the textures without making any request everytime I need 'em ? I guess I should preload them all on cache and then just call them right ? but how do I do this ? a Little example code would be of great help , really thankful @shaderbytes for your colaboration there , you dont know how much I appreciate that you took the time to help a fellow. really had been on this for days without knowing how could I fix it, and there you go with few minutes on the site and you got it all figured out.
and this kind of information is gonna make all my future and previous projects so much smoother , thanks for that.

P.D: english is not really my native language so I didnt quite catch your “hack the planet …!” joke ahahha would love to know if it was a praise (which I strongly doubt) or just a mockery

so obviously there has to be some network action to get the textures into the app at some point , doing them up front is a possibility, depends how you plan to handle that. I dont personally do it in my app , I just do the cache i mentioned so when reapplying it avoids the network action.

Just bare in mind , the code does become much more complex when doing this type of UX optimization , as you have it , it is simply updating the URL and boom it updates.

ps … hack the planet is a line from a old movie about computer hackers. I was playing fun at the idea of you telling us the secret key presses to open the inspector…

ahh ok I get it , so basically there is no way to avoid that delay ? cuz i really needed to bring that down or at least give the user a heads up like a progress bar or smth but during that request the browser wont render any changes done to a CSS or babylon gui progress bar, only after the response is back it would do the changes and by that time they wont be necessary, would you mind sharing some of that valuable knowledge about reusing the cache instead of making the call again , so at least I can make a real improve to my app ? or am I asking too much ? sorry if I am. So thinking about this would be great to have a way to store the textures you would like to use at some point in your app , right ? so there would be no delays at all, does this help with that at all Creating A Texture Package | Babylon.js Documentation ? or currently there is no tool for such a thing @shaderbytes @sebavan ?

ps: ahahah thanks for the break down, now i get the joke !!

I have not used babylons texture packer myself but I understand the concept well from working with 3D for a while , it is basically atlasing the textures ( combining textures ) and then using adjusted UV sets to point to the relevant position in the atlas.

EDIT: Im not sure how this handles material updates though…? It might just be a solution for mesh objects that are not expecting updates… actually im sure this is the case

my functions are very verbose due to my specific implementations and because I use an update system that supports my simple serialization needs but here is an outline :

I have a cache of all materials in the babylon scene (i know babylon has such a cache themselves) I just use my own due to some code that prevents duplicate materials.

I also have a custom class for textures in my app. This class has a field for the babylon texture object and some functions for preloading.

I cache these custom texture objects. Every texture my configurator uses and has to load has a unique key. This key is the key for caching

you could use a regular object or a map , im just using a object here.

when im invalidating my update system and handling textures,

  1. I check the cache using the key

  2. if not found i need to create one of my custom objects , storing it in the cache using the key , create a babylon texture object and assign it to a field in this object. Start preloading , use onLoadObservable of the babylon texture to handle load success and assign the loaded texture to the channel

  3. if found it has two possibilities , it might be created but still loading so I dont want to assign it to the channel yet or it causes the geometry to dissapear. i avoid this by checking “isReadyOrNotBlocking” of the babylon texture object. if true it directly assigns it.

ok this gets complex now ,

  1. if isReadyOrNotBlocking is false i still need to handle it because , per single texture loading , there might be 3 or 4 materials that expect to be updated once it loads. Since only the first call to a unloaded texture handles the success and first assignment , any calls by other materials expecting this texture that are received after it is first created but still loading need to have their own onLoadObservables created.

so when the texture is loaded , all observables are triggered and all materials waiting to be updated get updated.

its a mouthful , i did mention it becomes complex

wow @shaderbytes im gonna go ahead and stop you there cuz yeah that feels like to complex for me to achieve , but if i get on that boat i guess what you just explained to me makes half of it and its more than enough, I was more interested on how to call from cache and how to load into it , any cache , i dont really know how to access or use browser cache yet but i guess i can give it a look, from there all the logic dont seem to hard

cache is just an english word , im not referring to the browsers disc cache

var mytextureObjects = {}

is a cache
or

var myTextureObjects = new Map()

is a cache

both these use keys

i just check if it has a key or value for that key , the value stored here can be anything. maybe the babylon texture object itself , I just store a custom object with reference to a babylon object due to preloading concerns

depending on if you use an object or a map depends how you check for key or values. I use a object as mentioned and I just use the key and check for a value :

let myCachedTextureObject = mytextureObjects[textureKeyString];

if (myCachedTextureObject) {
  // has object could be loaded or not
  // assuming the cached object is a babylon texture
  if(myCachedTextureObject .isReadyOrNotBlocking()){          
      sceneEntities.collectionMaterials[material_key][channel_key] = myCachedTextureObject ;
  }
} else {
  // does not have object so create one
  //for doing babylon texture object directly

  myCachedTextureObject = mytextureObjects[textureKeyString] = new Texture(url);
  myCachedTextureObject.onLoadObservable.addOnce((eventData) => {
    //this is custom to my app , but just know here I assign the loaded texture to the material channel
    var channel = (sceneEntities.collectionMaterials[material_key][channel_key] = eventData);
  });
}

I left much code out because my project has all those complexities of multiple materials and more … but the above is meant to give you the idea you wanted

ahhh @shaderbytes thanks for the aclaration , that makes much more sense, im gonna go ahead and do some coding for that to store texture objects and reuse them, maybe even load them before hand and just using them when needed , maybe from there i can even build an object class for this kind of task and if its good enough you can include it in babylon core scripts

enjoy figuring something out :wink:

Take note this has a con of memory build up , but for most small configurators this wont be an issue. Im just pointing this out now in case it does become an issue someday.

cheers

thanks mate , you gave me the right angle for this, wouldnt have figured this out on my own since i didnt know what was causing the delays so this was a really useful talk or chat i guess.
and yeah dont worry about the memory as you could see on the site there arent much material variants there , just the labels and the signage labels so they re few

Yo @shaderbytes thanks for your help mate, Im here to let you know that the cache approach worked really good, it was a pretty simple fix, I just created a global variable called cacheTextures and inside my scene building function I just went trough my data to check for the urls of textures and store them on my cache variable, so yeah I actually loaded them in cache beforehand , and then when calling the changematerial function when a button was clicked I just went over the cache looking for the same url string, and assigned the texture object to the material, I also took the idea of your keyword but I made it a little simpler , I stored an array containing in the first slot the url string and in the second the texture object related to that url, so it looks something like this :

// Storing the textures inside the cache variable
data.FeaturesData.map(function (feature) {
        feature.Options.map(function (option) {
            if (option.hasOwnProperty('Material')) {
                cacheTextures.push([option.Material[0].Url,new BABYLON.Texture(option.Material[0].Url, scene)]); 
            }
        });
    });

// inside changeMaterial function
 cacheTextures.map(function (texture){
                if(texture[0] == Url){
                    child.material[materialChannel] = texture[1];
                }
            });

Also if you wanna check out the result you just gotta go back to the same site link: VALUTA Product Configurator ,there is no more delays thanks to your help. So Smooth now :smile:

Ps: the change material function has much more code than I showed but I thought it would be unnecessary to put all in there since the really important part was the texture assignation, the other code is just checking for the layers it has to put them on and some other material alpha settings

2 Likes

@Juancode testing it now , no delay :wink: well done.

1 Like