Skip to content

Instantly share code, notes, and snippets.

@FreyaHolmer
Created August 17, 2024 00:24
Show Gist options
  • Save FreyaHolmer/62e076182204db5e85b850db0ac5e3bc to your computer and use it in GitHub Desktop.
Save FreyaHolmer/62e076182204db5e85b850db0ac5e3bc to your computer and use it in GitHub Desktop.
Cursed compute shader issue in unity. Plop Ashl onto a game object, assign the compute shader in the inspector, and watch the console. You might need to save a scene and restart unity to see the red log messages!
////////////////////////////////////////////////////////////
// Ashl.cs
using UnityEditor;
using UnityEngine;
[ExecuteAlways]
public class Ashl : MonoBehaviour { // add this to a game object
public ComputeShader cs; // assign the compute shader in the inspector
private static GraphicsBuffer gpuBuffer;
static uint frameid;
void OnEnable() => SceneView.duringSceneGui += SceneViewOnduringSceneGui;
void OnDisable() { SceneView.duringSceneGui -= SceneViewOnduringSceneGui;
gpuBuffer?.Dispose(); gpuBuffer = null;
}
void SceneViewOnduringSceneGui( SceneView obj ) {
// on every 20th GUI frame, run apply instead of preview
if( Event.current.type == EventType.Repaint )
Dispatch( frameid++ % 20 == 0 ? BoxAction.Apply : BoxAction.Preview );
SceneView.RepaintAll(); // <-- just to make it re-trigger repaint like an update loop
}
void Dispatch( BoxAction action ) {
gpuBuffer ??= new GraphicsBuffer( GraphicsBuffer.Target.Structured, 1024, 4 );
cs.SetBuffer( 0, "_gBufSelection", gpuBuffer );
cs.SetInt( "_BoxAction", (int)action );
cs.Dispatch( 0, 1024 / 64, 1, 1 ); // run the compute shader
if( action == BoxAction.Apply ) {
uint[] dataAll = new uint[gpuBuffer.count];
gpuBuffer.GetData( dataAll ); // downloads the data from the GPU
uint data = dataAll[0];
// the data SHOULD be 0xFFFFFFFF at this point
bool works = data == 0xFFFFFFFF;
string color = works ? "ffffffaa" : "ff2222ff";
string msg = works ? "works fine so far" : "BUG HAPPENED";
Debug.Log( $"<color=#{color}>{msg}: {data:X}</color>" );
}
}
enum BoxAction { Preview = 1, Apply = 2 }
}
////////////////////////////////////////////////////////////
// shader.compute
#pragma kernel CSMain
#pragma exclude_renderers d3d11_9x
#pragma exclude_renderers d3d9
uint _BoxAction;
RWStructuredBuffer<uint> _gBufSelection;
#define BOX_APPLY 2
[numthreads(64,1,1)]
void CSMain(const uint chunkId : SV_DispatchThreadID) {
uint dims, stride;
_gBufSelection.GetDimensions(dims, stride);
if (chunkId >= dims)
return;
if (chunkId < 10) {
if (_BoxAction == BOX_APPLY)
_gBufSelection[chunkId] = 0xFFFFFFFF;
else
_gBufSelection[chunkId] = 0xAAAAAAAA;
} else
_gBufSelection[chunkId] = 0;
}
@FreyaHolmer
Copy link
Author

this is a compute shader representing a box selection, which can either just set bits for preview/hover purposes, or, actually apply the selection (sets selection bits), which is then downloaded to the CPU

the bug is that, the dispatch where _BoxAction is supposed to be 2 (apply), it's executed with a value of 1 (preview), before it's downloaded to the CPU

weird workarounds that magically make it work:

  • waiting long enough in unity (30 seconds or so)
  • attaching nvidia nsight
  • being in play mode (..mostly)
  • dispatching a different, empty kernel, directly after the dispatch
  • storing _BoxAction in a compute buffer instead of a property
  • Instantiating the compute shader, and using the clone for the apply variant, and the original on the preview variant

@aras-p
Copy link

aras-p commented Aug 18, 2024

My testing:

  • Unity 5.6.7: does not finish trying to activate the license lol
  • Unity 2017.4.40: works fine (the script does not compile out of the box since 2017 lacks some of the APIs; had to change ExecuteAlways->ExecuteInEditMode, GraphicsBuffer->ComputeBuffer, duringSceneGui->onSceneGUIDelegate, and remove some of more modern C# features).
  • All versions starting with Unity 2018.4: have the bug.

So my guess is that yes, this is an actual Unity bug that started happening in 2018, probably because of some sort of DX11 specific optimization that tries to reuse constant buffers or whatever, and occasionally fails.

All the replies on the internet suggesting that "GetData probably needs some sort of flush call from you, and/or you should use async readback" are mostly cargo culting. This seems very much just like a bug within Unity.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment