Restore Viewport debug text

Adds the AtomViewportDisplayInfo Gem which renders debug text to the default viewport context depending on the value of r_DisplayInfo.
The gem is flagged as a dependency of AtomBridge, so all Atom projects will consume it by default.
This commit is contained in:
nvsickle
2021-05-11 18:27:47 -07:00
parent cdca18ca25
commit 6fcd5c7817
11 changed files with 568 additions and 0 deletions
@@ -29,6 +29,8 @@ ly_add_target(
Gem::Atom_RPI.Public
Gem::Atom_Bootstrap.Headers
Legacy::CryCommon
RUNTIME_DEPENDENCIES
Gem::AtomViewportDisplayInfo
)
ly_add_target(
@@ -68,5 +70,7 @@ if(PAL_TRAIT_BUILD_HOST_TOOLS)
AZ::AssetBuilderSDK
Gem::Atom_Utils.Static
Gem::Atom_AtomBridge.Static
RUNTIME_DEPENDENCIES
Gem::AtomViewportDisplayInfo
)
endif()
@@ -0,0 +1,12 @@
#
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
# its licensors.
#
# For complete copyright and license terms please see the LICENSE at the root of this
# distribution (the "License"). All use of this software is governed by the License,
# or, if provided, by the license below or the license accompanying this file. Do not
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
add_subdirectory(Code)
@@ -0,0 +1,50 @@
#
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
# its licensors.
#
# For complete copyright and license terms please see the LICENSE at the root of this
# distribution (the "License"). All use of this software is governed by the License,
# or, if provided, by the license below or the license accompanying this file. Do not
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
ly_add_target(
NAME AtomViewportDisplayInfo GEM_MODULE
NAMESPACE Gem
FILES_CMAKE
atomviewportdisplayinfo_files.cmake
INCLUDE_DIRECTORIES
PRIVATE
Source
BUILD_DEPENDENCIES
PRIVATE
AZ::AzCore
AZ::AtomCore
Legacy::CryCommon
Gem::Atom_RHI.Reflect
Gem::Atom_RPI.Public
)
################################################################################
# Tests
################################################################################
if(PAL_TRAIT_BUILD_TESTS_SUPPORTED)
ly_add_target(
NAME AtomViewportDisplayInfo.Tests ${PAL_TRAIT_TEST_TARGET_TYPE}
NAMESPACE Gem
FILES_CMAKE
atomviewportdisplayinfo_test_files.cmake
INCLUDE_DIRECTORIES
PRIVATE
Include
Tests
BUILD_DEPENDENCIES
PRIVATE
AZ::AzTest
)
ly_add_googletest(
NAME Gem::AtomViewportDisplayInfo.Tests
)
endif()
@@ -0,0 +1,289 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include "AtomViewportDisplayInfoSystemComponent.h"
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzCore/Serialization/EditContextConstants.inl>
#include <AzCore/Console/IConsole.h>
#include <AzCore/Interface/Interface.h>
#include <Atom/RPI.Public/ViewportContextBus.h>
#include <Atom/RPI.Public/ViewportContext.h>
#include <Atom/RPI.Public/View.h>
#include <Atom/RHI/Factory.h>
#include <CrySystem/MemoryManager.h>
#include <CryCommon/ISystem.h>
#include <CryCommon/IConsole.h>
AZ_CVAR(float, r_fpsInterval, 1.0f, nullptr, AZ::ConsoleFunctorFlags::DontReplicate,
"The time period over which to calculate the framerate for r_displayInfo");
namespace AZ::Render
{
static constexpr int DisplayInfoLevelNone = 0;
static constexpr int DisplayInfoLevelNormal = 1;
static constexpr int DisplayInfoLevelFull = 2;
static constexpr int DisplayInfoLevelCompact = 3;
void AtomViewportDisplayInfoSystemComponent::Reflect(AZ::ReflectContext* context)
{
if (AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context))
{
serialize->Class<AtomViewportDisplayInfoSystemComponent, AZ::Component>()
->Version(0)
;
if (AZ::EditContext* ec = serialize->GetEditContext())
{
ec->Class<AtomViewportDisplayInfoSystemComponent>("Viewport Display Info", "Manages debug viewport information through r_DisplayInfo")
->ClassElement(Edit::ClassElements::EditorData, "")
->Attribute(Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("System", 0xc94d118b))
->Attribute(Edit::Attributes::AutoExpand, true)
;
}
}
}
void AtomViewportDisplayInfoSystemComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
{
provided.push_back(AZ_CRC("ViewportDisplayInfoService"));
}
void AtomViewportDisplayInfoSystemComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
{
incompatible.push_back(AZ_CRC("ViewportDisplayInfoService"));
}
void AtomViewportDisplayInfoSystemComponent::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required)
{
required.push_back(AZ_CRC("RPISystem", 0xf2add773));
}
void AtomViewportDisplayInfoSystemComponent::GetDependentServices([[maybe_unused]] AZ::ComponentDescriptor::DependencyArrayType& dependent)
{
}
void AtomViewportDisplayInfoSystemComponent::Activate()
{
AZ::Name apiName = AZ::RHI::Factory::Get().GetName();
if (!apiName.IsEmpty())
{
m_rendererDescription = AZStd::string::format("Atom using %s RHI", apiName.GetCStr());
}
CrySystemEventBus::Handler::BusConnect();
AZ::RPI::ViewportContextNotificationBus::Handler::BusConnect(
AZ::RPI::ViewportContextRequests::Get()->GetDefaultViewportContextName());
}
void AtomViewportDisplayInfoSystemComponent::Deactivate()
{
AZ::RPI::ViewportContextNotificationBus::Handler::BusDisconnect();
CrySystemEventBus::Handler::BusDisconnect();
}
AZ::RPI::ViewportContextPtr AtomViewportDisplayInfoSystemComponent::GetViewportContext() const
{
return AZ::RPI::ViewportContextRequests::Get()->GetDefaultViewportContext();
}
void AtomViewportDisplayInfoSystemComponent::DrawLine(AZStd::string_view line, AZ::Color color)
{
m_drawParams.m_color = color;
AzFramework::FontDrawInterface* fontDrawInterface =
AZ::Interface<AzFramework::FontQueryInterface>::Get()->GetDefaultFontDrawInterface();
AZ::Vector2 textSize = fontDrawInterface->GetTextSize(m_drawParams, line);
fontDrawInterface->DrawScreenAlignedText2d(m_drawParams, line);
m_drawParams.m_position.SetY(m_drawParams.m_position.GetY() + textSize.GetY() + m_lineSpacing);
}
void AtomViewportDisplayInfoSystemComponent::OnRenderTick()
{
AzFramework::FontDrawInterface* fontDrawInterface =
AZ::Interface<AzFramework::FontQueryInterface>::Get()->GetDefaultFontDrawInterface();
AZ::RPI::ViewportContextPtr viewportContext = GetViewportContext();
if (!fontDrawInterface || !viewportContext || !viewportContext->GetRenderScene())
{
return;
}
m_fpsInterval = AZStd::chrono::seconds(r_fpsInterval);
UpdateFramerate();
if (!m_displayInfoCVar)
{
return;
}
int displayLevel = m_displayInfoCVar->GetIVal();
if (displayLevel == DisplayInfoLevelNone)
{
return;
}
m_drawParams.m_drawViewportId = viewportContext->GetId();
auto viewportSize = viewportContext->GetViewportSize();
m_drawParams.m_position = AZ::Vector3(viewportSize.m_width, 0.f, 1.f);
m_drawParams.m_color = AZ::Colors::White;
m_drawParams.m_scale = AZ::Vector2(0.7f);
m_drawParams.m_hAlign = AzFramework::TextHorizontalAlignment::Right;
m_drawParams.m_monospace = false;
m_drawParams.m_depthTest = false;
m_drawParams.m_virtual800x600ScreenSize = true;
m_drawParams.m_scaleWithWindow = false;
m_drawParams.m_multiline = true;
m_drawParams.m_lineSpacing = 0.5f;
// Calculate line spacing based on the font's actual line height
const float lineHeight = fontDrawInterface->GetTextSize(m_drawParams, " ").GetY();
m_lineSpacing = lineHeight * m_drawParams.m_lineSpacing;
DrawRendererInfo();
if (displayLevel != DisplayInfoLevelCompact)
{
DrawCameraInfo();
DrawMemoryInfo();
}
DrawFramerate();
}
void AtomViewportDisplayInfoSystemComponent::OnCrySystemInitialized(ISystem& system, [[maybe_unused]]const SSystemInitParams& initParams)
{
m_displayInfoCVar = system.GetGlobalEnvironment()->pConsole->GetCVar("r_DisplayInfo");
}
void AtomViewportDisplayInfoSystemComponent::OnCrySystemShutdown([[maybe_unused]]ISystem& system)
{
m_displayInfoCVar = nullptr;
}
void AtomViewportDisplayInfoSystemComponent::DrawRendererInfo()
{
DrawLine(m_rendererDescription, AZ::Colors::Yellow);
}
void AtomViewportDisplayInfoSystemComponent::DrawCameraInfo()
{
AZ::RPI::ViewportContextPtr viewportContext = GetViewportContext();
AZ::RPI::ViewPtr currentView = viewportContext->GetDefaultView();
if (currentView == nullptr)
{
return;
}
auto viewportSize = viewportContext->GetViewportSize();
AzFramework::CameraState cameraState;
AzFramework::SetCameraClippingVolumeFromPerspectiveFovMatrixRH(cameraState, currentView->GetViewToClipMatrix());
const AZ::Transform transform = currentView->GetCameraTransform();
const AZ::Vector3 translation = transform.GetTranslation();
const AZ::Vector3 rotation = transform.GetEulerDegrees();
DrawLine(AZStd::string::format(
"CamPos=%.2f %.2f %.2f Angl=%3.0f %3.0f %4.0f ZN=%.2f ZF=%.0f",
translation.GetX(), translation.GetY(), translation.GetZ(),
rotation.GetX(), rotation.GetY(), rotation.GetZ(),
cameraState.m_nearClip, cameraState.m_farClip
));
}
void AtomViewportDisplayInfoSystemComponent::DrawMemoryInfo()
{
static IMemoryManager::SProcessMemInfo processMemInfo;
// Throttle memory usage updates to avoid potentially expensive memory usage API calls every tick.
constexpr AZStd::chrono::duration<double> memoryUpdateInterval = AZStd::chrono::seconds(0.5);
AZStd::chrono::time_point currentTime = m_fpsHistory.back().Get();
if (m_lastMemoryUpdate.has_value())
{
if (currentTime - m_lastMemoryUpdate.value() > memoryUpdateInterval)
{
if (auto memoryManager = GetISystem()->GetIMemoryManager())
{
memoryManager->GetProcessMemInfo(processMemInfo);
}
}
}
m_lastMemoryUpdate = currentTime;
int peakUsageMB = aznumeric_cast<int>(processMemInfo.PeakPagefileUsage >> 20);
int currentUsageMB = aznumeric_cast<int>(processMemInfo.PagefileUsage >> 20);
DrawLine(AZStd::string::format("Mem=%d Peak=%d", currentUsageMB, peakUsageMB));
}
void AtomViewportDisplayInfoSystemComponent::UpdateFramerate()
{
if (!m_tickRequests)
{
m_tickRequests = AZ::TickRequestBus::FindFirstHandler();
}
if (!m_tickRequests)
{
return;
}
AZ::ScriptTimePoint currentTime = m_tickRequests->GetTimeAtCurrentTick();
// Only keep as much sampling data is is required by our FPS history.
while (!m_fpsHistory.empty() && (currentTime.Get() - m_fpsHistory.front().Get() > m_fpsInterval))
{
m_fpsHistory.pop_front();
}
m_fpsHistory.push_back(currentTime);
}
void AtomViewportDisplayInfoSystemComponent::DrawFramerate()
{
AZStd::chrono::duration<double> actualInterval = AZStd::chrono::seconds(0);
AZStd::optional<AZ::ScriptTimePoint> lastTime;
AZStd::optional<double> minFPS;
AZStd::optional<double> maxFPS;
for (const AZ::ScriptTimePoint& time : m_fpsHistory)
{
if (lastTime.has_value())
{
AZStd::chrono::duration<double> deltaTime = time.Get() - lastTime.value().Get();
if (deltaTime.count() == 0.0)
{
continue;
}
double fps = AZStd::chrono::seconds(1) / deltaTime;
if (!minFPS.has_value())
{
minFPS = fps;
maxFPS = fps;
}
else
{
minFPS = AZStd::min(minFPS.value(), fps);
maxFPS = AZStd::max(maxFPS.value(), fps);
}
actualInterval += deltaTime;
}
lastTime = time;
}
const double averageFPS = aznumeric_cast<double>(m_fpsHistory.size()) / actualInterval.count();
const double frameIntervalSeconds = m_fpsInterval.count();
DrawLine(
AZStd::string::format(
"FPS %.1f [%.0f..%.0f], frame avg over %.1fs",
averageFPS,
minFPS.value_or(0.0),
maxFPS.value_or(0.0),
frameIntervalSeconds),
AZ::Colors::Yellow);
}
} // namespace AZ::Render
@@ -0,0 +1,79 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Component/Component.h>
#include <CryCommon/CrySystemBus.h>
#include <AzCore/Script/ScriptTimePoint.h>
#include <AzFramework/Entity/EntityDebugDisplayBus.h>
#include <AzFramework/Font/FontInterface.h>
#include <Atom/RPI.Public/Base.h>
#include <Atom/RPI.Public/ViewportContextBus.h>
struct ICVar;
namespace AZ
{
class TickRequests;
namespace Render
{
class AtomViewportDisplayInfoSystemComponent
: public AZ::Component
, public AZ::RPI::ViewportContextNotificationBus::Handler
, public CrySystemEventBus::Handler
{
public:
AZ_COMPONENT(AtomViewportDisplayInfoSystemComponent, "{AC32F173-E7E2-4943-8E6C-7C3091978221}");
static void Reflect(AZ::ReflectContext* context);
static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided);
static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible);
static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required);
static void GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent);
protected:
// AZ::Component overrides...
void Activate() override;
void Deactivate() override;
// AZ::RPI::ViewportContextNotificationBus::Handler overrides...
void OnRenderTick() override;
// CrySystemEventBus::Handler overrides...
void OnCrySystemInitialized(ISystem& system, const SSystemInitParams& initParams) override;
void OnCrySystemShutdown(ISystem& system) override;
private:
AZ::RPI::ViewportContextPtr GetViewportContext() const;
void DrawLine(AZStd::string_view line, AZ::Color color = AZ::Colors::White);
void UpdateFramerate();
void DrawRendererInfo();
void DrawCameraInfo();
void DrawMemoryInfo();
void DrawFramerate();
AZStd::string m_rendererDescription;
AzFramework::TextDrawParameters m_drawParams;
float m_lineSpacing;
AZStd::chrono::duration<double> m_fpsInterval = AZStd::chrono::seconds(1);
AZStd::deque<AZ::ScriptTimePoint> m_fpsHistory;
AZStd::optional<AZStd::chrono::system_clock::time_point> m_lastMemoryUpdate;
AZ::TickRequests* m_tickRequests = nullptr;
ICVar* m_displayInfoCVar = nullptr;
};
} // namespace Render
} // namespace AZ
@@ -0,0 +1,51 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/RTTI/RTTI.h>
#include <AzCore/Module/Module.h>
#include "AtomViewportDisplayInfoSystemComponent.h"
namespace AZ
{
namespace Render
{
class AtomViewportDisplayInfoModule
: public AZ::Module
{
public:
AZ_RTTI(AtomViewportDisplayInfoModule, "{B10C0E55-03A1-4A46-AE3E-D3615AEAA659}", AZ::Module);
AZ_CLASS_ALLOCATOR(AtomViewportDisplayInfoModule, AZ::SystemAllocator, 0);
AtomViewportDisplayInfoModule()
: AZ::Module()
{
m_descriptors.insert(m_descriptors.end(), {
AtomViewportDisplayInfoSystemComponent::CreateDescriptor(),
});
}
AZ::ComponentTypeList GetRequiredSystemComponents() const override
{
return AZ::ComponentTypeList{
azrtti_typeid<AtomViewportDisplayInfoSystemComponent>(),
};
}
};
} // namespace Render
} // namespace AZ
// DO NOT MODIFY THIS LINE UNLESS YOU RENAME THE GEM
// The first parameter should be GemName_GemIdLower
// The second should be the fully qualified name of the class above
AZ_DECLARE_MODULE_CLASS(Gem_AtomViewportDisplayInfo, AZ::Render::AtomViewportDisplayInfoModule)
@@ -0,0 +1,20 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzTest/AzTest.h>
AZ_UNIT_TEST_HOOK(DEFAULT_UNIT_TEST_ENV);
TEST(AtomViewportDisplayInfoSanityTest, Sanity)
{
EXPECT_EQ(1, 1);
}
@@ -0,0 +1,16 @@
#
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
# its licensors.
#
# For complete copyright and license terms please see the LICENSE at the root of this
# distribution (the "License"). All use of this software is governed by the License,
# or, if provided, by the license below or the license accompanying this file. Do not
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
set(FILES
Source/AtomViewportDisplayInfoSystemComponent.cpp
Source/AtomViewportDisplayInfoSystemComponent.h
Source/Module.cpp
)
@@ -0,0 +1,14 @@
#
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
# its licensors.
#
# For complete copyright and license terms please see the LICENSE at the root of this
# distribution (the "License"). All use of this software is governed by the License,
# or, if provided, by the license below or the license accompanying this file. Do not
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
set(FILES
Source/Tests/test_Main.cpp
)
@@ -0,0 +1,32 @@
{
"gem_name": "AtomLyIntegration_AtomViewportDisplayInfo",
"Dependencies": [
{
"Uuid": "a218db9eb2114477b46600fea4441a6c",
"VersionConstraints": [
"~>0.1.0"
],
"_comment": "Atom RPI"
},
{
"Uuid": "c7ff89ad6e8b4b45b2fadef2bcf12d6e",
"VersionConstraints": [
"~>0.1.0"
],
"_comment": "Atom_Bootstrap"
}
],
"GemFormatVersion": 4,
"Uuid": "7c255c884bae4046b0640abe3c88cc4c",
"Name": "AtomLyIntegration_AtomViewportDisplayInfo",
"DisplayName": "Atom.AtomViewportDisplayInfo",
"Version": "0.1.0",
"Summary": "Provides a diagnostic viewport overlay for the default O3DE Atom viewport.",
"Tags": ["Atom"],
"IconPath": "preview.png",
"Modules": [
{
"Type": "GameModule"
}
]
}
+1
View File
@@ -16,3 +16,4 @@ add_subdirectory(EMotionFXAtom)
add_subdirectory(AtomFont)
add_subdirectory(TechnicalArt)
add_subdirectory(AtomBridge)
add_subdirectory(AtomViewportDisplayInfo)