Keywords: UE4, RHI (Rendering Hardware Interface) Notes

Related article: [UE5]RDG Notes

Usages

RHI Global Variables

Engine\Source\Runtime\RHI\Private\RHI.cpp

RHI_API int32 volatile GCurrentTextureMemorySize = 0;
RHI_API int32 volatile GCurrentRendertargetMemorySize = 0;
RHI_API int64 GTexturePoolSize = 0 * 1024 * 1024;
RHI_API int32 GPoolSizeVRAMPercentage = 0;

//primitive draw calls
RHI_API int32 GNumDrawCallsRHI = 0;

//triangles drawn
RHI_API int32 GNumPrimitivesDrawnRHI = 0;
RHI Helper API

Engine\Source\Runtime\RHI\Public\RHI.h

How to get editor preview mode (RHI Feature Level):

// helper to check if a preview feature level has been requested.
RHI_API bool RHIGetPreviewFeatureLevel(ERHIFeatureLevel::Type& PreviewFeatureLevelOUT);

check if a specified Pixel Format is supported:

// helper to check if preferred EPixelFormat is supported, return one if it is not
RHI_API EPixelFormat RHIPreferredPixelFormatHint(EPixelFormat PreferredPixelFormat);
How to convert Texture2D asset to pixel of image in raw data format

Example (quoted from ImageWriteBlueprintLibrary.cpp):

FTextureResource* TextureResource = InTexture->Resource;
ENQUEUE_RENDER_COMMAND(ResolvePixelData)(
    [TextureResource, OnPixelsReady](FRHICommandListImmediate& RHICmdList)
    {
        FTexture2DRHIRef Texture2D = TextureResource->TextureRHI ? TextureResource->TextureRHI->GetTexture2D() : nullptr;
        if (!Texture2D)
        {
            OnPixelsReady(nullptr);
            return;
        }
        FIntRect SourceRect(0, 0, Texture2D->GetSizeX(), Texture2D->GetSizeY());
        switch (Texture2D->GetFormat())
        {
            case PF_FloatRGBA:
            {
                TArray<FFloat16Color> RawPixels;
                RawPixels.SetNum(SourceRect.Width() * SourceRect.Height());
                RHICmdList.ReadSurfaceFloatData(Texture2D, SourceRect, RawPixels, (ECubeFace)0, 0, 0);
                TUniquePtr<TImagePixelData<FFloat16Color>> PixelData = MakeUnique<TImagePixelData<FFloat16Color>>(SourceRect.Size(), TArray64<FFloat16Color>(MoveTemp(RawPixels)));
                if (PixelData->IsDataWellFormed())
                {
                    OnPixelsReady(MoveTemp(PixelData));
                    return;
                }
                break;
            }
            case PF_A32B32G32R32F:
            {
                FReadSurfaceDataFlags ReadDataFlags(RCM_MinMax);
                ReadDataFlags.SetLinearToGamma(false);

                TArray<FLinearColor> RawPixels;
                RawPixels.SetNum(SourceRect.Width() * SourceRect.Height());
                RHICmdList.ReadSurfaceData(Texture2D, SourceRect, RawPixels, ReadDataFlags);
                TUniquePtr<TImagePixelData<FLinearColor>> PixelData = MakeUnique<TImagePixelData<FLinearColor>>(SourceRect.Size(), TArray64<FLinearColor>(MoveTemp(RawPixels)));
                if (PixelData->IsDataWellFormed())
                {
                    OnPixelsReady(MoveTemp(PixelData));
                    return;
                }
                break;
            }
            case PF_R8G8B8A8:
            case PF_B8G8R8A8:
            {
                FReadSurfaceDataFlags ReadDataFlags;
                ReadDataFlags.SetLinearToGamma(false);

                TArray<FColor> RawPixels;
                RawPixels.SetNum(SourceRect.Width() * SourceRect.Height());
                RHICmdList.ReadSurfaceData(Texture2D, SourceRect, RawPixels, ReadDataFlags);

                TUniquePtr<TImagePixelData<FColor>> PixelData = MakeUnique<TImagePixelData<FColor>>(SourceRect.Size(), TArray64<FColor>(MoveTemp(RawPixels)));
                if (PixelData->IsDataWellFormed())
                {
                    OnPixelsReady(MoveTemp(PixelData));
                    return;
                }
                break;
            }
            default:
            break;
        }

        OnPixelsReady(nullptr);
    }
);
How to convert image raw data to Texture2D asset

