1973 lines
61 KiB
C++
1973 lines
61 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "SDrawPrimitiveDebugger.h"
|
|
|
|
#include "DrawPrimitiveDebugger.h"
|
|
#include "DrawPrimitiveDebuggerConfig.h"
|
|
#include "SlateOptMacros.h"
|
|
#include "PrimitiveSceneProxy.h"
|
|
#include "Engine/StaticMesh.h"
|
|
#include "Engine/SkeletalMesh.h"
|
|
#include "Engine/Texture.h"
|
|
#include "Engine/World.h"
|
|
#include "Components/LineBatchComponent.h"
|
|
#include "Components/StaticMeshComponent.h"
|
|
#include "Components/SkinnedMeshComponent.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/Input/SNumericEntryBox.h"
|
|
#include "Widgets/Layout/SScrollBox.h"
|
|
#include "Widgets/Layout/SScrollBar.h"
|
|
#include "Widgets/Layout/SBox.h"
|
|
#include "Widgets/Layout/SExpandableArea.h"
|
|
#include "Widgets/Text/STextBlock.h"
|
|
|
|
#define LOCTEXT_NAMESPACE "PRIMITIVE_DEBUGGER"
|
|
|
|
#if WITH_PRIMITIVE_DEBUGGER
|
|
|
|
#define PRIMITIVE_DEBUGGER_SUPPORT_DEBUG_VISUALIZATIONS UE_ENABLE_DEBUG_DRAWING
|
|
|
|
DECLARE_STATS_GROUP(TEXT("PrimitiveDebugger"), STATGROUP_PrimitiveDebugger, STATCAT_Advanced);
|
|
DECLARE_CYCLE_STAT(TEXT("Primitive Debugger - Process Primitives Refresh"), STAT_PrimitiveDebuggerRefresh, STATGROUP_PrimitiveDebugger);
|
|
DECLARE_CYCLE_STAT(TEXT("Primitive Debugger - Process Primitives Gather"), STAT_PrimitiveDebuggerRefreshGather, STATGROUP_PrimitiveDebugger);
|
|
DECLARE_CYCLE_STAT(TEXT("Primitive Debugger - Process Primitives Update Visible"), STAT_PrimitiveDebuggerUpdateVis, STATGROUP_PrimitiveDebugger);
|
|
DECLARE_CYCLE_STAT(TEXT("Primitive Debugger - UI Make Cell"), STAT_PrimitiveDebuggerMakeCell, STATGROUP_PrimitiveDebugger);
|
|
DECLARE_CYCLE_STAT(TEXT("Primitive Debugger - UI Make Cell: Visible"), STAT_PrimitiveDebuggerMakeCellVisible, STATGROUP_PrimitiveDebugger);
|
|
DECLARE_CYCLE_STAT(TEXT("Primitive Debugger - UI Make Cell: Pinned"), STAT_PrimitiveDebuggerMakeCellPinned, STATGROUP_PrimitiveDebugger);
|
|
DECLARE_CYCLE_STAT(TEXT("Primitive Debugger - UI Make Cell: Name"), STAT_PrimitiveDebuggerMakeCellName, STATGROUP_PrimitiveDebugger);
|
|
DECLARE_CYCLE_STAT(TEXT("Primitive Debugger - UI Make Cell: ActorClass"), STAT_PrimitiveDebuggerMakeCellActorClass, STATGROUP_PrimitiveDebugger);
|
|
DECLARE_CYCLE_STAT(TEXT("Primitive Debugger - UI Make Cell: Actor"), STAT_PrimitiveDebuggerMakeCellActor, STATGROUP_PrimitiveDebugger);
|
|
|
|
BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
|
|
|
|
SPrimitiveDebuggerDetailView::~SPrimitiveDebuggerDetailView() = default;
|
|
|
|
void SPrimitiveDebuggerDetailView::Construct(const FArguments& InArgs)
|
|
{
|
|
static const FMargin Margin(5, 2, 5, 2);
|
|
static const FMargin MarginInterior(5, 2, 0, 2);
|
|
PrimitiveDebugger = InArgs._PrimitiveDebugger;
|
|
ChildSlot
|
|
[
|
|
SNew(SScrollBox)
|
|
.Orientation(Orient_Vertical)
|
|
.ConsumeMouseWheel(EConsumeMouseWheel::Always)
|
|
+ SScrollBox::Slot()
|
|
[
|
|
SAssignNew(DetailPropertiesWidget, SVerticalBox)
|
|
]
|
|
];
|
|
GenerateDetailPanelEntry(LOCTEXT("DetailPanel_NameLabel", "Name:"), &SPrimitiveDebuggerDetailView::GetSelectedPrimitiveName, nullptr,
|
|
&SPrimitiveDebuggerDetailView::GetSelectedPrimitiveName, /* bSupportHighlighting */ true);
|
|
GenerateDetailPanelEntry(LOCTEXT("DetailPanel_TypeLabel", "Type:"), &SPrimitiveDebuggerDetailView::GetSelectedPrimitiveType, nullptr,
|
|
&SPrimitiveDebuggerDetailView::GetSelectedPrimitiveType, /* bSupportHighlighting */ true);
|
|
GenerateDetailPanelEntry(LOCTEXT("DetailPanel_ActorLabel", "Actor:"), &SPrimitiveDebuggerDetailView::GetSelectedActorName, nullptr,
|
|
&SPrimitiveDebuggerDetailView::GetSelectedActorToolTip, /* bSupportHighlighting */ true);
|
|
GenerateDetailPanelEntry(LOCTEXT("DetailPanel_ActorClassLabel", "Actor Class:"), &SPrimitiveDebuggerDetailView::GetSelectedActorClassName, nullptr,
|
|
&SPrimitiveDebuggerDetailView::GetSelectedActorClassToolTip, /* bSupportHighlighting */ true);
|
|
GenerateDetailPanelEntry(LOCTEXT("DetailPanel_LocationLabel", "Location:"), &SPrimitiveDebuggerDetailView::GetSelectedLocation);
|
|
GenerateDetailPanelEntry(LOCTEXT("DetailPanel_NaniteSupportLabel", "Supports Nanite:"), &SPrimitiveDebuggerDetailView::GetSelectedPrimitiveSupportsNanite,
|
|
&SPrimitiveDebuggerDetailView::StaticMeshDataVisibility);
|
|
GenerateDetailPanelEntry(LOCTEXT("DetailPanel_NaniteEnabledLabel", "Nanite Enabled:"), &SPrimitiveDebuggerDetailView::GetSelectedPrimitiveNaniteEnabled,
|
|
&SPrimitiveDebuggerDetailView::StaticMeshDataVisibility);
|
|
GenerateDetailPanelEntry(LOCTEXT("DetailPanel_CurrentLODLabel", "Current LOD:"), &SPrimitiveDebuggerDetailView::GetSelectedLOD,
|
|
&SPrimitiveDebuggerDetailView::NonNaniteDataVisibility);
|
|
GenerateDetailPanelEntry(LOCTEXT("DetailPanel_AvailableLODsLabel", "Available LODs:"), &SPrimitiveDebuggerDetailView::GetSelectedNumLODs,
|
|
&SPrimitiveDebuggerDetailView::NonNaniteDataVisibility);
|
|
GenerateDetailPanelEntry(LOCTEXT("DetailPanel_DrawCallsLabel", "Draw Calls:"), &SPrimitiveDebuggerDetailView::GetSelectedDrawCallCount,
|
|
&SPrimitiveDebuggerDetailView::NonNaniteDataVisibility);
|
|
GenerateDetailPanelEntry(LOCTEXT("DetailPanel_TrianglesLabel", "Triangles:"), &SPrimitiveDebuggerDetailView::GetSelectedTriangleCount,
|
|
&SPrimitiveDebuggerDetailView::NonNaniteDataVisibility);
|
|
GenerateDetailPanelEntry(LOCTEXT("DetailPanel_BonesLabel", "Bones:"), &SPrimitiveDebuggerDetailView::GetSelectedBoneCount,
|
|
&SPrimitiveDebuggerDetailView::SkeletalMeshDataVisibility);
|
|
|
|
DetailPropertiesWidget->AddSlot()
|
|
.Padding(Margin)
|
|
.AutoHeight()
|
|
[
|
|
SNew(SExpandableArea)
|
|
.Padding(MarginInterior)
|
|
.HeaderContent()
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(LOCTEXT("DetailPanel_MaterialsLabel", "Materials"))
|
|
]
|
|
.BodyContent()
|
|
[
|
|
GetSelectedMaterialsWidget()
|
|
]
|
|
];
|
|
|
|
DetailPropertiesWidget->AddSlot()
|
|
.Padding(Margin)
|
|
.AutoHeight()
|
|
[
|
|
SNew(SExpandableArea)
|
|
.Padding(MarginInterior)
|
|
.InitiallyCollapsed(false)
|
|
.HeaderContent()
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(LOCTEXT("DetailPanel_AdvancedOptionsLabel", "Advanced"))
|
|
]
|
|
.BodyContent()
|
|
[
|
|
GetAdvancedOptionsWidget()
|
|
]
|
|
];
|
|
}
|
|
|
|
void SPrimitiveDebuggerDetailView::Tick(const FGeometry& AllottedGeometry, const double InCurrentTime,
|
|
const float InDeltaTime)
|
|
{
|
|
SCompoundWidget::Tick(AllottedGeometry, InCurrentTime, InDeltaTime);
|
|
|
|
if (!PrimitiveDebugger.IsValid())
|
|
{
|
|
return;
|
|
}
|
|
const TSharedPtr<SDrawPrimitiveDebugger> DebuggerInstance = PrimitiveDebugger.Pin();
|
|
const FPrimitiveRowDataPtr Selection = DebuggerInstance->GetCurrentSelection();
|
|
if (Selection.IsValid() && Selection->Owner.IsValid())
|
|
{
|
|
CurrentLOD = Selection->GetCurrentLOD(PlayerIndex, ViewIndex);
|
|
bSelectionIsNaniteEnabledThisFrame = false;
|
|
if (bSelectionSupportsNanite && Selection->IsPrimitiveValid())
|
|
{
|
|
const FPrimitiveSceneProxy* Proxy = Selection->ComponentInterface->GetSceneProxy();
|
|
bSelectionIsNaniteEnabledThisFrame = Proxy && Proxy->IsNaniteMesh();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
CurrentLOD = nullptr;
|
|
bSelectionIsNaniteEnabledThisFrame = false;
|
|
}
|
|
}
|
|
|
|
static const FText PlaceholderValue = FText::FromString(TEXT("-"));
|
|
static const FText TrueTextValue = LOCTEXT("DetailPanel_True", "true");
|
|
static const FText FalseTextValue = LOCTEXT("DetailPanel_False", "false");
|
|
static const FText InvalidTextValue = LOCTEXT("DetailPanel_InvalidValue", "INVALID");
|
|
|
|
void SPrimitiveDebuggerDetailView::UpdateSelection()
|
|
{
|
|
CurrentLOD = nullptr;
|
|
SelectedActorName = PlaceholderValue;
|
|
SelectedActorPath = PlaceholderValue;
|
|
SelectedActorClassName = PlaceholderValue;
|
|
SelectedActorClassPath = PlaceholderValue;
|
|
SelectedPrimitiveType = PlaceholderValue;
|
|
bSelectionSupportsNanite = false;
|
|
SelectedComponentType = nullptr;
|
|
SelectedAsStaticMesh = nullptr;
|
|
SelectedAsSkinnedMesh = nullptr;
|
|
if (PrimitiveDebugger.IsValid())
|
|
{
|
|
const TSharedPtr<SDrawPrimitiveDebugger> DebuggerInstance = PrimitiveDebugger.Pin();
|
|
const FPrimitiveRowDataPtr Selection = DebuggerInstance->GetCurrentSelection();
|
|
if (Selection.IsValid() && Selection->Owner.IsValid())
|
|
{
|
|
CurrentLOD = Selection->GetCurrentLOD(PlayerIndex, ViewIndex);
|
|
SelectedActorName = FText::FromString(Selection->GetOwnerName());
|
|
SelectedActorPath = FText::FromString(Selection->Owner->GetPathName());
|
|
SelectedActorClassName = FText::FromString(Selection->Owner->GetClass()->GetName());
|
|
SelectedActorClassPath = FText::FromString(Selection->Owner->GetClass()->GetPathName());
|
|
if (Selection->IsPrimitiveValid())
|
|
{
|
|
UObject* Component = Selection->ComponentInterface->GetUObject();
|
|
SelectedComponentType = Component->GetClass();
|
|
SelectedAsStaticMesh = Cast<UStaticMeshComponent>(Component);
|
|
SelectedAsSkinnedMesh = Cast<USkinnedMeshComponent>(Component);
|
|
SelectedPrimitiveType = FText::FromString(SelectedComponentType->GetName());
|
|
if (SelectedAsStaticMesh.IsValid())
|
|
{
|
|
bSelectionSupportsNanite = SelectedAsStaticMesh->GetStaticMesh()->HasValidNaniteData();
|
|
// TODO: Handle support for non-static mesh nanite primitives as they become available
|
|
}
|
|
#if PRIMITIVE_DEBUGGER_SUPPORT_DEBUG_VISUALIZATIONS
|
|
if (SelectedAsSkinnedMesh.IsValid() && DebuggerInstance->IsEntryShowingDebugBones(Selection->ComponentId) && SelectedAsSkinnedMesh->ShouldDrawDebugSkeleton())
|
|
{
|
|
SelectedAsSkinnedMesh->SetDebugDrawColor(FLinearColor(FColor::Yellow));
|
|
SelectedAsSkinnedMesh->MarkRenderStateDirty();
|
|
}
|
|
#endif
|
|
const FPrimitiveSceneProxy* Proxy = Selection->ComponentInterface->GetSceneProxy();
|
|
bSelectionIsNaniteEnabledThisFrame = Proxy && Proxy->IsNaniteMesh();
|
|
}
|
|
}
|
|
}
|
|
GetSelectedMaterialsWidget();
|
|
GetAdvancedOptionsWidget();
|
|
}
|
|
|
|
void SPrimitiveDebuggerDetailView::ReleaseSelection()
|
|
{
|
|
if (!PrimitiveDebugger.IsValid())
|
|
{
|
|
return;
|
|
}
|
|
const TSharedPtr<SDrawPrimitiveDebugger> DebuggerInstance = PrimitiveDebugger.Pin();
|
|
const FPrimitiveRowDataPtr Selection = DebuggerInstance->GetCurrentSelection();
|
|
#if PRIMITIVE_DEBUGGER_SUPPORT_DEBUG_VISUALIZATIONS
|
|
if (Selection.IsValid())
|
|
{
|
|
if (SelectedAsSkinnedMesh.IsValid() && DebuggerInstance->IsEntryShowingDebugBones(Selection->ComponentId) && SelectedAsSkinnedMesh->ShouldDrawDebugSkeleton())
|
|
{
|
|
SelectedAsSkinnedMesh->SetDebugDrawColor(FLinearColor(FColor::Orange));
|
|
SelectedAsSkinnedMesh->MarkRenderStateDirty();
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
FText SPrimitiveDebuggerDetailView::GetSelectedPrimitiveName() const
|
|
{
|
|
if (!PrimitiveDebugger.IsValid())
|
|
{
|
|
return FText::GetEmpty();
|
|
}
|
|
const FPrimitiveRowDataPtr Selection = PrimitiveDebugger.Pin()->GetCurrentSelection();
|
|
return Selection.IsValid() ? FText::FromString(Selection->Name) : PlaceholderValue;
|
|
}
|
|
|
|
FText SPrimitiveDebuggerDetailView::GetSelectedPrimitiveType() const
|
|
{
|
|
if (!PrimitiveDebugger.IsValid())
|
|
{
|
|
return FText::GetEmpty();
|
|
}
|
|
return SelectedPrimitiveType;
|
|
}
|
|
|
|
FText SPrimitiveDebuggerDetailView::GetSelectedActorName() const
|
|
{
|
|
if (!PrimitiveDebugger.IsValid())
|
|
{
|
|
return FText::GetEmpty();
|
|
}
|
|
return SelectedActorName;
|
|
}
|
|
|
|
FText SPrimitiveDebuggerDetailView::GetSelectedActorToolTip() const
|
|
{
|
|
if (!PrimitiveDebugger.IsValid())
|
|
{
|
|
return FText::GetEmpty();
|
|
}
|
|
return SelectedActorPath;
|
|
}
|
|
|
|
FText SPrimitiveDebuggerDetailView::GetSelectedActorClassName() const
|
|
{
|
|
if (!PrimitiveDebugger.IsValid())
|
|
{
|
|
return FText::GetEmpty();
|
|
}
|
|
return SelectedActorClassName;
|
|
}
|
|
|
|
FText SPrimitiveDebuggerDetailView::GetSelectedActorClassToolTip() const
|
|
{
|
|
if (!PrimitiveDebugger.IsValid())
|
|
{
|
|
return FText::GetEmpty();
|
|
}
|
|
return SelectedActorClassPath;
|
|
}
|
|
|
|
FText SPrimitiveDebuggerDetailView::GetSelectedPrimitiveNaniteEnabled() const
|
|
{
|
|
if (!PrimitiveDebugger.IsValid())
|
|
{
|
|
return FText::GetEmpty();
|
|
}
|
|
return bSelectionIsNaniteEnabledThisFrame ? TrueTextValue : FalseTextValue;
|
|
}
|
|
|
|
FText SPrimitiveDebuggerDetailView::GetSelectedPrimitiveSupportsNanite() const
|
|
{
|
|
if (!PrimitiveDebugger.IsValid())
|
|
{
|
|
return FText::GetEmpty();
|
|
}
|
|
return bSelectionSupportsNanite ? TrueTextValue : FalseTextValue;
|
|
}
|
|
|
|
FText SPrimitiveDebuggerDetailView::GetSelectedDrawCallCount() const
|
|
{
|
|
if (!CurrentLOD)
|
|
{
|
|
return PlaceholderValue;
|
|
}
|
|
return FText::FromString(FString::FromInt(CurrentLOD->GetDrawCount()));
|
|
}
|
|
|
|
FText SPrimitiveDebuggerDetailView::GetSelectedLocation() const
|
|
{
|
|
if (!PrimitiveDebugger.IsValid())
|
|
{
|
|
return FText::GetEmpty();
|
|
}
|
|
const FPrimitiveRowDataPtr Selection = PrimitiveDebugger.Pin()->GetCurrentSelection();
|
|
return Selection.IsValid() && Selection->IsPrimitiveValid() ?
|
|
FText::FromString(Selection->GetPrimitiveLocation().ToString()) :
|
|
PlaceholderValue;
|
|
}
|
|
|
|
FText SPrimitiveDebuggerDetailView::GetSelectedLOD() const
|
|
{
|
|
TOptional<int> LOD = GetSelectedLODValue();
|
|
return LOD.IsSet() && LOD.GetValue() >= 0 ? FText::FromString(FString::FromInt(LOD.GetValue())) : PlaceholderValue;
|
|
}
|
|
|
|
FText SPrimitiveDebuggerDetailView::GetSelectedNumLODs() const
|
|
{
|
|
if (!PrimitiveDebugger.IsValid())
|
|
{
|
|
return FText::GetEmpty();
|
|
}
|
|
const FPrimitiveRowDataPtr Selection = PrimitiveDebugger.Pin()->GetCurrentSelection();
|
|
return Selection.IsValid() ?
|
|
FText::FromString(FString::FromInt(Selection->GetNumLODs())) :
|
|
PlaceholderValue;
|
|
}
|
|
|
|
TOptional<int> SPrimitiveDebuggerDetailView::GetSelectedLODValue() const
|
|
{
|
|
if (!CurrentLOD)
|
|
{
|
|
return TOptional<int>();
|
|
}
|
|
return CurrentLOD->LODIndex;
|
|
}
|
|
|
|
TOptional<int> SPrimitiveDebuggerDetailView::GetSelectedForcedLODValue() const
|
|
{
|
|
if (!PrimitiveDebugger.IsValid())
|
|
{
|
|
return TOptional<int>();
|
|
}
|
|
const FPrimitiveRowDataPtr Selection = PrimitiveDebugger.Pin()->GetCurrentSelection();
|
|
if (Selection.IsValid())
|
|
{
|
|
int ForcedLOD = 0;
|
|
if (SelectedAsStaticMesh.IsValid())
|
|
{
|
|
ForcedLOD = SelectedAsStaticMesh->ForcedLodModel;
|
|
}
|
|
if (SelectedAsSkinnedMesh.IsValid())
|
|
{
|
|
ForcedLOD = SelectedAsSkinnedMesh->GetForcedLOD();
|
|
}
|
|
if (ForcedLOD > 0)
|
|
{
|
|
return FMath::Clamp(ForcedLOD - 1, 0, GetSelectedNumLODsValue().Get(0) - 1);
|
|
}
|
|
}
|
|
return TOptional<int>();
|
|
}
|
|
|
|
TOptional<int> SPrimitiveDebuggerDetailView::GetSelectedNumLODsValue() const
|
|
{
|
|
if (!PrimitiveDebugger.IsValid())
|
|
{
|
|
return 0;
|
|
}
|
|
const FPrimitiveRowDataPtr Selection = PrimitiveDebugger.Pin()->GetCurrentSelection();
|
|
if (Selection.IsValid())
|
|
{
|
|
int NumLODs = Selection->GetNumLODs();
|
|
return NumLODs;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
TOptional<int> SPrimitiveDebuggerDetailView::GetSelectedForcedLODSliderMaxValue() const
|
|
{
|
|
if (!PrimitiveDebugger.IsValid())
|
|
{
|
|
return 0;
|
|
}
|
|
const FPrimitiveRowDataPtr Selection = PrimitiveDebugger.Pin()->GetCurrentSelection();
|
|
if (Selection.IsValid())
|
|
{
|
|
return Selection->GetNumLODs() - 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
FText SPrimitiveDebuggerDetailView::GetSelectedTriangleCount() const
|
|
{
|
|
if (!CurrentLOD)
|
|
{
|
|
return PlaceholderValue;
|
|
}
|
|
return FText::FromString(FString::FromInt(CurrentLOD->Triangles));
|
|
}
|
|
|
|
FText SPrimitiveDebuggerDetailView::GetSelectedBoneCount() const
|
|
{
|
|
if (!PrimitiveDebugger.IsValid())
|
|
{
|
|
return FText::GetEmpty();
|
|
}
|
|
return SelectedAsSkinnedMesh.IsValid() ?
|
|
FText::FromString(FString::FromInt(SelectedAsSkinnedMesh->GetNumBones())) :
|
|
PlaceholderValue;
|
|
}
|
|
|
|
void SPrimitiveDebuggerDetailView::GenerateDetailPanelEntry(const FText& Label,
|
|
FText(SPrimitiveDebuggerDetailView::* ValueGetter)() const,
|
|
EVisibility (SPrimitiveDebuggerDetailView::* VisibilityGetter)() const,
|
|
FText(SPrimitiveDebuggerDetailView::* TooltipGetter)() const,
|
|
bool bSupportHighlighting) const
|
|
{
|
|
static const FMargin Margin(5, 2, 5, 2);
|
|
static constexpr int32 LabelColumnWidth = 1;
|
|
static constexpr int32 ValueColumnWidth = 2;
|
|
|
|
TSharedRef<STextBlock> EntryValue = SNew(STextBlock)
|
|
.Text(this, ValueGetter)
|
|
.Justification(ETextJustify::Left)
|
|
.OverflowPolicy(ETextOverflowPolicy::Ellipsis);
|
|
|
|
if (TooltipGetter)
|
|
{
|
|
TAttribute<FText> TooltipTextAttribute = TAttribute<FText>::Create(TAttribute<FText>::FGetter::CreateSP(this, TooltipGetter));
|
|
EntryValue->SetToolTipText(TooltipTextAttribute);
|
|
}
|
|
if (bSupportHighlighting)
|
|
{
|
|
TAttribute<FText> HighlightTextAttribute = TAttribute<FText>::Create(TAttribute<FText>::FGetter::CreateSP(PrimitiveDebugger.Pin().Get(), &SDrawPrimitiveDebugger::GetFilterText));
|
|
EntryValue->SetHighlightText(HighlightTextAttribute);
|
|
}
|
|
|
|
TSharedRef<SHorizontalBox> Entry = SNew(SHorizontalBox)
|
|
+SHorizontalBox::Slot()
|
|
.HAlign(HAlign_Left)
|
|
.FillWidth(LabelColumnWidth)
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(Label)
|
|
.Justification(ETextJustify::Left)
|
|
]
|
|
+SHorizontalBox::Slot()
|
|
.HAlign(HAlign_Right)
|
|
.FillWidth(ValueColumnWidth)
|
|
[
|
|
EntryValue
|
|
];
|
|
|
|
if (VisibilityGetter)
|
|
{
|
|
TAttribute<EVisibility> EntryVisibilityAttribute = TAttribute<EVisibility>::Create(TAttribute<EVisibility>::FGetter::CreateSP(this, VisibilityGetter));
|
|
Entry->SetVisibility(EntryVisibilityAttribute);
|
|
}
|
|
|
|
DetailPropertiesWidget->AddSlot()
|
|
.Padding(Margin)
|
|
.AutoHeight()
|
|
[
|
|
Entry
|
|
];
|
|
}
|
|
|
|
TSharedRef<SVerticalBox> SPrimitiveDebuggerDetailView::GetSelectedMaterialsWidget()
|
|
{
|
|
if (!MaterialsWidget.IsValid()) SAssignNew(MaterialsWidget, SVerticalBox);
|
|
else
|
|
{
|
|
MaterialsWidget->ClearChildren();
|
|
}
|
|
|
|
if (!PrimitiveDebugger.IsValid())
|
|
{
|
|
return MaterialsWidget.ToSharedRef();
|
|
}
|
|
|
|
const FPrimitiveRowDataPtr Selection = PrimitiveDebugger.Pin()->GetCurrentSelection();
|
|
if (Selection.IsValid())
|
|
{
|
|
if (CurrentLOD)
|
|
{
|
|
const int32 Count = CurrentLOD->MaterialIndices.Num();
|
|
|
|
for (int i = 0; i < Count; i++)
|
|
{
|
|
CreateMaterialEntry(Selection->GetMaterial(CurrentLOD->MaterialIndices[i]), i, false);
|
|
}
|
|
if (Selection->OverlayMaterial.IsValid())
|
|
{
|
|
CreateMaterialEntry(Selection->OverlayMaterial.Get(), -1, true);
|
|
}
|
|
}
|
|
else if (bSelectionIsNaniteEnabledThisFrame)
|
|
{
|
|
const int32 Count = Selection->Materials.Num();
|
|
for (int i = 0; i < Count; i++)
|
|
{
|
|
CreateMaterialEntry(Selection->Materials[i].Get(), i, false);
|
|
}
|
|
if (Selection->OverlayMaterial.IsValid())
|
|
{
|
|
CreateMaterialEntry(Selection->OverlayMaterial.Get(), -1, true);
|
|
}
|
|
}
|
|
}
|
|
return MaterialsWidget.ToSharedRef();
|
|
}
|
|
|
|
void SPrimitiveDebuggerDetailView::CreateMaterialEntry(const UMaterialInterface* MI, int Index, bool bIsOverlay)
|
|
{
|
|
static const FMargin Margin(5, 2, 5, 2);
|
|
static const FMargin MarginInterior(10, 2, 0, 2);
|
|
static constexpr int32 LabelColumnWidth = 1;
|
|
static constexpr int32 ValueColumnWidth = 2;
|
|
|
|
FString MaterialName = "NULL";
|
|
FString MaterialPath = "NULL";
|
|
const TSharedRef<SVerticalBox> TextureList = SNew(SVerticalBox);
|
|
|
|
if (MI && MI->GetMaterial())
|
|
{
|
|
MaterialName = MI->GetMaterial()->GetName();
|
|
MaterialPath = MI->GetMaterial()->GetPathName().LeftChop(MaterialName.Len() + 1);
|
|
|
|
TArray<UTexture*> Textures;
|
|
MI->GetUsedTextures(Textures, EMaterialQualityLevel::Num, false, ERHIFeatureLevel::Num, false);
|
|
const int32 TexCount = Textures.Num();
|
|
for (int t = 0; t < TexCount; t++)
|
|
{
|
|
if (IsValid(Textures[t]))
|
|
{
|
|
FString TextureName = Textures[t]->GetName();
|
|
|
|
TextureList->AddSlot()
|
|
.Padding(Margin)
|
|
.AutoHeight()
|
|
[
|
|
SNew(SHorizontalBox)
|
|
+SHorizontalBox::Slot()
|
|
.HAlign(HAlign_Right)
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(FText::FromString(TextureName))
|
|
.Justification(ETextJustify::Right)
|
|
.ToolTipText(FText::FromString(Textures[t]->GetPathName().LeftChop(TextureName.Len() + 1)))
|
|
.OverflowPolicy(ETextOverflowPolicy::Ellipsis)
|
|
.HighlightText(PrimitiveDebugger.Pin().Get(), &SDrawPrimitiveDebugger::GetFilterText)
|
|
]
|
|
];
|
|
}
|
|
else
|
|
{
|
|
TextureList->AddSlot()
|
|
.Padding(Margin)
|
|
.AutoHeight()
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(InvalidTextValue)
|
|
.Justification(ETextJustify::Left)
|
|
];
|
|
}
|
|
}
|
|
}
|
|
MaterialsWidget->AddSlot()
|
|
.Padding(MarginInterior)
|
|
.AutoHeight()
|
|
[
|
|
SNew(SExpandableArea)
|
|
.Padding(MarginInterior)
|
|
.InitiallyCollapsed(true)
|
|
.HeaderContent()
|
|
[
|
|
SNew(SHorizontalBox)
|
|
+SHorizontalBox::Slot()
|
|
.HAlign(HAlign_Left)
|
|
.FillWidth(LabelColumnWidth)
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(bIsOverlay ? LOCTEXT("DetailPanel_OverlayLabel", "Overlay") : FText::FromString(FString::FromInt(Index)))
|
|
.Justification(ETextJustify::Left)
|
|
]
|
|
+SHorizontalBox::Slot()
|
|
.HAlign(HAlign_Right)
|
|
.FillWidth(ValueColumnWidth)
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(FText::FromString(MaterialName))
|
|
.Justification(ETextJustify::Left)
|
|
.ToolTipText(FText::FromString(MaterialPath))
|
|
.OverflowPolicy(ETextOverflowPolicy::Ellipsis)
|
|
.HighlightText(PrimitiveDebugger.Pin().Get(), &SDrawPrimitiveDebugger::GetFilterText)
|
|
]
|
|
]
|
|
.BodyContent()
|
|
[
|
|
SNew(SVerticalBox)
|
|
+SVerticalBox::Slot()
|
|
.Padding(MarginInterior)
|
|
.AutoHeight()
|
|
[
|
|
SNew(SExpandableArea)
|
|
.Padding(MarginInterior)
|
|
.InitiallyCollapsed(true)
|
|
.HeaderContent()
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(LOCTEXT("DetailPanel_TexturesLabel", "Textures"))
|
|
]
|
|
.BodyContent()
|
|
[
|
|
TextureList
|
|
]
|
|
]
|
|
]
|
|
];
|
|
}
|
|
|
|
TSharedRef<SVerticalBox> SPrimitiveDebuggerDetailView::GetAdvancedOptionsWidget()
|
|
{
|
|
static const FMargin Margin(5, 2, 5, 2);
|
|
static constexpr int32 LabelColumnWidth = 1;
|
|
static constexpr int32 ValueColumnWidth = 2;
|
|
|
|
if (!AdvancedOptionsWidget.IsValid()) SAssignNew(AdvancedOptionsWidget, SVerticalBox);
|
|
else
|
|
{
|
|
AdvancedOptionsWidget->ClearChildren();
|
|
}
|
|
|
|
if (!PrimitiveDebugger.IsValid())
|
|
{
|
|
return AdvancedOptionsWidget.ToSharedRef();
|
|
}
|
|
|
|
const FPrimitiveRowDataPtr Selection = PrimitiveDebugger.Pin()->GetCurrentSelection();
|
|
if (Selection.IsValid() && Selection->IsPrimitiveValid())
|
|
{
|
|
const FText ShowBoundsTooltip = LOCTEXT("DetailPanel_ShowBoundsTooltip", "Should a debug box of this mesh's bounds be displayed? DEVELOPMENT BUILDS ONLY");
|
|
const FText ShowBonesTooltip = LOCTEXT("DetailPanel_ShowBonesTooltip", "Should a debug display of this mesh's skeleton be displayed? DEVELOPMENT BUILDS ONLY");
|
|
const FText ForcedLODTooltip = LOCTEXT("DetailPanel_ForcedLODTooltip", "Should a specific LOD level be forced on this primitive?.");
|
|
const FText ForcedLODIndexTooltip = LOCTEXT("DetailPanel_ForcedLODIndexTooltip", "Controls the forced LOD level of this primitive.");
|
|
const FText ForceDisableNaniteTooltip = LOCTEXT("DetailPanel_ForcedDisableNaniteTooltip", "Should nanite be force disabled on this component?");
|
|
|
|
AdvancedOptionsWidget->AddSlot()
|
|
.Padding(Margin)
|
|
.AutoHeight()
|
|
[
|
|
SNew(SHorizontalBox)
|
|
#if !PRIMITIVE_DEBUGGER_SUPPORT_DEBUG_VISUALIZATIONS
|
|
.IsEnabled(false)
|
|
#endif
|
|
+SHorizontalBox::Slot()
|
|
.HAlign(HAlign_Left)
|
|
.FillWidth(LabelColumnWidth)
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(LOCTEXT("DetailPanel_ShowBoundsLabel", "Show Bounds"))
|
|
.Justification(ETextJustify::Left)
|
|
.ToolTipText(ShowBoundsTooltip)
|
|
]
|
|
+SHorizontalBox::Slot()
|
|
.HAlign(HAlign_Right)
|
|
.FillWidth(ValueColumnWidth)
|
|
[
|
|
SNew(SCheckBox)
|
|
.IsChecked(this, &SPrimitiveDebuggerDetailView::ShowDebugBoundsState)
|
|
.OnCheckStateChanged(this, &SPrimitiveDebuggerDetailView::OnToggleDebugBounds)
|
|
.ToolTipText(ShowBoundsTooltip)
|
|
]
|
|
];
|
|
AdvancedOptionsWidget->AddSlot()
|
|
.Padding(Margin)
|
|
.AutoHeight()
|
|
[
|
|
SNew(SHorizontalBox)
|
|
.Visibility(this, &SPrimitiveDebuggerDetailView::SkeletalMeshDataVisibility)
|
|
#if !PRIMITIVE_DEBUGGER_SUPPORT_DEBUG_VISUALIZATIONS
|
|
.IsEnabled(false)
|
|
#endif
|
|
+SHorizontalBox::Slot()
|
|
.HAlign(HAlign_Left)
|
|
.FillWidth(LabelColumnWidth)
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(LOCTEXT("DetailPanel_ShowBonesLabel", "Show Bones"))
|
|
.Justification(ETextJustify::Left)
|
|
.ToolTipText(ShowBonesTooltip)
|
|
]
|
|
+SHorizontalBox::Slot()
|
|
.HAlign(HAlign_Right)
|
|
.FillWidth(ValueColumnWidth)
|
|
[
|
|
SNew(SCheckBox)
|
|
.IsChecked(this, &SPrimitiveDebuggerDetailView::ShowDebugBonesState)
|
|
.OnCheckStateChanged(this, &SPrimitiveDebuggerDetailView::OnToggleDebugBones)
|
|
.ToolTipText(ShowBonesTooltip)
|
|
]
|
|
];
|
|
AdvancedOptionsWidget->AddSlot()
|
|
.Padding(Margin)
|
|
.AutoHeight()
|
|
[
|
|
SNew(SHorizontalBox)
|
|
.Visibility(this, &SPrimitiveDebuggerDetailView::OptionVisibilityForceLOD)
|
|
+SHorizontalBox::Slot()
|
|
.HAlign(HAlign_Left)
|
|
.FillWidth(LabelColumnWidth)
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(LOCTEXT("DetailPanel_ForceLODLabel", "Force LOD"))
|
|
.Justification(ETextJustify::Left)
|
|
.ToolTipText(ForcedLODTooltip)
|
|
]
|
|
+SHorizontalBox::Slot()
|
|
.HAlign(HAlign_Right)
|
|
.FillWidth(ValueColumnWidth)
|
|
[
|
|
SNew(SCheckBox)
|
|
.IsChecked(this, &SPrimitiveDebuggerDetailView::ForceLODState)
|
|
.OnCheckStateChanged(this, &SPrimitiveDebuggerDetailView::OnToggleForceLOD)
|
|
.ToolTipText(ForcedLODTooltip)
|
|
]
|
|
];
|
|
AdvancedOptionsWidget->AddSlot()
|
|
.Padding(Margin)
|
|
.AutoHeight()
|
|
[
|
|
SNew(SHorizontalBox)
|
|
.Visibility(this, &SPrimitiveDebuggerDetailView::OptionVisibilityForceLOD)
|
|
+SHorizontalBox::Slot()
|
|
.HAlign(HAlign_Left)
|
|
.FillWidth(LabelColumnWidth * 2)
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(LOCTEXT("DetailPanel_ForcedLODIndexLabel", "Forced LOD Index"))
|
|
.Justification(ETextJustify::Left)
|
|
.ToolTipText(ForcedLODIndexTooltip)
|
|
]
|
|
+SHorizontalBox::Slot()
|
|
.HAlign(HAlign_Fill)
|
|
.FillWidth(ValueColumnWidth / 2)
|
|
[
|
|
SNew(SNumericEntryBox<int>)
|
|
.Value(this, &SPrimitiveDebuggerDetailView::GetSelectedForcedLODValue)
|
|
.MinValue(0)
|
|
.MaxValue(this, &SPrimitiveDebuggerDetailView::GetSelectedForcedLODSliderMaxValue)
|
|
.MinSliderValue(0)
|
|
.MaxSliderValue(this, &SPrimitiveDebuggerDetailView::GetSelectedForcedLODSliderMaxValue)
|
|
.Delta(1)
|
|
.AllowSpin(true)
|
|
.AllowWheel(true)
|
|
.WheelStep(1)
|
|
.UndeterminedString(LOCTEXT("DetailPanel_AutomaticLODPlaceholder", "Auto"))
|
|
.IsEnabled(this, &SPrimitiveDebuggerDetailView::IsForceLODIndexSliderEnabled)
|
|
.OnValueChanged(this, &SPrimitiveDebuggerDetailView::HandleForceLOD)
|
|
.ToolTipText(ForcedLODIndexTooltip)
|
|
.Justification(ETextJustify::Right)
|
|
]
|
|
];
|
|
AdvancedOptionsWidget->AddSlot()
|
|
.Padding(Margin)
|
|
.AutoHeight()
|
|
[
|
|
SNew(SHorizontalBox)
|
|
.Visibility(this, &SPrimitiveDebuggerDetailView::OptionVisibilityForceDisableNanite)
|
|
+SHorizontalBox::Slot()
|
|
.HAlign(HAlign_Left)
|
|
.FillWidth(LabelColumnWidth)
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(LOCTEXT("DetailPanel_ForceDisableNaniteLabel", "Force Disable Nanite"))
|
|
.Justification(ETextJustify::Left)
|
|
.ToolTipText(ForceDisableNaniteTooltip)
|
|
]
|
|
+SHorizontalBox::Slot()
|
|
.HAlign(HAlign_Right)
|
|
.FillWidth(ValueColumnWidth)
|
|
[
|
|
SNew(SCheckBox)
|
|
.IsChecked(this, &SPrimitiveDebuggerDetailView::ForceDisableNaniteState)
|
|
.OnCheckStateChanged(this, &SPrimitiveDebuggerDetailView::OnToggleForceDisableNanite)
|
|
.ToolTipText(ForceDisableNaniteTooltip)
|
|
]
|
|
];
|
|
}
|
|
return AdvancedOptionsWidget.ToSharedRef();
|
|
}
|
|
|
|
EVisibility SPrimitiveDebuggerDetailView::OptionVisibilityForceLOD() const
|
|
{
|
|
TOptional<int> NumLODs = GetSelectedNumLODsValue();
|
|
return !bSelectionIsNaniteEnabledThisFrame && NumLODs.IsSet() && NumLODs.GetValue() > 1 ? EVisibility::Visible : EVisibility::Collapsed;
|
|
}
|
|
|
|
EVisibility SPrimitiveDebuggerDetailView::OptionVisibilityForceDisableNanite() const
|
|
{
|
|
return bSelectionSupportsNanite && SelectedAsStaticMesh.IsValid() ? EVisibility::Visible : EVisibility::Collapsed;
|
|
}
|
|
|
|
ECheckBoxState SPrimitiveDebuggerDetailView::ForceLODState() const
|
|
{
|
|
if (!PrimitiveDebugger.IsValid())
|
|
{
|
|
return ECheckBoxState::Undetermined;
|
|
}
|
|
const TSharedPtr<SDrawPrimitiveDebugger> DebuggerInstance = PrimitiveDebugger.Pin();
|
|
const FPrimitiveComponentId Selection = DebuggerInstance->GetCurrentSelectionId();
|
|
return DebuggerInstance->DoesEntryHaveForcedLOD(Selection) ? ECheckBoxState::Checked : ECheckBoxState::Unchecked;
|
|
}
|
|
|
|
bool SPrimitiveDebuggerDetailView::IsForceLODIndexSliderEnabled() const
|
|
{
|
|
const TSharedPtr<SDrawPrimitiveDebugger> DebuggerInstance = PrimitiveDebugger.Pin();
|
|
const FPrimitiveComponentId Selection = DebuggerInstance->GetCurrentSelectionId();
|
|
return DebuggerInstance->DoesEntryHaveForcedLOD(Selection);
|
|
}
|
|
|
|
void SPrimitiveDebuggerDetailView::OnToggleForceLOD(ECheckBoxState state)
|
|
{
|
|
if (!PrimitiveDebugger.IsValid())
|
|
{
|
|
return;
|
|
}
|
|
const TSharedPtr<SDrawPrimitiveDebugger> DebuggerInstance = PrimitiveDebugger.Pin();
|
|
const FPrimitiveComponentId Selection = DebuggerInstance->GetCurrentSelectionId();
|
|
if (state == ECheckBoxState::Unchecked)
|
|
{
|
|
DebuggerInstance->SetForcedLODForEntry(Selection, 0);
|
|
}
|
|
else if (state == ECheckBoxState::Checked)
|
|
{
|
|
DebuggerInstance->SetForcedLODForEntry(Selection, CurrentLOD->LODIndex + 1);
|
|
}
|
|
}
|
|
|
|
ECheckBoxState SPrimitiveDebuggerDetailView::ForceDisableNaniteState() const
|
|
{
|
|
if (SelectedAsStaticMesh.IsValid())
|
|
{
|
|
return SelectedAsStaticMesh->bForceDisableNanite ? ECheckBoxState::Checked : ECheckBoxState::Unchecked;
|
|
}
|
|
return ECheckBoxState::Unchecked;
|
|
}
|
|
|
|
void SPrimitiveDebuggerDetailView::OnToggleForceDisableNanite(ECheckBoxState state)
|
|
{
|
|
if (!PrimitiveDebugger.IsValid())
|
|
{
|
|
return;
|
|
}
|
|
const TSharedPtr<SDrawPrimitiveDebugger> DebuggerInstance = PrimitiveDebugger.Pin();
|
|
const FPrimitiveComponentId Selection = DebuggerInstance->GetCurrentSelectionId();
|
|
if (state != ECheckBoxState::Undetermined)
|
|
{
|
|
DebuggerInstance->SetForceDisabledNaniteForEntry(Selection, state == ECheckBoxState::Checked);
|
|
}
|
|
}
|
|
|
|
EVisibility SPrimitiveDebuggerDetailView::StaticMeshDataVisibility() const
|
|
{
|
|
if (!PrimitiveDebugger.IsValid())
|
|
{
|
|
return EVisibility::Collapsed;
|
|
}
|
|
return SelectedAsStaticMesh.IsValid() ? EVisibility::Visible : EVisibility::Collapsed;
|
|
}
|
|
|
|
void SPrimitiveDebuggerDetailView::HandleForceLOD(int ForcedLOD)
|
|
{
|
|
if (!PrimitiveDebugger.IsValid())
|
|
{
|
|
return;
|
|
}
|
|
const TSharedPtr<SDrawPrimitiveDebugger> DebuggerInstance = PrimitiveDebugger.Pin();
|
|
const FPrimitiveComponentId Selection = DebuggerInstance->GetCurrentSelectionId();
|
|
DebuggerInstance->SetForcedLODForEntry(Selection, ForcedLOD + 1);
|
|
}
|
|
|
|
ECheckBoxState SPrimitiveDebuggerDetailView::ShowDebugBoundsState() const
|
|
{
|
|
#if PRIMITIVE_DEBUGGER_SUPPORT_DEBUG_VISUALIZATIONS
|
|
if (!PrimitiveDebugger.IsValid())
|
|
{
|
|
return ECheckBoxState::Unchecked;
|
|
}
|
|
const TSharedPtr<SDrawPrimitiveDebugger> DebuggerInstance = PrimitiveDebugger.Pin();
|
|
const FPrimitiveComponentId Selection = DebuggerInstance->GetCurrentSelectionId();
|
|
return DebuggerInstance->IsEntryShowingDebugBounds(Selection) ? ECheckBoxState::Checked : ECheckBoxState::Unchecked;
|
|
#else
|
|
return ECheckBoxState::Unchecked;
|
|
#endif
|
|
}
|
|
|
|
void SPrimitiveDebuggerDetailView::OnToggleDebugBounds(ECheckBoxState state)
|
|
{
|
|
#if PRIMITIVE_DEBUGGER_SUPPORT_DEBUG_VISUALIZATIONS
|
|
if (!PrimitiveDebugger.IsValid())
|
|
{
|
|
return;
|
|
}
|
|
const TSharedPtr<SDrawPrimitiveDebugger> DebuggerInstance = PrimitiveDebugger.Pin();
|
|
const FPrimitiveComponentId Selection = DebuggerInstance->GetCurrentSelectionId();
|
|
if (state != ECheckBoxState::Undetermined)
|
|
{
|
|
DebuggerInstance->SetShowDebugBoundsForEntry(Selection, state == ECheckBoxState::Checked);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
EVisibility SPrimitiveDebuggerDetailView::SkeletalMeshDataVisibility() const
|
|
{
|
|
if (!PrimitiveDebugger.IsValid())
|
|
{
|
|
return EVisibility::Collapsed;
|
|
}
|
|
return SelectedAsSkinnedMesh.IsValid() ? EVisibility::Visible : EVisibility::Collapsed;
|
|
}
|
|
|
|
ECheckBoxState SPrimitiveDebuggerDetailView::ShowDebugBonesState() const
|
|
{
|
|
#if PRIMITIVE_DEBUGGER_SUPPORT_DEBUG_VISUALIZATIONS
|
|
if (!PrimitiveDebugger.IsValid())
|
|
{
|
|
return ECheckBoxState::Unchecked;
|
|
}
|
|
return SelectedAsSkinnedMesh.IsValid() && SelectedAsSkinnedMesh->ShouldDrawDebugSkeleton() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked;
|
|
#else
|
|
return ECheckBoxState::Unchecked;
|
|
#endif
|
|
}
|
|
|
|
void SPrimitiveDebuggerDetailView::OnToggleDebugBones(ECheckBoxState state)
|
|
{
|
|
#if PRIMITIVE_DEBUGGER_SUPPORT_DEBUG_VISUALIZATIONS
|
|
if (!PrimitiveDebugger.IsValid())
|
|
{
|
|
return;
|
|
}
|
|
const TSharedPtr<SDrawPrimitiveDebugger> DebuggerInstance = PrimitiveDebugger.Pin();
|
|
const FPrimitiveComponentId Selection = DebuggerInstance->GetCurrentSelectionId();
|
|
if (state != ECheckBoxState::Undetermined)
|
|
{
|
|
DebuggerInstance->SetShowDebugBonesForEntry(Selection, state == ECheckBoxState::Checked);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
EVisibility SPrimitiveDebuggerDetailView::NaniteDataVisibility() const
|
|
{
|
|
return bSelectionSupportsNanite && bSelectionIsNaniteEnabledThisFrame ? EVisibility::Visible : EVisibility::Collapsed;
|
|
}
|
|
|
|
EVisibility SPrimitiveDebuggerDetailView::NonNaniteDataVisibility() const
|
|
{
|
|
return !bSelectionSupportsNanite || !bSelectionIsNaniteEnabledThisFrame ? EVisibility::Visible : EVisibility::Collapsed;
|
|
}
|
|
|
|
SDrawPrimitiveDebugger::~SDrawPrimitiveDebugger()
|
|
{
|
|
SetActiveWorld(nullptr);
|
|
}
|
|
|
|
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 ActorColumn("Actor");
|
|
AddColumn(LOCTEXT("VisbleColumnLabel", "Visible"), VisibilityColumn);
|
|
AddColumn(LOCTEXT("PinnedColumnLabel", "Pinned"), PinColumn);
|
|
AddColumn(LOCTEXT("NameColumnLabel", "Name"), NameColumn);
|
|
AddColumn(LOCTEXT("ActorColumnLabel", "Actor"), ActorColumn);
|
|
|
|
FilterText = FText::GetEmpty();
|
|
IDrawPrimitiveDebugger::Get().CaptureSingleFrame();
|
|
|
|
Table = SNew(SListView<FPrimitiveRowDataPtr>)
|
|
.ListItemsSource(&VisibleEntries)
|
|
.HeaderRow(ColumnHeader)
|
|
.OnGenerateRow(this, &SDrawPrimitiveDebugger::MakeRowWidget)
|
|
.OnSelectionChanged(this, &SDrawPrimitiveDebugger::OnRowSelectionChanged)
|
|
.ExternalScrollbar(VerticalScrollBar)
|
|
.Orientation(Orient_Vertical)
|
|
.ConsumeMouseWheel(EConsumeMouseWheel::Never)
|
|
.SelectionMode(ESelectionMode::SingleToggle);
|
|
|
|
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(LOCTEXT("RefreshButtonLabel", "Refresh"))
|
|
.IsEnabled(this, &SDrawPrimitiveDebugger::CanCaptureSingleFrame)
|
|
.OnClicked(this, &SDrawPrimitiveDebugger::OnRefreshClick)
|
|
]
|
|
+ SHorizontalBox::Slot()
|
|
.Padding(6, 6)
|
|
.AutoWidth()
|
|
[
|
|
SNew(SButton)
|
|
.Text(LOCTEXT("SaveToCSVButtonLabel", "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(SHorizontalBox)
|
|
+ SHorizontalBox::Slot()
|
|
.FillWidth(5)
|
|
[
|
|
SNew(SScrollBox)
|
|
.Orientation(Orient_Vertical)
|
|
.ConsumeMouseWheel(EConsumeMouseWheel::Always)
|
|
+SScrollBox::Slot()
|
|
[
|
|
Table.ToSharedRef()
|
|
]
|
|
]
|
|
+ SHorizontalBox::Slot()
|
|
.FillWidth(5)
|
|
[
|
|
SAssignNew(DetailView, SPrimitiveDebuggerDetailView)
|
|
.PrimitiveDebugger(SharedThis(this))
|
|
.Visibility(this, &SDrawPrimitiveDebugger::DetailsPanelVisibility)
|
|
]
|
|
]
|
|
];
|
|
}
|
|
|
|
void SDrawPrimitiveDebugger::Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime)
|
|
{
|
|
SCompoundWidget::Tick(AllottedGeometry, InCurrentTime, InDeltaTime);
|
|
RedrawAllDebugBounds();
|
|
}
|
|
|
|
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() || !RowData->IsPrimitiveValid())
|
|
{
|
|
continue;
|
|
}
|
|
bool bPassesFilter = false;
|
|
UObject* Component = RowData->ComponentInterface->GetUObject();
|
|
|
|
if (RowData->Name.Contains(ActiveFilterString))
|
|
{
|
|
bPassesFilter = true;
|
|
}
|
|
else if (RowData->Owner.IsValid() &&
|
|
(RowData->Owner->GetClass()->GetName().Contains(ActiveFilterString) ||
|
|
RowData->Owner->GetFullName().Contains(ActiveFilterString)))
|
|
{
|
|
bPassesFilter = true;
|
|
}
|
|
else if (IsValid(Component->GetClass()) && Component->GetClass()->GetName().Contains(ActiveFilterString))
|
|
{
|
|
bPassesFilter = true;
|
|
}
|
|
else
|
|
{
|
|
for (const TWeakObjectPtr<UMaterialInterface> Material : RowData->Materials)
|
|
{
|
|
if (Material.IsValid() && IsValid(Material->GetMaterial()))
|
|
{
|
|
if (Material->GetMaterial()->GetName().Contains(ActiveFilterString))
|
|
{
|
|
bPassesFilter = true;
|
|
break;
|
|
}
|
|
TArray<UTexture*> Textures;
|
|
Material->GetUsedTextures(Textures, EMaterialQualityLevel::Num, false, ERHIFeatureLevel::Num, false);
|
|
const int32 TexCount = Textures.Num();
|
|
for (int t = 0; t < TexCount; t++)
|
|
{
|
|
if (IsValid(Textures[t]) && Textures[t]->GetName().Contains(ActiveFilterString))
|
|
{
|
|
bPassesFilter = true;
|
|
break;
|
|
}
|
|
}
|
|
if (bPassesFilter) break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (bPassesFilter)
|
|
{
|
|
VisibleEntries.Add(RowData);
|
|
}
|
|
}
|
|
}
|
|
SortRows();
|
|
}
|
|
|
|
void SDrawPrimitiveDebugger::SortRows()
|
|
{
|
|
VisibleEntries.Sort([this](FPrimitiveRowDataPtr A, FPrimitiveRowDataPtr B)
|
|
{
|
|
const bool bPinnedA = IsEntryPinned(A->ComponentId);
|
|
const bool bPinnedB = IsEntryPinned(B->ComponentId);
|
|
return (bPinnedA && !bPinnedB) || ((bPinnedA == bPinnedB) && *A < *B); // Put pinned entries first
|
|
});
|
|
}
|
|
|
|
void SDrawPrimitiveDebugger::Refresh()
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_PrimitiveDebuggerRefresh);
|
|
OnRowSelectionChanged(nullptr, ESelectInfo::Direct);
|
|
AvailableEntries.Empty();
|
|
|
|
TSet<FPrimitiveComponentId> OutdatedEntries;
|
|
// Get a list of all existing entry ids, any that are not rediscovered or marked for retention will be dropped
|
|
Entries.GetKeys(OutdatedEntries);
|
|
|
|
// Iterate over the new set of captured primitives to add new entries and check which entries to retain
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_PrimitiveDebuggerRefreshGather);
|
|
FViewDebugInfo::Get().ForEachPrimitive([this, &OutdatedEntries](const FViewDebugInfo::FPrimitiveInfo& Primitive)
|
|
{
|
|
FPrimitiveDebuggerEntry* ExistingEntry = Entries.Find(Primitive.ComponentId);
|
|
if (!ExistingEntry && Primitive.PrimitiveSceneInfo && Primitive.IsPrimitiveValid())
|
|
{
|
|
// Add the new entry
|
|
FPrimitiveDebuggerEntry& NewEntry = Entries.Add(Primitive.ComponentId, FPrimitiveDebuggerEntry(Primitive));
|
|
const UPrimitiveComponent* Component = Primitive.ComponentInterface->GetUObject<UPrimitiveComponent>();
|
|
if (IsValid(Component) && !Component->GetVisibleFlag())
|
|
{
|
|
NewEntry.bHidden = true;
|
|
NewEntry.bRetainDuringRefresh = true;
|
|
}
|
|
AvailableEntries.Add(NewEntry.Data);
|
|
}
|
|
else if (ExistingEntry)
|
|
{
|
|
// Get the latest version of the primitive data and make the entry available
|
|
ExistingEntry->Data = MakeShared<const FViewDebugInfo::FPrimitiveInfo>(Primitive);
|
|
AvailableEntries.Add(ExistingEntry->Data);
|
|
OutdatedEntries.Remove(Primitive.ComponentId);
|
|
}
|
|
});
|
|
}
|
|
|
|
// Of any remaining old entries, add any marked with bRetainDuringRefresh to AvailableEntries and delete the rest
|
|
for (const FPrimitiveComponentId& EntryId : OutdatedEntries)
|
|
{
|
|
const FPrimitiveDebuggerEntry* Entry = Entries.Find(EntryId);
|
|
if (Entry && Entry->bRetainDuringRefresh && Entry->Data.IsValid() && Entry->Data->IsPrimitiveValid())
|
|
{
|
|
AvailableEntries.Add(Entry->Data);
|
|
}
|
|
else if (Entry)
|
|
{
|
|
FlushDebugVisualizationsForEntry(EntryId);
|
|
Entries.Remove(EntryId);
|
|
}
|
|
}
|
|
|
|
SCOPE_CYCLE_COUNTER(STAT_PrimitiveDebuggerUpdateVis);
|
|
UpdateVisibleRows();
|
|
if (Table.IsValid())
|
|
{
|
|
Table->RequestListRefresh();
|
|
}
|
|
}
|
|
|
|
void SDrawPrimitiveDebugger::ClearAllEntries()
|
|
{
|
|
DetailView->ReleaseSelection();
|
|
Selection = nullptr;
|
|
ResetDebuggerChanges();
|
|
Entries.Empty();
|
|
AvailableEntries.Empty();
|
|
UpdateVisibleRows();
|
|
if (Table.IsValid())
|
|
{
|
|
Table->RequestListRefresh();
|
|
}
|
|
}
|
|
|
|
void SDrawPrimitiveDebugger::SetActiveWorld(UWorld* World)
|
|
{
|
|
if (ActiveWorld == World)
|
|
{
|
|
return;
|
|
}
|
|
ResetDebuggerChanges();
|
|
if (ActiveWorld.IsValid())
|
|
{
|
|
ActiveWorld->RemoveOnPreUnregisterAllActorComponentsHandler(ActorComponentsUnregisteredHandle);
|
|
}
|
|
if (IsValid(World))
|
|
{
|
|
ActorComponentsUnregisteredHandle = World->AddOnPreUnregisterAllActorComponentsHandler(FOnPreUnregisterAllActorComponents::FDelegate::CreateRaw(this, &SDrawPrimitiveDebugger::HandleActorCleanup));
|
|
}
|
|
ActiveWorld = World;
|
|
}
|
|
|
|
void SDrawPrimitiveDebugger::RemoveEntry(FPrimitiveRowDataPtr Entry)
|
|
{
|
|
if (!Entry.IsValid())
|
|
{
|
|
return;
|
|
}
|
|
AvailableEntries.Remove(Entry);
|
|
if (Selection && Selection->Data->ComponentId == Entry->ComponentId)
|
|
{
|
|
OnRowSelectionChanged(nullptr, ESelectInfo::Direct);
|
|
}
|
|
FlushDebugVisualizationsForEntry(Entry->ComponentId);
|
|
Entries.Remove(Entry->ComponentId);
|
|
VisibleEntries.Remove(Entry);
|
|
if (Table.IsValid())
|
|
{
|
|
Table->RequestListRefresh();
|
|
}
|
|
}
|
|
|
|
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");
|
|
SHeaderRow::FColumn::FArguments& NewColumnArgs = SHeaderRow::Column(ColumnId)
|
|
.DefaultLabel(Name);
|
|
// Handle columns that can be narrow and fixed
|
|
if (ColumnId.IsEqual(VisibilityColumn) || ColumnId.IsEqual(PinColumn))
|
|
{
|
|
NewColumnArgs = NewColumnArgs.FixedWidth(FontMeasure->Measure(Name, FontInfo).X);
|
|
}
|
|
ColumnHeader->AddColumn(NewColumnArgs);
|
|
}
|
|
|
|
void SDrawPrimitiveDebugger::OnChangeEntryVisibility(ECheckBoxState State, FPrimitiveRowDataPtr Data)
|
|
{
|
|
if (!Data.IsValid() || !Data->IsPrimitiveValid())
|
|
{
|
|
return;
|
|
}
|
|
FPrimitiveDebuggerEntry* Entry = Data.IsValid() ? Entries.Find(Data->ComponentId) : nullptr;
|
|
UPrimitiveComponent* Component = Data->ComponentInterface->GetUObject<UPrimitiveComponent>();
|
|
if (Entry && IsValid(Component) && State != ECheckBoxState::Undetermined)
|
|
{
|
|
Component->SetVisibility(State == ECheckBoxState::Checked);
|
|
if (State == ECheckBoxState::Unchecked)
|
|
{
|
|
Entry->bHidden = true;
|
|
Entry->bRetainDuringRefresh = true;
|
|
}
|
|
else if (State == ECheckBoxState::Checked)
|
|
{
|
|
Entry->bHidden = false;
|
|
Entry->bRetainDuringRefresh = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool SDrawPrimitiveDebugger::IsEntryVisible(FPrimitiveComponentId EntryId) const
|
|
{
|
|
const FPrimitiveDebuggerEntry* Entry = Entries.Find(EntryId);
|
|
return Entry && !Entry->bHidden;
|
|
}
|
|
|
|
bool SDrawPrimitiveDebugger::IsEntryVisible(FPrimitiveRowDataPtr Data) const
|
|
{
|
|
return Data.IsValid() ? IsEntryVisible(Data->ComponentId) : false;
|
|
}
|
|
|
|
void SDrawPrimitiveDebugger::OnRowSelectionChanged(FPrimitiveRowDataPtr InNewSelection, ESelectInfo::Type InSelectInfo)
|
|
{
|
|
if (Selection && Selection->Data == InNewSelection)
|
|
{
|
|
return;
|
|
}
|
|
DetailView->ReleaseSelection();
|
|
|
|
if (Selection)
|
|
{
|
|
Selection->bSelected = false;
|
|
}
|
|
|
|
if (InNewSelection.IsValid())
|
|
{
|
|
Selection = Entries.Find(InNewSelection->ComponentId);
|
|
Selection->bSelected = true;
|
|
}
|
|
else
|
|
{
|
|
Selection = nullptr;
|
|
}
|
|
DetailView->UpdateSelection();
|
|
}
|
|
|
|
EVisibility SDrawPrimitiveDebugger::DetailsPanelVisibility() const
|
|
{
|
|
return Selection && Selection->Data.IsValid() ? EVisibility::Visible : EVisibility::Collapsed;
|
|
}
|
|
|
|
void SDrawPrimitiveDebugger::OnChangeEntryPinned(ECheckBoxState State, FPrimitiveRowDataPtr Data)
|
|
{
|
|
if (State != ECheckBoxState::Undetermined && Data.IsValid())
|
|
{
|
|
FPrimitiveDebuggerEntry* Entry = Entries.Find(Data->ComponentId);
|
|
Entry->bPinned = State == ECheckBoxState::Checked;
|
|
}
|
|
UpdateVisibleRows();
|
|
if (Table.IsValid())
|
|
{
|
|
Table->RequestListRefresh();
|
|
}
|
|
}
|
|
|
|
bool SDrawPrimitiveDebugger::IsEntryPinned(FPrimitiveComponentId EntryId) const
|
|
{
|
|
const FPrimitiveDebuggerEntry* Entry = Entries.Find(EntryId);
|
|
return Entry && Entry->bPinned;
|
|
}
|
|
|
|
bool SDrawPrimitiveDebugger::IsEntryPinned(FPrimitiveRowDataPtr Data) const
|
|
{
|
|
return Data.IsValid() ? IsEntryPinned(Data->ComponentId) : false;
|
|
}
|
|
|
|
void SDrawPrimitiveDebugger::SetForcedLODForEntry(FPrimitiveComponentId EntryId, int32 NewForcedLOD)
|
|
{
|
|
if (FPrimitiveDebuggerEntry* Entry = Entries.Find(EntryId))
|
|
{
|
|
if (!Entry->Data.IsValid() || !Entry->Data->IsPrimitiveValid())
|
|
{
|
|
return;
|
|
}
|
|
if (UStaticMeshComponent* StaticMesh = Cast<UStaticMeshComponent>(Entry->Data->ComponentUObject.Get()))
|
|
{
|
|
if (StaticMesh->ForcedLodModel == NewForcedLOD)
|
|
{
|
|
return; // No change necessary
|
|
}
|
|
|
|
if (!Entry->bHasForcedLOD)
|
|
{
|
|
// Record the original desired forced LOD of the model
|
|
Entry->DesiredForcedLOD = StaticMesh->ForcedLodModel;
|
|
Entry->bHasForcedLOD = true;
|
|
}
|
|
|
|
StaticMesh->SetForcedLodModel(NewForcedLOD);
|
|
|
|
}
|
|
else if (USkinnedMeshComponent* SkinnedMesh = Cast<USkinnedMeshComponent>(Entry->Data->ComponentUObject.Get()))
|
|
{
|
|
if (SkinnedMesh->GetForcedLOD() == NewForcedLOD)
|
|
{
|
|
return; // No change necessary
|
|
}
|
|
if (!Entry->bHasForcedLOD)
|
|
{
|
|
// Record the original desired forced LOD of the model
|
|
Entry->DesiredForcedLOD = SkinnedMesh->GetForcedLOD();
|
|
Entry->bHasForcedLOD = true;
|
|
}
|
|
|
|
SkinnedMesh->SetForcedLOD(NewForcedLOD);
|
|
|
|
}
|
|
else return;
|
|
|
|
if (NewForcedLOD == Entry->DesiredForcedLOD)
|
|
{
|
|
// The value has been reset to the desired original value, we should no longer consider consider the LOD
|
|
// to have been modified by the debugger
|
|
Entry->bHasForcedLOD = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
void SDrawPrimitiveDebugger::ResetForcedLODForEntry(FPrimitiveComponentId EntryId)
|
|
{
|
|
if (FPrimitiveDebuggerEntry* Entry = Entries.Find(EntryId))
|
|
{
|
|
if (!Entry->bHasForcedLOD || !Entry->Data.IsValid() || !Entry->Data->IsPrimitiveValid())
|
|
{
|
|
return;
|
|
}
|
|
if (UStaticMeshComponent* StaticMesh = Cast<UStaticMeshComponent>(Entry->Data->ComponentUObject.Get()))
|
|
{
|
|
StaticMesh->SetForcedLodModel(Entry->DesiredForcedLOD);
|
|
}
|
|
else if (USkinnedMeshComponent* SkinnedMesh = Cast<USkinnedMeshComponent>(Entry->Data->ComponentUObject.Get()))
|
|
{
|
|
SkinnedMesh->SetForcedLOD(Entry->DesiredForcedLOD);
|
|
}
|
|
Entry->bHasForcedLOD = false;
|
|
}
|
|
}
|
|
|
|
bool SDrawPrimitiveDebugger::DoesEntryHaveForcedLOD(FPrimitiveComponentId EntryId) const
|
|
{
|
|
if (const FPrimitiveDebuggerEntry* Entry = Entries.Find(EntryId))
|
|
{
|
|
bool bHasForcedLOD = false;
|
|
if (const UStaticMeshComponent* StaticMesh = Cast<UStaticMeshComponent>(Entry->Data->ComponentUObject.Get()))
|
|
{
|
|
bHasForcedLOD = StaticMesh->ForcedLodModel != 0;
|
|
}
|
|
else if (const USkinnedMeshComponent* SkinnedMesh = Cast<USkinnedMeshComponent>(Entry->Data->ComponentUObject.Get()))
|
|
{
|
|
bHasForcedLOD = SkinnedMesh->GetForcedLOD() != 0;
|
|
}
|
|
return bHasForcedLOD;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void SDrawPrimitiveDebugger::SetForceDisabledNaniteForEntry(FPrimitiveComponentId EntryId, bool bForceDisableNanite)
|
|
{
|
|
if (FPrimitiveDebuggerEntry* Entry = Entries.Find(EntryId))
|
|
{
|
|
if (!Entry->Data.IsValid() || !Entry->Data->IsPrimitiveValid())
|
|
{
|
|
return;
|
|
}
|
|
UStaticMeshComponent* StaticMesh = Cast<UStaticMeshComponent>(Entry->Data->ComponentUObject.Get());
|
|
if (!IsValid(StaticMesh))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (StaticMesh->bForceDisableNanite == bForceDisableNanite)
|
|
{
|
|
return; // No change necessary
|
|
}
|
|
if (!Entry->bHasForceDisabledNanite)
|
|
{
|
|
// Record the original value of force disable nanite
|
|
Entry->bDesiredForceDisabledNaniteState = StaticMesh->bForceDisableNanite;
|
|
Entry->bHasForceDisabledNanite = true;
|
|
}
|
|
|
|
StaticMesh->SetForceDisableNanite(bForceDisableNanite);
|
|
|
|
if (bForceDisableNanite == Entry->bDesiredForceDisabledNaniteState)
|
|
{
|
|
// The value has been reset to the desired original value, we should consider this value no longer modified
|
|
Entry->bHasForceDisabledNanite = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
void SDrawPrimitiveDebugger::SetShowDebugBoundsForEntry(FPrimitiveComponentId EntryId, bool bShowDebugBounds)
|
|
{
|
|
#if PRIMITIVE_DEBUGGER_SUPPORT_DEBUG_VISUALIZATIONS
|
|
if (ActiveWorld.IsValid() && EntryId.IsValid())
|
|
{
|
|
FPrimitiveDebuggerEntry* Entry = Entries.Find(EntryId);
|
|
if (Entry && bShowDebugBounds != Entry->bShowingDebugBounds)
|
|
{
|
|
if (ULineBatchComponent* const LineBatcher = ActiveWorld->GetLineBatcher(UWorld::ELineBatcherType::WorldPersistent))
|
|
{
|
|
if (bShowDebugBounds && Entry->Data.IsValid() && Entry->Data->IsPrimitiveValid())
|
|
{
|
|
const FBoxSphereBounds Bounds = Entry->Data->ComponentInterface->GetBounds();
|
|
const FColor Color = Entry->bSelected ? FColor::Yellow : FColor::Orange;
|
|
const float Thickness = Entry->bSelected ? 1.25f : 1.0f;
|
|
LineBatcher->DrawBox(Bounds.Origin, Bounds.BoxExtent, Entry->Data->ComponentInterface->GetTransform().GetRotation(),
|
|
Color, -1.0f, SDPG_World, Thickness, EntryId.PrimIDValue);
|
|
Entry->bShowingDebugBounds = true;
|
|
EntriesShowingDebugBounds.Add(EntryId);
|
|
}
|
|
else
|
|
{
|
|
LineBatcher->ClearBatch(EntryId.PrimIDValue);
|
|
Entry->bShowingDebugBounds = false;
|
|
EntriesShowingDebugBounds.Remove(EntryId);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
bool SDrawPrimitiveDebugger::IsEntryShowingDebugBounds(FPrimitiveComponentId EntryId) const
|
|
{
|
|
#if PRIMITIVE_DEBUGGER_SUPPORT_DEBUG_VISUALIZATIONS
|
|
const FPrimitiveDebuggerEntry* Entry = Entries.Find(EntryId);
|
|
return Entry && Entry->bShowingDebugBounds;
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
void SDrawPrimitiveDebugger::RedrawAllDebugBounds() const
|
|
{
|
|
#if PRIMITIVE_DEBUGGER_SUPPORT_DEBUG_VISUALIZATIONS
|
|
if (ActiveWorld.IsValid())
|
|
{
|
|
if (ULineBatchComponent* const LineBatcher = ActiveWorld->GetLineBatcher(UWorld::ELineBatcherType::WorldPersistent))
|
|
{
|
|
for (const FPrimitiveComponentId EntryId : EntriesShowingDebugBounds)
|
|
{
|
|
const FPrimitiveDebuggerEntry* Entry = Entries.Find(EntryId);
|
|
if (Entry && Entry->Data.IsValid() && Entry->Data->IsPrimitiveValid())
|
|
{
|
|
const FBoxSphereBounds Bounds = Entry->Data->ComponentInterface->GetBounds();
|
|
LineBatcher->ClearBatch(EntryId.PrimIDValue);
|
|
const FColor Color = Entry->bSelected ? FColor::Yellow : FColor::Orange;
|
|
const float Thickness = Entry->bSelected ? 1.25f : 1.0f;
|
|
LineBatcher->DrawBox(Bounds.Origin, Bounds.BoxExtent, Entry->Data->ComponentInterface->GetTransform().GetRotation(),
|
|
Color, -1.0f, SDPG_World, Thickness, EntryId.PrimIDValue);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void SDrawPrimitiveDebugger::FlushAllDebugBounds()
|
|
{
|
|
#if PRIMITIVE_DEBUGGER_SUPPORT_DEBUG_VISUALIZATIONS
|
|
if (ActiveWorld.IsValid())
|
|
{
|
|
if (ULineBatchComponent* const LineBatcher = ActiveWorld->GetLineBatcher(UWorld::ELineBatcherType::WorldPersistent))
|
|
{
|
|
for (const FPrimitiveComponentId Entry : EntriesShowingDebugBounds)
|
|
{
|
|
LineBatcher->ClearBatch(Entry.PrimIDValue);
|
|
}
|
|
}
|
|
}
|
|
EntriesShowingDebugBounds.Empty();
|
|
#endif
|
|
}
|
|
|
|
void SDrawPrimitiveDebugger::SetShowDebugBonesForEntry(FPrimitiveComponentId EntryId, bool bShowDebugBones)
|
|
{
|
|
#if PRIMITIVE_DEBUGGER_SUPPORT_DEBUG_VISUALIZATIONS
|
|
if (FPrimitiveDebuggerEntry* Entry = Entries.Find(EntryId))
|
|
{
|
|
if (!Entry->Data.IsValid() || !Entry->Data->IsPrimitiveValid())
|
|
{
|
|
return;
|
|
}
|
|
|
|
USkinnedMeshComponent* SkinnedMesh = Cast<USkinnedMeshComponent>(Entry->Data->ComponentUObject.Get());
|
|
if (!IsValid(SkinnedMesh))
|
|
{
|
|
return;
|
|
}
|
|
|
|
const bool bCurrentState = SkinnedMesh->ShouldDrawDebugSkeleton();
|
|
if (bCurrentState && !bShowDebugBones)
|
|
{
|
|
SkinnedMesh->SetDebugDrawColor(FLinearColor::Transparent);
|
|
SkinnedMesh->SetDrawDebugSkeleton(false);
|
|
SkinnedMesh->MarkRenderStateDirty();
|
|
}
|
|
else if (!bCurrentState && bShowDebugBones)
|
|
{
|
|
SkinnedMesh->SetDebugDrawColor(Entry->bSelected ? FLinearColor::Yellow : FLinearColor(FColor::Orange));
|
|
SkinnedMesh->SetDrawDebugSkeleton(true);
|
|
SkinnedMesh->MarkRenderStateDirty();
|
|
}
|
|
Entry->bShowingDebugBones = bShowDebugBones;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
bool SDrawPrimitiveDebugger::IsEntryShowingDebugBones(FPrimitiveComponentId EntryId) const
|
|
{
|
|
#if PRIMITIVE_DEBUGGER_SUPPORT_DEBUG_VISUALIZATIONS
|
|
const FPrimitiveDebuggerEntry* Entry = Entries.Find(EntryId);
|
|
return Entry ? Entry->bShowingDebugBones : false;
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
void SDrawPrimitiveDebugger::FlushAllDebugBones()
|
|
{
|
|
#if PRIMITIVE_DEBUGGER_SUPPORT_DEBUG_VISUALIZATIONS
|
|
for (auto& [PrimitiveId, Entry] : Entries)
|
|
{
|
|
if (!Entry.bShowingDebugBones || !Entry.Data.IsValid() || !Entry.Data->IsPrimitiveValid())
|
|
{
|
|
continue;
|
|
}
|
|
if (USkinnedMeshComponent* SkinnedMesh = Cast<USkinnedMeshComponent>(Entry.Data->ComponentUObject.Get()))
|
|
{
|
|
SkinnedMesh->SetDebugDrawColor(FLinearColor::Transparent);
|
|
SkinnedMesh->SetDrawDebugSkeleton(false);
|
|
SkinnedMesh->MarkRenderStateDirty();
|
|
}
|
|
Entry.bShowingDebugBones = false;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void SDrawPrimitiveDebugger::FlushDebugVisualizationsForEntry(FPrimitiveComponentId EntryId)
|
|
{
|
|
SetShowDebugBoundsForEntry(EntryId, false);
|
|
SetShowDebugBonesForEntry(EntryId, false);
|
|
}
|
|
|
|
void SDrawPrimitiveDebugger::FlushAllDebugVisualizations()
|
|
{
|
|
FlushAllDebugBounds();
|
|
FlushAllDebugBones();
|
|
}
|
|
|
|
void SDrawPrimitiveDebugger::ResetDebuggerChanges()
|
|
{
|
|
for (auto& [PrimitiveId, Entry] : Entries)
|
|
{
|
|
if (!Entry.Data.IsValid() || !Entry.Data->IsPrimitiveValid())
|
|
{
|
|
continue;
|
|
}
|
|
UPrimitiveComponent* Component = Entry.Data->ComponentInterface->GetUObject<UPrimitiveComponent>();
|
|
if (Entry.bHidden)
|
|
{
|
|
Component->SetVisibility(true);
|
|
Entry.bHidden = false;
|
|
Entry.bRetainDuringRefresh = false;
|
|
}
|
|
if (Entry.bHasForcedLOD)
|
|
{
|
|
ResetForcedLODForEntry(PrimitiveId);
|
|
}
|
|
if (Entry.bHasForceDisabledNanite)
|
|
{
|
|
SetForceDisabledNaniteForEntry(PrimitiveId, Entry.bDesiredForceDisabledNaniteState);
|
|
}
|
|
#if PRIMITIVE_DEBUGGER_SUPPORT_DEBUG_VISUALIZATIONS
|
|
if (Entry.bShowingDebugBones)
|
|
{
|
|
if (USkinnedMeshComponent* SkinnedMesh = Cast<USkinnedMeshComponent>(Component))
|
|
{
|
|
SkinnedMesh->SetDebugDrawColor(FLinearColor::Transparent);
|
|
SkinnedMesh->SetDrawDebugSkeleton(false);
|
|
SkinnedMesh->MarkRenderStateDirty();
|
|
}
|
|
Entry.bShowingDebugBones = false;
|
|
}
|
|
if (Entry.bShowingDebugBounds)
|
|
{
|
|
if (ActiveWorld.IsValid())
|
|
{
|
|
if (ULineBatchComponent* const LineBatcher = ActiveWorld->GetLineBatcher(UWorld::ELineBatcherType::WorldPersistent))
|
|
{
|
|
LineBatcher->ClearBatch(PrimitiveId.PrimIDValue);
|
|
}
|
|
}
|
|
Entry.bShowingDebugBounds = false;
|
|
}
|
|
#endif
|
|
}
|
|
EntriesShowingDebugBounds.Empty();
|
|
}
|
|
|
|
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();
|
|
}
|
|
}
|
|
|
|
FPrimitiveRowDataPtr SDrawPrimitiveDebugger::GetCurrentSelection() const
|
|
{
|
|
return Selection ? Selection->Data : nullptr;
|
|
}
|
|
|
|
FPrimitiveComponentId SDrawPrimitiveDebugger::GetCurrentSelectionId() const
|
|
{
|
|
return Selection ? Selection->Data->ComponentId : FPrimitiveComponentId();
|
|
}
|
|
|
|
void SDrawPrimitiveDebugger::HandleActorCleanup(AActor* Actor)
|
|
{
|
|
TArray<UPrimitiveComponent*> PrimitiveComponents;
|
|
TSet<FPrimitiveComponentId> PrimitiveComponentIds;
|
|
|
|
Actor->GetComponents<UPrimitiveComponent>(PrimitiveComponents);
|
|
|
|
PrimitiveComponentIds.Reserve(PrimitiveComponents.Num());
|
|
|
|
for (const UPrimitiveComponent* Component : PrimitiveComponents)
|
|
{
|
|
FPrimitiveComponentId ComponentId = Component->GetPrimitiveSceneId();
|
|
FlushDebugVisualizationsForEntry(ComponentId);
|
|
Entries.Remove(ComponentId);
|
|
PrimitiveComponentIds.Add(ComponentId);
|
|
}
|
|
|
|
auto CheckForMatch = [PrimitiveComponentIds](FPrimitiveRowDataPtr Entry) -> bool
|
|
{
|
|
return PrimitiveComponentIds.Contains(Entry->ComponentId);
|
|
};
|
|
|
|
AvailableEntries.RemoveAll(CheckForMatch);
|
|
VisibleEntries.RemoveAll(CheckForMatch);
|
|
|
|
if (Selection && Selection->Data.IsValid() && PrimitiveComponentIds.Contains(Selection->Data->ComponentId))
|
|
{
|
|
this->OnRowSelectionChanged(nullptr, ESelectInfo::Direct);
|
|
}
|
|
|
|
if (Table.IsValid())
|
|
{
|
|
Table->RequestListRefresh();
|
|
}
|
|
}
|
|
|
|
SDrawPrimitiveDebugger::FPrimitiveDebuggerEntry::FPrimitiveDebuggerEntry(const FPrimitiveRowDataPtr& Data) : Data(Data)
|
|
{
|
|
bHidden = false;
|
|
bPinned = false;
|
|
bSelected = false;
|
|
bShowingDebugBones = false;
|
|
bShowingDebugBounds = false;
|
|
bHasForceDisabledNanite = false;
|
|
bHasForcedLOD = false;
|
|
bRetainDuringRefresh = false;
|
|
DesiredForcedLOD = 0;
|
|
bDesiredForceDisabledNaniteState = false;
|
|
}
|
|
|
|
SDrawPrimitiveDebugger::FPrimitiveDebuggerEntry::FPrimitiveDebuggerEntry(const FViewDebugInfo::FPrimitiveInfo& Primitive)
|
|
: FPrimitiveDebuggerEntry(MakeShared<const FViewDebugInfo::FPrimitiveInfo>(Primitive))
|
|
{
|
|
}
|
|
|
|
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)
|
|
{
|
|
static const FName VisibilityColumn("Visible");
|
|
static const FName PinColumn("Pin");
|
|
static const FName NameColumn("Name");
|
|
static const FName ActorClassColumn("ActorClass");
|
|
static const FName ActorColumn("Actor");
|
|
|
|
static const FMargin Margin(5, 2, 5, 2);
|
|
const TSharedRef<FSlateFontMeasure> FontMeasure = FSlateApplication::Get().GetRenderer()->GetFontMeasureService();
|
|
const FSlateFontInfo FontInfo = FSlateFontInfo(FCoreStyle::GetDefaultFont(), UDrawPrimitiveDebuggerUserSettings::GetFontSize());
|
|
|
|
SCOPE_CYCLE_COUNTER(STAT_PrimitiveDebuggerMakeCell);
|
|
SDrawPrimitiveDebugger* DrawPrimitiveDebuggerPtr = DrawPrimitiveDebugger.Pin().Get();
|
|
if (DrawPrimitiveDebuggerPtr && RowDataPtr.IsValid())
|
|
{
|
|
FText Value;
|
|
if (InColumnId.IsEqual(VisibilityColumn))
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_PrimitiveDebuggerMakeCellVisible);
|
|
return SNew(SBox)
|
|
.Padding(Margin)
|
|
.HAlign(HAlign_Center)
|
|
[
|
|
SNew(SCheckBox)
|
|
.IsChecked(this, &SDrawPrimitiveDebuggerListViewRow::IsVisible)
|
|
.OnCheckStateChanged(DrawPrimitiveDebuggerPtr, &SDrawPrimitiveDebugger::OnChangeEntryVisibility, RowDataPtr)
|
|
.HAlign(HAlign_Center)
|
|
];
|
|
}
|
|
if (InColumnId.IsEqual(PinColumn))
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_PrimitiveDebuggerMakeCellPinned);
|
|
return SNew(SBox)
|
|
.Padding(Margin)
|
|
.HAlign(HAlign_Center)
|
|
[
|
|
SNew(SCheckBox)
|
|
.IsChecked(this, &SDrawPrimitiveDebuggerListViewRow::IsPinned)
|
|
.OnCheckStateChanged(DrawPrimitiveDebuggerPtr, &SDrawPrimitiveDebugger::OnChangeEntryPinned, RowDataPtr)
|
|
.HAlign(HAlign_Center)
|
|
];
|
|
}
|
|
if (InColumnId.IsEqual(NameColumn))
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_PrimitiveDebuggerMakeCellName);
|
|
Value = FText::FromString(RowDataPtr->Name);
|
|
}
|
|
else if (InColumnId.IsEqual(ActorClassColumn))
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_PrimitiveDebuggerMakeCellActorClass);
|
|
Value = RowDataPtr->Owner.IsValid() && IsValid(RowDataPtr->Owner->GetClass()) ?
|
|
FText::FromString(RowDataPtr->Owner->GetClass()->GetName()) :
|
|
InvalidTextValue;
|
|
}
|
|
else if (InColumnId.IsEqual(ActorColumn))
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_PrimitiveDebuggerMakeCellActor);
|
|
Value = RowDataPtr->Owner.IsValid() ?
|
|
FText::FromString(RowDataPtr->GetOwnerName()) :
|
|
InvalidTextValue;
|
|
}
|
|
else
|
|
{
|
|
// Invalid Column name
|
|
return SNullWidget::NullWidget;
|
|
}
|
|
return SNew(SBox)
|
|
.Padding(Margin)
|
|
.HAlign(HAlign_Fill)
|
|
[
|
|
SNew(STextBlock)
|
|
.ColorAndOpacity(FSlateColor::UseForeground())
|
|
.Text(Value)
|
|
.ToolTipText(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
|
|
|
|
#undef LOCTEXT_NAMESPACE
|
|
|
|
#endif |