Download

Custom Post Process using ENQUEUE_UNIQUE_RENDER_COMMAND

I’m following a custom .usf post process tutorial and looking for some help in working around the need to edit the engine.

The tutorial calls for an additional block of code in FDeferredShadingSceneRenderer::RenderFinish to call this function:



void RenderMyTest(FRHICommandList& RHICmdList)
{
 //Get the collection of Global Shaders
 auto ShaderMap = GetGlobalShaderMap(ERHIFeatureLevel::SM5);
 //Get the actual shader instances off the ShaderMap

 TShaderMapRef<FMyTestVS> MyVS(ShaderMap);
 TShaderMapRef<FMyTestPS> MyPS(ShaderMap);
 //Declare a pipeline state object that holds all the rendering state
 FGraphicsPipelineStateInitializer PSOInitializer;
 PSOInitializer.PrimitiveType = PT_TriangleStrip;
 PSOInitializer.BoundShaderState.VertexDeclarationRHI = GetVertexDeclarationFVector4();
 PSOInitializer.BoundShaderState.VertexShaderRHI = MyVS->GetVertexShader();
 PSOInitializer.BoundShaderState.PixelShaderRHI = MyPS->GetPixelShader();
 PSOInitializer.RasterizerState = TStaticRasterizerState<FM_Solid, CM_None>::GetRHI();
 PSOInitializer.BlendState = TStaticBlendState<>::GetRHI();
 PSOInitializer.DepthStencilState = TStaticDepthStencilState<false, CF_Always>::GetRHI();

 SetGraphicsPipelineState(RHICmdList, PSOInitializer);
 MyPS->SetColor(RHICmdList, FLinearColor::Blue);

 //Setup the vertices
 FVector4 Vertices[4];
 Vertices[0].Set(-0.01f, 0.01f, 0, 1.0f);
 Vertices[1].Set( 0.01f, 0.01f, 0, 1.0f);
 Vertices[2].Set(-0.01f, -0.01f, 0, 1.0f);
 Vertices[3].Set( 0.01f, -0.01f, 0, 1.0f);

 //Draw the quad
 DrawPrimitiveUP(RHICmdList, PT_TriangleStrip, 2, Vertices, sizeof(Vertices[0]));
}


I would prefer to use ENQUEUE_UNIQUE_RENDER_COMMAND_ONEPARAMETER macro or equivalent, it looks like that’s the right way to go about avoiding the engine edit. I’ve added a call within a tick in my GameMode and it looks like this:



void AMyGameModeBase::Tick(float DeltaSeconds)
{
 Super::Tick(DeltaSeconds);

 ENQUEUE_UNIQUE_RENDER_COMMAND_ONEPARAMETER(
  FPixelShaderRunner,
  ASunriseParallaxGameModeBase*, PixelShader, this,
  {
   check(IsInRenderingThread());
   FRHICommandListImmediate& RHICmdList = GRHICommandList.GetImmediateCommandList();
   RenderMyTest(RHICmdList);
  }
 );
}


The ‘RenderMyTest’ function is being called in this scenario but the post process isn’t being rendered.

Thanks!
Andrew

I was trying to find a way to use shader code in a plugin without modifying engine code as well. En-queuing a render command in theTick() method doesn’t work for reasons you can see if you add a scoped draw event to the the command and do a render doc capture.

75a55eed32ec76b87a278937cb3806f17e58dc96.png

As you can see in the attached photo, the render pass is entirely outside of the scene and happens before it. So you need a way to have the draw happen later. One way that worked was adding an actor to my plugin that added itself to the HUD’s list of actors that will do something in post render, and doing ENQUEUE_RENDER_COMMAND from there:


