using System; using System.Collections.Generic; using System.Text; namespace UnityEngine.Rendering.RenderGraphModule.NativeRenderPassCompiler { internal partial class NativePassCompiler { static RenderGraph.DebugData.PassData.NRPInfo.NativeRenderPassInfo.AttachmentInfo MakeAttachmentInfo( CompilerContextData ctx, in NativePassData nativePass, int attachmentIndex) { var attachment = nativePass.attachments[attachmentIndex]; var pointTo = ctx.UnversionedResourceData(attachment.handle); LoadAudit loadAudit = nativePass.loadAudit[attachmentIndex]; string loadReason = LoadAudit.LoadReasonMessages[(int) loadAudit.reason]; if (loadAudit.passId >= 0) loadReason = loadReason.Replace("{pass}", $"{ctx.passNames[loadAudit.passId].name}"); StoreAudit storeAudit = nativePass.storeAudit[attachmentIndex]; string storeReason = StoreAudit.StoreReasonMessages[(int) storeAudit.reason]; if (storeAudit.passId >= 0) storeReason = storeReason.Replace("{pass}", $"{ctx.passNames[storeAudit.passId].name}"); string storeMsaaReason = string.Empty; if (storeAudit.msaaReason != StoreReason.InvalidReason && storeAudit.msaaReason != StoreReason.NoMSAABuffer) { storeMsaaReason = StoreAudit.StoreReasonMessages[(int) storeAudit.msaaReason]; if (storeAudit.msaaPassId >= 0) storeMsaaReason = storeMsaaReason.Replace("{pass}", $"{ctx.passNames[storeAudit.msaaPassId].name}"); } return new RenderGraph.DebugData.PassData.NRPInfo.NativeRenderPassInfo.AttachmentInfo { resourceName = pointTo.GetName(ctx, attachment.handle), attachmentIndex = attachmentIndex, loadReason = loadReason, storeReason = storeReason, storeMsaaReason = storeMsaaReason, attachment = attachment }; } internal static string MakePassBreakInfoMessage(CompilerContextData ctx, in NativePassData nativePass) { string msg = ""; if (nativePass.breakAudit.breakPass >= 0) { msg += $"Failed to merge {ctx.passNames[nativePass.breakAudit.breakPass].name} into this native pass.\n"; } msg += PassBreakAudit.BreakReasonMessages[(int) nativePass.breakAudit.reason]; return msg; } internal static string MakePassMergeMessage(CompilerContextData ctx, in PassData pass, in PassData prevPass, PassBreakAudit mergeResult) { string message = mergeResult.reason == PassBreakReason.Merged ? "The passes are compatible to be merged.\n\n" : "The passes are incompatible to be merged.\n\n"; string passName = InjectSpaces(pass.GetName(ctx).name); string prevPassName = InjectSpaces(prevPass.GetName(ctx).name); switch (mergeResult.reason) { case (PassBreakReason.Merged): if (pass.nativePassIndex == prevPass.nativePassIndex && pass.mergeState != PassMergeState.None) message += "Passes are merged."; else message += "Passes can be merged but are not recorded consecutively."; break; case PassBreakReason.TargetSizeMismatch: message += "The fragment attachments of the passes have different sizes or sample counts.\n" + $"- {prevPassName}: {prevPass.fragmentInfoWidth}x{prevPass.fragmentInfoHeight}, {prevPass.fragmentInfoSamples} sample(s).\n" + $"- {passName}: {pass.fragmentInfoWidth}x{pass.fragmentInfoHeight}, {pass.fragmentInfoSamples} sample(s)."; break; case PassBreakReason.NextPassReadsTexture: message += "The next pass reads one of the outputs as a regular texture, the pass needs to break."; break; case PassBreakReason.NonRasterPass: message += $"{prevPassName} is type {prevPass.type}. Only Raster passes can be merged."; break; case PassBreakReason.DifferentDepthTextures: message += $"{prevPassName} uses a different depth buffer than {passName}."; break; case PassBreakReason.AttachmentLimitReached: message += $"Merging the passes would use more than {FixedAttachmentArray.MaxAttachments} attachments."; break; case PassBreakReason.SubPassLimitReached: message += $"Merging the passes would use more than {k_MaxSubpass} native subpasses."; break; case PassBreakReason.EndOfGraph: message += "The pass is the last pass in the graph."; break; default: throw new ArgumentOutOfRangeException(); } return message; } static string InjectSpaces(string camelCaseString) { var bld = new StringBuilder(); for (var i = 0; i< camelCaseString.Length; i++) { if (char.IsUpper(camelCaseString[i]) && (i!=0 && char.IsLower(camelCaseString[i-1]) ) ) { bld.Append(" "); } bld.Append(camelCaseString[i]); } return bld.ToString(); } internal void GenerateNativeCompilerDebugData(ref RenderGraph.DebugData debugData) { ref var ctx = ref contextData; debugData.isNRPCompiler = true; // Resolve read/write lists per resource Dictionary<(RenderGraphResourceType, int), List> resourceReadLists = new Dictionary<(RenderGraphResourceType, int), List>(); Dictionary<(RenderGraphResourceType, int), List> resourceWriteLists = new Dictionary<(RenderGraphResourceType, int), List>(); foreach (var renderGraphPass in graph.m_RenderPasses) { for (int type = 0; type < (int)RenderGraphResourceType.Count; ++type) { int numResources = ctx.resources.unversionedData[type].Length; for (int resIndex = 0; resIndex < numResources; ++resIndex) { foreach (var read in renderGraphPass.resourceReadLists[type]) { if (renderGraphPass.implicitReadsList.Contains(read)) continue; // Implicit read - do not display them in RG Viewer if (read.type == (RenderGraphResourceType) type && read.index == resIndex) { var pair = ((RenderGraphResourceType) type, resIndex); if (!resourceReadLists.ContainsKey(pair)) resourceReadLists[pair] = new List(); resourceReadLists[pair].Add(renderGraphPass.index); } } foreach (var read in renderGraphPass.resourceWriteLists[type]) { if (read.type == (RenderGraphResourceType) type && read.index == resIndex) { var pair = ((RenderGraphResourceType) type, resIndex); if (!resourceWriteLists.ContainsKey(pair)) resourceWriteLists[pair] = new List(); resourceWriteLists[pair].Add(renderGraphPass.index); } } } } } // Create resource debug data for (int t = 0; t < (int)RenderGraphResourceType.Count; ++t) { var numResources = ctx.resources.unversionedData[t].Length; for (int i = 0; i < numResources; ++i) { ref var resourceUnversioned = ref ctx.resources.unversionedData[t].ElementAt(i); RenderGraph.DebugData.ResourceData debugResource = new RenderGraph.DebugData.ResourceData(); RenderGraphResourceType type = (RenderGraphResourceType) t; bool isNullResource = i == 0; if (!isNullResource) { string resourceName = ctx.resources.resourceNames[t][i].name; debugResource.name = !string.IsNullOrEmpty(resourceName) ? resourceName : "(unnamed)"; debugResource.imported = resourceUnversioned.isImported; } else { // The above functions will throw exceptions when used with the null argument so just use a dummy instead debugResource.name = ""; debugResource.imported = true; } var info = new RenderTargetInfo(); if (type == RenderGraphResourceType.Texture && !isNullResource) { var handle = new ResourceHandle(i, type, false); try { graph.m_ResourcesForDebugOnly.GetRenderTargetInfo(handle, out info); } catch (Exception) { } } debugResource.creationPassIndex = resourceUnversioned.firstUsePassID; debugResource.releasePassIndex = resourceUnversioned.lastUsePassID; debugResource.textureData = new RenderGraph.DebugData.TextureResourceData(); debugResource.textureData.width = resourceUnversioned.width; debugResource.textureData.height = resourceUnversioned.height; debugResource.textureData.depth = resourceUnversioned.volumeDepth; debugResource.textureData.samples = resourceUnversioned.msaaSamples; debugResource.textureData.format = info.format; debugResource.textureData.bindMS = resourceUnversioned.bindMS; debugResource.textureData.clearBuffer = resourceUnversioned.clear; debugResource.memoryless = resourceUnversioned.memoryLess; debugResource.consumerList = new List(); debugResource.producerList = new List(); if (resourceReadLists.ContainsKey(((RenderGraphResourceType) t, i))) debugResource.consumerList = resourceReadLists[((RenderGraphResourceType) t, i)]; if (resourceWriteLists.ContainsKey(((RenderGraphResourceType) t, i))) debugResource.producerList = resourceWriteLists[((RenderGraphResourceType) t, i)]; debugData.resourceLists[t].Add(debugResource); } } // Create pass debug data for (int passId = 0; passId < ctx.passData.Length; passId++) { var graphPass = graph.m_RenderPasses[passId]; ref var passData = ref ctx.passData.ElementAt(passId); string passName = passData.GetName(ctx).name; string passDisplayName = InjectSpaces(passName); RenderGraph.DebugData.PassData debugPass = new RenderGraph.DebugData.PassData(); debugPass.name = passDisplayName; debugPass.type = passData.type; debugPass.culled = passData.culled; debugPass.async = passData.asyncCompute; debugPass.nativeSubPassIndex = passData.nativeSubPassIndex; debugPass.generateDebugData = graphPass.generateDebugData; debugPass.resourceReadLists = new List[(int)RenderGraphResourceType.Count]; debugPass.resourceWriteLists = new List[(int)RenderGraphResourceType.Count]; RenderGraph.DebugData.s_PassScriptMetadata.TryGetValue(graphPass, out debugPass.scriptInfo); debugPass.syncFromPassIndex = -1; // TODO async compute support debugPass.syncToPassIndex = -1; // TODO async compute support debugPass.nrpInfo = new RenderGraph.DebugData.PassData.NRPInfo(); debugPass.nrpInfo.width = passData.fragmentInfoWidth; debugPass.nrpInfo.height = passData.fragmentInfoHeight; debugPass.nrpInfo.volumeDepth = passData.fragmentInfoVolumeDepth; debugPass.nrpInfo.samples = passData.fragmentInfoSamples; debugPass.nrpInfo.hasDepth = passData.fragmentInfoHasDepth; foreach (var setGlobal in graphPass.setGlobalsList) debugPass.nrpInfo.setGlobals.Add(setGlobal.Item1.handle.index); for (int type = 0; type < (int)RenderGraphResourceType.Count; ++type) { debugPass.resourceReadLists[type] = new List(); debugPass.resourceWriteLists[type] = new List(); foreach (var resRead in graphPass.resourceReadLists[type]) { if (graphPass.implicitReadsList.Contains(resRead)) continue; // Implicit read - do not display them in RG Viewer debugPass.resourceReadLists[type].Add(resRead.index); } foreach (var resWrite in graphPass.resourceWriteLists[type]) debugPass.resourceWriteLists[type].Add(resWrite.index); } foreach (var fragmentInput in passData.FragmentInputs(ctx)) { Debug.Assert(fragmentInput.resource.type == RenderGraphResourceType.Texture); debugPass.nrpInfo.textureFBFetchList.Add(fragmentInput.resource.index); } debugData.passList.Add(debugPass); } // Native pass info foreach (ref readonly var nativePassData in ctx.NativePasses) { List mergedPassIds = new List(); for (int graphPassId = nativePassData.firstGraphPass; graphPassId < nativePassData.lastGraphPass + 1; ++graphPassId) mergedPassIds.Add(graphPassId); if (nativePassData.numGraphPasses > 0) { var nativePassInfo = new RenderGraph.DebugData.PassData.NRPInfo.NativeRenderPassInfo(); nativePassInfo.passBreakReasoning = MakePassBreakInfoMessage(ctx, in nativePassData); nativePassInfo.attachmentInfos = new (); for (int a = 0; a < nativePassData.attachments.size; a++) nativePassInfo.attachmentInfos.Add(MakeAttachmentInfo(ctx, in nativePassData, a)); nativePassInfo.passCompatibility = new Dictionary(); nativePassInfo.mergedPassIds = mergedPassIds; for (int i = 0; i < mergedPassIds.Count; ++i) { var mergedPassId = mergedPassIds[i]; var debugPass = debugData.passList[mergedPassId]; debugPass.nrpInfo.nativePassInfo = nativePassInfo; debugData.passList[mergedPassId] = debugPass; } } } // Pass compatibility info for (int passIndex = 0; passIndex < ctx.passData.Length; passIndex++) { ref var pass = ref ctx.passData.ElementAt(passIndex); var nativePassInfo = debugData.passList[pass.passId].nrpInfo.nativePassInfo; if (nativePassInfo == null) continue; // Input dependencies foreach (ref readonly var input in pass.Inputs(ctx)) { ref var inputDataVersioned = ref ctx.VersionedResourceData(input.resource); if (inputDataVersioned.written) { var inputDependencyPass = ctx.passData[inputDataVersioned.writePassId]; PassBreakAudit mergeResult = inputDependencyPass.nativePassIndex >= 0 ? NativePassData.CanMerge(ctx, inputDependencyPass.nativePassIndex, pass.passId) : new PassBreakAudit(PassBreakReason.NonRasterPass, pass.passId); string mergeMessage = "This pass writes to a resource that is read by the currently selected pass.\n\n" + MakePassMergeMessage(ctx, pass, inputDependencyPass, mergeResult); nativePassInfo.passCompatibility.TryAdd(inputDependencyPass.passId, new RenderGraph.DebugData.PassData.NRPInfo.NativeRenderPassInfo.PassCompatibilityInfo { message = mergeMessage, isCompatible = mergeResult.reason == PassBreakReason.Merged }); } } // Output dependencies (only relevant if current pass is part of a native pass, and therefore candidate for merging) if (pass.nativePassIndex >= 0) { foreach (ref readonly var output in pass.Outputs(ctx)) { ref var outputDataUnversioned = ref ctx.UnversionedResourceData(output.resource); if (outputDataUnversioned.lastUsePassID != pass.passId) // Someone else is using this resource { ref var outputDataVersioned = ref ctx.VersionedResourceData(output.resource); var numReaders = outputDataVersioned.numReaders; for (var i = 0; i < numReaders; ++i) { var depIdx = ctx.resources.IndexReader(output.resource, i); ref var dep = ref ctx.resources.readerData[output.resource.iType].ElementAt(depIdx); var outputDependencyPass = ctx.passData[dep.passId]; PassBreakAudit mergeResult = NativePassData.CanMerge(ctx, pass.nativePassIndex, outputDependencyPass.passId); string mergeMessage = "This pass reads a resource that is written to by the currently selected pass.\n\n" + MakePassMergeMessage(ctx, outputDependencyPass, pass, mergeResult); nativePassInfo.passCompatibility.TryAdd(outputDependencyPass.passId, new RenderGraph.DebugData.PassData.NRPInfo.NativeRenderPassInfo.PassCompatibilityInfo { message = mergeMessage, isCompatible = mergeResult.reason == PassBreakReason.Merged }); } } } } } } } }