diff --git a/Code/CryEngine/CryCommon/IFont.h b/Code/CryEngine/CryCommon/IFont.h index 2706c350a3..7e2a3480b1 100644 --- a/Code/CryEngine/CryCommon/IFont.h +++ b/Code/CryEngine/CryCommon/IFont.h @@ -145,6 +145,7 @@ struct STextDrawContext Vec2 m_size; Vec2i m_requestSize; float m_widthScale; + float m_lineSpacing; float m_clipX; float m_clipY; @@ -175,6 +176,7 @@ struct STextDrawContext , m_size(16.0f, 16.0f) , m_requestSize(static_cast(m_size.x), static_cast(m_size.y)) , m_widthScale(1.0f) + , m_lineSpacing(0.f) , m_clipX(0) , m_clipY(0) , m_clipWidth(0) @@ -209,11 +211,13 @@ struct STextDrawContext void SetTransform(const Matrix34& transform) { m_transform = transform; } void SetBaseState(int baseState) { m_baseState = baseState; } void SetOverrideViewProjMatrices(bool overrideViewProjMatrices) { m_overrideViewProjMatrices = overrideViewProjMatrices; } + void SetLineSpacing(float lineSpacing) { m_lineSpacing = lineSpacing; } float GetCharWidth() const { return m_size.x; } float GetCharHeight() const { return m_size.y; } float GetCharWidthScale() const { return m_widthScale; } int GetFlags() const { return m_drawTextFlags; } + float GetLineSpacing() const { return m_lineSpacing; } bool IsColorOverridden() const { return m_colorOverride.a != 0; } }; diff --git a/Code/Framework/AzCore/AzCore/Script/ScriptTimePoint.h b/Code/Framework/AzCore/AzCore/Script/ScriptTimePoint.h index 7cb81829ef..41d80f42a3 100644 --- a/Code/Framework/AzCore/AzCore/Script/ScriptTimePoint.h +++ b/Code/Framework/AzCore/AzCore/Script/ScriptTimePoint.h @@ -24,9 +24,7 @@ namespace AZ AZ_TYPE_INFO_SPECIALIZE(AZStd::chrono::system_clock::time_point, "{5C48FD59-7267-405D-9C06-1EA31379FE82}"); - /** - * Wrapper that reflects a AZStd::chrono::system_clock::time_point to script. - */ + //! Wrapper that reflects a AZStd::chrono::system_clock::time_point to script. class ScriptTimePoint { public: @@ -38,33 +36,45 @@ namespace AZ explicit ScriptTimePoint(AZStd::chrono::system_clock::time_point timePoint) : m_timePoint(timePoint) {} - AZStd::string ToString() const - { - return AZStd::string::format("Time %llu", m_timePoint.time_since_epoch().count()); - } + //! Formats the time point in a string formatted as: "Time ". + AZStd::string ToString() const; - const AZStd::chrono::system_clock::time_point& Get() { return m_timePoint; } + //! Returns the time point. + const AZStd::chrono::system_clock::time_point& Get() const; - // Returns the time point in seconds - double GetSeconds() const - { - typedef AZStd::chrono::duration double_seconds; - return AZStd::chrono::duration_cast(m_timePoint.time_since_epoch()).count(); - } + //! Returns the time point in seconds + double GetSeconds() const; - // Returns the time point in milliseconds - double GetMilliseconds() const - { - typedef AZStd::chrono::duration double_ms; - return AZStd::chrono::duration_cast(m_timePoint.time_since_epoch()).count(); - } + //! Returns the time point in milliseconds + double GetMilliseconds() const; static void Reflect(ReflectContext* reflection); protected: - AZStd::chrono::system_clock::time_point m_timePoint; }; + + inline AZStd::string ScriptTimePoint::ToString() const + { + return AZStd::string::format("Time %llu", m_timePoint.time_since_epoch().count()); + } + + inline const AZStd::chrono::system_clock::time_point& ScriptTimePoint::Get() const + { + return m_timePoint; + } + + inline double ScriptTimePoint::GetSeconds() const + { + typedef AZStd::chrono::duration double_seconds; + return AZStd::chrono::duration_cast(m_timePoint.time_since_epoch()).count(); + } + + inline double ScriptTimePoint::GetMilliseconds() const + { + typedef AZStd::chrono::duration double_ms; + return AZStd::chrono::duration_cast(m_timePoint.time_since_epoch()).count(); + } } diff --git a/Code/Framework/AzFramework/AzFramework/Font/FontInterface.h b/Code/Framework/AzFramework/AzFramework/Font/FontInterface.h index 7c5bcce6d6..b64b61e22c 100644 --- a/Code/Framework/AzFramework/AzFramework/Font/FontInterface.h +++ b/Code/Framework/AzFramework/AzFramework/Font/FontInterface.h @@ -40,17 +40,18 @@ namespace AzFramework //! Standard parameters for drawing text on screen struct TextDrawParameters { - ViewportId m_drawViewportId = InvalidViewportId; //! Viewport to draw into - AZ::Vector3 m_position; //! world space position for 3d draws, screen space x,y,depth for 2d. - AZ::Color m_color = AZ::Colors::White; //! Color to draw the text - AZ::Vector2 m_scale = AZ::Vector2(1.0f); //! font scale - TextHorizontalAlignment m_hAlign = TextHorizontalAlignment::Left; //! Horizontal text alignment - TextVerticalAlignment m_vAlign = TextVerticalAlignment::Top; //! Vertical text alignment - bool m_monospace = false; //! disable character proportional spacing - bool m_depthTest = false; //! Test character against the depth buffer - bool m_virtual800x600ScreenSize = true; //! Text placement and size are scaled relative to a virtual 800x600 resolution - bool m_scaleWithWindow = false; //! Font gets bigger as the window gets bigger - bool m_multiline = true; //! text respects ascii newline characters + ViewportId m_drawViewportId = InvalidViewportId; //!< Viewport to draw into + AZ::Vector3 m_position; //!< world space position for 3d draws, screen space x,y,depth for 2d. + AZ::Color m_color = AZ::Colors::White; //!< Color to draw the text + AZ::Vector2 m_scale = AZ::Vector2(1.0f); //!< font scale + float m_lineSpacing; //!< Spacing between new lines, as a percentage of m_scale. + TextHorizontalAlignment m_hAlign = TextHorizontalAlignment::Left; //!< Horizontal text alignment + TextVerticalAlignment m_vAlign = TextVerticalAlignment::Top; //!< Vertical text alignment + bool m_monospace = false; //!< disable character proportional spacing + bool m_depthTest = false; //!< Test character against the depth buffer + bool m_virtual800x600ScreenSize = true; //!< Text placement and size are scaled relative to a virtual 800x600 resolution + bool m_scaleWithWindow = false; //!< Font gets bigger as the window gets bigger + bool m_multiline = true; //!< text respects ascii newline characters }; class FontDrawInterface @@ -63,10 +64,13 @@ namespace AzFramework virtual void DrawScreenAlignedText2d( const TextDrawParameters& params, - const AZStd::string_view& string) = 0; + AZStd::string_view text) = 0; virtual void DrawScreenAlignedText3d( const TextDrawParameters& params, - const AZStd::string_view& string) = 0; + AZStd::string_view text) = 0; + virtual AZ::Vector2 GetTextSize( + const TextDrawParameters& params, + AZStd::string_view text) = 0; }; class FontQueryInterface diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ToolsComponents/TransformComponent.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/ToolsComponents/TransformComponent.cpp index 70cad69268..8192e86956 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/ToolsComponents/TransformComponent.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ToolsComponents/TransformComponent.cpp @@ -254,7 +254,7 @@ namespace AzToolsFramework m_localTransformDirty = true; m_worldTransformDirty = true; - if (GetEntity()) + if (const AZ::Entity* entity = GetEntity()) { SetDirty(); @@ -273,6 +273,22 @@ namespace AzToolsFramework { boundsUnion->OnTransformUpdated(GetEntity()); } + // Fire a property changed notification for this component + if (const AZ::Component* component = entity->FindComponent()) + { + PropertyEditorEntityChangeNotificationBus::Event( + GetEntityId(), &PropertyEditorEntityChangeNotifications::OnEntityComponentPropertyChanged, component->GetId()); + } + + // Refresh the property editor if we're selected + bool selected = false; + ToolsApplicationRequestBus::BroadcastResult( + selected, &AzToolsFramework::ToolsApplicationRequests::IsSelected, GetEntityId()); + if (selected) + { + ToolsApplicationEvents::Bus::Broadcast( + &ToolsApplicationEvents::InvalidatePropertyDisplay, AzToolsFramework::Refresh_Values); + } } } diff --git a/Code/Sandbox/Editor/CMakeLists.txt b/Code/Sandbox/Editor/CMakeLists.txt index 759a3a7cf9..c62e05f012 100644 --- a/Code/Sandbox/Editor/CMakeLists.txt +++ b/Code/Sandbox/Editor/CMakeLists.txt @@ -123,6 +123,7 @@ ly_add_target( Gem::Atom_RPI.Public Gem::Atom_Feature_Common.Static Gem::AtomToolsFramework.Static + Gem::AtomViewportDisplayInfo ${additional_dependencies} PUBLIC 3rdParty::AWSNativeSDK::Core diff --git a/Code/Sandbox/Editor/ViewportTitleDlg.cpp b/Code/Sandbox/Editor/ViewportTitleDlg.cpp index 95531e117c..5ccb83cd5b 100644 --- a/Code/Sandbox/Editor/ViewportTitleDlg.cpp +++ b/Code/Sandbox/Editor/ViewportTitleDlg.cpp @@ -13,7 +13,7 @@ // Description : CViewportTitleDlg implementation file - +#if !defined(Q_MOC_RUN) #include "EditorDefs.h" #include "ViewportTitleDlg.h" @@ -36,10 +36,13 @@ #include "UsedResources.h" #include "Include/IObjectManager.h" +#include + AZ_PUSH_DISABLE_DLL_EXPORT_MEMBER_WARNING #include "ui_ViewportTitleDlg.h" AZ_POP_DISABLE_DLL_EXPORT_MEMBER_WARNING +#endif //!defined(Q_MOC_RUN) // CViewportTitleDlg dialog @@ -63,6 +66,32 @@ inline namespace Helpers } } +namespace +{ + class CViewportTitleDlgDisplayInfoHelper + : public QObject + , public AZ::AtomBridge::AtomViewportInfoDisplayNotificationBus::Handler + { + Q_OBJECT + + public: + CViewportTitleDlgDisplayInfoHelper(CViewportTitleDlg* parent) + : QObject(parent) + { + AZ::AtomBridge::AtomViewportInfoDisplayNotificationBus::Handler::BusConnect(); + } + + signals: + void ViewportInfoStatusUpdated(int newIndex); + + private: + void OnViewportInfoDisplayStateChanged(AZ::AtomBridge::ViewportInfoDisplayState state) + { + emit ViewportInfoStatusUpdated(static_cast(state)); + } + }; +} //end anonymous namespace + CViewportTitleDlg::CViewportTitleDlg(QWidget* pParent) : QWidget(pParent) , m_ui(new Ui::ViewportTitleDlg) @@ -115,14 +144,11 @@ void CViewportTitleDlg::OnInitDialog() m_ui->m_toggleHelpersBtn->setChecked(GetIEditor()->GetDisplaySettings()->IsDisplayHelpers()); - ICVar* pDisplayInfo(gEnv->pConsole->GetCVar("r_displayInfo")); - if (pDisplayInfo) - { - SFunctor oFunctor; - oFunctor.Set(OnChangedDisplayInfo, pDisplayInfo, m_ui->m_toggleDisplayInfoBtn); - m_displayInfoCallbackIndex = pDisplayInfo->AddOnChangeFunctor(oFunctor); - OnChangedDisplayInfo(pDisplayInfo, m_ui->m_toggleDisplayInfoBtn); - } + + // Add a child parented to us that listens for r_displayInfo changes. + auto displayInfoHelper = new CViewportTitleDlgDisplayInfoHelper(this); + connect(displayInfoHelper, &CViewportTitleDlgDisplayInfoHelper::ViewportInfoStatusUpdated, this, &CViewportTitleDlg::UpdateDisplayInfo); + UpdateDisplayInfo(); connect(m_ui->m_toggleHelpersBtn, &QToolButton::clicked, this, &CViewportTitleDlg::OnToggleHelpers); connect(m_ui->m_toggleDisplayInfoBtn, &QToolButton::clicked, this, &CViewportTitleDlg::OnToggleDisplayInfo); @@ -156,6 +182,29 @@ void CViewportTitleDlg::OnToggleHelpers() ////////////////////////////////////////////////////////////////////////// void CViewportTitleDlg::OnToggleDisplayInfo() { + AZ::AtomBridge::ViewportInfoDisplayState state = AZ::AtomBridge::ViewportInfoDisplayState::NoInfo; + AZ::AtomBridge::AtomViewportInfoDisplayRequestBus::BroadcastResult( + state, + &AZ::AtomBridge::AtomViewportInfoDisplayRequestBus::Events::GetDisplayState + ); + state = aznumeric_cast( + (aznumeric_cast(state)+1) % aznumeric_cast(AZ::AtomBridge::ViewportInfoDisplayState::Invalid)); + // SetDisplayState will fire OnViewportInfoDisplayStateChanged and notify us, no need to call UpdateDisplayInfo. + AZ::AtomBridge::AtomViewportInfoDisplayRequestBus::Broadcast( + &AZ::AtomBridge::AtomViewportInfoDisplayRequestBus::Events::SetDisplayState, + state + ); +} + +////////////////////////////////////////////////////////////////////////// +void CViewportTitleDlg::UpdateDisplayInfo() +{ + AZ::AtomBridge::ViewportInfoDisplayState state = AZ::AtomBridge::ViewportInfoDisplayState::NoInfo; + AZ::AtomBridge::AtomViewportInfoDisplayRequestBus::BroadcastResult( + state, + &AZ::AtomBridge::AtomViewportInfoDisplayRequestBus::Events::GetDisplayState + ); + m_ui->m_toggleDisplayInfoBtn->setChecked(state != AZ::AtomBridge::ViewportInfoDisplayState::NoInfo); } ////////////////////////////////////////////////////////////////////////// @@ -544,10 +593,6 @@ void CViewportTitleDlg::UpdateCustomPresets(const QString& text, QStringList& cu } } -void CViewportTitleDlg::OnChangedDisplayInfo([[maybe_unused]] ICVar* pDisplayInfo, [[maybe_unused]] QAbstractButton* pDisplayInfoButton) -{ -} - bool CViewportTitleDlg::eventFilter(QObject* object, QEvent* event) { bool consumeEvent = false; @@ -609,4 +654,5 @@ namespace AzToolsFramework } } +#include "ViewportTitleDlg.moc" #include diff --git a/Code/Sandbox/Editor/ViewportTitleDlg.h b/Code/Sandbox/Editor/ViewportTitleDlg.h index 55741636b3..ce2f116d97 100644 --- a/Code/Sandbox/Editor/ViewportTitleDlg.h +++ b/Code/Sandbox/Editor/ViewportTitleDlg.h @@ -60,7 +60,6 @@ public: static void LoadCustomPresets(const QString& section, const QString& keyName, QStringList& outCustompresets); static void SaveCustomPresets(const QString& section, const QString& keyName, const QStringList& custompresets); static void UpdateCustomPresets(const QString& text, QStringList& custompresets); - static void OnChangedDisplayInfo(ICVar* pDisplayInfo, QAbstractButton* pDisplayInfoButton); bool eventFilter(QObject* object, QEvent* event) override; @@ -77,6 +76,7 @@ protected: void OnMaximize(); void OnToggleHelpers(); void OnToggleDisplayInfo(); + void UpdateDisplayInfo(); QString m_title; @@ -87,8 +87,6 @@ protected: QStringList m_customFOVPresets; QStringList m_customAspectRatioPresets; - uint64 m_displayInfoCallbackIndex; - void OnMenuFOVCustom(); void CreateFOVMenu(); diff --git a/Code/Sandbox/Plugins/ComponentEntityEditorPlugin/Objects/ComponentEntityObject.cpp b/Code/Sandbox/Plugins/ComponentEntityEditorPlugin/Objects/ComponentEntityObject.cpp index df2e6bae10..053cfb45c9 100644 --- a/Code/Sandbox/Plugins/ComponentEntityEditorPlugin/Objects/ComponentEntityObject.cpp +++ b/Code/Sandbox/Plugins/ComponentEntityEditorPlugin/Objects/ComponentEntityObject.cpp @@ -609,14 +609,6 @@ void CComponentEntityObject::InvalidateTM(int nWhyFlags) { Matrix34 worldTransform = GetWorldTM(); EBUS_EVENT_ID(m_entityId, AZ::TransformBus, SetWorldTM, LYTransformToAZTransform(worldTransform)); - - // When transformed via the editor, make sure the entity is marked dirty for undo capture. - EBUS_EVENT(AzToolsFramework::ToolsApplicationRequests::Bus, AddDirtyEntity, m_entityId); - - if (CheckFlags(OBJFLAG_SELECTED)) - { - EBUS_EVENT(AzToolsFramework::ToolsApplicationEvents::Bus, InvalidatePropertyDisplay, AzToolsFramework::Refresh_Values); - } } } } diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/View.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/View.h index d6146d3760..aad099dc23 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/View.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/View.h @@ -91,6 +91,8 @@ namespace AZ const AZ::Matrix4x4& GetViewToWorldMatrix() const; const AZ::Matrix4x4& GetViewToClipMatrix() const; const AZ::Matrix4x4& GetWorldToClipMatrix() const; + //! Get the camera's world transform, converted from the viewToWorld matrix's native y-up to z-up + AZ::Transform GetCameraTransform() const; //! Finalize draw lists in this view. This function should only be called when all //! draw packets for current frame are added. diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/ViewportContextBus.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/ViewportContextBus.h index bc7e5a4b1d..377c1d5c4e 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/ViewportContextBus.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/ViewportContextBus.h @@ -92,6 +92,8 @@ namespace AZ virtual ViewPtr GetCurrentView(const Name& contextName) const = 0; }; + using ViewportContextRequests = AZ::Interface; + class ViewportContextManagerNotifications : public AZ::EBusTraits { diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/View.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/View.cpp index e3f41cbda9..21a46693d5 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/View.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/View.cpp @@ -110,6 +110,15 @@ namespace AZ InvalidateSrg(); } + AZ::Transform View::GetCameraTransform() const + { + static const Quaternion yUpToZUp = Quaternion::CreateRotationX(-AZ::Constants::HalfPi); + return AZ::Transform::CreateFromQuaternionAndTranslation( + Quaternion::CreateFromMatrix4x4(m_viewToWorldMatrix) * yUpToZUp, + m_viewToWorldMatrix.GetTranslation() + ).GetOrthogonalized(); + } + void View::SetCameraTransform(const AZ::Matrix3x4& cameraTransform) { m_position = cameraTransform.GetTranslation(); @@ -118,7 +127,7 @@ namespace AZ // is in a Z-up world and an identity matrix means that it faces along the positive-Y axis and Z is up. // An identity view matrix on the other hand looks along the negative Z-axis. // So we adjust for this by rotating the camera world matrix by 90 degrees around the X axis. - AZ::Matrix3x4 zUpToYUp = AZ::Matrix3x4::CreateRotationX(AZ::Constants::HalfPi); + static AZ::Matrix3x4 zUpToYUp = AZ::Matrix3x4::CreateRotationX(AZ::Constants::HalfPi); AZ::Matrix3x4 yUpWorld = cameraTransform * zUpToYUp; float viewToWorldMatrixRaw[16] = { diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/ViewportContext.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/ViewportContext.cpp index 0dae5ac2d0..22287238e9 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/ViewportContext.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/ViewportContext.cpp @@ -194,12 +194,7 @@ namespace AZ AZ::Transform ViewportContext::GetCameraTransform() const { - const Matrix4x4& worldToViewMatrix = GetDefaultView()->GetViewToWorldMatrix(); - const Quaternion zUpToYUp = Quaternion::CreateRotationX(-AZ::Constants::HalfPi); - return AZ::Transform::CreateFromQuaternionAndTranslation( - Quaternion::CreateFromMatrix4x4(worldToViewMatrix) * zUpToYUp, - worldToViewMatrix.GetTranslation() - ).GetOrthogonalized(); + return GetDefaultView()->GetCameraTransform(); } void ViewportContext::SetCameraTransform(const AZ::Transform& transform) diff --git a/Gems/AtomLyIntegration/AtomBridge/Code/CMakeLists.txt b/Gems/AtomLyIntegration/AtomBridge/Code/CMakeLists.txt index 33873e0dfa..c431746b40 100644 --- a/Gems/AtomLyIntegration/AtomBridge/Code/CMakeLists.txt +++ b/Gems/AtomLyIntegration/AtomBridge/Code/CMakeLists.txt @@ -63,6 +63,7 @@ ly_add_target( Gem::EMotionFX_Atom Gem::ImguiAtom Gem::AtomFont + Gem::AtomViewportDisplayInfo ) if(PAL_TRAIT_BUILD_HOST_TOOLS) @@ -104,5 +105,6 @@ if(PAL_TRAIT_BUILD_HOST_TOOLS) Gem::ImguiAtom Gem::AtomFont Gem::AtomToolsFramework.Editor + Gem::AtomViewportDisplayInfo ) endif() diff --git a/Gems/AtomLyIntegration/AtomFont/Code/Include/AtomLyIntegration/AtomFont/FFont.h b/Gems/AtomLyIntegration/AtomFont/Code/Include/AtomLyIntegration/AtomFont/FFont.h index fd66534197..1e224d6090 100644 --- a/Gems/AtomLyIntegration/AtomFont/Code/Include/AtomLyIntegration/AtomFont/FFont.h +++ b/Gems/AtomLyIntegration/AtomFont/Code/Include/AtomLyIntegration/AtomFont/FFont.h @@ -207,11 +207,15 @@ namespace AZ // AzFramework::FontDrawInterface implementation void DrawScreenAlignedText2d( const AzFramework::TextDrawParameters& params, - const AZStd::string_view& string) override; + AZStd::string_view text) override; void DrawScreenAlignedText3d( const AzFramework::TextDrawParameters& params, - const AZStd::string_view& string) override; + AZStd::string_view text) override; + + AZ::Vector2 GetTextSize( + const AzFramework::TextDrawParameters& params, + AZStd::string_view text) override; public: FFont(AtomFont* atomFont, const char* fontName); @@ -233,7 +237,7 @@ namespace AZ void Prepare(const char* str, bool updateTexture, const AtomFont::GlyphSize& glyphSize = AtomFont::defaultGlyphSize); void DrawStringUInternal( const RHI::Viewport& viewport, - RPI::ViewportContext* viewportContext, + RPI::ViewportContextPtr viewportContext, float x, float y, float z, @@ -282,6 +286,16 @@ namespace AZ RPI::WindowContextSharedPtr GetDefaultWindowContext() const; RPI::ViewportContextPtr GetDefaultViewportContext() const; + struct DrawParameters + { + TextDrawContext m_ctx; + AZ::Vector2 m_position; + AZ::Vector2 m_size; + AZ::RPI::ViewportContextPtr m_viewportContext; + const AZ::RHI::Viewport* m_viewport; + }; + DrawParameters ExtractDrawParameters(const AzFramework::TextDrawParameters& params, AZStd::string_view text, bool forceCalculateSize); + private: static constexpr uint32_t NumBuffers = 2; static constexpr float WindowScaleWidth = 800.0f; diff --git a/Gems/AtomLyIntegration/AtomFont/Code/Source/FFont.cpp b/Gems/AtomLyIntegration/AtomFont/Code/Source/FFont.cpp index 75b0e22e4a..21b57e0908 100644 --- a/Gems/AtomLyIntegration/AtomFont/Code/Source/FFont.cpp +++ b/Gems/AtomLyIntegration/AtomFont/Code/Source/FFont.cpp @@ -280,7 +280,7 @@ void AZ::FFont::DrawString(float x, float y, const char* str, const bool asciiMu return; } - DrawStringUInternal(GetDefaultWindowContext()->GetViewport(), GetDefaultViewportContext().get(), x, y, 1.0f, str, asciiMultiLine, ctx); + DrawStringUInternal(GetDefaultWindowContext()->GetViewport(), GetDefaultViewportContext(), x, y, 1.0f, str, asciiMultiLine, ctx); } void AZ::FFont::DrawString(float x, float y, float z, const char* str, const bool asciiMultiLine, const TextDrawContext& ctx) @@ -290,12 +290,12 @@ void AZ::FFont::DrawString(float x, float y, float z, const char* str, const boo return; } - DrawStringUInternal(GetDefaultWindowContext()->GetViewport(), GetDefaultViewportContext().get(), x, y, z, str, asciiMultiLine, ctx); + DrawStringUInternal(GetDefaultWindowContext()->GetViewport(), GetDefaultViewportContext(), x, y, z, str, asciiMultiLine, ctx); } void AZ::FFont::DrawStringUInternal( const RHI::Viewport& viewport, - RPI::ViewportContext* viewportContext, + RPI::ViewportContextPtr viewportContext, float x, float y, float z, @@ -505,7 +505,7 @@ Vec2 AZ::FFont::GetTextSizeUInternal( } charX = offset.x; - charY += size.y; + charY += size.y * (1.f + ctx.GetLineSpacing()); if (charY > maxH) { @@ -944,7 +944,7 @@ int AZ::FFont::CreateQuadsForText(const RHI::Viewport& viewport, float x, float case '\n': { charX = baseXY.x + offset.x; - charY += size.y; + charY += size.y * (1.f + ctx.GetLineSpacing()); continue; } break; @@ -1675,47 +1675,46 @@ static void SetCommonContextFlags(AZ::TextDrawContext& ctx, const AzFramework::T } } -void AZ::FFont::DrawScreenAlignedText2d( - const AzFramework::TextDrawParameters& params, - const AZStd::string_view& string) +AZ::FFont::DrawParameters AZ::FFont::ExtractDrawParameters(const AzFramework::TextDrawParameters& params, AZStd::string_view text, bool forceCalculateSize) { + DrawParameters internalParams; if (params.m_drawViewportId == AzFramework::InvalidViewportId || - string.empty()) + text.empty()) { - return; + return internalParams; } - //Code mostly duplicated from CRenderer::Draw2dTextWithDepth float posX = params.m_position.GetX(); float posY = params.m_position.GetY(); - AZ::RPI::ViewportContext* viewportContext = AZ::Interface::Get()->GetViewportContextById(params.m_drawViewportId).get(); - const AZ::RHI::Viewport& viewport = viewportContext->GetWindowContext()->GetViewport(); + internalParams.m_viewportContext = AZ::Interface::Get()->GetViewportContextById(params.m_drawViewportId); + const AZ::RHI::Viewport& viewport = internalParams.m_viewportContext->GetWindowContext()->GetViewport(); + internalParams.m_viewport = &viewport; if (params.m_virtual800x600ScreenSize) { posX *= WindowScaleWidth / (viewport.m_maxX - viewport.m_minX); posY *= WindowScaleHeight / (viewport.m_maxY - viewport.m_minY); } - TextDrawContext ctx; - ctx.SetBaseState(GS_NODEPTHTEST); - ctx.SetColor(AZColorToLYColorF(params.m_color)); - ctx.SetCharWidthScale((params.m_monospace || params.m_scaleWithWindow) ? 0.5f : 1.0f); - ctx.EnableFrame(false); - ctx.SetProportional(!params.m_monospace && params.m_scaleWithWindow); - ctx.SetSizeIn800x600(params.m_scaleWithWindow && params.m_virtual800x600ScreenSize); - ctx.SetSize(AZVec2ToLYVec2(UiDraw_TextSizeFactor * params.m_scale)); + internalParams.m_ctx.SetBaseState(GS_NODEPTHTEST); + internalParams.m_ctx.SetColor(AZColorToLYColorF(params.m_color)); + internalParams.m_ctx.SetCharWidthScale((params.m_monospace || params.m_scaleWithWindow) ? 0.5f : 1.0f); + internalParams.m_ctx.EnableFrame(false); + internalParams.m_ctx.SetProportional(!params.m_monospace && params.m_scaleWithWindow); + internalParams.m_ctx.SetSizeIn800x600(params.m_scaleWithWindow && params.m_virtual800x600ScreenSize); + internalParams.m_ctx.SetSize(AZVec2ToLYVec2(UiDraw_TextSizeFactor * params.m_scale)); + internalParams.m_ctx.SetLineSpacing(params.m_lineSpacing); if (params.m_monospace || !params.m_scaleWithWindow) { ScaleCoord(viewport, posX, posY); } if (params.m_hAlign != AzFramework::TextHorizontalAlignment::Left || - params.m_vAlign != AzFramework::TextVerticalAlignment::Top) + params.m_vAlign != AzFramework::TextVerticalAlignment::Top || + forceCalculateSize) { - Vec2 textSize = GetTextSizeUInternal(viewport, string.data(), params.m_multiline, ctx); - + Vec2 textSize = GetTextSizeUInternal(viewport, text.data(), params.m_multiline, internalParams.m_ctx); // If we're using virtual 800x600 coordinates, convert the text size from // pixels to that before using it as an offset. - if (ctx.m_sizeIn800x600) + if (internalParams.m_ctx.m_sizeIn800x600) { float width = 1.0f; float height = 1.0f; @@ -1741,33 +1740,46 @@ void AZ::FFont::DrawScreenAlignedText2d( { posY -= textSize.y; } + internalParams.m_size = AZ::Vector2{textSize.x, textSize.y}; + } + SetCommonContextFlags(internalParams.m_ctx, params); + internalParams.m_ctx.m_drawTextFlags |= eDrawText_2D; + internalParams.m_position = AZ::Vector2{posX, posY}; + return internalParams; +} + +void AZ::FFont::DrawScreenAlignedText2d( + const AzFramework::TextDrawParameters& params, + AZStd::string_view text) +{ + DrawParameters internalParams = ExtractDrawParameters(params, text, false); + if (!internalParams.m_viewportContext) + { + return; } - SetCommonContextFlags(ctx, params); - ctx.m_drawTextFlags |= eDrawText_2D; DrawStringUInternal( - viewport, - viewportContext, - posX, - posY, + *internalParams.m_viewport, + internalParams.m_viewportContext, + internalParams.m_position.GetX(), + internalParams.m_position.GetY(), params.m_position.GetZ(), // Z - string.data(), + text.data(), params.m_multiline, - ctx + internalParams.m_ctx ); } void AZ::FFont::DrawScreenAlignedText3d( const AzFramework::TextDrawParameters& params, - const AZStd::string_view& string) + AZStd::string_view text) { - if (params.m_drawViewportId == AzFramework::InvalidViewportId || - string.empty()) + DrawParameters internalParams = ExtractDrawParameters(params, text, false); + if (!internalParams.m_viewportContext) { return; } - AZ::RPI::ViewportContext* viewportContext = AZ::Interface::Get()->GetViewportContextById(params.m_drawViewportId).get(); - AZ::RPI::ViewPtr currentView = viewportContext->GetDefaultView(); + AZ::RPI::ViewPtr currentView = internalParams.m_viewportContext->GetDefaultView(); if (!currentView) { return; @@ -1779,7 +1791,23 @@ void AZ::FFont::DrawScreenAlignedText3d( ); AzFramework::TextDrawParameters param2d = params; param2d.m_position = positionNDC; - DrawScreenAlignedText2d(param2d, string); + + DrawStringUInternal( + *internalParams.m_viewport, + internalParams.m_viewportContext, + internalParams.m_position.GetX(), + internalParams.m_position.GetY(), + params.m_position.GetZ(), // Z + text.data(), + params.m_multiline, + internalParams.m_ctx + ); +} + +AZ::Vector2 AZ::FFont::GetTextSize(const AzFramework::TextDrawParameters& params, AZStd::string_view text) +{ + DrawParameters sizeParams = ExtractDrawParameters(params, text, true); + return sizeParams.m_size; } #endif //USE_NULLFONT_ALWAYS diff --git a/Gems/AtomLyIntegration/AtomViewportDisplayInfo/CMakeLists.txt b/Gems/AtomLyIntegration/AtomViewportDisplayInfo/CMakeLists.txt new file mode 100644 index 0000000000..20a680bce9 --- /dev/null +++ b/Gems/AtomLyIntegration/AtomViewportDisplayInfo/CMakeLists.txt @@ -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) diff --git a/Gems/AtomLyIntegration/AtomViewportDisplayInfo/Code/CMakeLists.txt b/Gems/AtomLyIntegration/AtomViewportDisplayInfo/Code/CMakeLists.txt new file mode 100644 index 0000000000..de4ee9b4b5 --- /dev/null +++ b/Gems/AtomLyIntegration/AtomViewportDisplayInfo/Code/CMakeLists.txt @@ -0,0 +1,52 @@ +# +# 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 + PUBLIC + Include + 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() + diff --git a/Gems/AtomLyIntegration/AtomViewportDisplayInfo/Code/Include/AtomLyIntegration/AtomViewportDisplayInfo/AtomViewportInfoDisplayBus.h b/Gems/AtomLyIntegration/AtomViewportDisplayInfo/Code/Include/AtomLyIntegration/AtomViewportDisplayInfo/AtomViewportInfoDisplayBus.h new file mode 100644 index 0000000000..ae9359d9d3 --- /dev/null +++ b/Gems/AtomLyIntegration/AtomViewportDisplayInfo/Code/Include/AtomLyIntegration/AtomViewportDisplayInfo/AtomViewportInfoDisplayBus.h @@ -0,0 +1,61 @@ +/* +* 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 +#include + +namespace AZ +{ + namespace AtomBridge + { + //! The level of information to display in the viewport info display overlay. + enum class ViewportInfoDisplayState : int + { + NoInfo = 0, + NormalInfo = 1, + FullInfo = 2, + CompactInfo = 3, + Invalid + }; + + //! This bus is used to request changes to the viewport info display overlay. + class AtomViewportInfoDisplayRequests + : public AZ::EBusTraits + { + public: + static const AZ::EBusHandlerPolicy HandlerPolicy = EBusHandlerPolicy::Single; + static const AZ::EBusAddressPolicy AddressPolicy = EBusAddressPolicy::Single; + + //! Gets the current viewport info overlay state. + virtual ViewportInfoDisplayState GetDisplayState() const = 0; + //! Sets the current viewport info overlay state. + //! The overlay will be drawn to the default viewport context every frame, if enabled. + virtual void SetDisplayState(ViewportInfoDisplayState state) = 0; + }; + + using AtomViewportInfoDisplayRequestBus = AZ::EBus; + + //! This bus is used to listen for state changes in the viewport info display overlay. + class AtomViewportInfoDisplayNotifications + : public AZ::EBusTraits + { + public: + static const AZ::EBusHandlerPolicy HandlerPolicy = EBusHandlerPolicy::Multiple; + static const AZ::EBusAddressPolicy AddressPolicy = EBusAddressPolicy::Single; + + //! Called when the ViewportInfoDisplayState (via the r_displayInfo CVar) has changed. + virtual void OnViewportInfoDisplayStateChanged([[maybe_unused]]ViewportInfoDisplayState state){} + }; + + using AtomViewportInfoDisplayNotificationBus = AZ::EBus; + } +} diff --git a/Gems/AtomLyIntegration/AtomViewportDisplayInfo/Code/Source/AtomViewportDisplayInfoSystemComponent.cpp b/Gems/AtomLyIntegration/AtomViewportDisplayInfo/Code/Source/AtomViewportDisplayInfoSystemComponent.cpp new file mode 100644 index 0000000000..672c26a9ab --- /dev/null +++ b/Gems/AtomLyIntegration/AtomViewportDisplayInfo/Code/Source/AtomViewportDisplayInfoSystemComponent.cpp @@ -0,0 +1,316 @@ +/* + * 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 +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace AZ::Render +{ + AZ_CVAR(int, r_displayInfo, 1, [](const int& newDisplayInfoVal)->void + { + // Forward this event to the system component so it can update accordingly. + // This callback only gets triggered by console commands, so this will not recurse. + AtomBridge::AtomViewportInfoDisplayRequestBus::Broadcast( + &AtomBridge::AtomViewportInfoDisplayRequestBus::Events::SetDisplayState, + aznumeric_cast(newDisplayInfoVal) + ); + }, AZ::ConsoleFunctorFlags::DontReplicate, + "Toggles debugging information display.\n" + "Usage: r_displayInfo [0=off/1=show/2=enhanced/3=compact]" + ); + AZ_CVAR(float, r_fpsCalcInterval, 1.0f, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, + "The time period over which to calculate the framerate for r_displayInfo." + ); + + void AtomViewportDisplayInfoSystemComponent::Reflect(AZ::ReflectContext* context) + { + if (AZ::SerializeContext* serialize = azrtti_cast(context)) + { + serialize->Class() + ->Version(0) + ; + + if (AZ::EditContext* ec = serialize->GetEditContext()) + { + ec->Class("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()); + } + + AZ::RPI::ViewportContextNotificationBus::Handler::BusConnect( + AZ::RPI::ViewportContextRequests::Get()->GetDefaultViewportContextName()); + AZ::AtomBridge::AtomViewportInfoDisplayRequestBus::Handler::BusConnect(); + } + + void AtomViewportDisplayInfoSystemComponent::Deactivate() + { + AZ::AtomBridge::AtomViewportInfoDisplayRequestBus::Handler::BusDisconnect(); + AZ::RPI::ViewportContextNotificationBus::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; + AZ::Vector2 textSize = m_fontDrawInterface->GetTextSize(m_drawParams, line); + m_fontDrawInterface->DrawScreenAlignedText2d(m_drawParams, line); + m_drawParams.m_position.SetY(m_drawParams.m_position.GetY() + textSize.GetY() + m_lineSpacing); + } + + void AtomViewportDisplayInfoSystemComponent::OnRenderTick() + { + if (!m_fontDrawInterface) + { + auto fontQueryInterface = AZ::Interface::Get(); + if (!fontQueryInterface) + { + return; + } + m_fontDrawInterface = + fontQueryInterface->GetDefaultFontDrawInterface(); + } + AZ::RPI::ViewportContextPtr viewportContext = GetViewportContext(); + + if (!m_fontDrawInterface || !viewportContext || !viewportContext->GetRenderScene()) + { + return; + } + + m_fpsInterval = AZStd::chrono::seconds(r_fpsCalcInterval); + + UpdateFramerate(); + + const AtomBridge::ViewportInfoDisplayState displayLevel = GetDisplayState(); + if (displayLevel == AtomBridge::ViewportInfoDisplayState::NoInfo) + { + return; + } + + if (m_updateRootPassQuery) + { + if (auto rootPass = AZ::RPI::PassSystemInterface::Get()->GetRootPass()) + { + rootPass->SetPipelineStatisticsQueryEnabled(displayLevel != AtomBridge::ViewportInfoDisplayState::CompactInfo); + m_updateRootPassQuery = false; + } + } + + 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 = m_fontDrawInterface->GetTextSize(m_drawParams, " ").GetY(); + m_lineSpacing = lineHeight * m_drawParams.m_lineSpacing; + + DrawRendererInfo(); + if (displayLevel == AtomBridge::ViewportInfoDisplayState::FullInfo) + { + DrawCameraInfo(); + } + if (displayLevel != AtomBridge::ViewportInfoDisplayState::CompactInfo) + { + DrawPassInfo(); + } + DrawFramerate(); + } + + AtomBridge::ViewportInfoDisplayState AtomViewportDisplayInfoSystemComponent::GetDisplayState() const + { + return aznumeric_cast(r_displayInfo.operator int()); + } + + void AtomViewportDisplayInfoSystemComponent::SetDisplayState(AtomBridge::ViewportInfoDisplayState state) + { + r_displayInfo = aznumeric_cast(state); + AtomBridge::AtomViewportInfoDisplayNotificationBus::Broadcast( + &AtomBridge::AtomViewportInfoDisplayNotificationBus::Events::OnViewportInfoDisplayStateChanged, + state); + m_updateRootPassQuery = true; + } + + 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::DrawPassInfo() + { + auto rootPass = AZ::RPI::PassSystemInterface::Get()->GetRootPass(); + const RPI::PipelineStatisticsResult stats = rootPass->GetLatestPipelineStatisticsResult(); + AZStd::function)> containingPassCount = [&containingPassCount](const AZ::RPI::Ptr pass) + { + int count = 1; + if (auto passAsParent = pass->AsParent()) + { + for (const auto& child : passAsParent->GetChildren()) + { + count += containingPassCount(child); + } + } + return count; + }; + const int numPasses = containingPassCount(rootPass); + DrawLine(AZStd::string::format( + "Total Passes: %d Vertex Count: %lld Primitive Count: %lld", + numPasses, + aznumeric_cast(stats.m_vertexCount), + aznumeric_cast(stats.m_primitiveCount) + )); + } + + 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 as is required by our FPS history. + while (!m_fpsHistory.empty() && (currentTime.Get() - m_fpsHistory.front().Get()) > m_fpsInterval) + { + m_fpsHistory.pop_front(); + } + + // Discard entries with a zero time-delta (can happen when we don't have window focus). + if (m_fpsHistory.empty() || (currentTime.Get() - m_fpsHistory.back().Get()) != AZStd::chrono::seconds(0)) + { + m_fpsHistory.push_back(currentTime); + } + } + + void AtomViewportDisplayInfoSystemComponent::DrawFramerate() + { + AZStd::chrono::duration actualInterval = AZStd::chrono::seconds(0); + AZStd::optional lastTime; + AZStd::optional minFPS; + AZStd::optional maxFPS; + for (const AZ::ScriptTimePoint& time : m_fpsHistory) + { + if (lastTime.has_value()) + { + AZStd::chrono::duration deltaTime = time.Get() - lastTime.value().Get(); + 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(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 diff --git a/Gems/AtomLyIntegration/AtomViewportDisplayInfo/Code/Source/AtomViewportDisplayInfoSystemComponent.h b/Gems/AtomLyIntegration/AtomViewportDisplayInfo/Code/Source/AtomViewportDisplayInfoSystemComponent.h new file mode 100644 index 0000000000..135082fd8c --- /dev/null +++ b/Gems/AtomLyIntegration/AtomViewportDisplayInfo/Code/Source/AtomViewportDisplayInfoSystemComponent.h @@ -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 + +#include +#include +#include +#include +#include +#include +#include + +namespace AZ +{ + class TickRequests; + + namespace Render + { + class AtomViewportDisplayInfoSystemComponent + : public AZ::Component + , public AZ::RPI::ViewportContextNotificationBus::Handler + , public AZ::AtomBridge::AtomViewportInfoDisplayRequestBus::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; + + // AZ::AtomBridge::AtomViewportInfoDisplayRequestBus::Handler overrides... + AtomBridge::ViewportInfoDisplayState GetDisplayState() const override; + void SetDisplayState(AtomBridge::ViewportInfoDisplayState state) 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 DrawPassInfo(); + void DrawFramerate(); + + AZStd::string m_rendererDescription; + AzFramework::TextDrawParameters m_drawParams; + AzFramework::FontDrawInterface* m_fontDrawInterface = nullptr; + float m_lineSpacing; + AZStd::chrono::duration m_fpsInterval = AZStd::chrono::seconds(1); + AZStd::deque m_fpsHistory; + AZStd::optional m_lastMemoryUpdate; + AZ::TickRequests* m_tickRequests = nullptr; + bool m_updateRootPassQuery = true; + }; + } // namespace Render +} // namespace AZ diff --git a/Gems/AtomLyIntegration/AtomViewportDisplayInfo/Code/Source/Module.cpp b/Gems/AtomLyIntegration/AtomViewportDisplayInfo/Code/Source/Module.cpp new file mode 100644 index 0000000000..f67e1bd1f5 --- /dev/null +++ b/Gems/AtomLyIntegration/AtomViewportDisplayInfo/Code/Source/Module.cpp @@ -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 +#include +#include + +#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(), + }; + } + }; + } // 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) diff --git a/Gems/AtomLyIntegration/AtomViewportDisplayInfo/Code/Source/Tests/test_Main.cpp b/Gems/AtomLyIntegration/AtomViewportDisplayInfo/Code/Source/Tests/test_Main.cpp new file mode 100644 index 0000000000..b533221bbe --- /dev/null +++ b/Gems/AtomLyIntegration/AtomViewportDisplayInfo/Code/Source/Tests/test_Main.cpp @@ -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 + +AZ_UNIT_TEST_HOOK(DEFAULT_UNIT_TEST_ENV); + +TEST(AtomViewportDisplayInfoSanityTest, Sanity) +{ + EXPECT_EQ(1, 1); +} diff --git a/Gems/AtomLyIntegration/AtomViewportDisplayInfo/Code/atomviewportdisplayinfo_files.cmake b/Gems/AtomLyIntegration/AtomViewportDisplayInfo/Code/atomviewportdisplayinfo_files.cmake new file mode 100644 index 0000000000..561971453b --- /dev/null +++ b/Gems/AtomLyIntegration/AtomViewportDisplayInfo/Code/atomviewportdisplayinfo_files.cmake @@ -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 +) diff --git a/Gems/AtomLyIntegration/AtomViewportDisplayInfo/Code/atomviewportdisplayinfo_test_files.cmake b/Gems/AtomLyIntegration/AtomViewportDisplayInfo/Code/atomviewportdisplayinfo_test_files.cmake new file mode 100644 index 0000000000..0bc1ee3a50 --- /dev/null +++ b/Gems/AtomLyIntegration/AtomViewportDisplayInfo/Code/atomviewportdisplayinfo_test_files.cmake @@ -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 +) diff --git a/Gems/AtomLyIntegration/AtomViewportDisplayInfo/gem.json b/Gems/AtomLyIntegration/AtomViewportDisplayInfo/gem.json new file mode 100644 index 0000000000..dd92a99ea9 --- /dev/null +++ b/Gems/AtomLyIntegration/AtomViewportDisplayInfo/gem.json @@ -0,0 +1,12 @@ +{ + "gem_name": "AtomLyIntegration_AtomViewportDisplayInfo", + "display_name": "Atom Viewport Display Info Overlay", + "summary": "Provides a diagnostic viewport overlay for the default O3DE Atom viewport.", + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + "AtomLyIntegration", + "AtomViewportDisplayInfo" + ] +} \ No newline at end of file diff --git a/Gems/AtomLyIntegration/CMakeLists.txt b/Gems/AtomLyIntegration/CMakeLists.txt index 57bb860a9e..35022e643b 100644 --- a/Gems/AtomLyIntegration/CMakeLists.txt +++ b/Gems/AtomLyIntegration/CMakeLists.txt @@ -16,3 +16,4 @@ add_subdirectory(EMotionFXAtom) add_subdirectory(AtomFont) add_subdirectory(TechnicalArt) add_subdirectory(AtomBridge) +add_subdirectory(AtomViewportDisplayInfo) diff --git a/Gems/Camera/Code/Source/CameraComponentController.cpp b/Gems/Camera/Code/Source/CameraComponentController.cpp index 2799d897d6..3dcee68169 100644 --- a/Gems/Camera/Code/Source/CameraComponentController.cpp +++ b/Gems/Camera/Code/Source/CameraComponentController.cpp @@ -160,6 +160,17 @@ namespace Camera incompatible.push_back(AZ_CRC("CameraService", 0x1dd1caa4)); } + void CameraComponentController::Init() + { + m_onViewMatrixChanged = AZ::Event::Handler([this](const AZ::Matrix4x4&) + { + if (!m_updatingTransformFromEntity) + { + AZ::TransformBus::Event(m_entityId, &AZ::TransformInterface::SetWorldTM, m_atomCamera->GetCameraTransform()); + } + }); + } + void CameraComponentController::Activate(AZ::EntityId entityId) { m_entityId = entityId; @@ -218,6 +229,8 @@ namespace Camera } } AZ::RPI::ViewProviderBus::Handler::BusConnect(m_entityId); + + m_atomCamera->ConnectWorldToViewMatrixChangedHandler(m_onViewMatrixChanged); } UpdateCamera(); @@ -258,6 +271,7 @@ namespace Camera if (atomViewportRequests) { AZ::RPI::ViewProviderBus::Handler::BusDisconnect(m_entityId); + m_onViewMatrixChanged.Disconnect(); } DeactivateAtomView(); @@ -376,7 +390,9 @@ namespace Camera if (m_atomCamera) { + m_updatingTransformFromEntity = true; m_atomCamera->SetCameraTransform(AZ::Matrix3x4::CreateFromTransform(world.GetOrthogonalized())); + m_updatingTransformFromEntity = false; } } @@ -425,7 +441,9 @@ namespace Camera m_config.m_nearClipDistance, m_config.m_farClipDistance, true); + m_updatingTransformFromEntity = true; m_atomCamera->SetViewToClipMatrix(viewToClipMatrix); + m_updatingTransformFromEntity = false; } } diff --git a/Gems/Camera/Code/Source/CameraComponentController.h b/Gems/Camera/Code/Source/CameraComponentController.h index 92e1354f17..cae6ad4663 100644 --- a/Gems/Camera/Code/Source/CameraComponentController.h +++ b/Gems/Camera/Code/Source/CameraComponentController.h @@ -77,6 +77,7 @@ namespace Camera static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided); static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible); + void Init(); void Activate(AZ::EntityId entityId); void Deactivate(); void SetConfiguration(const CameraComponentConfig& config); @@ -121,6 +122,8 @@ namespace Camera // Atom integration AZ::RPI::ViewPtr m_atomCamera; AZ::RPI::AuxGeomDrawPtr m_atomAuxGeom; + AZ::Event::Handler m_onViewMatrixChanged; + bool m_updatingTransformFromEntity = false; // Cry view integration IView* m_view = nullptr;