From 17a79daad89afe48b8d03df852c99eedcc887e9d Mon Sep 17 00:00:00 2001 From: sphrose <82213493+sphrose@users.noreply.github.com> Date: Mon, 12 Jul 2021 16:07:52 +0100 Subject: [PATCH 01/51] Add clear console when starting gamemode setting. Signed-off-by: sphrose <82213493+sphrose@users.noreply.github.com> --- Code/Editor/Controls/ConsoleSCB.cpp | 4 ++++ Code/Editor/Controls/ConsoleSCB.h | 2 ++ Code/Editor/EditorPreferencesPageGeneral.cpp | 5 +++++ Code/Editor/EditorPreferencesPageGeneral.h | 1 + Code/Editor/GameEngine.cpp | 6 ++++++ Code/Editor/Settings.cpp | 5 +++++ Code/Editor/Settings.h | 1 + 7 files changed, 24 insertions(+) diff --git a/Code/Editor/Controls/ConsoleSCB.cpp b/Code/Editor/Controls/ConsoleSCB.cpp index 32ec6506b1..32731b7317 100644 --- a/Code/Editor/Controls/ConsoleSCB.cpp +++ b/Code/Editor/Controls/ConsoleSCB.cpp @@ -537,6 +537,10 @@ void CConsoleSCB::AddToPendingLines(const QString& text, bool bNewLine) s_pendingLines.push_back({ text, bNewLine }); } +void CConsoleSCB::ClearText() +{ + ui->textEdit->clear(); +} /** * When a CVar variable is updated, we need to tell alert our console variables * pane so it can update the corresponding row diff --git a/Code/Editor/Controls/ConsoleSCB.h b/Code/Editor/Controls/ConsoleSCB.h index faa8c06124..d561e5f5a6 100644 --- a/Code/Editor/Controls/ConsoleSCB.h +++ b/Code/Editor/Controls/ConsoleSCB.h @@ -174,6 +174,8 @@ public: static void AddToPendingLines(const QString& text, bool bNewLine); // call this function instead of AddToConsole() until an instance of CConsoleSCB exists to prevent messages from getting lost + void ClearText(); + // EditorPreferencesNotificationBus... void OnEditorPreferencesChanged() override; diff --git a/Code/Editor/EditorPreferencesPageGeneral.cpp b/Code/Editor/EditorPreferencesPageGeneral.cpp index 861ef10c23..cf4a430c7e 100644 --- a/Code/Editor/EditorPreferencesPageGeneral.cpp +++ b/Code/Editor/EditorPreferencesPageGeneral.cpp @@ -31,6 +31,7 @@ void CEditorPreferencesPage_General::Reflect(AZ::SerializeContext& serialize) ->Field("PreviewPanel", &GeneralSettings::m_previewPanel) ->Field("ApplyConfigSpec", &GeneralSettings::m_applyConfigSpec) ->Field("EnableSourceControl", &GeneralSettings::m_enableSourceControl) + ->Field("ClearConsole", &GeneralSettings::m_clearConsoleOnGameModeStart) ->Field("ConsoleBackgroundColorTheme", &GeneralSettings::m_consoleBackgroundColorTheme) ->Field("AutoloadLastLevel", &GeneralSettings::m_autoLoadLastLevel) ->Field("ShowTimeInConsole", &GeneralSettings::m_bShowTimeInConsole) @@ -76,6 +77,8 @@ void CEditorPreferencesPage_General::Reflect(AZ::SerializeContext& serialize) ->DataElement(AZ::Edit::UIHandlers::CheckBox, &GeneralSettings::m_previewPanel, "Show Geometry Preview Panel", "Show Geometry Preview Panel") ->DataElement(AZ::Edit::UIHandlers::CheckBox, &GeneralSettings::m_applyConfigSpec, "Hide objects by config spec", "Hide objects by config spec") ->DataElement(AZ::Edit::UIHandlers::CheckBox, &GeneralSettings::m_enableSourceControl, "Enable Source Control", "Enable Source Control") + ->DataElement( + AZ::Edit::UIHandlers::CheckBox, &GeneralSettings::m_clearConsoleOnGameModeStart, "Clear Console at Game Startup", "Clear Console when Game Mode Starts") ->DataElement(AZ::Edit::UIHandlers::ComboBox, &GeneralSettings::m_consoleBackgroundColorTheme, "Console Background", "Console Background") ->EnumAttribute(AzToolsFramework::ConsoleColorTheme::Light, "Light") ->EnumAttribute(AzToolsFramework::ConsoleColorTheme::Dark, "Dark") @@ -141,6 +144,7 @@ void CEditorPreferencesPage_General::OnApply() gSettings.bPreviewGeometryWindow = m_generalSettings.m_previewPanel; gSettings.bApplyConfigSpecInEditor = m_generalSettings.m_applyConfigSpec; gSettings.enableSourceControl = m_generalSettings.m_enableSourceControl; + gSettings.clearConsoleOnGameModeStart = m_generalSettings.m_clearConsoleOnGameModeStart; gSettings.consoleBackgroundColorTheme = m_generalSettings.m_consoleBackgroundColorTheme; gSettings.bShowTimeInConsole = m_generalSettings.m_bShowTimeInConsole; gSettings.bShowDashboardAtStartup = m_messaging.m_showDashboard; @@ -175,6 +179,7 @@ void CEditorPreferencesPage_General::InitializeSettings() m_generalSettings.m_previewPanel = gSettings.bPreviewGeometryWindow; m_generalSettings.m_applyConfigSpec = gSettings.bApplyConfigSpecInEditor; m_generalSettings.m_enableSourceControl = gSettings.enableSourceControl; + m_generalSettings.m_clearConsoleOnGameModeStart = gSettings.clearConsoleOnGameModeStart; m_generalSettings.m_consoleBackgroundColorTheme = gSettings.consoleBackgroundColorTheme; m_generalSettings.m_bShowTimeInConsole = gSettings.bShowTimeInConsole; m_generalSettings.m_autoLoadLastLevel = gSettings.bAutoloadLastLevelAtStartup; diff --git a/Code/Editor/EditorPreferencesPageGeneral.h b/Code/Editor/EditorPreferencesPageGeneral.h index ca315c2d01..557a9d5bce 100644 --- a/Code/Editor/EditorPreferencesPageGeneral.h +++ b/Code/Editor/EditorPreferencesPageGeneral.h @@ -45,6 +45,7 @@ private: bool m_previewPanel; bool m_applyConfigSpec; bool m_enableSourceControl; + bool m_clearConsoleOnGameModeStart; AzToolsFramework::ConsoleColorTheme m_consoleBackgroundColorTheme; bool m_autoLoadLastLevel; bool m_bShowTimeInConsole; diff --git a/Code/Editor/GameEngine.cpp b/Code/Editor/GameEngine.cpp index a729be9175..e77d44d98e 100644 --- a/Code/Editor/GameEngine.cpp +++ b/Code/Editor/GameEngine.cpp @@ -29,6 +29,7 @@ // Editor #include "IEditorImpl.h" +#include "Controls/ConsoleSCB.h" #include "CryEditDoc.h" #include "Settings.h" @@ -565,6 +566,11 @@ void CGameEngine::SwitchToInGame() streamer->QueueRequest(flush); wait.acquire(); } + + if (gSettings.clearConsoleOnGameModeStart) + { + CConsoleSCB::GetCreatedInstance()->ClearText(); + } GetIEditor()->Notify(eNotify_OnBeginGameMode); diff --git a/Code/Editor/Settings.cpp b/Code/Editor/Settings.cpp index cdd44b5fff..f675116e56 100644 --- a/Code/Editor/Settings.cpp +++ b/Code/Editor/Settings.cpp @@ -188,6 +188,7 @@ SEditorSettings::SEditorSettings() consoleBackgroundColorTheme = AzToolsFramework::ConsoleColorTheme::Dark; bShowTimeInConsole = false; + clearConsoleOnGameModeStart = false; enableSceneInspector = false; @@ -526,6 +527,8 @@ void SEditorSettings::Save() SaveValue("Settings", "ConsoleBackgroundColorThemeV2", (int)consoleBackgroundColorTheme); + SaveValue("Settings", "ClearConsoleOnGameModeStart", clearConsoleOnGameModeStart); + SaveValue("Settings", "ShowTimeInConsole", bShowTimeInConsole); SaveValue("Settings", "EnableSceneInspector", enableSceneInspector); @@ -744,6 +747,8 @@ void SEditorSettings::Load() consoleBackgroundColorTheme = AzToolsFramework::ConsoleColorTheme::Dark; } + LoadValue("Settings", "ClearConsoleOnGameModeStart", clearConsoleOnGameModeStart); + LoadValue("Settings", "ShowTimeInConsole", bShowTimeInConsole); LoadValue("Settings", "EnableSceneInspector", enableSceneInspector); diff --git a/Code/Editor/Settings.h b/Code/Editor/Settings.h index cef77de6a7..ff4252f8d8 100644 --- a/Code/Editor/Settings.h +++ b/Code/Editor/Settings.h @@ -379,6 +379,7 @@ AZ_POP_DISABLE_DLL_EXPORT_BASECLASS_WARNING //! Source Control Enabling. bool enableSourceControl; + bool clearConsoleOnGameModeStart; //! Text editor. QString textEditorForScript; From 93cbb7c98186c15427826f639edf8e5abf30499b Mon Sep 17 00:00:00 2001 From: sphrose <82213493+sphrose@users.noreply.github.com> Date: Wed, 14 Jul 2021 10:14:25 +0100 Subject: [PATCH 02/51] Review changes Changed text case, removed ClearText API and added GameStartup motify listening. Signed-off-by: sphrose <82213493+sphrose@users.noreply.github.com> --- Code/Editor/Controls/ConsoleSCB.cpp | 23 ++++++++++++++++---- Code/Editor/Controls/ConsoleSCB.h | 5 +++-- Code/Editor/EditorPreferencesPageGeneral.cpp | 2 +- Code/Editor/GameEngine.cpp | 6 ----- 4 files changed, 23 insertions(+), 13 deletions(-) diff --git a/Code/Editor/Controls/ConsoleSCB.cpp b/Code/Editor/Controls/ConsoleSCB.cpp index 32731b7317..3498476797 100644 --- a/Code/Editor/Controls/ConsoleSCB.cpp +++ b/Code/Editor/Controls/ConsoleSCB.cpp @@ -337,6 +337,8 @@ CConsoleSCB::CConsoleSCB(QWidget* parent) connect(findPreviousAction, &QAction::triggered, this, &CConsoleSCB::findPrevious); ui->findPrevButton->addAction(findPreviousAction); + GetIEditor()->RegisterNotifyListener(this); + connect(ui->button, &QPushButton::clicked, this, &CConsoleSCB::showVariableEditor); connect(ui->findButton, &QPushButton::clicked, this, &CConsoleSCB::toggleConsoleSearch); connect(ui->textEdit, &ConsoleTextEdit::searchBarRequested, this, [this] @@ -375,6 +377,8 @@ CConsoleSCB::~CConsoleSCB() { AzToolsFramework::EditorPreferencesNotificationBus::Handler::BusDisconnect(); + GetIEditor()->UnregisterNotifyListener(this); + s_consoleSCB = nullptr; CLogFile::AttachEditBox(nullptr); } @@ -537,10 +541,6 @@ void CConsoleSCB::AddToPendingLines(const QString& text, bool bNewLine) s_pendingLines.push_back({ text, bNewLine }); } -void CConsoleSCB::ClearText() -{ - ui->textEdit->clear(); -} /** * When a CVar variable is updated, we need to tell alert our console variables * pane so it can update the corresponding row @@ -1355,4 +1355,19 @@ CConsoleSCB* CConsoleSCB::GetCreatedInstance() return s_consoleSCB; } +void CConsoleSCB::OnEditorNotifyEvent(EEditorNotifyEvent event) +{ + switch (event) + { + case eNotify_OnBeginGameMode: + if (gSettings.clearConsoleOnGameModeStart) + { + ui->textEdit->clear(); + } + break; + default: + break; + } +} + #include diff --git a/Code/Editor/Controls/ConsoleSCB.h b/Code/Editor/Controls/ConsoleSCB.h index d561e5f5a6..a62c1009b0 100644 --- a/Code/Editor/Controls/ConsoleSCB.h +++ b/Code/Editor/Controls/ConsoleSCB.h @@ -158,6 +158,7 @@ private: class CConsoleSCB : public QWidget , private AzToolsFramework::EditorPreferencesNotificationBus::Handler + , public IEditorNotifyListener { Q_OBJECT public: @@ -174,8 +175,6 @@ public: static void AddToPendingLines(const QString& text, bool bNewLine); // call this function instead of AddToConsole() until an instance of CConsoleSCB exists to prevent messages from getting lost - void ClearText(); - // EditorPreferencesNotificationBus... void OnEditorPreferencesChanged() override; @@ -188,6 +187,8 @@ private Q_SLOTS: void findNext(); private: + void OnEditorNotifyEvent(EEditorNotifyEvent event) override; + QScopedPointer ui; int m_richEditTextLength; diff --git a/Code/Editor/EditorPreferencesPageGeneral.cpp b/Code/Editor/EditorPreferencesPageGeneral.cpp index cf4a430c7e..046a546f59 100644 --- a/Code/Editor/EditorPreferencesPageGeneral.cpp +++ b/Code/Editor/EditorPreferencesPageGeneral.cpp @@ -78,7 +78,7 @@ void CEditorPreferencesPage_General::Reflect(AZ::SerializeContext& serialize) ->DataElement(AZ::Edit::UIHandlers::CheckBox, &GeneralSettings::m_applyConfigSpec, "Hide objects by config spec", "Hide objects by config spec") ->DataElement(AZ::Edit::UIHandlers::CheckBox, &GeneralSettings::m_enableSourceControl, "Enable Source Control", "Enable Source Control") ->DataElement( - AZ::Edit::UIHandlers::CheckBox, &GeneralSettings::m_clearConsoleOnGameModeStart, "Clear Console at Game Startup", "Clear Console when Game Mode Starts") + AZ::Edit::UIHandlers::CheckBox, &GeneralSettings::m_clearConsoleOnGameModeStart, "Clear Console at game startup", "Clear Console when game mode starts") ->DataElement(AZ::Edit::UIHandlers::ComboBox, &GeneralSettings::m_consoleBackgroundColorTheme, "Console Background", "Console Background") ->EnumAttribute(AzToolsFramework::ConsoleColorTheme::Light, "Light") ->EnumAttribute(AzToolsFramework::ConsoleColorTheme::Dark, "Dark") diff --git a/Code/Editor/GameEngine.cpp b/Code/Editor/GameEngine.cpp index e77d44d98e..c4fd38afc9 100644 --- a/Code/Editor/GameEngine.cpp +++ b/Code/Editor/GameEngine.cpp @@ -29,7 +29,6 @@ // Editor #include "IEditorImpl.h" -#include "Controls/ConsoleSCB.h" #include "CryEditDoc.h" #include "Settings.h" @@ -567,11 +566,6 @@ void CGameEngine::SwitchToInGame() wait.acquire(); } - if (gSettings.clearConsoleOnGameModeStart) - { - CConsoleSCB::GetCreatedInstance()->ClearText(); - } - GetIEditor()->Notify(eNotify_OnBeginGameMode); m_pISystem->GetIMovieSystem()->EnablePhysicsEvents(true); From fccb86d5c043b5104c41864d2da3c32b62859946 Mon Sep 17 00:00:00 2001 From: AMZN-Phil Date: Fri, 23 Jul 2021 13:58:58 -0700 Subject: [PATCH 03/51] Check for build process exit status and display log link in more cases. Signed-off-by: AMZN-Phil --- .../Platform/Windows/ProjectBuilderWorker_windows.cpp | 7 +++++-- .../ProjectManager/Source/ProjectBuilderController.cpp | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Code/Tools/ProjectManager/Platform/Windows/ProjectBuilderWorker_windows.cpp b/Code/Tools/ProjectManager/Platform/Windows/ProjectBuilderWorker_windows.cpp index a228f58e51..8856e2312a 100644 --- a/Code/Tools/ProjectManager/Platform/Windows/ProjectBuilderWorker_windows.cpp +++ b/Code/Tools/ProjectManager/Platform/Windows/ProjectBuilderWorker_windows.cpp @@ -118,7 +118,9 @@ namespace O3DE::ProjectManager } } - if (m_configProjectProcess->exitCode() != 0 || !containsGeneratingDone) + if (m_configProjectProcess->exitStatus() != QProcess::ExitStatus::NormalExit + || m_configProjectProcess->exitCode() != 0 + || !containsGeneratingDone) { QString error = tr("Configuring project failed. See log for details."); QStringToAZTracePrint(error); @@ -180,7 +182,8 @@ namespace O3DE::ProjectManager } } - if (m_configProjectProcess->exitCode() != 0) + if (m_configProjectProcess->exitStatus() != QProcess::ExitStatus::NormalExit + || m_configProjectProcess->exitCode() != 0) { QString error = tr("Building project failed. See log for details."); QStringToAZTracePrint(error); diff --git a/Code/Tools/ProjectManager/Source/ProjectBuilderController.cpp b/Code/Tools/ProjectManager/Source/ProjectBuilderController.cpp index e782f0b57f..0ff963e539 100644 --- a/Code/Tools/ProjectManager/Source/ProjectBuilderController.cpp +++ b/Code/Tools/ProjectManager/Source/ProjectBuilderController.cpp @@ -104,7 +104,7 @@ namespace O3DE::ProjectManager QMessageBox::critical(m_parent, tr("Project Failed to Build!"), result); m_projectInfo.m_buildFailed = true; - m_projectInfo.m_logUrl = QUrl(); + m_projectInfo.m_logUrl = QUrl("file:///" + m_worker->GetLogFilePath()); emit NotifyBuildProject(m_projectInfo); } From 74498089c3fe08474c97f51b72565f836a6e14c7 Mon Sep 17 00:00:00 2001 From: nvsickle Date: Mon, 2 Aug 2021 10:45:09 -0700 Subject: [PATCH 04/51] Ensure Editor FOV corrects on resize Signed-off-by: nvsickle --- Code/Editor/EditorViewportWidget.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Code/Editor/EditorViewportWidget.cpp b/Code/Editor/EditorViewportWidget.cpp index 357c27fd72..4ea36728ad 100644 --- a/Code/Editor/EditorViewportWidget.cpp +++ b/Code/Editor/EditorViewportWidget.cpp @@ -472,6 +472,12 @@ void EditorViewportWidget::Update() m_Camera.SetZRange(cameraState.m_nearClip, cameraState.m_farClip); } + // Ensure the FOV matches our internally stored setting if we're using the Editor camera + if (!m_viewEntityId.IsValid() && !GetIEditor()->IsInGameMode()) + { + SetFOV(GetFOV()); + } + // Reset the camera update flag now that we're finished updating our viewport context m_updateCameraPositionNextTick = false; @@ -2624,8 +2630,6 @@ void EditorViewportWidget::DestroyRenderContext() ////////////////////////////////////////////////////////////////////////// void EditorViewportWidget::SetDefaultCamera() { - // Ensure the FOV matches our internally stored setting - SetFOV(GetFOV()); if (IsDefaultCamera()) { return; From 8730d5657fa96328ba391b33eca7ae6db6d5539f Mon Sep 17 00:00:00 2001 From: Chris Burel Date: Fri, 16 Jul 2021 08:52:55 -0700 Subject: [PATCH 05/51] Allow special characters in AnimGraph node group names Relying on the command system's string processing syntax prevents certain names from being used. This converts the AnimGraphAdjustNodeGroup command to be directly invokable, so that arguments can be passed directly, instead of going through the CommandLine string parsing. Signed-off-by: Chris Burel --- .../Source/AnimGraphNodeCommands.cpp | 28 +- .../Source/AnimGraphNodeGroupCommands.cpp | 251 +++++++++--------- .../Source/AnimGraphNodeGroupCommands.h | 71 ++++- .../CommandSystem/Source/ParameterMixins.h | 2 + .../Source/AnimGraph/BlendGraphWidget.cpp | 30 ++- .../Source/AnimGraph/NodeGroupWindow.cpp | 47 ++-- Gems/EMotionFX/Code/MCore/Source/Command.cpp | 6 +- Gems/EMotionFX/Code/MCore/Source/Command.h | 2 +- 8 files changed, 260 insertions(+), 177 deletions(-) diff --git a/Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/AnimGraphNodeCommands.cpp b/Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/AnimGraphNodeCommands.cpp index f72833df56..fe41312701 100644 --- a/Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/AnimGraphNodeCommands.cpp +++ b/Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/AnimGraphNodeCommands.cpp @@ -11,6 +11,7 @@ #include "CommandManager.h" #include +#include #include #include #include @@ -858,8 +859,16 @@ namespace CommandSystem // add it to the old node group if it was assigned to one before if (!mNodeGroupName.empty()) { - commandString = AZStd::string::format("AnimGraphAdjustNodeGroup -animGraphID %i -name \"%s\" -nodeNames \"%s\" -nodeAction \"add\"", animGraph->GetID(), mNodeGroupName.c_str(), mName.c_str()); - if (GetCommandManager()->ExecuteCommandInsideCommand(commandString.c_str(), outResult) == false) + auto* command = aznew CommandSystem::CommandAnimGraphAdjustNodeGroup( + GetCommandManager()->FindCommand(CommandSystem::CommandAnimGraphAdjustNodeGroup::s_commandName), + /*animGraphId = */ animGraph->GetID(), + /*name = */ mNodeGroupName, + /*visible = */ AZStd::nullopt, + /*newName = */ AZStd::nullopt, + /*nodeNames = */ {{mName}}, + /*nodeAction = */ CommandSystem::CommandAnimGraphAdjustNodeGroup::NodeAction::Add + ); + if (GetCommandManager()->ExecuteCommandInsideCommand(command, outResult) == false) { if (outResult.size() > 0) { @@ -1363,11 +1372,16 @@ namespace CommandSystem EMotionFX::AnimGraphNodeGroup* nodeGroup = node->GetAnimGraph()->FindNodeGroupForNode(node); if (nodeGroup && !cutMode) { - commandString = AZStd::string::format("AnimGraphAdjustNodeGroup -animGraphID %d -name \"%s\" -nodeNames \"%s\" -nodeAction \"add\"", - targetAnimGraph->GetID(), - nodeGroup->GetName(), - nodeName.c_str()); - commandGroup->AddCommandString(commandString); + auto* command = aznew CommandSystem::CommandAnimGraphAdjustNodeGroup( + GetCommandManager()->FindCommand(CommandSystem::CommandAnimGraphAdjustNodeGroup::s_commandName), + /*animGraphId = */ targetAnimGraph->GetID(), + /*name = */ nodeGroup->GetNameString(), + /*visible = */ AZStd::nullopt, + /*newName = */ AZStd::nullopt, + /*nodeNames = */ {{nodeName}}, + /*nodeAction = */ CommandSystem::CommandAnimGraphAdjustNodeGroup::NodeAction::Add + ); + commandGroup->AddCommand(command); } // Recurse through the child nodes. diff --git a/Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/AnimGraphNodeGroupCommands.cpp b/Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/AnimGraphNodeGroupCommands.cpp index 29ee45312b..f5f913f0ca 100644 --- a/Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/AnimGraphNodeGroupCommands.cpp +++ b/Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/AnimGraphNodeGroupCommands.cpp @@ -6,6 +6,7 @@ * */ +#include #include #include "AnimGraphNodeGroupCommands.h" #include "AnimGraphConnectionCommands.h" @@ -22,45 +23,46 @@ namespace CommandSystem { + AZ_CLASS_ALLOCATOR_IMPL(CommandAnimGraphAdjustNodeGroup, EMotionFX::CommandAllocator, 0) + //-------------------------------------------------------------------------------- // CommandAnimGraphAdjustNodeGroup //-------------------------------------------------------------------------------- - CommandAnimGraphAdjustNodeGroup::CommandAnimGraphAdjustNodeGroup(MCore::Command* orgCommand) - : MCore::Command("AnimGraphAdjustNodeGroup", orgCommand) + CommandAnimGraphAdjustNodeGroup::CommandAnimGraphAdjustNodeGroup( + MCore::Command* orgCommand, + AZ::u32 animGraphId, + AZStd::string name, + AZStd::optional visible, + AZStd::optional newName, + AZStd::optional> nodeNames, + AZStd::optional nodeAction, + AZStd::optional color, + AZStd::optional updateUI + ) + : MCore::Command(s_commandName, orgCommand) + , ParameterMixinAnimGraphId(animGraphId) + , m_name(AZStd::move(name)) + , m_isVisible(visible) + , m_newName(AZStd::move(newName)) + , m_nodeNames(AZStd::move(nodeNames)) + , m_nodeAction(nodeAction) + , m_color(color) + , m_updateUI(updateUI) { } - - CommandAnimGraphAdjustNodeGroup::~CommandAnimGraphAdjustNodeGroup() + AZStd::vector CommandAnimGraphAdjustNodeGroup::GenerateNodeNameVector(EMotionFX::AnimGraph* animGraph, const AZStd::vector& nodeIDs) { - } - - - AZStd::string CommandAnimGraphAdjustNodeGroup::GenerateNodeNameString(EMotionFX::AnimGraph* animGraph, const AZStd::vector& nodeIDs) - { - if (nodeIDs.empty()) + AZStd::vector result; + for (const auto& nodeID : nodeIDs) { - return ""; - } - - AZStd::string result; - - const size_t numNodes = nodeIDs.size(); - for (size_t i = 0; i < numNodes; ++i) - { - EMotionFX::AnimGraphNode* animGraphNode = animGraph->RecursiveFindNodeById(nodeIDs[i]); + const EMotionFX::AnimGraphNode* animGraphNode = animGraph->RecursiveFindNodeById(nodeID); if (!animGraphNode) { continue; } - - result += animGraphNode->GetName(); - if (i < numNodes - 1) - { - result += ';'; - } + result.emplace_back(animGraphNode->GetName()); } - return result; } @@ -80,78 +82,51 @@ namespace CommandSystem } - bool CommandAnimGraphAdjustNodeGroup::Execute(const MCore::CommandLine& parameters, AZStd::string& outResult) + bool CommandAnimGraphAdjustNodeGroup::Execute(const MCore::CommandLine&, AZStd::string& outResult) { - EMotionFX::AnimGraph* animGraph = CommandsGetAnimGraph(parameters, this, outResult); + EMotionFX::AnimGraph* animGraph = EMotionFX::GetAnimGraphManager().FindAnimGraphByID(m_animGraphId); if (!animGraph) { return false; } - // get the node group name - AZStd::string groupName; - parameters.GetValue("name", this, groupName); - // find the node group index - const uint32 groupIndex = animGraph->FindNodeGroupIndexByName(groupName.c_str()); + const uint32 groupIndex = animGraph->FindNodeGroupIndexByName(m_name.c_str()); if (groupIndex == MCORE_INVALIDINDEX32) { - outResult = AZStd::string::format("Node group \"%s\" can not be found.", groupName.c_str()); + outResult = AZStd::string::format("Node group \"%s\" can not be found.", m_name.c_str()); return false; } - // get a pointer to the node group and keep the old name EMotionFX::AnimGraphNodeGroup* nodeGroup = animGraph->GetNodeGroup(groupIndex); - mOldName = nodeGroup->GetName(); - // is visible? - if (parameters.CheckIfHasParameter("isVisible")) + if (m_isVisible.has_value()) { - const bool isVisible = parameters.GetValueAsBool("isVisible", this); - mOldIsVisible = nodeGroup->GetIsVisible(); - nodeGroup->SetIsVisible(isVisible); + m_oldIsVisible = nodeGroup->GetIsVisible(); + nodeGroup->SetIsVisible(*m_isVisible); } - // background color - if (parameters.CheckIfHasParameter("color")) + if (m_color.has_value()) { - const AZ::Vector4 colorVector4 = parameters.GetValueAsVector4("color", this); - const AZ::u32 color = AZ::Color(static_cast(colorVector4.GetX()), static_cast(colorVector4.GetY()), static_cast(colorVector4.GetZ()), static_cast(colorVector4.GetW())).ToU32(); - mOldColor = nodeGroup->GetColor(); - nodeGroup->SetColor(color); + m_oldColor = nodeGroup->GetColor(); + nodeGroup->SetColor(*m_color); } - // set the new name - // if the new name is empty, the name is not changed - AZStd::string newGroupName; - parameters.GetValue("newName", this, newGroupName); - if (!newGroupName.empty()) + if (m_newName.has_value()) { - nodeGroup->SetName(newGroupName.c_str()); + nodeGroup->SetName(m_newName->c_str()); } // check if parametes nodeNames is set - if (parameters.CheckIfHasParameter("nodeNames")) + if (m_nodeNames.has_value()) { // keep the old nodes IDs - mOldNodeIds = CollectNodeIdsFromGroup(nodeGroup); - - // get the node action - AZStd::string nodeAction; - parameters.GetValue("nodeAction", this, nodeAction); - - // get the node names and split the string - AZStd::string nodeNamesString; - parameters.GetValue("nodeNames", this, nodeNamesString); - - - AZStd::vector nodeNames; - AzFramework::StringFunc::Tokenize(nodeNamesString.c_str(), nodeNames, ";", false, true); + m_oldNodeIds = CollectNodeIdsFromGroup(nodeGroup); // remove the selected nodes from the given node group - if (AzFramework::StringFunc::Equal(nodeAction.c_str(), "remove")) + if (*m_nodeAction == NodeAction::Remove) { - for (const AZStd::string& nodeName : nodeNames) + for (const AZStd::string& nodeName : *m_nodeNames) { EMotionFX::AnimGraphNode* animGraphNode = animGraph->RecursiveFindNodeByName(nodeName.c_str()); if (!animGraphNode) @@ -163,9 +138,9 @@ namespace CommandSystem nodeGroup->RemoveNodeById(animGraphNode->GetId()); } } - else if (AzFramework::StringFunc::Equal(nodeAction.c_str(), "add")) // add the selected nodes to the given node group + else if (*m_nodeAction == NodeAction::Add) { - for (const AZStd::string& nodeName : nodeNames) + for (const AZStd::string& nodeName : *m_nodeNames) { EMotionFX::AnimGraphNode* animGraphNode = animGraph->RecursiveFindNodeByName(nodeName.c_str()); if (!animGraphNode) @@ -184,12 +159,12 @@ namespace CommandSystem nodeGroup->AddNode(animGraphNode->GetId()); } } - else if (AzFramework::StringFunc::Equal(nodeAction.c_str(), "replace")) // clear the node group and then add the selected nodes to the given node group + else if (*m_nodeAction == NodeAction::Replace) { // clear the node group upfront nodeGroup->RemoveAllNodes(); - for (const AZStd::string& nodeName : nodeNames) + for (const AZStd::string& nodeName : *m_nodeNames) { EMotionFX::AnimGraphNode* animGraphNode = animGraph->RecursiveFindNodeByName(nodeName.c_str()); if (!animGraphNode) @@ -211,68 +186,40 @@ namespace CommandSystem } // save the current dirty flag and tell the anim graph that something got changed - mOldDirtyFlag = animGraph->GetDirtyFlag(); + m_oldDirtyFlag = animGraph->GetDirtyFlag(); animGraph->SetDirtyFlag(true); return true; } // undo the command - bool CommandAnimGraphAdjustNodeGroup::Undo(const MCore::CommandLine& parameters, AZStd::string& outResult) + bool CommandAnimGraphAdjustNodeGroup::Undo(const MCore::CommandLine&, AZStd::string& outResult) { - EMotionFX::AnimGraph* animGraph = CommandsGetAnimGraph(parameters, this, outResult); + EMotionFX::AnimGraph* animGraph = EMotionFX::GetAnimGraphManager().FindAnimGraphByID(m_animGraphId); if (!animGraph) { return false; } - AZStd::string commandString = AZStd::string::format("AnimGraphAdjustNodeGroup -animGraphID %i", animGraph->GetID()); - - // set the old name or simply set the name if the name is not changed - if (parameters.CheckIfHasParameter("newName")) - { - AZStd::string newName; - parameters.GetValue("newName", this, newName); - - commandString += AZStd::string::format(" -name \"%s\"", newName.c_str()); - commandString += AZStd::string::format(" -newName \"%s\"", mOldName.c_str()); - } - else - { - commandString += AZStd::string::format(" -name \"%s\"", mOldName.c_str()); - } - - // set the old visible flag - if (parameters.CheckIfHasParameter("isVisible")) - { - commandString += AZStd::string::format(" -isVisible %i", mOldIsVisible); - } - - // set the old color - if (parameters.CheckIfHasParameter("color")) - { - AZ::Color oldColor; - oldColor.FromU32(mOldColor); - const AZStd::string oldColorString = AZStd::string::format("%.8f,%.8f,%.8f,%.8f", static_cast(oldColor.GetR()), static_cast(oldColor.GetG()), static_cast(oldColor.GetB()), static_cast(oldColor.GetA())); - - commandString += AZStd::string::format(" -color \"%s\"", oldColorString.c_str()); - } - - // set the old nodes - if (parameters.CheckIfHasParameter("nodeNames")) - { - const AZStd::string nodeNamesString = CommandAnimGraphAdjustNodeGroup::GenerateNodeNameString(animGraph, mOldNodeIds); - commandString += AZStd::string::format(" -nodeNames \"%s\" -nodeAction \"replace\"", nodeNamesString.c_str()); - } + CommandAnimGraphAdjustNodeGroup* command = aznew CommandAnimGraphAdjustNodeGroup( + GetCommandManager()->FindCommand(CommandAnimGraphAdjustNodeGroup::s_commandName), + /*animGraphId = */ m_animGraphId, + /*name = */ m_newName.has_value() ? *m_newName : m_name, + /*visible = */ m_isVisible.has_value() ? AZStd::optional(m_oldIsVisible) : AZStd::nullopt, + /*newName = */ m_newName.has_value() ? AZStd::optional(m_name) : AZStd::nullopt, + /*nodeNames = */ m_nodeNames.has_value() ? AZStd::optional>(GenerateNodeNameVector(animGraph, m_oldNodeIds)) : AZStd::nullopt, + /*nodeAction = */ m_nodeNames.has_value() ? AZStd::optional(NodeAction::Replace) : AZStd::nullopt, + /*color = */ m_color.has_value() ? AZStd::optional(m_oldColor) : AZStd::nullopt + ); // execute the command - if (!GetCommandManager()->ExecuteCommandInsideCommand(commandString, outResult)) + if (!GetCommandManager()->ExecuteCommandInsideCommand(command, outResult)) { AZ_Error("EMotionFX", false, outResult.c_str()); } // set the dirty flag back to the old value - animGraph->SetDirtyFlag(mOldDirtyFlag); + animGraph->SetDirtyFlag(m_oldDirtyFlag); return true; } @@ -282,7 +229,7 @@ namespace CommandSystem { GetSyntax().ReserveParameters(8); GetSyntax().AddRequiredParameter("name", "The name of the node group to adjust.", MCore::CommandSyntax::PARAMTYPE_STRING); - GetSyntax().AddParameter("animGraphID", "The id of the blend set the node group belongs to.", MCore::CommandSyntax::PARAMTYPE_INT, "-1"); + EMotionFX::ParameterMixinAnimGraphId::InitSyntax(GetSyntax(), /*isParameterRequired=*/ false); GetSyntax().AddParameter("isVisible", "The visibility flag of the node group.", MCore::CommandSyntax::PARAMTYPE_BOOLEAN, "true"); GetSyntax().AddParameter("newName", "The new name of the node group.", MCore::CommandSyntax::PARAMTYPE_STRING, ""); GetSyntax().AddParameter("nodeNames", "A list of node names that should be added/removed to/from the node group.", MCore::CommandSyntax::PARAMTYPE_STRING, ""); @@ -291,6 +238,51 @@ namespace CommandSystem GetSyntax().AddParameter("updateUI", "Setting this to true will trigger a refresh of the node groups UI.", MCore::CommandSyntax::PARAMTYPE_BOOLEAN, "true"); } + bool CommandAnimGraphAdjustNodeGroup::SetCommandParameters(const MCore::CommandLine& parameters) + { + EMotionFX::ParameterMixinAnimGraphId::SetCommandParameters(parameters); + m_name = parameters.GetValue("name", this); + + if (parameters.CheckIfHasParameter("isVisible")) + { + m_isVisible = parameters.GetValueAsBool("isVisible", this); + } + if (parameters.CheckIfHasParameter("newName")) + { + m_newName = parameters.GetValue("newName", this); + } + if (parameters.CheckIfHasParameter("nodeNames")) + { + m_nodeNames.emplace(); + AzFramework::StringFunc::Tokenize(parameters.GetValue("nodeNames", this), m_nodeNames.value(), ";", false, true); + } + if (parameters.CheckIfHasValue("nodeAction")) + { + const AZStd::string& nodeActionStr = parameters.GetValue("nodeAction", this); + if (nodeActionStr == "add") + { + m_nodeAction = NodeAction::Add; + } + else if (nodeActionStr == "remove") + { + m_nodeAction = NodeAction::Remove; + } + else if (nodeActionStr == "replace") + { + m_nodeAction = NodeAction::Replace; + } + } + if (parameters.CheckIfHasParameter("color")) + { + m_color = AZ::Color(parameters.GetValueAsVector4("color", this)).ToU32(); + } + if (parameters.CheckIfHasParameter("updateUI")) + { + m_updateUI = parameters.GetValueAsBool("updateUI", this); + } + + return true; + } const char* CommandAnimGraphAdjustNodeGroup::GetDescription() const { @@ -447,21 +439,20 @@ namespace CommandSystem MCore::CommandGroup commandGroup; - AZStd::string commandString = AZStd::string::format("AnimGraphAddNodeGroup -animGraphID %i -name \"%s\" -updateUI %s",animGraph->GetID(), mOldName.c_str(), updateWindow.c_str()); - commandGroup.AddCommandString(commandString); + commandGroup.AddCommandString(AZStd::string::format("AnimGraphAddNodeGroup -animGraphID %i -name \"%s\" -updateUI %s",animGraph->GetID(), mOldName.c_str(), updateWindow.c_str())); - const AZStd::string nodeNamesString = CommandAnimGraphAdjustNodeGroup::GenerateNodeNameString(animGraph, mOldNodeIds); + auto* command = aznew CommandAnimGraphAdjustNodeGroup( + GetCommandManager()->FindCommand(CommandAnimGraphAdjustNodeGroup::s_commandName), + /*animGraphId = */ animGraph->GetID(), + /*name = */ mOldName, + /*visible = */ mOldIsVisible, + /*newName = */ AZStd::nullopt, + /*nodeNames = */ CommandAnimGraphAdjustNodeGroup::GenerateNodeNameVector(animGraph, mOldNodeIds), + /*nodeAction = */ CommandAnimGraphAdjustNodeGroup::NodeAction::Add, + /*color = */ mOldColor + ); - AZ::Color oldColor; - oldColor.FromU32(mOldColor); - const AZStd::string oldColorString = AZStd::string::format("%.8f,%.8f,%.8f,%.8f", - static_cast(oldColor.GetR()), static_cast(oldColor.GetG()), static_cast(oldColor.GetB()), static_cast(oldColor.GetA())); - - commandString = AZStd::string::format( - "AnimGraphAdjustNodeGroup -animGraphID %i -name \"%s\" -isVisible %s -color \"%s\" -nodeNames \"%s\" -nodeAction \"add\" -updateUI %s", - animGraph->GetID(), mOldName.c_str(), AZStd::to_string(mOldIsVisible).c_str(), oldColorString.c_str(), nodeNamesString.c_str(), updateWindow.c_str()); - - commandGroup.AddCommandString(commandString); + commandGroup.AddCommand(command); AZStd::string result; if (!GetCommandManager()->ExecuteCommandGroupInsideCommand(commandGroup, result)) diff --git a/Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/AnimGraphNodeGroupCommands.h b/Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/AnimGraphNodeGroupCommands.h index 85bd92f970..0fc497d2f1 100644 --- a/Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/AnimGraphNodeGroupCommands.h +++ b/Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/AnimGraphNodeGroupCommands.h @@ -13,22 +13,73 @@ #include #include #include +#include namespace CommandSystem { // adjust a node group - MCORE_DEFINECOMMAND_START(CommandAnimGraphAdjustNodeGroup, "Adjust anim graph node group", true) -public: - static AZStd::string GenerateNodeNameString(EMotionFX::AnimGraph* animGraph, const AZStd::vector& nodeIDs); - static AZStd::vector CollectNodeIdsFromGroup(EMotionFX::AnimGraphNodeGroup* nodeGroup); + class CommandAnimGraphAdjustNodeGroup + : public MCore::Command + , public EMotionFX::ParameterMixinAnimGraphId + { + public: + AZ_CLASS_ALLOCATOR_DECL - AZStd::string mOldName; - bool mOldIsVisible; - AZ::u32 mOldColor; - AZStd::vector mOldNodeIds; - bool mOldDirtyFlag; - MCORE_DEFINECOMMAND_END + static constexpr inline AZStd::string_view s_commandName = "AnimGraphAdjustNodeGroup"; + enum class NodeAction + { + Add, + Remove, + Replace + }; + + explicit CommandAnimGraphAdjustNodeGroup( + MCore::Command* orgCommand = nullptr, + AZ::u32 animGraphId = MCORE_INVALIDINDEX32, + AZStd::string name = AZStd::string{}, + AZStd::optional visible = AZStd::nullopt, + AZStd::optional newName = AZStd::nullopt, + AZStd::optional> nodeNames = AZStd::nullopt, + AZStd::optional nodeAction = AZStd::nullopt, + AZStd::optional color = AZStd::nullopt, + AZStd::optional updateUI = AZStd::nullopt + ); + bool Execute(const MCore::CommandLine& parameters, AZStd::string& outResult) override; + bool Undo(const MCore::CommandLine& parameters, AZStd::string& outResult) override; + void InitSyntax() override; + bool SetCommandParameters(const MCore::CommandLine& parameters) override; + bool GetIsUndoable() const override + { + return true; + } + const char* GetHistoryName() const override + { + return "Adjust anim graph node group"; + } + const char* GetDescription() const override; + MCore::Command* Create() override + { + return new CommandAnimGraphAdjustNodeGroup(this); + } + + static AZStd::vector GenerateNodeNameVector(EMotionFX::AnimGraph* animGraph, const AZStd::vector& nodeIDs); + static AZStd::vector CollectNodeIdsFromGroup(EMotionFX::AnimGraphNodeGroup* nodeGroup); + + private: + AZStd::string m_name; + AZStd::optional m_isVisible; + AZStd::optional m_newName; + AZStd::optional> m_nodeNames; + AZStd::optional m_nodeAction; + AZStd::optional m_color; + AZStd::optional m_updateUI; + + bool m_oldIsVisible; + AZ::u32 m_oldColor; + AZStd::vector m_oldNodeIds; + bool m_oldDirtyFlag; + }; // add node group MCORE_DEFINECOMMAND_START(CommandAnimGraphAddNodeGroup, "Add anim graph node group", true) diff --git a/Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/ParameterMixins.h b/Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/ParameterMixins.h index eb5de65f13..6461921160 100644 --- a/Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/ParameterMixins.h +++ b/Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/ParameterMixins.h @@ -80,6 +80,8 @@ namespace EMotionFX AZ_RTTI(ParameterMixinAnimGraphId, "{3F48199E-6566-471F-A7EA-ADF67CAC4DCD}") AZ_CLASS_ALLOCATOR_DECL + ParameterMixinAnimGraphId() = default; + ParameterMixinAnimGraphId(AZ::u32 id) : m_animGraphId(id) {} virtual ~ParameterMixinAnimGraphId() = default; static void Reflect(AZ::ReflectContext* context); diff --git a/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/Plugins/StandardPlugins/Source/AnimGraph/BlendGraphWidget.cpp b/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/Plugins/StandardPlugins/Source/AnimGraph/BlendGraphWidget.cpp index 9ec28d2e21..8a4a338b6e 100644 --- a/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/Plugins/StandardPlugins/Source/AnimGraph/BlendGraphWidget.cpp +++ b/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/Plugins/StandardPlugins/Source/AnimGraph/BlendGraphWidget.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -1208,7 +1209,7 @@ namespace EMStudio MCore::CommandGroup commandGroup("Adjust anim graph node group"); - AZStd::string nodeNames; + AZStd::vector nodeNames; for (const QModelIndex& selectedIndex : selectionList) { // Skip transitions and blend tree connections. @@ -1221,12 +1222,19 @@ namespace EMStudio EMotionFX::AnimGraphNodeGroup* nodeGroup = animGraph->FindNodeGroupForNode(selectedNode); if (nodeGroup) { - const AZStd::string command = AZStd::string::format("AnimGraphAdjustNodeGroup -animGraphID %i -name \"%s\" -nodeNames \"%s\" -nodeAction \"remove\"", animGraph->GetID(), nodeGroup->GetName(), selectedNode->GetName()); - commandGroup.AddCommandString(command); + auto* command = aznew CommandSystem::CommandAnimGraphAdjustNodeGroup( + GetCommandManager()->FindCommand(CommandSystem::CommandAnimGraphAdjustNodeGroup::s_commandName), + /*animGraphId = */ animGraph->GetID(), + /*name = */ nodeGroup->GetNameString(), + /*visible = */ AZStd::nullopt, + /*newName = */ AZStd::nullopt, + /*nodeNames = */ {{selectedNode->GetNameString()}}, + /*nodeAction = */ CommandSystem::CommandAnimGraphAdjustNodeGroup::NodeAction::Remove + ); + commandGroup.AddCommand(command); } - nodeNames += selectedNode->GetName(); - nodeNames += ";"; + nodeNames.emplace_back(selectedNode->GetName()); } if (!nodeNames.empty()) { @@ -1235,8 +1243,16 @@ namespace EMStudio if (newNodeGroup) { - const AZStd::string command = AZStd::string::format("AnimGraphAdjustNodeGroup -animGraphID %i -name \"%s\" -nodeNames \"%s\" -nodeAction \"add\"", animGraph->GetID(), newNodeGroup->GetName(), nodeNames.c_str()); - commandGroup.AddCommandString(command); + auto* command = aznew CommandSystem::CommandAnimGraphAdjustNodeGroup( + GetCommandManager()->FindCommand(CommandSystem::CommandAnimGraphAdjustNodeGroup::s_commandName), + /*animGraphId = */ animGraph->GetID(), + /*name = */ newNodeGroup->GetNameString(), + /*visible = */ AZStd::nullopt, + /*newName = */ AZStd::nullopt, + /*nodeNames = */ nodeNames, + /*nodeAction = */ CommandSystem::CommandAnimGraphAdjustNodeGroup::NodeAction::Add + ); + commandGroup.AddCommand(command); } AZStd::string outResult; diff --git a/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/Plugins/StandardPlugins/Source/AnimGraph/NodeGroupWindow.cpp b/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/Plugins/StandardPlugins/Source/AnimGraph/NodeGroupWindow.cpp index ecb9a227af..8ab7aa8d72 100644 --- a/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/Plugins/StandardPlugins/Source/AnimGraph/NodeGroupWindow.cpp +++ b/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/Plugins/StandardPlugins/Source/AnimGraph/NodeGroupWindow.cpp @@ -74,11 +74,6 @@ namespace EMStudio mLineEdit->setText(nodeGroup.c_str()); mLineEdit->selectAll(); - // create add the error message - /*mErrorMsg = new QLabel("Error: Duplicate name found"); - mErrorMsg->setAlignment(Qt::AlignVCenter | Qt::AlignLeft); - mErrorMsg->setVisible(false);*/ - // create the button layout QHBoxLayout* buttonLayout = new QHBoxLayout(); mOKButton = new QPushButton("OK"); @@ -139,10 +134,16 @@ namespace EMStudio void NodeGroupRenameWindow::Accepted() { // Execute the command - AZStd::string commandString, outResult; + AZStd::string outResult; const AZStd::string convertedNewName = FromQtString(mLineEdit->text()); - commandString = AZStd::string::format("AnimGraphAdjustNodeGroup -animGraphID %i -name \"%s\" -newName \"%s\"", mAnimGraph->GetID(), mNodeGroup.c_str(), convertedNewName.c_str()); - if (GetCommandManager()->ExecuteCommand(commandString.c_str(), outResult) == false) + auto* command = aznew CommandSystem::CommandAnimGraphAdjustNodeGroup( + GetCommandManager()->FindCommand(CommandSystem::CommandAnimGraphAdjustNodeGroup::s_commandName), + mAnimGraph->GetID(), + /*name = */ mNodeGroup, + /*visible = */ AZStd::nullopt, + /*newName = */ convertedNewName + ); + if (!GetCommandManager()->ExecuteCommand(command, outResult)) { MCore::LogError(outResult.c_str()); } @@ -167,7 +168,7 @@ namespace EMStudio mAdjustCallback = new CommandAnimGraphAdjustNodeGroupCallback(false); GetCommandManager()->RegisterCommandCallback("AnimGraphAddNodeGroup", mCreateCallback); GetCommandManager()->RegisterCommandCallback("AnimGraphRemoveNodeGroup", mRemoveCallback); - GetCommandManager()->RegisterCommandCallback("AnimGraphAdjustNodeGroup", mAdjustCallback); + GetCommandManager()->RegisterCommandCallback(CommandSystem::CommandAnimGraphAdjustNodeGroup::s_commandName.data(), mAdjustCallback); // add the add button mAddAction = new QAction(MysticQt::GetMysticQt()->FindIcon("Images/Icons/Plus.svg"), tr("Add new node group"), this); @@ -486,13 +487,16 @@ namespace EMStudio bool isVisible = state == Qt::Checked; - // construct the command - AZStd::string commandString; - commandString = AZStd::string::format("AnimGraphAdjustNodeGroup -animGraphID %i -name \"%s\" -isVisible %s", animGraph->GetID(), nodeGroup->GetName(), AZStd::to_string(isVisible).c_str()); + auto* command = aznew CommandSystem::CommandAnimGraphAdjustNodeGroup( + GetCommandManager()->FindCommand(CommandSystem::CommandAnimGraphAdjustNodeGroup::s_commandName), + /*animGraphId = */ animGraph->GetID(), + /*name = */ nodeGroup->GetNameString(), + /*visible = */ isVisible + ); // execute the command AZStd::string resultString; - if (GetCommandManager()->ExecuteCommand(commandString.c_str(), resultString) == false) + if (GetCommandManager()->ExecuteCommand(command, resultString) == false) { if (resultString.size() > 0) { @@ -519,16 +523,21 @@ namespace EMStudio // get a pointer to the node group EMotionFX::AnimGraphNodeGroup* nodeGroup = animGraph->GetNodeGroup(groupIndex); - // get the color - AZ::Vector4 finalColor = color.GetAsVector4(); - // construct the command - AZStd::string commandString; - commandString = AZStd::string::format("AnimGraphAdjustNodeGroup -animGraphID %i -name \"%s\" -color \"%s\"", animGraph->GetID(), nodeGroup->GetName(), AZStd::to_string(finalColor).c_str()); + auto* command = aznew CommandSystem::CommandAnimGraphAdjustNodeGroup( + GetCommandManager()->FindCommand(CommandSystem::CommandAnimGraphAdjustNodeGroup::s_commandName), + /*animGraphId = */ animGraph->GetID(), + /*name = */ nodeGroup->GetName(), + /*visible = */ AZStd::nullopt, + /*newName = */ AZStd::nullopt, + /*nodeNames = */ AZStd::nullopt, + /*nodeAction = */ AZStd::nullopt, + /*color = */ color.ToU32() + ); // execute the command AZStd::string resultString; - if (GetCommandManager()->ExecuteCommand(commandString.c_str(), resultString) == false) + if (GetCommandManager()->ExecuteCommand(command, resultString) == false) { if (resultString.size() > 0) { diff --git a/Gems/EMotionFX/Code/MCore/Source/Command.cpp b/Gems/EMotionFX/Code/MCore/Source/Command.cpp index b358a28546..39e9fde965 100644 --- a/Gems/EMotionFX/Code/MCore/Source/Command.cpp +++ b/Gems/EMotionFX/Code/MCore/Source/Command.cpp @@ -28,10 +28,10 @@ namespace MCore // constructor - Command::Command(const char* commandName, Command* originalCommand) + Command::Command(AZStd::string commandName, Command* originalCommand) + : mOrgCommand(originalCommand) + , mCommandName(AZStd::move(commandName)) { - mCommandName = commandName; - mOrgCommand = originalCommand; } diff --git a/Gems/EMotionFX/Code/MCore/Source/Command.h b/Gems/EMotionFX/Code/MCore/Source/Command.h index 6ad10ff559..7309c5dc91 100644 --- a/Gems/EMotionFX/Code/MCore/Source/Command.h +++ b/Gems/EMotionFX/Code/MCore/Source/Command.h @@ -185,7 +185,7 @@ namespace MCore * @param commandName The unique identifier for the command. * @param originalCommand The original command, or nullptr when this is the original command. */ - Command(const char* commandName, Command* originalCommand); + Command(AZStd::string commandName, Command* originalCommand); /** * Destructor. From 9e6832c6a982ca751db5cef2f33225b59e123a91 Mon Sep 17 00:00:00 2001 From: Chris Burel Date: Tue, 20 Jul 2021 13:03:56 -0700 Subject: [PATCH 06/51] Remove `BaseObject` as a base class from `NodeGroup` Nothing uses the use count that the `BaseObject` base class provides, so there's no reason to keep it. Signed-off-by: Chris Burel --- .../Source/NodeGroupCommands.cpp | 31 +++------- .../CommandSystem/Source/NodeGroupCommands.h | 2 +- .../EMotionFX/Code/EMotionFX/Source/Actor.cpp | 6 +- .../Source/Importer/ChunkProcessors.cpp | 2 +- .../Code/EMotionFX/Source/NodeGroup.cpp | 57 ++---------------- .../Code/EMotionFX/Source/NodeGroup.h | 58 ++----------------- 6 files changed, 21 insertions(+), 135 deletions(-) diff --git a/Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/NodeGroupCommands.cpp b/Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/NodeGroupCommands.cpp index fe4a3e1ca9..229ad34f19 100644 --- a/Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/NodeGroupCommands.cpp +++ b/Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/NodeGroupCommands.cpp @@ -24,7 +24,6 @@ namespace CommandSystem // constructor CommandAdjustNodeGroup::CommandAdjustNodeGroup(MCore::Command* orgCommand) : MCore::Command("AdjustNodeGroup", orgCommand) - , mOldNodeGroup(nullptr) { } @@ -32,10 +31,7 @@ namespace CommandSystem // destructor CommandAdjustNodeGroup::~CommandAdjustNodeGroup() { - if (mOldNodeGroup) - { - mOldNodeGroup->Destroy(); - } + delete mOldNodeGroup; } @@ -65,10 +61,7 @@ namespace CommandSystem } // copy the old node group for undo - if (mOldNodeGroup) - { - mOldNodeGroup->Destroy(); - } + delete mOldNodeGroup; mOldNodeGroup = aznew EMotionFX::NodeGroup(*nodeGroup); @@ -235,11 +228,8 @@ namespace CommandSystem } } - // delete the old node group - if (mOldNodeGroup) - { - mOldNodeGroup->Destroy(); - } + delete mOldNodeGroup; + mOldNodeGroup = nullptr; // set the dirty flag back to the old value @@ -304,7 +294,7 @@ namespace CommandSystem } // add new node group to the actor - EMotionFX::NodeGroup* nodeGroup = EMotionFX::NodeGroup::Create(name.c_str()); + EMotionFX::NodeGroup* nodeGroup = aznew EMotionFX::NodeGroup(name); actor->AddNodeGroup(nodeGroup); // save the current dirty flag and tell the actor that something got changed @@ -374,10 +364,7 @@ namespace CommandSystem // destructor CommandRemoveNodeGroup::~CommandRemoveNodeGroup() { - if (mOldNodeGroup) - { - mOldNodeGroup->Destroy(); - } + delete mOldNodeGroup; } @@ -407,11 +394,7 @@ namespace CommandSystem } // copy the old node group for undo - if (mOldNodeGroup) - { - mOldNodeGroup->Destroy(); - } - + delete mOldNodeGroup; mOldNodeGroup = aznew EMotionFX::NodeGroup(*nodeGroup); // remove the node group diff --git a/Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/NodeGroupCommands.h b/Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/NodeGroupCommands.h index e87ce2d62d..1931b6842e 100644 --- a/Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/NodeGroupCommands.h +++ b/Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/NodeGroupCommands.h @@ -22,7 +22,7 @@ namespace CommandSystem // adjust a node group MCORE_DEFINECOMMAND_START(CommandAdjustNodeGroup, "Adjust node group", true) bool mOldDirtyFlag; - EMotionFX::NodeGroup* mOldNodeGroup; + EMotionFX::NodeGroup* mOldNodeGroup = nullptr; MCORE_DEFINECOMMAND_END diff --git a/Gems/EMotionFX/Code/EMotionFX/Source/Actor.cpp b/Gems/EMotionFX/Code/EMotionFX/Source/Actor.cpp index 973d8eb460..c6b210da58 100644 --- a/Gems/EMotionFX/Code/EMotionFX/Source/Actor.cpp +++ b/Gems/EMotionFX/Code/EMotionFX/Source/Actor.cpp @@ -1015,7 +1015,7 @@ namespace EMotionFX const uint32 numGroups = mNodeGroups.GetLength(); for (uint32 i = 0; i < numGroups; ++i) { - mNodeGroups[i]->Destroy(); + delete mNodeGroups[i]; } mNodeGroups.Clear(); } @@ -2085,7 +2085,7 @@ namespace EMotionFX { if (delFromMem) { - mNodeGroups[index]->Destroy(); + delete mNodeGroups[index]; } mNodeGroups.Remove(index); @@ -2097,7 +2097,7 @@ namespace EMotionFX mNodeGroups.RemoveByValue(group); if (delFromMem) { - group->Destroy(); + delete group; } } diff --git a/Gems/EMotionFX/Code/EMotionFX/Source/Importer/ChunkProcessors.cpp b/Gems/EMotionFX/Code/EMotionFX/Source/Importer/ChunkProcessors.cpp index 7e7a9ae1c2..3cec3b599c 100644 --- a/Gems/EMotionFX/Code/EMotionFX/Source/Importer/ChunkProcessors.cpp +++ b/Gems/EMotionFX/Code/EMotionFX/Source/Importer/ChunkProcessors.cpp @@ -1469,7 +1469,7 @@ namespace EMotionFX } // create the new group inside the actor - NodeGroup* newGroup = NodeGroup::Create(groupName, fileGroup.mNumNodes, fileGroup.mDisabledOnDefault ? false : true); + NodeGroup* newGroup = aznew NodeGroup(groupName, fileGroup.mNumNodes, fileGroup.mDisabledOnDefault ? false : true); // read the node numbers uint16 nodeIndex; diff --git a/Gems/EMotionFX/Code/EMotionFX/Source/NodeGroup.cpp b/Gems/EMotionFX/Code/EMotionFX/Source/NodeGroup.cpp index d73926a166..e22ad5f064 100644 --- a/Gems/EMotionFX/Code/EMotionFX/Source/NodeGroup.cpp +++ b/Gems/EMotionFX/Code/EMotionFX/Source/NodeGroup.cpp @@ -17,63 +17,16 @@ namespace EMotionFX AZ_CLASS_ALLOCATOR_IMPL(NodeGroup, NodeAllocator, 0) - // default constructor - NodeGroup::NodeGroup() - : BaseObject() + NodeGroup::NodeGroup(const AZStd::string& groupName, uint16 numNodes, bool enabledOnDefault) + : mName(groupName) + , mNodes(numNodes) + , mEnabledOnDefault(enabledOnDefault) { - SetIsEnabledOnDefault(true); - } - - - // extended constructor - NodeGroup::NodeGroup(const char* groupName, bool enabledOnDefault) - : BaseObject() - { - SetName(groupName); - SetIsEnabledOnDefault(enabledOnDefault); - } - - - // another extended constructor - NodeGroup::NodeGroup(const char* groupName, uint16 numNodes, bool enabledOnDefault) - : BaseObject() - { - SetName(groupName); - SetNumNodes(numNodes); - SetIsEnabledOnDefault(enabledOnDefault); - } - - - // destructor - NodeGroup::~NodeGroup() - { - mNodes.Clear(); - } - - - // create - NodeGroup* NodeGroup::Create() - { - return aznew NodeGroup(); - } - - - // create - NodeGroup* NodeGroup::Create(const char* groupName, bool enabledOnDefault) - { - return aznew NodeGroup(groupName, enabledOnDefault); - } - - - // create - NodeGroup* NodeGroup::Create(const char* groupName, uint16 numNodes, bool enabledOnDefault) - { - return aznew NodeGroup(groupName, numNodes, enabledOnDefault); } // set the name of the group - void NodeGroup::SetName(const char* groupName) + void NodeGroup::SetName(const AZStd::string& groupName) { mName = groupName; } diff --git a/Gems/EMotionFX/Code/EMotionFX/Source/NodeGroup.h b/Gems/EMotionFX/Code/EMotionFX/Source/NodeGroup.h index c6dc86273b..dfe876c86a 100644 --- a/Gems/EMotionFX/Code/EMotionFX/Source/NodeGroup.h +++ b/Gems/EMotionFX/Code/EMotionFX/Source/NodeGroup.h @@ -30,38 +30,19 @@ namespace EMotionFX * might contain incorrect or even uninitialized data. */ class EMFX_API NodeGroup - : public BaseObject { public: AZ_CLASS_ALLOCATOR_DECL - /** - * The default creation method. - * This does not assign a name and there will be nodes inside this group on default. - * Also the default enabled state is set to true. - */ - static NodeGroup* Create(); - /** - * Extended creation. - * @param groupName The name of the group. Please keep in mind that it is not allowed to have two groups with the same name inside an Actor. - * @param enabledOnDefault Set to true (default) when the nodes inside this group should be enabled on default. - */ - static NodeGroup* Create(const char* groupName, bool enabledOnDefault = true); - - /** - * Another extended constructor. - * @param groupName The name of the group. Please keep in mind that it is not allowed to have two groups with the same name inside an Actor. - * @param numNodes The number of nodes to create inside the group. This will have all uninitialized values for the node indices in the group, so be sure that you - * set them all to some valid node index using the NodeGroup::SetNode(...) method. This method automatically calls the SetNumNodes(...) method. - * @param enabledOnDefault Set to true (default) when the nodes inside this group should be enabled on default. - */ - static NodeGroup* Create(const char* groupName, uint16 numNodes, bool enabledOnDefault = true); + NodeGroup(const AZStd::string& groupName = {}, uint16 numNodes = 0, bool enabledOnDefault = true); + NodeGroup(const NodeGroup& aOther); + NodeGroup& operator=(const NodeGroup& aOther); /** * Set the name of the group. Please keep in mind that group names must be unique inside the Actor objects. So you should not have two or more groups with the same name. * @param groupName The name of the group. */ - void SetName(const char* groupName); + void SetName(const AZStd::string& groupName); /** * Get the name of the group as null terminated character buffer. @@ -172,37 +153,6 @@ namespace EMotionFX */ void SetIsEnabledOnDefault(bool enabledOnDefault); - /** - * The default constructor. - * This does not assign a name and there will be nodes inside this group on default. - * Also the default enabled state is set to true. - */ - NodeGroup(); - - /** - * Extended constructor. - * @param groupName The name of the group. Please keep in mind that it is not allowed to have two groups with the same name inside an Actor. - * @param enabledOnDefault Set to true (default) when the nodes inside this group should be enabled on default. - */ - NodeGroup(const char* groupName, bool enabledOnDefault = true); - - /** - * Another extended constructor. - * @param groupName The name of the group. Please keep in mind that it is not allowed to have two groups with the same name inside an Actor. - * @param numNodes The number of nodes to create inside the group. This will have all uninitialized values for the node indices in the group, so be sure that you - * set them all to some valid node index using the NodeGroup::SetNode(...) method. This method automatically calls the SetNumNodes(...) method. - * @param enabledOnDefault Set to true (default) when the nodes inside this group should be enabled on default. - */ - NodeGroup(const char* groupName, uint16 numNodes, bool enabledOnDefault = true); - - /** - * The destructor. - */ - ~NodeGroup(); - - NodeGroup(const NodeGroup& aOther); - NodeGroup& operator=(const NodeGroup& aOther); - private: AZStd::string mName; /**< The name of the group. */ MCore::SmallArray mNodes; /**< The node index numbers that are inside this group. */ From 02c16a318e1bbfeb0b4afbf6dca52abaf16aae3c Mon Sep 17 00:00:00 2001 From: Chris Burel Date: Tue, 20 Jul 2021 13:28:32 -0700 Subject: [PATCH 07/51] Prefer `unique_ptr` to a raw pointer for `CommandAdjustNodeGroup`'s members Signed-off-by: Chris Burel --- .../Source/NodeGroupCommands.cpp | 16 ++------- .../CommandSystem/Source/NodeGroupCommands.h | 36 +++++++++++++++---- 2 files changed, 32 insertions(+), 20 deletions(-) diff --git a/Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/NodeGroupCommands.cpp b/Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/NodeGroupCommands.cpp index 229ad34f19..acb1388e38 100644 --- a/Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/NodeGroupCommands.cpp +++ b/Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/NodeGroupCommands.cpp @@ -9,7 +9,7 @@ // include the required headers #include "NodeGroupCommands.h" #include "CommandManager.h" -#include +#include #include #include #include @@ -28,13 +28,6 @@ namespace CommandSystem } - // destructor - CommandAdjustNodeGroup::~CommandAdjustNodeGroup() - { - delete mOldNodeGroup; - } - - // execute bool CommandAdjustNodeGroup::Execute(const MCore::CommandLine& parameters, AZStd::string& outResult) { @@ -60,10 +53,7 @@ namespace CommandSystem return false; } - // copy the old node group for undo - delete mOldNodeGroup; - - mOldNodeGroup = aznew EMotionFX::NodeGroup(*nodeGroup); + mOldNodeGroup = AZStd::make_unique(*nodeGroup); // check if newName is set and apply new name if (parameters.CheckIfHasParameter("newName")) @@ -228,8 +218,6 @@ namespace CommandSystem } } - delete mOldNodeGroup; - mOldNodeGroup = nullptr; // set the dirty flag back to the old value diff --git a/Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/NodeGroupCommands.h b/Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/NodeGroupCommands.h index 1931b6842e..d691df7c82 100644 --- a/Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/NodeGroupCommands.h +++ b/Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/NodeGroupCommands.h @@ -9,10 +9,13 @@ #pragma once // include the required headers +#include #include "CommandSystemConfig.h" #include #include #include +#include +#include EMFX_FORWARD_DECLARE(Actor); EMFX_FORWARD_DECLARE(NodeGroup); @@ -20,20 +23,41 @@ EMFX_FORWARD_DECLARE(NodeGroup); namespace CommandSystem { // adjust a node group - MCORE_DEFINECOMMAND_START(CommandAdjustNodeGroup, "Adjust node group", true) - bool mOldDirtyFlag; - EMotionFX::NodeGroup* mOldNodeGroup = nullptr; - MCORE_DEFINECOMMAND_END + class CommandAdjustNodeGroup + : public MCore::Command + { + public: + CommandAdjustNodeGroup(MCore::Command* orgCommand = nullptr); + bool Execute(const MCore::CommandLine& parameters, AZStd::string& outResult) override; + bool Undo(const MCore::CommandLine& parameters, AZStd::string& outResult) override; + void InitSyntax() override; + bool GetIsUndoable() const override + { + return true; + } + const char* GetHistoryName() const override + { + return "Adjust node group"; + } + const char* GetDescription() const override; + MCore::Command* Create() override + { + return new CommandAdjustNodeGroup(this); + } + protected: + bool mOldDirtyFlag = false; + AZStd::unique_ptr mOldNodeGroup = nullptr; + }; // add node group - MCORE_DEFINECOMMAND_START(CommandAddNodeGroup, "Add node group", true) + MCORE_DEFINECOMMAND_START(CommandAddNodeGroup, "Add node group", true) bool mOldDirtyFlag; MCORE_DEFINECOMMAND_END // remove a node group - MCORE_DEFINECOMMAND_START(CommandRemoveNodeGroup, "Remove node group", true) + MCORE_DEFINECOMMAND_START(CommandRemoveNodeGroup, "Remove node group", true) EMotionFX::NodeGroup * mOldNodeGroup; bool mOldDirtyFlag; MCORE_DEFINECOMMAND_END From 3a95243df513c35ddb8814c10aff1cffe62dff5b Mon Sep 17 00:00:00 2001 From: Chris Burel Date: Tue, 20 Jul 2021 13:37:58 -0700 Subject: [PATCH 08/51] Allow special characters in Actor node group names Relying on the command system's string processing syntax prevents certain names from being used. This converts the AdjustNodeGroup command to be directly invokable, so that arguments can be passed directly, instead of going through the CommandLine string parsing. Signed-off-by: Chris Burel --- .../Source/NodeGroupCommands.cpp | 222 ++++++++---------- .../CommandSystem/Source/NodeGroupCommands.h | 35 ++- .../NodeGroups/NodeGroupManagementWidget.cpp | 115 ++------- .../Source/NodeGroups/NodeGroupWidget.cpp | 76 +++--- .../Source/NodeGroups/NodeGroupWidget.h | 3 +- .../Source/NodeGroups/NodeGroupsPlugin.cpp | 3 +- 6 files changed, 186 insertions(+), 268 deletions(-) diff --git a/Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/NodeGroupCommands.cpp b/Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/NodeGroupCommands.cpp index acb1388e38..ea83d90d4b 100644 --- a/Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/NodeGroupCommands.cpp +++ b/Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/NodeGroupCommands.cpp @@ -20,208 +20,147 @@ namespace CommandSystem //-------------------------------------------------------------------------------- // CommandAdjustNodeGroup //-------------------------------------------------------------------------------- + AZ_CLASS_ALLOCATOR_IMPL(CommandAdjustNodeGroup, EMotionFX::CommandAllocator, 0) - // constructor - CommandAdjustNodeGroup::CommandAdjustNodeGroup(MCore::Command* orgCommand) - : MCore::Command("AdjustNodeGroup", orgCommand) + CommandAdjustNodeGroup::CommandAdjustNodeGroup( + MCore::Command* orgCommand, + uint32 actorId, + const AZStd::string& name, + AZStd::optional newName, + AZStd::optional enabledOnDefault, + AZStd::optional> nodeNames, + AZStd::optional nodeAction + ) + : MCore::Command(s_commandName.data(), orgCommand) + , EMotionFX::ParameterMixinActorId(actorId) + , m_name(name) + , m_newName(AZStd::move(newName)) + , m_enabledOnDefault(enabledOnDefault) + , m_nodeNames(AZStd::move(nodeNames)) + , m_nodeAction(nodeAction) { } // execute - bool CommandAdjustNodeGroup::Execute(const MCore::CommandLine& parameters, AZStd::string& outResult) + bool CommandAdjustNodeGroup::Execute(const MCore::CommandLine&, AZStd::string& outResult) { - AZStd::string valueString; - - // get the motion id and the corresponding motion pointer - const int32 actorID = parameters.GetValueAsInt("actorID", this); - parameters.GetValue("name", this, &valueString); - // get the actor - EMotionFX::Actor* actor = EMotionFX::GetActorManager().FindActorByID(actorID); + EMotionFX::Actor* actor = EMotionFX::GetActorManager().FindActorByID(m_actorId); if (actor == nullptr) { - outResult = AZStd::string::format("Cannot adjust node group. Actor with id='%i' does not exist.", actorID); + outResult = AZStd::string::format("Cannot adjust node group. Actor with id='%i' does not exist.", m_actorId); return false; } // get the node group - EMotionFX::NodeGroup* nodeGroup = actor->FindNodeGroupByNameNoCase(valueString.c_str()); + EMotionFX::NodeGroup* nodeGroup = actor->FindNodeGroupByNameNoCase(m_name.c_str()); if (nodeGroup == nullptr) { - outResult = AZStd::string::format("Cannot adjust node group. Node group with name='%s' does not exist.", valueString.c_str()); + outResult = AZStd::string::format("Cannot adjust node group. Node group with name='%s' does not exist.", m_name.c_str()); return false; } - mOldNodeGroup = AZStd::make_unique(*nodeGroup); + m_oldNodeGroup = AZStd::make_unique(*nodeGroup); // check if newName is set and apply new name - if (parameters.CheckIfHasParameter("newName")) + if (m_newName.has_value()) { - parameters.GetValue("newName", this, &valueString); - nodeGroup->SetName(valueString.c_str()); + nodeGroup->SetName(*m_newName); } // check if parameter disabledOnDefault is set and adjust it - if (parameters.CheckIfHasParameter("enabledOnDefault")) + if (m_enabledOnDefault.has_value()) { - const bool enabledOnDefault = parameters.GetValueAsBool("enabledOnDefault", this); - nodeGroup->SetIsEnabledOnDefault(enabledOnDefault); + nodeGroup->SetIsEnabledOnDefault(*m_enabledOnDefault); } // check if parametes nodeNames is set - if (parameters.CheckIfHasParameter("nodeNames")) + if (m_nodeNames.has_value()) { - // get the node action - AZStd::string nodeAction; - parameters.GetValue("nodeAction", this, &valueString); - - // get the node names and split the string - AZStd::string nodeNameString; - parameters.GetValue("nodeNames", this, &nodeNameString); - - // get the individual node names - AZStd::vector nodeNames; - AzFramework::StringFunc::Tokenize(nodeNameString.c_str(), nodeNames, MCore::CharacterConstants::semiColon, true /* keep empty strings */, true /* keep space strings */); - - // get the number of nodes - const size_t numNodes = nodeNames.size(); - - // remove the selected nodes from the node group - if (AzFramework::StringFunc::Equal(valueString.c_str(), "remove", false /* no case */)) + if (*m_nodeAction == NodeAction::Replace) { - for (size_t i = 0; i < numNodes; ++i) - { - // get the node - EMotionFX::Node* node = actor->GetSkeleton()->FindNodeByName(nodeNames[i].c_str()); - if (node == nullptr) - { - continue; - } - - // remove the node - nodeGroup->RemoveNodeByNodeIndex((uint16)node->GetNodeIndex()); - } - } - else if (AzFramework::StringFunc::Equal(valueString.c_str(), "add", false /* no case */)) // add the selected nodes to the node group - { - for (size_t i = 0; i < numNodes; ++i) - { - // get the node - EMotionFX::Node* node = actor->GetSkeleton()->FindNodeByName(nodeNames[i].c_str()); - if (node == nullptr) - { - continue; - } - - // add the node - uint16 nodeIndex = (uint16)node->GetNodeIndex(); - nodeGroup->RemoveNodeByNodeIndex(nodeIndex); - nodeGroup->AddNode(nodeIndex); - } - } - else // selected nodes form the new node group - { - // clear previous nodes nodeGroup->GetNodeArray().Clear(); - - // add all nodes to the group - for (size_t i = 0; i < numNodes; ++i) + } + for (const AZStd::string& nodeName : *m_nodeNames) + { + EMotionFX::Node* node = actor->GetSkeleton()->FindNodeByName(nodeName); + if (!node) { - // get the node - EMotionFX::Node* node = actor->GetSkeleton()->FindNodeByName(nodeNames[i].c_str()); + continue; + } - // check if node exists - if (node == nullptr) - { - continue; - } - - // add the node - nodeGroup->AddNode((uint16)node->GetNodeIndex()); + uint16 nodeIndex = (uint16)node->GetNodeIndex(); + nodeGroup->RemoveNodeByNodeIndex(nodeIndex); + if (*m_nodeAction == NodeAction::Add || *m_nodeAction == NodeAction::Replace) + { + nodeGroup->AddNode(nodeIndex); } } } // save the current dirty flag and tell the actor that something got changed - mOldDirtyFlag = actor->GetDirtyFlag(); + m_oldDirtyFlag = actor->GetDirtyFlag(); actor->SetDirtyFlag(true); return true; } // undo the command - bool CommandAdjustNodeGroup::Undo(const MCore::CommandLine& parameters, AZStd::string& outResult) + bool CommandAdjustNodeGroup::Undo(const MCore::CommandLine&, AZStd::string& outResult) { // return if no information about the previous node group was stored - if (!mOldNodeGroup) + if (!m_oldNodeGroup) { return false; } - // get the motion id and the corresponding motion pointer - int32 actorID = parameters.GetValueAsInt("actorID", this); - - // get the name - AZStd::string name; - if (parameters.CheckIfHasParameter("newName")) - { - parameters.GetValue("newName", this, &name); - } - else - { - parameters.GetValue("name", this, &name); - } - - // get the actor - EMotionFX::Actor* actor = EMotionFX::GetActorManager().FindActorByID(actorID); + EMotionFX::Actor* actor = EMotionFX::GetActorManager().FindActorByID(m_actorId); // return error if actor was not found if (actor == nullptr) { - outResult = AZStd::string::format("Cannot adjust node group. Actor with id='%i' does not exist.", actorID); + outResult = AZStd::string::format("Cannot adjust node group. Actor with id='%i' does not exist.", m_actorId); return false; } - // get the node group - EMotionFX::NodeGroup* nodeGroup = actor->FindNodeGroupByNameNoCase(name.c_str()); + EMotionFX::NodeGroup* nodeGroup = actor->FindNodeGroupByNameNoCase(m_newName.has_value() ? m_newName->c_str() : m_name.c_str()); - // return error if node group name is not set - if (nodeGroup == nullptr) + if (!nodeGroup) { - outResult = AZStd::string::format("Cannot adjust node group. Node group with name='%s' does not exist.", name.c_str()); + outResult = AZStd::string::format("Cannot adjust node group. Node group with name='%s' does not exist.", m_newName.has_value() ? m_newName->c_str() : m_name.c_str()); return false; } // reset the old values - if (parameters.CheckIfHasParameter("enabledOnDefault")) + if (m_enabledOnDefault.has_value()) { - nodeGroup->SetIsEnabledOnDefault(mOldNodeGroup->GetIsEnabledOnDefault()); + nodeGroup->SetIsEnabledOnDefault(m_oldNodeGroup->GetIsEnabledOnDefault()); } - if (parameters.CheckIfHasParameter("newName")) + if (m_newName.has_value()) { - nodeGroup->SetName(mOldNodeGroup->GetName()); + nodeGroup->SetName(m_oldNodeGroup->GetName()); } - if (parameters.CheckIfHasParameter("nodeNames")) + if (m_nodeNames.has_value()) { // clear previous nodes nodeGroup->GetNodeArray().Clear(); - const uint32 numNodes = mOldNodeGroup->GetNumNodes(); - nodeGroup->SetNumNodes(static_cast(numNodes)); + const uint16 numNodes = m_oldNodeGroup->GetNumNodes(); + nodeGroup->SetNumNodes(numNodes); // add all nodes to the group - for (uint32 i = 0; i < numNodes; ++i) + for (uint16 i = 0; i < numNodes; ++i) { - nodeGroup->SetNode(static_cast(i), mOldNodeGroup->GetNode(static_cast(i))); + nodeGroup->SetNode(i, m_oldNodeGroup->GetNode(i)); } } - mOldNodeGroup = nullptr; + m_oldNodeGroup = nullptr; // set the dirty flag back to the old value - actor->SetDirtyFlag(mOldDirtyFlag); + actor->SetDirtyFlag(m_oldDirtyFlag); return true; } @@ -230,7 +169,7 @@ namespace CommandSystem void CommandAdjustNodeGroup::InitSyntax() { GetSyntax().ReserveParameters(6); - GetSyntax().AddRequiredParameter("actorID", "The id of the actor the node group belongs to.", MCore::CommandSyntax::PARAMTYPE_INT); + EMotionFX::ParameterMixinActorId::InitSyntax(GetSyntax(), /*isParameterRequired=*/ true); GetSyntax().AddRequiredParameter("name", "The name of the node group to adjust.", MCore::CommandSyntax::PARAMTYPE_STRING); GetSyntax().AddParameter("newName", "The new name of the node group.", MCore::CommandSyntax::PARAMTYPE_STRING, ""); GetSyntax().AddParameter("enabledOnDefault", "The enabled on default flag.", MCore::CommandSyntax::PARAMTYPE_BOOLEAN, "false"); @@ -239,6 +178,45 @@ namespace CommandSystem } + bool CommandAdjustNodeGroup::SetCommandParameters(const MCore::CommandLine& parameters) + { + EMotionFX::ParameterMixinActorId::SetCommandParameters(parameters); + + m_name = parameters.GetValue("name", this); + if (parameters.CheckIfHasParameter("newName")) + { + m_newName = parameters.GetValue("newName", this); + } + if (parameters.CheckIfHasParameter("enabledOnDefault")) + { + m_enabledOnDefault = parameters.GetValueAsBool("enabledOnDefault", this); + } + if (parameters.CheckIfHasParameter("nodeNames")) + { + m_nodeNames.emplace(); + AzFramework::StringFunc::Tokenize(parameters.GetValue("nodeNames", this), m_nodeNames.value(), ";", false, true); + } + if (parameters.CheckIfHasParameter("nodeAction")) + { + const AZStd::string& nodeActionStr = parameters.GetValue("nodeAction", this); + if (nodeActionStr == "add") + { + m_nodeAction = NodeAction::Add; + } + else if (nodeActionStr == "remove") + { + m_nodeAction = NodeAction::Remove; + } + else if (nodeActionStr == "replace") + { + m_nodeAction = NodeAction::Replace; + } + } + + return true; + } + + // get the description const char* CommandAdjustNodeGroup::GetDescription() const { diff --git a/Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/NodeGroupCommands.h b/Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/NodeGroupCommands.h index d691df7c82..d29918822d 100644 --- a/Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/NodeGroupCommands.h +++ b/Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/NodeGroupCommands.h @@ -25,12 +25,33 @@ namespace CommandSystem // adjust a node group class CommandAdjustNodeGroup : public MCore::Command + , public EMotionFX::ParameterMixinActorId { public: - CommandAdjustNodeGroup(MCore::Command* orgCommand = nullptr); + AZ_CLASS_ALLOCATOR_DECL + + enum class NodeAction + { + Add, + Remove, + Replace + }; + + static constexpr inline AZStd::string_view s_commandName = "AdjustNodeGroup"; + + CommandAdjustNodeGroup( + MCore::Command* orgCommand = nullptr, + uint32 actorId = MCORE_INVALIDINDEX32, + const AZStd::string& name = {}, + AZStd::optional newName = AZStd::nullopt, + AZStd::optional enabledOnDefault = AZStd::nullopt, + AZStd::optional> nodeNames = AZStd::nullopt, + AZStd::optional nodeAction = AZStd::nullopt + ); bool Execute(const MCore::CommandLine& parameters, AZStd::string& outResult) override; bool Undo(const MCore::CommandLine& parameters, AZStd::string& outResult) override; void InitSyntax() override; + bool SetCommandParameters(const MCore::CommandLine& parameters) override; bool GetIsUndoable() const override { return true; @@ -45,9 +66,15 @@ namespace CommandSystem return new CommandAdjustNodeGroup(this); } - protected: - bool mOldDirtyFlag = false; - AZStd::unique_ptr mOldNodeGroup = nullptr; + private: + AZStd::string m_name; + AZStd::optional m_newName; + AZStd::optional m_enabledOnDefault; + AZStd::optional> m_nodeNames; + AZStd::optional m_nodeAction; + + bool m_oldDirtyFlag = false; + AZStd::unique_ptr m_oldNodeGroup = nullptr; }; // add node group diff --git a/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/Plugins/StandardPlugins/Source/NodeGroups/NodeGroupManagementWidget.cpp b/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/Plugins/StandardPlugins/Source/NodeGroups/NodeGroupManagementWidget.cpp index 1624b9d9ea..84c281a18f 100644 --- a/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/Plugins/StandardPlugins/Source/NodeGroups/NodeGroupManagementWidget.cpp +++ b/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/Plugins/StandardPlugins/Source/NodeGroups/NodeGroupManagementWidget.cpp @@ -112,8 +112,13 @@ namespace EMStudio // execute the command AZStd::string outResult; - AZStd::string command = AZStd::string::format("AdjustNodeGroup -actorID %i -name \"%s\" -newName \"%s\"", mActor->GetID(), mNodeGroupName.c_str(), convertedNewName.c_str()); - if (GetCommandManager()->ExecuteCommand(command.c_str(), outResult) == false) + auto* command = aznew CommandSystem::CommandAdjustNodeGroup( + GetCommandManager()->FindCommand(CommandSystem::CommandAdjustNodeGroup::s_commandName), + /*actorId=*/ mActor->GetID(), + /*name=*/ mNodeGroupName, + /*newName=*/ convertedNewName + ); + if (GetCommandManager()->ExecuteCommand(command, outResult) == false) { AZ_Error("EMotionFX", false, outResult.c_str()); } @@ -362,98 +367,6 @@ namespace EMStudio mSelectedRow = MCORE_INVALIDINDEX32; } } - /*void NodeGroupManagementWidget::UpdateNodeGroupWidget(QTableWidgetItem* current, QTableWidgetItem* previous) - { - MCORE_UNUSED(previous); - - // return if no node group widget is set - if (mNodeGroupWidget == nullptr) - return; - - // set the node group widget to the actual selection - mNodeGroupWidget->SetActor( mActor ); - - if (current) - { - // set the current row - mSelectedRow = current->row(); - - // set the node group - NodeGroup* nodeGroup = mActor->FindNodeGroupByName( FromQtString(mNodeGroupsTable->item(current->row(), 1)->text()).c_str() ); - mNodeGroupWidget->SetNodeGroup( nodeGroup ); - } - else - { - mNodeGroupWidget->SetNodeGroup( nullptr ); - mSelectedRow = MCORE_INVALIDINDEX32; - } - }*/ - - - // called whenever a cell is changed - /*void NodeGroupManagementWidget::NodeGroupNamesChanged(const QString& text) - { - // get the sender widget - QWidget* senderWidget = (QWidget*)sender(); - - // check for duplicates - const int duplicateFound = SearchTableForString( mNodeGroupsTable, text ); - - // mark edit field in red, if entry already exists - if (duplicateFound >= 0) - GetManager()->SetWidgetAsInvalidInput( senderWidget ); - else - senderWidget->setStyleSheet(""); - }*/ - - - // starts editing - /*void NodeGroupManagementWidget::NodeGroupeNameDoubleClicked(QTableWidgetItem* item) - { - // add new line edit for the selected widget - QLineEdit* lineEdit = new QLineEdit( mNodeGroupsTable->item(item->row(), 0)->text() ); - mNodeGroupsTable->setCellWidget( item->row(), 0, lineEdit ); - - // jump into the edit field - lineEdit->selectAll(); - lineEdit->setFocus(); - mNodeGroupsTable->setCurrentCell( item->row(), 0 ); - - // connect slots for edit finishing and text change - connect( lineEdit, SIGNAL(editingFinished()), this, SLOT(NodeGroupNameEditingFinished()) ); - connect( lineEdit, SIGNAL(textChanged(QString)), this, SLOT(NodeGroupNamesChanged(QString)) ); - }*/ - - - // called when editing is finished - /*void NodeGroupManagementWidget::NodeGroupNameEditingFinished() - { - // get the current item - QTableWidgetItem* item = mNodeGroupsTable->currentItem(); - - // get the sender widget - QLineEdit* senderWidget = (QLineEdit*)sender(); - - // return if one of the widgets does not exist - if (item == nullptr || senderWidget == nullptr) - return; - - // call commands for name change if name does not exist yet - if (senderWidget->styleSheet() == "") - { - // call command for adding a new node group - String outResult; - String command; - command.Format( "AdjustNodeGroup -actorID %i -name \"%s\" -newName \"%s\"", mActor->GetID(), FromQtString(item->text()).c_str(), FromQtString(senderWidget->text()).c_str() ); - if (EMStudio::GetCommandManager()->ExecuteCommand( command.c_str(), outResult ) == false) - LogError( outResult.c_str() ); - } - else - { - // delete the line edit - mNodeGroupsTable->setCellWidget(item->row(), item->column(), nullptr); - } - }*/ // function to add a new node group with the specified name @@ -562,16 +475,20 @@ namespace EMStudio if (rowChechbox == senderCheckbox) { nodeGroupName = mNodeGroupsTable->item(i, 1)->text().toUtf8().data(); + break; } } // execute the command AZStd::string outResult; - const AZStd::string command = AZStd::string::format("AdjustNodeGroup -actorID %i -name \"%s\" -enabledOnDefault \"%s\"", - mActor->GetID(), - nodeGroupName.c_str(), - AZStd::to_string(checked).c_str()); - if (EMStudio::GetCommandManager()->ExecuteCommand(command.c_str(), outResult) == false) + auto* command = aznew CommandSystem::CommandAdjustNodeGroup( + GetCommandManager()->FindCommand(CommandSystem::CommandAdjustNodeGroup::s_commandName), + /*actorId=*/ mActor->GetID(), + /*name=*/ nodeGroupName, + /*newName=*/ AZStd::nullopt, + /*enabledOnDefault=*/ checked + ); + if (EMStudio::GetCommandManager()->ExecuteCommand(command, outResult) == false) { AZ_Error("EMotionFX", false, outResult.c_str()); } diff --git a/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/Plugins/StandardPlugins/Source/NodeGroups/NodeGroupWidget.cpp b/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/Plugins/StandardPlugins/Source/NodeGroups/NodeGroupWidget.cpp index 31d704a1d5..39ee1c4f5b 100644 --- a/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/Plugins/StandardPlugins/Source/NodeGroups/NodeGroupWidget.cpp +++ b/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/Plugins/StandardPlugins/Source/NodeGroups/NodeGroupWidget.cpp @@ -35,7 +35,6 @@ namespace EMStudio mNodeTable = nullptr; mSelectNodesButton = nullptr; mNodeGroup = nullptr; - mNodeAction = ""; // init the widget Init(); @@ -254,11 +253,11 @@ namespace EMStudio QWidget* senderWidget = (QWidget*)sender(); if (senderWidget == mAddNodesButton) { - mNodeAction = "add"; + mNodeAction = CommandSystem::CommandAdjustNodeGroup::NodeAction::Add; } else { - mNodeAction = "select"; + mNodeAction = CommandSystem::CommandAdjustNodeGroup::NodeAction::Replace; } // get the selected actorinstance @@ -293,46 +292,37 @@ namespace EMStudio // remove nodes void NodeGroupWidget::RemoveNodesButtonPressed() { - // generate node list string - AZStd::string nodeList; - uint32 lowestSelectedRow = MCORE_INVALIDINDEX32; - const uint32 numTableRows = mNodeTable->rowCount(); - for (uint32 i = 0; i < numTableRows; ++i) - { - // get the current table item - QTableWidgetItem* item = mNodeTable->item(i, 0); - if (item == nullptr) - { - continue; - } - - // add the item to remove list, if it's selected - if (item->isSelected()) - { - nodeList += AZStd::string::format("%s;", item->text().toUtf8().data()); - if ((uint32)item->row() < lowestSelectedRow) - { - lowestSelectedRow = (uint32)item->row(); - } - } - } - - // stop here if nothing selected - if (nodeList.empty()) + if (mNodeTable->selectedItems().empty()) { return; } - // call command for adjusting disable on default flag + // generate node list string + AZStd::vector nodeList; + int lowestSelectedRow = AZStd::numeric_limits::max(); + for (const QTableWidgetItem* item : mNodeTable->selectedItems()) + { + nodeList.emplace_back(FromQtString(item->text())); + lowestSelectedRow = AZStd::min(lowestSelectedRow, item->row()); + } + AZStd::string outResult; - AZStd::string command = AZStd::string::format("AdjustNodeGroup -actorID %i -name \"%s\" -nodeAction \"remove\" -nodeNames \"%s\"", mActor->GetID(), mNodeGroup->GetName(), nodeList.c_str()); + auto* command = aznew CommandSystem::CommandAdjustNodeGroup( + GetCommandManager()->FindCommand(CommandSystem::CommandAdjustNodeGroup::s_commandName), + /*actorId=*/ mActor->GetID(), + /*name=*/ mNodeGroup->GetName(), + /*newName=*/ AZStd::nullopt, + /*enabledOnDefault=*/ AZStd::nullopt, + /*nodeNames=*/ AZStd::move(nodeList), + /*nodeAction=*/ CommandSystem::CommandAdjustNodeGroup::NodeAction::Remove + ); if (EMStudio::GetCommandManager()->ExecuteCommand(command, outResult) == false) { AZ_Error("EMotionFX", false, outResult.c_str()); } // selected the next row - if (lowestSelectedRow > ((uint32)mNodeTable->rowCount() - 1)) + if (lowestSelectedRow > (mNodeTable->rowCount() - 1)) { mNodeTable->selectRow(lowestSelectedRow - 1); } @@ -353,19 +343,23 @@ namespace EMStudio } // generate node list string - AZStd::string nodeList; - nodeList.reserve(16448); - const uint32 numSelectedNodes = selectionList.GetLength(); - for (uint32 i = 0; i < numSelectedNodes; ++i) + AZStd::vector nodeList; + const uint32 selectionListSize = selectionList.GetLength(); + for (uint32 i = 0; i < selectionListSize; ++i) { - nodeList += selectionList[i].GetNodeName(); - nodeList += ";"; + nodeList.emplace_back(selectionList[i].GetNodeName()); } - AzFramework::StringFunc::Strip(nodeList, MCore::CharacterConstants::semiColon, true /* case sensitive */, false /* beginning */, true /* ending */); - // call command for adjusting disable on default flag AZStd::string outResult; - AZStd::string command = AZStd::string::format("AdjustNodeGroup -actorID %i -name \"%s\" -nodeAction \"%s\" -nodeNames \"%s\"", mActor->GetID(), mNodeGroup->GetName(), mNodeAction.c_str(), nodeList.c_str()); + auto* command = aznew CommandSystem::CommandAdjustNodeGroup( + GetCommandManager()->FindCommand(CommandSystem::CommandAdjustNodeGroup::s_commandName), + /*actorId=*/ mActor->GetID(), + /*name=*/ mNodeGroup->GetName(), + /*newName=*/ AZStd::nullopt, + /*enabledOnDefault=*/ AZStd::nullopt, + /*nodeNames=*/ AZStd::move(nodeList), + /*nodeAction=*/ mNodeAction + ); if (EMStudio::GetCommandManager()->ExecuteCommand(command, outResult) == false) { AZ_Error("EMotionFX", false, outResult.c_str()); diff --git a/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/Plugins/StandardPlugins/Source/NodeGroups/NodeGroupWidget.h b/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/Plugins/StandardPlugins/Source/NodeGroups/NodeGroupWidget.h index ebef94b05c..2b4cad06de 100644 --- a/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/Plugins/StandardPlugins/Source/NodeGroups/NodeGroupWidget.h +++ b/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/Plugins/StandardPlugins/Source/NodeGroups/NodeGroupWidget.h @@ -13,6 +13,7 @@ #include #include "../../../../EMStudioSDK/Source/DockWidgetPlugin.h" #include "../../../../EMStudioSDK/Source/NodeSelectionWindow.h" +#include #endif QT_FORWARD_DECLARE_CLASS(QLineEdit) @@ -58,7 +59,7 @@ namespace EMStudio CommandSystem::SelectionList mNodeSelectionList; EMotionFX::NodeGroup* mNodeGroup; uint16 mNodeGroupIndex; - AZStd::string mNodeAction; + CommandSystem::CommandAdjustNodeGroup::NodeAction mNodeAction; // widgets QTableWidget* mNodeTable; diff --git a/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/Plugins/StandardPlugins/Source/NodeGroups/NodeGroupsPlugin.cpp b/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/Plugins/StandardPlugins/Source/NodeGroups/NodeGroupsPlugin.cpp index 91a96ef39d..218035dc30 100644 --- a/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/Plugins/StandardPlugins/Source/NodeGroups/NodeGroupsPlugin.cpp +++ b/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/Plugins/StandardPlugins/Source/NodeGroups/NodeGroupsPlugin.cpp @@ -11,6 +11,7 @@ #include "../../../../EMStudioSDK/Source/EMStudioCore.h" #include #include +#include #include "../../../../EMStudioSDK/Source/EMStudioManager.h" // include qt headers @@ -99,7 +100,7 @@ namespace EMStudio GetCommandManager()->RegisterCommandCallback("Select", mSelectCallback); GetCommandManager()->RegisterCommandCallback("Unselect", mUnselectCallback); GetCommandManager()->RegisterCommandCallback("ClearSelection", mClearSelectionCallback); - GetCommandManager()->RegisterCommandCallback("AdjustNodeGroup", mAdjustNodeGroupCallback); + GetCommandManager()->RegisterCommandCallback(CommandSystem::CommandAdjustNodeGroup::s_commandName.data(), mAdjustNodeGroupCallback); GetCommandManager()->RegisterCommandCallback("AddNodeGroup", mAddNodeGroupCallback); GetCommandManager()->RegisterCommandCallback("RemoveNodeGroup", mRemoveNodeGroupCallback); From c3103a3fe7d2a0cc6bd4c1b157bdf5dfcfcff1ed Mon Sep 17 00:00:00 2001 From: nvsickle Date: Mon, 2 Aug 2021 14:25:53 -0700 Subject: [PATCH 09/51] Fix release build error. Atom's DebugCamera SetOrthographic was only using a parameter during an assert, leading to a release compile error, this fixes that Signed-off-by: nvsickle --- Gems/Atom/Component/DebugCamera/Code/Source/CameraComponent.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gems/Atom/Component/DebugCamera/Code/Source/CameraComponent.cpp b/Gems/Atom/Component/DebugCamera/Code/Source/CameraComponent.cpp index 9a6f7c83ab..330b6571f3 100644 --- a/Gems/Atom/Component/DebugCamera/Code/Source/CameraComponent.cpp +++ b/Gems/Atom/Component/DebugCamera/Code/Source/CameraComponent.cpp @@ -235,7 +235,7 @@ namespace AZ UpdateViewToClipMatrix(); } - void CameraComponent::SetOrthographic(bool orthographic) + void CameraComponent::SetOrthographic([[maybe_unused]] bool orthographic) { AZ_Assert(!orthographic, "DebugCamera does not support orthographic projection"); } From 07e2bea1fe5f2250b778b871fe1c78568a0fbc2c Mon Sep 17 00:00:00 2001 From: puvvadar Date: Mon, 2 Aug 2021 17:41:09 -0700 Subject: [PATCH 10/51] Add connection interface timeout config and remove Ctrl+G timer Signed-off-by: puvvadar --- .../AzNetworking/Framework/INetworkInterface.h | 8 ++++++++ .../TcpTransport/TcpNetworkInterface.cpp | 12 +++++++++++- .../AzNetworking/TcpTransport/TcpNetworkInterface.h | 3 +++ .../UdpTransport/UdpNetworkInterface.cpp | 12 +++++++++++- .../AzNetworking/UdpTransport/UdpNetworkInterface.h | 3 +++ .../Source/Editor/MultiplayerEditorConnection.cpp | 1 + .../Editor/MultiplayerEditorSystemComponent.cpp | 6 +----- 7 files changed, 38 insertions(+), 7 deletions(-) diff --git a/Code/Framework/AzNetworking/AzNetworking/Framework/INetworkInterface.h b/Code/Framework/AzNetworking/AzNetworking/Framework/INetworkInterface.h index d47f76ceb8..c5f79699dd 100644 --- a/Code/Framework/AzNetworking/AzNetworking/Framework/INetworkInterface.h +++ b/Code/Framework/AzNetworking/AzNetworking/Framework/INetworkInterface.h @@ -103,6 +103,14 @@ namespace AzNetworking //! @return boolean true on success virtual bool Disconnect(ConnectionId connectionId, DisconnectReason reason) = 0; + //! Sets whether this connection interface can disconnect by virtue of a timeout + //! @param doesTimeout If this connection interface will automatically disconnect due to a timeout + virtual void SetDoesTimeout(bool doesTimeout) = 0; + + //! Whether this connection interface will disconnect by virtue of a time out (does not account for cvars affecting all connections) + //! @return boolean true if this connection will not disconnect on timeout (does not account for cvars affecting all connections) + virtual bool DoesTimeout() = 0; + //! Const access to the metrics tracked by this network interface. //! @return const reference to the metrics tracked by this network interface const NetworkInterfaceMetrics& GetMetrics() const; diff --git a/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpNetworkInterface.cpp b/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpNetworkInterface.cpp index f9569b5c7e..b3aeef3345 100644 --- a/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpNetworkInterface.cpp +++ b/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpNetworkInterface.cpp @@ -174,6 +174,16 @@ namespace AzNetworking return connection->Disconnect(reason, TerminationEndpoint::Local); } + void TcpNetworkInterface::SetDoesTimeout(bool doesTimeout) + { + m_doesTimeout = doesTimeout; + } + + bool TcpNetworkInterface::DoesTimeout() + { + return m_doesTimeout; + } + void TcpNetworkInterface::QueueNewConnection(const PendingConnection& pendingConnection) { m_pendingConnections.PushBackItem(pendingConnection); @@ -306,7 +316,7 @@ namespace AzNetworking { tcpConnection->SendReliablePacket(CorePackets::HeartbeatPacket()); } - else if (net_TcpTimeoutConnections) + else if (net_TcpTimeoutConnections && m_networkInterface.DoesTimeout()) { tcpConnection->Disconnect(DisconnectReason::Timeout, TerminationEndpoint::Local); return TimeoutResult::Delete; diff --git a/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpNetworkInterface.h b/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpNetworkInterface.h index 1d590da2aa..d1e9fb67cc 100644 --- a/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpNetworkInterface.h +++ b/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpNetworkInterface.h @@ -99,6 +99,8 @@ namespace AzNetworking bool WasPacketAcked(ConnectionId connectionId, PacketId packetId) override; bool StopListening() override; bool Disconnect(ConnectionId connectionId, DisconnectReason reason) override; + void SetDoesTimeout(bool doesTimeout) override; + bool DoesTimeout() override; //! @} //! Queues a new incoming connection for this network interface. @@ -154,6 +156,7 @@ namespace AzNetworking AZ::Name m_name; TrustZone m_trustZone; uint16_t m_port = 0; + bool m_doesTimeout = true; IConnectionListener& m_connectionListener; TcpConnectionSet m_connectionSet; TcpSocketManager m_tcpSocketManager; diff --git a/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpNetworkInterface.cpp b/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpNetworkInterface.cpp index a3ddb856d2..5be8594a2f 100644 --- a/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpNetworkInterface.cpp +++ b/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpNetworkInterface.cpp @@ -397,6 +397,16 @@ namespace AzNetworking return connection->Disconnect(reason, TerminationEndpoint::Local); } + void UdpNetworkInterface::SetDoesTimeout(bool doesTimeout) + { + m_doesTimeout = doesTimeout; + } + + bool UdpNetworkInterface::DoesTimeout() + { + return m_doesTimeout; + } + bool UdpNetworkInterface::IsEncrypted() const { return m_socket->IsEncrypted(); @@ -729,7 +739,7 @@ namespace AzNetworking { udpConnection->SendUnreliablePacket(CorePackets::HeartbeatPacket()); } - else if (net_UdpTimeoutConnections) + else if (net_UdpTimeoutConnections && m_networkInterface.DoesTimeout()) { udpConnection->Disconnect(DisconnectReason::Timeout, TerminationEndpoint::Local); return TimeoutResult::Delete; diff --git a/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpNetworkInterface.h b/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpNetworkInterface.h index 7a391c152e..b2e80dc3e9 100644 --- a/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpNetworkInterface.h +++ b/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpNetworkInterface.h @@ -104,6 +104,8 @@ namespace AzNetworking bool WasPacketAcked(ConnectionId connectionId, PacketId packetId) override; bool StopListening() override; bool Disconnect(ConnectionId connectionId, DisconnectReason reason) override; + void SetDoesTimeout(bool doesTimeout) override; + bool DoesTimeout() override; //! @} //! Returns true if this is an encrypted socket, false if not. @@ -179,6 +181,7 @@ namespace AzNetworking TrustZone m_trustZone; uint16_t m_port = 0; bool m_allowIncomingConnections = false; + bool m_doesTimeout = true; IConnectionListener& m_connectionListener; UdpConnectionSet m_connectionSet; TimeoutQueue m_connectionTimeoutQueue; diff --git a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.cpp b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.cpp index deb53bacab..db2a36ae20 100644 --- a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.cpp +++ b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.cpp @@ -32,6 +32,7 @@ namespace Multiplayer { m_networkEditorInterface = AZ::Interface::Get()->CreateNetworkInterface( AZ::Name(MPEditorInterfaceName), ProtocolType::Tcp, TrustZone::ExternalClientToServer, *this); + m_networkEditorInterface->SetDoesTimeout(false); if (editorsv_isDedicated) { uint16_t editorServerPort = DefaultServerEditorPort; diff --git a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp index 9030b150e6..d557c65215 100644 --- a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp @@ -147,13 +147,9 @@ namespace Multiplayer processLaunchInfo.m_showWindow = true; processLaunchInfo.m_processPriority = AzFramework::ProcessPriority::PROCESSPRIORITY_NORMAL; - // Launch the Server and give it a few seconds to boot up + // Launch the Server AzFramework::ProcessWatcher* outProcess = AzFramework::ProcessWatcher::LaunchProcess( processLaunchInfo, AzFramework::ProcessCommunicationType::COMMUNICATOR_TYPE_NONE); - if (outProcess) - { - AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(15000)); - } return outProcess; } From 115f669679521f6ad0d49317945dd82347a9233e Mon Sep 17 00:00:00 2001 From: puvvadar Date: Mon, 2 Aug 2021 20:31:02 -0700 Subject: [PATCH 11/51] Rename timeout functions for readability Signed-off-by: puvvadar --- .../AzNetworking/AzNetworking/Framework/INetworkInterface.h | 4 ++-- .../AzNetworking/TcpTransport/TcpNetworkInterface.cpp | 6 +++--- .../AzNetworking/TcpTransport/TcpNetworkInterface.h | 4 ++-- .../AzNetworking/UdpTransport/UdpNetworkInterface.cpp | 6 +++--- .../AzNetworking/UdpTransport/UdpNetworkInterface.h | 4 ++-- .../Code/Source/Editor/MultiplayerEditorConnection.cpp | 2 +- 6 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Code/Framework/AzNetworking/AzNetworking/Framework/INetworkInterface.h b/Code/Framework/AzNetworking/AzNetworking/Framework/INetworkInterface.h index c5f79699dd..303fd3de0f 100644 --- a/Code/Framework/AzNetworking/AzNetworking/Framework/INetworkInterface.h +++ b/Code/Framework/AzNetworking/AzNetworking/Framework/INetworkInterface.h @@ -105,11 +105,11 @@ namespace AzNetworking //! Sets whether this connection interface can disconnect by virtue of a timeout //! @param doesTimeout If this connection interface will automatically disconnect due to a timeout - virtual void SetDoesTimeout(bool doesTimeout) = 0; + virtual void SetTimeoutEnabled(bool doesTimeout) = 0; //! Whether this connection interface will disconnect by virtue of a time out (does not account for cvars affecting all connections) //! @return boolean true if this connection will not disconnect on timeout (does not account for cvars affecting all connections) - virtual bool DoesTimeout() = 0; + virtual bool IsTimeoutEnabled() = 0; //! Const access to the metrics tracked by this network interface. //! @return const reference to the metrics tracked by this network interface diff --git a/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpNetworkInterface.cpp b/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpNetworkInterface.cpp index b3aeef3345..26972cfa0b 100644 --- a/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpNetworkInterface.cpp +++ b/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpNetworkInterface.cpp @@ -174,12 +174,12 @@ namespace AzNetworking return connection->Disconnect(reason, TerminationEndpoint::Local); } - void TcpNetworkInterface::SetDoesTimeout(bool doesTimeout) + void TcpNetworkInterface::SetTimeoutEnabled(bool doesTimeout) { m_doesTimeout = doesTimeout; } - bool TcpNetworkInterface::DoesTimeout() + bool TcpNetworkInterface::IsTimeoutEnabled() { return m_doesTimeout; } @@ -316,7 +316,7 @@ namespace AzNetworking { tcpConnection->SendReliablePacket(CorePackets::HeartbeatPacket()); } - else if (net_TcpTimeoutConnections && m_networkInterface.DoesTimeout()) + else if (net_TcpTimeoutConnections && m_networkInterface.IsTimeoutEnabled()) { tcpConnection->Disconnect(DisconnectReason::Timeout, TerminationEndpoint::Local); return TimeoutResult::Delete; diff --git a/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpNetworkInterface.h b/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpNetworkInterface.h index d1e9fb67cc..f041f707be 100644 --- a/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpNetworkInterface.h +++ b/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpNetworkInterface.h @@ -99,8 +99,8 @@ namespace AzNetworking bool WasPacketAcked(ConnectionId connectionId, PacketId packetId) override; bool StopListening() override; bool Disconnect(ConnectionId connectionId, DisconnectReason reason) override; - void SetDoesTimeout(bool doesTimeout) override; - bool DoesTimeout() override; + void SetTimeoutEnabled(bool doesTimeout) override; + bool IsTimeoutEnabled() override; //! @} //! Queues a new incoming connection for this network interface. diff --git a/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpNetworkInterface.cpp b/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpNetworkInterface.cpp index 5be8594a2f..6be01d84ed 100644 --- a/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpNetworkInterface.cpp +++ b/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpNetworkInterface.cpp @@ -397,12 +397,12 @@ namespace AzNetworking return connection->Disconnect(reason, TerminationEndpoint::Local); } - void UdpNetworkInterface::SetDoesTimeout(bool doesTimeout) + void UdpNetworkInterface::SetTimeoutEnabled(bool doesTimeout) { m_doesTimeout = doesTimeout; } - bool UdpNetworkInterface::DoesTimeout() + bool UdpNetworkInterface::IsTimeoutEnabled() { return m_doesTimeout; } @@ -739,7 +739,7 @@ namespace AzNetworking { udpConnection->SendUnreliablePacket(CorePackets::HeartbeatPacket()); } - else if (net_UdpTimeoutConnections && m_networkInterface.DoesTimeout()) + else if (net_UdpTimeoutConnections && m_networkInterface.IsTimeoutEnabled()) { udpConnection->Disconnect(DisconnectReason::Timeout, TerminationEndpoint::Local); return TimeoutResult::Delete; diff --git a/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpNetworkInterface.h b/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpNetworkInterface.h index b2e80dc3e9..087ed9d52f 100644 --- a/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpNetworkInterface.h +++ b/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpNetworkInterface.h @@ -104,8 +104,8 @@ namespace AzNetworking bool WasPacketAcked(ConnectionId connectionId, PacketId packetId) override; bool StopListening() override; bool Disconnect(ConnectionId connectionId, DisconnectReason reason) override; - void SetDoesTimeout(bool doesTimeout) override; - bool DoesTimeout() override; + void SetTimeoutEnabled(bool doesTimeout) override; + bool IsTimeoutEnabled() override; //! @} //! Returns true if this is an encrypted socket, false if not. diff --git a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.cpp b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.cpp index db2a36ae20..fc398182ef 100644 --- a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.cpp +++ b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.cpp @@ -32,7 +32,7 @@ namespace Multiplayer { m_networkEditorInterface = AZ::Interface::Get()->CreateNetworkInterface( AZ::Name(MPEditorInterfaceName), ProtocolType::Tcp, TrustZone::ExternalClientToServer, *this); - m_networkEditorInterface->SetDoesTimeout(false); + m_networkEditorInterface->SetTimeoutEnabled(false); if (editorsv_isDedicated) { uint16_t editorServerPort = DefaultServerEditorPort; From 573fe425d7030d7aadc9149c8ff45c177798cd5c Mon Sep 17 00:00:00 2001 From: puvvadar Date: Mon, 2 Aug 2021 20:34:39 -0700 Subject: [PATCH 12/51] Also rename some variables to match renamed timeout funcs Signed-off-by: puvvadar --- .../AzNetworking/TcpTransport/TcpNetworkInterface.cpp | 6 +++--- .../AzNetworking/TcpTransport/TcpNetworkInterface.h | 4 ++-- .../AzNetworking/UdpTransport/UdpNetworkInterface.cpp | 6 +++--- .../AzNetworking/UdpTransport/UdpNetworkInterface.h | 4 ++-- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpNetworkInterface.cpp b/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpNetworkInterface.cpp index 26972cfa0b..52c696b663 100644 --- a/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpNetworkInterface.cpp +++ b/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpNetworkInterface.cpp @@ -174,14 +174,14 @@ namespace AzNetworking return connection->Disconnect(reason, TerminationEndpoint::Local); } - void TcpNetworkInterface::SetTimeoutEnabled(bool doesTimeout) + void TcpNetworkInterface::SetTimeoutEnabled(bool timeoutEnabled) { - m_doesTimeout = doesTimeout; + m_timeoutEnabled = timeoutEnabled; } bool TcpNetworkInterface::IsTimeoutEnabled() { - return m_doesTimeout; + return m_timeoutEnabled; } void TcpNetworkInterface::QueueNewConnection(const PendingConnection& pendingConnection) diff --git a/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpNetworkInterface.h b/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpNetworkInterface.h index f041f707be..3eb792bc7f 100644 --- a/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpNetworkInterface.h +++ b/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpNetworkInterface.h @@ -99,7 +99,7 @@ namespace AzNetworking bool WasPacketAcked(ConnectionId connectionId, PacketId packetId) override; bool StopListening() override; bool Disconnect(ConnectionId connectionId, DisconnectReason reason) override; - void SetTimeoutEnabled(bool doesTimeout) override; + void SetTimeoutEnabled(bool timeoutEnabled) override; bool IsTimeoutEnabled() override; //! @} @@ -156,7 +156,7 @@ namespace AzNetworking AZ::Name m_name; TrustZone m_trustZone; uint16_t m_port = 0; - bool m_doesTimeout = true; + bool m_timeoutEnabled = true; IConnectionListener& m_connectionListener; TcpConnectionSet m_connectionSet; TcpSocketManager m_tcpSocketManager; diff --git a/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpNetworkInterface.cpp b/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpNetworkInterface.cpp index 6be01d84ed..a80cb82d03 100644 --- a/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpNetworkInterface.cpp +++ b/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpNetworkInterface.cpp @@ -397,14 +397,14 @@ namespace AzNetworking return connection->Disconnect(reason, TerminationEndpoint::Local); } - void UdpNetworkInterface::SetTimeoutEnabled(bool doesTimeout) + void UdpNetworkInterface::SetTimeoutEnabled(bool timeoutEnabled) { - m_doesTimeout = doesTimeout; + m_timeoutEnabled = timeoutEnabled; } bool UdpNetworkInterface::IsTimeoutEnabled() { - return m_doesTimeout; + return m_timeoutEnabled; } bool UdpNetworkInterface::IsEncrypted() const diff --git a/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpNetworkInterface.h b/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpNetworkInterface.h index 087ed9d52f..0260491295 100644 --- a/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpNetworkInterface.h +++ b/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpNetworkInterface.h @@ -104,7 +104,7 @@ namespace AzNetworking bool WasPacketAcked(ConnectionId connectionId, PacketId packetId) override; bool StopListening() override; bool Disconnect(ConnectionId connectionId, DisconnectReason reason) override; - void SetTimeoutEnabled(bool doesTimeout) override; + void SetTimeoutEnabled(bool timeoutEnabled) override; bool IsTimeoutEnabled() override; //! @} @@ -181,7 +181,7 @@ namespace AzNetworking TrustZone m_trustZone; uint16_t m_port = 0; bool m_allowIncomingConnections = false; - bool m_doesTimeout = true; + bool m_timeoutEnabled = true; IConnectionListener& m_connectionListener; UdpConnectionSet m_connectionSet; TimeoutQueue m_connectionTimeoutQueue; From 6964e4f7e9182871b4e6c05607d5308237564ae1 Mon Sep 17 00:00:00 2001 From: puvvadar Date: Mon, 2 Aug 2021 20:35:44 -0700 Subject: [PATCH 13/51] Missed one variable rename Signed-off-by: puvvadar --- .../AzNetworking/AzNetworking/Framework/INetworkInterface.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Code/Framework/AzNetworking/AzNetworking/Framework/INetworkInterface.h b/Code/Framework/AzNetworking/AzNetworking/Framework/INetworkInterface.h index 303fd3de0f..f2ad1c03c3 100644 --- a/Code/Framework/AzNetworking/AzNetworking/Framework/INetworkInterface.h +++ b/Code/Framework/AzNetworking/AzNetworking/Framework/INetworkInterface.h @@ -104,8 +104,8 @@ namespace AzNetworking virtual bool Disconnect(ConnectionId connectionId, DisconnectReason reason) = 0; //! Sets whether this connection interface can disconnect by virtue of a timeout - //! @param doesTimeout If this connection interface will automatically disconnect due to a timeout - virtual void SetTimeoutEnabled(bool doesTimeout) = 0; + //! @param timeoutEnabled If this connection interface will automatically disconnect due to a timeout + virtual void SetTimeoutEnabled(bool timeoutEnabled) = 0; //! Whether this connection interface will disconnect by virtue of a time out (does not account for cvars affecting all connections) //! @return boolean true if this connection will not disconnect on timeout (does not account for cvars affecting all connections) From 86758fda35484b939f3cbffb7deb25b9d940cf6e Mon Sep 17 00:00:00 2001 From: hultonha Date: Thu, 22 Jul 2021 13:15:33 +0100 Subject: [PATCH 14/51] documentation pass for modular viewport camera controller Signed-off-by: hultonha --- .../ModularViewportCameraController.h | 66 ++++++++++++------- .../ModularViewportCameraController.cpp | 39 +++++------ 2 files changed, 61 insertions(+), 44 deletions(-) diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Viewport/ModularViewportCameraController.h b/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Viewport/ModularViewportCameraController.h index 1d2ccc07e1..ca6044c9de 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Viewport/ModularViewportCameraController.h +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Viewport/ModularViewportCameraController.h @@ -16,18 +16,21 @@ namespace AtomToolsFramework { - class ModernViewportCameraControllerInstance; + class ModularViewportCameraControllerInstance; + + //! Builder class to create and configure a ModularViewportCameraControllerInstance. class ModularViewportCameraController : public AzFramework::MultiViewportController< - ModernViewportCameraControllerInstance, AzFramework::ViewportControllerPriority::DispatchToAllPriorities> + ModularViewportCameraControllerInstance, + AzFramework::ViewportControllerPriority::DispatchToAllPriorities> { public: using CameraListBuilder = AZStd::function; using CameraPropsBuilder = AZStd::function; - //! Sets the camera list builder callback used to populate new ModernViewportCameraControllerInstances + //! Sets the camera list builder callback used to populate new ModularViewportCameraControllerInstances void SetCameraListBuilderCallback(const CameraListBuilder& builder); - //! Sets the camera props builder callback used to populate new ModernViewportCameraControllerInstances + //! Sets the camera props builder callback used to populate new ModularViewportCameraControllerInstances void SetCameraPropsBuilderCallback(const CameraPropsBuilder& builder); //! Sets up a camera list based on this controller's CameraListBuilderCallback void SetupCameras(AzFramework::Cameras& cameras); @@ -35,18 +38,22 @@ namespace AtomToolsFramework void SetupCameraProperies(AzFramework::CameraProps& cameraProps); private: - CameraListBuilder m_cameraListBuilder; - CameraPropsBuilder m_cameraPropsBuilder; + CameraListBuilder + m_cameraListBuilder; //!< Builder to generate a list of CameraInputs to run in the ModularViewportCameraControllerInstance. + CameraPropsBuilder m_cameraPropsBuilder; //!< Builder to define custom camera properties to use for things such as rotate and + //!< translate interpolation. }; - class ModernViewportCameraControllerInstance final - : public AzFramework::MultiViewportControllerInstanceInterface, - public ModularViewportCameraControllerRequestBus::Handler, - private AzFramework::ViewportDebugDisplayEventBus::Handler + //! A customizable camera controller than can be configured to a run varying set of CameraInput instances. + //! The controller can also be animated from its current transform to a new translation and orientation. + class ModularViewportCameraControllerInstance final + : public AzFramework::MultiViewportControllerInstanceInterface + , public ModularViewportCameraControllerRequestBus::Handler + , private AzFramework::ViewportDebugDisplayEventBus::Handler { public: - explicit ModernViewportCameraControllerInstance(AzFramework::ViewportId viewportId, ModularViewportCameraController* controller); - ~ModernViewportCameraControllerInstance() override; + explicit ModularViewportCameraControllerInstance(AzFramework::ViewportId viewportId, ModularViewportCameraController* controller); + ~ModularViewportCameraControllerInstance() override; // MultiViewportControllerInstanceInterface overrides ... bool HandleInputChannelEvent(const AzFramework::ViewportControllerInputEvent& event) override; @@ -60,25 +67,34 @@ namespace AtomToolsFramework // AzFramework::ViewportDebugDisplayEventBus overrides ... void DisplayViewport(const AzFramework::ViewportInfo& viewportInfo, AzFramework::DebugDisplayRequests& debugDisplay) override; + //! The current mode the camera controller is in. enum class CameraMode { - Control, - Animation + Control, //!< The camera is being driven by user input. + Animation //!< The camera is being animated (interpolated) from one transform to another. }; - AzFramework::Camera m_camera; - AzFramework::Camera m_targetCamera; - AzFramework::CameraSystem m_cameraSystem; - AzFramework::CameraProps m_cameraProps; + //! Encapsulates an animation (interpolation) between two transforms. + struct CameraAnimation + { + AZ::Transform m_transformStart = + AZ::Transform::CreateIdentity(); //!< The transform of the camera at the start of the animation. + AZ::Transform m_transformEnd = AZ::Transform::CreateIdentity(); //!< The transform of the camera at the end of the animation. + float m_animationT = 0.0f; //!< The interpolation amount between the start and end transforms (in the range 0.0-1.0). + }; - AZ::Transform m_transformStart = AZ::Transform::CreateIdentity(); - AZ::Transform m_transformEnd = AZ::Transform::CreateIdentity(); - float m_animationT = 0.0f; - CameraMode m_cameraMode = CameraMode::Control; + AzFramework::Camera m_camera; //!< The current camera state (pitch/yaw/position/look-distance). + AzFramework::Camera m_targetCamera; //!< The target (next) camera state that m_camera is catching up to. + AzFramework::CameraSystem m_cameraSystem; //!< The camera system responsible for managing all CameraInputs. + AzFramework::CameraProps m_cameraProps; //!< Camera properties to control rotate and translate smoothness. + + CameraAnimation m_cameraAnimation; //!< Camera animation state (used during CameraMode::Animation). + CameraMode m_cameraMode = CameraMode::Control; //!< The current mode the camera is operating in. AZStd::optional m_lookAtAfterInterpolation; //!< The look at point after an interpolation has finished. //!< Will be cleared when the view changes (camera looks away). - bool m_updatingTransform = false; - - AZ::RPI::ViewportContext::MatrixChangedEvent::Handler m_cameraViewMatrixChangeHandler; + bool m_updatingTransformInternally = + false; //!< Flag to prevent circular updates of the camera transform (while the viewport transform is being updated internally). + AZ::RPI::ViewportContext::MatrixChangedEvent::Handler + m_cameraViewMatrixChangeHandler; //!< Listen for camera view changes outside of the camera controller. }; } // namespace AtomToolsFramework diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Viewport/ModularViewportCameraController.cpp b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Viewport/ModularViewportCameraController.cpp index 10cfa059aa..ce4c6021af 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Viewport/ModularViewportCameraController.cpp +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Viewport/ModularViewportCameraController.cpp @@ -84,7 +84,7 @@ namespace AtomToolsFramework } } - ModernViewportCameraControllerInstance::ModernViewportCameraControllerInstance( + ModularViewportCameraControllerInstance::ModularViewportCameraControllerInstance( const AzFramework::ViewportId viewportId, ModularViewportCameraController* controller) : MultiViewportControllerInstanceInterface(viewportId, controller) { @@ -95,7 +95,8 @@ namespace AtomToolsFramework { auto handleCameraChange = [this, viewportContext](const AZ::Matrix4x4&) { - if (!m_updatingTransform) + // ignore these updates if the camera is being updated internally + if (!m_updatingTransformInternally) { UpdateCameraFromTransform(m_targetCamera, viewportContext->GetCameraTransform()); m_camera = m_targetCamera; @@ -111,7 +112,7 @@ namespace AtomToolsFramework ModularViewportCameraControllerRequestBus::Handler::BusConnect(viewportId); } - ModernViewportCameraControllerInstance::~ModernViewportCameraControllerInstance() + ModularViewportCameraControllerInstance::~ModularViewportCameraControllerInstance() { ModularViewportCameraControllerRequestBus::Handler::BusDisconnect(); AzFramework::ViewportDebugDisplayEventBus::Handler::BusDisconnect(); @@ -132,7 +133,7 @@ namespace AtomToolsFramework return AzFramework::ViewportControllerPriority::Normal; } - bool ModernViewportCameraControllerInstance::HandleInputChannelEvent(const AzFramework::ViewportControllerInputEvent& event) + bool ModularViewportCameraControllerInstance::HandleInputChannelEvent(const AzFramework::ViewportControllerInputEvent& event) { if (event.m_priority == GetPriority(m_cameraSystem)) { @@ -142,7 +143,7 @@ namespace AtomToolsFramework return false; } - void ModernViewportCameraControllerInstance::UpdateViewport(const AzFramework::ViewportControllerUpdateEvent& event) + void ModularViewportCameraControllerInstance::UpdateViewport(const AzFramework::ViewportControllerUpdateEvent& event) { // only update for a single priority (normal is the default) if (event.m_priority != AzFramework::ViewportControllerPriority::Normal) @@ -152,7 +153,7 @@ namespace AtomToolsFramework if (auto viewportContext = RetrieveViewportContext(GetViewportId())) { - m_updatingTransform = true; + m_updatingTransformInternally = true; if (m_cameraMode == CameraMode::Control) { @@ -180,10 +181,12 @@ namespace AtomToolsFramework return t * t * t * (t * (t * 6.0f - 15.0f) + 10.0f); }; - const float transitionT = smootherStepFn(m_animationT); + const auto& [transformStart, transformEnd, animationT] = m_cameraAnimation; + + const float transitionT = smootherStepFn(animationT); const AZ::Transform current = AZ::Transform::CreateFromQuaternionAndTranslation( - m_transformStart.GetRotation().Slerp(m_transformEnd.GetRotation(), transitionT), - m_transformStart.GetTranslation().Lerp(m_transformEnd.GetTranslation(), transitionT)); + transformStart.GetRotation().Slerp(transformEnd.GetRotation(), transitionT), + transformStart.GetTranslation().Lerp(transformEnd.GetTranslation(), transitionT)); const AZ::Vector3 eulerAngles = AzFramework::EulerAngles(AZ::Matrix3x3::CreateFromTransform(current)); m_camera.m_pitch = eulerAngles.GetX(); @@ -191,21 +194,21 @@ namespace AtomToolsFramework m_camera.m_lookAt = current.GetTranslation(); m_targetCamera = m_camera; - if (m_animationT >= 1.0f) + if (animationT >= 1.0f) { m_cameraMode = CameraMode::Control; } - m_animationT = AZ::GetClamp(m_animationT + event.m_deltaTime.count(), 0.0f, 1.0f); + m_cameraAnimation.m_animationT = AZ::GetClamp(animationT + event.m_deltaTime.count(), 0.0f, 1.0f); viewportContext->SetCameraTransform(current); } - m_updatingTransform = false; + m_updatingTransformInternally = false; } } - void ModernViewportCameraControllerInstance::DisplayViewport( + void ModularViewportCameraControllerInstance::DisplayViewport( [[maybe_unused]] const AzFramework::ViewportInfo& viewportInfo, AzFramework::DebugDisplayRequests& debugDisplay) { if (const float alpha = AZStd::min(-m_camera.m_lookDist / 5.0f, 1.0f); alpha > AZ::Constants::FloatEpsilon) @@ -216,16 +219,14 @@ namespace AtomToolsFramework } } - void ModernViewportCameraControllerInstance::InterpolateToTransform(const AZ::Transform& worldFromLocal, const float lookAtDistance) + void ModularViewportCameraControllerInstance::InterpolateToTransform(const AZ::Transform& worldFromLocal, const float lookAtDistance) { - m_animationT = 0.0f; m_cameraMode = CameraMode::Animation; - m_transformStart = m_camera.Transform(); - m_transformEnd = worldFromLocal; - m_lookAtAfterInterpolation = m_transformEnd.GetTranslation() + m_transformEnd.GetBasisY() * lookAtDistance; + m_cameraAnimation = CameraAnimation{ m_camera.Transform(), worldFromLocal, 0.0f }; + m_lookAtAfterInterpolation = worldFromLocal.GetTranslation() + worldFromLocal.GetBasisY() * lookAtDistance; } - AZStd::optional ModernViewportCameraControllerInstance::LookAtAfterInterpolation() const + AZStd::optional ModularViewportCameraControllerInstance::LookAtAfterInterpolation() const { return m_lookAtAfterInterpolation; } From 71a299d739b2750e1a6864e3f8ab73d742c6b0a2 Mon Sep 17 00:00:00 2001 From: hultonha Date: Tue, 3 Aug 2021 13:53:34 +0100 Subject: [PATCH 15/51] minor comment grammar fix Signed-off-by: hultonha --- .../Viewport/ModularViewportCameraController.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Viewport/ModularViewportCameraController.h b/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Viewport/ModularViewportCameraController.h index ca6044c9de..79219f50b0 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Viewport/ModularViewportCameraController.h +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Viewport/ModularViewportCameraController.h @@ -44,7 +44,7 @@ namespace AtomToolsFramework //!< translate interpolation. }; - //! A customizable camera controller than can be configured to a run varying set of CameraInput instances. + //! A customizable camera controller that can be configured to run a varying set of CameraInput instances. //! The controller can also be animated from its current transform to a new translation and orientation. class ModularViewportCameraControllerInstance final : public AzFramework::MultiViewportControllerInstanceInterface From be5a7f821c1f7727a1525808f74f30b77bf17875 Mon Sep 17 00:00:00 2001 From: jackalbe <23512001+jackalbe@users.noreply.github.com> Date: Tue, 3 Aug 2021 09:21:36 -0500 Subject: [PATCH 16/51] {LYN-4514} Re-factored Blast gem's python asset builder (#2143) * {LYN-4514} Re-factored Blast gem's python asset builder * Re-factored Blast gem's python asset builder so that the .blast file creates an asset info scene manifest * Added a python script to act as a SceneAPI script + Python Asset Builder (blast_asset_builder.py) * renaming types from "Slice" to "Chunk" Tests: Re-enabled Gems/Blast/Code/Tests/Editor/EditorBlastSliceAssetHandlerTest.cpp Signed-off-by: Jackson <23512001+jackalbe@users.noreply.github.com> * renaming from Slice to Chunks Signed-off-by: Jackson <23512001+jackalbe@users.noreply.github.com> * updated the Copyright Signed-off-by: Jackson <23512001+jackalbe@users.noreply.github.com> * Removing StdAfx.h includes Signed-off-by: Jackson <23512001+jackalbe@users.noreply.github.com> * null check added m_blastChunksAsset.Get() removing 'slice' like EditorBlastSliceAssetHandlerTestFixture delete old asset builder blast file Signed-off-by: Jackson <23512001+jackalbe@users.noreply.github.com> * adding source deps for FBX -> BLAST file Signed-off-by: Jackson <23512001+jackalbe@users.noreply.github.com> * removing slice name Signed-off-by: Jackson <23512001+jackalbe@users.noreply.github.com> * Adding error message and updates from PR Signed-off-by: Jackson <23512001+jackalbe@users.noreply.github.com> --- .../Code/Source/Asset/BlastChunksAsset.cpp | 33 ++ .../Code/Source/Asset/BlastChunksAsset.h | 32 ++ .../Code/Source/Asset/BlastSliceAsset.cpp | 62 --- .../Blast/Code/Source/Asset/BlastSliceAsset.h | 36 -- Gems/Blast/Code/Source/BlastModule.cpp | 4 +- .../Editor/EditorBlastChunksAssetHandler.cpp | 144 +++++++ .../Editor/EditorBlastChunksAssetHandler.h | 46 +++ .../Editor/EditorBlastMeshDataComponent.cpp | 36 +- .../Editor/EditorBlastMeshDataComponent.h | 8 +- .../Editor/EditorBlastSliceAssetHandler.cpp | 345 ---------------- .../Editor/EditorBlastSliceAssetHandler.h | 101 ----- .../Source/Editor/EditorSystemComponent.cpp | 14 +- .../Source/Editor/EditorSystemComponent.h | 4 +- .../EditorBlastChunksAssetHandlerTest.cpp | 207 ++++++++++ .../EditorBlastSliceAssetHandlerTest.cpp | 377 ------------------ Gems/Blast/Code/blast_editor_files.cmake | 4 +- .../Blast/Code/blast_editor_tests_files.cmake | 2 +- Gems/Blast/Code/blast_files.cmake | 4 +- .../Editor/Scripts/asset_builder_blast.py | 323 --------------- .../Editor/Scripts/blast_asset_builder.py | 290 ++++++++++++++ Gems/Blast/Editor/Scripts/bootstrap.py | 13 +- 21 files changed, 801 insertions(+), 1284 deletions(-) create mode 100644 Gems/Blast/Code/Source/Asset/BlastChunksAsset.cpp create mode 100644 Gems/Blast/Code/Source/Asset/BlastChunksAsset.h delete mode 100644 Gems/Blast/Code/Source/Asset/BlastSliceAsset.cpp delete mode 100644 Gems/Blast/Code/Source/Asset/BlastSliceAsset.h create mode 100644 Gems/Blast/Code/Source/Editor/EditorBlastChunksAssetHandler.cpp create mode 100644 Gems/Blast/Code/Source/Editor/EditorBlastChunksAssetHandler.h delete mode 100644 Gems/Blast/Code/Source/Editor/EditorBlastSliceAssetHandler.cpp delete mode 100644 Gems/Blast/Code/Source/Editor/EditorBlastSliceAssetHandler.h create mode 100644 Gems/Blast/Code/Tests/Editor/EditorBlastChunksAssetHandlerTest.cpp delete mode 100644 Gems/Blast/Code/Tests/Editor/EditorBlastSliceAssetHandlerTest.cpp delete mode 100755 Gems/Blast/Editor/Scripts/asset_builder_blast.py create mode 100644 Gems/Blast/Editor/Scripts/blast_asset_builder.py diff --git a/Gems/Blast/Code/Source/Asset/BlastChunksAsset.cpp b/Gems/Blast/Code/Source/Asset/BlastChunksAsset.cpp new file mode 100644 index 0000000000..1d0ce15241 --- /dev/null +++ b/Gems/Blast/Code/Source/Asset/BlastChunksAsset.cpp @@ -0,0 +1,33 @@ +/* + * 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 +#include + +namespace Blast +{ + void BlastChunksAsset::SetModelAssetIds(const AZStd::vector& modelAssetIds) + { + m_modelAssetIds = modelAssetIds; + } + + const AZStd::vector& BlastChunksAsset::GetModelAssetIds() const + { + return m_modelAssetIds; + } + + void BlastChunksAsset::Reflect(AZ::ReflectContext* context) + { + if (auto serializeContext = azrtti_cast(context)) + { + serializeContext->Class() + ->Version(1) + ->Field("modelAssetIds", &BlastChunksAsset::m_modelAssetIds); + } + } + +} // namespace Blast diff --git a/Gems/Blast/Code/Source/Asset/BlastChunksAsset.h b/Gems/Blast/Code/Source/Asset/BlastChunksAsset.h new file mode 100644 index 0000000000..1f6442d8fd --- /dev/null +++ b/Gems/Blast/Code/Source/Asset/BlastChunksAsset.h @@ -0,0 +1,32 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ +#pragma once + +#include + +namespace Blast +{ + //! The product asset file from a .blast_chunks file product asset file + class BlastChunksAsset final + : public AZ::Data::AssetData + { + public: + AZ_RTTI(BlastChunksAsset, "{993F0B0F-37D9-48C6-9CC2-E27D3F3E343E}", AZ::Data::AssetData); + AZ_CLASS_ALLOCATOR(BlastChunksAsset, AZ::SystemAllocator, 0); + + BlastChunksAsset() = default; + ~BlastChunksAsset() override = default; + + void SetModelAssetIds(const AZStd::vector& modelAssetIds); + const AZStd::vector& GetModelAssetIds() const; + + static void Reflect(AZ::ReflectContext* context); + + private: + AZStd::vector m_modelAssetIds; + }; +} // namespace Blast diff --git a/Gems/Blast/Code/Source/Asset/BlastSliceAsset.cpp b/Gems/Blast/Code/Source/Asset/BlastSliceAsset.cpp deleted file mode 100644 index acb3043c19..0000000000 --- a/Gems/Blast/Code/Source/Asset/BlastSliceAsset.cpp +++ /dev/null @@ -1,62 +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 -#include - -namespace Blast -{ - void BlastSliceAsset::SetMeshIdList(const AZStd::vector& meshAssetIdList) - { - m_meshAssetIdList = meshAssetIdList; - } - - const AZStd::vector& BlastSliceAsset::GetMeshIdList() const - { - return m_meshAssetIdList; - } - - void BlastSliceAsset::SetMaterialId(const AZ::Data::AssetId& materialAssetId) - { - m_materialAssetId = materialAssetId; - } - - const AZ::Data::AssetId& BlastSliceAsset::GetMaterialId() const - { - return m_materialAssetId; - } - - void BlastSliceAsset::Reflect(AZ::ReflectContext* context) - { - if (auto serializeContext = azrtti_cast(context)) - { - serializeContext->Class() - ->Version(1) - ->Field("meshAssetIdList", &BlastSliceAsset::m_meshAssetIdList) - ->Field("materialAssetId", &BlastSliceAsset::m_materialAssetId); - } - - if (AZ::BehaviorContext* behavior = azrtti_cast(context)) - { - behavior->Class("BlastSliceAsset") - ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation) - ->Attribute(AZ::Script::Attributes::Module, "blast") - ->Method("SetMeshIdList", &BlastSliceAsset::SetMeshIdList) - ->Method("GetMeshIdList", &BlastSliceAsset::GetMeshIdList) - ->Method("SetMaterialId", &BlastSliceAsset::SetMaterialId) - ->Method("GetMaterialId", &BlastSliceAsset::GetMaterialId) - ->Method( - "GetAssetTypeId", - [](BlastSliceAsset*) - { - return azrtti_typeid(); - }); - } - } - -} // namespace Blast diff --git a/Gems/Blast/Code/Source/Asset/BlastSliceAsset.h b/Gems/Blast/Code/Source/Asset/BlastSliceAsset.h deleted file mode 100644 index cab51791cd..0000000000 --- a/Gems/Blast/Code/Source/Asset/BlastSliceAsset.h +++ /dev/null @@ -1,36 +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 - -namespace Blast -{ - //! The product asset file from a .blast_slice file product asset file - class BlastSliceAsset final : public AZ::Data::AssetData - { - public: - AZ_RTTI(BlastSliceAsset, "{D04AAF07-EB12-4E50-8964-114A9B9C1FD1}", AZ::Data::AssetData); - AZ_CLASS_ALLOCATOR(BlastSliceAsset, AZ::SystemAllocator, 0); - - BlastSliceAsset() = default; - ~BlastSliceAsset() override = default; - - void SetMeshIdList(const AZStd::vector& meshAssetIdList); - const AZStd::vector& GetMeshIdList() const; - - void SetMaterialId(const AZ::Data::AssetId& materialAssetId); - const AZ::Data::AssetId& GetMaterialId() const; - - static void Reflect(AZ::ReflectContext* context); - - private: - AZStd::vector m_meshAssetIdList; - AZ::Data::AssetId m_materialAssetId; - }; -} // namespace Blast diff --git a/Gems/Blast/Code/Source/BlastModule.cpp b/Gems/Blast/Code/Source/BlastModule.cpp index cbb93a86f7..70b3285d1d 100644 --- a/Gems/Blast/Code/Source/BlastModule.cpp +++ b/Gems/Blast/Code/Source/BlastModule.cpp @@ -16,7 +16,6 @@ #ifdef BLAST_EDITOR #include #include -#include #include #endif @@ -40,8 +39,7 @@ namespace Blast #ifdef BLAST_EDITOR EditorSystemComponent::CreateDescriptor(), EditorBlastFamilyComponent::CreateDescriptor(), - EditorBlastMeshDataComponent::CreateDescriptor(), - BlastSliceAssetStorageComponent::CreateDescriptor(), + EditorBlastMeshDataComponent::CreateDescriptor() #endif }); } diff --git a/Gems/Blast/Code/Source/Editor/EditorBlastChunksAssetHandler.cpp b/Gems/Blast/Code/Source/Editor/EditorBlastChunksAssetHandler.cpp new file mode 100644 index 0000000000..d3ae8222b1 --- /dev/null +++ b/Gems/Blast/Code/Source/Editor/EditorBlastChunksAssetHandler.cpp @@ -0,0 +1,144 @@ +/* + * 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 +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Blast +{ + // + // EditorBlastChunksAssetHandler + // + + EditorBlastChunksAssetHandler::~EditorBlastChunksAssetHandler() + { + Unregister(); + } + + AZ::Data::AssetPtr EditorBlastChunksAssetHandler::CreateAsset(const AZ::Data::AssetId& id, const AZ::Data::AssetType& type) + { + if (type != GetAssetType()) + { + AZ_Error("Blast", type == GetAssetType(), "Invalid asset type! We only handle 'BlastChunksAsset'"); + return {}; + } + + if (!CanHandleAsset(id)) + { + return nullptr; + } + + return aznew BlastChunksAsset; + } + + AZ::Data::AssetHandler::LoadResult EditorBlastChunksAssetHandler::LoadAssetData( + const AZ::Data::Asset& asset, + AZStd::shared_ptr stream, + [[maybe_unused]] const AZ::Data::AssetFilterCB& assetLoadFilterCB) + { + BlastChunksAsset* blastChunksAsset = asset.GetAs(); + AZ_Error("blast", blastChunksAsset, + "This should be a BlastChunksAsset type, as this is the only type we process!"); + if (!blastChunksAsset) + { + return LoadResult::Error; + } + + // get all products from the source scene asset + bool found = false; + AZStd::vector productsAssetInfo; + AzToolsFramework::AssetSystemRequestBus::BroadcastResult( + found, + &AzToolsFramework::AssetSystemRequestBus::Events::GetAssetsProducedBySourceUUID, + asset.Get()->GetId().m_guid, + productsAssetInfo); + + if (!found) + { + AZ_Error("blast", + found, + "Could not find asset models produced by source asset ID %s, verify the output product model assets.", + asset.Get()->GetId().m_guid.ToString().c_str()); + return LoadResult::Error; + } + + // find all model assets + AZStd::vector modelAssetIdList; + for (const AZ::Data::AssetInfo& assetInfo : productsAssetInfo) + { + if (azrtti_typeid() == assetInfo.m_assetType) + { + modelAssetIdList.push_back(assetInfo.m_assetId); + } + } + blastChunksAsset->SetModelAssetIds(modelAssetIdList); + + return LoadResult::LoadComplete; + } + + void EditorBlastChunksAssetHandler::DestroyAsset(AZ::Data::AssetPtr ptr) + { + delete ptr; + } + + void EditorBlastChunksAssetHandler::GetHandledAssetTypes(AZStd::vector& assetTypes) + { + assetTypes.push_back(azrtti_typeid()); + } + + void EditorBlastChunksAssetHandler::Register() + { + AZ_Assert(AZ::Data::AssetManager::IsReady(), "Asset manager isn't ready!"); + AZ::Data::AssetManager::Instance().RegisterHandler(this, azrtti_typeid()); + AZ::AssetTypeInfoBus::Handler::BusConnect(azrtti_typeid()); + } + + void EditorBlastChunksAssetHandler::Unregister() + { + AZ::AssetTypeInfoBus::Handler::BusDisconnect(azrtti_typeid()); + if (AZ::Data::AssetManager::IsReady()) + { + AZ::Data::AssetManager::Instance().UnregisterHandler(this); + } + } + + AZ::Data::AssetType EditorBlastChunksAssetHandler::GetAssetType() const + { + return azrtti_typeid(); + } + + const char* EditorBlastChunksAssetHandler::GetAssetTypeDisplayName() const + { + return "Blast Chunks Asset"; + } + + const char* EditorBlastChunksAssetHandler::GetGroup() const + { + return "Blast"; + } + + const char* EditorBlastChunksAssetHandler::GetBrowserIcon() const + { + return "Icons/Components/Box.png"; + } + + void EditorBlastChunksAssetHandler::GetAssetTypeExtensions(AZStd::vector& extensions) + { + extensions.push_back("blast_chunks"); + } + +} // namespace Blast diff --git a/Gems/Blast/Code/Source/Editor/EditorBlastChunksAssetHandler.h b/Gems/Blast/Code/Source/Editor/EditorBlastChunksAssetHandler.h new file mode 100644 index 0000000000..baeb26a1ff --- /dev/null +++ b/Gems/Blast/Code/Source/Editor/EditorBlastChunksAssetHandler.h @@ -0,0 +1,46 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace Blast +{ + class EditorBlastChunksAssetHandler final + : public AZ::Data::AssetHandler + , public AZ::AssetTypeInfoBus::Handler + { + public: + AZ_CLASS_ALLOCATOR(EditorBlastChunksAssetHandler, AZ::SystemAllocator, 0); + + ~EditorBlastChunksAssetHandler() override; + + // AZ::Data::AssetHandler + AZ::Data::AssetPtr CreateAsset(const AZ::Data::AssetId& id, const AZ::Data::AssetType& type) override; + LoadResult LoadAssetData( + const AZ::Data::Asset& asset, + AZStd::shared_ptr stream, + const AZ::Data::AssetFilterCB& assetLoadFilterCB) override; + void DestroyAsset(AZ::Data::AssetPtr ptr) override; + void GetHandledAssetTypes(AZStd::vector& assetTypes) override; + + // AZ::AssetTypeInfoBus::Handler + AZ::Data::AssetType GetAssetType() const override; + const char* GetAssetTypeDisplayName() const override; + const char* GetGroup() const override; + const char* GetBrowserIcon() const override; + void GetAssetTypeExtensions(AZStd::vector& extensions) override; + + void Register(); + void Unregister(); + }; +} // namespace Blast diff --git a/Gems/Blast/Code/Source/Editor/EditorBlastMeshDataComponent.cpp b/Gems/Blast/Code/Source/Editor/EditorBlastMeshDataComponent.cpp index 3c29d1498e..27e57a569e 100644 --- a/Gems/Blast/Code/Source/Editor/EditorBlastMeshDataComponent.cpp +++ b/Gems/Blast/Code/Source/Editor/EditorBlastMeshDataComponent.cpp @@ -45,10 +45,10 @@ namespace Blast if (AZ::SerializeContext* serialize = azrtti_cast(context)) { serialize->Class() - ->Version(4) + ->Version(5) ->Field("Show Mesh Assets", &EditorBlastMeshDataComponent::m_showMeshAssets) ->Field("Mesh Assets", &EditorBlastMeshDataComponent::m_meshAssets) - ->Field("Blast Slice", &EditorBlastMeshDataComponent::m_blastSliceAsset); + ->Field("Blast Chunks", &EditorBlastMeshDataComponent::m_blastChunksAsset); if (AZ::EditContext* ec = serialize->GetEditContext()) { @@ -77,9 +77,9 @@ namespace Blast ->Attribute(AZ::Edit::Attributes::AutoExpand, false) ->Attribute(AZ::Edit::Attributes::ChangeNotify, &EditorBlastMeshDataComponent::OnMeshAssetsChanged) ->DataElement( - AZ::Edit::UIHandlers::Default, &EditorBlastMeshDataComponent::m_blastSliceAsset, "Blast Slice", - "Slice override to fill out meshes and material") - ->Attribute(AZ::Edit::Attributes::ChangeNotify, &EditorBlastMeshDataComponent::OnSliceAssetChanged); + AZ::Edit::UIHandlers::Default, &EditorBlastMeshDataComponent::m_blastChunksAsset, "Blast Chunks", + "Manifest override to fill out meshes and material") + ->Attribute(AZ::Edit::Attributes::ChangeNotify, &EditorBlastMeshDataComponent::OnBlastChunksAssetChanged); } } } @@ -107,23 +107,27 @@ namespace Blast UnregisterModel(); } - void EditorBlastMeshDataComponent::OnSliceAssetChanged() + void EditorBlastMeshDataComponent::OnBlastChunksAssetChanged() { - if (!m_blastSliceAsset.GetId().IsValid()) + if (!m_blastChunksAsset.GetId().IsValid()) { return; } using namespace AZ::Data; + const AssetId blastAssetId = m_blastChunksAsset.GetId(); + m_blastChunksAsset = AssetManager::Instance().GetAsset(blastAssetId, AssetLoadBehavior::QueueLoad); + m_blastChunksAsset.BlockUntilLoadComplete(); - const AssetId blastAssetId = m_blastSliceAsset.GetId(); - m_blastSliceAsset = - AssetManager::Instance().GetAsset(blastAssetId, AssetLoadBehavior::QueueLoad); - m_blastSliceAsset.BlockUntilLoadComplete(); + if (!m_blastChunksAsset.Get() || m_blastChunksAsset.Get()->GetModelAssetIds().empty()) + { + AZ_Warning("blast", false, "Blast Chunk Asset does not contain any models.") + return; + } // load up the new mesh list m_meshAssets.clear(); - for (const auto& meshId : m_blastSliceAsset.Get()->GetMeshIdList()) + for (const auto& meshId : m_blastChunksAsset.Get()->GetModelAssetIds()) { auto meshAsset = AssetManager::Instance().GetAsset(meshId, AssetLoadBehavior::QueueLoad); if (meshAsset) @@ -135,8 +139,8 @@ namespace Blast UnregisterModel(); RegisterModel(); - AzToolsFramework::ToolsApplicationEvents::Bus::Broadcast( - &AzToolsFramework::ToolsApplicationEvents::InvalidatePropertyDisplay, AzToolsFramework::Refresh_EntireTree); + using namespace AzToolsFramework; + ToolsApplicationEvents::Bus::Broadcast(&ToolsApplicationEvents::InvalidatePropertyDisplay, Refresh_EntireTree); } void EditorBlastMeshDataComponent::OnMeshAssetsChanged() @@ -205,9 +209,9 @@ namespace Blast gameEntity->CreateComponent(m_meshAssets); } - const AZ::Data::Asset& EditorBlastMeshDataComponent::GetBlastSliceAsset() const + const AZ::Data::Asset& EditorBlastMeshDataComponent::GetBlastChunksAsset() const { - return m_blastSliceAsset; + return m_blastChunksAsset; } const AZStd::vector>& EditorBlastMeshDataComponent::GetMeshAssets() const diff --git a/Gems/Blast/Code/Source/Editor/EditorBlastMeshDataComponent.h b/Gems/Blast/Code/Source/Editor/EditorBlastMeshDataComponent.h index 81818d3bf7..aed5155be7 100644 --- a/Gems/Blast/Code/Source/Editor/EditorBlastMeshDataComponent.h +++ b/Gems/Blast/Code/Source/Editor/EditorBlastMeshDataComponent.h @@ -7,7 +7,7 @@ */ #pragma once -#include +#include #include #include #include @@ -43,14 +43,14 @@ namespace Blast // EditorComponentBase void BuildGameEntity(AZ::Entity* gameEntity) override; - const AZ::Data::Asset& GetBlastSliceAsset() const; + const AZ::Data::Asset& GetBlastChunksAsset() const; const AZStd::vector>& GetMeshAssets() const; void OnMaterialsUpdated(const AZ::Render::MaterialAssignmentMap& materials) override; void OnTransformChanged(const AZ::Transform& local, const AZ::Transform& world) override; private: - void OnSliceAssetChanged(); + void OnBlastChunksAssetChanged(); void OnMeshAssetsChanged(); AZ::Crc32 GetMeshAssetsVisibility() const; void OnMeshAssetsVisibilityChanged(); @@ -62,7 +62,7 @@ namespace Blast ////////////////////////////////////////////////////////////////////////// // Reflected data bool m_showMeshAssets = false; - AZ::Data::Asset m_blastSliceAsset; + AZ::Data::Asset m_blastChunksAsset; AZStd::vector> m_meshAssets; ////////////////////////////////////////////////////////////////////////// diff --git a/Gems/Blast/Code/Source/Editor/EditorBlastSliceAssetHandler.cpp b/Gems/Blast/Code/Source/Editor/EditorBlastSliceAssetHandler.cpp deleted file mode 100644 index 52df273254..0000000000 --- a/Gems/Blast/Code/Source/Editor/EditorBlastSliceAssetHandler.cpp +++ /dev/null @@ -1,345 +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 -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#include - -namespace Blast -{ - // BlastSliceAssetStorageComponent - - void BlastSliceAssetStorageComponent::Reflect(AZ::ReflectContext* context) - { - using namespace AZ::Edit; - - if (AZ::SerializeContext* serialize = azrtti_cast(context)) - { - serialize->Class() - ->Version(2) - ->Field("Mesh Data", &BlastSliceAssetStorageComponent::m_meshAssetIdList) - ->Field("Mesh Path List", &BlastSliceAssetStorageComponent::m_meshAssetPathList); - - if (AZ::EditContext* ec = serialize->GetEditContext()) - { - ec->Class( - "Blast Slice Storage Component", "Used process blast slice data") - ->ClassElement(AZ::Edit::ClassElements::EditorData, "") - ->Attribute(AZ::Edit::Attributes::Category, "Physics") - ->Attribute(AZ::Edit::Attributes::Icon, "Icons/Components/Box.png") - ->Attribute(AZ::Edit::Attributes::ViewportIcon, "Icons/Components/Viewport/Box.png") - ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC_CE("Game")) - ->Attribute(AZ::Edit::Attributes::AutoExpand, true) - ->Attribute(AZ::Edit::Attributes::AddableByUser, false) - ->DataElement( - AZ::Edit::UIHandlers::Default, &BlastSliceAssetStorageComponent::m_meshAssetIdList, "Mesh Data", - "Slice data to fill out the mesh list") - ->DataElement( - AZ::Edit::UIHandlers::Default, &BlastSliceAssetStorageComponent::m_meshAssetPathList, - "Mesh Paths", "The mesh path list"); - } - } - - if (AZ::BehaviorContext* behavior = azrtti_cast(context)) - { - behavior->Class("BlastSliceAssetStorageComponent") - ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation) - ->Attribute(AZ::Script::Attributes::Module, "blast") - ->Method("GenerateAssetInfo", &BlastSliceAssetStorageComponent::GenerateAssetInfo) - ->Method("WriteMaterialFile", &BlastSliceAssetStorageComponent::WriteMaterialFile); - } - } - - bool BlastSliceAssetStorageComponent::GenerateAssetInfo( - const AZStd::vector& chunkNames, AZStd::string_view blastFilename, - AZStd::string_view assetinfoFilename) - { - AZ::SerializeContext* serializeContext = nullptr; - AZ::ComponentApplicationBus::BroadcastResult( - serializeContext, &AZ::ComponentApplicationBus::Events::GetSerializeContext); - if (serializeContext == nullptr) - { - return false; - } - using namespace AZ::SceneAPI::Containers; - using namespace AZ::SceneAPI::SceneData; - - AZStd::string filename; - AZ::StringFunc::Path::Split(blastFilename.data(), nullptr, nullptr, &filename, nullptr); - - AZStd::any sceneManifestPointer(serializeContext->CreateAny(azrtti_typeid())); - SceneManifest* sceneManifest = AZStd::any_cast(&sceneManifestPointer); - - AZStd::vector meshGroupData; - meshGroupData.reserve(chunkNames.size()); - - AZStd::vector materialRuleData; - materialRuleData.reserve(chunkNames.size()); - - for (const AZStd::string& chunkName : chunkNames) - { - meshGroupData.emplace_back(serializeContext->CreateAny(azrtti_typeid())); - AZStd::any& meshGroupPointer = meshGroupData.back(); - MeshGroup* meshGroup = AZStd::any_cast(&meshGroupPointer); - - // make selection list - meshGroup->GetSceneNodeSelectionList().RemoveSelectedNode("RootNode"); - for (const AZStd::string& node : chunkNames) - { - meshGroup->GetSceneNodeSelectionList().RemoveSelectedNode( - AZStd::string::format("RootNode.%s", node.c_str())); - } - meshGroup->GetSceneNodeSelectionList().AddSelectedNode( - AZStd::string::format("RootNode.%s", chunkName.c_str())); - - // create a default material for the mesh group - materialRuleData.emplace_back(serializeContext->CreateAny(azrtti_typeid())); - AZStd::any& materialRulePointer = materialRuleData.back(); - MaterialRule* materialRule = AZStd::any_cast(&materialRulePointer); - - // override the deleter since the AZStd::any will clean up later on - AZStd::shared_ptr materialRuleEntry = AZStd::shared_ptr( - materialRule, - [](auto) - { - }); - meshGroup->GetRuleContainer().AddRule(materialRuleEntry); - - // construct the asset name for the chunk's mesh group - AZStd::string meshGroupName(filename); - meshGroupName.append("-"); - meshGroupName.append(chunkName); - // TODO: Uncomment lines below as part of SPEC-3542 - // meshGroup->OverrideId(AZ::Uuid::CreateName(meshGroupName.c_str())); - // meshGroup->SetName(AZStd::move(meshGroupName)); - - // override the deleter since the AZStd::any will clean up later on - AZStd::shared_ptr meshGroupEntry = AZStd::shared_ptr( - meshGroup, - [](auto) - { - }); - sceneManifest->AddEntry(AZStd::move(meshGroupEntry)); - } - - return sceneManifest->SaveToFile(assetinfoFilename.data()); - } - - bool BlastSliceAssetStorageComponent::WriteMaterialFile( - AZStd::string_view materialGroupName, const AZStd::vector& materialNames, - AZStd::string_view materialFilename) - { - AZ::GFxFramework::MaterialGroup group; - for (const auto& texture : materialNames) - { - auto mat = AZStd::make_shared(); - mat->SetName(texture); - mat->SetTexture(AZ::GFxFramework::TextureMapType::Diffuse, "EngineAssets/Textures/white.dds"); - group.AddMaterial(mat); - } - group.SetMtlName(materialGroupName); - return group.WriteMtlFile(materialFilename.data()); - } - - // - // EditorBlastSliceAssetHandler - // - - EditorBlastSliceAssetHandler::~EditorBlastSliceAssetHandler() - { - Unregister(); - } - - AZ::Data::AssetPtr EditorBlastSliceAssetHandler::CreateAsset( - const AZ::Data::AssetId& id, const AZ::Data::AssetType& type) - { - if (type != GetAssetType()) - { - AZ_Error("Blast", type == GetAssetType(), "Invalid asset type! We only handle 'BlastAsset'"); - return {}; - } - - if (!CanHandleAsset(id)) - { - return nullptr; - } - - return aznew BlastSliceAsset; - } - - AZ::Data::AssetHandler::LoadResult EditorBlastSliceAssetHandler::LoadAssetData( - const AZ::Data::Asset& asset, AZStd::shared_ptr stream, - const AZ::Data::AssetFilterCB& assetLoadFilterCB) - { - BlastSliceAsset* blastSliceAssetData = asset.GetAs(); - AZ_Error( - "blast", blastSliceAssetData, - "This should be a BlastSliceAsset type, as this is the only type we process!"); - AZ::SerializeContext* serializeContext = nullptr; - AZ::ComponentApplicationBus::BroadcastResult( - serializeContext, &AZ::ComponentApplicationBus::Events::GetSerializeContext); - if (blastSliceAssetData && serializeContext) - { - AZ::ObjectStream::FilterDescriptor filter(assetLoadFilterCB); - AZStd::unique_ptr baseEntity( - AZ::Utils::LoadObjectFromStream(*stream, serializeContext, filter)); - AZ_Error("Blast", baseEntity, "Could not load slice root entity {asset id}"); - if (!baseEntity) - { - return LoadResult::Error; - } - - auto&& sliceComponent = baseEntity->FindComponent(); - AZ_Error("Blast", sliceComponent, "blast_slice entity missing SliceComponent!"); - if (sliceComponent == nullptr) - { - return LoadResult::Error; - } - - AZStd::vector enityList; - sliceComponent->GetEntities(enityList); - for (auto&& entity : enityList) - { - // the base element type to store Blast mesh data is the BlastSliceAssetStorageComponent - auto&& blastSliceAssetStorage = entity->FindComponent(); - if (blastSliceAssetStorage) - { - if (blastSliceAssetStorage->GetMeshData().empty() == false) - { - blastSliceAssetData->SetMeshIdList(blastSliceAssetStorage->GetMeshData()); - return LoadResult::LoadComplete; - } - else if (blastSliceAssetStorage->GetMeshPathList().empty() == false) - { - AZStd::vector meshAssetIdList; - meshAssetIdList.reserve(blastSliceAssetStorage->GetMeshPathList().size()); - - for (auto&& assetPath : blastSliceAssetStorage->GetMeshPathList()) - { - AZ::Data::AssetId meshAssetId; - AZ::Data::AssetCatalogRequestBus::BroadcastResult( - meshAssetId, &AZ::Data::AssetCatalogRequestBus::Events::GetAssetIdByPath, - assetPath.c_str(), AZ::Data::s_invalidAssetType, false); - - if (meshAssetId.IsValid()) - { - meshAssetIdList.emplace_back(meshAssetId); - } - } - blastSliceAssetData->SetMeshIdList(meshAssetIdList); - return LoadResult::LoadComplete; - } - } - - // back up logic to load blast data for the EditorBlastMeshDataComponent - auto&& meshDataComponent = entity->FindComponent(); - if (meshDataComponent) - { - auto&& innerBlastSliceAsset = meshDataComponent->GetBlastSliceAsset(); - if (innerBlastSliceAsset.IsReady()) - { - blastSliceAssetData->SetMeshIdList(innerBlastSliceAsset.Get()->GetMeshIdList()); - blastSliceAssetData->SetMaterialId(innerBlastSliceAsset.Get()->GetMaterialId()); - return LoadResult::LoadComplete; - } - else - { - auto&& meshDataList = meshDataComponent->GetMeshAssets(); - AZStd::vector meshAssetIdList; - meshAssetIdList.reserve(meshDataList.size()); - for (auto&& meshData : meshDataList) - { - AZ::RPI::ModelAsset* meshAsset = meshData.Get(); - if (meshAsset) - { - meshAssetIdList.push_back(meshAsset->GetId()); - } - } - blastSliceAssetData->SetMeshIdList(meshAssetIdList); - return LoadResult::LoadComplete; - } - } - } - AZ_Error( - "Blast", false, "blast_slice assetId:%s missing EditorBlastMeshDataComponent!", - asset->GetId().ToString().c_str()); - } - return LoadResult::Error; - } - - void EditorBlastSliceAssetHandler::DestroyAsset(AZ::Data::AssetPtr ptr) - { - delete ptr; - } - - void EditorBlastSliceAssetHandler::GetHandledAssetTypes(AZStd::vector& assetTypes) - { - assetTypes.push_back(azrtti_typeid()); - } - - void EditorBlastSliceAssetHandler::Register() - { - AZ_Assert(AZ::Data::AssetManager::IsReady(), "Asset manager isn't ready!"); - AZ::Data::AssetManager::Instance().RegisterHandler(this, azrtti_typeid()); - AZ::AssetTypeInfoBus::Handler::BusConnect(azrtti_typeid()); - } - - void EditorBlastSliceAssetHandler::Unregister() - { - AZ::AssetTypeInfoBus::Handler::BusDisconnect(azrtti_typeid()); - if (AZ::Data::AssetManager::IsReady()) - { - AZ::Data::AssetManager::Instance().UnregisterHandler(this); - } - } - - AZ::Data::AssetType EditorBlastSliceAssetHandler::GetAssetType() const - { - return azrtti_typeid(); - } - - const char* EditorBlastSliceAssetHandler::GetAssetTypeDisplayName() const - { - return "Blast Slice Asset"; - } - - const char* EditorBlastSliceAssetHandler::GetGroup() const - { - return "Blast"; - } - - const char* EditorBlastSliceAssetHandler::GetBrowserIcon() const - { - return "Icons/Components/Box.png"; - } - - void EditorBlastSliceAssetHandler::GetAssetTypeExtensions(AZStd::vector& extensions) - { - extensions.push_back("blast_slice"); - } - -} // namespace Blast diff --git a/Gems/Blast/Code/Source/Editor/EditorBlastSliceAssetHandler.h b/Gems/Blast/Code/Source/Editor/EditorBlastSliceAssetHandler.h deleted file mode 100644 index f33290f7f8..0000000000 --- a/Gems/Blast/Code/Source/Editor/EditorBlastSliceAssetHandler.h +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright (c) Contributors to the Open 3D Engine Project. - * For complete copyright and license terms please see the LICENSE at the root of this distribution. - * - * SPDX-License-Identifier: Apache-2.0 OR MIT - * - */ -#pragma once - -#include -#include -#include -#include -#include -#include - -namespace Blast -{ - //! Used to create store asset references (i.e. ids) to fill out the EditorBlastMeshDataComponent - class BlastSliceAssetStorageComponent final : public AzToolsFramework::Components::EditorComponentBase - { - public: - AZ_COMPONENT( - BlastSliceAssetStorageComponent, "{696C7E62-1EA4-41E2-B4F6-7BD0D30888DC}", - AzToolsFramework::Components::EditorComponentBase); - - ~BlastSliceAssetStorageComponent() override = default; - - static void Reflect(AZ::ReflectContext* context); - - const AZStd::vector& GetMeshData() const - { - return m_meshAssetIdList; - } - - void SetMeshData(const AZStd::vector& meshAssetIdList) - { - m_meshAssetIdList = meshAssetIdList; - } - - const AZStd::vector& GetMeshPathList() const - { - return m_meshAssetPathList; - } - - void SetMeshPathList(const AZStd::vector& meshAssetPathList) - { - m_meshAssetPathList = meshAssetPathList; - } - - private: - // AZ::Component interface implementation - void Activate() override {} - void Deactivate() override {} - - // EditorComponentBase - void BuildGameEntity([[maybe_unused]] AZ::Entity* gameEntity) override {} - - // Script API - bool GenerateAssetInfo( - const AZStd::vector& chunkNames, - AZStd::string_view blastFilename, - AZStd::string_view assetinfoFilename); - - bool WriteMaterialFile( - AZStd::string_view materialGroupName, - const AZStd::vector& materialNames, - AZStd::string_view materialFilename); - - AZStd::vector m_meshAssetIdList; - AZStd::vector m_meshAssetPathList; - }; - - class EditorBlastSliceAssetHandler final - : public AZ::Data::AssetHandler - , public AZ::AssetTypeInfoBus::Handler - { - public: - AZ_CLASS_ALLOCATOR(EditorBlastSliceAssetHandler, AZ::SystemAllocator, 0); - - ~EditorBlastSliceAssetHandler() override; - - // AZ::Data::AssetHandler - AZ::Data::AssetPtr CreateAsset(const AZ::Data::AssetId& id, const AZ::Data::AssetType& type) override; - LoadResult LoadAssetData( - const AZ::Data::Asset& asset, AZStd::shared_ptr stream, - const AZ::Data::AssetFilterCB& assetLoadFilterCB) override; - void DestroyAsset(AZ::Data::AssetPtr ptr) override; - void GetHandledAssetTypes(AZStd::vector& assetTypes) override; - - // AZ::AssetTypeInfoBus::Handler - AZ::Data::AssetType GetAssetType() const override; - const char* GetAssetTypeDisplayName() const override; - const char* GetGroup() const override; - const char* GetBrowserIcon() const override; - void GetAssetTypeExtensions(AZStd::vector& extensions) override; - - void Register(); - void Unregister(); - }; -} // namespace Blast diff --git a/Gems/Blast/Code/Source/Editor/EditorSystemComponent.cpp b/Gems/Blast/Code/Source/Editor/EditorSystemComponent.cpp index d8d703e51f..dc1cb0fc98 100644 --- a/Gems/Blast/Code/Source/Editor/EditorSystemComponent.cpp +++ b/Gems/Blast/Code/Source/Editor/EditorSystemComponent.cpp @@ -6,7 +6,7 @@ * */ -#include +#include #include #include #include @@ -16,7 +16,7 @@ namespace Blast { void EditorSystemComponent::Reflect(AZ::ReflectContext* context) { - BlastSliceAsset::Reflect(context); + BlastChunksAsset::Reflect(context); if (auto serializeContext = azrtti_cast(context)) { @@ -26,14 +26,14 @@ namespace Blast void EditorSystemComponent::Activate() { - m_editorBlastSliceAssetHandler = AZStd::make_unique(); - m_editorBlastSliceAssetHandler->Register(); + m_editorBlastChunksAssetHandler = AZStd::make_unique(); + m_editorBlastChunksAssetHandler->Register(); auto assetCatalog = AZ::Data::AssetCatalogRequestBus::FindFirstHandler(); if (assetCatalog) { - assetCatalog->EnableCatalogForAsset(azrtti_typeid()); - assetCatalog->AddExtension("blast_slice"); + assetCatalog->EnableCatalogForAsset(azrtti_typeid()); + assetCatalog->AddExtension("blast_chunks"); } AzToolsFramework::EditorEvents::Bus::Handler::BusConnect(); @@ -46,7 +46,7 @@ namespace Blast void EditorSystemComponent::Deactivate() { AzToolsFramework::EditorEvents::Bus::Handler::BusDisconnect(); - m_editorBlastSliceAssetHandler.reset(); + m_editorBlastChunksAssetHandler.reset(); } // This will be called when the IEditor instance is ready diff --git a/Gems/Blast/Code/Source/Editor/EditorSystemComponent.h b/Gems/Blast/Code/Source/Editor/EditorSystemComponent.h index 737a7d968d..31daae1a18 100644 --- a/Gems/Blast/Code/Source/Editor/EditorSystemComponent.h +++ b/Gems/Blast/Code/Source/Editor/EditorSystemComponent.h @@ -11,7 +11,7 @@ #include #include #include -#include +#include namespace Blast { @@ -39,7 +39,7 @@ namespace Blast required.push_back(AZ_CRC("BlastService", 0x75beae2d)); } - AZStd::unique_ptr m_editorBlastSliceAssetHandler; + AZStd::unique_ptr m_editorBlastChunksAssetHandler; // AZ::Component void Activate() override; diff --git a/Gems/Blast/Code/Tests/Editor/EditorBlastChunksAssetHandlerTest.cpp b/Gems/Blast/Code/Tests/Editor/EditorBlastChunksAssetHandlerTest.cpp new file mode 100644 index 0000000000..9f48cae4da --- /dev/null +++ b/Gems/Blast/Code/Tests/Editor/EditorBlastChunksAssetHandlerTest.cpp @@ -0,0 +1,207 @@ +/* + * 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 +#include +#include + +#include +#include +#include + +#include + +namespace UnitTest +{ + MockComponentApplication::MockComponentApplication() + { + AZ::ComponentApplicationBus::Handler::BusConnect(); + AZ::Interface::Register(this); + } + + MockComponentApplication::~MockComponentApplication() + { + AZ::Interface::Unregister(this); + AZ::ComponentApplicationBus::Handler::BusDisconnect(); + } + + class MockAssetCatalogRequestBusHandler final + : public AZ::Data::AssetCatalogRequestBus::Handler + { + public: + MockAssetCatalogRequestBusHandler() + { + AZ::Data::AssetCatalogRequestBus::Handler::BusConnect(); + } + + virtual ~MockAssetCatalogRequestBusHandler() + { + AZ::Data::AssetCatalogRequestBus::Handler::BusDisconnect(); + } + + MOCK_METHOD3(GetAssetIdByPath, AZ::Data::AssetId(const char*, const AZ::Data::AssetType&, bool)); + MOCK_METHOD1(GetAssetInfoById, AZ::Data::AssetInfo(const AZ::Data::AssetId&)); + MOCK_METHOD1(AddAssetType, void(const AZ::Data::AssetType&)); + MOCK_METHOD1(AddDeltaCatalog, bool(AZStd::shared_ptr)); + MOCK_METHOD1(AddExtension, void(const char*)); + MOCK_METHOD0(ClearCatalog, void()); + MOCK_METHOD5(CreateBundleManifest, bool(const AZStd::string&, const AZStd::vector&, const AZStd::string&, int, const AZStd::vector&)); + MOCK_METHOD2(CreateDeltaCatalog, bool(const AZStd::vector&, const AZStd::string&)); + MOCK_METHOD0(DisableCatalog, void()); + MOCK_METHOD1(EnableCatalogForAsset, void(const AZ::Data::AssetType&)); + MOCK_METHOD3(EnumerateAssets, void(BeginAssetEnumerationCB, AssetEnumerationCB, EndAssetEnumerationCB)); + MOCK_METHOD1(GenerateAssetIdTEMP, AZ::Data::AssetId(const char*)); + MOCK_METHOD1(GetAllProductDependencies, AZ::Outcome, AZStd::string>(const AZ::Data::AssetId&)); + MOCK_METHOD3(GetAllProductDependenciesFilter, AZ::Outcome, AZStd::string>(const AZ::Data::AssetId&, const AZStd::unordered_set&, const AZStd::vector&)); + MOCK_METHOD1(GetAssetPathById, AZStd::string(const AZ::Data::AssetId&)); + MOCK_METHOD1(GetDirectProductDependencies, AZ::Outcome, AZStd::string>(const AZ::Data::AssetId&)); + MOCK_METHOD1(GetHandledAssetTypes, void(AZStd::vector&)); + MOCK_METHOD0(GetRegisteredAssetPaths, AZStd::vector()); + MOCK_METHOD2(InsertDeltaCatalog, bool(AZStd::shared_ptr, size_t)); + MOCK_METHOD2(InsertDeltaCatalogBefore, bool(AZStd::shared_ptr, AZStd::shared_ptr)); + MOCK_METHOD1(LoadCatalog, bool(const char*)); + MOCK_METHOD2(RegisterAsset, void(const AZ::Data::AssetId&, AZ::Data::AssetInfo&)); + MOCK_METHOD1(RemoveDeltaCatalog, bool(AZStd::shared_ptr)); + MOCK_METHOD1(SaveCatalog, bool(const char*)); + MOCK_METHOD0(StartMonitoringAssets, void()); + MOCK_METHOD0(StopMonitoringAssets, void()); + MOCK_METHOD1(UnregisterAsset, void(const AZ::Data::AssetId&)); + }; + + class MockAssetManager + : public AZ::Data::AssetManager + { + public: + MockAssetManager(const AZ::Data::AssetManager::Descriptor& desc) : + AssetManager(desc) + { + } + }; + + class EditorBlastChunkAssetHandlerTestFixture + : public AllocatorsTestFixture + { + public: + AZStd::unique_ptr m_mockComponentApplicationBusHandler; + AZStd::unique_ptr m_mockAssetCatalogRequestBusHandler; + AZStd::unique_ptr m_mockAssetManager; + AZStd::unique_ptr m_serializeContext; + + void SetUpChunkComponents() + { + m_serializeContext = AZStd::make_unique(); + + AZ::Entity::Reflect(m_serializeContext.get()); + AzToolsFramework::Components::EditorComponentBase::Reflect(m_serializeContext.get()); + } + + void TearDownChunkComponents() + { + m_serializeContext.reset(); + } + + void SetUp() override final + { + AllocatorsTestFixture::SetUp(); + AZ::AllocatorInstance::Create(); + AZ::AllocatorInstance::Create(); + + m_mockComponentApplicationBusHandler = AZStd::make_unique(); + m_mockAssetCatalogRequestBusHandler = AZStd::make_unique(); + m_mockAssetManager = AZStd::make_unique(AZ::Data::AssetManager::Descriptor{}); + + AZ::Data::AssetManager::SetInstance(m_mockAssetManager.get()); + } + + void TearDown() override final + { + m_mockAssetManager.release(); + AZ::Data::AssetManager::Destroy(); + + m_mockAssetCatalogRequestBusHandler.reset(); + m_mockComponentApplicationBusHandler.reset(); + + AZ::AllocatorInstance::Destroy(); + AZ::AllocatorInstance::Destroy(); + AllocatorsTestFixture::TearDown(); + } + + void SaveChunkAssetToStream(AZ::Entity* chunkAssetEntity, AZStd::vector& buffer) + { + buffer.clear(); + AZ::IO::ByteContainerStream> stream(&buffer); + AZ::ObjectStream* objStream = AZ::ObjectStream::Create(&stream, *m_serializeContext.get(), AZ::ObjectStream::ST_XML); + objStream->WriteClass(chunkAssetEntity); + EXPECT_TRUE(objStream->Finalize()); + } + }; + + TEST_F(EditorBlastChunkAssetHandlerTestFixture, EditorBlastChunkAssetHandler_AssetManager_Registered) + { + Blast::EditorBlastChunksAssetHandler handler; + handler.Register(); + EXPECT_NE(nullptr, AZ::Data::AssetManager::Instance().GetHandler(azrtti_typeid())); + handler.Unregister(); + } + + TEST_F(EditorBlastChunkAssetHandlerTestFixture, EditorBlastChunkAssetHandler_AssetTypeInfoBus_Responds) + { + auto assetId = azrtti_typeid(); + + Blast::EditorBlastChunksAssetHandler handler; + handler.Register(); + + AZ::Data::AssetType assetType = AZ::Uuid::CreateNull(); + AZ::AssetTypeInfoBus::EventResult(assetType, assetId, &AZ::AssetTypeInfoBus::Events::GetAssetType); + EXPECT_NE(AZ::Uuid::CreateNull(), assetType); + + const char* displayName = nullptr; + AZ::AssetTypeInfoBus::EventResult(displayName, assetId, &AZ::AssetTypeInfoBus::Events::GetAssetTypeDisplayName); + EXPECT_STREQ("Blast Chunks Asset", displayName); + + const char* group = nullptr; + AZ::AssetTypeInfoBus::EventResult(group, assetId, &AZ::AssetTypeInfoBus::Events::GetGroup); + EXPECT_STREQ("Blast", group); + + const char* icon = nullptr; + AZ::AssetTypeInfoBus::EventResult(icon, assetId, &AZ::AssetTypeInfoBus::Events::GetBrowserIcon); + EXPECT_STREQ("Icons/Components/Box.png", icon); + + AZStd::vector extensions; + AZ::AssetTypeInfoBus::Event(assetId, &AZ::AssetTypeInfoBus::Events::GetAssetTypeExtensions, extensions); + ASSERT_EQ(1, extensions.size()); + ASSERT_EQ("blast_chunks", extensions[0]); + + handler.Unregister(); + } + + TEST_F(EditorBlastChunkAssetHandlerTestFixture, EditorBlastChunkAssetHandler_AssetHandler_Ready) + { + auto assetType = azrtti_typeid(); + auto&& assetManager = AZ::Data::AssetManager::Instance(); + + Blast::EditorBlastChunksAssetHandler handler; + handler.Register(); + EXPECT_EQ(&handler, assetManager.GetHandler(assetType)); + + // create and release an instance of the BlastChunkAsset asset type + { + using ::testing::Return; + using ::testing::_; + + EXPECT_CALL(*m_mockAssetCatalogRequestBusHandler, GetAssetInfoById(_)) + .Times(2) + .WillRepeatedly(Return(AZ::Data::AssetInfo{})); + + auto assetPtr = assetManager.CreateAsset(AZ::Data::AssetId(AZ::Uuid::CreateRandom(), 0)); + EXPECT_NE(nullptr, assetPtr.Get()); + EXPECT_EQ(azrtti_typeid(), assetPtr.GetType()); + } + + handler.Unregister(); + } + +} diff --git a/Gems/Blast/Code/Tests/Editor/EditorBlastSliceAssetHandlerTest.cpp b/Gems/Blast/Code/Tests/Editor/EditorBlastSliceAssetHandlerTest.cpp deleted file mode 100644 index 3df22d081f..0000000000 --- a/Gems/Blast/Code/Tests/Editor/EditorBlastSliceAssetHandlerTest.cpp +++ /dev/null @@ -1,377 +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 -#include -#include - -#include - -#include -#include -#include - -namespace UnitTest -{ - class MockComponentApplicationBusHandler final - //: public MockComponentApplication - : public AZ::ComponentApplicationBus::Handler - { - public: - MockComponentApplicationBusHandler() - { - AZ::ComponentApplicationBus::Handler::BusConnect(); - } - - virtual ~MockComponentApplicationBusHandler() - { - AZ::ComponentApplicationBus::Handler::BusDisconnect(); - } - - MOCK_METHOD0(Destroy, void()); - MOCK_METHOD1(RegisterComponentDescriptor, void(const AZ::ComponentDescriptor*)); - MOCK_METHOD1(UnregisterComponentDescriptor, void(const AZ::ComponentDescriptor*)); - MOCK_METHOD1(RemoveEntity, bool(AZ::Entity*)); - MOCK_METHOD1(DeleteEntity, bool(const AZ::EntityId&)); - MOCK_METHOD1(GetEntityName, AZStd::string(const AZ::EntityId&)); - MOCK_METHOD1(AddEntity, bool(AZ::Entity*)); - MOCK_METHOD1(FindEntity, AZ::Entity*(const AZ::EntityId&)); - MOCK_METHOD1(EnumerateEntities, void(const ComponentApplicationRequests::EntityCallback&)); - MOCK_METHOD0(GetApplication, AZ::ComponentApplication* ()); - MOCK_METHOD0(GetSerializeContext, AZ::SerializeContext* ()); - MOCK_METHOD0(GetBehaviorContext, AZ::BehaviorContext* ()); - MOCK_METHOD0(GetJsonRegistrationContext, AZ::JsonRegistrationContext* ()); - MOCK_METHOD0(GetAppRoot, const char* ()); - MOCK_CONST_METHOD0(GetExecutableFolder, const char* ()); - MOCK_METHOD0(GetDrillerManager, AZ::Debug::DrillerManager* ()); - MOCK_METHOD0(GetTickDeltaTime, float()); - MOCK_METHOD1(Tick, void(float)); - MOCK_METHOD0(TickSystem, void()); - MOCK_CONST_METHOD0(GetRequiredSystemComponents, AZ::ComponentTypeList()); - MOCK_METHOD1(ResolveModulePath, void(AZ::OSString&)); - MOCK_METHOD0(CreateSerializeContext, void()); - MOCK_METHOD0(DestroySerializeContext, void()); - MOCK_METHOD0(CreateBehaviorContext, void()); - MOCK_METHOD0(DestroyBehaviorContext, void()); - MOCK_METHOD0(RegisterCoreComponents, void()); - MOCK_METHOD1(AddSystemComponents, void(AZ::Entity*)); - MOCK_METHOD0(ReflectSerialize, void()); - MOCK_METHOD1(Reflect, void(AZ::ReflectContext*)); - MOCK_CONST_METHOD0(GetBinFolder, const char* ()); - }; - - class MockAssetCatalogRequestBusHandler final - : public AZ::Data::AssetCatalogRequestBus::Handler - { - public: - MockAssetCatalogRequestBusHandler() - { - AZ::Data::AssetCatalogRequestBus::Handler::BusConnect(); - } - - virtual ~MockAssetCatalogRequestBusHandler() - { - AZ::Data::AssetCatalogRequestBus::Handler::BusDisconnect(); - } - - MOCK_METHOD3(GetAssetIdByPath, AZ::Data::AssetId(const char*, const AZ::Data::AssetType&, bool)); - MOCK_METHOD1(GetAssetInfoById, AZ::Data::AssetInfo(const AZ::Data::AssetId&)); - MOCK_METHOD1(AddAssetType, void(const AZ::Data::AssetType&)); - MOCK_METHOD1(AddDeltaCatalog, bool(AZStd::shared_ptr)); - MOCK_METHOD1(AddExtension, void(const char*)); - MOCK_METHOD0(ClearCatalog, void()); - MOCK_METHOD5(CreateBundleManifest, bool(const AZStd::string&, const AZStd::vector&, const AZStd::string&, int, const AZStd::vector&)); - MOCK_METHOD2(CreateDeltaCatalog, bool(const AZStd::vector&, const AZStd::string&)); - MOCK_METHOD0(DisableCatalog, void()); - MOCK_METHOD1(EnableCatalogForAsset, void(const AZ::Data::AssetType&)); - MOCK_METHOD3(EnumerateAssets, void(BeginAssetEnumerationCB, AssetEnumerationCB, EndAssetEnumerationCB)); - MOCK_METHOD1(GenerateAssetIdTEMP, AZ::Data::AssetId(const char*)); - MOCK_METHOD1(GetAllProductDependencies, AZ::Outcome, AZStd::string>(const AZ::Data::AssetId&)); - MOCK_METHOD3(GetAllProductDependenciesFilter, AZ::Outcome, AZStd::string>(const AZ::Data::AssetId&, const AZStd::unordered_set&, const AZStd::vector&)); - MOCK_METHOD1(GetAssetPathById, AZStd::string(const AZ::Data::AssetId&)); - MOCK_METHOD1(GetDirectProductDependencies, AZ::Outcome, AZStd::string>(const AZ::Data::AssetId&)); - MOCK_METHOD1(GetHandledAssetTypes, void(AZStd::vector&)); - MOCK_METHOD0(GetRegisteredAssetPaths, AZStd::vector()); - MOCK_METHOD2(InsertDeltaCatalog, bool(AZStd::shared_ptr, size_t)); - MOCK_METHOD2(InsertDeltaCatalogBefore, bool(AZStd::shared_ptr, AZStd::shared_ptr)); - MOCK_METHOD1(LoadCatalog, bool(const char*)); - MOCK_METHOD2(RegisterAsset, void(const AZ::Data::AssetId&, AZ::Data::AssetInfo&)); - MOCK_METHOD1(RemoveDeltaCatalog, bool(AZStd::shared_ptr)); - MOCK_METHOD1(SaveCatalog, bool(const char*)); - MOCK_METHOD0(StartMonitoringAssets, void()); - MOCK_METHOD0(StopMonitoringAssets, void()); - MOCK_METHOD1(UnregisterAsset, void(const AZ::Data::AssetId&)); - }; - - class MockAssetManager - : public AZ::Data::AssetManager - { - public: - MockAssetManager(const AZ::Data::AssetManager::Descriptor& desc) : - AssetManager(desc) - { - } - }; - - class EditorBlastSliceAssetHandlerTestFixture - : public AllocatorsTestFixture - { - public: - AZStd::unique_ptr m_mockComponentApplicationBusHandler; - //AZStd::unique_ptr m_mockComponentApplicationBusHandler; - AZStd::unique_ptr m_mockAssetCatalogRequestBusHandler; - AZStd::unique_ptr m_mockAssetManager; - AZStd::unique_ptr m_serializeContext; - const AZ::ComponentDescriptor* m_sliceComponentDescriptor = nullptr; - - void SetUpSliceComponents() - { - m_serializeContext = AZStd::make_unique(); - - AZ::Entity::Reflect(m_serializeContext.get()); - Blast::BlastSliceAssetStorageComponent::Reflect(m_serializeContext.get()); - AzToolsFramework::Components::EditorComponentBase::Reflect(m_serializeContext.get()); - - m_sliceComponentDescriptor = AZ::SliceComponent::CreateDescriptor(); - m_sliceComponentDescriptor->Reflect(m_serializeContext.get()); - } - - void TearDownSliceComponents() - { - delete m_sliceComponentDescriptor; - m_serializeContext.reset(); - } - - void SetUp() override final - { - AllocatorsTestFixture::SetUp(); - AZ::AllocatorInstance::Create(); - AZ::AllocatorInstance::Create(); - - m_mockComponentApplicationBusHandler = AZStd::make_unique(); - m_mockAssetCatalogRequestBusHandler = AZStd::make_unique(); - m_mockAssetManager = AZStd::make_unique(AZ::Data::AssetManager::Descriptor{}); - - AZ::Data::AssetManager::SetInstance(m_mockAssetManager.get()); - } - - void TearDown() override final - { - AZ::Data::AssetManager::SetInstance(nullptr); - - m_mockAssetManager.reset(); - m_mockAssetCatalogRequestBusHandler.reset(); - m_mockComponentApplicationBusHandler.reset(); - - AZ::AllocatorInstance::Destroy(); - AZ::AllocatorInstance::Destroy(); - AllocatorsTestFixture::TearDown(); - } - - void SaveSliceAssetToStream(AZ::Entity* sliceAssetEntity, AZStd::vector& buffer) - { - buffer.clear(); - AZ::IO::ByteContainerStream> stream(&buffer); - AZ::ObjectStream* objStream = AZ::ObjectStream::Create(&stream, *m_serializeContext.get(), AZ::ObjectStream::ST_XML); - objStream->WriteClass(sliceAssetEntity); - EXPECT_TRUE(objStream->Finalize()); - } - }; - - TEST_F(EditorBlastSliceAssetHandlerTestFixture, EditorBlastSliceAssetHandler_AssetManager_Registered) - { - Blast::EditorBlastSliceAssetHandler handler; - handler.Register(); - EXPECT_NE(nullptr, AZ::Data::AssetManager::Instance().GetHandler(azrtti_typeid())); - handler.Unregister(); - } - - TEST_F(EditorBlastSliceAssetHandlerTestFixture, BlastSliceAssetStorageComponent_Behavior_Registered) - { - AZ::BehaviorContext behaviorContext; - Blast::BlastSliceAssetStorageComponent::Reflect(&behaviorContext); - - auto classEntry = behaviorContext.m_classes.find("BlastSliceAssetStorageComponent"); - EXPECT_NE(behaviorContext.m_classes.end(), classEntry); - AZ::BehaviorClass* behaviorClass = classEntry->second; - auto methodEntry = behaviorClass->m_methods.find("GenerateAssetInfo"); - EXPECT_NE(behaviorClass->m_methods.end(), methodEntry); - AZ::BehaviorMethod* behaviorMethod = methodEntry->second; - EXPECT_EQ(4, behaviorMethod->GetNumArguments()); - EXPECT_EQ(behaviorMethod->GetArgument(0)->m_typeId, azrtti_typeid()); - EXPECT_EQ(behaviorMethod->GetArgument(1)->m_typeId, azrtti_typeid>()); - EXPECT_EQ(behaviorMethod->GetArgument(2)->m_typeId, azrtti_typeid()); - EXPECT_EQ(behaviorMethod->GetArgument(3)->m_typeId, azrtti_typeid()); - } - - TEST_F(EditorBlastSliceAssetHandlerTestFixture, BlastSliceAsset_Behavior_Registered) - { - AZ::BehaviorContext behaviorContext; - Blast::BlastSliceAsset::Reflect(&behaviorContext); - - auto classEntry = behaviorContext.m_classes.find("BlastSliceAsset"); - EXPECT_NE(behaviorContext.m_classes.end(), classEntry); - AZ::BehaviorClass* behaviorClass = classEntry->second; - - auto setMeshIdListEntry = behaviorClass->m_methods.find("SetMeshIdList"); - EXPECT_NE(behaviorClass->m_methods.end(), setMeshIdListEntry); - { - AZ::BehaviorMethod* behaviorMethod = setMeshIdListEntry->second; - EXPECT_EQ(2, behaviorMethod->GetNumArguments()); - EXPECT_EQ(behaviorMethod->GetArgument(0)->m_typeId, azrtti_typeid()); - EXPECT_EQ(behaviorMethod->GetArgument(1)->m_typeId, azrtti_typeid>()); - } - - auto getMeshIdListEntry = behaviorClass->m_methods.find("GetMeshIdList"); - EXPECT_NE(behaviorClass->m_methods.end(), getMeshIdListEntry); - { - AZ::BehaviorMethod* behaviorMethod = getMeshIdListEntry->second; - EXPECT_EQ(1, behaviorMethod->GetNumArguments()); - EXPECT_EQ(behaviorMethod->GetArgument(0)->m_typeId, azrtti_typeid()); - EXPECT_EQ(behaviorMethod->GetResult()->m_typeId, azrtti_typeid>()); - } - - auto setMaterialIdEntry = behaviorClass->m_methods.find("SetMaterialId"); - EXPECT_NE(behaviorClass->m_methods.end(), setMaterialIdEntry); - { - AZ::BehaviorMethod* behaviorMethod = setMaterialIdEntry->second; - EXPECT_EQ(2, behaviorMethod->GetNumArguments()); - EXPECT_EQ(behaviorMethod->GetArgument(0)->m_typeId, azrtti_typeid()); - EXPECT_EQ(behaviorMethod->GetArgument(1)->m_typeId, azrtti_typeid()); - } - - auto getMaterialIdEntry = behaviorClass->m_methods.find("GetMaterialId"); - EXPECT_NE(behaviorClass->m_methods.end(), getMaterialIdEntry); - { - AZ::BehaviorMethod* behaviorMethod = getMaterialIdEntry->second; - EXPECT_EQ(1, behaviorMethod->GetNumArguments()); - EXPECT_EQ(behaviorMethod->GetArgument(0)->m_typeId, azrtti_typeid()); - EXPECT_EQ(behaviorMethod->GetResult()->m_typeId, azrtti_typeid()); - } - - auto getAssetTypeIdEntry = behaviorClass->m_methods.find("GetAssetTypeId"); - EXPECT_NE(behaviorClass->m_methods.end(), getAssetTypeIdEntry); - { - AZ::BehaviorMethod* behaviorMethod = getAssetTypeIdEntry->second; - EXPECT_EQ(1, behaviorMethod->GetNumArguments()); - EXPECT_EQ(behaviorMethod->GetArgument(0)->m_typeId, azrtti_typeid()); - EXPECT_EQ(behaviorMethod->GetResult()->m_typeId, azrtti_typeid()); - } - } - - TEST_F(EditorBlastSliceAssetHandlerTestFixture, EditorBlastSliceAssetHandler_AssetTypeInfoBus_Responds) - { - auto assetId = azrtti_typeid(); - - Blast::EditorBlastSliceAssetHandler handler; - handler.Register(); - - AZ::Data::AssetType assetType = AZ::Uuid::CreateNull(); - AZ::AssetTypeInfoBus::EventResult(assetType, assetId, &AZ::AssetTypeInfoBus::Events::GetAssetType); - EXPECT_NE(AZ::Uuid::CreateNull(), assetType); - - const char* displayName = nullptr; - AZ::AssetTypeInfoBus::EventResult(displayName, assetId, &AZ::AssetTypeInfoBus::Events::GetAssetTypeDisplayName); - EXPECT_STREQ("Blast Slice Asset", displayName); - - const char* group = nullptr; - AZ::AssetTypeInfoBus::EventResult(group, assetId, &AZ::AssetTypeInfoBus::Events::GetGroup); - EXPECT_STREQ("Blast", group); - - const char* icon = nullptr; - AZ::AssetTypeInfoBus::EventResult(icon, assetId, &AZ::AssetTypeInfoBus::Events::GetBrowserIcon); - EXPECT_STREQ("Editor/Icons/Components/Box.png", icon); - - AZStd::vector extensions; - AZ::AssetTypeInfoBus::Event(assetId, &AZ::AssetTypeInfoBus::Events::GetAssetTypeExtensions, extensions); - ASSERT_EQ(1, extensions.size()); - ASSERT_EQ("blast_slice", extensions[0]); - - handler.Unregister(); - } - - TEST_F(EditorBlastSliceAssetHandlerTestFixture, EditorBlastSliceAssetHandler_AssetHandler_Ready) - { - auto assetType = azrtti_typeid(); - auto&& assetManager = AZ::Data::AssetManager::Instance(); - - Blast::EditorBlastSliceAssetHandler handler; - handler.Register(); - EXPECT_EQ(&handler, assetManager.GetHandler(assetType)); - - // create and release an instance of the BlastSliceAsset asset type - { - using ::testing::Return; - using ::testing::_; - - EXPECT_CALL(*m_mockAssetCatalogRequestBusHandler, GetAssetInfoById(_)) - .Times(1) - .WillRepeatedly(Return(AZ::Data::AssetInfo{})); - - auto assetPtr = assetManager.CreateAsset(AZ::Data::AssetId(AZ::Uuid::CreateRandom(), 0)); - EXPECT_NE(nullptr, assetPtr.Get()); - EXPECT_EQ(azrtti_typeid(), assetPtr.GetType()); - } - - handler.Unregister(); - } - - TEST_F(EditorBlastSliceAssetHandlerTestFixture, EditorBlastSliceAssetHandler_AssetHandler_LoadsAssetData) - { - SetUpSliceComponents(); - - AZStd::vector meshAssetPathList = { "/foo/path/thing.cgf", "/foo/path/that.cgf" }; - AZ::Entity* storageEntity = aznew AZ::Entity(); - auto* blastStorage = storageEntity->CreateComponent(); - blastStorage->SetMeshPathList(meshAssetPathList); - - AZ::Entity sliceEntity; - AZ::SliceComponent* slice = sliceEntity.CreateComponent(); - slice->AddEntity(storageEntity); - - AZStd::vector buffer; - SaveSliceAssetToStream(&sliceEntity, buffer); - - // Load a slice with the BlastSliceAssetStorageComponent - Blast::EditorBlastSliceAssetHandler handler; - handler.Register(); - { - using ::testing::Return; - using ::testing::_; - - EXPECT_CALL(*m_mockComponentApplicationBusHandler, GetSerializeContext) - .Times(1) - .WillOnce(Return(m_serializeContext.get())); - - EXPECT_CALL(*m_mockComponentApplicationBusHandler, FindEntity(_)) - .Times(1) - .WillOnce(Return(&sliceEntity)); - - EXPECT_CALL(*m_mockAssetCatalogRequestBusHandler, GetAssetIdByPath(_,_,_)) - .Times(2) - .WillRepeatedly(Return(AZ::Data::AssetId(AZ::Uuid::CreateRandom(), 0))); - - EXPECT_CALL(*m_mockAssetCatalogRequestBusHandler, GetAssetInfoById(_)) - .Times(2) - .WillRepeatedly(Return(AZ::Data::AssetInfo{})); - - auto&& assetManager = AZ::Data::AssetManager::Instance(); - auto assetPtr = assetManager.CreateAsset(AZ::Data::AssetId(AZ::Uuid::CreateRandom(), 0)); - - AZ::IO::ByteContainerStream> stream(&buffer); - stream.Seek(0, AZ::IO::GenericStream::ST_SEEK_BEGIN); - - const AZ::Data::AssetFilterCB assetLoadFilterCB{}; - bool loaded = handler.LoadAssetData(assetPtr, &stream, assetLoadFilterCB); - EXPECT_TRUE(loaded); - } - handler.Unregister(); - - TearDownSliceComponents(); - } -} diff --git a/Gems/Blast/Code/blast_editor_files.cmake b/Gems/Blast/Code/blast_editor_files.cmake index 7d417176a2..dc991fb8a9 100644 --- a/Gems/Blast/Code/blast_editor_files.cmake +++ b/Gems/Blast/Code/blast_editor_files.cmake @@ -11,8 +11,8 @@ set(FILES Source/Editor/EditorBlastFamilyComponent.cpp Source/Editor/EditorBlastMeshDataComponent.cpp Source/Editor/EditorBlastMeshDataComponent.h - Source/Editor/EditorBlastSliceAssetHandler.h - Source/Editor/EditorBlastSliceAssetHandler.cpp + Source/Editor/EditorBlastChunksAssetHandler.h + Source/Editor/EditorBlastChunksAssetHandler.cpp Source/Editor/EditorSystemComponent.h Source/Editor/EditorSystemComponent.cpp Editor/ConfigurationWidget.h diff --git a/Gems/Blast/Code/blast_editor_tests_files.cmake b/Gems/Blast/Code/blast_editor_tests_files.cmake index 4e2a63d75c..7076530312 100644 --- a/Gems/Blast/Code/blast_editor_tests_files.cmake +++ b/Gems/Blast/Code/blast_editor_tests_files.cmake @@ -7,6 +7,6 @@ # set(FILES - # Tests/Editor/EditorBlastSliceAssetHandlerTest.cpp + Tests/Editor/EditorBlastChunksAssetHandlerTest.cpp Tests/Editor/EditorTestMain.cpp ) diff --git a/Gems/Blast/Code/blast_files.cmake b/Gems/Blast/Code/blast_files.cmake index 1707ec46a0..530d9cf7ca 100644 --- a/Gems/Blast/Code/blast_files.cmake +++ b/Gems/Blast/Code/blast_files.cmake @@ -26,8 +26,8 @@ set(FILES Source/Asset/BlastAsset.cpp Source/Asset/BlastAssetHandler.h Source/Asset/BlastAssetHandler.cpp - Source/Asset/BlastSliceAsset.h - Source/Asset/BlastSliceAsset.cpp + Source/Asset/BlastChunksAsset.h + Source/Asset/BlastChunksAsset.cpp Source/Components/BlastFamilyComponent.h Source/Components/BlastFamilyComponent.cpp Source/Components/BlastFamilyComponentNotificationBusHandler.h diff --git a/Gems/Blast/Editor/Scripts/asset_builder_blast.py b/Gems/Blast/Editor/Scripts/asset_builder_blast.py deleted file mode 100755 index beb455e335..0000000000 --- a/Gems/Blast/Editor/Scripts/asset_builder_blast.py +++ /dev/null @@ -1,323 +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 -""" -def install_user_site(): - import os - import sys - import azlmbr.paths - executableBinFolder = azlmbr.paths.executableFolder - - # the PyAssImp module checks the Windows PATH for the assimp DLL file - if os.name == "nt": - os.environ['PATH'] = os.environ['PATH'] + os.pathsep + executableBinFolder - - # PyAssImp module needs to find the shared library for assimp to load; "posix" handles Mac and Linux - if os.name == "posix": - if 'LD_LIBRARY_PATH' in os.environ: - os.environ['LD_LIBRARY_PATH'] = os.environ['LD_LIBRARY_PATH'] + os.pathsep + executableBinFolder - else: - os.environ['LD_LIBRARY_PATH'] = executableBinFolder - - # add the user site packages folder to find the pyassimp egg link - import site - for item in sys.path: - if (item.find('site-packages') != -1): - site.addsitedir(item) - -install_user_site() -import pyassimp - -import azlmbr.asset -import azlmbr.asset.builder -import azlmbr.asset.entity -import azlmbr.blast -import azlmbr.bus as bus -import azlmbr.editor as editor -import azlmbr.entity -import azlmbr.math -import os -import traceback -import binascii -import sys - -# the UUID must be unique amongst all the asset builders in Python or otherwise -# a collision of builders will happen preventing one from running -busIdString = '{CF5C74D1-9ED4-4851-85B1-9B15090DBEC7}' -busId = azlmbr.math.Uuid_CreateString(busIdString, 0) -handler = None -jobKeyName = 'Blast Chunk Assets' -sceneManifestType = azlmbr.math.Uuid_CreateString('{9274AD17-3212-4651-9F3B-7DCCB080E467}', 0) -dccMaterialType = azlmbr.math.Uuid_CreateString('{C88469CF-21E7-41EB-96FD-BF14FBB05EDC}', 0) - - -def log_exception_traceback(): - exc_type, exc_value, exc_tb = sys.exc_info() - data = traceback.format_exception(exc_type, exc_value, exc_tb) - print(str(data)) - - -def get_source_fbx_filename(request): - fullPath = os.path.join(request.watchFolder, request.sourceFile) - basePath, filePart = os.path.split(fullPath) - filename = os.path.splitext(filePart)[0] + '.fbx' - filename = os.path.join(basePath, filename) - return filename - - -def raise_error(message): - raise RuntimeError(f'[ERROR]: {message}') - - -def generate_asset_info(chunkNames, request): - import azlmbr.blast - - # write out an object stream with the extension of .fbx.assetinfo.generated - basePath, sceneFile = os.path.split(request.sourceFile) - assetinfoFilename = os.path.splitext(sceneFile)[0] + '.fbx.assetinfo.generated' - assetinfoFilename = os.path.join(basePath, assetinfoFilename) - assetinfoFilename = assetinfoFilename.replace('\\', '/').lower() - outputFilename = os.path.join(request.tempDirPath, assetinfoFilename) - - storage = azlmbr.blast.BlastSliceAssetStorageComponent() - if (storage.GenerateAssetInfo(chunkNames, request.sourceFile, outputFilename)): - product = azlmbr.asset.builder.JobProduct(assetinfoFilename, sceneManifestType, 1) - product.dependenciesHandled = True - return product - raise_error('Failed to generate assetinfo.generated') - - -def export_fbx_manifest(request): - output = [] - fbxFilename = get_source_fbx_filename(request) - sceneAsset = pyassimp.load(fbxFilename) - with sceneAsset as scene: - rootNode = scene.mRootNode.contents - for index in range(0, rootNode.mNumChildren): - child = rootNode.mChildren[index] - childNode = child.contents - childNodeName = bytes.decode(childNode.mName.data) - output.append(str(childNodeName)) - return output - - -def convert_to_asset_paths(fbxFilename, gameRoot, chunkNameList): - realtivePath = fbxFilename[len(gameRoot) + 1:] - realtivePath = os.path.splitext(realtivePath)[0] - output = [] - for chunk in chunkNameList: - assetPath = realtivePath + '-' + chunk + '.cgf' - assetPath = assetPath.replace('\\', '/') - assetPath = assetPath.lower() - output.append(assetPath) - return output - - -# creates a single job to compile for each platform -def create_jobs(request): - fbxSidecarFilename = get_source_fbx_filename(request) - if (os.path.exists(fbxSidecarFilename) is False): - print('[WARN] Sidecar FBX file {} is missing for blast file {}'.format(fbxSidecarFilename, request.sourceFile)) - return azlmbr.asset.builder.CreateJobsResponse() - - # see if the FBX file already has a .assetinfo source asset, if so then do not create a job - if (os.path.exists(f'{fbxSidecarFilename}.assetinfo')): - response = azlmbr.asset.builder.CreateJobsResponse() - response.result = azlmbr.asset.builder.CreateJobsResponse_ResultSuccess - return response - - # create job descriptor for each platform - jobDescriptorList = [] - for platformInfo in request.enabledPlatforms: - jobDesc = azlmbr.asset.builder.JobDescriptor() - jobDesc.jobKey = jobKeyName - jobDesc.priority = 12 # higher than the 'Scene compilation' or 'fbx' - jobDesc.set_platform_identifier(platformInfo.identifier) - jobDescriptorList.append(jobDesc) - - response = azlmbr.asset.builder.CreateJobsResponse() - response.result = azlmbr.asset.builder.CreateJobsResponse_ResultSuccess - response.createJobOutputs = jobDescriptorList - return response - -# handler to create jobs for a source asset - - -def on_create_jobs(args): - try: - request = args[0] - return create_jobs(request) - except: - log_exception_traceback() - return azlmbr.asset.builder.CreateJobsResponse() - - -def generate_blast_slice_asset(chunkNameList, request): - # get list of relative chunk paths - fbxFilename = get_source_fbx_filename(request) - assetPaths = convert_to_asset_paths(fbxFilename, request.watchFolder, chunkNameList) - - outcome = azlmbr.asset.entity.PythonBuilderRequestBus(bus.Broadcast, 'CreateEditorEntity', 'BlastData') - if (outcome.IsSuccess() is False): - raise_error('could not create an editor entity') - blastDataEntityId = outcome.GetValue() - - # create a component for the editor entity - gameType = azlmbr.entity.EntityType().Game - blastMeshDataTypeIdList = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["Blast Slice Storage Component"], gameType) - componentOutcome = editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentOfType', blastDataEntityId, blastMeshDataTypeIdList[0]) - if (componentOutcome.IsSuccess() is False): - raise_error('failed to add component (Blast Slice Storage Component) to the blast_slice') - - # build the blast slice using the chunk asset paths - blastMeshComponentId = componentOutcome.GetValue()[0] - outcome = editor.EditorComponentAPIBus(bus.Broadcast, 'BuildComponentPropertyTreeEditor', blastMeshComponentId) - if(outcome.IsSuccess() is False): - raise_error(f'failed to create Property Tree Editor for component ({blastMeshComponentId})') - pte = outcome.GetValue() - pte.set_visible_enforcement(True) - pte.set_value('Mesh Paths', assetPaths) - - # write out an object stream with the extension of .blast_slice - basePath, sceneFile = os.path.split(request.sourceFile) - blastFilename = os.path.splitext(sceneFile)[0] + '.blast_slice' - blastFilename = os.path.join(basePath, blastFilename) - blastFilename = blastFilename.replace('\\', '/').lower() - tempFilename = os.path.join(request.tempDirPath, blastFilename) - entityList = [blastDataEntityId] - makeDynamic = False - outcome = azlmbr.asset.entity.PythonBuilderRequestBus(bus.Broadcast, 'WriteSliceFile', tempFilename, entityList, makeDynamic) - if (outcome.IsSuccess() is False): - raise_error(f'WriteSliceFile failed for blast_slice file ({blastFilename})') - - # return a job product - blastSliceAsset = azlmbr.blast.BlastSliceAsset() - subId = binascii.crc32(blastFilename.encode('utf8')) - product = azlmbr.asset.builder.JobProduct(blastFilename, blastSliceAsset.GetAssetTypeId(), subId) - product.dependenciesHandled = True - return product - - -def read_in_string(data, dataLength): - stringData = '' - for idx in range(4, dataLength - 1): - char = bytes.decode(data[idx]) - if (str.isascii(char)): - stringData += char - return stringData - - -def import_material_info(fbxFilename): - _, group_name = os.path.split(fbxFilename) - group_name = os.path.splitext(group_name)[0] - output = {} - output['group_name'] = group_name - output['material_name_list'] = [] - sceneAsset = pyassimp.load(fbxFilename) - with sceneAsset as scene: - for materialIndex in range(0, scene.mNumMaterials): - material = scene.mMaterials[materialIndex].contents - for materialPropertyIdx in range(0, material.mNumProperties): - materialProperty = material.mProperties[materialPropertyIdx].contents - materialPropertyName = bytes.decode(materialProperty.mKey.data) - if (materialPropertyName.endswith('mat.name') and materialProperty.mType is 3): - stringData = read_in_string(materialProperty.mData, materialProperty.mDataLength) - output['material_name_list'].append(stringData) - return output - - -def write_material_file(sourceFile, destFolder): - # preserve source MTL files - rootPath, materialSourceFile = os.path.split(sourceFile) - materialSourceFile = os.path.splitext(materialSourceFile)[0] + '.mtl' - materialSourceFile = os.path.join(rootPath, materialSourceFile) - if (os.path.exists(materialSourceFile)): - print(f'{materialSourceFile} source already exists') - return None - - # auto-generate a DCC material file - info = import_material_info(sourceFile) - materialGroupName = info['group_name'] - materialNames = info['material_name_list'] - materialFilename = materialGroupName + '.dccmtl.generated' - subId = binascii.crc32(materialFilename.encode('utf8')) - materialFilename = os.path.join(destFolder, materialFilename) - storage = azlmbr.blast.BlastSliceAssetStorageComponent() - storage.WriteMaterialFile(materialGroupName, materialNames, materialFilename) - product = azlmbr.asset.builder.JobProduct(materialFilename, dccMaterialType, subId) - product.dependenciesHandled = True - return product - - -def process_fbx_file(request): - # fill out response object - response = azlmbr.asset.builder.ProcessJobResponse() - productOutputs = [] - - # write out DCCMTL file as a product (if needed) - materialProduct = write_material_file(get_source_fbx_filename(request), request.tempDirPath) - if (materialProduct is not None): - productOutputs.append(materialProduct) - - # prepare output folder - basePath, _ = os.path.split(request.sourceFile) - outputPath = os.path.join(request.tempDirPath, basePath) - os.makedirs(outputPath) - - # parse FBX for chunk names - chunkNameList = export_fbx_manifest(request) - - # create assetinfo generated (is product) - productOutputs.append(generate_asset_info(chunkNameList, request)) - - # write out the blast_slice object stream - productOutputs.append(generate_blast_slice_asset(chunkNameList, request)) - - response.outputProducts = productOutputs - response.resultCode = azlmbr.asset.builder.ProcessJobResponse_Success - response.dependenciesHandled = True - return response - - -# using the incoming 'request' find the type of job via 'jobKey' to determine what to do -def on_process_job(args): - try: - request = args[0] - if (request.jobDescription.jobKey.startswith(jobKeyName)): - return process_fbx_file(request) - - return azlmbr.asset.builder.ProcessJobResponse() - except: - log_exception_traceback() - return azlmbr.asset.builder.ProcessJobResponse() - -# register asset builder -def register_asset_builder(): - assetPattern = azlmbr.asset.builder.AssetBuilderPattern() - assetPattern.pattern = '*.blast' - assetPattern.type = azlmbr.asset.builder.AssetBuilderPattern_Wildcard - - builderDescriptor = azlmbr.asset.builder.AssetBuilderDesc() - builderDescriptor.name = "Blast Gem" - builderDescriptor.patterns = [assetPattern] - builderDescriptor.busId = busId - builderDescriptor.version = 5 - - outcome = azlmbr.asset.builder.PythonAssetBuilderRequestBus(azlmbr.bus.Broadcast, 'RegisterAssetBuilder', builderDescriptor) - if outcome.IsSuccess(): - # created the asset builder to hook into the notification bus - handler = azlmbr.asset.builder.PythonBuilderNotificationBusHandler() - handler.connect(busId) - handler.add_callback('OnCreateJobsRequest', on_create_jobs) - handler.add_callback('OnProcessJobRequest', on_process_job) - return handler - - -# create the asset builder handler -try: - handler = register_asset_builder() -except: - handler = None - log_exception_traceback() diff --git a/Gems/Blast/Editor/Scripts/blast_asset_builder.py b/Gems/Blast/Editor/Scripts/blast_asset_builder.py new file mode 100644 index 0000000000..06dc15f5c1 --- /dev/null +++ b/Gems/Blast/Editor/Scripts/blast_asset_builder.py @@ -0,0 +1,290 @@ +""" +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 a Python Asset Builder script examines each .blast file to see if an +associated .fbx file needs to be processed by exporting all of its chunks +into a scene manifest + +This is also a SceneAPI script that executes from a foo.fbx.assetinfo scene +manifest that writes out asset chunk data for .blast files +""" +import os, traceback, binascii, sys, json, pathlib +import azlmbr.math +import azlmbr.asset +import azlmbr.asset.entity +import azlmbr.asset.builder +import azlmbr.bus + +# +# Python Asset Builder +# +busId = azlmbr.math.Uuid_CreateString('{D4FA20E3-8EF4-44A3-A045-AAE6C1CCAAAB}', 0) +jobKeyName = 'Blast Chunk Assets' + +def log_exception_traceback(): + exc_type, exc_value, exc_tb = sys.exc_info() + data = traceback.format_exception(exc_type, exc_value, exc_tb) + print(str(data)) + +def raise_error(message): + print (f'ERROR - {message}'); + raise RuntimeError(f'[ERROR]: {message}'); + +# creates a single job to compile for each platform +def get_source_fbx_filename(request): + fullPath = os.path.join(request.watchFolder, request.sourceFile) + basePath, filePart = os.path.split(fullPath) + filename = os.path.splitext(filePart)[0] + '.fbx' + filename = os.path.join(basePath, filename) + return filename + +def create_jobs(request): + fbxSidecarFilename = get_source_fbx_filename(request) + if (os.path.exists(fbxSidecarFilename) is False): + print('[WARN] Sidecar FBX file {} is missing for blast file {}'.format(fbxSidecarFilename, request.sourceFile)) + return azlmbr.asset.builder.CreateJobsResponse() + + # see if the FBX file already has a .assetinfo source asset, if so then do not create a job + establishedAssetInfo = f'{fbxSidecarFilename}.assetinfo'; + if (os.path.exists(establishedAssetInfo)): + response = azlmbr.asset.builder.CreateJobsResponse() + response.result = azlmbr.asset.builder.CreateJobsResponse_ResultSuccess + return response + + # create job descriptor for each platform + jobDescriptorList = [] + for platformInfo in request.enabledPlatforms: + sourceFileDependency = azlmbr.asset.builder.SourceFileDependency() + sourceFileDependency.sourceFileDependencyPath = fbxSidecarFilename + + jobDependency = azlmbr.asset.builder.JobDependency() + jobDependency.sourceFile = sourceFileDependency + jobDependency.jobKey = jobKeyName + jobDependency.platformIdentifier = platformInfo.identifier + + jobDesc = azlmbr.asset.builder.JobDescriptor() + jobDesc.jobKey = jobKeyName + jobDesc.set_platform_identifier(platformInfo.identifier) + jobDesc.jobDependencyList = [jobDependency] + jobDescriptorList.append(jobDesc) + + response = azlmbr.asset.builder.CreateJobsResponse() + response.result = azlmbr.asset.builder.CreateJobsResponse_ResultSuccess + response.createJobOutputs = jobDescriptorList + return response + +# to create jobs for a source asset +def on_create_jobs(args): + try: + request = args[0] + return create_jobs(request) + except: + log_exception_traceback() + return azlmbr.asset.builder.CreateJobsResponse() + +def generate_assetinfo_product(request): + # write out a product asset file with the extension of .fbx.assetinfo.generated + basePath, sceneFile = os.path.split(request.sourceFile) + assetinfoFilename = os.path.splitext(sceneFile)[0] + '.fbx.assetinfo.generated' + assetinfoFilename = os.path.join(basePath, assetinfoFilename) + assetinfoFilename = assetinfoFilename.replace('\\', '/').lower() + outputFilename = os.path.join(request.tempDirPath, assetinfoFilename) + + # the only rule in it is to run this file again as a scene processor + currentScript = pathlib.Path(__file__).resolve() + aDict = {"values": [{"$type": "ScriptProcessorRule", "scriptFilename": f"{currentScript}"}]} + jsonString = json.dumps(aDict) + jsonFile = open(outputFilename, "w") + jsonFile.write(jsonString) + jsonFile.close() + + # return a job product for the generated assetinfo file + sceneManifestType = azlmbr.math.Uuid_CreateString('{9274AD17-3212-4651-9F3B-7DCCB080E467}', 0) + subId = 1 + product = azlmbr.asset.builder.JobProduct(outputFilename, sceneManifestType, subId) + product.dependenciesHandled = True + return product + +def process_fbx_file(request): + # fill out response object + response = azlmbr.asset.builder.ProcessJobResponse() + productOutputs = [] + + # prepare output folder + basePath, _ = os.path.split(request.sourceFile) + outputPath = os.path.join(request.tempDirPath, basePath) + os.makedirs(outputPath) + + # create assetinfo generated file + productOutputs.append(generate_assetinfo_product(request)) + + response.outputProducts = productOutputs + response.resultCode = azlmbr.asset.builder.ProcessJobResponse_Success + response.dependenciesHandled = True + return response + +# using the incoming 'request' find the type of job via 'jobKey' to determine what to do +def on_process_job(args): + try: + request = args[0] + if (request.jobDescription.jobKey.startswith(jobKeyName)): + return process_fbx_file(request) + + return azlmbr.asset.builder.ProcessJobResponse() + except: + log_exception_traceback() + return azlmbr.asset.builder.ProcessJobResponse() + +# register asset builder +def register_asset_builder(): + assetPattern = azlmbr.asset.builder.AssetBuilderPattern() + assetPattern.pattern = '*.blast' + assetPattern.type = azlmbr.asset.builder.AssetBuilderPattern_Wildcard + + builderDescriptor = azlmbr.asset.builder.AssetBuilderDesc() + builderDescriptor.name = "Blast Scene Builder" + builderDescriptor.patterns = [assetPattern] + builderDescriptor.busId = busId + builderDescriptor.version = 1 + + outcome = azlmbr.asset.builder.PythonAssetBuilderRequestBus(azlmbr.bus.Broadcast, 'RegisterAssetBuilder', builderDescriptor) + if outcome.IsSuccess(): + # created the asset builder to hook into the notification bus + handler = azlmbr.asset.builder.PythonBuilderNotificationBusHandler() + handler.connect(busId) + handler.add_callback('OnCreateJobsRequest', on_create_jobs) + handler.add_callback('OnProcessJobRequest', on_process_job) + return handler + +# create the asset builder handler +pythonAssetBuilderHandler = None +try: + if (pythonAssetBuilderHandler == None): + pythonAssetBuilderHandler = register_asset_builder() +except: + pythonAssetBuilderHandler = None + +# +# SceneAPI Processor +# +blastChunksAssetType = azlmbr.math.Uuid_CreateString('{993F0B0F-37D9-48C6-9CC2-E27D3F3E343E}', 0) + +def export_chunk_asset(scene, outputDirectory, platformIdentifier, productList): + import azlmbr.scene + import azlmbr.object + import azlmbr.paths + import json, os + + jsonFilename = os.path.basename(scene.sourceFilename) + jsonFilename = os.path.join(outputDirectory, jsonFilename + '.blast_chunks') + + # prepare output folder + basePath, _ = os.path.split(jsonFilename) + outputPath = os.path.join(outputDirectory, basePath) + if not os.path.exists(outputPath): + os.makedirs(outputPath, False) + + # write out a JSON file with the chunk file info + with open(jsonFilename, "w") as jsonFile: + jsonFile.write(scene.manifest.ExportToJson()) + + exportProduct = azlmbr.scene.ExportProduct() + exportProduct.filename = jsonFilename + exportProduct.sourceId = scene.sourceGuid + exportProduct.assetType = blastChunksAssetType + exportProduct.subId = 101 + + exportProductList = azlmbr.scene.ExportProductList() + exportProductList.AddProduct(exportProduct) + return exportProductList + +def on_prepare_for_export(args): + try: + scene = args[0] # azlmbr.scene.Scene + outputDirectory = args[1] # string + platformIdentifier = args[2] # string + productList = args[3] # azlmbr.scene.ExportProductList + return export_chunk_asset(scene, outputDirectory, platformIdentifier, productList) + except: + log_exception_traceback() + +def get_mesh_node_names(sceneGraph): + import azlmbr.scene as sceneApi + import azlmbr.scene.graph + from scene_api import scene_data as sceneData + + meshDataList = [] + node = sceneGraph.get_root() + children = [] + + while node.IsValid(): + # store children to process after siblings + if sceneGraph.has_node_child(node): + children.append(sceneGraph.get_node_child(node)) + + # store any node that has mesh data content + nodeContent = sceneGraph.get_node_content(node) + if nodeContent is not None and nodeContent.CastWithTypeName('MeshData'): + if sceneGraph.is_node_end_point(node) is False: + nodeName = sceneData.SceneGraphName(sceneGraph.get_node_name(node)) + nodePath = nodeName.get_path() + if (len(nodeName.get_path())): + meshDataList.append(sceneData.SceneGraphName(sceneGraph.get_node_name(node))) + + # advance to next node + if sceneGraph.has_node_sibling(node): + node = sceneGraph.get_node_sibling(node) + elif children: + node = children.pop() + else: + node = azlmbr.scene.graph.NodeIndex() + + return meshDataList + +def update_manifest(scene): + import uuid, os + import azlmbr.scene as sceneApi + import azlmbr.scene.graph + from scene_api import scene_data as sceneData + + graph = sceneData.SceneGraph(scene.graph) + meshNameList = get_mesh_node_names(graph) + sceneManifest = sceneData.SceneManifest() + sourceFilenameOnly = os.path.basename(scene.sourceFilename) + sourceFilenameOnly = sourceFilenameOnly.replace('.','_') + + for activeMeshIndex in range(len(meshNameList)): + chunkName = meshNameList[activeMeshIndex] + chunkPath = chunkName.get_path() + meshGroupName = '{}_{}'.format(sourceFilenameOnly, chunkName.get_name()) + meshGroup = sceneManifest.add_mesh_group(meshGroupName) + meshGroup['id'] = '{' + str(uuid.uuid5(uuid.NAMESPACE_DNS, sourceFilenameOnly + chunkPath)) + '}' + sceneManifest.mesh_group_select_node(meshGroup, chunkPath) + + return sceneManifest.export() + +sceneJobHandler = None + +def on_update_manifest(args): + try: + scene = args[0] + return update_manifest(scene) + except: + global sceneJobHandler + sceneJobHandler = None + log_exception_traceback() + +# try to create SceneAPI handler for processing +try: + import azlmbr.scene as sceneApi + if (sceneJobHandler == None): + sceneJobHandler = sceneApi.ScriptBuildingNotificationBusHandler() + sceneJobHandler.connect() + sceneJobHandler.add_callback('OnUpdateManifest', on_update_manifest) + sceneJobHandler.add_callback('OnPrepareForExport', on_prepare_for_export) +except: + sceneJobHandler = None diff --git a/Gems/Blast/Editor/Scripts/bootstrap.py b/Gems/Blast/Editor/Scripts/bootstrap.py index d9687bb85b..93004d474f 100755 --- a/Gems/Blast/Editor/Scripts/bootstrap.py +++ b/Gems/Blast/Editor/Scripts/bootstrap.py @@ -4,6 +4,13 @@ For complete copyright and license terms please see the LICENSE at the root of t SPDX-License-Identifier: Apache-2.0 OR MIT """ - -# LYN-652 to re-enable once the Blast gem tests are stable -# import asset_builder_blast +try: + import azlmbr.asset + import azlmbr.asset.entity + import azlmbr.asset.builder + import blast_asset_builder +except: + # this script only runs in an asset processing environment + # like the AssetProcessor or an AssetBuilder + # plus the Blast gem needs to be enabled for the project + pass From 7393c86416e8c84bff35bdd074d1b7ead99fc9ba Mon Sep 17 00:00:00 2001 From: hultonha Date: Tue, 3 Aug 2021 15:24:54 +0100 Subject: [PATCH 17/51] some formatting and naming changes after PR feedback Signed-off-by: hultonha --- .../Viewport/ModularViewportCameraController.h | 18 +++++++++--------- .../ModularViewportCameraController.cpp | 12 ++++++------ 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Viewport/ModularViewportCameraController.h b/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Viewport/ModularViewportCameraController.h index 79219f50b0..d8778a9b9d 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Viewport/ModularViewportCameraController.h +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Viewport/ModularViewportCameraController.h @@ -38,8 +38,8 @@ namespace AtomToolsFramework void SetupCameraProperies(AzFramework::CameraProps& cameraProps); private: - CameraListBuilder - m_cameraListBuilder; //!< Builder to generate a list of CameraInputs to run in the ModularViewportCameraControllerInstance. + //! Builder to generate a list of CameraInputs to run in the ModularViewportCameraControllerInstance. + CameraListBuilder m_cameraListBuilder; CameraPropsBuilder m_cameraPropsBuilder; //!< Builder to define custom camera properties to use for things such as rotate and //!< translate interpolation. }; @@ -77,10 +77,10 @@ namespace AtomToolsFramework //! Encapsulates an animation (interpolation) between two transforms. struct CameraAnimation { - AZ::Transform m_transformStart = - AZ::Transform::CreateIdentity(); //!< The transform of the camera at the start of the animation. + //! The transform of the camera at the start of the animation. + AZ::Transform m_transformStart = AZ::Transform::CreateIdentity(); AZ::Transform m_transformEnd = AZ::Transform::CreateIdentity(); //!< The transform of the camera at the end of the animation. - float m_animationT = 0.0f; //!< The interpolation amount between the start and end transforms (in the range 0.0-1.0). + float m_time = 0.0f; //!< The interpolation amount between the start and end transforms (in the range 0.0 - 1.0). }; AzFramework::Camera m_camera; //!< The current camera state (pitch/yaw/position/look-distance). @@ -92,9 +92,9 @@ namespace AtomToolsFramework CameraMode m_cameraMode = CameraMode::Control; //!< The current mode the camera is operating in. AZStd::optional m_lookAtAfterInterpolation; //!< The look at point after an interpolation has finished. //!< Will be cleared when the view changes (camera looks away). - bool m_updatingTransformInternally = - false; //!< Flag to prevent circular updates of the camera transform (while the viewport transform is being updated internally). - AZ::RPI::ViewportContext::MatrixChangedEvent::Handler - m_cameraViewMatrixChangeHandler; //!< Listen for camera view changes outside of the camera controller. + //! Flag to prevent circular updates of the camera transform (while the viewport transform is being updated internally). + bool m_updatingTransformInternally = false; + //! Listen for camera view changes outside of the camera controller. + AZ::RPI::ViewportContext::MatrixChangedEvent::Handler m_cameraViewMatrixChangeHandler; }; } // namespace AtomToolsFramework diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Viewport/ModularViewportCameraController.cpp b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Viewport/ModularViewportCameraController.cpp index ce4c6021af..cc87ba1e46 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Viewport/ModularViewportCameraController.cpp +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Viewport/ModularViewportCameraController.cpp @@ -181,12 +181,12 @@ namespace AtomToolsFramework return t * t * t * (t * (t * 6.0f - 15.0f) + 10.0f); }; - const auto& [transformStart, transformEnd, animationT] = m_cameraAnimation; + const auto& [transformStart, transformEnd, animationTime] = m_cameraAnimation; - const float transitionT = smootherStepFn(animationT); + const float transitionTime = smootherStepFn(animationTime); const AZ::Transform current = AZ::Transform::CreateFromQuaternionAndTranslation( - transformStart.GetRotation().Slerp(transformEnd.GetRotation(), transitionT), - transformStart.GetTranslation().Lerp(transformEnd.GetTranslation(), transitionT)); + transformStart.GetRotation().Slerp(transformEnd.GetRotation(), transitionTime), + transformStart.GetTranslation().Lerp(transformEnd.GetTranslation(), transitionTime)); const AZ::Vector3 eulerAngles = AzFramework::EulerAngles(AZ::Matrix3x3::CreateFromTransform(current)); m_camera.m_pitch = eulerAngles.GetX(); @@ -194,12 +194,12 @@ namespace AtomToolsFramework m_camera.m_lookAt = current.GetTranslation(); m_targetCamera = m_camera; - if (animationT >= 1.0f) + if (animationTime >= 1.0f) { m_cameraMode = CameraMode::Control; } - m_cameraAnimation.m_animationT = AZ::GetClamp(animationT + event.m_deltaTime.count(), 0.0f, 1.0f); + m_cameraAnimation.m_time = AZ::GetClamp(animationTime + event.m_deltaTime.count(), 0.0f, 1.0f); viewportContext->SetCameraTransform(current); } From 6520c347e4c49a18a410d39414662d64964af59d Mon Sep 17 00:00:00 2001 From: AMZN-stankowi <4838196+AMZN-stankowi@users.noreply.github.com> Date: Tue, 3 Aug 2021 08:00:39 -0700 Subject: [PATCH 18/51] Disabling a flaky test (#2749) ContainerFilterTest_ContainersWithAndWithoutFiltering_Success Signed-off-by: stankowi <4838196+AMZN-stankowi@users.noreply.github.com> --- Code/Framework/AzCore/Tests/Asset/AssetManagerLoadingTests.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Code/Framework/AzCore/Tests/Asset/AssetManagerLoadingTests.cpp b/Code/Framework/AzCore/Tests/Asset/AssetManagerLoadingTests.cpp index 156719b8a7..e25c3dbcf7 100644 --- a/Code/Framework/AzCore/Tests/Asset/AssetManagerLoadingTests.cpp +++ b/Code/Framework/AzCore/Tests/Asset/AssetManagerLoadingTests.cpp @@ -1150,7 +1150,7 @@ namespace UnitTest #if AZ_TRAIT_DISABLE_FAILED_ASSET_MANAGER_TESTS TEST_F(AssetJobsFloodTest, DISABLED_ContainerFilterTest_ContainersWithAndWithoutFiltering_Success) #else - TEST_F(AssetJobsFloodTest, ContainerFilterTest_ContainersWithAndWithoutFiltering_Success) + TEST_F(AssetJobsFloodTest, DISABLED_ContainerFilterTest_ContainersWithAndWithoutFiltering_Success) #endif // !AZ_TRAIT_DISABLE_FAILED_ASSET_MANAGER_TESTS { m_assetHandlerAndCatalog->AssetCatalogRequestBus::Handler::BusConnect(); From 00470acc1cf25636a8569d9af00b91eddd63922a Mon Sep 17 00:00:00 2001 From: puvvadar Date: Tue, 3 Aug 2021 09:36:39 -0700 Subject: [PATCH 19/51] Add const to timeout enabled getter Signed-off-by: puvvadar --- .../AzNetworking/AzNetworking/Framework/INetworkInterface.h | 2 +- .../AzNetworking/TcpTransport/TcpNetworkInterface.cpp | 2 +- .../AzNetworking/TcpTransport/TcpNetworkInterface.h | 2 +- .../AzNetworking/UdpTransport/UdpNetworkInterface.cpp | 2 +- .../AzNetworking/UdpTransport/UdpNetworkInterface.h | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Code/Framework/AzNetworking/AzNetworking/Framework/INetworkInterface.h b/Code/Framework/AzNetworking/AzNetworking/Framework/INetworkInterface.h index f2ad1c03c3..09aa62f7d7 100644 --- a/Code/Framework/AzNetworking/AzNetworking/Framework/INetworkInterface.h +++ b/Code/Framework/AzNetworking/AzNetworking/Framework/INetworkInterface.h @@ -109,7 +109,7 @@ namespace AzNetworking //! Whether this connection interface will disconnect by virtue of a time out (does not account for cvars affecting all connections) //! @return boolean true if this connection will not disconnect on timeout (does not account for cvars affecting all connections) - virtual bool IsTimeoutEnabled() = 0; + virtual bool IsTimeoutEnabled() const = 0; //! Const access to the metrics tracked by this network interface. //! @return const reference to the metrics tracked by this network interface diff --git a/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpNetworkInterface.cpp b/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpNetworkInterface.cpp index 52c696b663..62335a9b39 100644 --- a/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpNetworkInterface.cpp +++ b/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpNetworkInterface.cpp @@ -179,7 +179,7 @@ namespace AzNetworking m_timeoutEnabled = timeoutEnabled; } - bool TcpNetworkInterface::IsTimeoutEnabled() + bool TcpNetworkInterface::IsTimeoutEnabled() const { return m_timeoutEnabled; } diff --git a/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpNetworkInterface.h b/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpNetworkInterface.h index 3eb792bc7f..b9ea88974d 100644 --- a/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpNetworkInterface.h +++ b/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpNetworkInterface.h @@ -100,7 +100,7 @@ namespace AzNetworking bool StopListening() override; bool Disconnect(ConnectionId connectionId, DisconnectReason reason) override; void SetTimeoutEnabled(bool timeoutEnabled) override; - bool IsTimeoutEnabled() override; + bool IsTimeoutEnabled() const override; //! @} //! Queues a new incoming connection for this network interface. diff --git a/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpNetworkInterface.cpp b/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpNetworkInterface.cpp index a80cb82d03..48b3ad57e1 100644 --- a/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpNetworkInterface.cpp +++ b/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpNetworkInterface.cpp @@ -402,7 +402,7 @@ namespace AzNetworking m_timeoutEnabled = timeoutEnabled; } - bool UdpNetworkInterface::IsTimeoutEnabled() + bool UdpNetworkInterface::IsTimeoutEnabled() const { return m_timeoutEnabled; } diff --git a/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpNetworkInterface.h b/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpNetworkInterface.h index 0260491295..949914da91 100644 --- a/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpNetworkInterface.h +++ b/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpNetworkInterface.h @@ -105,7 +105,7 @@ namespace AzNetworking bool StopListening() override; bool Disconnect(ConnectionId connectionId, DisconnectReason reason) override; void SetTimeoutEnabled(bool timeoutEnabled) override; - bool IsTimeoutEnabled() override; + bool IsTimeoutEnabled() const override; //! @} //! Returns true if this is an encrypted socket, false if not. From 3a689aa31929001e6d6df360519ff3fc7e34ce2b Mon Sep 17 00:00:00 2001 From: michabr <82236305+michabr@users.noreply.github.com> Date: Tue, 3 Aug 2021 10:14:09 -0700 Subject: [PATCH 20/51] Reenable support for UI Elements that use Render Targets (#2352) * Re-add support for UI Elements that use Render Targets * Move LyShine pass request from Atom's MainPipeline.pass to project's * Make all dynamic draw contexts in LyShine draw to pass directly without the need of draw list tags * Remove local RPI changes that are no longer needed * Prevent crash if LyShine gem is enabled but its custom pass hasn't been added to the main render pipeline * Revert to default UI pass if the LyShine pass has not been added to project's main render pipeline Signed-off-by: abrmich --- AutomatedTesting/Passes/MainPipeline.pass | 483 ++++++++++++++++++ .../Atom/Feature/Common/Assets/Passes/UI.pass | 8 +- .../Code/Source/RPI.Public/PipelineState.cpp | 2 +- .../AtomBridge/Assets/Shaders/LyShineUI.azsl | 15 +- .../Shaders/LyShineUI.shadervariantlist | 22 +- Gems/LyShine/Assets/Passes/LyShineParent.pass | 22 + .../Passes/LyShinePassTemplates.azasset | 13 + Gems/LyShine/Code/CMakeLists.txt | 3 + Gems/LyShine/Code/Editor/EditorWindow.cpp | 14 + Gems/LyShine/Code/Editor/EditorWindow.h | 3 + Gems/LyShine/Code/Editor/ViewportWidget.cpp | 147 ++++-- Gems/LyShine/Code/Editor/ViewportWidget.h | 22 +- Gems/LyShine/Code/Source/Draw2d.cpp | 18 +- Gems/LyShine/Code/Source/LyShine.cpp | 26 +- Gems/LyShine/Code/Source/LyShine.h | 12 + Gems/LyShine/Code/Source/LyShinePass.cpp | 274 ++++++++++ Gems/LyShine/Code/Source/LyShinePass.h | 110 ++++ Gems/LyShine/Code/Source/LyShinePassDataBus.h | 61 +++ .../Code/Source/LyShineSystemComponent.cpp | 27 +- .../Code/Source/LyShineSystemComponent.h | 13 + Gems/LyShine/Code/Source/RenderGraph.cpp | 330 ++++++------ Gems/LyShine/Code/Source/RenderGraph.h | 64 ++- Gems/LyShine/Code/Source/RenderToTextureBus.h | 22 + .../LyShine/Code/Source/UiCanvasComponent.cpp | 87 +++- Gems/LyShine/Code/Source/UiCanvasComponent.h | 20 + Gems/LyShine/Code/Source/UiCanvasManager.cpp | 18 +- Gems/LyShine/Code/Source/UiCanvasManager.h | 4 + Gems/LyShine/Code/Source/UiFaderComponent.cpp | 123 ++--- Gems/LyShine/Code/Source/UiFaderComponent.h | 8 +- Gems/LyShine/Code/Source/UiMaskComponent.cpp | 174 +++---- Gems/LyShine/Code/Source/UiMaskComponent.h | 8 +- Gems/LyShine/Code/Source/UiRenderer.cpp | 139 +++-- Gems/LyShine/Code/Source/UiRenderer.h | 26 +- .../Code/Tests/UiTooltipComponentTest.cpp | 30 +- Gems/LyShine/Code/lyshine_static_files.cmake | 4 + Gems/LyShine/LyShineScript/LyShinePass.data | 20 + .../LyShineScript/PatchRenderPipeline.py | 71 +++ 37 files changed, 1951 insertions(+), 492 deletions(-) create mode 100644 AutomatedTesting/Passes/MainPipeline.pass create mode 100644 Gems/LyShine/Assets/Passes/LyShineParent.pass create mode 100644 Gems/LyShine/Assets/Passes/LyShinePassTemplates.azasset create mode 100644 Gems/LyShine/Code/Source/LyShinePass.cpp create mode 100644 Gems/LyShine/Code/Source/LyShinePass.h create mode 100644 Gems/LyShine/Code/Source/LyShinePassDataBus.h create mode 100644 Gems/LyShine/Code/Source/RenderToTextureBus.h create mode 100644 Gems/LyShine/LyShineScript/LyShinePass.data create mode 100644 Gems/LyShine/LyShineScript/PatchRenderPipeline.py diff --git a/AutomatedTesting/Passes/MainPipeline.pass b/AutomatedTesting/Passes/MainPipeline.pass new file mode 100644 index 0000000000..aa9f3757c4 --- /dev/null +++ b/AutomatedTesting/Passes/MainPipeline.pass @@ -0,0 +1,483 @@ +{ + "Type": "JsonSerialization", + "Version": 1, + "ClassName": "PassAsset", + "ClassData": { + "PassTemplate": { + "Name": "MainPipeline", + "PassClass": "ParentPass", + "Slots": [ + { + "Name": "SwapChainOutput", + "SlotType": "InputOutput", + "ScopeAttachmentUsage": "RenderTarget" + } + ], + "PassRequests": [ + { + "Name": "MorphTargetPass", + "TemplateName": "MorphTargetPassTemplate" + }, + { + "Name": "SkinningPass", + "TemplateName": "SkinningPassTemplate", + "Connections": [ + { + "LocalSlot": "SkinnedMeshOutputStream", + "AttachmentRef": { + "Pass": "MorphTargetPass", + "Attachment": "MorphTargetDeltaOutput" + } + } + ] + }, + { + "Name": "RayTracingAccelerationStructurePass", + "TemplateName": "RayTracingAccelerationStructurePassTemplate" + }, + { + "Name": "DiffuseProbeGridUpdatePass", + "TemplateName": "DiffuseProbeGridUpdatePassTemplate", + "ExecuteAfter": [ + "RayTracingAccelerationStructurePass" + ] + }, + { + "Name": "DepthPrePass", + "TemplateName": "DepthMSAAParentTemplate", + "Connections": [ + { + "LocalSlot": "SkinnedMeshes", + "AttachmentRef": { + "Pass": "SkinningPass", + "Attachment": "SkinnedMeshOutputStream" + } + }, + { + "LocalSlot": "SwapChainOutput", + "AttachmentRef": { + "Pass": "Parent", + "Attachment": "SwapChainOutput" + } + } + ] + }, + { + "Name": "MotionVectorPass", + "TemplateName": "MotionVectorParentTemplate", + "Connections": [ + { + "LocalSlot": "SkinnedMeshes", + "AttachmentRef": { + "Pass": "SkinningPass", + "Attachment": "SkinnedMeshOutputStream" + } + }, + { + "LocalSlot": "Depth", + "AttachmentRef": { + "Pass": "DepthPrePass", + "Attachment": "Depth" + } + }, + { + "LocalSlot": "SwapChainOutput", + "AttachmentRef": { + "Pass": "Parent", + "Attachment": "SwapChainOutput" + } + } + ] + }, + { + "Name": "LightCullingPass", + "TemplateName": "LightCullingParentTemplate", + "Connections": [ + { + "LocalSlot": "SkinnedMeshes", + "AttachmentRef": { + "Pass": "SkinningPass", + "Attachment": "SkinnedMeshOutputStream" + } + }, + { + "LocalSlot": "DepthMSAA", + "AttachmentRef": { + "Pass": "DepthPrePass", + "Attachment": "DepthMSAA" + } + }, + { + "LocalSlot": "SwapChainOutput", + "AttachmentRef": { + "Pass": "Parent", + "Attachment": "SwapChainOutput" + } + } + ] + }, + { + "Name": "ShadowPass", + "TemplateName": "ShadowParentTemplate", + "Connections": [ + { + "LocalSlot": "SkinnedMeshes", + "AttachmentRef": { + "Pass": "SkinningPass", + "Attachment": "SkinnedMeshOutputStream" + } + }, + { + "LocalSlot": "SwapChainOutput", + "AttachmentRef": { + "Pass": "Parent", + "Attachment": "SwapChainOutput" + } + } + ] + }, + { + "Name": "OpaquePass", + "TemplateName": "OpaqueParentTemplate", + "Connections": [ + { + "LocalSlot": "DirectionalShadowmap", + "AttachmentRef": { + "Pass": "ShadowPass", + "Attachment": "DirectionalShadowmap" + } + }, + { + "LocalSlot": "DirectionalESM", + "AttachmentRef": { + "Pass": "ShadowPass", + "Attachment": "DirectionalESM" + } + }, + { + "LocalSlot": "ProjectedShadowmap", + "AttachmentRef": { + "Pass": "ShadowPass", + "Attachment": "ProjectedShadowmap" + } + }, + { + "LocalSlot": "ProjectedESM", + "AttachmentRef": { + "Pass": "ShadowPass", + "Attachment": "ProjectedESM" + } + }, + { + "LocalSlot": "TileLightData", + "AttachmentRef": { + "Pass": "LightCullingPass", + "Attachment": "TileLightData" + } + }, + { + "LocalSlot": "LightListRemapped", + "AttachmentRef": { + "Pass": "LightCullingPass", + "Attachment": "LightListRemapped" + } + }, + { + "LocalSlot": "DepthLinear", + "AttachmentRef": { + "Pass": "DepthPrePass", + "Attachment": "DepthLinear" + } + }, + { + "LocalSlot": "DepthStencil", + "AttachmentRef": { + "Pass": "DepthPrePass", + "Attachment": "DepthMSAA" + } + }, + { + "LocalSlot": "SwapChainOutput", + "AttachmentRef": { + "Pass": "Parent", + "Attachment": "SwapChainOutput" + } + } + ] + }, + { + "Name": "TransparentPass", + "TemplateName": "TransparentParentTemplate", + "Connections": [ + { + "LocalSlot": "DirectionalShadowmap", + "AttachmentRef": { + "Pass": "ShadowPass", + "Attachment": "DirectionalShadowmap" + } + }, + { + "LocalSlot": "DirectionalESM", + "AttachmentRef": { + "Pass": "ShadowPass", + "Attachment": "DirectionalESM" + } + }, + { + "LocalSlot": "ProjectedShadowmap", + "AttachmentRef": { + "Pass": "ShadowPass", + "Attachment": "ProjectedShadowmap" + } + }, + { + "LocalSlot": "ProjectedESM", + "AttachmentRef": { + "Pass": "ShadowPass", + "Attachment": "ProjectedESM" + } + }, + { + "LocalSlot": "TileLightData", + "AttachmentRef": { + "Pass": "LightCullingPass", + "Attachment": "TileLightData" + } + }, + { + "LocalSlot": "LightListRemapped", + "AttachmentRef": { + "Pass": "LightCullingPass", + "Attachment": "LightListRemapped" + } + }, + { + "LocalSlot": "InputLinearDepth", + "AttachmentRef": { + "Pass": "DepthPrePass", + "Attachment": "DepthLinear" + } + }, + { + "LocalSlot": "DepthStencil", + "AttachmentRef": { + "Pass": "DepthPrePass", + "Attachment": "Depth" + } + }, + { + "LocalSlot": "InputOutput", + "AttachmentRef": { + "Pass": "OpaquePass", + "Attachment": "Output" + } + } + ] + }, + { + "Name": "DeferredFogPass", + "TemplateName": "DeferredFogPassTemplate", + "Enabled": false, + "Connections": [ + { + "LocalSlot": "InputLinearDepth", + "AttachmentRef": { + "Pass": "DepthPrePass", + "Attachment": "DepthLinear" + } + }, + { + "LocalSlot": "InputDepthStencil", + "AttachmentRef": { + "Pass": "DepthPrePass", + "Attachment": "Depth" + } + }, + { + "LocalSlot": "RenderTargetInputOutput", + "AttachmentRef": { + "Pass": "TransparentPass", + "Attachment": "InputOutput" + } + } + ], + "PassData": { + "$type": "FullscreenTrianglePassData", + "ShaderAsset": { + "FilePath": "Shaders/ScreenSpace/DeferredFog.shader" + }, + "PipelineViewTag": "MainCamera" + } + }, + { + "Name": "ReflectionCopyFrameBufferPass", + "TemplateName": "ReflectionCopyFrameBufferPassTemplate", + "Enabled": false, + "Connections": [ + { + "LocalSlot": "Input", + "AttachmentRef": { + "Pass": "DeferredFogPass", + "Attachment": "RenderTargetInputOutput" + } + } + ] + }, + { + "Name": "PostProcessPass", + "TemplateName": "PostProcessParentTemplate", + "Connections": [ + { + "LocalSlot": "LightingInput", + "AttachmentRef": { + "Pass": "DeferredFogPass", + "Attachment": "RenderTargetInputOutput" + } + }, + { + "LocalSlot": "Depth", + "AttachmentRef": { + "Pass": "DepthPrePass", + "Attachment": "Depth" + } + }, + { + "LocalSlot": "MotionVectors", + "AttachmentRef": { + "Pass": "MotionVectorPass", + "Attachment": "MotionVectorOutput" + } + }, + { + "LocalSlot": "SwapChainOutput", + "AttachmentRef": { + "Pass": "Parent", + "Attachment": "SwapChainOutput" + } + } + ] + }, + { + "Name": "AuxGeomPass", + "TemplateName": "AuxGeomPassTemplate", + "Enabled": true, + "Connections": [ + { + "LocalSlot": "ColorInputOutput", + "AttachmentRef": { + "Pass": "PostProcessPass", + "Attachment": "Output" + } + }, + { + "LocalSlot": "DepthInputOutput", + "AttachmentRef": { + "Pass": "DepthPrePass", + "Attachment": "Depth" + } + } + ], + "PassData": { + "$type": "RasterPassData", + "DrawListTag": "auxgeom", + "PipelineViewTag": "MainCamera" + } + }, + { + "Name": "DebugOverlayPass", + "TemplateName": "DebugOverlayParentTemplate", + "Connections": [ + { + "LocalSlot": "TileLightData", + "AttachmentRef": { + "Pass": "LightCullingPass", + "Attachment": "TileLightData" + } + }, + { + "LocalSlot": "RawLightingInput", + "AttachmentRef": { + "Pass": "PostProcessPass", + "Attachment": "RawLightingOutput" + } + }, + { + "LocalSlot": "LuminanceMipChainInput", + "AttachmentRef": { + "Pass": "PostProcessPass", + "Attachment": "LuminanceMipChainOutput" + } + }, + { + "LocalSlot": "InputOutput", + "AttachmentRef": { + "Pass": "AuxGeomPass", + "Attachment": "ColorInputOutput" + } + } + ] + }, + { + "Name": "LyShinePass", + "TemplateName": "LyShineParentTemplate", + "Connections": [ + { + "LocalSlot": "ColorInputOutput", + "AttachmentRef": { + "Pass": "DebugOverlayPass", + "Attachment": "InputOutput" + } + }, + { + "LocalSlot": "DepthInputOutput", + "AttachmentRef": { + "Pass": "DepthPrePass", + "Attachment": "Depth" + } + } + ] + }, + { + "Name": "UIPass", + "TemplateName": "UIParentTemplate", + "Connections": [ + { + "LocalSlot": "InputOutput", + "AttachmentRef": { + "Pass": "LyShinePass", + "Attachment": "ColorInputOutput" + } + }, + { + "LocalSlot": "DepthInputOutput", + "AttachmentRef": { + "Pass": "DepthPrePass", + "Attachment": "Depth" + } + } + ] + }, + { + "Name": "CopyToSwapChain", + "TemplateName": "FullscreenCopyTemplate", + "Connections": [ + { + "LocalSlot": "Input", + "AttachmentRef": { + "Pass": "UIPass", + "Attachment": "InputOutput" + } + }, + { + "LocalSlot": "Output", + "AttachmentRef": { + "Pass": "Parent", + "Attachment": "SwapChainOutput" + } + } + ] + } + ] + } + } +} \ No newline at end of file diff --git a/Gems/Atom/Feature/Common/Assets/Passes/UI.pass b/Gems/Atom/Feature/Common/Assets/Passes/UI.pass index ac43f17c11..fd59df5336 100644 --- a/Gems/Atom/Feature/Common/Assets/Passes/UI.pass +++ b/Gems/Atom/Feature/Common/Assets/Passes/UI.pass @@ -13,13 +13,7 @@ "ScopeAttachmentUsage": "DepthStencil", "LoadStoreAction": { "ClearValue": { - "Type": "DepthStencil", - "Value": [ - 0.0, - 0.0, - 0.0, - 0.0 - ] + "Type": "DepthStencil" }, "LoadActionStencil": "Clear" } diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/PipelineState.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/PipelineState.cpp index ba9e6b20ed..8e0aa62554 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/PipelineState.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/PipelineState.cpp @@ -172,7 +172,7 @@ namespace AZ } m_pipelineState = m_shader->AcquirePipelineState(descriptor); - } + } m_dirty = false; } return m_pipelineState; diff --git a/Gems/AtomLyIntegration/AtomBridge/Assets/Shaders/LyShineUI.azsl b/Gems/AtomLyIntegration/AtomBridge/Assets/Shaders/LyShineUI.azsl index 85b2dcc509..b5c0ffa068 100644 --- a/Gems/AtomLyIntegration/AtomBridge/Assets/Shaders/LyShineUI.azsl +++ b/Gems/AtomLyIntegration/AtomBridge/Assets/Shaders/LyShineUI.azsl @@ -10,9 +10,6 @@ #include -// Indicates whether to use pre-multiplied alpha -option bool o_preMultiplyAlpha; - // If true pixels with an alpha value of less than 0.5 are clipped option bool o_alphaTest; @@ -86,9 +83,9 @@ struct PSOutput float4 m_color : SV_Target0; }; -float4 SampleTriangleTexture(int texIndex, float2 uv) +float4 SampleTriangleTexture(uint texIndex, float2 uv) { - if ((InstanceSrg::m_isClamp & (1 << texIndex)) != 0) + if ((InstanceSrg::m_isClamp & (1U << texIndex)) != 0) { return InstanceSrg::m_texture[texIndex].Sample(InstanceSrg::m_clampSampler, uv); } @@ -120,14 +117,6 @@ PSOutput MainPS(VSOutput IN) resColor.xyz = LinearToSRGB(resColor.xyz); } - // Check for flag to premultiply alpha - if (o_preMultiplyAlpha) - { - // premultiply the color by the alpha. This would not be required if we had full access to the separate alpha blend mode - float preMult = resColor.w; - resColor.xyz *= preMult; - } - // If the o_modulate option is not None it means that the verts have two texture indicies. The second texture is used to // mask the first. This is used for gradient masks. if (o_modulate == Modulate::Alpha) diff --git a/Gems/AtomLyIntegration/AtomBridge/Assets/Shaders/LyShineUI.shadervariantlist b/Gems/AtomLyIntegration/AtomBridge/Assets/Shaders/LyShineUI.shadervariantlist index c7eddb10f2..56f71e72f5 100644 --- a/Gems/AtomLyIntegration/AtomBridge/Assets/Shaders/LyShineUI.shadervariantlist +++ b/Gems/AtomLyIntegration/AtomBridge/Assets/Shaders/LyShineUI.shadervariantlist @@ -4,7 +4,6 @@ { "StableId": 1, "Options": { - "o_preMultiplyAlpha": "false", "o_alphaTest": "false", "o_srgbWrite": "true", "o_modulate": "Modulate::None" @@ -13,11 +12,26 @@ { "StableId": 2, "Options": { - "o_preMultiplyAlpha": "false", - "o_alphaTest": "true", - "o_srgbWrite": "true", + "o_alphaTest": "false", + "o_srgbWrite": "false", "o_modulate": "Modulate::None" } + }, + { + "StableId": 3, + "Options": { + "o_alphaTest": "true", + "o_srgbWrite": "false", + "o_modulate": "Modulate::None" + } + }, + { + "StableId": 4, + "Options": { + "o_alphaTest": "false", + "o_srgbWrite": "false", + "o_modulate": "Modulate::Alpha" + } } ] } diff --git a/Gems/LyShine/Assets/Passes/LyShineParent.pass b/Gems/LyShine/Assets/Passes/LyShineParent.pass new file mode 100644 index 0000000000..7bbd4748a7 --- /dev/null +++ b/Gems/LyShine/Assets/Passes/LyShineParent.pass @@ -0,0 +1,22 @@ +{ + "Type": "JsonSerialization", + "Version": 1, + "ClassName": "PassAsset", + "ClassData": { + "PassTemplate": { + "Name": "LyShineParentTemplate", + "PassClass": "LyShinePass", + "Slots": [ + { + "Name": "ColorInputOutput", + "SlotType": "InputOutput" + }, + { + "Name": "DepthInputOutput", + "SlotType": "InputOutput", + "ScopeAttachmentUsage": "DepthStencil" + } + ] + } + } +} diff --git a/Gems/LyShine/Assets/Passes/LyShinePassTemplates.azasset b/Gems/LyShine/Assets/Passes/LyShinePassTemplates.azasset new file mode 100644 index 0000000000..c9ee5186e0 --- /dev/null +++ b/Gems/LyShine/Assets/Passes/LyShinePassTemplates.azasset @@ -0,0 +1,13 @@ +{ + "Type": "JsonSerialization", + "Version": 1, + "ClassName": "AssetAliasesSourceData", + "ClassData": { + "AssetPaths": [ + { + "Name": "LyShineParentTemplate", + "Path": "Passes/LyShineParent.pass" + } + ] + } +} diff --git a/Gems/LyShine/Code/CMakeLists.txt b/Gems/LyShine/Code/CMakeLists.txt index 171927c4a3..93bf84c66e 100644 --- a/Gems/LyShine/Code/CMakeLists.txt +++ b/Gems/LyShine/Code/CMakeLists.txt @@ -193,6 +193,9 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED) FILES_CMAKE lyshine_common_module_files.cmake lyshine_tests_files.cmake + COMPILE_DEFINITIONS + PRIVATE + LYSHINE_TESTS INCLUDE_DIRECTORIES PRIVATE Tests diff --git a/Gems/LyShine/Code/Editor/EditorWindow.cpp b/Gems/LyShine/Code/Editor/EditorWindow.cpp index 81360ed793..8d7e20492d 100644 --- a/Gems/LyShine/Code/Editor/EditorWindow.cpp +++ b/Gems/LyShine/Code/Editor/EditorWindow.cpp @@ -1547,6 +1547,20 @@ AssetTreeEntry* EditorWindow::GetSliceLibraryTree() return m_sliceLibraryTree; } +AZ::EntityId EditorWindow::GetCanvasForCurrentEditorMode() +{ + AZ::EntityId canvasEntityId; + if (GetEditorMode() == UiEditorMode::Edit) + { + canvasEntityId = GetCanvas(); + } + else + { + canvasEntityId = GetPreviewModeCanvas(); + } + return canvasEntityId; +} + void EditorWindow::ToggleEditorMode() { m_editorMode = (m_editorMode == UiEditorMode::Edit) ? UiEditorMode::Preview : UiEditorMode::Edit; diff --git a/Gems/LyShine/Code/Editor/EditorWindow.h b/Gems/LyShine/Code/Editor/EditorWindow.h index c2fa6808f1..d9b3e60c32 100644 --- a/Gems/LyShine/Code/Editor/EditorWindow.h +++ b/Gems/LyShine/Code/Editor/EditorWindow.h @@ -143,6 +143,9 @@ public: // member functions //! Returns the current mode of the editor (Edit or Preview) UiEditorMode GetEditorMode() { return m_editorMode; } + //! Returns the UI canvas for the current mode (Edit or Preview) + AZ::EntityId GetCanvasForCurrentEditorMode(); + //! Toggle the editor mode between Edit and Preview void ToggleEditorMode(); diff --git a/Gems/LyShine/Code/Editor/ViewportWidget.cpp b/Gems/LyShine/Code/Editor/ViewportWidget.cpp index 46f4f4f8fa..1ce8f5b64d 100644 --- a/Gems/LyShine/Code/Editor/ViewportWidget.cpp +++ b/Gems/LyShine/Code/Editor/ViewportWidget.cpp @@ -7,6 +7,8 @@ */ #include "EditorCommon.h" +#include "UiCanvasComponent.h" + #include "EditorDefs.h" #include "Settings.h" #include @@ -245,6 +247,7 @@ ViewportWidget::ViewportWidget(EditorWindow* parent) FontNotificationBus::Handler::BusConnect(); AZ::TickBus::Handler::BusConnect(); + AZ::RPI::ViewportContextNotificationBus::Handler::BusConnect(GetCurrentContextName()); } ViewportWidget::~ViewportWidget() @@ -252,6 +255,8 @@ ViewportWidget::~ViewportWidget() AzToolsFramework::EditorPickModeNotificationBus::Handler::BusDisconnect(); FontNotificationBus::Handler::BusDisconnect(); AZ::TickBus::Handler::BusDisconnect(); + LyShinePassDataRequestBus::Handler::BusDisconnect(); + AZ::RPI::ViewportContextNotificationBus::Handler::BusDisconnect(); m_uiRenderer.reset(); @@ -272,6 +277,8 @@ void ViewportWidget::InitUiRenderer() lyShine->SetUiRendererForEditor(m_uiRenderer); m_draw2d = AZStd::make_shared(GetViewportContext()); + + LyShinePassDataRequestBus::Handler::BusConnect(GetViewportContext()->GetRenderScene()->GetId()); } ViewportInteraction* ViewportWidget::GetViewportInteraction() @@ -487,30 +494,44 @@ void ViewportWidget::EnableCanvasRender() } void ViewportWidget::OnTick(float deltaTime, [[maybe_unused]] AZ::ScriptTimePoint time) +{ + // Update + UiEditorMode editorMode = m_editorWindow->GetEditorMode(); + if (editorMode == UiEditorMode::Edit) + { + UpdateEditMode(deltaTime); + } + else // if (editorMode == UiEditorMode::Preview) + { + UpdatePreviewMode(deltaTime); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +int ViewportWidget::GetTickOrder() +{ + return AZ::TICK_PRE_RENDER; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +void ViewportWidget::OnRenderTick() { if (!m_uiRenderer->IsReady() || !m_canvasRenderIsEnabled) { return; } -#ifdef LYSHINE_ATOM_TODO - gEnv->pRenderer->SetSrgbWrite(true); -#endif - const float dpiScale = QtHelpers::GetHighDpiScaleFactor(*this); ViewportIcon::SetDpiScaleFactor(dpiScale); - // Set up to render a frame to this viewport's window - GetViewportContext()->RenderTick(); - UiEditorMode editorMode = m_editorWindow->GetEditorMode(); if (editorMode == UiEditorMode::Edit) { - RenderEditMode(deltaTime); + RenderEditMode(); } else // if (editorMode == UiEditorMode::Preview) { - RenderPreviewMode(deltaTime); + RenderPreviewMode(); } } @@ -884,17 +905,37 @@ void ViewportWidget::OnFontTextureUpdated([[maybe_unused]] IFFont* font) m_fontTextureHasChanged = true; } +LyShine::AttachmentImagesAndDependencies ViewportWidget::GetRenderTargets() +{ + LyShine::AttachmentImagesAndDependencies canvasTargets; + + AZ::EntityId canvasEntityId = m_editorWindow->GetCanvasForCurrentEditorMode(); + if (canvasEntityId.IsValid()) + { + AZ::Entity* canvasEntity = nullptr; + EBUS_EVENT_RESULT(canvasEntity, AZ::ComponentApplicationBus, FindEntity, canvasEntityId); + AZ_Assert(canvasEntity, "Canvas entity not found by ID"); + if (canvasEntity) + { + UiCanvasComponent* canvasComponent = canvasEntity->FindComponent(); + AZ_Assert(canvasComponent, "Canvas entity has no canvas component"); + if (canvasComponent) + { + canvasComponent->GetRenderTargets(canvasTargets); + } + } + } + + return canvasTargets; +} + QPointF ViewportWidget::WidgetToViewport(const QPointF & point) const { return point * WidgetToViewportFactor(); } -void ViewportWidget::RenderEditMode(float deltaTime) +void ViewportWidget::UpdateEditMode(float deltaTime) { - // sort keys for different layers - static const int64_t backgroundKey = -0x1000; - static const int64_t topLayerKey = 0x1000000; - if (m_fontTextureHasChanged) { // A font texture has changed since we last rendered. Force a render graph update for each loaded canvas @@ -908,6 +949,28 @@ void ViewportWidget::RenderEditMode(float deltaTime) return; // this can happen if a render happens during a restart } + AZ::Vector2 canvasSize; + EBUS_EVENT_ID_RESULT(canvasSize, canvasEntityId, UiCanvasBus, GetCanvasSize); + + // Set the target size of the canvas + EBUS_EVENT_ID(canvasEntityId, UiCanvasBus, SetTargetCanvasSize, false, canvasSize); + + // Update this canvas (must be done after SetTargetCanvasSize) + EBUS_EVENT_ID(canvasEntityId, UiEditorCanvasBus, UpdateCanvasInEditorViewport, deltaTime, false); +} + +void ViewportWidget::RenderEditMode() +{ + // sort keys for different layers + static const int64_t backgroundKey = -0x1000; + static const int64_t topLayerKey = 0x1000000; + + AZ::EntityId canvasEntityId = m_editorWindow->GetCanvas(); + if (!canvasEntityId.IsValid()) + { + return; // this can happen if a render happens during a restart + } + Draw2dHelper draw2d(m_draw2d.get()); // sets and resets 2D draw mode in constructor/destructor QTreeWidgetItemRawPtrQList selection = m_editorWindow->GetHierarchy()->selectedItems(); @@ -936,9 +999,6 @@ void ViewportWidget::RenderEditMode(float deltaTime) // Set the target size of the canvas EBUS_EVENT_ID(canvasEntityId, UiCanvasBus, SetTargetCanvasSize, false, canvasSize); - // Update this canvas (must be done after SetTargetCanvasSize) - EBUS_EVENT_ID(canvasEntityId, UiEditorCanvasBus, UpdateCanvasInEditorViewport, deltaTime, false); - // Render this canvas QSize scaledViewportSize = QtHelpers::GetDpiScaledViewportSize(*this); AZ::Vector2 viewportSize(scaledViewportSize.width(), scaledViewportSize.height()); @@ -1037,11 +1097,8 @@ void ViewportWidget::RenderEditMode(float deltaTime) } } -void ViewportWidget::RenderPreviewMode(float deltaTime) +void ViewportWidget::UpdatePreviewMode(float deltaTime) { - // sort keys for different layers - static const int64_t backgroundKey = -0x1000; - AZ::EntityId canvasEntityId = m_editorWindow->GetPreviewModeCanvas(); if (m_fontTextureHasChanged) @@ -1051,6 +1108,37 @@ void ViewportWidget::RenderPreviewMode(float deltaTime) m_fontTextureHasChanged = false; } + if (canvasEntityId.IsValid()) + { + QSize scaledViewportSize = QtHelpers::GetDpiScaledViewportSize(*this); + AZ::Vector2 viewportSize(scaledViewportSize.width(), scaledViewportSize.height()); + + // Get the canvas size + AZ::Vector2 canvasSize = m_editorWindow->GetPreviewCanvasSize(); + if (canvasSize.GetX() == 0.0f && canvasSize.GetY() == 0.0f) + { + // special value of (0,0) means use the viewport size + canvasSize = viewportSize; + } + + // Set the target size of the canvas + EBUS_EVENT_ID(canvasEntityId, UiCanvasBus, SetTargetCanvasSize, true, canvasSize); + + // Update this canvas (must be done after SetTargetCanvasSize) + EBUS_EVENT_ID(canvasEntityId, UiEditorCanvasBus, UpdateCanvasInEditorViewport, deltaTime, true); + + // Execute events that have been queued during the canvas update + gEnv->pLyShine->ExecuteQueuedEvents(); + } +} + +void ViewportWidget::RenderPreviewMode() +{ + // sort keys for different layers + static const int64_t backgroundKey = -0x1000; + + AZ::EntityId canvasEntityId = m_editorWindow->GetPreviewModeCanvas(); + // Rather than scaling to exactly fit we try to draw at one of these preset scale factors // to make it it bit more obvious that the canvas size is changing float zoomScales[] = { @@ -1096,15 +1184,6 @@ void ViewportWidget::RenderPreviewMode(float deltaTime) } } - // Set the target size of the canvas - EBUS_EVENT_ID(canvasEntityId, UiCanvasBus, SetTargetCanvasSize, true, canvasSize); - - // Update this canvas (must be done after SetTargetCanvasSize) - EBUS_EVENT_ID(canvasEntityId, UiEditorCanvasBus, UpdateCanvasInEditorViewport, deltaTime, true); - - // Execute events that have been queued during the canvas update - gEnv->pLyShine->ExecuteQueuedEvents(); - // match scale to one of the predefined scales. If the scale is so small // that it is less than the smallest scale then leave it as it is for (int i = 0; i < AZ_ARRAY_SIZE(zoomScales); ++i) @@ -1131,14 +1210,6 @@ void ViewportWidget::RenderPreviewMode(float deltaTime) canvasToViewportMatrix.SetTranslation(translation); EBUS_EVENT_ID(canvasEntityId, UiCanvasBus, SetCanvasToViewportMatrix, canvasToViewportMatrix); -#ifdef LYSHINE_ATOM_TODO // mask support with Atom - // clear the stencil buffer before rendering each canvas - required for masking - // NOTE: the FRT_CLEAR_IMMEDIATE is required since we will not be setting the render target - // We also clear the color to a mid grey so that we can see the bounds of the canvas - ColorF viewportBackgroundColor(0.5f, 0.5f, 0.5f, 0); // if clearing color we want to set alpha to zero also - gEnv->pRenderer->ClearTargetsImmediately(FRT_CLEAR, viewportBackgroundColor); -#endif - m_draw2d->SetSortKey(backgroundKey); RenderViewportBackground(); diff --git a/Gems/LyShine/Code/Editor/ViewportWidget.h b/Gems/LyShine/Code/Editor/ViewportWidget.h index 0473b219dc..620cb8fb35 100644 --- a/Gems/LyShine/Code/Editor/ViewportWidget.h +++ b/Gems/LyShine/Code/Editor/ViewportWidget.h @@ -9,9 +9,11 @@ #if !defined(Q_MOC_RUN) #include "EditorCommon.h" +#include "LyShinePassDataBus.h" #include #include +#include #include @@ -27,6 +29,8 @@ class ViewportWidget : public AtomToolsFramework::RenderViewportWidget , private AzToolsFramework::EditorPickModeNotificationBus::Handler , private FontNotificationBus::Handler + , private LyShinePassDataRequestBus::Handler + , public AZ::RPI::ViewportContextNotificationBus::Handler { Q_OBJECT @@ -138,15 +142,29 @@ private: // member functions void OnFontTextureUpdated(IFFont* font) override; // ~FontNotifications + // LyShinePassDataRequestBus + LyShine::AttachmentImagesAndDependencies GetRenderTargets() override; + // ~LyShinePassDataRequestBus + // AZ::TickBus::Handler void OnTick(float deltaTime, AZ::ScriptTimePoint time) override; + int GetTickOrder() override; // ~AZ::TickBus::Handler + // AZ::RPI::ViewportContextNotificationBus::Handler overrides... + void OnRenderTick() override; + + //! Update UI canvases when in edit mode + void UpdateEditMode(float deltaTime); + //! Render the viewport when in edit mode - void RenderEditMode(float deltaTime); + void RenderEditMode(); + + //! Update UI canvases when in preview mode + void UpdatePreviewMode(float deltaTime); //! Render the viewport when in preview mode - void RenderPreviewMode(float deltaTime); + void RenderPreviewMode(); //! Fill the entire viewport area with a background color void RenderViewportBackground(); diff --git a/Gems/LyShine/Code/Source/Draw2d.cpp b/Gems/LyShine/Code/Source/Draw2d.cpp index 34ffb38fa3..2d3612fc07 100644 --- a/Gems/LyShine/Code/Source/Draw2d.cpp +++ b/Gems/LyShine/Code/Source/Draw2d.cpp @@ -9,6 +9,7 @@ #include // for SVF_P3F_C4B_T2F which will be removed in a coming PR #include +#include "LyShinePassDataBus.h" #include #include @@ -95,6 +96,12 @@ void CDraw2d::OnBootstrapSceneReady([[maybe_unused]] AZ::RPI::Scene* bootstrapSc AZ_Assert(scene != nullptr, "Attempting to create a DynamicDrawContext for a viewport context that has not been associated with a scene yet."); // Create and initialize a DynamicDrawContext for 2d drawing + + // Get the pass for the dynamic draw context to render to + AZ::RPI::RasterPass* uiCanvasPass = nullptr; + AZ::RPI::SceneId sceneId = scene->GetId(); + LyShinePassRequestBus::EventResult(uiCanvasPass, sceneId, &LyShinePassRequestBus::Events::GetUiCanvasPass); + m_dynamicDraw = AZ::RPI::DynamicDrawInterface::Get()->CreateDynamicDrawContext(); AZ::RPI::ShaderOptionList shaderOptions; shaderOptions.push_back(AZ::RPI::ShaderOption(AZ::Name("o_useColorChannels"), AZ::Name("true"))); @@ -106,7 +113,15 @@ void CDraw2d::OnBootstrapSceneReady([[maybe_unused]] AZ::RPI::Scene* bootstrapSc {"TEXCOORD0", AZ::RHI::Format::R32G32_FLOAT} }); m_dynamicDraw->AddDrawStateOptions(AZ::RPI::DynamicDrawContext::DrawStateOptions::PrimitiveType | AZ::RPI::DynamicDrawContext::DrawStateOptions::BlendMode); - m_dynamicDraw->SetOutputScope(scene.get()); + if (uiCanvasPass) + { + m_dynamicDraw->SetOutputScope(uiCanvasPass); + } + else + { + // Render target support is disabled + m_dynamicDraw->SetOutputScope(scene.get()); + } m_dynamicDraw->EndInit(); AZ::RHI::TargetBlendState targetBlendState; @@ -491,6 +506,7 @@ bool CDraw2d::GetDeferPrimitives() return m_deferCalls; } +//////////////////////////////////////////////////////////////////////////////////////////////////// void CDraw2d::SetSortKey(int64_t key) { m_dynamicDraw->SetSortKey(key); diff --git a/Gems/LyShine/Code/Source/LyShine.cpp b/Gems/LyShine/Code/Source/LyShine.cpp index f5edc4d7f1..c776dc11e9 100644 --- a/Gems/LyShine/Code/Source/LyShine.cpp +++ b/Gems/LyShine/Code/Source/LyShine.cpp @@ -163,6 +163,8 @@ CLyShine::CLyShine(ISystem* system) AzFramework::InputTextEventListener::Connect(); UiCursorBus::Handler::BusConnect(); AZ::TickBus::Handler::BusConnect(); + AZ::RPI::ViewportContextNotificationBus::Handler::BusConnect( + AZ::RPI::ViewportContextRequests::Get()->GetDefaultViewportContextName()); AZ::Render::Bootstrap::NotificationBus::Handler::BusConnect(); // These are internal Amazon components, so register them so that we can send back their names to our metrics collection @@ -240,9 +242,11 @@ CLyShine::~CLyShine() { UiCursorBus::Handler::BusDisconnect(); AZ::TickBus::Handler::BusDisconnect(); + AZ::RPI::ViewportContextNotificationBus::Handler::BusDisconnect(); AzFramework::InputTextEventListener::Disconnect(); AzFramework::InputChannelEventListener::Disconnect(); AZ::Render::Bootstrap::NotificationBus::Handler::BusDisconnect(); + LyShinePassDataRequestBus::Handler::BusDisconnect(); UiCanvasComponent::Shutdown(); @@ -642,15 +646,19 @@ void CLyShine::OnTick(float deltaTime, [[maybe_unused]] AZ::ScriptTimePoint time { // Update the loaded UI canvases Update(deltaTime); - - // Recreate dirty render graphs and send primitive data to the dynamic draw context - Render(); } //////////////////////////////////////////////////////////////////////////////////////////////////// int CLyShine::GetTickOrder() { - return AZ::TICK_UI; + return AZ::TICK_PRE_RENDER; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +void CLyShine::OnRenderTick() +{ + // Recreate dirty render graphs and send primitive data to the dynamic draw context + Render(); } //////////////////////////////////////////////////////////////////////////////////////////////////// @@ -658,6 +666,16 @@ void CLyShine::OnBootstrapSceneReady([[maybe_unused]] AZ::RPI::Scene* bootstrapS { // Load cursor if its path was set before RPI was initialized LoadUiCursor(); + + LyShinePassDataRequestBus::Handler::BusConnect(AZ::RPI::RPISystemInterface::Get()->GetDefaultScene()->GetId()); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +LyShine::AttachmentImagesAndDependencies CLyShine::GetRenderTargets() +{ + LyShine::AttachmentImagesAndDependencies attachmentImagesAndDependencies; + m_uiCanvasManager->GetRenderTargets(attachmentImagesAndDependencies); + return attachmentImagesAndDependencies; } //////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/Gems/LyShine/Code/Source/LyShine.h b/Gems/LyShine/Code/Source/LyShine.h index a7a8cab700..488131389a 100644 --- a/Gems/LyShine/Code/Source/LyShine.h +++ b/Gems/LyShine/Code/Source/LyShine.h @@ -16,8 +16,11 @@ #include #include +#include #include +#include "LyShinePassDataBus.h" + #if !defined(_RELEASE) #define LYSHINE_INTERNAL_UNIT_TEST #endif @@ -40,7 +43,9 @@ class CLyShine , public AzFramework::InputChannelEventListener , public AzFramework::InputTextEventListener , public AZ::TickBus::Handler + , public AZ::RPI::ViewportContextNotificationBus::Handler , protected AZ::Render::Bootstrap::NotificationBus::Handler + , protected LyShinePassDataRequestBus::Handler { public: @@ -111,10 +116,17 @@ public: int GetTickOrder() override; // ~TickEvents + // AZ::RPI::ViewportContextNotificationBus::Handler overrides... + void OnRenderTick() override; + // AZ::Render::Bootstrap::NotificationBus void OnBootstrapSceneReady(AZ::RPI::Scene* bootstrapScene) override; // ~AZ::Render::Bootstrap::NotificationBus + // LyShinePassDataRequestBus + LyShine::AttachmentImagesAndDependencies GetRenderTargets() override; + // ~LyShinePassDataRequestBus + // Get the UIRenderer for the game (which is owned by CLyShine). This is not exposed outside the gem. UiRenderer* GetUiRenderer(); diff --git a/Gems/LyShine/Code/Source/LyShinePass.cpp b/Gems/LyShine/Code/Source/LyShinePass.cpp new file mode 100644 index 0000000000..fbf7f34e14 --- /dev/null +++ b/Gems/LyShine/Code/Source/LyShinePass.cpp @@ -0,0 +1,274 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include "LyShinePass.h" + +namespace LyShine +{ + AZ::RPI::Ptr LyShinePass::Create(const AZ::RPI::PassDescriptor& descriptor) + { + return aznew LyShinePass(descriptor); + } + + LyShinePass::LyShinePass(const AZ::RPI::PassDescriptor& descriptor) + : Base(descriptor) + { + } + + LyShinePass::~LyShinePass() + { + LyShinePassRequestBus::Handler::BusDisconnect(); + } + + void LyShinePass::ResetInternal() + { + LyShinePassRequestBus::Handler::BusDisconnect(); + + Base::ResetInternal(); + } + + void LyShinePass::BuildInternal() + { + AZ::RPI::Scene* scene = GetScene(); + if (scene) + { + // Listen for rebuild requests + LyShinePassRequestBus::Handler::BusConnect(scene->GetId()); + + RemoveChildren(); + + // Get the current list of render targets being used across all loaded UI Canvases + LyShine::AttachmentImagesAndDependencies attachmentImagesAndDependencies; + LyShinePassDataRequestBus::EventResult( + attachmentImagesAndDependencies, + scene->GetId(), + &LyShinePassDataRequestBus::Events::GetRenderTargets + ); + + AddRttChildPasses(attachmentImagesAndDependencies); + AddUiCanvasChildPass(attachmentImagesAndDependencies); + } + + Base::BuildInternal(); + } + + void LyShinePass::RebuildRttChildren() + { + QueueForBuildAndInitialization(); + } + + AZ::RPI::RasterPass* LyShinePass::GetRttPass(const AZStd::string& name) + { + for (auto child:m_children) + { + if (child->GetName() == AZ::Name(name)) + { + return azrtti_cast(child.get()); + } + } + return nullptr; + } + + AZ::RPI::RasterPass* LyShinePass::GetUiCanvasPass() + { + return m_uiCanvasChildPass.get(); + } + + void LyShinePass::AddRttChildPasses(LyShine::AttachmentImagesAndDependencies attachmentImagesAndDependencies) + { + for (const auto& attachmentImageAndDependencies : attachmentImagesAndDependencies) + { + AddRttChildPass(attachmentImageAndDependencies.first, attachmentImageAndDependencies.second); + } + } + + void LyShinePass::AddRttChildPass(AZ::Data::Instance attachmentImage, AttachmentImages attachmentImageDependencies) + { + // Add a pass that renders to the specified texture + + // Create a pass template + auto passTemplate = AZStd::make_shared(); + passTemplate->m_name = "RttChildPass"; + passTemplate->m_passClass = AZ::Name("RttChildPass"); + + // Slots + passTemplate->m_slots.resize(2); + + AZ::RPI::PassSlot& depthInOutSlot = passTemplate->m_slots[0]; + depthInOutSlot.m_name = "DepthInputOutput"; + depthInOutSlot.m_slotType = AZ::RPI::PassSlotType::InputOutput; + depthInOutSlot.m_scopeAttachmentUsage = AZ::RHI::ScopeAttachmentUsage::DepthStencil; + depthInOutSlot.m_loadStoreAction.m_clearValue = AZ::RHI::ClearValue::CreateDepthStencil(0.0f, 0); + depthInOutSlot.m_loadStoreAction.m_loadActionStencil = AZ::RHI::AttachmentLoadAction::Clear; + + AZ::RPI::PassSlot& outSlot = passTemplate->m_slots[1]; + outSlot.m_name = AZ::Name("RenderTargetOutput"); + outSlot.m_slotType = AZ::RPI::PassSlotType::Output; + outSlot.m_scopeAttachmentUsage = AZ::RHI::ScopeAttachmentUsage::RenderTarget; + outSlot.m_loadStoreAction.m_clearValue = AZ::RHI::ClearValue::CreateVector4Float(0.0f, 0.0f, 0.0f, 0.0f); + outSlot.m_loadStoreAction.m_loadAction = AZ::RHI::AttachmentLoadAction::Clear; + + // Connections + passTemplate->m_connections.resize(1); + + AZ::RPI::PassConnection& depthInOutConnection = passTemplate->m_connections[0]; + depthInOutConnection.m_localSlot = "DepthInputOutput"; + depthInOutConnection.m_attachmentRef.m_pass = "Parent"; + depthInOutConnection.m_attachmentRef.m_attachment = "DepthInputOutput"; + + // Pass data + AZStd::shared_ptr passData = AZStd::make_shared(); + passData->m_drawListTag = AZ::Name("uicanvas"); + passData->m_pipelineViewTag = AZ::Name("MainCamera"); + auto size = attachmentImage->GetRHIImage()->GetDescriptor().m_size; + passData->m_overrideScissor = AZ::RHI::Scissor(0, 0, size.m_width, size.m_height); + passData->m_overrideViewport = AZ::RHI::Viewport(0, size.m_width, 0, size.m_height); + passTemplate->m_passData = AZStd::move(passData); + // Create a pass descriptor for the new child pass + AZ::RPI::PassDescriptor childDesc; + childDesc.m_passTemplate = passTemplate; + childDesc.m_passName = attachmentImage->GetAttachmentId(); + + AZ::RPI::PassSystemInterface* passSystem = AZ::RPI::PassSystemInterface::Get(); + AZ::RPI::Ptr rttChildPass = passSystem->CreatePass(childDesc); + AZ_Assert(rttChildPass, "[LyShinePass] Unable to create %s.", passTemplate->m_name.GetCStr()); + + // Store the info needed to attach to slots and set up frame graph dependencies + rttChildPass->m_attachmentImage = attachmentImage; + rttChildPass->m_attachmentImageDependencies = attachmentImageDependencies; + + AddChild(rttChildPass); + } + + void LyShinePass::AddUiCanvasChildPass(LyShine::AttachmentImagesAndDependencies AttachmentImagesAndDependencies) + { + if (!m_uiCanvasChildPass) + { + // Create a pass template + auto passTemplate = AZStd::make_shared(); + passTemplate->m_name = AZ::Name("LyShineChildPass"); + passTemplate->m_passClass = AZ::Name("LyShineChildPass"); + + // Slots + passTemplate->m_slots.resize(2); + + AZ::RPI::PassSlot& depthInOutSlot = passTemplate->m_slots[0]; + depthInOutSlot.m_name = "DepthInputOutput"; + depthInOutSlot.m_slotType = AZ::RPI::PassSlotType::InputOutput; + depthInOutSlot.m_scopeAttachmentUsage = AZ::RHI::ScopeAttachmentUsage::DepthStencil; + depthInOutSlot.m_loadStoreAction.m_clearValue = AZ::RHI::ClearValue::CreateDepthStencil(0.0f, 0); + depthInOutSlot.m_loadStoreAction.m_loadActionStencil = AZ::RHI::AttachmentLoadAction::Clear; + + AZ::RPI::PassSlot& inOutSlot = passTemplate->m_slots[1]; + inOutSlot.m_name = "ColorInputOutput"; + inOutSlot.m_slotType = AZ::RPI::PassSlotType::InputOutput; + inOutSlot.m_scopeAttachmentUsage = AZ::RHI::ScopeAttachmentUsage::RenderTarget; + + // Connections + passTemplate->m_connections.resize(2); + + AZ::RPI::PassConnection& depthInOutConnection = passTemplate->m_connections[0]; + depthInOutConnection.m_localSlot = "DepthInputOutput"; + depthInOutConnection.m_attachmentRef.m_pass = "Parent"; + depthInOutConnection.m_attachmentRef.m_attachment = "DepthInputOutput"; + + AZ::RPI::PassConnection& inOutConnection = passTemplate->m_connections[1]; + inOutConnection.m_localSlot = "ColorInputOutput"; + inOutConnection.m_attachmentRef.m_pass = "Parent"; + inOutConnection.m_attachmentRef.m_attachment = "ColorInputOutput"; + + // Pass data + AZStd::shared_ptr passData = AZStd::make_shared(); + passData->m_drawListTag = AZ::Name("uicanvas"); + passData->m_pipelineViewTag = AZ::Name("MainCamera"); + passTemplate->m_passData = AZStd::move(passData); + + // Create a pass descriptor for the new child pass + AZ::RPI::PassDescriptor childDesc; + childDesc.m_passTemplate = passTemplate; + childDesc.m_passName = AZ::Name("LyShineChildPass"); + + AZ::RPI::PassSystemInterface* passSystem = AZ::RPI::PassSystemInterface::Get(); + m_uiCanvasChildPass = passSystem->CreatePass(childDesc); + AZ_Assert(m_uiCanvasChildPass, "[LyShinePass] Unable to create %s.", passTemplate->m_name.GetCStr()); + } + + // Store the info needed to set up frame graph dependencies + m_uiCanvasChildPass->m_attachmentImageDependencies.clear(); + for (const auto& attachmentImageAndDescendents : AttachmentImagesAndDependencies) + { + m_uiCanvasChildPass->m_attachmentImageDependencies.emplace_back(attachmentImageAndDescendents.first); + } + + AddChild(m_uiCanvasChildPass); + } + + AZ::RPI::Ptr LyShineChildPass::Create(const AZ::RPI::PassDescriptor& descriptor) + { + return aznew LyShineChildPass(descriptor); + } + + LyShineChildPass::LyShineChildPass(const AZ::RPI::PassDescriptor& descriptor) + : RasterPass(descriptor) + { + } + + LyShineChildPass::~LyShineChildPass() + { + } + + void LyShineChildPass::SetupFrameGraphDependencies(AZ::RHI::FrameGraphInterface frameGraph) + { + AZ::RPI::RasterPass::SetupFrameGraphDependencies(frameGraph); + + for (auto attachmentImage : m_attachmentImageDependencies) + { + // Ensure that the image is imported into the attachment database. + // The image may not be imported if the owning pass has been disabled. + auto attachmentImageId = attachmentImage->GetAttachmentId(); + if (!frameGraph.GetAttachmentDatabase().IsAttachmentValid(attachmentImageId)) + { + frameGraph.GetAttachmentDatabase().ImportImage(attachmentImageId, attachmentImage->GetRHIImage()); + } + + AZ::RHI::ImageScopeAttachmentDescriptor desc; + desc.m_attachmentId = attachmentImageId; + desc.m_imageViewDescriptor = attachmentImage->GetImageView()->GetDescriptor(); + desc.m_loadStoreAction.m_loadAction = AZ::RHI::AttachmentLoadAction::Load; + + frameGraph.UseShaderAttachment(desc, AZ::RHI::ScopeAttachmentAccess::Read); + } + } + + AZ::RPI::Ptr RttChildPass::Create(const AZ::RPI::PassDescriptor& descriptor) + { + return aznew RttChildPass(descriptor); + } + + RttChildPass::RttChildPass(const AZ::RPI::PassDescriptor& descriptor) + : LyShineChildPass(descriptor) + { + } + + RttChildPass::~RttChildPass() + { + } + + void RttChildPass::BuildInternal() + { + AttachImageToSlot(AZ::Name("RenderTargetOutput"), m_attachmentImage); + } +} // namespace LyShine diff --git a/Gems/LyShine/Code/Source/LyShinePass.h b/Gems/LyShine/Code/Source/LyShinePass.h new file mode 100644 index 0000000000..6275353641 --- /dev/null +++ b/Gems/LyShine/Code/Source/LyShinePass.h @@ -0,0 +1,110 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ +#pragma once + +#include +#include +#include +#include +#include +#include "LyShinePassDataBus.h" + +namespace LyShine +{ + class LyShineChildPass; + + //! Manages child passes at runtime that render to render targets + class LyShinePass final + : public AZ::RPI::ParentPass + , protected LyShinePassRequestBus::Handler + { + AZ_RPI_PASS(LyShinePass); + using Base = AZ::RPI::ParentPass; + + public: + AZ_CLASS_ALLOCATOR(LyShinePass, AZ::SystemAllocator, 0); + AZ_RTTI(LyShinePass, "C3B812ED-3771-42F4-A96F-EBD94B4D54CA", Base); + + virtual ~LyShinePass(); + static AZ::RPI::Ptr Create(const AZ::RPI::PassDescriptor& descriptor); + + protected: + // Pass behavior overrides + void ResetInternal() override; + void BuildInternal() override; + + // LyShinePassRequestBus overrides + void RebuildRttChildren() override; + AZ::RPI::RasterPass* GetRttPass(const AZStd::string& name) override; + AZ::RPI::RasterPass* GetUiCanvasPass() override; + + private: + LyShinePass() = delete; + explicit LyShinePass(const AZ::RPI::PassDescriptor& descriptor); + + // Build the render to texture child passes + void AddRttChildPasses(LyShine::AttachmentImagesAndDependencies AttachmentImagesAndDependencies); + + // Add a render to texture child pass + void AddRttChildPass(AZ::Data::Instance attachmentImage, AttachmentImages dependentAttachmentImages); + + // Append the final pass to render UI Canvas elements to the screen + void AddUiCanvasChildPass(LyShine::AttachmentImagesAndDependencies AttachmentImagesAndDependencies); + + // Pass that renders the UI Canvas elements to the screen + AZ::RPI::Ptr m_uiCanvasChildPass; + }; + + // Child pass with potential attachment dependencies + class LyShineChildPass + : public AZ::RPI::RasterPass + { + AZ_RPI_PASS(LyShineChildPass); + + friend class LyShinePass; + public: + AZ_RTTI(LyShineChildPass, "{41D525F9-09EB-4004-97DC-082078FF8DD2}", RasterPass); + AZ_CLASS_ALLOCATOR(LyShineChildPass, AZ::SystemAllocator, 0); + virtual ~LyShineChildPass(); + + //! Creates a LyShineChildPass + static AZ::RPI::Ptr Create(const AZ::RPI::PassDescriptor& descriptor); + + protected: + LyShineChildPass(const AZ::RPI::PassDescriptor& descriptor); + + // Scope producer Overrides... + void SetupFrameGraphDependencies(AZ::RHI::FrameGraphInterface frameGraph) override; + + AttachmentImages m_attachmentImageDependencies; + }; + + // Child pass that renders UI elements to a render target + class RttChildPass + : public LyShineChildPass + { + AZ_RPI_PASS(RttChildPass); + + friend class LyShinePass; + + public: + AZ_RTTI(RttChildPass, "{54B0574D-2EB3-4054-9E1D-0E0D9C8CB09A}", LyShineChildPass); + AZ_CLASS_ALLOCATOR(RttChildPass, AZ::SystemAllocator, 0); + virtual ~RttChildPass(); + + //! Creates a RttChildPass + static AZ::RPI::Ptr Create(const AZ::RPI::PassDescriptor& descriptor); + + protected: + RttChildPass(const AZ::RPI::PassDescriptor& descriptor); + + // Pass behavior overrides + void BuildInternal() override; + + AZ::Data::Instance m_attachmentImage; + }; +} // namespace LyShine diff --git a/Gems/LyShine/Code/Source/LyShinePassDataBus.h b/Gems/LyShine/Code/Source/LyShinePassDataBus.h new file mode 100644 index 0000000000..a07e43bc44 --- /dev/null +++ b/Gems/LyShine/Code/Source/LyShinePassDataBus.h @@ -0,0 +1,61 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ +#pragma once + +#include +#include +#include +#include + +namespace AZ +{ + namespace RPI + { + class AttachmentImage; + class RasterPass; + } +} + +namespace LyShine +{ + using AttachmentImages = AZStd::vector>; + using AttachmentImageAndDependentsPair = AZStd::pair, AttachmentImages>; + using AttachmentImagesAndDependencies = AZStd::vector; +} + +class LyShinePassRequests + : public AZ::EBusTraits +{ +public: + static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single; + static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById; + using BusIdType = AZ::RPI::SceneId; + + //! Called when the number of render targets has changed and the LyShine pass needs to rebuild + virtual void RebuildRttChildren() = 0; + + //! Returns a render to texture pass based on render target name + virtual AZ::RPI::RasterPass* GetRttPass(const AZStd::string& name) = 0; + + //! Returns the final pass that renders the UI canvas contents + virtual AZ::RPI::RasterPass* GetUiCanvasPass() = 0; +}; +using LyShinePassRequestBus = AZ::EBus; + +class LyShinePassDataRequests + : public AZ::EBusTraits +{ +public: + static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single; + static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById; + using BusIdType = AZ::RPI::SceneId; + + //! Get a list of render targets that require a render to texture pass, and any + //! other render targets that are drawn on them + virtual LyShine::AttachmentImagesAndDependencies GetRenderTargets() = 0; +}; +using LyShinePassDataRequestBus = AZ::EBus; diff --git a/Gems/LyShine/Code/Source/LyShineSystemComponent.cpp b/Gems/LyShine/Code/Source/LyShineSystemComponent.cpp index 337fe27bc5..f05cbc04d4 100644 --- a/Gems/LyShine/Code/Source/LyShineSystemComponent.cpp +++ b/Gems/LyShine/Code/Source/LyShineSystemComponent.cpp @@ -49,6 +49,7 @@ #include "UiDynamicLayoutComponent.h" #include "UiDynamicScrollBoxComponent.h" #include "UiNavigationSettings.h" +#include "LyShinePass.h" namespace LyShine { @@ -113,9 +114,11 @@ namespace LyShine } //////////////////////////////////////////////////////////////////////////////////////////////////// - void LyShineSystemComponent::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required) + void LyShineSystemComponent::GetRequiredServices([[maybe_unused]] AZ::ComponentDescriptor::DependencyArrayType& required) { - (void)required; +#if !defined(LYSHINE_BUILDER) && !defined(LYSHINE_TESTS) + required.push_back(AZ_CRC("RPISystem", 0xf2add773)); +#endif } //////////////////////////////////////////////////////////////////////////////////////////////////// @@ -186,6 +189,17 @@ namespace LyShine RegisterComponentTypeForMenuOrdering(UiDynamicScrollBoxComponent::RTTI_Type()); RegisterComponentTypeForMenuOrdering(UiParticleEmitterComponent::RTTI_Type()); RegisterComponentTypeForMenuOrdering(UiFlipbookAnimationComponent::RTTI_Type()); + +#if !defined(LYSHINE_BUILDER) && !defined(LYSHINE_TESTS) + // Add LyShine pass + auto* passSystem = AZ::RPI::PassSystemInterface::Get(); + AZ_Assert(passSystem, "Cannot get the pass system."); + passSystem->AddPassCreator(AZ::Name("LyShinePass"), &LyShine::LyShinePass::Create); + + // Setup handler for load pass template mappings + m_loadTemplatesHandler = AZ::RPI::PassSystemInterface::OnReadyLoadTemplatesEvent::Handler([this]() { this->LoadPassTemplateMappings(); }); + AZ::RPI::PassSystemInterface::Get()->ConnectEvent(m_loadTemplatesHandler); +#endif } //////////////////////////////////////////////////////////////////////////////////////////////////// @@ -386,4 +400,13 @@ namespace LyShine { UiCursorBus::Broadcast(&UiCursorInterface::SetUiCursor, m_cursorImagePathname.GetAssetPath().c_str()); } + +#if !defined(LYSHINE_BUILDER) && !defined(LYSHINE_TESTS) + //////////////////////////////////////////////////////////////////////////////////////////////////// + void LyShineSystemComponent::LoadPassTemplateMappings() + { + const char* passTemplatesFile = "Passes/LyShinePassTemplates.azasset"; + AZ::RPI::PassSystemInterface::Get()->LoadPassTemplateMappings(passTemplatesFile); + } +#endif } diff --git a/Gems/LyShine/Code/Source/LyShineSystemComponent.h b/Gems/LyShine/Code/Source/LyShineSystemComponent.h index 9b5f32aa57..2e0d40e8b0 100644 --- a/Gems/LyShine/Code/Source/LyShineSystemComponent.h +++ b/Gems/LyShine/Code/Source/LyShineSystemComponent.h @@ -20,6 +20,10 @@ #include #include "LyShine.h" +#if !defined(LYSHINE_BUILDER) && !defined(LYSHINE_TESTS) +#include +#endif + namespace LyShine { // LyShine depends on the LegacyAllocator and CryStringAllocator. This will be managed @@ -90,6 +94,11 @@ namespace LyShine void BroadcastCursorImagePathname(); +#if !defined(LYSHINE_BUILDER) && !defined(LYSHINE_TESTS) + // Load pass template mappings for this gem + void LoadPassTemplateMappings(); +#endif + protected: // data CLyShine* m_pLyShine = nullptr; @@ -102,5 +111,9 @@ namespace LyShine // We only store this in order to generate metrics on LyShine specific components static const AZStd::list* m_componentDescriptors; + +#if !defined(LYSHINE_BUILDER) && !defined(LYSHINE_TESTS) + AZ::RPI::PassSystemInterface::OnReadyLoadTemplatesEvent::Handler m_loadTemplatesHandler; +#endif }; } diff --git a/Gems/LyShine/Code/Source/RenderGraph.cpp b/Gems/LyShine/Code/Source/RenderGraph.cpp index 9ee26cdf8e..ee26d1dd61 100644 --- a/Gems/LyShine/Code/Source/RenderGraph.cpp +++ b/Gems/LyShine/Code/Source/RenderGraph.cpp @@ -10,6 +10,9 @@ #include "UiRenderer.h" #include +#include + +#include #ifndef _RELEASE #include @@ -78,57 +81,28 @@ namespace LyShine } //////////////////////////////////////////////////////////////////////////////////////////////////// - void PrimitiveListRenderNode::Render(UiRenderer* uiRenderer) + void PrimitiveListRenderNode::Render(UiRenderer* uiRenderer + , const AZ::Matrix4x4& modelViewProjMat + , AZ::RHI::Ptr dynamicDraw) { -#ifdef LYSHINE_ATOM_TODO // keeping this code for reference for future phase (masks/render targets) - for (int i = 0; i < m_numTextures; ++i) - { - uiRenderer->SetTexture(m_textures[i].m_texture, i, m_textures[i].m_isClampTextureMode); - } - - int blendModeState = m_blendModeState; - - IRenderer* renderer = gEnv->pRenderer; - renderer->SetState(blendModeState | uiRenderer->GetBaseState()); - - if (m_isTextureSRGB) - { - renderer->SetSrgbWrite(false); - } - - // We are using SetColorOp as a way to set flags for the ui.cfx shader by reusing flags - // that the FixedPipelineEmu.cfx shader uses. So the names colorOp and alphaOp are used - // just because this are the inputs to SetColorOp. - uint8 colorOp = m_preMultiplyAlpha ? ColorOp_PreMultiplyAlpha : ColorOp_Normal; - uint8 alphaOp = AlphaOp_Normal; - switch (m_alphaMaskType) - { - case AlphaMaskType::None: - alphaOp = AlphaOp_Normal; - break; - case AlphaMaskType::ModulateAlpha: - alphaOp = AlphaOp_ModulateAlpha; - break; - case AlphaMaskType::ModulateAlphaAndColor: - alphaOp = AlphaOp_ModulateAlphaAndColor; - break; - } - - renderer->SetColorOp(colorOp, alphaOp, DEF_TEXARG0, DEF_TEXARG0); - - renderer->DrawDynUiPrimitiveList(m_primitives, m_totalNumVertices, m_totalNumIndices); - - if (m_isTextureSRGB) - { - renderer->SetSrgbWrite(true); - } -#endif if (!uiRenderer->IsReady()) { return; } - AZ::RHI::Ptr dynamicDraw = uiRenderer->GetDynamicDrawContext(); + UiRenderer::BaseState curBaseState = uiRenderer->GetBaseState(); + UiRenderer::BaseState prevBaseState = curBaseState; + if (m_isTextureSRGB) + { + curBaseState.m_srgbWrite = false; + } + + if (m_alphaMaskType == AlphaMaskType::ModulateAlpha) + { + curBaseState.m_modulateAlpha = true; + } + uiRenderer->SetBaseState(curBaseState); + const UiRenderer::UiShaderData& uiShaderData = uiRenderer->GetUiShaderData(); // Set render state @@ -167,7 +141,7 @@ namespace LyShine drawSrg->SetConstant(uiShaderData.m_isClampInputIndex, isClampTextureMode); // Set projection matrix - drawSrg->SetConstant(uiShaderData.m_viewProjInputIndex, uiRenderer->GetModelViewProjectionMatrix()); + drawSrg->SetConstant(uiShaderData.m_viewProjInputIndex, modelViewProjMat); drawSrg->Compile(); @@ -180,6 +154,8 @@ namespace LyShine { dynamicDraw->DrawIndexed(primitive.m_vertices, primitive.m_numVertices, primitive.m_indices, primitive.m_numIndices, AZ::RHI::IndexFormat::Uint16, drawSrg); } + + uiRenderer->SetBaseState(prevBaseState); } //////////////////////////////////////////////////////////////////////////////////////////////////// @@ -303,33 +279,35 @@ namespace LyShine } //////////////////////////////////////////////////////////////////////////////////////////////////// - void MaskRenderNode::Render(UiRenderer* uiRenderer) + void MaskRenderNode::Render(UiRenderer* uiRenderer + , const AZ::Matrix4x4& modelViewProjMat + , AZ::RHI::Ptr dynamicDraw) { UiRenderer::BaseState priorBaseState = uiRenderer->GetBaseState(); if (m_isMaskingEnabled || m_drawBehind) { - SetupBeforeRenderingMask(uiRenderer, true, priorBaseState); + SetupBeforeRenderingMask(uiRenderer, dynamicDraw, true, priorBaseState); for (RenderNode* renderNode : m_maskRenderNodes) { - renderNode->Render(uiRenderer); + renderNode->Render(uiRenderer, modelViewProjMat, dynamicDraw); } - SetupAfterRenderingMask(uiRenderer, true, priorBaseState); + SetupAfterRenderingMask(uiRenderer, dynamicDraw, true, priorBaseState); } for (RenderNode* renderNode : m_contentRenderNodes) { - renderNode->Render(uiRenderer); + renderNode->Render(uiRenderer, modelViewProjMat, dynamicDraw); } if (m_isMaskingEnabled || m_drawInFront) { - SetupBeforeRenderingMask(uiRenderer, false, priorBaseState); + SetupBeforeRenderingMask(uiRenderer, dynamicDraw, false, priorBaseState); for (RenderNode* renderNode : m_maskRenderNodes) { - renderNode->Render(uiRenderer); + renderNode->Render(uiRenderer, modelViewProjMat, dynamicDraw); } - SetupAfterRenderingMask(uiRenderer, false, priorBaseState); + SetupAfterRenderingMask(uiRenderer, dynamicDraw, false, priorBaseState); } } @@ -367,7 +345,9 @@ namespace LyShine #endif //////////////////////////////////////////////////////////////////////////////////////////////////// - void MaskRenderNode::SetupBeforeRenderingMask(UiRenderer* uiRenderer, bool firstPass, UiRenderer::BaseState priorBaseState) + void MaskRenderNode::SetupBeforeRenderingMask(UiRenderer* uiRenderer, + AZ::RHI::Ptr dynamicDraw, + bool firstPass, UiRenderer::BaseState priorBaseState) { UiRenderer::BaseState curBaseState = priorBaseState; @@ -406,7 +386,6 @@ namespace LyShine curBaseState.m_stencilState.m_backFace = stencilOpState; // set up for stencil write - AZ::RHI::Ptr dynamicDraw = uiRenderer->GetDynamicDrawContext(); dynamicDraw->SetStencilReference(uiRenderer->GetStencilRef()); curBaseState.m_stencilState.m_enable = true; curBaseState.m_stencilState.m_writeMask = 0xFF; @@ -421,7 +400,9 @@ namespace LyShine } //////////////////////////////////////////////////////////////////////////////////////////////////// - void MaskRenderNode::SetupAfterRenderingMask(UiRenderer* uiRenderer, bool firstPass, UiRenderer::BaseState priorBaseState) + void MaskRenderNode::SetupAfterRenderingMask(UiRenderer* uiRenderer, + AZ::RHI::Ptr dynamicDraw, + bool firstPass, UiRenderer::BaseState priorBaseState) { if (m_isMaskingEnabled) { @@ -439,7 +420,6 @@ namespace LyShine uiRenderer->DecrementStencilRef(); } - AZ::RHI::Ptr dynamicDraw = uiRenderer->GetDynamicDrawContext(); dynamicDraw->SetStencilReference(uiRenderer->GetStencilRef()); if (firstPass) @@ -474,16 +454,14 @@ namespace LyShine //////////////////////////////////////////////////////////////////////////////////////////////////// RenderTargetRenderNode::RenderTargetRenderNode( RenderTargetRenderNode* parentRenderTarget, - int renderTargetHandle, - SDepthTexture* renderTargetDepthSurface, + AZ::Data::Instance attachmentImage, const AZ::Vector2& viewportTopLeft, const AZ::Vector2& viewportSize, const AZ::Color& clearColor, int nestLevel) : RenderNode(RenderNodeType::RenderTarget) , m_parentRenderTarget(parentRenderTarget) - , m_renderTargetHandle(renderTargetHandle) - , m_renderTargetDepthSurface(renderTargetDepthSurface) + , m_attachmentImage(attachmentImage) , m_viewportX(viewportTopLeft.GetX()) , m_viewportY(viewportTopLeft.GetY()) , m_viewportWidth(viewportSize.GetX()) @@ -491,6 +469,13 @@ namespace LyShine , m_clearColor(clearColor) , m_nestLevel(nestLevel) { + AZ::MakeOrthographicMatrixRH(m_modelViewProjMat, + m_viewportX, + m_viewportX + m_viewportWidth, + m_viewportY + m_viewportHeight, + m_viewportY, + 0.0f, + 1.0f); } //////////////////////////////////////////////////////////////////////////////////////////////////// @@ -505,9 +490,11 @@ namespace LyShine } //////////////////////////////////////////////////////////////////////////////////////////////////// - void RenderTargetRenderNode::Render(UiRenderer* uiRenderer) + void RenderTargetRenderNode::Render(UiRenderer* uiRenderer + , [[maybe_unused]] const AZ::Matrix4x4& modelViewProjMat + , [[maybe_unused]] AZ::RHI::Ptr dynamicDraw) { - if (m_renderTargetHandle <= 0) + if (!m_attachmentImage) { return; } @@ -515,39 +502,52 @@ namespace LyShine ISystem* system = gEnv->pSystem; if (system && !gEnv->IsDedicated()) { - TransformationMatrices backupMatrices; - gEnv->pRenderer->Set2DModeNonZeroTopLeft(m_viewportX, m_viewportY, m_viewportWidth, m_viewportHeight, backupMatrices); - - // this will change the viewport - gEnv->pRenderer->SetRenderTarget(m_renderTargetHandle, m_renderTargetDepthSurface); - - // clear the render target before rendering to it - // NOTE: the FRT_CLEAR_IMMEDIATE is required since we will have already set the render target - // In theory we could call this before setting the render target without the immediate flag - // but that doesn't work. Perhaps because FX_Commit is not called. - ColorF viewportBackgroundColor(m_clearColor.GetR(), m_clearColor.GetG(), m_clearColor.GetB(), m_clearColor.GetA()); - gEnv->pRenderer->ClearTargetsImmediately(FRT_CLEAR, viewportBackgroundColor); - - // we could use SetSrgbWrite to write to a linear texture here. But that gets complicated with - // having to affect all decsendant element renders. So we just let it write srgb to the render target and - // allow for that when we render using the render target as a source texture. - - for (RenderNode* renderNode : m_childRenderNodes) + // Use a dedicated dynamic draw context for rendering to the texture since it can only have one draw list tag + if (!m_dynamicDraw) { - renderNode->Render(uiRenderer); + m_dynamicDraw = uiRenderer->CreateDynamicDrawContextForRTT(GetRenderTargetName()); } - gEnv->pRenderer->SetRenderTarget(0); // restore render target + if (m_dynamicDraw) + { + UiRenderer::BaseState priorBaseState = uiRenderer->GetBaseState(); - gEnv->pRenderer->Unset2DMode(backupMatrices); + UiRenderer::BaseState curBaseState = priorBaseState; + curBaseState.m_blendState.m_blendAlphaSource = AZ::RHI::BlendFactor::One; + curBaseState.m_blendState.m_blendAlphaDest = AZ::RHI::BlendFactor::AlphaSource1Inverse; + uiRenderer->SetBaseState(curBaseState); + + for (RenderNode* renderNode : m_childRenderNodes) + { + renderNode->Render(uiRenderer, m_modelViewProjMat, m_dynamicDraw); + } + + uiRenderer->SetBaseState(priorBaseState); + } + else + { + AZ_WarningOnce("UI", false, "Failed to create a Dynamic Draw Context for UI Element's render target. "\ + "Please ensure that the custom LyShinePass has been added to the project's main render pipeline."); + } } } //////////////////////////////////////////////////////////////////////////////////////////////////// const char* RenderTargetRenderNode::GetRenderTargetName() const { - ITexture* texture = gEnv->pRenderer->EF_GetTextureByID(m_renderTargetHandle); - return texture->GetName(); + return m_attachmentImage->GetRHIImage()->GetName().GetCStr(); + } + + //////////////////////////////////////////////////////////////////////////////////////////////////// + int RenderTargetRenderNode::GetNestLevel() const + { + return m_nestLevel; + } + + //////////////////////////////////////////////////////////////////////////////////////////////////// + const AZ::Data::Instance RenderTargetRenderNode::GetRenderTarget() const + { + return m_attachmentImage; } #ifndef _RELEASE @@ -671,31 +671,29 @@ namespace LyShine } //////////////////////////////////////////////////////////////////////////////////////////////////// - void RenderGraph::BeginRenderToTexture(int renderTargetHandle, SDepthTexture* renderTargetDepthSurface, + void RenderGraph::BeginRenderToTexture([[maybe_unused]] int renderTargetHandle, [[maybe_unused]] SDepthTexture* renderTargetDepthSurface, + [[maybe_unused]] const AZ::Vector2& viewportTopLeft, [[maybe_unused]] const AZ::Vector2& viewportSize, [[maybe_unused]] const AZ::Color& clearColor) + { + // LYSHINE_ATOM_TODO - this function will be removed when all IRenderer references are gone from UI components + } + + //////////////////////////////////////////////////////////////////////////////////////////////////// + void RenderGraph::BeginRenderToTexture(AZ::Data::Instance attachmentImage, const AZ::Vector2& viewportTopLeft, const AZ::Vector2& viewportSize, const AZ::Color& clearColor) { -#ifdef LYSHINE_ATOM_TODO // keeping this code for future phase (masks and render targets) // this uses pool allocator RenderTargetRenderNode* renderTargetRenderNode = new RenderTargetRenderNode( - m_currentRenderTarget, renderTargetHandle, renderTargetDepthSurface, + m_currentRenderTarget, attachmentImage, viewportTopLeft, viewportSize, clearColor, m_renderTargetNestLevel); m_currentRenderTarget = renderTargetRenderNode; m_renderNodeListStack.push(&m_currentRenderTarget->GetChildRenderNodeList()); m_renderTargetNestLevel++; -#else - AZ_UNUSED(clearColor); - AZ_UNUSED(viewportSize); - AZ_UNUSED(viewportTopLeft); - AZ_UNUSED(renderTargetDepthSurface); - AZ_UNUSED(renderTargetHandle); -#endif } //////////////////////////////////////////////////////////////////////////////////////////////////// void RenderGraph::EndRenderToTexture() { -#ifdef LYSHINE_ATOM_TODO // keeping this code for future phase (masks and render targets) AZ_Assert(m_currentRenderTarget, "Calling EndRenderToTexture while not defining a render target node"); if (m_currentRenderTarget) { @@ -709,7 +707,6 @@ namespace LyShine m_renderNodeListStack.pop(); m_renderTargetNestLevel--; } -#endif } void RenderGraph::AddPrimitive( @@ -803,11 +800,22 @@ namespace LyShine } //////////////////////////////////////////////////////////////////////////////////////////////////// - void RenderGraph::AddAlphaMaskPrimitive(IRenderer::DynUiPrimitive* primitive, - ITexture* texture, ITexture* maskTexture, - bool isClampTextureMode, bool isTextureSRGB, bool isTexturePremultipliedAlpha, BlendMode blendMode) + void RenderGraph::AddAlphaMaskPrimitive([[maybe_unused]] IRenderer::DynUiPrimitive* primitive, + [[maybe_unused]] ITexture* texture, [[maybe_unused]] ITexture* maskTexture, + [[maybe_unused]] bool isClampTextureMode, [[maybe_unused]] bool isTextureSRGB, [[maybe_unused]] bool isTexturePremultipliedAlpha, [[maybe_unused]] BlendMode blendMode) + { + // LYSHINE_ATOM_TODO - this function will be removed when all IRenderer references are gone from UI components + } + + //////////////////////////////////////////////////////////////////////////////////////////////////// + void RenderGraph::AddAlphaMaskPrimitiveAtom(IRenderer::DynUiPrimitive* primitive, + AZ::Data::Instance contentAttachmentImage, + AZ::Data::Instance maskAttachmentImage, + bool isClampTextureMode, + bool isTextureSRGB, + bool isTexturePremultipliedAlpha, + BlendMode blendMode) { -#ifdef LYSHINE_ATOM_TODO // keeping this code for future phase (masks and render targets) AZStd::vector* renderNodeList = m_renderNodeListStack.top(); int texUnit0 = -1; @@ -842,8 +850,8 @@ namespace LyShine { // render state is the same - we can add the primitive to this list if the texture is in // the list or there is space for another texture - texUnit0 = primListRenderNode->GetOrAddTexture(texture, true); - texUnit1 = primListRenderNode->GetOrAddTexture(maskTexture, true); + texUnit0 = primListRenderNode->GetOrAddTexture(contentAttachmentImage, true); + texUnit1 = primListRenderNode->GetOrAddTexture(maskAttachmentImage, true); if (texUnit0 != -1 && texUnit1 != -1) { @@ -857,7 +865,7 @@ namespace LyShine { // We can't add this primitive to the existing render node, we need to create a new render node // this uses a pool allocator for fast allocation - renderNodeToAddTo = new PrimitiveListRenderNode(texture, maskTexture, + renderNodeToAddTo = new PrimitiveListRenderNode(contentAttachmentImage, maskAttachmentImage, isClampTextureMode, isTextureSRGB, isPreMultiplyAlpha, alphaMaskType, blendModeState); renderNodeList->push_back(renderNodeToAddTo); @@ -881,15 +889,6 @@ namespace LyShine // add this primitive to the render node renderNodeToAddTo->AddPrimitive(primitive); } -#else - AZ_UNUSED(primitive); - AZ_UNUSED(texture); - AZ_UNUSED(maskTexture); - AZ_UNUSED(isClampTextureMode); - AZ_UNUSED(isTextureSRGB); - AZ_UNUSED(isTexturePremultipliedAlpha); - AZ_UNUSED(blendMode); -#endif } //////////////////////////////////////////////////////////////////////////////////////////////////// @@ -972,11 +971,8 @@ namespace LyShine } //////////////////////////////////////////////////////////////////////////////////////////////////// - void RenderGraph::Render(UiRenderer* uiRenderer, const AZ::Vector2& viewportSize) + void RenderGraph::Render(UiRenderer* uiRenderer, [[maybe_unused]] const AZ::Vector2& viewportSize) { - // LYSHINE_ATOM_TODO - will probably need to support this when converting UI Editor to use Atom - AZ_UNUSED(viewportSize); - AZ::RHI::Ptr dynamicDraw = uiRenderer->GetDynamicDrawContext(); // Disable stencil and enable blend/color write @@ -984,57 +980,35 @@ namespace LyShine dynamicDraw->SetTarget0BlendState(uiRenderer->GetBaseState().m_blendState); // First render the render targets, they are sorted so that more deeply nested ones are rendered first. - -#ifdef LYSHINE_ATOM_TODO // keeping this code for reference for future phase (render targets) // They only need to be rendered the first time that a render graph is rendered after it has been built. - // Though there is a special case, if this is the first time a shader variant has been used it can miss - // the first render. So to be safe we only stop rendering to render targets after we have rendered to - // them twice with no shader compiles initiated. - if (m_renderToRenderTargetCount < 2) + if (m_renderToRenderTargetCount == 0) + { + // Enable the Rtt passes to draw onto the render targets + SetRttPassesEnabled(uiRenderer, true); + } + + // LYSHINE_ATOM_TODO - It is currently necessary to render to the targets twice. Needs investigation + constexpr int timesToRenderToRenderTargets = 2; + if (m_renderToRenderTargetCount < timesToRenderToRenderTargets) { for (RenderNode* renderNode : m_renderTargetRenderNodes) { - renderNode->Render(uiRenderer); - } - - // if the render targets render OK we don't need to render them every frame. But if a new shader - // variant needed to be compiled then they will not have rendered OK. So we check is there are - // any shaders still in the process of compiling. Because they are compiled on the render - // thread, we may not know until the next frame that a shader needed to be compiled. So we need - // the counter. - SShaderCacheStatistics stats; - gEnv->pRenderer->EF_Query(EFQ_GetShaderCacheInfo, stats); - bool waitingOnShadersToCompile = stats.m_nNumShaderAsyncCompiles > 0 ? true : false; - if (!waitingOnShadersToCompile) - { - m_renderToRenderTargetCount++; - } - else - { - m_renderToRenderTargetCount = 0; + renderNode->Render(uiRenderer, uiRenderer->GetModelViewProjectionMatrix(), dynamicDraw); } + m_renderToRenderTargetCount++; } -#else - for (RenderNode* renderNode : m_renderTargetRenderNodes) + else if (m_renderToRenderTargetCount < timesToRenderToRenderTargets + 1) { - renderNode->Render(uiRenderer); + // Disable the rtt render passes since they don't need to be rendered to until the graph becomes invalidated again. + // This is also necessary to prevent the render targets' contents getting cleared on load by the pass. + SetRttPassesEnabled(uiRenderer, false); + m_renderToRenderTargetCount++; } -#endif -#ifdef LYSHINE_ATOM_TODO // keeping this code for reference for future phase (UI Editor) - // Set2DMode defines the viewport so we set it to canvas viewport here (the render target render nodes - // above will have set the viewport as they needed). - TransformationMatrices backupMatrices; - gEnv->pRenderer->Set2DMode(static_cast(viewportSize.GetX()), static_cast(viewportSize.GetY()), backupMatrices); -#endif for (RenderNode* renderNode : m_renderNodes) { - renderNode->Render(uiRenderer); + renderNode->Render(uiRenderer, uiRenderer->GetModelViewProjectionMatrix(), dynamicDraw); } -#ifdef LYSHINE_ATOM_TODO // keeping this code for reference for future phase (UI Editor) - // end the 2D mode - gEnv->pRenderer->Unset2DMode(backupMatrices); -#endif } //////////////////////////////////////////////////////////////////////////////////////////////////// @@ -1072,6 +1046,31 @@ namespace LyShine return m_renderNodes.empty(); } + //////////////////////////////////////////////////////////////////////////////////////////////////// + void RenderGraph::GetRenderTargetsAndDependencies(LyShine::AttachmentImagesAndDependencies& attachmentImagesAndDependencies) + { + for (RenderNode* renderNode : m_renderTargetRenderNodes) + { + const RenderTargetRenderNode* renderTargetRenderNode = static_cast(renderNode); + + if (renderTargetRenderNode->GetNestLevel() == 0) + { + LyShine::AttachmentImages attachmentImages; + const AZStd::vector& childNodeList = renderTargetRenderNode->GetChildRenderNodeList(); + for (auto& childNode : childNodeList) + { + if (childNode->GetType() == RenderNodeType::RenderTarget) + { + const RenderTargetRenderNode* childRenderTargetRenderNode = static_cast(childNode); + attachmentImages.emplace_back(childRenderTargetRenderNode->GetRenderTarget()); + } + } + + attachmentImagesAndDependencies.emplace_back(AttachmentImageAndDependentsPair(renderTargetRenderNode->GetRenderTarget(), attachmentImages)); + } + } + } + #ifndef _RELEASE //////////////////////////////////////////////////////////////////////////////////////////////////// void RenderGraph::ValidateGraph() @@ -1540,4 +1539,19 @@ namespace LyShine return flags; } + void RenderGraph::SetRttPassesEnabled(UiRenderer* uiRenderer, bool enabled) + { + // Enable or disable the rtt render passes + AZ::RPI::SceneId sceneId = uiRenderer->GetViewportContext()->GetRenderScene()->GetId(); + for (RenderTargetRenderNode* renderTargetRenderNode : m_renderTargetRenderNodes) + { + // Find the rtt pass to disable + AZ::RPI::RasterPass* rttPass = nullptr; + LyShinePassRequestBus::EventResult(rttPass, sceneId, &LyShinePassRequestBus::Events::GetRttPass, renderTargetRenderNode->GetRenderTargetName()); + if (rttPass) + { + rttPass->SetEnabled(enabled); + } + } + } } diff --git a/Gems/LyShine/Code/Source/RenderGraph.h b/Gems/LyShine/Code/Source/RenderGraph.h index 2529c0f47e..bc5bd094c8 100644 --- a/Gems/LyShine/Code/Source/RenderGraph.h +++ b/Gems/LyShine/Code/Source/RenderGraph.h @@ -15,10 +15,13 @@ #include #include +#include #include +#include #include #include "UiRenderer.h" +#include "LyShinePass.h" #ifndef _RELEASE #include "LyShineDebug.h" #endif @@ -46,7 +49,9 @@ namespace LyShine RenderNode(RenderNodeType type) : m_type(type) {} virtual ~RenderNode() {}; - virtual void Render(UiRenderer* uiRenderer) = 0; + virtual void Render(UiRenderer* uiRenderer + , const AZ::Matrix4x4& modelViewProjMat + , AZ::RHI::Ptr dynamicDraw) = 0; RenderNodeType GetType() const { return m_type; } @@ -70,7 +75,9 @@ namespace LyShine PrimitiveListRenderNode(const AZ::Data::Instance& texture, const AZ::Data::Instance& maskTexture, bool isClampTextureMode, bool isTextureSRGB, bool preMultiplyAlpha, AlphaMaskType alphaMaskType, int blendModeState); ~PrimitiveListRenderNode() override; - void Render(UiRenderer* uiRenderer) override; + void Render(UiRenderer* uiRenderer + , const AZ::Matrix4x4& modelViewProjMat + , AZ::RHI::Ptr dynamicDraw) override; void AddPrimitive(IRenderer::DynUiPrimitive* primitive); IRenderer::DynUiPrimitiveList& GetPrimitives() const; @@ -128,7 +135,9 @@ namespace LyShine MaskRenderNode(MaskRenderNode* parentMask, bool isMaskingEnabled, bool useAlphaTest, bool drawBehind, bool drawInFront); ~MaskRenderNode() override; - void Render(UiRenderer* uiRenderer) override; + void Render(UiRenderer* uiRenderer + , const AZ::Matrix4x4& modelViewProjMat + , AZ::RHI::Ptr dynamicDraw) override; AZStd::vector& GetMaskRenderNodeList() { return m_maskRenderNodes; } const AZStd::vector& GetMaskRenderNodeList() const { return m_maskRenderNodes; } @@ -152,8 +161,12 @@ namespace LyShine #endif private: // functions - void SetupBeforeRenderingMask(UiRenderer* uiRenderer, bool firstPass, UiRenderer::BaseState priorBaseState); - void SetupAfterRenderingMask(UiRenderer* uiRenderer, bool firstPass, UiRenderer::BaseState priorBaseState); + void SetupBeforeRenderingMask(UiRenderer* uiRenderer, + AZ::RHI::Ptr dynamicDraw, + bool firstPass, UiRenderer::BaseState priorBaseState); + void SetupAfterRenderingMask(UiRenderer* uiRenderer, + AZ::RHI::Ptr dynamicDraw, + bool firstPass, UiRenderer::BaseState priorBaseState); private: // data AZStd::vector m_maskRenderNodes; //!< The render nodes used to render the mask shape @@ -175,15 +188,17 @@ namespace LyShine // We use a pool allocator to keep these allocations fast. AZ_CLASS_ALLOCATOR(RenderTargetRenderNode, AZ::PoolAllocator, 0); - RenderTargetRenderNode(RenderTargetRenderNode* parentRenderTarget, int renderTargetHandle, - SDepthTexture* renderTargetDepthSurface, + RenderTargetRenderNode(RenderTargetRenderNode* parentRenderTarget, + AZ::Data::Instance attachmentImage, const AZ::Vector2& viewportTopLeft, const AZ::Vector2& viewportSize, const AZ::Color& clearColor, int nestLevel); ~RenderTargetRenderNode() override; - void Render(UiRenderer* uiRenderer) override; + void Render(UiRenderer* uiRenderer + , const AZ::Matrix4x4& modelViewProjMat + , AZ::RHI::Ptr dynamicDraw) override; AZStd::vector& GetChildRenderNodeList() { return m_childRenderNodes; } const AZStd::vector& GetChildRenderNodeList() const { return m_childRenderNodes; } @@ -197,6 +212,9 @@ namespace LyShine AZ::Color GetClearColor() const { return m_clearColor; } const char* GetRenderTargetName() const; + int GetNestLevel() const; + + const AZ::Data::Instance GetRenderTarget() const; #ifndef _RELEASE // A debug-only function useful for debugging @@ -213,13 +231,16 @@ namespace LyShine RenderTargetRenderNode* m_parentRenderTarget = nullptr; //! Used while building the render graph. - int m_renderTargetHandle = -1; - SDepthTexture* m_renderTargetDepthSurface = nullptr; + AZ::Data::Instance m_attachmentImage; + + // Each render target requires a unique dynamic draw context to draw to the raster pass associated with the target + AZ::RHI::Ptr m_dynamicDraw; float m_viewportX = 0; float m_viewportY = 0; float m_viewportWidth = 0; float m_viewportHeight = 0; + AZ::Matrix4x4 m_modelViewProjMat; AZ::Color m_clearColor; int m_nestLevel = 0; }; @@ -241,9 +262,10 @@ namespace LyShine void StartChildrenForMask() override; void EndMask() override; + //! Begin rendering to a texture void BeginRenderToTexture(int renderTargetHandle, SDepthTexture* renderTargetDepthSurface, - const AZ::Vector2& viewportTopLeft, const AZ::Vector2& viewportSize, - const AZ::Color& clearColor) override; + const AZ::Vector2& viewportTopLeft, const AZ::Vector2& viewportSize, const AZ::Color& clearColor) override; + void EndRenderToTexture() override; void AddPrimitive(IRenderer::DynUiPrimitive* primitive, ITexture* texture, @@ -268,6 +290,20 @@ namespace LyShine void AddPrimitiveAtom(IRenderer::DynUiPrimitive* primitive, const AZ::Data::Instance& texture, bool isClampTextureMode, bool isTextureSRGB, bool isTexturePremultipliedAlpha, BlendMode blendMode); + //! Add an indexed triangle list primitive to the render graph which will use maskTexture as an alpha (gradient) mask + void AddAlphaMaskPrimitiveAtom(IRenderer::DynUiPrimitive* primitive, + AZ::Data::Instance contentAttachmentImage, + AZ::Data::Instance maskAttachmentImage, + bool isClampTextureMode, + bool isTextureSRGB, + bool isTexturePremultipliedAlpha, + BlendMode blendMode); + + void BeginRenderToTexture(AZ::Data::Instance attachmentImage, + const AZ::Vector2& viewportTopLeft, + const AZ::Vector2& viewportSize, + const AZ::Color& clearColor); + //! Render the display graph void Render(UiRenderer* uiRenderer, const AZ::Vector2& viewportSize); @@ -283,6 +319,8 @@ namespace LyShine //! Test whether the render graph contains any render nodes bool IsEmpty(); + void GetRenderTargetsAndDependencies(LyShine::AttachmentImagesAndDependencies& attachmentImagesAndDependencies); + #ifndef _RELEASE // A debug-only function useful for debugging, not called but calls can be added during debugging void ValidateGraph(); @@ -311,6 +349,8 @@ namespace LyShine //! Given a blend mode and whether the shader will be outputing premultiplied alpha, return state flags int GetBlendModeState(LyShine::BlendMode blendMode, bool isShaderOutputPremultAlpha) const; + void SetRttPassesEnabled(UiRenderer* uiRenderer, bool enabled); + protected: // data AZStd::vector m_renderNodes; diff --git a/Gems/LyShine/Code/Source/RenderToTextureBus.h b/Gems/LyShine/Code/Source/RenderToTextureBus.h new file mode 100644 index 0000000000..310f1f93cf --- /dev/null +++ b/Gems/LyShine/Code/Source/RenderToTextureBus.h @@ -0,0 +1,22 @@ +/* + * 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 + +namespace LyShine +{ + //! Ebus to handle render target requests + class RenderToTextureRequests + : public AZ::ComponentBus + { + public: + virtual AZ::RHI::AttachmentId UseRenderTarget(const AZ::Name& renderTargetName, AZ::RHI::Size size) = 0; + virtual void ReleaseRenderTarget(const AZ::RHI::AttachmentId& attachmentId) = 0; + virtual AZ::Data::Instance GetRenderTarget(const AZ::RHI::AttachmentId& attachmentId) = 0; + }; + + using RenderToTextureRequestBus = AZ::EBus; +} diff --git a/Gems/LyShine/Code/Source/UiCanvasComponent.cpp b/Gems/LyShine/Code/Source/UiCanvasComponent.cpp index 72d9f92d1f..824b0d5698 100644 --- a/Gems/LyShine/Code/Source/UiCanvasComponent.cpp +++ b/Gems/LyShine/Code/Source/UiCanvasComponent.cpp @@ -49,6 +49,8 @@ #include #include +#include +#include #include "Animation/UiAnimationSystem.h" @@ -64,6 +66,8 @@ #include #endif +#include "LyShinePassDataBus.h" + //////////////////////////////////////////////////////////////////////////////////////////////////// //! UiCanvasNotificationBus Behavior context handler class class UiCanvasNotificationBusBehaviorHandler @@ -251,14 +255,22 @@ namespace UiRenderer* GetUiRendererForGame() { - CLyShine* lyShine = static_cast(gEnv->pLyShine); - return lyShine ? lyShine->GetUiRenderer() : nullptr; + if (gEnv && gEnv->pLyShine) + { + CLyShine* lyShine = static_cast(gEnv->pLyShine); + return lyShine->GetUiRenderer(); + } + return nullptr; } UiRenderer* GetUiRendererForEditor() { - CLyShine* lyShine = static_cast(gEnv->pLyShine); - return lyShine ? lyShine->GetUiRendererForEditor() : nullptr; + if (gEnv && gEnv->pLyShine) + { + CLyShine* lyShine = static_cast(gEnv->pLyShine); + return lyShine->GetUiRendererForEditor(); + } + return nullptr; } bool IsValidInteractable(const AZ::EntityId& entityId) @@ -1829,6 +1841,46 @@ void UiCanvasComponent::MarkRenderGraphDirty() } } +//////////////////////////////////////////////////////////////////////////////////////////////////// +AZ::RHI::AttachmentId UiCanvasComponent::UseRenderTarget(const AZ::Name& renderTargetName, AZ::RHI::Size size) +{ + // Create a render target that UI elements will render to + AZ::RHI::ImageDescriptor imageDesc; + imageDesc.m_bindFlags = AZ::RHI::ImageBindFlags::Color | AZ::RHI::ImageBindFlags::ShaderReadWrite; + imageDesc.m_size = size; + imageDesc.m_format = AZ::RHI::Format::R8G8B8A8_UNORM; + + AZ::Data::Instance pool = AZ::RPI::ImageSystemInterface::Get()->GetSystemAttachmentPool(); + auto attachmentImage = AZ::RPI::AttachmentImage::Create(*pool.get(), imageDesc, renderTargetName); + if (!attachmentImage) + { + AZ_Warning("UI", false, "Failed to create render target"); + return AZ::RHI::AttachmentId(); + } + + m_attachmentImageMap[attachmentImage->GetAttachmentId()] = attachmentImage; + + // Notify LyShine render pass that it needs to rebuild + QueueRttPassRebuild(); + + return attachmentImage->GetAttachmentId(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +void UiCanvasComponent::ReleaseRenderTarget(const AZ::RHI::AttachmentId& attachmentId) +{ + m_attachmentImageMap.erase(attachmentId); + + // Notify LyShine render pass that it needs to rebuild + QueueRttPassRebuild(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +AZ::Data::Instance UiCanvasComponent::GetRenderTarget(const AZ::RHI::AttachmentId& attachmentId) +{ + return m_attachmentImageMap[attachmentId]; +} + //////////////////////////////////////////////////////////////////////////////////////////////////// void UiCanvasComponent::UpdateCanvas(float deltaTime, bool isInGame) { @@ -1864,6 +1916,8 @@ void UiCanvasComponent::RenderCanvas(bool isInGame, AZ::Vector2 viewportSize, Ui return; } + m_renderInEditor = uiRenderer ? true : false; + if (!uiRenderer) { uiRenderer = GetUiRendererForGame(); @@ -1948,6 +2002,12 @@ void UiCanvasComponent::ScheduleElementDestroy(AZ::EntityId entityId) m_elementsScheduledForDestroy.push_back(entityId); } +//////////////////////////////////////////////////////////////////////////////////////////////////// +void UiCanvasComponent::GetRenderTargets(LyShine::AttachmentImagesAndDependencies& attachmentImagesAndDependencies) +{ + m_renderGraph.GetRenderTargetsAndDependencies(attachmentImagesAndDependencies); +} + //////////////////////////////////////////////////////////////////////////////////////////////////// void UiCanvasComponent::DestroyScheduledElements() { @@ -1959,6 +2019,17 @@ void UiCanvasComponent::DestroyScheduledElements() m_elementsScheduledForDestroy.clear(); } +//////////////////////////////////////////////////////////////////////////////////////////////////// +void UiCanvasComponent::QueueRttPassRebuild() +{ + UiRenderer* uiRenderer = m_renderInEditor ? GetUiRendererForEditor() : GetUiRendererForGame(); + if (uiRenderer && uiRenderer->GetViewportContext()) // can be null in automated testing + { + AZ::RPI::SceneId sceneId = uiRenderer->GetViewportContext()->GetRenderScene()->GetId(); + EBUS_EVENT_ID(sceneId, LyShinePassRequestBus, RebuildRttChildren); + } +} + //////////////////////////////////////////////////////////////////////////////////////////////////// #ifndef _RELEASE void UiCanvasComponent::GetDebugInfoInteractables(AZ::EntityId& activeInteractable, AZ::EntityId& hoverInteractable) const @@ -2350,6 +2421,7 @@ void UiCanvasComponent::Activate() UiCanvasComponentImplementationBus::Handler::BusConnect(m_entity->GetId()); UiEditorCanvasBus::Handler::BusConnect(m_entity->GetId()); UiAnimationBus::Handler::BusConnect(m_entity->GetId()); + LyShine::RenderToTextureRequestBus::Handler::BusConnect(m_entity->GetId()); // Reconnect to buses that we connect to intermittently // This will only happen if we have been deactivated and reactivated at runtime @@ -2382,6 +2454,7 @@ void UiCanvasComponent::Deactivate() UiCanvasComponentImplementationBus::Handler::BusDisconnect(); UiEditorCanvasBus::Handler::BusDisconnect(); UiAnimationBus::Handler::BusDisconnect(); + LyShine::RenderToTextureRequestBus::Handler::BusDisconnect(); // disconnect from any other buses we could be connected to if (m_hoverInteractable.IsValid() && AZ::EntityBus::Handler::BusIsConnectedId(m_hoverInteractable)) @@ -2400,6 +2473,12 @@ void UiCanvasComponent::Deactivate() DestroyRenderTarget(); } + // Destroy owned render targets + m_attachmentImageMap.clear(); + + //! Notify LyShine pass that it needs to rebuild + QueueRttPassRebuild(); + delete m_layoutManager; m_layoutManager = nullptr; diff --git a/Gems/LyShine/Code/Source/UiCanvasComponent.h b/Gems/LyShine/Code/Source/UiCanvasComponent.h index 852bb482d0..0848e368fa 100644 --- a/Gems/LyShine/Code/Source/UiCanvasComponent.h +++ b/Gems/LyShine/Code/Source/UiCanvasComponent.h @@ -32,6 +32,8 @@ #include "TextureAtlas/TextureAtlasBus.h" #include "TextureAtlas/TextureAtlasNotificationBus.h" +#include "RenderToTextureBus.h" + namespace AZ { class SerializeContext; @@ -51,6 +53,7 @@ class UiCanvasComponent , public IUiAnimationListener , public UiEditorCanvasBus::Handler , public UiCanvasComponentImplementationBus::Handler + , public LyShine::RenderToTextureRequestBus::Handler { public: // constants static const AZ::Vector2 s_defaultCanvasSize; @@ -232,6 +235,12 @@ public: // member functions void MarkRenderGraphDirty() override; // ~UiCanvasComponentImplementationInterface + // RenderToTextureRequests + AZ::RHI::AttachmentId UseRenderTarget(const AZ::Name& renderTargetName, AZ::RHI::Size size) override; + void ReleaseRenderTarget(const AZ::RHI::AttachmentId& attachmentId) override; + AZ::Data::Instance GetRenderTarget(const AZ::RHI::AttachmentId& attachmentId) override; + // ~RenderToTextureRequests + void UpdateCanvas(float deltaTime, bool isInGame); void RenderCanvas(bool isInGame, AZ::Vector2 viewportSize, UiRenderer* uiRenderer = nullptr); @@ -257,6 +266,10 @@ public: // member functions //! Queue an element to be destroyed at end of frame void ScheduleElementDestroy(AZ::EntityId entityId); + bool IsRenderGraphDirty() { return m_renderGraph.GetDirtyFlag(); } + + void GetRenderTargets(LyShine::AttachmentImagesAndDependencies& attachmentImagesAndDependencies); + #ifndef _RELEASE struct DebugInfoNumElements { @@ -427,6 +440,9 @@ private: // member functions void DestroyScheduledElements(); + //! Notify LyShine pass that it needs to rebuild its Rtt child passes + void QueueRttPassRebuild(); + private: // static member functions static AZ::u64 CreateUniqueId(); @@ -597,4 +613,8 @@ private: // static data LyShine::RenderGraph m_renderGraph; //!< the render graph for rendering the canvas, can be cached between frames bool m_isRendering = false; + bool m_renderInEditor = false; //!< indicates whether this canvas will render in the Editor viewport or the Game viewport + + //! Map of attachments used by this canvas's elements + AZStd::unordered_map> m_attachmentImageMap; }; diff --git a/Gems/LyShine/Code/Source/UiCanvasManager.cpp b/Gems/LyShine/Code/Source/UiCanvasManager.cpp index cb27f09672..0672d7b5f6 100644 --- a/Gems/LyShine/Code/Source/UiCanvasManager.cpp +++ b/Gems/LyShine/Code/Source/UiCanvasManager.cpp @@ -301,6 +301,17 @@ void UiCanvasManager::OnFontTextureUpdated([[maybe_unused]] IFFont* font) m_fontTextureHasChanged = true; } +//////////////////////////////////////////////////////////////////////////////////////////////////// +void UiCanvasManager::GetRenderTargets(LyShine::AttachmentImagesAndDependencies& attachmentImagesAndDependencies) +{ + for (auto canvas : m_loadedCanvases) + { + LyShine::AttachmentImagesAndDependencies canvasTargets; + canvas->GetRenderTargets(canvasTargets); + attachmentImagesAndDependencies.insert(attachmentImagesAndDependencies.end(), canvasTargets.begin(), canvasTargets.end()); + } +} + //////////////////////////////////////////////////////////////////////////////////////////////////// void UiCanvasManager::OnCatalogAssetChanged(const AZ::Data::AssetId& assetId) { @@ -606,13 +617,6 @@ void UiCanvasManager::RenderLoadedCanvases() m_fontTextureHasChanged = false; } -#ifdef LYSHINE_ATOM_TODO // render target conversion to Atom - // clear the stencil buffer before rendering the loaded canvases - required for masking - // NOTE: We want to use ClearTargetsImmediately instead of ClearTargetsLater since we will not be setting the render target - ColorF viewportBackgroundColor(0, 0, 0, 0); // if clearing color we want to set alpha to zero also - gEnv->pRenderer->ClearTargetsImmediately(FRT_CLEAR_STENCIL, viewportBackgroundColor); -#endif - for (auto canvas : m_loadedCanvases) { if (!canvas->GetIsRenderToTexture()) diff --git a/Gems/LyShine/Code/Source/UiCanvasManager.h b/Gems/LyShine/Code/Source/UiCanvasManager.h index 85783fab84..a2bf2dde97 100644 --- a/Gems/LyShine/Code/Source/UiCanvasManager.h +++ b/Gems/LyShine/Code/Source/UiCanvasManager.h @@ -11,6 +11,7 @@ #include #include #include +#include "LyShinePassDataBus.h" #include class UiCanvasComponent; @@ -92,6 +93,9 @@ public: // member functions bool HandleInputEventForLoadedCanvases(const AzFramework::InputChannel& inputChannel); bool HandleTextEventForLoadedCanvases(const AZStd::string& textUTF8); + // Get the render targets used by all currently loaded UI Canvases + void GetRenderTargets(LyShine::AttachmentImagesAndDependencies& attachmentImagesAndDependencies); + #ifndef _RELEASE void DebugDisplayCanvasData(int setting) const; void DebugDisplayDrawCallData() const; diff --git a/Gems/LyShine/Code/Source/UiFaderComponent.cpp b/Gems/LyShine/Code/Source/UiFaderComponent.cpp index dbc3981056..f195689a43 100644 --- a/Gems/LyShine/Code/Source/UiFaderComponent.cpp +++ b/Gems/LyShine/Code/Source/UiFaderComponent.cpp @@ -6,6 +6,7 @@ * */ #include "UiFaderComponent.h" +#include "RenderGraph.h" #include #include @@ -14,6 +15,9 @@ #include #include +#include +#include + #include #include #include @@ -22,6 +26,7 @@ #include #include "UiSerialize.h" +#include "RenderToTextureBus.h" // BehaviorContext UiFaderNotificationBus forwarder class BehaviorUiFaderNotificationBusHandler @@ -120,7 +125,7 @@ void UiFaderComponent::Render(LyShine::IRenderGraph* renderGraph, UiElementInter AZ::Vector2 renderTargetSize = pixelAlignedBottomRight - pixelAlignedTopLeft; bool needsResize = static_cast(renderTargetSize.GetX()) != m_renderTargetWidth || static_cast(renderTargetSize.GetY()) != m_renderTargetHeight; - if (m_renderTargetHandle == -1 || needsResize) + if (m_attachmentImageId.IsEmpty() || needsResize) { // We delay first creation of the render target until render time since size is not known in Activate // We also call this if the size has changed @@ -128,7 +133,7 @@ void UiFaderComponent::Render(LyShine::IRenderGraph* renderGraph, UiElementInter } // if the render target failed to be created (zero size for example) we don't render the element at all - if (m_renderTargetHandle == -1) + if (m_attachmentImageId.IsEmpty()) { return; } @@ -139,7 +144,7 @@ void UiFaderComponent::Render(LyShine::IRenderGraph* renderGraph, UiElementInter else { // destroy previous render target, if exists - if (m_renderTargetHandle != -1) + if (!m_attachmentImageId.IsEmpty()) { DestroyRenderTarget(); } @@ -452,54 +457,22 @@ void UiFaderComponent::CreateOrResizeRenderTarget(const AZ::Vector2& pixelAligne m_viewportTopLeft = pixelAlignedTopLeft; m_viewportSize = renderTargetSize; -#ifdef LYSHINE_ATOM_TODO // [LYN-3359] Support RTT using Atom - // Check if the render target already exists - if (m_renderTargetHandle != -1) - { - // Render target exists, resize it to the given size - if (!gEnv->pRenderer->ResizeRenderTarget(m_renderTargetHandle, static_cast(renderTargetSize.GetX()), static_cast(renderTargetSize.GetY()))) - { - AZ_Warning("UI", false, "Failed to resize render target for UiFaderComponent"); - DestroyRenderTarget(); - } - } - else - { - // Create a render target that this element and its children will be rendered to. - m_renderTargetHandle = gEnv->pRenderer->CreateRenderTarget(m_renderTargetName.c_str(), - static_cast(renderTargetSize.GetX()), static_cast(renderTargetSize.GetY()), Clr_Transparent, eTF_R8G8B8A8); + // LYSHINE_ATOM_TODO: optimize by reusing/resizing targets + DestroyRenderTarget(); - if (m_renderTargetHandle == -1) - { - AZ_Warning("UI", false, "Failed to create render target for UiFaderComponent"); - } - } - - // if depth surface already exists then destroy it - if (m_renderTargetDepthSurface) + // Create a render target that this element and its children will be rendered to + AZ::EntityId canvasEntityId; + EBUS_EVENT_ID_RESULT(canvasEntityId, GetEntityId(), UiElementBus, GetCanvasEntityId); + AZ::RHI::Size imageSize(renderTargetSize.GetX(), renderTargetSize.GetY(), 1); + EBUS_EVENT_ID_RESULT(m_attachmentImageId, canvasEntityId, LyShine::RenderToTextureRequestBus, UseRenderTarget, AZ::Name(m_renderTargetName.c_str()), imageSize); + if (m_attachmentImageId.IsEmpty()) { - gEnv->pRenderer->DestroyDepthSurface(m_renderTargetDepthSurface); - m_renderTargetDepthSurface = nullptr; + AZ_Warning("UI", false, "Failed to create render target for UiFaderComponent"); } - if (m_renderTargetHandle != -1) - { - // Also create a depth surface to render the canvas to, we need depth for masking - // since that uses the stencil buffer. We support any combination of nesting faders and masks - m_renderTargetDepthSurface = gEnv->pRenderer->CreateDepthSurface( - static_cast(renderTargetSize.GetX()), static_cast(renderTargetSize.GetY())); - - if (!m_renderTargetDepthSurface) - { - AZ_Warning("UI", false, "Failed to create depth surface for UiFaderComponent"); - DestroyRenderTarget(); - } - } -#endif - // at this point either all render targets and depth surfaces are created or none are. // If all succeeded then update the render target size - if (m_renderTargetHandle != -1) + if (!m_attachmentImageId.IsEmpty()) { m_renderTargetWidth = static_cast(renderTargetSize.GetX()); m_renderTargetHeight = static_cast(renderTargetSize.GetY()); @@ -511,16 +484,12 @@ void UiFaderComponent::CreateOrResizeRenderTarget(const AZ::Vector2& pixelAligne //////////////////////////////////////////////////////////////////////////////////////////////////// void UiFaderComponent::DestroyRenderTarget() { - if (m_renderTargetHandle != -1) + if (!m_attachmentImageId.IsEmpty()) { - gEnv->pRenderer->DestroyRenderTarget(m_renderTargetHandle); - m_renderTargetHandle = -1; - } - - if (m_renderTargetDepthSurface) - { - gEnv->pRenderer->DestroyDepthSurface(m_renderTargetDepthSurface); - m_renderTargetDepthSurface = nullptr; + AZ::EntityId canvasEntityId; + EBUS_EVENT_ID_RESULT(canvasEntityId, GetEntityId(), UiElementBus, GetCanvasEntityId); + EBUS_EVENT_ID(canvasEntityId, LyShine::RenderToTextureRequestBus, ReleaseRenderTarget, m_attachmentImageId); + m_attachmentImageId = AZ::RHI::AttachmentId{}; } } @@ -594,14 +563,20 @@ void UiFaderComponent::RenderStandardFader(LyShine::IRenderGraph* renderGraph, U void UiFaderComponent::RenderRttFader(LyShine::IRenderGraph* renderGraph, UiElementInterface* elementInterface, UiRenderInterface* renderInterface, int numChildren, bool isInGame) { + // Get the render target + AZ::Data::Instance attachmentImage; + AZ::EntityId canvasEntityId; + EBUS_EVENT_ID_RESULT(canvasEntityId, GetEntityId(), UiElementBus, GetCanvasEntityId); + EBUS_EVENT_ID_RESULT(attachmentImage, canvasEntityId, LyShine::RenderToTextureRequestBus, GetRenderTarget, m_attachmentImageId); + // Render the element and its children to a render target { // we always clear to transparent black - the accumulation of alpha in the render target requires it AZ::Color clearColor(0.0f, 0.0f, 0.0f, 0.0f); // Start building the render to texture node in the render graph - renderGraph->BeginRenderToTexture(m_renderTargetHandle, m_renderTargetDepthSurface, - m_viewportTopLeft, m_viewportSize, clearColor); + LyShine::RenderGraph* lyRenderGraph = dynamic_cast(renderGraph); + lyRenderGraph->BeginRenderToTexture(attachmentImage, m_viewportTopLeft, m_viewportSize, clearColor); // We don't want this fader or parent faders to affect what is rendered to the render target since we will // apply those fades when we render from the render target. @@ -624,14 +599,13 @@ void UiFaderComponent::RenderRttFader(LyShine::IRenderGraph* renderGraph, UiElem float desiredAlpha = renderGraph->GetAlphaFade() * m_fade; uint8 desiredPackedAlpha = static_cast(desiredAlpha * 255.0f); - UCol desiredPackedColor; - // This is a special case. We have an input texture that already has premultiplied alpha. - // So we tell the shader not to premultiply the output colors and we premultiply the alpha - // into the vertex colors so that they are premultiplied too. - desiredPackedColor.r = desiredPackedColor.g = desiredPackedColor.b = desiredPackedColor.a = desiredPackedAlpha; - if (m_cachedPrimitive.m_vertices[0].color.dcolor != desiredPackedColor.dcolor) + // If the fade value has changed we need to update the alpha values in the vertex colors but we do + // not want to touch or recompute the RGB values + if (m_cachedPrimitive.m_vertices[0].color.a != desiredPackedAlpha) { - // go through the cached vertices and update the color values + // go through all the cached vertices and update the alpha values + UCol desiredPackedColor = m_cachedPrimitive.m_vertices[0].color; + desiredPackedColor.a = desiredPackedAlpha; for (int i = 0; i < m_cachedPrimitive.m_numVertices; ++i) { m_cachedPrimitive.m_vertices[i].color = desiredPackedColor; @@ -639,21 +613,20 @@ void UiFaderComponent::RenderRttFader(LyShine::IRenderGraph* renderGraph, UiElem } } -#ifdef LYSHINE_ATOM_TODO // [LYN-3359] Support RTT using Atom // Add a primitive to render a quad using the render target we have created { - // Set the texture and other render state required - ITexture* texture = gEnv->pRenderer->EF_GetTextureByID(m_renderTargetHandle); - bool isClampTextureMode = true; - bool isTextureSRGB = true; - bool isTexturePremultipliedAlpha = true; - LyShine::BlendMode blendMode = LyShine::BlendMode::Normal; - - // add a render node to render from the render target texture to the current target - renderGraph->AddPrimitive(&m_cachedPrimitive, texture, - isClampTextureMode, isTextureSRGB, isTexturePremultipliedAlpha, blendMode); + LyShine::RenderGraph* lyRenderGraph = dynamic_cast(renderGraph); + if (lyRenderGraph) + { + // Set the texture and other render state required + AZ::Data::Instance image = attachmentImage; + bool isClampTextureMode = true; + bool isTextureSRGB = true; + bool isTexturePremultipliedAlpha = true; + LyShine::BlendMode blendMode = LyShine::BlendMode::Normal; + lyRenderGraph->AddPrimitiveAtom(&m_cachedPrimitive, image, isClampTextureMode, isTextureSRGB, isTexturePremultipliedAlpha, blendMode); + } } -#endif } } diff --git a/Gems/LyShine/Code/Source/UiFaderComponent.h b/Gems/LyShine/Code/Source/UiFaderComponent.h index a6960a5e36..560c1beaaa 100644 --- a/Gems/LyShine/Code/Source/UiFaderComponent.h +++ b/Gems/LyShine/Code/Source/UiFaderComponent.h @@ -18,6 +18,7 @@ #include #include +#include //////////////////////////////////////////////////////////////////////////////////////////////////// class UiFaderComponent @@ -156,11 +157,8 @@ private: // data //! This is generated from the entity ID and cached AZStd::string m_renderTargetName; - //! When rendering to a texture this is the texture ID of the render target - int m_renderTargetHandle = -1; - - //! When rendering to a texture this is our depth surface - SDepthTexture* m_renderTargetDepthSurface = nullptr; + //! When rendering to a texture this is the attachment image for the render target + AZ::RHI::AttachmentId m_attachmentImageId; //! The positions used for the render to texture viewport and to render the render target to the screen AZ::Vector2 m_viewportTopLeft; diff --git a/Gems/LyShine/Code/Source/UiMaskComponent.cpp b/Gems/LyShine/Code/Source/UiMaskComponent.cpp index 4ecb558b2b..2c1763e4ec 100644 --- a/Gems/LyShine/Code/Source/UiMaskComponent.cpp +++ b/Gems/LyShine/Code/Source/UiMaskComponent.cpp @@ -14,12 +14,17 @@ #include #include "IRenderer.h" +#include "RenderToTextureBus.h" +#include "RenderGraph.h" #include #include #include #include #include +#include +#include + //////////////////////////////////////////////////////////////////////////////////////////////////// // PUBLIC MEMBER FUNCTIONS //////////////////////////////////////////////////////////////////////////////////////////////////// @@ -79,7 +84,7 @@ void UiMaskComponent::Render(LyShine::IRenderGraph* renderGraph, UiElementInterf AZ::Vector2 renderTargetSize = pixelAlignedBottomRight - pixelAlignedTopLeft; bool needsResize = static_cast(renderTargetSize.GetX()) != m_renderTargetWidth || static_cast(renderTargetSize.GetY()) != m_renderTargetHeight; - if (m_contentRenderTargetHandle == -1 || needsResize) + if (m_contentAttachmentImageId.IsEmpty() || needsResize) { // Need to create or resize the render target CreateOrResizeRenderTarget(pixelAlignedTopLeft, pixelAlignedBottomRight); @@ -89,7 +94,7 @@ void UiMaskComponent::Render(LyShine::IRenderGraph* renderGraph, UiElementInterf // in theory the child mask element could still be non-zero size and could reveal things. But the way gradient masks // currently work is that the size of the render target is defined by the size of this element, therefore nothing would // be revealed by the mask if it is zero sized. - if (m_contentRenderTargetHandle == -1) + if (m_contentAttachmentImageId.IsEmpty()) { return; } @@ -101,7 +106,7 @@ void UiMaskComponent::Render(LyShine::IRenderGraph* renderGraph, UiElementInterf else { // using stencil mask, not going to use render targets, destroy previous render target, if exists - if (m_contentRenderTargetHandle != -1) + if (!m_contentAttachmentImageId.IsEmpty()) { DestroyRenderTarget(); } @@ -113,7 +118,7 @@ void UiMaskComponent::Render(LyShine::IRenderGraph* renderGraph, UiElementInterf else { // masking disabled, not going to use render targets, destroy previous render target, if exists - if (m_contentRenderTargetHandle != -1) + if (!m_contentAttachmentImageId.IsEmpty()) { DestroyRenderTarget(); } @@ -553,77 +558,30 @@ void UiMaskComponent::CreateOrResizeRenderTarget(const AZ::Vector2& pixelAligned m_viewportTopLeft = pixelAlignedTopLeft; m_viewportSize = renderTargetSize; -#ifdef LYSHINE_ATOM_TODO // [LYN-3359] Support RTT using Atom - // Check if the render target already exists - if (m_contentRenderTargetHandle != -1) - { - // Render target exists, resize it to the given size - if (!gEnv->pRenderer->ResizeRenderTarget(m_contentRenderTargetHandle, static_cast(renderTargetSize.GetX()), static_cast(renderTargetSize.GetY()))) - { - AZ_Warning("UI", false, "Failed to resize content render target for UiMaskComponent"); - DestroyRenderTarget(); - } - } - else - { - // Create a render target that this element and its children will be rendered to. - m_contentRenderTargetHandle = gEnv->pRenderer->CreateRenderTarget(m_renderTargetName.c_str(), - static_cast(renderTargetSize.GetX()), static_cast(renderTargetSize.GetY()), Clr_Transparent, eTF_R8G8B8A8); + // LYSHINE_ATOM_TODO: optimize by reusing/resizing targets + DestroyRenderTarget(); - if (m_contentRenderTargetHandle == -1) - { - AZ_Warning("UI", false, "Failed to create content render target for UiMaskComponent"); - } + // Create a render target that this element and its children will be rendered to + AZ::EntityId canvasEntityId; + EBUS_EVENT_ID_RESULT(canvasEntityId, GetEntityId(), UiElementBus, GetCanvasEntityId); + AZ::RHI::Size imageSize(renderTargetSize.GetX(), renderTargetSize.GetY(), 1); + EBUS_EVENT_ID_RESULT(m_contentAttachmentImageId, canvasEntityId, LyShine::RenderToTextureRequestBus, UseRenderTarget, AZ::Name(m_renderTargetName.c_str()), imageSize); + if (m_contentAttachmentImageId.IsEmpty()) + { + AZ_Warning("UI", false, "Failed to create content render target for UiMaskComponent"); } - // if depth surface already exists then destroy it - if (m_renderTargetDepthSurface) + // Create separate render target for the mask texture + EBUS_EVENT_ID_RESULT(m_maskAttachmentImageId, canvasEntityId, LyShine::RenderToTextureRequestBus, UseRenderTarget, AZ::Name(m_maskRenderTargetName.c_str()), imageSize); + if (m_maskAttachmentImageId.IsEmpty()) { - gEnv->pRenderer->DestroyDepthSurface(m_renderTargetDepthSurface); - m_renderTargetDepthSurface = nullptr; + AZ_Warning("UI", false, "Failed to create mask render target for UiMaskComponent"); + DestroyRenderTarget(); } - if (m_contentRenderTargetHandle != -1) - { - // Also create a depth surface to render the canvas to, we need depth for masking - // since that uses the stencil buffer. We support any combination of nesting faders and masks - m_renderTargetDepthSurface = gEnv->pRenderer->CreateDepthSurface( - static_cast(renderTargetSize.GetX()), static_cast(renderTargetSize.GetY())); - - if (!m_renderTargetDepthSurface) - { - AZ_Warning("UI", false, "Failed to create depth surface for UiMaskComponent"); - DestroyRenderTarget(); - } - } - - // Check if the mask render target already exists - if (m_maskRenderTargetHandle != -1) - { - // Render target exists, resize it to the given size - if (!gEnv->pRenderer->ResizeRenderTarget(m_maskRenderTargetHandle, static_cast(renderTargetSize.GetX()), static_cast(renderTargetSize.GetY()))) - { - AZ_Warning("UI", false, "Failed to resize mask render target for UiMaskComponent"); - DestroyRenderTarget(); - } - } - else - { - // create separate render target for the mask texture - m_maskRenderTargetHandle = gEnv->pRenderer->CreateRenderTarget(m_maskRenderTargetName.c_str(), - static_cast(renderTargetSize.GetX()), static_cast(renderTargetSize.GetY()), Clr_Transparent, eTF_R8G8B8A8); - - if (m_maskRenderTargetHandle == -1) - { - AZ_Warning("UI", false, "Failed to create mask render target for UiMaskComponent"); - DestroyRenderTarget(); - } - } -#endif - // at this point either all render targets and depth surfaces are created or none are. // If all succeeded then update the render target size - if (m_contentRenderTargetHandle != -1) + if (!m_contentAttachmentImageId.IsEmpty()) { m_renderTargetWidth = static_cast(renderTargetSize.GetX()); m_renderTargetHeight = static_cast(renderTargetSize.GetY()); @@ -635,22 +593,22 @@ void UiMaskComponent::CreateOrResizeRenderTarget(const AZ::Vector2& pixelAligned //////////////////////////////////////////////////////////////////////////////////////////////////// void UiMaskComponent::DestroyRenderTarget() { - if (m_contentRenderTargetHandle != -1) + if (!m_contentAttachmentImageId.IsEmpty()) { - gEnv->pRenderer->DestroyRenderTarget(m_contentRenderTargetHandle); - m_contentRenderTargetHandle = -1; + AZ::EntityId canvasEntityId; + EBUS_EVENT_ID_RESULT(canvasEntityId, GetEntityId(), UiElementBus, GetCanvasEntityId); + EBUS_EVENT_ID(canvasEntityId, LyShine::RenderToTextureRequestBus, ReleaseRenderTarget, m_contentAttachmentImageId); + + m_contentAttachmentImageId = AZ::RHI::AttachmentId{}; } - if (m_renderTargetDepthSurface) + if (!m_maskAttachmentImageId.IsEmpty()) { - gEnv->pRenderer->DestroyDepthSurface(m_renderTargetDepthSurface); - m_renderTargetDepthSurface = nullptr; - } + AZ::EntityId canvasEntityId; + EBUS_EVENT_ID_RESULT(canvasEntityId, GetEntityId(), UiElementBus, GetCanvasEntityId); + EBUS_EVENT_ID(canvasEntityId, LyShine::RenderToTextureRequestBus, ReleaseRenderTarget, m_maskAttachmentImageId); - if (m_maskRenderTargetHandle != -1) - { - gEnv->pRenderer->DestroyRenderTarget(m_maskRenderTargetHandle); - m_maskRenderTargetHandle = -1; + m_maskAttachmentImageId = AZ::RHI::AttachmentId{}; } } @@ -747,6 +705,14 @@ void UiMaskComponent::RenderUsingGradientMask(LyShine::IRenderGraph* renderGraph // we always clear to transparent black - the accumulation of alpha in the render target requires it AZ::Color clearColor(0.0f, 0.0f, 0.0f, 0.0f); + // Get the render targets + AZ::Data::Instance contentAttachmentImage; + AZ::Data::Instance maskAttachmentImage; + AZ::EntityId canvasEntityId; + EBUS_EVENT_ID_RESULT(canvasEntityId, GetEntityId(), UiElementBus, GetCanvasEntityId); + EBUS_EVENT_ID_RESULT(contentAttachmentImage, canvasEntityId, LyShine::RenderToTextureRequestBus, GetRenderTarget, m_contentAttachmentImageId); + EBUS_EVENT_ID_RESULT(maskAttachmentImage, canvasEntityId, LyShine::RenderToTextureRequestBus, GetRenderTarget, m_maskAttachmentImageId); + // We don't want parent faders to affect what is rendered to the render target since we will // apply those fades when we render from the render target. // Note that this means that, if there are parent (no render to texture) faders, we get a "free" @@ -756,8 +722,8 @@ void UiMaskComponent::RenderUsingGradientMask(LyShine::IRenderGraph* renderGraph // mask render target { // Start building the render to texture node in the render graph - renderGraph->BeginRenderToTexture(m_maskRenderTargetHandle, m_renderTargetDepthSurface, - m_viewportTopLeft, m_viewportSize, clearColor); + LyShine::RenderGraph* lyRenderGraph = dynamic_cast(renderGraph); + lyRenderGraph->BeginRenderToTexture(maskAttachmentImage, m_viewportTopLeft, m_viewportSize, clearColor); // Render the visual component for this element (if there is one) plus the child mask element (if there is one) RenderMaskPrimitives(renderGraph, renderInterface, childMaskElementInterface, isInGame); @@ -769,8 +735,8 @@ void UiMaskComponent::RenderUsingGradientMask(LyShine::IRenderGraph* renderGraph // content render target { // Start building the render to texture node for the content render target in the render graph - renderGraph->BeginRenderToTexture(m_contentRenderTargetHandle, m_renderTargetDepthSurface, - m_viewportTopLeft, m_viewportSize, clearColor); + LyShine::RenderGraph* lyRenderGraph = dynamic_cast(renderGraph); + lyRenderGraph->BeginRenderToTexture(contentAttachmentImage, m_viewportTopLeft, m_viewportSize, clearColor); // Render the "content" - the child elements excluding the child mask element (if any) RenderContentPrimitives(renderGraph, elementInterface, childMaskElementInterface, numChildren, isInGame); @@ -790,14 +756,13 @@ void UiMaskComponent::RenderUsingGradientMask(LyShine::IRenderGraph* renderGraph float desiredAlpha = renderGraph->GetAlphaFade(); uint32 desiredPackedAlpha = static_cast(desiredAlpha * 255.0f); - UCol desiredPackedColor; - // This is a special case. We have an input texture that already has premultiplied alpha. - // So we tell the shader not to premultiply the output colors and we premultiply the alpha - // into the vertex colors so that they are premultiplied too. - desiredPackedColor.r = desiredPackedColor.g = desiredPackedColor.b = desiredPackedColor.a = desiredPackedAlpha; - if (m_cachedPrimitive.m_vertices[0].color.dcolor != desiredPackedColor.dcolor) + // If the fade value has changed we need to update the alpha values in the vertex colors but we do + // not want to touch or recompute the RGB values + if (m_cachedPrimitive.m_vertices[0].color.a != desiredPackedAlpha) { - // go through the cached vertices and update the color values + // go through all the cached vertices and update the alpha values + UCol desiredPackedColor = m_cachedPrimitive.m_vertices[0].color; + desiredPackedColor.a = desiredPackedAlpha; for (int i = 0; i < m_cachedPrimitive.m_numVertices; ++i) { m_cachedPrimitive.m_vertices[i].color = desiredPackedColor; @@ -805,22 +770,29 @@ void UiMaskComponent::RenderUsingGradientMask(LyShine::IRenderGraph* renderGraph } } -#ifdef LYSHINE_ATOM_TODO // [LYN-3359] Support RTT using Atom // Add a primitive to do the alpha mask { - // Set the texture and other render state required - ITexture* texture = gEnv->pRenderer->EF_GetTextureByID(m_contentRenderTargetHandle); - ITexture* maskTexture = gEnv->pRenderer->EF_GetTextureByID(m_maskRenderTargetHandle); - bool isClampTextureMode = true; - bool isTextureSRGB = true; - bool isTexturePremultipliedAlpha = true; - LyShine::BlendMode blendMode = LyShine::BlendMode::Normal; + LyShine::RenderGraph* lyRenderGraph = dynamic_cast(renderGraph); + if (lyRenderGraph) + { + // Set the texture and other render state required + AZ::Data::Instance contentImage = contentAttachmentImage; + AZ::Data::Instance maskImage = maskAttachmentImage; + bool isClampTextureMode = true; + bool isTextureSRGB = true; + bool isTexturePremultipliedAlpha = false; + LyShine::BlendMode blendMode = LyShine::BlendMode::Normal; - // add a render node to render using the two render targets, one as an alpha mask of the other - renderGraph->AddAlphaMaskPrimitive(&m_cachedPrimitive, texture, maskTexture, - isClampTextureMode, isTextureSRGB, isTexturePremultipliedAlpha, blendMode); + // add a render node to render using the two render targets, one as an alpha mask of the other + lyRenderGraph->AddAlphaMaskPrimitiveAtom(&m_cachedPrimitive, + contentAttachmentImage, + maskAttachmentImage, + isClampTextureMode, + isTextureSRGB, + isTexturePremultipliedAlpha, + blendMode); + } } -#endif } } diff --git a/Gems/LyShine/Code/Source/UiMaskComponent.h b/Gems/LyShine/Code/Source/UiMaskComponent.h index ce0068ca97..8635f048f5 100644 --- a/Gems/LyShine/Code/Source/UiMaskComponent.h +++ b/Gems/LyShine/Code/Source/UiMaskComponent.h @@ -15,6 +15,7 @@ #include #include +#include //////////////////////////////////////////////////////////////////////////////////////////////////// class UiMaskComponent @@ -184,15 +185,16 @@ private: // data //! This is generated from the entity ID and cached AZStd::string m_maskRenderTargetName; - //! When rendering to a texture this is the texture ID of the render target - int m_contentRenderTargetHandle = -1; + //! When rendering to a texture this is the attachment image for the render target + AZ::RHI::AttachmentId m_contentAttachmentImageId; //! When rendering to a texture this is our depth surface, we use the same one for rendering the mask elements //! and the content elements - it is cleared in between. SDepthTexture* m_renderTargetDepthSurface = nullptr; //! When rendering to a texture this is the texture ID of the render target - int m_maskRenderTargetHandle = -1; + //! When rendering to a texture this is the attachment image for the render target + AZ::RHI::AttachmentId m_maskAttachmentImageId; //! The positions used for the render to texture viewport and to render the render target to the screen AZ::Vector2 m_viewportTopLeft = AZ::Vector2::CreateZero(); diff --git a/Gems/LyShine/Code/Source/UiRenderer.cpp b/Gems/LyShine/Code/Source/UiRenderer.cpp index 56374d1152..357431c80a 100644 --- a/Gems/LyShine/Code/Source/UiRenderer.cpp +++ b/Gems/LyShine/Code/Source/UiRenderer.cpp @@ -6,6 +6,7 @@ * */ #include "UiRenderer.h" +#include "LyShinePassDataBus.h" #include #include @@ -60,25 +61,32 @@ void UiRenderer::OnBootstrapSceneReady([[maybe_unused]] AZ::RPI::Scene* bootstra AZ::Data::Instance uiShader = AZ::RPI::LoadShader(uiShaderFilepath); // Create scene to be used by the dynamic draw context - AZ::RPI::ScenePtr scene; if (m_viewportContext) { // Create a new scene based on the user specified viewport context - scene = CreateScene(m_viewportContext); + m_scene = CreateScene(m_viewportContext); } else { // No viewport context specified, use default scene - scene = AZ::RPI::RPISystemInterface::Get()->GetDefaultScene(); + m_scene = AZ::RPI::RPISystemInterface::Get()->GetDefaultScene(); } // Create a dynamic draw context for UI Canvas drawing for the scene - CreateDynamicDrawContext(scene, uiShader); + m_dynamicDraw = CreateDynamicDrawContext(m_scene, uiShader); - // Cache shader data such as input indices for later use - CacheShaderData(m_dynamicDraw); + if (m_dynamicDraw) + { + // Cache shader data such as input indices for later use + CacheShaderData(m_dynamicDraw); - m_isRPIReady = true; + m_isRPIReady = true; + } + else + { + AZ_Error(LogName, false, "Failed to create a dynamic draw context for LyShine. \ + This can happen if the LyShine pass hasn't been added to the main render pipeline."); + } } AZ::RPI::ScenePtr UiRenderer::CreateScene(AZStd::shared_ptr viewportContext) @@ -107,22 +115,40 @@ AZ::RPI::ScenePtr UiRenderer::CreateScene(AZStd::shared_ptr uiShader) +AZ::RHI::Ptr UiRenderer::CreateDynamicDrawContext( + AZ::RPI::ScenePtr scene, + AZ::Data::Instance uiShader) { - m_dynamicDraw = AZ::RPI::DynamicDrawInterface::Get()->CreateDynamicDrawContext(); + // Find the pass that renders the UI canvases after the rtt passes + AZ::RPI::RasterPass* uiCanvasPass = nullptr; + AZ::RPI::SceneId sceneId = m_scene->GetId(); + LyShinePassRequestBus::EventResult(uiCanvasPass, sceneId, &LyShinePassRequestBus::Events::GetUiCanvasPass); + + AZ::RHI::Ptr dynamicDraw = AZ::RPI::DynamicDrawInterface::Get()->CreateDynamicDrawContext(); // Initialize the dynamic draw context - m_dynamicDraw->InitShader(uiShader); - m_dynamicDraw->InitVertexFormat( + dynamicDraw->InitShader(uiShader); + dynamicDraw->InitVertexFormat( { { "POSITION", AZ::RHI::Format::R32G32_FLOAT }, { "COLOR", AZ::RHI::Format::B8G8R8A8_UNORM }, { "TEXCOORD", AZ::RHI::Format::R32G32_FLOAT }, { "BLENDINDICES", AZ::RHI::Format::R16G16_UINT } } ); - m_dynamicDraw->AddDrawStateOptions(AZ::RPI::DynamicDrawContext::DrawStateOptions::StencilState + dynamicDraw->AddDrawStateOptions(AZ::RPI::DynamicDrawContext::DrawStateOptions::StencilState | AZ::RPI::DynamicDrawContext::DrawStateOptions::BlendMode); - m_dynamicDraw->SetOutputScope(scene.get()); - m_dynamicDraw->EndInit(); + + if (uiCanvasPass) + { + dynamicDraw->SetOutputScope(uiCanvasPass); + } + else + { + // Render target support is disabled + dynamicDraw->SetOutputScope(m_scene.get()); + } + dynamicDraw->EndInit(); + + return dynamicDraw; } AZStd::shared_ptr UiRenderer::GetViewportContext() @@ -158,19 +184,26 @@ void UiRenderer::CacheShaderData(const AZ::RHI::Ptr isClampIndexName); // Cache shader variants that will be used - // LYSHINE_ATOM_TODO - more variants will be used in future phase (masks/render target support) - AZ::RPI::ShaderOptionList shaderOptionsDefault; - shaderOptionsDefault.push_back(AZ::RPI::ShaderOption(AZ::Name("o_preMultiplyAlpha"), AZ::Name("false"))); - shaderOptionsDefault.push_back(AZ::RPI::ShaderOption(AZ::Name("o_alphaTest"), AZ::Name("false"))); - shaderOptionsDefault.push_back(AZ::RPI::ShaderOption(AZ::Name("o_srgbWrite"), AZ::Name("true"))); - shaderOptionsDefault.push_back(AZ::RPI::ShaderOption(AZ::Name("o_modulate"), AZ::Name("Modulate::None"))); - m_uiShaderData.m_shaderVariantDefault = dynamicDraw->UseShaderVariant(shaderOptionsDefault); - AZ::RPI::ShaderOptionList shaderOptionsAlphaTest; - shaderOptionsAlphaTest.push_back(AZ::RPI::ShaderOption(AZ::Name("o_preMultiplyAlpha"), AZ::Name("false"))); - shaderOptionsAlphaTest.push_back(AZ::RPI::ShaderOption(AZ::Name("o_alphaTest"), AZ::Name("true"))); - shaderOptionsAlphaTest.push_back(AZ::RPI::ShaderOption(AZ::Name("o_srgbWrite"), AZ::Name("true"))); - shaderOptionsAlphaTest.push_back(AZ::RPI::ShaderOption(AZ::Name("o_modulate"), AZ::Name("Modulate::None"))); - m_uiShaderData.m_shaderVariantAlphaTest = dynamicDraw->UseShaderVariant(shaderOptionsAlphaTest); + AZ::RPI::ShaderOptionList shaderOptionsTextureLinear; + shaderOptionsTextureLinear.push_back(AZ::RPI::ShaderOption(AZ::Name("o_alphaTest"), AZ::Name("false"))); + shaderOptionsTextureLinear.push_back(AZ::RPI::ShaderOption(AZ::Name("o_srgbWrite"), AZ::Name("true"))); + shaderOptionsTextureLinear.push_back(AZ::RPI::ShaderOption(AZ::Name("o_modulate"), AZ::Name("Modulate::None"))); + m_uiShaderData.m_shaderVariantTextureLinear = dynamicDraw->UseShaderVariant(shaderOptionsTextureLinear); + AZ::RPI::ShaderOptionList shaderOptionsTextureSrgb; + shaderOptionsTextureSrgb.push_back(AZ::RPI::ShaderOption(AZ::Name("o_alphaTest"), AZ::Name("false"))); + shaderOptionsTextureSrgb.push_back(AZ::RPI::ShaderOption(AZ::Name("o_srgbWrite"), AZ::Name("false"))); + shaderOptionsTextureSrgb.push_back(AZ::RPI::ShaderOption(AZ::Name("o_modulate"), AZ::Name("Modulate::None"))); + m_uiShaderData.m_shaderVariantTextureSrgb = dynamicDraw->UseShaderVariant(shaderOptionsTextureSrgb); + AZ::RPI::ShaderOptionList shaderVariantAlphaTestMask; + shaderVariantAlphaTestMask.push_back(AZ::RPI::ShaderOption(AZ::Name("o_alphaTest"), AZ::Name("true"))); + shaderVariantAlphaTestMask.push_back(AZ::RPI::ShaderOption(AZ::Name("o_srgbWrite"), AZ::Name("false"))); + shaderVariantAlphaTestMask.push_back(AZ::RPI::ShaderOption(AZ::Name("o_modulate"), AZ::Name("Modulate::None"))); + m_uiShaderData.m_shaderVariantAlphaTestMask = dynamicDraw->UseShaderVariant(shaderVariantAlphaTestMask); + AZ::RPI::ShaderOptionList shaderVariantGradientMask; + shaderVariantGradientMask.push_back(AZ::RPI::ShaderOption(AZ::Name("o_alphaTest"), AZ::Name("false"))); + shaderVariantGradientMask.push_back(AZ::RPI::ShaderOption(AZ::Name("o_srgbWrite"), AZ::Name("false"))); + shaderVariantGradientMask.push_back(AZ::RPI::ShaderOption(AZ::Name("o_modulate"), AZ::Name("Modulate::Alpha"))); + m_uiShaderData.m_shaderVariantGradientMask = dynamicDraw->UseShaderVariant(shaderVariantGradientMask); } //////////////////////////////////////////////////////////////////////////////////////////////////// @@ -215,6 +248,38 @@ AZ::RHI::Ptr UiRenderer::GetDynamicDrawContext() return m_dynamicDraw; } +//////////////////////////////////////////////////////////////////////////////////////////////////// +AZ::RHI::Ptr UiRenderer::CreateDynamicDrawContextForRTT(const AZStd::string& rttName) +{ + // find the rtt pass with the specified name + AZ::RPI::RasterPass* rttPass = nullptr; + AZ::RPI::SceneId sceneId = m_scene->GetId(); + LyShinePassRequestBus::EventResult(rttPass, sceneId, &LyShinePassRequestBus::Events::GetRttPass, rttName); + if (!rttPass) + { + return nullptr; + } + + AZ::RHI::Ptr dynamicDraw = AZ::RPI::DynamicDrawInterface::Get()->CreateDynamicDrawContext(); + + // Initialize the dynamic draw context + dynamicDraw->InitShader(m_dynamicDraw->GetShader()); + dynamicDraw->InitVertexFormat( + { { "POSITION", AZ::RHI::Format::R32G32_FLOAT }, + { "COLOR", AZ::RHI::Format::B8G8R8A8_UNORM }, + { "TEXCOORD", AZ::RHI::Format::R32G32_FLOAT }, + { "BLENDINDICES", AZ::RHI::Format::R16G16_UINT } } + ); + dynamicDraw->AddDrawStateOptions(AZ::RPI::DynamicDrawContext::DrawStateOptions::StencilState + | AZ::RPI::DynamicDrawContext::DrawStateOptions::BlendMode); + + dynamicDraw->SetOutputScope(rttPass); + + dynamicDraw->EndInit(); + + return dynamicDraw; +} + //////////////////////////////////////////////////////////////////////////////////////////////////// const UiRenderer::UiShaderData& UiRenderer::GetUiShaderData() { @@ -270,11 +335,27 @@ void UiRenderer::SetBaseState(BaseState state) //////////////////////////////////////////////////////////////////////////////////////////////////// AZ::RPI::ShaderVariantId UiRenderer::GetCurrentShaderVariant() { - AZ::RPI::ShaderVariantId variantId = m_uiShaderData.m_shaderVariantDefault; + AZ::RPI::ShaderVariantId variantId = m_uiShaderData.m_shaderVariantTextureLinear; if (m_baseState.m_useAlphaTest) { - variantId = m_uiShaderData.m_shaderVariantAlphaTest; + variantId = m_uiShaderData.m_shaderVariantAlphaTestMask; + } + else if (m_baseState.m_modulateAlpha) + { + variantId = m_uiShaderData.m_shaderVariantGradientMask; + } + else if (!m_baseState.m_useAlphaTest && m_baseState.m_srgbWrite) + { + variantId = m_uiShaderData.m_shaderVariantTextureLinear; + } + else if (!m_baseState.m_useAlphaTest && !m_baseState.m_srgbWrite) + { + variantId = m_uiShaderData.m_shaderVariantTextureSrgb; + } + else + { + AZ_Error(LogName, 0, "Unsupported shader variant."); } return variantId; diff --git a/Gems/LyShine/Code/Source/UiRenderer.h b/Gems/LyShine/Code/Source/UiRenderer.h index d05261c2d6..14fc67fc6b 100644 --- a/Gems/LyShine/Code/Source/UiRenderer.h +++ b/Gems/LyShine/Code/Source/UiRenderer.h @@ -36,8 +36,10 @@ public: // types AZ::RHI::ShaderInputConstantIndex m_viewProjInputIndex; AZ::RHI::ShaderInputConstantIndex m_isClampInputIndex; - AZ::RPI::ShaderVariantId m_shaderVariantDefault; - AZ::RPI::ShaderVariantId m_shaderVariantAlphaTest; + AZ::RPI::ShaderVariantId m_shaderVariantTextureLinear; + AZ::RPI::ShaderVariantId m_shaderVariantTextureSrgb; + AZ::RPI::ShaderVariantId m_shaderVariantAlphaTestMask; + AZ::RPI::ShaderVariantId m_shaderVariantGradientMask; }; // Base state @@ -56,17 +58,23 @@ public: // types m_blendState.m_blendSource = AZ::RHI::BlendFactor::AlphaSource; m_blendState.m_blendDest = AZ::RHI::BlendFactor::AlphaSourceInverse; m_blendState.m_blendOp = AZ::RHI::BlendOp::Add; + m_blendState.m_blendAlphaSource = AZ::RHI::BlendFactor::One; + m_blendState.m_blendAlphaDest = AZ::RHI::BlendFactor::Zero; + m_blendState.m_blendAlphaOp = AZ::RHI::BlendOp::Add; // Disable stencil m_stencilState = AZ::RHI::StencilState(); m_stencilState.m_enable = 0; m_useAlphaTest = false; + m_modulateAlpha = false; } AZ::RHI::TargetBlendState m_blendState; AZ::RHI::StencilState m_stencilState; bool m_useAlphaTest = false; + bool m_modulateAlpha = false; + bool m_srgbWrite = true; }; public: // member functions @@ -93,6 +101,8 @@ public: // member functions //! Return the dynamic draw context associated with this UI renderer AZ::RHI::Ptr GetDynamicDrawContext(); + AZ::RHI::Ptr CreateDynamicDrawContextForRTT(const AZStd::string& rttName); + //! Return the shader data for the ui shader const UiShaderData& GetUiShaderData(); @@ -123,6 +133,9 @@ public: // member functions //! Decrement the current stencil reference value void DecrementStencilRef(); + //! Return the viewport context set by the user, or the default if not set + AZStd::shared_ptr GetViewportContext(); + #ifndef _RELEASE //! Setup to record debug texture data before rendering void DebugSetRecordingOptionForTextureData(int recordingOption); @@ -143,10 +156,9 @@ private: // member functions AZ::RPI::ScenePtr CreateScene(AZStd::shared_ptr viewportContext); //! Create a dynamic draw context for this renderer - void CreateDynamicDrawContext(AZ::RPI::ScenePtr scene, AZ::Data::Instance); - - //! Return the viewport context set by the user, or the default if not set - AZStd::shared_ptr GetViewportContext(); + AZ::RHI::Ptr CreateDynamicDrawContext( + AZ::RPI::ScenePtr scene, + AZ::Data::Instance uiShader); //! Bind the global white texture for all the texture units we use void BindNullTexture(); @@ -168,6 +180,8 @@ protected: // attributes // Set by user when viewport context is not the main/default viewport AZStd::shared_ptr m_viewportContext; + AZ::RPI::ScenePtr m_scene; + #ifndef _RELEASE int m_debugTextureDataRecordLevel = 0; AZStd::unordered_set m_texturesUsedInFrame; // LYSHINE_ATOM_TODO - convert to RPI::Image diff --git a/Gems/LyShine/Code/Tests/UiTooltipComponentTest.cpp b/Gems/LyShine/Code/Tests/UiTooltipComponentTest.cpp index 17eb2c081f..65ca1ec280 100644 --- a/Gems/LyShine/Code/Tests/UiTooltipComponentTest.cpp +++ b/Gems/LyShine/Code/Tests/UiTooltipComponentTest.cpp @@ -163,11 +163,9 @@ namespace UnitTest SSystemGlobalEnvironment* prevEnv = gEnv; gEnv = &env; gEnv->pTimer = &m_timer; + gEnv->pLyShine = nullptr; - UiCanvasComponent* uiCanvasComponent; - UiTooltipDisplayComponent* uiTooltipDisplayComponent; - UiTooltipComponent* uiTooltipComponent; - std::tie(uiCanvasComponent, uiTooltipDisplayComponent, uiTooltipComponent) = CreateUiCanvasWithTooltip(); + auto [uiCanvasComponent, uiTooltipDisplayComponent, uiTooltipComponent] = CreateUiCanvasWithTooltip(); uiTooltipDisplayComponent->SetTriggerMode(UiTooltipDisplayInterface::TriggerMode::OnHover); AZ::Entity* uiTooltipEntity = uiTooltipComponent->GetEntity(); @@ -193,11 +191,9 @@ namespace UnitTest SSystemGlobalEnvironment* prevEnv = gEnv; gEnv = &env; gEnv->pTimer = &m_timer; + gEnv->pLyShine = nullptr; - UiCanvasComponent* uiCanvasComponent; - UiTooltipDisplayComponent* uiTooltipDisplayComponent; - UiTooltipComponent* uiTooltipComponent; - std::tie(uiCanvasComponent, uiTooltipDisplayComponent, uiTooltipComponent) = CreateUiCanvasWithTooltip(); + auto [uiCanvasComponent, uiTooltipDisplayComponent, uiTooltipComponent] = CreateUiCanvasWithTooltip(); uiTooltipDisplayComponent->SetTriggerMode(UiTooltipDisplayInterface::TriggerMode::OnHover); AZ::Entity* uiTooltipEntity = uiTooltipComponent->GetEntity(); @@ -221,11 +217,9 @@ namespace UnitTest SSystemGlobalEnvironment* prevEnv = gEnv; gEnv = &env; gEnv->pTimer = &m_timer; + gEnv->pLyShine = nullptr; - UiCanvasComponent* uiCanvasComponent; - UiTooltipDisplayComponent* uiTooltipDisplayComponent; - UiTooltipComponent* uiTooltipComponent; - std::tie(uiCanvasComponent, uiTooltipDisplayComponent, uiTooltipComponent) = CreateUiCanvasWithTooltip(); + auto [uiCanvasComponent, uiTooltipDisplayComponent, uiTooltipComponent] = CreateUiCanvasWithTooltip(); uiTooltipDisplayComponent->SetTriggerMode(UiTooltipDisplayInterface::TriggerMode::OnPress); AZ::Entity* uiTooltipEntity = uiTooltipComponent->GetEntity(); @@ -249,11 +243,9 @@ namespace UnitTest SSystemGlobalEnvironment* prevEnv = gEnv; gEnv = &env; gEnv->pTimer = &m_timer; + gEnv->pLyShine = nullptr; - UiCanvasComponent* uiCanvasComponent; - UiTooltipDisplayComponent* uiTooltipDisplayComponent; - UiTooltipComponent* uiTooltipComponent; - std::tie(uiCanvasComponent, uiTooltipDisplayComponent, uiTooltipComponent) = CreateUiCanvasWithTooltip(); + auto [uiCanvasComponent, uiTooltipDisplayComponent, uiTooltipComponent] = CreateUiCanvasWithTooltip(); uiTooltipDisplayComponent->SetTriggerMode(UiTooltipDisplayInterface::TriggerMode::OnPress); AZ::Entity* uiTooltipEntity = uiTooltipComponent->GetEntity(); @@ -277,11 +269,9 @@ namespace UnitTest SSystemGlobalEnvironment* prevEnv = gEnv; gEnv = &env; gEnv->pTimer = &m_timer; + gEnv->pLyShine = nullptr; - UiCanvasComponent* uiCanvasComponent; - UiTooltipDisplayComponent* uiTooltipDisplayComponent; - UiTooltipComponent* uiTooltipComponent; - std::tie(uiCanvasComponent, uiTooltipDisplayComponent, uiTooltipComponent) = CreateUiCanvasWithTooltip(); + auto [uiCanvasComponent, uiTooltipDisplayComponent, uiTooltipComponent] = CreateUiCanvasWithTooltip(); uiTooltipDisplayComponent->SetTriggerMode(UiTooltipDisplayInterface::TriggerMode::OnClick); AZ::Entity* uiTooltipEntity = uiTooltipComponent->GetEntity(); diff --git a/Gems/LyShine/Code/lyshine_static_files.cmake b/Gems/LyShine/Code/lyshine_static_files.cmake index 7fdac831b6..0c934eb057 100644 --- a/Gems/LyShine/Code/lyshine_static_files.cmake +++ b/Gems/LyShine/Code/lyshine_static_files.cmake @@ -11,8 +11,11 @@ set(FILES Include/LyShine/Draw2d.h Source/LyShine.cpp Source/LyShine.h + Source/LyShinePassDataBus.h Source/LyShineDebug.cpp Source/LyShineDebug.h + Source/LyShinePass.cpp + Source/LyShinePass.h Source/StringUtfUtils.h Source/UiImageComponent.cpp Source/UiImageComponent.h @@ -28,6 +31,7 @@ set(FILES Source/LyShineLoadScreen.h Source/RenderGraph.cpp Source/RenderGraph.h + Source/RenderToTextureBus.h Source/TextMarkup.cpp Source/TextMarkup.h Source/UiButtonComponent.cpp diff --git a/Gems/LyShine/LyShineScript/LyShinePass.data b/Gems/LyShine/LyShineScript/LyShinePass.data new file mode 100644 index 0000000000..af44db91ed --- /dev/null +++ b/Gems/LyShine/LyShineScript/LyShinePass.data @@ -0,0 +1,20 @@ + { + "Name": "LyShinePass", + "TemplateName": "LyShineParentTemplate", + "Connections": [ + { + "LocalSlot": "ColorInputOutput", + "AttachmentRef": { + "Pass": "DebugOverlayPass", + "Attachment": "InputOutput" + } + }, + { + "LocalSlot": "DepthInputOutput", + "AttachmentRef": { + "Pass": "DepthPrePass", + "Attachment": "Depth" + } + } + ] + } \ No newline at end of file diff --git a/Gems/LyShine/LyShineScript/PatchRenderPipeline.py b/Gems/LyShine/LyShineScript/PatchRenderPipeline.py new file mode 100644 index 0000000000..09a0049e81 --- /dev/null +++ b/Gems/LyShine/LyShineScript/PatchRenderPipeline.py @@ -0,0 +1,71 @@ +""" +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 + +""" + +import os +import sys + +# Parse arguments +if len(sys.argv) != 3: + print('Incorrect number of args') + exit() + +engine_path = sys.argv[1] +if not os.path.exists(engine_path): + print(f'Given path {engine_path} does not exist') + exit() + +project_path = sys.argv[2] +if not os.path.exists(project_path): + print(f'Given path {project_path} does not exist') + exit() + +sys.path.insert(0, os.path.join(engine_path, 'Gems/Atom/RPI/Tools/')) + +from atom_rpi_tools.pass_data import PassTemplate +import atom_rpi_tools.utils as utils + +# Folder of this py file +dir_name = os.path.dirname(os.path.realpath(__file__)) + +# Patch render pipeline to insert a custom LyShine parent pass + +# Gem::Atom_Feature_Common gem's path since default render pipeline is comming from this gem +gem_assets_path = os.path.join(engine_path,'Gems/Atom/feature/Common/Assets/') + +pipeline_relatvie_path = 'Passes/MainPipeline.pass' +srcRenderPipeline = os.path.join(gem_assets_path, pipeline_relatvie_path) +destRenderPipeline = os.path.join(project_path, pipeline_relatvie_path) +# If the project doesn't have a customized main pipeline +# copy the default render pipeline from Atom_Common_Feature gem to same path in project folder +utils.find_or_copy_file(destRenderPipeline, srcRenderPipeline) + +# Load project render pipeline +renderPipeline = PassTemplate(destRenderPipeline) + +# Skip if LyShinePass already exist +newPassName = 'LyShinePass' +if renderPipeline.find_pass(newPassName)>-1: + print('Skip merging. LyShinePass already exists') + exit() + +# Insert LyShinePass between DebugOverlayPass and UIPass +refPass = 'DebugOverlayPass' +# The data file for new pass request is in the same folder of the py file +newPassRequestFilePath = os.path.join(dir_name, 'LyShinePass.data') +newPassRequestData = utils.load_json_file(newPassRequestFilePath) +insertIndex = renderPipeline.find_pass(refPass) + 1 +if insertIndex>-1: + renderPipeline.insert_pass_request(insertIndex, newPassRequestData) +else: + print('Failed to find ', refPass) + exit() + +# Update attachment references for the passes following LyShinePass +renderPipeline.replace_references_after(newPassName, 'DebugOverlayPass', 'InputOutput', 'LyShinePass', 'ColorInputOutput') + +# Save the updated render pipeline +renderPipeline.save() From 38fd92a15ad55851052c25552fce0691a583c1f1 Mon Sep 17 00:00:00 2001 From: Alex Peterson <26804013+AMZN-alexpete@users.noreply.github.com> Date: Tue, 3 Aug 2021 10:48:57 -0700 Subject: [PATCH 21/51] Always display all gems in Gem Catalog (#2341) Signed-off-by: AMZN-alexpete <26804013+AMZN-alexpete@users.noreply.github.com> --- .../ProjectManager/Source/CreateProjectCtrl.cpp | 2 +- .../Source/GemCatalog/GemCatalogScreen.cpp | 17 ++++------------- .../Source/GemCatalog/GemCatalogScreen.h | 4 ++-- .../ProjectManager/Source/UpdateProjectCtrl.cpp | 2 +- 4 files changed, 8 insertions(+), 17 deletions(-) diff --git a/Code/Tools/ProjectManager/Source/CreateProjectCtrl.cpp b/Code/Tools/ProjectManager/Source/CreateProjectCtrl.cpp index 20d564d8b0..f098518fd3 100644 --- a/Code/Tools/ProjectManager/Source/CreateProjectCtrl.cpp +++ b/Code/Tools/ProjectManager/Source/CreateProjectCtrl.cpp @@ -265,6 +265,6 @@ namespace O3DE::ProjectManager void CreateProjectCtrl::ReinitGemCatalogForSelectedTemplate() { const QString projectTemplatePath = m_newProjectSettingsScreen->GetProjectTemplatePath(); - m_gemCatalogScreen->ReinitForProject(projectTemplatePath + "/Template", /*isNewProject=*/true); + m_gemCatalogScreen->ReinitForProject(projectTemplatePath + "/Template"); } } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp index 53d772c217..863f611ec8 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp @@ -62,10 +62,10 @@ namespace O3DE::ProjectManager hLayout->addWidget(m_gemInspector); } - void GemCatalogScreen::ReinitForProject(const QString& projectPath, bool isNewProject) + void GemCatalogScreen::ReinitForProject(const QString& projectPath) { m_gemModel->clear(); - FillModel(projectPath, isNewProject); + FillModel(projectPath); if (m_filterWidget) { @@ -88,18 +88,9 @@ namespace O3DE::ProjectManager }); } - void GemCatalogScreen::FillModel(const QString& projectPath, bool isNewProject) + void GemCatalogScreen::FillModel(const QString& projectPath) { - AZ::Outcome, AZStd::string> allGemInfosResult; - if (isNewProject) - { - allGemInfosResult = PythonBindingsInterface::Get()->GetEngineGemInfos(); - } - else - { - allGemInfosResult = PythonBindingsInterface::Get()->GetAllGemInfos(projectPath); - } - + AZ::Outcome, AZStd::string> allGemInfosResult = PythonBindingsInterface::Get()->GetAllGemInfos(projectPath); if (allGemInfosResult.IsSuccess()) { // Add all available gems to the model. diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.h b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.h index 204ad0e5c5..5b48b2f90e 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.h +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.h @@ -28,13 +28,13 @@ namespace O3DE::ProjectManager ~GemCatalogScreen() = default; ProjectManagerScreen GetScreenEnum() override; - void ReinitForProject(const QString& projectPath, bool isNewProject); + void ReinitForProject(const QString& projectPath); bool EnableDisableGemsForProject(const QString& projectPath); GemModel* GetGemModel() const { return m_gemModel; } private: - void FillModel(const QString& projectPath, bool isNewProject); + void FillModel(const QString& projectPath); GemListView* m_gemListView = nullptr; GemInspector* m_gemInspector = nullptr; diff --git a/Code/Tools/ProjectManager/Source/UpdateProjectCtrl.cpp b/Code/Tools/ProjectManager/Source/UpdateProjectCtrl.cpp index 981a9352f7..6aba261cd2 100644 --- a/Code/Tools/ProjectManager/Source/UpdateProjectCtrl.cpp +++ b/Code/Tools/ProjectManager/Source/UpdateProjectCtrl.cpp @@ -94,7 +94,7 @@ namespace O3DE::ProjectManager Update(); // Gather the available gems that will be shown in the gem catalog. - m_gemCatalogScreen->ReinitForProject(m_projectInfo.m_path, /*isNewProject=*/false); + m_gemCatalogScreen->ReinitForProject(m_projectInfo.m_path); } void UpdateProjectCtrl::HandleGemsButton() From 69bde80de3a35f2aa1b745304709ec75f8d64c0a Mon Sep 17 00:00:00 2001 From: sharmajs-amzn <82233357+sharmajs-amzn@users.noreply.github.com> Date: Tue, 3 Aug 2021 11:15:23 -0700 Subject: [PATCH 22/51] Nighly build test fixes (#2727) Signed-off-by: sharmajs-amzn <82233357+sharmajs-amzn@users.noreply.github.com> --- .../assetpipeline/asset_processor_tests/CMakeLists.txt | 2 +- .../asset_processor_tests/asset_builder_tests.py | 2 +- .../asset_processor_tests/asset_bundler_batch_tests.py | 9 +++++++-- Tools/LyTestTools/ly_test_tools/o3de/asset_processor.py | 2 +- 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/CMakeLists.txt b/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/CMakeLists.txt index 8b9de91906..0170d73af0 100644 --- a/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/CMakeLists.txt +++ b/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/CMakeLists.txt @@ -97,7 +97,7 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS) PATH ${CMAKE_CURRENT_LIST_DIR}/asset_bundler_batch_tests.py EXCLUDE_TEST_RUN_TARGET_FROM_IDE TEST_SERIAL - TIMEOUT 1500 + TIMEOUT 2400 TEST_SUITE periodic RUNTIME_DEPENDENCIES AZ::AssetProcessor diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/asset_builder_tests.py b/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/asset_builder_tests.py index 13b899dfdd..2c805f0291 100755 --- a/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/asset_builder_tests.py +++ b/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/asset_builder_tests.py @@ -113,7 +113,7 @@ class TestsAssetBuilder_WindowsAndMac(object): if listening_port: corrupted_slice_command.append(f'-port={listening_port}') if workspace.project: - corrupted_slice_command.append(f'-gamename={workspace.project}') + corrupted_slice_command.append(f'--project-path={workspace.project}') corrupted_slice_output = utils.safe_subprocess(corrupted_slice_command) # Verify corrupted slice produced error diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/asset_bundler_batch_tests.py b/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/asset_bundler_batch_tests.py index 768dd985fd..7f85e5e317 100755 --- a/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/asset_bundler_batch_tests.py +++ b/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/asset_bundler_batch_tests.py @@ -902,7 +902,7 @@ class TestsAssetBundlerBatch_WindowsAndMac(object): second_input_arg = asset_lists_to_string(second_asset_list) # --secondAssetList output_arg = asset_lists_to_string(output_file) # --output - def generate_compare_command(platform_arg: str) -> object: + def generate_compare_command(platform_arg: str, project_name : str) -> object: """Creates a string containing a full Compare command. This string can be executed as-is.""" cmd = [helper["bundler_batch"], "compare", f"--firstassetFile={first_input_arg}", f"--output={output_arg}"] if platform_arg is not None: @@ -918,6 +918,8 @@ class TestsAssetBundlerBatch_WindowsAndMac(object): if comp_type == "4": # Extra arguments for pattern comparison cmd.extend([f"--filePatternType={pattern_type}", f"--filePattern={pattern}"]) + if workspace.project: + cmd.append(f'--project-path={project_name}') return cmd # End generate_compare_command() @@ -936,6 +938,7 @@ class TestsAssetBundlerBatch_WindowsAndMac(object): # End verify_asset_list_contents() def run_compare_command_and_verify(platform_arg: str, expect_pc_output: bool, expect_mac_output: bool) -> None: + # Expected asset list to equal result of comparison expected_pc_asset_list = None expected_mac_asset_list = None @@ -957,7 +960,7 @@ class TestsAssetBundlerBatch_WindowsAndMac(object): output_mac_asset_list = helper.platform_file_name(last_output_arg, platform) # Build execution command - cmd = generate_compare_command(platform_arg) + cmd = generate_compare_command(platform_arg, workspace.project) # Execute command subprocess.check_call(cmd) @@ -992,10 +995,12 @@ class TestsAssetBundlerBatch_WindowsAndMac(object): f"--comparisonRulesFile={rule_file}", f"--comparisonType={args[1]}", r"--addComparison", + f"--project-path={workspace.project}", ] if args[1] == "4": # If pattern comparison, append a few extra arguments cmd.extend(["--filePatternType=0", "--filePattern=*.dat"]) + subprocess.check_call(cmd) assert os.path.exists(rule_file), f"Rule file {args[0]} was not created at location: {rule_file}" diff --git a/Tools/LyTestTools/ly_test_tools/o3de/asset_processor.py b/Tools/LyTestTools/ly_test_tools/o3de/asset_processor.py index 50021080fe..57454930eb 100644 --- a/Tools/LyTestTools/ly_test_tools/o3de/asset_processor.py +++ b/Tools/LyTestTools/ly_test_tools/o3de/asset_processor.py @@ -594,7 +594,7 @@ class AssetProcessor(object): output_list = None if capture_output: if decode: - output_list = run_result.stdout.decode('utf-8').splitlines() + output_list = run_result.stdout.decode('utf-8', errors="replace").splitlines() else: output_list = run_result.stdout.splitlines() From a0f3379999280200d74d3ddae0bf121f55097223 Mon Sep 17 00:00:00 2001 From: jromnoa <80134229+jromnoa@users.noreply.github.com> Date: Tue, 3 Aug 2021 14:08:59 -0700 Subject: [PATCH 23/51] Adds Light component tests (non-GPU portion) to AutomatedTesting from AtomTest (#2758) * Fixed Vegetation Layer Spawner documentation link. Signed-off-by: Chris Galvan Signed-off-by: jromnoa * add remaining non-GPU test portions for Light component test Signed-off-by: jromnoa * make non-GPU light component test more robust Signed-off-by: jromnoa * remove redundant logging, convert LIGHT_TYPES from list to dict, remove redundant f-string Signed-off-by: jromnoa Co-authored-by: Chris Galvan --- .../PythonTests/atom_renderer/CMakeLists.txt | 2 +- ...dra_AtomEditorComponents_LightComponent.py | 217 ++++++++++++++++++ .../atom_utils/atom_component_helper.py | 19 ++ .../atom_renderer/test_Atom_MainSuite.py | 64 +++++- 4 files changed, 300 insertions(+), 2 deletions(-) create mode 100644 AutomatedTesting/Gem/PythonTests/atom_renderer/atom_hydra_scripts/hydra_AtomEditorComponents_LightComponent.py create mode 100644 AutomatedTesting/Gem/PythonTests/atom_renderer/atom_utils/atom_component_helper.py diff --git a/AutomatedTesting/Gem/PythonTests/atom_renderer/CMakeLists.txt b/AutomatedTesting/Gem/PythonTests/atom_renderer/CMakeLists.txt index a342d95d98..d4f036faeb 100644 --- a/AutomatedTesting/Gem/PythonTests/atom_renderer/CMakeLists.txt +++ b/AutomatedTesting/Gem/PythonTests/atom_renderer/CMakeLists.txt @@ -17,7 +17,7 @@ if(PAL_TRAIT_BUILD_HOST_TOOLS AND PAL_TRAIT_BUILD_TESTS_SUPPORTED AND AutomatedT TEST_SUITE main PATH ${CMAKE_CURRENT_LIST_DIR}/test_Atom_MainSuite.py TEST_SERIAL - TIMEOUT 400 + TIMEOUT 600 RUNTIME_DEPENDENCIES AssetProcessor AutomatedTesting.Assets diff --git a/AutomatedTesting/Gem/PythonTests/atom_renderer/atom_hydra_scripts/hydra_AtomEditorComponents_LightComponent.py b/AutomatedTesting/Gem/PythonTests/atom_renderer/atom_hydra_scripts/hydra_AtomEditorComponents_LightComponent.py new file mode 100644 index 0000000000..ec8dc199ae --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/atom_renderer/atom_hydra_scripts/hydra_AtomEditorComponents_LightComponent.py @@ -0,0 +1,217 @@ +""" +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 + +Hydra script that creates an entity, attaches the Light component to it for test verifications. +The test verifies that each light type option is available and can be selected without errors. +""" + +import os +import sys + +import azlmbr.bus as bus +import azlmbr.editor as editor +import azlmbr.math as math +import azlmbr.paths +import azlmbr.legacy.general as general + +sys.path.append(os.path.join(azlmbr.paths.devassets, "Gem", "PythonTests")) + +import editor_python_test_tools.hydra_editor_utils as hydra +from atom_renderer.atom_utils.atom_component_helper import LIGHT_TYPES + +LIGHT_TYPE_PROPERTY = 'Controller|Configuration|Light type' +SPHERE_AND_SPOT_DISK_LIGHT_PROPERTIES = [ + ("Controller|Configuration|Shadows|Enable shadow", True), + ("Controller|Configuration|Shadows|Shadowmap size", 0), # 256 + ("Controller|Configuration|Shadows|Shadowmap size", 1), # 512 + ("Controller|Configuration|Shadows|Shadowmap size", 2), # 1024 + ("Controller|Configuration|Shadows|Shadowmap size", 3), # 2048 + ("Controller|Configuration|Shadows|Shadow filter method", 1), # PCF + ("Controller|Configuration|Shadows|Filtering sample count", 4.0), + ("Controller|Configuration|Shadows|Filtering sample count", 64.0), + ("Controller|Configuration|Shadows|PCF method", 0), # Bicubic + ("Controller|Configuration|Shadows|PCF method", 1), # Boundary search + ("Controller|Configuration|Shadows|Shadow filter method", 2), # ECM + ("Controller|Configuration|Shadows|ESM exponent", 50), + ("Controller|Configuration|Shadows|ESM exponent", 5000), + ("Controller|Configuration|Shadows|Shadow filter method", 3), # ESM+PCF +] +QUAD_LIGHT_PROPERTIES = [ + ("Controller|Configuration|Both directions", True), + ("Controller|Configuration|Fast approximation", True), +] +SIMPLE_POINT_LIGHT_PROPERTIES = [ + ("Controller|Configuration|Attenuation radius|Mode", 0), + ("Controller|Configuration|Attenuation radius|Radius", 100.0), +] +SIMPLE_SPOT_LIGHT_PROPERTIES = [ + ("Controller|Configuration|Shutters|Inner angle", 45.0), + ("Controller|Configuration|Shutters|Outer angle", 90.0), +] + + +def verify_required_component_property_value(entity_name, component, property_path, expected_property_value): + """ + Compares the property value of component against the expected_property_value. + :param entity_name: name of the entity to use (for test verification purposes). + :param component: component to check on a given entity for its current property value. + :param property_path: the path to the property inside the component. + :param expected_property_value: The value expected from the value inside property_path. + :return: None, but prints to general.log() which the test uses to verify against. + """ + property_value = editor.EditorComponentAPIBus( + bus.Broadcast, "GetComponentProperty", component, property_path).GetValue() + general.log(f"{entity_name}_test: Property value is {property_value} " + f"which matches {expected_property_value}") + + +def run(): + """ + Test Case - Light Component + 1. Creates a "light_entity" Entity and attaches a "Light" component to it. + 2. Updates the Light component to each light type option from the LIGHT_TYPES constant. + 3. The test will check the Editor log to ensure each light type was selected. + 4. Prints the string "Light component test (non-GPU) completed" after completion. + + Tests will fail immediately if any of these log lines are found: + 1. Trace::Assert + 2. Trace::Error + 3. Traceback (most recent call last): + + :return: None + """ + # Create a "light_entity" entity with "Light" component. + light_entity_name = "light_entity" + light_component = "Light" + light_entity = hydra.Entity(light_entity_name) + light_entity.create_entity(math.Vector3(-1.0, -2.0, 3.0), [light_component]) + general.log( + f"{light_entity_name}_test: Component added to the entity: " + f"{hydra.has_components(light_entity.id, [light_component])}") + + # Populate the light_component_id_pair value so that it can be used to select all Light component options. + light_component_id_pair = None + component_type_id_list = azlmbr.editor.EditorComponentAPIBus( + azlmbr.bus.Broadcast, 'FindComponentTypeIdsByEntityType', [light_component], 0) + if len(component_type_id_list) < 1: + general.log(f"ERROR: A component class with name {light_component} doesn't exist") + light_component_id_pair = None + elif len(component_type_id_list) > 1: + general.log(f"ERROR: Found more than one component classes with same name: {light_component}") + light_component_id_pair = None + entity_component_id_pair = azlmbr.editor.EditorComponentAPIBus( + azlmbr.bus.Broadcast, 'GetComponentOfType', light_entity.id, component_type_id_list[0]) + if entity_component_id_pair.IsSuccess(): + light_component_id_pair = entity_component_id_pair.GetValue() + + # Test each Light component option can be selected and it's properties updated. + # Point (sphere) light type checks. + light_type_property_test( + light_type=LIGHT_TYPES['sphere'], + light_properties=SPHERE_AND_SPOT_DISK_LIGHT_PROPERTIES, + light_component_id_pair=light_component_id_pair, + light_entity_name=light_entity_name, + light_entity=light_entity + ) + + # Spot (disk) light type checks. + light_type_property_test( + light_type=LIGHT_TYPES['spot_disk'], + light_properties=SPHERE_AND_SPOT_DISK_LIGHT_PROPERTIES, + light_component_id_pair=light_component_id_pair, + light_entity_name=light_entity_name, + light_entity=light_entity + ) + + # Capsule light type checks. + azlmbr.editor.EditorComponentAPIBus( + azlmbr.bus.Broadcast, + 'SetComponentProperty', + light_component_id_pair, + LIGHT_TYPE_PROPERTY, + LIGHT_TYPES['capsule'] + ) + verify_required_component_property_value( + entity_name=light_entity_name, + component=light_entity.components[0], + property_path=LIGHT_TYPE_PROPERTY, + expected_property_value=LIGHT_TYPES['capsule'] + ) + + # Quad light type checks. + light_type_property_test( + light_type=LIGHT_TYPES['quad'], + light_properties=QUAD_LIGHT_PROPERTIES, + light_component_id_pair=light_component_id_pair, + light_entity_name=light_entity_name, + light_entity=light_entity + ) + + # Polygon light type checks. + azlmbr.editor.EditorComponentAPIBus( + azlmbr.bus.Broadcast, + 'SetComponentProperty', + light_component_id_pair, + LIGHT_TYPE_PROPERTY, + LIGHT_TYPES['polygon'] + ) + verify_required_component_property_value( + entity_name=light_entity_name, + component=light_entity.components[0], + property_path=LIGHT_TYPE_PROPERTY, + expected_property_value=LIGHT_TYPES['polygon'] + ) + + # Point (simple punctual) light type checks. + light_type_property_test( + light_type=LIGHT_TYPES['simple_point'], + light_properties=SIMPLE_POINT_LIGHT_PROPERTIES, + light_component_id_pair=light_component_id_pair, + light_entity_name=light_entity_name, + light_entity=light_entity + ) + + # Spot (simple punctual) light type checks. + light_type_property_test( + light_type=LIGHT_TYPES['simple_spot'], + light_properties=SIMPLE_SPOT_LIGHT_PROPERTIES, + light_component_id_pair=light_component_id_pair, + light_entity_name=light_entity_name, + light_entity=light_entity + ) + + general.log("Light component test (non-GPU) completed.") + + +def light_type_property_test(light_type, light_properties, light_component_id_pair, light_entity_name, light_entity): + """ + Updates the current light type and modifies its properties, then verifies they are accurate to what was set. + :param light_type: The type of light to update, must match a value in LIGHT_TYPES + :param light_properties: List of tuples detailing properties to modify with update values. + :param light_component_id_pair: Entity + component ID pair for updating the light component on a given entity. + :param light_entity_name: the name of the Entity holding the light component. + :param light_entity: the Entity object containing the light component. + :return: None + """ + azlmbr.editor.EditorComponentAPIBus( + azlmbr.bus.Broadcast, + 'SetComponentProperty', + light_component_id_pair, + LIGHT_TYPE_PROPERTY, + light_type + ) + verify_required_component_property_value( + entity_name=light_entity_name, + component=light_entity.components[0], + property_path=LIGHT_TYPE_PROPERTY, + expected_property_value=light_type + ) + + for light_property in light_properties: + light_entity.get_set_test(0, light_property[0], light_property[1]) + + +if __name__ == "__main__": + run() diff --git a/AutomatedTesting/Gem/PythonTests/atom_renderer/atom_utils/atom_component_helper.py b/AutomatedTesting/Gem/PythonTests/atom_renderer/atom_utils/atom_component_helper.py new file mode 100644 index 0000000000..de4e28bb36 --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/atom_renderer/atom_utils/atom_component_helper.py @@ -0,0 +1,19 @@ +""" +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 + +File to assist with common hydra component functions or constants used across various Atom tests. +""" + +# Light type options for the Light component. +LIGHT_TYPES = { + 'unknown': 0, + 'sphere': 1, + 'spot_disk': 2, + 'capsule': 3, + 'quad': 4, + 'polygon': 5, + 'simple_point': 6, + 'simple_spot': 7, +} diff --git a/AutomatedTesting/Gem/PythonTests/atom_renderer/test_Atom_MainSuite.py b/AutomatedTesting/Gem/PythonTests/atom_renderer/test_Atom_MainSuite.py index ed5d057626..98d2ba0632 100644 --- a/AutomatedTesting/Gem/PythonTests/atom_renderer/test_Atom_MainSuite.py +++ b/AutomatedTesting/Gem/PythonTests/atom_renderer/test_Atom_MainSuite.py @@ -12,9 +12,10 @@ import os import pytest import editor_python_test_tools.hydra_test_utils as hydra +from atom_renderer.atom_utils.atom_component_helper import LIGHT_TYPES logger = logging.getLogger(__name__) -EDITOR_TIMEOUT = 300 +EDITOR_TIMEOUT = 120 TEST_DIRECTORY = os.path.join(os.path.dirname(__file__), "atom_hydra_scripts") @@ -180,3 +181,64 @@ class TestAtomEditorComponentsMain(object): null_renderer=True, cfg_args=cfg_args, ) + + def test_AtomEditorComponents_LightComponent( + self, request, editor, workspace, project, launcher_platform, level): + """ + Please review the hydra script run by this test for more specific test info. + Tests that the Light component has the expected property options available to it. + """ + cfg_args = [level] + + expected_lines = [ + "light_entity Entity successfully created", + "Entity has a Light component", + "light_entity_test: Component added to the entity: True", + f"light_entity_test: Property value is {LIGHT_TYPES['sphere']} which matches {LIGHT_TYPES['sphere']}", + "Controller|Configuration|Shadows|Enable shadow set to True", + "light_entity Controller|Configuration|Shadows|Shadowmap size: SUCCESS", + "Controller|Configuration|Shadows|Shadow filter method set to 1", # PCF + "Controller|Configuration|Shadows|Filtering sample count set to 4", + "Controller|Configuration|Shadows|Filtering sample count set to 64", + "Controller|Configuration|Shadows|PCF method set to 0", + "Controller|Configuration|Shadows|PCF method set to 1", + "Controller|Configuration|Shadows|Shadow filter method set to 2", # ESM + "Controller|Configuration|Shadows|ESM exponent set to 50.0", + "Controller|Configuration|Shadows|ESM exponent set to 5000.0", + "Controller|Configuration|Shadows|Shadow filter method set to 3", # ESM+PCF + f"light_entity_test: Property value is {LIGHT_TYPES['spot_disk']} which matches {LIGHT_TYPES['spot_disk']}", + f"light_entity_test: Property value is {LIGHT_TYPES['capsule']} which matches {LIGHT_TYPES['capsule']}", + f"light_entity_test: Property value is {LIGHT_TYPES['quad']} which matches {LIGHT_TYPES['quad']}", + "light_entity Controller|Configuration|Fast approximation: SUCCESS", + "light_entity Controller|Configuration|Both directions: SUCCESS", + f"light_entity_test: Property value is {LIGHT_TYPES['polygon']} which matches {LIGHT_TYPES['polygon']}", + f"light_entity_test: Property value is {LIGHT_TYPES['simple_point']} " + f"which matches {LIGHT_TYPES['simple_point']}", + "Controller|Configuration|Attenuation radius|Mode set to 0", + "Controller|Configuration|Attenuation radius|Radius set to 100.0", + f"light_entity_test: Property value is {LIGHT_TYPES['simple_spot']} " + f"which matches {LIGHT_TYPES['simple_spot']}", + "Controller|Configuration|Shutters|Outer angle set to 45.0", + "Controller|Configuration|Shutters|Outer angle set to 90.0", + "light_entity_test: Component added to the entity: True", + "Light component test (non-GPU) completed.", + ] + + unexpected_lines = [ + "Trace::Assert", + "Trace::Error", + "Traceback (most recent call last):", + ] + + hydra.launch_and_validate_results( + request, + TEST_DIRECTORY, + editor, + "hydra_AtomEditorComponents_LightComponent.py", + timeout=EDITOR_TIMEOUT, + expected_lines=expected_lines, + unexpected_lines=unexpected_lines, + halt_on_unexpected=True, + null_renderer=True, + cfg_args=cfg_args, + ) From 24740b3f8609c9af836d4bf13dd393a2f049ea26 Mon Sep 17 00:00:00 2001 From: Chris Burel Date: Tue, 3 Aug 2021 14:52:53 -0700 Subject: [PATCH 24/51] Update the cloth rule to look for optimized meshes (#2737) The cloth rule stores the name of a mesh node that is used to retrieve cloth data from. However, at asset processing time, the model builder switches things to look for the optimized version of a mesh. The cloth rule was not doing this, so it would return the cloth data for the unoptimized mesh. This resulted in the final mesh having some data from the optimized mesh and cloth data from the non-optimized mesh. This changes the cloth rule to use the optimized version of a mesh, if it exists, and fall back to the unoptimized mesh when it does not exist. This closes issue 2454. Signed-off-by: Chris Burel --- .../RPI.Builders/Model/ModelAssetBuilderComponent.cpp | 2 +- .../Code/Source/Pipeline/SceneAPIExt/ClothRule.cpp | 10 +++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/Gems/Atom/RPI/Code/Source/RPI.Builders/Model/ModelAssetBuilderComponent.cpp b/Gems/Atom/RPI/Code/Source/RPI.Builders/Model/ModelAssetBuilderComponent.cpp index 7100b2cd48..76e427a708 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Builders/Model/ModelAssetBuilderComponent.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Builders/Model/ModelAssetBuilderComponent.cpp @@ -109,7 +109,7 @@ namespace AZ if (auto* serialize = azrtti_cast(context)) { serialize->Class() - ->Version(29); // (updated to separate material slot ID from default material asset) + ->Version(30); // (updated to separate material slot ID from default material asset) } } diff --git a/Gems/NvCloth/Code/Source/Pipeline/SceneAPIExt/ClothRule.cpp b/Gems/NvCloth/Code/Source/Pipeline/SceneAPIExt/ClothRule.cpp index 383ea9e5f6..a2683be2ab 100644 --- a/Gems/NvCloth/Code/Source/Pipeline/SceneAPIExt/ClothRule.cpp +++ b/Gems/NvCloth/Code/Source/Pipeline/SceneAPIExt/ClothRule.cpp @@ -37,7 +37,15 @@ namespace NvCloth const AZ::SceneAPI::Containers::SceneGraph& graph, const size_t numVertices) const { - const auto meshNodeIndex = graph.Find(GetMeshNodeName()); + const AZ::SceneAPI::Containers::SceneGraph::NodeIndex meshNodeIndex = [this, &graph]() + { + if (const auto index = graph.Find(GetMeshNodeName() + AZStd::string(AZ::SceneAPI::Utilities::OptimizedMeshSuffix)); index.IsValid()) + { + return index; + } + return graph.Find(GetMeshNodeName()); + }(); + if (!meshNodeIndex.IsValid()) { return {}; From f269d222b7e699ae6fb4f02fdcbcd50a792e959d Mon Sep 17 00:00:00 2001 From: Guthrie Adams Date: Tue, 3 Aug 2021 17:07:03 -0500 Subject: [PATCH 25/51] Fixing issues with shader management console startup Updating test scripts Synchronizing SMC and ME application classes Signed-off-by: Guthrie Adams --- .../Application/AtomToolsApplication.cpp | 6 +-- .../Code/Source/MaterialEditorApplication.cpp | 10 ++-- .../Scripts/GenerateAllMaterialScreenshots.py | 4 +- .../ShaderManagementConsoleApplication.cpp | 48 ++++++++++--------- .../ShaderManagementConsoleApplication.h | 7 +-- 5 files changed, 38 insertions(+), 37 deletions(-) diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Application/AtomToolsApplication.cpp b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Application/AtomToolsApplication.cpp index 3a542db1a4..3d9219edc5 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Application/AtomToolsApplication.cpp +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Application/AtomToolsApplication.cpp @@ -87,14 +87,12 @@ namespace AtomToolsFramework if (auto behaviorContext = azrtti_cast(context)) { - auto targetName = GetBuildTargetName(); - // this will put these methods into the 'azlmbr.AtomTools.general' module - auto addGeneral = [targetName](AZ::BehaviorContext::GlobalMethodBuilder methodBuilder) + auto addGeneral = [](AZ::BehaviorContext::GlobalMethodBuilder methodBuilder) { methodBuilder->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation) ->Attribute(AZ::Script::Attributes::Category, "Editor") - ->Attribute(AZ::Script::Attributes::Module, targetName); + ->Attribute(AZ::Script::Attributes::Module, "atomtools.general"); }; // The reflection here is based on patterns in CryEditPythonHandler::Reflect addGeneral(behaviorContext->Method( diff --git a/Gems/Atom/Tools/MaterialEditor/Code/Source/MaterialEditorApplication.cpp b/Gems/Atom/Tools/MaterialEditor/Code/Source/MaterialEditorApplication.cpp index 0977694b90..c68c139961 100644 --- a/Gems/Atom/Tools/MaterialEditor/Code/Source/MaterialEditorApplication.cpp +++ b/Gems/Atom/Tools/MaterialEditor/Code/Source/MaterialEditorApplication.cpp @@ -9,7 +9,6 @@ #include #include #include -#include #include #include #include @@ -28,11 +27,11 @@ #include -#include #include #include #include +#include #include #include #include @@ -73,15 +72,14 @@ namespace MaterialEditor { QApplication::setApplicationName("O3DE Material Editor"); + // The settings registry has been created at this point, so add the CMake target AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddBuildSystemTargetSpecialization( *AZ::SettingsRegistry::Get(), GetBuildTargetName()); } MaterialEditorApplication::~MaterialEditorApplication() { - AzToolsFramework::AssetDatabase::AssetDatabaseRequestsBus::Handler::BusDisconnect(); MaterialEditorWindowNotificationBus::Handler::BusDisconnect(); - AzToolsFramework::EditorPythonConsoleNotificationBus::Handler::BusDisconnect(); } void MaterialEditorApplication::CreateStaticModules(AZStd::vector& outModules) @@ -122,8 +120,8 @@ namespace MaterialEditor &MaterialEditor::MaterialEditorWindowRequestBus::Handler::ActivateWindow); } - // Process command line options for opening one or more material documents on startup - size_t openDocumentCount = commandLine.GetNumMiscValues(); + // Process command line options for opening one or more documents on startup + size_t openDocumentCount = m_commandLine.GetNumMiscValues(); for (size_t openDocumentIndex = 0; openDocumentIndex < openDocumentCount; ++openDocumentIndex) { const AZStd::string openDocumentPath = commandLine.GetMiscValue(openDocumentIndex); diff --git a/Gems/Atom/Tools/MaterialEditor/Scripts/GenerateAllMaterialScreenshots.py b/Gems/Atom/Tools/MaterialEditor/Scripts/GenerateAllMaterialScreenshots.py index d2c9bf209e..d7f52d7a24 100755 --- a/Gems/Atom/Tools/MaterialEditor/Scripts/GenerateAllMaterialScreenshots.py +++ b/Gems/Atom/Tools/MaterialEditor/Scripts/GenerateAllMaterialScreenshots.py @@ -114,11 +114,11 @@ def SetCameraPitch(pitch): azlmbr.render.ArcBallControllerRequestBus(azlmbr.bus.Broadcast, 'SetPitch', pitch) def IdleFrames(numFrames): - azlmbr.materialeditor.general.idle_wait_frames(numFrames) + azlmbr.atomtools.general.idle_wait_frames(numFrames) def CaptureScreenshot(screenshotOutputPath): print("Capturing screenshot to " + screenshotOutputPath + " ...") - return ScreenshotHelper(azlmbr.materialeditor.general.idle_wait_frames).capture_screenshot_blocking(screenshotOutputPath) + return ScreenshotHelper(azlmbr.atomtools.general.idle_wait_frames).capture_screenshot_blocking(screenshotOutputPath) def ResizeViewport(width, height): # This locks the size of the render target to the desired resolution diff --git a/Gems/Atom/Tools/ShaderManagementConsole/Code/Source/ShaderManagementConsoleApplication.cpp b/Gems/Atom/Tools/ShaderManagementConsole/Code/Source/ShaderManagementConsoleApplication.cpp index 7e09f88f4f..66580e5906 100644 --- a/Gems/Atom/Tools/ShaderManagementConsole/Code/Source/ShaderManagementConsoleApplication.cpp +++ b/Gems/Atom/Tools/ShaderManagementConsole/Code/Source/ShaderManagementConsoleApplication.cpp @@ -25,26 +25,27 @@ #include #include -#include -#include #include -#include -#include +#include +#include #include #include #include #include +#include +#include + AZ_PUSH_DISABLE_WARNING(4251 4800, "-Wunknown-warning-option") // disable warnings spawned by QT -#include #include #include AZ_POP_DISABLE_WARNING namespace ShaderManagementConsole { + //! This function returns the build system target name of "ShaderManagementConsole AZStd::string ShaderManagementConsoleApplication::GetBuildTargetName() const { #if !defined(LY_CMAKE_TARGET) @@ -74,6 +75,11 @@ namespace ShaderManagementConsole *AZ::SettingsRegistry::Get(), GetBuildTargetName()); } + ShaderManagementConsoleApplication::~ShaderManagementConsoleApplication() + { + ShaderManagementConsoleWindowNotificationBus::Handler::BusDisconnect(); + } + void ShaderManagementConsoleApplication::CreateStaticModules(AZStd::vector& outModules) { Base::CreateStaticModules(outModules); @@ -84,8 +90,6 @@ namespace ShaderManagementConsole void ShaderManagementConsoleApplication::OnShaderManagementConsoleWindowClosing() { ExitMainLoop(); - ShaderManagementConsoleWindowNotificationBus::Handler::BusDisconnect(); - AzToolsFramework::EditorPythonConsoleNotificationBus::Handler::BusDisconnect(); } void ShaderManagementConsoleApplication::Destroy() @@ -104,27 +108,19 @@ namespace ShaderManagementConsole return AZStd::vector({ "passes/", "config/" }); } - void ShaderManagementConsoleApplication::ProcessCommandLine() + void ShaderManagementConsoleApplication::ProcessCommandLine(const AZ::CommandLine& commandLine) { - // Process command line options for running one or more python scripts on startup - const AZStd::string runPythonScriptSwitchName = "runpython"; - size_t runPythonScriptCount = m_commandLine.GetNumSwitchValues(runPythonScriptSwitchName); - for (size_t runPythonScriptIndex = 0; runPythonScriptIndex < runPythonScriptCount; ++runPythonScriptIndex) - { - const AZStd::string runPythonScriptPath = m_commandLine.GetSwitchValue(runPythonScriptSwitchName, runPythonScriptIndex); - AZStd::vector runPythonArgs; - AzToolsFramework::EditorPythonRunnerRequestBus::Broadcast( - &AzToolsFramework::EditorPythonRunnerRequestBus::Events::ExecuteByFilenameWithArgs, runPythonScriptPath, runPythonArgs); - } - // Process command line options for opening one or more documents on startup size_t openDocumentCount = m_commandLine.GetNumMiscValues(); for (size_t openDocumentIndex = 0; openDocumentIndex < openDocumentCount; ++openDocumentIndex) { - const AZStd::string openDocumentPath = m_commandLine.GetMiscValue(openDocumentIndex); - ShaderManagementConsoleDocumentSystemRequestBus::Broadcast( - &ShaderManagementConsoleDocumentSystemRequestBus::Events::OpenDocument, openDocumentPath); + const AZStd::string openDocumentPath = commandLine.GetMiscValue(openDocumentIndex); + + AZ_Printf(GetBuildTargetName().c_str(), "Opening document: %s", openDocumentPath.c_str()); + ShaderManagementConsoleDocumentSystemRequestBus::Broadcast(&ShaderManagementConsoleDocumentSystemRequestBus::Events::OpenDocument, openDocumentPath); } + + Base::ProcessCommandLine(commandLine); } void ShaderManagementConsoleApplication::StartInternal() @@ -136,4 +132,12 @@ namespace ShaderManagementConsole ShaderManagementConsole::ShaderManagementConsoleWindowRequestBus::Broadcast( &ShaderManagementConsole::ShaderManagementConsoleWindowRequestBus::Handler::CreateShaderManagementConsoleWindow); } + + void ShaderManagementConsoleApplication::Stop() + { + ShaderManagementConsole::ShaderManagementConsoleWindowRequestBus::Broadcast( + &ShaderManagementConsole::ShaderManagementConsoleWindowRequestBus::Handler::DestroyShaderManagementConsoleWindow); + + Base::Stop(); + } } // namespace ShaderManagementConsole diff --git a/Gems/Atom/Tools/ShaderManagementConsole/Code/Source/ShaderManagementConsoleApplication.h b/Gems/Atom/Tools/ShaderManagementConsole/Code/Source/ShaderManagementConsoleApplication.h index 5d3696fee3..d0a2b800b2 100644 --- a/Gems/Atom/Tools/ShaderManagementConsole/Code/Source/ShaderManagementConsoleApplication.h +++ b/Gems/Atom/Tools/ShaderManagementConsole/Code/Source/ShaderManagementConsoleApplication.h @@ -26,12 +26,13 @@ namespace ShaderManagementConsole using Base = AtomToolsFramework::AtomToolsApplication; ShaderManagementConsoleApplication(int* argc, char*** argv); - virtual ~ShaderManagementConsoleApplication() = default; + virtual ~ShaderManagementConsoleApplication(); ////////////////////////////////////////////////////////////////////////// // AzFramework::Application void CreateStaticModules(AZStd::vector& outModules) override; const char* GetCurrentConfigurationName() const override; + void Stop() override; private: ////////////////////////////////////////////////////////////////////////// @@ -44,9 +45,9 @@ namespace ShaderManagementConsole void Destroy() override; ////////////////////////////////////////////////////////////////////////// - void ProcessCommandLine(); + void ProcessCommandLine(const AZ::CommandLine& commandLine) override; void StartInternal() override; AZStd::string GetBuildTargetName() const override; AZStd::vector GetCriticalAssetFilters() const override; - }; + }; } // namespace ShaderManagementConsole From b594132a47b834570763eb3bbac15110f4356de1 Mon Sep 17 00:00:00 2001 From: Guthrie Adams Date: Tue, 3 Aug 2021 17:19:30 -0500 Subject: [PATCH 26/51] using command line parameter instead of member Signed-off-by: Guthrie Adams --- .../MaterialEditor/Code/Source/MaterialEditorApplication.cpp | 2 +- .../Code/Source/ShaderManagementConsoleApplication.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Gems/Atom/Tools/MaterialEditor/Code/Source/MaterialEditorApplication.cpp b/Gems/Atom/Tools/MaterialEditor/Code/Source/MaterialEditorApplication.cpp index c68c139961..6f1bdfb083 100644 --- a/Gems/Atom/Tools/MaterialEditor/Code/Source/MaterialEditorApplication.cpp +++ b/Gems/Atom/Tools/MaterialEditor/Code/Source/MaterialEditorApplication.cpp @@ -121,7 +121,7 @@ namespace MaterialEditor } // Process command line options for opening one or more documents on startup - size_t openDocumentCount = m_commandLine.GetNumMiscValues(); + size_t openDocumentCount = commandLine.GetNumMiscValues(); for (size_t openDocumentIndex = 0; openDocumentIndex < openDocumentCount; ++openDocumentIndex) { const AZStd::string openDocumentPath = commandLine.GetMiscValue(openDocumentIndex); diff --git a/Gems/Atom/Tools/ShaderManagementConsole/Code/Source/ShaderManagementConsoleApplication.cpp b/Gems/Atom/Tools/ShaderManagementConsole/Code/Source/ShaderManagementConsoleApplication.cpp index 66580e5906..947cf55050 100644 --- a/Gems/Atom/Tools/ShaderManagementConsole/Code/Source/ShaderManagementConsoleApplication.cpp +++ b/Gems/Atom/Tools/ShaderManagementConsole/Code/Source/ShaderManagementConsoleApplication.cpp @@ -111,7 +111,7 @@ namespace ShaderManagementConsole void ShaderManagementConsoleApplication::ProcessCommandLine(const AZ::CommandLine& commandLine) { // Process command line options for opening one or more documents on startup - size_t openDocumentCount = m_commandLine.GetNumMiscValues(); + size_t openDocumentCount = commandLine.GetNumMiscValues(); for (size_t openDocumentIndex = 0; openDocumentIndex < openDocumentCount; ++openDocumentIndex) { const AZStd::string openDocumentPath = commandLine.GetMiscValue(openDocumentIndex); From 6188df4b4964e303d2bc8e83d7bc2091a33f1146 Mon Sep 17 00:00:00 2001 From: lsemp3d <58790905+lsemp3d@users.noreply.github.com> Date: Tue, 3 Aug 2021 15:36:56 -0700 Subject: [PATCH 27/51] Added Get Direction Vector node for Vector2,3 and 4 Signed-off-by: lsemp3d <58790905+lsemp3d@users.noreply.github.com> --- .../Editor/Translation/scriptcanvas_en_us.ts | 195 ++++++++++++++++++ .../Libraries/Math/Vector2Nodes.h | 17 ++ .../Libraries/Math/Vector3Nodes.h | 18 ++ .../Libraries/Math/Vector4Nodes.h | 19 +- 4 files changed, 248 insertions(+), 1 deletion(-) diff --git a/Assets/Editor/Translation/scriptcanvas_en_us.ts b/Assets/Editor/Translation/scriptcanvas_en_us.ts index f057ba2f30..5604cfb631 100644 --- a/Assets/Editor/Translation/scriptcanvas_en_us.ts +++ b/Assets/Editor/Translation/scriptcanvas_en_us.ts @@ -2771,6 +2771,71 @@ VECTOR2_CREATEONE_OUTPUT0_TOOLTIP + + VECTOR2_DIRECTIONTO_NAME + Class/Bus: Vector2 Event/Method: DirectionTo + Get Direction Vector + + + VECTOR2_DIRECTIONTO_TOOLTIP + + + + VECTOR2_DIRECTIONTO_CATEGORY + + + + VECTOR2_DIRECTIONTO_OUT_NAME + + + + VECTOR2_DIRECTIONTO_OUT_TOOLTIP + + + + VECTOR2_DIRECTIONTOL_IN_NAME + + + + VECTOR2_DIRECTIONTO_IN_TOOLTIP + + + + VECTOR2_DIRECTIONTO_OUTPUT0_NAME + C++ Type: const Vector2 + Direction + + + VECTOR2_DIRECTIONTO_OUTPUT0_TOOLTIP + + + + VECTOR2_DIRECTIONTO_PARAM0_NAME + Simple Type: Vector2 C++ Type: Vector2* + From + + + VECTOR2_DIRECTIONTO_PARAM0_TOOLTIP + + + + VECTOR2_DIRECTIONTO_PARAM1_NAME + Simple Type: Vector2 C++ Type: Vector2* + To + + + VECTOR2_DIRECTIONTO_PARAM1_TOOLTIP + + + + VECTOR2_DIRECTIONTO_PARAM2_NAME + Simple Type: Vector2 C++ Type: Vector2* + Scale + + + VECTOR2_DIRECTIONTO_PARAM2_TOOLTIP + + VECTOR2_GETPROJECTED_NAME Class/Bus: Vector2 Event/Method: GetProjected @@ -32262,6 +32327,71 @@ An Entity can be selected by using the pick button, or by dragging an Entity fro VECTOR4_GETRECIPROCAL_PARAM0_TOOLTIP + + VECTOR4_DIRECTIONTO_NAME + Class/Bus: Vector4 Event/Method: DirectionTo + Get Direction Vector + + + VECTOR4_DIRECTIONTO_TOOLTIP + + + + VECTOR4_DIRECTIONTO_CATEGORY + + + + VECTOR4_DIRECTIONTO_OUT_NAME + + + + VECTOR4_DIRECTIONTO_OUT_TOOLTIP + + + + VECTOR4_DIRECTIONTOL_IN_NAME + + + + VECTOR4_DIRECTIONTO_IN_TOOLTIP + + + + VECTOR4_DIRECTIONTO_OUTPUT0_NAME + C++ Type: const Vector4 + Direction + + + VECTOR4_DIRECTIONTO_OUTPUT0_TOOLTIP + + + + VECTOR4_DIRECTIONTO_PARAM0_NAME + Simple Type: Vector4 C++ Type: Vector4* + From + + + VECTOR4_DIRECTIONTO_PARAM0_TOOLTIP + + + + VECTOR4_DIRECTIONTO_PARAM1_NAME + Simple Type: Vector4 C++ Type: Vector4* + To + + + VECTOR4_DIRECTIONTO_PARAM1_TOOLTIP + + + + VECTOR4_DIRECTIONTO_PARAM2_NAME + Simple Type: Vector4 C++ Type: Vector4* + Scale + + + VECTOR4_DIRECTIONTO_PARAM2_TOOLTIP + + VECTOR4_AXISX_NAME Class/Bus: Vector4 Event/Method: CreateAxisX @@ -37469,6 +37599,71 @@ An Entity can be selected by using the pick button, or by dragging an Entity fro VECTOR3_GETRECIPROCAL_PARAM0_TOOLTIP + + VECTOR3_DIRECTIONTO_NAME + Class/Bus: Vector3 Event/Method: DirectionTo + Get Direction Vector + + + VECTOR3_DIRECTIONTO_TOOLTIP + + + + VECTOR3_DIRECTIONTO_CATEGORY + + + + VECTOR3_DIRECTIONTO_OUT_NAME + + + + VECTOR3_DIRECTIONTO_OUT_TOOLTIP + + + + VECTOR3_DIRECTIONTOL_IN_NAME + + + + VECTOR3_DIRECTIONTO_IN_TOOLTIP + + + + VECTOR3_DIRECTIONTO_OUTPUT0_NAME + C++ Type: const Vector3 + Direction + + + VECTOR3_DIRECTIONTO_OUTPUT0_TOOLTIP + + + + VECTOR3_DIRECTIONTO_PARAM0_NAME + Simple Type: Vector3 C++ Type: Vector3* + From + + + VECTOR3_DIRECTIONTO_PARAM0_TOOLTIP + + + + VECTOR3_DIRECTIONTO_PARAM1_NAME + Simple Type: Vector3 C++ Type: Vector3* + To + + + VECTOR3_DIRECTIONTO_PARAM1_TOOLTIP + + + + VECTOR3_DIRECTIONTO_PARAM2_NAME + Simple Type: Vector3 C++ Type: Vector3* + Scale + + + VECTOR3_DIRECTIONTO_PARAM2_TOOLTIP + + VECTOR3_PROJECT_NAME Class/Bus: Vector3 Event/Method: Project diff --git a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Math/Vector2Nodes.h b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Math/Vector2Nodes.h index c4fee491a3..4ac14a7db6 100644 --- a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Math/Vector2Nodes.h +++ b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Math/Vector2Nodes.h @@ -243,6 +243,22 @@ namespace ScriptCanvas } SCRIPT_CANVAS_GENERIC_FUNCTION_NODE(ToPerpendicular, k_categoryName, "{CC4DC102-8B50-4828-BA94-0586F34E0D37}", "returns the vector (-Source.y, Source.x), a 90 degree, positive rotation", "Source"); + AZ_INLINE void DirectionToDefaults(Node& node) + { + SetDefaultValuesByIndex<0>::_(node, Data::Vector2Type()); + SetDefaultValuesByIndex<1>::_(node, Data::Vector2Type()); + SetDefaultValuesByIndex<2>::_(node, Data::NumberType(1.)); + } + + AZ_INLINE Vector2Type DirectionTo(const Vector2Type from, const Vector2Type to, NumberType optionalScale = 1.f) + { + Vector2Type r = to - from; + r.Normalize(); + r.SetLength(optionalScale); + return r; + } + SCRIPT_CANVAS_GENERIC_FUNCTION_NODE_WITH_DEFAULTS(DirectionTo, DirectionToDefaults, k_categoryName, "{49A2D7F6-6CD3-420E-8A79-D46B00DB6CED}", "Given two points in space, return a direction vector", "From", "To", "Scale"); + using Registrar = RegistrarGeneric < AbsoluteNode , AddNode @@ -295,6 +311,7 @@ namespace ScriptCanvas , SlerpNode , SubtractNode , ToPerpendicularNode + , DirectionToNode > ; } diff --git a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Math/Vector3Nodes.h b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Math/Vector3Nodes.h index 5962cee487..eb304c8362 100644 --- a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Math/Vector3Nodes.h +++ b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Math/Vector3Nodes.h @@ -329,6 +329,23 @@ namespace ScriptCanvas } SCRIPT_CANVAS_GENERIC_FUNCTION_NODE(ZAxisCross, k_categoryName, "{29206E84-392C-412E-9DD5-781B2759260D}", "returns the vector cross product of Z-Axis X Source", "Source"); + AZ_INLINE void DirectionToDefaults(Node& node) + { + SetDefaultValuesByIndex<0>::_(node, Data::Vector3Type()); + SetDefaultValuesByIndex<1>::_(node, Data::Vector3Type()); + SetDefaultValuesByIndex<2>::_(node, Data::NumberType(1.)); + } + + AZ_INLINE Vector3Type DirectionTo(const Vector3Type from, const Vector3Type to, NumberType optionalScale = 1.f) + { + Vector3Type r = to - from; + r.Normalize(); + r.SetLength(optionalScale); + return r; + } + SCRIPT_CANVAS_GENERIC_FUNCTION_NODE_WITH_DEFAULTS(DirectionTo, DirectionToDefaults, k_categoryName, "{28FBD529-4C9A-4E34-B8A0-A13B5DB3C331}", "Given two points in space, return a direction vector", "From", "To", "Scale"); + + using Registrar = RegistrarGeneric < AbsoluteNode , AddNode @@ -403,6 +420,7 @@ namespace ScriptCanvas , SlerpNode , SubtractNode + , DirectionToNode #if ENABLE_EXTENDED_MATH_SUPPORT , XAxisCrossNode diff --git a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Math/Vector4Nodes.h b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Math/Vector4Nodes.h index 4522ba07dc..ac9be8f135 100644 --- a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Math/Vector4Nodes.h +++ b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Math/Vector4Nodes.h @@ -214,6 +214,22 @@ namespace ScriptCanvas } SCRIPT_CANVAS_GENERIC_FUNCTION_NODE_DEPRECATED(Subtract, k_categoryName, "{A5FA6465-9C39-4A44-BD7C-E8ECF9503E46}", "This node is deprecated, use Subtract (-), it provides contextual type and slots", "A", "B"); + AZ_INLINE void DirectionToDefaults(Node& node) + { + SetDefaultValuesByIndex<0>::_(node, Data::Vector4Type()); + SetDefaultValuesByIndex<1>::_(node, Data::Vector4Type()); + SetDefaultValuesByIndex<2>::_(node, Data::NumberType(1.)); + } + + AZ_INLINE Vector4Type DirectionTo(const Vector4Type from, const Vector4Type to, NumberType optionalScale = 1.f) + { + Vector4Type r = to - from; + r.Normalize(); + r.SetLength(optionalScale); + return r; + } + SCRIPT_CANVAS_GENERIC_FUNCTION_NODE_WITH_DEFAULTS(DirectionTo, DirectionToDefaults, k_categoryName, "{463762DE-E541-4AFE-80C2-FED1C5273319}", "Given two points in space, return a direction vector", "From", "To", "Scale"); + using Registrar = RegistrarGeneric < AbsoluteNode, AddNode, @@ -260,7 +276,8 @@ namespace ScriptCanvas #endif ReciprocalNode, - SubtractNode + SubtractNode, + DirectionToNode > ; } From 45ebf57d3f9bb42767894240e4fc701d5f5d9a34 Mon Sep 17 00:00:00 2001 From: AMZN-AlexOteiza <82234181+AMZN-AlexOteiza@users.noreply.github.com> Date: Wed, 4 Aug 2021 02:38:18 +0100 Subject: [PATCH 28/51] Fixed bug in hash_table that made rehash() function run forever (#2745) * Fixed bug in hash_table that made rehash() function to run infinitely on specific conditions when inserting an already existing element Signed-off-by: Garcia Ruiz * Replaced erasing to happen in the source list instead Signed-off-by: Garcia Ruiz * minor comment improvement Signed-off-by: Garcia Ruiz * Small commment improvement Signed-off-by: Garcia Ruiz * Small comment fix Signed-off-by: Garcia Ruiz * Added assert and fixed code with incorrect hashing Signed-off-by: Garcia Ruiz * . Signed-off-by: Garcia Ruiz * Addressed PR comments, reverted to void* as it size_t hash is different Signed-off-by: Garcia Ruiz * Fixed build on linux Signed-off-by: Garcia Ruiz * Addressed PR comments Signed-off-by: Garcia Ruiz Co-authored-by: Garcia Ruiz --- Code/Framework/AzCore/AzCore/std/hash.cpp | 1 + Code/Framework/AzCore/AzCore/std/hash_table.h | 68 ++++++++++++------- Code/Framework/AzCore/Tests/AZStd/Hashed.cpp | 49 +++++++++++++ .../PhysXSceneSimulationFilterCallback.cpp | 4 +- 4 files changed, 95 insertions(+), 27 deletions(-) diff --git a/Code/Framework/AzCore/AzCore/std/hash.cpp b/Code/Framework/AzCore/AzCore/std/hash.cpp index c2f7a104d4..b5277de2f4 100644 --- a/Code/Framework/AzCore/AzCore/std/hash.cpp +++ b/Code/Framework/AzCore/AzCore/std/hash.cpp @@ -21,6 +21,7 @@ namespace AZStd 1610612741ul, 3221225473ul, 4294967291ul }; + // Bucket size suitable to hold n elements. AZStd::size_t hash_next_bucket_size(AZStd::size_t n) { const AZStd::size_t* first = prime_list; diff --git a/Code/Framework/AzCore/AzCore/std/hash_table.h b/Code/Framework/AzCore/AzCore/std/hash_table.h index 5b76b6cb82..c364720b3b 100644 --- a/Code/Framework/AzCore/AzCore/std/hash_table.h +++ b/Code/Framework/AzCore/AzCore/std/hash_table.h @@ -134,6 +134,7 @@ namespace AZStd void rehash(HashTable* table, size_type numBucketsMin) { size_type num_buckets = 0; + numBucketsMin = (AZStd::max)(numBucketsMin, (size_type)ceilf((float)m_list.size() / m_max_load_factor)); if (numBucketsMin != 0) @@ -143,7 +144,7 @@ namespace AZStd if (num_buckets == m_numBuckets) { - return; // no point + return; // no need yet to rehash } m_numBuckets = num_buckets; @@ -165,32 +166,43 @@ namespace AZStd while (!m_list.empty()) { cur = m_list.begin(); + typename list_type::iterator insertIter, curEnd(cur); + const typename HashTable::key_type& valueKey = Traits::key_from_value(*cur); - typename list_type::iterator newIter, iter(cur); size_type numValues = 1; - for (++iter; iter != last && table->m_keyEqual(Traits::key_from_value(*cur), Traits::key_from_value(*iter)); ++iter, ++numValues) + // Get the number of same consecutive elements in the table with same key, + // this allows range insertion of elements at once + for (++curEnd; curEnd != last && table->m_keyEqual(valueKey, Traits::key_from_value(*curEnd)); ++curEnd, ++numValues) { } - ; - const typename HashTable::key_type& valueKey = Traits::key_from_value(*cur); size_type newBucketIndex = table->bucket_from_hash(table->m_hasher(valueKey)); + + // newBucket.first holds the total number of elements in the bucket + // newBucket.second contains the pointer to the first element in the bucket vector_value_type& newBucket = newBuckets[newBucketIndex]; size_type numElements = newBucket.first; - newIter = newBucket.second; + insertIter = newBucket.second; + + // If we don't have elements in the bucket yet, transfer the elements directly if (numElements == 0) { - newList.splice(newList.begin(), m_list, cur, iter); + newList.splice(newList.begin(), m_list, cur, curEnd); newBucket.second = newList.begin(); } else { - if (!table->find_insert_position(valueKey, table->m_keyEqual, newIter, numElements, integral_constant())) + // Since there are elements already in the bucket, update `insertIter` to where the elements will need to be inserted. + if (!table->find_insert_position(valueKey, table->m_keyEqual, insertIter, numElements, integral_constant())) { - continue; + // An element was found but we don't allow for duplicate elements in this table. + // This happens when there was an insertion of two elements that are equal but have different hashes, + // which is undefined behavior for a hash table: ISO C++ N4713, section 23.14.15 - 5.3 + AZ_Assert(false, "Found a duplicate element when rehashing. " + "Review the hashing function for this type and make sure two equal elements always have the same hash"); } - newList.splice(newIter, m_list, cur, iter); + newList.splice(insertIter, m_list, cur, curEnd); } newBucket.first += numValues; @@ -251,15 +263,15 @@ namespace AZStd m_vector.set_allocator(typename vector_type::allocator_type(&m_allocator)); } - allocator_type m_allocator; ///< The single instance of the allocator shared between list and vector containers. - list_type m_list; ///< List with elements. - vector_type m_vector; ///< Buckets with list iterators. + allocator_type m_allocator; //!< The single instance of the allocator shared between list and vector containers. + list_type m_list; //!< List with elements. + vector_type m_vector; //!< Buckets with list iterators. private: - vector_value_type* m_buckets; ///< Current buckets array. (can point to the m_vector or m_startBucket). - size_type m_numBuckets; ///< Current number of buckets. - float m_max_load_factor; - vector_value_type m_startBucket; ///< Start bucket used for before we start dynamically allocate memory from m_vector. + vector_value_type* m_buckets; //!< Current buckets array. (can point to the m_vector or m_startBucket). + size_type m_numBuckets; //!< Current number of buckets. + float m_max_load_factor; //!< Maximum load (elements/buckets) before rehashing. + vector_value_type m_startBucket; //!< Start bucket used for before we start dynamically allocate memory from m_vector. }; /** @@ -321,8 +333,8 @@ namespace AZStd template AZ_FORCE_INLINE void rehash(HashTable*, size_type) {} - vector_type m_vector; ///< Buckets with list iterators. - list_type m_list; ///< List with elements. + vector_type m_vector; //!< Buckets with list iterators. + list_type m_list; //!< List with elements. }; } @@ -972,28 +984,32 @@ namespace AZStd rhs.clear(); } + // find_insert_position sets insertIter to where the element should be inserted + // and returns true if the element should be inserted, otherwise false template - bool find_insert_position(const ComparableToKey& keyCmp, const KeyEq& keyEq, iterator& iter, size_type numElements, const true_type& /* is multi elements */) + bool find_insert_position(const ComparableToKey& keyCmp, const KeyEq& keyEq, iterator& insertIter, size_type numElements, const true_type& /* is multi elements */) { - for (size_type i = 0; i < numElements; ++i, ++iter) + for (size_type i = 0; i < numElements; ++i, ++insertIter) { - if (keyEq(keyCmp, Traits::key_from_value(*iter))) + if (keyEq(keyCmp, Traits::key_from_value(*insertIter))) { - ++iter; + ++insertIter; break; } } + // always return true since multi elements (like multiset) allow repeated elements return true; } template - bool find_insert_position(const ComparableToKey& keyCmp, const KeyEq& keyEq, iterator& iter, size_type numElements, const false_type& /* !is multi elements */) + bool find_insert_position(const ComparableToKey& keyCmp, const KeyEq& keyEq, iterator& insertIter, size_type numElements, const false_type& /* !is multi elements */) { - for (size_type i = 0; i < numElements; ++i, ++iter) + for (size_type i = 0; i < numElements; ++i, ++insertIter) { - if (keyEq(keyCmp, Traits::key_from_value(*iter))) + if (keyEq(keyCmp, Traits::key_from_value(*insertIter))) { + // Element already exists, it shouldn't be inserted as we don't allow more than one repeated element for this specialization return false; } } diff --git a/Code/Framework/AzCore/Tests/AZStd/Hashed.cpp b/Code/Framework/AzCore/Tests/AZStd/Hashed.cpp index f2558289ea..4e4dfc1f89 100644 --- a/Code/Framework/AzCore/Tests/AZStd/Hashed.cpp +++ b/Code/Framework/AzCore/Tests/AZStd/Hashed.cpp @@ -287,6 +287,55 @@ namespace UnitTest } } + TEST_F(HashedContainers, HashTable_InsertionDuplicateOnRehash) + { + struct TwoPtrs + { + void* m_ptr1; + void* m_ptr2; + + bool operator==(const TwoPtrs& other) const + { + if (m_ptr1 == other.m_ptr1) + { + return m_ptr2 == other.m_ptr2; + } + else if (m_ptr1 == other.m_ptr2) + { + return m_ptr2 == other.m_ptr1; + } + return false; + } + }; + + // This hashing function produces different hashes for two equal values, + // which violates the requirement for hashing functions. + // The test makes sure that this does not reproduce an issue that caused the insert() function to loop infinitely. + struct TwoPtrsHasher + { + size_t operator()(const TwoPtrs& p) const + { + size_t hash{ 0 }; + AZStd::hash_combine(hash, p.m_ptr1, p.m_ptr2); + return hash; + } + }; + using PairSet = AZStd::unordered_set; + PairSet set; + set.insert({ (void*)1, (void*)2 }); + set.insert({ (void*)3, (void*)4 }); + set.insert({ (void*)5, (void*)6 }); + set.insert({ (void*)7, (void*)8 }); + // Elements with different hashes, but equal + set.insert({ (void*)0x000001ceddd9ca20, (void*)0x000001ceddd9cba0 }); // hash(148335135725641) + set.insert({ (void*)0x000001ceddd9cba0, (void*)0x000001ceddd9ca20 }); // hash(148335135764189) + AZ_TEST_START_TRACE_SUPPRESSION; + // This will trigger the assertion of duplicated elements found + // A bucket size of 23 since is where the collision between different hashes happens + set.rehash(23); + AZ_TEST_STOP_TRACE_SUPPRESSION(1); // 1 assertion + } + TEST_F(HashedContainers, HashTable_Fixed) { array elements = { diff --git a/Gems/PhysX/Code/Source/Scene/PhysXSceneSimulationFilterCallback.cpp b/Gems/PhysX/Code/Source/Scene/PhysXSceneSimulationFilterCallback.cpp index d902fb91ca..e103913cdf 100644 --- a/Gems/PhysX/Code/Source/Scene/PhysXSceneSimulationFilterCallback.cpp +++ b/Gems/PhysX/Code/Source/Scene/PhysXSceneSimulationFilterCallback.cpp @@ -55,7 +55,9 @@ namespace PhysX size_t SceneSimulationFilterCallback::CollisionPairHasher::operator()(const CollisionActorPair& collisionPair) const { size_t hash{ 0 }; - AZStd::hash_combine(hash, collisionPair.m_actorA, collisionPair.m_actorB); + // Order elements so {1,2} and {2,1} would generate the same hash + auto [smallerVal, biggerVal] = AZStd::minmax(collisionPair.m_actorA, collisionPair.m_actorB); + AZStd::hash_combine(hash, smallerVal, biggerVal); return hash; } From d06ec45aaa2f882f13008c6a5cc9578e9cbb291d Mon Sep 17 00:00:00 2001 From: aaguilea Date: Wed, 4 Aug 2021 13:05:00 +0100 Subject: [PATCH 29/51] changes to the move rotate and scale Signed-off-by: aaguilea --- Code/Editor/Core/LevelEditorMenuHandler.cpp | 8 +-- Code/Editor/CryEdit.cpp | 3 - Code/Editor/MainWindow.cpp | 69 ++++++++++++++++++--- Code/Editor/MainWindow.h | 14 +++-- Code/Editor/Resource.h | 5 -- Code/Editor/ViewportTitleDlg.cpp | 8 +-- 6 files changed, 79 insertions(+), 28 deletions(-) diff --git a/Code/Editor/Core/LevelEditorMenuHandler.cpp b/Code/Editor/Core/LevelEditorMenuHandler.cpp index 3c27c25c9a..70ff51c3d6 100644 --- a/Code/Editor/Core/LevelEditorMenuHandler.cpp +++ b/Code/Editor/Core/LevelEditorMenuHandler.cpp @@ -544,12 +544,12 @@ void LevelEditorMenuHandler::PopulateEditMenu(ActionManager::MenuWrapper& editMe auto snapMenu = modifyMenu.AddMenu(tr("Snap")); - snapMenu.AddAction(ID_SNAPANGLE); + snapMenu.AddAction(AzToolsFramework::SnapAngle); auto transformModeMenu = modifyMenu.AddMenu(tr("Transform Mode")); - transformModeMenu.AddAction(ID_EDITMODE_MOVE); - transformModeMenu.AddAction(ID_EDITMODE_ROTATE); - transformModeMenu.AddAction(ID_EDITMODE_SCALE); + transformModeMenu.AddAction(AzToolsFramework::EditModeMove); + transformModeMenu.AddAction(AzToolsFramework::EditModeRotate); + transformModeMenu.AddAction(AzToolsFramework::EditModeScale); editMenu.AddSeparator(); diff --git a/Code/Editor/CryEdit.cpp b/Code/Editor/CryEdit.cpp index 4bfc6a319d..407dfffd3e 100644 --- a/Code/Editor/CryEdit.cpp +++ b/Code/Editor/CryEdit.cpp @@ -375,9 +375,6 @@ void CCryEditApp::RegisterActionHandlers() }); ON_COMMAND(ID_MOVE_OBJECT, OnMoveObject) ON_COMMAND(ID_RENAME_OBJ, OnRenameObj) - ON_COMMAND(ID_EDITMODE_MOVE, OnEditmodeMove) - ON_COMMAND(ID_EDITMODE_ROTATE, OnEditmodeRotate) - ON_COMMAND(ID_EDITMODE_SCALE, OnEditmodeScale) ON_COMMAND(ID_UNDO, OnUndo) ON_COMMAND(ID_TOOLBAR_WIDGET_REDO, OnUndo) // Can't use the same ID, because for the menu we can't have a QWidgetAction, while for the toolbar we want one ON_COMMAND(ID_IMPORT_ASSET, OnOpenAssetImporter) diff --git a/Code/Editor/MainWindow.cpp b/Code/Editor/MainWindow.cpp index ff322b79ac..8309d99691 100644 --- a/Code/Editor/MainWindow.cpp +++ b/Code/Editor/MainWindow.cpp @@ -46,6 +46,7 @@ AZ_POP_DISABLE_WARNING #include #include #include +#include // AzQtComponents #include @@ -731,32 +732,84 @@ void MainWindow::InitActions() .SetStatusTip(tr("Restore saved state (Fetch)")); // Modify actions - am->AddAction(ID_EDITMODE_MOVE, tr("Move")) + am->AddAction(AzToolsFramework::EditModeMove, tr("Move")) .SetIcon(Style::icon("Move")) .SetApplyHoverEffect() .SetShortcut(tr("1")) .SetToolTip(tr("Move (1)")) .SetCheckable(true) .SetStatusTip(tr("Select and move selected object(s)")) - .RegisterUpdateCallback(cryEdit, &CCryEditApp::OnUpdateEditmodeMove); - am->AddAction(ID_EDITMODE_ROTATE, tr("Rotate")) + .RegisterUpdateCallback([](QAction* action) + { + Q_ASSERT(action->isCheckable()); + + AzToolsFramework::EditorTransformComponentSelectionRequests::Mode mode; + AzToolsFramework::EditorTransformComponentSelectionRequestBus::EventResult( + mode, AzToolsFramework::GetEntityContextId(), + &AzToolsFramework::EditorTransformComponentSelectionRequests::GetTransformMode); + + action->setChecked(mode == AzToolsFramework::EditorTransformComponentSelectionRequests::Mode::Translation); + }) + .Connect( + &QAction::triggered, + []() + { + EditorTransformComponentSelectionRequestBus::Event( + GetEntityContextId(), &EditorTransformComponentSelectionRequests::SetTransformMode, + EditorTransformComponentSelectionRequests::Mode::Translation); + }); + am->AddAction(AzToolsFramework::EditModeRotate, tr("Rotate")) .SetIcon(Style::icon("Translate")) .SetApplyHoverEffect() .SetShortcut(tr("2")) .SetToolTip(tr("Rotate (2)")) .SetCheckable(true) .SetStatusTip(tr("Select and rotate selected object(s)")) - .RegisterUpdateCallback(cryEdit, &CCryEditApp::OnUpdateEditmodeRotate); - am->AddAction(ID_EDITMODE_SCALE, tr("Scale")) + .RegisterUpdateCallback([](QAction* action) + { + Q_ASSERT(action->isCheckable()); + + AzToolsFramework::EditorTransformComponentSelectionRequests::Mode mode; + AzToolsFramework::EditorTransformComponentSelectionRequestBus::EventResult( + mode, AzToolsFramework::GetEntityContextId(), + &AzToolsFramework::EditorTransformComponentSelectionRequests::GetTransformMode); + + action->setChecked(mode == AzToolsFramework::EditorTransformComponentSelectionRequests::Mode::Rotation); + }) + .Connect( + &QAction::triggered, + []() + { + EditorTransformComponentSelectionRequestBus::Event( + GetEntityContextId(), &EditorTransformComponentSelectionRequests::SetTransformMode, + EditorTransformComponentSelectionRequests::Mode::Rotation); + }); + am->AddAction(AzToolsFramework::EditModeScale, tr("Scale")) .SetIcon(Style::icon("Scale")) .SetApplyHoverEffect() .SetShortcut(tr("3")) .SetToolTip(tr("Scale (3)")) .SetCheckable(true) .SetStatusTip(tr("Select and scale selected object(s)")) - .RegisterUpdateCallback(cryEdit, &CCryEditApp::OnUpdateEditmodeScale); + .RegisterUpdateCallback([](QAction* action) + { + Q_ASSERT(action->isCheckable()); - am->AddAction(ID_SNAP_TO_GRID, tr("Snap to grid")) + AzToolsFramework::EditorTransformComponentSelectionRequests::Mode mode; + AzToolsFramework::EditorTransformComponentSelectionRequestBus::EventResult( + mode, AzToolsFramework::GetEntityContextId(), + &AzToolsFramework::EditorTransformComponentSelectionRequests::GetTransformMode); + + action->setChecked(mode == AzToolsFramework::EditorTransformComponentSelectionRequests::Mode::Scale); + }) + .Connect( &QAction::triggered,[]() + { + EditorTransformComponentSelectionRequestBus::Event( + GetEntityContextId(), &EditorTransformComponentSelectionRequests::SetTransformMode, + EditorTransformComponentSelectionRequests::Mode::Scale); + }); + + am->AddAction(AzToolsFramework::SnapToGrid, tr("Snap to grid")) .SetIcon(Style::icon("Grid")) .SetApplyHoverEffect() .SetShortcut(tr("G")) @@ -769,7 +822,7 @@ void MainWindow::InitActions() }) .Connect(&QAction::triggered, []() { SandboxEditor::SetGridSnapping(!SandboxEditor::GridSnappingEnabled()); }); - am->AddAction(ID_SNAPANGLE, tr("Snap angle")) + am->AddAction(AzToolsFramework::SnapAngle, tr("Snap angle")) .SetIcon(Style::icon("Angle")) .SetApplyHoverEffect() .SetStatusTip(tr("Snap angle")) diff --git a/Code/Editor/MainWindow.h b/Code/Editor/MainWindow.h index e70355827c..1e375b08f2 100644 --- a/Code/Editor/MainWindow.h +++ b/Code/Editor/MainWindow.h @@ -59,11 +59,17 @@ namespace AzQtComponents namespace AzToolsFramework { class Ticker; -} - -namespace AzToolsFramework -{ class QtSourceControlNotificationHandler; + + //! @name Reverse URLs. + //! Used to identify common actions and override them when necessary. + //@{ + constexpr inline AZ::Crc32 EditModeMove = AZ_CRC_CE("com.o3de.action.editor.editmode.move"); + constexpr inline AZ::Crc32 EditModeRotate = AZ_CRC_CE("com.o3de.action.editor.editmode.rotate"); + constexpr inline AZ::Crc32 EditModeScale = AZ_CRC_CE("com.o3de.action.editor.editmode.scale"); + constexpr inline AZ::Crc32 SnapToGrid = AZ_CRC_CE("com.o3de.action.editor.snaptogrid"); + constexpr inline AZ::Crc32 SnapAngle = AZ_CRC_CE("com.o3de.action.editor.snapangle"); + //@} } #define MAINFRM_LAYOUT_NORMAL "NormalLayout" diff --git a/Code/Editor/Resource.h b/Code/Editor/Resource.h index a6f714afa4..b3640fac70 100644 --- a/Code/Editor/Resource.h +++ b/Code/Editor/Resource.h @@ -82,7 +82,6 @@ #define ID_TOOLS_CUSTOMIZEKEYBOARD 32914 #define ID_EXPORT_INDOORS 32915 #define ID_VIEW_CYCLE2DVIEWPORT 32916 -#define ID_SNAPANGLE 32917 #define ID_PHYSICS_GETPHYSICSSTATE 32937 #define ID_PHYSICS_RESETPHYSICSSTATE 32938 #define ID_GAME_SYNCPLAYER 32941 @@ -108,9 +107,6 @@ #define ID_MOVE_OBJECT 33481 #define ID_RENAME_OBJ 33483 #define ID_FETCH 33496 -#define ID_EDITMODE_ROTATE 33506 -#define ID_EDITMODE_SCALE 33507 -#define ID_EDITMODE_MOVE 33508 #define ID_SELECTION_DELETE 33512 #define ID_EDIT_ESCAPE 33513 #define ID_UNDO 33524 @@ -137,7 +133,6 @@ #define ID_ADDNODE 33570 #define ID_ADDSCENETRACK 33573 #define ID_FIND 33574 -#define ID_SNAP_TO_GRID 33575 #define ID_TAG_LOC1 33576 #define ID_TAG_LOC2 33577 #define ID_TAG_LOC3 33578 diff --git a/Code/Editor/ViewportTitleDlg.cpp b/Code/Editor/ViewportTitleDlg.cpp index 4d27506929..c1e1afb908 100644 --- a/Code/Editor/ViewportTitleDlg.cpp +++ b/Code/Editor/ViewportTitleDlg.cpp @@ -953,13 +953,13 @@ void CViewportTitleDlg::CheckForCameraSpeedUpdate() void CViewportTitleDlg::OnGridSnappingToggled() { m_gridSizeActionWidget->setEnabled(m_enableGridSnappingAction->isChecked()); - MainWindow::instance()->GetActionManager()->GetAction(ID_SNAP_TO_GRID)->trigger(); + MainWindow::instance()->GetActionManager()->GetAction(AzToolsFramework::SnapToGrid)->trigger(); } void CViewportTitleDlg::OnAngleSnappingToggled() { m_angleSizeActionWidget->setEnabled(m_enableAngleSnappingAction->isChecked()); - MainWindow::instance()->GetActionManager()->GetAction(ID_SNAPANGLE)->trigger(); + MainWindow::instance()->GetActionManager()->GetAction(AzToolsFramework::SnapAngle)->trigger(); } void CViewportTitleDlg::OnGridSpinBoxChanged(double value) @@ -974,14 +974,14 @@ void CViewportTitleDlg::OnAngleSpinBoxChanged(double value) void CViewportTitleDlg::UpdateOverFlowMenuState() { - bool gridSnappingActive = MainWindow::instance()->GetActionManager()->GetAction(ID_SNAP_TO_GRID)->isChecked(); + bool gridSnappingActive = MainWindow::instance()->GetActionManager()->GetAction(AzToolsFramework::SnapToGrid)->isChecked(); { QSignalBlocker signalBlocker(m_enableGridSnappingAction); m_enableGridSnappingAction->setChecked(gridSnappingActive); } m_gridSizeActionWidget->setEnabled(gridSnappingActive); - bool angleSnappingActive = MainWindow::instance()->GetActionManager()->GetAction(ID_SNAPANGLE)->isChecked(); + bool angleSnappingActive = MainWindow::instance()->GetActionManager()->GetAction(AzToolsFramework::SnapAngle)->isChecked(); { QSignalBlocker signalBlocker(m_enableAngleSnappingAction); m_enableAngleSnappingAction->setChecked(angleSnappingActive); From 6d2765ef4232aefcd7203919719bb375c2f41114 Mon Sep 17 00:00:00 2001 From: amzn-sean <75276488+amzn-sean@users.noreply.github.com> Date: Wed, 4 Aug 2021 13:10:17 +0100 Subject: [PATCH 30/51] moved default location of surfacetypemateriallibrary.physmaterial (#2786) from 'project root' to 'project root/Assets/Physics' The functionality of creating / using the default physmaterial file has only change in related to the file location, other functionality is unchanged. The following situations can occur: This will not affect have any project that uses a custom physmaterial file. This will not affect have any project that uses the default from the old location, as the configuration will still point there. New projects created will get the default physmaterial file at the new location. A Project that fails to load (or deletes) the selected physmaterial file, will get the default physmaterial file at the new location (this happens only on startup of the editor). Issue: #2765 Signed-off-by: amzn-sean 75276488+amzn-sean@users.noreply.github.com --- .../Physics/SurfaceTypeMaterialLibrary.physmaterial} | 0 .../physics/C15096740_Material_LibraryUpdatedCorrectly.py | 5 +++-- .../Gem/PythonTests/physics/Physmaterial_Editor.py | 2 +- .../C3510644_Collider_CollisionGroups.setreg_override | 5 +++-- .../Registry/C4976227_Collider_NewGroup.setreg_override | 5 +++-- ...4_Collider_SameGroupSameLayerCollision.setreg_override | 5 +++-- ...76245_PhysXCollider_CollisionLayerTest.setreg_override | 5 +++-- .../C4982593_PhysXCollider_CollisionLayer.setreg_override | 5 +++-- AutomatedTesting/Registry/physxsystemconfiguration.setreg | 5 +++-- .../Editor/Source/Components/EditorSystemComponent.cpp | 8 ++++---- 10 files changed, 26 insertions(+), 19 deletions(-) rename AutomatedTesting/{surfacetypemateriallibrary.physmaterial => Assets/Physics/SurfaceTypeMaterialLibrary.physmaterial} (100%) diff --git a/AutomatedTesting/surfacetypemateriallibrary.physmaterial b/AutomatedTesting/Assets/Physics/SurfaceTypeMaterialLibrary.physmaterial similarity index 100% rename from AutomatedTesting/surfacetypemateriallibrary.physmaterial rename to AutomatedTesting/Assets/Physics/SurfaceTypeMaterialLibrary.physmaterial diff --git a/AutomatedTesting/Gem/PythonTests/physics/C15096740_Material_LibraryUpdatedCorrectly.py b/AutomatedTesting/Gem/PythonTests/physics/C15096740_Material_LibraryUpdatedCorrectly.py index 060779082c..341597e36a 100755 --- a/AutomatedTesting/Gem/PythonTests/physics/C15096740_Material_LibraryUpdatedCorrectly.py +++ b/AutomatedTesting/Gem/PythonTests/physics/C15096740_Material_LibraryUpdatedCorrectly.py @@ -64,7 +64,8 @@ def C15096740_Material_LibraryUpdatedCorrectly(): # Constants library_property_path = "Configuration|Physics Material|Library" - default_material_path = "surfacetypemateriallibrary.physmaterial" + + default_material_path = os.path.join("assets", "physics", "surfacetypemateriallibrary.physmaterial") new_material_path = os.path.join("physicssurfaces", "default_phys_materials.physmaterial") helper.init_idle() @@ -82,7 +83,7 @@ def C15096740_Material_LibraryUpdatedCorrectly(): default_asset = Asset.find_asset_by_path(default_material_path) test_component.set_component_property_value(library_property_path, default_asset.id) default_asset.id = test_component.get_component_property_value(library_property_path) - Report.result(Tests.override_default_library, default_asset.get_path() == default_material_path) + Report.result(Tests.override_default_library, default_asset.get_path() == default_material_path.replace(os.sep, '/')) # 4) Switch it back again to the default material library. test_component.set_component_property_value(library_property_path, azasset.AssetId()) diff --git a/AutomatedTesting/Gem/PythonTests/physics/Physmaterial_Editor.py b/AutomatedTesting/Gem/PythonTests/physics/Physmaterial_Editor.py index 54ea3c7f15..cff8ae8377 100755 --- a/AutomatedTesting/Gem/PythonTests/physics/Physmaterial_Editor.py +++ b/AutomatedTesting/Gem/PythonTests/physics/Physmaterial_Editor.py @@ -133,7 +133,7 @@ class Physmaterial_Editor: def _set_path(self): # type: (str) -> str if self.document_filename == None: - self.document_filename = os.path.join(self.project_folder, "surfacetypemateriallibrary.physmaterial") + self.document_filename = os.path.join(self.project_folder, "assets", "physics", "surfacetypemateriallibrary.physmaterial") else: for (root, directories, root_files) in os.walk(self.project_folder): for root_file in root_files: diff --git a/AutomatedTesting/Registry/C3510644_Collider_CollisionGroups.setreg_override b/AutomatedTesting/Registry/C3510644_Collider_CollisionGroups.setreg_override index 9fa5e26768..e4ea71f652 100644 --- a/AutomatedTesting/Registry/C3510644_Collider_CollisionGroups.setreg_override +++ b/AutomatedTesting/Registry/C3510644_Collider_CollisionGroups.setreg_override @@ -121,9 +121,10 @@ }, "MaterialLibrary": { "assetId": { - "guid": "{3A055A3F-8CB7-5FEE-B437-EB365FACD0D4}" + "guid": "{62446378-67F8-5E49-AC31-761DD5942695}" }, - "assetHint": "surfacetypemateriallibrary.physmaterial" + "loadBehavior": "QueueLoad", + "assetHint": "assets/physics/surfacetypemateriallibrary.physmaterial" } } } diff --git a/AutomatedTesting/Registry/C4976227_Collider_NewGroup.setreg_override b/AutomatedTesting/Registry/C4976227_Collider_NewGroup.setreg_override index afbe6a9d38..e53d3893f8 100644 --- a/AutomatedTesting/Registry/C4976227_Collider_NewGroup.setreg_override +++ b/AutomatedTesting/Registry/C4976227_Collider_NewGroup.setreg_override @@ -109,9 +109,10 @@ }, "MaterialLibrary": { "assetId": { - "guid": "{3A055A3F-8CB7-5FEE-B437-EB365FACD0D4}" + "guid": "{62446378-67F8-5E49-AC31-761DD5942695}" }, - "assetHint": "surfacetypemateriallibrary.physmaterial" + "loadBehavior": "QueueLoad", + "assetHint": "assets/physics/surfacetypemateriallibrary.physmaterial" } } } diff --git a/AutomatedTesting/Registry/C4976244_Collider_SameGroupSameLayerCollision.setreg_override b/AutomatedTesting/Registry/C4976244_Collider_SameGroupSameLayerCollision.setreg_override index 9fa5e26768..e4ea71f652 100644 --- a/AutomatedTesting/Registry/C4976244_Collider_SameGroupSameLayerCollision.setreg_override +++ b/AutomatedTesting/Registry/C4976244_Collider_SameGroupSameLayerCollision.setreg_override @@ -121,9 +121,10 @@ }, "MaterialLibrary": { "assetId": { - "guid": "{3A055A3F-8CB7-5FEE-B437-EB365FACD0D4}" + "guid": "{62446378-67F8-5E49-AC31-761DD5942695}" }, - "assetHint": "surfacetypemateriallibrary.physmaterial" + "loadBehavior": "QueueLoad", + "assetHint": "assets/physics/surfacetypemateriallibrary.physmaterial" } } } diff --git a/AutomatedTesting/Registry/C4976245_PhysXCollider_CollisionLayerTest.setreg_override b/AutomatedTesting/Registry/C4976245_PhysXCollider_CollisionLayerTest.setreg_override index 9fa5e26768..e4ea71f652 100644 --- a/AutomatedTesting/Registry/C4976245_PhysXCollider_CollisionLayerTest.setreg_override +++ b/AutomatedTesting/Registry/C4976245_PhysXCollider_CollisionLayerTest.setreg_override @@ -121,9 +121,10 @@ }, "MaterialLibrary": { "assetId": { - "guid": "{3A055A3F-8CB7-5FEE-B437-EB365FACD0D4}" + "guid": "{62446378-67F8-5E49-AC31-761DD5942695}" }, - "assetHint": "surfacetypemateriallibrary.physmaterial" + "loadBehavior": "QueueLoad", + "assetHint": "assets/physics/surfacetypemateriallibrary.physmaterial" } } } diff --git a/AutomatedTesting/Registry/C4982593_PhysXCollider_CollisionLayer.setreg_override b/AutomatedTesting/Registry/C4982593_PhysXCollider_CollisionLayer.setreg_override index 9fa5e26768..e4ea71f652 100644 --- a/AutomatedTesting/Registry/C4982593_PhysXCollider_CollisionLayer.setreg_override +++ b/AutomatedTesting/Registry/C4982593_PhysXCollider_CollisionLayer.setreg_override @@ -121,9 +121,10 @@ }, "MaterialLibrary": { "assetId": { - "guid": "{3A055A3F-8CB7-5FEE-B437-EB365FACD0D4}" + "guid": "{62446378-67F8-5E49-AC31-761DD5942695}" }, - "assetHint": "surfacetypemateriallibrary.physmaterial" + "loadBehavior": "QueueLoad", + "assetHint": "assets/physics/surfacetypemateriallibrary.physmaterial" } } } diff --git a/AutomatedTesting/Registry/physxsystemconfiguration.setreg b/AutomatedTesting/Registry/physxsystemconfiguration.setreg index 02f65b685b..83aad307a6 100644 --- a/AutomatedTesting/Registry/physxsystemconfiguration.setreg +++ b/AutomatedTesting/Registry/physxsystemconfiguration.setreg @@ -103,9 +103,10 @@ }, "MaterialLibrary": { "assetId": { - "guid": "{3A055A3F-8CB7-5FEE-B437-EB365FACD0D4}" + "guid": "{62446378-67F8-5E49-AC31-761DD5942695}" }, - "assetHint": "surfacetypemateriallibrary.physmaterial" + "loadBehavior": "QueueLoad", + "assetHint": "assets/physics/surfacetypemateriallibrary.physmaterial" } } } diff --git a/Gems/PhysX/Code/Editor/Source/Components/EditorSystemComponent.cpp b/Gems/PhysX/Code/Editor/Source/Components/EditorSystemComponent.cpp index 6dc845199a..e4c65c8667 100644 --- a/Gems/PhysX/Code/Editor/Source/Components/EditorSystemComponent.cpp +++ b/Gems/PhysX/Code/Editor/Source/Components/EditorSystemComponent.cpp @@ -24,7 +24,7 @@ namespace PhysX { - constexpr const char* DefaultAssetFilename = "SurfaceTypeMaterialLibrary"; + constexpr const char* DefaultAssetFilePath = "Physics/SurfaceTypeMaterialLibrary"; constexpr const char* TemplateAssetFilename = "PhysX/TemplateMaterialLibrary"; static AZStd::optional> GetMaterialLibraryTemplate() @@ -227,7 +227,7 @@ namespace PhysX const AZStd::string& assetExtension = assetTypeExtensions[0]; // Use the path relative to the asset root to avoid hardcoding full path in the configuration - AZStd::string relativePath = DefaultAssetFilename; + AZStd::string relativePath = DefaultAssetFilePath; AzFramework::StringFunc::Path::ReplaceExtension(relativePath, assetExtension.c_str()); // Try to find an already existing material library @@ -237,9 +237,9 @@ namespace PhysX if (!resultAssetId.IsValid()) { // No file for the default material library, create it - const char* assetRoot = AZ::IO::FileIOBase::GetInstance()->GetAlias("@devassets@"); + const char* assetRoot = AZ::IO::FileIOBase::GetInstance()->GetAlias("@projectsourceassets@"); AZStd::string fullPath; - AzFramework::StringFunc::Path::ConstructFull(assetRoot, DefaultAssetFilename, assetExtension.c_str(), fullPath); + AzFramework::StringFunc::Path::ConstructFull(assetRoot, DefaultAssetFilePath, assetExtension.c_str(), fullPath); if (auto materialLibraryOpt = CreateMaterialLibrary(fullPath, relativePath)) { From c9e16c1c42e4fbad0bb949beaabb06850f3c2e67 Mon Sep 17 00:00:00 2001 From: aaguilea Date: Wed, 4 Aug 2021 14:44:50 +0100 Subject: [PATCH 31/51] Erased some legacy function that are no longer necessary Signed-off-by: aaguilea --- Code/Editor/CryEdit.cpp | 69 ----------------------------------------- Code/Editor/CryEdit.h | 6 ---- 2 files changed, 75 deletions(-) diff --git a/Code/Editor/CryEdit.cpp b/Code/Editor/CryEdit.cpp index 407dfffd3e..3b68758d17 100644 --- a/Code/Editor/CryEdit.cpp +++ b/Code/Editor/CryEdit.cpp @@ -2576,75 +2576,6 @@ void CCryEditApp::OnRenameObj() { } -////////////////////////////////////////////////////////////////////////// -void CCryEditApp::OnEditmodeMove() -{ - using namespace AzToolsFramework; - EditorTransformComponentSelectionRequestBus::Event( - GetEntityContextId(), - &EditorTransformComponentSelectionRequests::SetTransformMode, - EditorTransformComponentSelectionRequests::Mode::Translation); -} - -////////////////////////////////////////////////////////////////////////// -void CCryEditApp::OnEditmodeRotate() -{ - using namespace AzToolsFramework; - EditorTransformComponentSelectionRequestBus::Event( - GetEntityContextId(), - &EditorTransformComponentSelectionRequests::SetTransformMode, - EditorTransformComponentSelectionRequests::Mode::Rotation); -} - -////////////////////////////////////////////////////////////////////////// -void CCryEditApp::OnEditmodeScale() -{ - using namespace AzToolsFramework; - EditorTransformComponentSelectionRequestBus::Event( - GetEntityContextId(), - &EditorTransformComponentSelectionRequests::SetTransformMode, - EditorTransformComponentSelectionRequests::Mode::Scale); -} - -////////////////////////////////////////////////////////////////////////// -void CCryEditApp::OnUpdateEditmodeMove(QAction* action) -{ - Q_ASSERT(action->isCheckable()); - - AzToolsFramework::EditorTransformComponentSelectionRequests::Mode mode; - AzToolsFramework::EditorTransformComponentSelectionRequestBus::EventResult( - mode, AzToolsFramework::GetEntityContextId(), - &AzToolsFramework::EditorTransformComponentSelectionRequests::GetTransformMode); - - action->setChecked(mode == AzToolsFramework::EditorTransformComponentSelectionRequests::Mode::Translation); -} - -////////////////////////////////////////////////////////////////////////// -void CCryEditApp::OnUpdateEditmodeRotate(QAction* action) -{ - Q_ASSERT(action->isCheckable()); - - AzToolsFramework::EditorTransformComponentSelectionRequests::Mode mode; - AzToolsFramework::EditorTransformComponentSelectionRequestBus::EventResult( - mode, AzToolsFramework::GetEntityContextId(), - &AzToolsFramework::EditorTransformComponentSelectionRequests::GetTransformMode); - - action->setChecked(mode == AzToolsFramework::EditorTransformComponentSelectionRequests::Mode::Rotation); -} - -////////////////////////////////////////////////////////////////////////// -void CCryEditApp::OnUpdateEditmodeScale(QAction* action) -{ - Q_ASSERT(action->isCheckable()); - - AzToolsFramework::EditorTransformComponentSelectionRequests::Mode mode; - AzToolsFramework::EditorTransformComponentSelectionRequestBus::EventResult( - mode, AzToolsFramework::GetEntityContextId(), - &AzToolsFramework::EditorTransformComponentSelectionRequests::GetTransformMode); - - action->setChecked(mode == AzToolsFramework::EditorTransformComponentSelectionRequests::Mode::Scale); -} - void CCryEditApp::OnViewSwitchToGame() { if (IsInPreviewMode()) diff --git a/Code/Editor/CryEdit.h b/Code/Editor/CryEdit.h index af0fbb0971..9406b37ea2 100644 --- a/Code/Editor/CryEdit.h +++ b/Code/Editor/CryEdit.h @@ -208,12 +208,6 @@ public: void DeleteSelectedEntities(bool includeDescendants); void OnMoveObject(); void OnRenameObj(); - void OnEditmodeMove(); - void OnEditmodeRotate(); - void OnEditmodeScale(); - void OnUpdateEditmodeMove(QAction* action); - void OnUpdateEditmodeRotate(QAction* action); - void OnUpdateEditmodeScale(QAction* action); void OnUndo(); void OnOpenAssetImporter(); void OnUpdateSelected(QAction* action); From 20515c46beb88c8ffe9b2fd0c09f4ac64ebea7c1 Mon Sep 17 00:00:00 2001 From: lsemp3d <58790905+lsemp3d@users.noreply.github.com> Date: Wed, 4 Aug 2021 08:20:41 -0700 Subject: [PATCH 32/51] Updated DirectionTo node's tooltips to match the translation file Signed-off-by: lsemp3d <58790905+lsemp3d@users.noreply.github.com> --- .../Code/Include/ScriptCanvas/Libraries/Math/Vector2Nodes.h | 2 +- .../Code/Include/ScriptCanvas/Libraries/Math/Vector3Nodes.h | 2 +- .../Code/Include/ScriptCanvas/Libraries/Math/Vector4Nodes.h | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Math/Vector2Nodes.h b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Math/Vector2Nodes.h index 4ac14a7db6..5caff9ff25 100644 --- a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Math/Vector2Nodes.h +++ b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Math/Vector2Nodes.h @@ -257,7 +257,7 @@ namespace ScriptCanvas r.SetLength(optionalScale); return r; } - SCRIPT_CANVAS_GENERIC_FUNCTION_NODE_WITH_DEFAULTS(DirectionTo, DirectionToDefaults, k_categoryName, "{49A2D7F6-6CD3-420E-8A79-D46B00DB6CED}", "Given two points in space, return a direction vector", "From", "To", "Scale"); + SCRIPT_CANVAS_GENERIC_FUNCTION_NODE_WITH_DEFAULTS(DirectionTo, DirectionToDefaults, k_categoryName, "{49A2D7F6-6CD3-420E-8A79-D46B00DB6CED}", "Returns a direction vector between two points, by default the direction will be normalized, but it may be optionally scaled using the Scale parameter if different from 1.0", "From", "To", "Scale"); using Registrar = RegistrarGeneric < AbsoluteNode diff --git a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Math/Vector3Nodes.h b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Math/Vector3Nodes.h index eb304c8362..24a1710655 100644 --- a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Math/Vector3Nodes.h +++ b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Math/Vector3Nodes.h @@ -343,7 +343,7 @@ namespace ScriptCanvas r.SetLength(optionalScale); return r; } - SCRIPT_CANVAS_GENERIC_FUNCTION_NODE_WITH_DEFAULTS(DirectionTo, DirectionToDefaults, k_categoryName, "{28FBD529-4C9A-4E34-B8A0-A13B5DB3C331}", "Given two points in space, return a direction vector", "From", "To", "Scale"); + SCRIPT_CANVAS_GENERIC_FUNCTION_NODE_WITH_DEFAULTS(DirectionTo, DirectionToDefaults, k_categoryName, "{28FBD529-4C9A-4E34-B8A0-A13B5DB3C331}", "Returns a direction vector between two points, by default the direction will be normalized, but it may be optionally scaled using the Scale parameter if different from 1.0", "From", "To", "Scale"); using Registrar = RegistrarGeneric < diff --git a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Math/Vector4Nodes.h b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Math/Vector4Nodes.h index ac9be8f135..d420affd9a 100644 --- a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Math/Vector4Nodes.h +++ b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Math/Vector4Nodes.h @@ -228,7 +228,7 @@ namespace ScriptCanvas r.SetLength(optionalScale); return r; } - SCRIPT_CANVAS_GENERIC_FUNCTION_NODE_WITH_DEFAULTS(DirectionTo, DirectionToDefaults, k_categoryName, "{463762DE-E541-4AFE-80C2-FED1C5273319}", "Given two points in space, return a direction vector", "From", "To", "Scale"); + SCRIPT_CANVAS_GENERIC_FUNCTION_NODE_WITH_DEFAULTS(DirectionTo, DirectionToDefaults, k_categoryName, "{463762DE-E541-4AFE-80C2-FED1C5273319}", "Returns a direction vector between two points, by default the direction will be normalized, but it may be optionally scaled using the Scale parameter if different from 1.0", "From", "To", "Scale"); using Registrar = RegistrarGeneric < AbsoluteNode, From 760acdcdcc14fde196ced69002cf3251632fc812 Mon Sep 17 00:00:00 2001 From: lsemp3d <58790905+lsemp3d@users.noreply.github.com> Date: Wed, 4 Aug 2021 08:21:41 -0700 Subject: [PATCH 33/51] Updated DirectionTo tooltips in the translation file Signed-off-by: lsemp3d <58790905+lsemp3d@users.noreply.github.com> --- Assets/Editor/Translation/scriptcanvas_en_us.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Assets/Editor/Translation/scriptcanvas_en_us.ts b/Assets/Editor/Translation/scriptcanvas_en_us.ts index 5604cfb631..22cd00aaea 100644 --- a/Assets/Editor/Translation/scriptcanvas_en_us.ts +++ b/Assets/Editor/Translation/scriptcanvas_en_us.ts @@ -2778,7 +2778,7 @@ VECTOR2_DIRECTIONTO_TOOLTIP - + Returns a direction vector between two points, by default the direction will be normalized, but it may be optionally scaled using the Scale parameter if different from 1.0 VECTOR2_DIRECTIONTO_CATEGORY @@ -32334,7 +32334,7 @@ An Entity can be selected by using the pick button, or by dragging an Entity fro VECTOR4_DIRECTIONTO_TOOLTIP - + Returns a direction vector between two points, by default the direction will be normalized, but it may be optionally scaled using the Scale parameter if different from 1.0 VECTOR4_DIRECTIONTO_CATEGORY @@ -37606,7 +37606,7 @@ An Entity can be selected by using the pick button, or by dragging an Entity fro VECTOR3_DIRECTIONTO_TOOLTIP - + Returns a direction vector between two points, by default the direction will be normalized, but it may be optionally scaled using the Scale parameter if different from 1.0 VECTOR3_DIRECTIONTO_CATEGORY From 1f0fcf2aa27ed3bd3a353ce40ba583afd7ef5887 Mon Sep 17 00:00:00 2001 From: lsemp3d <58790905+lsemp3d@users.noreply.github.com> Date: Wed, 4 Aug 2021 08:42:26 -0700 Subject: [PATCH 34/51] Updates GetDirectionVector nodes to also return the distance between the points Signed-off-by: lsemp3d <58790905+lsemp3d@users.noreply.github.com> --- .../Editor/Translation/scriptcanvas_en_us.ts | 43 +++++++++++++++---- .../Libraries/Math/Vector2Nodes.h | 8 ++-- .../Libraries/Math/Vector3Nodes.h | 8 ++-- .../Libraries/Math/Vector4Nodes.h | 8 ++-- 4 files changed, 47 insertions(+), 20 deletions(-) diff --git a/Assets/Editor/Translation/scriptcanvas_en_us.ts b/Assets/Editor/Translation/scriptcanvas_en_us.ts index 22cd00aaea..937f6a4d96 100644 --- a/Assets/Editor/Translation/scriptcanvas_en_us.ts +++ b/Assets/Editor/Translation/scriptcanvas_en_us.ts @@ -2778,7 +2778,7 @@ VECTOR2_DIRECTIONTO_TOOLTIP - Returns a direction vector between two points, by default the direction will be normalized, but it may be optionally scaled using the Scale parameter if different from 1.0 + Returns a direction vector between two points and the distance between them, by default the direction will be normalized, it may be optionally scaled using the Scale parameter if different from 1.0 VECTOR2_DIRECTIONTO_CATEGORY @@ -2800,14 +2800,23 @@ VECTOR2_DIRECTIONTO_IN_TOOLTIP - + VECTOR2_DIRECTIONTO_OUTPUT0_NAME C++ Type: const Vector2 Direction VECTOR2_DIRECTIONTO_OUTPUT0_TOOLTIP - + The direction between To and From normalized and optionally scaled + + + VECTOR2_DIRECTIONTO_OUTPUT1_NAME + C++ Type: float + Distance + + + VECTOR2_DIRECTIONTO_OUTPUT1_TOOLTIP + The distance between To and From VECTOR2_DIRECTIONTO_PARAM0_NAME @@ -32334,7 +32343,7 @@ An Entity can be selected by using the pick button, or by dragging an Entity fro VECTOR4_DIRECTIONTO_TOOLTIP - Returns a direction vector between two points, by default the direction will be normalized, but it may be optionally scaled using the Scale parameter if different from 1.0 + Returns a direction vector between two points and the distance between them, by default the direction will be normalized, it may be optionally scaled using the Scale parameter if different from 1.0 VECTOR4_DIRECTIONTO_CATEGORY @@ -32363,7 +32372,16 @@ An Entity can be selected by using the pick button, or by dragging an Entity fro VECTOR4_DIRECTIONTO_OUTPUT0_TOOLTIP - + The direction between To and From normalized and optionally scaled + + + VECTOR4_DIRECTIONTO_OUTPUT1_NAME + C++ Type: float + Distance + + + VECTOR4_DIRECTIONTO_OUTPUT1_TOOLTIP + The distance between To and From VECTOR4_DIRECTIONTO_PARAM0_NAME @@ -37606,7 +37624,7 @@ An Entity can be selected by using the pick button, or by dragging an Entity fro VECTOR3_DIRECTIONTO_TOOLTIP - Returns a direction vector between two points, by default the direction will be normalized, but it may be optionally scaled using the Scale parameter if different from 1.0 + Returns a direction vector between two points and the distance between them, by default the direction will be normalized, it may be optionally scaled using the Scale parameter if different from 1.0 VECTOR3_DIRECTIONTO_CATEGORY @@ -37628,14 +37646,23 @@ An Entity can be selected by using the pick button, or by dragging an Entity fro VECTOR3_DIRECTIONTO_IN_TOOLTIP - + VECTOR3_DIRECTIONTO_OUTPUT0_NAME C++ Type: const Vector3 Direction VECTOR3_DIRECTIONTO_OUTPUT0_TOOLTIP - + The direction between To and From normalized and optionally scaled + + + VECTOR3_DIRECTIONTO_OUTPUT1_NAME + C++ Type: float + Distance + + + VECTOR3_DIRECTIONTO_OUTPUT1_TOOLTIP + The distance between To and From VECTOR3_DIRECTIONTO_PARAM0_NAME diff --git a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Math/Vector2Nodes.h b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Math/Vector2Nodes.h index 5caff9ff25..670c9f31a1 100644 --- a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Math/Vector2Nodes.h +++ b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Math/Vector2Nodes.h @@ -250,14 +250,14 @@ namespace ScriptCanvas SetDefaultValuesByIndex<2>::_(node, Data::NumberType(1.)); } - AZ_INLINE Vector2Type DirectionTo(const Vector2Type from, const Vector2Type to, NumberType optionalScale = 1.f) + AZ_INLINE std::tuple DirectionTo(const Vector2Type from, const Vector2Type to, NumberType optionalScale = 1.f) { Vector2Type r = to - from; - r.Normalize(); + float length = r.NormalizeWithLength(); r.SetLength(optionalScale); - return r; + return std::make_tuple(r, length); } - SCRIPT_CANVAS_GENERIC_FUNCTION_NODE_WITH_DEFAULTS(DirectionTo, DirectionToDefaults, k_categoryName, "{49A2D7F6-6CD3-420E-8A79-D46B00DB6CED}", "Returns a direction vector between two points, by default the direction will be normalized, but it may be optionally scaled using the Scale parameter if different from 1.0", "From", "To", "Scale"); + SCRIPT_CANVAS_GENERIC_FUNCTION_NODE_WITH_DEFAULTS(DirectionTo, DirectionToDefaults, k_categoryName, "{49A2D7F6-6CD3-420E-8A79-D46B00DB6CED}", "Returns a direction vector between two points and the distance between them, by default the direction will be normalized, but it may be optionally scaled using the Scale parameter if different from 1.0", "From", "To", "Scale"); using Registrar = RegistrarGeneric < AbsoluteNode diff --git a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Math/Vector3Nodes.h b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Math/Vector3Nodes.h index 24a1710655..492bc83e33 100644 --- a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Math/Vector3Nodes.h +++ b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Math/Vector3Nodes.h @@ -336,14 +336,14 @@ namespace ScriptCanvas SetDefaultValuesByIndex<2>::_(node, Data::NumberType(1.)); } - AZ_INLINE Vector3Type DirectionTo(const Vector3Type from, const Vector3Type to, NumberType optionalScale = 1.f) + AZ_INLINE std::tuple DirectionTo(const Vector3Type from, const Vector3Type to, NumberType optionalScale = 1.f) { Vector3Type r = to - from; - r.Normalize(); + float length = r.NormalizeWithLength(); r.SetLength(optionalScale); - return r; + return std::make_tuple(r, length); } - SCRIPT_CANVAS_GENERIC_FUNCTION_NODE_WITH_DEFAULTS(DirectionTo, DirectionToDefaults, k_categoryName, "{28FBD529-4C9A-4E34-B8A0-A13B5DB3C331}", "Returns a direction vector between two points, by default the direction will be normalized, but it may be optionally scaled using the Scale parameter if different from 1.0", "From", "To", "Scale"); + SCRIPT_CANVAS_GENERIC_FUNCTION_NODE_WITH_DEFAULTS(DirectionTo, DirectionToDefaults, k_categoryName, "{28FBD529-4C9A-4E34-B8A0-A13B5DB3C331}", "Returns a direction vector between two points and the distance between them, by default the direction will be normalized, but it may be optionally scaled using the Scale parameter if different from 1.0", "From", "To", "Scale"); using Registrar = RegistrarGeneric < diff --git a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Math/Vector4Nodes.h b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Math/Vector4Nodes.h index d420affd9a..26099c59c6 100644 --- a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Math/Vector4Nodes.h +++ b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Math/Vector4Nodes.h @@ -221,14 +221,14 @@ namespace ScriptCanvas SetDefaultValuesByIndex<2>::_(node, Data::NumberType(1.)); } - AZ_INLINE Vector4Type DirectionTo(const Vector4Type from, const Vector4Type to, NumberType optionalScale = 1.f) + AZ_INLINE std::tuple DirectionTo(const Vector4Type from, const Vector4Type to, NumberType optionalScale = 1.f) { Vector4Type r = to - from; - r.Normalize(); + float length = r.NormalizeWithLength(); r.SetLength(optionalScale); - return r; + return std::make_tuple(r, length); } - SCRIPT_CANVAS_GENERIC_FUNCTION_NODE_WITH_DEFAULTS(DirectionTo, DirectionToDefaults, k_categoryName, "{463762DE-E541-4AFE-80C2-FED1C5273319}", "Returns a direction vector between two points, by default the direction will be normalized, but it may be optionally scaled using the Scale parameter if different from 1.0", "From", "To", "Scale"); + SCRIPT_CANVAS_GENERIC_FUNCTION_NODE_WITH_DEFAULTS(DirectionTo, DirectionToDefaults, k_categoryName, "{463762DE-E541-4AFE-80C2-FED1C5273319}", false, "Returns a direction vector between two points and the distance between them, by default the direction will be normalized, but it may be optionally scaled using the Scale parameter if different from 1.0", "From", "To", "Scale"); using Registrar = RegistrarGeneric < AbsoluteNode, From 2c9655657695b9dc21c13b07d340bb9f0e18790e Mon Sep 17 00:00:00 2001 From: lsemp3d <58790905+lsemp3d@users.noreply.github.com> Date: Wed, 4 Aug 2021 08:47:37 -0700 Subject: [PATCH 35/51] Removed unnecessary argument in node generic macro Signed-off-by: lsemp3d <58790905+lsemp3d@users.noreply.github.com> --- .../Code/Include/ScriptCanvas/Libraries/Math/Vector4Nodes.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Math/Vector4Nodes.h b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Math/Vector4Nodes.h index 26099c59c6..14256fc969 100644 --- a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Math/Vector4Nodes.h +++ b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Math/Vector4Nodes.h @@ -228,7 +228,7 @@ namespace ScriptCanvas r.SetLength(optionalScale); return std::make_tuple(r, length); } - SCRIPT_CANVAS_GENERIC_FUNCTION_NODE_WITH_DEFAULTS(DirectionTo, DirectionToDefaults, k_categoryName, "{463762DE-E541-4AFE-80C2-FED1C5273319}", false, "Returns a direction vector between two points and the distance between them, by default the direction will be normalized, but it may be optionally scaled using the Scale parameter if different from 1.0", "From", "To", "Scale"); + SCRIPT_CANVAS_GENERIC_FUNCTION_NODE_WITH_DEFAULTS(DirectionTo, DirectionToDefaults, k_categoryName, "{463762DE-E541-4AFE-80C2-FED1C5273319}", "Returns a direction vector between two points and the distance between them, by default the direction will be normalized, but it may be optionally scaled using the Scale parameter if different from 1.0", "From", "To", "Scale"); using Registrar = RegistrarGeneric < AbsoluteNode, From 26c6d41e6358737f51c686b58fb76f9e76f374f6 Mon Sep 17 00:00:00 2001 From: Chris Aniszczyk Date: Wed, 4 Aug 2021 11:14:25 -0500 Subject: [PATCH 36/51] Update language Signed-off-by: Chris Aniszczyk --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c129fffb6c..e3eb6aa588 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# Open 3D Engine +# O3DE (Open 3D Engine) -Open 3D Engine (O3DE) is an open-source, real-time, multi-platform 3D engine that enables developers and content creators to build AAA games, cinema-quality 3D worlds, and high-fidelity simulations without any fees or commercial obligations. +O3DE (Open 3D Engine)is an open-source, real-time, multi-platform 3D engine that enables developers and content creators to build AAA games, cinema-quality 3D worlds, and high-fidelity simulations without any fees or commercial obligations. ## Contribute For information about contributing to Open 3D Engine, visit https://o3de.org/docs/contributing/ From dbb6c1ae469eea0b9f7eebd2460f1d619bb9b23d Mon Sep 17 00:00:00 2001 From: moraaar Date: Wed, 4 Aug 2021 17:57:48 +0100 Subject: [PATCH 37/51] Fixed EntitySpawnTicket move constructor (#2832) Signed-off-by: moraaar --- .../Spawnable/SpawnableEntitiesInterface.cpp | 2 ++ .../SpawnableEntitiesManagerTests.cpp | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.cpp b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.cpp index 87353c5807..617eb3a0b9 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.cpp +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.cpp @@ -227,8 +227,10 @@ namespace AzFramework EntitySpawnTicket::EntitySpawnTicket(EntitySpawnTicket&& rhs) : m_payload(rhs.m_payload) + , m_id(rhs.m_id) { rhs.m_payload = nullptr; + rhs.m_id = 0; } EntitySpawnTicket::EntitySpawnTicket(AZ::Data::Asset spawnable) diff --git a/Code/Framework/AzFramework/Tests/Spawnable/SpawnableEntitiesManagerTests.cpp b/Code/Framework/AzFramework/Tests/Spawnable/SpawnableEntitiesManagerTests.cpp index 7eff4b5eeb..f68af08f4a 100644 --- a/Code/Framework/AzFramework/Tests/Spawnable/SpawnableEntitiesManagerTests.cpp +++ b/Code/Framework/AzFramework/Tests/Spawnable/SpawnableEntitiesManagerTests.cpp @@ -366,6 +366,24 @@ namespace UnitTest } } + TEST_F(SpawnableEntitiesManagerTest, EntitySpawnTicket_Move_Works) + { + AzFramework::EntitySpawnTicket ticket1(*m_spawnableAsset); + AzFramework::EntitySpawnTicket ticket2(*m_spawnableAsset); + + const AzFramework::EntitySpawnTicket::Id ticket1Id = ticket1.GetId(); + const AzFramework::EntitySpawnTicket::Id ticket2Id = ticket2.GetId(); + + AzFramework::EntitySpawnTicket ticketMoveConstructor(AZStd::move(ticket1)); + EXPECT_TRUE(ticketMoveConstructor.IsValid()); + EXPECT_EQ(ticketMoveConstructor.GetId(), ticket1Id); + + AzFramework::EntitySpawnTicket ticketMoveOperator; + ticketMoveOperator = AZStd::move(ticket2); + EXPECT_TRUE(ticketMoveOperator.IsValid()); + EXPECT_EQ(ticketMoveOperator.GetId(), ticket2Id); + } + TEST_F(SpawnableEntitiesManagerTest, SpawnAllEntities_DeleteTicketBeforeCall_NoCrash) { { From 6d345512c136b65e471335616130d41cb683a0d7 Mon Sep 17 00:00:00 2001 From: Jacob Hilliard <64656371+jcbhl@users.noreply.github.com> Date: Wed, 4 Aug 2021 10:26:06 -0700 Subject: [PATCH 38/51] Visualizer: switch to erase_if to improve performance (#2779) Signed-off-by: Jacob Hilliard --- .../Code/Include/Atom/Utils/ImGuiCpuProfiler.inl | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiCpuProfiler.inl b/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiCpuProfiler.inl index ffd7af2f20..ae707a10a7 100644 --- a/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiCpuProfiler.inl +++ b/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiCpuProfiler.inl @@ -520,13 +520,14 @@ namespace AZ { AZStd::size_t sizeBeforeRemove = savedRegions.size(); - auto firstRegionToKeep = AZStd::lower_bound( - savedRegions.begin(), savedRegions.end(), deleteBeforeTick, - [](const TimeRegion& region, AZStd::sys_time_t target) + // Use erase_if over plain upper_bound + erase to avoid repeated shifts. erase requires a shift of all elements to the right + // for each element that is erased, while erase_if squashes all removes into a single shift which significantly improves perf. + AZStd::erase_if( + savedRegions, + [deleteBeforeTick](const TimeRegion& region) { - return region.m_startTick < target; + return region.m_startTick < deleteBeforeTick; }); - savedRegions.erase(savedRegions.begin(), firstRegionToKeep); m_savedRegionCount -= sizeBeforeRemove - savedRegions.size(); } From b3901b32513d2c1b0b089325f8757f92cb1f2436 Mon Sep 17 00:00:00 2001 From: Chris Aniszczyk Date: Wed, 4 Aug 2021 12:27:19 -0500 Subject: [PATCH 39/51] fix space Signed-off-by: Chris Aniszczyk --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e3eb6aa588..a783139eef 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # O3DE (Open 3D Engine) -O3DE (Open 3D Engine)is an open-source, real-time, multi-platform 3D engine that enables developers and content creators to build AAA games, cinema-quality 3D worlds, and high-fidelity simulations without any fees or commercial obligations. +O3DE (Open 3D Engine) is an open-source, real-time, multi-platform 3D engine that enables developers and content creators to build AAA games, cinema-quality 3D worlds, and high-fidelity simulations without any fees or commercial obligations. ## Contribute For information about contributing to Open 3D Engine, visit https://o3de.org/docs/contributing/ From 21ca3a4aea3b79b530e9abc1f0e2618aae88f05d Mon Sep 17 00:00:00 2001 From: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com> Date: Wed, 4 Aug 2021 13:13:28 -0500 Subject: [PATCH 40/51] The enable-gem command registers gem with project if not registered (#2817) * The enable_gems command now registers the gem with the project if only registered with o3de_manifest.json Updated the `enable_gems` command to register the gem with the project if the gem is not registered with either the project or the engine being used. This allows the gem to be added to the build system if it wasn't registered before. Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com> * Adding quoting around the invocation of the OpenProjectManager command The --project-path parameter now is able to pass in a path with spaces to the invocation of the Project Manager. Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com> --- Code/Editor/CryEdit.cpp | 9 +- scripts/o3de/o3de/enable_gem.py | 22 ++- scripts/o3de/o3de/register.py | 6 +- scripts/o3de/tests/CMakeLists.txt | 7 + scripts/o3de/tests/unit_test_enable_gem.py | 126 ++++++++++++++++++ .../tests/unit_test_project_properties.py | 36 +++-- 6 files changed, 183 insertions(+), 23 deletions(-) create mode 100644 scripts/o3de/tests/unit_test_enable_gem.py diff --git a/Code/Editor/CryEdit.cpp b/Code/Editor/CryEdit.cpp index 4bfc6a319d..61d4fd5aea 100644 --- a/Code/Editor/CryEdit.cpp +++ b/Code/Editor/CryEdit.cpp @@ -2901,7 +2901,14 @@ void CCryEditApp::OpenProjectManager(const AZStd::string& screen) { // provide the current project path for in case we want to update the project AZ::IO::FixedMaxPathString projectPath = AZ::Utils::GetProjectPath(); - const AZStd::string commandLineOptions = AZStd::string::format(" --screen %s --project-path %s", screen.c_str(), projectPath.c_str()); +#if !AZ_TRAIT_OS_PLATFORM_APPLE && !AZ_TRAIT_OS_USE_WINDOWS_FILE_PATHS + const char* argumentQuoteString = R"(")"; +#else + const char* argumentQuoteString = R"(\")"; +#endif + const AZStd::string commandLineOptions = AZStd::string::format(R"( --screen %s --project-path %s%s%s)", + screen.c_str(), + argumentQuoteString, projectPath.c_str(), argumentQuoteString); bool launchSuccess = AzFramework::ProjectManager::LaunchProjectManager(commandLineOptions); if (!launchSuccess) { diff --git a/scripts/o3de/o3de/enable_gem.py b/scripts/o3de/o3de/enable_gem.py index 67e1624086..1614dc4eda 100644 --- a/scripts/o3de/o3de/enable_gem.py +++ b/scripts/o3de/o3de/enable_gem.py @@ -16,7 +16,7 @@ import os import pathlib import sys -from o3de import cmake, manifest, validation +from o3de import cmake, manifest, register, validation logger = logging.getLogger() logging.basicConfig() @@ -87,8 +87,7 @@ def enable_gem_in_project(gem_name: str = None, if not enabled_gem_file.is_file(): logger.error(f'Enabled gem file {enabled_gem_file} is not present.') return 1 - # add the gem - ret_val = cmake.add_gem_dependency(enabled_gem_file, gem_json_data['gem_name']) + project_enabled_gem_file = enabled_gem_file else: # Find the path to enabled gem file. @@ -96,8 +95,21 @@ def enable_gem_in_project(gem_name: str = None, project_enabled_gem_file = cmake.get_enabled_gem_cmake_file(project_path=project_path) if not project_enabled_gem_file.is_file(): project_enabled_gem_file.touch() - # add the gem - ret_val = cmake.add_gem_dependency(project_enabled_gem_file, gem_json_data['gem_name']) + + # Before adding the gem_dependency check if the project is registered in either the project or engine + # manifest + buildable_gems = manifest.get_engine_gems() + buildable_gems.extend(manifest.get_project_gems(project_path)) + # Convert each path to pathlib.Path object and filter out duplictes using dict.fromkeys + buildable_gems = list(dict.fromkeys(map(lambda gem_path_string: pathlib.Path(gem_path_string), buildable_gems))) + + ret_val = 0 + # If the gem is not part of buildable set, it needs to be registered + if not gem_path in buildable_gems: + ret_val = register.register(gem_path=gem_path, external_subdir_project_path=project_path) + + # add the gem if it is registered in either the project.json or engine.json + ret_val = ret_val or cmake.add_gem_dependency(project_enabled_gem_file, gem_json_data['gem_name']) return ret_val diff --git a/scripts/o3de/o3de/register.py b/scripts/o3de/o3de/register.py index 8481c5fae0..7e182b5d2d 100644 --- a/scripts/o3de/o3de/register.py +++ b/scripts/o3de/o3de/register.py @@ -285,14 +285,14 @@ def register_o3de_object_path(json_data: dict, manifest_data = None if engine_path: - manifest_data = manifest.get_engine_json_data(json_data, engine_path) + manifest_data = manifest.get_engine_json_data(None, engine_path) if not manifest_data: logger.error(f'Cannot load engine.json data at path {engine_path}') return 1 save_path = engine_path / 'engine.json' elif project_path: - manifest_data = manifest.get_project_json_data(json_data, project_path) + manifest_data = manifest.get_project_json_data(None, project_path) if not manifest_data: logger.error(f'Cannot load project.json data at path {project_path}') return 1 @@ -329,7 +329,7 @@ def register_o3de_object_path(json_data: dict, try: o3de_object_path = o3de_object_path.relative_to(save_path.parent) except ValueError: - pass # It is OK relative path cannot be formed + pass # It is OK relative path cannot be formed manifest_data[o3de_object_key].insert(0, o3de_object_path.as_posix()) if save_path: manifest.save_o3de_manifest(manifest_data, save_path) diff --git a/scripts/o3de/tests/CMakeLists.txt b/scripts/o3de/tests/CMakeLists.txt index 1cd6eac7ee..de8e9e4974 100644 --- a/scripts/o3de/tests/CMakeLists.txt +++ b/scripts/o3de/tests/CMakeLists.txt @@ -25,6 +25,13 @@ ly_add_pytest( EXCLUDE_TEST_RUN_TARGET_FROM_IDE ) +ly_add_pytest( + NAME o3de_enable_gem + PATH ${CMAKE_CURRENT_LIST_DIR}/unit_test_enable_gem.py + TEST_SUITE smoke + EXCLUDE_TEST_RUN_TARGET_FROM_IDE +) + ly_add_pytest( NAME o3de_global_project PATH ${CMAKE_CURRENT_LIST_DIR}/unit_test_global_project.py diff --git a/scripts/o3de/tests/unit_test_enable_gem.py b/scripts/o3de/tests/unit_test_enable_gem.py new file mode 100644 index 0000000000..12896a51ba --- /dev/null +++ b/scripts/o3de/tests/unit_test_enable_gem.py @@ -0,0 +1,126 @@ +# +# 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 +# +# + +import io +import json +import logging + +import pytest +import pathlib +from unittest.mock import patch + +from o3de import enable_gem + + +TEST_PROJECT_JSON_PAYLOAD = ''' +{ + "project_name": "TestProject", + "origin": "The primary repo for TestProject goes here: i.e. http://www.mydomain.com", + "license": "What license TestProject uses goes here: i.e. https://opensource.org/licenses/MIT", + "display_name": "TestProject", + "summary": "A short description of TestProject.", + "canonical_tags": [ + "Project" + ], + "user_tags": [ + "TestProject" + ], + "icon_path": "preview.png", + "engine": "o3de-install", + "restricted_name": "projects", + "external_subdirectories": [ + ] +} +''' + +TEST_GEM_JSON_PAYLOAD = ''' +{ + "gem_name": "TestGem", + "display_name": "TestGem", + "license": "What license TestGem uses goes here: i.e. https://opensource.org/licenses/MIT", + "origin": "The primary repo for TestGem goes here: i.e. http://www.mydomain.com", + "type": "Code", + "summary": "A short description of TestGem.", + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + "TestGem" + ], + "icon_path": "preview.png", + "requirements": "" +} +''' + + +@pytest.fixture(scope='class') +def init_enable_gem_data(request): + class EnableGemData: + def __init__(self): + self.project_data = json.loads(TEST_PROJECT_JSON_PAYLOAD) + self.gem_data = json.loads(TEST_GEM_JSON_PAYLOAD) + request.cls.enable_gem = EnableGemData() + + +@pytest.mark.usefixtures('init_enable_gem_data') +class TestEnableGemCommand: + @pytest.mark.parametrize("gem_path, project_path, gem_registered_with_project, gem_registered_with_engine," + "expected_result", [ + pytest.param(pathlib.PurePath('E:/TestGem'), pathlib.PurePath('E:/TestProject'), False, True, 0), + pytest.param(pathlib.PurePath('E:/TestGem'), pathlib.PurePath('E:/TestProject'), False, False, 0), + pytest.param(pathlib.PurePath('E:/TestGem'), pathlib.PurePath('E:/TestProject'), True, False, 0), + ] + ) + def test_enable_gem_registers_gem_as_well(self, gem_path, project_path, gem_registered_with_project, gem_registered_with_engine, + expected_result): + + def get_registered_path(project_name: str = None, gem_name: str = None) -> pathlib.Path: + if project_name: + return project_path + elif gem_name: + return gem_path + return None + + def get_registered_gem_path(gem_name: str) -> pathlib.Path: + return gem_path + + def save_o3de_manifest(new_project_data: dict, manifest_path: pathlib.Path = None) -> bool: + if manifest_path == project_path: + self.enable_gem.project_data = new_project_data + return True + + def get_project_json_data(json_data: pathlib.Path, project_path: pathlib.Path): + return self.enable_gem.project_data + + def get_gem_json_data(gem_path: pathlib.Path, project_path: pathlib.Path): + return self.enable_gem.gem_data + + def get_project_gems(project_path: pathlib.Path): + return [gem_path] if gem_registered_with_project else [] + + def get_engine_gems(): + return [gem_path] if gem_registered_with_engine else [] + + def add_gem_dependency(enable_gem_cmake_file: pathlib.Path, gem_name: str): + return 0 + + with patch('pathlib.Path.is_dir', return_value=True) as pathlib_is_dir_patch,\ + patch('pathlib.Path.is_file', return_value=True) as pathlib_is_file_patch,\ + patch('o3de.manifest.save_o3de_manifest', side_effect=save_o3de_manifest) as save_o3de_manifest_patch,\ + patch('o3de.manifest.get_registered', side_effect=get_registered_path) as get_registered_patch,\ + patch('o3de.manifest.get_gem_json_data', side_effect=get_gem_json_data) as get_gem_json_data_patch,\ + patch('o3de.manifest.get_project_json_data', side_effect=get_project_json_data) as get_gem_json_data_patch,\ + patch('o3de.manifest.get_project_gems', side_effect=get_project_gems) as get_project_gems_patch,\ + patch('o3de.manifest.get_engine_gems', side_effect=get_engine_gems) as get_engine_gems_patch,\ + patch('o3de.cmake.add_gem_dependency', side_effect=add_gem_dependency) as add_gem_dependency_patch,\ + patch('o3de.validation.valid_o3de_gem_json', return_value=True) as valid_gem_json_patch: + result = enable_gem.enable_gem_in_project(gem_path=gem_path, project_path=project_path) + assert result == expected_result + # If the gem isn't registered with the engine or project already it should now be registered with the project + if not gem_registered_with_engine and gem_registered_with_project: + assert gem_path.as_posix() in self.enable_gem.project_data.get('external_subdirectories', []) diff --git a/scripts/o3de/tests/unit_test_project_properties.py b/scripts/o3de/tests/unit_test_project_properties.py index f72a4dfe4c..0236e6cf90 100644 --- a/scripts/o3de/tests/unit_test_project_properties.py +++ b/scripts/o3de/tests/unit_test_project_properties.py @@ -6,33 +6,41 @@ # # +import json import pytest import pathlib from unittest.mock import patch from o3de import project_properties -TEST_DEFAULT_PROJECT_DATA = { - "template_name": "DefaultProject", - "restricted_name": "o3de", - "restricted_platform_relative_path": "Templates", - "origin": "The primary repo for DefaultProject goes here: i.e. http://www.mydomain.com", - "license": "What license DefaultProject uses goes here: i.e. https://opensource.org/licenses/MIT", - "display_name": "Default", - "summary": "A short description of DefaultProject.", - "included_gems": ["Atom","Camera","EMotionFX","UI","Maestro","Input","ImGui"], - "canonical_tags": [], - "user_tags": [ - "DefaultProject" +TEST_PROJECT_JSON_PAYLOAD = ''' +{ + "project_name": "TestProject", + "origin": "The primary repo for TestProject goes here: i.e. http://www.mydomain.com", + "license": "What license TestProject uses goes here: i.e. https://opensource.org/licenses/MIT", + "display_name": "TestProject", + "summary": "A short description of TestProject.", + "canonical_tags": [ + "Project" ], - "icon_path": "preview.png" + "user_tags": [ + "TestProject" + ], + "icon_path": "preview.png", + "engine": "o3de-install", + "restricted_name": "projects", + "external_subdirectories": [ + "D:/TestGem" + ] } +''' + @pytest.fixture(scope='class') def init_project_json_data(request): class ProjectJsonData: def __init__(self): - self.data = TEST_DEFAULT_PROJECT_DATA + self.data = json.loads(TEST_PROJECT_JSON_PAYLOAD) request.cls.project_json = ProjectJsonData() @pytest.mark.usefixtures('init_project_json_data') From 627dcc49f1e151e0f881dd9ec6690e831175a022 Mon Sep 17 00:00:00 2001 From: nemerle <96597+nemerle@users.noreply.github.com> Date: Wed, 4 Aug 2021 20:56:37 +0200 Subject: [PATCH 41/51] GetActivePathName was using default - constructed enum DocumentEditingMode() Also, SaveLevel was not using destName when constructing newFilePath Other code changes: * LogLoadTime is simplified by using QFile * reduce nesting in DoSaveDocument by using early return. * marked a few eligible methods as const * Simplified OnEnvironmentPropertyChanged a bit Signed-off-by: nemerle <96597+nemerle@users.noreply.github.com> --- Code/Editor/CryEditDoc.cpp | 147 ++++++++++++++++--------------------- Code/Editor/CryEditDoc.h | 30 ++++---- 2 files changed, 79 insertions(+), 98 deletions(-) diff --git a/Code/Editor/CryEditDoc.cpp b/Code/Editor/CryEditDoc.cpp index 43f599b191..9421198de7 100644 --- a/Code/Editor/CryEditDoc.cpp +++ b/Code/Editor/CryEditDoc.cpp @@ -108,21 +108,12 @@ namespace Internal // CCryEditDoc construction/destruction CCryEditDoc::CCryEditDoc() - : doc_validate_surface_types(0) + : doc_validate_surface_types(nullptr) , m_modifiedModuleFlags(eModifiedNothing) - // It assumes loaded levels have already been exported. Can be a big fat lie, though. - // The right way would require us to save to the level folder the export status of the - // level. - , m_boLevelExported(true) - , m_modified(false) - , m_envProbeHeight(200.0f) - , m_envProbeSliceRelativePath("EngineAssets/Slices/DefaultLevelSetup.slice") { //////////////////////////////////////////////////////////////////////// // Set member variables to initial values //////////////////////////////////////////////////////////////////////// - m_bLoadFailed = false; - m_waterColor = QColor(0, 0, 255); m_fogTemplate = GetIEditor()->FindTemplate("Fog"); m_environmentTemplate = GetIEditor()->FindTemplate("Environment"); @@ -136,7 +127,6 @@ CCryEditDoc::CCryEditDoc() m_environmentTemplate = XmlHelpers::CreateXmlNode("Environment"); } - m_bDocumentReady = false; GetIEditor()->SetDocument(this); CLogFile::WriteLine("Document created"); RegisterConsoleVariables(); @@ -195,7 +185,7 @@ CCryEditDoc::DocumentEditingMode CCryEditDoc::GetEditMode() const QString CCryEditDoc::GetActivePathName() const { - return DocumentEditingMode() == CCryEditDoc::DocumentEditingMode::SliceEdit ? GetSlicePathName() : GetLevelPathName(); + return GetEditMode() == CCryEditDoc::DocumentEditingMode::SliceEdit ? GetSlicePathName() : GetLevelPathName(); } QString CCryEditDoc::GetTitle() const @@ -260,9 +250,9 @@ void CCryEditDoc::DeleteContents() GetIEditor()->FlushUndo(); // Notify listeners. - for (std::list::iterator it = m_listeners.begin(); it != m_listeners.end(); ++it) + for (IDocListener* listener : m_listeners) { - (*it)->OnCloseDocument(); + listener->OnCloseDocument(); } GetIEditor()->ResetViews(); @@ -458,7 +448,7 @@ void CCryEditDoc::Load(TDocMultiArchive& arrXmlAr, const QString& szFilename) ////////////////////////////////////////////////////////////////////////// // Load water color. ////////////////////////////////////////////////////////////////////////// - (*arrXmlAr[DMAS_GENERAL]).root->getAttr("WaterColor", m_waterColor); + (*arrXmlAr[DMAS_GENERAL]).root->getAttr("WaterColor", m_waterColor); ////////////////////////////////////////////////////////////////////////// // Load View Settings @@ -507,9 +497,9 @@ void CCryEditDoc::Load(TDocMultiArchive& arrXmlAr, const QString& szFilename) CAutoLogTime logtime("Post Load"); // Notify listeners. - for (std::list::iterator it = m_listeners.begin(); it != m_listeners.end(); ++it) + for (IDocListener* listener : m_listeners) { - (*it)->OnLoadDocument(); + listener->OnLoadDocument(); } } @@ -708,7 +698,8 @@ bool CCryEditDoc::SaveModified() return true; } - auto button = QMessageBox::question(AzToolsFramework::GetActiveWindow(), QString(), tr("Save changes to %1?").arg(GetTitle()), QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel); + auto button = QMessageBox::question(AzToolsFramework::GetActiveWindow(), QString(), tr("Save changes to %1?").arg(GetTitle()), + QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel); switch (button) { case QMessageBox::Cancel: @@ -933,8 +924,7 @@ bool CCryEditDoc::OnSaveDocument(const QString& lpszPathName) } TSaveDocContext context; - if (shouldSaveLevel && - BeforeSaveDocument(lpszPathName, context)) + if (shouldSaveLevel && BeforeSaveDocument(lpszPathName, context)) { DoSaveDocument(lpszPathName, context); saveSuccess = AfterSaveDocument(lpszPathName, context); @@ -972,7 +962,7 @@ bool CCryEditDoc::BeforeSaveDocument(const QString& lpszPathName, TSaveDocContex return TRUE; } -bool CCryEditDoc::HasLayerNameConflicts() +bool CCryEditDoc::HasLayerNameConflicts() const { AZStd::vector editorEntities; AzToolsFramework::EditorEntityContextRequestBus::Broadcast( @@ -1004,43 +994,42 @@ bool CCryEditDoc::HasLayerNameConflicts() bool CCryEditDoc::DoSaveDocument(const QString& filename, TSaveDocContext& context) { bool& bSaved = context.bSaved; - if (bSaved) + if (!bSaved) { - // Paranoia - we shouldn't get this far into the save routine without a level loaded (empty levelPath) - // If nothing is loaded, we don't need to save anything - if (filename.isEmpty()) - { - bSaved = false; - } - else - { - // Save Tag Point locations to file if auto save of tag points disabled - if (!gSettings.bAutoSaveTagPoints) - { - CCryEditApp::instance()->SaveTagLocations(); - } - - QString normalizedPath = Path::ToUnixPath(filename); - if (IsSliceFile(normalizedPath)) - { - bSaved = SaveSlice(normalizedPath); - } - else - { - bSaved = SaveLevel(normalizedPath); - } - - // Changes filename for this document. - SetPathName(normalizedPath); - } + return false; + } + // Paranoia - we shouldn't get this far into the save routine without a level loaded (empty levelPath) + // If nothing is loaded, we don't need to save anything + if (filename.isEmpty()) + { + bSaved = false; + return false; } + // Save Tag Point locations to file if auto save of tag points disabled + if (!gSettings.bAutoSaveTagPoints) + { + CCryEditApp::instance()->SaveTagLocations(); + } + + QString normalizedPath = Path::ToUnixPath(filename); + if (IsSliceFile(normalizedPath)) + { + bSaved = SaveSlice(normalizedPath); + } + else + { + bSaved = SaveLevel(normalizedPath); + } + + // Changes filename for this document. + SetPathName(normalizedPath); return bSaved; } bool CCryEditDoc::AfterSaveDocument([[maybe_unused]] const QString& lpszPathName, TSaveDocContext& context, bool bShowPrompt) { - bool& bSaved = context.bSaved; + bool bSaved = context.bSaved; GetIEditor()->Notify(eNotify_OnEndSceneSave); @@ -1067,8 +1056,7 @@ bool CCryEditDoc::AfterSaveDocument([[maybe_unused]] const QString& lpszPathName static void GetUserSettingsFile(const QString& levelFolder, QString& userSettings) { const char* pUserName = GetISystem()->GetUserName(); - QString fileName; - fileName = QStringLiteral("%1_usersettings.editor_xml").arg(pUserName); + QString fileName = QStringLiteral("%1_usersettings.editor_xml").arg(pUserName); userSettings = Path::Make(levelFolder, fileName); } @@ -1182,9 +1170,9 @@ bool CCryEditDoc::SaveLevel(const QString& filename) } QString oldFilePath = QDir(oldLevelFolder).absoluteFilePath(sourceName); - QString newFilePath = QDir(newLevelFolder).absoluteFilePath(sourceName); + QString newFilePath = QDir(newLevelFolder).absoluteFilePath(destName); CFileUtil::CopyFile(oldFilePath, newFilePath); - } while (findHandle = pIPak->FindNext(findHandle)); + } while ((findHandle = pIPak->FindNext(findHandle))); pIPak->FindClose(findHandle); } @@ -1506,7 +1494,7 @@ bool CCryEditDoc::LoadEntitiesFromLevel(const QString& levelPakFile) { AZStd::vector fileBuffer; fileBuffer.resize(entitiesFile.GetLength()); - if (fileBuffer.size() > 0) + if (!fileBuffer.empty()) { if (fileBuffer.size() == entitiesFile.ReadRaw(fileBuffer.begin(), fileBuffer.size())) { @@ -1910,7 +1898,7 @@ void CCryEditDoc::UnregisterListener(IDocListener* listener) m_listeners.remove(listener); } -void CCryEditDoc::LogLoadTime(int time) +void CCryEditDoc::LogLoadTime(int time) const { QString appFilePath = QDir::toNativeSeparators(QCoreApplication::applicationFilePath()); QString exePath = Path::GetPath(appFilePath); @@ -1922,21 +1910,18 @@ void CCryEditDoc::LogLoadTime(int time) SetFileAttributes(filename.toUtf8().data(), FILE_ATTRIBUTE_ARCHIVE); #endif - FILE* file = nullptr; - azfopen(&file, filename.toUtf8().data(), "at"); - - if (file) + QFile file(filename); + if (!file.open(QFile::Append | QFile::Text)) { - char version[50]; - GetIEditor()->GetFileVersion().ToShortString(version, AZ_ARRAY_SIZE(version)); - - QString text; - - time = time / 1000; - text = QStringLiteral("\n[%1] Level %2 loaded in %3 seconds").arg(version, level).arg(time); - fwrite(text.toUtf8().data(), text.toUtf8().length(), 1, file); - fclose(file); + return; } + + char version[50]; + GetIEditor()->GetFileVersion().ToShortString(version, AZ_ARRAY_SIZE(version)); + + time = time / 1000; + QString text = QStringLiteral("\n[%1] Level %2 loaded in %3 seconds").arg(version, level).arg(time); + file.write(text.toUtf8()); } void CCryEditDoc::SetDocumentReady(bool bReady) @@ -1944,7 +1929,7 @@ void CCryEditDoc::SetDocumentReady(bool bReady) m_bDocumentReady = bReady; } -void CCryEditDoc::GetMemoryUsage(ICrySizer* pSizer) +void CCryEditDoc::GetMemoryUsage(ICrySizer* pSizer) const { { SIZER_COMPONENT_NAME(pSizer, "UndoManager(estimate)"); @@ -2068,12 +2053,9 @@ void CCryEditDoc::InitEmptyLevel(int /*resolution*/, int /*unitSize*/, bool /*bU { // Notify listeners. std::list listeners = m_listeners; - std::list::iterator it, next; - for (it = listeners.begin(); it != listeners.end(); it = next) + for (IDocListener* listener : listeners) { - next = it; - next++; - (*it)->OnNewDocument(); + listener->OnNewDocument(); } } @@ -2134,25 +2116,23 @@ void CCryEditDoc::OnEnvironmentPropertyChanged(IVariable* pVar) { return; } + QString childValue; if (pVar->GetDataType() == IVariable::DT_COLOR) { Vec3 value; pVar->Get(value); - QString buff; QColor gammaColor = ColorLinearToGamma(ColorF(value.x, value.y, value.z)); - buff = QStringLiteral("%1,%2,%3").arg(gammaColor.red()).arg(gammaColor.green()).arg(gammaColor.blue()); - childNode->setAttr("value", buff.toUtf8().data()); + childValue = QStringLiteral("%1,%2,%3").arg(gammaColor.red()).arg(gammaColor.green()).arg(gammaColor.blue()); } else { - QString value; - pVar->Get(value); - childNode->setAttr("value", value.toUtf8().data()); + pVar->Get(childValue); } + childNode->setAttr("value", childValue.toUtf8().data()); } -QString CCryEditDoc::GetCryIndexPath(const LPCTSTR levelFilePath) +QString CCryEditDoc::GetCryIndexPath(const LPCTSTR levelFilePath) const { QString levelPath = Path::GetPath(levelFilePath); QString levelName = Path::GetFileName(levelFilePath); @@ -2183,8 +2163,7 @@ BOOL CCryEditDoc::LoadXmlArchiveArray(TDocMultiArchive& arrXmlAr, const QString& } CPakFile pakFile; - bool loadFromPakSuccess; - loadFromPakSuccess = xmlAr.LoadFromPak(levelPath, pakFile); + bool loadFromPakSuccess = xmlAr.LoadFromPak(levelPath, pakFile); pIPak->ClosePack(absoluteLevelPath.toUtf8().data()); if (!loadFromPakSuccess) { diff --git a/Code/Editor/CryEditDoc.h b/Code/Editor/CryEditDoc.h index 574f8eb7da..d32e8e5bb1 100644 --- a/Code/Editor/CryEditDoc.h +++ b/Code/Editor/CryEditDoc.h @@ -91,7 +91,7 @@ public: // Create from serialization only // ClassWizard generated virtual function overrides virtual bool OnOpenDocument(const QString& lpszPathName); - const bool IsLevelLoadFailed() const { return m_bLoadFailed; } + bool IsLevelLoadFailed() const { return m_bLoadFailed; } //! Marks this document as having errors. void SetHasErrors() { m_hasErrors = true; } @@ -121,7 +121,7 @@ public: // Create from serialization only CClouds* GetClouds() { return m_pClouds; } void SetWaterColor(const QColor& col) { m_waterColor = col; } - QColor GetWaterColor() { return m_waterColor; } + QColor GetWaterColor() const { return m_waterColor; } XmlNodeRef& GetFogTemplate() { return m_fogTemplate; } XmlNodeRef& GetEnvironmentTemplate() { return m_environmentTemplate; } void OnEnvironmentPropertyChanged(IVariable* pVar); @@ -129,7 +129,7 @@ public: // Create from serialization only void RegisterListener(IDocListener* listener); void UnregisterListener(IDocListener* listener); - void GetMemoryUsage(ICrySizer* pSizer); + void GetMemoryUsage(ICrySizer* pSizer) const; static bool IsBackupOrTempLevelSubdirectory(const QString& folderName); protected: @@ -161,14 +161,14 @@ protected: void SerializeFogSettings(CXmlArchive& xmlAr); virtual void SerializeViewSettings(CXmlArchive& xmlAr); void SerializeNameSelection(CXmlArchive& xmlAr); - void LogLoadTime(int time); + void LogLoadTime(int time) const; struct TSaveDocContext { bool bSaved; }; bool BeforeSaveDocument(const QString& lpszPathName, TSaveDocContext& context); - bool HasLayerNameConflicts(); + bool HasLayerNameConflicts() const; bool DoSaveDocument(const QString& lpszPathName, TSaveDocContext& context); bool AfterSaveDocument(const QString& lpszPathName, TSaveDocContext& context, bool bShowPrompt = true); @@ -180,7 +180,7 @@ protected: void OnStartLevelResourceList(); static void OnValidateSurfaceTypesChanged(ICVar*); - QString GetCryIndexPath(const LPCTSTR levelFilePath); + QString GetCryIndexPath(const LPCTSTR levelFilePath) const; ////////////////////////////////////////////////////////////////////////// // SliceEditorEntityOwnershipServiceNotificationBus::Handler @@ -188,24 +188,26 @@ protected: void OnSliceInstantiationFailed(const AZ::Data::AssetId& sliceAssetId, const AzFramework::SliceInstantiationTicket& /*ticket*/) override; ////////////////////////////////////////////////////////////////////////// - bool m_bLoadFailed; - QColor m_waterColor; + bool m_bLoadFailed = false; + QColor m_waterColor = QColor(0, 0, 255); XmlNodeRef m_fogTemplate; XmlNodeRef m_environmentTemplate; CClouds* m_pClouds; std::list m_listeners; - bool m_bDocumentReady; - ICVar* doc_validate_surface_types; + bool m_bDocumentReady = false; + ICVar* doc_validate_surface_types = nullptr; int m_modifiedModuleFlags; - bool m_boLevelExported; - bool m_modified; + // On construction, it assumes loaded levels have already been exported. Can be a big fat lie, though. + // The right way would require us to save to the level folder the export status of the level. + bool m_boLevelExported = true; + bool m_modified = false; QString m_pathName; QString m_slicePathName; QString m_title; AZ::Data::AssetId m_envProbeSliceAssetId; float m_terrainSize; - const char* m_envProbeSliceRelativePath; - const float m_envProbeHeight; + const char* m_envProbeSliceRelativePath = "EngineAssets/Slices/DefaultLevelSetup.slice"; + const float m_envProbeHeight = 200.0f; bool m_hasErrors = false; ///< This is used to warn the user that they may lose work when they go to save. }; From b7e69a1d1dc41722f0bf7553d66dbda128026365 Mon Sep 17 00:00:00 2001 From: jackalbe <23512001+jackalbe@users.noreply.github.com> Date: Wed, 4 Aug 2021 14:11:10 -0500 Subject: [PATCH 42/51] turning off editor.blast.tests due to AR failures on suite shutdown (#2837) removed unused code Signed-off-by: Jackson <23512001+jackalbe@users.noreply.github.com> --- .../EditorBlastChunksAssetHandlerTest.cpp | 23 ------------------- .../Blast/Code/blast_editor_tests_files.cmake | 2 +- 2 files changed, 1 insertion(+), 24 deletions(-) diff --git a/Gems/Blast/Code/Tests/Editor/EditorBlastChunksAssetHandlerTest.cpp b/Gems/Blast/Code/Tests/Editor/EditorBlastChunksAssetHandlerTest.cpp index 9f48cae4da..80a0587b1b 100644 --- a/Gems/Blast/Code/Tests/Editor/EditorBlastChunksAssetHandlerTest.cpp +++ b/Gems/Blast/Code/Tests/Editor/EditorBlastChunksAssetHandlerTest.cpp @@ -88,20 +88,6 @@ namespace UnitTest AZStd::unique_ptr m_mockComponentApplicationBusHandler; AZStd::unique_ptr m_mockAssetCatalogRequestBusHandler; AZStd::unique_ptr m_mockAssetManager; - AZStd::unique_ptr m_serializeContext; - - void SetUpChunkComponents() - { - m_serializeContext = AZStd::make_unique(); - - AZ::Entity::Reflect(m_serializeContext.get()); - AzToolsFramework::Components::EditorComponentBase::Reflect(m_serializeContext.get()); - } - - void TearDownChunkComponents() - { - m_serializeContext.reset(); - } void SetUp() override final { @@ -128,15 +114,6 @@ namespace UnitTest AZ::AllocatorInstance::Destroy(); AllocatorsTestFixture::TearDown(); } - - void SaveChunkAssetToStream(AZ::Entity* chunkAssetEntity, AZStd::vector& buffer) - { - buffer.clear(); - AZ::IO::ByteContainerStream> stream(&buffer); - AZ::ObjectStream* objStream = AZ::ObjectStream::Create(&stream, *m_serializeContext.get(), AZ::ObjectStream::ST_XML); - objStream->WriteClass(chunkAssetEntity); - EXPECT_TRUE(objStream->Finalize()); - } }; TEST_F(EditorBlastChunkAssetHandlerTestFixture, EditorBlastChunkAssetHandler_AssetManager_Registered) diff --git a/Gems/Blast/Code/blast_editor_tests_files.cmake b/Gems/Blast/Code/blast_editor_tests_files.cmake index 7076530312..10d2377ccf 100644 --- a/Gems/Blast/Code/blast_editor_tests_files.cmake +++ b/Gems/Blast/Code/blast_editor_tests_files.cmake @@ -7,6 +7,6 @@ # set(FILES - Tests/Editor/EditorBlastChunksAssetHandlerTest.cpp + # Disabled until SPEC-7904 is fixed Tests/Editor/EditorBlastChunksAssetHandlerTest.cpp Tests/Editor/EditorTestMain.cpp ) From 1e4c147e0bf86737e4803cb60579ed4aae3660bd Mon Sep 17 00:00:00 2001 From: Chris Galvan Date: Wed, 4 Aug 2021 15:34:22 -0500 Subject: [PATCH 43/51] Fixed asset name for default groundplane. Signed-off-by: Chris Galvan --- Assets/Editor/Prefabs/Default_Level.prefab | 6 +++--- .../{groundplane_521x521m.fbx => groundplane_512x512m.fbx} | 0 2 files changed, 3 insertions(+), 3 deletions(-) rename Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Groudplane/{groundplane_521x521m.fbx => groundplane_512x512m.fbx} (100%) diff --git a/Assets/Editor/Prefabs/Default_Level.prefab b/Assets/Editor/Prefabs/Default_Level.prefab index 64656e1e2f..d02d669f53 100644 --- a/Assets/Editor/Prefabs/Default_Level.prefab +++ b/Assets/Editor/Prefabs/Default_Level.prefab @@ -212,10 +212,10 @@ "Configuration": { "ModelAsset": { "assetId": { - "guid": "{935F694A-8639-515B-8133-81CDC7948E5B}", - "subId": 277333723 + "guid": "{0CD745C0-6AA8-569A-A68A-73A3270986C4}", + "subId": 277889906 }, - "assetHint": "objects/groudplane/groundplane_521x521m.azmodel" + "assetHint": "objects/groudplane/groundplane_512x512m.azmodel" } } } diff --git a/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Groudplane/groundplane_521x521m.fbx b/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Groudplane/groundplane_512x512m.fbx similarity index 100% rename from Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Groudplane/groundplane_521x521m.fbx rename to Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Groudplane/groundplane_512x512m.fbx From 7404622b482cd5a40b5832552958ed2c33de317f Mon Sep 17 00:00:00 2001 From: srikappa-amzn <82230713+srikappa-amzn@users.noreply.github.com> Date: Wed, 4 Aug 2021 16:54:40 -0700 Subject: [PATCH 44/51] Clear prefab templates on new level creations and loads (#2842) * Clear prefab templates on new level creations and loads Signed-off-by: srikappa-amzn * Fixed failing prefab unit tests after change to clear templates Signed-off-by: srikappa-amzn --- .../Entity/PrefabEditorEntityOwnershipService.cpp | 11 ++--------- .../PrefabInstanceToTemplatePropagatorTests.cpp | 4 +++- .../Tests/Prefab/PrefabUpdateInstancesTests.cpp | 1 + .../Tests/Prefab/PrefabUpdateTemplateTests.cpp | 1 + 4 files changed, 7 insertions(+), 10 deletions(-) diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/PrefabEditorEntityOwnershipService.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/PrefabEditorEntityOwnershipService.cpp index 2d97689610..b5cf5fb878 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/PrefabEditorEntityOwnershipService.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/PrefabEditorEntityOwnershipService.cpp @@ -72,15 +72,8 @@ namespace AzToolsFramework if (m_rootInstance != nullptr) { - // Need to save off the template id to remove the template after the instance is deleted. - Prefab::TemplateId templateId = m_rootInstance->GetTemplateId(); m_rootInstance.reset(); - if (templateId != Prefab::InvalidTemplateId) - { - // Remove the template here so that if we're in a Deactivate/Activate cycle, it can recreate the template/rootInstance - // correctly - m_prefabSystemComponent->RemoveTemplate(templateId); - } + m_prefabSystemComponent->RemoveAllTemplates(); } } @@ -95,7 +88,7 @@ namespace AzToolsFramework if (templateId != Prefab::InvalidTemplateId) { m_rootInstance->SetTemplateId(Prefab::InvalidTemplateId); - m_prefabSystemComponent->RemoveTemplate(templateId); + m_prefabSystemComponent->RemoveAllTemplates(); } m_rootInstance->SetContainerEntityName("Level"); } diff --git a/Code/Framework/AzToolsFramework/Tests/Prefab/PrefabInstanceToTemplatePropagatorTests.cpp b/Code/Framework/AzToolsFramework/Tests/Prefab/PrefabInstanceToTemplatePropagatorTests.cpp index e3b65db97e..8379992553 100644 --- a/Code/Framework/AzToolsFramework/Tests/Prefab/PrefabInstanceToTemplatePropagatorTests.cpp +++ b/Code/Framework/AzToolsFramework/Tests/Prefab/PrefabInstanceToTemplatePropagatorTests.cpp @@ -245,7 +245,9 @@ namespace UnitTest m_instanceToTemplateInterface->GenerateDomForInstance(instanceDomBeforeUpdate, *firstInstance); //remove instance from instance - firstInstance->DetachNestedInstance(addedAlias); + AZStd::unique_ptr detachedInstance = firstInstance->DetachNestedInstance(addedAlias); + ASSERT_TRUE(detachedInstance != nullptr); + m_prefabSystemComponent->RemoveLink(detachedInstance->GetLinkId()); //create document with after change snapshot PrefabDom instanceDomAfterUpdate; diff --git a/Code/Framework/AzToolsFramework/Tests/Prefab/PrefabUpdateInstancesTests.cpp b/Code/Framework/AzToolsFramework/Tests/Prefab/PrefabUpdateInstancesTests.cpp index 0d955789ac..baded6d43e 100644 --- a/Code/Framework/AzToolsFramework/Tests/Prefab/PrefabUpdateInstancesTests.cpp +++ b/Code/Framework/AzToolsFramework/Tests/Prefab/PrefabUpdateInstancesTests.cpp @@ -309,6 +309,7 @@ namespace UnitTest // and use the updated enclosing Instance to update the PrefabDom of Template. AZStd::unique_ptr detachedInstance = newEnclosingInstance->DetachNestedInstance(nestedInstanceAliases.front()); ASSERT_TRUE(detachedInstance); + m_prefabSystemComponent->RemoveLink(detachedInstance->GetLinkId()); PrefabDom updatedTemplateDom; ASSERT_TRUE(PrefabDomUtils::StoreInstanceInPrefabDom(*newEnclosingInstance, updatedTemplateDom)); diff --git a/Code/Framework/AzToolsFramework/Tests/Prefab/PrefabUpdateTemplateTests.cpp b/Code/Framework/AzToolsFramework/Tests/Prefab/PrefabUpdateTemplateTests.cpp index 4e7d9d95d1..6f90e245f7 100644 --- a/Code/Framework/AzToolsFramework/Tests/Prefab/PrefabUpdateTemplateTests.cpp +++ b/Code/Framework/AzToolsFramework/Tests/Prefab/PrefabUpdateTemplateTests.cpp @@ -274,6 +274,7 @@ namespace UnitTest InstanceAlias aliasOfWheelInstanceToRetain = wheelInstanceAliasesUnderAxle.front(); AZStd::unique_ptr detachedInstance = axleInstance->DetachNestedInstance(wheelInstanceAliasesUnderAxle.back()); ASSERT_TRUE(detachedInstance); + m_prefabSystemComponent->RemoveLink(detachedInstance->GetLinkId()); PrefabDom updatedAxleInstanceDom; ASSERT_TRUE(PrefabDomUtils::StoreInstanceInPrefabDom(*axleInstance, updatedAxleInstanceDom)); m_prefabSystemComponent->UpdatePrefabTemplate(axleTemplateId, updatedAxleInstanceDom); From 1169c82b98d4522541fb831d69902c54792ad7a3 Mon Sep 17 00:00:00 2001 From: hultonha <82228511+hultonha@users.noreply.github.com> Date: Thu, 5 Aug 2021 09:45:03 +0100 Subject: [PATCH 45/51] Make camera controller priority customizable (#2826) * make 'should handle' logic customizable Signed-off-by: hultonha * updates to get priority function Signed-off-by: hultonha * minor comment tweak Signed-off-by: hultonha --- Code/Editor/EditorViewportWidget.cpp | 7 +++ .../ModularViewportCameraController.h | 35 ++++++++++--- .../ModularViewportCameraController.cpp | 50 ++++++++++++------- 3 files changed, 66 insertions(+), 26 deletions(-) diff --git a/Code/Editor/EditorViewportWidget.cpp b/Code/Editor/EditorViewportWidget.cpp index 4ea36728ad..4c638e1f82 100644 --- a/Code/Editor/EditorViewportWidget.cpp +++ b/Code/Editor/EditorViewportWidget.cpp @@ -1240,6 +1240,13 @@ AZStd::shared_ptr CreateMod AzFramework::ViewportId viewportId) { auto controller = AZStd::make_shared(); + + controller->SetCameraPriorityBuilderCallback( + [](AtomToolsFramework::CameraControllerPriorityFn& cameraControllerPriorityFn) + { + cameraControllerPriorityFn = AtomToolsFramework::DefaultCameraControllerPriority; + }); + controller->SetCameraPropsBuilderCallback( [](AzFramework::CameraProps& cameraProps) { diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Viewport/ModularViewportCameraController.h b/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Viewport/ModularViewportCameraController.h index d8778a9b9d..e6a666c640 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Viewport/ModularViewportCameraController.h +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Viewport/ModularViewportCameraController.h @@ -18,6 +18,14 @@ namespace AtomToolsFramework { class ModularViewportCameraControllerInstance; + //! A function object to represent returning a camera controller priority. + using CameraControllerPriorityFn = + AZStd::function; + + //! The default behavior for what priority the camera controller should respond to events at. + //! @note This can change based on the state of the camera controller/system. + AzFramework::ViewportControllerPriority DefaultCameraControllerPriority(const AzFramework::CameraSystem& cameraSystem); + //! Builder class to create and configure a ModularViewportCameraControllerInstance. class ModularViewportCameraController : public AzFramework::MultiViewportController< @@ -25,23 +33,33 @@ namespace AtomToolsFramework AzFramework::ViewportControllerPriority::DispatchToAllPriorities> { public: + friend ModularViewportCameraControllerInstance; + using CameraListBuilder = AZStd::function; using CameraPropsBuilder = AZStd::function; + using CameraPriorityBuilder = AZStd::function; - //! Sets the camera list builder callback used to populate new ModularViewportCameraControllerInstances + //! Sets the camera list builder callback used to populate new ModularViewportCameraControllerInstances. void SetCameraListBuilderCallback(const CameraListBuilder& builder); - //! Sets the camera props builder callback used to populate new ModularViewportCameraControllerInstances + //! Sets the camera props builder callback used to populate new ModularViewportCameraControllerInstances. void SetCameraPropsBuilderCallback(const CameraPropsBuilder& builder); - //! Sets up a camera list based on this controller's CameraListBuilderCallback - void SetupCameras(AzFramework::Cameras& cameras); - //! Sets up properties shared across all cameras - void SetupCameraProperies(AzFramework::CameraProps& cameraProps); + //! Sets the camera controller priority builder callback used to populate new ModularViewportCameraControllerInstances. + void SetCameraPriorityBuilderCallback(const CameraPriorityBuilder& builder); private: + //! Sets up a camera list based on this controller's CameraListBuilderCallback. + void SetupCameras(AzFramework::Cameras& cameras); + //! Sets up properties shared across all cameras. + void SetupCameraProperties(AzFramework::CameraProps& cameraProps); + //! Sets up how the camera controller should decide at what priority level to respond to. + void SetupCameraControllerPriority(CameraControllerPriorityFn& cameraPriorityFn); + //! Builder to generate a list of CameraInputs to run in the ModularViewportCameraControllerInstance. CameraListBuilder m_cameraListBuilder; - CameraPropsBuilder m_cameraPropsBuilder; //!< Builder to define custom camera properties to use for things such as rotate and - //!< translate interpolation. + //! Builder to define custom camera properties to use for things such as rotate and translate interpolation. + CameraPropsBuilder m_cameraPropsBuilder; + //! Builder to define what priority level the camera controller should respond to events at. + CameraPriorityBuilder m_cameraControllerPriorityBuilder; }; //! A customizable camera controller that can be configured to run a varying set of CameraInput instances. @@ -87,6 +105,7 @@ namespace AtomToolsFramework AzFramework::Camera m_targetCamera; //!< The target (next) camera state that m_camera is catching up to. AzFramework::CameraSystem m_cameraSystem; //!< The camera system responsible for managing all CameraInputs. AzFramework::CameraProps m_cameraProps; //!< Camera properties to control rotate and translate smoothness. + CameraControllerPriorityFn m_priorityFn; //!< Controls at what priority the camera controller should respond to events. CameraAnimation m_cameraAnimation; //!< Camera animation state (used during CameraMode::Animation). CameraMode m_cameraMode = CameraMode::Control; //!< The current mode the camera is operating in. diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Viewport/ModularViewportCameraController.cpp b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Viewport/ModularViewportCameraController.cpp index cc87ba1e46..e98df83930 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Viewport/ModularViewportCameraController.cpp +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Viewport/ModularViewportCameraController.cpp @@ -68,6 +68,11 @@ namespace AtomToolsFramework m_cameraPropsBuilder = builder; } + void ModularViewportCameraController::SetCameraPriorityBuilderCallback(const CameraPriorityBuilder& builder) + { + m_cameraControllerPriorityBuilder = builder; + } + void ModularViewportCameraController::SetupCameras(AzFramework::Cameras& cameras) { if (m_cameraListBuilder) @@ -76,7 +81,7 @@ namespace AtomToolsFramework } } - void ModularViewportCameraController::SetupCameraProperies(AzFramework::CameraProps& cameraProps) + void ModularViewportCameraController::SetupCameraProperties(AzFramework::CameraProps& cameraProps) { if (m_cameraPropsBuilder) { @@ -84,12 +89,36 @@ namespace AtomToolsFramework } } + void ModularViewportCameraController::SetupCameraControllerPriority(CameraControllerPriorityFn& cameraPriorityFn) + { + if (m_cameraControllerPriorityBuilder) + { + m_cameraControllerPriorityBuilder(cameraPriorityFn); + } + } + + // what priority should the camera system respond to + AzFramework::ViewportControllerPriority DefaultCameraControllerPriority(const AzFramework::CameraSystem& cameraSystem) + { + // ModernViewportCameraControllerInstance receives events at all priorities, when it is in 'exclusive' mode + // or it is actively handling events (essentially when the camera system is 'active' and responding to inputs) + // it should only respond to the highest priority + if (cameraSystem.m_cameras.Exclusive() || cameraSystem.HandlingEvents()) + { + return AzFramework::ViewportControllerPriority::Highest; + } + + // otherwise it should only respond to normal priority events + return AzFramework::ViewportControllerPriority::Normal; + } + ModularViewportCameraControllerInstance::ModularViewportCameraControllerInstance( const AzFramework::ViewportId viewportId, ModularViewportCameraController* controller) : MultiViewportControllerInstanceInterface(viewportId, controller) { controller->SetupCameras(m_cameraSystem.m_cameras); - controller->SetupCameraProperies(m_cameraProps); + controller->SetupCameraProperties(m_cameraProps); + controller->SetupCameraControllerPriority(m_priorityFn); if (auto viewportContext = RetrieveViewportContext(GetViewportId())) { @@ -118,24 +147,9 @@ namespace AtomToolsFramework AzFramework::ViewportDebugDisplayEventBus::Handler::BusDisconnect(); } - // what priority should the camera system respond to - static AzFramework::ViewportControllerPriority GetPriority(const AzFramework::CameraSystem& cameraSystem) - { - // ModernViewportCameraControllerInstance receives events at all priorities, when it is in 'exclusive' mode - // or it is actively handling events (essentially when the camera system is 'active' and responding to inputs) - // it should only respond to the highest priority - if (cameraSystem.m_cameras.Exclusive() || cameraSystem.HandlingEvents()) - { - return AzFramework::ViewportControllerPriority::Highest; - } - - // otherwise it should only respond to normal priority events - return AzFramework::ViewportControllerPriority::Normal; - } - bool ModularViewportCameraControllerInstance::HandleInputChannelEvent(const AzFramework::ViewportControllerInputEvent& event) { - if (event.m_priority == GetPriority(m_cameraSystem)) + if (event.m_priority == m_priorityFn(m_cameraSystem)) { return m_cameraSystem.HandleEvents(AzFramework::BuildInputEvent(event.m_inputChannel)); } From e55c31d959957ab9e26ea34b9d2befe986c010f1 Mon Sep 17 00:00:00 2001 From: AMZN-AlexOteiza <82234181+AMZN-AlexOteiza@users.noreply.github.com> Date: Thu, 5 Aug 2021 11:03:02 +0100 Subject: [PATCH 46/51] Improve ui object tree to show the actual instance class (#2844) Signed-off-by: Garcia Ruiz Co-authored-by: Garcia Ruiz --- Gems/QtForPython/Editor/Scripts/show_object_tree.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Gems/QtForPython/Editor/Scripts/show_object_tree.py b/Gems/QtForPython/Editor/Scripts/show_object_tree.py index d68a288a3e..48fb5e585b 100755 --- a/Gems/QtForPython/Editor/Scripts/show_object_tree.py +++ b/Gems/QtForPython/Editor/Scripts/show_object_tree.py @@ -220,6 +220,8 @@ class ObjectTreeDialog(QDialog): return for child in obj.children(): object_type = type(child).__name__ + if child.metaObject().className() != object_type: + object_type = f"{child.metaObject().className()} ({object_type})" object_name = child.objectName() text = icon_text = title = window_title = geometry_str = classes = "(N/A)" if isinstance(child, QtGui.QWindow): From 34afed6792ef8a31532daebee86d168a2c3c135f Mon Sep 17 00:00:00 2001 From: amzn-sean <75276488+amzn-sean@users.noreply.github.com> Date: Thu, 5 Aug 2021 14:27:31 +0100 Subject: [PATCH 47/51] fixed Ragdoll component can crash on deactivate (#2834) fixes #2650 The root of the crash was connecting/disconnecting to the AZ::Event SceneSimulationStart when not on the main thread, as that is not thread safe. The connection/disconnection was originally handled from Enable/EnableQueued and Disable/DisabledQueued which can be called from other threads within EmotionFX. I've moved the connection/disconnection to the Constructor / destructor, as the handler is responsible for executing the queued enable/disable actions and it makes sense to have that connection happen external to the Enable/disable path. Signed-off-by: amzn-sean 75276488+amzn-sean@users.noreply.github.com --- .../Source/PhysXCharacters/API/Ragdoll.cpp | 30 ++++--------------- .../Code/Source/PhysXCharacters/API/Ragdoll.h | 1 - 2 files changed, 5 insertions(+), 26 deletions(-) diff --git a/Gems/PhysX/Code/Source/PhysXCharacters/API/Ragdoll.cpp b/Gems/PhysX/Code/Source/PhysXCharacters/API/Ragdoll.cpp index 5d3f6bf0e1..038a1e2ee7 100644 --- a/Gems/PhysX/Code/Source/PhysXCharacters/API/Ragdoll.cpp +++ b/Gems/PhysX/Code/Source/PhysXCharacters/API/Ragdoll.cpp @@ -36,9 +36,6 @@ namespace PhysX } } // namespace Internal - // PhysX::Ragdoll - /*static*/ AZStd::mutex Ragdoll::m_sceneEventMutex; - void Ragdoll::Reflect(AZ::ReflectContext* context) { AZ::SerializeContext* serializeContext = azrtti_cast(context); @@ -109,14 +106,15 @@ namespace PhysX }) { m_sceneOwner = sceneHandle; + if (auto* sceneInterface = AZ::Interface::Get()) + { + sceneInterface->RegisterSceneSimulationStartHandler(m_sceneOwner, m_sceneStartSimHandler); + } } Ragdoll::~Ragdoll() { - { - AZStd::scoped_lock lock(m_sceneEventMutex); - m_sceneStartSimHandler.Disconnect(); - } + m_sceneStartSimHandler.Disconnect(); m_nodes.clear(); //the nodes destructor will remove the simulated body from the scene. } @@ -214,13 +212,6 @@ namespace PhysX } } - // the handler is also connected in EnableSimulationQueued(), - // which will call this function, so if called from that path dont connect here. - if (!m_sceneStartSimHandler.IsConnected()) - { - AZStd::scoped_lock lock(m_sceneEventMutex); - sceneInterface->RegisterSceneSimulationStartHandler(m_sceneOwner, m_sceneStartSimHandler); - } sceneInterface->EnableSimulationOfBody(m_sceneOwner, m_bodyHandle); } @@ -231,12 +222,6 @@ namespace PhysX return; } - if (auto* sceneInterface = AZ::Interface::Get()) - { - AZStd::scoped_lock lock(m_sceneEventMutex); - sceneInterface->RegisterSceneSimulationStartHandler(m_sceneOwner, m_sceneStartSimHandler); - } - m_queuedInitialState = initialState; } @@ -253,11 +238,6 @@ namespace PhysX return; } - { - AZStd::scoped_lock lock(m_sceneEventMutex); - m_sceneStartSimHandler.Disconnect(); - } - physx::PxScene* pxScene = Internal::GetPxScene(m_sceneOwner); const size_t numNodes = m_nodes.size(); diff --git a/Gems/PhysX/Code/Source/PhysXCharacters/API/Ragdoll.h b/Gems/PhysX/Code/Source/PhysXCharacters/API/Ragdoll.h index 94a39d9732..c9d0d5d661 100644 --- a/Gems/PhysX/Code/Source/PhysXCharacters/API/Ragdoll.h +++ b/Gems/PhysX/Code/Source/PhysXCharacters/API/Ragdoll.h @@ -81,6 +81,5 @@ namespace PhysX bool m_queuedDisableSimulation = false; AzPhysics::SceneEvents::OnSceneSimulationStartHandler m_sceneStartSimHandler; - static AZStd::mutex m_sceneEventMutex; }; } // namespace PhysX From 96740ba74d351781d4e808a2c893b0d116629b84 Mon Sep 17 00:00:00 2001 From: Scott Romero <24445312+AMZN-ScottR@users.noreply.github.com> Date: Thu, 5 Aug 2021 13:41:30 -0700 Subject: [PATCH 48/51] [development] installer work - added 'files in use' page and fixed toolset path propagation (#2855) Enable files in use dialog in bootstrap installer to close running o3de tools during uninstall or repair Fixed installer toolset path propagation after variable name change Signed-off-by: AMZN-ScottR 24445312+AMZN-ScottR@users.noreply.github.com --- cmake/Platform/Windows/Packaging/Bootstrapper.wxs | 2 ++ .../Windows/Packaging/BootstrapperTheme.wxl.in | 8 ++++++++ .../Windows/Packaging/BootstrapperTheme.xml.in | 15 +++++++++++++++ cmake/Platform/Windows/Packaging_windows.cmake | 4 ++++ 4 files changed, 29 insertions(+) diff --git a/cmake/Platform/Windows/Packaging/Bootstrapper.wxs b/cmake/Platform/Windows/Packaging/Bootstrapper.wxs index 6971e32ca2..079c20e212 100644 --- a/cmake/Platform/Windows/Packaging/Bootstrapper.wxs +++ b/cmake/Platform/Windows/Packaging/Bootstrapper.wxs @@ -33,6 +33,7 @@ LogoFile="$(var.CPACK_WIX_PRODUCT_LOGO)" ThemeFile="$(var.CPACK_BOOTSTRAP_THEME_FILE).xml" LocalizationFile="$(var.CPACK_BOOTSTRAP_THEME_FILE).wxl" + ShowFilesInUse="yes" ShowVersion="yes" /> @@ -44,6 +45,7 @@ LogoFile="$(var.CPACK_WIX_PRODUCT_LOGO)" ThemeFile="$(var.CPACK_BOOTSTRAP_THEME_FILE).xml" LocalizationFile="$(var.CPACK_BOOTSTRAP_THEME_FILE).wxl" + ShowFilesInUse="yes" ShowVersion="yes" /> diff --git a/cmake/Platform/Windows/Packaging/BootstrapperTheme.wxl.in b/cmake/Platform/Windows/Packaging/BootstrapperTheme.wxl.in index 6c5d16fd49..7d221913d5 100644 --- a/cmake/Platform/Windows/Packaging/BootstrapperTheme.wxl.in +++ b/cmake/Platform/Windows/Packaging/BootstrapperTheme.wxl.in @@ -39,6 +39,14 @@ Setup will install [WixBundleName] on your computer. Click install to continue, Execution Progress &Cancel + + Files In Use + The following applications are using files that need to be modified: + Close the &applications + &Do not close applications, may cause unexpected side effects + &OK + &Cancel + Setup Successful Installation Successfully Completed diff --git a/cmake/Platform/Windows/Packaging/BootstrapperTheme.xml.in b/cmake/Platform/Windows/Packaging/BootstrapperTheme.xml.in index 8d8416539e..b340d23467 100644 --- a/cmake/Platform/Windows/Packaging/BootstrapperTheme.xml.in +++ b/cmake/Platform/Windows/Packaging/BootstrapperTheme.xml.in @@ -70,6 +70,21 @@ + + + #(loc.FilesInUseHeader) + + #(loc.FilesInUseLabel) + + + + + + + + + + #(loc.SuccessHeader) diff --git a/cmake/Platform/Windows/Packaging_windows.cmake b/cmake/Platform/Windows/Packaging_windows.cmake index 7c62a4984c..5bb9928b61 100644 --- a/cmake/Platform/Windows/Packaging_windows.cmake +++ b/cmake/Platform/Windows/Packaging_windows.cmake @@ -17,6 +17,10 @@ else() return() endif() +# IMPORTANT: CPACK_WIX_ROOT is a built-in variable that is required to propagate the path supplied +# via command line down to the cpack internals +set(CPACK_WIX_ROOT ${LY_INSTALLER_WIX_ROOT}) + set(CPACK_GENERATOR WIX) set(_cmake_package_name "cmake-${CPACK_DESIRED_CMAKE_VERSION}-windows-x86_64") From 08c85a40bc0ea2a70cac232fb48f84f445a81708 Mon Sep 17 00:00:00 2001 From: Artur K <96597+nemerle@users.noreply.github.com> Date: Thu, 5 Aug 2021 23:01:45 +0200 Subject: [PATCH 49/51] Fix logic in ReadConnectionSettingsFromSettingsRegistry (#2825) In case an asset platform setting is missing it was supposed to be set to a 'pc' value. Instead it was set to an empty string. Signed-off-by: nemerle <96597+nemerle@users.noreply.github.com> --- .../AzFramework/Asset/AssetSystemComponentHelper.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Code/Framework/AzFramework/AzFramework/Asset/AssetSystemComponentHelper.cpp b/Code/Framework/AzFramework/AzFramework/Asset/AssetSystemComponentHelper.cpp index 14ce16163a..f9411a1f8d 100644 --- a/Code/Framework/AzFramework/AzFramework/Asset/AssetSystemComponentHelper.cpp +++ b/Code/Framework/AzFramework/AzFramework/Asset/AssetSystemComponentHelper.cpp @@ -163,7 +163,11 @@ namespace AzFramework AZ_TracePrintfOnce("AssetSystemComponent", "Failed to find asset platform, setting 'pc'\n"); outputConnectionSettings.m_assetPlatform = "pc"; } - outputConnectionSettings.m_assetPlatform = assetsPlatform; + else + { + outputConnectionSettings.m_assetPlatform = assetsPlatform; + } + if (outputConnectionSettings.m_assetPlatform.empty()) { assetsPlatform = AzFramework::OSPlatformToDefaultAssetPlatform(AZ_TRAIT_OS_PLATFORM_CODENAME); From 4d82d9625cc20afcce0ab9fcebd6a7b0c97a683e Mon Sep 17 00:00:00 2001 From: Artur K <96597+nemerle@users.noreply.github.com> Date: Thu, 5 Aug 2021 23:04:19 +0200 Subject: [PATCH 50/51] Fix memory leak in ProcessLauncher::LaunchProcess (#2823) Inner scope numEnvironmentVars was shadowing the outer scope, and prevented env variable memory from being freed. Signed-off-by: nemerle <96597+nemerle@users.noreply.github.com> --- .../Platform/Linux/AzFramework/Process/ProcessWatcher_Linux.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Code/Framework/AzFramework/Platform/Linux/AzFramework/Process/ProcessWatcher_Linux.cpp b/Code/Framework/AzFramework/Platform/Linux/AzFramework/Process/ProcessWatcher_Linux.cpp index 51c256a9b3..8deed820d3 100644 --- a/Code/Framework/AzFramework/Platform/Linux/AzFramework/Process/ProcessWatcher_Linux.cpp +++ b/Code/Framework/AzFramework/Platform/Linux/AzFramework/Process/ProcessWatcher_Linux.cpp @@ -269,7 +269,7 @@ namespace AzFramework int numEnvironmentVars = 0; if (processLaunchInfo.m_environmentVariables) { - const int numEnvironmentVars = processLaunchInfo.m_environmentVariables->size(); + numEnvironmentVars = processLaunchInfo.m_environmentVariables->size(); // Adding one more as exec expects the array to have a nullptr as the last element environmentVariables = new char*[numEnvironmentVars + 1]; for (int i = 0; i < numEnvironmentVars; i++) From 7448bccea352b3a6150f9c62b11ff0b9fca836d4 Mon Sep 17 00:00:00 2001 From: Artur K <96597+nemerle@users.noreply.github.com> Date: Thu, 5 Aug 2021 23:10:08 +0200 Subject: [PATCH 51/51] Bunch of small bug fixes (#2813) * fix an error with addr_impl_ref assignment operator Signed-off-by: nemerle <96597+nemerle@users.noreply.github.com> * chrono duration unary '+' was missing a return Signed-off-by: nemerle <96597+nemerle@users.noreply.github.com> * HierarchyMenu constructor logic fix Signed-off-by: nemerle <96597+nemerle@users.noreply.github.com> * at least assert in case of invalid arguments to ring_buffer::insert Signed-off-by: nemerle <96597+nemerle@users.noreply.github.com> * EditorSettings using incorrect string_view::find result comparison Signed-off-by: nemerle <96597+nemerle@users.noreply.github.com> --- Code/Editor/Settings.cpp | 4 ++-- Code/Framework/AzCore/AzCore/std/chrono/types.h | 2 +- Code/Framework/AzCore/AzCore/std/containers/ring_buffer.h | 2 +- Code/Framework/AzCore/AzCore/std/utils.h | 2 +- Gems/LyShine/Code/Editor/HierarchyMenu.cpp | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Code/Editor/Settings.cpp b/Code/Editor/Settings.cpp index ad9996adcd..eef2ca0461 100644 --- a/Code/Editor/Settings.cpp +++ b/Code/Editor/Settings.cpp @@ -1088,7 +1088,7 @@ void SEditorSettings::ConvertPath(const AZStd::string_view sourcePath, AZStd::st AzToolsFramework::EditorSettingsAPIRequests::SettingOutcome SEditorSettings::GetValue(const AZStd::string_view path) { - if (path.find("|") < 0) + if (path.find("|") == AZStd::string_view::npos) { return { AZStd::string("Invalid Path - could not find separator \"|\"") }; } @@ -1106,7 +1106,7 @@ AzToolsFramework::EditorSettingsAPIRequests::SettingOutcome SEditorSettings::Get AzToolsFramework::EditorSettingsAPIRequests::SettingOutcome SEditorSettings::SetValue(const AZStd::string_view path, const AZStd::any& value) { - if (path.find("|") < 0) + if (path.find("|") == AZStd::string_view::npos) { return { AZStd::string("Invalid Path - could not find separator \"|\"") }; } diff --git a/Code/Framework/AzCore/AzCore/std/chrono/types.h b/Code/Framework/AzCore/AzCore/std/chrono/types.h index c86c684426..19ef2c8469 100644 --- a/Code/Framework/AzCore/AzCore/std/chrono/types.h +++ b/Code/Framework/AzCore/AzCore/std/chrono/types.h @@ -211,7 +211,7 @@ namespace AZStd // 20.9.3.2, observer: constexpr rep count() const { return m_rep; } // 20.9.3.3, arithmetic: - constexpr duration operator+() const { *this; } + constexpr duration operator+() const { return *this; } constexpr duration operator-() const { return duration(-m_rep); } constexpr duration& operator++() { ++m_rep; return *this; } constexpr duration operator++(int) { return duration(m_rep++); } diff --git a/Code/Framework/AzCore/AzCore/std/containers/ring_buffer.h b/Code/Framework/AzCore/AzCore/std/containers/ring_buffer.h index 746af8974e..31fbdd85e9 100644 --- a/Code/Framework/AzCore/AzCore/std/containers/ring_buffer.h +++ b/Code/Framework/AzCore/AzCore/std/containers/ring_buffer.h @@ -1056,7 +1056,7 @@ namespace AZStd inline void insert(const iterator& pos, ForwardIterator first, ForwardIterator last, const AZStd::forward_iterator_tag&) { size_type size = AZStd::distance(first, last); - AZSTD_CONTAINER_ASSERT(size >= 0, "AZStd::ring_buffer::insert - there are no elements to insert!"); + AZSTD_CONTAINER_ASSERT(first > last, "AZStd::ring_buffer::insert - there are no elements to insert!"); if (size == 0) { return; diff --git a/Code/Framework/AzCore/AzCore/std/utils.h b/Code/Framework/AzCore/AzCore/std/utils.h index 2d098af55e..0de56cedcb 100644 --- a/Code/Framework/AzCore/AzCore/std/utils.h +++ b/Code/Framework/AzCore/AzCore/std/utils.h @@ -294,7 +294,7 @@ namespace AZStd T& m_v; constexpr addr_impl_ref(T& v) : m_v(v) {} - constexpr addr_impl_ref& operator=(const addr_impl_ref& v) { m_v = v; } + constexpr addr_impl_ref& operator=(const addr_impl_ref& v) { m_v = v; return *this; } constexpr operator T& () const { return m_v; } }; diff --git a/Gems/LyShine/Code/Editor/HierarchyMenu.cpp b/Gems/LyShine/Code/Editor/HierarchyMenu.cpp index f4e1776440..ab0c250434 100644 --- a/Gems/LyShine/Code/Editor/HierarchyMenu.cpp +++ b/Gems/LyShine/Code/Editor/HierarchyMenu.cpp @@ -34,7 +34,7 @@ HierarchyMenu::HierarchyMenu(HierarchyWidget* hierarchy, New_EmptyElement(hierarchy, selectedItems, menu, (showMask & Show::kNew_EmptyElementAtRoot), optionalPos); } - if (showMask & Show::kNew_InstantiateSlice | Show::kNew_InstantiateSliceAtRoot) + if (showMask & (Show::kNew_InstantiateSlice | Show::kNew_InstantiateSliceAtRoot)) { New_ElementFromSlice(hierarchy, selectedItems, menu, (showMask & Show::kNew_InstantiateSliceAtRoot), optionalPos); }