// Called when the game starts or when spawned
void AMyActor::BeginPlay()
{
    Super::BeginPlay();
    AGameModeBase* Mode = UGameplayStatics::GetGameMode(this);
    APlayerController*PlayerController = UGameplayStatics::GetPlayerController(this, 0);
    AHUD* HUD = PlayerController->GetHUD();
    HUD->AddPostRenderedActor(this);
    HUD->bShowOverlays = true;
}

static TAutoConsoleVariable<int32> CVarMyTest(
    TEXT("r.MyTest"),
    0,
    TEXT("Test My Global Shader, set it to 0 to disable, or to 1, 2 or 3 for fun!"),
    ECVF_RenderThreadSafe
);

static void RenderMyTest(FRHICommandList& RHICmdList, ERHIFeatureLevel::Type FeatureLevel, const FLinearColor& Color)
{
    SCOPED_DRAW_EVENT(RHICmdList, RenderMyTest);
    // Get the collection of Global Shaders
    auto ShaderMap = GetGlobalShaderMap(FeatureLevel);
    // Get the actual shader instances off the ShaderMap
    TShaderMapRef<FMyTestVS> MyVS(ShaderMap);
    TShaderMapRef<FMyTestPS> MyPS(ShaderMap);

    // Declare a pipeline state object that holds all the rendering state
    FGraphicsPipelineStateInitializer PSOInitializer;
    PSOInitializer.PrimitiveType = PT_TriangleStrip;

    PSOInitializer.BoundShaderState.VertexDeclarationRHI = GetVertexDeclarationFVector4();
    PSOInitializer.BoundShaderState.VertexShaderRHI = MyVS->GetVertexShader();
    PSOInitializer.BoundShaderState.PixelShaderRHI = MyPS->GetPixelShader();
    PSOInitializer.RasterizerState = TStaticRasterizerState<FM_Solid, CM_None>::GetRHI();
    PSOInitializer.BlendState = TStaticBlendState<>::GetRHI();
    PSOInitializer.DepthStencilState = TStaticDepthStencilState<false, CF_Always>::GetRHI();

    // Apply it
    SetGraphicsPipelineState(RHICmdList, PSOInitializer, EApplyRendertargetOption::ForceApply);

    // Call our function to set up parameters. This has to happen AFTER the PSO has been applied!
    MyPS->SetColor(RHICmdList, Color);

    // Setup the vertices
    FVector4 Vertices[4];
    Vertices[0].Set(-1.0f, 1.0f, 0, 1.0f);
    Vertices[1].Set(1.0f, 1.0f, 0, 1.0f);
    Vertices[2].Set(-1.0f, -1.0f, 0, 1.0f);
    Vertices[3].Set(1.0f, -1.0f, 0, 1.0f);

    // Draw the quad
    DrawPrimitiveUP(RHICmdList, PT_TriangleStrip, 2, Vertices, sizeof(Vertices[0]));
}

void AMyActor::PostRenderFor(class APlayerController* PC, class UCanvas* Canvas, FVector CameraPosition, FVector CameraDir)
{
    Super::PostRenderFor(PC, Canvas,  CameraPosition,  CameraDir);
    ENQUEUE_UNIQUE_RENDER_COMMAND(
        ABasicShaderCode4x20GameModeBasePixelShaderRunner,
        {
               check(IsInRenderingThread());
               FRHICommandListImmediate& RHICmdList = GRHICommandList.GetImmediateCommandList();
               const ERHIFeatureLevel::Type FeatureLevel = GMaxRHIFeatureLevel;
               int32 MyTestValue = CVarMyTest.GetValueOnAnyThread();
               if (MyTestValue != 0)
               {
                  FLinearColor Color(MyTestValue == 1, MyTestValue == 2, MyTestValue == 3, 1);
                  RenderMyTest(RHICmdList, FeatureLevel, Color);
               }
        }
    );
}

I’m attaching a sample project with a plugin that does everything inside itself. If you launch the default map it has an actor of that type placed in the scene, and entering the r.MyTest 1 (or 2 or 3) into the console changes the color of the whole screen.