Download

Custom thumbnail not display - Asset is never loaded

Hi everyone,
I am currently trying to get a UThumbnailRenderer running for a custom type of mine that derives from UObject. I created the renderer class in my editor module and derived it from UTextureThumbnailRenderer since the custom thumbnail is supposed to be an icon that is saved within the custom type as a UTexture2D reference. In my renderer class I cast the Object parameter to my custom type UBuff, extract the icon out of it and pass it to the Draw function of the UTextureThumbnailRenderer.
According to the few tutorials I found regarding this topic, this should work fine but I have found, that my assets still use the default thumbnail texture reserved for all UObject derived types:
screen1.png

The buff to the left uses the same icon internally and should show the same texture as the ExampleSprite.

I debugged the code responsible for the asset and on line 1011 of AssetThumbnail.cpp (FAssetThumbnailPool::Tick) the code checks whether the asset is loaded already and if so, it tries to display a custom thumbnail for it. For my asset type (UBuff) this InfoRef->AssetData.IsAssetLoaded() check is never true and therefore my custom renderer is never even considered. I compared the setup to the one used for the Paper2DSprite and its custom thumbnail renderer but I have not found any differences. Still, the sprite renderer works fine.

Do I need to set a tag for my custom type to be loaded when inspected within the Content Browser? Does UnrealEd maybe consider the BlueprintClass default thumbnail to be of higher priority than my custom thumbnail?

Thanks in advance to anyone trying to help!
Schadek

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.