AskingQuestions / Shadeup

A language for WebGPU that makes writing shaders easier
https://shadeup.dev
150 stars 4 forks source link

UE5.3 Passing StructuredBuffer to Shader #14

Open ChrisSchroeder01 opened 11 months ago

ChrisSchroeder01 commented 11 months ago

Hello,

I am currently trying to pass a Structured Buffer to the shader. The compute shader should also be able to change the values of the structs in this Structured Buffer over time (multiple executions).

I have a struct:

struct FAgent
{
    FVector2f position;
    float angle;
};

And my Execute RTComputeShader method:

static void ExecuteRTComputeShader(UTextureRenderTarget2D* RT, float deltaTime, FVector2f res, TArray<FAgent> agents)
{
    // Create a dispatch parameters struct and fill it the input array with our args
    FSlimeCSDispatchParams Params(agents.Num(), 1, 1);
    Params.Resolution = static_cast<FVector2f>(res);
    Params.RenderTarget = RT->GameThread_GetRenderTargetResource();
    Params.DeltaTime = deltaTime;
    Params.Agents = &agents;

    FSlimeCSInterface::Dispatch(Params);
}

I got the pass to shader working with the following in the DispatchRenderThread Method:

FRDGBufferRef AgentBuffer = CreateStructuredBuffer(GraphBuilder, TEXT("AgentBuffer"), sizeof(FAgent), Params.Agents->Num(), Params.Agents->GetData(), sizeof(FAgent) * Params.Agents->Num());
PassParameters->Agents = GraphBuilder.CreateUAV(AgentBuffer, PF_R32_FLOAT);

Unfortunately I could not figure out how to create an external buffer, which i assume will fix my issue that the computed data of my compute shader will not be overwritten. I tryied RegisterExternalBuffer but this Function is undefined. FRDGBuilder as a method called RegisterExternalBuffer but this method requires a TRefCountPtr parameter, but I could not figure out how to get such a value.

I am using the following Shader Parameter Definition: SHADER_PARAMETER_RDG_BUFFER_UAV(RWStructuredBuffer<FAgent>, Agents) because the pre-generated commented propose SHADER_PARAMETER_UAV(RWStructuredBuffer<FMyCustomStruct>, MyCustomStructs) // On the shader side: RWStructuredBuffer<FMyCustomStruct> MyCustomStructs; again I could not figure out how to get a FRHIUnorderedAccessView * from the TArray.

I came across several posts also trying to achieve what I want: https://forums.unrealengine.com/t/loading-data-to-from-structured-buffer-compute-shaders/470083 https://forums.unrealengine.com/t/rwbuffer-and-buffer-in-compute-shaders-when-they-binding-with-the-same-resource/508164

There are a few things I noticed, it seems that Epic Games changed things from for the 5.3 Version. For example FRHICommandListImmediate::LockStructuredBuffer does not exist anymore (see: https://docs.unrealengine.com/5.3/en-US/API/Runtime/RHI/FRHICommandListImmediate/LockStructuredBuffer/)

I also looked at the Indirect Instancing examples but they looked pretty complex on the first sight. It would generally be nice to have a single Compute Shader Example with every possible Shader Parameter (the 7 commented ones) to know what to do for each parameter type.

AskingQuestions commented 11 months ago

So if I'm understanding you correctly, you want a long-lived buffer on the GPU (one that persists through multiple frames)?

If so, the indirect instancing example has this setup, and the name of the relevant array is RWBaseInstanceBuffer. But it is complicated as you mentioned, so I'll distill it here:

[!NOTE] This is crudely adapted from the indirect instancing example and can be simplified a bit

compute.usf

struct MeshItem {
    float3 Position;
    float3 Rotation;
    float3 Scale;
};

// This is our buffer in the shader
RWStructuredBuffer<MeshItem> RWBaseInstanceBuffer;

main.h - under the shader definition

BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
SHADER_PARAMETER_RDG_BUFFER_UAV(RWStructuredBuffer<FMeshItem>, RWBaseInstanceBuffer)
END_SHADER_PARAMETER_STRUCT()

main.cpp

/** Structure to carry RDG resources. */
struct FVolatileResources
{
  FRDGBufferRef BaseInstanceBuffer;
  FRDGBufferUAVRef BaseInstanceBufferUAV;
  FRDGBufferSRVRef BaseInstanceBufferSRV;
};

main.cpp - when you dispatch

// ...
PassParameters->RWBaseInstanceBuffer = InVolatileResources.BaseInstanceBufferUAV;
// ...
struct ShaderContext {
  // Multi-frame buffers used to store the instance data.
  // This is initialized in InitializeResources()
  mutable TRefCountPtr<FRDGPooledBuffer> BaseInstanceBuffer;
}

main.cpp - when we need to initialize stuff for our shader we call this and pass in a long-lived shader context and volatile resources

/** Initialize the volatile resources used in the render graph. */
void InitializeResources(FRDGBuilder &GraphBuilder, ShaderContext &Ctx, FVolatileResources &OutResources)
{
  if (!Ctx.BaseInstanceBuffer.IsValid())
  {
    // We need to create the instance buffers.
    OutResources.BaseInstanceBuffer = GraphBuilder.CreateBuffer(FRDGBufferDesc::CreateStructuredDesc(sizeof(FMeshItem), MAX_RENDER_ITEMS), TEXT("Mesh.BaseInstanceBuffer"));
    Ctx.BaseInstanceBuffer = GraphBuilder.ConvertToExternalBuffer(OutResources.BaseInstanceBuffer);
  }
  else
  {
    // Buffers already exist, we can use them.
    OutResources.BaseInstanceBuffer = GraphBuilder.RegisterExternalBuffer(Ctx.BaseInstanceBuffer);
  }

  OutResources.BaseInstanceBufferUAV = GraphBuilder.CreateUAV(OutResources.BaseInstanceBuffer);
  OutResources.BaseInstanceBufferSRV = GraphBuilder.CreateSRV(OutResources.BaseInstanceBuffer);
}

Hopefully that captures it correctly. Let me know if something's not working or if you need help finding where to put it