Files
UnrealEngineUWP/Engine/Source/Developer/DrawPrimitiveDebugger/Private/DrawPrimitiveDebuggerModule.cpp
zach harris c8c7fd62ec Various fixes and enhancements to the primitive debugger tool.
- Renamed the command to access the tool from DrawPrimitiveDebugger.Open to PrimitiveDebugger.Open
- Fixed access violations and crashes that occurred when opening and using the tool.
- Fixed issue where the debugger table would not populate until filter text was entered when using it in-game.
- Fixed an issue causing the server to crash if the console command cheat DrawPrimitiveDebugger.Open is used
- Added support for skeletal meshes and other primitives.
- Added a details panel for the currently selected row to declutter the table view and provide more in depth information such as the materials and textures used and the number of available LODs. The data displayed will refresh automatically and display the active location, LOD, and triangle count of the primitive.
- The details panel can force LOD levels, force disable nanite, display bounds, and show skeletal bones. Bounds and bone displays only work in development or debug builds.
- Note that any changes to primitives made by the debugger, as well as pinned and hidden entries, will be reset when the debugger is closed.
- Fixed communication between the game and render threads when handling frame captures.
- Enhanced search functionality. You can now search by primitive name, primitive class, actor name, actor class, material name, and texture used.

#rb daniele.vettorel
[FYI] elizabeth.bunner, nicolas.mercier, bryce.lumpkin
#tests FNTEST-128602

[CL 32064534 by zach harris in ue5-main branch]
2024-03-06 15:16:33 -05:00

