A game about forced loneliness, made by TACStudios
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}