diff --git a/Code/Editor/Core/QtEditorApplication.cpp b/Code/Editor/Core/QtEditorApplication.cpp index a4aab24be4..66ed42af8f 100644 --- a/Code/Editor/Core/QtEditorApplication.cpp +++ b/Code/Editor/Core/QtEditorApplication.cpp @@ -44,7 +44,7 @@ enum { // in milliseconds GameModeIdleFrequency = 0, - EditorModeIdleFrequency = 1, + EditorModeIdleFrequency = 0, InactiveModeFrequency = 10, UninitializedFrequency = 9999, }; diff --git a/Code/Editor/CryEditPy.cpp b/Code/Editor/CryEditPy.cpp index 7a407aac37..1b38fe23b8 100644 --- a/Code/Editor/CryEditPy.cpp +++ b/Code/Editor/CryEditPy.cpp @@ -77,10 +77,6 @@ namespace // This closes the current document (level) currentLevel->OnNewDocument(); - // Then we freeze the viewport's input - AzToolsFramework::ViewportInteraction::ViewportFreezeRequestBus::Broadcast( - &AzToolsFramework::ViewportInteraction::ViewportFreezeRequestBus::Events::FreezeViewportInput, true); - // Then we need to tell the game engine there is no level to render anymore if (GetIEditor()->GetGameEngine()) { diff --git a/Code/Editor/EditorViewportWidget.cpp b/Code/Editor/EditorViewportWidget.cpp index 851d2c5b6f..f009fe7602 100644 --- a/Code/Editor/EditorViewportWidget.cpp +++ b/Code/Editor/EditorViewportWidget.cpp @@ -669,16 +669,6 @@ void EditorViewportWidget::OnEditorNotifyEvent(EEditorNotifyEvent event) case eNotify_OnEndSceneSave: PopDisableRendering(); break; - - case eNotify_OnBeginLoad: // disables viewport input when starting to load an existing level - case eNotify_OnBeginCreate: // disables viewport input when starting to create a new level - m_freezeViewportInput = true; - break; - - case eNotify_OnEndLoad: // enables viewport input when finished loading an existing level - case eNotify_OnEndCreate: // enables viewport input when finished creating a new level - m_freezeViewportInput = false; - break; } } @@ -962,16 +952,6 @@ AzFramework::ScreenPoint EditorViewportWidget::ViewportWorldToScreen(const AZ::V return m_renderViewport->ViewportWorldToScreen(worldPosition); } -bool EditorViewportWidget::IsViewportInputFrozen() -{ - return m_freezeViewportInput; -} - -void EditorViewportWidget::FreezeViewportInput(bool freeze) -{ - m_freezeViewportInput = freeze; -} - QWidget* EditorViewportWidget::GetWidgetForViewportContextMenu() { return this; @@ -1057,9 +1037,9 @@ void EditorViewportWidget::SetViewportId(int id) void EditorViewportWidget::ConnectViewportInteractionRequestBus() { - AzToolsFramework::ViewportInteraction::ViewportFreezeRequestBus::Handler::BusConnect(GetViewportId()); AzToolsFramework::ViewportInteraction::MainEditorViewportInteractionRequestBus::Handler::BusConnect(GetViewportId()); AzToolsFramework::ViewportInteraction::EditorEntityViewportInteractionRequestBus::Handler::BusConnect(GetViewportId()); + AzToolsFramework::ViewportInteraction::EditorModifierKeyRequestBus::Handler::BusConnect(); m_viewportUi.ConnectViewportUiBus(GetViewportId()); AzFramework::InputSystemCursorConstraintRequestBus::Handler::BusConnect(); @@ -1070,9 +1050,9 @@ void EditorViewportWidget::DisconnectViewportInteractionRequestBus() AzFramework::InputSystemCursorConstraintRequestBus::Handler::BusDisconnect(); m_viewportUi.DisconnectViewportUiBus(); + AzToolsFramework::ViewportInteraction::EditorModifierKeyRequestBus::Handler::BusDisconnect(); AzToolsFramework::ViewportInteraction::EditorEntityViewportInteractionRequestBus::Handler::BusDisconnect(); AzToolsFramework::ViewportInteraction::MainEditorViewportInteractionRequestBus::Handler::BusDisconnect(); - AzToolsFramework::ViewportInteraction::ViewportFreezeRequestBus::Handler::BusDisconnect(); } namespace AZ::ViewportHelpers diff --git a/Code/Editor/EditorViewportWidget.h b/Code/Editor/EditorViewportWidget.h index 825d21a034..c4a8e9fcca 100644 --- a/Code/Editor/EditorViewportWidget.h +++ b/Code/Editor/EditorViewportWidget.h @@ -89,7 +89,6 @@ class SANDBOX_API EditorViewportWidget final , private Camera::EditorCameraRequestBus::Handler , private Camera::CameraNotificationBus::Handler , private AzFramework::InputSystemCursorConstraintRequestBus::Handler - , private AzToolsFramework::ViewportInteraction::ViewportFreezeRequestBus::Handler , private AzToolsFramework::ViewportInteraction::MainEditorViewportInteractionRequestBus::Handler , private AzToolsFramework::ViewportInteraction::EditorEntityViewportInteractionRequestBus::Handler , private AzToolsFramework::ViewportInteraction::EditorModifierKeyRequestBus::Handler @@ -202,10 +201,6 @@ private: // AzFramework::InputSystemCursorConstraintRequestBus overrides ... void* GetSystemCursorConstraintWindow() const override; - // AzToolsFramework::ViewportFreezeRequestBus overrides ... - bool IsViewportInputFrozen() override; - void FreezeViewportInput(bool freeze) override; - // AzToolsFramework::MainEditorViewportInteractionRequestBus overrides ... AZ::EntityId PickEntity(const AzFramework::ScreenPoint& point) override; AZ::Vector3 PickTerrain(const AzFramework::ScreenPoint& point) override; @@ -387,9 +382,6 @@ private: // Unclear if it's still necessary. QSet m_keyDown; - // State for ViewportFreezeRequestBus, currently does nothing - bool m_freezeViewportInput = false; - // This widget holds a reference to the manipulator manage because its responsible for drawing manipulators AZStd::shared_ptr m_manipulatorManager; diff --git a/Code/Editor/Style/Editor.qss b/Code/Editor/Style/Editor.qss index 302b7703a5..5eb280260e 100644 --- a/Code/Editor/Style/Editor.qss +++ b/Code/Editor/Style/Editor.qss @@ -191,94 +191,4 @@ ConsoleTextEdit:focus, border-width: 0px; border-color: #e9e9e9; border-style: solid; -} - -/* Welcome Screen styling */ - -WelcomeScreenDialog QLabel -{ - font-size: 12px; - color: #FFFFFF; - line-height: 20px; - background-color: transparent; - margin: 0; -} - -WelcomeScreenDialog QLabel#currentProjectLabel -{ - margin-top: 10px; -} - -WelcomeScreenDialog QPushButton -{ - font-size: 14px; - line-height: 16px; -} - -WelcomeScreenDialog QWidget#articleViewContainerRoot -{ - background: #444444; -} - -WelcomeScreenDialog QWidget#levelViewFTUEContainer -{ - background: #282828; -} - -QTableWidget#recentLevelTable::item { - background-color: rgb(64,64,64); - margin-bottom: 4px; - margin-top: 4px; -} - -/* Particle Editor */ - -#NumParticlesLabel -{ - margin-top: 6px; -} - -#LibrarySearchIcon -{ - max-width: 16px; - max-height: 16px; - qproperty-iconSize: 16px 16px; -} - - -#ClosePrefabDialog, #SavePrefabDialog -{ - min-width : 640px; -} - -#SaveDependentPrefabsCard -{ - margin: 0px 15px 10px 15px; -} - -#PrefabSavedMessageFrame{ - border: 1px solid green; - margin: 10px 15px 10px 15px; - border-radius: 2px; - padding: 5px 2px 5px 2px; -} - -#ClosePrefabDialog #PrefabSaveWarningFrame -{ - border: 1px solid orange; - margin: 10px 15px 10px 15px; - border-radius: 2px; - padding: 5px 2px 5px 2px; - color : white; -} - -#SavePrefabDialog #FooterSeparatorLine -{ - color: gray; -} - -#SavePrefabDialog #PrefabSavePreferenceHint -{ - font: italic; - color: #999999; } \ No newline at end of file diff --git a/Code/Editor/TrackView/SequenceBatchRenderDialog.cpp b/Code/Editor/TrackView/SequenceBatchRenderDialog.cpp index b510315995..6b3f1c2633 100644 --- a/Code/Editor/TrackView/SequenceBatchRenderDialog.cpp +++ b/Code/Editor/TrackView/SequenceBatchRenderDialog.cpp @@ -36,6 +36,9 @@ #include "CryEdit.h" #include "Viewport.h" +// Atom Renderer +#include + AZ_PUSH_DISABLE_DLL_EXPORT_MEMBER_WARNING #include AZ_POP_DISABLE_DLL_EXPORT_MEMBER_WARNING @@ -1234,6 +1237,13 @@ void CSequenceBatchRenderDialog::OnKickIdleTimout() { componentApplication->TickSystem(); } + + // Directly tick the renderer, as it's no longer part of the system tick + if (auto rpiSystem = AZ::RPI::RPISystemInterface::Get()) + { + rpiSystem->SimulationTick(); + rpiSystem->RenderTick(); + } } } diff --git a/Code/Framework/AzCore/AzCore/Component/ComponentApplication.cpp b/Code/Framework/AzCore/AzCore/Component/ComponentApplication.cpp index 168807cd97..a13f11c007 100644 --- a/Code/Framework/AzCore/AzCore/Component/ComponentApplication.cpp +++ b/Code/Framework/AzCore/AzCore/Component/ComponentApplication.cpp @@ -74,6 +74,8 @@ #include #include +AZ_CVAR(float, g_simulation_tick_rate, 0, nullptr, AZ::ConsoleFunctorFlags::Null, "The rate at which the game simulation tick loop runs, or 0 for as fast as possible"); + static void PrintEntityName(const AZ::ConsoleCommandContainer& arguments) { if (arguments.empty()) @@ -1394,6 +1396,23 @@ namespace AZ AZ_PROFILE_SCOPE(AzCore, "ComponentApplication::Tick:OnTick"); EBUS_EVENT(TickBus, OnTick, m_deltaTime, ScriptTimePoint(now)); } + + // If tick rate limiting is on, ensure (1 / g_simulation_tick_rate) ms has elapsed since the last frame, + // sleeping if there's still time remaining. + if (g_simulation_tick_rate > 0.f) + { + now = AZStd::chrono::system_clock::now(); + + // Work in microsecond durations here as that's the native measurement time for time_point + constexpr float microsecondsPerSecond = 1000.f * 1000.f; + const AZStd::chrono::microseconds timeBudgetPerTick(static_cast(microsecondsPerSecond / g_simulation_tick_rate)); + AZStd::chrono::microseconds timeUntilNextTick = m_currentTime + timeBudgetPerTick - now; + + if (timeUntilNextTick.count() > 0) + { + AZStd::this_thread::sleep_for(timeUntilNextTick); + } + } } } diff --git a/Code/Framework/AzCore/AzCore/Component/TickBus.h b/Code/Framework/AzCore/AzCore/Component/TickBus.h index e65efb93f2..966a3c303e 100644 --- a/Code/Framework/AzCore/AzCore/Component/TickBus.h +++ b/Code/Framework/AzCore/AzCore/Component/TickBus.h @@ -46,6 +46,8 @@ namespace AZ TICK_PRE_RENDER = 750, ///< Suggested tick handler position to update render-related data. + TICK_RENDER = 800, ///< Suggested tick handler position for rendering. + TICK_DEFAULT = 1000, ///< Default tick handler position when the handler is constructed. TICK_UI = 2000, ///< Suggested tick handler position for UI components. diff --git a/Code/Framework/AzManipulatorTestFramework/Include/AzManipulatorTestFramework/ActionDispatcher.h b/Code/Framework/AzManipulatorTestFramework/Include/AzManipulatorTestFramework/ActionDispatcher.h index 1b555a4a81..6bd72f5101 100644 --- a/Code/Framework/AzManipulatorTestFramework/Include/AzManipulatorTestFramework/ActionDispatcher.h +++ b/Code/Framework/AzManipulatorTestFramework/Include/AzManipulatorTestFramework/ActionDispatcher.h @@ -39,6 +39,8 @@ namespace AzManipulatorTestFramework DerivedDispatcherT* MouseLButtonDown(); //! Set the left mouse button up. DerivedDispatcherT* MouseLButtonUp(); + //! Send a double click event. + DerivedDispatcherT* MouseLButtonDoubleClick(); //! Set the keyboard modifier button down. DerivedDispatcherT* KeyboardModifierDown(const AzToolsFramework::ViewportInteraction::KeyboardModifier& keyModifier); //! Set the keyboard modifier button up. @@ -71,6 +73,7 @@ namespace AzManipulatorTestFramework virtual void CameraStateImpl(const AzFramework::CameraState& cameraState) = 0; virtual void MouseLButtonDownImpl() = 0; virtual void MouseLButtonUpImpl() = 0; + virtual void MouseLButtonDoubleClickImpl() = 0; virtual void MousePositionImpl(const AzFramework::ScreenPoint& position) = 0; virtual void KeyboardModifierDownImpl(const AzToolsFramework::ViewportInteraction::KeyboardModifier& keyModifier) = 0; virtual void KeyboardModifierUpImpl(const AzToolsFramework::ViewportInteraction::KeyboardModifier& keyModifier) = 0; @@ -167,7 +170,7 @@ namespace AzManipulatorTestFramework template DerivedDispatcherT* ActionDispatcher::MouseLButtonDown() { - Log("%s", "Mouse left button down"); + Log("Mouse left button down"); MouseLButtonDownImpl(); return static_cast(this); } @@ -175,11 +178,19 @@ namespace AzManipulatorTestFramework template DerivedDispatcherT* ActionDispatcher::MouseLButtonUp() { - Log("%s", "Mouse left button up"); + Log("Mouse left button up"); MouseLButtonUpImpl(); return static_cast(this); } + template + DerivedDispatcherT* ActionDispatcher::MouseLButtonDoubleClick() + { + Log("Mouse left button double click"); + MouseLButtonDoubleClickImpl(); + return static_cast(this); + } + template const char* ActionDispatcher::KeyboardModifierString( const AzToolsFramework::ViewportInteraction::KeyboardModifier& keyModifier) diff --git a/Code/Framework/AzManipulatorTestFramework/Include/AzManipulatorTestFramework/ImmediateModeActionDispatcher.h b/Code/Framework/AzManipulatorTestFramework/Include/AzManipulatorTestFramework/ImmediateModeActionDispatcher.h index 7a4773a37c..9e11a7543c 100644 --- a/Code/Framework/AzManipulatorTestFramework/Include/AzManipulatorTestFramework/ImmediateModeActionDispatcher.h +++ b/Code/Framework/AzManipulatorTestFramework/Include/AzManipulatorTestFramework/ImmediateModeActionDispatcher.h @@ -58,6 +58,7 @@ namespace AzManipulatorTestFramework void CameraStateImpl(const AzFramework::CameraState& cameraState) override; void MouseLButtonDownImpl() override; void MouseLButtonUpImpl() override; + void MouseLButtonDoubleClickImpl() override; void MousePositionImpl(const AzFramework::ScreenPoint& position) override; void KeyboardModifierDownImpl(const KeyboardModifier& keyModifier) override; void KeyboardModifierUpImpl(const KeyboardModifier& keyModifier) override; diff --git a/Code/Framework/AzManipulatorTestFramework/Include/AzManipulatorTestFramework/RetainedModeActionDispatcher.h b/Code/Framework/AzManipulatorTestFramework/Include/AzManipulatorTestFramework/RetainedModeActionDispatcher.h deleted file mode 100644 index d9aceedf8a..0000000000 --- a/Code/Framework/AzManipulatorTestFramework/Include/AzManipulatorTestFramework/RetainedModeActionDispatcher.h +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (c) Contributors to the Open 3D Engine Project. - * For complete copyright and license terms please see the LICENSE at the root of this distribution. - * - * SPDX-License-Identifier: Apache-2.0 OR MIT - * - */ - -#pragma once - -#include -#include -#include -#include -#include - -namespace AzManipulatorTestFramework -{ - //! Buffers actions to be dispatched upon a call to Execute(). - class RetainedModeActionDispatcher - : public ActionDispatcher - { - public: - explicit RetainedModeActionDispatcher(ManipulatorViewportInteraction& viewportManipulatorInteraction); - //! Execute the sequence of actions and lock the dispatcher from adding further actions. - RetainedModeActionDispatcher* Execute(); - //! Reset the sequence of actions and unlock the dispatcher from adding further actions. - RetainedModeActionDispatcher* ResetSequence(); - - protected: - // ActionDispatcher ... - void EnableSnapToGridImpl() override; - void DisableSnapToGridImpl() override; - void GridSizeImpl(float size) override; - void CameraStateImpl(const AzFramework::CameraState& cameraState) override; - void MouseLButtonDownImpl() override; - void MouseLButtonUpImpl() override; - void MousePositionImpl(const AzFramework::ScreenPoint& position) override; - void KeyboardModifierDownImpl(const AzToolsFramework::ViewportInteraction::KeyboardModifier& keyModifier) override; - void KeyboardModifierUpImpl(const AzToolsFramework::ViewportInteraction::KeyboardModifier& keyModifier) override; - void ExpectManipulatorBeingInteractedImpl() override; - void ExpectManipulatorNotBeingInteractedImpl() override; - void SetEntityWorldTransformImpl(AZ::EntityId entityId, const AZ::Transform& transform) override; - void SetSelectedEntityImpl(AZ::EntityId entity) override; - void SetSelectedEntitiesImpl(const AzToolsFramework::EntityIdList& entities) override; - void EnterComponentModeImpl(const AZ::Uuid& uuid) override; - - private: - using Action = AZStd::function; - void AddActionToSequence(Action&& action); - ImmediateModeActionDispatcher m_dispatcher; - AZStd::list m_actions; - bool m_locked = false; - }; -} // namespace AzManipulatorTestFramework diff --git a/Code/Framework/AzManipulatorTestFramework/Source/ImmediateModeActionDispatcher.cpp b/Code/Framework/AzManipulatorTestFramework/Source/ImmediateModeActionDispatcher.cpp index 895899cf3f..23cc2a21f5 100644 --- a/Code/Framework/AzManipulatorTestFramework/Source/ImmediateModeActionDispatcher.cpp +++ b/Code/Framework/AzManipulatorTestFramework/Source/ImmediateModeActionDispatcher.cpp @@ -83,7 +83,17 @@ namespace AzManipulatorTestFramework void ImmediateModeActionDispatcher::MouseLButtonUpImpl() { GetMouseInteractionEvent()->m_mouseEvent = AzToolsFramework::ViewportInteraction::MouseEvent::Up; - m_viewportManipulatorInteraction.GetManipulatorManager().ConsumeMouseInteractionEvent(*GetMouseInteractionEvent()); + m_viewportManipulatorInteraction.GetManipulatorManager().ConsumeMouseInteractionEvent(*m_event); + ToggleOff(GetMouseInteractionEvent()->m_mouseInteraction.m_mouseButtons.m_mouseButtons, MouseButton::Left); + // the mouse position will be the same as the previous event, thus the delta will be 0 + MouseMoveAfterButton(); + } + + void ImmediateModeActionDispatcher::MouseLButtonDoubleClickImpl() + { + GetMouseInteractionEvent()->m_mouseEvent = AzToolsFramework::ViewportInteraction::MouseEvent::DoubleClick; + ToggleOn(GetMouseInteractionEvent()->m_mouseInteraction.m_mouseButtons.m_mouseButtons, MouseButton::Left); + m_viewportManipulatorInteraction.GetManipulatorManager().ConsumeMouseInteractionEvent(*m_event); ToggleOff(GetMouseInteractionEvent()->m_mouseInteraction.m_mouseButtons.m_mouseButtons, MouseButton::Left); // the mouse position will be the same as the previous event, thus the delta will be 0 MouseMoveAfterButton(); diff --git a/Code/Framework/AzManipulatorTestFramework/Source/RetainedModeActionDispatcher.cpp b/Code/Framework/AzManipulatorTestFramework/Source/RetainedModeActionDispatcher.cpp deleted file mode 100644 index 34ae4aaf4a..0000000000 --- a/Code/Framework/AzManipulatorTestFramework/Source/RetainedModeActionDispatcher.cpp +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright (c) Contributors to the Open 3D Engine Project. - * For complete copyright and license terms please see the LICENSE at the root of this distribution. - * - * SPDX-License-Identifier: Apache-2.0 OR MIT - * - */ - -#include - -namespace AzManipulatorTestFramework -{ - using KeyboardModifier = AzToolsFramework::ViewportInteraction::KeyboardModifier; - - RetainedModeActionDispatcher::RetainedModeActionDispatcher( - ManipulatorViewportInteraction& viewportManipulatorInteraction) - : m_dispatcher(viewportManipulatorInteraction) - { - } - - void RetainedModeActionDispatcher::AddActionToSequence(Action&& action) - { - if (m_locked) - { - const char* error = "Couldn't add action to sequence, dispatcher is locked (you must call ResetSequence() \ - before adding actions to this dispatcher)"; - Log("%s", error); - AZ_Assert(false, "Error: %s", error); - } - - m_actions.emplace_back(action); - } - - void RetainedModeActionDispatcher::EnableSnapToGridImpl() - { - AddActionToSequence([=]() { m_dispatcher.EnableSnapToGrid(); }); - } - - void RetainedModeActionDispatcher::DisableSnapToGridImpl() - { - AddActionToSequence([=]() { m_dispatcher.DisableSnapToGrid(); }); - } - - void RetainedModeActionDispatcher::GridSizeImpl(float size) - { - AddActionToSequence([=]() { m_dispatcher.GridSize(size); }); - } - - void RetainedModeActionDispatcher::CameraStateImpl(const AzFramework::CameraState& cameraState) - { - AddActionToSequence([=]() { m_dispatcher.CameraState(cameraState); }); - } - - void RetainedModeActionDispatcher::MouseLButtonDownImpl() - { - AddActionToSequence([=]() { m_dispatcher.MouseLButtonDown(); }); - } - - void RetainedModeActionDispatcher::MouseLButtonUpImpl() - { - AddActionToSequence([=]() { m_dispatcher.MouseLButtonUp(); }); - } - - void RetainedModeActionDispatcher::MousePositionImpl(const AzFramework::ScreenPoint& position) - { - AddActionToSequence([=]() { m_dispatcher.MousePosition(position); }); - } - - void RetainedModeActionDispatcher::KeyboardModifierDownImpl(const KeyboardModifier& keyModifier) - { - AddActionToSequence([=]() { m_dispatcher.KeyboardModifierDown(keyModifier); }); - } - - void RetainedModeActionDispatcher::KeyboardModifierUpImpl(const KeyboardModifier& keyModifier) - { - AddActionToSequence([=]() { m_dispatcher.KeyboardModifierUp(keyModifier); }); - } - - void RetainedModeActionDispatcher::ExpectManipulatorBeingInteractedImpl() - { - AddActionToSequence([=]() { m_dispatcher.ExpectManipulatorBeingInteracted(); }); - } - - void RetainedModeActionDispatcher::ExpectManipulatorNotBeingInteractedImpl() - { - AddActionToSequence([=]() { m_dispatcher.ExpectManipulatorNotBeingInteracted(); }); - } - - void RetainedModeActionDispatcher::SetEntityWorldTransformImpl(AZ::EntityId entityId, const AZ::Transform& transform) - { - AddActionToSequence([=]() { m_dispatcher.SetEntityWorldTransform(entityId, transform); }); - } - - void RetainedModeActionDispatcher::SetSelectedEntityImpl(AZ::EntityId entity) - { - AddActionToSequence([=]() { m_dispatcher.SetSelectedEntity(entity); }); - } - - void RetainedModeActionDispatcher::SetSelectedEntitiesImpl(const AzToolsFramework::EntityIdList& entities) - { - AddActionToSequence([=]() { m_dispatcher.SetSelectedEntities(entities); }); - } - - void RetainedModeActionDispatcher::EnterComponentModeImpl(const AZ::Uuid& uuid) - { - AddActionToSequence([=]() { m_dispatcher.EnterComponentMode(uuid); }); - } - - RetainedModeActionDispatcher* RetainedModeActionDispatcher::ResetSequence() - { - Log("%s", "Resetting the action sequence"); - m_actions.clear(); - m_dispatcher.ResetEvent(); - m_locked = false; - return this; - } - - RetainedModeActionDispatcher* RetainedModeActionDispatcher::Execute() - { - Log("Executing %u actions", m_actions.size()); - for (auto& action : m_actions) - { - action(); - } - m_dispatcher.ResetEvent(); - m_locked = true; - return this; - } -} // namespace AzManipulatorTestFramework diff --git a/Code/Framework/AzManipulatorTestFramework/azmanipulatortestframework_files.cmake b/Code/Framework/AzManipulatorTestFramework/azmanipulatortestframework_files.cmake index 3fe9f4266c..9f480a3e2a 100644 --- a/Code/Framework/AzManipulatorTestFramework/azmanipulatortestframework_files.cmake +++ b/Code/Framework/AzManipulatorTestFramework/azmanipulatortestframework_files.cmake @@ -14,12 +14,10 @@ set(FILES Include/AzManipulatorTestFramework/DirectManipulatorViewportInteraction.h Include/AzManipulatorTestFramework/IndirectManipulatorViewportInteraction.h Include/AzManipulatorTestFramework/ImmediateModeActionDispatcher.h - Include/AzManipulatorTestFramework/RetainedModeActionDispatcher.h Include/AzManipulatorTestFramework/AzManipulatorTestFrameworkUtils.h Source/ViewportInteraction.cpp Source/DirectManipulatorViewportInteraction.cpp Source/IndirectManipulatorViewportInteraction.cpp Source/ImmediateModeActionDispatcher.cpp - Source/RetainedModeActionDispatcher.cpp Source/AzManipulatorTestFrameworkUtils.cpp ) diff --git a/Code/Framework/AzQtComponents/AzQtComponents/Components/FancyDocking.cpp b/Code/Framework/AzQtComponents/AzQtComponents/Components/FancyDocking.cpp index d73ebbec28..f60a98f392 100644 --- a/Code/Framework/AzQtComponents/AzQtComponents/Components/FancyDocking.cpp +++ b/Code/Framework/AzQtComponents/AzQtComponents/Components/FancyDocking.cpp @@ -2461,6 +2461,18 @@ namespace AzQtComponents placeholderRect.translate(0, -margins.bottom()); } + // Also adjust the placeholderRect by the relative dpi change from the original screen, since setGeometry uses the screen's + // virtualGeometry! + QScreen* fromScreen = dock->screen(); + QScreen* toScreen = Utilities::ScreenAtPoint(placeholderRect.topLeft()); + + if (fromScreen != toScreen) + { + qreal factorRatio = QHighDpiScaling::factor(fromScreen) / QHighDpiScaling::factor(toScreen); + placeholderRect.setWidth(aznumeric_cast(aznumeric_cast(placeholderRect.width()) * factorRatio)); + placeholderRect.setHeight(aznumeric_cast(aznumeric_cast(placeholderRect.height()) * factorRatio)); + } + // Place the floating dock widget makeDockWidgetFloating(dock, placeholderRect); clearDraggingState(); diff --git a/Code/Framework/AzQtComponents/AzQtComponents/Components/StyleManager.cpp b/Code/Framework/AzQtComponents/AzQtComponents/Components/StyleManager.cpp index c7ec4d4c7a..8f659afb7e 100644 --- a/Code/Framework/AzQtComponents/AzQtComponents/Components/StyleManager.cpp +++ b/Code/Framework/AzQtComponents/AzQtComponents/Components/StyleManager.cpp @@ -169,6 +169,10 @@ namespace AzQtComponents initializeSearchPaths(application, engineRootPath); initializeFonts(); + QFont defaultFont("Open Sans"); + defaultFont.setPixelSize(12); + QApplication::setFont(defaultFont); + m_titleBarOverdrawHandler = TitleBarOverdrawHandler::createHandler(application, this); // The window decoration wrappers require the titlebar overdraw handler diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Viewport/ViewportMessages.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Viewport/ViewportMessages.h index c90c506a05..8bddeb7a44 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Viewport/ViewportMessages.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Viewport/ViewportMessages.h @@ -221,24 +221,6 @@ namespace AzToolsFramework using ViewportSettingsNotificationBus = AZ::EBus; - //! Requests to freeze the Viewport Input - //! Added to prevent a bug with the legacy CryEngine Viewport code that would - //! keep doing raycast tests even when no level is loaded, causing a crash. - class ViewportFreezeRequests - { - public: - //! Return if Viewport Input is frozen - virtual bool IsViewportInputFrozen() = 0; - //! Sets the Viewport Input freeze state - virtual void FreezeViewportInput(bool freeze) = 0; - - protected: - ~ViewportFreezeRequests() = default; - }; - - //! Type to inherit to implement ViewportFreezeRequests. - using ViewportFreezeRequestBus = AZ::EBus; - //! Viewport requests that are only guaranteed to be serviced by the Main Editor viewport. class MainEditorViewportInteractionRequests { diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.cpp index 3db8492d70..5a7b097e29 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.cpp @@ -1770,6 +1770,12 @@ namespace AzToolsFramework return false; } + void EditorTransformComponentSelection::ChangeSelectedEntity(const AZ::EntityId entityId) + { + DeselectEntities(); + SelectDeselect(entityId); + } + bool EditorTransformComponentSelection::HandleMouseInteraction(const ViewportInteraction::MouseInteractionEvent& mouseInteraction) { AZ_PROFILE_FUNCTION(AzToolsFramework); @@ -1821,202 +1827,239 @@ namespace AzToolsFramework return true; } - // double click to deselect all - if (Input::DeselectAll(mouseInteraction)) + if (ed_viewportStickySelect) { - // note: even if m_selectedEntityIds is technically empty, we - // may still have an entity selected that was clicked in the - // entity outliner - we still want to make sure the deselect all - // action clears the selection - DeselectEntities(); - return false; + // double click to deselect all + if (Input::DeselectAll(mouseInteraction)) + { + // note: even if m_selectedEntityIds is technically empty, we + // may still have an entity selected that was clicked in the + // entity outliner - we still want to make sure the deselect all + // action clears the selection + DeselectEntities(); + return false; + } + } + + // select/deselect (add/remove) entities with ctrl held + if (Input::AdditiveIndividualSelect(clickOutcome, mouseInteraction)) + { + if (SelectDeselect(entityIdUnderCursor)) + { + if (m_selectedEntityIds.empty()) + { + m_pivotOverrideFrame.Reset(); + } + + return false; + } } if (!m_selectedEntityIds.empty()) { - // select/deselect (add/remove) entities with ctrl held - if (Input::AdditiveIndividualSelect(clickOutcome, mouseInteraction)) - { - if (SelectDeselect(entityIdUnderCursor)) - { - if (m_selectedEntityIds.empty()) - { - m_pivotOverrideFrame.Reset(); - } - - return false; - } - } - // group copying/alignment to specific entity - 'ditto' position/orientation for group - if (Input::GroupDitto(mouseInteraction)) + if (Input::GroupDitto(mouseInteraction) && PerformGroupDitto(entityIdUnderCursor)) { - if (entityIdUnderCursor.IsValid()) - { - AZ::Transform worldFromLocal = AZ::Transform::CreateIdentity(); - AZ::TransformBus::EventResult(worldFromLocal, entityIdUnderCursor, &AZ::TransformBus::Events::GetWorldTM); - - switch (m_mode) - { - case Mode::Rotation: - CopyOrientationToSelectedEntitiesGroup(QuaternionFromTransformNoScaling(worldFromLocal)); - break; - case Mode::Scale: - CopyScaleToSelectedEntitiesIndividualWorld(worldFromLocal.GetUniformScale()); - break; - case Mode::Translation: - CopyTranslationToSelectedEntitiesGroup(worldFromLocal.GetTranslation()); - break; - default: - // do nothing - break; - } - - return false; - } + return false; } // individual copying/alignment to specific entity - 'ditto' position/orientation for individual - if (Input::IndividualDitto(mouseInteraction)) + if (Input::IndividualDitto(mouseInteraction) && PerformIndividualDitto(entityIdUnderCursor)) { - if (entityIdUnderCursor.IsValid()) - { - AZ::Transform worldFromLocal = AZ::Transform::CreateIdentity(); - AZ::TransformBus::EventResult(worldFromLocal, entityIdUnderCursor, &AZ::TransformBus::Events::GetWorldTM); - - switch (m_mode) - { - case Mode::Rotation: - CopyOrientationToSelectedEntitiesIndividual(QuaternionFromTransformNoScaling(worldFromLocal)); - break; - case Mode::Scale: - CopyScaleToSelectedEntitiesIndividualWorld(worldFromLocal.GetUniformScale()); - break; - case Mode::Translation: - CopyTranslationToSelectedEntitiesIndividual(worldFromLocal.GetTranslation()); - break; - default: - // do nothing - break; - } - - return false; - } + return false; } // try snapping to the terrain (if in Translation mode) and entity wasn't picked if (Input::SnapTerrain(mouseInteraction)) { - for (AZ::EntityId entityId : m_selectedEntityIds) - { - ScopedUndoBatch::MarkEntityDirty(entityId); - } - - if (m_mode == Mode::Translation) - { - const AZ::Vector3 finalSurfacePosition = PickTerrainPosition(mouseInteraction.m_mouseInteraction); - - // handle modifier alternatives - if (Input::IndividualDitto(mouseInteraction)) - { - CopyTranslationToSelectedEntitiesIndividual(finalSurfacePosition); - } - else if (Input::GroupDitto(mouseInteraction)) - { - CopyTranslationToSelectedEntitiesGroup(finalSurfacePosition); - } - } - else if (m_mode == Mode::Rotation) - { - // handle modifier alternatives - if (Input::IndividualDitto(mouseInteraction)) - { - CopyOrientationToSelectedEntitiesIndividual(AZ::Quaternion::CreateIdentity()); - } - else if (Input::GroupDitto(mouseInteraction)) - { - CopyOrientationToSelectedEntitiesGroup(AZ::Quaternion::CreateIdentity()); - } - } - + PerformSnapToTerrain(mouseInteraction); return false; } // set manipulator pivot override translation or orientation (update manipulators) if (Input::ManipulatorDitto(mouseInteraction)) { - if (m_entityIdManipulators.m_manipulators) - { - ScopedUndoBatch undoBatch(s_dittoManipulatorUndoRedoDesc); - - auto manipulatorCommand = - AZStd::make_unique(CreateManipulatorCommandStateFromSelf(), s_manipulatorUndoRedoName); - - if (entityIdUnderCursor.IsValid()) - { - AZ::Transform worldFromLocal = AZ::Transform::CreateIdentity(); - AZ::TransformBus::EventResult(worldFromLocal, entityIdUnderCursor, &AZ::TransformBus::Events::GetWorldTM); - - // set orientation/translation to match picked entity - switch (m_mode) - { - case Mode::Rotation: - OverrideManipulatorOrientation(QuaternionFromTransformNoScaling(worldFromLocal)); - break; - case Mode::Translation: - OverrideManipulatorTranslation(worldFromLocal.GetTranslation()); - break; - case Mode::Scale: - // do nothing - break; - default: - break; - } - - // only update pivot override when in translation or rotation mode - switch (m_mode) - { - case Mode::Rotation: - m_pivotOverrideFrame.m_pickTypes |= OptionalFrame::PickType::Orientation; - [[fallthrough]]; - case Mode::Translation: - m_pivotOverrideFrame.m_pickTypes |= OptionalFrame::PickType::Translation; - m_pivotOverrideFrame.m_pickedEntityIdOverride = entityIdUnderCursor; - break; - case Mode::Scale: - // do nothing - break; - default: - break; - } - } - else - { - // match the same behavior as if we pressed Ctrl+R to reset the manipulator - DelegateClearManipulatorOverride(); - } - - manipulatorCommand->SetManipulatorAfter(EntityManipulatorCommand::State( - BuildPivotOverride(m_pivotOverrideFrame.HasTranslationOverride(), m_pivotOverrideFrame.HasOrientationOverride()), - m_entityIdManipulators.m_manipulators->GetLocalTransform(), entityIdUnderCursor)); - - manipulatorCommand->SetParent(undoBatch.GetUndoBatch()); - manipulatorCommand.release(); - } + PerformManipulatorDitto(entityIdUnderCursor); + return false; } - return false; + if (ed_viewportStickySelect) + { + return false; + } } // standard toggle selection if (Input::IndividualSelect(clickOutcome)) { - SelectDeselect(entityIdUnderCursor); + if (!ed_viewportStickySelect) + { + ChangeSelectedEntity(entityIdUnderCursor); + } + else + { + SelectDeselect(entityIdUnderCursor); + } } return false; } + bool EditorTransformComponentSelection::PerformGroupDitto(const AZ::EntityId entityId) + { + if (entityId.IsValid()) + { + AZ::Transform worldFromLocal = AZ::Transform::CreateIdentity(); + AZ::TransformBus::EventResult(worldFromLocal, entityId, &AZ::TransformBus::Events::GetWorldTM); + + switch (m_mode) + { + case Mode::Rotation: + CopyOrientationToSelectedEntitiesGroup(QuaternionFromTransformNoScaling(worldFromLocal)); + break; + case Mode::Scale: + CopyScaleToSelectedEntitiesIndividualWorld(worldFromLocal.GetUniformScale()); + break; + case Mode::Translation: + CopyTranslationToSelectedEntitiesGroup(worldFromLocal.GetTranslation()); + break; + default: + // do nothing + break; + } + + return true; + } + + return false; + } + + bool EditorTransformComponentSelection::PerformIndividualDitto(const AZ::EntityId entityId) + { + if (entityId.IsValid()) + { + AZ::Transform worldFromLocal = AZ::Transform::CreateIdentity(); + AZ::TransformBus::EventResult(worldFromLocal, entityId, &AZ::TransformBus::Events::GetWorldTM); + + switch (m_mode) + { + case Mode::Rotation: + CopyOrientationToSelectedEntitiesIndividual(QuaternionFromTransformNoScaling(worldFromLocal)); + break; + case Mode::Scale: + CopyScaleToSelectedEntitiesIndividualWorld(worldFromLocal.GetUniformScale()); + break; + case Mode::Translation: + CopyTranslationToSelectedEntitiesIndividual(worldFromLocal.GetTranslation()); + break; + default: + // do nothing + break; + } + + return true; + } + + return false; + } + + void EditorTransformComponentSelection::PerformSnapToTerrain(const ViewportInteraction::MouseInteractionEvent& mouseInteraction) + { + for (AZ::EntityId entityId : m_selectedEntityIds) + { + ScopedUndoBatch::MarkEntityDirty(entityId); + } + + if (m_mode == Mode::Translation) + { + const AZ::Vector3 finalSurfacePosition = PickTerrainPosition(mouseInteraction.m_mouseInteraction); + + // handle modifier alternatives + if (Input::IndividualDitto(mouseInteraction)) + { + CopyTranslationToSelectedEntitiesIndividual(finalSurfacePosition); + } + else if (Input::GroupDitto(mouseInteraction)) + { + CopyTranslationToSelectedEntitiesGroup(finalSurfacePosition); + } + } + else if (m_mode == Mode::Rotation) + { + // handle modifier alternatives + if (Input::IndividualDitto(mouseInteraction)) + { + CopyOrientationToSelectedEntitiesIndividual(AZ::Quaternion::CreateIdentity()); + } + else if (Input::GroupDitto(mouseInteraction)) + { + CopyOrientationToSelectedEntitiesGroup(AZ::Quaternion::CreateIdentity()); + } + } + } + + void EditorTransformComponentSelection::PerformManipulatorDitto(const AZ::EntityId entityId) + { + if (m_entityIdManipulators.m_manipulators) + { + ScopedUndoBatch undoBatch(s_dittoManipulatorUndoRedoDesc); + + auto manipulatorCommand = + AZStd::make_unique(CreateManipulatorCommandStateFromSelf(), s_manipulatorUndoRedoName); + + if (entityId.IsValid()) + { + AZ::Transform worldFromLocal = AZ::Transform::CreateIdentity(); + AZ::TransformBus::EventResult(worldFromLocal, entityId, &AZ::TransformBus::Events::GetWorldTM); + + // set orientation/translation to match picked entity + switch (m_mode) + { + case Mode::Rotation: + OverrideManipulatorOrientation(QuaternionFromTransformNoScaling(worldFromLocal)); + break; + case Mode::Translation: + OverrideManipulatorTranslation(worldFromLocal.GetTranslation()); + break; + case Mode::Scale: + // do nothing + break; + default: + break; + } + + // only update pivot override when in translation or rotation mode + switch (m_mode) + { + case Mode::Rotation: + m_pivotOverrideFrame.m_pickTypes |= OptionalFrame::PickType::Orientation; + [[fallthrough]]; + case Mode::Translation: + m_pivotOverrideFrame.m_pickTypes |= OptionalFrame::PickType::Translation; + m_pivotOverrideFrame.m_pickedEntityIdOverride = entityId; + break; + case Mode::Scale: + // do nothing + break; + default: + break; + } + } + else + { + // match the same behavior as if we pressed Ctrl+R to reset the manipulator + DelegateClearManipulatorOverride(); + } + + manipulatorCommand->SetManipulatorAfter(EntityManipulatorCommand::State( + BuildPivotOverride(m_pivotOverrideFrame.HasTranslationOverride(), m_pivotOverrideFrame.HasOrientationOverride()), + m_entityIdManipulators.m_manipulators->GetLocalTransform(), entityId)); + + manipulatorCommand->SetParent(undoBatch.GetUndoBatch()); + manipulatorCommand.release(); + } + } + template static void AddAction( AZStd::vector>& actions, diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.h b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.h index 35d3587d0b..478c0b775e 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.h @@ -207,6 +207,7 @@ namespace AzToolsFramework void SetSelectedEntities(const EntityIdList& entityIds); void DeselectEntities(); bool SelectDeselect(AZ::EntityId entityId); + void ChangeSelectedEntity(AZ::EntityId entityId); void RefreshSelectedEntityIds(); void RefreshSelectedEntityIds(const EntityIdList& selectedEntityIds); @@ -298,6 +299,11 @@ namespace AzToolsFramework void SetEntityLocalRotation(AZ::EntityId entityId, const AZ::Vector3& localRotation); void SetEntityLocalRotation(AZ::EntityId entityId, const AZ::Quaternion& localRotation); + bool PerformGroupDitto(AZ::EntityId entityId); + bool PerformIndividualDitto(AZ::EntityId entityId); + void PerformManipulatorDitto(AZ::EntityId entityId); + void PerformSnapToTerrain(const ViewportInteraction::MouseInteractionEvent& mouseInteraction); + //! Responsible for keeping the space cluster in sync with the current reference frame. void UpdateSpaceCluster(ReferenceFrame referenceFrame); diff --git a/Code/Framework/AzToolsFramework/Tests/EditorTransformComponentSelectionTests.cpp b/Code/Framework/AzToolsFramework/Tests/EditorTransformComponentSelectionTests.cpp index 8a958af567..241ee3f576 100644 --- a/Code/Framework/AzToolsFramework/Tests/EditorTransformComponentSelectionTests.cpp +++ b/Code/Framework/AzToolsFramework/Tests/EditorTransformComponentSelectionTests.cpp @@ -614,7 +614,7 @@ namespace UnitTest using EditorTransformComponentSelectionViewportPickingManipulatorTestFixture = IndirectCallManipulatorViewportInteractionFixtureMixin; - TEST_F(EditorTransformComponentSelectionViewportPickingManipulatorTestFixture, SingleClickWithNoSelectionWillSelectEntity) + TEST_F(EditorTransformComponentSelectionViewportPickingManipulatorTestFixture, StickySingleClickWithNoSelectionWillSelectEntity) { AzToolsFramework::ed_viewportStickySelect = true; @@ -637,19 +637,44 @@ namespace UnitTest EXPECT_THAT(selectedEntitiesAfter.front(), Eq(m_entityId1)); } - TEST_F(EditorTransformComponentSelectionViewportPickingManipulatorTestFixture, SingleClickOffEntityWithSelectionWillNotDeselectEntity) + TEST_F(EditorTransformComponentSelectionViewportPickingManipulatorTestFixture, UnstickySingleClickWithNoSelectionWillSelectEntity) + { + AzToolsFramework::ed_viewportStickySelect = false; + + PositionEntities(); + PositionCamera(m_cameraState); + + using ::testing::Eq; + auto selectedEntitiesBefore = SelectedEntities(); + EXPECT_TRUE(selectedEntitiesBefore.empty()); + + // calculate the position in screen space of the initial entity position + const auto entity1ScreenPosition = AzFramework::WorldToScreen(m_entity1WorldTranslation, m_cameraState); + + // click the entity in the viewport + m_actionDispatcher->CameraState(m_cameraState)->MousePosition(entity1ScreenPosition)->MouseLButtonDown()->MouseLButtonUp(); + + // entity is selected + auto selectedEntitiesAfter = SelectedEntities(); + EXPECT_THAT(selectedEntitiesAfter.size(), Eq(1)); + EXPECT_THAT(selectedEntitiesAfter.front(), Eq(m_entityId1)); + } + + TEST_F( + EditorTransformComponentSelectionViewportPickingManipulatorTestFixture, + StickySingleClickOffEntityWithSelectionWillNotDeselectEntity) { AzToolsFramework::ed_viewportStickySelect = true; PositionEntities(); PositionCamera(m_cameraState); - // position in space above the entity + // position in space above the entities const auto clickOffPositionWorld = AZ::Vector3(5.0f, 15.0f, 12.0f); AzToolsFramework::SelectEntity(m_entityId1); - // calculate the position in screen space of the initial position of the entity + // calculate the screen space position of the click const auto clickOffPositionScreen = AzFramework::WorldToScreen(clickOffPositionWorld, m_cameraState); // click the empty space in the viewport @@ -662,9 +687,32 @@ namespace UnitTest EXPECT_THAT(selectedEntitiesAfter.front(), Eq(m_entityId1)); } + TEST_F( + EditorTransformComponentSelectionViewportPickingManipulatorTestFixture, UnstickySingleClickOffEntityWithSelectionWillDeselectEntity) + { + AzToolsFramework::ed_viewportStickySelect = false; + + PositionEntities(); + PositionCamera(m_cameraState); + + AzToolsFramework::SelectEntity(m_entityId1); + + // position in space above the entities + const auto clickOffPositionWorld = AZ::Vector3(5.0f, 15.0f, 12.0f); + // calculate the screen space position of the click + const auto clickOffPositionScreen = AzFramework::WorldToScreen(clickOffPositionWorld, m_cameraState); + + // click the empty space in the viewport + m_actionDispatcher->CameraState(m_cameraState)->MousePosition(clickOffPositionScreen)->MouseLButtonDown()->MouseLButtonUp(); + + // entity was deselected + auto selectedEntitiesAfter = SelectedEntities(); + EXPECT_TRUE(selectedEntitiesAfter.empty()); + } + TEST_F( EditorTransformComponentSelectionViewportPickingManipulatorTestFixture, - SingleClickOnNewEntityWithSelectionWillNotChangeSelectedEntity) + StickySingleClickOnNewEntityWithSelectionWillNotChangeSelectedEntity) { AzToolsFramework::ed_viewportStickySelect = true; @@ -688,7 +736,31 @@ namespace UnitTest TEST_F( EditorTransformComponentSelectionViewportPickingManipulatorTestFixture, - CtrlSingleClickOnNewEntityWithSelectionWillAppendSelectedEntityToSelection) + UnstickySingleClickOnNewEntityWithSelectionWillChangeSelectedEntity) + { + AzToolsFramework::ed_viewportStickySelect = false; + + PositionEntities(); + PositionCamera(m_cameraState); + + AzToolsFramework::SelectEntity(m_entityId1); + + // calculate the position in screen space of the second entity + const auto entity2ScreenPosition = AzFramework::WorldToScreen(m_entity2WorldTranslation, m_cameraState); + + // click the entity in the viewport + m_actionDispatcher->CameraState(m_cameraState)->MousePosition(entity2ScreenPosition)->MouseLButtonDown()->MouseLButtonUp(); + + // entity selection was changed + using ::testing::Eq; + auto selectedEntitiesAfter = SelectedEntities(); + EXPECT_THAT(selectedEntitiesAfter.size(), Eq(1)); + EXPECT_THAT(selectedEntitiesAfter.front(), Eq(m_entityId2)); + } + + TEST_F( + EditorTransformComponentSelectionViewportPickingManipulatorTestFixture, + StickyCtrlSingleClickOnNewEntityWithSelectionWillAppendSelectedEntityToSelection) { AzToolsFramework::ed_viewportStickySelect = true; @@ -715,7 +787,34 @@ namespace UnitTest TEST_F( EditorTransformComponentSelectionViewportPickingManipulatorTestFixture, - CtrlSingleClickOnEntityInSelectionWillRemoveEntityFromSelection) + UnstickyCtrlSingleClickOnNewEntityWithSelectionWillAppendSelectedEntityToSelection) + { + AzToolsFramework::ed_viewportStickySelect = false; + + PositionEntities(); + PositionCamera(m_cameraState); + + AzToolsFramework::SelectEntity(m_entityId1); + + // calculate the position in screen space of the second entity + const auto entity2ScreenPosition = AzFramework::WorldToScreen(m_entity2WorldTranslation, m_cameraState); + + // click the entity in the viewport + m_actionDispatcher->CameraState(m_cameraState) + ->MousePosition(entity2ScreenPosition) + ->KeyboardModifierDown(AzToolsFramework::ViewportInteraction::KeyboardModifier::Control) + ->MouseLButtonDown() + ->MouseLButtonUp(); + + // entity selection was changed (one entity selected to two) + using ::testing::UnorderedElementsAre; + auto selectedEntitiesAfter = SelectedEntities(); + EXPECT_THAT(selectedEntitiesAfter, UnorderedElementsAre(m_entityId1, m_entityId2)); + } + + TEST_F( + EditorTransformComponentSelectionViewportPickingManipulatorTestFixture, + StickyCtrlSingleClickOnEntityInSelectionWillRemoveEntityFromSelection) { AzToolsFramework::ed_viewportStickySelect = true; @@ -740,7 +839,36 @@ namespace UnitTest EXPECT_THAT(selectedEntitiesAfter, UnorderedElementsAre(m_entityId1)); } - TEST_F(EditorTransformComponentSelectionViewportPickingManipulatorTestFixture, BoxSelectWithNoInitialSelectionAddsEntitiesToSelection) + TEST_F( + EditorTransformComponentSelectionViewportPickingManipulatorTestFixture, + UnstickyCtrlSingleClickOnEntityInSelectionWillRemoveEntityFromSelection) + { + AzToolsFramework::ed_viewportStickySelect = false; + + PositionEntities(); + PositionCamera(m_cameraState); + + AzToolsFramework::SelectEntities({ m_entityId1, m_entityId2 }); + + // calculate the position in screen space of the second entity + const auto entity2ScreenPosition = AzFramework::WorldToScreen(m_entity2WorldTranslation, m_cameraState); + + // click the entity in the viewport + m_actionDispatcher->CameraState(m_cameraState) + ->MousePosition(entity2ScreenPosition) + ->KeyboardModifierDown(AzToolsFramework::ViewportInteraction::KeyboardModifier::Control) + ->MouseLButtonDown() + ->MouseLButtonUp(); + + // entity selection was changed (entity2 was deselected) + using ::testing::UnorderedElementsAre; + auto selectedEntitiesAfter = SelectedEntities(); + EXPECT_THAT(selectedEntitiesAfter, UnorderedElementsAre(m_entityId1)); + } + + TEST_F( + EditorTransformComponentSelectionViewportPickingManipulatorTestFixture, + BoxSelectWithNoInitialSelectionAddsEntitiesToSelection) { AzToolsFramework::ed_viewportStickySelect = true; @@ -835,6 +963,32 @@ namespace UnitTest EXPECT_TRUE(selectedEntitiesAfter.empty()); } + TEST_F(EditorTransformComponentSelectionViewportPickingManipulatorTestFixture, StickyDoubleClickWithSelectionWillDeselectEntities) + { + AzToolsFramework::ed_viewportStickySelect = true; + + PositionEntities(); + PositionCamera(m_cameraState); + + AzToolsFramework::SelectEntities({ m_entityId1, m_entityId2, m_entityId3 }); + + using ::testing::UnorderedElementsAre; + auto selectedEntitiesBefore = SelectedEntities(); + EXPECT_THAT(selectedEntitiesBefore, UnorderedElementsAre(m_entityId1, m_entityId2, m_entityId3)); + + // position in space above the entities + const auto clickOffPositionWorld = AZ::Vector3(5.0f, 15.0f, 12.0f); + // calculate the screen space position of the click + const auto clickOffPositionScreen = AzFramework::WorldToScreen(clickOffPositionWorld, m_cameraState); + + // double click to deselect entities + m_actionDispatcher->CameraState(m_cameraState)->MousePosition(clickOffPositionScreen)->MouseLButtonDoubleClick(); + + // no entities are selected + auto selectedEntitiesAfter = SelectedEntities(); + EXPECT_TRUE(selectedEntitiesAfter.empty()); + } + using EditorTransformComponentSelectionManipulatorTestFixture = IndirectCallManipulatorViewportInteractionFixtureMixin; diff --git a/Gems/Atom/Asset/Shader/Code/Source/Editor/AzslCompiler.cpp b/Gems/Atom/Asset/Shader/Code/Source/Editor/AzslCompiler.cpp index a82c731d7b..79a39ff793 100644 --- a/Gems/Atom/Asset/Shader/Code/Source/Editor/AzslCompiler.cpp +++ b/Gems/Atom/Asset/Shader/Code/Source/Editor/AzslCompiler.cpp @@ -1150,7 +1150,7 @@ namespace AZ return BuildResult::CompilationFailed; } - auto readJsonResult = JsonSerializationUtils::ReadJsonFile(outputFile, AZ::RPI::JsonUtils::AtomMaxFileSize); + auto readJsonResult = JsonSerializationUtils::ReadJsonFile(outputFile, AZ::RPI::JsonUtils::DefaultMaxFileSize); if (readJsonResult.IsSuccess()) { @@ -1171,7 +1171,7 @@ namespace AZ AZStd::string outputFile = m_inputFilePath; AzFramework::StringFunc::Path::ReplaceExtension(outputFile, outputExtension); - auto readJsonResult = JsonSerializationUtils::ReadJsonFile(outputFile, AZ::RPI::JsonUtils::AtomMaxFileSize); + auto readJsonResult = JsonSerializationUtils::ReadJsonFile(outputFile, AZ::RPI::JsonUtils::DefaultMaxFileSize); if (readJsonResult.IsSuccess()) { diff --git a/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderAssetBuilder.cpp b/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderAssetBuilder.cpp index 6b673759cf..6673da2be3 100644 --- a/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderAssetBuilder.cpp +++ b/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderAssetBuilder.cpp @@ -556,7 +556,7 @@ namespace AZ shaderAssetCreator.SetRenderStates(renderStates); } - Outcome hlslSourceCodeOutcome = Utils::ReadFile(hlslFullPath, AZ::RPI::JsonUtils::AtomMaxFileSize); + Outcome hlslSourceCodeOutcome = Utils::ReadFile(hlslFullPath, AZ::RPI::JsonUtils::DefaultMaxFileSize); if (!hlslSourceCodeOutcome.IsSuccess()) { AZ_Error( diff --git a/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderBuilderUtility.cpp b/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderBuilderUtility.cpp index 9c616569bd..e47dd55a97 100644 --- a/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderBuilderUtility.cpp +++ b/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderBuilderUtility.cpp @@ -52,7 +52,7 @@ namespace AZ { RPI::ShaderSourceData shaderSourceData; - auto document = JsonSerializationUtils::ReadJsonFile(fullPathToJsonFile, AZ::RPI::JsonUtils::AtomMaxFileSize); + auto document = JsonSerializationUtils::ReadJsonFile(fullPathToJsonFile, AZ::RPI::JsonUtils::DefaultMaxFileSize); if (!document.IsSuccess()) { @@ -128,7 +128,7 @@ namespace AZ AZStd::unordered_map> outcomes; for (int i : indicesOfInterest) { - outcomes[i] = JsonSerializationUtils::ReadJsonFile(pathOfJsonFiles[i], AZ::RPI::JsonUtils::AtomMaxFileSize); + outcomes[i] = JsonSerializationUtils::ReadJsonFile(pathOfJsonFiles[i], AZ::RPI::JsonUtils::DefaultMaxFileSize); if (!outcomes[i].IsSuccess()) { AZ_Error(builderName, false, "%s", outcomes[i].GetError().c_str()); @@ -623,7 +623,7 @@ namespace AZ StructData inputStruct; inputStruct.m_id = ""; - auto jsonOutcome = JsonSerializationUtils::ReadJsonFile(pathToIaJson, AZ::RPI::JsonUtils::AtomMaxFileSize); + auto jsonOutcome = JsonSerializationUtils::ReadJsonFile(pathToIaJson, AZ::RPI::JsonUtils::DefaultMaxFileSize); if (!jsonOutcome.IsSuccess()) { AZ_Error(ShaderBuilderUtilityName, false, "%s", jsonOutcome.GetError().c_str()); @@ -716,7 +716,7 @@ namespace AZ StructData outputStruct; outputStruct.m_id = ""; - auto jsonOutcome = JsonSerializationUtils::ReadJsonFile(pathToOmJson, AZ::RPI::JsonUtils::AtomMaxFileSize); + auto jsonOutcome = JsonSerializationUtils::ReadJsonFile(pathToOmJson, AZ::RPI::JsonUtils::DefaultMaxFileSize); if (!jsonOutcome.IsSuccess()) { AZ_Error(ShaderBuilderUtilityName, false, "%s", jsonOutcome.GetError().c_str()); diff --git a/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderVariantAssetBuilder.cpp b/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderVariantAssetBuilder.cpp index 5a552a2702..7c5be89508 100644 --- a/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderVariantAssetBuilder.cpp +++ b/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderVariantAssetBuilder.cpp @@ -478,7 +478,7 @@ namespace AZ RPI::Ptr shaderOptionGroupLayout = RPI::ShaderOptionGroupLayout::Create(); // The shader options define what options are available, what are the allowed values/range // for each option and what is its default value. - auto jsonOutcome = JsonSerializationUtils::ReadJsonFile(optionsGroupJsonPath, AZ::RPI::JsonUtils::AtomMaxFileSize); + auto jsonOutcome = JsonSerializationUtils::ReadJsonFile(optionsGroupJsonPath, AZ::RPI::JsonUtils::DefaultMaxFileSize); if (!jsonOutcome.IsSuccess()) { AZ_Error(ShaderVariantAssetBuilderName, false, "%s", jsonOutcome.GetError().c_str()); @@ -509,7 +509,7 @@ namespace AZ } auto functionsJsonPath = functionsJsonPathOutcome.TakeValue(); - auto jsonOutcome = JsonSerializationUtils::ReadJsonFile(functionsJsonPath, AZ::RPI::JsonUtils::AtomMaxFileSize); + auto jsonOutcome = JsonSerializationUtils::ReadJsonFile(functionsJsonPath, AZ::RPI::JsonUtils::DefaultMaxFileSize); if (!jsonOutcome.IsSuccess()) { AZ_Error(ShaderVariantAssetBuilderName, false, "%s", jsonOutcome.GetError().c_str()); @@ -541,7 +541,7 @@ namespace AZ } auto srgJsonPath = srgJsonPathOutcome.TakeValue(); - auto jsonOutcome = JsonSerializationUtils::ReadJsonFile(srgJsonPath, AZ::RPI::JsonUtils::AtomMaxFileSize); + auto jsonOutcome = JsonSerializationUtils::ReadJsonFile(srgJsonPath, AZ::RPI::JsonUtils::DefaultMaxFileSize); if (!jsonOutcome.IsSuccess()) { AZ_Error(ShaderVariantAssetBuilderName, false, "%s", jsonOutcome.GetError().c_str()); @@ -598,7 +598,7 @@ namespace AZ } auto bindingsJsonPath = bindingsJsonPathOutcome.TakeValue(); - auto jsonOutcome = JsonSerializationUtils::ReadJsonFile(bindingsJsonPath, AZ::RPI::JsonUtils::AtomMaxFileSize); + auto jsonOutcome = JsonSerializationUtils::ReadJsonFile(bindingsJsonPath, AZ::RPI::JsonUtils::DefaultMaxFileSize); if (!jsonOutcome.IsSuccess()) { AZ_Error(ShaderVariantAssetBuilderName, false, "%s", jsonOutcome.GetError().c_str()); @@ -630,7 +630,7 @@ namespace AZ } hlslSourcePath = hlslSourcePathOutcome.TakeValue(); - Outcome hlslSourceOutcome = Utils::ReadFile(hlslSourcePath, AZ::RPI::JsonUtils::AtomMaxFileSize); + Outcome hlslSourceOutcome = Utils::ReadFile(hlslSourcePath, AZ::RPI::JsonUtils::DefaultMaxFileSize); if (!hlslSourceOutcome.IsSuccess()) { AZ_Error( diff --git a/Gems/Atom/Bootstrap/Code/Include/Atom/Bootstrap/BootstrapNotificationBus.h b/Gems/Atom/Bootstrap/Code/Include/Atom/Bootstrap/BootstrapNotificationBus.h index e40bf39923..f4ce51fc02 100644 --- a/Gems/Atom/Bootstrap/Code/Include/Atom/Bootstrap/BootstrapNotificationBus.h +++ b/Gems/Atom/Bootstrap/Code/Include/Atom/Bootstrap/BootstrapNotificationBus.h @@ -56,7 +56,8 @@ namespace AZ ////////////////////////////////////////////////////////////////////////// - virtual void OnBootstrapSceneReady(AZ::RPI::Scene* bootstrapScene) = 0; + virtual void OnBootstrapSceneReady([[maybe_unused]]AZ::RPI::Scene* bootstrapScene){} + virtual void OnFrameRateLimitChanged([[maybe_unused]]float fpsLimit){} }; using NotificationBus = AZ::EBus; } // namespace Bootstrap diff --git a/Gems/Atom/Bootstrap/Code/Include/Atom/Bootstrap/BootstrapRequestBus.h b/Gems/Atom/Bootstrap/Code/Include/Atom/Bootstrap/BootstrapRequestBus.h index 21cc91420b..fbf3bad935 100644 --- a/Gems/Atom/Bootstrap/Code/Include/Atom/Bootstrap/BootstrapRequestBus.h +++ b/Gems/Atom/Bootstrap/Code/Include/Atom/Bootstrap/BootstrapRequestBus.h @@ -23,6 +23,8 @@ namespace AZ::Render::Bootstrap virtual AZ::RPI::ScenePtr GetOrCreateAtomSceneFromAzScene(AzFramework::Scene* scene) = 0; virtual bool EnsureDefaultRenderPipelineInstalledForScene(AZ::RPI::ScenePtr scene, AZ::RPI::ViewportContextPtr viewportContext) = 0; + virtual float GetFrameRateLimit() const = 0; + virtual void SetFrameRateLimit(float fpsLimit) = 0; protected: ~Request() = default; diff --git a/Gems/Atom/Bootstrap/Code/Source/BootstrapSystemComponent.cpp b/Gems/Atom/Bootstrap/Code/Source/BootstrapSystemComponent.cpp index d837988cfb..97faa7380f 100644 --- a/Gems/Atom/Bootstrap/Code/Source/BootstrapSystemComponent.cpp +++ b/Gems/Atom/Bootstrap/Code/Source/BootstrapSystemComponent.cpp @@ -41,7 +41,14 @@ #include #include +static void OnFrameRateLimitChanged(const float& fpsLimit) +{ + AZ::Render::Bootstrap::RequestBus::Broadcast( + &AZ::Render::Bootstrap::RequestBus::Events::SetFrameRateLimit, fpsLimit); +} + AZ_CVAR(AZ::CVarFixedString, r_default_pipeline_name, AZ_TRAIT_BOOTSTRAPSYSTEMCOMPONENT_PIPELINE_NAME, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "Default Render pipeline name"); +AZ_CVAR(float, r_fps_limit, 0, OnFrameRateLimitChanged, AZ::ConsoleFunctorFlags::Null, "The maximum framerate to render at, or 0 for unlimited"); namespace AZ { @@ -342,6 +349,22 @@ namespace AZ return true; } + float BootstrapSystemComponent::GetFrameRateLimit() const + { + return r_fps_limit; + } + + void BootstrapSystemComponent::SetFrameRateLimit(float fpsLimit) + { + r_fps_limit = fpsLimit; + if (m_viewportContext) + { + m_viewportContext->SetFpsLimit(r_fps_limit); + } + Render::Bootstrap::NotificationBus::Broadcast( + &Render::Bootstrap::NotificationBus::Events::OnFrameRateLimitChanged, fpsLimit); + } + void BootstrapSystemComponent::CreateDefaultRenderPipeline() { EnsureDefaultRenderPipelineInstalledForScene(m_defaultScene, m_viewportContext); @@ -381,23 +404,11 @@ namespace AZ } void BootstrapSystemComponent::OnTick([[maybe_unused]] float deltaTime, [[maybe_unused]] ScriptTimePoint time) - { - // Temp: When running in the launcher without the legacy renderer - // we need to call RenderTick on the viewport context each frame. - if (m_viewportContext) - { - AZ::ApplicationTypeQuery appType; - ComponentApplicationBus::Broadcast(&AZ::ComponentApplicationBus::Events::QueryApplicationType, appType); - if (appType.IsGame()) - { - m_viewportContext->RenderTick(); - } - } - } + { } int BootstrapSystemComponent::GetTickOrder() { - return TICK_LAST; + return TICK_PRE_RENDER; } void BootstrapSystemComponent::OnWindowClosed() diff --git a/Gems/Atom/Bootstrap/Code/Source/BootstrapSystemComponent.h b/Gems/Atom/Bootstrap/Code/Source/BootstrapSystemComponent.h index 566d19b1a4..438c6cb236 100644 --- a/Gems/Atom/Bootstrap/Code/Source/BootstrapSystemComponent.h +++ b/Gems/Atom/Bootstrap/Code/Source/BootstrapSystemComponent.h @@ -69,6 +69,8 @@ namespace AZ // Render::Bootstrap::RequestBus::Handler overrides ... AZ::RPI::ScenePtr GetOrCreateAtomSceneFromAzScene(AzFramework::Scene* scene) override; bool EnsureDefaultRenderPipelineInstalledForScene(AZ::RPI::ScenePtr scene, AZ::RPI::ViewportContextPtr viewportContext) override; + float GetFrameRateLimit() const override; + void SetFrameRateLimit(float fpsLimit) override; protected: // Component overrides ... diff --git a/Gems/Atom/RHI/Code/Source/RHI/CpuProfilerImpl.cpp b/Gems/Atom/RHI/Code/Source/RHI/CpuProfilerImpl.cpp index 8099fc3a32..e16b89b5cd 100644 --- a/Gems/Atom/RHI/Code/Source/RHI/CpuProfilerImpl.cpp +++ b/Gems/Atom/RHI/Code/Source/RHI/CpuProfilerImpl.cpp @@ -144,7 +144,7 @@ namespace AZ // Try to lock here, the shutdownMutex will only be contested when the CpuProfiler is shutting down. if (m_shutdownMutex.try_lock_shared()) { - if (m_enabled) + if (m_enabled && ms_threadLocalStorage != nullptr) { ms_threadLocalStorage->RegionStackPopBack(); } diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Common/JsonUtils.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Common/JsonUtils.h index 913a9702b1..4715acd64c 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Common/JsonUtils.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Common/JsonUtils.h @@ -22,7 +22,7 @@ namespace AZ { //! Protects from allocating too much memory. The choice of a 1MB threshold is arbitrary. //! If you need to work with larger files, please use AZ::IO directly instead of these utility functions. - inline constexpr size_t AtomMaxFileSize = 1024 * 1024; + inline constexpr size_t DefaultMaxFileSize = 1024 * 1024; // Declarations... @@ -43,7 +43,7 @@ namespace AZ { objectData = ObjectType(); - auto loadOutcome = AZ::JsonSerializationUtils::ReadJsonFile(path, AtomMaxFileSize); + auto loadOutcome = AZ::JsonSerializationUtils::ReadJsonFile(path, DefaultMaxFileSize); if (!loadOutcome.IsSuccess()) { AZ_Error("AZ::RPI::JsonUtils", false, "%s", loadOutcome.GetError().c_str()); diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/RenderPipeline.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/RenderPipeline.h index 90389687de..cc25aa17c4 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/RenderPipeline.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/RenderPipeline.h @@ -161,19 +161,25 @@ namespace AZ //! Add this RenderPipeline to RPI system's RenderTick and it will be rendered whenever //! the RPI system's RenderTick is called. - //! The RenderPipeline is rendered per RenderTick by default unless AddToRenderTickOnce() was called. + //! The RenderPipeline is rendered per RenderTick by default. void AddToRenderTick(); + //! Add this RenderPipeline to RPI system's RenderTick and it will be rendered every RenderTick + //! after the specified interval has elapsed since the last rendered frame. + //! @param renderInterval The desired time between rendered frames, in seconds. + void AddToRenderTickAtInterval(AZStd::chrono::duration renderInterval); + //! Disable render for this RenderPipeline void RemoveFromRenderTick(); ~RenderPipeline(); - + enum class RenderMode : uint8_t { - RenderEveryTick, // Render at each RPI system render tick - RenderOnce, // Render once in next RPI system render tick - NoRender // Render disabled. + RenderEveryTick, //!< Render at each RPI system render tick. + RenderAtTargetRate, //!< Render on RPI system render tick after a target refresh rate interval has passed. + RenderOnce, //!< Render once in next RPI system render tick. + NoRender //!< Render disabled. }; //! Get current render mode @@ -185,6 +191,12 @@ namespace AZ //! Get draw filter mask RHI::DrawFilterMask GetDrawFilterMask() const; + using FrameNotificationEvent = AZ::Event<>; + //! Notifies a listener when a frame is about to be prepared for render, before SRGs are bound. + void ConnectPrepareFrameHandler(FrameNotificationEvent::Handler& handler); + //! Notifies a listener when the rendering of a frame has finished + void ConnectEndFrameHandler(FrameNotificationEvent::Handler& handler); + private: RenderPipeline() = default; @@ -202,8 +214,11 @@ namespace AZ void OnAddedToScene(Scene* scene); void OnRemovedFromScene(Scene* scene); + // Called before this pipeline is about to be rendered and before SRGs are bound. + void OnPrepareFrame(); + // Called when this pipeline is about to be rendered - void OnStartFrame(const TickTimeInfo& tick); + void OnStartFrame(); // Called when the rendering of current frame is finished. void OnFrameEnd(); @@ -228,8 +243,14 @@ namespace AZ PipelineViewMap m_pipelineViewsByTag; - /// The system time when the last time this pipeline render was started - float m_lastRenderStartTime = 0; + // The system time when the last time this pipeline render was started + AZStd::chrono::system_clock::time_point m_lastRenderStartTime; + + // The current system time, as of OnPrepareFrame's execution. + AZStd::chrono::system_clock::time_point m_lastRenderRequestTime; + + // The target time between renders when m_renderMode is RenderMode::RenderAtTargetRate + AZStd::chrono::duration m_targetRefreshRate; // RenderPipeline's name id, it will be used to identify the render pipeline when it's added to a Scene RenderPipelineId m_nameId; @@ -259,7 +280,11 @@ namespace AZ RHI::DrawFilterTag m_drawFilterTag; // A mask to filter draw items submitted by passes of this render pipeline. // This mask is created from the value of m_drawFilterTag. - RHI::DrawFilterMask m_drawFilterMask = 0; + RHI::DrawFilterMask m_drawFilterMask = 0; + + // Events for notification on render state + FrameNotificationEvent m_prepareFrameEvent; + FrameNotificationEvent m_endFrameEvent; }; } // namespace RPI diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/SceneBus.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/SceneBus.h index ca531d2de6..748c470edf 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/SceneBus.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/SceneBus.h @@ -67,6 +67,9 @@ namespace AZ //! Notifies when the PrepareRender phase is ending virtual void OnEndPrepareRender() {} + + //! Notifies when the render tick for a given frame has finished. + virtual void OnFrameEnd() {} }; using SceneNotificationBus = AZ::EBus; diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/ViewportContext.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/ViewportContext.h index 966e6b3016..feed24a80d 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/ViewportContext.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/ViewportContext.h @@ -51,9 +51,21 @@ namespace AZ //! Sets the root scene associated with this viewport. //! This does not provide a default render pipeline, one must be provided to enable rendering. void SetRenderScene(ScenePtr scene); - //! Runs one simulation and render tick and renders a frame to this viewport's window. - //! @note This is likely to be replaced by a tick management system in the RPI. - void RenderTick(); + + //! Gets the maximum frame rate this viewport context's pipeline can render at, 0 for unlimited. + //! The target framerate for the pipeline will be determined by this frame limit and the + //! vsync settings for the current window. + float GetFpsLimit() const; + + //! Sets the maximum frame rate this viewport context's pipeline can render at, 0 for unlimited. + //! The target framerate for the pipeline will be determined by this frame limit and the + //! vsync settings for the current window. + void SetFpsLimit(float fpsLimit); + + //! Gets the target frame rate for this viewport context. + //! This returns the lowest of either the current VSync refresh rate + //! or 0 for an unlimited frame rate (if there's no FPS limit and vsync is off). + float GetTargetFrameRate() const; //! Gets the current name of this ViewportContext. //! This name is used to tie this ViewportContext to its View stack, and ViewportContexts may be @@ -74,19 +86,28 @@ namespace AZ //! \see AzFramework::WindowRequests::GetDpiScaleFactor float GetDpiScalingFactor() const; + //! Gets the current vsync interval, as a divisor of the current refresh rate. + //! A value of 0 indicates that vsync is disabled. + uint32_t GetVsyncInterval() const; + + //! Gets the current display refresh rate, in frames per second. + uint32_t GetRefreshRate() const; + // SceneNotificationBus interface overrides... //! Ensures our default view remains set when our scene's render pipelines are modified. void OnRenderPipelineAdded(RenderPipelinePtr pipeline) override; //! Ensures our default view remains set when our scene's render pipelines are modified. void OnRenderPipelineRemoved(RenderPipeline* pipeline) override; - //! OnBeginPrepareRender is forwarded to our RenderTick notification to allow subscribers to do rendering. - void OnBeginPrepareRender() override; // WindowNotificationBus interface overrides... //! Used to fire a notification when our window resizes. void OnWindowResized(uint32_t width, uint32_t height) override; //! Used to fire a notification when our window DPI changes. void OnDpiScaleFactorChanged(float dpiScaleFactor) override; + //! Used to fire a notification when our vsync interval changes. + void OnVsyncIntervalChanged(uint32_t interval) override; + //! Used to fire a notification when our refresh rate changes. + void OnRefreshRateChanged(uint32_t refreshRate) override; using SizeChangedEvent = AZ::Event; //! Notifies consumers when the viewport size has changed. @@ -98,6 +119,12 @@ namespace AZ //! Alternatively, connect to ViewportContextNotificationsBus and listen to ViewportContextNotifications::OnViewportDpiScalingChanged. void ConnectDpiScalingFactorChangedHandler(ScalarChangedEvent::Handler& handler); + using UintChangedEvent = AZ::Event; + //! Notifies consumers when the vsync interval has changed. + void ConnectVsyncIntervalChangedHandler(UintChangedEvent::Handler& handler); + //! Notifies consumers when the refresh rate has changed. + void ConnectRefreshRateChangedHandler(UintChangedEvent::Handler& handler); + using MatrixChangedEvent = AZ::Event; //! Notifies consumers when the view matrix has changed. void ConnectViewMatrixChangedHandler(MatrixChangedEvent::Handler& handler); @@ -139,15 +166,24 @@ namespace AZ void SetDefaultView(ViewPtr view); // Ensures our render pipeline's default camera matches ours. void UpdatePipelineView(); + // Ensures our render pipeline refresh rate matches our refresh rate. + void UpdatePipelineRefreshRate(); + // Resets the current pipeline reference and ensures pipeline events are disconnected. + void ResetCurrentPipeline(); ScenePtr m_rootScene; WindowContextSharedPtr m_windowContext; ViewPtr m_defaultView; AzFramework::WindowSize m_viewportSize; float m_viewportDpiScaleFactor = 1.0f; + uint32_t m_vsyncInterval = 1; + uint32_t m_refreshRate = 60; + float m_fpsLimit = 0.f; SizeChangedEvent m_sizeChangedEvent; ScalarChangedEvent m_dpiScalingFactorChangedEvent; + UintChangedEvent m_vsyncIntervalChangedEvent; + UintChangedEvent m_refreshRateChangedEvent; MatrixChangedEvent m_viewMatrixChangedEvent; MatrixChangedEvent::Handler m_onViewMatrixChangedHandler; MatrixChangedEvent m_projectionMatrixChangedEvent; @@ -157,6 +193,9 @@ namespace AZ ViewChangedEvent m_defaultViewChangedEvent; ViewportIdEvent m_aboutToBeDestroyedEvent; + AZ::Event<>::Handler m_prepareFrameHandler; + AZ::Event<>::Handler m_endFrameHandler; + ViewportContextManager* m_manager; RenderPipelinePtr m_currentPipeline; Name m_name; 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 0b53172ba2..fa13a3987a 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/ViewportContextBus.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/ViewportContextBus.h @@ -110,11 +110,13 @@ namespace AZ virtual void OnViewportSizeChanged(AzFramework::WindowSize size){AZ_UNUSED(size);} //! Called when the window DPI scaling changes for a given viewport context. virtual void OnViewportDpiScalingChanged(float dpiScale){AZ_UNUSED(dpiScale);} - //! Called when the active view for a given viewport context name changes. + //! Called when the active view changes for a given viewport context. virtual void OnViewportDefaultViewChanged(AZ::RPI::ViewPtr view){AZ_UNUSED(view);} //! Called when the viewport is to be rendered. - //! Add draws to this functions if they only need to be rendered to this viewport. - virtual void OnRenderTick(){}; + //! Add draws to this function if they only need to be rendered to this viewport. + virtual void OnRenderTick(){} + //! Called when the viewport finishes rendering a frame. + virtual void OnFrameEnd(){} protected: ~ViewportContextNotifications() = default; diff --git a/Gems/Atom/RPI/Code/Source/RPI.Builders/Material/MaterialBuilder.cpp b/Gems/Atom/RPI/Code/Source/RPI.Builders/Material/MaterialBuilder.cpp index 62e078c628..5ddeb08ef8 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Builders/Material/MaterialBuilder.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Builders/Material/MaterialBuilder.cpp @@ -152,7 +152,7 @@ namespace AZ AZStd::string fullSourcePath; AzFramework::StringFunc::Path::ConstructFull(request.m_watchFolder.data(), request.m_sourceFile.data(), fullSourcePath, true); - auto loadOutcome = JsonSerializationUtils::ReadJsonFile(fullSourcePath, AZ::RPI::JsonUtils::AtomMaxFileSize); + auto loadOutcome = JsonSerializationUtils::ReadJsonFile(fullSourcePath, AZ::RPI::JsonUtils::DefaultMaxFileSize); if (!loadOutcome.IsSuccess()) { AZ_Error(MaterialBuilderName, false, "%s", loadOutcome.GetError().c_str()); @@ -299,7 +299,7 @@ namespace AZ AZStd::string fullSourcePath; AzFramework::StringFunc::Path::ConstructFull(request.m_watchFolder.data(), request.m_sourceFile.data(), fullSourcePath, true); - auto loadOutcome = JsonSerializationUtils::ReadJsonFile(fullSourcePath, AZ::RPI::JsonUtils::AtomMaxFileSize); + auto loadOutcome = JsonSerializationUtils::ReadJsonFile(fullSourcePath, AZ::RPI::JsonUtils::DefaultMaxFileSize); if (!loadOutcome.IsSuccess()) { AZ_Error(MaterialBuilderName, false, "Failed to load material file: %s", loadOutcome.GetError().c_str()); diff --git a/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialSourceDataSerializer.cpp b/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialSourceDataSerializer.cpp index 6f68115ccb..5e4af07aae 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialSourceDataSerializer.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialSourceDataSerializer.cpp @@ -68,7 +68,7 @@ namespace AZ { AZStd::string materialTypePath = AssetUtils::ResolvePathReference(jsonFileLoadContext->GetFilePath(), materialSourceData->m_materialType); - auto materialTypeJson = JsonSerializationUtils::ReadJsonFile(materialTypePath, AZ::RPI::JsonUtils::AtomMaxFileSize); + auto materialTypeJson = JsonSerializationUtils::ReadJsonFile(materialTypePath, AZ::RPI::JsonUtils::DefaultMaxFileSize); if (!materialTypeJson.IsSuccess()) { AZStd::string failureMessage; diff --git a/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialUtils.cpp b/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialUtils.cpp index 80b292ea9b..62f4f02e3c 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialUtils.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialUtils.cpp @@ -65,7 +65,7 @@ namespace AZ AZ::Outcome loadOutcome; if (document == nullptr) { - loadOutcome = AZ::JsonSerializationUtils::ReadJsonFile(filePath, AZ::RPI::JsonUtils::AtomMaxFileSize); + loadOutcome = AZ::JsonSerializationUtils::ReadJsonFile(filePath, AZ::RPI::JsonUtils::DefaultMaxFileSize); if (!loadOutcome.IsSuccess()) { AZ_Error("AZ::RPI::JsonUtils", false, "%s", loadOutcome.GetError().c_str()); diff --git a/Gems/Atom/RPI/Code/Source/RPI.Private/RPISystemComponent.cpp b/Gems/Atom/RPI/Code/Source/RPI.Private/RPISystemComponent.cpp index 2567d221e5..789d43712e 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Private/RPISystemComponent.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Private/RPISystemComponent.cpp @@ -14,6 +14,9 @@ #include #include +#include +#include +#include #include #include @@ -93,20 +96,29 @@ namespace AZ } m_rpiSystem.Initialize(m_rpiDescriptor); - AZ::SystemTickBus::Handler::BusConnect(); + AZ::TickBus::Handler::BusConnect(); } void RPISystemComponent::Deactivate() { - AZ::SystemTickBus::Handler::BusDisconnect(); + AZ::TickBus::Handler::BusDisconnect(); m_rpiSystem.Shutdown(); } - void RPISystemComponent::OnSystemTick() + void RPISystemComponent::OnTick([[maybe_unused]]float deltaTime, [[maybe_unused]]ScriptTimePoint time) { + if (deltaTime == 0.f) + { + return; + } + m_rpiSystem.SimulationTick(); m_rpiSystem.RenderTick(); } + int RPISystemComponent::GetTickOrder() + { + return AZ::ComponentTickBus::TICK_RENDER; + } } // namespace RPI } // namespace AZ diff --git a/Gems/Atom/RPI/Code/Source/RPI.Private/RPISystemComponent.h b/Gems/Atom/RPI/Code/Source/RPI.Private/RPISystemComponent.h index e0a128c3f1..7933e1581c 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Private/RPISystemComponent.h +++ b/Gems/Atom/RPI/Code/Source/RPI.Private/RPISystemComponent.h @@ -32,7 +32,7 @@ namespace AZ */ class RPISystemComponent final : public AZ::Component - , public AZ::SystemTickBus::Handler + , private AZ::TickBus::Handler { public: AZ_COMPONENT(RPISystemComponent, "{83E301F3-7A0C-4099-B530-9342B91B1BC0}"); @@ -50,8 +50,9 @@ namespace AZ private: RPISystemComponent(const RPISystemComponent&) = delete; - // SystemTickBus overrides... - void OnSystemTick() override; + // TickBus overrides... + void OnTick(float deltaTime, ScriptTimePoint time) override; + int GetTickOrder() override; RPISystem m_rpiSystem; diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/Pass.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/Pass.cpp index a8d94e9a91..c5e871697b 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/Pass.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/Pass.cpp @@ -93,8 +93,11 @@ namespace AZ void Pass::SetEnabled(bool enabled) { - m_flags.m_enabled = enabled; - OnHierarchyChange(); + if (m_flags.m_enabled != enabled) + { + m_flags.m_enabled = enabled; + OnHierarchyChange(); + } } bool Pass::IsEnabled() const diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/RenderPipeline.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/RenderPipeline.cpp index 6d974a074f..7f0ab9c8aa 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/RenderPipeline.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/RenderPipeline.cpp @@ -301,6 +301,26 @@ namespace AZ m_drawFilterMask = 0; } + void RenderPipeline::OnPrepareFrame() + { + m_lastRenderRequestTime = AZStd::chrono::system_clock::now(); + + // If we're attempting to render at a target interval, check to see if we're within + // 1ms of that interval, enabling rendering only if we are. + if (m_renderMode == RenderMode::RenderAtTargetRate) + { + constexpr AZStd::chrono::duration updateThresholdMs(0.001f); + const bool shouldRender = + m_lastRenderRequestTime - m_lastRenderStartTime + updateThresholdMs >= m_targetRefreshRate; + m_rootPass->SetEnabled(shouldRender); + } + + if (NeedsRender()) + { + m_prepareFrameEvent.Signal(); + } + } + void RenderPipeline::OnPassModified() { if (m_needsPassRecreate) @@ -375,11 +395,11 @@ namespace AZ m_scene->RemoveRenderPipeline(m_nameId); } - void RenderPipeline::OnStartFrame(const TickTimeInfo& tick) + void RenderPipeline::OnStartFrame() { AZ_PROFILE_FUNCTION(RPI); - m_lastRenderStartTime = tick.m_currentGameTime; + m_lastRenderStartTime = m_lastRenderRequestTime; OnPassModified(); @@ -407,6 +427,7 @@ namespace AZ { RemoveFromRenderTick(); } + m_endFrameEvent.Signal(); } void RenderPipeline::CollectPersistentViews(AZStd::map& outViewMasks) const @@ -489,6 +510,13 @@ namespace AZ m_renderMode = RenderMode::RenderEveryTick; } + void RenderPipeline::AddToRenderTickAtInterval(AZStd::chrono::duration renderInterval) + { + m_rootPass->SetEnabled(false); + m_renderMode = RenderMode::RenderAtTargetRate; + m_targetRefreshRate = renderInterval; + } + void RenderPipeline::RemoveFromRenderTick() { m_renderMode = RenderMode::NoRender; @@ -502,7 +530,7 @@ namespace AZ bool RenderPipeline::NeedsRender() const { - return m_renderMode != RenderMode::NoRender; + return m_rootPass->IsEnabled(); } RHI::DrawFilterTag RenderPipeline::GetDrawFilterTag() const @@ -515,6 +543,16 @@ namespace AZ return m_drawFilterMask; } + void RenderPipeline::ConnectPrepareFrameHandler(FrameNotificationEvent::Handler& handler) + { + handler.Connect(m_prepareFrameEvent); + } + + void RenderPipeline::ConnectEndFrameHandler(FrameNotificationEvent::Handler& handler) + { + handler.Connect(m_endFrameEvent); + } + void RenderPipeline::SetDrawFilterTag(RHI::DrawFilterTag tag) { m_drawFilterTag = tag; diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/Scene.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/Scene.cpp index 0b0481b960..9fa49f7f2a 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/Scene.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/Scene.cpp @@ -420,7 +420,7 @@ namespace AZ } } - void Scene::PrepareRender(const TickTimeInfo& tickInfo, RHI::JobPolicy jobPolicy) + void Scene::PrepareRender([[maybe_unused]]const TickTimeInfo& tickInfo, RHI::JobPolicy jobPolicy) { AZ_ATOM_PROFILE_FUNCTION("RPI", "Scene: PrepareRender"); @@ -432,20 +432,27 @@ namespace AZ SceneNotificationBus::Event(GetId(), &SceneNotification::OnBeginPrepareRender); - // Get active pipelines which need to be rendered and notify them frame started + // Get active pipelines which need to be rendered and notify them of an impending frame. AZStd::vector activePipelines; { - AZ_ATOM_PROFILE_TIME_GROUP_REGION("RPI", "Scene: OnStartFrame"); + AZ_ATOM_PROFILE_TIME_GROUP_REGION("RPI", "Scene: OnPrepareFrame"); for (auto& pipeline : m_pipelines) { + pipeline->OnPrepareFrame(); if (pipeline->NeedsRender()) { activePipelines.push_back(pipeline); - pipeline->OnStartFrame(tickInfo); } } } + // Get active pipelines which need to be rendered and notify them frame started + for (const auto& pipeline : activePipelines) + { + AZ_ATOM_PROFILE_TIME_GROUP_REGION("RPI", "Scene: OnStartFrame"); + pipeline->OnStartFrame(); + } + // Return if there is no active render pipeline if (activePipelines.empty()) { @@ -587,10 +594,12 @@ namespace AZ void Scene::OnFrameEnd() { AZ_ATOM_PROFILE_FUNCTION("RPI", "Scene: OnFrameEnd"); + bool didRender = false; for (auto& pipeline : m_pipelines) { if (pipeline->NeedsRender()) { + didRender = true; pipeline->OnFrameEnd(); } } @@ -598,6 +607,10 @@ namespace AZ { fp->OnRenderEnd(); } + if (didRender) + { + SceneNotificationBus::Event(GetId(), &SceneNotification::OnFrameEnd); + } } void Scene::UpdateSrgs() diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/View.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/View.cpp index b07728938f..bdb3ea8899 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/View.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/View.cpp @@ -241,7 +241,10 @@ namespace AZ { AZ_PROFILE_FUNCTION(RPI); m_drawListContext.FinalizeLists(); - SortFinalizedDrawLists(); + if (m_passesByDrawList) + { + SortFinalizedDrawLists(); + } } void View::SortFinalizedDrawLists() diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/ViewportContext.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/ViewportContext.cpp index 77114e5cf5..3dcbae2fb9 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/ViewportContext.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/ViewportContext.cpp @@ -25,14 +25,13 @@ namespace AZ , m_viewportSize(1, 1) { m_windowContext->Initialize(device, nativeWindow); - AzFramework::WindowRequestBus::EventResult( - m_viewportSize, - nativeWindow, - &AzFramework::WindowRequestBus::Events::GetClientAreaSize); - AzFramework::WindowRequestBus::EventResult( - m_viewportDpiScaleFactor, - nativeWindow, - &AzFramework::WindowRequestBus::Events::GetDpiScaleFactor); + AzFramework::WindowRequestBus::Event(nativeWindow, [this](AzFramework::WindowRequestBus::Events* window) + { + m_viewportSize = window->GetClientAreaSize(); + m_viewportDpiScaleFactor = window->GetDpiScaleFactor(); + m_vsyncInterval = window->GetSyncInterval(); + m_refreshRate = window->GetDisplayRefreshRate(); + }); AzFramework::WindowNotificationBus::Handler::BusConnect(nativeWindow); AzFramework::ViewportRequestBus::Handler::BusConnect(id); @@ -46,6 +45,20 @@ namespace AZ m_viewMatrixChangedEvent.Signal(matrix); }); + m_prepareFrameHandler = RenderPipeline::FrameNotificationEvent::Handler( + [this]() + { + ViewportContextNotificationBus::Event(GetName(), &ViewportContextNotificationBus::Events::OnRenderTick); + ViewportContextIdNotificationBus::Event(GetId(), &ViewportContextIdNotificationBus::Events::OnRenderTick); + }); + + m_endFrameHandler = RenderPipeline::FrameNotificationEvent::Handler( + [this]() + { + ViewportContextNotificationBus::Event(GetName(), &ViewportContextNotificationBus::Events::OnFrameEnd); + ViewportContextIdNotificationBus::Event(GetId(), &ViewportContextIdNotificationBus::Events::OnFrameEnd); + }); + SetRenderScene(renderScene); } @@ -111,26 +124,38 @@ namespace AZ { SceneNotificationBus::Handler::BusConnect(m_rootScene->GetId()); } - m_currentPipeline.reset(); + ResetCurrentPipeline(); UpdatePipelineView(); + UpdatePipelineRefreshRate(); } m_sceneChangedEvent.Signal(scene); } - void ViewportContext::RenderTick() + float ViewportContext::GetFpsLimit() const { - // add the current pipeline to next render tick if it's not already added. - if (m_currentPipeline && m_currentPipeline->GetRenderMode() != RenderPipeline::RenderMode::RenderOnce) - { - m_currentPipeline->AddToRenderTickOnce(); - } + return m_fpsLimit; } - void ViewportContext::OnBeginPrepareRender() + void ViewportContext::SetFpsLimit(float fpsLimit) { - ViewportContextNotificationBus::Event(GetName(), &ViewportContextNotificationBus::Events::OnRenderTick); - ViewportContextIdNotificationBus::Event(GetId(), &ViewportContextIdNotificationBus::Events::OnRenderTick); + m_fpsLimit = fpsLimit; + UpdatePipelineRefreshRate(); + } + + float ViewportContext::GetTargetFrameRate() const + { + float targetFrameRate = GetFpsLimit(); + const AZ::u32 vsyncInterval = GetVsyncInterval(); + if (vsyncInterval != 0) + { + const float vsyncFrameRate = static_cast(GetRefreshRate()) / static_cast(vsyncInterval); + if (targetFrameRate == 0.f || vsyncFrameRate < targetFrameRate) + { + targetFrameRate = vsyncFrameRate; + } + } + return targetFrameRate; } AZ::Name ViewportContext::GetName() const @@ -158,6 +183,16 @@ namespace AZ return m_viewportDpiScaleFactor; } + uint32_t ViewportContext::GetVsyncInterval() const + { + return m_vsyncInterval; + } + + uint32_t ViewportContext::GetRefreshRate() const + { + return m_refreshRate; + } + void ViewportContext::ConnectSizeChangedHandler(SizeChangedEvent::Handler& handler) { handler.Connect(m_sizeChangedEvent); @@ -168,6 +203,16 @@ namespace AZ handler.Connect(m_dpiScalingFactorChangedEvent); } + void ViewportContext::ConnectVsyncIntervalChangedHandler(UintChangedEvent::Handler& handler) + { + handler.Connect(m_vsyncIntervalChangedEvent); + } + + void ViewportContext::ConnectRefreshRateChangedHandler(UintChangedEvent::Handler& handler) + { + handler.Connect(m_refreshRateChangedEvent); + } + void ViewportContext::ConnectViewMatrixChangedHandler(MatrixChangedEvent::Handler& handler) { handler.Connect(m_viewMatrixChangedEvent); @@ -263,12 +308,43 @@ namespace AZ m_currentPipelineChangedEvent.Signal(m_currentPipeline); } - if (auto pipeline = GetCurrentPipeline()) + if (m_currentPipeline) { - pipeline->SetDefaultView(m_defaultView); + if (!m_prepareFrameHandler.IsConnected()) + { + m_currentPipeline->ConnectPrepareFrameHandler(m_prepareFrameHandler); + m_currentPipeline->ConnectEndFrameHandler(m_endFrameHandler); + } + m_currentPipeline->SetDefaultView(m_defaultView); } } + void ViewportContext::UpdatePipelineRefreshRate() + { + if (!m_currentPipeline) + { + return; + } + + const float refreshRate = GetTargetFrameRate(); + // If we have a truly unlimited framerate, just render every tick + if (refreshRate == 0.f) + { + m_currentPipeline->AddToRenderTick(); + } + else + { + m_currentPipeline->AddToRenderTickAtInterval(AZStd::chrono::duration(1.f / refreshRate)); + } + } + + void ViewportContext::ResetCurrentPipeline() + { + m_prepareFrameHandler.Disconnect(); + m_endFrameHandler.Disconnect(); + m_currentPipeline.reset(); + } + RenderPipelinePtr ViewportContext::GetCurrentPipeline() { return m_currentPipeline; @@ -281,8 +357,9 @@ namespace AZ // in the event prioritization is added later if (pipeline->GetWindowHandle() == m_windowContext->GetWindowHandle()) { - m_currentPipeline.reset(); + ResetCurrentPipeline(); UpdatePipelineView(); + UpdatePipelineRefreshRate(); } } @@ -290,8 +367,9 @@ namespace AZ { if (m_currentPipeline.get() == pipeline) { - m_currentPipeline.reset(); + ResetCurrentPipeline(); UpdatePipelineView(); + UpdatePipelineRefreshRate(); } } @@ -305,10 +383,30 @@ namespace AZ } } + void ViewportContext::OnRefreshRateChanged(uint32_t refreshRate) + { + if (m_refreshRate != refreshRate) + { + m_refreshRate = refreshRate; + m_refreshRateChangedEvent.Signal(m_refreshRate); + UpdatePipelineRefreshRate(); + } + } + void ViewportContext::OnDpiScaleFactorChanged(float dpiScaleFactor) { m_viewportDpiScaleFactor = dpiScaleFactor; m_dpiScalingFactorChangedEvent.Signal(dpiScaleFactor); } + + void ViewportContext::OnVsyncIntervalChanged(uint32_t interval) + { + if (m_vsyncInterval != interval) + { + m_vsyncInterval = interval; + m_vsyncIntervalChangedEvent.Signal(m_vsyncInterval); + UpdatePipelineRefreshRate(); + } + } } // namespace RPI } // namespace AZ diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Viewport/RenderViewportWidget.h b/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Viewport/RenderViewportWidget.h index 071bc5c997..1b718f7710 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Viewport/RenderViewportWidget.h +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Viewport/RenderViewportWidget.h @@ -10,6 +10,7 @@ #include #include +#include #include #include #include @@ -21,6 +22,8 @@ #include #include #include +#include +#include namespace AtomToolsFramework { @@ -35,6 +38,8 @@ namespace AtomToolsFramework , public AzFramework::WindowRequestBus::Handler , protected AzFramework::InputChannelEventListener , protected AZ::TickBus::Handler + , protected AZ::Render::Bootstrap::NotificationBus::Handler + , protected AtomToolsFramework::RenderViewportWidgetNotificationBus::Handler { public: //! Creates a RenderViewportWidget. @@ -121,6 +126,7 @@ namespace AtomToolsFramework // AZ::TickBus::Handler ... void OnTick(float deltaTime, AZ::ScriptTimePoint time) override; + int GetTickOrder() override; // QWidget ... void resizeEvent(QResizeEvent *event) override; @@ -128,9 +134,21 @@ namespace AtomToolsFramework void enterEvent(QEvent* event) override; void leaveEvent(QEvent* event) override; void mouseMoveEvent(QMouseEvent* event) override; + void focusInEvent(QFocusEvent* event) override; + + // AZ::Render::Bootstrap::NotificationBus::Handler ... + void OnFrameRateLimitChanged(float fpsLimit) override; + + // AtomToolsFramework::RenderViewportWidgetNotificationBus::Handler ... + void OnInactiveViewportFrameRateChanged(float fpsLimit) override; private: + AzFramework::NativeWindowHandle GetNativeWindowHandle() const; + void UpdateFrameRate(); + + void SetScreen(QScreen* screen); void SendWindowResizeEvent(); + void NotifyUpdateRefreshRate(); // The underlying ViewportContext, our entry-point to the Atom RPI. AZ::RPI::ViewportContextPtr m_viewportContext; @@ -153,5 +171,11 @@ namespace AtomToolsFramework AZ::ScriptTimePoint m_time; // Maps our internal Qt events into AzFramework InputChannels for our ViewportControllerList. AzToolsFramework::QtEventToAzInputMapper* m_inputChannelMapper = nullptr; + // Stores our current screen, used for tracking the current refresh rate. + QScreen* m_screen = nullptr; + // Stores the last RenderViewportWidget that has received user focus. + // This is used for optional framerate throtting for "inactive" viewports via the + // ed_inactive_viewport_fps_limit CVAR. + AZ::EnvironmentVariable m_lastFocusedViewport; }; } //namespace AtomToolsFramework diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Viewport/RenderViewportWidgetNotificationBus.h b/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Viewport/RenderViewportWidgetNotificationBus.h new file mode 100644 index 0000000000..0563bd6bf2 --- /dev/null +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Viewport/RenderViewportWidgetNotificationBus.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include + +namespace AtomToolsFramework +{ + //! Provides an interface for providing notifications specific to RenderViewportWidget. + //! @note Most behaviors in RenderViewportWidget are handled by its underyling + //! ViewportContext, this bus is specifically for functionality exclusive to the + //! Qt layer provided by RenderViewportWidget. + class RenderViewportWidgetNotifications : public AZ::EBusTraits + { + public: + static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single; + static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple; + + //! Triggered when the idle frame rate limit for inactive viewports changed. + //! Controlled by the ed_inactive_viewport_fps_limit CVAR. + //! Active viewports are controlled by the r_fps_limit CVAR. + virtual void OnInactiveViewportFrameRateChanged([[maybe_unused]]float fpsLimit){} + + protected: + ~RenderViewportWidgetNotifications() = default; + }; + + using RenderViewportWidgetNotificationBus = AZ::EBus; +} // namespace AtomToolsFramework diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Viewport/RenderViewportWidget.cpp b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Viewport/RenderViewportWidget.cpp index 027ac80151..4446e11231 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Viewport/RenderViewportWidget.cpp +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Viewport/RenderViewportWidget.cpp @@ -6,23 +6,42 @@ * */ -#include +#include +#include +#include #include #include -#include +#include +#include +#include #include #include #include #include -#include -#include -#include #include -#include #include -#include +#include #include +#include +#include +#include + +static void OnInactiveViewportFrameRateChanged(const float& fpsLimit) +{ + AtomToolsFramework::RenderViewportWidgetNotificationBus::Broadcast( + &AtomToolsFramework::RenderViewportWidgetNotificationBus::Events::OnInactiveViewportFrameRateChanged, fpsLimit); +} + +AZ_CVAR( + float, + ed_inactive_viewport_fps_limit, + 0, + OnInactiveViewportFrameRateChanged, + AZ::ConsoleFunctorFlags::Null, + "The maximum framerate to render viewports that don't have focus at"); + +static constexpr const char* LastFocusedViewportVariableName = "AtomToolsFramework::RenderViewportWidget::LastFocusedViewport"; namespace AtomToolsFramework { @@ -30,6 +49,12 @@ namespace AtomToolsFramework : QWidget(parent) , AzFramework::InputChannelEventListener(AzFramework::InputChannelEventListener::GetPriorityDefault()) { + m_lastFocusedViewport = AZ::Environment::FindVariable(LastFocusedViewportVariableName); + if (!m_lastFocusedViewport) + { + m_lastFocusedViewport = AZ::Environment::CreateVariable(LastFocusedViewportVariableName, nullptr); + } + if (shouldInitializeViewportContext) { InitializeViewportContext(); @@ -38,13 +63,24 @@ namespace AtomToolsFramework setUpdatesEnabled(false); setFocusPolicy(Qt::FocusPolicy::WheelFocus); setMouseTracking(true); + + // Wait a frame for our window handle to be constructed, then wire up our screen change signals. + QTimer::singleShot( + 0, + [this]() + { + QObject::connect(windowHandle(), &QWindow::screenChanged, this, &RenderViewportWidget::SetScreen); + }); + SetScreen(screen()); } bool RenderViewportWidget::InitializeViewportContext(AzFramework::ViewportId id) { if (m_viewportContext != nullptr) { - AZ_Assert(id == AzFramework::InvalidViewportId || m_viewportContext->GetId() == id, "Attempted to reinitialize RenderViewportWidget with a different ID"); + AZ_Assert( + id == AzFramework::InvalidViewportId || m_viewportContext->GetId() == id, + "Attempted to reinitialize RenderViewportWidget with a different ID"); return true; } @@ -59,7 +95,7 @@ namespace AtomToolsFramework // Before we do anything else, we must create a ViewportContext which will give us a ViewportId if we didn't manually specify one. AZ::RPI::ViewportContextRequestsInterface::CreationParameters params; params.device = AZ::RHI::RHISystemInterface::Get()->GetDevice(); - params.windowHandle = reinterpret_cast(winId()); + params.windowHandle = GetNativeWindowHandle(); params.id = id; AzFramework::WindowRequestBus::Handler::BusConnect(params.windowHandle); m_viewportContext = viewportContextManager->CreateViewportContext(AZ::Name(), params); @@ -80,29 +116,46 @@ namespace AtomToolsFramework AzFramework::InputChannelEventListener::Connect(); AZ::TickBus::Handler::BusConnect(); AzFramework::WindowRequestBus::Handler::BusConnect(params.windowHandle); + AZ::Render::Bootstrap::NotificationBus::Handler::BusConnect(); + AtomToolsFramework::RenderViewportWidgetNotificationBus::Handler::BusConnect(); m_inputChannelMapper = new AzToolsFramework::QtEventToAzInputMapper(this, id); // Forward input events to our controller list. - QObject::connect(m_inputChannelMapper, &AzToolsFramework::QtEventToAzInputMapper::InputChannelUpdated, this, + QObject::connect( + m_inputChannelMapper, &AzToolsFramework::QtEventToAzInputMapper::InputChannelUpdated, this, [this](const AzFramework::InputChannel* inputChannel, QEvent* event) - { - AzFramework::NativeWindowHandle windowId = reinterpret_cast(winId()); - if (m_controllerList->HandleInputChannelEvent(AzFramework::ViewportControllerInputEvent{GetId(), windowId, *inputChannel})) { - // If the controller handled the input event, mark the event as accepted so it doesn't continue to propagate. - if (event) + const AzFramework::NativeWindowHandle windowId = GetNativeWindowHandle(); + if (m_controllerList->HandleInputChannelEvent( + AzFramework::ViewportControllerInputEvent{ GetId(), windowId, *inputChannel })) { - event->setAccepted(true); + // If the controller handled the input event, mark the event as accepted so it doesn't continue to propagate. + if (event) + { + event->setAccepted(true); + } } - } - }); + }); + + // Update our target frame rate. If we're the only viewport, become active. + if (m_lastFocusedViewport.Get() == nullptr) + { + m_lastFocusedViewport.Set(this); + } + UpdateFrameRate(); + return true; } RenderViewportWidget::~RenderViewportWidget() { + if (m_lastFocusedViewport.Get() == this) + { + m_lastFocusedViewport.Set(nullptr); + } + AzFramework::WindowRequestBus::Handler::BusDisconnect(); AZ::TickBus::Handler::BusDisconnect(); AzFramework::InputChannelEventListener::Disconnect(); @@ -181,17 +234,22 @@ namespace AtomToolsFramework bool shouldConsumeEvent = true; - AzFramework::NativeWindowHandle windowId = reinterpret_cast(winId()); - const bool eventHandled = m_controllerList->HandleInputChannelEvent({GetId(), windowId, inputChannel}); + const bool eventHandled = m_controllerList->HandleInputChannelEvent({ GetId(), GetNativeWindowHandle(), inputChannel }); - // If our controllers handled the event and it's one we can safely consume (i.e. it's not an Ended event that other viewports might need), consume it. + // If our controllers handled the event and it's one we can safely consume (i.e. it's not an Ended event that other viewports might + // need), consume it. return eventHandled && shouldConsumeEvent; } - void RenderViewportWidget::OnTick([[maybe_unused]]float deltaTime, AZ::ScriptTimePoint time) + void RenderViewportWidget::OnTick([[maybe_unused]] float deltaTime, AZ::ScriptTimePoint time) { m_time = time; - m_controllerList->UpdateViewport({GetId(), AzFramework::FloatSeconds(deltaTime), m_time}); + m_controllerList->UpdateViewport({ GetId(), AzFramework::FloatSeconds(deltaTime), m_time }); + } + + int RenderViewportWidget::GetTickOrder() + { + return AZ::ComponentTickBus::TICK_PRE_RENDER; } void RenderViewportWidget::resizeEvent([[maybe_unused]] QResizeEvent* event) @@ -236,6 +294,75 @@ namespace AtomToolsFramework m_mousePosition = event->localPos(); } + void RenderViewportWidget::focusInEvent([[maybe_unused]] QFocusEvent* event) + { + RenderViewportWidget* lastFocusedViewport = m_lastFocusedViewport.Get(); + if (lastFocusedViewport == this) + { + return; + } + + RenderViewportWidget* previousFocusWidget = lastFocusedViewport; + m_lastFocusedViewport.Set(this); + + // Ensure this viewport and whatever viewport last had focus (if any) respect + // the active / inactive viewport frame rate settings. + UpdateFrameRate(); + if (previousFocusWidget != nullptr) + { + previousFocusWidget->UpdateFrameRate(); + } + } + + void RenderViewportWidget::OnFrameRateLimitChanged([[maybe_unused]] float fpsLimit) + { + UpdateFrameRate(); + } + + void RenderViewportWidget::OnInactiveViewportFrameRateChanged([[maybe_unused]] float fpsLimit) + { + UpdateFrameRate(); + } + + AzFramework::NativeWindowHandle RenderViewportWidget::GetNativeWindowHandle() const + { + return reinterpret_cast(winId()); + } + + void RenderViewportWidget::UpdateFrameRate() + { + if (ed_inactive_viewport_fps_limit > 0.f && m_lastFocusedViewport.Get() != this) + { + m_viewportContext->SetFpsLimit(ed_inactive_viewport_fps_limit); + } + else + { + float fpsLimit = 0.f; + AZ::Render::Bootstrap::RequestBus::BroadcastResult(fpsLimit, &AZ::Render::Bootstrap::RequestBus::Events::GetFrameRateLimit); + m_viewportContext->SetFpsLimit(fpsLimit); + } + } + + void RenderViewportWidget::SetScreen(QScreen* screen) + { + if (m_screen != screen) + { + if (m_screen) + { + QObject::disconnect(m_screen, &QScreen::refreshRateChanged, this, &RenderViewportWidget::NotifyUpdateRefreshRate); + } + + if (screen) + { + QObject::connect(m_screen, &QScreen::refreshRateChanged, this, &RenderViewportWidget::NotifyUpdateRefreshRate); + } + + NotifyUpdateRefreshRate(); + + m_screen = screen; + } + } + void RenderViewportWidget::SendWindowResizeEvent() { // Scale the size by the DPI of the platform to @@ -243,11 +370,17 @@ namespace AtomToolsFramework const QSize uiWindowSize = size(); const QSize windowSize = uiWindowSize * devicePixelRatioF(); - const AzFramework::NativeWindowHandle windowId = reinterpret_cast(winId()); - AzFramework::WindowNotificationBus::Event(windowId, &AzFramework::WindowNotifications::OnWindowResized, windowSize.width(), windowSize.height()); + AzFramework::WindowNotificationBus::Event( + GetNativeWindowHandle(), &AzFramework::WindowNotifications::OnWindowResized, windowSize.width(), windowSize.height()); m_windowResizedEvent = false; } + void RenderViewportWidget::NotifyUpdateRefreshRate() + { + AzFramework::WindowNotificationBus::Event( + GetNativeWindowHandle(), &AzFramework::WindowNotificationBus::Events::OnRefreshRateChanged, GetDisplayRefreshRate()); + } + AZ::Name RenderViewportWidget::GetCurrentContextName() const { return m_viewportContext->GetName(); @@ -303,9 +436,7 @@ namespace AtomToolsFramework // Build camera state from Atom camera transforms AzFramework::CameraState cameraState = AzFramework::CreateCameraFromWorldFromViewMatrix( - currentView->GetViewToWorldMatrix(), - AZ::Vector2{aznumeric_cast(width()), aznumeric_cast(height())} - ); + currentView->GetViewToWorldMatrix(), AZ::Vector2{ aznumeric_cast(width()), aznumeric_cast(height()) }); AzFramework::SetCameraClippingVolumeFromPerspectiveFovMatrixRH(cameraState, currentView->GetViewToClipMatrix()); // Convert from Z-up @@ -317,8 +448,7 @@ namespace AtomToolsFramework AzFramework::ScreenPoint RenderViewportWidget::ViewportWorldToScreen(const AZ::Vector3& worldPosition) { - if (AZ::RPI::ViewPtr currentView = m_viewportContext->GetDefaultView(); - currentView == nullptr) + if (AZ::RPI::ViewPtr currentView = m_viewportContext->GetDefaultView(); currentView == nullptr) { return AzFramework::ScreenPoint(0, 0); } @@ -331,12 +461,10 @@ namespace AtomToolsFramework const auto& cameraProjection = m_viewportContext->GetCameraProjectionMatrix(); const auto& cameraView = m_viewportContext->GetCameraViewMatrix(); - const AZ::Vector4 normalizedScreenPosition { - screenPosition.m_x * 2.f / width() - 1.0f, - (height() - screenPosition.m_y) * 2.f / height() - 1.0f, - 1.f - depth, // [GFX TODO] [ATOM-1501] Currently we always assume reverse depth - 1.f - }; + const AZ::Vector4 normalizedScreenPosition{ screenPosition.m_x * 2.f / width() - 1.0f, + (height() - screenPosition.m_y) * 2.f / height() - 1.0f, + 1.f - depth, // [GFX TODO] [ATOM-1501] Currently we always assume reverse depth + 1.f }; AZ::Matrix4x4 worldFromScreen = cameraProjection * cameraView; worldFromScreen.InvertFull(); @@ -365,7 +493,7 @@ namespace AtomToolsFramework AZ::Vector3 rayDirection = pos1.value() - pos0.value(); rayDirection.Normalize(); - return AzToolsFramework::ViewportInteraction::ProjectedViewportRay{rayOrigin, rayDirection}; + return AzToolsFramework::ViewportInteraction::ProjectedViewportRay{ rayOrigin, rayDirection }; } float RenderViewportWidget::DeviceScalingFactor() @@ -395,12 +523,12 @@ namespace AtomToolsFramework AzFramework::WindowSize RenderViewportWidget::GetClientAreaSize() const { - return AzFramework::WindowSize{aznumeric_cast(width()), aznumeric_cast(height())}; + return AzFramework::WindowSize{ aznumeric_cast(width()), aznumeric_cast(height()) }; } void RenderViewportWidget::ResizeClientArea(AzFramework::WindowSize clientAreaSize) { - const QSize targetSize = QSize{aznumeric_cast(clientAreaSize.m_width), aznumeric_cast(clientAreaSize.m_height)}; + const QSize targetSize = QSize{ aznumeric_cast(clientAreaSize.m_width), aznumeric_cast(clientAreaSize.m_height) }; resize(targetSize); } @@ -410,7 +538,7 @@ namespace AtomToolsFramework return false; } - void RenderViewportWidget::SetFullScreenState([[maybe_unused]]bool fullScreenState) + void RenderViewportWidget::SetFullScreenState([[maybe_unused]] bool fullScreenState) { // The RenderViewportWidget does not currently support full screen. } @@ -433,11 +561,20 @@ namespace AtomToolsFramework uint32_t RenderViewportWidget::GetDisplayRefreshRate() const { - return 60; + return static_cast(screen()->refreshRate()); } uint32_t RenderViewportWidget::GetSyncInterval() const { - return 1; + uint32_t interval = 1; + + // Get vsync_interval from AzFramework::NativeWindow, which owns it. + // NativeWindow also handles broadcasting OnVsyncIntervalChanged to all + // WindowNotificationBus listeners. + if (auto console = AZ::Interface::Get()) + { + console->GetCvarValue("vsync_interval", interval); + } + return interval; } -} //namespace AtomToolsFramework +} // namespace AtomToolsFramework diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/atomtoolsframework_files.cmake b/Gems/Atom/Tools/AtomToolsFramework/Code/atomtoolsframework_files.cmake index 3d4bb82eec..a24b45179f 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/atomtoolsframework_files.cmake +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/atomtoolsframework_files.cmake @@ -28,6 +28,7 @@ set(FILES Include/AtomToolsFramework/Util/MaterialPropertyUtil.h Include/AtomToolsFramework/Util/Util.h Include/AtomToolsFramework/Viewport/RenderViewportWidget.h + Include/AtomToolsFramework/Viewport/RenderViewportWidgetNotificationBus.h Include/AtomToolsFramework/Viewport/ModularViewportCameraController.h Include/AtomToolsFramework/Viewport/ModularViewportCameraControllerRequestBus.h Include/AtomToolsFramework/Window/AtomToolsMainWindow.h diff --git a/Gems/AtomLyIntegration/AtomBridge/Code/Source/Editor/AssetCollectionAsyncLoaderTestComponent.cpp b/Gems/AtomLyIntegration/AtomBridge/Code/Source/Editor/AssetCollectionAsyncLoaderTestComponent.cpp index 8270901b6b..92e09bf4b1 100644 --- a/Gems/AtomLyIntegration/AtomBridge/Code/Source/Editor/AssetCollectionAsyncLoaderTestComponent.cpp +++ b/Gems/AtomLyIntegration/AtomBridge/Code/Source/Editor/AssetCollectionAsyncLoaderTestComponent.cpp @@ -115,7 +115,7 @@ namespace AZ { rapidjson::Document jsonDoc; - auto readJsonResult = JsonSerializationUtils::ReadJsonFile(pathToAssetListJson, AZ::RPI::JsonUtils::AtomMaxFileSize); + auto readJsonResult = JsonSerializationUtils::ReadJsonFile(pathToAssetListJson, AZ::RPI::JsonUtils::DefaultMaxFileSize); if (!readJsonResult.IsSuccess()) { diff --git a/Gems/AtomLyIntegration/AtomViewportDisplayInfo/Code/Source/AtomViewportDisplayInfoSystemComponent.cpp b/Gems/AtomLyIntegration/AtomViewportDisplayInfo/Code/Source/AtomViewportDisplayInfoSystemComponent.cpp index 7d659ebb7a..bf356fa4b5 100644 --- a/Gems/AtomLyIntegration/AtomViewportDisplayInfo/Code/Source/AtomViewportDisplayInfoSystemComponent.cpp +++ b/Gems/AtomLyIntegration/AtomViewportDisplayInfo/Code/Source/AtomViewportDisplayInfoSystemComponent.cpp @@ -183,6 +183,15 @@ namespace AZ::Render DrawFramerate(); } + void AtomViewportDisplayInfoSystemComponent::OnFrameEnd() + { + auto currentTime = AZStd::chrono::system_clock::now(); + if (!m_fpsHistory.empty()) + { + m_fpsHistory.back().m_endFrameTime = currentTime; + } + } + AtomBridge::ViewportInfoDisplayState AtomViewportDisplayInfoSystemComponent::GetDisplayState() const { return aznumeric_cast(r_displayInfo.operator int()); @@ -248,11 +257,11 @@ namespace AZ::Render void AtomViewportDisplayInfoSystemComponent::UpdateFramerate() { auto currentTime = AZStd::chrono::system_clock::now(); - while (!m_fpsHistory.empty() && (currentTime - m_fpsHistory.front()) > m_fpsInterval) + while (!m_fpsHistory.empty() && (currentTime - m_fpsHistory.front().m_beginFrameTime) > m_fpsInterval) { m_fpsHistory.pop_front(); } - m_fpsHistory.push_back(currentTime); + m_fpsHistory.push_back(FrameTimingInfo(currentTime)); } void AtomViewportDisplayInfoSystemComponent::DrawFramerate() @@ -261,25 +270,31 @@ namespace AZ::Render double minFPS = DBL_MAX; double maxFPS = 0; AZStd::chrono::duration deltaTime; + AZStd::chrono::milliseconds totalFrameMS(0); for (const auto& time : m_fpsHistory) { if (lastTime.has_value()) { - deltaTime = time - lastTime.value(); + deltaTime = time.m_beginFrameTime - lastTime.value(); double fps = AZStd::chrono::seconds(1) / deltaTime; minFPS = AZStd::min(minFPS, fps); maxFPS = AZStd::max(maxFPS, fps); } - lastTime = time; + lastTime = time.m_beginFrameTime; + + if (time.m_endFrameTime.has_value()) + { + totalFrameMS += time.m_endFrameTime.value() - time.m_beginFrameTime; + } } double averageFPS = 0; double averageFrameMs = 0; if (m_fpsHistory.size() > 1) { - deltaTime = m_fpsHistory.back() - m_fpsHistory.front(); - averageFPS = AZStd::chrono::seconds(m_fpsHistory.size()) / deltaTime; - averageFrameMs = 1000.0f/averageFPS; + deltaTime = m_fpsHistory.back().m_beginFrameTime - m_fpsHistory.front().m_beginFrameTime; + averageFPS = AZStd::chrono::seconds(m_fpsHistory.size() - 1) / deltaTime; + averageFrameMs = aznumeric_cast(totalFrameMS.count()) / (m_fpsHistory.size() - 1); } const double frameIntervalSeconds = m_fpsInterval.count(); @@ -288,7 +303,7 @@ namespace AZ::Render AZStd::string::format( "FPS %.1f [%.0f..%.0f], %.1fms/frame, avg over %.1fs", averageFPS, - minFPS, + minFPS == DBL_MAX ? 0.0 : minFPS, maxFPS, averageFrameMs, frameIntervalSeconds), diff --git a/Gems/AtomLyIntegration/AtomViewportDisplayInfo/Code/Source/AtomViewportDisplayInfoSystemComponent.h b/Gems/AtomLyIntegration/AtomViewportDisplayInfo/Code/Source/AtomViewportDisplayInfoSystemComponent.h index 1d53e188d0..689cfdb43b 100644 --- a/Gems/AtomLyIntegration/AtomViewportDisplayInfo/Code/Source/AtomViewportDisplayInfoSystemComponent.h +++ b/Gems/AtomLyIntegration/AtomViewportDisplayInfo/Code/Source/AtomViewportDisplayInfoSystemComponent.h @@ -45,6 +45,7 @@ namespace AZ // AZ::RPI::ViewportContextNotificationBus::Handler overrides... void OnRenderTick() override; + void OnFrameEnd() override; // AZ::AtomBridge::AtomViewportInfoDisplayRequestBus::Handler overrides... AtomBridge::ViewportInfoDisplayState GetDisplayState() const override; @@ -61,6 +62,8 @@ namespace AZ void DrawPassInfo(); void DrawFramerate(); + void UpdateScene(AZ::RPI::ScenePtr scene); + static constexpr float BaseFontSize = 0.7f; AZStd::string m_rendererDescription; @@ -68,7 +71,17 @@ namespace AZ AzFramework::FontDrawInterface* m_fontDrawInterface = nullptr; float m_lineSpacing; AZStd::chrono::duration m_fpsInterval = AZStd::chrono::seconds(1); - AZStd::deque m_fpsHistory; + struct FrameTimingInfo + { + AZStd::chrono::system_clock::time_point m_beginFrameTime; + AZStd::optional m_endFrameTime; + + explicit FrameTimingInfo(AZStd::chrono::system_clock::time_point beginFrameTime) + : m_beginFrameTime(beginFrameTime) + { + } + }; + AZStd::deque m_fpsHistory; AZStd::optional m_lastMemoryUpdate; bool m_updateRootPassQuery = true; }; diff --git a/Gems/Terrain/Code/Tests/TerrainMocks.h b/Gems/Terrain/Code/Tests/TerrainMocks.h index 5f90cafd69..674104e26b 100644 --- a/Gems/Terrain/Code/Tests/TerrainMocks.h +++ b/Gems/Terrain/Code/Tests/TerrainMocks.h @@ -46,6 +46,7 @@ namespace UnitTest { provided.push_back(AZ_CRC("ShapeService", 0xe86aa5fe)); provided.push_back(AZ_CRC("BoxShapeService", 0x946a0032)); + provided.push_back(AZ_CRC_CE("AxisAlignedBoxShapeService")); } static void GetIncompatibleServices([[maybe_unused]] AZ::ComponentDescriptor::DependencyArrayType& incompatible) @@ -74,7 +75,7 @@ namespace UnitTest Terrain::TerrainSystemServiceRequestBus::Handler::BusDisconnect(); } - void SetWorldBounds(const AZ::Aabb& worldBounds) override + void SetWorldBounds([[maybe_unused]] const AZ::Aabb& worldBounds) override { } diff --git a/cmake/3rdParty/Platform/Android/BuiltInPackages_android.cmake b/cmake/3rdParty/Platform/Android/BuiltInPackages_android.cmake index f22de7e4c8..47c3c66463 100644 --- a/cmake/3rdParty/Platform/Android/BuiltInPackages_android.cmake +++ b/cmake/3rdParty/Platform/Android/BuiltInPackages_android.cmake @@ -9,15 +9,15 @@ # shared by other platforms: ly_associate_package(PACKAGE_NAME md5-2.0-multiplatform TARGETS md5 PACKAGE_HASH 29e52ad22c78051551f78a40c2709594f0378762ae03b417adca3f4b700affdf) ly_associate_package(PACKAGE_NAME RapidJSON-1.1.0-rev1-multiplatform TARGETS RapidJSON PACKAGE_HASH 2f5e26ecf86c3b7a262753e7da69ac59928e78e9534361f3d00c1ad5879e4023) -ly_associate_package(PACKAGE_NAME RapidXML-1.13-multiplatform TARGETS RapidXML PACKAGE_HASH 510b3c12f8872c54b34733e34f2f69dd21837feafa55bfefa445c98318d96ebf) +ly_associate_package(PACKAGE_NAME RapidXML-1.13-rev1-multiplatform TARGETS RapidXML PACKAGE_HASH 4b7b5651e47cfd019b6b295cc17bb147b65e53073eaab4a0c0d20a37ab74a246) ly_associate_package(PACKAGE_NAME cityhash-1.1-multiplatform TARGETS cityhash PACKAGE_HASH 0ace9e6f0b2438c5837510032d2d4109125845c0efd7d807f4561ec905512dd2) ly_associate_package(PACKAGE_NAME expat-2.1.0-multiplatform TARGETS expat PACKAGE_HASH 452256acd1fd699cef24162575b3524fccfb712f5321c83f1df1ce878de5b418) ly_associate_package(PACKAGE_NAME zstd-1.35-multiplatform TARGETS zstd PACKAGE_HASH 45d466c435f1095898578eedde85acf1fd27190e7ea99aeaa9acfd2f09e12665) ly_associate_package(PACKAGE_NAME glad-2.0.0-beta-rev2-multiplatform TARGETS glad PACKAGE_HASH ff97ee9664e97d0854b52a3734c2289329d9f2b4cd69478df6d0ca1f1c9392ee) ly_associate_package(PACKAGE_NAME lux_core-2.2-rev5-multiplatform TARGETS lux_core PACKAGE_HASH c8c13cf7bc351643e1abd294d0841b24dee60e51647dff13db7aec396ad1e0b5) # platform-specific: -ly_associate_package(PACKAGE_NAME freetype-2.10.4.14-android TARGETS freetype PACKAGE_HASH 74dd75382688323c3a2a5090f473840b5d7e9d2aed1a4fcdff05ed2a09a664f2) -ly_associate_package(PACKAGE_NAME tiff-4.2.0.14-android TARGETS tiff PACKAGE_HASH a9b30a1980946390c2fad0ed94562476a1d7ba8c1f36934ae140a89c54a8efd0) +ly_associate_package(PACKAGE_NAME tiff-4.2.0.15-rev2-android TARGETS tiff PACKAGE_HASH 252b99e5886ec59fdccf38603c1399dd3fc02d878641aba35a7f8d2504065a06) +ly_associate_package(PACKAGE_NAME freetype-2.10.4.16-android TARGETS freetype PACKAGE_HASH df9e4d559ea0f03b0666b48c79813b1cd4d9624429148a249865de9f5c2c11cd) ly_associate_package(PACKAGE_NAME AWSNativeSDK-1.7.167-rev6-android TARGETS AWSNativeSDK PACKAGE_HASH 1624ba9aaf03d001ed0ffc57d2f945ff82590e75a7ea868de35043cf673e82fb) ly_associate_package(PACKAGE_NAME Lua-5.3.5-rev5-android TARGETS Lua PACKAGE_HASH 1f638e94a17a87fe9e588ea456d5893876094b4db191234380e4c4eb9e06c300) ly_associate_package(PACKAGE_NAME PhysX-4.1.2.29882248-rev3-android TARGETS PhysX PACKAGE_HASH b8cb6aa46b2a21671f6cb1f6a78713a3ba88824d0447560ff5ce6c01014b9f43) diff --git a/cmake/3rdParty/Platform/Linux/BuiltInPackages_linux.cmake b/cmake/3rdParty/Platform/Linux/BuiltInPackages_linux.cmake index c9315336b9..429665f04a 100644 --- a/cmake/3rdParty/Platform/Linux/BuiltInPackages_linux.cmake +++ b/cmake/3rdParty/Platform/Linux/BuiltInPackages_linux.cmake @@ -12,7 +12,7 @@ ly_associate_package(PACKAGE_NAME assimp-5.0.1-rev11-multiplatform ly_associate_package(PACKAGE_NAME ASTCEncoder-2017_11_14-rev2-multiplatform TARGETS ASTCEncoder PACKAGE_HASH c240ffc12083ee39a5ce9dc241de44d116e513e1e3e4cc1d05305e7aa3bdc326) ly_associate_package(PACKAGE_NAME md5-2.0-multiplatform TARGETS md5 PACKAGE_HASH 29e52ad22c78051551f78a40c2709594f0378762ae03b417adca3f4b700affdf) ly_associate_package(PACKAGE_NAME RapidJSON-1.1.0-rev1-multiplatform TARGETS RapidJSON PACKAGE_HASH 2f5e26ecf86c3b7a262753e7da69ac59928e78e9534361f3d00c1ad5879e4023) -ly_associate_package(PACKAGE_NAME RapidXML-1.13-multiplatform TARGETS RapidXML PACKAGE_HASH 510b3c12f8872c54b34733e34f2f69dd21837feafa55bfefa445c98318d96ebf) +ly_associate_package(PACKAGE_NAME RapidXML-1.13-rev1-multiplatform TARGETS RapidXML PACKAGE_HASH 4b7b5651e47cfd019b6b295cc17bb147b65e53073eaab4a0c0d20a37ab74a246) ly_associate_package(PACKAGE_NAME pybind11-2.4.3-rev2-multiplatform TARGETS pybind11 PACKAGE_HASH d8012f907b6c54ac990b899a0788280857e7c93a9595405a28114b48c354eb1b) ly_associate_package(PACKAGE_NAME cityhash-1.1-multiplatform TARGETS cityhash PACKAGE_HASH 0ace9e6f0b2438c5837510032d2d4109125845c0efd7d807f4561ec905512dd2) ly_associate_package(PACKAGE_NAME expat-2.1.0-multiplatform TARGETS expat PACKAGE_HASH 452256acd1fd699cef24162575b3524fccfb712f5321c83f1df1ce878de5b418) @@ -25,8 +25,8 @@ ly_associate_package(PACKAGE_NAME PVRTexTool-4.24.0-rev4-multiplatform # platform-specific: ly_associate_package(PACKAGE_NAME AWSGameLiftServerSDK-3.4.1-rev1-linux TARGETS AWSGameLiftServerSDK PACKAGE_HASH a8149a95bd100384af6ade97e2b21a56173740d921e6c3da8188cd51554d39af) -ly_associate_package(PACKAGE_NAME freetype-2.10.4.14-linux TARGETS freetype PACKAGE_HASH 9ad246873067717962c6b780d28a5ce3cef3321b73c9aea746a039c798f52e93) -ly_associate_package(PACKAGE_NAME tiff-4.2.0.15-linux TARGETS tiff PACKAGE_HASH ae92b4d3b189c42ef644abc5cac865d1fb2eb7cb5622ec17e35642b00d1a0a76) +ly_associate_package(PACKAGE_NAME tiff-4.2.0.15-rev2-linux TARGETS tiff PACKAGE_HASH 19791da0a370470a6c187199f97c2c46efcc2d89146e2013775fb3600fd7317d) +ly_associate_package(PACKAGE_NAME freetype-2.10.4.16-linux TARGETS freetype PACKAGE_HASH 3f10c703d9001ecd2bb51a3bd003d3237c02d8f947ad0161c0252fdc54cbcf97) ly_associate_package(PACKAGE_NAME AWSNativeSDK-1.7.167-rev6-linux TARGETS AWSNativeSDK PACKAGE_HASH 490291e4c8057975c3ab86feb971b8a38871c58bac5e5d86abdd1aeb7141eec4) ly_associate_package(PACKAGE_NAME Lua-5.3.5-rev5-linux TARGETS Lua PACKAGE_HASH 1adc812abe3dd0dbb2ca9756f81d8f0e0ba45779ac85bf1d8455b25c531a38b0) ly_associate_package(PACKAGE_NAME PhysX-4.1.2.29882248-rev3-linux TARGETS PhysX PACKAGE_HASH a110249cbef4f266b0002c4ee9a71f59f373040cefbe6b82f1e1510c811edde6) diff --git a/cmake/3rdParty/Platform/Mac/BuiltInPackages_mac.cmake b/cmake/3rdParty/Platform/Mac/BuiltInPackages_mac.cmake index 4d62f6a7bf..4900a9d77e 100644 --- a/cmake/3rdParty/Platform/Mac/BuiltInPackages_mac.cmake +++ b/cmake/3rdParty/Platform/Mac/BuiltInPackages_mac.cmake @@ -12,7 +12,7 @@ ly_associate_package(PACKAGE_NAME assimp-5.0.1-rev11-multiplatform ly_associate_package(PACKAGE_NAME ASTCEncoder-2017_11_14-rev2-multiplatform TARGETS ASTCEncoder PACKAGE_HASH c240ffc12083ee39a5ce9dc241de44d116e513e1e3e4cc1d05305e7aa3bdc326) ly_associate_package(PACKAGE_NAME md5-2.0-multiplatform TARGETS md5 PACKAGE_HASH 29e52ad22c78051551f78a40c2709594f0378762ae03b417adca3f4b700affdf) ly_associate_package(PACKAGE_NAME RapidJSON-1.1.0-rev1-multiplatform TARGETS RapidJSON PACKAGE_HASH 2f5e26ecf86c3b7a262753e7da69ac59928e78e9534361f3d00c1ad5879e4023) -ly_associate_package(PACKAGE_NAME RapidXML-1.13-multiplatform TARGETS RapidXML PACKAGE_HASH 510b3c12f8872c54b34733e34f2f69dd21837feafa55bfefa445c98318d96ebf) +ly_associate_package(PACKAGE_NAME RapidXML-1.13-rev1-multiplatform TARGETS RapidXML PACKAGE_HASH 4b7b5651e47cfd019b6b295cc17bb147b65e53073eaab4a0c0d20a37ab74a246) ly_associate_package(PACKAGE_NAME pybind11-2.4.3-rev2-multiplatform TARGETS pybind11 PACKAGE_HASH d8012f907b6c54ac990b899a0788280857e7c93a9595405a28114b48c354eb1b) ly_associate_package(PACKAGE_NAME cityhash-1.1-multiplatform TARGETS cityhash PACKAGE_HASH 0ace9e6f0b2438c5837510032d2d4109125845c0efd7d807f4561ec905512dd2) ly_associate_package(PACKAGE_NAME expat-2.1.0-multiplatform TARGETS expat PACKAGE_HASH 452256acd1fd699cef24162575b3524fccfb712f5321c83f1df1ce878de5b418) @@ -27,8 +27,8 @@ ly_associate_package(PACKAGE_NAME PVRTexTool-4.24.0-rev4-multiplatform # platform-specific: ly_associate_package(PACKAGE_NAME DirectXShaderCompilerDxc-1.6.2104-o3de-rev3-mac TARGETS DirectXShaderCompilerDxc PACKAGE_HASH 3f77367dbb0342136ec4ebbd44bc1fedf7198089a0f83c5631248530769b2be6) ly_associate_package(PACKAGE_NAME SPIRVCross-2021.04.29-rev1-mac TARGETS SPIRVCross PACKAGE_HASH 78c6376ed2fd195b9b1f5fb2b56e5267a32c3aa21fb399e905308de470eb4515) -ly_associate_package(PACKAGE_NAME freetype-2.10.4.14-mac-ios TARGETS freetype PACKAGE_HASH 67b4f57aed92082d3fd7c16aa244a7d908d90122c296b0a63f73e0a0b8761977) -ly_associate_package(PACKAGE_NAME tiff-4.2.0.15-mac-ios TARGETS tiff PACKAGE_HASH a23ae1f8991a29f8e5df09d6d5b00d7768a740f90752cef465558c1768343709) +ly_associate_package(PACKAGE_NAME tiff-4.2.0.15-rev2-mac TARGETS tiff PACKAGE_HASH b6f3040319f5bfe465d7e3f9b12ceed0dc951e66e05562beaac1c8da3b1b5d3f) +ly_associate_package(PACKAGE_NAME freetype-2.10.4.16-mac TARGETS freetype PACKAGE_HASH f159b346ac3251fb29cb8dd5f805c99b0015ed7fdb3887f656945ca701a61d0d) ly_associate_package(PACKAGE_NAME AWSNativeSDK-1.7.167-rev5-mac TARGETS AWSNativeSDK PACKAGE_HASH ffb890bd9cf23afb429b9214ad9bac1bf04696f07a0ebb93c42058c482ab2f01) ly_associate_package(PACKAGE_NAME Lua-5.3.5-rev6-mac TARGETS Lua PACKAGE_HASH b9079fd35634774c9269028447562c6b712dbc83b9c64975c095fd423ff04c08) ly_associate_package(PACKAGE_NAME PhysX-4.1.2.29882248-rev3-mac TARGETS PhysX PACKAGE_HASH 5e092a11d5c0a50c4dd99bb681a04b566a4f6f29aa08443d9bffc8dc12c27c8e) diff --git a/cmake/3rdParty/Platform/Windows/BuiltInPackages_windows.cmake b/cmake/3rdParty/Platform/Windows/BuiltInPackages_windows.cmake index 64cbfa05dd..3855b7d712 100644 --- a/cmake/3rdParty/Platform/Windows/BuiltInPackages_windows.cmake +++ b/cmake/3rdParty/Platform/Windows/BuiltInPackages_windows.cmake @@ -12,7 +12,7 @@ ly_associate_package(PACKAGE_NAME assimp-5.0.1-rev11-multiplatform ly_associate_package(PACKAGE_NAME ASTCEncoder-2017_11_14-rev2-multiplatform TARGETS ASTCEncoder PACKAGE_HASH c240ffc12083ee39a5ce9dc241de44d116e513e1e3e4cc1d05305e7aa3bdc326) ly_associate_package(PACKAGE_NAME md5-2.0-multiplatform TARGETS md5 PACKAGE_HASH 29e52ad22c78051551f78a40c2709594f0378762ae03b417adca3f4b700affdf) ly_associate_package(PACKAGE_NAME RapidJSON-1.1.0-rev1-multiplatform TARGETS RapidJSON PACKAGE_HASH 2f5e26ecf86c3b7a262753e7da69ac59928e78e9534361f3d00c1ad5879e4023) -ly_associate_package(PACKAGE_NAME RapidXML-1.13-multiplatform TARGETS RapidXML PACKAGE_HASH 510b3c12f8872c54b34733e34f2f69dd21837feafa55bfefa445c98318d96ebf) +ly_associate_package(PACKAGE_NAME RapidXML-1.13-rev1-multiplatform TARGETS RapidXML PACKAGE_HASH 4b7b5651e47cfd019b6b295cc17bb147b65e53073eaab4a0c0d20a37ab74a246) ly_associate_package(PACKAGE_NAME pybind11-2.4.3-rev2-multiplatform TARGETS pybind11 PACKAGE_HASH d8012f907b6c54ac990b899a0788280857e7c93a9595405a28114b48c354eb1b) ly_associate_package(PACKAGE_NAME cityhash-1.1-multiplatform TARGETS cityhash PACKAGE_HASH 0ace9e6f0b2438c5837510032d2d4109125845c0efd7d807f4561ec905512dd2) ly_associate_package(PACKAGE_NAME expat-2.1.0-multiplatform TARGETS expat PACKAGE_HASH 452256acd1fd699cef24162575b3524fccfb712f5321c83f1df1ce878de5b418) @@ -28,8 +28,8 @@ ly_associate_package(PACKAGE_NAME PVRTexTool-4.24.0-rev4-multiplatform ly_associate_package(PACKAGE_NAME AWSGameLiftServerSDK-3.4.1-rev1-windows TARGETS AWSGameLiftServerSDK PACKAGE_HASH a0586b006e4def65cc25f388de17dc475e417dc1e6f9d96749777c88aa8271b0) ly_associate_package(PACKAGE_NAME DirectXShaderCompilerDxc-1.6.2104-o3de-rev3-windows TARGETS DirectXShaderCompilerDxc PACKAGE_HASH 803e10b94006b834cbbdd30f562a8ddf04174c2cb6956c8399ec164ef8418d1f) ly_associate_package(PACKAGE_NAME SPIRVCross-2021.04.29-rev1-windows TARGETS SPIRVCross PACKAGE_HASH 7d601ea9d625b1d509d38bd132a1f433d7e895b16adab76bac6103567a7a6817) -ly_associate_package(PACKAGE_NAME freetype-2.10.4.14-windows TARGETS freetype PACKAGE_HASH 88dedc86ccb8c92f14c2c033e51ee7d828fa08eafd6475c6aa963938a99f4bf3) -ly_associate_package(PACKAGE_NAME tiff-4.2.0.14-windows TARGETS tiff PACKAGE_HASH ab60d1398e4e1e375ec0f1a00cdb1d812a07c0096d827db575ce52dd6d714207) +ly_associate_package(PACKAGE_NAME tiff-4.2.0.15-rev2-windows TARGETS tiff PACKAGE_HASH ff03464ca460fc34a8406b2a0c548ad221b10e40480b0abb954f1e649c20bad0) +ly_associate_package(PACKAGE_NAME freetype-2.10.4.16-windows TARGETS freetype PACKAGE_HASH 9809255f1c59b07875097aa8d8c6c21c97c47a31fb35e30f2bb93188e99a85ff) ly_associate_package(PACKAGE_NAME AWSNativeSDK-1.7.167-rev4-windows TARGETS AWSNativeSDK PACKAGE_HASH a900e80f7259e43aed5c847afee2599ada37f29db70505481397675bcbb6c76c) ly_associate_package(PACKAGE_NAME Lua-5.3.5-rev5-windows TARGETS Lua PACKAGE_HASH 136faccf1f73891e3fa3b95f908523187792e56f5b92c63c6a6d7e72d1158d40) ly_associate_package(PACKAGE_NAME PhysX-4.1.2.29882248-rev3-windows TARGETS PhysX PACKAGE_HASH 0c5ffbd9fa588e5cf7643721a7cfe74d0fe448bf82252d39b3a96d06dfca2298) diff --git a/cmake/3rdParty/Platform/iOS/BuiltInPackages_ios.cmake b/cmake/3rdParty/Platform/iOS/BuiltInPackages_ios.cmake index 69576bb665..25fbaf830f 100644 --- a/cmake/3rdParty/Platform/iOS/BuiltInPackages_ios.cmake +++ b/cmake/3rdParty/Platform/iOS/BuiltInPackages_ios.cmake @@ -9,7 +9,7 @@ # shared by other platforms: ly_associate_package(PACKAGE_NAME md5-2.0-multiplatform TARGETS md5 PACKAGE_HASH 29e52ad22c78051551f78a40c2709594f0378762ae03b417adca3f4b700affdf) ly_associate_package(PACKAGE_NAME RapidJSON-1.1.0-rev1-multiplatform TARGETS RapidJSON PACKAGE_HASH 2f5e26ecf86c3b7a262753e7da69ac59928e78e9534361f3d00c1ad5879e4023) -ly_associate_package(PACKAGE_NAME RapidXML-1.13-multiplatform TARGETS RapidXML PACKAGE_HASH 510b3c12f8872c54b34733e34f2f69dd21837feafa55bfefa445c98318d96ebf) +ly_associate_package(PACKAGE_NAME RapidXML-1.13-rev1-multiplatform TARGETS RapidXML PACKAGE_HASH 4b7b5651e47cfd019b6b295cc17bb147b65e53073eaab4a0c0d20a37ab74a246) ly_associate_package(PACKAGE_NAME cityhash-1.1-multiplatform TARGETS cityhash PACKAGE_HASH 0ace9e6f0b2438c5837510032d2d4109125845c0efd7d807f4561ec905512dd2) ly_associate_package(PACKAGE_NAME expat-2.1.0-multiplatform TARGETS expat PACKAGE_HASH 452256acd1fd699cef24162575b3524fccfb712f5321c83f1df1ce878de5b418) ly_associate_package(PACKAGE_NAME zstd-1.35-multiplatform TARGETS zstd PACKAGE_HASH 45d466c435f1095898578eedde85acf1fd27190e7ea99aeaa9acfd2f09e12665) @@ -17,8 +17,8 @@ ly_associate_package(PACKAGE_NAME glad-2.0.0-beta-rev2-multiplatform TARGETS gla ly_associate_package(PACKAGE_NAME lux_core-2.2-rev5-multiplatform TARGETS lux_core PACKAGE_HASH c8c13cf7bc351643e1abd294d0841b24dee60e51647dff13db7aec396ad1e0b5) # platform-specific: -ly_associate_package(PACKAGE_NAME freetype-2.10.4.14-mac-ios TARGETS freetype PACKAGE_HASH 67b4f57aed92082d3fd7c16aa244a7d908d90122c296b0a63f73e0a0b8761977) -ly_associate_package(PACKAGE_NAME tiff-4.2.0.15-mac-ios TARGETS tiff PACKAGE_HASH a23ae1f8991a29f8e5df09d6d5b00d7768a740f90752cef465558c1768343709) +ly_associate_package(PACKAGE_NAME tiff-4.2.0.15-rev2-ios TARGETS tiff PACKAGE_HASH d864beb0c955a55f28c2a993843afb2ecf6e01519ddfc857cedf34fc5db68d49) +ly_associate_package(PACKAGE_NAME freetype-2.10.4.16-ios TARGETS freetype PACKAGE_HASH 3ac3c35e056ae4baec2e40caa023d76a7a3320895ef172b6655e9261b0dc2e29) ly_associate_package(PACKAGE_NAME AWSNativeSDK-1.7.167-rev3-ios TARGETS AWSNativeSDK PACKAGE_HASH 1246219a213ccfff76b526011febf521586d44dbc1753e474f8fb5fd861654a4) ly_associate_package(PACKAGE_NAME Lua-5.3.5-rev5-ios TARGETS Lua PACKAGE_HASH c2d3c4e67046c293049292317a7d60fdb8f23effeea7136aefaef667163e5ffe) ly_associate_package(PACKAGE_NAME PhysX-4.1.2.29882248-rev3-ios TARGETS PhysX PACKAGE_HASH b1bbc1fc068d2c6e1eb18eecd4e8b776adc516833e8da3dcb1970cef2a8f0cbd) diff --git a/cmake/OutputDirectory.cmake b/cmake/OutputDirectory.cmake index a75b47e818..6906f76de5 100644 --- a/cmake/OutputDirectory.cmake +++ b/cmake/OutputDirectory.cmake @@ -14,6 +14,16 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin CACHE PATH "Build dir # We install outside of the binary dir because our install support muliple platforms to # be installed together. We also have an exclusion rule in the AP that filters out the # "install" folder to avoid the AP picking it up +unset(define_with_force) if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) - set(CMAKE_INSTALL_PREFIX ${CMAKE_SOURCE_DIR}/install CACHE PATH "Install directory" FORCE) + set(define_with_force FORCE) +endif() +set(CMAKE_INSTALL_PREFIX ${CMAKE_SOURCE_DIR}/install CACHE PATH "Install directory" ${define_with_force}) + +cmake_path(ABSOLUTE_PATH CMAKE_BINARY_DIR NORMALIZE OUTPUT_VARIABLE cmake_binary_dir_normalized) +cmake_path(ABSOLUTE_PATH CMAKE_INSTALL_PREFIX NORMALIZE OUTPUT_VARIABLE cmake_install_prefix_normalized) +cmake_path(COMPARE ${cmake_binary_dir_normalized} EQUAL ${cmake_install_prefix_normalized} are_paths_equal) +if(are_paths_equal) + message(FATAL_ERROR "Binary dir is the same path as install prefix, indicate a different install prefix with " + "CMAKE_INSTALL_PREFIX or a different binary dir with -B ") endif()