Custom thumbnail not display - Asset is never loaded

To follow up on this, I have found a solution:
Turns out Unreal uses a caching system for its thumbnails so that it does not need to load an asset in the Content Browser just to be able to display its thumbnail. Instead, every time that asset is being loaded, its generated thumbnail will be saved to disk. The next time that asset should be previewed in the editor, instead of loading that object, it will load the cached thumbnail from disk. Seems to me like an efficient system.

That was just the first half of the problem though. I still wasn’t able to display a custom thumbnail for my UObject-derived type. I had to do some trickery for that. When one derives a class from UObject or another native class naively inherited from UObject, the editor will treat this new class as a Blueprint Class asset type, meaning, that it will open the Blueprint editor, can contain variables, graphs etc. like an Actor class. That also meant though, that my custom UThumbnailRenderer needed to take UBlueprint as a target type and not UBuff since the asset itself is not of my custom type. There is a UBlueprintThumbnailRenderer already in the engine source so what I did was to derive my custom renderer from that type and in the StartupModule function of my editor plugin I removed the default UBlueprintThumbnailRenderer und replaced it with my own:

[SPOILER]



UThumbnailManager::Get().UnregisterCustomRenderer(UBlueprint::StaticClass());
UThumbnailManager::Get().RegisterCustomRenderer(UBlueprint::StaticClass(), UCustomBlueprintThumbnailRenderer::StaticClass());


[/SPOILER]

The actual custom renderer looks like this:

Header:
[SPOILER]



UCLASS()
class UCustomBlueprintThumbnailRenderer : public UBlueprintThumbnailRenderer
{ GENERATED_BODY()
  protected:
  
UCustomBlueprintThumbnailRenderer(const FObjectInitializer& ObjectInitializer)
  [INDENT=2]: Super(ObjectInitializer)[/INDENT]
  {}

  // UThumbnailRenderer implementation
  virtual void GetThumbnailSize(UObject* Object, float Zoom, uint32& OutWidth, uint32& OutHeight) const override;
  virtual void Draw(UObject* Object, int32 X, int32 Y, uint32 Width, uint32 Height, FRenderTarget*, FCanvas* Canvas, bool bAdditionalViewFamily) override;
  virtual bool CanVisualizeAsset(UObject* Object) override;
 
  protected: 
UTexture2D* GetTextureFromGeneratedClass(UClass* Class) const;
 

};


[/SPOILER]

Implementation:
[SPOILER]



void UCustomBlueprintThumbnailRenderer::GetThumbnailSize(UObject* Object, float Zoom, uint32& OutWidth, uint32& OutHeight) const
{ UBlueprint* Blueprint = Cast<UBlueprint>(Object);

if (Blueprint)
  {
  [INDENT=2]if (UTexture2D* Texture = GetTextureFromGeneratedClass(Blueprint->GeneratedClass))[/INDENT]
  [INDENT=2]{[/INDENT]
  [INDENT=3]OutWidth = FMath::TruncToInt(Zoom * (float)Texture->GetSurfaceWidth());[/INDENT]
  [INDENT=3]OutHeight = FMath::TruncToInt(Zoom * (float)Texture->GetSurfaceHeight());[/INDENT]
  [INDENT=2]}[/INDENT]
  }
  Super::GetThumbnailSize(Object, Zoom, OutWidth, OutHeight);
 
 }

