AskingQuestions / Shadeup

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

How to pass "Shared" buffer between Shader1 and Shader2 and Vertex Factory without CPU reading #10

Closed Zifkan closed 11 months ago

Zifkan commented 1 year ago

I need some help. I have two Compute Shaders and one Vertex shader with custom vertex factory.

1) I want buffer SHADER_PARAMETER_RDG_BUFFER_UAV(RWBuffer, Output) for shader1 pass it into shader2 without reading it on CPU. I thought about made "shared" buffer. But i don't know what type to choose. Shader one will write into eat and Shader2 will read it.

So lets say that how i use this buffer in Shader1 reading it on CPU

SHADER_PARAMETER_RDG_BUFFER_UAV(RWBuffer<uint2>, Output)
PassParameters->Output = GraphBuilder.CreateUAV(FRDGBufferUAVDesc(OutputBuffer, PF_R32G32_UINT));

FRHIGPUBufferReadback* GPUBufferReadback = new FRHIGPUBufferReadback(TEXT("ExecuteFrustumCullingComputeShaderOutput"));
AddEnqueueCopyPass(GraphBuilder, GPUBufferReadback, OutputBuffer,OutputBuffer->GetSize());

//Read in callback on CPU

if (GPUBufferReadback->IsReady())
{   

    FUint32Vector2*  Buffer = (FUint32Vector2*)GPUBufferReadback->Lock(OutputNum * sizeof(FUint32Vector2));             
    FMemory::Memcpy(OutputVal.GetData(),Buffer,OutputNum * sizeof(FUint32Vector2));
    GPUBufferReadback->Unlock();
}

So how should i change it to pass it into shader2 without CPU reading.

2) Later i want that shader2 when done his job also pass another "shared" buffer into Vertex Shader (Vertex factory). So it should be also something like this, but as in first example im not really sure how to properly define it.

in my custom CustomLocalVertexFactory.ush i declare it like this Buffer Instancing_BoneMatrixBufferSRV; In Vertex factory bind like this

 Instancing_MatrixBufferSRVParameter.Bind(ParameterMap, TEXT("Instancing_BoneMatrixBufferSRV"));
 LAYOUT_FIELD(FShaderResourceParameter, Instancing_MatrixBufferSRVParameter);

and then using memcopy through FShaderResourceViewRHIRef MatrixBufferSRV; but want avoid this and pass from shader2 into Vertex Factory directly

AskingQuestions commented 1 year ago

Here's a quick (untested) write-up from a project I did a little while ago. Hopefully, this will help point you in the right direction:

The following assumes you already have:

Setup

1

You can create a "UserData" struct for the VertexFactory like this (if you haven't already done so):

// VertexFactory.h

/**
 * Per frame UserData to pass to the vertex shader.
 */
struct FMyFactoryUserData : public FOneFrameResource
{
    FRHIShaderResourceView* DataFromComputeShaderSRV;
};

2

Inside of GetElementShaderBindings() in your VertexFactory:

// VertexFactory.cpp

GetElementShaderBindings(...) {
    ...
    FMyFactoryUserData * UserData = (FMyFactoryUserData *)BatchElement.UserData;
    ShaderBindings.Add(InstanceBufferParameter, UserData->DataFromComputeShaderSRV);
    ...
}

3

Inside of GetDynamicMeshElements in your scene proxy within the batch element loop:


FMyFactoryUserData *UserData = &Collector.AllocateOneFrameResource<FMyFactoryUserData>();
BatchElement.UserData = (void *)UserData;

UserData->DataFromComputeShaderSRV = /* Grab the FShaderResourceViewRHIRef from your state somewhere */;

Transition

You'll need to transition the buffer(s) from UAVMask to SRVMask after you're done dispatching compute shaders that write to the resource for this frame:

1

// Transition from SRVMask to UAVMask
TArray<FRHITransitionInfo> TransitionInfos;
TransitionInfos.Add(FRHITransitionInfo(DataFromComputeShaderBufferUAV, ERHIAccess::SRVMask,ERHIAccess::UAVMask ));

// Transition from UAVMask to SRVMask 
TArray<FRHITransitionInfo> TransitionInfos;
TransitionInfos.Add(FRHITransitionInfo(DataFromComputeShaderBufferUAV, ERHIAccess::UAVMask ,ERHIAccess::SRVMask));

// Dispatch the transition
AddPass(GraphBuilder, RDG_EVENT_NAME("TransitionDrawBuffer"), [TransitionInfos](FRHICommandList &InRHICmdList) {
   InRHICmdList.Transition(TransitionInfos);
 });

Passing the buffer to multiple shaders

You can dispatch multiple compute shaders one after the other who all write to an arbitrary buffer. After you're done writing to them you can use the transition code above to transition the buffer(s) for drawing. Once you're done drawing (start of next frame) you can transition them back to writable.

So the high-level overview of the steps that run each frame look like this: Start of frame

  1. if (Buffer is currently in SRVMask mode) { Transition back to UAVMask mode }
  2. Dispatch compute shader 1 with access to Buffer
  3. Dispatch compute shader 2 with access to Buffer
  4. Transition buffer from UAVMask to SRVMask
  5. UE will automatically call get elements on your scene proxy and draw
Zifkan commented 1 year ago

@AskingQuestions tyvm for answer. Anyway i can't understand how to assign buffer between shaders and factory. UAV buffer created through GraphBuilder.CreateUAV returning FRDGBufferUAVRef. So this type should be convert or remade somehow to FShaderResourceViewRHIRef (for me my factory beffers made as this type )SRV type for factory?

AskingQuestions commented 1 year ago

Good question, I forgot to mention this:

You're going to have to create the buffer outside of the RDG. You can make a FBufferRHIRef by doing:

FRHIResourceCreateInfo CreateInfo(TEXT("helpful name"));
FBufferRHIRef YourBuffer = RHICreateStructuredBuffer(ItemSize, BufferSize, BUF_UnorderedAccess | BUF_ShaderResource, ERHIAccess::SRVMask, CreateInfo);
YourBufferUAV = RHICreateUnorderedAccessView(YourBuffer, false, false);
YourBufferSRV = RHICreateShaderResourceView(YourBuffer);

You'll also have to change your shader parameter from RDG to non-RDG in the compute shader definition:

SHADER_PARAMETER_UAV(RWStructuredBuffer<...>, RWYourBuffer)

When assigning PassParameters:

PassParameters->RWYourBuffer = YourBufferUAV;

When assigning UserData for the vertex factory:

UserData->YourBufferSRV = YourBufferSRV;