I’m not sure if this is the right place to post this, but I’d like to share with you a little something I’ve been working on.
GitHub - hamish-milne/UnityShaderCompiler: Interface and experiments with Unity's shader compiler
What you can find here is a .net interface to UnityShaderCompiler.exe, the new ‘service process’ introduced in 4.5 to replace the old CGBatch.exe, which, while much slower, did have the ability to be used outside of the Unity editor. This interface allows the same functionality in newer versions, and the output, like its predecessor, can be used (I believe!) in the Material constructor to load the shader directly into the game at runtime.
Why exactly would you need something like this?
Since it uses .net, you could use it from within the editor, at runtime, or outside it altogether. You could, in theory, do things like:
- Creating and compiling shaders from your own code, rather than having to manually copy the files over
- Creating shaders programatically at runtime
- Direct access to all the input and output from the compiler, errors, stats etc.
- Manual optimizations of the compiled code
- Compile externally from another program, such as an IDE (this was what motivated me personally)
It’s niche, I’ll admit, and though I’ve found a few posts talking about offline compilation pre and post 4.5, I’m not sure if anyone outside of myself will find this useful. But hey, I’m a programmer, half of this was just for the fun of it ![]()
How did I manage it?
It was certainly tricky. There is zero documentation on the internal communication between Unity and the compiler. Finding the method wasn’t too tricky; the //./pipe/ prefix in the command (which I sniffed with procmon) was a dead giveaway: named pipes. It was easy enough to create a little interceptor tool in C#, which, with typical hacky elegance, we simply replace the original compiler executable with. The interceptor starts up its own named pipe server, connects to the real compiler and records all transactions to a log file.
Working out the message length was a bit of a pain. Essentially, every command sent either way ends in a newline, which is fine, but there’s no indication of how many lines to expect. So in fact, I had to reverse-engineer each command somewhat before I could know how many lines to read and write at each end. That code is in UnityShaderCompilerIntercept, and I’ve basically finished with it now.
Once I had a transcript, it was easy enough to work intuitively from there after compiling a variety of shaders on different platforms. Though, since I really have very little idea about how shaders work, some things still remain a mystery.
One example is ‘getPlatforms’. That command is run as soon as Unity starts up, and it returns 13 numbers. But the numbers it outputs seem to be the same on every system I’ve tried. I even made a tool to spoof the output (GetPlatformsTest) to see if changing them had any effect on Unity at all, but they did not. So I’m stumped. If anyone wants to help me out, compile and run the main project on your system and, if the output is different from “-3085, 0, 0, 1, 0, 2, 8, 1, 1, 2, 10, 0, 0” I’d be very interested to hear!
There are other ID numbers, values, semantics etc., particularly in the ‘bindings’ found before DirectX assembly, that I don’t really understand fully, and they’re all marked in the code.
Where to go from here?
Make a single function that takes some source code, and outputs the full compiled shader code for every platform. This shouldn’t take too long, as soon as I can properly parse the parameter that is used to determine what platforms are compatible.
Following that, I could see about making a full ‘spoofer’ so I can adjust some of the compiler’s output and see how Unity responds.
If you’re still reading, congrats! You’ve suffered through many paragraphs of rambling. If you know more than me about shaders (which you probably do) and want to help out, I could really use some assistance in deciphering various numbers.
Thanks for reading! I’ll stick around here for a while if anyone wants to know anything.
EDIT: Aha! Turns out the platform parameter is just a bitwise OR of all the available platforms, with the bits corresponding to the ID numbers I found earlier.