756 lines
25 KiB
C++
756 lines
25 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "Slate/SRetainerWidget.h"
|
|
#include "Misc/App.h"
|
|
#include "UObject/Package.h"
|
|
#include "Framework/Application/SlateApplication.h"
|
|
#include "Materials/MaterialInstanceDynamic.h"
|
|
#include "RenderDeferredCleanup.h"
|
|
#include "RHI.h"
|
|
#include "Engine/TextureRenderTarget2D.h"
|
|
#include "Engine/World.h"
|
|
#include "UMGPrivate.h"
|
|
#include "Widgets/SNullWidget.h"
|
|
|
|
DECLARE_CYCLE_STAT(TEXT("Retainer Widget Tick"), STAT_SlateRetainerWidgetTick, STATGROUP_Slate);
|
|
DECLARE_CYCLE_STAT(TEXT("Retainer Widget Paint"), STAT_SlateRetainerWidgetPaint, STATGROUP_Slate);
|
|
|
|
#if !UE_BUILD_SHIPPING
|
|
FOnRetainedModeChanged SRetainerWidget::OnRetainerModeChangedDelegate;
|
|
#endif
|
|
|
|
static void HandleDesignerRetainedRenderingToggled(IConsoleVariable* CVar)
|
|
{
|
|
FSlateApplication::Get().InvalidateAllWidgets(false);
|
|
}
|
|
|
|
/** True if we should do retained rendering in designer. */
|
|
bool GEnableDesignerRetainedRendering = true;
|
|
FAutoConsoleVariableRef EnableDesignerRetainedRendering(
|
|
TEXT("Slate.EnableDesignerRetainedRendering"),
|
|
GEnableDesignerRetainedRendering,
|
|
TEXT("Controls if retainer renders in designer; 0 - Never; 1 - Per Widget Properties"),
|
|
FConsoleVariableDelegate::CreateStatic(&HandleDesignerRetainedRenderingToggled)
|
|
);
|
|
|
|
/** True if we should allow widgets to be cached in the UI at all. */
|
|
int32 GEnableRetainedRendering = 1;
|
|
FAutoConsoleVariableRef EnableRetainedRendering(
|
|
TEXT("Slate.EnableRetainedRendering"),
|
|
GEnableRetainedRendering,
|
|
TEXT("Whether to attempt to render things in SRetainerWidgets to render targets first.")
|
|
);
|
|
|
|
/** True if we allow the transform to be modify to render in local space. */
|
|
bool GSlateEnableRenderWithLocalTransform = true;
|
|
FAutoConsoleVariableRef CVarGlateEnableRenderWithLocalTransform(
|
|
TEXT("Slate.EnableRetainedRenderingWithLocalTransform"),
|
|
GSlateEnableRenderWithLocalTransform,
|
|
TEXT("This console variable is no longer useful, as the SRetainerWidget render bugs workedaround by it have been fixed. It will be removed.")
|
|
);
|
|
|
|
static bool IsRetainedRenderingEnabled()
|
|
{
|
|
return GEnableRetainedRendering != 0;
|
|
}
|
|
|
|
/** Whether or not the platform should have deferred retainer widget render target updating enabled by default */
|
|
#define PLATFORM_REQUIRES_DEFERRED_RETAINER_UPDATE PLATFORM_IOS || PLATFORM_ANDROID;
|
|
|
|
/**
|
|
* If this is true the retained rendering render thread work will happen during normal slate render thread rendering after the back buffer has been presented
|
|
* in order to avoid extra render target switching in the middle of the frame. The downside is that the UI update will be a frame late
|
|
*/
|
|
int32 GDeferRetainedRenderingRenderThread = PLATFORM_REQUIRES_DEFERRED_RETAINER_UPDATE;
|
|
FAutoConsoleVariableRef DeferRetainedRenderingRT(
|
|
TEXT("Slate.DeferRetainedRenderingRenderThread"),
|
|
GDeferRetainedRenderingRenderThread,
|
|
TEXT("Whether or not to defer retained rendering to happen at the same time as the rest of slate render thread work"));
|
|
|
|
|
|
class FRetainerWidgetRenderingResources : public FDeferredCleanupInterface, public FGCObject
|
|
{
|
|
public:
|
|
FRetainerWidgetRenderingResources()
|
|
: WidgetRenderer(nullptr)
|
|
, RenderTarget(nullptr)
|
|
, DynamicEffect(nullptr)
|
|
{}
|
|
|
|
~FRetainerWidgetRenderingResources()
|
|
{
|
|
// Note not using deferred cleanup for widget renderer here as it is already in deferred cleanup
|
|
if (WidgetRenderer)
|
|
{
|
|
delete WidgetRenderer;
|
|
}
|
|
}
|
|
|
|
/** FGCObject interface */
|
|
virtual void AddReferencedObjects(FReferenceCollector& Collector) override
|
|
{
|
|
Collector.AddReferencedObject(RenderTarget);
|
|
Collector.AddReferencedObject(DynamicEffect);
|
|
}
|
|
|
|
virtual FString GetReferencerName() const override
|
|
{
|
|
return TEXT("FRetainerWidgetRenderingResources");
|
|
}
|
|
|
|
public:
|
|
FWidgetRenderer* WidgetRenderer;
|
|
TObjectPtr<UTextureRenderTarget2D> RenderTarget;
|
|
TObjectPtr<UMaterialInstanceDynamic> DynamicEffect;
|
|
};
|
|
|
|
TArray<SRetainerWidget*, TInlineAllocator<3>> SRetainerWidget::Shared_WaitingToRender;
|
|
int32 SRetainerWidget::Shared_MaxRetainerWorkPerFrame(0);
|
|
TFrameValue<int32> SRetainerWidget::Shared_RetainerWorkThisFrame(0);
|
|
|
|
|
|
SRetainerWidget::SRetainerWidget()
|
|
: PreviousRenderSize(FIntPoint::NoneValue)
|
|
, PreviousClipRectSize(FIntPoint::NoneValue)
|
|
, PreviousColorAndOpacity(FColor::Transparent)
|
|
, LastIncomingLayerId(0)
|
|
, VirtualWindow(SNew(SVirtualWindow))
|
|
, HittestGrid(MakeShared<FHittestGrid>())
|
|
, RenderingResources(new FRetainerWidgetRenderingResources)
|
|
{
|
|
FSlateApplicationBase::Get().OnGlobalInvalidationToggled().AddRaw(this, &SRetainerWidget::OnGlobalInvalidationToggled);
|
|
if (FSlateApplication::IsInitialized())
|
|
{
|
|
#if !UE_BUILD_SHIPPING
|
|
OnRetainerModeChangedDelegate.RemoveAll(this);
|
|
#endif
|
|
}
|
|
bHasCustomPrepass = true;
|
|
SetInvalidationRootWidget(*this);
|
|
SetInvalidationRootHittestGrid(HittestGrid.Get());
|
|
SetCanTick(false);
|
|
}
|
|
|
|
SRetainerWidget::~SRetainerWidget()
|
|
{
|
|
if( FSlateApplication::IsInitialized() )
|
|
{
|
|
FSlateApplicationBase::Get().OnGlobalInvalidationToggled().RemoveAll(this);
|
|
#if !UE_BUILD_SHIPPING
|
|
OnRetainerModeChangedDelegate.RemoveAll( this );
|
|
#endif
|
|
}
|
|
|
|
// Begin deferred cleanup of rendering resources. DO NOT delete here. Will be deleted when safe
|
|
BeginCleanup(RenderingResources);
|
|
|
|
Shared_WaitingToRender.Remove(this);
|
|
}
|
|
|
|
void SRetainerWidget::UpdateWidgetRenderer()
|
|
{
|
|
// We can't write out linear. If we write out linear, then we end up with premultiplied alpha
|
|
// in linear space, which blending with gamma space later is difficult...impossible? to get right
|
|
// since the rest of slate does blending in gamma space.
|
|
const bool bWriteContentInGammaSpace = true;
|
|
|
|
if (!RenderingResources->WidgetRenderer)
|
|
{
|
|
RenderingResources->WidgetRenderer = new FWidgetRenderer(bWriteContentInGammaSpace);
|
|
}
|
|
|
|
UTextureRenderTarget2D* RenderTarget = RenderingResources->RenderTarget;
|
|
FWidgetRenderer* WidgetRenderer = RenderingResources->WidgetRenderer;
|
|
|
|
WidgetRenderer->SetUseGammaCorrection(bWriteContentInGammaSpace);
|
|
|
|
// This will be handled by the main slate rendering pass
|
|
WidgetRenderer->SetApplyColorDeficiencyCorrection(false);
|
|
|
|
WidgetRenderer->SetIsPrepassNeeded(false);
|
|
WidgetRenderer->SetClearHitTestGrid(false);
|
|
|
|
// Update the render target to match the current gamma rendering preferences.
|
|
if (RenderTarget && RenderTarget->SRGB != !bWriteContentInGammaSpace)
|
|
{
|
|
// Note, we do the opposite here of whatever write is, if we we're writing out gamma,
|
|
// then sRGB writes were not supported, so it won't be an sRGB texture.
|
|
RenderTarget->TargetGamma = !bWriteContentInGammaSpace ? 0.0f : 1.0;
|
|
RenderTarget->SRGB = !bWriteContentInGammaSpace;
|
|
|
|
RenderTarget->UpdateResource();
|
|
}
|
|
}
|
|
|
|
void SRetainerWidget::Construct(const FArguments& InArgs)
|
|
{
|
|
STAT(MyStatId = FDynamicStats::CreateStatId<FStatGroup_STATGROUP_Slate>(InArgs._StatId);)
|
|
|
|
UTextureRenderTarget2D* RenderTarget = NewObject<UTextureRenderTarget2D>();
|
|
RenderTarget->ClearColor = FLinearColor::Transparent;
|
|
RenderTarget->RenderTargetFormat = RTF_RGBA8_SRGB;
|
|
|
|
RenderingResources->RenderTarget = RenderTarget;
|
|
SurfaceBrush.SetResourceObject(RenderTarget);
|
|
|
|
VirtualWindow->SetVisibility(EVisibility::SelfHitTestInvisible); // deubanks: We don't want Retainer Widgets blocking hit testing for tooltips
|
|
VirtualWindow->SetShouldResolveDeferred(false);
|
|
|
|
UpdateWidgetRenderer();
|
|
|
|
MyWidget = InArgs._Content.Widget;
|
|
|
|
RenderOnPhase = InArgs._RenderOnPhase;
|
|
RenderOnInvalidation = InArgs._RenderOnInvalidation;
|
|
|
|
Phase = InArgs._Phase;
|
|
PhaseCount = InArgs._PhaseCount;
|
|
|
|
LastDrawTime = FApp::GetCurrentTime();
|
|
LastTickedFrame = 0;
|
|
|
|
bEnableRetainedRenderingDesire = true;
|
|
bEnableRetainedRendering = false;
|
|
SetVolatilePrepass(bEnableRetainedRendering);
|
|
|
|
#if WITH_EDITOR
|
|
bIsDesignTime = false;
|
|
bShowEffectsInDesigner = true;
|
|
bWarnOnInvalidSize = InArgs._bWarnOnInvalidSize;
|
|
#endif // WITH_EDITOR
|
|
|
|
RefreshRenderingMode();
|
|
bRenderRequested = true;
|
|
bInvalidSizeLogged = false;
|
|
|
|
ChildSlot
|
|
[
|
|
MyWidget.ToSharedRef()
|
|
];
|
|
|
|
if ( FSlateApplication::IsInitialized() )
|
|
{
|
|
#if !UE_BUILD_SHIPPING
|
|
OnRetainerModeChangedDelegate.AddRaw(this, &SRetainerWidget::OnRetainerModeChanged);
|
|
|
|
static bool bStaticInit = false;
|
|
|
|
if ( !bStaticInit )
|
|
{
|
|
bStaticInit = true;
|
|
EnableRetainedRendering->SetOnChangedCallback(FConsoleVariableDelegate::CreateStatic(&SRetainerWidget::OnRetainerModeCVarChanged));
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
bool SRetainerWidget::ShouldBeRenderingOffscreen() const
|
|
{
|
|
return bEnableRetainedRenderingDesire && IsRetainedRenderingEnabled();
|
|
}
|
|
|
|
bool SRetainerWidget::IsAnythingVisibleToRender() const
|
|
{
|
|
return MyWidget.IsValid() && MyWidget->GetVisibility().IsVisible();
|
|
}
|
|
|
|
void SRetainerWidget::OnRetainerModeChanged()
|
|
{
|
|
if (MyWidget.IsValid())
|
|
{
|
|
InvalidateChildRemovedFromTree(*MyWidget.Get());
|
|
}
|
|
|
|
// Invalidate myself
|
|
Advanced_ResetInvalidation(true);
|
|
|
|
// Invalidate my invalidation root, since all my children were once it's children
|
|
// it needs to force a generation bump just like me.
|
|
if (FSlateInvalidationRoot* MyInvalidationRoot = GetProxyHandle().GetInvalidationRootHandle().GetInvalidationRoot())
|
|
{
|
|
MyInvalidationRoot->Advanced_ResetInvalidation(true);
|
|
}
|
|
|
|
RefreshRenderingMode();
|
|
|
|
bRenderRequested = true;
|
|
}
|
|
|
|
void SRetainerWidget::OnRootInvalidated()
|
|
{
|
|
RequestRender();
|
|
}
|
|
|
|
#if !UE_BUILD_SHIPPING
|
|
|
|
void SRetainerWidget::OnRetainerModeCVarChanged( IConsoleVariable* CVar )
|
|
{
|
|
OnRetainerModeChangedDelegate.Broadcast();
|
|
}
|
|
|
|
#endif
|
|
|
|
void SRetainerWidget::SetRetainedRendering(bool bRetainRendering)
|
|
{
|
|
if (bEnableRetainedRenderingDesire != bRetainRendering)
|
|
{
|
|
bEnableRetainedRenderingDesire = bRetainRendering;
|
|
OnRetainerModeChanged();
|
|
}
|
|
}
|
|
|
|
void SRetainerWidget::RefreshRenderingMode()
|
|
{
|
|
const bool bShouldBeRenderingOffscreen = ShouldBeRenderingOffscreen();
|
|
|
|
if ( bEnableRetainedRendering != bShouldBeRenderingOffscreen )
|
|
{
|
|
bEnableRetainedRendering = bShouldBeRenderingOffscreen;
|
|
SetVolatilePrepass(bEnableRetainedRendering);
|
|
InvalidateRootChildOrder();
|
|
}
|
|
}
|
|
|
|
void SRetainerWidget::SetContent(const TSharedRef< SWidget >& InContent)
|
|
{
|
|
MyWidget = InContent;
|
|
ChildSlot
|
|
[
|
|
InContent
|
|
];
|
|
}
|
|
|
|
UMaterialInstanceDynamic* SRetainerWidget::GetEffectMaterial() const
|
|
{
|
|
return RenderingResources->DynamicEffect;
|
|
}
|
|
|
|
void SRetainerWidget::SetEffectMaterial(UMaterialInterface* EffectMaterial)
|
|
{
|
|
if ( EffectMaterial )
|
|
{
|
|
UMaterialInstanceDynamic* DynamicEffect = Cast<UMaterialInstanceDynamic>(EffectMaterial);
|
|
if ( !DynamicEffect )
|
|
{
|
|
DynamicEffect = UMaterialInstanceDynamic::Create(EffectMaterial, GetTransientPackage());
|
|
}
|
|
RenderingResources->DynamicEffect = DynamicEffect;
|
|
|
|
SurfaceBrush.SetResourceObject(RenderingResources->DynamicEffect);
|
|
}
|
|
else
|
|
{
|
|
RenderingResources->DynamicEffect = nullptr;
|
|
SurfaceBrush.SetResourceObject(RenderingResources->RenderTarget);
|
|
}
|
|
|
|
UpdateWidgetRenderer();
|
|
}
|
|
|
|
void SRetainerWidget::SetTextureParameter(FName TextureParameter)
|
|
{
|
|
DynamicEffectTextureParameter = TextureParameter;
|
|
}
|
|
|
|
void SRetainerWidget::SetWorld(UWorld* World)
|
|
{
|
|
OuterWorld = World;
|
|
}
|
|
|
|
FChildren* SRetainerWidget::GetChildren()
|
|
{
|
|
if (bEnableRetainedRendering)
|
|
{
|
|
return &FNoChildren::NoChildrenInstance;
|
|
}
|
|
else
|
|
{
|
|
return SCompoundWidget::GetChildren();
|
|
}
|
|
}
|
|
|
|
#if WITH_SLATE_DEBUGGING
|
|
FChildren* SRetainerWidget::Debug_GetChildrenForReflector()
|
|
{
|
|
return SCompoundWidget::GetChildren();
|
|
}
|
|
#endif
|
|
|
|
void SRetainerWidget::SetRenderingPhase(int32 InPhase, int32 InPhaseCount)
|
|
{
|
|
Phase = InPhase;
|
|
PhaseCount = InPhaseCount;
|
|
}
|
|
|
|
void SRetainerWidget::RequestRender()
|
|
{
|
|
bRenderRequested = true;
|
|
InvalidateRootChildOrder();
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
void SRetainerWidget::SetIsDesignTime(bool bInIsDesignTime)
|
|
{
|
|
bIsDesignTime = bInIsDesignTime;
|
|
}
|
|
|
|
void SRetainerWidget::SetShowEffectsInDesigner(bool bInShowEffectsInDesigner)
|
|
{
|
|
bShowEffectsInDesigner = bInShowEffectsInDesigner;
|
|
}
|
|
#endif // WITH_EDITOR
|
|
|
|
SRetainerWidget::EPaintRetainedContentResult SRetainerWidget::PaintRetainedContentImpl(const FSlateInvalidationContext& Context, const FGeometry& AllottedGeometry, int32 LayerId)
|
|
{
|
|
if (RenderOnPhase)
|
|
{
|
|
if (LastTickedFrame != GFrameCounter && (GFrameCounter % PhaseCount) == Phase)
|
|
{
|
|
// If doing some phase based invalidation, just redraw everything again
|
|
bRenderRequested = true;
|
|
InvalidateRootLayout();
|
|
}
|
|
}
|
|
|
|
const FPaintGeometry PaintGeometry = AllottedGeometry.ToPaintGeometry();
|
|
FSlateRenderTransform AccumulatedRenderTransform = PaintGeometry.GetAccumulatedRenderTransform();
|
|
const FVector2f RenderSize = FVector2f(PaintGeometry.GetLocalSize()) * AccumulatedRenderTransform.GetMatrix().GetScale().GetVector();
|
|
const FIntPoint RoundedRenderSize = RenderSize.IntPoint();
|
|
|
|
if (RenderOnInvalidation)
|
|
{
|
|
// the invalidation root will take care of whether or not we actually rendered
|
|
bRenderRequested = true;
|
|
|
|
const FIntPoint ClipRectSize = Context.CullingRect.GetSize().IntPoint();
|
|
const TOptional<FSlateClippingState> ClippingState = Context.WindowElementList->GetClippingState();
|
|
const FColor ColorAndOpacityTint = Context.WidgetStyle.GetColorAndOpacityTint().ToFColor(false);
|
|
|
|
|
|
// Aggressively re-layout when a base state changes.
|
|
if (RoundedRenderSize != PreviousRenderSize
|
|
|| AllottedGeometry != PreviousAllottedGeometry
|
|
|| AllottedGeometry.GetAccumulatedRenderTransform() != PreviousAllottedGeometry.GetAccumulatedRenderTransform()
|
|
|| ClipRectSize != PreviousClipRectSize
|
|
|| ClippingState != PreviousClippingState)
|
|
{
|
|
PreviousRenderSize = RoundedRenderSize;
|
|
PreviousAllottedGeometry = AllottedGeometry;
|
|
PreviousClipRectSize = ClipRectSize;
|
|
PreviousClippingState = ClippingState;
|
|
|
|
InvalidateRootLayout();
|
|
}
|
|
|
|
// Aggressively repaint when a base state changes.
|
|
if (LayerId != LastIncomingLayerId
|
|
|| ColorAndOpacityTint != PreviousColorAndOpacity)
|
|
{
|
|
LastIncomingLayerId = LayerId;
|
|
PreviousColorAndOpacity = ColorAndOpacityTint;
|
|
|
|
GetRootWidget()->Invalidate(EInvalidateWidgetReason::Paint);
|
|
}
|
|
}
|
|
else if (RoundedRenderSize != PreviousRenderSize)
|
|
{
|
|
bRenderRequested = true;
|
|
InvalidateRootLayout();
|
|
PreviousRenderSize = RoundedRenderSize;
|
|
}
|
|
|
|
if (Shared_MaxRetainerWorkPerFrame > 0)
|
|
{
|
|
if (Shared_RetainerWorkThisFrame.TryGetValue(0) > Shared_MaxRetainerWorkPerFrame)
|
|
{
|
|
Shared_WaitingToRender.AddUnique(this);
|
|
return EPaintRetainedContentResult::Queued;
|
|
}
|
|
}
|
|
|
|
if (bRenderRequested)
|
|
{
|
|
// In order to get material parameter collections to function properly, we need the current world's Scene
|
|
// properly propagated through to any widgets that depend on that functionality. The SceneViewport and RetainerWidget the
|
|
// only location where this information exists in Slate, so we push the current scene onto the current
|
|
// Slate application so that we can leverage it in later calls.
|
|
UWorld* TickWorld = OuterWorld.Get();
|
|
if (TickWorld && TickWorld->Scene && IsInGameThread())
|
|
{
|
|
FSlateApplication::Get().GetRenderer()->RegisterCurrentScene(TickWorld->Scene);
|
|
}
|
|
else if (IsInGameThread())
|
|
{
|
|
FSlateApplication::Get().GetRenderer()->RegisterCurrentScene(nullptr);
|
|
}
|
|
|
|
// Update the number of retainers we've drawn this frame.
|
|
Shared_RetainerWorkThisFrame = Shared_RetainerWorkThisFrame.TryGetValue(0) + 1;
|
|
|
|
LastTickedFrame = GFrameCounter;
|
|
const double TimeSinceLastDraw = FApp::GetCurrentTime() - LastDrawTime;
|
|
|
|
// Size must be a positive integer to allocate the RenderTarget
|
|
const uint32 RenderTargetWidth = FMath::RoundToInt(FMath::Abs(RenderSize.X));
|
|
const uint32 RenderTargetHeight = FMath::RoundToInt(FMath::Abs(RenderSize.Y));
|
|
const bool bTextureIsTooLarge = FMath::Max(RenderTargetWidth, RenderTargetHeight) > GetMax2DTextureDimension();
|
|
const bool bTextureSizeZero = (RenderTargetWidth == 0 || RenderTargetHeight == 0);
|
|
|
|
if (bTextureIsTooLarge || bTextureSizeZero)
|
|
{
|
|
// if bTextureTooLarge then the user probably have a layout issue. Warn the user.
|
|
if (!bInvalidSizeLogged)
|
|
{
|
|
bInvalidSizeLogged = true;
|
|
|
|
const bool bEnableWarnOnInvalidSize =
|
|
#if WITH_EDITOR
|
|
bWarnOnInvalidSize;
|
|
#else
|
|
true;
|
|
#endif
|
|
if (bTextureIsTooLarge)
|
|
{
|
|
if (bEnableWarnOnInvalidSize)
|
|
{
|
|
UE_LOG(LogUMG, Warning, TEXT("The requested size for SRetainerWidget is too large. W:%i H:%i"), RenderTargetWidth, RenderTargetHeight);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (bEnableWarnOnInvalidSize)
|
|
{
|
|
UE_LOG(LogUMG, Warning, TEXT("The requested size for SRetainerWidget is 0. W:%i H:%i"), RenderTargetWidth, RenderTargetHeight);
|
|
}
|
|
}
|
|
}
|
|
return bTextureIsTooLarge ? EPaintRetainedContentResult::TextureSizeTooBig : EPaintRetainedContentResult::TextureSizeZero;
|
|
}
|
|
bInvalidSizeLogged = false;
|
|
|
|
if (RenderTargetWidth >= 1 && RenderTargetHeight >= 1)
|
|
{
|
|
const FVector2D ViewOffset = PaintGeometry.GetAccumulatedRenderTransform().GetTranslation();
|
|
|
|
UTextureRenderTarget2D* RenderTarget = RenderingResources->RenderTarget;
|
|
FWidgetRenderer* WidgetRenderer = RenderingResources->WidgetRenderer;
|
|
|
|
if ( MyWidget->GetVisibility().IsVisible() )
|
|
{
|
|
if ( (int32)RenderTarget->GetSurfaceWidth() != (int32)RenderTargetWidth ||
|
|
(int32)RenderTarget->GetSurfaceHeight() != (int32)RenderTargetHeight )
|
|
{
|
|
|
|
// If the render target resource already exists just resize it. Calling InitCustomFormat flushes render commands which could result in a huge hitch
|
|
if(RenderTarget->GameThread_GetRenderTargetResource() && RenderTarget->OverrideFormat == PF_B8G8R8A8)
|
|
{
|
|
RenderTarget->ResizeTarget(RenderTargetWidth, RenderTargetHeight);
|
|
}
|
|
else
|
|
{
|
|
const bool bForceLinearGamma = false;
|
|
RenderTarget->InitCustomFormat(RenderTargetWidth, RenderTargetHeight, PF_B8G8R8A8, bForceLinearGamma);
|
|
RenderTarget->UpdateResourceImmediate();
|
|
}
|
|
}
|
|
|
|
const float Scale = AllottedGeometry.Scale;
|
|
|
|
const FVector2D DrawSize = FVector2D(RenderTargetWidth, RenderTargetHeight);
|
|
//const FGeometry WindowGeometry = FGeometry::MakeRoot(DrawSize * ( 1 / Scale ), FSlateLayoutTransform(Scale, PaintGeometry.DrawPosition));
|
|
|
|
// Update the surface brush to match the latest size.
|
|
SurfaceBrush.ImageSize = DrawSize;
|
|
|
|
WidgetRenderer->ViewOffset = -ViewOffset;
|
|
WidgetRenderer->SetIsPrepassNeeded(false);
|
|
|
|
FVector2f WindowSize(RenderSize);
|
|
SWindow* PaintWindow = Context.WindowElementList->GetPaintWindow();
|
|
if (PaintWindow)
|
|
{
|
|
const FVector2f ViewportSize = PaintWindow->GetViewportSize();
|
|
WindowSize.X = FMath::Max(WindowSize.X, ViewportSize.X);
|
|
WindowSize.Y = FMath::Max(WindowSize.Y, ViewportSize.Y);
|
|
}
|
|
VirtualWindow->Resize(WindowSize);
|
|
|
|
bool bRepaintedWidgets = WidgetRenderer->DrawInvalidationRoot(VirtualWindow, RenderTarget, *this, Context, GDeferRetainedRenderingRenderThread != 0);
|
|
bRenderRequested = false;
|
|
Shared_WaitingToRender.Remove(this);
|
|
|
|
LastDrawTime = FApp::GetCurrentTime();
|
|
|
|
return bRepaintedWidgets ? EPaintRetainedContentResult::Painted : EPaintRetainedContentResult::NotPainted;
|
|
}
|
|
}
|
|
}
|
|
|
|
return EPaintRetainedContentResult::NotPainted;
|
|
}
|
|
|
|
int32 SRetainerWidget::OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const
|
|
{
|
|
STAT(FScopeCycleCounter PaintCycleCounter(MyStatId););
|
|
|
|
SRetainerWidget* MutableThis = const_cast<SRetainerWidget*>(this);
|
|
|
|
bool bShouldRetainRendering = bEnableRetainedRendering;
|
|
|
|
#if WITH_EDITOR
|
|
bool bShouldSkipDesignerRendering = bIsDesignTime && (!bShowEffectsInDesigner || !GEnableDesignerRetainedRendering);
|
|
bShouldRetainRendering = bEnableRetainedRendering && !bShouldSkipDesignerRendering;
|
|
#endif // WITH_EDITOR
|
|
|
|
if (bShouldRetainRendering && IsAnythingVisibleToRender())
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_SlateRetainerWidgetPaint);
|
|
|
|
// Copy hit test grid settings from the root
|
|
const bool bHittestCleared = HittestGrid->SetHittestArea(Args.RootGrid.GetGridOrigin(), Args.RootGrid.GetGridSize(), Args.RootGrid.GetGridWindowOrigin());
|
|
if (bHittestCleared)
|
|
{
|
|
MutableThis->RequestRender();
|
|
}
|
|
HittestGrid->SetOwner(this);
|
|
HittestGrid->SetCullingRect(MyCullingRect);
|
|
|
|
FPaintArgs NewArgs = Args.WithNewHitTestGrid(HittestGrid.Get());
|
|
|
|
// Copy the current user index into the new grid since nested hittest grids should inherit their parents user id
|
|
NewArgs.GetHittestGrid().SetUserIndex(Args.RootGrid.GetUserIndex());
|
|
|
|
FSlateInvalidationContext Context(OutDrawElements, InWidgetStyle);
|
|
Context.bParentEnabled = bParentEnabled;
|
|
Context.bAllowFastPathUpdate = true;
|
|
Context.LayoutScaleMultiplier = GetPrepassLayoutScaleMultiplier();
|
|
Context.PaintArgs = &NewArgs;
|
|
Context.IncomingLayerId = LayerId;
|
|
Context.CullingRect = MyCullingRect;
|
|
|
|
EPaintRetainedContentResult PaintResult = MutableThis->PaintRetainedContentImpl(Context, AllottedGeometry, LayerId);
|
|
|
|
#if WITH_SLATE_DEBUGGING
|
|
if (PaintResult == EPaintRetainedContentResult::NotPainted
|
|
|| PaintResult == EPaintRetainedContentResult::TextureSizeZero
|
|
|| PaintResult == EPaintRetainedContentResult::TextureSizeTooBig)
|
|
{
|
|
MutableThis->SetLastPaintType(ESlateInvalidationPaintType::None);
|
|
}
|
|
#endif
|
|
|
|
if (PaintResult == EPaintRetainedContentResult::TextureSizeTooBig)
|
|
{
|
|
return SCompoundWidget::OnPaint(Args, AllottedGeometry, MyCullingRect, OutDrawElements, LayerId, InWidgetStyle, bParentEnabled);
|
|
}
|
|
else if (PaintResult == EPaintRetainedContentResult::TextureSizeZero)
|
|
{
|
|
return GetCachedMaxLayerId();
|
|
}
|
|
else
|
|
{
|
|
UTextureRenderTarget2D* RenderTarget = RenderingResources->RenderTarget;
|
|
check(RenderTarget);
|
|
|
|
if (RenderTarget->GetSurfaceWidth() >= 1 && RenderTarget->GetSurfaceHeight() >= 1)
|
|
{
|
|
const FLinearColor ComputedColorAndOpacity(Context.WidgetStyle.GetColorAndOpacityTint() * GetColorAndOpacity() * SurfaceBrush.GetTint(Context.WidgetStyle));
|
|
// Retainer widget uses pre-multiplied alpha, so pre-multiply the color by the alpha to respect opacity.
|
|
const FLinearColor PremultipliedColorAndOpacity(ComputedColorAndOpacity * ComputedColorAndOpacity.A);
|
|
|
|
FWidgetRenderer* WidgetRenderer = RenderingResources->WidgetRenderer;
|
|
UMaterialInstanceDynamic* DynamicEffect = RenderingResources->DynamicEffect;
|
|
|
|
const bool bDynamicMaterialInUse = (DynamicEffect != nullptr);
|
|
if (bDynamicMaterialInUse)
|
|
{
|
|
DynamicEffect->SetTextureParameterValue(DynamicEffectTextureParameter, RenderTarget);
|
|
}
|
|
|
|
FSlateDrawElement::MakeBox(
|
|
*Context.WindowElementList,
|
|
Context.IncomingLayerId,
|
|
AllottedGeometry.ToPaintGeometry(),
|
|
&SurfaceBrush,
|
|
// We always write out the content in gamma space, so when we render the final version we need to
|
|
// render without gamma correction enabled.
|
|
ESlateDrawEffect::PreMultipliedAlpha | ESlateDrawEffect::NoGamma,
|
|
FLinearColor(PremultipliedColorAndOpacity.R, PremultipliedColorAndOpacity.G, PremultipliedColorAndOpacity.B, PremultipliedColorAndOpacity.A)
|
|
);
|
|
}
|
|
|
|
const bool bInheritedHittestability = Args.GetInheritedHittestability();
|
|
const bool bOutgoingHittestability = bInheritedHittestability && GetVisibility().AreChildrenHitTestVisible();
|
|
|
|
// add our widgets to the root hit test grid
|
|
if (bOutgoingHittestability)
|
|
{
|
|
Args.GetHittestGrid().AddGrid(HittestGrid);
|
|
}
|
|
|
|
return GetCachedMaxLayerId();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return SCompoundWidget::OnPaint(Args, AllottedGeometry, MyCullingRect, OutDrawElements, LayerId, InWidgetStyle, bParentEnabled);
|
|
}
|
|
}
|
|
|
|
FVector2D SRetainerWidget::ComputeDesiredSize(float LayoutScaleMuliplier) const
|
|
{
|
|
if ( bEnableRetainedRendering )
|
|
{
|
|
return MyWidget->GetDesiredSize();
|
|
}
|
|
else
|
|
{
|
|
return SCompoundWidget::ComputeDesiredSize(LayoutScaleMuliplier);
|
|
}
|
|
}
|
|
|
|
void SRetainerWidget::OnGlobalInvalidationToggled(bool bGlobalInvalidationEnabled)
|
|
{
|
|
InvalidateRootChildOrder();
|
|
|
|
ClearAllFastPathData(true);
|
|
}
|
|
|
|
bool SRetainerWidget::CustomPrepass(float LayoutScaleMultiplier)
|
|
{
|
|
if (bEnableRetainedRendering)
|
|
{
|
|
if (NeedsPrepass())
|
|
{
|
|
SetNeedsSlowPath(true);
|
|
}
|
|
ProcessInvalidation();
|
|
if (NeedsSlowPath())
|
|
{
|
|
FChildren* Children = SCompoundWidget::GetChildren();
|
|
Prepass_ChildLoop(LayoutScaleMultiplier, Children);
|
|
}
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
TSharedRef<SWidget> SRetainerWidget::GetRootWidget()
|
|
{
|
|
return bEnableRetainedRendering ? SCompoundWidget::GetChildren()->GetChildAt(0) : SNullWidget::NullWidget;
|
|
}
|
|
|
|
int32 SRetainerWidget::PaintSlowPath(const FSlateInvalidationContext& Context)
|
|
{
|
|
HittestGrid->Clear();
|
|
|
|
const FGeometry& OriginalPaintSpaceGeometry = GetPaintSpaceGeometry();
|
|
FSlateRenderTransform SimplifiedRenderTransform(OriginalPaintSpaceGeometry.GetAccumulatedRenderTransform().GetMatrix().GetScale(), OriginalPaintSpaceGeometry.GetAccumulatedRenderTransform().GetTranslation());
|
|
FTransform2D ScalePosTransform(OriginalPaintSpaceGeometry.Scale, OriginalPaintSpaceGeometry.AbsolutePosition);
|
|
SimplifiedRenderTransform = Concatenate(SimplifiedRenderTransform, Inverse(ScalePosTransform));
|
|
const FGeometry NewPaintSpaceGeometry = FGeometry::MakeRoot(OriginalPaintSpaceGeometry.GetLocalSize(), FSlateLayoutTransform(OriginalPaintSpaceGeometry.Scale, OriginalPaintSpaceGeometry.AbsolutePosition)).MakeChild(SimplifiedRenderTransform, FVector2D::ZeroVector);
|
|
return SCompoundWidget::OnPaint(*Context.PaintArgs, NewPaintSpaceGeometry, Context.CullingRect, *Context.WindowElementList, Context.IncomingLayerId, Context.WidgetStyle, Context.bParentEnabled);
|
|
} |