diff --git a/.gitattributes b/.gitattributes index d7f9d36c50..2037909dbd 100644 --- a/.gitattributes +++ b/.gitattributes @@ -115,7 +115,6 @@ *.wav filter=lfs diff=lfs merge=lfs -text *.webm filter=lfs diff=lfs merge=lfs -text *.wem filter=lfs diff=lfs merge=lfs -text -*.wxs filter=lfs diff=lfs merge=lfs -text *.zip filter=lfs diff=lfs merge=lfs -text *.tbscene filter=lfs diff=lfs merge=lfs -text *.spp filter=lfs diff=lfs merge=lfs -text diff --git a/AutomatedTesting/Gem/PythonTests/Blast/CMakeLists.txt b/AutomatedTesting/Gem/PythonTests/Blast/CMakeLists.txt new file mode 100644 index 0000000000..61bb2e8355 --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/Blast/CMakeLists.txt @@ -0,0 +1,25 @@ +# +# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +# its licensors. +# +# For complete copyright and license terms please see the LICENSE at the root of this +# distribution (the "License"). All use of this software is governed by the License, +# or, if provided, by the license below or the license accompanying this file. Do not +# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# + +if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS) + ly_add_pytest( + NAME AutomatedTesting::BlastTests + TEST_SUITE main + TEST_SERIAL TRUE + PATH ${CMAKE_CURRENT_LIST_DIR}/TestSuite_Active.py + TIMEOUT 1500 + RUNTIME_DEPENDENCIES + Legacy::Editor + AZ::AssetProcessor + AutomatedTesting.Assets + COMPONENT Blast + ) +endif() diff --git a/AutomatedTesting/Gem/PythonTests/CMakeLists.txt b/AutomatedTesting/Gem/PythonTests/CMakeLists.txt index 6f72d45893..d6f9ecff4b 100644 --- a/AutomatedTesting/Gem/PythonTests/CMakeLists.txt +++ b/AutomatedTesting/Gem/PythonTests/CMakeLists.txt @@ -15,406 +15,46 @@ ly_get_list_relative_pal_filename(pal_dir ${CMAKE_CURRENT_LIST_DIR}/Platform/${PAL_PLATFORM_NAME}) +include(${pal_dir}/PAL_traits_${PAL_PLATFORM_NAME_LOWERCASE}.cmake) + +## Asset pipeline ## add_subdirectory(assetpipeline) + +## Atom Renderer ## add_subdirectory(atom_renderer) ## Physics ## -if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS) - ly_add_pytest( - NAME AutomatedTesting::PhysicsTests_Main - TEST_SUITE main - TEST_SERIAL - PATH ${CMAKE_CURRENT_LIST_DIR}/physics/TestSuite_Main.py - TIMEOUT 1500 - RUNTIME_DEPENDENCIES - Legacy::Editor - AZ::AssetProcessor - AutomatedTesting.Assets - COMPONENT - Physics - ) - ly_add_pytest( - NAME AutomatedTesting::PhysicsTests_Periodic - TEST_SUITE periodic - TEST_SERIAL - PATH ${CMAKE_CURRENT_LIST_DIR}/physics/TestSuite_Periodic.py - TIMEOUT 1500 - RUNTIME_DEPENDENCIES - Legacy::Editor - AZ::AssetProcessor - AutomatedTesting.Assets - COMPONENT - Physics - ) - ly_add_pytest( - NAME AutomatedTesting::PhysicsTests_Sandbox - TEST_SUITE sandbox - TEST_SERIAL - PATH ${CMAKE_CURRENT_LIST_DIR}/physics/TestSuite_Sandbox.py - TIMEOUT 1500 - RUNTIME_DEPENDENCIES - Legacy::Editor - AZ::AssetProcessor - AutomatedTesting.Assets - COMPONENT - Physics - ) -endif() +add_subdirectory(physics) ## ScriptCanvas ## -if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS) - ly_add_pytest( - NAME AutomatedTesting::ScriptCanvasTests - TEST_SUITE periodic - TEST_SERIAL - PATH ${CMAKE_CURRENT_LIST_DIR}/scripting/TestSuite_Active.py - TIMEOUT 1500 - RUNTIME_DEPENDENCIES - Legacy::Editor - AZ::AssetProcessor - AutomatedTesting.Assets - COMPONENT - ScriptCanvas - ) - ly_add_pytest( - NAME AutomatedTesting::ScriptCanvasTests_Sandbox - TEST_SUITE sandbox - TEST_SERIAL - PATH ${CMAKE_CURRENT_LIST_DIR}/scripting/TestSuite_Sandbox.py - TIMEOUT 1500 - RUNTIME_DEPENDENCIES - Legacy::Editor - AZ::AssetProcessor - AutomatedTesting.Assets - ) -endif() +add_subdirectory(scripting) ## White Box ## -if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS) - ly_add_pytest( - NAME AutomatedTesting::WhiteBoxTests - TEST_SUITE main - TEST_SERIAL - PATH ${CMAKE_CURRENT_LIST_DIR}/WhiteBox/TestSuite_Active.py - TIMEOUT 1500 - RUNTIME_DEPENDENCIES - Legacy::Editor - AZ::AssetProcessor - AutomatedTesting.Assets - COMPONENT - WhiteBox - ) -endif() +add_subdirectory(WhiteBox) ## NvCloth ## -if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS) - ly_add_pytest( - NAME AutomatedTesting::NvClothTests_Main - TEST_SUITE main - TEST_SERIAL - PATH ${CMAKE_CURRENT_LIST_DIR}/NvCloth/TestSuite_Active.py - TIMEOUT 1500 - RUNTIME_DEPENDENCIES - Legacy::Editor - AZ::AssetProcessor - AutomatedTesting.Assets - COMPONENT - NvCloth - ) -endif() +add_subdirectory(NvCloth) ## Prefab ## -if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS) - ly_add_pytest( - NAME AutomatedTesting::PrefabTests - TEST_SUITE main - TEST_SERIAL - PATH ${CMAKE_CURRENT_LIST_DIR}/prefab/TestSuite_Main.py - TIMEOUT 1500 - RUNTIME_DEPENDENCIES - Legacy::Editor - AZ::AssetProcessor - AutomatedTesting.Assets - ) -endif() +add_subdirectory(prefab) ## Editor Python Bindings ## -if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS) - ly_add_pytest( - NAME AutomatedTesting::EditorPythonBindings - TEST_SUITE sandbox - TEST_SERIAL - PATH ${CMAKE_CURRENT_LIST_DIR}/EditorPythonBindings - TIMEOUT 1500 - RUNTIME_DEPENDENCIES - Legacy::Editor - AZ::AssetProcessor - AutomatedTesting.Assets - Gem::EditorPythonBindings.Editor - COMPONENT TestTools - ) -endif() +add_subdirectory(EditorPythonBindings) ## Python Asset Builder ## -if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS) - ly_add_pytest( - NAME AutomatedTesting::PythonAssetBuilder - TEST_SUITE periodic - TEST_SERIAL - PATH ${CMAKE_CURRENT_LIST_DIR}/PythonAssetBuilder - TIMEOUT 1500 - RUNTIME_DEPENDENCIES - Legacy::Editor - AZ::AssetProcessor - AutomatedTesting.Assets - Gem::EditorPythonBindings.Editor - Gem::PythonAssetBuilder.Editor - COMPONENT TestTools - ) -endif() +add_subdirectory(PythonAssetBuilder) ## Blast ## -if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS) - ly_add_pytest( - NAME AutomatedTesting::BlastTests - TEST_SUITE main - TEST_SERIAL TRUE - PATH ${CMAKE_CURRENT_LIST_DIR}/Blast/TestSuite_Active.py - TIMEOUT 1500 - RUNTIME_DEPENDENCIES - Legacy::Editor - AZ::AssetProcessor - AutomatedTesting.Assets - COMPONENT Blast - ) -endif() - -############# +add_subdirectory(Blast) ## Large Worlds ## - -include(${pal_dir}/PAL_traits_${PAL_PLATFORM_NAME_LOWERCASE}.cmake) -if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS AND PAL_TRAIT_LARGE_WORLDS_TEST_SUPPORTED) - -## DynVeg ## - # Temporarily moving all tests to periodic suite - SPEC-6553 - #ly_add_pytest( - # NAME AutomatedTesting::DynamicVegetationTests_Main - # TEST_SERIAL - # TEST_SUITE main - # PATH ${CMAKE_CURRENT_LIST_DIR}/largeworlds/dyn_veg - # PYTEST_MARKS "not SUITE_sandbox and not SUITE_periodic and not SUITE_benchmark" - # TIMEOUT 1500 - # RUNTIME_DEPENDENCIES - # AZ::AssetProcessor - # Legacy::Editor - # AutomatedTesting.GameLauncher - # AutomatedTesting.Assets - # COMPONENT - # LargeWorlds - #) - - - ly_add_pytest( - NAME AutomatedTesting::DynamicVegetationTests_Sandbox - TEST_SERIAL - TEST_SUITE sandbox - PATH ${CMAKE_CURRENT_LIST_DIR}/largeworlds/dyn_veg - PYTEST_MARKS "SUITE_sandbox" - TIMEOUT 1500 - RUNTIME_DEPENDENCIES - AZ::AssetProcessor - Legacy::Editor - AutomatedTesting.GameLauncher - AutomatedTesting.Assets - COMPONENT - LargeWorlds - ) - - ly_add_pytest( - NAME AutomatedTesting::DynamicVegetationFilterTests_Periodic - TEST_SERIAL - TEST_SUITE periodic - PATH ${CMAKE_CURRENT_LIST_DIR}/largeworlds/dyn_veg - PYTEST_MARKS "SUITE_periodic and dynveg_filter" - TIMEOUT 1500 - RUNTIME_DEPENDENCIES - AZ::AssetProcessor - Legacy::Editor - AutomatedTesting.Assets - COMPONENT - LargeWorlds - ) - - ly_add_pytest( - NAME AutomatedTesting::DynamicVegetationModifierTests_Periodic - TEST_SERIAL - TEST_SUITE periodic - PATH ${CMAKE_CURRENT_LIST_DIR}/largeworlds/dyn_veg - PYTEST_MARKS "SUITE_periodic and dynveg_modifier" - TIMEOUT 1500 - RUNTIME_DEPENDENCIES - AZ::AssetProcessor - Legacy::Editor - AutomatedTesting.Assets - COMPONENT - LargeWorlds - ) - - ly_add_pytest( - NAME AutomatedTesting::DynamicVegetationRegressionTests_Periodic - TEST_SERIAL - TEST_SUITE periodic - PATH ${CMAKE_CURRENT_LIST_DIR}/largeworlds/dyn_veg - PYTEST_MARKS "SUITE_periodic and dynveg_regression" - TIMEOUT 1500 - RUNTIME_DEPENDENCIES - AZ::AssetProcessor - Legacy::Editor - AutomatedTesting.Assets - COMPONENT - LargeWorlds - ) - - ly_add_pytest( - NAME AutomatedTesting::DynamicVegetationAreaTests_Periodic - TEST_SERIAL - TEST_SUITE periodic - PATH ${CMAKE_CURRENT_LIST_DIR}/largeworlds/dyn_veg - PYTEST_MARKS "SUITE_periodic and dynveg_area" - TIMEOUT 1500 - RUNTIME_DEPENDENCIES - AZ::AssetProcessor - Legacy::Editor - AutomatedTesting.Assets - COMPONENT - LargeWorlds - ) - - ly_add_pytest( - NAME AutomatedTesting::DynamicVegetationMiscTests_Periodic - TEST_SERIAL - TEST_SUITE periodic - PATH ${CMAKE_CURRENT_LIST_DIR}/largeworlds/dyn_veg - PYTEST_MARKS "SUITE_periodic and dynveg_misc" - TIMEOUT 1500 - RUNTIME_DEPENDENCIES - AZ::AssetProcessor - Legacy::Editor - AutomatedTesting.Assets - COMPONENT - LargeWorlds - ) - - ly_add_pytest( - NAME AutomatedTesting::DynamicVegetationSurfaceTagTests_Periodic - TEST_SERIAL - TEST_SUITE periodic - PATH ${CMAKE_CURRENT_LIST_DIR}/largeworlds/dyn_veg - PYTEST_MARKS "SUITE_periodic and dynveg_surfacetagemitter" - TIMEOUT 1500 - RUNTIME_DEPENDENCIES - AZ::AssetProcessor - Legacy::Editor - AutomatedTesting.Assets - COMPONENT - LargeWorlds - ) -## LandscapeCanvas ## - # Temporarily moving all tests to periodic suite - SPEC-6553 - #ly_add_pytest( - # NAME AutomatedTesting::LandscapeCanvasTests_Main - # TEST_SERIAL - # TEST_SUITE main - # PATH ${CMAKE_CURRENT_LIST_DIR}/largeworlds/landscape_canvas - # PYTEST_MARKS "not SUITE_sandbox and not SUITE_periodic and not SUITE_benchmark" - # TIMEOUT 1500 - # RUNTIME_DEPENDENCIES - # AZ::AssetProcessor - # Legacy::Editor - # AutomatedTesting.Assets - # COMPONENT - # LargeWorlds - #) - - ly_add_pytest( - NAME AutomatedTesting::LandscapeCanvasTests_Periodic - TEST_SERIAL - TEST_SUITE periodic - PATH ${CMAKE_CURRENT_LIST_DIR}/largeworlds/landscape_canvas - PYTEST_MARKS "SUITE_periodic" - TIMEOUT 1500 - RUNTIME_DEPENDENCIES - AZ::AssetProcessor - Legacy::Editor - AutomatedTesting.Assets - COMPONENT - LargeWorlds - ) - -## GradientSignal ## - ly_add_pytest( - NAME AutomatedTesting::GradientSignalTests_Periodic - TEST_SERIAL - TEST_SUITE periodic - PATH ${CMAKE_CURRENT_LIST_DIR}/largeworlds/gradient_signal - TIMEOUT 1500 - RUNTIME_DEPENDENCIES - AZ::AssetProcessor - Legacy::Editor - AutomatedTesting.Assets - COMPONENT - LargeWorlds - ) - -endif() +add_subdirectory(largeworlds) ## Editor ## -if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS AND PAL_TRAIT_FOUNDATION_TEST_SUPPORTED) - ly_add_pytest( - NAME AutomatedTesting::EditorTests_Periodic - TEST_SUITE periodic - TEST_SERIAL - PATH ${CMAKE_CURRENT_LIST_DIR}/editor - TIMEOUT 1500 - RUNTIME_DEPENDENCIES - Legacy::Editor - AZ::AssetProcessor - AutomatedTesting.Assets - COMPONENT - Editor - ) -endif() +add_subdirectory(editor) -if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS) - # Unstable, SPEC-3838 will restore - #ly_add_pytest( - # NAME AutomatedTesting::asset_load_benchmark_test - # TEST_SERIAL - # TEST_SUITE benchmark - # PATH ${CMAKE_CURRENT_LIST_DIR}/streaming/benchmark/asset_load_benchmark_test.py - # RUNTIME_DEPENDENCIES - # AZ::AssetProcessor - # AZ::AssetProcessorBatch - # AutomatedTesting.GameLauncher - #) -endif() +## Streaming ## +add_subdirectory(streaming) -## Smoke ## -if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS) - ly_add_pytest( - NAME AutomatedTesting::SmokeTest - TEST_SUITE smoke - TEST_SERIAL - PATH ${CMAKE_CURRENT_LIST_DIR}/smoke - TIMEOUT 1500 - RUNTIME_DEPENDENCIES - AZ::AssetProcessor - AZ::PythonBindingsExample - Legacy::Editor - AutomatedTesting.GameLauncher - AutomatedTesting.Assets - COMPONENT - Smoke - ) -endif() +## Streaming ## +add_subdirectory(smoke) diff --git a/AutomatedTesting/Gem/PythonTests/EditorPythonBindings/CMakeLists.txt b/AutomatedTesting/Gem/PythonTests/EditorPythonBindings/CMakeLists.txt new file mode 100644 index 0000000000..20a0ca8e65 --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/EditorPythonBindings/CMakeLists.txt @@ -0,0 +1,26 @@ +# +# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +# its licensors. +# +# For complete copyright and license terms please see the LICENSE at the root of this +# distribution (the "License"). All use of this software is governed by the License, +# or, if provided, by the license below or the license accompanying this file. Do not +# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# + +if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS) + ly_add_pytest( + NAME AutomatedTesting::EditorPythonBindings + TEST_SUITE sandbox + TEST_SERIAL + PATH ${CMAKE_CURRENT_LIST_DIR} + TIMEOUT 1500 + RUNTIME_DEPENDENCIES + Legacy::Editor + AZ::AssetProcessor + AutomatedTesting.Assets + Gem::EditorPythonBindings.Editor + COMPONENT TestTools + ) +endif() diff --git a/AutomatedTesting/Gem/PythonTests/NvCloth/CMakeLists.txt b/AutomatedTesting/Gem/PythonTests/NvCloth/CMakeLists.txt new file mode 100644 index 0000000000..058cc9ad94 --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/NvCloth/CMakeLists.txt @@ -0,0 +1,26 @@ +# +# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +# its licensors. +# +# For complete copyright and license terms please see the LICENSE at the root of this +# distribution (the "License"). All use of this software is governed by the License, +# or, if provided, by the license below or the license accompanying this file. Do not +# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# + +if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS) + ly_add_pytest( + NAME AutomatedTesting::NvClothTests_Main + TEST_SUITE main + TEST_SERIAL + PATH ${CMAKE_CURRENT_LIST_DIR}/TestSuite_Active.py + TIMEOUT 1500 + RUNTIME_DEPENDENCIES + Legacy::Editor + AZ::AssetProcessor + AutomatedTesting.Assets + COMPONENT + NvCloth + ) +endif() diff --git a/AutomatedTesting/Gem/PythonTests/PythonAssetBuilder/CMakeLists.txt b/AutomatedTesting/Gem/PythonTests/PythonAssetBuilder/CMakeLists.txt new file mode 100644 index 0000000000..0a550de539 --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/PythonAssetBuilder/CMakeLists.txt @@ -0,0 +1,27 @@ +# +# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +# its licensors. +# +# For complete copyright and license terms please see the LICENSE at the root of this +# distribution (the "License"). All use of this software is governed by the License, +# or, if provided, by the license below or the license accompanying this file. Do not +# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# + +if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS) + ly_add_pytest( + NAME AutomatedTesting::PythonAssetBuilder + TEST_SUITE periodic + TEST_SERIAL + PATH ${CMAKE_CURRENT_LIST_DIR} + TIMEOUT 1500 + RUNTIME_DEPENDENCIES + Legacy::Editor + AZ::AssetProcessor + AutomatedTesting.Assets + Gem::EditorPythonBindings.Editor + Gem::PythonAssetBuilder.Editor + COMPONENT TestTools + ) +endif() diff --git a/AutomatedTesting/Gem/PythonTests/WhiteBox/CMakeLists.txt b/AutomatedTesting/Gem/PythonTests/WhiteBox/CMakeLists.txt new file mode 100644 index 0000000000..a6c0a9aa21 --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/WhiteBox/CMakeLists.txt @@ -0,0 +1,26 @@ +# +# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +# its licensors. +# +# For complete copyright and license terms please see the LICENSE at the root of this +# distribution (the "License"). All use of this software is governed by the License, +# or, if provided, by the license below or the license accompanying this file. Do not +# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# + +if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS) + ly_add_pytest( + NAME AutomatedTesting::WhiteBoxTests + TEST_SUITE main + TEST_SERIAL + PATH ${CMAKE_CURRENT_LIST_DIR}/TestSuite_Active.py + TIMEOUT 1500 + RUNTIME_DEPENDENCIES + Legacy::Editor + AZ::AssetProcessor + AutomatedTesting.Assets + COMPONENT + WhiteBox + ) +endif() diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/CMakeLists.txt b/AutomatedTesting/Gem/PythonTests/assetpipeline/CMakeLists.txt index 74ef6c97a7..f453a38486 100644 --- a/AutomatedTesting/Gem/PythonTests/assetpipeline/CMakeLists.txt +++ b/AutomatedTesting/Gem/PythonTests/assetpipeline/CMakeLists.txt @@ -13,8 +13,6 @@ add_subdirectory(asset_processor_tests) if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS) ## AP Python Tests ## - - ly_add_pytest( NAME AssetPipelineTests.AuxiliaryContent PATH ${CMAKE_CURRENT_LIST_DIR}/auxiliary_content_tests/auxiliary_content_tests.py @@ -22,7 +20,6 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS) TEST_SUITE periodic ) - ly_add_pytest( NAME AssetPipelineTests.BankInfoParser PATH ${CMAKE_CURRENT_LIST_DIR}/wwise_bank_dependency_tests/bank_info_parser_tests.py @@ -33,4 +30,3 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS) ) endif() - diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/CMakeLists.txt b/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/CMakeLists.txt index 6a865ee690..a2002f2d15 100644 --- a/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/CMakeLists.txt +++ b/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/CMakeLists.txt @@ -141,4 +141,3 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS) # ) endif() - diff --git a/AutomatedTesting/Gem/PythonTests/editor/CMakeLists.txt b/AutomatedTesting/Gem/PythonTests/editor/CMakeLists.txt new file mode 100644 index 0000000000..44e3ed0425 --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/editor/CMakeLists.txt @@ -0,0 +1,26 @@ +# +# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +# its licensors. +# +# For complete copyright and license terms please see the LICENSE at the root of this +# distribution (the "License"). All use of this software is governed by the License, +# or, if provided, by the license below or the license accompanying this file. Do not +# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# + +if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS AND PAL_TRAIT_FOUNDATION_TEST_SUPPORTED) + ly_add_pytest( + NAME AutomatedTesting::EditorTests_Periodic + TEST_SUITE periodic + TEST_SERIAL + PATH ${CMAKE_CURRENT_LIST_DIR} + TIMEOUT 1500 + RUNTIME_DEPENDENCIES + Legacy::Editor + AZ::AssetProcessor + AutomatedTesting.Assets + COMPONENT + Editor + ) +endif() diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/CMakeLists.txt b/AutomatedTesting/Gem/PythonTests/largeworlds/CMakeLists.txt new file mode 100644 index 0000000000..72e3bec3df --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/largeworlds/CMakeLists.txt @@ -0,0 +1,186 @@ +# +# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +# its licensors. +# +# For complete copyright and license terms please see the LICENSE at the root of this +# distribution (the "License"). All use of this software is governed by the License, +# or, if provided, by the license below or the license accompanying this file. Do not +# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# + +if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS AND PAL_TRAIT_LARGE_WORLDS_TEST_SUPPORTED) + +## DynVeg ## + + # Temporarily moving all tests to periodic suite - SPEC-6553 + #ly_add_pytest( + # NAME AutomatedTesting::DynamicVegetationTests_Main + # TEST_SERIAL + # TEST_SUITE main + # PATH ${CMAKE_CURRENT_LIST_DIR}/dyn_veg + # PYTEST_MARKS "not SUITE_sandbox and not SUITE_periodic and not SUITE_benchmark" + # TIMEOUT 1500 + # RUNTIME_DEPENDENCIES + # AZ::AssetProcessor + # Legacy::Editor + # AutomatedTesting.GameLauncher + # AutomatedTesting.Assets + # COMPONENT + # LargeWorlds + #) + + + ly_add_pytest( + NAME AutomatedTesting::DynamicVegetationTests_Sandbox + TEST_SERIAL + TEST_SUITE sandbox + PATH ${CMAKE_CURRENT_LIST_DIR}/dyn_veg + PYTEST_MARKS "SUITE_sandbox" + TIMEOUT 1500 + RUNTIME_DEPENDENCIES + AZ::AssetProcessor + Legacy::Editor + AutomatedTesting.GameLauncher + AutomatedTesting.Assets + COMPONENT + LargeWorlds + ) + + ly_add_pytest( + NAME AutomatedTesting::DynamicVegetationFilterTests_Periodic + TEST_SERIAL + TEST_SUITE periodic + PATH ${CMAKE_CURRENT_LIST_DIR}/dyn_veg + PYTEST_MARKS "SUITE_periodic and dynveg_filter" + TIMEOUT 1500 + RUNTIME_DEPENDENCIES + AZ::AssetProcessor + Legacy::Editor + AutomatedTesting.Assets + COMPONENT + LargeWorlds + ) + + ly_add_pytest( + NAME AutomatedTesting::DynamicVegetationModifierTests_Periodic + TEST_SERIAL + TEST_SUITE periodic + PATH ${CMAKE_CURRENT_LIST_DIR}/dyn_veg + PYTEST_MARKS "SUITE_periodic and dynveg_modifier" + TIMEOUT 1500 + RUNTIME_DEPENDENCIES + AZ::AssetProcessor + Legacy::Editor + AutomatedTesting.Assets + COMPONENT + LargeWorlds + ) + + ly_add_pytest( + NAME AutomatedTesting::DynamicVegetationRegressionTests_Periodic + TEST_SERIAL + TEST_SUITE periodic + PATH ${CMAKE_CURRENT_LIST_DIR}/dyn_veg + PYTEST_MARKS "SUITE_periodic and dynveg_regression" + TIMEOUT 1500 + RUNTIME_DEPENDENCIES + AZ::AssetProcessor + Legacy::Editor + AutomatedTesting.Assets + COMPONENT + LargeWorlds + ) + + ly_add_pytest( + NAME AutomatedTesting::DynamicVegetationAreaTests_Periodic + TEST_SERIAL + TEST_SUITE periodic + PATH ${CMAKE_CURRENT_LIST_DIR}/dyn_veg + PYTEST_MARKS "SUITE_periodic and dynveg_area" + TIMEOUT 1500 + RUNTIME_DEPENDENCIES + AZ::AssetProcessor + Legacy::Editor + AutomatedTesting.Assets + COMPONENT + LargeWorlds + ) + + ly_add_pytest( + NAME AutomatedTesting::DynamicVegetationMiscTests_Periodic + TEST_SERIAL + TEST_SUITE periodic + PATH ${CMAKE_CURRENT_LIST_DIR}/dyn_veg + PYTEST_MARKS "SUITE_periodic and dynveg_misc" + TIMEOUT 1500 + RUNTIME_DEPENDENCIES + AZ::AssetProcessor + Legacy::Editor + AutomatedTesting.Assets + COMPONENT + LargeWorlds + ) + + ly_add_pytest( + NAME AutomatedTesting::DynamicVegetationSurfaceTagTests_Periodic + TEST_SERIAL + TEST_SUITE periodic + PATH ${CMAKE_CURRENT_LIST_DIR}/dyn_veg + PYTEST_MARKS "SUITE_periodic and dynveg_surfacetagemitter" + TIMEOUT 1500 + RUNTIME_DEPENDENCIES + AZ::AssetProcessor + Legacy::Editor + AutomatedTesting.Assets + COMPONENT + LargeWorlds + ) +## LandscapeCanvas ## + # Temporarily moving all tests to periodic suite - SPEC-6553 + #ly_add_pytest( + # NAME AutomatedTesting::LandscapeCanvasTests_Main + # TEST_SERIAL + # TEST_SUITE main + # PATH ${CMAKE_CURRENT_LIST_DIR}/largeworlds/landscape_canvas + # PYTEST_MARKS "not SUITE_sandbox and not SUITE_periodic and not SUITE_benchmark" + # TIMEOUT 1500 + # RUNTIME_DEPENDENCIES + # AZ::AssetProcessor + # Legacy::Editor + # AutomatedTesting.Assets + # COMPONENT + # LargeWorlds + #) + + ly_add_pytest( + NAME AutomatedTesting::LandscapeCanvasTests_Periodic + TEST_SERIAL + TEST_SUITE periodic + PATH ${CMAKE_CURRENT_LIST_DIR}/landscape_canvas + PYTEST_MARKS "SUITE_periodic" + TIMEOUT 1500 + RUNTIME_DEPENDENCIES + AZ::AssetProcessor + Legacy::Editor + AutomatedTesting.Assets + COMPONENT + LargeWorlds + ) + +## GradientSignal ## + ly_add_pytest( + NAME AutomatedTesting::GradientSignalTests_Periodic + TEST_SERIAL + TEST_SUITE periodic + PATH ${CMAKE_CURRENT_LIST_DIR}/gradient_signal + TIMEOUT 1500 + RUNTIME_DEPENDENCIES + AZ::AssetProcessor + Legacy::Editor + AutomatedTesting.Assets + COMPONENT + LargeWorlds + ) + +endif() diff --git a/AutomatedTesting/Gem/PythonTests/physics/CMakeLists.txt b/AutomatedTesting/Gem/PythonTests/physics/CMakeLists.txt new file mode 100644 index 0000000000..4f89e841df --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/physics/CMakeLists.txt @@ -0,0 +1,52 @@ +# +# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +# its licensors. +# +# For complete copyright and license terms please see the LICENSE at the root of this +# distribution (the "License"). All use of this software is governed by the License, +# or, if provided, by the license below or the license accompanying this file. Do not +# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# + +if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS) + ly_add_pytest( + NAME AutomatedTesting::PhysicsTests_Main + TEST_SUITE main + TEST_SERIAL + PATH ${CMAKE_CURRENT_LIST_DIR}/TestSuite_Main.py + TIMEOUT 1500 + RUNTIME_DEPENDENCIES + Legacy::Editor + AZ::AssetProcessor + AutomatedTesting.Assets + COMPONENT + Physics + ) + ly_add_pytest( + NAME AutomatedTesting::PhysicsTests_Periodic + TEST_SUITE periodic + TEST_SERIAL + PATH ${CMAKE_CURRENT_LIST_DIR}/TestSuite_Periodic.py + TIMEOUT 1500 + RUNTIME_DEPENDENCIES + Legacy::Editor + AZ::AssetProcessor + AutomatedTesting.Assets + COMPONENT + Physics + ) + ly_add_pytest( + NAME AutomatedTesting::PhysicsTests_Sandbox + TEST_SUITE sandbox + TEST_SERIAL + PATH ${CMAKE_CURRENT_LIST_DIR}/TestSuite_Sandbox.py + TIMEOUT 1500 + RUNTIME_DEPENDENCIES + Legacy::Editor + AZ::AssetProcessor + AutomatedTesting.Assets + COMPONENT + Physics + ) +endif() diff --git a/AutomatedTesting/Gem/PythonTests/prefab/CMakeLists.txt b/AutomatedTesting/Gem/PythonTests/prefab/CMakeLists.txt new file mode 100644 index 0000000000..61f1944974 --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/prefab/CMakeLists.txt @@ -0,0 +1,25 @@ +# +# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +# its licensors. +# +# For complete copyright and license terms please see the LICENSE at the root of this +# distribution (the "License"). All use of this software is governed by the License, +# or, if provided, by the license below or the license accompanying this file. Do not +# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# + +## Prefab ## +if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS) + ly_add_pytest( + NAME AutomatedTesting::PrefabTests + TEST_SUITE main + TEST_SERIAL + PATH ${CMAKE_CURRENT_LIST_DIR}/TestSuite_Main.py + TIMEOUT 1500 + RUNTIME_DEPENDENCIES + Legacy::Editor + AZ::AssetProcessor + AutomatedTesting.Assets + ) +endif() diff --git a/AutomatedTesting/Gem/PythonTests/scripting/CMakeLists.txt b/AutomatedTesting/Gem/PythonTests/scripting/CMakeLists.txt new file mode 100644 index 0000000000..58507c680c --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/scripting/CMakeLists.txt @@ -0,0 +1,37 @@ +# +# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +# its licensors. +# +# For complete copyright and license terms please see the LICENSE at the root of this +# distribution (the "License"). All use of this software is governed by the License, +# or, if provided, by the license below or the license accompanying this file. Do not +# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# + +if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS) + ly_add_pytest( + NAME AutomatedTesting::ScriptCanvasTests_Perodic + TEST_SUITE periodic + TEST_SERIAL + PATH ${CMAKE_CURRENT_LIST_DIR}/TestSuite_Periodic.py + TIMEOUT 1500 + RUNTIME_DEPENDENCIES + Legacy::Editor + AZ::AssetProcessor + AutomatedTesting.Assets + COMPONENT + ScriptCanvas + ) + ly_add_pytest( + NAME AutomatedTesting::ScriptCanvasTests_Sandbox + TEST_SUITE sandbox + TEST_SERIAL + PATH ${CMAKE_CURRENT_LIST_DIR}/TestSuite_Sandbox.py + TIMEOUT 1500 + RUNTIME_DEPENDENCIES + Legacy::Editor + AZ::AssetProcessor + AutomatedTesting.Assets + ) +endif() diff --git a/AutomatedTesting/Gem/PythonTests/scripting/TestSuite_Active.py b/AutomatedTesting/Gem/PythonTests/scripting/TestSuite_Periodic.py similarity index 100% rename from AutomatedTesting/Gem/PythonTests/scripting/TestSuite_Active.py rename to AutomatedTesting/Gem/PythonTests/scripting/TestSuite_Periodic.py diff --git a/AutomatedTesting/Gem/PythonTests/smoke/CMakeLists.txt b/AutomatedTesting/Gem/PythonTests/smoke/CMakeLists.txt new file mode 100644 index 0000000000..d351ec0e6c --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/smoke/CMakeLists.txt @@ -0,0 +1,27 @@ +# +# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +# its licensors. +# +# For complete copyright and license terms please see the LICENSE at the root of this +# distribution (the "License"). All use of this software is governed by the License, +# or, if provided, by the license below or the license accompanying this file. Do not +# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + +if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS) + ly_add_pytest( + NAME AutomatedTesting::SmokeTest + TEST_SUITE smoke + TEST_SERIAL + PATH ${CMAKE_CURRENT_LIST_DIR} + TIMEOUT 1500 + RUNTIME_DEPENDENCIES + AZ::AssetProcessor + AZ::PythonBindingsExample + Legacy::Editor + AutomatedTesting.GameLauncher + AutomatedTesting.Assets + COMPONENT + Smoke + ) +endif() \ No newline at end of file diff --git a/AutomatedTesting/Gem/PythonTests/smoke/test_CLITool_AssetBuilder_Works.py b/AutomatedTesting/Gem/PythonTests/smoke/test_CLITool_AssetBuilder_Works.py index 5381fd9fd8..10df59e086 100644 --- a/AutomatedTesting/Gem/PythonTests/smoke/test_CLITool_AssetBuilder_Works.py +++ b/AutomatedTesting/Gem/PythonTests/smoke/test_CLITool_AssetBuilder_Works.py @@ -20,6 +20,7 @@ import subprocess @pytest.mark.SUITE_smoke class TestCLIToolAssetBuilderWorks(object): + @pytest.mark.xfail(reason="Ignoring failure temporarily - SPEC-6905") def test_CLITool_AssetBuilder_Works(self, build_directory): file_path = os.path.join(build_directory, "AssetBuilder") help_message = "AssetBuilder is part of the Asset Processor" diff --git a/AutomatedTesting/Gem/PythonTests/streaming/CMakeLists.txt b/AutomatedTesting/Gem/PythonTests/streaming/CMakeLists.txt new file mode 100644 index 0000000000..10b9fabcc0 --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/streaming/CMakeLists.txt @@ -0,0 +1,24 @@ +# +# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +# its licensors. +# +# For complete copyright and license terms please see the LICENSE at the root of this +# distribution (the "License"). All use of this software is governed by the License, +# or, if provided, by the license below or the license accompanying this file. Do not +# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# + +if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS) + # Unstable, SPEC-3838 will restore + #ly_add_pytest( + # NAME AutomatedTesting::asset_load_benchmark_test + # TEST_SERIAL + # TEST_SUITE benchmark + # PATH ${CMAKE_CURRENT_LIST_DIR}/benchmark/asset_load_benchmark_test.py + # RUNTIME_DEPENDENCIES + # AZ::AssetProcessor + # AZ::AssetProcessorBatch + # AutomatedTesting.GameLauncher + #) +endif() diff --git a/Code/CryEngine/CryCommon/IShader.h b/Code/CryEngine/CryCommon/IShader.h index aea0762a81..81fe18706d 100644 --- a/Code/CryEngine/CryCommon/IShader.h +++ b/Code/CryEngine/CryCommon/IShader.h @@ -772,7 +772,6 @@ _MS_ALIGN(16) struct SSkinningData void* pCharInstCB; // used if per char instance cbs are available in renderdll (d3d11+); // members below are for Software Skinning void* pCustomData; // client specific data, used for example for sw-skinning on animation side - SSkinningData** pMasterSkinningDataList; // used by the SkinningData for a Character Instance, contains a list of all Skin Instances which need SW-Skinning SSkinningData* pNextSkinningData; // List to the next element which needs SW-Skinning } _ALIGN(16); diff --git a/Code/CryEngine/CrySystem/DebugCallStack.cpp b/Code/CryEngine/CrySystem/DebugCallStack.cpp new file mode 100644 index 0000000000..2a219ce674 --- /dev/null +++ b/Code/CryEngine/CrySystem/DebugCallStack.cpp @@ -0,0 +1,900 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ +// Original file Copyright Crytek GMBH or its affiliates, used under license. + +#include "CrySystem_precompiled.h" +#include "DebugCallStack.h" + +#if defined(WIN32) || defined(WIN64) + +#include +#include +#include "System.h" + +#include +#include + +#define VS_VERSION_INFO 1 +#define IDD_CRITICAL_ERROR 101 +#define IDB_CONFIRM_SAVE 102 +#define IDB_DONT_SAVE 103 +#define IDD_CONFIRM_SAVE_LEVEL 127 +#define IDB_CRASH_FACE 128 +#define IDD_EXCEPTION 245 +#define IDC_CALLSTACK 1001 +#define IDC_EXCEPTION_CODE 1002 +#define IDC_EXCEPTION_ADDRESS 1003 +#define IDC_EXCEPTION_MODULE 1004 +#define IDC_EXCEPTION_DESC 1005 +#define IDB_EXIT 1008 +#define IDB_IGNORE 1010 +__pragma(comment(lib, "version.lib")) + +//! Needs one external of DLL handle. +extern HMODULE gDLLHandle; + +#include + +#define MAX_PATH_LENGTH 1024 +#define MAX_SYMBOL_LENGTH 512 + +static HWND hwndException = 0; +static bool g_bUserDialog = true; // true=on crash show dialog box, false=supress user interaction + +static int PrintException(EXCEPTION_POINTERS* pex); + +static bool IsFloatingPointException(EXCEPTION_POINTERS* pex); + +extern LONG WINAPI CryEngineExceptionFilterWER(struct _EXCEPTION_POINTERS* pExceptionPointers); +extern LONG WINAPI CryEngineExceptionFilterMiniDump(struct _EXCEPTION_POINTERS* pExceptionPointers, const char* szDumpPath, MINIDUMP_TYPE mdumpValue); + +//============================================================================= +CONTEXT CaptureCurrentContext() +{ + CONTEXT context; + memset(&context, 0, sizeof(context)); + context.ContextFlags = CONTEXT_FULL; + RtlCaptureContext(&context); + + return context; +} + +LONG __stdcall CryUnhandledExceptionHandler(EXCEPTION_POINTERS* pex) +{ + return DebugCallStack::instance()->handleException(pex); +} + + +BOOL CALLBACK EnumModules( + PCSTR ModuleName, + DWORD64 BaseOfDll, + PVOID UserContext) +{ + DebugCallStack::TModules& modules = *static_cast(UserContext); + modules[(void*)BaseOfDll] = ModuleName; + + return TRUE; +} +//============================================================================= +// Class Statics +//============================================================================= + +// Return single instance of class. +IDebugCallStack* IDebugCallStack::instance() +{ + static DebugCallStack sInstance; + return &sInstance; +} + +//------------------------------------------------------------------------------------------------------------------------ +// Sets up the symbols for functions in the debug file. +//------------------------------------------------------------------------------------------------------------------------ +DebugCallStack::DebugCallStack() + : prevExceptionHandler(0) + , m_pSystem(0) + , m_nSkipNumFunctions(0) + , m_bCrash(false) + , m_szBugMessage(NULL) +{ +} + +DebugCallStack::~DebugCallStack() +{ +} + +void DebugCallStack::RemoveOldFiles() +{ + RemoveFile("error.log"); + RemoveFile("error.bmp"); + RemoveFile("error.dmp"); +} + +void DebugCallStack::RemoveFile(const char* szFileName) +{ + FILE* pFile = nullptr; + azfopen(&pFile, szFileName, "r"); + const bool bFileExists = (pFile != NULL); + + if (bFileExists) + { + fclose(pFile); + + WriteLineToLog("Removing file \"%s\"...", szFileName); + if (remove(szFileName) == 0) + { + WriteLineToLog("File successfully removed."); + } + else + { + WriteLineToLog("Couldn't remove file!"); + } + } +} + +void DebugCallStack::installErrorHandler(ISystem* pSystem) +{ + m_pSystem = pSystem; + prevExceptionHandler = (void*)SetUnhandledExceptionFilter(CryUnhandledExceptionHandler); +} + +////////////////////////////////////////////////////////////////////////// +void DebugCallStack::SetUserDialogEnable(const bool bUserDialogEnable) +{ + g_bUserDialog = bUserDialogEnable; +} + + +DWORD g_idDebugThreads[10]; +const char* g_nameDebugThreads[10]; +int g_nDebugThreads = 0; +volatile int g_lockThreadDumpList = 0; + +void MarkThisThreadForDebugging(const char* name) +{ + EBUS_EVENT(AZ::Debug::EventTraceDrillerSetupBus, SetThreadName, AZStd::this_thread::get_id(), name); + + WriteLock lock(g_lockThreadDumpList); + DWORD id = GetCurrentThreadId(); + if (g_nDebugThreads == sizeof(g_idDebugThreads) / sizeof(g_idDebugThreads[0])) + { + return; + } + for (int i = 0; i < g_nDebugThreads; i++) + { + if (g_idDebugThreads[i] == id) + { + return; + } + } + g_nameDebugThreads[g_nDebugThreads] = name; + g_idDebugThreads[g_nDebugThreads++] = id; + ((CSystem*)gEnv->pSystem)->EnableFloatExceptions(g_cvars.sys_float_exceptions); +} + +void UnmarkThisThreadFromDebugging() +{ + WriteLock lock(g_lockThreadDumpList); + DWORD id = GetCurrentThreadId(); + for (int i = g_nDebugThreads - 1; i >= 0; i--) + { + if (g_idDebugThreads[i] == id) + { + memmove(g_idDebugThreads + i, g_idDebugThreads + i + 1, (g_nDebugThreads - 1 - i) * sizeof(g_idDebugThreads[0])); + memmove(g_nameDebugThreads + i, g_nameDebugThreads + i + 1, (g_nDebugThreads - 1 - i) * sizeof(g_nameDebugThreads[0])); + --g_nDebugThreads; + } + } +} + +extern int prev_sys_float_exceptions; +void UpdateFPExceptionsMaskForThreads() +{ + int mask = -iszero(g_cvars.sys_float_exceptions); + CONTEXT ctx; + for (int i = 0; i < g_nDebugThreads; i++) + { + if (g_idDebugThreads[i] != GetCurrentThreadId()) + { + HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, TRUE, g_idDebugThreads[i]); + ctx.ContextFlags = CONTEXT_ALL; + SuspendThread(hThread); + GetThreadContext(hThread, &ctx); +#ifndef WIN64 + (ctx.FloatSave.ControlWord |= 7) &= ~5 | mask; + (*(WORD*)(ctx.ExtendedRegisters + 24) |= 0x280) &= ~0x280 | mask; +#else + (ctx.FltSave.ControlWord |= 7) &= ~5 | mask; + (ctx.FltSave.MxCsr |= 0x280) &= ~0x280 | mask; +#endif + SetThreadContext(hThread, &ctx); + ResumeThread(hThread); + } + } +} + +////////////////////////////////////////////////////////////////////////// +int DebugCallStack::handleException(EXCEPTION_POINTERS* exception_pointer) +{ + if (gEnv == NULL) + { + return EXCEPTION_EXECUTE_HANDLER; + } + + ResetFPU(exception_pointer); + + prev_sys_float_exceptions = 0; + const int cached_sys_float_exceptions = g_cvars.sys_float_exceptions; + + ((CSystem*)gEnv->pSystem)->EnableFloatExceptions(0); + + if (g_cvars.sys_WER) + { + gEnv->pLog->FlushAndClose(); + return CryEngineExceptionFilterWER(exception_pointer); + } + + if (g_cvars.sys_no_crash_dialog) + { + DWORD dwMode = SetErrorMode(SEM_NOGPFAULTERRORBOX); + SetErrorMode(dwMode | SEM_NOGPFAULTERRORBOX); + } + + m_bCrash = true; + + if (g_cvars.sys_no_crash_dialog) + { + DWORD dwMode = SetErrorMode(SEM_NOGPFAULTERRORBOX); + SetErrorMode(dwMode | SEM_NOGPFAULTERRORBOX); + } + + static bool firstTime = true; + + if (g_cvars.sys_dump_aux_threads) + { + for (int i = 0; i < g_nDebugThreads; i++) + { + if (g_idDebugThreads[i] != GetCurrentThreadId()) + { + SuspendThread(OpenThread(THREAD_ALL_ACCESS, TRUE, g_idDebugThreads[i])); + } + } + } + + // uninstall our exception handler. + SetUnhandledExceptionFilter((LPTOP_LEVEL_EXCEPTION_FILTER)prevExceptionHandler); + + if (!firstTime) + { + WriteLineToLog("Critical Exception! Called Multiple Times!"); + gEnv->pLog->FlushAndClose(); + // Exception called more then once. + return EXCEPTION_EXECUTE_HANDLER; + } + + // Print exception info: + { + char excCode[80]; + char excAddr[80]; + WriteLineToLog(""); + sprintf_s(excAddr, "0x%04X:0x%p", exception_pointer->ContextRecord->SegCs, exception_pointer->ExceptionRecord->ExceptionAddress); + sprintf_s(excCode, "0x%08X", exception_pointer->ExceptionRecord->ExceptionCode); + WriteLineToLog("Exception: %s, at Address: %s", excCode, excAddr); + } + + firstTime = false; + + const int ret = SubmitBug(exception_pointer); + + if (ret != IDB_IGNORE) + { + CryEngineExceptionFilterWER(exception_pointer); + } + + gEnv->pLog->FlushAndClose(); + + if (exception_pointer->ExceptionRecord->ExceptionFlags & EXCEPTION_NONCONTINUABLE) + { + // This is non continuable exception. abort application now. + exit(exception_pointer->ExceptionRecord->ExceptionCode); + } + + //typedef long (__stdcall *ExceptionFunc)(EXCEPTION_POINTERS*); + //ExceptionFunc prevFunc = (ExceptionFunc)prevExceptionHandler; + //return prevFunc( (EXCEPTION_POINTERS*)exception_pointer ); + if (ret == IDB_EXIT) + { + // Immediate exit. + // on windows, exit() and _exit() do all sorts of things, unfortuantely + // TerminateProcess is the only way to die. + TerminateProcess(GetCurrentProcess(), exception_pointer->ExceptionRecord->ExceptionCode); // we crashed, so don't return a zero exit code! + // on linux based systems, _exit will not call ATEXIT and other things, which makes it more suitable for termination in an emergency such + // as an unhandled exception. + // however, this function is a windows exception handler. + } + else if (ret == IDB_IGNORE) + { +#ifndef WIN64 + exception_pointer->ContextRecord->FloatSave.StatusWord &= ~31; + exception_pointer->ContextRecord->FloatSave.ControlWord |= 7; + (*(WORD*)(exception_pointer->ContextRecord->ExtendedRegisters + 24) &= 31) |= 0x1F80; +#else + exception_pointer->ContextRecord->FltSave.StatusWord &= ~31; + exception_pointer->ContextRecord->FltSave.ControlWord |= 7; + (exception_pointer->ContextRecord->FltSave.MxCsr &= 31) |= 0x1F80; +#endif + firstTime = true; + prevExceptionHandler = (void*)SetUnhandledExceptionFilter(CryUnhandledExceptionHandler); + g_cvars.sys_float_exceptions = cached_sys_float_exceptions; + ((CSystem*)gEnv->pSystem)->EnableFloatExceptions(g_cvars.sys_float_exceptions); + return EXCEPTION_CONTINUE_EXECUTION; + } + + // Continue; + return EXCEPTION_EXECUTE_HANDLER; +} + +void DebugCallStack::ReportBug(const char* szErrorMessage) +{ + WriteLineToLog("Reporting bug: %s", szErrorMessage); + + m_szBugMessage = szErrorMessage; + m_context = CaptureCurrentContext(); + SubmitBug(NULL); + m_szBugMessage = NULL; +} + +void DebugCallStack::dumpCallStack(std::vector& funcs) +{ + WriteLineToLog("============================================================================="); + int len = (int)funcs.size(); + for (int i = 0; i < len; i++) + { + const char* str = funcs[i].c_str(); + WriteLineToLog("%2d) %s", len - i, str); + } + WriteLineToLog("============================================================================="); +} + + +////////////////////////////////////////////////////////////////////////// +void DebugCallStack::LogExceptionInfo(EXCEPTION_POINTERS* pex) +{ + string path(""); + if ((gEnv) && (gEnv->pFileIO)) + { + const char* logAlias = gEnv->pFileIO->GetAlias("@log@"); + if (!logAlias) + { + logAlias = gEnv->pFileIO->GetAlias("@root@"); + } + if (logAlias) + { + path = logAlias; + path += "/"; + } + } + + string fileName = path; + fileName += "error.log"; + + struct stat fileInfo; + string timeStamp; + string backupPath; + if (gEnv->IsDedicated()) + { + backupPath = PathUtil::ToUnixPath(PathUtil::AddSlash(path + "DumpBackups")); + gEnv->pFileIO->CreatePath(backupPath.c_str()); + + if (stat(fileName.c_str(), &fileInfo) == 0) + { + // Backup log + tm creationTime; + localtime_s(&creationTime, &fileInfo.st_mtime); + char tempBuffer[32]; + strftime(tempBuffer, sizeof(tempBuffer), "%d %b %Y (%H %M %S)", &creationTime); + timeStamp = tempBuffer; + + string backupFileName = backupPath + timeStamp + " error.log"; + CopyFile(fileName.c_str(), backupFileName.c_str(), true); + } + } + + FILE* f = nullptr; + azfopen(&f, fileName.c_str(), "wt"); + + static char errorString[s_iCallStackSize]; + errorString[0] = 0; + + // Time and Version. + char versionbuf[1024]; + azstrcpy(versionbuf, AZ_ARRAY_SIZE(versionbuf), ""); + PutVersion(versionbuf, AZ_ARRAY_SIZE(versionbuf)); + cry_strcat(errorString, versionbuf); + cry_strcat(errorString, "\n"); + + char excCode[MAX_WARNING_LENGTH]; + char excAddr[80]; + char desc[1024]; + char excDesc[MAX_WARNING_LENGTH]; + + // make sure the mouse cursor is visible + ShowCursor(TRUE); + + const char* excName; + if (m_bIsFatalError || !pex) + { + const char* const szMessage = m_bIsFatalError ? s_szFatalErrorCode : m_szBugMessage; + excName = szMessage; + cry_strcpy(excCode, szMessage); + cry_strcpy(excAddr, ""); + cry_strcpy(desc, ""); + cry_strcpy(m_excModule, ""); + cry_strcpy(excDesc, szMessage); + } + else + { + sprintf_s(excAddr, "0x%04X:0x%p", pex->ContextRecord->SegCs, pex->ExceptionRecord->ExceptionAddress); + sprintf_s(excCode, "0x%08X", pex->ExceptionRecord->ExceptionCode); + excName = TranslateExceptionCode(pex->ExceptionRecord->ExceptionCode); + cry_strcpy(desc, ""); + sprintf_s(excDesc, "%s\r\n%s", excName, desc); + + + if (pex->ExceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION) + { + if (pex->ExceptionRecord->NumberParameters > 1) + { + ULONG_PTR iswrite = pex->ExceptionRecord->ExceptionInformation[0]; + DWORD64 accessAddr = pex->ExceptionRecord->ExceptionInformation[1]; + if (iswrite) + { + sprintf_s(desc, "Attempt to write data to address 0x%08llu\r\nThe memory could not be \"written\"", accessAddr); + } + else + { + sprintf_s(desc, "Attempt to read from address 0x%08llu\r\nThe memory could not be \"read\"", accessAddr); + } + } + } + } + + + WriteLineToLog("Exception Code: %s", excCode); + WriteLineToLog("Exception Addr: %s", excAddr); + WriteLineToLog("Exception Module: %s", m_excModule); + WriteLineToLog("Exception Name : %s", excName); + WriteLineToLog("Exception Description: %s", desc); + + + cry_strcpy(m_excDesc, excDesc); + cry_strcpy(m_excAddr, excAddr); + cry_strcpy(m_excCode, excCode); + + + char errs[32768]; + sprintf_s(errs, "Exception Code: %s\nException Addr: %s\nException Module: %s\nException Description: %s, %s\n", + excCode, excAddr, m_excModule, excName, desc); + + + cry_strcat(errs, "\nCall Stack Trace:\n"); + + std::vector funcs; + { + AZ::Debug::StackFrame frames[25]; + AZ::Debug::SymbolStorage::StackLine lines[AZ_ARRAY_SIZE(frames)]; + unsigned int numFrames = AZ::Debug::StackRecorder::Record(frames, AZ_ARRAY_SIZE(frames), 3); + if (numFrames) + { + AZ::Debug::SymbolStorage::DecodeFrames(frames, numFrames, lines); + for (unsigned int i = 0; i < numFrames; i++) + { + funcs.push_back(lines[i]); + } + } + dumpCallStack(funcs); + // Fill call stack. + char str[s_iCallStackSize]; + cry_strcpy(str, ""); + for (unsigned int i = 0; i < funcs.size(); i++) + { + char temp[s_iCallStackSize]; + sprintf_s(temp, "%2zd) %s", funcs.size() - i, (const char*)funcs[i].c_str()); + cry_strcat(str, temp); + cry_strcat(str, "\r\n"); + cry_strcat(errs, temp); + cry_strcat(errs, "\n"); + } + cry_strcpy(m_excCallstack, str); + } + + cry_strcat(errorString, errs); + + if (f) + { + fwrite(errorString, strlen(errorString), 1, f); + { + if (g_cvars.sys_dump_aux_threads) + { + for (int i = 0; i < g_nDebugThreads; i++) + { + if (g_idDebugThreads[i] != GetCurrentThreadId()) + { + fprintf(f, "\n\nSuspended thread (%s):\n", g_nameDebugThreads[i]); + HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, TRUE, g_idDebugThreads[i]); + + // mirrors the AZ::Debug::Trace::PrintCallstack() functionality, but prints to a file + { + AZ::Debug::StackFrame frames[10]; + + // Without StackFrame explicit alignment frames array is aligned to 4 bytes + // which causes the stack tracing to fail. + AZ::Debug::SymbolStorage::StackLine lines[AZ_ARRAY_SIZE(frames)]; + + unsigned int numFrames = AZ::Debug::StackRecorder::Record(frames, AZ_ARRAY_SIZE(frames), 0, hThread); + if (numFrames) + { + AZ::Debug::SymbolStorage::DecodeFrames(frames, numFrames, lines); + for (unsigned int i2 = 0; i2 < numFrames; ++i2) + { + fprintf(f, "%2d) %s\n", numFrames - i2, lines[i2]); + } + } + } + + ResumeThread(hThread); + } + } + } + } + fflush(f); + fclose(f); + } + + if (pex) + { + MINIDUMP_TYPE mdumpValue; + bool bDump = true; + switch (g_cvars.sys_dump_type) + { + case 0: + bDump = false; + break; + case 1: + mdumpValue = MiniDumpNormal; + break; + case 2: + mdumpValue = (MINIDUMP_TYPE)(MiniDumpWithIndirectlyReferencedMemory | MiniDumpWithDataSegs); + break; + case 3: + mdumpValue = MiniDumpWithFullMemory; + break; + default: + mdumpValue = (MINIDUMP_TYPE)g_cvars.sys_dump_type; + break; + } + if (bDump) + { + fileName = path + "error.dmp"; + + if (gEnv->IsDedicated() && stat(fileName.c_str(), &fileInfo) == 0) + { + // Backup dump (use timestamp from error.log if available) + if (timeStamp.empty()) + { + tm creationTime; + localtime_s(&creationTime, &fileInfo.st_mtime); + char tempBuffer[32]; + strftime(tempBuffer, sizeof(tempBuffer), "%d %b %Y (%H %M %S)", &creationTime); + timeStamp = tempBuffer; + } + + string backupFileName = backupPath + timeStamp + " error.dmp"; + CopyFile(fileName.c_str(), backupFileName.c_str(), true); + } + + CryEngineExceptionFilterMiniDump(pex, fileName.c_str(), mdumpValue); + } + } + + //if no crash dialog don't even submit the bug + if (m_postBackupProcess && g_cvars.sys_no_crash_dialog == 0 && g_bUserDialog) + { + m_postBackupProcess(); + } + else + { + // lawsonn: Disabling the JIRA-based crash reporter for now + // we'll need to deal with it our own way, pending QA. + // if you're customizing the engine this is also your opportunity to deal with it. + if (g_cvars.sys_no_crash_dialog != 0 || !g_bUserDialog) + { + // ------------ place custom crash handler here --------------------- + // it should launch an executable! + /// by this time, error.bmp will be in the engine root folder + // error.log and error.dmp will also be present in the engine root folder + // if your error dumper wants those, it should zip them up and send them or offer to do so. + // ------------------------------------------------------------------ + } + } + const bool bQuitting = !gEnv || !gEnv->pSystem || gEnv->pSystem->IsQuitting(); + + //[AlexMcC|16.04.10] When the engine is shutting down, MessageBox doesn't display a box + // and immediately returns IDYES. Avoid this by just not trying to save if we're quitting. + // Don't ask to save if this isn't a real crash (a real crash has exception pointers) + if (g_cvars.sys_no_crash_dialog == 0 && g_bUserDialog && gEnv->IsEditor() && !bQuitting && pex) + { + BackupCurrentLevel(); + + const INT_PTR res = DialogBoxParam(gDLLHandle, MAKEINTRESOURCE(IDD_CONFIRM_SAVE_LEVEL), NULL, DebugCallStack::ConfirmSaveDialogProc, NULL); + if (res == IDB_CONFIRM_SAVE) + { + if (SaveCurrentLevel()) + { + MessageBox(NULL, "Level has been successfully saved!\r\nPress Ok to terminate Editor.", "Save", MB_OK); + } + else + { + MessageBox(NULL, "Error saving level.\r\nPress Ok to terminate Editor.", "Save", MB_OK | MB_ICONWARNING); + } + } + } + + if (g_cvars.sys_no_crash_dialog != 0 || !g_bUserDialog) + { + // terminate immediately - since we're in a crash, there is no point unwinding stack, we've already done access violation or worse. + // calling exit will only cause further death down the line... + TerminateProcess(GetCurrentProcess(), pex->ExceptionRecord->ExceptionCode); + } +} + + +INT_PTR CALLBACK DebugCallStack::ExceptionDialogProc(HWND hwndDlg, UINT message, WPARAM wParam, LPARAM lParam) +{ + static EXCEPTION_POINTERS* pex; + + static char errorString[32768] = ""; + + switch (message) + { + case WM_INITDIALOG: + { + pex = (EXCEPTION_POINTERS*)lParam; + HWND h; + + if (pex->ExceptionRecord->ExceptionFlags & EXCEPTION_NONCONTINUABLE) + { + // Disable continue button for non continuable exceptions. + //h = GetDlgItem( hwndDlg,IDB_CONTINUE ); + //if (h) EnableWindow( h,FALSE ); + } + + DebugCallStack* pDCS = static_cast(DebugCallStack::instance()); + + h = GetDlgItem(hwndDlg, IDC_EXCEPTION_DESC); + if (h) + { + SendMessage(h, EM_REPLACESEL, FALSE, (LONG_PTR)pDCS->m_excDesc); + } + + h = GetDlgItem(hwndDlg, IDC_EXCEPTION_CODE); + if (h) + { + SendMessage(h, EM_REPLACESEL, FALSE, (LONG_PTR)pDCS->m_excCode); + } + + h = GetDlgItem(hwndDlg, IDC_EXCEPTION_MODULE); + if (h) + { + SendMessage(h, EM_REPLACESEL, FALSE, (LONG_PTR)pDCS->m_excModule); + } + + h = GetDlgItem(hwndDlg, IDC_EXCEPTION_ADDRESS); + if (h) + { + SendMessage(h, EM_REPLACESEL, FALSE, (LONG_PTR)pDCS->m_excAddr); + } + + // Fill call stack. + HWND callStack = GetDlgItem(hwndDlg, IDC_CALLSTACK); + if (callStack) + { + SendMessage(callStack, WM_SETTEXT, FALSE, (LPARAM)pDCS->m_excCallstack); + } + + if (hwndException) + { + DestroyWindow(hwndException); + hwndException = 0; + } + + if (IsFloatingPointException(pex)) + { + EnableWindow(GetDlgItem(hwndDlg, IDB_IGNORE), TRUE); + } + } + break; + + case WM_COMMAND: + switch (LOWORD(wParam)) + { + case IDB_EXIT: + case IDB_IGNORE: + // Fall through. + + EndDialog(hwndDlg, wParam); + return TRUE; + } + } + return FALSE; +} + +INT_PTR CALLBACK DebugCallStack::ConfirmSaveDialogProc(HWND hwndDlg, UINT message, WPARAM wParam, [[maybe_unused]] LPARAM lParam) +{ + switch (message) + { + case WM_INITDIALOG: + { + // The user might be holding down the spacebar while the engine crashes. + // If we don't remove keyboard focus from this dialog, the keypress will + // press the default button before the dialog actually appears, even if + // the user has already released the key, which is bad. + SetFocus(NULL); + } break; + case WM_COMMAND: + { + switch (LOWORD(wParam)) + { + case IDB_CONFIRM_SAVE: // Fall through + case IDB_DONT_SAVE: + { + EndDialog(hwndDlg, wParam); + return TRUE; + } + } + } break; + } + + return FALSE; +} + +bool DebugCallStack::BackupCurrentLevel() +{ + CSystem* pSystem = static_cast(m_pSystem); + if (pSystem && pSystem->GetUserCallback()) + { + return pSystem->GetUserCallback()->OnBackupDocument(); + } + + return false; +} + +bool DebugCallStack::SaveCurrentLevel() +{ + CSystem* pSystem = static_cast(m_pSystem); + if (pSystem && pSystem->GetUserCallback()) + { + return pSystem->GetUserCallback()->OnSaveDocument(); + } + + return false; +} + +int DebugCallStack::SubmitBug(EXCEPTION_POINTERS* exception_pointer) +{ + int ret = IDB_EXIT; + + assert(!hwndException); + + RemoveOldFiles(); + + AZ::Debug::Trace::PrintCallstack("", 2); + + LogExceptionInfo(exception_pointer); + + if (IsFloatingPointException(exception_pointer)) + { + //! Print exception dialog. + ret = PrintException(exception_pointer); + } + + return ret; +} + +void DebugCallStack::ResetFPU(EXCEPTION_POINTERS* pex) +{ + if (IsFloatingPointException(pex)) + { + // How to reset FPU: http://www.experts-exchange.com/Programming/System/Windows__Programming/Q_10310953.html + _clearfp(); +#ifndef WIN64 + pex->ContextRecord->FloatSave.ControlWord |= 0x2F; + pex->ContextRecord->FloatSave.StatusWord &= ~0x8080; +#endif + } +} + +string DebugCallStack::GetModuleNameForAddr(void* addr) +{ + if (m_modules.empty()) + { + return "[unknown]"; + } + + if (addr < m_modules.begin()->first) + { + return "[unknown]"; + } + + TModules::const_iterator it = m_modules.begin(); + TModules::const_iterator end = m_modules.end(); + for (; ++it != end; ) + { + if (addr < it->first) + { + return (--it)->second; + } + } + + //if address is higher than the last module, we simply assume it is in the last module. + return m_modules.rbegin()->second; +} + +void DebugCallStack::GetProcNameForAddr(void* addr, string& procName, void*& baseAddr, string& filename, int& line) +{ + AZ::Debug::SymbolStorage::StackLine func, file, module; + AZ::Debug::SymbolStorage::FindFunctionFromIP(addr, &func, &file, &module, line, baseAddr); + procName = func; + filename = file; +} + +string DebugCallStack::GetCurrentFilename() +{ + char fullpath[MAX_PATH_LENGTH + 1]; + GetModuleFileName(NULL, fullpath, MAX_PATH_LENGTH); + return fullpath; +} + +static bool IsFloatingPointException(EXCEPTION_POINTERS* pex) +{ + if (!pex) + { + return false; + } + + DWORD exceptionCode = pex->ExceptionRecord->ExceptionCode; + switch (exceptionCode) + { + case EXCEPTION_FLT_DENORMAL_OPERAND: + case EXCEPTION_FLT_DIVIDE_BY_ZERO: + case EXCEPTION_FLT_INEXACT_RESULT: + case EXCEPTION_FLT_INVALID_OPERATION: + case EXCEPTION_FLT_OVERFLOW: + case EXCEPTION_FLT_UNDERFLOW: + case STATUS_FLOAT_MULTIPLE_FAULTS: + case STATUS_FLOAT_MULTIPLE_TRAPS: + return true; + + default: + return false; + } +} + +int DebugCallStack::PrintException(EXCEPTION_POINTERS* exception_pointer) +{ + return (int)DialogBoxParam(gDLLHandle, MAKEINTRESOURCE(IDD_CRITICAL_ERROR), NULL, DebugCallStack::ExceptionDialogProc, (LPARAM)exception_pointer); +} + +#else +void MarkThisThreadForDebugging(const char*) {} +void UnmarkThisThreadFromDebugging() {} +void UpdateFPExceptionsMaskForThreads() {} +#endif //WIN32 diff --git a/Code/CryEngine/CrySystem/DebugCallStack.h b/Code/CryEngine/CrySystem/DebugCallStack.h new file mode 100644 index 0000000000..c37e6ba0d4 --- /dev/null +++ b/Code/CryEngine/CrySystem/DebugCallStack.h @@ -0,0 +1,95 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ +// Original file Copyright Crytek GMBH or its affiliates, used under license. + +#ifndef CRYINCLUDE_CRYSYSTEM_DEBUGCALLSTACK_H +#define CRYINCLUDE_CRYSYSTEM_DEBUGCALLSTACK_H +#pragma once + + +#include "IDebugCallStack.h" + +#if defined (WIN32) || defined (WIN64) + +//! Limits the maximal number of functions in call stack. +const int MAX_DEBUG_STACK_ENTRIES_FILE_DUMP = 12; + +struct ISystem; + +//!============================================================================ +//! +//! DebugCallStack class, capture call stack information from symbol files. +//! +//!============================================================================ +class DebugCallStack + : public IDebugCallStack +{ +public: + DebugCallStack(); + virtual ~DebugCallStack(); + + ISystem* GetSystem() { return m_pSystem; }; + + virtual string GetModuleNameForAddr(void* addr); + virtual void GetProcNameForAddr(void* addr, string& procName, void*& baseAddr, string& filename, int& line); + virtual string GetCurrentFilename(); + + void installErrorHandler(ISystem* pSystem); + virtual int handleException(EXCEPTION_POINTERS* exception_pointer); + + virtual void ReportBug(const char*); + + void dumpCallStack(std::vector& functions); + + void SetUserDialogEnable(const bool bUserDialogEnable); + + typedef std::map TModules; +protected: + static void RemoveOldFiles(); + static void RemoveFile(const char* szFileName); + + static int PrintException(EXCEPTION_POINTERS* exception_pointer); + static INT_PTR CALLBACK ExceptionDialogProc(HWND hwndDlg, UINT message, WPARAM wParam, LPARAM lParam); + static INT_PTR CALLBACK ConfirmSaveDialogProc(HWND hwndDlg, UINT message, WPARAM wParam, LPARAM lParam); + + void LogExceptionInfo(EXCEPTION_POINTERS* exception_pointer); + bool BackupCurrentLevel(); + bool SaveCurrentLevel(); + int SubmitBug(EXCEPTION_POINTERS* exception_pointer); + void ResetFPU(EXCEPTION_POINTERS* pex); + + static const int s_iCallStackSize = 32768; + + char m_excLine[256]; + char m_excModule[128]; + + char m_excDesc[MAX_WARNING_LENGTH]; + char m_excCode[MAX_WARNING_LENGTH]; + char m_excAddr[80]; + char m_excCallstack[s_iCallStackSize]; + + void* prevExceptionHandler; + + bool m_bCrash; + const char* m_szBugMessage; + + ISystem* m_pSystem; + + int m_nSkipNumFunctions; + CONTEXT m_context; + + TModules m_modules; +}; + +#endif //WIN32 + +#endif // CRYINCLUDE_CRYSYSTEM_DEBUGCALLSTACK_H diff --git a/Code/CryEngine/CrySystem/DllMain.cpp b/Code/CryEngine/CrySystem/DllMain.cpp index 7fd620835b..53593821d9 100644 --- a/Code/CryEngine/CrySystem/DllMain.cpp +++ b/Code/CryEngine/CrySystem/DllMain.cpp @@ -14,6 +14,7 @@ #include "CrySystem_precompiled.h" #include "System.h" #include +#include "DebugCallStack.h" #if defined(AZ_RESTRICTED_PLATFORM) #undef AZ_RESTRICTED_SECTION @@ -87,6 +88,16 @@ CRYSYSTEM_API ISystem* CreateSystemInterface(const SSystemInitParams& startupPar startupParams.pUserCallback->OnSystemConnect(pSystem); } +#if defined(WIN32) + // Environment Variable to signal we don't want to override our exception handler - our crash report system will set this + auto envVar = AZ::Environment::FindVariable("ExceptionHandlerIsSet"); + const bool handlerIsSet = (envVar && *envVar); + if (!handlerIsSet) + { + ((DebugCallStack*)IDebugCallStack::instance())->installErrorHandler(pSystem); + } +#endif + bool retVal = false; { AZ::Debug::StartupLogSinkReporter initLogSink; diff --git a/Code/CryEngine/CrySystem/IDebugCallStack.cpp b/Code/CryEngine/CrySystem/IDebugCallStack.cpp new file mode 100644 index 0000000000..c14dd2b0da --- /dev/null +++ b/Code/CryEngine/CrySystem/IDebugCallStack.cpp @@ -0,0 +1,275 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ +// Original file Copyright Crytek GMBH or its affiliates, used under license. + +// Description : A multiplatform base class for handling errors and collecting call stacks + + +#include "CrySystem_precompiled.h" +#include "IDebugCallStack.h" +#include "System.h" +#include +#include +#include +#include +//#if !defined(LINUX) + +#include + +const char* const IDebugCallStack::s_szFatalErrorCode = "FATAL_ERROR"; + +IDebugCallStack::IDebugCallStack() + : m_bIsFatalError(false) + , m_postBackupProcess(0) + , m_memAllocFileHandle(AZ::IO::InvalidHandle) +{ +} + +IDebugCallStack::~IDebugCallStack() +{ + StopMemLog(); +} + +#if AZ_LEGACY_CRYSYSTEM_TRAIT_DEBUGCALLSTACK_SINGLETON +IDebugCallStack* IDebugCallStack::instance() +{ + static IDebugCallStack sInstance; + return &sInstance; +} +#endif + +void IDebugCallStack::FileCreationCallback(void (* postBackupProcess)()) +{ + m_postBackupProcess = postBackupProcess; +} +////////////////////////////////////////////////////////////////////////// +void IDebugCallStack::LogCallstack() +{ + AZ::Debug::Trace::PrintCallstack("", 2); +} + +const char* IDebugCallStack::TranslateExceptionCode(DWORD dwExcept) +{ + switch (dwExcept) + { +#if AZ_LEGACY_CRYSYSTEM_TRAIT_DEBUGCALLSTACK_TRANSLATE + case EXCEPTION_ACCESS_VIOLATION: + return "EXCEPTION_ACCESS_VIOLATION"; + break; + case EXCEPTION_DATATYPE_MISALIGNMENT: + return "EXCEPTION_DATATYPE_MISALIGNMENT"; + break; + case EXCEPTION_BREAKPOINT: + return "EXCEPTION_BREAKPOINT"; + break; + case EXCEPTION_SINGLE_STEP: + return "EXCEPTION_SINGLE_STEP"; + break; + case EXCEPTION_ARRAY_BOUNDS_EXCEEDED: + return "EXCEPTION_ARRAY_BOUNDS_EXCEEDED"; + break; + case EXCEPTION_FLT_DENORMAL_OPERAND: + return "EXCEPTION_FLT_DENORMAL_OPERAND"; + break; + case EXCEPTION_FLT_DIVIDE_BY_ZERO: + return "EXCEPTION_FLT_DIVIDE_BY_ZERO"; + break; + case EXCEPTION_FLT_INEXACT_RESULT: + return "EXCEPTION_FLT_INEXACT_RESULT"; + break; + case EXCEPTION_FLT_INVALID_OPERATION: + return "EXCEPTION_FLT_INVALID_OPERATION"; + break; + case EXCEPTION_FLT_OVERFLOW: + return "EXCEPTION_FLT_OVERFLOW"; + break; + case EXCEPTION_FLT_STACK_CHECK: + return "EXCEPTION_FLT_STACK_CHECK"; + break; + case EXCEPTION_FLT_UNDERFLOW: + return "EXCEPTION_FLT_UNDERFLOW"; + break; + case EXCEPTION_INT_DIVIDE_BY_ZERO: + return "EXCEPTION_INT_DIVIDE_BY_ZERO"; + break; + case EXCEPTION_INT_OVERFLOW: + return "EXCEPTION_INT_OVERFLOW"; + break; + case EXCEPTION_PRIV_INSTRUCTION: + return "EXCEPTION_PRIV_INSTRUCTION"; + break; + case EXCEPTION_IN_PAGE_ERROR: + return "EXCEPTION_IN_PAGE_ERROR"; + break; + case EXCEPTION_ILLEGAL_INSTRUCTION: + return "EXCEPTION_ILLEGAL_INSTRUCTION"; + break; + case EXCEPTION_NONCONTINUABLE_EXCEPTION: + return "EXCEPTION_NONCONTINUABLE_EXCEPTION"; + break; + case EXCEPTION_STACK_OVERFLOW: + return "EXCEPTION_STACK_OVERFLOW"; + break; + case EXCEPTION_INVALID_DISPOSITION: + return "EXCEPTION_INVALID_DISPOSITION"; + break; + case EXCEPTION_GUARD_PAGE: + return "EXCEPTION_GUARD_PAGE"; + break; + case EXCEPTION_INVALID_HANDLE: + return "EXCEPTION_INVALID_HANDLE"; + break; + //case EXCEPTION_POSSIBLE_DEADLOCK: return "EXCEPTION_POSSIBLE_DEADLOCK"; break ; + + case STATUS_FLOAT_MULTIPLE_FAULTS: + return "STATUS_FLOAT_MULTIPLE_FAULTS"; + break; + case STATUS_FLOAT_MULTIPLE_TRAPS: + return "STATUS_FLOAT_MULTIPLE_TRAPS"; + break; + + +#endif + default: + return "Unknown"; + break; + } +} + +void IDebugCallStack::PutVersion(char* str, size_t length) +{ +AZ_PUSH_DISABLE_WARNING(4996, "-Wunknown-warning-option") + + if (!gEnv || !gEnv->pSystem) + { + return; + } + + char sFileVersion[128]; + gEnv->pSystem->GetFileVersion().ToString(sFileVersion, sizeof(sFileVersion)); + + char sProductVersion[128]; + gEnv->pSystem->GetProductVersion().ToString(sProductVersion, sizeof(sFileVersion)); + + + //! Get time. + time_t ltime; + time(<ime); + tm* today = localtime(<ime); + + char s[1024]; + //! Use strftime to build a customized time string. + strftime(s, 128, "Logged at %#c\n", today); + azstrcat(str, length, s); + sprintf_s(s, "FileVersion: %s\n", sFileVersion); + azstrcat(str, length, s); + sprintf_s(s, "ProductVersion: %s\n", sProductVersion); + azstrcat(str, length, s); + + if (gEnv->pLog) + { + const char* logfile = gEnv->pLog->GetFileName(); + if (logfile) + { + sprintf (s, "LogFile: %s\n", logfile); + azstrcat(str, length, s); + } + } + + AZ::IO::FixedMaxPathString projectPath = AZ::Utils::GetProjectPath(); + azstrcat(str, length, "ProjectDir: "); + azstrcat(str, length, projectPath.c_str()); + azstrcat(str, length, "\n"); + +#if AZ_LEGACY_CRYSYSTEM_TRAIT_DEBUGCALLSTACK_APPEND_MODULENAME + GetModuleFileNameA(NULL, s, sizeof(s)); + + // Log EXE filename only if possible (not full EXE path which could contain sensitive info) + AZStd::string exeName; + if (AZ::StringFunc::Path::GetFullFileName(s, exeName)) + { + azstrcat(str, length, "Executable: "); + azstrcat(str, length, exeName.c_str()); + +# ifdef AZ_DEBUG_BUILD + azstrcat(str, length, " (debug: yes"); +# else + azstrcat(str, length, " (debug: no"); +# endif + } +#endif +AZ_POP_DISABLE_WARNING +} + + +//Crash the application, in this way the debug callstack routine will be called and it will create all the necessary files (error.log, dump, and eventually screenshot) +void IDebugCallStack::FatalError(const char* description) +{ + m_bIsFatalError = true; + WriteLineToLog(description); + +#ifndef _RELEASE + bool bShowDebugScreen = g_cvars.sys_no_crash_dialog == 0; + // showing the debug screen is not safe when not called from mainthread + // it normally leads to a infinity recursion followed by a stack overflow, preventing + // useful call stacks, thus they are disabled + bShowDebugScreen = bShowDebugScreen && gEnv->mMainThreadId == CryGetCurrentThreadId(); + if (bShowDebugScreen) + { + EBUS_EVENT(AZ::NativeUI::NativeUIRequestBus, DisplayOkDialog, "Open 3D Engine Fatal Error", description, false); + } +#endif + +#if defined(WIN32) || !defined(_RELEASE) + int* p = 0x0; + PREFAST_SUPPRESS_WARNING(6011) * p = 1; // we're intentionally crashing here +#endif +} + +void IDebugCallStack::WriteLineToLog(const char* format, ...) +{ + va_list ArgList; + char szBuffer[MAX_WARNING_LENGTH]; + va_start(ArgList, format); + vsnprintf_s(szBuffer, sizeof(szBuffer), sizeof(szBuffer) - 1, format, ArgList); + cry_strcat(szBuffer, "\n"); + szBuffer[sizeof(szBuffer) - 1] = '\0'; + va_end(ArgList); + + AZ::IO::HandleType fileHandle = AZ::IO::InvalidHandle; + AZ::IO::FileIOBase::GetDirectInstance()->Open("@Log@\\error.log", AZ::IO::GetOpenModeFromStringMode("a+t"), fileHandle); + if (fileHandle != AZ::IO::InvalidHandle) + { + AZ::IO::FileIOBase::GetDirectInstance()->Write(fileHandle, szBuffer, strlen(szBuffer)); + AZ::IO::FileIOBase::GetDirectInstance()->Flush(fileHandle); + AZ::IO::FileIOBase::GetDirectInstance()->Close(fileHandle); + } +} + +////////////////////////////////////////////////////////////////////////// +void IDebugCallStack::StartMemLog() +{ + AZ::IO::FileIOBase::GetDirectInstance()->Open("@Log@\\memallocfile.log", AZ::IO::OpenMode::ModeWrite, m_memAllocFileHandle); + + assert(m_memAllocFileHandle != AZ::IO::InvalidHandle); +} + +////////////////////////////////////////////////////////////////////////// +void IDebugCallStack::StopMemLog() +{ + if (m_memAllocFileHandle != AZ::IO::InvalidHandle) + { + AZ::IO::FileIOBase::GetDirectInstance()->Close(m_memAllocFileHandle); + m_memAllocFileHandle = AZ::IO::InvalidHandle; + } +} +//#endif //!defined(LINUX) diff --git a/Code/CryEngine/CrySystem/IDebugCallStack.h b/Code/CryEngine/CrySystem/IDebugCallStack.h new file mode 100644 index 0000000000..f181b73913 --- /dev/null +++ b/Code/CryEngine/CrySystem/IDebugCallStack.h @@ -0,0 +1,90 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ +// Original file Copyright Crytek GMBH or its affiliates, used under license. + +// Description : A multiplatform base class for handling errors and collecting call stacks + +#ifndef CRYINCLUDE_CRYSYSTEM_IDEBUGCALLSTACK_H +#define CRYINCLUDE_CRYSYSTEM_IDEBUGCALLSTACK_H +#pragma once + +#include "System.h" + +#if AZ_LEGACY_CRYSYSTEM_TRAIT_FORWARD_EXCEPTION_POINTERS +struct EXCEPTION_POINTERS; +#endif +//! Limits the maximal number of functions in call stack. +enum +{ + MAX_DEBUG_STACK_ENTRIES = 80 +}; + +class IDebugCallStack +{ +public: + // Returns single instance of DebugStack + static IDebugCallStack* instance(); + + virtual int handleException([[maybe_unused]] EXCEPTION_POINTERS* exception_pointer){return 0; } + + // returns the module name of a given address + virtual string GetModuleNameForAddr([[maybe_unused]] void* addr) { return "[unknown]"; } + + // returns the function name of a given address together with source file and line number (if available) of a given address + virtual void GetProcNameForAddr(void* addr, string& procName, void*& baseAddr, string& filename, int& line) + { + filename = "[unknown]"; + line = 0; + baseAddr = addr; +#if defined(PLATFORM_64BIT) + procName.Format("[%016llX]", addr); +#else + procName.Format("[%08X]", addr); +#endif + } + + // returns current filename + virtual string GetCurrentFilename() { return "[unknown]"; } + + //! Dumps Current Call Stack to log. + virtual void LogCallstack(); + //triggers a fatal error, so the DebugCallstack can create the error.log and terminate the application + void FatalError(const char*); + + //Reports a bug and continues execution + virtual void ReportBug(const char*) {} + + virtual void FileCreationCallback(void (* postBackupProcess)()); + + static void WriteLineToLog(const char* format, ...); + + virtual void StartMemLog(); + virtual void StopMemLog(); + +protected: + IDebugCallStack(); + virtual ~IDebugCallStack(); + + static const char* TranslateExceptionCode(DWORD dwExcept); + static void PutVersion(char* str, size_t length); + + bool m_bIsFatalError; + static const char* const s_szFatalErrorCode; + + void (* m_postBackupProcess)(); + + AZ::IO::HandleType m_memAllocFileHandle; +}; + + + +#endif // CRYINCLUDE_CRYSYSTEM_IDEBUGCALLSTACK_H diff --git a/Code/CryEngine/CrySystem/System.h b/Code/CryEngine/CrySystem/System.h index 631d84d934..b91b1ba059 100644 --- a/Code/CryEngine/CrySystem/System.h +++ b/Code/CryEngine/CrySystem/System.h @@ -208,6 +208,7 @@ struct SSystemCVars int sys_no_crash_dialog; int sys_no_error_report_window; int sys_dump_aux_threads; + int sys_WER; int sys_dump_type; int sys_ai; int sys_entitysystem; diff --git a/Code/CryEngine/CrySystem/SystemInit.cpp b/Code/CryEngine/CrySystem/SystemInit.cpp index a2c21ea04c..25d6b9c601 100644 --- a/Code/CryEngine/CrySystem/SystemInit.cpp +++ b/Code/CryEngine/CrySystem/SystemInit.cpp @@ -121,6 +121,10 @@ # include #endif +#ifdef WIN32 +extern LONG WINAPI CryEngineExceptionFilterWER(struct _EXCEPTION_POINTERS* pExceptionPointers); +#endif + #if defined(AZ_RESTRICTED_PLATFORM) #define AZ_RESTRICTED_SECTION SYSTEMINIT_CPP_SECTION_14 #include AZ_RESTRICTED_FILE(SystemInit_cpp) @@ -1484,6 +1488,13 @@ AZ_POP_DISABLE_WARNING InlineInitializationProcessing("CSystem::Init LoadConfigurations"); +#ifdef WIN32 + if (g_cvars.sys_WER) + { + SetUnhandledExceptionFilter(CryEngineExceptionFilterWER); + } +#endif + ////////////////////////////////////////////////////////////////////////// // Localization ////////////////////////////////////////////////////////////////////////// @@ -2020,6 +2031,14 @@ void CSystem::CreateSystemVars() REGISTER_CVAR2("sys_update_profile_time", &g_cvars.sys_update_profile_time, 1.0f, 0, "Time to keep updates timings history for."); REGISTER_CVAR2("sys_no_crash_dialog", &g_cvars.sys_no_crash_dialog, m_bNoCrashDialog, VF_NULL, "Whether to disable the crash dialog window"); REGISTER_CVAR2("sys_no_error_report_window", &g_cvars.sys_no_error_report_window, m_bNoErrorReportWindow, VF_NULL, "Whether to disable the error report list"); +#if defined(_RELEASE) + if (!gEnv->IsDedicated()) + { + REGISTER_CVAR2("sys_WER", &g_cvars.sys_WER, 1, 0, "Enables Windows Error Reporting"); + } +#else + REGISTER_CVAR2("sys_WER", &g_cvars.sys_WER, 0, 0, "Enables Windows Error Reporting"); +#endif #ifdef USE_HTTP_WEBSOCKETS REGISTER_CVAR2("sys_simple_http_base_port", &g_cvars.sys_simple_http_base_port, 1880, VF_REQUIRE_APP_RESTART, diff --git a/Code/CryEngine/CrySystem/SystemWin32.cpp b/Code/CryEngine/CrySystem/SystemWin32.cpp index c974365bfc..eb6aa17532 100644 --- a/Code/CryEngine/CrySystem/SystemWin32.cpp +++ b/Code/CryEngine/CrySystem/SystemWin32.cpp @@ -46,6 +46,8 @@ #include #endif +#include "IDebugCallStack.h" + #if defined(APPLE) || defined(LINUX) #include #endif @@ -355,6 +357,7 @@ void CSystem::FatalError(const char* format, ...) } // Dump callstack. + IDebugCallStack::instance()->FatalError(szBuffer); #endif CryDebugBreak(); @@ -396,6 +399,8 @@ void CSystem::ReportBug([[maybe_unused]] const char* format, ...) va_start(ArgList, format); azvsnprintf(szBuffer + strlen(sPrefix), MAX_WARNING_LENGTH - strlen(sPrefix), format, ArgList); va_end(ArgList); + + IDebugCallStack::instance()->ReportBug(szBuffer); #endif } diff --git a/Code/CryEngine/CrySystem/WindowsErrorReporting.cpp b/Code/CryEngine/CrySystem/WindowsErrorReporting.cpp new file mode 100644 index 0000000000..feaffd42aa --- /dev/null +++ b/Code/CryEngine/CrySystem/WindowsErrorReporting.cpp @@ -0,0 +1,137 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ +// Original file Copyright Crytek GMBH or its affiliates, used under license. + +// Description : Support for Windows Error Reporting (WER) + + +#include "CrySystem_precompiled.h" + +#ifdef WIN32 + +#include "System.h" +#include +#include +#include "errorrep.h" +#include "ISystem.h" + +#include + +static WCHAR szPath[MAX_PATH + 1]; +static WCHAR szFR[] = L"\\System32\\FaultRep.dll"; + +WCHAR* GetFullPathToFaultrepDll(void) +{ + UINT rc = GetSystemWindowsDirectoryW(szPath, ARRAYSIZE(szPath)); + if (rc == 0 || rc > ARRAYSIZE(szPath) - ARRAYSIZE(szFR) - 1) + { + return NULL; + } + + wcscat_s(szPath, szFR); + return szPath; +} + + +typedef BOOL (WINAPI * MINIDUMPWRITEDUMP)(HANDLE hProcess, DWORD dwPid, HANDLE hFile, MINIDUMP_TYPE DumpType, + CONST PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam, + CONST PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam, + CONST PMINIDUMP_CALLBACK_INFORMATION CallbackParam + ); + +////////////////////////////////////////////////////////////////////////// +LONG WINAPI CryEngineExceptionFilterMiniDump(struct _EXCEPTION_POINTERS* pExceptionPointers, const char* szDumpPath, MINIDUMP_TYPE DumpType) +{ + // note: In debug mode, this dll is loaded on startup anyway, so this should not incur an additional load unless it crashes + // very early during startup. + + fflush(nullptr); // according to MSDN on fflush, calling fflush on null flushes all buffers. + HMODULE hndDBGHelpDLL = LoadLibraryA("DBGHELP.DLL"); + + if (!hndDBGHelpDLL) + { + CryLogAlways("Failed to record DMP file: Could not open DBGHELP.DLL"); + return EXCEPTION_CONTINUE_SEARCH; + } + + MINIDUMPWRITEDUMP dumpFnPtr = (MINIDUMPWRITEDUMP)::GetProcAddress(hndDBGHelpDLL, "MiniDumpWriteDump"); + if (!dumpFnPtr) + { + CryLogAlways("Failed to record DMP file: Unable to find MiniDumpWriteDump in DBGHELP.DLL"); + return EXCEPTION_CONTINUE_SEARCH; + } + + HANDLE hFile = ::CreateFile(szDumpPath, GENERIC_WRITE, FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + if (hFile == INVALID_HANDLE_VALUE) + { + CryLogAlways("Failed to record DMP file: could not open file '%s' for writing - error code: %d", szDumpPath, GetLastError()); + return EXCEPTION_CONTINUE_SEARCH; + } + + _MINIDUMP_EXCEPTION_INFORMATION ExInfo; + ExInfo.ThreadId = ::GetCurrentThreadId(); + ExInfo.ExceptionPointers = pExceptionPointers; + ExInfo.ClientPointers = NULL; + + BOOL bOK = dumpFnPtr(GetCurrentProcess(), GetCurrentProcessId(), hFile, DumpType, &ExInfo, NULL, NULL); + ::CloseHandle(hFile); + + if (bOK) + { + CryLogAlways("Successfully recorded DMP file: '%s'", szDumpPath); + return EXCEPTION_EXECUTE_HANDLER; // SUCCESS! you can execute your handlers now + } + else + { + CryLogAlways("Failed to record DMP file: '%s' - error code: %d", szDumpPath, GetLastError()); + } + + return EXCEPTION_CONTINUE_SEARCH; +} + +////////////////////////////////////////////////////////////////////////// +LONG WINAPI CryEngineExceptionFilterWER(struct _EXCEPTION_POINTERS* pExceptionPointers) +{ + if (g_cvars.sys_WER > 1) + { + char szScratch [_MAX_PATH]; + const char* szDumpPath = gEnv->pCryPak->AdjustFileName("@log@/CE2Dump.dmp", szScratch, AZ_ARRAY_SIZE(szScratch), 0); + + MINIDUMP_TYPE mdumpValue = (MINIDUMP_TYPE)(MiniDumpNormal); + if (g_cvars.sys_WER > 1) + { + mdumpValue = (MINIDUMP_TYPE)(g_cvars.sys_WER - 2); + } + + return CryEngineExceptionFilterMiniDump(pExceptionPointers, szDumpPath, mdumpValue); + } + + LONG lRet = EXCEPTION_CONTINUE_SEARCH; + WCHAR* psz = GetFullPathToFaultrepDll(); + if (psz) + { + HMODULE hFaultRepDll = LoadLibraryW(psz); + if (hFaultRepDll) + { + pfn_REPORTFAULT pfn = (pfn_REPORTFAULT)GetProcAddress(hFaultRepDll, "ReportFault"); + if (pfn) + { + pfn(pExceptionPointers, 0); + lRet = EXCEPTION_EXECUTE_HANDLER; + } + FreeLibrary(hFaultRepDll); + } + } + return lRet; +} + +#endif // WIN32 diff --git a/Code/CryEngine/CrySystem/crysystem_files.cmake b/Code/CryEngine/CrySystem/crysystem_files.cmake index 6a56339b85..84250de95b 100644 --- a/Code/CryEngine/CrySystem/crysystem_files.cmake +++ b/Code/CryEngine/CrySystem/crysystem_files.cmake @@ -15,6 +15,8 @@ set(FILES CmdLineArg.cpp ConsoleBatchFile.cpp ConsoleHelpGen.cpp + DebugCallStack.cpp + IDebugCallStack.cpp Log.cpp System.cpp SystemCFG.cpp @@ -31,6 +33,8 @@ set(FILES CmdLineArg.h ConsoleBatchFile.h ConsoleHelpGen.h + DebugCallStack.h + IDebugCallStack.h Log.h SimpleStringPool.h CrySystem_precompiled.h @@ -72,4 +76,5 @@ set(FILES ViewSystem/ViewSystem.cpp ViewSystem/ViewSystem.h CrySystem_precompiled.cpp + WindowsErrorReporting.cpp ) diff --git a/Code/Framework/AtomCore/AtomCore/std/parallel/concurrency_checker.h b/Code/Framework/AtomCore/AtomCore/std/parallel/concurrency_checker.h index 311b574fdf..4a952721b6 100644 --- a/Code/Framework/AtomCore/AtomCore/std/parallel/concurrency_checker.h +++ b/Code/Framework/AtomCore/AtomCore/std/parallel/concurrency_checker.h @@ -20,10 +20,12 @@ namespace AZStd { - //! Simple class for verifying that no concurrent access is occuring. + //! Simple class for verifying that no concurrent access is occurring. //! This is *not* a synchronization primitive, and is intended simply for checking that no concurrency issues exist. //! It will be compiled out in release builds. //! Use concurrency_checker like a mutex (i.e. call soft_lock() and soft_unlock() around all instances of your data access). + //! Use soft_lock_shared and soft_unlock_shared around places where multiple threads are allowed to have read access + //! at the same time as long as nothing else already has a soft lock //! It will assert if there are multiple threads accessing the locked code/data at the same time. //! Expected use case is for defensive programming: when you do not expect any concurrent access within a system, //! but want to verify that it stays that way in the future, without incurring the overhead of a mutex. @@ -34,7 +36,7 @@ namespace AZStd { #ifdef AZ_CONCURRENCY_CHECKER_ENABLED uint32_t count = ++m_concurrencyCounter; - AZ_Assert(count == 1, "Concurrency check failed. Multiple threads are trying to access data at the same time, or there is a lock/unlock mismatch."); + AZ_Assert(count == 1 && m_sharedConcurrencyCounter == 0, "Concurrency check failed. Multiple threads are trying to access data at the same time, or there is a lock/unlock mismatch."); #endif } @@ -46,9 +48,27 @@ namespace AZStd #endif } + AZ_FORCE_INLINE void soft_lock_shared() + { +#ifdef AZ_CONCURRENCY_CHECKER_ENABLED + AZ_Assert(m_concurrencyCounter == 0, "Concurrency check failed. A soft_lock_shared was attempted when there was already a soft_lock."); + ++m_sharedConcurrencyCounter; +#endif + } + + AZ_FORCE_INLINE void soft_unlock_shared() + { +#ifdef AZ_CONCURRENCY_CHECKER_ENABLED + AZ_Assert(m_sharedConcurrencyCounter != 0, "Concurrency check failed. There is a shared_lock/shared_unlock mismatch."); + --m_sharedConcurrencyCounter; +#endif + } + + private: #ifdef AZ_CONCURRENCY_CHECKER_ENABLED AZStd::atomic_uint32_t m_concurrencyCounter = 0; + AZStd::atomic_uint32_t m_sharedConcurrencyCounter = 0; #endif }; diff --git a/Code/Framework/AtomCore/Tests/ArrayView.cpp b/Code/Framework/AtomCore/Tests/ArrayView.cpp index 83fa20aaf0..56627d280e 100644 --- a/Code/Framework/AtomCore/Tests/ArrayView.cpp +++ b/Code/Framework/AtomCore/Tests/ArrayView.cpp @@ -293,15 +293,12 @@ namespace UnitTest { array_view view({ 1,2,3,4 }); - UnitTest::TestRunner::Instance().StartAssertTests(); + AZ_TEST_START_TRACE_SUPPRESSION; - EXPECT_EQ(0, UnitTest::TestRunner::Instance().m_numAssertsFailed); view[4]; - EXPECT_EQ(1, UnitTest::TestRunner::Instance().m_numAssertsFailed); view[5]; - EXPECT_EQ(2, UnitTest::TestRunner::Instance().m_numAssertsFailed); - UnitTest::TestRunner::Instance().StopAssertTests(); + AZ_TEST_STOP_TRACE_SUPPRESSION(2); } } diff --git a/Code/Framework/AtomCore/Tests/ConcurrencyCheckerTests.cpp b/Code/Framework/AtomCore/Tests/ConcurrencyCheckerTests.cpp new file mode 100644 index 0000000000..7cd1fa27ac --- /dev/null +++ b/Code/Framework/AtomCore/Tests/ConcurrencyCheckerTests.cpp @@ -0,0 +1,100 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#include + +#include + +using namespace AZStd; + +namespace UnitTest +{ + class ConcurrencyCheckerTestFixture + : public AllocatorsTestFixture + { + + void SetUp() override + { + AllocatorsFixture::SetUp(); + } + }; + + TEST_F(AllocatorsTestFixture, SoftLock_NoContention_NoAsserts) + { + concurrency_checker concurrencyChecker; + concurrencyChecker.soft_lock(); + concurrencyChecker.soft_unlock(); + concurrencyChecker.soft_lock(); + concurrencyChecker.soft_unlock(); + } + + TEST_F(AllocatorsTestFixture, SoftLock_AlreadyLocked_Assert) + { + concurrency_checker concurrencyChecker; + concurrencyChecker.soft_lock(); + AZ_TEST_START_TRACE_SUPPRESSION; + concurrencyChecker.soft_lock(); + AZ_TEST_STOP_TRACE_SUPPRESSION(1); + } + + TEST_F(AllocatorsTestFixture, SoftUnlock_NotAlreadyLocked_Assert) + { + concurrency_checker concurrencyChecker; + concurrencyChecker.soft_lock(); + concurrencyChecker.soft_unlock(); + AZ_TEST_START_TRACE_SUPPRESSION; + concurrencyChecker.soft_unlock(); + AZ_TEST_STOP_TRACE_SUPPRESSION(1); + } + + TEST_F(AllocatorsTestFixture, SoftLockShared_NoContention_NoAsserts) + { + concurrency_checker concurrencyChecker; + // Multiple shared locks can be made at once, + // as long as they are all unlocked before the next soft_lock + concurrencyChecker.soft_lock_shared(); + concurrencyChecker.soft_lock_shared(); + concurrencyChecker.soft_unlock_shared(); + concurrencyChecker.soft_unlock_shared(); + + concurrencyChecker.soft_lock(); + concurrencyChecker.soft_unlock(); + + concurrencyChecker.soft_lock_shared(); + concurrencyChecker.soft_lock_shared(); + concurrencyChecker.soft_unlock_shared(); + concurrencyChecker.soft_unlock_shared(); + + concurrencyChecker.soft_lock(); + concurrencyChecker.soft_unlock(); + } + + TEST_F(AllocatorsTestFixture, SoftLockShared_SharedLockAfterSoftLock_Assert) + { + concurrency_checker concurrencyChecker; + + concurrencyChecker.soft_lock(); + AZ_TEST_START_TRACE_SUPPRESSION; + concurrencyChecker.soft_lock_shared(); + AZ_TEST_STOP_TRACE_SUPPRESSION(1); + } + + TEST_F(AllocatorsTestFixture, SoftUnlockShared_NotAlreadyLocked_Assert) + { + concurrency_checker concurrencyChecker; + concurrencyChecker.soft_lock_shared(); + concurrencyChecker.soft_unlock_shared(); + AZ_TEST_START_TRACE_SUPPRESSION; + concurrencyChecker.soft_unlock_shared(); + AZ_TEST_STOP_TRACE_SUPPRESSION(1); + } +} diff --git a/Code/Framework/AtomCore/Tests/atomcore_tests_files.cmake b/Code/Framework/AtomCore/Tests/atomcore_tests_files.cmake index 26efd09fe1..24d23cb852 100644 --- a/Code/Framework/AtomCore/Tests/atomcore_tests_files.cmake +++ b/Code/Framework/AtomCore/Tests/atomcore_tests_files.cmake @@ -11,6 +11,7 @@ set(FILES ArrayView.cpp + ConcurrencyCheckerTests.cpp InstanceDatabase.cpp JsonSerializationUtilsTests.cpp lru_cache.cpp diff --git a/Code/Framework/AzCore/AzCore/Asset/AssetCommon.h b/Code/Framework/AzCore/AzCore/Asset/AssetCommon.h index c5cdbbf331..11f4531124 100644 --- a/Code/Framework/AzCore/AzCore/Asset/AssetCommon.h +++ b/Code/Framework/AzCore/AzCore/Asset/AssetCommon.h @@ -307,6 +307,8 @@ namespace AZ Asset(AssetLoadBehavior loadBehavior = AssetLoadBehavior::Default); /// Create an asset from a valid asset data (created asset), might not be loaded or currently loading. Asset(AssetData* assetData, AssetLoadBehavior loadBehavior); + /// Create an asset from a valid asset data (created asset) and set the asset id for both, might not be loaded or currently loading. + Asset(const AZ::Data::AssetId& id, AssetData* assetData, AssetLoadBehavior loadBehavior); /// Initialize asset pointer with id, type, and hint. No data construction will occur until QueueLoad is called. Asset(const AZ::Data::AssetId& id, const AZ::Data::AssetType& type, const AZStd::string& hint = AZStd::string()); @@ -787,6 +789,18 @@ namespace AZ SetData(assetData); } + //========================================================================= + template + Asset::Asset(const AssetId& id, AssetData* assetData, AssetLoadBehavior loadBehavior) + : m_assetId(id) + , m_assetType(azrtti_typeid()) + , m_loadBehavior(loadBehavior) + { + AZ_Assert(!assetData->m_assetId.IsValid(), "Asset data already has an ID set."); + assetData->m_assetId = id; + SetData(assetData); + } + //========================================================================= template Asset::Asset(const AssetId& id, const AZ::Data::AssetType& type, const AZStd::string& hint) diff --git a/Code/Framework/AzCore/AzCore/Memory/OverrunDetectionAllocator.cpp b/Code/Framework/AzCore/AzCore/Memory/OverrunDetectionAllocator.cpp index 78f6ac0fb1..9e5b586c39 100644 --- a/Code/Framework/AzCore/AzCore/Memory/OverrunDetectionAllocator.cpp +++ b/Code/Framework/AzCore/AzCore/Memory/OverrunDetectionAllocator.cpp @@ -103,7 +103,7 @@ namespace AZ } } - /// Returns a pointer to the beginning of master vector of SmallAllocationGroups. + /// Returns a pointer to the beginning of vector of SmallAllocationGroups. SmallAllocationGroup* ArrayHead() { return this - m_index; @@ -169,7 +169,7 @@ namespace AZ return m_marker == MARKER; } - /// Returns the master index of the SmallAllocationGroup containing this allocation + /// Returns the index of the SmallAllocationGroup containing this allocation uint32_t GetSmallAllocationIndex() const { return (uint32_t)(m_data & 0xFFFFFFFF); diff --git a/Code/Framework/AzCore/Platform/iOS/AzCore/Module/DynamicModuleHandle_iOS.cpp b/Code/Framework/AzCore/Platform/iOS/AzCore/Module/DynamicModuleHandle_iOS.cpp index 53514c89e4..a389162ccc 100644 --- a/Code/Framework/AzCore/Platform/iOS/AzCore/Module/DynamicModuleHandle_iOS.cpp +++ b/Code/Framework/AzCore/Platform/iOS/AzCore/Module/DynamicModuleHandle_iOS.cpp @@ -40,7 +40,7 @@ namespace AZ // Afterwards use the AZ::IO::Path Append function append the filename as a child // of the framework directory AZ::IO::FixedMaxPathString fileName{ fullPath.Filename().Native() }; - fullPath.ReplaceFilename(AZ::IO::PathView((fileName + ".framework").c_str())); + fullPath.ReplaceFilename(AZ::IO::PathView(AZStd::string_view(fileName + ".framework"))); fullPath /= fileName; } } diff --git a/Code/Framework/AzFramework/AzFramework/Archive/IArchive.h b/Code/Framework/AzFramework/AzFramework/Archive/IArchive.h index f5de1a0f3a..ce8403b033 100644 --- a/Code/Framework/AzFramework/AzFramework/Archive/IArchive.h +++ b/Code/Framework/AzFramework/AzFramework/Archive/IArchive.h @@ -115,7 +115,7 @@ namespace AZ::IO // If used, the source path will be treated as the destination path // and no transformations will be done. Pass this flag when the path is to be the actual // path on the disk/in the packs and doesn't need adjustment (or after it has come through adjustments already) - // if this is set, AdjustFileName will not map the input path into the master folder (Ex: Shaders will not be converted to Game\Shaders) + // if this is set, AdjustFileName will not map the input path into the folder (Ex: Shaders will not be converted to Game\Shaders) FLAGS_PATH_REAL = 1 << 16, // AdjustFileName will always copy the file path to the destination path: @@ -318,7 +318,6 @@ namespace AZ::IO virtual ArchiveFileIterator FindFirst(AZStd::string_view pDir, uint32_t nFlags = 0, bool bAllowUseFileSystem = false) = 0; virtual ArchiveFileIterator FindNext(AZ::IO::ArchiveFileIterator handle) = 0; virtual bool FindClose(AZ::IO::ArchiveFileIterator handle) = 0; - // virtual bool IsOutOfDate(const char * szCompiledName, const char * szMasterFile)=0; //returns file modification time virtual IArchive::FileTime GetModificationTime(AZ::IO::HandleType fileHandle) = 0; diff --git a/Code/Framework/AzFramework/AzFramework/Archive/INestedArchive.h b/Code/Framework/AzFramework/AzFramework/Archive/INestedArchive.h index 25d36f009b..29a1030cf0 100644 --- a/Code/Framework/AzFramework/AzFramework/Archive/INestedArchive.h +++ b/Code/Framework/AzFramework/AzFramework/Archive/INestedArchive.h @@ -47,7 +47,7 @@ namespace AZ::IO enum EPakFlags { // support for absolute and other complex path specifications - - // all paths will be treated relatively to the current directory (normally MasterCD) + // all paths will be treated relatively to the current directory FLAGS_ABSOLUTE_PATHS = 1, // if this is set, the object will only understand relative to the zip file paths, diff --git a/Code/Framework/AzFramework/AzFramework/Physics/Character.h b/Code/Framework/AzFramework/AzFramework/Physics/Character.h index 19a6cbfe03..2401c9c5b9 100644 --- a/Code/Framework/AzFramework/AzFramework/Physics/Character.h +++ b/Code/Framework/AzFramework/AzFramework/Physics/Character.h @@ -18,6 +18,7 @@ #include #include +#include #include #include @@ -36,7 +37,7 @@ namespace Physics static void Reflect(AZ::ReflectContext* context); AZStd::string m_name; - ShapeConfigurationList m_shapes; + AzPhysics::ShapeColliderPairList m_shapes; }; class CharacterColliderConfiguration diff --git a/Code/Framework/AzFramework/AzFramework/Physics/ClassConverters.cpp b/Code/Framework/AzFramework/AzFramework/Physics/ClassConverters.cpp index eb74390638..e43bda4c88 100644 --- a/Code/Framework/AzFramework/AzFramework/Physics/ClassConverters.cpp +++ b/Code/Framework/AzFramework/AzFramework/Physics/ClassConverters.cpp @@ -57,7 +57,7 @@ namespace Physics classElement.RemoveElement(shapesIndex); // add a new vector in the new format - const int newShapesIndex = classElement.AddElement(context, "shapes"); + const int newShapesIndex = classElement.AddElement(context, "shapes"); if (newShapesIndex != -1) { AZ::SerializeContext::DataElementNode& newShapesElement = classElement.GetSubElement(newShapesIndex); @@ -65,7 +65,9 @@ namespace Physics // convert the old shapes into the new format and add to the vector for (AZ::SerializeContext::DataElementNode shape : shapesCopy) { - const int pairIndex = newShapesElement.AddElementWithData(context, "element", ShapeConfigurationPair()); + const int pairIndex = newShapesElement.AddElementWithData( + context, "element", AzPhysics::ShapeColliderPair()); + AZ::SerializeContext::DataElementNode& pairElement = newShapesElement.GetSubElement(pairIndex); ColliderConfiguration colliderConfig; @@ -131,8 +133,8 @@ namespace Physics AZ::SerializeContext::DataElementNode* baseBaseClass1 = baseClass1->FindSubElement(AZ_CRC("BaseClass1", 0xd4925735)); if (baseBaseClass1 && baseBaseClass1->FindSubElementAndGetData(AZ_CRC("name", 0x5e237e06), name)) { - ShapeConfigurationList shapes; - if (nodeElement.FindSubElementAndGetData(AZ_CRC("shapes", 0x93dba512), shapes)) + AzPhysics::ShapeColliderPairList shapes; + if (nodeElement.FindSubElementAndGetData(AZ_CRC("shapes", 0x93dba512), shapes)) { CharacterColliderNodeConfiguration newColliderNodeConfig; newColliderNodeConfig.m_name = name; diff --git a/Code/Framework/AzFramework/AzFramework/Physics/Common/PhysicsTypes.h b/Code/Framework/AzFramework/AzFramework/Physics/Common/PhysicsTypes.h index 5158da275e..b2b5fd1511 100644 --- a/Code/Framework/AzFramework/AzFramework/Physics/Common/PhysicsTypes.h +++ b/Code/Framework/AzFramework/AzFramework/Physics/Common/PhysicsTypes.h @@ -70,7 +70,10 @@ namespace AzPhysics using SimulatedBodyHandleList = AZStd::vector; //! Helper used for pairing the ShapeConfiguration and ColliderConfiguration together which is used when creating a Simulated Body. - using ShapeColliderPair = AZStd::pair; + using ShapeColliderPair = AZStd::pair< + AZStd::shared_ptr, + AZStd::shared_ptr>; + using ShapeColliderPairList = AZStd::vector; //! Flags used to specifying which properties of a body to compute. enum class MassComputeFlags : AZ::u8 diff --git a/Code/Framework/AzFramework/AzFramework/Physics/Shape.h b/Code/Framework/AzFramework/AzFramework/Physics/Shape.h index 115503c536..2ac38690ff 100644 --- a/Code/Framework/AzFramework/AzFramework/Physics/Shape.h +++ b/Code/Framework/AzFramework/AzFramework/Physics/Shape.h @@ -80,9 +80,6 @@ namespace Physics void OnContactOffsetChanged(); }; - using ShapeConfigurationPair = AZStd::pair, AZStd::shared_ptr>; - using ShapeConfigurationList = AZStd::vector; - struct RayCastRequest; class Shape diff --git a/Code/Framework/AzFramework/AzFramework/Script/ScriptComponent.cpp b/Code/Framework/AzFramework/AzFramework/Script/ScriptComponent.cpp index cdfad7f116..4f8da821c1 100644 --- a/Code/Framework/AzFramework/AzFramework/Script/ScriptComponent.cpp +++ b/Code/Framework/AzFramework/AzFramework/Script/ScriptComponent.cpp @@ -264,12 +264,12 @@ namespace AzFramework // SampleRPC = // { // // Two callbacks can be registered to the NetRPC - // // A function to be invoked on the Master - OnMaster + // // A function to be invoked on the main server - OnServer // // and a function to be invoked on the Proxy - OnProxy // // - // // Every NetRPC needs to have a valid OnMaster function, while OnProxy is optional. - // OnMaster = function() - // Debug.Log("Function to be invoked on the Master."); + // // Every NetRPC needs to have a valid OnServer function, while OnProxy is optional. + // OnServer = function() + // Debug.Log("Function to be invoked on the server."); // end // // OnProxy = function() diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.h b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.h index 95d0b9e3a7..ac66288ff2 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.h +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.h @@ -84,6 +84,7 @@ namespace AzFramework }; using EntitySpawnCallback = AZStd::function; + using EntityPreInsertionCallback = AZStd::function; using EntityDespawnCallback = AZStd::function; using ReloadSpawnableCallback = AZStd::function; using ListEntitiesCallback = AZStd::function; @@ -110,7 +111,8 @@ namespace AzFramework //! @param completionCallback Optional callback that's called when spawning entities has completed. This can be called from //! a different thread than the one that made the function call. The returned list of entities contains all the newly //! created entities. - virtual void SpawnAllEntities(EntitySpawnTicket& ticket, EntitySpawnCallback completionCallback = {}) = 0; + virtual void SpawnAllEntities(EntitySpawnTicket& ticket, EntityPreInsertionCallback preInsertionCallback = {}, + EntitySpawnCallback completionCallback = {}) = 0; //! Spawn instances of some entities in the spawnable. //! @param ticket Stores the results of the call. Use this ticket to spawn additional entities or to despawn them. //! @param entityIndices The indices into the template entities stored in the spawnable that will be used to spawn entities from. @@ -118,7 +120,7 @@ namespace AzFramework //! a different thread than the one that made this function call. The returned list of entities contains all the newly //! created entities. virtual void SpawnEntities(EntitySpawnTicket& ticket, AZStd::vector entityIndices, - EntitySpawnCallback completionCallback = {}) = 0; + EntityPreInsertionCallback preInsertionCallback = {}, EntitySpawnCallback completionCallback = {}) = 0; //! Removes all entities in the provided list from the environment. //! @param ticket The ticket previously used to spawn entities with. //! @param completionCallback Optional callback that's called when despawning entities has completed. This can be called from diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp index 3ab004b516..6799ea9353 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp @@ -14,17 +14,20 @@ #include #include #include +#include #include #include #include namespace AzFramework { - void SpawnableEntitiesManager::SpawnAllEntities(EntitySpawnTicket& ticket, EntitySpawnCallback completionCallback) + void SpawnableEntitiesManager::SpawnAllEntities(EntitySpawnTicket& ticket, EntityPreInsertionCallback preInsertionCallback, + EntitySpawnCallback completionCallback) { SpawnAllEntitiesCommand queueEntry; queueEntry.m_ticket = &ticket; queueEntry.m_completionCallback = AZStd::move(completionCallback); + queueEntry.m_preInsertionCallback = AZStd::move(preInsertionCallback); { AZStd::scoped_lock queueLock(m_pendingRequestQueueMutex); queueEntry.m_ticketId = GetTicketPayload(ticket).m_nextTicketId++; @@ -32,13 +35,15 @@ namespace AzFramework } } - void SpawnableEntitiesManager::SpawnEntities(EntitySpawnTicket& ticket, AZStd::vector entityIndices, - EntitySpawnCallback completionCallback) + void SpawnableEntitiesManager::SpawnEntities( + EntitySpawnTicket& ticket, AZStd::vector entityIndices, + EntityPreInsertionCallback preInsertionCallback, EntitySpawnCallback completionCallback) { SpawnEntitiesCommand queueEntry; queueEntry.m_ticket = &ticket; queueEntry.m_entityIndices = AZStd::move(entityIndices); queueEntry.m_completionCallback = AZStd::move(completionCallback); + queueEntry.m_preInsertionCallback = AZStd::move(preInsertionCallback); { AZStd::scoped_lock queueLock(m_pendingRequestQueueMutex); queueEntry.m_ticketId = GetTicketPayload(ticket).m_nextTicketId++; @@ -205,6 +210,7 @@ namespace AzFramework AZ::Entity* clone = serializeContext.CloneObject(&entityTemplate); AZ_Assert(clone != nullptr, "Failed to clone spawnable entity."); clone->SetId(AZ::Entity::MakeId()); + GameEntityContextRequestBus::Broadcast(&GameEntityContextRequestBus::Events::AddGameEntity, clone); return clone; } @@ -214,23 +220,79 @@ namespace AzFramework Ticket& ticket = GetTicketPayload(*request.m_ticket); if (ticket.m_spawnable.IsReady() && request.m_ticketId == ticket.m_currentTicketId) { - size_t spawnedEntitiesCount = ticket.m_spawnedEntities.size(); + AZStd::vector& spawnedEntities = ticket.m_spawnedEntities; + AZStd::vector& spawnedEntityIndices = ticket.m_spawnedEntityIndices; - const Spawnable::EntityList& entities = ticket.m_spawnable->GetEntities(); - size_t entitiesSize = entities.size(); - ticket.m_spawnedEntities.reserve(ticket.m_spawnedEntities.size() + entitiesSize); - ticket.m_spawnedEntityIndices.reserve(ticket.m_spawnedEntityIndices.size() + entitiesSize); + // Keep track how many entities there were in the array initially + size_t spawnedEntitiesInitialCount = spawnedEntities.size(); - for(size_t i=0; iGetEntities(); + size_t entitiesToSpawnSize = entitiesToSpawn.size(); + + // Reserve buffers + spawnedEntities.reserve(spawnedEntities.size() + entitiesToSpawnSize); + ticket.m_spawnedEntityIndices.reserve(ticket.m_spawnedEntityIndices.size() + entitiesToSpawnSize); + + // TEMP: To be replaced by IdUtils::Remapper + using EntityIdMap = AZStd::unordered_map; + EntityIdMap templateToCloneIdMap; + // \TEMP + + // Clone the entities from Spawnable + for (size_t i = 0; i < entitiesToSpawnSize; ++i) { - ticket.m_spawnedEntities.push_back(SpawnSingleEntity(*entities[i], serializeContext)); - ticket.m_spawnedEntityIndices.push_back(i); + const AZ::Entity& entityTemplate = *entitiesToSpawn[i]; + + AZ::Entity* clone = serializeContext.CloneObject(&entityTemplate); + AZ_Assert(clone != nullptr, "Failed to clone spawnable entity."); + clone->SetId(AZ::Entity::MakeId()); + + spawnedEntities.push_back(clone); + spawnedEntityIndices.push_back(i); + + // TEMP: To be replaced by IdUtils::Remapper + templateToCloneIdMap[entityTemplate.GetId()] = clone->GetId(); + + // Update TransformComponent parent Id. It is guaranteed for the entities array to be sorted from parent->child here. + auto* transformComponent = clone->FindComponent(); + AZ::EntityId parentId = transformComponent->GetParentId(); + if (parentId.IsValid()) + { + auto it = templateToCloneIdMap.find(parentId); + if (it != templateToCloneIdMap.end()) + { + transformComponent->SetParentRelative(it->second); + } + else + { + AZ_Warning( + "SpawnableEntitiesManager", false, "Entity %s doesn't have the parent entity %s present in the spawnable", + clone->GetName().c_str(), parentId.ToString().data()); + } + } + // \TEMP } + // Let other systems know about newly spawned entities for any pre-processing before adding to the scene/game context. + if (request.m_preInsertionCallback) + { + request.m_preInsertionCallback(*request.m_ticket, SpawnableEntityContainerView( + ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount, ticket.m_spawnedEntities.end())); + } + + // Add to the game context, now the entities are active + AZStd::for_each(ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount, ticket.m_spawnedEntities.end(), + [](AZ::Entity* entity) + { + GameEntityContextRequestBus::Broadcast(&GameEntityContextRequestBus::Events::AddGameEntity, entity); + }); + + // Let other systems know about newly spawned entities for any post-processing after adding to the scene/game context. if (request.m_completionCallback) { request.m_completionCallback(*request.m_ticket, SpawnableConstEntityContainerView( - ticket.m_spawnedEntities.begin() + spawnedEntitiesCount, ticket.m_spawnedEntities.end())); + ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount, ticket.m_spawnedEntities.end())); } m_onSpawnedEvent.Signal(ticket.m_spawnable); @@ -249,24 +311,56 @@ namespace AzFramework Ticket& ticket = GetTicketPayload(*request.m_ticket); if (ticket.m_spawnable.IsReady() && request.m_ticketId == ticket.m_currentTicketId) { - size_t spawnedEntitiesCount = ticket.m_spawnedEntities.size(); + AZStd::vector& spawnedEntities = ticket.m_spawnedEntities; + AZStd::vector& spawnedEntityIndices = ticket.m_spawnedEntityIndices; - const Spawnable::EntityList& entities = ticket.m_spawnable->GetEntities(); - size_t entitiesSize = entities.size(); - ticket.m_spawnedEntities.reserve(ticket.m_spawnedEntities.size() + entitiesSize); - ticket.m_spawnedEntityIndices.reserve(ticket.m_spawnedEntityIndices.size() + entitiesSize); + // Keep track how many entities there were in the array initially + size_t spawnedEntitiesInitialCount = spawnedEntities.size(); + + // These are 'template' entities we'll be cloning from + const Spawnable::EntityList& entitiesToSpawn = ticket.m_spawnable->GetEntities(); + size_t entitiesToSpawnSize = request.m_entityIndices.size(); + + spawnedEntities.reserve(spawnedEntities.size() + entitiesToSpawnSize); + spawnedEntityIndices.reserve(spawnedEntityIndices.size() + entitiesToSpawnSize); for (size_t index : request.m_entityIndices) { - ticket.m_spawnedEntities.push_back(SpawnSingleEntity(*entities[index], serializeContext)); - ticket.m_spawnedEntityIndices.push_back(index); + if (index < entitiesToSpawn.size()) + { + const AZ::Entity& entityTemplate = *entitiesToSpawn[index]; + + AZ::Entity* clone = serializeContext.CloneObject(&entityTemplate); + AZ_Assert(clone != nullptr, "Failed to clone spawnable entity."); + clone->SetId(AZ::Entity::MakeId()); + + spawnedEntities.push_back(clone); + spawnedEntityIndices.push_back(index); + + } } ticket.m_loadAll = false; + // Let other systems know about newly spawned entities for any pre-processing before adding to the scene/game context. + if (request.m_preInsertionCallback) + { + request.m_preInsertionCallback( + *request.m_ticket, + SpawnableEntityContainerView( + ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount, ticket.m_spawnedEntities.end())); + } + + // Add to the game context, now the entities are active + AZStd::for_each(ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount, ticket.m_spawnedEntities.end(), + [](AZ::Entity* entity) + { + GameEntityContextRequestBus::Broadcast(&GameEntityContextRequestBus::Events::AddGameEntity, entity); + }); + if (request.m_completionCallback) { request.m_completionCallback(*request.m_ticket, SpawnableConstEntityContainerView( - ticket.m_spawnedEntities.begin() + spawnedEntitiesCount, ticket.m_spawnedEntities.end())); + ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount, ticket.m_spawnedEntities.end())); } m_onSpawnedEvent.Signal(ticket.m_spawnable); diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.h b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.h index c70b9ccaa6..54f48055fd 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.h +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.h @@ -47,8 +47,8 @@ namespace AzFramework // The following functions are thread safe // - void SpawnAllEntities(EntitySpawnTicket& ticket, EntitySpawnCallback completionCallback = {}) override; - void SpawnEntities(EntitySpawnTicket& ticket, AZStd::vector entityIndices, + void SpawnAllEntities(EntitySpawnTicket& ticket, EntityPreInsertionCallback preInsertionCallback = {}, EntitySpawnCallback completionCallback = {}) override; + void SpawnEntities(EntitySpawnTicket& ticket, AZStd::vector entityIndices, EntityPreInsertionCallback preInsertionCallback = {}, EntitySpawnCallback completionCallback = {}) override; void DespawnAllEntities(EntitySpawnTicket& ticket, EntityDespawnCallback completionCallback = {}) override; @@ -90,6 +90,7 @@ namespace AzFramework struct SpawnAllEntitiesCommand { EntitySpawnCallback m_completionCallback; + EntityPreInsertionCallback m_preInsertionCallback; EntitySpawnTicket* m_ticket; uint32_t m_ticketId; }; @@ -97,6 +98,7 @@ namespace AzFramework { AZStd::vector m_entityIndices; EntitySpawnCallback m_completionCallback; + EntityPreInsertionCallback m_preInsertionCallback; EntitySpawnTicket* m_ticket; uint32_t m_ticketId; }; diff --git a/Code/Framework/AzFramework/AzFramework/TargetManagement/NeighborhoodAPI.cpp b/Code/Framework/AzFramework/AzFramework/TargetManagement/NeighborhoodAPI.cpp index 44c601f5f5..7f3d0770c0 100644 --- a/Code/Framework/AzFramework/AzFramework/TargetManagement/NeighborhoodAPI.cpp +++ b/Code/Framework/AzFramework/AzFramework/TargetManagement/NeighborhoodAPI.cpp @@ -41,7 +41,7 @@ namespace Neighborhood { //--------------------------------------------------------------------- void NeighborReplica::OnReplicaActivate(const GridMate::ReplicaContext& /*rc*/) { - // TODO: Should we send the message to ourselves as well (master)? + // TODO: Should we send the message to ourselves as well? if (IsProxy()) { AZ_Assert(m_persistentName.Get().c_str(), "Received NeighborReplica with missing persistent name!"); @@ -52,7 +52,7 @@ namespace Neighborhood { //--------------------------------------------------------------------- void NeighborReplica::OnReplicaDeactivate(const GridMate::ReplicaContext& /*rc*/) { - // TODO: Should we send the message to ourselves as well (master)? + // TODO: Should we send the message to ourselves as well? if (IsProxy()) { EBUS_EVENT(NeighborhoodBus, OnNodeLeft, *this); diff --git a/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.cpp b/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.cpp index daf2c63921..79c1a28e5d 100644 --- a/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.cpp +++ b/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.cpp @@ -30,7 +30,8 @@ namespace AzFramework AZ_CVAR(float, ed_cameraSystemOrbitDollyScrollSpeed, 0.02f, nullptr, AZ::ConsoleFunctorFlags::Null, ""); AZ_CVAR(float, ed_cameraSystemOrbitDollyCursorSpeed, 0.01f, nullptr, AZ::ConsoleFunctorFlags::Null, ""); AZ_CVAR(float, ed_cameraSystemScrollTranslateSpeed, 0.02f, nullptr, AZ::ConsoleFunctorFlags::Null, ""); - AZ_CVAR(float, ed_cameraSystemMaxOrbitDistance, 60.0f, nullptr, AZ::ConsoleFunctorFlags::Null, ""); + AZ_CVAR(float, ed_cameraSystemMinOrbitDistance, 6.0f, nullptr, AZ::ConsoleFunctorFlags::Null, ""); + AZ_CVAR(float, ed_cameraSystemMaxOrbitDistance, 50.0f, nullptr, AZ::ConsoleFunctorFlags::Null, ""); AZ_CVAR(float, ed_cameraSystemLookSmoothness, 5.0f, nullptr, AZ::ConsoleFunctorFlags::Null, ""); AZ_CVAR(float, ed_cameraSystemTranslateSmoothness, 5.0f, nullptr, AZ::ConsoleFunctorFlags::Null, ""); AZ_CVAR(float, ed_cameraSystemRotateSpeed, 0.005f, nullptr, AZ::ConsoleFunctorFlags::Null, ""); @@ -532,20 +533,39 @@ namespace AzFramework if (Beginning()) { - float hit_distance = 0.0f; - AZ::Plane::CreateFromNormalAndPoint(AZ::Vector3::CreateAxisZ(), AZ::Vector3::CreateAxisZ(ed_cameraSystemDefaultPlaneHeight)) - .CastRay(targetCamera.Translation(), targetCamera.Rotation().GetBasisY(), hit_distance); + const auto hasLookAt = [&nextCamera, &targetCamera, lookAtFn = m_lookAtFn] { + if (lookAtFn) + { + if (const auto lookAt = lookAtFn()) + { + auto transform = AZ::Transform::CreateLookAt(targetCamera.m_lookAt, *lookAt); + nextCamera.m_lookDist = -lookAt->GetDistance(targetCamera.m_lookAt); + UpdateCameraFromTransform(nextCamera, transform); - if (hit_distance > 0.0f) + return true; + } + } + return false; + }(); + + if (!hasLookAt) { - hit_distance = AZStd::min(hit_distance, ed_cameraSystemMaxOrbitDistance); - nextCamera.m_lookDist = -hit_distance; - nextCamera.m_lookAt = targetCamera.Translation() + targetCamera.Rotation().GetBasisY() * hit_distance; - } - else - { - nextCamera.m_lookDist = -ed_cameraSystemMaxOrbitDistance; - nextCamera.m_lookAt = targetCamera.Translation() + targetCamera.Rotation().GetBasisY() * ed_cameraSystemMaxOrbitDistance; + float hit_distance = 0.0f; + AZ::Plane::CreateFromNormalAndPoint(AZ::Vector3::CreateAxisZ(), AZ::Vector3::CreateAxisZ(ed_cameraSystemDefaultPlaneHeight)) + .CastRay(targetCamera.Translation(), targetCamera.Rotation().GetBasisY(), hit_distance); + + if (hit_distance > 0.0f) + { + hit_distance = AZStd::min(hit_distance, ed_cameraSystemMaxOrbitDistance); + nextCamera.m_lookDist = -hit_distance; + nextCamera.m_lookAt = targetCamera.Translation() + targetCamera.Rotation().GetBasisY() * hit_distance; + } + else + { + nextCamera.m_lookDist = -ed_cameraSystemMinOrbitDistance; + nextCamera.m_lookAt = + targetCamera.Translation() + targetCamera.Rotation().GetBasisY() * ed_cameraSystemMinOrbitDistance; + } } } diff --git a/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.h b/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.h index 41d7f11385..b6b2bc1e6a 100644 --- a/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.h +++ b/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.h @@ -199,6 +199,7 @@ namespace AzFramework public: explicit RotateCameraInput(InputChannelId rotateChannelId); + // CameraInput overrides ... void HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) override; Camera StepCamera(const Camera& targetCamera, const ScreenVector& cursorDelta, float scrollDelta, float deltaTime) override; @@ -239,6 +240,7 @@ namespace AzFramework public: PanCameraInput(InputChannelId panChannelId, PanAxesFn panAxesFn); + // CameraInput overrides ... void HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) override; Camera StepCamera(const Camera& targetCamera, const ScreenVector& cursorDelta, float scrollDelta, float deltaTime) override; @@ -279,6 +281,7 @@ namespace AzFramework public: explicit TranslateCameraInput(TranslationAxesFn translationAxesFn); + // CameraInput overrides ... void HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) override; Camera StepCamera(const Camera& targetCamera, const ScreenVector& cursorDelta, float scrollDelta, float deltaTime) override; void ResetImpl() override; @@ -348,6 +351,7 @@ namespace AzFramework class OrbitDollyScrollCameraInput : public CameraInput { public: + // CameraInput overrides ... void HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) override; Camera StepCamera(const Camera& targetCamera, const ScreenVector& cursorDelta, float scrollDelta, float deltaTime) override; }; @@ -357,6 +361,7 @@ namespace AzFramework public: explicit OrbitDollyCursorMoveCameraInput(InputChannelId dollyChannelId); + // CameraInput overrides ... void HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) override; Camera StepCamera(const Camera& targetCamera, const ScreenVector& cursorDelta, float scrollDelta, float deltaTime) override; @@ -367,6 +372,7 @@ namespace AzFramework class ScrollTranslationCameraInput : public CameraInput { public: + // CameraInput overrides ... void HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) override; Camera StepCamera(const Camera& targetCamera, const ScreenVector& cursorDelta, float scrollDelta, float deltaTime) override; }; @@ -374,16 +380,32 @@ namespace AzFramework class OrbitCameraInput : public CameraInput { public: + using LookAtFn = AZStd::function()>; + + // CameraInput overrides ... void HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) override; Camera StepCamera(const Camera& targetCamera, const ScreenVector& cursorDelta, float scrollDelta, float deltaTime) override; - bool Exclusive() const override - { - return true; - } + bool Exclusive() const override; Cameras m_orbitCameras; + + //! Override the default behavior for how a look-at point is calculated. + void SetLookAtFn(const LookAtFn& lookAtFn); + + private: + LookAtFn m_lookAtFn; }; + inline void OrbitCameraInput::SetLookAtFn(const LookAtFn& lookAtFn) + { + m_lookAtFn = lookAtFn; + } + + inline bool OrbitCameraInput::Exclusive() const + { + return true; + } + struct WindowSize; //! Map from a generic InputChannel event to a camera specific InputEvent. diff --git a/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpSocket.cpp b/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpSocket.cpp index 78020cb09d..70000dc9a8 100644 --- a/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpSocket.cpp +++ b/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpSocket.cpp @@ -116,8 +116,8 @@ namespace AzNetworking int32_t TcpSocket::Receive(uint8_t* outData, uint32_t size) const { - AZ_Assert(size > 0, "Invalid data size for send"); - AZ_Assert(outData != nullptr, "NULL data pointer passed to send"); + AZ_Assert(size > 0, "Invalid data size for receive"); + AZ_Assert(outData != nullptr, "NULL data pointer passed to receive"); if (!IsOpen()) { return SocketOpResultErrorNotOpen; @@ -176,7 +176,7 @@ namespace AzNetworking if (::bind(aznumeric_cast(m_socketFd), (const sockaddr*)&hints, sizeof(hints)) != 0) { const int32_t error = GetLastNetworkError(); - AZLOG_ERROR("Failed to bind socket (%d:%s)", error, GetNetworkErrorDesc(error)); + AZLOG_ERROR("Failed to bind TCP socket to port %u (%d:%s)", uint32_t(port), error, GetNetworkErrorDesc(error)); return false; } diff --git a/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpNetworkInterface.cpp b/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpNetworkInterface.cpp index 870545e6c8..5676d48150 100644 --- a/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpNetworkInterface.cpp +++ b/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpNetworkInterface.cpp @@ -162,7 +162,7 @@ namespace AzNetworking const UdpReaderThread::ReceivedPackets* packets = m_readerThread.GetReceivedPackets(m_socket.get()); if (packets == nullptr) { - AZ_Assert(false, "nullptr was retrieved for the received packet buffer, check that the socket has been registered with the reader thread"); + // Socket is not yet registered with the reader thread and is likely still pending, try again later return; } diff --git a/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpSocket.cpp b/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpSocket.cpp index e642c87623..cbb5f8e6c0 100644 --- a/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpSocket.cpp +++ b/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpSocket.cpp @@ -82,7 +82,7 @@ namespace AzNetworking if (::bind(static_cast(m_socketFd), (const sockaddr *)&hints, sizeof(hints)) != 0) { const int32_t error = GetLastNetworkError(); - AZLOG_ERROR("Failed to bind socket to port %u (%d:%s)", uint32_t(port), error, GetNetworkErrorDesc(error)); + AZLOG_ERROR("Failed to bind UDP socket to port %u (%d:%s)", uint32_t(port), error, GetNetworkErrorDesc(error)); return false; } } diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/PrefabEditorEntityOwnershipInterface.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/PrefabEditorEntityOwnershipInterface.h index 19c236f509..8412361657 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/PrefabEditorEntityOwnershipInterface.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/PrefabEditorEntityOwnershipInterface.h @@ -12,6 +12,7 @@ #pragma once +#include #include #include #include @@ -46,6 +47,10 @@ namespace AzToolsFramework virtual Prefab::InstanceOptionalReference GetRootPrefabInstance() = 0; + //! Get all Assets generated by Prefab processing when entering Play-In Editor mode (Ctrl+G) + //! /return The vector of Assets generated by Prefab processing + virtual const AZStd::vector>& GetPlayInEditorAssetData() = 0; + virtual bool LoadFromStream(AZ::IO::GenericStream& stream, AZStd::string_view filename) = 0; virtual bool SaveToStream(AZ::IO::GenericStream& stream, AZStd::string_view filename) = 0; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/PrefabEditorEntityOwnershipService.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/PrefabEditorEntityOwnershipService.cpp index 02243a8c77..da529d9349 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/PrefabEditorEntityOwnershipService.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/PrefabEditorEntityOwnershipService.cpp @@ -314,6 +314,11 @@ namespace AzToolsFramework return *m_rootInstance; } + const AZStd::vector>& PrefabEditorEntityOwnershipService::GetPlayInEditorAssetData() + { + return m_playInEditorData.m_assets; + } + void PrefabEditorEntityOwnershipService::OnEntityRemoved(AZ::EntityId entityId) { AzFramework::SliceEntityRequestBus::MultiHandler::BusDisconnect(entityId); diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/PrefabEditorEntityOwnershipService.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/PrefabEditorEntityOwnershipService.h index 48e07df091..3be9b95df0 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/PrefabEditorEntityOwnershipService.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/PrefabEditorEntityOwnershipService.h @@ -195,6 +195,8 @@ namespace AzToolsFramework AZ::IO::PathView filePath, Prefab::InstanceOptionalReference instanceToParentUnder) override; Prefab::InstanceOptionalReference GetRootPrefabInstance() override; + + const AZStd::vector>& GetPlayInEditorAssetData() override; ////////////////////////////////////////////////////////////////////////// void OnEntityRemoved(AZ::EntityId entityId); diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/InstanceUpdateExecutor.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/InstanceUpdateExecutor.cpp index fa03ab22e5..e0d29f2ab1 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/InstanceUpdateExecutor.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/InstanceUpdateExecutor.cpp @@ -134,9 +134,13 @@ namespace AzToolsFramework } } - auto findInstancesResult = m_templateInstanceMapperInterface->FindInstancesOwnedByTemplate(instanceTemplateId)->get(); + auto findInstancesResult = m_templateInstanceMapperInterface->FindInstancesOwnedByTemplate(instanceTemplateId); + AZ_Assert( + findInstancesResult.has_value(), "Prefab Instances corresponding to template with id %llu couldn't be found.", + instanceTemplateId); - if (findInstancesResult.find(instanceToUpdate) == findInstancesResult.end()) + if (findInstancesResult == AZStd::nullopt || + findInstancesResult->get().find(instanceToUpdate) == findInstancesResult->get().end()) { // Since nested instances get reconstructed during propagation, remove any nested instance that no longer // maps to a template. diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Link/Link.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Link/Link.cpp index e0834ed53b..308749ab28 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Link/Link.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Link/Link.cpp @@ -182,16 +182,16 @@ namespace AzToolsFramework else { AZ::JsonSerializationResult::ResultCode applyPatchResult = AZ::JsonSerialization::ApplyPatch( - linkedInstanceDom, + sourceTemplateDomCopy, targetTemplatePrefabDom.GetAllocator(), - sourceTemplatePrefabDom, patchesReference->get(), AZ::JsonMergeApproach::JsonPatch); + linkedInstanceDom.CopyFrom(sourceTemplateDomCopy, targetTemplatePrefabDom.GetAllocator()); if (applyPatchResult.GetProcessing() != AZ::JsonSerializationResult::Processing::Completed) { - AZ_Error("Prefab", false, - "Link::UpdateTarget - " - "ApplyPatches failed for Prefab DOM from source Template '%u' and target Template '%u'.", + AZ_Error( + "Prefab", false, + "Link::UpdateTarget - ApplyPatches failed for Prefab DOM from source Template '%u' and target Template '%u'.", m_sourceTemplateId, m_targetTemplateId); return false; } diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp index 200a5c96fb..e6bb8c7dee 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp @@ -122,11 +122,14 @@ namespace AzToolsFramework AZ::EntityId containerEntityId = instanceToCreate->get().GetContainerEntityId(); - // Parent the entities to the container entity. Parenting the container entities of the instances passed to createPrefab - // will be done during the creation of links below. - for (AZ::Entity* topLevelEntity : entities) + // Parent the non-container top level entities to the container entity. + // Parenting the top level container entities will be done during the creation of links. + for (AZ::Entity* topLevelEntity : topLevelEntities) { - AZ::TransformBus::Event(topLevelEntity->GetId(), &AZ::TransformBus::Events::SetParent, containerEntityId); + if (!IsInstanceContainerEntity(topLevelEntity->GetId())) + { + AZ::TransformBus::Event(topLevelEntity->GetId(), &AZ::TransformBus::Events::SetParent, containerEntityId); + } } // Update the template of the instance since the entities are modified since the template creation. @@ -142,11 +145,25 @@ namespace AzToolsFramework AZ_Assert( nestedInstanceContainerEntity, "Invalid container entity found for the nested instance used in prefab creation."); + AZ::EntityId parentId; + AZ::TransformBus::EventResult( + parentId, nestedInstanceContainerEntity->get().GetId(), &AZ::TransformBus::Events::GetParentId); + + auto entityIterator = AZStd::find_if( + entities.begin(), entities.end(), [parentId](AZ::Entity* entity) { return entity->GetId() == parentId; }); + + // If the previous parent entity of the nested instance is not part of the entities of the newly created prefab, + // then set the parent of the nested prefab as the container entity of the newly created prefab. + if (entityIterator == entities.end()) + { + parentId = containerEntityId; + } + // These link creations shouldn't be undone because that would put the template in a non-usable state if a user // chooses to instantiate the template after undoing the creation. CreateLink( {&nestedInstanceContainerEntity->get()}, *nestedInstance, instanceToCreate->get().GetTemplateId(), - undoBatch.GetUndoBatch(), containerEntityId, false); + undoBatch.GetUndoBatch(), parentId, false); }); // Create a link between the templates of the newly created instance and the instance it's being parented under. diff --git a/Code/Framework/GridMate/GridMate/Carrier/DefaultTrafficControl.cpp b/Code/Framework/GridMate/GridMate/Carrier/DefaultTrafficControl.cpp index 99f38f3171..f5fb0cb721 100644 --- a/Code/Framework/GridMate/GridMate/Carrier/DefaultTrafficControl.cpp +++ b/Code/Framework/GridMate/GridMate/Carrier/DefaultTrafficControl.cpp @@ -296,7 +296,7 @@ DefaultTrafficControl::OnReceived(TrafficControlConnectionId id, DataGramControl if (m_maxRecvPackets != 0) { --cd->m_recvPacketAllowance; - if (cd->m_recvPacketAllowance == 0) // hit the limit -> let's blacklist connection + if (cd->m_recvPacketAllowance == 0) // hit the limit { cd->m_canReceiveData = false; } diff --git a/Code/Sandbox/Editor/EditorViewportWidget.cpp b/Code/Sandbox/Editor/EditorViewportWidget.cpp index 436d4bba63..85c40f2092 100644 --- a/Code/Sandbox/Editor/EditorViewportWidget.cpp +++ b/Code/Sandbox/Editor/EditorViewportWidget.cpp @@ -49,6 +49,7 @@ #include #include #include +#include // AtomToolsFramework #include @@ -1238,6 +1239,20 @@ void EditorViewportWidget::SetViewportId(int id) auto firstPersonWheelCamera = AZStd::make_shared(); auto orbitCamera = AZStd::make_shared(); + orbitCamera->SetLookAtFn([]() -> AZStd::optional { + AZStd::optional manipulatorTransform; + AzToolsFramework::EditorTransformComponentSelectionRequestBus::EventResult( + manipulatorTransform, AzToolsFramework::GetEntityContextId(), + &AzToolsFramework::EditorTransformComponentSelectionRequestBus::Events::GetManipulatorTransform); + + if (manipulatorTransform) + { + return manipulatorTransform->GetTranslation(); + } + + return {}; + }); + auto orbitRotateCamera = AZStd::make_shared(AzFramework::CameraOrbitLookButton); auto orbitTranslateCamera = AZStd::make_shared(AzFramework::OrbitTranslation); auto orbitDollyWheelCamera = AZStd::make_shared(); diff --git a/Code/Tools/ProjectManager/Source/PythonBindings.cpp b/Code/Tools/ProjectManager/Source/PythonBindings.cpp index a25db506f4..e925c81032 100644 --- a/Code/Tools/ProjectManager/Source/PythonBindings.cpp +++ b/Code/Tools/ProjectManager/Source/PythonBindings.cpp @@ -200,11 +200,11 @@ namespace RedirectOutput { s_RedirectModule = module; - SetRedirection("stdout", g_redirect_stdout_saved, g_redirect_stdout, [](const char* msg) { + SetRedirection("stdout", g_redirect_stdout_saved, g_redirect_stdout, []([[maybe_unused]] const char* msg) { AZ_TracePrintf("Python", msg); }); - SetRedirection("stderr", g_redirect_stderr_saved, g_redirect_stderr, [](const char* msg) { + SetRedirection("stderr", g_redirect_stderr_saved, g_redirect_stderr, []([[maybe_unused]] const char* msg) { AZ_TracePrintf("Python", msg); }); diff --git a/Code/Tools/SerializeContextTools/SliceConverter.cpp b/Code/Tools/SerializeContextTools/SliceConverter.cpp index 7631eabf22..a18a6ff3a3 100644 --- a/Code/Tools/SerializeContextTools/SliceConverter.cpp +++ b/Code/Tools/SerializeContextTools/SliceConverter.cpp @@ -173,8 +173,7 @@ namespace AZ return false; } - const SliceComponent::SliceList& sliceList = sliceComponent->GetSlices(); - AZ_Warning("Convert-Slice", sliceList.empty(), " Slice depends on other slices, this conversion will lose data.\n"); + AZ_Warning("Convert-Slice", sliceComponent->GetSlices().empty(), " Slice depends on other slices, this conversion will lose data.\n"); // Create the Prefab with the entities from the slice AZStd::unique_ptr sourceInstance( diff --git a/Code/Tools/Standalone/Source/Driller/Workspaces/Workspace.h b/Code/Tools/Standalone/Source/Driller/Workspaces/Workspace.h index 05b52430b7..d5e872b95a 100644 --- a/Code/Tools/Standalone/Source/Driller/Workspaces/Workspace.h +++ b/Code/Tools/Standalone/Source/Driller/Workspaces/Workspace.h @@ -10,8 +10,7 @@ * */ -#ifndef DRILLER_WORKSPACE_SETTINGS_MASTER_H -#define DRILLER_WORKSPACE_SETTINGS_MASTER_H +#pragma once #include #include @@ -77,7 +76,3 @@ namespace Driller SavedWorkspaceMap m_WorkspaceSaveData; }; } - -#pragma once - -#endif // DRILLER_WORKSPACE_SETTINGS_MASTER_H diff --git a/Gems/AssetMemoryAnalyzer/Code/Source/AssetMemoryAnalyzer.cpp b/Gems/AssetMemoryAnalyzer/Code/Source/AssetMemoryAnalyzer.cpp index f371210d68..25d607dce3 100644 --- a/Gems/AssetMemoryAnalyzer/Code/Source/AssetMemoryAnalyzer.cpp +++ b/Gems/AssetMemoryAnalyzer/Code/Source/AssetMemoryAnalyzer.cpp @@ -81,12 +81,12 @@ namespace AssetMemoryAnalyzer using AssetTree = AZ::Debug::AssetTree; using AssetTreeNode = typename AssetTree::NodeType; using AllocationTable = AZ::Debug::AllocationTable; - using MasterCodePoints = AZStd::unordered_set, AZStd::equal_to, AZ::Debug::AZStdAssetTrackingAllocator>; + using CodePoints = AZStd::unordered_set, AZStd::equal_to, AZ::Debug::AZStdAssetTrackingAllocator>; using mutex_type = AZStd::mutex; using lock_type = AZStd::lock_guard; mutex_type m_mutex; - MasterCodePoints m_masterCodePoints; + CodePoints m_codePoints; AssetTree m_assetTree; AllocationTable m_allocationTable; AZ::Debug::AssetTracking m_assetTracking; @@ -195,7 +195,7 @@ namespace AssetMemoryAnalyzer { // Store a record for this allocation, at this code-point lock_type lock(m_mutex); - auto insertResult = m_masterCodePoints.emplace(Data::CodePoint{ fileName ? fileName : "", lineNum, category }); + auto insertResult = m_codePoints.emplace(Data::CodePoint{ fileName ? fileName : "", lineNum, category }); Data::CodePoint* cp = &*insertResult.first; m_allocationTable.Get().emplace(address, AllocationTable::RecordType{ activeAsset, (uint32_t)byteSize, Data::AllocationData{ cp, categoryInfo } }); static_cast(activeAsset)->m_data.m_totalAllocations[(int)category]++; diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR.materialtype b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR.materialtype index a74ceb1783..2b0d09bc5c 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR.materialtype +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR.materialtype @@ -1199,6 +1199,14 @@ "file": "./StandardPBR_ForwardPass_EDS.shader", "tag": "ForwardPass_EDS" }, + { + "file": "./StandardPBR_LowEndForward.shader", + "tag": "LowEndForward" + }, + { + "file": "./StandardPBR_LowEndForward_EDS.shader", + "tag": "LowEndForward_EDS" + }, { "file": "Shaders/Shadow/Shadowmap.shader", "tag": "Shadowmap" @@ -1289,10 +1297,6 @@ "textureProperty": "baseColor.textureMap", "useTextureProperty": "baseColor.useTexture", "dependentProperties": ["baseColor.textureMapUv", "baseColor.textureBlendMode"], - "shaderTags": [ - "ForwardPass", - "ForwardPass_EDS" - ], "shaderOption": "o_baseColor_useTexture" } }, @@ -1302,10 +1306,6 @@ "textureProperty": "metallic.textureMap", "useTextureProperty": "metallic.useTexture", "dependentProperties": ["metallic.textureMapUv"], - "shaderTags": [ - "ForwardPass", - "ForwardPass_EDS" - ], "shaderOption": "o_metallic_useTexture" } }, @@ -1315,10 +1315,6 @@ "textureProperty": "specularF0.textureMap", "useTextureProperty": "specularF0.useTexture", "dependentProperties": ["specularF0.textureMapUv"], - "shaderTags": [ - "ForwardPass", - "ForwardPass_EDS" - ], "shaderOption": "o_specularF0_useTexture" } }, @@ -1328,10 +1324,6 @@ "textureProperty": "normal.textureMap", "useTextureProperty": "normal.useTexture", "dependentProperties": ["normal.textureMapUv", "normal.factor", "normal.flipX", "normal.flipY"], - "shaderTags": [ - "ForwardPass", - "ForwardPass_EDS" - ], "shaderOption": "o_normal_useTexture" } }, diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_ForwardPass.azsl b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_ForwardPass.azsl index f362349a7b..bf1ec96b79 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_ForwardPass.azsl +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_ForwardPass.azsl @@ -10,6 +10,8 @@ * */ +#include "Atom/Features/ShaderQualityOptions.azsli" + #include "StandardPBR_Common.azsli" // SRGs @@ -317,13 +319,18 @@ ForwardPassOutputWithDepth StandardPbr_ForwardPassPS(VSOutput IN, bool isFrontFa PbrLightingOutput lightingOutput = ForwardPassPS_Common(IN, isFrontFace, depth); +#ifdef UNIFIED_FORWARD_OUTPUT + OUT.m_color.rgb = lightingOutput.m_diffuseColor.rgb + lightingOutput.m_specularColor.rgb; + OUT.m_color.a = lightingOutput.m_diffuseColor.a; + OUT.m_depth = depth; +#else OUT.m_diffuseColor = lightingOutput.m_diffuseColor; OUT.m_specularColor = lightingOutput.m_specularColor; OUT.m_specularF0 = lightingOutput.m_specularF0; OUT.m_albedo = lightingOutput.m_albedo; OUT.m_normal = lightingOutput.m_normal; OUT.m_depth = depth; - +#endif return OUT; } @@ -335,12 +342,16 @@ ForwardPassOutput StandardPbr_ForwardPassPS_EDS(VSOutput IN, bool isFrontFace : PbrLightingOutput lightingOutput = ForwardPassPS_Common(IN, isFrontFace, depth); +#ifdef UNIFIED_FORWARD_OUTPUT + OUT.m_color.rgb = lightingOutput.m_diffuseColor.rgb + lightingOutput.m_specularColor.rgb; + OUT.m_color.a = lightingOutput.m_diffuseColor.a; +#else OUT.m_diffuseColor = lightingOutput.m_diffuseColor; OUT.m_specularColor = lightingOutput.m_specularColor; OUT.m_specularF0 = lightingOutput.m_specularF0; OUT.m_albedo = lightingOutput.m_albedo; OUT.m_normal = lightingOutput.m_normal; - +#endif return OUT; } diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_LowEndForward.azsl b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_LowEndForward.azsl new file mode 100644 index 0000000000..c87faffcbe --- /dev/null +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_LowEndForward.azsl @@ -0,0 +1,17 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +// NOTE: This file is a temporary workaround until .shader files can #define macros for their .azsl files + +#define QUALITY_LOW_END 1 + +#include "StandardPBR_ForwardPass.azsl" diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_LowEndForward.shader b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_LowEndForward.shader new file mode 100644 index 0000000000..44139608ca --- /dev/null +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_LowEndForward.shader @@ -0,0 +1,59 @@ +{ + // Note: "LowEnd" shaders are for supporting the low end pipeline + // These shaders can be safely added to materials without incurring additional runtime draw + // items as draw items for shaders are only created if the scene has a pass with a matching + // DrawListTag. If your pipeline doesn't have a "lowEndForward" DrawListTag, no draw items + // for this shader will be created. + + "Source" : "./StandardPBR_LowEndForward.azsl", + + "DepthStencilState" : + { + "Depth" : + { + "Enable" : true, + "CompareFunc" : "GreaterEqual" + }, + "Stencil" : + { + "Enable" : true, + "ReadMask" : "0x00", + "WriteMask" : "0xFF", + "FrontFace" : + { + "Func" : "Always", + "DepthFailOp" : "Keep", + "FailOp" : "Keep", + "PassOp" : "Replace" + }, + "BackFace" : + { + "Func" : "Always", + "DepthFailOp" : "Keep", + "FailOp" : "Keep", + "PassOp" : "Replace" + } + } + }, + + "CompilerHints" : { + "DisableOptimizations" : false + }, + + "ProgramSettings": + { + "EntryPoints": + [ + { + "name": "StandardPbr_ForwardPassVS", + "type": "Vertex" + }, + { + "name": "StandardPbr_ForwardPassPS", + "type": "Fragment" + } + ] + }, + + "DrawList" : "lowEndForward" +} diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_LowEndForward_EDS.shader b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_LowEndForward_EDS.shader new file mode 100644 index 0000000000..9faa1d3698 --- /dev/null +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_LowEndForward_EDS.shader @@ -0,0 +1,59 @@ +{ + // Note: "LowEnd" shaders are for supporting the low end pipeline + // These shaders can be safely added to materials without incurring additional runtime draw + // items as draw items for shaders are only created if the scene has a pass with a matching + // DrawListTag. If your pipeline doesn't have a "lowEndForward" DrawListTag, no draw items + // for this shader will be created. + + "Source" : "./StandardPBR_LowEndForward.azsl", + + "DepthStencilState" : + { + "Depth" : + { + "Enable" : true, + "CompareFunc" : "GreaterEqual" + }, + "Stencil" : + { + "Enable" : true, + "ReadMask" : "0x00", + "WriteMask" : "0xFF", + "FrontFace" : + { + "Func" : "Always", + "DepthFailOp" : "Keep", + "FailOp" : "Keep", + "PassOp" : "Replace" + }, + "BackFace" : + { + "Func" : "Always", + "DepthFailOp" : "Keep", + "FailOp" : "Keep", + "PassOp" : "Replace" + } + } + }, + + "CompilerHints" : { + "DisableOptimizations" : false + }, + + "ProgramSettings": + { + "EntryPoints": + [ + { + "name": "StandardPbr_ForwardPassVS", + "type": "Vertex" + }, + { + "name": "StandardPbr_ForwardPassPS_EDS", + "type": "Fragment" + } + ] + }, + + "DrawList" : "lowEndForward" +} diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_ShaderEnable.lua b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_ShaderEnable.lua index 7c3d989c35..2733713122 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_ShaderEnable.lua +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_ShaderEnable.lua @@ -29,26 +29,33 @@ function Process(context) local depthPass = context:GetShaderByTag("DepthPass") local shadowMap = context:GetShaderByTag("Shadowmap") local forwardPassEDS = context:GetShaderByTag("ForwardPass_EDS") + local lowEndForwardEDS = context:GetShaderByTag("LowEndForward_EDS") + local depthPassWithPS = context:GetShaderByTag("DepthPass_WithPS") local shadowMapWitPS = context:GetShaderByTag("Shadowmap_WithPS") local forwardPass = context:GetShaderByTag("ForwardPass") + local lowEndForward = context:GetShaderByTag("LowEndForward") if parallaxEnabled and parallaxPdoEnabled then depthPass:SetEnabled(false) shadowMap:SetEnabled(false) forwardPassEDS:SetEnabled(false) + lowEndForwardEDS:SetEnabled(false) depthPassWithPS:SetEnabled(true) shadowMapWitPS:SetEnabled(true) forwardPass:SetEnabled(true) + lowEndForward:SetEnabled(true) else depthPass:SetEnabled(opacityMode == OpacityMode_Opaque) shadowMap:SetEnabled(opacityMode == OpacityMode_Opaque) forwardPassEDS:SetEnabled((opacityMode == OpacityMode_Opaque) or (opacityMode == OpacityMode_Blended) or (opacityMode == OpacityMode_TintedTransparent)) + lowEndForwardEDS:SetEnabled((opacityMode == OpacityMode_Opaque) or (opacityMode == OpacityMode_Blended) or (opacityMode == OpacityMode_TintedTransparent)) depthPassWithPS:SetEnabled(opacityMode == OpacityMode_Cutout) shadowMapWitPS:SetEnabled(opacityMode == OpacityMode_Cutout) forwardPass:SetEnabled(opacityMode == OpacityMode_Cutout) + lowEndForward:SetEnabled(opacityMode == OpacityMode_Cutout) end context:GetShaderByTag("DepthPassTransparentMin"):SetEnabled((opacityMode == OpacityMode_Blended) or (opacityMode == OpacityMode_TintedTransparent)) diff --git a/Gems/Atom/Feature/Common/Assets/Passes/Forward.pass b/Gems/Atom/Feature/Common/Assets/Passes/Forward.pass index 31a8ed1879..b66e3bb4e1 100644 --- a/Gems/Atom/Feature/Common/Assets/Passes/Forward.pass +++ b/Gems/Atom/Feature/Common/Assets/Passes/Forward.pass @@ -148,22 +148,6 @@ }, "LoadAction": "Clear" } - }, - { - "Name": "ScatterDistanceOutput", - "SlotType": "Output", - "ScopeAttachmentUsage": "RenderTarget", - "LoadStoreAction": { - "ClearValue": { - "Value": [ - 0.0, - 0.0, - 0.0, - 0.0 - ] - }, - "LoadAction": "Clear" - } } ], "ImageAttachments": [ @@ -238,19 +222,6 @@ "AssetRef": { "FilePath": "Textures/BRDFTexture.attimage" } - }, - { - "Name": "ScatterDistanceImage", - "SizeSource": { - "Source": { - "Pass": "Parent", - "Attachment": "SwapChainOutput" - } - }, - "ImageDescriptor": { - "Format": "R11G11B10_FLOAT", - "SharedQueueMask": "Graphics" - } } ], "Connections": [ @@ -295,13 +266,6 @@ "Pass": "This", "Attachment": "BRDFTexture" } - }, - { - "LocalSlot": "ScatterDistanceOutput", - "AttachmentRef": { - "Pass": "This", - "Attachment": "ScatterDistanceImage" - } } ] } diff --git a/Gems/Atom/Feature/Common/Assets/Passes/LightAdaptationParent.pass b/Gems/Atom/Feature/Common/Assets/Passes/LightAdaptationParent.pass new file mode 100644 index 0000000000..3e804d23e2 --- /dev/null +++ b/Gems/Atom/Feature/Common/Assets/Passes/LightAdaptationParent.pass @@ -0,0 +1,146 @@ +{ + "Type": "JsonSerialization", + "Version": 1, + "ClassName": "PassAsset", + "ClassData": { + "PassTemplate": { + "Name": "LightAdaptationParentTemplate", + "PassClass": "ParentPass", + "Slots": [ + // Inputs... + { + "Name": "LightingInput", + "SlotType": "Input" + }, + // SwapChain here is only used to reference the frame height and format + { + "Name": "SwapChainOutput", + "SlotType": "InputOutput" + }, + // Outputs... + { + "Name": "Output", + "SlotType": "Output" + }, + // Debug Outputs... + { + "Name": "LuminanceMipChainOutput", + "SlotType": "Output" + } + ], + "Connections": [ + { + "LocalSlot": "Output", + "AttachmentRef": { + "Pass": "DisplayMapperPass", + "Attachment": "Output" + } + }, + { + "LocalSlot": "LuminanceMipChainOutput", + "AttachmentRef": { + "Pass": "DownsampleLuminanceMipChain", + "Attachment": "MipChainInputOutput" + } + } + ], + "PassRequests": [ + { + "Name": "DownsampleLuminanceMinAvgMax", + "TemplateName": "DownsampleLuminanceMinAvgMaxCS", + "Connections": [ + { + "LocalSlot": "Input", + "AttachmentRef": { + "Pass": "Parent", + "Attachment": "LightingInput" + } + } + ] + }, + { + "Name": "DownsampleLuminanceMipChain", + "TemplateName": "DownsampleMipChainTemplate", + "Connections": [ + { + "LocalSlot": "MipChainInputOutput", + "AttachmentRef": { + "Pass": "DownsampleLuminanceMinAvgMax", + "Attachment": "Output" + } + } + ], + "PassData": { + "$type": "DownsampleMipChainPassData", + "ShaderAsset": { + "FilePath": "Shaders/PostProcessing/DownsampleMinAvgMaxCS.shader" + } + } + }, + { + "Name": "EyeAdaptationPass", + "TemplateName": "EyeAdaptationTemplate", + "Enabled": false, + "Connections": [ + { + "LocalSlot": "SceneLuminanceInput", + "AttachmentRef": { + "Pass": "DownsampleLuminanceMipChain", + "Attachment": "MipChainInputOutput" + } + } + ] + }, + { + "Name": "LookModificationTransformPass", + "TemplateName": "LookModificationTransformTemplate", + "Enabled": true, + "Connections": [ + { + "LocalSlot": "Input", + "AttachmentRef": { + "Pass": "Parent", + "Attachment": "LightingInput" + } + }, + { + "LocalSlot": "EyeAdaptationDataInput", + "AttachmentRef": { + "Pass": "EyeAdaptationPass", + "Attachment": "EyeAdaptationDataInputOutput" + } + }, + { + "LocalSlot": "SwapChainOutput", + "AttachmentRef": { + "Pass": "Parent", + "Attachment": "SwapChainOutput" + } + } + ] + }, + { + "Name": "DisplayMapperPass", + "TemplateName": "DisplayMapperTemplate", + "Enabled": true, + "Connections": [ + { + "LocalSlot": "Input", + "AttachmentRef": { + "Pass": "LookModificationTransformPass", + "Attachment": "Output" + } + }, + { + "LocalSlot": "SwapChainOutput", + "AttachmentRef": { + "Pass": "Parent", + "Attachment": "SwapChainOutput" + } + } + ] + } + ] + } + } +} diff --git a/Gems/Atom/Feature/Common/Assets/Passes/LowEndForward.pass b/Gems/Atom/Feature/Common/Assets/Passes/LowEndForward.pass new file mode 100644 index 0000000000..4b865fcb6d --- /dev/null +++ b/Gems/Atom/Feature/Common/Assets/Passes/LowEndForward.pass @@ -0,0 +1,133 @@ +{ + "Type": "JsonSerialization", + "Version": 1, + "ClassName": "PassAsset", + "ClassData": { + "PassTemplate": { + "Name": "LowEndForwardPassTemplate", + "PassClass": "RasterPass", + "Slots": [ + // Inputs... + { + "Name": "BRDFTextureInput", + "ShaderInputName": "m_brdfMap", + "SlotType": "Input", + "ScopeAttachmentUsage": "Shader" + }, + { + "Name": "DirectionalLightShadowmap", + "ShaderInputName": "m_directionalLightShadowmap", + "SlotType": "Input", + "ScopeAttachmentUsage": "Shader", + "ImageViewDesc": { + "IsArray": 1 + } + }, + { + "Name": "ExponentialShadowmapDirectional", + "ShaderInputName": "m_directionalLightExponentialShadowmap", + "SlotType": "Input", + "ScopeAttachmentUsage": "Shader", + "ImageViewDesc": { + "IsArray": 1 + } + }, + { + "Name": "ProjectedShadowmap", + "ShaderInputName": "m_projectedShadowmaps", + "SlotType": "Input", + "ScopeAttachmentUsage": "Shader", + "ImageViewDesc": { + "IsArray": 1 + } + }, + { + "Name": "ExponentialShadowmapProjected", + "ShaderInputName": "m_projectedExponentialShadowmap", + "SlotType": "Input", + "ScopeAttachmentUsage": "Shader", + "ImageViewDesc": { + "IsArray": 1 + } + }, + { + "Name": "TileLightData", + "SlotType": "Input", + "ShaderInputName": "m_tileLightData", + "ScopeAttachmentUsage": "Shader" + }, + { + "Name": "LightListRemapped", + "SlotType": "Input", + "ShaderInputName": "m_lightListRemapped", + "ScopeAttachmentUsage": "Shader" + }, + // Input/Outputs... + { + "Name": "DepthStencilInputOutput", + "SlotType": "InputOutput", + "ScopeAttachmentUsage": "DepthStencil" + }, + // Outputs... + { + "Name": "LightingOutput", + "SlotType": "Output", + "ScopeAttachmentUsage": "RenderTarget", + "LoadStoreAction": { + "ClearValue": { + "Value": [ + 0.0, + 0.0, + 0.0, + 0.0 + ] + }, + "LoadAction": "Clear" + } + } + ], + "ImageAttachments": [ + { + "Name": "LightingAttachment", + "SizeSource": { + "Source": { + "Pass": "Parent", + "Attachment": "SwapChainOutput" + } + }, + "MultisampleSource": { + "Pass": "This", + "Attachment": "DepthStencilInputOutput" + }, + "ImageDescriptor": { + "Format": "R16G16B16A16_FLOAT", + "SharedQueueMask": "Graphics" + } + }, + { + "Name": "BRDFTexture", + "Lifetime": "Imported", + "AssetRef": { + "FilePath": "Textures/BRDFTexture.attimage" + } + } + ], + "Connections": [ + { + "LocalSlot": "LightingOutput", + "AttachmentRef": { + "Pass": "This", + "Attachment": "LightingAttachment" + } + }, + { + "LocalSlot": "BRDFTextureInput", + "AttachmentRef": { + "Pass": "This", + "Attachment": "BRDFTexture" + } + } + ] + } + } +} diff --git a/Gems/Atom/Feature/Common/Assets/Passes/LowEndPipeline.pass b/Gems/Atom/Feature/Common/Assets/Passes/LowEndPipeline.pass new file mode 100644 index 0000000000..b19569fb9d --- /dev/null +++ b/Gems/Atom/Feature/Common/Assets/Passes/LowEndPipeline.pass @@ -0,0 +1,344 @@ +{ + "Type": "JsonSerialization", + "Version": 1, + "ClassName": "PassAsset", + "ClassData": { + "PassTemplate": { + "Name": "LowEndPipelineTemplate", + "PassClass": "ParentPass", + "Slots": [ + { + "Name": "SwapChainOutput", + "SlotType": "InputOutput", + "ScopeAttachmentUsage": "RenderTarget" + } + ], + "PassRequests": [ + { + "Name": "MorphTargetPass", + "TemplateName": "MorphTargetPassTemplate" + }, + { + "Name": "SkinningPass", + "TemplateName": "SkinningPassTemplate", + "Connections": [ + { + "LocalSlot": "SkinnedMeshOutputStream", + "AttachmentRef": { + "Pass": "MorphTargetPass", + "Attachment": "MorphTargetDeltaOutput" + } + } + ] + }, + { + "Name": "DepthPrePass", + "TemplateName": "DepthMSAAParentTemplate", + "Connections": [ + { + "LocalSlot": "SkinnedMeshes", + "AttachmentRef": { + "Pass": "SkinningPass", + "Attachment": "SkinnedMeshOutputStream" + } + }, + { + "LocalSlot": "SwapChainOutput", + "AttachmentRef": { + "Pass": "Parent", + "Attachment": "SwapChainOutput" + } + } + ] + }, + { + "Name": "LightCullingPass", + "TemplateName": "LightCullingParentTemplate", + "Connections": [ + { + "LocalSlot": "SkinnedMeshes", + "AttachmentRef": { + "Pass": "SkinningPass", + "Attachment": "SkinnedMeshOutputStream" + } + }, + { + "LocalSlot": "DepthMSAA", + "AttachmentRef": { + "Pass": "DepthPrePass", + "Attachment": "DepthMSAA" + } + }, + { + "LocalSlot": "SwapChainOutput", + "AttachmentRef": { + "Pass": "Parent", + "Attachment": "SwapChainOutput" + } + } + ] + }, + { + "Name": "ShadowPass", + "TemplateName": "ShadowParentTemplate", + "Connections": [ + { + "LocalSlot": "SkinnedMeshes", + "AttachmentRef": { + "Pass": "SkinningPass", + "Attachment": "SkinnedMeshOutputStream" + } + }, + { + "LocalSlot": "SwapChainOutput", + "AttachmentRef": { + "Pass": "Parent", + "Attachment": "SwapChainOutput" + } + } + ] + }, + { + "Name": "ForwardPass", + "TemplateName": "LowEndForwardPassTemplate", + "Connections": [ + // Inputs... + { + "LocalSlot": "DirectionalLightShadowmap", + "AttachmentRef": { + "Pass": "ShadowPass", + "Attachment": "DirectionalShadowmap" + } + }, + { + "LocalSlot": "ExponentialShadowmapDirectional", + "AttachmentRef": { + "Pass": "ShadowPass", + "Attachment": "DirectionalESM" + } + }, + { + "LocalSlot": "ProjectedShadowmap", + "AttachmentRef": { + "Pass": "ShadowPass", + "Attachment": "ProjectedShadowmap" + } + }, + { + "LocalSlot": "ExponentialShadowmapProjected", + "AttachmentRef": { + "Pass": "ShadowPass", + "Attachment": "ProjectedESM" + } + }, + { + "LocalSlot": "TileLightData", + "AttachmentRef": { + "Pass": "LightCullingPass", + "Attachment": "TileLightData" + } + }, + { + "LocalSlot": "LightListRemapped", + "AttachmentRef": { + "Pass": "LightCullingPass", + "Attachment": "LightListRemapped" + } + }, + // Input/Outputs... + { + "LocalSlot": "DepthStencilInputOutput", + "AttachmentRef": { + "Pass": "DepthPrePass", + "Attachment": "DepthMSAA" + } + } + ], + "PassData": { + "$type": "RasterPassData", + "DrawListTag": "lowEndForward", + "PipelineViewTag": "MainCamera", + "PassSrgAsset": { + "FilePath": "shaderlib/atom/features/pbr/forwardpasssrg.azsli:PassSrg" + } + } + }, + { + "Name": "SkyBoxPass", + "TemplateName": "SkyBoxTemplate", + "Enabled": true, + "Connections": [ + { + "LocalSlot": "SpecularInputOutput", + "AttachmentRef": { + "Pass": "ForwardPass", + "Attachment": "LightingOutput" + } + }, + { + "LocalSlot": "SkyBoxDepth", + "AttachmentRef": { + "Pass": "ForwardPass", + "Attachment": "DepthStencilInputOutput" + } + } + ] + }, + { + "Name": "MSAAResolvePass", + "TemplateName": "MSAAResolveColorTemplate", + "Connections": [ + { + "LocalSlot": "Input", + "AttachmentRef": { + "Pass": "SkyBoxPass", + "Attachment": "SpecularInputOutput" + } + } + ] + }, + { + "Name": "TransparentPass", + "TemplateName": "TransparentParentTemplate", + "Connections": [ + { + "LocalSlot": "DirectionalShadowmap", + "AttachmentRef": { + "Pass": "ShadowPass", + "Attachment": "DirectionalShadowmap" + } + }, + { + "LocalSlot": "DirectionalESM", + "AttachmentRef": { + "Pass": "ShadowPass", + "Attachment": "DirectionalESM" + } + }, + { + "LocalSlot": "ProjectedShadowmap", + "AttachmentRef": { + "Pass": "ShadowPass", + "Attachment": "ProjectedShadowmap" + } + }, + { + "LocalSlot": "ProjectedESM", + "AttachmentRef": { + "Pass": "ShadowPass", + "Attachment": "ProjectedESM" + } + }, + { + "LocalSlot": "TileLightData", + "AttachmentRef": { + "Pass": "LightCullingPass", + "Attachment": "TileLightData" + } + }, + { + "LocalSlot": "LightListRemapped", + "AttachmentRef": { + "Pass": "LightCullingPass", + "Attachment": "LightListRemapped" + } + }, + { + "LocalSlot": "DepthStencil", + "AttachmentRef": { + "Pass": "DepthPrePass", + "Attachment": "Depth" + } + }, + { + "LocalSlot": "InputOutput", + "AttachmentRef": { + "Pass": "MSAAResolvePass", + "Attachment": "Output" + } + } + ] + }, + { + "Name": "LightAdaptation", + "TemplateName": "LightAdaptationParentTemplate", + "Connections": [ + { + "LocalSlot": "LightingInput", + "AttachmentRef": { + "Pass": "TransparentPass", + "Attachment": "InputOutput" + } + }, + { + "LocalSlot": "SwapChainOutput", + "AttachmentRef": { + "Pass": "Parent", + "Attachment": "SwapChainOutput" + } + } + ] + }, + { + "Name": "AuxGeomPass", + "TemplateName": "AuxGeomPassTemplate", + "Enabled": true, + "Connections": [ + { + "LocalSlot": "ColorInputOutput", + "AttachmentRef": { + "Pass": "LightAdaptation", + "Attachment": "Output" + } + }, + { + "LocalSlot": "DepthInputOutput", + "AttachmentRef": { + "Pass": "DepthPrePass", + "Attachment": "Depth" + } + } + ], + "PassData": { + "$type": "RasterPassData", + "DrawListTag": "auxgeom", + "PipelineViewTag": "MainCamera" + } + }, + { + "Name": "UIPass", + "TemplateName": "UIParentTemplate", + "Connections": [ + { + "LocalSlot": "InputOutput", + "AttachmentRef": { + "Pass": "AuxGeomPass", + "Attachment": "ColorInputOutput" + } + } + ] + }, + { + "Name": "CopyToSwapChain", + "TemplateName": "FullscreenCopyTemplate", + "Connections": [ + { + "LocalSlot": "Input", + "AttachmentRef": { + "Pass": "UIPass", + "Attachment": "InputOutput" + } + }, + { + "LocalSlot": "Output", + "AttachmentRef": { + "Pass": "Parent", + "Attachment": "SwapChainOutput" + } + } + ] + } + ] + } + } +} diff --git a/Gems/Atom/Feature/Common/Assets/Passes/OpaqueParent.pass b/Gems/Atom/Feature/Common/Assets/Passes/OpaqueParent.pass index dda120e164..a691fe2534 100644 --- a/Gems/Atom/Feature/Common/Assets/Passes/OpaqueParent.pass +++ b/Gems/Atom/Feature/Common/Assets/Passes/OpaqueParent.pass @@ -305,7 +305,7 @@ }, { "Name": "SkyBoxPass", - "TemplateName": "SkyBoxTemplate", + "TemplateName": "SkyBoxTwoOutputsTemplate", "Enabled": true, "Connections": [ { diff --git a/Gems/Atom/Feature/Common/Assets/Passes/PassTemplates.azasset b/Gems/Atom/Feature/Common/Assets/Passes/PassTemplates.azasset index b83ab65ff2..c56e8932b1 100644 --- a/Gems/Atom/Feature/Common/Assets/Passes/PassTemplates.azasset +++ b/Gems/Atom/Feature/Common/Assets/Passes/PassTemplates.azasset @@ -92,6 +92,10 @@ "Name": "SkyBoxTemplate", "Path": "Passes/SkyBox.pass" }, + { + "Name": "SkyBoxTwoOutputsTemplate", + "Path": "Passes/SkyBox_TwoOutputs.pass" + }, { "Name": "UIPassTemplate", "Path": "Passes/UI.pass" @@ -483,6 +487,18 @@ { "Name": "UIParentTemplate", "Path": "Passes/UIParent.pass" + }, + { + "Name": "LightAdaptationParentTemplate", + "Path": "Passes/LightAdaptationParent.pass" + }, + { + "Name": "LowEndForwardPassTemplate", + "Path": "Passes/LowEndForward.pass" + }, + { + "Name": "LowEndPipelineTemplate", + "Path": "Passes/LowEndPipeline.pass" } ] } diff --git a/Gems/Atom/Feature/Common/Assets/Passes/PostProcessParent.pass b/Gems/Atom/Feature/Common/Assets/Passes/PostProcessParent.pass index 37b1ee5c5a..36f7f1e985 100644 --- a/Gems/Atom/Feature/Common/Assets/Passes/PostProcessParent.pass +++ b/Gems/Atom/Feature/Common/Assets/Passes/PostProcessParent.pass @@ -40,7 +40,7 @@ { "LocalSlot": "Output", "AttachmentRef": { - "Pass": "DisplayMapperPass", + "Pass": "LightAdaptation", "Attachment": "Output" } }, @@ -54,8 +54,8 @@ { "LocalSlot": "LuminanceMipChainOutput", "AttachmentRef": { - "Pass": "DownsampleLuminanceMipChain", - "Attachment": "MipChainInputOutput" + "Pass": "LightAdaptation", + "Attachment": "LuminanceMipChainOutput" } } ], @@ -115,94 +115,16 @@ } ] }, - // Everything before this point deals in raw lighting values - // --------------------------------------------------------- - // Everything after starts to map to values we see on screen { - "Name": "DownsampleLuminanceMinAvgMax", - "TemplateName": "DownsampleLuminanceMinAvgMaxCS", + "Name": "LightAdaptation", + "TemplateName": "LightAdaptationParentTemplate", "Connections": [ { - "LocalSlot": "Input", + "LocalSlot": "LightingInput", "AttachmentRef": { "Pass": "BloomPass", "Attachment": "InputOutput" } - } - ] - }, - { - "Name": "DownsampleLuminanceMipChain", - "TemplateName": "DownsampleMipChainTemplate", - "Connections": [ - { - "LocalSlot": "MipChainInputOutput", - "AttachmentRef": { - "Pass": "DownsampleLuminanceMinAvgMax", - "Attachment": "Output" - } - } - ], - "PassData": { - "$type": "DownsampleMipChainPassData", - "ShaderAsset": { - "FilePath": "Shaders/PostProcessing/DownsampleMinAvgMaxCS.shader" - } - } - }, - { - "Name": "EyeAdaptationPass", - "TemplateName": "EyeAdaptationTemplate", - "Enabled": false, - "Connections": [ - { - "LocalSlot": "SceneLuminanceInput", - "AttachmentRef": { - "Pass": "DownsampleLuminanceMipChain", - "Attachment": "MipChainInputOutput" - } - } - ] - }, - { - "Name": "LookModificationTransformPass", - "TemplateName": "LookModificationTransformTemplate", - "Enabled": true, - "Connections": [ - { - "LocalSlot": "Input", - "AttachmentRef": { - "Pass": "BloomPass", - "Attachment": "InputOutput" - } - }, - { - "LocalSlot": "EyeAdaptationDataInput", - "AttachmentRef": { - "Pass": "EyeAdaptationPass", - "Attachment": "EyeAdaptationDataInputOutput" - } - }, - { - "LocalSlot": "SwapChainOutput", - "AttachmentRef": { - "Pass": "Parent", - "Attachment": "SwapChainOutput" - } - } - ] - }, - { - "Name": "DisplayMapperPass", - "TemplateName": "DisplayMapperTemplate", - "Enabled": true, - "Connections": [ - { - "LocalSlot": "Input", - "AttachmentRef": { - "Pass": "LookModificationTransformPass", - "Attachment": "Output" - } }, { "LocalSlot": "SwapChainOutput", diff --git a/Gems/Atom/Feature/Common/Assets/Passes/SkyBox.pass b/Gems/Atom/Feature/Common/Assets/Passes/SkyBox.pass index 57f442e5de..fb16271ba7 100644 --- a/Gems/Atom/Feature/Common/Assets/Passes/SkyBox.pass +++ b/Gems/Atom/Feature/Common/Assets/Passes/SkyBox.pass @@ -12,11 +12,6 @@ "SlotType": "InputOutput", "ScopeAttachmentUsage": "RenderTarget" }, - { - "Name": "ReflectionInputOutput", - "SlotType": "InputOutput", - "ScopeAttachmentUsage": "RenderTarget" - }, { "Name": "SkyBoxDepth", "SlotType": "InputOutput", diff --git a/Gems/Atom/Feature/Common/Assets/Passes/SkyBox_TwoOutputs.pass b/Gems/Atom/Feature/Common/Assets/Passes/SkyBox_TwoOutputs.pass new file mode 100644 index 0000000000..0ed7b39288 --- /dev/null +++ b/Gems/Atom/Feature/Common/Assets/Passes/SkyBox_TwoOutputs.pass @@ -0,0 +1,43 @@ +{ + "Type": "JsonSerialization", + "Version": 1, + "ClassName": "PassAsset", + "ClassData": { + "PassTemplate": { + "Name": "SkyBoxTwoOutputsTemplate", + "PassClass": "FullScreenTriangle", + "Slots": [ + { + "Name": "SpecularInputOutput", + "SlotType": "InputOutput", + "ScopeAttachmentUsage": "RenderTarget" + }, + { + "Name": "ReflectionInputOutput", + "SlotType": "InputOutput", + "ScopeAttachmentUsage": "RenderTarget" + }, + { + "Name": "SkyBoxDepth", + "SlotType": "InputOutput", + "ScopeAttachmentUsage": "DepthStencil" + } + ], + "PassData": { + "$type": "FullscreenTrianglePassData", + "ShaderAsset": { + "FilePath": "shaders/skybox/skybox_twooutputs.shader" + }, + "PipelineViewTag": "MainCamera", + "ShaderDataMappings": { + "FloatMappings": [ + { + "Name": "m_sunIntensityMultiplier", + "Value": 1.0 + } + ] + } + } + } + } +} diff --git a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/LightCulling/NVLC.azsli b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/LightCulling/NVLC.azsli index 8bcd21b19b..60d5bf38f2 100644 --- a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/LightCulling/NVLC.azsli +++ b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/LightCulling/NVLC.azsli @@ -26,7 +26,7 @@ // //---------------------------------------------------------------------------------- -#define Depth_to_Z(d, unprojectZ) (unprojectZ.x / (d + unprojectZ.y)) +#define DepthBufferToViewSpace(d, unprojectZ) (unprojectZ.x / (d + unprojectZ.y)) #define NVLC_MAX_POSSIBLE_LIGHTS_PER_BIN 256 @@ -187,7 +187,7 @@ float4 RemapZToUnit(float4 z, float2 minmaxz) uint DepthSamplesToBinMask2x(float2 d, float2 minmaxz, float2 unprojectZ) { - float2 z = Depth_to_Z(d, unprojectZ); + float2 z = DepthBufferToViewSpace(d, unprojectZ); // Tile_UnitValueToBit will convert that 0 to 1 value into 0.0 to 31.99999 float2 bit = Tile_UnitValueToBit(RemapZToUnit(z, minmaxz)); @@ -207,7 +207,7 @@ uint DepthSamplesToBinMask2x(float2 d, float2 minmaxz, float2 unprojectZ) uint DepthSamplesToBinMask4x(float4 d, float2 minmaxz, float2 unprojectZ) { - float4 z = Depth_to_Z(d, unprojectZ); + float4 z = DepthBufferToViewSpace(d, unprojectZ); // Tile_UnitValueToBit will convert that 0 to 1 value into 0.0 to 31.99999 float4 bit = Tile_UnitValueToBit(RemapZToUnit(z, minmaxz)); diff --git a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/ForwardPassOutput.azsli b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/ForwardPassOutput.azsli index acc215f1c9..5821deb3b1 100644 --- a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/ForwardPassOutput.azsli +++ b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/ForwardPassOutput.azsli @@ -10,6 +10,21 @@ * */ +#ifdef UNIFIED_FORWARD_OUTPUT + +struct ForwardPassOutput +{ + float4 m_color : SV_Target0; +}; + +struct ForwardPassOutputWithDepth +{ + float4 m_color : SV_Target0; + float m_depth : SV_Depth; +}; + +#else + struct ForwardPassOutput { float4 m_diffuseColor : SV_Target0; //!< RGB = Diffuse Lighting, A = Blend Alpha (for blended surfaces) OR A = special encoding of surfaceScatteringFactor, m_subsurfaceScatteringQuality, o_enableSubsurfaceScattering @@ -30,3 +45,5 @@ struct ForwardPassOutputWithDepth float4 m_normal : SV_Target4; float m_depth : SV_Depth; }; + +#endif diff --git a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Lights/Ibl.azsli b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Lights/Ibl.azsli index 7400005508..3be9d5756a 100644 --- a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Lights/Ibl.azsli +++ b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Lights/Ibl.azsli @@ -12,38 +12,39 @@ #pragma once +// --- Static Options Available --- +// FORCE_IBL_IN_FORWARD_PASS - forces IBL lighting to be run in the forward pass, used in pipelines that don't have a reflection pass + #include #include #include #include -void ApplyIblDiffuse( +float3 GetIblDiffuse( float3 normal, float3 albedo, - float3 diffuseResponse, - out float3 outDiffuse) + float3 diffuseResponse) { float3 irradianceDir = MultiplyVectorQuaternion(normal, SceneSrg::m_iblOrientation); float3 diffuseSample = SceneSrg::m_diffuseEnvMap.Sample(SceneSrg::m_samplerEnv, GetCubemapCoords(irradianceDir)).rgb; - outDiffuse = diffuseResponse * albedo * diffuseSample; + return diffuseResponse * albedo * diffuseSample; } -void ApplyIblSpecular( +float3 GetIblSpecular( float3 position, float3 normal, float3 specularF0, float roughnessLinear, float3 dirToCamera, - float2 brdf, - out float3 outSpecular) + float2 brdf) { float3 reflectDir = reflect(-dirToCamera, normal); reflectDir = MultiplyVectorQuaternion(reflectDir, SceneSrg::m_iblOrientation); // global - outSpecular = SceneSrg::m_specularEnvMap.SampleLevel(SceneSrg::m_samplerEnv, GetCubemapCoords(reflectDir), GetRoughnessMip(roughnessLinear)).rgb; + float3 outSpecular = SceneSrg::m_specularEnvMap.SampleLevel(SceneSrg::m_samplerEnv, GetCubemapCoords(reflectDir), GetRoughnessMip(roughnessLinear)).rgb; outSpecular *= (specularF0 * brdf.x + brdf.y); // reflection probe @@ -72,86 +73,55 @@ void ApplyIblSpecular( outSpecular = lerp(outSpecular, probeSpecular, blendAmount); } + return outSpecular; } void ApplyIBL(Surface surface, inout LightingData lightingData) { - if (o_opacity_mode == OpacityMode::Blended || o_opacity_mode == OpacityMode::TintedTransparent) +#ifdef FORCE_IBL_IN_FORWARD_PASS + bool useDiffuseIbl = true; + bool useSpecularIbl = true; + bool useIbl = o_enableIBL; +#else + bool isTransparent = (o_opacity_mode == OpacityMode::Blended || o_opacity_mode == OpacityMode::TintedTransparent); + bool useDiffuseIbl = isTransparent; + bool useSpecularIbl = (isTransparent || o_meshUseForwardPassIBLSpecular || o_materialUseForwardPassIBLSpecular); + bool useIbl = o_enableIBL && (useDiffuseIbl || useSpecularIbl); +#endif + + if(useIbl) { - // transparencies currently require IBL in the forward pass - if (o_enableIBL) + float iblExposureFactor = pow(2.0, SceneSrg::m_iblExposure); + + if(useDiffuseIbl) { - float3 iblDiffuse = 0.0f; - ApplyIblDiffuse( - surface.normal, - surface.albedo, - lightingData.diffuseResponse, - iblDiffuse); - - float3 iblSpecular = 0.0f; - ApplyIblSpecular( - surface.position, - surface.normal, - surface.specularF0, - surface.roughnessLinear, - lightingData.dirToCamera, - lightingData.brdf, - iblSpecular); - - // Adjust IBL lighting by exposure. - float iblExposureFactor = pow(2.0, SceneSrg::m_iblExposure); + float3 iblDiffuse = GetIblDiffuse(surface.normal, surface.albedo, lightingData.diffuseResponse); lightingData.diffuseLighting += (iblDiffuse * iblExposureFactor * lightingData.diffuseAmbientOcclusion); - lightingData.specularLighting += (iblSpecular * iblExposureFactor); } - } - else if (o_meshUseForwardPassIBLSpecular || o_materialUseForwardPassIBLSpecular) - { - if (o_enableIBL) - { - float3 iblSpecular = 0.0f; - ApplyIblSpecular( - surface.position, - surface.normal, - surface.specularF0, - surface.roughnessLinear, - lightingData.dirToCamera, - lightingData.brdf, - iblSpecular); + if(useSpecularIbl) + { + float3 iblSpecular = GetIblSpecular(surface.position, surface.normal, surface.specularF0, surface.roughnessLinear, lightingData.dirToCamera, lightingData.brdf); iblSpecular *= lightingData.multiScatterCompensation; - if (o_clearCoat_feature_enabled) + if (o_clearCoat_feature_enabled && surface.clearCoat.factor > 0.0f) { - if (surface.clearCoat.factor > 0.0f) - { - float clearCoatNdotV = saturate(dot(surface.clearCoat.normal, lightingData.dirToCamera)); - clearCoatNdotV = max(clearCoatNdotV, 0.01f); // [GFX TODO][ATOM-4466] This is a current band-aid for specular noise at grazing angles. - float2 clearCoatBrdf = PassSrg::m_brdfMap.Sample(PassSrg::LinearSampler, GetBRDFTexCoords(surface.clearCoat.roughness, clearCoatNdotV)).rg; + float clearCoatNdotV = saturate(dot(surface.clearCoat.normal, lightingData.dirToCamera)); + clearCoatNdotV = max(clearCoatNdotV, 0.01f); // [GFX TODO][ATOM-4466] This is a current band-aid for specular noise at grazing angles. + float2 clearCoatBrdf = PassSrg::m_brdfMap.Sample(PassSrg::LinearSampler, GetBRDFTexCoords(surface.clearCoat.roughness, clearCoatNdotV)).rg; - // clear coat uses fixed IOR = 1.5 represents polyurethane which is the most common material for gloss clear coat - // coat layer assumed to be dielectric thus don't need multiple scattering compensation - float3 clearCoatSpecularF0 = float3(0.04f, 0.04f, 0.04f); - float3 clearCoatIblSpecular = 0.0f; + // clear coat uses fixed IOR = 1.5 represents polyurethane which is the most common material for gloss clear coat + // coat layer assumed to be dielectric thus don't need multiple scattering compensation + float3 clearCoatSpecularF0 = float3(0.04f, 0.04f, 0.04f); + float3 clearCoatIblSpecular = GetIblSpecular(surface.position, surface.clearCoat.normal, clearCoatSpecularF0, surface.clearCoat.roughness, lightingData.dirToCamera, clearCoatBrdf); - ApplyIblSpecular( - surface.position, - surface.clearCoat.normal, - clearCoatSpecularF0, - surface.clearCoat.roughness, - lightingData.dirToCamera, - clearCoatBrdf, - clearCoatIblSpecular); - - clearCoatIblSpecular *= surface.clearCoat.factor; + clearCoatIblSpecular *= surface.clearCoat.factor; - // attenuate base layer energy - float3 clearCoatResponse = FresnelSchlickWithRoughness(clearCoatNdotV, clearCoatSpecularF0, surface.clearCoat.roughness) * surface.clearCoat.factor; - iblSpecular = iblSpecular * (1.0 - clearCoatResponse) * (1.0 - clearCoatResponse) + clearCoatIblSpecular; - } + // attenuate base layer energy + float3 clearCoatResponse = FresnelSchlickWithRoughness(clearCoatNdotV, clearCoatSpecularF0, surface.clearCoat.roughness) * surface.clearCoat.factor; + iblSpecular = iblSpecular * (1.0 - clearCoatResponse) * (1.0 - clearCoatResponse) + clearCoatIblSpecular; } - - float iblExposureFactor = pow(2.0f, SceneSrg::m_iblExposure); lightingData.specularLighting += (iblSpecular * iblExposureFactor); } } diff --git a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/ShaderQualityOptions.azsli b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/ShaderQualityOptions.azsli new file mode 100644 index 0000000000..cc4aa7cf42 --- /dev/null +++ b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/ShaderQualityOptions.azsli @@ -0,0 +1,26 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#pragma once + +// This file translates quality option macros like QUALITY_LOW_END to their relevant settings + +#ifdef QUALITY_LOW_END + + // Unifies the forward output into a single lighting buffer instead of splitting it into a GBuffer + #define UNIFIED_FORWARD_OUTPUT 1 + + // Forces IBL lighting to be executed in the forward pass instead of subsequent refleciton passes + #define FORCE_IBL_IN_FORWARD_PASS 1 + +#endif + diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/LightCulling/LightCullingTilePrepare.azsl b/Gems/Atom/Feature/Common/Assets/Shaders/LightCulling/LightCullingTilePrepare.azsl index fba2175c9b..a8b2ab76db 100644 --- a/Gems/Atom/Feature/Common/Assets/Shaders/LightCulling/LightCullingTilePrepare.azsl +++ b/Gems/Atom/Feature/Common/Assets/Shaders/LightCulling/LightCullingTilePrepare.azsl @@ -150,7 +150,7 @@ uint ComputeTransparentBitMask(float2 minmaxZ) return 0; } - float2 minmaxZ_transparent = Depth_to_Z(minmaxDepth_transparent, PassSrg::m_constantData.m_unprojectZ); + float2 minmaxZ_transparent = DepthBufferToViewSpace(minmaxDepth_transparent, PassSrg::m_constantData.m_unprojectZ); float2 minmaxUnit_transparent = RemapZToUnit(minmaxZ_transparent, minmaxZ); @@ -295,7 +295,7 @@ void MainCS( float2 minmaxDepth_opaque = ComputeDepthMinMaxFrom2Samples(opaqueDepthSamples); minmaxDepth_both = ExpandMinMax(minmaxDepth_opaque, minmaxDepth_transparent); UpdateMinMaxFromAllThreads(minmaxDepth_both, minmaxDepth_transparent, isPixelOnScreen); - minmaxDepth_both = Depth_to_Z(minmaxDepth_both, PassSrg::m_constantData.m_unprojectZ); + minmaxDepth_both = DepthBufferToViewSpace(minmaxDepth_both, PassSrg::m_constantData.m_unprojectZ); // if zNear == zFar we want to map z == zNear to 0-bit, so we have to keep zNear without modifications minmaxDepth_both.y = IncrementULP(minmaxDepth_both.y); @@ -313,7 +313,7 @@ void MainCS( float2 minmaxDepth_opaque = ComputeDepthMinMaxFrom4Samples(opaqueDepthSamples); minmaxDepth_both = ExpandMinMax(minmaxDepth_opaque, minmaxDepth_transparent); UpdateMinMaxFromAllThreads(minmaxDepth_both, minmaxDepth_transparent, isPixelOnScreen); - minmaxDepth_both = Depth_to_Z(minmaxDepth_both, PassSrg::m_constantData.m_unprojectZ); + minmaxDepth_both = DepthBufferToViewSpace(minmaxDepth_both, PassSrg::m_constantData.m_unprojectZ); // if zNear == zFar we want to map z == zNear to 0-bit, so we have to keep zNear without modifications minmaxDepth_both.y = IncrementULP(minmaxDepth_both.y); diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/SkyBox/SkyBox.azsl b/Gems/Atom/Feature/Common/Assets/Shaders/SkyBox/SkyBox.azsl index 1ee30a4f98..1bebb2ec47 100644 --- a/Gems/Atom/Feature/Common/Assets/Shaders/SkyBox/SkyBox.azsl +++ b/Gems/Atom/Feature/Common/Assets/Shaders/SkyBox/SkyBox.azsl @@ -10,6 +10,9 @@ * */ +// --- Static Options Available --- +// SKYBOX_TWO_OUTPUTS - Skybox renders to two rendertargets instead of one (SkyBox_TwoOutputs.pass writes to specular and reflection targets) + #include #include #include @@ -102,7 +105,9 @@ float3 GetCubemapCoords(float3 original) struct PSOutput { float4 m_specular : SV_Target0; +#ifdef SKYBOX_TWO_OUTPUTS float4 m_reflection : SV_Target1; +#endif }; PSOutput MainPS(VSOutput input) @@ -163,6 +168,8 @@ PSOutput MainPS(VSOutput input) PSOutput OUT; OUT.m_specular = float4(color, 1.0); +#ifdef SKYBOX_TWO_OUTPUTS OUT.m_reflection = float4(color, 1.0); +#endif return OUT; } diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/SkyBox/SkyBox_TwoOutputs.azsl b/Gems/Atom/Feature/Common/Assets/Shaders/SkyBox/SkyBox_TwoOutputs.azsl new file mode 100644 index 0000000000..feacd2f44f --- /dev/null +++ b/Gems/Atom/Feature/Common/Assets/Shaders/SkyBox/SkyBox_TwoOutputs.azsl @@ -0,0 +1,17 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +// NOTE: This file is a temporary workaround until .shader files can #define macros for their .azsl files + +#define SKYBOX_TWO_OUTPUTS + +#include "SkyBox.azsl" diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/SkyBox/SkyBox_TwoOutputs.shader b/Gems/Atom/Feature/Common/Assets/Shaders/SkyBox/SkyBox_TwoOutputs.shader new file mode 100644 index 0000000000..ec80d4a20e --- /dev/null +++ b/Gems/Atom/Feature/Common/Assets/Shaders/SkyBox/SkyBox_TwoOutputs.shader @@ -0,0 +1,22 @@ +{ + "Source" : "SkyBox_TwoOutputs", + + "DepthStencilState" : { + "Depth" : { "Enable" : true, "CompareFunc" : "GreaterEqual" } + }, + + "ProgramSettings": + { + "EntryPoints": + [ + { + "name": "MainVS", + "type": "Vertex" + }, + { + "name": "MainPS", + "type": "Fragment" + } + ] + } +} diff --git a/Gems/Atom/Feature/Common/Assets/atom_feature_common_asset_files.cmake b/Gems/Atom/Feature/Common/Assets/atom_feature_common_asset_files.cmake index 84ef494216..f1d8fa81be 100644 --- a/Gems/Atom/Feature/Common/Assets/atom_feature_common_asset_files.cmake +++ b/Gems/Atom/Feature/Common/Assets/atom_feature_common_asset_files.cmake @@ -38,6 +38,7 @@ set(FILES Materials/Types/StandardMultilayerPBR_ForwardPass_EDS.shader Materials/Types/StandardMultilayerPBR_Parallax.lua Materials/Types/StandardMultilayerPBR_ParallaxPerLayer.lua + Materials/Types/StandardMultilayerPBR_ShaderEnable.lua Materials/Types/StandardMultilayerPBR_Shadowmap_WithPS.azsl Materials/Types/StandardMultilayerPBR_Shadowmap_WithPS.shader Materials/Types/StandardPBR.materialtype @@ -52,6 +53,9 @@ set(FILES Materials/Types/StandardPBR_ForwardPass_EDS.shader Materials/Types/StandardPBR_HandleOpacityDoubleSided.lua Materials/Types/StandardPBR_HandleOpacityMode.lua + Materials/Types/StandardPBR_LowEndForward.azsl + Materials/Types/StandardPBR_LowEndForward.shader + Materials/Types/StandardPBR_LowEndForward_EDS.shader Materials/Types/StandardPBR_ParallaxState.lua Materials/Types/StandardPBR_Roughness.lua Materials/Types/StandardPBR_ShaderEnable.lua @@ -116,6 +120,7 @@ set(FILES Passes/DiffuseProbeGridBlendDistance.pass Passes/DiffuseProbeGridBlendIrradiance.pass Passes/DiffuseProbeGridBorderUpdate.pass + Passes/DiffuseProbeGridClassification.pass Passes/DiffuseProbeGridDownsample.pass Passes/DiffuseProbeGridRayTracing.pass Passes/DiffuseProbeGridRelocation.pass @@ -144,6 +149,7 @@ set(FILES Passes/FullscreenCopy.pass Passes/FullscreenOutputOnly.pass Passes/ImGui.pass + Passes/LightAdaptationParent.pass Passes/LightCulling.pass Passes/LightCullingHeatmap.pass Passes/LightCullingParent.pass @@ -152,6 +158,8 @@ set(FILES Passes/LightCullingTilePrepareMSAA.pass Passes/LookModificationComposite.pass Passes/LookModificationTransform.pass + Passes/LowEndForward.pass + Passes/LowEndPipeline.pass Passes/LuminanceHeatmap.pass Passes/LuminanceHistogramGenerator.pass Passes/MainPipeline.pass @@ -179,13 +187,16 @@ set(FILES Passes/ReflectionScreenSpace.pass Passes/ReflectionScreenSpaceBlur.pass Passes/ReflectionScreenSpaceBlurHorizontal.pass + Passes/ReflectionScreenSpaceBlurMobile.pass Passes/ReflectionScreenSpaceBlurVertical.pass Passes/ReflectionScreenSpaceComposite.pass + Passes/ReflectionScreenSpaceMobile.pass Passes/ReflectionScreenSpaceTrace.pass Passes/Reflections_nomsaa.pass Passes/ShadowParent.pass Passes/Skinning.pass Passes/SkyBox.pass + Passes/SkyBox_TwoOutputs.pass Passes/SMAA1xApplyLinearHDRColor.pass Passes/SMAA1xApplyPerceptualColor.pass Passes/SMAABlendingWeightCalculation.pass @@ -205,6 +216,7 @@ set(FILES ShaderLib/Atom/Features/IndirectRendering.azsli ShaderLib/Atom/Features/MatrixUtility.azsli ShaderLib/Atom/Features/ParallaxMapping.azsli + ShaderLib/Atom/Features/ShaderQualityOptions.azsli ShaderLib/Atom/Features/SphericalHarmonicsUtility.azsli ShaderLib/Atom/Features/SrgSemantics.azsli ShaderLib/Atom/Features/ColorManagement/TransformColor.azsli @@ -272,6 +284,7 @@ set(FILES ShaderLib/Atom/Features/PostProcessing/GlyphData.azsli ShaderLib/Atom/Features/PostProcessing/GlyphRender.azsli ShaderLib/Atom/Features/PostProcessing/PostProcessUtil.azsli + ShaderLib/Atom/Features/RayTracing/RayTracingSceneSrg.azsli ShaderLib/Atom/Features/ScreenSpace/ScreenSpaceUtil.azsli ShaderLib/Atom/Features/Shadow/BicubicPcfFilters.azsli ShaderLib/Atom/Features/Shadow/DirectionalLightShadow.azsli @@ -471,4 +484,6 @@ set(FILES Shaders/SkinnedMesh/LinearSkinningPassSRG.azsli Shaders/SkyBox/SkyBox.azsl Shaders/SkyBox/SkyBox.shader + Shaders/SkyBox/SkyBox_TwoOutputs.azsl + Shaders/SkyBox/SkyBox_TwoOutputs.shader ) diff --git a/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/MorphTargets/MorphTargetInputBuffers.h b/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/MorphTargets/MorphTargetInputBuffers.h index 56ecd7de4d..8bef9ea427 100644 --- a/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/MorphTargets/MorphTargetInputBuffers.h +++ b/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/MorphTargets/MorphTargetInputBuffers.h @@ -53,6 +53,7 @@ namespace AZ void SetBufferViewsOnShaderResourceGroup(const Data::Instance& perInstanceSRG); private: RHI::Ptr m_vertexDeltaBufferView; + Data::Instance m_vertexDeltaBuffer; }; struct MorphTargetMetaData diff --git a/Gems/Atom/Feature/Common/Code/Source/MorphTargets/MorphTargetComputePass.cpp b/Gems/Atom/Feature/Common/Code/Source/MorphTargets/MorphTargetComputePass.cpp index 35793b82a3..1bb537ecef 100644 --- a/Gems/Atom/Feature/Common/Code/Source/MorphTargets/MorphTargetComputePass.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/MorphTargets/MorphTargetComputePass.cpp @@ -12,6 +12,7 @@ #include +#include #include #include @@ -38,6 +39,11 @@ namespace AZ return m_shader; } + void MorphTargetComputePass::SetFeatureProcessor(SkinnedMeshFeatureProcessor* skinnedMeshFeatureProcessor) + { + m_skinnedMeshFeatureProcessor = skinnedMeshFeatureProcessor; + } + void MorphTargetComputePass::BuildAttachmentsInternal() { // The same buffer that skinning writes to is used to manage the computed vertex deltas that are passed from the @@ -45,30 +51,16 @@ namespace AZ AttachBufferToSlot(Name{ "MorphTargetDeltaOutput" }, SkinnedMeshOutputStreamManagerInterface::Get()->GetBuffer()); } - void MorphTargetComputePass::AddDispatchItem(const RHI::DispatchItem* dispatchItem) - { - AZ_Assert(dispatchItem != nullptr, "invalid dispatchItem"); - - AZStd::lock_guard lock(m_mutex); - //using an unordered_set here to prevent redundantly adding the same dispatchItem to the submission queue - //(i.e. if the same morph target exists in multiple views, it can call AddDispatchItem multiple times with the same item) - m_dispatches.insert(dispatchItem); - } - void MorphTargetComputePass::BuildCommandListInternal(const RHI::FrameGraphExecuteContext& context) { - RHI::CommandList* commandList = context.GetCommandList(); - - SetSrgsForDispatch(commandList); - - AZStd::lock_guard lock(m_mutex); - for (const RHI::DispatchItem* dispatchItem : m_dispatches) + if (m_skinnedMeshFeatureProcessor) { - commandList->Submit(*dispatchItem); - } + RHI::CommandList* commandList = context.GetCommandList(); - // Clear the dispatch items. They will need to be re-populated next frame - m_dispatches.clear(); + SetSrgsForDispatch(commandList); + + m_skinnedMeshFeatureProcessor->SubmitMorphTargetDispatchItems(commandList); + } } } // namespace Render } // namespace AZ diff --git a/Gems/Atom/Feature/Common/Code/Source/MorphTargets/MorphTargetComputePass.h b/Gems/Atom/Feature/Common/Code/Source/MorphTargets/MorphTargetComputePass.h index 3967d9190e..61fa485fbc 100644 --- a/Gems/Atom/Feature/Common/Code/Source/MorphTargets/MorphTargetComputePass.h +++ b/Gems/Atom/Feature/Common/Code/Source/MorphTargets/MorphTargetComputePass.h @@ -18,6 +18,8 @@ namespace AZ { namespace Render { + class SkinnedMeshFeatureProcessor; + //! The morph target compute pass submits dispatch items for morph targets. The dispatch items are cleared every frame, so it needs to be re-populated. class MorphTargetComputePass : public RPI::ComputePass @@ -31,16 +33,14 @@ namespace AZ static RPI::Ptr Create(const RPI::PassDescriptor& descriptor); - //! Thread-safe function for adding a dispatch item to the current frame. - void AddDispatchItem(const RHI::DispatchItem* dispatchItem); Data::Instance GetShader() const; + void SetFeatureProcessor(SkinnedMeshFeatureProcessor* m_skinnedMeshFeatureProcessor); private: void BuildAttachmentsInternal() override; void BuildCommandListInternal(const RHI::FrameGraphExecuteContext& context) override; - AZStd::mutex m_mutex; - AZStd::unordered_set m_dispatches; + SkinnedMeshFeatureProcessor* m_skinnedMeshFeatureProcessor = nullptr; }; } } diff --git a/Gems/Atom/Feature/Common/Code/Source/MorphTargets/MorphTargetDispatchItem.cpp b/Gems/Atom/Feature/Common/Code/Source/MorphTargets/MorphTargetDispatchItem.cpp index d7bbc315ed..7b3aedd64e 100644 --- a/Gems/Atom/Feature/Common/Code/Source/MorphTargets/MorphTargetDispatchItem.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/MorphTargets/MorphTargetDispatchItem.cpp @@ -11,7 +11,7 @@ */ #include -#include +#include #include #include @@ -30,7 +30,7 @@ namespace AZ MorphTargetDispatchItem::MorphTargetDispatchItem( const AZStd::intrusive_ptr inputBuffers, const MorphTargetMetaData& morphTargetMetaData, - RPI::Ptr morphTargetComputePass, + SkinnedMeshFeatureProcessor* skinnedMeshFeatureProcessor, MorphTargetInstanceMetaData morphInstanceMetaData, float morphDeltaIntegerEncoding) : m_inputBuffers(inputBuffers) @@ -38,7 +38,7 @@ namespace AZ , m_morphInstanceMetaData(morphInstanceMetaData) , m_accumulatedDeltaIntegerEncoding(morphDeltaIntegerEncoding) { - m_morphTargetShader = morphTargetComputePass->GetShader(); + m_morphTargetShader = skinnedMeshFeatureProcessor->GetMorphTargetShader(); RPI::ShaderReloadNotificationBus::Handler::BusConnect(m_morphTargetShader->GetAssetId()); } diff --git a/Gems/Atom/Feature/Common/Code/Source/MorphTargets/MorphTargetDispatchItem.h b/Gems/Atom/Feature/Common/Code/Source/MorphTargets/MorphTargetDispatchItem.h index 46680eb465..ad1fd969a5 100644 --- a/Gems/Atom/Feature/Common/Code/Source/MorphTargets/MorphTargetDispatchItem.h +++ b/Gems/Atom/Feature/Common/Code/Source/MorphTargets/MorphTargetDispatchItem.h @@ -37,7 +37,7 @@ namespace AZ namespace Render { - class MorphTargetComputePass; + class SkinnedMeshFeatureProcessor; //! Holds and manages an RHI DispatchItem for a specific morph target, and the resources that are needed to build and maintain it. class MorphTargetDispatchItem @@ -51,7 +51,7 @@ namespace AZ explicit MorphTargetDispatchItem( const AZStd::intrusive_ptr inputBuffers, const MorphTargetMetaData& morphTargetMetaData, - RPI::Ptr morphTargetComputePass, + SkinnedMeshFeatureProcessor* skinnedMeshFeatureProcessor, MorphTargetInstanceMetaData morphInstanceMetaData, float accumulatedDeltaRange ); diff --git a/Gems/Atom/Feature/Common/Code/Source/MorphTargets/MorphTargetInputBuffers.cpp b/Gems/Atom/Feature/Common/Code/Source/MorphTargets/MorphTargetInputBuffers.cpp index 35437f2b1e..e6fa8bb54e 100644 --- a/Gems/Atom/Feature/Common/Code/Source/MorphTargets/MorphTargetInputBuffers.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/MorphTargets/MorphTargetInputBuffers.cpp @@ -30,16 +30,18 @@ namespace AZ { MorphTargetInputBuffers::MorphTargetInputBuffers(const RPI::BufferAssetView& bufferAssetView, const AZStd::string& bufferNamePrefix) { - auto buffer = RPI::Buffer::FindOrCreate(bufferAssetView.GetBufferAsset()); - - AZ::RHI::Ptr bufferView = RHI::Factory::Get().CreateBufferView(); + m_vertexDeltaBuffer = RPI::Buffer::FindOrCreate(bufferAssetView.GetBufferAsset()); + if (m_vertexDeltaBuffer) { - bufferView->SetName(Name(bufferNamePrefix + "MorphTargetVertexDeltaView")); - [[maybe_unused]] RHI::ResultCode resultCode = bufferView->Init(*buffer->GetRHIBuffer(), bufferAssetView.GetBufferViewDescriptor()); - AZ_Error("MorphTargetInputBuffers", resultCode == RHI::ResultCode::Success, "Failed to initialize buffer view for morph target."); - } + AZ::RHI::Ptr bufferView = RHI::Factory::Get().CreateBufferView(); + { + bufferView->SetName(Name(bufferNamePrefix + "MorphTargetVertexDeltaView")); + [[maybe_unused]] RHI::ResultCode resultCode = bufferView->Init(*m_vertexDeltaBuffer->GetRHIBuffer(), bufferAssetView.GetBufferViewDescriptor()); + AZ_Error("MorphTargetInputBuffers", resultCode == RHI::ResultCode::Success, "Failed to initialize buffer view for morph target."); + } - m_vertexDeltaBufferView = bufferView; + m_vertexDeltaBufferView = bufferView; + } } void MorphTargetInputBuffers::SetBufferViewsOnShaderResourceGroup(const Data::Instance& perInstanceSRG) diff --git a/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshComputePass.cpp b/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshComputePass.cpp index d7b2c605b4..a3feddb0b6 100644 --- a/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshComputePass.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshComputePass.cpp @@ -12,6 +12,7 @@ #include +#include #include #include @@ -22,11 +23,9 @@ namespace AZ { namespace Render { - SkinnedMeshComputePass::SkinnedMeshComputePass(const RPI::PassDescriptor& descriptor) : RPI::ComputePass(descriptor) { - m_cachedShaderOptions.SetShader(m_shader); } RPI::Ptr SkinnedMeshComputePass::Create(const RPI::PassDescriptor& descriptor) @@ -40,42 +39,30 @@ namespace AZ return m_shader; } - RPI::ShaderOptionGroup SkinnedMeshComputePass::CreateShaderOptionGroup(const SkinnedMeshShaderOptions shaderOptions, SkinnedMeshShaderOptionNotificationBus::Handler& shaderReinitializedHandler) + void SkinnedMeshComputePass::SetFeatureProcessor(SkinnedMeshFeatureProcessor* skinnedMeshFeatureProcessor) { - m_cachedShaderOptions.ConnectToShaderReinitializedEvent(shaderReinitializedHandler); - return m_cachedShaderOptions.CreateShaderOptionGroup(shaderOptions); - } - - void SkinnedMeshComputePass::AddDispatchItem(const RHI::DispatchItem* dispatchItem) - { - AZ_Assert(dispatchItem != nullptr, "invalid dispatchItem"); - - AZStd::lock_guard lock(m_mutex); - //using an unordered_set here to prevent redundantly adding the same dispatchItem to the submission queue - //(i.e. if the same skinnedMesh exists in multiple views, it can call AddDispatchItem multiple times with the same item) - m_dispatches.insert(dispatchItem); + m_skinnedMeshFeatureProcessor = skinnedMeshFeatureProcessor; } void SkinnedMeshComputePass::BuildCommandListInternal(const RHI::FrameGraphExecuteContext& context) { - RHI::CommandList* commandList = context.GetCommandList(); - - SetSrgsForDispatch(commandList); - - AZStd::lock_guard lock(m_mutex); - for (const RHI::DispatchItem* dispatchItem : m_dispatches) + if (m_skinnedMeshFeatureProcessor) { - commandList->Submit(*dispatchItem); - } + RHI::CommandList* commandList = context.GetCommandList(); - // Clear the dispatch items. They will need to be re-populated next frame - m_dispatches.clear(); + SetSrgsForDispatch(commandList); + + m_skinnedMeshFeatureProcessor->SubmitSkinningDispatchItems(commandList); + } } void SkinnedMeshComputePass::OnShaderReinitialized(const RPI::Shader& shader) { ComputePass::OnShaderReinitialized(shader); - m_cachedShaderOptions.SetShader(m_shader); + if (m_skinnedMeshFeatureProcessor) + { + m_skinnedMeshFeatureProcessor->OnSkinningShaderReinitialized(m_shader); + } } void SkinnedMeshComputePass::OnShaderVariantReinitialized(const RPI::Shader& shader, const RPI::ShaderVariantId&, RPI::ShaderVariantStableId) diff --git a/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshComputePass.h b/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshComputePass.h index d2e0fb77dc..5f7ff08e47 100644 --- a/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshComputePass.h +++ b/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshComputePass.h @@ -20,6 +20,8 @@ namespace AZ { namespace Render { + class SkinnedMeshFeatureProcessor; + //! The skinned mesh compute pass submits dispatch items for skinning. The dispatch items are cleared every frame, so it needs to be re-populated. class SkinnedMeshComputePass : public RPI::ComputePass @@ -33,10 +35,9 @@ namespace AZ static RPI::Ptr Create(const RPI::PassDescriptor& descriptor); - //! Thread-safe function for adding a dispatch item to the current frame. - void AddDispatchItem(const RHI::DispatchItem* dispatchItem); Data::Instance GetShader() const; - RPI::ShaderOptionGroup CreateShaderOptionGroup(const SkinnedMeshShaderOptions shaderOptions, SkinnedMeshShaderOptionNotificationBus::Handler& shaderReinitializedHandler); + + void SetFeatureProcessor(SkinnedMeshFeatureProcessor* m_skinnedMeshFeatureProcessor); private: void BuildCommandListInternal(const RHI::FrameGraphExecuteContext& context) override; @@ -45,9 +46,7 @@ namespace AZ void OnShaderReinitialized(const RPI::Shader& shader) override; void OnShaderVariantReinitialized(const RPI::Shader& shader, const RPI::ShaderVariantId& shaderVariantId, RPI::ShaderVariantStableId shaderVariantStableId) override; - AZStd::mutex m_mutex; - AZStd::unordered_set m_dispatches; - CachedSkinnedMeshShaderOptions m_cachedShaderOptions; + SkinnedMeshFeatureProcessor* m_skinnedMeshFeatureProcessor = nullptr; }; } } diff --git a/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshDispatchItem.cpp b/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshDispatchItem.cpp index c9054fc6c8..647a87a788 100644 --- a/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshDispatchItem.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshDispatchItem.cpp @@ -12,7 +12,7 @@ #include #include -#include +#include #include #include @@ -34,7 +34,7 @@ namespace AZ size_t lodIndex, Data::Instance boneTransforms, const SkinnedMeshShaderOptions& shaderOptions, - RPI::Ptr skinnedMeshComputePass, + SkinnedMeshFeatureProcessor* skinnedMeshFeatureProcessor, MorphTargetInstanceMetaData morphTargetInstanceMetaData, float morphTargetDeltaIntegerEncoding) : m_inputBuffers(inputBuffers) @@ -45,7 +45,7 @@ namespace AZ , m_morphTargetInstanceMetaData(morphTargetInstanceMetaData) , m_morphTargetDeltaIntegerEncoding(morphTargetDeltaIntegerEncoding) { - m_skinningShader = skinnedMeshComputePass->GetShader(); + m_skinningShader = skinnedMeshFeatureProcessor->GetSkinningShader(); // Shader options are generally set per-skinned mesh instance, but morph targets may only exist on some lods. Override the option for applying morph targets here if (m_morphTargetInstanceMetaData.m_accumulatedPositionDeltaOffsetInBytes != MorphTargetConstants::s_invalidDeltaOffset) @@ -58,7 +58,7 @@ namespace AZ } // CreateShaderOptionGroup will also connect to the SkinnedMeshShaderOptionNotificationBus - m_shaderOptionGroup = skinnedMeshComputePass->CreateShaderOptionGroup(m_shaderOptions, *this); + m_shaderOptionGroup = skinnedMeshFeatureProcessor->CreateSkinningShaderOptionGroup(m_shaderOptions, *this); } SkinnedMeshDispatchItem::~SkinnedMeshDispatchItem() diff --git a/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshDispatchItem.h b/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshDispatchItem.h index c80b002106..33bec89afd 100644 --- a/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshDispatchItem.h +++ b/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshDispatchItem.h @@ -38,7 +38,7 @@ namespace AZ namespace Render { - class SkinnedMeshComputePass; + class SkinnedMeshFeatureProcessor; //! Holds and manages an RHI DispatchItem for a specific skinned mesh, and the resources that are needed to build and maintain it. class SkinnedMeshDispatchItem @@ -55,7 +55,7 @@ namespace AZ size_t lodIndex, Data::Instance skinningMatrices, const SkinnedMeshShaderOptions& shaderOptions, - RPI::Ptr skinnedMeshComputePass, + SkinnedMeshFeatureProcessor* skinnedMeshFeatureProcessor, MorphTargetInstanceMetaData morphTargetInstanceMetaData, float morphTargetDeltaIntegerEncoding ); diff --git a/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshFeatureProcessor.cpp b/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshFeatureProcessor.cpp index 193a36e588..388f4112a0 100644 --- a/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshFeatureProcessor.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshFeatureProcessor.cpp @@ -24,8 +24,10 @@ #include #include #include +#include #include +#include #include #include @@ -84,11 +86,6 @@ namespace AZ AZ_PROFILE_FUNCTION(Debug::ProfileCategory::AzRender); AZ_ATOM_PROFILE_FUNCTION("SkinnedMesh", "SkinnedMeshFeatureProcessor: Render"); - if (!m_skinningPass) - { - return; - } - #if 0 //[GFX_TODO][ATOM-13564] Temporarily disable skinning culling until we figure out how to hook up visibility & lod selection with skinning: //Setup the culling workgroup (it will be re-used for each view) { @@ -132,7 +129,7 @@ namespace AZ //Dispatch the workgroup to each view for (const RPI::ViewPtr& viewPtr : packet.m_views) { - Job *processWorkgroupJob = AZ::CreateJobFunction( + Job* processWorkgroupJob = AZ::CreateJobFunction( [this, cullingSystem, viewPtr](AZ::Job& thisJob) { AZ_PROFILE_SCOPE_DYNAMIC(Debug::ProfileCategory::AzRender, "skinningMeshFP processWorkgroupJob - View: %s", viewPtr->GetName().GetCStr()); @@ -167,7 +164,16 @@ namespace AZ float maxScreenPercentage(lod.m_range.m_max); if (approxScreenPercentage >= minScreenPercentage && approxScreenPercentage <= maxScreenPercentage) { - m_skinningPass->AddDispatchItem(&renderProxy->m_dispatchItemsByLod[lodIndex]->GetRHIDispatchItem()); + AZStd::lock_guard lock(m_dispatchItemMutex); + m_skinningDispatches.insert(&renderProxy->m_dispatchItemsByLod[lodIndex]->GetRHIDispatchItem()); + for (size_t morphTargetIndex = 0; morphTargetIndex < renderProxy->m_morphTargetDispatchItemsByLod[lodIndex].size(); morphTargetIndex++) + { + const MorphTargetDispatchItem* dispatchItem = renderProxy->m_morphTargetDispatchItemsByLod[lodIndex][morphTargetIndex].get(); + if (dispatchItem && dispatchItem->GetWeight() > AZ::Constants::FloatEpsilon) + { + m_morphTargetDispatches.insert(&dispatchItem->GetRHIDispatchItem()); + } + } } } } @@ -232,13 +238,14 @@ namespace AZ //Note that this supports overlapping lod ranges (to support cross-fading lods, for example) if (approxScreenPercentage >= lod.m_screenCoverageMin && approxScreenPercentage <= lod.m_screenCoverageMax) { - m_skinningPass->AddDispatchItem(&renderProxy.m_dispatchItemsByLod[lodIndex]->GetRHIDispatchItem()); + AZStd::lock_guard lock(m_dispatchItemMutex); + m_skinningDispatches.insert(&renderProxy.m_dispatchItemsByLod[lodIndex]->GetRHIDispatchItem()); for (size_t morphTargetIndex = 0; morphTargetIndex < renderProxy.m_morphTargetDispatchItemsByLod[lodIndex].size(); morphTargetIndex++) { const MorphTargetDispatchItem* dispatchItem = renderProxy.m_morphTargetDispatchItemsByLod[lodIndex][morphTargetIndex].get(); if (dispatchItem && dispatchItem->GetWeight() > AZ::Constants::FloatEpsilon) { - m_morphTargetPass->AddDispatchItem(&dispatchItem->GetRHIDispatchItem()); + m_morphTargetDispatches.insert(&dispatchItem->GetRHIDispatchItem()); } } } @@ -248,19 +255,14 @@ namespace AZ #endif } - void SkinnedMeshFeatureProcessor::OnRenderPipelineAdded([[maybe_unused]] RPI::RenderPipelinePtr pipeline) + void SkinnedMeshFeatureProcessor::OnRenderPipelineAdded(RPI::RenderPipelinePtr pipeline) { - InitSkinningAndMorphPass(); + InitSkinningAndMorphPass(pipeline->GetRootPass()); } - void SkinnedMeshFeatureProcessor::OnRenderPipelineRemoved([[maybe_unused]] RPI::RenderPipeline* pipeline) + void SkinnedMeshFeatureProcessor::OnRenderPipelinePassesChanged(RPI::RenderPipeline* renderPipeline) { - InitSkinningAndMorphPass(); - } - - void SkinnedMeshFeatureProcessor::OnRenderPipelinePassesChanged([[maybe_unused]] RPI::RenderPipeline* renderPipeline) - { - InitSkinningAndMorphPass(); + InitSkinningAndMorphPass(renderPipeline->GetRootPass()); } void SkinnedMeshFeatureProcessor::OnBeginPrepareRender() @@ -268,9 +270,15 @@ namespace AZ m_renderProxiesChecker.soft_lock(); } - void SkinnedMeshFeatureProcessor::OnEndPrepareRender() + void SkinnedMeshFeatureProcessor::OnRenderEnd() { m_renderProxiesChecker.soft_unlock(); + + // Clear any dispatch items that were added but never submitted + // in case there were no passes that submitted this frame + // because they execute at a lower frequency + m_skinningDispatches.clear(); + m_morphTargetDispatches.clear(); } SkinnedMeshRenderProxyHandle SkinnedMeshFeatureProcessor::AcquireRenderProxy(const SkinnedMeshRenderProxyDesc& desc) @@ -295,61 +303,73 @@ namespace AZ return false; } - void SkinnedMeshFeatureProcessor::InitSkinningAndMorphPass() + void SkinnedMeshFeatureProcessor::InitSkinningAndMorphPass(const RPI::Ptr pipelineRootPass) { - m_skinningPass = nullptr; //reset it to null, just in case it fails to load the assets properly - m_morphTargetPass = nullptr; - - RPI::PassSystemInterface* passSystem = RPI::PassSystemInterface::Get(); - if (passSystem->HasPassesForTemplateName(AZ::Name{ "SkinningPassTemplate" })) + RPI::Ptr skinningPass = pipelineRootPass->FindPassByNameRecursive(AZ::Name{ "SkinningPass" }); + if (skinningPass) { - auto& skinningPasses = passSystem->GetPassesForTemplateName(AZ::Name{ "SkinningPassTemplate" }); + SkinnedMeshComputePass* skinnedMeshComputePass = azdynamic_cast(skinningPass.get()); + skinnedMeshComputePass->SetFeatureProcessor(this); - // For now, assume one skinning pass - if (!skinningPasses.empty() && skinningPasses[0]) + // There may be multiple skinning passes in the scene due to multiple pipelines, but there is only one skinning shader + m_skinningShader = skinnedMeshComputePass->GetShader(); + + if (!m_skinningShader) { - m_skinningPass = static_cast(skinningPasses[0]); - const Data::Instance shader = m_skinningPass->GetShader(); - - if (!shader) - { - AZ_Error(s_featureProcessorName, false, "Failed to get skinning pass shader. It may need to finish processing."); - } + AZ_Error(s_featureProcessorName, false, "Failed to get skinning pass shader. It may need to finish processing."); } else { - AZ_Error(s_featureProcessorName, false, "\"SkinningPassTemplate\" does not have any valid passes. Check your game project's .pass assets."); + m_cachedSkinningShaderOptions.SetShader(m_skinningShader); } } - else - { - AZ_Error(s_featureProcessorName, false, "Failed to find passes for \"SkinningPassTemplate\". Check your game project's .pass assets."); - } - if (passSystem->HasPassesForTemplateName(AZ::Name{ "MorphTargetPassTemplate" })) + RPI::Ptr morphTargetPass = pipelineRootPass->FindPassByNameRecursive(AZ::Name{ "MorphTargetPass" }); + if (morphTargetPass) { - auto& morphTargetPasses = passSystem->GetPassesForTemplateName(AZ::Name{ "MorphTargetPassTemplate" }); + MorphTargetComputePass* morphTargetComputePass = azdynamic_cast(morphTargetPass.get()); + morphTargetComputePass->SetFeatureProcessor(this); - // For now, assume one skinning pass - if (!morphTargetPasses.empty() && morphTargetPasses[0]) + // There may be multiple morph target passes in the scene due to multiple pipelines, but there is only one morph target shader + m_morphTargetShader = morphTargetComputePass->GetShader(); + + if (!m_morphTargetShader) { - m_morphTargetPass = static_cast(morphTargetPasses[0]); - const Data::Instance shader = m_morphTargetPass->GetShader(); + AZ_Error(s_featureProcessorName, false, "Failed to get morph target pass shader. It may need to finish processing."); + } + } + } - if (!shader) - { - AZ_Error(s_featureProcessorName, false, "Failed to get morph target pass shader. It may need to finish processing."); - } - } - else - { - AZ_Error(s_featureProcessorName, false, "\"MorphTargetPassTemplate\" does not have any valid passes. Check your game project's .pass assets."); - } - } - else + RPI::ShaderOptionGroup SkinnedMeshFeatureProcessor::CreateSkinningShaderOptionGroup(const SkinnedMeshShaderOptions shaderOptions, SkinnedMeshShaderOptionNotificationBus::Handler& shaderReinitializedHandler) + { + m_cachedSkinningShaderOptions.ConnectToShaderReinitializedEvent(shaderReinitializedHandler); + return m_cachedSkinningShaderOptions.CreateShaderOptionGroup(shaderOptions); + } + + void SkinnedMeshFeatureProcessor::OnSkinningShaderReinitialized(const Data::Instance skinningShader) + { + m_skinningShader = skinningShader; + m_cachedSkinningShaderOptions.SetShader(m_skinningShader); + } + + void SkinnedMeshFeatureProcessor::SubmitSkinningDispatchItems(RHI::CommandList* commandList) + { + AZStd::lock_guard lock(m_dispatchItemMutex); + for (const RHI::DispatchItem* dispatchItem : m_skinningDispatches) { - AZ_Error(s_featureProcessorName, false, "Failed to find passes for \"MorphTargetPassTemplate\". Check your game project's .pass assets."); + commandList->Submit(*dispatchItem); } + m_skinningDispatches.clear(); + } + + void SkinnedMeshFeatureProcessor::SubmitMorphTargetDispatchItems(RHI::CommandList* commandList) + { + AZStd::lock_guard lock(m_dispatchItemMutex); + for (const RHI::DispatchItem* dispatchItem : m_morphTargetDispatches) + { + commandList->Submit(*dispatchItem); + } + m_morphTargetDispatches.clear(); } SkinnedMeshRenderProxyInterfaceHandle SkinnedMeshFeatureProcessor::AcquireRenderProxyInterface(const SkinnedMeshRenderProxyDesc& desc) @@ -363,14 +383,14 @@ namespace AZ return ReleaseRenderProxy(handle); } - RPI::Ptr SkinnedMeshFeatureProcessor::GetSkinningPass() const + Data::Instance SkinnedMeshFeatureProcessor::GetSkinningShader() const { - return m_skinningPass; + return m_skinningShader; } - RPI::Ptr SkinnedMeshFeatureProcessor::GetMorphTargetPass() const + Data::Instance SkinnedMeshFeatureProcessor::GetMorphTargetShader() const { - return m_morphTargetPass; + return m_morphTargetShader; } } // namespace Render } // namespace AZ diff --git a/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshFeatureProcessor.h b/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshFeatureProcessor.h index 86dedfa161..bb3dd242a1 100644 --- a/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshFeatureProcessor.h +++ b/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshFeatureProcessor.h @@ -51,35 +51,46 @@ namespace AZ void Deactivate() override; void Simulate(const FeatureProcessor::SimulatePacket& packet) override; void Render(const FeatureProcessor::RenderPacket& packet) override; + void OnRenderEnd() override; // RPI::SceneNotificationBus overrides ... void OnRenderPipelineAdded(RPI::RenderPipelinePtr pipeline) override; - void OnRenderPipelineRemoved(RPI::RenderPipeline* pipeline) override; void OnRenderPipelinePassesChanged(RPI::RenderPipeline* renderPipeline) override; void OnBeginPrepareRender() override; - void OnEndPrepareRender() override; SkinnedMeshRenderProxyHandle AcquireRenderProxy(const SkinnedMeshRenderProxyDesc& desc); bool ReleaseRenderProxy(SkinnedMeshRenderProxyHandle& handle); - RPI::Ptr GetSkinningPass() const; - RPI::Ptr GetMorphTargetPass() const; + Data::Instance GetSkinningShader() const; + RPI::ShaderOptionGroup CreateSkinningShaderOptionGroup(const SkinnedMeshShaderOptions shaderOptions, SkinnedMeshShaderOptionNotificationBus::Handler& shaderReinitializedHandler); + void OnSkinningShaderReinitialized(const Data::Instance skinningShader); + void SubmitSkinningDispatchItems(RHI::CommandList* commandList); + + Data::Instance GetMorphTargetShader() const; + void SubmitMorphTargetDispatchItems(RHI::CommandList* commandList); private: AZ_DISABLE_COPY_MOVE(SkinnedMeshFeatureProcessor); - void InitSkinningAndMorphPass(); + void InitSkinningAndMorphPass(const RPI::Ptr pipelineRootPass); SkinnedMeshRenderProxyInterfaceHandle AcquireRenderProxyInterface(const SkinnedMeshRenderProxyDesc& desc) override; bool ReleaseRenderProxyInterface(SkinnedMeshRenderProxyInterfaceHandle& handle) override; static const char* s_featureProcessorName; - RPI::Ptr m_skinningPass; - RPI::Ptr m_morphTargetPass; + + Data::Instance m_skinningShader; + CachedSkinnedMeshShaderOptions m_cachedSkinningShaderOptions; + + Data::Instance m_morphTargetShader; + AZStd::concurrency_checker m_renderProxiesChecker; StableDynamicArray m_renderProxies; AZStd::unique_ptr m_statsCollector; MeshFeatureProcessor* m_meshFeatureProcessor = nullptr; + AZStd::unordered_set m_skinningDispatches; + AZStd::unordered_set m_morphTargetDispatches; + AZStd::mutex m_dispatchItemMutex; }; } // namespace Render diff --git a/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshRenderProxy.cpp b/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshRenderProxy.cpp index 90c54dfc10..090b720406 100644 --- a/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshRenderProxy.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshRenderProxy.cpp @@ -60,13 +60,7 @@ namespace AZ bool SkinnedMeshRenderProxy::BuildDispatchItem([[maybe_unused]] const RPI::Scene& scene, size_t modelLodIndex, [[maybe_unused]] const SkinnedMeshShaderOptions& shaderOptions) { - if (!m_featureProcessor->GetSkinningPass()) - { - AZ_Error("Skinned Mesh Feature Processor", false, "Failed to get Skinning Pass. Make sure the project has a skinning pass."); - return false; - } - - Data::Instance skinningShader = m_featureProcessor->GetSkinningPass()->GetShader(); + Data::Instance skinningShader = m_featureProcessor->GetSkinningShader(); if (!skinningShader) { AZ_Error("Skinned Mesh Feature Processor", false, "Failed to get skinning shader from skinning pass"); @@ -89,7 +83,7 @@ namespace AZ m_instance->m_outputStreamOffsetsInBytes[modelLodIndex], modelLodIndex, m_boneTransforms, m_shaderOptions, - m_featureProcessor->GetSkinningPass(), + m_featureProcessor, m_instance->m_morphTargetInstanceMetaData[modelLodIndex], morphDeltaIntegerEncoding }); @@ -100,7 +94,7 @@ namespace AZ } // Get the data needed to create a morph target dispatch item - Data::Instance morphTargetShader = m_featureProcessor->GetMorphTargetPass()->GetShader(); + Data::Instance morphTargetShader = m_featureProcessor->GetMorphTargetShader(); const AZStd::vector>& morphTargetInputBuffersVector = m_inputBuffers->GetMorphTargetInputBuffers(modelLodIndex); AZ_Assert(morphTargetMetaDatas.size() == morphTargetInputBuffersVector.size(), "Skinned Mesh Feature Processor - Mismatch in morph target metadata count and morph target input buffer count"); @@ -118,7 +112,7 @@ namespace AZ aznew MorphTargetDispatchItem{ morphTargetInputBuffersVector[morphTargetIndex], morphTargetMetaDatas[morphTargetIndex], - m_featureProcessor->GetMorphTargetPass(), + m_featureProcessor, m_instance->m_morphTargetInstanceMetaData[modelLodIndex], morphDeltaIntegerEncoding }); diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Pass/Pass.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Pass/Pass.h index b0d6bd4117..1cba71ae7e 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Pass/Pass.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Pass/Pass.h @@ -381,6 +381,7 @@ namespace AZ uint64_t m_createdByPassRequest : 1; uint64_t m_initialized : 1; uint64_t m_enabled : 1; + uint64_t m_parentEnabled : 1; uint64_t m_alreadyCreated : 1; uint64_t m_alreadyReset : 1; uint64_t m_alreadyPrepared : 1; diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/Pass.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/Pass.cpp index 9401d1a9e0..6ed8ac018c 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/Pass.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/Pass.cpp @@ -93,11 +93,12 @@ namespace AZ void Pass::SetEnabled(bool enabled) { m_flags.m_enabled = enabled; + OnHierarchyChange(); } bool Pass::IsEnabled() const { - return m_flags.m_enabled; + return m_flags.m_enabled && (m_flags.m_parentEnabled || m_parent == nullptr); } // --- Error Logging --- @@ -140,6 +141,7 @@ namespace AZ } // Set new tree depth and path + m_flags.m_parentEnabled = m_parent->m_flags.m_enabled && (m_parent->m_flags.m_parentEnabled || m_parent->m_parent == nullptr); m_treeDepth = m_parent->m_treeDepth + 1; m_path = ConcatPassName(m_parent->m_path, m_name); m_flags.m_partOfHierarchy = m_parent->m_flags.m_partOfHierarchy; diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/CoreLights/AreaLightComponentConfig.h b/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/CoreLights/AreaLightComponentConfig.h index fe40cabc12..31cdb34ddd 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/CoreLights/AreaLightComponentConfig.h +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/CoreLights/AreaLightComponentConfig.h @@ -62,7 +62,7 @@ namespace AZ bool m_enableShadow = false; ShadowmapSize m_shadowmapMaxSize = ShadowmapSize::Size256; ShadowFilterMethod m_shadowFilterMethod = ShadowFilterMethod::None; - PcfMethod m_pcfMethod = PcfMethod::BoundarySearch; + PcfMethod m_pcfMethod = PcfMethod::Bicubic; float m_boundaryWidthInDegrees = 0.25f; uint16_t m_predictionSampleCount = 4; uint16_t m_filteringSampleCount = 12; diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/CoreLights/DirectionalLightComponentConfig.h b/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/CoreLights/DirectionalLightComponentConfig.h index 7de2857541..237c1f3016 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/CoreLights/DirectionalLightComponentConfig.h +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/CoreLights/DirectionalLightComponentConfig.h @@ -61,7 +61,7 @@ namespace AZ float m_shadowFarClipDistance = 100.f; //! Width/Height of shadowmap images. - ShadowmapSize m_shadowmapSize = MaxShadowmapImageSize; + ShadowmapSize m_shadowmapSize = ShadowmapSize::Size1024; //! Number of cascades. uint32_t m_cascadeCount = 4; @@ -117,7 +117,7 @@ namespace AZ //! It is used only when the pixel is predicted as on the boundary. uint16_t m_filteringSampleCount = 32; - PcfMethod m_pcfMethod = PcfMethod::BoundarySearch; + PcfMethod m_pcfMethod = PcfMethod::Bicubic; bool IsSplitManual() const; bool IsSplitAutomatic() const; diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/EditorAreaLightComponent.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/EditorAreaLightComponent.cpp index a77bcfdd12..69bec21a6c 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/EditorAreaLightComponent.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/EditorAreaLightComponent.cpp @@ -173,10 +173,10 @@ namespace AZ ->DataElement( Edit::UIHandlers::ComboBox, &AreaLightComponentConfig::m_pcfMethod, "Pcf method", "Type of PCF to use.\n" - " Boundary search: do several taps to first determine if we are on a shadow boundary\n" - " Bicubic: a smooth, fixed-size kernel \n") - ->EnumAttribute(PcfMethod::BoundarySearch, "Boundary search") + " Bicubic: a smooth, fixed-size kernel \n" + " Boundary search: do several taps to first determine if we are on a shadow boundary\n") ->EnumAttribute(PcfMethod::Bicubic, "Bicubic") + ->EnumAttribute(PcfMethod::BoundarySearch, "Boundary search") ->Attribute(Edit::Attributes::ChangeNotify, Edit::PropertyRefreshLevels::ValuesOnly) ->Attribute(Edit::Attributes::Visibility, &AreaLightComponentConfig::SupportsShadows) ->Attribute(Edit::Attributes::ReadOnly, &AreaLightComponentConfig::IsShadowPcfDisabled); diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/EditorDirectionalLightComponent.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/EditorDirectionalLightComponent.cpp index 35c5522c4e..a40557f2f1 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/EditorDirectionalLightComponent.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/EditorDirectionalLightComponent.cpp @@ -163,10 +163,10 @@ namespace AZ ->DataElement( Edit::UIHandlers::ComboBox, &DirectionalLightComponentConfig::m_pcfMethod, "Pcf Method", "Type of Pcf to use.\n" - " Boundary search: do several taps to first determine if we are on a shadow boundary\n" - " Bicubic: a smooth, fixed-size kernel \n") - ->EnumAttribute(PcfMethod::BoundarySearch, "Boundary Search") + " Bicubic: a smooth, fixed-size kernel \n" + " Boundary search: do several taps to first determine if we are on a shadow boundary\n") ->EnumAttribute(PcfMethod::Bicubic, "Bicubic") + ->EnumAttribute(PcfMethod::BoundarySearch, "Boundary Search") ->Attribute(Edit::Attributes::ChangeNotify, Edit::PropertyRefreshLevels::ValuesOnly) ->Attribute(Edit::Attributes::ReadOnly, &DirectionalLightComponentConfig::IsShadowPcfDisabled); ; diff --git a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/ActorAsset.cpp b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/ActorAsset.cpp index 3594802dab..8dc7f9387c 100644 --- a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/ActorAsset.cpp +++ b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/ActorAsset.cpp @@ -500,7 +500,16 @@ namespace AZ skinnedMeshLod.SetIndexBufferAsset(mesh0.GetIndexBufferAssetView().GetBufferAsset()); skinnedMeshLod.SetStaticBufferAsset(mesh0.GetSemanticBufferAssetView(Name{ "UV" })->GetBufferAsset(), SkinnedMeshStaticVertexStreams::UV_0); - const RPI::BufferAssetView* morphBufferAssetView = mesh0.GetSemanticBufferAssetView(Name{ "MORPHTARGET_VERTEXDELTAS" }); + const RPI::BufferAssetView* morphBufferAssetView = nullptr; + for (const auto& mesh : modelLodAsset->GetMeshes()) + { + morphBufferAssetView = mesh.GetSemanticBufferAssetView(Name{ "MORPHTARGET_VERTEXDELTAS" }); + if (morphBufferAssetView) + { + break; + } + } + if (morphBufferAssetView) { ProcessMorphsForLod(actor, morphBufferAssetView->GetBufferAsset(), lodIndex, fullFileName, skinnedMeshLod); diff --git a/Gems/Blast/Code/Source/Actor/ShapesProvider.cpp b/Gems/Blast/Code/Source/Actor/ShapesProvider.cpp index 42bdd773f7..a739f25cf9 100644 --- a/Gems/Blast/Code/Source/Actor/ShapesProvider.cpp +++ b/Gems/Blast/Code/Source/Actor/ShapesProvider.cpp @@ -27,7 +27,7 @@ namespace Blast PhysX::ColliderComponentRequestBus::Handler::BusDisconnect(m_entityId); } - Physics::ShapeConfigurationList ShapesProvider::GetShapeConfigurations() + AzPhysics::ShapeColliderPairList ShapesProvider::GetShapeConfigurations() { return {}; } diff --git a/Gems/Blast/Code/Source/Actor/ShapesProvider.h b/Gems/Blast/Code/Source/Actor/ShapesProvider.h index 4e737be080..0b23dc542c 100644 --- a/Gems/Blast/Code/Source/Actor/ShapesProvider.h +++ b/Gems/Blast/Code/Source/Actor/ShapesProvider.h @@ -28,7 +28,7 @@ namespace Blast void AddShape(AZStd::shared_ptr shape); // This class is not supposed to provide shape configurations, only shapes themselves. - Physics::ShapeConfigurationList GetShapeConfigurations() override; + AzPhysics::ShapeColliderPairList GetShapeConfigurations() override; AZStd::vector> GetShapes() override; diff --git a/Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/ColliderCommands.cpp b/Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/ColliderCommands.cpp index fae3ee742c..bd0d998737 100644 --- a/Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/ColliderCommands.cpp +++ b/Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/ColliderCommands.cpp @@ -251,7 +251,7 @@ namespace EMotionFX return false; } - Physics::ShapeConfigurationPair newCollider; + AzPhysics::ShapeColliderPair newCollider; // Either in case the contents got specified via a command parameter or in case of redo. if (m_contents) @@ -263,7 +263,7 @@ namespace EMotionFX else if (m_colliderType) { // Create new collider. - AZ::Outcome colliderOutcome = PhysicsSetup::CreateColliderByType(m_colliderType.value(), outResult); + AZ::Outcome colliderOutcome = PhysicsSetup::CreateColliderByType(m_colliderType.value(), outResult); if (!colliderOutcome.IsSuccess()) { return false; @@ -367,7 +367,7 @@ namespace EMotionFX return false; } - const Physics::ShapeConfigurationPair& collider = nodeConfig->m_shapes[m_oldColliderIndex.value()]; + const AzPhysics::ShapeColliderPair& collider = nodeConfig->m_shapes[m_oldColliderIndex.value()]; m_contents = MCore::ReflectionSerializer::Serialize(&collider).GetValue(); CommandColliderHelpers::RemoveCollider(m_actorId, m_jointName, m_configType, m_oldColliderIndex.value(), /*commandGroup*/ nullptr, true); @@ -472,7 +472,7 @@ namespace EMotionFX AZ_UNUSED(parameters); Actor* actor = nullptr; - Physics::ShapeConfigurationPair* shapeConfigPair = GetShapeConfigPair(&actor, outResult); + AzPhysics::ShapeColliderPair* shapeConfigPair = GetShapeConfigPair(&actor, outResult); if (!shapeConfigPair) { return false; @@ -524,7 +524,7 @@ namespace EMotionFX AZ_UNUSED(parameters); Actor* actor = nullptr; - Physics::ShapeConfigurationPair* shapeConfigPair = GetShapeConfigPair(&actor, outResult); + AzPhysics::ShapeColliderPair* shapeConfigPair = GetShapeConfigPair(&actor, outResult); if (!shapeConfigPair) { return false; @@ -606,7 +606,7 @@ namespace EMotionFX return true; } - Physics::ShapeConfigurationPair* CommandAdjustCollider::GetShapeConfigPair(Actor** outActor, AZStd::string& outResult) const + AzPhysics::ShapeColliderPair* CommandAdjustCollider::GetShapeConfigPair(Actor** outActor, AZStd::string& outResult) const { Actor* actor = GetActor(this, outResult); if (!actor) @@ -647,7 +647,7 @@ namespace EMotionFX return nullptr; } - Physics::ShapeConfigurationPair& shapeConfigPair = nodeConfig->m_shapes[m_index.value()]; + AzPhysics::ShapeColliderPair& shapeConfigPair = nodeConfig->m_shapes[m_index.value()]; return &shapeConfigPair; } diff --git a/Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/ColliderCommands.h b/Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/ColliderCommands.h index f0d20ce5f3..bb84e955ef 100644 --- a/Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/ColliderCommands.h +++ b/Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/ColliderCommands.h @@ -159,7 +159,7 @@ namespace EMotionFX static const char* s_commandName; private: - Physics::ShapeConfigurationPair* GetShapeConfigPair(Actor** outActor, AZStd::string& outResult) const; + AzPhysics::ShapeColliderPair* GetShapeConfigPair(Actor** outActor, AZStd::string& outResult) const; AZStd::optional m_configType; AZStd::optional m_index; diff --git a/Gems/EMotionFX/Code/EMotionFX/Source/PhysicsSetup.cpp b/Gems/EMotionFX/Code/EMotionFX/Source/PhysicsSetup.cpp index e8e5afb90f..cbe3e73cb3 100644 --- a/Gems/EMotionFX/Code/EMotionFX/Source/PhysicsSetup.cpp +++ b/Gems/EMotionFX/Code/EMotionFX/Source/PhysicsSetup.cpp @@ -198,13 +198,13 @@ namespace EMotionFX return m_config.m_simulatedObjectColliderConfig; } - AZ::Outcome PhysicsSetup::CreateColliderByType(const AZ::TypeId& typeId) + AZ::Outcome PhysicsSetup::CreateColliderByType(const AZ::TypeId& typeId) { AZStd::string outResult; return CreateColliderByType(typeId, outResult); } - AZ::Outcome PhysicsSetup::CreateColliderByType(const AZ::TypeId& typeId, AZStd::string& outResult) + AZ::Outcome PhysicsSetup::CreateColliderByType(const AZ::TypeId& typeId, AZStd::string& outResult) { if (typeId.IsNull()) { @@ -227,14 +227,16 @@ namespace EMotionFX return AZ::Failure(); } - Physics::ShapeConfiguration* shapeConfig = reinterpret_cast(classData->m_factory->Create(classData->m_name)); + AZStd::shared_ptr shapeConfig( + reinterpret_cast(classData->m_factory->Create(classData->m_name))); + if (!shapeConfig) { outResult = AZStd::string::format("Could not create collider with type '%s'.", typeId.ToString().c_str()); return AZ::Failure(); } - Physics::ShapeConfigurationPair pair(AZStd::make_shared(), shapeConfig); + AzPhysics::ShapeColliderPair pair(AZStd::make_shared(), shapeConfig); if (pair.first->m_materialSelection.GetMaterialIdsAssignedToSlots().empty()) { pair.first->m_materialSelection.SetMaterialSlots(Physics::MaterialSelection::SlotsArray()); @@ -242,7 +244,7 @@ namespace EMotionFX return AZ::Success(pair); } - void PhysicsSetup::AutoSizeCollider(Physics::ShapeConfigurationPair& collider, const Actor* actor, const Node* joint) + void PhysicsSetup::AutoSizeCollider(AzPhysics::ShapeColliderPair& collider, const Actor* actor, const Node* joint) { if (!collider.second || !actor || !joint) { @@ -417,7 +419,7 @@ namespace EMotionFX nodeConfig = &hitDetectionConfig.m_nodes.back(); } - Physics::ShapeConfigurationList& collisionShapes = nodeConfig->m_shapes; + AzPhysics::ShapeColliderPairList& collisionShapes = nodeConfig->m_shapes; Physics::ColliderConfiguration* colliderConfig = aznew Physics::ColliderConfiguration(); colliderConfig->m_position = position; diff --git a/Gems/EMotionFX/Code/EMotionFX/Source/PhysicsSetup.h b/Gems/EMotionFX/Code/EMotionFX/Source/PhysicsSetup.h index 4ff925739a..1da80faa34 100644 --- a/Gems/EMotionFX/Code/EMotionFX/Source/PhysicsSetup.h +++ b/Gems/EMotionFX/Code/EMotionFX/Source/PhysicsSetup.h @@ -68,9 +68,9 @@ namespace EMotionFX void OptimizeForServer(); - static AZ::Outcome CreateColliderByType(const AZ::TypeId& typeId); - static AZ::Outcome CreateColliderByType(const AZ::TypeId& typeId, AZStd::string& outResult); - static void AutoSizeCollider(Physics::ShapeConfigurationPair& collider, const Actor* actor, const Node* node); + static AZ::Outcome CreateColliderByType(const AZ::TypeId& typeId); + static AZ::Outcome CreateColliderByType(const AZ::TypeId& typeId, AZStd::string& outResult); + static void AutoSizeCollider(AzPhysics::ShapeColliderPair& collider, const Actor* actor, const Node* node); static void Reflect(AZ::ReflectContext* context); diff --git a/Gems/EMotionFX/Code/EMotionFX/Source/SpringSolver.cpp b/Gems/EMotionFX/Code/EMotionFX/Source/SpringSolver.cpp index 7a219b5ff3..0edc6ebb38 100644 --- a/Gems/EMotionFX/Code/EMotionFX/Source/SpringSolver.cpp +++ b/Gems/EMotionFX/Code/EMotionFX/Source/SpringSolver.cpp @@ -51,7 +51,7 @@ namespace EMotionFX m_collisionObjects.reserve(3); } - void SpringSolver::CreateCollider(AZ::u32 skeletonJointIndex, const Physics::ShapeConfigurationPair& shapePair) + void SpringSolver::CreateCollider(AZ::u32 skeletonJointIndex, const AzPhysics::ShapeColliderPair& shapePair) { const Physics::ShapeConfiguration* shapeConfig = shapePair.second.get(); if (!shapeConfig) @@ -104,7 +104,7 @@ namespace EMotionFX bool colliderFound = false; for (const auto& nodeConfig : colliderSetup.m_nodes) { - for (const Physics::ShapeConfigurationPair& shapePair : nodeConfig.m_shapes) + for (const AzPhysics::ShapeColliderPair& shapePair : nodeConfig.m_shapes) { if (shapePair.first->m_tag == colliderTag) { diff --git a/Gems/EMotionFX/Code/EMotionFX/Source/SpringSolver.h b/Gems/EMotionFX/Code/EMotionFX/Source/SpringSolver.h index 5843ea02be..ca45343a6b 100644 --- a/Gems/EMotionFX/Code/EMotionFX/Source/SpringSolver.h +++ b/Gems/EMotionFX/Code/EMotionFX/Source/SpringSolver.h @@ -87,7 +87,7 @@ namespace EMotionFX AZ::Vector3 m_end = AZ::Vector3::CreateZero(); /**< The end position of the primitive. In case of a sphere this is ignored. */ float m_radius = 1.0f; /**< The radius or thickness. */ float m_scaledRadius = 1.0f; /**< The scaled radius value, scaled by the joint's world space transform. */ - const Physics::ShapeConfigurationPair* m_shapePair = nullptr; + const AzPhysics::ShapeColliderPair* m_shapePair = nullptr; }; struct EMFX_API InitSettings @@ -151,7 +151,7 @@ namespace EMotionFX private: void InitColliders(const InitSettings& initSettings); - void CreateCollider(AZ::u32 skeletonJointIndex, const Physics::ShapeConfigurationPair& shapePair); + void CreateCollider(AZ::u32 skeletonJointIndex, const AzPhysics::ShapeColliderPair& shapePair); void InitColliderFromColliderSetupShape(CollisionObject& collider); void InitCollidersFromColliderSetupShapes(); bool RecursiveAddJoint(const SimulatedJoint* joint, size_t parentParticleIndex); diff --git a/Gems/EMotionFX/Code/Source/Editor/ColliderContainerWidget.cpp b/Gems/EMotionFX/Code/Source/Editor/ColliderContainerWidget.cpp index f6c196b23e..bd013da767 100644 --- a/Gems/EMotionFX/Code/Source/Editor/ColliderContainerWidget.cpp +++ b/Gems/EMotionFX/Code/Source/Editor/ColliderContainerWidget.cpp @@ -266,7 +266,7 @@ namespace EMotionFX connect(this, &AzQtComponents::Card::contextMenuRequested, this, &ColliderWidget::OnCardContextMenu); } - void ColliderWidget::Update(Actor* actor, Node* joint, size_t colliderIndex, PhysicsSetup::ColliderConfigType colliderType, const Physics::ShapeConfigurationPair& collider) + void ColliderWidget::Update(Actor* actor, Node* joint, size_t colliderIndex, PhysicsSetup::ColliderConfigType colliderType, const AzPhysics::ShapeColliderPair& collider) { m_actor = actor; m_joint = joint; @@ -276,7 +276,7 @@ namespace EMotionFX if (!collider.first || !collider.second) { m_editor->ClearInstances(true); - m_collider = Physics::ShapeConfigurationPair(); + m_collider = AzPhysics::ShapeColliderPair(); return; } @@ -520,7 +520,7 @@ namespace EMotionFX CommandSystem::GetCommandManager()->RemoveCommandCallback(m_commandCallback, /*delFromMem=*/true); } - void ColliderContainerWidget::Update(Actor* actor, Node* joint, PhysicsSetup::ColliderConfigType colliderType, const Physics::ShapeConfigurationList& colliders, AZ::SerializeContext* serializeContext) + void ColliderContainerWidget::Update(Actor* actor, Node* joint, PhysicsSetup::ColliderConfigType colliderType, const AzPhysics::ShapeColliderPairList& colliders, AZ::SerializeContext* serializeContext) { m_actor = actor; m_joint = joint; @@ -557,7 +557,7 @@ namespace EMotionFX for (size_t i = numColliders; i < numAvailableColliderWidgets; ++i) { m_colliderWidgets[i]->hide(); - m_colliderWidgets[i]->Update(nullptr, nullptr, MCORE_INVALIDINDEX32, PhysicsSetup::ColliderConfigType::Unknown, Physics::ShapeConfigurationPair()); + m_colliderWidgets[i]->Update(nullptr, nullptr, MCORE_INVALIDINDEX32, PhysicsSetup::ColliderConfigType::Unknown, AzPhysics::ShapeColliderPair()); } } @@ -571,7 +571,7 @@ namespace EMotionFX void ColliderContainerWidget::Reset() { - Update(nullptr, nullptr, PhysicsSetup::ColliderConfigType::Unknown, Physics::ShapeConfigurationList(), nullptr); + Update(nullptr, nullptr, PhysicsSetup::ColliderConfigType::Unknown, AzPhysics::ShapeColliderPairList(), nullptr); } void ColliderContainerWidget::contextMenuEvent(QContextMenuEvent* event) @@ -614,7 +614,7 @@ namespace EMotionFX return QWidget::sizeHint() + QSize(0, s_layoutSpacing); } - void ColliderContainerWidget::RenderColliders(const Physics::ShapeConfigurationList& colliders, + void ColliderContainerWidget::RenderColliders(const AzPhysics::ShapeColliderPairList& colliders, const ActorInstance* actorInstance, const Node* node, EMStudio::EMStudioPlugin::RenderInfo* renderInfo, @@ -704,7 +704,7 @@ namespace EMotionFX if (joint) { const bool jointSelected = selectedJointIndices.empty() || selectedJointIndices.find(joint->GetNodeIndex()) != selectedJointIndices.end(); - const Physics::ShapeConfigurationList& colliders = nodeConfig.m_shapes; + const AzPhysics::ShapeColliderPairList& colliders = nodeConfig.m_shapes; RenderColliders(colliders, actorInstance, joint, renderInfo, jointSelected ? selectedColor : defaultColor); } } diff --git a/Gems/EMotionFX/Code/Source/Editor/ColliderContainerWidget.h b/Gems/EMotionFX/Code/Source/Editor/ColliderContainerWidget.h index e8cc717e49..31f28ce210 100644 --- a/Gems/EMotionFX/Code/Source/Editor/ColliderContainerWidget.h +++ b/Gems/EMotionFX/Code/Source/Editor/ColliderContainerWidget.h @@ -71,7 +71,7 @@ namespace EMotionFX public: ColliderWidget(QIcon* icon, QWidget* parent, AZ::SerializeContext* serializeContext); - void Update(Actor* actor, Node* joint, size_t colliderIndex, PhysicsSetup::ColliderConfigType colliderType, const Physics::ShapeConfigurationPair& collider); + void Update(Actor* actor, Node* joint, size_t colliderIndex, PhysicsSetup::ColliderConfigType colliderType, const AzPhysics::ShapeColliderPair& collider); void Update(); void Reset(); @@ -99,7 +99,7 @@ namespace EMotionFX PhysicsSetup::ColliderConfigType m_colliderType = PhysicsSetup::ColliderConfigType::Unknown; Node* m_joint = nullptr; size_t m_colliderIndex = MCORE_INVALIDINDEX32; - Physics::ShapeConfigurationPair m_collider; + AzPhysics::ShapeColliderPair m_collider; QIcon* m_icon; }; @@ -140,7 +140,7 @@ namespace EMotionFX ColliderContainerWidget(const QIcon& colliderIcon, QWidget* parent = nullptr); ~ColliderContainerWidget(); - void Update(Actor* actor, Node* joint, PhysicsSetup::ColliderConfigType colliderType, const Physics::ShapeConfigurationList& colliders, AZ::SerializeContext* serializeContext); + void Update(Actor* actor, Node* joint, PhysicsSetup::ColliderConfigType colliderType, const AzPhysics::ShapeColliderPairList& colliders, AZ::SerializeContext* serializeContext); void Update(); void Reset(); PhysicsSetup::ColliderConfigType ColliderType() { return m_colliderType; } @@ -156,7 +156,7 @@ namespace EMotionFX * @param[in] renderInfo Needed to access the render util. * @param[in] colliderColor The collider color. */ - static void RenderColliders(const Physics::ShapeConfigurationList& colliders, + static void RenderColliders(const AzPhysics::ShapeColliderPairList& colliders, const ActorInstance* actorInstance, const Node* node, EMStudio::EMStudioPlugin::RenderInfo* renderInfo, diff --git a/Gems/EMotionFX/Code/Source/Editor/ColliderHelpers.cpp b/Gems/EMotionFX/Code/Source/Editor/ColliderHelpers.cpp index b8f6311600..2cab16e2c1 100644 --- a/Gems/EMotionFX/Code/Source/Editor/ColliderHelpers.cpp +++ b/Gems/EMotionFX/Code/Source/Editor/ColliderHelpers.cpp @@ -40,7 +40,7 @@ namespace EMotionFX const Physics::CharacterColliderNodeConfiguration* copyFromNodeConfig = copyFromColliderConfig->FindNodeConfigByName(joint->GetNameString()); if (copyFromNodeConfig) { - for (const Physics::ShapeConfigurationPair& shapeConfigPair : copyFromNodeConfig->m_shapes) + for (const AzPhysics::ShapeColliderPair& shapeConfigPair : copyFromNodeConfig->m_shapes) { const AZStd::string contents = MCore::ReflectionSerializer::Serialize(&shapeConfigPair).GetValue(); CommandColliderHelpers::AddCollider(actor->GetID(), joint->GetNameString(), copyTo, contents, AZStd::nullopt, &commandGroup); @@ -231,7 +231,7 @@ namespace EMotionFX const Physics::CharacterColliderNodeConfiguration* copyFromNodeConfig = copyFromColliderConfig->FindNodeConfigByName(joint->GetNameString()); if (copyFromNodeConfig && shapeIndex < copyFromNodeConfig->m_shapes.size()) { - const Physics::ShapeConfigurationPair* shape = ©FromNodeConfig->m_shapes[shapeIndex]; + const AzPhysics::ShapeColliderPair* shape = ©FromNodeConfig->m_shapes[shapeIndex]; const AZStd::string contents = MCore::ReflectionSerializer::Serialize(shape).GetValue(); QMimeData* mimeData = new QMimeData(); mimeData->setData( diff --git a/Gems/EMotionFX/Code/Source/Editor/ColliderHelpers.h b/Gems/EMotionFX/Code/Source/Editor/ColliderHelpers.h index 462638e793..a2eba33682 100644 --- a/Gems/EMotionFX/Code/Source/Editor/ColliderHelpers.h +++ b/Gems/EMotionFX/Code/Source/Editor/ColliderHelpers.h @@ -28,7 +28,7 @@ namespace EMotionFX public: static QString GetMimeTypeForColliderShape() { - return QString("com.amazon.lumberyard/%1").arg(azrtti_typeid().ToString()); + return QString("com.amazon.lumberyard/%1").arg(azrtti_typeid().ToString()); } static void AddCopyColliderCommandToGroup(const Actor* actor, const Node* joint, PhysicsSetup::ColliderConfigType copyFrom, PhysicsSetup::ColliderConfigType copyTo, MCore::CommandGroup& commandGroup); diff --git a/Gems/EMotionFX/Code/Source/Editor/Plugins/Ragdoll/RagdollNodeInspectorPlugin.cpp b/Gems/EMotionFX/Code/Source/Editor/Plugins/Ragdoll/RagdollNodeInspectorPlugin.cpp index c2e2c5cc1b..e2fde04016 100644 --- a/Gems/EMotionFX/Code/Source/Editor/Plugins/Ragdoll/RagdollNodeInspectorPlugin.cpp +++ b/Gems/EMotionFX/Code/Source/Editor/Plugins/Ragdoll/RagdollNodeInspectorPlugin.cpp @@ -507,7 +507,7 @@ namespace EMotionFX const Physics::CharacterColliderNodeConfiguration* colliderNodeConfig = colliderConfig.FindNodeConfigByName(joint->GetNameString()); if (colliderNodeConfig) { - const Physics::ShapeConfigurationList& colliders = colliderNodeConfig->m_shapes; + const AzPhysics::ShapeColliderPairList& colliders = colliderNodeConfig->m_shapes; ColliderContainerWidget::RenderColliders(colliders, actorInstance, joint, renderInfo, finalColor); } } diff --git a/Gems/EMotionFX/Code/Tests/ColliderCommandTests.cpp b/Gems/EMotionFX/Code/Tests/ColliderCommandTests.cpp index 0d81bc55b9..772ebad295 100644 --- a/Gems/EMotionFX/Code/Tests/ColliderCommandTests.cpp +++ b/Gems/EMotionFX/Code/Tests/ColliderCommandTests.cpp @@ -196,7 +196,7 @@ namespace EMotionFX ASSERT_TRUE(nodeConfig != nullptr); EXPECT_EQ(nodeConfig->m_shapes.size(), 1); - Physics::ShapeConfigurationPair& shapeConfigPair = nodeConfig->m_shapes[0]; + AzPhysics::ShapeColliderPair& shapeConfigPair = nodeConfig->m_shapes[0]; Physics::ColliderConfiguration* colliderConfig = shapeConfigPair.first.get(); Physics::ShapeConfiguration* shapeConfig = shapeConfigPair.second.get(); Physics::BoxShapeConfiguration* boxShapeConfig = azdynamic_cast(shapeConfig); diff --git a/Gems/EMotionFX/Code/Tests/PhysicsSetupUtils.cpp b/Gems/EMotionFX/Code/Tests/PhysicsSetupUtils.cpp index 9f13e3e34f..c201ae1f48 100644 --- a/Gems/EMotionFX/Code/Tests/PhysicsSetupUtils.cpp +++ b/Gems/EMotionFX/Code/Tests/PhysicsSetupUtils.cpp @@ -37,7 +37,7 @@ namespace EMotionFX else { // Count in only the given collider type. - for (const Physics::ShapeConfigurationPair& shapeConfigPair : nodeConfig.m_shapes) + for (const AzPhysics::ShapeColliderPair& shapeConfigPair : nodeConfig.m_shapes) { if (shapeConfigPair.second->GetShapeType() == shapeTypeToCount) { diff --git a/Gems/ImGui/Code/Source/LYCommonMenu/ImGuiLYAssetExplorer.cpp b/Gems/ImGui/Code/Source/LYCommonMenu/ImGuiLYAssetExplorer.cpp index 251c178035..093c9dad22 100644 --- a/Gems/ImGui/Code/Source/LYCommonMenu/ImGuiLYAssetExplorer.cpp +++ b/Gems/ImGui/Code/Source/LYCommonMenu/ImGuiLYAssetExplorer.cpp @@ -163,7 +163,7 @@ namespace ImGui void ImGuiLYAssetExplorer::ImGuiUpdate_DrawMenu() { - // Master on / off Switch + // Primary on / off Switch ImGui::Checkbox("Mesh Debug Enabled", &m_meshDebugEnabled); ImGui::SameLine(); diff --git a/Gems/ImGui/Code/Source/LYCommonMenu/ImGuiLYAssetExplorer.h b/Gems/ImGui/Code/Source/LYCommonMenu/ImGuiLYAssetExplorer.h index b5f8ea576c..04d87575fb 100644 --- a/Gems/ImGui/Code/Source/LYCommonMenu/ImGuiLYAssetExplorer.h +++ b/Gems/ImGui/Code/Source/LYCommonMenu/ImGuiLYAssetExplorer.h @@ -103,7 +103,7 @@ namespace ImGui void MeshInstanceList_CheckMeshFilter(); void MeshInstanceList_CheckEntityFilter(); - // The Master list of Meshes and Instances of them + // The Primary list of Meshes and Instances of them AZStd::list m_meshInstanceDisplayList; // Helper functions for the ImGui Update diff --git a/Gems/LmbrCentral/Code/Source/Bundling/BundlingSystemComponent.h b/Gems/LmbrCentral/Code/Source/Bundling/BundlingSystemComponent.h index e01268cfe2..7b054b8c00 100644 --- a/Gems/LmbrCentral/Code/Source/Bundling/BundlingSystemComponent.h +++ b/Gems/LmbrCentral/Code/Source/Bundling/BundlingSystemComponent.h @@ -85,10 +85,10 @@ namespace LmbrCentral AZStd::vector GetBundleList(const char* bundlePath, const char* bundleExtension) const; //! Bundles which are split across archives (Usually due to size constraints) have the dependent bundles listed in the manifest - //! of the master bundle. This method manages opening the dependent bundles. + //! of the main bundle. This method manages opening the dependent bundles. void OpenDependentBundles(const char* bundleName, AZStd::shared_ptr bundleManifest); //! Bundles which are split across archives (Usually due to size constraints) have the dependent bundles listed in the manifest - //! of the master bundle. This method manages closing the dependent bundles. + //! of the main bundle. This method manages closing the dependent bundles. void CloseDependentBundles(const char* bundleName, AZStd::shared_ptr bundleManifest); size_t GetOpenedBundleCount() const override; diff --git a/Gems/LyShine/Code/Source/UiTextComponent.cpp b/Gems/LyShine/Code/Source/UiTextComponent.cpp index 937f323b01..648013108b 100644 --- a/Gems/LyShine/Code/Source/UiTextComponent.cpp +++ b/Gems/LyShine/Code/Source/UiTextComponent.cpp @@ -3568,13 +3568,14 @@ UiTextComponent::FontEffectComboBoxVec UiTextComponent::PopulateFontEffectList() FontEffectComboBoxVec result; AZStd::vector entityIdList; - // there is always a valid font since we default to "default-ui" - // so just get the effects from the font and add their names to the result list - unsigned int numEffects = m_font->GetNumEffects(); - for (int i = 0; i < numEffects; ++i) + if (m_font) { - const char* name = m_font->GetEffectName(i); - result.push_back(AZStd::make_pair(i, name)); + unsigned int numEffects = m_font->GetNumEffects(); + for (int i = 0; i < numEffects; ++i) + { + const char* name = m_font->GetEffectName(i); + result.push_back(AZStd::make_pair(i, name)); + } } return result; diff --git a/Gems/Multiplayer/Code/CMakeLists.txt b/Gems/Multiplayer/Code/CMakeLists.txt index 4eeee15c47..019f341d0c 100644 --- a/Gems/Multiplayer/Code/CMakeLists.txt +++ b/Gems/Multiplayer/Code/CMakeLists.txt @@ -59,6 +59,26 @@ ly_add_target( ) if (PAL_TRAIT_BUILD_HOST_TOOLS) + ly_add_target( + NAME Multiplayer.Tools.Static STATIC + NAMESPACE Gem + FILES_CMAKE + multiplayer_tools_files.cmake + COMPILE_DEFINITIONS + PUBLIC + MULTIPLAYER_TOOLS + INCLUDE_DIRECTORIES + PRIVATE + . + Source + ${pal_source_dir} + PUBLIC + Include + BUILD_DEPENDENCIES + PUBLIC + AZ::AzToolsFramework + Gem::Multiplayer.Static + ) ly_add_target( NAME Multiplayer.Tools MODULE @@ -74,15 +94,14 @@ if (PAL_TRAIT_BUILD_HOST_TOOLS) Include BUILD_DEPENDENCIES PRIVATE - AZ::AzToolsFramework - Gem::Multiplayer.Static + Gem::Multiplayer.Tools.Static ) - + ly_add_target( - NAME Multiplayer.Editor.Static STATIC + NAME Multiplayer.Editor GEM_MODULE NAMESPACE Gem FILES_CMAKE - multiplayer_editor_files.cmake + multiplayer_editor_shared_files.cmake COMPILE_DEFINITIONS PUBLIC MULTIPLAYER_EDITOR @@ -94,7 +113,7 @@ if (PAL_TRAIT_BUILD_HOST_TOOLS) PUBLIC Include BUILD_DEPENDENCIES - PUBLIC + PRIVATE Legacy::CryCommon Legacy::Editor.Headers AZ::AzCore @@ -102,23 +121,7 @@ if (PAL_TRAIT_BUILD_HOST_TOOLS) AZ::AzNetworking AZ::AzToolsFramework Gem::Multiplayer.Static - ) - - ly_add_target( - NAME Multiplayer.Editor GEM_MODULE - NAMESPACE Gem - FILES_CMAKE - multiplayer_editor_shared_files.cmake - INCLUDE_DIRECTORIES - PRIVATE - . - Source - ${pal_source_dir} - PUBLIC - Include - BUILD_DEPENDENCIES - PRIVATE - Gem::Multiplayer.Editor.Static + Gem::Multiplayer.Tools ) endif() @@ -145,6 +148,30 @@ if (PAL_TRAIT_BUILD_TESTS_SUPPORTED) ly_add_googletest( NAME Gem::Multiplayer.Tests ) + + if (PAL_TRAIT_BUILD_HOST_TOOLS) + ly_add_target( + NAME Multiplayer.Tools.Tests ${PAL_TRAIT_TEST_TARGET_TYPE} + NAMESPACE Gem + FILES_CMAKE + multiplayer_tools_tests_files.cmake + INCLUDE_DIRECTORIES + PRIVATE + Tests + Source + . + BUILD_DEPENDENCIES + PRIVATE + AZ::AzTest + AZ::AzTestShared + AZ::AzToolsFrameworkTestCommon + Gem::Multiplayer.Tools.Static + ) + ly_add_googletest( + NAME Gem::Multiplayer.Tools.Tests + ) + endif() + endif() ly_add_target( diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/IMultiplayerTools.h b/Gems/Multiplayer/Code/Include/Multiplayer/IMultiplayerTools.h new file mode 100644 index 0000000000..c621808f7a --- /dev/null +++ b/Gems/Multiplayer/Code/Include/Multiplayer/IMultiplayerTools.h @@ -0,0 +1,39 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#pragma once + +#include + +namespace Multiplayer +{ + //! IMultiplayer provides insight into the Multiplayer session and its Agents + class IMultiplayerTools + { + public: + // NetworkPrefabProcessor is the only class that should be setting process network prefab status + friend class NetworkPrefabProcessor; + + AZ_RTTI(IMultiplayerTools, "{E8A80EAB-29CB-4E3B-A0B2-FFCB37060FB0}"); + + virtual ~IMultiplayerTools() = default; + + //! Returns if network prefab processing has created currently active or pending spawnables + //! @return If network prefab processing has created currently active or pending spawnables + virtual bool DidProcessNetworkPrefabs() = 0; + + private: + //! Sets if network prefab processing has created currently active or pending spawnables + //! @param didProcessNetPrefabs if network prefab processing has created currently active or pending spawnables + virtual void SetDidProcessNetworkPrefabs(bool didProcessNetPrefabs) = 0; + }; +} diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/INetworkSpawnableLibrary.h b/Gems/Multiplayer/Code/Include/Multiplayer/INetworkSpawnableLibrary.h new file mode 100644 index 0000000000..422f7f4be3 --- /dev/null +++ b/Gems/Multiplayer/Code/Include/Multiplayer/INetworkSpawnableLibrary.h @@ -0,0 +1,34 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#pragma once + +#include +#include +#include + +namespace Multiplayer +{ + //! @class INetworkSpawnableLibrary + //! @brief The interface for managing network spawnables. + class INetworkSpawnableLibrary + { + public: + AZ_RTTI(INetworkSpawnableLibrary, "{A3CF809C-6C1D-4B43-B2C4-3901B5DE1ABE}"); + + virtual ~INetworkSpawnableLibrary() = default; + virtual void BuildSpawnablesList() = 0; + virtual void ProcessSpawnableAsset(const AZStd::string& relativePath, AZ::Data::AssetId id) = 0; + virtual AZ::Name GetSpawnableNameFromAssetId(AZ::Data::AssetId assetId) = 0; + virtual AZ::Data::AssetId GetAssetIdByName(AZ::Name name) = 0; + }; +} diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/MultiplayerConstants.h b/Gems/Multiplayer/Code/Include/Multiplayer/MultiplayerConstants.h new file mode 100644 index 0000000000..b82fab91be --- /dev/null +++ b/Gems/Multiplayer/Code/Include/Multiplayer/MultiplayerConstants.h @@ -0,0 +1,32 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace Multiplayer +{ + constexpr AZStd::string_view MPNetworkInterfaceName("MultiplayerNetworkInterface"); + constexpr AZStd::string_view MPEditorInterfaceName("MultiplayerEditorNetworkInterface"); + + constexpr AZStd::string_view LocalHost("127.0.0.1"); + constexpr uint16_t DefaultServerPort = 30090; + constexpr uint16_t DefaultServerEditorPort = 30091; + +} + diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/INetworkEntityManager.h b/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/INetworkEntityManager.h index 72dbe201e5..4fc063333f 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/INetworkEntityManager.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/INetworkEntityManager.h @@ -78,6 +78,12 @@ namespace Multiplayer const AZ::Transform& transform ) = 0; + //! Configures new networked entity + //! @param netEntity the entity to setup + //! @param prefabEntryId the name of the spawnable the entity originated from + //! @param netEntityRole the net role the entity should be setup for + virtual void SetupNetEntity(AZ::Entity* netEntity, PrefabEntityId prefabEntityId, NetEntityRole netEntityRole) = 0; + //! Returns an ConstEntityPtr for the provided entityId. //! @param netEntityId the netEntityId to get an ConstEntityPtr for //! @return the requested ConstEntityPtr diff --git a/Gems/Multiplayer/Code/Source/AutoGen/Multiplayer.AutoPackets.xml b/Gems/Multiplayer/Code/Source/AutoGen/Multiplayer.AutoPackets.xml index 2f934979b1..a1e68aa708 100644 --- a/Gems/Multiplayer/Code/Source/AutoGen/Multiplayer.AutoPackets.xml +++ b/Gems/Multiplayer/Code/Source/AutoGen/Multiplayer.AutoPackets.xml @@ -59,4 +59,5 @@ + diff --git a/Gems/Multiplayer/Code/Source/AutoGen/MultiplayerEditor.AutoPackets.xml b/Gems/Multiplayer/Code/Source/AutoGen/MultiplayerEditor.AutoPackets.xml new file mode 100644 index 0000000000..8f55ecd2b8 --- /dev/null +++ b/Gems/Multiplayer/Code/Source/AutoGen/MultiplayerEditor.AutoPackets.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/Gems/Multiplayer/Code/Source/Components/LocalPredictionPlayerInputComponent.cpp b/Gems/Multiplayer/Code/Source/Components/LocalPredictionPlayerInputComponent.cpp index 10a0d17e73..612601883c 100644 --- a/Gems/Multiplayer/Code/Source/Components/LocalPredictionPlayerInputComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Components/LocalPredictionPlayerInputComponent.cpp @@ -23,7 +23,7 @@ namespace Multiplayer { AZ_CVAR(AZ::TimeMs, cl_InputRateMs, AZ::TimeMs{ 33 }, nullptr, AZ::ConsoleFunctorFlags::Null, "Rate at which to sample and process client inputs"); AZ_CVAR(AZ::TimeMs, cl_MaxRewindHistoryMs, AZ::TimeMs{ 2000 }, nullptr, AZ::ConsoleFunctorFlags::Null, "Maximum number of milliseconds to keep for server correction rewind and replay"); -#ifndef _RELEASE +#ifndef AZ_RELEASE_BUILD AZ_CVAR(float, cl_DebugHackTimeMultiplier, 1.0f, nullptr, AZ::ConsoleFunctorFlags::Null, "Scalar value used to simulate clock hacking cheats for validating bank time system and anticheat"); #endif @@ -477,7 +477,7 @@ namespace Multiplayer const double inputRate = static_cast(static_cast(cl_InputRateMs)) / 1000.0; const double maxRewindHistory = static_cast(static_cast(cl_MaxRewindHistoryMs)) / 1000.0; -#ifndef _RELEASE +#ifndef AZ_RELEASE_BUILD m_moveAccumulator += deltaTime * cl_DebugHackTimeMultiplier; #else m_moveAccumulator += deltaTime; diff --git a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.cpp b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.cpp new file mode 100644 index 0000000000..f684e1f12f --- /dev/null +++ b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.cpp @@ -0,0 +1,187 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Multiplayer +{ + using namespace AzNetworking; + + AZ_CVAR(bool, editorsv_isDedicated, false, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "Whether to init as a server expecting data from an Editor. Do not modify unless you're sure of what you're doing."); + + MultiplayerEditorConnection::MultiplayerEditorConnection() + : m_byteStream(&m_buffer) + { + m_networkEditorInterface = AZ::Interface::Get()->CreateNetworkInterface( + AZ::Name(MPEditorInterfaceName), ProtocolType::Tcp, TrustZone::ExternalClientToServer, *this); + if (editorsv_isDedicated) + { + uint16_t editorServerPort = DefaultServerEditorPort; + if (auto console = AZ::Interface::Get(); console) + { + console->GetCvarValue("editorsv_port", editorServerPort); + } + AZ_Assert(m_networkEditorInterface, "MP Editor Network Interface was unregistered before Editor Server could start listening."); + m_networkEditorInterface->Listen(editorServerPort); + } + } + + bool MultiplayerEditorConnection::HandleRequest + ( + [[maybe_unused]] AzNetworking::IConnection* connection, + [[maybe_unused]] const IPacketHeader& packetHeader, + [[maybe_unused]] MultiplayerEditorPackets::EditorServerInit& packet + ) + { + // Editor Server Init is intended for non-release targets + if (!packet.GetLastUpdate()) + { + // More packets are expected, flush this to the buffer + m_byteStream.Write(TcpPacketEncodingBuffer::GetCapacity(), reinterpret_cast(packet.ModifyAssetData().GetBuffer())); + } + else + { + // This is the last expected packet, flush it to the buffer + m_byteStream.Write(packet.GetAssetData().GetSize(), reinterpret_cast(packet.ModifyAssetData().GetBuffer())); + + // Read all assets out of the buffer + m_byteStream.Seek(0, AZ::IO::GenericStream::SeekMode::ST_SEEK_BEGIN); + AZStd::vector> assetData; + while (m_byteStream.GetCurPos() < m_byteStream.GetLength()) + { + AZ::Data::AssetId assetId; + uint32_t hintSize; + AZStd::string assetHint; + m_byteStream.Read(sizeof(AZ::Data::AssetId), reinterpret_cast(&assetId)); + m_byteStream.Read(sizeof(uint32_t), reinterpret_cast(&hintSize)); + assetHint.resize(hintSize); + m_byteStream.Read(hintSize, assetHint.data()); + + size_t assetSize = m_byteStream.GetCurPos(); + AZ::Data::AssetData* assetDatum = AZ::Utils::LoadObjectFromStream(m_byteStream, nullptr); + assetSize = m_byteStream.GetCurPos() - assetSize; + AZ::Data::Asset asset = AZ::Data::Asset(assetId, assetDatum, AZ::Data::AssetLoadBehavior::NoLoad); + asset.SetHint(assetHint); + + AZ::Data::AssetInfo assetInfo; + assetInfo.m_assetId = asset.GetId(); + assetInfo.m_assetType = asset.GetType(); + assetInfo.m_relativePath = asset.GetHint(); + assetInfo.m_sizeBytes = assetSize; + + // Register Asset to AssetManager + AZ::Data::AssetManager::Instance().AssignAssetData(asset); + AZ::Data::AssetCatalogRequestBus::Broadcast(&AZ::Data::AssetCatalogRequests::RegisterAsset, asset.GetId(), assetInfo); + + assetData.push_back(asset); + } + + // Now that we've deserialized, clear the byte stream + m_byteStream.Seek(0, AZ::IO::GenericStream::SeekMode::ST_SEEK_BEGIN); + m_byteStream.Truncate(); + + // Load the level via the root spawnable that was registered + const AZ::CVarFixedString loadLevelString = "LoadLevel Root.spawnable"; + AZ::Interface::Get()->PerformCommand(loadLevelString.c_str()); + + // Setup the normal multiplayer connection + AZ::Interface::Get()->InitializeMultiplayer(MultiplayerAgentType::DedicatedServer); + INetworkInterface* networkInterface = AZ::Interface::Get()->RetrieveNetworkInterface(AZ::Name(MPNetworkInterfaceName)); + + uint16_t serverPort = DefaultServerPort; + if (auto console = AZ::Interface::Get(); console) + { + console->GetCvarValue("sv_port", serverPort); + } + networkInterface->Listen(serverPort); + + AZLOG_INFO("Editor Server completed asset receive, responding to Editor..."); + return connection->SendReliablePacket(MultiplayerEditorPackets::EditorServerReady()); + } + + return true; + } + + bool MultiplayerEditorConnection::HandleRequest + ( + [[maybe_unused]] AzNetworking::IConnection* connection, + [[maybe_unused]] const IPacketHeader& packetHeader, + [[maybe_unused]] MultiplayerEditorPackets::EditorServerReady& packet + ) + { + if (connection->GetConnectionRole() == ConnectionRole::Connector) + { + // Receiving this packet means Editor sync is done, disconnect + connection->Disconnect(AzNetworking::DisconnectReason::TerminatedByClient, AzNetworking::TerminationEndpoint::Local); + + if (auto console = AZ::Interface::Get(); console) + { + AZ::CVarFixedString remoteAddress; + uint16_t remotePort; + if (console->GetCvarValue("editorsv_serveraddr", remoteAddress) != AZ::GetValueResult::ConsoleVarNotFound && + console->GetCvarValue("editorsv_port", remotePort) != AZ::GetValueResult::ConsoleVarNotFound) + { + // Connect the Editor to the editor server for Multiplayer simulation + AZ::Interface::Get()->InitializeMultiplayer(MultiplayerAgentType::Client); + INetworkInterface* networkInterface = + AZ::Interface::Get()->RetrieveNetworkInterface(AZ::Name(MPNetworkInterfaceName)); + + const IpAddress ipAddress(remoteAddress.c_str(), remotePort, networkInterface->GetType()); + networkInterface->Connect(ipAddress); + } + } + } + return true; + } + + ConnectResult MultiplayerEditorConnection::ValidateConnect + ( + [[maybe_unused]] const IpAddress& remoteAddress, + [[maybe_unused]] const IPacketHeader& packetHeader, + [[maybe_unused]] ISerializer& serializer + ) + { + return ConnectResult::Accepted; + } + + void MultiplayerEditorConnection::OnConnect([[maybe_unused]] AzNetworking::IConnection* connection) + { + ; + } + + bool MultiplayerEditorConnection::OnPacketReceived(AzNetworking::IConnection* connection, const IPacketHeader& packetHeader, ISerializer& serializer) + { + return MultiplayerEditorPackets::DispatchPacket(connection, packetHeader, serializer, *this); + } + + void MultiplayerEditorConnection::OnPacketLost([[maybe_unused]] IConnection* connection, [[maybe_unused]] PacketId packetId) + { + ; + } + + void MultiplayerEditorConnection::OnDisconnect([[maybe_unused]] AzNetworking::IConnection* connection, [[maybe_unused]] DisconnectReason reason, [[maybe_unused]] TerminationEndpoint endpoint) + { + ; + } +} diff --git a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.h b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.h new file mode 100644 index 0000000000..d803a60744 --- /dev/null +++ b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.h @@ -0,0 +1,58 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#pragma once + +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace AzNetworking +{ + class INetworkInterface; +} + +namespace Multiplayer +{ + //! MultiplayerEditorConnection is a connection listener to synchronize the Editor and a local server it launches + class MultiplayerEditorConnection final + : public AzNetworking::IConnectionListener + { + public: + MultiplayerEditorConnection(); + ~MultiplayerEditorConnection() = default; + + bool HandleRequest(AzNetworking::IConnection* connection, const AzNetworking::IPacketHeader& packetHeader, MultiplayerEditorPackets::EditorServerInit& packet); + bool HandleRequest(AzNetworking::IConnection* connection, const AzNetworking::IPacketHeader& packetHeader, MultiplayerEditorPackets::EditorServerReady& packet); + + //! IConnectionListener interface + //! @{ + AzNetworking::ConnectResult ValidateConnect(const AzNetworking::IpAddress& remoteAddress, const AzNetworking::IPacketHeader& packetHeader, AzNetworking::ISerializer& serializer) override; + void OnConnect(AzNetworking::IConnection* connection) override; + bool OnPacketReceived(AzNetworking::IConnection* connection, const AzNetworking::IPacketHeader& packetHeader, AzNetworking::ISerializer& serializer) override; + void OnPacketLost(AzNetworking::IConnection* connection, AzNetworking::PacketId packetId) override; + void OnDisconnect(AzNetworking::IConnection* connection, AzNetworking::DisconnectReason reason, AzNetworking::TerminationEndpoint endpoint) override; + //! @} + + private: + + AzNetworking::INetworkInterface* m_networkEditorInterface = nullptr; + AZStd::vector m_buffer; + AZ::IO::ByteContainerStream> m_byteStream; + }; +} diff --git a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorDispatcher.cpp b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorDispatcher.cpp deleted file mode 100644 index 470aa61cd0..0000000000 --- a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorDispatcher.cpp +++ /dev/null @@ -1,21 +0,0 @@ -/* - * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or - * its licensors. - * - * For complete copyright and license terms please see the LICENSE at the root of this - * distribution (the "License"). All use of this software is governed by the License, - * or, if provided, by the license below or the license accompanying this file. Do not - * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * - */ - -#include - -namespace Multiplayer -{ - MultiplayerEditorDispatcher::MultiplayerEditorDispatcher() - { - ; - } -} diff --git a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorDispatcher.h b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorDispatcher.h deleted file mode 100644 index c1058dc8a0..0000000000 --- a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorDispatcher.h +++ /dev/null @@ -1,36 +0,0 @@ -/* -* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -* its licensors. -* -* For complete copyright and license terms please see the LICENSE at the root of this -* distribution (the "License"). All use of this software is governed by the License, -* or, if provided, by the license below or the license accompanying this file. Do not -* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* -*/ - -#pragma once - -#include - -#include -#include -#include -#include - -#include - - -namespace Multiplayer -{ - //! MultiplayerEditorDispatcher is responsible for dispatching delta from the Editor to an Editor launched local server - class MultiplayerEditorDispatcher final - { - public: - MultiplayerEditorDispatcher(); - ~MultiplayerEditorDispatcher() = default; - - private: - }; -} diff --git a/Gems/Multiplayer/Code/Source/MultiplayerEditorGem.cpp b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorGem.cpp similarity index 86% rename from Gems/Multiplayer/Code/Source/MultiplayerEditorGem.cpp rename to Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorGem.cpp index 97c5e4d105..2fe0aabe5f 100644 --- a/Gems/Multiplayer/Code/Source/MultiplayerEditorGem.cpp +++ b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorGem.cpp @@ -10,13 +10,13 @@ * */ -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include -#include +#include namespace Multiplayer { diff --git a/Gems/Multiplayer/Code/Source/MultiplayerEditorGem.h b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorGem.h similarity index 100% rename from Gems/Multiplayer/Code/Source/MultiplayerEditorGem.h rename to Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorGem.h diff --git a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp index aec3e7870f..829fe7e495 100644 --- a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp @@ -10,12 +10,22 @@ * */ -#include -#include +#include +#include +#include + +#include +#include +#include + +#include #include #include +#include #include #include +#include +#include namespace Multiplayer { @@ -23,8 +33,12 @@ namespace Multiplayer AZ_CVAR(bool, editorsv_enabled, false, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "Whether Editor launching a local server to connect to is supported"); + AZ_CVAR(bool, editorsv_launch, true, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, + "Whether Editor should launch a server when the server address is localhost"); AZ_CVAR(AZ::CVarFixedString, editorsv_process, "", nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The server executable that should be run. Empty to use the current project's ServerLauncher"); + AZ_CVAR(AZ::CVarFixedString, editorsv_serveraddr, AZ::CVarFixedString(LocalHost), nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The address of the server to connect to"); + AZ_CVAR(uint16_t, editorsv_port, DefaultServerEditorPort, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The port that the multiplayer editor gem will bind to for traffic"); void MultiplayerEditorSystemComponent::Reflect(AZ::ReflectContext* context) { @@ -57,12 +71,14 @@ namespace Multiplayer void MultiplayerEditorSystemComponent::Activate() { + AzFramework::GameEntityContextEventBus::Handler::BusConnect(); AzToolsFramework::EditorEvents::Bus::Handler::BusConnect(); } void MultiplayerEditorSystemComponent::Deactivate() { AzToolsFramework::EditorEvents::Bus::Handler::BusDisconnect(); + AzFramework::GameEntityContextEventBus::Handler::BusDisconnect(); } void MultiplayerEditorSystemComponent::NotifyRegisterViews() @@ -77,50 +93,6 @@ namespace Multiplayer { switch (event) { - case eNotify_OnBeginGameMode: - { - AZ::TickBus::Handler::BusConnect(); - - if (editorsv_enabled) - { - // Assemble the server's path - AZ::CVarFixedString serverProcess = editorsv_process; - if (serverProcess.empty()) - { - // If enabled but no process name is supplied, try this project's ServerLauncher - serverProcess = AZ::Utils::GetProjectName() + ".ServerLauncher"; - } - - AZ::IO::FixedMaxPathString serverPath = AZ::Utils::GetExecutableDirectory(); - if (!serverProcess.contains(AZ_TRAIT_OS_PATH_SEPARATOR)) - { - // If only the process name is specified, append that as well - serverPath.append(AZ_TRAIT_OS_PATH_SEPARATOR + serverProcess); - } - else - { - // If any path was already specified, then simply assign - serverPath = serverProcess; - } - - if (!serverProcess.ends_with(AZ_TRAIT_OS_EXECUTABLE_EXTENSION)) - { - // Add this platform's exe extension if it's not specified - serverPath.append(AZ_TRAIT_OS_EXECUTABLE_EXTENSION); - } - - // Start the configured server if it's available - AzFramework::ProcessLauncher::ProcessLaunchInfo processLaunchInfo; - processLaunchInfo.m_commandlineParameters = - AZStd::string::format("\"%s\"", serverPath.c_str()); - processLaunchInfo.m_showWindow = true; - processLaunchInfo.m_processPriority = AzFramework::ProcessPriority::PROCESSPRIORITY_NORMAL; - - m_serverProcess = AzFramework::ProcessWatcher::LaunchProcess( - processLaunchInfo, AzFramework::ProcessCommunicationType::COMMUNICATOR_TYPE_NONE); - } - break; - } case eNotify_OnQuit: AZ_Warning("Multiplayer Editor", m_editor != nullptr, "Multiplayer Editor received On Quit without an Editor pointer."); if (m_editor) @@ -130,25 +102,132 @@ namespace Multiplayer } [[fallthrough]]; case eNotify_OnEndGameMode: - AZ::TickBus::Handler::BusDisconnect(); // Kill the configured server if it's active if (m_serverProcess) { m_serverProcess->TerminateProcess(0); m_serverProcess = nullptr; } + INetworkInterface* editorNetworkInterface = AZ::Interface::Get()->RetrieveNetworkInterface(AZ::Name(MPEditorInterfaceName)); + if (editorNetworkInterface) + { + editorNetworkInterface->Disconnect(m_editorConnId, AzNetworking::DisconnectReason::TerminatedByClient); + } break; } } - void MultiplayerEditorSystemComponent::OnTick([[maybe_unused]] float deltaTime, [[maybe_unused]] AZ::ScriptTimePoint time) + AzFramework::ProcessWatcher* LaunchEditorServer() { + // Assemble the server's path + AZ::CVarFixedString serverProcess = editorsv_process; + AZ::IO::FixedMaxPath serverPath; + if (serverProcess.empty()) + { + // If enabled but no process name is supplied, try this project's ServerLauncher + serverProcess = AZ::Utils::GetProjectName() + ".ServerLauncher"; + serverPath = AZ::Utils::GetExecutableDirectory(); + serverPath /= serverProcess + AZ_TRAIT_OS_EXECUTABLE_EXTENSION; + } + else + { + serverPath = serverProcess; + } + // Start the configured server if it's available + AzFramework::ProcessLauncher::ProcessLaunchInfo processLaunchInfo; + processLaunchInfo.m_commandlineParameters = AZStd::string::format("\"%s\" --editorsv_isDedicated true", serverPath.c_str()); + processLaunchInfo.m_showWindow = true; + processLaunchInfo.m_processPriority = AzFramework::ProcessPriority::PROCESSPRIORITY_NORMAL; + + // Launch the Server and give it a few seconds to boot up + AzFramework::ProcessWatcher* outProcess = AzFramework::ProcessWatcher::LaunchProcess( + processLaunchInfo, AzFramework::ProcessCommunicationType::COMMUNICATOR_TYPE_NONE); + if (outProcess) + { + AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(15000)); + } + + return outProcess; } - int MultiplayerEditorSystemComponent::GetTickOrder() + void MultiplayerEditorSystemComponent::OnGameEntitiesStarted() { - // Tick immediately after the network system component - return AZ::TICK_PLACEMENT + 1; + auto prefabEditorEntityOwnershipInterface = AZ::Interface::Get(); + if (!prefabEditorEntityOwnershipInterface) + { + AZ_Error("MultiplayerEditor", prefabEditorEntityOwnershipInterface != nullptr, "PrefabEditorEntityOwnershipInterface unavailable"); + } + + // BeginGameMode and Prefab Processing have completed at this point + IMultiplayerTools* mpTools = AZ::Interface::Get(); + if (editorsv_enabled && mpTools != nullptr && mpTools->DidProcessNetworkPrefabs()) + { + const AZStd::vector>& assetData = prefabEditorEntityOwnershipInterface->GetPlayInEditorAssetData(); + + AZStd::vector buffer; + AZ::IO::ByteContainerStream byteStream(&buffer); + + // Serialize Asset information and AssetData into a potentially large buffer + for (const auto& asset : assetData) + { + AZ::Data::AssetId assetId = asset.GetId(); + AZStd::string assetHint = asset.GetHint(); + uint32_t hintSize = aznumeric_cast(assetHint.size()); + + byteStream.Write(sizeof(AZ::Data::AssetId), reinterpret_cast(&assetId)); + byteStream.Write(sizeof(uint32_t), reinterpret_cast(&hintSize)); + byteStream.Write(assetHint.size(), assetHint.data()); + AZ::Utils::SaveObjectToStream(byteStream, AZ::DataStream::ST_BINARY, asset.GetData(), asset.GetData()->GetType()); + } + + const AZ::CVarFixedString remoteAddress = editorsv_serveraddr; + if (editorsv_launch && LocalHost == remoteAddress) + { + m_serverProcess = LaunchEditorServer(); + } + + // Now that the server has launched, attempt to connect the NetworkInterface + INetworkInterface* editorNetworkInterface = AZ::Interface::Get()->RetrieveNetworkInterface(AZ::Name(MPEditorInterfaceName)); + AZ_Assert(editorNetworkInterface, "MP Editor Network Interface was unregistered before Editor could connect."); + m_editorConnId = editorNetworkInterface->Connect( + AzNetworking::IpAddress(remoteAddress.c_str(), editorsv_port, AzNetworking::ProtocolType::Tcp)); + + if (m_editorConnId == AzNetworking::InvalidConnectionId) + { + AZ_Warning( + "MultiplayerEditor", false, + "Could not connect to server targeted by Editor. If using a local server, check that it's built and editorsv_launch is true."); + return; + } + + // Read the buffer into EditorServerInit packets until we've flushed the whole thing + byteStream.Seek(0, AZ::IO::GenericStream::SeekMode::ST_SEEK_BEGIN); + + while (byteStream.GetCurPos() < byteStream.GetLength()) + { + MultiplayerEditorPackets::EditorServerInit packet; + AzNetworking::TcpPacketEncodingBuffer& outBuffer = packet.ModifyAssetData(); + + // Size the packet's buffer appropriately + size_t readSize = TcpPacketEncodingBuffer::GetCapacity(); + size_t byteStreamSize = byteStream.GetLength() - byteStream.GetCurPos(); + if (byteStreamSize < readSize) + { + readSize = byteStreamSize; + } + + outBuffer.Resize(readSize); + byteStream.Read(readSize, outBuffer.GetBuffer()); + + // If we've run out of buffer, mark that we're done + if (byteStream.GetCurPos() == byteStream.GetLength()) + { + packet.SetLastUpdate(true); + } + editorNetworkInterface->SendReliablePacket(m_editorConnId, packet); + } + } + } } diff --git a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.h b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.h index 8c18a2e57a..81b138c675 100644 --- a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.h +++ b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.h @@ -14,15 +14,16 @@ #include +#include + #include #include #include #include - +#include #include #include - namespace AzNetworking { class INetworkInterface; @@ -33,7 +34,7 @@ namespace Multiplayer //! Multiplayer system component wraps the bridging logic between the game and transport layer. class MultiplayerEditorSystemComponent final : public AZ::Component - , private AZ::TickBus::Handler + , private AzFramework::GameEntityContextEventBus::Handler , private AzToolsFramework::EditorEvents::Bus::Handler , private IEditorNotifyListener { @@ -59,17 +60,19 @@ namespace Multiplayer void NotifyRegisterViews() override; //! @} - private: - - //! AZ::TickBus::Handler overrides. + private: + //! EditorEvents::Handler overrides //! @{ - void OnTick(float deltaTime, AZ::ScriptTimePoint time) override; - int GetTickOrder() override; - //! @} - //! void OnEditorNotifyEvent(EEditorNotifyEvent event) override; + //! @} + + //! GameEntityContextEventBus::Handler overrides + //! @{ + void OnGameEntitiesStarted() override; + //! @} IEditor* m_editor = nullptr; AzFramework::ProcessWatcher* m_serverProcess = nullptr; + AzNetworking::ConnectionId m_editorConnId; }; } diff --git a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp index 8eb5bf7b0c..aa6fe6e72c 100644 --- a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp +++ b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp @@ -10,22 +10,27 @@ * */ -#include -#include -#include -#include -#include -#include -#include +#include #include -#include + +#include +#include +#include +#include +#include +#include +#include + #include +#include #include #include #include #include #include +#include #include +#include namespace AZ::ConsoleTypeHelpers { @@ -59,11 +64,8 @@ namespace Multiplayer { using namespace AzNetworking; - static const AZStd::string_view s_networkInterfaceName("MultiplayerNetworkInterface"); - static constexpr uint16_t DefaultServerPort = 30090; - AZ_CVAR(uint16_t, cl_clientport, 0, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The port to bind to for game traffic when connecting to a remote host, a value of 0 will select any available port"); - AZ_CVAR(AZ::CVarFixedString, cl_serveraddr, "127.0.0.1", nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The address of the remote server or host to connect to"); + AZ_CVAR(AZ::CVarFixedString, cl_serveraddr, AZ::CVarFixedString(LocalHost), nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The address of the remote server or host to connect to"); AZ_CVAR(AZ::CVarFixedString, cl_serverpassword, "", nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "Optional server password"); AZ_CVAR(uint16_t, cl_serverport, DefaultServerPort, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The port of the remote host to connect to for game traffic"); AZ_CVAR(uint16_t, sv_port, DefaultServerPort, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The port that this multiplayer gem will bind to for game traffic"); @@ -140,7 +142,7 @@ namespace Multiplayer void MultiplayerSystemComponent::Activate() { AZ::TickBus::Handler::BusConnect(); - m_networkInterface = AZ::Interface::Get()->CreateNetworkInterface(AZ::Name(s_networkInterfaceName), sv_protocol, TrustZone::ExternalClientToServer, *this); + m_networkInterface = AZ::Interface::Get()->CreateNetworkInterface(AZ::Name(MPNetworkInterfaceName), sv_protocol, TrustZone::ExternalClientToServer, *this); m_consoleCommandHandler.Connect(AZ::Interface::Get()->GetConsoleCommandInvokedEvent()); AZ::Interface::Register(this); @@ -664,7 +666,7 @@ namespace Multiplayer { Multiplayer::MultiplayerAgentType serverType = sv_isDedicated ? MultiplayerAgentType::DedicatedServer : MultiplayerAgentType::ClientServer; AZ::Interface::Get()->InitializeMultiplayer(serverType); - INetworkInterface* networkInterface = AZ::Interface::Get()->RetrieveNetworkInterface(AZ::Name(s_networkInterfaceName)); + INetworkInterface* networkInterface = AZ::Interface::Get()->RetrieveNetworkInterface(AZ::Name(MPNetworkInterfaceName)); networkInterface->Listen(sv_port); } AZ_CONSOLEFREEFUNC(host, AZ::ConsoleFunctorFlags::DontReplicate, "Opens a multiplayer connection as a host for other clients to connect to"); @@ -672,7 +674,7 @@ namespace Multiplayer void connect([[maybe_unused]] const AZ::ConsoleCommandContainer& arguments) { AZ::Interface::Get()->InitializeMultiplayer(MultiplayerAgentType::Client); - INetworkInterface* networkInterface = AZ::Interface::Get()->RetrieveNetworkInterface(AZ::Name(s_networkInterfaceName)); + INetworkInterface* networkInterface = AZ::Interface::Get()->RetrieveNetworkInterface(AZ::Name(MPNetworkInterfaceName)); if (arguments.size() < 1) { @@ -702,7 +704,7 @@ namespace Multiplayer void disconnect([[maybe_unused]] const AZ::ConsoleCommandContainer& arguments) { AZ::Interface::Get()->InitializeMultiplayer(MultiplayerAgentType::Uninitialized); - INetworkInterface* networkInterface = AZ::Interface::Get()->RetrieveNetworkInterface(AZ::Name(s_networkInterfaceName)); + INetworkInterface* networkInterface = AZ::Interface::Get()->RetrieveNetworkInterface(AZ::Name(MPNetworkInterfaceName)); auto visitor = [](IConnection& connection) { connection.Disconnect(DisconnectReason::TerminatedByUser, TerminationEndpoint::Local); }; networkInterface->GetConnectionSet().VisitConnections(visitor); } diff --git a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h index 6bedd0599b..1410a82a3c 100644 --- a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h +++ b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h @@ -12,17 +12,20 @@ #pragma once +#include +#include +#include +#include +#include + #include #include #include #include +#include #include #include #include -#include -#include -#include -#include namespace AzNetworking { @@ -72,7 +75,7 @@ namespace Multiplayer bool HandleRequest(AzNetworking::IConnection* connection, const AzNetworking::IPacketHeader& packetHeader, MultiplayerPackets::NotifyClientMigration& packet); bool HandleRequest(AzNetworking::IConnection* connection, const AzNetworking::IPacketHeader& packetHeader, MultiplayerPackets::EntityMigration& packet); bool HandleRequest(AzNetworking::IConnection* connection, const AzNetworking::IPacketHeader& packetHeader, MultiplayerPackets::ReadyForEntityUpdates& packet); - + //! IConnectionListener interface //! @{ AzNetworking::ConnectResult ValidateConnect(const AzNetworking::IpAddress& remoteAddress, const AzNetworking::IPacketHeader& packetHeader, AzNetworking::ISerializer& serializer) override; @@ -109,6 +112,7 @@ namespace Multiplayer AZ_CONSOLEFUNC(MultiplayerSystemComponent, DumpStats, AZ::ConsoleFunctorFlags::Null, "Dumps stats for the current multiplayer session"); AzNetworking::INetworkInterface* m_networkInterface = nullptr; + AzNetworking::INetworkInterface* m_networkEditorInterface = nullptr; AZ::ConsoleCommandInvokedEvent::Handler m_consoleCommandHandler; AZ::ThreadSafeDeque m_cvarCommands; @@ -124,5 +128,9 @@ namespace Multiplayer AZ::TimeMs m_lastReplicatedHostTimeMs = AZ::TimeMs{ 0 }; HostFrameId m_lastReplicatedHostFrameId = InvalidHostFrameId; + +#if !defined(AZ_RELEASE_BUILD) + MultiplayerEditorConnection m_editorConnectionListener; +#endif }; } diff --git a/Gems/Multiplayer/Code/Source/MultiplayerToolsModule.cpp b/Gems/Multiplayer/Code/Source/MultiplayerToolsModule.cpp index 5a223d6214..3646dd52a4 100644 --- a/Gems/Multiplayer/Code/Source/MultiplayerToolsModule.cpp +++ b/Gems/Multiplayer/Code/Source/MultiplayerToolsModule.cpp @@ -10,40 +10,40 @@ * */ -#include -#include +#include +#include #include + #include #include namespace Multiplayer { - //! Multiplayer Tools system component provides serialize context reflection for tools-only systems. - class MultiplayerToolsSystemComponent final - : public AZ::Component + + void MultiplayerToolsSystemComponent::Reflect(AZ::ReflectContext* context) { - public: - AZ_COMPONENT(MultiplayerToolsSystemComponent, "{65AF5342-0ECE-423B-B646-AF55A122F72B}"); + NetworkPrefabProcessor::Reflect(context); + } - static void Reflect(AZ::ReflectContext* context) - { - NetworkPrefabProcessor::Reflect(context); - } + void MultiplayerToolsSystemComponent::Activate() + { + AZ::Interface::Register(this); + } - MultiplayerToolsSystemComponent() = default; - ~MultiplayerToolsSystemComponent() override = default; + void MultiplayerToolsSystemComponent::Deactivate() + { + AZ::Interface::Unregister(this); + } - /// AZ::Component overrides. - void Activate() override - { + bool MultiplayerToolsSystemComponent::DidProcessNetworkPrefabs() + { + return m_didProcessNetPrefabs; + } - } - - void Deactivate() override - { - - } - }; + void MultiplayerToolsSystemComponent::SetDidProcessNetworkPrefabs(bool didProcessNetPrefabs) + { + m_didProcessNetPrefabs = didProcessNetPrefabs; + } MultiplayerToolsModule::MultiplayerToolsModule() : AZ::Module() diff --git a/Gems/Multiplayer/Code/Source/MultiplayerToolsModule.h b/Gems/Multiplayer/Code/Source/MultiplayerToolsModule.h index 823bd63a1d..b05e2aa5fb 100644 --- a/Gems/Multiplayer/Code/Source/MultiplayerToolsModule.h +++ b/Gems/Multiplayer/Code/Source/MultiplayerToolsModule.h @@ -12,10 +12,36 @@ #pragma once +#include #include +#include namespace Multiplayer { + class MultiplayerToolsSystemComponent final + : public AZ::Component + , public IMultiplayerTools + { + public: + AZ_COMPONENT(MultiplayerToolsSystemComponent, "{65AF5342-0ECE-423B-B646-AF55A122F72B}"); + + static void Reflect(AZ::ReflectContext* context); + + MultiplayerToolsSystemComponent() = default; + ~MultiplayerToolsSystemComponent() override = default; + + /// AZ::Component overrides. + void Activate() override; + void Deactivate() override; + + bool DidProcessNetworkPrefabs() override; + + private: + void SetDidProcessNetworkPrefabs(bool didProcessNetPrefabs) override; + + bool m_didProcessNetPrefabs = false; + }; + class MultiplayerToolsModule : public AZ::Module { diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicationManager.cpp b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicationManager.cpp index e47d142be8..ba8158ca18 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicationManager.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicationManager.cpp @@ -555,10 +555,10 @@ namespace Multiplayer { replicatorEntity = entityList[0]; } - - AZ_Assert(replicatorEntity != nullptr, "Failed to create entity from prefab %s", prefabEntityId.m_prefabName.GetCStr()); - if (replicatorEntity == nullptr) + else { + AZ_Assert(false, "There should be exactly one created entity out of prefab %s, index %d. Got: %d", + prefabEntityId.m_prefabName.GetCStr(), prefabEntityId.m_entityOffset, entityList.size()); return false; } } diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp index 6f3fdf2b23..dda3f18ad4 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp @@ -34,18 +34,15 @@ namespace Multiplayer : m_networkEntityAuthorityTracker(*this) , m_removeEntitiesEvent([this] { RemoveEntities(); }, AZ::Name("NetworkEntityManager remove entities event")) , m_updateEntityDomainEvent([this] { UpdateEntityDomain(); }, AZ::Name("NetworkEntityManager update entity domain event")) - , m_onSpawnedHandler([this](AZ::Data::Asset spawnable) { this->OnSpawned(spawnable); }) - , m_onDespawnedHandler([this](AZ::Data::Asset spawnable) { this->OnDespawned(spawnable); }) { + AZ::Interface::Register(this); AzFramework::RootSpawnableNotificationBus::Handler::BusConnect(); - - AzFramework::SpawnableEntitiesInterface::Get()->AddOnSpawnedHandler(m_onSpawnedHandler); - AzFramework::SpawnableEntitiesInterface::Get()->AddOnDespawnedHandler(m_onDespawnedHandler); } NetworkEntityManager::~NetworkEntityManager() { AzFramework::RootSpawnableNotificationBus::Handler::BusDisconnect(); + AZ::Interface::Unregister(this); } void NetworkEntityManager::Initialize(HostId hostId, AZStd::unique_ptr entityDomain) @@ -331,17 +328,41 @@ namespace Multiplayer const AzFramework::Spawnable::EntityList& entities = spawnable.GetEntities(); size_t entitiesSize = entities.size(); + using EntityIdMap = AZStd::unordered_map; + EntityIdMap originalToCloneIdMap; + for (size_t i = 0; i < entitiesSize; ++i) { - AZ::Entity* clone = serializeContext->CloneObject(entities[i].get()); + AZ::Entity* originalEntity = entities[i].get(); + AZ::Entity* clone = serializeContext->CloneObject(originalEntity); AZ_Assert(clone != nullptr, "Failed to clone spawnable entity."); + clone->SetId(AZ::Entity::MakeId()); + originalToCloneIdMap[originalEntity->GetId()] = clone->GetId(); + NetBindComponent* netBindComponent = clone->FindComponent(); if (netBindComponent != nullptr) { + // Update TransformComponent parent Id. It is guaranteed for the entities array to be sorted from parent->child here. + auto* transformComponent = clone->FindComponent(); + AZ::EntityId parentId = transformComponent->GetParentId(); + if (parentId.IsValid()) + { + auto it = originalToCloneIdMap.find(parentId); + if (it != originalToCloneIdMap.end()) + { + transformComponent->SetParentRelative(it->second); + } + else + { + AZ_Warning("NetworkEntityManager", false, "Entity %s doesn't have the parent entity %s present in network.spawnable", + clone->GetName().c_str(), parentId.ToString().data()); + } + } + PrefabEntityId prefabEntityId; - prefabEntityId.m_prefabName = m_networkPrefabLibrary.GetPrefabNameFromAssetId(spawnable.GetId()); + prefabEntityId.m_prefabName = m_networkPrefabLibrary.GetSpawnableNameFromAssetId(spawnable.GetId()); prefabEntityId.m_entityOffset = aznumeric_cast(i); const NetEntityId netEntityId = NextId(); @@ -469,57 +490,19 @@ namespace Multiplayer } } - void NetworkEntityManager::OnSpawned(AZ::Data::Asset spawnable) + void NetworkEntityManager::SetupNetEntity(AZ::Entity* netEntity, PrefabEntityId prefabEntityId, NetEntityRole netEntityRole) { - AzFramework::Spawnable* spawnableData = spawnable.GetAs(); - const auto& entityList = spawnableData->GetEntities(); - if (entityList.size() == 0) + auto* netBindComponent = netEntity->FindComponent(); + + if (netBindComponent) { - AZ_Error("NetworkEntityManager", false, "OnSpawned: Spawnable %s doesn't have any entities.", - spawnable.GetHint().c_str()); - return; + const NetEntityId netEntityId = NextId(); + netBindComponent->PreInit(netEntity, prefabEntityId, netEntityId, netEntityRole); } - - const auto& rootEntity = entityList[0]; - auto* spawnableHolder = rootEntity->FindComponent(); - if (!spawnableHolder) + else { - // Root entity doesn't have NetworkSpawnableHolderComponent. It means there's no corresponding network spawnable. - return; + AZ_Error("NetworkEntityManager", false, "SetupNetEntity called for an entity with no NetBindComponent. Entity: %s", + netEntity->GetName().c_str()); } - - AZ::Data::Asset netSpawnableAsset = spawnableHolder->GetNetworkSpawnableAsset(); - AzFramework::Spawnable* netSpawnable = netSpawnableAsset.GetAs(); - if (!netSpawnable) - { - // TODO: Temp sync load until JsonSerialization of loadBehavior is fixed. - netSpawnableAsset = AZ::Data::AssetManager::Instance().GetAsset( - netSpawnableAsset.GetId(), AZ::Data::AssetLoadBehavior::PreLoad); - AZ::Data::AssetManager::Instance().BlockUntilLoadComplete(netSpawnableAsset); - - netSpawnable = netSpawnableAsset.GetAs(); - } - - if (!netSpawnable) - { - AZ_Error("NetworkEntityManager", false, "OnRootSpawnableAssigned: Net spawnable doesn't have any data."); - return; - } - - auto* multiplayer = GetMultiplayer(); - - const auto agentType = multiplayer->GetAgentType(); - const bool spawnImmediately = - (agentType == MultiplayerAgentType::ClientServer || agentType == MultiplayerAgentType::DedicatedServer); - - if (spawnImmediately) - { - CreateEntitiesImmediate(*netSpawnable, NetEntityRole::Authority); - } - } - - void NetworkEntityManager::OnDespawned([[maybe_unused]]AZ::Data::Asset spawnable) - { - // TODO: Remove entities instantiated from the spawnable } } diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.h b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.h index 3291f2c38d..dd9ac99a06 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.h +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.h @@ -62,6 +62,8 @@ namespace Multiplayer const AZ::Transform& transform ) override; + void SetupNetEntity(AZ::Entity* netEntity, PrefabEntityId prefabEntityId, NetEntityRole netEntityRole) override; + uint32_t GetEntityCount() const override; NetworkEntityHandle AddEntityToEntityMap(NetEntityId netEntityId, AZ::Entity* entity) override; void MarkForRemoval(const ConstNetworkEntityHandle& entityHandle) override; @@ -93,9 +95,6 @@ namespace Multiplayer void RemoveEntities(); NetEntityId NextId(); - void OnSpawned(AZ::Data::Asset spawnable); - void OnDespawned(AZ::Data::Asset spawnable); - NetworkEntityTracker m_networkEntityTracker; NetworkEntityAuthorityTracker m_networkEntityAuthorityTracker; MultiplayerComponentRegistry m_multiplayerComponentRegistry; @@ -123,8 +122,5 @@ namespace Multiplayer DeferredRpcMessages m_localDeferredRpcMessages; NetworkSpawnableLibrary m_networkPrefabLibrary; - - AZ::Event>::Handler m_onSpawnedHandler; - AZ::Event>::Handler m_onDespawnedHandler; }; } diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkSpawnableLibrary.cpp b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkSpawnableLibrary.cpp index ebf5d2609b..935744f807 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkSpawnableLibrary.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkSpawnableLibrary.cpp @@ -14,20 +14,23 @@ #include #include #include +#include namespace Multiplayer { NetworkSpawnableLibrary::NetworkSpawnableLibrary() { + AZ::Interface::Register(this); AzFramework::AssetCatalogEventBus::Handler::BusConnect(); } NetworkSpawnableLibrary::~NetworkSpawnableLibrary() { AzFramework::AssetCatalogEventBus::Handler::BusDisconnect(); + AZ::Interface::Unregister(this); } - void NetworkSpawnableLibrary::BuildPrefabsList() + void NetworkSpawnableLibrary::BuildSpawnablesList() { auto enumerateCallback = [this](const AZ::Data::AssetId id, const AZ::Data::AssetInfo& info) { @@ -50,10 +53,10 @@ namespace Multiplayer void NetworkSpawnableLibrary::OnCatalogLoaded([[maybe_unused]] const char* catalogFile) { - BuildPrefabsList(); + BuildSpawnablesList(); } - AZ::Name NetworkSpawnableLibrary::GetPrefabNameFromAssetId(AZ::Data::AssetId assetId) + AZ::Name NetworkSpawnableLibrary::GetSpawnableNameFromAssetId(AZ::Data::AssetId assetId) { if (assetId.IsValid()) { diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkSpawnableLibrary.h b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkSpawnableLibrary.h index a2c3d4ae56..6ebe2a7418 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkSpawnableLibrary.h +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkSpawnableLibrary.h @@ -12,30 +12,31 @@ #pragma once -#include -#include +#include #include -#include namespace Multiplayer { /// Implementation of the network prefab library interface. class NetworkSpawnableLibrary final - : private AzFramework::AssetCatalogEventBus::Handler + : public INetworkSpawnableLibrary + , private AzFramework::AssetCatalogEventBus::Handler { public: + AZ_RTTI(NetworkSpawnableLibrary, "{65E15F33-E893-49C2-A8E2-B6A8A6EF31E0}", INetworkSpawnableLibrary); + NetworkSpawnableLibrary(); ~NetworkSpawnableLibrary(); - void BuildPrefabsList(); - void ProcessSpawnableAsset(const AZStd::string& relativePath, AZ::Data::AssetId id); + /// INetworkSpawnableLibrary overrides. + void BuildSpawnablesList() override; + void ProcessSpawnableAsset(const AZStd::string& relativePath, AZ::Data::AssetId id) override; + AZ::Name GetSpawnableNameFromAssetId(AZ::Data::AssetId assetId) override; + AZ::Data::AssetId GetAssetIdByName(AZ::Name name) override; /// AssetCatalogEventBus overrides. void OnCatalogLoaded(const char* catalogFile) override; - AZ::Name GetPrefabNameFromAssetId(AZ::Data::AssetId assetId); - AZ::Data::AssetId GetAssetIdByName(AZ::Name name); - private: AZStd::unordered_map m_spawnables; AZStd::unordered_map m_spawnablesReverseLookup; diff --git a/Gems/Multiplayer/Code/Source/Pipeline/NetBindMarkerComponent.cpp b/Gems/Multiplayer/Code/Source/Pipeline/NetBindMarkerComponent.cpp index 84983b700e..1696e851a5 100644 --- a/Gems/Multiplayer/Code/Source/Pipeline/NetBindMarkerComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Pipeline/NetBindMarkerComponent.cpp @@ -11,7 +11,12 @@ */ #include +#include #include +#include +#include +#include +#include namespace Multiplayer { @@ -21,15 +26,91 @@ namespace Multiplayer if (serializeContext) { serializeContext->Class() - ->Version(1); + ->Version(1) + ->Field("NetEntityIndex", &NetBindMarkerComponent::m_netEntityIndex) + ->Field("NetSpawnableAsset", &NetBindMarkerComponent::m_networkSpawnableAsset); } } + AzFramework::Spawnable* GetSpawnableFromAsset(AZ::Data::Asset& asset) + { + AzFramework::Spawnable* spawnable = asset.GetAs(); + if (!spawnable) + { + asset = + AZ::Data::AssetManager::Instance().GetAsset(asset.GetId(), AZ::Data::AssetLoadBehavior::PreLoad); + AZ::Data::AssetManager::Instance().BlockUntilLoadComplete(asset); + + spawnable = asset.GetAs(); + } + + return spawnable; + } + + void NetBindMarkerComponent::Activate() { + const auto agentType = AZ::Interface::Get()->GetAgentType(); + const bool spawnImmediately = + (agentType == MultiplayerAgentType::ClientServer || agentType == MultiplayerAgentType::DedicatedServer); + + if (spawnImmediately && m_networkSpawnableAsset.GetId().IsValid()) + { + AZ::Transform worldTm = GetEntity()->FindComponent()->GetWorldTM(); + auto preInsertionCallback = + [worldTm = AZStd::move(worldTm), netEntityIndex = m_netEntityIndex, spawnableAssetId = m_networkSpawnableAsset.GetId()] + (AzFramework::EntitySpawnTicket&, AzFramework::SpawnableEntityContainerView entities) + { + if (entities.size() == 1) + { + AZ::Entity* netEntity = *entities.begin(); + + auto* transformComponent = netEntity->FindComponent(); + transformComponent->SetWorldTM(worldTm); + + AZ::Name spawnableName = AZ::Interface::Get()->GetSpawnableNameFromAssetId(spawnableAssetId); + PrefabEntityId prefabEntityId; + prefabEntityId.m_prefabName = spawnableName; + prefabEntityId.m_entityOffset = netEntityIndex; + AZ::Interface::Get()->SetupNetEntity(netEntity, prefabEntityId, NetEntityRole::Authority); + } + else + { + AZ_Error("NetBindMarkerComponent", false, "Requested to spawn 1 entity, but received %d", entities.size()); + } + }; + + m_netSpawnTicket = AzFramework::EntitySpawnTicket(m_networkSpawnableAsset); + AzFramework::SpawnableEntitiesInterface::Get()->SpawnEntities(m_netSpawnTicket, {m_netEntityIndex}, preInsertionCallback); + } } void NetBindMarkerComponent::Deactivate() { + if(m_netSpawnTicket.IsValid()) + { + AzFramework::SpawnableEntitiesInterface::Get()->DespawnAllEntities(m_netSpawnTicket); + } } + + size_t NetBindMarkerComponent::GetNetEntityIndex() const + { + return m_netEntityIndex; + } + + void NetBindMarkerComponent::SetNetEntityIndex(size_t netEntityIndex) + { + m_netEntityIndex = netEntityIndex; + } + + void NetBindMarkerComponent::SetNetworkSpawnableAsset(AZ::Data::Asset networkSpawnableAsset) + { + m_networkSpawnableAsset = networkSpawnableAsset; + } + + AZ::Data::Asset NetBindMarkerComponent::GetNetworkSpawnableAsset() const + { + return m_networkSpawnableAsset; + } + } diff --git a/Gems/Multiplayer/Code/Source/Pipeline/NetBindMarkerComponent.h b/Gems/Multiplayer/Code/Source/Pipeline/NetBindMarkerComponent.h index ebafead73c..5ab42ab3aa 100644 --- a/Gems/Multiplayer/Code/Source/Pipeline/NetBindMarkerComponent.h +++ b/Gems/Multiplayer/Code/Source/Pipeline/NetBindMarkerComponent.h @@ -13,6 +13,9 @@ #pragma once #include +#include +#include +#include namespace Multiplayer { @@ -34,6 +37,15 @@ namespace Multiplayer void Deactivate() override; //! @} + size_t GetNetEntityIndex() const; + void SetNetEntityIndex(size_t val); + + void SetNetworkSpawnableAsset(AZ::Data::Asset networkSpawnableAsset); + AZ::Data::Asset GetNetworkSpawnableAsset() const; + private: + AZ::Data::Asset m_networkSpawnableAsset{AZ::Data::AssetLoadBehavior::PreLoad}; + size_t m_netEntityIndex = 0; + AzFramework::EntitySpawnTicket m_netSpawnTicket; }; } // namespace Multiplayer diff --git a/Gems/Multiplayer/Code/Source/Pipeline/NetworkPrefabProcessor.cpp b/Gems/Multiplayer/Code/Source/Pipeline/NetworkPrefabProcessor.cpp index 4661df7564..0bbfe17801 100644 --- a/Gems/Multiplayer/Code/Source/Pipeline/NetworkPrefabProcessor.cpp +++ b/Gems/Multiplayer/Code/Source/Pipeline/NetworkPrefabProcessor.cpp @@ -10,7 +10,11 @@ * */ -#include +#include +#include +#include +#include +#include #include #include @@ -18,9 +22,6 @@ #include #include #include -#include -#include -#include namespace Multiplayer { @@ -29,9 +30,20 @@ namespace Multiplayer void NetworkPrefabProcessor::Process(PrefabProcessorContext& context) { + IMultiplayerTools* mpTools = AZ::Interface::Get(); + if (mpTools) + { + mpTools->SetDidProcessNetworkPrefabs(false); + } + context.ListPrefabs([&context](AZStd::string_view prefabName, PrefabDom& prefab) { ProcessPrefab(context, prefabName, prefab); }); + + if (mpTools && !context.GetProcessedObjects().empty()) + { + mpTools->SetDidProcessNetworkPrefabs(true); + } } void NetworkPrefabProcessor::Reflect(AZ::ReflectContext* context) @@ -59,6 +71,7 @@ namespace Multiplayer return result; } + void NetworkPrefabProcessor::ProcessPrefab(PrefabProcessorContext& context, AZStd::string_view prefabName, PrefabDom& prefab) { using namespace AzToolsFramework::Prefab; @@ -113,29 +126,35 @@ namespace Multiplayer AZStd::unique_ptr networkInstance(aznew Instance()); - for (auto entityId : networkedEntityIds) - { - AZ::Entity* netEntity = sourceInstance->DetachEntity(entityId).release(); + AZ::Data::Asset networkSpawnableAsset; + networkSpawnableAsset.Create(networkSpawnable->GetId()); + networkSpawnableAsset.SetAutoLoadBehavior(AZ::Data::AssetLoadBehavior::PreLoad); + for (size_t entityIndex = 0; entityIndex < networkedEntityIds.size(); ++entityIndex) + { + AZ::EntityId entityId = networkedEntityIds[entityIndex]; + + AZ::Entity* netEntity = sourceInstance->DetachEntity(entityId).release(); + // Net entity will need a new ID to avoid IDs collision + netEntity->SetId(AZ::Entity::MakeId()); networkInstance->AddEntity(*netEntity); - AZ::Entity* breadcrumbEntity = aznew AZ::Entity(netEntity->GetName()); + // Use the old ID for the breadcrumb entity to keep parent-child relationship in the original spawnable + AZ::Entity* breadcrumbEntity = aznew AZ::Entity(entityId, netEntity->GetName()); breadcrumbEntity->SetRuntimeActiveByDefault(netEntity->IsRuntimeActiveByDefault()); - breadcrumbEntity->CreateComponent(); + + NetBindMarkerComponent* netBindMarkerComponent = breadcrumbEntity->CreateComponent(); + // Each spawnable has a root meta-data entity at position 0, so starting net indices from 1 + netBindMarkerComponent->SetNetEntityIndex(entityIndex + 1); + netBindMarkerComponent->SetNetworkSpawnableAsset(networkSpawnableAsset); AzFramework::TransformComponent* transformComponent = netEntity->FindComponent(); breadcrumbEntity->CreateComponent(*transformComponent); - - // TODO: Configure NetBindMarkerComponent to refer to the net entity + sourceInstance->AddEntity(*breadcrumbEntity); } // Add net spawnable asset holder { - AZ::Data::AssetId assetId = networkSpawnable->GetId(); - AZ::Data::Asset networkSpawnableAsset; - networkSpawnableAsset.Create(assetId); - networkSpawnableAsset.SetAutoLoadBehavior(AZ::Data::AssetLoadBehavior::PreLoad); - EntityOptionalReference containerEntityRef = sourceInstance->GetContainerEntity(); if (containerEntityRef.has_value()) { @@ -175,6 +194,9 @@ namespace Multiplayer (*it)->InvalidateDependencies(); (*it)->EvaluateDependencies(); } + + SpawnableUtils::SortEntitiesByTransformHierarchy(*networkSpawnable); + context.GetProcessedObjects().push_back(AZStd::move(object)); } else diff --git a/Gems/Multiplayer/Code/Tests/MainTools.cpp b/Gems/Multiplayer/Code/Tests/MainTools.cpp new file mode 100644 index 0000000000..ccb4d568f8 --- /dev/null +++ b/Gems/Multiplayer/Code/Tests/MainTools.cpp @@ -0,0 +1,55 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Multiplayer +{ + class MultiplayerToolsTestEnvironment : public AZ::Test::GemTestEnvironment + { + AZ::ComponentApplication* CreateApplicationInstance() override + { + return aznew UnitTest::ToolsTestApplication("MultiplayerToolsTest"); + } + + void AddGemsAndComponents() override + { + AZStd::vector descriptors({ + NetBindComponent::CreateDescriptor(), + NetBindMarkerComponent::CreateDescriptor(), + NetworkSpawnableHolderComponent::CreateDescriptor() + }); + + AddComponentDescriptors(descriptors); + } + }; +} // namespace UnitTest + +// Required to support running integration tests with Qt +AZTEST_EXPORT int AZ_UNIT_TEST_HOOK_NAME(int argc, char** argv) +{ + ::testing::InitGoogleMock(&argc, argv); + AzQtComponents::PrepareQtPaths(); + QApplication app(argc, argv); + AZ::Test::printUnusedParametersWarning(argc, argv); + AZ::Test::addTestEnvironments({new Multiplayer::MultiplayerToolsTestEnvironment}); + int result = RUN_ALL_TESTS(); + return result; +} diff --git a/Gems/Multiplayer/Code/Tests/MultiplayerSystemTests.cpp b/Gems/Multiplayer/Code/Tests/MultiplayerSystemTests.cpp index 2e0b9c0759..6ace4db592 100644 --- a/Gems/Multiplayer/Code/Tests/MultiplayerSystemTests.cpp +++ b/Gems/Multiplayer/Code/Tests/MultiplayerSystemTests.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -30,6 +31,7 @@ namespace UnitTest SetupAllocator(); AZ::NameDictionary::Create(); m_spawnableComponent = new AzFramework::SpawnableSystemComponent(); + m_netComponent = new AzNetworking::NetworkingSystemComponent(); m_mpComponent = new Multiplayer::MultiplayerSystemComponent(); m_initHandler = Multiplayer::SessionInitEvent::Handler([this](AzNetworking::INetworkInterface* value) { TestInitEvent(value); }); @@ -43,6 +45,7 @@ namespace UnitTest void TearDown() override { delete m_mpComponent; + delete m_netComponent; delete m_spawnableComponent; AZ::NameDictionary::Destroy(); TeardownAllocator(); @@ -71,6 +74,7 @@ namespace UnitTest Multiplayer::SessionShutdownEvent::Handler m_shutdownHandler; Multiplayer::ConnectionAcquiredEvent::Handler m_connAcquiredHandler; + AzNetworking::NetworkingSystemComponent* m_netComponent = nullptr; Multiplayer::MultiplayerSystemComponent* m_mpComponent = nullptr; AzFramework::SpawnableSystemComponent* m_spawnableComponent = nullptr; }; diff --git a/Gems/Multiplayer/Code/Tests/PrefabProcessingTests.cpp b/Gems/Multiplayer/Code/Tests/PrefabProcessingTests.cpp new file mode 100644 index 0000000000..c441d2ba5f --- /dev/null +++ b/Gems/Multiplayer/Code/Tests/PrefabProcessingTests.cpp @@ -0,0 +1,114 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace UnitTest +{ + class PrefabProcessingTestFixture : public ::testing::Test + { + public: + static void ConvertEntitiesToPrefab(const AZStd::vector& entities, AzToolsFramework::Prefab::PrefabDom& prefabDom) + { + auto* prefabSystem = AZ::Interface::Get(); + AZStd::unique_ptr sourceInstance(prefabSystem->CreatePrefab(entities, {}, "test/path")); + ASSERT_TRUE(sourceInstance); + + auto& prefabTemplateDom = prefabSystem->FindTemplateDom(sourceInstance->GetTemplateId()); + prefabDom.CopyFrom(prefabTemplateDom, prefabDom.GetAllocator()); + } + + static AZ::Entity* CreateSourceEntity(const char* name, bool networked, const AZ::Transform& tm, AZ::Entity* parent = nullptr) + { + AZ::Entity* entity = aznew AZ::Entity(name); + auto* transformComponent = entity->CreateComponent(); + + if (parent) + { + transformComponent->SetParent(parent->GetId()); + transformComponent->SetLocalTM(tm); + } + else + { + transformComponent->SetWorldTM(tm); + } + + if(networked) + { + entity->CreateComponent(); + } + + return entity; + } + }; + + TEST_F(PrefabProcessingTestFixture, NetworkPrefabProcessor_ProcessPrefabTwoEntities_NetEntityGoesToNetSpawnable) + { + using AzToolsFramework::Prefab::PrefabConversionUtils::PrefabProcessorContext; + + AZStd::vector entities; + + // Create test entities: 1 networked and 1 static + const AZStd::string staticEntityName = "static_floor"; + entities.emplace_back(CreateSourceEntity(staticEntityName.c_str(), false, AZ::Transform::CreateIdentity())); + + const AZStd::string netEntityName = "networked_entity"; + entities.emplace_back(CreateSourceEntity(netEntityName.c_str(), true, AZ::Transform::CreateIdentity())); + + // Convert the entities into prefab. Note: This will transfer the ownership of AZ::Entity* into Prefab + AzToolsFramework::Prefab::PrefabDom prefabDom; + ConvertEntitiesToPrefab(entities, prefabDom); + + // Add the prefab into the Prefab Processor Context + const AZStd::string prefabName = "testPrefab"; + PrefabProcessorContext prefabProcessorContext{AZ::Uuid::CreateRandom()}; + prefabProcessorContext.AddPrefab(prefabName, AZStd::move(prefabDom)); + + // Request NetworkPrefabProcessor to process the prefab + Multiplayer::NetworkPrefabProcessor processor; + processor.Process(prefabProcessorContext); + + // Validate results + EXPECT_TRUE(prefabProcessorContext.HasCompletedSuccessfully()); + + // Should be 1 networked spawnable + const auto& processedObjects = prefabProcessorContext.GetProcessedObjects(); + EXPECT_EQ(processedObjects.size(), 1); + + // Verify the name and the type of the spawnable asset + const AZ::Data::AssetData& spawnableAsset = processedObjects[0].GetAsset(); + EXPECT_EQ(prefabName + ".network.spawnable", processedObjects[0].GetId()); + EXPECT_EQ(spawnableAsset.GetType(), azrtti_typeid()); + + // Verify we have only the networked entity in the network spawnable and not the static one + const AzFramework::Spawnable* netSpawnable = azrtti_cast(&spawnableAsset); + const AzFramework::Spawnable::EntityList& entityList = netSpawnable->GetEntities(); + auto countEntityCallback = [](const auto& name) + { + return [name](const auto& entity) + { + return entity->GetName() == name; + }; + }; + + EXPECT_EQ(0, AZStd::count_if(entityList.begin(), entityList.end(), countEntityCallback(staticEntityName))); + EXPECT_EQ(1, AZStd::count_if(entityList.begin(), entityList.end(), countEntityCallback(netEntityName))); + } + +} // namespace UnitTest diff --git a/Gems/Multiplayer/Code/multiplayer_editor_shared_files.cmake b/Gems/Multiplayer/Code/multiplayer_editor_shared_files.cmake index 2d5611d4b6..3fb76061b8 100644 --- a/Gems/Multiplayer/Code/multiplayer_editor_shared_files.cmake +++ b/Gems/Multiplayer/Code/multiplayer_editor_shared_files.cmake @@ -12,8 +12,8 @@ set(FILES Source/MultiplayerGem.cpp Source/MultiplayerGem.h - Source/MultiplayerEditorGem.cpp - Source/MultiplayerEditorGem.h + Source/Editor/MultiplayerEditorGem.cpp + Source/Editor/MultiplayerEditorGem.h Source/Editor/MultiplayerEditorSystemComponent.cpp Source/Editor/MultiplayerEditorSystemComponent.h ) diff --git a/Gems/Multiplayer/Code/multiplayer_files.cmake b/Gems/Multiplayer/Code/multiplayer_files.cmake index 8dd7c07689..eb856a48db 100644 --- a/Gems/Multiplayer/Code/multiplayer_files.cmake +++ b/Gems/Multiplayer/Code/multiplayer_files.cmake @@ -11,6 +11,8 @@ set(FILES Include/Multiplayer/IMultiplayer.h + Include/Multiplayer/IMultiplayerTools.h + Include/Multiplayer/MultiplayerConstants.h Include/Multiplayer/MultiplayerStats.h Include/Multiplayer/MultiplayerTypes.h Include/Multiplayer/Components/LocalPredictionPlayerInputComponent.h @@ -22,6 +24,7 @@ set(FILES Include/Multiplayer/ConnectionData/IConnectionData.h Include/Multiplayer/EntityDomains/IEntityDomain.h Include/Multiplayer/NetworkEntity/INetworkEntityManager.h + Include/Multiplayer/INetworkSpawnableLibrary.h Include/Multiplayer/NetworkEntity/NetworkEntityRpcMessage.h Include/Multiplayer/NetworkEntity/NetworkEntityUpdateMessage.h Include/Multiplayer/NetworkEntity/NetworkEntityHandle.h @@ -45,6 +48,7 @@ set(FILES Source/AutoGen/AutoComponentTypes_Source.jinja Source/AutoGen/LocalPredictionPlayerInputComponent.AutoComponent.xml Source/AutoGen/Multiplayer.AutoPackets.xml + Source/AutoGen/MultiplayerEditor.AutoPackets.xml Source/AutoGen/NetworkTransformComponent.AutoComponent.xml Source/Components/LocalPredictionPlayerInputComponent.cpp Source/Components/MultiplayerComponent.cpp @@ -58,6 +62,8 @@ set(FILES Source/ConnectionData/ServerToClientConnectionData.cpp Source/ConnectionData/ServerToClientConnectionData.h Source/ConnectionData/ServerToClientConnectionData.inl + Source/Editor/MultiplayerEditorConnection.cpp + Source/Editor/MultiplayerEditorConnection.h Source/EntityDomains/FullOwnershipEntityDomain.cpp Source/EntityDomains/FullOwnershipEntityDomain.h Source/NetworkEntity/EntityReplication/EntityReplicationManager.cpp diff --git a/Gems/Multiplayer/Code/multiplayer_tools_files.cmake b/Gems/Multiplayer/Code/multiplayer_tools_files.cmake index 1be02fd999..3fef954ba6 100644 --- a/Gems/Multiplayer/Code/multiplayer_tools_files.cmake +++ b/Gems/Multiplayer/Code/multiplayer_tools_files.cmake @@ -10,6 +10,7 @@ # set(FILES + Include/Multiplayer/IMultiplayerTools.h Source/Multiplayer_precompiled.cpp Source/Multiplayer_precompiled.h Source/Pipeline/NetworkPrefabProcessor.cpp diff --git a/Gems/Multiplayer/Code/multiplayer_editor_files.cmake b/Gems/Multiplayer/Code/multiplayer_tools_tests_files.cmake similarity index 84% rename from Gems/Multiplayer/Code/multiplayer_editor_files.cmake rename to Gems/Multiplayer/Code/multiplayer_tools_tests_files.cmake index ce3e3227e0..c308b3de52 100644 --- a/Gems/Multiplayer/Code/multiplayer_editor_files.cmake +++ b/Gems/Multiplayer/Code/multiplayer_tools_tests_files.cmake @@ -10,6 +10,6 @@ # set(FILES - Source/Editor/MultiplayerEditorDispatcher.cpp - Source/Editor/MultiplayerEditorDispatcher.h + Tests/MainTools.cpp + Tests/PrefabProcessingTests.cpp ) diff --git a/Gems/NvCloth/Code/Source/Components/ClothComponentMesh/ActorClothColliders.cpp b/Gems/NvCloth/Code/Source/Components/ClothComponentMesh/ActorClothColliders.cpp index f63382ca6e..2c9d14dc94 100644 --- a/Gems/NvCloth/Code/Source/Components/ClothComponentMesh/ActorClothColliders.cpp +++ b/Gems/NvCloth/Code/Source/Components/ClothComponentMesh/ActorClothColliders.cpp @@ -95,7 +95,7 @@ namespace NvCloth continue; } - for (const Physics::ShapeConfigurationPair& shapeConfigPair : clothNodeConfig.m_shapes) + for (const AzPhysics::ShapeColliderPair& shapeConfigPair : clothNodeConfig.m_shapes) { const auto& colliderConfig = shapeConfigPair.first; diff --git a/Gems/PhysX/Code/Include/PhysX/ColliderComponentBus.h b/Gems/PhysX/Code/Include/PhysX/ColliderComponentBus.h index fc06d69b4a..4b53ccec4e 100644 --- a/Gems/PhysX/Code/Include/PhysX/ColliderComponentBus.h +++ b/Gems/PhysX/Code/Include/PhysX/ColliderComponentBus.h @@ -30,7 +30,7 @@ namespace PhysX { public: //! Gets the collection of collider configuration / shape configuration pairs used to define the collider's shapes. - virtual Physics::ShapeConfigurationList GetShapeConfigurations() = 0; + virtual AzPhysics::ShapeColliderPairList GetShapeConfigurations() = 0; //! Gets the collection of physics shapes associated with the collider. virtual AZStd::vector> GetShapes() = 0; diff --git a/Gems/PhysX/Code/Include/PhysX/MeshAsset.h b/Gems/PhysX/Code/Include/PhysX/MeshAsset.h index 1af297edcc..8467c04359 100644 --- a/Gems/PhysX/Code/Include/PhysX/MeshAsset.h +++ b/Gems/PhysX/Code/Include/PhysX/MeshAsset.h @@ -58,7 +58,7 @@ namespace PhysX static constexpr AZ::u16 TriangleMeshMaterialIndex = (std::numeric_limits::max)(); using ShapeConfigurationPair = AZStd::pair, - AZStd::shared_ptr>; // Have to use shared_ptr here because Physics::ShapeConfigurationList uses it + AZStd::shared_ptr>; // Have to use shared_ptr here because AzPhysics::ShapeColliderPairList uses it using ShapeConfigurationList = AZStd::vector; ShapeConfigurationList m_colliderShapes; //!< Shapes data with optional collider configuration override. diff --git a/Gems/PhysX/Code/Source/BaseColliderComponent.cpp b/Gems/PhysX/Code/Source/BaseColliderComponent.cpp index c8581efcb7..57a37e5e88 100644 --- a/Gems/PhysX/Code/Source/BaseColliderComponent.cpp +++ b/Gems/PhysX/Code/Source/BaseColliderComponent.cpp @@ -104,7 +104,7 @@ namespace PhysX } } - void BaseColliderComponent::SetShapeConfigurationList(const Physics::ShapeConfigurationList& shapeConfigList) + void BaseColliderComponent::SetShapeConfigurationList(const AzPhysics::ShapeColliderPairList& shapeConfigList) { if (GetEntity()->GetState() == AZ::Entity::State::Active) { @@ -115,7 +115,7 @@ namespace PhysX m_shapeConfigList = shapeConfigList; } - Physics::ShapeConfigurationList BaseColliderComponent::GetShapeConfigurations() + AzPhysics::ShapeColliderPairList BaseColliderComponent::GetShapeConfigurations() { return m_shapeConfigList; } @@ -319,7 +319,7 @@ namespace PhysX { AZ_Assert(IsMeshCollider(), "InitMeshCollider called for a non-mesh collider."); - const Physics::ShapeConfigurationPair& shapeConfigurationPair = *(m_shapeConfigList.begin()); + const AzPhysics::ShapeColliderPair& shapeConfigurationPair = *(m_shapeConfigList.begin()); const Physics::ColliderConfiguration& componentColliderConfiguration = *(shapeConfigurationPair.first.get()); const Physics::PhysicsAssetShapeConfiguration& physicsAssetConfiguration = *(static_cast(shapeConfigurationPair.second.get())); diff --git a/Gems/PhysX/Code/Source/BaseColliderComponent.h b/Gems/PhysX/Code/Source/BaseColliderComponent.h index b23e6f1f09..747ffe9d16 100644 --- a/Gems/PhysX/Code/Source/BaseColliderComponent.h +++ b/Gems/PhysX/Code/Source/BaseColliderComponent.h @@ -40,10 +40,10 @@ namespace PhysX BaseColliderComponent() = default; - void SetShapeConfigurationList(const Physics::ShapeConfigurationList& shapeConfigList); + void SetShapeConfigurationList(const AzPhysics::ShapeColliderPairList& shapeConfigList); // ColliderComponentRequestBus - Physics::ShapeConfigurationList GetShapeConfigurations() override; + AzPhysics::ShapeColliderPairList GetShapeConfigurations() override; AZStd::vector> GetShapes() override; // TransformNotificationsBus @@ -114,7 +114,7 @@ namespace PhysX virtual void UpdateScaleForShapeConfigs(); ShapeInfoCache m_shapeInfoCache; - Physics::ShapeConfigurationList m_shapeConfigList; + AzPhysics::ShapeColliderPairList m_shapeConfigList; private: bool InitShapes(); bool IsMeshCollider() const; diff --git a/Gems/PhysX/Code/Source/EditorColliderComponent.cpp b/Gems/PhysX/Code/Source/EditorColliderComponent.cpp index 18bb06ba74..e9b9c41da3 100644 --- a/Gems/PhysX/Code/Source/EditorColliderComponent.cpp +++ b/Gems/PhysX/Code/Source/EditorColliderComponent.cpp @@ -312,6 +312,26 @@ namespace PhysX } } + AZStd::shared_ptr EditorProxyShapeConfig::CloneCurrent() const + { + switch (m_shapeType) + { + case Physics::ShapeType::Sphere: + return AZStd::make_shared(m_sphere); + case Physics::ShapeType::Capsule: + return AZStd::make_shared(m_capsule); + case Physics::ShapeType::PhysicsAsset: + return AZStd::make_shared(m_physicsAsset.m_configuration); + case Physics::ShapeType::CookedMesh: + return AZStd::make_shared(m_cookedMesh); + default: + AZ_Warning("EditorProxyShapeConfig", false, "Unsupported shape type, defaulting to Box."); + [[fallthrough]]; + case Physics::ShapeType::Box: + return AZStd::make_shared(m_box); + } + } + bool EditorProxyShapeConfig::ShowingSubdivisionLevel() const { return (m_hasNonUniformScale && (IsCapsuleConfig() || IsSphereConfig() || IsAssetConfig())); @@ -590,10 +610,6 @@ namespace PhysX configuration.m_entityId = GetEntityId(); configuration.m_debugName = GetEntity()->GetName(); - // This configuration needs to be at the scope of the function to be added - // to m_colliderAndShapeData as a pointer. - Physics::ColliderConfiguration colliderConfig; - if (m_shapeConfiguration.IsAssetConfig()) { AZStd::vector> shapes; @@ -603,13 +619,15 @@ namespace PhysX } else { - colliderConfig = GetColliderConfigurationScaled(); - Physics::ShapeConfiguration& shapeConfig = m_shapeConfiguration.GetCurrent(); + AZStd::shared_ptr colliderConfig = AZStd::make_shared( + GetColliderConfigurationScaled()); + AZStd::shared_ptr shapeConfig = m_shapeConfiguration.CloneCurrent(); + if (IsNonUniformlyScaledPrimitive(m_shapeConfiguration)) { - auto convexConfig = Utils::CreateConvexFromPrimitive(GetColliderConfiguration(), shapeConfig, - m_shapeConfiguration.m_subdivisionLevel, shapeConfig.m_scale); - auto colliderConfigurationNoOffset = colliderConfig; + auto convexConfig = Utils::CreateConvexFromPrimitive(GetColliderConfiguration(), *(shapeConfig.get()), + m_shapeConfiguration.m_subdivisionLevel, shapeConfig->m_scale); + Physics::ColliderConfiguration colliderConfigurationNoOffset = *colliderConfig; colliderConfigurationNoOffset.m_rotation = AZ::Quaternion::CreateIdentity(); colliderConfigurationNoOffset.m_position = AZ::Vector3::CreateZero(); @@ -622,7 +640,7 @@ namespace PhysX } else { - configuration.m_colliderAndShapeData = AzPhysics::ShapeColliderPair(&colliderConfig, &shapeConfig); + configuration.m_colliderAndShapeData = AzPhysics::ShapeColliderPair(colliderConfig, shapeConfig); } } @@ -805,7 +823,7 @@ namespace PhysX return; } - Physics::ShapeConfigurationList shapeConfigList; + AzPhysics::ShapeColliderPairList shapeConfigList; Utils::GetColliderShapeConfigsFromAsset(physicsAssetConfiguration, m_configuration, m_hasNonUniformScale, m_shapeConfiguration.m_subdivisionLevel, shapeConfigList); @@ -879,7 +897,7 @@ namespace PhysX const Physics::PhysicsAssetShapeConfiguration& physicsAssetConfiguration = m_shapeConfiguration.m_physicsAsset.m_configuration; - Physics::ShapeConfigurationList shapeConfigList; + AzPhysics::ShapeColliderPairList shapeConfigList; Utils::GetColliderShapeConfigsFromAsset(physicsAssetConfiguration, m_configuration, m_hasNonUniformScale, m_shapeConfiguration.m_subdivisionLevel, shapeConfigList); diff --git a/Gems/PhysX/Code/Source/EditorColliderComponent.h b/Gems/PhysX/Code/Source/EditorColliderComponent.h index 1f9c00d4f5..818de62a04 100644 --- a/Gems/PhysX/Code/Source/EditorColliderComponent.h +++ b/Gems/PhysX/Code/Source/EditorColliderComponent.h @@ -86,6 +86,8 @@ namespace PhysX Physics::ShapeConfiguration& GetCurrent(); const Physics::ShapeConfiguration& GetCurrent() const; + AZStd::shared_ptr CloneCurrent() const; + bool ShowingSubdivisionLevel() const; AZ::u32 OnConfigurationChanged(); diff --git a/Gems/PhysX/Code/Source/EditorShapeColliderComponent.cpp b/Gems/PhysX/Code/Source/EditorShapeColliderComponent.cpp index 82a38a04c2..379afc3f2d 100644 --- a/Gems/PhysX/Code/Source/EditorShapeColliderComponent.cpp +++ b/Gems/PhysX/Code/Source/EditorShapeColliderComponent.cpp @@ -226,7 +226,7 @@ namespace PhysX void EditorShapeColliderComponent::BuildGameEntity(AZ::Entity* gameEntity) { auto* shapeColliderComponent = gameEntity->CreateComponent(); - Physics::ShapeConfigurationList shapeConfigurationList; + AzPhysics::ShapeColliderPairList shapeConfigurationList; shapeConfigurationList.reserve(m_shapeConfigs.size()); for (const auto& shapeConfig : m_shapeConfigs) { @@ -257,11 +257,12 @@ namespace PhysX configuration.m_entityId = GetEntityId(); configuration.m_debugName = GetEntity()->GetName(); - AZStd::vector colliderShapePairs; + AzPhysics::ShapeColliderPairList colliderShapePairs; colliderShapePairs.reserve(m_shapeConfigs.size()); for (const auto& shapeConfig : m_shapeConfigs) { - colliderShapePairs.emplace_back(&m_colliderConfig, shapeConfig.get()); + colliderShapePairs.emplace_back( + AZStd::make_shared(m_colliderConfig), shapeConfig); } configuration.m_colliderAndShapeData = colliderShapePairs; diff --git a/Gems/PhysX/Code/Source/Utils.cpp b/Gems/PhysX/Code/Source/Utils.cpp index 767f386b6f..a85426d532 100644 --- a/Gems/PhysX/Code/Source/Utils.cpp +++ b/Gems/PhysX/Code/Source/Utils.cpp @@ -770,7 +770,7 @@ namespace PhysX return worldPosAabb; } - Physics::ShapeConfigurationList colliderShapes; + AzPhysics::ShapeColliderPairList colliderShapes; GetColliderShapeConfigsFromAsset(physicsAssetConfig, colliderConfiguration, hasNonUniformScale, @@ -812,7 +812,7 @@ namespace PhysX void GetColliderShapeConfigsFromAsset(const Physics::PhysicsAssetShapeConfiguration& assetConfiguration, const Physics::ColliderConfiguration& originalColliderConfiguration, bool hasNonUniformScale, - AZ::u8 subdivisionLevel, Physics::ShapeConfigurationList& resultingColliderShapes) + AZ::u8 subdivisionLevel, AzPhysics::ShapeColliderPairList& resultingColliderShapes) { if (!assetConfiguration.m_asset.IsReady()) { @@ -896,13 +896,13 @@ namespace PhysX const Physics::ColliderConfiguration& originalColliderConfiguration, bool hasNonUniformScale, AZ::u8 subdivisionLevel, AZStd::vector>& resultingShapes) { - Physics::ShapeConfigurationList resultingColliderShapeConfigs; + AzPhysics::ShapeColliderPairList resultingColliderShapeConfigs; GetColliderShapeConfigsFromAsset(assetConfiguration, originalColliderConfiguration, hasNonUniformScale, subdivisionLevel, resultingColliderShapeConfigs); resultingShapes.reserve(resultingShapes.size() + resultingColliderShapeConfigs.size()); - for (const Physics::ShapeConfigurationPair& shapeConfigPair : resultingColliderShapeConfigs) + for (const AzPhysics::ShapeColliderPair& shapeConfigPair : resultingColliderShapeConfigs) { // Scale the collider offset shapeConfigPair.first->m_position *= shapeConfigPair.second->m_scale; diff --git a/Gems/PhysX/Code/Source/Utils.h b/Gems/PhysX/Code/Source/Utils.h index 2a8329b267..2885e86a09 100644 --- a/Gems/PhysX/Code/Source/Utils.h +++ b/Gems/PhysX/Code/Source/Utils.h @@ -179,7 +179,7 @@ namespace PhysX void GetColliderShapeConfigsFromAsset(const Physics::PhysicsAssetShapeConfiguration& assetConfiguration, const Physics::ColliderConfiguration& originalColliderConfiguration, - bool hasNonUniformScale, AZ::u8 subdivisionLevel, Physics::ShapeConfigurationList& resultingColliderShapes); + bool hasNonUniformScale, AZ::u8 subdivisionLevel, AzPhysics::ShapeColliderPairList& resultingColliderShapes); //! Gets the scale from the entity's Transform component. AZ::Vector3 GetTransformScale(AZ::EntityId entityId); diff --git a/Gems/PhysX/Code/Tests/Benchmarks/PhysXBenchmarkWashingMachine.cpp b/Gems/PhysX/Code/Tests/Benchmarks/PhysXBenchmarkWashingMachine.cpp index adc910e8c8..24fbfd742e 100644 --- a/Gems/PhysX/Code/Tests/Benchmarks/PhysXBenchmarkWashingMachine.cpp +++ b/Gems/PhysX/Code/Tests/Benchmarks/PhysXBenchmarkWashingMachine.cpp @@ -13,6 +13,7 @@ #ifdef HAVE_BENCHMARK #include +#include #include #include #include @@ -96,9 +97,12 @@ namespace PhysX::Benchmarks config.m_position.SetY((cylinderRadius + halfCylinderWallThickness) * std::sin(AZ::Constants::TwoPi * i / NumCylinderSide) + position.GetY()); config.m_position.SetZ(z); config.m_orientation = AZ::Quaternion::CreateRotationZ(AZ::Constants::HalfPi + (cylinderTheta * i)); - Physics::ColliderConfiguration colliderConfig; - Physics::BoxShapeConfiguration shapeConfiguration(AZ::Vector3(cylinderRadius, cylinderWallThickness, cylinderHeight)); - config.m_colliderAndShapeData = AzPhysics::ShapeColliderPair(&colliderConfig, &shapeConfiguration); + + auto shapeConfiguration = AZStd::make_shared( + AZ::Vector3(cylinderRadius, cylinderWallThickness, cylinderHeight)); + + config.m_colliderAndShapeData = AzPhysics::ShapeColliderPair( + AZStd::make_shared(), shapeConfiguration); m_cylinder[i] = scene->AddSimulatedBody(&config); } @@ -114,9 +118,9 @@ namespace PhysX::Benchmarks bladeRigidBodyConfig.m_position = position; bladeRigidBodyConfig.m_position.SetZ(position.GetZ() + (bladeHeight / 2.0f)); bladeRigidBodyConfig.m_orientation = AZ::Quaternion::CreateRotationZ(0.0f); - Physics::ColliderConfiguration bladeColliderConfig; - Physics::BoxShapeConfiguration bladeShapeConfiguration(AZ::Vector3(bladeLength, 1.0f, bladeHeight)); - bladeRigidBodyConfig.m_colliderAndShapeData = AZStd::make_pair(&bladeColliderConfig, &bladeShapeConfiguration); + auto bladeShapeConfiguration = AZStd::make_shared(AZ::Vector3(bladeLength, 1.0f, bladeHeight)); + bladeRigidBodyConfig.m_colliderAndShapeData = AzPhysics::ShapeColliderPair( + AZStd::make_shared(), bladeShapeConfiguration); m_blade = scene->AddSimulatedBody(&bladeRigidBodyConfig); } diff --git a/Gems/PhysX/Code/Tests/Benchmarks/PhysXBenchmarksUtilities.cpp b/Gems/PhysX/Code/Tests/Benchmarks/PhysXBenchmarksUtilities.cpp index 1684dd4c7f..bc48b5fb92 100644 --- a/Gems/PhysX/Code/Tests/Benchmarks/PhysXBenchmarksUtilities.cpp +++ b/Gems/PhysX/Code/Tests/Benchmarks/PhysXBenchmarksUtilities.cpp @@ -13,6 +13,7 @@ #include #include +#include #include #include #include @@ -34,9 +35,9 @@ namespace PhysX::Benchmarks AzPhysics::RigidBodyConfiguration rigidBodyConfig; rigidBodyConfig.m_ccdEnabled = enableCCD; - Physics::ColliderConfiguration rigidBodyColliderConfig; + auto rigidBodyColliderConfig = AZStd::make_shared(); - Physics::BoxShapeConfiguration defaultShapeConfiguration = Physics::BoxShapeConfiguration(AZ::Vector3::CreateOne()); + auto defaultShapeConfiguration = AZStd::make_shared(AZ::Vector3::CreateOne()); for (int i = 0; i < numRigidBodies; i++) { //call the optional function pointers, otherwise assign a default @@ -57,16 +58,16 @@ namespace PhysX::Benchmarks rigidBodyConfig.m_orientation = (*genSpawnOriFuncPtr)(i); } - Physics::ShapeConfiguration* shapeConfig = nullptr; + AZStd::shared_ptr shapeConfig = nullptr; if (genColliderFuncPtr != nullptr) { shapeConfig = (*genColliderFuncPtr)(i); } if (shapeConfig == nullptr) { - shapeConfig = &defaultShapeConfiguration; + shapeConfig = defaultShapeConfiguration; } - rigidBodyConfig.m_colliderAndShapeData = AZStd::make_pair(&rigidBodyColliderConfig, shapeConfig); + rigidBodyConfig.m_colliderAndShapeData = AzPhysics::ShapeColliderPair(rigidBodyColliderConfig, shapeConfig); AzPhysics::SimulatedBodyHandle simBodyHandle = scene->AddSimulatedBody(&rigidBodyConfig); rigidBodies.push_back(simBodyHandle); diff --git a/Gems/PhysX/Code/Tests/Benchmarks/PhysXBenchmarksUtilities.h b/Gems/PhysX/Code/Tests/Benchmarks/PhysXBenchmarksUtilities.h index ab9a0ba2ab..ab9bd58148 100644 --- a/Gems/PhysX/Code/Tests/Benchmarks/PhysXBenchmarksUtilities.h +++ b/Gems/PhysX/Code/Tests/Benchmarks/PhysXBenchmarksUtilities.h @@ -40,7 +40,7 @@ namespace PhysX::Benchmarks namespace Utils { //! Function pointer to allow Shape configuration customization rigid bodies created with Utils::CreateRigidBodies. int param is the id of the rigid body being created (values 0-N, where N=number requested to be created) - using GenerateColliderFuncPtr = AZStd::function; + using GenerateColliderFuncPtr = AZStd::function(int)>; //! Function pointer to allow spawn position customization rigid bodies created with Utils::CreateRigidBodies. int param is the id of the rigid body being created (values 0-N, where N=number requested to be created) using GenerateSpawnPositionFuncPtr = AZStd::function; //! Function pointer to allow spawn orientation customization rigid bodies created with Utils::CreateRigidBodies. int param is the id of the rigid body being created (values 0-N, where N=number requested to be created) diff --git a/Gems/PhysX/Code/Tests/Benchmarks/PhysXJointBenchmarks.cpp b/Gems/PhysX/Code/Tests/Benchmarks/PhysXJointBenchmarks.cpp index 7d650d6bef..24b51f0085 100644 --- a/Gems/PhysX/Code/Tests/Benchmarks/PhysXJointBenchmarks.cpp +++ b/Gems/PhysX/Code/Tests/Benchmarks/PhysXJointBenchmarks.cpp @@ -136,9 +136,8 @@ namespace PhysX::Benchmarks { AZStd::vector joints; - Physics::ColliderConfiguration colliderConfig; - Physics::SphereShapeConfiguration shapeConfiguration = Physics::SphereShapeConfiguration(JointConstants::CreateJointDefaults::ColliderRadius); - AzPhysics::ShapeColliderPair shapeColliderConfig(&colliderConfig, &shapeConfiguration); + auto shapeConfiguration = AZStd::make_shared(JointConstants::CreateJointDefaults::ColliderRadius); + AzPhysics::ShapeColliderPair shapeColliderConfig(AZStd::make_shared(), shapeConfiguration); for (int i = 0; i < numJoints; i++) { JointGroup newJoint; @@ -340,13 +339,13 @@ namespace PhysX::Benchmarks const int numSegments = aznumeric_cast(state.range(0)); //create the collider shape config to use on the whole snake - Physics::SphereShapeConfiguration snakePartShapeConfiguration = Physics::SphereShapeConfiguration(JointConstants::CreateJointDefaults::ColliderRadius); - Physics::ColliderConfiguration snakeHeadcolliderConfig; + auto snakePartShapeConfiguration = AZStd::make_shared(JointConstants::CreateJointDefaults::ColliderRadius); //create the had of the snake this is the only static part. AzPhysics::StaticRigidBodyConfiguration snakeHeadBodyConfig; snakeHeadBodyConfig.m_position = AZ::Vector3::CreateZero(); - snakeHeadBodyConfig.m_colliderAndShapeData = AzPhysics::ShapeColliderPair(&snakeHeadcolliderConfig, &snakePartShapeConfiguration); + snakeHeadBodyConfig.m_colliderAndShapeData = AzPhysics::ShapeColliderPair( + AZStd::make_shared(), snakePartShapeConfiguration); AzPhysics::SimulatedBody* snakeHead = nullptr; if (auto* sceneInterface = AZ::Interface::Get()) @@ -358,7 +357,7 @@ namespace PhysX::Benchmarks //create the body Utils::GenerateColliderFuncPtr colliderGenerator = [&snakePartShapeConfiguration]([[maybe_unused]] int idx) -> auto { - return &snakePartShapeConfiguration; + return snakePartShapeConfiguration; }; Utils::GenerateSpawnPositionFuncPtr posGenerator = [](int idx) -> auto { diff --git a/Gems/PhysX/Code/Tests/Benchmarks/PhysXRigidBodyBenchmarks.cpp b/Gems/PhysX/Code/Tests/Benchmarks/PhysXRigidBodyBenchmarks.cpp index e295916554..7ad3061910 100644 --- a/Gems/PhysX/Code/Tests/Benchmarks/PhysXRigidBodyBenchmarks.cpp +++ b/Gems/PhysX/Code/Tests/Benchmarks/PhysXRigidBodyBenchmarks.cpp @@ -203,10 +203,10 @@ namespace PhysX::Benchmarks return AZ::Vector3(x, y, z); }; - Physics::BoxShapeConfiguration boxShapeConfiguration = Physics::BoxShapeConfiguration(AZ::Vector3(RigidBodyConstants::RigidBodys::BoxSize)); - Utils::GenerateColliderFuncPtr colliderGenerator = [&boxShapeConfiguration]([[maybe_unused]] int idx) -> Physics::ShapeConfiguration* + auto boxShapeConfiguration = AZStd::make_shared(AZ::Vector3(RigidBodyConstants::RigidBodys::BoxSize)); + Utils::GenerateColliderFuncPtr colliderGenerator = [&boxShapeConfiguration]([[maybe_unused]] int idx) { - return &boxShapeConfiguration; + return boxShapeConfiguration; }; //spawn the rigid bodies AzPhysics::SimulatedBodyHandleList rigidBodies = Utils::CreateRigidBodies(numRigidBodies, m_defaultScene, @@ -275,10 +275,10 @@ namespace PhysX::Benchmarks Utils::GenerateMassFuncPtr massGenerator = [&rand]([[maybe_unused]] int idx) -> float { return rand.GetRandomFloat() * 25.0f + 5.0f; }; - Physics::BoxShapeConfiguration boxShapeConfiguration = Physics::BoxShapeConfiguration(AZ::Vector3(RigidBodyConstants::RigidBodys::BoxSize)); - Utils::GenerateColliderFuncPtr colliderGenerator = [&boxShapeConfiguration]([[maybe_unused]] int idx) -> Physics::ShapeConfiguration* + auto boxShapeConfiguration = AZStd::make_shared(AZ::Vector3(RigidBodyConstants::RigidBodys::BoxSize)); + Utils::GenerateColliderFuncPtr colliderGenerator = [&boxShapeConfiguration]([[maybe_unused]] int idx) { - return &boxShapeConfiguration; + return boxShapeConfiguration; }; //spawn the rigid bodies AzPhysics::SimulatedBodyHandleList rigidBodies = Utils::CreateRigidBodies(numRigidBodies, m_defaultScene, @@ -408,10 +408,10 @@ namespace PhysX::Benchmarks Utils::GenerateEntityIdFuncPtr entityIdGenerator = [&rand](int idx) -> AZ::EntityId { return AZ::EntityId(static_cast(idx) + RigidBodyConstants::RigidBodys::RigidBodyEntityIdStart); }; - Physics::BoxShapeConfiguration boxShapeConfiguration = Physics::BoxShapeConfiguration(AZ::Vector3(RigidBodyConstants::RigidBodys::BoxSize)); - Utils::GenerateColliderFuncPtr colliderGenerator = [&boxShapeConfiguration]([[maybe_unused]] int idx) -> Physics::ShapeConfiguration* + auto boxShapeConfiguration = AZStd::make_shared(AZ::Vector3(RigidBodyConstants::RigidBodys::BoxSize)); + Utils::GenerateColliderFuncPtr colliderGenerator = [&boxShapeConfiguration]([[maybe_unused]] int idx) { - return &boxShapeConfiguration; + return boxShapeConfiguration; }; //spawn the rigid bodies AzPhysics::SimulatedBodyHandleList rigidBodies = Utils::CreateRigidBodies(numRigidBodies, m_defaultScene, diff --git a/Gems/PhysX/Code/Tests/ColliderScalingTests.cpp b/Gems/PhysX/Code/Tests/ColliderScalingTests.cpp index ae65fcfc23..25747a55f6 100644 --- a/Gems/PhysX/Code/Tests/ColliderScalingTests.cpp +++ b/Gems/PhysX/Code/Tests/ColliderScalingTests.cpp @@ -30,7 +30,7 @@ namespace PhysXEditorTests PhysX::BaseColliderComponent* colliderComponent = gameEntity->FindComponent(); ASSERT_TRUE(colliderComponent != nullptr); - Physics::ShapeConfigurationList shapeConfigList = colliderComponent->GetShapeConfigurations(); + AzPhysics::ShapeColliderPairList shapeConfigList = colliderComponent->GetShapeConfigurations(); EXPECT_EQ(shapeConfigList.size(), 1); for (const auto& shapeConfigPair : shapeConfigList) diff --git a/Gems/PhysX/Code/Tests/PhysXGenericTest.cpp b/Gems/PhysX/Code/Tests/PhysXGenericTest.cpp index 45b1e469f4..7648871380 100644 --- a/Gems/PhysX/Code/Tests/PhysXGenericTest.cpp +++ b/Gems/PhysX/Code/Tests/PhysXGenericTest.cpp @@ -325,9 +325,10 @@ namespace PhysX // Box should start asleep AzPhysics::RigidBodyConfiguration config; config.m_startAsleep = true; - Physics::ColliderConfiguration colliderConfig; - Physics::SphereShapeConfiguration shapeConfiguration; - config.m_colliderAndShapeData = AZStd::make_pair(&colliderConfig, &shapeConfiguration); + config.m_colliderAndShapeData = AzPhysics::ShapeColliderPair( + AZStd::make_shared(), + AZStd::make_shared() + ); AzPhysics::SimulatedBodyHandle rigidBodyHandle = sceneInterface->AddSimulatedBody(sceneHandle, &config); TestUtils::UpdateScene(sceneHandle, 1.0f / 60.0f, 100); diff --git a/Gems/PhysX/Code/Tests/PhysXGenericTestFixture.cpp b/Gems/PhysX/Code/Tests/PhysXGenericTestFixture.cpp index 5240cc06e6..9955cbbd97 100644 --- a/Gems/PhysX/Code/Tests/PhysXGenericTestFixture.cpp +++ b/Gems/PhysX/Code/Tests/PhysXGenericTestFixture.cpp @@ -104,12 +104,12 @@ namespace PhysX auto colliderConfig = AZStd::make_shared(); colliderConfig->m_collisionLayer = config.m_layer; - Physics::ShapeConfigurationList shapeconfigurationList; + AzPhysics::ShapeColliderPairList shapeconfigurationList; struct Visitor { AZStd::shared_ptr& colliderConfig; - Physics::ShapeConfigurationList& shapeconfigurationList; + AzPhysics::ShapeColliderPairList& shapeconfigurationList; void operator()(const MultiShapeConfig::ShapeList::ShapeData::Box& box) const { diff --git a/Gems/PhysX/Code/Tests/PhysXSceneTests.cpp b/Gems/PhysX/Code/Tests/PhysXSceneTests.cpp index 2a38e1b867..9514f6908e 100644 --- a/Gems/PhysX/Code/Tests/PhysXSceneTests.cpp +++ b/Gems/PhysX/Code/Tests/PhysXSceneTests.cpp @@ -83,10 +83,10 @@ namespace PhysX auto* sceneInterface = AZ::Interface::Get(); //add a static rigid body - Physics::ColliderConfiguration colliderConfig; - Physics::BoxShapeConfiguration shapeConfiguration(AZ::Vector3(1.0f, 1.0f, 1.0f)); AzPhysics::StaticRigidBodyConfiguration config; - config.m_colliderAndShapeData = AZStd::make_pair(&colliderConfig, &shapeConfiguration); + config.m_colliderAndShapeData = AzPhysics::ShapeColliderPair( + AZStd::make_shared(), + AZStd::make_shared(AZ::Vector3::CreateOne())); AzPhysics::SimulatedBodyHandle simBodyHandle = sceneInterface->AddSimulatedBody(m_testSceneHandle, &config); EXPECT_FALSE(simBodyHandle == AzPhysics::InvalidSimulatedBodyHandle); } @@ -104,14 +104,15 @@ namespace PhysX EXPECT_TRUE(emptyBodies.empty()); //add some rigid bodies - Physics::ColliderConfiguration colliderConfig; - Physics::BoxShapeConfiguration shapeConfiguration(AZ::Vector3(1.0f, 1.0f, 1.0f)); + AzPhysics::ShapeColliderPair shapeColliderData( + AZStd::make_shared(), + AZStd::make_shared(AZ::Vector3::CreateOne())); constexpr const int numberOfBodies = 100; for (int i = 0; i < numberOfBodies; i++) { const float xpos = 2.0f * static_cast(i); AzPhysics::RigidBodyConfiguration* config = aznew AzPhysics::RigidBodyConfiguration(); - config->m_colliderAndShapeData = AZStd::make_pair(&colliderConfig, &shapeConfiguration); + config->m_colliderAndShapeData = shapeColliderData; config->m_position = AZ::Vector3::CreateAxisX(xpos); configs.emplace_back(config); } @@ -150,15 +151,16 @@ namespace PhysX AzPhysics::SimulatedBodyConfigurationList configs; //add some rigid bodies - Physics::ColliderConfiguration colliderConfig; - Physics::BoxShapeConfiguration shapeConfiguration(AZ::Vector3(1.0f, 1.0f, 1.0f)); - + AzPhysics::ShapeColliderPair shapeColliderData( + AZStd::make_shared(), + AZStd::make_shared(AZ::Vector3::CreateOne())); + constexpr const int numberOfBodies = 100; for (int i = 0; i < numberOfBodies; i++) { const float xpos = 2.0f * static_cast(i); AzPhysics::RigidBodyConfiguration* config = aznew AzPhysics::RigidBodyConfiguration(); - config->m_colliderAndShapeData = AZStd::make_pair(&colliderConfig, &shapeConfiguration); + config->m_colliderAndShapeData = shapeColliderData; config->m_position = AZ::Vector3::CreateAxisX(xpos); configs.emplace_back(config); } @@ -205,10 +207,11 @@ namespace PhysX auto* sceneInterface = AZ::Interface::Get(); //add a simulated body - Physics::ColliderConfiguration colliderConfig; - Physics::BoxShapeConfiguration shapeConfiguration(AZ::Vector3(1.0f, 1.0f, 1.0f)); + AzPhysics::ShapeColliderPair shapeColliderData( + AZStd::make_shared(), + AZStd::make_shared(AZ::Vector3::CreateOne())); AzPhysics::StaticRigidBodyConfiguration config; - config.m_colliderAndShapeData = AZStd::make_pair(&colliderConfig, &shapeConfiguration); + config.m_colliderAndShapeData = shapeColliderData; AzPhysics::SimulatedBodyHandle simBodyHandle = sceneInterface->AddSimulatedBody(m_testSceneHandle, &config); //remove the body @@ -223,10 +226,11 @@ namespace PhysX auto* sceneInterface = AZ::Interface::Get(); //add a few simulated body - Physics::ColliderConfiguration colliderConfig; - Physics::BoxShapeConfiguration shapeConfiguration(AZ::Vector3(1.0f, 1.0f, 1.0f)); + AzPhysics::ShapeColliderPair shapeColliderData( + AZStd::make_shared(), + AZStd::make_shared(AZ::Vector3::CreateOne())); AzPhysics::StaticRigidBodyConfiguration config; - config.m_colliderAndShapeData = AZStd::make_pair(&colliderConfig, &shapeConfiguration); + config.m_colliderAndShapeData = shapeColliderData; AzPhysics::SimulatedBodyHandleList simBodyHandles; constexpr const int numBodies = 10; @@ -281,10 +285,11 @@ namespace PhysX sceneInterface->RegisterSimulationBodyRemovedHandler(m_testSceneHandle, removedEvent); //add a simulated body - Physics::ColliderConfiguration colliderConfig; - Physics::BoxShapeConfiguration shapeConfiguration(AZ::Vector3(1.0f, 1.0f, 1.0f)); + AzPhysics::ShapeColliderPair shapeColliderData( + AZStd::make_shared(), + AZStd::make_shared(AZ::Vector3::CreateOne())); AzPhysics::StaticRigidBodyConfiguration config; - config.m_colliderAndShapeData = AZStd::make_pair(&colliderConfig, &shapeConfiguration); + config.m_colliderAndShapeData = shapeColliderData; AzPhysics::SimulatedBodyHandle simBodyHandle = sceneInterface->AddSimulatedBody(m_testSceneHandle, &config); EXPECT_TRUE(addTriggered); @@ -552,17 +557,18 @@ namespace PhysX auto* sceneInterface = AZ::Interface::Get(); // setup shape config - Physics::ColliderConfiguration colliderConfig; - Physics::BoxShapeConfiguration shapeConfiguration(AZ::Vector3(1.0f, 1.0f, 1.0f)); + AzPhysics::ShapeColliderPair shapeColliderData( + AZStd::make_shared(), + AZStd::make_shared(AZ::Vector3::CreateOne())); // add a static simulated body - this is not expected to be reported as an active actor AzPhysics::StaticRigidBodyConfiguration staticConfig; - staticConfig.m_colliderAndShapeData = AZStd::make_pair(&colliderConfig, &shapeConfiguration); + staticConfig.m_colliderAndShapeData = shapeColliderData; AzPhysics::SimulatedBodyHandle staticSphereHandle = sceneInterface->AddSimulatedBody(m_testSceneHandle, &staticConfig); // add a rigid body - this is expect to be reported as an active actor AzPhysics::RigidBodyConfiguration rigidConfig; - rigidConfig.m_colliderAndShapeData = AZStd::make_pair(&colliderConfig, &shapeConfiguration); + rigidConfig.m_colliderAndShapeData = shapeColliderData; AzPhysics::SimulatedBodyHandle rigidSphereHandle = sceneInterface->AddSimulatedBody(m_testSceneHandle, &rigidConfig); // create + register the active handler diff --git a/Gems/PhysX/Code/Tests/PhysXSpecificTest.cpp b/Gems/PhysX/Code/Tests/PhysXSpecificTest.cpp index 74c88d0b46..98ee82f11a 100644 --- a/Gems/PhysX/Code/Tests/PhysXSpecificTest.cpp +++ b/Gems/PhysX/Code/Tests/PhysXSpecificTest.cpp @@ -539,14 +539,14 @@ namespace PhysX TEST_F(PhysXSpecificTest, RigidBody_CenterOfMassOffsetComputed) { AZ::Vector3 halfExtents(1.0f, 2.0f, 3.0f); - Physics::BoxShapeConfiguration shapeConfig(halfExtents * 2.0f); - Physics::ColliderConfiguration colliderConfig; - colliderConfig.m_rotation = AZ::Quaternion::CreateRotationX(AZ::Constants::HalfPi); + auto shapeConfig = AZStd::make_shared(halfExtents * 2.0f); + auto colliderConfig = AZStd::make_shared(); + colliderConfig->m_rotation = AZ::Quaternion::CreateRotationX(AZ::Constants::HalfPi); AzPhysics::RigidBodyConfiguration rigidBodyConfiguration; rigidBodyConfiguration.m_computeCenterOfMass = true; rigidBodyConfiguration.m_computeInertiaTensor = true; - rigidBodyConfiguration.m_colliderAndShapeData = AZStd::make_pair(&colliderConfig, &shapeConfig); + rigidBodyConfiguration.m_colliderAndShapeData = AzPhysics::ShapeColliderPair(colliderConfig, shapeConfig); AzPhysics::RigidBody* rigidBody = nullptr; if (auto* sceneInterface = AZ::Interface::Get()) { @@ -562,15 +562,15 @@ namespace PhysX TEST_F(PhysXSpecificTest, RigidBody_CenterOfMassOffsetSpecified) { AZ::Vector3 halfExtents(1.0f, 2.0f, 3.0f); - Physics::BoxShapeConfiguration shapeConfig(halfExtents * 2.0f); - Physics::ColliderConfiguration colliderConfig; - colliderConfig.m_rotation = AZ::Quaternion::CreateRotationX(AZ::Constants::HalfPi); + auto shapeConfig = AZStd::make_shared(halfExtents * 2.0f); + auto colliderConfig = AZStd::make_shared(); + colliderConfig->m_rotation = AZ::Quaternion::CreateRotationX(AZ::Constants::HalfPi); AzPhysics::RigidBodyConfiguration rigidBodyConfiguration; rigidBodyConfiguration.m_computeCenterOfMass = false; rigidBodyConfiguration.m_centerOfMassOffset = AZ::Vector3::CreateOne(); rigidBodyConfiguration.m_computeInertiaTensor = true; - rigidBodyConfiguration.m_colliderAndShapeData = AZStd::make_pair(&colliderConfig, &shapeConfig); + rigidBodyConfiguration.m_colliderAndShapeData = AzPhysics::ShapeColliderPair(colliderConfig, shapeConfig); AzPhysics::RigidBody* rigidBody = nullptr; if (auto* sceneInterface = AZ::Interface::Get()) @@ -1113,15 +1113,15 @@ namespace PhysX auto CreateBoxRigidBody = [this](const AZ::Vector3& position, bool simulatedFlag, bool triggerFlag) -> AzPhysics::RigidBody* { - Physics::ColliderConfiguration colliderConfig; - colliderConfig.m_isSimulated = simulatedFlag; - colliderConfig.m_isTrigger = triggerFlag; - Physics::BoxShapeConfiguration shapeConfig; + auto colliderConfig = AZStd::make_shared(); + colliderConfig->m_isSimulated = simulatedFlag; + colliderConfig->m_isTrigger = triggerFlag; AzPhysics::RigidBodyConfiguration rigidBodyConfig; rigidBodyConfig.m_entityId = AZ::EntityId(0); // Set entity ID to avoid warnings in OnTriggerEnter rigidBodyConfig.m_position = position; - rigidBodyConfig.m_colliderAndShapeData = AZStd::make_pair(&colliderConfig, &shapeConfig); + rigidBodyConfig.m_colliderAndShapeData = AzPhysics::ShapeColliderPair( + colliderConfig, AZStd::make_shared()); if (auto* sceneInterface = AZ::Interface::Get()) { diff --git a/Gems/PhysX/Code/Tests/PhysXTestCommon.cpp b/Gems/PhysX/Code/Tests/PhysXTestCommon.cpp index 93ad7704c9..b4e5410981 100644 --- a/Gems/PhysX/Code/Tests/PhysXTestCommon.cpp +++ b/Gems/PhysX/Code/Tests/PhysXTestCommon.cpp @@ -107,7 +107,7 @@ namespace PhysX auto shapeConfig = AZStd::make_shared(radius); auto shpereColliderComponent = entity->CreateComponent(); - shpereColliderComponent->SetShapeConfigurationList({ AZStd::make_pair(colliderConfig, shapeConfig) }); + shpereColliderComponent->SetShapeConfigurationList({ AzPhysics::ShapeColliderPair(colliderConfig, shapeConfig) }); AzPhysics::RigidBodyConfiguration rigidBodyConfig; rigidBodyConfig.m_computeMass = false; @@ -136,7 +136,7 @@ namespace PhysX colliderConfig->m_collisionLayer = layer; auto shapeConfig = AZStd::make_shared(radius); auto sphereColliderComponent = entity->CreateComponent(); - sphereColliderComponent->SetShapeConfigurationList({ AZStd::make_pair(colliderConfig, shapeConfig) }); + sphereColliderComponent->SetShapeConfigurationList({ AzPhysics::ShapeColliderPair(colliderConfig, shapeConfig) }); entity->CreateComponent(sceneHandle); @@ -171,7 +171,7 @@ namespace PhysX auto boxColliderComponent = entity->CreateComponent(); auto colliderConfig = AZStd::make_shared(); colliderConfig->m_collisionLayer = layer; - boxColliderComponent->SetShapeConfigurationList({ AZStd::make_pair(colliderConfig, shapeConfig) }); + boxColliderComponent->SetShapeConfigurationList({ AzPhysics::ShapeColliderPair(colliderConfig, shapeConfig) }); entity->CreateComponent(sceneHandle); entity->Activate(); return entity; @@ -195,7 +195,7 @@ namespace PhysX colliderConfig->m_collisionLayer = layer; auto shapeConfig = AZStd::make_shared(height, radius); auto capsuleColliderComponent = entity->CreateComponent(); - capsuleColliderComponent->SetShapeConfigurationList({ AZStd::make_pair(colliderConfig, shapeConfig) }); + capsuleColliderComponent->SetShapeConfigurationList({ AzPhysics::ShapeColliderPair(colliderConfig, shapeConfig) }); AzPhysics::RigidBodyConfiguration rigidBodyConfig; rigidBodyConfig.m_computeMass = false; @@ -223,7 +223,7 @@ namespace PhysX colliderConfig->m_collisionLayer = layer; auto shapeConfig = AZStd::make_shared(height, radius); auto capsuleColliderComponent = entity->CreateComponent(); - capsuleColliderComponent->SetShapeConfigurationList({ AZStd::make_pair(colliderConfig, shapeConfig) }); + capsuleColliderComponent->SetShapeConfigurationList({ AzPhysics::ShapeColliderPair(colliderConfig, shapeConfig) }); entity->CreateComponent(sceneHandle); @@ -244,14 +244,13 @@ namespace PhysX AZ_Assert(cookingResult, "Failed to cook the cube mesh."); // Setup shape & collider configurations - Physics::CookedMeshShapeConfiguration shapeConfig; - shapeConfig.SetCookedMeshData(cookedData.data(), cookedData.size(), + auto shapeConfig = AZStd::make_shared(); + shapeConfig->SetCookedMeshData(cookedData.data(), cookedData.size(), Physics::CookedMeshShapeConfiguration::MeshType::TriangleMesh); - Physics::ColliderConfiguration colliderConfig; - AzPhysics::StaticRigidBodyConfiguration staticRigidBodyConfiguration; - staticRigidBodyConfiguration.m_colliderAndShapeData = AzPhysics::ShapeColliderPair(&colliderConfig, &shapeConfig); + staticRigidBodyConfiguration.m_colliderAndShapeData = AzPhysics::ShapeColliderPair( + AZStd::make_shared(), shapeConfig); if (auto* sceneInterface = AZ::Interface::Get()) { @@ -290,7 +289,7 @@ namespace PhysX auto shapeConfig = AZStd::make_shared(dimensions); auto boxColliderComponent = entity->CreateComponent(); - boxColliderComponent->SetShapeConfigurationList({ AZStd::make_pair(colliderConfig, shapeConfig) }); + boxColliderComponent->SetShapeConfigurationList({ AzPhysics::ShapeColliderPair(colliderConfig, shapeConfig) }); AzPhysics::RigidBodyConfiguration rigidBodyConfig; rigidBodyConfig.m_computeMass = false; @@ -307,7 +306,7 @@ namespace PhysX AZ::TransformConfig transformConfig; transformConfig.m_worldTransform = AZ::Transform::CreateTranslation(position); entity->CreateComponent()->SetConfiguration(transformConfig); - Physics::ShapeConfigurationList shapeConfigList = { AZStd::make_pair( + AzPhysics::ShapeColliderPairList shapeConfigList = { AzPhysics::ShapeColliderPair( AZStd::make_shared(), AZStd::make_shared()) }; auto boxCollider = entity->CreateComponent(); @@ -371,10 +370,10 @@ namespace PhysX AzPhysics::StaticRigidBody* AddStaticFloorToScene(AzPhysics::SceneHandle sceneHandle, const AZ::Transform& transform) { - Physics::ColliderConfiguration colliderConfig; - Physics::BoxShapeConfiguration shapeConfiguration(AZ::Vector3(20.0f, 20.0f, 1.0f)); AzPhysics::StaticRigidBodyConfiguration staticBodyConfiguration; - staticBodyConfiguration.m_colliderAndShapeData = AZStd::make_pair(&colliderConfig, &shapeConfiguration); + staticBodyConfiguration.m_colliderAndShapeData = AzPhysics::ShapeColliderPair( + AZStd::make_shared(), + AZStd::make_shared(AZ::Vector3(20.0f, 20.0f, 1.0f))); if (auto* sceneInterface = AZ::Interface::Get()) { AzPhysics::SimulatedBodyHandle simBodyHandle = sceneInterface->AddSimulatedBody(sceneHandle, &staticBodyConfiguration); @@ -410,10 +409,10 @@ namespace PhysX AzPhysics::SimulatedBodyHandle AddSphereToScene(AzPhysics::SceneHandle sceneHandle, const AZ::Vector3& position, const float radius /*= 0.5f*/, const AzPhysics::CollisionLayer& layer /*= AzPhysics::CollisionLayer::Default*/) { - Physics::ColliderConfiguration colliderConfig; - colliderConfig.m_collisionLayer = layer; - Physics::SphereShapeConfiguration shapeConfiguration; - shapeConfiguration.m_radius = radius; + auto colliderConfig = AZStd::make_shared(); + colliderConfig->m_collisionLayer = layer; + auto shapeConfiguration = AZStd::make_shared(); + shapeConfiguration->m_radius = radius; AzPhysics::RigidBodyConfiguration rigidBodySettings; rigidBodySettings.m_computeMass = false; rigidBodySettings.m_computeInertiaTensor = false; @@ -421,7 +420,7 @@ namespace PhysX rigidBodySettings.m_mass = 1.0f; rigidBodySettings.m_position = position; rigidBodySettings.m_linearDamping = 0.0f; - rigidBodySettings.m_colliderAndShapeData = AZStd::make_pair(&colliderConfig, &shapeConfiguration); + rigidBodySettings.m_colliderAndShapeData = AzPhysics::ShapeColliderPair(colliderConfig, shapeConfiguration); if (auto* sceneInterface = AZ::Interface::Get()) { @@ -435,11 +434,12 @@ namespace PhysX const AzPhysics::CollisionLayer& layer /*= AzPhysics::CollisionLayer::Default*/) { AzPhysics::RigidBodyConfiguration rigidBodySettings; - Physics::ColliderConfiguration colliderConfig; - colliderConfig.m_collisionLayer = layer; - colliderConfig.m_rotation = AZ::Quaternion::CreateRotationX(AZ::Constants::HalfPi); - Physics::CapsuleShapeConfiguration shapeConfig(height, radius); - rigidBodySettings.m_colliderAndShapeData = AZStd::make_pair(&colliderConfig, &shapeConfig); + auto colliderConfig = AZStd::make_shared(); + colliderConfig->m_collisionLayer = layer; + colliderConfig->m_rotation = AZ::Quaternion::CreateRotationX(AZ::Constants::HalfPi); + + rigidBodySettings.m_colliderAndShapeData = AzPhysics::ShapeColliderPair(colliderConfig, + AZStd::make_shared(height, radius)); rigidBodySettings.m_position = position; rigidBodySettings.m_computeMass = false; rigidBodySettings.m_computeInertiaTensor = false; @@ -457,10 +457,10 @@ namespace PhysX const AZ::Vector3& position, const AZ::Vector3& dimensions /*= AZ::Vector3(1.0f)*/, const AzPhysics::CollisionLayer& layer /*= AzPhysics::CollisionLayer::Default*/) { - Physics::ColliderConfiguration colliderConfig; - colliderConfig.m_collisionLayer = layer; - Physics::BoxShapeConfiguration shapeConfiguration; - shapeConfiguration.m_dimensions = dimensions; + auto colliderConfig = AZStd::make_shared(); + colliderConfig->m_collisionLayer = layer; + auto shapeConfiguration = AZStd::make_shared(); + shapeConfiguration->m_dimensions = dimensions; AzPhysics::RigidBodyConfiguration rigidBodySettings; rigidBodySettings.m_computeMass = false; @@ -469,7 +469,7 @@ namespace PhysX rigidBodySettings.m_mass = 1.0f; rigidBodySettings.m_position = position; rigidBodySettings.m_linearDamping = 0.0f; - rigidBodySettings.m_colliderAndShapeData = AZStd::make_pair(&colliderConfig, &shapeConfiguration); + rigidBodySettings.m_colliderAndShapeData = AzPhysics::ShapeColliderPair(colliderConfig, shapeConfiguration); if (auto* sceneInterface = AZ::Interface::Get()) { return sceneInterface->AddSimulatedBody(sceneHandle, &rigidBodySettings); @@ -481,13 +481,14 @@ namespace PhysX const AZ::Vector3& position, const AZ::Vector3& dimensions /*= AZ::Vector3(1.0f)*/, const AzPhysics::CollisionLayer& layer /*= AzPhysics::CollisionLayer::Default*/) { - Physics::ColliderConfiguration colliderConfig; - colliderConfig.m_collisionLayer = layer; - Physics::BoxShapeConfiguration shapeConfiguration; - shapeConfiguration.m_dimensions = dimensions; + auto colliderConfig = AZStd::make_shared(); + colliderConfig->m_collisionLayer = layer; + auto shapeConfiguration = AZStd::make_shared(); + shapeConfiguration->m_dimensions = dimensions; + AzPhysics::StaticRigidBodyConfiguration rigidBodySettings; rigidBodySettings.m_position = position; - rigidBodySettings.m_colliderAndShapeData = AZStd::make_pair(&colliderConfig, &shapeConfiguration); + rigidBodySettings.m_colliderAndShapeData = AzPhysics::ShapeColliderPair(colliderConfig, shapeConfiguration); if (auto* sceneInterface = AZ::Interface::Get()) { diff --git a/Gems/QtForPython/Code/CMakeLists.txt b/Gems/QtForPython/Code/CMakeLists.txt index 413e542c9f..74c660043f 100644 --- a/Gems/QtForPython/Code/CMakeLists.txt +++ b/Gems/QtForPython/Code/CMakeLists.txt @@ -23,6 +23,7 @@ endif() ly_add_target( NAME QtForPython.Editor.Static STATIC NAMESPACE Gem + find_package(Qt) FILES_CMAKE qtforpython_editor_${PAL_PLATFORM_NAME_LOWERCASE}_files.cmake PLATFORM_INCLUDE_FILES @@ -40,6 +41,7 @@ ly_add_target( Gem::EditorPythonBindings.Static RUNTIME_DEPENDENCIES 3rdParty::pyside2 + Qt5::Test ) ly_add_target( diff --git a/Gems/WhiteBox/Code/Source/Components/EditorWhiteBoxColliderComponent.cpp b/Gems/WhiteBox/Code/Source/Components/EditorWhiteBoxColliderComponent.cpp index c8b189942e..d7acb18f29 100644 --- a/Gems/WhiteBox/Code/Source/Components/EditorWhiteBoxColliderComponent.cpp +++ b/Gems/WhiteBox/Code/Source/Components/EditorWhiteBoxColliderComponent.cpp @@ -18,6 +18,7 @@ #include #include +#include #include #include #include @@ -150,7 +151,9 @@ namespace WhiteBox bodyConfiguration.m_entityId = GetEntityId(); bodyConfiguration.m_orientation = GetTransform()->GetWorldRotationQuaternion(); bodyConfiguration.m_position = GetTransform()->GetWorldTranslation(); - bodyConfiguration.m_colliderAndShapeData = AzPhysics::ShapeColliderPair(&m_physicsColliderConfiguration, &m_meshShapeConfiguration); + bodyConfiguration.m_colliderAndShapeData = AzPhysics::ShapeColliderPair( + AZStd::make_shared(m_physicsColliderConfiguration), + AZStd::make_shared(m_meshShapeConfiguration)); if (m_sceneInterface) { diff --git a/cmake/Packaging.cmake b/cmake/Packaging.cmake index 4f6565edc7..ba610b1883 100644 --- a/cmake/Packaging.cmake +++ b/cmake/Packaging.cmake @@ -13,7 +13,34 @@ if(NOT PAL_TRAIT_BUILD_CPACK_SUPPORTED) return() endif() -ly_get_absolute_pal_filename(pal_dir ${CMAKE_SOURCE_DIR}/cmake/Platform/${PAL_HOST_PLATFORM_NAME}) +# public facing options will be used for conversion into cpack specific ones below. +set(LY_INSTALLER_DOWNLOAD_URL "" CACHE STRING "URL embedded into the installer to download additional artifacts") +set(LY_INSTALLER_LICENSE_URL "" CACHE STRING "Optionally embed a link to the license instead of raw text") + +# set all common cpack variable overrides first so they can be accessible via configure_file +# when the platform specific settings are applied below. additionally, any variable with +# the "CPACK_" prefix will automatically be cached for use in any phase of cpack namely +# pre/post build +set(CPACK_PACKAGE_VENDOR "${PROJECT_NAME}") +set(CPACK_PACKAGE_VERSION "${LY_VERSION_STRING}") +set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Installation Tool") + +string(TOLOWER ${PROJECT_NAME} _project_name_lower) +set(CPACK_PACKAGE_FILE_NAME "${_project_name_lower}_${LY_VERSION_STRING}_installer") + +set(DEFAULT_LICENSE_NAME "Apache-2.0") +set(DEFAULT_LICENSE_FILE "${CMAKE_SOURCE_DIR}/LICENSE.txt") + +set(CPACK_RESOURCE_FILE_LICENSE ${DEFAULT_LICENSE_FILE}) +set(CPACK_LICENSE_URL ${LY_INSTALLER_LICENSE_URL}) + +set(CPACK_PACKAGE_INSTALL_DIRECTORY "${CPACK_PACKAGE_VENDOR}/${CPACK_PACKAGE_VERSION}") + +# CMAKE_SOURCE_DIR doesn't equate to anything during execution of pre/post build scripts +set(CPACK_SOURCE_DIR ${CMAKE_SOURCE_DIR}/cmake) + +# attempt to apply platform specific settings +ly_get_absolute_pal_filename(pal_dir ${CPACK_SOURCE_DIR}/Platform/${PAL_HOST_PLATFORM_NAME}) include(${pal_dir}/Packaging_${PAL_HOST_PLATFORM_NAME_LOWERCASE}.cmake) # if we get here and the generator hasn't been set, then a non fatal error occurred disabling packaging support @@ -21,20 +48,6 @@ if(NOT CPACK_GENERATOR) return() endif() -set(CPACK_PACKAGE_VENDOR "${PROJECT_NAME}") -set(CPACK_PACKAGE_VERSION "${LY_VERSION_STRING}") -set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Installation Tool") - -string(TOLOWER ${PROJECT_NAME} _project_name_lower) -set(CPACK_PACKAGE_FILE_NAME "${_project_name_lower}_installer") - -set(DEFAULT_LICENSE_NAME "Apache-2.0") -set(DEFAULT_LICENSE_FILE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE.txt") - -set(CPACK_RESOURCE_FILE_LICENSE ${DEFAULT_LICENSE_FILE}) - -set(CPACK_PACKAGE_INSTALL_DIRECTORY "${CPACK_PACKAGE_VENDOR}/${CPACK_PACKAGE_VERSION}") - # IMPORTANT: required to be included AFTER setting all property overrides include(CPack REQUIRED) @@ -76,3 +89,12 @@ ly_configure_cpack_component( DISPLAY_NAME "${PROJECT_NAME} Core" DESCRIPTION "${PROJECT_NAME} Headers, Libraries and Tools" ) + +if(LY_INSTALLER_DOWNLOAD_URL) + # this will set the following variables: CPACK_DOWNLOAD_SITE, CPACK_DOWNLOAD_ALL, and CPACK_UPLOAD_DIRECTORY + cpack_configure_downloads( + ${LY_INSTALLER_DOWNLOAD_URL} + UPLOAD_DIRECTORY ${CMAKE_BINARY_DIR}/_CPack_Uploads # to match the _CPack_Packages directory + ALL + ) +endif() diff --git a/cmake/Platform/Windows/PackagingBootstrapper.wxs b/cmake/Platform/Windows/PackagingBootstrapper.wxs new file mode 100644 index 0000000000..c3d1dd7a7b --- /dev/null +++ b/cmake/Platform/Windows/PackagingBootstrapper.wxs @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cmake/Platform/Windows/PackagingPostBuild.cmake b/cmake/Platform/Windows/PackagingPostBuild.cmake new file mode 100644 index 0000000000..3ba6ef2096 --- /dev/null +++ b/cmake/Platform/Windows/PackagingPostBuild.cmake @@ -0,0 +1,88 @@ +# +# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +# its licensors. +# +# For complete copyright and license terms please see the LICENSE at the root of this +# distribution (the "License"). All use of this software is governed by the License, +# or, if provided, by the license below or the license accompanying this file. Do not +# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# + +# convert the path to a windows style path using string replace because TO_NATIVE_PATH +# only works on real paths +string(REPLACE "/" "\\" _fixed_package_install_dir ${CPACK_PACKAGE_INSTALL_DIRECTORY}) + +# directory where the auto generated files live e.g /_CPack_Package/win64/WIX +set(_cpack_wix_out_dir ${CPACK_TOPLEVEL_DIRECTORY}) +set(_bootstrap_out_dir "${CPACK_TOPLEVEL_DIRECTORY}/bootstrap") + +set(_bootstrap_filename "${CPACK_PACKAGE_FILE_NAME}.exe") +set(_bootstrap_output_file ${_cpack_wix_out_dir}/${_bootstrap_filename}) + +set(_ext_flags + -ext WixBalExtension +) + +set(_addtional_defines + -dCPACK_BOOTSTRAP_UPGRADE_GUID=${CPACK_WIX_BOOTSTRAP_UPGRADE_GUID} + -dCPACK_DOWNLOAD_SITE=${CPACK_DOWNLOAD_SITE} + -dCPACK_LOCAL_INSTALLER_DIR=${_cpack_wix_out_dir} + -dCPACK_PACKAGE_FILE_NAME=${CPACK_PACKAGE_FILE_NAME} + -dCPACK_PACKAGE_INSTALL_DIRECTORY=${_fixed_package_install_dir} +) + +if(CPACK_LICENSE_URL) + list(APPEND _addtional_defines -dCPACK_LICENSE_URL=${CPACK_LICENSE_URL}) +endif() + +set(_candle_command + ${CPACK_WIX_CANDLE_EXECUTABLE} + -nologo + -arch x64 + "-I${_cpack_wix_out_dir}" # to include cpack_variables.wxi + ${_addtional_defines} + ${_ext_flags} + "${CPACK_SOURCE_DIR}/Platform/Windows/PackagingBootstrapper.wxs" + -o "${_bootstrap_out_dir}/" +) + +set(_light_command + ${CPACK_WIX_LIGHT_EXECUTABLE} + -nologo + ${_ext_flags} + ${_bootstrap_out_dir}/*.wixobj + -o "${_bootstrap_output_file}" +) + +message(STATUS "Creating Bootstrap Installer...") +execute_process( + COMMAND ${_candle_command} + COMMAND_ERROR_IS_FATAL ANY +) +execute_process( + COMMAND ${_light_command} + COMMAND_ERROR_IS_FATAL ANY +) + +file(COPY ${_bootstrap_output_file} + DESTINATION ${CPACK_PACKAGE_DIRECTORY} +) + +message(STATUS "Bootstrap installer generated to ${CPACK_PACKAGE_DIRECTORY}/${_bootstrap_filename}") + +# use the internal default path if somehow not specified from cpack_configure_downloads +if(NOT CPACK_UPLOAD_DIRECTORY) + set(CPACK_UPLOAD_DIRECTORY ${CPACK_PACKAGE_DIRECTORY}/CPackUploads) +endif() + +# copy the artifacts intended to be uploaded to a remote server into the folder specified +# through cpack_configure_downloads. this mimics the same process cpack does natively for +# some other frameworks that have built-in online installer support. +message(STATUS "Copying installer artifacts to upload directory...") +file(REMOVE_RECURSE ${CPACK_UPLOAD_DIRECTORY}) +file(GLOB _artifacts "${_cpack_wix_out_dir}/*.msi" "${_cpack_wix_out_dir}/*.cab") +file(COPY ${_artifacts} + DESTINATION ${CPACK_UPLOAD_DIRECTORY} +) +message(STATUS "Artifacts copied to ${CPACK_UPLOAD_DIRECTORY}") diff --git a/cmake/Platform/Windows/PackagingTemplate.wxs.in b/cmake/Platform/Windows/PackagingTemplate.wxs.in index 3e5db03ec2..0b3c597ab6 100644 --- a/cmake/Platform/Windows/PackagingTemplate.wxs.in +++ b/cmake/Platform/Windows/PackagingTemplate.wxs.in @@ -12,10 +12,10 @@ Manufacturer="$(var.CPACK_PACKAGE_VENDOR)" UpgradeCode="$(var.CPACK_WIX_UPGRADE_GUID)"> - + - - + + {tgt} : {e}", e.errno) diff --git a/scripts/build/Platform/Linux/build_config.json b/scripts/build/Platform/Linux/build_config.json index bbfc3e4269..5d96ae7846 100644 --- a/scripts/build/Platform/Linux/build_config.json +++ b/scripts/build/Platform/Linux/build_config.json @@ -83,7 +83,7 @@ "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-6.0 -DCMAKE_CXX_COMPILER=clang++-6.0 -DLY_UNITY_BUILD=TRUE -DLY_PARALLEL_LINK_JOBS=4 -DO3DE_HOME_PATH=\"${WORKSPACE}/home\" -DO3DE_REGISTER_ENGINE_PATH=\"${WORKSPACE}/o3de\" -DO3DE_REGISTER_THIS_ENGINE=TRUE", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "all", - "CTEST_OPTIONS": "-E (Gem::EMotionFX.Editor.Tests|Gem::AWSClientAuth.Tests|Gem::AWSCore.Editor.Tests) -L FRAMEWORK_googletest" + "CTEST_OPTIONS": "-E (Gem::EMotionFX.Editor.Tests|Gem::AWSClientAuth.Tests|Gem::AWSCore.Editor.Tests) -L FRAMEWORK_googletest --repeat until-pass:5" } }, "test_profile_nounity": { @@ -95,7 +95,7 @@ "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-6.0 -DCMAKE_CXX_COMPILER=clang++-6.0 -DLY_UNITY_BUILD=FALSE -DLY_PARALLEL_LINK_JOBS=4 -DO3DE_HOME_PATH=\"${WORKSPACE}/home\" -DO3DE_REGISTER_ENGINE_PATH=\"${WORKSPACE}/o3de\" -DO3DE_REGISTER_THIS_ENGINE=TRUE", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "all", - "CTEST_OPTIONS": "-E (Gem::EMotionFX.Editor.Tests|Gem::AWSClientAuth.Tests|Gem::AWSCore.Editor.Tests) -L FRAMEWORK_googletest" + "CTEST_OPTIONS": "-E (Gem::EMotionFX.Editor.Tests|Gem::AWSClientAuth.Tests|Gem::AWSCore.Editor.Tests) -L FRAMEWORK_googletest --repeat until-pass:5" } }, "asset_profile": { diff --git a/scripts/ctest/result_processing/result_processing.py b/scripts/ctest/result_processing/result_processing.py index 791b15fece..e8ba78265c 100755 --- a/scripts/ctest/result_processing/result_processing.py +++ b/scripts/ctest/result_processing/result_processing.py @@ -118,8 +118,10 @@ def _merge_xml_results(xml_results_path, prefix, merged_xml_name, parent_element def _aggregate_attributes(nodes): for node in nodes: for attribute in attributes_to_aggregate: - value = node.attrib[attribute.name] - temp_dict[attribute.name] += attribute.func(value) + if attribute.name in node.attrib: + temp_dict[attribute.name] += attribute.func(node.attrib[attribute.name]) + else: + print("Failed to find key {} in {}, continuing...".format(attribute.name, node.tag)) base_tree = xet.parse(xml_files[0]) base_tree_root = base_tree.getroot()