Example (quoted from CoreUtilityBPLibrary.cpp):

//testing image path
FString ImagePath = { "C:\\Sven_icon.png" };

//image raw data
TArray<uint8> ImageRawData;
FFileHelper::LoadFileToArray(ImageRawData, *ImagePath);

//Convert the UTexture2D back to an image
UTexture2D* Texture = nullptr;

static IImageWrapperModule& ImageWrapperModule = FModuleManager::LoadModuleChecked<IImageWrapperModule>(FName("ImageWrapper"));
EImageFormat DetectedFormat = ImageWrapperModule.DetectImageFormat(ImageRawData.GetData(), ImageRawData.Num());

TSharedPtr<IImageWrapper> ImageWrapper = ImageWrapperModule.CreateImageWrapper(DetectedFormat);

//Set the compressed bytes - we need this information on game thread to be able to determine texture size, otherwise we'll need a complete async callback
if (ImageWrapper.IsValid() && ImageWrapper->SetCompressed(ImageRawData.GetData(), ImageRawData.Num()))
{
    //Create image given sizes
    Texture = UTexture2D::CreateTransient(ImageWrapper->GetWidth(), ImageWrapper->GetHeight(), PF_B8G8R8A8);
    Texture->UpdateResource();

    //Uncompress on a background thread pool
    Async(EAsyncExecution::ThreadPool, [ImageWrapper, Texture] {
        TArray<uint8> UncompressedBGRA;
        if (ImageWrapper->GetRaw(ERGBFormat::BGRA, 8, UncompressedBGRA))
        {
            FUpdateTextureData* UpdateData = new FUpdateTextureData;
            UpdateData->Texture2D = Texture;
            UpdateData->Region = FUpdateTextureRegion2D(0, 0, 0, 0, Texture->GetSizeX(), Texture->GetSizeY());
            UpdateData->BufferArray = &UncompressedBGRA;
            UpdateData->Pitch = Texture->GetSizeX() * 4;
            UpdateData->Wrapper = ImageWrapper;

            ENQUEUE_RENDER_COMMAND(UpdateTextureDataCommand)([UpdateData](FRHICommandListImmediate& RHICmdList) {
                RHIUpdateTexture2D(
                ((FTexture2DResource*)UpdateData->Texture2D->Resource)->GetTexture2DRHI(),
                0,
                UpdateData->Region,
                UpdateData->Pitch,
                UpdateData->BufferArray->GetData()
                );
                delete UpdateData; //now that we've updated the texture data, we can finally release any data we're holding on to
            });//End Enqueue
        }
    });
}
else
{
    UE_LOG(LogTemp, Warning, TEXT("Invalid image format cannot decode %d"), (int32)DetectedFormat);
}
How to dispatch async task (lambda) into render thread from game thread
namespace
{
    void DoTesting_OnRenderThread(FRHICommandListImmediate& RHICmdList, AMyActor* Actor)
    {
        if(AMyActor)
        {
            AMyActor->DoMyTask();
        }
    }
}

void AMyCharacter::TestRender()
{
    AMyActor* Actor = CreateTestActor();

    ENQUEUE_RENDER_COMMAND(MyRenderingCommand)(
        [Actor](FRHICommandListImmediate& RHICmdList)
        {
            DoTesting_OnRenderThread(RHICmdList, Actor);
        });
}
How to copy texture

Example:
\Engine\Plugins\Runtime\Oculus\OculusVR\Source\OculusHMD\Private\OculusHMD_CustomPresent.cpp

void FCustomPresent::CopyTexture_RenderThread(FRHICommandListImmediate& RHICmdList, FRHITexture* DstTexture, FRHITexture* SrcTexture,
FIntRect DstRect = FIntRect(), FIntRect SrcRect = FIntRect(), bool bAlphaPremultiply = false, 
bool bNoAlphaWrite = false, bool bInvertY = true, bool sRGBSource = false) const;
How to Generate Mipmaps

