[UE4]RHI (Rendering Hardware Interface) Notes
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
-
Add
DX11
inMyProj.Build.cs
:PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "DX11" });
-
Include header:
#if PLATFORM_WINDOWS #include <D3D11.h> #endif
-
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