144 lines
		
	
	
		
			6.2 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
		
		
			
		
	
	
			144 lines
		
	
	
		
			6.2 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
|  | using System.Collections.Generic; | ||
|  | using UnityEngine; | ||
|  | using UnityEngine.Rendering.RenderGraphModule; | ||
|  | using UnityEngine.Rendering; | ||
|  | using UnityEngine.Rendering.Universal; | ||
|  | 
 | ||
|  | 
 | ||
|  | // This RendererFeature shows how a compute shader can be used together with RenderGraph. | ||
|  | 
 | ||
|  | // What this example doesn't show is that it can run together with render passes. If the | ||
|  | // compute shader is using resources which are also used by render passes then a dependency | ||
|  | // between the passes are created as they would have done for two render passes. | ||
|  | public class ComputeRendererFeature : ScriptableRendererFeature | ||
|  | { | ||
|  |     // We will treat the compute pass as a normal Scriptable Render Pass. | ||
|  |     class ComputePass : ScriptableRenderPass | ||
|  |     { | ||
|  |         // Compute shader. | ||
|  |         ComputeShader cs; | ||
|  | 
 | ||
|  |         // Compute buffers: | ||
|  |         GraphicsBuffer inputBuffer; | ||
|  |         GraphicsBuffer outputBuffer; | ||
|  | 
 | ||
|  |         // Reflection of the data output. I use a preallocated list to avoid memory | ||
|  |         // allocations each frame. | ||
|  |         int[] outputData = new int[20]; | ||
|  | 
 | ||
|  |         // Constructor is used to initialize the compute buffers. | ||
|  |         public ComputePass() | ||
|  |         { | ||
|  |             BufferDesc desc = new BufferDesc(20, sizeof(int)); | ||
|  |             inputBuffer = new GraphicsBuffer(GraphicsBuffer.Target.Structured, 20, sizeof(int)); | ||
|  |             var list = new List<int>(); | ||
|  |             for (int i = 0; i < 20; i++) | ||
|  |             { | ||
|  |                 list.Add(i); | ||
|  |             } | ||
|  |             inputBuffer.SetData(list); | ||
|  |             outputBuffer = new GraphicsBuffer(GraphicsBuffer.Target.Structured, 20, sizeof(int)); | ||
|  |             // We don't need to initialize the output normaly with data but I read the | ||
|  |             // buffer from the start when each frame is starting to look at last frames result. | ||
|  |             outputBuffer.SetData(list); | ||
|  |         } | ||
|  | 
 | ||
|  |         // Setup function to transfer the compute shader from the renderer feature to | ||
|  |         // the render pass. | ||
|  |         public void Setup(ComputeShader cs) | ||
|  |         { | ||
|  |             this.cs = cs; | ||
|  |         } | ||
|  | 
 | ||
|  |         // PassData is used to pass data when recording to the execution of the pass. | ||
|  |         class PassData | ||
|  |         { | ||
|  |             // Compute shader. | ||
|  |             public ComputeShader cs; | ||
|  |             // Buffer handles for the compute buffers. | ||
|  |             public BufferHandle input; | ||
|  |             public BufferHandle output; | ||
|  |         } | ||
|  | 
 | ||
|  |         // Records a render graph render pass which blits the BlitData's active texture back to the camera's color attachment. | ||
|  |         public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData) | ||
|  |         { | ||
|  |             // Last frame data should be done. Retrive the data if valid. | ||
|  |             outputBuffer.GetData(outputData); | ||
|  |             Debug.Log($"Output from compute shader: {string.Join(", ", outputData)}"); | ||
|  | 
 | ||
|  |             // We need to import buffers when they are created outside of the render graph. | ||
|  |             BufferHandle inputHandle = renderGraph.ImportBuffer(inputBuffer); | ||
|  |             BufferHandle outputHandle = renderGraph.ImportBuffer(outputBuffer); | ||
|  | 
 | ||
|  |             // Starts the recording of the render graph pass given the name of the pass | ||
|  |             // and outputting the data used to pass data to the execution of the render function. | ||
|  |             // Notice that we use "AddComputePass" when we are working with compute. | ||
|  |             using (var builder = renderGraph.AddComputePass("ComputePass", out PassData passData)) | ||
|  |             { | ||
|  |                 // Set the pass data so the data can be transfered from the recording to the execution. | ||
|  |                 passData.cs = cs; | ||
|  |                 passData.input = inputHandle; | ||
|  |                 passData.output = outputHandle; | ||
|  | 
 | ||
|  |                 // UseBuffer is used to setup render graph dependencies together with read and write flags. | ||
|  |                 builder.UseBuffer(passData.input); | ||
|  |                 builder.UseBuffer(passData.output, AccessFlags.Write); | ||
|  |                 // The execution function is also call SetRenderfunc for compute passes. | ||
|  |                 builder.SetRenderFunc((PassData data, ComputeGraphContext cgContext) => ExecutePass(data, cgContext)); | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         // ExecutePass is the render function set in the render graph recordings. | ||
|  |         // This is good practice to avoid using variables outside of the lambda it is called from. | ||
|  |         // It is static to avoid using member variables which could cause unintended behaviour. | ||
|  |         static void ExecutePass(PassData data, ComputeGraphContext cgContext) | ||
|  |         { | ||
|  |             // Attaches the compute buffers. | ||
|  |             cgContext.cmd.SetComputeBufferParam(data.cs, data.cs.FindKernel("CSMain"), "inputData", data.input); | ||
|  |             cgContext.cmd.SetComputeBufferParam(data.cs, data.cs.FindKernel("CSMain"), "outputData", data.output); | ||
|  |             // Dispaches the compute shader with a given kernel as entrypoint. | ||
|  |             // The amount of thread groups determine how many groups to execute of the kernel. | ||
|  |             cgContext.cmd.DispatchCompute(data.cs, data.cs.FindKernel("CSMain"), 1, 1, 1); | ||
|  |         } | ||
|  |     } | ||
|  | 
 | ||
|  |     [SerializeField] | ||
|  |     ComputeShader computeShader; | ||
|  | 
 | ||
|  |     ComputePass m_ComputePass; | ||
|  | 
 | ||
|  |     /// <inheritdoc/> | ||
|  |     public override void Create() | ||
|  |     { | ||
|  |         // Initialize the compute pass. | ||
|  |         m_ComputePass = new ComputePass(); | ||
|  |         // Sets the renderer feature to execute before rendering. | ||
|  |         m_ComputePass.renderPassEvent = RenderPassEvent.BeforeRendering; | ||
|  |     } | ||
|  | 
 | ||
|  |     // Here you can inject one or multiple render passes in the renderer. | ||
|  |     // This method is called when setting up the renderer once per-camera. | ||
|  |     public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData) | ||
|  |     { | ||
|  |         // Check if the system support compute shaders, if not make an early exit. | ||
|  |         if (!SystemInfo.supportsComputeShaders) | ||
|  |         { | ||
|  |             Debug.LogWarning("Device does not support compute shaders. The pass will be skipped."); | ||
|  |             return; | ||
|  |         } | ||
|  |         // Skip the render pass if the compute shader is null. | ||
|  |         if (computeShader == null) | ||
|  |         { | ||
|  |             Debug.LogWarning("The compute shader is null. The pass will be skipped."); | ||
|  |             return; | ||
|  |         } | ||
|  |         // Call Setup on the render pass and transfer the compute shader. | ||
|  |         m_ComputePass.Setup(computeShader); | ||
|  |         // Enqueue the compute pass. | ||
|  |         renderer.EnqueuePass(m_ComputePass); | ||
|  |     } | ||
|  | } | ||
|  | 
 | ||
|  | 
 |