// 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 "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 "EditorStyleSet.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 "IAssetTools.h" #include "AssetTypeActions_Base.h" #include "AssetToolsModule.h" #include "Styling/SlateIconFinder.h" #include "ClassIconFinder.h" #include "IVREditorModule.h" #include "Framework/Application/SlateApplication.h" class SAssetThumbnail: public SCompoundWidget { public: SLATE_BEGIN_ARGS(SAssetThumbnail) : _Style("AssetThumbnail"), _ThumbnailPool(NULL), _AllowFadeIn(false), _ForceGenericThumbnail(false), _AllowHintText(true), _AllowAssetSpecificThumbnailOverlay(false), _Label(EThumbnailLabel::ClassName), _HighlightedText(FText::GetEmpty()), _HintColorAndOpacity(FLinearColor(0.0f, 0.0f, 0.0f, 0.0f)), _ClassThumbnailBrushOverride(NAME_None), _AssetTypeColorOverride() {} 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(EThumbnailLabel::Type, Label) SLATE_ATTRIBUTE(FText, HighlightedText) SLATE_ATTRIBUTE(FLinearColor, HintColorAndOpacity) SLATE_ARGUMENT(FName, ClassThumbnailBrushOverride) SLATE_ARGUMENT(TOptional, AssetTypeColorOverride) 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; ThumbnailBrush = nullptr; ClassIconBrush = nullptr; AssetThumbnail = InArgs._AssetThumbnail; bHasRenderedThumbnail = false; WidthLastFrame = 0; GenericThumbnailBorderPadding = 2.f; AssetThumbnail->OnAssetDataChanged().AddSP(this, &SAssetThumbnail::OnAssetDataChanged); const FAssetData& AssetData = AssetThumbnail->GetAssetData(); UClass* Class = FindObjectSafe(ANY_PACKAGE, *AssetData.AssetClass.ToString()); 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() [SAssignNew(AssetBackgroundWidget, SBorder) .BorderImage(GetAssetBackgroundBrush()) .BorderBackgroundColor(AssetColor.CopyWithNewOpacity(0.3f)) .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(FEditorStyle::GetColor(Style, ".ColorAndOpacity")) .ShadowOffset(FEditorStyle::GetVector(Style, ".ShadowOffset")) .ShadowColorAndOpacity(FEditorStyle::GetColor(Style, ".ShadowColorAndOpacity")) .HighlightText(HighlightedText)] + SOverlay::Slot() [SAssignNew(GenericThumbnailImage, SImage) .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); 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(0) .BorderImage(FEditorStyle::GetBrush("NoBrush")) .ColorAndOpacity(this, &SAssetThumbnail::GetViewportColorAndOpacity) [Viewport.ToSharedRef()]]; } if (ThumbnailClass.Get() && bIsClassType) { OverlayWidget->AddSlot() .VAlign(VAlign_Bottom) .HAlign(HAlign_Right) .Padding(TAttribute(this, &SAssetThumbnail::GetClassIconPadding)) [SAssignNew(ClassIconWidget, SBorder) .BorderImage(FEditorStyle::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(FEditorStyle::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(FEditorStyle::GetColor(Style, ".HintColorAndOpacity")) .ShadowOffset(FEditorStyle::GetVector(Style, ".HintShadowOffset")) .ShadowColorAndOpacity(FEditorStyle::GetColor(Style, ".HintShadowColorAndOpacity")) .HighlightText(HighlightedText)]]; } // The asset color strip OverlayWidget->AddSlot() .HAlign(HAlign_Fill) .VAlign(VAlign_Bottom) [SAssignNew(AssetColorStripWidget, SBorder) .BorderImage(FEditorStyle::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().AssetClass.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))); } // SWidget implementation virtual void OnMouseEnter(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override { SCompoundWidget::OnMouseEnter(MyGeometry, MouseEvent); if (!GetDefault()->RealTimeThumbnails) { // Update hovered thumbnails if we are not already updating them in real-time AssetThumbnail->RefreshThumbnail(); } } 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(ANY_PACKAGE, *AssetData.AssetClass.ToString()); 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 FEditorStyle::GetFontStyle(WidthLastFrame <= 64 ? FEditorStyle::Join(Style, ".FontSmall") : FEditorStyle::Join(Style, ".Font")); } FSlateFontInfo GetHintTextFont() const { return FEditorStyle::GetFontStyle(WidthLastFrame <= 64 ? FEditorStyle::Join(Style, ".HintFontSmall") : FEditorStyle::Join(Style, ".HintFont")); } float GetTextWrapWidth() const { return WidthLastFrame - GenericThumbnailBorderPadding * 2.f; } const FSlateBrush* GetAssetBackgroundBrush() const { return FEditorStyle::GetBrush(AssetBackgroundBrushName); } const FSlateBrush* GetClassBackgroundBrush() const { return FEditorStyle::GetBrush(ClassBackgroundBrushName); } FSlateColor GetViewportBorderColorAndOpacity() const { return FLinearColor(AssetColor.R, AssetColor.G, AssetColor.B, ViewportFadeCurve.GetLerp()); } FLinearColor GetViewportColorAndOpacity() const { return FLinearColor(1, 1, 1, ViewportFadeCurve.GetLerp()); } EVisibility GetViewportVisibility() const { return bHasRenderedThumbnail ? EVisibility::Visible : EVisibility::Collapsed; } float GetAssetColorStripHeight() const { // The strip is 2.5% the height of the thumbnail, but at least 3 units tall return FMath::Max(FMath::CeilToFloat(WidthLastFrame * 0.025f), 3.0f); } FMargin GetAssetColorStripPadding() const { const float Height = GetAssetColorStripHeight(); return FMargin(0, Height, 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 { const float Height = GetAssetColorStripHeight(); return FMargin(0, 0, 0, Height); } 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.AssetClass != UBlueprint::StaticClass()->GetFName()) { // 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(ANY_PACKAGE, *AssetData.AssetClass.ToString()); 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(), NULL, &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; } } 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(); FString AssetClass = AssetData.AssetClass.ToString(); UClass* Class = FindObjectSafe(ANY_PACKAGE, *AssetClass); if (Class) { return GetDisplayNameForClass(Class, &AssetData); } return FText::FromString(AssetClass); } 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); } } } 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 HighlightedText; EThumbnailLabel::Type Label; TAttribute HintColorAndOpacity; bool bAllowHintText; /** 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) .ClassThumbnailBrushOverride(InConfig.ClassThumbnailBrushOverride) .AllowAssetSpecificThumbnailOverlay(InConfig.bAllowAssetSpecificThumbnailOverlay) .AssetTypeColorOverride(InConfig.AssetTypeColorOverride); } void FAssetThumbnail::RefreshThumbnail() { if (ThumbnailPool.IsValid() && AssetData.IsValid()) { ThumbnailPool.Pin()->RefreshThumbnail(SharedThis(this)); } } FAssetThumbnailPool::FAssetThumbnailPool(uint32 InNumInPool, const TAttribute& InAreRealTimeThumbnailsAllowed, double InMaxFrameTimeAllowance, uint32 InMaxRealTimeThumbnailsPerFrame) : AreRealTimeThumbnailsAllowed(InAreRealTimeThumbnailsAllowed), 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); } } 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> 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; } 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; } // 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 are 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 = AreRealTimeThumbnailsAllowed.Get() && 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()) { TSharedRef InfoRef = Info.ToSharedRef(); if (InfoRef->AssetData.IsValid()) { const FObjectThumbnail* ObjectThumbnail = NULL; bool bLoadedThumbnail = false; // If this is a loaded asset and we have a rendering info for it, render a fresh thumbnail here if (InfoRef->AssetData.IsAssetLoaded()) { UObject* Asset = InfoRef->AssetData.GetAsset(); FThumbnailRenderingInfo* RenderInfo = GUnrealEd->GetThumbnailManager()->GetRenderingInfo(Asset); if (RenderInfo != NULL && RenderInfo->Renderer != NULL) { FThumbnailInfo_RenderThread ThumbInfo = InfoRef.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 (InfoRef->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, InfoRef->Width, InfoRef->Height, ThumbnailTools::EThumbnailTextureFlushMode::NeverFlush, InfoRef->ThumbnailRenderTarget); } bLoadedThumbnail = true; // Since this was rendered, add it to the list of thumbnails that can be rendered in real-time RealTimeThumbnails.AddUnique(InfoRef); } } FThumbnailMap ThumbnailMap; // If we could not render a fresh thumbnail, see if we already have a cached one to load if (!bLoadedThumbnail) { // Unloaded asset const FObjectThumbnail* FoundThumbnail = ThumbnailTools::FindCachedThumbnail(InfoRef->AssetData.GetFullName()); if (FoundThumbnail) { ObjectThumbnail = FoundThumbnail; } else { // If we don't have a cached thumbnail, try to find it on disk FString PackageFilename; if (FPackageName::DoesPackageExist(InfoRef->AssetData.PackageName.ToString(), NULL, &PackageFilename)) { TSet ObjectFullNames; FName ObjectFullName = FName(*InfoRef->AssetData.GetFullName()); ObjectFullNames.Add(ObjectFullName); ThumbnailTools::LoadThumbnailsFromPackage(PackageFilename, ObjectFullNames, ThumbnailMap); const FObjectThumbnail* ThumbnailPtr = ThumbnailMap.Find(ObjectFullName); if (ThumbnailPtr) { ObjectThumbnail = ThumbnailPtr; } } } } if (ObjectThumbnail) { if (ObjectThumbnail->GetImageWidth() > 0 && ObjectThumbnail->GetImageHeight() > 0 && ObjectThumbnail->GetUncompressedImageData().Num() > 0) { // Make bulk data for updating the texture memory later FSlateTextureData* BulkData = new FSlateTextureData(ObjectThumbnail->GetImageWidth(), ObjectThumbnail->GetImageHeight(), GPixelFormats[PF_B8G8R8A8].BlockBytes, ObjectThumbnail->AccessImageData()); // Update the texture RHI FThumbnailInfo_RenderThread ThumbInfo = InfoRef.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(); }); bLoadedThumbnail = true; } else { bLoadedThumbnail = false; } } if (bLoadedThumbnail) { // Mark it as updated InfoRef->LastUpdateTime = FPlatformTime::Seconds(); // Notify listeners that a thumbnail has been rendered ThumbnailRenderedEvent.Broadcast(InfoRef->AssetData); } else { // Notify listeners that a thumbnail has been rendered ThumbnailRenderFailedEvent.Broadcast(InfoRef->AssetData); } } } } } } 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>::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>& 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> 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::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 != NULL) { // 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 && ObjectBeingModified->IsAsset()) { // 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); // Don't try loading thumbnails for package that have never been saved if (Thumbnail == NULL && !IsGarbageCollecting() && !ObjectBeingModified->GetOutermost()->HasAnyPackageFlags(PKG_NewlyCreated)) { // If we don't yet have a thumbnail map, load one from disk if possible // Don't attempt to do this while garbage collecting since loading or finding objects during GC is illegal FName ObjectFullName = FName(*ObjectBeingModified->GetFullName()); TArray ObjectFullNames; FThumbnailMap LoadedThumbnails; ObjectFullNames.Add(ObjectFullName); if (ThumbnailTools::ConditionallyLoadThumbnailsForObjects(ObjectFullNames, LoadedThumbnails)) { Thumbnail = LoadedThumbnails.Find(ObjectFullName); if (Thumbnail != NULL) { Thumbnail = ThumbnailTools::CacheThumbnail(ObjectBeingModified->GetFullName(), Thumbnail, ObjectBeingModified->GetOutermost()); } } } if (Thumbnail != NULL) { // Mark the thumbnail as dirty Thumbnail->MarkAsDirty(); } RefreshThumbnailsFor(FName(*ObjectBeingModified->GetPathName())); } }