Lags due to shader compilation have always been an annoyance because they are often quite noticeable and they happen especially often on a players first session which leaves a bad first impression.
So we as a developer want to hide those lags by prewarming our shaders in loading screens or in the background while streaming in new parts of a level. This never was Unitys strength. Up until recently we had Unity - Scripting API: ShaderVariantCollection.WarmUp which had multiple design issues:
ShaderVariantCollections could only be created in the Editor
You only had the choice between “Click this button and save everything” or “Add them by code but then you have to find out what to add on your own”
WarmUp is a synchronous API. You cannot prewarm a large collection asynchronously over multiple frames. This means it is unusable for background prewarming and even in loading screens you get unpleasant lag spikes
Both functions require a vertex attribute setup to work but it seems like there is no easy way to get this information. Sure I can loop over all renderers in my scene and get that information. But that will only work with static or objects that are in the level at startup. I could scan periodically while playing the level. But how often should I check? How can I assure that I get every object even if it is short lived without adding a script to all of them? How am I supposed to get that information from VisualEffects? Their renderer type is internal. Do I have to use reflection for that? And after all of that: Did I get everything? I don´t know because Unity will not tell me.
This needs a rework. My main requirements for a new API would be:
There should be no guesswork and manually collecting everything. Let us register a callback somewhere that gets called for each new shader compilation that provides all information we need to preload those shaders in the next build. Something like:
ShaderWarmup.RegisterShaderCompilationCallback(MyCallback);
private static void MyCallback(Shader shader, Experimental.Rendering.ShaderWarmupSetup setup) {
//We can save this information however we want
}
This API would be very flexible. We can collect everything in a big list. Or make one per level (This would require the possibility to somehow reset the system so the same shaders can trigger the callback multiple times so we can record them for multiple levels). We can add things later or remove them if we want to.
The callback should work at runtime. We want to create builds with this callback enabled so we can collect prewarm information while QA is testing these builds.
Prewarming should be an asynchronous API so we can avoid big lag spikes and use it in the background while playing.
Just bumping up this as I was just looking into it and very much agree with the original poster. The experimental API is near pointless as I see no sane way to gather all VertexAttributeDescriptors and map them to shaders. You’re already automatically gathering some of the info in editor, so you might as well help us go the rest of the way.
Not trying to be antagonistic, but are these priorities set by users? Having the game run slow for a long time is basically a deal-killer. Are there many more important priorities than not having the software closed because its infuriately slow?
Users’ opinions are definitely considered when prioritising features.
There are ways to do proper warmup manually. It’s just not a very nice thing to implement.
You could render each object in the scene manually positioning it right before the camera and hide that behind the loading screen. This would get all the necessary shader variants warmed up.
Thanks for your answer ! I double down on feedback for this one, especially regarding shader warmup considerations for VFXGraph.
From what I understand, Even though VFX Shaders are added to a variant collection, they will be loaded if the corresponding output context is rendered : How to preload shader in VFX graph?
@aleksandrk : I have made some testing using your manual warnup simple effects, but there are too many cornercases that could prevent particles from spawning, especially because of custom events, property interface or even other considerations that would make a VFX not working without its proper context in the scene.
However it seems that it still tackles a majority of “simple” effects.
@JulienF_Unity , @PaulDemeulenaere : I would really like to hear from VFX Graph team’s some advice regarding such prewarming if it is currently possible, especially how to handle ShaderVariantCollections with VFXGraph.
Is there some sample code with good practices available?
Unity should probably warn developers this is a thing they need to do when they select DirectX12, Metal or Vulkan for their games. Learn from Unreal’s mistakes and the dozens of high profile failures resulting from this very issue.
Epic also let it fester for years. Should be a higher priority, and not buried in some obscure part of the documentation.
Here’s what Unity could do:
Give us a script that collects shader variants during gameplay and allow us to plug that data into the warmup function.
Instead of halting the render when a shader variant is missing, skip over rendering it until compilation is done (Unreal 5.2 does this).
You propose to create loading screen and hide all meshes behind it before starting the game?
Im now working on optimizing RAM usage in my game for Android, I can easily tell you that this is not possible to just put everything into the memory and not get a crash on game bigger than hyper-casual. And also there is not only meshes but also particles. I noticed lag for them as well. First launch of the game was always a problem for my project, we even lost one publisher because of this issue game lags no matter what I do on first launch.
Question is why this is so tricky and complex to do? I think anything like that should be done on Unity-end and also async shader.warmupall would be really useful.
Please, don’t force peoples to do some tricky nightmare like that, this is not what we expect from game engine.
Instead of fixing and implementing a gazillion smaller features that would improve all Unity games, that most developers end up implementing themselves, (things that a good engine should handle for you, right? that’s their reason for existing. Imagine a kick-ass, best in class, shader warm-up system. Hitching because of shader loading becoming a weird thing that only lesser engines do and that other engines look up to Unity for how it should be done), instead they are busy splitting the render engine 3 ways and discovering DOD and thinking it’s pretty cool for a decade.
Meanwhile the highest bar the rest of their feature set can reach is “eh, I guess it mostly works”.
Unity just should add label “Experimental” on their main page right before anyone will install it. When anyone try to download Unity it should show him popup with next message: “This engine is experimental and not ready for production use. Also it can deprecate any moment. Download at your own risk.”
Frankly, I think this issue is more on modern graphics APIs since Unreal is still struggling with the same issue.
But Unity and Epic are members of Khronos, so at least they should be lobbying for a fix, right? (why has this taken 7 years wtf)
Here’s the situation: we can’t sell constant stutter, nor a long (longer than a few seconds) shader pre-compilation screen to consumers. Please fix. We’ll be using DX11 in the meantime.
The only thing we need is some examples. In HDRP “unwarmed” shaders cause constant stuttering. On DX11, the shader collection we have refers to 1000 variants for a single scene. We need to prewarm these on Vulkan for the Steam Deck.
I went ahead and logged all VertexAttributeDescriptor[ ] for meshes found in our scene and it’s something like 19 unique sets. So starting from there it should be fairly simple to get things to work, but it’s not…
How do we get VertexDescriptor from vfx graph? This should be really easy to answer, but difficult to guess. Unity must know what kind of mesh the VFX graph author?
I assume we should warm shaders using VertexAttributeDescriptor[ ] they are used on, right?
Overall, it would be great to simply have a way to save an asset containing pairs of VertexAttributeDescriptor[ ] and ShaderVariantCollections.