Example:
Engine\Plugins\Runtime\Oculus\OculusVR\Source\OculusHMD\Private\OculusHMD_Layer.cpp

void FXRSwapChain::GenerateMips_RenderThread(FRHICommandListImmediate& RHICmdList)
How to access DirectX interface immediately
  1. Add DX11 in MyProj.Build.cs:

     PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "DX11" });
    
  2. Include header:

    #if PLATFORM_WINDOWS
    #include <D3D11.h>
    #endif
    
  3. Initializ d3d device:

    void AMyGameMode::StartPlay()
    {
        Super::StartPlay();
    
        #if PLATFORM_WINDOWS
        if( !IsValidRef( GD3DDevice ) || !IsValidRef( GD3DDeviceContext ) )
        {
            // Init D3D
            uint32 DeviceCreationFlags = D3D11_CREATE_DEVICE_SINGLETHREADED;
            D3D_DRIVER_TYPE DriverType = D3D_DRIVER_TYPE_HARDWARE;
    
            if( FParse::Param( FCommandLine::Get(), TEXT("d3ddebug") ) )
            {
                DeviceCreationFlags |= D3D11_CREATE_DEVICE_DEBUG;
            }
    
            const D3D_FEATURE_LEVEL FeatureLevels[] = { D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_10_1, D3D_FEATURE_LEVEL_10_0, D3D_FEATURE_LEVEL_9_3 };
            D3D_FEATURE_LEVEL CreatedFeatureLevel;
            HRESULT Hr = D3D11CreateDevice( NULL, DriverType, NULL, DeviceCreationFlags, FeatureLevels,
                sizeof(FeatureLevels)/sizeof(D3D_FEATURE_LEVEL), D3D11_SDK_VERSION, GD3DDevice.GetInitReference(),
                &CreatedFeatureLevel, GD3DDeviceContext.GetInitReference());
    
            if (FAILED(Hr))
            {
                UE_LOG(LogTemp, Error, TEXT("D3D Device can't be initialized +++++++++++ "));
            }
        }
        #endif
    }
    
Screenshot at runtime

Interface:

FScreenshotRequest::RequestScreenshot()

quick code tutorial, how to get a screenshot and write the texture data to a file
https://forums.unrealengine.com/t/quick-code-tutorial-how-to-get-a-screenshot-and-write-the-texture-data-to-a-file/

How to get framebuffer
FSlateApplication::Get().GetRenderer()->OnBackBufferReadyToPresent()

You can also use FFrameGrabber for the virtual camera.

Origin:How to get framebuffer in Unreal Engine 4?

Issues

Please use ENQUEUE_RENDER_COMMAND instead Please update your code to the new API before upgrading to the next release

Warning on compiling:

warning: Please use ENQUEUE_RENDER_COMMAND instead Please update your code to the new API before upgrading to the next release, otherwise your project will no longer compile. [-W#pragma-messages]

Caused by:
ENQUEUE_UNIQUE_RENDER_COMMAND_ONEPARAMETER was obsolete, use ENQUEUE_RENDER_COMMAND instead.

Solution:

MyRenderingHandle* Handle = CreateMyRenderingHandle();

ENQUEUE_RENDER_COMMAND(MyRenderingCommand)(
[Handle](FRHICommandListImmediate& RHICmdList)
{
    Handle->ExecuteMyRenderTask();
});

Reference:
UE4.24源码分析 - ENQUEUE_RENDER_COMMAND
https://zhuanlan.zhihu.com/p/149917554

How to use ENQUEUE_RENDER_COMMAND instead of ENQUEUE_UNIQUE_RENDER_COMMAND_ONEPARAMETER
https://forums.unrealengine.com/development-discussion/engine-source-github/1608365-how-to-use-enqueue_render_command-instead-of-enqueue_unique_render_command_oneparameter

Reference

Create a Viewport that draws your own Shader and RHI
https://historia.co.jp/archives/12659/


The leaders who work most effectively, it seems to me, never say "I." And that's not because they have trained themselves not to say "I." They don't think "I." They think "we"; they think "team." They understand their job to be to make the team function. They accept responsibility and don't sidestep it, but "we" gets the credit. This is what creates trust, what enables you to get the task done. ― Peter Drucker