using System; using System.Collections; using System.Collections.Generic; using System.Text.RegularExpressions; using NUnit.Framework; using UnityEngine.Rendering.RenderGraphModule; using UnityEngine.TestTools; namespace UnityEngine.Rendering.Universal.Tests { [TestFixture] class RenderGraphConstraintsTests { RenderPipelineAsset m_PreviousQualityRenderPipelineAsset; Type m_PreviousSessionType; UniversalRenderPipelineAsset m_UniversalRenderPipelineAsset; UniversalRendererData m_UniversalRendererData; OnTileValidationConfiguration m_DefaultOnTileValidationConfiguration; EntityId m_CameraEntityId; static UniversalAdditionalCameraData s_AdditionalCameraData; static PostProcessData s_PostProcessData; static HashSet s_RenderingModes; readonly HashSet m_ResourceTestNames = new() { "_CameraTargetAttachmentA", "_CameraTargetAttachmentB", "_CameraTargetAttachment", "_CameraDepthAttachment", "_CameraDepthTexture", "_CameraOpaqueTexture", "_CameraNormalsTexture", "_MotionVectorTexture", "_MotionVectorDepthTexture", "_CameraRenderingLayersTexture", "_GBuffer0", "_GBuffer1", "_GBuffer2", "_GBuffer3", "_GBuffer4", "_GBuffer5", "_GBuffer6", "_DBufferTexture0", "_DBufferTexture1", "_DBufferTexture2", "_ScreenSpaceShadowmapTexture", "_CameraColorAfterPostProcessing", "_CameraColorFullScreenPass", }; [OneTimeSetUp] public void OneTimeSetUp() { if (GraphicsSettings.currentRenderPipeline is not UniversalRenderPipelineAsset) Assert.Ignore($"This test will be only executed when URP is the current pipeline"); #if UNITY_EDITOR if (UnityEditor.EditorWindow.HasOpenInstances()) UnityEditor.EditorWindow.GetWindow().Close(); #endif m_PreviousSessionType = RenderGraphDebugSession.currentDebugSession?.GetType(); m_PreviousQualityRenderPipelineAsset = QualitySettings.renderPipeline; RenderGraphDebugSession.Create(); // Create new URP asset m_UniversalRenderPipelineAsset = Create(); QualitySettings.renderPipeline = m_UniversalRenderPipelineAsset; Assume.That(m_UniversalRenderPipelineAsset.rendererDataList != null && m_UniversalRenderPipelineAsset.rendererDataList.Length > 0); // Get URP Renderer Data m_UniversalRendererData = m_UniversalRenderPipelineAsset.rendererDataList[0] as UniversalRendererData; Assume.That(m_UniversalRendererData != null); m_DefaultOnTileValidationConfiguration = new OnTileValidationConfiguration(); var gameObject = new GameObject(); var camera = gameObject.AddComponent(); m_CameraEntityId = camera.GetEntityId(); s_AdditionalCameraData = gameObject.AddComponent(); s_AdditionalCameraData.renderPostProcessing = true; s_AdditionalCameraData.SetRenderer(0); } [UnitySetUp] public IEnumerator SetUp() { m_DefaultOnTileValidationConfiguration.ApplyToAssetAndRenderer(m_UniversalRenderPipelineAsset, m_UniversalRendererData); yield return null; if (!RenderGraphDebugSession.GetRegisteredGraphs().Contains("URPRenderGraph")) Assert.Ignore("Ignore this test as we couldn't setup debug session for URPRenderGraph."); } UniversalRenderPipelineAsset Create() { // Create Universal RP Asset var instance = ScriptableObject.CreateInstance(); // Initialize default renderer data instance.m_RendererDataList[0] = ScriptableObject.CreateInstance(); // Setup PP from existing urp asset s_RenderingModes = new HashSet(); ProcessRenderPipelineAsset(GraphicsSettings.defaultRenderPipeline, instance); for (int i = 0; i < QualitySettings.count; i++) ProcessRenderPipelineAsset(QualitySettings.GetRenderPipelineAssetAt(i), instance); return instance; } static void ProcessRenderPipelineAsset(RenderPipelineAsset rpAsset, UniversalRenderPipelineAsset instance) { if (rpAsset == null || rpAsset is not UniversalRenderPipelineAsset urpAsset) return; foreach (var existingRendererData in urpAsset.rendererDataList) { if (existingRendererData == null || existingRendererData is not UniversalRendererData urpRendererData) continue; if (urpRendererData.postProcessData != null && s_PostProcessData == null) { s_PostProcessData = urpRendererData.postProcessData; ((UniversalRendererData)instance.m_RendererDataList[0]).postProcessData = urpRendererData.postProcessData; } s_RenderingModes.Add(urpRendererData.renderingMode); } } [OneTimeTearDown] public void OneTimeTearDown() { if (m_PreviousSessionType != null) RenderGraphDebugSession.Create(m_PreviousSessionType); else RenderGraphDebugSession.EndSession(); QualitySettings.renderPipeline = m_PreviousQualityRenderPipelineAsset; Object.DestroyImmediate(m_UniversalRenderPipelineAsset); Object.DestroyImmediate(m_UniversalRendererData); Object.DestroyImmediate(s_AdditionalCameraData.gameObject); } public class OnTileValidationConfiguration { public bool supportsCameraOpaqueTexture { get; set; } = false; public bool supportsCameraDepthTexture { get; set; } = false; public bool supportsHDR { get; set; } = false; public int msaaSampleCount { get; set; } = 1; public RenderingMode renderingMode { get; set; } = RenderingMode.Forward; public bool occlusionCulling { get; set; } = false; public float renderScale { get; set; } = 1.0f; public bool postProcessingEnabled { get; set; } = false; public bool tileOnlyMode { get; set; } = false; public List rendererFeatures { get; set; } = new(); bool TryApplyToAssetAndRenderer(UniversalRenderPipelineAsset asset, UniversalRendererData rendererData) { // Apply asset settings asset.supportsCameraDepthTexture = supportsCameraDepthTexture; asset.supportsCameraOpaqueTexture = supportsCameraOpaqueTexture; asset.supportsHDR = supportsHDR; asset.msaaSampleCount = msaaSampleCount; asset.renderScale = renderScale; if (occlusionCulling) { if (!Application.isEditor) return false; #if UNITY_EDITOR if (UnityEditor.Rendering.EditorGraphicsSettings.batchRendererGroupShaderStrippingMode != UnityEditor.Rendering.BatchRendererGroupStrippingMode.KeepAll) return false; #endif asset.gpuResidentDrawerMode = GPUResidentDrawerMode.InstancedDrawing; asset.gpuResidentDrawerEnableOcclusionCullingInCameras = occlusionCulling; renderingMode = RenderingMode.ForwardPlus; } // Apply renderer settings rendererData.tileOnlyMode = tileOnlyMode; if (renderingMode != RenderingMode.Forward && !s_RenderingModes.Contains(renderingMode)) return false; rendererData.renderingMode = renderingMode; if (postProcessingEnabled && s_PostProcessData == null) return false; rendererData.postProcessData = s_PostProcessData != null ? s_PostProcessData : null; // Clear rendererData.rendererFeatures.Clear(); foreach (var feature in rendererFeatures) rendererData.rendererFeatures.Add(feature); return true; } public void ApplyToAssetAndRenderer(UniversalRenderPipelineAsset asset, UniversalRendererData rendererData) { if (!TryApplyToAssetAndRenderer(asset, rendererData)) Assert.Ignore("We can't run this test for On Tile Validation because some resources could be missing."); } } static TestCaseData[] s_OnTileValidationCases = { new TestCaseData(new OnTileValidationConfiguration { supportsCameraOpaqueTexture = true }) .SetName("Camera Opaque Texture on Universal Render Pipeline Asset.").Returns(null), new TestCaseData(new OnTileValidationConfiguration { supportsCameraDepthTexture = true }) .SetName("Camera Depth Texture on Universal Render Pipeline Asset.").Returns(null), new TestCaseData(new OnTileValidationConfiguration { supportsHDR = true }) .SetName("Support HDR on Universal Render Pipeline Asset.").Returns(null), new TestCaseData(new OnTileValidationConfiguration { msaaSampleCount = 4 }) .SetName("MSAA Samples > 1 on Universal Render Pipeline Asset.").Returns(null), new TestCaseData(new OnTileValidationConfiguration { occlusionCulling = true }) .SetName("GPU Occlusion Culling on Universal Render Pipeline Asset.").Returns(null), new TestCaseData(new OnTileValidationConfiguration { renderingMode = RenderingMode.Deferred }) .SetName("Deferred Rendering Mode on Universal Renderer.").Returns(null), new TestCaseData(new OnTileValidationConfiguration { renderingMode = RenderingMode.DeferredPlus }) .SetName("Deferred Plus Rendering Mode on Universal Renderer.").Returns(null), new TestCaseData(new OnTileValidationConfiguration { renderScale = 0.65f }) .SetName("Render Scale on Universal Renderer.").Returns(null), new TestCaseData(new OnTileValidationConfiguration { postProcessingEnabled = true }) .SetName("Post Processing on Universal Renderer.").Returns(null), }; [UnityPlatform(exclude = new RuntimePlatform[] { RuntimePlatform.WebGLPlayer })] [UnityTest] [TestCaseSource(nameof(s_OnTileValidationCases))] public IEnumerator URPSettingsShouldNotCauseNonMemorylessTargets(OnTileValidationConfiguration config) { // Arrange LogAssert.ignoreFailingMessages = true; config.ApplyToAssetAndRenderer(m_UniversalRenderPipelineAsset, m_UniversalRendererData); // Act m_UniversalRendererData.tileOnlyMode = true; yield return null; // No need to go further if we don't support memoryless textures if (SystemInfo.supportsMemorylessTextures) { Assert.That(OnlyBackbufferOrMemoryless(outputNonMemoryless: true), Is.True, "Non-memoryless intermediate targets were produced."); } LogAssert.ignoreFailingMessages = false; } [UnityPlatform(exclude = new RuntimePlatform[] { RuntimePlatform.WebGLPlayer})] [UnityTest] public IEnumerator CameraStackingProduceWarning() { if (Display.main.requiresSrgbBlitToBackbuffer) Assert.Ignore("Tile-Only Mode falls back to normal rendering when the display requires an sRGB present blit; the overlay-unsupported warning won't be produced."); // Arrange var gameObject = new GameObject(); var camera = gameObject.AddComponent(); var additionalCameraData = gameObject.AddComponent(); additionalCameraData.renderType = CameraRenderType.Overlay; Assume.That(s_AdditionalCameraData.TryAddCameraToStack(camera), "Couldn't add camera to stack."); yield return null; Assume.That(OnlyBackbufferOrMemoryless(), Is.False, "Intermediate Targets expected without On Tile Validation."); yield return null; // Act LogAssert.Expect(LogType.Warning, new Regex("does not support Overlay cameras")); LogAssert.Expect(LogType.Warning, new Regex("camera overlay no longer exists")); m_UniversalRendererData.tileOnlyMode = true; yield return null; m_UniversalRendererData.tileOnlyMode = false; yield return null; Object.DestroyImmediate(gameObject); s_AdditionalCameraData.UpdateCameraStack(); } bool OnlyBackbufferOrMemoryless(bool outputNonMemoryless = false) { var onlyMemoryless = true; var debugData = RenderGraphDebugSession.GetDebugData("URPRenderGraph", m_CameraEntityId); var textureList = debugData.resourceLists[(int)RenderGraphResourceType.Texture]; for (int i = 0; i < textureList.Count; i++) { var resource = textureList[i]; if (resource is { releasePassIndex: -1, creationPassIndex: -1 }) continue; if (!IsCameraTargetAttachment(resource.name) || resource.memoryless) continue; if (!outputNonMemoryless) return false; Debug.Log($"{resource.name} isn't memoryless."); onlyMemoryless = false; } return onlyMemoryless; } bool IsCameraTargetAttachment(string name) { return m_ResourceTestNames.Contains(name); } class TestRenderGraphDebugSession : RenderGraphDebugSession { public override bool isActive => true; public TestRenderGraphDebugSession() { RegisterAllLocallyKnownGraphsAndExecutions(); } } class TestScriptableRendererFeature : ScriptableRendererFeature { TestScriptableRendererPass m_TestScriptableRendererPass; public override void Create() { m_TestScriptableRendererPass = new TestScriptableRendererPass(); } public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData) { renderer.EnqueuePass(m_TestScriptableRendererPass); } } public class TestPass { } class TestScriptableRendererPass : ScriptableRenderPass { public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData) { using var builder = renderGraph.AddRasterRenderPass("Test Scriptable Renderer Pass", out _); builder.SetRenderFunc(static (TestPass _, RasterGraphContext _) => { }); } } } }