From a71a5746ccd6734a68b8c592cab757879993301f Mon Sep 17 00:00:00 2001 From: sphrose <82213493+sphrose@users.noreply.github.com> Date: Tue, 13 Apr 2021 17:15:10 +0100 Subject: [PATCH 01/69] [LY-113714] Jira: LY-113714 https://jira.agscollab.com/browse/LY-113714 --- .../Serialization/EditContextConstants.inl | 2 + .../Components/img/UI20/line.svg | 7 ++++ .../AzQtComponents/Components/resources.qrc | 1 + .../UI/PropertyEditor/PropertyRowWidget.cpp | 37 ++++++++++++++++++- .../UI/PropertyEditor/PropertyRowWidget.hxx | 8 ++++ Code/Sandbox/Editor/Style/Editor.qss | 5 +++ Gems/Vegetation/Code/Source/Descriptor.cpp | 2 + 7 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 Code/Framework/AzQtComponents/AzQtComponents/Components/img/UI20/line.svg diff --git a/Code/Framework/AzCore/AzCore/Serialization/EditContextConstants.inl b/Code/Framework/AzCore/AzCore/Serialization/EditContextConstants.inl index d4658d4e8c..de67013741 100644 --- a/Code/Framework/AzCore/AzCore/Serialization/EditContextConstants.inl +++ b/Code/Framework/AzCore/AzCore/Serialization/EditContextConstants.inl @@ -62,6 +62,8 @@ namespace AZ const static AZ::Crc32 ButtonTooltip = AZ_CRC("ButtonTooltip", 0x1605a7d2); const static AZ::Crc32 CheckboxTooltip = AZ_CRC("CheckboxTooltip", 0x1159eb78); const static AZ::Crc32 CheckboxDefaultValue = AZ_CRC("CheckboxDefaultValue", 0x03f117e6); + //! Emboldens the text and adds a line above this item within the RPE. + const static AZ::Crc32 RPESectionSeparator = AZ_CRC("RPESectionSeparator", 0xc6249a95); //! Affects the display order of a node relative to it's parent/children. Higher values display further down (after) lower values. Default is 0, negative values are allowed. Must be applied as an attribute to the EditorData element const static AZ::Crc32 DisplayOrder = AZ_CRC("DisplayOrder", 0x23660ec2); //! Specifies whether the UI should support multi-edit for aggregate instances of this property diff --git a/Code/Framework/AzQtComponents/AzQtComponents/Components/img/UI20/line.svg b/Code/Framework/AzQtComponents/AzQtComponents/Components/img/UI20/line.svg new file mode 100644 index 0000000000..60f7c07c8d --- /dev/null +++ b/Code/Framework/AzQtComponents/AzQtComponents/Components/img/UI20/line.svg @@ -0,0 +1,7 @@ + + + line + + + + \ No newline at end of file diff --git a/Code/Framework/AzQtComponents/AzQtComponents/Components/resources.qrc b/Code/Framework/AzQtComponents/AzQtComponents/Components/resources.qrc index 77dead96a1..decc1bd72f 100644 --- a/Code/Framework/AzQtComponents/AzQtComponents/Components/resources.qrc +++ b/Code/Framework/AzQtComponents/AzQtComponents/Components/resources.qrc @@ -627,6 +627,7 @@ img/UI20/Settings.svg img/UI20/Asset_Folder.svg img/UI20/Asset_File.svg + img/UI20/line.svg img/UI20/AssetEditor/default_document.svg diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.cpp index 8c371baf8c..a16c1f7480 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.cpp @@ -44,6 +44,24 @@ namespace AzToolsFramework m_iconOpen = s_iconOpen; m_iconClosed = s_iconClosed; + m_outerLayout = new QVBoxLayout(nullptr); + m_outerLayout->setSpacing(0); + m_outerLayout->setContentsMargins(0, 0, 0, 0); + + // separatorLayout will contain a spacer and a separator line. The width of the spacer is adjusted later to ensure the line is the + // correct length. + QHBoxLayout* separatorLayout = new QHBoxLayout(nullptr); + m_outerLayout->addLayout(separatorLayout); + + m_separatorIndent = new QSpacerItem(1, 1); + separatorLayout->addItem(m_separatorIndent); + + m_separatorLine.load(QStringLiteral(":/Gallery/line.svg")); + m_separatorLine.setFixedHeight(3); + + separatorLayout->addWidget(&m_separatorLine); + m_separatorLine.setVisible(false); + m_mainLayout = new QHBoxLayout(); m_mainLayout->setSpacing(0); m_mainLayout->setContentsMargins(0, 1, 0, 1); @@ -118,7 +136,8 @@ namespace AzToolsFramework m_handler = nullptr; m_containerSize = 0; - setLayout(m_mainLayout); + m_outerLayout->addLayout(m_mainLayout); + setLayout(m_outerLayout); } bool PropertyRowWidget::HasChildWidgetAlready() const @@ -301,6 +320,9 @@ namespace AzToolsFramework } } + m_isSectionSeparator = false; + m_separatorLine.setVisible(false); + RefreshAttributesFromNode(true); // --------------------- HANDLER discovery: @@ -946,6 +968,11 @@ namespace AzToolsFramework { HandleChangeNotifyAttribute(reader, m_sourceNode ? m_sourceNode->GetParent() : nullptr, m_editingCompleteNotifiers); } + else if (attributeName == AZ::Edit::Attributes::RPESectionSeparator) + { + m_separatorLine.setVisible(true); + m_isSectionSeparator = true; + } } void PropertyRowWidget::SetReadOnlyQueryFunction(const ReadOnlyQueryFunction& readOnlyQueryFunction) @@ -1070,6 +1097,7 @@ namespace AzToolsFramework { m_dropDownArrow->hide(); } + m_separatorIndent->changeSize((m_treeDepth * m_treeIndentation) + m_leafIndentation, 1, QSizePolicy::Fixed, QSizePolicy::Fixed); m_indent->changeSize((m_treeDepth * m_treeIndentation) + m_leafIndentation, 1, QSizePolicy::Fixed, QSizePolicy::Fixed); m_leftHandSideLayout->invalidate(); m_leftHandSideLayout->update(); @@ -1085,6 +1113,7 @@ namespace AzToolsFramework connect(m_dropDownArrow, &QCheckBox::clicked, this, &PropertyRowWidget::OnClickedExpansionButton); } m_dropDownArrow->show(); + m_separatorIndent->changeSize((m_treeDepth * m_treeIndentation), 1, QSizePolicy::Fixed, QSizePolicy::Fixed); m_indent->changeSize((m_treeDepth * m_treeIndentation), 1, QSizePolicy::Fixed, QSizePolicy::Fixed); m_leftHandSideLayout->invalidate(); m_leftHandSideLayout->update(); @@ -1095,6 +1124,7 @@ namespace AzToolsFramework void PropertyRowWidget::SetIndentSize(int w) { + m_separatorIndent->changeSize(w, 1, QSizePolicy::Fixed, QSizePolicy::Fixed); m_indent->changeSize(w, 1, QSizePolicy::Fixed, QSizePolicy::Fixed); m_leftHandSideLayout->invalidate(); m_leftHandSideLayout->update(); @@ -1318,6 +1348,11 @@ namespace AzToolsFramework return canBeTopLevel(this); } + bool PropertyRowWidget::IsSectionSeparator() const + { + return m_isSectionSeparator; + } + bool PropertyRowWidget::GetAppendDefaultLabelToName() { return false; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.hxx b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.hxx index e4b538ccdc..af1b68b66f 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.hxx +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.hxx @@ -25,6 +25,7 @@ AZ_PUSH_DISABLE_WARNING(4251, "-Wunknown-warning-option") // class '...' needs t #include #include #include +#include #include AZ_POP_DISABLE_WARNING @@ -44,6 +45,7 @@ namespace AzToolsFramework Q_PROPERTY(bool hasChildRows READ HasChildRows); Q_PROPERTY(bool isTopLevel READ IsTopLevel); Q_PROPERTY(int getLevel READ GetLevel); + Q_PROPERTY(bool isSectionSeparator READ IsSectionSeparator); Q_PROPERTY(bool appendDefaultLabelToName READ GetAppendDefaultLabelToName WRITE AppendDefaultLabelToName) public: AZ_CLASS_ALLOCATOR(PropertyRowWidget, AZ::SystemAllocator, 0) @@ -82,6 +84,7 @@ namespace AzToolsFramework PropertyRowWidget* GetParentRow() const { return m_parentRow; } int GetLevel() const; bool IsTopLevel() const; + bool IsSectionSeparator() const; // Remove the default label and append the text to the name label. bool GetAppendDefaultLabelToName(); @@ -161,6 +164,9 @@ namespace AzToolsFramework QHBoxLayout* m_leftHandSideLayout; QHBoxLayout* m_middleLayout; QHBoxLayout* m_rightHandSideLayout; + QVBoxLayout* m_outerLayout; + QSvgWidget m_separatorLine; + QSpacerItem* m_separatorIndent; QPointer m_dropDownArrow; QPointer m_containerClearButton; @@ -229,6 +235,8 @@ namespace AzToolsFramework int m_treeIndentation = 14; int m_leafIndentation = 16; + bool m_isSectionSeparator = false; + QIcon m_iconOpen; QIcon m_iconClosed; diff --git a/Code/Sandbox/Editor/Style/Editor.qss b/Code/Sandbox/Editor/Style/Editor.qss index 0c3f64b85c..7887560105 100644 --- a/Code/Sandbox/Editor/Style/Editor.qss +++ b/Code/Sandbox/Editor/Style/Editor.qss @@ -38,6 +38,11 @@ AzToolsFramework--ComponentPaletteWidget > QTreeView background-color: #222222; } +AzToolsFramework--PropertyRowWidget[isSectionSeparator="true"] QLabel#Name +{ + font-weight: bold; +} + /* Style for visualizing property values overridden from their prefab values */ AzToolsFramework--PropertyRowWidget[IsOverridden=true] #Name QLabel, AzToolsFramework--ComponentEditorHeader #Title[IsOverridden="true"] diff --git a/Gems/Vegetation/Code/Source/Descriptor.cpp b/Gems/Vegetation/Code/Source/Descriptor.cpp index 93a301f073..4fd1037f09 100644 --- a/Gems/Vegetation/Code/Source/Descriptor.cpp +++ b/Gems/Vegetation/Code/Source/Descriptor.cpp @@ -170,6 +170,8 @@ namespace Vegetation { edit->Class( "Vegetation Descriptor", "Details used to create vegetation instances") + ->ClassElement(AZ::Edit::ClassElements::EditorData, "") + ->Attribute(AZ::Edit::Attributes::RPESectionSeparator, true) // For this ComboBox to actually work, there is a PropertyHandler registration in EditorVegetationSystemComponent.cpp ->DataElement(AZ::Edit::UIHandlers::ComboBox, &Descriptor::m_spawnerType, "Instance Spawner", "The type of instances to spawn") ->Attribute(AZ::Edit::Attributes::GenericValueList, &Descriptor::GetSpawnerTypeList) From a94700786133526d14971bddfdd87e5d989d8678 Mon Sep 17 00:00:00 2001 From: sphrose <82213493+sphrose@users.noreply.github.com> Date: Wed, 14 Apr 2021 15:15:42 +0100 Subject: [PATCH 02/69] Darken line --- .../AzQtComponents/AzQtComponents/Components/img/UI20/line.svg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Code/Framework/AzQtComponents/AzQtComponents/Components/img/UI20/line.svg b/Code/Framework/AzQtComponents/AzQtComponents/Components/img/UI20/line.svg index 60f7c07c8d..fe01efddba 100644 --- a/Code/Framework/AzQtComponents/AzQtComponents/Components/img/UI20/line.svg +++ b/Code/Framework/AzQtComponents/AzQtComponents/Components/img/UI20/line.svg @@ -2,6 +2,6 @@ line - + \ No newline at end of file From 2dbd9e4a050e141a45c85a12b4d245f0695f581a Mon Sep 17 00:00:00 2001 From: sphrose <82213493+sphrose@users.noreply.github.com> Date: Mon, 19 Apr 2021 15:54:41 +0100 Subject: [PATCH 03/69] Changed to use direct line drawing rather than adding svg. --- .../Components/img/UI20/line.svg | 7 ---- .../UI/PropertyEditor/PropertyRowWidget.cpp | 40 +++++++------------ .../UI/PropertyEditor/PropertyRowWidget.hxx | 4 +- 3 files changed, 16 insertions(+), 35 deletions(-) delete mode 100644 Code/Framework/AzQtComponents/AzQtComponents/Components/img/UI20/line.svg diff --git a/Code/Framework/AzQtComponents/AzQtComponents/Components/img/UI20/line.svg b/Code/Framework/AzQtComponents/AzQtComponents/Components/img/UI20/line.svg deleted file mode 100644 index fe01efddba..0000000000 --- a/Code/Framework/AzQtComponents/AzQtComponents/Components/img/UI20/line.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - line - - - - \ No newline at end of file diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.cpp index a16c1f7480..5b02e81e6d 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.cpp @@ -27,6 +27,7 @@ AZ_PUSH_DISABLE_WARNING(4244 4251 4800, "-Wunknown-warning-option") // 4244: con #include #include #include +#include AZ_POP_DISABLE_WARNING static const int LabelColumnStretch = 2; @@ -44,24 +45,6 @@ namespace AzToolsFramework m_iconOpen = s_iconOpen; m_iconClosed = s_iconClosed; - m_outerLayout = new QVBoxLayout(nullptr); - m_outerLayout->setSpacing(0); - m_outerLayout->setContentsMargins(0, 0, 0, 0); - - // separatorLayout will contain a spacer and a separator line. The width of the spacer is adjusted later to ensure the line is the - // correct length. - QHBoxLayout* separatorLayout = new QHBoxLayout(nullptr); - m_outerLayout->addLayout(separatorLayout); - - m_separatorIndent = new QSpacerItem(1, 1); - separatorLayout->addItem(m_separatorIndent); - - m_separatorLine.load(QStringLiteral(":/Gallery/line.svg")); - m_separatorLine.setFixedHeight(3); - - separatorLayout->addWidget(&m_separatorLine); - m_separatorLine.setVisible(false); - m_mainLayout = new QHBoxLayout(); m_mainLayout->setSpacing(0); m_mainLayout->setContentsMargins(0, 1, 0, 1); @@ -136,8 +119,20 @@ namespace AzToolsFramework m_handler = nullptr; m_containerSize = 0; - m_outerLayout->addLayout(m_mainLayout); - setLayout(m_outerLayout); + setLayout(m_mainLayout); + } + + void PropertyRowWidget::paintEvent(QPaintEvent* event) + { + QStylePainter p(this); + + if (IsSectionSeparator()) + { + const QPen linePen(QColor(0x3B3E3F)); + p.setPen(linePen); + int indent = m_treeDepth * m_treeIndentation; + p.drawLine(event->rect().topLeft() + QPoint(indent, 0), event->rect().topRight()); + } } bool PropertyRowWidget::HasChildWidgetAlready() const @@ -321,7 +316,6 @@ namespace AzToolsFramework } m_isSectionSeparator = false; - m_separatorLine.setVisible(false); RefreshAttributesFromNode(true); @@ -970,7 +964,6 @@ namespace AzToolsFramework } else if (attributeName == AZ::Edit::Attributes::RPESectionSeparator) { - m_separatorLine.setVisible(true); m_isSectionSeparator = true; } } @@ -1097,7 +1090,6 @@ namespace AzToolsFramework { m_dropDownArrow->hide(); } - m_separatorIndent->changeSize((m_treeDepth * m_treeIndentation) + m_leafIndentation, 1, QSizePolicy::Fixed, QSizePolicy::Fixed); m_indent->changeSize((m_treeDepth * m_treeIndentation) + m_leafIndentation, 1, QSizePolicy::Fixed, QSizePolicy::Fixed); m_leftHandSideLayout->invalidate(); m_leftHandSideLayout->update(); @@ -1113,7 +1105,6 @@ namespace AzToolsFramework connect(m_dropDownArrow, &QCheckBox::clicked, this, &PropertyRowWidget::OnClickedExpansionButton); } m_dropDownArrow->show(); - m_separatorIndent->changeSize((m_treeDepth * m_treeIndentation), 1, QSizePolicy::Fixed, QSizePolicy::Fixed); m_indent->changeSize((m_treeDepth * m_treeIndentation), 1, QSizePolicy::Fixed, QSizePolicy::Fixed); m_leftHandSideLayout->invalidate(); m_leftHandSideLayout->update(); @@ -1124,7 +1115,6 @@ namespace AzToolsFramework void PropertyRowWidget::SetIndentSize(int w) { - m_separatorIndent->changeSize(w, 1, QSizePolicy::Fixed, QSizePolicy::Fixed); m_indent->changeSize(w, 1, QSizePolicy::Fixed, QSizePolicy::Fixed); m_leftHandSideLayout->invalidate(); m_leftHandSideLayout->update(); diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.hxx b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.hxx index af1b68b66f..d08779caa4 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.hxx +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.hxx @@ -129,6 +129,7 @@ namespace AzToolsFramework void SetSelectionEnabled(bool selectionEnabled); void SetSelected(bool selected); bool eventFilter(QObject *watched, QEvent *event) override; + void paintEvent(QPaintEvent*) override; /// Apply tooltip to widget and some of its children. void SetDescription(const QString& text); @@ -164,9 +165,6 @@ namespace AzToolsFramework QHBoxLayout* m_leftHandSideLayout; QHBoxLayout* m_middleLayout; QHBoxLayout* m_rightHandSideLayout; - QVBoxLayout* m_outerLayout; - QSvgWidget m_separatorLine; - QSpacerItem* m_separatorIndent; QPointer m_dropDownArrow; QPointer m_containerClearButton; From b1d8330870f31399d05aa0c1d1b28e5f55512580 Mon Sep 17 00:00:00 2001 From: sphrose <82213493+sphrose@users.noreply.github.com> Date: Tue, 20 Apr 2021 17:20:19 +0100 Subject: [PATCH 04/69] Review fixes. --- .../AzQtComponents/AzQtComponents/Components/resources.qrc | 1 - .../AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.hxx | 1 - 2 files changed, 2 deletions(-) diff --git a/Code/Framework/AzQtComponents/AzQtComponents/Components/resources.qrc b/Code/Framework/AzQtComponents/AzQtComponents/Components/resources.qrc index e253c6492d..f9e601fb6d 100644 --- a/Code/Framework/AzQtComponents/AzQtComponents/Components/resources.qrc +++ b/Code/Framework/AzQtComponents/AzQtComponents/Components/resources.qrc @@ -630,7 +630,6 @@ img/UI20/Settings.svg img/UI20/Asset_Folder.svg img/UI20/Asset_File.svg - img/UI20/line.svg img/UI20/AssetEditor/default_document.svg diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.hxx b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.hxx index d08779caa4..2fb695fca5 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.hxx +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.hxx @@ -25,7 +25,6 @@ AZ_PUSH_DISABLE_WARNING(4251, "-Wunknown-warning-option") // class '...' needs t #include #include #include -#include #include AZ_POP_DISABLE_WARNING From 2f4120cdfbbd736d18fdc5f3187a94f6640ed28b Mon Sep 17 00:00:00 2001 From: puvvadar Date: Mon, 3 May 2021 13:07:11 -0700 Subject: [PATCH 05/69] Update Ctrl+G logic to account for prefab processing status and timing --- .../PrefabEditorEntityOwnershipInterface.h | 2 + .../PrefabEditorEntityOwnershipService.cpp | 5 ++ .../PrefabEditorEntityOwnershipService.h | 2 + Gems/Multiplayer/Code/CMakeLists.txt | 1 + .../Code/Include/IMultiplayerTools.h | 39 ++++++++++++ .../MultiplayerEditorSystemComponent.cpp | 63 ++++++++++++------- .../Editor/MultiplayerEditorSystemComponent.h | 12 +++- .../Code/Source/MultiplayerToolsModule.cpp | 35 ++++------- .../Code/Source/MultiplayerToolsModule.h | 27 ++++++++ .../Pipeline/NetworkPrefabProcessor.cpp | 3 + .../Code/multiplayer_tools_files.cmake | 1 + 11 files changed, 142 insertions(+), 48 deletions(-) create mode 100644 Gems/Multiplayer/Code/Include/IMultiplayerTools.h diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/PrefabEditorEntityOwnershipInterface.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/PrefabEditorEntityOwnershipInterface.h index 19c236f509..4476876b59 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/PrefabEditorEntityOwnershipInterface.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/PrefabEditorEntityOwnershipInterface.h @@ -46,6 +46,8 @@ namespace AzToolsFramework virtual Prefab::InstanceOptionalReference GetRootPrefabInstance() = 0; + virtual const AZStd::vector>& GetPlayInEditorAssetData() = 0; + virtual bool LoadFromStream(AZ::IO::GenericStream& stream, AZStd::string_view filename) = 0; virtual bool SaveToStream(AZ::IO::GenericStream& stream, AZStd::string_view filename) = 0; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/PrefabEditorEntityOwnershipService.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/PrefabEditorEntityOwnershipService.cpp index 81233069a9..e81d6bf08e 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/PrefabEditorEntityOwnershipService.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/PrefabEditorEntityOwnershipService.cpp @@ -321,6 +321,11 @@ namespace AzToolsFramework return *m_rootInstance; } + const AZStd::vector>& PrefabEditorEntityOwnershipService::GetPlayInEditorAssetData() + { + return m_playInEditorData.m_assets; + } + void PrefabEditorEntityOwnershipService::OnEntityRemoved(AZ::EntityId entityId) { AzFramework::SliceEntityRequestBus::MultiHandler::BusDisconnect(entityId); diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/PrefabEditorEntityOwnershipService.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/PrefabEditorEntityOwnershipService.h index 9c483e61c5..cf62220e67 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/PrefabEditorEntityOwnershipService.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/PrefabEditorEntityOwnershipService.h @@ -195,6 +195,8 @@ namespace AzToolsFramework AZ::IO::PathView filePath, Prefab::InstanceOptionalReference instanceToParentUnder) override; Prefab::InstanceOptionalReference GetRootPrefabInstance() override; + + const AZStd::vector>& GetPlayInEditorAssetData() override; ////////////////////////////////////////////////////////////////////////// void OnEntityRemoved(AZ::EntityId entityId); diff --git a/Gems/Multiplayer/Code/CMakeLists.txt b/Gems/Multiplayer/Code/CMakeLists.txt index 4eeee15c47..46f56ef315 100644 --- a/Gems/Multiplayer/Code/CMakeLists.txt +++ b/Gems/Multiplayer/Code/CMakeLists.txt @@ -119,6 +119,7 @@ if (PAL_TRAIT_BUILD_HOST_TOOLS) BUILD_DEPENDENCIES PRIVATE Gem::Multiplayer.Editor.Static + Gem::Multiplayer.Tools ) endif() diff --git a/Gems/Multiplayer/Code/Include/IMultiplayerTools.h b/Gems/Multiplayer/Code/Include/IMultiplayerTools.h new file mode 100644 index 0000000000..c621808f7a --- /dev/null +++ b/Gems/Multiplayer/Code/Include/IMultiplayerTools.h @@ -0,0 +1,39 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#pragma once + +#include + +namespace Multiplayer +{ + //! IMultiplayer provides insight into the Multiplayer session and its Agents + class IMultiplayerTools + { + public: + // NetworkPrefabProcessor is the only class that should be setting process network prefab status + friend class NetworkPrefabProcessor; + + AZ_RTTI(IMultiplayerTools, "{E8A80EAB-29CB-4E3B-A0B2-FFCB37060FB0}"); + + virtual ~IMultiplayerTools() = default; + + //! Returns if network prefab processing has created currently active or pending spawnables + //! @return If network prefab processing has created currently active or pending spawnables + virtual bool DidProcessNetworkPrefabs() = 0; + + private: + //! Sets if network prefab processing has created currently active or pending spawnables + //! @param didProcessNetPrefabs if network prefab processing has created currently active or pending spawnables + virtual void SetDidProcessNetworkPrefabs(bool didProcessNetPrefabs) = 0; + }; +} diff --git a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp index aec3e7870f..0850b858a2 100644 --- a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp @@ -10,12 +10,14 @@ * */ +#include #include #include #include #include #include #include +#include namespace Multiplayer { @@ -57,12 +59,14 @@ namespace Multiplayer void MultiplayerEditorSystemComponent::Activate() { + AzFramework::GameEntityContextEventBus::Handler::BusConnect(); AzToolsFramework::EditorEvents::Bus::Handler::BusConnect(); } void MultiplayerEditorSystemComponent::Deactivate() { AzToolsFramework::EditorEvents::Bus::Handler::BusDisconnect(); + AzFramework::GameEntityContextEventBus::Handler::BusDisconnect(); } void MultiplayerEditorSystemComponent::NotifyRegisterViews() @@ -77,11 +81,42 @@ namespace Multiplayer { switch (event) { - case eNotify_OnBeginGameMode: - { + case eNotify_OnQuit: + AZ_Warning("Multiplayer Editor", m_editor != nullptr, "Multiplayer Editor received On Quit without an Editor pointer."); + if (m_editor) + { + m_editor->UnregisterNotifyListener(this); + m_editor = nullptr; + } + [[fallthrough]]; + case eNotify_OnEndGameMode: + AZ::TickBus::Handler::BusDisconnect(); + // Kill the configured server if it's active + if (m_serverProcess) + { + m_serverProcess->TerminateProcess(0); + m_serverProcess = nullptr; + } + break; + } + } + + void MultiplayerEditorSystemComponent::OnGameEntitiesStarted() + { + // BeginGameMode and Prefab Processing have completed at this point + IMultiplayerTools* mpTools = AZ::Interface::Get(); + if (editorsv_enabled && mpTools != nullptr && mpTools->DidProcessNetworkPrefabs()) + { AZ::TickBus::Handler::BusConnect(); - if (editorsv_enabled) + auto prefabEditorEntityOwnershipInterface = AZ::Interface::Get(); + if (!prefabEditorEntityOwnershipInterface) + { + AZ_Error("MultiplayerEditor", prefabEditorEntityOwnershipInterface != nullptr, "PrefabEditorEntityOwnershipInterface unavailable"); + } + const AZStd::vector>& assetData = prefabEditorEntityOwnershipInterface->GetPlayInEditorAssetData(); + + if (assetData.size() > 0) { // Assemble the server's path AZ::CVarFixedString serverProcess = editorsv_process; @@ -111,33 +146,13 @@ namespace Multiplayer // Start the configured server if it's available AzFramework::ProcessLauncher::ProcessLaunchInfo processLaunchInfo; - processLaunchInfo.m_commandlineParameters = - AZStd::string::format("\"%s\"", serverPath.c_str()); + processLaunchInfo.m_commandlineParameters = AZStd::string::format("\"%s\"", serverPath.c_str()); processLaunchInfo.m_showWindow = true; processLaunchInfo.m_processPriority = AzFramework::ProcessPriority::PROCESSPRIORITY_NORMAL; m_serverProcess = AzFramework::ProcessWatcher::LaunchProcess( processLaunchInfo, AzFramework::ProcessCommunicationType::COMMUNICATOR_TYPE_NONE); } - break; - } - case eNotify_OnQuit: - AZ_Warning("Multiplayer Editor", m_editor != nullptr, "Multiplayer Editor received On Quit without an Editor pointer."); - if (m_editor) - { - m_editor->UnregisterNotifyListener(this); - m_editor = nullptr; - } - [[fallthrough]]; - case eNotify_OnEndGameMode: - AZ::TickBus::Handler::BusDisconnect(); - // Kill the configured server if it's active - if (m_serverProcess) - { - m_serverProcess->TerminateProcess(0); - m_serverProcess = nullptr; - } - break; } } diff --git a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.h b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.h index 8c18a2e57a..31ecdf83a3 100644 --- a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.h +++ b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.h @@ -19,6 +19,7 @@ #include #include +#include #include #include @@ -34,6 +35,7 @@ namespace Multiplayer class MultiplayerEditorSystemComponent final : public AZ::Component , private AZ::TickBus::Handler + , private AzFramework::GameEntityContextEventBus::Handler , private AzToolsFramework::EditorEvents::Bus::Handler , private IEditorNotifyListener { @@ -66,8 +68,16 @@ namespace Multiplayer void OnTick(float deltaTime, AZ::ScriptTimePoint time) override; int GetTickOrder() override; //! @} - //! + + //! EditorEvents::Handler overrides + //! @{ void OnEditorNotifyEvent(EEditorNotifyEvent event) override; + //! @} + + //! GameEntityContextEventBus::Handler overrides + //! @{ + void OnGameEntitiesStarted() override; + //! @} IEditor* m_editor = nullptr; AzFramework::ProcessWatcher* m_serverProcess = nullptr; diff --git a/Gems/Multiplayer/Code/Source/MultiplayerToolsModule.cpp b/Gems/Multiplayer/Code/Source/MultiplayerToolsModule.cpp index 5a223d6214..a5df3c1dc5 100644 --- a/Gems/Multiplayer/Code/Source/MultiplayerToolsModule.cpp +++ b/Gems/Multiplayer/Code/Source/MultiplayerToolsModule.cpp @@ -18,32 +18,21 @@ namespace Multiplayer { - //! Multiplayer Tools system component provides serialize context reflection for tools-only systems. - class MultiplayerToolsSystemComponent final - : public AZ::Component - { - public: - AZ_COMPONENT(MultiplayerToolsSystemComponent, "{65AF5342-0ECE-423B-B646-AF55A122F72B}"); - - static void Reflect(AZ::ReflectContext* context) - { - NetworkPrefabProcessor::Reflect(context); - } - - MultiplayerToolsSystemComponent() = default; - ~MultiplayerToolsSystemComponent() override = default; - /// AZ::Component overrides. - void Activate() override - { - - } + void MultiplayerToolsSystemComponent::Reflect(AZ::ReflectContext* context) + { + NetworkPrefabProcessor::Reflect(context); + } - void Deactivate() override - { + bool MultiplayerToolsSystemComponent::DidProcessNetworkPrefabs() + { + return m_didProcessNetPrefabs; + } - } - }; + void MultiplayerToolsSystemComponent::SetDidProcessNetworkPrefabs(bool didProcessNetPrefabs) + { + m_didProcessNetPrefabs = didProcessNetPrefabs; + } MultiplayerToolsModule::MultiplayerToolsModule() : AZ::Module() diff --git a/Gems/Multiplayer/Code/Source/MultiplayerToolsModule.h b/Gems/Multiplayer/Code/Source/MultiplayerToolsModule.h index 823bd63a1d..82d0415c5a 100644 --- a/Gems/Multiplayer/Code/Source/MultiplayerToolsModule.h +++ b/Gems/Multiplayer/Code/Source/MultiplayerToolsModule.h @@ -12,10 +12,37 @@ #pragma once +#include #include +#include namespace Multiplayer { + class MultiplayerToolsSystemComponent final + : public AZ::Component + , public IMultiplayerTools + { + public: + AZ_COMPONENT(MultiplayerToolsSystemComponent, "{65AF5342-0ECE-423B-B646-AF55A122F72B}"); + + static void Reflect(AZ::ReflectContext* context); + + MultiplayerToolsSystemComponent() = default; + ~MultiplayerToolsSystemComponent() override = default; + + /// AZ::Component overrides. + void Activate() override {}; + + void Deactivate() override {}; + + bool DidProcessNetworkPrefabs() override; + + private: + void SetDidProcessNetworkPrefabs(bool didProcessNetPrefabs) override; + + bool m_didProcessNetPrefabs = false; + }; + class MultiplayerToolsModule : public AZ::Module { diff --git a/Gems/Multiplayer/Code/Source/Pipeline/NetworkPrefabProcessor.cpp b/Gems/Multiplayer/Code/Source/Pipeline/NetworkPrefabProcessor.cpp index 2006272135..8528b3d564 100644 --- a/Gems/Multiplayer/Code/Source/Pipeline/NetworkPrefabProcessor.cpp +++ b/Gems/Multiplayer/Code/Source/Pipeline/NetworkPrefabProcessor.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -29,6 +30,8 @@ namespace Multiplayer void NetworkPrefabProcessor::Process(PrefabProcessorContext& context) { + IMultiplayerTools* mpTools = AZ::Interface::Get(); + mpTools->SetDidProcessNetworkPrefabs(false); context.ListPrefabs([&context](AZStd::string_view prefabName, PrefabDom& prefab) { ProcessPrefab(context, prefabName, prefab); }); diff --git a/Gems/Multiplayer/Code/multiplayer_tools_files.cmake b/Gems/Multiplayer/Code/multiplayer_tools_files.cmake index 1be02fd999..12f12479ba 100644 --- a/Gems/Multiplayer/Code/multiplayer_tools_files.cmake +++ b/Gems/Multiplayer/Code/multiplayer_tools_files.cmake @@ -10,6 +10,7 @@ # set(FILES + Include/IMultiplayerTools.h Source/Multiplayer_precompiled.cpp Source/Multiplayer_precompiled.h Source/Pipeline/NetworkPrefabProcessor.cpp From 7eb6cc10b6559511927028d9f6c5514b973a99bb Mon Sep 17 00:00:00 2001 From: sphrose <82213493+sphrose@users.noreply.github.com> Date: Wed, 5 May 2021 08:57:39 +0100 Subject: [PATCH 06/69] Made change affect all modifiable containers. --- .../Serialization/EditContextConstants.inl | 2 -- .../UI/PropertyEditor/PropertyRowWidget.cpp | 22 +++++++++++++------ .../UI/PropertyEditor/PropertyRowWidget.hxx | 5 +++-- Gems/Vegetation/Code/Source/Descriptor.cpp | 2 -- 4 files changed, 18 insertions(+), 13 deletions(-) diff --git a/Code/Framework/AzCore/AzCore/Serialization/EditContextConstants.inl b/Code/Framework/AzCore/AzCore/Serialization/EditContextConstants.inl index 481ae5a91c..90b9ba5afd 100644 --- a/Code/Framework/AzCore/AzCore/Serialization/EditContextConstants.inl +++ b/Code/Framework/AzCore/AzCore/Serialization/EditContextConstants.inl @@ -62,8 +62,6 @@ namespace AZ const static AZ::Crc32 ButtonTooltip = AZ_CRC("ButtonTooltip", 0x1605a7d2); const static AZ::Crc32 CheckboxTooltip = AZ_CRC("CheckboxTooltip", 0x1159eb78); const static AZ::Crc32 CheckboxDefaultValue = AZ_CRC("CheckboxDefaultValue", 0x03f117e6); - //! Emboldens the text and adds a line above this item within the RPE. - const static AZ::Crc32 RPESectionSeparator = AZ_CRC("RPESectionSeparator", 0xc6249a95); //! Affects the display order of a node relative to it's parent/children. Higher values display further down (after) lower values. Default is 0, negative values are allowed. Must be applied as an attribute to the EditorData element const static AZ::Crc32 DisplayOrder = AZ_CRC("DisplayOrder", 0x23660ec2); //! Specifies whether the UI should support multi-edit for aggregate instances of this property diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.cpp index 5b02e81e6d..faeae0a06d 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.cpp @@ -315,8 +315,6 @@ namespace AzToolsFramework } } - m_isSectionSeparator = false; - RefreshAttributesFromNode(true); // --------------------- HANDLER discovery: @@ -962,10 +960,6 @@ namespace AzToolsFramework { HandleChangeNotifyAttribute(reader, m_sourceNode ? m_sourceNode->GetParent() : nullptr, m_editingCompleteNotifiers); } - else if (attributeName == AZ::Edit::Attributes::RPESectionSeparator) - { - m_isSectionSeparator = true; - } } void PropertyRowWidget::SetReadOnlyQueryFunction(const ReadOnlyQueryFunction& readOnlyQueryFunction) @@ -1340,7 +1334,7 @@ namespace AzToolsFramework bool PropertyRowWidget::IsSectionSeparator() const { - return m_isSectionSeparator; + return CanBeReordered(); } bool PropertyRowWidget::GetAppendDefaultLabelToName() @@ -1686,6 +1680,20 @@ namespace AzToolsFramework m_nameLabel->setFilter(m_currentFilterString); } + bool PropertyRowWidget::CanChildrenBeReordered() const + { + return m_containerEditable; + } + + bool PropertyRowWidget::CanBeReordered() const + { + if (!m_parentRow) + { + return false; + } + + return m_parentRow->CanChildrenBeReordered(); + } } #include "UI/PropertyEditor/moc_PropertyRowWidget.cpp" diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.hxx b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.hxx index 2fb695fca5..b6c94dc98b 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.hxx +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.hxx @@ -149,6 +149,9 @@ namespace AzToolsFramework QLabel* GetNameLabel() { return m_nameLabel; } void SetIndentSize(int w); void SetAsCustom(bool custom) { m_custom = custom; } + + bool CanChildrenBeReordered() const; + bool CanBeReordered() const; protected: int CalculateLabelWidth() const; @@ -232,8 +235,6 @@ namespace AzToolsFramework int m_treeIndentation = 14; int m_leafIndentation = 16; - bool m_isSectionSeparator = false; - QIcon m_iconOpen; QIcon m_iconClosed; diff --git a/Gems/Vegetation/Code/Source/Descriptor.cpp b/Gems/Vegetation/Code/Source/Descriptor.cpp index 45ec25038d..ecda8415e7 100644 --- a/Gems/Vegetation/Code/Source/Descriptor.cpp +++ b/Gems/Vegetation/Code/Source/Descriptor.cpp @@ -170,8 +170,6 @@ namespace Vegetation { edit->Class( "Vegetation Descriptor", "Details used to create vegetation instances") - ->ClassElement(AZ::Edit::ClassElements::EditorData, "") - ->Attribute(AZ::Edit::Attributes::RPESectionSeparator, true) // For this ComboBox to actually work, there is a PropertyHandler registration in EditorVegetationSystemComponent.cpp ->DataElement(AZ::Edit::UIHandlers::ComboBox, &Descriptor::m_spawnerType, "Instance Spawner", "The type of instances to spawn") ->Attribute(AZ::Edit::Attributes::GenericValueList, &Descriptor::GetSpawnerTypeList) From aa51233536a55816324da9d41fff6afe4e3970c9 Mon Sep 17 00:00:00 2001 From: puvvadar Date: Thu, 6 May 2021 16:18:13 -0700 Subject: [PATCH 07/69] Add Asset serialization for Ctrl+G and related net interfaces --- .../AzNetworking/TcpTransport/TcpSocket.cpp | 4 +- .../AutoGen/Multiplayer.AutoPackets.xml | 6 ++ .../MultiplayerEditorSystemComponent.cpp | 93 +++++++++++++++++-- .../Editor/MultiplayerEditorSystemComponent.h | 2 + .../Source/MultiplayerSystemComponent.cpp | 21 +++++ .../Code/Source/MultiplayerSystemComponent.h | 4 +- .../Code/Source/MultiplayerToolsModule.cpp | 10 ++ .../Code/Source/MultiplayerToolsModule.h | 5 +- 8 files changed, 132 insertions(+), 13 deletions(-) diff --git a/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpSocket.cpp b/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpSocket.cpp index 78020cb09d..e8c30d0816 100644 --- a/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpSocket.cpp +++ b/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpSocket.cpp @@ -116,8 +116,8 @@ namespace AzNetworking int32_t TcpSocket::Receive(uint8_t* outData, uint32_t size) const { - AZ_Assert(size > 0, "Invalid data size for send"); - AZ_Assert(outData != nullptr, "NULL data pointer passed to send"); + AZ_Assert(size > 0, "Invalid data size for receive"); + AZ_Assert(outData != nullptr, "NULL data pointer passed to receive"); if (!IsOpen()) { return SocketOpResultErrorNotOpen; diff --git a/Gems/Multiplayer/Code/Source/AutoGen/Multiplayer.AutoPackets.xml b/Gems/Multiplayer/Code/Source/AutoGen/Multiplayer.AutoPackets.xml index 5de466899c..21faefa880 100644 --- a/Gems/Multiplayer/Code/Source/AutoGen/Multiplayer.AutoPackets.xml +++ b/Gems/Multiplayer/Code/Source/AutoGen/Multiplayer.AutoPackets.xml @@ -57,4 +57,10 @@ + + + + + + diff --git a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp index 0850b858a2..15f00bdf80 100644 --- a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp @@ -10,23 +10,32 @@ * */ +#include #include +#include +#include #include #include +#include #include #include #include #include +#include #include namespace Multiplayer { + static const AZStd::string_view s_networkInterfaceName("MultiplayerEditorServerInterface"); + using namespace AzNetworking; AZ_CVAR(bool, editorsv_enabled, false, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "Whether Editor launching a local server to connect to is supported"); AZ_CVAR(AZ::CVarFixedString, editorsv_process, "", nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The server executable that should be run. Empty to use the current project's ServerLauncher"); + AZ_CVAR(AZ::CVarFixedString, sv_serveraddr, "127.0.0.1", nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The address of the server to connect to"); + AZ_CVAR(uint16_t, sv_port, 30091, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The port that the multiplayer editor gem will bind to for traffic"); void MultiplayerEditorSystemComponent::Reflect(AZ::ReflectContext* context) { @@ -61,6 +70,16 @@ namespace Multiplayer { AzFramework::GameEntityContextEventBus::Handler::BusConnect(); AzToolsFramework::EditorEvents::Bus::Handler::BusConnect(); + + // Setup a network interface handled by MultiplayerSystemComponent + if (m_editorNetworkInterface == nullptr) + { + AZ::Entity* systemEntity = this->GetEntity(); + MultiplayerSystemComponent* mpSysComponent = systemEntity->FindComponent(); + + m_editorNetworkInterface = AZ::Interface::Get()->CreateNetworkInterface( + AZ::Name(s_networkInterfaceName), ProtocolType::Tcp, TrustZone::ExternalClientToServer, *mpSysComponent); + } } void MultiplayerEditorSystemComponent::Deactivate() @@ -97,25 +116,52 @@ namespace Multiplayer m_serverProcess->TerminateProcess(0); m_serverProcess = nullptr; } + if (m_editorNetworkInterface) + { + // Disconnect the interface, connection management will clean it up + m_editorNetworkInterface->Disconnect(m_editorConnId, AzNetworking::DisconnectReason::TerminatedByUser); + m_editorConnId = AzNetworking::InvalidConnectionId; + } break; } } void MultiplayerEditorSystemComponent::OnGameEntitiesStarted() { + auto prefabEditorEntityOwnershipInterface = AZ::Interface::Get(); + if (!prefabEditorEntityOwnershipInterface) + { + AZ_Error("MultiplayerEditor", prefabEditorEntityOwnershipInterface != nullptr, "PrefabEditorEntityOwnershipInterface unavailable"); + } + const AZStd::vector>& assetData = prefabEditorEntityOwnershipInterface->GetPlayInEditorAssetData(); + + AZStd::vector buffer; + AZ::IO::ByteContainerStream byteStream(&buffer); + + // Serialize Asset information and AssetData into a potentially large buffer + for (auto asset : assetData) + { + AZ::Data::AssetId assetId = asset.GetId(); + AZ::Data::AssetType assetType = asset.GetType(); + const AZStd::string& assetHint = asset.GetHint(); + AZ::IO::SizeType assetHintSize = assetHint.size(); + AZ::Data::AssetLoadBehavior assetLoadBehavior = asset.GetAutoLoadBehavior(); + + byteStream.Write(sizeof(AZ::Data::AssetId), reinterpret_cast(&assetId)); + byteStream.Write(sizeof(AZ::Data::AssetType), reinterpret_cast(&assetType)); + byteStream.Write(sizeof(assetHintSize), reinterpret_cast(&assetHintSize)); + byteStream.Write(assetHint.size(), assetHint.c_str()); + byteStream.Write(sizeof(AZ::Data::AssetLoadBehavior), reinterpret_cast(&assetLoadBehavior)); + + AZ::Utils::SaveObjectToStream(byteStream, AZ::DataStream::ST_BINARY, asset.GetData(), asset.GetData()->GetType()); + } + // BeginGameMode and Prefab Processing have completed at this point IMultiplayerTools* mpTools = AZ::Interface::Get(); if (editorsv_enabled && mpTools != nullptr && mpTools->DidProcessNetworkPrefabs()) { AZ::TickBus::Handler::BusConnect(); - auto prefabEditorEntityOwnershipInterface = AZ::Interface::Get(); - if (!prefabEditorEntityOwnershipInterface) - { - AZ_Error("MultiplayerEditor", prefabEditorEntityOwnershipInterface != nullptr, "PrefabEditorEntityOwnershipInterface unavailable"); - } - const AZStd::vector>& assetData = prefabEditorEntityOwnershipInterface->GetPlayInEditorAssetData(); - if (assetData.size() > 0) { // Assemble the server's path @@ -154,6 +200,39 @@ namespace Multiplayer processLaunchInfo, AzFramework::ProcessCommunicationType::COMMUNICATOR_TYPE_NONE); } } + + // Now that the server has launched, attempt to connect the NetworkInterface + const AZ::CVarFixedString remoteAddress = sv_serveraddr; + m_editorConnId = m_editorNetworkInterface->Connect( + AzNetworking::IpAddress(remoteAddress.c_str(), sv_port, AzNetworking::ProtocolType::Tcp)); + + // Read the buffer into EditorServerInit packets until we've flushed the whole thing + byteStream.Seek(0, AZ::IO::GenericStream::SeekMode::ST_SEEK_BEGIN); + + while (byteStream.GetCurPos() < byteStream.GetLength()) + { + MultiplayerPackets::EditorServerInit packet; + AzNetworking::TcpPacketEncodingBuffer& outBuffer = packet.ModifyAssetData(); + + // Size the packet's buffer appropriately + size_t readSize = TcpPacketEncodingBuffer::GetCapacity(); + size_t byteStreamSize = byteStream.GetLength() - byteStream.GetCurPos(); + if (byteStreamSize < readSize) + { + readSize = byteStreamSize; + } + + outBuffer.Resize(readSize); + byteStream.Read(readSize, outBuffer.GetBuffer()); + + // If we've run out of buffer, mark that we're done + if (byteStream.GetCurPos() == byteStream.GetLength()) + { + packet.SetLastUpdate(true); + } + m_editorNetworkInterface->SendReliablePacket(m_editorConnId, packet); + } + } void MultiplayerEditorSystemComponent::OnTick([[maybe_unused]] float deltaTime, [[maybe_unused]] AZ::ScriptTimePoint time) diff --git a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.h b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.h index 31ecdf83a3..d43d8747b9 100644 --- a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.h +++ b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.h @@ -81,5 +81,7 @@ namespace Multiplayer IEditor* m_editor = nullptr; AzFramework::ProcessWatcher* m_serverProcess = nullptr; + AzNetworking::ConnectionId m_editorConnId; + AzNetworking::INetworkInterface* m_editorNetworkInterface = nullptr; }; } diff --git a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp index 03c661ab03..3c1e142d96 100644 --- a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp +++ b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp @@ -57,7 +57,9 @@ namespace Multiplayer using namespace AzNetworking; static const AZStd::string_view s_networkInterfaceName("MultiplayerNetworkInterface"); + static const AZStd::string_view s_networkEditorInterfaceName("MultiplayerEditorNetworkInterface"); static constexpr uint16_t DefaultServerPort = 30090; + static constexpr uint16_t DefaultServerEditorPort = 30091; AZ_CVAR(uint16_t, cl_clientport, 0, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The port to bind to for game traffic when connecting to a remote host, a value of 0 will select any available port"); AZ_CVAR(AZ::CVarFixedString, cl_serveraddr, "127.0.0.1", nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The address of the remote server or host to connect to"); @@ -397,6 +399,19 @@ namespace Multiplayer return false; } + bool MultiplayerSystemComponent::HandleRequest + ( + [[maybe_unused]] AzNetworking::IConnection* connection, + [[maybe_unused]] const IPacketHeader& packetHeader, + [[maybe_unused]] MultiplayerPackets::EditorServerInit& packet + ) + { +#if !defined(_RELEASE) + // Support Editor Server Init for all non-release targets +#endif + return true; + } + ConnectResult MultiplayerSystemComponent::ValidateConnect ( [[maybe_unused]] const IpAddress& remoteAddress, @@ -492,6 +507,12 @@ namespace Multiplayer { if (multiplayerType == MultiplayerAgentType::ClientServer || multiplayerType == MultiplayerAgentType::DedicatedServer) { +#if !defined(_RELEASE) + m_networkEditorInterface = AZ::Interface::Get()->CreateNetworkInterface( + AZ::Name(s_networkEditorInterfaceName), ProtocolType::Tcp, TrustZone::ExternalClientToServer, *this); + m_networkEditorInterface->Listen(DefaultServerEditorPort); +#endif + m_initEvent.Signal(m_networkInterface); const AZ::Aabb worldBounds = AZ::Aabb::CreateFromMinMax(AZ::Vector3(-16384.0f), AZ::Vector3(16384.0f)); diff --git a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h index f25e530b61..e77fa129b0 100644 --- a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h +++ b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h @@ -72,7 +72,8 @@ namespace Multiplayer bool HandleRequest(AzNetworking::IConnection* connection, const AzNetworking::IPacketHeader& packetHeader, MultiplayerPackets::NotifyClientMigration& packet); bool HandleRequest(AzNetworking::IConnection* connection, const AzNetworking::IPacketHeader& packetHeader, MultiplayerPackets::EntityMigration& packet); bool HandleRequest(AzNetworking::IConnection* connection, const AzNetworking::IPacketHeader& packetHeader, MultiplayerPackets::ReadyForEntityUpdates& packet); - + bool HandleRequest(AzNetworking::IConnection* connection, const AzNetworking::IPacketHeader& packetHeader, MultiplayerPackets::EditorServerInit& packet); + //! IConnectionListener interface //! @{ AzNetworking::ConnectResult ValidateConnect(const AzNetworking::IpAddress& remoteAddress, const AzNetworking::IPacketHeader& packetHeader, AzNetworking::ISerializer& serializer) override; @@ -109,6 +110,7 @@ namespace Multiplayer AZ_CONSOLEFUNC(MultiplayerSystemComponent, DumpStats, AZ::ConsoleFunctorFlags::Null, "Dumps stats for the current multiplayer session"); AzNetworking::INetworkInterface* m_networkInterface = nullptr; + AzNetworking::INetworkInterface* m_networkEditorInterface = nullptr; AZ::ConsoleCommandInvokedEvent::Handler m_consoleCommandHandler; AZ::ThreadSafeDeque m_cvarCommands; diff --git a/Gems/Multiplayer/Code/Source/MultiplayerToolsModule.cpp b/Gems/Multiplayer/Code/Source/MultiplayerToolsModule.cpp index a5df3c1dc5..ae91999a0c 100644 --- a/Gems/Multiplayer/Code/Source/MultiplayerToolsModule.cpp +++ b/Gems/Multiplayer/Code/Source/MultiplayerToolsModule.cpp @@ -24,6 +24,16 @@ namespace Multiplayer NetworkPrefabProcessor::Reflect(context); } + void MultiplayerToolsSystemComponent::Activate() + { + AZ::Interface::Register(this); + } + + void MultiplayerToolsSystemComponent::Deactivate() + { + AZ::Interface::Unregister(this); + } + bool MultiplayerToolsSystemComponent::DidProcessNetworkPrefabs() { return m_didProcessNetPrefabs; diff --git a/Gems/Multiplayer/Code/Source/MultiplayerToolsModule.h b/Gems/Multiplayer/Code/Source/MultiplayerToolsModule.h index 82d0415c5a..181a971150 100644 --- a/Gems/Multiplayer/Code/Source/MultiplayerToolsModule.h +++ b/Gems/Multiplayer/Code/Source/MultiplayerToolsModule.h @@ -31,9 +31,8 @@ namespace Multiplayer ~MultiplayerToolsSystemComponent() override = default; /// AZ::Component overrides. - void Activate() override {}; - - void Deactivate() override {}; + void Activate() override; + void Deactivate() override; bool DidProcessNetworkPrefabs() override; From 1f3d0beb387a68ac5ac87c63aafa5105d1f253b6 Mon Sep 17 00:00:00 2001 From: antonmic Date: Fri, 7 May 2021 18:51:20 -0700 Subject: [PATCH 08/69] work in progress --- .../Materials/Types/StandardPBR.materialtype | 15 + .../Types/StandardPBR_ForwardPass.azsl | 15 +- .../Types/StandardPBR_LowEndForward.azsl | 15 + .../Types/StandardPBR_LowEndForward.shader | 53 +++ .../StandardPBR_LowEndForward_EDS.shader | 53 +++ .../Feature/Common/Assets/Passes/Forward.pass | 16 - .../Assets/Passes/LightAdaptationParent.pass | 146 ++++++++ .../Common/Assets/Passes/LowEndForward.pass | 133 +++++++ .../Common/Assets/Passes/LowEndPipeline.pass | 344 ++++++++++++++++++ .../Common/Assets/Passes/OpaqueParent.pass | 11 +- .../Assets/Passes/PassTemplates.azasset | 12 + .../Assets/Passes/PostProcessParent.pass | 90 +---- .../Feature/Common/Assets/Passes/SkyBox.pass | 5 - .../Atom/Features/PBR/ForwardPassOutput.azsli | 17 + .../PBR/LowEndForwardPassOutput.azsli | 32 ++ .../Atom/Features/ShaderQualityOptions.azsli | 24 ++ .../Reflections/ReflectionComposite.azsl | 15 +- .../Common/Assets/Shaders/SkyBox/SkyBox.azsl | 2 - .../atom_feature_common_asset_files.cmake | 10 + 19 files changed, 887 insertions(+), 121 deletions(-) create mode 100644 Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_LowEndForward.azsl create mode 100644 Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_LowEndForward.shader create mode 100644 Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_LowEndForward_EDS.shader create mode 100644 Gems/Atom/Feature/Common/Assets/Passes/LightAdaptationParent.pass create mode 100644 Gems/Atom/Feature/Common/Assets/Passes/LowEndForward.pass create mode 100644 Gems/Atom/Feature/Common/Assets/Passes/LowEndPipeline.pass create mode 100644 Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/LowEndForwardPassOutput.azsli create mode 100644 Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/ShaderQualityOptions.azsli diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR.materialtype b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR.materialtype index e071a793a5..fa5c94c606 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR.materialtype +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR.materialtype @@ -77,6 +77,13 @@ ], "properties": { "general": [ + { + "id": "useLowEndShader", + "displayName": "Use Low End", + "description": "Whether to use the low end shader.", + "type": "Bool", + "defaultValue": false + }, { "id": "applySpecularAA", "displayName": "Apply Specular AA", @@ -1175,6 +1182,14 @@ "file": "./StandardPBR_ForwardPass_EDS.shader", "tag": "ForwardPass_EDS" }, + { + "file": "./StandardPBR_LowEndForward.shader", + "tag": "LowEndForward" + }, + { + "file": "./StandardPBR_LowEndForward_EDS.shader", + "tag": "LowEndForward_EDS" + }, { "file": "Shaders/Shadow/Shadowmap.shader", "tag": "Shadowmap" diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_ForwardPass.azsl b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_ForwardPass.azsl index d3bc72d162..bb4e4cdaab 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_ForwardPass.azsl +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_ForwardPass.azsl @@ -10,6 +10,8 @@ * */ +#include "Atom/Features/ShaderQualityOptions.azsli" + #include "StandardPBR_Common.azsli" // SRGs @@ -306,13 +308,18 @@ ForwardPassOutputWithDepth StandardPbr_ForwardPassPS(VSOutput IN, bool isFrontFa PbrLightingOutput lightingOutput = ForwardPassPS_Common(IN, isFrontFace, depth); +#ifdef UNIFIED_FORWARD_OUTPUT + OUT.m_color.rgb = lightingOutput.m_diffuseColor.rgb + lightingOutput.m_specularColor.rgb; + OUT.m_color.a = 1.0f; + OUT.m_depth = depth; +#else OUT.m_diffuseColor = lightingOutput.m_diffuseColor; OUT.m_specularColor = lightingOutput.m_specularColor; OUT.m_specularF0 = lightingOutput.m_specularF0; OUT.m_albedo = lightingOutput.m_albedo; OUT.m_normal = lightingOutput.m_normal; OUT.m_depth = depth; - +#endif return OUT; } @@ -324,12 +331,16 @@ ForwardPassOutput StandardPbr_ForwardPassPS_EDS(VSOutput IN, bool isFrontFace : PbrLightingOutput lightingOutput = ForwardPassPS_Common(IN, isFrontFace, depth); +#ifdef UNIFIED_FORWARD_OUTPUT + OUT.m_color.rgb = lightingOutput.m_diffuseColor.rgb + lightingOutput.m_specularColor.rgb; + OUT.m_color.a = 1.0f; +#else OUT.m_diffuseColor = lightingOutput.m_diffuseColor; OUT.m_specularColor = lightingOutput.m_specularColor; OUT.m_specularF0 = lightingOutput.m_specularF0; OUT.m_albedo = lightingOutput.m_albedo; OUT.m_normal = lightingOutput.m_normal; - +#endif return OUT; } diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_LowEndForward.azsl b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_LowEndForward.azsl new file mode 100644 index 0000000000..a690cbf84a --- /dev/null +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_LowEndForward.azsl @@ -0,0 +1,15 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#define QUALITY_LOW_END 1 + +#include "StandardPBR_ForwardPass.azsl" diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_LowEndForward.shader b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_LowEndForward.shader new file mode 100644 index 0000000000..19538e5db3 --- /dev/null +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_LowEndForward.shader @@ -0,0 +1,53 @@ +{ + "Source" : "./StandardPBR_LowEndForward.azsl", + + "DepthStencilState" : + { + "Depth" : + { + "Enable" : true, + "CompareFunc" : "GreaterEqual" + }, + "Stencil" : + { + "Enable" : true, + "ReadMask" : "0x00", + "WriteMask" : "0xFF", + "FrontFace" : + { + "Func" : "Always", + "DepthFailOp" : "Keep", + "FailOp" : "Keep", + "PassOp" : "Replace" + }, + "BackFace" : + { + "Func" : "Always", + "DepthFailOp" : "Keep", + "FailOp" : "Keep", + "PassOp" : "Replace" + } + } + }, + + "CompilerHints" : { + "DisableOptimizations" : false + }, + + "ProgramSettings": + { + "EntryPoints": + [ + { + "name": "StandardPbr_ForwardPassVS", + "type": "Vertex" + }, + { + "name": "StandardPbr_ForwardPassPS", + "type": "Fragment" + } + ] + }, + + "DrawList" : "lowEndForward" +} diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_LowEndForward_EDS.shader b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_LowEndForward_EDS.shader new file mode 100644 index 0000000000..1b5f014d0e --- /dev/null +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_LowEndForward_EDS.shader @@ -0,0 +1,53 @@ +{ + "Source" : "./StandardPBR_LowEndForward.azsl", + + "DepthStencilState" : + { + "Depth" : + { + "Enable" : true, + "CompareFunc" : "GreaterEqual" + }, + "Stencil" : + { + "Enable" : true, + "ReadMask" : "0x00", + "WriteMask" : "0xFF", + "FrontFace" : + { + "Func" : "Always", + "DepthFailOp" : "Keep", + "FailOp" : "Keep", + "PassOp" : "Replace" + }, + "BackFace" : + { + "Func" : "Always", + "DepthFailOp" : "Keep", + "FailOp" : "Keep", + "PassOp" : "Replace" + } + } + }, + + "CompilerHints" : { + "DisableOptimizations" : false + }, + + "ProgramSettings": + { + "EntryPoints": + [ + { + "name": "StandardPbr_ForwardPassVS", + "type": "Vertex" + }, + { + "name": "StandardPbr_ForwardPassPS_EDS", + "type": "Fragment" + } + ] + }, + + "DrawList" : "lowEndForward" +} diff --git a/Gems/Atom/Feature/Common/Assets/Passes/Forward.pass b/Gems/Atom/Feature/Common/Assets/Passes/Forward.pass index 31a8ed1879..3dcc90ac5c 100644 --- a/Gems/Atom/Feature/Common/Assets/Passes/Forward.pass +++ b/Gems/Atom/Feature/Common/Assets/Passes/Forward.pass @@ -148,22 +148,6 @@ }, "LoadAction": "Clear" } - }, - { - "Name": "ScatterDistanceOutput", - "SlotType": "Output", - "ScopeAttachmentUsage": "RenderTarget", - "LoadStoreAction": { - "ClearValue": { - "Value": [ - 0.0, - 0.0, - 0.0, - 0.0 - ] - }, - "LoadAction": "Clear" - } } ], "ImageAttachments": [ diff --git a/Gems/Atom/Feature/Common/Assets/Passes/LightAdaptationParent.pass b/Gems/Atom/Feature/Common/Assets/Passes/LightAdaptationParent.pass new file mode 100644 index 0000000000..3e804d23e2 --- /dev/null +++ b/Gems/Atom/Feature/Common/Assets/Passes/LightAdaptationParent.pass @@ -0,0 +1,146 @@ +{ + "Type": "JsonSerialization", + "Version": 1, + "ClassName": "PassAsset", + "ClassData": { + "PassTemplate": { + "Name": "LightAdaptationParentTemplate", + "PassClass": "ParentPass", + "Slots": [ + // Inputs... + { + "Name": "LightingInput", + "SlotType": "Input" + }, + // SwapChain here is only used to reference the frame height and format + { + "Name": "SwapChainOutput", + "SlotType": "InputOutput" + }, + // Outputs... + { + "Name": "Output", + "SlotType": "Output" + }, + // Debug Outputs... + { + "Name": "LuminanceMipChainOutput", + "SlotType": "Output" + } + ], + "Connections": [ + { + "LocalSlot": "Output", + "AttachmentRef": { + "Pass": "DisplayMapperPass", + "Attachment": "Output" + } + }, + { + "LocalSlot": "LuminanceMipChainOutput", + "AttachmentRef": { + "Pass": "DownsampleLuminanceMipChain", + "Attachment": "MipChainInputOutput" + } + } + ], + "PassRequests": [ + { + "Name": "DownsampleLuminanceMinAvgMax", + "TemplateName": "DownsampleLuminanceMinAvgMaxCS", + "Connections": [ + { + "LocalSlot": "Input", + "AttachmentRef": { + "Pass": "Parent", + "Attachment": "LightingInput" + } + } + ] + }, + { + "Name": "DownsampleLuminanceMipChain", + "TemplateName": "DownsampleMipChainTemplate", + "Connections": [ + { + "LocalSlot": "MipChainInputOutput", + "AttachmentRef": { + "Pass": "DownsampleLuminanceMinAvgMax", + "Attachment": "Output" + } + } + ], + "PassData": { + "$type": "DownsampleMipChainPassData", + "ShaderAsset": { + "FilePath": "Shaders/PostProcessing/DownsampleMinAvgMaxCS.shader" + } + } + }, + { + "Name": "EyeAdaptationPass", + "TemplateName": "EyeAdaptationTemplate", + "Enabled": false, + "Connections": [ + { + "LocalSlot": "SceneLuminanceInput", + "AttachmentRef": { + "Pass": "DownsampleLuminanceMipChain", + "Attachment": "MipChainInputOutput" + } + } + ] + }, + { + "Name": "LookModificationTransformPass", + "TemplateName": "LookModificationTransformTemplate", + "Enabled": true, + "Connections": [ + { + "LocalSlot": "Input", + "AttachmentRef": { + "Pass": "Parent", + "Attachment": "LightingInput" + } + }, + { + "LocalSlot": "EyeAdaptationDataInput", + "AttachmentRef": { + "Pass": "EyeAdaptationPass", + "Attachment": "EyeAdaptationDataInputOutput" + } + }, + { + "LocalSlot": "SwapChainOutput", + "AttachmentRef": { + "Pass": "Parent", + "Attachment": "SwapChainOutput" + } + } + ] + }, + { + "Name": "DisplayMapperPass", + "TemplateName": "DisplayMapperTemplate", + "Enabled": true, + "Connections": [ + { + "LocalSlot": "Input", + "AttachmentRef": { + "Pass": "LookModificationTransformPass", + "Attachment": "Output" + } + }, + { + "LocalSlot": "SwapChainOutput", + "AttachmentRef": { + "Pass": "Parent", + "Attachment": "SwapChainOutput" + } + } + ] + } + ] + } + } +} diff --git a/Gems/Atom/Feature/Common/Assets/Passes/LowEndForward.pass b/Gems/Atom/Feature/Common/Assets/Passes/LowEndForward.pass new file mode 100644 index 0000000000..4b865fcb6d --- /dev/null +++ b/Gems/Atom/Feature/Common/Assets/Passes/LowEndForward.pass @@ -0,0 +1,133 @@ +{ + "Type": "JsonSerialization", + "Version": 1, + "ClassName": "PassAsset", + "ClassData": { + "PassTemplate": { + "Name": "LowEndForwardPassTemplate", + "PassClass": "RasterPass", + "Slots": [ + // Inputs... + { + "Name": "BRDFTextureInput", + "ShaderInputName": "m_brdfMap", + "SlotType": "Input", + "ScopeAttachmentUsage": "Shader" + }, + { + "Name": "DirectionalLightShadowmap", + "ShaderInputName": "m_directionalLightShadowmap", + "SlotType": "Input", + "ScopeAttachmentUsage": "Shader", + "ImageViewDesc": { + "IsArray": 1 + } + }, + { + "Name": "ExponentialShadowmapDirectional", + "ShaderInputName": "m_directionalLightExponentialShadowmap", + "SlotType": "Input", + "ScopeAttachmentUsage": "Shader", + "ImageViewDesc": { + "IsArray": 1 + } + }, + { + "Name": "ProjectedShadowmap", + "ShaderInputName": "m_projectedShadowmaps", + "SlotType": "Input", + "ScopeAttachmentUsage": "Shader", + "ImageViewDesc": { + "IsArray": 1 + } + }, + { + "Name": "ExponentialShadowmapProjected", + "ShaderInputName": "m_projectedExponentialShadowmap", + "SlotType": "Input", + "ScopeAttachmentUsage": "Shader", + "ImageViewDesc": { + "IsArray": 1 + } + }, + { + "Name": "TileLightData", + "SlotType": "Input", + "ShaderInputName": "m_tileLightData", + "ScopeAttachmentUsage": "Shader" + }, + { + "Name": "LightListRemapped", + "SlotType": "Input", + "ShaderInputName": "m_lightListRemapped", + "ScopeAttachmentUsage": "Shader" + }, + // Input/Outputs... + { + "Name": "DepthStencilInputOutput", + "SlotType": "InputOutput", + "ScopeAttachmentUsage": "DepthStencil" + }, + // Outputs... + { + "Name": "LightingOutput", + "SlotType": "Output", + "ScopeAttachmentUsage": "RenderTarget", + "LoadStoreAction": { + "ClearValue": { + "Value": [ + 0.0, + 0.0, + 0.0, + 0.0 + ] + }, + "LoadAction": "Clear" + } + } + ], + "ImageAttachments": [ + { + "Name": "LightingAttachment", + "SizeSource": { + "Source": { + "Pass": "Parent", + "Attachment": "SwapChainOutput" + } + }, + "MultisampleSource": { + "Pass": "This", + "Attachment": "DepthStencilInputOutput" + }, + "ImageDescriptor": { + "Format": "R16G16B16A16_FLOAT", + "SharedQueueMask": "Graphics" + } + }, + { + "Name": "BRDFTexture", + "Lifetime": "Imported", + "AssetRef": { + "FilePath": "Textures/BRDFTexture.attimage" + } + } + ], + "Connections": [ + { + "LocalSlot": "LightingOutput", + "AttachmentRef": { + "Pass": "This", + "Attachment": "LightingAttachment" + } + }, + { + "LocalSlot": "BRDFTextureInput", + "AttachmentRef": { + "Pass": "This", + "Attachment": "BRDFTexture" + } + } + ] + } + } +} diff --git a/Gems/Atom/Feature/Common/Assets/Passes/LowEndPipeline.pass b/Gems/Atom/Feature/Common/Assets/Passes/LowEndPipeline.pass new file mode 100644 index 0000000000..b19569fb9d --- /dev/null +++ b/Gems/Atom/Feature/Common/Assets/Passes/LowEndPipeline.pass @@ -0,0 +1,344 @@ +{ + "Type": "JsonSerialization", + "Version": 1, + "ClassName": "PassAsset", + "ClassData": { + "PassTemplate": { + "Name": "LowEndPipelineTemplate", + "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": "DepthPrePass", + "TemplateName": "DepthMSAAParentTemplate", + "Connections": [ + { + "LocalSlot": "SkinnedMeshes", + "AttachmentRef": { + "Pass": "SkinningPass", + "Attachment": "SkinnedMeshOutputStream" + } + }, + { + "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": "ForwardPass", + "TemplateName": "LowEndForwardPassTemplate", + "Connections": [ + // Inputs... + { + "LocalSlot": "DirectionalLightShadowmap", + "AttachmentRef": { + "Pass": "ShadowPass", + "Attachment": "DirectionalShadowmap" + } + }, + { + "LocalSlot": "ExponentialShadowmapDirectional", + "AttachmentRef": { + "Pass": "ShadowPass", + "Attachment": "DirectionalESM" + } + }, + { + "LocalSlot": "ProjectedShadowmap", + "AttachmentRef": { + "Pass": "ShadowPass", + "Attachment": "ProjectedShadowmap" + } + }, + { + "LocalSlot": "ExponentialShadowmapProjected", + "AttachmentRef": { + "Pass": "ShadowPass", + "Attachment": "ProjectedESM" + } + }, + { + "LocalSlot": "TileLightData", + "AttachmentRef": { + "Pass": "LightCullingPass", + "Attachment": "TileLightData" + } + }, + { + "LocalSlot": "LightListRemapped", + "AttachmentRef": { + "Pass": "LightCullingPass", + "Attachment": "LightListRemapped" + } + }, + // Input/Outputs... + { + "LocalSlot": "DepthStencilInputOutput", + "AttachmentRef": { + "Pass": "DepthPrePass", + "Attachment": "DepthMSAA" + } + } + ], + "PassData": { + "$type": "RasterPassData", + "DrawListTag": "lowEndForward", + "PipelineViewTag": "MainCamera", + "PassSrgAsset": { + "FilePath": "shaderlib/atom/features/pbr/forwardpasssrg.azsli:PassSrg" + } + } + }, + { + "Name": "SkyBoxPass", + "TemplateName": "SkyBoxTemplate", + "Enabled": true, + "Connections": [ + { + "LocalSlot": "SpecularInputOutput", + "AttachmentRef": { + "Pass": "ForwardPass", + "Attachment": "LightingOutput" + } + }, + { + "LocalSlot": "SkyBoxDepth", + "AttachmentRef": { + "Pass": "ForwardPass", + "Attachment": "DepthStencilInputOutput" + } + } + ] + }, + { + "Name": "MSAAResolvePass", + "TemplateName": "MSAAResolveColorTemplate", + "Connections": [ + { + "LocalSlot": "Input", + "AttachmentRef": { + "Pass": "SkyBoxPass", + "Attachment": "SpecularInputOutput" + } + } + ] + }, + { + "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": "DepthStencil", + "AttachmentRef": { + "Pass": "DepthPrePass", + "Attachment": "Depth" + } + }, + { + "LocalSlot": "InputOutput", + "AttachmentRef": { + "Pass": "MSAAResolvePass", + "Attachment": "Output" + } + } + ] + }, + { + "Name": "LightAdaptation", + "TemplateName": "LightAdaptationParentTemplate", + "Connections": [ + { + "LocalSlot": "LightingInput", + "AttachmentRef": { + "Pass": "TransparentPass", + "Attachment": "InputOutput" + } + }, + { + "LocalSlot": "SwapChainOutput", + "AttachmentRef": { + "Pass": "Parent", + "Attachment": "SwapChainOutput" + } + } + ] + }, + { + "Name": "AuxGeomPass", + "TemplateName": "AuxGeomPassTemplate", + "Enabled": true, + "Connections": [ + { + "LocalSlot": "ColorInputOutput", + "AttachmentRef": { + "Pass": "LightAdaptation", + "Attachment": "Output" + } + }, + { + "LocalSlot": "DepthInputOutput", + "AttachmentRef": { + "Pass": "DepthPrePass", + "Attachment": "Depth" + } + } + ], + "PassData": { + "$type": "RasterPassData", + "DrawListTag": "auxgeom", + "PipelineViewTag": "MainCamera" + } + }, + { + "Name": "UIPass", + "TemplateName": "UIParentTemplate", + "Connections": [ + { + "LocalSlot": "InputOutput", + "AttachmentRef": { + "Pass": "AuxGeomPass", + "Attachment": "ColorInputOutput" + } + } + ] + }, + { + "Name": "CopyToSwapChain", + "TemplateName": "FullscreenCopyTemplate", + "Connections": [ + { + "LocalSlot": "Input", + "AttachmentRef": { + "Pass": "UIPass", + "Attachment": "InputOutput" + } + }, + { + "LocalSlot": "Output", + "AttachmentRef": { + "Pass": "Parent", + "Attachment": "SwapChainOutput" + } + } + ] + } + ] + } + } +} diff --git a/Gems/Atom/Feature/Common/Assets/Passes/OpaqueParent.pass b/Gems/Atom/Feature/Common/Assets/Passes/OpaqueParent.pass index dda120e164..40d6a51e77 100644 --- a/Gems/Atom/Feature/Common/Assets/Passes/OpaqueParent.pass +++ b/Gems/Atom/Feature/Common/Assets/Passes/OpaqueParent.pass @@ -315,13 +315,6 @@ "Attachment": "SpecularInputOutput" } }, - { - "LocalSlot": "ReflectionInputOutput", - "AttachmentRef": { - "Pass": "ReflectionsPass", - "Attachment": "ReflectionOutput" - } - }, { "LocalSlot": "SkyBoxDepth", "AttachmentRef": { @@ -338,8 +331,8 @@ { "LocalSlot": "ReflectionInput", "AttachmentRef": { - "Pass": "SkyBoxPass", - "Attachment": "ReflectionInputOutput" + "Pass": "ReflectionsPass", + "Attachment": "ReflectionOutput" } }, { diff --git a/Gems/Atom/Feature/Common/Assets/Passes/PassTemplates.azasset b/Gems/Atom/Feature/Common/Assets/Passes/PassTemplates.azasset index b83ab65ff2..2421d7fbe7 100644 --- a/Gems/Atom/Feature/Common/Assets/Passes/PassTemplates.azasset +++ b/Gems/Atom/Feature/Common/Assets/Passes/PassTemplates.azasset @@ -483,6 +483,18 @@ { "Name": "UIParentTemplate", "Path": "Passes/UIParent.pass" + }, + { + "Name": "LightAdaptationParentTemplate", + "Path": "Passes/LightAdaptationParent.pass" + }, + { + "Name": "LowEndForwardPassTemplate", + "Path": "Passes/LowEndForward.pass" + }, + { + "Name": "LowEndPipelineTemplate", + "Path": "Passes/LowEndPipeline.pass" } ] } diff --git a/Gems/Atom/Feature/Common/Assets/Passes/PostProcessParent.pass b/Gems/Atom/Feature/Common/Assets/Passes/PostProcessParent.pass index 37b1ee5c5a..36f7f1e985 100644 --- a/Gems/Atom/Feature/Common/Assets/Passes/PostProcessParent.pass +++ b/Gems/Atom/Feature/Common/Assets/Passes/PostProcessParent.pass @@ -40,7 +40,7 @@ { "LocalSlot": "Output", "AttachmentRef": { - "Pass": "DisplayMapperPass", + "Pass": "LightAdaptation", "Attachment": "Output" } }, @@ -54,8 +54,8 @@ { "LocalSlot": "LuminanceMipChainOutput", "AttachmentRef": { - "Pass": "DownsampleLuminanceMipChain", - "Attachment": "MipChainInputOutput" + "Pass": "LightAdaptation", + "Attachment": "LuminanceMipChainOutput" } } ], @@ -115,94 +115,16 @@ } ] }, - // Everything before this point deals in raw lighting values - // --------------------------------------------------------- - // Everything after starts to map to values we see on screen { - "Name": "DownsampleLuminanceMinAvgMax", - "TemplateName": "DownsampleLuminanceMinAvgMaxCS", + "Name": "LightAdaptation", + "TemplateName": "LightAdaptationParentTemplate", "Connections": [ { - "LocalSlot": "Input", + "LocalSlot": "LightingInput", "AttachmentRef": { "Pass": "BloomPass", "Attachment": "InputOutput" } - } - ] - }, - { - "Name": "DownsampleLuminanceMipChain", - "TemplateName": "DownsampleMipChainTemplate", - "Connections": [ - { - "LocalSlot": "MipChainInputOutput", - "AttachmentRef": { - "Pass": "DownsampleLuminanceMinAvgMax", - "Attachment": "Output" - } - } - ], - "PassData": { - "$type": "DownsampleMipChainPassData", - "ShaderAsset": { - "FilePath": "Shaders/PostProcessing/DownsampleMinAvgMaxCS.shader" - } - } - }, - { - "Name": "EyeAdaptationPass", - "TemplateName": "EyeAdaptationTemplate", - "Enabled": false, - "Connections": [ - { - "LocalSlot": "SceneLuminanceInput", - "AttachmentRef": { - "Pass": "DownsampleLuminanceMipChain", - "Attachment": "MipChainInputOutput" - } - } - ] - }, - { - "Name": "LookModificationTransformPass", - "TemplateName": "LookModificationTransformTemplate", - "Enabled": true, - "Connections": [ - { - "LocalSlot": "Input", - "AttachmentRef": { - "Pass": "BloomPass", - "Attachment": "InputOutput" - } - }, - { - "LocalSlot": "EyeAdaptationDataInput", - "AttachmentRef": { - "Pass": "EyeAdaptationPass", - "Attachment": "EyeAdaptationDataInputOutput" - } - }, - { - "LocalSlot": "SwapChainOutput", - "AttachmentRef": { - "Pass": "Parent", - "Attachment": "SwapChainOutput" - } - } - ] - }, - { - "Name": "DisplayMapperPass", - "TemplateName": "DisplayMapperTemplate", - "Enabled": true, - "Connections": [ - { - "LocalSlot": "Input", - "AttachmentRef": { - "Pass": "LookModificationTransformPass", - "Attachment": "Output" - } }, { "LocalSlot": "SwapChainOutput", diff --git a/Gems/Atom/Feature/Common/Assets/Passes/SkyBox.pass b/Gems/Atom/Feature/Common/Assets/Passes/SkyBox.pass index 57f442e5de..fb16271ba7 100644 --- a/Gems/Atom/Feature/Common/Assets/Passes/SkyBox.pass +++ b/Gems/Atom/Feature/Common/Assets/Passes/SkyBox.pass @@ -12,11 +12,6 @@ "SlotType": "InputOutput", "ScopeAttachmentUsage": "RenderTarget" }, - { - "Name": "ReflectionInputOutput", - "SlotType": "InputOutput", - "ScopeAttachmentUsage": "RenderTarget" - }, { "Name": "SkyBoxDepth", "SlotType": "InputOutput", diff --git a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/ForwardPassOutput.azsli b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/ForwardPassOutput.azsli index acc215f1c9..5821deb3b1 100644 --- a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/ForwardPassOutput.azsli +++ b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/ForwardPassOutput.azsli @@ -10,6 +10,21 @@ * */ +#ifdef UNIFIED_FORWARD_OUTPUT + +struct ForwardPassOutput +{ + float4 m_color : SV_Target0; +}; + +struct ForwardPassOutputWithDepth +{ + float4 m_color : SV_Target0; + float m_depth : SV_Depth; +}; + +#else + struct ForwardPassOutput { float4 m_diffuseColor : SV_Target0; //!< RGB = Diffuse Lighting, A = Blend Alpha (for blended surfaces) OR A = special encoding of surfaceScatteringFactor, m_subsurfaceScatteringQuality, o_enableSubsurfaceScattering @@ -30,3 +45,5 @@ struct ForwardPassOutputWithDepth float4 m_normal : SV_Target4; float m_depth : SV_Depth; }; + +#endif diff --git a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/LowEndForwardPassOutput.azsli b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/LowEndForwardPassOutput.azsli new file mode 100644 index 0000000000..acc215f1c9 --- /dev/null +++ b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/LowEndForwardPassOutput.azsli @@ -0,0 +1,32 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +struct ForwardPassOutput +{ + float4 m_diffuseColor : SV_Target0; //!< RGB = Diffuse Lighting, A = Blend Alpha (for blended surfaces) OR A = special encoding of surfaceScatteringFactor, m_subsurfaceScatteringQuality, o_enableSubsurfaceScattering + float4 m_specularColor : SV_Target1; //!< RGB = Specular Lighting, A = Unused + float4 m_albedo : SV_Target2; //!< RGB = Surface albedo pre-multiplied by other factors that will be multiplied later by diffuse GI, A = specularOcclusion + float4 m_specularF0 : SV_Target3; //!< RGB = Specular F0, A = roughness + float4 m_normal : SV_Target4; //!< RGB10 = EncodeNormalSignedOctahedron(worldNormal), A2 = multiScatterCompensationEnabled +}; + +struct ForwardPassOutputWithDepth +{ + // See above for descriptions of special encodings + + float4 m_diffuseColor : SV_Target0; + float4 m_specularColor : SV_Target1; + float4 m_albedo : SV_Target2; + float4 m_specularF0 : SV_Target3; + float4 m_normal : SV_Target4; + float m_depth : SV_Depth; +}; diff --git a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/ShaderQualityOptions.azsli b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/ShaderQualityOptions.azsli new file mode 100644 index 0000000000..907e67ada5 --- /dev/null +++ b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/ShaderQualityOptions.azsli @@ -0,0 +1,24 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#pragma once + +// These are a list of quality options to specify as macros (either in azsl or in shader files) +// +// QUALITY_LOW_END + +#ifdef QUALITY_LOW_END + +#define UNIFIED_FORWARD_OUTPUT 1 + +#endif + diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/Reflections/ReflectionComposite.azsl b/Gems/Atom/Feature/Common/Assets/Shaders/Reflections/ReflectionComposite.azsl index 92f8c3f638..865d657d85 100644 --- a/Gems/Atom/Feature/Common/Assets/Shaders/Reflections/ReflectionComposite.azsl +++ b/Gems/Atom/Feature/Common/Assets/Shaders/Reflections/ReflectionComposite.azsl @@ -53,13 +53,22 @@ PSOutput MainPS(VSOutput IN) uint width, height, samples; PassSrg::m_reflection.GetDimensions(width, height, samples); + float nonZeroSamples = 0.0f; for (uint sampleIndex = 0; sampleIndex < samples; ++sampleIndex) { - reflection += PassSrg::m_reflection.Load(IN.m_position.xy, sampleIndex).rgb; + float3 reflectionSample = PassSrg::m_reflection.Load(IN.m_position.xy, sampleIndex).rgb; + if(any(reflectionSample)) + { + reflection += reflectionSample; + nonZeroSamples += 1.0f; + } + } + + if(nonZeroSamples != 0.0f) + { + reflection /= nonZeroSamples; } - reflection /= samples; - PSOutput OUT; OUT.m_color = float4(reflection, 1.0f); return OUT; diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/SkyBox/SkyBox.azsl b/Gems/Atom/Feature/Common/Assets/Shaders/SkyBox/SkyBox.azsl index 1ee30a4f98..a7de426374 100644 --- a/Gems/Atom/Feature/Common/Assets/Shaders/SkyBox/SkyBox.azsl +++ b/Gems/Atom/Feature/Common/Assets/Shaders/SkyBox/SkyBox.azsl @@ -102,7 +102,6 @@ float3 GetCubemapCoords(float3 original) struct PSOutput { float4 m_specular : SV_Target0; - float4 m_reflection : SV_Target1; }; PSOutput MainPS(VSOutput input) @@ -163,6 +162,5 @@ PSOutput MainPS(VSOutput input) PSOutput OUT; OUT.m_specular = float4(color, 1.0); - OUT.m_reflection = float4(color, 1.0); return OUT; } diff --git a/Gems/Atom/Feature/Common/Assets/atom_feature_common_asset_files.cmake b/Gems/Atom/Feature/Common/Assets/atom_feature_common_asset_files.cmake index f14fb4f4a0..6902d456ff 100644 --- a/Gems/Atom/Feature/Common/Assets/atom_feature_common_asset_files.cmake +++ b/Gems/Atom/Feature/Common/Assets/atom_feature_common_asset_files.cmake @@ -52,6 +52,8 @@ set(FILES Materials/Types/StandardPBR_ForwardPass_EDS.shader Materials/Types/StandardPBR_HandleOpacityDoubleSided.lua Materials/Types/StandardPBR_HandleOpacityMode.lua + Materials/Types/StandardPBR_LowEndForward.azsl + Materials/Types/StandardPBR_LowEndForward.shader Materials/Types/StandardPBR_ParallaxState.lua Materials/Types/StandardPBR_Roughness.lua Materials/Types/StandardPBR_ShaderEnable.lua @@ -116,6 +118,7 @@ set(FILES Passes/DiffuseProbeGridBlendDistance.pass Passes/DiffuseProbeGridBlendIrradiance.pass Passes/DiffuseProbeGridBorderUpdate.pass + Passes/DiffuseProbeGridClassification.pass Passes/DiffuseProbeGridDownsample.pass Passes/DiffuseProbeGridRayTracing.pass Passes/DiffuseProbeGridRelocation.pass @@ -144,6 +147,7 @@ set(FILES Passes/FullscreenCopy.pass Passes/FullscreenOutputOnly.pass Passes/ImGui.pass + Passes/LightAdaptationParent.pass Passes/LightCulling.pass Passes/LightCullingHeatmap.pass Passes/LightCullingParent.pass @@ -152,6 +156,8 @@ set(FILES Passes/LightCullingTilePrepareMSAA.pass Passes/LookModificationComposite.pass Passes/LookModificationTransform.pass + Passes/LowEndForward.pass + Passes/LowEndPipeline.pass Passes/LuminanceHeatmap.pass Passes/LuminanceHistogramGenerator.pass Passes/MainPipeline.pass @@ -179,8 +185,10 @@ set(FILES Passes/ReflectionScreenSpace.pass Passes/ReflectionScreenSpaceBlur.pass Passes/ReflectionScreenSpaceBlurHorizontal.pass + Passes/ReflectionScreenSpaceBlurMobile.pass Passes/ReflectionScreenSpaceBlurVertical.pass Passes/ReflectionScreenSpaceComposite.pass + Passes/ReflectionScreenSpaceMobile.pass Passes/ReflectionScreenSpaceTrace.pass Passes/Reflections_nomsaa.pass Passes/ShadowParent.pass @@ -205,6 +213,7 @@ set(FILES ShaderLib/Atom/Features/IndirectRendering.azsli ShaderLib/Atom/Features/MatrixUtility.azsli ShaderLib/Atom/Features/ParallaxMapping.azsli + ShaderLib/Atom/Features/ShaderQualityOptions.azsli ShaderLib/Atom/Features/SphericalHarmonicsUtility.azsli ShaderLib/Atom/Features/SrgSemantics.azsli ShaderLib/Atom/Features/ColorManagement/TransformColor.azsli @@ -234,6 +243,7 @@ set(FILES ShaderLib/Atom/Features/PBR/Hammersley.azsli ShaderLib/Atom/Features/PBR/LightingOptions.azsli ShaderLib/Atom/Features/PBR/LightingUtils.azsli + ShaderLib/Atom/Features/PBR/LowEndForwardPassOutput.azsli ShaderLib/Atom/Features/PBR/TransparentPassSrg.azsli ShaderLib/Atom/Features/PBR/Lighting/DualSpecularLighting.azsli ShaderLib/Atom/Features/PBR/Lighting/EnhancedLighting.azsli From 24aa0f852179f94bce8ae354b66c6804628bd1b7 Mon Sep 17 00:00:00 2001 From: antonmic Date: Fri, 7 May 2021 20:56:40 -0700 Subject: [PATCH 09/69] skybox pass separation for single vs double output --- .../Common/Assets/Passes/OpaqueParent.pass | 13 ++++-- .../Assets/Passes/PassTemplates.azasset | 4 ++ .../Feature/Common/Assets/Passes/SkyBox.pass | 5 +++ .../Assets/Passes/SkyBox_TwoOutputs.pass | 43 +++++++++++++++++++ .../Reflections/ReflectionComposite.azsl | 15 ++----- .../Common/Assets/Shaders/SkyBox/SkyBox.azsl | 11 +++++ .../Shaders/SkyBox/SkyBox_TwoOutputs.azsl | 15 +++++++ .../Shaders/SkyBox/SkyBox_TwoOutputs.shader | 22 ++++++++++ 8 files changed, 113 insertions(+), 15 deletions(-) create mode 100644 Gems/Atom/Feature/Common/Assets/Passes/SkyBox_TwoOutputs.pass create mode 100644 Gems/Atom/Feature/Common/Assets/Shaders/SkyBox/SkyBox_TwoOutputs.azsl create mode 100644 Gems/Atom/Feature/Common/Assets/Shaders/SkyBox/SkyBox_TwoOutputs.shader diff --git a/Gems/Atom/Feature/Common/Assets/Passes/OpaqueParent.pass b/Gems/Atom/Feature/Common/Assets/Passes/OpaqueParent.pass index 40d6a51e77..a691fe2534 100644 --- a/Gems/Atom/Feature/Common/Assets/Passes/OpaqueParent.pass +++ b/Gems/Atom/Feature/Common/Assets/Passes/OpaqueParent.pass @@ -305,7 +305,7 @@ }, { "Name": "SkyBoxPass", - "TemplateName": "SkyBoxTemplate", + "TemplateName": "SkyBoxTwoOutputsTemplate", "Enabled": true, "Connections": [ { @@ -315,6 +315,13 @@ "Attachment": "SpecularInputOutput" } }, + { + "LocalSlot": "ReflectionInputOutput", + "AttachmentRef": { + "Pass": "ReflectionsPass", + "Attachment": "ReflectionOutput" + } + }, { "LocalSlot": "SkyBoxDepth", "AttachmentRef": { @@ -331,8 +338,8 @@ { "LocalSlot": "ReflectionInput", "AttachmentRef": { - "Pass": "ReflectionsPass", - "Attachment": "ReflectionOutput" + "Pass": "SkyBoxPass", + "Attachment": "ReflectionInputOutput" } }, { diff --git a/Gems/Atom/Feature/Common/Assets/Passes/PassTemplates.azasset b/Gems/Atom/Feature/Common/Assets/Passes/PassTemplates.azasset index 2421d7fbe7..c56e8932b1 100644 --- a/Gems/Atom/Feature/Common/Assets/Passes/PassTemplates.azasset +++ b/Gems/Atom/Feature/Common/Assets/Passes/PassTemplates.azasset @@ -92,6 +92,10 @@ "Name": "SkyBoxTemplate", "Path": "Passes/SkyBox.pass" }, + { + "Name": "SkyBoxTwoOutputsTemplate", + "Path": "Passes/SkyBox_TwoOutputs.pass" + }, { "Name": "UIPassTemplate", "Path": "Passes/UI.pass" diff --git a/Gems/Atom/Feature/Common/Assets/Passes/SkyBox.pass b/Gems/Atom/Feature/Common/Assets/Passes/SkyBox.pass index fb16271ba7..57f442e5de 100644 --- a/Gems/Atom/Feature/Common/Assets/Passes/SkyBox.pass +++ b/Gems/Atom/Feature/Common/Assets/Passes/SkyBox.pass @@ -12,6 +12,11 @@ "SlotType": "InputOutput", "ScopeAttachmentUsage": "RenderTarget" }, + { + "Name": "ReflectionInputOutput", + "SlotType": "InputOutput", + "ScopeAttachmentUsage": "RenderTarget" + }, { "Name": "SkyBoxDepth", "SlotType": "InputOutput", diff --git a/Gems/Atom/Feature/Common/Assets/Passes/SkyBox_TwoOutputs.pass b/Gems/Atom/Feature/Common/Assets/Passes/SkyBox_TwoOutputs.pass new file mode 100644 index 0000000000..0ed7b39288 --- /dev/null +++ b/Gems/Atom/Feature/Common/Assets/Passes/SkyBox_TwoOutputs.pass @@ -0,0 +1,43 @@ +{ + "Type": "JsonSerialization", + "Version": 1, + "ClassName": "PassAsset", + "ClassData": { + "PassTemplate": { + "Name": "SkyBoxTwoOutputsTemplate", + "PassClass": "FullScreenTriangle", + "Slots": [ + { + "Name": "SpecularInputOutput", + "SlotType": "InputOutput", + "ScopeAttachmentUsage": "RenderTarget" + }, + { + "Name": "ReflectionInputOutput", + "SlotType": "InputOutput", + "ScopeAttachmentUsage": "RenderTarget" + }, + { + "Name": "SkyBoxDepth", + "SlotType": "InputOutput", + "ScopeAttachmentUsage": "DepthStencil" + } + ], + "PassData": { + "$type": "FullscreenTrianglePassData", + "ShaderAsset": { + "FilePath": "shaders/skybox/skybox_twooutputs.shader" + }, + "PipelineViewTag": "MainCamera", + "ShaderDataMappings": { + "FloatMappings": [ + { + "Name": "m_sunIntensityMultiplier", + "Value": 1.0 + } + ] + } + } + } + } +} diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/Reflections/ReflectionComposite.azsl b/Gems/Atom/Feature/Common/Assets/Shaders/Reflections/ReflectionComposite.azsl index 865d657d85..92f8c3f638 100644 --- a/Gems/Atom/Feature/Common/Assets/Shaders/Reflections/ReflectionComposite.azsl +++ b/Gems/Atom/Feature/Common/Assets/Shaders/Reflections/ReflectionComposite.azsl @@ -53,22 +53,13 @@ PSOutput MainPS(VSOutput IN) uint width, height, samples; PassSrg::m_reflection.GetDimensions(width, height, samples); - float nonZeroSamples = 0.0f; for (uint sampleIndex = 0; sampleIndex < samples; ++sampleIndex) { - float3 reflectionSample = PassSrg::m_reflection.Load(IN.m_position.xy, sampleIndex).rgb; - if(any(reflectionSample)) - { - reflection += reflectionSample; - nonZeroSamples += 1.0f; - } - } - - if(nonZeroSamples != 0.0f) - { - reflection /= nonZeroSamples; + reflection += PassSrg::m_reflection.Load(IN.m_position.xy, sampleIndex).rgb; } + reflection /= samples; + PSOutput OUT; OUT.m_color = float4(reflection, 1.0f); return OUT; diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/SkyBox/SkyBox.azsl b/Gems/Atom/Feature/Common/Assets/Shaders/SkyBox/SkyBox.azsl index a7de426374..4b3e9536b7 100644 --- a/Gems/Atom/Feature/Common/Assets/Shaders/SkyBox/SkyBox.azsl +++ b/Gems/Atom/Feature/Common/Assets/Shaders/SkyBox/SkyBox.azsl @@ -10,6 +10,11 @@ * */ +// Static Options: +// +// SKYBOX_TWO_OUTPUTS - Allows the skybox to render to two rendertargets instead of one + + #include #include #include @@ -102,6 +107,9 @@ float3 GetCubemapCoords(float3 original) struct PSOutput { float4 m_specular : SV_Target0; +#ifdef SKYBOX_TWO_OUTPUTS + float4 m_reflection : SV_Target1; +#endif }; PSOutput MainPS(VSOutput input) @@ -162,5 +170,8 @@ PSOutput MainPS(VSOutput input) PSOutput OUT; OUT.m_specular = float4(color, 1.0); +#ifdef SKYBOX_TWO_OUTPUTS + OUT.m_reflection = float4(color, 1.0); +#endif return OUT; } diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/SkyBox/SkyBox_TwoOutputs.azsl b/Gems/Atom/Feature/Common/Assets/Shaders/SkyBox/SkyBox_TwoOutputs.azsl new file mode 100644 index 0000000000..99d7b45e4c --- /dev/null +++ b/Gems/Atom/Feature/Common/Assets/Shaders/SkyBox/SkyBox_TwoOutputs.azsl @@ -0,0 +1,15 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#define SKYBOX_TWO_OUTPUTS + +#include "SkyBox.azsl" diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/SkyBox/SkyBox_TwoOutputs.shader b/Gems/Atom/Feature/Common/Assets/Shaders/SkyBox/SkyBox_TwoOutputs.shader new file mode 100644 index 0000000000..ec80d4a20e --- /dev/null +++ b/Gems/Atom/Feature/Common/Assets/Shaders/SkyBox/SkyBox_TwoOutputs.shader @@ -0,0 +1,22 @@ +{ + "Source" : "SkyBox_TwoOutputs", + + "DepthStencilState" : { + "Depth" : { "Enable" : true, "CompareFunc" : "GreaterEqual" } + }, + + "ProgramSettings": + { + "EntryPoints": + [ + { + "name": "MainVS", + "type": "Vertex" + }, + { + "name": "MainPS", + "type": "Fragment" + } + ] + } +} From cb245730a1f214c4891817b11bb617166f73c7b7 Mon Sep 17 00:00:00 2001 From: antonmic Date: Sun, 9 May 2021 23:01:24 -0700 Subject: [PATCH 10/69] work in progress --- .../Types/StandardPBR_LowEndForward.azsl | 2 ++ .../Feature/Common/Assets/Passes/Forward.pass | 20 ------------------- .../Feature/Common/Assets/Passes/SkyBox.pass | 5 ----- .../Shaders/SkyBox/SkyBox_TwoOutputs.azsl | 2 ++ .../atom_feature_common_asset_files.cmake | 4 ++++ 5 files changed, 8 insertions(+), 25 deletions(-) diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_LowEndForward.azsl b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_LowEndForward.azsl index a690cbf84a..c87faffcbe 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_LowEndForward.azsl +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_LowEndForward.azsl @@ -10,6 +10,8 @@ * */ +// NOTE: This file is a temporary workaround until .shader files can #define macros for their .azsl files + #define QUALITY_LOW_END 1 #include "StandardPBR_ForwardPass.azsl" diff --git a/Gems/Atom/Feature/Common/Assets/Passes/Forward.pass b/Gems/Atom/Feature/Common/Assets/Passes/Forward.pass index 3dcc90ac5c..b66e3bb4e1 100644 --- a/Gems/Atom/Feature/Common/Assets/Passes/Forward.pass +++ b/Gems/Atom/Feature/Common/Assets/Passes/Forward.pass @@ -222,19 +222,6 @@ "AssetRef": { "FilePath": "Textures/BRDFTexture.attimage" } - }, - { - "Name": "ScatterDistanceImage", - "SizeSource": { - "Source": { - "Pass": "Parent", - "Attachment": "SwapChainOutput" - } - }, - "ImageDescriptor": { - "Format": "R11G11B10_FLOAT", - "SharedQueueMask": "Graphics" - } } ], "Connections": [ @@ -279,13 +266,6 @@ "Pass": "This", "Attachment": "BRDFTexture" } - }, - { - "LocalSlot": "ScatterDistanceOutput", - "AttachmentRef": { - "Pass": "This", - "Attachment": "ScatterDistanceImage" - } } ] } diff --git a/Gems/Atom/Feature/Common/Assets/Passes/SkyBox.pass b/Gems/Atom/Feature/Common/Assets/Passes/SkyBox.pass index 57f442e5de..fb16271ba7 100644 --- a/Gems/Atom/Feature/Common/Assets/Passes/SkyBox.pass +++ b/Gems/Atom/Feature/Common/Assets/Passes/SkyBox.pass @@ -12,11 +12,6 @@ "SlotType": "InputOutput", "ScopeAttachmentUsage": "RenderTarget" }, - { - "Name": "ReflectionInputOutput", - "SlotType": "InputOutput", - "ScopeAttachmentUsage": "RenderTarget" - }, { "Name": "SkyBoxDepth", "SlotType": "InputOutput", diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/SkyBox/SkyBox_TwoOutputs.azsl b/Gems/Atom/Feature/Common/Assets/Shaders/SkyBox/SkyBox_TwoOutputs.azsl index 99d7b45e4c..feacd2f44f 100644 --- a/Gems/Atom/Feature/Common/Assets/Shaders/SkyBox/SkyBox_TwoOutputs.azsl +++ b/Gems/Atom/Feature/Common/Assets/Shaders/SkyBox/SkyBox_TwoOutputs.azsl @@ -10,6 +10,8 @@ * */ +// NOTE: This file is a temporary workaround until .shader files can #define macros for their .azsl files + #define SKYBOX_TWO_OUTPUTS #include "SkyBox.azsl" diff --git a/Gems/Atom/Feature/Common/Assets/atom_feature_common_asset_files.cmake b/Gems/Atom/Feature/Common/Assets/atom_feature_common_asset_files.cmake index 6902d456ff..7419c1e669 100644 --- a/Gems/Atom/Feature/Common/Assets/atom_feature_common_asset_files.cmake +++ b/Gems/Atom/Feature/Common/Assets/atom_feature_common_asset_files.cmake @@ -54,6 +54,7 @@ set(FILES Materials/Types/StandardPBR_HandleOpacityMode.lua Materials/Types/StandardPBR_LowEndForward.azsl Materials/Types/StandardPBR_LowEndForward.shader + Materials/Types/StandardPBR_LowEndForward_EDS.shader Materials/Types/StandardPBR_ParallaxState.lua Materials/Types/StandardPBR_Roughness.lua Materials/Types/StandardPBR_ShaderEnable.lua @@ -194,6 +195,7 @@ set(FILES Passes/ShadowParent.pass Passes/Skinning.pass Passes/SkyBox.pass + Passes/SkyBox_TwoOutputs.pass Passes/SMAA1xApplyLinearHDRColor.pass Passes/SMAA1xApplyPerceptualColor.pass Passes/SMAABlendingWeightCalculation.pass @@ -483,4 +485,6 @@ set(FILES Shaders/SkinnedMesh/LinearSkinningPassSRG.azsli Shaders/SkyBox/SkyBox.azsl Shaders/SkyBox/SkyBox.shader + Shaders/SkyBox/SkyBox_TwoOutputs.azsl + Shaders/SkyBox/SkyBox_TwoOutputs.shader ) From 7276253c4225dd09dc0208693815abffe7ac672c Mon Sep 17 00:00:00 2001 From: sphrose <82213493+sphrose@users.noreply.github.com> Date: Tue, 11 May 2021 08:20:44 +0100 Subject: [PATCH 11/69] renamed function --- .../AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.cpp | 4 ++-- .../AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.hxx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.cpp index faeae0a06d..85784d61e0 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.cpp @@ -126,7 +126,7 @@ namespace AzToolsFramework { QStylePainter p(this); - if (IsSectionSeparator()) + if (IsReorderableRow()) { const QPen linePen(QColor(0x3B3E3F)); p.setPen(linePen); @@ -1332,7 +1332,7 @@ namespace AzToolsFramework return canBeTopLevel(this); } - bool PropertyRowWidget::IsSectionSeparator() const + bool PropertyRowWidget::IsReorderableRow() const { return CanBeReordered(); } diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.hxx b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.hxx index b6c94dc98b..58bf3bb9f0 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.hxx +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.hxx @@ -83,7 +83,7 @@ namespace AzToolsFramework PropertyRowWidget* GetParentRow() const { return m_parentRow; } int GetLevel() const; bool IsTopLevel() const; - bool IsSectionSeparator() const; + bool IsReorderableRow() const; // Remove the default label and append the text to the name label. bool GetAppendDefaultLabelToName(); From 8e4d0d73dcea7f50d05a9318011de9a07e0d88e6 Mon Sep 17 00:00:00 2001 From: antonmic Date: Tue, 11 May 2021 01:29:53 -0700 Subject: [PATCH 12/69] Good working state, but material always emmits low end draw item --- .../Materials/Types/StandardPBR.materialtype | 24 ++-- .../Atom/Features/PBR/Lights/Ibl.azsli | 108 ++++++------------ .../Atom/Features/ShaderQualityOptions.azsli | 3 +- .../Code/Include/Atom/RPI.Public/Pass/Pass.h | 1 + .../RPI/Code/Source/RPI.Public/Pass/Pass.cpp | 4 +- 5 files changed, 59 insertions(+), 81 deletions(-) diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR.materialtype b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR.materialtype index 1612bf43b0..47d8a9d9d5 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR.materialtype +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR.materialtype @@ -1304,9 +1304,11 @@ "textureProperty": "baseColor.textureMap", "useTextureProperty": "baseColor.useTexture", "dependentProperties": ["baseColor.textureMapUv", "baseColor.textureBlendMode"], - "shaderTags": [ + "shaderTags": [ "ForwardPass", - "ForwardPass_EDS" + "ForwardPass_EDS", + "LowEndForward", + "LowEndForward_EDS" ], "shaderOption": "o_baseColor_useTexture" } @@ -1317,9 +1319,11 @@ "textureProperty": "metallic.textureMap", "useTextureProperty": "metallic.useTexture", "dependentProperties": ["metallic.textureMapUv"], - "shaderTags": [ + "shaderTags": [ "ForwardPass", - "ForwardPass_EDS" + "ForwardPass_EDS", + "LowEndForward", + "LowEndForward_EDS" ], "shaderOption": "o_metallic_useTexture" } @@ -1330,9 +1334,11 @@ "textureProperty": "specularF0.textureMap", "useTextureProperty": "specularF0.useTexture", "dependentProperties": ["specularF0.textureMapUv"], - "shaderTags": [ + "shaderTags": [ "ForwardPass", - "ForwardPass_EDS" + "ForwardPass_EDS", + "LowEndForward", + "LowEndForward_EDS" ], "shaderOption": "o_specularF0_useTexture" } @@ -1343,9 +1349,11 @@ "textureProperty": "normal.textureMap", "useTextureProperty": "normal.useTexture", "dependentProperties": ["normal.textureMapUv", "normal.factor", "normal.flipX", "normal.flipY"], - "shaderTags": [ + "shaderTags": [ "ForwardPass", - "ForwardPass_EDS" + "ForwardPass_EDS", + "LowEndForward", + "LowEndForward_EDS" ], "shaderOption": "o_normal_useTexture" } diff --git a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Lights/Ibl.azsli b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Lights/Ibl.azsli index 7400005508..721c48835d 100644 --- a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Lights/Ibl.azsli +++ b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Lights/Ibl.azsli @@ -18,32 +18,30 @@ #include #include -void ApplyIblDiffuse( +float3 GetIblDiffuse( float3 normal, float3 albedo, - float3 diffuseResponse, - out float3 outDiffuse) + float3 diffuseResponse) { float3 irradianceDir = MultiplyVectorQuaternion(normal, SceneSrg::m_iblOrientation); float3 diffuseSample = SceneSrg::m_diffuseEnvMap.Sample(SceneSrg::m_samplerEnv, GetCubemapCoords(irradianceDir)).rgb; - outDiffuse = diffuseResponse * albedo * diffuseSample; + return diffuseResponse * albedo * diffuseSample; } -void ApplyIblSpecular( +float3 GetIblSpecular( float3 position, float3 normal, float3 specularF0, float roughnessLinear, float3 dirToCamera, - float2 brdf, - out float3 outSpecular) + float2 brdf) { float3 reflectDir = reflect(-dirToCamera, normal); reflectDir = MultiplyVectorQuaternion(reflectDir, SceneSrg::m_iblOrientation); // global - outSpecular = SceneSrg::m_specularEnvMap.SampleLevel(SceneSrg::m_samplerEnv, GetCubemapCoords(reflectDir), GetRoughnessMip(roughnessLinear)).rgb; + float3 outSpecular = SceneSrg::m_specularEnvMap.SampleLevel(SceneSrg::m_samplerEnv, GetCubemapCoords(reflectDir), GetRoughnessMip(roughnessLinear)).rgb; outSpecular *= (specularF0 * brdf.x + brdf.y); // reflection probe @@ -72,86 +70,54 @@ void ApplyIblSpecular( outSpecular = lerp(outSpecular, probeSpecular, blendAmount); } + return outSpecular; } void ApplyIBL(Surface surface, inout LightingData lightingData) { - if (o_opacity_mode == OpacityMode::Blended || o_opacity_mode == OpacityMode::TintedTransparent) +#ifdef FORCE_IBL_IN_FORWARD_PASS + bool useDiffuseIbl = true; + bool useSpecularIbl = true; + bool useIbl = true; +#else + bool useDiffuseIbl = (o_opacity_mode == OpacityMode::Blended || o_opacity_mode == OpacityMode::TintedTransparent); + bool useSpecularIbl = (useDiffuseIbl || o_meshUseForwardPassIBLSpecular || o_materialUseForwardPassIBLSpecular); + bool useIbl = o_enableIBL && (useDiffuseIbl || useSpecularIbl); +#endif + + if(useIbl) { - // transparencies currently require IBL in the forward pass - if (o_enableIBL) + float iblExposureFactor = pow(2.0, SceneSrg::m_iblExposure); + + if(useDiffuseIbl) { - float3 iblDiffuse = 0.0f; - ApplyIblDiffuse( - surface.normal, - surface.albedo, - lightingData.diffuseResponse, - iblDiffuse); - - float3 iblSpecular = 0.0f; - ApplyIblSpecular( - surface.position, - surface.normal, - surface.specularF0, - surface.roughnessLinear, - lightingData.dirToCamera, - lightingData.brdf, - iblSpecular); - - // Adjust IBL lighting by exposure. - float iblExposureFactor = pow(2.0, SceneSrg::m_iblExposure); + float3 iblDiffuse = GetIblDiffuse(surface.normal, surface.albedo, lightingData.diffuseResponse); lightingData.diffuseLighting += (iblDiffuse * iblExposureFactor * lightingData.diffuseAmbientOcclusion); - lightingData.specularLighting += (iblSpecular * iblExposureFactor); } - } - else if (o_meshUseForwardPassIBLSpecular || o_materialUseForwardPassIBLSpecular) - { - if (o_enableIBL) - { - float3 iblSpecular = 0.0f; - ApplyIblSpecular( - surface.position, - surface.normal, - surface.specularF0, - surface.roughnessLinear, - lightingData.dirToCamera, - lightingData.brdf, - iblSpecular); + if(useSpecularIbl) + { + float3 iblSpecular = GetIblSpecular(surface.position, surface.normal, surface.specularF0, surface.roughnessLinear, lightingData.dirToCamera, lightingData.brdf); iblSpecular *= lightingData.multiScatterCompensation; - if (o_clearCoat_feature_enabled) + if (o_clearCoat_feature_enabled && surface.clearCoat.factor > 0.0f) { - if (surface.clearCoat.factor > 0.0f) - { - float clearCoatNdotV = saturate(dot(surface.clearCoat.normal, lightingData.dirToCamera)); - clearCoatNdotV = max(clearCoatNdotV, 0.01f); // [GFX TODO][ATOM-4466] This is a current band-aid for specular noise at grazing angles. - float2 clearCoatBrdf = PassSrg::m_brdfMap.Sample(PassSrg::LinearSampler, GetBRDFTexCoords(surface.clearCoat.roughness, clearCoatNdotV)).rg; - - // clear coat uses fixed IOR = 1.5 represents polyurethane which is the most common material for gloss clear coat - // coat layer assumed to be dielectric thus don't need multiple scattering compensation - float3 clearCoatSpecularF0 = float3(0.04f, 0.04f, 0.04f); - float3 clearCoatIblSpecular = 0.0f; + float clearCoatNdotV = saturate(dot(surface.clearCoat.normal, lightingData.dirToCamera)); + clearCoatNdotV = max(clearCoatNdotV, 0.01f); // [GFX TODO][ATOM-4466] This is a current band-aid for specular noise at grazing angles. + float2 clearCoatBrdf = PassSrg::m_brdfMap.Sample(PassSrg::LinearSampler, GetBRDFTexCoords(surface.clearCoat.roughness, clearCoatNdotV)).rg; - ApplyIblSpecular( - surface.position, - surface.clearCoat.normal, - clearCoatSpecularF0, - surface.clearCoat.roughness, - lightingData.dirToCamera, - clearCoatBrdf, - clearCoatIblSpecular); + // clear coat uses fixed IOR = 1.5 represents polyurethane which is the most common material for gloss clear coat + // coat layer assumed to be dielectric thus don't need multiple scattering compensation + float3 clearCoatSpecularF0 = float3(0.04f, 0.04f, 0.04f); + float3 clearCoatIblSpecular = GetIblSpecular(surface.position, surface.clearCoat.normal, clearCoatSpecularF0, surface.clearCoat.roughness, lightingData.dirToCamera, clearCoatBrdf); - clearCoatIblSpecular *= surface.clearCoat.factor; + clearCoatIblSpecular *= surface.clearCoat.factor; - // attenuate base layer energy - float3 clearCoatResponse = FresnelSchlickWithRoughness(clearCoatNdotV, clearCoatSpecularF0, surface.clearCoat.roughness) * surface.clearCoat.factor; - iblSpecular = iblSpecular * (1.0 - clearCoatResponse) * (1.0 - clearCoatResponse) + clearCoatIblSpecular; - } + // attenuate base layer energy + float3 clearCoatResponse = FresnelSchlickWithRoughness(clearCoatNdotV, clearCoatSpecularF0, surface.clearCoat.roughness) * surface.clearCoat.factor; + iblSpecular = iblSpecular * (1.0 - clearCoatResponse) * (1.0 - clearCoatResponse) + clearCoatIblSpecular; } - - float iblExposureFactor = pow(2.0f, SceneSrg::m_iblExposure); lightingData.specularLighting += (iblSpecular * iblExposureFactor); } } diff --git a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/ShaderQualityOptions.azsli b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/ShaderQualityOptions.azsli index 907e67ada5..d6fb259548 100644 --- a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/ShaderQualityOptions.azsli +++ b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/ShaderQualityOptions.azsli @@ -18,7 +18,8 @@ #ifdef QUALITY_LOW_END -#define UNIFIED_FORWARD_OUTPUT 1 +#define UNIFIED_FORWARD_OUTPUT 1 +#define FORCE_IBL_IN_FORWARD_PASS 1 #endif diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Pass/Pass.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Pass/Pass.h index b0d6bd4117..1cba71ae7e 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Pass/Pass.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Pass/Pass.h @@ -381,6 +381,7 @@ namespace AZ uint64_t m_createdByPassRequest : 1; uint64_t m_initialized : 1; uint64_t m_enabled : 1; + uint64_t m_parentEnabled : 1; uint64_t m_alreadyCreated : 1; uint64_t m_alreadyReset : 1; uint64_t m_alreadyPrepared : 1; diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/Pass.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/Pass.cpp index 9401d1a9e0..f93d661b0f 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/Pass.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/Pass.cpp @@ -93,11 +93,12 @@ namespace AZ void Pass::SetEnabled(bool enabled) { m_flags.m_enabled = enabled; + OnHierarchyChange(); } bool Pass::IsEnabled() const { - return m_flags.m_enabled; + return m_flags.m_enabled && (m_flags.m_parentEnabled || m_parent == nullptr); } // --- Error Logging --- @@ -140,6 +141,7 @@ namespace AZ } // Set new tree depth and path + m_flags.m_parentEnabled = m_parent->IsEnabled(); m_treeDepth = m_parent->m_treeDepth + 1; m_path = ConcatPassName(m_parent->m_path, m_name); m_flags.m_partOfHierarchy = m_parent->m_flags.m_partOfHierarchy; From b08643d9da90d0215ed5b22efcc3716fdaa90622 Mon Sep 17 00:00:00 2001 From: sphrose <82213493+sphrose@users.noreply.github.com> Date: Tue, 11 May 2021 11:24:51 +0100 Subject: [PATCH 13/69] Use renamed functions in stylesheet. --- .../AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.hxx | 2 +- Code/Sandbox/Editor/Style/Editor.qss | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.hxx b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.hxx index 58bf3bb9f0..1c17cab69f 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.hxx +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.hxx @@ -44,7 +44,7 @@ namespace AzToolsFramework Q_PROPERTY(bool hasChildRows READ HasChildRows); Q_PROPERTY(bool isTopLevel READ IsTopLevel); Q_PROPERTY(int getLevel READ GetLevel); - Q_PROPERTY(bool isSectionSeparator READ IsSectionSeparator); + Q_PROPERTY(bool canBeReordered READ CanBeReordered); Q_PROPERTY(bool appendDefaultLabelToName READ GetAppendDefaultLabelToName WRITE AppendDefaultLabelToName) public: AZ_CLASS_ALLOCATOR(PropertyRowWidget, AZ::SystemAllocator, 0) diff --git a/Code/Sandbox/Editor/Style/Editor.qss b/Code/Sandbox/Editor/Style/Editor.qss index 7887560105..fa7d67dd43 100644 --- a/Code/Sandbox/Editor/Style/Editor.qss +++ b/Code/Sandbox/Editor/Style/Editor.qss @@ -38,7 +38,7 @@ AzToolsFramework--ComponentPaletteWidget > QTreeView background-color: #222222; } -AzToolsFramework--PropertyRowWidget[isSectionSeparator="true"] QLabel#Name +AzToolsFramework--PropertyRowWidget[canBeReordered="true"] QLabel#Name { font-weight: bold; } From 9775822778ec2cf8003ea86f455906f75bce1c14 Mon Sep 17 00:00:00 2001 From: scottr Date: Tue, 11 May 2021 16:09:16 -0700 Subject: [PATCH 14/69] [cpack_installer] remove wxs file ext from lfs filter --- .gitattributes | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitattributes b/.gitattributes index 1755def66a..55b43e4ba7 100644 --- a/.gitattributes +++ b/.gitattributes @@ -115,5 +115,4 @@ *.wav filter=lfs diff=lfs merge=lfs -text *.webm filter=lfs diff=lfs merge=lfs -text *.wem filter=lfs diff=lfs merge=lfs -text -*.wxs filter=lfs diff=lfs merge=lfs -text *.zip filter=lfs diff=lfs merge=lfs -text From c777e2e35301cd0054fe39e8fdccb5e632d48a21 Mon Sep 17 00:00:00 2001 From: scottr Date: Tue, 11 May 2021 18:07:19 -0700 Subject: [PATCH 15/69] [cpack_installer] some cpack cleanup and prep for online installer support (pre/post build steps) --- cmake/Packaging.cmake | 28 +++++++++++++------ .../Platform/Windows/PackagingPostBuild.cmake | 12 ++++++++ .../Platform/Windows/Packaging_windows.cmake | 8 ++++-- .../Windows/platform_windows_files.cmake | 1 + 4 files changed, 38 insertions(+), 11 deletions(-) create mode 100644 cmake/Platform/Windows/PackagingPostBuild.cmake diff --git a/cmake/Packaging.cmake b/cmake/Packaging.cmake index 4f6565edc7..e398ea7509 100644 --- a/cmake/Packaging.cmake +++ b/cmake/Packaging.cmake @@ -13,28 +13,38 @@ if(NOT PAL_TRAIT_BUILD_CPACK_SUPPORTED) return() endif() -ly_get_absolute_pal_filename(pal_dir ${CMAKE_SOURCE_DIR}/cmake/Platform/${PAL_HOST_PLATFORM_NAME}) -include(${pal_dir}/Packaging_${PAL_HOST_PLATFORM_NAME_LOWERCASE}.cmake) - -# if we get here and the generator hasn't been set, then a non fatal error occurred disabling packaging support -if(NOT CPACK_GENERATOR) - return() -endif() +# set the common cpack variables first so they are accessible via configure_file +# when the platforms specific properties are applied below +set(LY_INSTALLER_DOWNLOAD_URL "" CACHE PATH "URL embded into the installer to download additional artifacts") set(CPACK_PACKAGE_VENDOR "${PROJECT_NAME}") set(CPACK_PACKAGE_VERSION "${LY_VERSION_STRING}") set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Installation Tool") string(TOLOWER ${PROJECT_NAME} _project_name_lower) -set(CPACK_PACKAGE_FILE_NAME "${_project_name_lower}_installer") +set(CPACK_PACKAGE_FILE_NAME "${_project_name_lower}_${LY_VERSION_STRING}_installer") set(DEFAULT_LICENSE_NAME "Apache-2.0") -set(DEFAULT_LICENSE_FILE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE.txt") +set(DEFAULT_LICENSE_FILE "${CMAKE_SOURCE_DIR}/LICENSE.txt") set(CPACK_RESOURCE_FILE_LICENSE ${DEFAULT_LICENSE_FILE}) set(CPACK_PACKAGE_INSTALL_DIRECTORY "${CPACK_PACKAGE_VENDOR}/${CPACK_PACKAGE_VERSION}") +# custom cpack cache variables for use in pre/post build scripts +set(CPACK_SOURCE_DIR ${CMAKE_SOURCE_DIR}/cmake) +set(CPACK_BINARY_DIR ${CMAKE_BINARY_DIR}/installer) +set(CPACK_DOWNLOAD_URL ${LY_INSTALLER_DOWNLOAD_URL}) + +# attempt to apply platform specific settings +ly_get_absolute_pal_filename(pal_dir ${CMAKE_SOURCE_DIR}/cmake/Platform/${PAL_HOST_PLATFORM_NAME}) +include(${pal_dir}/Packaging_${PAL_HOST_PLATFORM_NAME_LOWERCASE}.cmake) + +# if we get here and the generator hasn't been set, then a non fatal error occurred disabling packaging support +if(NOT CPACK_GENERATOR) + return() +endif() + # IMPORTANT: required to be included AFTER setting all property overrides include(CPack REQUIRED) diff --git a/cmake/Platform/Windows/PackagingPostBuild.cmake b/cmake/Platform/Windows/PackagingPostBuild.cmake new file mode 100644 index 0000000000..fe57904003 --- /dev/null +++ b/cmake/Platform/Windows/PackagingPostBuild.cmake @@ -0,0 +1,12 @@ +# +# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +# its licensors. +# +# For complete copyright and license terms please see the LICENSE at the root of this +# distribution (the "License"). All use of this software is governed by the License, +# or, if provided, by the license below or the license accompanying this file. Do not +# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# + +message(STATUS "Hello from CPack post build!") diff --git a/cmake/Platform/Windows/Packaging_windows.cmake b/cmake/Platform/Windows/Packaging_windows.cmake index 8aa6f2386d..ba3ce011a4 100644 --- a/cmake/Platform/Windows/Packaging_windows.cmake +++ b/cmake/Platform/Windows/Packaging_windows.cmake @@ -32,7 +32,7 @@ set(CPACK_GENERATOR "WIX") # however, they are unique for each run. instead, let's do the auto generation here and add it to # the cache for run persistence. an additional cache file will be used to store the information on # the original generation so we still have the ability to detect if they are still being used. -set(_guid_cache_file "${CMAKE_BINARY_DIR}/installer/wix_guid_cache.cmake") +set(_guid_cache_file "${CPACK_BINARY_DIR}/wix_guid_cache.cmake") if(NOT EXISTS ${_guid_cache_file}) set(_wix_guid_namespace "6D43F57A-2917-4AD9-B758-1F13CDB08593") @@ -89,4 +89,8 @@ endif() set(CPACK_WIX_PRODUCT_GUID ${LY_WIX_PRODUCT_GUID}) set(CPACK_WIX_UPGRADE_GUID ${LY_WIX_UPGRADE_GUID}) -set(CPACK_WIX_TEMPLATE "${CMAKE_SOURCE_DIR}/cmake/Platform/Windows/PackagingTemplate.wxs.in") +set(CPACK_WIX_TEMPLATE "${CPACK_SOURCE_DIR}/Platform/Windows/PackagingTemplate.wxs.in") + +set(CPACK_POST_BUILD_SCRIPTS + ${CPACK_SOURCE_DIR}/Platform/Windows/PackagingPostBuild.cmake +) diff --git a/cmake/Platform/Windows/platform_windows_files.cmake b/cmake/Platform/Windows/platform_windows_files.cmake index 2fc869b43e..579621d5ea 100644 --- a/cmake/Platform/Windows/platform_windows_files.cmake +++ b/cmake/Platform/Windows/platform_windows_files.cmake @@ -24,5 +24,6 @@ set(FILES PALDetection_windows.cmake Install_windows.cmake Packaging_windows.cmake + PackagingPostBuild.cmake PackagingTemplate.wxs.in ) From f5e91c6e4284f00e64db5cc4ba4e0678be111b1c Mon Sep 17 00:00:00 2001 From: antonmic Date: Wed, 12 May 2021 10:39:18 -0700 Subject: [PATCH 16/69] Added low end shaders in StandardPBR_ShaderEnable.lua --- .../Assets/Materials/Types/StandardPBR_ShaderEnable.lua | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_ShaderEnable.lua b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_ShaderEnable.lua index 7c3d989c35..2733713122 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_ShaderEnable.lua +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_ShaderEnable.lua @@ -29,26 +29,33 @@ function Process(context) local depthPass = context:GetShaderByTag("DepthPass") local shadowMap = context:GetShaderByTag("Shadowmap") local forwardPassEDS = context:GetShaderByTag("ForwardPass_EDS") + local lowEndForwardEDS = context:GetShaderByTag("LowEndForward_EDS") + local depthPassWithPS = context:GetShaderByTag("DepthPass_WithPS") local shadowMapWitPS = context:GetShaderByTag("Shadowmap_WithPS") local forwardPass = context:GetShaderByTag("ForwardPass") + local lowEndForward = context:GetShaderByTag("LowEndForward") if parallaxEnabled and parallaxPdoEnabled then depthPass:SetEnabled(false) shadowMap:SetEnabled(false) forwardPassEDS:SetEnabled(false) + lowEndForwardEDS:SetEnabled(false) depthPassWithPS:SetEnabled(true) shadowMapWitPS:SetEnabled(true) forwardPass:SetEnabled(true) + lowEndForward:SetEnabled(true) else depthPass:SetEnabled(opacityMode == OpacityMode_Opaque) shadowMap:SetEnabled(opacityMode == OpacityMode_Opaque) forwardPassEDS:SetEnabled((opacityMode == OpacityMode_Opaque) or (opacityMode == OpacityMode_Blended) or (opacityMode == OpacityMode_TintedTransparent)) + lowEndForwardEDS:SetEnabled((opacityMode == OpacityMode_Opaque) or (opacityMode == OpacityMode_Blended) or (opacityMode == OpacityMode_TintedTransparent)) depthPassWithPS:SetEnabled(opacityMode == OpacityMode_Cutout) shadowMapWitPS:SetEnabled(opacityMode == OpacityMode_Cutout) forwardPass:SetEnabled(opacityMode == OpacityMode_Cutout) + lowEndForward:SetEnabled(opacityMode == OpacityMode_Cutout) end context:GetShaderByTag("DepthPassTransparentMin"):SetEnabled((opacityMode == OpacityMode_Blended) or (opacityMode == OpacityMode_TintedTransparent)) From 92b7099d78953eef8552633201b98d8f07597529 Mon Sep 17 00:00:00 2001 From: antonmic Date: Wed, 12 May 2021 11:10:13 -0700 Subject: [PATCH 17/69] Some clean up --- .../Common/Assets/Materials/Types/StandardPBR.materialtype | 7 ------- .../Assets/Materials/Types/StandardPBR_ForwardPass.azsl | 4 ++-- .../Assets/ShaderLib/Atom/Features/PBR/Lights/Ibl.azsli | 3 +++ .../ShaderLib/Atom/Features/ShaderQualityOptions.azsli | 4 +--- Gems/Atom/Feature/Common/Assets/Shaders/SkyBox/SkyBox.azsl | 6 ++---- 5 files changed, 8 insertions(+), 16 deletions(-) diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR.materialtype b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR.materialtype index 47d8a9d9d5..a9a3e9e09b 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR.materialtype +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR.materialtype @@ -77,13 +77,6 @@ ], "properties": { "general": [ - { - "id": "useLowEndShader", - "displayName": "Use Low End", - "description": "Whether to use the low end shader.", - "type": "Bool", - "defaultValue": false - }, { "id": "applySpecularAA", "displayName": "Apply Specular AA", diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_ForwardPass.azsl b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_ForwardPass.azsl index 1e6fafda9b..7ae5934d4f 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_ForwardPass.azsl +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_ForwardPass.azsl @@ -321,7 +321,7 @@ ForwardPassOutputWithDepth StandardPbr_ForwardPassPS(VSOutput IN, bool isFrontFa #ifdef UNIFIED_FORWARD_OUTPUT OUT.m_color.rgb = lightingOutput.m_diffuseColor.rgb + lightingOutput.m_specularColor.rgb; - OUT.m_color.a = 1.0f; + OUT.m_color.a = lightingOutput.m_diffuseColor.a; OUT.m_depth = depth; #else OUT.m_diffuseColor = lightingOutput.m_diffuseColor; @@ -344,7 +344,7 @@ ForwardPassOutput StandardPbr_ForwardPassPS_EDS(VSOutput IN, bool isFrontFace : #ifdef UNIFIED_FORWARD_OUTPUT OUT.m_color.rgb = lightingOutput.m_diffuseColor.rgb + lightingOutput.m_specularColor.rgb; - OUT.m_color.a = 1.0f; + OUT.m_color.a = lightingOutput.m_diffuseColor.a; #else OUT.m_diffuseColor = lightingOutput.m_diffuseColor; OUT.m_specularColor = lightingOutput.m_specularColor; diff --git a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Lights/Ibl.azsli b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Lights/Ibl.azsli index 721c48835d..3e3544fe9e 100644 --- a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Lights/Ibl.azsli +++ b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Lights/Ibl.azsli @@ -12,6 +12,9 @@ #pragma once +// --- Static Options Available --- +// FORCE_IBL_IN_FORWARD_PASS - forces IBL lighting to be run in the forward pass, used in pipelines that don't have a reflection pass + #include #include diff --git a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/ShaderQualityOptions.azsli b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/ShaderQualityOptions.azsli index d6fb259548..6e89269f8d 100644 --- a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/ShaderQualityOptions.azsli +++ b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/ShaderQualityOptions.azsli @@ -12,9 +12,7 @@ #pragma once -// These are a list of quality options to specify as macros (either in azsl or in shader files) -// -// QUALITY_LOW_END +// This file translates quality option macros like QUALITY_LOW_END to their relevant settings #ifdef QUALITY_LOW_END diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/SkyBox/SkyBox.azsl b/Gems/Atom/Feature/Common/Assets/Shaders/SkyBox/SkyBox.azsl index 4b3e9536b7..1bebb2ec47 100644 --- a/Gems/Atom/Feature/Common/Assets/Shaders/SkyBox/SkyBox.azsl +++ b/Gems/Atom/Feature/Common/Assets/Shaders/SkyBox/SkyBox.azsl @@ -10,10 +10,8 @@ * */ -// Static Options: -// -// SKYBOX_TWO_OUTPUTS - Allows the skybox to render to two rendertargets instead of one - +// --- Static Options Available --- +// SKYBOX_TWO_OUTPUTS - Skybox renders to two rendertargets instead of one (SkyBox_TwoOutputs.pass writes to specular and reflection targets) #include #include From b52388f5ebfb5af09505a99fe1e416ae12907dd0 Mon Sep 17 00:00:00 2001 From: antonmic Date: Wed, 12 May 2021 11:11:45 -0700 Subject: [PATCH 18/69] Remove unused file --- .../PBR/LowEndForwardPassOutput.azsli | 32 ------------------- 1 file changed, 32 deletions(-) delete mode 100644 Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/LowEndForwardPassOutput.azsli diff --git a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/LowEndForwardPassOutput.azsli b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/LowEndForwardPassOutput.azsli deleted file mode 100644 index acc215f1c9..0000000000 --- a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/LowEndForwardPassOutput.azsli +++ /dev/null @@ -1,32 +0,0 @@ -/* -* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -* its licensors. -* -* For complete copyright and license terms please see the LICENSE at the root of this -* distribution (the "License"). All use of this software is governed by the License, -* or, if provided, by the license below or the license accompanying this file. Do not -* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* -*/ - -struct ForwardPassOutput -{ - float4 m_diffuseColor : SV_Target0; //!< RGB = Diffuse Lighting, A = Blend Alpha (for blended surfaces) OR A = special encoding of surfaceScatteringFactor, m_subsurfaceScatteringQuality, o_enableSubsurfaceScattering - float4 m_specularColor : SV_Target1; //!< RGB = Specular Lighting, A = Unused - float4 m_albedo : SV_Target2; //!< RGB = Surface albedo pre-multiplied by other factors that will be multiplied later by diffuse GI, A = specularOcclusion - float4 m_specularF0 : SV_Target3; //!< RGB = Specular F0, A = roughness - float4 m_normal : SV_Target4; //!< RGB10 = EncodeNormalSignedOctahedron(worldNormal), A2 = multiScatterCompensationEnabled -}; - -struct ForwardPassOutputWithDepth -{ - // See above for descriptions of special encodings - - float4 m_diffuseColor : SV_Target0; - float4 m_specularColor : SV_Target1; - float4 m_albedo : SV_Target2; - float4 m_specularF0 : SV_Target3; - float4 m_normal : SV_Target4; - float m_depth : SV_Depth; -}; From 36a79aca8a353316f560f3079ff2b66cb5120996 Mon Sep 17 00:00:00 2001 From: sphrose <82213493+sphrose@users.noreply.github.com> Date: Thu, 13 May 2021 10:56:39 +0100 Subject: [PATCH 19/69] Remove redundant function. --- .../UI/PropertyEditor/PropertyRowWidget.cpp | 7 +------ .../UI/PropertyEditor/PropertyRowWidget.hxx | 1 - 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.cpp index 85784d61e0..890253b528 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.cpp @@ -126,7 +126,7 @@ namespace AzToolsFramework { QStylePainter p(this); - if (IsReorderableRow()) + if (CanBeReordered()) { const QPen linePen(QColor(0x3B3E3F)); p.setPen(linePen); @@ -1332,11 +1332,6 @@ namespace AzToolsFramework return canBeTopLevel(this); } - bool PropertyRowWidget::IsReorderableRow() const - { - return CanBeReordered(); - } - bool PropertyRowWidget::GetAppendDefaultLabelToName() { return false; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.hxx b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.hxx index 1c17cab69f..79121403c9 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.hxx +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.hxx @@ -83,7 +83,6 @@ namespace AzToolsFramework PropertyRowWidget* GetParentRow() const { return m_parentRow; } int GetLevel() const; bool IsTopLevel() const; - bool IsReorderableRow() const; // Remove the default label and append the text to the name label. bool GetAppendDefaultLabelToName(); From 8a39f9f1b474640b4b525423b0ec3fa9c32c187e Mon Sep 17 00:00:00 2001 From: puvvadar Date: Thu, 13 May 2021 15:31:37 -0700 Subject: [PATCH 20/69] Streamline MP Ctrl+G logic via MultiplayerEditorConnection --- .../AzNetworking/TcpTransport/TcpSocket.cpp | 2 +- .../UdpTransport/UdpNetworkInterface.cpp | 2 +- .../AzNetworking/UdpTransport/UdpSocket.cpp | 2 +- Gems/Multiplayer/Code/CMakeLists.txt | 25 +-- .../AutoGen/Multiplayer.AutoPackets.xml | 7 +- .../AutoGen/MultiplayerEditor.AutoPackets.xml | 14 ++ .../Editor/MultiplayerEditorConnection.cpp | 162 +++++++++++++++ .../Editor/MultiplayerEditorConnection.h | 55 ++++++ .../Editor/MultiplayerEditorDispatcher.cpp | 21 -- .../Editor/MultiplayerEditorDispatcher.h | 36 ---- .../{ => Editor}/MultiplayerEditorGem.cpp | 2 +- .../{ => Editor}/MultiplayerEditorGem.h | 0 .../MultiplayerEditorSystemComponent.cpp | 187 ++++++++---------- .../Editor/MultiplayerEditorSystemComponent.h | 13 +- .../Source/MultiplayerSystemComponent.cpp | 42 ++-- .../Code/Source/MultiplayerSystemComponent.h | 8 +- .../Pipeline/NetworkPrefabProcessor.cpp | 4 + .../Code/multiplayer_editor_files.cmake | 2 - .../multiplayer_editor_shared_files.cmake | 4 +- Gems/Multiplayer/Code/multiplayer_files.cmake | 3 + 20 files changed, 353 insertions(+), 238 deletions(-) create mode 100644 Gems/Multiplayer/Code/Source/AutoGen/MultiplayerEditor.AutoPackets.xml create mode 100644 Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.cpp create mode 100644 Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.h delete mode 100644 Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorDispatcher.cpp delete mode 100644 Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorDispatcher.h rename Gems/Multiplayer/Code/Source/{ => Editor}/MultiplayerEditorGem.cpp (97%) rename Gems/Multiplayer/Code/Source/{ => Editor}/MultiplayerEditorGem.h (100%) diff --git a/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpSocket.cpp b/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpSocket.cpp index e8c30d0816..70000dc9a8 100644 --- a/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpSocket.cpp +++ b/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpSocket.cpp @@ -176,7 +176,7 @@ namespace AzNetworking if (::bind(aznumeric_cast(m_socketFd), (const sockaddr*)&hints, sizeof(hints)) != 0) { const int32_t error = GetLastNetworkError(); - AZLOG_ERROR("Failed to bind socket (%d:%s)", error, GetNetworkErrorDesc(error)); + AZLOG_ERROR("Failed to bind TCP socket to port %u (%d:%s)", uint32_t(port), error, GetNetworkErrorDesc(error)); return false; } diff --git a/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpNetworkInterface.cpp b/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpNetworkInterface.cpp index 870545e6c8..5676d48150 100644 --- a/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpNetworkInterface.cpp +++ b/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpNetworkInterface.cpp @@ -162,7 +162,7 @@ namespace AzNetworking const UdpReaderThread::ReceivedPackets* packets = m_readerThread.GetReceivedPackets(m_socket.get()); if (packets == nullptr) { - AZ_Assert(false, "nullptr was retrieved for the received packet buffer, check that the socket has been registered with the reader thread"); + // Socket is not yet registered with the reader thread and is likely still pending, try again later return; } diff --git a/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpSocket.cpp b/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpSocket.cpp index e642c87623..cbb5f8e6c0 100644 --- a/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpSocket.cpp +++ b/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpSocket.cpp @@ -82,7 +82,7 @@ namespace AzNetworking if (::bind(static_cast(m_socketFd), (const sockaddr *)&hints, sizeof(hints)) != 0) { const int32_t error = GetLastNetworkError(); - AZLOG_ERROR("Failed to bind socket to port %u (%d:%s)", uint32_t(port), error, GetNetworkErrorDesc(error)); + AZLOG_ERROR("Failed to bind UDP socket to port %u (%d:%s)", uint32_t(port), error, GetNetworkErrorDesc(error)); return false; } } diff --git a/Gems/Multiplayer/Code/CMakeLists.txt b/Gems/Multiplayer/Code/CMakeLists.txt index 46f56ef315..3f0c2936b7 100644 --- a/Gems/Multiplayer/Code/CMakeLists.txt +++ b/Gems/Multiplayer/Code/CMakeLists.txt @@ -77,12 +77,12 @@ if (PAL_TRAIT_BUILD_HOST_TOOLS) AZ::AzToolsFramework Gem::Multiplayer.Static ) - + ly_add_target( - NAME Multiplayer.Editor.Static STATIC + NAME Multiplayer.Editor GEM_MODULE NAMESPACE Gem FILES_CMAKE - multiplayer_editor_files.cmake + multiplayer_editor_shared_files.cmake COMPILE_DEFINITIONS PUBLIC MULTIPLAYER_EDITOR @@ -94,7 +94,7 @@ if (PAL_TRAIT_BUILD_HOST_TOOLS) PUBLIC Include BUILD_DEPENDENCIES - PUBLIC + PRIVATE Legacy::CryCommon Legacy::Editor.Headers AZ::AzCore @@ -102,23 +102,6 @@ if (PAL_TRAIT_BUILD_HOST_TOOLS) AZ::AzNetworking AZ::AzToolsFramework Gem::Multiplayer.Static - ) - - ly_add_target( - NAME Multiplayer.Editor GEM_MODULE - NAMESPACE Gem - FILES_CMAKE - multiplayer_editor_shared_files.cmake - INCLUDE_DIRECTORIES - PRIVATE - . - Source - ${pal_source_dir} - PUBLIC - Include - BUILD_DEPENDENCIES - PRIVATE - Gem::Multiplayer.Editor.Static Gem::Multiplayer.Tools ) diff --git a/Gems/Multiplayer/Code/Source/AutoGen/Multiplayer.AutoPackets.xml b/Gems/Multiplayer/Code/Source/AutoGen/Multiplayer.AutoPackets.xml index 84a7207f0c..633218c215 100644 --- a/Gems/Multiplayer/Code/Source/AutoGen/Multiplayer.AutoPackets.xml +++ b/Gems/Multiplayer/Code/Source/AutoGen/Multiplayer.AutoPackets.xml @@ -59,10 +59,5 @@ - - - - - - + diff --git a/Gems/Multiplayer/Code/Source/AutoGen/MultiplayerEditor.AutoPackets.xml b/Gems/Multiplayer/Code/Source/AutoGen/MultiplayerEditor.AutoPackets.xml new file mode 100644 index 0000000000..986860e822 --- /dev/null +++ b/Gems/Multiplayer/Code/Source/AutoGen/MultiplayerEditor.AutoPackets.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.cpp b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.cpp new file mode 100644 index 0000000000..dcf9c134da --- /dev/null +++ b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.cpp @@ -0,0 +1,162 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Multiplayer +{ + using namespace AzNetworking; + + static const AZStd::string_view s_networkInterfaceName("MultiplayerNetworkInterface"); + static const AZStd::string_view s_networkEditorInterfaceName("MultiplayerEditorNetworkInterface"); + static constexpr AZStd::string_view DefaultEditorIp = "127.0.0.1"; + static constexpr uint16_t DefaultServerPort = 30090; + static constexpr uint16_t DefaultServerEditorPort = 30091; + + static AZStd::vector buffer; + static AZ::IO::ByteContainerStream> s_byteStream(&buffer); + + AZ_CVAR(bool, editorsv_isDedicated, false, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "Whether to init as a server expecting data from an Editor. Do not modify unless you're sure of what you're doing."); + + MultiplayerEditorConnection::MultiplayerEditorConnection() + { + m_networkEditorInterface = AZ::Interface::Get()->CreateNetworkInterface( + AZ::Name(s_networkEditorInterfaceName), ProtocolType::Tcp, TrustZone::ExternalClientToServer, *this); + if (editorsv_isDedicated) + { + m_networkEditorInterface->Listen(DefaultServerEditorPort); + } + } + + bool MultiplayerEditorConnection::HandleRequest + ( + [[maybe_unused]] AzNetworking::IConnection* connection, + [[maybe_unused]] const IPacketHeader& packetHeader, + [[maybe_unused]] MultiplayerEditorPackets::EditorServerInit& packet + ) + { + // Editor Server Init is intended for non-release targets + if (!packet.GetLastUpdate()) + { + // More packets are expected, flush this to the buffer + s_byteStream.Write(TcpPacketEncodingBuffer::GetCapacity(), reinterpret_cast(packet.ModifyAssetData().GetBuffer())); + } + else + { + // This is the last expected packet, flush it to the buffer + s_byteStream.Write(packet.GetAssetData().GetSize(), reinterpret_cast(packet.ModifyAssetData().GetBuffer())); + + // Read all assets out of the buffer + s_byteStream.Seek(0, AZ::IO::GenericStream::SeekMode::ST_SEEK_BEGIN); + AZStd::vector> assetData; + while (s_byteStream.GetCurPos() < s_byteStream.GetLength()) + { + AZ::Data::AssetLoadBehavior assetLoadBehavior; + s_byteStream.Read(sizeof(AZ::Data::AssetLoadBehavior), reinterpret_cast(&assetLoadBehavior)); + + AZ::Data::AssetData* assetDatum = AZ::Utils::LoadObjectFromStream(s_byteStream, nullptr); + AZ::Data::Asset asset = AZ::Data::Asset(assetDatum, assetLoadBehavior); + + /* + // Register Asset to AssetManager + */ + + assetData.push_back(asset); + } + + // Now that we've deserialized, clear the byte stream + s_byteStream.Seek(0, AZ::IO::GenericStream::SeekMode::ST_SEEK_BEGIN); + s_byteStream.Truncate(); + + /* + // Hand-off our resultant assets + */ + + AZLOG_INFO("Editor Server completed asset receive, responding to Editor..."); + if (connection->SendReliablePacket(MultiplayerEditorPackets::EditorServerReady())) + { + // Setup the normal multiplayer connection + AZ::Interface::Get()->InitializeMultiplayer(MultiplayerAgentType::DedicatedServer); + INetworkInterface* networkInterface = AZ::Interface::Get()->RetrieveNetworkInterface(AZ::Name(s_networkInterfaceName)); + networkInterface->Listen(DefaultServerPort); + + return true; + } + else + { + return false; + } + } + + return true; + } + + bool MultiplayerEditorConnection::HandleRequest + ( + [[maybe_unused]] AzNetworking::IConnection* connection, + [[maybe_unused]] const IPacketHeader& packetHeader, + [[maybe_unused]] MultiplayerEditorPackets::EditorServerReady& packet + ) + { + if (connection->GetConnectionRole() == ConnectionRole::Connector) + { + // Receiving this packet means Editor sync is done, disconnect + connection->Disconnect(AzNetworking::DisconnectReason::TerminatedByClient, AzNetworking::TerminationEndpoint::Local); + + // Connect the Editor to the local server for Multiplayer simulation + AZ::Interface::Get()->InitializeMultiplayer(MultiplayerAgentType::Client); + INetworkInterface* networkInterface = AZ::Interface::Get()->RetrieveNetworkInterface(AZ::Name(s_networkInterfaceName)); + const IpAddress ipAddress(DefaultEditorIp.data(), DefaultServerEditorPort, networkInterface->GetType()); + networkInterface->Connect(ipAddress); + } + return true; + } + + ConnectResult MultiplayerEditorConnection::ValidateConnect + ( + [[maybe_unused]] const IpAddress& remoteAddress, + [[maybe_unused]] const IPacketHeader& packetHeader, + [[maybe_unused]] ISerializer& serializer + ) + { + return ConnectResult::Accepted; + } + + void MultiplayerEditorConnection::OnConnect([[maybe_unused]] AzNetworking::IConnection* connection) + { + ; + } + + bool MultiplayerEditorConnection::OnPacketReceived(AzNetworking::IConnection* connection, const IPacketHeader& packetHeader, ISerializer& serializer) + { + return MultiplayerEditorPackets::DispatchPacket(connection, packetHeader, serializer, *this); + } + + void MultiplayerEditorConnection::OnPacketLost([[maybe_unused]] IConnection* connection, [[maybe_unused]] PacketId packetId) + { + ; + } + + void MultiplayerEditorConnection::OnDisconnect([[maybe_unused]] AzNetworking::IConnection* connection, [[maybe_unused]] DisconnectReason reason, [[maybe_unused]] TerminationEndpoint endpoint) + { + ; + } +} diff --git a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.h b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.h new file mode 100644 index 0000000000..3621e3aee6 --- /dev/null +++ b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.h @@ -0,0 +1,55 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace AzNetworking +{ + class INetworkInterface; +} + +namespace Multiplayer +{ + //! MultiplayerEditorConnection is a connection listener to synchronize the Editor and a local server it launches + class MultiplayerEditorConnection final + : public AzNetworking::IConnectionListener + { + public: + MultiplayerEditorConnection(); + ~MultiplayerEditorConnection() = default; + + bool HandleRequest(AzNetworking::IConnection* connection, const AzNetworking::IPacketHeader& packetHeader, MultiplayerEditorPackets::EditorServerInit& packet); + bool HandleRequest(AzNetworking::IConnection* connection, const AzNetworking::IPacketHeader& packetHeader, MultiplayerEditorPackets::EditorServerReady& packet); + + //! IConnectionListener interface + //! @{ + AzNetworking::ConnectResult ValidateConnect(const AzNetworking::IpAddress& remoteAddress, const AzNetworking::IPacketHeader& packetHeader, AzNetworking::ISerializer& serializer) override; + void OnConnect(AzNetworking::IConnection* connection) override; + bool OnPacketReceived(AzNetworking::IConnection* connection, const AzNetworking::IPacketHeader& packetHeader, AzNetworking::ISerializer& serializer) override; + void OnPacketLost(AzNetworking::IConnection* connection, AzNetworking::PacketId packetId) override; + void OnDisconnect(AzNetworking::IConnection* connection, AzNetworking::DisconnectReason reason, AzNetworking::TerminationEndpoint endpoint) override; + //! @} + + private: + + AzNetworking::INetworkInterface* m_networkEditorInterface = nullptr; + }; +} diff --git a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorDispatcher.cpp b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorDispatcher.cpp deleted file mode 100644 index 470aa61cd0..0000000000 --- a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorDispatcher.cpp +++ /dev/null @@ -1,21 +0,0 @@ -/* - * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or - * its licensors. - * - * For complete copyright and license terms please see the LICENSE at the root of this - * distribution (the "License"). All use of this software is governed by the License, - * or, if provided, by the license below or the license accompanying this file. Do not - * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * - */ - -#include - -namespace Multiplayer -{ - MultiplayerEditorDispatcher::MultiplayerEditorDispatcher() - { - ; - } -} diff --git a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorDispatcher.h b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorDispatcher.h deleted file mode 100644 index c1058dc8a0..0000000000 --- a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorDispatcher.h +++ /dev/null @@ -1,36 +0,0 @@ -/* -* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -* its licensors. -* -* For complete copyright and license terms please see the LICENSE at the root of this -* distribution (the "License"). All use of this software is governed by the License, -* or, if provided, by the license below or the license accompanying this file. Do not -* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* -*/ - -#pragma once - -#include - -#include -#include -#include -#include - -#include - - -namespace Multiplayer -{ - //! MultiplayerEditorDispatcher is responsible for dispatching delta from the Editor to an Editor launched local server - class MultiplayerEditorDispatcher final - { - public: - MultiplayerEditorDispatcher(); - ~MultiplayerEditorDispatcher() = default; - - private: - }; -} diff --git a/Gems/Multiplayer/Code/Source/MultiplayerEditorGem.cpp b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorGem.cpp similarity index 97% rename from Gems/Multiplayer/Code/Source/MultiplayerEditorGem.cpp rename to Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorGem.cpp index 97c5e4d105..ae38ef1d6d 100644 --- a/Gems/Multiplayer/Code/Source/MultiplayerEditorGem.cpp +++ b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorGem.cpp @@ -13,7 +13,7 @@ #include #include #include -#include +#include #include #include diff --git a/Gems/Multiplayer/Code/Source/MultiplayerEditorGem.h b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorGem.h similarity index 100% rename from Gems/Multiplayer/Code/Source/MultiplayerEditorGem.h rename to Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorGem.h diff --git a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp index 15f00bdf80..4c3e8200a1 100644 --- a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp @@ -26,16 +26,16 @@ namespace Multiplayer { - static const AZStd::string_view s_networkInterfaceName("MultiplayerEditorServerInterface"); + static const AZStd::string_view s_networkEditorInterfaceName("MultiplayerEditorNetworkInterface"); using namespace AzNetworking; - AZ_CVAR(bool, editorsv_enabled, false, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, + AZ_CVAR(bool, editorsv_enabled, true, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "Whether Editor launching a local server to connect to is supported"); AZ_CVAR(AZ::CVarFixedString, editorsv_process, "", nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The server executable that should be run. Empty to use the current project's ServerLauncher"); - AZ_CVAR(AZ::CVarFixedString, sv_serveraddr, "127.0.0.1", nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The address of the server to connect to"); - AZ_CVAR(uint16_t, sv_port, 30091, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The port that the multiplayer editor gem will bind to for traffic"); + AZ_CVAR(AZ::CVarFixedString, editorsv_serveraddr, "127.0.0.1", nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The address of the server to connect to"); + AZ_CVAR(uint16_t, editorsv_port, 30091, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The port that the multiplayer editor gem will bind to for traffic"); void MultiplayerEditorSystemComponent::Reflect(AZ::ReflectContext* context) { @@ -70,16 +70,6 @@ namespace Multiplayer { AzFramework::GameEntityContextEventBus::Handler::BusConnect(); AzToolsFramework::EditorEvents::Bus::Handler::BusConnect(); - - // Setup a network interface handled by MultiplayerSystemComponent - if (m_editorNetworkInterface == nullptr) - { - AZ::Entity* systemEntity = this->GetEntity(); - MultiplayerSystemComponent* mpSysComponent = systemEntity->FindComponent(); - - m_editorNetworkInterface = AZ::Interface::Get()->CreateNetworkInterface( - AZ::Name(s_networkInterfaceName), ProtocolType::Tcp, TrustZone::ExternalClientToServer, *mpSysComponent); - } } void MultiplayerEditorSystemComponent::Deactivate() @@ -109,18 +99,16 @@ namespace Multiplayer } [[fallthrough]]; case eNotify_OnEndGameMode: - AZ::TickBus::Handler::BusDisconnect(); // Kill the configured server if it's active if (m_serverProcess) { m_serverProcess->TerminateProcess(0); m_serverProcess = nullptr; } - if (m_editorNetworkInterface) + INetworkInterface* editorNetworkInterface = AZ::Interface::Get()->RetrieveNetworkInterface(AZ::Name(s_networkEditorInterfaceName)); + if (editorNetworkInterface) { - // Disconnect the interface, connection management will clean it up - m_editorNetworkInterface->Disconnect(m_editorConnId, AzNetworking::DisconnectReason::TerminatedByUser); - m_editorConnId = AzNetworking::InvalidConnectionId; + editorNetworkInterface->Disconnect(m_editorConnId, AzNetworking::DisconnectReason::TerminatedByClient); } break; } @@ -133,116 +121,95 @@ namespace Multiplayer { AZ_Error("MultiplayerEditor", prefabEditorEntityOwnershipInterface != nullptr, "PrefabEditorEntityOwnershipInterface unavailable"); } - const AZStd::vector>& assetData = prefabEditorEntityOwnershipInterface->GetPlayInEditorAssetData(); - - AZStd::vector buffer; - AZ::IO::ByteContainerStream byteStream(&buffer); - - // Serialize Asset information and AssetData into a potentially large buffer - for (auto asset : assetData) - { - AZ::Data::AssetId assetId = asset.GetId(); - AZ::Data::AssetType assetType = asset.GetType(); - const AZStd::string& assetHint = asset.GetHint(); - AZ::IO::SizeType assetHintSize = assetHint.size(); - AZ::Data::AssetLoadBehavior assetLoadBehavior = asset.GetAutoLoadBehavior(); - - byteStream.Write(sizeof(AZ::Data::AssetId), reinterpret_cast(&assetId)); - byteStream.Write(sizeof(AZ::Data::AssetType), reinterpret_cast(&assetType)); - byteStream.Write(sizeof(assetHintSize), reinterpret_cast(&assetHintSize)); - byteStream.Write(assetHint.size(), assetHint.c_str()); - byteStream.Write(sizeof(AZ::Data::AssetLoadBehavior), reinterpret_cast(&assetLoadBehavior)); - - AZ::Utils::SaveObjectToStream(byteStream, AZ::DataStream::ST_BINARY, asset.GetData(), asset.GetData()->GetType()); - } // BeginGameMode and Prefab Processing have completed at this point IMultiplayerTools* mpTools = AZ::Interface::Get(); if (editorsv_enabled && mpTools != nullptr && mpTools->DidProcessNetworkPrefabs()) { - AZ::TickBus::Handler::BusConnect(); + const AZStd::vector>& assetData = prefabEditorEntityOwnershipInterface->GetPlayInEditorAssetData(); + + AZStd::vector buffer; + AZ::IO::ByteContainerStream byteStream(&buffer); - if (assetData.size() > 0) + // Serialize Asset information and AssetData into a potentially large buffer + for (auto asset : assetData) { - // Assemble the server's path - AZ::CVarFixedString serverProcess = editorsv_process; - if (serverProcess.empty()) - { - // If enabled but no process name is supplied, try this project's ServerLauncher - serverProcess = AZ::Utils::GetProjectName() + ".ServerLauncher"; - } - - AZ::IO::FixedMaxPathString serverPath = AZ::Utils::GetExecutableDirectory(); - if (!serverProcess.contains(AZ_TRAIT_OS_PATH_SEPARATOR)) - { - // If only the process name is specified, append that as well - serverPath.append(AZ_TRAIT_OS_PATH_SEPARATOR + serverProcess); - } - else - { - // If any path was already specified, then simply assign - serverPath = serverProcess; - } - - if (!serverProcess.ends_with(AZ_TRAIT_OS_EXECUTABLE_EXTENSION)) - { - // Add this platform's exe extension if it's not specified - serverPath.append(AZ_TRAIT_OS_EXECUTABLE_EXTENSION); - } - - // Start the configured server if it's available - AzFramework::ProcessLauncher::ProcessLaunchInfo processLaunchInfo; - processLaunchInfo.m_commandlineParameters = AZStd::string::format("\"%s\"", serverPath.c_str()); - processLaunchInfo.m_showWindow = true; - processLaunchInfo.m_processPriority = AzFramework::ProcessPriority::PROCESSPRIORITY_NORMAL; + AZ::Data::AssetLoadBehavior assetLoadBehavior = asset.GetAutoLoadBehavior(); + byteStream.Write(sizeof(AZ::Data::AssetLoadBehavior), reinterpret_cast(&assetLoadBehavior)); + + AZ::Utils::SaveObjectToStream(byteStream, AZ::DataStream::ST_BINARY, asset.GetData(), asset.GetData()->GetType()); + } - m_serverProcess = AzFramework::ProcessWatcher::LaunchProcess( - processLaunchInfo, AzFramework::ProcessCommunicationType::COMMUNICATOR_TYPE_NONE); + // Assemble the server's path + AZ::CVarFixedString serverProcess = editorsv_process; + if (serverProcess.empty()) + { + // If enabled but no process name is supplied, try this project's ServerLauncher + serverProcess = AZ::Utils::GetProjectName() + ".ServerLauncher"; } - } - // Now that the server has launched, attempt to connect the NetworkInterface - const AZ::CVarFixedString remoteAddress = sv_serveraddr; - m_editorConnId = m_editorNetworkInterface->Connect( - AzNetworking::IpAddress(remoteAddress.c_str(), sv_port, AzNetworking::ProtocolType::Tcp)); + AZ::IO::FixedMaxPathString serverPath = AZ::Utils::GetExecutableDirectory(); + if (!serverProcess.contains(AZ_TRAIT_OS_PATH_SEPARATOR)) + { + // If only the process name is specified, append that as well + serverPath.append(AZ_TRAIT_OS_PATH_SEPARATOR + serverProcess); + } + else + { + // If any path was already specified, then simply assign + serverPath = serverProcess; + } - // Read the buffer into EditorServerInit packets until we've flushed the whole thing - byteStream.Seek(0, AZ::IO::GenericStream::SeekMode::ST_SEEK_BEGIN); - - while (byteStream.GetCurPos() < byteStream.GetLength()) - { - MultiplayerPackets::EditorServerInit packet; - AzNetworking::TcpPacketEncodingBuffer& outBuffer = packet.ModifyAssetData(); - - // Size the packet's buffer appropriately - size_t readSize = TcpPacketEncodingBuffer::GetCapacity(); - size_t byteStreamSize = byteStream.GetLength() - byteStream.GetCurPos(); - if (byteStreamSize < readSize) + if (!serverProcess.ends_with(AZ_TRAIT_OS_EXECUTABLE_EXTENSION)) { - readSize = byteStreamSize; + // Add this platform's exe extension if it's not specified + serverPath.append(AZ_TRAIT_OS_EXECUTABLE_EXTENSION); } - outBuffer.Resize(readSize); - byteStream.Read(readSize, outBuffer.GetBuffer()); + // Start the configured server if it's available + AzFramework::ProcessLauncher::ProcessLaunchInfo processLaunchInfo; + processLaunchInfo.m_commandlineParameters = AZStd::string::format("\"%s\" --editorsv_isDedicated true", serverPath.c_str()); + processLaunchInfo.m_showWindow = true; + processLaunchInfo.m_processPriority = AzFramework::ProcessPriority::PROCESSPRIORITY_NORMAL; - // If we've run out of buffer, mark that we're done - if (byteStream.GetCurPos() == byteStream.GetLength()) + // Launch the Server and give it a few seconds to boot up + m_serverProcess = AzFramework::ProcessWatcher::LaunchProcess( + processLaunchInfo, AzFramework::ProcessCommunicationType::COMMUNICATOR_TYPE_NONE); + AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(15000)); + + // Now that the server has launched, attempt to connect the NetworkInterface + const AZ::CVarFixedString remoteAddress = editorsv_serveraddr; + INetworkInterface* editorNetworkInterface = AZ::Interface::Get()->RetrieveNetworkInterface(AZ::Name(s_networkEditorInterfaceName)); + m_editorConnId = editorNetworkInterface->Connect( + AzNetworking::IpAddress(remoteAddress.c_str(), editorsv_port, AzNetworking::ProtocolType::Tcp)); + + // Read the buffer into EditorServerInit packets until we've flushed the whole thing + byteStream.Seek(0, AZ::IO::GenericStream::SeekMode::ST_SEEK_BEGIN); + + while (byteStream.GetCurPos() < byteStream.GetLength()) { - packet.SetLastUpdate(true); - } - m_editorNetworkInterface->SendReliablePacket(m_editorConnId, packet); - } + MultiplayerEditorPackets::EditorServerInit packet; + AzNetworking::TcpPacketEncodingBuffer& outBuffer = packet.ModifyAssetData(); - } + // Size the packet's buffer appropriately + size_t readSize = TcpPacketEncodingBuffer::GetCapacity(); + size_t byteStreamSize = byteStream.GetLength() - byteStream.GetCurPos(); + if (byteStreamSize < readSize) + { + readSize = byteStreamSize; + } - void MultiplayerEditorSystemComponent::OnTick([[maybe_unused]] float deltaTime, [[maybe_unused]] AZ::ScriptTimePoint time) - { + outBuffer.Resize(readSize); + byteStream.Read(readSize, outBuffer.GetBuffer()); - } + // If we've run out of buffer, mark that we're done + if (byteStream.GetCurPos() == byteStream.GetLength()) + { + packet.SetLastUpdate(true); + } + editorNetworkInterface->SendReliablePacket(m_editorConnId, packet); + } + } - int MultiplayerEditorSystemComponent::GetTickOrder() - { - // Tick immediately after the network system component - return AZ::TICK_PLACEMENT + 1; } } diff --git a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.h b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.h index d43d8747b9..569092e981 100644 --- a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.h +++ b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.h @@ -14,6 +14,8 @@ #include +#include + #include #include #include @@ -34,7 +36,6 @@ namespace Multiplayer //! Multiplayer system component wraps the bridging logic between the game and transport layer. class MultiplayerEditorSystemComponent final : public AZ::Component - , private AZ::TickBus::Handler , private AzFramework::GameEntityContextEventBus::Handler , private AzToolsFramework::EditorEvents::Bus::Handler , private IEditorNotifyListener @@ -61,14 +62,7 @@ namespace Multiplayer void NotifyRegisterViews() override; //! @} - private: - - //! AZ::TickBus::Handler overrides. - //! @{ - void OnTick(float deltaTime, AZ::ScriptTimePoint time) override; - int GetTickOrder() override; - //! @} - + private: //! EditorEvents::Handler overrides //! @{ void OnEditorNotifyEvent(EEditorNotifyEvent event) override; @@ -82,6 +76,5 @@ namespace Multiplayer IEditor* m_editor = nullptr; AzFramework::ProcessWatcher* m_serverProcess = nullptr; AzNetworking::ConnectionId m_editorConnId; - AzNetworking::INetworkInterface* m_editorNetworkInterface = nullptr; }; } diff --git a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp index 5dfdd86607..e277b394ff 100644 --- a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp +++ b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp @@ -23,6 +23,9 @@ #include #include #include +#include +#include +#include namespace AZ::ConsoleTypeHelpers { @@ -60,6 +63,8 @@ namespace Multiplayer static const AZStd::string_view s_networkEditorInterfaceName("MultiplayerEditorNetworkInterface"); static constexpr uint16_t DefaultServerPort = 30090; static constexpr uint16_t DefaultServerEditorPort = 30091; + //static AZStd::vector buffer; + //static AZ::IO::ByteContainerStream> s_byteStream(&buffer); AZ_CVAR(uint16_t, cl_clientport, 0, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The port to bind to for game traffic when connecting to a remote host, a value of 0 will select any available port"); AZ_CVAR(AZ::CVarFixedString, cl_serveraddr, "127.0.0.1", nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The address of the remote server or host to connect to"); @@ -107,7 +112,7 @@ namespace Multiplayer AZ::ConsoleInvokedFrom invokedFrom ) { OnConsoleCommandInvoked(command, args, flags, invokedFrom); }) { - ; + } void MultiplayerSystemComponent::Activate() @@ -401,19 +406,6 @@ namespace Multiplayer return false; } - bool MultiplayerSystemComponent::HandleRequest - ( - [[maybe_unused]] AzNetworking::IConnection* connection, - [[maybe_unused]] const IPacketHeader& packetHeader, - [[maybe_unused]] MultiplayerPackets::EditorServerInit& packet - ) - { -#if !defined(_RELEASE) - // Support Editor Server Init for all non-release targets -#endif - return true; - } - ConnectResult MultiplayerSystemComponent::ValidateConnect ( [[maybe_unused]] const IpAddress& remoteAddress, @@ -447,13 +439,19 @@ namespace Multiplayer // TODO: This needs to be set to the players autonomous proxy ------------v NetworkEntityHandle controlledEntity = GetNetworkEntityTracker()->Get(NetEntityId{ 0 }); - if (connection->GetUserData() == nullptr) // Only add user data if the connect event handler has not already done so + if (controlledEntity.GetEntity() != nullptr) { - connection->SetUserData(new ServerToClientConnectionData(connection, *this, controlledEntity)); - } + if (connection->GetUserData() == nullptr) // Only add user data if the connect event handler has not already done so + { + connection->SetUserData(new ServerToClientConnectionData(connection, *this, controlledEntity)); + } - AZStd::unique_ptr window = AZStd::make_unique(controlledEntity, connection); - reinterpret_cast(connection->GetUserData())->GetReplicationManager().SetReplicationWindow(AZStd::move(window)); + AZStd::unique_ptr window = + AZStd::make_unique(controlledEntity, connection); + reinterpret_cast(connection->GetUserData()) + ->GetReplicationManager() + .SetReplicationWindow(AZStd::move(window)); + } } else { @@ -509,12 +507,6 @@ namespace Multiplayer { if (multiplayerType == MultiplayerAgentType::ClientServer || multiplayerType == MultiplayerAgentType::DedicatedServer) { -#if !defined(_RELEASE) - m_networkEditorInterface = AZ::Interface::Get()->CreateNetworkInterface( - AZ::Name(s_networkEditorInterfaceName), ProtocolType::Tcp, TrustZone::ExternalClientToServer, *this); - m_networkEditorInterface->Listen(DefaultServerEditorPort); -#endif - m_initEvent.Signal(m_networkInterface); const AZ::Aabb worldBounds = AZ::Aabb::CreateFromMinMax(AZ::Vector3(-16384.0f), AZ::Vector3(16384.0f)); diff --git a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h index af5791e5aa..c49e5367e6 100644 --- a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h +++ b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h @@ -16,10 +16,12 @@ #include #include #include +#include #include #include #include #include +#include #include #include #include @@ -72,7 +74,7 @@ namespace Multiplayer bool HandleRequest(AzNetworking::IConnection* connection, const AzNetworking::IPacketHeader& packetHeader, MultiplayerPackets::NotifyClientMigration& packet); bool HandleRequest(AzNetworking::IConnection* connection, const AzNetworking::IPacketHeader& packetHeader, MultiplayerPackets::EntityMigration& packet); bool HandleRequest(AzNetworking::IConnection* connection, const AzNetworking::IPacketHeader& packetHeader, MultiplayerPackets::ReadyForEntityUpdates& packet); - bool HandleRequest(AzNetworking::IConnection* connection, const AzNetworking::IPacketHeader& packetHeader, MultiplayerPackets::EditorServerInit& packet); + //bool HandleRequest(AzNetworking::IConnection* connection, const AzNetworking::IPacketHeader& packetHeader, MultiplayerPackets::EditorServerInit& packet); //! IConnectionListener interface //! @{ @@ -125,5 +127,9 @@ namespace Multiplayer AZ::TimeMs m_lastReplicatedHostTimeMs = AZ::TimeMs{ 0 }; HostFrameId m_lastReplicatedHostFrameId = InvalidHostFrameId; + +#if !defined(_RELEASE) + MultiplayerEditorConnection m_editorConnectionListener; +#endif }; } diff --git a/Gems/Multiplayer/Code/Source/Pipeline/NetworkPrefabProcessor.cpp b/Gems/Multiplayer/Code/Source/Pipeline/NetworkPrefabProcessor.cpp index c2b0c07976..bc6e5710fc 100644 --- a/Gems/Multiplayer/Code/Source/Pipeline/NetworkPrefabProcessor.cpp +++ b/Gems/Multiplayer/Code/Source/Pipeline/NetworkPrefabProcessor.cpp @@ -35,6 +35,10 @@ namespace Multiplayer context.ListPrefabs([&context](AZStd::string_view prefabName, PrefabDom& prefab) { ProcessPrefab(context, prefabName, prefab); }); + if (context.GetProcessedObjects().size() > 0) + { + mpTools->SetDidProcessNetworkPrefabs(true); + } } void NetworkPrefabProcessor::Reflect(AZ::ReflectContext* context) diff --git a/Gems/Multiplayer/Code/multiplayer_editor_files.cmake b/Gems/Multiplayer/Code/multiplayer_editor_files.cmake index ce3e3227e0..5714be5dfb 100644 --- a/Gems/Multiplayer/Code/multiplayer_editor_files.cmake +++ b/Gems/Multiplayer/Code/multiplayer_editor_files.cmake @@ -10,6 +10,4 @@ # set(FILES - Source/Editor/MultiplayerEditorDispatcher.cpp - Source/Editor/MultiplayerEditorDispatcher.h ) diff --git a/Gems/Multiplayer/Code/multiplayer_editor_shared_files.cmake b/Gems/Multiplayer/Code/multiplayer_editor_shared_files.cmake index 2d5611d4b6..3fb76061b8 100644 --- a/Gems/Multiplayer/Code/multiplayer_editor_shared_files.cmake +++ b/Gems/Multiplayer/Code/multiplayer_editor_shared_files.cmake @@ -12,8 +12,8 @@ set(FILES Source/MultiplayerGem.cpp Source/MultiplayerGem.h - Source/MultiplayerEditorGem.cpp - Source/MultiplayerEditorGem.h + Source/Editor/MultiplayerEditorGem.cpp + Source/Editor/MultiplayerEditorGem.h Source/Editor/MultiplayerEditorSystemComponent.cpp Source/Editor/MultiplayerEditorSystemComponent.h ) diff --git a/Gems/Multiplayer/Code/multiplayer_files.cmake b/Gems/Multiplayer/Code/multiplayer_files.cmake index 1f4e57ae43..b8fd842426 100644 --- a/Gems/Multiplayer/Code/multiplayer_files.cmake +++ b/Gems/Multiplayer/Code/multiplayer_files.cmake @@ -33,6 +33,7 @@ set(FILES Source/AutoGen/AutoComponentTypes_Source.jinja Source/AutoGen/LocalPredictionPlayerInputComponent.AutoComponent.xml Source/AutoGen/Multiplayer.AutoPackets.xml + Source/AutoGen/MultiplayerEditor.AutoPackets.xml Source/AutoGen/NetworkTransformComponent.AutoComponent.xml Source/Components/LocalPredictionPlayerInputComponent.cpp Source/Components/LocalPredictionPlayerInputComponent.h @@ -52,6 +53,8 @@ set(FILES Source/ConnectionData/ServerToClientConnectionData.cpp Source/ConnectionData/ServerToClientConnectionData.h Source/ConnectionData/ServerToClientConnectionData.inl + Source/Editor/MultiplayerEditorConnection.cpp + Source/Editor/MultiplayerEditorConnection.h Source/EntityDomains/FullOwnershipEntityDomain.cpp Source/EntityDomains/FullOwnershipEntityDomain.h Source/NetworkEntity/EntityReplication/EntityReplicationManager.cpp From 467caa61759b12426ba8bbd969b4c89aed21c5a1 Mon Sep 17 00:00:00 2001 From: puvvadar Date: Thu, 13 May 2021 15:42:21 -0700 Subject: [PATCH 21/69] Some whitespace and comment cleanup --- Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp | 2 +- Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp index e277b394ff..867a9d3c2e 100644 --- a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp +++ b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp @@ -112,7 +112,7 @@ namespace Multiplayer AZ::ConsoleInvokedFrom invokedFrom ) { OnConsoleCommandInvoked(command, args, flags, invokedFrom); }) { - + ; } void MultiplayerSystemComponent::Activate() diff --git a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h index c49e5367e6..eba6813169 100644 --- a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h +++ b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h @@ -74,8 +74,7 @@ namespace Multiplayer bool HandleRequest(AzNetworking::IConnection* connection, const AzNetworking::IPacketHeader& packetHeader, MultiplayerPackets::NotifyClientMigration& packet); bool HandleRequest(AzNetworking::IConnection* connection, const AzNetworking::IPacketHeader& packetHeader, MultiplayerPackets::EntityMigration& packet); bool HandleRequest(AzNetworking::IConnection* connection, const AzNetworking::IPacketHeader& packetHeader, MultiplayerPackets::ReadyForEntityUpdates& packet); - //bool HandleRequest(AzNetworking::IConnection* connection, const AzNetworking::IPacketHeader& packetHeader, MultiplayerPackets::EditorServerInit& packet); - + //! IConnectionListener interface //! @{ AzNetworking::ConnectResult ValidateConnect(const AzNetworking::IpAddress& remoteAddress, const AzNetworking::IPacketHeader& packetHeader, AzNetworking::ISerializer& serializer) override; From 74ea093f71096c124ec25ca39630f5abbea2bdde Mon Sep 17 00:00:00 2001 From: puvvadar Date: Thu, 13 May 2021 15:43:07 -0700 Subject: [PATCH 22/69] More comment cleanup --- Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp index 867a9d3c2e..f920e7ed74 100644 --- a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp +++ b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp @@ -63,8 +63,6 @@ namespace Multiplayer static const AZStd::string_view s_networkEditorInterfaceName("MultiplayerEditorNetworkInterface"); static constexpr uint16_t DefaultServerPort = 30090; static constexpr uint16_t DefaultServerEditorPort = 30091; - //static AZStd::vector buffer; - //static AZ::IO::ByteContainerStream> s_byteStream(&buffer); AZ_CVAR(uint16_t, cl_clientport, 0, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The port to bind to for game traffic when connecting to a remote host, a value of 0 will select any available port"); AZ_CVAR(AZ::CVarFixedString, cl_serveraddr, "127.0.0.1", nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The address of the remote server or host to connect to"); From 6d7f7547ec6a64c34258dfcb54db9abc6754602f Mon Sep 17 00:00:00 2001 From: scottr Date: Fri, 14 May 2021 08:20:59 -0700 Subject: [PATCH 23/69] [cpack_installer] fix incorrect caching type of installer download url and add configure of download info --- cmake/Packaging.cmake | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/cmake/Packaging.cmake b/cmake/Packaging.cmake index e398ea7509..188cfbf52d 100644 --- a/cmake/Packaging.cmake +++ b/cmake/Packaging.cmake @@ -15,7 +15,7 @@ endif() # set the common cpack variables first so they are accessible via configure_file # when the platforms specific properties are applied below -set(LY_INSTALLER_DOWNLOAD_URL "" CACHE PATH "URL embded into the installer to download additional artifacts") +set(LY_INSTALLER_DOWNLOAD_URL "" CACHE STRING "URL embded into the installer to download additional artifacts") set(CPACK_PACKAGE_VENDOR "${PROJECT_NAME}") set(CPACK_PACKAGE_VERSION "${LY_VERSION_STRING}") @@ -34,7 +34,6 @@ set(CPACK_PACKAGE_INSTALL_DIRECTORY "${CPACK_PACKAGE_VENDOR}/${CPACK_PACKAGE_VER # custom cpack cache variables for use in pre/post build scripts set(CPACK_SOURCE_DIR ${CMAKE_SOURCE_DIR}/cmake) set(CPACK_BINARY_DIR ${CMAKE_BINARY_DIR}/installer) -set(CPACK_DOWNLOAD_URL ${LY_INSTALLER_DOWNLOAD_URL}) # attempt to apply platform specific settings ly_get_absolute_pal_filename(pal_dir ${CMAKE_SOURCE_DIR}/cmake/Platform/${PAL_HOST_PLATFORM_NAME}) @@ -86,3 +85,11 @@ ly_configure_cpack_component( DISPLAY_NAME "${PROJECT_NAME} Core" DESCRIPTION "${PROJECT_NAME} Headers, Libraries and Tools" ) + +if(LY_INSTALLER_DOWNLOAD_URL) + cpack_configure_downloads( + ${LY_INSTALLER_DOWNLOAD_URL} + UPLOAD_DIRECTORY artifacts + ALL + ) +endif() From f6b1fac139f649062e6f9595e0476b5299439cad Mon Sep 17 00:00:00 2001 From: scottr Date: Fri, 14 May 2021 12:03:20 -0700 Subject: [PATCH 24/69] [cpack_installer] initial bootstrap installer generation, this bootstrapper is what downloads the artifacts --- .../Windows/PackagingBootstrapper.wxs | 36 ++++++++++++++ .../Platform/Windows/PackagingPostBuild.cmake | 47 ++++++++++++++++++- .../Platform/Windows/PackagingTemplate.wxs.in | 4 +- .../Platform/Windows/Packaging_windows.cmake | 14 +++++- .../Windows/platform_windows_files.cmake | 1 + 5 files changed, 97 insertions(+), 5 deletions(-) create mode 100644 cmake/Platform/Windows/PackagingBootstrapper.wxs diff --git a/cmake/Platform/Windows/PackagingBootstrapper.wxs b/cmake/Platform/Windows/PackagingBootstrapper.wxs new file mode 100644 index 0000000000..711b60d854 --- /dev/null +++ b/cmake/Platform/Windows/PackagingBootstrapper.wxs @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/cmake/Platform/Windows/PackagingPostBuild.cmake b/cmake/Platform/Windows/PackagingPostBuild.cmake index fe57904003..064fb0d530 100644 --- a/cmake/Platform/Windows/PackagingPostBuild.cmake +++ b/cmake/Platform/Windows/PackagingPostBuild.cmake @@ -9,4 +9,49 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # -message(STATUS "Hello from CPack post build!") +# convert the path to a windows style path +string(REPLACE "/" "\\" _install_dir ${CPACK_PACKAGE_INSTALL_DIRECTORY}) + +# directory where the auto generated files live e.g /_CPack_Package/win64/WIX +set(_cpack_out_dir "${CPACK_TOPLEVEL_DIRECTORY}") +set(_out_dir "${CPACK_BINARY_DIR}/wixobj_bootstrap") + +set(_wix_ext_flags + -ext WixBalExtension +) + +set(_candle_command + ${CPACK_WIX_ROOT}/bin/candle.exe + -nologo + -arch x64 + "-I${_cpack_out_dir}" + ${_wix_ext_flags} + + -dCPACK_DOWNLOAD_SITE=${CPACK_DOWNLOAD_SITE} + -dCPACK_LOCAL_INSTALLER_DIR=${_cpack_out_dir} + -dCPACK_PACKAGE_FILE_NAME=${CPACK_PACKAGE_FILE_NAME} + -dCPACK_PACKAGE_INSTALL_DIRECTORY=${_install_dir} + + "${CPACK_SOURCE_DIR}/Platform/Windows/PackagingBootstrapper.wxs" + + -o "${_out_dir}" +) + +set(_light_command + ${CPACK_WIX_ROOT}/bin/light.exe + -nologo + ${_wix_ext_flags} + ${_out_dir}/*.wixobj + + -o "${CPACK_BINARY_DIR}/installer.exe" +) + +message(STATUS "Creating Installer Bootstrapper...") + +execute_process( + COMMAND + ${_candle_command} + + COMMAND + ${_light_command} +) diff --git a/cmake/Platform/Windows/PackagingTemplate.wxs.in b/cmake/Platform/Windows/PackagingTemplate.wxs.in index 3e5db03ec2..fd3610259a 100644 --- a/cmake/Platform/Windows/PackagingTemplate.wxs.in +++ b/cmake/Platform/Windows/PackagingTemplate.wxs.in @@ -14,8 +14,8 @@ - - + + Date: Sat, 15 May 2021 12:58:30 -0700 Subject: [PATCH 25/69] Cleanup server launch, misc. MP consts, and register Editor Spawnable assets server side --- .../AzCore/AzCore/Asset/AssetCommon.h | 13 +++ .../PrefabEditorEntityOwnershipInterface.h | 2 + .../Code/Include/MultiplayerConstants.h | 32 ++++++ .../Editor/MultiplayerEditorConnection.cpp | 87 +++++++++----- .../MultiplayerEditorSystemComponent.cpp | 108 +++++++++++------- .../Source/MultiplayerSystemComponent.cpp | 16 +-- .../Pipeline/NetworkPrefabProcessor.cpp | 9 +- Gems/Multiplayer/Code/multiplayer_files.cmake | 1 + 8 files changed, 188 insertions(+), 80 deletions(-) create mode 100644 Gems/Multiplayer/Code/Include/MultiplayerConstants.h diff --git a/Code/Framework/AzCore/AzCore/Asset/AssetCommon.h b/Code/Framework/AzCore/AzCore/Asset/AssetCommon.h index c8a245af68..ff1cd13023 100644 --- a/Code/Framework/AzCore/AzCore/Asset/AssetCommon.h +++ b/Code/Framework/AzCore/AzCore/Asset/AssetCommon.h @@ -308,6 +308,8 @@ namespace AZ Asset(AssetLoadBehavior loadBehavior = AssetLoadBehavior::Default); /// Create an asset from a valid asset data (created asset), might not be loaded or currently loading. Asset(AssetData* assetData, AssetLoadBehavior loadBehavior); + /// Create an asset from a valid asset data (created asset) and set the asset id for both, might not be loaded or currently loading. + Asset(const AZ::Data::AssetId& id, AssetData* assetData, AssetLoadBehavior loadBehavior); /// Initialize asset pointer with id, type, and hint. No data construction will occur until QueueLoad is called. Asset(const AZ::Data::AssetId& id, const AZ::Data::AssetType& type, const AZStd::string& hint = AZStd::string()); @@ -788,6 +790,17 @@ namespace AZ SetData(assetData); } + //========================================================================= + template + Asset::Asset(const AssetId& id, AssetData* assetData, AssetLoadBehavior loadBehavior) + : m_assetId(id) + , m_assetType(azrtti_typeid()) + , m_loadBehavior(loadBehavior) + { + assetData->m_assetId = id; + SetData(assetData); + } + //========================================================================= template Asset::Asset(const AssetId& id, const AZ::Data::AssetType& type, const AZStd::string& hint) diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/PrefabEditorEntityOwnershipInterface.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/PrefabEditorEntityOwnershipInterface.h index 4476876b59..2afabd16a9 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/PrefabEditorEntityOwnershipInterface.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/PrefabEditorEntityOwnershipInterface.h @@ -46,6 +46,8 @@ namespace AzToolsFramework virtual Prefab::InstanceOptionalReference GetRootPrefabInstance() = 0; + //! Get all Assets generated by Prefab processing when entering Play-In Editor mode (Ctrl+G) + //! /return The vector of Assets generated by Prefab processing virtual const AZStd::vector>& GetPlayInEditorAssetData() = 0; virtual bool LoadFromStream(AZ::IO::GenericStream& stream, AZStd::string_view filename) = 0; diff --git a/Gems/Multiplayer/Code/Include/MultiplayerConstants.h b/Gems/Multiplayer/Code/Include/MultiplayerConstants.h new file mode 100644 index 0000000000..892691177a --- /dev/null +++ b/Gems/Multiplayer/Code/Include/MultiplayerConstants.h @@ -0,0 +1,32 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace Multiplayer +{ + static constexpr AZStd::string_view MPNetworkInterfaceName("MultiplayerNetworkInterface"); + static constexpr AZStd::string_view MPEditorInterfaceName("MultiplayerEditorNetworkInterface"); + + static constexpr AZStd::string_view LocalHost("127.0.0.1"); + static constexpr uint16_t DefaultServerPort = 30090; + static constexpr uint16_t DefaultServerEditorPort = 30091; + +} + diff --git a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.cpp b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.cpp index dcf9c134da..c97c66544e 100644 --- a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.cpp +++ b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.cpp @@ -11,26 +11,22 @@ */ #include +#include #include #include #include #include -#include +#include #include #include #include #include +#include namespace Multiplayer { using namespace AzNetworking; - static const AZStd::string_view s_networkInterfaceName("MultiplayerNetworkInterface"); - static const AZStd::string_view s_networkEditorInterfaceName("MultiplayerEditorNetworkInterface"); - static constexpr AZStd::string_view DefaultEditorIp = "127.0.0.1"; - static constexpr uint16_t DefaultServerPort = 30090; - static constexpr uint16_t DefaultServerEditorPort = 30091; - static AZStd::vector buffer; static AZ::IO::ByteContainerStream> s_byteStream(&buffer); @@ -39,10 +35,16 @@ namespace Multiplayer MultiplayerEditorConnection::MultiplayerEditorConnection() { m_networkEditorInterface = AZ::Interface::Get()->CreateNetworkInterface( - AZ::Name(s_networkEditorInterfaceName), ProtocolType::Tcp, TrustZone::ExternalClientToServer, *this); + AZ::Name(MPEditorInterfaceName), ProtocolType::Tcp, TrustZone::ExternalClientToServer, *this); if (editorsv_isDedicated) { - m_networkEditorInterface->Listen(DefaultServerEditorPort); + uint16_t editorServerPort = DefaultServerEditorPort; + if (auto console = AZ::Interface::Get(); console) + { + console->GetCvarValue("editorsv_port", editorServerPort); + } + AZ_Assert(m_networkEditorInterface, "MP Editor Network Interface was unregistered before Editor Server could start listening."); + m_networkEditorInterface->Listen(editorServerPort); } } @@ -69,34 +71,56 @@ namespace Multiplayer AZStd::vector> assetData; while (s_byteStream.GetCurPos() < s_byteStream.GetLength()) { + AZ::Data::AssetId assetId; AZ::Data::AssetLoadBehavior assetLoadBehavior; + uint32_t hintSize; + AZStd::string assetHint; + s_byteStream.Read(sizeof(AZ::Data::AssetId), reinterpret_cast(&assetId)); s_byteStream.Read(sizeof(AZ::Data::AssetLoadBehavior), reinterpret_cast(&assetLoadBehavior)); + s_byteStream.Read(sizeof(uint32_t), reinterpret_cast(&hintSize)); + assetHint.resize(hintSize); + s_byteStream.Read(hintSize, assetHint.data()); + + size_t assetSize = s_byteStream.GetCurPos(); + AZ::Data::AssetData* assetDatum = AZ::Utils::LoadObjectFromStream(s_byteStream, nullptr); + assetSize = s_byteStream.GetCurPos() - assetSize; + AZ::Data::Asset asset = AZ::Data::Asset(assetId, assetDatum, assetLoadBehavior); + asset.SetHint(assetHint); + + AZ::Data::AssetInfo assetInfo; + assetInfo.m_assetId = asset.GetId(); + assetInfo.m_assetType = asset.GetType(); + assetInfo.m_relativePath = asset.GetHint(); + assetInfo.m_sizeBytes = assetSize; - AZ::Data::AssetData* assetDatum = AZ::Utils::LoadObjectFromStream(s_byteStream, nullptr); - AZ::Data::Asset asset = AZ::Data::Asset(assetDatum, assetLoadBehavior); - - /* // Register Asset to AssetManager - */ - - assetData.push_back(asset); + AZ::Data::AssetManager::Instance().AssignAssetData(asset); + AZ::Data::AssetCatalogRequestBus::Broadcast(&AZ::Data::AssetCatalogRequests::RegisterAsset, asset.GetId(), assetInfo); + + assetData.push_back(asset); } // Now that we've deserialized, clear the byte stream s_byteStream.Seek(0, AZ::IO::GenericStream::SeekMode::ST_SEEK_BEGIN); s_byteStream.Truncate(); - /* - // Hand-off our resultant assets - */ + // Load the level via the root spawnable tha was registered + AZ::CVarFixedString loadLevelString = "LoadLevel Root.spawnable"; + AZ::Interface::Get()->PerformCommand(loadLevelString.c_str()); AZLOG_INFO("Editor Server completed asset receive, responding to Editor..."); if (connection->SendReliablePacket(MultiplayerEditorPackets::EditorServerReady())) { // Setup the normal multiplayer connection AZ::Interface::Get()->InitializeMultiplayer(MultiplayerAgentType::DedicatedServer); - INetworkInterface* networkInterface = AZ::Interface::Get()->RetrieveNetworkInterface(AZ::Name(s_networkInterfaceName)); - networkInterface->Listen(DefaultServerPort); + INetworkInterface* networkInterface = AZ::Interface::Get()->RetrieveNetworkInterface(AZ::Name(MPNetworkInterfaceName)); + + uint16_t serverPort = DefaultServerPort; + if (auto console = AZ::Interface::Get(); console) + { + console->GetCvarValue("sv_port", serverPort); + } + networkInterface->Listen(serverPort); return true; } @@ -121,11 +145,22 @@ namespace Multiplayer // Receiving this packet means Editor sync is done, disconnect connection->Disconnect(AzNetworking::DisconnectReason::TerminatedByClient, AzNetworking::TerminationEndpoint::Local); - // Connect the Editor to the local server for Multiplayer simulation - AZ::Interface::Get()->InitializeMultiplayer(MultiplayerAgentType::Client); - INetworkInterface* networkInterface = AZ::Interface::Get()->RetrieveNetworkInterface(AZ::Name(s_networkInterfaceName)); - const IpAddress ipAddress(DefaultEditorIp.data(), DefaultServerEditorPort, networkInterface->GetType()); - networkInterface->Connect(ipAddress); + if (auto console = AZ::Interface::Get(); console) + { + AZ::CVarFixedString remoteAddress; + uint16_t remotePort; + if (console->GetCvarValue("editorsv_serveraddr", remoteAddress) != AZ::GetValueResult::ConsoleVarNotFound && + console->GetCvarValue("editorsv_port", remotePort) != AZ::GetValueResult::ConsoleVarNotFound) + { + // Connect the Editor to the editor server for Multiplayer simulation + AZ::Interface::Get()->InitializeMultiplayer(MultiplayerAgentType::Client); + INetworkInterface* networkInterface = + AZ::Interface::Get()->RetrieveNetworkInterface(AZ::Name(MPNetworkInterfaceName)); + + const IpAddress ipAddress(remoteAddress.c_str(), remotePort, networkInterface->GetType()); + networkInterface->Connect(ipAddress); + } + } } return true; } diff --git a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp index 4c3e8200a1..89cb816695 100644 --- a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp @@ -12,6 +12,7 @@ #include #include +#include #include #include #include @@ -26,16 +27,16 @@ namespace Multiplayer { - static const AZStd::string_view s_networkEditorInterfaceName("MultiplayerEditorNetworkInterface"); - using namespace AzNetworking; AZ_CVAR(bool, editorsv_enabled, true, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "Whether Editor launching a local server to connect to is supported"); + AZ_CVAR(bool, editorsv_launch, false, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, + "Whether Editor should launch a server when the server address is localhost"); AZ_CVAR(AZ::CVarFixedString, editorsv_process, "", nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The server executable that should be run. Empty to use the current project's ServerLauncher"); - AZ_CVAR(AZ::CVarFixedString, editorsv_serveraddr, "127.0.0.1", nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The address of the server to connect to"); - AZ_CVAR(uint16_t, editorsv_port, 30091, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The port that the multiplayer editor gem will bind to for traffic"); + AZ_CVAR(AZ::CVarFixedString, editorsv_serveraddr, LocalHost.data(), nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The address of the server to connect to"); + AZ_CVAR(uint16_t, editorsv_port, DefaultServerEditorPort, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The port that the multiplayer editor gem will bind to for traffic"); void MultiplayerEditorSystemComponent::Reflect(AZ::ReflectContext* context) { @@ -105,7 +106,7 @@ namespace Multiplayer m_serverProcess->TerminateProcess(0); m_serverProcess = nullptr; } - INetworkInterface* editorNetworkInterface = AZ::Interface::Get()->RetrieveNetworkInterface(AZ::Name(s_networkEditorInterfaceName)); + INetworkInterface* editorNetworkInterface = AZ::Interface::Get()->RetrieveNetworkInterface(AZ::Name(MPEditorInterfaceName)); if (editorNetworkInterface) { editorNetworkInterface->Disconnect(m_editorConnId, AzNetworking::DisconnectReason::TerminatedByClient); @@ -114,6 +115,46 @@ namespace Multiplayer } } + void LaunchEditorServer(AzFramework::ProcessWatcher* outProcess) + { + // Assemble the server's path + AZ::CVarFixedString serverProcess = editorsv_process; + if (serverProcess.empty()) + { + // If enabled but no process name is supplied, try this project's ServerLauncher + serverProcess = AZ::Utils::GetProjectName() + ".ServerLauncher"; + } + + AZ::IO::FixedMaxPathString serverPath = AZ::Utils::GetExecutableDirectory(); + if (!serverProcess.contains(AZ_TRAIT_OS_PATH_SEPARATOR)) + { + // If only the process name is specified, append that as well + serverPath.append(AZ_TRAIT_OS_PATH_SEPARATOR + serverProcess); + } + else + { + // If any path was already specified, then simply assign + serverPath = serverProcess; + } + + if (!serverProcess.ends_with(AZ_TRAIT_OS_EXECUTABLE_EXTENSION)) + { + // Add this platform's exe extension if it's not specified + serverPath.append(AZ_TRAIT_OS_EXECUTABLE_EXTENSION); + } + + // Start the configured server if it's available + AzFramework::ProcessLauncher::ProcessLaunchInfo processLaunchInfo; + processLaunchInfo.m_commandlineParameters = AZStd::string::format("\"%s\" --editorsv_isDedicated true", serverPath.c_str()); + processLaunchInfo.m_showWindow = true; + processLaunchInfo.m_processPriority = AzFramework::ProcessPriority::PROCESSPRIORITY_NORMAL; + + // Launch the Server and give it a few seconds to boot up + outProcess = AzFramework::ProcessWatcher::LaunchProcess( + processLaunchInfo, AzFramework::ProcessCommunicationType::COMMUNICATOR_TYPE_NONE); + AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(15000)); + } + void MultiplayerEditorSystemComponent::OnGameEntitiesStarted() { auto prefabEditorEntityOwnershipInterface = AZ::Interface::Get(); @@ -134,55 +175,38 @@ namespace Multiplayer // Serialize Asset information and AssetData into a potentially large buffer for (auto asset : assetData) { + AZ::Data::AssetId assetId = asset.GetId(); AZ::Data::AssetLoadBehavior assetLoadBehavior = asset.GetAutoLoadBehavior(); + AZStd::string assetHint = asset.GetHint(); + uint32_t hintSize = aznumeric_cast(assetHint.size()); + + byteStream.Write(sizeof(AZ::Data::AssetId), reinterpret_cast(&assetId)); byteStream.Write(sizeof(AZ::Data::AssetLoadBehavior), reinterpret_cast(&assetLoadBehavior)); - + byteStream.Write(sizeof(uint32_t), reinterpret_cast(&hintSize)); + byteStream.Write(assetHint.size(), assetHint.data()); AZ::Utils::SaveObjectToStream(byteStream, AZ::DataStream::ST_BINARY, asset.GetData(), asset.GetData()->GetType()); } - // Assemble the server's path - AZ::CVarFixedString serverProcess = editorsv_process; - if (serverProcess.empty()) + const AZ::CVarFixedString remoteAddress = editorsv_serveraddr; + if (editorsv_launch && LocalHost.compare(remoteAddress.c_str()) == 0) { - // If enabled but no process name is supplied, try this project's ServerLauncher - serverProcess = AZ::Utils::GetProjectName() + ".ServerLauncher"; + LaunchEditorServer(m_serverProcess); } - AZ::IO::FixedMaxPathString serverPath = AZ::Utils::GetExecutableDirectory(); - if (!serverProcess.contains(AZ_TRAIT_OS_PATH_SEPARATOR)) - { - // If only the process name is specified, append that as well - serverPath.append(AZ_TRAIT_OS_PATH_SEPARATOR + serverProcess); - } - else - { - // If any path was already specified, then simply assign - serverPath = serverProcess; - } + // Now that the server has launched, attempt to connect the NetworkInterface + INetworkInterface* editorNetworkInterface = AZ::Interface::Get()->RetrieveNetworkInterface(AZ::Name(MPEditorInterfaceName)); + AZ_Assert(editorNetworkInterface, "MP Editor Network Interface was unregistered before Editor could connect."); + m_editorConnId = editorNetworkInterface->Connect( + AzNetworking::IpAddress(remoteAddress.c_str(), editorsv_port, AzNetworking::ProtocolType::Tcp)); - if (!serverProcess.ends_with(AZ_TRAIT_OS_EXECUTABLE_EXTENSION)) + if (m_editorConnId == AzNetworking::InvalidConnectionId) { - // Add this platform's exe extension if it's not specified - serverPath.append(AZ_TRAIT_OS_EXECUTABLE_EXTENSION); + AZ_Warning( + "MultiplayerEditor", false, + "Could not connect to server targeted by Editor. If using a local server, check that it's built and editorsv_launch is true."); + return; } - // Start the configured server if it's available - AzFramework::ProcessLauncher::ProcessLaunchInfo processLaunchInfo; - processLaunchInfo.m_commandlineParameters = AZStd::string::format("\"%s\" --editorsv_isDedicated true", serverPath.c_str()); - processLaunchInfo.m_showWindow = true; - processLaunchInfo.m_processPriority = AzFramework::ProcessPriority::PROCESSPRIORITY_NORMAL; - - // Launch the Server and give it a few seconds to boot up - m_serverProcess = AzFramework::ProcessWatcher::LaunchProcess( - processLaunchInfo, AzFramework::ProcessCommunicationType::COMMUNICATOR_TYPE_NONE); - AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(15000)); - - // Now that the server has launched, attempt to connect the NetworkInterface - const AZ::CVarFixedString remoteAddress = editorsv_serveraddr; - INetworkInterface* editorNetworkInterface = AZ::Interface::Get()->RetrieveNetworkInterface(AZ::Name(s_networkEditorInterfaceName)); - m_editorConnId = editorNetworkInterface->Connect( - AzNetworking::IpAddress(remoteAddress.c_str(), editorsv_port, AzNetworking::ProtocolType::Tcp)); - // Read the buffer into EditorServerInit packets until we've flushed the whole thing byteStream.Seek(0, AZ::IO::GenericStream::SeekMode::ST_SEEK_BEGIN); diff --git a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp index f920e7ed74..a0a343f320 100644 --- a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp +++ b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp @@ -10,6 +10,7 @@ * */ +#include #include #include #include @@ -59,13 +60,8 @@ namespace Multiplayer { using namespace AzNetworking; - static const AZStd::string_view s_networkInterfaceName("MultiplayerNetworkInterface"); - static const AZStd::string_view s_networkEditorInterfaceName("MultiplayerEditorNetworkInterface"); - static constexpr uint16_t DefaultServerPort = 30090; - static constexpr uint16_t DefaultServerEditorPort = 30091; - AZ_CVAR(uint16_t, cl_clientport, 0, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The port to bind to for game traffic when connecting to a remote host, a value of 0 will select any available port"); - AZ_CVAR(AZ::CVarFixedString, cl_serveraddr, "127.0.0.1", nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The address of the remote server or host to connect to"); + AZ_CVAR(AZ::CVarFixedString, cl_serveraddr, LocalHost.data(), nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The address of the remote server or host to connect to"); AZ_CVAR(AZ::CVarFixedString, cl_serverpassword, "", nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "Optional server password"); AZ_CVAR(uint16_t, cl_serverport, DefaultServerPort, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The port of the remote host to connect to for game traffic"); AZ_CVAR(uint16_t, sv_port, DefaultServerPort, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The port that this multiplayer gem will bind to for game traffic"); @@ -116,7 +112,7 @@ namespace Multiplayer void MultiplayerSystemComponent::Activate() { AZ::TickBus::Handler::BusConnect(); - m_networkInterface = AZ::Interface::Get()->CreateNetworkInterface(AZ::Name(s_networkInterfaceName), sv_protocol, TrustZone::ExternalClientToServer, *this); + m_networkInterface = AZ::Interface::Get()->CreateNetworkInterface(AZ::Name(MPNetworkInterfaceName), sv_protocol, TrustZone::ExternalClientToServer, *this); m_consoleCommandHandler.Connect(AZ::Interface::Get()->GetConsoleCommandInvokedEvent()); AZ::Interface::Register(this); @@ -635,7 +631,7 @@ namespace Multiplayer { Multiplayer::MultiplayerAgentType serverType = sv_isDedicated ? MultiplayerAgentType::DedicatedServer : MultiplayerAgentType::ClientServer; AZ::Interface::Get()->InitializeMultiplayer(serverType); - INetworkInterface* networkInterface = AZ::Interface::Get()->RetrieveNetworkInterface(AZ::Name(s_networkInterfaceName)); + INetworkInterface* networkInterface = AZ::Interface::Get()->RetrieveNetworkInterface(AZ::Name(MPNetworkInterfaceName)); networkInterface->Listen(sv_port); } AZ_CONSOLEFREEFUNC(host, AZ::ConsoleFunctorFlags::DontReplicate, "Opens a multiplayer connection as a host for other clients to connect to"); @@ -643,7 +639,7 @@ namespace Multiplayer void connect([[maybe_unused]] const AZ::ConsoleCommandContainer& arguments) { AZ::Interface::Get()->InitializeMultiplayer(MultiplayerAgentType::Client); - INetworkInterface* networkInterface = AZ::Interface::Get()->RetrieveNetworkInterface(AZ::Name(s_networkInterfaceName)); + INetworkInterface* networkInterface = AZ::Interface::Get()->RetrieveNetworkInterface(AZ::Name(MPNetworkInterfaceName)); if (arguments.size() < 1) { @@ -673,7 +669,7 @@ namespace Multiplayer void disconnect([[maybe_unused]] const AZ::ConsoleCommandContainer& arguments) { AZ::Interface::Get()->InitializeMultiplayer(MultiplayerAgentType::Uninitialized); - INetworkInterface* networkInterface = AZ::Interface::Get()->RetrieveNetworkInterface(AZ::Name(s_networkInterfaceName)); + INetworkInterface* networkInterface = AZ::Interface::Get()->RetrieveNetworkInterface(AZ::Name(MPNetworkInterfaceName)); auto visitor = [](IConnection& connection) { connection.Disconnect(DisconnectReason::TerminatedByUser, TerminationEndpoint::Local); }; networkInterface->GetConnectionSet().VisitConnections(visitor); } diff --git a/Gems/Multiplayer/Code/Source/Pipeline/NetworkPrefabProcessor.cpp b/Gems/Multiplayer/Code/Source/Pipeline/NetworkPrefabProcessor.cpp index bc6e5710fc..137bd27794 100644 --- a/Gems/Multiplayer/Code/Source/Pipeline/NetworkPrefabProcessor.cpp +++ b/Gems/Multiplayer/Code/Source/Pipeline/NetworkPrefabProcessor.cpp @@ -31,11 +31,16 @@ namespace Multiplayer void NetworkPrefabProcessor::Process(PrefabProcessorContext& context) { IMultiplayerTools* mpTools = AZ::Interface::Get(); - mpTools->SetDidProcessNetworkPrefabs(false); + if (mpTools) + { + mpTools->SetDidProcessNetworkPrefabs(false); + } + context.ListPrefabs([&context](AZStd::string_view prefabName, PrefabDom& prefab) { ProcessPrefab(context, prefabName, prefab); }); - if (context.GetProcessedObjects().size() > 0) + + if (mpTools && context.GetProcessedObjects().size() > 0) { mpTools->SetDidProcessNetworkPrefabs(true); } diff --git a/Gems/Multiplayer/Code/multiplayer_files.cmake b/Gems/Multiplayer/Code/multiplayer_files.cmake index b8fd842426..ecde01e2d7 100644 --- a/Gems/Multiplayer/Code/multiplayer_files.cmake +++ b/Gems/Multiplayer/Code/multiplayer_files.cmake @@ -17,6 +17,7 @@ set(FILES Include/INetworkEntityManager.h Include/INetworkTime.h Include/IReplicationWindow.h + Include/MultiplayerConstants.h Include/MultiplayerStats.cpp Include/MultiplayerStats.h Include/MultiplayerTypes.h From 0e53c775162bd46ffd89245b1792e6bb98919a6d Mon Sep 17 00:00:00 2001 From: puvvadar Date: Sat, 15 May 2021 13:47:03 -0700 Subject: [PATCH 26/69] Fix some include paths --- .../Code/Source/AutoGen/MultiplayerEditor.AutoPackets.xml | 4 ++-- .../Code/Source/Editor/MultiplayerEditorConnection.cpp | 4 ++-- Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Gems/Multiplayer/Code/Source/AutoGen/MultiplayerEditor.AutoPackets.xml b/Gems/Multiplayer/Code/Source/AutoGen/MultiplayerEditor.AutoPackets.xml index 986860e822..9904eef83c 100644 --- a/Gems/Multiplayer/Code/Source/AutoGen/MultiplayerEditor.AutoPackets.xml +++ b/Gems/Multiplayer/Code/Source/AutoGen/MultiplayerEditor.AutoPackets.xml @@ -2,8 +2,8 @@ - - + + diff --git a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.cpp b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.cpp index c97c66544e..545e17eff6 100644 --- a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.cpp +++ b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.cpp @@ -10,8 +10,8 @@ * */ -#include -#include +#include +#include #include #include #include diff --git a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp index c8bdd80a39..fe90d68696 100644 --- a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp +++ b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp @@ -10,7 +10,7 @@ * */ -#include +#include #include #include #include From df68ab6c568a632b9591570454f5c8062a35448f Mon Sep 17 00:00:00 2001 From: puvvadar Date: Sun, 16 May 2021 15:30:00 -0700 Subject: [PATCH 27/69] Update headers in editor auto packets --- .../Code/Source/AutoGen/MultiplayerEditor.AutoPackets.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gems/Multiplayer/Code/Source/AutoGen/MultiplayerEditor.AutoPackets.xml b/Gems/Multiplayer/Code/Source/AutoGen/MultiplayerEditor.AutoPackets.xml index 9904eef83c..8f55ecd2b8 100644 --- a/Gems/Multiplayer/Code/Source/AutoGen/MultiplayerEditor.AutoPackets.xml +++ b/Gems/Multiplayer/Code/Source/AutoGen/MultiplayerEditor.AutoPackets.xml @@ -3,7 +3,7 @@ - + From d12cf2b6e136ce7f21801df3f4165477a393fa2d Mon Sep 17 00:00:00 2001 From: balibhan Date: Mon, 17 May 2021 10:21:56 +0530 Subject: [PATCH 28/69] Pane properties test script --- ...Pane_PropertiesChanged_RetainsOnRestart.py | 161 ++++++++++++++++++ .../PythonTests/scripting/TestSuite_Active.py | 41 ++++- 2 files changed, 195 insertions(+), 7 deletions(-) create mode 100644 AutomatedTesting/Gem/PythonTests/scripting/Pane_PropertiesChanged_RetainsOnRestart.py diff --git a/AutomatedTesting/Gem/PythonTests/scripting/Pane_PropertiesChanged_RetainsOnRestart.py b/AutomatedTesting/Gem/PythonTests/scripting/Pane_PropertiesChanged_RetainsOnRestart.py new file mode 100644 index 0000000000..3750fd290b --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/scripting/Pane_PropertiesChanged_RetainsOnRestart.py @@ -0,0 +1,161 @@ +""" +All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +its licensors. + +For complete copyright and license terms please see the LICENSE at the root of this +distribution (the "License"). All use of this software is governed by the License, +or, if provided, by the license below or the license accompanying this file. Do not +remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +""" + + +class Tests: + test_panes_visible = "All the test panes are opened" + close_pane_1 = "Test pane 1 is closed" + resize_pane_3 = "Test pane 3 resized successfully" + location_changed = "Location of test pane 2 changed successfully" + visiblity_retained = "Test pane retained its visiblity on Editor restart" + location_retained = "Test pane retained its location on Editor restart" + size_retained = "Test pane retained its size on Editor restart" + + +def Pane_PropertiesChanged_RetainsOnRestart(): + """ + Summary: + The Script Canvas window is opened to verify if Script canvas panes can retain its visibility, size and location + upon Editor restart. + + Expected Behavior: + The ScriptCanvas pane retain it's visiblity, size and location upon Editor restart. + + Test Steps: + 1) Open Script Canvas window (Tools > Script Canvas) + 2) Make sure test panes are open and visible + 3) Close test pane 1 + 4) Change dock location of test pane 2 + 5) Resize test pane 3 + 6) Restart Editor + 7) Verify if test pane 1 retain its visiblity + 8) Verify if location of test pane 2 is retained + 9) Verify if size of test pane 3 is retained + 10) Restore default layout and close SC window + + Note: + - This test file must be called from the Open 3D Engine Editor command terminal + - Any passed and failed tests are written to the Editor.log file. + Parsing the file or running a log_monitor are required to observe the test results. + + :return: None + """ + + import sys + + # Helper imports + from utils import Report + from utils import TestHelper as helper + import pyside_utils + + # Lumberyard Imports + import azlmbr.legacy.general as general + + # Pyside imports + from PySide2 import QtCore, QtWidgets + from PySide2.QtCore import Qt + + # Constants + TEST_CONDITION = sys.argv[1] + TEST_PANE_1 = "NodePalette" # pane used to test visibility + TEST_PANE_2 = "VariableManager" # pane used to test location + TEST_PANE_3 = "NodeInspector" # pane used to test size + SCALE_INT = 10 # Random resize scale integer + DOCKAREA = Qt.TopDockWidgetArea # Preferred top area since no widget is docked on top + + def click_menu_option(window, option_text): + action = pyside_utils.find_child_by_pattern(window, {"text": option_text, "type": QtWidgets.QAction}) + action.trigger() + + def find_pane(window, pane_name): + return window.findChild(QtWidgets.QDockWidget, pane_name) + + # Test starts here + general.idle_enable(True) + + # 1) Open Script Canvas window (Tools > Script Canvas) + general.open_pane("Script Canvas") + helper.wait_for_condition(lambda: general.is_pane_visible("Script Canvas"), 5.0) + + if TEST_CONDITION == "before_restart": + # 2) Make sure test panes are open and visible + editor_window = pyside_utils.get_editor_main_window() + sc = editor_window.findChild(QtWidgets.QDockWidget, "Script Canvas") + click_menu_option(sc, "Restore Default Layout") + test_pane_1 = sc.findChild(QtWidgets.QDockWidget, TEST_PANE_1) + test_pane_2 = sc.findChild(QtWidgets.QDockWidget, TEST_PANE_2) + test_pane_3 = sc.findChild(QtWidgets.QDockWidget, TEST_PANE_3) + + result = test_pane_1.isVisible() and test_pane_2.isVisible() and test_pane_3.isVisible() + Report.info(f"{Tests.test_panes_visible}: {result}") + + # 3) Close test pane + test_pane_1.close() + Report.info(f"{Tests.close_pane_1}: {not test_pane_1.isVisible()}") + + # 4) Change dock location of test pane 2 + sc_main = sc.findChild(QtWidgets.QMainWindow) + sc_main.addDockWidget(DOCKAREA, find_pane(sc_main, TEST_PANE_2), QtCore.Qt.Vertical) + Report.info(f"{Tests.location_changed}: {sc_main.dockWidgetArea(find_pane(sc_main, TEST_PANE_2)) == DOCKAREA}") + + # 5) Resize test pane 3 + initial_size = test_pane_3.frameSize() + test_pane_3.resize(initial_size.width() + SCALE_INT, initial_size.height() + SCALE_INT) + new_size = test_pane_3.frameSize() + resize_success = ( + abs(initial_size.width() - new_size.width()) == abs(initial_size.height() - new_size.height()) == SCALE_INT + ) + Report.info(f"{Tests.resize_pane_3}: {resize_success}") + + if TEST_CONDITION == "after_restart": + try: + # 6) Restart Editor + # Restart is not possible through script and hence it is done by running the same file as 2 tests with a + # condition as before_test and after_test + + # 7) Verify if test pane 1 retain its visiblity + # This pane closed before restart and expected that pane should not be visible. + editor_window = pyside_utils.get_editor_main_window() + sc = editor_window.findChild(QtWidgets.QDockWidget, "Script Canvas") + Report.info(f"{Tests.visiblity_retained}: {not find_pane(sc, TEST_PANE_1).isVisible()}") + + # 8) Verify if location of test pane 2 is retained + # This pane was set at DOCKAREA lcoation before restart + sc_main = sc.findChild(QtWidgets.QMainWindow) + Report.info( + f"{Tests.location_retained}: {sc_main.dockWidgetArea(find_pane(sc_main, TEST_PANE_2)) == DOCKAREA}" + ) + + # 9) Verify if size of test pane 3 is retained + # Verifying if size retained by checking current size not matching with default size + test_pane_3 = find_pane(sc, TEST_PANE_3) + retained_size = test_pane_3.frameSize() + click_menu_option(sc, "Restore Default Layout") + actual_size = test_pane_3.frameSize() + Report.info(f"{Tests.size_retained}: {retained_size != actual_size}") + + finally: + # 10) Restore default layout and close SC window + general.open_pane("Script Canvas") + helper.wait_for_condition(lambda: general.is_pane_visible("Script Canvas"), 5.0) + sc = editor_window.findChild(QtWidgets.QDockWidget, "Script Canvas") + click_menu_option(sc, "Restore Default Layout") + sc.close() + + +if __name__ == "__main__": + import ImportPathHelper as imports + + imports.init() + + from utils import Report + + Report.start_test(Pane_PropertiesChanged_RetainsOnRestart) diff --git a/AutomatedTesting/Gem/PythonTests/scripting/TestSuite_Active.py b/AutomatedTesting/Gem/PythonTests/scripting/TestSuite_Active.py index c42c9f1a03..9180c1b44c 100755 --- a/AutomatedTesting/Gem/PythonTests/scripting/TestSuite_Active.py +++ b/AutomatedTesting/Gem/PythonTests/scripting/TestSuite_Active.py @@ -76,14 +76,8 @@ class TestAutomation(TestAutomationBase): from . import ScriptCanvasComponent_OnEntityActivatedDeactivated_PrintMessage as test_module self._run_test(request, workspace, editor, test_module) -<<<<<<< HEAD def test_NodePalette_HappyPath_ClearSelection(self, request, workspace, editor, launcher_platform, project): from . import NodePalette_HappyPath_ClearSelection as test_module -======= - @pytest.mark.test_case_id("T92562993") - def test_NodePalette_ClearSelection(self, request, workspace, editor, launcher_platform, project): - from . import NodePalette_ClearSelection as test_module ->>>>>>> main self._run_test(request, workspace, editor, test_module) @pytest.mark.parametrize("level", ["tmp_level"]) @@ -119,7 +113,6 @@ class TestAutomation(TestAutomationBase): from . import Debugger_HappyPath_TargetMultipleGraphs as test_module self._run_test(request, workspace, editor, test_module) - @pytest.mark.test_case_id("T92569137") def test_Debugging_TargetMultipleGraphs(self, request, workspace, editor, launcher_platform, project): from . import Debugging_TargetMultipleGraphs as test_module self._run_test(request, workspace, editor, test_module) @@ -262,3 +255,37 @@ class TestScriptCanvasTests(object): auto_test_mode=False, timeout=60, ) + + @pytest.mark.parametrize( + "config", + [ + { + "cfg_args": "before_restart", + "expected_lines": [ + "All the test panes are opened: True", + "Test pane 1 is closed: True", + "Location of test pane 2 changed successfully: True", + "Test pane 3 resized successfully: True", + ], + }, + { + "cfg_args": "after_restart", + "expected_lines": [ + "Test pane retained its visiblity on Editor restart: True", + "Test pane retained its location on Editor restart: True", + "Test pane retained its size on Editor restart: True", + ], + }, + ], + ) + def test_Pane_PropertiesChanged_RetainsOnRestart(self, request, editor, config, project, launcher_platform): + hydra.launch_and_validate_results( + request, + TEST_DIRECTORY, + editor, + "Pane_PropertiesChanged_RetainsOnRestart.py", + config.get('expected_lines'), + cfg_args=[config.get('cfg_args')], + auto_test_mode=False, + timeout=60, + ) From f94d0c99e72738d4add4b10e053e403bf67bba47 Mon Sep 17 00:00:00 2001 From: puvvadar Date: Mon, 17 May 2021 09:58:58 -0700 Subject: [PATCH 29/69] Cleanup connection order slightly --- .../Editor/MultiplayerEditorConnection.cpp | 31 +++++++------------ 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.cpp b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.cpp index 545e17eff6..731c865137 100644 --- a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.cpp +++ b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.cpp @@ -93,7 +93,7 @@ namespace Multiplayer assetInfo.m_relativePath = asset.GetHint(); assetInfo.m_sizeBytes = assetSize; - // Register Asset to AssetManager + // Register Asset to AssetManager AZ::Data::AssetManager::Instance().AssignAssetData(asset); AZ::Data::AssetCatalogRequestBus::Broadcast(&AZ::Data::AssetCatalogRequests::RegisterAsset, asset.GetId(), assetInfo); @@ -108,26 +108,19 @@ namespace Multiplayer AZ::CVarFixedString loadLevelString = "LoadLevel Root.spawnable"; AZ::Interface::Get()->PerformCommand(loadLevelString.c_str()); - AZLOG_INFO("Editor Server completed asset receive, responding to Editor..."); - if (connection->SendReliablePacket(MultiplayerEditorPackets::EditorServerReady())) - { - // Setup the normal multiplayer connection - AZ::Interface::Get()->InitializeMultiplayer(MultiplayerAgentType::DedicatedServer); - INetworkInterface* networkInterface = AZ::Interface::Get()->RetrieveNetworkInterface(AZ::Name(MPNetworkInterfaceName)); - - uint16_t serverPort = DefaultServerPort; - if (auto console = AZ::Interface::Get(); console) - { - console->GetCvarValue("sv_port", serverPort); - } - networkInterface->Listen(serverPort); - - return true; - } - else + // Setup the normal multiplayer connection + AZ::Interface::Get()->InitializeMultiplayer(MultiplayerAgentType::DedicatedServer); + INetworkInterface* networkInterface = AZ::Interface::Get()->RetrieveNetworkInterface(AZ::Name(MPNetworkInterfaceName)); + + uint16_t serverPort = DefaultServerPort; + if (auto console = AZ::Interface::Get(); console) { - return false; + console->GetCvarValue("sv_port", serverPort); } + networkInterface->Listen(serverPort); + + AZLOG_INFO("Editor Server completed asset receive, responding to Editor..."); + return connection->SendReliablePacket(MultiplayerEditorPackets::EditorServerReady()); } return true; From 83a56ce71cdb62e0a815b3a0f8a1c1419f1c5221 Mon Sep 17 00:00:00 2001 From: puvvadar Date: Mon, 17 May 2021 12:53:15 -0700 Subject: [PATCH 30/69] Cleanup typo --- .../Code/Source/Editor/MultiplayerEditorConnection.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.cpp b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.cpp index 731c865137..e5f7ed4a68 100644 --- a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.cpp +++ b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.cpp @@ -104,7 +104,7 @@ namespace Multiplayer s_byteStream.Seek(0, AZ::IO::GenericStream::SeekMode::ST_SEEK_BEGIN); s_byteStream.Truncate(); - // Load the level via the root spawnable tha was registered + // Load the level via the root spawnable that was registered AZ::CVarFixedString loadLevelString = "LoadLevel Root.spawnable"; AZ::Interface::Get()->PerformCommand(loadLevelString.c_str()); From 6f3f5268d830736872a3ccdc31f03182b2cb91d0 Mon Sep 17 00:00:00 2001 From: puvvadar Date: Mon, 17 May 2021 14:06:20 -0700 Subject: [PATCH 31/69] Fix build error on unity builds --- .../Entity/PrefabEditorEntityOwnershipInterface.h | 1 + 1 file changed, 1 insertion(+) diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/PrefabEditorEntityOwnershipInterface.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/PrefabEditorEntityOwnershipInterface.h index 2afabd16a9..8412361657 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/PrefabEditorEntityOwnershipInterface.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/PrefabEditorEntityOwnershipInterface.h @@ -12,6 +12,7 @@ #pragma once +#include #include #include #include From bb851943c8bb20d65ae81c43aeb0638b7414c369 Mon Sep 17 00:00:00 2001 From: puvvadar Date: Mon, 17 May 2021 15:16:35 -0700 Subject: [PATCH 32/69] Update unit test to account for missing dependency --- Gems/Multiplayer/Code/Tests/MultiplayerSystemTests.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Gems/Multiplayer/Code/Tests/MultiplayerSystemTests.cpp b/Gems/Multiplayer/Code/Tests/MultiplayerSystemTests.cpp index 2e0b9c0759..6ace4db592 100644 --- a/Gems/Multiplayer/Code/Tests/MultiplayerSystemTests.cpp +++ b/Gems/Multiplayer/Code/Tests/MultiplayerSystemTests.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -30,6 +31,7 @@ namespace UnitTest SetupAllocator(); AZ::NameDictionary::Create(); m_spawnableComponent = new AzFramework::SpawnableSystemComponent(); + m_netComponent = new AzNetworking::NetworkingSystemComponent(); m_mpComponent = new Multiplayer::MultiplayerSystemComponent(); m_initHandler = Multiplayer::SessionInitEvent::Handler([this](AzNetworking::INetworkInterface* value) { TestInitEvent(value); }); @@ -43,6 +45,7 @@ namespace UnitTest void TearDown() override { delete m_mpComponent; + delete m_netComponent; delete m_spawnableComponent; AZ::NameDictionary::Destroy(); TeardownAllocator(); @@ -71,6 +74,7 @@ namespace UnitTest Multiplayer::SessionShutdownEvent::Handler m_shutdownHandler; Multiplayer::ConnectionAcquiredEvent::Handler m_connAcquiredHandler; + AzNetworking::NetworkingSystemComponent* m_netComponent = nullptr; Multiplayer::MultiplayerSystemComponent* m_mpComponent = nullptr; AzFramework::SpawnableSystemComponent* m_spawnableComponent = nullptr; }; From b19779e4912d05b194f7b49266f33a215dde14a4 Mon Sep 17 00:00:00 2001 From: scottr Date: Mon, 17 May 2021 15:28:32 -0700 Subject: [PATCH 33/69] [cpack_installer] cpack variable usage cleanup --- cmake/Packaging.cmake | 8 +-- .../Platform/Windows/PackagingPostBuild.cmake | 58 ++++++++++--------- .../Platform/Windows/Packaging_windows.cmake | 2 +- 3 files changed, 36 insertions(+), 32 deletions(-) diff --git a/cmake/Packaging.cmake b/cmake/Packaging.cmake index 188cfbf52d..72216847f1 100644 --- a/cmake/Packaging.cmake +++ b/cmake/Packaging.cmake @@ -31,12 +31,12 @@ set(CPACK_RESOURCE_FILE_LICENSE ${DEFAULT_LICENSE_FILE}) set(CPACK_PACKAGE_INSTALL_DIRECTORY "${CPACK_PACKAGE_VENDOR}/${CPACK_PACKAGE_VERSION}") -# custom cpack cache variables for use in pre/post build scripts +# CMAKE_SOURCE_DIR doesn't equate to anything during execution of pre/post build scripts. +# to pass it down, we can utilize the auto-caching of any variable with prefix "CPACK_" set(CPACK_SOURCE_DIR ${CMAKE_SOURCE_DIR}/cmake) -set(CPACK_BINARY_DIR ${CMAKE_BINARY_DIR}/installer) # attempt to apply platform specific settings -ly_get_absolute_pal_filename(pal_dir ${CMAKE_SOURCE_DIR}/cmake/Platform/${PAL_HOST_PLATFORM_NAME}) +ly_get_absolute_pal_filename(pal_dir ${CPACK_SOURCE_DIR}/Platform/${PAL_HOST_PLATFORM_NAME}) include(${pal_dir}/Packaging_${PAL_HOST_PLATFORM_NAME_LOWERCASE}.cmake) # if we get here and the generator hasn't been set, then a non fatal error occurred disabling packaging support @@ -89,7 +89,7 @@ ly_configure_cpack_component( if(LY_INSTALLER_DOWNLOAD_URL) cpack_configure_downloads( ${LY_INSTALLER_DOWNLOAD_URL} - UPLOAD_DIRECTORY artifacts + UPLOAD_DIRECTORY ${CMAKE_BINARY_DIR}/_CPack_Uploads # to match the _CPack_Packages directory ALL ) endif() diff --git a/cmake/Platform/Windows/PackagingPostBuild.cmake b/cmake/Platform/Windows/PackagingPostBuild.cmake index 064fb0d530..5262b42beb 100644 --- a/cmake/Platform/Windows/PackagingPostBuild.cmake +++ b/cmake/Platform/Windows/PackagingPostBuild.cmake @@ -9,49 +9,53 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # -# convert the path to a windows style path -string(REPLACE "/" "\\" _install_dir ${CPACK_PACKAGE_INSTALL_DIRECTORY}) +# convert the path to a windows style path using string replace because TO_NATIVE_PATH +# only works on real paths +string(REPLACE "/" "\\" _fixed_package_install_dir ${CPACK_PACKAGE_INSTALL_DIRECTORY}) # directory where the auto generated files live e.g /_CPack_Package/win64/WIX -set(_cpack_out_dir "${CPACK_TOPLEVEL_DIRECTORY}") -set(_out_dir "${CPACK_BINARY_DIR}/wixobj_bootstrap") +set(_cpack_wix_out_dir ${CPACK_TOPLEVEL_DIRECTORY}) +set(_bootstrap_out_dir "${CPACK_TOPLEVEL_DIRECTORY}/bootstrap") -set(_wix_ext_flags +set(_bootstrap_filename "${CPACK_PACKAGE_FILE_NAME}.exe") +set(_bootstrap_output_file ${_cpack_wix_out_dir}/${_bootstrap_filename}) + +set(_ext_flags -ext WixBalExtension ) -set(_candle_command - ${CPACK_WIX_ROOT}/bin/candle.exe - -nologo - -arch x64 - "-I${_cpack_out_dir}" - ${_wix_ext_flags} - +set(_addtional_defines -dCPACK_DOWNLOAD_SITE=${CPACK_DOWNLOAD_SITE} - -dCPACK_LOCAL_INSTALLER_DIR=${_cpack_out_dir} + -dCPACK_LOCAL_INSTALLER_DIR=${_cpack_wix_out_dir} -dCPACK_PACKAGE_FILE_NAME=${CPACK_PACKAGE_FILE_NAME} - -dCPACK_PACKAGE_INSTALL_DIRECTORY=${_install_dir} + -dCPACK_PACKAGE_INSTALL_DIRECTORY=${_fixed_package_install_dir} +) +set(_candle_command + ${CPACK_WIX_CANDLE_EXECUTABLE} + -nologo + -arch x64 + "-I${_cpack_wix_out_dir}" # to include cpack_variables.wxi + ${_addtional_defines} + ${_ext_flags} "${CPACK_SOURCE_DIR}/Platform/Windows/PackagingBootstrapper.wxs" - - -o "${_out_dir}" + -o "${_bootstrap_out_dir}/" ) set(_light_command - ${CPACK_WIX_ROOT}/bin/light.exe + ${CPACK_WIX_LIGHT_EXECUTABLE} -nologo - ${_wix_ext_flags} - ${_out_dir}/*.wixobj - - -o "${CPACK_BINARY_DIR}/installer.exe" + ${_ext_flags} + ${_bootstrap_out_dir}/*.wixobj + -o "${_bootstrap_output_file}" ) message(STATUS "Creating Installer Bootstrapper...") - execute_process( - COMMAND - ${_candle_command} - - COMMAND - ${_light_command} + COMMAND ${_candle_command} + COMMAND_ERROR_IS_FATAL ANY +) +execute_process( + COMMAND ${_light_command} + COMMAND_ERROR_IS_FATAL ANY ) diff --git a/cmake/Platform/Windows/Packaging_windows.cmake b/cmake/Platform/Windows/Packaging_windows.cmake index f8c79e11d5..4044450d4c 100644 --- a/cmake/Platform/Windows/Packaging_windows.cmake +++ b/cmake/Platform/Windows/Packaging_windows.cmake @@ -32,7 +32,7 @@ set(CPACK_GENERATOR "WIX") # however, they are unique for each run. instead, let's do the auto generation here and add it to # the cache for run persistence. an additional cache file will be used to store the information on # the original generation so we still have the ability to detect if they are still being used. -set(_guid_cache_file "${CPACK_BINARY_DIR}/wix_guid_cache.cmake") +set(_guid_cache_file "${CMAKE_BINARY_DIR}/CPackWiXConfig.cmake") if(NOT EXISTS ${_guid_cache_file}) set(_wix_guid_namespace "6D43F57A-2917-4AD9-B758-1F13CDB08593") From 2ddbd36f9a7ff537bb98b05d8894326fc7636c81 Mon Sep 17 00:00:00 2001 From: scottr Date: Mon, 17 May 2021 16:34:24 -0700 Subject: [PATCH 34/69] [cpack_installer] add option to specify license url. replicate online artifacts copy --- cmake/Packaging.cmake | 12 ++++++++---- .../Windows/PackagingBootstrapper.wxs | 18 +++++++++++++----- .../Platform/Windows/PackagingPostBuild.cmake | 19 +++++++++++++++++++ 3 files changed, 40 insertions(+), 9 deletions(-) diff --git a/cmake/Packaging.cmake b/cmake/Packaging.cmake index 72216847f1..0fbd70e54c 100644 --- a/cmake/Packaging.cmake +++ b/cmake/Packaging.cmake @@ -13,10 +13,14 @@ if(NOT PAL_TRAIT_BUILD_CPACK_SUPPORTED) return() endif() -# set the common cpack variables first so they are accessible via configure_file -# when the platforms specific properties are applied below +# public facing options will eventually be converted into cpack specific ones below. +# all variables with the "CPACK_" prefix will automatically be cached for use in any +# of the build steps cpack runs e.g. pre-build, standard build, post-build. set(LY_INSTALLER_DOWNLOAD_URL "" CACHE STRING "URL embded into the installer to download additional artifacts") +set(LY_INSTALLER_LICENSE_URL "" CACHE STRING "Optionally embed a link to the license instead of raw text") +# set all common cpack variable overrides first so they can be accessible via configure_file +# when the platform specific settings are applied below set(CPACK_PACKAGE_VENDOR "${PROJECT_NAME}") set(CPACK_PACKAGE_VERSION "${LY_VERSION_STRING}") set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Installation Tool") @@ -28,11 +32,11 @@ set(DEFAULT_LICENSE_NAME "Apache-2.0") set(DEFAULT_LICENSE_FILE "${CMAKE_SOURCE_DIR}/LICENSE.txt") set(CPACK_RESOURCE_FILE_LICENSE ${DEFAULT_LICENSE_FILE}) +set(CPACK_LICENSE_URL ${LY_INSTALLER_LICENSE_URL}) set(CPACK_PACKAGE_INSTALL_DIRECTORY "${CPACK_PACKAGE_VENDOR}/${CPACK_PACKAGE_VERSION}") -# CMAKE_SOURCE_DIR doesn't equate to anything during execution of pre/post build scripts. -# to pass it down, we can utilize the auto-caching of any variable with prefix "CPACK_" +# CMAKE_SOURCE_DIR doesn't equate to anything during execution of pre/post build scripts set(CPACK_SOURCE_DIR ${CMAKE_SOURCE_DIR}/cmake) # attempt to apply platform specific settings diff --git a/cmake/Platform/Windows/PackagingBootstrapper.wxs b/cmake/Platform/Windows/PackagingBootstrapper.wxs index 711b60d854..f231f05413 100644 --- a/cmake/Platform/Windows/PackagingBootstrapper.wxs +++ b/cmake/Platform/Windows/PackagingBootstrapper.wxs @@ -16,11 +16,19 @@ Value="[ProgramFiles64Folder]$(var.CPACK_PACKAGE_INSTALL_DIRECTORY)" bal:Overridable="yes"/> - - - + + + + + + + + + Date: Mon, 17 May 2021 16:48:05 -0700 Subject: [PATCH 35/69] Move static buffer to member to prevent potential memory issues --- .../Editor/MultiplayerEditorConnection.cpp | 30 +++++++++---------- .../Editor/MultiplayerEditorConnection.h | 2 ++ 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.cpp b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.cpp index e5f7ed4a68..2fdd29c542 100644 --- a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.cpp +++ b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.cpp @@ -27,12 +27,10 @@ namespace Multiplayer { using namespace AzNetworking; - static AZStd::vector buffer; - static AZ::IO::ByteContainerStream> s_byteStream(&buffer); - AZ_CVAR(bool, editorsv_isDedicated, false, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "Whether to init as a server expecting data from an Editor. Do not modify unless you're sure of what you're doing."); MultiplayerEditorConnection::MultiplayerEditorConnection() + : m_byteStream(&m_buffer) { m_networkEditorInterface = AZ::Interface::Get()->CreateNetworkInterface( AZ::Name(MPEditorInterfaceName), ProtocolType::Tcp, TrustZone::ExternalClientToServer, *this); @@ -59,31 +57,31 @@ namespace Multiplayer if (!packet.GetLastUpdate()) { // More packets are expected, flush this to the buffer - s_byteStream.Write(TcpPacketEncodingBuffer::GetCapacity(), reinterpret_cast(packet.ModifyAssetData().GetBuffer())); + m_byteStream.Write(TcpPacketEncodingBuffer::GetCapacity(), reinterpret_cast(packet.ModifyAssetData().GetBuffer())); } else { // This is the last expected packet, flush it to the buffer - s_byteStream.Write(packet.GetAssetData().GetSize(), reinterpret_cast(packet.ModifyAssetData().GetBuffer())); + m_byteStream.Write(packet.GetAssetData().GetSize(), reinterpret_cast(packet.ModifyAssetData().GetBuffer())); // Read all assets out of the buffer - s_byteStream.Seek(0, AZ::IO::GenericStream::SeekMode::ST_SEEK_BEGIN); + m_byteStream.Seek(0, AZ::IO::GenericStream::SeekMode::ST_SEEK_BEGIN); AZStd::vector> assetData; - while (s_byteStream.GetCurPos() < s_byteStream.GetLength()) + while (m_byteStream.GetCurPos() < m_byteStream.GetLength()) { AZ::Data::AssetId assetId; AZ::Data::AssetLoadBehavior assetLoadBehavior; uint32_t hintSize; AZStd::string assetHint; - s_byteStream.Read(sizeof(AZ::Data::AssetId), reinterpret_cast(&assetId)); - s_byteStream.Read(sizeof(AZ::Data::AssetLoadBehavior), reinterpret_cast(&assetLoadBehavior)); - s_byteStream.Read(sizeof(uint32_t), reinterpret_cast(&hintSize)); + m_byteStream.Read(sizeof(AZ::Data::AssetId), reinterpret_cast(&assetId)); + m_byteStream.Read(sizeof(AZ::Data::AssetLoadBehavior), reinterpret_cast(&assetLoadBehavior)); + m_byteStream.Read(sizeof(uint32_t), reinterpret_cast(&hintSize)); assetHint.resize(hintSize); - s_byteStream.Read(hintSize, assetHint.data()); + m_byteStream.Read(hintSize, assetHint.data()); - size_t assetSize = s_byteStream.GetCurPos(); - AZ::Data::AssetData* assetDatum = AZ::Utils::LoadObjectFromStream(s_byteStream, nullptr); - assetSize = s_byteStream.GetCurPos() - assetSize; + size_t assetSize = m_byteStream.GetCurPos(); + AZ::Data::AssetData* assetDatum = AZ::Utils::LoadObjectFromStream(m_byteStream, nullptr); + assetSize = m_byteStream.GetCurPos() - assetSize; AZ::Data::Asset asset = AZ::Data::Asset(assetId, assetDatum, assetLoadBehavior); asset.SetHint(assetHint); @@ -101,8 +99,8 @@ namespace Multiplayer } // Now that we've deserialized, clear the byte stream - s_byteStream.Seek(0, AZ::IO::GenericStream::SeekMode::ST_SEEK_BEGIN); - s_byteStream.Truncate(); + m_byteStream.Seek(0, AZ::IO::GenericStream::SeekMode::ST_SEEK_BEGIN); + m_byteStream.Truncate(); // Load the level via the root spawnable that was registered AZ::CVarFixedString loadLevelString = "LoadLevel Root.spawnable"; diff --git a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.h b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.h index 3621e3aee6..d559651080 100644 --- a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.h +++ b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.h @@ -51,5 +51,7 @@ namespace Multiplayer private: AzNetworking::INetworkInterface* m_networkEditorInterface = nullptr; + AZStd::vector m_buffer; + AZ::IO::ByteContainerStream> m_byteStream; }; } From 93e267345fb2fb8954ec090e25296dc7c625c98e Mon Sep 17 00:00:00 2001 From: puvvadar Date: Mon, 17 May 2021 18:46:21 -0700 Subject: [PATCH 36/69] Address string/mem feedback plus some misc cleanup --- .../Multiplayer/MultiplayerConstants.h | 10 ++--- .../Editor/MultiplayerEditorConnection.cpp | 7 ++-- .../Source/Editor/MultiplayerEditorGem.cpp | 12 +++--- .../MultiplayerEditorSystemComponent.cpp | 38 ++++++++----------- .../Editor/MultiplayerEditorSystemComponent.h | 4 +- .../Source/MultiplayerSystemComponent.cpp | 20 +++++----- .../Pipeline/NetworkPrefabProcessor.cpp | 12 +++--- 7 files changed, 49 insertions(+), 54 deletions(-) diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/MultiplayerConstants.h b/Gems/Multiplayer/Code/Include/Multiplayer/MultiplayerConstants.h index 892691177a..b82fab91be 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/MultiplayerConstants.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/MultiplayerConstants.h @@ -21,12 +21,12 @@ namespace Multiplayer { - static constexpr AZStd::string_view MPNetworkInterfaceName("MultiplayerNetworkInterface"); - static constexpr AZStd::string_view MPEditorInterfaceName("MultiplayerEditorNetworkInterface"); + constexpr AZStd::string_view MPNetworkInterfaceName("MultiplayerNetworkInterface"); + constexpr AZStd::string_view MPEditorInterfaceName("MultiplayerEditorNetworkInterface"); - static constexpr AZStd::string_view LocalHost("127.0.0.1"); - static constexpr uint16_t DefaultServerPort = 30090; - static constexpr uint16_t DefaultServerEditorPort = 30091; + constexpr AZStd::string_view LocalHost("127.0.0.1"); + constexpr uint16_t DefaultServerPort = 30090; + constexpr uint16_t DefaultServerEditorPort = 30091; } diff --git a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.cpp b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.cpp index 2fdd29c542..f88a314ea5 100644 --- a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.cpp +++ b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.cpp @@ -12,16 +12,17 @@ #include #include -#include +#include #include -#include -#include + #include #include #include #include #include #include +#include +#include namespace Multiplayer { diff --git a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorGem.cpp b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorGem.cpp index ae38ef1d6d..2fe0aabe5f 100644 --- a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorGem.cpp +++ b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorGem.cpp @@ -10,13 +10,13 @@ * */ -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include -#include +#include namespace Multiplayer { diff --git a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp index d837b15b05..159f3b944b 100644 --- a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp @@ -13,13 +13,15 @@ #include #include #include + +#include +#include #include -#include -#include -#include + #include #include #include +#include #include #include #include @@ -31,11 +33,11 @@ namespace Multiplayer AZ_CVAR(bool, editorsv_enabled, true, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "Whether Editor launching a local server to connect to is supported"); - AZ_CVAR(bool, editorsv_launch, false, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, + AZ_CVAR(bool, editorsv_launch, true, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "Whether Editor should launch a server when the server address is localhost"); AZ_CVAR(AZ::CVarFixedString, editorsv_process, "", nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The server executable that should be run. Empty to use the current project's ServerLauncher"); - AZ_CVAR(AZ::CVarFixedString, editorsv_serveraddr, LocalHost.data(), nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The address of the server to connect to"); + AZ_CVAR(AZ::CVarFixedString, editorsv_serveraddr, AZ::CVarFixedString(LocalHost), nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The address of the server to connect to"); AZ_CVAR(uint16_t, editorsv_port, DefaultServerEditorPort, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The port that the multiplayer editor gem will bind to for traffic"); void MultiplayerEditorSystemComponent::Reflect(AZ::ReflectContext* context) @@ -115,34 +117,24 @@ namespace Multiplayer } } - void LaunchEditorServer(AzFramework::ProcessWatcher* outProcess) + AzFramework::ProcessWatcher* LaunchEditorServer() { // Assemble the server's path AZ::CVarFixedString serverProcess = editorsv_process; + AZ::IO::FixedMaxPath serverPath; if (serverProcess.empty()) { // If enabled but no process name is supplied, try this project's ServerLauncher serverProcess = AZ::Utils::GetProjectName() + ".ServerLauncher"; - } - AZ::IO::FixedMaxPathString serverPath = AZ::Utils::GetExecutableDirectory(); - if (!serverProcess.contains(AZ_TRAIT_OS_PATH_SEPARATOR)) - { - // If only the process name is specified, append that as well - serverPath.append(AZ_TRAIT_OS_PATH_SEPARATOR + serverProcess); + serverPath = AZ::Utils::GetExecutableDirectory(); + serverPath /= serverProcess + AZ_TRAIT_OS_EXECUTABLE_EXTENSION; } else { - // If any path was already specified, then simply assign serverPath = serverProcess; } - if (!serverProcess.ends_with(AZ_TRAIT_OS_EXECUTABLE_EXTENSION)) - { - // Add this platform's exe extension if it's not specified - serverPath.append(AZ_TRAIT_OS_EXECUTABLE_EXTENSION); - } - // Start the configured server if it's available AzFramework::ProcessLauncher::ProcessLaunchInfo processLaunchInfo; processLaunchInfo.m_commandlineParameters = AZStd::string::format("\"%s\" --editorsv_isDedicated true", serverPath.c_str()); @@ -150,9 +142,11 @@ namespace Multiplayer processLaunchInfo.m_processPriority = AzFramework::ProcessPriority::PROCESSPRIORITY_NORMAL; // Launch the Server and give it a few seconds to boot up - outProcess = AzFramework::ProcessWatcher::LaunchProcess( + AzFramework::ProcessWatcher* outProcess = AzFramework::ProcessWatcher::LaunchProcess( processLaunchInfo, AzFramework::ProcessCommunicationType::COMMUNICATOR_TYPE_NONE); AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(15000)); + + return outProcess; } void MultiplayerEditorSystemComponent::OnGameEntitiesStarted() @@ -188,9 +182,9 @@ namespace Multiplayer } const AZ::CVarFixedString remoteAddress = editorsv_serveraddr; - if (editorsv_launch && LocalHost.compare(remoteAddress.c_str()) == 0) + if (editorsv_launch && LocalHost == remoteAddress) { - LaunchEditorServer(m_serverProcess); + m_serverProcess = LaunchEditorServer(); } // Now that the server has launched, attempt to connect the NetworkInterface diff --git a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.h b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.h index 569092e981..81b138c675 100644 --- a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.h +++ b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.h @@ -14,18 +14,16 @@ #include -#include +#include #include #include #include #include - #include #include #include - namespace AzNetworking { class INetworkInterface; diff --git a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp index c6a8458eae..aa6fe6e72c 100644 --- a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp +++ b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp @@ -11,15 +11,16 @@ */ #include -#include -#include -#include -#include -#include -#include -#include #include -#include + +#include +#include +#include +#include +#include +#include +#include + #include #include #include @@ -29,6 +30,7 @@ #include #include #include +#include namespace AZ::ConsoleTypeHelpers { @@ -63,7 +65,7 @@ namespace Multiplayer using namespace AzNetworking; AZ_CVAR(uint16_t, cl_clientport, 0, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The port to bind to for game traffic when connecting to a remote host, a value of 0 will select any available port"); - AZ_CVAR(AZ::CVarFixedString, cl_serveraddr, LocalHost.data(), nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The address of the remote server or host to connect to"); + AZ_CVAR(AZ::CVarFixedString, cl_serveraddr, AZ::CVarFixedString(LocalHost), nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The address of the remote server or host to connect to"); AZ_CVAR(AZ::CVarFixedString, cl_serverpassword, "", nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "Optional server password"); AZ_CVAR(uint16_t, cl_serverport, DefaultServerPort, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The port of the remote host to connect to for game traffic"); AZ_CVAR(uint16_t, sv_port, DefaultServerPort, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The port that this multiplayer gem will bind to for game traffic"); diff --git a/Gems/Multiplayer/Code/Source/Pipeline/NetworkPrefabProcessor.cpp b/Gems/Multiplayer/Code/Source/Pipeline/NetworkPrefabProcessor.cpp index 8bea44e893..bd6899aad6 100644 --- a/Gems/Multiplayer/Code/Source/Pipeline/NetworkPrefabProcessor.cpp +++ b/Gems/Multiplayer/Code/Source/Pipeline/NetworkPrefabProcessor.cpp @@ -10,7 +10,11 @@ * */ -#include +#include +#include +#include +#include +#include #include #include @@ -18,10 +22,6 @@ #include #include #include -#include -#include -#include -#include namespace Multiplayer { @@ -40,7 +40,7 @@ namespace Multiplayer ProcessPrefab(context, prefabName, prefab); }); - if (mpTools && context.GetProcessedObjects().size() > 0) + if (mpTools && !context.GetProcessedObjects().empty()) { mpTools->SetDidProcessNetworkPrefabs(true); } From 8792cac88a863d9b1daacf6d6964c5d58af80062 Mon Sep 17 00:00:00 2001 From: sconel Date: Mon, 17 May 2021 18:50:21 -0700 Subject: [PATCH 37/69] Updating SpawnableEntitiesManager to handle entity references during spawn --- .../Spawnable/SpawnableEntitiesManager.cpp | 28 ++++++++++++++----- .../Spawnable/SpawnableEntitiesManager.h | 7 ++++- 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp index 3ab004b516..ac71b9fed8 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp @@ -11,6 +11,7 @@ */ #include +#include #include #include #include @@ -200,11 +201,13 @@ namespace AzFramework } } - AZ::Entity* SpawnableEntitiesManager::SpawnSingleEntity(const AZ::Entity& entityTemplate, AZ::SerializeContext& serializeContext) + AZ::Entity* SpawnableEntitiesManager::SpawnSingleEntity(const AZ::Entity& entityTemplate, EntityIdMap& spawnableToInstanceEntityIdMap, + AZ::SerializeContext& serializeContext) { - AZ::Entity* clone = serializeContext.CloneObject(&entityTemplate); + AZ::Entity* clone = AZ::IdUtils::Remapper::CloneObjectAndGenerateNewIdsAndFixRefs( + &entityTemplate, spawnableToInstanceEntityIdMap, &serializeContext); + AZ_Assert(clone != nullptr, "Failed to clone spawnable entity."); - clone->SetId(AZ::Entity::MakeId()); GameEntityContextRequestBus::Broadcast(&GameEntityContextRequestBus::Events::AddGameEntity, clone); return clone; } @@ -217,13 +220,16 @@ namespace AzFramework size_t spawnedEntitiesCount = ticket.m_spawnedEntities.size(); const Spawnable::EntityList& entities = ticket.m_spawnable->GetEntities(); + EntityIdMap& spawnableToInstanceEntityIdMap = ticket.m_spawnableToInstanceEntityIdMap; + size_t entitiesSize = entities.size(); ticket.m_spawnedEntities.reserve(ticket.m_spawnedEntities.size() + entitiesSize); ticket.m_spawnedEntityIndices.reserve(ticket.m_spawnedEntityIndices.size() + entitiesSize); + spawnableToInstanceEntityIdMap.reserve(entitiesSize); for(size_t i=0; iGetEntities(); + EntityIdMap& spawnableToInstanceEntityIdMap = ticket.m_spawnableToInstanceEntityIdMap; + size_t entitiesSize = entities.size(); ticket.m_spawnedEntities.reserve(ticket.m_spawnedEntities.size() + entitiesSize); ticket.m_spawnedEntityIndices.reserve(ticket.m_spawnedEntityIndices.size() + entitiesSize); + spawnableToInstanceEntityIdMap.reserve(entitiesSize); for (size_t index : request.m_entityIndices) { - ticket.m_spawnedEntities.push_back(SpawnSingleEntity(*entities[index], serializeContext)); + ticket.m_spawnedEntities.push_back(SpawnSingleEntity(*entities[index], spawnableToInstanceEntityIdMap, serializeContext)); ticket.m_spawnedEntityIndices.push_back(index); } ticket.m_loadAll = false; @@ -337,6 +346,8 @@ namespace AzFramework // Rebuild the list of entities. ticket.m_spawnedEntities.clear(); const Spawnable::EntityList& entities = request.m_spawnable->GetEntities(); + EntityIdMap& spawnableToInstanceEntityIdMap = ticket.m_spawnableToInstanceEntityIdMap; + if (ticket.m_loadAll) { // The new spawnable may have a different number of entities and since the intent of the user was @@ -346,7 +357,9 @@ namespace AzFramework size_t entitiesSize = entities.size(); for (size_t i = 0; i < entitiesSize; ++i) { - ticket.m_spawnedEntities.push_back(SpawnSingleEntity(*entities[i], serializeContext)); + ticket.m_spawnedEntities.push_back( + SpawnSingleEntity(*entities[i], spawnableToInstanceEntityIdMap, serializeContext)); + ticket.m_spawnedEntityIndices.push_back(i); } } @@ -356,7 +369,8 @@ namespace AzFramework for (size_t index : ticket.m_spawnedEntityIndices) { ticket.m_spawnedEntities.push_back( - index < entitiesSize ? SpawnSingleEntity(*entities[index], serializeContext) : nullptr); + index < entitiesSize ? + SpawnSingleEntity(*entities[index], spawnableToInstanceEntityIdMap, serializeContext) : nullptr); } } ticket.m_spawnable = AZStd::move(request.m_spawnable); diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.h b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.h index c70b9ccaa6..b101268c5f 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.h +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.h @@ -29,6 +29,8 @@ namespace AZ namespace AzFramework { + using EntityIdMap = AZStd::unordered_map; + class SpawnableEntitiesManager : public SpawnableEntitiesInterface::Registrar { @@ -81,6 +83,8 @@ namespace AzFramework AZStd::vector m_spawnedEntities; AZStd::vector m_spawnedEntityIndices; + EntityIdMap m_spawnableToInstanceEntityIdMap; + AZ::Data::Asset m_spawnable; uint32_t m_nextTicketId{ 0 }; //!< Next id for this ticket. uint32_t m_currentTicketId{ 0 }; //!< The id for the command that should be executed. @@ -140,7 +144,8 @@ namespace AzFramework using Requests = AZStd::variant; - AZ::Entity* SpawnSingleEntity(const AZ::Entity& entityTemplate, AZ::SerializeContext& serializeContext); + AZ::Entity* SpawnSingleEntity(const AZ::Entity& entityTemplate, EntityIdMap& spawnableToInstanceEntityIdMap, + AZ::SerializeContext& serializeContext); bool ProcessRequest(SpawnAllEntitiesCommand& request, AZ::SerializeContext& serializeContext); bool ProcessRequest(SpawnEntitiesCommand& request, AZ::SerializeContext& serializeContext); From cf4e04ba573aad88417671a1424f3896cac96b6b Mon Sep 17 00:00:00 2001 From: puvvadar Date: Mon, 17 May 2021 18:52:40 -0700 Subject: [PATCH 38/69] Cleanup a few more headers --- .../Code/Source/Editor/MultiplayerEditorConnection.h | 3 ++- Gems/Multiplayer/Code/Source/MultiplayerToolsModule.cpp | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.h b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.h index d559651080..d803a60744 100644 --- a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.h +++ b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.h @@ -12,6 +12,8 @@ #pragma once +#include + #include #include #include @@ -19,7 +21,6 @@ #include #include #include -#include namespace AzNetworking { diff --git a/Gems/Multiplayer/Code/Source/MultiplayerToolsModule.cpp b/Gems/Multiplayer/Code/Source/MultiplayerToolsModule.cpp index ae91999a0c..3646dd52a4 100644 --- a/Gems/Multiplayer/Code/Source/MultiplayerToolsModule.cpp +++ b/Gems/Multiplayer/Code/Source/MultiplayerToolsModule.cpp @@ -10,9 +10,10 @@ * */ -#include -#include +#include +#include #include + #include #include From 920f85981de33e1ebf2bae5d548b3a6df41d1a6a Mon Sep 17 00:00:00 2001 From: puvvadar Date: Mon, 17 May 2021 18:55:00 -0700 Subject: [PATCH 39/69] Add another missed header file --- .../Code/Source/MultiplayerSystemComponent.h | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h index a48363abe4..cac64db89b 100644 --- a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h +++ b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h @@ -12,6 +12,12 @@ #pragma once +#include +#include +#include +#include +#include + #include #include #include @@ -20,11 +26,6 @@ #include #include #include -#include -#include -#include -#include -#include namespace AzNetworking { From 37b2ac797d9c78fb557051301ec52a146e5d3f57 Mon Sep 17 00:00:00 2001 From: scottr Date: Mon, 17 May 2021 20:52:06 -0700 Subject: [PATCH 40/69] [cpack_installer] bootstrap installer is copied to root of build directory. uploads directory is cleaned before copied to. --- cmake/Platform/Windows/PackagingPostBuild.cmake | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/cmake/Platform/Windows/PackagingPostBuild.cmake b/cmake/Platform/Windows/PackagingPostBuild.cmake index bfe725ab3b..cfed90e155 100644 --- a/cmake/Platform/Windows/PackagingPostBuild.cmake +++ b/cmake/Platform/Windows/PackagingPostBuild.cmake @@ -54,7 +54,7 @@ set(_light_command -o "${_bootstrap_output_file}" ) -message(STATUS "Creating Installer Bootstrapper...") +message(STATUS "Creating Bootstrap Installer...") execute_process( COMMAND ${_candle_command} COMMAND_ERROR_IS_FATAL ANY @@ -64,6 +64,12 @@ execute_process( COMMAND_ERROR_IS_FATAL ANY ) +file(COPY ${_bootstrap_output_file} + DESTINATION ${CPACK_PACKAGE_DIRECTORY} +) + +message(STATUS "Bootstrap installer generated to ${CPACK_PACKAGE_DIRECTORY}/${_bootstrap_filename}") + # use the internal default path if somehow not specified from cpack_configure_downloads if(NOT CPACK_UPLOAD_DIRECTORY) set(CPACK_UPLOAD_DIRECTORY ${CPACK_PACKAGE_DIRECTORY}/CPackUploads) @@ -73,6 +79,7 @@ endif() # through cpack_configure_downloads. this mimics the same process cpack does natively for # some other frameworks that have built-in online installer support. message(STATUS "Copying installer artifacts to upload directory...") +file(REMOVE_RECURSE ${CPACK_UPLOAD_DIRECTORY}) file(GLOB _artifacts "${_cpack_wix_out_dir}/*.msi" "${_cpack_wix_out_dir}/*.cab") file(COPY ${_artifacts} DESTINATION ${CPACK_UPLOAD_DIRECTORY} From efbb0077b5c8c79a73aa1a6a77a967e15ed81a19 Mon Sep 17 00:00:00 2001 From: scottr Date: Mon, 17 May 2021 21:43:46 -0700 Subject: [PATCH 41/69] [cpack_installer] configure install to be per machine --- cmake/Platform/Windows/PackagingTemplate.wxs.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/Platform/Windows/PackagingTemplate.wxs.in b/cmake/Platform/Windows/PackagingTemplate.wxs.in index fd3610259a..0b3c597ab6 100644 --- a/cmake/Platform/Windows/PackagingTemplate.wxs.in +++ b/cmake/Platform/Windows/PackagingTemplate.wxs.in @@ -12,7 +12,7 @@ Manufacturer="$(var.CPACK_PACKAGE_VENDOR)" UpgradeCode="$(var.CPACK_WIX_UPGRADE_GUID)"> - + From 66ad040102cf1b60bf54dba93826e6a48370c6f3 Mon Sep 17 00:00:00 2001 From: scottr Date: Mon, 17 May 2021 22:51:48 -0700 Subject: [PATCH 42/69] [cpack_installer] some minor comment cleanup --- cmake/Packaging.cmake | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/cmake/Packaging.cmake b/cmake/Packaging.cmake index 0fbd70e54c..8f4e1134b9 100644 --- a/cmake/Packaging.cmake +++ b/cmake/Packaging.cmake @@ -13,14 +13,14 @@ if(NOT PAL_TRAIT_BUILD_CPACK_SUPPORTED) return() endif() -# public facing options will eventually be converted into cpack specific ones below. -# all variables with the "CPACK_" prefix will automatically be cached for use in any -# of the build steps cpack runs e.g. pre-build, standard build, post-build. +# public facing options will be used for conversion into cpack specific ones below. set(LY_INSTALLER_DOWNLOAD_URL "" CACHE STRING "URL embded into the installer to download additional artifacts") set(LY_INSTALLER_LICENSE_URL "" CACHE STRING "Optionally embed a link to the license instead of raw text") # set all common cpack variable overrides first so they can be accessible via configure_file -# when the platform specific settings are applied below +# when the platform specific settings are applied below. additionally, any variable with +# the "CPACK_" prefix will automatically be cached for use in any phase of cpack namely +# pre/post build set(CPACK_PACKAGE_VENDOR "${PROJECT_NAME}") set(CPACK_PACKAGE_VERSION "${LY_VERSION_STRING}") set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Installation Tool") @@ -91,6 +91,7 @@ ly_configure_cpack_component( ) if(LY_INSTALLER_DOWNLOAD_URL) + # this will set the following variables: CPACK_DOWNLOAD_SITE, CPACK_DOWNLOAD_ALL, and CPACK_UPLOAD_DIRECTORY cpack_configure_downloads( ${LY_INSTALLER_DOWNLOAD_URL} UPLOAD_DIRECTORY ${CMAKE_BINARY_DIR}/_CPack_Uploads # to match the _CPack_Packages directory From 242a10dd10c4d206bd30af7009b30a6ae76b59db Mon Sep 17 00:00:00 2001 From: antonmic Date: Mon, 17 May 2021 23:57:01 -0700 Subject: [PATCH 43/69] addressed PR feedback --- .../Materials/Types/StandardPBR.materialtype | 24 ------------------- .../Types/StandardPBR_LowEndForward.shader | 6 +++++ .../StandardPBR_LowEndForward_EDS.shader | 6 +++++ .../Atom/Features/PBR/Lights/Ibl.azsli | 7 +++--- .../Atom/Features/ShaderQualityOptions.azsli | 7 ++++-- .../RPI/Code/Source/RPI.Public/Pass/Pass.cpp | 2 +- 6 files changed, 22 insertions(+), 30 deletions(-) diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR.materialtype b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR.materialtype index a9a3e9e09b..2b0d09bc5c 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR.materialtype +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR.materialtype @@ -1297,12 +1297,6 @@ "textureProperty": "baseColor.textureMap", "useTextureProperty": "baseColor.useTexture", "dependentProperties": ["baseColor.textureMapUv", "baseColor.textureBlendMode"], - "shaderTags": [ - "ForwardPass", - "ForwardPass_EDS", - "LowEndForward", - "LowEndForward_EDS" - ], "shaderOption": "o_baseColor_useTexture" } }, @@ -1312,12 +1306,6 @@ "textureProperty": "metallic.textureMap", "useTextureProperty": "metallic.useTexture", "dependentProperties": ["metallic.textureMapUv"], - "shaderTags": [ - "ForwardPass", - "ForwardPass_EDS", - "LowEndForward", - "LowEndForward_EDS" - ], "shaderOption": "o_metallic_useTexture" } }, @@ -1327,12 +1315,6 @@ "textureProperty": "specularF0.textureMap", "useTextureProperty": "specularF0.useTexture", "dependentProperties": ["specularF0.textureMapUv"], - "shaderTags": [ - "ForwardPass", - "ForwardPass_EDS", - "LowEndForward", - "LowEndForward_EDS" - ], "shaderOption": "o_specularF0_useTexture" } }, @@ -1342,12 +1324,6 @@ "textureProperty": "normal.textureMap", "useTextureProperty": "normal.useTexture", "dependentProperties": ["normal.textureMapUv", "normal.factor", "normal.flipX", "normal.flipY"], - "shaderTags": [ - "ForwardPass", - "ForwardPass_EDS", - "LowEndForward", - "LowEndForward_EDS" - ], "shaderOption": "o_normal_useTexture" } }, diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_LowEndForward.shader b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_LowEndForward.shader index 19538e5db3..44139608ca 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_LowEndForward.shader +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_LowEndForward.shader @@ -1,4 +1,10 @@ { + // Note: "LowEnd" shaders are for supporting the low end pipeline + // These shaders can be safely added to materials without incurring additional runtime draw + // items as draw items for shaders are only created if the scene has a pass with a matching + // DrawListTag. If your pipeline doesn't have a "lowEndForward" DrawListTag, no draw items + // for this shader will be created. + "Source" : "./StandardPBR_LowEndForward.azsl", "DepthStencilState" : diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_LowEndForward_EDS.shader b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_LowEndForward_EDS.shader index 1b5f014d0e..9faa1d3698 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_LowEndForward_EDS.shader +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_LowEndForward_EDS.shader @@ -1,4 +1,10 @@ { + // Note: "LowEnd" shaders are for supporting the low end pipeline + // These shaders can be safely added to materials without incurring additional runtime draw + // items as draw items for shaders are only created if the scene has a pass with a matching + // DrawListTag. If your pipeline doesn't have a "lowEndForward" DrawListTag, no draw items + // for this shader will be created. + "Source" : "./StandardPBR_LowEndForward.azsl", "DepthStencilState" : diff --git a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Lights/Ibl.azsli b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Lights/Ibl.azsli index 3e3544fe9e..3be9d5756a 100644 --- a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Lights/Ibl.azsli +++ b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Lights/Ibl.azsli @@ -81,10 +81,11 @@ void ApplyIBL(Surface surface, inout LightingData lightingData) #ifdef FORCE_IBL_IN_FORWARD_PASS bool useDiffuseIbl = true; bool useSpecularIbl = true; - bool useIbl = true; + bool useIbl = o_enableIBL; #else - bool useDiffuseIbl = (o_opacity_mode == OpacityMode::Blended || o_opacity_mode == OpacityMode::TintedTransparent); - bool useSpecularIbl = (useDiffuseIbl || o_meshUseForwardPassIBLSpecular || o_materialUseForwardPassIBLSpecular); + bool isTransparent = (o_opacity_mode == OpacityMode::Blended || o_opacity_mode == OpacityMode::TintedTransparent); + bool useDiffuseIbl = isTransparent; + bool useSpecularIbl = (isTransparent || o_meshUseForwardPassIBLSpecular || o_materialUseForwardPassIBLSpecular); bool useIbl = o_enableIBL && (useDiffuseIbl || useSpecularIbl); #endif diff --git a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/ShaderQualityOptions.azsli b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/ShaderQualityOptions.azsli index 6e89269f8d..cc4aa7cf42 100644 --- a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/ShaderQualityOptions.azsli +++ b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/ShaderQualityOptions.azsli @@ -16,8 +16,11 @@ #ifdef QUALITY_LOW_END -#define UNIFIED_FORWARD_OUTPUT 1 -#define FORCE_IBL_IN_FORWARD_PASS 1 + // Unifies the forward output into a single lighting buffer instead of splitting it into a GBuffer + #define UNIFIED_FORWARD_OUTPUT 1 + + // Forces IBL lighting to be executed in the forward pass instead of subsequent refleciton passes + #define FORCE_IBL_IN_FORWARD_PASS 1 #endif diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/Pass.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/Pass.cpp index f93d661b0f..6ed8ac018c 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/Pass.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/Pass.cpp @@ -141,7 +141,7 @@ namespace AZ } // Set new tree depth and path - m_flags.m_parentEnabled = m_parent->IsEnabled(); + m_flags.m_parentEnabled = m_parent->m_flags.m_enabled && (m_parent->m_flags.m_parentEnabled || m_parent->m_parent == nullptr); m_treeDepth = m_parent->m_treeDepth + 1; m_path = ConcatPassName(m_parent->m_path, m_name); m_flags.m_partOfHierarchy = m_parent->m_flags.m_partOfHierarchy; From b45d01919dac7e4ff096f58ab81c422e99d4faa6 Mon Sep 17 00:00:00 2001 From: scottr Date: Tue, 18 May 2021 00:22:17 -0700 Subject: [PATCH 44/69] [cpack_installer] simplify guid generation by using existing project props instead of timestamp in seed value. add bootstrapper specific guids. --- .../Windows/PackagingBootstrapper.wxs | 2 +- .../Platform/Windows/PackagingPostBuild.cmake | 1 + .../Platform/Windows/Packaging_windows.cmake | 42 ++++++++----------- 3 files changed, 19 insertions(+), 26 deletions(-) diff --git a/cmake/Platform/Windows/PackagingBootstrapper.wxs b/cmake/Platform/Windows/PackagingBootstrapper.wxs index f231f05413..c3d1dd7a7b 100644 --- a/cmake/Platform/Windows/PackagingBootstrapper.wxs +++ b/cmake/Platform/Windows/PackagingBootstrapper.wxs @@ -8,7 +8,7 @@ Date: Tue, 18 May 2021 15:06:26 +0530 Subject: [PATCH 45/69] removed unused import --- .../editor_python_test_tools/pyside_utils.py | 1 - 1 file changed, 1 deletion(-) diff --git a/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools/editor_python_test_tools/pyside_utils.py b/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools/editor_python_test_tools/pyside_utils.py index 10f060bba6..eed6a18614 100644 --- a/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools/editor_python_test_tools/pyside_utils.py +++ b/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools/editor_python_test_tools/pyside_utils.py @@ -18,7 +18,6 @@ from PySide2 import QtCore, QtWidgets, QtGui, QtTest from PySide2.QtWidgets import QAction, QWidget from PySide2.QtCore import Qt from PySide2.QtTest import QTest -import azlmbr.legacy.general as general import traceback import threading import types From 19316e422b7c42cce7b3ac6d52c5fbb9e9dc68a2 Mon Sep 17 00:00:00 2001 From: puvvadar Date: Tue, 18 May 2021 10:46:18 -0700 Subject: [PATCH 46/69] Check launch process exists before waiting for 15 seconds --- .../Code/Source/Editor/MultiplayerEditorSystemComponent.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp index 159f3b944b..a3d2551dd8 100644 --- a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp @@ -126,7 +126,6 @@ namespace Multiplayer { // If enabled but no process name is supplied, try this project's ServerLauncher serverProcess = AZ::Utils::GetProjectName() + ".ServerLauncher"; - serverPath = AZ::Utils::GetExecutableDirectory(); serverPath /= serverProcess + AZ_TRAIT_OS_EXECUTABLE_EXTENSION; } @@ -144,7 +143,10 @@ namespace Multiplayer // Launch the Server and give it a few seconds to boot up AzFramework::ProcessWatcher* outProcess = AzFramework::ProcessWatcher::LaunchProcess( processLaunchInfo, AzFramework::ProcessCommunicationType::COMMUNICATOR_TYPE_NONE); - AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(15000)); + if (outProcess) + { + AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(15000)); + } return outProcess; } From e03645f8161cfcb67400176e23cc3870c1aa872b Mon Sep 17 00:00:00 2001 From: puvvadar Date: Tue, 18 May 2021 10:48:39 -0700 Subject: [PATCH 47/69] Disable editorsv launch by default --- .../Code/Source/Editor/MultiplayerEditorSystemComponent.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp index a3d2551dd8..3c361dab2a 100644 --- a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp @@ -31,7 +31,7 @@ namespace Multiplayer { using namespace AzNetworking; - AZ_CVAR(bool, editorsv_enabled, true, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, + AZ_CVAR(bool, editorsv_enabled, false, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "Whether Editor launching a local server to connect to is supported"); AZ_CVAR(bool, editorsv_launch, true, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "Whether Editor should launch a server when the server address is localhost"); From 54dc47eb91e55e6c70da8b624017c86b338aa50b Mon Sep 17 00:00:00 2001 From: scottr Date: Tue, 18 May 2021 11:50:01 -0700 Subject: [PATCH 48/69] [cpack_installer] fixed typo in help string --- cmake/Packaging.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/Packaging.cmake b/cmake/Packaging.cmake index 8f4e1134b9..ba610b1883 100644 --- a/cmake/Packaging.cmake +++ b/cmake/Packaging.cmake @@ -14,7 +14,7 @@ if(NOT PAL_TRAIT_BUILD_CPACK_SUPPORTED) endif() # public facing options will be used for conversion into cpack specific ones below. -set(LY_INSTALLER_DOWNLOAD_URL "" CACHE STRING "URL embded into the installer to download additional artifacts") +set(LY_INSTALLER_DOWNLOAD_URL "" CACHE STRING "URL embedded into the installer to download additional artifacts") set(LY_INSTALLER_LICENSE_URL "" CACHE STRING "Optionally embed a link to the license instead of raw text") # set all common cpack variable overrides first so they can be accessible via configure_file From 3182dc37c3d708ac37bf02b0bcd871877312c27d Mon Sep 17 00:00:00 2001 From: Tommy Walton <82672795+amzn-tommy@users.noreply.github.com> Date: Tue, 18 May 2021 13:52:01 -0700 Subject: [PATCH 49/69] Fix for ATOM-15488 : Rendering out an animation with shadows crashes the editor (#794) Previously, the SkinnedMeshFeatureProcessor assumed there would only be one skinning pass. However, that's not always the case. When rendering with track view, the feature processor was getting a pass that only updated once every three frames, which could lead to a condition where a skinned mesh was released, but the pass never submitted and cleared the previously added dispatch items, and one or two frames later it would go to submit after the skinned mesh and all of its resources had already been released. -Modified the skinning and morph target compute passes to pull dispatch items from the feature processor instead of the feature processor pushing them to the passes. -If more than one skinning (or morph target) pass is active in the frame, whichever one is first will submit all the dispatch items, and clear the feature processor's dispatch items before the next one tries to submit anything -Moved the logic for caching shader options from the SkinnedMeshComputePass to the SkinnedMeshFeatureProcessor, since there may be more than one pass but only one feature processor per scene --- .../MorphTargets/MorphTargetComputePass.cpp | 30 ++-- .../MorphTargets/MorphTargetComputePass.h | 8 +- .../MorphTargets/MorphTargetDispatchItem.cpp | 6 +- .../MorphTargets/MorphTargetDispatchItem.h | 4 +- .../SkinnedMesh/SkinnedMeshComputePass.cpp | 37 ++--- .../SkinnedMesh/SkinnedMeshComputePass.h | 11 +- .../SkinnedMesh/SkinnedMeshDispatchItem.cpp | 8 +- .../SkinnedMesh/SkinnedMeshDispatchItem.h | 4 +- .../SkinnedMeshFeatureProcessor.cpp | 140 ++++++++++-------- .../SkinnedMesh/SkinnedMeshFeatureProcessor.h | 25 +++- .../SkinnedMesh/SkinnedMeshRenderProxy.cpp | 14 +- 11 files changed, 145 insertions(+), 142 deletions(-) diff --git a/Gems/Atom/Feature/Common/Code/Source/MorphTargets/MorphTargetComputePass.cpp b/Gems/Atom/Feature/Common/Code/Source/MorphTargets/MorphTargetComputePass.cpp index 35793b82a3..1bb537ecef 100644 --- a/Gems/Atom/Feature/Common/Code/Source/MorphTargets/MorphTargetComputePass.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/MorphTargets/MorphTargetComputePass.cpp @@ -12,6 +12,7 @@ #include +#include #include #include @@ -38,6 +39,11 @@ namespace AZ return m_shader; } + void MorphTargetComputePass::SetFeatureProcessor(SkinnedMeshFeatureProcessor* skinnedMeshFeatureProcessor) + { + m_skinnedMeshFeatureProcessor = skinnedMeshFeatureProcessor; + } + void MorphTargetComputePass::BuildAttachmentsInternal() { // The same buffer that skinning writes to is used to manage the computed vertex deltas that are passed from the @@ -45,30 +51,16 @@ namespace AZ AttachBufferToSlot(Name{ "MorphTargetDeltaOutput" }, SkinnedMeshOutputStreamManagerInterface::Get()->GetBuffer()); } - void MorphTargetComputePass::AddDispatchItem(const RHI::DispatchItem* dispatchItem) - { - AZ_Assert(dispatchItem != nullptr, "invalid dispatchItem"); - - AZStd::lock_guard lock(m_mutex); - //using an unordered_set here to prevent redundantly adding the same dispatchItem to the submission queue - //(i.e. if the same morph target exists in multiple views, it can call AddDispatchItem multiple times with the same item) - m_dispatches.insert(dispatchItem); - } - void MorphTargetComputePass::BuildCommandListInternal(const RHI::FrameGraphExecuteContext& context) { - RHI::CommandList* commandList = context.GetCommandList(); + if (m_skinnedMeshFeatureProcessor) + { + RHI::CommandList* commandList = context.GetCommandList(); - SetSrgsForDispatch(commandList); + SetSrgsForDispatch(commandList); - AZStd::lock_guard lock(m_mutex); - for (const RHI::DispatchItem* dispatchItem : m_dispatches) - { - commandList->Submit(*dispatchItem); + m_skinnedMeshFeatureProcessor->SubmitMorphTargetDispatchItems(commandList); } - - // Clear the dispatch items. They will need to be re-populated next frame - m_dispatches.clear(); } } // namespace Render } // namespace AZ diff --git a/Gems/Atom/Feature/Common/Code/Source/MorphTargets/MorphTargetComputePass.h b/Gems/Atom/Feature/Common/Code/Source/MorphTargets/MorphTargetComputePass.h index 3967d9190e..61fa485fbc 100644 --- a/Gems/Atom/Feature/Common/Code/Source/MorphTargets/MorphTargetComputePass.h +++ b/Gems/Atom/Feature/Common/Code/Source/MorphTargets/MorphTargetComputePass.h @@ -18,6 +18,8 @@ namespace AZ { namespace Render { + class SkinnedMeshFeatureProcessor; + //! The morph target compute pass submits dispatch items for morph targets. The dispatch items are cleared every frame, so it needs to be re-populated. class MorphTargetComputePass : public RPI::ComputePass @@ -31,16 +33,14 @@ namespace AZ static RPI::Ptr Create(const RPI::PassDescriptor& descriptor); - //! Thread-safe function for adding a dispatch item to the current frame. - void AddDispatchItem(const RHI::DispatchItem* dispatchItem); Data::Instance GetShader() const; + void SetFeatureProcessor(SkinnedMeshFeatureProcessor* m_skinnedMeshFeatureProcessor); private: void BuildAttachmentsInternal() override; void BuildCommandListInternal(const RHI::FrameGraphExecuteContext& context) override; - AZStd::mutex m_mutex; - AZStd::unordered_set m_dispatches; + SkinnedMeshFeatureProcessor* m_skinnedMeshFeatureProcessor = nullptr; }; } } diff --git a/Gems/Atom/Feature/Common/Code/Source/MorphTargets/MorphTargetDispatchItem.cpp b/Gems/Atom/Feature/Common/Code/Source/MorphTargets/MorphTargetDispatchItem.cpp index d7bbc315ed..7b3aedd64e 100644 --- a/Gems/Atom/Feature/Common/Code/Source/MorphTargets/MorphTargetDispatchItem.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/MorphTargets/MorphTargetDispatchItem.cpp @@ -11,7 +11,7 @@ */ #include -#include +#include #include #include @@ -30,7 +30,7 @@ namespace AZ MorphTargetDispatchItem::MorphTargetDispatchItem( const AZStd::intrusive_ptr inputBuffers, const MorphTargetMetaData& morphTargetMetaData, - RPI::Ptr morphTargetComputePass, + SkinnedMeshFeatureProcessor* skinnedMeshFeatureProcessor, MorphTargetInstanceMetaData morphInstanceMetaData, float morphDeltaIntegerEncoding) : m_inputBuffers(inputBuffers) @@ -38,7 +38,7 @@ namespace AZ , m_morphInstanceMetaData(morphInstanceMetaData) , m_accumulatedDeltaIntegerEncoding(morphDeltaIntegerEncoding) { - m_morphTargetShader = morphTargetComputePass->GetShader(); + m_morphTargetShader = skinnedMeshFeatureProcessor->GetMorphTargetShader(); RPI::ShaderReloadNotificationBus::Handler::BusConnect(m_morphTargetShader->GetAssetId()); } diff --git a/Gems/Atom/Feature/Common/Code/Source/MorphTargets/MorphTargetDispatchItem.h b/Gems/Atom/Feature/Common/Code/Source/MorphTargets/MorphTargetDispatchItem.h index 46680eb465..ad1fd969a5 100644 --- a/Gems/Atom/Feature/Common/Code/Source/MorphTargets/MorphTargetDispatchItem.h +++ b/Gems/Atom/Feature/Common/Code/Source/MorphTargets/MorphTargetDispatchItem.h @@ -37,7 +37,7 @@ namespace AZ namespace Render { - class MorphTargetComputePass; + class SkinnedMeshFeatureProcessor; //! Holds and manages an RHI DispatchItem for a specific morph target, and the resources that are needed to build and maintain it. class MorphTargetDispatchItem @@ -51,7 +51,7 @@ namespace AZ explicit MorphTargetDispatchItem( const AZStd::intrusive_ptr inputBuffers, const MorphTargetMetaData& morphTargetMetaData, - RPI::Ptr morphTargetComputePass, + SkinnedMeshFeatureProcessor* skinnedMeshFeatureProcessor, MorphTargetInstanceMetaData morphInstanceMetaData, float accumulatedDeltaRange ); diff --git a/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshComputePass.cpp b/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshComputePass.cpp index d7b2c605b4..a3feddb0b6 100644 --- a/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshComputePass.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshComputePass.cpp @@ -12,6 +12,7 @@ #include +#include #include #include @@ -22,11 +23,9 @@ namespace AZ { namespace Render { - SkinnedMeshComputePass::SkinnedMeshComputePass(const RPI::PassDescriptor& descriptor) : RPI::ComputePass(descriptor) { - m_cachedShaderOptions.SetShader(m_shader); } RPI::Ptr SkinnedMeshComputePass::Create(const RPI::PassDescriptor& descriptor) @@ -40,42 +39,30 @@ namespace AZ return m_shader; } - RPI::ShaderOptionGroup SkinnedMeshComputePass::CreateShaderOptionGroup(const SkinnedMeshShaderOptions shaderOptions, SkinnedMeshShaderOptionNotificationBus::Handler& shaderReinitializedHandler) + void SkinnedMeshComputePass::SetFeatureProcessor(SkinnedMeshFeatureProcessor* skinnedMeshFeatureProcessor) { - m_cachedShaderOptions.ConnectToShaderReinitializedEvent(shaderReinitializedHandler); - return m_cachedShaderOptions.CreateShaderOptionGroup(shaderOptions); - } - - void SkinnedMeshComputePass::AddDispatchItem(const RHI::DispatchItem* dispatchItem) - { - AZ_Assert(dispatchItem != nullptr, "invalid dispatchItem"); - - AZStd::lock_guard lock(m_mutex); - //using an unordered_set here to prevent redundantly adding the same dispatchItem to the submission queue - //(i.e. if the same skinnedMesh exists in multiple views, it can call AddDispatchItem multiple times with the same item) - m_dispatches.insert(dispatchItem); + m_skinnedMeshFeatureProcessor = skinnedMeshFeatureProcessor; } void SkinnedMeshComputePass::BuildCommandListInternal(const RHI::FrameGraphExecuteContext& context) { - RHI::CommandList* commandList = context.GetCommandList(); + if (m_skinnedMeshFeatureProcessor) + { + RHI::CommandList* commandList = context.GetCommandList(); - SetSrgsForDispatch(commandList); + SetSrgsForDispatch(commandList); - AZStd::lock_guard lock(m_mutex); - for (const RHI::DispatchItem* dispatchItem : m_dispatches) - { - commandList->Submit(*dispatchItem); + m_skinnedMeshFeatureProcessor->SubmitSkinningDispatchItems(commandList); } - - // Clear the dispatch items. They will need to be re-populated next frame - m_dispatches.clear(); } void SkinnedMeshComputePass::OnShaderReinitialized(const RPI::Shader& shader) { ComputePass::OnShaderReinitialized(shader); - m_cachedShaderOptions.SetShader(m_shader); + if (m_skinnedMeshFeatureProcessor) + { + m_skinnedMeshFeatureProcessor->OnSkinningShaderReinitialized(m_shader); + } } void SkinnedMeshComputePass::OnShaderVariantReinitialized(const RPI::Shader& shader, const RPI::ShaderVariantId&, RPI::ShaderVariantStableId) diff --git a/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshComputePass.h b/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshComputePass.h index d2e0fb77dc..5f7ff08e47 100644 --- a/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshComputePass.h +++ b/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshComputePass.h @@ -20,6 +20,8 @@ namespace AZ { namespace Render { + class SkinnedMeshFeatureProcessor; + //! The skinned mesh compute pass submits dispatch items for skinning. The dispatch items are cleared every frame, so it needs to be re-populated. class SkinnedMeshComputePass : public RPI::ComputePass @@ -33,10 +35,9 @@ namespace AZ static RPI::Ptr Create(const RPI::PassDescriptor& descriptor); - //! Thread-safe function for adding a dispatch item to the current frame. - void AddDispatchItem(const RHI::DispatchItem* dispatchItem); Data::Instance GetShader() const; - RPI::ShaderOptionGroup CreateShaderOptionGroup(const SkinnedMeshShaderOptions shaderOptions, SkinnedMeshShaderOptionNotificationBus::Handler& shaderReinitializedHandler); + + void SetFeatureProcessor(SkinnedMeshFeatureProcessor* m_skinnedMeshFeatureProcessor); private: void BuildCommandListInternal(const RHI::FrameGraphExecuteContext& context) override; @@ -45,9 +46,7 @@ namespace AZ void OnShaderReinitialized(const RPI::Shader& shader) override; void OnShaderVariantReinitialized(const RPI::Shader& shader, const RPI::ShaderVariantId& shaderVariantId, RPI::ShaderVariantStableId shaderVariantStableId) override; - AZStd::mutex m_mutex; - AZStd::unordered_set m_dispatches; - CachedSkinnedMeshShaderOptions m_cachedShaderOptions; + SkinnedMeshFeatureProcessor* m_skinnedMeshFeatureProcessor = nullptr; }; } } diff --git a/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshDispatchItem.cpp b/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshDispatchItem.cpp index c9054fc6c8..647a87a788 100644 --- a/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshDispatchItem.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshDispatchItem.cpp @@ -12,7 +12,7 @@ #include #include -#include +#include #include #include @@ -34,7 +34,7 @@ namespace AZ size_t lodIndex, Data::Instance boneTransforms, const SkinnedMeshShaderOptions& shaderOptions, - RPI::Ptr skinnedMeshComputePass, + SkinnedMeshFeatureProcessor* skinnedMeshFeatureProcessor, MorphTargetInstanceMetaData morphTargetInstanceMetaData, float morphTargetDeltaIntegerEncoding) : m_inputBuffers(inputBuffers) @@ -45,7 +45,7 @@ namespace AZ , m_morphTargetInstanceMetaData(morphTargetInstanceMetaData) , m_morphTargetDeltaIntegerEncoding(morphTargetDeltaIntegerEncoding) { - m_skinningShader = skinnedMeshComputePass->GetShader(); + m_skinningShader = skinnedMeshFeatureProcessor->GetSkinningShader(); // Shader options are generally set per-skinned mesh instance, but morph targets may only exist on some lods. Override the option for applying morph targets here if (m_morphTargetInstanceMetaData.m_accumulatedPositionDeltaOffsetInBytes != MorphTargetConstants::s_invalidDeltaOffset) @@ -58,7 +58,7 @@ namespace AZ } // CreateShaderOptionGroup will also connect to the SkinnedMeshShaderOptionNotificationBus - m_shaderOptionGroup = skinnedMeshComputePass->CreateShaderOptionGroup(m_shaderOptions, *this); + m_shaderOptionGroup = skinnedMeshFeatureProcessor->CreateSkinningShaderOptionGroup(m_shaderOptions, *this); } SkinnedMeshDispatchItem::~SkinnedMeshDispatchItem() diff --git a/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshDispatchItem.h b/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshDispatchItem.h index c80b002106..33bec89afd 100644 --- a/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshDispatchItem.h +++ b/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshDispatchItem.h @@ -38,7 +38,7 @@ namespace AZ namespace Render { - class SkinnedMeshComputePass; + class SkinnedMeshFeatureProcessor; //! Holds and manages an RHI DispatchItem for a specific skinned mesh, and the resources that are needed to build and maintain it. class SkinnedMeshDispatchItem @@ -55,7 +55,7 @@ namespace AZ size_t lodIndex, Data::Instance skinningMatrices, const SkinnedMeshShaderOptions& shaderOptions, - RPI::Ptr skinnedMeshComputePass, + SkinnedMeshFeatureProcessor* skinnedMeshFeatureProcessor, MorphTargetInstanceMetaData morphTargetInstanceMetaData, float morphTargetDeltaIntegerEncoding ); diff --git a/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshFeatureProcessor.cpp b/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshFeatureProcessor.cpp index 193a36e588..388f4112a0 100644 --- a/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshFeatureProcessor.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshFeatureProcessor.cpp @@ -24,8 +24,10 @@ #include #include #include +#include #include +#include #include #include @@ -84,11 +86,6 @@ namespace AZ AZ_PROFILE_FUNCTION(Debug::ProfileCategory::AzRender); AZ_ATOM_PROFILE_FUNCTION("SkinnedMesh", "SkinnedMeshFeatureProcessor: Render"); - if (!m_skinningPass) - { - return; - } - #if 0 //[GFX_TODO][ATOM-13564] Temporarily disable skinning culling until we figure out how to hook up visibility & lod selection with skinning: //Setup the culling workgroup (it will be re-used for each view) { @@ -132,7 +129,7 @@ namespace AZ //Dispatch the workgroup to each view for (const RPI::ViewPtr& viewPtr : packet.m_views) { - Job *processWorkgroupJob = AZ::CreateJobFunction( + Job* processWorkgroupJob = AZ::CreateJobFunction( [this, cullingSystem, viewPtr](AZ::Job& thisJob) { AZ_PROFILE_SCOPE_DYNAMIC(Debug::ProfileCategory::AzRender, "skinningMeshFP processWorkgroupJob - View: %s", viewPtr->GetName().GetCStr()); @@ -167,7 +164,16 @@ namespace AZ float maxScreenPercentage(lod.m_range.m_max); if (approxScreenPercentage >= minScreenPercentage && approxScreenPercentage <= maxScreenPercentage) { - m_skinningPass->AddDispatchItem(&renderProxy->m_dispatchItemsByLod[lodIndex]->GetRHIDispatchItem()); + AZStd::lock_guard lock(m_dispatchItemMutex); + m_skinningDispatches.insert(&renderProxy->m_dispatchItemsByLod[lodIndex]->GetRHIDispatchItem()); + for (size_t morphTargetIndex = 0; morphTargetIndex < renderProxy->m_morphTargetDispatchItemsByLod[lodIndex].size(); morphTargetIndex++) + { + const MorphTargetDispatchItem* dispatchItem = renderProxy->m_morphTargetDispatchItemsByLod[lodIndex][morphTargetIndex].get(); + if (dispatchItem && dispatchItem->GetWeight() > AZ::Constants::FloatEpsilon) + { + m_morphTargetDispatches.insert(&dispatchItem->GetRHIDispatchItem()); + } + } } } } @@ -232,13 +238,14 @@ namespace AZ //Note that this supports overlapping lod ranges (to support cross-fading lods, for example) if (approxScreenPercentage >= lod.m_screenCoverageMin && approxScreenPercentage <= lod.m_screenCoverageMax) { - m_skinningPass->AddDispatchItem(&renderProxy.m_dispatchItemsByLod[lodIndex]->GetRHIDispatchItem()); + AZStd::lock_guard lock(m_dispatchItemMutex); + m_skinningDispatches.insert(&renderProxy.m_dispatchItemsByLod[lodIndex]->GetRHIDispatchItem()); for (size_t morphTargetIndex = 0; morphTargetIndex < renderProxy.m_morphTargetDispatchItemsByLod[lodIndex].size(); morphTargetIndex++) { const MorphTargetDispatchItem* dispatchItem = renderProxy.m_morphTargetDispatchItemsByLod[lodIndex][morphTargetIndex].get(); if (dispatchItem && dispatchItem->GetWeight() > AZ::Constants::FloatEpsilon) { - m_morphTargetPass->AddDispatchItem(&dispatchItem->GetRHIDispatchItem()); + m_morphTargetDispatches.insert(&dispatchItem->GetRHIDispatchItem()); } } } @@ -248,19 +255,14 @@ namespace AZ #endif } - void SkinnedMeshFeatureProcessor::OnRenderPipelineAdded([[maybe_unused]] RPI::RenderPipelinePtr pipeline) + void SkinnedMeshFeatureProcessor::OnRenderPipelineAdded(RPI::RenderPipelinePtr pipeline) { - InitSkinningAndMorphPass(); + InitSkinningAndMorphPass(pipeline->GetRootPass()); } - void SkinnedMeshFeatureProcessor::OnRenderPipelineRemoved([[maybe_unused]] RPI::RenderPipeline* pipeline) + void SkinnedMeshFeatureProcessor::OnRenderPipelinePassesChanged(RPI::RenderPipeline* renderPipeline) { - InitSkinningAndMorphPass(); - } - - void SkinnedMeshFeatureProcessor::OnRenderPipelinePassesChanged([[maybe_unused]] RPI::RenderPipeline* renderPipeline) - { - InitSkinningAndMorphPass(); + InitSkinningAndMorphPass(renderPipeline->GetRootPass()); } void SkinnedMeshFeatureProcessor::OnBeginPrepareRender() @@ -268,9 +270,15 @@ namespace AZ m_renderProxiesChecker.soft_lock(); } - void SkinnedMeshFeatureProcessor::OnEndPrepareRender() + void SkinnedMeshFeatureProcessor::OnRenderEnd() { m_renderProxiesChecker.soft_unlock(); + + // Clear any dispatch items that were added but never submitted + // in case there were no passes that submitted this frame + // because they execute at a lower frequency + m_skinningDispatches.clear(); + m_morphTargetDispatches.clear(); } SkinnedMeshRenderProxyHandle SkinnedMeshFeatureProcessor::AcquireRenderProxy(const SkinnedMeshRenderProxyDesc& desc) @@ -295,61 +303,73 @@ namespace AZ return false; } - void SkinnedMeshFeatureProcessor::InitSkinningAndMorphPass() + void SkinnedMeshFeatureProcessor::InitSkinningAndMorphPass(const RPI::Ptr pipelineRootPass) { - m_skinningPass = nullptr; //reset it to null, just in case it fails to load the assets properly - m_morphTargetPass = nullptr; - - RPI::PassSystemInterface* passSystem = RPI::PassSystemInterface::Get(); - if (passSystem->HasPassesForTemplateName(AZ::Name{ "SkinningPassTemplate" })) + RPI::Ptr skinningPass = pipelineRootPass->FindPassByNameRecursive(AZ::Name{ "SkinningPass" }); + if (skinningPass) { - auto& skinningPasses = passSystem->GetPassesForTemplateName(AZ::Name{ "SkinningPassTemplate" }); + SkinnedMeshComputePass* skinnedMeshComputePass = azdynamic_cast(skinningPass.get()); + skinnedMeshComputePass->SetFeatureProcessor(this); - // For now, assume one skinning pass - if (!skinningPasses.empty() && skinningPasses[0]) - { - m_skinningPass = static_cast(skinningPasses[0]); - const Data::Instance shader = m_skinningPass->GetShader(); + // There may be multiple skinning passes in the scene due to multiple pipelines, but there is only one skinning shader + m_skinningShader = skinnedMeshComputePass->GetShader(); - if (!shader) - { - AZ_Error(s_featureProcessorName, false, "Failed to get skinning pass shader. It may need to finish processing."); - } + if (!m_skinningShader) + { + AZ_Error(s_featureProcessorName, false, "Failed to get skinning pass shader. It may need to finish processing."); } else { - AZ_Error(s_featureProcessorName, false, "\"SkinningPassTemplate\" does not have any valid passes. Check your game project's .pass assets."); + m_cachedSkinningShaderOptions.SetShader(m_skinningShader); } } - else - { - AZ_Error(s_featureProcessorName, false, "Failed to find passes for \"SkinningPassTemplate\". Check your game project's .pass assets."); - } - if (passSystem->HasPassesForTemplateName(AZ::Name{ "MorphTargetPassTemplate" })) + RPI::Ptr morphTargetPass = pipelineRootPass->FindPassByNameRecursive(AZ::Name{ "MorphTargetPass" }); + if (morphTargetPass) { - auto& morphTargetPasses = passSystem->GetPassesForTemplateName(AZ::Name{ "MorphTargetPassTemplate" }); + MorphTargetComputePass* morphTargetComputePass = azdynamic_cast(morphTargetPass.get()); + morphTargetComputePass->SetFeatureProcessor(this); - // For now, assume one skinning pass - if (!morphTargetPasses.empty() && morphTargetPasses[0]) - { - m_morphTargetPass = static_cast(morphTargetPasses[0]); - const Data::Instance shader = m_morphTargetPass->GetShader(); + // There may be multiple morph target passes in the scene due to multiple pipelines, but there is only one morph target shader + m_morphTargetShader = morphTargetComputePass->GetShader(); - if (!shader) - { - AZ_Error(s_featureProcessorName, false, "Failed to get morph target pass shader. It may need to finish processing."); - } - } - else + if (!m_morphTargetShader) { - AZ_Error(s_featureProcessorName, false, "\"MorphTargetPassTemplate\" does not have any valid passes. Check your game project's .pass assets."); + AZ_Error(s_featureProcessorName, false, "Failed to get morph target pass shader. It may need to finish processing."); } } - else + } + + RPI::ShaderOptionGroup SkinnedMeshFeatureProcessor::CreateSkinningShaderOptionGroup(const SkinnedMeshShaderOptions shaderOptions, SkinnedMeshShaderOptionNotificationBus::Handler& shaderReinitializedHandler) + { + m_cachedSkinningShaderOptions.ConnectToShaderReinitializedEvent(shaderReinitializedHandler); + return m_cachedSkinningShaderOptions.CreateShaderOptionGroup(shaderOptions); + } + + void SkinnedMeshFeatureProcessor::OnSkinningShaderReinitialized(const Data::Instance skinningShader) + { + m_skinningShader = skinningShader; + m_cachedSkinningShaderOptions.SetShader(m_skinningShader); + } + + void SkinnedMeshFeatureProcessor::SubmitSkinningDispatchItems(RHI::CommandList* commandList) + { + AZStd::lock_guard lock(m_dispatchItemMutex); + for (const RHI::DispatchItem* dispatchItem : m_skinningDispatches) + { + commandList->Submit(*dispatchItem); + } + m_skinningDispatches.clear(); + } + + void SkinnedMeshFeatureProcessor::SubmitMorphTargetDispatchItems(RHI::CommandList* commandList) + { + AZStd::lock_guard lock(m_dispatchItemMutex); + for (const RHI::DispatchItem* dispatchItem : m_morphTargetDispatches) { - AZ_Error(s_featureProcessorName, false, "Failed to find passes for \"MorphTargetPassTemplate\". Check your game project's .pass assets."); + commandList->Submit(*dispatchItem); } + m_morphTargetDispatches.clear(); } SkinnedMeshRenderProxyInterfaceHandle SkinnedMeshFeatureProcessor::AcquireRenderProxyInterface(const SkinnedMeshRenderProxyDesc& desc) @@ -363,14 +383,14 @@ namespace AZ return ReleaseRenderProxy(handle); } - RPI::Ptr SkinnedMeshFeatureProcessor::GetSkinningPass() const + Data::Instance SkinnedMeshFeatureProcessor::GetSkinningShader() const { - return m_skinningPass; + return m_skinningShader; } - RPI::Ptr SkinnedMeshFeatureProcessor::GetMorphTargetPass() const + Data::Instance SkinnedMeshFeatureProcessor::GetMorphTargetShader() const { - return m_morphTargetPass; + return m_morphTargetShader; } } // namespace Render } // namespace AZ diff --git a/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshFeatureProcessor.h b/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshFeatureProcessor.h index 86dedfa161..bb3dd242a1 100644 --- a/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshFeatureProcessor.h +++ b/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshFeatureProcessor.h @@ -51,35 +51,46 @@ namespace AZ void Deactivate() override; void Simulate(const FeatureProcessor::SimulatePacket& packet) override; void Render(const FeatureProcessor::RenderPacket& packet) override; + void OnRenderEnd() override; // RPI::SceneNotificationBus overrides ... void OnRenderPipelineAdded(RPI::RenderPipelinePtr pipeline) override; - void OnRenderPipelineRemoved(RPI::RenderPipeline* pipeline) override; void OnRenderPipelinePassesChanged(RPI::RenderPipeline* renderPipeline) override; void OnBeginPrepareRender() override; - void OnEndPrepareRender() override; SkinnedMeshRenderProxyHandle AcquireRenderProxy(const SkinnedMeshRenderProxyDesc& desc); bool ReleaseRenderProxy(SkinnedMeshRenderProxyHandle& handle); - RPI::Ptr GetSkinningPass() const; - RPI::Ptr GetMorphTargetPass() const; + Data::Instance GetSkinningShader() const; + RPI::ShaderOptionGroup CreateSkinningShaderOptionGroup(const SkinnedMeshShaderOptions shaderOptions, SkinnedMeshShaderOptionNotificationBus::Handler& shaderReinitializedHandler); + void OnSkinningShaderReinitialized(const Data::Instance skinningShader); + void SubmitSkinningDispatchItems(RHI::CommandList* commandList); + + Data::Instance GetMorphTargetShader() const; + void SubmitMorphTargetDispatchItems(RHI::CommandList* commandList); private: AZ_DISABLE_COPY_MOVE(SkinnedMeshFeatureProcessor); - void InitSkinningAndMorphPass(); + void InitSkinningAndMorphPass(const RPI::Ptr pipelineRootPass); SkinnedMeshRenderProxyInterfaceHandle AcquireRenderProxyInterface(const SkinnedMeshRenderProxyDesc& desc) override; bool ReleaseRenderProxyInterface(SkinnedMeshRenderProxyInterfaceHandle& handle) override; static const char* s_featureProcessorName; - RPI::Ptr m_skinningPass; - RPI::Ptr m_morphTargetPass; + + Data::Instance m_skinningShader; + CachedSkinnedMeshShaderOptions m_cachedSkinningShaderOptions; + + Data::Instance m_morphTargetShader; + AZStd::concurrency_checker m_renderProxiesChecker; StableDynamicArray m_renderProxies; AZStd::unique_ptr m_statsCollector; MeshFeatureProcessor* m_meshFeatureProcessor = nullptr; + AZStd::unordered_set m_skinningDispatches; + AZStd::unordered_set m_morphTargetDispatches; + AZStd::mutex m_dispatchItemMutex; }; } // namespace Render diff --git a/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshRenderProxy.cpp b/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshRenderProxy.cpp index 90c54dfc10..090b720406 100644 --- a/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshRenderProxy.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshRenderProxy.cpp @@ -60,13 +60,7 @@ namespace AZ bool SkinnedMeshRenderProxy::BuildDispatchItem([[maybe_unused]] const RPI::Scene& scene, size_t modelLodIndex, [[maybe_unused]] const SkinnedMeshShaderOptions& shaderOptions) { - if (!m_featureProcessor->GetSkinningPass()) - { - AZ_Error("Skinned Mesh Feature Processor", false, "Failed to get Skinning Pass. Make sure the project has a skinning pass."); - return false; - } - - Data::Instance skinningShader = m_featureProcessor->GetSkinningPass()->GetShader(); + Data::Instance skinningShader = m_featureProcessor->GetSkinningShader(); if (!skinningShader) { AZ_Error("Skinned Mesh Feature Processor", false, "Failed to get skinning shader from skinning pass"); @@ -89,7 +83,7 @@ namespace AZ m_instance->m_outputStreamOffsetsInBytes[modelLodIndex], modelLodIndex, m_boneTransforms, m_shaderOptions, - m_featureProcessor->GetSkinningPass(), + m_featureProcessor, m_instance->m_morphTargetInstanceMetaData[modelLodIndex], morphDeltaIntegerEncoding }); @@ -100,7 +94,7 @@ namespace AZ } // Get the data needed to create a morph target dispatch item - Data::Instance morphTargetShader = m_featureProcessor->GetMorphTargetPass()->GetShader(); + Data::Instance morphTargetShader = m_featureProcessor->GetMorphTargetShader(); const AZStd::vector>& morphTargetInputBuffersVector = m_inputBuffers->GetMorphTargetInputBuffers(modelLodIndex); AZ_Assert(morphTargetMetaDatas.size() == morphTargetInputBuffersVector.size(), "Skinned Mesh Feature Processor - Mismatch in morph target metadata count and morph target input buffer count"); @@ -118,7 +112,7 @@ namespace AZ aznew MorphTargetDispatchItem{ morphTargetInputBuffersVector[morphTargetIndex], morphTargetMetaDatas[morphTargetIndex], - m_featureProcessor->GetMorphTargetPass(), + m_featureProcessor, m_instance->m_morphTargetInstanceMetaData[modelLodIndex], morphDeltaIntegerEncoding }); From e2ade654fb30d12bdb1564f04f8700e3652532d8 Mon Sep 17 00:00:00 2001 From: puvvadar Date: Tue, 18 May 2021 13:52:28 -0700 Subject: [PATCH 50/69] Address misc feedback --- .../Components/LocalPredictionPlayerInputComponent.cpp | 4 ++-- .../Code/Source/Editor/MultiplayerEditorConnection.cpp | 6 ++---- .../Code/Source/Editor/MultiplayerEditorSystemComponent.cpp | 4 +--- Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h | 2 +- 4 files changed, 6 insertions(+), 10 deletions(-) diff --git a/Gems/Multiplayer/Code/Source/Components/LocalPredictionPlayerInputComponent.cpp b/Gems/Multiplayer/Code/Source/Components/LocalPredictionPlayerInputComponent.cpp index 10a0d17e73..612601883c 100644 --- a/Gems/Multiplayer/Code/Source/Components/LocalPredictionPlayerInputComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Components/LocalPredictionPlayerInputComponent.cpp @@ -23,7 +23,7 @@ namespace Multiplayer { AZ_CVAR(AZ::TimeMs, cl_InputRateMs, AZ::TimeMs{ 33 }, nullptr, AZ::ConsoleFunctorFlags::Null, "Rate at which to sample and process client inputs"); AZ_CVAR(AZ::TimeMs, cl_MaxRewindHistoryMs, AZ::TimeMs{ 2000 }, nullptr, AZ::ConsoleFunctorFlags::Null, "Maximum number of milliseconds to keep for server correction rewind and replay"); -#ifndef _RELEASE +#ifndef AZ_RELEASE_BUILD AZ_CVAR(float, cl_DebugHackTimeMultiplier, 1.0f, nullptr, AZ::ConsoleFunctorFlags::Null, "Scalar value used to simulate clock hacking cheats for validating bank time system and anticheat"); #endif @@ -477,7 +477,7 @@ namespace Multiplayer const double inputRate = static_cast(static_cast(cl_InputRateMs)) / 1000.0; const double maxRewindHistory = static_cast(static_cast(cl_MaxRewindHistoryMs)) / 1000.0; -#ifndef _RELEASE +#ifndef AZ_RELEASE_BUILD m_moveAccumulator += deltaTime * cl_DebugHackTimeMultiplier; #else m_moveAccumulator += deltaTime; diff --git a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.cpp b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.cpp index f88a314ea5..f684e1f12f 100644 --- a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.cpp +++ b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.cpp @@ -71,11 +71,9 @@ namespace Multiplayer while (m_byteStream.GetCurPos() < m_byteStream.GetLength()) { AZ::Data::AssetId assetId; - AZ::Data::AssetLoadBehavior assetLoadBehavior; uint32_t hintSize; AZStd::string assetHint; m_byteStream.Read(sizeof(AZ::Data::AssetId), reinterpret_cast(&assetId)); - m_byteStream.Read(sizeof(AZ::Data::AssetLoadBehavior), reinterpret_cast(&assetLoadBehavior)); m_byteStream.Read(sizeof(uint32_t), reinterpret_cast(&hintSize)); assetHint.resize(hintSize); m_byteStream.Read(hintSize, assetHint.data()); @@ -83,7 +81,7 @@ namespace Multiplayer size_t assetSize = m_byteStream.GetCurPos(); AZ::Data::AssetData* assetDatum = AZ::Utils::LoadObjectFromStream(m_byteStream, nullptr); assetSize = m_byteStream.GetCurPos() - assetSize; - AZ::Data::Asset asset = AZ::Data::Asset(assetId, assetDatum, assetLoadBehavior); + AZ::Data::Asset asset = AZ::Data::Asset(assetId, assetDatum, AZ::Data::AssetLoadBehavior::NoLoad); asset.SetHint(assetHint); AZ::Data::AssetInfo assetInfo; @@ -104,7 +102,7 @@ namespace Multiplayer m_byteStream.Truncate(); // Load the level via the root spawnable that was registered - AZ::CVarFixedString loadLevelString = "LoadLevel Root.spawnable"; + const AZ::CVarFixedString loadLevelString = "LoadLevel Root.spawnable"; AZ::Interface::Get()->PerformCommand(loadLevelString.c_str()); // Setup the normal multiplayer connection diff --git a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp index 3c361dab2a..0a95f96ff7 100644 --- a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp @@ -169,15 +169,13 @@ namespace Multiplayer AZ::IO::ByteContainerStream byteStream(&buffer); // Serialize Asset information and AssetData into a potentially large buffer - for (auto asset : assetData) + for (auto& asset : assetData) { AZ::Data::AssetId assetId = asset.GetId(); - AZ::Data::AssetLoadBehavior assetLoadBehavior = asset.GetAutoLoadBehavior(); AZStd::string assetHint = asset.GetHint(); uint32_t hintSize = aznumeric_cast(assetHint.size()); byteStream.Write(sizeof(AZ::Data::AssetId), reinterpret_cast(&assetId)); - byteStream.Write(sizeof(AZ::Data::AssetLoadBehavior), reinterpret_cast(&assetLoadBehavior)); byteStream.Write(sizeof(uint32_t), reinterpret_cast(&hintSize)); byteStream.Write(assetHint.size(), assetHint.data()); AZ::Utils::SaveObjectToStream(byteStream, AZ::DataStream::ST_BINARY, asset.GetData(), asset.GetData()->GetType()); diff --git a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h index cac64db89b..1410a82a3c 100644 --- a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h +++ b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h @@ -129,7 +129,7 @@ namespace Multiplayer AZ::TimeMs m_lastReplicatedHostTimeMs = AZ::TimeMs{ 0 }; HostFrameId m_lastReplicatedHostFrameId = InvalidHostFrameId; -#if !defined(_RELEASE) +#if !defined(AZ_RELEASE_BUILD) MultiplayerEditorConnection m_editorConnectionListener; #endif }; From 519525b28f006e1d66b6cd6046f93a70e799c9fa Mon Sep 17 00:00:00 2001 From: antonmic Date: Tue, 18 May 2021 13:54:07 -0700 Subject: [PATCH 51/69] fixing feature common asset cmake --- .../Common/Assets/atom_feature_common_asset_files.cmake | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Gems/Atom/Feature/Common/Assets/atom_feature_common_asset_files.cmake b/Gems/Atom/Feature/Common/Assets/atom_feature_common_asset_files.cmake index c922eb428f..f1d8fa81be 100644 --- a/Gems/Atom/Feature/Common/Assets/atom_feature_common_asset_files.cmake +++ b/Gems/Atom/Feature/Common/Assets/atom_feature_common_asset_files.cmake @@ -38,6 +38,7 @@ set(FILES Materials/Types/StandardMultilayerPBR_ForwardPass_EDS.shader Materials/Types/StandardMultilayerPBR_Parallax.lua Materials/Types/StandardMultilayerPBR_ParallaxPerLayer.lua + Materials/Types/StandardMultilayerPBR_ShaderEnable.lua Materials/Types/StandardMultilayerPBR_Shadowmap_WithPS.azsl Materials/Types/StandardMultilayerPBR_Shadowmap_WithPS.shader Materials/Types/StandardPBR.materialtype @@ -245,7 +246,6 @@ set(FILES ShaderLib/Atom/Features/PBR/Hammersley.azsli ShaderLib/Atom/Features/PBR/LightingOptions.azsli ShaderLib/Atom/Features/PBR/LightingUtils.azsli - ShaderLib/Atom/Features/PBR/LowEndForwardPassOutput.azsli ShaderLib/Atom/Features/PBR/TransparentPassSrg.azsli ShaderLib/Atom/Features/PBR/Lighting/DualSpecularLighting.azsli ShaderLib/Atom/Features/PBR/Lighting/EnhancedLighting.azsli @@ -284,6 +284,7 @@ set(FILES ShaderLib/Atom/Features/PostProcessing/GlyphData.azsli ShaderLib/Atom/Features/PostProcessing/GlyphRender.azsli ShaderLib/Atom/Features/PostProcessing/PostProcessUtil.azsli + ShaderLib/Atom/Features/RayTracing/RayTracingSceneSrg.azsli ShaderLib/Atom/Features/ScreenSpace/ScreenSpaceUtil.azsli ShaderLib/Atom/Features/Shadow/BicubicPcfFilters.azsli ShaderLib/Atom/Features/Shadow/DirectionalLightShadow.azsli From 55ecd8517d3b8769199b36edf000f69a356cf4fd Mon Sep 17 00:00:00 2001 From: puvvadar Date: Tue, 18 May 2021 14:57:27 -0700 Subject: [PATCH 52/69] Add assert to new Asset constructor to declare intent and safeguard ID overwrite --- Code/Framework/AzCore/AzCore/Asset/AssetCommon.h | 1 + 1 file changed, 1 insertion(+) diff --git a/Code/Framework/AzCore/AzCore/Asset/AssetCommon.h b/Code/Framework/AzCore/AzCore/Asset/AssetCommon.h index 3666eae2d4..11f4531124 100644 --- a/Code/Framework/AzCore/AzCore/Asset/AssetCommon.h +++ b/Code/Framework/AzCore/AzCore/Asset/AssetCommon.h @@ -796,6 +796,7 @@ namespace AZ , m_assetType(azrtti_typeid()) , m_loadBehavior(loadBehavior) { + AZ_Assert(!assetData->m_assetId.IsValid(), "Asset data already has an ID set."); assetData->m_assetId = id; SetData(assetData); } From 350e5a0cd24c634bc7cd78036300937569c56333 Mon Sep 17 00:00:00 2001 From: puvvadar Date: Tue, 18 May 2021 15:22:22 -0700 Subject: [PATCH 53/69] Update to const auto& --- .../Code/Source/Editor/MultiplayerEditorSystemComponent.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp index 0a95f96ff7..829fe7e495 100644 --- a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp @@ -169,7 +169,7 @@ namespace Multiplayer AZ::IO::ByteContainerStream byteStream(&buffer); // Serialize Asset information and AssetData into a potentially large buffer - for (auto& asset : assetData) + for (const auto& asset : assetData) { AZ::Data::AssetId assetId = asset.GetId(); AZStd::string assetHint = asset.GetHint(); From b006eb57fe939fcf9277291a3094c607d0cdca22 Mon Sep 17 00:00:00 2001 From: gallowj Date: Mon, 3 May 2021 19:21:17 -0500 Subject: [PATCH 54/69] Updating a bunch of materials in TestData to correct changes to referenced texture paths --- .../SkinTestCases/001_lucy_regression_test.material | 4 ++-- .../SkinTestCases/002_wrinkle_regression_test.material | 2 +- .../101_DetailMaps_LucyBaseNoDetailMaps.material | 8 ++++---- .../StandardPbrTestCases/102_DetailMaps_All.material | 8 ++++---- .../105_DetailMaps_BlendMaskUsingDetailUVs.material | 8 ++++---- 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/Gems/Atom/TestData/TestData/Materials/SkinTestCases/001_lucy_regression_test.material b/Gems/Atom/TestData/TestData/Materials/SkinTestCases/001_lucy_regression_test.material index f43f6d0808..c359fea3b5 100644 --- a/Gems/Atom/TestData/TestData/Materials/SkinTestCases/001_lucy_regression_test.material +++ b/Gems/Atom/TestData/TestData/Materials/SkinTestCases/001_lucy_regression_test.material @@ -11,7 +11,7 @@ 0.29372090101242068, 1.0 ], - "textureMap": "Objects/Lucy/Lucy_brass_baseColor.tif", + "textureMap": "Objects/Lucy/Lucy_bronze_BaseColor.png", "useTexture": false }, "detailLayerGroup": { @@ -30,7 +30,7 @@ }, "normal": { "flipY": true, - "textureMap": "Objects/Lucy/Lucy_normal.tif" + "textureMap": "Objects/Lucy/Lucy_normal.png" }, "subsurfaceScattering": { "enableSubsurfaceScattering": true, diff --git a/Gems/Atom/TestData/TestData/Materials/SkinTestCases/002_wrinkle_regression_test.material b/Gems/Atom/TestData/TestData/Materials/SkinTestCases/002_wrinkle_regression_test.material index c611b992b6..ce42f32b67 100644 --- a/Gems/Atom/TestData/TestData/Materials/SkinTestCases/002_wrinkle_regression_test.material +++ b/Gems/Atom/TestData/TestData/Materials/SkinTestCases/002_wrinkle_regression_test.material @@ -29,7 +29,7 @@ }, "normal": { "flipY": true, - "textureMap": "Objects/Lucy/Lucy_normal.tif" + "textureMap": "Objects/Lucy/Lucy_normal.png" }, "subsurfaceScattering": { "enableSubsurfaceScattering": true, diff --git a/Gems/Atom/TestData/TestData/Materials/StandardPbrTestCases/101_DetailMaps_LucyBaseNoDetailMaps.material b/Gems/Atom/TestData/TestData/Materials/StandardPbrTestCases/101_DetailMaps_LucyBaseNoDetailMaps.material index 2c711a3bf3..7b1f0ba6a9 100644 --- a/Gems/Atom/TestData/TestData/Materials/StandardPbrTestCases/101_DetailMaps_LucyBaseNoDetailMaps.material +++ b/Gems/Atom/TestData/TestData/Materials/StandardPbrTestCases/101_DetailMaps_LucyBaseNoDetailMaps.material @@ -5,20 +5,20 @@ "propertyLayoutVersion": 3, "properties": { "baseColor": { - "textureMap": "Objects/Lucy/Lucy_brass_baseColor.tif", + "textureMap": "Objects/Lucy/Lucy_bronze_BaseColor.png", "textureMapUv": "Unwrapped" }, "metallic": { - "textureMap": "Objects/Lucy/Lucy_brass_metalness.tif", + "textureMap": "Objects/Lucy/Lucy_bronze_metallic.png", "textureMapUv": "Unwrapped" }, "normal": { "flipY": true, - "textureMap": "Objects/Lucy/Lucy_normal.tif", + "textureMap": "Objects/Lucy/Lucy_normal.png", "textureMapUv": "Unwrapped" }, "roughness": { - "textureMap": "Objects/Lucy/Lucy_brass_roughness.tif", + "textureMap": "Objects/Lucy/Lucy_bronze_roughness.png", "textureMapUv": "Unwrapped" } } diff --git a/Gems/Atom/TestData/TestData/Materials/StandardPbrTestCases/102_DetailMaps_All.material b/Gems/Atom/TestData/TestData/Materials/StandardPbrTestCases/102_DetailMaps_All.material index 7a94386a18..55a01866b5 100644 --- a/Gems/Atom/TestData/TestData/Materials/StandardPbrTestCases/102_DetailMaps_All.material +++ b/Gems/Atom/TestData/TestData/Materials/StandardPbrTestCases/102_DetailMaps_All.material @@ -5,7 +5,7 @@ "propertyLayoutVersion": 3, "properties": { "baseColor": { - "textureMap": "Objects/Lucy/Lucy_brass_baseColor.tif", + "textureMap": "Objects/Lucy/Lucy_bronze_baseColor.png", "textureMapUv": "Unwrapped" }, "detailLayerGroup": { @@ -22,16 +22,16 @@ "scale": 10.0 }, "metallic": { - "textureMap": "Objects/Lucy/Lucy_brass_metalness.tif", + "textureMap": "Objects/Lucy/Lucy_bronze_metallic.png", "textureMapUv": "Unwrapped" }, "normal": { "flipY": true, - "textureMap": "Objects/Lucy/Lucy_normal.tif", + "textureMap": "Objects/Lucy/Lucy_normal.png", "textureMapUv": "Unwrapped" }, "roughness": { - "textureMap": "Objects/Lucy/Lucy_brass_roughness.tif", + "textureMap": "Objects/Lucy/Lucy_bronze_roughness.png", "textureMapUv": "Unwrapped" } } diff --git a/Gems/Atom/TestData/TestData/Materials/StandardPbrTestCases/105_DetailMaps_BlendMaskUsingDetailUVs.material b/Gems/Atom/TestData/TestData/Materials/StandardPbrTestCases/105_DetailMaps_BlendMaskUsingDetailUVs.material index ddeace43da..6193cf4eed 100644 --- a/Gems/Atom/TestData/TestData/Materials/StandardPbrTestCases/105_DetailMaps_BlendMaskUsingDetailUVs.material +++ b/Gems/Atom/TestData/TestData/Materials/StandardPbrTestCases/105_DetailMaps_BlendMaskUsingDetailUVs.material @@ -5,7 +5,7 @@ "propertyLayoutVersion": 3, "properties": { "baseColor": { - "textureMap": "Objects/Lucy/Lucy_brass_baseColor.tif", + "textureMap": "Objects/Lucy/Lucy_bronze_baseColor.png", "textureMapUv": "Unwrapped" }, "detailLayerGroup": { @@ -21,16 +21,16 @@ "scale": 10.0 }, "metallic": { - "textureMap": "Objects/Lucy/Lucy_brass_metalness.tif", + "textureMap": "Objects/Lucy/Lucy_bronze_metallic.png", "textureMapUv": "Unwrapped" }, "normal": { "flipY": true, - "textureMap": "Objects/Lucy/Lucy_normal.tif", + "textureMap": "Objects/Lucy/Lucy_normal.png", "textureMapUv": "Unwrapped" }, "roughness": { - "textureMap": "Objects/Lucy/Lucy_brass_roughness.tif", + "textureMap": "Objects/Lucy/Lucy_bronze_roughness.png", "textureMapUv": "Unwrapped" } } From a2608e187b40424995ca69b9568a809d899a5b09 Mon Sep 17 00:00:00 2001 From: bosnichd Date: Tue, 18 May 2021 17:37:02 -0600 Subject: [PATCH 55/69] Resurrect error.log and error.dmp file output when the engine crashes (LYN-3873) (#811) Resurrect error.log and error.dmp file output when the engine crashes (LYN-3873). This was recently removed as part of 96b85e6813920554f7dfdc7752c1dd4452919b92 --- Code/CryEngine/CrySystem/DebugCallStack.cpp | 900 ++++++++++++++++++ Code/CryEngine/CrySystem/DebugCallStack.h | 95 ++ Code/CryEngine/CrySystem/DllMain.cpp | 11 + Code/CryEngine/CrySystem/IDebugCallStack.cpp | 275 ++++++ Code/CryEngine/CrySystem/IDebugCallStack.h | 90 ++ Code/CryEngine/CrySystem/System.h | 1 + Code/CryEngine/CrySystem/SystemInit.cpp | 19 + Code/CryEngine/CrySystem/SystemWin32.cpp | 5 + .../CrySystem/WindowsErrorReporting.cpp | 137 +++ .../CryEngine/CrySystem/crysystem_files.cmake | 5 + 10 files changed, 1538 insertions(+) create mode 100644 Code/CryEngine/CrySystem/DebugCallStack.cpp create mode 100644 Code/CryEngine/CrySystem/DebugCallStack.h create mode 100644 Code/CryEngine/CrySystem/IDebugCallStack.cpp create mode 100644 Code/CryEngine/CrySystem/IDebugCallStack.h create mode 100644 Code/CryEngine/CrySystem/WindowsErrorReporting.cpp diff --git a/Code/CryEngine/CrySystem/DebugCallStack.cpp b/Code/CryEngine/CrySystem/DebugCallStack.cpp new file mode 100644 index 0000000000..2a219ce674 --- /dev/null +++ b/Code/CryEngine/CrySystem/DebugCallStack.cpp @@ -0,0 +1,900 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ +// Original file Copyright Crytek GMBH or its affiliates, used under license. + +#include "CrySystem_precompiled.h" +#include "DebugCallStack.h" + +#if defined(WIN32) || defined(WIN64) + +#include +#include +#include "System.h" + +#include +#include + +#define VS_VERSION_INFO 1 +#define IDD_CRITICAL_ERROR 101 +#define IDB_CONFIRM_SAVE 102 +#define IDB_DONT_SAVE 103 +#define IDD_CONFIRM_SAVE_LEVEL 127 +#define IDB_CRASH_FACE 128 +#define IDD_EXCEPTION 245 +#define IDC_CALLSTACK 1001 +#define IDC_EXCEPTION_CODE 1002 +#define IDC_EXCEPTION_ADDRESS 1003 +#define IDC_EXCEPTION_MODULE 1004 +#define IDC_EXCEPTION_DESC 1005 +#define IDB_EXIT 1008 +#define IDB_IGNORE 1010 +__pragma(comment(lib, "version.lib")) + +//! Needs one external of DLL handle. +extern HMODULE gDLLHandle; + +#include + +#define MAX_PATH_LENGTH 1024 +#define MAX_SYMBOL_LENGTH 512 + +static HWND hwndException = 0; +static bool g_bUserDialog = true; // true=on crash show dialog box, false=supress user interaction + +static int PrintException(EXCEPTION_POINTERS* pex); + +static bool IsFloatingPointException(EXCEPTION_POINTERS* pex); + +extern LONG WINAPI CryEngineExceptionFilterWER(struct _EXCEPTION_POINTERS* pExceptionPointers); +extern LONG WINAPI CryEngineExceptionFilterMiniDump(struct _EXCEPTION_POINTERS* pExceptionPointers, const char* szDumpPath, MINIDUMP_TYPE mdumpValue); + +//============================================================================= +CONTEXT CaptureCurrentContext() +{ + CONTEXT context; + memset(&context, 0, sizeof(context)); + context.ContextFlags = CONTEXT_FULL; + RtlCaptureContext(&context); + + return context; +} + +LONG __stdcall CryUnhandledExceptionHandler(EXCEPTION_POINTERS* pex) +{ + return DebugCallStack::instance()->handleException(pex); +} + + +BOOL CALLBACK EnumModules( + PCSTR ModuleName, + DWORD64 BaseOfDll, + PVOID UserContext) +{ + DebugCallStack::TModules& modules = *static_cast(UserContext); + modules[(void*)BaseOfDll] = ModuleName; + + return TRUE; +} +//============================================================================= +// Class Statics +//============================================================================= + +// Return single instance of class. +IDebugCallStack* IDebugCallStack::instance() +{ + static DebugCallStack sInstance; + return &sInstance; +} + +//------------------------------------------------------------------------------------------------------------------------ +// Sets up the symbols for functions in the debug file. +//------------------------------------------------------------------------------------------------------------------------ +DebugCallStack::DebugCallStack() + : prevExceptionHandler(0) + , m_pSystem(0) + , m_nSkipNumFunctions(0) + , m_bCrash(false) + , m_szBugMessage(NULL) +{ +} + +DebugCallStack::~DebugCallStack() +{ +} + +void DebugCallStack::RemoveOldFiles() +{ + RemoveFile("error.log"); + RemoveFile("error.bmp"); + RemoveFile("error.dmp"); +} + +void DebugCallStack::RemoveFile(const char* szFileName) +{ + FILE* pFile = nullptr; + azfopen(&pFile, szFileName, "r"); + const bool bFileExists = (pFile != NULL); + + if (bFileExists) + { + fclose(pFile); + + WriteLineToLog("Removing file \"%s\"...", szFileName); + if (remove(szFileName) == 0) + { + WriteLineToLog("File successfully removed."); + } + else + { + WriteLineToLog("Couldn't remove file!"); + } + } +} + +void DebugCallStack::installErrorHandler(ISystem* pSystem) +{ + m_pSystem = pSystem; + prevExceptionHandler = (void*)SetUnhandledExceptionFilter(CryUnhandledExceptionHandler); +} + +////////////////////////////////////////////////////////////////////////// +void DebugCallStack::SetUserDialogEnable(const bool bUserDialogEnable) +{ + g_bUserDialog = bUserDialogEnable; +} + + +DWORD g_idDebugThreads[10]; +const char* g_nameDebugThreads[10]; +int g_nDebugThreads = 0; +volatile int g_lockThreadDumpList = 0; + +void MarkThisThreadForDebugging(const char* name) +{ + EBUS_EVENT(AZ::Debug::EventTraceDrillerSetupBus, SetThreadName, AZStd::this_thread::get_id(), name); + + WriteLock lock(g_lockThreadDumpList); + DWORD id = GetCurrentThreadId(); + if (g_nDebugThreads == sizeof(g_idDebugThreads) / sizeof(g_idDebugThreads[0])) + { + return; + } + for (int i = 0; i < g_nDebugThreads; i++) + { + if (g_idDebugThreads[i] == id) + { + return; + } + } + g_nameDebugThreads[g_nDebugThreads] = name; + g_idDebugThreads[g_nDebugThreads++] = id; + ((CSystem*)gEnv->pSystem)->EnableFloatExceptions(g_cvars.sys_float_exceptions); +} + +void UnmarkThisThreadFromDebugging() +{ + WriteLock lock(g_lockThreadDumpList); + DWORD id = GetCurrentThreadId(); + for (int i = g_nDebugThreads - 1; i >= 0; i--) + { + if (g_idDebugThreads[i] == id) + { + memmove(g_idDebugThreads + i, g_idDebugThreads + i + 1, (g_nDebugThreads - 1 - i) * sizeof(g_idDebugThreads[0])); + memmove(g_nameDebugThreads + i, g_nameDebugThreads + i + 1, (g_nDebugThreads - 1 - i) * sizeof(g_nameDebugThreads[0])); + --g_nDebugThreads; + } + } +} + +extern int prev_sys_float_exceptions; +void UpdateFPExceptionsMaskForThreads() +{ + int mask = -iszero(g_cvars.sys_float_exceptions); + CONTEXT ctx; + for (int i = 0; i < g_nDebugThreads; i++) + { + if (g_idDebugThreads[i] != GetCurrentThreadId()) + { + HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, TRUE, g_idDebugThreads[i]); + ctx.ContextFlags = CONTEXT_ALL; + SuspendThread(hThread); + GetThreadContext(hThread, &ctx); +#ifndef WIN64 + (ctx.FloatSave.ControlWord |= 7) &= ~5 | mask; + (*(WORD*)(ctx.ExtendedRegisters + 24) |= 0x280) &= ~0x280 | mask; +#else + (ctx.FltSave.ControlWord |= 7) &= ~5 | mask; + (ctx.FltSave.MxCsr |= 0x280) &= ~0x280 | mask; +#endif + SetThreadContext(hThread, &ctx); + ResumeThread(hThread); + } + } +} + +////////////////////////////////////////////////////////////////////////// +int DebugCallStack::handleException(EXCEPTION_POINTERS* exception_pointer) +{ + if (gEnv == NULL) + { + return EXCEPTION_EXECUTE_HANDLER; + } + + ResetFPU(exception_pointer); + + prev_sys_float_exceptions = 0; + const int cached_sys_float_exceptions = g_cvars.sys_float_exceptions; + + ((CSystem*)gEnv->pSystem)->EnableFloatExceptions(0); + + if (g_cvars.sys_WER) + { + gEnv->pLog->FlushAndClose(); + return CryEngineExceptionFilterWER(exception_pointer); + } + + if (g_cvars.sys_no_crash_dialog) + { + DWORD dwMode = SetErrorMode(SEM_NOGPFAULTERRORBOX); + SetErrorMode(dwMode | SEM_NOGPFAULTERRORBOX); + } + + m_bCrash = true; + + if (g_cvars.sys_no_crash_dialog) + { + DWORD dwMode = SetErrorMode(SEM_NOGPFAULTERRORBOX); + SetErrorMode(dwMode | SEM_NOGPFAULTERRORBOX); + } + + static bool firstTime = true; + + if (g_cvars.sys_dump_aux_threads) + { + for (int i = 0; i < g_nDebugThreads; i++) + { + if (g_idDebugThreads[i] != GetCurrentThreadId()) + { + SuspendThread(OpenThread(THREAD_ALL_ACCESS, TRUE, g_idDebugThreads[i])); + } + } + } + + // uninstall our exception handler. + SetUnhandledExceptionFilter((LPTOP_LEVEL_EXCEPTION_FILTER)prevExceptionHandler); + + if (!firstTime) + { + WriteLineToLog("Critical Exception! Called Multiple Times!"); + gEnv->pLog->FlushAndClose(); + // Exception called more then once. + return EXCEPTION_EXECUTE_HANDLER; + } + + // Print exception info: + { + char excCode[80]; + char excAddr[80]; + WriteLineToLog(""); + sprintf_s(excAddr, "0x%04X:0x%p", exception_pointer->ContextRecord->SegCs, exception_pointer->ExceptionRecord->ExceptionAddress); + sprintf_s(excCode, "0x%08X", exception_pointer->ExceptionRecord->ExceptionCode); + WriteLineToLog("Exception: %s, at Address: %s", excCode, excAddr); + } + + firstTime = false; + + const int ret = SubmitBug(exception_pointer); + + if (ret != IDB_IGNORE) + { + CryEngineExceptionFilterWER(exception_pointer); + } + + gEnv->pLog->FlushAndClose(); + + if (exception_pointer->ExceptionRecord->ExceptionFlags & EXCEPTION_NONCONTINUABLE) + { + // This is non continuable exception. abort application now. + exit(exception_pointer->ExceptionRecord->ExceptionCode); + } + + //typedef long (__stdcall *ExceptionFunc)(EXCEPTION_POINTERS*); + //ExceptionFunc prevFunc = (ExceptionFunc)prevExceptionHandler; + //return prevFunc( (EXCEPTION_POINTERS*)exception_pointer ); + if (ret == IDB_EXIT) + { + // Immediate exit. + // on windows, exit() and _exit() do all sorts of things, unfortuantely + // TerminateProcess is the only way to die. + TerminateProcess(GetCurrentProcess(), exception_pointer->ExceptionRecord->ExceptionCode); // we crashed, so don't return a zero exit code! + // on linux based systems, _exit will not call ATEXIT and other things, which makes it more suitable for termination in an emergency such + // as an unhandled exception. + // however, this function is a windows exception handler. + } + else if (ret == IDB_IGNORE) + { +#ifndef WIN64 + exception_pointer->ContextRecord->FloatSave.StatusWord &= ~31; + exception_pointer->ContextRecord->FloatSave.ControlWord |= 7; + (*(WORD*)(exception_pointer->ContextRecord->ExtendedRegisters + 24) &= 31) |= 0x1F80; +#else + exception_pointer->ContextRecord->FltSave.StatusWord &= ~31; + exception_pointer->ContextRecord->FltSave.ControlWord |= 7; + (exception_pointer->ContextRecord->FltSave.MxCsr &= 31) |= 0x1F80; +#endif + firstTime = true; + prevExceptionHandler = (void*)SetUnhandledExceptionFilter(CryUnhandledExceptionHandler); + g_cvars.sys_float_exceptions = cached_sys_float_exceptions; + ((CSystem*)gEnv->pSystem)->EnableFloatExceptions(g_cvars.sys_float_exceptions); + return EXCEPTION_CONTINUE_EXECUTION; + } + + // Continue; + return EXCEPTION_EXECUTE_HANDLER; +} + +void DebugCallStack::ReportBug(const char* szErrorMessage) +{ + WriteLineToLog("Reporting bug: %s", szErrorMessage); + + m_szBugMessage = szErrorMessage; + m_context = CaptureCurrentContext(); + SubmitBug(NULL); + m_szBugMessage = NULL; +} + +void DebugCallStack::dumpCallStack(std::vector& funcs) +{ + WriteLineToLog("============================================================================="); + int len = (int)funcs.size(); + for (int i = 0; i < len; i++) + { + const char* str = funcs[i].c_str(); + WriteLineToLog("%2d) %s", len - i, str); + } + WriteLineToLog("============================================================================="); +} + + +////////////////////////////////////////////////////////////////////////// +void DebugCallStack::LogExceptionInfo(EXCEPTION_POINTERS* pex) +{ + string path(""); + if ((gEnv) && (gEnv->pFileIO)) + { + const char* logAlias = gEnv->pFileIO->GetAlias("@log@"); + if (!logAlias) + { + logAlias = gEnv->pFileIO->GetAlias("@root@"); + } + if (logAlias) + { + path = logAlias; + path += "/"; + } + } + + string fileName = path; + fileName += "error.log"; + + struct stat fileInfo; + string timeStamp; + string backupPath; + if (gEnv->IsDedicated()) + { + backupPath = PathUtil::ToUnixPath(PathUtil::AddSlash(path + "DumpBackups")); + gEnv->pFileIO->CreatePath(backupPath.c_str()); + + if (stat(fileName.c_str(), &fileInfo) == 0) + { + // Backup log + tm creationTime; + localtime_s(&creationTime, &fileInfo.st_mtime); + char tempBuffer[32]; + strftime(tempBuffer, sizeof(tempBuffer), "%d %b %Y (%H %M %S)", &creationTime); + timeStamp = tempBuffer; + + string backupFileName = backupPath + timeStamp + " error.log"; + CopyFile(fileName.c_str(), backupFileName.c_str(), true); + } + } + + FILE* f = nullptr; + azfopen(&f, fileName.c_str(), "wt"); + + static char errorString[s_iCallStackSize]; + errorString[0] = 0; + + // Time and Version. + char versionbuf[1024]; + azstrcpy(versionbuf, AZ_ARRAY_SIZE(versionbuf), ""); + PutVersion(versionbuf, AZ_ARRAY_SIZE(versionbuf)); + cry_strcat(errorString, versionbuf); + cry_strcat(errorString, "\n"); + + char excCode[MAX_WARNING_LENGTH]; + char excAddr[80]; + char desc[1024]; + char excDesc[MAX_WARNING_LENGTH]; + + // make sure the mouse cursor is visible + ShowCursor(TRUE); + + const char* excName; + if (m_bIsFatalError || !pex) + { + const char* const szMessage = m_bIsFatalError ? s_szFatalErrorCode : m_szBugMessage; + excName = szMessage; + cry_strcpy(excCode, szMessage); + cry_strcpy(excAddr, ""); + cry_strcpy(desc, ""); + cry_strcpy(m_excModule, ""); + cry_strcpy(excDesc, szMessage); + } + else + { + sprintf_s(excAddr, "0x%04X:0x%p", pex->ContextRecord->SegCs, pex->ExceptionRecord->ExceptionAddress); + sprintf_s(excCode, "0x%08X", pex->ExceptionRecord->ExceptionCode); + excName = TranslateExceptionCode(pex->ExceptionRecord->ExceptionCode); + cry_strcpy(desc, ""); + sprintf_s(excDesc, "%s\r\n%s", excName, desc); + + + if (pex->ExceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION) + { + if (pex->ExceptionRecord->NumberParameters > 1) + { + ULONG_PTR iswrite = pex->ExceptionRecord->ExceptionInformation[0]; + DWORD64 accessAddr = pex->ExceptionRecord->ExceptionInformation[1]; + if (iswrite) + { + sprintf_s(desc, "Attempt to write data to address 0x%08llu\r\nThe memory could not be \"written\"", accessAddr); + } + else + { + sprintf_s(desc, "Attempt to read from address 0x%08llu\r\nThe memory could not be \"read\"", accessAddr); + } + } + } + } + + + WriteLineToLog("Exception Code: %s", excCode); + WriteLineToLog("Exception Addr: %s", excAddr); + WriteLineToLog("Exception Module: %s", m_excModule); + WriteLineToLog("Exception Name : %s", excName); + WriteLineToLog("Exception Description: %s", desc); + + + cry_strcpy(m_excDesc, excDesc); + cry_strcpy(m_excAddr, excAddr); + cry_strcpy(m_excCode, excCode); + + + char errs[32768]; + sprintf_s(errs, "Exception Code: %s\nException Addr: %s\nException Module: %s\nException Description: %s, %s\n", + excCode, excAddr, m_excModule, excName, desc); + + + cry_strcat(errs, "\nCall Stack Trace:\n"); + + std::vector funcs; + { + AZ::Debug::StackFrame frames[25]; + AZ::Debug::SymbolStorage::StackLine lines[AZ_ARRAY_SIZE(frames)]; + unsigned int numFrames = AZ::Debug::StackRecorder::Record(frames, AZ_ARRAY_SIZE(frames), 3); + if (numFrames) + { + AZ::Debug::SymbolStorage::DecodeFrames(frames, numFrames, lines); + for (unsigned int i = 0; i < numFrames; i++) + { + funcs.push_back(lines[i]); + } + } + dumpCallStack(funcs); + // Fill call stack. + char str[s_iCallStackSize]; + cry_strcpy(str, ""); + for (unsigned int i = 0; i < funcs.size(); i++) + { + char temp[s_iCallStackSize]; + sprintf_s(temp, "%2zd) %s", funcs.size() - i, (const char*)funcs[i].c_str()); + cry_strcat(str, temp); + cry_strcat(str, "\r\n"); + cry_strcat(errs, temp); + cry_strcat(errs, "\n"); + } + cry_strcpy(m_excCallstack, str); + } + + cry_strcat(errorString, errs); + + if (f) + { + fwrite(errorString, strlen(errorString), 1, f); + { + if (g_cvars.sys_dump_aux_threads) + { + for (int i = 0; i < g_nDebugThreads; i++) + { + if (g_idDebugThreads[i] != GetCurrentThreadId()) + { + fprintf(f, "\n\nSuspended thread (%s):\n", g_nameDebugThreads[i]); + HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, TRUE, g_idDebugThreads[i]); + + // mirrors the AZ::Debug::Trace::PrintCallstack() functionality, but prints to a file + { + AZ::Debug::StackFrame frames[10]; + + // Without StackFrame explicit alignment frames array is aligned to 4 bytes + // which causes the stack tracing to fail. + AZ::Debug::SymbolStorage::StackLine lines[AZ_ARRAY_SIZE(frames)]; + + unsigned int numFrames = AZ::Debug::StackRecorder::Record(frames, AZ_ARRAY_SIZE(frames), 0, hThread); + if (numFrames) + { + AZ::Debug::SymbolStorage::DecodeFrames(frames, numFrames, lines); + for (unsigned int i2 = 0; i2 < numFrames; ++i2) + { + fprintf(f, "%2d) %s\n", numFrames - i2, lines[i2]); + } + } + } + + ResumeThread(hThread); + } + } + } + } + fflush(f); + fclose(f); + } + + if (pex) + { + MINIDUMP_TYPE mdumpValue; + bool bDump = true; + switch (g_cvars.sys_dump_type) + { + case 0: + bDump = false; + break; + case 1: + mdumpValue = MiniDumpNormal; + break; + case 2: + mdumpValue = (MINIDUMP_TYPE)(MiniDumpWithIndirectlyReferencedMemory | MiniDumpWithDataSegs); + break; + case 3: + mdumpValue = MiniDumpWithFullMemory; + break; + default: + mdumpValue = (MINIDUMP_TYPE)g_cvars.sys_dump_type; + break; + } + if (bDump) + { + fileName = path + "error.dmp"; + + if (gEnv->IsDedicated() && stat(fileName.c_str(), &fileInfo) == 0) + { + // Backup dump (use timestamp from error.log if available) + if (timeStamp.empty()) + { + tm creationTime; + localtime_s(&creationTime, &fileInfo.st_mtime); + char tempBuffer[32]; + strftime(tempBuffer, sizeof(tempBuffer), "%d %b %Y (%H %M %S)", &creationTime); + timeStamp = tempBuffer; + } + + string backupFileName = backupPath + timeStamp + " error.dmp"; + CopyFile(fileName.c_str(), backupFileName.c_str(), true); + } + + CryEngineExceptionFilterMiniDump(pex, fileName.c_str(), mdumpValue); + } + } + + //if no crash dialog don't even submit the bug + if (m_postBackupProcess && g_cvars.sys_no_crash_dialog == 0 && g_bUserDialog) + { + m_postBackupProcess(); + } + else + { + // lawsonn: Disabling the JIRA-based crash reporter for now + // we'll need to deal with it our own way, pending QA. + // if you're customizing the engine this is also your opportunity to deal with it. + if (g_cvars.sys_no_crash_dialog != 0 || !g_bUserDialog) + { + // ------------ place custom crash handler here --------------------- + // it should launch an executable! + /// by this time, error.bmp will be in the engine root folder + // error.log and error.dmp will also be present in the engine root folder + // if your error dumper wants those, it should zip them up and send them or offer to do so. + // ------------------------------------------------------------------ + } + } + const bool bQuitting = !gEnv || !gEnv->pSystem || gEnv->pSystem->IsQuitting(); + + //[AlexMcC|16.04.10] When the engine is shutting down, MessageBox doesn't display a box + // and immediately returns IDYES. Avoid this by just not trying to save if we're quitting. + // Don't ask to save if this isn't a real crash (a real crash has exception pointers) + if (g_cvars.sys_no_crash_dialog == 0 && g_bUserDialog && gEnv->IsEditor() && !bQuitting && pex) + { + BackupCurrentLevel(); + + const INT_PTR res = DialogBoxParam(gDLLHandle, MAKEINTRESOURCE(IDD_CONFIRM_SAVE_LEVEL), NULL, DebugCallStack::ConfirmSaveDialogProc, NULL); + if (res == IDB_CONFIRM_SAVE) + { + if (SaveCurrentLevel()) + { + MessageBox(NULL, "Level has been successfully saved!\r\nPress Ok to terminate Editor.", "Save", MB_OK); + } + else + { + MessageBox(NULL, "Error saving level.\r\nPress Ok to terminate Editor.", "Save", MB_OK | MB_ICONWARNING); + } + } + } + + if (g_cvars.sys_no_crash_dialog != 0 || !g_bUserDialog) + { + // terminate immediately - since we're in a crash, there is no point unwinding stack, we've already done access violation or worse. + // calling exit will only cause further death down the line... + TerminateProcess(GetCurrentProcess(), pex->ExceptionRecord->ExceptionCode); + } +} + + +INT_PTR CALLBACK DebugCallStack::ExceptionDialogProc(HWND hwndDlg, UINT message, WPARAM wParam, LPARAM lParam) +{ + static EXCEPTION_POINTERS* pex; + + static char errorString[32768] = ""; + + switch (message) + { + case WM_INITDIALOG: + { + pex = (EXCEPTION_POINTERS*)lParam; + HWND h; + + if (pex->ExceptionRecord->ExceptionFlags & EXCEPTION_NONCONTINUABLE) + { + // Disable continue button for non continuable exceptions. + //h = GetDlgItem( hwndDlg,IDB_CONTINUE ); + //if (h) EnableWindow( h,FALSE ); + } + + DebugCallStack* pDCS = static_cast(DebugCallStack::instance()); + + h = GetDlgItem(hwndDlg, IDC_EXCEPTION_DESC); + if (h) + { + SendMessage(h, EM_REPLACESEL, FALSE, (LONG_PTR)pDCS->m_excDesc); + } + + h = GetDlgItem(hwndDlg, IDC_EXCEPTION_CODE); + if (h) + { + SendMessage(h, EM_REPLACESEL, FALSE, (LONG_PTR)pDCS->m_excCode); + } + + h = GetDlgItem(hwndDlg, IDC_EXCEPTION_MODULE); + if (h) + { + SendMessage(h, EM_REPLACESEL, FALSE, (LONG_PTR)pDCS->m_excModule); + } + + h = GetDlgItem(hwndDlg, IDC_EXCEPTION_ADDRESS); + if (h) + { + SendMessage(h, EM_REPLACESEL, FALSE, (LONG_PTR)pDCS->m_excAddr); + } + + // Fill call stack. + HWND callStack = GetDlgItem(hwndDlg, IDC_CALLSTACK); + if (callStack) + { + SendMessage(callStack, WM_SETTEXT, FALSE, (LPARAM)pDCS->m_excCallstack); + } + + if (hwndException) + { + DestroyWindow(hwndException); + hwndException = 0; + } + + if (IsFloatingPointException(pex)) + { + EnableWindow(GetDlgItem(hwndDlg, IDB_IGNORE), TRUE); + } + } + break; + + case WM_COMMAND: + switch (LOWORD(wParam)) + { + case IDB_EXIT: + case IDB_IGNORE: + // Fall through. + + EndDialog(hwndDlg, wParam); + return TRUE; + } + } + return FALSE; +} + +INT_PTR CALLBACK DebugCallStack::ConfirmSaveDialogProc(HWND hwndDlg, UINT message, WPARAM wParam, [[maybe_unused]] LPARAM lParam) +{ + switch (message) + { + case WM_INITDIALOG: + { + // The user might be holding down the spacebar while the engine crashes. + // If we don't remove keyboard focus from this dialog, the keypress will + // press the default button before the dialog actually appears, even if + // the user has already released the key, which is bad. + SetFocus(NULL); + } break; + case WM_COMMAND: + { + switch (LOWORD(wParam)) + { + case IDB_CONFIRM_SAVE: // Fall through + case IDB_DONT_SAVE: + { + EndDialog(hwndDlg, wParam); + return TRUE; + } + } + } break; + } + + return FALSE; +} + +bool DebugCallStack::BackupCurrentLevel() +{ + CSystem* pSystem = static_cast(m_pSystem); + if (pSystem && pSystem->GetUserCallback()) + { + return pSystem->GetUserCallback()->OnBackupDocument(); + } + + return false; +} + +bool DebugCallStack::SaveCurrentLevel() +{ + CSystem* pSystem = static_cast(m_pSystem); + if (pSystem && pSystem->GetUserCallback()) + { + return pSystem->GetUserCallback()->OnSaveDocument(); + } + + return false; +} + +int DebugCallStack::SubmitBug(EXCEPTION_POINTERS* exception_pointer) +{ + int ret = IDB_EXIT; + + assert(!hwndException); + + RemoveOldFiles(); + + AZ::Debug::Trace::PrintCallstack("", 2); + + LogExceptionInfo(exception_pointer); + + if (IsFloatingPointException(exception_pointer)) + { + //! Print exception dialog. + ret = PrintException(exception_pointer); + } + + return ret; +} + +void DebugCallStack::ResetFPU(EXCEPTION_POINTERS* pex) +{ + if (IsFloatingPointException(pex)) + { + // How to reset FPU: http://www.experts-exchange.com/Programming/System/Windows__Programming/Q_10310953.html + _clearfp(); +#ifndef WIN64 + pex->ContextRecord->FloatSave.ControlWord |= 0x2F; + pex->ContextRecord->FloatSave.StatusWord &= ~0x8080; +#endif + } +} + +string DebugCallStack::GetModuleNameForAddr(void* addr) +{ + if (m_modules.empty()) + { + return "[unknown]"; + } + + if (addr < m_modules.begin()->first) + { + return "[unknown]"; + } + + TModules::const_iterator it = m_modules.begin(); + TModules::const_iterator end = m_modules.end(); + for (; ++it != end; ) + { + if (addr < it->first) + { + return (--it)->second; + } + } + + //if address is higher than the last module, we simply assume it is in the last module. + return m_modules.rbegin()->second; +} + +void DebugCallStack::GetProcNameForAddr(void* addr, string& procName, void*& baseAddr, string& filename, int& line) +{ + AZ::Debug::SymbolStorage::StackLine func, file, module; + AZ::Debug::SymbolStorage::FindFunctionFromIP(addr, &func, &file, &module, line, baseAddr); + procName = func; + filename = file; +} + +string DebugCallStack::GetCurrentFilename() +{ + char fullpath[MAX_PATH_LENGTH + 1]; + GetModuleFileName(NULL, fullpath, MAX_PATH_LENGTH); + return fullpath; +} + +static bool IsFloatingPointException(EXCEPTION_POINTERS* pex) +{ + if (!pex) + { + return false; + } + + DWORD exceptionCode = pex->ExceptionRecord->ExceptionCode; + switch (exceptionCode) + { + case EXCEPTION_FLT_DENORMAL_OPERAND: + case EXCEPTION_FLT_DIVIDE_BY_ZERO: + case EXCEPTION_FLT_INEXACT_RESULT: + case EXCEPTION_FLT_INVALID_OPERATION: + case EXCEPTION_FLT_OVERFLOW: + case EXCEPTION_FLT_UNDERFLOW: + case STATUS_FLOAT_MULTIPLE_FAULTS: + case STATUS_FLOAT_MULTIPLE_TRAPS: + return true; + + default: + return false; + } +} + +int DebugCallStack::PrintException(EXCEPTION_POINTERS* exception_pointer) +{ + return (int)DialogBoxParam(gDLLHandle, MAKEINTRESOURCE(IDD_CRITICAL_ERROR), NULL, DebugCallStack::ExceptionDialogProc, (LPARAM)exception_pointer); +} + +#else +void MarkThisThreadForDebugging(const char*) {} +void UnmarkThisThreadFromDebugging() {} +void UpdateFPExceptionsMaskForThreads() {} +#endif //WIN32 diff --git a/Code/CryEngine/CrySystem/DebugCallStack.h b/Code/CryEngine/CrySystem/DebugCallStack.h new file mode 100644 index 0000000000..c37e6ba0d4 --- /dev/null +++ b/Code/CryEngine/CrySystem/DebugCallStack.h @@ -0,0 +1,95 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ +// Original file Copyright Crytek GMBH or its affiliates, used under license. + +#ifndef CRYINCLUDE_CRYSYSTEM_DEBUGCALLSTACK_H +#define CRYINCLUDE_CRYSYSTEM_DEBUGCALLSTACK_H +#pragma once + + +#include "IDebugCallStack.h" + +#if defined (WIN32) || defined (WIN64) + +//! Limits the maximal number of functions in call stack. +const int MAX_DEBUG_STACK_ENTRIES_FILE_DUMP = 12; + +struct ISystem; + +//!============================================================================ +//! +//! DebugCallStack class, capture call stack information from symbol files. +//! +//!============================================================================ +class DebugCallStack + : public IDebugCallStack +{ +public: + DebugCallStack(); + virtual ~DebugCallStack(); + + ISystem* GetSystem() { return m_pSystem; }; + + virtual string GetModuleNameForAddr(void* addr); + virtual void GetProcNameForAddr(void* addr, string& procName, void*& baseAddr, string& filename, int& line); + virtual string GetCurrentFilename(); + + void installErrorHandler(ISystem* pSystem); + virtual int handleException(EXCEPTION_POINTERS* exception_pointer); + + virtual void ReportBug(const char*); + + void dumpCallStack(std::vector& functions); + + void SetUserDialogEnable(const bool bUserDialogEnable); + + typedef std::map TModules; +protected: + static void RemoveOldFiles(); + static void RemoveFile(const char* szFileName); + + static int PrintException(EXCEPTION_POINTERS* exception_pointer); + static INT_PTR CALLBACK ExceptionDialogProc(HWND hwndDlg, UINT message, WPARAM wParam, LPARAM lParam); + static INT_PTR CALLBACK ConfirmSaveDialogProc(HWND hwndDlg, UINT message, WPARAM wParam, LPARAM lParam); + + void LogExceptionInfo(EXCEPTION_POINTERS* exception_pointer); + bool BackupCurrentLevel(); + bool SaveCurrentLevel(); + int SubmitBug(EXCEPTION_POINTERS* exception_pointer); + void ResetFPU(EXCEPTION_POINTERS* pex); + + static const int s_iCallStackSize = 32768; + + char m_excLine[256]; + char m_excModule[128]; + + char m_excDesc[MAX_WARNING_LENGTH]; + char m_excCode[MAX_WARNING_LENGTH]; + char m_excAddr[80]; + char m_excCallstack[s_iCallStackSize]; + + void* prevExceptionHandler; + + bool m_bCrash; + const char* m_szBugMessage; + + ISystem* m_pSystem; + + int m_nSkipNumFunctions; + CONTEXT m_context; + + TModules m_modules; +}; + +#endif //WIN32 + +#endif // CRYINCLUDE_CRYSYSTEM_DEBUGCALLSTACK_H diff --git a/Code/CryEngine/CrySystem/DllMain.cpp b/Code/CryEngine/CrySystem/DllMain.cpp index 7fd620835b..53593821d9 100644 --- a/Code/CryEngine/CrySystem/DllMain.cpp +++ b/Code/CryEngine/CrySystem/DllMain.cpp @@ -14,6 +14,7 @@ #include "CrySystem_precompiled.h" #include "System.h" #include +#include "DebugCallStack.h" #if defined(AZ_RESTRICTED_PLATFORM) #undef AZ_RESTRICTED_SECTION @@ -87,6 +88,16 @@ CRYSYSTEM_API ISystem* CreateSystemInterface(const SSystemInitParams& startupPar startupParams.pUserCallback->OnSystemConnect(pSystem); } +#if defined(WIN32) + // Environment Variable to signal we don't want to override our exception handler - our crash report system will set this + auto envVar = AZ::Environment::FindVariable("ExceptionHandlerIsSet"); + const bool handlerIsSet = (envVar && *envVar); + if (!handlerIsSet) + { + ((DebugCallStack*)IDebugCallStack::instance())->installErrorHandler(pSystem); + } +#endif + bool retVal = false; { AZ::Debug::StartupLogSinkReporter initLogSink; diff --git a/Code/CryEngine/CrySystem/IDebugCallStack.cpp b/Code/CryEngine/CrySystem/IDebugCallStack.cpp new file mode 100644 index 0000000000..c14dd2b0da --- /dev/null +++ b/Code/CryEngine/CrySystem/IDebugCallStack.cpp @@ -0,0 +1,275 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ +// Original file Copyright Crytek GMBH or its affiliates, used under license. + +// Description : A multiplatform base class for handling errors and collecting call stacks + + +#include "CrySystem_precompiled.h" +#include "IDebugCallStack.h" +#include "System.h" +#include +#include +#include +#include +//#if !defined(LINUX) + +#include + +const char* const IDebugCallStack::s_szFatalErrorCode = "FATAL_ERROR"; + +IDebugCallStack::IDebugCallStack() + : m_bIsFatalError(false) + , m_postBackupProcess(0) + , m_memAllocFileHandle(AZ::IO::InvalidHandle) +{ +} + +IDebugCallStack::~IDebugCallStack() +{ + StopMemLog(); +} + +#if AZ_LEGACY_CRYSYSTEM_TRAIT_DEBUGCALLSTACK_SINGLETON +IDebugCallStack* IDebugCallStack::instance() +{ + static IDebugCallStack sInstance; + return &sInstance; +} +#endif + +void IDebugCallStack::FileCreationCallback(void (* postBackupProcess)()) +{ + m_postBackupProcess = postBackupProcess; +} +////////////////////////////////////////////////////////////////////////// +void IDebugCallStack::LogCallstack() +{ + AZ::Debug::Trace::PrintCallstack("", 2); +} + +const char* IDebugCallStack::TranslateExceptionCode(DWORD dwExcept) +{ + switch (dwExcept) + { +#if AZ_LEGACY_CRYSYSTEM_TRAIT_DEBUGCALLSTACK_TRANSLATE + case EXCEPTION_ACCESS_VIOLATION: + return "EXCEPTION_ACCESS_VIOLATION"; + break; + case EXCEPTION_DATATYPE_MISALIGNMENT: + return "EXCEPTION_DATATYPE_MISALIGNMENT"; + break; + case EXCEPTION_BREAKPOINT: + return "EXCEPTION_BREAKPOINT"; + break; + case EXCEPTION_SINGLE_STEP: + return "EXCEPTION_SINGLE_STEP"; + break; + case EXCEPTION_ARRAY_BOUNDS_EXCEEDED: + return "EXCEPTION_ARRAY_BOUNDS_EXCEEDED"; + break; + case EXCEPTION_FLT_DENORMAL_OPERAND: + return "EXCEPTION_FLT_DENORMAL_OPERAND"; + break; + case EXCEPTION_FLT_DIVIDE_BY_ZERO: + return "EXCEPTION_FLT_DIVIDE_BY_ZERO"; + break; + case EXCEPTION_FLT_INEXACT_RESULT: + return "EXCEPTION_FLT_INEXACT_RESULT"; + break; + case EXCEPTION_FLT_INVALID_OPERATION: + return "EXCEPTION_FLT_INVALID_OPERATION"; + break; + case EXCEPTION_FLT_OVERFLOW: + return "EXCEPTION_FLT_OVERFLOW"; + break; + case EXCEPTION_FLT_STACK_CHECK: + return "EXCEPTION_FLT_STACK_CHECK"; + break; + case EXCEPTION_FLT_UNDERFLOW: + return "EXCEPTION_FLT_UNDERFLOW"; + break; + case EXCEPTION_INT_DIVIDE_BY_ZERO: + return "EXCEPTION_INT_DIVIDE_BY_ZERO"; + break; + case EXCEPTION_INT_OVERFLOW: + return "EXCEPTION_INT_OVERFLOW"; + break; + case EXCEPTION_PRIV_INSTRUCTION: + return "EXCEPTION_PRIV_INSTRUCTION"; + break; + case EXCEPTION_IN_PAGE_ERROR: + return "EXCEPTION_IN_PAGE_ERROR"; + break; + case EXCEPTION_ILLEGAL_INSTRUCTION: + return "EXCEPTION_ILLEGAL_INSTRUCTION"; + break; + case EXCEPTION_NONCONTINUABLE_EXCEPTION: + return "EXCEPTION_NONCONTINUABLE_EXCEPTION"; + break; + case EXCEPTION_STACK_OVERFLOW: + return "EXCEPTION_STACK_OVERFLOW"; + break; + case EXCEPTION_INVALID_DISPOSITION: + return "EXCEPTION_INVALID_DISPOSITION"; + break; + case EXCEPTION_GUARD_PAGE: + return "EXCEPTION_GUARD_PAGE"; + break; + case EXCEPTION_INVALID_HANDLE: + return "EXCEPTION_INVALID_HANDLE"; + break; + //case EXCEPTION_POSSIBLE_DEADLOCK: return "EXCEPTION_POSSIBLE_DEADLOCK"; break ; + + case STATUS_FLOAT_MULTIPLE_FAULTS: + return "STATUS_FLOAT_MULTIPLE_FAULTS"; + break; + case STATUS_FLOAT_MULTIPLE_TRAPS: + return "STATUS_FLOAT_MULTIPLE_TRAPS"; + break; + + +#endif + default: + return "Unknown"; + break; + } +} + +void IDebugCallStack::PutVersion(char* str, size_t length) +{ +AZ_PUSH_DISABLE_WARNING(4996, "-Wunknown-warning-option") + + if (!gEnv || !gEnv->pSystem) + { + return; + } + + char sFileVersion[128]; + gEnv->pSystem->GetFileVersion().ToString(sFileVersion, sizeof(sFileVersion)); + + char sProductVersion[128]; + gEnv->pSystem->GetProductVersion().ToString(sProductVersion, sizeof(sFileVersion)); + + + //! Get time. + time_t ltime; + time(<ime); + tm* today = localtime(<ime); + + char s[1024]; + //! Use strftime to build a customized time string. + strftime(s, 128, "Logged at %#c\n", today); + azstrcat(str, length, s); + sprintf_s(s, "FileVersion: %s\n", sFileVersion); + azstrcat(str, length, s); + sprintf_s(s, "ProductVersion: %s\n", sProductVersion); + azstrcat(str, length, s); + + if (gEnv->pLog) + { + const char* logfile = gEnv->pLog->GetFileName(); + if (logfile) + { + sprintf (s, "LogFile: %s\n", logfile); + azstrcat(str, length, s); + } + } + + AZ::IO::FixedMaxPathString projectPath = AZ::Utils::GetProjectPath(); + azstrcat(str, length, "ProjectDir: "); + azstrcat(str, length, projectPath.c_str()); + azstrcat(str, length, "\n"); + +#if AZ_LEGACY_CRYSYSTEM_TRAIT_DEBUGCALLSTACK_APPEND_MODULENAME + GetModuleFileNameA(NULL, s, sizeof(s)); + + // Log EXE filename only if possible (not full EXE path which could contain sensitive info) + AZStd::string exeName; + if (AZ::StringFunc::Path::GetFullFileName(s, exeName)) + { + azstrcat(str, length, "Executable: "); + azstrcat(str, length, exeName.c_str()); + +# ifdef AZ_DEBUG_BUILD + azstrcat(str, length, " (debug: yes"); +# else + azstrcat(str, length, " (debug: no"); +# endif + } +#endif +AZ_POP_DISABLE_WARNING +} + + +//Crash the application, in this way the debug callstack routine will be called and it will create all the necessary files (error.log, dump, and eventually screenshot) +void IDebugCallStack::FatalError(const char* description) +{ + m_bIsFatalError = true; + WriteLineToLog(description); + +#ifndef _RELEASE + bool bShowDebugScreen = g_cvars.sys_no_crash_dialog == 0; + // showing the debug screen is not safe when not called from mainthread + // it normally leads to a infinity recursion followed by a stack overflow, preventing + // useful call stacks, thus they are disabled + bShowDebugScreen = bShowDebugScreen && gEnv->mMainThreadId == CryGetCurrentThreadId(); + if (bShowDebugScreen) + { + EBUS_EVENT(AZ::NativeUI::NativeUIRequestBus, DisplayOkDialog, "Open 3D Engine Fatal Error", description, false); + } +#endif + +#if defined(WIN32) || !defined(_RELEASE) + int* p = 0x0; + PREFAST_SUPPRESS_WARNING(6011) * p = 1; // we're intentionally crashing here +#endif +} + +void IDebugCallStack::WriteLineToLog(const char* format, ...) +{ + va_list ArgList; + char szBuffer[MAX_WARNING_LENGTH]; + va_start(ArgList, format); + vsnprintf_s(szBuffer, sizeof(szBuffer), sizeof(szBuffer) - 1, format, ArgList); + cry_strcat(szBuffer, "\n"); + szBuffer[sizeof(szBuffer) - 1] = '\0'; + va_end(ArgList); + + AZ::IO::HandleType fileHandle = AZ::IO::InvalidHandle; + AZ::IO::FileIOBase::GetDirectInstance()->Open("@Log@\\error.log", AZ::IO::GetOpenModeFromStringMode("a+t"), fileHandle); + if (fileHandle != AZ::IO::InvalidHandle) + { + AZ::IO::FileIOBase::GetDirectInstance()->Write(fileHandle, szBuffer, strlen(szBuffer)); + AZ::IO::FileIOBase::GetDirectInstance()->Flush(fileHandle); + AZ::IO::FileIOBase::GetDirectInstance()->Close(fileHandle); + } +} + +////////////////////////////////////////////////////////////////////////// +void IDebugCallStack::StartMemLog() +{ + AZ::IO::FileIOBase::GetDirectInstance()->Open("@Log@\\memallocfile.log", AZ::IO::OpenMode::ModeWrite, m_memAllocFileHandle); + + assert(m_memAllocFileHandle != AZ::IO::InvalidHandle); +} + +////////////////////////////////////////////////////////////////////////// +void IDebugCallStack::StopMemLog() +{ + if (m_memAllocFileHandle != AZ::IO::InvalidHandle) + { + AZ::IO::FileIOBase::GetDirectInstance()->Close(m_memAllocFileHandle); + m_memAllocFileHandle = AZ::IO::InvalidHandle; + } +} +//#endif //!defined(LINUX) diff --git a/Code/CryEngine/CrySystem/IDebugCallStack.h b/Code/CryEngine/CrySystem/IDebugCallStack.h new file mode 100644 index 0000000000..f181b73913 --- /dev/null +++ b/Code/CryEngine/CrySystem/IDebugCallStack.h @@ -0,0 +1,90 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ +// Original file Copyright Crytek GMBH or its affiliates, used under license. + +// Description : A multiplatform base class for handling errors and collecting call stacks + +#ifndef CRYINCLUDE_CRYSYSTEM_IDEBUGCALLSTACK_H +#define CRYINCLUDE_CRYSYSTEM_IDEBUGCALLSTACK_H +#pragma once + +#include "System.h" + +#if AZ_LEGACY_CRYSYSTEM_TRAIT_FORWARD_EXCEPTION_POINTERS +struct EXCEPTION_POINTERS; +#endif +//! Limits the maximal number of functions in call stack. +enum +{ + MAX_DEBUG_STACK_ENTRIES = 80 +}; + +class IDebugCallStack +{ +public: + // Returns single instance of DebugStack + static IDebugCallStack* instance(); + + virtual int handleException([[maybe_unused]] EXCEPTION_POINTERS* exception_pointer){return 0; } + + // returns the module name of a given address + virtual string GetModuleNameForAddr([[maybe_unused]] void* addr) { return "[unknown]"; } + + // returns the function name of a given address together with source file and line number (if available) of a given address + virtual void GetProcNameForAddr(void* addr, string& procName, void*& baseAddr, string& filename, int& line) + { + filename = "[unknown]"; + line = 0; + baseAddr = addr; +#if defined(PLATFORM_64BIT) + procName.Format("[%016llX]", addr); +#else + procName.Format("[%08X]", addr); +#endif + } + + // returns current filename + virtual string GetCurrentFilename() { return "[unknown]"; } + + //! Dumps Current Call Stack to log. + virtual void LogCallstack(); + //triggers a fatal error, so the DebugCallstack can create the error.log and terminate the application + void FatalError(const char*); + + //Reports a bug and continues execution + virtual void ReportBug(const char*) {} + + virtual void FileCreationCallback(void (* postBackupProcess)()); + + static void WriteLineToLog(const char* format, ...); + + virtual void StartMemLog(); + virtual void StopMemLog(); + +protected: + IDebugCallStack(); + virtual ~IDebugCallStack(); + + static const char* TranslateExceptionCode(DWORD dwExcept); + static void PutVersion(char* str, size_t length); + + bool m_bIsFatalError; + static const char* const s_szFatalErrorCode; + + void (* m_postBackupProcess)(); + + AZ::IO::HandleType m_memAllocFileHandle; +}; + + + +#endif // CRYINCLUDE_CRYSYSTEM_IDEBUGCALLSTACK_H diff --git a/Code/CryEngine/CrySystem/System.h b/Code/CryEngine/CrySystem/System.h index 631d84d934..b91b1ba059 100644 --- a/Code/CryEngine/CrySystem/System.h +++ b/Code/CryEngine/CrySystem/System.h @@ -208,6 +208,7 @@ struct SSystemCVars int sys_no_crash_dialog; int sys_no_error_report_window; int sys_dump_aux_threads; + int sys_WER; int sys_dump_type; int sys_ai; int sys_entitysystem; diff --git a/Code/CryEngine/CrySystem/SystemInit.cpp b/Code/CryEngine/CrySystem/SystemInit.cpp index a2c21ea04c..25d6b9c601 100644 --- a/Code/CryEngine/CrySystem/SystemInit.cpp +++ b/Code/CryEngine/CrySystem/SystemInit.cpp @@ -121,6 +121,10 @@ # include #endif +#ifdef WIN32 +extern LONG WINAPI CryEngineExceptionFilterWER(struct _EXCEPTION_POINTERS* pExceptionPointers); +#endif + #if defined(AZ_RESTRICTED_PLATFORM) #define AZ_RESTRICTED_SECTION SYSTEMINIT_CPP_SECTION_14 #include AZ_RESTRICTED_FILE(SystemInit_cpp) @@ -1484,6 +1488,13 @@ AZ_POP_DISABLE_WARNING InlineInitializationProcessing("CSystem::Init LoadConfigurations"); +#ifdef WIN32 + if (g_cvars.sys_WER) + { + SetUnhandledExceptionFilter(CryEngineExceptionFilterWER); + } +#endif + ////////////////////////////////////////////////////////////////////////// // Localization ////////////////////////////////////////////////////////////////////////// @@ -2020,6 +2031,14 @@ void CSystem::CreateSystemVars() REGISTER_CVAR2("sys_update_profile_time", &g_cvars.sys_update_profile_time, 1.0f, 0, "Time to keep updates timings history for."); REGISTER_CVAR2("sys_no_crash_dialog", &g_cvars.sys_no_crash_dialog, m_bNoCrashDialog, VF_NULL, "Whether to disable the crash dialog window"); REGISTER_CVAR2("sys_no_error_report_window", &g_cvars.sys_no_error_report_window, m_bNoErrorReportWindow, VF_NULL, "Whether to disable the error report list"); +#if defined(_RELEASE) + if (!gEnv->IsDedicated()) + { + REGISTER_CVAR2("sys_WER", &g_cvars.sys_WER, 1, 0, "Enables Windows Error Reporting"); + } +#else + REGISTER_CVAR2("sys_WER", &g_cvars.sys_WER, 0, 0, "Enables Windows Error Reporting"); +#endif #ifdef USE_HTTP_WEBSOCKETS REGISTER_CVAR2("sys_simple_http_base_port", &g_cvars.sys_simple_http_base_port, 1880, VF_REQUIRE_APP_RESTART, diff --git a/Code/CryEngine/CrySystem/SystemWin32.cpp b/Code/CryEngine/CrySystem/SystemWin32.cpp index c974365bfc..eb6aa17532 100644 --- a/Code/CryEngine/CrySystem/SystemWin32.cpp +++ b/Code/CryEngine/CrySystem/SystemWin32.cpp @@ -46,6 +46,8 @@ #include #endif +#include "IDebugCallStack.h" + #if defined(APPLE) || defined(LINUX) #include #endif @@ -355,6 +357,7 @@ void CSystem::FatalError(const char* format, ...) } // Dump callstack. + IDebugCallStack::instance()->FatalError(szBuffer); #endif CryDebugBreak(); @@ -396,6 +399,8 @@ void CSystem::ReportBug([[maybe_unused]] const char* format, ...) va_start(ArgList, format); azvsnprintf(szBuffer + strlen(sPrefix), MAX_WARNING_LENGTH - strlen(sPrefix), format, ArgList); va_end(ArgList); + + IDebugCallStack::instance()->ReportBug(szBuffer); #endif } diff --git a/Code/CryEngine/CrySystem/WindowsErrorReporting.cpp b/Code/CryEngine/CrySystem/WindowsErrorReporting.cpp new file mode 100644 index 0000000000..feaffd42aa --- /dev/null +++ b/Code/CryEngine/CrySystem/WindowsErrorReporting.cpp @@ -0,0 +1,137 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ +// Original file Copyright Crytek GMBH or its affiliates, used under license. + +// Description : Support for Windows Error Reporting (WER) + + +#include "CrySystem_precompiled.h" + +#ifdef WIN32 + +#include "System.h" +#include +#include +#include "errorrep.h" +#include "ISystem.h" + +#include + +static WCHAR szPath[MAX_PATH + 1]; +static WCHAR szFR[] = L"\\System32\\FaultRep.dll"; + +WCHAR* GetFullPathToFaultrepDll(void) +{ + UINT rc = GetSystemWindowsDirectoryW(szPath, ARRAYSIZE(szPath)); + if (rc == 0 || rc > ARRAYSIZE(szPath) - ARRAYSIZE(szFR) - 1) + { + return NULL; + } + + wcscat_s(szPath, szFR); + return szPath; +} + + +typedef BOOL (WINAPI * MINIDUMPWRITEDUMP)(HANDLE hProcess, DWORD dwPid, HANDLE hFile, MINIDUMP_TYPE DumpType, + CONST PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam, + CONST PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam, + CONST PMINIDUMP_CALLBACK_INFORMATION CallbackParam + ); + +////////////////////////////////////////////////////////////////////////// +LONG WINAPI CryEngineExceptionFilterMiniDump(struct _EXCEPTION_POINTERS* pExceptionPointers, const char* szDumpPath, MINIDUMP_TYPE DumpType) +{ + // note: In debug mode, this dll is loaded on startup anyway, so this should not incur an additional load unless it crashes + // very early during startup. + + fflush(nullptr); // according to MSDN on fflush, calling fflush on null flushes all buffers. + HMODULE hndDBGHelpDLL = LoadLibraryA("DBGHELP.DLL"); + + if (!hndDBGHelpDLL) + { + CryLogAlways("Failed to record DMP file: Could not open DBGHELP.DLL"); + return EXCEPTION_CONTINUE_SEARCH; + } + + MINIDUMPWRITEDUMP dumpFnPtr = (MINIDUMPWRITEDUMP)::GetProcAddress(hndDBGHelpDLL, "MiniDumpWriteDump"); + if (!dumpFnPtr) + { + CryLogAlways("Failed to record DMP file: Unable to find MiniDumpWriteDump in DBGHELP.DLL"); + return EXCEPTION_CONTINUE_SEARCH; + } + + HANDLE hFile = ::CreateFile(szDumpPath, GENERIC_WRITE, FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + if (hFile == INVALID_HANDLE_VALUE) + { + CryLogAlways("Failed to record DMP file: could not open file '%s' for writing - error code: %d", szDumpPath, GetLastError()); + return EXCEPTION_CONTINUE_SEARCH; + } + + _MINIDUMP_EXCEPTION_INFORMATION ExInfo; + ExInfo.ThreadId = ::GetCurrentThreadId(); + ExInfo.ExceptionPointers = pExceptionPointers; + ExInfo.ClientPointers = NULL; + + BOOL bOK = dumpFnPtr(GetCurrentProcess(), GetCurrentProcessId(), hFile, DumpType, &ExInfo, NULL, NULL); + ::CloseHandle(hFile); + + if (bOK) + { + CryLogAlways("Successfully recorded DMP file: '%s'", szDumpPath); + return EXCEPTION_EXECUTE_HANDLER; // SUCCESS! you can execute your handlers now + } + else + { + CryLogAlways("Failed to record DMP file: '%s' - error code: %d", szDumpPath, GetLastError()); + } + + return EXCEPTION_CONTINUE_SEARCH; +} + +////////////////////////////////////////////////////////////////////////// +LONG WINAPI CryEngineExceptionFilterWER(struct _EXCEPTION_POINTERS* pExceptionPointers) +{ + if (g_cvars.sys_WER > 1) + { + char szScratch [_MAX_PATH]; + const char* szDumpPath = gEnv->pCryPak->AdjustFileName("@log@/CE2Dump.dmp", szScratch, AZ_ARRAY_SIZE(szScratch), 0); + + MINIDUMP_TYPE mdumpValue = (MINIDUMP_TYPE)(MiniDumpNormal); + if (g_cvars.sys_WER > 1) + { + mdumpValue = (MINIDUMP_TYPE)(g_cvars.sys_WER - 2); + } + + return CryEngineExceptionFilterMiniDump(pExceptionPointers, szDumpPath, mdumpValue); + } + + LONG lRet = EXCEPTION_CONTINUE_SEARCH; + WCHAR* psz = GetFullPathToFaultrepDll(); + if (psz) + { + HMODULE hFaultRepDll = LoadLibraryW(psz); + if (hFaultRepDll) + { + pfn_REPORTFAULT pfn = (pfn_REPORTFAULT)GetProcAddress(hFaultRepDll, "ReportFault"); + if (pfn) + { + pfn(pExceptionPointers, 0); + lRet = EXCEPTION_EXECUTE_HANDLER; + } + FreeLibrary(hFaultRepDll); + } + } + return lRet; +} + +#endif // WIN32 diff --git a/Code/CryEngine/CrySystem/crysystem_files.cmake b/Code/CryEngine/CrySystem/crysystem_files.cmake index 6a56339b85..84250de95b 100644 --- a/Code/CryEngine/CrySystem/crysystem_files.cmake +++ b/Code/CryEngine/CrySystem/crysystem_files.cmake @@ -15,6 +15,8 @@ set(FILES CmdLineArg.cpp ConsoleBatchFile.cpp ConsoleHelpGen.cpp + DebugCallStack.cpp + IDebugCallStack.cpp Log.cpp System.cpp SystemCFG.cpp @@ -31,6 +33,8 @@ set(FILES CmdLineArg.h ConsoleBatchFile.h ConsoleHelpGen.h + DebugCallStack.h + IDebugCallStack.h Log.h SimpleStringPool.h CrySystem_precompiled.h @@ -72,4 +76,5 @@ set(FILES ViewSystem/ViewSystem.cpp ViewSystem/ViewSystem.h CrySystem_precompiled.cpp + WindowsErrorReporting.cpp ) From 66a7db44f7521bdabee11aeb69a6fa3cab620b78 Mon Sep 17 00:00:00 2001 From: sconel Date: Tue, 18 May 2021 17:13:24 -0700 Subject: [PATCH 56/69] Reduced scope of change to focus on SpawnAllEntities --- .../Spawnable/SpawnableEntitiesManager.cpp | 60 ++++++++----------- .../Spawnable/SpawnableEntitiesManager.h | 6 +- 2 files changed, 29 insertions(+), 37 deletions(-) diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp index ad2e1551d5..39501ba0cd 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp @@ -216,6 +216,18 @@ namespace AzFramework return clone; } + Spawnable::EntityList* SpawnableEntitiesManager::CloneAllEntities(const Spawnable::EntityList& entitiesTemplate, + AZ::SerializeContext& serializeContext) + { + // Map keeps track of ids from template (spawnable) to clone (instance) + // Allowing patch ups of fields referring to entityIds outside of a given entity + EntityIdMap templateToCloneIdMap; + templateToCloneIdMap.reserve(entitiesTemplate.size()); + + return AZ::IdUtils::Remapper::CloneObjectAndGenerateNewIdsAndFixRefs( + &entitiesTemplate, templateToCloneIdMap, &serializeContext); + } + bool SpawnableEntitiesManager::ProcessRequest(SpawnAllEntitiesCommand& request, AZ::SerializeContext& serializeContext) { Ticket& ticket = GetTicketPayload(*request.m_ticket); @@ -235,44 +247,16 @@ namespace AzFramework spawnedEntities.reserve(spawnedEntities.size() + entitiesToSpawnSize); ticket.m_spawnedEntityIndices.reserve(ticket.m_spawnedEntityIndices.size() + entitiesToSpawnSize); - // TEMP: To be replaced by IdUtils::Remapper - using EntityIdMap = AZStd::unordered_map; - EntityIdMap templateToCloneIdMap; - // \TEMP - // Clone the entities from Spawnable - for (size_t i = 0; i < entitiesToSpawnSize; ++i) - { - const AZ::Entity& entityTemplate = *entitiesToSpawn[i]; + Spawnable::EntityList* clonedEntities = CloneAllEntities(entitiesToSpawn, serializeContext); + AZ_Assert(clonedEntities != nullptr, "Failed to clone entities while processing a SpawnAllEntitiesCommand"); - AZ::Entity* clone = serializeContext.CloneObject(&entityTemplate); - AZ_Assert(clone != nullptr, "Failed to clone spawnable entity."); - clone->SetId(AZ::Entity::MakeId()); + spawnedEntities.insert(spawnedEntities.end(), clonedEntities->begin(), clonedEntities->end()); - spawnedEntities.push_back(clone); + // Mark all indices as spawned + for (size_t i = 0; i < entitiesToSpawnSize; ++i) + { spawnedEntityIndices.push_back(i); - - // TEMP: To be replaced by IdUtils::Remapper - templateToCloneIdMap[entityTemplate.GetId()] = clone->GetId(); - - // Update TransformComponent parent Id. It is guaranteed for the entities array to be sorted from parent->child here. - auto* transformComponent = clone->FindComponent(); - AZ::EntityId parentId = transformComponent->GetParentId(); - if (parentId.IsValid()) - { - auto it = templateToCloneIdMap.find(parentId); - if (it != templateToCloneIdMap.end()) - { - transformComponent->SetParentRelative(it->second); - } - else - { - AZ_Warning( - "SpawnableEntitiesManager", false, "Entity %s doesn't have the parent entity %s present in the spawnable", - clone->GetName().c_str(), parentId.ToString().data()); - } - } - // \TEMP } // Let other systems know about newly spawned entities for any pre-processing before adding to the scene/game context. @@ -438,10 +422,16 @@ namespace AzFramework // to load every, simply start over. ticket.m_spawnedEntityIndices.clear(); + // Clone the entities from Spawnable + Spawnable::EntityList* clonedEntities = CloneAllEntities(entities, serializeContext); + AZ_Assert(clonedEntities != nullptr, "Failed to clone entities while processing a SpawnAllEntitiesCommand"); + + ticket.m_spawnedEntities.insert(ticket.m_spawnedEntities.end(), clonedEntities->begin(), clonedEntities->end()); + + // Mark all indices as spawned size_t entitiesSize = entities.size(); for (size_t i = 0; i < entitiesSize; ++i) { - ticket.m_spawnedEntities.push_back(SpawnSingleEntity(*entities[i], serializeContext)); ticket.m_spawnedEntityIndices.push_back(i); } } diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.h b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.h index bcd6a7b6ea..c9e1fbd715 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.h +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.h @@ -83,7 +83,6 @@ namespace AzFramework AZStd::vector m_spawnedEntities; AZStd::vector m_spawnedEntityIndices; - EntityIdMap m_spawnableToInstanceEntityIdMap; AZ::Data::Asset m_spawnable; uint32_t m_nextTicketId{ 0 }; //!< Next id for this ticket. @@ -146,7 +145,10 @@ namespace AzFramework using Requests = AZStd::variant; - AZ::Entity* SpawnSingleEntity(const AZ::Entity& entityTemplate, EntityIdMap& spawnableToInstanceEntityIdMap, + AZ::Entity* SpawnSingleEntity(const AZ::Entity& entityTemplate, + AZ::SerializeContext& serializeContext); + + Spawnable::EntityList* CloneAllEntities(const Spawnable::EntityList& entitiesTemplate, AZ::SerializeContext& serializeContext); bool ProcessRequest(SpawnAllEntitiesCommand& request, AZ::SerializeContext& serializeContext); From 8733f0e4928eb8481dfcf153cc12ada493daad3a Mon Sep 17 00:00:00 2001 From: sconel Date: Tue, 18 May 2021 17:16:44 -0700 Subject: [PATCH 57/69] Remove extra newline --- .../AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.h | 1 - 1 file changed, 1 deletion(-) diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.h b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.h index c9e1fbd715..3def85170f 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.h +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.h @@ -83,7 +83,6 @@ namespace AzFramework AZStd::vector m_spawnedEntities; AZStd::vector m_spawnedEntityIndices; - AZ::Data::Asset m_spawnable; uint32_t m_nextTicketId{ 0 }; //!< Next id for this ticket. uint32_t m_currentTicketId{ 0 }; //!< The id for the command that should be executed. From 8a07408a0c62952d3a04f9da5bd569bbfd8f5720 Mon Sep 17 00:00:00 2001 From: Steve Pham <82231385+spham-amzn@users.noreply.github.com> Date: Tue, 18 May 2021 18:39:42 -0700 Subject: [PATCH 58/69] Fix for intermitten AzNetworkingTest on Linux AR runs --- scripts/build/Platform/Linux/build_config.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/build/Platform/Linux/build_config.json b/scripts/build/Platform/Linux/build_config.json index bbfc3e4269..5d96ae7846 100644 --- a/scripts/build/Platform/Linux/build_config.json +++ b/scripts/build/Platform/Linux/build_config.json @@ -83,7 +83,7 @@ "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-6.0 -DCMAKE_CXX_COMPILER=clang++-6.0 -DLY_UNITY_BUILD=TRUE -DLY_PARALLEL_LINK_JOBS=4 -DO3DE_HOME_PATH=\"${WORKSPACE}/home\" -DO3DE_REGISTER_ENGINE_PATH=\"${WORKSPACE}/o3de\" -DO3DE_REGISTER_THIS_ENGINE=TRUE", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "all", - "CTEST_OPTIONS": "-E (Gem::EMotionFX.Editor.Tests|Gem::AWSClientAuth.Tests|Gem::AWSCore.Editor.Tests) -L FRAMEWORK_googletest" + "CTEST_OPTIONS": "-E (Gem::EMotionFX.Editor.Tests|Gem::AWSClientAuth.Tests|Gem::AWSCore.Editor.Tests) -L FRAMEWORK_googletest --repeat until-pass:5" } }, "test_profile_nounity": { @@ -95,7 +95,7 @@ "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-6.0 -DCMAKE_CXX_COMPILER=clang++-6.0 -DLY_UNITY_BUILD=FALSE -DLY_PARALLEL_LINK_JOBS=4 -DO3DE_HOME_PATH=\"${WORKSPACE}/home\" -DO3DE_REGISTER_ENGINE_PATH=\"${WORKSPACE}/o3de\" -DO3DE_REGISTER_THIS_ENGINE=TRUE", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "all", - "CTEST_OPTIONS": "-E (Gem::EMotionFX.Editor.Tests|Gem::AWSClientAuth.Tests|Gem::AWSCore.Editor.Tests) -L FRAMEWORK_googletest" + "CTEST_OPTIONS": "-E (Gem::EMotionFX.Editor.Tests|Gem::AWSClientAuth.Tests|Gem::AWSCore.Editor.Tests) -L FRAMEWORK_googletest --repeat until-pass:5" } }, "asset_profile": { From 85feef74dc990eb1feae981db2cd6c3be8e2b6c8 Mon Sep 17 00:00:00 2001 From: Mike Chang <62353586+amzn-changml@users.noreply.github.com> Date: Tue, 18 May 2021 22:24:14 -0700 Subject: [PATCH 59/69] Update Windows 2019 AMI v3 Label * Added new Windows v3 build label * New v3 AMI --- scripts/build/Platform/Windows/pipeline.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/build/Platform/Windows/pipeline.json b/scripts/build/Platform/Windows/pipeline.json index 5f10ccc7ae..622fa9d5ae 100644 --- a/scripts/build/Platform/Windows/pipeline.json +++ b/scripts/build/Platform/Windows/pipeline.json @@ -1,6 +1,6 @@ { "ENV": { - "NODE_LABEL": "windows-047e5cdf", + "NODE_LABEL": "windows-b3c8994f1", "LY_3RDPARTY_PATH": "C:/ly/3rdParty", "TIMEOUT": 30, "WORKSPACE": "D:/workspace", From bf4b65afdeec8f5fa1f72639193f23e90e65f5b9 Mon Sep 17 00:00:00 2001 From: amzn-sean <75276488+amzn-sean@users.noreply.github.com> Date: Wed, 19 May 2021 14:18:54 +0100 Subject: [PATCH 60/69] Fix crash in character component when using prefabs (#798) --- .../CharacterControllerComponent.cpp | 43 ++++++++++++++----- .../Components/CharacterControllerComponent.h | 7 +++ Gems/PhysX/Code/Source/Scene/PhysXScene.cpp | 1 + 3 files changed, 41 insertions(+), 10 deletions(-) diff --git a/Gems/PhysX/Code/Source/PhysXCharacters/Components/CharacterControllerComponent.cpp b/Gems/PhysX/Code/Source/PhysXCharacters/Components/CharacterControllerComponent.cpp index 473e9534cb..6fa69c7a24 100644 --- a/Gems/PhysX/Code/Source/PhysXCharacters/Components/CharacterControllerComponent.cpp +++ b/Gems/PhysX/Code/Source/PhysXCharacters/Components/CharacterControllerComponent.cpp @@ -73,7 +73,10 @@ namespace PhysX { } - CharacterControllerComponent::~CharacterControllerComponent() = default; + CharacterControllerComponent::~CharacterControllerComponent() + { + DisableController(); + } // AZ::Component void CharacterControllerComponent::Init() @@ -92,7 +95,7 @@ namespace PhysX void CharacterControllerComponent::Deactivate() { - DestroyController(); + DisableController(); Physics::CollisionFilteringRequestBus::Handler::BusDisconnect(); AzPhysics::SimulatedBodyComponentRequestsBus::Handler::BusDisconnect(); @@ -198,7 +201,7 @@ namespace PhysX void CharacterControllerComponent::DisablePhysics() { - DestroyController(); + DisableController(); } bool CharacterControllerComponent::IsPhysicsEnabled() const @@ -421,17 +424,32 @@ namespace PhysX AZ::TransformBus::EventResult(entityTranslation, GetEntityId(), &AZ::TransformBus::Events::GetWorldTranslation); m_characterConfig->m_position = entityTranslation; - if (auto* sceneInterface = AZ::Interface::Get()) + auto* sceneInterface = AZ::Interface::Get(); + if (sceneInterface != nullptr) { - AzPhysics::SimulatedBodyHandle bodyHandle = sceneInterface->AddSimulatedBody(defaultSceneHandle, m_characterConfig.get()); - m_controller = azdynamic_cast(sceneInterface->GetSimulatedBodyFromHandle(defaultSceneHandle, bodyHandle)); + m_controllerBodyHandle = sceneInterface->AddSimulatedBody(defaultSceneHandle, m_characterConfig.get()); + m_controller = azdynamic_cast( + sceneInterface->GetSimulatedBodyFromHandle(defaultSceneHandle, m_controllerBodyHandle)); } if (m_controller == nullptr) { AZ_Error("PhysX Character Controller Component", false, "Failed to create character controller."); return; } - + + if (sceneInterface != nullptr) + { + // if the scene removes this controller body, we should also clean up our resources. + m_onSimulatedBodyRemovedHandler = AzPhysics::SceneEvents::OnSimulationBodyRemoved::Handler( + [this]([[maybe_unused]] AzPhysics::SceneHandle sceneHandle, AzPhysics::SimulatedBodyHandle bodyHandle) { + if (bodyHandle == m_controllerBodyHandle) + { + DestroyController(); + } + }); + sceneInterface->RegisterSimulationBodyRemovedHandler(defaultSceneHandle, m_onSimulatedBodyRemovedHandler); + } + CharacterControllerRequestBus::Handler::BusConnect(GetEntityId()); m_preSimulateHandler = AzPhysics::SystemEvents::OnPresimulateEvent::Handler( @@ -447,7 +465,7 @@ namespace PhysX } } - void CharacterControllerComponent::DestroyController() + void CharacterControllerComponent::DisableController() { if (!IsPhysicsEnabled()) { @@ -460,10 +478,15 @@ namespace PhysX { sceneInterface->RemoveSimulatedBody(m_controller->m_sceneOwner, m_controller->m_bodyHandle); } - m_controller = nullptr; - m_preSimulateHandler.Disconnect(); + DestroyController(); + } + void CharacterControllerComponent::DestroyController() + { + m_controller = nullptr; + m_preSimulateHandler.Disconnect(); + m_onSimulatedBodyRemovedHandler.Disconnect(); CharacterControllerRequestBus::Handler::BusDisconnect(); } } // namespace PhysX diff --git a/Gems/PhysX/Code/Source/PhysXCharacters/Components/CharacterControllerComponent.h b/Gems/PhysX/Code/Source/PhysXCharacters/Components/CharacterControllerComponent.h index a7a1a92ad2..7c25312b72 100644 --- a/Gems/PhysX/Code/Source/PhysXCharacters/Components/CharacterControllerComponent.h +++ b/Gems/PhysX/Code/Source/PhysXCharacters/Components/CharacterControllerComponent.h @@ -131,7 +131,12 @@ namespace PhysX void ToggleCollisionLayer(const AZStd::string& layerName, AZ::Crc32 colliderTag, bool enabled) override; private: + // Creates the physics character controller in the current default physics scene. + // This will do nothing if the controller is already created. void CreateController(); + // Removes the physics character controller from the scene and will call DestroyController for clean up. + void DisableController(); + // Cleans up all references and events used with the physics character controller. void DestroyController(); void OnPreSimulate(float deltaTime); @@ -139,6 +144,8 @@ namespace PhysX AZStd::unique_ptr m_characterConfig; AZStd::shared_ptr m_shapeConfig; PhysX::CharacterController* m_controller = nullptr; + AzPhysics::SimulatedBodyHandle m_controllerBodyHandle = AzPhysics::InvalidSimulatedBodyHandle; AzPhysics::SystemEvents::OnPresimulateEvent::Handler m_preSimulateHandler; + AzPhysics::SceneEvents::OnSimulationBodyRemoved::Handler m_onSimulatedBodyRemovedHandler; }; } // namespace PhysX diff --git a/Gems/PhysX/Code/Source/Scene/PhysXScene.cpp b/Gems/PhysX/Code/Source/Scene/PhysXScene.cpp index 79aa767959..689ea47be7 100644 --- a/Gems/PhysX/Code/Source/Scene/PhysXScene.cpp +++ b/Gems/PhysX/Code/Source/Scene/PhysXScene.cpp @@ -489,6 +489,7 @@ namespace PhysX // Disable simulation on body (not signaling OnSimulationBodySimulationDisabled event) DisableSimulationOfBodyInternal(*simulatedBody.second); } + m_simulatedBodyRemovedEvent.Signal(m_sceneHandle, simulatedBody.second->m_bodyHandle); delete simulatedBody.second; } } From c49875fc37568a9cb6c7af52e1deb7b5fbcccc7f Mon Sep 17 00:00:00 2001 From: ibtehajn <81370835+ibtehajn@users.noreply.github.com> Date: Wed, 12 May 2021 11:08:23 +0100 Subject: [PATCH 61/69] Use author instead of committer in changelog computations Any commits created through the GitHub UI (e.g. commits created by merging PRs) usually assign GitHub itself as the ccommitter. This is expected behaviour, as the commit is applied by GitHub itself. However, for the purposes of changelog creation, showing the author (e.g. the person who clicked the merge button on the PR) is more useful. --- scripts/build/Jenkins/Jenkinsfile | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/scripts/build/Jenkins/Jenkinsfile b/scripts/build/Jenkins/Jenkinsfile index ae2779eba8..ad01d6ed05 100644 --- a/scripts/build/Jenkins/Jenkinsfile +++ b/scripts/build/Jenkins/Jenkinsfile @@ -190,6 +190,7 @@ def CheckoutBootstrapScripts(String branchName) { doGenerateSubmoduleConfigurations: false, extensions: [ [$class: 'PruneStaleBranch'], + [$class: 'AuthorInChangelog'], [$class: 'SparseCheckoutPaths', sparseCheckoutPaths: [ [ $class: 'SparseCheckoutPath', path: 'scripts/build/Jenkins/' ], [ $class: 'SparseCheckoutPath', path: 'scripts/build/bootstrap/' ], @@ -234,6 +235,7 @@ def CheckoutRepo(boolean disableSubmodules = false) { branches: scm.branches, extensions: [ [$class: 'PruneStaleBranch'], + [$class: 'AuthorInChangelog'], [$class: 'SubmoduleOption', disableSubmodules: disableSubmodules, recursiveSubmodules: true], [$class: 'CheckoutOption', timeout: 60] ], @@ -339,7 +341,10 @@ def TestMetrics(Map pipelineConfig, String workspace, String branchName, String checkout scm: [ $class: 'GitSCM', branches: [[name: '*/main']], - extensions: [[$class: 'RelativeTargetDirectory', relativeTargetDir: 'mars']], + extensions: [ + [$class: 'AuthorInChangelog'], + [$class: 'RelativeTargetDirectory', relativeTargetDir: 'mars'] + ], userRemoteConfigs: [[url: "${env.MARS_REPO}", name: 'mars', credentialsId: "${env.GITHUB_USER}"]] ] withCredentials([usernamePassword(credentialsId: "${env.SERVICE_USER}", passwordVariable: 'apitoken', usernameVariable: 'username')]) { From d8126d59c7082bceff7b31622e504fb3cc1e10c0 Mon Sep 17 00:00:00 2001 From: sconel Date: Wed, 19 May 2021 08:39:00 -0700 Subject: [PATCH 62/69] Moved to iterative clone instead of bulk, addressed PR feedback --- .../Spawnable/SpawnableEntitiesManager.cpp | 50 +++++++++++-------- .../Spawnable/SpawnableEntitiesManager.h | 4 +- 2 files changed, 31 insertions(+), 23 deletions(-) diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp index 39501ba0cd..fd838d5cb5 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp @@ -216,16 +216,11 @@ namespace AzFramework return clone; } - Spawnable::EntityList* SpawnableEntitiesManager::CloneAllEntities(const Spawnable::EntityList& entitiesTemplate, - AZ::SerializeContext& serializeContext) + AZ::Entity* SpawnableEntitiesManager::CloneSingleEntity(const AZ::Entity& entityTemplate, + EntityIdMap& templateToCloneEntityIdMap, AZ::SerializeContext& serializeContext) { - // Map keeps track of ids from template (spawnable) to clone (instance) - // Allowing patch ups of fields referring to entityIds outside of a given entity - EntityIdMap templateToCloneIdMap; - templateToCloneIdMap.reserve(entitiesTemplate.size()); - return AZ::IdUtils::Remapper::CloneObjectAndGenerateNewIdsAndFixRefs( - &entitiesTemplate, templateToCloneIdMap, &serializeContext); + &entityTemplate, templateToCloneEntityIdMap, &serializeContext); } bool SpawnableEntitiesManager::ProcessRequest(SpawnAllEntitiesCommand& request, AZ::SerializeContext& serializeContext) @@ -243,19 +238,25 @@ namespace AzFramework const Spawnable::EntityList& entitiesToSpawn = ticket.m_spawnable->GetEntities(); size_t entitiesToSpawnSize = entitiesToSpawn.size(); + // Map keeps track of ids from template (spawnable) to clone (instance) + // Allowing patch ups of fields referring to entityIds outside of a given entity + EntityIdMap templateToCloneEntityIdMap; + // Reserve buffers spawnedEntities.reserve(spawnedEntities.size() + entitiesToSpawnSize); - ticket.m_spawnedEntityIndices.reserve(ticket.m_spawnedEntityIndices.size() + entitiesToSpawnSize); - - // Clone the entities from Spawnable - Spawnable::EntityList* clonedEntities = CloneAllEntities(entitiesToSpawn, serializeContext); - AZ_Assert(clonedEntities != nullptr, "Failed to clone entities while processing a SpawnAllEntitiesCommand"); - - spawnedEntities.insert(spawnedEntities.end(), clonedEntities->begin(), clonedEntities->end()); + spawnedEntityIndices.reserve(spawnedEntityIndices.size() + entitiesToSpawnSize); + templateToCloneEntityIdMap.reserve(entitiesToSpawnSize); // Mark all indices as spawned for (size_t i = 0; i < entitiesToSpawnSize; ++i) { + const AZ::Entity& entityTemplate = *entitiesToSpawn[i]; + + AZ::Entity* clone = CloneSingleEntity(entityTemplate, templateToCloneEntityIdMap, serializeContext); + + AZ_Assert(clone != nullptr, "Failed to clone spawnable entity."); + + spawnedEntities.emplace_back(clone); spawnedEntityIndices.push_back(i); } @@ -422,16 +423,23 @@ namespace AzFramework // to load every, simply start over. ticket.m_spawnedEntityIndices.clear(); - // Clone the entities from Spawnable - Spawnable::EntityList* clonedEntities = CloneAllEntities(entities, serializeContext); - AZ_Assert(clonedEntities != nullptr, "Failed to clone entities while processing a SpawnAllEntitiesCommand"); + size_t entitiesToSpawnSize = entities.size(); - ticket.m_spawnedEntities.insert(ticket.m_spawnedEntities.end(), clonedEntities->begin(), clonedEntities->end()); + // Map keeps track of ids from template (spawnable) to clone (instance) + // Allowing patch ups of fields referring to entityIds outside of a given entity + EntityIdMap templateToCloneEntityIdMap; + templateToCloneEntityIdMap.reserve(entitiesToSpawnSize); // Mark all indices as spawned - size_t entitiesSize = entities.size(); - for (size_t i = 0; i < entitiesSize; ++i) + for (size_t i = 0; i < entitiesToSpawnSize; ++i) { + const AZ::Entity& entityTemplate = *entities[i]; + + AZ::Entity* clone = CloneSingleEntity(entityTemplate, templateToCloneEntityIdMap, serializeContext); + + AZ_Assert(clone != nullptr, "Failed to clone spawnable entity."); + + ticket.m_spawnedEntities.emplace_back(clone); ticket.m_spawnedEntityIndices.push_back(i); } } diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.h b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.h index 3def85170f..e20f58ac76 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.h +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.h @@ -147,8 +147,8 @@ namespace AzFramework AZ::Entity* SpawnSingleEntity(const AZ::Entity& entityTemplate, AZ::SerializeContext& serializeContext); - Spawnable::EntityList* CloneAllEntities(const Spawnable::EntityList& entitiesTemplate, - AZ::SerializeContext& serializeContext); + AZ::Entity* CloneSingleEntity(const AZ::Entity& entityTemplate, + EntityIdMap& templateToCloneEntityIdMap, AZ::SerializeContext& serializeContext); bool ProcessRequest(SpawnAllEntitiesCommand& request, AZ::SerializeContext& serializeContext); bool ProcessRequest(SpawnEntitiesCommand& request, AZ::SerializeContext& serializeContext); From 8dbcd9f199825e857e32dcb2dbcee5fa3422b113 Mon Sep 17 00:00:00 2001 From: amzn-sean <75276488+amzn-sean@users.noreply.github.com> Date: Wed, 19 May 2021 16:44:53 +0100 Subject: [PATCH 63/69] increase physics max frame time to 0.1 seconds (10fps) (#824) --- .../AzFramework/Physics/Configuration/SystemConfiguration.cpp | 2 +- .../AzFramework/Physics/Configuration/SystemConfiguration.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Code/Framework/AzFramework/AzFramework/Physics/Configuration/SystemConfiguration.cpp b/Code/Framework/AzFramework/AzFramework/Physics/Configuration/SystemConfiguration.cpp index 497d67bef1..cd250b71a9 100644 --- a/Code/Framework/AzFramework/AzFramework/Physics/Configuration/SystemConfiguration.cpp +++ b/Code/Framework/AzFramework/AzFramework/Physics/Configuration/SystemConfiguration.cpp @@ -21,7 +21,7 @@ namespace AzPhysics namespace { const float TimestepMin = 0.001f; //1000fps - const float TimestepMax = 0.05f; //20fps + const float TimestepMax = 0.1f; //10fps } AZ_CLASS_ALLOCATOR_IMPL(SystemConfiguration, AZ::SystemAllocator, 0); diff --git a/Code/Framework/AzFramework/AzFramework/Physics/Configuration/SystemConfiguration.h b/Code/Framework/AzFramework/AzFramework/Physics/Configuration/SystemConfiguration.h index 3de4dafd8c..0a00d627a7 100644 --- a/Code/Framework/AzFramework/AzFramework/Physics/Configuration/SystemConfiguration.h +++ b/Code/Framework/AzFramework/AzFramework/Physics/Configuration/SystemConfiguration.h @@ -34,7 +34,7 @@ namespace AzPhysics static constexpr float DefaultFixedTimestep = 0.0166667f; //! Value represents 1/60th or 60 FPS. - float m_maxTimestep = 1.f / 20.f; //!< Maximum fixed timestep in seconds to run the physics update. + float m_maxTimestep = 0.1f; //!< Maximum fixed timestep in seconds to run the physics update (10FPS). float m_fixedTimestep = DefaultFixedTimestep; //!< Timestep in seconds to run the physics update. See DefaultFixedTimestep. AZ::u64 m_raycastBufferSize = 32; //!< Maximum number of hits that will be returned from a raycast. From 4769664e9e660b40696168cedb65645b0ea12f20 Mon Sep 17 00:00:00 2001 From: sconel Date: Wed, 19 May 2021 08:50:14 -0700 Subject: [PATCH 64/69] Updating the loadAll flag after a SpawnAllCommand --- .../AzFramework/Spawnable/SpawnableEntitiesManager.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp index fd838d5cb5..0418bce3a2 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp @@ -260,6 +260,8 @@ namespace AzFramework spawnedEntityIndices.push_back(i); } + ticket.m_loadAll = true; + // Let other systems know about newly spawned entities for any pre-processing before adding to the scene/game context. if (request.m_preInsertionCallback) { From 31e5a312b4b6499d4047ba941ebfff9a4a146a22 Mon Sep 17 00:00:00 2001 From: sconel Date: Wed, 19 May 2021 08:58:12 -0700 Subject: [PATCH 65/69] Updated loadAll check to set to false if previous entities already spawned on ticket --- .../Spawnable/SpawnableEntitiesManager.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp index 0418bce3a2..8045766686 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp @@ -260,7 +260,16 @@ namespace AzFramework spawnedEntityIndices.push_back(i); } - ticket.m_loadAll = true; + // loadAll is true if every entity has been spawned only once + if (spawnedEntities.size() == entitiesToSpawnSize) + { + ticket.m_loadAll = true; + } + else + { + // Case where there were already spawns from a previous request + ticket.m_loadAll = false; + } // Let other systems know about newly spawned entities for any pre-processing before adding to the scene/game context. if (request.m_preInsertionCallback) From 3c3f3fa91e7723b64818e02281790b1d8933ff15 Mon Sep 17 00:00:00 2001 From: ibtehajn <81370835+ibtehajn@users.noreply.github.com> Date: Wed, 12 May 2021 11:04:54 +0100 Subject: [PATCH 66/69] Disable shallow checkout in initial setup step Performing a shallow checkout breaks changelog computation, which is required for accurate build failure notifications. --- scripts/build/Jenkins/Jenkinsfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/build/Jenkins/Jenkinsfile b/scripts/build/Jenkins/Jenkinsfile index ad01d6ed05..eb0778791d 100644 --- a/scripts/build/Jenkins/Jenkinsfile +++ b/scripts/build/Jenkins/Jenkinsfile @@ -196,7 +196,8 @@ def CheckoutBootstrapScripts(String branchName) { [ $class: 'SparseCheckoutPath', path: 'scripts/build/bootstrap/' ], [ $class: 'SparseCheckoutPath', path: 'scripts/build/Platform' ] ]], - [$class: 'CloneOption', depth: 1, noTags: false, reference: '', shallow: true] + // Shallow checkouts break changelog computation. Do not enable. + [$class: 'CloneOption', noTags: false, reference: '', shallow: false] ], submoduleCfg: [], userRemoteConfigs: scm.userRemoteConfigs From ad2d2381a4a350804b653e63fc2a069f86390562 Mon Sep 17 00:00:00 2001 From: Vincent Liu <5900509+onecent1101@users.noreply.github.com> Date: Wed, 19 May 2021 10:33:48 -0700 Subject: [PATCH 67/69] [SPEC-6713] Add common session interfaces and notifications (#773) --- .../Session/ISessionHandlingRequests.h | 78 +++++++ .../AzFramework/Session/ISessionRequests.cpp | 130 ++++++++++++ .../AzFramework/Session/ISessionRequests.h | 192 ++++++++++++++++++ .../AzFramework/Session/SessionConfig.cpp | 74 +++++++ .../AzFramework/Session/SessionConfig.h | 70 +++++++ .../Session/SessionNotifications.h | 47 +++++ .../AzFramework/azframework_files.cmake | 6 + 7 files changed, 597 insertions(+) create mode 100644 Code/Framework/AzFramework/AzFramework/Session/ISessionHandlingRequests.h create mode 100644 Code/Framework/AzFramework/AzFramework/Session/ISessionRequests.cpp create mode 100644 Code/Framework/AzFramework/AzFramework/Session/ISessionRequests.h create mode 100644 Code/Framework/AzFramework/AzFramework/Session/SessionConfig.cpp create mode 100644 Code/Framework/AzFramework/AzFramework/Session/SessionConfig.h create mode 100644 Code/Framework/AzFramework/AzFramework/Session/SessionNotifications.h diff --git a/Code/Framework/AzFramework/AzFramework/Session/ISessionHandlingRequests.h b/Code/Framework/AzFramework/AzFramework/Session/ISessionHandlingRequests.h new file mode 100644 index 0000000000..47388c56c3 --- /dev/null +++ b/Code/Framework/AzFramework/AzFramework/Session/ISessionHandlingRequests.h @@ -0,0 +1,78 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#pragma once + +#include + +namespace AzFramework +{ + //! SessionConnectionConfig + //! The properties for handling join session request. + struct SessionConnectionConfig + { + // A unique identifier for registered player in session. + AZStd::string m_playerSessionId; + + // The DNS identifier assigned to the instance that is running the session. + AZStd::string m_dnsName; + + // The IP address of the session. + AZStd::string m_ipAddress; + + // The port number for the session. + uint16_t m_port; + }; + + //! SessionConnectionConfig + //! The properties for handling player connect/disconnect + struct PlayerConnectionConfig + { + // A unique identifier for player connection. + uint32_t m_playerConnectionId; + + // A unique identifier for registered player in session. + AZStd::string m_playerSessionId; + }; + + //! ISessionHandlingClientRequests + //! The session handling events to invoke multiplayer component handle the work on client side + class ISessionHandlingClientRequests + { + public: + // Handle the player join session process + // @param sessionConnectionConfig The required properties to handle the player join session process + // @return The result of player join session process + virtual bool HandlePlayerJoinSession(const SessionConnectionConfig& sessionConnectionConfig) = 0; + + // Handle the player leave session process + virtual void HandlePlayerLeaveSession() = 0; + }; + + //! ISessionHandlingServerRequests + //! The session handling events to invoke server provider handle the work on server side + class ISessionHandlingServerRequests + { + public: + // Handle the destroy session process + virtual void HandleDestroySession() = 0; + + // Validate the player join session process + // @param playerConnectionConfig The required properties to validate the player join session process + // @return The result of player join session validation + virtual bool ValidatePlayerJoinSession(const PlayerConnectionConfig& playerConnectionConfig) = 0; + + // Handle the player leave session process + // @param playerConnectionConfig The required properties to handle the player leave session process + virtual void HandlePlayerLeaveSession(const PlayerConnectionConfig& playerConnectionConfig) = 0; + }; +} // namespace AzFramework diff --git a/Code/Framework/AzFramework/AzFramework/Session/ISessionRequests.cpp b/Code/Framework/AzFramework/AzFramework/Session/ISessionRequests.cpp new file mode 100644 index 0000000000..4eb42ab815 --- /dev/null +++ b/Code/Framework/AzFramework/AzFramework/Session/ISessionRequests.cpp @@ -0,0 +1,130 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#include +#include +#include +#include + +namespace AzFramework +{ + void CreateSessionRequest::Reflect(AZ::ReflectContext* context) + { + if (auto serializeContext = azrtti_cast(context)) + { + serializeContext->Class() + ->Version(0) + ->Field("creatorId", &CreateSessionRequest::m_creatorId) + ->Field("sessionProperties", &CreateSessionRequest::m_sessionProperties) + ->Field("sessionName", &CreateSessionRequest::m_sessionName) + ->Field("maxPlayer", &CreateSessionRequest::m_maxPlayer) + ; + + if (AZ::EditContext* editContext = serializeContext->GetEditContext()) + { + editContext->Class("CreateSessionRequest", "The container for CreateSession request parameters") + ->ClassElement(AZ::Edit::ClassElements::EditorData, "") + ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly) + ->DataElement(AZ::Edit::UIHandlers::Default, &CreateSessionRequest::m_creatorId, + "CreatorId", "A unique identifier for a player or entity creating the session") + ->DataElement(AZ::Edit::UIHandlers::Default, &CreateSessionRequest::m_sessionProperties, + "SessionProperties", "A collection of custom properties for a session") + ->DataElement(AZ::Edit::UIHandlers::Default, &CreateSessionRequest::m_sessionName, + "SessionName", "A descriptive label that is associated with a session") + ->DataElement(AZ::Edit::UIHandlers::Default, &CreateSessionRequest::m_maxPlayer, + "MaxPlayer", "The maximum number of players that can be connected simultaneously to the session") + ; + } + } + } + + void SearchSessionsRequest::Reflect(AZ::ReflectContext* context) + { + if (auto serializeContext = azrtti_cast(context)) + { + serializeContext->Class() + ->Version(0) + ->Field("filterExpression", &SearchSessionsRequest::m_filterExpression) + ->Field("sortExpression", &SearchSessionsRequest::m_sortExpression) + ->Field("maxResult", &SearchSessionsRequest::m_maxResult) + ->Field("nextToken", &SearchSessionsRequest::m_nextToken) + ; + + if (AZ::EditContext* editContext = serializeContext->GetEditContext()) + { + editContext->Class("SearchSessionsRequest", "The container for SearchSessions request parameters") + ->ClassElement(AZ::Edit::ClassElements::EditorData, "") + ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly) + ->DataElement(AZ::Edit::UIHandlers::Default, &SearchSessionsRequest::m_filterExpression, + "FilterExpression", "String containing the search criteria for the session search") + ->DataElement(AZ::Edit::UIHandlers::Default, &SearchSessionsRequest::m_sortExpression, + "SortExpression", "Instructions on how to sort the search results") + ->DataElement(AZ::Edit::UIHandlers::Default, &SearchSessionsRequest::m_maxResult, + "MaxResult", "The maximum number of results to return") + ->DataElement(AZ::Edit::UIHandlers::Default, &SearchSessionsRequest::m_nextToken, + "NextToken", "A token that indicates the start of the next sequential page of results") + ; + } + } + } + + void SearchSessionsResponse::Reflect(AZ::ReflectContext* context) + { + if (auto serializeContext = azrtti_cast(context)) + { + serializeContext->Class() + ->Version(0) + ->Field("sessionConfigs", &SearchSessionsResponse::m_sessionConfigs) + ->Field("nextToken", &SearchSessionsResponse::m_nextToken) + ; + + if (AZ::EditContext* editContext = serializeContext->GetEditContext()) + { + editContext->Class("SearchSessionsResponse", "The container for SearchSession request results") + ->ClassElement(AZ::Edit::ClassElements::EditorData, "") + ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly) + ->DataElement(AZ::Edit::UIHandlers::Default, &SearchSessionsResponse::m_sessionConfigs, + "SessionConfigs", "A collection of sessions that match the search criteria and sorted in specific order") + ->DataElement(AZ::Edit::UIHandlers::Default, &SearchSessionsResponse::m_nextToken, + "NextToken", "A token that indicates the start of the next sequential page of results") + ; + } + } + } + + void JoinSessionRequest::Reflect(AZ::ReflectContext* context) + { + if (auto serializeContext = azrtti_cast(context)) + { + serializeContext->Class() + ->Version(0) + ->Field("sessionId", &JoinSessionRequest::m_sessionId) + ->Field("playerId", &JoinSessionRequest::m_playerId) + ->Field("playerData", &JoinSessionRequest::m_playerData) + ; + + if (AZ::EditContext* editContext = serializeContext->GetEditContext()) + { + editContext->Class("JoinSessionRequest", "The container for JoinSession request parameters") + ->ClassElement(AZ::Edit::ClassElements::EditorData, "") + ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly) + ->DataElement(AZ::Edit::UIHandlers::Default, &JoinSessionRequest::m_sessionId, + "SessionId", "A unique identifier for the session") + ->DataElement(AZ::Edit::UIHandlers::Default, &JoinSessionRequest::m_playerId, + "PlayerId", "A unique identifier for a player. Player IDs are developer-defined") + ->DataElement(AZ::Edit::UIHandlers::Default, &JoinSessionRequest::m_playerData, + "PlayerData", "Developer-defined information related to a player") + ; + } + } + } +} // namespace AzFramework diff --git a/Code/Framework/AzFramework/AzFramework/Session/ISessionRequests.h b/Code/Framework/AzFramework/AzFramework/Session/ISessionRequests.h new file mode 100644 index 0000000000..9d21a7f282 --- /dev/null +++ b/Code/Framework/AzFramework/AzFramework/Session/ISessionRequests.h @@ -0,0 +1,192 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#pragma once + +#include +#include +#include +#include +#include + +namespace AzFramework +{ + struct SessionConfig; + + //! CreateSessionRequest + //! The container for CreateSession request parameters. + struct CreateSessionRequest + { + AZ_RTTI(CreateSessionRequest, "{E39C2A45-89C9-4CFB-B337-9734DC798930}"); + static void Reflect(AZ::ReflectContext* context); + + CreateSessionRequest() = default; + virtual ~CreateSessionRequest() = default; + + // A unique identifier for a player or entity creating the session. + AZStd::string m_creatorId; + + // A collection of custom properties for a session. + AZStd::unordered_map m_sessionProperties; + + // A descriptive label that is associated with a session. + AZStd::string m_sessionName; + + // The maximum number of players that can be connected simultaneously to the session. + uint64_t m_maxPlayer; + }; + + //! SearchSessionsRequest + //! The container for SearchSessions request parameters. + struct SearchSessionsRequest + { + AZ_RTTI(SearchSessionsRequest, "{B49207A8-8549-4ADB-B7D9-D7A4932F9B4B}"); + static void Reflect(AZ::ReflectContext* context); + + SearchSessionsRequest() = default; + virtual ~SearchSessionsRequest() = default; + + // String containing the search criteria for the session search. If no filter expression is included, the request returns results + // for all active sessions. + AZStd::string m_filterExpression; + + // Instructions on how to sort the search results. If no sort expression is included, the request returns results in random order. + AZStd::string m_sortExpression; + + // The maximum number of results to return. + uint8_t m_maxResult; + + // A token that indicates the start of the next sequential page of results. + AZStd::string m_nextToken; + }; + + //! SearchSessionsResponse + //! The container for SearchSession request results. + struct SearchSessionsResponse + { + AZ_RTTI(SearchSessionsResponse, "{F93DE7DC-D381-4E08-8A3B-0B08F7C38714}"); + static void Reflect(AZ::ReflectContext* context); + + SearchSessionsResponse() = default; + virtual ~SearchSessionsResponse() = default; + + // A collection of sessions that match the search criteria and sorted in specific order. + AZStd::vector m_sessionConfigs; + + // A token that indicates the start of the next sequential page of results. + AZStd::string m_nextToken; + }; + + //! JoinSessionRequest + //! The container for JoinSession request parameters. + struct JoinSessionRequest + { + AZ_RTTI(JoinSessionRequest, "{519769E8-3CDE-4385-A0D7-24DBB3685657}"); + static void Reflect(AZ::ReflectContext* context); + + JoinSessionRequest() = default; + virtual ~JoinSessionRequest() = default; + + // A unique identifier for the session. + AZStd::string m_sessionId; + + // A unique identifier for a player. Player IDs are developer-defined. + AZStd::string m_playerId; + + // Developer-defined information related to a player. + AZStd::string m_playerData; + }; + + //! ISessionRequests + //! Pure virtual session interface class to abstract the details of session handling from application code. + class ISessionRequests + { + public: + AZ_RTTI(ISessionRequests, "{D6C41A71-DD8D-47FE-8515-FAF90670AE2F}"); + + ISessionRequests() = default; + virtual ~ISessionRequests() = default; + + // Create a session for players to find and join. + // @param createSessionRequest The request of CreateSession operation + // @return The request id if session creation request succeeds; empty if it fails + virtual AZStd::string CreateSession(const CreateSessionRequest& createSessionRequest) = 0; + + // Retrieve all active sessions that match the given search criteria and sorted in specific order. + // @param searchSessionsRequest The request of SearchSessions operation + // @return The response of SearchSessions operation + virtual SearchSessionsResponse SearchSessions(const SearchSessionsRequest& searchSessionsRequest) const = 0; + + // Reserve an open player slot in a session, and perform connection from client to server. + // @param joinSessionRequest The request of JoinSession operation + // @return True if joining session succeeds; False otherwise + virtual bool JoinSession(const JoinSessionRequest& joinSessionRequest) = 0; + + // Disconnect player from session. + virtual void LeaveSession() = 0; + }; + + //! ISessionAsyncRequests + //! Async version of ISessionRequests + class ISessionAsyncRequests + { + public: + AZ_RTTI(ISessionAsyncRequests, "{471542AF-96B9-4930-82FE-242A4E68432D}"); + + ISessionAsyncRequests() = default; + virtual ~ISessionAsyncRequests() = default; + + // CreateSession Async + // @param createSessionRequest The request of CreateSession operation + virtual void CreateSessionAsync(const CreateSessionRequest& createSessionRequest) = 0; + + // SearchSessions Async + // @param searchSessionsRequest The request of SearchSessions operation + virtual void SearchSessionsAsync(const SearchSessionsRequest& searchSessionsRequest) const = 0; + + // JoinSession Async + // @param joinSessionRequest The request of JoinSession operation + virtual void JoinSessionAsync(const JoinSessionRequest& joinSessionRequest) = 0; + + // LeaveSession Async + virtual void LeaveSessionAsync() = 0; + }; + + //! SessionAsyncRequestNotifications + //! The notifications correspond to session async requests + class SessionAsyncRequestNotifications + : public AZ::EBusTraits + { + public: + ////////////////////////////////////////////////////////////////////////// + // EBusTraits overrides + static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple; + static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single; + ////////////////////////////////////////////////////////////////////////// + + // OnCreateSessionAsyncComplete is fired once CreateSessionAsync completes + // @param createSessionResponse The request id if session creation request succeeds; empty if it fails + virtual void OnCreateSessionAsyncComplete(const AZStd::string& createSessionReponse) = 0; + + // OnSearchSessionsAsyncComplete is fired once SearchSessionsAsync completes + // @param searchSessionsResponse The response of SearchSessions call + virtual void OnSearchSessionsAsyncComplete(const SearchSessionsResponse& searchSessionsResponse) = 0; + + // OnJoinSessionAsyncComplete is fired once JoinSessionAsync completes + // @param joinSessionsResponse True if joining session succeeds; False otherwise + virtual void OnJoinSessionAsyncComplete(bool joinSessionsResponse) = 0; + + // OnLeaveSessionAsyncComplete is fired once LeaveSessionAsync completes + virtual void OnLeaveSessionAsyncComplete() = 0; + }; + using SessionAsyncRequestNotificationBus = AZ::EBus; +} // namespace AzFramework diff --git a/Code/Framework/AzFramework/AzFramework/Session/SessionConfig.cpp b/Code/Framework/AzFramework/AzFramework/Session/SessionConfig.cpp new file mode 100644 index 0000000000..12c5163031 --- /dev/null +++ b/Code/Framework/AzFramework/AzFramework/Session/SessionConfig.cpp @@ -0,0 +1,74 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#include +#include +#include + +namespace AzFramework +{ + void SessionConfig::Reflect(AZ::ReflectContext* context) + { + if (auto serializeContext = azrtti_cast(context)) + { + serializeContext->Class() + ->Version(0) + ->Field("creationTime", &SessionConfig::m_creationTime) + ->Field("terminationTime", &SessionConfig::m_terminationTime) + ->Field("creatorId", &SessionConfig::m_creatorId) + ->Field("sessionProperties", &SessionConfig::m_sessionProperties) + ->Field("sessionId", &SessionConfig::m_sessionId) + ->Field("sessionName", &SessionConfig::m_sessionName) + ->Field("dnsName", &SessionConfig::m_dnsName) + ->Field("ipAddress", &SessionConfig::m_ipAddress) + ->Field("port", &SessionConfig::m_port) + ->Field("maxPlayer", &SessionConfig::m_maxPlayer) + ->Field("currentPlayer", &SessionConfig::m_currentPlayer) + ->Field("status", &SessionConfig::m_status) + ->Field("statusReason", &SessionConfig::m_statusReason) + ; + + if (AZ::EditContext* editContext = serializeContext->GetEditContext()) + { + editContext->Class("SessionConfig", "Properties describing a session") + ->ClassElement(AZ::Edit::ClassElements::EditorData, "") + ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly) + ->DataElement(AZ::Edit::UIHandlers::Default, &AzFramework::SessionConfig::m_creationTime, + "CreationTime", "A time stamp indicating when this session was created. Format is a number expressed in Unix time as milliseconds.") + ->DataElement(AZ::Edit::UIHandlers::Default, &AzFramework::SessionConfig::m_terminationTime, + "TerminationTime", "A time stamp indicating when this data object was terminated. Same format as creation time.") + ->DataElement(AZ::Edit::UIHandlers::Default, &AzFramework::SessionConfig::m_creatorId, + "CreatorId", "A unique identifier for a player or entity creating the session.") + ->DataElement(AZ::Edit::UIHandlers::Default, &AzFramework::SessionConfig::m_sessionProperties, + "SessionProperties", "A collection of custom properties for a session.") + ->DataElement(AZ::Edit::UIHandlers::Default, &AzFramework::SessionConfig::m_sessionId, + "SessionId", "A unique identifier for the session.") + ->DataElement(AZ::Edit::UIHandlers::Default, &AzFramework::SessionConfig::m_sessionName, + "SessionName", "A descriptive label that is associated with a session.") + ->DataElement(AZ::Edit::UIHandlers::Default, &AzFramework::SessionConfig::m_dnsName, + "DnsName", "The DNS identifier assigned to the instance that is running the session.") + ->DataElement(AZ::Edit::UIHandlers::Default, &AzFramework::SessionConfig::m_ipAddress, + "IpAddress", "The IP address of the session.") + ->DataElement(AZ::Edit::UIHandlers::Default, &AzFramework::SessionConfig::m_port, + "Port", "The port number for the session.") + ->DataElement(AZ::Edit::UIHandlers::Default, &AzFramework::SessionConfig::m_maxPlayer, + "MaxPlayer", "The maximum number of players that can be connected simultaneously to the session.") + ->DataElement(AZ::Edit::UIHandlers::Default, &AzFramework::SessionConfig::m_currentPlayer, + "CurrentPlayer", "Number of players currently in the session.") + ->DataElement(AZ::Edit::UIHandlers::Default, &AzFramework::SessionConfig::m_status, + "Status", "Current status of the session.") + ->DataElement(AZ::Edit::UIHandlers::Default, &AzFramework::SessionConfig::m_statusReason, + "StatusReason", "Provides additional information about session status."); + } + } + } +} // namespace AzFramework diff --git a/Code/Framework/AzFramework/AzFramework/Session/SessionConfig.h b/Code/Framework/AzFramework/AzFramework/Session/SessionConfig.h new file mode 100644 index 0000000000..22d1c9e875 --- /dev/null +++ b/Code/Framework/AzFramework/AzFramework/Session/SessionConfig.h @@ -0,0 +1,70 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#pragma once + +#include +#include +#include + +namespace AzFramework +{ + //! SessionConfig + //! Properties describing a session. + struct SessionConfig + { + AZ_RTTI(SessionConfig, "{992DD4BE-8BA5-4071-8818-B99FD2952086}"); + static void Reflect(AZ::ReflectContext* context); + + SessionConfig() = default; + virtual ~SessionConfig() = default; + + // A time stamp indicating when this session was created. Format is a number expressed in Unix time as milliseconds. + uint64_t m_creationTime; + + // A time stamp indicating when this data object was terminated. Same format as creation time. + uint64_t m_terminationTime; + + // A unique identifier for a player or entity creating the session. + AZStd::string m_creatorId; + + // A collection of custom properties for a session. + AZStd::unordered_map m_sessionProperties; + + // A unique identifier for the session. + AZStd::string m_sessionId; + + // A descriptive label that is associated with a session. + AZStd::string m_sessionName; + + // The DNS identifier assigned to the instance that is running the session. + AZStd::string m_dnsName; + + // The IP address of the session. + AZStd::string m_ipAddress; + + // The port number for the session. + uint16_t m_port; + + // The maximum number of players that can be connected simultaneously to the session. + uint64_t m_maxPlayer; + + // Number of players currently in the session. + uint64_t m_currentPlayer; + + // Current status of the session. + AZStd::string m_status; + + // Provides additional information about session status. + AZStd::string m_statusReason; + }; +} // namespace AzFramework diff --git a/Code/Framework/AzFramework/AzFramework/Session/SessionNotifications.h b/Code/Framework/AzFramework/AzFramework/Session/SessionNotifications.h new file mode 100644 index 0000000000..a61c995db7 --- /dev/null +++ b/Code/Framework/AzFramework/AzFramework/Session/SessionNotifications.h @@ -0,0 +1,47 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#pragma once + +#include + +namespace AzFramework +{ + struct SessionConfig; + + //! SessionNotifications + //! The session notifications to listen for performing required operations + class SessionNotifications + : public AZ::EBusTraits + { + public: + ////////////////////////////////////////////////////////////////////////// + // EBusTraits overrides + static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple; + static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single; + ////////////////////////////////////////////////////////////////////////// + + // OnSessionHealthCheck is fired in health check process + // @return The result of all OnSessionHealthCheck + virtual bool OnSessionHealthCheck() = 0; + + // OnCreateSessionBegin is fired at the beginning of session creation + // @param sessionConfig The properties to describe a session + // @return The result of all OnCreateSessionBegin notifications + virtual bool OnCreateSessionBegin(const SessionConfig& sessionConfig) = 0; + + // OnDestroySessionBegin is fired at the beginning of session termination + // @return The result of all OnDestroySessionBegin notifications + virtual bool OnDestroySessionBegin() = 0; + }; + using SessionNotificationBus = AZ::EBus; +} // namespace AzFramework diff --git a/Code/Framework/AzFramework/AzFramework/azframework_files.cmake b/Code/Framework/AzFramework/AzFramework/azframework_files.cmake index 8cff479ec8..13dff43f68 100644 --- a/Code/Framework/AzFramework/AzFramework/azframework_files.cmake +++ b/Code/Framework/AzFramework/AzFramework/azframework_files.cmake @@ -188,6 +188,12 @@ set(FILES Script/ScriptDebugMsgReflection.h Script/ScriptRemoteDebugging.cpp Script/ScriptRemoteDebugging.h + Session/ISessionHandlingRequests.h + Session/ISessionRequests.cpp + Session/ISessionRequests.h + Session/SessionConfig.cpp + Session/SessionConfig.h + Session/SessionNotifications.h StreamingInstall/StreamingInstall.h StreamingInstall/StreamingInstall.cpp StreamingInstall/StreamingInstallRequests.h From 659998cd26bb501eb94ea28df9e515fead1ef9fa Mon Sep 17 00:00:00 2001 From: amzn-hdoke <61443753+hdoke@users.noreply.github.com> Date: Wed, 19 May 2021 11:07:53 -0700 Subject: [PATCH 68/69] AWSI Gems CDK Automation fixtures (#707) * Adding AWS automation tests cdk and resource mapping fixtures * Add aws_utils fixture * Update assume role arn * Get region and account id from aws_utils fixture * Adding NodeJS and AWS CDK as install dependencies * Fixing missing copyright headers * Add missing copyright header * Remove cdk and node install from build folder * Remove unused script canvas file * Uncomment code, remove unused script canvas * Add region to aws_utils fixture * Adding AWS gems to automated testing for all platforms * Re-exporting ClientAuth level * Add PythonTests/AWS CMakeLists.txt --- .../Config/aws_resource_mappings.json | 6 + .../Gem/Code/runtime_dependencies.cmake | 3 + .../Gem/Code/tool_dependencies.cmake | 3 + .../Gem/PythonTests/AWS/CMakeLists.txt | 31 + .../Gem/PythonTests/AWS/Windows/cdk/cdk.py | 155 ++ .../client_auth/test_anonymous_credentials.py | 78 + .../AWS/Windows/resource_mappings/__init__.py | 10 + .../resource_mappings/resource_mappings.py | 137 + .../Gem/PythonTests/AWS/__init__.py | 11 + .../Gem/PythonTests/AWS/common/aws_utils.py | 82 + .../Gem/PythonTests/CMakeLists.txt | 5 +- .../Levels/AWS/ClientAuth/ClientAuth.ly | 3 + .../ConitoAnonymousAuthorization.scriptcanvas | 2313 +++++++++++++++++ .../AWS/ClientAuth/LevelData/Environment.xml | 1 + .../AWS/ClientAuth/LevelData/TimeOfDay.xml | 1 + .../Levels/AWS/ClientAuth/filelist.xml | 6 + .../Levels/AWS/ClientAuth/level.pak | 3 + .../Levels/AWS/ClientAuth/tags.txt | 12 + .../Registry/awscoreconfiguration.setreg | 10 + 19 files changed, 2869 insertions(+), 1 deletion(-) create mode 100644 AutomatedTesting/Config/aws_resource_mappings.json create mode 100644 AutomatedTesting/Gem/PythonTests/AWS/CMakeLists.txt create mode 100644 AutomatedTesting/Gem/PythonTests/AWS/Windows/cdk/cdk.py create mode 100644 AutomatedTesting/Gem/PythonTests/AWS/Windows/client_auth/test_anonymous_credentials.py create mode 100644 AutomatedTesting/Gem/PythonTests/AWS/Windows/resource_mappings/__init__.py create mode 100644 AutomatedTesting/Gem/PythonTests/AWS/Windows/resource_mappings/resource_mappings.py create mode 100644 AutomatedTesting/Gem/PythonTests/AWS/__init__.py create mode 100644 AutomatedTesting/Gem/PythonTests/AWS/common/aws_utils.py create mode 100644 AutomatedTesting/Levels/AWS/ClientAuth/ClientAuth.ly create mode 100644 AutomatedTesting/Levels/AWS/ClientAuth/ConitoAnonymousAuthorization.scriptcanvas create mode 100644 AutomatedTesting/Levels/AWS/ClientAuth/LevelData/Environment.xml create mode 100644 AutomatedTesting/Levels/AWS/ClientAuth/LevelData/TimeOfDay.xml create mode 100644 AutomatedTesting/Levels/AWS/ClientAuth/filelist.xml create mode 100644 AutomatedTesting/Levels/AWS/ClientAuth/level.pak create mode 100644 AutomatedTesting/Levels/AWS/ClientAuth/tags.txt create mode 100644 AutomatedTesting/Registry/awscoreconfiguration.setreg diff --git a/AutomatedTesting/Config/aws_resource_mappings.json b/AutomatedTesting/Config/aws_resource_mappings.json new file mode 100644 index 0000000000..03a611b749 --- /dev/null +++ b/AutomatedTesting/Config/aws_resource_mappings.json @@ -0,0 +1,6 @@ +{ + "AWSResourceMappings": {}, + "AccountId": "", + "Region": "us-west-2", + "Version": "1.0.0" +} \ No newline at end of file diff --git a/AutomatedTesting/Gem/Code/runtime_dependencies.cmake b/AutomatedTesting/Gem/Code/runtime_dependencies.cmake index 280c25bcf7..33c2bf8d5f 100644 --- a/AutomatedTesting/Gem/Code/runtime_dependencies.cmake +++ b/AutomatedTesting/Gem/Code/runtime_dependencies.cmake @@ -45,4 +45,7 @@ set(GEM_DEPENDENCIES Gem::Atom_AtomBridge Gem::NvCloth Gem::Blast + Gem::AWSCore + Gem::AWSClientAuth + Gem::AWSMetrics ) diff --git a/AutomatedTesting/Gem/Code/tool_dependencies.cmake b/AutomatedTesting/Gem/Code/tool_dependencies.cmake index fc50707c12..d4a49bfad5 100644 --- a/AutomatedTesting/Gem/Code/tool_dependencies.cmake +++ b/AutomatedTesting/Gem/Code/tool_dependencies.cmake @@ -55,4 +55,7 @@ set(GEM_DEPENDENCIES Gem::Atom_AtomBridge.Editor Gem::NvCloth.Editor Gem::Blast.Editor + Gem::AWSCore.Editor + Gem::AWSClientAuth + Gem::AWSMetrics ) diff --git a/AutomatedTesting/Gem/PythonTests/AWS/CMakeLists.txt b/AutomatedTesting/Gem/PythonTests/AWS/CMakeLists.txt new file mode 100644 index 0000000000..b406ea77de --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/AWS/CMakeLists.txt @@ -0,0 +1,31 @@ +# +# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +# its licensors. +# +# For complete copyright and license terms please see the LICENSE at the root of this +# distribution (the "License"). All use of this software is governed by the License, +# or, if provided, by the license below or the license accompanying this file. Do not +# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# + +################################################################################ +# AWS Automated Tests +# Runs AWS Gems automation tests. +################################################################################ + +if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS) + # Enable after installing NodeJS and CDK on jenkins Windows AMI. + #ly_add_pytest( + # NAME AutomatedTesting::AWSTests + # TEST_SUITE periodic + # TEST_SERIAL + # PATH ${CMAKE_CURRENT_LIST_DIR}/AWS/${PAL_PLATFORM_NAME}/ + # RUNTIME_DEPENDENCIES + # Legacy::Editor + # AZ::AssetProcessor + # AutomatedTesting.Assets + # COMPONENT + # AWS + #) +endif() diff --git a/AutomatedTesting/Gem/PythonTests/AWS/Windows/cdk/cdk.py b/AutomatedTesting/Gem/PythonTests/AWS/Windows/cdk/cdk.py new file mode 100644 index 0000000000..455b3f94cb --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/AWS/Windows/cdk/cdk.py @@ -0,0 +1,155 @@ +""" +All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +its licensors. + +For complete copyright and license terms please see the LICENSE at the root of this +distribution (the "License"). All use of this software is governed by the License, +or, if provided, by the license below or the license accompanying this file. Do not +remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +""" + +import os +import pytest +import boto3 + +import ly_test_tools.environment.process_utils as process_utils +from typing import List + + +class Cdk: + """ + Cdk class that provides methods to run cdk application commands. + Expects system to have NodeJS, AWS CLI and CDK installed globally and have their paths setup as env variables. + """ + def __init__(self, cdk_path: str, project: str, account_id: str, + workspace: pytest.fixture, session: boto3.session.Session): + """ + :param cdk_path: Path where cdk app.py is stored. + :param project: Project name used for cdk project name env variable. + :param account_id: AWS account id to use with cdk application. + :param workspace: ly_test_tools workspace fixture. + """ + self._cdk_env = os.environ.copy() + self._cdk_env['O3DE_AWS_PROJECT_NAME'] = project + self._cdk_env['O3DE_AWS_DEPLOY_REGION'] = session.region_name + self._cdk_env['O3DE_AWS_DEPLOY_ACCOUNT'] = account_id + self._cdk_env['PATH'] = f'{workspace.paths.engine_root()}\\python;' + self._cdk_env['PATH'] + + credentials = session.get_credentials().get_frozen_credentials() + self._cdk_env['AWS_ACCESS_KEY_ID'] = credentials.access_key + self._cdk_env['AWS_SECRET_ACCESS_KEY'] = credentials.secret_key + self._cdk_env['AWS_SESSION_TOKEN'] = credentials.token + self._stacks = [] + self._cdk_path = cdk_path + + output = process_utils.check_output( + 'python -m pip install -r requirements.txt', + cwd=self._cdk_path, + env=self._cdk_env, + shell=True) + + def list(self) -> List[str]: + """ + lists cdk stack names + :return List of cdk stack names + """ + + if not self._cdk_path: + return [] + + list_cdk_application_cmd = ['cdk', 'list'] + output = process_utils.check_output( + list_cdk_application_cmd, + cwd=self._cdk_path, + env=self._cdk_env, + shell=True) + + return output.splitlines() + + def synthesize(self) -> None: + """ + Synthesizes all cdk stacks + """ + if not self._cdk_path: + return + + list_cdk_application_cmd = ['cdk', 'synth'] + + process_utils.check_output( + list_cdk_application_cmd, + cwd=self._cdk_path, + env=self._cdk_env, + shell=True) + + def deploy(self, context_variable: str = '') -> List[str]: + """ + Deploys all the CDK stacks. + :param context_variable: Context variable for enabling optional features. + :return List of deployed stack arns. + """ + if not self._cdk_path: + return [] + + deploy_cdk_application_cmd = ['cdk', 'deploy', '--require-approval', 'never'] + if context_variable: + deploy_cdk_application_cmd.extend(['-c', f'{context_variable}']) + + output = process_utils.check_output( + deploy_cdk_application_cmd, + cwd=self._cdk_path, + env=self._cdk_env, + shell=True) + + stacks = [] + for line in output.splitlines(): + line_sections = line.split('/') + assert len(line_sections), 3 + stacks.append(line.split('/')[-2]) + + return stacks + + def destroy(self) -> None: + """ + Destroys the cdk application. + """ + destroy_cdk_application_cmd = ['cdk', 'destroy', '-f'] + process_utils.check_output( + destroy_cdk_application_cmd, + cwd=self._cdk_path, + env=self._cdk_env, + shell=True) + + self._stacks = [] + self._cdk_path = '' + + +@pytest.fixture(scope='function') +def cdk( + request: pytest.fixture, + project: str, + feature_name: str, + workspace: pytest.fixture, + aws_utils: pytest.fixture, + destroy_stacks_on_teardown: bool = True) -> Cdk: + """ + Fixture for setting up a Cdk + :param request: _pytest.fixtures.SubRequest class that handles getting + a pytest fixture from a pytest function/fixture. + :param project: Project name used for cdk project name env variable. + :param feature_name: Feature gem name to expect cdk folder in. + :param workspace: ly_test_tools workspace fixture. + :param aws_utils: aws_utils fixture. + :param destroy_stacks_on_teardown: option to control calling destroy ot the end of test. + :return Cdk class object. + """ + + cdk_path = f'{workspace.paths.engine_root()}/Gems/{feature_name}/cdk' + cdk_obj = Cdk(cdk_path, project, aws_utils.assume_account_id(), workspace, aws_utils.assume_session()) + + def teardown(): + if destroy_stacks_on_teardown: + cdk_obj.destroy() + request.addfinalizer(teardown) + + return cdk_obj diff --git a/AutomatedTesting/Gem/PythonTests/AWS/Windows/client_auth/test_anonymous_credentials.py b/AutomatedTesting/Gem/PythonTests/AWS/Windows/client_auth/test_anonymous_credentials.py new file mode 100644 index 0000000000..5997701870 --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/AWS/Windows/client_auth/test_anonymous_credentials.py @@ -0,0 +1,78 @@ +""" +All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +its licensors. +For complete copyright and license terms please see the LICENSE at the root of this +distribution (the "License"). All use of this software is governed by the License, +or, if provided, by the license below or the license accompanying this file. Do not +remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +""" +import pytest +import os +import logging +import ly_test_tools.log.log_monitor + +from AWS.Windows.resource_mappings.resource_mappings import resource_mappings +from AWS.Windows.cdk.cdk import cdk +from AWS.common.aws_utils import aws_utils +from assetpipeline.ap_fixtures.asset_processor_fixture import asset_processor as asset_processor + +AWS_PROJECT_NAME = 'AWS-AutomationTest' +AWS_CLIENT_AUTH_FEATURE_NAME = 'AWSClientAuth' +AWS_CLIENT_AUTH_DEFAULT_PROFILE_NAME = 'default' + +GAME_LOG_NAME = 'Game.log' + +logger = logging.getLogger(__name__) + + +@pytest.mark.SUITE_periodic +@pytest.mark.usefixtures('automatic_process_killer') +@pytest.mark.usefixtures('asset_processor') +@pytest.mark.usefixtures('workspace') +@pytest.mark.parametrize('project', ['AutomatedTesting']) +@pytest.mark.parametrize('level', ['AWS/ClientAuth']) +@pytest.mark.usefixtures('cdk') +@pytest.mark.parametrize('feature_name', [AWS_CLIENT_AUTH_FEATURE_NAME]) +@pytest.mark.usefixtures('resource_mappings') +@pytest.mark.parametrize('resource_mappings_filename', ['aws_resource_mappings.json']) +@pytest.mark.usefixtures('aws_utils') +@pytest.mark.parametrize('region_name', ['us-west-2']) +@pytest.mark.parametrize('assume_role_arn', ['arn:aws:iam::645075835648:role/o3de-automation-tests']) +@pytest.mark.parametrize('session_name', ['o3de-Automation-session']) +class TestAWSClientAuthAnonymousCredentials(object): + """ + Test class to verify AWS Cognito Identity pool anonymous authorization. + """ + + def test_anonymous_credentials(self, + level: str, + launcher: pytest.fixture, + cdk: pytest.fixture, + resource_mappings: pytest.fixture, + workspace: pytest.fixture, + asset_processor: pytest.fixture + ): + """ + Setup: Deploys cdk and updates resource mapping file. + Tests: Getting AWS credentials for no signed in user. + Verification: Log monitor looks for success credentials log. + """ + logger.info(f'Cdk stack names:\n{cdk.list()}') + stacks = cdk.deploy() + resource_mappings.populate_output_keys(stacks) + asset_processor.start() + asset_processor.wait_for_idle() + + file_to_monitor = os.path.join(launcher.workspace.paths.project_log(), GAME_LOG_NAME) + log_monitor = ly_test_tools.log.log_monitor.LogMonitor(launcher=launcher, log_file_path=file_to_monitor) + + launcher.args = ['+LoadLevel', level] + + with launcher.start(launch_ap=False): + result = log_monitor.monitor_log_for_lines( + expected_lines=['(Script) - Success anonymous credentials'], + unexpected_lines=['(Script) - Fail anonymous credentials'], + halt_on_unexpected=True, + ) + assert result, 'Anonymous credentials fetched successfully.' diff --git a/AutomatedTesting/Gem/PythonTests/AWS/Windows/resource_mappings/__init__.py b/AutomatedTesting/Gem/PythonTests/AWS/Windows/resource_mappings/__init__.py new file mode 100644 index 0000000000..6ed3dc4bda --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/AWS/Windows/resource_mappings/__init__.py @@ -0,0 +1,10 @@ +""" +All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +its licensors. + +For complete copyright and license terms please see the LICENSE at the root of this +distribution (the "License"). All use of this software is governed by the License, +or, if provided, by the license below or the license accompanying this file. Do not +remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +""" \ No newline at end of file diff --git a/AutomatedTesting/Gem/PythonTests/AWS/Windows/resource_mappings/resource_mappings.py b/AutomatedTesting/Gem/PythonTests/AWS/Windows/resource_mappings/resource_mappings.py new file mode 100644 index 0000000000..c8d8cff828 --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/AWS/Windows/resource_mappings/resource_mappings.py @@ -0,0 +1,137 @@ +""" +All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +its licensors. + +For complete copyright and license terms please see the LICENSE at the root of this +distribution (the "License"). All use of this software is governed by the License, +or, if provided, by the license below or the license accompanying this file. Do not +remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +""" + +import os +import pytest +import json + +AWS_RESOURCE_MAPPINGS_KEY = 'AWSResourceMappings' +AWS_RESOURCE_MAPPINGS_ACCOUNT_ID_KEY = 'AccountId' +AWS_RESOURCE_MAPPINGS_REGION_KEY = 'Region' + + +class ResourceMappings: + """ + ResourceMappings class that handles writing Cloud formation outputs to resource mappings json file in a project. + """ + + def __init__(self, file_path: str, region: str, feature_name: str, account_id: str, workspace: pytest.fixture, + cloud_formation_client): + """ + :param file_path: Path for the resource mapping file. + :param region: Region value for the resource mapping file. + :param feature_name: Feature gem name to use to append name to mappings key. + :param account_id: AWS account id value for the resource mapping file. + :param workspace: ly_test_tools workspace fixture. + :param cloud_formation_client: AWS cloud formation client. + """ + self._cdk_env = os.environ.copy() + self._cdk_env['PATH'] = f'{workspace.paths.engine_root()}\\python;' + self._cdk_env['PATH'] + self._resource_mapping_file_path = file_path + self._region = region + self._feature_name = feature_name + self._account_id = account_id + + assert os.path.exists(self._resource_mapping_file_path), \ + f'Invalid resource mapping file path {self._resource_mapping_file_path}' + self._client = cloud_formation_client + + def populate_output_keys(self, stacks=[]) -> None: + """ + Calls describe stacks on cloud formation service and persists outputs to resource mappings file. + :param stacks List of stack arns to describe and populate resource mappings with. + """ + for stack_name in stacks: + response = self._client.describe_stacks( + StackName=stack_name + ) + stacks = response.get('Stacks', []) + assert len(stacks) == 1, f'{stack_name} is invalid.' + + self.__write_resource_mappings(stacks[0].get('Outputs', [])) + + def __write_resource_mappings(self, outputs, append_feature_name = True) -> None: + with open(self._resource_mapping_file_path) as file_content: + resource_mappings = json.load(file_content) + + resource_mappings[AWS_RESOURCE_MAPPINGS_ACCOUNT_ID_KEY] = self._account_id + resource_mappings[AWS_RESOURCE_MAPPINGS_REGION_KEY] = self._region + + # Append new mappings. + resource_mappings[AWS_RESOURCE_MAPPINGS_KEY] = resource_mappings.get(AWS_RESOURCE_MAPPINGS_KEY, {}) + + for output in outputs: + if append_feature_name: + resource_key = f'{self._feature_name}.{output.get("OutputKey", "InvalidKey")}' + else: + resource_key = output.get("OutputKey", "InvalidKey") + resource_mappings[AWS_RESOURCE_MAPPINGS_KEY][resource_key] = resource_mappings[ + AWS_RESOURCE_MAPPINGS_KEY].get(resource_key, {}) + resource_mappings[AWS_RESOURCE_MAPPINGS_KEY][resource_key]['Type'] = 'AutomationTestType' + resource_mappings[AWS_RESOURCE_MAPPINGS_KEY][resource_key]['Name/ID'] = output.get('OutputValue', + 'InvalidId') + + with open(self._resource_mapping_file_path, 'w') as file_content: + json.dump(resource_mappings, file_content, indent=4) + + def clear_output_keys(self) -> None: + """ + Clears values of all resource mapping keys. Sets region to default to us-west-2 + """ + with open(self._resource_mapping_file_path) as file_content: + resource_mappings = json.load(file_content) + + resource_mappings[AWS_RESOURCE_MAPPINGS_ACCOUNT_ID_KEY] = '' + resource_mappings[AWS_RESOURCE_MAPPINGS_REGION_KEY] = 'us-west-2' + + # Append new mappings. + resource_mappings[AWS_RESOURCE_MAPPINGS_KEY] = resource_mappings.get(AWS_RESOURCE_MAPPINGS_KEY, {}) + resource_mappings[AWS_RESOURCE_MAPPINGS_KEY] = {} + + with open(self._resource_mapping_file_path, 'w') as file_content: + json.dump(resource_mappings, file_content, indent=4) + + self._resource_mapping_file_path = '' + self._region = '' + self._client = None + + +@pytest.fixture(scope='function') +def resource_mappings( + request: pytest.fixture, + project: str, + feature_name: str, + resource_mappings_filename: str, + workspace: pytest.fixture, + aws_utils: pytest.fixture) -> ResourceMappings: + """ + Fixture for setting up resource mappings file. + :param request: _pytest.fixtures.SubRequest class that handles getting + a pytest fixture from a pytest function/fixture. + :param project: Project to find resource mapping file. + :param feature_name: AWS Gem name that is prepended to resource mapping keys. + :param resource_mappings_filename: Name of resource mapping file. + :param workspace: ly_test_tools workspace fixture. + :param aws_utils: AWS utils fixture. + :return: ResourceMappings class object. + """ + + path = f'{workspace.paths.engine_root()}\\{project}\\Config\\{resource_mappings_filename}' + resource_mappings_obj = ResourceMappings(path, aws_utils.assume_session().region_name, feature_name, + aws_utils.assume_account_id(), workspace, + aws_utils.client('cloudformation')) + + def teardown(): + resource_mappings_obj.clear_output_keys() + + request.addfinalizer(teardown) + + return resource_mappings_obj diff --git a/AutomatedTesting/Gem/PythonTests/AWS/__init__.py b/AutomatedTesting/Gem/PythonTests/AWS/__init__.py new file mode 100644 index 0000000000..8caef52682 --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/AWS/__init__.py @@ -0,0 +1,11 @@ +""" +All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +its licensors. + +For complete copyright and license terms please see the LICENSE at the root of this +distribution (the "License"). All use of this software is governed by the License, +or, if provided, by the license below or the license accompanying this file. Do not +remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +""" + diff --git a/AutomatedTesting/Gem/PythonTests/AWS/common/aws_utils.py b/AutomatedTesting/Gem/PythonTests/AWS/common/aws_utils.py new file mode 100644 index 0000000000..7a15ba0abe --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/AWS/common/aws_utils.py @@ -0,0 +1,82 @@ +""" +All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +its licensors. +For complete copyright and license terms please see the LICENSE at the root of this +distribution (the "License"). All use of this software is governed by the License, +or, if provided, by the license below or the license accompanying this file. Do not +remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +""" +import boto3 +import pytest +import logging + +logger = logging.getLogger(__name__) + + +class AwsUtils: + + def __init__(self, arn: str, session_name: str, region_name: str): + local_session = boto3.Session(profile_name='default') + local_sts_client = local_session.client('sts') + self._local_account_id = local_sts_client.get_caller_identity()["Account"] + logger.info(f'Local Account Id: {self._local_account_id}') + + response = local_sts_client.assume_role(RoleArn=arn, RoleSessionName=session_name) + + self._assume_session = boto3.Session(aws_access_key_id=response['Credentials']['AccessKeyId'], + aws_secret_access_key=response['Credentials']['SecretAccessKey'], + aws_session_token=response['Credentials']['SessionToken'], + region_name=region_name) + + assume_sts_client = self._assume_session.client('sts') + assume_account_id = assume_sts_client.get_caller_identity()["Account"] + logger.info(f'Assume Account Id: {assume_account_id}') + self._assume_account_id = assume_account_id + + def client(self, service: str): + """ + Get the client for a specific AWS service from configured session + :return: Client for the AWS service. + """ + return self._assume_session.client(service) + + def assume_session(self): + return self._assume_session + + def local_account_id(self): + return self._local_account_id + + def assume_account_id(self): + return self._assume_account_id + + def destroy(self) -> None: + """ + clears stored session + """ + self._assume_session = None + + +@pytest.fixture(scope='function') +def aws_utils( + request: pytest.fixture, + assume_role_arn: str, + session_name: str, + region_name: str): + """ + Fixture for setting up a Cdk + :param request: _pytest.fixtures.SubRequest class that handles getting + a pytest fixture from a pytest function/fixture. + :param assume_role_arn: Role used to fetch temporary aws credentials, configure service clients with obtained credentials. + :param session_name: Session name to set. + :param region_name: AWS account region to set for session. + :return AWSUtils class object. + """ + aws_utils_obj = AwsUtils(assume_role_arn, session_name, region_name) + + def teardown(): + aws_utils_obj.destroy() + + request.addfinalizer(teardown) + + return aws_utils_obj diff --git a/AutomatedTesting/Gem/PythonTests/CMakeLists.txt b/AutomatedTesting/Gem/PythonTests/CMakeLists.txt index d6f9ecff4b..c6ed6c7538 100644 --- a/AutomatedTesting/Gem/PythonTests/CMakeLists.txt +++ b/AutomatedTesting/Gem/PythonTests/CMakeLists.txt @@ -56,5 +56,8 @@ add_subdirectory(editor) ## Streaming ## add_subdirectory(streaming) -## Streaming ## +## Smoke ## add_subdirectory(smoke) + +## AWS ## +add_subdirectory(AWS) diff --git a/AutomatedTesting/Levels/AWS/ClientAuth/ClientAuth.ly b/AutomatedTesting/Levels/AWS/ClientAuth/ClientAuth.ly new file mode 100644 index 0000000000..af8a7f5c8e --- /dev/null +++ b/AutomatedTesting/Levels/AWS/ClientAuth/ClientAuth.ly @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f0f4d4e0155feaa76c80a14128000a0fd9570ab76e79f4847eaef9006324a4d2 +size 9084 diff --git a/AutomatedTesting/Levels/AWS/ClientAuth/ConitoAnonymousAuthorization.scriptcanvas b/AutomatedTesting/Levels/AWS/ClientAuth/ConitoAnonymousAuthorization.scriptcanvas new file mode 100644 index 0000000000..ef03c66b16 --- /dev/null +++ b/AutomatedTesting/Levels/AWS/ClientAuth/ConitoAnonymousAuthorization.scriptcanvas @@ -0,0 +1,2313 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/AutomatedTesting/Levels/AWS/ClientAuth/LevelData/Environment.xml b/AutomatedTesting/Levels/AWS/ClientAuth/LevelData/Environment.xml new file mode 100644 index 0000000000..d4e3d33551 --- /dev/null +++ b/AutomatedTesting/Levels/AWS/ClientAuth/LevelData/Environment.xml @@ -0,0 +1 @@ + diff --git a/AutomatedTesting/Levels/AWS/ClientAuth/LevelData/TimeOfDay.xml b/AutomatedTesting/Levels/AWS/ClientAuth/LevelData/TimeOfDay.xml new file mode 100644 index 0000000000..d827d4da29 --- /dev/null +++ b/AutomatedTesting/Levels/AWS/ClientAuth/LevelData/TimeOfDay.xml @@ -0,0 +1 @@ + diff --git a/AutomatedTesting/Levels/AWS/ClientAuth/filelist.xml b/AutomatedTesting/Levels/AWS/ClientAuth/filelist.xml new file mode 100644 index 0000000000..f69a99fe37 --- /dev/null +++ b/AutomatedTesting/Levels/AWS/ClientAuth/filelist.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/AutomatedTesting/Levels/AWS/ClientAuth/level.pak b/AutomatedTesting/Levels/AWS/ClientAuth/level.pak new file mode 100644 index 0000000000..1ae0bb1f7a --- /dev/null +++ b/AutomatedTesting/Levels/AWS/ClientAuth/level.pak @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4900bdf28654e21032e69957f2762fa0a3b93a4b82163267a1f10f19f6d78692 +size 3795 diff --git a/AutomatedTesting/Levels/AWS/ClientAuth/tags.txt b/AutomatedTesting/Levels/AWS/ClientAuth/tags.txt new file mode 100644 index 0000000000..0d6c1880e7 --- /dev/null +++ b/AutomatedTesting/Levels/AWS/ClientAuth/tags.txt @@ -0,0 +1,12 @@ +0,0,0,0,0,0 +0,0,0,0,0,0 +0,0,0,0,0,0 +0,0,0,0,0,0 +0,0,0,0,0,0 +0,0,0,0,0,0 +0,0,0,0,0,0 +0,0,0,0,0,0 +0,0,0,0,0,0 +0,0,0,0,0,0 +0,0,0,0,0,0 +0,0,0,0,0,0 diff --git a/AutomatedTesting/Registry/awscoreconfiguration.setreg b/AutomatedTesting/Registry/awscoreconfiguration.setreg new file mode 100644 index 0000000000..ca110eb103 --- /dev/null +++ b/AutomatedTesting/Registry/awscoreconfiguration.setreg @@ -0,0 +1,10 @@ +{ + "Amazon": + { + "AWSCore": + { + "ProfileName": "default", + "ResourceMappingConfigFileName": "aws_resource_mappings.json" + } + } +} \ No newline at end of file From ded39be57ee8410bdc4a48b5e81065bd8e909133 Mon Sep 17 00:00:00 2001 From: Tommy Walton <82672795+amzn-tommy@users.noreply.github.com> Date: Wed, 19 May 2021 11:21:44 -0700 Subject: [PATCH 69/69] Merging WrinkleMask support from 1.0 to main (#680) Added a loop to the skin shader that will sample from wrinkle masks, multiply them by a weight, combine them, and use them instead of vertex colors for wrinkle map blending Added an array of masks, an array of weights, and a wrinkle mask count to the DefaultObjectSrg. -Will create a follow up task to handle this a better way. Removed motion vector (for now) from skin.materialtype since we're not using them, and removed depthtransparent since skin doesn't support transparency Added an interface to the MeshFeatureProcessor to get the object srg Wrapped srg->Compile in if(srg->IsQueuedForCompile()) to prevent compiling twice --This doesn't stop a race condition if both happen at the same time, but that is at least far less likely. It will need a better solution later. Added a function to the MorphTargetExporter that will check to see if a texture that matches the blend shape name exists in a particular folder, and adds a reference to that image to the MorphTargetMetaAsset --Only supports .tif, and doesn't automatically re-process the .fbx if the folder is updated. These can be improved in later iterations Added a null check in MaterialTypeSourceData.cpp to fix a crash I ran into Added a for loop in two places to look for the first submesh that has a morph target, instead of just using the first to check if a lod has morph targets or not. --I have a better fix for this, but it involves more areas of the code, so I'm saving that for another change. Modified AtomActorInstance to look for any morph targets that have a wrinkle mask reference Then each frame, for any morph targets with non-zero weights that also have wrinkle masks, it updates the mask array, weights, and count on the object srg. --- .../Common/Assets/Materials/Types/Skin.azsl | 53 ++++++---- .../Assets/Materials/Types/Skin.materialtype | 9 -- .../Atom/Features/PBR/DefaultObjectSrg.azsli | 10 ++ .../Atom/Feature/Mesh/MeshFeatureProcessor.h | 2 + .../Mesh/MeshFeatureProcessorInterface.h | 8 ++ .../Code/Mocks/MockMeshFeatureProcessor.h | 2 + .../Code/Source/Mesh/MeshFeatureProcessor.cpp | 13 +++ .../SkinnedMeshFeatureProcessor.cpp | 12 +-- .../SkinnedMesh/SkinnedMeshFeatureProcessor.h | 1 - .../RPI.Reflect/Model/MorphTargetMetaAsset.h | 4 + .../Model/MorphTargetExporter.cpp | 53 +++++++++- .../RPI.Builders/Model/MorphTargetExporter.h | 6 +- .../Material/MaterialTypeSourceData.cpp | 2 +- .../Model/MorphTargetMetaAsset.cpp | 1 + .../Code/Source/AtomActorInstance.cpp | 100 +++++++++++++++++- .../Code/Source/AtomActorInstance.h | 15 +++ 16 files changed, 247 insertions(+), 44 deletions(-) diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/Skin.azsl b/Gems/Atom/Feature/Common/Assets/Materials/Types/Skin.azsl index 84095ac163..456d7cbabe 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/Skin.azsl +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/Skin.azsl @@ -101,7 +101,7 @@ struct VSOutput float2 m_uv[UvSetCount] : UV1; float2 m_detailUv : UV3; - float4 m_blendMask : UV8; + float4 m_wrinkleBlendFactors : UV8; }; #include @@ -132,11 +132,11 @@ VSOutput SkinVS(VSInput IN) if(o_blendMask_isBound) { - OUT.m_blendMask = IN.m_optional_blendMask; + OUT.m_wrinkleBlendFactors = IN.m_optional_blendMask; } else { - OUT.m_blendMask = float4(0,1,0,0); + OUT.m_wrinkleBlendFactors = float4(0,0,0,0); } VertexHelper(IN, OUT, worldPosition, false); @@ -214,7 +214,22 @@ PbrLightingOutput SkinPS_Common(VSOutput IN) float2 normalUv = IN.m_uv[MaterialSrg::m_normalMapUvIndex]; float detailLayerNormalFactor = MaterialSrg::m_detail_normal_factor * detailLayerBlendFactor; - + + // ------- Wrinkle Map Setup ------- + + // Combine the optional per-morph target wrinkle masks + float4 wrinkleBlendFactors = float4(0.0, 0.0, 0.0, 0.0); + for(uint wrinkleMaskIndex = 0; wrinkleMaskIndex < ObjectSrg::m_wrinkle_mask_count; ++wrinkleMaskIndex) + { + wrinkleBlendFactors += ObjectSrg::m_wrinkle_masks[wrinkleMaskIndex].Sample(MaterialSrg::m_sampler, normalUv) * ObjectSrg::GetWrinkleMaskWeight(wrinkleMaskIndex); + } + + // If texture based morph target driven masks are being used, use those values instead of the per-vertex colors + if(ObjectSrg::m_wrinkle_mask_count) + { + IN.m_wrinkleBlendFactors = saturate(wrinkleBlendFactors); + } + // Since the wrinkle normal maps should all be in the same tangent space as the main normal map, we should be able to blend the raw normal map // texture values before doing all the tangent space transforms, so we only have to do the transforms once, for better performance. @@ -223,12 +238,12 @@ PbrLightingOutput SkinPS_Common(VSOutput IN) { normalMapSample = SampleNormalXY(MaterialSrg::m_normalMap, MaterialSrg::m_sampler, normalUv, MaterialSrg::m_flipNormalX, MaterialSrg::m_flipNormalY); } - if(o_wrinkleLayers_enabled && o_blendMask_isBound && o_wrinkleLayers_normal_enabled) + if(o_wrinkleLayers_enabled && o_wrinkleLayers_normal_enabled) { - normalMapSample = ApplyNormalWrinkleMap(o_wrinkleLayers_normal_useTexture1, normalMapSample, MaterialSrg::m_wrinkle_normal_texture1, MaterialSrg::m_sampler, normalUv, MaterialSrg::m_flipNormalX, MaterialSrg::m_flipNormalY, IN.m_blendMask.r); - normalMapSample = ApplyNormalWrinkleMap(o_wrinkleLayers_normal_useTexture2, normalMapSample, MaterialSrg::m_wrinkle_normal_texture2, MaterialSrg::m_sampler, normalUv, MaterialSrg::m_flipNormalX, MaterialSrg::m_flipNormalY, IN.m_blendMask.g); - normalMapSample = ApplyNormalWrinkleMap(o_wrinkleLayers_normal_useTexture3, normalMapSample, MaterialSrg::m_wrinkle_normal_texture3, MaterialSrg::m_sampler, normalUv, MaterialSrg::m_flipNormalX, MaterialSrg::m_flipNormalY, IN.m_blendMask.b); - normalMapSample = ApplyNormalWrinkleMap(o_wrinkleLayers_normal_useTexture4, normalMapSample, MaterialSrg::m_wrinkle_normal_texture4, MaterialSrg::m_sampler, normalUv, MaterialSrg::m_flipNormalX, MaterialSrg::m_flipNormalY, IN.m_blendMask.a); + normalMapSample = ApplyNormalWrinkleMap(o_wrinkleLayers_normal_useTexture1, normalMapSample, MaterialSrg::m_wrinkle_normal_texture1, MaterialSrg::m_sampler, normalUv, MaterialSrg::m_flipNormalX, MaterialSrg::m_flipNormalY, IN.m_wrinkleBlendFactors.r); + normalMapSample = ApplyNormalWrinkleMap(o_wrinkleLayers_normal_useTexture2, normalMapSample, MaterialSrg::m_wrinkle_normal_texture2, MaterialSrg::m_sampler, normalUv, MaterialSrg::m_flipNormalX, MaterialSrg::m_flipNormalY, IN.m_wrinkleBlendFactors.g); + normalMapSample = ApplyNormalWrinkleMap(o_wrinkleLayers_normal_useTexture3, normalMapSample, MaterialSrg::m_wrinkle_normal_texture3, MaterialSrg::m_sampler, normalUv, MaterialSrg::m_flipNormalX, MaterialSrg::m_flipNormalY, IN.m_wrinkleBlendFactors.b); + normalMapSample = ApplyNormalWrinkleMap(o_wrinkleLayers_normal_useTexture4, normalMapSample, MaterialSrg::m_wrinkle_normal_texture4, MaterialSrg::m_sampler, normalUv, MaterialSrg::m_flipNormalX, MaterialSrg::m_flipNormalY, IN.m_wrinkleBlendFactors.a); } if(o_detail_normal_useTexture) @@ -255,7 +270,7 @@ PbrLightingOutput SkinPS_Common(VSOutput IN) float3 baseColor = GetBaseColorInput(MaterialSrg::m_baseColorMap, MaterialSrg::m_sampler, baseColorUv, MaterialSrg::m_baseColor, o_baseColor_useTexture); bool useSampledBaseColor = o_baseColor_useTexture; - if(o_wrinkleLayers_enabled && o_blendMask_isBound && o_wrinkleLayers_baseColor_enabled) + if(o_wrinkleLayers_enabled && o_wrinkleLayers_baseColor_enabled) { // If any of the wrinkle maps are applied, we will use the Base Color blend settings to apply the MaterialSrg::m_baseColor tint to the wrinkle maps, // even if the main base color map is not used. @@ -272,10 +287,10 @@ PbrLightingOutput SkinPS_Common(VSOutput IN) baseColor = float3(1,1,1); } - baseColor = ApplyBaseColorWrinkleMap(o_wrinkleLayers_baseColor_useTexture1, baseColor, MaterialSrg::m_wrinkle_baseColor_texture1, MaterialSrg::m_sampler, baseColorUv, IN.m_blendMask.r); - baseColor = ApplyBaseColorWrinkleMap(o_wrinkleLayers_baseColor_useTexture2, baseColor, MaterialSrg::m_wrinkle_baseColor_texture2, MaterialSrg::m_sampler, baseColorUv, IN.m_blendMask.g); - baseColor = ApplyBaseColorWrinkleMap(o_wrinkleLayers_baseColor_useTexture3, baseColor, MaterialSrg::m_wrinkle_baseColor_texture3, MaterialSrg::m_sampler, baseColorUv, IN.m_blendMask.b); - baseColor = ApplyBaseColorWrinkleMap(o_wrinkleLayers_baseColor_useTexture4, baseColor, MaterialSrg::m_wrinkle_baseColor_texture4, MaterialSrg::m_sampler, baseColorUv, IN.m_blendMask.a); + baseColor = ApplyBaseColorWrinkleMap(o_wrinkleLayers_baseColor_useTexture1, baseColor, MaterialSrg::m_wrinkle_baseColor_texture1, MaterialSrg::m_sampler, baseColorUv, IN.m_wrinkleBlendFactors.r); + baseColor = ApplyBaseColorWrinkleMap(o_wrinkleLayers_baseColor_useTexture2, baseColor, MaterialSrg::m_wrinkle_baseColor_texture2, MaterialSrg::m_sampler, baseColorUv, IN.m_wrinkleBlendFactors.g); + baseColor = ApplyBaseColorWrinkleMap(o_wrinkleLayers_baseColor_useTexture3, baseColor, MaterialSrg::m_wrinkle_baseColor_texture3, MaterialSrg::m_sampler, baseColorUv, IN.m_wrinkleBlendFactors.b); + baseColor = ApplyBaseColorWrinkleMap(o_wrinkleLayers_baseColor_useTexture4, baseColor, MaterialSrg::m_wrinkle_baseColor_texture4, MaterialSrg::m_sampler, baseColorUv, IN.m_wrinkleBlendFactors.a); } @@ -283,13 +298,13 @@ PbrLightingOutput SkinPS_Common(VSOutput IN) baseColor = ApplyTextureOverlay(o_detail_baseColor_useTexture, baseColor, MaterialSrg::m_detail_baseColor_texture, MaterialSrg::m_sampler, IN.m_detailUv, detailLayerBaseColorFactor); - if(o_wrinkleLayers_enabled && o_wrinkleLayers_showBlendMaskValues && o_blendMask_isBound) + if(o_wrinkleLayers_enabled && o_wrinkleLayers_showBlendMaskValues) { // Overlay debug colors to highlight the different blend weights coming from the vertex color stream. - if(o_wrinkleLayers_count > 0) { baseColor = lerp(baseColor, float3(1,0,0), IN.m_blendMask.r); } - if(o_wrinkleLayers_count > 1) { baseColor = lerp(baseColor, float3(0,1,0), IN.m_blendMask.g); } - if(o_wrinkleLayers_count > 2) { baseColor = lerp(baseColor, float3(0,0,1), IN.m_blendMask.b); } - if(o_wrinkleLayers_count > 3) { baseColor = lerp(baseColor, float3(1,1,1), IN.m_blendMask.a); } + if(o_wrinkleLayers_count > 0) { baseColor = lerp(baseColor, float3(1,0,0), IN.m_wrinkleBlendFactors.r); } + if(o_wrinkleLayers_count > 1) { baseColor = lerp(baseColor, float3(0,1,0), IN.m_wrinkleBlendFactors.g); } + if(o_wrinkleLayers_count > 2) { baseColor = lerp(baseColor, float3(0,0,1), IN.m_wrinkleBlendFactors.b); } + if(o_wrinkleLayers_count > 3) { baseColor = lerp(baseColor, float3(1,1,1), IN.m_wrinkleBlendFactors.a); } } // ------- Specular ------- diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/Skin.materialtype b/Gems/Atom/Feature/Common/Assets/Materials/Types/Skin.materialtype index b8951d69c7..101a03b907 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/Skin.materialtype +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/Skin.materialtype @@ -987,15 +987,6 @@ { "file": "Shaders/MotionVector/SkinnedMeshMotionVector.shader", "tag": "SkinnedMeshMotionVector" - }, - // Used by the light culling system to produce accurate depth bounds for this object when it uses blended transparency - { - "file": "Shaders/Depth/DepthPassTransparentMin.shader", - "tag": "DepthPassTransparentMin" - }, - { - "file": "Shaders/Depth/DepthPassTransparentMax.shader", - "tag": "DepthPassTransparentMax" } ], "functors": [ diff --git a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/DefaultObjectSrg.azsli b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/DefaultObjectSrg.azsli index 50896cdf25..abc4ec7fc4 100644 --- a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/DefaultObjectSrg.azsli +++ b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/DefaultObjectSrg.azsli @@ -31,6 +31,16 @@ ShaderResourceGroup ObjectSrg : SRG_PerObject return SceneSrg::GetObjectToWorldInverseTransposeMatrix(m_objectId); } + //[GFX TODO][ATOM-15280] Move wrinkle mask data from the default object srg into something specific to the Skin shader + uint m_wrinkle_mask_count; + float4 m_wrinkle_mask_weights[4]; + Texture2D m_wrinkle_masks[16]; + + float GetWrinkleMaskWeight(uint index) + { + return m_wrinkle_mask_weights[index / 4][index % 4]; + } + //! Reflection Probe (smallest probe volume that overlaps the object position) struct ReflectionProbeData { diff --git a/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Mesh/MeshFeatureProcessor.h b/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Mesh/MeshFeatureProcessor.h index 7875e38fc0..0d61ef82d1 100644 --- a/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Mesh/MeshFeatureProcessor.h +++ b/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Mesh/MeshFeatureProcessor.h @@ -148,6 +148,8 @@ namespace AZ Data::Instance GetModel(const MeshHandle& meshHandle) const override; Data::Asset GetModelAsset(const MeshHandle& meshHandle) const override; + Data::Instance GetObjectSrg(const MeshHandle& meshHandle) const override; + void QueueObjectSrgForCompile(const MeshHandle& meshHandle) const override; void SetMaterialAssignmentMap(const MeshHandle& meshHandle, const Data::Instance& material) override; void SetMaterialAssignmentMap(const MeshHandle& meshHandle, const MaterialAssignmentMap& materials) override; const MaterialAssignmentMap& GetMaterialAssignmentMap(const MeshHandle& meshHandle) const override; diff --git a/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Mesh/MeshFeatureProcessorInterface.h b/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Mesh/MeshFeatureProcessorInterface.h index c2360068de..fb5bff5584 100644 --- a/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Mesh/MeshFeatureProcessorInterface.h +++ b/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Mesh/MeshFeatureProcessorInterface.h @@ -61,6 +61,14 @@ namespace AZ virtual Data::Instance GetModel(const MeshHandle& meshHandle) const = 0; //! Gets the underlying RPI::ModelAsset for a meshHandle. virtual Data::Asset GetModelAsset(const MeshHandle& meshHandle) const = 0; + //! Gets the ObjectSrg for a meshHandle. + //! Updating the ObjectSrg should be followed by a call to QueueObjectSrgForCompile, + //! instead of compiling the srg directly. This way, if the srg has already been queued for compile, + //! it will not be queued twice in the same frame. The ObjectSrg should not be updated during + //! Simulate, or it will create a race between updating the data and the call to Compile + virtual Data::Instance GetObjectSrg(const MeshHandle& meshHandle) const = 0; + //! Queues the object srg for compile. + virtual void QueueObjectSrgForCompile(const MeshHandle& meshHandle) const = 0; //! Sets the MaterialAssignmentMap for a meshHandle, using just a single material for the DefaultMaterialAssignmentId. //! Note if there is already a material assignment map, this will replace the entire map with just a single material. virtual void SetMaterialAssignmentMap(const MeshHandle& meshHandle, const Data::Instance& material) = 0; diff --git a/Gems/Atom/Feature/Common/Code/Mocks/MockMeshFeatureProcessor.h b/Gems/Atom/Feature/Common/Code/Mocks/MockMeshFeatureProcessor.h index 39fd7b4380..418ee0cfb8 100644 --- a/Gems/Atom/Feature/Common/Code/Mocks/MockMeshFeatureProcessor.h +++ b/Gems/Atom/Feature/Common/Code/Mocks/MockMeshFeatureProcessor.h @@ -23,6 +23,8 @@ namespace UnitTest MOCK_METHOD1(CloneMesh, MeshHandle(const MeshHandle&)); MOCK_CONST_METHOD1(GetModel, AZStd::intrusive_ptr(const MeshHandle&)); MOCK_CONST_METHOD1(GetModelAsset, AZ::Data::Asset(const MeshHandle&)); + MOCK_CONST_METHOD1(GetObjectSrg, AZStd::intrusive_ptr(const MeshHandle&)); + MOCK_CONST_METHOD1(QueueObjectSrgForCompile, void(const MeshHandle&)); MOCK_CONST_METHOD1(GetMaterialAssignmentMap, const AZ::Render::MaterialAssignmentMap&(const MeshHandle&)); MOCK_METHOD2(ConnectModelChangeEventHandler, void(const MeshHandle&, ModelChangedEvent::Handler&)); MOCK_METHOD3(SetTransform, void(const MeshHandle&, const AZ::Transform&, const AZ::Vector3&)); diff --git a/Gems/Atom/Feature/Common/Code/Source/Mesh/MeshFeatureProcessor.cpp b/Gems/Atom/Feature/Common/Code/Source/Mesh/MeshFeatureProcessor.cpp index 29f0636e9e..4059d65cbb 100644 --- a/Gems/Atom/Feature/Common/Code/Source/Mesh/MeshFeatureProcessor.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/Mesh/MeshFeatureProcessor.cpp @@ -231,6 +231,19 @@ namespace AZ return {}; } + Data::Instance MeshFeatureProcessor::GetObjectSrg(const MeshHandle& meshHandle) const + { + return meshHandle.IsValid() ? meshHandle->m_shaderResourceGroup : nullptr; + } + + void MeshFeatureProcessor::QueueObjectSrgForCompile(const MeshHandle& meshHandle) const + { + if (meshHandle.IsValid()) + { + meshHandle->m_objectSrgNeedsUpdate = true; + } + } + void MeshFeatureProcessor::SetMaterialAssignmentMap(const MeshHandle& meshHandle, const Data::Instance& material) { Render::MaterialAssignmentMap materials; diff --git a/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshFeatureProcessor.cpp b/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshFeatureProcessor.cpp index 388f4112a0..cb2d6a69ef 100644 --- a/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshFeatureProcessor.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshFeatureProcessor.cpp @@ -71,16 +71,6 @@ namespace AZ } - void SkinnedMeshFeatureProcessor::Simulate(const FeatureProcessor::SimulatePacket& packet) - { - AZ_PROFILE_FUNCTION(Debug::ProfileCategory::AzRender); - AZ_ATOM_PROFILE_FUNCTION("SkinnedMesh", "SkinnedMeshFeatureProcessor: Simulate"); - AZ_UNUSED(packet); - - SkinnedMeshFeatureProcessorNotificationBus::Broadcast(&SkinnedMeshFeatureProcessorNotificationBus::Events::OnUpdateSkinningMatrices); - - } - void SkinnedMeshFeatureProcessor::Render(const FeatureProcessor::RenderPacket& packet) { AZ_PROFILE_FUNCTION(Debug::ProfileCategory::AzRender); @@ -268,6 +258,8 @@ namespace AZ void SkinnedMeshFeatureProcessor::OnBeginPrepareRender() { m_renderProxiesChecker.soft_lock(); + + SkinnedMeshFeatureProcessorNotificationBus::Broadcast(&SkinnedMeshFeatureProcessorNotificationBus::Events::OnUpdateSkinningMatrices); } void SkinnedMeshFeatureProcessor::OnRenderEnd() diff --git a/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshFeatureProcessor.h b/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshFeatureProcessor.h index bb3dd242a1..75d41742b2 100644 --- a/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshFeatureProcessor.h +++ b/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshFeatureProcessor.h @@ -49,7 +49,6 @@ namespace AZ // FeatureProcessor overrides ... void Activate() override; void Deactivate() override; - void Simulate(const FeatureProcessor::SimulatePacket& packet) override; void Render(const FeatureProcessor::RenderPacket& packet) override; void OnRenderEnd() override; diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Model/MorphTargetMetaAsset.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Model/MorphTargetMetaAsset.h index 5b92047226..4aa3faa6c9 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Model/MorphTargetMetaAsset.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Model/MorphTargetMetaAsset.h @@ -15,6 +15,7 @@ #include #include #include +#include namespace AZ::RPI { @@ -56,6 +57,9 @@ namespace AZ::RPI float m_minPositionDelta; float m_maxPositionDelta; + //! Reference to the wrinkle mask, if it exists + AZ::Data::Asset m_wrinkleMask; + //! Boolean to indicate the presence or absence of color deltas bool m_hasColorDeltas = false; diff --git a/Gems/Atom/RPI/Code/Source/RPI.Builders/Model/MorphTargetExporter.cpp b/Gems/Atom/RPI/Code/Source/RPI.Builders/Model/MorphTargetExporter.cpp index 7aace50760..3d0cbca8e6 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Builders/Model/MorphTargetExporter.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Builders/Model/MorphTargetExporter.cpp @@ -18,6 +18,9 @@ #include #include +#include +#include + namespace AZ::RPI { using namespace AZ::SceneAPI; @@ -114,7 +117,7 @@ namespace AZ::RPI meshNodeName, sourceMesh.m_name.GetCStr()); const DataTypes::MatrixType globalTransform = Utilities::BuildWorldTransform(sceneGraph, sceneNodeIndex); - BuildMorphTargetMesh(vertexOffset, sourceMesh, productMesh, metaAssetCreator, blendShapeName, blendShapeData, globalTransform, coordSysConverter); + BuildMorphTargetMesh(vertexOffset, sourceMesh, productMesh, metaAssetCreator, blendShapeName, blendShapeData, globalTransform, coordSysConverter, scene.GetSourceFilename()); } } } @@ -157,7 +160,8 @@ namespace AZ::RPI const AZStd::string& blendShapeName, const AZStd::shared_ptr& blendShapeData, const DataTypes::MatrixType& globalTransform, - const AZ::SceneAPI::CoordinateSystemConverter& coordSysConverter) + const AZ::SceneAPI::CoordinateSystemConverter& coordSysConverter, + const AZStd::string& sourceSceneFilename) { const float tolerance = CalcPositionDeltaTolerance(sourceMesh); AZ::Aabb deltaPositionAabb = AZ::Aabb::CreateNull(); @@ -288,6 +292,8 @@ namespace AZ::RPI metaData.m_maxPositionDelta = maxValue; } + metaData.m_wrinkleMask = GetWrinkleMask(sourceSceneFilename, blendShapeName); + metaAssetCreator.AddMorphTarget(metaData); AZ_Assert(uncompressedPositionDeltas.size() == compressedDeltas.size(), "Number of uncompressed (%d) and compressed position delta components (%d) do not match.", @@ -312,4 +318,47 @@ namespace AZ::RPI AZ_Assert((packedCompressedMorphTargetVertexData.size() - metaData.m_startIndex) == numMorphedVertices, "Vertex index range (%d) in morph target meta data does not match number of morphed vertices (%d).", packedCompressedMorphTargetVertexData.size() - metaData.m_startIndex, numMorphedVertices); } + + Data::Asset MorphTargetExporter::GetWrinkleMask(const AZStd::string& sourceSceneFullFilePath, const AZStd::string& blendShapeName) const + { + AZ::Data::Asset imageAsset; + + // See if there is a wrinkle map mask for this mesh + AZStd::string sceneRelativeFilePath; + bool relativePathFound = true; + AzToolsFramework::AssetSystemRequestBus::BroadcastResult(relativePathFound, &AzToolsFramework::AssetSystemRequestBus::Events::GetRelativeProductPathFromFullSourceOrProductPath, sourceSceneFullFilePath, sceneRelativeFilePath); + + if (relativePathFound) + { + AZ::StringFunc::Path::StripFullName(sceneRelativeFilePath); + + // Get the folder the masks are supposed to be in + AZStd::string folderName; + AZ::StringFunc::Path::GetFileName(sourceSceneFullFilePath.c_str(), folderName); + folderName += "_wrinklemasks"; + + // Note: for now, we're assuming the mask is always authored as a .tif + AZStd::string blendMaskFileName = blendShapeName + "_wrinklemask.tif.streamingimage"; + + AZStd::string maskFolderAndFile; + AZ::StringFunc::Path::Join(folderName.c_str(), blendMaskFileName.c_str(), maskFolderAndFile); + + AZStd::string maskRelativePath; + AZ::StringFunc::Path::Join(sceneRelativeFilePath.c_str(), maskFolderAndFile.c_str(), maskRelativePath); + AZ::StringFunc::Path::Normalize(maskRelativePath); + + // Now see if the file exists + AZ::Data::AssetId maskAssetId; + Data::AssetCatalogRequestBus::BroadcastResult(maskAssetId, &Data::AssetCatalogRequests::GetAssetIdByPath, maskRelativePath.c_str(), AZ::Data::s_invalidAssetType, false); + + if (maskAssetId.IsValid()) + { + // Flush asset manager events to ensure no asset references are held by closures queued on Ebuses. + AZ::Data::AssetManager::Instance().DispatchEvents(); + + imageAsset.Create(maskAssetId, AZ::Data::AssetLoadBehavior::PreLoad, false); + } + } + return imageAsset; + } } // namespace AZ::RPI diff --git a/Gems/Atom/RPI/Code/Source/RPI.Builders/Model/MorphTargetExporter.h b/Gems/Atom/RPI/Code/Source/RPI.Builders/Model/MorphTargetExporter.h index d968a803d6..4845d7d1da 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Builders/Model/MorphTargetExporter.h +++ b/Gems/Atom/RPI/Code/Source/RPI.Builders/Model/MorphTargetExporter.h @@ -64,7 +64,11 @@ namespace AZ const AZStd::string& blendShapeName, const AZStd::shared_ptr& blendShapeData, const AZ::SceneAPI::DataTypes::MatrixType& globalTransform, - const AZ::SceneAPI::CoordinateSystemConverter& coordSysConverter); + const AZ::SceneAPI::CoordinateSystemConverter& coordSysConverter, + const AZStd::string& sourceSceneFilename); + + // Find a wrinkle mask for this morph target, if it exists + Data::Asset GetWrinkleMask(const AZStd::string& sourceSceneFullFilePath, const AZStd::string& blendShapeName) const; }; } // namespace RPI } // namespace AZ diff --git a/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialTypeSourceData.cpp b/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialTypeSourceData.cpp index 2ab6ee92e9..109af70166 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialTypeSourceData.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialTypeSourceData.cpp @@ -422,7 +422,7 @@ namespace AZ const MaterialPropertyDescriptor* propertyDescriptor = materialTypeAssetCreator.GetMaterialPropertiesLayout()->GetPropertyDescriptor(propertyIndex); AZ::Name enumName = AZ::Name(property.m_value.GetValue()); - uint32_t enumValue = propertyDescriptor->GetEnumValue(enumName); + uint32_t enumValue = propertyDescriptor ? propertyDescriptor->GetEnumValue(enumName) : MaterialPropertyDescriptor::InvalidEnumValue; if (enumValue == MaterialPropertyDescriptor::InvalidEnumValue) { materialTypeAssetCreator.ReportError("Enum value '%s' couldn't be found in the 'enumValues' list", enumName.GetCStr()); diff --git a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Model/MorphTargetMetaAsset.cpp b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Model/MorphTargetMetaAsset.cpp index 313e0bea31..3c0f832807 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Model/MorphTargetMetaAsset.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Model/MorphTargetMetaAsset.cpp @@ -28,6 +28,7 @@ namespace AZ::RPI ->Field("numVertices", &MorphTargetMetaAsset::MorphTarget::m_numVertices) ->Field("minPositionDelta", &MorphTargetMetaAsset::MorphTarget::m_minPositionDelta) ->Field("maxPositionDelta", &MorphTargetMetaAsset::MorphTarget::m_maxPositionDelta) + ->Field("wrinkleMask", &MorphTargetMetaAsset::MorphTarget::m_wrinkleMask) ->Field("hasColorDeltas", &MorphTargetMetaAsset::MorphTarget::m_hasColorDeltas) ; } diff --git a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorInstance.cpp b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorInstance.cpp index d0116452b7..9079f639ba 100644 --- a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorInstance.cpp +++ b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorInstance.cpp @@ -28,6 +28,7 @@ #include #include +#include #include #include @@ -39,6 +40,8 @@ namespace AZ { namespace Render { + static constexpr uint32_t s_maxActiveWrinkleMasks = 16; + AZ_CLASS_ALLOCATOR_IMPL(AtomActorInstance, EMotionFX::Integration::EMotionFXAllocator, 0) AtomActorInstance::AtomActorInstance(AZ::EntityId entityId, @@ -413,6 +416,10 @@ namespace AZ EMotionFX::MorphSetup* morphSetup = m_actorInstance->GetActor()->GetMorphSetup(lodIndex); if (morphSetup) { + // Track all the masks/weights that are currently active + m_wrinkleMasks.clear(); + m_wrinkleMaskWeights.clear(); + uint32_t morphTargetCount = morphSetup->GetNumMorphTargets(); m_morphTargetWeights.clear(); for (uint32_t morphTargetIndex = 0; morphTargetIndex < morphTargetCount; ++morphTargetIndex) @@ -437,11 +444,28 @@ namespace AZ const EMotionFX::MorphTargetStandard::DeformData* deformData = morphTargetStandard->GetDeformData(deformDataIndex); if (deformData->mNumVerts > 0) { - m_morphTargetWeights.push_back(morphTargetSetupInstance->GetWeight()); + float weight = morphTargetSetupInstance->GetWeight(); + m_morphTargetWeights.push_back(weight); + + // If the morph target is active and it has a wrinkle mask + auto wrinkleMaskIter = m_morphTargetWrinkleMaskMapsByLod[lodIndex].find(morphTargetStandard); + if (weight > 0 && wrinkleMaskIter != m_morphTargetWrinkleMaskMapsByLod[lodIndex].end()) + { + // Add the wrinkle mask and weight, to be set on the material + m_wrinkleMasks.push_back(wrinkleMaskIter->second); + m_wrinkleMaskWeights.push_back(weight); + } } } } m_skinnedMeshRenderProxy->SetMorphTargetWeights(lodIndex, m_morphTargetWeights); + + // Until EMotionFX and Atom lods are synchronized [ATOM-13564] we don't know which EMotionFX lod to pull the weights from + // Until that is fixed, just use lod 0 [ATOM-15251] + if (lodIndex == 0) + { + UpdateWrinkleMasks(); + } } } } @@ -453,6 +477,8 @@ namespace AZ MaterialComponentRequestBus::EventResult(materials, m_entityId, &MaterialComponentRequests::GetMaterialOverrides); CreateRenderProxy(materials); + InitWrinkleMasks(); + TransformNotificationBus::Handler::BusConnect(m_entityId); MaterialComponentNotificationBus::Handler::BusConnect(m_entityId); MeshComponentRequestBus::Handler::BusConnect(m_entityId); @@ -573,5 +599,77 @@ namespace AZ { CreateSkinnedMeshInstance(); } + + void AtomActorInstance::InitWrinkleMasks() + { + EMotionFX::Actor* actor = m_actorAsset->GetActor(); + m_morphTargetWrinkleMaskMapsByLod.resize(m_skinnedMeshInputBuffers->GetLodCount()); + m_wrinkleMasks.reserve(s_maxActiveWrinkleMasks); + m_wrinkleMaskWeights.reserve(s_maxActiveWrinkleMasks); + + for (size_t lodIndex = 0; lodIndex < m_skinnedMeshInputBuffers->GetLodCount(); ++lodIndex) + { + EMotionFX::MorphSetup* morphSetup = actor->GetMorphSetup(lodIndex); + if (morphSetup) + { + const AZStd::vector& metaDatas = actor->GetMorphTargetMetaAsset()->GetMorphTargets(); + // Loop over all the EMotionFX morph targets + uint32_t numMorphTargets = morphSetup->GetNumMorphTargets(); + for (uint32_t morphTargetIndex = 0; morphTargetIndex < numMorphTargets; ++morphTargetIndex) + { + EMotionFX::MorphTargetStandard* morphTarget = static_cast(morphSetup->GetMorphTarget(morphTargetIndex)); + for (const RPI::MorphTargetMetaAsset::MorphTarget& metaData : metaDatas) + { + // Find the metaData associated with this morph target + if (metaData.m_morphTargetName == morphTarget->GetNameString() && metaData.m_wrinkleMask && metaData.m_numVertices > 0) + { + // If the metaData has a wrinkle mask, add it to the map + Data::Instance streamingImage = RPI::StreamingImage::FindOrCreate(metaData.m_wrinkleMask); + if (streamingImage) + { + m_morphTargetWrinkleMaskMapsByLod[lodIndex][morphTarget] = streamingImage; + } + } + } + } + } + } + } + + void AtomActorInstance::UpdateWrinkleMasks() + { + if (m_meshHandle) + { + Data::Instance wrinkleMaskObjectSrg = m_meshFeatureProcessor->GetObjectSrg(*m_meshHandle); + if (wrinkleMaskObjectSrg) + { + RHI::ShaderInputImageIndex wrinkleMasksIndex = wrinkleMaskObjectSrg->FindShaderInputImageIndex(Name{ "m_wrinkle_masks" }); + RHI::ShaderInputConstantIndex wrinkleMaskWeightsIndex = wrinkleMaskObjectSrg->FindShaderInputConstantIndex(Name{ "m_wrinkle_mask_weights" }); + RHI::ShaderInputConstantIndex wrinkleMaskCountIndex = wrinkleMaskObjectSrg->FindShaderInputConstantIndex(Name{ "m_wrinkle_mask_count" }); + if (wrinkleMasksIndex.IsValid() || wrinkleMaskWeightsIndex.IsValid() || wrinkleMaskCountIndex.IsValid()) + { + AZ_Error("AtomActorInstance", wrinkleMasksIndex.IsValid(), "m_wrinkle_masks not found on the ObjectSrg, but m_wrinkle_mask_weights and/or m_wrinkle_mask_count are being used."); + AZ_Error("AtomActorInstance", wrinkleMaskWeightsIndex.IsValid(), "m_wrinkle_mask_weights not found on the ObjectSrg, but m_wrinkle_masks and/or m_wrinkle_mask_count are being used."); + AZ_Error("AtomActorInstance", wrinkleMaskCountIndex.IsValid(), "m_wrinkle_mask_count not found on the ObjectSrg, but m_wrinkle_mask_weights and/or m_wrinkle_masks are being used."); + + if (m_wrinkleMasks.size()) + { + wrinkleMaskObjectSrg->SetImageArray(wrinkleMasksIndex, AZStd::array_view>(m_wrinkleMasks.data(), m_wrinkleMasks.size())); + + // Set the weights for any active masks + for (size_t i = 0; i < m_wrinkleMaskWeights.size(); ++i) + { + wrinkleMaskObjectSrg->SetConstant(wrinkleMaskWeightsIndex, m_wrinkleMaskWeights[i], i); + } + AZ_Error("AtomActorInstance", m_wrinkleMaskWeights.size() <= s_maxActiveWrinkleMasks, "The skinning shader supports no more than %d active morph targets with wrinkle masks.", s_maxActiveWrinkleMasks); + } + + wrinkleMaskObjectSrg->SetConstant(wrinkleMaskCountIndex, aznumeric_cast(m_wrinkleMasks.size())); + m_meshFeatureProcessor->QueueObjectSrgForCompile(*m_meshHandle); + } + } + } + } + } //namespace Render } // namespace AZ diff --git a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorInstance.h b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorInstance.h index 1002fcbde1..e05280e896 100644 --- a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorInstance.h +++ b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorInstance.h @@ -17,6 +17,7 @@ #include #include +#include #include @@ -29,6 +30,8 @@ #include #include #include +#include + #include #include @@ -41,6 +44,7 @@ namespace AZ::RPI { class Model; class Buffer; + class StreamingImage; } namespace AZ @@ -168,6 +172,11 @@ namespace AZ // SkinnedMeshOutputStreamNotificationBus void OnSkinnedMeshOutputStreamMemoryAvailable() override; + // Check to see if the skin material is being used, + // and if there are blend shapes with wrinkle masks that should be applied to it + void InitWrinkleMasks(); + void UpdateWrinkleMasks(); + AZStd::intrusive_ptr m_skinnedMeshInputBuffers = nullptr; AZStd::intrusive_ptr m_skinnedMeshInstance; AZ::Data::Instance m_boneTransforms = nullptr; @@ -179,6 +188,12 @@ namespace AZ AZ::TransformInterface* m_transformInterface = nullptr; AZStd::set m_waitForMaterialLoadIds; AZStd::vector m_morphTargetWeights; + + typedef AZStd::unordered_map> MorphTargetWrinkleMaskMap; + AZStd::vector m_morphTargetWrinkleMaskMapsByLod; + + AZStd::vector> m_wrinkleMasks; + AZStd::vector m_wrinkleMaskWeights; }; } // namespace Render