using NUnit.Framework; using System; using System.Collections.Generic; using UnityEngine.Rendering.RenderGraphModule; #if UNITY_EDITOR using UnityEditor.Rendering; #endif namespace UnityEngine.Rendering.Tests { // TODO: Move this class to the Tests/Editor folder once the "IsolatedPackagesVerified" CI test correctly resolves all dependencies. // Currently, the URP package fails to locate the RenderGraphTestsCore class when it's placed under Tests/Editor, // unless the Core package is explicitly added to the "testables" list in the project manifest — which is not a sustainable solution. internal class RenderGraphTestsCore { // For RG Record/Hash/Compile testing, use m_RenderGraph protected RenderGraph m_RenderGraph; protected RenderPipelineAsset m_OldDefaultRenderPipeline; protected RenderPipelineAsset m_OldQualityRenderPipeline; // For RG Execute/Submit testing with rendering, use m_RenderGraphTestPipeline and m_RenderGraph in its recordRenderGraphBody protected RenderGraphTestPipelineAsset m_RenderGraphTestPipeline; protected RenderGraphTestGlobalSettings m_RenderGraphTestGlobalSettings; protected GameObject m_GameObject; // We need a camera to execute the render graph and a game object to attach a camera protected Camera m_Camera; // For the testing of the following RG steps: Execute and Submit (native) with camera rendering, use this custom RenderGraph render pipeline // through a camera render call to test the RG with a real ScriptableRenderContext internal class RenderGraphTestPipelineAsset : RenderPipelineAsset { public Action recordRenderGraphBody; public RenderGraph renderGraph; public RenderTextureUVOriginStrategy renderTextureUVOriginStrategy; public bool invalidContextForTesting; protected override RenderPipeline CreatePipeline() { return new RenderGraphTestPipelineInstance(this); } // Called only once per UTR void OnEnable() { renderGraph = new(); } } internal class RenderGraphTestPipelineInstance : RenderPipeline { RenderGraphTestPipelineAsset asset; // Having the RG at this level allows us to handle RG framework within Render() for easier testing RenderGraph m_RenderGraph; public RenderGraphTestPipelineInstance(RenderGraphTestPipelineAsset asset) { this.asset = asset; this.m_RenderGraph = asset.renderGraph; } protected override void Render(ScriptableRenderContext renderContext, List cameras) { foreach (var camera in cameras) { if (!camera.enabled) continue; var cmd = CommandBufferPool.Get(); RenderGraphParameters rgParams = new() { commandBuffer = cmd, scriptableRenderContext = renderContext, currentFrameIndex = Time.frameCount, invalidContextForTesting = asset.invalidContextForTesting, renderTextureUVOriginStrategy = asset.renderTextureUVOriginStrategy }; try { // Necessary to reinitialize the state, since we have many tests which do not use this system but directly adding // passes to the graph (to test the compilation) and destroying them without executing them with camera.Render() // (e.g GraphicsPassWriteWaitOnAsyncPipe). So the state becomes RecordGraph because of the Builder.Destroy logic. m_RenderGraph.RenderGraphState = RenderGraphState.Idle; m_RenderGraph.BeginRecording(rgParams); asset.recordRenderGraphBody?.Invoke(renderContext, camera, cmd); m_RenderGraph.EndRecordingAndExecute(); } catch (Exception e) { if (m_RenderGraph.ResetGraphAndLogException(e)) throw; return; } if (rgParams.invalidContextForTesting == false) { renderContext.ExecuteCommandBuffer(cmd); } CommandBufferPool.Release(cmd); } renderContext.Submit(); } } [SupportedOnRenderPipeline(typeof(RenderGraphTestPipelineAsset))] [System.ComponentModel.DisplayName("RenderGraphTest")] internal class RenderGraphTestGlobalSettings : RenderPipelineGlobalSettings { [SerializeField] RenderPipelineGraphicsSettingsContainer m_Settings = new(); protected override List settingsList => m_Settings.settingsList; } [OneTimeSetUp] public void Setup() { // Setting default global settings to the custom RG render pipeline type, no quality settings so we can rely on the default RP m_RenderGraphTestGlobalSettings = ScriptableObject.CreateInstance(); #if UNITY_EDITOR EditorGraphicsSettings.SetRenderPipelineGlobalSettingsAsset(m_RenderGraphTestGlobalSettings); #endif // Saving old render pipelines to set them back after testing m_OldDefaultRenderPipeline = GraphicsSettings.defaultRenderPipeline; m_OldQualityRenderPipeline = QualitySettings.renderPipeline; // Setting the custom RG render pipeline m_RenderGraphTestPipeline = ScriptableObject.CreateInstance(); GraphicsSettings.defaultRenderPipeline = m_RenderGraphTestPipeline; QualitySettings.renderPipeline = m_RenderGraphTestPipeline; // Getting the RG from the custom asset pipeline m_RenderGraph = m_RenderGraphTestPipeline.renderGraph; m_RenderGraph.nativeRenderPassesEnabled = true; // Necessary to disable it for the Unit Tests, as the caller is not the same. RenderGraph.RenderGraphExceptionMessages.enableCaller = false; // We need a real ScriptableRenderContext and a camera to execute the Render Graph m_GameObject = new GameObject("testGameObject") { hideFlags = HideFlags.HideAndDontSave }; m_GameObject.tag = "MainCamera"; m_Camera = m_GameObject.AddComponent(); } [OneTimeTearDown] public void Cleanup() { GraphicsSettings.defaultRenderPipeline = m_OldDefaultRenderPipeline; m_OldDefaultRenderPipeline = null; QualitySettings.renderPipeline = m_OldQualityRenderPipeline; m_OldQualityRenderPipeline = null; m_RenderGraph.Cleanup(); Object.DestroyImmediate(m_RenderGraphTestPipeline); #if UNITY_EDITOR EditorGraphicsSettings.SetRenderPipelineGlobalSettingsAsset(null); #endif Object.DestroyImmediate(m_RenderGraphTestGlobalSettings); GameObject.DestroyImmediate(m_GameObject); m_GameObject = null; m_Camera = null; } [TearDown] public void CleanupRenderGraph() { m_RenderGraphTestPipeline.invalidContextForTesting = false; // Cleaning all Render Graph resources and data structures // Nothing remains, Render Graph in next test will start from scratch m_RenderGraph.CleanupResourcesAndGraph(); } } }