// Copyright Epic Games, Inc. All Rights Reserved. #include "SlateMaterialResource.h" #include "SlateRHIRendererSettings.h" #include "Engine/Texture.h" #include "Materials/MaterialInstanceDynamic.h" #include "Materials/MaterialRenderProxy.h" #include "MaterialShared.h" #include "Styling/SlateBrush.h" namespace SlateMaterialResource { #if SLATE_CHECK_UOBJECT_RENDER_RESOURCES void CheckInvalidUMaterial(const UMaterialInterface& InMaterialResource, const FName& InDebugName) { if (GSlateCheckUObjectRenderResources) { bool bIsValidLowLevel = InMaterialResource.IsValidLowLevelFast(false); if (!bIsValidLowLevel || !IsValid(&InMaterialResource) || InMaterialResource.GetClass() == UMaterialInterface::StaticClass()) { UE_LOG(LogSlate, Error, TEXT("Material '%s' is not valid. PendingKill:'%d'. ValidLowLevelFast:'%d'. InvalidClass:'%d'") , *InDebugName.ToString() , (bIsValidLowLevel ? !IsValid(&InMaterialResource) : false) , bIsValidLowLevel , (bIsValidLowLevel ? InMaterialResource.GetClass() == UMaterialInterface::StaticClass() : false)); const TCHAR* Message = TEXT("We detected an invalid resource in FSlateMaterialResource. Check the log for more detail."); if (GSlateCheckUObjectRenderResourcesShouldLogFatal) { UE_LOG(LogSlate, Fatal, TEXT("%s"), Message); } else { ensureAlwaysMsgf(false, TEXT("%s"), Message); } } } } void CheckInvalidMaterialProxy(const FMaterialRenderProxy* MaterialProxy, const FName& InDebugName) { if (GSlateCheckUObjectRenderResources) { if (MaterialProxy == nullptr || MaterialProxy->IsDeleted() || MaterialProxy->IsMarkedForGarbageCollection()) { UE_LOG(LogSlate, Error, TEXT("Material '%s' Render Proxy is: nullptr:'%d'. Deleted:'%d'. Marked for GC:'%d'") , *InDebugName.ToString() , (MaterialProxy == nullptr) , (MaterialProxy ? MaterialProxy->IsDeleted() : false) , (MaterialProxy ? MaterialProxy->IsMarkedForGarbageCollection() : false)); const TCHAR* Message = TEXT("We detected an invalid resource render proxy in FSlateMaterialResource. Check the log for more detail."); if (GSlateCheckUObjectRenderResourcesShouldLogFatal) { UE_LOG(LogSlate, Fatal, TEXT("%s"), Message); } else { ensureAlwaysMsgf(false, TEXT("%s"), Message); } } } } #endif } FSlateMaterialResource::FSlateMaterialResource(const UMaterialInterface& InMaterialResource, const FVector2f InImageSize, FSlateShaderResource* InTextureMask ) : MaterialObject( &InMaterialResource) , SlateProxy( new FSlateShaderResourceProxy ) , TextureMaskResource( InTextureMask ) , Width(FMath::RoundToInt(InImageSize.X)) , Height(FMath::RoundToInt(InImageSize.Y)) , CachedSlatePostBuffers(ESlatePostRT::None) , CachedUsesVirtualTextures(false) { #if SLATE_CHECK_UOBJECT_RENDER_RESOURCES SlateMaterialResource::CheckInvalidUMaterial(InMaterialResource, NAME_None); MaterialProxy = InMaterialResource.GetRenderProxy(); MaterialObjectWeakPtr = MaterialObject; UpdateMaterialName(); SlateMaterialResource::CheckInvalidMaterialProxy(MaterialProxy, DebugName); #else MaterialProxy = InMaterialResource.GetRenderProxy(); #endif SlateProxy->ActualSize = InImageSize.IntPoint(); SlateProxy->Resource = this; if (MaterialObject && (IsInRenderingThread() || IsInGameThread())) { // Quality / Feature level irrelevant since flag to search all levels for both is true TArray OutUsedTextures; MaterialObject->GetUsedTextures(OutUsedTextures, EMaterialQualityLevel::Num, true, ERHIFeatureLevel::Num, true); CachedSlatePostBuffers = ESlatePostRT::None; if (const USlateRHIRendererSettings* RendererSettings = USlateRHIRendererSettings::Get()) { for (const UTexture* OutUsedTexture : OutUsedTextures) { for (const TPair& SlatePostSetting : RendererSettings->GetSlatePostSettings()) { const ESlatePostRT SlatePostBitflag = SlatePostSetting.Key; const FSlatePostSettings& SlatePostSettingValue = SlatePostSetting.Value; if (SlatePostSettingValue.bEnabled && OutUsedTexture && OutUsedTexture->GetPathName() == SlatePostSettingValue.GetPathToSlatePostRT()) { CachedSlatePostBuffers |= SlatePostBitflag; } } CachedUsesVirtualTextures |= (OutUsedTexture && OutUsedTexture->IsCurrentlyVirtualTextured()); } } } if (MaterialProxy && (MaterialProxy->IsDeleted() || MaterialProxy->IsMarkedForGarbageCollection())) { MaterialProxy = nullptr; } } FSlateMaterialResource::~FSlateMaterialResource() { if (SlateProxy) { delete SlateProxy; } } ESlatePostRT FSlateMaterialResource::GetUsedSlatePostBuffers() const { return CachedSlatePostBuffers; } bool FSlateMaterialResource::RequiresVirtualTextureFeedback() const { return CachedUsesVirtualTextures; } bool FSlateMaterialResource::IsResourceValid() const { if (MaterialProxy && MaterialObject) { // Conservatively only return invalid in scenarios where proxy is mismatched return MaterialProxy == MaterialObject->GetRenderProxy(); } return true; } void FSlateMaterialResource::UpdateMaterial(const UMaterialInterface& InMaterialResource, const FVector2f InImageSize, FSlateShaderResource* InTextureMask) { MaterialObject = ensure(IsValid(&InMaterialResource) && InMaterialResource.IsValidLowLevelFast(false)) ? &InMaterialResource : nullptr; MaterialProxy = MaterialObject ? MaterialObject->GetRenderProxy() : nullptr; #if SLATE_CHECK_UOBJECT_RENDER_RESOURCES SlateMaterialResource::CheckInvalidUMaterial(InMaterialResource, DebugName); MaterialObjectWeakPtr = MaterialObject; UpdateMaterialName(); SlateMaterialResource::CheckInvalidMaterialProxy(MaterialProxy, DebugName); #endif if (MaterialObject && (IsInRenderingThread() || IsInGameThread())) { // Quality / Feature level irrelevant since flag to search all levels for both is true TArray OutUsedTextures; MaterialObject->GetUsedTextures(OutUsedTextures, EMaterialQualityLevel::Num, true, ERHIFeatureLevel::Num, true); CachedSlatePostBuffers = ESlatePostRT::None; if (const USlateRHIRendererSettings* RendererSettings = USlateRHIRendererSettings::Get()) { for (const UTexture* OutUsedTexture : OutUsedTextures) { for (const TPair& SlatePostSetting : RendererSettings->GetSlatePostSettings()) { const ESlatePostRT SlatePostBitflag = SlatePostSetting.Key; const FSlatePostSettings& SlatePostSettingValue = SlatePostSetting.Value; if (SlatePostSettingValue.bEnabled && OutUsedTexture && OutUsedTexture->GetPathName() == SlatePostSettingValue.GetPathToSlatePostRT()) { CachedSlatePostBuffers |= SlatePostBitflag; } } } } } if (MaterialProxy && (MaterialProxy->IsDeleted() || MaterialProxy->IsMarkedForGarbageCollection())) { MaterialProxy = nullptr; } if (!SlateProxy) { SlateProxy = new FSlateShaderResourceProxy; } TextureMaskResource = InTextureMask; SlateProxy->ActualSize = InImageSize.IntPoint(); SlateProxy->Resource = this; Width = FMath::RoundToInt(InImageSize.X); Height = FMath::RoundToInt(InImageSize.Y); } void FSlateMaterialResource::ResetMaterial() { MaterialObject = nullptr; MaterialProxy = nullptr; #if SLATE_CHECK_UOBJECT_RENDER_RESOURCES MaterialObjectWeakPtr = nullptr; UpdateMaterialName(); #endif CachedSlatePostBuffers = ESlatePostRT::None; TextureMaskResource = nullptr; if (SlateProxy) { delete SlateProxy; } SlateProxy = nullptr; Width = 0; Height = 0; } #if SLATE_CHECK_UOBJECT_RENDER_RESOURCES void FSlateMaterialResource::UpdateMaterialName() { const UMaterialInstanceDynamic* MID = Cast(MaterialObject); if(MID && MID->Parent) { // MID's don't have nice names. Get the name of the parent instead for tracking DebugName = MID->Parent->GetFName(); } else if(MaterialObject) { DebugName = MaterialObject->GetFName(); } else { DebugName = NAME_None; } } void FSlateMaterialResource::CheckForStaleResources() const { if (DebugName != NAME_None) { // pending kill objects may still be rendered for a frame so it is valid for the check to pass const bool bEvenIfPendingKill = true; // This test needs to be thread safe. It doesn't give us as many chances to trap bugs here but it is still useful const bool bThreadSafe = true; checkf(MaterialObjectWeakPtr.IsValid(bEvenIfPendingKill, bThreadSafe), TEXT("Material %s has become invalid. This means the resource was garbage collected while slate was using it"), *DebugName.ToString()); } } #endif