Files
UnrealEngineUWP/Engine/Source/Developer/DrawPrimitiveDebugger/Private/SDrawPrimitiveDebugger.cpp

512 lines
15 KiB
C++
Raw Normal View History

// Copyright Epic Games, Inc. All Rights Reserved.
#include "SDrawPrimitiveDebugger.h"
#include "DrawPrimitiveDebugger.h"
#include "DrawPrimitiveDebuggerConfig.h"
#include "SlateOptMacros.h"
#include "Framework/Application/SlateApplication.h"
#include "Components/PrimitiveComponent.h"
#include "Fonts/FontMeasure.h"
#include "Materials/Material.h"
#include "Widgets/Input/SButton.h"
#include "Widgets/SBoxPanel.h"
#include "Widgets/Input/SCheckBox.h"
#include "Widgets/Layout/SScrollBox.h"
#include "Widgets/Layout/SScrollBar.h"
#include "Widgets/Layout/SBox.h"
#include "Widgets/Text/STextBlock.h"
#if !UE_BUILD_SHIPPING
BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
void SDrawPrimitiveDebugger::Construct(const FArguments& InArgs)
{
const TSharedRef<SScrollBar> VerticalScrollBar = SNew(SScrollBar)
.Orientation(Orient_Vertical)
.Thickness(FVector2D(12.0f, 12.0f));
ColumnHeader = SNew(SHeaderRow).ResizeMode(ESplitterResizeMode::Fill);
const FName VisibilityColumn("Visible");
const FName PinColumn("Pin");
const FName NameColumn("Name");
const FName NumDrawsColumn("NumDraws");
const FName ActorClassColumn("ActorClass");
const FName ActorColumn("Actor");
const FName LocationColumn("Location");
const FName MaterialsColumn("Materials");
const FName LODColumn("LOD");
const FName TrianglesColumn("Triangles");
AddColumn(FText::FromString("Visible"), VisibilityColumn);
AddColumn(FText::FromString("Pinned"), PinColumn);
AddColumn(FText::FromString("Name"), NameColumn);
AddColumn(FText::FromString("Draws"), NumDrawsColumn);
AddColumn(FText::FromString("ActorClass"), ActorClassColumn);
AddColumn(FText::FromString("Actor"), ActorColumn);
AddColumn(FText::FromString("Location"), LocationColumn);
AddColumn(FText::FromString("Materials"), MaterialsColumn);
AddColumn(FText::FromString("LOD"), LODColumn);
AddColumn(FText::FromString("Triangles"), TrianglesColumn);
Refresh();
Table = SNew(SListView<FPrimitiveRowDataPtr>)
.ListItemsSource(&VisibleEntries)
.HeaderRow(ColumnHeader)
.OnGenerateRow(this, &SDrawPrimitiveDebugger::MakeRowWidget)
.ExternalScrollbar(VerticalScrollBar)
.Orientation(Orient_Vertical)
.ConsumeMouseWheel(EConsumeMouseWheel::Never)
.SelectionMode(ESelectionMode::Multi);
ChildSlot
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.AutoHeight()
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.Padding(6, 6)
.HAlign(HAlign_Fill)
.FillWidth(2)
[
SAssignNew(SearchBox, SSearchBox)
.InitialText(this, &SDrawPrimitiveDebugger::GetFilterText)
.OnTextChanged(this, &SDrawPrimitiveDebugger::OnFilterTextChanged)
.OnTextCommitted(this, &SDrawPrimitiveDebugger::OnFilterTextCommitted)
]
+ SHorizontalBox::Slot()
.Padding(6, 6)
.AutoWidth()
[
SNew(SButton)
.Text(FText::FromString("Refresh"))
.IsEnabled(this, &SDrawPrimitiveDebugger::CanCaptureSingleFrame)
.OnClicked(this, &SDrawPrimitiveDebugger::OnRefreshClick)
]
+ SHorizontalBox::Slot()
.Padding(6, 6)
.AutoWidth()
[
SNew(SButton)
.Text(FText::FromString("Save to CSV"))
.OnClicked(this, &SDrawPrimitiveDebugger::OnSaveClick)
]
/*+ SHorizontalBox::Slot()
.Padding(6, 6)
.AutoWidth()
[
SNew(SCheckBox)
[
SNew(STextBlock)
.Text(FText::FromString("Enable Live Capture"))
.Font(FSlateFontInfo(FCoreStyle::GetDefaultFont(), UDrawPrimitiveDebuggerUserSettings::GetFontSize()))
]
.IsChecked(this, &SDrawPrimitiveDebugger::IsLiveCaptureChecked)
.OnCheckStateChanged(this, &SDrawPrimitiveDebugger::OnToggleLiveCapture)
]*/ // TODO: Re-enable after the performance issues have been fixed
]
+SVerticalBox::Slot()
.Padding(6, 6)
[
SNew(SScrollBox)
.Orientation(Orient_Vertical)
.ConsumeMouseWheel(EConsumeMouseWheel::Always)
+SScrollBox::Slot()
[
Table.ToSharedRef()
]
]
];
}
FText SDrawPrimitiveDebugger::GetFilterText() const
{
return FilterText;
}
void SDrawPrimitiveDebugger::OnFilterTextChanged(const FText& InFilterText)
{
FilterText = InFilterText;
UpdateVisibleRows();
if (Table.IsValid())
{
Table->RequestListRefresh();
}
}
void SDrawPrimitiveDebugger::OnFilterTextCommitted(const FText& NewText, ETextCommit::Type CommitInfo)
{
if (CommitInfo == ETextCommit::OnCleared)
{
SearchBox->SetText(FText::GetEmpty());
OnFilterTextChanged(FText::GetEmpty());
}
}
TSharedRef<ITableRow> SDrawPrimitiveDebugger::MakeRowWidget(FPrimitiveRowDataPtr InRowDataPtr, const TSharedRef<STableViewBase>& OwnerTable)
{
return SNew(SDrawPrimitiveDebuggerListViewRow, OwnerTable)
.DrawPrimitiveDebugger(SharedThis(this))
.RowDataPtr(InRowDataPtr);
}
void SDrawPrimitiveDebugger::UpdateVisibleRows()
{
if (FilterText.IsEmptyOrWhitespace())
{
VisibleEntries = AvailableEntries;
}
else
{
VisibleEntries.Empty();
const FString& ActiveFilterString = FilterText.ToString();
for (const FPrimitiveRowDataPtr& RowData : AvailableEntries)
{
if (!RowData.IsValid()) continue;
bool bPassesFilter = false;
if (RowData->Name.Contains(ActiveFilterString))
{
bPassesFilter = true;
}
else if (IsValid(RowData->Owner) &&
(RowData->Owner->GetClass()->GetName().Contains(ActiveFilterString) ||
RowData->Owner->GetFullName().Contains(ActiveFilterString)))
{
bPassesFilter = true;
}
else
{
for (const UMaterialInterface* Material : RowData->Materials)
{
if (IsValid(Material) && IsValid(Material->GetMaterial()) &&
Material->GetMaterial()->GetName().Contains(ActiveFilterString))
{
bPassesFilter = true;
break;
}
}
}
if (bPassesFilter)
{
VisibleEntries.Add(RowData);
}
}
}
SortRows();
}
void SDrawPrimitiveDebugger::SortRows()
{
VisibleEntries.Sort([this](FPrimitiveRowDataPtr A, FPrimitiveRowDataPtr B)
{
const bool bPinnedA = IsEntryPinned(A);
const bool bPinnedB = IsEntryPinned(B);
return (bPinnedA > bPinnedB) || ((bPinnedA == bPinnedB) && *A < *B); // Put pinned entries first
});
}
void SDrawPrimitiveDebugger::Refresh()
{
AvailableEntries.Empty();
for (const TPair<FPrimitiveComponentId, FViewDebugInfo::FPrimitiveInfo>& Copy : LocalCopies)
{
AvailableEntries.Add(MakeShared<const FViewDebugInfo::FPrimitiveInfo>(Copy.Value)); // Add the local copies (pinned and hidden) at the top
}
FViewDebugInfo::Get().ForEachPrimitive([this](const FViewDebugInfo::FPrimitiveInfo& Primitive)
{
if (!LocalCopies.Contains(Primitive.ComponentId))
{
AvailableEntries.Add(MakeShared<const FViewDebugInfo::FPrimitiveInfo>(Primitive));
if (IsValid(Primitive.Component) && !Primitive.Component->IsVisible())
{
LocalCopies.Add(Primitive.ComponentId, Primitive);
HiddenEntries.Add(Primitive.ComponentId);
}
}
});
UpdateVisibleRows();
}
void SDrawPrimitiveDebugger::AddColumn(const FText& Name, const FName& ColumnId)
{
const TSharedRef<FSlateFontMeasure> FontMeasure = FSlateApplication::Get().GetRenderer()->GetFontMeasureService();
const FSlateFontInfo FontInfo = FSlateFontInfo(FCoreStyle::GetDefaultFont(), 12);
const FName VisibilityColumn("Visible");
const FName PinColumn("Pin");
const FName NumDrawsColumn("NumDraws");
const FName LODColumn("LOD");
if (ColumnId.IsEqual(VisibilityColumn) || ColumnId.IsEqual(PinColumn) ||
ColumnId.IsEqual(NumDrawsColumn) || ColumnId.IsEqual(LODColumn))
{ // Rows that can be narrow and fixed
ColumnHeader->AddColumn(
SHeaderRow::Column(ColumnId)
.DefaultLabel(Name)
.FixedWidth(FontMeasure->Measure(Name, FontInfo).X)
);
}
else
{
ColumnHeader->AddColumn(
SHeaderRow::Column(ColumnId)
.DefaultLabel(Name)
);
}
}
void SDrawPrimitiveDebugger::OnChangeEntryVisibility(ECheckBoxState State, FPrimitiveRowDataPtr Data)
{
if (Data.IsValid() && IsValid(Data->Component) && State != ECheckBoxState::Undetermined)
{
Data->Component->SetVisibility(State == ECheckBoxState::Checked);
if (State == ECheckBoxState::Unchecked)
{
if (!LocalCopies.Contains(Data->ComponentId))
{
LocalCopies.Add(Data->ComponentId, *Data); // Keep a local copy to prevent the primitive from disappearing during visibility check
}
HiddenEntries.Add(Data->ComponentId);
}
else if (State == ECheckBoxState::Checked)
{
HiddenEntries.Remove(Data->ComponentId);
if (!IsEntryPinned(Data))
{
LocalCopies.Remove(Data->ComponentId); // Remove the local copy when no longer needed
}
}
}
}
bool SDrawPrimitiveDebugger::IsEntryVisible(FPrimitiveRowDataPtr Data) const
{
return !HiddenEntries.Contains(Data->ComponentId);
}
void SDrawPrimitiveDebugger::OnChangeEntryPinned(ECheckBoxState State, FPrimitiveRowDataPtr Data)
{
if (State != ECheckBoxState::Undetermined)
{
if (State == ECheckBoxState::Checked)
{
if (!LocalCopies.Contains(Data->ComponentId))
{
LocalCopies.Add(Data->ComponentId, *Data); // Keep a local copy to prevent the primitive from disappearing during visibility check
}
PinnedEntries.Add(Data->ComponentId);
}
else if (State == ECheckBoxState::Unchecked)
{
PinnedEntries.Remove(Data->ComponentId);
if (IsEntryVisible(Data))
{
LocalCopies.Remove(Data->ComponentId); // Remove the local copy when no longer needed
}
}
}
UpdateVisibleRows();
if (Table.IsValid())
{
Table->RequestListRefresh();
}
}
bool SDrawPrimitiveDebugger::IsEntryPinned(FPrimitiveRowDataPtr Data) const
{
return PinnedEntries.Contains(Data->ComponentId);
}
bool SDrawPrimitiveDebugger::CanCaptureSingleFrame() const
{
return IDrawPrimitiveDebugger::IsAvailable() && !IDrawPrimitiveDebugger::Get().IsLiveCaptureEnabled();
}
FReply SDrawPrimitiveDebugger::OnRefreshClick()
{
IDrawPrimitiveDebugger::Get().CaptureSingleFrame();
return FReply::Handled();
}
FReply SDrawPrimitiveDebugger::OnSaveClick()
{
FViewDebugInfo::Get().DumpToCSV();
return FReply::Handled();
}
ECheckBoxState SDrawPrimitiveDebugger::IsLiveCaptureChecked() const
{
return IDrawPrimitiveDebugger::Get().IsLiveCaptureEnabled() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked;
}
void SDrawPrimitiveDebugger::OnToggleLiveCapture(ECheckBoxState state)
{
if (state == ECheckBoxState::Checked)
{
IDrawPrimitiveDebugger::Get().EnableLiveCapture();
}
else if (state == ECheckBoxState::Unchecked)
{
IDrawPrimitiveDebugger::Get().DisableLiveCapture();
}
}
void SDrawPrimitiveDebuggerListViewRow::Construct(const FArguments& InArgs,
const TSharedRef<STableViewBase>& InOwnerTableView)
{
RowDataPtr = InArgs._RowDataPtr;
DrawPrimitiveDebugger = InArgs._DrawPrimitiveDebugger;
SMultiColumnTableRow<FPrimitiveRowDataPtr>::Construct(
FSuperRowType::FArguments(),
InOwnerTableView
);
}
TSharedRef<SWidget> SDrawPrimitiveDebuggerListViewRow::GenerateWidgetForColumn(const FName& ColumnName)
{
const TSharedPtr<SDrawPrimitiveDebugger> DrawPrimitiveDebuggerPtr = DrawPrimitiveDebugger.Pin();
return (DrawPrimitiveDebuggerPtr.IsValid())
? MakeCellWidget(IndexInList, ColumnName)
: SNullWidget::NullWidget;
}
TSharedRef<SWidget> SDrawPrimitiveDebuggerListViewRow::MakeCellWidget(const int32 InRowIndex, const FName& InColumnId)
{
SDrawPrimitiveDebugger* DrawPrimitiveDebuggerPtr = DrawPrimitiveDebugger.Pin().Get();
if (DrawPrimitiveDebuggerPtr && RowDataPtr.IsValid())
{
const TSharedRef<FSlateFontMeasure> FontMeasure = FSlateApplication::Get().GetRenderer()->GetFontMeasureService();
const FSlateFontInfo FontInfo = FSlateFontInfo(FCoreStyle::GetDefaultFont(), UDrawPrimitiveDebuggerUserSettings::GetFontSize());
FText Value;
const FName VisibilityColumn("Visible");
const FName PinColumn("Pin");
const FName NameColumn("Name");
const FName ActorClassColumn("ActorClass");
const FName ActorColumn("Actor");
const FName LocationColumn("Location");
const FName MaterialsColumn("Materials");
const FName NumDrawsColumn("NumDraws");
const FName LODColumn("LOD");
const FName TrianglesColumn("Triangles");
if (InColumnId.IsEqual(VisibilityColumn))
{
return SNew(SBox)
.Padding(FMargin(5, 2, 5, 2))
.HAlign(HAlign_Center)
[
SNew(SCheckBox)
.IsChecked(this, &SDrawPrimitiveDebuggerListViewRow::IsVisible)
.OnCheckStateChanged(DrawPrimitiveDebuggerPtr, &SDrawPrimitiveDebugger::OnChangeEntryVisibility, RowDataPtr)
.HAlign(HAlign_Center)
];
}
if (InColumnId.IsEqual(PinColumn))
{
return SNew(SBox)
.Padding(FMargin(5, 2, 5, 2))
.HAlign(HAlign_Center)
[
SNew(SCheckBox)
.IsChecked(this, &SDrawPrimitiveDebuggerListViewRow::IsPinned)
.OnCheckStateChanged(DrawPrimitiveDebuggerPtr, &SDrawPrimitiveDebugger::OnChangeEntryPinned, RowDataPtr)
.HAlign(HAlign_Center)
];
}
if (InColumnId.IsEqual(NameColumn))
{
Value = FText::FromString(RowDataPtr->Name);
}
else if (InColumnId.IsEqual(ActorClassColumn))
{
Value = IsValid(RowDataPtr->Owner) ?
FText::FromString(RowDataPtr->Owner->GetClass()->GetName()) :
FText::FromString("INVALID");
}
else if (InColumnId.IsEqual(ActorColumn))
{
Value = IsValid(RowDataPtr->Owner) ?
FText::FromString(RowDataPtr->Owner->GetHumanReadableName()) :
FText::FromString("INVALID");
}
else if (InColumnId.IsEqual(LocationColumn))
{
Value = IsValid(RowDataPtr->Owner) ?
FText::FromString(RowDataPtr->Owner->GetActorLocation().ToString()) :
FText::FromString("INVALID");
}
else if (InColumnId.IsEqual(MaterialsColumn))
{
const int32 Count = RowDataPtr->Materials.Num();
FString Materials = FString::Printf(TEXT("[%d] {"), Count);
for (int i = 0; i < Count; i++)
{
const UMaterialInterface* MI = RowDataPtr->Materials[i];
if (MI && MI->GetMaterial())
{
Materials += MI->GetMaterial()->GetName();
}
else
{
Materials += "Null";
}
if (i < Count - 1)
{
Materials += ", ";
}
}
Materials += "}";
Value = FText::FromString(Materials);
}
else if (InColumnId.IsEqual(NumDrawsColumn))
{
Value = FText::FromString(FString::FromInt(RowDataPtr->DrawCount));
}
else if (InColumnId.IsEqual(LODColumn))
{
Value = FText::FromString(FString::FromInt(RowDataPtr->LOD));
}
else if (InColumnId.IsEqual(TrianglesColumn))
{
Value = FText::FromString(FString::FromInt(RowDataPtr->TriangleCount));
}
else
{
// Invalid Column name
return SNullWidget::NullWidget;
}
return SNew(SBox)
.Padding(FMargin(5, 2, 5, 2))
.HAlign(HAlign_Fill)
[
SNew(STextBlock)
.ColorAndOpacity(FSlateColor::UseForeground())
.Text(Value)
.Font(FontInfo)
.IsEnabled(DrawPrimitiveDebuggerPtr, &SDrawPrimitiveDebugger::IsEntryVisible, RowDataPtr)
.Justification(ETextJustify::Left)
.HighlightText(DrawPrimitiveDebuggerPtr, &SDrawPrimitiveDebugger::GetFilterText)
];
}
return SNullWidget::NullWidget;
}
ECheckBoxState SDrawPrimitiveDebuggerListViewRow::IsVisible() const
{
const SDrawPrimitiveDebugger* DrawPrimitiveDebuggerPtr = DrawPrimitiveDebugger.Pin().Get();
return DrawPrimitiveDebuggerPtr && DrawPrimitiveDebuggerPtr->IsEntryVisible(RowDataPtr) ? ECheckBoxState::Checked : ECheckBoxState::Unchecked;
}
ECheckBoxState SDrawPrimitiveDebuggerListViewRow::IsPinned() const
{
const SDrawPrimitiveDebugger* DrawPrimitiveDebuggerPtr = DrawPrimitiveDebugger.Pin().Get();
return DrawPrimitiveDebuggerPtr && DrawPrimitiveDebuggerPtr->IsEntryPinned(RowDataPtr) ? ECheckBoxState::Checked : ECheckBoxState::Unchecked;
}
END_SLATE_FUNCTION_BUILD_OPTIMIZATION
#endif