I’ve been checking why my Android device runs out of memory since I migrated to URP and it was running fine using the built-in render pipeline… to find out that Unity shaders take a huuuuuuuge amount of memory on the device.
URP Simple Lit on android device compiled for opengl 3.0 is about 38 megas. To give you an idea, my terrain meshes, all game meshes and most of my textures combined… take less RAM than the URP Simple Lit shader. It is in fact the LARGEST item in memory for the whole game.
Oh, and I have all the built settings set up for stripping the shaders variants and all.
So what’s going on? That’s a single shader. If I start to combine shaders it gets crazy quick on mobile.
We found Unity’s shader stripping rather lackluster, rolling out our own reduced the size of shaders by more than a 10 fold.
What we do is enable shader compilation logging (its a player setting), and let our game run for a while. This will give you an exact list of what shader is used, with what variant, pass etc. Then using an IPreprocessShaders you can strip out the shaders that arent used. One downside is that your buildflow is now kinda wack, you need to make a clean unstripped build every time a new variant is included, to collect it.
For example without stripping we’ve even seen META passes get compiled into the game. This is especially bad on platforms with shared memory.
Yeah, I had to manually edit the shader and remove chunks of code to reduce it to about 3 mb. Still, 3mb for a shader is horrible, and this is URP which should be ok for mobile.
Answering the question about variants, I don’t need to check the log, I can see it compiling about 1400 variants when I build. With stripping enabled and the settings set for only 1 directional light and depth camera. On android OpenGL 3.0
To give you an idea, on built-in with exactly the same image quality and more settings enabled (I had to disable tons so that it would run at a decent fps on URP)… it takes less than 1/4 the memory of URP.
Definitely not happy with URP and its current state. But at least begin by reducing the RAM requirement… I can’t imagine the huge amount of unused code that must be there to generate 38 mb from a single shader on mobile. Isn’t this larger than the whole Unity engine compiled on mobile? Just one shader…
URP added a lot of features in 2021.2. Most of that should be handled by the stripping that is shipped with URP, though, so I’m surprised to see you mentioning it compiles 1.400 variants. Did you tweak URP settings? They drive the amount of variants that end up in the build.
create new urp project, empty. Settings for mobile android OpenGL 3.0. Set the usual mobile optimizations, but enable shadows and depth buffer. Also make sure to set shader variant stripping on. I just want to prove that the default settings without going into manually editing shaders are not right.
add a plain cube. Set material to Lit Simple… because it’s mobile and we want it light and fast.
add a basic terrain. As basic as you want. Any texture setup. Try to make it super small if you want to. Or large. It doesn’t matter for the shader as long as you have a texture there.
add a light and make shadows available for the project.
compile and run on the device. It should work fine. I won’t comment on the fps (it’s crap compared to built in doing the same). But let’s focus on the memory for now.
open the memory profiler and take a snapshot of the device. Do you think Unity is doing a good job with the shaders size? What results do you get? My device ram is consumed primarily by the shader. By a large amount.
This is a brilliant way to do the shader stripping. Will employ this idea for my game, thank you.
It’s especially great when you do this at the end cycle of the development, as so we know no more shader variants will be added afterward. For during development, I recommend just strip unused shaders instead of doing this procedure every build.
An older forum post by Christophe Riccio provides information on scriptable stripping:
The general idea is to strip shader variants based on keywords/combinations that are not needed at runtime.
We are currently working to provide up-to-date documentation.
@dnach all that is a given. Doing all that, the URP shader is larger in RAM than the whole unity engine. You can test it yourself, just create a terrain and paint it a bit, nothing else, disable everything you want. Enable stripping. Optimize the hell out of it. Build and run on android. Check the memory profiler. See all the junk loaded from the URP terrain shader and tell me that is normal and cannot be improved.
Repeat the process using built-in render pipeline. Use the terrain diffuse shader, not the PBR that nobody uses in mobile if they want performance and that it spilled over URP. Check the memory on the android device.
RAM in terrain shaders in URP: 180 mb.
RAM in terrain shader in built-in: 5mb
Don’t blame it on the user and how they set it up please. URP is so unoptimized and so memory hungry it’s insulting.
@creat327 We are currently working on immediate and longer term solutions to improve shader variant management as a whole, including reducing the runtime memory usage of shaders.
We understand this is a big pain, and are not taking it lightly!
Thanks, but at this point, fewer words and more actions. It’s version 14 of URP with countless versions in between. I’m sorry if I sound negative, but Unity made me like this.
Several immediate improvements will be released in beta soon, and will soon share more information on the expected release and when you’ll see backports.
Edit: you can find more detail on the above, as well as general guidelines and other useful information about shader variants logging and stripping, in the following post: https://discussions.unity.com/t/887170