Files
2025-05-18 13:04:45 +08:00

1804 lines
66 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "SlateRHIRenderer.h"
#include "Fonts/FontCache.h"
#include "SlateRHIRenderingPolicy.h"
#include "SlateRHIRendererSettings.h"
#include "Misc/ScopeLock.h"
#include "Modules/ModuleManager.h"
#include "Styling/CoreStyle.h"
#include "Widgets/SWindow.h"
#include "Framework/Application/SlateApplication.h"
#include "EngineGlobals.h"
#include "Engine/AssetManager.h"
#include "Engine/TextureRenderTarget2D.h"
#include "Engine/UserInterfaceSettings.h"
#include "FX/SlateFXSubsystem.h"
#include "FX/SlateRHIPostBufferProcessor.h"
#include "Materials/MaterialRenderProxy.h"
#include "MaterialShared.h"
#include "RendererInterface.h"
#include "StaticBoundShaderState.h"
#include "SceneInterface.h"
#include "SceneUtils.h"
#include "RHIStaticStates.h"
#include "UnrealEngine.h"
#include "GlobalShader.h"
#include "ScreenRendering.h"
#include "SlateShaders.h"
#include "Rendering/ElementBatcher.h"
#include "Rendering/SlateRenderer.h"
#include "RenderResource.h"
#include "RenderingThread.h"
#include "RHIResources.h"
#include "RHIUtilities.h"
#include "StereoRendering.h"
#include "SlateNativeTextureResource.h"
#include "SceneUtils.h"
#include "TextureResource.h"
#include "VolumeRendering.h"
#include "PipelineStateCache.h"
#include "EngineModule.h"
#include "Interfaces/ISlate3DRenderer.h"
#include "SlateRHIRenderingPolicy.h"
#include "Interfaces/SlateRHIRenderingPolicyInterface.h"
#include "Slate/SlateTextureAtlasInterface.h"
#include "Types/ReflectionMetadata.h"
#include "CommonRenderResources.h"
#include "RenderTargetPool.h"
#include "RendererUtils.h"
#include "HAL/LowLevelMemTracker.h"
#include "Rendering/RenderingCommon.h"
#include "IHeadMountedDisplayModule.h"
#include "HDRHelper.h"
#include "RenderCore.h"
#include "DataDrivenShaderPlatformInfo.h"
#include "SlatePostProcessor.h"
#include "Stats/ThreadIdleStats.h"
#include "VT/VirtualTextureFeedbackResource.h"
#if WITH_EDITORONLY_DATA
#include "ShaderCompiler.h"
#endif
DECLARE_CYCLE_STAT(TEXT("Total Render Thread time including dependent waits"), STAT_RenderThreadCriticalPath, STATGROUP_Threading);
CSV_DEFINE_CATEGORY(RenderThreadIdle, true);
CSV_DECLARE_CATEGORY_MODULE_EXTERN(SLATECORE_API, Slate);
DECLARE_GPU_DRAWCALL_STAT_NAMED(SlateUI, TEXT("Slate UI"));
// Defines the maximum size that a slate viewport will create
#define MIN_VIEWPORT_SIZE 8
#define MAX_VIEWPORT_SIZE 16384
static TAutoConsoleVariable<float> CVarUILevel(
TEXT("r.HDR.UI.Level"),
1.0f,
TEXT("Luminance level for UI elements when compositing into HDR framebuffer (default: 1.0)."),
ECVF_RenderThreadSafe);
static TAutoConsoleVariable<float> CVarHDRUILuminance(
TEXT("r.HDR.UI.Luminance"),
300.0f,
TEXT("Base Luminance in nits for UI elements when compositing into HDR framebuffer. Gets multiplied by r.HDR.UI.Level"),
ECVF_RenderThreadSafe);
static TAutoConsoleVariable<int32> CVarUICompositeMode(
TEXT("r.HDR.UI.CompositeMode"),
1,
TEXT("Mode used when compositing the UI layer:\n")
TEXT("0: Standard compositing\n")
TEXT("1: Shader pass to improve HDR blending\n"),
ECVF_RenderThreadSafe);
static TAutoConsoleVariable<int32> CVarCopyBackbufferToSlatePostRenderTargets(
TEXT("Slate.CopyBackbufferToSlatePostRenderTargets"),
0,
TEXT("Experimental. Set true to copy final backbuffer into slate RTs for slate post processing / material usage"),
ECVF_RenderThreadSafe);
#if WITH_SLATE_VISUALIZERS
TAutoConsoleVariable<int32> CVarShowSlateOverdraw(
TEXT("Slate.ShowOverdraw"),
0,
TEXT("0: Don't show overdraw, 1: Show Overdraw"),
ECVF_RenderThreadSafe
);
TAutoConsoleVariable<int32> CVarShowSlateBatching(
TEXT("Slate.ShowBatching"),
0,
TEXT("0: Don't show batching, 1: Show Batching"),
ECVF_RenderThreadSafe
);
#endif
bool GSlateWireframe = false;
static FAutoConsoleVariableRef CVarSlateWireframe(TEXT("Slate.ShowWireFrame"), GSlateWireframe, TEXT(""), ECVF_Default);
// RT stat including waits toggle. Off by default for historical tracking reasons
static TAutoConsoleVariable<int32> CVarRenderThreadTimeIncludesDependentWaits(
TEXT("r.RenderThreadTimeIncludesDependentWaits"),
0,
TEXT("0: RT stat only includes non-idle time, 1: RT stat includes dependent waits (matching RenderThreadTime_CriticalPath)"),
ECVF_Default
);
#if WITH_SLATE_DEBUGGING
static TAutoConsoleVariable<bool> CVarSlateDumpNumDefaultPostBufferUpdates(
TEXT("Slate.DumpNumDefaultPostBufferUpdates"),
false,
TEXT("Dump number of slate default post buffer updates in a frame. Updates every 60f. See also: Slate.DumpNumWidgetPostBufferUpdates.")
);
#endif // WITH_SLATE_DEBUGGING
static bool IsVSyncRequired(const FSlateElementBatcher& ElementBatcher)
{
bool bLockToVsync = ElementBatcher.RequiresVsync();
if (GIsEditor)
{
static IConsoleVariable* CVar = IConsoleManager::Get().FindConsoleVariable(TEXT("r.VSyncEditor"));
bLockToVsync |= (CVar->GetInt() != 0);
}
else
{
static IConsoleVariable* CVar = IConsoleManager::Get().FindConsoleVariable(TEXT("r.VSync"));
bLockToVsync |= (CVar->GetInt() != 0);
}
return bLockToVsync;
}
struct FSlateViewportInfo : public FRenderResource
{
FViewportRHIRef ViewportRHI;
void* OSWindow = nullptr;
FMatrix ProjectionMatrix;
FIntPoint Extent = FIntPoint::ZeroValue;
FIntPoint ExtentToResizeTo = FIntPoint::ZeroValue;
EPixelFormat PixelFormat = EPixelFormat::PF_Unknown;
EDisplayColorGamut HDRDisplayColorGamut = EDisplayColorGamut::sRGB_D65;
EDisplayOutputFormat HDRDisplayOutputFormat = EDisplayOutputFormat::SDR_sRGB;
bool bDisplayFormatIsHDR = false;
bool bFullscreen = false;
void ReleaseRHI() override
{
// Full GPU sync here to simplify memory lifetime of the underlying resource.
FRHICommandListExecutor::GetImmediateCommandList().BlockUntilGPUIdle();
ViewportRHI.SafeRelease();
}
};
struct FSlatePostProcessUpdateRequest
{
TSharedPtr<FSlateRHIPostBufferProcessorProxy> PostProcessorProxy;
FTextureResource* RenderTargetTextureResource = nullptr;
ESlatePostRT RenderTarget = ESlatePostRT::None;
};
struct FSlateDrawWindowPassInputs
{
FSlateRHIRenderer* Renderer = nullptr;
FSlateWindowElementList* WindowElementList = nullptr;
SWindow* Window = nullptr;
FSlateViewportInfo* ViewportInfo = nullptr;
TConstArrayView<FSlatePostProcessUpdateRequest> PostProcessUpdateRequests;
FIntPoint CursorPosition = FIntPoint::ZeroValue;
FIntRect SceneViewRect;
float ViewportScaleUI = 0.0f;
ESlatePostRT UsedSlatePostBuffers = ESlatePostRT::None;
#if WANTS_DRAW_MESH_EVENTS
FString WindowTitle;
#endif
FGameTime Time;
bool bLockToVsync = false;
bool bClear = false;
};
struct FSlateDrawWindowPassOutputs
{
FRHIViewport* ViewportRHI = nullptr;
FRHITexture* ViewportTextureRHI = nullptr;
FRHITexture* OutputTextureRHI = nullptr;
};
FMatrix CreateSlateProjectionMatrix(uint32 Width, uint32 Height)
{
// Create ortho projection matrix
const float Left = 0;
const float Right = Left + Width;
const float Top = 0;
const float Bottom = Top + Height;
const float ZNear = -100.0f;
const float ZFar = 100.0f;
return AdjustProjectionMatrixForRHI(
FMatrix(
FPlane(2.0f / (Right - Left), 0, 0, 0),
FPlane(0, 2.0f / (Top - Bottom), 0, 0),
FPlane(0, 0, 1 / (ZNear - ZFar), 0),
FPlane((Left + Right) / (Left - Right), (Top + Bottom) / (Bottom - Top), ZNear / (ZNear - ZFar), 1)
)
);
}
FSlateRHIRenderer::FSlateRHIRenderer(TSharedRef<FSlateFontServices> InSlateFontServices, TSharedRef<FSlateRHIResourceManager> InResourceManager)
: FSlateRenderer(InSlateFontServices)
, ResourceManager(InResourceManager)
, bIsStandaloneStereoOnlyDevice(IHeadMountedDisplayModule::IsAvailable() && IHeadMountedDisplayModule::Get().IsStandaloneStereoOnlyDevice())
{
for (uint64& LastFramePostBufferUsed : LastFramesPostBufferUsed)
{
LastFramePostBufferUsed = 0;
}
}
bool FSlateRHIRenderer::Initialize()
{
LoadUsedTextures();
RenderingPolicy = MakeShareable(new FSlateRHIRenderingPolicy(SlateFontServices.ToSharedRef(), ResourceManager.ToSharedRef()));
ElementBatcher = MakeUnique<FSlateElementBatcher>(RenderingPolicy.ToSharedRef());
CurrentSceneIndex = -1;
ActiveScenes.Empty();
return true;
}
void FSlateRHIRenderer::Destroy()
{
ResourceManager->ReleaseResources();
SlateFontServices->ReleaseResources();
for (auto& Entry : WindowToViewportInfo)
{
BeginReleaseResource(Entry.Value);
}
FlushPendingDeletes();
FlushRenderingCommands();
ElementBatcher.Reset();
RenderingPolicy.Reset();
ResourceManager.Reset();
SlateFontServices.Reset();
DeferredUpdateContexts.Empty();
for (auto& Entry : WindowToViewportInfo)
{
delete Entry.Value;
}
WindowToViewportInfo.Empty();
CurrentSceneIndex = -1;
ActiveScenes.Empty();
}
/** Returns a draw buffer that can be used by Slate windows to draw window elements */
FSlateDrawBuffer& FSlateRHIRenderer::AcquireDrawBuffer()
{
FreeBufferIndex = (FreeBufferIndex + 1) % NumDrawBuffers;
FSlateDrawBuffer* Buffer = &DrawBuffers[FreeBufferIndex];
while (!Buffer->Lock())
{
// If the buffer cannot be locked then the buffer is still in use. If we are here all buffers are in use
// so wait until one is free.
if (IsInSlateThread())
{
// We can't flush commands on the slate thread, so simply spinlock until we're done
// this happens if the render thread becomes completely blocked by expensive tasks when the Slate thread is running
// in this case we cannot tick Slate.
FPlatformProcess::Sleep(0.001f);
}
else
{
FlushCommands();
UE_LOG(LogSlate, Warning, TEXT("Slate: Had to block on waiting for a draw buffer"));
FreeBufferIndex = (FreeBufferIndex + 1) % NumDrawBuffers;
}
Buffer = &DrawBuffers[FreeBufferIndex];
}
// Safely remove brushes by emptying the array and releasing references
DynamicBrushesToRemove[FreeBufferIndex].Empty();
Buffer->ClearBuffer();
Buffer->UpdateResourceVersion(ResourceVersion);
return *Buffer;
}
void FSlateRHIRenderer::ReleaseDrawBuffer(FSlateDrawBuffer& WindowDrawBuffer)
{
#if DO_CHECK
bool bFound = false;
for (int32 Index = 0; Index < NumDrawBuffers; ++Index)
{
if (&DrawBuffers[Index] == &WindowDrawBuffer)
{
bFound = true;
break;
}
}
ensureMsgf(bFound, TEXT("It release a DrawBuffer that is not a member of the SlateRHIRenderer"));
#endif
ENQUEUE_RENDER_COMMAND(SlateReleaseDrawBufferCommand)([&WindowDrawBuffer](FRHICommandList& RHICmdList)
{
WindowDrawBuffer.Unlock(FRDGBuilder::GetAsyncExecuteTask());
});
}
void FSlateRHIRenderer::CreateViewport(const TSharedRef<SWindow> Window)
{
if (WindowToViewportInfo.Contains(&Window.Get()))
{
return;
}
FlushRenderingCommands();
const FVector2f ViewportSize = Window->GetViewportSize();
FIntPoint ExtentToResizeTo;
ExtentToResizeTo.X = FMath::CeilToInt(ViewportSize.X);
ExtentToResizeTo.Y = FMath::CeilToInt(ViewportSize.Y);
ExtentToResizeTo = ExtentToResizeTo.ComponentMax(FIntPoint(MIN_VIEWPORT_SIZE));
if (!ensureMsgf(ExtentToResizeTo.X <= MAX_VIEWPORT_SIZE && ExtentToResizeTo.Y <= MAX_VIEWPORT_SIZE, TEXT("Invalid window with Width=%u and Height=%u"), ExtentToResizeTo.X, ExtentToResizeTo.Y))
{
ExtentToResizeTo = ExtentToResizeTo.ComponentMin(FIntPoint(MAX_VIEWPORT_SIZE));
}
FSlateViewportInfo* ViewInfo = new FSlateViewportInfo();
ViewInfo->OSWindow = Window->GetNativeWindow()->GetOSWindowHandle();
ViewInfo->ProjectionMatrix = CreateSlateProjectionMatrix(ExtentToResizeTo.X, ExtentToResizeTo.Y);
ViewInfo->Extent = ExtentToResizeTo;
ViewInfo->ExtentToResizeTo = ExtentToResizeTo;
HDRGetMetaData(ViewInfo->HDRDisplayOutputFormat, ViewInfo->HDRDisplayColorGamut, ViewInfo->bDisplayFormatIsHDR, Window->GetPositionInScreen(), Window->GetPositionInScreen() + Window->GetSizeInScreen(), ViewInfo->OSWindow);
const bool bFullscreen = IsViewportFullscreen(*Window);
ViewInfo->PixelFormat = GetViewportPixelFormat(*Window, ViewInfo->bDisplayFormatIsHDR);
ViewInfo->ViewportRHI = RHICreateViewport(ViewInfo->OSWindow, ExtentToResizeTo.X, ExtentToResizeTo.Y, bFullscreen, ViewInfo->PixelFormat);
ViewInfo->bFullscreen = bFullscreen;
BeginInitResource(ViewInfo);
WindowToViewportInfo.Add(&Window.Get(), ViewInfo);
Window->SetIsHDR(ViewInfo->bDisplayFormatIsHDR);
}
void FSlateRHIRenderer::ResizeViewportIfNeeded(FSlateViewportInfo* ViewInfo, FIntPoint ExtentToResizeTo, bool bFullscreen, SWindow* Window)
{
checkSlow(IsThreadSafeForSlateRendering());
if (!IsInGameThread() || IsInSlateThread() || !ViewInfo)
{
return;
}
bool bHDREnabled = IsHDREnabled();
EDisplayColorGamut HDRColorGamut = HDRGetDefaultDisplayColorGamut();
EDisplayOutputFormat HDROutputDevice = HDRGetDefaultDisplayOutputFormat();
HDRGetMetaData(HDROutputDevice, HDRColorGamut, bHDREnabled, Window->GetPositionInScreen(), Window->GetPositionInScreen() + Window->GetSizeInScreen(), ViewInfo->OSWindow);
bool bHDRStale = false;
bHDRStale |= HDROutputDevice != ViewInfo->HDRDisplayOutputFormat;
bHDRStale |= HDRColorGamut != ViewInfo->HDRDisplayColorGamut;
bHDRStale |= bHDREnabled != ViewInfo->bDisplayFormatIsHDR;
if (bHDRStale || ViewInfo->Extent != ExtentToResizeTo || ViewInfo->bFullscreen != bFullscreen || !IsValidRef(ViewInfo->ViewportRHI))
{
// Prevent the texture update logic to use the RHI while the viewport is resized.
// This could happen if a streaming IO request completes and throws a callback.
// @todo : this does not in fact stop texture tasks from using the RHI while the viewport is resized
// because they can be running in other threads, or even in retraction on this thread inside the D3D Wait
// this should be removed and whatever streaming thread safety is needed during a viewport resize should be done correctly
SuspendTextureStreamingRenderTasks();
// Wait for any pending async cleanup
ENQUEUE_RENDER_COMMAND(FAsyncCleanup)([](FRHICommandListImmediate&)
{
FRDGBuilder::WaitForAsyncDeleteTask();
});
// cannot resize the viewport while potentially using it.
FlushRenderingCommands();
// Windows are allowed to be zero sized (sometimes they are animating to/from zero for example) but not viewports.
ExtentToResizeTo = ExtentToResizeTo.ComponentMax(FIntPoint(MIN_VIEWPORT_SIZE, MIN_VIEWPORT_SIZE));
if (ExtentToResizeTo.X > MAX_VIEWPORT_SIZE)
{
UE_LOG(LogSlate, Warning, TEXT("Tried to set viewport width size to %d. Clamping size to max allowed size of %d instead."), ExtentToResizeTo.X, MAX_VIEWPORT_SIZE);
ExtentToResizeTo.X = MAX_VIEWPORT_SIZE;
}
if (ExtentToResizeTo.Y > MAX_VIEWPORT_SIZE)
{
UE_LOG(LogSlate, Warning, TEXT("Tried to set viewport height size to %d. Clamping size to max allowed size of %d instead."), ExtentToResizeTo.Y, MAX_VIEWPORT_SIZE);
ExtentToResizeTo.Y = MAX_VIEWPORT_SIZE;
}
ViewInfo->ProjectionMatrix = CreateSlateProjectionMatrix(ExtentToResizeTo.X, ExtentToResizeTo.Y);
ViewInfo->Extent = ExtentToResizeTo;
ViewInfo->ExtentToResizeTo = ExtentToResizeTo;
ViewInfo->bFullscreen = bFullscreen;
ViewInfo->HDRDisplayColorGamut = HDRColorGamut;
ViewInfo->HDRDisplayOutputFormat = HDROutputDevice;
ViewInfo->bDisplayFormatIsHDR = bHDREnabled;
ViewInfo->PixelFormat = GetViewportPixelFormat(*Window, bHDREnabled);
PreResizeBackBufferDelegate.Broadcast(&ViewInfo->ViewportRHI);
if (IsValidRef(ViewInfo->ViewportRHI))
{
ensureMsgf(ViewInfo->ViewportRHI->GetRefCount() == 1, TEXT("Viewport backbuffer was not properly released"));
RHIResizeViewport(ViewInfo->ViewportRHI, ExtentToResizeTo.X, ExtentToResizeTo.Y, bFullscreen, ViewInfo->PixelFormat);
}
else
{
ViewInfo->ViewportRHI = RHICreateViewport(ViewInfo->OSWindow, ExtentToResizeTo.X, ExtentToResizeTo.Y, bFullscreen, ViewInfo->PixelFormat);
}
PostResizeBackBufferDelegate.Broadcast(&ViewInfo->ViewportRHI);
// Reset texture streaming texture updates.
ResumeTextureStreamingRenderTasks();
// when the window's state for HDR changed, we need to invalidate the window to make sure the viewport will end up in the appropriate FSlateBatchData, see FSlateElementBatcher::AddViewportElement
if (bHDRStale)
{
Window->Invalidate(EInvalidateWidgetReason::Paint);
}
}
}
EPixelFormat FSlateRHIRenderer::GetViewportPixelFormat(const SWindow& Window, bool bDisplayFormatIsHDR)
{
// Use the configured HDR format if enabled.
if (bDisplayFormatIsHDR)
{
return GRHIHDRDisplayOutputFormat;
}
// Use a known default format in VR / Mobile / Transparent Window SDR configurations.
if (bIsStandaloneStereoOnlyDevice || GMaxRHIFeatureLevel == ERHIFeatureLevel::ES3_1
#if ALPHA_BLENDED_WINDOWS
|| Window.GetTransparencySupport() == EWindowTransparency::PerPixel
#endif
)
{
return GetSlateRecommendedColorFormat();
}
// Let the RHI decide.
return PF_Unknown;
}
void FSlateRHIRenderer::OnVirtualDesktopSizeChanged(const FDisplayMetrics& NewDisplayMetric)
{
// Defer the update to as we need to call FlushRenderingCommands() before sending the event to the RHI.
// FlushRenderingCommands -> FRenderCommandFence::IsFenceComplete -> CheckRenderingThreadHealth -> FPlatformApplicationMisc::PumpMessages
// The Display change event is not been consumed yet, and we do BroadcastDisplayMetricsChanged -> OnVirtualDesktopSizeChanged again
bUpdateHDRDisplayInformation = true;
}
void FSlateRHIRenderer::UpdateFullscreenState(const TSharedRef<SWindow> Window, uint32 OverrideResX, uint32 OverrideResY)
{
FSlateViewportInfo* ViewInfo = WindowToViewportInfo.FindRef(&Window.Get());
if (!ViewInfo)
{
CreateViewport(Window);
}
ViewInfo = WindowToViewportInfo.FindRef(&Window.Get());
if (ViewInfo)
{
const bool bFullscreen = IsViewportFullscreen(*Window);
const bool bIsRenderingStereo = GEngine && GEngine->XRSystem.IsValid() && GEngine->StereoRenderingDevice.IsValid() && GEngine->StereoRenderingDevice->IsStereoEnabled();
FIntPoint ExtentToResizeTo(OverrideResX ? OverrideResX : GSystemResolution.ResX, OverrideResY ? OverrideResY : GSystemResolution.ResY);
if ((GIsEditor && Window->IsViewportSizeDrivenByWindow()) || (Window->GetWindowMode() == EWindowMode::WindowedFullscreen) || bIsRenderingStereo)
{
ExtentToResizeTo = ViewInfo->ExtentToResizeTo;
}
ResizeViewportIfNeeded(ViewInfo, ExtentToResizeTo, bFullscreen, &Window.Get());
}
}
void FSlateRHIRenderer::SetSystemResolution(uint32 Width, uint32 Height)
{
FSystemResolution::RequestResolutionChange(Width, Height, FPlatformProperties::HasFixedResolution() ? EWindowMode::Fullscreen : GSystemResolution.WindowMode);
IConsoleManager::Get().CallAllConsoleVariableSinks();
}
void FSlateRHIRenderer::RestoreSystemResolution(const TSharedRef<SWindow> InWindow)
{
if (!GIsEditor && InWindow->GetWindowMode() == EWindowMode::Fullscreen)
{
// Force the window system to resize the active viewport, even though nothing might have appeared to change.
// On windows, DXGI might change the window resolution behind our backs when we alt-tab out. This will make
// sure that we are actually in the resolution we think we are.
GSystemResolution.ForceRefresh();
}
}
void FSlateRHIRenderer::OnWindowDestroyed(const TSharedRef<SWindow>& InWindow)
{
if (FSlateViewportInfo** ViewportInfoPtr = WindowToViewportInfo.Find(&InWindow.Get()))
{
FSlateViewportInfo* ViewportInfo = *ViewportInfoPtr;
OnSlateWindowDestroyedDelegate.Broadcast(&ViewportInfo->ViewportRHI);
// Perform the release in lock-step with the render thread to simplify resource lifetimes.
FlushRenderingCommands();
BeginReleaseResource(ViewportInfo);
FlushRenderingCommands();
delete ViewportInfo;
WindowToViewportInfo.Remove(&InWindow.Get());
}
}
void FSlateRHIRenderer::OnWindowFinishReshaped(const TSharedPtr<SWindow>& InWindow)
{
FSlateViewportInfo* ViewInfo = WindowToViewportInfo.FindRef(InWindow.Get());
RHICheckViewportHDRStatus(ViewInfo->ViewportRHI);
}
// Limited platform support for HDR UI composition
bool SupportsCompositeUIWithSceneHDR(const EShaderPlatform Platform)
{
return IsFeatureLevelSupported(Platform, ERHIFeatureLevel::SM5) && (RHISupportsGeometryShaders(Platform) || RHISupportsVertexShaderLayer(Platform));
}
bool CompositeUIWithSceneHDR()
{
// Optional off-screen UI composition during HDR rendering
static const auto CVarCompositeMode = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.HDR.UI.CompositeMode"));
return GRHISupportsHDROutput
&& RHISupportsVolumeTextureRendering(GetFeatureLevelShaderPlatform(GMaxRHIFeatureLevel))
&& SupportsCompositeUIWithSceneHDR(GetFeatureLevelShaderPlatform(GMaxRHIFeatureLevel))
&& CVarCompositeMode
&& CVarCompositeMode->GetValueOnAnyThread() != 0;
}
BEGIN_SHADER_PARAMETER_STRUCT(FCompositeShaderCommonParameters, )
SHADER_PARAMETER_RDG_TEXTURE(Texture2D, UITexture)
SHADER_PARAMETER_SAMPLER(SamplerState, UISampler)
SHADER_PARAMETER_RDG_TEXTURE(Texture2D<uint>, UIWriteMaskTexture)
SHADER_PARAMETER(float, UILevel)
SHADER_PARAMETER(float, UILuminance)
SHADER_PARAMETER(float, ColorVisionDeficiencyType)
SHADER_PARAMETER(float, ColorVisionDeficiencySeverity)
SHADER_PARAMETER(float, bCorrectDeficiency)
SHADER_PARAMETER(float, bSimulateCorrectionWithDeficiency)
END_SHADER_PARAMETER_STRUCT()
class FCompositeShader : public FGlobalShader
{
public:
class FSCRGBEncoding : SHADER_PERMUTATION_BOOL("SCRGB_ENCODING");
class FApplyColorDeficiency : SHADER_PERMUTATION_BOOL("APPLY_COLOR_DEFICIENCY");
using FPermutationDomain = TShaderPermutationDomain<FSCRGBEncoding, FApplyColorDeficiency>;
FCompositeShader() {}
FCompositeShader(const ShaderMetaType::CompiledShaderInitializerType& Initializer)
: FGlobalShader(Initializer)
{}
static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters)
{
return SupportsCompositeUIWithSceneHDR(Parameters.Platform);
}
};
class FCompositePS : public FCompositeShader
{
public:
DECLARE_GLOBAL_SHADER(FCompositePS);
SHADER_USE_PARAMETER_STRUCT(FCompositePS, FCompositeShader);
BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
SHADER_PARAMETER_STRUCT_INCLUDE(FCompositeShaderCommonParameters, Common)
SHADER_PARAMETER_RDG_TEXTURE(Texture2D, SceneTexture)
SHADER_PARAMETER_SAMPLER(SamplerState, SceneSampler)
RENDER_TARGET_BINDING_SLOTS()
END_SHADER_PARAMETER_STRUCT()
};
IMPLEMENT_GLOBAL_SHADER(FCompositePS, "/Engine/Private/CompositeUIPixelShader.usf", "Main", SF_Pixel);
class FCompositeCS : public FCompositeShader
{
public:
static const uint32 NUM_THREADS_PER_GROUP = 16;
DECLARE_GLOBAL_SHADER(FCompositeCS);
SHADER_USE_PARAMETER_STRUCT(FCompositeCS, FCompositeShader);
BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
SHADER_PARAMETER_STRUCT_INCLUDE(FCompositeShaderCommonParameters, Common)
SHADER_PARAMETER_RDG_TEXTURE_UAV(RWTexture2D<float4>, RWSceneTexture)
SHADER_PARAMETER(FVector4f, SceneTextureDimensions)
END_SHADER_PARAMETER_STRUCT()
static bool IsShaderSupported(const EShaderPlatform ShaderPlatform)
{
return RHISupports4ComponentUAVReadWrite(ShaderPlatform) && RHISupportsSwapchainUAVs(ShaderPlatform);
}
static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters)
{
return FCompositeShader::ShouldCompilePermutation(Parameters) && IsShaderSupported(Parameters.Platform);
}
static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment)
{
FGlobalShader::ModifyCompilationEnvironment(Parameters, OutEnvironment);
OutEnvironment.SetDefine(TEXT("USE_COMPUTE_FOR_COMPOSITION"), 1);
OutEnvironment.SetDefine(TEXT("NUM_THREADS_PER_GROUP"), NUM_THREADS_PER_GROUP);
}
};
IMPLEMENT_GLOBAL_SHADER(FCompositeCS, "/Engine/Private/CompositeUIPixelShader.usf", "CompositeUICS", SF_Compute);
FSlateDrawWindowPassOutputs FSlateRHIRenderer::DrawWindow_RenderThread(FRDGBuilder& GraphBuilder, const FSlateDrawWindowPassInputs& Inputs)
{
LLM_SCOPE(ELLMTag::SceneRender);
FSlateViewportInfo& ViewportInfo = *Inputs.ViewportInfo;
FSlateWindowElementList& WindowElementList = *Inputs.WindowElementList;
FMaterialRenderProxy::UpdateDeferredCachedUniformExpressions(GraphBuilder.RHICmdList);
GetRendererModule().InitializeSystemTextures(GraphBuilder.RHICmdList);
FRHITexture* ViewportTextureRHI;
FRHITexture* OutputTextureRHI;
{
RDG_GPU_MASK_SCOPE(GraphBuilder, FRHIGPUMask::FromIndex(RHIGetViewportNextPresentGPUIndex(ViewportInfo.ViewportRHI)));
#if WANTS_DRAW_MESH_EVENTS
RDG_EVENT_SCOPE_CONDITIONAL_STAT(GraphBuilder, Inputs.WindowTitle.IsEmpty(), SlateUI, "SlateUI Title = <none>");
RDG_EVENT_SCOPE_CONDITIONAL_STAT(GraphBuilder, !Inputs.WindowTitle.IsEmpty(), SlateUI, "SlateUI Title = %s", *Inputs.WindowTitle);
#else
RDG_EVENT_SCOPE_STAT(GraphBuilder, SlateUI, "SlateUI");
#endif
RDG_GPU_STAT_SCOPE(GraphBuilder, SlateUI);
RDG_CSV_STAT_EXCLUSIVE_SCOPE(GraphBuilder, Slate);
TRACE_CPUPROFILER_EVENT_SCOPE(Slate::DrawWindow_RenderThread);
ISlateViewport* SlateViewport = Inputs.Window->GetViewport().Get();
// The viewport texture is an optional user-allocated render target. This is rendered to if valid.
ViewportTextureRHI = SlateViewport && SlateViewport->UseSeparateRenderTarget() ? static_cast<FSlateRenderTargetRHI*>(SlateViewport->GetViewportRenderTargetTexture())->GetTypedResource() : nullptr;
// The swap chain is the final output. This is rendered to if no viewport render target is provided.
FRHITexture* SwapChainTextureRHI = RHIGetViewportBackBuffer(ViewportInfo.ViewportRHI);
// Only render to the intermediate viewport render target if stereo rendering is enabled, which we'll then composite later.
const bool bCompositeStereoToSwapChain = ViewportTextureRHI && GEngine && GEngine->StereoRenderingDevice.IsValid() && SlateViewport && SlateViewport->IsStereoscopic3D();
// The output texture is what we ultimately render or composite slate elements into.
OutputTextureRHI = bCompositeStereoToSwapChain ? ViewportTextureRHI : SwapChainTextureRHI;
FRDGTexture* OutputTexture = RegisterExternalTexture(GraphBuilder, OutputTextureRHI, TEXT("SlateOutputTexture"));
// The elements texture contains UI elements. It can be the same as the output or allocated separately and composited.
FRDGTexture* ElementsTexture = OutputTexture;
const FIntPoint OutputExtent = OutputTexture->Desc.Extent;
TArray<FRDGTexture*, FRDGArrayAllocator> PostProcessTextures;
PostProcessTextures.Reserve(Inputs.PostProcessUpdateRequests.Num());
for (const FSlatePostProcessUpdateRequest& Request : Inputs.PostProcessUpdateRequests)
{
FRDGTexture* Texture = RegisterExternalTexture(GraphBuilder, Request.RenderTargetTextureResource->GetTexture2DRHI(), TEXT("PostProcessRT"));
PostProcessTextures.Emplace(Texture);
GraphBuilder.UseInternalAccessMode(Texture);
}
// The post process input texture will be the separate viewport texture if it exists, or the swap chain.
FScreenPassTexture PostProcessInputTexture(RegisterExternalTexture(GraphBuilder, ViewportTextureRHI ? ViewportTextureRHI : SwapChainTextureRHI, TEXT("ViewportTexture")));
for (int32 PostProcessIndex = 0; PostProcessIndex < Inputs.PostProcessUpdateRequests.Num(); ++PostProcessIndex)
{
const FSlatePostProcessUpdateRequest& Request = Inputs.PostProcessUpdateRequests[PostProcessIndex];
const FScreenPassTexture PostProcessOutputTexture(PostProcessTextures[PostProcessIndex]);
if (ViewportTextureRHI)
{
PostProcessInputTexture.ViewRect = FIntRect(FIntPoint::ZeroValue, ViewportTextureRHI->GetSizeXY());
}
else
{
PostProcessInputTexture.ViewRect = Inputs.SceneViewRect;
}
if (Request.PostProcessorProxy)
{
Request.PostProcessorProxy->PostProcess_Renderthread(GraphBuilder, PostProcessInputTexture, PostProcessOutputTexture);
}
else
{
AddDrawTexturePass(GraphBuilder, FScreenPassViewInfo(), PostProcessInputTexture, PostProcessOutputTexture);
}
}
for (FRDGTexture* Texture : PostProcessTextures)
{
GraphBuilder.UseExternalAccessMode(Texture, ERHIAccess::SRVMask);
}
const bool bCompositeUIWithSceneHDR = ViewportInfo.bDisplayFormatIsHDR && CompositeUIWithSceneHDR();
bool bClearElementsTexture = Inputs.bClear || GSlateWireframe;
#if WITH_SLATE_VISUALIZERS
bClearElementsTexture |= CVarShowSlateBatching.GetValueOnRenderThread() != 0 || CVarShowSlateOverdraw.GetValueOnRenderThread() != 0;
#endif
if (bCompositeUIWithSceneHDR)
{
const ETextureCreateFlags WriteMaskFlags = RHISupportsRenderTargetWriteMask(GMaxRHIShaderPlatform) ? ETextureCreateFlags::NoFastClearFinalize | ETextureCreateFlags::DisableDCC : ETextureCreateFlags::None;
ElementsTexture = GraphBuilder.CreateTexture(
FRDGTextureDesc::Create2D(
OutputExtent,
GetSlateRecommendedColorFormat(),
FClearValueBinding::Transparent,
ETextureCreateFlags::ShaderResource | ETextureCreateFlags::RenderTargetable | WriteMaskFlags),
TEXT("CompositeUIWithSceneHDRTexture"));
// Force a clear of the UI elements texture to black
bClearElementsTexture = true;
}
FSlateBatchData& BatchData = WindowElementList.GetBatchData();
FSlateBatchData& BatchDataHDR = WindowElementList.GetBatchDataHDR();
const bool bRequiresVirtualTextureFeedback = BatchData.IsVirtualTextureFeedbackRequired() || BatchDataHDR.IsVirtualTextureFeedbackRequired();
if (bRequiresVirtualTextureFeedback)
{
VirtualTexture::BeginFeedback(GraphBuilder);
}
const FSlateElementsBuffers SlateElementsBuffers = BuildSlateElementsBuffers(GraphBuilder, BatchData);
const FSlateElementsBuffers SlateElementsBuffersHDR = BuildSlateElementsBuffers(GraphBuilder, BatchDataHDR);
FRDGTexture* SlateStencilTexture = nullptr;
if (BatchData.IsStencilClippingRequired() || BatchDataHDR.IsStencilClippingRequired())
{
SlateStencilTexture = GraphBuilder.CreateTexture(FRDGTextureDesc::Create2D(OutputExtent, PF_DepthStencil, FClearValueBinding::DepthZero, GetSlateTransientDepthStencilFlags()), TEXT("SlateDepthStencil"));
}
FSlateDrawElementsPassInputs DrawElementsInputs =
{
.SceneViewportTexture = OutputTexture
, .ElementsMatrix = FMatrix44f(ViewportInfo.ProjectionMatrix)
, .SceneViewRect = Inputs.SceneViewRect
, .CursorPosition = Inputs.CursorPosition
, .Time = Inputs.Time
, .HDRDisplayColorGamut = ViewportInfo.HDRDisplayColorGamut
, .UsedSlatePostBuffers = Inputs.UsedSlatePostBuffers
, .ViewportScaleUI = Inputs.ViewportScaleUI
, .bWireframe = GSlateWireframe
, .bElementsTextureIsHDRDisplay = ViewportInfo.bDisplayFormatIsHDR
};
if (bCompositeUIWithSceneHDR)
{
// Color deficiency correction is performed inside of the CompositeUI pass instead.
DrawElementsInputs.bAllowColorDeficiencyCorrection = false;
if (!BatchDataHDR.GetRenderBatches().IsEmpty())
{
DrawElementsInputs.ElementsTexture = OutputTexture;
DrawElementsInputs.ElementsLoadAction = ERenderTargetLoadAction::EClear;
DrawElementsInputs.ElementsBuffers = SlateElementsBuffersHDR;
DrawElementsInputs.StencilTexture = BatchDataHDR.IsStencilClippingRequired() ? SlateStencilTexture : nullptr;
AddSlateDrawElementsPass(GraphBuilder, *RenderingPolicy, DrawElementsInputs, BatchDataHDR.GetRenderBatches(), BatchDataHDR.GetFirstRenderBatchIndex());
}
DrawElementsInputs.bElementsTextureIsHDRDisplay = false;
}
DrawElementsInputs.ElementsTexture = ElementsTexture;
DrawElementsInputs.ElementsLoadAction = bClearElementsTexture ? ERenderTargetLoadAction::EClear : ERenderTargetLoadAction::ELoad;
DrawElementsInputs.ElementsBuffers = SlateElementsBuffers;
DrawElementsInputs.StencilTexture = BatchData.IsStencilClippingRequired() ? SlateStencilTexture : nullptr;
AddSlateDrawElementsPass(GraphBuilder, *RenderingPolicy, DrawElementsInputs, BatchData.GetRenderBatches(), BatchData.GetFirstRenderBatchIndex());
if (bCompositeUIWithSceneHDR)
{
RDG_EVENT_SCOPE(GraphBuilder, "CompositeUI");
FRDGTexture* ElementsWriteMaskTexture = nullptr;
FGlobalShaderMap* ShaderMap = GetGlobalShaderMap(GMaxRHIFeatureLevel);
if (RHISupportsRenderTargetWriteMask(GMaxRHIShaderPlatform))
{
FRenderTargetWriteMask::Decode(GraphBuilder, ShaderMap, MakeArrayView({ ElementsTexture }), ElementsWriteMaskTexture, ETextureCreateFlags::None, TEXT("ElementsWriteMaskTexture"));
}
static const auto CVarOutputDevice = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.HDR.Display.OutputDevice"));
FCompositeShader::FPermutationDomain PermutationVector;
PermutationVector.Set<FCompositeShader::FSCRGBEncoding>(ViewportInfo.HDRDisplayOutputFormat == EDisplayOutputFormat::HDR_ACES_1000nit_ScRGB || ViewportInfo.HDRDisplayOutputFormat == EDisplayOutputFormat::HDR_ACES_2000nit_ScRGB);
PermutationVector.Set<FCompositeShader::FApplyColorDeficiency>(GSlateColorDeficiencyType != EColorVisionDeficiency::NormalVision && GSlateColorDeficiencySeverity > 0);
FCompositeShaderCommonParameters CommonParameters;
CommonParameters.UIWriteMaskTexture = ElementsWriteMaskTexture;
CommonParameters.UITexture = ElementsTexture;
CommonParameters.UISampler = TStaticSamplerState<SF_Point>::GetRHI();
CommonParameters.UILevel = CVarUILevel.GetValueOnRenderThread();
CommonParameters.UILuminance = CVarHDRUILuminance.GetValueOnRenderThread();
CommonParameters.ColorVisionDeficiencySeverity = (float)GSlateColorDeficiencySeverity;
CommonParameters.ColorVisionDeficiencyType = (float)GSlateColorDeficiencyType;
CommonParameters.bSimulateCorrectionWithDeficiency = GSlateShowColorDeficiencyCorrectionWithDeficiency ? 1.0f : 0.0f;
CommonParameters.bCorrectDeficiency = GSlateColorDeficiencyCorrection ? 1.0f : 0.0f;
if (FCompositeCS::IsShaderSupported(GMaxRHIShaderPlatform))
{
FCompositeCS::FParameters* PassParameters = GraphBuilder.AllocParameters<FCompositeCS::FParameters>();
PassParameters->Common = CommonParameters;
PassParameters->RWSceneTexture = GraphBuilder.CreateUAV(OutputTexture);
PassParameters->SceneTextureDimensions = FVector4f((float)OutputExtent.X, (float)OutputExtent.Y, 1.0f/(float)OutputExtent.X, 1.0f/(float)OutputExtent.Y);
TShaderMapRef<FCompositeCS> ComputeShader(ShaderMap, PermutationVector);
FComputeShaderUtils::AddPass(
GraphBuilder,
RDG_EVENT_NAME("CompositeUI"),
ComputeShader,
PassParameters,
FIntVector(
FMath::DivideAndRoundUp<int32>(OutputExtent.X, FCompositeCS::NUM_THREADS_PER_GROUP),
FMath::DivideAndRoundUp<int32>(OutputExtent.Y, FCompositeCS::NUM_THREADS_PER_GROUP),
1));
}
else
{
FRDGTexture* ViewportCopyTexture = GraphBuilder.CreateTexture(
FRDGTextureDesc::Create2D(
OutputExtent,
OutputTexture->Desc.Format,
FClearValueBinding::Transparent,
GetSlateTransientRenderTargetFlags()),
TEXT("SlateViewportCopyTexture"));
AddCopyTexturePass(GraphBuilder, OutputTexture, ViewportCopyTexture);
const FScreenPassTextureViewport Viewport(OutputTexture);
FCompositePS::FParameters* PassParameters = GraphBuilder.AllocParameters<FCompositePS::FParameters>();
PassParameters->Common = CommonParameters;
PassParameters->RenderTargets[0] = FRenderTargetBinding(OutputTexture, ERenderTargetLoadAction::ENoAction);
PassParameters->SceneTexture = ViewportCopyTexture;
PassParameters->SceneSampler = TStaticSamplerState<SF_Point>::GetRHI();
TShaderMapRef<FCompositePS> PixelShader(ShaderMap, PermutationVector);
AddDrawScreenPass(GraphBuilder, RDG_EVENT_NAME("CompositeUI"), FScreenPassViewInfo(), Viewport, Viewport, PixelShader, PassParameters);
}
}
if (bCompositeStereoToSwapChain)
{
FRDGTexture* SwapChainTexture = RegisterExternalTexture(GraphBuilder, SwapChainTextureRHI, TEXT("StereoSpectatorSwapChainTexture"));
GraphBuilder.SetTextureAccessFinal(SwapChainTexture, ERHIAccess::Present);
GEngine->StereoRenderingDevice->RenderTexture_RenderThread(GraphBuilder, SwapChainTexture, OutputTexture, WindowElementList.GetWindowSize());
}
else
{
GraphBuilder.SetTextureAccessFinal(OutputTexture, ERHIAccess::Present);
}
if (bRequiresVirtualTextureFeedback)
{
VirtualTexture::EndFeedback(GraphBuilder);
}
OnAddBackBufferReadyToPresentPassDelegate.Broadcast(GraphBuilder, *Inputs.Window, OutputTexture);
if (ScreenshotState.ViewportToCapture == &ViewportInfo)
{
// Sanity check to make sure the user specified a valid screenshot rect.
FIntRect ViewRectClamped;
ViewRectClamped.Min = ScreenshotState.ViewRect.Min;
ViewRectClamped.Max = ScreenshotState.ViewRect.Max.ComponentMin(OutputExtent);
ViewRectClamped.Max = ScreenshotState.ViewRect.Min.ComponentMax(ViewRectClamped.Max);
if (ViewRectClamped != ScreenshotState.ViewRect)
{
UE_LOG(LogSlate, Warning, TEXT("Slate: Screenshot rect max coordinate had to be clamped from [%d, %d] to [%d, %d]"), ScreenshotState.ViewRect.Max.X, ScreenshotState.ViewRect.Max.Y, ViewRectClamped.Max.X, ViewRectClamped.Max.Y);
}
if (!ViewRectClamped.IsEmpty())
{
AddReadbackTexturePass(GraphBuilder, RDG_EVENT_NAME("ScreenshotReadback"), OutputTexture,
[this, OutputTexture, ViewRectClamped, ColorDataHDR = ScreenshotState.ColorDataHDR, ColorData = ScreenshotState.ColorData] (FRHICommandListImmediate& RHICmdList)
{
if (ColorDataHDR)
{
RHICmdList.ReadSurfaceData(OutputTexture->GetRHI(), ViewRectClamped, *ColorDataHDR, FReadSurfaceDataFlags(RCM_MinMax));
}
else
{
check(ColorData);
RHICmdList.ReadSurfaceData(OutputTexture->GetRHI(), ViewRectClamped, *ColorData, FReadSurfaceDataFlags());
}
});
}
}
}
FSlateDrawWindowPassOutputs Outputs;
Outputs.ViewportRHI = ViewportInfo.ViewportRHI;
Outputs.ViewportTextureRHI = ViewportTextureRHI;
Outputs.OutputTextureRHI = OutputTextureRHI;
return Outputs;
}
void FSlateRHIRenderer::PresentWindow_RenderThread(FRHICommandListImmediate& RHICmdList, const FSlateDrawWindowPassInputs& DrawPassInputs, const FSlateDrawWindowPassOutputs& DrawPassOutputs)
{
OnBackBufferReadyToPresentDelegate.Broadcast(*DrawPassInputs.Window, DrawPassOutputs.OutputTextureRHI);
uint32 StartTime = FPlatformTime::Cycles();
RHICmdList.EnqueueLambda([CurrentFrameCounter = GFrameCounterRenderThread](FRHICommandListImmediate& InRHICmdList)
{
UEngine::SetPresentLatencyMarkerStart(CurrentFrameCounter);
});
FRHITexture* OptionalSDRBuffer = nullptr;
RHICmdList.BeginDrawingViewport(DrawPassOutputs.ViewportRHI, FTextureRHIRef());
if (GRHIGlobals.NeedsExtraTransitions)
{
RHICmdList.Transition(FRHITransitionInfo(DrawPassOutputs.OutputTextureRHI, ERHIAccess::Unknown, ERHIAccess::Present));
OptionalSDRBuffer = DrawPassOutputs.ViewportRHI->GetOptionalSDRBackBuffer(DrawPassOutputs.OutputTextureRHI);
if (OptionalSDRBuffer)
{
RHICmdList.Transition(FRHITransitionInfo(OptionalSDRBuffer, ERHIAccess::Unknown, ERHIAccess::Present));
}
}
RHICmdList.EndDrawingViewport(DrawPassOutputs.ViewportRHI, true, DrawPassInputs.bLockToVsync);
RHICmdList.EnqueueLambda([CurrentFrameCounter = GFrameCounterRenderThread](FRHICommandListImmediate& InRHICmdList)
{
UEngine::SetPresentLatencyMarkerEnd(CurrentFrameCounter);
});
uint32 EndTime = FPlatformTime::Cycles();
GSwapBufferTime = EndTime - StartTime;
SET_CYCLE_COUNTER(STAT_PresentTime, GSwapBufferTime);
static uint32 LastTimestamp = FPlatformTime::Cycles();
uint32 ThreadTime = EndTime - LastTimestamp;
LastTimestamp = EndTime;
uint32 RenderThreadIdle = 0;
UE::Stats::FThreadIdleStats& RenderThread = UE::Stats::FThreadIdleStats::Get();
GRenderThreadIdle[ERenderThreadIdleTypes::WaitingForAllOtherSleep] = RenderThread.Waits;
GRenderThreadIdle[ERenderThreadIdleTypes::WaitingForGPUPresent] += GSwapBufferTime;
SET_CYCLE_COUNTER(STAT_RenderingIdleTime_RenderThreadSleepTime, GRenderThreadIdle[ERenderThreadIdleTypes::WaitingForAllOtherSleep]);
SET_CYCLE_COUNTER(STAT_RenderingIdleTime_WaitingForGPUQuery , GRenderThreadIdle[ERenderThreadIdleTypes::WaitingForGPUQuery ]);
SET_CYCLE_COUNTER(STAT_RenderingIdleTime_WaitingForGPUPresent , GRenderThreadIdle[ERenderThreadIdleTypes::WaitingForGPUPresent ]);
const uint32 RenderThreadNonCriticalWaits = RenderThread.Waits - RenderThread.WaitsCriticalPath;
const uint32 RenderThreadWaitingForGPUQuery = GRenderThreadIdle[ERenderThreadIdleTypes::WaitingForGPUQuery];
// Set the RenderThreadIdle CSV stats
CSV_CUSTOM_STAT(RenderThreadIdle, Total , FPlatformTime::ToMilliseconds(RenderThread.Waits ), ECsvCustomStatOp::Set);
CSV_CUSTOM_STAT(RenderThreadIdle, CriticalPath , FPlatformTime::ToMilliseconds(RenderThread.WaitsCriticalPath), ECsvCustomStatOp::Set);
CSV_CUSTOM_STAT(RenderThreadIdle, SwapBuffer , FPlatformTime::ToMilliseconds(GSwapBufferTime ), ECsvCustomStatOp::Set);
CSV_CUSTOM_STAT(RenderThreadIdle, NonCriticalPath, FPlatformTime::ToMilliseconds(RenderThreadNonCriticalWaits ), ECsvCustomStatOp::Set);
CSV_CUSTOM_STAT(RenderThreadIdle, GPUQuery , FPlatformTime::ToMilliseconds(RenderThreadWaitingForGPUQuery), ECsvCustomStatOp::Set);
for (int32 Index = 0; Index < ERenderThreadIdleTypes::Num; Index++)
{
RenderThreadIdle += GRenderThreadIdle[Index];
GRenderThreadIdle[Index] = 0;
}
SET_CYCLE_COUNTER(STAT_RenderingIdleTime, RenderThreadIdle);
GRenderThreadTime = (ThreadTime > RenderThreadIdle) ? (ThreadTime - RenderThreadIdle) : ThreadTime;
GRenderThreadWaitTime = RenderThreadIdle;
// Compute GRenderThreadTimeCriticalPath
uint32 RenderThreadNonCriticalPathIdle = RenderThreadIdle - RenderThread.WaitsCriticalPath;
GRenderThreadTimeCriticalPath = (ThreadTime > RenderThreadNonCriticalPathIdle) ? (ThreadTime - RenderThreadNonCriticalPathIdle) : ThreadTime;
SET_CYCLE_COUNTER(STAT_RenderThreadCriticalPath, GRenderThreadTimeCriticalPath);
if (CVarRenderThreadTimeIncludesDependentWaits.GetValueOnRenderThread())
{
// Optionally force the renderthread stat to include dependent waits
GRenderThreadTime = GRenderThreadTimeCriticalPath;
}
// Reset the idle stats
RenderThread.Reset();
static TOptional<uint32> RHITCycles;
if (IsRunningRHIInSeparateThread())
{
RHICmdList.EnqueueLambda([](FRHICommandListImmediate&)
{
// Update RHI thread time
UE::Stats::FThreadIdleStats& RHIThreadStats = UE::Stats::FThreadIdleStats::Get();
if (!RHITCycles.IsSet())
{
RHITCycles = FPlatformTime::Cycles();
}
uint32 Next = FPlatformTime::Cycles();
int32 Result = int32(Next - RHITCycles.GetValue() - RHIThreadStats.Waits);
RHITCycles = Next;
FPlatformAtomics::AtomicStore((int32*)&GRHIThreadTime, FMath::Max(Result, 0));
RHIThreadStats.Reset();
});
}
else
{
RHITCycles.Reset();
}
}
void FSlateRHIRenderer::DrawWindows(FSlateDrawBuffer& WindowDrawBuffer)
{
DrawWindows_Private(WindowDrawBuffer);
}
void FSlateRHIRenderer::PrepareToTakeScreenshot(const FIntRect& Rect, TArray<FColor>* OutColorData, SWindow* InScreenshotWindow)
{
check(OutColorData);
ScreenshotState.ViewRect = Rect;
ScreenshotState.ViewportToCapture = WindowToViewportInfo.FindRef(InScreenshotWindow);
ScreenshotState.ColorData = OutColorData;
ScreenshotState.ColorDataHDR = nullptr;
}
void FSlateRHIRenderer::PrepareToTakeHDRScreenshot(const FIntRect& Rect, TArray<FLinearColor>* OutColorData, SWindow* InScreenshotWindow)
{
check(OutColorData);
ScreenshotState.ViewRect = Rect;
ScreenshotState.ViewportToCapture = WindowToViewportInfo.FindRef(InScreenshotWindow);
ScreenshotState.ColorData = nullptr;
ScreenshotState.ColorDataHDR = OutColorData;
}
struct FSlateDrawWindowsCommand : public TConcurrentLinearObject<FSlateDrawWindowsCommand>
{
bool IsEmpty() { return Windows.IsEmpty() && DeferredUpdates.IsEmpty(); }
TArray<FSlateDrawWindowPassInputs, FConcurrentLinearArrayAllocator> Windows;
TArray<FSlatePostProcessUpdateRequest, FConcurrentLinearArrayAllocator> PostProcessUpdates;
TArray<FRenderThreadUpdateContext, FConcurrentLinearArrayAllocator> DeferredUpdates;
};
void FSlateRHIRenderer::DrawWindows_RenderThread(FRHICommandListImmediate& RHICmdList, TConstArrayView<FSlateDrawWindowPassInputs> Windows, TConstArrayView<FRenderThreadUpdateContext> DeferredUpdates)
{
struct FWindowPresentCommand
{
FWindowPresentCommand(const FSlateDrawWindowPassInputs& InInputs, const FSlateDrawWindowPassOutputs& InOutputs)
: Inputs(InInputs)
, Outputs(InOutputs)
{}
const FSlateDrawWindowPassInputs& Inputs;
FSlateDrawWindowPassOutputs Outputs;
};
TArray<FWindowPresentCommand, FConcurrentLinearArrayAllocator> WindowPresentCommands;
WindowPresentCommands.Reserve(Windows.Num());
int32 WindowIndex = 0;
do
{
{
FRDGBuilder GraphBuilder(RHICmdList, RDG_EVENT_NAME("Slate"), ERDGBuilderFlags::ParallelSetup | ERDGBuilderFlags::ParallelExecute);
for (const FRenderThreadUpdateContext& DeferredUpdateContext : DeferredUpdates)
{
DeferredUpdateContext.Renderer->DrawWindowToTarget_RenderThread(GraphBuilder, DeferredUpdateContext);
}
// D3D12 can't handle more than 8 swap chains at a time, start a new batch if we hit this amount and continue with a new builder.
for (int32 NumWindows = 0; NumWindows < 8 && WindowIndex < Windows.Num(); ++NumWindows, ++WindowIndex)
{
const FSlateDrawWindowPassInputs& DrawWindowPassInputs = Windows[WindowIndex];
WindowPresentCommands.Emplace(DrawWindowPassInputs, DrawWindow_RenderThread(GraphBuilder, DrawWindowPassInputs));
}
GraphBuilder.AddDispatchHint();
GraphBuilder.Execute();
}
for (const FRenderThreadUpdateContext& DeferredUpdateContext : DeferredUpdates)
{
DeferredUpdateContext.Renderer->ReleaseDrawBuffer(*DeferredUpdateContext.WindowDrawBuffer);
}
for (const FWindowPresentCommand& Command : WindowPresentCommands)
{
PresentWindow_RenderThread(RHICmdList, Command.Inputs, Command.Outputs);
}
WindowPresentCommands.Reset();
DeferredUpdates = {};
} while (WindowIndex < Windows.Num());
}
void FSlateRHIRenderer::DrawWindows_Private(FSlateDrawBuffer& WindowDrawBuffer)
{
checkSlow(IsThreadSafeForSlateRendering());
CSV_SCOPED_TIMING_STAT(Slate, DrawWindows_Private);
if (bUpdateHDRDisplayInformation && IsHDRAllowed() && IsInGameThread())
{
FlushRenderingCommands();
RHIHandleDisplayChange();
bUpdateHDRDisplayInformation = false;
}
if (DoesThreadOwnSlateRendering())
{
ResourceManager->UpdateTextureAtlases();
}
USlateRHIRendererSettings* RendererSettings = USlateRHIRendererSettings::GetMutable();
const float AppDeltaTime = FApp::GetDeltaTime();
const FGameTime AppDilatedTime = FGameTime::CreateDilated(FPlatformTime::Seconds() - GStartTime, AppDeltaTime, FApp::GetCurrentTime() - GStartTime, AppDeltaTime);
const FVector2f AppCursorPosition = FSlateApplication::Get().GetCursorPos();
const bool bAppCanRender = GIsClient && !IsRunningCommandlet() && !GUsingNullRHI;
const bool bAppCanRenderPostProcess = RendererSettings && IsInGameThread() && UAssetManager::IsInitialized() && bAppCanRender && CVarCopyBackbufferToSlatePostRenderTargets.GetValueOnGameThread() > 0;
EPixelFormat AppViewportSceneFormat = PF_Unknown;
FIntPoint AppViewportExtentMax = FIntPoint::ZeroValue;
ESlatePostRT PostProcessAnyUsedBits = ESlatePostRT::None;
const TSharedRef<FSlateFontCache> FontCache = SlateFontServices->GetFontCache();
struct FWindowToRender
{
SWindow* Window;
FSlateWindowElementList* WindowElementList;
FSlateViewportInfo* ViewportInfo;
FIntPoint ViewportOffset;
FIntPoint ViewportExtent;
FIntRect ViewportRect;
float ViewportScaleUI;
ESlatePostRT PostProcessUsedBits;
ESlatePostRT PostProcessCustumDrawBits;
ESlatePostRT PostProcessSkipUpdateBits;
FIntPoint CursorPosition;
bool bLockToVsync;
};
TArray<FWindowToRender, FConcurrentLinearArrayAllocator> WindowsToRender;
if (bAppCanRender)
{
WindowsToRender.Reserve(WindowDrawBuffer.GetWindowElementLists().Num());
for (const TSharedRef<FSlateWindowElementList>& WindowElementListRef : WindowDrawBuffer.GetWindowElementLists())
{
FSlateWindowElementList* WindowElementList = &(*WindowElementListRef);
SWindow* Window = WindowElementList->GetRenderWindow();
if (!Window)
{
ensureMsgf(false, TEXT("Window isn't valid but being drawn!"));
continue;
}
// This will return zero if both the viewport and the window are zero sized.
const FVector2f WindowSize = Window->GetViewportSize();
if (WindowSize.X <= 0.0f || WindowSize.Y <= 0.0f)
{
continue;
}
TRACE_CPUPROFILER_EVENT_SCOPE(GatherWindowElements);
// It's possible for a window to not have a viewport, in which case the viewport dimensions will be zero.
FVector2f ViewportCursorPosition = AppCursorPosition - Window->GetPositionInScreen();
FIntPoint ViewportOffset = FIntPoint::ZeroValue;
FIntPoint ViewportExtent = FIntPoint::ZeroValue;
FIntRect ViewportRect;
float ViewportScaleUI = Window->GetViewportScaleUIOverride();
if (ISlateViewport* Viewport = Window->GetViewport().Get())
{
TSharedPtr<SWidget> ViewportWidget = Viewport->GetWidget().Pin();
if (ViewportWidget)
{
ViewportOffset = FIntPoint(
FMath::RoundToInt32(ViewportWidget->GetTickSpaceGeometry().GetAbsolutePosition().X - Window->GetPositionInScreen().X),
FMath::RoundToInt32(ViewportWidget->GetTickSpaceGeometry().GetAbsolutePosition().Y - Window->GetPositionInScreen().Y));
ViewportCursorPosition -= ViewportWidget->GetPaintSpaceGeometry().AbsolutePosition;
}
ViewportExtent = Viewport->GetSize();
ViewportRect = FIntRect(ViewportOffset, ViewportOffset + ViewportExtent);
ensureMsgf(AppViewportSceneFormat == PF_Unknown || AppViewportSceneFormat == Viewport->GetSceneTargetFormat(),
TEXT("Multiple viewport formats coming from multiple windows are not a supported scenario in slate. This will cause undefined behavior with Slate Post Buffers."));
AppViewportSceneFormat = Viewport->GetSceneTargetFormat();
AppViewportExtentMax = AppViewportExtentMax.ComponentMax(ViewportExtent);
if (ViewportScaleUI < 0.0f)
{
ViewportScaleUI = GetDefault<UUserInterfaceSettings>()->GetDPIScaleBasedOnSize(ViewportExtent);
}
}
if (FSlateViewportInfo* ViewportInfo = WindowToViewportInfo.FindRef(Window))
{
if (Window->IsViewportSizeDrivenByWindow())
{
ResizeViewportIfNeeded(ViewportInfo, ViewportInfo->ExtentToResizeTo, IsViewportFullscreen(*Window), Window);
}
Window->SetIsHDR(ViewportInfo->bDisplayFormatIsHDR);
Window->ResetViewportScaleUIOverride();
ElementBatcher->SetCompositeHDRViewports(ViewportInfo->bDisplayFormatIsHDR && CompositeUIWithSceneHDR());
ElementBatcher->AddElements(*WindowElementList);
const bool bWindowCanRenderPostProcess = bAppCanRender && ViewportExtent != FIntPoint::ZeroValue;
const ESlatePostRT PostProcessUsedBits = bWindowCanRenderPostProcess ? ElementBatcher->GetUsedSlatePostBuffers() : ESlatePostRT::None;
const ESlatePostRT PostProcessCustomDrawBits = bWindowCanRenderPostProcess ? ElementBatcher->GetResourceUpdatingPostBuffers() : ESlatePostRT::None;
const ESlatePostRT PostProcessSkipUpdateBits = bWindowCanRenderPostProcess ? ElementBatcher->GetSkipDefaultUpdatePostBuffers() : ESlatePostRT::None;
const bool bLockToVsync = IsVSyncRequired(*ElementBatcher);
ElementBatcher->ResetBatches();
ElementBatcher->SetCompositeHDRViewports(false);
WindowsToRender.Emplace(FWindowToRender
{
.Window = Window
, .WindowElementList = WindowElementList
, .ViewportInfo = ViewportInfo
, .ViewportOffset = ViewportOffset
, .ViewportExtent = ViewportExtent
, .ViewportRect = ViewportRect
, .ViewportScaleUI = ViewportScaleUI
, .PostProcessUsedBits = PostProcessUsedBits
, .PostProcessCustumDrawBits = PostProcessCustomDrawBits
, .PostProcessSkipUpdateBits = PostProcessSkipUpdateBits
, .CursorPosition = FIntPoint(ViewportCursorPosition.X, ViewportCursorPosition.Y)
, .bLockToVsync = bLockToVsync
});
PostProcessAnyUsedBits |= PostProcessUsedBits;
}
}
}
// Update the font cache now that all element batches were processed.
FontCache->UpdateCache();
// Allocate any post process render targets that are used by any viewport.
if (bAppCanRenderPostProcess)
{
if (AppViewportExtentMax.X != 0 && AppViewportExtentMax.Y != 0)
{
for (ESlatePostRT Bit : MakeFlagsRange(PostProcessAnyUsedBits))
{
if (UTextureRenderTarget2D* RenderTarget = RendererSettings->LoadGetPostBufferRT(Bit))
{
if (RenderTarget->SizeX != AppViewportExtentMax.X || RenderTarget->SizeY != AppViewportExtentMax.Y || RenderTarget->GetFormat() != AppViewportSceneFormat)
{
TRACE_CPUPROFILER_EVENT_SCOPE(AllocatePostProcessTexture);
RenderTarget->InitCustomFormat(AppViewportExtentMax.X, AppViewportExtentMax.Y, AppViewportSceneFormat, true);
}
PostProcessRenderTargets.LastUsedFrameCounter[(int32)Bit] = GFrameCounter;
}
}
}
for (ESlatePostRT Bit : MakeFlagsRange(ESlatePostRT::All & ~PostProcessAnyUsedBits))
{
UTextureRenderTarget2D* RenderTarget = RendererSettings->TryGetPostBufferRT(Bit);;
if (RenderTarget
&& RenderTarget->GetResource()
&& RenderTarget->SizeX != 1
&& RenderTarget->SizeY != 1
&& PostProcessRenderTargets.LastUsedFrameCounter[(int32)Bit] < GFrameCounter)
{
// Trim unused post process render targets down to 1x1 to reclaim memory.
TRACE_CPUPROFILER_EVENT_SCOPE(TrimPostProcessTexture);
RenderTarget->InitCustomFormat(1, 1, PF_A2B10G10R10, true);
}
}
}
TUniquePtr<FSlateDrawWindowsCommand> DrawWindowsCommand = MakeUnique<FSlateDrawWindowsCommand>();
DrawWindowsCommand->Windows.Reserve(WindowsToRender.Num());
DrawWindowsCommand->DeferredUpdates = MoveTemp(DeferredUpdateContexts);
DrawWindowsCommand->PostProcessUpdates.Reserve(WindowsToRender.Num() * FMath::CountBits((int32)PostProcessAnyUsedBits));
bool bScreenshotProcessed = false;
for (const FWindowToRender& WindowToRender : WindowsToRender)
{
const int32 PostProcessUpdatesOffset = DrawWindowsCommand->PostProcessUpdates.Num();
if (bAppCanRenderPostProcess)
{
// Process bits that were NOT marked to skip the update.
for (ESlatePostRT Bit : MakeFlagsRange(ESlatePostRT::All & WindowToRender.PostProcessUsedBits & ~WindowToRender.PostProcessSkipUpdateBits))
{
UTextureRenderTarget2D* RenderTarget = RendererSettings->TryGetPostBufferRT(Bit);
check(RenderTarget);
FSlatePostProcessUpdateRequest Request;
Request.RenderTarget = Bit;
Request.RenderTargetTextureResource = RenderTarget->GetResource();
if (USlateRHIPostBufferProcessor* PostProcessor = USlateFXSubsystem::GetPostProcessor(Bit))
{
Request.PostProcessorProxy = PostProcessor->GetRenderThreadProxy();
}
checkf(DrawWindowsCommand->PostProcessUpdates.Num() != DrawWindowsCommand->PostProcessUpdates.Max(), TEXT("This container is about to resize which will result in a dangling memory access"));
DrawWindowsCommand->PostProcessUpdates.Add(Request);
}
}
TConstArrayView<FSlatePostProcessUpdateRequest> PostProcessUpdatesForWindow;
const int32 PostProcessUpdateRequestsCount = DrawWindowsCommand->PostProcessUpdates.Num() - PostProcessUpdatesOffset;
if (PostProcessUpdateRequestsCount != 0)
{
PostProcessUpdatesForWindow = MakeArrayView(&DrawWindowsCommand->PostProcessUpdates[PostProcessUpdatesOffset], PostProcessUpdateRequestsCount);
}
bScreenshotProcessed |= ScreenshotState.ViewportToCapture == WindowToRender.ViewportInfo;
if (bAppCanRender)
{
DrawWindowsCommand->Windows.Emplace(FSlateDrawWindowPassInputs
{
.Renderer = this
, .WindowElementList = WindowToRender.WindowElementList
, .Window = WindowToRender.Window
, .ViewportInfo = WindowToRender.ViewportInfo
, .PostProcessUpdateRequests = PostProcessUpdatesForWindow
, .CursorPosition = WindowToRender.CursorPosition
, .SceneViewRect = WindowToRender.ViewportRect
, .ViewportScaleUI = WindowToRender.ViewportScaleUI
, .UsedSlatePostBuffers = WindowToRender.PostProcessUsedBits
#if WANTS_DRAW_MESH_EVENTS
, .WindowTitle = WindowToRender.Window->GetTitle().ToString()
#endif
, .Time = AppDilatedTime
, .bLockToVsync = WindowToRender.bLockToVsync
#if ALPHA_BLENDED_WINDOWS
, .bClear = WindowToRender.Window->GetTransparencySupport() == EWindowTransparency::PerPixel
#endif
});
}
}
if (!DrawWindowsCommand->IsEmpty())
{
ENQUEUE_RENDER_COMMAND(SlateDrawWindowsCommand)([this, DrawWindowsCommand = MoveTemp(DrawWindowsCommand)](FRHICommandListImmediate& RHICmdList)
{
DrawWindows_RenderThread(RHICmdList, DrawWindowsCommand->Windows, DrawWindowsCommand->DeferredUpdates);
});
check(DeferredUpdateContexts.IsEmpty());
}
if (bScreenshotProcessed)
{
FlushRenderingCommands();
ScreenshotState = {};
}
for (const FWindowToRender& WindowToRender : WindowsToRender)
{
SlateWindowRendered.Broadcast(*WindowToRender.Window, &WindowToRender.ViewportInfo->ViewportRHI);
}
FlushPendingDeletes();
FontCache->ConditionalFlushCache();
ResourceManager->ConditionalFlushAtlases();
}
FIntPoint FSlateRHIRenderer::GenerateDynamicImageResource(const FName InTextureName)
{
check(IsInGameThread());
uint32 Width = 0;
uint32 Height = 0;
TArray<uint8> RawData;
TSharedPtr<FSlateDynamicTextureResource> TextureResource = ResourceManager->GetDynamicTextureResourceByName(InTextureName);
if (!TextureResource.IsValid())
{
// Load the image from disk
bool bSucceeded = ResourceManager->LoadTexture(InTextureName, InTextureName.ToString(), Width, Height, RawData);
if (bSucceeded)
{
TextureResource = ResourceManager->MakeDynamicTextureResource(InTextureName, Width, Height, RawData);
}
}
return TextureResource.IsValid() ? TextureResource->Proxy->ActualSize : FIntPoint(0, 0);
}
bool FSlateRHIRenderer::GenerateDynamicImageResource(FName ResourceName, uint32 Width, uint32 Height, const TArray< uint8 >& Bytes)
{
check(IsInGameThread());
TSharedPtr<FSlateDynamicTextureResource> TextureResource = ResourceManager->GetDynamicTextureResourceByName(ResourceName);
if (!TextureResource.IsValid())
{
TextureResource = ResourceManager->MakeDynamicTextureResource(ResourceName, Width, Height, Bytes);
}
return TextureResource.IsValid();
}
bool FSlateRHIRenderer::GenerateDynamicImageResource(FName ResourceName, FSlateTextureDataRef TextureData)
{
check(IsInGameThread());
TSharedPtr<FSlateDynamicTextureResource> TextureResource = ResourceManager->GetDynamicTextureResourceByName(ResourceName);
if (!TextureResource.IsValid())
{
TextureResource = ResourceManager->MakeDynamicTextureResource(ResourceName, TextureData);
}
return TextureResource.IsValid();
}
FSlateResourceHandle FSlateRHIRenderer::GetResourceHandle(const FSlateBrush& Brush, FVector2f LocalSize, float DrawScale)
{
return ResourceManager->GetResourceHandle(Brush, LocalSize, DrawScale);
}
bool FSlateRHIRenderer::CanRenderResource(UObject& InResourceObject) const
{
return Cast<UTexture>(&InResourceObject) || Cast<ISlateTextureAtlasInterface>(&InResourceObject) || Cast<UMaterialInterface>(&InResourceObject);
}
void FSlateRHIRenderer::RemoveDynamicBrushResource( TSharedPtr<FSlateDynamicImageBrush> BrushToRemove )
{
if (BrushToRemove.IsValid())
{
DynamicBrushesToRemove[FreeBufferIndex].Add(BrushToRemove);
}
}
void FSlateRHIRenderer::FlushCommands() const
{
if (IsInGameThread() || IsInSlateThread())
{
FlushRenderingCommands();
}
}
void FSlateRHIRenderer::Sync() const
{
FFrameEndSync::Sync(FFrameEndSync::EFlushMode::EndFrame);
}
void FSlateRHIRenderer::BeginFrame() const
{
ENQUEUE_RENDER_COMMAND(SlateRHIBeginFrame)([](FRHICommandListImmediate& RHICmdList)
{
// Suspend stat gathering when running modal dialog 'fake' frame loops
GPU_STATS_SUSPENDFRAME();
});
}
void FSlateRHIRenderer::EndFrame() const
{
ENQUEUE_RENDER_COMMAND(SlateRHIEndFrame)([](FRHICommandListImmediate& RHICmdList)
{
RHICmdList.EndFrame();
});
}
void FSlateRHIRenderer::ReloadTextureResources()
{
ResourceManager->ReloadTextures();
}
void FSlateRHIRenderer::LoadUsedTextures()
{
if (ResourceManager.IsValid())
{
ResourceManager->LoadUsedTextures();
}
}
void FSlateRHIRenderer::LoadStyleResources(const ISlateStyle& Style)
{
if (ResourceManager.IsValid())
{
ResourceManager->LoadStyleResources(Style);
}
}
void FSlateRHIRenderer::ReleaseDynamicResource(const FSlateBrush& InBrush)
{
ensure(IsInGameThread());
ResourceManager->ReleaseDynamicResource(InBrush);
}
void* FSlateRHIRenderer::GetViewportResource(const SWindow& Window)
{
checkSlow(IsThreadSafeForSlateRendering());
FSlateViewportInfo** InfoPtr = WindowToViewportInfo.Find(&Window);
if (InfoPtr)
{
FSlateViewportInfo* ViewportInfo = *InfoPtr;
if (!IsValidRef(ViewportInfo->ViewportRHI))
{
checkf(ViewportInfo->Extent.X <= MAX_VIEWPORT_SIZE && ViewportInfo->Extent.Y <= MAX_VIEWPORT_SIZE, TEXT("Invalid window with Width=%u and Height=%u"), ViewportInfo->Extent.X, ViewportInfo->Extent.Y);
ViewportInfo->ViewportRHI = RHICreateViewport(ViewportInfo->OSWindow, ViewportInfo->Extent.X, ViewportInfo->Extent.Y, IsViewportFullscreen(Window), ViewportInfo->PixelFormat);
}
return &ViewportInfo->ViewportRHI;
}
return nullptr;
}
void FSlateRHIRenderer::SetColorVisionDeficiencyType(EColorVisionDeficiency Type, int32 Severity, bool bCorrectDeficiency, bool bShowCorrectionWithDeficiency)
{
GSlateColorDeficiencyType = Type;
GSlateColorDeficiencySeverity = FMath::Clamp(Severity, 0, 10);
GSlateColorDeficiencyCorrection = bCorrectDeficiency;
GSlateShowColorDeficiencyCorrectionWithDeficiency = bShowCorrectionWithDeficiency;
}
FSlateUpdatableTexture* FSlateRHIRenderer::CreateUpdatableTexture(uint32 Width, uint32 Height)
{
const bool bCreateEmptyTexture = true;
FSlateTexture2DRHIRef* NewTexture = new FSlateTexture2DRHIRef(Width, Height, GetSlateRecommendedColorFormat(), nullptr, TexCreate_None, bCreateEmptyTexture);
BeginInitResource(NewTexture);
return NewTexture;
}
FSlateUpdatableTexture* FSlateRHIRenderer::CreateSharedHandleTexture(void* SharedHandle)
{
return nullptr;
}
void FSlateRHIRenderer::ReleaseUpdatableTexture(FSlateUpdatableTexture* Texture)
{
if (IsInRenderingThread())
{
Texture->GetRenderResource()->ReleaseResource();
delete Texture;
}
else
{
Texture->Cleanup();
}
}
ISlateAtlasProvider* FSlateRHIRenderer::GetTextureAtlasProvider()
{
if (ResourceManager.IsValid())
{
return ResourceManager->GetTextureAtlasProvider();
}
return nullptr;
}
int32 FSlateRHIRenderer::RegisterCurrentScene(FSceneInterface* Scene)
{
check(IsInGameThread());
if (Scene && Scene->GetWorld())
{
CurrentSceneIndex = ActiveScenes.IndexOfByPredicate([&Scene](const FSceneInterface* TestScene) { return TestScene->GetWorld() == Scene->GetWorld(); });
if (CurrentSceneIndex == INDEX_NONE)
{
CurrentSceneIndex = ActiveScenes.Add(Scene);
if (CurrentSceneIndex >= 0)
{
ENQUEUE_RENDER_COMMAND(RegisterCurrentSceneOnPolicy)([RenderingPolicy = RenderingPolicy.Get(), Scene, CurrentSceneIndex = CurrentSceneIndex](FRHICommandListBase&)
{
RenderingPolicy->AddSceneAt(Scene, CurrentSceneIndex);
});
}
}
}
else
{
CurrentSceneIndex = -1;
}
return CurrentSceneIndex;
}
int32 FSlateRHIRenderer::GetCurrentSceneIndex() const
{
return CurrentSceneIndex;
}
void FSlateRHIRenderer::SetCurrentSceneIndex(int32 InIndex)
{
CurrentSceneIndex = InIndex;
}
void FSlateRHIRenderer::ClearScenes()
{
if (!IsInSlateThread())
{
CurrentSceneIndex = -1;
ActiveScenes.Empty();
ENQUEUE_RENDER_COMMAND(ClearScenesOnPolicy)([RenderingPolicy = RenderingPolicy.Get()](FRHICommandListBase&)
{
RenderingPolicy->ClearScenes();
});
}
}
EPixelFormat FSlateRHIRenderer::GetSlateRecommendedColorFormat()
{
return bIsStandaloneStereoOnlyDevice ? PF_R8G8B8A8 : PF_B8G8R8A8;
}
void FSlateRHIRenderer::DestroyCachedFastPathRenderingData(FSlateCachedFastPathRenderingData* CachedRenderingData)
{
check(CachedRenderingData);
PendingDeletes.CachedRenderingData.Emplace(CachedRenderingData);
}
void FSlateRHIRenderer::DestroyCachedFastPathElementData(FSlateCachedElementData* CachedElementData)
{
check(CachedElementData);
PendingDeletes.CachedElementData.Emplace(CachedElementData);
}
void FSlateRHIRenderer::FlushPendingDeletes()
{
if (!PendingDeletes.IsEmpty())
{
ENQUEUE_RENDER_COMMAND(SlateDeferredDelete)([PendingDeletes = MoveTemp(PendingDeletes)](FRHICommandListBase&)
{
for (FSlateCachedFastPathRenderingData* Data : PendingDeletes.CachedRenderingData)
{
delete Data;
}
for (FSlateCachedElementData* Data : PendingDeletes.CachedElementData)
{
delete Data;
}
});
PendingDeletes = {};
}
}
bool FSlateRHIRenderer::AreShadersInitialized() const
{
#if WITH_EDITORONLY_DATA
static bool bSlateShadersInitialized = false;
static FDelegateHandle GlobalShaderCompilationDelegateHandle;
if (!bSlateShadersInitialized)
{
bSlateShadersInitialized = IsGlobalShaderMapComplete(TEXT("SlateElement"));
// if shaders are initialized, cache the value until global shaders gets recompiled.
if (bSlateShadersInitialized)
{
GlobalShaderCompilationDelegateHandle = GetOnGlobalShaderCompilation().AddLambda([]()
{
bSlateShadersInitialized = false;
GetOnGlobalShaderCompilation().Remove(GlobalShaderCompilationDelegateHandle);
});
}
}
return bSlateShadersInitialized;
#else
return true;
#endif
}
void FSlateRHIRenderer::InvalidateAllViewports()
{
for (auto& Entry : WindowToViewportInfo)
{
Entry.Value->ViewportRHI = nullptr;
}
}
FCriticalSection* FSlateRHIRenderer::GetResourceCriticalSection()
{
return ResourceManager->GetResourceCriticalSection();
}
void FSlateRHIRenderer::ReleaseAccessedResources(bool bImmediatelyFlush)
{
// We keep track of the Scene objects from SceneViewports on the SlateRenderer. Make sure that this gets refreshed every frame.
ClearScenes();
if (bImmediatelyFlush)
{
// Increment resource version to allow buffers to shrink or cached structures to clean up.
ResourceVersion++;
}
}
void FSlateRHIRenderer::RequestResize(const TSharedPtr<SWindow>& Window, uint32 NewWidth, uint32 NewHeight)
{
checkSlow(IsThreadSafeForSlateRendering());
FSlateViewportInfo* ViewInfo = WindowToViewportInfo.FindRef(Window.Get());
if (ViewInfo)
{
ViewInfo->ExtentToResizeTo.X = NewWidth;
ViewInfo->ExtentToResizeTo.Y = NewHeight;
}
}
void FSlateRHIRenderer::AddWidgetRendererUpdate(const FRenderThreadUpdateContext& Context, bool bDeferredRenderTargetUpdate)
{
if (bDeferredRenderTargetUpdate)
{
DeferredUpdateContexts.Add(Context);
}
else
{
ENQUEUE_RENDER_COMMAND(DrawWidgetRendererImmediate)([Context](FRHICommandListImmediate& RHICmdList)
{
FRDGBuilder GraphBuilder(RHICmdList, RDG_EVENT_NAME("SlateWidgetRender"), ERDGBuilderFlags::ParallelSetup | ERDGBuilderFlags::ParallelExecute);
Context.Renderer->DrawWindowToTarget_RenderThread(GraphBuilder, Context);
GraphBuilder.Execute();
});
}
}