Merge branch 'development' of https://github.com/o3de/o3de into Atom/gallowj/UpdatePostFXLayerCategories
This commit is contained in:
@@ -44,7 +44,7 @@ enum
|
||||
{
|
||||
// in milliseconds
|
||||
GameModeIdleFrequency = 0,
|
||||
EditorModeIdleFrequency = 1,
|
||||
EditorModeIdleFrequency = 0,
|
||||
InactiveModeFrequency = 10,
|
||||
UninitializedFrequency = 9999,
|
||||
};
|
||||
|
||||
@@ -1039,6 +1039,7 @@ void EditorViewportWidget::ConnectViewportInteractionRequestBus()
|
||||
{
|
||||
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();
|
||||
@@ -1049,6 +1050,7 @@ 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();
|
||||
}
|
||||
|
||||
@@ -36,6 +36,9 @@
|
||||
#include "CryEdit.h"
|
||||
#include "Viewport.h"
|
||||
|
||||
// Atom Renderer
|
||||
#include <Atom/RPI.Public/RPISystemInterface.h>
|
||||
|
||||
AZ_PUSH_DISABLE_DLL_EXPORT_MEMBER_WARNING
|
||||
#include <TrackView/ui_SequenceBatchRenderDialog.h>
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -74,6 +74,8 @@
|
||||
#include <AzCore/Module/Environment.h>
|
||||
#include <AzCore/std/string/conversions.h>
|
||||
|
||||
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())
|
||||
@@ -1392,6 +1394,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<int>(microsecondsPerSecond / g_simulation_tick_rate));
|
||||
AZStd::chrono::microseconds timeUntilNextTick = m_currentTime + timeBudgetPerTick - now;
|
||||
|
||||
if (timeUntilNextTick.count() > 0)
|
||||
{
|
||||
AZStd::this_thread::sleep_for(timeUntilNextTick);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
+13
-2
@@ -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<typename DerivedDispatcherT>
|
||||
DerivedDispatcherT* ActionDispatcher<DerivedDispatcherT>::MouseLButtonDown()
|
||||
{
|
||||
Log("%s", "Mouse left button down");
|
||||
Log("Mouse left button down");
|
||||
MouseLButtonDownImpl();
|
||||
return static_cast<DerivedDispatcherT*>(this);
|
||||
}
|
||||
@@ -175,11 +178,19 @@ namespace AzManipulatorTestFramework
|
||||
template<typename DerivedDispatcherT>
|
||||
DerivedDispatcherT* ActionDispatcher<DerivedDispatcherT>::MouseLButtonUp()
|
||||
{
|
||||
Log("%s", "Mouse left button up");
|
||||
Log("Mouse left button up");
|
||||
MouseLButtonUpImpl();
|
||||
return static_cast<DerivedDispatcherT*>(this);
|
||||
}
|
||||
|
||||
template<typename DerivedDispatcherT>
|
||||
DerivedDispatcherT* ActionDispatcher<DerivedDispatcherT>::MouseLButtonDoubleClick()
|
||||
{
|
||||
Log("Mouse left button double click");
|
||||
MouseLButtonDoubleClickImpl();
|
||||
return static_cast<DerivedDispatcherT*>(this);
|
||||
}
|
||||
|
||||
template<typename DerivedDispatcherT>
|
||||
const char* ActionDispatcher<DerivedDispatcherT>::KeyboardModifierString(
|
||||
const AzToolsFramework::ViewportInteraction::KeyboardModifier& keyModifier)
|
||||
|
||||
+1
@@ -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;
|
||||
|
||||
-55
@@ -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 <AzManipulatorTestFramework/AzManipulatorTestFramework.h>
|
||||
#include <AzManipulatorTestFramework/ActionDispatcher.h>
|
||||
#include <AzManipulatorTestFramework/ImmediateModeActionDispatcher.h>
|
||||
#include <AzCore/std/containers/list.h>
|
||||
#include <AzCore/std/functional.h>
|
||||
|
||||
namespace AzManipulatorTestFramework
|
||||
{
|
||||
//! Buffers actions to be dispatched upon a call to Execute().
|
||||
class RetainedModeActionDispatcher
|
||||
: public ActionDispatcher<RetainedModeActionDispatcher>
|
||||
{
|
||||
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()>;
|
||||
void AddActionToSequence(Action&& action);
|
||||
ImmediateModeActionDispatcher m_dispatcher;
|
||||
AZStd::list<Action> m_actions;
|
||||
bool m_locked = false;
|
||||
};
|
||||
} // namespace AzManipulatorTestFramework
|
||||
@@ -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();
|
||||
|
||||
@@ -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 <AzManipulatorTestFramework/RetainedModeActionDispatcher.h>
|
||||
|
||||
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
|
||||
@@ -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
|
||||
)
|
||||
|
||||
+205
-162
@@ -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<EntityManipulatorCommand>(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<EntityManipulatorCommand>(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<typename T>
|
||||
static void AddAction(
|
||||
AZStd::vector<AZStd::unique_ptr<QAction>>& actions,
|
||||
|
||||
+6
@@ -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);
|
||||
|
||||
|
||||
@@ -614,7 +614,7 @@ namespace UnitTest
|
||||
using EditorTransformComponentSelectionViewportPickingManipulatorTestFixture =
|
||||
IndirectCallManipulatorViewportInteractionFixtureMixin<EditorTransformComponentSelectionViewportPickingFixture>;
|
||||
|
||||
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<EditorTransformComponentSelectionFixture>;
|
||||
|
||||
|
||||
@@ -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())
|
||||
{
|
||||
|
||||
@@ -556,7 +556,7 @@ namespace AZ
|
||||
shaderAssetCreator.SetRenderStates(renderStates);
|
||||
}
|
||||
|
||||
Outcome<AZStd::string, AZStd::string> hlslSourceCodeOutcome = Utils::ReadFile(hlslFullPath, AZ::RPI::JsonUtils::AtomMaxFileSize);
|
||||
Outcome<AZStd::string, AZStd::string> hlslSourceCodeOutcome = Utils::ReadFile(hlslFullPath, AZ::RPI::JsonUtils::DefaultMaxFileSize);
|
||||
if (!hlslSourceCodeOutcome.IsSuccess())
|
||||
{
|
||||
AZ_Error(
|
||||
|
||||
@@ -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<int, Outcome<rapidjson::Document, AZStd::string>> 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());
|
||||
|
||||
@@ -478,7 +478,7 @@ namespace AZ
|
||||
RPI::Ptr<RPI::ShaderOptionGroupLayout> 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<AZStd::string, AZStd::string> hlslSourceOutcome = Utils::ReadFile(hlslSourcePath, AZ::RPI::JsonUtils::AtomMaxFileSize);
|
||||
Outcome<AZStd::string, AZStd::string> hlslSourceOutcome = Utils::ReadFile(hlslSourcePath, AZ::RPI::JsonUtils::DefaultMaxFileSize);
|
||||
if (!hlslSourceOutcome.IsSuccess())
|
||||
{
|
||||
AZ_Error(
|
||||
|
||||
@@ -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<Notification>;
|
||||
} // namespace Bootstrap
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -41,7 +41,14 @@
|
||||
#include <AzCore/Console/IConsole.h>
|
||||
#include <BootstrapSystemComponent_Traits_Platform.h>
|
||||
|
||||
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()
|
||||
|
||||
@@ -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 ...
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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<float> 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<float> 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
|
||||
|
||||
@@ -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<SceneNotification>;
|
||||
|
||||
@@ -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<AzFramework::WindowSize>;
|
||||
//! 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<uint32_t>;
|
||||
//! 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<const AZ::Matrix4x4&>;
|
||||
//! 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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -65,7 +65,7 @@ namespace AZ
|
||||
AZ::Outcome<rapidjson::Document, AZStd::string> 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());
|
||||
|
||||
@@ -14,6 +14,9 @@
|
||||
#include <RPI.Private/RPISystemComponent.h>
|
||||
|
||||
#include <Atom/RHI/Factory.h>
|
||||
#include <Atom/RPI.Public/ViewportContextBus.h>
|
||||
#include <Atom/RPI.Public/ViewportContext.h>
|
||||
#include <Atom/RPI.Public/RenderPipeline.h>
|
||||
|
||||
#include <AzCore/Asset/AssetManager.h>
|
||||
#include <AzCore/IO/IOUtils.h>
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<float> 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<ViewPtr, RHI::DrawListMask>& outViewMasks) const
|
||||
@@ -489,6 +510,13 @@ namespace AZ
|
||||
m_renderMode = RenderMode::RenderEveryTick;
|
||||
}
|
||||
|
||||
void RenderPipeline::AddToRenderTickAtInterval(AZStd::chrono::duration<float> 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;
|
||||
|
||||
@@ -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<RenderPipelinePtr> 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()
|
||||
|
||||
@@ -241,7 +241,10 @@ namespace AZ
|
||||
{
|
||||
AZ_PROFILE_FUNCTION(RPI);
|
||||
m_drawListContext.FinalizeLists();
|
||||
SortFinalizedDrawLists();
|
||||
if (m_passesByDrawList)
|
||||
{
|
||||
SortFinalizedDrawLists();
|
||||
}
|
||||
}
|
||||
|
||||
void View::SortFinalizedDrawLists()
|
||||
|
||||
@@ -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<float>(GetRefreshRate()) / static_cast<float>(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<float>(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
|
||||
|
||||
+24
@@ -10,6 +10,7 @@
|
||||
|
||||
#include <QWidget>
|
||||
#include <QElapsedTimer>
|
||||
#include <QPointer>
|
||||
#include <Atom/RPI.Public/Base.h>
|
||||
#include <AzToolsFramework/Viewport/ViewportMessages.h>
|
||||
#include <AzToolsFramework/Input/QtEventToAzInputManager.h>
|
||||
@@ -21,6 +22,8 @@
|
||||
#include <AzFramework/Windowing/WindowBus.h>
|
||||
#include <AzCore/Component/TickBus.h>
|
||||
#include <Atom/RPI.Public/AuxGeom/AuxGeomFeatureProcessorInterface.h>
|
||||
#include <Atom/Bootstrap/BootstrapNotificationBus.h>
|
||||
#include <AtomToolsFramework/Viewport/RenderViewportWidgetNotificationBus.h>
|
||||
|
||||
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<RenderViewportWidget*> m_lastFocusedViewport;
|
||||
};
|
||||
} //namespace AtomToolsFramework
|
||||
|
||||
+35
@@ -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 <AzCore/EBus/EBus.h>
|
||||
|
||||
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<RenderViewportWidgetNotifications>;
|
||||
} // namespace AtomToolsFramework
|
||||
@@ -6,23 +6,42 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#include <AtomToolsFramework/Viewport/RenderViewportWidget.h>
|
||||
#include <Atom/Bootstrap/BootstrapRequestBus.h>
|
||||
#include <Atom/RHI/RHISystemInterface.h>
|
||||
#include <Atom/RPI.Public/View.h>
|
||||
#include <Atom/RPI.Public/ViewportContext.h>
|
||||
#include <Atom/RPI.Public/ViewportContextBus.h>
|
||||
#include <Atom/RPI.Public/View.h>
|
||||
#include <AtomToolsFramework/Viewport/RenderViewportWidget.h>
|
||||
#include <AzCore/Console/Console.h>
|
||||
#include <AzCore/Math/MathUtils.h>
|
||||
#include <AzFramework/Input/Devices/Mouse/InputDeviceMouse.h>
|
||||
#include <AzFramework/Viewport/ViewportControllerList.h>
|
||||
#include <AzFramework/Viewport/ViewportScreen.h>
|
||||
#include <AzToolsFramework/Viewport/ViewportTypes.h>
|
||||
#include <AzCore/Math/MathUtils.h>
|
||||
#include <Atom/RHI/RHISystemInterface.h>
|
||||
#include <Atom/Bootstrap/BootstrapRequestBus.h>
|
||||
|
||||
#include <QApplication>
|
||||
#include <QCursor>
|
||||
#include <QBoxLayout>
|
||||
#include <QWindow>
|
||||
#include <QCursor>
|
||||
#include <QMouseEvent>
|
||||
#include <QScreen>
|
||||
#include <QTimer>
|
||||
#include <QWindow>
|
||||
|
||||
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<RenderViewportWidget*>(LastFocusedViewportVariableName);
|
||||
if (!m_lastFocusedViewport)
|
||||
{
|
||||
m_lastFocusedViewport = AZ::Environment::CreateVariable<RenderViewportWidget*>(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<AzFramework::NativeWindowHandle>(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<AzFramework::NativeWindowHandle>(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<AzFramework::NativeWindowHandle>(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<AzFramework::NativeWindowHandle>(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<AzFramework::NativeWindowHandle>(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<float>(width()), aznumeric_cast<float>(height())}
|
||||
);
|
||||
currentView->GetViewToWorldMatrix(), AZ::Vector2{ aznumeric_cast<float>(width()), aznumeric_cast<float>(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<uint32_t>(width()), aznumeric_cast<uint32_t>(height())};
|
||||
return AzFramework::WindowSize{ aznumeric_cast<uint32_t>(width()), aznumeric_cast<uint32_t>(height()) };
|
||||
}
|
||||
|
||||
void RenderViewportWidget::ResizeClientArea(AzFramework::WindowSize clientAreaSize)
|
||||
{
|
||||
const QSize targetSize = QSize{aznumeric_cast<int>(clientAreaSize.m_width), aznumeric_cast<int>(clientAreaSize.m_height)};
|
||||
const QSize targetSize = QSize{ aznumeric_cast<int>(clientAreaSize.m_width), aznumeric_cast<int>(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<uint32_t>(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<AZ::IConsole>::Get())
|
||||
{
|
||||
console->GetCvarValue<uint32_t>("vsync_interval", interval);
|
||||
}
|
||||
return interval;
|
||||
}
|
||||
} //namespace AtomToolsFramework
|
||||
} // namespace AtomToolsFramework
|
||||
|
||||
@@ -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
|
||||
|
||||
+1
-1
@@ -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())
|
||||
{
|
||||
|
||||
+23
-8
@@ -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<AtomBridge::ViewportInfoDisplayState>(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<double> 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<double>(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),
|
||||
|
||||
+14
-1
@@ -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<double> m_fpsInterval = AZStd::chrono::seconds(1);
|
||||
AZStd::deque<AZStd::chrono::system_clock::time_point> m_fpsHistory;
|
||||
struct FrameTimingInfo
|
||||
{
|
||||
AZStd::chrono::system_clock::time_point m_beginFrameTime;
|
||||
AZStd::optional<AZStd::chrono::system_clock::time_point> m_endFrameTime;
|
||||
|
||||
explicit FrameTimingInfo(AZStd::chrono::system_clock::time_point beginFrameTime)
|
||||
: m_beginFrameTime(beginFrameTime)
|
||||
{
|
||||
}
|
||||
};
|
||||
AZStd::deque<FrameTimingInfo> m_fpsHistory;
|
||||
AZStd::optional<AZStd::chrono::system_clock::time_point> m_lastMemoryUpdate;
|
||||
bool m_updateRootPassQuery = true;
|
||||
};
|
||||
|
||||
@@ -89,11 +89,23 @@ namespace Multiplayer
|
||||
//! @param deltaTime amount of time to integrate the provided inputs over
|
||||
virtual void ProcessInput(NetworkInput& networkInput, float deltaTime) = 0;
|
||||
|
||||
//! Similar to ProcessInput, do not call directly.
|
||||
//! This only needs to be overridden in components which allow NetworkInput to be processed by script.
|
||||
//! @param networkInput input structure to process
|
||||
//! @param deltaTime amount of time to integrate the provided inputs over
|
||||
virtual void ProcessInputFromScript([[maybe_unused]] NetworkInput& networkInput, [[maybe_unused]] float deltaTime){}
|
||||
|
||||
//! Only valid on a client, should never be invoked on the server.
|
||||
//! @param networkInput input structure to process
|
||||
//! @param deltaTime amount of time to integrate the provided inputs over
|
||||
virtual void CreateInput(NetworkInput& networkInput, float deltaTime) = 0;
|
||||
|
||||
//! Similar to CreateInput, should never be invoked on the server.
|
||||
//! This only needs to be overridden in components which allow NetworkInput creation to be handled by scripts.
|
||||
//! @param networkInput input structure to process
|
||||
//! @param deltaTime amount of time to integrate the provided inputs over
|
||||
virtual void CreateInputFromScript([[maybe_unused]]NetworkInput& networkInput, [[maybe_unused]] float deltaTime) {}
|
||||
|
||||
template <typename ComponentType>
|
||||
const ComponentType* FindComponent() const;
|
||||
|
||||
|
||||
@@ -252,7 +252,44 @@ void Signal{{ PropertyName }}({{ ', '.join(paramDefines) }});
|
||||
{#
|
||||
|
||||
#}
|
||||
{%- macro EmitDerivedClassesComment(dataFileNames, Component, ComponentName, ComponentNameBase, ComponentDerived, ControllerName, ControllerNameBase, ControllerDerived, NetworkInputCount) -%}
|
||||
{% macro GetNetworkInputCount(Component) -%}
|
||||
{{ Component.findall('NetworkInput') | len }}
|
||||
{%- endmacro -%}
|
||||
{#
|
||||
|
||||
#}
|
||||
{% macro ParseNetworkInputsExposedToScript(Component) -%}
|
||||
{% set NetworkInputsExposedToScript = namespace(value=0) %}
|
||||
{% for netInput in Component.findall('NetworkInput') %}
|
||||
{% if ('ExposeToScript' in netInput.attrib) and (netInput.attrib['ExposeToScript'] |booleanTrue) %}
|
||||
{{ caller(netInput) -}}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{%- endmacro -%}
|
||||
{#
|
||||
|
||||
#}
|
||||
{% macro GetNetworkInputsExposedToScriptCount(Component) -%}
|
||||
{% set NetworkInputsExposedToScript = namespace(value=0) %}
|
||||
{% call (netInput) ParseNetworkInputsExposedToScript(Component) %}
|
||||
{% set NetworkInputsExposedToScript.value = NetworkInputsExposedToScript.value + 1 %}
|
||||
{% endcall %}
|
||||
{{ NetworkInputsExposedToScript.value }}
|
||||
{%- endmacro -%}
|
||||
{#
|
||||
|
||||
#}
|
||||
{% macro GetCommaSeparatedParamListOfScriptableNetworkInputs(Component) -%}
|
||||
{% set parameters = [] %}
|
||||
{% call (netInput) ParseNetworkInputsExposedToScript(Component) %}
|
||||
{% set parameters = parameters.append(netInput.attrib['Type'] + ' ' + LowerFirst(netInput.attrib['Name'])) %}
|
||||
{% endcall %}
|
||||
{{ parameters | join(', ') }}
|
||||
{%- endmacro -%}
|
||||
{#
|
||||
|
||||
#}
|
||||
{%- macro EmitDerivedClassesComment(dataFileNames, Component, ComponentName, ComponentNameBase, ComponentDerived, ControllerName, ControllerNameBase, ControllerDerived) -%}
|
||||
{% if ComponentDerived or ControllerDerived %}
|
||||
/*
|
||||
/// You may use the classes below as a basis for your new derived classes. Derived classes must be marked in {{ (dataFileNames[0] | basename) }}
|
||||
@@ -293,7 +330,16 @@ namespace {{ Component.attrib['Namespace'] }}
|
||||
|
||||
void OnActivate(Multiplayer::EntityIsMigrating entityIsMigrating) override;
|
||||
void OnDeactivate(Multiplayer::EntityIsMigrating entityIsMigrating) override;
|
||||
{% set NetworkInputCount = GetNetworkInputCount(Component) | int %}
|
||||
{% if NetworkInputCount > 0 %}
|
||||
|
||||
//! Common input creation logic for the NetworkInput.
|
||||
//! Fill out the input struct and the MultiplayerInputDriver will send the input data over the network
|
||||
//! to ensure it's processed.
|
||||
//! @param input input structure which to store input data for sending to the authority
|
||||
//! @param deltaTime amount of time to integrate the provided inputs over
|
||||
void CreateInput(Multiplayer::NetworkInput& input, float deltaTime) override;
|
||||
|
||||
//! Common input processing logic for the NetworkInput.
|
||||
//! @param input input structure to process
|
||||
//! @param deltaTime amount of time to integrate the provided inputs over
|
||||
@@ -366,6 +412,18 @@ namespace {{ Component.attrib['Namespace'] }}
|
||||
{
|
||||
}
|
||||
{% if NetworkInputCount > 0 %}
|
||||
{% set net_input_parameters_name = [] %}
|
||||
{% call (netInput) ParseNetworkInputsExposedToScript(Component) %}
|
||||
{% set net_input_parameters_name = net_input_parameters_name.append(LowerFirst(netInput.attrib['Name'])) %}
|
||||
{% endcall %}
|
||||
|
||||
void {{ ControllerName }}::CreateInput([[maybe_unused]] Multiplayer::NetworkInput& input, [[maybe_unused]] float deltaTime)
|
||||
{
|
||||
{% if (GetNetworkInputsExposedToScriptCount(Component) | int) > 0 %}
|
||||
// Remember the following NetworkInputs have been exposed to script: {{ net_input_parameters_name|join(', ') }}.
|
||||
// If a script is handling these inputs they will have already be filled out by now.
|
||||
{% endif %}
|
||||
}
|
||||
|
||||
void {{ ControllerName }}::ProcessInput([[maybe_unused]] Multiplayer::NetworkInput& input, [[maybe_unused]] float deltaTime)
|
||||
{
|
||||
|
||||
@@ -230,7 +230,8 @@ AZStd::fixed_vector<{{ Property.attrib['Type'] }}, {{ Property.attrib['Count'] }
|
||||
{% if ControllerDerived %}
|
||||
{% set ControllerBaseName = ControllerName + "Base" %}
|
||||
{% endif %}
|
||||
{% set NetworkInputCount = Component.findall('NetworkInput') | len %}
|
||||
{% set NetworkInputCount = AutoComponentMacros.GetNetworkInputCount(Component) | int %}
|
||||
{% set NetworkInputsExposedToScriptCount = AutoComponentMacros.GetNetworkInputsExposedToScriptCount(Component) | int %}
|
||||
{% set NetworkPropertyCount = Component.findall('NetworkProperty') | len %}
|
||||
{% set RpcCount = Component.findall('RemoteProcedure') | len %}
|
||||
#include "AutoComponentTypes.h"
|
||||
@@ -250,6 +251,10 @@ AZStd::fixed_vector<{{ Property.attrib['Type'] }}, {{ Property.attrib['Count'] }
|
||||
{% call(Include) AutoComponentMacros.ParseIncludes(Component) %}
|
||||
#include <{{ Include.attrib['File'] }}>
|
||||
{% endcall %}
|
||||
{% if NetworkInputsExposedToScriptCount > 0 %}
|
||||
#include <AzCore/RTTI/BehaviorContext.h>
|
||||
{% endif %}
|
||||
|
||||
|
||||
{% for Service in Component.iter('ComponentRelation') %}
|
||||
{% if Service.attrib['Constraint'] != 'Incompatible' %}
|
||||
@@ -263,7 +268,7 @@ namespace {{ Service.attrib['Namespace'] }}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
{{ AutoComponentMacros.EmitDerivedClassesComment(dataFileNames, Component, ComponentName, ComponentBaseName, ComponentDerived, ControllerName, ControllerBaseName, ControllerDerived, NetworkInputCount) }}
|
||||
{{ AutoComponentMacros.EmitDerivedClassesComment(dataFileNames, Component, ComponentName, ComponentBaseName, ComponentDerived, ControllerName, ControllerBaseName, ControllerDerived) }}
|
||||
namespace {{ Component.attrib['Namespace'] }}
|
||||
{
|
||||
//! Forward declarations
|
||||
@@ -337,6 +342,13 @@ namespace {{ Component.attrib['Namespace'] }}
|
||||
: public Multiplayer::IMultiplayerComponentInput
|
||||
{
|
||||
public:
|
||||
{% if NetworkInputsExposedToScriptCount > 0 %}
|
||||
AZ_TYPE_INFO({{ ComponentName }}NetworkInput, "{{ (ComponentName ~ "NetworkInput") | createHashGuid }}")
|
||||
{{ ComponentName }}NetworkInput() = default;
|
||||
{{ ComponentName }}NetworkInput({{ AutoComponentMacros.GetCommaSeparatedParamListOfScriptableNetworkInputs(Component) }});
|
||||
static void Reflect(AZ::ReflectContext* context);
|
||||
|
||||
{% endif%}
|
||||
Multiplayer::NetComponentId GetNetComponentId() const override;
|
||||
bool Serialize(AzNetworking::ISerializer& serializer) override;
|
||||
Multiplayer::IMultiplayerComponentInput& operator =(const Multiplayer::IMultiplayerComponentInput& rhs) override;
|
||||
@@ -348,7 +360,41 @@ namespace {{ Component.attrib['Namespace'] }}
|
||||
static Multiplayer::NetComponentId s_netComponentId;
|
||||
friend void RegisterMultiplayerComponents();
|
||||
};
|
||||
{% if NetworkInputsExposedToScriptCount > 0 %}
|
||||
|
||||
class {{ ComponentName }}Requests
|
||||
: public AZ::ComponentBus
|
||||
{
|
||||
public:
|
||||
AZ_RTTI({{ ComponentName }}Requests, "{{ (ComponentName ~ "Requests") | createHashGuid }}")
|
||||
|
||||
virtual {{ ComponentName }}NetworkInput CreateInput(float deltaTime) = 0;
|
||||
virtual void ProcessInput({{ ComponentName }}NetworkInput* networkInput, float deltaTime) = 0;
|
||||
};
|
||||
|
||||
using {{ ComponentName }}RequestBus = AZ::EBus<{{ ComponentName }}Requests>;
|
||||
|
||||
class {{ ComponentName }}BusHandler final
|
||||
: public {{ ComponentName }}RequestBus::Handler
|
||||
, public AZ::BehaviorEBusHandler
|
||||
{
|
||||
public:
|
||||
AZ_EBUS_BEHAVIOR_BINDER({{ ComponentName }}BusHandler, "{{ (ComponentName ~ "BusHandler") | createHashGuid }}", AZ::SystemAllocator, CreateInput, ProcessInput)
|
||||
|
||||
{{ ComponentName }}NetworkInput CreateInput(float deltaTime) override
|
||||
{
|
||||
{{ ComponentName }}NetworkInput result;
|
||||
CallResult(result, FN_CreateInput, deltaTime);
|
||||
return result;
|
||||
}
|
||||
|
||||
void ProcessInput({{ ComponentName }}NetworkInput* networkInput, float deltaTime) override
|
||||
{
|
||||
Call(FN_ProcessInput, networkInput, deltaTime);
|
||||
}
|
||||
};
|
||||
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
class {{ ControllerBaseName }}{% if not ControllerDerived %} final{% endif %}{{ "" }}
|
||||
: public Multiplayer::MultiplayerController
|
||||
@@ -373,8 +419,14 @@ namespace {{ Component.attrib['Namespace'] }}
|
||||
//! MultiplayerController interface
|
||||
//! @{
|
||||
Multiplayer::MultiplayerController::InputPriorityOrder GetInputOrder() const override { return Multiplayer::MultiplayerController::InputPriorityOrder::Default; }
|
||||
|
||||
{% if NetworkInputsExposedToScriptCount > 0 %}
|
||||
void CreateInputFromScript([[maybe_unused]] Multiplayer::NetworkInput& input, [[maybe_unused]] float deltaTime) final;
|
||||
void ProcessInputFromScript([[maybe_unused]] Multiplayer::NetworkInput& input, [[maybe_unused]] float deltaTime) final;
|
||||
{% endif %}
|
||||
void CreateInput([[maybe_unused]] Multiplayer::NetworkInput& input, [[maybe_unused]] float deltaTime) override {}
|
||||
void ProcessInput([[maybe_unused]] Multiplayer::NetworkInput& input, [[maybe_unused]] float deltaTime) override {}
|
||||
|
||||
//! @}
|
||||
|
||||
{{ DeclareNetworkPropertyAccessors(Component, 'Authority', 'Server', false)|indent(8) -}}
|
||||
|
||||
@@ -1152,7 +1152,8 @@ m_{{ LowerFirst(Property.attrib['Name']) }} = m_{{ LowerFirst(Property.attrib['N
|
||||
{% else %}
|
||||
{% set ControllerBaseName = ControllerName %}
|
||||
{% endif %}
|
||||
{% set NetworkInputCount = Component.findall('NetworkInput') | len %}
|
||||
{% set NetworkInputCount = AutoComponentMacros.GetNetworkInputCount(Component) | int %}
|
||||
{% set NetworkInputsExposedToScriptCount = AutoComponentMacros.GetNetworkInputsExposedToScriptCount(Component) | int %}
|
||||
{% set NetworkPropertyCount = Component.findall('NetworkProperty') | len %}
|
||||
{% set RpcCount = Component.findall('RemoteProcedure') | len %}
|
||||
#include "{{ includeFile }}"
|
||||
@@ -1299,6 +1300,56 @@ namespace {{ Component.attrib['Namespace'] }}
|
||||
}
|
||||
|
||||
{% if NetworkInputCount > 0 %}
|
||||
{% set ScriptableNetworkInputParamNames = [] %}
|
||||
{% call(netInput) AutoComponentMacros.ParseNetworkInputsExposedToScript(Component) %}
|
||||
{% set ScriptableNetworkInputParamNames = ScriptableNetworkInputParamNames.append(LowerFirst(netInput.attrib['Name'])) %}
|
||||
{% endcall %}
|
||||
{% if NetworkInputsExposedToScriptCount > 0 %}
|
||||
{{ ComponentName }}NetworkInput Construct{{ ComponentName }}NetworkInput({{ AutoComponentMacros.GetCommaSeparatedParamListOfScriptableNetworkInputs(Component) }})
|
||||
{
|
||||
return {{ ComponentName }}NetworkInput({{ ScriptableNetworkInputParamNames|join(', ') }});
|
||||
}
|
||||
|
||||
{{ ComponentName }}NetworkInput::{{ ComponentName }}NetworkInput({{ AutoComponentMacros.GetCommaSeparatedParamListOfScriptableNetworkInputs(Component) }})
|
||||
: {% for param_name in ScriptableNetworkInputParamNames %}m_{{ LowerFirst(param_name) }}({{ LowerFirst(param_name) }}){% if not loop.last %}, {% endif %}{% endfor -%}{}
|
||||
|
||||
void {{ ComponentName }}NetworkInput::Reflect(AZ::ReflectContext* context)
|
||||
{
|
||||
AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
|
||||
if (serializeContext)
|
||||
{
|
||||
serializeContext->Class<{{ ComponentName }}NetworkInput>()
|
||||
->Version(1)
|
||||
;
|
||||
}
|
||||
|
||||
AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context);
|
||||
if (behaviorContext)
|
||||
{
|
||||
behaviorContext->Class<{{ ComponentName }}NetworkInput>("{{ ComponentName }}NetworkInput")
|
||||
->Attribute(AZ::Script::Attributes::Module, "{{ LowerFirst(Component.attrib['Namespace']) }}")
|
||||
->Attribute(AZ::Script::Attributes::Category, "{{ UpperFirst(Component.attrib['Namespace']) }}")
|
||||
{% set ScriptableNetInputNames = [] %}
|
||||
{% call (netInput) AutoComponentMacros.ParseNetworkInputsExposedToScript(Component) %}
|
||||
{% set ScriptableNetInputNames = ScriptableNetInputNames.append(netInput.attrib['Name']) %}
|
||||
{% endcall %}
|
||||
->Method("CreateFromValues", &Construct{{ ComponentName }}NetworkInput, { { {% for param_name in ScriptableNetInputNames %}{"{{ LowerFirst(param_name) }}"}{% if not loop.last %}, {% endif %}{% endfor -%} } })
|
||||
|
||||
{% for param_name in ScriptableNetInputNames %}
|
||||
->Property("{{ param_name }}", BehaviorValueProperty(&{{ ComponentName }}NetworkInput::m_{{ LowerFirst(param_name) }}))
|
||||
{% endfor %}
|
||||
;
|
||||
|
||||
behaviorContext->EBus<{{ ComponentName }}RequestBus>("{{ ComponentName }}BusHandler")
|
||||
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)
|
||||
->Attribute(AZ::Script::Attributes::Module, "{{ LowerFirst(Component.attrib['Namespace']) }}")
|
||||
->Attribute(AZ::Script::Attributes::Category, "{{ UpperFirst(Component.attrib['Namespace']) }}")
|
||||
->Handler<{{ ComponentName }}BusHandler>()
|
||||
;
|
||||
}
|
||||
}
|
||||
{% endif %}
|
||||
|
||||
Multiplayer::NetComponentId {{ ComponentName }}NetworkInput::GetNetComponentId() const
|
||||
{
|
||||
return {{ ComponentName }}NetworkInput::s_netComponentId;
|
||||
@@ -1347,6 +1398,26 @@ namespace {{ Component.attrib['Namespace'] }}
|
||||
{% endif %}
|
||||
}
|
||||
|
||||
{% if NetworkInputsExposedToScriptCount > 0 %}
|
||||
void {{ ControllerBaseName }}::CreateInputFromScript([[maybe_unused]] Multiplayer::NetworkInput& input, [[maybe_unused]] float deltaTime)
|
||||
{
|
||||
{{ ComponentName }}NetworkInput result;
|
||||
{{ ComponentName }}RequestBus::EventResult(result, GetEntity()->GetId(), &{{ ComponentName }}RequestBus::Events::CreateInput, deltaTime);
|
||||
|
||||
// Inputs for your own component always exist
|
||||
{{ ComponentName }}NetworkInput* {{ LowerFirst(ComponentName) }}Input = input.FindComponentInput<{{ ComponentName }}NetworkInput>();
|
||||
{% call(netInput) AutoComponentMacros.ParseNetworkInputsExposedToScript(Component) %}
|
||||
{{ LowerFirst(ComponentName) }}Input->m_{{ LowerFirst(netInput.attrib['Name']) }} = result.m_{{ LowerFirst(netInput.attrib['Name']) }};
|
||||
{% endcall %}
|
||||
}
|
||||
|
||||
void {{ ControllerBaseName }}::ProcessInputFromScript([[maybe_unused]] Multiplayer::NetworkInput& input, [[maybe_unused]] float deltaTime)
|
||||
{
|
||||
{{ ComponentName }}NetworkInput* {{ LowerFirst(ComponentName) }}Input = input.FindComponentInput<{{ ComponentName }}NetworkInput>();
|
||||
{{ ComponentName }}RequestBus::Event(GetEntity()->GetId(), &{{ ComponentName }}RequestBus::Events::ProcessInput, {{ LowerFirst(ComponentName) }}Input, deltaTime);
|
||||
}
|
||||
{% endif %}
|
||||
|
||||
const {{ ComponentName }}& {{ ControllerBaseName }}::GetParent() const
|
||||
{
|
||||
return static_cast<const {{ ComponentName }}&>(GetOwner());
|
||||
@@ -1399,6 +1470,9 @@ namespace {{ Component.attrib['Namespace'] }}
|
||||
}
|
||||
ReflectToEditContext(context);
|
||||
ReflectToBehaviorContext(context);
|
||||
{% if NetworkInputsExposedToScriptCount > 0 %}
|
||||
{{ ComponentName }}NetworkInput::Reflect(context);
|
||||
{% endif %}
|
||||
}
|
||||
|
||||
void {{ ComponentBaseName }}::ReflectToEditContext(AZ::ReflectContext* context)
|
||||
@@ -1448,8 +1522,8 @@ namespace {{ Component.attrib['Namespace'] }}
|
||||
{{ DefineNetworkPropertyBehaviorReflection(Component, 'Authority', 'Server', ComponentName) | indent(16) -}}
|
||||
{{ DefineNetworkPropertyBehaviorReflection(Component, 'Authority', 'Client', ComponentName) | indent(16) -}}
|
||||
{{ DefineNetworkPropertyBehaviorReflection(Component, 'Authority', 'Autonomous', ComponentName) | indent(16) -}}
|
||||
{{ DefineNetworkPropertyBehaviorReflection(Component, 'Autonomous', 'Authority', ComponentName) | indent(16) -}}
|
||||
|
||||
{{ DefineNetworkPropertyBehaviorReflection(Component, 'Autonomous', 'Authority', ComponentName) | indent(16) }}
|
||||
|
||||
// Reflect RPCs
|
||||
{{ ReflectRpcInvocations(Component, ComponentName, 'Server', 'Authority')|indent(4) -}}
|
||||
{{ ReflectRpcInvocations(Component, ComponentName, 'Autonomous', 'Authority')|indent(4) -}}
|
||||
@@ -1459,7 +1533,6 @@ namespace {{ Component.attrib['Namespace'] }}
|
||||
{{ ReflectRpcEvents(Component, ComponentName, 'Autonomous', 'Authority')|indent(4) -}}
|
||||
{{ ReflectRpcEvents(Component, ComponentName, 'Authority', 'Autonomous')|indent(4) -}}
|
||||
{{ ReflectRpcEvents(Component, ComponentName, 'Authority', 'Client')|indent(4) -}}
|
||||
|
||||
{{- DefineArchetypePropertyBehaviorReflection(Component, ComponentName) | indent(16) }}
|
||||
;
|
||||
}
|
||||
@@ -1508,7 +1581,6 @@ namespace {{ Component.attrib['Namespace'] }}
|
||||
}
|
||||
|
||||
{{ ComponentBaseName }}::{{ ComponentBaseName }}() = default;
|
||||
|
||||
{{ ComponentBaseName }}::~{{ ComponentBaseName }}() = default;
|
||||
|
||||
void {{ ComponentBaseName }}::Init()
|
||||
|
||||
@@ -278,6 +278,7 @@ namespace Multiplayer
|
||||
AZ_Assert(IsNetEntityRoleAutonomous(), "Incorrect network role for input creation");
|
||||
for (MultiplayerComponent* multiplayerComponent : m_multiplayerInputComponentVector)
|
||||
{
|
||||
multiplayerComponent->GetController()->CreateInputFromScript(networkInput, deltaTime);
|
||||
multiplayerComponent->GetController()->CreateInput(networkInput, deltaTime);
|
||||
}
|
||||
}
|
||||
@@ -289,6 +290,7 @@ namespace Multiplayer
|
||||
AZ_Assert((NetworkRoleHasController(m_netEntityRole)), "Incorrect network role for input processing");
|
||||
for (MultiplayerComponent* multiplayerComponent : m_multiplayerInputComponentVector)
|
||||
{
|
||||
multiplayerComponent->GetController()->ProcessInputFromScript(networkInput, deltaTime);
|
||||
multiplayerComponent->GetController()->ProcessInput(networkInput, deltaTime);
|
||||
}
|
||||
m_isProcessingInput = false;
|
||||
|
||||
@@ -1,45 +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
|
||||
#
|
||||
#
|
||||
# This file is copied during engine registration. Edits to this file will be lost next
|
||||
# time a registration happens.
|
||||
|
||||
include_guard()
|
||||
|
||||
set(LY_EXTERNAL_SUBDIRS "" CACHE STRING "Additional list of subdirectory to recurse into via the cmake `add_subdirectory()` command. \
|
||||
The subdirectories are included after the restricted platform folders have been visited by a call to `add_subdirectory(restricted/\${restricted_platform})`")
|
||||
|
||||
#! read_engine_external_subdirs
|
||||
# Read the external subdirectories from the engine.json file
|
||||
# External subdirectories are any folders with CMakeLists.txt in them
|
||||
# This could be regular subdirectories, Gems(contains an additional gem.json),
|
||||
# Restricted folders(contains an additional restricted.json), etc...
|
||||
# \arg:output_external_subdirs name of output variable to store external subdirectories into
|
||||
function(read_engine_external_subdirs output_external_subdirs)
|
||||
ly_file_read(${LY_ROOT_FOLDER}/engine.json engine_json_data)
|
||||
string(JSON external_subdirs_count ERROR_VARIABLE engine_json_error
|
||||
LENGTH ${engine_json_data} "external_subdirectories")
|
||||
if(engine_json_error)
|
||||
message(FATAL_ERROR "Error querying number of elements in JSON array \"external_subdirectories\": ${engine_json_error}")
|
||||
endif()
|
||||
|
||||
if(external_subdirs_count GREATER 0)
|
||||
math(EXPR external_subdir_range "${external_subdirs_count}-1")
|
||||
# Convert the paths the relative paths to absolute paths using the engine root
|
||||
# as the base directory
|
||||
foreach(external_subdir_index RANGE ${external_subdir_range})
|
||||
string(JSON external_subdir ERROR_VARIABLE engine_json_error
|
||||
GET ${engine_json_data} "external_subdirectories" "${external_subdir_index}")
|
||||
if(engine_json_error)
|
||||
message(FATAL_ERROR "Error reading field at index ${external_subdir_index} in \"external_subdirectories\" JSON array: ${engine_json_error}")
|
||||
endif()
|
||||
file(REAL_PATH ${external_subdir} real_external_subdir BASE_DIRECTORY ${CMAKE_SOURCE_DIR})
|
||||
list(APPEND external_subdirs ${real_external_subdir})
|
||||
endforeach()
|
||||
endif()
|
||||
set(${output_external_subdirs} ${external_subdirs} PARENT_SCOPE)
|
||||
endfunction()
|
||||
@@ -15,7 +15,6 @@ set(FILES
|
||||
Configurations.cmake
|
||||
Dependencies.cmake
|
||||
Deployment.cmake
|
||||
EngineJson.cmake
|
||||
FileUtil.cmake
|
||||
Findo3de.cmake
|
||||
Gems.cmake
|
||||
@@ -28,6 +27,7 @@ set(FILES
|
||||
LYPython.cmake
|
||||
LYWrappers.cmake
|
||||
Monolithic.cmake
|
||||
O3DEJson.cmake
|
||||
OutputDirectory.cmake
|
||||
Packaging.cmake
|
||||
PAL.cmake
|
||||
|
||||
Reference in New Issue
Block a user