A game about forced loneliness, made by TACStudios
at master 430 lines 17 kB view raw
1using System; 2using System.Collections.Generic; 3using Unity.Collections; 4using Unity.Collections.LowLevel.Unsafe; 5using UnityEngine.Assertions; 6 7namespace UnityEngine.Rendering 8{ 9 /// <summary> 10 /// Internal development tool. 11 /// Manages gpu-buffers for shader debug printing. 12 /// </summary> 13 14 public sealed class ShaderDebugPrintManager 15 { 16 private static readonly ShaderDebugPrintManager s_Instance = new ShaderDebugPrintManager(); 17 18 private const int k_FramesInFlight = 4; 19 private const int k_MaxBufferElements = 1024 * 16; // Must match the shader size definition 20 21 private List<GraphicsBuffer> m_OutputBuffers = new List<GraphicsBuffer>(); 22 23 private List<Rendering.AsyncGPUReadbackRequest> m_ReadbackRequests = 24 new List<Rendering.AsyncGPUReadbackRequest>(); 25 26 // Cache Action to avoid delegate allocation 27 private Action<AsyncGPUReadbackRequest> m_BufferReadCompleteAction; 28 29 private int m_FrameCounter = 0; 30 private bool m_FrameCleared = false; 31 32 private string m_OutputLine = ""; 33 private Action<string> m_OutputAction; 34 35 private static readonly int m_ShaderPropertyIDInputMouse = Shader.PropertyToID("_ShaderDebugPrintInputMouse"); 36 private static readonly int m_ShaderPropertyIDInputFrame = Shader.PropertyToID("_ShaderDebugPrintInputFrame"); 37 private static readonly int m_shaderDebugOutputData = Shader.PropertyToID("shaderDebugOutputData"); 38 39 // A static "container" for all profiler markers. 40 private static class Profiling 41 { 42 // Uses nameof to avoid aliasing 43 public static readonly ProfilingSampler BufferReadComplete = new ProfilingSampler($"{nameof(ShaderDebugPrintManager)}.{nameof(BufferReadComplete)}"); 44 } 45 46 // Should match: com.unity.render-pipelines.core/ShaderLibrary/ShaderDebugPrint.hlsl 47 enum DebugValueType 48 { 49 TypeUint = 1, 50 TypeInt = 2, 51 TypeFloat = 3, 52 TypeUint2 = 4, 53 TypeInt2 = 5, 54 TypeFloat2 = 6, 55 TypeUint3 = 7, 56 TypeInt3 = 8, 57 TypeFloat3 = 9, 58 TypeUint4 = 10, 59 TypeInt4 = 11, 60 TypeFloat4 = 12, 61 TypeBool = 13, 62 }; 63 private const uint k_TypeHasTag = 128; 64 65 private int DebugValueTypeToElemSize(DebugValueType type) 66 { 67 switch (type) 68 { 69 case DebugValueType.TypeUint: 70 case DebugValueType.TypeInt: 71 case DebugValueType.TypeFloat: 72 case DebugValueType.TypeBool: 73 return 1; 74 case DebugValueType.TypeUint2: 75 case DebugValueType.TypeInt2: 76 case DebugValueType.TypeFloat2: 77 return 2; 78 case DebugValueType.TypeUint3: 79 case DebugValueType.TypeInt3: 80 case DebugValueType.TypeFloat3: 81 return 3; 82 case DebugValueType.TypeUint4: 83 case DebugValueType.TypeInt4: 84 case DebugValueType.TypeFloat4: 85 return 4; 86 default: 87 return 0; 88 } 89 } 90 91 private ShaderDebugPrintManager() 92 { 93 for (int i = 0; i < k_FramesInFlight; i++) 94 { 95 m_OutputBuffers.Add(new GraphicsBuffer(GraphicsBuffer.Target.Structured, k_MaxBufferElements, 4)); 96 m_ReadbackRequests.Add(new Rendering.AsyncGPUReadbackRequest()); 97 } 98 99 m_BufferReadCompleteAction = BufferReadComplete; 100 m_OutputAction = DefaultOutput; 101 } 102 103 /// <summary> 104 /// Get the current instance. 105 /// </summary> 106 public static ShaderDebugPrintManager instance => s_Instance; 107 108 /// <summary> 109 /// Set shader input constants. 110 /// </summary> 111 /// <param name="cmd">CommandBuffer to store the commands.</param> 112 /// <param name="input">Input parameters for the constants.</param> 113 public void SetShaderDebugPrintInputConstants(CommandBuffer cmd, ShaderDebugPrintInput input) 114 { 115 var mouse = new Vector4(input.pos.x, input.pos.y, input.leftDown ? 1 : 0, input.rightDown ? 1 : 0); 116 cmd.SetGlobalVector(m_ShaderPropertyIDInputMouse, mouse); 117 cmd.SetGlobalInt(m_ShaderPropertyIDInputFrame, m_FrameCounter); 118 } 119 120 /// <summary> 121 /// Binds the gpu-buffers for current frame. 122 /// </summary> 123 /// <param name="cmd">CommandBuffer to store the commands.</param> 124 public void SetShaderDebugPrintBindings(CommandBuffer cmd) 125 { 126 int index = m_FrameCounter % k_FramesInFlight; 127 if (!m_ReadbackRequests[index].done) 128 { 129 // We shouldn't end up here too often 130 m_ReadbackRequests[index].WaitForCompletion(); 131 } 132 133 cmd.SetGlobalBuffer(m_shaderDebugOutputData, m_OutputBuffers[index]); 134 135 ClearShaderDebugPrintBuffer(); 136 } 137 138 private void ClearShaderDebugPrintBuffer() 139 { 140 // Only clear the buffer the first time this is called in each frame 141 if (!m_FrameCleared) 142 { 143 int index = m_FrameCounter % k_FramesInFlight; 144 NativeArray<uint> data = new NativeArray<uint>(1, Allocator.Temp); 145 data[0] = 0; 146 m_OutputBuffers[index].SetData(data, 0, 0, 1); 147 m_FrameCleared = true; 148 } 149 } 150 151 private void BufferReadComplete(Rendering.AsyncGPUReadbackRequest request) 152 { 153 using var profScope = new ProfilingScope(Profiling.BufferReadComplete); 154 155 Assert.IsTrue(request.done); 156 157 if (!request.hasError) 158 { 159 NativeArray<uint> data = request.GetData<uint>(0); 160 161 uint count = data[0]; 162 163 if (count >= k_MaxBufferElements) 164 { 165 count = k_MaxBufferElements; 166 // Shader print buffer is full, some data is lost! 167 Debug.LogWarning("Debug Shader Print Buffer Full!"); 168 } 169 170 string newOutputLine = ""; 171 if (count > 0) 172 newOutputLine += "Frame #" + m_FrameCounter + ": "; 173 174 unsafe // Need to do ugly casts via pointers 175 { 176 uint* ptr = (uint*)data.GetUnsafePtr(); 177 for (int i = 1; i < count;) 178 { 179 DebugValueType type = (DebugValueType)(data[i] & 0x0f); 180 bool hasTag = (data[i] & k_TypeHasTag) == k_TypeHasTag; 181 182 // ensure elem for tag after the header 183 if (hasTag && i + 1 < count) 184 { 185 uint tagEncoded = data[i + 1]; 186 i++; 187 188 for (int j = 0; j < 4; j++) 189 { 190 char c = (char)(tagEncoded & 255); 191 // skip '\0', for low-level output (avoid string termination) 192 if (c == 0) 193 continue; 194 newOutputLine += c; 195 tagEncoded >>= 8; 196 } 197 198 newOutputLine += " "; 199 } 200 201 // ensure elem for payload after the header/tag 202 int elemSize = DebugValueTypeToElemSize(type); 203 if (i + elemSize > count) 204 break; 205 206 i++; // [i] == payload 207 208 switch (type) 209 { 210 case DebugValueType.TypeUint: 211 { 212 newOutputLine += $"{data[i]}u"; 213 break; 214 } 215 case DebugValueType.TypeInt: 216 { 217 int valueInt = *(int*)&ptr[i]; 218 newOutputLine += valueInt; 219 break; 220 } 221 case DebugValueType.TypeFloat: 222 { 223 float valueFloat = *(float*)&ptr[i]; 224 newOutputLine += $"{valueFloat}f"; 225 break; 226 } 227 case DebugValueType.TypeUint2: 228 { 229 uint* valueUint2 = &ptr[i]; 230 newOutputLine += $"uint2({valueUint2[0]}, {valueUint2[1]})"; 231 break; 232 } 233 case DebugValueType.TypeInt2: 234 { 235 int* valueInt2 = (int*)&ptr[i]; 236 newOutputLine += $"int2({valueInt2[0]}, {valueInt2[1]})"; 237 break; 238 } 239 case DebugValueType.TypeFloat2: 240 { 241 float* valueFloat2 = (float*)&ptr[i]; 242 newOutputLine += $"float2({valueFloat2[0]}, {valueFloat2[1]})"; 243 break; 244 } 245 case DebugValueType.TypeUint3: 246 { 247 uint* valueUint3 = &ptr[i]; 248 newOutputLine += $"uint3({valueUint3[0]}, {valueUint3[1]}, {valueUint3[2]})"; 249 break; 250 } 251 case DebugValueType.TypeInt3: 252 { 253 int* valueInt3 = (int*)&ptr[i]; 254 newOutputLine += $"int3({valueInt3[0]}, {valueInt3[1]}, {valueInt3[2]})"; 255 break; 256 } 257 case DebugValueType.TypeFloat3: 258 { 259 float* valueFloat3 = (float*)&ptr[i]; 260 newOutputLine += $"float3({valueFloat3[0]}, {valueFloat3[1]}, {valueFloat3[2]})"; 261 break; 262 } 263 case DebugValueType.TypeUint4: 264 { 265 uint* valueUint4 = &ptr[i]; 266 newOutputLine += $"uint4({valueUint4[0]}, {valueUint4[1]}, {valueUint4[2]}, {valueUint4[3]})"; 267 break; 268 } 269 case DebugValueType.TypeInt4: 270 { 271 int* valueInt4 = (int*)&ptr[i]; 272 newOutputLine += $"int4({valueInt4[0]}, {valueInt4[1]}, {valueInt4[2]}, {valueInt4[3]})"; 273 break; 274 } 275 case DebugValueType.TypeFloat4: 276 { 277 float* valueFloat4 = (float*)&ptr[i]; 278 newOutputLine += $"float4({valueFloat4[0]}, {valueFloat4[1]}, {valueFloat4[2]}, {valueFloat4[3]})"; 279 break; 280 } 281 case DebugValueType.TypeBool: 282 { 283 newOutputLine += ((data[i] == 0) ? "False" : "True"); 284 break; 285 } 286 default: 287 i = (int)count; // Cannot handle the rest if there is an unknown type 288 break; 289 } 290 291 i += elemSize; 292 293 newOutputLine += " "; 294 } 295 } 296 297 if (count > 0) 298 { 299 m_OutputLine = newOutputLine; 300 m_OutputAction(newOutputLine); 301 } 302 } 303 else 304 { 305 const string errorMsg = "Error at read back!"; 306 m_OutputLine = errorMsg; 307 m_OutputAction(errorMsg); 308 } 309 } 310 311 /// <summary> 312 /// Initiate async read-back of the GPU buffer to the CPU. 313 /// Prepare a new GPU buffer for the next frame. 314 /// </summary> 315 public void EndFrame() 316 { 317 int index = m_FrameCounter % k_FramesInFlight; 318 m_ReadbackRequests[index] = Rendering.AsyncGPUReadback.Request(m_OutputBuffers[index], m_BufferReadCompleteAction); 319 320 m_FrameCounter++; 321 m_FrameCleared = false; 322 } 323 324 /// <summary> 325 /// Initiate synchronous read-back of the GPU buffer to the CPU and executes output action. By default prints to the debug log. 326 /// </summary> 327 public void PrintImmediate() 328 { 329 int index = m_FrameCounter % k_FramesInFlight; 330 var request = Rendering.AsyncGPUReadback.Request(m_OutputBuffers[index]); 331 request.WaitForCompletion(); 332 m_BufferReadCompleteAction(request); 333 334 m_FrameCounter++; 335 m_FrameCleared = false; 336 } 337 338 // Custom output API 339 340 /// <summary> 341 /// Get current print line. 342 /// </summary> 343 public string outputLine { get => m_OutputLine; } 344 345 /// <summary> 346 /// Action taken for each print line. By default prints to the debug log. 347 /// </summary> 348 public Action<string> outputAction { set => m_OutputAction = value; } 349 350 /// <summary> 351 /// The default output action. Print to the debug log. 352 /// </summary> 353 /// <param name="line">Line to be printed.</param> 354 public void DefaultOutput(string line) 355 { 356 Debug.Log(line); 357 } 358 } 359 360 /// <summary> 361 /// Shader constant input parameters. 362 /// </summary> 363 public struct ShaderDebugPrintInput 364 { 365 // Mouse input for the shader 366 367 /// <summary> 368 /// Mouse position. 369 /// GameView bottom-left == (0,0) top-right == (surface.width, surface.height) where surface == game display surface/rendertarget 370 /// For screen pixel coordinates, game-view should be set to "Free Aspect". 371 /// Works only in PlayMode. 372 /// </summary> 373 public Vector2 pos { get; set; } 374 375 /// <summary> 376 /// Left mouse button is pressed. 377 /// </summary> 378 public bool leftDown { get; set; } 379 380 /// <summary> 381 /// Right mouse button is pressed. 382 /// </summary> 383 public bool rightDown { get; set; } 384 385 /// <summary> 386 /// Middle mouse button is pressed. 387 /// </summary> 388 public bool middleDown { get; set; } 389 390 /// <summary> 391 /// Pretty print parameters for debug purposes. 392 /// </summary> 393 /// <returns>A string containing debug information</returns> 394 // NOTE: Separate from ToString on purpose. 395 public string String() 396 { 397 return $"Mouse: {pos.x}x{pos.y} Btns: Left:{leftDown} Right:{rightDown} Middle:{middleDown} "; 398 } 399 } 400 401 /// <summary> 402 /// Reads system input to produce ShaderDebugPrintInput parameters. 403 /// </summary> 404 public static class ShaderDebugPrintInputProducer 405 { 406 /// <summary> 407 /// Read system input. 408 /// </summary> 409 /// <returns>Input parameters for ShaderDebugPrintManager.</returns> 410 static public ShaderDebugPrintInput Get() 411 { 412 var r = new ShaderDebugPrintInput(); 413#if ENABLE_LEGACY_INPUT_MANAGER 414 r.pos = Input.mousePosition; 415 r.leftDown = Input.GetMouseButton(0); 416 r.rightDown = Input.GetMouseButton(1); 417 r.middleDown = Input.GetMouseButton(2); 418#endif 419#if ENABLE_INPUT_SYSTEM && ENABLE_INPUT_SYSTEM_PACKAGE 420 // NOTE: needs Unity.InputSystem asmdef reference. 421 var mouse = InputSystem.Mouse.current; 422 r.pos = mouse.position.ReadValue(); 423 r.leftDown = mouse.leftButton.isPressed; 424 r.rightDown = mouse.rightButton.isPressed; 425 r.middleDown = mouse.middleButton.isPressed; 426#endif 427 return r; 428 } 429 } 430}