Jump to content

Performance - standardMaterial.isReady and scene.pick


Recommended Posts



Two questions related to performance tuning:


1. I don't know what StandardMaterial.isReady does, but it seems to return early in various cases. What can I do to make my materials fall into those cases? This is usually the #1 most costly function in my game.


2. It appears that scene.pick gets called every onPointerMove, even though I'm not using picking or BJS's pointer support in any way. Can I prevent this?


In both cases the underlying problem is that v8 deoptimizes the functions, making them dog-slow. This may be fixable in and of itself, but I expect it would be a lot of work.


Thanks for any help...


Link to comment
Share on other sites

1. Thanks :)


If you don't mind feedback, this really feels like something that should be managed internally, if the performance affects most users. I know you're not fond of setters or but it's weird that the user should need to set a magic flag to get best performance in the default case, and then set another one after changing certain properties.


If it's just me that this is slow for, then never mind of course...

Link to comment
Share on other sites

I cannot set the checkOnlyOnce  = true by default because it will be incredibly hard to understand for most users that they need to call some functions to have their values updated.


Regarding setters, we could do it for all material properties (which will be painful and will increase the size of the framework) but what about others properties like fog for instance? this will mean that each time you change the fog, or enable mirrors or any other global properties (not mentioning light properties) that affect shaders you will have to go through all shaders to turn the flag on?


I don't think this is a good idea :)


If you are smart enough to think about checkOnlyOnce then I know that you won't be too much bothered to change one flag :)

Link to comment
Share on other sites

This is just feedback, I don't necessarily mean you should make huge changes now. But if there was an opportunity, like major update, using setters when necessary would definitely make the API better.


Look at it this way: in any API there are basically three kinds of settings:

  • Settings you can't change
  • Settings you can change any time
  • Settings you can change, but it causes side effects

In a perfect API, the first group should be getters, the second properties, and the third setters*. If those rules are followed then the API documents itself, and cannot be used incorrectly. But otherwise, you start to require users to think about internal implementation, which is precisely what abstraction layers are meant to prevent.


* (in practice of course this can mean real JS setters, or setValue() functions, or cached properties are checked each render. To the user it doesn't matter which)



Personal note: for my project I started out using three.js, but I found it annoying to memorize all the cases where you have to set special flags every time you update properties. The cleaner API is 100% of the reason I switched to BJS, that's why I feel strongly about it. ;)

Link to comment
Share on other sites

To be cleaner, I've just added material.resetCheckReadyOnlyOnceFlag()


What does it do, is it equivalent to three's needsUpdate?


If so you might consider giving it a generic name (like markForUpdate or markDirty), so that people who don't know what the readOnlyOnce flag is doing (i.e. me). Also if other places in the engine needed a similar functionality they could use a consistent name.

Link to comment
Share on other sites

I presume this flag do what you were previously doing by hand : set checkOnlyOnce = false, wait one render and set again checkOnlyOnce = true

So you can change your material, and simply call this update flag to be able to apply your changes but still with the checkOnlyOnce = true always active for performance.

Link to comment
Share on other sites

Ok for the rename it is better, you are right.


I'm absolutely agree with you that API has to be really SIMPLE. this is why the checkOnlyOnce is false by default. Turning it on will offer better performance but you will have to call markDirty (see, I use the new name already :)) to validate your changes


We cannot always have simpliity and performance. This specific case is a perfect example. If I want checkOnlyOnce = true AND no need to call markDirty then I will generate a really complex code to maintain because all entities controling values used by the shaders have to go through ALL materials each time you change a value to call markDirty.


So, in a general way, I completely agree with you and this is what I want to enforce everywhere in babylon.js but this case is an exception :(

Link to comment
Share on other sites

We cannot always have simpliity and performance. This specific case is a perfect example. If I want checkOnlyOnce = true AND no need to call markDirty then I will generate a really complex code to maintain because all entities controling values used by the shaders have to go through ALL materials each time you change a value to call markDirty.


Sorry, I'm not sure if my point has gotten across. Probably my language. ;)


The underlying issue here is that lots of material settings have side effects when changed, right? That is, BJS needs to know whether the user has done anything to invalidate the material. But it can't check this without doing a lot of work, so it assumes the material might be invalid, and implements several flags that let the user override this, but then you have to manually tell BJS when an invalidation happens. That's basically what's happening, right?


What I'm saying is, in general I think such issues occur when an API uses plain properties for settings that have side effects, and the issue can be avoided by converting properties to setters. In other words, any time an API says "when you change property X then you should set flag Y", instead it could just implement X as a setter that updates Y automatically.


With that said, three.js is a very mature API and it doesn't do this, so maybe there are considerations I'm overlooking. I don't think it can be performance. (I mean setters are a little slower than properties, but nobody is going to change a material's diffuseTexture thousands of times per frame.) Maybe it's browser support? IE6/8 might not support setters, I don't remember.



And again, this is just feedback! I'm not asking you to drop everything and change dozens of properties just so that I don't have to set one flag. :) But as a general design principle, I hope you can find ways to use setters to hide complexity, where possible.

Link to comment
Share on other sites

No I think it's me who is not clear :)


Let me try again: let's consider I'm adding setters everywhere to implement  X as a setter that updates Y automatically.


Now let's consider I'm doing that for texture.uScale for instance.


The pseudo code would be: 

