Compute shader being compiled for all graphics APIs after upgrade to 6.3

Hello!

I have recently upgraded from Unity 6.0 to 6.3.
It looks like Unity now compiles all kernels of a compute shader for all graphics APIs when clicking on “Show compiled code” (Direct3D11, Direct3D12, Metal, OpenGLCore, OpenGLES3, Vulkan, WebGPU), where previously the shader was only compiled for Direct3D12.
This causes an increase in compilation time from ~20s to ~8min for my current kernels.
The only reason I am using the show compiled code button is that this triggers parallel compilation of the compute shader.

This happens using Windows with Direct3D12 as the only enabled graphics API (but since 6.3 there are separate lists in the Player settings for Mac and Linux which cannot be empty and contain Metal and Vulkan).

I would like to be able to compile for my current platform only and I’m also interested in finding other ways to trigger parallel compilation of compute shaders. I am also having a hard time finding any documentation on this behavior.

Any help is highly appreciated!

Hi!
Which problem are you trying to solve by using this button? Kernel variants should be compiled in parallel most of the time even without that.

What I did in Unity 6.0: I have a single large compute shader with many kernels. When changing the shader and refreshing Unity with CTRL+R all kernels in the shader seem to be compiled sequentially which takes a total of ~120s (low CPU usage, compiled in background).
Pressing the “Show compiled code” button parallelizes the compilation and speeds up the process to ~20s (100% CPU usage).
There is another thread regarding this and a corresponding bug report that was closed because this seems to be the expected behavior so I did not investigate further.

The problem I am trying to resolve with this new topic is that after upgrading to Unity 6.3, shaders are now compiled for all existing graphics APIs when pressing that button.
(The list now always shows all APIs, but I believe it will only actually be compiled for all APIs when pressing the button, because only then do I get the errors shown in the second screenshot).

Screenshots (for testing, I used a kernel that will only work with DX11/12; both screenshots show a new 6.0/6.3 project with a single shader side-by-side):
(1) Before pressing the button. Unity 6.3 always shows all APIs for the kernel (but no errors shown yet, indicating compilation didn’t happen for all APIs?),
(2) After pressing the button. Only 6.3 shows the expected errors (and the compiled code being opened contains compiled code for all APIs) while 6.0 shows no errors (and compiled only for DX).

Basically what I would like to have is a way to compile the compute shader kernels in parallel and only for the currently active graphics API (so I can test faster in the editor), which is exactly what I was able to achieve by pressing the “show compiled code” button in the previous Unity version (6.0).

Notes:

  • There’s a “#pragma only_renderers d3d11” statement but this has multiple problems: There is no d3d12 value that can be used (setting d3d11 will compile both d3d11 and 12) and this statement needs to be updated manually when switching the graphics APIs / Platforms.
  • Because code is compiled for all APIs, even parallel compilation now takes much longer (~8minutes).
  • Because code is compiled for all APIs, there are errors (e.g. for OpenGL, which I do not want to support).

Thank you for the explanation!

When changing the shader and refreshing Unity with CTRL+R all kernels in the shader seem to be compiled sequentially

Are the kernels not used immediately? Background compilation is only used as a smoke test - a single kernel variant is compiled to check if there are obvious errors.
So, what problem are you trying to solve with pressing the “Show compiled code button”? Do you want to check the errors/warnings faster, or something else?

This is an intended behaviour change. The smoke test I mentioned above is only doing it for the current graphics API, though, so you won’t get errors from other APIs.

If you don’t want to support a graphics API (e.g. OpenGL), you can use #pragma exclude_renderers glcore. This will exclude desktop OpenGL.

The easiest way to do this is to actually use the kernels.

Thanks for your response! Yes, I basically want to check errors/warnings faster.

I have taken some time to summarize the current Unity behavior and to perform some tests regarding the compilation performance.
Please let me know if the summary below contains any mistakes and I will update this post.

Behavior:

  • Ctrl+R: Smoke test. Only single API. Compilation using only a single thread (slow, background).
    As intended, but it would be nice if one could trigger parallel compilation of these smoke tests.
  • Show compiled code (Unity 6.3): All APIs (even those not enabled in Player Settings). Many threads (slow because all APIs, foreground).
    Some control via “#pragma only_renderers” and “#pragma exclude_renderers” (no separate settings for DX11/DX12).
  • Show compiled code (Unity 6.0): Only single API. Many threads (fast, foreground).
  • Ctrl+R + Kernel calls: Kernels compiled when used (background).
    However, in order for them to be compiled in parallel, they have to be called in parallel as well. Dispatching them sequentially (e.g. in a simple loop) will compile and execute them sequentially (see Test 3).

Tests:

  • Test 1: Change shader → Ctrl+R → Wait for background compilation → 60s, 1 Thread → Execute kernels → <1s [total ~60s].
  • Test 2: Change shader → Ctrl+R → Show Compiled Code Button → 22s, Many Threads → Execute kernels → <1s [total ~22s].
    Can be brought down to ~10s by excluding all APIs except D3D12/D3D11 using “#pragma exclude_renderers glcore gles3 webgpu metal vulkan” (and might be even faster if it was possible to actually exclude 11 and 12 separately).
  • Test 3: Change shader → Ctrl+R → Execute each kernel (simple loop with .Dispatch over all kernels) → 40s, 1 Thread (not sure why this is slightly faster than Test 1, but it’s still very slow) [total ~40s].
  • Test 4: Change shader → Ctrl+R → Execute kernels using ExecuteCommandBuffer → 4s, Many Threads [total ~4s].

My use-case:

  • I usually refresh Unity Ctrl+R after making changes to the shader, wait for the compilation to finish to see if there are any errors and then start the game for testing.
    In case there are errors I simply fix them before even starting the game / calling the kernels.
  • Change a shader while in play mode. This is also one of my use cases but to me it always looked like a simple single-threaded background compilation, probably because I use sequential Dispatch calls (see Test 3).
  • I am not using any variants / keywords.

Open problems:

  • Calling kernels / Test 3 is still slow. What exactly happens when a kernel is dispatched? Is it compiled and then dispatched before returning? If this is the case, compilation would still happen sequentially, because the next Dispatch() is only called after the previous compilation finished, right? This would explain why Test 3 is still slow / sequential.
  • I’d like to achieve the speed and behavior of Test 4. Maybe I can add a button to the inspector that will “dummy-execute” all kernels with some dummy buffers using a CommandBuffer? But simply dispatching all kernels using empty buffers in some kind of dummy-execution is probably a bad idea, because some kernels might expect certain data and otherwise crash the device (e.g. by ending up in infinite loops).

The attached files contain the compute shader that I have used for testing and a simple MonoBehaviour for calling the kernels.

ShaderCompilationTest.cs (3.4 KB)

StandaloneShader_500_500.zip (151.5 KB)

I think you summarised this pretty well :slightly_smiling_face:
The .Dispatch call does synchronous kernel compilation, so it’s expected for it to be slow in your case.

Test 4 seems like the right way to do things.