diff --git a/AutomatedTesting/Gem/PythonTests/scripting/TestSuite_Periodic.py b/AutomatedTesting/Gem/PythonTests/scripting/TestSuite_Periodic.py index 887bfe2426..58a0b42394 100755 --- a/AutomatedTesting/Gem/PythonTests/scripting/TestSuite_Periodic.py +++ b/AutomatedTesting/Gem/PythonTests/scripting/TestSuite_Periodic.py @@ -61,6 +61,7 @@ class TestAutomation(TestAutomationBase): from . import Graph_HappyPath_ZoomInZoomOut as test_module self._run_test(request, workspace, editor, test_module) + @pytest.mark.xfail(reason="Test fails on nightly build builds, it needs to be fixed.") def test_NodePalette_HappyPath_CanSelectNode(self, request, workspace, editor, launcher_platform): from . import NodePalette_HappyPath_CanSelectNode as test_module self._run_test(request, workspace, editor, test_module) @@ -113,6 +114,7 @@ class TestAutomation(TestAutomationBase): from . import Debugger_HappyPath_TargetMultipleGraphs as test_module self._run_test(request, workspace, editor, test_module) + @pytest.mark.xfail(reason="Test fails on nightly build builds, it needs to be fixed.") @pytest.mark.parametrize("level", ["tmp_level"]) def test_Debugger_HappyPath_TargetMultipleEntities(self, request, workspace, editor, launcher_platform, project, level): def teardown(): @@ -174,6 +176,7 @@ class TestAutomation(TestAutomationBase): from . import ScriptEvents_ReturnSetType_Successfully as test_module self._run_test(request, workspace, editor, test_module) + @pytest.mark.xfail(reason="Test fails on nightly build builds, it needs to be fixed.") def test_NodeCategory_ExpandOnClick(self, request, workspace, editor, launcher_platform): from . import NodeCategory_ExpandOnClick as test_module self._run_test(request, workspace, editor, test_module) @@ -187,6 +190,7 @@ class TestAutomation(TestAutomationBase): from . import VariableManager_UnpinVariableType_Works as test_module self._run_test(request, workspace, editor, test_module) + @pytest.mark.xfail(reason="Test fails on nightly build builds, it needs to be fixed.") def test_Node_HappyPath_DuplicateNode(self, request, workspace, editor, launcher_platform): from . import Node_HappyPath_DuplicateNode as test_module self._run_test(request, workspace, editor, test_module) @@ -263,6 +267,7 @@ class TestScriptCanvasTests(object): timeout=60, ) + @pytest.mark.xfail(reason="Test fails on nightly build builds, it needs to be fixed.") def test_VariableManager_Default_CreateDeleteVars(self, request, editor, launcher_platform): var_types = ["Boolean", "Color", "EntityID", "Number", "String", "Transform", "Vector2", "Vector3", "Vector4"] expected_lines = [f"Success: {var_type} variable is created" for var_type in var_types] diff --git a/Code/Framework/AzFramework/AzFramework/SurfaceData/SurfaceData.cpp b/Code/Framework/AzFramework/AzFramework/SurfaceData/SurfaceData.cpp index 55fec57bb6..e287c8d948 100644 --- a/Code/Framework/AzFramework/AzFramework/SurfaceData/SurfaceData.cpp +++ b/Code/Framework/AzFramework/AzFramework/SurfaceData/SurfaceData.cpp @@ -37,6 +37,7 @@ namespace AzFramework::SurfaceData { if (AZ::SerializeContext* serializeContext = azrtti_cast(context)) { + serializeContext->Class>(); serializeContext->Class() ->Field("m_position", &SurfacePoint::m_position) ->Field("m_normal", &SurfacePoint::m_normal) @@ -46,6 +47,7 @@ namespace AzFramework::SurfaceData if (AZ::BehaviorContext* behaviorContext = azrtti_cast(context)) { + behaviorContext->Class>(); behaviorContext->Class("AzFramework::SurfaceData::SurfacePoint") ->Attribute(AZ::Script::Attributes::Category, "SurfaceData") ->Constructor() diff --git a/Code/Framework/AzFramework/AzFramework/SurfaceData/SurfaceData.h b/Code/Framework/AzFramework/AzFramework/SurfaceData/SurfaceData.h index 9faa03f921..45e2ad3abb 100644 --- a/Code/Framework/AzFramework/AzFramework/SurfaceData/SurfaceData.h +++ b/Code/Framework/AzFramework/AzFramework/SurfaceData/SurfaceData.h @@ -10,13 +10,19 @@ #include #include #include -#include +#include namespace AzFramework::SurfaceData { namespace Constants { static constexpr const char* s_unassignedTagName = "(unassigned)"; + + //! The maximum number of surface weights that we can store. + //! For performance reasons, we want to limit this so that we can preallocate the max size in advance. + //! The current number is chosen to be higher than expected needs, but small enough to avoid being excessively wasteful. + //! (Dynamic structures would end up taking more memory than what we're preallocating) + static constexpr size_t MaxSurfaceWeights = 16; } struct SurfaceTagWeight @@ -70,7 +76,7 @@ namespace AzFramework::SurfaceData } }; - using SurfaceTagWeightList = AZStd::vector; + using SurfaceTagWeightList = AZStd::fixed_vector; struct SurfacePoint final { diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/EditorEntityUi/EditorEntityUiHandlerBase.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/EditorEntityUi/EditorEntityUiHandlerBase.cpp index 866080a83f..4896a5af49 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/EditorEntityUi/EditorEntityUiHandlerBase.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/EditorEntityUi/EditorEntityUiHandlerBase.cpp @@ -7,6 +7,7 @@ */ #include +#include #include @@ -117,9 +118,16 @@ namespace AzToolsFramework { } - bool EditorEntityUiHandlerBase::OnEntityDoubleClick([[maybe_unused]] AZ::EntityId entityId) const + bool EditorEntityUiHandlerBase::OnOutlinerItemDoubleClick([[maybe_unused]] const QModelIndex& index) const { return false; } + AZ::EntityId EditorEntityUiHandlerBase::GetEntityIdFromIndex(const QModelIndex& index) + { + QModelIndex firstColumnIndex = index.siblingAtColumn(EntityOutlinerListModel::ColumnName); + + return AZ::EntityId(firstColumnIndex.data(EntityOutlinerListModel::EntityIdRole).value()); + } + } // namespace AzToolsFramework diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/EditorEntityUi/EditorEntityUiHandlerBase.h b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/EditorEntityUi/EditorEntityUiHandlerBase.h index 96d393efa1..948c26d710 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/EditorEntityUi/EditorEntityUiHandlerBase.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/EditorEntityUi/EditorEntityUiHandlerBase.h @@ -21,7 +21,6 @@ class QTreeView; namespace AzToolsFramework { //! Defines a handler that can customize entity UI appearance and behavior in the Entity Outliner. - //! This class is meant to be abstract, entities do not have a handler by default. class EditorEntityUiHandlerBase { protected: @@ -33,7 +32,7 @@ namespace AzToolsFramework public: EditorEntityUiHandlerId GetHandlerId(); - // # Entity Outliner + // # Entity Outliner Item //! Returns the item info string that is appended to the item name in the Outliner. virtual QString GenerateItemInfoString(AZ::EntityId entityId) const; @@ -41,10 +40,12 @@ namespace AzToolsFramework virtual QString GenerateItemTooltip(AZ::EntityId entityId) const; //! Returns the item icon pixmap to display in the Outliner. virtual QIcon GenerateItemIcon(AZ::EntityId entityId) const; - //! Returns whether the element's lock and visibility state should be accessible in the Outliner - virtual bool CanToggleLockVisibility(AZ::EntityId entityId) const; //! Returns whether the element's name should be editable virtual bool CanRename(AZ::EntityId entityId) const; + //! Returns whether the element's lock and visibility state should be accessible in the Outliner + virtual bool CanToggleLockVisibility(AZ::EntityId entityId) const; + + // Qt-specific painting functions //! Paints the background of the item in the Outliner. virtual void PaintItemBackground(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const; @@ -54,24 +55,27 @@ namespace AzToolsFramework //! Paints the background of the descendant branches of the item in the Outliner. virtual void PaintDescendantBranchBackground(QPainter* painter, const QTreeView* view, const QRect& rect, const QModelIndex& index, const QModelIndex& descendantIndex) const; - //! Paints visual elements on the foreground of the item in the Outliner. virtual void PaintItemForeground(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const; //! Paints visual elements on the foreground of the descendants of the item in the Outliner. virtual void PaintDescendantForeground(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index, const QModelIndex& descendantIndex) const; + // Outliner-specific interactions + //! Triggered when the entity is clicked in the Outliner. //! @return True if the click has been handled and should not be propagated, false otherwise. virtual bool OnOutlinerItemClick(const QPoint& position, const QStyleOptionViewItem& option, const QModelIndex& index) const; + //! Triggered when the entity is double-clicked in the Outliner. + //! @return True if the double-click has been handled and should not be propagated, false otherwise. + virtual bool OnOutlinerItemDoubleClick(const QModelIndex& index) const; //! Triggered when an entity's children are expanded in the Outliner. virtual void OnOutlinerItemExpand(const QModelIndex& index) const; //! Triggered when an entity's children are collapsed in the Outliner. virtual void OnOutlinerItemCollapse(const QModelIndex& index) const; - //! Triggered when the entity is double clicked in the Outliner or in the Viewport. - //! @return True if the double click has been handled and should not be propagated, false otherwise. - virtual bool OnEntityDoubleClick(AZ::EntityId entityId) const; + protected: + static AZ::EntityId GetEntityIdFromIndex(const QModelIndex& index); private: EditorEntityUiHandlerId m_handlerId = 0; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerWidget.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerWidget.cpp index 52b688543a..f4855cb715 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerWidget.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerWidget.cpp @@ -945,7 +945,7 @@ namespace AzToolsFramework { if (AZ::EntityId entityId = GetEntityIdFromIndex(index); auto entityUiHandler = m_editorEntityUiInterface->GetHandler(entityId)) { - entityUiHandler->OnEntityDoubleClick(entityId); + entityUiHandler->OnOutlinerItemDoubleClick(index); } } diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/LevelRootUiHandler.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/LevelRootUiHandler.cpp index b34bbff298..9c5c478a2f 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/LevelRootUiHandler.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/LevelRootUiHandler.cpp @@ -33,7 +33,7 @@ namespace AzToolsFramework } } - QIcon LevelRootUiHandler::GenerateItemIcon(AZ::EntityId /*entityId*/) const + QIcon LevelRootUiHandler::GenerateItemIcon([[maybe_unused]] AZ::EntityId entityId) const { return QIcon(m_levelRootIconPath); } @@ -62,17 +62,18 @@ namespace AzToolsFramework return infoString; } - bool LevelRootUiHandler::CanToggleLockVisibility(AZ::EntityId /*entityId*/) const + bool LevelRootUiHandler::CanToggleLockVisibility([[maybe_unused]] AZ::EntityId entityId) const { return false; } - bool LevelRootUiHandler::CanRename(AZ::EntityId /*entityId*/) const + bool LevelRootUiHandler::CanRename([[maybe_unused]] AZ::EntityId entityId) const { return false; } - void LevelRootUiHandler::PaintItemBackground(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& /*index*/) const + void LevelRootUiHandler::PaintItemBackground( + QPainter* painter, const QStyleOptionViewItem& option, [[maybe_unused]] const QModelIndex& index) const { if (!painter) { @@ -94,8 +95,10 @@ namespace AzToolsFramework painter->restore(); } - bool LevelRootUiHandler::OnEntityDoubleClick(AZ::EntityId entityId) const + bool LevelRootUiHandler::OnOutlinerItemDoubleClick(const QModelIndex& index) const { + AZ::EntityId entityId = GetEntityIdFromIndex(index); + if (auto prefabFocusPublicInterface = AZ::Interface::Get(); !prefabFocusPublicInterface->IsOwningPrefabBeingFocused(entityId)) { diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/LevelRootUiHandler.h b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/LevelRootUiHandler.h index 3f7f56670e..a43aa0606a 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/LevelRootUiHandler.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/LevelRootUiHandler.h @@ -33,7 +33,7 @@ namespace AzToolsFramework bool CanToggleLockVisibility(AZ::EntityId entityId) const override; bool CanRename(AZ::EntityId entityId) const override; void PaintItemBackground(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override; - bool OnEntityDoubleClick(AZ::EntityId entityId) const override; + bool OnOutlinerItemDoubleClick(const QModelIndex& index) const override; private: Prefab::PrefabPublicInterface* m_prefabPublicInterface = nullptr; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabUiHandler.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabUiHandler.cpp index 69d6ae82ca..f3579c223c 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabUiHandler.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabUiHandler.cpp @@ -99,7 +99,7 @@ namespace AzToolsFramework return; } - AZ::EntityId entityId(index.data(EntityOutlinerListModel::EntityIdRole).value()); + AZ::EntityId entityId = GetEntityIdFromIndex(index); const bool isFirstColumn = index.column() == EntityOutlinerListModel::ColumnName; const bool isLastColumn = index.column() == EntityOutlinerListModel::ColumnLockToggle; QModelIndex firstColumnIndex = index.siblingAtColumn(EntityOutlinerListModel::ColumnName); @@ -183,7 +183,7 @@ namespace AzToolsFramework return; } - AZ::EntityId entityId(index.data(EntityOutlinerListModel::EntityIdRole).value()); + AZ::EntityId entityId = GetEntityIdFromIndex(index); const QTreeView* outlinerTreeView(qobject_cast(option.widget)); const int ancestorLeft = outlinerTreeView->visualRect(index).left() + (m_prefabBorderThickness / 2) - 1; @@ -283,7 +283,7 @@ namespace AzToolsFramework void PrefabUiHandler::PaintItemForeground(QPainter* painter, const QStyleOptionViewItem& option, [[maybe_unused]] const QModelIndex& index) const { - AZ::EntityId entityId(index.data(EntityOutlinerListModel::EntityIdRole).value()); + AZ::EntityId entityId = GetEntityIdFromIndex(index); const QPoint offset = QPoint(-18, 3); QModelIndex firstColumnIndex = index.siblingAtColumn(EntityOutlinerListModel::ColumnName); const int iconSize = 16; @@ -385,7 +385,7 @@ namespace AzToolsFramework bool PrefabUiHandler::OnOutlinerItemClick(const QPoint& position, const QStyleOptionViewItem& option, const QModelIndex& index) const { - AZ::EntityId entityId(index.data(EntityOutlinerListModel::EntityIdRole).value()); + AZ::EntityId entityId = GetEntityIdFromIndex(index); const QPoint offset = QPoint(-18, 3); if (m_prefabFocusPublicInterface->IsOwningPrefabInFocusHierarchy(entityId)) @@ -411,7 +411,7 @@ namespace AzToolsFramework void PrefabUiHandler::OnOutlinerItemCollapse(const QModelIndex& index) const { - AZ::EntityId entityId(index.data(EntityOutlinerListModel::EntityIdRole).value()); + AZ::EntityId entityId = GetEntityIdFromIndex(index); if (m_prefabFocusPublicInterface->IsOwningPrefabBeingFocused(entityId)) { @@ -420,8 +420,10 @@ namespace AzToolsFramework } } - bool PrefabUiHandler::OnEntityDoubleClick(AZ::EntityId entityId) const + bool PrefabUiHandler::OnOutlinerItemDoubleClick(const QModelIndex& index) const { + AZ::EntityId entityId = GetEntityIdFromIndex(index); + if (!m_prefabFocusPublicInterface->IsOwningPrefabBeingFocused(entityId)) { // Focus on this prefab diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabUiHandler.h b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabUiHandler.h index a1c624f85a..7166629cf3 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabUiHandler.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabUiHandler.h @@ -43,8 +43,8 @@ namespace AzToolsFramework const QModelIndex& index, const QModelIndex& descendantIndex) const override; bool OnOutlinerItemClick(const QPoint& position, const QStyleOptionViewItem& option, const QModelIndex& index) const override; + bool OnOutlinerItemDoubleClick(const QModelIndex& index) const override; void OnOutlinerItemCollapse(const QModelIndex& index) const override; - bool OnEntityDoubleClick(AZ::EntityId entityId) const override; protected: Prefab::PrefabFocusPublicInterface* m_prefabFocusPublicInterface = nullptr; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyAssetCtrl.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyAssetCtrl.cpp index bd0cb3844a..e4a1a35b2c 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyAssetCtrl.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyAssetCtrl.cpp @@ -102,7 +102,7 @@ namespace AzToolsFramework m_editButton->setAutoRaise(true); m_editButton->setIcon(QIcon(":/stylesheet/img/UI20/open-in-internal-app.svg")); m_editButton->setToolTip("Edit asset"); - m_editButton->setVisible(false); + SetEditButtonVisible(false); connect(m_editButton, &QToolButton::clicked, this, &PropertyAssetCtrl::OnEditButtonClicked); @@ -961,12 +961,16 @@ namespace AzToolsFramework AzFramework::StringFunc::Path::GetFileName(assetPath.c_str(), m_defaultAssetHint); } m_browseEdit->setPlaceholderText((m_defaultAssetHint + m_DefaultSuffix).c_str()); + + UpdateEditButton(); } void PropertyAssetCtrl::UpdateAssetDisplay() { UpdateThumbnail(); + UpdateEditButton(); + if (m_currentAssetType == AZ::Data::s_invalidAssetType) { return; @@ -1109,7 +1113,9 @@ namespace AzToolsFramework void PropertyAssetCtrl::SetEditButtonVisible(bool visible) { - m_editButton->setVisible(visible); + m_showEditButton = visible; + m_editButton->setVisible(m_showEditButton); + UpdateEditButton(); } void PropertyAssetCtrl::SetEditButtonIcon(const QIcon& icon) @@ -1205,6 +1211,15 @@ namespace AzToolsFramework m_thumbnail->ClearThumbnail(); } + void PropertyAssetCtrl::UpdateEditButton() + { + // if Edit button is in use (shown), enable/disable it depending on the current asset id. + if (m_showEditButton && m_disableEditButtonWhenNoAssetSelected) + { + m_editButton->setEnabled(GetCurrentAssetID().IsValid()); + } + } + void PropertyAssetCtrl::SetClearButtonEnabled(bool enable) { m_browseEdit->setClearButtonEnabled(enable); @@ -1236,6 +1251,17 @@ namespace AzToolsFramework return m_hideProductFilesInAssetPicker; } + void PropertyAssetCtrl::SetDisableEditButtonWhenNoAssetSelected(bool disableEditButtonWhenNoAssetSelected) + { + m_disableEditButtonWhenNoAssetSelected = disableEditButtonWhenNoAssetSelected; + UpdateEditButton(); + } + + bool PropertyAssetCtrl::GetDisableEditButtonWhenNoAssetSelected() const + { + return m_disableEditButtonWhenNoAssetSelected; + } + void PropertyAssetCtrl::SetShowThumbnail(bool enable) { m_showThumbnail = enable; @@ -1349,6 +1375,12 @@ namespace AzToolsFramework GUI->SetEditButtonTooltip(tr(buttonTooltip.c_str())); } } + else if (attrib == AZ_CRC_CE("DisableEditButtonWhenNoAssetSelected")) + { + bool disableEditButtonWhenNoAssetSelected = false; + attrValue->Read(disableEditButtonWhenNoAssetSelected); + GUI->SetDisableEditButtonWhenNoAssetSelected(disableEditButtonWhenNoAssetSelected); + } else if (attrib == AZ::Edit::Attributes::DefaultAsset) { AZ::Data::AssetId assetId; @@ -1597,6 +1629,12 @@ namespace AzToolsFramework GUI->SetEditButtonTooltip(tr(buttonTooltip.c_str())); } } + else if (attrib == AZ_CRC_CE("DisableEditButtonWhenNoAssetSelected")) + { + bool disableEditButtonWhenNoAssetSelected = false; + attrValue->Read(disableEditButtonWhenNoAssetSelected); + GUI->SetDisableEditButtonWhenNoAssetSelected(disableEditButtonWhenNoAssetSelected); + } } void SimpleAssetPropertyHandlerDefault::WriteGUIValuesIntoProperty(size_t index, PropertyAssetCtrl* GUI, property_t& instance, InstanceDataNode* node) diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyAssetCtrl.hxx b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyAssetCtrl.hxx index d6ff1b8de6..87fd691b20 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyAssetCtrl.hxx +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyAssetCtrl.hxx @@ -159,6 +159,10 @@ namespace AzToolsFramework //! By default the asset picker shows both on an AZ::Asset<> property. You can hide product assets with this flag. bool m_hideProductFilesInAssetPicker = false; + //! True to disable the edit button when there is no asset currently selected. + bool m_disableEditButtonWhenNoAssetSelected = false; + + bool m_showEditButton = false; bool m_showThumbnail = false; bool m_showThumbnailDropDownButton = false; EditCallbackType* m_thumbnailCallback = nullptr; @@ -220,6 +224,9 @@ namespace AzToolsFramework void SetHideProductFilesInAssetPicker(bool hide); bool GetHideProductFilesInAssetPicker() const; + void SetDisableEditButtonWhenNoAssetSelected(bool disableEditButtonWhenNoAssetSelected); + bool GetDisableEditButtonWhenNoAssetSelected() const; + // Enable and configure a thumbnail widget that displays an asset preview and dropdown arrow for a dropdown menu void SetShowThumbnail(bool enable); bool GetShowThumbnail() const; @@ -250,6 +257,7 @@ namespace AzToolsFramework private: void UpdateThumbnail(); + void UpdateEditButton(); }; class AssetPropertyHandlerDefault diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyEditorAPI_Internals.h b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyEditorAPI_Internals.h index ecd27baed4..f1ddc2268d 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyEditorAPI_Internals.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyEditorAPI_Internals.h @@ -154,6 +154,16 @@ namespace AzToolsFramework (void)debugName; } + // provides an option to specify reading parent element attributes. + // This allows parent elements to override attributes of their children if needed. + virtual void ConsumeParentAttribute(WidgetType* widget, AZ::u32 attrib, PropertyAttributeReader* attrValue, const char* debugName) + { + (void)widget; + (void)attrib; + (void)attrValue; + (void)debugName; + } + // override GetFirstInTabOrder, GetLastInTabOrder in your base class to define which widget gets focus first when pressing tab, // and also what widget is last. // for example, if your widget is a compound widget and contains, say, 5 buttons diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyEditorAPI_Internals_Impl.h b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyEditorAPI_Internals_Impl.h index 2ebacfa558..ec428de951 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyEditorAPI_Internals_Impl.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyEditorAPI_Internals_Impl.h @@ -40,7 +40,14 @@ namespace AzToolsFramework } void* classInstance = parent->FirstInstance(); // pointer to the owner class so we can read member variables and functions - auto consumeAttributes = [&](const auto& attributes, const char* name) + + void* parentClassInstance = nullptr; + if (InstanceDataNode* parentInstanceDataNode = parent->GetParent()) + { + parentClassInstance = parentInstanceDataNode->FirstInstance(); + } + + auto consumeAttributes = [this, classInstance, wid](const auto& attributes, const char* name) { for (size_t i = 0; i < attributes.size(); ++i) { @@ -50,25 +57,43 @@ namespace AzToolsFramework } }; + auto consumeParentAttributes = [this, parentClassInstance, wid](const auto& attributes, const char* name) + { + if (parentClassInstance) + { + for (size_t i = 0; i < attributes.size(); ++i) + { + const auto& attrPair = attributes[i]; + PropertyAttributeReader reader(parentClassInstance, &*attrPair.second); + ConsumeParentAttribute(wid, attrPair.first, &reader, name); + } + } + }; + const AZ::SerializeContext::ClassElement* element = dataNode->GetElementMetadata(); if (element) { consumeAttributes(element->m_attributes, element->m_name); - const AZ::Edit::ElementData* elementEdit = dataNode->GetElementEditMetadata(); - if (elementEdit) + + if (const AZ::Edit::ElementData* elementEdit = dataNode->GetElementEditMetadata(); + elementEdit != nullptr) { consumeAttributes(elementEdit->m_attributes, elementEdit->m_name); } - } - if (dataNode->GetClassMetadata()) - { - const AZ::Edit::ClassData* classEditData = dataNode->GetClassMetadata()->m_editData; - if (classEditData) + const AZ::SerializeContext::ClassElement* parentElement = parent != dataNode ? + dataNode->GetElementMetadata() : + nullptr; + + if (parentElement != nullptr) { - for (auto it = classEditData->m_elements.begin(); it != classEditData->m_elements.end(); ++it) + // Reuse the current instance element name for the debug name + consumeParentAttributes(parentElement->m_attributes, element->m_name); + + if (const AZ::Edit::ElementData* elementEdit = parent->GetElementEditMetadata(); + elementEdit != nullptr) { - consumeAttributes(it->m_attributes, it->m_name); + consumeParentAttributes(elementEdit->m_attributes, elementEdit->m_name); } } } diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyEnumComboBoxCtrl.hxx b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyEnumComboBoxCtrl.hxx index 206b972b37..fc1d24b7a5 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyEnumComboBoxCtrl.hxx +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyEnumComboBoxCtrl.hxx @@ -66,6 +66,12 @@ namespace AzToolsFramework class GenericEnumPropertyComboBoxHandler : public GenericComboBoxHandler { + virtual void ConsumeParentAttribute(GenericComboBoxCtrlBase* GUI, AZ::u32 attrib, PropertyAttributeReader* attrValue, const char* debugName) override + { + // Simply re-route to ConsumeAttribute since no special logic is needed. + ConsumeAttribute(GUI, attrib, attrValue, debugName); + } + virtual void ConsumeAttribute(GenericComboBoxCtrlBase* GUI, AZ::u32 attrib, PropertyAttributeReader* attrValue, const char* debugName) override { (void)debugName; diff --git a/Code/Framework/AzToolsFramework/Tests/FocusMode/ContainerEntitySelectionTests.cpp b/Code/Framework/AzToolsFramework/Tests/FocusMode/ContainerEntitySelectionTests.cpp index 0ad61b924b..483db4076e 100644 --- a/Code/Framework/AzToolsFramework/Tests/FocusMode/ContainerEntitySelectionTests.cpp +++ b/Code/Framework/AzToolsFramework/Tests/FocusMode/ContainerEntitySelectionTests.cpp @@ -15,7 +15,7 @@ namespace UnitTest // When no containers are in the way, the function will just return the entityId of the entity that was clicked. // Click on Car Entity - ClickAtWorldPositionOnViewport(WorldCarEntityPosition); + ClickAtWorldPositionOnViewport(s_worldCarEntityPosition); // Verify the correct entity is selected auto selectedEntitiesAfter = GetSelectedEntities(); @@ -29,7 +29,7 @@ namespace UnitTest m_containerEntityInterface->RegisterEntityAsContainer(m_entityMap[StreetEntityName]); // Containers are closed by default // Click on Car Entity - ClickAtWorldPositionOnViewport(WorldCarEntityPosition); + ClickAtWorldPositionOnViewport(s_worldCarEntityPosition); // Verify the correct entity is selected auto selectedEntitiesAfter = GetSelectedEntities(); @@ -47,7 +47,7 @@ namespace UnitTest m_containerEntityInterface->SetContainerOpen(m_entityMap[StreetEntityName], true); // Click on Car Entity - ClickAtWorldPositionOnViewport(WorldCarEntityPosition); + ClickAtWorldPositionOnViewport(s_worldCarEntityPosition); // Verify the correct entity is selected auto selectedEntitiesAfter = GetSelectedEntities(); @@ -65,7 +65,7 @@ namespace UnitTest m_containerEntityInterface->RegisterEntityAsContainer(m_entityMap[CityEntityName]); // Click on Car Entity - ClickAtWorldPositionOnViewport(WorldCarEntityPosition); + ClickAtWorldPositionOnViewport(s_worldCarEntityPosition); // Verify the correct entity is selected auto selectedEntitiesAfter = GetSelectedEntities(); @@ -85,7 +85,7 @@ namespace UnitTest m_containerEntityInterface->SetContainerOpen(m_entityMap[CityEntityName], true); // Click on Car Entity - ClickAtWorldPositionOnViewport(WorldCarEntityPosition); + ClickAtWorldPositionOnViewport(s_worldCarEntityPosition); // Verify the correct entity is selected auto selectedEntitiesAfter = GetSelectedEntities(); diff --git a/Code/Framework/AzToolsFramework/Tests/FocusMode/EditorFocusModeFixture.cpp b/Code/Framework/AzToolsFramework/Tests/FocusMode/EditorFocusModeFixture.cpp index 90becfa46f..374b85c01d 100644 --- a/Code/Framework/AzToolsFramework/Tests/FocusMode/EditorFocusModeFixture.cpp +++ b/Code/Framework/AzToolsFramework/Tests/FocusMode/EditorFocusModeFixture.cpp @@ -8,6 +8,7 @@ #include +#include #include #include @@ -93,10 +94,13 @@ namespace UnitTest entity->CreateComponent(); entity->Activate(); - // Move the CarEntity so it's out of the way. - AZ::TransformBus::Event(m_entityMap[CarEntityName], &AZ::TransformBus::Events::SetWorldTranslation, WorldCarEntityPosition); + // Move the City so that it is in view + AZ::TransformBus::Event(m_entityMap[CityEntityName], &AZ::TransformBus::Events::SetWorldTranslation, s_worldCityEntityPosition); - // Setup the camera so the Car entity is in view. + // Move the CarEntity so that it's not overlapping with the rest + AZ::TransformBus::Event(m_entityMap[CarEntityName], &AZ::TransformBus::Events::SetWorldTranslation, s_worldCarEntityPosition); + + // Setup the camera so the entities is in view. AzFramework::SetCameraTransform( m_cameraState, AZ::Transform::CreateFromQuaternionAndTranslation( @@ -113,4 +117,5 @@ namespace UnitTest return entity->GetId(); } + } // namespace UnitTest diff --git a/Code/Framework/AzToolsFramework/Tests/FocusMode/EditorFocusModeFixture.h b/Code/Framework/AzToolsFramework/Tests/FocusMode/EditorFocusModeFixture.h index 0cf1be6ffd..88cc5ac921 100644 --- a/Code/Framework/AzToolsFramework/Tests/FocusMode/EditorFocusModeFixture.h +++ b/Code/Framework/AzToolsFramework/Tests/FocusMode/EditorFocusModeFixture.h @@ -8,7 +8,6 @@ #pragma once -#include #include #include @@ -38,9 +37,6 @@ namespace UnitTest AzToolsFramework::EntityIdList GetSelectedEntities(); AzFramework::EntityContextId m_editorEntityContextId = AzFramework::EntityContextId::CreateNull(); - AzFramework::CameraState m_cameraState; - - inline static const AZ::Vector3 CameraPosition = AZ::Vector3(10.0f, 15.0f, 10.0f); inline static const char* CityEntityName = "City"; inline static const char* StreetEntityName = "Street"; @@ -49,7 +45,11 @@ namespace UnitTest inline static const char* Passenger1EntityName = "Passenger1"; inline static const char* Passenger2EntityName = "Passenger2"; - inline static AZ::Vector3 WorldCarEntityPosition = AZ::Vector3(5.0f, 15.0f, 0.0f); + AzFramework::CameraState m_cameraState; + + inline static const AZ::Vector3 CameraPosition = AZ::Vector3(10.0f, 15.0f, 10.0f); + inline static AZ::Vector3 s_worldCityEntityPosition = AZ::Vector3(5.0f, 10.0f, 0.0f); + inline static AZ::Vector3 s_worldCarEntityPosition = AZ::Vector3(5.0f, 15.0f, 0.0f); }; } // namespace UnitTest diff --git a/Code/Framework/AzToolsFramework/Tests/FocusMode/EditorFocusModeSelectionFixture.h b/Code/Framework/AzToolsFramework/Tests/FocusMode/EditorFocusModeSelectionFixture.h index fe1de9b122..5dc323dd8c 100644 --- a/Code/Framework/AzToolsFramework/Tests/FocusMode/EditorFocusModeSelectionFixture.h +++ b/Code/Framework/AzToolsFramework/Tests/FocusMode/EditorFocusModeSelectionFixture.h @@ -45,5 +45,20 @@ namespace UnitTest // Click the entity in the viewport m_actionDispatcher->CameraState(m_cameraState)->MousePosition(carScreenPosition)->MouseLButtonDown()->MouseLButtonUp(); } + + void BoxSelectOnViewport() + { + // Calculate the position in screen space of where to begin and end the box select action + const auto beginningPositionWorldBoxSelect = AzFramework::WorldToScreen(AZ::Vector3(-10.0f, 15.0f, 5.0f), m_cameraState); + const auto endingPositionWorldBoxSelect = AzFramework::WorldToScreen(AZ::Vector3(10.0f, 15.0f, -5.0f), m_cameraState); + + // Perform a box select in the viewport + m_actionDispatcher->SetStickySelect(true) + ->CameraState(m_cameraState) + ->MousePosition(beginningPositionWorldBoxSelect) + ->MouseLButtonDown() + ->MousePosition(endingPositionWorldBoxSelect) + ->MouseLButtonUp(); + } }; } // namespace UnitTest diff --git a/Code/Framework/AzToolsFramework/Tests/FocusMode/EditorFocusModeSelectionTests.cpp b/Code/Framework/AzToolsFramework/Tests/FocusMode/EditorFocusModeSelectionTests.cpp index 1efcb15b30..ee338539a8 100644 --- a/Code/Framework/AzToolsFramework/Tests/FocusMode/EditorFocusModeSelectionTests.cpp +++ b/Code/Framework/AzToolsFramework/Tests/FocusMode/EditorFocusModeSelectionTests.cpp @@ -13,7 +13,7 @@ namespace UnitTest TEST_F(EditorFocusModeSelectionFixture, EditorFocusModeSelectionSelectEntityWithFocusOnLevel) { // Click on Car Entity - ClickAtWorldPositionOnViewport(WorldCarEntityPosition); + ClickAtWorldPositionOnViewport(s_worldCarEntityPosition); // Verify entity is selected auto selectedEntitiesAfter = GetSelectedEntities(); @@ -27,7 +27,7 @@ namespace UnitTest m_focusModeInterface->SetFocusRoot(m_entityMap[StreetEntityName]); // Click on Car Entity - ClickAtWorldPositionOnViewport(WorldCarEntityPosition); + ClickAtWorldPositionOnViewport(s_worldCarEntityPosition); // Verify entity is selected auto selectedEntitiesAfter = GetSelectedEntities(); @@ -41,7 +41,7 @@ namespace UnitTest m_focusModeInterface->SetFocusRoot(m_entityMap[CarEntityName]); // Click on Car Entity - ClickAtWorldPositionOnViewport(WorldCarEntityPosition); + ClickAtWorldPositionOnViewport(s_worldCarEntityPosition); // Verify entity is selected auto selectedEntitiesAfter = GetSelectedEntities(); @@ -55,7 +55,7 @@ namespace UnitTest m_focusModeInterface->SetFocusRoot(m_entityMap[SportsCarEntityName]); // Click on Car Entity - ClickAtWorldPositionOnViewport(WorldCarEntityPosition); + ClickAtWorldPositionOnViewport(s_worldCarEntityPosition); // Verify entity is selected auto selectedEntitiesAfter = GetSelectedEntities(); @@ -68,10 +68,71 @@ namespace UnitTest m_focusModeInterface->SetFocusRoot(m_entityMap[Passenger1EntityName]); // Click on Car Entity - ClickAtWorldPositionOnViewport(WorldCarEntityPosition); + ClickAtWorldPositionOnViewport(s_worldCarEntityPosition); // Verify entity is selected auto selectedEntitiesAfter = GetSelectedEntities(); EXPECT_EQ(selectedEntitiesAfter.size(), 0); } + + TEST_F(EditorFocusModeSelectionFixture, EditorFocusModeSelectionBoxSelectWithFocusOnLevel) + { + // Do a box select that includes all entities in the fixture + BoxSelectOnViewport(); + + // Entities are selected + using ::testing::UnorderedElementsAre; + auto selectedEntitiesAfter = GetSelectedEntities(); + EXPECT_THAT(selectedEntitiesAfter, + UnorderedElementsAre( + m_entityMap[CityEntityName], + m_entityMap[StreetEntityName], + m_entityMap[CarEntityName], + m_entityMap[Passenger1EntityName], + m_entityMap[SportsCarEntityName], + m_entityMap[Passenger2EntityName] + ) + ); + } + + TEST_F(EditorFocusModeSelectionFixture, EditorFocusModeSelectionBoxSelectWithFocusOnChild) + { + // Set the focus on the Passenger1 Entity (child of the entity) + m_focusModeInterface->SetFocusRoot(m_entityMap[StreetEntityName]); + + // Do a box select that includes all entities in the fixture + BoxSelectOnViewport(); + + // Entities are selected + using ::testing::UnorderedElementsAre; + auto selectedEntitiesAfter = GetSelectedEntities(); + EXPECT_THAT(selectedEntitiesAfter, + UnorderedElementsAre( + m_entityMap[StreetEntityName], + m_entityMap[CarEntityName], + m_entityMap[Passenger1EntityName], + m_entityMap[SportsCarEntityName], + m_entityMap[Passenger2EntityName] + ) + ); + } + + TEST_F(EditorFocusModeSelectionFixture, EditorFocusModeSelectionBoxSelectWithFocusOnLeaf) + { + // Set the focus on the Passenger1 Entity (child of the entity) + m_focusModeInterface->SetFocusRoot(m_entityMap[Passenger1EntityName]); + + // Do a box select that includes all entities in the fixture + BoxSelectOnViewport(); + + // Entities are selected + using ::testing::UnorderedElementsAre; + auto selectedEntitiesAfter = GetSelectedEntities(); + EXPECT_THAT(selectedEntitiesAfter, + UnorderedElementsAre( + m_entityMap[Passenger1EntityName] + ) + ); + } + } // namespace UnitTest diff --git a/Gems/Atom/Asset/Shader/Code/Source/Editor/AzslShaderBuilderSystemComponent.cpp b/Gems/Atom/Asset/Shader/Code/Source/Editor/AzslShaderBuilderSystemComponent.cpp index 7e510e2e3a..43d67cc95c 100644 --- a/Gems/Atom/Asset/Shader/Code/Source/Editor/AzslShaderBuilderSystemComponent.cpp +++ b/Gems/Atom/Asset/Shader/Code/Source/Editor/AzslShaderBuilderSystemComponent.cpp @@ -82,7 +82,7 @@ namespace AZ // Register Shader Asset Builder AssetBuilderSDK::AssetBuilderDesc shaderAssetBuilderDescriptor; shaderAssetBuilderDescriptor.m_name = "Shader Asset Builder"; - shaderAssetBuilderDescriptor.m_version = 110; // Add "Definitions" field to shader asset to support convenient addition of preprocessor definitions + shaderAssetBuilderDescriptor.m_version = 111; // Enable shader PDB generation globally if Atom/GraphicsDevMode settings registry key is set shaderAssetBuilderDescriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern( AZStd::string::format("*.%s", RPI::ShaderSourceData::Extension), AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard)); shaderAssetBuilderDescriptor.m_busId = azrtti_typeid(); shaderAssetBuilderDescriptor.m_createJobFunction = AZStd::bind(&ShaderAssetBuilder::CreateJobs, &m_shaderAssetBuilder, AZStd::placeholders::_1, AZStd::placeholders::_2); diff --git a/Gems/Atom/Bootstrap/Code/Source/BootstrapSystemComponent.cpp b/Gems/Atom/Bootstrap/Code/Source/BootstrapSystemComponent.cpp index ef03a839d7..60ca6ff429 100644 --- a/Gems/Atom/Bootstrap/Code/Source/BootstrapSystemComponent.cpp +++ b/Gems/Atom/Bootstrap/Code/Source/BootstrapSystemComponent.cpp @@ -318,10 +318,8 @@ namespace AZ RPI::RenderPipelineDescriptor renderPipelineDescriptor = *RPI::GetDataFromAnyAsset(pipelineAsset); renderPipelineDescriptor.m_name = AZStd::string::format("%s_%i", renderPipelineDescriptor.m_name.c_str(), viewportContext->GetId()); - // Make sure non-msaa super variant is used for non-msaa pipeline - bool isNonMsaaPipeline = (renderPipelineDescriptor.m_renderSettings.m_multisampleState.m_samples == 1); - const char* supervariantName = isNonMsaaPipeline ? AZ::RPI::NoMsaaSupervariantName : ""; - AZ::RPI::ShaderSystemInterface::Get()->SetSupervariantName(AZ::Name(supervariantName)); + // The default pipeline determines the initial MSAA state for the application + AZ::RPI::RPISystemInterface::Get()->SetApplicationMultisampleState(renderPipelineDescriptor.m_renderSettings.m_multisampleState); if (!scene->GetRenderPipeline(AZ::Name(renderPipelineDescriptor.m_name))) { diff --git a/Gems/Atom/Feature/Common/Assets/Passes/EnvironmentCubeMapDepthMSAA.pass b/Gems/Atom/Feature/Common/Assets/Passes/EnvironmentCubeMapDepthMSAA.pass index 5abbe7d62d..cb94e75389 100644 --- a/Gems/Atom/Feature/Common/Assets/Passes/EnvironmentCubeMapDepthMSAA.pass +++ b/Gems/Atom/Feature/Common/Assets/Passes/EnvironmentCubeMapDepthMSAA.pass @@ -34,11 +34,11 @@ "Attachment": "Output" } }, + "MultisampleSource": { + "Pass": "Pipeline" + }, "ImageDescriptor": { "Format": "D32_FLOAT_S8X24_UINT", - "MultisampleState": { - "samples": 4 - }, "SharedQueueMask": "Graphics" } } diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/DiffuseProbeGridRender.precompiledshader b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/DiffuseProbeGridRender.precompiledshader index 97c58091a2..613714b0b6 100644 --- a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/DiffuseProbeGridRender.precompiledshader +++ b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/DiffuseProbeGridRender.precompiledshader @@ -29,6 +29,24 @@ "RootShaderVariantAssetFileName": "diffuseprobegridrender_null_0.azshadervariant" } ] + }, + { + "Name": "NoMSAA", + "RootShaderVariantAssets": + [ + { + "APIName": "dx12", + "RootShaderVariantAssetFileName": "diffuseprobegridrender-nomsaa_dx12_0.azshadervariant" + }, + { + "APIName": "vulkan", + "RootShaderVariantAssetFileName": "diffuseprobegridrender-nomsaa_vulkan_0.azshadervariant" + }, + { + "APIName": "null", + "RootShaderVariantAssetFileName": "diffuseprobegridrender-nomsaa_null_0.azshadervariant" + } + ] } ] } diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridrender-nomsaa_dx12_0.azshadervariant b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridrender-nomsaa_dx12_0.azshadervariant new file mode 100644 index 0000000000..31ca52ba02 Binary files /dev/null and b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridrender-nomsaa_dx12_0.azshadervariant differ diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridrender-nomsaa_null_0.azshadervariant b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridrender-nomsaa_null_0.azshadervariant new file mode 100644 index 0000000000..299490c1bd Binary files /dev/null and b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridrender-nomsaa_null_0.azshadervariant differ diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridrender-nomsaa_vulkan_0.azshadervariant b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridrender-nomsaa_vulkan_0.azshadervariant new file mode 100644 index 0000000000..1e0c497d0b Binary files /dev/null and b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridrender-nomsaa_vulkan_0.azshadervariant differ diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridrender.azshader b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridrender.azshader index 4676c42e8d..16db49af1b 100644 Binary files a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridrender.azshader and b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridrender.azshader differ diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridrender_dx12_0.azshadervariant b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridrender_dx12_0.azshadervariant index 9bf8e7f53f..23b0c807bf 100644 Binary files a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridrender_dx12_0.azshadervariant and b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridrender_dx12_0.azshadervariant differ diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridrender_null_0.azshadervariant b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridrender_null_0.azshadervariant index 98402384db..299490c1bd 100644 Binary files a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridrender_null_0.azshadervariant and b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridrender_null_0.azshadervariant differ diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridrender_vulkan_0.azshadervariant b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridrender_vulkan_0.azshadervariant index 5caf308476..447f0712d3 100644 Binary files a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridrender_vulkan_0.azshadervariant and b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridrender_vulkan_0.azshadervariant differ diff --git a/Gems/Atom/Feature/Common/Code/Source/DiffuseGlobalIllumination/DiffuseProbeGridFeatureProcessor.cpp b/Gems/Atom/Feature/Common/Code/Source/DiffuseGlobalIllumination/DiffuseProbeGridFeatureProcessor.cpp index 84c066604a..5e85c3b247 100644 --- a/Gems/Atom/Feature/Common/Code/Source/DiffuseGlobalIllumination/DiffuseProbeGridFeatureProcessor.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/DiffuseGlobalIllumination/DiffuseProbeGridFeatureProcessor.cpp @@ -92,21 +92,24 @@ namespace AZ AZ_Error("DiffuseProbeGridFeatureProcessor", m_probeGridRenderData.m_srgLayout != nullptr, "Failed to find ObjectSrg layout"); } - // initialize the buffer pools for the DiffuseProbeGrid visualization - m_visualizationBufferPools = RHI::RayTracingBufferPools::CreateRHIRayTracingBufferPools(); - m_visualizationBufferPools->Init(device); - - // load probe visualization model, the BLAS will be created in OnAssetReady() - m_visualizationModelAsset = AZ::RPI::AssetUtils::GetAssetByProductPath( - "Models/DiffuseProbeSphere.azmodel", - AZ::RPI::AssetUtils::TraceLevel::Assert); - - if (!m_visualizationModelAsset.IsReady()) + if (device->GetFeatures().m_rayTracing) { - m_visualizationModelAsset.QueueLoad(); - } + // initialize the buffer pools for the DiffuseProbeGrid visualization + m_visualizationBufferPools = RHI::RayTracingBufferPools::CreateRHIRayTracingBufferPools(); + m_visualizationBufferPools->Init(device); - Data::AssetBus::MultiHandler::BusConnect(m_visualizationModelAsset.GetId()); + // load probe visualization model, the BLAS will be created in OnAssetReady() + m_visualizationModelAsset = AZ::RPI::AssetUtils::GetAssetByProductPath( + "Models/DiffuseProbeSphere.azmodel", + AZ::RPI::AssetUtils::TraceLevel::Assert); + + if (!m_visualizationModelAsset.IsReady()) + { + m_visualizationModelAsset.QueueLoad(); + } + + Data::AssetBus::MultiHandler::BusConnect(m_visualizationModelAsset.GetId()); + } EnableSceneNotification(); } diff --git a/Gems/Atom/Feature/Common/Code/Source/ReflectionProbe/ReflectionProbe.cpp b/Gems/Atom/Feature/Common/Code/Source/ReflectionProbe/ReflectionProbe.cpp index f5aaec4faa..539531b5a0 100644 --- a/Gems/Atom/Feature/Common/Code/Source/ReflectionProbe/ReflectionProbe.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/ReflectionProbe/ReflectionProbe.cpp @@ -115,10 +115,6 @@ namespace AZ // all faces of the cubemap have been rendered, invoke the callback m_callback(m_environmentCubeMapPass->GetTextureData(), m_environmentCubeMapPass->GetTextureFormat()); - // remove the pipeline - m_scene->RemoveRenderPipeline(m_environmentCubeMapPipelineId); - m_environmentCubeMapPass = nullptr; - // restore exposures sceneSrg->SetConstant(m_globalIblExposureConstantIndex, m_previousGlobalIblExposure); sceneSrg->SetConstant(m_skyBoxExposureConstantIndex, m_previousSkyBoxExposure); @@ -223,6 +219,16 @@ namespace AZ } } + void ReflectionProbe::OnRenderEnd() + { + if (m_environmentCubeMapPass && m_environmentCubeMapPass->IsFinished()) + { + // remove the cubemap pipeline + // Note: this must be done here (not in Simulate) to avoid a race condition with other feature processors + m_scene->RemoveRenderPipeline(m_environmentCubeMapPipelineId); + m_environmentCubeMapPass = nullptr; + } + } void ReflectionProbe::SetTransform(const AZ::Transform& transform) { @@ -282,7 +288,7 @@ namespace AZ AZ::RPI::RenderPipelineDescriptor environmentCubeMapPipelineDesc; environmentCubeMapPipelineDesc.m_mainViewTagName = "MainCamera"; - environmentCubeMapPipelineDesc.m_renderSettings.m_multisampleState.m_samples = 4; + environmentCubeMapPipelineDesc.m_renderSettings.m_multisampleState = RPI::RPISystemInterface::Get()->GetApplicationMultisampleState(); environmentCubeMapPipelineDesc.m_renderSettings.m_size.m_width = RPI::EnvironmentCubeMapPass::CubeMapFaceSize; environmentCubeMapPipelineDesc.m_renderSettings.m_size.m_height = RPI::EnvironmentCubeMapPass::CubeMapFaceSize; diff --git a/Gems/Atom/Feature/Common/Code/Source/ReflectionProbe/ReflectionProbe.h b/Gems/Atom/Feature/Common/Code/Source/ReflectionProbe/ReflectionProbe.h index 17ef54367b..6ec9368167 100644 --- a/Gems/Atom/Feature/Common/Code/Source/ReflectionProbe/ReflectionProbe.h +++ b/Gems/Atom/Feature/Common/Code/Source/ReflectionProbe/ReflectionProbe.h @@ -75,6 +75,7 @@ namespace AZ void Init(RPI::Scene* scene, ReflectionRenderData* reflectionRenderData); void Simulate(uint32_t probeIndex); + void OnRenderEnd(); const Vector3& GetPosition() const { return m_transform.GetTranslation(); } const AZ::Transform& GetTransform() const { return m_transform; } diff --git a/Gems/Atom/Feature/Common/Code/Source/ReflectionProbe/ReflectionProbeFeatureProcessor.cpp b/Gems/Atom/Feature/Common/Code/Source/ReflectionProbe/ReflectionProbeFeatureProcessor.cpp index 155a159447..cfe807610b 100644 --- a/Gems/Atom/Feature/Common/Code/Source/ReflectionProbe/ReflectionProbeFeatureProcessor.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/ReflectionProbe/ReflectionProbeFeatureProcessor.cpp @@ -166,6 +166,18 @@ namespace AZ } } + void ReflectionProbeFeatureProcessor::OnRenderEnd() + { + // call OnRenderEnd on all reflection probes + for (uint32_t probeIndex = 0; probeIndex < m_reflectionProbes.size(); ++probeIndex) + { + AZStd::shared_ptr& reflectionProbe = m_reflectionProbes[probeIndex]; + AZ_Assert(reflectionProbe.use_count() > 1, "ReflectionProbe found with no corresponding owner, ensure that RemoveProbe() is called before releasing probe handles"); + + reflectionProbe->OnRenderEnd(); + } + } + ReflectionProbeHandle ReflectionProbeFeatureProcessor::AddProbe(const AZ::Transform& transform, bool useParallaxCorrection) { AZStd::shared_ptr reflectionProbe = AZStd::make_shared(); diff --git a/Gems/Atom/Feature/Common/Code/Source/ReflectionProbe/ReflectionProbeFeatureProcessor.h b/Gems/Atom/Feature/Common/Code/Source/ReflectionProbe/ReflectionProbeFeatureProcessor.h index ded36f5496..92fb3fe604 100644 --- a/Gems/Atom/Feature/Common/Code/Source/ReflectionProbe/ReflectionProbeFeatureProcessor.h +++ b/Gems/Atom/Feature/Common/Code/Source/ReflectionProbe/ReflectionProbeFeatureProcessor.h @@ -46,6 +46,7 @@ namespace AZ void Activate() override; void Deactivate() override; void Simulate(const FeatureProcessor::SimulatePacket& packet) override; + void OnRenderEnd() override; // find the reflection probe volumes that contain the position using ReflectionProbeVector = AZStd::vector>; diff --git a/Gems/Atom/RHI/Code/Include/Atom/RHI/RHIUtils.h b/Gems/Atom/RHI/Code/Include/Atom/RHI/RHIUtils.h index 73e8e1ad56..48a041ad7d 100644 --- a/Gems/Atom/RHI/Code/Include/Atom/RHI/RHIUtils.h +++ b/Gems/Atom/RHI/Code/Include/Atom/RHI/RHIUtils.h @@ -42,6 +42,9 @@ namespace AZ //! Returns if the current bakcend is a null renderer bool IsNullRenderer(); + + //! Returns true if the Atom/GraphicsDevMode settings registry key is set + bool IsGraphicsDevModeEnabled(); } } diff --git a/Gems/Atom/RHI/Code/Source/RHI/RHIUtils.cpp b/Gems/Atom/RHI/Code/Source/RHI/RHIUtils.cpp index bc4cde9406..de27f4ce18 100644 --- a/Gems/Atom/RHI/Code/Source/RHI/RHIUtils.cpp +++ b/Gems/Atom/RHI/Code/Source/RHI/RHIUtils.cpp @@ -9,9 +9,12 @@ #include #include #include +#include #include #include +static constexpr char GraphicsDevModeSetting[] = "/Atom/GraphicsDevMode"; + namespace AZ { namespace RHI @@ -134,5 +137,16 @@ namespace AZ } return false; } + + bool IsGraphicsDevModeEnabled() + { + AZ::SettingsRegistryInterface* settingsRegistry = AZ::SettingsRegistry::Get(); + bool graphicsDevMode = false; + if (settingsRegistry) + { + settingsRegistry->Get(graphicsDevMode, GraphicsDevModeSetting); + } + return graphicsDevMode; + } } } diff --git a/Gems/Atom/RHI/DX12/Code/Source/RHI.Builders/ShaderPlatformInterface.cpp b/Gems/Atom/RHI/DX12/Code/Source/RHI.Builders/ShaderPlatformInterface.cpp index ee293292e1..287c8e4fd5 100644 --- a/Gems/Atom/RHI/DX12/Code/Source/RHI.Builders/ShaderPlatformInterface.cpp +++ b/Gems/Atom/RHI/DX12/Code/Source/RHI.Builders/ShaderPlatformInterface.cpp @@ -12,10 +12,10 @@ #include #include #include +#include #include #include - #include namespace AZ @@ -253,9 +253,11 @@ namespace AZ return false; } + const bool graphicsDevMode = RHI::IsGraphicsDevModeEnabled(); + // Compilation parameters AZStd::string params = shaderCompilerArguments.MakeAdditionalDxcCommandLineString(); - if (BuildHasDebugInfo(shaderCompilerArguments)) + if (graphicsDevMode || BuildHasDebugInfo(shaderCompilerArguments)) { params += " -Zi"; // Generate debug information params += " -Zss"; // Compute Shader Hash considering source information @@ -284,7 +286,7 @@ namespace AZ // If we use the auto-name (hash), there is no way we can retrieve that name apart from listing the directory. // Instead, let's just generate that hash ourselves. AZStd::string symbolDatabaseFileCliArgument{" "}; // when not debug: still insert a space between 5.dxil and 7.hlsl-in - if (BuildHasDebugInfo(shaderCompilerArguments)) + if (graphicsDevMode || BuildHasDebugInfo(shaderCompilerArguments)) { // prepare .pdb filename: AZStd::string md5hex = RHI::ByteToHexString(md5); @@ -353,7 +355,7 @@ namespace AZ byProducts.m_dynamicBranchCount = ByProducts::UnknownDynamicBranchCount; } - if (BuildHasDebugInfo(shaderCompilerArguments)) + if (graphicsDevMode || BuildHasDebugInfo(shaderCompilerArguments)) { byProducts.m_intermediatePaths.emplace(AZStd::move(objectCodeOutputFile)); } diff --git a/Gems/Atom/RHI/Vulkan/Code/Source/RHI.Builders/ShaderPlatformInterface.cpp b/Gems/Atom/RHI/Vulkan/Code/Source/RHI.Builders/ShaderPlatformInterface.cpp index c5f1060ca3..f5716f384f 100644 --- a/Gems/Atom/RHI/Vulkan/Code/Source/RHI.Builders/ShaderPlatformInterface.cpp +++ b/Gems/Atom/RHI/Vulkan/Code/Source/RHI.Builders/ShaderPlatformInterface.cpp @@ -11,6 +11,7 @@ #include #include +#include #include #include @@ -281,7 +282,9 @@ namespace AZ args.m_destinationFolder = tempFolder.c_str(); const auto dxcInputFile = RHI::PrependFile(args); // Prepend header - if (BuildHasDebugInfo(shaderCompilerArguments)) + const bool graphicsDevMode = RHI::IsGraphicsDevModeEnabled(); + + if (graphicsDevMode || BuildHasDebugInfo(shaderCompilerArguments)) { // dump intermediate "true final HLSL" file (shadername.vulkan.shadersource.prepend) byProducts.m_intermediatePaths.insert(dxcInputFile); @@ -334,7 +337,7 @@ namespace AZ byProducts.m_dynamicBranchCount = ByProducts::UnknownDynamicBranchCount; } - if (BuildHasDebugInfo(shaderCompilerArguments)) + if (graphicsDevMode || BuildHasDebugInfo(shaderCompilerArguments)) { byProducts.m_intermediatePaths.emplace(AZStd::move(objectCodeOutputFile)); } diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/RPISystem.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/RPISystem.h index 57f595ccba..dbf70fb0cc 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/RPISystem.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/RPISystem.h @@ -86,6 +86,8 @@ namespace AZ const RPISystemDescriptor& GetDescriptor() const override; Name GetRenderApiName() const override; uint64_t GetCurrentTick() const override; + void SetApplicationMultisampleState(const RHI::MultisampleState& multisampleState) override; + const RHI::MultisampleState& GetApplicationMultisampleState() const override; // AZ::Debug::TraceMessageBus::Handler overrides... bool OnPreAssert(const char* fileName, int line, const char* func, const char* message) override; @@ -136,6 +138,9 @@ namespace AZ bool m_systemAssetsInitialized = false; uint64_t m_renderTick = 0; + + // Application multisample state + RHI::MultisampleState m_multisampleState; }; } // namespace RPI diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/RPISystemInterface.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/RPISystemInterface.h index 693185b6d2..7853e8f51e 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/RPISystemInterface.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/RPISystemInterface.h @@ -90,6 +90,10 @@ namespace AZ //! Get the index of current render tick virtual uint64_t GetCurrentTick() const = 0; + + //! Application multisample state + virtual void SetApplicationMultisampleState(const RHI::MultisampleState& multisampleState) = 0; + virtual const RHI::MultisampleState& GetApplicationMultisampleState() const = 0; }; } // namespace RPI diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/RPIUtils.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/RPIUtils.h index 546089ab1e..6516407265 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/RPIUtils.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/RPIUtils.h @@ -60,6 +60,11 @@ namespace AZ //! Same as above. Provided as a convenience when all arguments of the 'numthreads' attributes should be assigned to RHI::DispatchDirect::m_threadsPerGroup* variables. AZ::Outcome GetComputeShaderNumThreads(const Data::Asset& shaderAsset, RHI::DispatchDirect& dispatchDirect); + //! Get single image pixel value from raw image data + //! This assumes the imageData is not empty + template + T GetImageDataPixelValue(AZStd::span imageData, const AZ::RHI::ImageDescriptor& imageDescriptor, uint32_t x, uint32_t y, uint32_t componentIndex = 0); + //! Get single image pixel value for specified mip and slice template T GetSubImagePixelValue(const AZ::Data::Asset& imageAsset, uint32_t x, uint32_t y, uint32_t componentIndex = 0, uint32_t mip = 0, uint32_t slice = 0); diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/RPISystem.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/RPISystem.cpp index 3416b2895d..57bef2c7e5 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/RPISystem.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/RPISystem.cpp @@ -434,5 +434,29 @@ namespace AZ return m_renderTick; } + void RPISystem::SetApplicationMultisampleState(const RHI::MultisampleState& multisampleState) + { + m_multisampleState = multisampleState; + + bool isNonMsaaPipeline = (m_multisampleState.m_samples == 1); + const char* supervariantName = isNonMsaaPipeline ? AZ::RPI::NoMsaaSupervariantName : ""; + AZ::RPI::ShaderSystemInterface::Get()->SetSupervariantName(AZ::Name(supervariantName)); + + // reinitialize pipelines for all scenes + for (auto& scene : m_scenes) + { + for (auto& renderPipeline : scene->GetRenderPipelines()) + { + renderPipeline->GetRenderSettings().m_multisampleState = multisampleState; + renderPipeline->SetPassNeedsRecreate(); + } + } + } + + const RHI::MultisampleState& RPISystem::GetApplicationMultisampleState() const + { + return m_multisampleState; + } + } //namespace RPI } //namespace AZ diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/RPIUtils.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/RPIUtils.cpp index 9ff26f0d3a..8a6cc80cfe 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/RPIUtils.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/RPIUtils.cpp @@ -232,16 +232,12 @@ namespace AZ } } - template - T GetSubImagePixelValueInternal(const AZ::Data::Asset& imageAsset, uint32_t x, uint32_t y, uint32_t componentIndex, uint32_t mip, uint32_t slice) + size_t GetImageDataIndex(const AZ::RHI::ImageDescriptor& imageDescriptor, uint32_t x, uint32_t y, uint32_t componentIndex) { - AZStd::array values{ aznumeric_cast(0) }; + auto width = imageDescriptor.m_size.m_width; + const uint32_t numComponents = AZ::RHI::GetFormatComponentCount(imageDescriptor.m_format); - auto topLeft = AZStd::make_pair(x, y); - auto bottomRight = AZStd::make_pair(x + 1, y + 1); - GetSubImagePixelValues(imageAsset, topLeft, bottomRight, AZStd::span(values), componentIndex, mip, slice); - - return values[0]; + return (y * width + x) * numComponents + componentIndex; } } @@ -447,22 +443,60 @@ namespace AZ return GetComputeShaderNumThreads(shaderAsset, &dispatchDirect.m_threadsPerGroupX, &dispatchDirect.m_threadsPerGroupY, &dispatchDirect.m_threadsPerGroupZ); } + template<> + float GetImageDataPixelValue(AZStd::span imageData, const AZ::RHI::ImageDescriptor& imageDescriptor, uint32_t x, uint32_t y, uint32_t componentIndex) + { + size_t imageDataIndex = Internal::GetImageDataIndex(imageDescriptor, x, y, componentIndex); + return Internal::RetrieveFloatValue(imageData.data(), imageDataIndex, imageDescriptor.m_format); + } + + template<> + AZ::u32 GetImageDataPixelValue(AZStd::span imageData, const AZ::RHI::ImageDescriptor& imageDescriptor, uint32_t x, uint32_t y, uint32_t componentIndex) + { + size_t imageDataIndex = Internal::GetImageDataIndex(imageDescriptor, x, y, componentIndex); + return Internal::RetrieveUintValue(imageData.data(), imageDataIndex, imageDescriptor.m_format); + } + + template<> + AZ::s32 GetImageDataPixelValue(AZStd::span imageData, const AZ::RHI::ImageDescriptor& imageDescriptor, uint32_t x, uint32_t y, uint32_t componentIndex) + { + size_t imageDataIndex = Internal::GetImageDataIndex(imageDescriptor, x, y, componentIndex); + return Internal::RetrieveIntValue(imageData.data(), imageDataIndex, imageDescriptor.m_format); + } + + template + T GetSubImagePixelValueInternal(const AZ::Data::Asset& imageAsset, uint32_t x, uint32_t y, uint32_t componentIndex, uint32_t mip, uint32_t slice) + { + if (!imageAsset.IsReady()) + { + return aznumeric_cast(0); + } + + auto imageData = imageAsset->GetSubImageData(mip, slice); + if (imageData.empty()) + { + return aznumeric_cast(0); + } + + return GetImageDataPixelValue(imageData, imageAsset->GetImageDescriptor(), x, y, componentIndex); + } + template<> float GetSubImagePixelValue(const AZ::Data::Asset& imageAsset, uint32_t x, uint32_t y, uint32_t componentIndex, uint32_t mip, uint32_t slice) { - return Internal::GetSubImagePixelValueInternal(imageAsset, x, y, componentIndex, mip, slice); + return GetSubImagePixelValueInternal(imageAsset, x, y, componentIndex, mip, slice); } template<> AZ::u32 GetSubImagePixelValue(const AZ::Data::Asset& imageAsset, uint32_t x, uint32_t y, uint32_t componentIndex, uint32_t mip, uint32_t slice) { - return Internal::GetSubImagePixelValueInternal(imageAsset, x, y, componentIndex, mip, slice); + return GetSubImagePixelValueInternal(imageAsset, x, y, componentIndex, mip, slice); } template<> AZ::s32 GetSubImagePixelValue(const AZ::Data::Asset& imageAsset, uint32_t x, uint32_t y, uint32_t componentIndex, uint32_t mip, uint32_t slice) { - return Internal::GetSubImagePixelValueInternal(imageAsset, x, y, componentIndex, mip, slice); + return GetSubImagePixelValueInternal(imageAsset, x, y, componentIndex, mip, slice); } bool GetSubImagePixelValues(const AZ::Data::Asset& imageAsset, AZStd::pair topLeft, AZStd::pair bottomRight, AZStd::span outValues, uint32_t componentIndex, uint32_t mip, uint32_t slice) @@ -478,16 +512,14 @@ namespace AZ return false; } - const AZ::RHI::ImageDescriptor imageDescriptor = imageAsset->GetImageDescriptor(); - auto width = imageDescriptor.m_size.m_width; - const uint32_t numComponents = AZ::RHI::GetFormatComponentCount(imageDescriptor.m_format); + const AZ::RHI::ImageDescriptor& imageDescriptor = imageAsset->GetImageDescriptor(); size_t outValuesIndex = 0; for (uint32_t y = topLeft.second; y < bottomRight.second; ++y) { for (uint32_t x = topLeft.first; x < bottomRight.first; ++x) { - size_t imageDataIndex = (y * width + x) * numComponents + componentIndex; + size_t imageDataIndex = Internal::GetImageDataIndex(imageDescriptor, x, y, componentIndex); auto& outValue = outValues[outValuesIndex++]; outValue = Internal::RetrieveFloatValue(imageData.data(), imageDataIndex, imageDescriptor.m_format); @@ -510,16 +542,14 @@ namespace AZ return false; } - const AZ::RHI::ImageDescriptor imageDescriptor = imageAsset->GetImageDescriptor(); - auto width = imageDescriptor.m_size.m_width; - const uint32_t numComponents = AZ::RHI::GetFormatComponentCount(imageDescriptor.m_format); + const AZ::RHI::ImageDescriptor& imageDescriptor = imageAsset->GetImageDescriptor(); size_t outValuesIndex = 0; for (uint32_t y = topLeft.second; y < bottomRight.second; ++y) { for (uint32_t x = topLeft.first; x < bottomRight.first; ++x) { - size_t imageDataIndex = (y * width + x) * numComponents + componentIndex; + size_t imageDataIndex = Internal::GetImageDataIndex(imageDescriptor, x, y, componentIndex); auto& outValue = outValues[outValuesIndex++]; outValue = Internal::RetrieveUintValue(imageData.data(), imageDataIndex, imageDescriptor.m_format); @@ -542,16 +572,14 @@ namespace AZ return false; } - const AZ::RHI::ImageDescriptor imageDescriptor = imageAsset->GetImageDescriptor(); - auto width = imageDescriptor.m_size.m_width; - const uint32_t numComponents = AZ::RHI::GetFormatComponentCount(imageDescriptor.m_format); + const AZ::RHI::ImageDescriptor& imageDescriptor = imageAsset->GetImageDescriptor(); size_t outValuesIndex = 0; for (uint32_t y = topLeft.second; y < bottomRight.second; ++y) { for (uint32_t x = topLeft.first; x < bottomRight.first; ++x) { - size_t imageDataIndex = (y * width + x) * numComponents + componentIndex; + size_t imageDataIndex = Internal::GetImageDataIndex(imageDescriptor, x, y, componentIndex); auto& outValue = outValues[outValuesIndex++]; outValue = Internal::RetrieveIntValue(imageData.data(), imageDataIndex, imageDescriptor.m_format); diff --git a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Model/ModelAsset.cpp b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Model/ModelAsset.cpp index 6780763d7c..5685dfece2 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Model/ModelAsset.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Model/ModelAsset.cpp @@ -15,6 +15,7 @@ #include #include +#include namespace AZ { @@ -35,6 +36,15 @@ namespace AZ ->Field("MaterialSlots", &ModelAsset::m_materialSlots) ->Field("LodAssets", &ModelAsset::m_lodAssets) ; + + // Note: This class needs to have edit context reflection so PropertyAssetCtrl::OnEditButtonClicked + // can open the asset with the preferred asset editor (Scene Settings). + if (auto* editContext = serializeContext->GetEditContext()) + { + editContext->Class("Model Asset", "") + ->ClassElement(AZ::Edit::ClassElements::EditorData, "") + ; + } } } diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/PreviewRenderer/PreviewRenderer.cpp b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/PreviewRenderer/PreviewRenderer.cpp index 27d468e64b..638e8da87c 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/PreviewRenderer/PreviewRenderer.cpp +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/PreviewRenderer/PreviewRenderer.cpp @@ -63,10 +63,8 @@ namespace AtomToolsFramework pipelineDesc.m_mainViewTagName = "MainCamera"; pipelineDesc.m_name = pipelineName; pipelineDesc.m_rootPassTemplate = "ToolsPipelineRenderToTexture"; + pipelineDesc.m_renderSettings.m_multisampleState = AZ::RPI::RPISystemInterface::Get()->GetApplicationMultisampleState(); - // We have to set the samples to 4 to match the pipeline passes' setting, otherwise it may lead to device lost issue - // [GFX TODO] [ATOM-13551] Default value sand validation required to prevent pipeline crash and device lost - pipelineDesc.m_renderSettings.m_multisampleState.m_samples = 4; m_renderPipeline = AZ::RPI::RenderPipeline::CreateRenderPipeline(pipelineDesc); m_scene->AddRenderPipeline(m_renderPipeline); m_scene->Activate(); diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Mesh/EditorMeshComponent.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Mesh/EditorMeshComponent.cpp index 255f639cd7..0d6b87e795 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Mesh/EditorMeshComponent.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Mesh/EditorMeshComponent.cpp @@ -72,6 +72,9 @@ namespace AZ ->ClassElement(AZ::Edit::ClassElements::EditorData, "") ->Attribute(AZ::Edit::Attributes::AutoExpand, true) ->DataElement(AZ::Edit::UIHandlers::Default, &MeshComponentConfig::m_modelAsset, "Mesh Asset", "Mesh asset reference") + ->Attribute(AZ_CRC_CE("EditButton"), "") + ->Attribute(AZ_CRC_CE("EditDescription"), "Open in Scene Settings") + ->Attribute(AZ_CRC_CE("DisableEditButtonWhenNoAssetSelected"), true) ->DataElement(AZ::Edit::UIHandlers::Default, &MeshComponentConfig::m_sortKey, "Sort Key", "Transparent meshes are drawn by sort key then depth. Used this to force certain transparent meshes to draw before or after others.") ->Attribute(AZ::Edit::Attributes::Visibility, &MeshComponentConfig::IsAssetSet) ->DataElement(AZ::Edit::UIHandlers::CheckBox, &MeshComponentConfig::m_excludeFromReflectionCubeMaps, "Exclude from reflection cubemaps", "Mesh will not be visible in baked reflection probe cubemaps") diff --git a/Gems/GradientSignal/Code/Include/GradientSignal/Components/ImageGradientComponent.h b/Gems/GradientSignal/Code/Include/GradientSignal/Components/ImageGradientComponent.h index 0cbd8bd4c7..7ec1c785e5 100644 --- a/Gems/GradientSignal/Code/Include/GradientSignal/Components/ImageGradientComponent.h +++ b/Gems/GradientSignal/Code/Include/GradientSignal/Components/ImageGradientComponent.h @@ -98,6 +98,8 @@ namespace GradientSignal void SetupDependencies(); + void GetSubImageData(); + // ImageGradientRequestBus overrides... AZStd::string GetImageAssetPath() const override; void SetImageAssetPath(const AZStd::string& assetPath) override; @@ -113,5 +115,6 @@ namespace GradientSignal LmbrCentral::DependencyMonitor m_dependencyMonitor; mutable AZStd::shared_mutex m_imageMutex; GradientTransform m_gradientTransform; + AZStd::span m_imageData; }; } diff --git a/Gems/GradientSignal/Code/Include/GradientSignal/ImageAsset.h b/Gems/GradientSignal/Code/Include/GradientSignal/ImageAsset.h index 811d74082d..500251d924 100644 --- a/Gems/GradientSignal/Code/Include/GradientSignal/ImageAsset.h +++ b/Gems/GradientSignal/Code/Include/GradientSignal/ImageAsset.h @@ -62,6 +62,6 @@ namespace GradientSignal } }; - float GetValueFromImageAsset(const AZ::Data::Asset& imageAsset, const AZ::Vector3& uvw, float tilingX, float tilingY, float defaultValue); + float GetValueFromImageAsset(AZStd::span imageData, const AZ::RHI::ImageDescriptor& imageDescriptor, const AZ::Vector3& uvw, float tilingX, float tilingY, float defaultValue); } // namespace GradientSignal diff --git a/Gems/GradientSignal/Code/Source/Components/ImageGradientComponent.cpp b/Gems/GradientSignal/Code/Source/Components/ImageGradientComponent.cpp index 468b96e5ad..0bf914fea3 100644 --- a/Gems/GradientSignal/Code/Source/Components/ImageGradientComponent.cpp +++ b/Gems/GradientSignal/Code/Source/Components/ImageGradientComponent.cpp @@ -215,6 +215,16 @@ namespace GradientSignal m_dependencyMonitor.ConnectDependency(m_configuration.m_imageAsset.GetId()); } + void ImageGradientComponent::GetSubImageData() + { + if (!m_configuration.m_imageAsset || !m_configuration.m_imageAsset.IsReady()) + { + return; + } + + m_imageData = m_configuration.m_imageAsset->GetSubImageData(0, 0); + } + void ImageGradientComponent::Activate() { // This will immediately call OnGradientTransformChanged and initialize m_gradientTransform. @@ -224,10 +234,13 @@ namespace GradientSignal ImageGradientRequestBus::Handler::BusConnect(GetEntityId()); GradientRequestBus::Handler::BusConnect(GetEntityId()); - AZ::Data::AssetBus::Handler::BusConnect(m_configuration.m_imageAsset.GetId()); - AZStd::unique_lock imageLock(m_imageMutex); + // Invoke the QueueLoad before connecting to the AssetBus, so that + // if the asset is already ready, then OnAssetReady will be triggered immediately + m_imageData = AZStd::span(); m_configuration.m_imageAsset.QueueLoad(); + + AZ::Data::AssetBus::Handler::BusConnect(m_configuration.m_imageAsset.GetId()); } void ImageGradientComponent::Deactivate() @@ -267,18 +280,24 @@ namespace GradientSignal { AZStd::unique_lock imageLock(m_imageMutex); m_configuration.m_imageAsset = asset; + + GetSubImageData(); } void ImageGradientComponent::OnAssetMoved(AZ::Data::Asset asset, [[maybe_unused]] void* oldDataPointer) { AZStd::unique_lock imageLock(m_imageMutex); m_configuration.m_imageAsset = asset; + + GetSubImageData(); } void ImageGradientComponent::OnAssetReloaded(AZ::Data::Asset asset) { AZStd::unique_lock imageLock(m_imageMutex); m_configuration.m_imageAsset = asset; + + GetSubImageData(); } void ImageGradientComponent::OnGradientTransformChanged(const GradientTransform& newTransform) @@ -292,6 +311,12 @@ namespace GradientSignal AZ::Vector3 uvw = sampleParams.m_position; bool wasPointRejected = false; + // Return immediately if our cached image data hasn't been retrieved yet + if (m_imageData.empty()) + { + return 0.0f; + } + { AZStd::shared_lock imageLock(m_imageMutex); @@ -300,7 +325,7 @@ namespace GradientSignal if (!wasPointRejected) { return GetValueFromImageAsset( - m_configuration.m_imageAsset, uvw, m_configuration.m_tilingX, m_configuration.m_tilingY, 0.0f); + m_imageData, m_configuration.m_imageAsset->GetImageDescriptor(), uvw, m_configuration.m_tilingX, m_configuration.m_tilingY, 0.0f); } } @@ -315,6 +340,12 @@ namespace GradientSignal return; } + // Return immediately if our cached image data hasn't been retrieved yet + if (m_imageData.empty()) + { + return; + } + AZ::Vector3 uvw; bool wasPointRejected = false; @@ -327,7 +358,7 @@ namespace GradientSignal if (!wasPointRejected) { outValues[index] = GetValueFromImageAsset( - m_configuration.m_imageAsset, uvw, m_configuration.m_tilingX, m_configuration.m_tilingY, 0.0f); + m_imageData, m_configuration.m_imageAsset->GetImageDescriptor(), uvw, m_configuration.m_tilingX, m_configuration.m_tilingY, 0.0f); } else { @@ -353,6 +384,10 @@ namespace GradientSignal { AZStd::unique_lock imageLock(m_imageMutex); + + // Clear our cached image data + m_imageData = AZStd::span(); + m_configuration.m_imageAsset = AZ::Data::AssetManager::Instance().FindOrCreateAsset(assetId, azrtti_typeid(), m_configuration.m_imageAsset.GetAutoLoadBehavior()); } diff --git a/Gems/GradientSignal/Code/Source/Editor/EditorGradientSurfaceDataComponent.cpp b/Gems/GradientSignal/Code/Source/Editor/EditorGradientSurfaceDataComponent.cpp index e318e0fdf6..692576b3b3 100644 --- a/Gems/GradientSignal/Code/Source/Editor/EditorGradientSurfaceDataComponent.cpp +++ b/Gems/GradientSignal/Code/Source/Editor/EditorGradientSurfaceDataComponent.cpp @@ -11,6 +11,7 @@ #include #include #include +#include namespace GradientSignal { @@ -61,6 +62,12 @@ namespace GradientSignal void EditorGradientSurfaceDataComponent::Deactivate() { + // Make sure any previews for this entity aren't currently trying to refresh. Otherwise, the preview job could call + // back into our FilterFunc lambda below after the entity has already been destroyed. + AZ::EntityId canceledEntity; + GradientSignal::GradientPreviewRequestBus::EventResult( + canceledEntity, GetEntityId(), &GradientSignal::GradientPreviewRequestBus::Events::CancelRefresh); + // If the preview shouldn't be active, use an invalid entityId m_gradientEntityId = AZ::EntityId(); AzFramework::EntityDebugDisplayEventBus::Handler::BusDisconnect(); diff --git a/Gems/GradientSignal/Code/Source/ImageAsset.cpp b/Gems/GradientSignal/Code/Source/ImageAsset.cpp index 0f91a3ea08..05ad13d193 100644 --- a/Gems/GradientSignal/Code/Source/ImageAsset.cpp +++ b/Gems/GradientSignal/Code/Source/ImageAsset.cpp @@ -78,11 +78,10 @@ namespace GradientSignal return true; } - float GetValueFromImageAsset(const AZ::Data::Asset& imageAsset, const AZ::Vector3& uvw, float tilingX, float tilingY, float defaultValue) + float GetValueFromImageAsset(AZStd::span imageData, const AZ::RHI::ImageDescriptor& imageDescriptor, const AZ::Vector3& uvw, float tilingX, float tilingY, float defaultValue) { - if (imageAsset.IsReady()) + if (!imageData.empty()) { - auto imageDescriptor = imageAsset->GetImageDescriptor(); auto width = imageDescriptor.m_size.m_width; auto height = imageDescriptor.m_size.m_height; @@ -125,7 +124,7 @@ namespace GradientSignal // Flip the y because images are stored in reverse of our world axes y = (height - 1) - y; - return AZ::RPI::GetSubImagePixelValue(imageAsset, x, y); + return AZ::RPI::GetImageDataPixelValue(imageData, imageDescriptor, x, y); } } diff --git a/Gems/GradientSignal/Code/Tests/GradientSignalTestFixtures.h b/Gems/GradientSignal/Code/Tests/GradientSignalTestFixtures.h index c45af43ad9..4d2233528b 100644 --- a/Gems/GradientSignal/Code/Tests/GradientSignalTestFixtures.h +++ b/Gems/GradientSignal/Code/Tests/GradientSignalTestFixtures.h @@ -90,7 +90,7 @@ namespace UnitTest AZStd::unique_ptr BuildTestSurfaceMaskGradient(float shapeHalfBounds); AZStd::unique_ptr BuildTestSurfaceSlopeGradient(float shapeHalfBounds); - AZ::RPI::AssetHandlerPtrList m_assetHandlers; + AZStd::fixed_vector, 2> m_assetHandlers; }; struct GradientSignalTest diff --git a/Gems/LyShine/Code/Editor/HierarchyMenu.cpp b/Gems/LyShine/Code/Editor/HierarchyMenu.cpp index 004fe53354..4fb09ea1f8 100644 --- a/Gems/LyShine/Code/Editor/HierarchyMenu.cpp +++ b/Gems/LyShine/Code/Editor/HierarchyMenu.cpp @@ -387,7 +387,9 @@ void HierarchyMenu::New_ElementFromSlice(HierarchyWidget* hierarchy, AZ::Vector2 viewportPosition(-1.0f,-1.0f); // indicates no viewport position specified if (optionalPos) { - viewportPosition = QtHelpers::QPointFToVector2(*optionalPos); + // Convert position to render viewport coords + QPointF scaledPosition = *optionalPos * hierarchy->GetEditorWindow()->GetViewport()->WidgetToViewportFactor(); + viewportPosition = QtHelpers::QPointFToVector2(scaledPosition); } SliceMenuHelpers::CreateInstantiateSliceMenu(hierarchy, @@ -405,7 +407,9 @@ void HierarchyMenu::New_ElementFromSlice(HierarchyWidget* hierarchy, AZ::Vector2 viewportPosition(-1.0f,-1.0f); // indicates no viewport position specified if (optionalPos) { - viewportPosition = QtHelpers::QPointFToVector2(*optionalPos); + // Convert position to render viewport coords + QPointF scaledPosition = *optionalPos * hierarchy->GetEditorWindow()->GetViewport()->WidgetToViewportFactor(); + viewportPosition = QtHelpers::QPointFToVector2(scaledPosition); } hierarchy->GetEditorWindow()->GetSliceManager()->InstantiateSliceUsingBrowser(hierarchy, viewportPosition); } diff --git a/Gems/LyShine/Code/Editor/HierarchyWidget.cpp b/Gems/LyShine/Code/Editor/HierarchyWidget.cpp index 30cb7e51dc..cd80e5a278 100644 --- a/Gems/LyShine/Code/Editor/HierarchyWidget.cpp +++ b/Gems/LyShine/Code/Editor/HierarchyWidget.cpp @@ -1239,11 +1239,13 @@ void HierarchyWidget::AddElement(const QTreeWidgetItemRawPtrQList& selectedItems this, selectedItems, childIndex, - [optionalPos](AZ::Entity* element) + [this, optionalPos](AZ::Entity* element) { if (optionalPos) { - EntityHelpers::MoveElementToGlobalPosition(element, *optionalPos); + // Convert position to render viewport coords + QPoint scaledPosition = *optionalPos * GetEditorWindow()->GetViewport()->WidgetToViewportFactor(); + EntityHelpers::MoveElementToGlobalPosition(element, scaledPosition); } }); } diff --git a/Gems/MotionMatching/Code/CMakeLists.txt b/Gems/MotionMatching/Code/CMakeLists.txt index ae613f5d7f..22d2f28f3b 100644 --- a/Gems/MotionMatching/Code/CMakeLists.txt +++ b/Gems/MotionMatching/Code/CMakeLists.txt @@ -121,27 +121,27 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED) ly_add_googletest( NAME Gem::MotionMatching.Tests ) -endif() -# If we are a host platform we want to add tools test like editor tests here -if(PAL_TRAIT_BUILD_HOST_TOOLS) - ly_add_target( - NAME MotionMatching.Editor.Tests ${PAL_TRAIT_TEST_TARGET_TYPE} - NAMESPACE Gem - FILES_CMAKE - motionmatching_editor_tests_files.cmake - INCLUDE_DIRECTORIES - PRIVATE - Tests - Source - BUILD_DEPENDENCIES - PRIVATE - AZ::AzTest - Gem::MotionMatching.Editor - ) + # If we are a host platform we want to add tools test like editor tests here + if(PAL_TRAIT_BUILD_HOST_TOOLS) + ly_add_target( + NAME MotionMatching.Editor.Tests ${PAL_TRAIT_TEST_TARGET_TYPE} + NAMESPACE Gem + FILES_CMAKE + motionmatching_editor_tests_files.cmake + INCLUDE_DIRECTORIES + PRIVATE + Tests + Source + BUILD_DEPENDENCIES + PRIVATE + AZ::AzTest + Gem::MotionMatching.Editor + ) - # Add MotionMatching.Editor.Tests to googletest - ly_add_googletest( - NAME Gem::MotionMatching.Editor.Tests - ) + # Add MotionMatching.Editor.Tests to googletest + ly_add_googletest( + NAME Gem::MotionMatching.Editor.Tests + ) + endif() endif() diff --git a/Gems/Multiplayer/Code/Source/NetworkTime/NetworkTime.cpp b/Gems/Multiplayer/Code/Source/NetworkTime/NetworkTime.cpp index 59dddf5652..c6b6385845 100644 --- a/Gems/Multiplayer/Code/Source/NetworkTime/NetworkTime.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkTime/NetworkTime.cpp @@ -122,43 +122,41 @@ namespace Multiplayer { AZ::Entity* entity = static_cast(visEntry->m_userData); NetworkEntityHandle entityHandle(entity, networkEntityTracker); - if (entityHandle.GetNetBindComponent() == nullptr) + if (entityHandle.GetNetBindComponent() != nullptr) { - // Not a net-bound entity, terminate processing of this entity - return; - } - - const AZ::Aabb currentBounds = entityBoundsUnion->GetEntityWorldBoundsUnion(entity->GetId()); - const AZ::Vector3 currentCenter = currentBounds.GetCenter(); - NetworkTransformComponent* networkTransform = entity->template FindComponent(); - if (debugDisplay) - { - debugDisplay->SetColor(AZ::Colors::White); - debugDisplay->DrawWireBox(currentBounds.GetMin(), currentBounds.GetMax()); - } - - if (networkTransform != nullptr) - { - // Get the rewound position for target host frame ID plus the one preceding it for potential lerp - AZ::Vector3 rewindCenter = networkTransform->GetTranslation(); - const AZ::Vector3 rewindCenterPrevious = networkTransform->GetTranslationPrevious(); - const float blendFactor = GetNetworkTime()->GetHostBlendFactor(); - if (!AZ::IsClose(blendFactor, 1.0f) && !rewindCenter.IsClose(rewindCenterPrevious)) - { - // If we have a blend factor, lerp the translation for accuracy - rewindCenter = rewindCenterPrevious.Lerp(rewindCenter, blendFactor); - } - const AZ::Vector3 rewindOffset = rewindCenter - currentCenter; // Compute offset between rewound and current positions - const AZ::Aabb rewoundAabb = currentBounds.GetTranslated(rewindOffset); // Apply offset to the entity aabb + const AZ::Aabb currentBounds = entityBoundsUnion->GetEntityWorldBoundsUnion(entity->GetId()); + const AZ::Vector3 currentCenter = currentBounds.GetCenter(); + NetworkTransformComponent* networkTransform = entity->template FindComponent(); if (debugDisplay) { - debugDisplay->SetColor(AZ::Colors::Grey); - debugDisplay->DrawWireBox(rewoundAabb.GetMin(), rewoundAabb.GetMax()); + debugDisplay->SetColor(AZ::Colors::White); + debugDisplay->DrawWireBox(currentBounds.GetMin(), currentBounds.GetMax()); } - if (AZ::ShapeIntersection::Overlaps(rewoundAabb, rewindVolume)) // Validate the rewound aabb intersects our rewind volume + if (networkTransform != nullptr) { - m_rewoundEntities.push_back(entityHandle); + // Get the rewound position for target host frame ID plus the one preceding it for potential lerp + AZ::Vector3 rewindCenter = networkTransform->GetTranslation(); + const AZ::Vector3 rewindCenterPrevious = networkTransform->GetTranslationPrevious(); + const float blendFactor = GetNetworkTime()->GetHostBlendFactor(); + if (!AZ::IsClose(blendFactor, 1.0f) && !rewindCenter.IsClose(rewindCenterPrevious)) + { + // If we have a blend factor, lerp the translation for accuracy + rewindCenter = rewindCenterPrevious.Lerp(rewindCenter, blendFactor); + } + const AZ::Vector3 rewindOffset = rewindCenter - currentCenter; // Compute offset between rewound and current positions + const AZ::Aabb rewoundAabb = currentBounds.GetTranslated(rewindOffset); // Apply offset to the entity aabb + if (debugDisplay) + { + debugDisplay->SetColor(AZ::Colors::Grey); + debugDisplay->DrawWireBox(rewoundAabb.GetMin(), rewoundAabb.GetMax()); + } + + if (AZ::ShapeIntersection::Overlaps(rewoundAabb, rewindVolume)) // Validate the rewound aabb intersects our rewind volume + { + m_rewoundEntities.push_back(entityHandle); + entityHandle.GetNetBindComponent()->NotifySyncRewindState(); + } } } } diff --git a/Gems/PhysX/Code/Source/EditorColliderComponent.cpp b/Gems/PhysX/Code/Source/EditorColliderComponent.cpp index 7e1d1c341f..69ae037e07 100644 --- a/Gems/PhysX/Code/Source/EditorColliderComponent.cpp +++ b/Gems/PhysX/Code/Source/EditorColliderComponent.cpp @@ -60,6 +60,7 @@ namespace PhysX "Specifies the PhysX mesh collider asset for this PhysX collider component.") ->Attribute(AZ_CRC_CE("EditButton"), "") ->Attribute(AZ_CRC_CE("EditDescription"), "Open in Scene Settings") + ->Attribute(AZ_CRC_CE("DisableEditButtonWhenNoAssetSelected"), true) ->DataElement(AZ::Edit::UIHandlers::Default, &EditorProxyAssetShapeConfig::m_configuration, "Configuration", "PhysX mesh asset collider configuration.") ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly); diff --git a/Gems/ScriptCanvas/Code/Editor/Include/ScriptCanvas/Components/EditorScriptCanvasComponentSerializer.cpp b/Gems/ScriptCanvas/Code/Editor/Include/ScriptCanvas/Components/EditorScriptCanvasComponentSerializer.cpp index 596c2d9dbf..069b7a5f48 100644 --- a/Gems/ScriptCanvas/Code/Editor/Include/ScriptCanvas/Components/EditorScriptCanvasComponentSerializer.cpp +++ b/Gems/ScriptCanvas/Code/Editor/Include/ScriptCanvas/Components/EditorScriptCanvasComponentSerializer.cpp @@ -5,7 +5,7 @@ * SPDX-License-Identifier: Apache-2.0 OR MIT * */ - + #include #include #include @@ -17,7 +17,7 @@ namespace AZ JsonSerializationResult::Result EditorScriptCanvasComponentSerializer::Load ( void* outputValue - , const Uuid& outputValueTypeId + , [[maybe_unused]] const Uuid& outputValueTypeId , const rapidjson::Value& inputValue , JsonDeserializerContext& context) { @@ -32,9 +32,7 @@ namespace AZ JsonSerializationResult::ResultCode result = BaseJsonSerializer::Load(outputValue , azrtti_typeid(), inputValue, context); - // load child data one by one... - result.Combine(BaseJsonSerializer::Load(outputValue, outputValueTypeId, inputValue, context)); - + // load child data one by one if (result.GetProcessing() != JSR::Processing::Halted) { result.Combine(ContinueLoadingFromJsonObjectField diff --git a/Gems/SurfaceData/Code/Include/SurfaceData/SurfaceDataTypes.h b/Gems/SurfaceData/Code/Include/SurfaceData/SurfaceDataTypes.h index 943c45bb6a..1d49fff3dc 100644 --- a/Gems/SurfaceData/Code/Include/SurfaceData/SurfaceDataTypes.h +++ b/Gems/SurfaceData/Code/Include/SurfaceData/SurfaceDataTypes.h @@ -29,12 +29,6 @@ namespace SurfaceData class SurfaceTagWeights { public: - //! The maximum number of surface weights that we can store. - //! For performance reasons, we want to limit this so that we can preallocate the max size in advance. - //! The current number is chosen to be higher than expected needs, but small enough to avoid being excessively wasteful. - //! (Dynamic structures would end up taking more memory than what we're preallocating) - static inline constexpr size_t MaxSurfaceWeights = 16; - SurfaceTagWeights() = default; //! Construct a collection of SurfaceTagWeights from the given SurfaceTagWeightList. @@ -65,7 +59,7 @@ namespace SurfaceData // early-out once we pass the location for the entry instead of always searching every entry. if (weightItr->m_surfaceType > tag) { - if (m_weights.size() != MaxSurfaceWeights) + if (m_weights.size() != AzFramework::SurfaceData::Constants::MaxSurfaceWeights) { // We didn't find the surface type, so add the new entry in sorted order. m_weights.insert(weightItr, { tag, weight }); @@ -85,7 +79,7 @@ namespace SurfaceData } // We didn't find the surface weight, and the sort order for it is at the end, so add it to the back of the list. - if (m_weights.size() != MaxSurfaceWeights) + if (m_weights.size() != AzFramework::SurfaceData::Constants::MaxSurfaceWeights) { m_weights.emplace_back(tag, weight); } @@ -188,7 +182,7 @@ namespace SurfaceData //! @return The pointer to the tag that's found, or end() if it wasn't found. const AzFramework::SurfaceData::SurfaceTagWeight* FindTag(AZ::Crc32 tag) const; - AZStd::fixed_vector m_weights; + AZStd::fixed_vector m_weights; }; //! SurfacePointList stores a collection of surface point data, which consists of positions, normals, and surface tag weights. diff --git a/Gems/SurfaceData/Code/Include/SurfaceData/SurfaceTag.h b/Gems/SurfaceData/Code/Include/SurfaceData/SurfaceTag.h index d16856995d..ab24ba09f0 100644 --- a/Gems/SurfaceData/Code/Include/SurfaceData/SurfaceTag.h +++ b/Gems/SurfaceData/Code/Include/SurfaceData/SurfaceTag.h @@ -19,7 +19,8 @@ namespace SurfaceData { public: AZ_CLASS_ALLOCATOR(SurfaceTag, AZ::SystemAllocator, 0); - AZ_RTTI(SurfaceTag, "{67C8C6ED-F32A-443E-A777-1CAE48B22CD7}"); + AZ_TYPE_INFO(SurfaceTag, "{67C8C6ED-F32A-443E-A777-1CAE48B22CD7}"); + static void Reflect(AZ::ReflectContext* context); SurfaceTag() @@ -47,6 +48,8 @@ namespace SurfaceData m_surfaceTagCrc = AZ::Crc32(value.data()); } + AZStd::string GetDisplayName() const; + static AZStd::vector> GetRegisteredTags(); private: @@ -54,8 +57,6 @@ namespace SurfaceData AZStd::vector> BuildSelectableTagList() const; - AZStd::string GetDisplayName() const; - AZ::u32 m_surfaceTagCrc; }; diff --git a/Gems/SurfaceData/Code/Source/SurfaceDataTypes.cpp b/Gems/SurfaceData/Code/Source/SurfaceDataTypes.cpp index a25273896e..3d6378aa7c 100644 --- a/Gems/SurfaceData/Code/Source/SurfaceDataTypes.cpp +++ b/Gems/SurfaceData/Code/Source/SurfaceDataTypes.cpp @@ -42,7 +42,7 @@ namespace SurfaceData AzFramework::SurfaceData::SurfaceTagWeightList SurfaceTagWeights::GetSurfaceTagWeightList() const { AzFramework::SurfaceData::SurfaceTagWeightList weights; - weights.reserve(m_weights.size()); + for (auto& weight : m_weights) { weights.emplace_back(weight); diff --git a/Gems/SurfaceData/Code/Tests/SurfaceDataBenchmarks.cpp b/Gems/SurfaceData/Code/Tests/SurfaceDataBenchmarks.cpp index df011113da..becb91f0ce 100644 --- a/Gems/SurfaceData/Code/Tests/SurfaceDataBenchmarks.cpp +++ b/Gems/SurfaceData/Code/Tests/SurfaceDataBenchmarks.cpp @@ -275,7 +275,7 @@ namespace UnitTest { AZ_PROFILE_FUNCTION(Entity); - AZ::Crc32 tags[SurfaceData::SurfaceTagWeights::MaxSurfaceWeights]; + AZ::Crc32 tags[AzFramework::SurfaceData::Constants::MaxSurfaceWeights]; AZ::SimpleLcgRandom randomGenerator(1234567); // Declare this outside the loop so that we aren't benchmarking creation and destruction. @@ -316,7 +316,7 @@ namespace UnitTest { AZ_PROFILE_FUNCTION(Entity); - AZ::Crc32 tags[SurfaceData::SurfaceTagWeights::MaxSurfaceWeights]; + AZ::Crc32 tags[AzFramework::SurfaceData::Constants::MaxSurfaceWeights]; AZ::SimpleLcgRandom randomGenerator(1234567); // Declare this outside the loop so that we aren't benchmarking creation and destruction. diff --git a/Gems/Terrain/Code/Source/Components/TerrainPhysicsColliderComponent.cpp b/Gems/Terrain/Code/Source/Components/TerrainPhysicsColliderComponent.cpp index 457c74a6dd..a0689ec23a 100644 --- a/Gems/Terrain/Code/Source/Components/TerrainPhysicsColliderComponent.cpp +++ b/Gems/Terrain/Code/Source/Components/TerrainPhysicsColliderComponent.cpp @@ -22,6 +22,7 @@ #include #include + namespace Terrain { void TerrainPhysicsSurfaceMaterialMapping::Reflect(AZ::ReflectContext* context) @@ -32,27 +33,10 @@ namespace Terrain ->Version(1) ->Field("Surface", &TerrainPhysicsSurfaceMaterialMapping::m_surfaceTag) ->Field("Material", &TerrainPhysicsSurfaceMaterialMapping::m_materialId); - - if (auto edit = serialize->GetEditContext()) - { - edit->Class( - "Terrain Surface Material Mapping", "Mapping between a surface and a physics material.") - - ->ClassElement(AZ::Edit::ClassElements::EditorData, "") - ->Attribute(AZ::Edit::Attributes::AutoExpand, true) - ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::Show) - - ->DataElement( - AZ::Edit::UIHandlers::ComboBox, &TerrainPhysicsSurfaceMaterialMapping::m_surfaceTag, "Surface Tag", - "Surface type to map to a physics material.") - ->DataElement(AZ::Edit::UIHandlers::Default, &TerrainPhysicsSurfaceMaterialMapping::m_materialId, "Material ID", "") - ->ElementAttribute(Physics::Attributes::MaterialLibraryAssetId, &TerrainPhysicsSurfaceMaterialMapping::GetMaterialLibraryId) - ->Attribute(AZ::Edit::Attributes::AutoExpand, true) - ->Attribute(AZ::Edit::Attributes::ShowProductAssetFileName, true); - } } } + AZ::Data::AssetId TerrainPhysicsSurfaceMaterialMapping::GetMaterialLibraryId() { if (const auto* physicsSystem = AZ::Interface::Get()) @@ -76,22 +60,6 @@ namespace Terrain ->Field("DefaultMaterial", &TerrainPhysicsColliderConfig::m_defaultMaterialSelection) ->Field("Mappings", &TerrainPhysicsColliderConfig::m_surfaceMaterialMappings) ; - - if (auto edit = serialize->GetEditContext()) - { - edit->Class( - "Terrain Physics Collider Component", - "Provides terrain data to a physics collider with configurable surface mappings.") - ->ClassElement(AZ::Edit::ClassElements::EditorData, "") - ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly) - ->Attribute(AZ::Edit::Attributes::AutoExpand, true) - ->DataElement(AZ::Edit::UIHandlers::Default, &TerrainPhysicsColliderConfig::m_defaultMaterialSelection, - "Default Surface Physics Material", "Select a material to be used by unmapped surfaces by default") - ->DataElement( - AZ::Edit::UIHandlers::Default, &TerrainPhysicsColliderConfig::m_surfaceMaterialMappings, - "Surface to Material Mappings", "Maps surfaces to physics materials") - ; - } } } diff --git a/Gems/Terrain/Code/Source/Components/TerrainPhysicsColliderComponent.h b/Gems/Terrain/Code/Source/Components/TerrainPhysicsColliderComponent.h index 5f79fa9103..2bd0e95769 100644 --- a/Gems/Terrain/Code/Source/Components/TerrainPhysicsColliderComponent.h +++ b/Gems/Terrain/Code/Source/Components/TerrainPhysicsColliderComponent.h @@ -25,6 +25,8 @@ namespace LmbrCentral namespace Terrain { + class EditorSurfaceTagListProvider; + static const uint8_t InvalidSurfaceTagIndex = 0xFF; struct TerrainPhysicsSurfaceMaterialMapping final @@ -33,12 +35,16 @@ namespace Terrain AZ_CLASS_ALLOCATOR(TerrainPhysicsSurfaceMaterialMapping, AZ::SystemAllocator, 0); AZ_RTTI(TerrainPhysicsSurfaceMaterialMapping, "{A88B5289-DFCD-4564-8395-E2177DFE5B18}"); static void Reflect(AZ::ReflectContext* context); + static AZ::Data::AssetId GetMaterialLibraryId(); + + AZStd::vector> BuildSelectableTagList() const; + void SetTagListProvider(const EditorSurfaceTagListProvider* tagListProvider); SurfaceData::SurfaceTag m_surfaceTag; Physics::MaterialId m_materialId; private: - static AZ::Data::AssetId GetMaterialLibraryId(); + const EditorSurfaceTagListProvider* m_tagListProvider = nullptr; }; class TerrainPhysicsColliderConfig @@ -48,6 +54,7 @@ namespace Terrain AZ_CLASS_ALLOCATOR(TerrainPhysicsColliderConfig, AZ::SystemAllocator, 0); AZ_RTTI(TerrainPhysicsColliderConfig, "{E9EADB8F-C3A5-4B9C-A62D-2DBC86B4CE59}", AZ::ComponentConfig); static void Reflect(AZ::ReflectContext* context); + Physics::MaterialSelection m_defaultMaterialSelection; AZStd::vector m_surfaceMaterialMappings; }; diff --git a/Gems/Terrain/Code/Source/Components/TerrainSurfaceGradientListComponent.cpp b/Gems/Terrain/Code/Source/Components/TerrainSurfaceGradientListComponent.cpp index 7d41b829c1..a93bda1872 100644 --- a/Gems/Terrain/Code/Source/Components/TerrainSurfaceGradientListComponent.cpp +++ b/Gems/Terrain/Code/Source/Components/TerrainSurfaceGradientListComponent.cpp @@ -26,26 +26,6 @@ namespace Terrain ->Field("Gradient Entity", &TerrainSurfaceGradientMapping::m_gradientEntityId) ->Field("Surface Tag", &TerrainSurfaceGradientMapping::m_surfaceTag) ; - - if (auto edit = serialize->GetEditContext()) - { - edit->Class("Terrain Surface Gradient Mapping", "Mapping between a gradient and a surface.") - ->ClassElement(AZ::Edit::ClassElements::EditorData, "") - ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::Show) - ->Attribute(AZ::Edit::Attributes::AutoExpand, true) - - ->DataElement( - AZ::Edit::UIHandlers::Default, &TerrainSurfaceGradientMapping::m_gradientEntityId, - "Gradient Entity", "ID of Entity providing a gradient.") - ->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ::Edit::PropertyRefreshLevels::AttributesAndValues) - ->UIElement("GradientPreviewer", "Previewer") - ->Attribute(AZ::Edit::Attributes::NameLabelOverride, "") - ->Attribute(AZ_CRC_CE("GradientEntity"), &TerrainSurfaceGradientMapping::m_gradientEntityId) - ->DataElement( - AZ::Edit::UIHandlers::Default, &TerrainSurfaceGradientMapping::m_surfaceTag, "Surface Tag", - "Surface type to map to this gradient.") - ; - } } if (auto behaviorContext = azrtti_cast(context)) @@ -71,21 +51,6 @@ namespace Terrain ->Version(1) ->Field("Mappings", &TerrainSurfaceGradientListConfig::m_gradientSurfaceMappings) ; - - AZ::EditContext* edit = serialize->GetEditContext(); - if (edit) - { - edit->Class( - "Terrain Surface Gradient List Component", "Provide mapping between gradients and surfaces.") - ->ClassElement(AZ::Edit::ClassElements::EditorData, "") - ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::Show) - ->Attribute(AZ::Edit::Attributes::AutoExpand, true) - - ->DataElement( - AZ::Edit::UIHandlers::Default, &TerrainSurfaceGradientListConfig::m_gradientSurfaceMappings, - "Gradient to Surface Mappings", "Maps Gradient Entities to Surfaces.") - ; - } } } diff --git a/Gems/Terrain/Code/Source/Components/TerrainSurfaceGradientListComponent.h b/Gems/Terrain/Code/Source/Components/TerrainSurfaceGradientListComponent.h index 36c16bed8f..fd6c16d30d 100644 --- a/Gems/Terrain/Code/Source/Components/TerrainSurfaceGradientListComponent.h +++ b/Gems/Terrain/Code/Source/Components/TerrainSurfaceGradientListComponent.h @@ -25,6 +25,8 @@ namespace LmbrCentral namespace Terrain { + class EditorSurfaceTagListProvider; + class TerrainSurfaceGradientMapping final { public: @@ -39,8 +41,14 @@ namespace Terrain { } + AZStd::vector> BuildSelectableTagList() const; + void SetTagListProvider(const EditorSurfaceTagListProvider* tagListProvider); + AZ::EntityId m_gradientEntityId; SurfaceData::SurfaceTag m_surfaceTag; + + private: + const EditorSurfaceTagListProvider* m_tagListProvider = nullptr; }; class TerrainSurfaceGradientListConfig : public AZ::ComponentConfig diff --git a/Gems/Terrain/Code/Source/EditorComponents/EditorTerrainPhysicsColliderComponent.cpp b/Gems/Terrain/Code/Source/EditorComponents/EditorTerrainPhysicsColliderComponent.cpp index 6df6c3b440..5f76b7de6a 100644 --- a/Gems/Terrain/Code/Source/EditorComponents/EditorTerrainPhysicsColliderComponent.cpp +++ b/Gems/Terrain/Code/Source/EditorComponents/EditorTerrainPhysicsColliderComponent.cpp @@ -20,5 +20,85 @@ namespace Terrain &LmbrCentral::EditorWrappedComponentBaseVersionConverter ); + + if (auto serialize = azrtti_cast(context)) + { + if (auto edit = serialize->GetEditContext()) + { + edit->Class( + "Terrain Surface Material Mapping", "Mapping between a surface and a physics material.") + + ->ClassElement(AZ::Edit::ClassElements::EditorData, "") + ->Attribute(AZ::Edit::Attributes::AutoExpand, true) + ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::Show) + + ->DataElement( + AZ::Edit::UIHandlers::ComboBox, &TerrainPhysicsSurfaceMaterialMapping::m_surfaceTag, "Surface Tag", + "Surface type to map to a physics material.") + ->Attribute(AZ::Edit::Attributes::EnumValues, &TerrainPhysicsSurfaceMaterialMapping::BuildSelectableTagList) + + ->DataElement(AZ::Edit::UIHandlers::Default, &TerrainPhysicsSurfaceMaterialMapping::m_materialId, "Material ID", "") + ->ElementAttribute(Physics::Attributes::MaterialLibraryAssetId, &TerrainPhysicsSurfaceMaterialMapping::GetMaterialLibraryId) + ->Attribute(AZ::Edit::Attributes::AutoExpand, true) + ->Attribute(AZ::Edit::Attributes::ShowProductAssetFileName, true) + ; + + edit->Class( + "Terrain Physics Collider Component", + "Provides terrain data to a physics collider with configurable surface mappings.") + ->ClassElement(AZ::Edit::ClassElements::EditorData, "") + ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly) + ->Attribute(AZ::Edit::Attributes::AutoExpand, true) + ->DataElement(AZ::Edit::UIHandlers::Default, &TerrainPhysicsColliderConfig::m_defaultMaterialSelection, + "Default Surface Physics Material", "Select a material to be used by unmapped surfaces by default") + ->DataElement( + AZ::Edit::UIHandlers::Default, &TerrainPhysicsColliderConfig::m_surfaceMaterialMappings, + "Surface to Material Mappings", "Maps surfaces to physics materials") + ; + } + } + } + + void EditorTerrainPhysicsColliderComponent::Activate() + { + UpdateConfigurationTagProvider(); + BaseClassType::Activate(); + } + + AZStd::unordered_set EditorTerrainPhysicsColliderComponent::GetSurfaceTagsInUse() const + { + AZStd::unordered_set tagsInUse; + + for (const TerrainPhysicsSurfaceMaterialMapping& mapping : m_configuration.m_surfaceMaterialMappings) + { + tagsInUse.insert(mapping.m_surfaceTag); + } + + return AZStd::move(tagsInUse); + } + + AZ::u32 EditorTerrainPhysicsColliderComponent::ConfigurationChanged() + { + UpdateConfigurationTagProvider(); + return BaseClassType::ConfigurationChanged(); + } + + void EditorTerrainPhysicsColliderComponent::UpdateConfigurationTagProvider() + { + for (TerrainPhysicsSurfaceMaterialMapping& mapping : m_configuration.m_surfaceMaterialMappings) + { + mapping.SetTagListProvider(this); + } + } + + AZStd::vector> TerrainPhysicsSurfaceMaterialMapping::BuildSelectableTagList() const + { + AZ_PROFILE_FUNCTION(Entity); + return AZStd::move(Terrain::BuildSelectableTagList(m_tagListProvider, m_surfaceTag)); + } + + void TerrainPhysicsSurfaceMaterialMapping::SetTagListProvider(const EditorSurfaceTagListProvider* tagListProvider) + { + m_tagListProvider = tagListProvider; } } diff --git a/Gems/Terrain/Code/Source/EditorComponents/EditorTerrainPhysicsColliderComponent.h b/Gems/Terrain/Code/Source/EditorComponents/EditorTerrainPhysicsColliderComponent.h index 924426c262..fca8e52246 100644 --- a/Gems/Terrain/Code/Source/EditorComponents/EditorTerrainPhysicsColliderComponent.h +++ b/Gems/Terrain/Code/Source/EditorComponents/EditorTerrainPhysicsColliderComponent.h @@ -11,22 +11,34 @@ #include #include #include +#include namespace Terrain { class EditorTerrainPhysicsColliderComponent : public LmbrCentral::EditorWrappedComponentBase + , public EditorSurfaceTagListProvider { public: using BaseClassType = LmbrCentral::EditorWrappedComponentBase; AZ_EDITOR_COMPONENT(EditorTerrainPhysicsColliderComponent, "{C43FAB8F-3968-46A6-920E-E84AEDED3DF5}", BaseClassType); static void Reflect(AZ::ReflectContext* context); + // AZ::Component interface implementation + void Activate() override; + static constexpr auto s_categoryName = "Terrain"; static constexpr auto s_componentName = "Terrain Physics Heightfield Collider"; static constexpr auto s_componentDescription = "Provides terrain data to a physics collider in the form of a heightfield and surface->material mapping."; static constexpr auto s_icon = "Editor/Icons/Components/TerrainPhysicsCollider.svg"; static constexpr auto s_viewportIcon = "Editor/Icons/Components/Viewport/TerrainPhysicsCollider.svg"; static constexpr auto s_helpUrl = ""; + + private: + // EditorSurfaceTagListProvider interface implementation + AZStd::unordered_set GetSurfaceTagsInUse() const override; + + AZ::u32 ConfigurationChanged() override; + void UpdateConfigurationTagProvider(); }; } diff --git a/Gems/Terrain/Code/Source/EditorComponents/EditorTerrainSurfaceGradientListComponent.cpp b/Gems/Terrain/Code/Source/EditorComponents/EditorTerrainSurfaceGradientListComponent.cpp index bc823734b4..0ea51f282b 100644 --- a/Gems/Terrain/Code/Source/EditorComponents/EditorTerrainSurfaceGradientListComponent.cpp +++ b/Gems/Terrain/Code/Source/EditorComponents/EditorTerrainSurfaceGradientListComponent.cpp @@ -19,5 +19,83 @@ namespace Terrain &LmbrCentral::EditorWrappedComponentBaseVersionConverter ); + + if (auto serialize = azrtti_cast(context)) + { + if (auto edit = serialize->GetEditContext()) + { + edit->Class("Terrain Surface Gradient Mapping", "Mapping between a gradient and a surface.") + ->ClassElement(AZ::Edit::ClassElements::EditorData, "") + ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::Show) + ->Attribute(AZ::Edit::Attributes::AutoExpand, true) + + ->DataElement( + AZ::Edit::UIHandlers::Default, &TerrainSurfaceGradientMapping::m_gradientEntityId, + "Gradient Entity", "ID of Entity providing a gradient.") + ->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ::Edit::PropertyRefreshLevels::AttributesAndValues) + ->UIElement("GradientPreviewer", "Previewer") + ->Attribute(AZ::Edit::Attributes::NameLabelOverride, "") + ->Attribute(AZ_CRC_CE("GradientEntity"), &TerrainSurfaceGradientMapping::m_gradientEntityId) + ->DataElement( + AZ::Edit::UIHandlers::Default, &TerrainSurfaceGradientMapping::m_surfaceTag, "Surface Tag", + "Surface type to map to this gradient.") + ->Attribute(AZ::Edit::Attributes::EnumValues, &TerrainSurfaceGradientMapping::BuildSelectableTagList) + ; + + edit->Class( + "Terrain Surface Gradient List Component", "Provide mapping between gradients and surfaces.") + ->ClassElement(AZ::Edit::ClassElements::EditorData, "") + ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::Show) + ->Attribute(AZ::Edit::Attributes::AutoExpand, true) + + ->DataElement( + AZ::Edit::UIHandlers::Default, &TerrainSurfaceGradientListConfig::m_gradientSurfaceMappings, + "Gradient to Surface Mappings", "Maps Gradient Entities to Surfaces.") + ; + } + } + } + + void EditorTerrainSurfaceGradientListComponent::Activate() + { + UpdateConfigurationTagProvider(); + BaseClassType::Activate(); + } + + AZ::u32 EditorTerrainSurfaceGradientListComponent::ConfigurationChanged() + { + UpdateConfigurationTagProvider(); + return BaseClassType::ConfigurationChanged(); + } + + void EditorTerrainSurfaceGradientListComponent::UpdateConfigurationTagProvider() + { + for (TerrainSurfaceGradientMapping& mapping : m_configuration.m_gradientSurfaceMappings) + { + mapping.SetTagListProvider(this); + } + } + + AZStd::unordered_set EditorTerrainSurfaceGradientListComponent::GetSurfaceTagsInUse() const + { + AZStd::unordered_set tagsInUse; + + for (const TerrainSurfaceGradientMapping& mapping : m_configuration.m_gradientSurfaceMappings) + { + tagsInUse.insert(mapping.m_surfaceTag); + } + + return AZStd::move(tagsInUse); + } + + AZStd::vector> TerrainSurfaceGradientMapping::BuildSelectableTagList() const + { + AZ_PROFILE_FUNCTION(Entity); + return AZStd::move(Terrain::BuildSelectableTagList(m_tagListProvider, m_surfaceTag)); + } + + void TerrainSurfaceGradientMapping::SetTagListProvider(const EditorSurfaceTagListProvider* tagListProvider) + { + m_tagListProvider = tagListProvider; } } diff --git a/Gems/Terrain/Code/Source/EditorComponents/EditorTerrainSurfaceGradientListComponent.h b/Gems/Terrain/Code/Source/EditorComponents/EditorTerrainSurfaceGradientListComponent.h index 58cb776823..bcd5d84c38 100644 --- a/Gems/Terrain/Code/Source/EditorComponents/EditorTerrainSurfaceGradientListComponent.h +++ b/Gems/Terrain/Code/Source/EditorComponents/EditorTerrainSurfaceGradientListComponent.h @@ -11,22 +11,34 @@ #include #include #include +#include namespace Terrain { class EditorTerrainSurfaceGradientListComponent : public LmbrCentral::EditorWrappedComponentBase + , public EditorSurfaceTagListProvider { public: using BaseClassType = LmbrCentral::EditorWrappedComponentBase; AZ_EDITOR_COMPONENT(EditorTerrainSurfaceGradientListComponent, "{49831E91-A11F-4EFF-A824-6D85C284B934}", BaseClassType); static void Reflect(AZ::ReflectContext* context); + // AZ::Component interface implementation + void Activate() override; + static constexpr const char* const s_categoryName = "Terrain"; static constexpr const char* const s_componentName = "Terrain Surface Gradient List"; static constexpr const char* const s_componentDescription = "Provides a mapping between gradients and surface tags for use by the terrain system."; static constexpr const char* const s_icon = "Editor/Icons/Components/TerrainSurfaceGradientList.svg"; static constexpr const char* const s_viewportIcon = "Editor/Icons/Components/Viewport/TerrainSurfaceGradientList.svg"; static constexpr const char* const s_helpUrl = "https://o3de.org/docs/user-guide/components/reference/terrain/surface-gradient-list/"; + + private: + // EditorSurfaceTagListProvider interface implementation + AZStd::unordered_set GetSurfaceTagsInUse() const override; + + AZ::u32 ConfigurationChanged() override; + void UpdateConfigurationTagProvider(); }; } diff --git a/Gems/Terrain/Code/Source/EditorSurfaceTagListProvider.cpp b/Gems/Terrain/Code/Source/EditorSurfaceTagListProvider.cpp new file mode 100644 index 0000000000..bb4dcb6afe --- /dev/null +++ b/Gems/Terrain/Code/Source/EditorSurfaceTagListProvider.cpp @@ -0,0 +1,41 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include + + +namespace Terrain +{ + AZStd::vector> BuildSelectableTagList(const EditorSurfaceTagListProvider* tagListProvider, + const SurfaceData::SurfaceTag& currentTag) + { + AZStd::vector> availableTags = SurfaceData::SurfaceTag::GetRegisteredTags(); + + AZStd::unordered_set tagsInUse; + + if (tagListProvider) + { + tagsInUse = AZStd::move(tagListProvider->GetSurfaceTagsInUse()); + + // Filter out all tags in use from the list of registered tags + AZStd::erase_if(availableTags, [&tagsInUse](const auto& tag)-> bool + { + return tagsInUse.contains(SurfaceData::SurfaceTag(tag.first)); + }); + } + + // Insert the current tag back if it was removed via tagsInUse + availableTags.emplace_back(AZ::u32(currentTag), currentTag.GetDisplayName()); + + // Sorting for consistency + AZStd::sort(availableTags.begin(), availableTags.end(), [](const auto& lhs, const auto& rhs) {return lhs.second < rhs.second; }); + + return AZStd::move(availableTags); + } +} diff --git a/Gems/Terrain/Code/Source/EditorSurfaceTagListProvider.h b/Gems/Terrain/Code/Source/EditorSurfaceTagListProvider.h new file mode 100644 index 0000000000..ed5a5c1c0a --- /dev/null +++ b/Gems/Terrain/Code/Source/EditorSurfaceTagListProvider.h @@ -0,0 +1,29 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once +#include +#include +#include +#include + +namespace Terrain +{ + //! Interface for a class providing information about surface tags available for selecting in Editor components. + class EditorSurfaceTagListProvider + { + public: + //! Returns a set of all surface tags currently in use that won't be available for selecting. + virtual AZStd::unordered_set GetSurfaceTagsInUse() const = 0; + }; + + //! Returns a list of available tags to be selected in the component. + AZStd::vector> BuildSelectableTagList( + const EditorSurfaceTagListProvider* tagListProvider, + const SurfaceData::SurfaceTag& currentTag); +} diff --git a/Gems/Terrain/Code/Source/TerrainRenderer/Components/TerrainSurfaceMaterialsListComponent.cpp b/Gems/Terrain/Code/Source/TerrainRenderer/Components/TerrainSurfaceMaterialsListComponent.cpp index 5f2a50f903..fc4b821578 100644 --- a/Gems/Terrain/Code/Source/TerrainRenderer/Components/TerrainSurfaceMaterialsListComponent.cpp +++ b/Gems/Terrain/Code/Source/TerrainRenderer/Components/TerrainSurfaceMaterialsListComponent.cpp @@ -8,21 +8,19 @@ #include -#include -#include #include +#include #include -#include -#include -#include +#include #include +#include +#include #include -#include -#include - #include +#include +#include #include namespace Terrain @@ -38,15 +36,14 @@ namespace Terrain if (auto edit = serialize->GetEditContext()) { - edit->Class("Terrain Surface Gradient Mapping", "Mapping between a surface and a material.") + edit->Class("Terrain surface gradient mapping", "Mapping between a surface and a material.") ->ClassElement(AZ::Edit::ClassElements::EditorData, "") - ->Attribute(AZ::Edit::Attributes::AutoExpand, true) - ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::Show) - - ->DataElement(AZ::Edit::UIHandlers::ComboBox, &TerrainSurfaceMaterialMapping::m_surfaceTag, "Surface Tag", "Surface type to map to a material.") - ->DataElement(AZ::Edit::UIHandlers::Default, &TerrainSurfaceMaterialMapping::m_materialAsset, "Material Asset", "") - ->Attribute(AZ::Edit::Attributes::AutoExpand, true) - ->Attribute(AZ::Edit::Attributes::ShowProductAssetFileName, true) + ->Attribute(AZ::Edit::Attributes::AutoExpand, true) + + ->DataElement(AZ::Edit::UIHandlers::ComboBox, &TerrainSurfaceMaterialMapping::m_surfaceTag, "Surface tag", "Surface type to map to a material.") + ->DataElement(AZ::Edit::UIHandlers::Default, &TerrainSurfaceMaterialMapping::m_materialAsset, "Material asset", "") + ->Attribute(AZ::Edit::Attributes::AutoExpand, true) + ->Attribute(AZ::Edit::Attributes::ShowProductAssetFileName, true) ; } } @@ -60,7 +57,8 @@ namespace Terrain if (serialize) { serialize->Class() - ->Version(1) + ->Version(2) + ->Field("DefaultMaterial", &TerrainSurfaceMaterialsListConfig::m_defaultSurfaceMaterial) ->Field("Mappings", &TerrainSurfaceMaterialsListConfig::m_surfaceMaterials); AZ::EditContext* edit = serialize->GetEditContext(); @@ -68,10 +66,13 @@ namespace Terrain { edit->Class( "Terrain Surface Material List Component", "Provide mapping between surfaces and render materials.") + ->SetDynamicEditDataProvider(&TerrainSurfaceMaterialsListConfig::GetDynamicData) ->ClassElement(AZ::Edit::ClassElements::EditorData, "") - ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::Show) - ->Attribute(AZ::Edit::Attributes::AutoExpand, true) + ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::Show) + ->Attribute(AZ::Edit::Attributes::AutoExpand, true) + ->DataElement(AZ::Edit::UIHandlers::Default, &TerrainSurfaceMaterialsListConfig::m_defaultSurfaceMaterial, + "Default Material", "The default material to fall back to where no other material surface mappings exist.") ->DataElement( AZ::Edit::UIHandlers::Default, &TerrainSurfaceMaterialsListConfig::m_surfaceMaterials, "Material Mappings", "Maps surfaces to materials."); @@ -79,6 +80,26 @@ namespace Terrain } } + TerrainSurfaceMaterialsListConfig::TerrainSurfaceMaterialsListConfig() + { + m_hideSurfaceTagData.m_attributes.push_back( + { + AZ::Edit::Attributes::Visibility, + aznew AZ::Edit::AttributeData(AZ::Edit::PropertyVisibility::Hide) + } + ); + } + + const AZ::Edit::ElementData* TerrainSurfaceMaterialsListConfig::GetDynamicData(const void* handlerPtr, const void* elementPtr, const AZ::Uuid& ) + { + const TerrainSurfaceMaterialsListConfig* owner = reinterpret_cast(handlerPtr); + if (elementPtr == &owner->m_defaultSurfaceMaterial.m_surfaceTag) + { + return &owner->m_hideSurfaceTagData; + } + return nullptr; + } + void TerrainSurfaceMaterialsListComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& services) { services.push_back(AZ_CRC_CE("TerrainMaterialProviderService")); @@ -116,15 +137,21 @@ namespace Terrain { m_cachedAabb = AZ::Aabb::CreateNull(); + auto checkLoadMaterial = [&](TerrainSurfaceMaterialMapping& material) + { + if (material.m_materialAsset.GetId().IsValid()) + { + material.m_active = false; + material.m_materialAsset.QueueLoad(); + AZ::Data::AssetBus::MultiHandler::BusConnect(material.m_materialAsset.GetId()); + } + }; + // Set all the materials as inactive and start loading. + checkLoadMaterial(m_configuration.m_defaultSurfaceMaterial); for (auto& surfaceMaterialMapping : m_configuration.m_surfaceMaterials) { - if (surfaceMaterialMapping.m_materialAsset.GetId().IsValid()) - { - surfaceMaterialMapping.m_active = false; - surfaceMaterialMapping.m_materialAsset.QueueLoad(); - AZ::Data::AssetBus::MultiHandler::BusConnect(surfaceMaterialMapping.m_materialAsset.GetId()); - } + checkLoadMaterial(surfaceMaterialMapping); } // Announce initial shape using OnShapeChanged @@ -135,24 +162,35 @@ namespace Terrain { TerrainAreaMaterialRequestBus::Handler::BusDisconnect(); + auto checkResetMaterial = [&](TerrainSurfaceMaterialMapping& material) + { + if (material.m_materialAsset.GetId().IsValid()) + { + AZ::Data::AssetBus::MultiHandler::BusDisconnect(material.m_materialAsset.GetId()); + material.m_materialAsset.Release(); + material.m_materialInstance.reset(); + material.m_activeMaterialAssetId = AZ::Data::AssetId(); + } + }; + + checkResetMaterial(m_configuration.m_defaultSurfaceMaterial); for (auto& surfaceMaterialMapping : m_configuration.m_surfaceMaterials) { - if (surfaceMaterialMapping.m_materialAsset.GetId().IsValid()) - { - AZ::Data::AssetBus::MultiHandler::BusDisconnect(surfaceMaterialMapping.m_materialAsset.GetId()); - surfaceMaterialMapping.m_materialAsset.Release(); - surfaceMaterialMapping.m_materialInstance.reset(); - surfaceMaterialMapping.m_activeMaterialAssetId = AZ::Data::AssetId(); - } + checkResetMaterial(surfaceMaterialMapping); } HandleMaterialStateChanges(); } - int TerrainSurfaceMaterialsListComponent::CountMaterialIDInstances(AZ::Data::AssetId id) const + int TerrainSurfaceMaterialsListComponent::CountMaterialIdInstances(AZ::Data::AssetId id) const { int count = 0; + if (m_configuration.m_defaultSurfaceMaterial.m_activeMaterialAssetId == id) + { + count++; + } + for (const auto& surfaceMaterialMapping : m_configuration.m_surfaceMaterials) { if (surfaceMaterialMapping.m_activeMaterialAssetId == id) @@ -169,28 +207,63 @@ namespace Terrain bool anyMaterialIsActive = false; bool anyMaterialWasAlreadyActive = false; - for (auto& surfaceMaterialMapping : m_configuration.m_surfaceMaterials) { - const bool wasPreviouslyActive = surfaceMaterialMapping.m_active; - const bool isNowActive = (surfaceMaterialMapping.m_materialInstance != nullptr); + // Handle default material first + auto& defaultMaterial = m_configuration.m_defaultSurfaceMaterial; - if (wasPreviouslyActive) - { - anyMaterialWasAlreadyActive = true; - } + const bool wasPreviouslyActive = defaultMaterial.m_active; + defaultMaterial.m_active = (defaultMaterial.m_materialInstance != nullptr); - if (isNowActive) - { - anyMaterialIsActive = true; - } - - surfaceMaterialMapping.m_active = isNowActive; - - if (!wasPreviouslyActive && !isNowActive) + anyMaterialWasAlreadyActive = wasPreviouslyActive; + anyMaterialIsActive = defaultMaterial.m_active; + + if (!wasPreviouslyActive && !defaultMaterial.m_active) { // A material has been assigned but has not yet completed loading. } - else if (!wasPreviouslyActive && isNowActive) + else if (!wasPreviouslyActive && defaultMaterial.m_active) + { + TerrainAreaMaterialNotificationBus::Broadcast( + &TerrainAreaMaterialNotificationBus::Events::OnTerrainDefaultSurfaceMaterialCreated, GetEntityId(), + defaultMaterial.m_materialInstance); + defaultMaterial.m_previousChangeId = defaultMaterial.m_materialInstance->GetCurrentChangeId(); + } + else if (wasPreviouslyActive && !defaultMaterial.m_active) + { + // Don't disconnect from the AssetBus if this material is mapped more than once. + if (CountMaterialIdInstances(defaultMaterial.m_activeMaterialAssetId) == 1) + { + AZ::Data::AssetBus::MultiHandler::BusDisconnect(defaultMaterial.m_activeMaterialAssetId); + } + defaultMaterial = {}; + + TerrainAreaMaterialNotificationBus::Broadcast( + &TerrainAreaMaterialNotificationBus::Events::OnTerrainDefaultSurfaceMaterialDestroyed, GetEntityId()); + } + else if (defaultMaterial.m_materialInstance->GetAssetId() != defaultMaterial.m_activeMaterialAssetId || + defaultMaterial.m_materialInstance->GetCurrentChangeId() != defaultMaterial.m_previousChangeId) + { + defaultMaterial.m_previousChangeId = defaultMaterial.m_materialInstance->GetCurrentChangeId(); + defaultMaterial.m_activeMaterialAssetId = defaultMaterial.m_materialInstance->GetAssetId(); + + TerrainAreaMaterialNotificationBus::Broadcast( + &TerrainAreaMaterialNotificationBus::Events::OnTerrainDefaultSurfaceMaterialChanged, GetEntityId(), defaultMaterial.m_materialInstance); + } + } + + for (auto& surfaceMaterialMapping : m_configuration.m_surfaceMaterials) + { + const bool wasPreviouslyActive = surfaceMaterialMapping.m_active; + surfaceMaterialMapping.m_active = surfaceMaterialMapping.m_materialInstance != nullptr; + + anyMaterialWasAlreadyActive = anyMaterialWasAlreadyActive || wasPreviouslyActive; + anyMaterialIsActive = anyMaterialIsActive || surfaceMaterialMapping.m_active; + + if (!wasPreviouslyActive && !surfaceMaterialMapping.m_active) + { + // A material has been assigned but has not yet completed loading. + } + else if (!wasPreviouslyActive && surfaceMaterialMapping.m_active) { // Remember the asset id so we can disconnect from the AssetBus if the material asset is removed. surfaceMaterialMapping.m_activeMaterialAssetId = surfaceMaterialMapping.m_materialAsset.GetId(); @@ -199,27 +272,47 @@ namespace Terrain &TerrainAreaMaterialNotificationBus::Events::OnTerrainSurfaceMaterialMappingCreated, GetEntityId(), surfaceMaterialMapping.m_surfaceTag, surfaceMaterialMapping.m_materialInstance); + + surfaceMaterialMapping.m_previousChangeId = surfaceMaterialMapping.m_materialInstance->GetCurrentChangeId(); + surfaceMaterialMapping.m_previousTag = surfaceMaterialMapping.m_surfaceTag; } - else if (wasPreviouslyActive && !isNowActive) + else if (wasPreviouslyActive && !surfaceMaterialMapping.m_active) { // Don't disconnect from the AssetBus if this material is mapped more than once. - if (CountMaterialIDInstances(surfaceMaterialMapping.m_activeMaterialAssetId) == 1) + if (CountMaterialIdInstances(surfaceMaterialMapping.m_activeMaterialAssetId) == 1) { AZ::Data::AssetBus::MultiHandler::BusDisconnect(surfaceMaterialMapping.m_activeMaterialAssetId); } - surfaceMaterialMapping.m_activeMaterialAssetId = AZ::Data::AssetId(); + surfaceMaterialMapping.m_activeMaterialAssetId = {}; + surfaceMaterialMapping.m_previousChangeId = AZ::RPI::Material::DEFAULT_CHANGE_ID; + surfaceMaterialMapping.m_previousTag = {}; TerrainAreaMaterialNotificationBus::Broadcast( &TerrainAreaMaterialNotificationBus::Events::OnTerrainSurfaceMaterialMappingDestroyed, GetEntityId(), surfaceMaterialMapping.m_surfaceTag); } - else + else { - TerrainAreaMaterialNotificationBus::Broadcast( - &TerrainAreaMaterialNotificationBus::Events::OnTerrainSurfaceMaterialMappingChanged, GetEntityId(), - surfaceMaterialMapping.m_surfaceTag, - surfaceMaterialMapping.m_materialInstance); + if (surfaceMaterialMapping.m_previousTag != surfaceMaterialMapping.m_surfaceTag) + { + TerrainAreaMaterialNotificationBus::Broadcast( + &TerrainAreaMaterialNotificationBus::Events::OnTerrainSurfaceMaterialMappingTagChanged, GetEntityId(), + surfaceMaterialMapping.m_previousTag, + surfaceMaterialMapping.m_surfaceTag); + surfaceMaterialMapping.m_previousTag = surfaceMaterialMapping.m_surfaceTag; + } + if (surfaceMaterialMapping.m_materialInstance->GetAssetId() != surfaceMaterialMapping.m_activeMaterialAssetId || + surfaceMaterialMapping.m_materialInstance->GetCurrentChangeId() != surfaceMaterialMapping.m_previousChangeId) + { + surfaceMaterialMapping.m_previousChangeId = surfaceMaterialMapping.m_materialInstance->GetCurrentChangeId(); + surfaceMaterialMapping.m_activeMaterialAssetId = surfaceMaterialMapping.m_materialInstance->GetAssetId(); + + TerrainAreaMaterialNotificationBus::Broadcast( + &TerrainAreaMaterialNotificationBus::Events::OnTerrainSurfaceMaterialMappingMaterialChanged, GetEntityId(), + surfaceMaterialMapping.m_surfaceTag, + surfaceMaterialMapping.m_materialInstance); + } } } @@ -290,14 +383,28 @@ namespace Terrain void TerrainSurfaceMaterialsListComponent::OnAssetReady(AZ::Data::Asset asset) { // Find the missing material instance with the correct id. - for (auto& surfaceMaterialMapping : m_configuration.m_surfaceMaterials) + auto checkUpdateMaterialAsset = [](TerrainSurfaceMaterialMapping& mapping, const AZ::Data::Asset& asset) -> bool { - if (surfaceMaterialMapping.m_materialAsset.GetId() == asset.GetId() && - (!surfaceMaterialMapping.m_materialInstance || - surfaceMaterialMapping.m_materialInstance->GetAssetId() != surfaceMaterialMapping.m_materialAsset.GetId())) + if (mapping.m_materialAsset.GetId() == asset.GetId() && + (!mapping.m_materialInstance || mapping.m_materialInstance->GetAssetId() != mapping.m_materialAsset.GetId())) { - surfaceMaterialMapping.m_materialInstance = AZ::RPI::Material::FindOrCreate(surfaceMaterialMapping.m_materialAsset); - surfaceMaterialMapping.m_materialAsset.Release(); + mapping.m_materialInstance = AZ::RPI::Material::FindOrCreate(mapping.m_materialAsset); + mapping.m_materialAsset.Release(); + return true; + } + return false; + }; + + // First check the default material + if (!checkUpdateMaterialAsset(m_configuration.m_defaultSurfaceMaterial, asset)) + { + // If the default materail wasn't updated, then check all the surface material mappings. + for (auto& surfaceMaterialMapping : m_configuration.m_surfaceMaterials) + { + if (checkUpdateMaterialAsset(surfaceMaterialMapping, asset)) + { + break; + } } } HandleMaterialStateChanges(); diff --git a/Gems/Terrain/Code/Source/TerrainRenderer/Components/TerrainSurfaceMaterialsListComponent.h b/Gems/Terrain/Code/Source/TerrainRenderer/Components/TerrainSurfaceMaterialsListComponent.h index 0e32cb12c0..96a7da60de 100644 --- a/Gems/Terrain/Code/Source/TerrainRenderer/Components/TerrainSurfaceMaterialsListComponent.h +++ b/Gems/Terrain/Code/Source/TerrainRenderer/Components/TerrainSurfaceMaterialsListComponent.h @@ -8,15 +8,18 @@ #pragma once -#include -#include #include #include +#include + +#include +#include + #include + #include #include - namespace LmbrCentral { template @@ -27,16 +30,20 @@ namespace Terrain { struct TerrainSurfaceMaterialMapping final { - public: AZ_CLASS_ALLOCATOR(TerrainSurfaceMaterialMapping, AZ::SystemAllocator, 0); AZ_RTTI(TerrainSurfaceMaterialMapping, "{37D2A586-CDDD-4FB7-A7D6-0B4CC575AB8C}"); static void Reflect(AZ::ReflectContext* context); - SurfaceData::SurfaceTag m_surfaceTag; - AZ::Data::AssetId m_activeMaterialAssetId; AZ::Data::Asset m_materialAsset; AZ::Data::Instance m_materialInstance; + AZ::Data::AssetId m_activeMaterialAssetId; + AZ::RPI::Material::ChangeId m_previousChangeId = AZ::RPI::Material::DEFAULT_CHANGE_ID; + + // Surface tags not used by default material + SurfaceData::SurfaceTag m_surfaceTag; + SurfaceData::SurfaceTag m_previousTag; + bool m_active = false; }; @@ -47,7 +54,13 @@ namespace Terrain AZ_RTTI(TerrainSurfaceMaterialsListConfig, "{68A1CB1B-C835-4C3A-8D1C-08692E07711A}", AZ::ComponentConfig); static void Reflect(AZ::ReflectContext* context); + TerrainSurfaceMaterialsListConfig(); + + TerrainSurfaceMaterialMapping m_defaultSurfaceMaterial; AZStd::vector m_surfaceMaterials; + private: + static const AZ::Edit::ElementData* GetDynamicData(const void* handlerPtr, const void* elementPtr, const AZ::Uuid& elementType); + AZ::Edit::ElementData m_hideSurfaceTagData; }; class TerrainSurfaceMaterialsListComponent @@ -78,7 +91,7 @@ namespace Terrain private: void HandleMaterialStateChanges(); - int CountMaterialIDInstances(AZ::Data::AssetId id) const; + int CountMaterialIdInstances(AZ::Data::AssetId id) const; //////////////////////////////////////////////////////////////////////// // ShapeComponentNotificationsBus diff --git a/Gems/Terrain/Code/Source/TerrainRenderer/TerrainAreaMaterialRequestBus.h b/Gems/Terrain/Code/Source/TerrainRenderer/TerrainAreaMaterialRequestBus.h index ae36a2639e..a2225702ef 100644 --- a/Gems/Terrain/Code/Source/TerrainRenderer/TerrainAreaMaterialRequestBus.h +++ b/Gems/Terrain/Code/Source/TerrainRenderer/TerrainAreaMaterialRequestBus.h @@ -45,6 +45,27 @@ namespace Terrain static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single; ////////////////////////////////////////////////////////////////////////// + //! The default surface material has been assigned and loaded + virtual void OnTerrainDefaultSurfaceMaterialCreated( + [[maybe_unused]] AZ::EntityId entityId, + [[maybe_unused]] AZ::Data::Instance material) + { + } + + //! The default surface material has been unassigned + virtual void OnTerrainDefaultSurfaceMaterialDestroyed( + [[maybe_unused]] AZ::EntityId entityId) + { + } + + //! The default surface material has been changed to a different material + virtual void OnTerrainDefaultSurfaceMaterialChanged( + [[maybe_unused]] AZ::EntityId entityId, + [[maybe_unused]] AZ::Data::Instance newMaterial) + { + } + + //! A loaded material mapped to a valid surface tag has been created virtual void OnTerrainSurfaceMaterialMappingCreated( [[maybe_unused]] AZ::EntityId entityId, [[maybe_unused]] SurfaceData::SurfaceTag surface, @@ -52,19 +73,30 @@ namespace Terrain { } + //! Either the material or surface tag was unassigned, making this mapping invalid virtual void OnTerrainSurfaceMaterialMappingDestroyed( [[maybe_unused]] AZ::EntityId entityId, [[maybe_unused]] SurfaceData::SurfaceTag surface) { } - virtual void OnTerrainSurfaceMaterialMappingChanged( + //! The surface tag has changed to tag for an existing material + virtual void OnTerrainSurfaceMaterialMappingTagChanged( + [[maybe_unused]] AZ::EntityId entityId, + [[maybe_unused]] SurfaceData::SurfaceTag oldSurface, + [[maybe_unused]] SurfaceData::SurfaceTag newSurface) + { + } + + //! The material has changed for an existing surface tag + virtual void OnTerrainSurfaceMaterialMappingMaterialChanged( [[maybe_unused]] AZ::EntityId entityId, [[maybe_unused]] SurfaceData::SurfaceTag surface, [[maybe_unused]] AZ::Data::Instance material) { } + //! The bounds of this set of surface material mappings has changed virtual void OnTerrainSurfaceMaterialMappingRegionChanged( [[maybe_unused]] AZ::EntityId entityId, [[maybe_unused]] const AZ::Aabb& oldRegion, diff --git a/Gems/Terrain/Code/Source/TerrainRenderer/TerrainDetailMaterialManager.cpp b/Gems/Terrain/Code/Source/TerrainRenderer/TerrainDetailMaterialManager.cpp index a00d2efe59..f817c138a7 100644 --- a/Gems/Terrain/Code/Source/TerrainRenderer/TerrainDetailMaterialManager.cpp +++ b/Gems/Terrain/Code/Source/TerrainRenderer/TerrainDetailMaterialManager.cpp @@ -106,6 +106,8 @@ namespace Terrain return; } + InitializePassthroughDetailMaterial(); + ClipmapBoundsDescriptor desc; desc.m_clipmapUpdateMultiple = 1; desc.m_clipToWorldScale = DetailTextureScale; @@ -260,20 +262,73 @@ namespace Terrain m_dirtyDetailRegion.AddAabb(dirtyRegion); } } + + bool TerrainDetailMaterialManager::ForSurfaceTag(DetailMaterialListRegion& materialRegion, + SurfaceData::SurfaceTag surfaceTag, DefaultMaterialSurfaceCallback callback) + { + for (DetailMaterialSurface& surface : materialRegion.m_materialsForSurfaces) + { + if (surface.m_surfaceTag == surfaceTag) + { + callback(surface); + return true; + } + } + return false; + } + void TerrainDetailMaterialManager::OnTerrainDefaultSurfaceMaterialCreated(AZ::EntityId entityId, MaterialInstance material) + { + DetailMaterialListRegion& materialRegion = FindOrCreateByEntityId(entityId, m_detailMaterialRegions); + AZ_Error("TerrainDetailMaterialManager", materialRegion.m_defaultDetailMaterialId == InvalidDetailMaterailId, + "Default detail material created but was already set for this region."); + + materialRegion.m_defaultDetailMaterialId = CreateOrUpdateDetailMaterial(material); + m_detailMaterials.GetData(materialRegion.m_defaultDetailMaterialId).refCount++; + m_dirtyDetailRegion.AddAabb(materialRegion.m_region); + } + + void TerrainDetailMaterialManager::OnTerrainDefaultSurfaceMaterialDestroyed(AZ::EntityId entityId) + { + DetailMaterialListRegion* materialRegion = FindByEntityId(entityId, m_detailMaterialRegions); + if (materialRegion == nullptr) + { + AZ_Assert(false, "OnTerrainDefaultSurfaceMaterialDestroyed() called for region that doesn't exist."); + return; + } + + CheckDetailMaterialForDeletion(materialRegion->m_defaultDetailMaterialId); + materialRegion->m_defaultDetailMaterialId = InvalidDetailMaterailId; + } + + void TerrainDetailMaterialManager::OnTerrainDefaultSurfaceMaterialChanged(AZ::EntityId entityId, MaterialInstance newMaterial) + { + DetailMaterialListRegion* materialRegion = FindByEntityId(entityId, m_detailMaterialRegions); + if (materialRegion == nullptr) + { + AZ_Assert(false, "OnTerrainDefaultSurfaceMaterialChanged() called for region that doesn't exist."); + return; + } + + // Update existing entry or create a new material entry + uint16_t materialId = CreateOrUpdateDetailMaterial(newMaterial); + if (materialRegion->m_defaultDetailMaterialId != materialId) + { + ++m_detailMaterials.GetData(materialId).refCount; + CheckDetailMaterialForDeletion(materialRegion->m_defaultDetailMaterialId); + materialRegion->m_defaultDetailMaterialId = materialId; + } + } + void TerrainDetailMaterialManager::OnTerrainSurfaceMaterialMappingCreated(AZ::EntityId entityId, SurfaceData::SurfaceTag surfaceTag, MaterialInstance material) { DetailMaterialListRegion& materialRegion = FindOrCreateByEntityId(entityId, m_detailMaterialRegions); // Validate that the surface tag is new - for (DetailMaterialSurface& surface : materialRegion.m_materialsForSurfaces) + ForSurfaceTag(materialRegion, surfaceTag, [](DetailMaterialSurface&) { - if (surface.m_surfaceTag == surfaceTag) - { - AZ_Error(TerrainDetailMaterialManagerName, false, "Already have a surface material mapping for this surface tag."); - return; - } - } + AZ_Error(TerrainDetailMaterialManagerName, false, "Already have a surface material mapping for this surface tag."); + }); uint16_t detailMaterialId = CreateOrUpdateDetailMaterial(material); materialRegion.m_materialsForSurfaces.push_back({ surfaceTag, detailMaterialId }); @@ -284,52 +339,71 @@ namespace Terrain void TerrainDetailMaterialManager::OnTerrainSurfaceMaterialMappingDestroyed(AZ::EntityId entityId, SurfaceData::SurfaceTag surfaceTag) { DetailMaterialListRegion& materialRegion = FindOrCreateByEntityId(entityId, m_detailMaterialRegions); - - for (DetailMaterialSurface& surface : materialRegion.m_materialsForSurfaces) + + [[maybe_unused]] bool found = ForSurfaceTag(materialRegion, surfaceTag, + [&](DetailMaterialSurface& surface) { - if (surface.m_surfaceTag == surfaceTag) - { - CheckDetailMaterialForDeletion(surface.m_detailMaterialId); + CheckDetailMaterialForDeletion(surface.m_detailMaterialId); - if (surface.m_surfaceTag != materialRegion.m_materialsForSurfaces.back().m_surfaceTag) - { - AZStd::swap(surface, materialRegion.m_materialsForSurfaces.back()); - } - materialRegion.m_materialsForSurfaces.pop_back(); - m_dirtyDetailRegion.AddAabb(materialRegion.m_region); - return; + if (surface.m_surfaceTag != materialRegion.m_materialsForSurfaces.back().m_surfaceTag) + { + AZStd::swap(surface, materialRegion.m_materialsForSurfaces.back()); } + materialRegion.m_materialsForSurfaces.pop_back(); + m_dirtyDetailRegion.AddAabb(materialRegion.m_region); + return; + }); + + AZ_Error(TerrainDetailMaterialManagerName, found, "Could not find surface tag to destroy for OnTerrainSurfaceMaterialMappingDestroyed()."); + } + + void TerrainDetailMaterialManager::OnTerrainSurfaceMaterialMappingMaterialChanged( + AZ::EntityId entityId, SurfaceData::SurfaceTag surfaceTag, MaterialInstance material) + { + DetailMaterialListRegion* materialRegion = FindByEntityId(entityId, m_detailMaterialRegions); + if (materialRegion == nullptr) + { + AZ_Assert(false, "OnTerrainSurfaceMaterialMappingMaterialChanged() called for region that doesn't exist."); + return; } - AZ_Error(TerrainDetailMaterialManagerName, false, "Could not find surface tag to destroy for OnTerrainSurfaceMaterialMappingDestroyed()."); + + // Update existing entry or create a new material entry + uint16_t materialId = CreateOrUpdateDetailMaterial(material); + + [[maybe_unused]] bool found = ForSurfaceTag(*materialRegion, surfaceTag, + [&](DetailMaterialSurface& surface) + { + if (surface.m_detailMaterialId != materialId) + { + // Updated material was a different asset than the old material, decrement ref count and + // delete if no other surface tags are using it. + ++m_detailMaterials.GetData(materialId).refCount; + CheckDetailMaterialForDeletion(surface.m_detailMaterialId); + surface.m_detailMaterialId = materialId; + } + m_dirtyDetailRegion.AddAabb(materialRegion->m_region); + }); + + AZ_Assert(found, "OnTerrainSurfaceMaterialMappingMaterialChanged() called for tag that doesn't exist."); } - void TerrainDetailMaterialManager::OnTerrainSurfaceMaterialMappingChanged(AZ::EntityId entityId, SurfaceData::SurfaceTag surfaceTag, MaterialInstance material) + void TerrainDetailMaterialManager::OnTerrainSurfaceMaterialMappingTagChanged( + AZ::EntityId entityId, SurfaceData::SurfaceTag oldTag, SurfaceData::SurfaceTag newTag) { - DetailMaterialListRegion& materialRegion = FindOrCreateByEntityId(entityId, m_detailMaterialRegions); - - bool found = false; - uint16_t materialId = CreateOrUpdateDetailMaterial(material); - for (DetailMaterialSurface& surface : materialRegion.m_materialsForSurfaces) + DetailMaterialListRegion* materialRegion = FindByEntityId(entityId, m_detailMaterialRegions); + if (materialRegion == nullptr) { - if (surface.m_surfaceTag == surfaceTag) - { - found = true; - if (surface.m_detailMaterialId != materialId) - { - ++m_detailMaterials.GetData(materialId).refCount; - CheckDetailMaterialForDeletion(surface.m_detailMaterialId); - surface.m_detailMaterialId = materialId; - } - break; - } + AZ_Assert(false, "OnTerrainSurfaceMaterialMappingTagChanged() called for region that doesn't exist."); + return; } - - if (!found) + + [[maybe_unused]] bool found = ForSurfaceTag(*materialRegion, oldTag, + [&](DetailMaterialSurface& surface) { - ++m_detailMaterials.GetData(materialId).refCount; - materialRegion.m_materialsForSurfaces.push_back({ surfaceTag, materialId }); - } - m_dirtyDetailRegion.AddAabb(materialRegion.m_region); + surface.m_surfaceTag = newTag; + m_dirtyDetailRegion.AddAabb(materialRegion->m_region); + }); + AZ_Assert(found, "OnTerrainSurfaceMaterialMappingTagChanged() called for tag that doesn't exist."); } void TerrainDetailMaterialManager::OnTerrainSurfaceMaterialMappingRegionChanged(AZ::EntityId entityId, const AZ::Aabb& oldRegion, const AZ::Aabb& newRegion) @@ -667,23 +741,38 @@ namespace Terrain bool isFirstMaterial = true; float firstWeight = 0.0f; AZ::Vector2 position(surfacePoint.m_position.GetX(), surfacePoint.m_position.GetY()); + const DetailMaterialListRegion* region = FindRegionForPosition(position); + + if (region == nullptr) + { + pixels.at(index).m_material1 = m_passthroughMaterialId; + ++index; + return; + } + for (const auto& surfaceTagWeight : surfacePoint.m_surfaceTags) { if (surfaceTagWeight.m_weight > 0.0f) { AZ::Crc32 surfaceType = surfaceTagWeight.m_surfaceType; - uint16_t materialId = GetDetailMaterialForSurfaceTypeAndPosition(surfaceType, position); - if (materialId != m_detailMaterials.NoFreeSlot && materialId < 255) + uint16_t materialId = GetDetailMaterialForSurfaceType(*region, surfaceType); + if (materialId < 255) { if (isFirstMaterial) { + // First material is valid. Save its weight to calculate blend later pixels.at(index).m_material1 = aznumeric_cast(materialId); firstWeight = surfaceTagWeight.m_weight; - // m_blend only needs to be calculated is material 2 is found, otherwise the initial value of 0 is correct. isFirstMaterial = false; + static constexpr float MaxValueBeforeRounding = 254.5f / 255.0f; + if (firstWeight >= MaxValueBeforeRounding) + { + break; + } } else { + // Second material is valid, weight is relative based on first material's weight. pixels.at(index).m_material2 = aznumeric_cast(materialId); float totalWeight = firstWeight + surfaceTagWeight.m_weight; float blendWeight = 1.0f - (firstWeight / totalWeight); @@ -691,11 +780,37 @@ namespace Terrain break; } } + continue; // search for second material } else { - break; // since the list is ordered, no other materials are in the list with positive weights. + // No more valid materials in list since surfaceTagWeight is ordered. + + uint8_t defaultMaterial = region->m_defaultDetailMaterialId == InvalidDetailMaterailId ? m_passthroughMaterialId : + aznumeric_cast(m_detailMaterials.GetData(region->m_defaultDetailMaterialId).m_detailMaterialBufferIndex); + + if (isFirstMaterial) + { + // Only one material and it's the default material. + pixels.at(index).m_material1 = defaultMaterial; + } + else + { + // Second material is default, weight is exactly what the first material requested + pixels.at(index).m_material2 = defaultMaterial; + float blendWeight = 1.0f - AZStd::clamp(firstWeight, 0.0f, 1.0f); + pixels.at(index).m_blend = aznumeric_cast(AZStd::round(blendWeight * 255.0f)); + } } + + if (pixels.at(index).m_material1 == pixels.at(index).m_material2) + { + // If the materials are the same, then make the blend 100% on the first id so the shader + // doesn't blend identical materials + pixels.at(index).m_blend = 0; + } + + break; } ++index; }; @@ -723,23 +838,37 @@ namespace Terrain m_detailTextureImage->UpdateImageContents(imageUpdateRequest); } - - uint16_t TerrainDetailMaterialManager::GetDetailMaterialForSurfaceTypeAndPosition(AZ::Crc32 surfaceType, const AZ::Vector2& position) + + uint16_t TerrainDetailMaterialManager::GetDetailMaterialForSurfaceType(const DetailMaterialListRegion& materialRegion, AZ::Crc32 surfaceType) const + { + for (const auto& materialSurface : materialRegion.m_materialsForSurfaces) + { + if (materialSurface.m_surfaceTag == surfaceType) + { + return m_detailMaterials.GetData(materialSurface.m_detailMaterialId).m_detailMaterialBufferIndex; + } + } + return InvalidDetailMaterailId; + } + + auto TerrainDetailMaterialManager::FindRegionForPosition(const AZ::Vector2& position) const -> const DetailMaterialListRegion* { for (const auto& materialRegion : m_detailMaterialRegions.GetDataVector()) { if (materialRegion.m_region.Contains(AZ::Vector3(position.GetX(), position.GetY(), 0.0f))) { - for (const auto& materialSurface : materialRegion.m_materialsForSurfaces) - { - if (materialSurface.m_surfaceTag == surfaceType) - { - return m_detailMaterials.GetData(materialSurface.m_detailMaterialId).m_detailMaterialBufferIndex; - } - } + return &materialRegion; } } - return m_detailMaterials.NoFreeSlot; + return nullptr; + } + + void TerrainDetailMaterialManager::InitializePassthroughDetailMaterial() + { + m_passthroughMaterialId = aznumeric_cast(m_detailMaterialShaderData.Reserve()); + DetailMaterialShaderData& materialShaderData = m_detailMaterialShaderData.GetElement(m_passthroughMaterialId); + // Material defaults to white (1.0, 1.0, 1.0), set the blend mode to multiply so it passes through to the macro material. + materialShaderData.m_flags = DetailTextureFlags::BlendModeMultiply; } auto TerrainDetailMaterialManager::FindByEntityId(AZ::EntityId entityId, AZ::Render::IndexedDataVector& container) @@ -784,5 +913,5 @@ namespace Terrain } AZ_Assert(false, "Entity Id not found in container.") } - + } diff --git a/Gems/Terrain/Code/Source/TerrainRenderer/TerrainDetailMaterialManager.h b/Gems/Terrain/Code/Source/TerrainRenderer/TerrainDetailMaterialManager.h index 3f63812e47..16423251ec 100644 --- a/Gems/Terrain/Code/Source/TerrainRenderer/TerrainDetailMaterialManager.h +++ b/Gems/Terrain/Code/Source/TerrainRenderer/TerrainDetailMaterialManager.h @@ -11,6 +11,7 @@ #include #include #include +#include #include @@ -151,7 +152,11 @@ namespace Terrain AZ::EntityId m_entityId; AZ::Aabb m_region{AZ::Aabb::CreateNull()}; AZStd::vector m_materialsForSurfaces; + uint16_t m_defaultDetailMaterialId; }; + + using DetailMaterialContainer = AZ::Render::IndexedDataVector; + static constexpr auto InvalidDetailMaterailId = DetailMaterialContainer::NoFreeSlot; // System-level parameters static constexpr int32_t DetailTextureSize{ 1024 }; @@ -162,9 +167,14 @@ namespace Terrain void OnTerrainDataChanged(const AZ::Aabb& dirtyRegion, TerrainDataChangedMask dataChangedMask) override; // TerrainAreaMaterialNotificationBus overrides... + void OnTerrainDefaultSurfaceMaterialCreated(AZ::EntityId entityId, AZ::Data::Instance material) override; + void OnTerrainDefaultSurfaceMaterialDestroyed(AZ::EntityId entityId) override; + void OnTerrainDefaultSurfaceMaterialChanged(AZ::EntityId entityId, AZ::Data::Instance newMaterial) override; void OnTerrainSurfaceMaterialMappingCreated(AZ::EntityId entityId, SurfaceData::SurfaceTag surfaceTag, MaterialInstance material) override; void OnTerrainSurfaceMaterialMappingDestroyed(AZ::EntityId entityId, SurfaceData::SurfaceTag surfaceTag) override; - void OnTerrainSurfaceMaterialMappingChanged(AZ::EntityId entityId, SurfaceData::SurfaceTag surfaceTag, MaterialInstance material) override; + void OnTerrainSurfaceMaterialMappingMaterialChanged(AZ::EntityId entityId, SurfaceData::SurfaceTag surfaceTag, MaterialInstance material) override; + void OnTerrainSurfaceMaterialMappingTagChanged( + AZ::EntityId entityId, SurfaceData::SurfaceTag oldSurfaceTag, SurfaceData::SurfaceTag newSurfaceTag) override; void OnTerrainSurfaceMaterialMappingRegionChanged(AZ::EntityId entityId, const AZ::Aabb& oldRegion, const AZ::Aabb& newRegion) override; //! Removes all images from all detail materials from the bindless image array @@ -186,22 +196,32 @@ namespace Terrain //! Updates the detail texture in a given area void UpdateDetailTexture(const AZ::Aabb& worldUpdateAabb, const Aabb2i& textureUpdateAabb); - //! Finds the detail material Id for a surface type and position - uint16_t GetDetailMaterialForSurfaceTypeAndPosition(AZ::Crc32 surfaceType, const AZ::Vector2& position); + //! Finds the detail material Id for a region and surface type + uint16_t GetDetailMaterialForSurfaceType(const DetailMaterialListRegion& materialRegion, AZ::Crc32 surfaceType) const; + //! Finds a region for a position. Returns nullptr if none found. + const DetailMaterialListRegion* FindRegionForPosition(const AZ::Vector2& position) const; + + //! Initializes shader data for the default passthrough material which is used when no other detail material is found. + void InitializePassthroughDetailMaterial(); + + using DefaultMaterialSurfaceCallback = AZStd::function; + bool ForSurfaceTag(DetailMaterialListRegion& materialRegion, + SurfaceData::SurfaceTag surfaceTag, DefaultMaterialSurfaceCallback callback); DetailMaterialListRegion* FindByEntityId(AZ::EntityId entityId, AZ::Render::IndexedDataVector& container); DetailMaterialListRegion& FindOrCreateByEntityId(AZ::EntityId entityId, AZ::Render::IndexedDataVector& container); void RemoveByEntityId(AZ::EntityId entityId, AZ::Render::IndexedDataVector& container); - + AZStd::shared_ptr m_bindlessImageHandler; AZ::Data::Instance m_detailTextureImage; - AZ::Render::IndexedDataVector m_detailMaterials; + DetailMaterialContainer m_detailMaterials; AZ::Render::IndexedDataVector m_detailMaterialRegions; AZ::Render::SparseVector m_detailMaterialShaderData; AZ::Render::GpuBufferHandler m_detailMaterialDataBuffer; - + uint8_t m_passthroughMaterialId = 0; + AZ::Aabb m_dirtyDetailRegion{ AZ::Aabb::CreateNull() }; ClipmapBounds m_detailMaterialIdBounds; @@ -212,6 +232,6 @@ namespace Terrain bool m_isInitialized{ false }; bool m_detailMaterialBufferNeedsUpdate{ false }; bool m_detailImageNeedsUpdate{ false }; - + }; } diff --git a/Gems/Terrain/Code/Source/TerrainSystem/TerrainSystem.cpp b/Gems/Terrain/Code/Source/TerrainSystem/TerrainSystem.cpp index 8976faadac..6ce83094e4 100644 --- a/Gems/Terrain/Code/Source/TerrainSystem/TerrainSystem.cpp +++ b/Gems/Terrain/Code/Source/TerrainSystem/TerrainSystem.cpp @@ -177,6 +177,193 @@ bool TerrainSystem::InWorldBounds(float x, float y) const return false; } +// Generate positions to be queried based on the sampler type. +void TerrainSystem::GenerateQueryPositions(const AZStd::span& inPositions, + AZStd::vector& outPositions, + Sampler sampler) const +{ + const float minHeight = m_currentSettings.m_worldBounds.GetMin().GetZ(); + for (auto& position : inPositions) + { + switch(sampler) + { + case AzFramework::Terrain::TerrainDataRequests::Sampler::BILINEAR: + { + AZ::Vector2 normalizedDelta; + AZ::Vector2 pos0; + ClampPosition(position.GetX(), position.GetY(), pos0, normalizedDelta); + const AZ::Vector2 pos1(pos0.GetX() + m_currentSettings.m_heightQueryResolution, + pos0.GetY() + m_currentSettings.m_heightQueryResolution); + outPositions.emplace_back(AZ::Vector3(pos0.GetX(), pos0.GetY(), minHeight)); + outPositions.emplace_back(AZ::Vector3(pos1.GetX(), pos0.GetY(), minHeight)); + outPositions.emplace_back(AZ::Vector3(pos0.GetX(), pos1.GetY(), minHeight)); + outPositions.emplace_back(AZ::Vector3(pos1.GetX(), pos1.GetY(), minHeight)); + } + break; + case AzFramework::Terrain::TerrainDataRequests::Sampler::CLAMP: + { + AZ::Vector2 normalizedDelta; + AZ::Vector2 clampedPosition; + ClampPosition(position.GetX(), position.GetY(), clampedPosition, normalizedDelta); + outPositions.emplace_back(AZ::Vector3(clampedPosition.GetX(), clampedPosition.GetY(), minHeight)); + } + break; + case AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT: + [[fallthrough]]; + default: + outPositions.emplace_back(AZ::Vector3(position.GetX(), position.GetY(), minHeight)); + break; + } + } +} + +AZStd::vector TerrainSystem::GenerateInputPositionsFromRegion( + const AZ::Aabb& inRegion, + const AZ::Vector2& stepSize) const +{ + AZStd::vector inPositions; + const auto [numSamplesX, numSamplesY] = GetNumSamplesFromRegion(inRegion, stepSize); + inPositions.reserve(numSamplesX * numSamplesY); + + for (size_t y = 0; y < numSamplesY; y++) + { + float fy = aznumeric_cast(inRegion.GetMin().GetY() + (y * stepSize.GetY())); + for (size_t x = 0; x < numSamplesX; x++) + { + float fx = aznumeric_cast(inRegion.GetMin().GetX() + (x * stepSize.GetX())); + inPositions.emplace_back(AZ::Vector3(fx, fy, 0.0f)); + } + } + + return inPositions; +} + +void TerrainSystem::MakeBulkQueries( + const AZStd::span inPositions, + AZStd::span outPositions, + AZStd::span outTerrainExists, + AZStd::span outSurfaceWeights, + BulkQueriesCallback queryCallback) const +{ + AZ::Aabb bounds; + AZ::EntityId prevAreaId = FindBestAreaEntityAtPosition(inPositions[0].GetX(), inPositions[0].GetY(), bounds); + + // We use a sliding window here and update the window end for each + // position that falls in the same area as the previous positions. This consumes lesser memory + // than sorting the points into separate lists and handling putting them back together. + // This may be sub optimal if the points are randomly distributed in the list as opposed + // to points in the same area id being close to each other. + size_t windowStart = 0; + size_t windowEnd = 0; + const size_t numPositions = inPositions.size(); + for(int i = 1; i < numPositions; i++) + { + AZ::EntityId areaId = FindBestAreaEntityAtPosition(inPositions[i].GetX(), inPositions[i].GetY(), bounds); + bool queryHeights = false; + if (areaId == prevAreaId) + { + // Update window end to current position. + // If it's the last position, submit the query. + windowEnd = i; + if (windowEnd == numPositions - 1) + { + queryHeights = true; + } + } + else + { + queryHeights = true; + } + + if (queryHeights) + { + // If the area id is a default entity id, it usually means the + // position is outside world bounds. + if (prevAreaId != AZ::EntityId()) + { + size_t spanLength = (windowEnd - windowStart) + 1; + queryCallback(AZStd::span(inPositions.begin() + windowStart, spanLength), + AZStd::span(outPositions.begin() + windowStart, spanLength), + AZStd::span(outTerrainExists.begin() + windowStart, spanLength), + AZStd::span(outSurfaceWeights.begin() + windowStart, spanLength), + prevAreaId); + } + + // Reset the window to start at the current position. Set the new area + // id on which to run the next query. + windowStart = windowEnd = i; + prevAreaId = areaId; + } + } +} + +void TerrainSystem::GetHeightsSynchronous(const AZStd::span& inPositions, Sampler sampler, + AZStd::span heights, AZStd::span terrainExists) const +{ + AZStd::shared_lock lock(m_areaMutex); + + AZStd::vector outPositions; + AZStd::vector outTerrainExists; + + // outPositions holds the iterators to results of the bulk queries. + // In the case of the bilinear sampler, we'll be making 4 queries per + // input position. + size_t indexStepSize = (sampler == AzFramework::Terrain::TerrainDataRequests::Sampler::BILINEAR) ? 4 : 1; + outPositions.reserve(inPositions.size() * indexStepSize); + outTerrainExists.resize(inPositions.size() * indexStepSize); + + GenerateQueryPositions(inPositions, outPositions, sampler); + + auto callback = []([[maybe_unused]] const AZStd::span inPositions, + AZStd::span outPositions, + AZStd::span outTerrainExists, + [[maybe_unused]] AZStd::span outSurfaceWeights, + AZ::EntityId areaId) + { + AZ_Assert((inPositions.size() == outPositions.size() && inPositions.size() == outTerrainExists.size()), + "The sizes of the terrain exists list and in/out positions list should match."); + Terrain::TerrainAreaHeightRequestBus::Event(areaId, &Terrain::TerrainAreaHeightRequestBus::Events::GetHeights, + outPositions, outTerrainExists); + }; + + // This will be unused for heights. It's fine if it's empty. + AZStd::vector outSurfaceWeights; + MakeBulkQueries(outPositions, outPositions, outTerrainExists, outSurfaceWeights, callback); + + // Compute/store the final result + for (size_t i = 0, iteratorIndex = 0; i < inPositions.size(); i++, iteratorIndex += indexStepSize) + { + switch(sampler) + { + case AzFramework::Terrain::TerrainDataRequests::Sampler::BILINEAR: + { + // We now need to compute the final height after all the bulk queries are done. + AZ::Vector2 normalizedDelta; + AZ::Vector2 clampedPosition; + ClampPosition(inPositions[i].GetX(), inPositions[i].GetY(), clampedPosition, normalizedDelta); + const float heightX0Y0 = outPositions[iteratorIndex].GetZ(); + const float heightX1Y0 = outPositions[iteratorIndex + 1].GetZ(); + const float heightX0Y1 = outPositions[iteratorIndex + 2].GetZ(); + const float heightX1Y1 = outPositions[iteratorIndex + 3].GetZ(); + const float heightXY0 = AZ::Lerp(heightX0Y0, heightX1Y0, normalizedDelta.GetX()); + const float heightXY1 = AZ::Lerp(heightX0Y1, heightX1Y1, normalizedDelta.GetX()); + heights[i] = AZ::Lerp(heightXY0, heightXY1, normalizedDelta.GetY()); + terrainExists[i] = outTerrainExists[iteratorIndex]; + } + break; + case AzFramework::Terrain::TerrainDataRequests::Sampler::CLAMP: + [[fallthrough]]; + case AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT: + [[fallthrough]]; + default: + // For clamp and exact, we just need to store the results of the bulk query. + heights[i] = outPositions[iteratorIndex].GetZ(); + terrainExists[i] = outTerrainExists[iteratorIndex]; + break; + } + } +} + float TerrainSystem::GetHeightSynchronous(float x, float y, Sampler sampler, bool* terrainExistsPtr) const { bool terrainExists = false; @@ -315,6 +502,39 @@ bool TerrainSystem::GetIsHoleFromFloats(float x, float y, Sampler sampler) const return !terrainExists; } +void TerrainSystem::GetNormalsSynchronous(const AZStd::span& inPositions, Sampler sampler, + AZStd::span normals, AZStd::span terrainExists) const +{ + AZStd::vector directionVectors; + directionVectors.reserve(inPositions.size() * 4); + const AZ::Vector2 range(m_currentSettings.m_heightQueryResolution / 2.0f, m_currentSettings.m_heightQueryResolution / 2.0f); + size_t indexStepSize = 4; + for (auto& position : inPositions) + { + directionVectors.emplace_back(position.GetX(), position.GetY() - range.GetY(), 0.0f); + directionVectors.emplace_back(position.GetX() - range.GetX(), position.GetY(), 0.0f); + directionVectors.emplace_back(position.GetX() + range.GetX(), position.GetY(), 0.0f); + directionVectors.emplace_back(position.GetX(), position.GetY() + range.GetY(), 0.0f); + } + + AZStd::vector heights(directionVectors.size()); + AZStd::vector exists(directionVectors.size()); + GetHeightsSynchronous(directionVectors, sampler, heights, exists); + + for (size_t i = 0, iteratorIndex = 0; i < inPositions.size(); i++, iteratorIndex += indexStepSize) + { + directionVectors[iteratorIndex].SetZ(heights[iteratorIndex]); + directionVectors[iteratorIndex + 1].SetZ(heights[iteratorIndex + 1]); + directionVectors[iteratorIndex + 2].SetZ(heights[iteratorIndex + 2]); + directionVectors[iteratorIndex + 3].SetZ(heights[iteratorIndex + 3]); + + normals[i] = (directionVectors[iteratorIndex + 2] - directionVectors[iteratorIndex + 1]). + Cross(directionVectors[iteratorIndex + 3] - directionVectors[iteratorIndex]).GetNormalized(); + + terrainExists[i] = exists[iteratorIndex]; + } +} + AZ::Vector3 TerrainSystem::GetNormalSynchronous(float x, float y, Sampler sampler, bool* terrainExistsPtr) const { AZStd::shared_lock lock(m_areaMutex); @@ -471,6 +691,35 @@ AZ::EntityId TerrainSystem::FindBestAreaEntityAtPosition(float x, float y, AZ::A return AZ::EntityId(); } +void TerrainSystem::GetOrderedSurfaceWeightsFromList( + const AZStd::span& inPositions, + [[maybe_unused]] Sampler sampler, + AZStd::span outSurfaceWeightsList, + AZStd::span terrainExists) const +{ + if (terrainExists.size() == outSurfaceWeightsList.size()) + { + AZStd::vector heights(inPositions.size()); + GetHeightsSynchronous(inPositions, AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT, heights, terrainExists); + } + + auto callback = [](const AZStd::span inPositions, + [[maybe_unused]] AZStd::span outPositions, + [[maybe_unused]] AZStd::span outTerrainExists, + AZStd::span outSurfaceWeights, + AZ::EntityId areaId) + { + AZ_Assert(inPositions.size() == outSurfaceWeights.size(), + "The sizes of the surface weights list and in/out positions list should match."); + Terrain::TerrainAreaSurfaceRequestBus::Event(areaId, &Terrain::TerrainAreaSurfaceRequestBus::Events::GetSurfaceWeightsFromList, + inPositions, outSurfaceWeights); + }; + + // This will be unused for surface weights. It's fine if it's empty. + AZStd::vector outPositions; + MakeBulkQueries(inPositions, outPositions, terrainExists, outSurfaceWeightsList, callback); +} + void TerrainSystem::GetOrderedSurfaceWeights( const float x, const float y, @@ -551,13 +800,17 @@ void TerrainSystem::ProcessHeightsFromList( return; } + AZStd::vector terrainExists(inPositions.size()); + AZStd::vector heights(inPositions.size()); + + GetHeightsSynchronous(inPositions, sampleFilter, heights, terrainExists); + AzFramework::SurfaceData::SurfacePoint surfacePoint; - for (const auto& position : inPositions) + for (size_t i = 0; i < inPositions.size(); i++) { - bool terrainExists = false; - surfacePoint.m_position = position; - surfacePoint.m_position.SetZ(GetHeight(position, sampleFilter, &terrainExists)); - perPositionCallback(surfacePoint, terrainExists); + surfacePoint.m_position = inPositions[i]; + surfacePoint.m_position.SetZ(heights[i]); + perPositionCallback(surfacePoint, terrainExists[i]); } } @@ -571,13 +824,17 @@ void TerrainSystem::ProcessNormalsFromList( return; } + AZStd::vector terrainExists(inPositions.size()); + AZStd::vector normals(inPositions.size()); + + GetNormalsSynchronous(inPositions, sampleFilter, normals, terrainExists); + AzFramework::SurfaceData::SurfacePoint surfacePoint; - for (const auto& position : inPositions) + for (size_t i = 0; i < inPositions.size(); i++) { - bool terrainExists = false; - surfacePoint.m_position = position; - surfacePoint.m_normal = GetNormal(position, sampleFilter, &terrainExists); - perPositionCallback(surfacePoint, terrainExists); + surfacePoint.m_position = inPositions[i]; + surfacePoint.m_normal = AZStd::move(normals[i]); + perPositionCallback(surfacePoint, terrainExists[i]); } } @@ -591,13 +848,17 @@ void TerrainSystem::ProcessSurfaceWeightsFromList( return; } + AZStd::vector outSurfaceWeightsList(inPositions.size()); + AZStd::vector terrainExists(inPositions.size()); + + GetOrderedSurfaceWeightsFromList(inPositions, sampleFilter, outSurfaceWeightsList, terrainExists); + AzFramework::SurfaceData::SurfacePoint surfacePoint; - for (const auto& position : inPositions) + for (size_t i = 0; i < inPositions.size(); i++) { - bool terrainExists = false; - surfacePoint.m_position = position; - GetSurfaceWeights(position, surfacePoint.m_surfaceTags, sampleFilter, &terrainExists); - perPositionCallback(surfacePoint, terrainExists); + surfacePoint.m_position = inPositions[i]; + surfacePoint.m_surfaceTags = AZStd::move(outSurfaceWeightsList[i]); + perPositionCallback(surfacePoint, terrainExists[i]); } } @@ -611,13 +872,26 @@ void TerrainSystem::ProcessSurfacePointsFromList( return; } + AZStd::vector heights(inPositions.size()); + AZStd::vector normals(inPositions.size()); + AZStd::vector outSurfaceWeightsList(inPositions.size()); + AZStd::vector terrainExists(inPositions.size()); + + GetHeightsSynchronous(inPositions, sampleFilter, heights, terrainExists); + GetNormalsSynchronous(inPositions, sampleFilter, normals, terrainExists); + + // We can skip the unnecessary call to GetHeights since we already + // got the terrain exists flags in the earlier call to GetHeights + AZStd::vector terrainExistsEmpty; + GetOrderedSurfaceWeightsFromList(inPositions, sampleFilter, outSurfaceWeightsList, terrainExistsEmpty); + AzFramework::SurfaceData::SurfacePoint surfacePoint; - for (const auto& position : inPositions) + for (size_t i = 0; i < inPositions.size(); i++) { - bool terrainExists = false; - surfacePoint.m_position = position; - GetSurfacePoint(position, surfacePoint, sampleFilter, &terrainExists); - perPositionCallback(surfacePoint, terrainExists); + surfacePoint.m_position.Set(inPositions[i].GetX(), inPositions[i].GetY(), heights[i]); + surfacePoint.m_normal = AZStd::move(normals[i]); + surfacePoint.m_surfaceTags = AZStd::move(outSurfaceWeightsList[i]); + perPositionCallback(surfacePoint, terrainExists[i]); } } @@ -723,20 +997,23 @@ void TerrainSystem::ProcessHeightsFromRegion( return; } - const size_t numSamplesX = aznumeric_cast(ceil(inRegion.GetExtents().GetX() / stepSize.GetX())); - const size_t numSamplesY = aznumeric_cast(ceil(inRegion.GetExtents().GetY() / stepSize.GetY())); + const auto [numSamplesX, numSamplesY] = GetNumSamplesFromRegion(inRegion, stepSize); + + AZStd::vector inPositions = GenerateInputPositionsFromRegion(inRegion, stepSize); + + AZStd::vector terrainExists(inPositions.size()); + AZStd::vector heights(inPositions.size()); + + GetHeightsSynchronous(inPositions, sampleFilter, heights, terrainExists); AzFramework::SurfaceData::SurfacePoint surfacePoint; - for (size_t y = 0; y < numSamplesY; y++) + for (size_t y = 0, i = 0; y < numSamplesY; y++) { - float fy = aznumeric_cast(inRegion.GetMin().GetY() + (y * stepSize.GetY())); for (size_t x = 0; x < numSamplesX; x++) { - bool terrainExists = false; - float fx = aznumeric_cast(inRegion.GetMin().GetX() + (x * stepSize.GetX())); - surfacePoint.m_position.Set(fx, fy, 0.0f); - surfacePoint.m_position.SetZ(GetHeight(surfacePoint.m_position, sampleFilter, &terrainExists)); - perPositionCallback(x, y, surfacePoint, terrainExists); + surfacePoint.m_position.Set(inPositions[i].GetX(), inPositions[i].GetY(), heights[i]); + perPositionCallback(x, y, surfacePoint, terrainExists[i]); + i++; } } } @@ -753,20 +1030,24 @@ void TerrainSystem::ProcessNormalsFromRegion( return; } - const size_t numSamplesX = aznumeric_cast(ceil(inRegion.GetExtents().GetX() / stepSize.GetX())); - const size_t numSamplesY = aznumeric_cast(ceil(inRegion.GetExtents().GetY() / stepSize.GetY())); + const auto [numSamplesX, numSamplesY] = GetNumSamplesFromRegion(inRegion, stepSize); + + AZStd::vector inPositions = GenerateInputPositionsFromRegion(inRegion, stepSize); + + AZStd::vector terrainExists(inPositions.size()); + AZStd::vector normals(inPositions.size()); + + GetNormalsSynchronous(inPositions, sampleFilter, normals, terrainExists); AzFramework::SurfaceData::SurfacePoint surfacePoint; - for (size_t y = 0; y < numSamplesY; y++) + for (size_t y = 0, i = 0; y < numSamplesY; y++) { - float fy = aznumeric_cast(inRegion.GetMin().GetY() + (y * stepSize.GetY())); for (size_t x = 0; x < numSamplesX; x++) { - bool terrainExists = false; - float fx = aznumeric_cast(inRegion.GetMin().GetX() + (x * stepSize.GetX())); - surfacePoint.m_position.Set(fx, fy, 0.0f); - surfacePoint.m_normal = GetNormal(surfacePoint.m_position, sampleFilter, &terrainExists); - perPositionCallback(x, y, surfacePoint, terrainExists); + surfacePoint.m_position.Set(inPositions[i].GetX(), inPositions[i].GetY(), 0.0f); + surfacePoint.m_normal = AZStd::move(normals[i]); + perPositionCallback(x, y, surfacePoint, terrainExists[i]); + i++; } } } @@ -783,20 +1064,24 @@ void TerrainSystem::ProcessSurfaceWeightsFromRegion( return; } - const size_t numSamplesX = aznumeric_cast(ceil(inRegion.GetExtents().GetX() / stepSize.GetX())); - const size_t numSamplesY = aznumeric_cast(ceil(inRegion.GetExtents().GetY() / stepSize.GetY())); + const auto [numSamplesX, numSamplesY] = GetNumSamplesFromRegion(inRegion, stepSize); + + AZStd::vector inPositions = GenerateInputPositionsFromRegion(inRegion, stepSize); + + AZStd::vector outSurfaceWeightsList(inPositions.size()); + AZStd::vector terrainExists(inPositions.size()); + + GetOrderedSurfaceWeightsFromList(inPositions, sampleFilter, outSurfaceWeightsList, terrainExists); AzFramework::SurfaceData::SurfacePoint surfacePoint; - for (size_t y = 0; y < numSamplesY; y++) + for (size_t y = 0, i = 0; y < numSamplesY; y++) { - float fy = aznumeric_cast(inRegion.GetMin().GetY() + (y * stepSize.GetY())); for (size_t x = 0; x < numSamplesX; x++) { - bool terrainExists = false; - float fx = aznumeric_cast(inRegion.GetMin().GetX() + (x * stepSize.GetX())); - surfacePoint.m_position.Set(fx, fy, 0.0f); - GetSurfaceWeights(surfacePoint.m_position, surfacePoint.m_surfaceTags, sampleFilter, &terrainExists); - perPositionCallback(x, y, surfacePoint, terrainExists); + surfacePoint.m_position.Set(inPositions[i].GetX(), inPositions[i].GetY(), 0.0f); + surfacePoint.m_surfaceTags = AZStd::move(outSurfaceWeightsList[i]); + perPositionCallback(x, y, surfacePoint, terrainExists[i]); + i++; } } } @@ -813,20 +1098,33 @@ void TerrainSystem::ProcessSurfacePointsFromRegion( return; } - const size_t numSamplesX = aznumeric_cast(ceil(inRegion.GetExtents().GetX() / stepSize.GetX())); - const size_t numSamplesY = aznumeric_cast(ceil(inRegion.GetExtents().GetY() / stepSize.GetY())); + const auto [numSamplesX, numSamplesY] = GetNumSamplesFromRegion(inRegion, stepSize); + + AZStd::vector inPositions = GenerateInputPositionsFromRegion(inRegion, stepSize); + + AZStd::vector heights(inPositions.size()); + AZStd::vector normals(inPositions.size()); + AZStd::vector outSurfaceWeightsList(inPositions.size()); + AZStd::vector terrainExists(inPositions.size()); + + GetHeightsSynchronous(inPositions, sampleFilter, heights, terrainExists); + GetNormalsSynchronous(inPositions, sampleFilter, normals, terrainExists); + + // We can skip the unnecessary call to GetHeights since we already + // got the terrain exists flags in the earlier call to GetHeights + AZStd::vector terrainExistsEmpty; + GetOrderedSurfaceWeightsFromList(inPositions, sampleFilter, outSurfaceWeightsList, terrainExistsEmpty); AzFramework::SurfaceData::SurfacePoint surfacePoint; - for (size_t y = 0; y < numSamplesY; y++) + for (size_t y = 0, i = 0; y < numSamplesY; y++) { - float fy = aznumeric_cast(inRegion.GetMin().GetY() + (y * stepSize.GetY())); for (size_t x = 0; x < numSamplesX; x++) { - bool terrainExists = false; - float fx = aznumeric_cast(inRegion.GetMin().GetX() + (x * stepSize.GetX())); - surfacePoint.m_position.Set(fx, fy, 0.0f); - GetSurfacePoint(surfacePoint.m_position, surfacePoint, sampleFilter, &terrainExists); - perPositionCallback(x, y, surfacePoint, terrainExists); + surfacePoint.m_position.Set(inPositions[i].GetX(), inPositions[i].GetY(), heights[i]); + surfacePoint.m_normal = AZStd::move(normals[i]); + surfacePoint.m_surfaceTags = AZStd::move(outSurfaceWeightsList[i]); + perPositionCallback(x, y, surfacePoint, terrainExists[i]); + i++; } } } diff --git a/Gems/Terrain/Code/Source/TerrainSystem/TerrainSystem.h b/Gems/Terrain/Code/Source/TerrainSystem/TerrainSystem.h index e0457b80af..7895ab96cc 100644 --- a/Gems/Terrain/Code/Source/TerrainSystem/TerrainSystem.h +++ b/Gems/Terrain/Code/Source/TerrainSystem/TerrainSystem.h @@ -207,6 +207,38 @@ namespace Terrain float GetTerrainAreaHeight(float x, float y, bool& terrainExists) const; AZ::Vector3 GetNormalSynchronous(float x, float y, Sampler sampler, bool* terrainExistsPtr) const; + typedef AZStd::function inPositions, + AZStd::span outPositions, + AZStd::span outTerrainExists, + AZStd::span outSurfaceWeights, + AZ::EntityId areaId)> BulkQueriesCallback; + + void GetHeightsSynchronous( + const AZStd::span& inPositions, + Sampler sampler, AZStd::span heights, + AZStd::span terrainExists) const; + void GetNormalsSynchronous( + const AZStd::span& inPositions, + Sampler sampler, AZStd::span normals, + AZStd::span terrainExists) const; + void GetOrderedSurfaceWeightsFromList( + const AZStd::span& inPositions, Sampler sampler, + AZStd::span outSurfaceWeightsList, + AZStd::span terrainExists) const; + void MakeBulkQueries( + const AZStd::span inPositions, + AZStd::span outPositions, + AZStd::span outTerrainExists, + AZStd::span outSurfaceWieghts, + BulkQueriesCallback queryCallback) const; + void GenerateQueryPositions(const AZStd::span& inPositions, + AZStd::vector& outPositions, + Sampler sampler) const; + AZStd::vector GenerateInputPositionsFromRegion( + const AZ::Aabb& inRegion, + const AZ::Vector2& stepSize) const; + // AZ::TickBus::Handler overrides ... void OnTick(float deltaTime, AZ::ScriptTimePoint time) override; diff --git a/Gems/Terrain/Code/Tests/TerrainSystemTest.cpp b/Gems/Terrain/Code/Tests/TerrainSystemTest.cpp index 2c01a3d455..1277e9ed20 100644 --- a/Gems/Terrain/Code/Tests/TerrainSystemTest.cpp +++ b/Gems/Terrain/Code/Tests/TerrainSystemTest.cpp @@ -158,6 +158,15 @@ namespace UnitTest // Let the test function modify these values based on the needs of the specific test. mockHeights(outPosition, terrainExists); }); + ON_CALL(*m_terrainAreaHeightRequests, GetHeights) + .WillByDefault( + [mockHeights](AZStd::span inOutPositionList, AZStd::span terrainExistsList) + { + for (int i = 0; i < inOutPositionList.size(); i++) + { + mockHeights(inOutPositionList[i], terrainExistsList[i]); + } + }); ActivateEntity(entity.get()); return entity; @@ -184,9 +193,9 @@ namespace UnitTest tagWeight3.m_weight = 0.3f; expectedTags.push_back(tagWeight3); - m_terrainAreaSurfaceRequests = AZStd::make_unique>(entity->GetId()); - ON_CALL(*m_terrainAreaSurfaceRequests, GetSurfaceWeights).WillByDefault( - [tagWeight1, tagWeight2, tagWeight3](const AZ::Vector3& position, AzFramework::SurfaceData::SurfaceTagWeightList& surfaceWeights) + auto mockGetSurfaceWeights = [tagWeight1, tagWeight2, tagWeight3]( + const AZ::Vector3& position, + AzFramework::SurfaceData::SurfaceTagWeightList& surfaceWeights) { surfaceWeights.clear(); float absYPos = fabsf(position.GetY()); @@ -202,6 +211,19 @@ namespace UnitTest { surfaceWeights.push_back(tagWeight3); } + }; + + m_terrainAreaSurfaceRequests = AZStd::make_unique>(entity->GetId()); + ON_CALL(*m_terrainAreaSurfaceRequests, GetSurfaceWeights).WillByDefault(mockGetSurfaceWeights); + ON_CALL(*m_terrainAreaSurfaceRequests, GetSurfaceWeightsFromList).WillByDefault( + [mockGetSurfaceWeights]( + AZStd::span inPositionList, + AZStd::span outSurfaceWeightsList) + { + for (size_t i = 0; i < inPositionList.size(); i++) + { + mockGetSurfaceWeights(inPositionList[i], outSurfaceWeightsList[i]); + } } ); } diff --git a/Gems/Terrain/Code/terrain_editor_shared_files.cmake b/Gems/Terrain/Code/terrain_editor_shared_files.cmake index 09724751a9..ed64b7c4fc 100644 --- a/Gems/Terrain/Code/terrain_editor_shared_files.cmake +++ b/Gems/Terrain/Code/terrain_editor_shared_files.cmake @@ -25,6 +25,8 @@ set(FILES Source/EditorComponents/EditorTerrainSystemComponent.h Source/EditorTerrainModule.cpp Source/EditorTerrainModule.h + Source/EditorSurfaceTagListProvider.h + Source/EditorSurfaceTagListProvider.cpp Source/TerrainModule.cpp Source/TerrainModule.h Source/TerrainRenderer/EditorComponents/EditorTerrainSurfaceMaterialsListComponent.cpp diff --git a/Tools/LyTestTools/ly_test_tools/_internal/managers/abstract_resource_locator.py b/Tools/LyTestTools/ly_test_tools/_internal/managers/abstract_resource_locator.py index 4514e5d812..6e0923b916 100755 --- a/Tools/LyTestTools/ly_test_tools/_internal/managers/abstract_resource_locator.py +++ b/Tools/LyTestTools/ly_test_tools/_internal/managers/abstract_resource_locator.py @@ -175,13 +175,20 @@ class AbstractResourceLocator(object): return os.path.join(self.build_directory(), 'AssetProcessor') def asset_processor_batch(self): - """" + """ Return path for the AssetProcessorBatch compatible with this build platform and configuration ex. engine_root/dev/mac/bin/profile/AssetProcessorBatch :return: path to AssetProcessorBatch """ return os.path.join(self.build_directory(), 'AssetProcessorBatch') + def ap_job_logs(self): + """ + Return path to the Asset Processor JobLogs directory. + :return: path to /user/log/JobLogs + """ + return os.path.join(self.project_log(), 'JobLogs') + def editor(self): """ Return path to the editor executable compatible with the current build. diff --git a/Tools/LyTestTools/ly_test_tools/o3de/editor_test.py b/Tools/LyTestTools/ly_test_tools/o3de/editor_test.py index f363689066..3c4e2b49a4 100644 --- a/Tools/LyTestTools/ly_test_tools/o3de/editor_test.py +++ b/Tools/LyTestTools/ly_test_tools/o3de/editor_test.py @@ -851,11 +851,11 @@ class EditorTestSuite(): workspace.artifact_manager.save_artifact(os.path.join(editor_utils.retrieve_log_path(run_id, workspace), log_name), f'({run_id}){log_name}') if return_code == 0: - # No need to scrap the output, as all the tests have passed + # No need to scrape the output, as all the tests have passed for test_spec in test_spec_list: results[test_spec.__name__] = Result.Pass.create(test_spec, output, editor_log_content) else: - # Scrap the output to attempt to find out which tests failed. + # Scrape the output to attempt to find out which tests failed. # This function should always populate the result list, if it didn't find it, it will have "Unknown" type of result results = self._get_results_using_output(test_spec_list, output, editor_log_content) assert len(results) == len(test_spec_list), "bug in _get_results_using_output(), the number of results don't match the tests ran" @@ -940,6 +940,9 @@ class EditorTestSuite(): editor_test_data.results.update(results) test_name, test_result = next(iter(results.items())) self._report_result(test_name, test_result) + # If test did not pass, save assets with errors and warnings + if not isinstance(test_result, Result.Pass): + editor_utils.save_failed_asset_joblogs(workspace) def _run_batched_tests(self, request: Request, workspace: AbstractWorkspace, editor: Editor, editor_test_data: TestData, test_spec_list: list[EditorSharedTest], extra_cmdline_args: list[str] = []) -> None: @@ -961,6 +964,11 @@ class EditorTestSuite(): extra_cmdline_args) assert results is not None editor_test_data.results.update(results) + # If at least one test did not pass, save assets with errors and warnings + for result in results: + if not isinstance(result, Result.Pass): + editor_utils.save_failed_asset_joblogs(workspace) + return def _run_parallel_tests(self, request: Request, workspace: AbstractWorkspace, editor: Editor, editor_test_data: TestData, test_spec_list: list[EditorSharedTest], extra_cmdline_args: list[str] = []) -> None: @@ -1007,8 +1015,14 @@ class EditorTestSuite(): for t in threads: t.join() + save_asset_logs = False for result in results_per_thread: editor_test_data.results.update(result) + if not isinstance(result, Result.Pass): + save_asset_logs = True + # If at least one test did not pass, save assets with errors and warnings + if save_asset_logs: + editor_utils.save_failed_asset_joblogs(workspace) def _run_parallel_batched_tests(self, request: Request, workspace: AbstractWorkspace, editor: Editor, editor_test_data: TestData, test_spec_list: list[EditorSharedTest], extra_cmdline_args: list[str] = []) -> None: @@ -1056,8 +1070,14 @@ class EditorTestSuite(): for t in threads: t.join() + save_asset_logs = False for result in results_per_thread: editor_test_data.results.update(result) + if not isinstance(result, Result.Pass): + save_asset_logs = True + # If at least one test did not pass, save assets with errors and warnings + if save_asset_logs: + editor_utils.save_failed_asset_joblogs(workspace) def _get_number_parallel_editors(self, request: Request) -> int: """ diff --git a/Tools/LyTestTools/ly_test_tools/o3de/editor_test_utils.py b/Tools/LyTestTools/ly_test_tools/o3de/editor_test_utils.py index 7b36a82c07..4e678151bd 100644 --- a/Tools/LyTestTools/ly_test_tools/o3de/editor_test_utils.py +++ b/Tools/LyTestTools/ly_test_tools/o3de/editor_test_utils.py @@ -10,6 +10,7 @@ from __future__ import annotations import os import time import logging +import re import ly_test_tools.environment.process_utils as process_utils import ly_test_tools.environment.waiter as waiter @@ -151,3 +152,41 @@ def retrieve_last_run_test_index_from_output(test_spec_list: list[EditorTestBase else: index += 1 return index + +def save_failed_asset_joblogs(workspace: AbstractWorkspace) -> None: + """ + Checks all asset logs in the JobLogs directory to see if the asset has any warnings or errors. If so, the asset is + saved via ArtifactManager. + + :param workspace: The AbstractWorkspace to access the JobLogs path + :return: None + """ + for walk_tuple in os.walk(workspace.paths.ap_job_logs()): + for log_file in walk_tuple[2]: + full_log_path = os.path.join(walk_tuple[0], log_file) + # Only save asset logs that contain errors or warnings + if _check_log_errors_warnings(full_log_path): + try: + workspace.artifact_manager.save_artifact(full_log_path) + except Exception as e: # Purposefully broad + logger.warning(f"Error when saving log at path:{full_log_path}\n{e}") + +def _check_log_errors_warnings(log_path: str) -> bool: + """ + Checks to see if the asset log contains any errors or warnings. Also returns True is no regex is found because + something probably went wrong. + Example log lines: ~~1643759303647~~1~~00000000000009E0~~AssetBuilder~~S: 0 errors, 1 warnings + + :param log_path: The full path to the asset log file to read + :return: True if the regex finds an error or warning, else False + """ + log_regex = "(\\d+) errors, (\\d+) warnings" + with open(log_path, 'r') as opened_asset_log: + for log_line in opened_asset_log: + regex_match = re.search(log_regex, log_line) + if regex_match is not None: + break + # If we match any non zero numbers in: n error, n warnings + if regex_match is None or (int)(regex_match.group(1)) != 0 or (int)(regex_match.group(2)) != 0: + return True + return False diff --git a/Tools/LyTestTools/tests/unit/test_editor_test_utils.py b/Tools/LyTestTools/tests/unit/test_editor_test_utils.py index f81d2fd8e6..cca9a161c8 100644 --- a/Tools/LyTestTools/tests/unit/test_editor_test_utils.py +++ b/Tools/LyTestTools/tests/unit/test_editor_test_utils.py @@ -170,3 +170,59 @@ class TestEditorTestUtils(unittest.TestCase): mock_test_list.append(mock_test) assert 0 == editor_test_utils.retrieve_last_run_test_index_from_output(mock_test_list, mock_editor_output) + + @mock.patch('ly_test_tools.o3de.editor_test_utils._check_log_errors_warnings') + @mock.patch('os.walk') + def test_SaveFailedAssetJoblogs_ManyValidLogs_SavesCorrectly(self, mock_walk, mock_check_log): + mock_workspace = mock.MagicMock() + mock_walk.return_value = [['MockDirectory', None, ['mock_log.log']], + ['MockDirectory2', None, ['mock_log2.log']]] + mock_check_log.return_value = True + + editor_test_utils.save_failed_asset_joblogs(mock_workspace) + + assert mock_workspace.artifact_manager.save_artifact.call_count == 2 + + @mock.patch('ly_test_tools.o3de.editor_test_utils._check_log_errors_warnings') + @mock.patch('os.walk') + def test_SaveFailedAssetJoblogs_ManyInvalidLogs_NoSaves(self, mock_walk, mock_check_log): + mock_workspace = mock.MagicMock() + mock_walk.return_value = [['MockDirectory', None, ['mock_log.log']], + ['MockDirectory2', None, ['mock_log2.log']]] + mock_check_log.return_value = False + + editor_test_utils.save_failed_asset_joblogs(mock_workspace) + + assert mock_workspace.artifact_manager.save_artifact.call_count == 0 + + def test_CheckLogErrorWarnings_ValidLine_ReturnsTrue(self): + mock_log = '~~1643759303647~~1~~00000000000009E0~~AssetBuilder~~S: 0 errors, 1 warnings' + mock_log_path = mock.MagicMock() + + with mock.patch('builtins.open', mock.mock_open(read_data=mock_log)) as mock_file: + expected = editor_test_utils._check_log_errors_warnings(mock_log_path) + assert expected + + def test_CheckLogErrorWarnings_MultipleValidLine_ReturnsTrue(self): + mock_log = 'foo\nfoo\n~~1643759303647~~1~~00000000000009E0~~AssetBuilder~~S: 1 errors, 1 warnings' + mock_log_path = mock.MagicMock() + + with mock.patch('builtins.open', mock.mock_open(read_data=mock_log)) as mock_file: + expected = editor_test_utils._check_log_errors_warnings(mock_log_path) + assert expected + + def test_CheckLogErrorWarnings_InvalidLine_ReturnsFalse(self): + mock_log = 'foo\n~~1643759303647~~1~~00000000000009E0~~AssetBuilder~~S: 0 errors, 0 warnings' + mock_log_path = mock.MagicMock() + + with mock.patch('builtins.open', mock.mock_open(read_data=mock_log)) as mock_file: + expected = editor_test_utils._check_log_errors_warnings(mock_log_path) + assert not expected + + def test_CheckLogErrorWarnings_InvalidRegex_ReturnsTrue(self): + mock_log = 'Invalid last line' + mock_log_path = mock.MagicMock() + + with mock.patch('builtins.open', mock.mock_open(read_data=mock_log)) as mock_file: + expected = editor_test_utils._check_log_errors_warnings(mock_log_path) + assert expected diff --git a/Tools/LyTestTools/tests/unit/test_o3de_editor_test.py b/Tools/LyTestTools/tests/unit/test_o3de_editor_test.py index 973366aa94..f63ec315eb 100644 --- a/Tools/LyTestTools/tests/unit/test_o3de_editor_test.py +++ b/Tools/LyTestTools/tests/unit/test_o3de_editor_test.py @@ -871,6 +871,7 @@ class TestRunningTests(unittest.TestCase): @mock.patch('ly_test_tools.o3de.editor_test.EditorTestSuite._report_result') @mock.patch('ly_test_tools.o3de.editor_test.EditorTestSuite._exec_editor_test') @mock.patch('ly_test_tools.o3de.editor_test.EditorTestSuite._setup_editor_test') + @mock.patch('ly_test_tools.o3de.editor_test_utils.save_failed_asset_joblogs', mock.MagicMock()) def test_RunSingleTest_ValidTest_ReportsResults(self, mock_setup_test, mock_exec_editor_test, mock_report_result): mock_test_suite = ly_test_tools.o3de.editor_test.EditorTestSuite() mock_test_data = mock.MagicMock() @@ -903,6 +904,7 @@ class TestRunningTests(unittest.TestCase): @mock.patch('threading.Thread') @mock.patch('ly_test_tools.o3de.editor_test.EditorTestSuite._get_number_parallel_editors') @mock.patch('ly_test_tools.o3de.editor_test.EditorTestSuite._setup_editor_test') + @mock.patch('ly_test_tools.o3de.editor_test_utils.save_failed_asset_joblogs', mock.MagicMock()) def test_RunParallelTests_TwoTestsAndEditors_TwoThreads(self, mock_setup_test, mock_get_num_editors, mock_thread): mock_test_suite = ly_test_tools.o3de.editor_test.EditorTestSuite() mock_get_num_editors.return_value = 2 @@ -919,6 +921,7 @@ class TestRunningTests(unittest.TestCase): @mock.patch('threading.Thread') @mock.patch('ly_test_tools.o3de.editor_test.EditorTestSuite._get_number_parallel_editors') @mock.patch('ly_test_tools.o3de.editor_test.EditorTestSuite._setup_editor_test') + @mock.patch('ly_test_tools.o3de.editor_test_utils.save_failed_asset_joblogs', mock.MagicMock()) def test_RunParallelTests_TenTestsAndTwoEditors_TenThreads(self, mock_setup_test, mock_get_num_editors, mock_thread): mock_test_suite = ly_test_tools.o3de.editor_test.EditorTestSuite() mock_get_num_editors.return_value = 2 @@ -937,6 +940,7 @@ class TestRunningTests(unittest.TestCase): @mock.patch('threading.Thread') @mock.patch('ly_test_tools.o3de.editor_test.EditorTestSuite._get_number_parallel_editors') @mock.patch('ly_test_tools.o3de.editor_test.EditorTestSuite._setup_editor_test') + @mock.patch('ly_test_tools.o3de.editor_test_utils.save_failed_asset_joblogs', mock.MagicMock()) def test_RunParallelTests_TenTestsAndThreeEditors_TenThreads(self, mock_setup_test, mock_get_num_editors, mock_thread): mock_test_suite = ly_test_tools.o3de.editor_test.EditorTestSuite() @@ -956,6 +960,7 @@ class TestRunningTests(unittest.TestCase): @mock.patch('threading.Thread') @mock.patch('ly_test_tools.o3de.editor_test.EditorTestSuite._get_number_parallel_editors') @mock.patch('ly_test_tools.o3de.editor_test.EditorTestSuite._setup_editor_test') + @mock.patch('ly_test_tools.o3de.editor_test_utils.save_failed_asset_joblogs', mock.MagicMock()) def test_RunParallelBatchedTests_TwoTestsAndEditors_TwoThreads(self, mock_setup_test, mock_get_num_editors, mock_thread): mock_test_suite = ly_test_tools.o3de.editor_test.EditorTestSuite() @@ -973,6 +978,7 @@ class TestRunningTests(unittest.TestCase): @mock.patch('threading.Thread') @mock.patch('ly_test_tools.o3de.editor_test.EditorTestSuite._get_number_parallel_editors') @mock.patch('ly_test_tools.o3de.editor_test.EditorTestSuite._setup_editor_test') + @mock.patch('ly_test_tools.o3de.editor_test_utils.save_failed_asset_joblogs', mock.MagicMock()) def test_RunParallelBatchedTests_TenTestsAndTwoEditors_2Threads(self, mock_setup_test, mock_get_num_editors, mock_thread): mock_test_suite = ly_test_tools.o3de.editor_test.EditorTestSuite() @@ -992,6 +998,7 @@ class TestRunningTests(unittest.TestCase): @mock.patch('threading.Thread') @mock.patch('ly_test_tools.o3de.editor_test.EditorTestSuite._get_number_parallel_editors') @mock.patch('ly_test_tools.o3de.editor_test.EditorTestSuite._setup_editor_test') + @mock.patch('ly_test_tools.o3de.editor_test_utils.save_failed_asset_joblogs', mock.MagicMock()) def test_RunParallelBatchedTests_TenTestsAndThreeEditors_ThreeThreads(self, mock_setup_test, mock_get_num_editors, mock_thread): mock_test_suite = ly_test_tools.o3de.editor_test.EditorTestSuite() diff --git a/cmake/3rdParty/Platform/Mac/BuiltInPackages_mac.cmake b/cmake/3rdParty/Platform/Mac/BuiltInPackages_mac.cmake index ae51076857..c0bc9060d4 100644 --- a/cmake/3rdParty/Platform/Mac/BuiltInPackages_mac.cmake +++ b/cmake/3rdParty/Platform/Mac/BuiltInPackages_mac.cmake @@ -42,5 +42,5 @@ ly_associate_package(PACKAGE_NAME astc-encoder-3.2-rev5-mac ly_associate_package(PACKAGE_NAME ISPCTexComp-36b80aa-rev1-mac TARGETS ISPCTexComp PACKAGE_HASH 8a4e93277b8face6ea2fd57c6d017bdb55643ed3d6387110bc5f6b3b884dd169) ly_associate_package(PACKAGE_NAME lz4-1.9.3-vcpkg-rev4-mac TARGETS lz4 PACKAGE_HASH 891ff630bf34f7ab1d8eaee2ea0a8f1fca89dbdc63fca41ee592703dd488a73b) ly_associate_package(PACKAGE_NAME azslc-1.7.35-rev1-mac TARGETS azslc PACKAGE_HASH 03cb1ea8c47d4c80c893e2e88767272d5d377838f5ba94b777a45902dd85052e) -ly_associate_package(PACKAGE_NAME SQLite-3.37.2-rev1-mac TARGETS SQLite PACKAGE_HASH f9101023f99cf32fc5867284ceb28c0761c23d2c5a4b1748349c69f976a2fbea) +ly_associate_package(PACKAGE_NAME SQLite-3.37.2-rev2-mac TARGETS SQLite PACKAGE_HASH b7d9abdb68045003e030e1a9a805db1aefa5e8fde6dccfbb4fab3a06249a41fc) ly_associate_package(PACKAGE_NAME AwsIotDeviceSdkCpp-1.15.2-rev2-mac TARGETS AwsIotDeviceSdkCpp PACKAGE_HASH 4854edb7b88fa6437b4e69e87d0ee111a25313ac2a2db5bb2f8b674ba0974f95) diff --git a/python/requirements.txt b/python/requirements.txt index 9648a20ba0..c000be48de 100644 --- a/python/requirements.txt +++ b/python/requirements.txt @@ -6,13 +6,13 @@ attrs==20.1.0 \ --hash=sha256:0ef97238856430dcf9228e07f316aefc17e8939fc8507e18c6501b761ef1a42a \ --hash=sha256:2867b7b9f8326499ab5b0e2d12801fa5c98842d2cbd22b35112ae04bf85b4dff # via -r .\requirements.txt -boto3==1.12.21 \ - --hash=sha256:50105a25e301e20b361b2b8fafee196a425a4758e51f400a0f381d42e4bd909e \ - --hash=sha256:5fe70e656f92e4e649dc4cf05786f57d180e5d491bcb22c80411512ec2b27c15 \ +boto3==1.20.44 \ + --hash=sha256:26f18ca7411615f33d8d1bf60cc8efe5b331a57b3013d5f8f3587cd5350c27cb \ + --hash=sha256:4470f64e4af609ff678055338c96a6f7cbe601d1fb06a4ea7dc8d9223c2e527a \ # via -r .\requirements.txt -botocore==1.15.21 \ - --hash=sha256:4aaf6c94bcaace260138d32eae144be1b5d2ddce9ef0f395da32c68e106ff20f \ - --hash=sha256:86f7f1c489887f9e3c2ede598e2a30f8bd259c11e8ebe25e897e40231b3f4bc8 \ +botocore==1.23.44 \ + --hash=sha256:11483a493de4a76ef218d8cd3980c63550d006a0d082c10d53c0954184ca542a \ + --hash=sha256:8e5317f84fc1118bff58fa6fa79a9b62083e75a2a9c62feb3ea73694c550b99d \ # via -r .\requirements.txt, boto3, s3transfer certifi==2019.11.28 \ --hash=sha256:017c25db2a153ce562900032d5bc68e9f191e44e9a0f762f373977de9df1fbb3 \ @@ -22,6 +22,11 @@ chardet==3.0.4 \ --hash=sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae \ --hash=sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691 \ # via -r .\requirements.txt, requests +charset-normalizer==2.0.10 \ + --hash=sha256:876d180e9d7432c5d1dfd4c5d26b72f099d503e8fcc0feb7532c9289be60fcbd \ + --hash=sha256:cb957888737fc0bbcd78e3df769addb41fd1ff8cf950dc9e7ad7793f1bf44455 \ + # via + # -r requirements.txt, requests colorama==0.4.3 \ --hash=sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff \ --hash=sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1 \ @@ -129,30 +134,40 @@ packaging==20.4 \ --hash=sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8 \ --hash=sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181 \ # pytest -pillow==7.0.0 \ - --hash=sha256:0a628977ac2e01ca96aaae247ec2bd38e729631ddf2221b4b715446fd45505be \ - --hash=sha256:4d9ed9a64095e031435af120d3c910148067087541131e82b3e8db302f4c8946 \ - --hash=sha256:54ebae163e8412aff0b9df1e88adab65788f5f5b58e625dc5c7f51eaf14a6837 \ - --hash=sha256:5bfef0b1cdde9f33881c913af14e43db69815c7e8df429ceda4c70a5e529210f \ - --hash=sha256:5f3546ceb08089cedb9e8ff7e3f6a7042bb5b37c2a95d392fb027c3e53a2da00 \ - --hash=sha256:5f7ae9126d16194f114435ebb79cc536b5682002a4fa57fa7bb2cbcde65f2f4d \ - --hash=sha256:62a889aeb0a79e50ecf5af272e9e3c164148f4bd9636cc6bcfa182a52c8b0533 \ - --hash=sha256:7406f5a9b2fd966e79e6abdaf700585a4522e98d6559ce37fc52e5c955fade0a \ - --hash=sha256:8453f914f4e5a3d828281a6628cf517832abfa13ff50679a4848926dac7c0358 \ - --hash=sha256:87269cc6ce1e3dee11f23fa515e4249ae678dbbe2704598a51cee76c52e19cda \ - --hash=sha256:875358310ed7abd5320f21dd97351d62de4929b0426cdb1eaa904b64ac36b435 \ - --hash=sha256:8ac6ce7ff3892e5deaab7abaec763538ffd011f74dc1801d93d3c5fc541feee2 \ - --hash=sha256:91b710e3353aea6fc758cdb7136d9bbdcb26b53cefe43e2cba953ac3ee1d3313 \ - --hash=sha256:9d2ba4ed13af381233e2d810ff3bab84ef9f18430a9b336ab69eaf3cd24299ff \ - --hash=sha256:a62ec5e13e227399be73303ff301f2865bf68657d15ea50b038d25fc41097317 \ - --hash=sha256:ab76e5580b0ed647a8d8d2d2daee170e8e9f8aad225ede314f684e297e3643c2 \ - --hash=sha256:bf4003aa538af3f4205c5fac56eacaa67a6dd81e454ffd9e9f055fff9f1bc614 \ - --hash=sha256:bf598d2e37cf8edb1a2f26ed3fb255191f5232badea4003c16301cb94ac5bdd0 \ - --hash=sha256:c18f70dc27cc5d236f10e7834236aff60aadc71346a5bc1f4f83a4b3abee6386 \ - --hash=sha256:c5ed816632204a2fc9486d784d8e0d0ae754347aba99c811458d69fcdfd2a2f9 \ - --hash=sha256:dc058b7833184970d1248135b8b0ab702e6daa833be14035179f2acb78ff5636 \ - --hash=sha256:ff3797f2f16bf9d17d53257612da84dd0758db33935777149b3334c01ff68865 \ - # via requirements.txt, imageio +pillow==9.0.0 \ + --hash=sha256:03b27b197deb4ee400ed57d8d4e572d2d8d80f825b6634daf6e2c18c3c6ccfa6 \ + --hash=sha256:0b281fcadbb688607ea6ece7649c5d59d4bbd574e90db6cd030e9e85bde9fecc \ + --hash=sha256:0ebd8b9137630a7bbbff8c4b31e774ff05bbb90f7911d93ea2c9371e41039b52 \ + --hash=sha256:113723312215b25c22df1fdf0e2da7a3b9c357a7d24a93ebbe80bfda4f37a8d4 \ + --hash=sha256:2d16b6196fb7a54aff6b5e3ecd00f7c0bab1b56eee39214b2b223a9d938c50af \ + --hash=sha256:2fd8053e1f8ff1844419842fd474fc359676b2e2a2b66b11cc59f4fa0a301315 \ + --hash=sha256:31b265496e603985fad54d52d11970383e317d11e18e856971bdbb86af7242a4 \ + --hash=sha256:3586e12d874ce2f1bc875a3ffba98732ebb12e18fb6d97be482bd62b56803281 \ + --hash=sha256:47f5cf60bcb9fbc46011f75c9b45a8b5ad077ca352a78185bd3e7f1d294b98bb \ + --hash=sha256:490e52e99224858f154975db61c060686df8a6b3f0212a678e5d2e2ce24675c9 \ + --hash=sha256:500d397ddf4bbf2ca42e198399ac13e7841956c72645513e8ddf243b31ad2128 \ + --hash=sha256:52abae4c96b5da630a8b4247de5428f593465291e5b239f3f843a911a3cf0105 \ + --hash=sha256:6579f9ba84a3d4f1807c4aab4be06f373017fc65fff43498885ac50a9b47a553 \ + --hash=sha256:68e06f8b2248f6dc8b899c3e7ecf02c9f413aab622f4d6190df53a78b93d97a5 \ + --hash=sha256:6c5439bfb35a89cac50e81c751317faea647b9a3ec11c039900cd6915831064d \ + --hash=sha256:72c3110228944019e5f27232296c5923398496b28be42535e3b2dc7297b6e8b6 \ + --hash=sha256:72f649d93d4cc4d8cf79c91ebc25137c358718ad75f99e99e043325ea7d56100 \ + --hash=sha256:7aaf07085c756f6cb1c692ee0d5a86c531703b6e8c9cae581b31b562c16b98ce \ + --hash=sha256:80fe92813d208ce8aa7d76da878bdc84b90809f79ccbad2a288e9bcbeac1d9bd \ + --hash=sha256:95545137fc56ce8c10de646074d242001a112a92de169986abd8c88c27566a05 \ + --hash=sha256:97b6d21771da41497b81652d44191489296555b761684f82b7b544c49989110f \ + --hash=sha256:98cb63ca63cb61f594511c06218ab4394bf80388b3d66cd61d0b1f63ee0ea69f \ + --hash=sha256:9f3b4522148586d35e78313db4db0df4b759ddd7649ef70002b6c3767d0fdeb7 \ + --hash=sha256:a09a9d4ec2b7887f7a088bbaacfd5c07160e746e3d47ec5e8050ae3b2a229e9f \ + --hash=sha256:b5050d681bcf5c9f2570b93bee5d3ec8ae4cf23158812f91ed57f7126df91762 \ + --hash=sha256:bb47a548cea95b86494a26c89d153fd31122ed65255db5dcbc421a2d28eb3379 \ + --hash=sha256:bc462d24500ba707e9cbdef436c16e5c8cbf29908278af053008d9f689f56dee \ + --hash=sha256:c2067b3bb0781f14059b112c9da5a91c80a600a97915b4f48b37f197895dd925 \ + --hash=sha256:d154ed971a4cc04b93a6d5b47f37948d1f621f25de3e8fa0c26b2d44f24e3e8f \ + --hash=sha256:d5dcea1387331c905405b09cdbfb34611050cc52c865d71f2362f354faee1e9f \ + --hash=sha256:ee6e2963e92762923956fe5d3479b1fdc3b76c83f290aad131a2f98c3df0593e \ + --hash=sha256:fd0e5062f11cb3e730450a7d9f323f4051b532781026395c4323b8ad055523c4 \ + # via -r requirements.txt pluggy==0.13.1 \ --hash=sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0 \ --hash=sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d \ @@ -191,10 +206,10 @@ psutil==5.8.0 \ --hash=sha256:f4634b033faf0d968bb9220dd1c793b897ab7f1189956e1aa9eae752527127d3 \ --hash=sha256:fcc01e900c1d7bee2a37e5d6e4f9194760a93597c97fee89c4ae51701de03563 # via requirements.txt -py==1.9.0 \ - --hash=sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2 \ - --hash=sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342 \ - # via -r .\requirements.txt, pytest +py==1.11.0 \ + --hash=sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719 \ + --hash=sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378 \ + # via -r requirements.txt pyparsing==2.4.7 \ --hash=sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1 \ --hash=sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b \ @@ -246,13 +261,13 @@ PyYAML==5.4.1 \ --hash=sha256:e4fac90784481d221a8e4b1162afa7c47ed953be40d31ab4629ae917510051df \ --hash=sha256:fa5ae20527d8e831e8230cbffd9f8fe952815b2b7dae6ffec25318803a7528fc \ # via requirements.txt -requests==2.23.0 \ - --hash=sha256:43999036bfa82904b6af1d99e4882b560e5e2c68e5c4b0aa03b655f3d7d73fee \ - --hash=sha256:b3f43d496c6daba4493e7c431722aeb7dbc6288f52a6e04e7b6023b0247817e6 \ - # via -r .\requirements.txt -s3transfer==0.3.3 \ - --hash=sha256:2482b4259524933a022d59da830f51bd746db62f047d6eb213f2f8855dcb8a13 \ - --hash=sha256:921a37e2aefc64145e7b73d50c71bb4f26f46e4c9f414dc648c6245ff92cf7db \ +requests==2.27.1 \ + --hash=sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61 \ + --hash=sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d \ + # via -r requirements.txt +s3transfer==0.5.0 \ + --hash=sha256:50ed823e1dc5868ad40c8dc92072f757aa0e653a192845c94a3b676f4a62da4c \ + --hash=sha256:9c1dc369814391a6bda20ebbf4b70a0f34630592c9aa520856bf384916af2803 \ # boto3 scipy==1.4.1 \ --hash=sha256:00af72998a46c25bdb5824d2b729e7dabec0c765f9deb0b504f928591f5ff9d4 \ @@ -290,10 +305,10 @@ smmap==3.0.5 \ --hash=sha256:7bfcf367828031dc893530a29cb35eb8c8f2d7c8f2d0989354d75d24c8573714 \ --hash=sha256:84c2751ef3072d4f6b2785ec7ee40244c6f45eb934d9e543e2c51f1bd3d54c50 \ # via smmap2 -urllib3==1.25.8 \ - --hash=sha256:2f3db8b19923a873b3e5256dc9c2dedfa883e33d87c690d9c7913e1f40673cdc \ - --hash=sha256:87716c2d2a7121198ebcb7ce7cccf6ce5e9ba539041cfbaeecfb641dc0bf6acc \ - # via -r .\requirements.txt, botocore, requests +urllib3==1.26.8 \ + --hash=sha256:000ca7f471a233c2251c6c7023ee85305721bfdf18621ebff4fd17a8653427ed \ + --hash=sha256:0e7c33d9a63e7ddfcb86780aac87befc2fbddf46c58dbb487e0855f7ceec283c \ + # via -r requirements.txt wcwidth==0.2.5 \ --hash=sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784 \ --hash=sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83 \