void FSlateRHIResourceManager::CreateTextures( const TArray< const FSlateBrush* >& Resources ) { DECLARE_SCOPE_CYCLE_COUNTER(TEXT("Loading Slate Textures"), STAT_Slate, STATGROUP_LoadTime); TMap<FName,FNewTextureInfo> TextureInfoMap; const uint32 Stride = GPixelFormats[PF_R8G8B8A8].BlockBytes; for( int32 ResourceIndex = 0; ResourceIndex < Resources.Num(); ++ResourceIndex ) { const FSlateBrush& Brush = *Resources[ResourceIndex]; const FName TextureName = Brush.GetResourceName(); if( TextureName != NAME_None && !Brush.HasUObject() && !Brush.IsDynamicallyLoaded() && !ResourceMap.Contains(TextureName) ) { // Find the texture or add it if it doesnt exist (only load the texture once) FNewTextureInfo& Info = TextureInfoMap.FindOrAdd( TextureName ); Info.bSrgb = (Brush.ImageType != ESlateBrushImageType::Linear); // Only atlas the texture if none of the brushes that use it tile it and the image is srgb Info.bShouldAtlas &= ( Brush.Tiling == ESlateBrushTileType::NoTile && Info.bSrgb && AtlasSize > 0 ); // Texture has been loaded if the texture data is valid if( !Info.TextureData.IsValid() ) { uint32 Width = 0; uint32 Height = 0; TArray<uint8> RawData; bool bSucceeded = LoadTexture( Brush, Width, Height, RawData ); Info.TextureData = MakeShareable( new FSlateTextureData( Width, Height, Stride, RawData ) ); const bool bTooLargeForAtlas = (Width >= 256 || Height >= 256 || Width >= AtlasSize || Height >= AtlasSize ); Info.bShouldAtlas &= !bTooLargeForAtlas; if( !bSucceeded || !ensureMsgf( Info.TextureData->GetRawBytes().Num() > 0, TEXT("Slate resource: (%s) contains no data"), *TextureName.ToString() ) ) { TextureInfoMap.Remove( TextureName ); } } } } // Sort textures by size. The largest textures are atlased first which creates a more compact atlas TextureInfoMap.ValueSort( FCompareFNewTextureInfoByTextureSize() ); for( TMap<FName,FNewTextureInfo>::TConstIterator It(TextureInfoMap); It; ++It ) { const FNewTextureInfo& Info = It.Value(); FName TextureName = It.Key(); FString NameStr = TextureName.ToString(); checkSlow( TextureName != NAME_None ); FSlateShaderResourceProxy* NewTexture = GenerateTextureResource( Info ); ResourceMap.Add( TextureName, NewTexture ); } }
void FSlateD3DTextureManager::CreateTextures( const TArray< const FSlateBrush* >& Resources ) { TMap<FName,FNewTextureInfo> TextureInfoMap; for( int32 ResourceIndex = 0; ResourceIndex < Resources.Num(); ++ResourceIndex ) { const FSlateBrush& Brush = *Resources[ResourceIndex]; const FName TextureName = Brush.GetResourceName(); if( TextureName != NAME_None && !ResourceMap.Contains(TextureName) ) { // Find the texture or add it if it doesn't exist (only load the texture once) FNewTextureInfo& Info = TextureInfoMap.FindOrAdd( TextureName ); Info.bSrgb = (Brush.ImageType != ESlateBrushImageType::Linear); // Only atlas the texture if none of the brushes that use it tile it Info.bShouldAtlas &= (Brush.Tiling == ESlateBrushTileType::NoTile && Info.bSrgb ); if( !Info.TextureData.IsValid()) { uint32 Width = 0; uint32 Height = 0; TArray<uint8> RawData; bool bSucceeded = LoadTexture( Brush, Width, Height, RawData ); const uint32 Stride = 4; // RGBA Info.TextureData = MakeShareable( new FSlateTextureData( Width, Height, Stride, RawData ) ); const bool bTooLargeForAtlas = (Width >= 256 || Height >= 256); Info.bShouldAtlas &= !bTooLargeForAtlas; if( !bSucceeded ) { TextureInfoMap.Remove( TextureName ); } } } } TextureInfoMap.ValueSort( FCompareFNewTextureInfoByTextureSize() ); for( TMap<FName,FNewTextureInfo>::TConstIterator It(TextureInfoMap); It; ++It ) { const FNewTextureInfo& Info = It.Value(); FName TextureName = It.Key(); FString NameStr = TextureName.ToString(); FSlateShaderResourceProxy* NewTexture = GenerateTextureResource( Info ); ResourceMap.Add( TextureName, NewTexture ); } }
void FPhysxSharedData::DumpSharedMemoryUsage(FOutputDevice* Ar) { struct FSharedResourceEntry { uint64 MemorySize; uint64 Count; }; struct FSortBySize { FORCEINLINE bool operator()( const FSharedResourceEntry& A, const FSharedResourceEntry& B ) const { // Sort descending return B.MemorySize < A.MemorySize; } }; TMap<FString, FSharedResourceEntry> AllocationsByType; uint64 OverallSize = 0; int32 OverallCount = 0; TMap<FString, TArray<PxBase*> > ObjectsByType; for (int32 i=0; i < (int32)SharedObjects->getNbObjects(); ++i) { PxBase& Obj = SharedObjects->getObject(i); FString TypeName = ANSI_TO_TCHAR(Obj.getConcreteTypeName()); TArray<PxBase*>* ObjectsArray = ObjectsByType.Find(TypeName); if (ObjectsArray == NULL) { ObjectsByType.Add(TypeName, TArray<PxBase*>()); ObjectsArray = ObjectsByType.Find(TypeName); } check(ObjectsArray); ObjectsArray->Add(&Obj); } TArray<FString> TypeNames; ObjectsByType.GetKeys(TypeNames); for (int32 TypeIdx=0; TypeIdx < TypeNames.Num(); ++TypeIdx) { const FString& TypeName = TypeNames[TypeIdx]; TArray<PxBase*>* ObjectsArray = ObjectsByType.Find(TypeName); check(ObjectsArray); PxSerializationRegistry* Sr = PxSerialization::createSerializationRegistry(*GPhysXSDK); PxCollection* Collection = PxCreateCollection(); for (int32 i=0; i < ObjectsArray->Num(); ++i) { Collection->add(*((*ObjectsArray)[i]));; } PxSerialization::complete(*Collection, *Sr); // chase all other stuff (shared shaps, materials, etc) needed to serialize this collection FPhysXCountMemoryStream Out; PxSerialization::serializeCollectionToBinary(Out, *Collection, *Sr); Collection->release(); Sr->release(); OverallSize += Out.UsedMemory; OverallCount += ObjectsArray->Num(); FSharedResourceEntry NewEntry; NewEntry.Count = ObjectsArray->Num(); NewEntry.MemorySize = Out.UsedMemory; AllocationsByType.Add(TypeName, NewEntry); } Ar->Logf(TEXT("")); Ar->Logf(TEXT("Shared Resources:")); Ar->Logf(TEXT("")); AllocationsByType.ValueSort(FSortBySize()); Ar->Logf(TEXT("%-10d %s (%d)"), OverallSize, TEXT("Overall"), OverallCount ); for( auto It=AllocationsByType.CreateConstIterator(); It; ++It ) { Ar->Logf(TEXT("%-10d %s (%d)"), It.Value().MemorySize, *It.Key(), It.Value().Count ); } }
void ULightComponent::ReassignStationaryLightChannels(UWorld* TargetWorld, bool bAssignForLightingBuild) { TMap<FLightAndChannel*, TArray<FLightAndChannel*> > LightToOverlapMap; // Build an array of all static shadowing lights that need to be assigned for(TObjectIterator<ULightComponent> LightIt(RF_ClassDefaultObject|RF_PendingKill); LightIt; ++LightIt) { ULightComponent* const LightComponent = *LightIt; const bool bLightIsInWorld = LightComponent->GetOwner() && TargetWorld->ContainsActor(LightComponent->GetOwner()) && !LightComponent->GetOwner()->IsPendingKill(); if (bLightIsInWorld // Only operate on stationary light components (static shadowing only) && LightComponent->HasStaticShadowing() && !LightComponent->HasStaticLighting()) { if (LightComponent->bAffectsWorld && LightComponent->CastShadows && LightComponent->CastStaticShadows) { LightToOverlapMap.Add(new FLightAndChannel(LightComponent), TArray<FLightAndChannel*>()); } else { // Reset the preview channel of stationary light components that shouldn't get a channel // This is necessary to handle a light being newly disabled LightComponent->PreviewShadowMapChannel = INDEX_NONE; #if WITH_EDITOR LightComponent->UpdateLightSpriteTexture(); #endif } } } // Build an array of overlapping lights for (TMap<FLightAndChannel*, TArray<FLightAndChannel*> >::TIterator It(LightToOverlapMap); It; ++It) { ULightComponent* CurrentLight = It.Key()->Light; TArray<FLightAndChannel*>& OverlappingLights = It.Value(); if (bAssignForLightingBuild) { // This should have happened during lighting invalidation at the beginning of the build anyway CurrentLight->ShadowMapChannel = INDEX_NONE; } for (TMap<FLightAndChannel*, TArray<FLightAndChannel*> >::TIterator OtherIt(LightToOverlapMap); OtherIt; ++OtherIt) { ULightComponent* OtherLight = OtherIt.Key()->Light; if (CurrentLight != OtherLight // Testing both directions because the spotlight <-> spotlight test is just cone vs bounding sphere //@todo - more accurate spotlight <-> spotlight intersection && CurrentLight->AffectsBounds(FBoxSphereBounds(OtherLight->GetBoundingSphere())) && OtherLight->AffectsBounds(FBoxSphereBounds(CurrentLight->GetBoundingSphere()))) { OverlappingLights.Add(OtherIt.Key()); } } } // Sort lights with the most overlapping lights first LightToOverlapMap.ValueSort(FCompareLightsByArrayCount()); TMap<FLightAndChannel*, TArray<FLightAndChannel*> > SortedLightToOverlapMap; // Add directional lights to the beginning so they always get channels for (TMap<FLightAndChannel*, TArray<FLightAndChannel*> >::TIterator It(LightToOverlapMap); It; ++It) { FLightAndChannel* CurrentLight = It.Key(); if (CurrentLight->Light->GetLightType() == LightType_Directional) { SortedLightToOverlapMap.Add(It.Key(), It.Value()); } } // Add everything else, which has been sorted by descending overlaps for (TMap<FLightAndChannel*, TArray<FLightAndChannel*> >::TIterator It(LightToOverlapMap); It; ++It) { FLightAndChannel* CurrentLight = It.Key(); if (CurrentLight->Light->GetLightType() != LightType_Directional) { SortedLightToOverlapMap.Add(It.Key(), It.Value()); } } // Go through lights and assign shadowmap channels //@todo - retry with different ordering heuristics when it fails for (TMap<FLightAndChannel*, TArray<FLightAndChannel*> >::TIterator It(SortedLightToOverlapMap); It; ++It) { bool bChannelUsed[4] = {0}; FLightAndChannel* CurrentLight = It.Key(); const TArray<FLightAndChannel*>& OverlappingLights = It.Value(); // Mark which channels have already been assigned to overlapping lights for (int32 OverlappingIndex = 0; OverlappingIndex < OverlappingLights.Num(); OverlappingIndex++) { FLightAndChannel* OverlappingLight = OverlappingLights[OverlappingIndex]; if (OverlappingLight->Channel != INDEX_NONE) { bChannelUsed[OverlappingLight->Channel] = true; } } // Use the lowest free channel for (int32 ChannelIndex = 0; ChannelIndex < ARRAY_COUNT(bChannelUsed); ChannelIndex++) { if (!bChannelUsed[ChannelIndex]) { CurrentLight->Channel = ChannelIndex; break; } } } // Go through the assigned lights and update their render state and icon for (TMap<FLightAndChannel*, TArray<FLightAndChannel*> >::TIterator It(SortedLightToOverlapMap); It; ++It) { FLightAndChannel* CurrentLight = It.Key(); if (CurrentLight->Light->PreviewShadowMapChannel != CurrentLight->Channel) { CurrentLight->Light->PreviewShadowMapChannel = CurrentLight->Channel; CurrentLight->Light->MarkRenderStateDirty(); } #if WITH_EDITOR CurrentLight->Light->UpdateLightSpriteTexture(); #endif if (bAssignForLightingBuild) { CurrentLight->Light->ShadowMapChannel = CurrentLight->Channel; if (CurrentLight->Light->ShadowMapChannel == INDEX_NONE) { FMessageLog("LightingResults").Error() ->AddToken(FUObjectToken::Create(CurrentLight->Light->GetOwner())) ->AddToken(FTextToken::Create( NSLOCTEXT("Lightmass", "LightmassError_FailedToAllocateShadowmapChannel", "Severe performance loss: Failed to allocate shadowmap channel for stationary light due to overlap - light will fall back to dynamic shadows!") ) ); } } delete CurrentLight; } }
void FStatsMemoryDumpCommand::ProcessingUObjectAllocations( const TMap<uint64, FAllocationInfo>& AllocationMap ) { // This code is not optimized. FScopeLogTime SLT( TEXT( "ProcessingUObjectAllocations" ), nullptr, FScopeLogTime::ScopeLog_Seconds ); UE_LOG( LogStats, Warning, TEXT( "Processing UObject allocations" ) ); FDiagnosticTableViewer MemoryReport( *FDiagnosticTableViewer::GetUniqueTemporaryFilePath( TEXT( "MemoryReport-UObject" ) ) ); // Write a row of headings for the table's columns. MemoryReport.AddColumn( TEXT( "Size (bytes)" ) ); MemoryReport.AddColumn( TEXT( "Size (MB)" ) ); MemoryReport.AddColumn( TEXT( "Count" ) ); MemoryReport.AddColumn( TEXT( "UObject class" ) ); MemoryReport.CycleRow(); TMap<FName, FSizeAndCount> UObjectAllocations; // To minimize number of calls to expensive DecodeCallstack. TMap<FName,FName> UObjectCallstackToClassMapping; uint64 NumAllocations = 0; uint64 TotalAllocatedMemory = 0; for( const auto& It : AllocationMap ) { const FAllocationInfo& Alloc = It.Value; FName UObjectClass = UObjectCallstackToClassMapping.FindRef( Alloc.EncodedCallstack ); if( UObjectClass == NAME_None ) { TArray<FString> DecodedCallstack; DecodeCallstack( Alloc.EncodedCallstack, DecodedCallstack ); for( int32 Index = DecodedCallstack.Num() - 1; Index >= 0; --Index ) { NAME_INDEX NameIndex = 0; TTypeFromString<NAME_INDEX>::FromString( NameIndex, *DecodedCallstack[Index] ); const FName LongName = FName( NameIndex, NameIndex, 0 ); const bool bValid = UObjectNames.Contains( LongName ); if( bValid ) { const FString ObjectName = FStatNameAndInfo::GetShortNameFrom( LongName ).GetPlainNameString(); UObjectClass = *ObjectName.Left( ObjectName.Find( TEXT( "//" ) ) );; UObjectCallstackToClassMapping.Add( Alloc.EncodedCallstack, UObjectClass ); break; } } } if( UObjectClass != NAME_None ) { FSizeAndCount& SizeAndCount = UObjectAllocations.FindOrAdd( UObjectClass ); SizeAndCount.Size += Alloc.Size; SizeAndCount.Count += 1; TotalAllocatedMemory += Alloc.Size; NumAllocations++; } } // Dump memory to the log. UObjectAllocations.ValueSort( FSizeAndCountGreater() ); const float MaxPctDisplayed = 0.90f; int32 CurrentIndex = 0; uint64 DisplayedSoFar = 0; UE_LOG( LogStats, Warning, TEXT( "Index, Size (Size MB), Count, UObject class" ) ); for( const auto& It : UObjectAllocations ) { const FSizeAndCount& SizeAndCount = It.Value; const FName& UObjectClass = It.Key; UE_LOG( LogStats, Log, TEXT( "%2i, %llu (%.2f MB), %llu, %s" ), CurrentIndex, SizeAndCount.Size, SizeAndCount.Size / 1024.0f / 1024.0f, SizeAndCount.Count, *UObjectClass.GetPlainNameString() ); // Dump stats MemoryReport.AddColumn( TEXT( "%llu" ), SizeAndCount.Size ); MemoryReport.AddColumn( TEXT( "%.2f MB" ), SizeAndCount.Size / 1024.0f / 1024.0f ); MemoryReport.AddColumn( TEXT( "%llu" ), SizeAndCount.Count ); MemoryReport.AddColumn( *UObjectClass.GetPlainNameString() ); MemoryReport.CycleRow(); CurrentIndex++; DisplayedSoFar += SizeAndCount.Size; const float CurrentPct = (float)DisplayedSoFar / (float)TotalAllocatedMemory; if( CurrentPct > MaxPctDisplayed ) { break; } } UE_LOG( LogStats, Warning, TEXT( "Allocated memory: %llu bytes (%.2f MB)" ), TotalAllocatedMemory, TotalAllocatedMemory / 1024.0f / 1024.0f ); // Add a total row. MemoryReport.CycleRow(); MemoryReport.CycleRow(); MemoryReport.CycleRow(); MemoryReport.AddColumn( TEXT( "%llu" ), TotalAllocatedMemory ); MemoryReport.AddColumn( TEXT( "%.2f MB" ), TotalAllocatedMemory / 1024.0f / 1024.0f ); MemoryReport.AddColumn( TEXT( "%llu" ), NumAllocations ); MemoryReport.AddColumn( TEXT( "TOTAL" ) ); MemoryReport.CycleRow(); }
void FStatsMemoryDumpCommand::ProcessingScopedAllocations( const TMap<uint64, FAllocationInfo>& AllocationMap ) { // This code is not optimized. FScopeLogTime SLT( TEXT( "ProcessingScopedAllocations" ), nullptr, FScopeLogTime::ScopeLog_Seconds ); UE_LOG( LogStats, Warning, TEXT( "Processing scoped allocations" ) ); FDiagnosticTableViewer MemoryReport( *FDiagnosticTableViewer::GetUniqueTemporaryFilePath( TEXT( "MemoryReport-Scoped" ) ) ); // Write a row of headings for the table's columns. MemoryReport.AddColumn( TEXT( "Size (bytes)" ) ); MemoryReport.AddColumn( TEXT( "Size (MB)" ) ); MemoryReport.AddColumn( TEXT( "Count" ) ); MemoryReport.AddColumn( TEXT( "Callstack" ) ); MemoryReport.CycleRow(); TMap<FName, FSizeAndCount> ScopedAllocations; uint64 NumAllocations = 0; uint64 TotalAllocatedMemory = 0; for( const auto& It : AllocationMap ) { const FAllocationInfo& Alloc = It.Value; FSizeAndCount& SizeAndCount = ScopedAllocations.FindOrAdd( Alloc.EncodedCallstack ); SizeAndCount.Size += Alloc.Size; SizeAndCount.Count += 1; TotalAllocatedMemory += Alloc.Size; NumAllocations++; } // Dump memory to the log. ScopedAllocations.ValueSort( FSizeAndCountGreater() ); const float MaxPctDisplayed = 0.90f; int32 CurrentIndex = 0; uint64 DisplayedSoFar = 0; UE_LOG( LogStats, Warning, TEXT( "Index, Size (Size MB), Count, Stat desc" ) ); for( const auto& It : ScopedAllocations ) { const FSizeAndCount& SizeAndCount = It.Value; const FName& EncodedCallstack = It.Key; const FString AllocCallstack = GetCallstack( EncodedCallstack ); UE_LOG( LogStats, Log, TEXT( "%2i, %llu (%.2f MB), %llu, %s" ), CurrentIndex, SizeAndCount.Size, SizeAndCount.Size / 1024.0f / 1024.0f, SizeAndCount.Count, *AllocCallstack ); // Dump stats MemoryReport.AddColumn( TEXT( "%llu" ), SizeAndCount.Size ); MemoryReport.AddColumn( TEXT( "%.2f MB" ), SizeAndCount.Size / 1024.0f / 1024.0f ); MemoryReport.AddColumn( TEXT( "%llu" ), SizeAndCount.Count ); MemoryReport.AddColumn( *AllocCallstack ); MemoryReport.CycleRow(); CurrentIndex++; DisplayedSoFar += SizeAndCount.Size; const float CurrentPct = (float)DisplayedSoFar / (float)TotalAllocatedMemory; if( CurrentPct > MaxPctDisplayed ) { break; } } UE_LOG( LogStats, Warning, TEXT( "Allocated memory: %llu bytes (%.2f MB)" ), TotalAllocatedMemory, TotalAllocatedMemory / 1024.0f / 1024.0f ); // Add a total row. MemoryReport.CycleRow(); MemoryReport.CycleRow(); MemoryReport.CycleRow(); MemoryReport.AddColumn( TEXT( "%llu" ), TotalAllocatedMemory ); MemoryReport.AddColumn( TEXT( "%.2f MB" ), TotalAllocatedMemory / 1024.0f / 1024.0f ); MemoryReport.AddColumn( TEXT( "%llu" ), NumAllocations ); MemoryReport.AddColumn( TEXT( "TOTAL" ) ); MemoryReport.CycleRow(); }
void SDetailsViewBase::QueryCustomDetailLayout(FDetailLayoutBuilderImpl& CustomDetailLayout) { FPropertyEditorModule& ParentPlugin = FModuleManager::GetModuleChecked<FPropertyEditorModule>("PropertyEditor"); // Get the registered classes that customize details FCustomDetailLayoutNameMap& GlobalCustomLayoutNameMap = ParentPlugin.ClassNameToDetailLayoutNameMap; UStruct* BaseStruct = GetBaseStruct(); // All the current customization instances need to be deleted when it is safe CustomizationClassInstancesPendingDelete = CustomizationClassInstances; CustomizationClassInstances.Empty(); //Ask for generic details not specific to an object being viewed if (GenericLayoutDelegate.IsBound()) { // Create a new instance of the custom detail layout for the current class TSharedRef<IDetailCustomization> CustomizationInstance = GenericLayoutDelegate.Execute(); // Ask for details immediately CustomizationInstance->CustomizeDetails(CustomDetailLayout); // Save the instance from destruction until we refresh CustomizationClassInstances.Add(CustomizationInstance); } // Sort them by query order. @todo not good enough struct FCompareFDetailLayoutCallback { FORCEINLINE bool operator()(const FDetailLayoutCallback& A, const FDetailLayoutCallback& B) const { return A.Order < B.Order; } }; TMap< TWeakObjectPtr<UStruct>, FDetailLayoutCallback*> FinalCallbackMap; for (auto ClassIt = ClassesWithProperties.CreateConstIterator(); ClassIt; ++ClassIt) { // Check the instanced map first FDetailLayoutCallback* Callback = InstancedClassToDetailLayoutMap.Find(*ClassIt); if (!Callback) { // callback wasn't found in the per instance map, try the global instances instead Callback = GlobalCustomLayoutNameMap.Find((*ClassIt)->GetFName()); } if (Callback) { FinalCallbackMap.Add(*ClassIt, Callback); } } FinalCallbackMap.ValueSort(FCompareFDetailLayoutCallback()); TSet<UStruct*> QueriedClasses; if (FinalCallbackMap.Num() > 0) { // Ask each class that we have properties for to customize its layout for (auto LayoutIt(FinalCallbackMap.CreateConstIterator()); LayoutIt; ++LayoutIt) { const TWeakObjectPtr<UStruct> WeakClass = LayoutIt.Key(); if (WeakClass.IsValid()) { UStruct* Class = WeakClass.Get(); FClassInstanceToPropertyMap& InstancedPropertyMap = ClassToPropertyMap.FindChecked(Class->GetFName()); for (FClassInstanceToPropertyMap::TIterator InstanceIt(InstancedPropertyMap); InstanceIt; ++InstanceIt) { FName Key = InstanceIt.Key(); CustomDetailLayout.SetCurrentCustomizationClass(CastChecked<UClass>(Class), Key); const FOnGetDetailCustomizationInstance& DetailDelegate = LayoutIt.Value()->DetailLayoutDelegate; if (DetailDelegate.IsBound()) { QueriedClasses.Add(Class); // Create a new instance of the custom detail layout for the current class TSharedRef<IDetailCustomization> CustomizationInstance = DetailDelegate.Execute(); // Ask for details immediately CustomizationInstance->CustomizeDetails(CustomDetailLayout); // Save the instance from destruction until we refresh CustomizationClassInstances.Add(CustomizationInstance); } } } } } // Ensure that the base class and its parents are always queried TSet<UStruct*> ParentClassesToQuery; if (BaseStruct && !QueriedClasses.Contains(BaseStruct)) { ParentClassesToQuery.Add(BaseStruct); ClassesWithProperties.Add(BaseStruct); } // Find base classes of queried classes that were not queried and add them to the query list // this supports cases where a parent class has no properties but still wants to add customization for (auto QueriedClassIt = ClassesWithProperties.CreateConstIterator(); QueriedClassIt; ++QueriedClassIt) { UStruct* ParentStruct = (*QueriedClassIt)->GetSuperStruct(); while (ParentStruct && ParentStruct->IsA(UClass::StaticClass()) && !QueriedClasses.Contains(ParentStruct) && !ClassesWithProperties.Contains(ParentStruct)) { ParentClassesToQuery.Add(ParentStruct); ParentStruct = ParentStruct->GetSuperStruct(); } } // Query extra base classes for (auto ParentIt = ParentClassesToQuery.CreateConstIterator(); ParentIt; ++ParentIt) { if (Cast<UClass>(*ParentIt)) { QueryLayoutForClass(CustomDetailLayout, *ParentIt); } } }
void FGPUProfilerEventNodeFrame::DumpEventTree() { if (EventTree.Num() > 0) { float RootResult = GetRootTimingResults(); //extern ENGINE_API float GUnit_GPUFrameTime; //UE_LOG(LogRHI, Warning, TEXT("Perf marker hierarchy, total GPU time %.2fms (%.2fms smoothed)"), RootResult * 1000.0f, GUnit_GPUFrameTime); UE_LOG(LogRHI, Warning, TEXT("Perf marker hierarchy, total GPU time %.2fms"), RootResult * 1000.0f); LogDisjointQuery(); TMap<FString, FGPUProfilerEventNodeStats> EventHistogram; for (int32 BaseNodeIndex = 0; BaseNodeIndex < EventTree.Num(); BaseNodeIndex++) { GatherStatsEventNode(EventTree[BaseNodeIndex], 0, EventHistogram); } int32 NumNodes = 0; int32 NumDraws = 0; for (int32 BaseNodeIndex = 0; BaseNodeIndex < EventTree.Num(); BaseNodeIndex++) { DumpStatsEventNode(EventTree[BaseNodeIndex], RootResult, 0, NumNodes, NumDraws); } //@todo - calculate overhead instead of hardcoding // This .012ms of overhead is based on what Nsight shows as the minimum draw call duration on a 580 GTX, // Which is apparently how long it takes to issue two timing events. UE_LOG(LogRHI, Warning, TEXT("Total Nodes %u Draws %u approx overhead %.2fms"), NumNodes, NumDraws, .012f * NumNodes); UE_LOG(LogRHI, Warning, TEXT("")); UE_LOG(LogRHI, Warning, TEXT("")); // Sort descending based on node duration EventHistogram.ValueSort( FNodeStatsCompare() ); // Log stats about the node histogram UE_LOG(LogRHI, Warning, TEXT("Node histogram %u buckets"), EventHistogram.Num()); int32 NumNotShown = 0; for (TMap<FString, FGPUProfilerEventNodeStats>::TIterator It(EventHistogram); It; ++It) { const FGPUProfilerEventNodeStats& NodeStats = It.Value(); if (NodeStats.TimingResult > RootResult * 1000.0f * .005f) { UE_LOG(LogRHI, Warning, TEXT(" %.2fms %s Events %u Draws %u"), NodeStats.TimingResult, *It.Key(), NodeStats.NumEvents, NodeStats.NumDraws); } else { NumNotShown++; } } UE_LOG(LogRHI, Warning, TEXT(" %u buckets not shown"), NumNotShown); #if !UE_BUILD_SHIPPING // Create and display profile visualizer data if (RHIConfig::ShouldShowProfilerAfterProfilingGPU()) { // execute on main thread { struct FDisplayProfilerVisualizer { void Thread( TSharedPtr<FVisualizerEvent> InVisualizerData ) { static FName TaskGraphModule(TEXT("TaskGraph")); if (FModuleManager::Get().IsModuleLoaded(TaskGraphModule)) { IProfileVisualizerModule* ProfileVisualizer = (IProfileVisualizerModule*)&FModuleManager::Get().GetModuleInterface(TaskGraphModule); ProfileVisualizer->DisplayProfileVisualizer( InVisualizerData, TEXT("GPU") ); } } } DisplayProfilerVisualizer; TSharedPtr<FVisualizerEvent> VisualizerData = CreateVisualizerData( EventTree ); FSimpleDelegateGraphTask::CreateAndDispatchWhenReady ( FSimpleDelegateGraphTask::FDelegate::CreateRaw(&DisplayProfilerVisualizer, &FDisplayProfilerVisualizer::Thread, VisualizerData ) , TEXT("DisplayProfilerVisualizer") , nullptr, ENamedThreads::GameThread ); } } #endif } }