240 lines
7.1 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "CoreMinimal.h"
#include "Engine/Engine.h"
#include "Engine/World.h"
#include "Modules/ModuleManager.h"
#include "DrawPrimitiveDebugger.h"
#include "SDrawPrimitiveDebugger.h"
#include "ViewDebug.h"
#include "Widgets/Docking/SDockTab.h"
DEFINE_LOG_CATEGORY(LogDrawPrimitiveDebugger)
#if WITH_PRIMITIVE_DEBUGGER
static FAutoConsoleCommand SummonDebuggerCmd(
TEXT("PrimitiveDebugger.Open"),
TEXT("Summons the primitive debugger window."),
FConsoleCommandDelegate::CreateLambda([]() { IDrawPrimitiveDebugger::Get().OpenDebugWindow(); })
);
/*static FAutoConsoleCommand EnableLiveCaptureCmd(
TEXT("PrimitiveDebugger.EnableLiveCapture"),
TEXT("Enables live capture for the primitive debugger."),
FConsoleCommandDelegate::CreateLambda([]() { IDrawPrimitiveDebugger::Get().EnableLiveCapture(); })
);
static FAutoConsoleCommand DisableLiveCaptureCmd(
TEXT("PrimitiveDebugger.DisableLiveCapture"),
TEXT("Disables live capture for the primitive debugger."),
FConsoleCommandDelegate::CreateLambda([]() { IDrawPrimitiveDebugger::Get().DisableLiveCapture(); })
);*/ // TODO: Re-enable these commands once live capture performance has been fixed
static FAutoConsoleCommand TakeSnapshotCmd(
TEXT("PrimitiveDebugger.Snapshot"),
TEXT("Captures the primitives rendered on the next frame for the primitive debugger."),
FConsoleCommandDelegate::CreateLambda([]() { IDrawPrimitiveDebugger::Get().CaptureSingleFrame(); })
);
#endif
class FDrawPrimitiveDebuggerModule : public IDrawPrimitiveDebugger
{
public:
virtual void StartupModule() override;
virtual void ShutdownModule() override;
virtual void CaptureSingleFrame() override;
virtual bool IsLiveCaptureEnabled() const override;
virtual void EnableLiveCapture() override;
virtual void DisableLiveCapture() override;
virtual void DiscardCaptureData() override;
virtual void OpenDebugWindow() override;
virtual void CloseDebugWindow() override;
#if WITH_PRIMITIVE_DEBUGGER
static const FViewDebugInfo& GetViewDebugInfo();
#endif
private:
bool bLiveCaptureEnabled = false;
FDelegateHandle UpdateDelegateHandle;
#if WITH_PRIMITIVE_DEBUGGER
TSharedPtr<SDrawPrimitiveDebugger> DebuggerWidget;
TSharedPtr<SDockTab> DebuggerTab;
FDelegateHandle OnWorldDestroyedHandle;
FDelegateHandle OnWorldAddedHandle;
TSharedRef<SDockTab> MakeDrawPrimitiveDebuggerTab(const FSpawnTabArgs&);
void OnTabClosed(TSharedRef<SDockTab> Tab);
void OnUpdateViewInformation();
void HandleWorldDestroyed(UWorld* World);
void HandleWorldAdded(UWorld* World);
#endif
};
IMPLEMENT_MODULE(FDrawPrimitiveDebuggerModule, DrawPrimitiveDebugger)
void FDrawPrimitiveDebuggerModule::StartupModule()
{
bLiveCaptureEnabled = false;
#if WITH_PRIMITIVE_DEBUGGER
UpdateDelegateHandle = FViewDebugInfo::Instance.AddUpdateHandler(this, &FDrawPrimitiveDebuggerModule::OnUpdateViewInformation);
FGlobalTabmanager::Get()->RegisterNomadTabSpawner("Primitive Debugger", FOnSpawnTab::CreateRaw(this, &FDrawPrimitiveDebuggerModule::MakeDrawPrimitiveDebuggerTab) );
#endif
}
void FDrawPrimitiveDebuggerModule::ShutdownModule()
{
#if WITH_PRIMITIVE_DEBUGGER
FViewDebugInfo::Instance.RemoveUpdateHandler(UpdateDelegateHandle);
#endif
}
void FDrawPrimitiveDebuggerModule::CaptureSingleFrame()
{
#if WITH_PRIMITIVE_DEBUGGER
UE_LOG(LogDrawPrimitiveDebugger, Log, TEXT("Collecting a single frame graphics data capture"));
FViewDebugInfo::Instance.CaptureNextFrame();
#endif
}
bool FDrawPrimitiveDebuggerModule::IsLiveCaptureEnabled() const
{
return bLiveCaptureEnabled;
}
void FDrawPrimitiveDebuggerModule::EnableLiveCapture()
{
#if WITH_PRIMITIVE_DEBUGGER
if (!bLiveCaptureEnabled)
{
UE_LOG(LogDrawPrimitiveDebugger, Log, TEXT("Enabling live graphics data capture"));
bLiveCaptureEnabled = true;
FViewDebugInfo::Instance.EnableLiveCapture();
}
#endif
}
void FDrawPrimitiveDebuggerModule::DisableLiveCapture()
{
#if WITH_PRIMITIVE_DEBUGGER
if (bLiveCaptureEnabled)
{
UE_LOG(LogDrawPrimitiveDebugger, Log, TEXT("Disabling live graphics data capture"));
bLiveCaptureEnabled = false;
FViewDebugInfo::Instance.DisableLiveCapture();
}
#endif
}
void FDrawPrimitiveDebuggerModule::DiscardCaptureData()
{
#if WITH_PRIMITIVE_DEBUGGER
UE_LOG(LogDrawPrimitiveDebugger, Log, TEXT("Clearing the captured graphics data from the Primitive Debugger"));
FViewDebugInfo::Instance.ClearCaptureData();
#endif
}
#if WITH_PRIMITIVE_DEBUGGER
const FViewDebugInfo& FDrawPrimitiveDebuggerModule::GetViewDebugInfo()
{
return FViewDebugInfo::Get();
}
#endif
void FDrawPrimitiveDebuggerModule::OpenDebugWindow()
{
#if WITH_PRIMITIVE_DEBUGGER
if (!DebuggerTab.IsValid())
{
UE_LOG(LogDrawPrimitiveDebugger, Log, TEXT("Opening the Primitive Debugger"));
OnWorldDestroyedHandle = GEngine->OnWorldDestroyed().AddRaw(this, &FDrawPrimitiveDebuggerModule::HandleWorldDestroyed);
OnWorldAddedHandle = FCoreUObjectDelegates::PostLoadMapWithWorld.AddRaw(this, &FDrawPrimitiveDebuggerModule::HandleWorldAdded);
if (!FViewDebugInfo::Instance.HasEverUpdated())
{
CaptureSingleFrame();
}
FGlobalTabmanager::Get()->TryInvokeTab(FTabId("Primitive Debugger"));
}
#endif
}
void FDrawPrimitiveDebuggerModule::CloseDebugWindow()
{
#if WITH_PRIMITIVE_DEBUGGER
if (DebuggerTab.IsValid())
{
UE_LOG(LogDrawPrimitiveDebugger, Log, TEXT("Closing the Primitive Debugger"));
DebuggerTab->RequestCloseTab();
GEngine->OnWorldDestroyed().Remove(OnWorldDestroyedHandle);
FCoreUObjectDelegates::PostLoadMapWithWorld.Remove(OnWorldAddedHandle);
}
#endif
}
#if WITH_PRIMITIVE_DEBUGGER
TSharedRef<SDockTab> FDrawPrimitiveDebuggerModule::MakeDrawPrimitiveDebuggerTab(const FSpawnTabArgs&)
{
DebuggerTab = SNew(SDockTab)
.TabRole(ETabRole::NomadTab)
.OnTabClosed(SDockTab::FOnTabClosedCallback::CreateRaw(this, &FDrawPrimitiveDebuggerModule::OnTabClosed));
if (!DebuggerWidget.IsValid())
{
DebuggerWidget = SNew(SDrawPrimitiveDebugger);
DebuggerWidget->SetActiveWorld(GEngine->GetCurrentPlayWorld());
DebuggerTab->SetContent(DebuggerWidget.ToSharedRef());
}
return DebuggerTab.ToSharedRef();
}
void FDrawPrimitiveDebuggerModule::OnTabClosed(TSharedRef<SDockTab> Tab)
{
DebuggerTab.Reset();
DebuggerTab = nullptr;
DebuggerWidget.Reset();
DebuggerWidget = nullptr;
}
void FDrawPrimitiveDebuggerModule::OnUpdateViewInformation()
{
if (DebuggerWidget && DebuggerWidget.IsValid())
{
DebuggerWidget->Refresh();
}
}
void FDrawPrimitiveDebuggerModule::HandleWorldDestroyed(UWorld* World)
{
#if WITH_EDITOR
if (IsValid(World) && !World->IsGameWorld())
{
// We should only have to care about the game world, not any editor specific worlds
return;
}
#endif
if (DebuggerWidget.IsValid())
{
// Clear all data bound to the debugger so that it no longer attempts to access them
DebuggerWidget->ClearAllEntries();
DebuggerWidget->SetActiveWorld(nullptr);
}
DiscardCaptureData();
}
void FDrawPrimitiveDebuggerModule::HandleWorldAdded(UWorld* World)
{
#if WITH_EDITOR
if (IsValid(World) && !World->IsGameWorld())
{
// We should only have to care about the game world, not any editor specific worlds
return;
}
#endif
if (DebuggerWidget.IsValid())
{
DebuggerWidget->SetActiveWorld(World);
}
}
#endif