A game about forced loneliness, made by TACStudios
1using System;
2using System.Collections.Generic;
3using System.Text;
4
5namespace UnityEngine.Rendering.RenderGraphModule.NativeRenderPassCompiler
6{
7 internal partial class NativePassCompiler
8 {
9 static RenderGraph.DebugData.PassData.NRPInfo.NativeRenderPassInfo.AttachmentInfo MakeAttachmentInfo(
10 CompilerContextData ctx,
11 in NativePassData nativePass,
12 int attachmentIndex)
13 {
14 var attachment = nativePass.attachments[attachmentIndex];
15 var pointTo = ctx.UnversionedResourceData(attachment.handle);
16
17 LoadAudit loadAudit = nativePass.loadAudit[attachmentIndex];
18 string loadReason = LoadAudit.LoadReasonMessages[(int) loadAudit.reason];
19 if (loadAudit.passId >= 0)
20 loadReason = loadReason.Replace("{pass}", $"<b>{ctx.passNames[loadAudit.passId].name}</b>");
21
22 StoreAudit storeAudit = nativePass.storeAudit[attachmentIndex];
23 string storeReason = StoreAudit.StoreReasonMessages[(int) storeAudit.reason];
24 if (storeAudit.passId >= 0)
25 storeReason = storeReason.Replace("{pass}", $"<b>{ctx.passNames[storeAudit.passId].name}</b>");
26
27 string storeMsaaReason = string.Empty;
28 if (storeAudit.msaaReason != StoreReason.InvalidReason && storeAudit.msaaReason != StoreReason.NoMSAABuffer)
29 {
30 storeMsaaReason = StoreAudit.StoreReasonMessages[(int) storeAudit.msaaReason];
31 if (storeAudit.msaaPassId >= 0)
32 storeMsaaReason = storeMsaaReason.Replace("{pass}", $"<b>{ctx.passNames[storeAudit.msaaPassId].name}</b>");
33 }
34
35 return new RenderGraph.DebugData.PassData.NRPInfo.NativeRenderPassInfo.AttachmentInfo
36 {
37 resourceName = pointTo.GetName(ctx, attachment.handle),
38 attachmentIndex = attachmentIndex,
39 loadAction = attachment.loadAction.ToString(),
40 loadReason = loadReason,
41 storeAction = attachment.storeAction.ToString(),
42 storeReason = storeReason,
43 storeMsaaReason = storeMsaaReason
44 };
45 }
46
47 internal static string MakePassBreakInfoMessage(CompilerContextData ctx, in NativePassData nativePass)
48 {
49 string msg = "";
50 if (nativePass.breakAudit.breakPass >= 0)
51 {
52 msg += $"Failed to merge {ctx.passNames[nativePass.breakAudit.breakPass].name} into this native pass.\n";
53 }
54
55 msg += PassBreakAudit.BreakReasonMessages[(int) nativePass.breakAudit.reason];
56 return msg;
57 }
58
59 internal static string MakePassMergeMessage(CompilerContextData ctx, in PassData pass, in PassData prevPass, PassBreakAudit mergeResult)
60 {
61 string message = mergeResult.reason == PassBreakReason.Merged ?
62 "The passes are <b>compatible</b> to be merged.\n\n" :
63 "The passes are <b>incompatible</b> to be merged.\n\n";
64 string passName = InjectSpaces(pass.GetName(ctx).name);
65 string prevPassName = InjectSpaces(prevPass.GetName(ctx).name);
66 switch (mergeResult.reason)
67 {
68 case (PassBreakReason.Merged):
69 if (pass.nativePassIndex == prevPass.nativePassIndex && pass.mergeState != PassMergeState.None)
70 message += "Passes are merged.";
71 else
72 message += "Passes can be merged but are not recorded consecutively.";
73 break;
74 case PassBreakReason.TargetSizeMismatch:
75 message += "The fragment attachments of the passes have different sizes or sample counts.\n" +
76 $"- {prevPassName}: {prevPass.fragmentInfoWidth}x{prevPass.fragmentInfoHeight}, {prevPass.fragmentInfoSamples} sample(s).\n" +
77 $"- {passName}: {pass.fragmentInfoWidth}x{pass.fragmentInfoHeight}, {pass.fragmentInfoSamples} sample(s).";
78 break;
79 case PassBreakReason.NextPassReadsTexture:
80 message += "The next pass reads one of the outputs as a regular texture, the pass needs to break.";
81 break;
82 case PassBreakReason.NonRasterPass:
83 message += $"{prevPassName} is type {prevPass.type}. Only Raster passes can be merged.";
84 break;
85 case PassBreakReason.DifferentDepthTextures:
86 message += $"{prevPassName} uses a different depth buffer than {passName}.";
87 break;
88 case PassBreakReason.AttachmentLimitReached:
89 message += $"Merging the passes would use more than {FixedAttachmentArray<PassFragmentData>.MaxAttachments} attachments.";
90 break;
91 case PassBreakReason.SubPassLimitReached:
92 message += $"Merging the passes would use more than {k_MaxSubpass} native subpasses.";
93 break;
94 case PassBreakReason.EndOfGraph:
95 message += "The pass is the last pass in the graph.";
96 break;
97 default:
98 throw new ArgumentOutOfRangeException();
99 }
100
101 return message;
102 }
103
104 static string InjectSpaces(string camelCaseString)
105 {
106 var bld = new StringBuilder();
107
108 for (var i = 0; i< camelCaseString.Length; i++)
109 {
110 if (char.IsUpper(camelCaseString[i])
111 && (i!=0 && char.IsLower(camelCaseString[i-1]) ) )
112 {
113 bld.Append(" ");
114 }
115
116 bld.Append(camelCaseString[i]);
117 }
118 return bld.ToString();
119 }
120
121 internal void GenerateNativeCompilerDebugData(ref RenderGraph.DebugData debugData)
122 {
123 ref var ctx = ref contextData;
124
125 debugData.isNRPCompiler = true;
126
127 // Resolve read/write lists per resource
128 Dictionary<(RenderGraphResourceType, int), List<int>> resourceReadLists = new Dictionary<(RenderGraphResourceType, int), List<int>>();
129 Dictionary<(RenderGraphResourceType, int), List<int>> resourceWriteLists = new Dictionary<(RenderGraphResourceType, int), List<int>>();
130
131 foreach (var renderGraphPass in graph.m_RenderPasses)
132 {
133 for (int type = 0; type < (int)RenderGraphResourceType.Count; ++type)
134 {
135 int numResources = ctx.resources.unversionedData[type].Length;
136
137 for (int resIndex = 0; resIndex < numResources; ++resIndex)
138 {
139 foreach (var read in renderGraphPass.resourceReadLists[type])
140 {
141 if (renderGraphPass.implicitReadsList.Contains(read))
142 continue; // Implicit read - do not display them in RG Viewer
143
144 if (read.type == (RenderGraphResourceType) type && read.index == resIndex)
145 {
146 var pair = ((RenderGraphResourceType) type, resIndex);
147 if (!resourceReadLists.ContainsKey(pair))
148 resourceReadLists[pair] = new List<int>();
149 resourceReadLists[pair].Add(renderGraphPass.index);
150 }
151 }
152 foreach (var read in renderGraphPass.resourceWriteLists[type])
153 {
154 if (read.type == (RenderGraphResourceType) type && read.index == resIndex)
155 {
156 var pair = ((RenderGraphResourceType) type, resIndex);
157 if (!resourceWriteLists.ContainsKey(pair))
158 resourceWriteLists[pair] = new List<int>();
159 resourceWriteLists[pair].Add(renderGraphPass.index);
160 }
161 }
162 }
163 }
164 }
165
166 // Create resource debug data
167 for (int t = 0; t < (int)RenderGraphResourceType.Count; ++t)
168 {
169 var numResources = ctx.resources.unversionedData[t].Length;
170 for (int i = 0; i < numResources; ++i)
171 {
172 ref var resourceUnversioned = ref ctx.resources.unversionedData[t].ElementAt(i);
173 RenderGraph.DebugData.ResourceData debugResource = new RenderGraph.DebugData.ResourceData();
174 RenderGraphResourceType type = (RenderGraphResourceType) t;
175 bool isNullResource = i == 0;
176
177 if (!isNullResource)
178 {
179 string resourceName = ctx.resources.resourceNames[t][i].name;
180 debugResource.name = !string.IsNullOrEmpty(resourceName) ? resourceName : "(unnamed)";
181 debugResource.imported = resourceUnversioned.isImported;
182 }
183 else
184 {
185 // The above functions will throw exceptions when used with the null argument so just use a dummy instead
186 debugResource.name = "<null>";
187 debugResource.imported = true;
188 }
189
190 var info = new RenderTargetInfo();
191 if (type == RenderGraphResourceType.Texture && !isNullResource)
192 {
193 var handle = new ResourceHandle(i, type, false);
194 try
195 {
196 graph.m_ResourcesForDebugOnly.GetRenderTargetInfo(handle, out info);
197 }
198 catch (Exception) { }
199 }
200
201 debugResource.creationPassIndex = resourceUnversioned.firstUsePassID;
202 debugResource.releasePassIndex = resourceUnversioned.lastUsePassID;
203
204 debugResource.textureData = new RenderGraph.DebugData.TextureResourceData();
205 debugResource.textureData.width = resourceUnversioned.width;
206 debugResource.textureData.height = resourceUnversioned.height;
207 debugResource.textureData.depth = resourceUnversioned.volumeDepth;
208 debugResource.textureData.samples = resourceUnversioned.msaaSamples;
209 debugResource.textureData.format = info.format;
210 debugResource.memoryless = resourceUnversioned.memoryLess;
211
212 debugResource.consumerList = new List<int>();
213 debugResource.producerList = new List<int>();
214
215 if (resourceReadLists.ContainsKey(((RenderGraphResourceType) t, i)))
216 debugResource.consumerList = resourceReadLists[((RenderGraphResourceType) t, i)];
217
218 if (resourceWriteLists.ContainsKey(((RenderGraphResourceType) t, i)))
219 debugResource.producerList = resourceWriteLists[((RenderGraphResourceType) t, i)];
220
221 debugData.resourceLists[t].Add(debugResource);
222 }
223 }
224
225 // Create pass debug data
226 for (int passId = 0; passId < ctx.passData.Length; passId++)
227 {
228 var graphPass = graph.m_RenderPasses[passId];
229 ref var passData = ref ctx.passData.ElementAt(passId);
230 string passName = passData.GetName(ctx).name;
231 string passDisplayName = InjectSpaces(passName);
232
233 RenderGraph.DebugData.PassData debugPass = new RenderGraph.DebugData.PassData();
234 debugPass.name = passDisplayName;
235 debugPass.type = passData.type;
236 debugPass.culled = passData.culled;
237 debugPass.async = passData.asyncCompute;
238 debugPass.nativeSubPassIndex = passData.nativeSubPassIndex;
239 debugPass.generateDebugData = graphPass.generateDebugData;
240 debugPass.resourceReadLists = new List<int>[(int)RenderGraphResourceType.Count];
241 debugPass.resourceWriteLists = new List<int>[(int)RenderGraphResourceType.Count];
242
243 RenderGraph.DebugData.s_PassScriptMetadata.TryGetValue(passName, out debugPass.scriptInfo);
244
245 debugPass.syncFromPassIndex = -1; // TODO async compute support
246 debugPass.syncToPassIndex = -1; // TODO async compute support
247
248 debugPass.nrpInfo = new RenderGraph.DebugData.PassData.NRPInfo();
249
250 debugPass.nrpInfo.width = passData.fragmentInfoWidth;
251 debugPass.nrpInfo.height = passData.fragmentInfoHeight;
252 debugPass.nrpInfo.volumeDepth = passData.fragmentInfoVolumeDepth;
253 debugPass.nrpInfo.samples = passData.fragmentInfoSamples;
254 debugPass.nrpInfo.hasDepth = passData.fragmentInfoHasDepth;
255
256 foreach (var setGlobal in graphPass.setGlobalsList)
257 debugPass.nrpInfo.setGlobals.Add(setGlobal.Item1.handle.index);
258
259 for (int type = 0; type < (int)RenderGraphResourceType.Count; ++type)
260 {
261 debugPass.resourceReadLists[type] = new List<int>();
262 debugPass.resourceWriteLists[type] = new List<int>();
263
264 foreach (var resRead in graphPass.resourceReadLists[type])
265 {
266 if (graphPass.implicitReadsList.Contains(resRead))
267 continue; // Implicit read - do not display them in RG Viewer
268 debugPass.resourceReadLists[type].Add(resRead.index);
269 }
270
271 foreach (var resWrite in graphPass.resourceWriteLists[type])
272 debugPass.resourceWriteLists[type].Add(resWrite.index);
273 }
274
275 foreach (var fragmentInput in passData.FragmentInputs(ctx))
276 {
277 Debug.Assert(fragmentInput.resource.type == RenderGraphResourceType.Texture);
278 debugPass.nrpInfo.textureFBFetchList.Add(fragmentInput.resource.index);
279 }
280
281 debugData.passList.Add(debugPass);
282 }
283
284 // Native pass info
285 foreach (ref readonly var nativePassData in ctx.NativePasses)
286 {
287 List<int> mergedPassIds = new List<int>();
288 for (int graphPassId = nativePassData.firstGraphPass; graphPassId < nativePassData.lastGraphPass + 1; ++graphPassId)
289 mergedPassIds.Add(graphPassId);
290
291 if (nativePassData.numGraphPasses > 0)
292 {
293 var nativePassInfo = new RenderGraph.DebugData.PassData.NRPInfo.NativeRenderPassInfo();
294 nativePassInfo.passBreakReasoning = MakePassBreakInfoMessage(ctx, in nativePassData);
295 nativePassInfo.attachmentInfos = new ();
296 for (int a = 0; a < nativePassData.attachments.size; a++)
297 nativePassInfo.attachmentInfos.Add(MakeAttachmentInfo(ctx, in nativePassData, a));
298 nativePassInfo.passCompatibility = new Dictionary<int, RenderGraph.DebugData.PassData.NRPInfo.NativeRenderPassInfo.PassCompatibilityInfo>();
299 nativePassInfo.mergedPassIds = mergedPassIds;
300
301 for (int i = 0; i < mergedPassIds.Count; ++i)
302 {
303 var mergedPassId = mergedPassIds[i];
304 var debugPass = debugData.passList[mergedPassId];
305 debugPass.nrpInfo.nativePassInfo = nativePassInfo;
306 debugData.passList[mergedPassId] = debugPass;
307 }
308 }
309 }
310
311 // Pass compatibility info
312 for (int passIndex = 0; passIndex < ctx.passData.Length; passIndex++)
313 {
314 ref var pass = ref ctx.passData.ElementAt(passIndex);
315 var nativePassInfo = debugData.passList[pass.passId].nrpInfo.nativePassInfo;
316 if (nativePassInfo == null)
317 continue;
318
319 // Input dependencies
320 foreach (ref readonly var input in pass.Inputs(ctx))
321 {
322 ref var inputDataVersioned = ref ctx.VersionedResourceData(input.resource);
323 if (inputDataVersioned.written)
324 {
325 var inputDependencyPass = ctx.passData[inputDataVersioned.writePassId];
326 PassBreakAudit mergeResult = inputDependencyPass.nativePassIndex >= 0
327 ? NativePassData.CanMerge(ctx, inputDependencyPass.nativePassIndex, pass.passId)
328 : new PassBreakAudit(PassBreakReason.NonRasterPass, pass.passId);
329
330 string mergeMessage = "This pass writes to a resource that is read by the currently selected pass.\n\n"
331 + MakePassMergeMessage(ctx, pass, inputDependencyPass, mergeResult);
332 nativePassInfo.passCompatibility.TryAdd(inputDependencyPass.passId,
333 new RenderGraph.DebugData.PassData.NRPInfo.NativeRenderPassInfo.PassCompatibilityInfo
334 {
335 message = mergeMessage,
336 isCompatible = mergeResult.reason == PassBreakReason.Merged
337 });
338 }
339 }
340
341 // Output dependencies (only relevant if current pass is part of a native pass, and therefore candidate for merging)
342 if (pass.nativePassIndex >= 0)
343 {
344 foreach (ref readonly var output in pass.Outputs(ctx))
345 {
346 ref var outputDataUnversioned = ref ctx.UnversionedResourceData(output.resource);
347 if (outputDataUnversioned.lastUsePassID != pass.passId) // Someone else is using this resource
348 {
349 ref var outputDataVersioned = ref ctx.VersionedResourceData(output.resource);
350 var numReaders = outputDataVersioned.numReaders;
351 for (var i = 0; i < numReaders; ++i)
352 {
353 var depIdx = ResourcesData.IndexReader(output.resource, i);
354 ref var dep = ref ctx.resources.readerData[output.resource.iType].ElementAt(depIdx);
355
356 var outputDependencyPass = ctx.passData[dep.passId];
357 PassBreakAudit mergeResult = NativePassData.CanMerge(ctx, pass.nativePassIndex,
358 outputDependencyPass.passId);
359
360 string mergeMessage = "This pass reads a resource that is written to by the currently selected pass.\n\n"
361 + MakePassMergeMessage(ctx, outputDependencyPass, pass, mergeResult);
362 nativePassInfo.passCompatibility.TryAdd(outputDependencyPass.passId,
363 new RenderGraph.DebugData.PassData.NRPInfo.NativeRenderPassInfo.PassCompatibilityInfo
364 {
365 message = mergeMessage,
366 isCompatible = mergeResult.reason == PassBreakReason.Merged
367 });
368 }
369 }
370 }
371 }
372 }
373 }
374 }
375}