texture.uScale = function(value) {

    texture._uscale = value;

    for each material in the scene: if the material uses this texture then mark it as dirty



So setting texture.uScale becomes now a complete scan of all materials in your scene. And this will be the same for, let's say, scene.fogEnable which will have to go through all materials as well.



So this is why I say performance penalties are too important to make this change. This is not related to performance hit due to the setter but due to what the setter has to do.

Link to comment
Share on other sites

Ahh.. so you're saying the cost here isn't inherent, it just arises because BJS doesn't keep a list of materials using a given texture, so you're asking the (advanced) user to know that instead, is that correct?


If so then I certainly see why that's a reasonable tradeoff, but the cost (to you) is that you're signing yourself up for more questions. ;)


Starting with this: your sample code suggests that changing uv scale/offsets should invalidate a material, but is that the case? In my scene right now I'm just setting every material's checkReadyOnlyOnce to true, all the time, and nothing seems to break - even though I dynamically change a lot of material parameters - uvscale/offset, emissiveColor, diffuseColor, and others.


What sorts of parameters do I need to be careful with?

Link to comment
Share on other sites

Absolutely and even keeping the list of materials using a texutre is not that easy because of custom materials that people can create. How can I force them to indicate to the texture that they are using it? this will require a massive rework I'm not ready to do


uv were an example to make it clearer (I should have used fog actually). The real issue with isReady is that this function compiles the right shader for the current option by defining "#define" to use inside the shader. This is done to provide the more efficient shader possible (for example if fog is off, the shader code for fog willl be removed hence better perf).


So if you go there: https://github.com/BabylonJS/Babylon.js/blob/master/src/Materials/babylon.standardMaterial.ts#L20, you're gonna see the options that can control the compilation of the shader:


public DIFFUSE = false;   public AMBIENT = false;   public OPACITY = false;   public OPACITYRGB = false;   public REFLECTION = false;   public EMISSIVE = false;   public SPECULAR = false;   public BUMP = false;   public SPECULAROVERALPHA = false;   public CLIPPLANE = false;   public ALPHATEST = false;   public ALPHAFROMDIFFUSE = false;   public POINTSIZE = false;   public FOG = false;   public LIGHT0 = false;   public LIGHT1 = false;   public LIGHT2 = false;   public LIGHT3 = false;   public SPOTLIGHT0 = false;   public SPOTLIGHT1 = false;   public SPOTLIGHT2 = false;   public SPOTLIGHT3 = false;   public HEMILIGHT0 = false;   public HEMILIGHT1 = false;   public HEMILIGHT2 = false;   public HEMILIGHT3 = false;   public POINTLIGHT0 = false;   public POINTLIGHT1 = false;   public POINTLIGHT2 = false;   public POINTLIGHT3 = false;   public DIRLIGHT0 = false;   public DIRLIGHT1 = false;   public DIRLIGHT2 = false;   public DIRLIGHT3 = false;   public SPECULARTERM = false;   public SHADOW0 = false;   public SHADOW1 = false;   public SHADOW2 = false;   public SHADOW3 = false;   public SHADOWS = false;   public SHADOWVSM0 = false;   public SHADOWVSM1 = false;   public SHADOWVSM2 = false;   public SHADOWVSM3 = false;   public SHADOWPCF0 = false;   public SHADOWPCF1 = false;   public SHADOWPCF2 = false;   public SHADOWPCF3 = false;   public DIFFUSEFRESNEL = false;   public OPACITYFRESNEL = false;   public REFLECTIONFRESNEL = false;   public EMISSIVEFRESNEL = false;   public FRESNEL = false;   public NORMAL = false;   public UV1 = false;   public UV2 = false;   public VERTEXCOLOR = false;   public VERTEXALPHA = false;   public NUM_BONE_INFLUENCERS = 0;   public BonesPerMesh = 0;   public INSTANCES = false;   public GLOSSINESS = false;   public ROUGHNESS = false;   public EMISSIVEASILLUMINATION = false;   public LINKEMISSIVEWITHDIFFUSE = false;   public REFLECTIONFRESNELFROMSPECULAR = false;   public LIGHTMAP = false;   public USELIGHTMAPASSHADOWMAP = false;   public REFLECTIONMAP_3D = false;   public REFLECTIONMAP_SPHERICAL = false;   public REFLECTIONMAP_PLANAR = false;   public REFLECTIONMAP_CUBIC = false;   public REFLECTIONMAP_PROJECTION = false;   public REFLECTIONMAP_SKYBOX = false;   public REFLECTIONMAP_EXPLICIT = false;   public REFLECTIONMAP_EQUIRECTANGULAR = false;   public INVERTCUBICMAP = false;


For example, if you set checkOnlyOnce =  true and you use your material with a mesh without bones and with a mesh with bones then it will NOT work.


Why? because the first mesh compiles the shader without bone and the second one will not compile a new version because of checkOnlyOnce.


This cannot be fixed even with setters unfortunately. Smart shaders are really powerful (If you want more detail on it, I wrote a chapter about them in this book) but the performance and memory gains come at the cost of adapting them per mesh and per frame.



Link to comment
Share on other sites

Ah, so changing the settings for a given feature doesn't invalidate the material, but adding and removing features does. That makes sense.


And again, I understand this is done for performance, it's just that in my scenes the isReady function was eventually taking >10% of the total processing time, at least in Chrome.


Thanks as always!

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.


  • Recently Browsing   0 members

    • No registered users viewing this page.
  • Create New...