// Copyright Epic Games, Inc. All Rights Reserved. #include "AssetThumbnail.h" #include "Engine/Blueprint.h" #include "GameFramework/Actor.h" #include "Layout/Margin.h" #include "Widgets/DeclarativeSyntaxSupport.h" #include "Widgets/SOverlay.h" #include "Engine/GameViewportClient.h" #include "Interfaces/Interface_AsyncCompilation.h" #include "Modules/ModuleManager.h" #include "Animation/CurveHandle.h" #include "Animation/CurveSequence.h" #include "Textures/SlateTextureData.h" #include "Fonts/SlateFontInfo.h" #include "Application/ThrottleManager.h" #include "Widgets/SCompoundWidget.h" #include "Widgets/Layout/SBorder.h" #include "Widgets/Images/SImage.h" #include "Widgets/Text/STextBlock.h" #include "Widgets/SViewport.h" #include "Styling/AppStyle.h" #include "RenderingThread.h" #include "Settings/ContentBrowserSettings.h" #include "RenderUtils.h" #include "Editor/UnrealEdEngine.h" #include "ThumbnailRendering/ThumbnailManager.h" #include "Editor.h" #include "UnrealEdGlobals.h" #include "Slate/SlateTextures.h" #include "ObjectTools.h" #include "AssetRegistry/AssetRegistryModule.h" #include "AssetRegistry/IAssetRegistry.h" #include "ShaderCompiler.h" #include "AssetCompilingManager.h" #include "IAssetTools.h" #include "AssetTypeActions_Base.h" #include "AssetToolsModule.h" #include "Styling/SlateIconFinder.h" #include "ClassIconFinder.h" #include "IVREditorModule.h" #include "Framework/Application/SlateApplication.h" FName FAssetThumbnailPool::CustomThumbnailTagName = "CustomThumbnail"; class SAssetThumbnail : public SCompoundWidget { public: SLATE_BEGIN_ARGS( SAssetThumbnail ) : _Style("AssetThumbnail") , _ThumbnailPool(nullptr) , _AllowFadeIn(false) , _ForceGenericThumbnail(false) , _AllowHintText(true) , _AllowAssetSpecificThumbnailOverlay(false) , _AllowRealTimeOnHovered(true) , _Label(EThumbnailLabel::ClassName) , _HighlightedText(FText::GetEmpty()) , _HintColorAndOpacity(FLinearColor(0.0f, 0.0f, 0.0f, 0.0f)) , _ClassThumbnailBrushOverride(NAME_None) , _AssetTypeColorOverride() , _Padding(0) , _GenericThumbnailSize(64) , _ColorStripOrientation(EThumbnailColorStripOrientation::HorizontalBottomEdge) {} SLATE_ARGUMENT(FName, Style) SLATE_ARGUMENT(TSharedPtr, AssetThumbnail) SLATE_ARGUMENT(TSharedPtr, ThumbnailPool) SLATE_ARGUMENT(bool, AllowFadeIn) SLATE_ARGUMENT(bool, ForceGenericThumbnail) SLATE_ARGUMENT(bool, AllowHintText) SLATE_ARGUMENT(bool, AllowAssetSpecificThumbnailOverlay) SLATE_ARGUMENT(bool, AllowRealTimeOnHovered) SLATE_ARGUMENT(EThumbnailLabel::Type, Label) SLATE_ATTRIBUTE(FText, HighlightedText) SLATE_ATTRIBUTE(FLinearColor, HintColorAndOpacity) SLATE_ARGUMENT(FName, ClassThumbnailBrushOverride) SLATE_ARGUMENT(TOptional, AssetTypeColorOverride) SLATE_ARGUMENT(FMargin, Padding) SLATE_ATTRIBUTE(int32, GenericThumbnailSize) SLATE_ARGUMENT(EThumbnailColorStripOrientation, ColorStripOrientation) SLATE_END_ARGS() /** Constructs this widget with InArgs */ void Construct( const FArguments& InArgs ) { Style = InArgs._Style; HighlightedText = InArgs._HighlightedText; Label = InArgs._Label; HintColorAndOpacity = InArgs._HintColorAndOpacity; bAllowHintText = InArgs._AllowHintText; bAllowRealTimeOnHovered = InArgs._AllowRealTimeOnHovered; ThumbnailBrush = nullptr; ClassIconBrush = nullptr; AssetThumbnail = InArgs._AssetThumbnail; bHasRenderedThumbnail = false; WidthLastFrame = 0; GenericThumbnailBorderPadding = 2.f; GenericThumbnailSize = InArgs._GenericThumbnailSize; ColorStripOrientation = InArgs._ColorStripOrientation; AssetThumbnail->OnAssetDataChanged().AddSP(this, &SAssetThumbnail::OnAssetDataChanged); const FAssetData& AssetData = AssetThumbnail->GetAssetData(); UClass* Class = FindObjectSafe(AssetData.AssetClassPath); static FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked(TEXT("AssetTools")); TSharedPtr AssetTypeActions; if ( Class != NULL ) { AssetTypeActions = AssetToolsModule.Get().GetAssetTypeActionsForClass(Class).Pin(); } AssetTypeColorOverride = InArgs._AssetTypeColorOverride; AssetColor = FLinearColor::White; if( AssetTypeColorOverride.IsSet() ) { AssetColor = AssetTypeColorOverride.GetValue(); } else if ( AssetTypeActions.IsValid() ) { AssetColor = AssetTypeActions->GetTypeColor(); } TSharedRef OverlayWidget = SNew(SOverlay); UpdateThumbnailClass(); ClassThumbnailBrushOverride = InArgs._ClassThumbnailBrushOverride; AssetBackgroundBrushName = *(Style.ToString() + TEXT(".AssetBackground")); ClassBackgroundBrushName = *(Style.ToString() + TEXT(".ClassBackground")); // The generic representation of the thumbnail, for use before the rendered version, if it exists OverlayWidget->AddSlot() .Padding(InArgs._Padding) [ SAssignNew(AssetBackgroundWidget, SBorder) .BorderImage(GetAssetBackgroundBrush()) .Padding(GenericThumbnailBorderPadding) .VAlign(VAlign_Center) .HAlign(HAlign_Center) .Visibility(this, &SAssetThumbnail::GetGenericThumbnailVisibility) [ SNew(SOverlay) +SOverlay::Slot() [ SAssignNew(GenericLabelTextBlock, STextBlock) .Text(GetLabelText()) .Font(GetTextFont()) .Justification(ETextJustify::Center) .ColorAndOpacity(FAppStyle::GetColor(Style, ".ColorAndOpacity")) .HighlightText(HighlightedText) ] +SOverlay::Slot() [ SAssignNew(GenericThumbnailImage, SImage) .DesiredSizeOverride(this, &SAssetThumbnail::GetGenericThumbnailDesiredSize) .Image(this, &SAssetThumbnail::GetClassThumbnailBrush) ] ] ]; if ( InArgs._ThumbnailPool.IsValid() && !InArgs._ForceGenericThumbnail ) { ViewportFadeAnimation = FCurveSequence(); ViewportFadeCurve = ViewportFadeAnimation.AddCurve(0.f, 0.25f, ECurveEaseFunction::QuadOut); TSharedPtr Viewport = SNew( SViewport ) .EnableGammaCorrection(false) // In VR editor every widget is in the world and gamma corrected by the scene renderer. Thumbnails will have already been gamma // corrected and so they need to be reversed .ReverseGammaCorrection(IVREditorModule::Get().IsVREditorModeActive()) .EnableBlending(true) .ViewportSize(AssetThumbnail->GetSize()); Viewport->SetViewportInterface( AssetThumbnail.ToSharedRef() ); AssetThumbnail->GetViewportRenderTargetTexture(); // Access the render texture to push it on the stack if it isn't already rendered InArgs._ThumbnailPool->OnThumbnailRendered().AddSP(this, &SAssetThumbnail::OnThumbnailRendered); InArgs._ThumbnailPool->OnThumbnailRenderFailed().AddSP(this, &SAssetThumbnail::OnThumbnailRenderFailed); if ( ShouldRender() && (!InArgs._AllowFadeIn || InArgs._ThumbnailPool->IsRendered(AssetThumbnail)) ) { bHasRenderedThumbnail = true; ViewportFadeAnimation.JumpToEnd(); } // The viewport for the rendered thumbnail, if it exists OverlayWidget->AddSlot() [ SAssignNew(RenderedThumbnailWidget, SBorder) .Padding(InArgs._Padding) .BorderImage(FStyleDefaults::GetNoBrush()) .ColorAndOpacity(this, &SAssetThumbnail::GetViewportColorAndOpacity) .HAlign(HAlign_Center) .VAlign(VAlign_Center) [ Viewport.ToSharedRef() ] ]; } if( ThumbnailClass.Get() && bIsClassType ) { OverlayWidget->AddSlot() .VAlign(VAlign_Bottom) .HAlign(HAlign_Right) .Padding(GetClassIconPadding()) [ SAssignNew(ClassIconWidget, SBorder) .BorderImage(FAppStyle::GetNoBrush()) [ SNew(SImage) .Image(this, &SAssetThumbnail::GetClassIconBrush) ] ]; } if( bAllowHintText ) { OverlayWidget->AddSlot() .HAlign(HAlign_Center) .VAlign(VAlign_Top) .Padding(FMargin(2, 2, 2, 2)) [ SNew(SBorder) .BorderImage(FAppStyle::GetBrush(Style, ".HintBackground")) .BorderBackgroundColor(this, &SAssetThumbnail::GetHintBackgroundColor) //Adjust the opacity of the border itself .ColorAndOpacity(HintColorAndOpacity) //adjusts the opacity of the contents of the border .Visibility(this, &SAssetThumbnail::GetHintTextVisibility) .Padding(0) [ SAssignNew(HintTextBlock, STextBlock) .Text(GetLabelText()) .Font(GetHintTextFont()) .ColorAndOpacity(FAppStyle::GetColor(Style, ".HintColorAndOpacity")) .HighlightText(HighlightedText) ] ]; } // The asset color strip OverlayWidget->AddSlot() .HAlign(ColorStripOrientation == EThumbnailColorStripOrientation::HorizontalBottomEdge ? HAlign_Fill : HAlign_Right) .VAlign(ColorStripOrientation == EThumbnailColorStripOrientation::HorizontalBottomEdge ? VAlign_Bottom : VAlign_Fill) [ SAssignNew(AssetColorStripWidget, SBorder) .BorderImage(FAppStyle::GetBrush("WhiteBrush")) .BorderBackgroundColor(AssetColor) .Padding(this, &SAssetThumbnail::GetAssetColorStripPadding) ]; if( InArgs._AllowAssetSpecificThumbnailOverlay && AssetTypeActions.IsValid() ) { // Does the asset provide an additional thumbnail overlay? TSharedPtr AssetSpecificThumbnailOverlay = AssetTypeActions->GetThumbnailOverlay(AssetData); if( AssetSpecificThumbnailOverlay.IsValid() ) { OverlayWidget->AddSlot() [ AssetSpecificThumbnailOverlay.ToSharedRef() ]; } } ChildSlot [ OverlayWidget ]; UpdateThumbnailVisibilities(); } void UpdateThumbnailClass() { const FAssetData& AssetData = AssetThumbnail->GetAssetData(); ThumbnailClass = MakeWeakObjectPtr(const_cast(FClassIconFinder::GetIconClassForAssetData(AssetData, &bIsClassType))); // For non-class types, use the default based upon the actual asset class // This has the side effect of not showing a class icon for assets that don't have a proper thumbnail image available const FName DefaultThumbnail = (bIsClassType) ? NAME_None : FName(*FString::Printf(TEXT("ClassThumbnail.%s"), *AssetThumbnail->GetAssetData().AssetClassPath.GetAssetName().ToString())); ThumbnailBrush = FClassIconFinder::FindThumbnailForClass(ThumbnailClass.Get(), DefaultThumbnail); ClassIconBrush = FSlateIconFinder::FindIconBrushForClass(ThumbnailClass.Get()); } FSlateColor GetHintBackgroundColor() const { const FLinearColor Color = HintColorAndOpacity.Get(); return FSlateColor( FLinearColor( Color.R, Color.G, Color.B, FMath::Lerp( 0.0f, 0.5f, Color.A ) ) ); } virtual void OnMouseEnter( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent ) override { SCompoundWidget::OnMouseEnter( MyGeometry, MouseEvent ); if ( bAllowRealTimeOnHovered ) { AssetThumbnail->SetRealTime( true ); } } virtual void OnMouseLeave( const FPointerEvent& MouseEvent ) override { SCompoundWidget::OnMouseLeave( MouseEvent ); if ( bAllowRealTimeOnHovered ) { AssetThumbnail->SetRealTime( false ); } } virtual void Tick( const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime ) override { if ( WidthLastFrame != AllottedGeometry.Size.X ) { WidthLastFrame = AllottedGeometry.Size.X; // The width changed, update the font if ( GenericLabelTextBlock.IsValid() ) { GenericLabelTextBlock->SetFont( GetTextFont() ); GenericLabelTextBlock->SetWrapTextAt( GetTextWrapWidth() ); } if ( HintTextBlock.IsValid() ) { HintTextBlock->SetFont( GetHintTextFont() ); HintTextBlock->SetWrapTextAt( GetTextWrapWidth() ); } } } private: void OnAssetDataChanged() { if ( GenericLabelTextBlock.IsValid() ) { GenericLabelTextBlock->SetText( GetLabelText() ); } if ( HintTextBlock.IsValid() ) { HintTextBlock->SetText( GetLabelText() ); } // Check if the asset has a thumbnail. const FObjectThumbnail* ObjectThumbnail = NULL; FThumbnailMap ThumbnailMap; if( AssetThumbnail->GetAsset() ) { FName FullAssetName = FName( *(AssetThumbnail->GetAssetData().GetFullName()) ); TArray ObjectNames; ObjectNames.Add( FullAssetName ); ThumbnailTools::ConditionallyLoadThumbnailsForObjects(ObjectNames, ThumbnailMap); ObjectThumbnail = ThumbnailMap.Find( FullAssetName ); } bHasRenderedThumbnail = ObjectThumbnail && !ObjectThumbnail->IsEmpty(); ViewportFadeAnimation.JumpToEnd(); AssetThumbnail->GetViewportRenderTargetTexture(); // Access the render texture to push it on the stack if it isnt already rendered const FAssetData& AssetData = AssetThumbnail->GetAssetData(); UClass* Class = FindObject(AssetData.AssetClassPath); FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked(TEXT("AssetTools")); TWeakPtr AssetTypeActions; if ( Class != NULL ) { AssetTypeActions = AssetToolsModule.Get().GetAssetTypeActionsForClass(Class); } UpdateThumbnailClass(); AssetColor = FLinearColor::White; if( AssetTypeColorOverride.IsSet() ) { AssetColor = AssetTypeColorOverride.GetValue(); } else if ( AssetTypeActions.IsValid() ) { AssetColor = AssetTypeActions.Pin()->GetTypeColor(); } //AssetBackgroundWidget->SetBorderBackgroundColor(AssetColor.CopyWithNewOpacity(0.3f)); AssetColorStripWidget->SetBorderBackgroundColor(AssetColor); UpdateThumbnailVisibilities(); } FSlateFontInfo GetTextFont() const { return FAppStyle::GetFontStyle( WidthLastFrame <= 64 ? FAppStyle::Join(Style, ".FontSmall") : FAppStyle::Join(Style, ".Font") ); } FSlateFontInfo GetHintTextFont() const { return FAppStyle::GetFontStyle( WidthLastFrame <= 64 ? FAppStyle::Join(Style, ".HintFontSmall") : FAppStyle::Join(Style, ".HintFont") ); } float GetTextWrapWidth() const { return WidthLastFrame - GenericThumbnailBorderPadding * 2.f; } const FSlateBrush* GetAssetBackgroundBrush() const { return FAppStyle::GetBrush(AssetBackgroundBrushName); } const FSlateBrush* GetClassBackgroundBrush() const { return FAppStyle::GetBrush(ClassBackgroundBrushName); } FLinearColor GetViewportColorAndOpacity() const { return FLinearColor(1, 1, 1, ViewportFadeCurve.GetLerp()); } EVisibility GetViewportVisibility() const { return bHasRenderedThumbnail ? EVisibility::Visible : EVisibility::Collapsed; } /** The height of the color strip (if it's oriented along the bottom edge) or its width (if it's oriented along the right edge) */ float GetAssetColorStripThickness() const { return 2.0f; } FMargin GetAssetColorStripPadding() const { if (ColorStripOrientation == EThumbnailColorStripOrientation::HorizontalBottomEdge) { const float Height = GetAssetColorStripThickness(); return FMargin(0, Height, 0, 0); } else { const float Width = GetAssetColorStripThickness(); return FMargin(Width, 0, 0, 0); } } const FSlateBrush* GetClassThumbnailBrush() const { if (ClassThumbnailBrushOverride.IsNone()) { return ThumbnailBrush; } else { // Instead of getting the override thumbnail directly from the editor style here get it from the // ClassIconFinder since it may have additional styles registered which can be searched by passing // it as a default with no class to search for. return FClassIconFinder::FindThumbnailForClass(nullptr, ClassThumbnailBrushOverride); } } EVisibility GetClassThumbnailVisibility() const { if(!bHasRenderedThumbnail) { const FSlateBrush* ClassThumbnailBrush = GetClassThumbnailBrush(); if( ClassThumbnailBrush && ThumbnailClass.Get() ) { return EVisibility::Visible; } } return EVisibility::Collapsed; } EVisibility GetGenericThumbnailVisibility() const { return (bHasRenderedThumbnail && ViewportFadeAnimation.IsAtEnd()) ? EVisibility::Collapsed : EVisibility::Visible; } const FSlateBrush* GetClassIconBrush() const { return ClassIconBrush; } FMargin GetClassIconPadding() const { if (ColorStripOrientation == EThumbnailColorStripOrientation::HorizontalBottomEdge) { const float Height = GetAssetColorStripThickness(); return FMargin(0, 0, 0, Height); } else { const float Width = GetAssetColorStripThickness(); return FMargin(0, 0, Width, 0); } } EVisibility GetHintTextVisibility() const { if ( bAllowHintText && ( bHasRenderedThumbnail || !GenericLabelTextBlock.IsValid() ) && HintColorAndOpacity.Get().A > 0 ) { return EVisibility::Visible; } return EVisibility::Collapsed; } void OnThumbnailRendered(const FAssetData& AssetData) { if ( !bHasRenderedThumbnail && AssetData == AssetThumbnail->GetAssetData() && ShouldRender() ) { OnRenderedThumbnailChanged( true ); ViewportFadeAnimation.Play( this->AsShared() ); } } void OnThumbnailRenderFailed(const FAssetData& AssetData) { if ( bHasRenderedThumbnail && AssetData == AssetThumbnail->GetAssetData() ) { OnRenderedThumbnailChanged( false ); } } bool ShouldRender() const { const FAssetData& AssetData = AssetThumbnail->GetAssetData(); // Never render a thumbnail for an invalid asset if ( !AssetData.IsValid() ) { return false; } if( AssetData.IsAssetLoaded() ) { // Loaded asset, return true if there is a rendering info for it UObject* Asset = AssetData.GetAsset(); FThumbnailRenderingInfo* RenderInfo = GUnrealEd->GetThumbnailManager()->GetRenderingInfo( Asset ); if ( RenderInfo != NULL && RenderInfo->Renderer != NULL ) { return true; } } const FObjectThumbnail* CachedThumbnail = ThumbnailTools::FindCachedThumbnail(*AssetData.GetFullName()); if ( CachedThumbnail != NULL ) { // There is a cached thumbnail for this asset, we should render it return !CachedThumbnail->IsEmpty(); } if ( AssetData.AssetClassPath != UBlueprint::StaticClass()->GetClassPathName() ) { // If we are not a blueprint, see if the CDO of the asset's class has a rendering info // Blueprints can't do this because the rendering info is based on the generated class UClass* AssetClass = FindObject(AssetData.AssetClassPath); if ( AssetClass ) { FThumbnailRenderingInfo* RenderInfo = GUnrealEd->GetThumbnailManager()->GetRenderingInfo( AssetClass->GetDefaultObject() ); if ( RenderInfo != NULL && RenderInfo->Renderer != NULL ) { return true; } } } // Unloaded blueprint or asset that may have a custom thumbnail, check to see if there is a thumbnail in the package to render FString PackageFilename; if ( FPackageName::DoesPackageExist(AssetData.PackageName.ToString(), &PackageFilename) ) { TSet ObjectFullNames; FThumbnailMap ThumbnailMap; FName ObjectFullName = FName(*AssetData.GetFullName()); ObjectFullNames.Add(ObjectFullName); ThumbnailTools::LoadThumbnailsFromPackage(PackageFilename, ObjectFullNames, ThumbnailMap); const FObjectThumbnail* ThumbnailPtr = ThumbnailMap.Find(ObjectFullName); if (ThumbnailPtr) { const FObjectThumbnail& ObjectThumbnail = *ThumbnailPtr; return ObjectThumbnail.GetImageWidth() > 0 && ObjectThumbnail.GetImageHeight() > 0 && ObjectThumbnail.GetCompressedDataSize() > 0; } } // Always render thumbnails with custom thumbnails FString CustomThumbnailTagValue; if (AssetData.GetTagValue(FAssetThumbnailPool::CustomThumbnailTagName, CustomThumbnailTagValue)) { return true; } return false; } FText GetLabelText() const { if( Label != EThumbnailLabel::NoLabel ) { if ( Label == EThumbnailLabel::ClassName ) { return GetAssetClassDisplayName(); } else if ( Label == EThumbnailLabel::AssetName ) { return GetAssetDisplayName(); } } return FText::GetEmpty(); } FText GetDisplayNameForClass( UClass* Class, const FAssetData* AssetData = nullptr) const { FText ClassDisplayName; if ( Class ) { FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked("AssetTools"); TWeakPtr AssetTypeActions = AssetToolsModule.Get().GetAssetTypeActionsForClass(Class); if ( AssetTypeActions.IsValid() ) { if (AssetData != nullptr) { ClassDisplayName = AssetTypeActions.Pin()->GetDisplayNameFromAssetData(*AssetData); } if (ClassDisplayName.IsEmpty()) { ClassDisplayName = AssetTypeActions.Pin()->GetName(); } } if ( ClassDisplayName.IsEmpty() ) { ClassDisplayName = Class->GetDisplayNameText(); } } return ClassDisplayName; } FText GetAssetClassDisplayName() const { const FAssetData& AssetData = AssetThumbnail->GetAssetData(); FTopLevelAssetPath AssetClass = AssetData.AssetClassPath; UClass* Class = FindObjectSafe(AssetClass); if ( Class ) { return GetDisplayNameForClass( Class, &AssetData ); } return FText::FromString(AssetClass.ToString()); } FText GetAssetDisplayName() const { const FAssetData& AssetData = AssetThumbnail->GetAssetData(); if ( AssetData.GetClass() == UClass::StaticClass() ) { UClass* Class = Cast( AssetData.GetAsset() ); return GetDisplayNameForClass( Class ); } return FText::FromName(AssetData.AssetName); } void OnRenderedThumbnailChanged( bool bInHasRenderedThumbnail ) { bHasRenderedThumbnail = bInHasRenderedThumbnail; UpdateThumbnailVisibilities(); } void UpdateThumbnailVisibilities() { // Either the generic label or thumbnail should be shown, but not both at once const EVisibility ClassThumbnailVisibility = GetClassThumbnailVisibility(); if( GenericThumbnailImage.IsValid() ) { GenericThumbnailImage->SetVisibility( ClassThumbnailVisibility ); } if( GenericLabelTextBlock.IsValid() ) { GenericLabelTextBlock->SetVisibility( (ClassThumbnailVisibility == EVisibility::Visible) ? EVisibility::Collapsed : EVisibility::Visible ); } const EVisibility ViewportVisibility = GetViewportVisibility(); if( RenderedThumbnailWidget.IsValid() ) { RenderedThumbnailWidget->SetVisibility( ViewportVisibility ); if( ClassIconWidget.IsValid() ) { ClassIconWidget->SetVisibility( ViewportVisibility ); } } } TOptional GetGenericThumbnailDesiredSize() const { const int32 Size = GenericThumbnailSize.Get(); return FVector2D(Size, Size); } private: TSharedPtr GenericLabelTextBlock; TSharedPtr HintTextBlock; TSharedPtr GenericThumbnailImage; TSharedPtr ClassIconWidget; TSharedPtr RenderedThumbnailWidget; TSharedPtr AssetBackgroundWidget; TSharedPtr AssetColorStripWidget; TSharedPtr AssetThumbnail; FCurveSequence ViewportFadeAnimation; FCurveHandle ViewportFadeCurve; FLinearColor AssetColor; TOptional AssetTypeColorOverride; float WidthLastFrame; float GenericThumbnailBorderPadding; bool bHasRenderedThumbnail; FName Style; TAttribute< FText > HighlightedText; EThumbnailLabel::Type Label; TAttribute< FLinearColor > HintColorAndOpacity; TAttribute GenericThumbnailSize; EThumbnailColorStripOrientation ColorStripOrientation; bool bAllowHintText; bool bAllowRealTimeOnHovered; /** The name of the thumbnail which should be used instead of the class thumbnail. */ FName ClassThumbnailBrushOverride; FName AssetBackgroundBrushName; FName ClassBackgroundBrushName; const FSlateBrush* ThumbnailBrush; const FSlateBrush* ClassIconBrush; /** The class to use when finding the thumbnail. */ TWeakObjectPtr ThumbnailClass; /** Are we showing a class type? (UClass, UBlueprint) */ bool bIsClassType; }; FAssetThumbnail::FAssetThumbnail( UObject* InAsset, uint32 InWidth, uint32 InHeight, const TSharedPtr& InThumbnailPool ) : ThumbnailPool(InThumbnailPool) , AssetData(InAsset ? FAssetData(InAsset) : FAssetData()) , Width( InWidth ) , Height( InHeight ) { if ( InThumbnailPool.IsValid() ) { InThumbnailPool->AddReferencer(*this); } } FAssetThumbnail::FAssetThumbnail( const FAssetData& InAssetData , uint32 InWidth, uint32 InHeight, const TSharedPtr& InThumbnailPool ) : ThumbnailPool( InThumbnailPool ) , AssetData ( InAssetData ) , Width( InWidth ) , Height( InHeight ) { if ( InThumbnailPool.IsValid() ) { InThumbnailPool->AddReferencer(*this); } } FAssetThumbnail::~FAssetThumbnail() { if ( ThumbnailPool.IsValid() ) { ThumbnailPool.Pin()->RemoveReferencer(*this); } } FIntPoint FAssetThumbnail::GetSize() const { return FIntPoint( Width, Height ); } FSlateShaderResource* FAssetThumbnail::GetViewportRenderTargetTexture() const { FSlateTexture2DRHIRef* Texture = NULL; if ( ThumbnailPool.IsValid() ) { Texture = ThumbnailPool.Pin()->AccessTexture( AssetData, Width, Height ); } if( !Texture || !Texture->IsValid() ) { return NULL; } return Texture; } UObject* FAssetThumbnail::GetAsset() const { if ( AssetData.ObjectPath != NAME_None ) { return FindObject(NULL, *AssetData.ObjectPath.ToString()); } else { return NULL; } } const FAssetData& FAssetThumbnail::GetAssetData() const { return AssetData; } void FAssetThumbnail::SetAsset( const UObject* InAsset ) { SetAsset( FAssetData(InAsset) ); } void FAssetThumbnail::SetAsset( const FAssetData& InAssetData ) { if ( ThumbnailPool.IsValid() ) { ThumbnailPool.Pin()->RemoveReferencer(*this); } if ( InAssetData.IsValid() ) { AssetData = InAssetData; if ( ThumbnailPool.IsValid() ) { ThumbnailPool.Pin()->AddReferencer(*this); } } else { AssetData = FAssetData(); } AssetDataChangedEvent.Broadcast(); } TSharedRef FAssetThumbnail::MakeThumbnailWidget( const FAssetThumbnailConfig& InConfig ) { return SNew(SAssetThumbnail) .AssetThumbnail(SharedThis(this)) .ThumbnailPool(ThumbnailPool.Pin()) .AllowFadeIn(InConfig.bAllowFadeIn) .ForceGenericThumbnail(InConfig.bForceGenericThumbnail) .Label(InConfig.ThumbnailLabel) .HighlightedText(InConfig.HighlightedText) .HintColorAndOpacity(InConfig.HintColorAndOpacity) .AllowHintText(InConfig.bAllowHintText) .AllowRealTimeOnHovered(InConfig.bAllowRealTimeOnHovered) .ClassThumbnailBrushOverride(InConfig.ClassThumbnailBrushOverride) .AllowAssetSpecificThumbnailOverlay(InConfig.bAllowAssetSpecificThumbnailOverlay) .AssetTypeColorOverride(InConfig.AssetTypeColorOverride) .Padding(InConfig.Padding) .GenericThumbnailSize(InConfig.GenericThumbnailSize) .ColorStripOrientation(InConfig.ColorStripOrientation); } void FAssetThumbnail::RefreshThumbnail() { if ( ThumbnailPool.IsValid() && AssetData.IsValid() ) { ThumbnailPool.Pin()->RefreshThumbnail( SharedThis(this) ); } } void FAssetThumbnail::SetRealTime(bool bRealTime) { if (ThumbnailPool.IsValid() && AssetData.IsValid()) { ThumbnailPool.Pin()->SetRealTimeThumbnail( SharedThis(this), bRealTime ); } } FAssetThumbnailPool::FAssetThumbnailPool( uint32 InNumInPool, double InMaxFrameTimeAllowance, uint32 InMaxRealTimeThumbnailsPerFrame ) : NumInPool( InNumInPool ) , MaxRealTimeThumbnailsPerFrame( InMaxRealTimeThumbnailsPerFrame ) , MaxFrameTimeAllowance( InMaxFrameTimeAllowance ) { FCoreUObjectDelegates::OnObjectPropertyChanged.AddRaw(this, &FAssetThumbnailPool::OnObjectPropertyChanged); FCoreUObjectDelegates::OnAssetLoaded.AddRaw(this, &FAssetThumbnailPool::OnAssetLoaded); if ( GEditor ) { GEditor->OnActorMoved().AddRaw( this, &FAssetThumbnailPool::OnActorPostEditMove ); } // Add the custom thumbnail tag to the list of tags that the asset registry can parse TSet& MetaDataTagsForAssetRegistry = UObject::GetMetaDataTagsForAssetRegistry(); MetaDataTagsForAssetRegistry.Add(CustomThumbnailTagName); } FAssetThumbnailPool::~FAssetThumbnailPool() { FCoreUObjectDelegates::OnObjectPropertyChanged.RemoveAll(this); FCoreUObjectDelegates::OnAssetLoaded.RemoveAll(this); if ( GEditor ) { GEditor->OnActorMoved().RemoveAll(this); } // Release all the texture resources ReleaseResources(); } FAssetThumbnailPool::FThumbnailInfo::~FThumbnailInfo() { if( ThumbnailTexture ) { delete ThumbnailTexture; ThumbnailTexture = NULL; } if( ThumbnailRenderTarget ) { delete ThumbnailRenderTarget; ThumbnailRenderTarget = NULL; } } void FAssetThumbnailPool::ReleaseResources() { // Clear all pending render requests ThumbnailsToRenderStack.Empty(); RealTimeThumbnails.Empty(); RealTimeThumbnailsToRender.Empty(); TArray< TSharedRef > ThumbnailsToRelease; for( auto ThumbIt = ThumbnailToTextureMap.CreateConstIterator(); ThumbIt; ++ThumbIt ) { ThumbnailsToRelease.Add(ThumbIt.Value()); } ThumbnailToTextureMap.Empty(); for( auto ThumbIt = FreeThumbnails.CreateConstIterator(); ThumbIt; ++ThumbIt ) { ThumbnailsToRelease.Add(*ThumbIt); } FreeThumbnails.Empty(); for ( auto ThumbIt = ThumbnailsToRelease.CreateConstIterator(); ThumbIt; ++ThumbIt ) { const TSharedRef& Thumb = *ThumbIt; // Release rendering resources FThumbnailInfo_RenderThread ThumbInfo = Thumb.Get(); ENQUEUE_RENDER_COMMAND(ReleaseThumbnailResources)( [ThumbInfo](FRHICommandListImmediate& RHICmdList) { ThumbInfo.ThumbnailTexture->ClearTextureData(); ThumbInfo.ThumbnailTexture->ReleaseResource(); ThumbInfo.ThumbnailRenderTarget->ReleaseResource(); }); } // Wait for all resources to be released FlushRenderingCommands(); // Make sure there are no more references to any of our thumbnails now that rendering commands have been flushed for ( auto ThumbIt = ThumbnailsToRelease.CreateConstIterator(); ThumbIt; ++ThumbIt ) { const TSharedRef& Thumb = *ThumbIt; if ( !Thumb.IsUnique() ) { ensureMsgf(0, TEXT("Thumbnail info for '%s' is still referenced by '%d' other objects"), *Thumb->AssetData.ObjectPath.ToString(), Thumb.GetSharedReferenceCount()); } } } TStatId FAssetThumbnailPool::GetStatId() const { RETURN_QUICK_DECLARE_CYCLE_STAT( FAssetThumbnailPool, STATGROUP_Tickables ); } bool FAssetThumbnailPool::IsTickable() const { return RecentlyLoadedAssets.Num() > 0 || ThumbnailsToRenderStack.Num() > 0 || RealTimeThumbnails.Num() > 0 || bWereShadersCompilingLastFrame || (GShaderCompilingManager && GShaderCompilingManager->IsCompiling()); } void FAssetThumbnailPool::Tick( float DeltaTime ) { // If throttling do not tick unless drag dropping which could have a thumbnail as the cursor decorator if (FSlateApplication::IsInitialized() && !FSlateApplication::Get().IsDragDropping() && !FSlateThrottleManager::Get().IsAllowingExpensiveTasks() && !FSlateApplication::Get().AnyMenusVisible()) { return; } const bool bAreShadersCompiling = (GShaderCompilingManager && GShaderCompilingManager->IsCompiling()); if (bWereShadersCompilingLastFrame && !bAreShadersCompiling) { ThumbnailsToRenderStack.Reset(); // Reschedule visible thumbnails to be rerendered now that shaders are finished compiling for (auto ThumbIt = ThumbnailToTextureMap.CreateIterator(); ThumbIt; ++ThumbIt) { ThumbnailsToRenderStack.Push(ThumbIt.Value()); } } bWereShadersCompilingLastFrame = bAreShadersCompiling; TRACE_CPUPROFILER_EVENT_SCOPE(FAssetThumbnailPool::Tick); // If there were any assets loaded since last frame that we are currently displaying thumbnails for, push them on the render stack now. if ( RecentlyLoadedAssets.Num() > 0 ) { for ( int32 LoadedAssetIdx = 0; LoadedAssetIdx < RecentlyLoadedAssets.Num(); ++LoadedAssetIdx ) { RefreshThumbnailsFor(RecentlyLoadedAssets[LoadedAssetIdx]); } RecentlyLoadedAssets.Empty(); } // If we have dynamic thumbnails and we are done rendering the last batch of dynamic thumbnails, start a new batch as long as real-time thumbnails are enabled const bool bIsInPIEOrSimulate = GEditor->PlayWorld != NULL || GEditor->bIsSimulatingInEditor; const bool bShouldUseRealtimeThumbnails = GetDefault()->RealTimeThumbnails && !bIsInPIEOrSimulate; if ( bShouldUseRealtimeThumbnails && RealTimeThumbnails.Num() > 0 && RealTimeThumbnailsToRender.Num() == 0 ) { double CurrentTime = FPlatformTime::Seconds(); for ( int32 ThumbIdx = RealTimeThumbnails.Num() - 1; ThumbIdx >= 0; --ThumbIdx ) { const TSharedRef& Thumb = RealTimeThumbnails[ThumbIdx]; if ( Thumb->AssetData.IsAssetLoaded() ) { // Only render thumbnails that have been requested recently if ( (CurrentTime - Thumb->LastAccessTime) < 1.f ) { RealTimeThumbnailsToRender.Add(Thumb); } } else { RealTimeThumbnails.RemoveAt(ThumbIdx); } } } uint32 NumRealTimeThumbnailsRenderedThisFrame = 0; // If there are any thumbnails to render, pop one off the stack and render it. if( ThumbnailsToRenderStack.Num() + RealTimeThumbnailsToRender.Num() > 0 ) { double FrameStartTime = FPlatformTime::Seconds(); // Render as many thumbnails as we are allowed to while( ThumbnailsToRenderStack.Num() + RealTimeThumbnailsToRender.Num() > 0 && FPlatformTime::Seconds() - FrameStartTime < MaxFrameTimeAllowance ) { TSharedPtr Info; if ( ThumbnailsToRenderStack.Num() > 0 ) { Info = ThumbnailsToRenderStack.Pop(); } else if (RealTimeThumbnailsToRender.Num() > 0 && NumRealTimeThumbnailsRenderedThisFrame < MaxRealTimeThumbnailsPerFrame ) { Info = RealTimeThumbnailsToRender.Pop(); NumRealTimeThumbnailsRenderedThisFrame++; } else { // No thumbnails left to render or we don't want to render any more break; } if( Info.IsValid() ) { bool bIsAssetStillCompiling = false; TSharedRef InfoRef = Info.ToSharedRef(); if ( InfoRef->AssetData.IsValid() ) { FAssetData CustomThumbnailAsset; // Check if a different asset should be used to generate the thumbnail for this asset FString CustomThumbnailTagValue; if (InfoRef->AssetData.GetTagValue(CustomThumbnailTagName, CustomThumbnailTagValue)) { if (FPackageName::IsValidObjectPath(CustomThumbnailTagValue)) { CustomThumbnailAsset = FModuleManager::LoadModuleChecked(AssetRegistryConstants::ModuleName).Get().GetAssetByObjectPath(*CustomThumbnailTagValue); } } bool bLoadedThumbnail = LoadThumbnail(InfoRef, bIsAssetStillCompiling, CustomThumbnailAsset); // If we failed to load a custom thumbnail, then load the custom thumbnail's asset and try again if (!bLoadedThumbnail && CustomThumbnailAsset.IsValid()) { // Only load the custom thumbnail if the original asset is also loaded //if (InfoRef->AssetData.IsAssetLoaded()) { if (UObject* CustomThumbnail = CustomThumbnailAsset.GetAsset()) { bLoadedThumbnail = LoadThumbnail(InfoRef, bIsAssetStillCompiling, CustomThumbnailAsset); CustomThumbnail->ClearFlags(RF_Standalone); } } } if ( bLoadedThumbnail ) { // Mark it as updated InfoRef->LastUpdateTime = FPlatformTime::Seconds(); // Notify listeners that a thumbnail has been rendered ThumbnailRenderedEvent.Broadcast(InfoRef->AssetData); } // Do not send a failure event for this asset yet if shaders are still compiling or the asset itself is compiling. // The failure event will disable the rendering of this asset for good and we need to have a chance to // rerender it when everything settles down. else if (!bAreShadersCompiling && !bIsAssetStillCompiling) { // Notify listeners that a thumbnail render has failed ThumbnailRenderFailedEvent.Broadcast(InfoRef->AssetData); } } } } } } bool FAssetThumbnailPool::LoadThumbnail(TSharedRef ThumbnailInfo, bool &bIsAssetStillCompiling, const FAssetData& CustomAssetToRender) { const FAssetData& AssetData = CustomAssetToRender.IsValid() ? CustomAssetToRender : ThumbnailInfo->AssetData; UObject* Asset = AssetData.IsAssetLoaded() ? AssetData.GetAsset() : nullptr; const bool bAreShadersCompiling = (GShaderCompilingManager && GShaderCompilingManager->IsCompiling()); if (Asset && !bAreShadersCompiling) { //Avoid rendering the thumbnail of an asset that is currently edited asynchronously const IInterface_AsyncCompilation* Interface_AsyncCompilation = Cast(Asset); bIsAssetStillCompiling = Interface_AsyncCompilation && Interface_AsyncCompilation->IsCompiling(); if (!bIsAssetStillCompiling) { FThumbnailRenderingInfo* RenderInfo = GUnrealEd->GetThumbnailManager()->GetRenderingInfo(Asset); if (RenderInfo != nullptr && RenderInfo->Renderer != nullptr) { FThumbnailInfo_RenderThread ThumbInfo = ThumbnailInfo.Get(); ENQUEUE_RENDER_COMMAND(SyncSlateTextureCommand)( [ThumbInfo](FRHICommandListImmediate& RHICmdList) { if (ThumbInfo.ThumbnailTexture->GetTypedResource() != ThumbInfo.ThumbnailRenderTarget->GetTextureRHI()) { ThumbInfo.ThumbnailTexture->ClearTextureData(); ThumbInfo.ThumbnailTexture->ReleaseDynamicRHI(); ThumbInfo.ThumbnailTexture->SetRHIRef(ThumbInfo.ThumbnailRenderTarget->GetTextureRHI(), ThumbInfo.Width, ThumbInfo.Height); } }); if (ThumbnailInfo->LastUpdateTime <= 0.0f || RenderInfo->Renderer->AllowsRealtimeThumbnails(Asset)) { //@todo: this should be done on the GPU only but it is not supported by thumbnail tools yet ThumbnailTools::RenderThumbnail( Asset, ThumbnailInfo->Width, ThumbnailInfo->Height, ThumbnailTools::EThumbnailTextureFlushMode::NeverFlush, ThumbnailInfo->ThumbnailRenderTarget ); } return true; } } } FThumbnailMap ThumbnailMap; // If we could not render a fresh thumbnail, see if we already have a cached one to load const FObjectThumbnail* FoundThumbnail = ThumbnailTools::FindCachedThumbnail(AssetData.GetFullName()); if (!FoundThumbnail) { // If we don't have a cached thumbnail, try to find it on disk FString PackageFilename; if (FPackageName::DoesPackageExist(AssetData.PackageName.ToString(), &PackageFilename)) { TSet ObjectFullNames; FName ObjectFullName = FName(*AssetData.GetFullName()); ObjectFullNames.Add(ObjectFullName); ThumbnailTools::LoadThumbnailsFromPackage(PackageFilename, ObjectFullNames, ThumbnailMap); FoundThumbnail = ThumbnailMap.Find(ObjectFullName); } } if (FoundThumbnail) { if (FoundThumbnail->GetImageWidth() > 0 && FoundThumbnail->GetImageHeight() > 0 && FoundThumbnail->GetUncompressedImageData().Num() > 0) { // Make bulk data for updating the texture memory later FSlateTextureData* BulkData = new FSlateTextureData(FoundThumbnail->GetImageWidth(), FoundThumbnail->GetImageHeight(), GPixelFormats[PF_B8G8R8A8].BlockBytes, FoundThumbnail->AccessImageData()); // Update the texture RHI FThumbnailInfo_RenderThread ThumbInfo = ThumbnailInfo.Get(); ENQUEUE_RENDER_COMMAND(ClearSlateTextureCommand)( [ThumbInfo, BulkData](FRHICommandListImmediate& RHICmdList) { if (ThumbInfo.ThumbnailTexture->GetTypedResource() == ThumbInfo.ThumbnailRenderTarget->GetTextureRHI()) { ThumbInfo.ThumbnailTexture->SetRHIRef(NULL, ThumbInfo.Width, ThumbInfo.Height); } ThumbInfo.ThumbnailTexture->SetTextureData(MakeShareable(BulkData)); ThumbInfo.ThumbnailTexture->UpdateRHI(); }); return true; } } return false; } FSlateTexture2DRHIRef* FAssetThumbnailPool::AccessTexture( const FAssetData& AssetData, uint32 Width, uint32 Height ) { if(AssetData.ObjectPath == NAME_None || Width == 0 || Height == 0) { return NULL; } else { FThumbId ThumbId( AssetData.ObjectPath, Width, Height ) ; // Check to see if a thumbnail for this asset exists. If so we don't need to render it const TSharedRef* ThumbnailInfoPtr = ThumbnailToTextureMap.Find( ThumbId ); TSharedPtr ThumbnailInfo; if( ThumbnailInfoPtr ) { ThumbnailInfo = *ThumbnailInfoPtr; } else { // If a the max number of thumbnails allowed by the pool exists then reuse its rendering resource for the new thumbnail if( FreeThumbnails.Num() == 0 && ThumbnailToTextureMap.Num() == NumInPool ) { // Find the thumbnail which was accessed last and use it for the new thumbnail float LastAccessTime = FLT_MAX; const FThumbId* AssetToRemove = NULL; for( TMap< FThumbId, TSharedRef >::TConstIterator It(ThumbnailToTextureMap); It; ++It ) { if( It.Value()->LastAccessTime < LastAccessTime ) { LastAccessTime = It.Value()->LastAccessTime; AssetToRemove = &It.Key(); } } check( AssetToRemove ); // Remove the old mapping ThumbnailInfo = ThumbnailToTextureMap.FindAndRemoveChecked( *AssetToRemove ); } else if( FreeThumbnails.Num() > 0 ) { ThumbnailInfo = FreeThumbnails.Pop(); FSlateTextureRenderTarget2DResource* ThumbnailRenderTarget = ThumbnailInfo->ThumbnailRenderTarget; ENQUEUE_RENDER_COMMAND(SlateUpdateThumbSizeCommand)( [ThumbnailRenderTarget, Width, Height](FRHICommandListImmediate& RHICmdList) { ThumbnailRenderTarget->SetSize(Width, Height); }); } else { // There are no free thumbnail resources check( (uint32)ThumbnailToTextureMap.Num() <= NumInPool ); check( !ThumbnailInfo.IsValid() ); // The pool isn't used up so just make a new texture // Make new thumbnail info if it doesn't exist // This happens when the pool is not yet full ThumbnailInfo = MakeShareable( new FThumbnailInfo ); // Set the thumbnail and asset on the info. It is NOT safe to change or NULL these pointers until ReleaseResources. ThumbnailInfo->ThumbnailTexture = new FSlateTexture2DRHIRef( Width, Height, PF_B8G8R8A8, NULL, TexCreate_Dynamic ); ThumbnailInfo->ThumbnailRenderTarget = new FSlateTextureRenderTarget2DResource(FLinearColor::Black, Width, Height, PF_B8G8R8A8, SF_Point, TA_Wrap, TA_Wrap, 0.0f); BeginInitResource( ThumbnailInfo->ThumbnailTexture ); BeginInitResource( ThumbnailInfo->ThumbnailRenderTarget ); } check( ThumbnailInfo.IsValid() ); TSharedRef ThumbnailRef = ThumbnailInfo.ToSharedRef(); // Map the object to its thumbnail info ThumbnailToTextureMap.Add( ThumbId, ThumbnailRef ); ThumbnailInfo->AssetData = AssetData; ThumbnailInfo->Width = Width; ThumbnailInfo->Height = Height; // Mark this thumbnail as needing to be updated ThumbnailInfo->LastUpdateTime = -1.f; // Request that the thumbnail be rendered as soon as possible ThumbnailsToRenderStack.Push( ThumbnailRef ); } // This thumbnail was accessed, update its last time to the current time // We'll use LastAccessTime to determine the order to recycle thumbnails if the pool is full ThumbnailInfo->LastAccessTime = FPlatformTime::Seconds(); return ThumbnailInfo->ThumbnailTexture; } } void FAssetThumbnailPool::AddReferencer( const FAssetThumbnail& AssetThumbnail ) { FIntPoint Size = AssetThumbnail.GetSize(); if ( AssetThumbnail.GetAssetData().ObjectPath == NAME_None || Size.X == 0 || Size.Y == 0 ) { // Invalid referencer return; } // Generate a key and look up the number of references in the RefCountMap FThumbId ThumbId( AssetThumbnail.GetAssetData().ObjectPath, Size.X, Size.Y ) ; int32* RefCountPtr = RefCountMap.Find(ThumbId); if ( RefCountPtr ) { // Already in the map, increment a reference (*RefCountPtr)++; } else { // New referencer, add it to the map with a RefCount of 1 RefCountMap.Add(ThumbId, 1); } } void FAssetThumbnailPool::RemoveReferencer( const FAssetThumbnail& AssetThumbnail ) { FIntPoint Size = AssetThumbnail.GetSize(); const FName ObjectPath = AssetThumbnail.GetAssetData().ObjectPath; if ( ObjectPath == NAME_None || Size.X == 0 || Size.Y == 0 ) { // Invalid referencer return; } // Generate a key and look up the number of references in the RefCountMap FThumbId ThumbId( ObjectPath, Size.X, Size.Y ) ; int32* RefCountPtr = RefCountMap.Find(ThumbId); // This should complement an AddReferencer so this entry should be in the map if ( RefCountPtr ) { // Decrement the RefCount (*RefCountPtr)--; // If we reached zero, free the thumbnail and remove it from the map. if ( (*RefCountPtr) <= 0 ) { RefCountMap.Remove(ThumbId); FreeThumbnail(ObjectPath, Size.X, Size.Y); } } else { // This FAssetThumbnail did not reference anything or was deleted after the pool was deleted. } } bool FAssetThumbnailPool::IsInRenderStack( const TSharedPtr& Thumbnail ) const { const FAssetData& AssetData = Thumbnail->GetAssetData(); const uint32 Width = Thumbnail->GetSize().X; const uint32 Height = Thumbnail->GetSize().Y; if ( ensure(AssetData.ObjectPath != NAME_None) && ensure(Width > 0) && ensure(Height > 0) ) { FThumbId ThumbId( AssetData.ObjectPath, Width, Height ) ; const TSharedRef* ThumbnailInfoPtr = ThumbnailToTextureMap.Find( ThumbId ); if ( ThumbnailInfoPtr ) { return ThumbnailsToRenderStack.Contains(*ThumbnailInfoPtr); } } return false; } bool FAssetThumbnailPool::IsRendered(const TSharedPtr& Thumbnail) const { const FAssetData& AssetData = Thumbnail->GetAssetData(); const uint32 Width = Thumbnail->GetSize().X; const uint32 Height = Thumbnail->GetSize().Y; if (ensure(AssetData.ObjectPath != NAME_None) && ensure(Width > 0) && ensure(Height > 0)) { FThumbId ThumbId(AssetData.ObjectPath, Width, Height); const TSharedRef* ThumbnailInfoPtr = ThumbnailToTextureMap.Find(ThumbId); if (ThumbnailInfoPtr) { return (*ThumbnailInfoPtr).Get().LastUpdateTime >= 0.f; } } return false; } void FAssetThumbnailPool::PrioritizeThumbnails( const TArray< TSharedPtr >& ThumbnailsToPrioritize, uint32 Width, uint32 Height ) { if ( ensure(Width > 0) && ensure(Height > 0) ) { TSet ObjectPathList; for ( int32 ThumbIdx = 0; ThumbIdx < ThumbnailsToPrioritize.Num(); ++ThumbIdx ) { ObjectPathList.Add(ThumbnailsToPrioritize[ThumbIdx]->GetAssetData().ObjectPath); } TArray< TSharedRef > FoundThumbnails; for ( int32 ThumbIdx = ThumbnailsToRenderStack.Num() - 1; ThumbIdx >= 0; --ThumbIdx ) { const TSharedRef& ThumbnailInfo = ThumbnailsToRenderStack[ThumbIdx]; if ( ThumbnailInfo->Width == Width && ThumbnailInfo->Height == Height && ObjectPathList.Contains(ThumbnailInfo->AssetData.ObjectPath) ) { FoundThumbnails.Add(ThumbnailInfo); ThumbnailsToRenderStack.RemoveAt(ThumbIdx); } } for ( int32 ThumbIdx = 0; ThumbIdx < FoundThumbnails.Num(); ++ThumbIdx ) { ThumbnailsToRenderStack.Push(FoundThumbnails[ThumbIdx]); } } } void FAssetThumbnailPool::RefreshThumbnail( const TSharedPtr& ThumbnailToRefresh ) { const FAssetData& AssetData = ThumbnailToRefresh->GetAssetData(); const uint32 Width = ThumbnailToRefresh->GetSize().X; const uint32 Height = ThumbnailToRefresh->GetSize().Y; if ( ensure(AssetData.ObjectPath != NAME_None) && ensure(Width > 0) && ensure(Height > 0) ) { FThumbId ThumbId( AssetData.ObjectPath, Width, Height ) ; const TSharedRef* ThumbnailInfoPtr = ThumbnailToTextureMap.Find( ThumbId ); if ( ThumbnailInfoPtr ) { ThumbnailsToRenderStack.AddUnique(*ThumbnailInfoPtr); } } } void FAssetThumbnailPool::SetRealTimeThumbnail( const TSharedPtr& Thumbnail, bool bRealTimeThumbnail ) { const FAssetData& AssetData = Thumbnail->GetAssetData(); const uint32 Width = Thumbnail->GetSize().X; const uint32 Height = Thumbnail->GetSize().Y; if (ensure(AssetData.ObjectPath != NAME_None) && ensure(Width > 0) && ensure(Height > 0) ) { FThumbId ThumbId( AssetData.ObjectPath, Width, Height ); const TSharedRef* ThumbnailInfoPtr = ThumbnailToTextureMap.Find( ThumbId ); if ( ThumbnailInfoPtr ) { if ( bRealTimeThumbnail ) { RealTimeThumbnails.AddUnique(*ThumbnailInfoPtr); } else { RealTimeThumbnails.Remove(*ThumbnailInfoPtr); } } } } void FAssetThumbnailPool::FreeThumbnail( const FName& ObjectPath, uint32 Width, uint32 Height ) { if(ObjectPath != NAME_None && Width != 0 && Height != 0) { FThumbId ThumbId( ObjectPath, Width, Height ) ; const TSharedRef* ThumbnailInfoPtr = ThumbnailToTextureMap.Find(ThumbId); if( ThumbnailInfoPtr ) { TSharedRef ThumbnailInfo = *ThumbnailInfoPtr; ThumbnailToTextureMap.Remove(ThumbId); ThumbnailsToRenderStack.Remove(ThumbnailInfo); RealTimeThumbnails.Remove(ThumbnailInfo); RealTimeThumbnailsToRender.Remove(ThumbnailInfo); FSlateTexture2DRHIRef* ThumbnailTexture = ThumbnailInfo->ThumbnailTexture; ENQUEUE_RENDER_COMMAND(ReleaseThumbnailTextureData)( [ThumbnailTexture](FRHICommandListImmediate& RHICmdList) { ThumbnailTexture->ClearTextureData(); }); FreeThumbnails.Add( ThumbnailInfo ); } } } void FAssetThumbnailPool::RefreshThumbnailsFor( FName ObjectPath ) { for ( auto ThumbIt = ThumbnailToTextureMap.CreateIterator(); ThumbIt; ++ThumbIt) { if ( ThumbIt.Key().ObjectPath == ObjectPath ) { ThumbnailsToRenderStack.Push( ThumbIt.Value() ); } } } void FAssetThumbnailPool::OnAssetLoaded( UObject* Asset ) { if ( Asset != NULL ) { RecentlyLoadedAssets.Add( FName(*Asset->GetPathName()) ); } } void FAssetThumbnailPool::OnActorPostEditMove( AActor* Actor ) { DirtyThumbnailForObject(Actor); } void FAssetThumbnailPool::OnObjectPropertyChanged( UObject* ObjectBeingModified, FPropertyChangedEvent& PropertyChangedEvent ) { DirtyThumbnailForObject(ObjectBeingModified); } void FAssetThumbnailPool::DirtyThumbnailForObject(UObject* ObjectBeingModified) { if (!ObjectBeingModified) { return; } if (ObjectBeingModified->HasAnyFlags(RF_ClassDefaultObject)) { if (ObjectBeingModified->GetClass()->ClassGeneratedBy != nullptr) { // This is a blueprint modification. Check to see if this thumbnail is the blueprint of the modified CDO ObjectBeingModified = ObjectBeingModified->GetClass()->ClassGeneratedBy; } } else if (AActor* ActorBeingModified = Cast(ObjectBeingModified)) { // This is a non CDO actor getting modified. Update the actor's world's thumbnail. ObjectBeingModified = ActorBeingModified->GetWorld(); } if (ObjectBeingModified) { // An object in memory was modified. We'll mark it's thumbnail as dirty so that it'll be // regenerated on demand later. (Before being displayed in the browser, or package saves, etc.) FObjectThumbnail* Thumbnail = ThumbnailTools::GetThumbnailForObject(ObjectBeingModified); // If we don't yet have a thumbnail map, load one from disk if possible if (Thumbnail == nullptr) { UPackage* ObjectPackage = ObjectBeingModified->GetOutermost(); const bool bMemoryPackage = FPackageName::IsMemoryPackage(ObjectBeingModified->GetPathName()); // Don't try to load from disk if the package is a memory package const bool bUnsavedPackage = ObjectPackage->HasAnyPackageFlags(PKG_NewlyCreated); // Don't try loading thumbnails for package that have never been saved const bool bIsGarbageCollecting = IsGarbageCollecting(); // Don't attempt to do this while garbage collecting since loading or finding objects during GC is illegal const bool bTryLoadThumbnailFromDisk = !bIsGarbageCollecting && !bMemoryPackage && !bUnsavedPackage; if (bTryLoadThumbnailFromDisk) { FName ObjectFullName = FName(*ObjectBeingModified->GetFullName()); FThumbnailMap LoadedThumbnails; if (ThumbnailTools::ConditionallyLoadThumbnailsForObjects({ ObjectFullName }, LoadedThumbnails)) { Thumbnail = LoadedThumbnails.Find(ObjectFullName); if (Thumbnail != nullptr) { Thumbnail = ThumbnailTools::CacheThumbnail(ObjectBeingModified->GetFullName(), Thumbnail, ObjectPackage); } } } } if (Thumbnail != nullptr) { // Mark the thumbnail as dirty Thumbnail->MarkAsDirty(); } RefreshThumbnailsFor( FName(*ObjectBeingModified->GetPathName()) ); } }