void UCustomBlueprintThumbnailRenderer::Draw(UObject* Object, int32 X, int32 Y, uint32 Width, uint32 Height, FRenderTarget* RenderTarget, FCanvas* Canvas, bool bAdditionalViewFamily)
{ UBlueprint* Blueprint = Cast<UBlueprint>(Object);
  if (Blueprint)
  {
  [INDENT=2]if (UTexture2D* Texture2D = GetTextureFromGeneratedClass(Blueprint->GeneratedClass))[/INDENT]
  [INDENT=2]{[/INDENT]
  [INDENT=3]const bool bUseTranslucentBlend = Texture2D && Texture2D->HasAlphaChannel() && ((Texture2D->LODGroup == TEXTUREGROUP_UI) || (Texture2D->LODGroup == TEXTUREGROUP_Pixels2D));[/INDENT]
  [INDENT=3]TRefCountPtr<FBatchedElementParameters> BatchedElementParameters;[/INDENT]
  [INDENT=3]if (bUseTranslucentBlend)[/INDENT]
  [INDENT=3]{[/INDENT]
  [INDENT=4]// If using alpha, draw a checkerboard underneath first.[/INDENT]
  [INDENT=4]const int32 CheckerDensity = 8;[/INDENT]
  [INDENT=4]auto* Checker = UThumbnailManager::Get().CheckerboardTexture;[/INDENT]
  [INDENT=4]Canvas->DrawTile([/INDENT]
  [INDENT=4]0.0f, 0.0f, Width, Height, // Dimensions[/INDENT]
  [INDENT=4]0.0f, 0.0f, CheckerDensity, CheckerDensity, // UVs[/INDENT]
  [INDENT=4]FLinearColor::White, Checker->Resource); // Tint & Texture[/INDENT]
  [INDENT=3]}[/INDENT]
  [INDENT=3]// Use A canvas tile item to draw[/INDENT]
  [INDENT=3]FCanvasTileItem CanvasTile(FVector2D(X, Y), Texture2D->Resource, FVector2D(Width, Height), FLinearColor::White);[/INDENT]
  [INDENT=3]CanvasTile.BlendMode = bUseTranslucentBlend ? SE_BLEND_Translucent : SE_BLEND_Opaque;[/INDENT]
  [INDENT=3]CanvasTile.BatchedElementParameters = BatchedElementParameters;[/INDENT]
  [INDENT=3]CanvasTile.Draw(Canvas);[/INDENT]
  [INDENT=3]if (Texture2D && Texture2D->IsCurrentlyVirtualTextured())[/INDENT]
  [INDENT=3]{[/INDENT]
  [INDENT=4]auto VTChars = TEXT("VT");[/INDENT]
  [INDENT=4]int32 VTWidth = 0;[/INDENT]
  [INDENT=4]int32 VTHeight = 0;[/INDENT]
  [INDENT=4]StringSize(GEngine->GetLargeFont(), VTWidth, VTHeight, VTChars);[/INDENT]
  [INDENT=4]float PaddingX = Width / 128.0f;[/INDENT]
  [INDENT=4]float PaddingY = Height / 128.0f;[/INDENT]
  [INDENT=4]float ScaleX = Width / 64.0f; //Text is 1/64'th of the size of the thumbnails[/INDENT]
  [INDENT=4]float ScaleY = Height / 64.0f;[/INDENT]
  [INDENT=4]// VT overlay[/INDENT]
  [INDENT=4]FCanvasTextItem TextItem(FVector2D(Width - PaddingX - VTWidth * ScaleX, Height - PaddingY - VTHeight * ScaleY), FText::FromString(VTChars), GEngine->GetLargeFont(), FLinearColor::White);[/INDENT]
  [INDENT=4]TextItem.EnableShadow(FLinearColor::Black);[/INDENT]
  [INDENT=4]TextItem.Scale = FVector2D(ScaleX, ScaleY);[/INDENT]
  [INDENT=4]TextItem.Draw(Canvas);[/INDENT]
  [INDENT=3]}
return;[/INDENT]
  [INDENT=2]}[/INDENT]
  [INDENT=2] [/INDENT]
  }
  Super::Draw(Object, X, Y, Width, Height, RenderTarget, Canvas, bAdditionalViewFamily);
 
 }

bool UCustomBlueprintThumbnailRenderer::CanVisualizeAsset(UObject* Object)
{ UBlueprint* Blueprint = Cast<UBlueprint>(Object);

if (Blueprint && GetTextureFromGeneratedClass(Blueprint->GeneratedClass) != nullptr)
{
  [INDENT=2]return true;[/INDENT]
  }
  return Super::CanVisualizeAsset(Object);
 
 }

UTexture2D* UCustomBlueprintThumbnailRenderer::GetTextureFromGeneratedClass(UClass* Class) const
{ if (Class)
  {
  [INDENT=2]if (Class->IsChildOf(USkillBase::StaticClass()))[/INDENT]
  [INDENT=2]{[/INDENT]
  [INDENT=3]if (USkillBase* CDO = Class->GetDefaultObject<USkillBase>())[/INDENT]
  [INDENT=3]{[/INDENT]
  [INDENT=4]return CDO->GetIcon();[/INDENT]
  [INDENT=3]}[/INDENT]
  [INDENT=2]}[/INDENT]
  [INDENT=2]else if (Class->IsChildOf(UBuff::StaticClass()))[/INDENT]
  [INDENT=2]{[/INDENT]
  [INDENT=3]if (UBuff* CDO = Class->GetDefaultObject<UBuff>())[/INDENT]
  [INDENT=3]{[/INDENT]
  [INDENT=4]return CDO->GetIcon();[/INDENT]
  [INDENT=3]}[/INDENT]
  [INDENT=2]}[/INDENT]
  }
  return nullptr;
 
 }


[/SPOILER]

The implementation is an amalgamation of the UTextureThumbnailRenderer and the UBlueprintThumbnailRenderer. It works perfectly fine in the editor.

3 Likes