UnityGame/Library/PackageCache/com.unity.render-pipelines.universal/Samples~/URPRenderGraphSamples/Compute/ComputeRendererFeature.cs
2024-10-27 10:53:47 +03:00

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);
}
}