Skip to content

Render With PipelineStateObject#

C++
static const FName RendererModuleName( "Renderer" );
  // @todo: JIRA UE-41879 and UE-43829 - added defensive guards against memory trampling on this render command to try and ascertain why it occasionally crashes
  uint32 MemoryGuard1 = 0xaffec7ed;

  // Load the renderermodule on the main thread, as the module manager is not thread-safe, and copy the ptr into the render command, along with 'this' (which is protected by BlockUntilAvailable in ~FViewportSurfaceReader())
  IRendererModule* RendererModule = &FModuleManager::GetModuleChecked<IRendererModule>(RendererModuleName);

  uint32 MemoryGuard2 = 0xaffec7ed;
  IRendererModule* RendererModuleDebug = RendererModule;

  auto RenderCommand = [=](FRHICommandListImmediate& RHICmdList){

    // @todo: JIRA UE-41879 and UE-43829. If any of these ensures go off, something has overwritten the memory for this render command (buffer underflow/overflow?)
    bool bMemoryTrample = !ensureMsgf(RendererModule, TEXT("RendererModule has become null. This indicates a memory trample.")) ||
      !ensureMsgf(RendererModule == RendererModuleDebug, TEXT("RendererModule and RendererModuleDebug are not equal (0x%016x != 0x%016x). This indicates a memory trample."), (void*)RendererModule, (void*)RendererModuleDebug) ||
      !ensureMsgf(MemoryGuard1 == 0xaffec7ed, TEXT("Memory guard 1 is now 0x%08x, expected 0xaffec7ed."), MemoryGuard1) ||
      !ensureMsgf(MemoryGuard2 == 0xaffec7ed, TEXT("Memory guard 2 is now 0x%08x, expected 0xaffec7ed."), MemoryGuard2);

    if (bMemoryTrample)
    {
      // In the hope that 'this' is still ok, triggering the event will prevent a deadlock. If it's not ok, this may crash, but it was going to crash anyway
      AvailableEvent->Trigger();
      return;
    }

    const FIntPoint TargetSize(ReadbackTexture->GetSizeX(), ReadbackTexture->GetSizeY());

    FPooledRenderTargetDesc OutputDesc = FPooledRenderTargetDesc::Create2DDesc(
      TargetSize,
      ReadbackTexture->GetFormat(),
      FClearValueBinding::None,
      TexCreate_None,
      TexCreate_RenderTargetable,
      false);

    // @todo: JIRA UE-41879 and UE-43829. If any of these ensures go off, something has overwritten the memory for this render command (buffer underflow/overflow?)
    bMemoryTrample = !ensureMsgf(RendererModule, TEXT("RendererModule has become null. This indicates a memory trample.")) ||
      !ensureMsgf(RendererModule == RendererModuleDebug, TEXT("RendererModule and RendererModuleDebug are not equal (0x%16x != 0x%16x). This indicates a memory trample."), (void*)RendererModule, (void*)RendererModuleDebug) ||
      !ensureMsgf(MemoryGuard1 == 0xaffec7ed, TEXT("Memory guard 1 is now 0x%08x, expected 0xaffec7ed."), MemoryGuard1) ||
      !ensureMsgf(MemoryGuard2 == 0xaffec7ed, TEXT("Memory guard 2 is now 0x%08x, expected 0xaffec7ed."), MemoryGuard2);

    if (bMemoryTrample)
    {
      // In the hope that 'this' is still ok, triggering the event will prevent a deadlock. If it's not ok, this may crash, but it was going to crash anyway
      AvailableEvent->Trigger();
      return;
    }

    TRefCountPtr<IPooledRenderTarget> ResampleTexturePooledRenderTarget;
    RendererModule->RenderTargetPoolFindFreeElement(RHICmdList, OutputDesc, ResampleTexturePooledRenderTarget, TEXT("ResampleTexture"));
    check(ResampleTexturePooledRenderTarget);

    const FSceneRenderTargetItem& DestRenderTarget = ResampleTexturePooledRenderTarget->GetRenderTargetItem();

    SetRenderTarget(RHICmdList, DestRenderTarget.TargetableTexture, FTextureRHIRef());
    RHICmdList.SetViewport(0, 0, 0.0f, TargetSize.X, TargetSize.Y, 1.0f);

    FGraphicsPipelineStateInitializer GraphicsPSOInit;
    RHICmdList.ApplyCachedRenderTargets(GraphicsPSOInit);
    GraphicsPSOInit.BlendState = TStaticBlendState<>::GetRHI();
    GraphicsPSOInit.RasterizerState = TStaticRasterizerState<>::GetRHI();
    GraphicsPSOInit.DepthStencilState = TStaticDepthStencilState<false,CF_Always>::GetRHI();

    const ERHIFeatureLevel::Type FeatureLevel = GMaxRHIFeatureLevel;

    TShaderMap<FGlobalShaderType>* ShaderMap = GetGlobalShaderMap(FeatureLevel);
    TShaderMapRef<FScreenVS> VertexShader(ShaderMap);
    TShaderMapRef<FScreenPS> PixelShader(ShaderMap);

    GraphicsPSOInit.BoundShaderState.VertexDeclarationRHI = RendererModule->GetFilterVertexDeclaration().VertexDeclarationRHI;
    GraphicsPSOInit.BoundShaderState.VertexShaderRHI = GETSAFERHISHADER_VERTEX(*VertexShader);
    GraphicsPSOInit.BoundShaderState.PixelShaderRHI = GETSAFERHISHADER_PIXEL(*PixelShader);
    GraphicsPSOInit.PrimitiveType = PT_TriangleList;

    SetGraphicsPipelineState(RHICmdList, GraphicsPSOInit);

    FTexture2DRHIRef SourceBackBuffer = RHICmdList.GetViewportBackBuffer(ViewportRHI);

    if (TargetSize.X != SourceBackBuffer->GetSizeX() || TargetSize.Y != SourceBackBuffer->GetSizeY())
    {
      PixelShader->SetParameters(RHICmdList, TStaticSamplerState<SF_Bilinear>::GetRHI(), SourceBackBuffer);
    }
    else
    {
      PixelShader->SetParameters(RHICmdList, TStaticSamplerState<SF_Point>::GetRHI(), SourceBackBuffer);
    }

    float U = float(CaptureRect.Min.X) / float(SourceBackBuffer->GetSizeX());
    float V = float(CaptureRect.Min.Y) / float(SourceBackBuffer->GetSizeY());
    float SizeU = float(CaptureRect.Max.X) / float(SourceBackBuffer->GetSizeX()) - U;
    float SizeV = float(CaptureRect.Max.Y) / float(SourceBackBuffer->GetSizeY()) - V;

    RendererModule->DrawRectangle(
      RHICmdList,
      0, 0,									// Dest X, Y
      TargetSize.X,							// Dest Width
      TargetSize.Y,							// Dest Height
      U, V,									// Source U, V
      1, 1,									// Source USize, VSize
      CaptureRect.Max - CaptureRect.Min,		// Target buffer size
      FIntPoint(1, 1),						// Source texture size
      *VertexShader,
      EDRF_Default);

    // Asynchronously copy render target from GPU to CPU
    const bool bKeepOriginalSurface = false;
    RHICmdList.CopyToResolveTarget(
      DestRenderTarget.TargetableTexture,
      ReadbackTexture,
      bKeepOriginalSurface,
      FResolveParams());

    void* ColorDataBuffer = nullptr;

    int32 Width = 0, Height = 0;
    RHICmdList.MapStagingSurface(ReadbackTexture, ColorDataBuffer, Width, Height);

    Callback((FColor*)ColorDataBuffer, Width, Height);

    RHICmdList.UnmapStagingSurface(ReadbackTexture);
    AvailableEvent->Trigger();
  };

  ENQUEUE_UNIQUE_RENDER_COMMAND_ONEPARAMETER(
    ResolveCaptureFrameTexture,
    TFunction<void(FRHICommandListImmediate&)>, InRenderCommand, RenderCommand,
  {
    InRenderCommand(RHICmdList);
  });