using System; using System.Collections.Generic; using NUnit.Framework; using UnityEngine.Profiling; using UnityEngine.Rendering.RenderGraphModule; using UnityEngine.Rendering.Universal; namespace UnityEngine.Rendering.Tests { class CullingTestRenderPass : ScriptableRenderPass { /// public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData) { var cameraData = frameData.Get(); var cullContextData = frameData.Get(); Assert.IsTrue(cameraData != null); Assert.IsTrue(cullContextData != null); cameraData.camera.TryGetCullingParameters(false, out var cullingParameters); var cullingResults = cullContextData.Cull(ref cullingParameters); Assert.IsTrue(cullingResults != null); Assert.IsTrue(cullingResults.visibleLights.Length != 0); } /// [Obsolete(DeprecationMessage.CompatibilityScriptingAPIObsolete, false)] public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData) { // This path does not implement the CullContextData. } } class RenderGraphTests { static Recorder gcAllocRecorder = Recorder.Get("GC.Alloc"); const int kLightCount = 3; CullingTestRenderPass m_TestRenderPass; ScriptableRenderContext? m_RenderContext; [SetUp] public void Setup() { m_TestRenderPass = new CullingTestRenderPass(); RenderPipelineManager.beginCameraRendering += OnBeginCamera; m_RenderContext = null; } [TearDown] public void TearDown() { m_TestRenderPass = null; m_RenderContext = null; RenderPipelineManager.beginCameraRendering -= OnBeginCamera; } [Test] public void RenderPassCullingAPIWorks() { if (DisableTestWhenExecutedOnNonURPProject()) return; // We need a real ScriptableRenderContext and a camera to execute the render graph // add the default camera var cameraGO = new GameObject("Culling_GameObject") { hideFlags = HideFlags.HideAndDontSave }; cameraGO.tag = "MainCamera"; var camera = cameraGO.AddComponent(); var goToRemove = new List(); goToRemove.Add(cameraGO); for (int i = 0; i < kLightCount; ++i) { var lightGO = new GameObject("Light_GameObject" + i) { hideFlags = HideFlags.HideAndDontSave }; var light = lightGO.AddComponent(); light.type = LightType.Point; goToRemove.Add(lightGO); } SubmitCameraRenderRequest(camera); foreach (var obj in goToRemove) { GameObject.DestroyImmediate(obj); } } [Test] public void RenderPassCullingAPIDoesNotAlloc() { if (DisableTestWhenExecutedOnNonURPProject()) return; // We need a real ScriptableRenderContext and a camera to execute the render graph // add the default camera var cameraGO = new GameObject("Culling_GameObject") { hideFlags = HideFlags.HideAndDontSave }; cameraGO.tag = "MainCamera"; var camera = cameraGO.AddComponent(); SubmitCameraRenderRequest(camera); Assert.IsTrue(m_RenderContext != null); var contextContainer = new ContextContainer(); var cullData = contextContainer.Create(); ValidateNoGCAllocs(() => { cullData.SetRenderContext(m_RenderContext.Value); }); GameObject.DestroyImmediate(cameraGO); } bool DisableTestWhenExecutedOnNonURPProject() { return !(GraphicsSettings.currentRenderPipeline is UniversalRenderPipelineAsset); } void OnBeginCamera(ScriptableRenderContext context, Camera cam) { m_RenderContext = context; // Use the EnqueuePass method to inject a custom render pass cam.GetUniversalAdditionalCameraData().scriptableRenderer.EnqueuePass(m_TestRenderPass); } void SubmitCameraRenderRequest(Camera camera) { var request = new RenderPipeline.StandardRequest(); var desc = new RenderTextureDescriptor(camera.pixelWidth, camera.pixelHeight, RenderTextureFormat.Default, 32); request.destination = RenderTexture.GetTemporary(desc); // Check if the active render pipeline supports the render request if (RenderPipeline.SupportsRenderRequest(camera, request)) { RenderPipeline.SubmitRenderRequest(camera, request); } RenderTexture.ReleaseTemporary(request.destination); } void ValidateNoGCAllocs(Action action) { // Warmup - this will catch static c'tors etc. CountGCAllocs(action); // Actual test. var count = CountGCAllocs(action); if (count != 0) throw new AssertionException($"Expected 0 GC allocations but there were {count}"); } int CountGCAllocs(Action action) { gcAllocRecorder.FilterToCurrentThread(); gcAllocRecorder.enabled = true; action(); gcAllocRecorder.enabled = false; return gcAllocRecorder.sampleBlockCount; } } }