diff --git a/.gitignore b/.gitignore index 3a9b8f6f8e..5f6172cd76 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ Editor/EditorEventLog.xml Editor/EditorLayout.xml **/*egg-info/** **/*egg-link +**/[Rr]estricted UserSettings.xml [Uu]ser/ FrameCapture/** diff --git a/Assets/Editor/Prefabs/Default_Level.prefab b/Assets/Editor/Prefabs/Default_Level.prefab index d02d669f53..0267131fa0 100644 --- a/Assets/Editor/Prefabs/Default_Level.prefab +++ b/Assets/Editor/Prefabs/Default_Level.prefab @@ -185,7 +185,7 @@ { "id": { "materialAssetId": { - "guid": "{935F694A-8639-515B-8133-81CDC7948E5B}", + "guid": "{0CD745C0-6AA8-569A-A68A-73A3270986C4}", "subId": 803645540 } } @@ -197,7 +197,7 @@ "id": { "lodIndex": 0, "materialAssetId": { - "guid": "{935F694A-8639-515B-8133-81CDC7948E5B}", + "guid": "{0CD745C0-6AA8-569A-A68A-73A3270986C4}", "subId": 803645540 } } diff --git a/Assets/Editor/Scripts/lua_symbols.py b/Assets/Editor/Scripts/lua_symbols.py new file mode 100644 index 0000000000..b21edb41d0 --- /dev/null +++ b/Assets/Editor/Scripts/lua_symbols.py @@ -0,0 +1,116 @@ +# +# Copyright (c) Contributors to the Open 3D Engine Project. +# For complete copyright and license terms please see the LICENSE at the root of this distribution. +# +# SPDX-License-Identifier: Apache-2.0 OR MIT +# +# + + +# This script shows basic usage of LuaSymbolsReporterBus, +# Which can be used to report all symbols available for +# game scripting with Lua. + +import sys +import os + +import azlmbr.bus as azbus +import azlmbr.script as azscript +import azlmbr.legacy.general as azgeneral + + +def _dump_class_symbol(class_symbol: azlmbr.script.LuaClassSymbol): + print(f"** {class_symbol}") + print("Properties:") + for property_symbol in class_symbol.properties: + print(f" - {property_symbol}") + print("Methods:") + for method_symbol in class_symbol.methods: + print(f" - {method_symbol}") + + +def _dump_lua_classes(): + class_symbols = azscript.LuaSymbolsReporterBus(azbus.Broadcast, + "GetListOfClasses") + print("======== Classes ==========") + sorted_classes_by_named = sorted(class_symbols, key=lambda class_symbol: class_symbol.name) + for class_symbol in sorted_classes_by_named: + _dump_class_symbol(class_symbol) + print("\n\n") + + +def _dump_lua_globals(): + global_properties = azscript.LuaSymbolsReporterBus(azbus.Broadcast, + "GetListOfGlobalProperties") + print("======== Global Properties ==========") + sorted_properties_by_name = sorted(global_properties, key=lambda symbol: symbol.name) + for property_symbol in sorted_properties_by_name: + print(f"- {property_symbol}") + print("\n\n") + global_functions = azscript.LuaSymbolsReporterBus(azbus.Broadcast, + "GetListOfGlobalFunctions") + print("======== Global Functions ==========") + sorted_functions_by_name = sorted(global_functions, key=lambda symbol: symbol.name) + for function_symbol in sorted_functions_by_name: + print(f"- {function_symbol}") + print("\n\n") + + +def _dump_lua_ebus(ebus_symbol: azlmbr.script.LuaEBusSymbol): + print(f">> {ebus_symbol}") + sorted_senders = sorted(ebus_symbol.senders, key=lambda symbol: symbol.name) + for sender in sorted_senders: + print(f" - {sender}") + print("\n") + + +def _dump_lua_ebuses(): + ebuses = azscript.LuaSymbolsReporterBus(azbus.Broadcast, + "GetListOfEBuses") + print("======== Ebus List ==========") + sorted_ebuses_by_name = sorted(ebuses, key=lambda symbol: symbol.name) + for ebus_symbol in sorted_ebuses_by_name: + _dump_lua_ebus(ebus_symbol) + print("\n\n") + + +class WhatToDo: + DumpClasses = "c" + DumpGlobals = "g" + DumpEBuses = "e" + +if __name__ == "__main__": + redirecting_stdout = False + orig_stdout = sys.stdout + if len(sys.argv) > 1: + output_file_name = sys.argv[1] + if not os.path.isabs(output_file_name): + game_root_path = os.path.normpath(azgeneral.get_game_folder()) + output_file_name = os.path.join(game_root_path, output_file_name) + try: + file_obj = open(output_file_name, 'wt') + sys.stdout = file_obj + redirecting_stdout = True + except Exception as e: + print(f"Failed to open {output_file_name}: {e}") + sys.exit(-1) + + what_to_do = [action.lower() for action in sys.argv[2:]] + + # If the user did not specify what to do, then let's dump + # all the symbols. + if len(what_to_do) < 1: + what_to_do = [WhatToDo.DumpClasses, WhatToDo.DumpGlobals, WhatToDo.DumpEBuses] + + for action in what_to_do: + if action == WhatToDo.DumpClasses: + _dump_lua_classes() + elif action == WhatToDo.DumpGlobals: + _dump_lua_globals() + elif action == WhatToDo.DumpEBuses: + _dump_lua_ebuses() + + if redirecting_stdout: + sys.stdout.close() + sys.stdout = orig_stdout + print(f" Lua Symbols Are available in: {output_file_name}") diff --git a/Assets/Engine/Engine_Dependencies.xml b/Assets/Engine/Engine_Dependencies.xml index 0d6541e18e..b969b3bc97 100644 --- a/Assets/Engine/Engine_Dependencies.xml +++ b/Assets/Engine/Engine_Dependencies.xml @@ -1,16 +1,9 @@ - - - - - - - - - - - - - - - \ No newline at end of file + + + + + + + + diff --git a/Assets/Engine/SeedAssetList.seed b/Assets/Engine/SeedAssetList.seed index aafbffbe8f..18ccd19dc3 100644 --- a/Assets/Engine/SeedAssetList.seed +++ b/Assets/Engine/SeedAssetList.seed @@ -1,621 +1,260 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - diff --git a/AutomatedTesting/Editor/Scripts/Profiler/__init__.py b/AutomatedTesting/Editor/Scripts/Profiler/__init__.py new file mode 100644 index 0000000000..7a325ca97e --- /dev/null +++ b/AutomatedTesting/Editor/Scripts/Profiler/__init__.py @@ -0,0 +1,7 @@ +# +# Copyright (c) Contributors to the Open 3D Engine Project. +# For complete copyright and license terms please see the LICENSE at the root of this distribution. +# +# SPDX-License-Identifier: Apache-2.0 OR MIT +# +# diff --git a/AutomatedTesting/Editor/Scripts/Profiler/profiler_system_example.py b/AutomatedTesting/Editor/Scripts/Profiler/profiler_system_example.py new file mode 100644 index 0000000000..16f161c50e --- /dev/null +++ b/AutomatedTesting/Editor/Scripts/Profiler/profiler_system_example.py @@ -0,0 +1,30 @@ +# +# Copyright (c) Contributors to the Open 3D Engine Project. +# For complete copyright and license terms please see the LICENSE at the root of this distribution. +# +# SPDX-License-Identifier: Apache-2.0 OR MIT +# +# + +import azlmbr.debug as debug + +import pathlib + +def test_profiler_system(): + if not debug.g_ProfilerSystem.IsValid(): + print('g_ProfilerSystem is INVALID') + return + + state = 'ACTIVE' if debug.g_ProfilerSystem.IsActive() else 'INACTIVE' + print(f'Profiler system is currently {state}') + + capture_location = pathlib.Path(debug.g_ProfilerSystem.GetCaptureLocation()) + print(f'Capture location set to {capture_location}') + + print('Capturing single frame...' ) + capture_file = str(capture_location / 'script_capture_frame.json') + debug.g_ProfilerSystem.CaptureFrame(capture_file) + +# Invoke main function +if __name__ == '__main__': + test_profiler_system() diff --git a/AutomatedTesting/Gem/AssetProcessorGemConfig.setreg b/AutomatedTesting/Gem/AssetProcessorGemConfig.setreg index 043774c9cc..d4de26a2dc 100644 --- a/AutomatedTesting/Gem/AssetProcessorGemConfig.setreg +++ b/AutomatedTesting/Gem/AssetProcessorGemConfig.setreg @@ -3,19 +3,19 @@ "AssetProcessor": { "Settings": { "Exclude PythonTest Benchmark Settings Assets": { - "pattern": ".*\\\\/PythonTests\\\\/.*benchmarksettings" + "pattern": "(^|.+/)PythonTests/.*benchmarksettings" }, "Exclude fbx_tests": { - "pattern": ".*\\\\/fbx_tests\\\\/assets\\\\/.*" + "pattern": "(^|.+/)fbx_tests/assets(/.+)$" }, "Exclude wwise_bank_dependency_tests": { - "pattern": ".*\\\\/wwise_bank_dependency_tests\\\\/assets\\\\/.*" + "pattern": "(^|.+/)wwise_bank_dependency_tests/assets(/.+)$" }, "Exclude AssetProcessorTestAssets": { - "pattern": ".*\\\\/asset_processor_tests\\\\/assets\\\\/.*" + "pattern": "(^|.+/)asset_processor_tests/assets(/.+)$" }, "Exclude Restricted AssetProcessorTestAssets": { - "pattern": ".*\\\\/asset_processor_tests\\\\/restricted\\\\/.*" + "pattern": "(^|.+/)asset_processor_tests/restricted(/.+)$" } } } diff --git a/AutomatedTesting/Gem/Code/CMakeLists.txt b/AutomatedTesting/Gem/Code/CMakeLists.txt index 6f55cc2764..8808507765 100644 --- a/AutomatedTesting/Gem/Code/CMakeLists.txt +++ b/AutomatedTesting/Gem/Code/CMakeLists.txt @@ -14,15 +14,25 @@ ly_add_target( FILES_CMAKE automatedtesting_files.cmake ${pal_dir}/platform_${PAL_PLATFORM_NAME_LOWERCASE}_files.cmake + automatedtesting_autogen_files.cmake INCLUDE_DIRECTORIES PRIVATE Source PUBLIC Include BUILD_DEPENDENCIES + PUBLIC + AZ::AzNetworking + Gem::Multiplayer PRIVATE AZ::AzCore Gem::Atom_AtomBridge.Static + Gem::Multiplayer.Static + AUTOGEN_RULES + *.AutoComponent.xml,AutoComponent_Header.jinja,$path/$fileprefix.AutoComponent.h + *.AutoComponent.xml,AutoComponent_Source.jinja,$path/$fileprefix.AutoComponent.cpp + *.AutoComponent.xml,AutoComponentTypes_Header.jinja,$path/AutoComponentTypes.h + *.AutoComponent.xml,AutoComponentTypes_Source.jinja,$path/AutoComponentTypes.cpp ) # if enabled, AutomatedTesting is used by all kinds of applications diff --git a/AutomatedTesting/Gem/Code/Source/AutoGen/NetworkTestPlayerComponent.AutoComponent.xml b/AutomatedTesting/Gem/Code/Source/AutoGen/NetworkTestPlayerComponent.AutoComponent.xml new file mode 100644 index 0000000000..12c222add6 --- /dev/null +++ b/AutomatedTesting/Gem/Code/Source/AutoGen/NetworkTestPlayerComponent.AutoComponent.xml @@ -0,0 +1,16 @@ + + + + + + + + + + diff --git a/AutomatedTesting/Gem/Code/Source/AutomatedTestingModule.cpp b/AutomatedTesting/Gem/Code/Source/AutomatedTestingModule.cpp index 7ed37e89ca..b5be8410ef 100644 --- a/AutomatedTesting/Gem/Code/Source/AutomatedTestingModule.cpp +++ b/AutomatedTesting/Gem/Code/Source/AutomatedTestingModule.cpp @@ -8,6 +8,7 @@ #include #include +#include #include @@ -27,6 +28,8 @@ namespace AutomatedTesting m_descriptors.insert(m_descriptors.end(), { AutomatedTestingSystemComponent::CreateDescriptor(), }); + + CreateComponentDescriptors(m_descriptors); //< Register multiplayer components } /** diff --git a/AutomatedTesting/Gem/Code/Source/AutomatedTestingSystemComponent.cpp b/AutomatedTesting/Gem/Code/Source/AutomatedTestingSystemComponent.cpp index 3b373b1f65..06a0775d70 100644 --- a/AutomatedTesting/Gem/Code/Source/AutomatedTestingSystemComponent.cpp +++ b/AutomatedTesting/Gem/Code/Source/AutomatedTestingSystemComponent.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include @@ -45,7 +46,7 @@ namespace AutomatedTesting void AutomatedTestingSystemComponent::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required) { - AZ_UNUSED(required); + required.push_back(AZ_CRC_CE("MultiplayerService")); } void AutomatedTestingSystemComponent::GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent) @@ -60,6 +61,7 @@ namespace AutomatedTesting void AutomatedTestingSystemComponent::Activate() { AutomatedTestingRequestBus::Handler::BusConnect(); + RegisterMultiplayerComponents(); //< Register AutomatedTesting's multiplayer components to assign NetComponentIds } void AutomatedTestingSystemComponent::Deactivate() diff --git a/AutomatedTesting/Gem/Code/automatedtesting_autogen_files.cmake b/AutomatedTesting/Gem/Code/automatedtesting_autogen_files.cmake new file mode 100644 index 0000000000..b3c6fcaa0b --- /dev/null +++ b/AutomatedTesting/Gem/Code/automatedtesting_autogen_files.cmake @@ -0,0 +1,14 @@ +# +# Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution. +# +# SPDX-License-Identifier: Apache-2.0 OR MIT +# +# + +set(FILES + ${LY_ROOT_FOLDER}/Gems/Multiplayer/Code/Include/Multiplayer/AutoGen/AutoComponent_Common.jinja + ${LY_ROOT_FOLDER}/Gems/Multiplayer/Code/Include/Multiplayer/AutoGen/AutoComponent_Header.jinja + ${LY_ROOT_FOLDER}/Gems/Multiplayer/Code/Include/Multiplayer/AutoGen/AutoComponent_Source.jinja + ${LY_ROOT_FOLDER}/Gems/Multiplayer/Code/Include/Multiplayer/AutoGen/AutoComponentTypes_Header.jinja + ${LY_ROOT_FOLDER}/Gems/Multiplayer/Code/Include/Multiplayer/AutoGen/AutoComponentTypes_Source.jinja +) diff --git a/AutomatedTesting/Gem/Code/automatedtesting_files.cmake b/AutomatedTesting/Gem/Code/automatedtesting_files.cmake index 6bf2bf72d9..eb619104a4 100644 --- a/AutomatedTesting/Gem/Code/automatedtesting_files.cmake +++ b/AutomatedTesting/Gem/Code/automatedtesting_files.cmake @@ -11,4 +11,5 @@ set(FILES Source/AutomatedTestingModule.cpp Source/AutomatedTestingSystemComponent.cpp Source/AutomatedTestingSystemComponent.h + Source/AutoGen/NetworkTestPlayerComponent.AutoComponent.xml ) diff --git a/AutomatedTesting/Gem/Code/enabled_gems.cmake b/AutomatedTesting/Gem/Code/enabled_gems.cmake index f653a54505..e6a4d6ca37 100644 --- a/AutomatedTesting/Gem/Code/enabled_gems.cmake +++ b/AutomatedTesting/Gem/Code/enabled_gems.cmake @@ -55,4 +55,5 @@ set(ENABLED_GEMS PrefabBuilder AudioSystem Profiler + Multiplayer ) diff --git a/AutomatedTesting/Gem/PythonTests/Atom/CMakeLists.txt b/AutomatedTesting/Gem/PythonTests/Atom/CMakeLists.txt index ff3cd5c465..87a66c880e 100644 --- a/AutomatedTesting/Gem/PythonTests/Atom/CMakeLists.txt +++ b/AutomatedTesting/Gem/PythonTests/Atom/CMakeLists.txt @@ -20,19 +20,6 @@ if(PAL_TRAIT_BUILD_HOST_TOOLS AND PAL_TRAIT_BUILD_TESTS_SUPPORTED) COMPONENT Atom ) - ly_add_pytest( - NAME AutomatedTesting::Atom_TestSuite_Main_Optimized - TEST_SUITE main - PATH ${CMAKE_CURRENT_LIST_DIR}/TestSuite_Main_Optimized.py - TEST_SERIAL - TIMEOUT 600 - RUNTIME_DEPENDENCIES - AssetProcessor - AutomatedTesting.Assets - Editor - COMPONENT - Atom - ) ly_add_pytest( NAME AutomatedTesting::Atom_TestSuite_Sandbox TEST_SUITE sandbox diff --git a/AutomatedTesting/Gem/PythonTests/Atom/TestSuite_Main.py b/AutomatedTesting/Gem/PythonTests/Atom/TestSuite_Main.py index 6cc48984ab..52e7ec993e 100644 --- a/AutomatedTesting/Gem/PythonTests/Atom/TestSuite_Main.py +++ b/AutomatedTesting/Gem/PythonTests/Atom/TestSuite_Main.py @@ -4,252 +4,95 @@ For complete copyright and license terms please see the LICENSE at the root of t SPDX-License-Identifier: Apache-2.0 OR MIT """ -import logging -import os - import pytest -import ly_test_tools.environment.file_system as file_system -import editor_python_test_tools.hydra_test_utils as hydra -from Atom.atom_utils.atom_constants import LIGHT_TYPES - -logger = logging.getLogger(__name__) -TEST_DIRECTORY = os.path.join(os.path.dirname(__file__), "tests") +from ly_test_tools.o3de.editor_test import EditorSharedTest, EditorTestSuite @pytest.mark.parametrize("project", ["AutomatedTesting"]) @pytest.mark.parametrize("launcher_platform", ['windows_editor']) -@pytest.mark.parametrize("level", ["auto_test"]) -class TestAtomEditorComponentsMain(object): - """Holds tests for Atom components.""" +class TestAutomation(EditorTestSuite): - @pytest.mark.test_case_id("C32078118") # Decal - @pytest.mark.test_case_id("C32078119") # DepthOfField - @pytest.mark.test_case_id("C32078120") # Directional Light - @pytest.mark.test_case_id("C32078121") # Exposure Control - @pytest.mark.test_case_id("C32078115") # Global Skylight (IBL) - @pytest.mark.test_case_id("C32078125") # Physical Sky - @pytest.mark.test_case_id("C32078127") # PostFX Layer - @pytest.mark.test_case_id("C32078131") # PostFX Radius Weight Modifier - @pytest.mark.test_case_id("C32078117") # Light - @pytest.mark.test_case_id("C36525660") # Display Mapper - def test_AtomEditorComponents_AddedToEntity(self, request, editor, level, workspace, project, launcher_platform): - """ - Please review the hydra script run by this test for more specific test info. - Tests the Atom components & verifies all "expected_lines" appear in Editor.log - """ - cfg_args = [level] + @pytest.mark.test_case_id("C36525657") + class AtomEditorComponents_BloomAdded(EditorSharedTest): + from Atom.tests import hydra_AtomEditorComponents_BloomAdded as test_module - expected_lines = [ - # Decal Component - "Decal Entity successfully created", - "Decal_test: Component added to the entity: True", - "Decal_test: Component removed after UNDO: True", - "Decal_test: Component added after REDO: True", - "Decal_test: Entered game mode: True", - "Decal_test: Exit game mode: True", - "Decal Controller|Configuration|Material: SUCCESS", - "Decal_test: Entity is hidden: True", - "Decal_test: Entity is shown: True", - "Decal_test: Entity deleted: True", - "Decal_test: UNDO entity deletion works: True", - "Decal_test: REDO entity deletion works: True", - # DepthOfField Component - "DepthOfField Entity successfully created", - "DepthOfField_test: Component added to the entity: True", - "DepthOfField_test: Component removed after UNDO: True", - "DepthOfField_test: Component added after REDO: True", - "DepthOfField_test: Entered game mode: True", - "DepthOfField_test: Exit game mode: True", - "DepthOfField_test: Entity disabled initially: True", - "DepthOfField_test: Entity enabled after adding required components: True", - "DepthOfField Controller|Configuration|Camera Entity: SUCCESS", - "DepthOfField_test: Entity is hidden: True", - "DepthOfField_test: Entity is shown: True", - "DepthOfField_test: Entity deleted: True", - "DepthOfField_test: UNDO entity deletion works: True", - "DepthOfField_test: REDO entity deletion works: True", - # Directional Light Component - "Directional Light Entity successfully created", - "Directional Light_test: Component added to the entity: True", - "Directional Light_test: Component removed after UNDO: True", - "Directional Light_test: Component added after REDO: True", - "Directional Light_test: Entered game mode: True", - "Directional Light_test: Exit game mode: True", - "Directional Light_test: Entity is hidden: True", - "Directional Light_test: Entity is shown: True", - "Directional Light_test: Entity deleted: True", - "Directional Light_test: UNDO entity deletion works: True", - "Directional Light_test: REDO entity deletion works: True", - # Exposure Control Component - "Exposure Control Entity successfully created", - "Exposure Control_test: Component added to the entity: True", - "Exposure Control_test: Component removed after UNDO: True", - "Exposure Control_test: Component added after REDO: True", - "Exposure Control_test: Entered game mode: True", - "Exposure Control_test: Exit game mode: True", - "Exposure Control_test: Entity disabled initially: True", - "Exposure Control_test: Entity enabled after adding required components: True", - "Exposure Control_test: Entity is hidden: True", - "Exposure Control_test: Entity is shown: True", - "Exposure Control_test: Entity deleted: True", - "Exposure Control_test: UNDO entity deletion works: True", - "Exposure Control_test: REDO entity deletion works: True", - # Global Skylight (IBL) Component - "Global Skylight (IBL) Entity successfully created", - "Global Skylight (IBL)_test: Component added to the entity: True", - "Global Skylight (IBL)_test: Component removed after UNDO: True", - "Global Skylight (IBL)_test: Component added after REDO: True", - "Global Skylight (IBL)_test: Entered game mode: True", - "Global Skylight (IBL)_test: Exit game mode: True", - "Global Skylight (IBL) Controller|Configuration|Diffuse Image: SUCCESS", - "Global Skylight (IBL) Controller|Configuration|Specular Image: SUCCESS", - "Global Skylight (IBL)_test: Entity is hidden: True", - "Global Skylight (IBL)_test: Entity is shown: True", - "Global Skylight (IBL)_test: Entity deleted: True", - "Global Skylight (IBL)_test: UNDO entity deletion works: True", - "Global Skylight (IBL)_test: REDO entity deletion works: True", - # Physical Sky Component - "Physical Sky Entity successfully created", - "Physical Sky component was added to entity", - "Entity has a Physical Sky component", - "Physical Sky_test: Component added to the entity: True", - "Physical Sky_test: Component removed after UNDO: True", - "Physical Sky_test: Component added after REDO: True", - "Physical Sky_test: Entered game mode: True", - "Physical Sky_test: Exit game mode: True", - "Physical Sky_test: Entity is hidden: True", - "Physical Sky_test: Entity is shown: True", - "Physical Sky_test: Entity deleted: True", - "Physical Sky_test: UNDO entity deletion works: True", - "Physical Sky_test: REDO entity deletion works: True", - # PostFX Layer Component - "PostFX Layer Entity successfully created", - "PostFX Layer_test: Component added to the entity: True", - "PostFX Layer_test: Component removed after UNDO: True", - "PostFX Layer_test: Component added after REDO: True", - "PostFX Layer_test: Entered game mode: True", - "PostFX Layer_test: Exit game mode: True", - "PostFX Layer_test: Entity is hidden: True", - "PostFX Layer_test: Entity is shown: True", - "PostFX Layer_test: Entity deleted: True", - "PostFX Layer_test: UNDO entity deletion works: True", - "PostFX Layer_test: REDO entity deletion works: True", - # PostFX Radius Weight Modifier Component - "PostFX Radius Weight Modifier Entity successfully created", - "PostFX Radius Weight Modifier_test: Component added to the entity: True", - "PostFX Radius Weight Modifier_test: Component removed after UNDO: True", - "PostFX Radius Weight Modifier_test: Component added after REDO: True", - "PostFX Radius Weight Modifier_test: Entered game mode: True", - "PostFX Radius Weight Modifier_test: Exit game mode: True", - "PostFX Radius Weight Modifier_test: Entity is hidden: True", - "PostFX Radius Weight Modifier_test: Entity is shown: True", - "PostFX Radius Weight Modifier_test: Entity deleted: True", - "PostFX Radius Weight Modifier_test: UNDO entity deletion works: True", - "PostFX Radius Weight Modifier_test: REDO entity deletion works: True", - # Light Component - "Light Entity successfully created", - "Light_test: Component added to the entity: True", - "Light_test: Component removed after UNDO: True", - "Light_test: Component added after REDO: True", - "Light_test: Entered game mode: True", - "Light_test: Exit game mode: True", - "Light_test: Entity is hidden: True", - "Light_test: Entity is shown: True", - "Light_test: Entity deleted: True", - "Light_test: UNDO entity deletion works: True", - "Light_test: REDO entity deletion works: True", - # Display Mapper Component - "Display Mapper Entity successfully created", - "Display Mapper_test: Component added to the entity: True", - "Display Mapper_test: Component removed after UNDO: True", - "Display Mapper_test: Component added after REDO: True", - "Display Mapper_test: Entered game mode: True", - "Display Mapper_test: Exit game mode: True", - "Display Mapper_test: Entity is hidden: True", - "Display Mapper_test: Entity is shown: True", - "Display Mapper_test: Entity deleted: True", - "Display Mapper_test: UNDO entity deletion works: True", - "Display Mapper_test: REDO entity deletion works: True", - ] + @pytest.mark.test_case_id("C32078118") + class AtomEditorComponents_DecalAdded(EditorSharedTest): + from Atom.tests import hydra_AtomEditorComponents_DecalAdded as test_module - unexpected_lines = [ - "Trace::Assert", - "Trace::Error", - "Traceback (most recent call last):", - ] + @pytest.mark.test_case_id("C36525658") + class AtomEditorComponents_DeferredFogAdded(EditorSharedTest): + from Atom.tests import hydra_AtomEditorComponents_DeferredFogAdded as test_module - hydra.launch_and_validate_results( - request, - TEST_DIRECTORY, - editor, - "hydra_AtomEditorComponents_AddedToEntity.py", - timeout=120, - expected_lines=expected_lines, - unexpected_lines=unexpected_lines, - halt_on_unexpected=True, - null_renderer=True, - cfg_args=cfg_args, - ) + @pytest.mark.test_case_id("C32078119") + class AtomEditorComponents_DepthOfFieldAdded(EditorSharedTest): + from Atom.tests import hydra_AtomEditorComponents_DepthOfFieldAdded as test_module - @pytest.mark.test_case_id("C34525095") - def test_AtomEditorComponents_LightComponent( - self, request, editor, workspace, project, launcher_platform, level): - """ - Please review the hydra script run by this test for more specific test info. - Tests that the Light component has the expected property options available to it. - """ - cfg_args = [level] + @pytest.mark.test_case_id("C32078120") + class AtomEditorComponents_DirectionalLightAdded(EditorSharedTest): + from Atom.tests import hydra_AtomEditorComponents_DirectionalLightAdded as test_module - expected_lines = [ - "light_entity Entity successfully created", - "Entity has a Light component", - "light_entity_test: Component added to the entity: True", - f"light_entity_test: Property value is {LIGHT_TYPES['sphere']} which matches {LIGHT_TYPES['sphere']}", - "Controller|Configuration|Shadows|Enable shadow set to True", - "light_entity Controller|Configuration|Shadows|Shadowmap size: SUCCESS", - "Controller|Configuration|Shadows|Shadow filter method set to 1", # PCF - "Controller|Configuration|Shadows|Filtering sample count set to 4", - "Controller|Configuration|Shadows|Filtering sample count set to 64", - "Controller|Configuration|Shadows|Shadow filter method set to 2", # ESM - "Controller|Configuration|Shadows|ESM exponent set to 50.0", - "Controller|Configuration|Shadows|ESM exponent set to 5000.0", - "Controller|Configuration|Shadows|Shadow filter method set to 3", # ESM+PCF - f"light_entity_test: Property value is {LIGHT_TYPES['spot_disk']} which matches {LIGHT_TYPES['spot_disk']}", - f"light_entity_test: Property value is {LIGHT_TYPES['capsule']} which matches {LIGHT_TYPES['capsule']}", - f"light_entity_test: Property value is {LIGHT_TYPES['quad']} which matches {LIGHT_TYPES['quad']}", - "light_entity Controller|Configuration|Fast approximation: SUCCESS", - "light_entity Controller|Configuration|Both directions: SUCCESS", - f"light_entity_test: Property value is {LIGHT_TYPES['polygon']} which matches {LIGHT_TYPES['polygon']}", - f"light_entity_test: Property value is {LIGHT_TYPES['simple_point']} " - f"which matches {LIGHT_TYPES['simple_point']}", - "Controller|Configuration|Attenuation radius|Mode set to 0", - "Controller|Configuration|Attenuation radius|Radius set to 100.0", - f"light_entity_test: Property value is {LIGHT_TYPES['simple_spot']} " - f"which matches {LIGHT_TYPES['simple_spot']}", - "Controller|Configuration|Shutters|Outer angle set to 45.0", - "Controller|Configuration|Shutters|Outer angle set to 90.0", - "light_entity_test: Component added to the entity: True", - "Light component test (non-GPU) completed.", - ] + @pytest.mark.test_case_id("C36525660") + class AtomEditorComponents_DisplayMapperAdded(EditorSharedTest): + from Atom.tests import hydra_AtomEditorComponents_DisplayMapperAdded as test_module - unexpected_lines = [ - "Trace::Assert", - "Trace::Error", - "Traceback (most recent call last):", - ] + @pytest.mark.test_case_id("C32078121") + class AtomEditorComponents_ExposureControlAdded(EditorSharedTest): + from Atom.tests import hydra_AtomEditorComponents_ExposureControlAdded as test_module - hydra.launch_and_validate_results( - request, - TEST_DIRECTORY, - editor, - "hydra_AtomEditorComponents_LightComponent.py", - timeout=120, - expected_lines=expected_lines, - unexpected_lines=unexpected_lines, - halt_on_unexpected=True, - null_renderer=True, - cfg_args=cfg_args, - ) + @pytest.mark.test_case_id("C32078115") + class AtomEditorComponents_GlobalSkylightIBLAdded(EditorSharedTest): + from Atom.tests import hydra_AtomEditorComponents_GlobalSkylightIBLAdded as test_module + + @pytest.mark.test_case_id("C32078122") + class AtomEditorComponents_GridAdded(EditorSharedTest): + from Atom.tests import hydra_AtomEditorComponents_GridAdded as test_module + @pytest.mark.test_case_id("C36525671") + class AtomEditorComponents_HDRColorGradingAdded(EditorSharedTest): + from Atom.tests import hydra_AtomEditorComponents_HDRColorGradingAdded as test_module + @pytest.mark.test_case_id("C32078117") + class AtomEditorComponents_LightAdded(EditorSharedTest): + from Atom.tests import hydra_AtomEditorComponents_LightAdded as test_module + + @pytest.mark.test_case_id("C32078123") + class AtomEditorComponents_MaterialAdded(EditorSharedTest): + from Atom.tests import hydra_AtomEditorComponents_MaterialAdded as test_module + + @pytest.mark.test_case_id("C32078124") + class AtomEditorComponents_MeshAdded(EditorSharedTest): + from Atom.tests import hydra_AtomEditorComponents_MeshAdded as test_module + + @pytest.mark.test_case_id("C36525663") + class AtomEditorComponents_OcclusionCullingPlaneAdded(EditorSharedTest): + from Atom.tests import hydra_AtomEditorComponents_OcclusionCullingPlaneAdded as test_module + + @pytest.mark.test_case_id("C32078125") + class AtomEditorComponents_PhysicalSkyAdded(EditorSharedTest): + from Atom.tests import hydra_AtomEditorComponents_PhysicalSkyAdded as test_module + + @pytest.mark.test_case_id("C36525664") + class AtomEditorComponents_PostFXGradientWeightModifierAdded(EditorSharedTest): + from Atom.tests import hydra_AtomEditorComponents_PostFXGradientWeightModifierAdded as test_module + + @pytest.mark.test_case_id("C32078127") + class AtomEditorComponents_PostFXLayerAdded(EditorSharedTest): + from Atom.tests import hydra_AtomEditorComponents_PostFXLayerAdded as test_module + + @pytest.mark.test_case_id("C32078131") + class AtomEditorComponents_PostFXRadiusWeightModifierAdded(EditorSharedTest): + from Atom.tests import ( + hydra_AtomEditorComponents_PostFXRadiusWeightModifierAdded as test_module) + + @pytest.mark.test_case_id("C36525665") + class AtomEditorComponents_PostFXShapeWeightModifierAdded(EditorSharedTest): + from Atom.tests import hydra_AtomEditorComponents_PostFxShapeWeightModifierAdded as test_module + + @pytest.mark.test_case_id("C32078128") + class AtomEditorComponents_ReflectionProbeAdded(EditorSharedTest): + from Atom.tests import hydra_AtomEditorComponents_ReflectionProbeAdded as test_module + + class ShaderAssetBuilder_RecompilesShaderAsChainOfDependenciesChanges(EditorSharedTest): + from Atom.tests import hydra_ShaderAssetBuilder_RecompilesShaderAsChainOfDependenciesChanges as test_module diff --git a/AutomatedTesting/Gem/PythonTests/Atom/TestSuite_Main_GPU.py b/AutomatedTesting/Gem/PythonTests/Atom/TestSuite_Main_GPU.py index 23fb249761..f0e92c7e3e 100644 --- a/AutomatedTesting/Gem/PythonTests/Atom/TestSuite_Main_GPU.py +++ b/AutomatedTesting/Gem/PythonTests/Atom/TestSuite_Main_GPU.py @@ -13,10 +13,10 @@ import zipfile import pytest import ly_test_tools.environment.file_system as file_system -from ly_test_tools.image.screenshot_compare_qssim import qssim as compare_screenshots from ly_test_tools.benchmark.data_aggregator import BenchmarkDataAggregator import editor_python_test_tools.hydra_test_utils as hydra +from .atom_utils.atom_component_helper import compare_screenshot_similarity, ImageComparisonTestFailure logger = logging.getLogger(__name__) DEFAULT_SUBFOLDER_PATH = 'user/PythonTests/Automated/Screenshots' @@ -69,7 +69,7 @@ def create_screenshots_archive(screenshot_path): @pytest.mark.parametrize("project", ["AutomatedTesting"]) @pytest.mark.parametrize("launcher_platform", ["windows_editor"]) -@pytest.mark.parametrize("level", ["auto_test"]) +@pytest.mark.parametrize("level", ["Base"]) class TestAllComponentsIndepthTests(object): @pytest.mark.parametrize("screenshot_name", ["AtomBasicLevelSetup.ppm"]) @@ -91,12 +91,7 @@ class TestAllComponentsIndepthTests(object): "Viewport is set to the expected size: True", "Exited game mode" ] - unexpected_lines = [ - "Trace::Assert", - "Trace::Error", - "Traceback (most recent call last):", - "Screenshot failed" - ] + unexpected_lines = ["Traceback (most recent call last):"] hydra.launch_and_validate_results( request, @@ -111,10 +106,12 @@ class TestAllComponentsIndepthTests(object): null_renderer=False, ) - for test_screenshot, golden_screenshot in zip(test_screenshots, golden_images): - compare_screenshots(test_screenshot, golden_screenshot) - - create_screenshots_archive(screenshot_directory) + similarity_threshold = 0.99 + for test_screenshot, golden_image in zip(test_screenshots, golden_images): + screenshot_comparison_result = compare_screenshot_similarity( + test_screenshot, golden_image, similarity_threshold, True, screenshot_directory) + if screenshot_comparison_result != "Screenshots match": + raise Exception(f"Screenshot test failed: {screenshot_comparison_result}") @pytest.mark.test_case_id("C34525095") def test_LightComponent_ScreenshotMatchesGoldenImage( @@ -149,12 +146,7 @@ class TestAllComponentsIndepthTests(object): golden_images.append(golden_image_path) expected_lines = ["spot_light Controller|Configuration|Shadows|Shadowmap size: SUCCESS"] - unexpected_lines = [ - "Trace::Assert", - "Trace::Error", - "Traceback (most recent call last):", - "Screenshot failed", - ] + unexpected_lines = ["Traceback (most recent call last):"] hydra.launch_and_validate_results( request, TEST_DIRECTORY, @@ -168,10 +160,12 @@ class TestAllComponentsIndepthTests(object): null_renderer=False, ) - for test_screenshot, golden_screenshot in zip(test_screenshots, golden_images): - compare_screenshots(test_screenshot, golden_screenshot) - - create_screenshots_archive(screenshot_directory) + similarity_threshold = 0.99 + for test_screenshot, golden_image in zip(test_screenshots, golden_images): + screenshot_comparison_result = compare_screenshot_similarity( + test_screenshot, golden_image, similarity_threshold, True, screenshot_directory) + if screenshot_comparison_result != "Screenshots match": + raise ImageComparisonTestFailure(f"Screenshot test failed: {screenshot_comparison_result}") @pytest.mark.parametrize('rhi', ['dx12', 'vulkan']) @@ -219,7 +213,6 @@ class TestPerformanceBenchmarkSuite(object): @pytest.mark.parametrize("project", ["AutomatedTesting"]) @pytest.mark.parametrize("launcher_platform", ['windows_generic']) -@pytest.mark.system class TestMaterialEditor(object): @pytest.mark.parametrize("cfg_args,expected_lines", [ diff --git a/AutomatedTesting/Gem/PythonTests/Atom/TestSuite_Main_Optimized.py b/AutomatedTesting/Gem/PythonTests/Atom/TestSuite_Main_Optimized.py deleted file mode 100644 index c29a391be4..0000000000 --- a/AutomatedTesting/Gem/PythonTests/Atom/TestSuite_Main_Optimized.py +++ /dev/null @@ -1,79 +0,0 @@ -""" -Copyright (c) Contributors to the Open 3D Engine Project. -For complete copyright and license terms please see the LICENSE at the root of this distribution. - -SPDX-License-Identifier: Apache-2.0 OR MIT -""" -import pytest - -from ly_test_tools.o3de.editor_test import EditorSharedTest, EditorTestSuite - - -@pytest.mark.xfail(reason="Optimized tests are experimental, we will enable xfail and monitor them temporarily.") -@pytest.mark.parametrize("project", ["AutomatedTesting"]) -@pytest.mark.parametrize("launcher_platform", ['windows_editor']) -class TestAutomation(EditorTestSuite): - - @pytest.mark.test_case_id("C32078118") - class AtomEditorComponents_DecalAdded(EditorSharedTest): - from Atom.tests import hydra_AtomEditorComponents_DecalAdded as test_module - - @pytest.mark.test_case_id("C32078119") - class AtomEditorComponents_DepthOfFieldAdded(EditorSharedTest): - from Atom.tests import hydra_AtomEditorComponents_DepthOfFieldAdded as test_module - - @pytest.mark.test_case_id("C32078120") - class AtomEditorComponents_DirectionalLightAdded(EditorSharedTest): - from Atom.tests import hydra_AtomEditorComponents_DirectionalLightAdded as test_module - - @pytest.mark.test_case_id("C32078121") - class AtomEditorComponents_ExposureControlAdded(EditorSharedTest): - from Atom.tests import hydra_AtomEditorComponents_ExposureControlAdded as test_module - - @pytest.mark.test_case_id("C32078115") - class AtomEditorComponents_GlobalSkylightIBLAdded(EditorSharedTest): - from Atom.tests import hydra_AtomEditorComponents_GlobalSkylightIBLAdded as test_module - - @pytest.mark.test_case_id("C32078125") - class AtomEditorComponents_PhysicalSkyAdded(EditorSharedTest): - from Atom.tests import hydra_AtomEditorComponents_PhysicalSkyAdded as test_module - - @pytest.mark.test_case_id("C32078131") - class AtomEditorComponents_PostFXRadiusWeightModifierAdded(EditorSharedTest): - from Atom.tests import ( - hydra_AtomEditorComponents_PostFXRadiusWeightModifierAdded as test_module) - - @pytest.mark.test_case_id("C32078117") - class AtomEditorComponents_LightAdded(EditorSharedTest): - from Atom.tests import hydra_AtomEditorComponents_LightAdded as test_module - - @pytest.mark.test_case_id("C36525660") - class AtomEditorComponents_DisplayMapperAdded(EditorSharedTest): - from Atom.tests import hydra_AtomEditorComponents_DisplayMapperAdded as test_module - - @pytest.mark.test_case_id("C32078128") - class AtomEditorComponents_ReflectionProbeAdded(EditorSharedTest): - from Atom.tests import hydra_AtomEditorComponents_ReflectionProbeAdded as test_module - - @pytest.mark.test_case_id("C32078124") - class AtomEditorComponents_MeshAdded(EditorSharedTest): - from Atom.tests import hydra_AtomEditorComponents_MeshAdded as test_module - - @pytest.mark.test_case_id("C32078123") - class AtomEditorComponents_MaterialAdded(EditorSharedTest): - from Atom.tests import hydra_AtomEditorComponents_MaterialAdded as test_module - - @pytest.mark.test_case_id("C32078127") - class AtomEditorComponents_PostFXLayerAdded(EditorSharedTest): - from Atom.tests import hydra_AtomEditorComponents_PostFXLayerAdded as test_module - - @pytest.mark.test_case_id("C36525665") - class AtomEditorComponents_PostFXShapeWeightModifierAdded(EditorSharedTest): - from Atom.tests import hydra_AtomEditorComponents_PostFxShapeWeightModifierAdded as test_module - - @pytest.mark.test_case_id("C36525664") - class AtomEditorComponents_PostFXGradientWeightModifierAdded(EditorSharedTest): - from Atom.tests import hydra_AtomEditorComponents_PostFXGradientWeightModifierAdded as test_module - - class ShaderAssetBuilder_RecompilesShaderAsChainOfDependenciesChanges(EditorSharedTest): - from Atom.tests import hydra_ShaderAssetBuilder_RecompilesShaderAsChainOfDependenciesChanges as test_module diff --git a/AutomatedTesting/Gem/PythonTests/Atom/TestSuite_Sandbox.py b/AutomatedTesting/Gem/PythonTests/Atom/TestSuite_Sandbox.py index ad45e51080..9ceb3c951f 100644 --- a/AutomatedTesting/Gem/PythonTests/Atom/TestSuite_Sandbox.py +++ b/AutomatedTesting/Gem/PythonTests/Atom/TestSuite_Sandbox.py @@ -9,63 +9,84 @@ import os import pytest +import ly_test_tools.environment.file_system as file_system import editor_python_test_tools.hydra_test_utils as hydra +from Atom.atom_utils.atom_constants import LIGHT_TYPES + logger = logging.getLogger(__name__) TEST_DIRECTORY = os.path.join(os.path.dirname(__file__), "tests") + class TestAtomEditorComponentsSandbox(object): # It requires at least one test def test_Dummy(self, request, editor, level, workspace, project, launcher_platform): pass - @pytest.mark.parametrize("project", ["AutomatedTesting"]) - @pytest.mark.parametrize("launcher_platform", ['windows_editor']) - @pytest.mark.parametrize("level", ["auto_test"]) - class TestAtomEditorComponentsMain(object): - """Holds tests for Atom components.""" - @pytest.mark.test_case_id("C32078128") - def test_AtomEditorComponents_ReflectionProbeAddedToEntity( - self, request, editor, level, workspace, project, launcher_platform): - """ - Please review the hydra script run by this test for more specific test info. - Tests the following Atom components and verifies all "expected_lines" appear in Editor.log: - 1. Reflection Probe - """ - cfg_args = [level] +@pytest.mark.parametrize("project", ["AutomatedTesting"]) +@pytest.mark.parametrize("launcher_platform", ['windows_editor']) +@pytest.mark.parametrize("level", ["auto_test"]) +class TestAtomEditorComponentsMain(object): + """Holds tests for Atom components.""" - expected_lines = [ - # Reflection Probe Component - "Reflection Probe Entity successfully created", - "Reflection Probe_test: Component added to the entity: True", - "Reflection Probe_test: Component removed after UNDO: True", - "Reflection Probe_test: Component added after REDO: True", - "Reflection Probe_test: Entered game mode: True", - "Reflection Probe_test: Exit game mode: True", - "Reflection Probe_test: Entity disabled initially: True", - "Reflection Probe_test: Entity enabled after adding required components: True", - "Reflection Probe_test: Cubemap is generated: True", - "Reflection Probe_test: Entity is hidden: True", - "Reflection Probe_test: Entity is shown: True", - "Reflection Probe_test: Entity deleted: True", - "Reflection Probe_test: UNDO entity deletion works: True", - "Reflection Probe_test: REDO entity deletion works: True", - ] + @pytest.mark.test_case_id("C34525095") + def test_AtomEditorComponents_LightComponent( + self, request, editor, workspace, project, launcher_platform, level): + """ + Please review the hydra script run by this test for more specific test info. + Tests that the Light component has the expected property options available to it. + """ + cfg_args = [level] + + expected_lines = [ + "light_entity Entity successfully created", + "Entity has a Light component", + "light_entity_test: Component added to the entity: True", + f"light_entity_test: Property value is {LIGHT_TYPES['sphere']} which matches {LIGHT_TYPES['sphere']}", + "Controller|Configuration|Shadows|Enable shadow set to True", + "light_entity Controller|Configuration|Shadows|Shadowmap size: SUCCESS", + "Controller|Configuration|Shadows|Shadow filter method set to 1", # PCF + "Controller|Configuration|Shadows|Filtering sample count set to 4", + "Controller|Configuration|Shadows|Filtering sample count set to 64", + "Controller|Configuration|Shadows|Shadow filter method set to 2", # ESM + "Controller|Configuration|Shadows|ESM exponent set to 50.0", + "Controller|Configuration|Shadows|ESM exponent set to 5000.0", + "Controller|Configuration|Shadows|Shadow filter method set to 3", # ESM+PCF + f"light_entity_test: Property value is {LIGHT_TYPES['spot_disk']} which matches {LIGHT_TYPES['spot_disk']}", + f"light_entity_test: Property value is {LIGHT_TYPES['capsule']} which matches {LIGHT_TYPES['capsule']}", + f"light_entity_test: Property value is {LIGHT_TYPES['quad']} which matches {LIGHT_TYPES['quad']}", + "light_entity Controller|Configuration|Fast approximation: SUCCESS", + "light_entity Controller|Configuration|Both directions: SUCCESS", + f"light_entity_test: Property value is {LIGHT_TYPES['polygon']} which matches {LIGHT_TYPES['polygon']}", + f"light_entity_test: Property value is {LIGHT_TYPES['simple_point']} " + f"which matches {LIGHT_TYPES['simple_point']}", + "Controller|Configuration|Attenuation radius|Mode set to 0", + "Controller|Configuration|Attenuation radius|Radius set to 100.0", + f"light_entity_test: Property value is {LIGHT_TYPES['simple_spot']} " + f"which matches {LIGHT_TYPES['simple_spot']}", + "Controller|Configuration|Shutters|Outer angle set to 45.0", + "Controller|Configuration|Shutters|Outer angle set to 90.0", + "light_entity_test: Component added to the entity: True", + "Light component test (non-GPU) completed.", + ] + + unexpected_lines = ["Traceback (most recent call last):"] + + hydra.launch_and_validate_results( + request, + TEST_DIRECTORY, + editor, + "hydra_AtomEditorComponents_LightComponent.py", + timeout=120, + expected_lines=expected_lines, + unexpected_lines=unexpected_lines, + halt_on_unexpected=True, + null_renderer=True, + cfg_args=cfg_args, + ) - hydra.launch_and_validate_results( - request, - TEST_DIRECTORY, - editor, - "hydra_AtomEditorComponents_AddedToEntity.py", - timeout=120, - expected_lines=expected_lines, - unexpected_lines=[], - halt_on_unexpected=True, - null_renderer=True, - cfg_args=cfg_args, - ) @pytest.mark.parametrize("project", ["AutomatedTesting"]) @pytest.mark.parametrize("launcher_platform", ['windows_generic']) @@ -119,8 +140,6 @@ class TestMaterialEditorBasicTests(object): "Save All worked as expected: True", ] unexpected_lines = [ - # "Trace::Assert", - # "Trace::Error", "Traceback (most recent call last):" ] diff --git a/AutomatedTesting/Gem/PythonTests/Atom/atom_utils/atom_component_helper.py b/AutomatedTesting/Gem/PythonTests/Atom/atom_utils/atom_component_helper.py index 11832f8846..50cc15f7e8 100644 --- a/AutomatedTesting/Gem/PythonTests/Atom/atom_utils/atom_component_helper.py +++ b/AutomatedTesting/Gem/PythonTests/Atom/atom_utils/atom_component_helper.py @@ -1,5 +1,6 @@ """ -Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution. +Copyright (c) Contributors to the Open 3D Engine Project. +For complete copyright and license terms please see the LICENSE at the root of this distribution. SPDX-License-Identifier: Apache-2.0 OR MIT @@ -8,12 +9,19 @@ import datetime import os import zipfile +from ly_test_tools.image.screenshot_compare_qssim import qssim as compare_screenshots + + +class ImageComparisonTestFailure(Exception): + """Custom test failure for failed image comparisons.""" + pass + def create_screenshots_archive(screenshot_path): """ Creates a new zip file archive at archive_path containing all files listed within archive_path. :param screenshot_path: location containing the files to archive, the zip archive file will also be saved here. - :return: None, but creates a new zip file archive inside path containing all of the files inside archive_path. + :return: path to the created .zip file archive. """ files_to_archive = [] @@ -27,14 +35,16 @@ def create_screenshots_archive(screenshot_path): # Setup variables for naming the zip archive file. timestamp = datetime.datetime.now().timestamp() formatted_timestamp = datetime.datetime.utcfromtimestamp(timestamp).strftime("%Y-%m-%d_%H-%M-%S") - screenshots_file = os.path.join(screenshot_path, f'screenshots_{formatted_timestamp}.zip') + screenshots_zip_file = os.path.join(screenshot_path, f'screenshots_{formatted_timestamp}.zip') # Write all of the valid .png and .ppm files to the archive file. - with zipfile.ZipFile(screenshots_file, 'w', compression=zipfile.ZIP_DEFLATED, allowZip64=True) as zip_archive: + with zipfile.ZipFile(screenshots_zip_file, 'w', compression=zipfile.ZIP_DEFLATED, allowZip64=True) as zip_archive: for file_path in files_to_archive: file_name = os.path.basename(file_path) zip_archive.write(file_path, file_name) + return screenshots_zip_file + def golden_images_directory(): """ @@ -53,6 +63,36 @@ def golden_images_directory(): return golden_images_dir +def compare_screenshot_similarity( + test_screenshot, golden_image, similarity_threshold, create_zip_archive=False, screenshot_directory=""): + """ + Compares the similarity between a test screenshot and a golden image. + It returns a "Screenshots match" string if the comparison mean value is higher than the similarity threshold. + Otherwise, it returns an error string. + :param test_screenshot: path to the test screenshot to compare. + :param golden_image: path to the golden image to compare. + :param similarity_threshold: value for the comparison mean value to be asserted against. + :param create_zip_archive: toggle to create a zip archive containing the screenshots if the assert check fails. + :param screenshot_directory: directory containing screenshots to create zip archive from. + :return: Error string if compared mean value < similarity threshold or screenshot_directory is missing for .zip, + otherwise it returns a "Screenshots match" string. + """ + result = "Screenshots match" + if create_zip_archive and not screenshot_directory: + result = 'You must specify a screenshot_directory in order to create a zip archive.\n' + + mean_similarity = compare_screenshots(test_screenshot, golden_image) + if not mean_similarity > similarity_threshold: + if create_zip_archive: + create_screenshots_archive(screenshot_directory) + result = ( + f"When comparing the test_screenshot: '{test_screenshot}' " + f"to golden_image: '{golden_image}' the mean similarity of '{mean_similarity}' " + f"was lower than the similarity threshold of '{similarity_threshold}'. ") + + return result + + def create_basic_atom_level(level_name): """ Creates a new level inside the Editor matching level_name & adds the following: @@ -76,29 +116,11 @@ def create_basic_atom_level(level_name): helper = EditorTestHelper(log_prefix="Atom_EditorTestHelper") - # Create a new level. - new_level_name = level_name - heightmap_resolution = 512 - heightmap_meters_per_pixel = 1 - terrain_texture_resolution = 412 - use_terrain = False - - # Return codes are ECreateLevelResult defined in CryEdit.h - return_code = general.create_level_no_prompt( - new_level_name, heightmap_resolution, heightmap_meters_per_pixel, terrain_texture_resolution, use_terrain) - if return_code == 1: - general.log(f"{new_level_name} level already exists") - elif return_code == 2: - general.log("Failed to create directory") - elif return_code == 3: - general.log("Directory length is too long") - elif return_code != 0: - general.log("Unknown error, failed to create level") - else: - general.log(f"{new_level_name} level created successfully") - - # Enable idle and update viewport. + # Wait for Editor idle loop before executing Python hydra scripts. general.idle_enable(True) + + # Basic setup for opened level. + helper.open_level(level_name="Base") general.idle_wait(1.0) general.update_viewport() general.idle_wait(0.5) # half a second is more than enough for updating the viewport. @@ -165,24 +187,25 @@ def create_basic_atom_level(level_name): components=["Material"], parent_id=default_level.id) azlmbr.components.TransformBus(azlmbr.bus.Event, "SetLocalUniformScale", ground_plane.id, 32.0) - ground_plane_material_asset_path = os.path.join( - "Materials", "Presets", "PBR", "metal_chrome.azmaterial") - ground_plane_material_asset_value = asset.AssetCatalogRequestBus( - bus.Broadcast, "GetAssetIdByPath", ground_plane_material_asset_path, math.Uuid(), False) - ground_plane.get_set_test(0, "Default Material|Material Asset", ground_plane_material_asset_value) - # Work around to add the correct Atom Mesh component + # Work around to add the correct Atom Mesh component and asset. mesh_type_id = azlmbr.globals.property.EditorMeshComponentTypeId ground_plane.components.append( editor.EditorComponentAPIBus( bus.Broadcast, "AddComponentsOfType", ground_plane.id, [mesh_type_id] ).GetValue()[0] ) - ground_plane_mesh_asset_path = os.path.join("Models", "plane.azmodel") + ground_plane_mesh_asset_path = os.path.join("TestData", "Objects", "plane.azmodel") ground_plane_mesh_asset_value = asset.AssetCatalogRequestBus( bus.Broadcast, "GetAssetIdByPath", ground_plane_mesh_asset_path, math.Uuid(), False) ground_plane.get_set_test(1, "Controller|Configuration|Mesh Asset", ground_plane_mesh_asset_value) + # Add Atom Material component and asset. + ground_plane_material_asset_path = os.path.join("Materials", "Presets", "PBR", "metal_chrome.azmaterial") + ground_plane_material_asset_value = asset.AssetCatalogRequestBus( + bus.Broadcast, "GetAssetIdByPath", ground_plane_material_asset_path, math.Uuid(), False) + ground_plane.get_set_test(0, "Default Material|Material Asset", ground_plane_material_asset_value) + # Create directional_light entity and set the properties directional_light = hydra.Entity("directional_light") directional_light.create_entity( @@ -199,12 +222,8 @@ def create_basic_atom_level(level_name): entity_position=math.Vector3(0.0, 0.0, 1.0), components=["Material"], parent_id=default_level.id) - sphere_material_asset_path = os.path.join("Materials", "Presets", "PBR", "metal_brass_polished.azmaterial") - sphere_material_asset_value = asset.AssetCatalogRequestBus( - bus.Broadcast, "GetAssetIdByPath", sphere_material_asset_path, math.Uuid(), False) - sphere_entity.get_set_test(0, "Default Material|Material Asset", sphere_material_asset_value) - # Work around to add the correct Atom Mesh component + # Work around to add the correct Atom Mesh component and asset. sphere_entity.components.append( editor.EditorComponentAPIBus( bus.Broadcast, "AddComponentsOfType", sphere_entity.id, [mesh_type_id] @@ -215,6 +234,12 @@ def create_basic_atom_level(level_name): bus.Broadcast, "GetAssetIdByPath", sphere_mesh_asset_path, math.Uuid(), False) sphere_entity.get_set_test(1, "Controller|Configuration|Mesh Asset", sphere_mesh_asset_value) + # Add Atom Material component and asset. + sphere_material_asset_path = os.path.join("Materials", "Presets", "PBR", "metal_brass_polished.azmaterial") + sphere_material_asset_value = asset.AssetCatalogRequestBus( + bus.Broadcast, "GetAssetIdByPath", sphere_material_asset_path, math.Uuid(), False) + sphere_entity.get_set_test(0, "Default Material|Material Asset", sphere_material_asset_value) + # Create camera component and set the properties camera_entity = hydra.Entity("camera") camera_entity.create_entity( diff --git a/AutomatedTesting/Gem/PythonTests/Atom/atom_utils/atom_constants.py b/AutomatedTesting/Gem/PythonTests/Atom/atom_utils/atom_constants.py index 344a0dbd29..70156b9375 100644 --- a/AutomatedTesting/Gem/PythonTests/Atom/atom_utils/atom_constants.py +++ b/AutomatedTesting/Gem/PythonTests/Atom/atom_utils/atom_constants.py @@ -42,12 +42,14 @@ class AtomComponentProperties: Bloom component properties. Requires PostFX Layer component. - 'requires' a list of component names as strings required by this component. Use editor_entity_utils EditorEntity.add_components(list) to add this list of requirements.\n + - 'Enable Bloom' Toggle active state of the component True/False :param property: From the last element of the property tree path. Default 'name' for component name string. :return: Full property path OR component name if no property specified. """ properties = { 'name': 'Bloom', 'requires': [AtomComponentProperties.postfx_layer()], + 'Enable Bloom': 'Controller|Configuration|Enable Bloom', } return properties[property] @@ -83,12 +85,14 @@ class AtomComponentProperties: Deferred Fog component properties. Requires PostFX Layer component. - 'requires' a list of component names as strings required by this component. Use editor_entity_utils EditorEntity.add_components(list) to add this list of requirements.\n + - 'Enable Deferred Fog' Toggle active state of the component True/False :param property: From the last element of the property tree path. Default 'name' for component name string. :return: Full property path OR component name if no property specified. """ properties = { 'name': 'Deferred Fog', 'requires': [AtomComponentProperties.postfx_layer()], + 'Enable Deferred Fog': 'Controller|Configuration|Enable Deferred Fog', } return properties[property] @@ -212,12 +216,14 @@ class AtomComponentProperties: HDR Color Grading component properties. Requires PostFX Layer component. - 'requires' a list of component names as strings required by this component. Use editor_entity_utils EditorEntity.add_components(list) to add this list of requirements.\n + - 'Enable HDR color grading' Toggle active state of the component True/False :param property: From the last element of the property tree path. Default 'name' for component name string. :return: Full property path OR component name if no property specified. """ properties = { 'name': 'HDR Color Grading', 'requires': [AtomComponentProperties.postfx_layer()], + 'Enable HDR color grading': 'Controller|Configuration|Enable HDR color grading', } return properties[property] diff --git a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_AddedToEntity.py b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_AddedToEntity.py deleted file mode 100644 index bbc8463152..0000000000 --- a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_AddedToEntity.py +++ /dev/null @@ -1,238 +0,0 @@ -""" -Copyright (c) Contributors to the Open 3D Engine Project. -For complete copyright and license terms please see the LICENSE at the root of this distribution. - -SPDX-License-Identifier: Apache-2.0 OR MIT -""" - -import os -import sys - -import azlmbr.math as math -import azlmbr.bus as bus -import azlmbr.paths -import azlmbr.asset as asset -import azlmbr.entity as entity -import azlmbr.legacy.general as general -import azlmbr.editor as editor -import azlmbr.render as render - -sys.path.append(os.path.join(azlmbr.paths.projectroot, "Gem", "PythonTests")) - -import editor_python_test_tools.hydra_editor_utils as hydra -from editor_python_test_tools.utils import TestHelper - - -def run(): - """ - Summary: - The below common tests are done for each of the components. - 1) Addition of component to the entity - 2) UNDO/REDO of addition of component - 3) Enter/Exit game mode - 4) Hide/Show entity containing component - 5) Deletion of component - 6) UNDO/REDO of deletion of component - Some additional tests for specific components include - 1) Assigning value to some properties of each component - 2) Verifying if the component is activated only when the required components are added - - Expected Result: - 1) Component can be added to an entity. - 2) The addition of component can be undone and redone. - 3) Game mode can be entered/exited without issue. - 4) Entity with component can be hidden/shown. - 5) Component can be deleted. - 6) The deletion of component can be undone and redone. - 7) Component is activated only when the required components are added - 8) Values can be assigned to the properties of the component - - :return: None - """ - - def create_entity_undo_redo_component_addition(component_name): - new_entity = hydra.Entity(f"{component_name}") - new_entity.create_entity(math.Vector3(512.0, 512.0, 34.0), [component_name]) - general.log(f"{component_name}_test: Component added to the entity: " - f"{hydra.has_components(new_entity.id, [component_name])}") - - # undo component addition - general.undo() - TestHelper.wait_for_condition(lambda: not hydra.has_components(new_entity.id, [component_name]), 2.0) - general.log(f"{component_name}_test: Component removed after UNDO: " - f"{not hydra.has_components(new_entity.id, [component_name])}") - - # redo component addition - general.redo() - TestHelper.wait_for_condition(lambda: hydra.has_components(new_entity.id, [component_name]), 2.0) - general.log(f"{component_name}_test: Component added after REDO: " - f"{hydra.has_components(new_entity.id, [component_name])}") - - return new_entity - - def verify_enter_exit_game_mode(component_name): - general.enter_game_mode() - TestHelper.wait_for_condition(lambda: general.is_in_game_mode(), 2.0) - general.log(f"{component_name}_test: Entered game mode: {general.is_in_game_mode()}") - general.exit_game_mode() - TestHelper.wait_for_condition(lambda: not general.is_in_game_mode(), 2.0) - general.log(f"{component_name}_test: Exit game mode: {not general.is_in_game_mode()}") - - def verify_hide_unhide_entity(component_name, entity_obj): - - def is_entity_hidden(entity_id): - return editor.EditorEntityInfoRequestBus(bus.Event, "IsHidden", entity_id) - - editor.EditorEntityAPIBus(bus.Event, "SetVisibilityState", entity_obj.id, False) - general.idle_wait_frames(1) - general.log(f"{component_name}_test: Entity is hidden: {is_entity_hidden(entity_obj.id)}") - editor.EditorEntityAPIBus(bus.Event, "SetVisibilityState", entity_obj.id, True) - general.idle_wait_frames(1) - general.log(f"{component_name}_test: Entity is shown: {not is_entity_hidden(entity_obj.id)}") - - def verify_deletion_undo_redo(component_name, entity_obj): - editor.ToolsApplicationRequestBus(bus.Broadcast, "DeleteEntityById", entity_obj.id) - TestHelper.wait_for_condition(lambda: not hydra.find_entity_by_name(entity_obj.name), 2.0) - general.log(f"{component_name}_test: Entity deleted: {not hydra.find_entity_by_name(entity_obj.name)}") - - general.undo() - TestHelper.wait_for_condition(lambda: hydra.find_entity_by_name(entity_obj.name) is not None, 2.0) - general.log(f"{component_name}_test: UNDO entity deletion works: " - f"{hydra.find_entity_by_name(entity_obj.name) is not None}") - - general.redo() - TestHelper.wait_for_condition(lambda: not hydra.find_entity_by_name(entity_obj.name), 2.0) - general.log(f"{component_name}_test: REDO entity deletion works: " - f"{not hydra.find_entity_by_name(entity_obj.name)}") - - def verify_required_component_addition(entity_obj, components_to_add, component_name): - - def is_component_enabled(entity_componentid_pair): - return editor.EditorComponentAPIBus(bus.Broadcast, "IsComponentEnabled", entity_componentid_pair) - - general.log( - f"{component_name}_test: Entity disabled initially: " - f"{not is_component_enabled(entity_obj.components[0])}") - for component in components_to_add: - entity_obj.add_component(component) - TestHelper.wait_for_condition(lambda: is_component_enabled(entity_obj.components[0]), 2.0) - general.log( - f"{component_name}_test: Entity enabled after adding " - f"required components: {is_component_enabled(entity_obj.components[0])}" - ) - - def verify_set_property(entity_obj, path, value): - entity_obj.get_set_test(0, path, value) - - # Verify cubemap generation - def verify_cubemap_generation(component_name, entity_obj): - # Initially Check if the component has Reflection Probe component - if not hydra.has_components(entity_obj.id, ["Reflection Probe"]): - raise ValueError(f"Given entity {entity_obj.name} has no Reflection Probe component") - render.EditorReflectionProbeBus(azlmbr.bus.Event, "BakeReflectionProbe", entity_obj.id) - - def get_value(): - hydra.get_component_property_value(entity_obj.components[0], "Cubemap|Baked Cubemap Path") - - TestHelper.wait_for_condition(lambda: get_value() != "", 20.0) - general.log(f"{component_name}_test: Cubemap is generated: {get_value() != ''}") - - # Wait for Editor idle loop before executing Python hydra scripts. - TestHelper.init_idle() - - # Delete all existing entities initially - search_filter = azlmbr.entity.SearchFilter() - all_entities = entity.SearchBus(azlmbr.bus.Broadcast, "SearchEntities", search_filter) - editor.ToolsApplicationRequestBus(bus.Broadcast, "DeleteEntities", all_entities) - - class ComponentTests: - """Test launcher for each component.""" - def __init__(self, component_name, *additional_tests): - self.component_name = component_name - self.additional_tests = additional_tests - self.run_component_tests() - - def run_component_tests(self): - # Run common and additional tests - entity_obj = create_entity_undo_redo_component_addition(self.component_name) - - # Enter/Exit game mode test - verify_enter_exit_game_mode(self.component_name) - - # Any additional tests are executed here - for test in self.additional_tests: - test(entity_obj) - - # Hide/Unhide entity test - verify_hide_unhide_entity(self.component_name, entity_obj) - - # Deletion/Undo/Redo test - verify_deletion_undo_redo(self.component_name, entity_obj) - - # DepthOfField Component - camera_entity = hydra.Entity("camera_entity") - camera_entity.create_entity(math.Vector3(512.0, 512.0, 34.0), ["Camera"]) - depth_of_field = "DepthOfField" - ComponentTests( - depth_of_field, - lambda entity_obj: verify_required_component_addition(entity_obj, ["PostFX Layer"], depth_of_field), - lambda entity_obj: verify_set_property( - entity_obj, "Controller|Configuration|Camera Entity", camera_entity.id)) - - # Decal Component - material_asset_path = os.path.join("AutomatedTesting", "Materials", "basic_grey.material") - material_asset = asset.AssetCatalogRequestBus( - bus.Broadcast, "GetAssetIdByPath", material_asset_path, math.Uuid(), False) - ComponentTests( - "Decal", lambda entity_obj: verify_set_property( - entity_obj, "Controller|Configuration|Material", material_asset)) - - # Directional Light Component - ComponentTests( - "Directional Light", - lambda entity_obj: verify_set_property( - entity_obj, "Controller|Configuration|Shadow|Camera", camera_entity.id)) - - # Exposure Control Component - ComponentTests( - "Exposure Control", lambda entity_obj: verify_required_component_addition( - entity_obj, ["PostFX Layer"], "Exposure Control")) - - # Global Skylight (IBL) Component - diffuse_image_path = os.path.join("LightingPresets", "greenwich_park_02_4k_iblskyboxcm.exr.streamingimage") - diffuse_image_asset = asset.AssetCatalogRequestBus( - bus.Broadcast, "GetAssetIdByPath", diffuse_image_path, math.Uuid(), False) - specular_image_path = os.path.join("LightingPresets", "greenwich_park_02_4k_iblskyboxcm.exr.streamingimage") - specular_image_asset = asset.AssetCatalogRequestBus( - bus.Broadcast, "GetAssetIdByPath", specular_image_path, math.Uuid(), False) - ComponentTests( - "Global Skylight (IBL)", - lambda entity_obj: verify_set_property( - entity_obj, "Controller|Configuration|Diffuse Image", diffuse_image_asset), - lambda entity_obj: verify_set_property( - entity_obj, "Controller|Configuration|Specular Image", specular_image_asset)) - - # Physical Sky Component - ComponentTests("Physical Sky") - - # PostFX Layer Component - ComponentTests("PostFX Layer") - - # PostFX Radius Weight Modifier Component - ComponentTests("PostFX Radius Weight Modifier") - - # Light Component - ComponentTests("Light") - - # Display Mapper Component - ComponentTests("Display Mapper") - - # Reflection Probe Component - reflection_probe = "Reflection Probe" - ComponentTests( - reflection_probe, - lambda entity_obj: verify_required_component_addition(entity_obj, ["Box Shape"], reflection_probe), - lambda entity_obj: verify_cubemap_generation(reflection_probe, entity_obj),) - -if __name__ == "__main__": - run() diff --git a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_BloomAdded.py b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_BloomAdded.py new file mode 100644 index 0000000000..56b22eb20d --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_BloomAdded.py @@ -0,0 +1,189 @@ +""" +Copyright (c) Contributors to the Open 3D Engine Project. +For complete copyright and license terms please see the LICENSE at the root of this distribution. + +SPDX-License-Identifier: Apache-2.0 OR MIT +""" + + +class Tests: + creation_undo = ( + "UNDO Entity creation success", + "UNDO Entity creation failed") + creation_redo = ( + "REDO Entity creation success", + "REDO Entity creation failed") + bloom_creation = ( + "Bloom Entity successfully created", + "Bloom Entity failed to be created") + bloom_component = ( + "Entity has a Bloom component", + "Entity failed to find Bloom component") + bloom_disabled = ( + "Bloom component disabled", + "Bloom component was not disabled") + postfx_layer_component = ( + "Entity has a PostFX Layer component", + "Entity did not have an PostFX Layer component") + bloom_enabled = ( + "Bloom component enabled", + "Bloom component was not enabled") + enable_bloom_parameter_enabled = ( + "Enable Bloom parameter enabled", + "Enable Bloom parameter was not enabled") + enter_game_mode = ( + "Entered game mode", + "Failed to enter game mode") + exit_game_mode = ( + "Exited game mode", + "Couldn't exit game mode") + is_visible = ( + "Entity is visible", + "Entity was not visible") + is_hidden = ( + "Entity is hidden", + "Entity was not hidden") + entity_deleted = ( + "Entity deleted", + "Entity was not deleted") + deletion_undo = ( + "UNDO deletion success", + "UNDO deletion failed") + deletion_redo = ( + "REDO deletion success", + "REDO deletion failed") + + +def AtomEditorComponents_Bloom_AddedToEntity(): + """ + Summary: + Tests the Bloom component can be added to an entity and has the expected functionality. + + Test setup: + - Wait for Editor idle loop. + - Open the "Base" level. + + Expected Behavior: + The component can be added, used in game mode, hidden/shown, deleted, and has accurate required components. + Creation and deletion undo/redo should also work. + + Test Steps: + 1) Create an Bloom entity with no components. + 2) Add Bloom component to Bloom entity. + 3) UNDO the entity creation and component addition. + 4) REDO the entity creation and component addition. + 5) Verify Bloom component not enabled. + 6) Add PostFX Layer component since it is required by the Bloom component. + 7) Verify Bloom component is enabled. + 8) Enable the "Enable Bloom" parameter. + 9) Enter/Exit game mode. + 10) Test IsHidden. + 11) Test IsVisible. + 12) Delete Bloom entity. + 13) UNDO deletion. + 14) REDO deletion. + 15) Look for errors. + + :return: None + """ + + import azlmbr.legacy.general as general + + from editor_python_test_tools.editor_entity_utils import EditorEntity + from editor_python_test_tools.utils import Report, Tracer, TestHelper + from Atom.atom_utils.atom_constants import AtomComponentProperties + + with Tracer() as error_tracer: + # Test setup begins. + # Setup: Wait for Editor idle loop before executing Python hydra scripts then open "Base" level. + TestHelper.init_idle() + TestHelper.open_level("", "Base") + + # Test steps begin. + # 1. Create an Bloom entity with no components. + bloom_entity = EditorEntity.create_editor_entity(AtomComponentProperties.bloom()) + Report.critical_result(Tests.bloom_creation, bloom_entity.exists()) + + # 2. Add Bloom component to Bloom entity. + bloom_component = bloom_entity.add_component(AtomComponentProperties.bloom()) + Report.critical_result(Tests.bloom_component, bloom_entity.has_component(AtomComponentProperties.bloom())) + + # 3. UNDO the entity creation and component addition. + # -> UNDO component addition. + general.undo() + # -> UNDO naming entity. + general.undo() + # -> UNDO selecting entity. + general.undo() + # -> UNDO entity creation. + general.undo() + general.idle_wait_frames(1) + Report.result(Tests.creation_undo, not bloom_entity.exists()) + + # 4. REDO the entity creation and component addition. + # -> REDO entity creation. + general.redo() + # -> REDO selecting entity. + general.redo() + # -> REDO naming entity. + general.redo() + # -> REDO component addition. + general.redo() + general.idle_wait_frames(1) + Report.result(Tests.creation_redo, bloom_entity.exists()) + + # 5. Verify Bloom component not enabled. + Report.result(Tests.bloom_disabled, not bloom_component.is_enabled()) + + # 6. Add PostFX Layer component since it is required by the Bloom component. + bloom_entity.add_component(AtomComponentProperties.postfx_layer()) + Report.result( + Tests.postfx_layer_component, + bloom_entity.has_component(AtomComponentProperties.postfx_layer())) + + # 7. Verify Bloom component is enabled. + Report.result(Tests.bloom_enabled, bloom_component.is_enabled()) + + # 8. Enable the "Enable Bloom" parameter. + bloom_component.set_component_property_value(AtomComponentProperties.bloom('Enable Bloom'), True) + Report.result( + Tests.enable_bloom_parameter_enabled, + bloom_component.get_component_property_value(AtomComponentProperties.bloom('Enable Bloom')) is True) + + # 9. Enter/Exit game mode. + TestHelper.enter_game_mode(Tests.enter_game_mode) + general.idle_wait_frames(1) + TestHelper.exit_game_mode(Tests.exit_game_mode) + + # 10. Test IsHidden. + bloom_entity.set_visibility_state(False) + Report.result(Tests.is_hidden, bloom_entity.is_hidden() is True) + + # 11. Test IsVisible. + bloom_entity.set_visibility_state(True) + general.idle_wait_frames(1) + Report.result(Tests.is_visible, bloom_entity.is_visible() is True) + + # 12. Delete Bloom entity. + bloom_entity.delete() + Report.result(Tests.entity_deleted, not bloom_entity.exists()) + + # 13. UNDO deletion. + general.undo() + Report.result(Tests.deletion_undo, bloom_entity.exists()) + + # 14. REDO deletion. + general.redo() + Report.result(Tests.deletion_redo, not bloom_entity.exists()) + + # 15. Look for errors and asserts. + TestHelper.wait_for_condition(lambda: error_tracer.has_errors or error_tracer.has_asserts, 1.0) + for error_info in error_tracer.errors: + Report.info(f"Error: {error_info.filename} {error_info.function} | {error_info.message}") + for assert_info in error_tracer.asserts: + Report.info(f"Assert: {assert_info.filename} {assert_info.function} | {assert_info.message}") + + +if __name__ == "__main__": + from editor_python_test_tools.utils import Report + Report.start_test(AtomEditorComponents_Bloom_AddedToEntity) diff --git a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_DecalAdded.py b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_DecalAdded.py index fa02b75fe2..e840cf3cf1 100644 --- a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_DecalAdded.py +++ b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_DecalAdded.py @@ -5,25 +5,52 @@ For complete copyright and license terms please see the LICENSE at the root of t SPDX-License-Identifier: Apache-2.0 OR MIT """ -# fmt: off class Tests: - camera_creation = ("Camera Entity successfully created", "Camera Entity failed to be created") - camera_component_added = ("Camera component was added to entity", "Camera component failed to be added to entity") - camera_component_check = ("Entity has a Camera component", "Entity failed to find Camera component") - creation_undo = ("UNDO Entity creation success", "UNDO Entity creation failed") - creation_redo = ("REDO Entity creation success", "REDO Entity creation failed") - decal_creation = ("Decal Entity successfully created", "Decal Entity failed to be created") - decal_component = ("Entity has a Decal component", "Entity failed to find Decal component") - material_property_set = ("Material property set on Decal component", "Couldn't set Material property on Decal component") - enter_game_mode = ("Entered game mode", "Failed to enter game mode") - exit_game_mode = ("Exited game mode", "Couldn't exit game mode") - is_visible = ("Entity is visible", "Entity was not visible") - is_hidden = ("Entity is hidden", "Entity was not hidden") - entity_deleted = ("Entity deleted", "Entity was not deleted") - deletion_undo = ("UNDO deletion success", "UNDO deletion failed") - deletion_redo = ("REDO deletion success", "REDO deletion failed") - no_error_occurred = ("No errors detected", "Errors were detected") -# fmt: on + camera_creation = ( + "Camera Entity successfully created", + "Camera Entity failed to be created") + camera_component_added = ( + "Camera component was added to entity", + "Camera component failed to be added to entity") + camera_component_check = ( + "Entity has a Camera component", + "Entity failed to find Camera component") + creation_undo = ( + "UNDO Entity creation success", + "UNDO Entity creation failed") + creation_redo = ( + "REDO Entity creation success", + "REDO Entity creation failed") + decal_creation = ( + "Decal Entity successfully created", + "Decal Entity failed to be created") + decal_component = ( + "Entity has a Decal component", + "Entity failed to find Decal component") + material_property_set = ( + "Material property set on Decal component", + "Couldn't set Material property on Decal component") + enter_game_mode = ( + "Entered game mode", + "Failed to enter game mode") + exit_game_mode = ( + "Exited game mode", + "Couldn't exit game mode") + is_visible = ( + "Entity is visible", + "Entity was not visible") + is_hidden = ( + "Entity is hidden", + "Entity was not hidden") + entity_deleted = ( + "Entity deleted", + "Entity was not deleted") + deletion_undo = ( + "UNDO deletion success", + "UNDO deletion failed") + deletion_redo = ( + "REDO deletion success", + "REDO deletion failed") def AtomEditorComponents_Decal_AddedToEntity(): @@ -51,35 +78,33 @@ def AtomEditorComponents_Decal_AddedToEntity(): 9) Delete Decal entity. 10) UNDO deletion. 11) REDO deletion. - 12) Look for errors. + 12) Look for errors and asserts. :return: None """ import os - import azlmbr.asset as asset - import azlmbr.bus as bus import azlmbr.legacy.general as general - import azlmbr.math as math + from editor_python_test_tools.asset_utils import Asset from editor_python_test_tools.editor_entity_utils import EditorEntity - from editor_python_test_tools.utils import Report, Tracer, TestHelper as helper + from editor_python_test_tools.utils import Report, Tracer, TestHelper + from Atom.atom_utils.atom_constants import AtomComponentProperties with Tracer() as error_tracer: # Test setup begins. # Setup: Wait for Editor idle loop before executing Python hydra scripts then open "Base" level. - helper.init_idle() - helper.open_level("", "Base") + TestHelper.init_idle() + TestHelper.open_level("", "Base") # Test steps begin. # 1. Create a Decal entity with no components. - decal_name = "Decal" - decal_entity = EditorEntity.create_editor_entity_at(math.Vector3(512.0, 512.0, 34.0), decal_name) + decal_entity = EditorEntity.create_editor_entity(AtomComponentProperties.decal()) Report.critical_result(Tests.decal_creation, decal_entity.exists()) # 2. Add Decal component to Decal entity. - decal_component = decal_entity.add_component(decal_name) - Report.critical_result(Tests.decal_component, decal_entity.has_component(decal_name)) + decal_component = decal_entity.add_component(AtomComponentProperties.decal()) + Report.critical_result(Tests.decal_component, decal_entity.has_component(AtomComponentProperties.decal())) # 3. UNDO the entity creation and component addition. # -> UNDO component addition. @@ -106,9 +131,9 @@ def AtomEditorComponents_Decal_AddedToEntity(): Report.result(Tests.creation_redo, decal_entity.exists()) # 5. Enter/Exit game mode. - helper.enter_game_mode(Tests.enter_game_mode) + TestHelper.enter_game_mode(Tests.enter_game_mode) general.idle_wait_frames(1) - helper.exit_game_mode(Tests.exit_game_mode) + TestHelper.exit_game_mode(Tests.exit_game_mode) # 6. Test IsHidden. decal_entity.set_visibility_state(False) @@ -120,13 +145,11 @@ def AtomEditorComponents_Decal_AddedToEntity(): Report.result(Tests.is_visible, decal_entity.is_visible() is True) # 8. Set Material property on Decal component. - decal_material_property_path = "Controller|Configuration|Material" - decal_material_asset_path = os.path.join("AutomatedTesting", "Materials", "basic_grey.material") - decal_material_asset = asset.AssetCatalogRequestBus( - bus.Broadcast, "GetAssetIdByPath", decal_material_asset_path, math.Uuid(), False) - decal_component.set_component_property_value(decal_material_property_path, decal_material_asset) - get_material_property = decal_component.get_component_property_value(decal_material_property_path) - Report.result(Tests.material_property_set, get_material_property == decal_material_asset) + decal_material_asset_path = os.path.join("materials", "basic_grey.azmaterial") + decal_material_asset = Asset.find_asset_by_path(decal_material_asset_path, False) + decal_component.set_component_property_value(AtomComponentProperties.decal('Material'), decal_material_asset.id) + get_material_property = decal_component.get_component_property_value(AtomComponentProperties.decal('Material')) + Report.result(Tests.material_property_set, get_material_property == decal_material_asset.id) # 9. Delete Decal entity. decal_entity.delete() @@ -141,9 +164,12 @@ def AtomEditorComponents_Decal_AddedToEntity(): general.redo() Report.result(Tests.deletion_redo, not decal_entity.exists()) - # 12. Look for errors. - helper.wait_for_condition(lambda: error_tracer.has_errors, 1.0) - Report.result(Tests.no_error_occurred, not error_tracer.has_errors) + # 12. Look for errors and asserts. + TestHelper.wait_for_condition(lambda: error_tracer.has_errors or error_tracer.has_asserts, 1.0) + for error_info in error_tracer.errors: + Report.info(f"Error: {error_info.filename} {error_info.function} | {error_info.message}") + for assert_info in error_tracer.asserts: + Report.info(f"Assert: {assert_info.filename} {assert_info.function} | {assert_info.message}") if __name__ == "__main__": diff --git a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_DeferredFogAdded.py b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_DeferredFogAdded.py new file mode 100644 index 0000000000..71163ece94 --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_DeferredFogAdded.py @@ -0,0 +1,193 @@ +""" +Copyright (c) Contributors to the Open 3D Engine Project. +For complete copyright and license terms please see the LICENSE at the root of this distribution. + +SPDX-License-Identifier: Apache-2.0 OR MIT +""" + + +class Tests: + creation_undo = ( + "UNDO Entity creation success", + "UNDO Entity creation failed") + creation_redo = ( + "REDO Entity creation success", + "REDO Entity creation failed") + deferred_fog_creation = ( + "Deferred Fog Entity successfully created", + "Deferred Fog Entity failed to be created") + deferred_fog_component = ( + "Entity has a Deferred Fog component", + "Entity failed to find Deferred Fog component") + deferred_fog_disabled = ( + "Deferred Fog component disabled", + "Deferred Fog component was not disabled") + postfx_layer_component = ( + "Entity has a PostFX Layer component", + "Entity did not have an PostFX Layer component") + deferred_fog_enabled = ( + "Deferred Fog component enabled", + "Deferred Fog component was not enabled") + enable_deferred_fog_parameter_enabled = ( + "Enable Deferred Fog parameter enabled", + "Enable Deferred Fog parameter was not enabled") + enter_game_mode = ( + "Entered game mode", + "Failed to enter game mode") + exit_game_mode = ( + "Exited game mode", + "Couldn't exit game mode") + is_visible = ( + "Entity is visible", + "Entity was not visible") + is_hidden = ( + "Entity is hidden", + "Entity was not hidden") + entity_deleted = ( + "Entity deleted", + "Entity was not deleted") + deletion_undo = ( + "UNDO deletion success", + "UNDO deletion failed") + deletion_redo = ( + "REDO deletion success", + "REDO deletion failed") + + +def AtomEditorComponents_DeferredFog_AddedToEntity(): + """ + Summary: + Tests the Deferred Fog component can be added to an entity and has the expected functionality. + + Test setup: + - Wait for Editor idle loop. + - Open the "Base" level. + + Expected Behavior: + The component can be added, used in game mode, hidden/shown, deleted, and has accurate required components. + Creation and deletion undo/redo should also work. + + Test Steps: + 1) Create an Deferred Fog entity with no components. + 2) Add Deferred Fog component to Deferred Fog entity. + 3) UNDO the entity creation and component addition. + 4) REDO the entity creation and component addition. + 5) Verify Deferred Fog component not enabled. + 6) Add PostFX Layer component since it is required by the Deferred Fog component. + 7) Verify Deferred Fog component is enabled. + 8) Enable the "Enable Deferred Fog" parameter. + 9) Enter/Exit game mode. + 10) Test IsHidden. + 11) Test IsVisible. + 12) Delete Deferred Fog entity. + 13) UNDO deletion. + 14) REDO deletion. + 15) Look for errors. + + :return: None + """ + + import azlmbr.legacy.general as general + + from editor_python_test_tools.editor_entity_utils import EditorEntity + from editor_python_test_tools.utils import Report, Tracer, TestHelper + from Atom.atom_utils.atom_constants import AtomComponentProperties + + with Tracer() as error_tracer: + # Test setup begins. + # Setup: Wait for Editor idle loop before executing Python hydra scripts then open "Base" level. + TestHelper.init_idle() + TestHelper.open_level("", "Base") + + # Test steps begin. + # 1. Create an Deferred Fog entity with no components. + deferred_fog_entity = EditorEntity.create_editor_entity(AtomComponentProperties.deferred_fog()) + Report.critical_result(Tests.deferred_fog_creation, deferred_fog_entity.exists()) + + # 2. Add Deferred Fog component to Deferred Fog entity. + deferred_fog_component = deferred_fog_entity.add_component( + AtomComponentProperties.deferred_fog()) + Report.critical_result( + Tests.deferred_fog_component, + deferred_fog_entity.has_component(AtomComponentProperties.deferred_fog())) + + # 3. UNDO the entity creation and component addition. + # -> UNDO component addition. + general.undo() + # -> UNDO naming entity. + general.undo() + # -> UNDO selecting entity. + general.undo() + # -> UNDO entity creation. + general.undo() + general.idle_wait_frames(1) + Report.result(Tests.creation_undo, not deferred_fog_entity.exists()) + + # 4. REDO the entity creation and component addition. + # -> REDO entity creation. + general.redo() + # -> REDO selecting entity. + general.redo() + # -> REDO naming entity. + general.redo() + # -> REDO component addition. + general.redo() + general.idle_wait_frames(1) + Report.result(Tests.creation_redo, deferred_fog_entity.exists()) + + # 5. Verify Deferred Fog component not enabled. + Report.result(Tests.deferred_fog_disabled, not deferred_fog_component.is_enabled()) + + # 6. Add PostFX Layer component since it is required by the Deferred Fog component. + deferred_fog_entity.add_component(AtomComponentProperties.postfx_layer()) + Report.result( + Tests.postfx_layer_component, + deferred_fog_entity.has_component(AtomComponentProperties.postfx_layer())) + + # 7. Verify Deferred Fog component is enabled. + Report.result(Tests.deferred_fog_enabled, deferred_fog_component.is_enabled()) + + # 8. Enable the "Enable Deferred Fog" parameter. + deferred_fog_component.set_component_property_value( + AtomComponentProperties.deferred_fog('Enable Deferred Fog'), True) + Report.result(Tests.enable_deferred_fog_parameter_enabled, + deferred_fog_component.get_component_property_value( + AtomComponentProperties.deferred_fog('Enable Deferred Fog')) is True) + + # 9. Enter/Exit game mode. + TestHelper.enter_game_mode(Tests.enter_game_mode) + general.idle_wait_frames(1) + TestHelper.exit_game_mode(Tests.exit_game_mode) + + # 10. Test IsHidden. + deferred_fog_entity.set_visibility_state(False) + Report.result(Tests.is_hidden, deferred_fog_entity.is_hidden() is True) + + # 11. Test IsVisible. + deferred_fog_entity.set_visibility_state(True) + general.idle_wait_frames(1) + Report.result(Tests.is_visible, deferred_fog_entity.is_visible() is True) + + # 12. Delete Deferred Fog entity. + deferred_fog_entity.delete() + Report.result(Tests.entity_deleted, not deferred_fog_entity.exists()) + + # 13. UNDO deletion. + general.undo() + Report.result(Tests.deletion_undo, deferred_fog_entity.exists()) + + # 14. REDO deletion. + general.redo() + Report.result(Tests.deletion_redo, not deferred_fog_entity.exists()) + + # 15. Look for errors and asserts. + TestHelper.wait_for_condition(lambda: error_tracer.has_errors or error_tracer.has_asserts, 1.0) + for error_info in error_tracer.errors: + Report.info(f"Error: {error_info.filename} {error_info.function} | {error_info.message}") + for assert_info in error_tracer.asserts: + Report.info(f"Assert: {assert_info.filename} {assert_info.function} | {assert_info.message}") + + +if __name__ == "__main__": + from editor_python_test_tools.utils import Report + Report.start_test(AtomEditorComponents_DeferredFog_AddedToEntity) diff --git a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_DepthOfFieldAdded.py b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_DepthOfFieldAdded.py index 80284902ea..e2c5f9c77d 100644 --- a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_DepthOfFieldAdded.py +++ b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_DepthOfFieldAdded.py @@ -5,28 +5,61 @@ For complete copyright and license terms please see the LICENSE at the root of t SPDX-License-Identifier: Apache-2.0 OR MIT """ -# fmt: off class Tests: - camera_creation = ("Camera Entity successfully created", "Camera Entity failed to be created") - camera_component_added = ("Camera component was added to Camera entity", "Camera component failed to be added to entity") - camera_component_check = ("Entity has a Camera component", "Entity failed to find Camera component") - camera_property_set = ("DepthOfField Entity set Camera Entity", "DepthOfField Entity could not set Camera Entity") - creation_undo = ("UNDO Entity creation success", "UNDO Entity creation failed") - creation_redo = ("REDO Entity creation success", "REDO Entity creation failed") - depth_of_field_creation = ("DepthOfField Entity successfully created", "DepthOfField Entity failed to be created") - depth_of_field_component = ("Entity has a DepthOfField component", "Entity failed to find DepthOfField component") - depth_of_field_disabled = ("DepthOfField component disabled", "DepthOfField component was not disabled.") - post_fx_component = ("Entity has a Post FX Layer component", "Entity did not have a Post FX Layer component") - depth_of_field_enabled = ("DepthOfField component enabled", "DepthOfField component was not enabled.") - enter_game_mode = ("Entered game mode", "Failed to enter game mode") - exit_game_mode = ("Exited game mode", "Couldn't exit game mode") - is_visible = ("Entity is visible", "Entity was not visible") - is_hidden = ("Entity is hidden", "Entity was not hidden") - entity_deleted = ("Entity deleted", "Entity was not deleted") - deletion_undo = ("UNDO deletion success", "UNDO deletion failed") - deletion_redo = ("REDO deletion success", "REDO deletion failed") - no_error_occurred = ("No errors detected", "Errors were detected") -# fmt: on + camera_creation = ( + "Camera Entity successfully created", + "Camera Entity failed to be created") + camera_component_added = ( + "Camera component was added to Camera entity", + "Camera component failed to be added to entity") + camera_component_check = ( + "Entity has a Camera component", + "Entity failed to find Camera component") + camera_property_set = ( + "DepthOfField Entity set Camera Entity", + "DepthOfField Entity could not set Camera Entity") + creation_undo = ( + "UNDO Entity creation success", + "UNDO Entity creation failed") + creation_redo = ( + "REDO Entity creation success", + "REDO Entity creation failed") + depth_of_field_creation = ( + "DepthOfField Entity successfully created", + "DepthOfField Entity failed to be created") + depth_of_field_component = ( + "Entity has a DepthOfField component", + "Entity failed to find DepthOfField component") + depth_of_field_disabled = ( + "DepthOfField component disabled", + "DepthOfField component was not disabled.") + post_fx_component = ( + "Entity has a Post FX Layer component", + "Entity did not have a Post FX Layer component") + depth_of_field_enabled = ( + "DepthOfField component enabled", + "DepthOfField component was not enabled.") + enter_game_mode = ( + "Entered game mode", + "Failed to enter game mode") + exit_game_mode = ( + "Exited game mode", + "Couldn't exit game mode") + is_visible = ( + "Entity is visible", + "Entity was not visible") + is_hidden = ( + "Entity is hidden", + "Entity was not hidden") + entity_deleted = ( + "Entity deleted", + "Entity was not deleted") + deletion_undo = ( + "UNDO deletion success", + "UNDO deletion failed") + deletion_redo = ( + "REDO deletion success", + "REDO deletion failed") def AtomEditorComponents_DepthOfField_AddedToEntity(): @@ -59,33 +92,32 @@ def AtomEditorComponents_DepthOfField_AddedToEntity(): 14) Delete DepthOfField entity. 15) UNDO deletion. 16) REDO deletion. - 17) Look for errors. + 17) Look for errors and asserts. :return: None """ import azlmbr.legacy.general as general - import azlmbr.math as math from editor_python_test_tools.editor_entity_utils import EditorEntity - from editor_python_test_tools.utils import Report, Tracer, TestHelper as helper + from editor_python_test_tools.utils import Report, Tracer, TestHelper + from Atom.atom_utils.atom_constants import AtomComponentProperties with Tracer() as error_tracer: # Test setup begins. # Setup: Wait for Editor idle loop before executing Python hydra scripts then open "Base" level. - helper.init_idle() - helper.open_level("", "Base") + TestHelper.init_idle() + TestHelper.open_level("", "Base") # Test steps begin. # 1. Create a DepthOfField entity with no components. - depth_of_field_name = "DepthOfField" - depth_of_field_entity = EditorEntity.create_editor_entity_at( - math.Vector3(512.0, 512.0, 34.0), depth_of_field_name) + depth_of_field_entity = EditorEntity.create_editor_entity(AtomComponentProperties.depth_of_field()) Report.critical_result(Tests.depth_of_field_creation, depth_of_field_entity.exists()) # 2. Add a DepthOfField component to DepthOfField entity. - depth_of_field_component = depth_of_field_entity.add_component(depth_of_field_name) - Report.critical_result(Tests.depth_of_field_component, depth_of_field_entity.has_component(depth_of_field_name)) + depth_of_field_component = depth_of_field_entity.add_component(AtomComponentProperties.depth_of_field()) + Report.critical_result(Tests.depth_of_field_component, + depth_of_field_entity.has_component(AtomComponentProperties.depth_of_field())) # 3. UNDO the entity creation and component addition. # -> UNDO component addition. @@ -115,17 +147,16 @@ def AtomEditorComponents_DepthOfField_AddedToEntity(): Report.result(Tests.depth_of_field_disabled, not depth_of_field_component.is_enabled()) # 6. Add Post FX Layer component since it is required by the DepthOfField component. - post_fx_layer = "PostFX Layer" - depth_of_field_entity.add_component(post_fx_layer) - Report.result(Tests.post_fx_component, depth_of_field_entity.has_component(post_fx_layer)) + depth_of_field_entity.add_component(AtomComponentProperties.postfx_layer()) + Report.result(Tests.post_fx_component, depth_of_field_entity.has_component(AtomComponentProperties.postfx_layer())) # 7. Verify DepthOfField component is enabled. Report.result(Tests.depth_of_field_enabled, depth_of_field_component.is_enabled()) # 8. Enter/Exit game mode. - helper.enter_game_mode(Tests.enter_game_mode) + TestHelper.enter_game_mode(Tests.enter_game_mode) general.idle_wait_frames(1) - helper.exit_game_mode(Tests.exit_game_mode) + TestHelper.exit_game_mode(Tests.exit_game_mode) # 9. Test IsHidden. depth_of_field_entity.set_visibility_state(False) @@ -137,19 +168,20 @@ def AtomEditorComponents_DepthOfField_AddedToEntity(): Report.result(Tests.is_visible, depth_of_field_entity.is_visible() is True) # 11. Add Camera entity. - camera_name = "Camera" - camera_entity = EditorEntity.create_editor_entity_at(math.Vector3(512.0, 512.0, 34.0), camera_name) + camera_entity = EditorEntity.create_editor_entity(AtomComponentProperties.camera()) Report.result(Tests.camera_creation, camera_entity.exists()) # 12. Add Camera component to Camera entity. - camera_entity.add_component(camera_name) - Report.result(Tests.camera_component_added, camera_entity.has_component(camera_name)) + camera_entity.add_component(AtomComponentProperties.camera()) + Report.result(Tests.camera_component_added, camera_entity.has_component(AtomComponentProperties.camera())) # 13. Set the DepthOfField components's Camera Entity to the newly created Camera entity. - depth_of_field_camera_property_path = "Controller|Configuration|Camera Entity" - depth_of_field_component.set_component_property_value(depth_of_field_camera_property_path, camera_entity.id) - camera_entity_set = depth_of_field_component.get_component_property_value(depth_of_field_camera_property_path) - Report.result(Tests.camera_property_set, camera_entity.id == camera_entity_set) + depth_of_field_component.set_component_property_value( + AtomComponentProperties.depth_of_field('Camera Entity'), camera_entity.id) + Report.result( + Tests.camera_property_set, + camera_entity.id == depth_of_field_component.get_component_property_value( + AtomComponentProperties.depth_of_field('Camera Entity'))) # 14. Delete DepthOfField entity. depth_of_field_entity.delete() @@ -163,9 +195,12 @@ def AtomEditorComponents_DepthOfField_AddedToEntity(): general.redo() Report.result(Tests.deletion_redo, not depth_of_field_entity.exists()) - # 17. Look for errors. - helper.wait_for_condition(lambda: error_tracer.has_errors, 1.0) - Report.result(Tests.no_error_occurred, not error_tracer.has_errors) + # 17. Look for errors and asserts. + TestHelper.wait_for_condition(lambda: error_tracer.has_errors or error_tracer.has_asserts, 1.0) + for error_info in error_tracer.errors: + Report.info(f"Error: {error_info.filename} {error_info.function} | {error_info.message}") + for assert_info in error_tracer.asserts: + Report.info(f"Assert: {assert_info.filename} {assert_info.function} | {assert_info.message}") if __name__ == "__main__": diff --git a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_DirectionalLightAdded.py b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_DirectionalLightAdded.py index 048e132df4..f3ffc0f366 100644 --- a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_DirectionalLightAdded.py +++ b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_DirectionalLightAdded.py @@ -5,25 +5,52 @@ For complete copyright and license terms please see the LICENSE at the root of t SPDX-License-Identifier: Apache-2.0 OR MIT """ -# fmt: off class Tests: - camera_creation = ("Camera Entity successfully created", "Camera Entity failed to be created") - camera_component_added = ("Camera component was added to entity", "Camera component failed to be added to entity") - camera_component_check = ("Entity has a Camera component", "Entity failed to find Camera component") - creation_undo = ("UNDO Entity creation success", "UNDO Entity creation failed") - creation_redo = ("REDO Entity creation success", "REDO Entity creation failed") - directional_light_creation = ("Directional Light Entity successfully created", "Directional Light Entity failed to be created") - directional_light_component = ("Entity has a Directional Light component", "Entity failed to find Directional Light component") - shadow_camera_check = ("Directional Light component Shadow camera set", "Directional Light component Shadow camera was not set") - enter_game_mode = ("Entered game mode", "Failed to enter game mode") - exit_game_mode = ("Exited game mode", "Couldn't exit game mode") - is_visible = ("Entity is visible", "Entity was not visible") - is_hidden = ("Entity is hidden", "Entity was not hidden") - entity_deleted = ("Entity deleted", "Entity was not deleted") - deletion_undo = ("UNDO deletion success", "UNDO deletion failed") - deletion_redo = ("REDO deletion success", "REDO deletion failed") - no_error_occurred = ("No errors detected", "Errors were detected") -# fmt: on + camera_creation = ( + "Camera Entity successfully created", + "Camera Entity failed to be created") + camera_component_added = ( + "Camera component was added to entity", + "Camera component failed to be added to entity") + camera_component_check = ( + "Entity has a Camera component", + "Entity failed to find Camera component") + creation_undo = ( + "UNDO Entity creation success", + "UNDO Entity creation failed") + creation_redo = ( + "REDO Entity creation success", + "REDO Entity creation failed") + directional_light_creation = ( + "Directional Light Entity successfully created", + "Directional Light Entity failed to be created") + directional_light_component = ( + "Entity has a Directional Light component", + "Entity failed to find Directional Light component") + shadow_camera_check = ( + "Directional Light component Shadow camera set", + "Directional Light component Shadow camera was not set") + enter_game_mode = ( + "Entered game mode", + "Failed to enter game mode") + exit_game_mode = ( + "Exited game mode", + "Couldn't exit game mode") + is_visible = ( + "Entity is visible", + "Entity was not visible") + is_hidden = ( + "Entity is hidden", + "Entity was not hidden") + entity_deleted = ( + "Entity deleted", + "Entity was not deleted") + deletion_undo = ( + "UNDO deletion success", + "UNDO deletion failed") + deletion_redo = ( + "REDO deletion success", + "REDO deletion failed") def AtomEditorComponents_DirectionalLight_AddedToEntity(): @@ -53,34 +80,33 @@ def AtomEditorComponents_DirectionalLight_AddedToEntity(): 11) Delete Directional Light entity. 12) UNDO deletion. 13) REDO deletion. - 14) Look for errors. + 14) Look for errors and asserts. :return: None """ import azlmbr.legacy.general as general - import azlmbr.math as math from editor_python_test_tools.editor_entity_utils import EditorEntity - from editor_python_test_tools.utils import Report, Tracer, TestHelper as helper + from editor_python_test_tools.utils import Report, Tracer, TestHelper + from Atom.atom_utils.atom_constants import AtomComponentProperties with Tracer() as error_tracer: # Test setup begins. # Setup: Wait for Editor idle loop before executing Python hydra scripts then open "Base" level. - helper.init_idle() - helper.open_level("", "Base") + TestHelper.init_idle() + TestHelper.open_level("", "Base") # Test steps begin. # 1. Create a Directional Light entity with no components. - directional_light_name = "Directional Light" - directional_light_entity = EditorEntity.create_editor_entity_at( - math.Vector3(512.0, 512.0, 34.0), directional_light_name) + directional_light_entity = EditorEntity.create_editor_entity(AtomComponentProperties.directional_light()) Report.critical_result(Tests.directional_light_creation, directional_light_entity.exists()) # 2. Add Directional Light component to Directional Light entity. - directional_light_component = directional_light_entity.add_component(directional_light_name) + directional_light_component = directional_light_entity.add_component(AtomComponentProperties.directional_light()) Report.critical_result( - Tests.directional_light_component, directional_light_entity.has_component(directional_light_name)) + Tests.directional_light_component, + directional_light_entity.has_component(AtomComponentProperties.directional_light())) # 3. UNDO the entity creation and component addition. # -> UNDO component addition. @@ -107,9 +133,9 @@ def AtomEditorComponents_DirectionalLight_AddedToEntity(): Report.result(Tests.creation_redo, directional_light_entity.exists()) # 5. Enter/Exit game mode. - helper.enter_game_mode(Tests.enter_game_mode) + TestHelper.enter_game_mode(Tests.enter_game_mode) general.idle_wait_frames(1) - helper.exit_game_mode(Tests.exit_game_mode) + TestHelper.exit_game_mode(Tests.exit_game_mode) # 6. Test IsHidden. directional_light_entity.set_visibility_state(False) @@ -121,19 +147,20 @@ def AtomEditorComponents_DirectionalLight_AddedToEntity(): Report.result(Tests.is_visible, directional_light_entity.is_visible() is True) # 8. Add Camera entity. - camera_name = "Camera" - camera_entity = EditorEntity.create_editor_entity_at(math.Vector3(512.0, 512.0, 34.0), camera_name) + camera_entity = EditorEntity.create_editor_entity(AtomComponentProperties.camera()) Report.result(Tests.camera_creation, camera_entity.exists()) # 9. Add Camera component to Camera entity. - camera_entity.add_component(camera_name) - Report.result(Tests.camera_component_added, camera_entity.has_component(camera_name)) + camera_entity.add_component(AtomComponentProperties.camera()) + Report.result(Tests.camera_component_added, camera_entity.has_component(AtomComponentProperties.camera())) # 10. Set the Directional Light component property Shadow|Camera to the Camera entity. - shadow_camera_property_path = "Controller|Configuration|Shadow|Camera" - directional_light_component.set_component_property_value(shadow_camera_property_path, camera_entity.id) - shadow_camera_set = directional_light_component.get_component_property_value(shadow_camera_property_path) - Report.result(Tests.shadow_camera_check, camera_entity.id == shadow_camera_set) + directional_light_component.set_component_property_value( + AtomComponentProperties.directional_light('Camera'), camera_entity.id) + Report.result( + Tests.shadow_camera_check, + camera_entity.id == directional_light_component.get_component_property_value( + AtomComponentProperties.directional_light('Camera'))) # 11. Delete DirectionalLight entity. directional_light_entity.delete() @@ -147,9 +174,12 @@ def AtomEditorComponents_DirectionalLight_AddedToEntity(): general.redo() Report.result(Tests.deletion_redo, not directional_light_entity.exists()) - # 14. Look for errors. - helper.wait_for_condition(lambda: error_tracer.has_errors, 1.0) - Report.result(Tests.no_error_occurred, not error_tracer.has_errors) + # 14. Look for errors and asserts. + TestHelper.wait_for_condition(lambda: error_tracer.has_errors or error_tracer.has_asserts, 1.0) + for error_info in error_tracer.errors: + Report.info(f"Error: {error_info.filename} {error_info.function} | {error_info.message}") + for assert_info in error_tracer.asserts: + Report.info(f"Assert: {assert_info.filename} {assert_info.function} | {assert_info.message}") if __name__ == "__main__": diff --git a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_DisplayMapperAdded.py b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_DisplayMapperAdded.py index 39d7acf4f4..f8881bfa2a 100644 --- a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_DisplayMapperAdded.py +++ b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_DisplayMapperAdded.py @@ -5,24 +5,49 @@ For complete copyright and license terms please see the LICENSE at the root of t SPDX-License-Identifier: Apache-2.0 OR MIT """ -# fmt: off class Tests: - camera_creation = ("Camera Entity successfully created", "Camera Entity failed to be created") - camera_component_added = ("Camera component was added to entity", "Camera component failed to be added to entity") - camera_component_check = ("Entity has a Camera component", "Entity failed to find Camera component") - creation_undo = ("UNDO Entity creation success", "UNDO Entity creation failed") - creation_redo = ("REDO Entity creation success", "REDO Entity creation failed") - display_mapper_creation = ("Display Mapper Entity successfully created", "Display Mapper Entity failed to be created") - display_mapper_component = ("Entity has a Display Mapper component", "Entity failed to find Display Mapper component") - enter_game_mode = ("Entered game mode", "Failed to enter game mode") - exit_game_mode = ("Exited game mode", "Couldn't exit game mode") - is_visible = ("Entity is visible", "Entity was not visible") - is_hidden = ("Entity is hidden", "Entity was not hidden") - entity_deleted = ("Entity deleted", "Entity was not deleted") - deletion_undo = ("UNDO deletion success", "UNDO deletion failed") - deletion_redo = ("REDO deletion success", "REDO deletion failed") - no_error_occurred = ("No errors detected", "Errors were detected") -# fmt: on + camera_creation = ( + "Camera Entity successfully created", + "Camera Entity failed to be created") + camera_component_added = ( + "Camera component was added to entity", + "Camera component failed to be added to entity") + camera_component_check = ( + "Entity has a Camera component", + "Entity failed to find Camera component") + creation_undo = ( + "UNDO Entity creation success", + "UNDO Entity creation failed") + creation_redo = ( + "REDO Entity creation success", + "REDO Entity creation failed") + display_mapper_creation = ( + "Display Mapper Entity successfully created", + "Display Mapper Entity failed to be created") + display_mapper_component = ( + "Entity has a Display Mapper component", + "Entity failed to find Display Mapper component") + enter_game_mode = ( + "Entered game mode", + "Failed to enter game mode") + exit_game_mode = ( + "Exited game mode", + "Couldn't exit game mode") + is_visible = ( + "Entity is visible", + "Entity was not visible") + is_hidden = ( + "Entity is hidden", + "Entity was not hidden") + entity_deleted = ( + "Entity deleted", + "Entity was not deleted") + deletion_undo = ( + "UNDO deletion success", + "UNDO deletion failed") + deletion_redo = ( + "REDO deletion success", + "REDO deletion failed") def AtomEditorComponents_DisplayMapper_AddedToEntity(): @@ -49,33 +74,33 @@ def AtomEditorComponents_DisplayMapper_AddedToEntity(): 8) Delete Display Mapper entity. 9) UNDO deletion. 10) REDO deletion. - 11) Look for errors. + 11) Look for errors and asserts. :return: None """ import azlmbr.legacy.general as general - import azlmbr.math as math from editor_python_test_tools.editor_entity_utils import EditorEntity - from editor_python_test_tools.utils import Report, Tracer, TestHelper as helper + from editor_python_test_tools.utils import Report, Tracer, TestHelper + from Atom.atom_utils.atom_constants import AtomComponentProperties with Tracer() as error_tracer: # Test setup begins. # Setup: Wait for Editor idle loop before executing Python hydra scripts then open "Base" level. - helper.init_idle() - helper.open_level("", "Base") + TestHelper.init_idle() + TestHelper.open_level("", "Base") # Test steps begin. # 1. Create a Display Mapper entity with no components. - display_mapper = "Display Mapper" - display_mapper_entity = EditorEntity.create_editor_entity_at( - math.Vector3(512.0, 512.0, 34.0), f"{display_mapper}") + display_mapper_entity = EditorEntity.create_editor_entity(AtomComponentProperties.display_mapper()) Report.critical_result(Tests.display_mapper_creation, display_mapper_entity.exists()) # 2. Add Display Mapper component to Display Mapper entity. - display_mapper_entity.add_component(display_mapper) - Report.critical_result(Tests.display_mapper_component, display_mapper_entity.has_component(display_mapper)) + display_mapper_entity.add_component(AtomComponentProperties.display_mapper()) + Report.critical_result( + Tests.display_mapper_component, + display_mapper_entity.has_component(AtomComponentProperties.display_mapper())) # 3. UNDO the entity creation and component addition. # -> UNDO component addition. @@ -102,9 +127,9 @@ def AtomEditorComponents_DisplayMapper_AddedToEntity(): Report.result(Tests.creation_redo, display_mapper_entity.exists()) # 5. Enter/Exit game mode. - helper.enter_game_mode(Tests.enter_game_mode) + TestHelper.enter_game_mode(Tests.enter_game_mode) general.idle_wait_frames(1) - helper.exit_game_mode(Tests.exit_game_mode) + TestHelper.exit_game_mode(Tests.exit_game_mode) # 6. Test IsHidden. display_mapper_entity.set_visibility_state(False) @@ -127,9 +152,12 @@ def AtomEditorComponents_DisplayMapper_AddedToEntity(): general.redo() Report.result(Tests.deletion_redo, not display_mapper_entity.exists()) - # 11. Look for errors. - helper.wait_for_condition(lambda: error_tracer.has_errors, 1.0) - Report.result(Tests.no_error_occurred, not error_tracer.has_errors) + # 11. Look for errors and asserts. + TestHelper.wait_for_condition(lambda: error_tracer.has_errors or error_tracer.has_asserts, 1.0) + for error_info in error_tracer.errors: + Report.info(f"Error: {error_info.filename} {error_info.function} | {error_info.message}") + for assert_info in error_tracer.asserts: + Report.info(f"Assert: {assert_info.filename} {assert_info.function} | {assert_info.message}") if __name__ == "__main__": diff --git a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_ExposureControlAdded.py b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_ExposureControlAdded.py index 23a84435f7..6fa9660539 100644 --- a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_ExposureControlAdded.py +++ b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_ExposureControlAdded.py @@ -5,25 +5,58 @@ For complete copyright and license terms please see the LICENSE at the root of t SPDX-License-Identifier: Apache-2.0 OR MIT """ -# fmt: off class Tests: - camera_creation = ("Camera Entity successfully created", "Camera Entity failed to be created") - camera_component_added = ("Camera component was added to entity", "Camera component failed to be added to entity") - camera_component_check = ("Entity has a Camera component", "Entity failed to find Camera component") - creation_undo = ("UNDO Entity creation success", "UNDO Entity creation failed") - creation_redo = ("REDO Entity creation success", "REDO Entity creation failed") - exposure_control_creation = ("ExposureControl Entity successfully created", "ExposureControl Entity failed to be created") - exposure_control_component = ("Entity has a Exposure Control component", "Entity failed to find Exposure Control component") - post_fx_component = ("Entity has a Post FX Layer component", "Entity did not have a Post FX Layer component") - enter_game_mode = ("Entered game mode", "Failed to enter game mode") - exit_game_mode = ("Exited game mode", "Couldn't exit game mode") - is_visible = ("Entity is visible", "Entity was not visible") - is_hidden = ("Entity is hidden", "Entity was not hidden") - entity_deleted = ("Entity deleted", "Entity was not deleted") - deletion_undo = ("UNDO deletion success", "UNDO deletion failed") - deletion_redo = ("REDO deletion success", "REDO deletion failed") - no_error_occurred = ("No errors detected", "Errors were detected") -# fmt: on + camera_creation = ( + "Camera Entity successfully created", + "Camera Entity failed to be created") + camera_component_added = ( + "Camera component was added to entity", + "Camera component failed to be added to entity") + camera_component_check = ( + "Entity has a Camera component", + "Entity failed to find Camera component") + creation_undo = ( + "UNDO Entity creation success", + "UNDO Entity creation failed") + creation_redo = ( + "REDO Entity creation success", + "REDO Entity creation failed") + exposure_control_creation = ( + "ExposureControl Entity successfully created", + "ExposureControl Entity failed to be created") + exposure_control_component = ( + "Entity has a Exposure Control component", + "Entity failed to find Exposure Control component") + exposure_control_disabled = ( + "DepthOfField component disabled", + "DepthOfField component was not disabled.") + post_fx_component = ( + "Entity has a Post FX Layer component", + "Entity did not have a Post FX Layer component") + exposure_control_enabled = ( + "DepthOfField component enabled", + "DepthOfField component was not enabled.") + enter_game_mode = ( + "Entered game mode", + "Failed to enter game mode") + exit_game_mode = ( + "Exited game mode", + "Couldn't exit game mode") + is_visible = ( + "Entity is visible", + "Entity was not visible") + is_hidden = ( + "Entity is hidden", + "Entity was not hidden") + entity_deleted = ( + "Entity deleted", + "Entity was not deleted") + deletion_undo = ( + "UNDO deletion success", + "UNDO deletion failed") + deletion_redo = ( + "REDO deletion success", + "REDO deletion failed") def AtomEditorComponents_ExposureControl_AddedToEntity(): @@ -44,41 +77,42 @@ def AtomEditorComponents_ExposureControl_AddedToEntity(): 2) Add Exposure Control component to Exposure Control entity. 3) UNDO the entity creation and component addition. 4) REDO the entity creation and component addition. - 5) Enter/Exit game mode. - 6) Test IsHidden. - 7) Test IsVisible. - 8) Add Post FX Layer component. - 9) Delete Exposure Control entity. - 10) UNDO deletion. - 11) REDO deletion. - 12) Look for errors. + 5) Verify Exposure Control component not enabled. + 6) Add Post FX Layer component since it is required by the Exposure Control component. + 7) Verify Exposure Control component is enabled. + 8) Enter/Exit game mode. + 9) Test IsHidden. + 10) Test IsVisible. + 11) Delete Exposure Control entity. + 12) UNDO deletion. + 13) REDO deletion. + 14) Look for errors and asserts. :return: None """ import azlmbr.legacy.general as general - import azlmbr.math as math from editor_python_test_tools.editor_entity_utils import EditorEntity - from editor_python_test_tools.utils import Report, Tracer, TestHelper as helper + from editor_python_test_tools.utils import Report, Tracer, TestHelper + from Atom.atom_utils.atom_constants import AtomComponentProperties with Tracer() as error_tracer: # Test setup begins. # Setup: Wait for Editor idle loop before executing Python hydra scripts then open "Base" level. - helper.init_idle() - helper.open_level("", "Base") + TestHelper.init_idle() + TestHelper.open_level("", "Base") # Test steps begin. # 1. Creation of Exposure Control entity with no components. - exposure_control_name = "Exposure Control" - exposure_control_entity = EditorEntity.create_editor_entity_at( - math.Vector3(512.0, 512.0, 34.0), f"{exposure_control_name}") + exposure_control_entity = EditorEntity.create_editor_entity(AtomComponentProperties.exposure_control()) Report.critical_result(Tests.exposure_control_creation, exposure_control_entity.exists()) # 2. Add Exposure Control component to Exposure Control entity. - exposure_control_entity.add_component(exposure_control_name) + exposure_control_component = exposure_control_entity.add_component(AtomComponentProperties.exposure_control()) Report.critical_result( - Tests.exposure_control_component, exposure_control_entity.has_component(exposure_control_name)) + Tests.exposure_control_component, + exposure_control_entity.has_component(AtomComponentProperties.exposure_control())) # 3. UNDO the entity creation and component addition. # -> UNDO component addition. @@ -104,40 +138,49 @@ def AtomEditorComponents_ExposureControl_AddedToEntity(): general.idle_wait_frames(1) Report.result(Tests.creation_redo, exposure_control_entity.exists()) - # 5. Enter/Exit game mode. - helper.enter_game_mode(Tests.enter_game_mode) - general.idle_wait_frames(1) - helper.exit_game_mode(Tests.exit_game_mode) + # 5. Verify Exposure Control component not enabled. + Report.result(Tests.exposure_control_disabled, not exposure_control_component.is_enabled()) - # 6. Test IsHidden. + # 6. Add Post FX Layer component since it is required by the Exposure Control component. + exposure_control_entity.add_component(AtomComponentProperties.postfx_layer()) + Report.result(Tests.post_fx_component, + exposure_control_entity.has_component(AtomComponentProperties.postfx_layer())) + + # 7. Verify Exposure Control component is enabled. + Report.result(Tests.exposure_control_enabled, exposure_control_component.is_enabled()) + + # 8. Enter/Exit game mode. + TestHelper.enter_game_mode(Tests.enter_game_mode) + general.idle_wait_frames(1) + TestHelper.exit_game_mode(Tests.exit_game_mode) + + # 9. Test IsHidden. exposure_control_entity.set_visibility_state(False) Report.result(Tests.is_hidden, exposure_control_entity.is_hidden() is True) - # 7. Test IsVisible. + # 10. Test IsVisible. exposure_control_entity.set_visibility_state(True) general.idle_wait_frames(1) Report.result(Tests.is_visible, exposure_control_entity.is_visible() is True) - # 8. Add Post FX Layer component. - post_fx_layer_name = "PostFX Layer" - exposure_control_entity.add_component(post_fx_layer_name) - Report.result(Tests.post_fx_component, exposure_control_entity.has_component(post_fx_layer_name)) - - # 9. Delete ExposureControl entity. + # 11. Delete ExposureControl entity. exposure_control_entity.delete() Report.result(Tests.entity_deleted, not exposure_control_entity.exists()) - # 10. UNDO deletion. + # 12. UNDO deletion. general.undo() Report.result(Tests.deletion_undo, exposure_control_entity.exists()) - # 11. REDO deletion. + # 13. REDO deletion. general.redo() Report.result(Tests.deletion_redo, not exposure_control_entity.exists()) - # 12. Look for errors. - helper.wait_for_condition(lambda: error_tracer.has_errors, 1.0) - Report.result(Tests.no_error_occurred, not error_tracer.has_errors) + # 14. Look for errors and asserts. + TestHelper.wait_for_condition(lambda: error_tracer.has_errors or error_tracer.has_asserts, 1.0) + for error_info in error_tracer.errors: + Report.info(f"Error: {error_info.filename} {error_info.function} | {error_info.message}") + for assert_info in error_tracer.asserts: + Report.info(f"Assert: {assert_info.filename} {assert_info.function} | {assert_info.message}") if __name__ == "__main__": diff --git a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_GlobalSkylightIBLAdded.py b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_GlobalSkylightIBLAdded.py index cc891f5929..db8f2daaee 100644 --- a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_GlobalSkylightIBLAdded.py +++ b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_GlobalSkylightIBLAdded.py @@ -5,26 +5,55 @@ For complete copyright and license terms please see the LICENSE at the root of t SPDX-License-Identifier: Apache-2.0 OR MIT """ -# fmt: off class Tests: - camera_creation = ("Camera Entity successfully created", "Camera Entity failed to be created") - camera_component_added = ("Camera component was added to entity", "Camera component failed to be added to entity") - camera_component_check = ("Entity has a Camera component", "Entity failed to find Camera component") - creation_undo = ("UNDO Entity creation success", "UNDO Entity creation failed") - creation_redo = ("REDO Entity creation success", "REDO Entity creation failed") - global_skylight_creation = ("Global Skylight (IBL) Entity successfully created", "Global Skylight (IBL) Entity failed to be created") - global_skylight_component = ("Entity has a Global Skylight (IBL) component", "Entity failed to find Global Skylight (IBL) component") - diffuse_image_set = ("Entity has the Diffuse Image set", "Entity did not the Diffuse Image set") - specular_image_set = ("Entity has the Specular Image set", "Entity did not the Specular Image set") - enter_game_mode = ("Entered game mode", "Failed to enter game mode") - exit_game_mode = ("Exited game mode", "Couldn't exit game mode") - is_visible = ("Entity is visible", "Entity was not visible") - is_hidden = ("Entity is hidden", "Entity was not hidden") - entity_deleted = ("Entity deleted", "Entity was not deleted") - deletion_undo = ("UNDO deletion success", "UNDO deletion failed") - deletion_redo = ("REDO deletion success", "REDO deletion failed") - no_error_occurred = ("No errors detected", "Errors were detected") -# fmt: on + camera_creation = ( + "Camera Entity successfully created", + "Camera Entity failed to be created") + camera_component_added = ( + "Camera component was added to entity", + "Camera component failed to be added to entity") + camera_component_check = ( + "Entity has a Camera component", + "Entity failed to find Camera component") + creation_undo = ( + "UNDO Entity creation success", + "UNDO Entity creation failed") + creation_redo = ( + "REDO Entity creation success", + "REDO Entity creation failed") + global_skylight_creation = ( + "Global Skylight (IBL) Entity successfully created", + "Global Skylight (IBL) Entity failed to be created") + global_skylight_component = ( + "Entity has a Global Skylight (IBL) component", + "Entity failed to find Global Skylight (IBL) component") + diffuse_image_set = ( + "Entity has the Diffuse Image set", + "Entity did not the Diffuse Image set") + specular_image_set = ( + "Entity has the Specular Image set", + "Entity did not the Specular Image set") + enter_game_mode = ( + "Entered game mode", + "Failed to enter game mode") + exit_game_mode = ( + "Exited game mode", + "Couldn't exit game mode") + is_visible = ( + "Entity is visible", + "Entity was not visible") + is_hidden = ( + "Entity is hidden", + "Entity was not hidden") + entity_deleted = ( + "Entity deleted", + "Entity was not deleted") + deletion_undo = ( + "UNDO deletion success", + "UNDO deletion failed") + deletion_redo = ( + "REDO deletion success", + "REDO deletion failed") def AtomEditorComponents_GlobalSkylightIBL_AddedToEntity(): @@ -60,29 +89,28 @@ def AtomEditorComponents_GlobalSkylightIBL_AddedToEntity(): import os import azlmbr.legacy.general as general - import azlmbr.math as math from editor_python_test_tools.asset_utils import Asset from editor_python_test_tools.editor_entity_utils import EditorEntity - from editor_python_test_tools.utils import Report, Tracer, TestHelper as helper + from editor_python_test_tools.utils import Report, Tracer, TestHelper + from Atom.atom_utils.atom_constants import AtomComponentProperties with Tracer() as error_tracer: # Test setup begins. # Setup: Wait for Editor idle loop before executing Python hydra scripts then open "Base" level. - helper.init_idle() - helper.open_level("", "Base") + TestHelper.init_idle() + TestHelper.open_level("", "Base") # Test steps begin. # 1. Create a Global Skylight (IBL) entity with no components. - global_skylight_name = "Global Skylight (IBL)" - global_skylight_entity = EditorEntity.create_editor_entity_at( - math.Vector3(512.0, 512.0, 34.0), global_skylight_name) + global_skylight_entity = EditorEntity.create_editor_entity(AtomComponentProperties.global_skylight()) Report.critical_result(Tests.global_skylight_creation, global_skylight_entity.exists()) # 2. Add Global Skylight (IBL) component to Global Skylight (IBL) entity. - global_skylight_component = global_skylight_entity.add_component(global_skylight_name) + global_skylight_component = global_skylight_entity.add_component(AtomComponentProperties.global_skylight()) Report.critical_result( - Tests.global_skylight_component, global_skylight_entity.has_component(global_skylight_name)) + Tests.global_skylight_component, + global_skylight_entity.has_component(AtomComponentProperties.global_skylight())) # 3. UNDO the entity creation and component addition. # -> UNDO component addition. @@ -109,9 +137,9 @@ def AtomEditorComponents_GlobalSkylightIBL_AddedToEntity(): Report.result(Tests.creation_redo, global_skylight_entity.exists()) # 5. Enter/Exit game mode. - helper.enter_game_mode(Tests.enter_game_mode) + TestHelper.enter_game_mode(Tests.enter_game_mode) general.idle_wait_frames(1) - helper.exit_game_mode(Tests.exit_game_mode) + TestHelper.exit_game_mode(Tests.exit_game_mode) # 6. Test IsHidden. global_skylight_entity.set_visibility_state(False) @@ -123,24 +151,24 @@ def AtomEditorComponents_GlobalSkylightIBL_AddedToEntity(): Report.result(Tests.is_visible, global_skylight_entity.is_visible() is True) # 8. Set the Diffuse Image asset on the Global Skylight (IBL) entity. - global_skylight_diffuse_image_property = "Controller|Configuration|Diffuse Image" diffuse_image_path = os.path.join("LightingPresets", "greenwich_park_02_4k_iblskyboxcm.exr.streamingimage") diffuse_image_asset = Asset.find_asset_by_path(diffuse_image_path, False) global_skylight_component.set_component_property_value( - global_skylight_diffuse_image_property, diffuse_image_asset.id) - diffuse_image_set = global_skylight_component.get_component_property_value( - global_skylight_diffuse_image_property) - Report.result(Tests.diffuse_image_set, diffuse_image_set == diffuse_image_asset.id) + AtomComponentProperties.global_skylight('Diffuse Image'), diffuse_image_asset.id) + Report.result( + Tests.diffuse_image_set, + diffuse_image_asset.id == global_skylight_component.get_component_property_value( + AtomComponentProperties.global_skylight('Diffuse Image'))) # 9. Set the Specular Image asset on the Global Light (IBL) entity. - global_skylight_specular_image_property = "Controller|Configuration|Specular Image" specular_image_path = os.path.join("LightingPresets", "greenwich_park_02_4k_iblskyboxcm.exr.streamingimage") specular_image_asset = Asset.find_asset_by_path(specular_image_path, False) global_skylight_component.set_component_property_value( - global_skylight_specular_image_property, specular_image_asset.id) - specular_image_added = global_skylight_component.get_component_property_value( - global_skylight_specular_image_property) - Report.result(Tests.specular_image_set, specular_image_added == specular_image_asset.id) + AtomComponentProperties.global_skylight('Specular Image'), specular_image_asset.id) + Report.result( + Tests.specular_image_set, + specular_image_asset.id == global_skylight_component.get_component_property_value( + AtomComponentProperties.global_skylight('Specular Image'))) # 10. Delete Global Skylight (IBL) entity. global_skylight_entity.delete() @@ -154,9 +182,12 @@ def AtomEditorComponents_GlobalSkylightIBL_AddedToEntity(): general.redo() Report.result(Tests.deletion_redo, not global_skylight_entity.exists()) - # 13. Look for errors. - helper.wait_for_condition(lambda: error_tracer.has_errors, 1.0) - Report.result(Tests.no_error_occurred, not error_tracer.has_errors) + # 13. Look for errors and asserts. + TestHelper.wait_for_condition(lambda: error_tracer.has_errors or error_tracer.has_asserts, 1.0) + for error_info in error_tracer.errors: + Report.info(f"Error: {error_info.filename} {error_info.function} | {error_info.message}") + for assert_info in error_tracer.asserts: + Report.info(f"Assert: {assert_info.filename} {assert_info.function} | {assert_info.message}") if __name__ == "__main__": diff --git a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_GridAdded.py b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_GridAdded.py new file mode 100644 index 0000000000..a77a1f50a4 --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_GridAdded.py @@ -0,0 +1,158 @@ +""" +Copyright (c) Contributors to the Open 3D Engine Project. +For complete copyright and license terms please see the LICENSE at the root of this distribution. + +SPDX-License-Identifier: Apache-2.0 OR MIT +""" + +class Tests: + creation_undo = ( + "UNDO Entity creation success", + "UNDO Entity creation failed") + creation_redo = ( + "REDO Entity creation success", + "REDO Entity creation failed") + grid_entity_creation = ( + "Grid Entity successfully created", + "Grid Entity failed to be created") + grid_component_added = ( + "Entity has a Grid component", + "Entity failed to find Grid component") + enter_game_mode = ( + "Entered game mode", + "Failed to enter game mode") + exit_game_mode = ( + "Exited game mode", + "Couldn't exit game mode") + is_visible = ( + "Entity is visible", + "Entity was not visible") + is_hidden = ( + "Entity is hidden", + "Entity was not hidden") + entity_deleted = ( + "Entity deleted", + "Entity was not deleted") + deletion_undo = ( + "UNDO deletion success", + "UNDO deletion failed") + deletion_redo = ( + "REDO deletion success", + "REDO deletion failed") + + +def AtomEditorComponents_Grid_AddedToEntity(): + """ + Summary: + Tests the Grid component can be added to an entity and has the expected functionality. + + Test setup: + - Wait for Editor idle loop. + - Open the "Base" level. + + Expected Behavior: + The component can be added, used in game mode, hidden/shown, deleted, and has accurate required components. + Creation and deletion undo/redo should also work. + + Test Steps: + 1) Create a Grid entity with no components. + 2) Add a Grid component to Grid entity. + 3) UNDO the entity creation and component addition. + 4) REDO the entity creation and component addition. + 5) Enter/Exit game mode. + 6) Test IsHidden. + 7) Test IsVisible. + 8) Delete Grid entity. + 9) UNDO deletion. + 10) REDO deletion. + 11) Look for errors. + + :return: None + """ + + import os + + import azlmbr.legacy.general as general + + from editor_python_test_tools.editor_entity_utils import EditorEntity + from editor_python_test_tools.utils import Report, Tracer, TestHelper + from Atom.atom_utils.atom_constants import AtomComponentProperties + + with Tracer() as error_tracer: + # Test setup begins. + # Setup: Wait for Editor idle loop before executing Python hydra scripts then open "Base" level. + TestHelper.init_idle() + TestHelper.open_level("", "Base") + + # Test steps begin. + # 1. Create a Grid entity with no components. + grid_entity = EditorEntity.create_editor_entity(AtomComponentProperties.grid()) + Report.critical_result(Tests.grid_entity_creation, grid_entity.exists()) + + # 2. Add a Grid component to Grid entity. + grid_component = grid_entity.add_component(AtomComponentProperties.grid()) + Report.critical_result( + Tests.grid_component_added, + grid_entity.has_component(AtomComponentProperties.grid())) + + # 3. UNDO the entity creation and component addition. + # -> UNDO component addition. + general.undo() + # -> UNDO naming entity. + general.undo() + # -> UNDO selecting entity. + general.undo() + # -> UNDO entity creation. + general.undo() + general.idle_wait_frames(1) + Report.result(Tests.creation_undo, not grid_entity.exists()) + + # 4. REDO the entity creation and component addition. + # -> REDO entity creation. + general.redo() + # -> REDO selecting entity. + general.redo() + # -> REDO naming entity. + general.redo() + # -> REDO component addition. + general.redo() + general.idle_wait_frames(1) + Report.result(Tests.creation_redo, grid_entity.exists()) + + # 5. Enter/Exit game mode. + TestHelper.enter_game_mode(Tests.enter_game_mode) + general.idle_wait_frames(1) + TestHelper.exit_game_mode(Tests.exit_game_mode) + + # 6. Test IsHidden. + grid_entity.set_visibility_state(False) + Report.result(Tests.is_hidden, grid_entity.is_hidden() is True) + + # 7. Test IsVisible. + grid_entity.set_visibility_state(True) + general.idle_wait_frames(1) + Report.result(Tests.is_visible, grid_entity.is_visible() is True) + + # 8. Delete Grid entity. + grid_entity.delete() + Report.result(Tests.entity_deleted, not grid_entity.exists()) + + # 9. UNDO deletion. + general.undo() + Report.result(Tests.deletion_undo, grid_entity.exists()) + + # 10. REDO deletion. + general.redo() + Report.result(Tests.deletion_redo, not grid_entity.exists()) + + # 11. Look for errors or asserts. + TestHelper.wait_for_condition(lambda: error_tracer.has_errors or error_tracer.has_asserts, 1.0) + for error_info in error_tracer.errors: + Report.info(f"Error: {error_info.filename} {error_info.function} | {error_info.message}") + for assert_info in error_tracer.asserts: + Report.info(f"Assert: {assert_info.filename} {assert_info.function} | {assert_info.message}") + + +if __name__ == "__main__": + from editor_python_test_tools.utils import Report + Report.start_test(AtomEditorComponents_Grid_AddedToEntity) diff --git a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_HDRColorGradingAdded.py b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_HDRColorGradingAdded.py new file mode 100644 index 0000000000..4972079fcd --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_HDRColorGradingAdded.py @@ -0,0 +1,192 @@ +""" +Copyright (c) Contributors to the Open 3D Engine Project. +For complete copyright and license terms please see the LICENSE at the root of this distribution. + +SPDX-License-Identifier: Apache-2.0 OR MIT +""" + +class Tests: + creation_undo = ( + "UNDO Entity creation success", + "UNDO Entity creation failed") + creation_redo = ( + "REDO Entity creation success", + "REDO Entity creation failed") + hdr_color_grading_creation = ( + "HDR Color Grading Entity successfully created", + "HDR Color Grading Entity failed to be created") + hdr_color_grading_component = ( + "Entity has an HDR Color Grading component", + "Entity failed to find HDR Color Grading component") + hdr_color_grading_disabled = ( + "HDR Color Grading component disabled", + "HDR Color Grading component was not disabled") + postfx_layer_component = ( + "Entity has a PostFX Layer component", + "Entity did not have an PostFX Layer component") + hdr_color_grading_enabled = ( + "HDR Color Grading component enabled", + "HDR Color Grading component was not enabled") + enable_hdr_color_grading_parameter_enabled = ( + "Enable HDR Color Grading parameter enabled", + "Enable HDR Color Grading parameter was not enabled") + enter_game_mode = ( + "Entered game mode", + "Failed to enter game mode") + exit_game_mode = ( + "Exited game mode", + "Couldn't exit game mode") + is_visible = ( + "Entity is visible", + "Entity was not visible") + is_hidden = ( + "Entity is hidden", + "Entity was not hidden") + entity_deleted = ( + "Entity deleted", + "Entity was not deleted") + deletion_undo = ( + "UNDO deletion success", + "UNDO deletion failed") + deletion_redo = ( + "REDO deletion success", + "REDO deletion failed") + + +def AtomEditorComponents_HDRColorGrading_AddedToEntity(): + """ + Summary: + Tests the HDR Color Grading component can be added to an entity and has the expected functionality. + + Test setup: + - Wait for Editor idle loop. + - Open the "Base" level. + + Expected Behavior: + The component can be added, used in game mode, hidden/shown, deleted, and has accurate required components. + Creation and deletion undo/redo should also work. + + Test Steps: + 1) Create an HDR Color Grading entity with no components. + 2) Add HDR Color Grading component to HDR Color Grading entity. + 3) UNDO the entity creation and component addition. + 4) REDO the entity creation and component addition. + 5) Verify HDR Color Grading component not enabled. + 6) Add PostFX Layer component since it is required by the HDR Color Grading component. + 7) Verify HDR Color Grading component is enabled. + 8) Enable the "Enable HDR Color Grading" parameter. + 9) Enter/Exit game mode. + 10) Test IsHidden. + 11) Test IsVisible. + 12) Delete HDR Color Grading entity. + 13) UNDO deletion. + 14) REDO deletion. + 15) Look for errors. + + :return: None + """ + + import azlmbr.legacy.general as general + + from editor_python_test_tools.editor_entity_utils import EditorEntity + from editor_python_test_tools.utils import Report, Tracer, TestHelper + from Atom.atom_utils.atom_constants import AtomComponentProperties + + with Tracer() as error_tracer: + # Test setup begins. + # Setup: Wait for Editor idle loop before executing Python hydra scripts then open "Base" level. + TestHelper.init_idle() + TestHelper.open_level("", "Base") + + # Test steps begin. + # 1. Create an HDR Color Grading entity with no components. + hdr_color_grading_entity = EditorEntity.create_editor_entity(AtomComponentProperties.hdr_color_grading()) + Report.critical_result(Tests.hdr_color_grading_creation, hdr_color_grading_entity.exists()) + + # 2. Add HDR Color Grading component to HDR Color Grading entity. + hdr_color_grading_component = hdr_color_grading_entity.add_component( + AtomComponentProperties.hdr_color_grading()) + Report.critical_result( + Tests.hdr_color_grading_component, + hdr_color_grading_entity.has_component(AtomComponentProperties.hdr_color_grading())) + + # 3. UNDO the entity creation and component addition. + # -> UNDO component addition. + general.undo() + # -> UNDO naming entity. + general.undo() + # -> UNDO selecting entity. + general.undo() + # -> UNDO entity creation. + general.undo() + general.idle_wait_frames(1) + Report.result(Tests.creation_undo, not hdr_color_grading_entity.exists()) + + # 4. REDO the entity creation and component addition. + # -> REDO entity creation. + general.redo() + # -> REDO selecting entity. + general.redo() + # -> REDO naming entity. + general.redo() + # -> REDO component addition. + general.redo() + general.idle_wait_frames(1) + Report.result(Tests.creation_redo, hdr_color_grading_entity.exists()) + + # 5. Verify HDR Color Grading component not enabled. + Report.result(Tests.hdr_color_grading_disabled, not hdr_color_grading_component.is_enabled()) + + # 6. Add PostFX Layer component since it is required by the HDR Color Grading component. + hdr_color_grading_entity.add_component(AtomComponentProperties.postfx_layer()) + Report.result( + Tests.postfx_layer_component, + hdr_color_grading_entity.has_component(AtomComponentProperties.postfx_layer())) + + # 7. Verify HDR Color Grading component is enabled. + Report.result(Tests.hdr_color_grading_enabled, hdr_color_grading_component.is_enabled()) + + # 8. Enable the "Enable HDR Color Grading" parameter. + hdr_color_grading_component.set_component_property_value( + AtomComponentProperties.hdr_color_grading('Enable HDR color grading'), True) + Report.result(Tests.enable_hdr_color_grading_parameter_enabled, + hdr_color_grading_component.get_component_property_value( + AtomComponentProperties.hdr_color_grading('Enable HDR color grading')) is True) + + # 9. Enter/Exit game mode. + TestHelper.enter_game_mode(Tests.enter_game_mode) + general.idle_wait_frames(1) + TestHelper.exit_game_mode(Tests.exit_game_mode) + + # 10. Test IsHidden. + hdr_color_grading_entity.set_visibility_state(False) + Report.result(Tests.is_hidden, hdr_color_grading_entity.is_hidden() is True) + + # 11. Test IsVisible. + hdr_color_grading_entity.set_visibility_state(True) + general.idle_wait_frames(1) + Report.result(Tests.is_visible, hdr_color_grading_entity.is_visible() is True) + + # 12. Delete HDR Color Grading entity. + hdr_color_grading_entity.delete() + Report.result(Tests.entity_deleted, not hdr_color_grading_entity.exists()) + + # 13. UNDO deletion. + general.undo() + Report.result(Tests.deletion_undo, hdr_color_grading_entity.exists()) + + # 14. REDO deletion. + general.redo() + Report.result(Tests.deletion_redo, not hdr_color_grading_entity.exists()) + + # 15. Look for errors and asserts. + TestHelper.wait_for_condition(lambda: error_tracer.has_errors or error_tracer.has_asserts, 1.0) + for error_info in error_tracer.errors: + Report.info(f"Error: {error_info.filename} {error_info.function} | {error_info.message}") + for assert_info in error_tracer.asserts: + Report.info(f"Assert: {assert_info.filename} {assert_info.function} | {assert_info.message}") + + +if __name__ == "__main__": + from editor_python_test_tools.utils import Report + Report.start_test(AtomEditorComponents_HDRColorGrading_AddedToEntity) diff --git a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_LightAdded.py b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_LightAdded.py index 8b1432f1f7..249671556d 100644 --- a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_LightAdded.py +++ b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_LightAdded.py @@ -5,24 +5,49 @@ For complete copyright and license terms please see the LICENSE at the root of t SPDX-License-Identifier: Apache-2.0 OR MIT """ -# fmt: off class Tests: - camera_creation = ("Camera Entity successfully created", "Camera Entity failed to be created") - camera_component_added = ("Camera component was added to entity", "Camera component failed to be added to entity") - camera_component_check = ("Entity has a Camera component", "Entity failed to find Camera component") - creation_undo = ("UNDO Entity creation success", "UNDO Entity creation failed") - creation_redo = ("REDO Entity creation success", "REDO Entity creation failed") - light_creation = ("Light Entity successfully created", "Light Entity failed to be created") - light_component = ("Entity has a Light component", "Entity failed to find Light component") - enter_game_mode = ("Entered game mode", "Failed to enter game mode") - exit_game_mode = ("Exited game mode", "Couldn't exit game mode") - is_visible = ("Entity is visible", "Entity was not visible") - is_hidden = ("Entity is hidden", "Entity was not hidden") - entity_deleted = ("Entity deleted", "Entity was not deleted") - deletion_undo = ("UNDO deletion success", "UNDO deletion failed") - deletion_redo = ("REDO deletion success", "REDO deletion failed") - no_error_occurred = ("No errors detected", "Errors were detected") -# fmt: on + camera_creation = ( + "Camera Entity successfully created", + "Camera Entity failed to be created") + camera_component_added = ( + "Camera component was added to entity", + "Camera component failed to be added to entity") + camera_component_check = ( + "Entity has a Camera component", + "Entity failed to find Camera component") + creation_undo = ( + "UNDO Entity creation success", + "UNDO Entity creation failed") + creation_redo = ( + "REDO Entity creation success", + "REDO Entity creation failed") + light_creation = ( + "Light Entity successfully created", + "Light Entity failed to be created") + light_component = ( + "Entity has a Light component", + "Entity failed to find Light component") + enter_game_mode = ( + "Entered game mode", + "Failed to enter game mode") + exit_game_mode = ( + "Exited game mode", + "Couldn't exit game mode") + is_visible = ( + "Entity is visible", + "Entity was not visible") + is_hidden = ( + "Entity is hidden", + "Entity was not hidden") + entity_deleted = ( + "Entity deleted", + "Entity was not deleted") + deletion_undo = ( + "UNDO deletion success", + "UNDO deletion failed") + deletion_redo = ( + "REDO deletion success", + "REDO deletion failed") def AtomEditorComponents_Light_AddedToEntity(): @@ -55,26 +80,25 @@ def AtomEditorComponents_Light_AddedToEntity(): """ import azlmbr.legacy.general as general - import azlmbr.math as math from editor_python_test_tools.editor_entity_utils import EditorEntity - from editor_python_test_tools.utils import Report, Tracer, TestHelper as helper + from editor_python_test_tools.utils import Report, Tracer, TestHelper + from Atom.atom_utils.atom_constants import AtomComponentProperties with Tracer() as error_tracer: # Test setup begins. # Setup: Wait for Editor idle loop before executing Python hydra scripts then open "Base" level. - helper.init_idle() - helper.open_level("", "Base") + TestHelper.init_idle() + TestHelper.open_level("", "Base") # Test steps begin. # 1. Create a Light entity with no components. - light_name = "Light" - light_entity = EditorEntity.create_editor_entity_at(math.Vector3(512.0, 512.0, 34.0), light_name) + light_entity = EditorEntity.create_editor_entity(AtomComponentProperties.light()) Report.critical_result(Tests.light_creation, light_entity.exists()) # 2. Add Light component to the Light entity. - light_entity.add_component(light_name) - Report.critical_result(Tests.light_component, light_entity.has_component(light_name)) + light_component = light_entity.add_component(AtomComponentProperties.light()) + Report.critical_result(Tests.light_component, light_entity.has_component(AtomComponentProperties.light())) # 3. UNDO the entity creation and component addition. # -> UNDO component addition. @@ -101,9 +125,9 @@ def AtomEditorComponents_Light_AddedToEntity(): Report.result(Tests.creation_redo, light_entity.exists()) # 5. Enter/Exit game mode. - helper.enter_game_mode(Tests.enter_game_mode) + TestHelper.enter_game_mode(Tests.enter_game_mode) general.idle_wait_frames(1) - helper.exit_game_mode(Tests.exit_game_mode) + TestHelper.exit_game_mode(Tests.exit_game_mode) # 6. Test IsHidden. light_entity.set_visibility_state(False) @@ -126,9 +150,12 @@ def AtomEditorComponents_Light_AddedToEntity(): general.redo() Report.result(Tests.deletion_redo, not light_entity.exists()) - # 11. Look for errors. - helper.wait_for_condition(lambda: error_tracer.has_errors, 1.0) - Report.result(Tests.no_error_occurred, not error_tracer.has_errors) + # 11. Look for errors asserts. + TestHelper.wait_for_condition(lambda: error_tracer.has_errors or error_tracer.has_asserts, 1.0) + for error_info in error_tracer.errors: + Report.info(f"Error: {error_info.filename} {error_info.function} | {error_info.message}") + for assert_info in error_tracer.asserts: + Report.info(f"Assert: {assert_info.filename} {assert_info.function} | {assert_info.message}") if __name__ == "__main__": diff --git a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_MaterialAdded.py b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_MaterialAdded.py index 32cd5471f2..c613bb23e7 100644 --- a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_MaterialAdded.py +++ b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_MaterialAdded.py @@ -96,6 +96,7 @@ def AtomEditorComponents_Material_AddedToEntity(): from editor_python_test_tools.editor_entity_utils import EditorEntity from editor_python_test_tools.utils import Report, Tracer, TestHelper + from Atom.atom_utils.atom_constants import AtomComponentProperties with Tracer() as error_tracer: # Test setup begins. @@ -105,15 +106,14 @@ def AtomEditorComponents_Material_AddedToEntity(): # Test steps begin. # 1. Create a Material entity with no components. - material_name = "Material" - material_entity = EditorEntity.create_editor_entity(material_name) + material_entity = EditorEntity.create_editor_entity(AtomComponentProperties.material()) Report.critical_result(Tests.material_creation, material_entity.exists()) # 2. Add a Material component to Material entity. - material_component = material_entity.add_component(material_name) + material_component = material_entity.add_component(AtomComponentProperties.material()) Report.critical_result( Tests.material_component, - material_entity.has_component(material_name)) + material_entity.has_component(AtomComponentProperties.material())) # 3. UNDO the entity creation and component addition. # -> UNDO component addition. @@ -143,9 +143,8 @@ def AtomEditorComponents_Material_AddedToEntity(): Report.result(Tests.material_disabled, not material_component.is_enabled()) # 6. Add Actor component since it is required by the Material component. - actor_name = "Actor" - material_entity.add_component(actor_name) - Report.result(Tests.actor_component, material_entity.has_component(actor_name)) + material_entity.add_component(AtomComponentProperties.actor()) + Report.result(Tests.actor_component, material_entity.has_component(AtomComponentProperties.actor())) # 7. Verify Material component is enabled. Report.result(Tests.material_enabled, material_component.is_enabled()) @@ -153,15 +152,14 @@ def AtomEditorComponents_Material_AddedToEntity(): # 8. UNDO component addition. general.undo() general.idle_wait_frames(1) - Report.result(Tests.actor_undo, not material_entity.has_component(actor_name)) + Report.result(Tests.actor_undo, not material_entity.has_component(AtomComponentProperties.actor())) # 9. Verify Material component not enabled. Report.result(Tests.material_disabled, not material_component.is_enabled()) # 10. Add Mesh component since it is required by the Material component. - mesh_name = "Mesh" - material_entity.add_component(mesh_name) - Report.result(Tests.mesh_component, material_entity.has_component(mesh_name)) + material_entity.add_component(AtomComponentProperties.mesh()) + Report.result(Tests.mesh_component, material_entity.has_component(AtomComponentProperties.mesh())) # 11. Verify Material component is enabled. Report.result(Tests.material_enabled, material_component.is_enabled()) diff --git a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_MeshAdded.py b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_MeshAdded.py index 82d3b89309..9d56753961 100644 --- a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_MeshAdded.py +++ b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_MeshAdded.py @@ -81,7 +81,7 @@ def AtomEditorComponents_Mesh_AddedToEntity(): from editor_python_test_tools.asset_utils import Asset from editor_python_test_tools.editor_entity_utils import EditorEntity from editor_python_test_tools.utils import Report, Tracer, TestHelper - from Atom.atom_utils.atom_constants import AtomComponentProperties as Atom + from Atom.atom_utils.atom_constants import AtomComponentProperties with Tracer() as error_tracer: # Test setup begins. @@ -91,14 +91,14 @@ def AtomEditorComponents_Mesh_AddedToEntity(): # Test steps begin. # 1. Create a Mesh entity with no components. - mesh_entity = EditorEntity.create_editor_entity(Atom.mesh()) + mesh_entity = EditorEntity.create_editor_entity(AtomComponentProperties.mesh()) Report.critical_result(Tests.mesh_entity_creation, mesh_entity.exists()) # 2. Add a Mesh component to Mesh entity. - mesh_component = mesh_entity.add_component(Atom.mesh()) + mesh_component = mesh_entity.add_component(AtomComponentProperties.mesh()) Report.critical_result( Tests.mesh_component_added, - mesh_entity.has_component(Atom.mesh())) + mesh_entity.has_component(AtomComponentProperties.mesh())) # 3. UNDO the entity creation and component addition. # -> UNDO component addition. @@ -127,9 +127,9 @@ def AtomEditorComponents_Mesh_AddedToEntity(): # 5. Set Mesh component asset property model_path = os.path.join('Objects', 'shaderball', 'shaderball_default_1m.azmodel') model = Asset.find_asset_by_path(model_path) - mesh_component.set_component_property_value(Atom.mesh('Mesh Asset'), model.id) + mesh_component.set_component_property_value(AtomComponentProperties.mesh('Mesh Asset'), model.id) Report.result(Tests.mesh_asset_specified, - mesh_component.get_component_property_value(Atom.mesh('Mesh Asset')) == model.id) + mesh_component.get_component_property_value(AtomComponentProperties.mesh('Mesh Asset')) == model.id) # 6. Enter/Exit game mode. TestHelper.enter_game_mode(Tests.enter_game_mode) diff --git a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_OcclusionCullingPlaneAdded.py b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_OcclusionCullingPlaneAdded.py new file mode 100644 index 0000000000..4226ae3dfe --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_OcclusionCullingPlaneAdded.py @@ -0,0 +1,159 @@ +""" +Copyright (c) Contributors to the Open 3D Engine Project. +For complete copyright and license terms please see the LICENSE at the root of this distribution. + +SPDX-License-Identifier: Apache-2.0 OR MIT +""" + +class Tests: + creation_undo = ( + "UNDO Entity creation success", + "UNDO Entity creation failed") + creation_redo = ( + "REDO Entity creation success", + "REDO Entity creation failed") + occlusion_culling_plane_entity_creation = ( + "Occlusion Culling Plane Entity successfully created", + "Occlusion Culling Plane Entity failed to be created") + occlusion_culling_plane_component_added = ( + "Entity has a Occlusion Culling Plane component", + "Entity failed to find Occlusion Culling Plane component") + enter_game_mode = ( + "Entered game mode", + "Failed to enter game mode") + exit_game_mode = ( + "Exited game mode", + "Couldn't exit game mode") + is_visible = ( + "Entity is visible", + "Entity was not visible") + is_hidden = ( + "Entity is hidden", + "Entity was not hidden") + entity_deleted = ( + "Entity deleted", + "Entity was not deleted") + deletion_undo = ( + "UNDO deletion success", + "UNDO deletion failed") + deletion_redo = ( + "REDO deletion success", + "REDO deletion failed") + + +def AtomEditorComponents_OcclusionCullingPlane_AddedToEntity(): + """ + Summary: + Tests the occlusion culling plane component can be added to an entity and has the expected functionality. + + Test setup: + - Wait for Editor idle loop. + - Open the "Base" level. + + Expected Behavior: + The component can be added, used in game mode, hidden/shown, deleted, and has accurate required components. + Creation and deletion undo/redo should also work. + + Test Steps: + 1) Create a Occlusion Culling Plane entity with no components. + 2) Add a Occlusion Culling Plane component to Occlusion Culling Plane entity. + 3) UNDO the entity creation and component addition. + 4) REDO the entity creation and component addition. + 5) Enter/Exit game mode. + 6) Test IsHidden. + 7) Test IsVisible. + 8) Delete Occlusion Culling Plane entity. + 9) UNDO deletion. + 10) REDO deletion. + 11) Look for errors. + + :return: None + """ + + import azlmbr.legacy.general as general + + from editor_python_test_tools.editor_entity_utils import EditorEntity + from editor_python_test_tools.utils import Report, Tracer, TestHelper + from Atom.atom_utils.atom_constants import AtomComponentProperties + + with Tracer() as error_tracer: + # Test setup begins. + # Setup: Wait for Editor idle loop before executing Python hydra scripts then open "Base" level. + TestHelper.init_idle() + TestHelper.open_level("", "Base") + + # Test steps begin. + # 1. Create a occlusion culling plane entity with no components. + occlusion_culling_plane_entity = EditorEntity.create_editor_entity( + AtomComponentProperties.occlusion_culling_plane()) + Report.critical_result(Tests.occlusion_culling_plane_entity_creation, + occlusion_culling_plane_entity.exists()) + + # 2. Add a occlusion culling plane component to occlusion culling plane entity. + occlusion_culling_plane_component = occlusion_culling_plane_entity.add_component( + AtomComponentProperties.occlusion_culling_plane()) + Report.critical_result( + Tests.occlusion_culling_plane_component_added, + occlusion_culling_plane_entity.has_component(AtomComponentProperties.occlusion_culling_plane())) + + # 3. UNDO the entity creation and component addition. + # -> UNDO component addition. + general.undo() + # -> UNDO naming entity. + general.undo() + # -> UNDO selecting entity. + general.undo() + # -> UNDO entity creation. + general.undo() + general.idle_wait_frames(1) + Report.result(Tests.creation_undo, not occlusion_culling_plane_entity.exists()) + + # 4. REDO the entity creation and component addition. + # -> REDO entity creation. + general.redo() + # -> REDO selecting entity. + general.redo() + # -> REDO naming entity. + general.redo() + # -> REDO component addition. + general.redo() + general.idle_wait_frames(1) + Report.result(Tests.creation_redo, occlusion_culling_plane_entity.exists()) + + # 5. Enter/Exit game mode. + TestHelper.enter_game_mode(Tests.enter_game_mode) + general.idle_wait_frames(1) + TestHelper.exit_game_mode(Tests.exit_game_mode) + + # 6. Test IsHidden. + occlusion_culling_plane_entity.set_visibility_state(False) + Report.result(Tests.is_hidden, occlusion_culling_plane_entity.is_hidden() is True) + + # 7. Test IsVisible. + occlusion_culling_plane_entity.set_visibility_state(True) + general.idle_wait_frames(1) + Report.result(Tests.is_visible, occlusion_culling_plane_entity.is_visible() is True) + + # 8. Delete occlusion_culling_plane entity. + occlusion_culling_plane_entity.delete() + Report.result(Tests.entity_deleted, not occlusion_culling_plane_entity.exists()) + + # 9. UNDO deletion. + general.undo() + Report.result(Tests.deletion_undo, occlusion_culling_plane_entity.exists()) + + # 10. REDO deletion. + general.redo() + Report.result(Tests.deletion_redo, not occlusion_culling_plane_entity.exists()) + + # 11. Look for errors or asserts. + TestHelper.wait_for_condition(lambda: error_tracer.has_errors or error_tracer.has_asserts, 1.0) + for error_info in error_tracer.errors: + Report.info(f"Error: {error_info.filename} {error_info.function} | {error_info.message}") + for assert_info in error_tracer.asserts: + Report.info(f"Assert: {assert_info.filename} {assert_info.function} | {assert_info.message}") + + +if __name__ == "__main__": + from editor_python_test_tools.utils import Report + Report.start_test(AtomEditorComponents_OcclusionCullingPlane_AddedToEntity) diff --git a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_PhysicalSkyAdded.py b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_PhysicalSkyAdded.py index 04441d5b2c..fa8c626016 100644 --- a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_PhysicalSkyAdded.py +++ b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_PhysicalSkyAdded.py @@ -5,24 +5,49 @@ For complete copyright and license terms please see the LICENSE at the root of t SPDX-License-Identifier: Apache-2.0 OR MIT """ -# fmt: off class Tests: - camera_creation = ("Camera Entity successfully created", "Camera Entity failed to be created") - camera_component_added = ("Camera component was added to entity", "Camera component failed to be added to entity") - camera_component_check = ("Entity has a Camera component", "Entity failed to find Camera component") - creation_undo = ("UNDO Entity creation success", "UNDO Entity creation failed") - creation_redo = ("REDO Entity creation success", "REDO Entity creation failed") - physical_sky_creation = ("Physical Sky Entity successfully created", "Physical Sky Entity failed to be created") - physical_sky_component = ("Entity has a Physical Sky component", "Entity failed to find Physical Sky component") - enter_game_mode = ("Entered game mode", "Failed to enter game mode") - exit_game_mode = ("Exited game mode", "Couldn't exit game mode") - is_visible = ("Entity is visible", "Entity was not visible") - is_hidden = ("Entity is hidden", "Entity was not hidden") - entity_deleted = ("Entity deleted", "Entity was not deleted") - deletion_undo = ("UNDO deletion success", "UNDO deletion failed") - deletion_redo = ("REDO deletion success", "REDO deletion failed") - no_error_occurred = ("No errors detected", "Errors were detected") -# fmt: on + camera_creation = ( + "Camera Entity successfully created", + "Camera Entity failed to be created") + camera_component_added = ( + "Camera component was added to entity", + "Camera component failed to be added to entity") + camera_component_check = ( + "Entity has a Camera component", + "Entity failed to find Camera component") + creation_undo = ( + "UNDO Entity creation success", + "UNDO Entity creation failed") + creation_redo = ( + "REDO Entity creation success", + "REDO Entity creation failed") + physical_sky_creation = ( + "Physical Sky Entity successfully created", + "Physical Sky Entity failed to be created") + physical_sky_component = ( + "Entity has a Physical Sky component", + "Entity failed to find Physical Sky component") + enter_game_mode = ( + "Entered game mode", + "Failed to enter game mode") + exit_game_mode = ( + "Exited game mode", + "Couldn't exit game mode") + is_visible = ( + "Entity is visible", + "Entity was not visible") + is_hidden = ( + "Entity is hidden", + "Entity was not hidden") + entity_deleted = ( + "Entity deleted", + "Entity was not deleted") + deletion_undo = ( + "UNDO deletion success", + "UNDO deletion failed") + deletion_redo = ( + "REDO deletion success", + "REDO deletion failed") def AtomEditorComponents_PhysicalSky_AddedToEntity(): @@ -49,32 +74,33 @@ def AtomEditorComponents_PhysicalSky_AddedToEntity(): 8) Delete Physical Sky entity. 9) UNDO deletion. 10) REDO deletion. - 11) Look for errors. + 11) Look for errors and asserts. :return: None """ import azlmbr.legacy.general as general - import azlmbr.math as math from editor_python_test_tools.editor_entity_utils import EditorEntity - from editor_python_test_tools.utils import Report, Tracer, TestHelper as helper + from editor_python_test_tools.utils import Report, Tracer, TestHelper + from Atom.atom_utils.atom_constants import AtomComponentProperties with Tracer() as error_tracer: # Test setup begins. # Setup: Wait for Editor idle loop before executing Python hydra scripts then open "Base" level. - helper.init_idle() - helper.open_level("", "Base") + TestHelper.init_idle() + TestHelper.open_level("", "Base") # Test steps begin. # 1. Create a Physical Sky entity with no components. - physical_sky_name = "Physical Sky" - physical_sky_entity = EditorEntity.create_editor_entity_at(math.Vector3(512.0, 512.0, 34.0), physical_sky_name) + physical_sky_entity = EditorEntity.create_editor_entity(AtomComponentProperties.physical_sky()) Report.critical_result(Tests.physical_sky_creation, physical_sky_entity.exists()) # 2. Add Physical Sky component to Physical Sky entity. - physical_sky_entity.add_component(physical_sky_name) - Report.critical_result(Tests.physical_sky_component, physical_sky_entity.has_component(physical_sky_name)) + physical_sky_component = physical_sky_entity.add_component(AtomComponentProperties.physical_sky()) + Report.critical_result( + Tests.physical_sky_component, + physical_sky_entity.has_component(AtomComponentProperties.physical_sky())) # 3. UNDO the entity creation and component addition. # -> UNDO component addition. @@ -101,9 +127,9 @@ def AtomEditorComponents_PhysicalSky_AddedToEntity(): Report.result(Tests.creation_redo, physical_sky_entity.exists()) # 5. Enter/Exit game mode. - helper.enter_game_mode(Tests.enter_game_mode) + TestHelper.enter_game_mode(Tests.enter_game_mode) general.idle_wait_frames(1) - helper.exit_game_mode(Tests.exit_game_mode) + TestHelper.exit_game_mode(Tests.exit_game_mode) # 6. Test IsHidden. physical_sky_entity.set_visibility_state(False) @@ -126,9 +152,12 @@ def AtomEditorComponents_PhysicalSky_AddedToEntity(): general.redo() Report.result(Tests.deletion_redo, not physical_sky_entity.exists()) - # 11. Look for errors. - helper.wait_for_condition(lambda: error_tracer.has_errors, 1.0) - Report.result(Tests.no_error_occurred, not error_tracer.has_errors) + # 11. Look for errors and asserts. + TestHelper.wait_for_condition(lambda: error_tracer.has_errors or error_tracer.has_asserts, 1.0) + for error_info in error_tracer.errors: + Report.info(f"Error: {error_info.filename} {error_info.function} | {error_info.message}") + for assert_info in error_tracer.asserts: + Report.info(f"Assert: {assert_info.filename} {assert_info.function} | {assert_info.message}") if __name__ == "__main__": diff --git a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_PostFXGradientWeightModifierAdded.py b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_PostFXGradientWeightModifierAdded.py index d38e96739d..6e4ae2d9ac 100644 --- a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_PostFXGradientWeightModifierAdded.py +++ b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_PostFXGradientWeightModifierAdded.py @@ -86,6 +86,7 @@ def AtomEditorComponents_PostFXGradientWeightModifier_AddedToEntity(): from editor_python_test_tools.editor_entity_utils import EditorEntity from editor_python_test_tools.utils import Report, Tracer, TestHelper + from Atom.atom_utils.atom_constants import AtomComponentProperties with Tracer() as error_tracer: # Test setup begins. @@ -95,15 +96,15 @@ def AtomEditorComponents_PostFXGradientWeightModifier_AddedToEntity(): # Test steps begin. # 1. Create a PostFX Gradient Weight Modifier entity with no components. - postfx_gradient_weight_name = "PostFX Gradient Weight Modifier" - postfx_gradient_weight_entity = EditorEntity.create_editor_entity(postfx_gradient_weight_name) + postfx_gradient_weight_entity = EditorEntity.create_editor_entity(AtomComponentProperties.postfx_gradient()) Report.critical_result(Tests.postfx_gradient_weight_creation, postfx_gradient_weight_entity.exists()) # 2. Add a PostFX Gradient Weight Modifier component to PostFX Gradient Weight Modifier entity. - postfx_gradient_weight_component = postfx_gradient_weight_entity.add_component(postfx_gradient_weight_name) + postfx_gradient_weight_component = postfx_gradient_weight_entity.add_component( + AtomComponentProperties.postfx_gradient()) Report.critical_result( Tests.postfx_gradient_weight_component, - postfx_gradient_weight_entity.has_component(postfx_gradient_weight_name)) + postfx_gradient_weight_entity.has_component(AtomComponentProperties.postfx_gradient())) # 3. UNDO the entity creation and component addition. # -> UNDO component addition. @@ -133,9 +134,10 @@ def AtomEditorComponents_PostFXGradientWeightModifier_AddedToEntity(): Report.result(Tests.postfx_gradient_weight_disabled, not postfx_gradient_weight_component.is_enabled()) # 6. Add PostFX Layer component since it is required by the PostFX Gradient Weight Modifier component. - postfx_layer_name = "PostFX Layer" - postfx_gradient_weight_entity.add_component(postfx_layer_name) - Report.result(Tests.postfx_layer_component, postfx_gradient_weight_entity.has_component(postfx_layer_name)) + postfx_gradient_weight_entity.add_component(AtomComponentProperties.postfx_layer()) + Report.result( + Tests.postfx_layer_component, + postfx_gradient_weight_entity.has_component(AtomComponentProperties.postfx_layer())) # 7. Verify PostFX Gradient Weight Modifier component is enabled. Report.result(Tests.postfx_gradient_weight_enabled, postfx_gradient_weight_component.is_enabled()) diff --git a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_PostFXLayerAdded.py b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_PostFXLayerAdded.py index 8c2dee416b..efef4c0a43 100644 --- a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_PostFXLayerAdded.py +++ b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_PostFXLayerAdded.py @@ -70,12 +70,11 @@ def AtomEditorComponents_postfx_layer_AddedToEntity(): :return: None """ - import os - import azlmbr.legacy.general as general from editor_python_test_tools.editor_entity_utils import EditorEntity from editor_python_test_tools.utils import Report, Tracer, TestHelper + from Atom.atom_utils.atom_constants import AtomComponentProperties with Tracer() as error_tracer: # Test setup begins. @@ -85,13 +84,14 @@ def AtomEditorComponents_postfx_layer_AddedToEntity(): # Test steps begin. # 1. Create a PostFX Layer entity with no components. - postfx_layer_name = "PostFX Layer" - postfx_layer_entity = EditorEntity.create_editor_entity(postfx_layer_name) + postfx_layer_entity = EditorEntity.create_editor_entity(AtomComponentProperties.postfx_layer()) Report.critical_result(Tests.postfx_layer_entity_creation, postfx_layer_entity.exists()) # 2. Add a PostFX Layer component to PostFX Layer entity. - postfx_layer_component = postfx_layer_entity.add_component(postfx_layer_name) - Report.critical_result(Tests.postfx_layer_component_added, postfx_layer_entity.has_component(postfx_layer_name)) + postfx_layer_component = postfx_layer_entity.add_component(AtomComponentProperties.postfx_layer()) + Report.critical_result( + Tests.postfx_layer_component_added, + postfx_layer_entity.has_component(AtomComponentProperties.postfx_layer())) # 3. UNDO the entity creation and component addition. # -> UNDO component addition. diff --git a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_PostFXRadiusWeightModifierAdded.py b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_PostFXRadiusWeightModifierAdded.py index 8914ab9e7e..228ddfcdc5 100644 --- a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_PostFXRadiusWeightModifierAdded.py +++ b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_PostFXRadiusWeightModifierAdded.py @@ -5,24 +5,49 @@ For complete copyright and license terms please see the LICENSE at the root of t SPDX-License-Identifier: Apache-2.0 OR MIT """ -# fmt: off class Tests: - camera_creation = ("Camera Entity successfully created", "Camera Entity failed to be created") - camera_component_added = ("Camera component was added to entity", "Camera component failed to be added to entity") - camera_component_check = ("Entity has a Camera component", "Entity failed to find Camera component") - creation_undo = ("UNDO Entity creation success", "UNDO Entity creation failed") - creation_redo = ("REDO Entity creation success", "REDO Entity creation failed") - postfx_radius_weight_creation = ("PostFX Radius Weight Modifier Entity successfully created", "PostFX Radius Weight Modifier Entity failed to be created") - postfx_radius_weight_component = ("Entity has a PostFX Radius Weight Modifier component", "Entity failed to find PostFX Radius Weight Modifier component") - enter_game_mode = ("Entered game mode", "Failed to enter game mode") - exit_game_mode = ("Exited game mode", "Couldn't exit game mode") - is_visible = ("Entity is visible", "Entity was not visible") - is_hidden = ("Entity is hidden", "Entity was not hidden") - entity_deleted = ("Entity deleted", "Entity was not deleted") - deletion_undo = ("UNDO deletion success", "UNDO deletion failed") - deletion_redo = ("REDO deletion success", "REDO deletion failed") - no_error_occurred = ("No errors detected", "Errors were detected") -# fmt: on + creation_undo = ( + "UNDO Entity creation success", + "UNDO Entity creation failed") + creation_redo = ( + "REDO Entity creation success", + "REDO Entity creation failed") + postfx_radius_weight_creation = ( + "PostFX Radius Weight Modifier Entity successfully created", + "PostFX Radius Weight Modifier Entity failed to be created") + postfx_radius_weight_component = ( + "Entity has a PostFX Radius Weight Modifier component", + "Entity failed to find PostFX Radius Weight Modifier component") + postfx_radius_weight_disabled = ( + "PostFX Radius Weight Modifier component disabled", + "PostFX Radius Weight Modifier component was not disabled.") + postfx_layer_component = ( + "Entity has a PostFX Layer component", + "Entity did not have an PostFX Layer component") + postfx_radius_weight_enabled = ( + "PostFX Radius Weight Modifier component enabled", + "PostFX Radius Weight Modifier component was not enabled.") + enter_game_mode = ( + "Entered game mode", + "Failed to enter game mode") + exit_game_mode = ( + "Exited game mode", + "Couldn't exit game mode") + is_visible = ( + "Entity is visible", + "Entity was not visible") + is_hidden = ( + "Entity is hidden", + "Entity was not hidden") + entity_deleted = ( + "Entity deleted", + "Entity was not deleted") + deletion_undo = ( + "UNDO deletion success", + "UNDO deletion failed") + deletion_redo = ( + "REDO deletion success", + "REDO deletion failed") def AtomEditorComponents_PostFXRadiusWeightModifier_AddedToEntity(): @@ -43,40 +68,42 @@ def AtomEditorComponents_PostFXRadiusWeightModifier_AddedToEntity(): 2) Add Post FX Radius Weight Modifier component to Post FX Radius Weight Modifier entity. 3) UNDO the entity creation and component addition. 4) REDO the entity creation and component addition. - 5) Enter/Exit game mode. - 6) Test IsHidden. - 7) Test IsVisible. - 8) Delete PostFX Radius Weight Modifier entity. - 9) UNDO deletion. - 10) REDO deletion. - 11) Look for errors. + 5) Verify PostFX Radius Weight Modifier component not enabled. + 6) Add PostFX Layer component since it is required by the PostFX Radius Weight Modifier component. + 7) Verify PostFX Radius Weight Modifier component is enabled. + 8) Enter/Exit game mode. + 9) Test IsHidden. + 10) Test IsVisible. + 11) Delete PostFX Radius Weight Modifier entity. + 12) UNDO deletion. + 13) REDO deletion. + 14) Look for errors. :return: None """ import azlmbr.legacy.general as general - import azlmbr.math as math from editor_python_test_tools.editor_entity_utils import EditorEntity - from editor_python_test_tools.utils import Report, Tracer, TestHelper as helper + from editor_python_test_tools.utils import Report, Tracer, TestHelper + from Atom.atom_utils.atom_constants import AtomComponentProperties with Tracer() as error_tracer: # Test setup begins. # Setup: Wait for Editor idle loop before executing Python hydra scripts then open "Base" level. - helper.init_idle() - helper.open_level("", "Base") + TestHelper.init_idle() + TestHelper.open_level("", "Base") # Test steps begin. # 1. Create a Post FX Radius Weight Modifier entity with no components. - postfx_radius_weight_name = "PostFX Radius Weight Modifier" - postfx_radius_weight_entity = EditorEntity.create_editor_entity_at( - math.Vector3(512.0, 512.0, 34.0), postfx_radius_weight_name) + postfx_radius_weight_entity = EditorEntity.create_editor_entity(AtomComponentProperties.postfx_radius()) Report.critical_result(Tests.postfx_radius_weight_creation, postfx_radius_weight_entity.exists()) # 2. Add Post FX Radius Weight Modifier component to Post FX Radius Weight Modifier entity. - postfx_radius_weight_entity.add_component(postfx_radius_weight_name) + postfx_radius_component = postfx_radius_weight_entity.add_component(AtomComponentProperties.postfx_radius()) Report.critical_result( - Tests.postfx_radius_weight_component, postfx_radius_weight_entity.has_component(postfx_radius_weight_name)) + Tests.postfx_radius_weight_component, + postfx_radius_weight_entity.has_component(AtomComponentProperties.postfx_radius())) # 3. UNDO the entity creation and component addition. # -> UNDO component addition. @@ -102,35 +129,50 @@ def AtomEditorComponents_PostFXRadiusWeightModifier_AddedToEntity(): general.idle_wait_frames(1) Report.result(Tests.creation_redo, postfx_radius_weight_entity.exists()) - # 5. Enter/Exit game mode. - helper.enter_game_mode(Tests.enter_game_mode) - general.idle_wait_frames(1) - helper.exit_game_mode(Tests.exit_game_mode) + # 5. Verify PostFX Radius Weight Modifier component not enabled. + Report.result(Tests.postfx_radius_weight_disabled, not postfx_radius_component.is_enabled()) - # 6. Test IsHidden. + # 6. Add PostFX Layer component since it is required by the PostFX Radius Weight Modifier component. + postfx_radius_weight_entity.add_component(AtomComponentProperties.postfx_layer()) + Report.result( + Tests.postfx_layer_component, + postfx_radius_weight_entity.has_component(AtomComponentProperties.postfx_layer())) + + # 7. Verify PostFX Radius Weight Modifier component is enabled. + Report.result(Tests.postfx_radius_weight_enabled, postfx_radius_component.is_enabled()) + + # 8. Enter/Exit game mode. + TestHelper.enter_game_mode(Tests.enter_game_mode) + general.idle_wait_frames(1) + TestHelper.exit_game_mode(Tests.exit_game_mode) + + # 9. Test IsHidden. postfx_radius_weight_entity.set_visibility_state(False) Report.result(Tests.is_hidden, postfx_radius_weight_entity.is_hidden() is True) - # 7. Test IsVisible. + # 10. Test IsVisible. postfx_radius_weight_entity.set_visibility_state(True) general.idle_wait_frames(1) Report.result(Tests.is_visible, postfx_radius_weight_entity.is_visible() is True) - # 8. Delete PostFX Radius Weight Modifier entity. + # 11. Delete PostFX Radius Weight Modifier entity. postfx_radius_weight_entity.delete() Report.result(Tests.entity_deleted, not postfx_radius_weight_entity.exists()) - # 9. UNDO deletion. + # 12. UNDO deletion. general.undo() Report.result(Tests.deletion_undo, postfx_radius_weight_entity.exists()) - # 10. REDO deletion. + # 13. REDO deletion. general.redo() Report.result(Tests.deletion_redo, not postfx_radius_weight_entity.exists()) - # 11. Look for errors. - helper.wait_for_condition(lambda: error_tracer.has_errors, 1.0) - Report.result(Tests.no_error_occurred, not error_tracer.has_errors) + # 14. Look for errors and asserts. + TestHelper.wait_for_condition(lambda: error_tracer.has_errors or error_tracer.has_asserts, 1.0) + for error_info in error_tracer.errors: + Report.info(f"Error: {error_info.filename} {error_info.function} | {error_info.message}") + for assert_info in error_tracer.asserts: + Report.info(f"Assert: {assert_info.filename} {assert_info.function} | {assert_info.message}") if __name__ == "__main__": diff --git a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_PostFxShapeWeightModifierAdded.py b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_PostFxShapeWeightModifierAdded.py index 4c98bcd130..0a37ac6bf7 100644 --- a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_PostFxShapeWeightModifierAdded.py +++ b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_PostFxShapeWeightModifierAdded.py @@ -92,6 +92,7 @@ def AtomEditorComponents_postfx_shape_weight_AddedToEntity(): from editor_python_test_tools.editor_entity_utils import EditorEntity from editor_python_test_tools.utils import Report, Tracer, TestHelper + from Atom.atom_utils.atom_constants import AtomComponentProperties with Tracer() as error_tracer: # Test setup begins. @@ -101,15 +102,14 @@ def AtomEditorComponents_postfx_shape_weight_AddedToEntity(): # Test steps begin. # 1. Create a PostFx Shape Weight Modifier entity with no components. - postfx_shape_weight_name = "PostFX Shape Weight Modifier" - postfx_shape_weight_entity = EditorEntity.create_editor_entity(postfx_shape_weight_name) + postfx_shape_weight_entity = EditorEntity.create_editor_entity(AtomComponentProperties.postfx_shape()) Report.critical_result(Tests.postfx_shape_weight_creation, postfx_shape_weight_entity.exists()) # 2. Add a PostFx Shape Weight Modifier component to PostFx Shape Weight Modifier entity. - postfx_shape_weight_component = postfx_shape_weight_entity.add_component(postfx_shape_weight_name) + postfx_shape_weight_component = postfx_shape_weight_entity.add_component(AtomComponentProperties.postfx_shape()) Report.critical_result( Tests.postfx_shape_weight_component, - postfx_shape_weight_entity.has_component(postfx_shape_weight_name)) + postfx_shape_weight_entity.has_component(AtomComponentProperties.postfx_shape())) # 3. UNDO the entity creation and component addition. # -> UNDO component addition. @@ -139,16 +139,16 @@ def AtomEditorComponents_postfx_shape_weight_AddedToEntity(): Report.result(Tests.postfx_shape_weight_disabled, not postfx_shape_weight_component.is_enabled()) # 6. Add PostFX Layer component since it is required by the PostFx Shape Weight Modifier component. - postfx_layer_name = "PostFX Layer" - postfx_shape_weight_entity.add_component(postfx_layer_name) - Report.result(Tests.postfx_layer_component, postfx_shape_weight_entity.has_component(postfx_layer_name)) + postfx_shape_weight_entity.add_component(AtomComponentProperties.postfx_layer()) + Report.result( + Tests.postfx_layer_component, + postfx_shape_weight_entity.has_component(AtomComponentProperties.postfx_layer())) # 7. Verify PostFx Shape Weight Modifier component is NOT enabled since it also requires a shape. Report.result(Tests.postfx_shape_weight_disabled, not postfx_shape_weight_component.is_enabled()) # 8. Add a required shape looping over a list and checking if it enables PostFX Shape Weight Modifier. - for shape in ['Axis Aligned Box Shape', 'Box Shape', 'Capsule Shape', 'Compound Shape', 'Cylinder Shape', - 'Disk Shape', 'Polygon Prism Shape', 'Quad Shape', 'Sphere Shape', 'Vegetation Reference Shape']: + for shape in AtomComponentProperties.postfx_shape('shapes'): postfx_shape_weight_entity.add_component(shape) test_shape = ( f"Entity has a {shape} component", diff --git a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_ReflectionProbeAdded.py b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_ReflectionProbeAdded.py index 9b13eb2c7e..70adf9143a 100644 --- a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_ReflectionProbeAdded.py +++ b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_ReflectionProbeAdded.py @@ -87,30 +87,28 @@ def AtomEditorComponents_ReflectionProbe_AddedToEntity(): """ import azlmbr.legacy.general as general - import azlmbr.math as math import azlmbr.render as render from editor_python_test_tools.editor_entity_utils import EditorEntity - from editor_python_test_tools.utils import Report, Tracer, TestHelper as helper + from editor_python_test_tools.utils import Report, Tracer, TestHelper + from Atom.atom_utils.atom_constants import AtomComponentProperties with Tracer() as error_tracer: # Test setup begins. # Setup: Wait for Editor idle loop before executing Python hydra scripts then open "Base" level. - helper.init_idle() - helper.open_level("", "Base") + TestHelper.init_idle() + TestHelper.open_level("", "Base") # Test steps begin. # 1. Create a Reflection Probe entity with no components. - reflection_probe_name = "Reflection Probe" - reflection_probe_entity = EditorEntity.create_editor_entity_at( - math.Vector3(512.0, 512.0, 34.0), reflection_probe_name) + reflection_probe_entity = EditorEntity.create_editor_entity(AtomComponentProperties.reflection_probe()) Report.critical_result(Tests.reflection_probe_creation, reflection_probe_entity.exists()) # 2. Add a Reflection Probe component to Reflection Probe entity. - reflection_probe_component = reflection_probe_entity.add_component(reflection_probe_name) + reflection_probe_component = reflection_probe_entity.add_component(AtomComponentProperties.reflection_probe()) Report.critical_result( Tests.reflection_probe_component, - reflection_probe_entity.has_component(reflection_probe_name)) + reflection_probe_entity.has_component(AtomComponentProperties.reflection_probe())) # 3. UNDO the entity creation and component addition. # -> UNDO component addition. @@ -139,18 +137,27 @@ def AtomEditorComponents_ReflectionProbe_AddedToEntity(): # 5. Verify Reflection Probe component not enabled. Report.result(Tests.reflection_probe_disabled, not reflection_probe_component.is_enabled()) - # 6. Add Box Shape component since it is required by the Reflection Probe component. - box_shape = "Box Shape" - reflection_probe_entity.add_component(box_shape) - Report.result(Tests.box_shape_component, reflection_probe_entity.has_component(box_shape)) + # 6. Add Shape component since it is required by the Reflection Probe component. + for shape in AtomComponentProperties.reflection_probe('shapes'): + reflection_probe_entity.add_component(shape) + test_shape = ( + f"Entity has a {shape} component", + f"Entity did not have a {shape} component") + Report.result(test_shape, reflection_probe_entity.has_component(shape)) - # 7. Verify Reflection Probe component is enabled. - Report.result(Tests.reflection_probe_enabled, reflection_probe_component.is_enabled()) + # 7. Check if required shape allows Reflection Probe to be enabled + Report.result(Tests.reflection_probe_enabled, reflection_probe_component.is_enabled()) + + # Undo to remove each added shape except the last one and verify Reflection Probe is not enabled. + if not (shape == AtomComponentProperties.reflection_probe('shapes')[-1]): + general.undo() + TestHelper.wait_for_condition(lambda: not reflection_probe_entity.has_component(shape), 1.0) + Report.result(Tests.reflection_probe_disabled, not reflection_probe_component.is_enabled()) # 8. Enter/Exit game mode. - helper.enter_game_mode(Tests.enter_game_mode) + TestHelper.enter_game_mode(Tests.enter_game_mode) general.idle_wait_frames(1) - helper.exit_game_mode(Tests.exit_game_mode) + TestHelper.exit_game_mode(Tests.exit_game_mode) # 9. Test IsHidden. reflection_probe_entity.set_visibility_state(False) @@ -165,8 +172,9 @@ def AtomEditorComponents_ReflectionProbe_AddedToEntity(): render.EditorReflectionProbeBus(azlmbr.bus.Event, "BakeReflectionProbe", reflection_probe_entity.id) Report.result( Tests.reflection_map_generated, - helper.wait_for_condition( - lambda: reflection_probe_component.get_component_property_value("Cubemap|Baked Cubemap Path") != "", + TestHelper.wait_for_condition( + lambda: reflection_probe_component.get_component_property_value( + AtomComponentProperties.reflection_probe('Baked Cubemap Path')) != "", 20.0)) # 12. Delete Reflection Probe entity. @@ -182,7 +190,7 @@ def AtomEditorComponents_ReflectionProbe_AddedToEntity(): Report.result(Tests.deletion_redo, not reflection_probe_entity.exists()) # 15. Look for errors or asserts. - helper.wait_for_condition(lambda: error_tracer.has_errors or error_tracer.has_asserts, 1.0) + TestHelper.wait_for_condition(lambda: error_tracer.has_errors or error_tracer.has_asserts, 1.0) for error_info in error_tracer.errors: Report.info(f"Error: {error_info.filename} {error_info.function} | {error_info.message}") for assert_info in error_tracer.asserts: diff --git a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_GPUTest_BasicLevelSetup.py b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_GPUTest_BasicLevelSetup.py index 9515712583..645447e6de 100644 --- a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_GPUTest_BasicLevelSetup.py +++ b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_GPUTest_BasicLevelSetup.py @@ -6,18 +6,6 @@ SPDX-License-Identifier: Apache-2.0 OR MIT """ import os -import sys - -import azlmbr.asset as asset -import azlmbr.bus as bus -import azlmbr.camera -import azlmbr.entity as entity -import azlmbr.legacy.general as general -import azlmbr.math as math -import azlmbr.paths -import azlmbr.editor as editor - -sys.path.append(os.path.join(azlmbr.paths.projectroot, "Gem", "PythonTests")) import editor_python_test_tools.hydra_editor_utils as hydra from editor_python_test_tools.editor_test_helper import EditorTestHelper @@ -45,9 +33,18 @@ def run(): 11. Adds a "camera" entity to "default_level" & adds a Camera component with 80 degree FOV and Transform values: Translate - x:5.5m, y:-12.0m, z:9.0m Rotate - x:-27.0, y:-12.0, z:25.0 - 12. Finally enters game mode, takes a screenshot, exits game mode, & saves the level. + 12. Finally enters game mode, takes a screenshot, & exits game mode. :return: None """ + import azlmbr.asset as asset + import azlmbr.bus as bus + import azlmbr.camera as camera + import azlmbr.entity as entity + import azlmbr.legacy.general as general + import azlmbr.math as math + import azlmbr.paths + import azlmbr.editor as editor + def initial_viewport_setup(screen_width, screen_height): general.set_viewport_size(screen_width, screen_height) general.update_viewport() @@ -84,33 +81,11 @@ def run(): general.run_console("r_displayInfo=0") general.idle_wait(1.0) - return True - # Wait for Editor idle loop before executing Python hydra scripts. general.idle_enable(True) - # Open the auto_test level. - new_level_name = "auto_test" # Specified in class TestAllComponentsIndepthTests() - heightmap_resolution = 512 - heightmap_meters_per_pixel = 1 - terrain_texture_resolution = 412 - use_terrain = False - - # Return codes are ECreateLevelResult defined in CryEdit.h - return_code = general.create_level_no_prompt( - new_level_name, heightmap_resolution, heightmap_meters_per_pixel, terrain_texture_resolution, use_terrain) - if return_code == 1: - general.log(f"{new_level_name} level already exists") - elif return_code == 2: - general.log("Failed to create directory") - elif return_code == 3: - general.log("Directory length is too long") - elif return_code != 0: - general.log("Unknown error, failed to create level") - else: - general.log(f"{new_level_name} level created successfully") - - # Basic setup for newly created level. + # Basic setup for opened level. + helper.open_level(level_name="Base") after_level_load() initial_viewport_setup(SCREEN_WIDTH, SCREEN_HEIGHT) @@ -147,22 +122,25 @@ def run(): parent_id=default_level.id ) azlmbr.components.TransformBus(azlmbr.bus.Event, "SetLocalUniformScale", ground_plane.id, 32.0) - ground_plane_material_asset_path = os.path.join("Materials", "Presets", "PBR", "metal_chrome.azmaterial") - ground_plane_material_asset = asset.AssetCatalogRequestBus( - bus.Broadcast, "GetAssetIdByPath", ground_plane_material_asset_path, math.Uuid(), False) - ground_plane.get_set_test(0, "Default Material|Material Asset", ground_plane_material_asset) - # Work around to add the correct Atom Mesh component + + # Work around to add the correct Atom Mesh component and asset. mesh_type_id = azlmbr.globals.property.EditorMeshComponentTypeId ground_plane.components.append( editor.EditorComponentAPIBus( bus.Broadcast, "AddComponentsOfType", ground_plane.id, [mesh_type_id] ).GetValue()[0] ) - ground_plane_mesh_asset_path = os.path.join("Objects", "plane.azmodel") + ground_plane_mesh_asset_path = os.path.join("TestData", "Objects", "plane.azmodel") ground_plane_mesh_asset = asset.AssetCatalogRequestBus( bus.Broadcast, "GetAssetIdByPath", ground_plane_mesh_asset_path, math.Uuid(), False) hydra.get_set_test(ground_plane, 1, "Controller|Configuration|Mesh Asset", ground_plane_mesh_asset) + # Add Atom Material component and asset. + ground_plane_material_asset_path = os.path.join("Materials", "Presets", "PBR", "metal_chrome.azmaterial") + ground_plane_material_asset = asset.AssetCatalogRequestBus( + bus.Broadcast, "GetAssetIdByPath", ground_plane_material_asset_path, math.Uuid(), False) + ground_plane.get_set_test(0, "Default Material|Material Asset", ground_plane_material_asset) + # Create directional_light entity and set the properties directional_light = hydra.Entity("directional_light") directional_light.create_entity( @@ -180,11 +158,8 @@ def run(): components=["Material"], parent_id=default_level.id ) - sphere_material_asset_path = os.path.join("Materials", "Presets", "PBR", "metal_brass_polished.azmaterial") - sphere_material_asset = asset.AssetCatalogRequestBus( - bus.Broadcast, "GetAssetIdByPath", sphere_material_asset_path, math.Uuid(), False) - sphere.get_set_test(0, "Default Material|Material Asset", sphere_material_asset) - # Work around to add the correct Atom Mesh component + + # Work around to add the correct Atom Mesh component and asset. sphere.components.append( editor.EditorComponentAPIBus( bus.Broadcast, "AddComponentsOfType", sphere.id, [mesh_type_id] @@ -195,6 +170,12 @@ def run(): bus.Broadcast, "GetAssetIdByPath", sphere_mesh_asset_path, math.Uuid(), False) hydra.get_set_test(sphere, 1, "Controller|Configuration|Mesh Asset", sphere_mesh_asset) + # Add Atom Material component and asset. + sphere_material_asset_path = os.path.join("Materials", "Presets", "PBR", "metal_brass_polished.azmaterial") + sphere_material_asset = asset.AssetCatalogRequestBus( + bus.Broadcast, "GetAssetIdByPath", sphere_material_asset_path, math.Uuid(), False) + sphere.get_set_test(0, "Default Material|Material Asset", sphere_material_asset) + # Create camera component and set the properties camera_entity = hydra.Entity("camera") position = math.Vector3(5.5, -12.0, 9.0) @@ -204,10 +185,9 @@ def run(): ) azlmbr.components.TransformBus(azlmbr.bus.Event, "SetLocalRotation", camera_entity.id, rotation) camera_entity.get_set_test(0, "Controller|Configuration|Field of view", 60.0) - azlmbr.camera.EditorCameraViewRequestBus(azlmbr.bus.Event, "ToggleCameraAsActiveView", camera_entity.id) + camera.EditorCameraViewRequestBus(azlmbr.bus.Event, "ToggleCameraAsActiveView", camera_entity.id) - # Save level, enter game mode, take screenshot, & exit game mode. - general.save_level() + # Enter game mode, take screenshot, & exit game mode. general.idle_wait(0.5) general.enter_game_mode() general.idle_wait(1.0) diff --git a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_GPUTest_LightComponent.py b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_GPUTest_LightComponent.py index 1c3e6226c1..f69ceb2120 100644 --- a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_GPUTest_LightComponent.py +++ b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_GPUTest_LightComponent.py @@ -22,7 +22,7 @@ from editor_python_test_tools.editor_test_helper import EditorTestHelper helper = EditorTestHelper(log_prefix="Atom_EditorTestHelper") -LEVEL_NAME = "auto_test" +LEVEL_NAME = "Base" LIGHT_COMPONENT = "Light" LIGHT_TYPE_PROPERTY = 'Controller|Configuration|Light type' DEGREE_RADIAN_FACTOR = 0.0174533 diff --git a/AutomatedTesting/Gem/PythonTests/Blast/CMakeLists.txt b/AutomatedTesting/Gem/PythonTests/Blast/CMakeLists.txt index cb83fe5344..aea2562e0b 100644 --- a/AutomatedTesting/Gem/PythonTests/Blast/CMakeLists.txt +++ b/AutomatedTesting/Gem/PythonTests/Blast/CMakeLists.txt @@ -6,7 +6,11 @@ # # -if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS) +ly_get_list_relative_pal_filename(pal_dir ${CMAKE_CURRENT_LIST_DIR}/Platform/${PAL_PLATFORM_NAME}) + +include(${pal_dir}/PAL_${PAL_PLATFORM_NAME_LOWERCASE}.cmake) # for PAL_TRAIT_BLAST Traits + +if(PAL_TRAIT_BLAST_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS) ly_add_pytest( NAME AutomatedTesting::BlastTests_Main TEST_SUITE main diff --git a/AutomatedTesting/Gem/PythonTests/Blast/Platform/Android/PAL_android.cmake b/AutomatedTesting/Gem/PythonTests/Blast/Platform/Android/PAL_android.cmake new file mode 100644 index 0000000000..f91b18e9f1 --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/Blast/Platform/Android/PAL_android.cmake @@ -0,0 +1,9 @@ +# +# Copyright (c) Contributors to the Open 3D Engine Project. +# For complete copyright and license terms please see the LICENSE at the root of this distribution. +# +# SPDX-License-Identifier: Apache-2.0 OR MIT +# +# + +set(PAL_TRAIT_BLAST_TESTS_SUPPORTED FALSE) diff --git a/AutomatedTesting/Gem/PythonTests/Blast/Platform/Linux/PAL_linux.cmake b/AutomatedTesting/Gem/PythonTests/Blast/Platform/Linux/PAL_linux.cmake new file mode 100644 index 0000000000..f91b18e9f1 --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/Blast/Platform/Linux/PAL_linux.cmake @@ -0,0 +1,9 @@ +# +# Copyright (c) Contributors to the Open 3D Engine Project. +# For complete copyright and license terms please see the LICENSE at the root of this distribution. +# +# SPDX-License-Identifier: Apache-2.0 OR MIT +# +# + +set(PAL_TRAIT_BLAST_TESTS_SUPPORTED FALSE) diff --git a/AutomatedTesting/Gem/PythonTests/Blast/Platform/Mac/PAL_mac.cmake b/AutomatedTesting/Gem/PythonTests/Blast/Platform/Mac/PAL_mac.cmake new file mode 100644 index 0000000000..f91b18e9f1 --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/Blast/Platform/Mac/PAL_mac.cmake @@ -0,0 +1,9 @@ +# +# Copyright (c) Contributors to the Open 3D Engine Project. +# For complete copyright and license terms please see the LICENSE at the root of this distribution. +# +# SPDX-License-Identifier: Apache-2.0 OR MIT +# +# + +set(PAL_TRAIT_BLAST_TESTS_SUPPORTED FALSE) diff --git a/AutomatedTesting/Gem/PythonTests/Blast/Platform/Windows/PAL_windows.cmake b/AutomatedTesting/Gem/PythonTests/Blast/Platform/Windows/PAL_windows.cmake new file mode 100644 index 0000000000..28767237de --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/Blast/Platform/Windows/PAL_windows.cmake @@ -0,0 +1,9 @@ +# +# Copyright (c) Contributors to the Open 3D Engine Project. +# For complete copyright and license terms please see the LICENSE at the root of this distribution. +# +# SPDX-License-Identifier: Apache-2.0 OR MIT +# +# + +set(PAL_TRAIT_BLAST_TESTS_SUPPORTED TRUE) diff --git a/AutomatedTesting/Gem/PythonTests/Blast/Platform/iOS/PAL_ios.cmake b/AutomatedTesting/Gem/PythonTests/Blast/Platform/iOS/PAL_ios.cmake new file mode 100644 index 0000000000..f91b18e9f1 --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/Blast/Platform/iOS/PAL_ios.cmake @@ -0,0 +1,9 @@ +# +# Copyright (c) Contributors to the Open 3D Engine Project. +# For complete copyright and license terms please see the LICENSE at the root of this distribution. +# +# SPDX-License-Identifier: Apache-2.0 OR MIT +# +# + +set(PAL_TRAIT_BLAST_TESTS_SUPPORTED FALSE) diff --git a/AutomatedTesting/Gem/PythonTests/CMakeLists.txt b/AutomatedTesting/Gem/PythonTests/CMakeLists.txt index fd3222ba83..bec49185bd 100644 --- a/AutomatedTesting/Gem/PythonTests/CMakeLists.txt +++ b/AutomatedTesting/Gem/PythonTests/CMakeLists.txt @@ -59,5 +59,8 @@ add_subdirectory(smoke) ## AWS ## add_subdirectory(AWS) +## Multiplayer ## +add_subdirectory(Multiplayer) + ## Integration tests for editor testing framework ## add_subdirectory(editor_test_testing) diff --git a/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools/editor_python_test_tools/editor_entity_utils.py b/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools/editor_python_test_tools/editor_entity_utils.py index 844f8de903..9e857ed8bc 100644 --- a/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools/editor_python_test_tools/editor_entity_utils.py +++ b/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools/editor_python_test_tools/editor_entity_utils.py @@ -107,6 +107,19 @@ class EditorComponent: return type_ids + +def convert_to_azvector3(xyz) -> azlmbr.math.Vector3: + """ + Converts a vector3-like element into a azlmbr.math.Vector3 + """ + if isinstance(xyz, Tuple) or isinstance(xyz, List): + assert len(xyz) == 3, ValueError("vector must be a 3 element list/tuple or azlmbr.math.Vector3") + return math.Vector3(float(xyz[0]), float(xyz[1]), float(xyz[2])) + elif isinstance(xyz, type(math.Vector3())): + return xyz + else: + raise ValueError("vector must be a 3 element list/tuple or azlmbr.math.Vector3") + class EditorEntity: """ Entity class is used to create and interact with Editor Entities. @@ -183,15 +196,6 @@ class EditorEntity: :return: EditorEntity class object """ - def convert_to_azvector3(xyz) -> math.Vector3: - if isinstance(xyz, Tuple) or isinstance(xyz, List): - assert len(xyz) == 3, ValueError("vector must be a 3 element list/tuple or azlmbr.math.Vector3") - return math.Vector3(*xyz) - elif isinstance(xyz, type(math.Vector3())): - return xyz - else: - raise ValueError("vector must be a 3 element list/tuple or azlmbr.math.Vector3") - if parent_id is None: parent_id = azlmbr.entity.EntityId() @@ -206,7 +210,7 @@ class EditorEntity: return entity # Methods - def set_name(self, entity_name: str): + def set_name(self, entity_name: str) -> None: """ Given entity_name, sets name to Entity :param: entity_name: Name of the entity to set @@ -324,7 +328,7 @@ class EditorEntity: self.start_status = status return status - def set_start_status(self, desired_start_status: str): + def set_start_status(self, desired_start_status: str) -> None: """ Set an entity as active/inactive at beginning of runtime or it is editor-only, given its entity id and the start status then return set success @@ -382,18 +386,75 @@ class EditorEntity: """ return editor.EditorEntityInfoRequestBus(bus.Event, "IsVisible", self.id) + # World Transform Functions + def get_world_translation(self) -> azlmbr.math.Vector3: + """ + Gets the world translation of the entity + """ + return azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldTranslation", self.id) + + def set_world_translation(self, new_translation) -> None: + """ + Sets the new world translation of the current entity + """ + new_translation = convert_to_azvector3(new_translation) + azlmbr.components.TransformBus(azlmbr.bus.Event, "SetWorldTranslation", self.id, new_translation) + + def get_world_rotation(self) -> azlmbr.math.Quaternion: + """ + Gets the world rotation of the entity + """ + return azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldRotation", self.id) + + def set_world_rotation(self, new_rotation): + """ + Sets the new world rotation of the current entity + """ + new_rotation = convert_to_azvector3(new_rotation) + azlmbr.components.TransformBus(azlmbr.bus.Event, "SetWorldRotation", self.id, new_rotation) + + # Local Transform Functions + def get_local_uniform_scale(self) -> float: + """ + Gets the local uniform scale of the entity + """ + return azlmbr.components.TransformBus(azlmbr.bus.Event, "GetLocalUniformScale", self.id) + def set_local_uniform_scale(self, scale_float) -> None: """ - Sets the "SetLocalUniformScale" value on the entity. + Sets the local uniform scale value(relative to the parent) on the entity. :param scale_float: value for "SetLocalUniformScale" to set to. :return: None """ azlmbr.components.TransformBus(azlmbr.bus.Event, "SetLocalUniformScale", self.id, scale_float) - def set_local_rotation(self, vector3_rotation) -> None: + def get_local_rotation(self) -> azlmbr.math.Quaternion: """ - Sets the "SetLocalRotation" value on the entity. + Gets the local rotation of the entity + """ + return azlmbr.components.TransformBus(azlmbr.bus.Event, "GetLocalRotation", self.id) + + def set_local_rotation(self, new_rotation) -> None: + """ + Sets the set the local rotation(relative to the parent) of the current entity. :param vector3_rotation: The math.Vector3 value to use for rotation on the entity (uses radians). :return: None """ - azlmbr.components.TransformBus(azlmbr.bus.Event, "SetLocalRotation", self.id, vector3_rotation) + new_rotation = convert_to_azvector3(new_rotation) + azlmbr.components.TransformBus(azlmbr.bus.Event, "SetLocalRotation", self.id, new_rotation) + + def get_local_translation(self) -> azlmbr.math.Vector3: + """ + Gets the local translation of the current entity. + :return: The math.Vector3 value of the local translation. + """ + return azlmbr.components.TransformBus(azlmbr.bus.Event, "GetLocalTranslation", self.id) + + def set_local_translation(self, new_translation) -> None: + """ + Sets the local translation(relative to the parent) of the current entity. + :param vector3_translation: The math.Vector3 value to use for translation on the entity. + :return: None + """ + new_translation = convert_to_azvector3(new_translation) + azlmbr.components.TransformBus(azlmbr.bus.Event, "SetLocalTranslation", self.id, new_translation) diff --git a/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools/editor_python_test_tools/prefab_utils.py b/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools/editor_python_test_tools/prefab_utils.py index 10a6ab1ef4..76c2a42c0a 100644 --- a/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools/editor_python_test_tools/prefab_utils.py +++ b/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools/editor_python_test_tools/prefab_utils.py @@ -138,6 +138,14 @@ class PrefabInstance: self.container_entity = reparented_container_entity current_instance_prefab.instances.add(self) + def get_direct_child_entities(self): + """ + Returns the entities only contained in the current prefab instance. + This function does not return entities contained in other child instances + """ + return self.container_entity.get_children() + + # This is a helper class which contains some of the useful information about a prefab template. class Prefab: diff --git a/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools/editor_python_test_tools/utils.py b/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools/editor_python_test_tools/utils.py index 1b094b1cfa..481d73274f 100644 --- a/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools/editor_python_test_tools/utils.py +++ b/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools/editor_python_test_tools/utils.py @@ -14,7 +14,10 @@ from typing import Callable, Tuple import azlmbr import azlmbr.legacy.general as general +import azlmbr.multiplayer as multiplayer import azlmbr.debug +import ly_test_tools.environment.waiter as waiter +import ly_test_tools.environment.process_utils as process_utils class FailFast(Exception): @@ -66,6 +69,56 @@ class TestHelper: TestHelper.wait_for_condition(lambda : general.is_in_game_mode(), 1.0) Report.critical_result(msgtuple_success_fail, general.is_in_game_mode()) + @staticmethod + def multiplayer_enter_game_mode(msgtuple_success_fail : Tuple[str, str], sv_default_player_spawn_asset : str): + # type: (tuple) -> None + """ + :param msgtuple_success_fail: The tuple with the expected/unexpected messages for entering game mode. + :param sv_default_player_spawn_asset: The path to the network player prefab that will be automatically spawned upon entering gamemode. The engine default is "prefabs/player.network.spawnable" + + :return: None + """ + + # looks for an expected line in a list of tracers lines + # lines: the tracer list of lines to search. options are section_tracer.warnings, section_tracer.errors, section_tracer.asserts, section_tracer.prints + # return: true if the line is found, otherwise false + def find_expected_line(expected_line, lines): + found_lines = [printInfo.message.strip() for printInfo in lines] + return expected_line in found_lines + + def wait_for_critical_expected_line(expected_line, lines, time_out): + TestHelper.wait_for_condition(lambda : find_expected_line(expected_line, lines), time_out) + Report.critical_result(("Found expected line: " + expected_line, "Failed to find expected line: " + expected_line), find_expected_line(expected_line, lines)) + + def wait_for_critical_unexpected_line(unexpected_line, lines, time_out): + TestHelper.wait_for_condition(lambda : find_expected_line(unexpected_line, lines), time_out) + Report.critical_result(("Unexpected line not found: " + unexpected_line, "Unexpected line found: " + unexpected_line), not find_expected_line(unexpected_line, lines)) + + + Report.info("Entering game mode") + if sv_default_player_spawn_asset : + general.set_cvar("sv_defaultPlayerSpawnAsset", sv_default_player_spawn_asset) + + with Tracer() as section_tracer: + # enter game-mode. + # game-mode in multiplayer will also launch ServerLauncher.exe and connect to the editor + multiplayer.PythonEditorFuncs_enter_game_mode() + + # make sure the server launcher binary exists + wait_for_critical_unexpected_line("LaunchEditorServer failed! The ServerLauncher binary is missing!", section_tracer.errors, 0.5) + + # make sure the server launcher is running + waiter.wait_for(lambda: process_utils.process_exists("AutomatedTesting.ServerLauncher", ignore_extensions=True), timeout=5.0, exc=AssertionError("AutomatedTesting.ServerLauncher has NOT launched!"), interval=1.0) + + # make sure the editor connects to the editor-server and sends the level data packet + wait_for_critical_expected_line("Editor is sending the editor-server the level data packet.", section_tracer.prints, 5.0) + + # make sure the editor finally connects to the editor-server network simulation + wait_for_critical_expected_line("Editor-server ready. Editor has successfully connected to the editor-server's network simulation.", section_tracer.prints, 5.0) + + TestHelper.wait_for_condition(lambda : multiplayer.PythonEditorFuncs_is_in_game_mode(), 5.0) + Report.critical_result(msgtuple_success_fail, multiplayer.PythonEditorFuncs_is_in_game_mode()) + @staticmethod def exit_game_mode(msgtuple_success_fail : Tuple[str, str]): # type: (tuple) -> None diff --git a/AutomatedTesting/Gem/PythonTests/Multiplayer/CMakeLists.txt b/AutomatedTesting/Gem/PythonTests/Multiplayer/CMakeLists.txt new file mode 100644 index 0000000000..367de4da9a --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/Multiplayer/CMakeLists.txt @@ -0,0 +1,23 @@ +# +# Copyright (c) Contributors to the Open 3D Engine Project. +# For complete copyright and license terms please see the LICENSE at the root of this distribution. +# +# SPDX-License-Identifier: Apache-2.0 OR MIT +# +# + +if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS) + ly_add_pytest( + NAME AutomatedTesting::MultiplayerTests_Sandbox + TEST_SUITE sandbox + TEST_SERIAL + PATH ${CMAKE_CURRENT_LIST_DIR}/TestSuite_Sandbox.py + RUNTIME_DEPENDENCIES + Legacy::Editor + AZ::AssetProcessor + AutomatedTesting.Assets + AutomatedTesting.ServerLauncher + COMPONENT + Multiplayer + ) +endif() diff --git a/AutomatedTesting/Gem/PythonTests/Multiplayer/TestSuite_Main.py b/AutomatedTesting/Gem/PythonTests/Multiplayer/TestSuite_Main.py new file mode 100644 index 0000000000..df1eb62943 --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/Multiplayer/TestSuite_Main.py @@ -0,0 +1,29 @@ +""" +Copyright (c) Contributors to the Open 3D Engine Project. +For complete copyright and license terms please see the LICENSE at the root of this distribution. + +SPDX-License-Identifier: Apache-2.0 OR MIT + +""" + +# This suite consists of all test cases that are under development and have not been verified yet. +# Once they are verified, please move them to TestSuite_Active.py + +import pytest +import os +import sys + + +sys.path.append(os.path.dirname(os.path.abspath(__file__)) + '/../automatedtesting_shared') + +from base import TestAutomationBase + +@pytest.mark.parametrize("project", ["AutomatedTesting"]) +@pytest.mark.parametrize("launcher_platform", ['windows_editor']) +class TestAutomation(TestAutomationBase): + def _run_prefab_test(self, request, workspace, editor, test_module, batch_mode=True, autotest_mode=True): + self._run_test(request, workspace, editor, test_module, + extra_cmdline_args=["--regset=/Amazon/Preferences/EnablePrefabSystem=true"], + batch_mode=batch_mode, + autotest_mode=autotest_mode) + diff --git a/AutomatedTesting/Gem/PythonTests/Multiplayer/TestSuite_Sandbox.py b/AutomatedTesting/Gem/PythonTests/Multiplayer/TestSuite_Sandbox.py new file mode 100644 index 0000000000..52ac19b26e --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/Multiplayer/TestSuite_Sandbox.py @@ -0,0 +1,35 @@ +""" +Copyright (c) Contributors to the Open 3D Engine Project. +For complete copyright and license terms please see the LICENSE at the root of this distribution. + +SPDX-License-Identifier: Apache-2.0 OR MIT + +""" + +# This suite consists of all test cases that are under development and have not been verified yet. +# Once they are verified, please move them to TestSuite_Active.py + +import pytest +import os +import sys + + +sys.path.append(os.path.dirname(os.path.abspath(__file__)) + '/../automatedtesting_shared') + +from base import TestAutomationBase + +@pytest.mark.SUITE_sandbox +@pytest.mark.parametrize("project", ["AutomatedTesting"]) +@pytest.mark.parametrize("launcher_platform", ['windows_editor']) +class TestAutomation(TestAutomationBase): + def _run_prefab_test(self, request, workspace, editor, test_module, batch_mode=True, autotest_mode=True): + self._run_test(request, workspace, editor, test_module, + extra_cmdline_args=["--regset=/Amazon/Preferences/EnablePrefabSystem=true"], + batch_mode=batch_mode, + autotest_mode=autotest_mode) + + ## Seems to be flaky, need to investigate + def test_Multiplayer_AutoComponent_NetworkInput(self, request, workspace, editor, launcher_platform): + from .tests import Multiplayer_AutoComponent_NetworkInput as test_module + self._run_prefab_test(request, workspace, editor, test_module) + diff --git a/AutomatedTesting/Gem/PythonTests/Multiplayer/__init__.py b/AutomatedTesting/Gem/PythonTests/Multiplayer/__init__.py new file mode 100644 index 0000000000..f5193b300e --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/Multiplayer/__init__.py @@ -0,0 +1,6 @@ +""" +Copyright (c) Contributors to the Open 3D Engine Project. +For complete copyright and license terms please see the LICENSE at the root of this distribution. + +SPDX-License-Identifier: Apache-2.0 OR MIT +""" diff --git a/AutomatedTesting/Gem/PythonTests/Multiplayer/tests/Multiplayer_AutoComponent_NetworkInput.py b/AutomatedTesting/Gem/PythonTests/Multiplayer/tests/Multiplayer_AutoComponent_NetworkInput.py new file mode 100644 index 0000000000..7b56213313 --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/Multiplayer/tests/Multiplayer_AutoComponent_NetworkInput.py @@ -0,0 +1,115 @@ +""" +Copyright (c) Contributors to the Open 3D Engine Project. +For complete copyright and license terms please see the LICENSE at the root of this distribution. + +SPDX-License-Identifier: Apache-2.0 OR MIT +""" + + +# Test Case Title : Check that network input can be created, received by the authority, and processed + + +# fmt: off +class Tests(): + enter_game_mode = ("Entered game mode", "Failed to enter game mode") + exit_game_mode = ("Exited game mode", "Couldn't exit game mode") + find_network_player = ("Found network player", "Couldn't find network player") + found_lines = ("Expected log lines were found", "Expected log lines were not found") + found_unexpected_lines = ("Unexpected log lines were not found", "Unexpected log lines were found") +# fmt: on + + +def Multiplayer_AutoComponent_NetworkInput(): + r""" + Summary: + Runs a test to make sure that network input can be sent from the autonomous player, received by the authority, and processed + + Level Description: + - Dynamic + 1. Although the level is empty, when the server and editor connect the server will spawn and replicate the player network prefab. + a. The player network prefab has a NetworkTestPlayerComponent.AutoComponent and a script canvas attached which will listen for the CreateInput and ProcessInput events. + Print logs occur upon triggering the CreateInput and ProcessInput events along with their values; we are testing to make sure the expected events are values are recieved. + - Static + 1. This is an empty level. All the logic occurs on the Player.network.spawnable (see the above Dynamic description) + + + Expected Outcome: + We should see editor logs stating that network input has been created and processed. + However, if the script receives unexpected values for the Process event we will see print logs for bad data as well. + + :return: + """ + import azlmbr.legacy.general as general + from editor_python_test_tools.utils import Report + from editor_python_test_tools.utils import Tracer + + from editor_python_test_tools.utils import TestHelper as helper + from ly_remote_console.remote_console_commands import RemoteConsole as RemoteConsole + + + def find_expected_line(expected_line): + found_lines = [printInfo.message.strip() for printInfo in section_tracer.prints] + return expected_line in found_lines + + def find_unexpected_line(expected_line): + return not find_expected_line(expected_line) + + unexpected_lines = [ + 'AutoComponent_NetworkInput received bad fwdback!', + 'AutoComponent_NetworkInput received bad leftright!', + + ] + expected_lines = [ + 'AutoComponent_NetworkInput ProcessInput called!', + 'AutoComponent_NetworkInput CreateInput called!', + ] + + expected_lines_server = [ + '(Script) - AutoComponent_NetworkInput ProcessInput called!', + ] + + level_name = "AutoComponent_NetworkInput" + player_prefab_name = "Player" + player_prefab_path = f"levels/multiplayer/{level_name}/{player_prefab_name}.network.spawnable" + + helper.init_idle() + + + # 1) Open Level + helper.open_level("Multiplayer", level_name) + + with Tracer() as section_tracer: + # 2) Enter game mode + helper.multiplayer_enter_game_mode(Tests.enter_game_mode, player_prefab_path.lower()) + + # 3) Make sure the network player was spawned + player_id = general.find_game_entity(player_prefab_name) + Report.critical_result(Tests.find_network_player, player_id.IsValid()) + + # 4) Check the editor logs for expected and unexpected log output + EXPECTEDLINE_WAIT_TIME_SECONDS = 1.0 + for expected_line in expected_lines : + helper.wait_for_condition(lambda: find_expected_line(expected_line), EXPECTEDLINE_WAIT_TIME_SECONDS) + Report.result(Tests.found_lines, find_expected_line(expected_line)) + + general.idle_wait_frames(1) + for unexpected_line in unexpected_lines : + Report.result(Tests.found_unexpected_lines, find_unexpected_line(unexpected_line)) + + # 5) Check the ServerLauncher logs for expected log output + # Since the editor has started a server launcher, the RemoteConsole with the default port=4600 will automatically be able to read the server logs + server_console = RemoteConsole() + server_console.start() + for line in expected_lines_server: + assert server_console.expect_log_line(line, EXPECTEDLINE_WAIT_TIME_SECONDS), f"Expected line not found: {line}" + server_console.stop() + + + # Exit game mode + helper.exit_game_mode(Tests.exit_game_mode) + + + +if __name__ == "__main__": + from editor_python_test_tools.utils import Report + Report.start_test(Multiplayer_AutoComponent_NetworkInput) diff --git a/AutomatedTesting/Gem/PythonTests/Physics/TestSuite_Main_Optimized.py b/AutomatedTesting/Gem/PythonTests/Physics/TestSuite_Main_Optimized.py index 63d3b58249..3d668a2085 100644 --- a/AutomatedTesting/Gem/PythonTests/Physics/TestSuite_Main_Optimized.py +++ b/AutomatedTesting/Gem/PythonTests/Physics/TestSuite_Main_Optimized.py @@ -53,6 +53,27 @@ class EditorSingleTest_WithFileOverrides(EditorSingleTest): for f in original_file_list: fm._restore_file(f, file_list[f]) +@pytest.mark.xfail(reason="Optimized tests are experimental, we will enable xfail and monitor them temporarily.") +@pytest.mark.SUITE_main +@pytest.mark.parametrize("launcher_platform", ['windows_editor']) +@pytest.mark.parametrize("project", ["AutomatedTesting"]) +class TestAutomationWithPrefabSystemEnabled(EditorTestSuite): + + global_extra_cmdline_args = ['-BatchMode', '-autotest_mode', + 'extra_cmdline_args=["--regset=/Amazon/Preferences/EnablePrefabSystem=true"]'] + + @staticmethod + def get_number_parallel_editors(): + return 16 + + class C4982801_PhysXColliderShape_CanBeSelected(EditorSharedTest): + from .tests.collider import Collider_BoxShapeEditing as test_module + + class C4982800_PhysXColliderShape_CanBeSelected(EditorSharedTest): + from .tests.collider import Collider_SphereShapeEditing as test_module + + class C4982802_PhysXColliderShape_CanBeSelected(EditorSharedTest): + from .tests.collider import Collider_CapsuleShapeEditing as test_module @pytest.mark.xfail(reason="Optimized tests are experimental, we will enable xfail and monitor them temporarily.") @pytest.mark.SUITE_main @@ -286,15 +307,6 @@ class TestAutomation(EditorTestSuite): class C19723164_ShapeCollider_WontCrashEditor(EditorSharedTest): from .tests.shape_collider import ShapeCollider_LargeNumberOfShapeCollidersWontCrashEditor as test_module - class C4982800_PhysXColliderShape_CanBeSelected(EditorSharedTest): - from .tests.collider import Collider_SphereShapeEditting as test_module - - class C4982801_PhysXColliderShape_CanBeSelected(EditorSharedTest): - from .tests.collider import Collider_BoxShapeEditting as test_module - - class C4982802_PhysXColliderShape_CanBeSelected(EditorSharedTest): - from .tests.collider import Collider_CapsuleShapeEditting as test_module - class C12905528_ForceRegion_WithNonTriggerCollider(EditorSharedTest): from .tests.force_region import ForceRegion_WithNonTriggerColliderWarning as test_module # Fixme: expected_lines = ["[Warning] (PhysX Force Region) - Please ensure collider component marked as trigger exists in entity"] diff --git a/AutomatedTesting/Gem/PythonTests/Physics/TestSuite_Periodic.py b/AutomatedTesting/Gem/PythonTests/Physics/TestSuite_Periodic.py index e5dd9adbb9..55e51dd2f8 100755 --- a/AutomatedTesting/Gem/PythonTests/Physics/TestSuite_Periodic.py +++ b/AutomatedTesting/Gem/PythonTests/Physics/TestSuite_Periodic.py @@ -401,19 +401,22 @@ class TestAutomation(TestAutomationBase): self._run_test(request, workspace, editor, test_module) @revert_physics_config - def test_Collider_SphereShapeEditting(self, request, workspace, editor, launcher_platform): - from .tests.collider import Collider_SphereShapeEditting as test_module - self._run_test(request, workspace, editor, test_module) + def test_Collider_SphereShapeEditing(self, request, workspace, editor, launcher_platform): + from .tests.collider import Collider_SphereShapeEditing as test_module + self._run_test(request, workspace, editor, test_module, + extra_cmdline_args=["--regset=/Amazon/Preferences/EnablePrefabSystem=true"]) @revert_physics_config - def test_Collider_BoxShapeEditting(self, request, workspace, editor, launcher_platform): - from .tests.collider import Collider_BoxShapeEditting as test_module - self._run_test(request, workspace, editor, test_module) + def test_Collider_BoxShapeEditing(self, request, workspace, editor, launcher_platform): + from .tests.collider import Collider_BoxShapeEditing as test_module + self._run_test(request, workspace, editor, test_module, + extra_cmdline_args=["--regset=/Amazon/Preferences/EnablePrefabSystem=true"]) @revert_physics_config - def test_Collider_CapsuleShapeEditting(self, request, workspace, editor, launcher_platform): - from .tests.collider import Collider_CapsuleShapeEditting as test_module - self._run_test(request, workspace, editor, test_module) + def test_Collider_CapsuleShapeEditing(self, request, workspace, editor, launcher_platform): + from .tests.collider import Collider_CapsuleShapeEditing as test_module + self._run_test(request, workspace, editor, test_module, + extra_cmdline_args=["--regset=/Amazon/Preferences/EnablePrefabSystem=true"]) def test_ForceRegion_WithNonTriggerColliderWarning(self, request, workspace, editor, launcher_platform): from .tests.force_region import ForceRegion_WithNonTriggerColliderWarning as test_module diff --git a/AutomatedTesting/Gem/PythonTests/Physics/tests/collider/Collider_BoxShapeEditting.py b/AutomatedTesting/Gem/PythonTests/Physics/tests/collider/Collider_BoxShapeEditing.py similarity index 97% rename from AutomatedTesting/Gem/PythonTests/Physics/tests/collider/Collider_BoxShapeEditting.py rename to AutomatedTesting/Gem/PythonTests/Physics/tests/collider/Collider_BoxShapeEditing.py index 68ff0b4edc..a6730c8559 100644 --- a/AutomatedTesting/Gem/PythonTests/Physics/tests/collider/Collider_BoxShapeEditting.py +++ b/AutomatedTesting/Gem/PythonTests/Physics/tests/collider/Collider_BoxShapeEditing.py @@ -19,7 +19,7 @@ class Tests(): # fmt: on -def Collider_BoxShapeEditting(): +def Collider_BoxShapeEditing(): """ Summary: Adding PhysX Collider and Shape components to test entity, then attempting to modify the shape's dimensions @@ -73,7 +73,7 @@ def Collider_BoxShapeEditting(): helper.init_idle() # 1) Load the empty level - helper.open_level("Physics", "Base") + helper.open_level("", "Base") # 2) Create the test entity test_entity = Entity.create_editor_entity("Test Entity") @@ -102,4 +102,4 @@ def Collider_BoxShapeEditting(): if __name__ == "__main__": from editor_python_test_tools.utils import Report - Report.start_test(Collider_BoxShapeEditting) + Report.start_test(Collider_BoxShapeEditing) diff --git a/AutomatedTesting/Gem/PythonTests/Physics/tests/collider/Collider_CapsuleShapeEditting.py b/AutomatedTesting/Gem/PythonTests/Physics/tests/collider/Collider_CapsuleShapeEditing.py similarity index 97% rename from AutomatedTesting/Gem/PythonTests/Physics/tests/collider/Collider_CapsuleShapeEditting.py rename to AutomatedTesting/Gem/PythonTests/Physics/tests/collider/Collider_CapsuleShapeEditing.py index 7df12c68f0..12435cc54a 100644 --- a/AutomatedTesting/Gem/PythonTests/Physics/tests/collider/Collider_CapsuleShapeEditting.py +++ b/AutomatedTesting/Gem/PythonTests/Physics/tests/collider/Collider_CapsuleShapeEditing.py @@ -19,7 +19,7 @@ class Tests(): # fmt: on -def Collider_CapsuleShapeEditting(): +def Collider_CapsuleShapeEditing(): """ Summary: Adding PhysX Collider and Shape components to test entity, then attempting to modify the shape's dimensions @@ -74,7 +74,7 @@ def Collider_CapsuleShapeEditting(): helper.init_idle() # 1) Load the empty level - helper.open_level("Physics", "Base") + helper.open_level("", "Base") # 2) Create the test entity test_entity = Entity.create_editor_entity("Test Entity") @@ -102,4 +102,4 @@ def Collider_CapsuleShapeEditting(): if __name__ == "__main__": from editor_python_test_tools.utils import Report - Report.start_test(Collider_CapsuleShapeEditting) + Report.start_test(Collider_CapsuleShapeEditing) diff --git a/AutomatedTesting/Gem/PythonTests/Physics/tests/collider/Collider_SphereShapeEditting.py b/AutomatedTesting/Gem/PythonTests/Physics/tests/collider/Collider_SphereShapeEditing.py similarity index 96% rename from AutomatedTesting/Gem/PythonTests/Physics/tests/collider/Collider_SphereShapeEditting.py rename to AutomatedTesting/Gem/PythonTests/Physics/tests/collider/Collider_SphereShapeEditing.py index bffd041d92..ef91235411 100644 --- a/AutomatedTesting/Gem/PythonTests/Physics/tests/collider/Collider_SphereShapeEditting.py +++ b/AutomatedTesting/Gem/PythonTests/Physics/tests/collider/Collider_SphereShapeEditing.py @@ -19,7 +19,7 @@ class Tests(): # fmt: on -def Collider_SphereShapeEditting(): +def Collider_SphereShapeEditing(): """ Summary: Adding PhysX Collider and Shape components to test entity, then attempting to modify the shape's dimensions @@ -57,7 +57,7 @@ def Collider_SphereShapeEditting(): helper.init_idle() # 1) Load the empty level - helper.open_level("Physics", "Base") + helper.open_level("", "Base") # 2) Create the test entity test_entity = Entity.create_editor_entity("Test Entity") @@ -90,4 +90,4 @@ def Collider_SphereShapeEditting(): if __name__ == "__main__": from editor_python_test_tools.utils import Report - Report.start_test(Collider_SphereShapeEditting) + Report.start_test(Collider_SphereShapeEditing) diff --git a/AutomatedTesting/Gem/PythonTests/Prefab/TestSuite_Main.py b/AutomatedTesting/Gem/PythonTests/Prefab/TestSuite_Main.py index 5337f0669c..479915752f 100644 --- a/AutomatedTesting/Gem/PythonTests/Prefab/TestSuite_Main.py +++ b/AutomatedTesting/Gem/PythonTests/Prefab/TestSuite_Main.py @@ -55,3 +55,11 @@ class TestAutomation(TestAutomationBase): def test_PrefabBasicWorkflow_CreateAndDuplicatePrefab(self, request, workspace, editor, launcher_platform): from .tests import PrefabBasicWorkflow_CreateAndDuplicatePrefab as test_module self._run_prefab_test(request, workspace, editor, test_module) + + def test_PrefabComplexWorflow_CreatePrefabOfChildEntity(self, request, workspace, editor, launcher_platform): + from .tests import PrefabComplexWorflow_CreatePrefabOfChildEntity as test_module + self._run_prefab_test(request, workspace, editor, test_module, autotest_mode=False) + + def test_PrefabComplexWorflow_CreatePrefabInsidePrefab(self, request, workspace, editor, launcher_platform): + from .tests import PrefabComplexWorflow_CreatePrefabInsidePrefab as test_module + self._run_prefab_test(request, workspace, editor, test_module, autotest_mode=False) diff --git a/AutomatedTesting/Gem/PythonTests/Prefab/tests/PrefabComplexWorflow_CreatePrefabInsidePrefab.py b/AutomatedTesting/Gem/PythonTests/Prefab/tests/PrefabComplexWorflow_CreatePrefabInsidePrefab.py new file mode 100644 index 0000000000..e14fc96449 --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/Prefab/tests/PrefabComplexWorflow_CreatePrefabInsidePrefab.py @@ -0,0 +1,57 @@ +""" +Copyright (c) Contributors to the Open 3D Engine Project. +For complete copyright and license terms please see the LICENSE at the root of this distribution. + +SPDX-License-Identifier: Apache-2.0 OR MIT +""" + +def PrefabComplexWorflow_CreatePrefabInsidePrefab(): + """ + Test description: + - Creates an entity with a physx collider + - Creates a prefab "Outer_prefab" and an instance based of that entity + - Creates a prefab "Inner_prefab" inside "Outer_prefab" based the entity contained inside of it + Checks that the entity is correctly handlded by the prefab system checking the name and that it contains the physx collider + """ + + from editor_python_test_tools.editor_entity_utils import EditorEntity + from editor_python_test_tools.prefab_utils import Prefab + + import PrefabTestUtils as prefab_test_utils + + prefab_test_utils.open_base_tests_level() + + # Creates a new Entity at the root level + # Asserts if creation didn't succeed + entity = EditorEntity.create_editor_entity_at((100.0, 100.0, 100.0), name = "TestEntity") + assert entity.id.IsValid(), "Couldn't create entity" + entity.add_component("PhysX Collider") + assert entity.has_component("PhysX Collider"), "Attempted to add a PhysX Collider but no physx collider collider was found afterwards" + + # Create a prefab based on that entity + outer_prefab, outer_instance = Prefab.create_prefab([entity], "Outer_prefab") + # The test should be now inside the outer prefab instance. + entity = outer_instance.get_direct_child_entities()[0] + # We track if that is the same entity by checking the name and if it still contains the component that we created before + assert entity.get_name() == "TestEntity", f"Entity name inside outer_prefab doesn't match the original name, original:'TestEntity' current:'{entity.get_name()}'" + assert entity.has_component("PhysX Collider"), "Entity name inside outer_prefab doesn't have the collider component it should" + + # Now, create another prefab, based on the entity that is inside outer_prefab + inner_prefab, inner_instance = Prefab.create_prefab([entity], "Inner_prefab") + # The test entity should now be inside the inner prefab instance + entity = inner_instance.get_direct_child_entities()[0] + # We track if that is the same entity by checking the name and if it still contains the component that we created before + assert entity.get_name() == "TestEntity", f"Entity name inside inner_prefab doesn't match the original name, original:'TestEntity' current:'{entity.get_name()}'" + assert entity.has_component("PhysX Collider"), "Entity name inside inner_prefab doesn't have the collider component it should" + + # Verify hierarchy of entities: + # Outer_prefab + # |- Inner_prefab + # | |- TestEntity + assert entity.get_parent_id() == inner_instance.container_entity.id + assert inner_instance.container_entity.get_parent_id() == outer_instance.container_entity.id + + +if __name__ == "__main__": + from editor_python_test_tools.utils import Report + Report.start_test(PrefabComplexWorflow_CreatePrefabInsidePrefab) diff --git a/AutomatedTesting/Gem/PythonTests/Prefab/tests/PrefabComplexWorflow_CreatePrefabOfChildEntity.py b/AutomatedTesting/Gem/PythonTests/Prefab/tests/PrefabComplexWorflow_CreatePrefabOfChildEntity.py new file mode 100644 index 0000000000..dec44d52be --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/Prefab/tests/PrefabComplexWorflow_CreatePrefabOfChildEntity.py @@ -0,0 +1,52 @@ +""" +Copyright (c) Contributors to the Open 3D Engine Project. +For complete copyright and license terms please see the LICENSE at the root of this distribution. + +SPDX-License-Identifier: Apache-2.0 OR MIT +""" + +def PrefabComplexWorflow_CreatePrefabOfChildEntity(): + """ + Test description: + - Creates two entities, parent and child. Child entity has Parent entity as its parent. + - Creates a prefab of the child entity. + Test is successful if the new instanced prefab of the child has the parent entity id + """ + + CAR_PREFAB_FILE_NAME = 'car_prefab' + + from editor_python_test_tools.editor_entity_utils import EditorEntity + from editor_python_test_tools.prefab_utils import Prefab + + import PrefabTestUtils as prefab_test_utils + + prefab_test_utils.open_base_tests_level() + + # Creates a new Entity at the root level + # Asserts if creation didn't succeed + parent_entity = EditorEntity.create_editor_entity_at((100.0, 100.0, 100.0)) + assert parent_entity.id.IsValid(), "Couldn't create parent entity" + + child_entity = EditorEntity.create_editor_entity(parent_id=parent_entity.id) + assert child_entity.id.IsValid(), "Couldn't create child entity" + assert child_entity.get_world_translation().IsClose(parent_entity.get_world_translation()), f"Child entity position{child_entity.get_world_translation().ToString()}" \ + f" is not located at the same position as the parent{parent_entity.get_world_translation().ToString()}" + + # Asserts if prefab creation doesn't succeed + child_prefab, child_instance = Prefab.create_prefab([child_entity], CAR_PREFAB_FILE_NAME) + child_entity_on_child_instance = child_instance.get_direct_child_entities()[0] + assert child_instance.container_entity.get_parent_id().IsValid(), "Newly instanced entity has no parent" + assert child_instance.container_entity.get_parent_id() == parent_entity.id, "Newly instanced entity parent does not match the expected parent" + assert child_instance.container_entity.get_world_translation().IsClose(parent_entity.get_world_translation()), "Newly instanced entity position is not located at the same position as the parent" + # Move the parent position, it should update the child position + parent_entity.set_world_translation((200.0, 200.0, 200.0)) + child_instance_translation = child_instance.container_entity.get_world_translation() + assert child_instance_translation.IsClose(azlmbr.math.Vector3(200.0, 200.0, 200.0)), f"Instance position position{child_instance_translation.ToString()} didn't get updated" \ + f" to the same position as the parent{parent_entity.get_world_translation().ToString()}" + child_translation = child_entity_on_child_instance.get_world_translation() + assert child_translation.IsClose(azlmbr.math.Vector3(200.0, 200.0, 200.0)), f"Entity position{child_translation.ToString()} of the instance didn't get updated" \ + f" to the same position as the parent{parent_entity.get_world_translation().ToString()}" + +if __name__ == "__main__": + from editor_python_test_tools.utils import Report + Report.start_test(PrefabComplexWorflow_CreatePrefabOfChildEntity) diff --git a/AutomatedTesting/Gem/PythonTests/PythonAssetBuilder/mock_asset_builder.py b/AutomatedTesting/Gem/PythonTests/PythonAssetBuilder/mock_asset_builder.py index 443a420b61..a6e8b97b63 100644 --- a/AutomatedTesting/Gem/PythonTests/PythonAssetBuilder/mock_asset_builder.py +++ b/AutomatedTesting/Gem/PythonTests/PythonAssetBuilder/mock_asset_builder.py @@ -23,7 +23,7 @@ def create_jobs(request): jobDescriptorList = [] for platformInfo in request.enabledPlatforms: jobDesc = azlmbr.asset.builder.JobDescriptor() - jobDesc.jobKey = jobKeyName + jobDesc.jobKey = f'{jobKeyName}-{platformInfo.identifier}' jobDesc.set_platform_identifier(platformInfo.identifier) jobDescriptorList.append(jobDesc) @@ -38,7 +38,7 @@ def on_create_jobs(args): return create_jobs(request) except: log_exception_traceback() - # returing back a default CreateJobsResponse() records an asset error + # returning back a default CreateJobsResponse() records an asset error return azlmbr.asset.builder.CreateJobsResponse() def process_file(request): @@ -58,6 +58,7 @@ def process_file(request): fileOutput = open(tempFilename, "w") fileOutput.write('{}') fileOutput.close() + print(f'Wrote mock asset file: {tempFilename}') # generate a product asset file entry subId = binascii.crc32(mockFilename.encode()) diff --git a/AutomatedTesting/Gem/PythonTests/WhiteBox/CMakeLists.txt b/AutomatedTesting/Gem/PythonTests/WhiteBox/CMakeLists.txt index 10cf949c25..733e8edf29 100644 --- a/AutomatedTesting/Gem/PythonTests/WhiteBox/CMakeLists.txt +++ b/AutomatedTesting/Gem/PythonTests/WhiteBox/CMakeLists.txt @@ -6,7 +6,11 @@ # # -if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS) +ly_get_list_relative_pal_filename(pal_dir ${CMAKE_CURRENT_LIST_DIR}/Platform/${PAL_PLATFORM_NAME}) + +include(${pal_dir}/PAL_${PAL_PLATFORM_NAME_LOWERCASE}.cmake) # for PAL_TRAIT_WHITEBOX Traits + +if(PAL_TRAIT_WHITEBOX_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS) ly_add_pytest( NAME AutomatedTesting::WhiteBoxTests TEST_SUITE main diff --git a/AutomatedTesting/Gem/PythonTests/WhiteBox/Platform/Android/PAL_android.cmake b/AutomatedTesting/Gem/PythonTests/WhiteBox/Platform/Android/PAL_android.cmake new file mode 100644 index 0000000000..07aa0bb13d --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/WhiteBox/Platform/Android/PAL_android.cmake @@ -0,0 +1,9 @@ +# +# Copyright (c) Contributors to the Open 3D Engine Project. +# For complete copyright and license terms please see the LICENSE at the root of this distribution. +# +# SPDX-License-Identifier: Apache-2.0 OR MIT +# +# + +set(PAL_TRAIT_WHITEBOX_TESTS_SUPPORTED FALSE) diff --git a/AutomatedTesting/Gem/PythonTests/WhiteBox/Platform/Linux/PAL_linux.cmake b/AutomatedTesting/Gem/PythonTests/WhiteBox/Platform/Linux/PAL_linux.cmake new file mode 100644 index 0000000000..07aa0bb13d --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/WhiteBox/Platform/Linux/PAL_linux.cmake @@ -0,0 +1,9 @@ +# +# Copyright (c) Contributors to the Open 3D Engine Project. +# For complete copyright and license terms please see the LICENSE at the root of this distribution. +# +# SPDX-License-Identifier: Apache-2.0 OR MIT +# +# + +set(PAL_TRAIT_WHITEBOX_TESTS_SUPPORTED FALSE) diff --git a/AutomatedTesting/Gem/PythonTests/WhiteBox/Platform/Mac/PAL_mac.cmake b/AutomatedTesting/Gem/PythonTests/WhiteBox/Platform/Mac/PAL_mac.cmake new file mode 100644 index 0000000000..07aa0bb13d --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/WhiteBox/Platform/Mac/PAL_mac.cmake @@ -0,0 +1,9 @@ +# +# Copyright (c) Contributors to the Open 3D Engine Project. +# For complete copyright and license terms please see the LICENSE at the root of this distribution. +# +# SPDX-License-Identifier: Apache-2.0 OR MIT +# +# + +set(PAL_TRAIT_WHITEBOX_TESTS_SUPPORTED FALSE) diff --git a/AutomatedTesting/Gem/PythonTests/WhiteBox/Platform/Windows/PAL_windows.cmake b/AutomatedTesting/Gem/PythonTests/WhiteBox/Platform/Windows/PAL_windows.cmake new file mode 100644 index 0000000000..a83d56788a --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/WhiteBox/Platform/Windows/PAL_windows.cmake @@ -0,0 +1,9 @@ +# +# Copyright (c) Contributors to the Open 3D Engine Project. +# For complete copyright and license terms please see the LICENSE at the root of this distribution. +# +# SPDX-License-Identifier: Apache-2.0 OR MIT +# +# + +set(PAL_TRAIT_WHITEBOX_TESTS_SUPPORTED TRUE) diff --git a/AutomatedTesting/Gem/PythonTests/WhiteBox/Platform/iOS/PAL_ios.cmake b/AutomatedTesting/Gem/PythonTests/WhiteBox/Platform/iOS/PAL_ios.cmake new file mode 100644 index 0000000000..07aa0bb13d --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/WhiteBox/Platform/iOS/PAL_ios.cmake @@ -0,0 +1,9 @@ +# +# Copyright (c) Contributors to the Open 3D Engine Project. +# For complete copyright and license terms please see the LICENSE at the root of this distribution. +# +# SPDX-License-Identifier: Apache-2.0 OR MIT +# +# + +set(PAL_TRAIT_WHITEBOX_TESTS_SUPPORTED FALSE) diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/ap_fixtures/ap_fast_scan_setting_backup_fixture.py b/AutomatedTesting/Gem/PythonTests/assetpipeline/ap_fixtures/ap_fast_scan_setting_backup_fixture.py index 4a096ca3fe..c224289cf4 100755 --- a/AutomatedTesting/Gem/PythonTests/assetpipeline/ap_fixtures/ap_fast_scan_setting_backup_fixture.py +++ b/AutomatedTesting/Gem/PythonTests/assetpipeline/ap_fixtures/ap_fast_scan_setting_backup_fixture.py @@ -29,6 +29,9 @@ def ap_fast_scan_setting_backup_fixture(request, workspace) -> PlatformSetting: if workspace.asset_processor_platform == 'mac': pytest.skip("Mac plist file editing not implemented yet") + if workspace.asset_processor_platform == 'linux': + pytest.skip("Linux system settings not implemented yet") + key = fast_scan_key subkey = fast_scan_subkey diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/asset_processor_batch_dependency_tests.py b/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/asset_processor_batch_dependency_tests.py index ee8e177bbb..303a012cda 100755 --- a/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/asset_processor_batch_dependency_tests.py +++ b/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/asset_processor_batch_dependency_tests.py @@ -47,82 +47,6 @@ class TestsAssetProcessorBatch_DependenycyTests(object): """ AssetProcessorBatch Dependency tests """ - - @pytest.mark.test_case_id("C16877166") - @pytest.mark.BAT - @pytest.mark.assetpipeline - # fmt:off - def test_WindowsMacPlatforms_RunAPBatch_NotMissingDependency(self, ap_setup_fixture, asset_processor, - workspace): - # fmt:on - """ - Engine Schema - This test case has a conditional scenario depending on the existence of surfacetypes.xml in a project. - Some projects have this file and others do not. Run the conditional scenario depending on the existence - of the file in the project - libs/materialeffects/surfacetypes.xml is listed as an entry engine_dependencies.xml - libs/materialeffects/surfacetypes.xml is not listed as a missing dependency - in the 'assetprocessorbatch' console output - - Test Steps: - 1. Assets are pre-processed - 2. Verify that engine_dependencies.xml exists - 3. Verify engine_dependencies.xml has surfacetypes.xml present - 4. Run Missing Dependency scanner against the engine_dependenciese.xml - 5. Verify that Surfacetypes.xml is NOT in the missing depdencies output - 6. Add the schema file which allows our xml parser to understand dependencies for our engine_dependencies file - 7. Process assets - 8. Run Missing Dependency scanner against the engine_dependenciese.xml - 9. Verify that surfacetypes.xml is in the missing dependencies out - """ - - env = ap_setup_fixture - BATCH_LOG_PATH = env["ap_batch_log_file"] - asset_processor.create_temp_asset_root() - asset_processor.add_relative_source_asset(os.path.join("Assets", "Engine", "engine_dependencies.xml")) - asset_processor.add_scan_folder(os.path.join("Assets", "Engine")) - asset_processor.add_relative_source_asset(os.path.join("Assets", "Engine", "Libs", "MaterialEffects", "surfacetypes.xml")) - - # Precondition: Assets are all processed - asset_processor.batch_process() - - DEPENDENCIES_PATH = os.path.join(asset_processor.temp_project_cache(), "engine_dependencies.xml") - assert os.path.exists(DEPENDENCIES_PATH), "The engine_dependencies.xml does not exist." - surfacetypes_in_dependencies = False - surfacetypes_missing_logline = False - - # Read engine_dependencies.xml to see if surfacetypes.xml is present - with open(DEPENDENCIES_PATH, "r") as dependencies_file: - for line in dependencies_file.readlines(): - if "surfacetypes.xml" in line: - surfacetypes_in_dependencies = True - logger.info("Surfacetypes.xml was listed in the engine_dependencies.xml file.") - break - - if not surfacetypes_in_dependencies: - logger.info("Surfacetypes.xml was not listed in the engine_dependencies.xml file.") - - _, output = asset_processor.batch_process(capture_output=True, - extra_params="--dsp=%engine_dependencies.xml") - log = APOutputParser(output) - for _ in log.get_lines(run=-1, contains=["surfacetypes.xml", "Missing"]): - surfacetypes_missing_logline = True - - assert surfacetypes_missing_logline, "Surfacetypes.xml not seen in the batch log as missing." - - # Add the schema file which allows our xml parser to understand dependencies for our engine_dependencies file - asset_processor.add_relative_source_asset(os.path.join("Assets", "Engine", "Schema", "enginedependency.xmlschema")) - asset_processor.batch_process() - - _, output = asset_processor.batch_process(capture_output=True, - extra_params="--dsp=%engine_dependencies.xml") - log = APOutputParser(output) - surfacetypes_missing_logline = False - for _ in log.get_lines(run=-1, contains=["surfacetypes.xml", "Missing"]): - surfacetypes_missing_logline = True - - assert not surfacetypes_missing_logline, "Surfacetypes.xml not seen in the batch log as missing." - schemas = [ ("C16877167", ".ent"), ("C16877168", "Environment.xml"), diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/asset_processor_batch_tests.py b/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/asset_processor_batch_tests.py index f3483d9c1f..2d67bca8fe 100755 --- a/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/asset_processor_batch_tests.py +++ b/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/asset_processor_batch_tests.py @@ -329,7 +329,7 @@ class TestsAssetProcessorBatch_AllPlatforms(object): # or an expected behavior has changed. Processing bootstrap.cfg sometimes but not other times should not # cause a failure in this test. num_processed_assets = asset_processor_utils.get_num_processed_assets(output) - assert num_processed_assets >= 8, f'Wrong number of successfully processed assets found in output: '\ + assert num_processed_assets >= 6, f'Wrong number of successfully processed assets found in output: '\ '{num_processed_assets}' missing_assets, _ = asset_processor.compare_assets_with_cache() @@ -585,20 +585,18 @@ class TestsAssetProcessorBatch_AllPlatforms(object): 3. Verify that logs exist for both AP Batch & AP GUI """ asset_processor.create_temp_asset_root() + asset_processor.create_temp_log_root() LOG_PATH = { "batch_log": workspace.paths.ap_batch_log(), - "gui_log": workspace.paths.ap_gui_log(), - "job_logs": workspace.paths.ap_job_logs(), + "gui_log": workspace.paths.ap_gui_log() } class LogTimes: batch_log_start_time = 0 gui_log_start_time = 0 - job_logs_start_time = 0 batch_log_final_time = 0 gui_log_final_time = 0 - job_logs_final_time = 0 @staticmethod def Report(): @@ -607,12 +605,10 @@ class TestsAssetProcessorBatch_AllPlatforms(object): Original Times: Batch: {LogTimes.batch_log_start_time} GUI: {LogTimes.gui_log_start_time} - JobLogs:{LogTimes.job_logs_start_time} Post-Run Times: Batch: {LogTimes.batch_log_final_time} GUI: {LogTimes.gui_log_final_time} - JobLogs:{LogTimes.job_logs_final_time} """ ) @@ -629,23 +625,19 @@ class TestsAssetProcessorBatch_AllPlatforms(object): LogTimes.Report() def check_existence(name, path): - assert os.path.exists(path), f"{name} could not be located after running the AP." + assert os.path.exists(path), f"{name} could not be located {path} after running the AP." # Check if log files previously exist and grab their modification times update_times("_start_time") # Run the Batch process - assert asset_processor.batch_process(), "Batch process failed to successfully terminate" + assert asset_processor.batch_process(create_temp_log=False), "Batch process failed to successfully terminate" - asset_processor.gui_process(quitonidle=True) + asset_processor.gui_process(quitonidle=True, create_temp_log=False) # Check that the Logs directory exists (C1564055) check_existence("Logs Directory", workspace.paths.ap_log_dir()) - # Check that the logs and JobLogs directory have updated modified times (C1564056) - for key in LOG_PATH.keys(): - check_existence(key, LOG_PATH[key]) - update_times("_final_time") for key in LOG_PATH.keys(): @@ -708,6 +700,7 @@ class TestsAssetProcessorBatch_AllPlatforms(object): @pytest.mark.BAT @pytest.mark.assetpipeline + @pytest.mark.skip(reason="need to change assets from .slice files to an asset type that can have nested dependencies") def test_validateNestedPreloadDependency_Found(self, asset_processor, ap_setup_fixture, workspace): """ Tests processing of a nested circular dependency and verifies that Asset Processor will return an error diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/assets/C1568831/cgf_to_delete.cgf b/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/assets/C1568831/cgf_to_delete.cgf deleted file mode 100644 index 47571e39a9..0000000000 --- a/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/assets/C1568831/cgf_to_delete.cgf +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7932fada1523fad4eb6ad5c13111bb4c00d6b0584ec8641060bf25f306b17e69 -size 84632 diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/assets/C1568831/fbx_to_delete.fbx b/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/assets/C1568831/fbx_to_delete.fbx deleted file mode 100644 index 165bc01a54..0000000000 --- a/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/assets/C1568831/fbx_to_delete.fbx +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:bf94b548eccb78432db65077124cadf20de975919b181d712fde081f59edd353 -size 38848 diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/assets/C1568831/file_to_delete.prefab b/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/assets/C1568831/file_to_delete.prefab new file mode 100644 index 0000000000..9e26dfeeb6 --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/assets/C1568831/file_to_delete.prefab @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/assets/C1568831/file_to_delete.txt b/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/assets/C1568831/file_to_delete.txt new file mode 100644 index 0000000000..3d789f8b83 --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/assets/C1568831/file_to_delete.txt @@ -0,0 +1 @@ +to be deleted \ No newline at end of file diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/assets/C1571774/test_mesh_robot.cgf b/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/assets/C1571774/test_mesh_robot.cgf deleted file mode 100644 index fcb3513ed0..0000000000 --- a/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/assets/C1571774/test_mesh_robot.cgf +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1675471085483e0bd252a5bdd4db1b365c66f16ac32a6e6dd70bb1c23df83fa5 -size 24160 diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/assets/C1591338/test_mesh_robot.cgf b/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/assets/C1591338/test_mesh_robot.cgf deleted file mode 100644 index fcb3513ed0..0000000000 --- a/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/assets/C1591338/test_mesh_robot.cgf +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1675471085483e0bd252a5bdd4db1b365c66f16ac32a6e6dd70bb1c23df83fa5 -size 24160 diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/assets/C1591338/test_mesh_robot.prefab b/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/assets/C1591338/test_mesh_robot.prefab new file mode 100644 index 0000000000..9e26dfeeb6 --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/assets/C1591338/test_mesh_robot.prefab @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/assets/test_AddSameAssetsDifferentNames_ShouldProcess/Energy_Background_One.png b/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/assets/test_AddSameAssetsDifferentNames_ShouldProcess/Energy_Background_One.png deleted file mode 100644 index e012e887e7..0000000000 --- a/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/assets/test_AddSameAssetsDifferentNames_ShouldProcess/Energy_Background_One.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:21df6ab62f2572daa6d0710ad6819a728ee751a62170903a6773563d614aa51f -size 15191 diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/assets/test_AddSameAssetsDifferentNames_ShouldProcess/Energy_Background_Two.png b/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/assets/test_AddSameAssetsDifferentNames_ShouldProcess/Energy_Background_Two.png deleted file mode 100644 index e012e887e7..0000000000 --- a/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/assets/test_AddSameAssetsDifferentNames_ShouldProcess/Energy_Background_Two.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:21df6ab62f2572daa6d0710ad6819a728ee751a62170903a6773563d614aa51f -size 15191 diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/assets/test_ProcessAndDeleteCache_APBatchShouldReprocess/Init.bnk b/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/assets/test_ProcessAndDeleteCache_APBatchShouldReprocess/Init.bnk deleted file mode 100644 index f525a402af..0000000000 --- a/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/assets/test_ProcessAndDeleteCache_APBatchShouldReprocess/Init.bnk +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f0b4750147acbcc6229a1043ed47ee48e7703a5241f27fc72976e95741144369 -size 2372 diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/assets/test_ProcessAndDeleteCache_APBatchShouldReprocess/entity_icon_example_2.png b/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/assets/test_ProcessAndDeleteCache_APBatchShouldReprocess/entity_icon_example_2.png deleted file mode 100644 index 47e2d35331..0000000000 --- a/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/assets/test_ProcessAndDeleteCache_APBatchShouldReprocess/entity_icon_example_2.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a036c3763b96079dde8505ad6995f8b717a777c78d7a434ac8de6f1ccb5d8dd1 -size 4327 diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/assets/test_ProcessByBothApAndBatch_Md5ShouldMatch/SoundWave_1.png b/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/assets/test_ProcessByBothApAndBatch_Md5ShouldMatch/SoundWave_1.png deleted file mode 100644 index b3b8fba4a1..0000000000 --- a/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/assets/test_ProcessByBothApAndBatch_Md5ShouldMatch/SoundWave_1.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:cf55a4372d82d70d8cdcc95468367cd4fad7649d3fb99c4d85049977b506885c -size 13429 diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/assets/test_ProcessByBothApAndBatch_Md5ShouldMatch/extra_file.prefab b/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/assets/test_ProcessByBothApAndBatch_Md5ShouldMatch/extra_file.prefab new file mode 100644 index 0000000000..9e26dfeeb6 --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/assets/test_ProcessByBothApAndBatch_Md5ShouldMatch/extra_file.prefab @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/assets/test_ProcessByBothApAndBatch_Md5ShouldMatch/test_cube.fbx b/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/assets/test_ProcessByBothApAndBatch_Md5ShouldMatch/test_cube.fbx deleted file mode 100644 index 57fa7b327c..0000000000 --- a/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/assets/test_ProcessByBothApAndBatch_Md5ShouldMatch/test_cube.fbx +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1821d000b583821fd36ec73f91073d51ae90762225a34a81a74771c36236b5e1 -size 12963 diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/Motion/Jack_Idle_Aim_ZUp.dbgsg b/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/Motion/SceneDebug/Jack_Idle_Aim_ZUp.dbgsg similarity index 99% rename from AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/Motion/Jack_Idle_Aim_ZUp.dbgsg rename to AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/Motion/SceneDebug/Jack_Idle_Aim_ZUp.dbgsg index 8b0132a448..c8c5c8697b 100644 --- a/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/Motion/Jack_Idle_Aim_ZUp.dbgsg +++ b/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/Motion/SceneDebug/Jack_Idle_Aim_ZUp.dbgsg @@ -242,7 +242,7 @@ Node Type: BoneData BasisX: < 1.000000, -0.000000, 0.000000> BasisY: < 0.000000, 1.000000, 0.000000> BasisZ: <-0.000000, -0.000000, 1.000000> - Transl: < 0.152547, 0.043345, 0.090955> + Transl: < 0.152547, 0.043345, 0.090954> Node Name: animation Node Path: RootNode.jack_root.Bip01__pelvis.spine1.spine2.animation @@ -544,7 +544,7 @@ Node Type: BoneData Node Name: animation Node Path: RootNode.jack_root.Bip01__pelvis.spine1.spine2.spine3.l_shldr.l_upArm.l_upArmRoll.animation Node Type: AnimationData - KeyFrames: Count 195. Hash: 15529789169672670472 + KeyFrames: Count 195. Hash: 8781707605519483934 TimeStepBetweenFrames: 0.033333 Node Name: transform @@ -710,7 +710,7 @@ Node Type: BoneData BasisX: < 0.514369, 0.855813, 0.054857> BasisY: < 0.088153, 0.010863, -0.996047> BasisZ: <-0.853026, 0.517172, -0.069855> - Transl: <-0.247306, -0.062325, 0.878373> + Transl: <-0.247306, -0.062325, 0.878372> Node Name: animation Node Path: RootNode.jack_root.Bip01__pelvis.spine1.spine2.spine3.r_shldr.r_upArm.r_loArm.r_loArmRoll.animation @@ -857,7 +857,7 @@ Node Type: BoneData BasisX: < 0.329257, 0.944038, -0.019538> BasisY: < 0.465563, -0.180309, -0.866452> BasisZ: <-0.821487, 0.276189, -0.498877> - Transl: <-0.255124, -0.049696, 0.794467> + Transl: <-0.255124, -0.049696, 0.794466> Node Name: animation Node Path: RootNode.jack_root.Bip01__pelvis.spine1.spine2.spine3.l_shldr.l_upArm.l_loArm.l_hand.l_metacarpal.animation @@ -939,7 +939,6 @@ Node Type: AnimationData Node Name: transform Node Path: RootNode.jack_root.Bip01__pelvis.spine1.spine2.spine3.r_shldr.r_upArm.r_loArm.r_hand.r_index1.transform - Node Type: TransformData Matrix: BasisX: < 0.939162, 0.133704, -0.316383> @@ -954,7 +953,7 @@ Node Type: BoneData BasisX: <-0.102387, -0.418082, -0.902621> BasisY: < 0.928150, 0.286271, -0.237880> BasisZ: < 0.357847, -0.862123, 0.358732> - Transl: < 0.187367, 0.698324, 1.467209> + Transl: < 0.187367, 0.698323, 1.467209> Node Name: animation Node Path: RootNode.jack_root.Bip01__pelvis.spine1.spine2.spine3.r_shldr.r_upArm.r_loArm.r_hand.r_mid1.animation @@ -1513,3 +1512,4 @@ Node Type: TransformData BasisY: < 0.000000, 0.229519, -0.973304> BasisZ: < 0.000000, 0.973304, 0.229519> Transl: < 0.000000, -0.023770, 0.000000> + diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/Motion/SceneDebug/jack_idle_aim_zup.dbgsg.xml b/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/Motion/SceneDebug/jack_idle_aim_zup.dbgsg.xml new file mode 100644 index 0000000000..caaf3810fe --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/Motion/SceneDebug/jack_idle_aim_zup.dbgsg.xml @@ -0,0 +1,3223 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/OneMeshMultipleMaterials/single_mesh_multiple_materials.dbgsg b/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/OneMeshMultipleMaterials/SceneDebug/single_mesh_multiple_materials.dbgsg similarity index 100% rename from AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/OneMeshMultipleMaterials/single_mesh_multiple_materials.dbgsg rename to AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/OneMeshMultipleMaterials/SceneDebug/single_mesh_multiple_materials.dbgsg diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/OneMeshMultipleMaterials/SceneDebug/single_mesh_multiple_materials.dbgsg.xml b/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/OneMeshMultipleMaterials/SceneDebug/single_mesh_multiple_materials.dbgsg.xml new file mode 100644 index 0000000000..80b80fd67c --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/OneMeshMultipleMaterials/SceneDebug/single_mesh_multiple_materials.dbgsg.xml @@ -0,0 +1,849 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/OneMeshOneMaterial/onemeshonematerial.dbgsg b/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/OneMeshOneMaterial/SceneDebug/onemeshonematerial.dbgsg similarity index 100% rename from AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/OneMeshOneMaterial/onemeshonematerial.dbgsg rename to AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/OneMeshOneMaterial/SceneDebug/onemeshonematerial.dbgsg diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/OneMeshOneMaterial/SceneDebug/onemeshonematerial.dbgsg.xml b/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/OneMeshOneMaterial/SceneDebug/onemeshonematerial.dbgsg.xml new file mode 100644 index 0000000000..87cc4fe260 --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/OneMeshOneMaterial/SceneDebug/onemeshonematerial.dbgsg.xml @@ -0,0 +1,519 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/ShaderBall/SceneDebug/shaderball.dbgsg b/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/ShaderBall/SceneDebug/shaderball.dbgsg new file mode 100644 index 0000000000..7d070fcea0 --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/ShaderBall/SceneDebug/shaderball.dbgsg @@ -0,0 +1,3111 @@ +ProductName: shaderball.dbgsg +debugSceneGraphVersion: 1 +shaderball +Node Name: RootNode +Node Path: RootNode +Node Type: RootBoneData + WorldTransform: + BasisX: < 1.000000, 0.000000, 0.000000> + BasisY: < 0.000000, 1.000000, 0.000000> + BasisZ: < 0.000000, 0.000000, 1.000000> + Transl: < 0.000000, 0.000000, 0.000000> + +Node Name: ShaderBall_1m +Node Path: RootNode.ShaderBall_1m +Node Type: BoneData + WorldTransform: + BasisX: < 1.000000, 0.000000, 0.000000> + BasisY: < 0.000000, -0.000000, 1.000000> + BasisZ: < 0.000000, -1.000000, -0.000000> + Transl: < 0.000000, 0.000000, 0.000000> + +Node Name: transform +Node Path: RootNode.ShaderBall_1m.transform +Node Type: TransformData + Matrix: + BasisX: < 1.000000, 0.000000, 0.000000> + BasisY: < 0.000000, -0.000000, 1.000000> + BasisZ: < 0.000000, -1.000000, -0.000000> + Transl: < 0.000000, 0.000000, 0.000000> + +Node Name: InnerPortion +Node Path: RootNode.ShaderBall_1m.InnerPortion +Node Type: BoneData + WorldTransform: + BasisX: < 1.000000, 0.000000, 0.000000> + BasisY: < 0.000000, -0.000000, 1.000000> + BasisZ: < 0.000000, -1.000000, -0.000000> + Transl: < 0.000000, 0.000000, 0.000000> + +Node Name: MaterialBase +Node Path: RootNode.ShaderBall_1m.MaterialBase +Node Type: BoneData + WorldTransform: + BasisX: < 1.000000, 0.000000, 0.000000> + BasisY: < 0.000000, -0.000000, 1.000000> + BasisZ: < 0.000000, -1.000000, -0.000000> + Transl: < 0.000000, 0.000000, 0.000000> + +Node Name: InlayRings +Node Path: RootNode.ShaderBall_1m.InlayRings +Node Type: BoneData + WorldTransform: + BasisX: < 1.000000, 0.000000, 0.000000> + BasisY: < 0.000000, -0.000000, 1.000000> + BasisZ: < 0.000000, -1.000000, -0.000000> + Transl: < 0.000000, 0.000000, 0.000000> + +Node Name: MainSphere +Node Path: RootNode.ShaderBall_1m.MainSphere +Node Type: BoneData + WorldTransform: + BasisX: < 1.000000, 0.000000, 0.000000> + BasisY: < 0.000000, -0.000000, 1.000000> + BasisZ: < 0.000000, -1.000000, -0.000000> + Transl: < 0.000000, 0.000000, 0.000000> + +Node Name: HubCap_1 +Node Path: RootNode.ShaderBall_1m.InnerPortion.HubCap.HubCap_1 +Node Type: MeshData + Positions: Count 20476. Hash: 5411361036988924549 + Normals: Count 20476. Hash: 5915154682063029054 + FaceList: Count 8676. Hash: 9142863582186575896 + FaceMaterialIds: Count 8676. Hash: 723360536895379791 + +Node Name: HubCap_2 +Node Path: RootNode.ShaderBall_1m.InnerPortion.HubCap.HubCap_2 +Node Type: BoneData + WorldTransform: + BasisX: < 1.000000, 0.000000, 0.000000> + BasisY: < 0.000000, -0.000000, 1.000000> + BasisZ: < 0.000000, -1.000000, -0.000000> + Transl: < 0.000000, 0.000000, 0.000000> + +Node Name: HubCap_1_optimized +Node Path: RootNode.ShaderBall_1m.InnerPortion.HubCap.HubCap_1_optimized +Node Type: MeshData + Positions: Count 4724. Hash: 448283466401665158 + Normals: Count 4724. Hash: 345267294337234954 + FaceList: Count 8676. Hash: 11155608373229651496 + FaceMaterialIds: Count 8676. Hash: 723360536895379791 + +Node Name: InnerSphere_1 +Node Path: RootNode.ShaderBall_1m.InnerPortion.InnerSphere.InnerSphere_1 +Node Type: MeshData + Positions: Count 19800. Hash: 17342106809405761922 + Normals: Count 19800. Hash: 602384960091561079 + FaceList: Count 9800. Hash: 15975352410309879244 + FaceMaterialIds: Count 9800. Hash: 2219364576630417284 + +Node Name: InnerSphere_2 +Node Path: RootNode.ShaderBall_1m.InnerPortion.InnerSphere.InnerSphere_2 +Node Type: BoneData + WorldTransform: + BasisX: < 1.000000, 0.000000, 0.000000> + BasisY: < 0.000000, -0.000000, 1.000000> + BasisZ: < 0.000000, -1.000000, -0.000000> + Transl: < 0.000000, 0.000000, 0.000000> + +Node Name: InnerSphere_1_optimized +Node Path: RootNode.ShaderBall_1m.InnerPortion.InnerSphere.InnerSphere_1_optimized +Node Type: MeshData + Positions: Count 5846. Hash: 18229431498904963984 + Normals: Count 5846. Hash: 12980164457801827192 + FaceList: Count 9800. Hash: 2914108932212582430 + FaceMaterialIds: Count 9800. Hash: 2219364576630417284 + +Node Name: InnerCone_1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerCone.InnerCone_1 +Node Type: MeshData + Positions: Count 192. Hash: 3022774266117638288 + Normals: Count 192. Hash: 12863565187316537150 + FaceList: Count 64. Hash: 1255131899577053537 + FaceMaterialIds: Count 64. Hash: 6312841653578246165 + +Node Name: InnerCone_2 +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerCone.InnerCone_2 +Node Type: BoneData + WorldTransform: + BasisX: < 1.000000, 0.000000, 0.000000> + BasisY: < 0.000000, -0.000000, 1.000000> + BasisZ: < 0.000000, -1.000000, -0.000000> + Transl: < 0.000000, 0.000000, 0.000000> + +Node Name: InnerCone_1_optimized +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerCone.InnerCone_1_optimized +Node Type: MeshData + Positions: Count 65. Hash: 832325255726840940 + Normals: Count 65. Hash: 14121159448916141450 + FaceList: Count 64. Hash: 9660474080562375138 + FaceMaterialIds: Count 64. Hash: 6312841653578246165 + +Node Name: InnerPost_1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerPost.InnerPost_1 +Node Type: MeshData + Positions: Count 4864. Hash: 7530018531436061848 + Normals: Count 4864. Hash: 534420091635424803 + FaceList: Count 2432. Hash: 4330575616942580147 + FaceMaterialIds: Count 2432. Hash: 12393250111858627709 + +Node Name: InnerPost_2 +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerPost.InnerPost_2 +Node Type: BoneData + WorldTransform: + BasisX: < 1.000000, 0.000000, 0.000000> + BasisY: < 0.000000, -0.000000, 1.000000> + BasisZ: < 0.000000, -1.000000, -0.000000> + Transl: < 0.000000, 0.000000, 0.000000> + +Node Name: InnerPost_1_optimized +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerPost.InnerPost_1_optimized +Node Type: MeshData + Positions: Count 1300. Hash: 15277132209442728123 + Normals: Count 1300. Hash: 1247055552073340932 + FaceList: Count 2432. Hash: 16808521883586752319 + FaceMaterialIds: Count 2432. Hash: 12393250111858627709 + +Node Name: InnerBaseCuff_1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerBaseCuff.InnerBaseCuff_1 +Node Type: MeshData + Positions: Count 4096. Hash: 1814004361979755447 + Normals: Count 4096. Hash: 1760582409511017750 + FaceList: Count 2048. Hash: 6642755656211284824 + FaceMaterialIds: Count 2048. Hash: 2795877824940392899 + +Node Name: InnerBaseCuff_2 +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerBaseCuff.InnerBaseCuff_2 +Node Type: BoneData + WorldTransform: + BasisX: < 1.000000, 0.000000, 0.000000> + BasisY: < 0.000000, -0.000000, 1.000000> + BasisZ: < 0.000000, -1.000000, -0.000000> + Transl: < 0.000000, 0.000000, 0.000000> + +Node Name: InnerBaseCuff_1_optimized +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerBaseCuff.InnerBaseCuff_1_optimized +Node Type: MeshData + Positions: Count 1122. Hash: 15009434720129565864 + Normals: Count 1122. Hash: 7245598986723209052 + FaceList: Count 2048. Hash: 2887888015166354330 + FaceMaterialIds: Count 2048. Hash: 2795877824940392899 + +Node Name: Inset_1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.Inset.Inset_1 +Node Type: MeshData + Positions: Count 1536. Hash: 6247721054948161820 + Normals: Count 1536. Hash: 16362051041722971623 + FaceList: Count 768. Hash: 3142075668387852387 + FaceMaterialIds: Count 768. Hash: 2979441869813298271 + +Node Name: Inset_2 +Node Path: RootNode.ShaderBall_1m.MaterialBase.Inset.Inset_2 +Node Type: BoneData + WorldTransform: + BasisX: < 1.000000, 0.000000, 0.000000> + BasisY: < 0.000000, -0.000000, 1.000000> + BasisZ: < 0.000000, -1.000000, -0.000000> + Transl: < 0.000000, 0.000000, 0.000000> + +Node Name: Inset_1_optimized +Node Path: RootNode.ShaderBall_1m.MaterialBase.Inset.Inset_1_optimized +Node Type: MeshData + Positions: Count 448. Hash: 9520190263881589378 + Normals: Count 448. Hash: 8658432920439039136 + FaceList: Count 768. Hash: 8621713142583851338 + FaceMaterialIds: Count 768. Hash: 2979441869813298271 + +Node Name: BottomCap_1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.BottomCap.BottomCap_1 +Node Type: MeshData + Positions: Count 960. Hash: 13502046855286963834 + Normals: Count 960. Hash: 7165134632415312413 + FaceList: Count 448. Hash: 11458684524699877690 + FaceMaterialIds: Count 448. Hash: 15444662993354423801 + +Node Name: BottomCap_2 +Node Path: RootNode.ShaderBall_1m.MaterialBase.BottomCap.BottomCap_2 +Node Type: BoneData + WorldTransform: + BasisX: < 1.000000, 0.000000, 0.000000> + BasisY: < 0.000000, -0.000000, 1.000000> + BasisZ: < 0.000000, -1.000000, -0.000000> + Transl: < 0.000000, 0.000000, 0.000000> + +Node Name: BottomCap_1_optimized +Node Path: RootNode.ShaderBall_1m.MaterialBase.BottomCap.BottomCap_1_optimized +Node Type: MeshData + Positions: Count 257. Hash: 6437758550313952689 + Normals: Count 257. Hash: 9382966961287919705 + FaceList: Count 448. Hash: 16093592375171717669 + FaceMaterialIds: Count 448. Hash: 15444662993354423801 + +Node Name: InnerCushion_1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerCushion.InnerCushion_1 +Node Type: MeshData + Positions: Count 54208. Hash: 17548679297809443982 + Normals: Count 54208. Hash: 18279651495381460361 + FaceList: Count 27104. Hash: 12714006452420198903 + FaceMaterialIds: Count 27104. Hash: 12674758055018775830 + +Node Name: InnerCushion_2 +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerCushion.InnerCushion_2 +Node Type: BoneData + WorldTransform: + BasisX: < 1.000000, 0.000000, 0.000000> + BasisY: < 0.000000, -0.000000, 1.000000> + BasisZ: < 0.000000, -1.000000, -0.000000> + Transl: < 0.000000, 0.000000, 0.000000> + +Node Name: InnerCushion_1_optimized +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerCushion.InnerCushion_1_optimized +Node Type: MeshData + Positions: Count 13860. Hash: 15965614532178328362 + Normals: Count 13860. Hash: 7297376140184766040 + FaceList: Count 27104. Hash: 2523751700832022798 + FaceMaterialIds: Count 27104. Hash: 12674758055018775830 + +Node Name: OuterBaseCuff_1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.OuterBaseCuff.OuterBaseCuff_1 +Node Type: MeshData + Positions: Count 20048. Hash: 3861768905934340307 + Normals: Count 20048. Hash: 12292053423401502106 + FaceList: Count 10024. Hash: 4821138438569111218 + FaceMaterialIds: Count 10024. Hash: 9849297365743356453 + +Node Name: OuterBaseCuff_2 +Node Path: RootNode.ShaderBall_1m.MaterialBase.OuterBaseCuff.OuterBaseCuff_2 +Node Type: BoneData + WorldTransform: + BasisX: < 1.000000, 0.000000, 0.000000> + BasisY: < 0.000000, -0.000000, 1.000000> + BasisZ: < 0.000000, -1.000000, -0.000000> + Transl: < 0.000000, 0.000000, 0.000000> + +Node Name: OuterBaseCuff_1_optimized +Node Path: RootNode.ShaderBall_1m.MaterialBase.OuterBaseCuff.OuterBaseCuff_1_optimized +Node Type: MeshData + Positions: Count 5206. Hash: 5201932106801476439 + Normals: Count 5206. Hash: 15696187265416627056 + FaceList: Count 10024. Hash: 10907381836011223214 + FaceMaterialIds: Count 10024. Hash: 9849297365743356453 + +Node Name: RingLeft_1 +Node Path: RootNode.ShaderBall_1m.InlayRings.RingLeft.RingLeft_1 +Node Type: MeshData + Positions: Count 2560. Hash: 10944366330558725569 + Normals: Count 2560. Hash: 11471590896496428199 + FaceList: Count 1280. Hash: 2548580276766813978 + FaceMaterialIds: Count 1280. Hash: 6371267399123018661 + +Node Name: RingLeft_2 +Node Path: RootNode.ShaderBall_1m.InlayRings.RingLeft.RingLeft_2 +Node Type: BoneData + WorldTransform: + BasisX: < 1.000000, 0.000000, 0.000000> + BasisY: < 0.000000, -0.000000, 1.000000> + BasisZ: < 0.000000, -1.000000, -0.000000> + Transl: < 0.000000, 0.000000, 0.000000> + +Node Name: RingLeft_1_optimized +Node Path: RootNode.ShaderBall_1m.InlayRings.RingLeft.RingLeft_1_optimized +Node Type: MeshData + Positions: Count 720. Hash: 14374869925777029719 + Normals: Count 720. Hash: 2252820527750115179 + FaceList: Count 1280. Hash: 17583744445000895264 + FaceMaterialIds: Count 1280. Hash: 6371267399123018661 + +Node Name: RingRight_1 +Node Path: RootNode.ShaderBall_1m.InlayRings.RingRight.RingRight_1 +Node Type: MeshData + Positions: Count 2560. Hash: 17639843025153488175 + Normals: Count 2560. Hash: 8564843488923790338 + FaceList: Count 1280. Hash: 2548580276766813978 + FaceMaterialIds: Count 1280. Hash: 6371267399123018661 + +Node Name: RingRight_2 +Node Path: RootNode.ShaderBall_1m.InlayRings.RingRight.RingRight_2 +Node Type: BoneData + WorldTransform: + BasisX: < 1.000000, 0.000000, 0.000000> + BasisY: < 0.000000, -0.000000, 1.000000> + BasisZ: < 0.000000, -1.000000, -0.000000> + Transl: < 0.000000, 0.000000, 0.000000> + +Node Name: RingRight_1_optimized +Node Path: RootNode.ShaderBall_1m.InlayRings.RingRight.RingRight_1_optimized +Node Type: MeshData + Positions: Count 720. Hash: 17033597990859129189 + Normals: Count 720. Hash: 14263134585377771060 + FaceList: Count 1280. Hash: 13824345071081010014 + FaceMaterialIds: Count 1280. Hash: 6371267399123018661 + +Node Name: RightHub_1 +Node Path: RootNode.ShaderBall_1m.MainSphere.RightHub.RightHub_1 +Node Type: MeshData + Positions: Count 6304. Hash: 11021385291123971383 + Normals: Count 6304. Hash: 10267084099841111837 + FaceList: Count 3152. Hash: 17728162485525521181 + FaceMaterialIds: Count 3152. Hash: 17405713692885844041 + +Node Name: RightHub_2 +Node Path: RootNode.ShaderBall_1m.MainSphere.RightHub.RightHub_2 +Node Type: BoneData + WorldTransform: + BasisX: < 1.000000, 0.000000, 0.000000> + BasisY: < 0.000000, -0.000000, 1.000000> + BasisZ: < 0.000000, -1.000000, -0.000000> + Transl: < 0.000000, 0.000000, 0.000000> + +Node Name: RightHub_1_optimized +Node Path: RootNode.ShaderBall_1m.MainSphere.RightHub.RightHub_1_optimized +Node Type: MeshData + Positions: Count 1617. Hash: 9820946434895369083 + Normals: Count 1617. Hash: 16951123277321747448 + FaceList: Count 3152. Hash: 15437295155178226041 + FaceMaterialIds: Count 3152. Hash: 17405713692885844041 + +Node Name: LeftHub_1 +Node Path: RootNode.ShaderBall_1m.MainSphere.LeftHub.LeftHub_1 +Node Type: MeshData + Positions: Count 6304. Hash: 7564731352768958355 + Normals: Count 6304. Hash: 8133714814490222392 + FaceList: Count 3152. Hash: 17728162485525521181 + FaceMaterialIds: Count 3152. Hash: 17405713692885844041 + +Node Name: LeftHub_2 +Node Path: RootNode.ShaderBall_1m.MainSphere.LeftHub.LeftHub_2 +Node Type: BoneData + WorldTransform: + BasisX: < 1.000000, 0.000000, 0.000000> + BasisY: < 0.000000, -0.000000, 1.000000> + BasisZ: < 0.000000, -1.000000, -0.000000> + Transl: < 0.000000, 0.000000, 0.000000> + +Node Name: LeftHub_1_optimized +Node Path: RootNode.ShaderBall_1m.MainSphere.LeftHub.LeftHub_1_optimized +Node Type: MeshData + Positions: Count 1617. Hash: 8811470420139443913 + Normals: Count 1617. Hash: 11224729257132823435 + FaceList: Count 3152. Hash: 16696390019846851737 + FaceMaterialIds: Count 3152. Hash: 17405713692885844041 + +Node Name: Inside_1 +Node Path: RootNode.ShaderBall_1m.MainSphere.Inside.Inside_1 +Node Type: MeshData + Positions: Count 3520. Hash: 13476777876937219698 + Normals: Count 3520. Hash: 10561277746451021236 + FaceList: Count 1760. Hash: 1345243954764462275 + FaceMaterialIds: Count 1760. Hash: 3100204266221257056 + +Node Name: Inside_2 +Node Path: RootNode.ShaderBall_1m.MainSphere.Inside.Inside_2 +Node Type: BoneData + WorldTransform: + BasisX: < 1.000000, 0.000000, 0.000000> + BasisY: < 0.000000, -0.000000, 1.000000> + BasisZ: < 0.000000, -1.000000, -0.000000> + Transl: < 0.000000, 0.000000, 0.000000> + +Node Name: Inside_1_optimized +Node Path: RootNode.ShaderBall_1m.MainSphere.Inside.Inside_1_optimized +Node Type: MeshData + Positions: Count 960. Hash: 1074011803485396393 + Normals: Count 960. Hash: 16318911598614464642 + FaceList: Count 1760. Hash: 3382287220404120115 + FaceMaterialIds: Count 1760. Hash: 3100204266221257056 + +Node Name: MainOuterSphere_1 +Node Path: RootNode.ShaderBall_1m.MainSphere.MainOuterSphere.MainOuterSphere_1 +Node Type: MeshData + Positions: Count 18912. Hash: 15956257973657753552 + Normals: Count 18912. Hash: 9328348641512406334 + FaceList: Count 9456. Hash: 9836933646038198686 + FaceMaterialIds: Count 9456. Hash: 13982281543095132650 + +Node Name: MainOuterSphere_2 +Node Path: RootNode.ShaderBall_1m.MainSphere.MainOuterSphere.MainOuterSphere_2 +Node Type: BoneData + WorldTransform: + BasisX: < 1.000000, 0.000000, 0.000000> + BasisY: < 0.000000, -0.000000, 1.000000> + BasisZ: < 0.000000, -1.000000, -0.000000> + Transl: < 0.000000, 0.000000, 0.000000> + +Node Name: MainOuterSphere_1_optimized +Node Path: RootNode.ShaderBall_1m.MainSphere.MainOuterSphere.MainOuterSphere_1_optimized +Node Type: MeshData + Positions: Count 4891. Hash: 9579089515723090907 + Normals: Count 4891. Hash: 10089610259011330329 + FaceList: Count 9456. Hash: 13541126452398082145 + FaceMaterialIds: Count 9456. Hash: 13982281543095132650 + +Node Name: Tiled +Node Path: RootNode.ShaderBall_1m.InnerPortion.HubCap.HubCap_1.Tiled +Node Type: MeshVertexUVData + UVs: Count 20476. Hash: 10688945422788452939 + UVCustomName: Tiled + +Node Name: Unwrapped +Node Path: RootNode.ShaderBall_1m.InnerPortion.HubCap.HubCap_1.Unwrapped +Node Type: MeshVertexUVData + UVs: Count 20476. Hash: 1387390223232454000 + UVCustomName: Unwrapped + +Node Name: blinn1 +Node Path: RootNode.ShaderBall_1m.InnerPortion.HubCap.HubCap_1.blinn1 +Node Type: MaterialData + MaterialName: blinn1 + UniqueId: 2076548245838624187 + IsNoDraw: false + DiffuseColor: < 0.800000, 0.800000, 0.800000> + SpecularColor: < 0.500000, 0.500000, 0.500000> + EmissiveColor: < 0.000000, 0.000000, 0.000000> + Opacity: 1.000000 + Shininess: 6.311791 + UseColorMap: Not set + BaseColor: Not set + UseMetallicMap: Not set + MetallicFactor: Not set + UseRoughnessMap: Not set + RoughnessFactor: Not set + UseEmissiveMap: Not set + EmissiveIntensity: Not set + UseAOMap: Not set + DiffuseTexture: ShaderBall/_dev_shaderball_00_basecolor.png + SpecularTexture: + BumpTexture: + NormalTexture: + MetallicTexture: + RoughnessTexture: + AmbientOcclusionTexture: + EmissiveTexture: + BaseColorTexture: ShaderBall/_dev_shaderball_00_basecolor.png + +Node Name: TangentSet_0 +Node Path: RootNode.ShaderBall_1m.InnerPortion.HubCap.HubCap_1.TangentSet_0 +Node Type: MeshVertexTangentData + Tangents: Count 20476. Hash: 4424021631256816544 + GenerationMethod: 1 + SetIndex: 0 + +Node Name: BitangentSet_0 +Node Path: RootNode.ShaderBall_1m.InnerPortion.HubCap.HubCap_1.BitangentSet_0 +Node Type: MeshVertexBitangentData + Bitangents: Count 20476. Hash: 9768152532901400557 + GenerationMethod: 1 + +Node Name: TangentSet_1 +Node Path: RootNode.ShaderBall_1m.InnerPortion.HubCap.HubCap_1.TangentSet_1 +Node Type: MeshVertexTangentData + Tangents: Count 20476. Hash: 4890305528292926235 + GenerationMethod: 1 + SetIndex: 1 + +Node Name: BitangentSet_1 +Node Path: RootNode.ShaderBall_1m.InnerPortion.HubCap.HubCap_1.BitangentSet_1 +Node Type: MeshVertexBitangentData + Bitangents: Count 20476. Hash: 309820643999247955 + GenerationMethod: 1 + +Node Name: Tiled +Node Path: RootNode.ShaderBall_1m.InnerPortion.HubCap.HubCap_2.Tiled +Node Type: MeshVertexUVData + UVs: Count 20476. Hash: 10688945422788452939 + UVCustomName: Tiled + +Node Name: Unwrapped +Node Path: RootNode.ShaderBall_1m.InnerPortion.HubCap.HubCap_2.Unwrapped +Node Type: MeshVertexUVData + UVs: Count 20476. Hash: 1387390223232454000 + UVCustomName: Unwrapped + +Node Name: blinn1 +Node Path: RootNode.ShaderBall_1m.InnerPortion.HubCap.HubCap_2.blinn1 +Node Type: MaterialData + MaterialName: blinn1 + UniqueId: 2076548245838624187 + IsNoDraw: false + DiffuseColor: < 0.800000, 0.800000, 0.800000> + SpecularColor: < 0.500000, 0.500000, 0.500000> + EmissiveColor: < 0.000000, 0.000000, 0.000000> + Opacity: 1.000000 + Shininess: 6.311791 + UseColorMap: Not set + BaseColor: Not set + UseMetallicMap: Not set + MetallicFactor: Not set + UseRoughnessMap: Not set + RoughnessFactor: Not set + UseEmissiveMap: Not set + EmissiveIntensity: Not set + UseAOMap: Not set + DiffuseTexture: ShaderBall/_dev_shaderball_00_basecolor.png + SpecularTexture: + BumpTexture: + NormalTexture: + MetallicTexture: + RoughnessTexture: + AmbientOcclusionTexture: + EmissiveTexture: + BaseColorTexture: ShaderBall/_dev_shaderball_00_basecolor.png + +Node Name: Tiled +Node Path: RootNode.ShaderBall_1m.InnerPortion.HubCap.HubCap_1_optimized.Tiled +Node Type: MeshVertexUVData + UVs: Count 4724. Hash: 10265340188340999982 + UVCustomName: Tiled + +Node Name: Unwrapped +Node Path: RootNode.ShaderBall_1m.InnerPortion.HubCap.HubCap_1_optimized.Unwrapped +Node Type: MeshVertexUVData + UVs: Count 4724. Hash: 6393506322209434620 + UVCustomName: Unwrapped + +Node Name: TangentSet_0 +Node Path: RootNode.ShaderBall_1m.InnerPortion.HubCap.HubCap_1_optimized.TangentSet_0 +Node Type: MeshVertexTangentData + Tangents: Count 4724. Hash: 7072046838448900487 + GenerationMethod: 1 + SetIndex: 0 + +Node Name: TangentSet_1 +Node Path: RootNode.ShaderBall_1m.InnerPortion.HubCap.HubCap_1_optimized.TangentSet_1 +Node Type: MeshVertexTangentData + Tangents: Count 4724. Hash: 14469479861311642848 + GenerationMethod: 1 + SetIndex: 1 + +Node Name: BitangentSet_0 +Node Path: RootNode.ShaderBall_1m.InnerPortion.HubCap.HubCap_1_optimized.BitangentSet_0 +Node Type: MeshVertexBitangentData + Bitangents: Count 4724. Hash: 14586570136206675867 + GenerationMethod: 1 + +Node Name: BitangentSet_1 +Node Path: RootNode.ShaderBall_1m.InnerPortion.HubCap.HubCap_1_optimized.BitangentSet_1 +Node Type: MeshVertexBitangentData + Bitangents: Count 4724. Hash: 1080930468361041453 + GenerationMethod: 1 + +Node Name: blinn1 +Node Path: RootNode.ShaderBall_1m.InnerPortion.HubCap.HubCap_1_optimized.blinn1 +Node Type: MaterialData + MaterialName: blinn1 + UniqueId: 2076548245838624187 + IsNoDraw: false + DiffuseColor: < 0.800000, 0.800000, 0.800000> + SpecularColor: < 0.500000, 0.500000, 0.500000> + EmissiveColor: < 0.000000, 0.000000, 0.000000> + Opacity: 1.000000 + Shininess: 6.311791 + UseColorMap: Not set + BaseColor: Not set + UseMetallicMap: Not set + MetallicFactor: Not set + UseRoughnessMap: Not set + RoughnessFactor: Not set + UseEmissiveMap: Not set + EmissiveIntensity: Not set + UseAOMap: Not set + DiffuseTexture: ShaderBall/_dev_shaderball_00_basecolor.png + SpecularTexture: + BumpTexture: + NormalTexture: + MetallicTexture: + RoughnessTexture: + AmbientOcclusionTexture: + EmissiveTexture: + BaseColorTexture: ShaderBall/_dev_shaderball_00_basecolor.png + +Node Name: Tiled +Node Path: RootNode.ShaderBall_1m.InnerPortion.InnerSphere.InnerSphere_1.Tiled +Node Type: MeshVertexUVData + UVs: Count 19800. Hash: 9998270082112342253 + UVCustomName: Tiled + +Node Name: Unwrapped +Node Path: RootNode.ShaderBall_1m.InnerPortion.InnerSphere.InnerSphere_1.Unwrapped +Node Type: MeshVertexUVData + UVs: Count 19800. Hash: 715698668253946311 + UVCustomName: Unwrapped + +Node Name: blinn1 +Node Path: RootNode.ShaderBall_1m.InnerPortion.InnerSphere.InnerSphere_1.blinn1 +Node Type: MaterialData + MaterialName: blinn1 + UniqueId: 2076548245838624187 + IsNoDraw: false + DiffuseColor: < 0.800000, 0.800000, 0.800000> + SpecularColor: < 0.500000, 0.500000, 0.500000> + EmissiveColor: < 0.000000, 0.000000, 0.000000> + Opacity: 1.000000 + Shininess: 6.311791 + UseColorMap: Not set + BaseColor: Not set + UseMetallicMap: Not set + MetallicFactor: Not set + UseRoughnessMap: Not set + RoughnessFactor: Not set + UseEmissiveMap: Not set + EmissiveIntensity: Not set + UseAOMap: Not set + DiffuseTexture: ShaderBall/_dev_shaderball_00_basecolor.png + SpecularTexture: + BumpTexture: + NormalTexture: + MetallicTexture: + RoughnessTexture: + AmbientOcclusionTexture: + EmissiveTexture: + BaseColorTexture: ShaderBall/_dev_shaderball_00_basecolor.png + +Node Name: TangentSet_0 +Node Path: RootNode.ShaderBall_1m.InnerPortion.InnerSphere.InnerSphere_1.TangentSet_0 +Node Type: MeshVertexTangentData + Tangents: Count 19800. Hash: 9689144294054390217 + GenerationMethod: 1 + SetIndex: 0 + +Node Name: BitangentSet_0 +Node Path: RootNode.ShaderBall_1m.InnerPortion.InnerSphere.InnerSphere_1.BitangentSet_0 +Node Type: MeshVertexBitangentData + Bitangents: Count 19800. Hash: 13129471596255615133 + GenerationMethod: 1 + +Node Name: TangentSet_1 +Node Path: RootNode.ShaderBall_1m.InnerPortion.InnerSphere.InnerSphere_1.TangentSet_1 +Node Type: MeshVertexTangentData + Tangents: Count 19800. Hash: 12915864712175384367 + GenerationMethod: 1 + SetIndex: 1 + +Node Name: BitangentSet_1 +Node Path: RootNode.ShaderBall_1m.InnerPortion.InnerSphere.InnerSphere_1.BitangentSet_1 +Node Type: MeshVertexBitangentData + Bitangents: Count 19800. Hash: 704744783983559605 + GenerationMethod: 1 + +Node Name: Tiled +Node Path: RootNode.ShaderBall_1m.InnerPortion.InnerSphere.InnerSphere_2.Tiled +Node Type: MeshVertexUVData + UVs: Count 19800. Hash: 9998270082112342253 + UVCustomName: Tiled + +Node Name: Unwrapped +Node Path: RootNode.ShaderBall_1m.InnerPortion.InnerSphere.InnerSphere_2.Unwrapped +Node Type: MeshVertexUVData + UVs: Count 19800. Hash: 715698668253946311 + UVCustomName: Unwrapped + +Node Name: blinn1 +Node Path: RootNode.ShaderBall_1m.InnerPortion.InnerSphere.InnerSphere_2.blinn1 +Node Type: MaterialData + MaterialName: blinn1 + UniqueId: 2076548245838624187 + IsNoDraw: false + DiffuseColor: < 0.800000, 0.800000, 0.800000> + SpecularColor: < 0.500000, 0.500000, 0.500000> + EmissiveColor: < 0.000000, 0.000000, 0.000000> + Opacity: 1.000000 + Shininess: 6.311791 + UseColorMap: Not set + BaseColor: Not set + UseMetallicMap: Not set + MetallicFactor: Not set + UseRoughnessMap: Not set + RoughnessFactor: Not set + UseEmissiveMap: Not set + EmissiveIntensity: Not set + UseAOMap: Not set + DiffuseTexture: ShaderBall/_dev_shaderball_00_basecolor.png + SpecularTexture: + BumpTexture: + NormalTexture: + MetallicTexture: + RoughnessTexture: + AmbientOcclusionTexture: + EmissiveTexture: + BaseColorTexture: ShaderBall/_dev_shaderball_00_basecolor.png + +Node Name: Tiled +Node Path: RootNode.ShaderBall_1m.InnerPortion.InnerSphere.InnerSphere_1_optimized.Tiled +Node Type: MeshVertexUVData + UVs: Count 5846. Hash: 2128276120164603588 + UVCustomName: Tiled + +Node Name: Unwrapped +Node Path: RootNode.ShaderBall_1m.InnerPortion.InnerSphere.InnerSphere_1_optimized.Unwrapped +Node Type: MeshVertexUVData + UVs: Count 5846. Hash: 954788723450394678 + UVCustomName: Unwrapped + +Node Name: TangentSet_0 +Node Path: RootNode.ShaderBall_1m.InnerPortion.InnerSphere.InnerSphere_1_optimized.TangentSet_0 +Node Type: MeshVertexTangentData + Tangents: Count 5846. Hash: 14309642535096835998 + GenerationMethod: 1 + SetIndex: 0 + +Node Name: TangentSet_1 +Node Path: RootNode.ShaderBall_1m.InnerPortion.InnerSphere.InnerSphere_1_optimized.TangentSet_1 +Node Type: MeshVertexTangentData + Tangents: Count 5846. Hash: 2996000735336843208 + GenerationMethod: 1 + SetIndex: 1 + +Node Name: BitangentSet_0 +Node Path: RootNode.ShaderBall_1m.InnerPortion.InnerSphere.InnerSphere_1_optimized.BitangentSet_0 +Node Type: MeshVertexBitangentData + Bitangents: Count 5846. Hash: 6423899825309547347 + GenerationMethod: 1 + +Node Name: BitangentSet_1 +Node Path: RootNode.ShaderBall_1m.InnerPortion.InnerSphere.InnerSphere_1_optimized.BitangentSet_1 +Node Type: MeshVertexBitangentData + Bitangents: Count 5846. Hash: 6861847030641362531 + GenerationMethod: 1 + +Node Name: blinn1 +Node Path: RootNode.ShaderBall_1m.InnerPortion.InnerSphere.InnerSphere_1_optimized.blinn1 +Node Type: MaterialData + MaterialName: blinn1 + UniqueId: 2076548245838624187 + IsNoDraw: false + DiffuseColor: < 0.800000, 0.800000, 0.800000> + SpecularColor: < 0.500000, 0.500000, 0.500000> + EmissiveColor: < 0.000000, 0.000000, 0.000000> + Opacity: 1.000000 + Shininess: 6.311791 + UseColorMap: Not set + BaseColor: Not set + UseMetallicMap: Not set + MetallicFactor: Not set + UseRoughnessMap: Not set + RoughnessFactor: Not set + UseEmissiveMap: Not set + EmissiveIntensity: Not set + UseAOMap: Not set + DiffuseTexture: ShaderBall/_dev_shaderball_00_basecolor.png + SpecularTexture: + BumpTexture: + NormalTexture: + MetallicTexture: + RoughnessTexture: + AmbientOcclusionTexture: + EmissiveTexture: + BaseColorTexture: ShaderBall/_dev_shaderball_00_basecolor.png + +Node Name: Tiled +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerCone.InnerCone_1.Tiled +Node Type: MeshVertexUVData + UVs: Count 192. Hash: 10645867109602892598 + UVCustomName: Tiled + +Node Name: Unwrapped +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerCone.InnerCone_1.Unwrapped +Node Type: MeshVertexUVData + UVs: Count 192. Hash: 7257961874201179082 + UVCustomName: Unwrapped + +Node Name: blinn1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerCone.InnerCone_1.blinn1 +Node Type: MaterialData + MaterialName: blinn1 + UniqueId: 2076548245838624187 + IsNoDraw: false + DiffuseColor: < 0.800000, 0.800000, 0.800000> + SpecularColor: < 0.500000, 0.500000, 0.500000> + EmissiveColor: < 0.000000, 0.000000, 0.000000> + Opacity: 1.000000 + Shininess: 6.311791 + UseColorMap: Not set + BaseColor: Not set + UseMetallicMap: Not set + MetallicFactor: Not set + UseRoughnessMap: Not set + RoughnessFactor: Not set + UseEmissiveMap: Not set + EmissiveIntensity: Not set + UseAOMap: Not set + DiffuseTexture: ShaderBall/_dev_shaderball_00_basecolor.png + SpecularTexture: + BumpTexture: + NormalTexture: + MetallicTexture: + RoughnessTexture: + AmbientOcclusionTexture: + EmissiveTexture: + BaseColorTexture: ShaderBall/_dev_shaderball_00_basecolor.png + +Node Name: TangentSet_0 +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerCone.InnerCone_1.TangentSet_0 +Node Type: MeshVertexTangentData + Tangents: Count 192. Hash: 12720324392877726426 + GenerationMethod: 1 + SetIndex: 0 + +Node Name: BitangentSet_0 +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerCone.InnerCone_1.BitangentSet_0 +Node Type: MeshVertexBitangentData + Bitangents: Count 192. Hash: 7937958557505694755 + GenerationMethod: 1 + +Node Name: TangentSet_1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerCone.InnerCone_1.TangentSet_1 +Node Type: MeshVertexTangentData + Tangents: Count 192. Hash: 15065829696130008213 + GenerationMethod: 1 + SetIndex: 1 + +Node Name: BitangentSet_1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerCone.InnerCone_1.BitangentSet_1 +Node Type: MeshVertexBitangentData + Bitangents: Count 192. Hash: 14378088137727336097 + GenerationMethod: 1 + +Node Name: Tiled +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerCone.InnerCone_2.Tiled +Node Type: MeshVertexUVData + UVs: Count 192. Hash: 10645867109602892598 + UVCustomName: Tiled + +Node Name: Unwrapped +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerCone.InnerCone_2.Unwrapped +Node Type: MeshVertexUVData + UVs: Count 192. Hash: 7257961874201179082 + UVCustomName: Unwrapped + +Node Name: blinn1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerCone.InnerCone_2.blinn1 +Node Type: MaterialData + MaterialName: blinn1 + UniqueId: 2076548245838624187 + IsNoDraw: false + DiffuseColor: < 0.800000, 0.800000, 0.800000> + SpecularColor: < 0.500000, 0.500000, 0.500000> + EmissiveColor: < 0.000000, 0.000000, 0.000000> + Opacity: 1.000000 + Shininess: 6.311791 + UseColorMap: Not set + BaseColor: Not set + UseMetallicMap: Not set + MetallicFactor: Not set + UseRoughnessMap: Not set + RoughnessFactor: Not set + UseEmissiveMap: Not set + EmissiveIntensity: Not set + UseAOMap: Not set + DiffuseTexture: ShaderBall/_dev_shaderball_00_basecolor.png + SpecularTexture: + BumpTexture: + NormalTexture: + MetallicTexture: + RoughnessTexture: + AmbientOcclusionTexture: + EmissiveTexture: + BaseColorTexture: ShaderBall/_dev_shaderball_00_basecolor.png + +Node Name: Tiled +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerCone.InnerCone_1_optimized.Tiled +Node Type: MeshVertexUVData + UVs: Count 65. Hash: 3145658351065228323 + UVCustomName: Tiled + +Node Name: Unwrapped +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerCone.InnerCone_1_optimized.Unwrapped +Node Type: MeshVertexUVData + UVs: Count 65. Hash: 13102825703658386866 + UVCustomName: Unwrapped + +Node Name: TangentSet_0 +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerCone.InnerCone_1_optimized.TangentSet_0 +Node Type: MeshVertexTangentData + Tangents: Count 65. Hash: 14651886668877289638 + GenerationMethod: 1 + SetIndex: 0 + +Node Name: TangentSet_1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerCone.InnerCone_1_optimized.TangentSet_1 +Node Type: MeshVertexTangentData + Tangents: Count 65. Hash: 7706988068999921308 + GenerationMethod: 1 + SetIndex: 1 + +Node Name: BitangentSet_0 +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerCone.InnerCone_1_optimized.BitangentSet_0 +Node Type: MeshVertexBitangentData + Bitangents: Count 65. Hash: 1183045102730537867 + GenerationMethod: 1 + +Node Name: BitangentSet_1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerCone.InnerCone_1_optimized.BitangentSet_1 +Node Type: MeshVertexBitangentData + Bitangents: Count 65. Hash: 15200278891890596008 + GenerationMethod: 1 + +Node Name: blinn1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerCone.InnerCone_1_optimized.blinn1 +Node Type: MaterialData + MaterialName: blinn1 + UniqueId: 2076548245838624187 + IsNoDraw: false + DiffuseColor: < 0.800000, 0.800000, 0.800000> + SpecularColor: < 0.500000, 0.500000, 0.500000> + EmissiveColor: < 0.000000, 0.000000, 0.000000> + Opacity: 1.000000 + Shininess: 6.311791 + UseColorMap: Not set + BaseColor: Not set + UseMetallicMap: Not set + MetallicFactor: Not set + UseRoughnessMap: Not set + RoughnessFactor: Not set + UseEmissiveMap: Not set + EmissiveIntensity: Not set + UseAOMap: Not set + DiffuseTexture: ShaderBall/_dev_shaderball_00_basecolor.png + SpecularTexture: + BumpTexture: + NormalTexture: + MetallicTexture: + RoughnessTexture: + AmbientOcclusionTexture: + EmissiveTexture: + BaseColorTexture: ShaderBall/_dev_shaderball_00_basecolor.png + +Node Name: Tiled +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerPost.InnerPost_1.Tiled +Node Type: MeshVertexUVData + UVs: Count 4864. Hash: 5283498994389857134 + UVCustomName: Tiled + +Node Name: Unwrapped +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerPost.InnerPost_1.Unwrapped +Node Type: MeshVertexUVData + UVs: Count 4864. Hash: 4759806696539235318 + UVCustomName: Unwrapped + +Node Name: blinn1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerPost.InnerPost_1.blinn1 +Node Type: MaterialData + MaterialName: blinn1 + UniqueId: 2076548245838624187 + IsNoDraw: false + DiffuseColor: < 0.800000, 0.800000, 0.800000> + SpecularColor: < 0.500000, 0.500000, 0.500000> + EmissiveColor: < 0.000000, 0.000000, 0.000000> + Opacity: 1.000000 + Shininess: 6.311791 + UseColorMap: Not set + BaseColor: Not set + UseMetallicMap: Not set + MetallicFactor: Not set + UseRoughnessMap: Not set + RoughnessFactor: Not set + UseEmissiveMap: Not set + EmissiveIntensity: Not set + UseAOMap: Not set + DiffuseTexture: ShaderBall/_dev_shaderball_00_basecolor.png + SpecularTexture: + BumpTexture: + NormalTexture: + MetallicTexture: + RoughnessTexture: + AmbientOcclusionTexture: + EmissiveTexture: + BaseColorTexture: ShaderBall/_dev_shaderball_00_basecolor.png + +Node Name: TangentSet_0 +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerPost.InnerPost_1.TangentSet_0 +Node Type: MeshVertexTangentData + Tangents: Count 4864. Hash: 1916980755154570809 + GenerationMethod: 1 + SetIndex: 0 + +Node Name: BitangentSet_0 +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerPost.InnerPost_1.BitangentSet_0 +Node Type: MeshVertexBitangentData + Bitangents: Count 4864. Hash: 3641075817419129841 + GenerationMethod: 1 + +Node Name: TangentSet_1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerPost.InnerPost_1.TangentSet_1 +Node Type: MeshVertexTangentData + Tangents: Count 4864. Hash: 1597884606887295389 + GenerationMethod: 1 + SetIndex: 1 + +Node Name: BitangentSet_1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerPost.InnerPost_1.BitangentSet_1 +Node Type: MeshVertexBitangentData + Bitangents: Count 4864. Hash: 12470368568863176335 + GenerationMethod: 1 + +Node Name: Tiled +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerPost.InnerPost_2.Tiled +Node Type: MeshVertexUVData + UVs: Count 4864. Hash: 5283498994389857134 + UVCustomName: Tiled + +Node Name: Unwrapped +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerPost.InnerPost_2.Unwrapped +Node Type: MeshVertexUVData + UVs: Count 4864. Hash: 4759806696539235318 + UVCustomName: Unwrapped + +Node Name: blinn1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerPost.InnerPost_2.blinn1 +Node Type: MaterialData + MaterialName: blinn1 + UniqueId: 2076548245838624187 + IsNoDraw: false + DiffuseColor: < 0.800000, 0.800000, 0.800000> + SpecularColor: < 0.500000, 0.500000, 0.500000> + EmissiveColor: < 0.000000, 0.000000, 0.000000> + Opacity: 1.000000 + Shininess: 6.311791 + UseColorMap: Not set + BaseColor: Not set + UseMetallicMap: Not set + MetallicFactor: Not set + UseRoughnessMap: Not set + RoughnessFactor: Not set + UseEmissiveMap: Not set + EmissiveIntensity: Not set + UseAOMap: Not set + DiffuseTexture: ShaderBall/_dev_shaderball_00_basecolor.png + SpecularTexture: + BumpTexture: + NormalTexture: + MetallicTexture: + RoughnessTexture: + AmbientOcclusionTexture: + EmissiveTexture: + BaseColorTexture: ShaderBall/_dev_shaderball_00_basecolor.png + +Node Name: Tiled +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerPost.InnerPost_1_optimized.Tiled +Node Type: MeshVertexUVData + UVs: Count 1300. Hash: 519927422840758162 + UVCustomName: Tiled + +Node Name: Unwrapped +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerPost.InnerPost_1_optimized.Unwrapped +Node Type: MeshVertexUVData + UVs: Count 1300. Hash: 16690819534142395524 + UVCustomName: Unwrapped + +Node Name: TangentSet_0 +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerPost.InnerPost_1_optimized.TangentSet_0 +Node Type: MeshVertexTangentData + Tangents: Count 1300. Hash: 13697492049473980098 + GenerationMethod: 1 + SetIndex: 0 + +Node Name: TangentSet_1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerPost.InnerPost_1_optimized.TangentSet_1 +Node Type: MeshVertexTangentData + Tangents: Count 1300. Hash: 16069718749273082363 + GenerationMethod: 1 + SetIndex: 1 + +Node Name: BitangentSet_0 +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerPost.InnerPost_1_optimized.BitangentSet_0 +Node Type: MeshVertexBitangentData + Bitangents: Count 1300. Hash: 9503213552298852479 + GenerationMethod: 1 + +Node Name: BitangentSet_1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerPost.InnerPost_1_optimized.BitangentSet_1 +Node Type: MeshVertexBitangentData + Bitangents: Count 1300. Hash: 14920629477034919393 + GenerationMethod: 1 + +Node Name: blinn1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerPost.InnerPost_1_optimized.blinn1 +Node Type: MaterialData + MaterialName: blinn1 + UniqueId: 2076548245838624187 + IsNoDraw: false + DiffuseColor: < 0.800000, 0.800000, 0.800000> + SpecularColor: < 0.500000, 0.500000, 0.500000> + EmissiveColor: < 0.000000, 0.000000, 0.000000> + Opacity: 1.000000 + Shininess: 6.311791 + UseColorMap: Not set + BaseColor: Not set + UseMetallicMap: Not set + MetallicFactor: Not set + UseRoughnessMap: Not set + RoughnessFactor: Not set + UseEmissiveMap: Not set + EmissiveIntensity: Not set + UseAOMap: Not set + DiffuseTexture: ShaderBall/_dev_shaderball_00_basecolor.png + SpecularTexture: + BumpTexture: + NormalTexture: + MetallicTexture: + RoughnessTexture: + AmbientOcclusionTexture: + EmissiveTexture: + BaseColorTexture: ShaderBall/_dev_shaderball_00_basecolor.png + +Node Name: Tiled +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerBaseCuff.InnerBaseCuff_1.Tiled +Node Type: MeshVertexUVData + UVs: Count 4096. Hash: 12344372623177285558 + UVCustomName: Tiled + +Node Name: Unwrapped +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerBaseCuff.InnerBaseCuff_1.Unwrapped +Node Type: MeshVertexUVData + UVs: Count 4096. Hash: 3415525735687273566 + UVCustomName: Unwrapped + +Node Name: blinn1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerBaseCuff.InnerBaseCuff_1.blinn1 +Node Type: MaterialData + MaterialName: blinn1 + UniqueId: 2076548245838624187 + IsNoDraw: false + DiffuseColor: < 0.800000, 0.800000, 0.800000> + SpecularColor: < 0.500000, 0.500000, 0.500000> + EmissiveColor: < 0.000000, 0.000000, 0.000000> + Opacity: 1.000000 + Shininess: 6.311791 + UseColorMap: Not set + BaseColor: Not set + UseMetallicMap: Not set + MetallicFactor: Not set + UseRoughnessMap: Not set + RoughnessFactor: Not set + UseEmissiveMap: Not set + EmissiveIntensity: Not set + UseAOMap: Not set + DiffuseTexture: ShaderBall/_dev_shaderball_00_basecolor.png + SpecularTexture: + BumpTexture: + NormalTexture: + MetallicTexture: + RoughnessTexture: + AmbientOcclusionTexture: + EmissiveTexture: + BaseColorTexture: ShaderBall/_dev_shaderball_00_basecolor.png + +Node Name: TangentSet_0 +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerBaseCuff.InnerBaseCuff_1.TangentSet_0 +Node Type: MeshVertexTangentData + Tangents: Count 4096. Hash: 1937219152553164558 + GenerationMethod: 1 + SetIndex: 0 + +Node Name: BitangentSet_0 +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerBaseCuff.InnerBaseCuff_1.BitangentSet_0 +Node Type: MeshVertexBitangentData + Bitangents: Count 4096. Hash: 16718347243549159919 + GenerationMethod: 1 + +Node Name: TangentSet_1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerBaseCuff.InnerBaseCuff_1.TangentSet_1 +Node Type: MeshVertexTangentData + Tangents: Count 4096. Hash: 7775661729866538946 + GenerationMethod: 1 + SetIndex: 1 + +Node Name: BitangentSet_1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerBaseCuff.InnerBaseCuff_1.BitangentSet_1 +Node Type: MeshVertexBitangentData + Bitangents: Count 4096. Hash: 16465645522600859391 + GenerationMethod: 1 + +Node Name: Tiled +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerBaseCuff.InnerBaseCuff_2.Tiled +Node Type: MeshVertexUVData + UVs: Count 4096. Hash: 12344372623177285558 + UVCustomName: Tiled + +Node Name: Unwrapped +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerBaseCuff.InnerBaseCuff_2.Unwrapped +Node Type: MeshVertexUVData + UVs: Count 4096. Hash: 3415525735687273566 + UVCustomName: Unwrapped + +Node Name: blinn1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerBaseCuff.InnerBaseCuff_2.blinn1 +Node Type: MaterialData + MaterialName: blinn1 + UniqueId: 2076548245838624187 + IsNoDraw: false + DiffuseColor: < 0.800000, 0.800000, 0.800000> + SpecularColor: < 0.500000, 0.500000, 0.500000> + EmissiveColor: < 0.000000, 0.000000, 0.000000> + Opacity: 1.000000 + Shininess: 6.311791 + UseColorMap: Not set + BaseColor: Not set + UseMetallicMap: Not set + MetallicFactor: Not set + UseRoughnessMap: Not set + RoughnessFactor: Not set + UseEmissiveMap: Not set + EmissiveIntensity: Not set + UseAOMap: Not set + DiffuseTexture: ShaderBall/_dev_shaderball_00_basecolor.png + SpecularTexture: + BumpTexture: + NormalTexture: + MetallicTexture: + RoughnessTexture: + AmbientOcclusionTexture: + EmissiveTexture: + BaseColorTexture: ShaderBall/_dev_shaderball_00_basecolor.png + +Node Name: Tiled +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerBaseCuff.InnerBaseCuff_1_optimized.Tiled +Node Type: MeshVertexUVData + UVs: Count 1122. Hash: 10794215007683911939 + UVCustomName: Tiled + +Node Name: Unwrapped +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerBaseCuff.InnerBaseCuff_1_optimized.Unwrapped +Node Type: MeshVertexUVData + UVs: Count 1122. Hash: 4030215540982392192 + UVCustomName: Unwrapped + +Node Name: TangentSet_0 +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerBaseCuff.InnerBaseCuff_1_optimized.TangentSet_0 +Node Type: MeshVertexTangentData + Tangents: Count 1122. Hash: 14289595630739500233 + GenerationMethod: 1 + SetIndex: 0 + +Node Name: TangentSet_1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerBaseCuff.InnerBaseCuff_1_optimized.TangentSet_1 +Node Type: MeshVertexTangentData + Tangents: Count 1122. Hash: 13010448485976282215 + GenerationMethod: 1 + SetIndex: 1 + +Node Name: BitangentSet_0 +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerBaseCuff.InnerBaseCuff_1_optimized.BitangentSet_0 +Node Type: MeshVertexBitangentData + Bitangents: Count 1122. Hash: 8318401526835048407 + GenerationMethod: 1 + +Node Name: BitangentSet_1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerBaseCuff.InnerBaseCuff_1_optimized.BitangentSet_1 +Node Type: MeshVertexBitangentData + Bitangents: Count 1122. Hash: 100095329364523248 + GenerationMethod: 1 + +Node Name: blinn1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerBaseCuff.InnerBaseCuff_1_optimized.blinn1 +Node Type: MaterialData + MaterialName: blinn1 + UniqueId: 2076548245838624187 + IsNoDraw: false + DiffuseColor: < 0.800000, 0.800000, 0.800000> + SpecularColor: < 0.500000, 0.500000, 0.500000> + EmissiveColor: < 0.000000, 0.000000, 0.000000> + Opacity: 1.000000 + Shininess: 6.311791 + UseColorMap: Not set + BaseColor: Not set + UseMetallicMap: Not set + MetallicFactor: Not set + UseRoughnessMap: Not set + RoughnessFactor: Not set + UseEmissiveMap: Not set + EmissiveIntensity: Not set + UseAOMap: Not set + DiffuseTexture: ShaderBall/_dev_shaderball_00_basecolor.png + SpecularTexture: + BumpTexture: + NormalTexture: + MetallicTexture: + RoughnessTexture: + AmbientOcclusionTexture: + EmissiveTexture: + BaseColorTexture: ShaderBall/_dev_shaderball_00_basecolor.png + +Node Name: Tiled +Node Path: RootNode.ShaderBall_1m.MaterialBase.Inset.Inset_1.Tiled +Node Type: MeshVertexUVData + UVs: Count 1536. Hash: 7123339701675171032 + UVCustomName: Tiled + +Node Name: Unwrapped +Node Path: RootNode.ShaderBall_1m.MaterialBase.Inset.Inset_1.Unwrapped +Node Type: MeshVertexUVData + UVs: Count 1536. Hash: 15827204344457762670 + UVCustomName: Unwrapped + +Node Name: blinn1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.Inset.Inset_1.blinn1 +Node Type: MaterialData + MaterialName: blinn1 + UniqueId: 2076548245838624187 + IsNoDraw: false + DiffuseColor: < 0.800000, 0.800000, 0.800000> + SpecularColor: < 0.500000, 0.500000, 0.500000> + EmissiveColor: < 0.000000, 0.000000, 0.000000> + Opacity: 1.000000 + Shininess: 6.311791 + UseColorMap: Not set + BaseColor: Not set + UseMetallicMap: Not set + MetallicFactor: Not set + UseRoughnessMap: Not set + RoughnessFactor: Not set + UseEmissiveMap: Not set + EmissiveIntensity: Not set + UseAOMap: Not set + DiffuseTexture: ShaderBall/_dev_shaderball_00_basecolor.png + SpecularTexture: + BumpTexture: + NormalTexture: + MetallicTexture: + RoughnessTexture: + AmbientOcclusionTexture: + EmissiveTexture: + BaseColorTexture: ShaderBall/_dev_shaderball_00_basecolor.png + +Node Name: TangentSet_0 +Node Path: RootNode.ShaderBall_1m.MaterialBase.Inset.Inset_1.TangentSet_0 +Node Type: MeshVertexTangentData + Tangents: Count 1536. Hash: 15732245908985477324 + GenerationMethod: 1 + SetIndex: 0 + +Node Name: BitangentSet_0 +Node Path: RootNode.ShaderBall_1m.MaterialBase.Inset.Inset_1.BitangentSet_0 +Node Type: MeshVertexBitangentData + Bitangents: Count 1536. Hash: 865397990859725823 + GenerationMethod: 1 + +Node Name: TangentSet_1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.Inset.Inset_1.TangentSet_1 +Node Type: MeshVertexTangentData + Tangents: Count 1536. Hash: 2552281640891423471 + GenerationMethod: 1 + SetIndex: 1 + +Node Name: BitangentSet_1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.Inset.Inset_1.BitangentSet_1 +Node Type: MeshVertexBitangentData + Bitangents: Count 1536. Hash: 17779645716491562667 + GenerationMethod: 1 + +Node Name: Tiled +Node Path: RootNode.ShaderBall_1m.MaterialBase.Inset.Inset_2.Tiled +Node Type: MeshVertexUVData + UVs: Count 1536. Hash: 7123339701675171032 + UVCustomName: Tiled + +Node Name: Unwrapped +Node Path: RootNode.ShaderBall_1m.MaterialBase.Inset.Inset_2.Unwrapped +Node Type: MeshVertexUVData + UVs: Count 1536. Hash: 15827204344457762670 + UVCustomName: Unwrapped + +Node Name: blinn1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.Inset.Inset_2.blinn1 +Node Type: MaterialData + MaterialName: blinn1 + UniqueId: 2076548245838624187 + IsNoDraw: false + DiffuseColor: < 0.800000, 0.800000, 0.800000> + SpecularColor: < 0.500000, 0.500000, 0.500000> + EmissiveColor: < 0.000000, 0.000000, 0.000000> + Opacity: 1.000000 + Shininess: 6.311791 + UseColorMap: Not set + BaseColor: Not set + UseMetallicMap: Not set + MetallicFactor: Not set + UseRoughnessMap: Not set + RoughnessFactor: Not set + UseEmissiveMap: Not set + EmissiveIntensity: Not set + UseAOMap: Not set + DiffuseTexture: ShaderBall/_dev_shaderball_00_basecolor.png + SpecularTexture: + BumpTexture: + NormalTexture: + MetallicTexture: + RoughnessTexture: + AmbientOcclusionTexture: + EmissiveTexture: + BaseColorTexture: ShaderBall/_dev_shaderball_00_basecolor.png + +Node Name: Tiled +Node Path: RootNode.ShaderBall_1m.MaterialBase.Inset.Inset_1_optimized.Tiled +Node Type: MeshVertexUVData + UVs: Count 448. Hash: 11533511688039530581 + UVCustomName: Tiled + +Node Name: Unwrapped +Node Path: RootNode.ShaderBall_1m.MaterialBase.Inset.Inset_1_optimized.Unwrapped +Node Type: MeshVertexUVData + UVs: Count 448. Hash: 6463578982826635244 + UVCustomName: Unwrapped + +Node Name: TangentSet_0 +Node Path: RootNode.ShaderBall_1m.MaterialBase.Inset.Inset_1_optimized.TangentSet_0 +Node Type: MeshVertexTangentData + Tangents: Count 448. Hash: 10590255987412720146 + GenerationMethod: 1 + SetIndex: 0 + +Node Name: TangentSet_1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.Inset.Inset_1_optimized.TangentSet_1 +Node Type: MeshVertexTangentData + Tangents: Count 448. Hash: 17744412650281038495 + GenerationMethod: 1 + SetIndex: 1 + +Node Name: BitangentSet_0 +Node Path: RootNode.ShaderBall_1m.MaterialBase.Inset.Inset_1_optimized.BitangentSet_0 +Node Type: MeshVertexBitangentData + Bitangents: Count 448. Hash: 8278337182397882868 + GenerationMethod: 1 + +Node Name: BitangentSet_1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.Inset.Inset_1_optimized.BitangentSet_1 +Node Type: MeshVertexBitangentData + Bitangents: Count 448. Hash: 8818080862566813062 + GenerationMethod: 1 + +Node Name: blinn1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.Inset.Inset_1_optimized.blinn1 +Node Type: MaterialData + MaterialName: blinn1 + UniqueId: 2076548245838624187 + IsNoDraw: false + DiffuseColor: < 0.800000, 0.800000, 0.800000> + SpecularColor: < 0.500000, 0.500000, 0.500000> + EmissiveColor: < 0.000000, 0.000000, 0.000000> + Opacity: 1.000000 + Shininess: 6.311791 + UseColorMap: Not set + BaseColor: Not set + UseMetallicMap: Not set + MetallicFactor: Not set + UseRoughnessMap: Not set + RoughnessFactor: Not set + UseEmissiveMap: Not set + EmissiveIntensity: Not set + UseAOMap: Not set + DiffuseTexture: ShaderBall/_dev_shaderball_00_basecolor.png + SpecularTexture: + BumpTexture: + NormalTexture: + MetallicTexture: + RoughnessTexture: + AmbientOcclusionTexture: + EmissiveTexture: + BaseColorTexture: ShaderBall/_dev_shaderball_00_basecolor.png + +Node Name: Tiled +Node Path: RootNode.ShaderBall_1m.MaterialBase.BottomCap.BottomCap_1.Tiled +Node Type: MeshVertexUVData + UVs: Count 960. Hash: 12059380739436291361 + UVCustomName: Tiled + +Node Name: Unwrapped +Node Path: RootNode.ShaderBall_1m.MaterialBase.BottomCap.BottomCap_1.Unwrapped +Node Type: MeshVertexUVData + UVs: Count 960. Hash: 17894062399627363441 + UVCustomName: Unwrapped + +Node Name: blinn1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.BottomCap.BottomCap_1.blinn1 +Node Type: MaterialData + MaterialName: blinn1 + UniqueId: 2076548245838624187 + IsNoDraw: false + DiffuseColor: < 0.800000, 0.800000, 0.800000> + SpecularColor: < 0.500000, 0.500000, 0.500000> + EmissiveColor: < 0.000000, 0.000000, 0.000000> + Opacity: 1.000000 + Shininess: 6.311791 + UseColorMap: Not set + BaseColor: Not set + UseMetallicMap: Not set + MetallicFactor: Not set + UseRoughnessMap: Not set + RoughnessFactor: Not set + UseEmissiveMap: Not set + EmissiveIntensity: Not set + UseAOMap: Not set + DiffuseTexture: ShaderBall/_dev_shaderball_00_basecolor.png + SpecularTexture: + BumpTexture: + NormalTexture: + MetallicTexture: + RoughnessTexture: + AmbientOcclusionTexture: + EmissiveTexture: + BaseColorTexture: ShaderBall/_dev_shaderball_00_basecolor.png + +Node Name: TangentSet_0 +Node Path: RootNode.ShaderBall_1m.MaterialBase.BottomCap.BottomCap_1.TangentSet_0 +Node Type: MeshVertexTangentData + Tangents: Count 960. Hash: 3025207993250150716 + GenerationMethod: 1 + SetIndex: 0 + +Node Name: BitangentSet_0 +Node Path: RootNode.ShaderBall_1m.MaterialBase.BottomCap.BottomCap_1.BitangentSet_0 +Node Type: MeshVertexBitangentData + Bitangents: Count 960. Hash: 909667023812047269 + GenerationMethod: 1 + +Node Name: TangentSet_1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.BottomCap.BottomCap_1.TangentSet_1 +Node Type: MeshVertexTangentData + Tangents: Count 960. Hash: 9807712812840041710 + GenerationMethod: 1 + SetIndex: 1 + +Node Name: BitangentSet_1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.BottomCap.BottomCap_1.BitangentSet_1 +Node Type: MeshVertexBitangentData + Bitangents: Count 960. Hash: 15826838159231044203 + GenerationMethod: 1 + +Node Name: Tiled +Node Path: RootNode.ShaderBall_1m.MaterialBase.BottomCap.BottomCap_2.Tiled +Node Type: MeshVertexUVData + UVs: Count 960. Hash: 12059380739436291361 + UVCustomName: Tiled + +Node Name: Unwrapped +Node Path: RootNode.ShaderBall_1m.MaterialBase.BottomCap.BottomCap_2.Unwrapped +Node Type: MeshVertexUVData + UVs: Count 960. Hash: 17894062399627363441 + UVCustomName: Unwrapped + +Node Name: blinn1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.BottomCap.BottomCap_2.blinn1 +Node Type: MaterialData + MaterialName: blinn1 + UniqueId: 2076548245838624187 + IsNoDraw: false + DiffuseColor: < 0.800000, 0.800000, 0.800000> + SpecularColor: < 0.500000, 0.500000, 0.500000> + EmissiveColor: < 0.000000, 0.000000, 0.000000> + Opacity: 1.000000 + Shininess: 6.311791 + UseColorMap: Not set + BaseColor: Not set + UseMetallicMap: Not set + MetallicFactor: Not set + UseRoughnessMap: Not set + RoughnessFactor: Not set + UseEmissiveMap: Not set + EmissiveIntensity: Not set + UseAOMap: Not set + DiffuseTexture: ShaderBall/_dev_shaderball_00_basecolor.png + SpecularTexture: + BumpTexture: + NormalTexture: + MetallicTexture: + RoughnessTexture: + AmbientOcclusionTexture: + EmissiveTexture: + BaseColorTexture: ShaderBall/_dev_shaderball_00_basecolor.png + +Node Name: Tiled +Node Path: RootNode.ShaderBall_1m.MaterialBase.BottomCap.BottomCap_1_optimized.Tiled +Node Type: MeshVertexUVData + UVs: Count 257. Hash: 8823641736072761245 + UVCustomName: Tiled + +Node Name: Unwrapped +Node Path: RootNode.ShaderBall_1m.MaterialBase.BottomCap.BottomCap_1_optimized.Unwrapped +Node Type: MeshVertexUVData + UVs: Count 257. Hash: 17988941723121644388 + UVCustomName: Unwrapped + +Node Name: TangentSet_0 +Node Path: RootNode.ShaderBall_1m.MaterialBase.BottomCap.BottomCap_1_optimized.TangentSet_0 +Node Type: MeshVertexTangentData + Tangents: Count 257. Hash: 8180735114915510878 + GenerationMethod: 1 + SetIndex: 0 + +Node Name: TangentSet_1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.BottomCap.BottomCap_1_optimized.TangentSet_1 +Node Type: MeshVertexTangentData + Tangents: Count 257. Hash: 6608315278879931556 + GenerationMethod: 1 + SetIndex: 1 + +Node Name: BitangentSet_0 +Node Path: RootNode.ShaderBall_1m.MaterialBase.BottomCap.BottomCap_1_optimized.BitangentSet_0 +Node Type: MeshVertexBitangentData + Bitangents: Count 257. Hash: 15606348252975307518 + GenerationMethod: 1 + +Node Name: BitangentSet_1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.BottomCap.BottomCap_1_optimized.BitangentSet_1 +Node Type: MeshVertexBitangentData + Bitangents: Count 257. Hash: 9909736394462106525 + GenerationMethod: 1 + +Node Name: blinn1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.BottomCap.BottomCap_1_optimized.blinn1 +Node Type: MaterialData + MaterialName: blinn1 + UniqueId: 2076548245838624187 + IsNoDraw: false + DiffuseColor: < 0.800000, 0.800000, 0.800000> + SpecularColor: < 0.500000, 0.500000, 0.500000> + EmissiveColor: < 0.000000, 0.000000, 0.000000> + Opacity: 1.000000 + Shininess: 6.311791 + UseColorMap: Not set + BaseColor: Not set + UseMetallicMap: Not set + MetallicFactor: Not set + UseRoughnessMap: Not set + RoughnessFactor: Not set + UseEmissiveMap: Not set + EmissiveIntensity: Not set + UseAOMap: Not set + DiffuseTexture: ShaderBall/_dev_shaderball_00_basecolor.png + SpecularTexture: + BumpTexture: + NormalTexture: + MetallicTexture: + RoughnessTexture: + AmbientOcclusionTexture: + EmissiveTexture: + BaseColorTexture: ShaderBall/_dev_shaderball_00_basecolor.png + +Node Name: Tiled +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerCushion.InnerCushion_1.Tiled +Node Type: MeshVertexUVData + UVs: Count 54208. Hash: 3444203649101035485 + UVCustomName: Tiled + +Node Name: Unwrapped +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerCushion.InnerCushion_1.Unwrapped +Node Type: MeshVertexUVData + UVs: Count 54208. Hash: 937532470362399061 + UVCustomName: Unwrapped + +Node Name: blinn1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerCushion.InnerCushion_1.blinn1 +Node Type: MaterialData + MaterialName: blinn1 + UniqueId: 2076548245838624187 + IsNoDraw: false + DiffuseColor: < 0.800000, 0.800000, 0.800000> + SpecularColor: < 0.500000, 0.500000, 0.500000> + EmissiveColor: < 0.000000, 0.000000, 0.000000> + Opacity: 1.000000 + Shininess: 6.311791 + UseColorMap: Not set + BaseColor: Not set + UseMetallicMap: Not set + MetallicFactor: Not set + UseRoughnessMap: Not set + RoughnessFactor: Not set + UseEmissiveMap: Not set + EmissiveIntensity: Not set + UseAOMap: Not set + DiffuseTexture: ShaderBall/_dev_shaderball_00_basecolor.png + SpecularTexture: + BumpTexture: + NormalTexture: + MetallicTexture: + RoughnessTexture: + AmbientOcclusionTexture: + EmissiveTexture: + BaseColorTexture: ShaderBall/_dev_shaderball_00_basecolor.png + +Node Name: TangentSet_0 +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerCushion.InnerCushion_1.TangentSet_0 +Node Type: MeshVertexTangentData + Tangents: Count 54208. Hash: 196567085853229565 + GenerationMethod: 1 + SetIndex: 0 + +Node Name: BitangentSet_0 +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerCushion.InnerCushion_1.BitangentSet_0 +Node Type: MeshVertexBitangentData + Bitangents: Count 54208. Hash: 1716600532837684655 + GenerationMethod: 1 + +Node Name: TangentSet_1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerCushion.InnerCushion_1.TangentSet_1 +Node Type: MeshVertexTangentData + Tangents: Count 54208. Hash: 2905235470236164097 + GenerationMethod: 1 + SetIndex: 1 + +Node Name: BitangentSet_1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerCushion.InnerCushion_1.BitangentSet_1 +Node Type: MeshVertexBitangentData + Bitangents: Count 54208. Hash: 2074363216216487237 + GenerationMethod: 1 + +Node Name: Tiled +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerCushion.InnerCushion_2.Tiled +Node Type: MeshVertexUVData + UVs: Count 54208. Hash: 3444203649101035485 + UVCustomName: Tiled + +Node Name: Unwrapped +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerCushion.InnerCushion_2.Unwrapped +Node Type: MeshVertexUVData + UVs: Count 54208. Hash: 937532470362399061 + UVCustomName: Unwrapped + +Node Name: blinn1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerCushion.InnerCushion_2.blinn1 +Node Type: MaterialData + MaterialName: blinn1 + UniqueId: 2076548245838624187 + IsNoDraw: false + DiffuseColor: < 0.800000, 0.800000, 0.800000> + SpecularColor: < 0.500000, 0.500000, 0.500000> + EmissiveColor: < 0.000000, 0.000000, 0.000000> + Opacity: 1.000000 + Shininess: 6.311791 + UseColorMap: Not set + BaseColor: Not set + UseMetallicMap: Not set + MetallicFactor: Not set + UseRoughnessMap: Not set + RoughnessFactor: Not set + UseEmissiveMap: Not set + EmissiveIntensity: Not set + UseAOMap: Not set + DiffuseTexture: ShaderBall/_dev_shaderball_00_basecolor.png + SpecularTexture: + BumpTexture: + NormalTexture: + MetallicTexture: + RoughnessTexture: + AmbientOcclusionTexture: + EmissiveTexture: + BaseColorTexture: ShaderBall/_dev_shaderball_00_basecolor.png + +Node Name: Tiled +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerCushion.InnerCushion_1_optimized.Tiled +Node Type: MeshVertexUVData + UVs: Count 13860. Hash: 263381874971540959 + UVCustomName: Tiled + +Node Name: Unwrapped +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerCushion.InnerCushion_1_optimized.Unwrapped +Node Type: MeshVertexUVData + UVs: Count 13860. Hash: 15191673020616208011 + UVCustomName: Unwrapped + +Node Name: TangentSet_0 +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerCushion.InnerCushion_1_optimized.TangentSet_0 +Node Type: MeshVertexTangentData + Tangents: Count 13860. Hash: 7105458185902008309 + GenerationMethod: 1 + SetIndex: 0 + +Node Name: TangentSet_1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerCushion.InnerCushion_1_optimized.TangentSet_1 +Node Type: MeshVertexTangentData + Tangents: Count 13860. Hash: 16259705089531364237 + GenerationMethod: 1 + SetIndex: 1 + +Node Name: BitangentSet_0 +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerCushion.InnerCushion_1_optimized.BitangentSet_0 +Node Type: MeshVertexBitangentData + Bitangents: Count 13860. Hash: 16022317673583270139 + GenerationMethod: 1 + +Node Name: BitangentSet_1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerCushion.InnerCushion_1_optimized.BitangentSet_1 +Node Type: MeshVertexBitangentData + Bitangents: Count 13860. Hash: 7251784463761381809 + GenerationMethod: 1 + +Node Name: blinn1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerCushion.InnerCushion_1_optimized.blinn1 +Node Type: MaterialData + MaterialName: blinn1 + UniqueId: 2076548245838624187 + IsNoDraw: false + DiffuseColor: < 0.800000, 0.800000, 0.800000> + SpecularColor: < 0.500000, 0.500000, 0.500000> + EmissiveColor: < 0.000000, 0.000000, 0.000000> + Opacity: 1.000000 + Shininess: 6.311791 + UseColorMap: Not set + BaseColor: Not set + UseMetallicMap: Not set + MetallicFactor: Not set + UseRoughnessMap: Not set + RoughnessFactor: Not set + UseEmissiveMap: Not set + EmissiveIntensity: Not set + UseAOMap: Not set + DiffuseTexture: ShaderBall/_dev_shaderball_00_basecolor.png + SpecularTexture: + BumpTexture: + NormalTexture: + MetallicTexture: + RoughnessTexture: + AmbientOcclusionTexture: + EmissiveTexture: + BaseColorTexture: ShaderBall/_dev_shaderball_00_basecolor.png + +Node Name: Tiled +Node Path: RootNode.ShaderBall_1m.MaterialBase.OuterBaseCuff.OuterBaseCuff_1.Tiled +Node Type: MeshVertexUVData + UVs: Count 20048. Hash: 10464397683020008867 + UVCustomName: Tiled + +Node Name: Unwrapped +Node Path: RootNode.ShaderBall_1m.MaterialBase.OuterBaseCuff.OuterBaseCuff_1.Unwrapped +Node Type: MeshVertexUVData + UVs: Count 20048. Hash: 11067273583889078226 + UVCustomName: Unwrapped + +Node Name: blinn1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.OuterBaseCuff.OuterBaseCuff_1.blinn1 +Node Type: MaterialData + MaterialName: blinn1 + UniqueId: 2076548245838624187 + IsNoDraw: false + DiffuseColor: < 0.800000, 0.800000, 0.800000> + SpecularColor: < 0.500000, 0.500000, 0.500000> + EmissiveColor: < 0.000000, 0.000000, 0.000000> + Opacity: 1.000000 + Shininess: 6.311791 + UseColorMap: Not set + BaseColor: Not set + UseMetallicMap: Not set + MetallicFactor: Not set + UseRoughnessMap: Not set + RoughnessFactor: Not set + UseEmissiveMap: Not set + EmissiveIntensity: Not set + UseAOMap: Not set + DiffuseTexture: ShaderBall/_dev_shaderball_00_basecolor.png + SpecularTexture: + BumpTexture: + NormalTexture: + MetallicTexture: + RoughnessTexture: + AmbientOcclusionTexture: + EmissiveTexture: + BaseColorTexture: ShaderBall/_dev_shaderball_00_basecolor.png + +Node Name: TangentSet_0 +Node Path: RootNode.ShaderBall_1m.MaterialBase.OuterBaseCuff.OuterBaseCuff_1.TangentSet_0 +Node Type: MeshVertexTangentData + Tangents: Count 20048. Hash: 15901168792190323178 + GenerationMethod: 1 + SetIndex: 0 + +Node Name: BitangentSet_0 +Node Path: RootNode.ShaderBall_1m.MaterialBase.OuterBaseCuff.OuterBaseCuff_1.BitangentSet_0 +Node Type: MeshVertexBitangentData + Bitangents: Count 20048. Hash: 552570814640404138 + GenerationMethod: 1 + +Node Name: TangentSet_1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.OuterBaseCuff.OuterBaseCuff_1.TangentSet_1 +Node Type: MeshVertexTangentData + Tangents: Count 20048. Hash: 17726428588184726937 + GenerationMethod: 1 + SetIndex: 1 + +Node Name: BitangentSet_1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.OuterBaseCuff.OuterBaseCuff_1.BitangentSet_1 +Node Type: MeshVertexBitangentData + Bitangents: Count 20048. Hash: 7508591493894188819 + GenerationMethod: 1 + +Node Name: Tiled +Node Path: RootNode.ShaderBall_1m.MaterialBase.OuterBaseCuff.OuterBaseCuff_2.Tiled +Node Type: MeshVertexUVData + UVs: Count 20048. Hash: 10464397683020008867 + UVCustomName: Tiled + +Node Name: Unwrapped +Node Path: RootNode.ShaderBall_1m.MaterialBase.OuterBaseCuff.OuterBaseCuff_2.Unwrapped +Node Type: MeshVertexUVData + UVs: Count 20048. Hash: 11067273583889078226 + UVCustomName: Unwrapped + +Node Name: blinn1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.OuterBaseCuff.OuterBaseCuff_2.blinn1 +Node Type: MaterialData + MaterialName: blinn1 + UniqueId: 2076548245838624187 + IsNoDraw: false + DiffuseColor: < 0.800000, 0.800000, 0.800000> + SpecularColor: < 0.500000, 0.500000, 0.500000> + EmissiveColor: < 0.000000, 0.000000, 0.000000> + Opacity: 1.000000 + Shininess: 6.311791 + UseColorMap: Not set + BaseColor: Not set + UseMetallicMap: Not set + MetallicFactor: Not set + UseRoughnessMap: Not set + RoughnessFactor: Not set + UseEmissiveMap: Not set + EmissiveIntensity: Not set + UseAOMap: Not set + DiffuseTexture: ShaderBall/_dev_shaderball_00_basecolor.png + SpecularTexture: + BumpTexture: + NormalTexture: + MetallicTexture: + RoughnessTexture: + AmbientOcclusionTexture: + EmissiveTexture: + BaseColorTexture: ShaderBall/_dev_shaderball_00_basecolor.png + +Node Name: Tiled +Node Path: RootNode.ShaderBall_1m.MaterialBase.OuterBaseCuff.OuterBaseCuff_1_optimized.Tiled +Node Type: MeshVertexUVData + UVs: Count 5206. Hash: 1074257132915638034 + UVCustomName: Tiled + +Node Name: Unwrapped +Node Path: RootNode.ShaderBall_1m.MaterialBase.OuterBaseCuff.OuterBaseCuff_1_optimized.Unwrapped +Node Type: MeshVertexUVData + UVs: Count 5206. Hash: 2243653809093009497 + UVCustomName: Unwrapped + +Node Name: TangentSet_0 +Node Path: RootNode.ShaderBall_1m.MaterialBase.OuterBaseCuff.OuterBaseCuff_1_optimized.TangentSet_0 +Node Type: MeshVertexTangentData + Tangents: Count 5206. Hash: 11559777087289074275 + GenerationMethod: 1 + SetIndex: 0 + +Node Name: TangentSet_1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.OuterBaseCuff.OuterBaseCuff_1_optimized.TangentSet_1 +Node Type: MeshVertexTangentData + Tangents: Count 5206. Hash: 15450183130901763011 + GenerationMethod: 1 + SetIndex: 1 + +Node Name: BitangentSet_0 +Node Path: RootNode.ShaderBall_1m.MaterialBase.OuterBaseCuff.OuterBaseCuff_1_optimized.BitangentSet_0 +Node Type: MeshVertexBitangentData + Bitangents: Count 5206. Hash: 15359221084106995089 + GenerationMethod: 1 + +Node Name: BitangentSet_1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.OuterBaseCuff.OuterBaseCuff_1_optimized.BitangentSet_1 +Node Type: MeshVertexBitangentData + Bitangents: Count 5206. Hash: 8652443944286435468 + GenerationMethod: 1 + +Node Name: blinn1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.OuterBaseCuff.OuterBaseCuff_1_optimized.blinn1 +Node Type: MaterialData + MaterialName: blinn1 + UniqueId: 2076548245838624187 + IsNoDraw: false + DiffuseColor: < 0.800000, 0.800000, 0.800000> + SpecularColor: < 0.500000, 0.500000, 0.500000> + EmissiveColor: < 0.000000, 0.000000, 0.000000> + Opacity: 1.000000 + Shininess: 6.311791 + UseColorMap: Not set + BaseColor: Not set + UseMetallicMap: Not set + MetallicFactor: Not set + UseRoughnessMap: Not set + RoughnessFactor: Not set + UseEmissiveMap: Not set + EmissiveIntensity: Not set + UseAOMap: Not set + DiffuseTexture: ShaderBall/_dev_shaderball_00_basecolor.png + SpecularTexture: + BumpTexture: + NormalTexture: + MetallicTexture: + RoughnessTexture: + AmbientOcclusionTexture: + EmissiveTexture: + BaseColorTexture: ShaderBall/_dev_shaderball_00_basecolor.png + +Node Name: Tiled +Node Path: RootNode.ShaderBall_1m.InlayRings.RingLeft.RingLeft_1.Tiled +Node Type: MeshVertexUVData + UVs: Count 2560. Hash: 14931201357194905697 + UVCustomName: Tiled + +Node Name: Unwrapped +Node Path: RootNode.ShaderBall_1m.InlayRings.RingLeft.RingLeft_1.Unwrapped +Node Type: MeshVertexUVData + UVs: Count 2560. Hash: 5600314145323623005 + UVCustomName: Unwrapped + +Node Name: blinn1 +Node Path: RootNode.ShaderBall_1m.InlayRings.RingLeft.RingLeft_1.blinn1 +Node Type: MaterialData + MaterialName: blinn1 + UniqueId: 2076548245838624187 + IsNoDraw: false + DiffuseColor: < 0.800000, 0.800000, 0.800000> + SpecularColor: < 0.500000, 0.500000, 0.500000> + EmissiveColor: < 0.000000, 0.000000, 0.000000> + Opacity: 1.000000 + Shininess: 6.311791 + UseColorMap: Not set + BaseColor: Not set + UseMetallicMap: Not set + MetallicFactor: Not set + UseRoughnessMap: Not set + RoughnessFactor: Not set + UseEmissiveMap: Not set + EmissiveIntensity: Not set + UseAOMap: Not set + DiffuseTexture: ShaderBall/_dev_shaderball_00_basecolor.png + SpecularTexture: + BumpTexture: + NormalTexture: + MetallicTexture: + RoughnessTexture: + AmbientOcclusionTexture: + EmissiveTexture: + BaseColorTexture: ShaderBall/_dev_shaderball_00_basecolor.png + +Node Name: TangentSet_0 +Node Path: RootNode.ShaderBall_1m.InlayRings.RingLeft.RingLeft_1.TangentSet_0 +Node Type: MeshVertexTangentData + Tangents: Count 2560. Hash: 11738661055172304644 + GenerationMethod: 1 + SetIndex: 0 + +Node Name: BitangentSet_0 +Node Path: RootNode.ShaderBall_1m.InlayRings.RingLeft.RingLeft_1.BitangentSet_0 +Node Type: MeshVertexBitangentData + Bitangents: Count 2560. Hash: 320620113692599118 + GenerationMethod: 1 + +Node Name: TangentSet_1 +Node Path: RootNode.ShaderBall_1m.InlayRings.RingLeft.RingLeft_1.TangentSet_1 +Node Type: MeshVertexTangentData + Tangents: Count 2560. Hash: 8466587534284734762 + GenerationMethod: 1 + SetIndex: 1 + +Node Name: BitangentSet_1 +Node Path: RootNode.ShaderBall_1m.InlayRings.RingLeft.RingLeft_1.BitangentSet_1 +Node Type: MeshVertexBitangentData + Bitangents: Count 2560. Hash: 3318206549561696188 + GenerationMethod: 1 + +Node Name: Tiled +Node Path: RootNode.ShaderBall_1m.InlayRings.RingLeft.RingLeft_2.Tiled +Node Type: MeshVertexUVData + UVs: Count 2560. Hash: 14931201357194905697 + UVCustomName: Tiled + +Node Name: Unwrapped +Node Path: RootNode.ShaderBall_1m.InlayRings.RingLeft.RingLeft_2.Unwrapped +Node Type: MeshVertexUVData + UVs: Count 2560. Hash: 5600314145323623005 + UVCustomName: Unwrapped + +Node Name: blinn1 +Node Path: RootNode.ShaderBall_1m.InlayRings.RingLeft.RingLeft_2.blinn1 +Node Type: MaterialData + MaterialName: blinn1 + UniqueId: 2076548245838624187 + IsNoDraw: false + DiffuseColor: < 0.800000, 0.800000, 0.800000> + SpecularColor: < 0.500000, 0.500000, 0.500000> + EmissiveColor: < 0.000000, 0.000000, 0.000000> + Opacity: 1.000000 + Shininess: 6.311791 + UseColorMap: Not set + BaseColor: Not set + UseMetallicMap: Not set + MetallicFactor: Not set + UseRoughnessMap: Not set + RoughnessFactor: Not set + UseEmissiveMap: Not set + EmissiveIntensity: Not set + UseAOMap: Not set + DiffuseTexture: ShaderBall/_dev_shaderball_00_basecolor.png + SpecularTexture: + BumpTexture: + NormalTexture: + MetallicTexture: + RoughnessTexture: + AmbientOcclusionTexture: + EmissiveTexture: + BaseColorTexture: ShaderBall/_dev_shaderball_00_basecolor.png + +Node Name: Tiled +Node Path: RootNode.ShaderBall_1m.InlayRings.RingLeft.RingLeft_1_optimized.Tiled +Node Type: MeshVertexUVData + UVs: Count 720. Hash: 75483450873662317 + UVCustomName: Tiled + +Node Name: Unwrapped +Node Path: RootNode.ShaderBall_1m.InlayRings.RingLeft.RingLeft_1_optimized.Unwrapped +Node Type: MeshVertexUVData + UVs: Count 720. Hash: 3793407172213641704 + UVCustomName: Unwrapped + +Node Name: TangentSet_0 +Node Path: RootNode.ShaderBall_1m.InlayRings.RingLeft.RingLeft_1_optimized.TangentSet_0 +Node Type: MeshVertexTangentData + Tangents: Count 720. Hash: 2508676912793167321 + GenerationMethod: 1 + SetIndex: 0 + +Node Name: TangentSet_1 +Node Path: RootNode.ShaderBall_1m.InlayRings.RingLeft.RingLeft_1_optimized.TangentSet_1 +Node Type: MeshVertexTangentData + Tangents: Count 720. Hash: 2013136453053212946 + GenerationMethod: 1 + SetIndex: 1 + +Node Name: BitangentSet_0 +Node Path: RootNode.ShaderBall_1m.InlayRings.RingLeft.RingLeft_1_optimized.BitangentSet_0 +Node Type: MeshVertexBitangentData + Bitangents: Count 720. Hash: 9302779689053257196 + GenerationMethod: 1 + +Node Name: BitangentSet_1 +Node Path: RootNode.ShaderBall_1m.InlayRings.RingLeft.RingLeft_1_optimized.BitangentSet_1 +Node Type: MeshVertexBitangentData + Bitangents: Count 720. Hash: 16922723248982534245 + GenerationMethod: 1 + +Node Name: blinn1 +Node Path: RootNode.ShaderBall_1m.InlayRings.RingLeft.RingLeft_1_optimized.blinn1 +Node Type: MaterialData + MaterialName: blinn1 + UniqueId: 2076548245838624187 + IsNoDraw: false + DiffuseColor: < 0.800000, 0.800000, 0.800000> + SpecularColor: < 0.500000, 0.500000, 0.500000> + EmissiveColor: < 0.000000, 0.000000, 0.000000> + Opacity: 1.000000 + Shininess: 6.311791 + UseColorMap: Not set + BaseColor: Not set + UseMetallicMap: Not set + MetallicFactor: Not set + UseRoughnessMap: Not set + RoughnessFactor: Not set + UseEmissiveMap: Not set + EmissiveIntensity: Not set + UseAOMap: Not set + DiffuseTexture: ShaderBall/_dev_shaderball_00_basecolor.png + SpecularTexture: + BumpTexture: + NormalTexture: + MetallicTexture: + RoughnessTexture: + AmbientOcclusionTexture: + EmissiveTexture: + BaseColorTexture: ShaderBall/_dev_shaderball_00_basecolor.png + +Node Name: Tiled +Node Path: RootNode.ShaderBall_1m.InlayRings.RingRight.RingRight_1.Tiled +Node Type: MeshVertexUVData + UVs: Count 2560. Hash: 9568588494434360329 + UVCustomName: Tiled + +Node Name: Unwrapped +Node Path: RootNode.ShaderBall_1m.InlayRings.RingRight.RingRight_1.Unwrapped +Node Type: MeshVertexUVData + UVs: Count 2560. Hash: 5573555818259644549 + UVCustomName: Unwrapped + +Node Name: blinn1 +Node Path: RootNode.ShaderBall_1m.InlayRings.RingRight.RingRight_1.blinn1 +Node Type: MaterialData + MaterialName: blinn1 + UniqueId: 2076548245838624187 + IsNoDraw: false + DiffuseColor: < 0.800000, 0.800000, 0.800000> + SpecularColor: < 0.500000, 0.500000, 0.500000> + EmissiveColor: < 0.000000, 0.000000, 0.000000> + Opacity: 1.000000 + Shininess: 6.311791 + UseColorMap: Not set + BaseColor: Not set + UseMetallicMap: Not set + MetallicFactor: Not set + UseRoughnessMap: Not set + RoughnessFactor: Not set + UseEmissiveMap: Not set + EmissiveIntensity: Not set + UseAOMap: Not set + DiffuseTexture: ShaderBall/_dev_shaderball_00_basecolor.png + SpecularTexture: + BumpTexture: + NormalTexture: + MetallicTexture: + RoughnessTexture: + AmbientOcclusionTexture: + EmissiveTexture: + BaseColorTexture: ShaderBall/_dev_shaderball_00_basecolor.png + +Node Name: TangentSet_0 +Node Path: RootNode.ShaderBall_1m.InlayRings.RingRight.RingRight_1.TangentSet_0 +Node Type: MeshVertexTangentData + Tangents: Count 2560. Hash: 12082144485035076372 + GenerationMethod: 1 + SetIndex: 0 + +Node Name: BitangentSet_0 +Node Path: RootNode.ShaderBall_1m.InlayRings.RingRight.RingRight_1.BitangentSet_0 +Node Type: MeshVertexBitangentData + Bitangents: Count 2560. Hash: 5616878210949329467 + GenerationMethod: 1 + +Node Name: TangentSet_1 +Node Path: RootNode.ShaderBall_1m.InlayRings.RingRight.RingRight_1.TangentSet_1 +Node Type: MeshVertexTangentData + Tangents: Count 2560. Hash: 4057154333260499171 + GenerationMethod: 1 + SetIndex: 1 + +Node Name: BitangentSet_1 +Node Path: RootNode.ShaderBall_1m.InlayRings.RingRight.RingRight_1.BitangentSet_1 +Node Type: MeshVertexBitangentData + Bitangents: Count 2560. Hash: 1620460765416970456 + GenerationMethod: 1 + +Node Name: Tiled +Node Path: RootNode.ShaderBall_1m.InlayRings.RingRight.RingRight_2.Tiled +Node Type: MeshVertexUVData + UVs: Count 2560. Hash: 9568588494434360329 + UVCustomName: Tiled + +Node Name: Unwrapped +Node Path: RootNode.ShaderBall_1m.InlayRings.RingRight.RingRight_2.Unwrapped +Node Type: MeshVertexUVData + UVs: Count 2560. Hash: 5573555818259644549 + UVCustomName: Unwrapped + +Node Name: blinn1 +Node Path: RootNode.ShaderBall_1m.InlayRings.RingRight.RingRight_2.blinn1 +Node Type: MaterialData + MaterialName: blinn1 + UniqueId: 2076548245838624187 + IsNoDraw: false + DiffuseColor: < 0.800000, 0.800000, 0.800000> + SpecularColor: < 0.500000, 0.500000, 0.500000> + EmissiveColor: < 0.000000, 0.000000, 0.000000> + Opacity: 1.000000 + Shininess: 6.311791 + UseColorMap: Not set + BaseColor: Not set + UseMetallicMap: Not set + MetallicFactor: Not set + UseRoughnessMap: Not set + RoughnessFactor: Not set + UseEmissiveMap: Not set + EmissiveIntensity: Not set + UseAOMap: Not set + DiffuseTexture: ShaderBall/_dev_shaderball_00_basecolor.png + SpecularTexture: + BumpTexture: + NormalTexture: + MetallicTexture: + RoughnessTexture: + AmbientOcclusionTexture: + EmissiveTexture: + BaseColorTexture: ShaderBall/_dev_shaderball_00_basecolor.png + +Node Name: Tiled +Node Path: RootNode.ShaderBall_1m.InlayRings.RingRight.RingRight_1_optimized.Tiled +Node Type: MeshVertexUVData + UVs: Count 720. Hash: 3542867785718437760 + UVCustomName: Tiled + +Node Name: Unwrapped +Node Path: RootNode.ShaderBall_1m.InlayRings.RingRight.RingRight_1_optimized.Unwrapped +Node Type: MeshVertexUVData + UVs: Count 720. Hash: 5575788853295372734 + UVCustomName: Unwrapped + +Node Name: TangentSet_0 +Node Path: RootNode.ShaderBall_1m.InlayRings.RingRight.RingRight_1_optimized.TangentSet_0 +Node Type: MeshVertexTangentData + Tangents: Count 720. Hash: 8244955966647049157 + GenerationMethod: 1 + SetIndex: 0 + +Node Name: TangentSet_1 +Node Path: RootNode.ShaderBall_1m.InlayRings.RingRight.RingRight_1_optimized.TangentSet_1 +Node Type: MeshVertexTangentData + Tangents: Count 720. Hash: 17763430282605007327 + GenerationMethod: 1 + SetIndex: 1 + +Node Name: BitangentSet_0 +Node Path: RootNode.ShaderBall_1m.InlayRings.RingRight.RingRight_1_optimized.BitangentSet_0 +Node Type: MeshVertexBitangentData + Bitangents: Count 720. Hash: 5442657821959315522 + GenerationMethod: 1 + +Node Name: BitangentSet_1 +Node Path: RootNode.ShaderBall_1m.InlayRings.RingRight.RingRight_1_optimized.BitangentSet_1 +Node Type: MeshVertexBitangentData + Bitangents: Count 720. Hash: 12477592739739533067 + GenerationMethod: 1 + +Node Name: blinn1 +Node Path: RootNode.ShaderBall_1m.InlayRings.RingRight.RingRight_1_optimized.blinn1 +Node Type: MaterialData + MaterialName: blinn1 + UniqueId: 2076548245838624187 + IsNoDraw: false + DiffuseColor: < 0.800000, 0.800000, 0.800000> + SpecularColor: < 0.500000, 0.500000, 0.500000> + EmissiveColor: < 0.000000, 0.000000, 0.000000> + Opacity: 1.000000 + Shininess: 6.311791 + UseColorMap: Not set + BaseColor: Not set + UseMetallicMap: Not set + MetallicFactor: Not set + UseRoughnessMap: Not set + RoughnessFactor: Not set + UseEmissiveMap: Not set + EmissiveIntensity: Not set + UseAOMap: Not set + DiffuseTexture: ShaderBall/_dev_shaderball_00_basecolor.png + SpecularTexture: + BumpTexture: + NormalTexture: + MetallicTexture: + RoughnessTexture: + AmbientOcclusionTexture: + EmissiveTexture: + BaseColorTexture: ShaderBall/_dev_shaderball_00_basecolor.png + +Node Name: Tiled +Node Path: RootNode.ShaderBall_1m.MainSphere.RightHub.RightHub_1.Tiled +Node Type: MeshVertexUVData + UVs: Count 6304. Hash: 4609122246850169975 + UVCustomName: Tiled + +Node Name: Unwrapped +Node Path: RootNode.ShaderBall_1m.MainSphere.RightHub.RightHub_1.Unwrapped +Node Type: MeshVertexUVData + UVs: Count 6304. Hash: 2696528076485355457 + UVCustomName: Unwrapped + +Node Name: blinn1 +Node Path: RootNode.ShaderBall_1m.MainSphere.RightHub.RightHub_1.blinn1 +Node Type: MaterialData + MaterialName: blinn1 + UniqueId: 2076548245838624187 + IsNoDraw: false + DiffuseColor: < 0.800000, 0.800000, 0.800000> + SpecularColor: < 0.500000, 0.500000, 0.500000> + EmissiveColor: < 0.000000, 0.000000, 0.000000> + Opacity: 1.000000 + Shininess: 6.311791 + UseColorMap: Not set + BaseColor: Not set + UseMetallicMap: Not set + MetallicFactor: Not set + UseRoughnessMap: Not set + RoughnessFactor: Not set + UseEmissiveMap: Not set + EmissiveIntensity: Not set + UseAOMap: Not set + DiffuseTexture: ShaderBall/_dev_shaderball_00_basecolor.png + SpecularTexture: + BumpTexture: + NormalTexture: + MetallicTexture: + RoughnessTexture: + AmbientOcclusionTexture: + EmissiveTexture: + BaseColorTexture: ShaderBall/_dev_shaderball_00_basecolor.png + +Node Name: TangentSet_0 +Node Path: RootNode.ShaderBall_1m.MainSphere.RightHub.RightHub_1.TangentSet_0 +Node Type: MeshVertexTangentData + Tangents: Count 6304. Hash: 6640465288865380105 + GenerationMethod: 1 + SetIndex: 0 + +Node Name: BitangentSet_0 +Node Path: RootNode.ShaderBall_1m.MainSphere.RightHub.RightHub_1.BitangentSet_0 +Node Type: MeshVertexBitangentData + Bitangents: Count 6304. Hash: 133343330720363387 + GenerationMethod: 1 + +Node Name: TangentSet_1 +Node Path: RootNode.ShaderBall_1m.MainSphere.RightHub.RightHub_1.TangentSet_1 +Node Type: MeshVertexTangentData + Tangents: Count 6304. Hash: 790774688340154169 + GenerationMethod: 1 + SetIndex: 1 + +Node Name: BitangentSet_1 +Node Path: RootNode.ShaderBall_1m.MainSphere.RightHub.RightHub_1.BitangentSet_1 +Node Type: MeshVertexBitangentData + Bitangents: Count 6304. Hash: 3046294637431015510 + GenerationMethod: 1 + +Node Name: Tiled +Node Path: RootNode.ShaderBall_1m.MainSphere.RightHub.RightHub_2.Tiled +Node Type: MeshVertexUVData + UVs: Count 6304. Hash: 4609122246850169975 + UVCustomName: Tiled + +Node Name: Unwrapped +Node Path: RootNode.ShaderBall_1m.MainSphere.RightHub.RightHub_2.Unwrapped +Node Type: MeshVertexUVData + UVs: Count 6304. Hash: 2696528076485355457 + UVCustomName: Unwrapped + +Node Name: blinn1 +Node Path: RootNode.ShaderBall_1m.MainSphere.RightHub.RightHub_2.blinn1 +Node Type: MaterialData + MaterialName: blinn1 + UniqueId: 2076548245838624187 + IsNoDraw: false + DiffuseColor: < 0.800000, 0.800000, 0.800000> + SpecularColor: < 0.500000, 0.500000, 0.500000> + EmissiveColor: < 0.000000, 0.000000, 0.000000> + Opacity: 1.000000 + Shininess: 6.311791 + UseColorMap: Not set + BaseColor: Not set + UseMetallicMap: Not set + MetallicFactor: Not set + UseRoughnessMap: Not set + RoughnessFactor: Not set + UseEmissiveMap: Not set + EmissiveIntensity: Not set + UseAOMap: Not set + DiffuseTexture: ShaderBall/_dev_shaderball_00_basecolor.png + SpecularTexture: + BumpTexture: + NormalTexture: + MetallicTexture: + RoughnessTexture: + AmbientOcclusionTexture: + EmissiveTexture: + BaseColorTexture: ShaderBall/_dev_shaderball_00_basecolor.png + +Node Name: Tiled +Node Path: RootNode.ShaderBall_1m.MainSphere.RightHub.RightHub_1_optimized.Tiled +Node Type: MeshVertexUVData + UVs: Count 1617. Hash: 16761061667647714654 + UVCustomName: Tiled + +Node Name: Unwrapped +Node Path: RootNode.ShaderBall_1m.MainSphere.RightHub.RightHub_1_optimized.Unwrapped +Node Type: MeshVertexUVData + UVs: Count 1617. Hash: 14954885971875692232 + UVCustomName: Unwrapped + +Node Name: TangentSet_0 +Node Path: RootNode.ShaderBall_1m.MainSphere.RightHub.RightHub_1_optimized.TangentSet_0 +Node Type: MeshVertexTangentData + Tangents: Count 1617. Hash: 37061345418264916 + GenerationMethod: 1 + SetIndex: 0 + +Node Name: TangentSet_1 +Node Path: RootNode.ShaderBall_1m.MainSphere.RightHub.RightHub_1_optimized.TangentSet_1 +Node Type: MeshVertexTangentData + Tangents: Count 1617. Hash: 7368059604555723833 + GenerationMethod: 1 + SetIndex: 1 + +Node Name: BitangentSet_0 +Node Path: RootNode.ShaderBall_1m.MainSphere.RightHub.RightHub_1_optimized.BitangentSet_0 +Node Type: MeshVertexBitangentData + Bitangents: Count 1617. Hash: 6244533771017459078 + GenerationMethod: 1 + +Node Name: BitangentSet_1 +Node Path: RootNode.ShaderBall_1m.MainSphere.RightHub.RightHub_1_optimized.BitangentSet_1 +Node Type: MeshVertexBitangentData + Bitangents: Count 1617. Hash: 6416015160247779547 + GenerationMethod: 1 + +Node Name: blinn1 +Node Path: RootNode.ShaderBall_1m.MainSphere.RightHub.RightHub_1_optimized.blinn1 +Node Type: MaterialData + MaterialName: blinn1 + UniqueId: 2076548245838624187 + IsNoDraw: false + DiffuseColor: < 0.800000, 0.800000, 0.800000> + SpecularColor: < 0.500000, 0.500000, 0.500000> + EmissiveColor: < 0.000000, 0.000000, 0.000000> + Opacity: 1.000000 + Shininess: 6.311791 + UseColorMap: Not set + BaseColor: Not set + UseMetallicMap: Not set + MetallicFactor: Not set + UseRoughnessMap: Not set + RoughnessFactor: Not set + UseEmissiveMap: Not set + EmissiveIntensity: Not set + UseAOMap: Not set + DiffuseTexture: ShaderBall/_dev_shaderball_00_basecolor.png + SpecularTexture: + BumpTexture: + NormalTexture: + MetallicTexture: + RoughnessTexture: + AmbientOcclusionTexture: + EmissiveTexture: + BaseColorTexture: ShaderBall/_dev_shaderball_00_basecolor.png + +Node Name: Tiled +Node Path: RootNode.ShaderBall_1m.MainSphere.LeftHub.LeftHub_1.Tiled +Node Type: MeshVertexUVData + UVs: Count 6304. Hash: 2524875548439506384 + UVCustomName: Tiled + +Node Name: Unwrapped +Node Path: RootNode.ShaderBall_1m.MainSphere.LeftHub.LeftHub_1.Unwrapped +Node Type: MeshVertexUVData + UVs: Count 6304. Hash: 89810986084680009 + UVCustomName: Unwrapped + +Node Name: blinn1 +Node Path: RootNode.ShaderBall_1m.MainSphere.LeftHub.LeftHub_1.blinn1 +Node Type: MaterialData + MaterialName: blinn1 + UniqueId: 2076548245838624187 + IsNoDraw: false + DiffuseColor: < 0.800000, 0.800000, 0.800000> + SpecularColor: < 0.500000, 0.500000, 0.500000> + EmissiveColor: < 0.000000, 0.000000, 0.000000> + Opacity: 1.000000 + Shininess: 6.311791 + UseColorMap: Not set + BaseColor: Not set + UseMetallicMap: Not set + MetallicFactor: Not set + UseRoughnessMap: Not set + RoughnessFactor: Not set + UseEmissiveMap: Not set + EmissiveIntensity: Not set + UseAOMap: Not set + DiffuseTexture: ShaderBall/_dev_shaderball_00_basecolor.png + SpecularTexture: + BumpTexture: + NormalTexture: + MetallicTexture: + RoughnessTexture: + AmbientOcclusionTexture: + EmissiveTexture: + BaseColorTexture: ShaderBall/_dev_shaderball_00_basecolor.png + +Node Name: TangentSet_0 +Node Path: RootNode.ShaderBall_1m.MainSphere.LeftHub.LeftHub_1.TangentSet_0 +Node Type: MeshVertexTangentData + Tangents: Count 6304. Hash: 1044733215619569246 + GenerationMethod: 1 + SetIndex: 0 + +Node Name: BitangentSet_0 +Node Path: RootNode.ShaderBall_1m.MainSphere.LeftHub.LeftHub_1.BitangentSet_0 +Node Type: MeshVertexBitangentData + Bitangents: Count 6304. Hash: 15252409165383740719 + GenerationMethod: 1 + +Node Name: TangentSet_1 +Node Path: RootNode.ShaderBall_1m.MainSphere.LeftHub.LeftHub_1.TangentSet_1 +Node Type: MeshVertexTangentData + Tangents: Count 6304. Hash: 3184716392697283856 + GenerationMethod: 1 + SetIndex: 1 + +Node Name: BitangentSet_1 +Node Path: RootNode.ShaderBall_1m.MainSphere.LeftHub.LeftHub_1.BitangentSet_1 +Node Type: MeshVertexBitangentData + Bitangents: Count 6304. Hash: 4969210758291089995 + GenerationMethod: 1 + +Node Name: Tiled +Node Path: RootNode.ShaderBall_1m.MainSphere.LeftHub.LeftHub_2.Tiled +Node Type: MeshVertexUVData + UVs: Count 6304. Hash: 2524875548439506384 + UVCustomName: Tiled + +Node Name: Unwrapped +Node Path: RootNode.ShaderBall_1m.MainSphere.LeftHub.LeftHub_2.Unwrapped +Node Type: MeshVertexUVData + UVs: Count 6304. Hash: 89810986084680009 + UVCustomName: Unwrapped + +Node Name: blinn1 +Node Path: RootNode.ShaderBall_1m.MainSphere.LeftHub.LeftHub_2.blinn1 +Node Type: MaterialData + MaterialName: blinn1 + UniqueId: 2076548245838624187 + IsNoDraw: false + DiffuseColor: < 0.800000, 0.800000, 0.800000> + SpecularColor: < 0.500000, 0.500000, 0.500000> + EmissiveColor: < 0.000000, 0.000000, 0.000000> + Opacity: 1.000000 + Shininess: 6.311791 + UseColorMap: Not set + BaseColor: Not set + UseMetallicMap: Not set + MetallicFactor: Not set + UseRoughnessMap: Not set + RoughnessFactor: Not set + UseEmissiveMap: Not set + EmissiveIntensity: Not set + UseAOMap: Not set + DiffuseTexture: ShaderBall/_dev_shaderball_00_basecolor.png + SpecularTexture: + BumpTexture: + NormalTexture: + MetallicTexture: + RoughnessTexture: + AmbientOcclusionTexture: + EmissiveTexture: + BaseColorTexture: ShaderBall/_dev_shaderball_00_basecolor.png + +Node Name: Tiled +Node Path: RootNode.ShaderBall_1m.MainSphere.LeftHub.LeftHub_1_optimized.Tiled +Node Type: MeshVertexUVData + UVs: Count 1617. Hash: 8467946356718878053 + UVCustomName: Tiled + +Node Name: Unwrapped +Node Path: RootNode.ShaderBall_1m.MainSphere.LeftHub.LeftHub_1_optimized.Unwrapped +Node Type: MeshVertexUVData + UVs: Count 1617. Hash: 2373603727160338558 + UVCustomName: Unwrapped + +Node Name: TangentSet_0 +Node Path: RootNode.ShaderBall_1m.MainSphere.LeftHub.LeftHub_1_optimized.TangentSet_0 +Node Type: MeshVertexTangentData + Tangents: Count 1617. Hash: 1521191693628786862 + GenerationMethod: 1 + SetIndex: 0 + +Node Name: TangentSet_1 +Node Path: RootNode.ShaderBall_1m.MainSphere.LeftHub.LeftHub_1_optimized.TangentSet_1 +Node Type: MeshVertexTangentData + Tangents: Count 1617. Hash: 7175852234718691900 + GenerationMethod: 1 + SetIndex: 1 + +Node Name: BitangentSet_0 +Node Path: RootNode.ShaderBall_1m.MainSphere.LeftHub.LeftHub_1_optimized.BitangentSet_0 +Node Type: MeshVertexBitangentData + Bitangents: Count 1617. Hash: 12283058591680528758 + GenerationMethod: 1 + +Node Name: BitangentSet_1 +Node Path: RootNode.ShaderBall_1m.MainSphere.LeftHub.LeftHub_1_optimized.BitangentSet_1 +Node Type: MeshVertexBitangentData + Bitangents: Count 1617. Hash: 14516337485055158228 + GenerationMethod: 1 + +Node Name: blinn1 +Node Path: RootNode.ShaderBall_1m.MainSphere.LeftHub.LeftHub_1_optimized.blinn1 +Node Type: MaterialData + MaterialName: blinn1 + UniqueId: 2076548245838624187 + IsNoDraw: false + DiffuseColor: < 0.800000, 0.800000, 0.800000> + SpecularColor: < 0.500000, 0.500000, 0.500000> + EmissiveColor: < 0.000000, 0.000000, 0.000000> + Opacity: 1.000000 + Shininess: 6.311791 + UseColorMap: Not set + BaseColor: Not set + UseMetallicMap: Not set + MetallicFactor: Not set + UseRoughnessMap: Not set + RoughnessFactor: Not set + UseEmissiveMap: Not set + EmissiveIntensity: Not set + UseAOMap: Not set + DiffuseTexture: ShaderBall/_dev_shaderball_00_basecolor.png + SpecularTexture: + BumpTexture: + NormalTexture: + MetallicTexture: + RoughnessTexture: + AmbientOcclusionTexture: + EmissiveTexture: + BaseColorTexture: ShaderBall/_dev_shaderball_00_basecolor.png + +Node Name: Tiled +Node Path: RootNode.ShaderBall_1m.MainSphere.Inside.Inside_1.Tiled +Node Type: MeshVertexUVData + UVs: Count 3520. Hash: 7734180808251274182 + UVCustomName: Tiled + +Node Name: Unwrapped +Node Path: RootNode.ShaderBall_1m.MainSphere.Inside.Inside_1.Unwrapped +Node Type: MeshVertexUVData + UVs: Count 3520. Hash: 13560118186140352568 + UVCustomName: Unwrapped + +Node Name: blinn1 +Node Path: RootNode.ShaderBall_1m.MainSphere.Inside.Inside_1.blinn1 +Node Type: MaterialData + MaterialName: blinn1 + UniqueId: 2076548245838624187 + IsNoDraw: false + DiffuseColor: < 0.800000, 0.800000, 0.800000> + SpecularColor: < 0.500000, 0.500000, 0.500000> + EmissiveColor: < 0.000000, 0.000000, 0.000000> + Opacity: 1.000000 + Shininess: 6.311791 + UseColorMap: Not set + BaseColor: Not set + UseMetallicMap: Not set + MetallicFactor: Not set + UseRoughnessMap: Not set + RoughnessFactor: Not set + UseEmissiveMap: Not set + EmissiveIntensity: Not set + UseAOMap: Not set + DiffuseTexture: ShaderBall/_dev_shaderball_00_basecolor.png + SpecularTexture: + BumpTexture: + NormalTexture: + MetallicTexture: + RoughnessTexture: + AmbientOcclusionTexture: + EmissiveTexture: + BaseColorTexture: ShaderBall/_dev_shaderball_00_basecolor.png + +Node Name: TangentSet_0 +Node Path: RootNode.ShaderBall_1m.MainSphere.Inside.Inside_1.TangentSet_0 +Node Type: MeshVertexTangentData + Tangents: Count 3520. Hash: 5538036360908204376 + GenerationMethod: 1 + SetIndex: 0 + +Node Name: BitangentSet_0 +Node Path: RootNode.ShaderBall_1m.MainSphere.Inside.Inside_1.BitangentSet_0 +Node Type: MeshVertexBitangentData + Bitangents: Count 3520. Hash: 470358662493460341 + GenerationMethod: 1 + +Node Name: TangentSet_1 +Node Path: RootNode.ShaderBall_1m.MainSphere.Inside.Inside_1.TangentSet_1 +Node Type: MeshVertexTangentData + Tangents: Count 3520. Hash: 13801426056203770982 + GenerationMethod: 1 + SetIndex: 1 + +Node Name: BitangentSet_1 +Node Path: RootNode.ShaderBall_1m.MainSphere.Inside.Inside_1.BitangentSet_1 +Node Type: MeshVertexBitangentData + Bitangents: Count 3520. Hash: 8463107387658894201 + GenerationMethod: 1 + +Node Name: Tiled +Node Path: RootNode.ShaderBall_1m.MainSphere.Inside.Inside_2.Tiled +Node Type: MeshVertexUVData + UVs: Count 3520. Hash: 7734180808251274182 + UVCustomName: Tiled + +Node Name: Unwrapped +Node Path: RootNode.ShaderBall_1m.MainSphere.Inside.Inside_2.Unwrapped +Node Type: MeshVertexUVData + UVs: Count 3520. Hash: 13560118186140352568 + UVCustomName: Unwrapped + +Node Name: blinn1 +Node Path: RootNode.ShaderBall_1m.MainSphere.Inside.Inside_2.blinn1 +Node Type: MaterialData + MaterialName: blinn1 + UniqueId: 2076548245838624187 + IsNoDraw: false + DiffuseColor: < 0.800000, 0.800000, 0.800000> + SpecularColor: < 0.500000, 0.500000, 0.500000> + EmissiveColor: < 0.000000, 0.000000, 0.000000> + Opacity: 1.000000 + Shininess: 6.311791 + UseColorMap: Not set + BaseColor: Not set + UseMetallicMap: Not set + MetallicFactor: Not set + UseRoughnessMap: Not set + RoughnessFactor: Not set + UseEmissiveMap: Not set + EmissiveIntensity: Not set + UseAOMap: Not set + DiffuseTexture: ShaderBall/_dev_shaderball_00_basecolor.png + SpecularTexture: + BumpTexture: + NormalTexture: + MetallicTexture: + RoughnessTexture: + AmbientOcclusionTexture: + EmissiveTexture: + BaseColorTexture: ShaderBall/_dev_shaderball_00_basecolor.png + +Node Name: Tiled +Node Path: RootNode.ShaderBall_1m.MainSphere.Inside.Inside_1_optimized.Tiled +Node Type: MeshVertexUVData + UVs: Count 960. Hash: 2691029117997309291 + UVCustomName: Tiled + +Node Name: Unwrapped +Node Path: RootNode.ShaderBall_1m.MainSphere.Inside.Inside_1_optimized.Unwrapped +Node Type: MeshVertexUVData + UVs: Count 960. Hash: 10093724573967674240 + UVCustomName: Unwrapped + +Node Name: TangentSet_0 +Node Path: RootNode.ShaderBall_1m.MainSphere.Inside.Inside_1_optimized.TangentSet_0 +Node Type: MeshVertexTangentData + Tangents: Count 960. Hash: 1221219959437752888 + GenerationMethod: 1 + SetIndex: 0 + +Node Name: TangentSet_1 +Node Path: RootNode.ShaderBall_1m.MainSphere.Inside.Inside_1_optimized.TangentSet_1 +Node Type: MeshVertexTangentData + Tangents: Count 960. Hash: 1294720383009806722 + GenerationMethod: 1 + SetIndex: 1 + +Node Name: BitangentSet_0 +Node Path: RootNode.ShaderBall_1m.MainSphere.Inside.Inside_1_optimized.BitangentSet_0 +Node Type: MeshVertexBitangentData + Bitangents: Count 960. Hash: 10294793677923893113 + GenerationMethod: 1 + +Node Name: BitangentSet_1 +Node Path: RootNode.ShaderBall_1m.MainSphere.Inside.Inside_1_optimized.BitangentSet_1 +Node Type: MeshVertexBitangentData + Bitangents: Count 960. Hash: 6108415656799664788 + GenerationMethod: 1 + +Node Name: blinn1 +Node Path: RootNode.ShaderBall_1m.MainSphere.Inside.Inside_1_optimized.blinn1 +Node Type: MaterialData + MaterialName: blinn1 + UniqueId: 2076548245838624187 + IsNoDraw: false + DiffuseColor: < 0.800000, 0.800000, 0.800000> + SpecularColor: < 0.500000, 0.500000, 0.500000> + EmissiveColor: < 0.000000, 0.000000, 0.000000> + Opacity: 1.000000 + Shininess: 6.311791 + UseColorMap: Not set + BaseColor: Not set + UseMetallicMap: Not set + MetallicFactor: Not set + UseRoughnessMap: Not set + RoughnessFactor: Not set + UseEmissiveMap: Not set + EmissiveIntensity: Not set + UseAOMap: Not set + DiffuseTexture: ShaderBall/_dev_shaderball_00_basecolor.png + SpecularTexture: + BumpTexture: + NormalTexture: + MetallicTexture: + RoughnessTexture: + AmbientOcclusionTexture: + EmissiveTexture: + BaseColorTexture: ShaderBall/_dev_shaderball_00_basecolor.png + +Node Name: Tiled +Node Path: RootNode.ShaderBall_1m.MainSphere.MainOuterSphere.MainOuterSphere_1.Tiled +Node Type: MeshVertexUVData + UVs: Count 18912. Hash: 4120783891454032649 + UVCustomName: Tiled + +Node Name: Unwrapped +Node Path: RootNode.ShaderBall_1m.MainSphere.MainOuterSphere.MainOuterSphere_1.Unwrapped +Node Type: MeshVertexUVData + UVs: Count 18912. Hash: 9011003754405408275 + UVCustomName: Unwrapped + +Node Name: blinn1 +Node Path: RootNode.ShaderBall_1m.MainSphere.MainOuterSphere.MainOuterSphere_1.blinn1 +Node Type: MaterialData + MaterialName: blinn1 + UniqueId: 2076548245838624187 + IsNoDraw: false + DiffuseColor: < 0.800000, 0.800000, 0.800000> + SpecularColor: < 0.500000, 0.500000, 0.500000> + EmissiveColor: < 0.000000, 0.000000, 0.000000> + Opacity: 1.000000 + Shininess: 6.311791 + UseColorMap: Not set + BaseColor: Not set + UseMetallicMap: Not set + MetallicFactor: Not set + UseRoughnessMap: Not set + RoughnessFactor: Not set + UseEmissiveMap: Not set + EmissiveIntensity: Not set + UseAOMap: Not set + DiffuseTexture: ShaderBall/_dev_shaderball_00_basecolor.png + SpecularTexture: + BumpTexture: + NormalTexture: + MetallicTexture: + RoughnessTexture: + AmbientOcclusionTexture: + EmissiveTexture: + BaseColorTexture: ShaderBall/_dev_shaderball_00_basecolor.png + +Node Name: TangentSet_0 +Node Path: RootNode.ShaderBall_1m.MainSphere.MainOuterSphere.MainOuterSphere_1.TangentSet_0 +Node Type: MeshVertexTangentData + Tangents: Count 18912. Hash: 12406712159692783345 + GenerationMethod: 1 + SetIndex: 0 + +Node Name: BitangentSet_0 +Node Path: RootNode.ShaderBall_1m.MainSphere.MainOuterSphere.MainOuterSphere_1.BitangentSet_0 +Node Type: MeshVertexBitangentData + Bitangents: Count 18912. Hash: 7868083933985729169 + GenerationMethod: 1 + +Node Name: TangentSet_1 +Node Path: RootNode.ShaderBall_1m.MainSphere.MainOuterSphere.MainOuterSphere_1.TangentSet_1 +Node Type: MeshVertexTangentData + Tangents: Count 18912. Hash: 1990603474898794477 + GenerationMethod: 1 + SetIndex: 1 + +Node Name: BitangentSet_1 +Node Path: RootNode.ShaderBall_1m.MainSphere.MainOuterSphere.MainOuterSphere_1.BitangentSet_1 +Node Type: MeshVertexBitangentData + Bitangents: Count 18912. Hash: 4812378464029296668 + GenerationMethod: 1 + +Node Name: Tiled +Node Path: RootNode.ShaderBall_1m.MainSphere.MainOuterSphere.MainOuterSphere_2.Tiled +Node Type: MeshVertexUVData + UVs: Count 18912. Hash: 4120783891454032649 + UVCustomName: Tiled + +Node Name: Unwrapped +Node Path: RootNode.ShaderBall_1m.MainSphere.MainOuterSphere.MainOuterSphere_2.Unwrapped +Node Type: MeshVertexUVData + UVs: Count 18912. Hash: 9011003754405408275 + UVCustomName: Unwrapped + +Node Name: blinn1 +Node Path: RootNode.ShaderBall_1m.MainSphere.MainOuterSphere.MainOuterSphere_2.blinn1 +Node Type: MaterialData + MaterialName: blinn1 + UniqueId: 2076548245838624187 + IsNoDraw: false + DiffuseColor: < 0.800000, 0.800000, 0.800000> + SpecularColor: < 0.500000, 0.500000, 0.500000> + EmissiveColor: < 0.000000, 0.000000, 0.000000> + Opacity: 1.000000 + Shininess: 6.311791 + UseColorMap: Not set + BaseColor: Not set + UseMetallicMap: Not set + MetallicFactor: Not set + UseRoughnessMap: Not set + RoughnessFactor: Not set + UseEmissiveMap: Not set + EmissiveIntensity: Not set + UseAOMap: Not set + DiffuseTexture: ShaderBall/_dev_shaderball_00_basecolor.png + SpecularTexture: + BumpTexture: + NormalTexture: + MetallicTexture: + RoughnessTexture: + AmbientOcclusionTexture: + EmissiveTexture: + BaseColorTexture: ShaderBall/_dev_shaderball_00_basecolor.png + +Node Name: Tiled +Node Path: RootNode.ShaderBall_1m.MainSphere.MainOuterSphere.MainOuterSphere_1_optimized.Tiled +Node Type: MeshVertexUVData + UVs: Count 4891. Hash: 15498889522919365505 + UVCustomName: Tiled + +Node Name: Unwrapped +Node Path: RootNode.ShaderBall_1m.MainSphere.MainOuterSphere.MainOuterSphere_1_optimized.Unwrapped +Node Type: MeshVertexUVData + UVs: Count 4891. Hash: 15832520573612498718 + UVCustomName: Unwrapped + +Node Name: TangentSet_0 +Node Path: RootNode.ShaderBall_1m.MainSphere.MainOuterSphere.MainOuterSphere_1_optimized.TangentSet_0 +Node Type: MeshVertexTangentData + Tangents: Count 4891. Hash: 6697133368486369688 + GenerationMethod: 1 + SetIndex: 0 + +Node Name: TangentSet_1 +Node Path: RootNode.ShaderBall_1m.MainSphere.MainOuterSphere.MainOuterSphere_1_optimized.TangentSet_1 +Node Type: MeshVertexTangentData + Tangents: Count 4891. Hash: 18420832496569008358 + GenerationMethod: 1 + SetIndex: 1 + +Node Name: BitangentSet_0 +Node Path: RootNode.ShaderBall_1m.MainSphere.MainOuterSphere.MainOuterSphere_1_optimized.BitangentSet_0 +Node Type: MeshVertexBitangentData + Bitangents: Count 4891. Hash: 2267250201584370787 + GenerationMethod: 1 + +Node Name: BitangentSet_1 +Node Path: RootNode.ShaderBall_1m.MainSphere.MainOuterSphere.MainOuterSphere_1_optimized.BitangentSet_1 +Node Type: MeshVertexBitangentData + Bitangents: Count 4891. Hash: 7889928104085840247 + GenerationMethod: 1 + +Node Name: blinn1 +Node Path: RootNode.ShaderBall_1m.MainSphere.MainOuterSphere.MainOuterSphere_1_optimized.blinn1 +Node Type: MaterialData + MaterialName: blinn1 + UniqueId: 2076548245838624187 + IsNoDraw: false + DiffuseColor: < 0.800000, 0.800000, 0.800000> + SpecularColor: < 0.500000, 0.500000, 0.500000> + EmissiveColor: < 0.000000, 0.000000, 0.000000> + Opacity: 1.000000 + Shininess: 6.311791 + UseColorMap: Not set + BaseColor: Not set + UseMetallicMap: Not set + MetallicFactor: Not set + UseRoughnessMap: Not set + RoughnessFactor: Not set + UseEmissiveMap: Not set + EmissiveIntensity: Not set + UseAOMap: Not set + DiffuseTexture: ShaderBall/_dev_shaderball_00_basecolor.png + SpecularTexture: + BumpTexture: + NormalTexture: + MetallicTexture: + RoughnessTexture: + AmbientOcclusionTexture: + EmissiveTexture: + BaseColorTexture: ShaderBall/_dev_shaderball_00_basecolor.png + diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/ShaderBall/SceneDebug/shaderball.dbgsg.xml b/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/ShaderBall/SceneDebug/shaderball.dbgsg.xml new file mode 100644 index 0000000000..acff9e5f02 --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/ShaderBall/SceneDebug/shaderball.dbgsg.xml @@ -0,0 +1,9491 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/ShaderBall/_dev_shaderball_00_basecolor.png b/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/ShaderBall/_dev_shaderball_00_basecolor.png new file mode 100644 index 0000000000..415ca3e521 --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/ShaderBall/_dev_shaderball_00_basecolor.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:93a7e033d9fb0fcac221647322bde03716643d789390f79078c4fcc37ecfd005 +size 68327 diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/ShaderBall/shaderball.fbx b/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/ShaderBall/shaderball.fbx new file mode 100644 index 0000000000..caf6dcbe8f --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/ShaderBall/shaderball.fbx @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6e63a55a35c749a16a03e10a1f53a48bd426c61db80151de080235b14cf6b70d +size 2479344 diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/SoftNamingLOD/lodtest.dbgsg b/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/SoftNamingLOD/SceneDebug/lodtest.dbgsg similarity index 100% rename from AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/SoftNamingLOD/lodtest.dbgsg rename to AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/SoftNamingLOD/SceneDebug/lodtest.dbgsg diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/SoftNamingLOD/SceneDebug/lodtest.dbgsg.xml b/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/SoftNamingLOD/SceneDebug/lodtest.dbgsg.xml new file mode 100644 index 0000000000..d1af4198b5 --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/SoftNamingLOD/SceneDebug/lodtest.dbgsg.xml @@ -0,0 +1,2007 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/SoftNamingPhysics/physicstest.dbgsg b/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/SoftNamingPhysics/SceneDebug/physicstest.dbgsg similarity index 100% rename from AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/SoftNamingPhysics/physicstest.dbgsg rename to AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/SoftNamingPhysics/SceneDebug/physicstest.dbgsg diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/SoftNamingPhysics/SceneDebug/physicstest.dbgsg.xml b/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/SoftNamingPhysics/SceneDebug/physicstest.dbgsg.xml new file mode 100644 index 0000000000..b22eb14fc6 --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/SoftNamingPhysics/SceneDebug/physicstest.dbgsg.xml @@ -0,0 +1,817 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/TwoMeshLinkedMaterials/multiple_mesh_linked_materials.dbgsg b/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/TwoMeshLinkedMaterials/SceneDebug/multiple_mesh_linked_materials.dbgsg similarity index 100% rename from AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/TwoMeshLinkedMaterials/multiple_mesh_linked_materials.dbgsg rename to AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/TwoMeshLinkedMaterials/SceneDebug/multiple_mesh_linked_materials.dbgsg diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/TwoMeshLinkedMaterials/SceneDebug/multiple_mesh_linked_materials.dbgsg.xml b/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/TwoMeshLinkedMaterials/SceneDebug/multiple_mesh_linked_materials.dbgsg.xml new file mode 100644 index 0000000000..3eaf018fb6 --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/TwoMeshLinkedMaterials/SceneDebug/multiple_mesh_linked_materials.dbgsg.xml @@ -0,0 +1,1345 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/TwoMeshOneMaterial/multiple_mesh_one_material.dbgsg b/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/TwoMeshOneMaterial/SceneDebug/multiple_mesh_one_material.dbgsg similarity index 100% rename from AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/TwoMeshOneMaterial/multiple_mesh_one_material.dbgsg rename to AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/TwoMeshOneMaterial/SceneDebug/multiple_mesh_one_material.dbgsg diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/TwoMeshOneMaterial/SceneDebug/multiple_mesh_one_material.dbgsg.xml b/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/TwoMeshOneMaterial/SceneDebug/multiple_mesh_one_material.dbgsg.xml new file mode 100644 index 0000000000..39ac33f654 --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/TwoMeshOneMaterial/SceneDebug/multiple_mesh_one_material.dbgsg.xml @@ -0,0 +1,1015 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/TwoMeshTwoMaterial/multiple_mesh_multiple_material.dbgsg b/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/TwoMeshTwoMaterial/SceneDebug/multiple_mesh_multiple_material.dbgsg similarity index 100% rename from AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/TwoMeshTwoMaterial/multiple_mesh_multiple_material.dbgsg rename to AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/TwoMeshTwoMaterial/SceneDebug/multiple_mesh_multiple_material.dbgsg diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/TwoMeshTwoMaterial/SceneDebug/multiple_mesh_multiple_material.dbgsg.xml b/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/TwoMeshTwoMaterial/SceneDebug/multiple_mesh_multiple_material.dbgsg.xml new file mode 100644 index 0000000000..c41c1414a4 --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/TwoMeshTwoMaterial/SceneDebug/multiple_mesh_multiple_material.dbgsg.xml @@ -0,0 +1,1015 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/TwoMeshTwoMaterial/multiple_mesh_multiple_material_override.dbgsg b/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/TwoMeshTwoMaterial/SceneDebug/multiple_mesh_multiple_material_override.dbgsg similarity index 100% rename from AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/TwoMeshTwoMaterial/multiple_mesh_multiple_material_override.dbgsg rename to AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/TwoMeshTwoMaterial/SceneDebug/multiple_mesh_multiple_material_override.dbgsg diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/TwoMeshTwoMaterial/SceneDebug/multiple_mesh_multiple_material_override.dbgsg.xml b/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/TwoMeshTwoMaterial/SceneDebug/multiple_mesh_multiple_material_override.dbgsg.xml new file mode 100644 index 0000000000..01167ec0ee --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/TwoMeshTwoMaterial/SceneDebug/multiple_mesh_multiple_material_override.dbgsg.xml @@ -0,0 +1,817 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/VertexColor/vertexcolor.dbgsg b/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/VertexColor/SceneDebug/vertexcolor.dbgsg similarity index 100% rename from AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/VertexColor/vertexcolor.dbgsg rename to AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/VertexColor/SceneDebug/vertexcolor.dbgsg diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/VertexColor/SceneDebug/vertexcolor.dbgsg.xml b/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/VertexColor/SceneDebug/vertexcolor.dbgsg.xml new file mode 100644 index 0000000000..f5caa70d63 --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/VertexColor/SceneDebug/vertexcolor.dbgsg.xml @@ -0,0 +1,576 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/fbx_tests.py b/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/fbx_tests.py index 71f200074c..c28dfa64f2 100755 --- a/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/fbx_tests.py +++ b/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/fbx_tests.py @@ -21,8 +21,8 @@ from ly_test_tools.o3de.asset_processor import ASSET_PROCESSOR_PLATFORM_MAP from ..ap_fixtures.asset_processor_fixture import asset_processor as asset_processor from ..ap_fixtures.ap_setup_fixture import ap_setup_fixture as ap_setup_fixture from ..ap_fixtures.ap_config_backup_fixture import ap_config_backup_fixture as ap_config_backup_fixture -from ..ap_fixtures.ap_config_default_platform_fixture import ap_config_default_platform_fixture as ap_config_default_platform_fixture - +from ..ap_fixtures.ap_config_default_platform_fixture \ + import ap_config_default_platform_fixture as ap_config_default_platform_fixture # Import LyShared import ly_test_tools.o3de.pipeline_utils as utils @@ -33,6 +33,7 @@ logger = logging.getLogger(__name__) # Helper: variables we will use for parameter values in the test: targetProjects = ["AutomatedTesting"] + @pytest.fixture @pytest.mark.SUITE_sandbox def local_resources(request, workspace, ap_setup_fixture): @@ -54,25 +55,30 @@ class BlackboxAssetTest: blackbox_fbx_tests = [ pytest.param( BlackboxAssetTest( - test_name= "OneMeshOneMaterial_RunAP_SuccessWithMatchingProducts", - asset_folder= "OneMeshOneMaterial", + test_name="OneMeshOneMaterial_RunAP_SuccessWithMatchingProducts", + asset_folder="OneMeshOneMaterial", scene_debug_file="onemeshonematerial.dbgsg", - assets = [ + assets=[ asset_db_utils.DBSourceAsset( - source_file_name = "OneMeshOneMaterial.fbx", - uuid = b"8a9164adb84859be893e18aa819438e1", - jobs = [ + source_file_name="OneMeshOneMaterial.fbx", + uuid=b"8a9164adb84859be893e18aa819438e1", + jobs=[ asset_db_utils.DBJob( - job_key= "Scene compilation", + job_key="Scene compilation", builder_guid=b"bd8bf65894854fe3830e8ec3a23c35f3", status=4, error_count=0, warning_count=1, - products = [ + products=[ asset_db_utils.DBProduct( product_name='onemeshonematerial/onemeshonematerial.dbgsg', sub_id=1918494907, - asset_type=b'07f289d14dc74c4094b40a53bbcb9f0b') + asset_type=b'07f289d14dc74c4094b40a53bbcb9f0b'), + asset_db_utils.DBProduct( + product_name='onemeshonematerial/onemeshonematerial.dbgsg.xml', + sub_id=556355570, + asset_type=b'51f376140d774f369ac67ed70a0ac868' + ) ] ), ] @@ -86,25 +92,30 @@ blackbox_fbx_tests = [ BlackboxAssetTest( # Verifies that the soft naming convention feature with level of detail meshes works. # https://docs.aws.amazon.com/lumberyard/latest/userguide/char-fbx-importer-soft-naming.html - test_name= "SoftNamingLOD_RunAP_SuccessWithMatchingProducts", - asset_folder= "SoftNamingLOD", + test_name="SoftNamingLOD_RunAP_SuccessWithMatchingProducts", + asset_folder="SoftNamingLOD", scene_debug_file="lodtest.dbgsg", - assets = [ + assets=[ asset_db_utils.DBSourceAsset( - source_file_name = "lodtest.fbx", - uuid = b"44c8627fe2c25aae91fe3ff9547be3b9", - jobs = [ + source_file_name="lodtest.fbx", + uuid=b"44c8627fe2c25aae91fe3ff9547be3b9", + jobs=[ asset_db_utils.DBJob( - job_key= "Scene compilation", + job_key="Scene compilation", builder_guid=b"bd8bf65894854fe3830e8ec3a23c35f3", status=4, error_count=0, warning_count=22, - products = [ + products=[ asset_db_utils.DBProduct( product_name='softnaminglod/lodtest.dbgsg', sub_id=-632012261, - asset_type=b'07f289d14dc74c4094b40a53bbcb9f0b') + asset_type=b'07f289d14dc74c4094b40a53bbcb9f0b'), + asset_db_utils.DBProduct( + product_name='softnaminglod/lodtest.dbgsg.xml', + sub_id=-2036095434, + asset_type=b'51f376140d774f369ac67ed70a0ac868' + ) ] ), ] @@ -118,31 +129,36 @@ blackbox_fbx_tests = [ BlackboxAssetTest( # Verifies that the soft naming convention feature with physics proxies works. # https://docs.aws.amazon.com/lumberyard/latest/userguide/char-fbx-importer-soft-naming.html - test_name= "SoftNamingPhysics_RunAP_SuccessWithMatchingProducts", - asset_folder= "SoftNamingPhysics", + test_name="SoftNamingPhysics_RunAP_SuccessWithMatchingProducts", + asset_folder="SoftNamingPhysics", scene_debug_file="physicstest.dbgsg", - assets = [ + assets=[ asset_db_utils.DBSourceAsset( - source_file_name = "physicstest.fbx", - uuid = b"df957b7918cf5b029806c73f630fa1c8", - jobs = [ + source_file_name="physicstest.fbx", + uuid=b"df957b7918cf5b029806c73f630fa1c8", + jobs=[ asset_db_utils.DBJob( - job_key= "Scene compilation", + job_key="Scene compilation", builder_guid=b"bd8bf65894854fe3830e8ec3a23c35f3", status=4, error_count=0, warning_count=14, - products = [ + products=[ asset_db_utils.DBProduct( product_name='softnamingphysics/physicstest.dbgsg', sub_id=-740411732, asset_type=b'07f289d14dc74c4094b40a53bbcb9f0b' ), + asset_db_utils.DBProduct( + product_name='softnamingphysics/physicstest.dbgsg.xml', + sub_id=330338417, + asset_type=b'51f376140d774f369ac67ed70a0ac868' + ), asset_db_utils.DBProduct( product_name="softnamingphysics/physicstest.pxmesh", sub_id=640975857, asset_type=b"7a2871b95eab4de0a901b0d2c6920ddb" - ), + ) ] ), ] @@ -152,25 +168,29 @@ blackbox_fbx_tests = [ ), pytest.param( BlackboxAssetTest( - test_name= "MultipleMeshOneMaterial_RunAP_SuccessWithMatchingProducts", - asset_folder= "TwoMeshOneMaterial", + test_name="MultipleMeshOneMaterial_RunAP_SuccessWithMatchingProducts", + asset_folder="TwoMeshOneMaterial", scene_debug_file="multiple_mesh_one_material.dbgsg", - assets = [ + assets=[ asset_db_utils.DBSourceAsset( - source_file_name = "multiple_mesh_one_material.fbx", - uuid = b"597618fd497659a1b197a015fe47aa95", - jobs = [ + source_file_name="multiple_mesh_one_material.fbx", + uuid=b"597618fd497659a1b197a015fe47aa95", + jobs=[ asset_db_utils.DBJob( - job_key= "Scene compilation", + job_key="Scene compilation", builder_guid=b"bd8bf65894854fe3830e8ec3a23c35f3", status=4, error_count=0, warning_count=2, - products = [ + products=[ asset_db_utils.DBProduct( product_name='twomeshonematerial/multiple_mesh_one_material.dbgsg', sub_id=2077268018, - asset_type=b'07f289d14dc74c4094b40a53bbcb9f0b') + asset_type=b'07f289d14dc74c4094b40a53bbcb9f0b'), + asset_db_utils.DBProduct( + product_name='twomeshonematerial/multiple_mesh_one_material.dbgsg.xml', + sub_id=1321067730, + asset_type=b'51f376140d774f369ac67ed70a0ac868') ] ), ] @@ -183,26 +203,31 @@ blackbox_fbx_tests = [ pytest.param( BlackboxAssetTest( # Verifies whether multiple meshes can share linked materials - test_name= "MultipleMeshLinkedMaterials_RunAP_SuccessWithMatchingProducts", - asset_folder= "TwoMeshLinkedMaterials", - scene_debug_file= "multiple_mesh_linked_materials.dbgsg", - assets = [ + test_name="MultipleMeshLinkedMaterials_RunAP_SuccessWithMatchingProducts", + asset_folder="TwoMeshLinkedMaterials", + scene_debug_file="multiple_mesh_linked_materials.dbgsg", + assets=[ asset_db_utils.DBSourceAsset( - source_file_name = "multiple_mesh_linked_materials.fbx", - uuid = b"25d8301c2eef5dc7bded310db8ea608d", - jobs = [ + source_file_name="multiple_mesh_linked_materials.fbx", + uuid=b"25d8301c2eef5dc7bded310db8ea608d", + jobs=[ asset_db_utils.DBJob( - job_key= "Scene compilation", - platform= "pc", + job_key="Scene compilation", + platform="pc", builder_guid=b"bd8bf65894854fe3830e8ec3a23c35f3", status=4, error_count=0, warning_count=2, - products= [ + products=[ asset_db_utils.DBProduct( product_name='twomeshlinkedmaterials/multiple_mesh_linked_materials.dbgsg', sub_id=-1898461950, asset_type=b'07f289d14dc74c4094b40a53bbcb9f0b' + ), + asset_db_utils.DBProduct( + product_name='twomeshlinkedmaterials/multiple_mesh_linked_materials.dbgsg.xml', + sub_id=-772341513, + asset_type=b'51f376140d774f369ac67ed70a0ac868' ) ] ), @@ -216,26 +241,31 @@ blackbox_fbx_tests = [ pytest.param( BlackboxAssetTest( # Verifies a mesh with multiple materials - test_name= "SingleMeshMultipleMaterials_RunAP_SuccessWithMatchingProducts", - asset_folder= "OneMeshMultipleMaterials", + test_name="SingleMeshMultipleMaterials_RunAP_SuccessWithMatchingProducts", + asset_folder="OneMeshMultipleMaterials", scene_debug_file="single_mesh_multiple_materials.dbgsg", - assets = [ + assets=[ asset_db_utils.DBSourceAsset( - source_file_name = "single_mesh_multiple_materials.fbx", - uuid = b"f08fd585dfa35881b4bf86637da5e858", - jobs = [ + source_file_name="single_mesh_multiple_materials.fbx", + uuid=b"f08fd585dfa35881b4bf86637da5e858", + jobs=[ asset_db_utils.DBJob( - job_key= "Scene compilation", - platform= "pc", + job_key="Scene compilation", + platform="pc", builder_guid=b"bd8bf65894854fe3830e8ec3a23c35f3", status=4, error_count=0, warning_count=1, - products = [ + products=[ asset_db_utils.DBProduct( product_name='onemeshmultiplematerials/single_mesh_multiple_materials.dbgsg', sub_id=-262822238, - asset_type=b'07f289d14dc74c4094b40a53bbcb9f0b') + asset_type=b'07f289d14dc74c4094b40a53bbcb9f0b'), + asset_db_utils.DBProduct( + product_name='onemeshmultiplematerials/single_mesh_multiple_materials.dbgsg.xml', + sub_id=1462358160, + asset_type=b'51f376140d774f369ac67ed70a0ac868' + ) ] ), ] @@ -265,7 +295,12 @@ blackbox_fbx_tests = [ asset_db_utils.DBProduct( product_name='vertexcolor/vertexcolor.dbgsg', sub_id=-1543877170, - asset_type=b'07f289d14dc74c4094b40a53bbcb9f0b') + asset_type=b'07f289d14dc74c4094b40a53bbcb9f0b'), + asset_db_utils.DBProduct( + product_name='vertexcolor/vertexcolor.dbgsg.xml', + sub_id=1743516586, + asset_type=b'51f376140d774f369ac67ed70a0ac868' + ) ] ), ] @@ -277,25 +312,30 @@ blackbox_fbx_tests = [ ), pytest.param( BlackboxAssetTest( - test_name= "MotionTest_RunAP_SuccessWithMatchingProducts", - asset_folder= "Motion", + test_name="MotionTest_RunAP_SuccessWithMatchingProducts", + asset_folder="Motion", scene_debug_file="Jack_Idle_Aim_ZUp.dbgsg", - assets = [ + assets=[ asset_db_utils.DBSourceAsset( - source_file_name = "Jack_Idle_Aim_ZUp.fbx", - uuid = b"eda904ae0e145f8b973d57fc5809918b", - jobs = [ + source_file_name="Jack_Idle_Aim_ZUp.fbx", + uuid=b"eda904ae0e145f8b973d57fc5809918b", + jobs=[ asset_db_utils.DBJob( - job_key= "Scene compilation", + job_key="Scene compilation", builder_guid=b"bd8bf65894854fe3830e8ec3a23c35f3", status=4, error_count=0, warning_count=0, - products = [ + products=[ asset_db_utils.DBProduct( product_name='motion/jack_idle_aim_zup.dbgsg', sub_id=-517610290, asset_type=b'07f289d14dc74c4094b40a53bbcb9f0b'), + asset_db_utils.DBProduct( + product_name='motion/jack_idle_aim_zup.dbgsg.xml', + sub_id=-817863914, + asset_type=b'51f376140d774f369ac67ed70a0ac868' + ), asset_db_utils.DBProduct( product_name='motion/jack_idle_aim_zup.motion', sub_id=186392073, @@ -307,33 +347,69 @@ blackbox_fbx_tests = [ ] ), ), + pytest.param( + BlackboxAssetTest( + test_name="ShaderBall_RunAP_SuccessWithMatchingProducts", + asset_folder="ShaderBall", + scene_debug_file="shaderball.dbgsg", + assets=[ + asset_db_utils.DBSourceAsset( + source_file_name="shaderball.fbx", + uuid=b"48181ba8038e5193997540fc8dffb06d", + jobs=[ + asset_db_utils.DBJob( + job_key="Scene compilation", + builder_guid=b"bd8bf65894854fe3830e8ec3a23c35f3", + status=4, + error_count=0, + warning_count=30, + products=[ + asset_db_utils.DBProduct( + product_name='shaderball/shaderball.dbgsg', + sub_id=-1607815784, + asset_type=b'07f289d14dc74c4094b40a53bbcb9f0b'), + asset_db_utils.DBProduct( + product_name='shaderball/shaderball.dbgsg.xml', + sub_id=-1153118555, + asset_type=b'51f376140d774f369ac67ed70a0ac868'), + ] + ), + ] + ) + ] + ), + ), ] - blackbox_fbx_special_tests = [ pytest.param( BlackboxAssetTest( - test_name= "MultipleMeshMultipleMaterial_MultipleAssetInfo_RunAP_SuccessWithMatchingProducts", - asset_folder= "TwoMeshTwoMaterial", - override_asset_folder = "OverrideAssetInfoForTwoMeshTwoMaterial", + test_name="MultipleMeshMultipleMaterial_MultipleAssetInfo_RunAP_SuccessWithMatchingProducts", + asset_folder="TwoMeshTwoMaterial", + override_asset_folder="OverrideAssetInfoForTwoMeshTwoMaterial", scene_debug_file="multiple_mesh_multiple_material.dbgsg", override_scene_debug_file="multiple_mesh_multiple_material_override.dbgsg", - assets = [ + assets=[ asset_db_utils.DBSourceAsset( - source_file_name = "multiple_mesh_multiple_material.fbx", - uuid = b"b5915fb874af5c8a866ccabbddb57595", - jobs = [ + source_file_name="multiple_mesh_multiple_material.fbx", + uuid=b"b5915fb874af5c8a866ccabbddb57595", + jobs=[ asset_db_utils.DBJob( job_key="Scene compilation", builder_guid=b"bd8bf65894854fe3830e8ec3a23c35f3", status=4, error_count=0, warning_count=2, - products = [ + products=[ asset_db_utils.DBProduct( product_name='twomeshtwomaterial/multiple_mesh_multiple_material.dbgsg', sub_id=896980093, - asset_type=b'07f289d14dc74c4094b40a53bbcb9f0b') + asset_type=b'07f289d14dc74c4094b40a53bbcb9f0b'), + asset_db_utils.DBProduct( + product_name='twomeshtwomaterial/multiple_mesh_multiple_material.dbgsg.xml', + sub_id=-1556988544, + asset_type=b'51f376140d774f369ac67ed70a0ac868' + ) ] ), ] @@ -341,20 +417,25 @@ blackbox_fbx_special_tests = [ ], override_assets=[ asset_db_utils.DBSourceAsset( - source_file_name = "multiple_mesh_multiple_material.fbx", - uuid = b"b5915fb874af5c8a866ccabbddb57595", - jobs = [ + source_file_name="multiple_mesh_multiple_material.fbx", + uuid=b"b5915fb874af5c8a866ccabbddb57595", + jobs=[ asset_db_utils.DBJob( - job_key= "Scene compilation", + job_key="Scene compilation", builder_guid=b"bd8bf65894854fe3830e8ec3a23c35f3", status=4, error_count=0, warning_count=2, - products = [ + products=[ asset_db_utils.DBProduct( product_name='twomeshtwomaterial/multiple_mesh_multiple_material.dbgsg', sub_id=896980093, - asset_type=b'07f289d14dc74c4094b40a53bbcb9f0b') + asset_type=b'07f289d14dc74c4094b40a53bbcb9f0b'), + asset_db_utils.DBProduct( + product_name='twomeshtwomaterial/multiple_mesh_multiple_material.dbgsg.xml', + sub_id=-1556988544, + asset_type=b'51f376140d774f369ac67ed70a0ac868' + ) ] ), ] @@ -378,29 +459,26 @@ class TestsFBX_AllPlatforms(object): @pytest.mark.BAT @pytest.mark.SUITE_sandbox @pytest.mark.parametrize("blackbox_param", blackbox_fbx_tests) - def test_FBXBlackboxTest_SourceFiles_Processed_ResultInExpectedProducts(self, workspace, - ap_setup_fixture, asset_processor, project, - blackbox_param): + def test_FBXBlackboxTest_SourceFiles_Processed_ResultInExpectedProducts(self, workspace, ap_setup_fixture, + asset_processor, project, blackbox_param): """ - Please see run_fbx_test(...) for details + Please see run_fbx_test(...) for details Test Steps: 1. Determine if blackbox is set to none 2. Run FBX Test + """ if blackbox_param == None: return - self.run_fbx_test(workspace, ap_setup_fixture, - asset_processor, project, blackbox_param) + self.run_fbx_test(workspace, ap_setup_fixture, asset_processor, project, blackbox_param) @pytest.mark.BAT @pytest.mark.SUITE_sandbox @pytest.mark.parametrize("blackbox_param", blackbox_fbx_special_tests) - def test_FBXBlackboxTest_AssetInfoModified_AssetReprocessed_ResultInExpectedProducts(self, - workspace, ap_setup_fixture, - asset_processor, project, - blackbox_param): + def test_FBXBlackboxTest_AssetInfoModified_AssetReprocessed_ResultInExpectedProducts( + self, workspace, ap_setup_fixture, asset_processor, project, blackbox_param): """ Please see run_fbx_test(...) for details @@ -429,8 +507,21 @@ class TestsFBX_AllPlatforms(object): product.product_name = job.platform + "/" \ + product.product_name + def compare_scene_debug_file(self, asset_processor, expected_file_path, actual_file_path): + debug_graph_path = os.path.join(asset_processor.project_test_cache_folder(), actual_file_path) + expected_debug_graph_path = os.path.join(asset_processor.project_test_source_folder(), "SceneDebug", expected_file_path) + + logger.info(f"Parsing scene graph: {debug_graph_path}") + with open(debug_graph_path, "r") as scene_file: + actual_lines = scene_file.readlines() + + logger.info(f"Parsing scene graph: {expected_debug_graph_path}") + with open(expected_debug_graph_path, "r") as scene_file: + expected_lines = scene_file.readlines() + + assert utils.compare_lists(actual_lines, expected_lines), "Scene mismatch" def run_fbx_test(self, workspace, ap_setup_fixture, asset_processor, - project, blackbox_params: BlackboxAssetTest, overrideAsset = False): + project, blackbox_params: BlackboxAssetTest, overrideAsset=False): """ These tests work by having the test case ingest the test data and determine the run pattern. Tests will process scene settings files and will additionally do a verification against a provided debug file @@ -469,32 +560,27 @@ class TestsFBX_AllPlatforms(object): expected_product_list.append(expected_product.product_name) missing_assets, _ = utils.compare_assets_with_cache(expected_product_list, - asset_processor.project_test_cache_folder()) + asset_processor.project_test_cache_folder()) - assert not missing_assets, f'The following assets were expected to be in, but not found in cache: {str(missing_assets)}' + assert not missing_assets, \ + f'The following assets were expected to be in, but not found in cache: {str(missing_assets)}' # Load the asset database. db_path = os.path.join(asset_processor.temp_asset_root(), "Cache", "assetdb.sqlite") cache_root = os.path.dirname(os.path.join(asset_processor.temp_asset_root(), "Cache", - ASSET_PROCESSOR_PLATFORM_MAP[workspace.asset_processor_platform])) + ASSET_PROCESSOR_PLATFORM_MAP[workspace.asset_processor_platform])) if blackbox_params.scene_debug_file: - scene_debug_file = blackbox_params.override_scene_debug_file if overrideAsset\ + scene_debug_file = blackbox_params.override_scene_debug_file if overrideAsset \ else blackbox_params.scene_debug_file - debug_graph_path = os.path.join(asset_processor.project_test_cache_folder(), blackbox_params.scene_debug_file) - expected_debug_graph_path = os.path.join(asset_processor.project_test_source_folder(), scene_debug_file) + self.compare_scene_debug_file(asset_processor, scene_debug_file, blackbox_params.scene_debug_file) - logger.info(f"Parsing scene graph: {debug_graph_path}") - with open(debug_graph_path, "r") as scene_file: - actual_lines = scene_file.readlines() - - logger.info(f"Parsing scene graph: {expected_debug_graph_path}") - with open(expected_debug_graph_path, "r") as scene_file: - expected_lines = scene_file.readlines() - - assert utils.compare_lists(actual_lines, expected_lines), "Scene mismatch" + # Run again for the .dbgsg.xml file + self.compare_scene_debug_file(asset_processor, + scene_debug_file + ".xml", + blackbox_params.scene_debug_file + ".xml") # Check that each given source asset resulted in the expected jobs and products. self.populateAssetInfo(workspace, project, assetsToValidate) diff --git a/AutomatedTesting/Gem/PythonTests/editor_test_testing/TestSuite_Main.py b/AutomatedTesting/Gem/PythonTests/editor_test_testing/TestSuite_Main.py index 15ba6690a1..50b89ab138 100644 --- a/AutomatedTesting/Gem/PythonTests/editor_test_testing/TestSuite_Main.py +++ b/AutomatedTesting/Gem/PythonTests/editor_test_testing/TestSuite_Main.py @@ -15,6 +15,7 @@ import sys import importlib import re +import ly_test_tools from ly_test_tools import LAUNCHERS sys.path.append(os.path.dirname(os.path.abspath(__file__))) @@ -25,8 +26,15 @@ import ly_test_tools.environment.process_utils as process_utils import argparse, sys -@pytest.mark.SUITE_main -@pytest.mark.parametrize("launcher_platform", ['windows_editor']) +def get_editor_launcher_platform(): + if ly_test_tools.WINDOWS: + return "windows_editor" + elif ly_test_tools.LINUX: + return "linux_editor" + else: + return None + +@pytest.mark.parametrize("launcher_platform", [get_editor_launcher_platform()]) @pytest.mark.parametrize("project", ["AutomatedTesting"]) class TestEditorTest: @@ -69,7 +77,7 @@ class TestEditorTest: from ly_test_tools.o3de.editor_test import EditorSingleTest, EditorSharedTest, EditorTestSuite @pytest.mark.SUITE_main - @pytest.mark.parametrize("launcher_platform", ['windows_editor']) + @pytest.mark.parametrize("launcher_platform", [{get_editor_launcher_platform()}]) @pytest.mark.parametrize("project", ["AutomatedTesting"]) class TestAutomation(EditorTestSuite): class test_single(EditorSingleTest): @@ -123,7 +131,7 @@ class TestEditorTest: from ly_test_tools.o3de.editor_test import EditorSingleTest, EditorSharedTest, EditorTestSuite @pytest.mark.SUITE_main - @pytest.mark.parametrize("launcher_platform", ['windows_editor']) + @pytest.mark.parametrize("launcher_platform", [{get_editor_launcher_platform()}]) @pytest.mark.parametrize("project", ["AutomatedTesting"]) class TestAutomation(EditorTestSuite): {module_class_code} diff --git a/AutomatedTesting/Gem/PythonTests/smoke/test_CLITool_AzTestRunner_Works.py b/AutomatedTesting/Gem/PythonTests/smoke/test_CLITool_AzTestRunner_Works.py index b269c9e131..df755d5d11 100644 --- a/AutomatedTesting/Gem/PythonTests/smoke/test_CLITool_AzTestRunner_Works.py +++ b/AutomatedTesting/Gem/PythonTests/smoke/test_CLITool_AzTestRunner_Works.py @@ -13,18 +13,26 @@ import os import pytest import subprocess +import ly_test_tools + @pytest.mark.SUITE_smoke class TestCLIToolAzTestRunnerWorks(object): - def test_CLITool_AzTestRunner_Works(self, build_directory): + def test_CLITool_AzTestRunner_ListSelfTests(self, build_directory): file_path = os.path.join(build_directory, "AzTestRunner") help_message = "OKAY Symbol found: AzRunUnitTests" - # Launch AzTestRunner + + if ly_test_tools.WINDOWS: + target_lib = "AzTestRunner.Tests" + else: + target_lib = "libAzTestRunner.Tests" + + # Launch AzTestRunner, load self-tests, print test names output = subprocess.run( - [file_path, "AzTestRunner.Tests", "AzRunUnitTests", "--gtest_list_tests"], capture_output=True, timeout=10 + [file_path, target_lib, "AzRunUnitTests", "--gtest_list_tests"], capture_output=True, timeout=10 ) assert ( len(output.stderr) == 0 and output.returncode == 0 ), f"Error occurred while launching {file_path}: {output.stderr}" # Verify help message - assert help_message in str(output.stdout), f"Help Message: {help_message} is not present" + assert help_message in str(output.stdout), f"Help Message: '{help_message}' unexpectedly not present" diff --git a/AutomatedTesting/Levels/Base/Base.prefab b/AutomatedTesting/Levels/Base/Base.prefab new file mode 100644 index 0000000000..98495663b7 --- /dev/null +++ b/AutomatedTesting/Levels/Base/Base.prefab @@ -0,0 +1,53 @@ +{ + "ContainerEntity": { + "Id": "ContainerEntity", + "Name": "Base", + "Components": { + "Component_[10182366347512475253]": { + "$type": "EditorPrefabComponent", + "Id": 10182366347512475253 + }, + "Component_[12917798267488243668]": { + "$type": "EditorPendingCompositionComponent", + "Id": 12917798267488243668 + }, + "Component_[3261249813163778338]": { + "$type": "EditorOnlyEntityComponent", + "Id": 3261249813163778338 + }, + "Component_[3837204912784440039]": { + "$type": "EditorDisabledCompositionComponent", + "Id": 3837204912784440039 + }, + "Component_[4272963378099646759]": { + "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent", + "Id": 4272963378099646759, + "Parent Entity": "" + }, + "Component_[4848458548047175816]": { + "$type": "EditorVisibilityComponent", + "Id": 4848458548047175816 + }, + "Component_[5787060997243919943]": { + "$type": "EditorInspectorComponent", + "Id": 5787060997243919943 + }, + "Component_[7804170251266531779]": { + "$type": "EditorLockComponent", + "Id": 7804170251266531779 + }, + "Component_[7874177159288365422]": { + "$type": "EditorEntitySortComponent", + "Id": 7874177159288365422 + }, + "Component_[8018146290632383969]": { + "$type": "EditorEntityIconComponent", + "Id": 8018146290632383969 + }, + "Component_[8452360690590857075]": { + "$type": "SelectionComponent", + "Id": 8452360690590857075 + } + } + } +} \ No newline at end of file diff --git a/AutomatedTesting/Levels/Multiplayer/AutoComponent_NetworkInput/AutoComponent_NetworkInput.prefab b/AutomatedTesting/Levels/Multiplayer/AutoComponent_NetworkInput/AutoComponent_NetworkInput.prefab new file mode 100644 index 0000000000..78c88144fd --- /dev/null +++ b/AutomatedTesting/Levels/Multiplayer/AutoComponent_NetworkInput/AutoComponent_NetworkInput.prefab @@ -0,0 +1,525 @@ +{ + "ContainerEntity": { + "Id": "Entity_[1146574390643]", + "Name": "Level", + "Components": { + "Component_[10641544592923449938]": { + "$type": "EditorInspectorComponent", + "Id": 10641544592923449938 + }, + "Component_[12039882709170782873]": { + "$type": "EditorOnlyEntityComponent", + "Id": 12039882709170782873 + }, + "Component_[12265484671603697631]": { + "$type": "EditorPendingCompositionComponent", + "Id": 12265484671603697631 + }, + "Component_[14126657869720434043]": { + "$type": "EditorEntitySortComponent", + "Id": 14126657869720434043 + }, + "Component_[15230859088967841193]": { + "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent", + "Id": 15230859088967841193, + "Parent Entity": "" + }, + "Component_[16239496886950819870]": { + "$type": "EditorDisabledCompositionComponent", + "Id": 16239496886950819870 + }, + "Component_[5688118765544765547]": { + "$type": "EditorEntityIconComponent", + "Id": 5688118765544765547 + }, + "Component_[6545738857812235305]": { + "$type": "SelectionComponent", + "Id": 6545738857812235305 + }, + "Component_[7247035804068349658]": { + "$type": "EditorPrefabComponent", + "Id": 7247035804068349658 + }, + "Component_[9307224322037797205]": { + "$type": "EditorLockComponent", + "Id": 9307224322037797205 + }, + "Component_[9562516168917670048]": { + "$type": "EditorVisibilityComponent", + "Id": 9562516168917670048 + } + } + }, + "Entities": { + "Entity_[1155164325235]": { + "Id": "Entity_[1155164325235]", + "Name": "Sun", + "Components": { + "Component_[10440557478882592717]": { + "$type": "SelectionComponent", + "Id": 10440557478882592717 + }, + "Component_[13620450453324765907]": { + "$type": "EditorLockComponent", + "Id": 13620450453324765907 + }, + "Component_[2134313378593666258]": { + "$type": "EditorInspectorComponent", + "Id": 2134313378593666258 + }, + "Component_[234010807770404186]": { + "$type": "EditorVisibilityComponent", + "Id": 234010807770404186 + }, + "Component_[2970359110423865725]": { + "$type": "EditorEntityIconComponent", + "Id": 2970359110423865725 + }, + "Component_[3722854130373041803]": { + "$type": "EditorOnlyEntityComponent", + "Id": 3722854130373041803 + }, + "Component_[5992533738676323195]": { + "$type": "EditorDisabledCompositionComponent", + "Id": 5992533738676323195 + }, + "Component_[7378860763541895402]": { + "$type": "AZ::Render::EditorDirectionalLightComponent", + "Id": 7378860763541895402, + "Controller": { + "Configuration": { + "Intensity": 1.0, + "CameraEntityId": "", + "ShadowFilterMethod": 1 + } + } + }, + "Component_[7892834440890947578]": { + "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent", + "Id": 7892834440890947578, + "Parent Entity": "Entity_[1176639161715]", + "Transform Data": { + "Translate": [ + 0.0, + 0.0, + 13.487043380737305 + ], + "Rotate": [ + -76.13099670410156, + -0.847000002861023, + -15.8100004196167 + ] + } + }, + "Component_[8599729549570828259]": { + "$type": "EditorEntitySortComponent", + "Id": 8599729549570828259 + }, + "Component_[952797371922080273]": { + "$type": "EditorPendingCompositionComponent", + "Id": 952797371922080273 + } + } + }, + "Entity_[1159459292531]": { + "Id": "Entity_[1159459292531]", + "Name": "Ground", + "Components": { + "Component_[11701138785793981042]": { + "$type": "SelectionComponent", + "Id": 11701138785793981042 + }, + "Component_[12260880513256986252]": { + "$type": "EditorEntityIconComponent", + "Id": 12260880513256986252 + }, + "Component_[13711420870643673468]": { + "$type": "EditorDisabledCompositionComponent", + "Id": 13711420870643673468 + }, + "Component_[138002849734991713]": { + "$type": "EditorOnlyEntityComponent", + "Id": 138002849734991713 + }, + "Component_[16578565737331764849]": { + "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent", + "Id": 16578565737331764849, + "Parent Entity": "Entity_[1176639161715]" + }, + "Component_[16919232076966545697]": { + "$type": "EditorInspectorComponent", + "Id": 16919232076966545697 + }, + "Component_[5182430712893438093]": { + "$type": "EditorMaterialComponent", + "Id": 5182430712893438093, + "materialSlots": [ + { + "id": { + "materialSlotStableId": 803645540 + } + }, + { + "id": { + "materialSlotStableId": 803645540 + } + } + ], + "materialSlotsByLod": [ + [ + { + "id": { + "lodIndex": 0, + "materialSlotStableId": 803645540 + } + } + ], + [ + { + "id": { + "lodIndex": 0, + "materialSlotStableId": 803645540 + } + } + ] + ] + }, + "Component_[5675108321710651991]": { + "$type": "AZ::Render::EditorMeshComponent", + "Id": 5675108321710651991, + "Controller": { + "Configuration": { + "ModelAsset": { + "assetId": { + "guid": "{0CD745C0-6AA8-569A-A68A-73A3270986C4}", + "subId": 277889906 + }, + "assetHint": "objects/groudplane/groundplane_512x512m.azmodel" + } + } + } + }, + "Component_[5681893399601237518]": { + "$type": "EditorEntitySortComponent", + "Id": 5681893399601237518 + }, + "Component_[592692962543397545]": { + "$type": "EditorPendingCompositionComponent", + "Id": 592692962543397545 + }, + "Component_[7090012899106946164]": { + "$type": "EditorLockComponent", + "Id": 7090012899106946164 + }, + "Component_[9410832619875640998]": { + "$type": "EditorVisibilityComponent", + "Id": 9410832619875640998 + } + } + }, + "Entity_[1163754259827]": { + "Id": "Entity_[1163754259827]", + "Name": "Camera", + "Components": { + "Component_[11895140916889160460]": { + "$type": "EditorEntityIconComponent", + "Id": 11895140916889160460 + }, + "Component_[16880285896855930892]": { + "$type": "{CA11DA46-29FF-4083-B5F6-E02C3A8C3A3D} EditorCameraComponent", + "Id": 16880285896855930892, + "Controller": { + "Configuration": { + "Field of View": 55.0, + "EditorEntityId": 12554887233631987164 + } + } + }, + "Component_[17187464423780271193]": { + "$type": "EditorLockComponent", + "Id": 17187464423780271193 + }, + "Component_[17495696818315413311]": { + "$type": "EditorEntitySortComponent", + "Id": 17495696818315413311 + }, + "Component_[18086214374043522055]": { + "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent", + "Id": 18086214374043522055, + "Parent Entity": "Entity_[1176639161715]", + "Transform Data": { + "Translate": [ + -2.3000001907348633, + -3.9368600845336914, + 1.0 + ], + "Rotate": [ + -2.050307512283325, + 1.9552897214889526, + -43.623355865478516 + ] + } + }, + "Component_[18387556550380114975]": { + "$type": "SelectionComponent", + "Id": 18387556550380114975 + }, + "Component_[2654521436129313160]": { + "$type": "EditorVisibilityComponent", + "Id": 2654521436129313160 + }, + "Component_[5265045084611556958]": { + "$type": "EditorDisabledCompositionComponent", + "Id": 5265045084611556958 + }, + "Component_[7169798125182238623]": { + "$type": "EditorPendingCompositionComponent", + "Id": 7169798125182238623 + }, + "Component_[7255796294953281766]": { + "$type": "GenericComponentWrapper", + "Id": 7255796294953281766, + "m_template": { + "$type": "FlyCameraInputComponent" + } + }, + "Component_[8866210352157164042]": { + "$type": "EditorInspectorComponent", + "Id": 8866210352157164042 + }, + "Component_[9129253381063760879]": { + "$type": "EditorOnlyEntityComponent", + "Id": 9129253381063760879 + } + } + }, + "Entity_[1168049227123]": { + "Id": "Entity_[1168049227123]", + "Name": "Grid", + "Components": { + "Component_[11443347433215807130]": { + "$type": "EditorEntityIconComponent", + "Id": 11443347433215807130 + }, + "Component_[11779275529534764488]": { + "$type": "SelectionComponent", + "Id": 11779275529534764488 + }, + "Component_[14249419413039427459]": { + "$type": "EditorInspectorComponent", + "Id": 14249419413039427459 + }, + "Component_[15448581635946161318]": { + "$type": "AZ::Render::EditorGridComponent", + "Id": 15448581635946161318, + "Controller": { + "Configuration": { + "primarySpacing": 4.0, + "primaryColor": [ + 0.501960813999176, + 0.501960813999176, + 0.501960813999176 + ], + "secondarySpacing": 0.5, + "secondaryColor": [ + 0.250980406999588, + 0.250980406999588, + 0.250980406999588 + ] + } + } + }, + "Component_[1843303322527297409]": { + "$type": "EditorDisabledCompositionComponent", + "Id": 1843303322527297409 + }, + "Component_[380249072065273654]": { + "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent", + "Id": 380249072065273654, + "Parent Entity": "Entity_[1176639161715]" + }, + "Component_[7476660583684339787]": { + "$type": "EditorPendingCompositionComponent", + "Id": 7476660583684339787 + }, + "Component_[7557626501215118375]": { + "$type": "EditorEntitySortComponent", + "Id": 7557626501215118375 + }, + "Component_[7984048488947365511]": { + "$type": "EditorVisibilityComponent", + "Id": 7984048488947365511 + }, + "Component_[8118181039276487398]": { + "$type": "EditorOnlyEntityComponent", + "Id": 8118181039276487398 + }, + "Component_[9189909764215270515]": { + "$type": "EditorLockComponent", + "Id": 9189909764215270515 + } + } + }, + "Entity_[1176639161715]": { + "Id": "Entity_[1176639161715]", + "Name": "Atom Default Environment", + "Components": { + "Component_[10757302973393310045]": { + "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent", + "Id": 10757302973393310045, + "Parent Entity": "Entity_[1146574390643]" + }, + "Component_[14505817420424255464]": { + "$type": "EditorInspectorComponent", + "Id": 14505817420424255464, + "ComponentOrderEntryArray": [ + { + "ComponentId": 10757302973393310045 + } + ] + }, + "Component_[14988041764659020032]": { + "$type": "EditorLockComponent", + "Id": 14988041764659020032 + }, + "Component_[15808690248755038124]": { + "$type": "SelectionComponent", + "Id": 15808690248755038124 + }, + "Component_[15900837685796817138]": { + "$type": "EditorVisibilityComponent", + "Id": 15900837685796817138 + }, + "Component_[3298767348226484884]": { + "$type": "EditorOnlyEntityComponent", + "Id": 3298767348226484884 + }, + "Component_[4076975109609220594]": { + "$type": "EditorPendingCompositionComponent", + "Id": 4076975109609220594 + }, + "Component_[5679760548946028854]": { + "$type": "EditorDisabledCompositionComponent", + "Id": 5679760548946028854 + }, + "Component_[5855590796136709437]": { + "$type": "EditorEntitySortComponent", + "Id": 5855590796136709437, + "ChildEntityOrderEntryArray": [ + { + "EntityId": "Entity_[1155164325235]" + }, + { + "EntityId": "Entity_[1180934129011]", + "SortIndex": 1 + }, + { + "EntityId": "", + "SortIndex": 2 + }, + { + "EntityId": "Entity_[1168049227123]", + "SortIndex": 3 + }, + { + "EntityId": "Entity_[1163754259827]", + "SortIndex": 4 + }, + { + "EntityId": "Entity_[1159459292531]", + "SortIndex": 5 + } + ] + }, + "Component_[9277695270015777859]": { + "$type": "EditorEntityIconComponent", + "Id": 9277695270015777859 + } + } + }, + "Entity_[1180934129011]": { + "Id": "Entity_[1180934129011]", + "Name": "Global Sky", + "Components": { + "Component_[11231930600558681245]": { + "$type": "AZ::Render::EditorHDRiSkyboxComponent", + "Id": 11231930600558681245, + "Controller": { + "Configuration": { + "CubemapAsset": { + "assetId": { + "guid": "{215E47FD-D181-5832-B1AB-91673ABF6399}", + "subId": 1000 + }, + "assetHint": "lightingpresets/highcontrast/goegap_4k_skyboxcm.exr.streamingimage" + } + } + } + }, + "Component_[11980494120202836095]": { + "$type": "SelectionComponent", + "Id": 11980494120202836095 + }, + "Component_[1428633914413949476]": { + "$type": "EditorLockComponent", + "Id": 1428633914413949476 + }, + "Component_[14936200426671614999]": { + "$type": "AZ::Render::EditorImageBasedLightComponent", + "Id": 14936200426671614999, + "Controller": { + "Configuration": { + "diffuseImageAsset": { + "assetId": { + "guid": "{3FD09945-D0F2-55C8-B9AF-B2FD421FE3BE}", + "subId": 3000 + }, + "assetHint": "lightingpresets/highcontrast/goegap_4k_iblglobalcm_ibldiffuse.exr.streamingimage" + }, + "specularImageAsset": { + "assetId": { + "guid": "{3FD09945-D0F2-55C8-B9AF-B2FD421FE3BE}", + "subId": 2000 + }, + "assetHint": "lightingpresets/highcontrast/goegap_4k_iblglobalcm_iblspecular.exr.streamingimage" + } + } + } + }, + "Component_[14994774102579326069]": { + "$type": "EditorDisabledCompositionComponent", + "Id": 14994774102579326069 + }, + "Component_[15417479889044493340]": { + "$type": "EditorPendingCompositionComponent", + "Id": 15417479889044493340 + }, + "Component_[15826613364991382688]": { + "$type": "EditorEntitySortComponent", + "Id": 15826613364991382688 + }, + "Component_[1665003113283562343]": { + "$type": "EditorOnlyEntityComponent", + "Id": 1665003113283562343 + }, + "Component_[3704934735944502280]": { + "$type": "EditorEntityIconComponent", + "Id": 3704934735944502280 + }, + "Component_[5698542331457326479]": { + "$type": "EditorVisibilityComponent", + "Id": 5698542331457326479 + }, + "Component_[6644513399057217122]": { + "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent", + "Id": 6644513399057217122, + "Parent Entity": "Entity_[1176639161715]" + }, + "Component_[931091830724002070]": { + "$type": "EditorInspectorComponent", + "Id": 931091830724002070 + } + } + } + } +} \ No newline at end of file diff --git a/AutomatedTesting/Levels/Multiplayer/AutoComponent_NetworkInput/AutoComponent_NetworkInput.scriptcanvas b/AutomatedTesting/Levels/Multiplayer/AutoComponent_NetworkInput/AutoComponent_NetworkInput.scriptcanvas new file mode 100644 index 0000000000..2f8a434108 --- /dev/null +++ b/AutomatedTesting/Levels/Multiplayer/AutoComponent_NetworkInput/AutoComponent_NetworkInput.scriptcanvas @@ -0,0 +1,1827 @@ +{ + "Type": "JsonSerialization", + "Version": 1, + "ClassName": "ScriptCanvasData", + "ClassData": { + "m_scriptCanvas": { + "Id": { + "id": 20239954977260 + }, + "Name": "AutoComponent_NetworkInput", + "Components": { + "Component_[14059414856740480991]": { + "$type": "EditorGraphVariableManagerComponent", + "Id": 14059414856740480991, + "m_variableData": { + "m_nameVariableMap": [ + { + "Key": { + "m_id": "{720D213F-AA7A-48B0-ABBB-A3756C528CF9}" + }, + "Value": { + "Datum": { + "scriptCanvasType": { + "m_type": 3 + }, + "isNullPointer": false, + "$type": "double", + "value": 0.25 + }, + "VariableId": { + "m_id": "{720D213F-AA7A-48B0-ABBB-A3756C528CF9}" + }, + "VariableName": "leftright" + } + }, + { + "Key": { + "m_id": "{B2E338E9-2ACB-42FC-B0BE-EDA14A8A86AD}" + }, + "Value": { + "Datum": { + "scriptCanvasType": { + "m_type": 3 + }, + "isNullPointer": false, + "$type": "double", + "value": 1.0 + }, + "VariableId": { + "m_id": "{B2E338E9-2ACB-42FC-B0BE-EDA14A8A86AD}" + }, + "VariableName": "fwdback" + } + }, + { + "Key": { + "m_id": "{D843B13F-B3C7-4619-8092-7164EB1D7888}" + }, + "Value": { + "Datum": { + "scriptCanvasType": { + "m_type": 3 + }, + "isNullPointer": false, + "$type": "double", + "value": 20.0 + }, + "VariableId": { + "m_id": "{D843B13F-B3C7-4619-8092-7164EB1D7888}" + }, + "VariableName": "VELOCITY" + } + } + ] + } + }, + "Component_[642525765775040231]": { + "$type": "{4D755CA9-AB92-462C-B24F-0B3376F19967} Graph", + "Id": 642525765775040231, + "m_graphData": { + "m_nodes": [ + { + "Id": { + "id": 20265724781036 + }, + "Name": "SC-Node(NotEqualTo)", + "Components": { + "Component_[12036973200504716538]": { + "$type": "NotEqualTo", + "Id": 12036973200504716538, + "Slots": [ + { + "id": { + "m_id": "{6D0EF497-7C52-432B-9301-7BAB26F4F5A5}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Result", + "DisplayDataType": { + "m_type": 0 + }, + "Descriptor": { + "ConnectionType": 2, + "SlotType": 2 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{AE7E453C-15DE-47D2-954A-05C3895B7AC6}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "In", + "toolTip": "Signal to perform the evaluation when desired.", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{AC364E17-A9A1-42DB-A29D-7B2D666E4287}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "True", + "toolTip": "Signaled if the result of the operation is true.", + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{0754AC37-61A6-42A7-B4EC-D35D18D27960}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "False", + "toolTip": "Signaled if the result of the operation is false.", + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{57EBC15B-452E-49B3-8BD3-4FBBB07F1F14}" + }, + "DynamicTypeOverride": 3, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Value A", + "DisplayDataType": { + "m_type": 3 + }, + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DynamicGroup": { + "Value": 3545012108 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{21864AEE-7954-4F68-82C6-573C175724EF}" + }, + "DynamicTypeOverride": 3, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Value B", + "DisplayDataType": { + "m_type": 3 + }, + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DynamicGroup": { + "Value": 3545012108 + }, + "DataType": 1, + "IsReference": true, + "VariableReference": { + "m_id": "{720D213F-AA7A-48B0-ABBB-A3756C528CF9}" + } + } + ], + "Datums": [ + { + "scriptCanvasType": { + "m_type": 3 + }, + "isNullPointer": false, + "$type": "double", + "value": 0.0, + "label": "Value A" + }, + { + "scriptCanvasType": { + "m_type": 3 + }, + "isNullPointer": false, + "$type": "double", + "value": 0.0, + "label": "Value B" + } + ] + } + } + }, + { + "Id": { + "id": 20257134846444 + }, + "Name": "SC-Node(NotEqualTo)", + "Components": { + "Component_[12036973200504716538]": { + "$type": "NotEqualTo", + "Id": 12036973200504716538, + "Slots": [ + { + "id": { + "m_id": "{6D0EF497-7C52-432B-9301-7BAB26F4F5A5}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Result", + "DisplayDataType": { + "m_type": 0 + }, + "Descriptor": { + "ConnectionType": 2, + "SlotType": 2 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{AE7E453C-15DE-47D2-954A-05C3895B7AC6}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "In", + "toolTip": "Signal to perform the evaluation when desired.", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{AC364E17-A9A1-42DB-A29D-7B2D666E4287}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "True", + "toolTip": "Signaled if the result of the operation is true.", + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{0754AC37-61A6-42A7-B4EC-D35D18D27960}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "False", + "toolTip": "Signaled if the result of the operation is false.", + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{57EBC15B-452E-49B3-8BD3-4FBBB07F1F14}" + }, + "DynamicTypeOverride": 3, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Value A", + "DisplayDataType": { + "m_type": 3 + }, + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DynamicGroup": { + "Value": 3545012108 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{21864AEE-7954-4F68-82C6-573C175724EF}" + }, + "DynamicTypeOverride": 3, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Value B", + "DisplayDataType": { + "m_type": 3 + }, + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DynamicGroup": { + "Value": 3545012108 + }, + "DataType": 1, + "IsReference": true, + "VariableReference": { + "m_id": "{B2E338E9-2ACB-42FC-B0BE-EDA14A8A86AD}" + } + } + ], + "Datums": [ + { + "scriptCanvasType": { + "m_type": 3 + }, + "isNullPointer": false, + "$type": "double", + "value": 0.0, + "label": "Value A" + }, + { + "scriptCanvasType": { + "m_type": 3 + }, + "isNullPointer": false, + "$type": "double", + "value": 0.0, + "label": "Value B" + } + ] + } + } + }, + { + "Id": { + "id": 20278609682924 + }, + "Name": "SC-Node(Print)", + "Components": { + "Component_[12996217042650310160]": { + "$type": "Print", + "Id": 12996217042650310160, + "Slots": [ + { + "id": { + "m_id": "{D994D58A-DBF1-4929-B779-F0D2CBAD2F0D}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "In", + "toolTip": "Input signal", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{233C15BC-0186-4A7F-B272-6B2C020DE57A}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Out", + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + } + } + ], + "m_format": "AutoComponent_NetworkInput ProcessInput called!", + "m_unresolvedString": [ + "AutoComponent_NetworkInput ProcessInput called!" + ] + } + } + }, + { + "Id": { + "id": 20244249944556 + }, + "Name": "SC-Node(CreateFromValues)", + "Components": { + "Component_[16858161274259468878]": { + "$type": "{E42861BD-1956-45AE-8DD7-CCFC1E3E5ACF} Method", + "Id": 16858161274259468878, + "Slots": [ + { + "id": { + "m_id": "{7CF3B6C0-FE59-4E16-ABC6-E7CC8AFA8EF3}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "fwdBack", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DataType": 1, + "IsReference": true, + "VariableReference": { + "m_id": "{B2E338E9-2ACB-42FC-B0BE-EDA14A8A86AD}" + } + }, + { + "id": { + "m_id": "{02473A8A-A0F0-4E73-8999-0B16D17E8E2A}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "leftRight", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DataType": 1, + "IsReference": true, + "VariableReference": { + "m_id": "{720D213F-AA7A-48B0-ABBB-A3756C528CF9}" + } + }, + { + "id": { + "m_id": "{514CDDAA-290F-4758-B28F-4003E719E635}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "In", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{E3A94716-C880-4D6D-ADD6-34D019161ED1}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Out", + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{90E52F81-54E0-4C63-9881-B661FB5D87D1}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Result: NetworkTestPlayerComponentNetworkInput", + "DisplayDataType": { + "m_type": 4, + "m_azType": "{12A1776B-61F6-4E5F-356A-AD718A62051F}" + }, + "Descriptor": { + "ConnectionType": 2, + "SlotType": 2 + }, + "DataType": 1 + } + ], + "Datums": [ + { + "scriptCanvasType": { + "m_type": 3 + }, + "isNullPointer": false, + "$type": "double", + "value": 0.0, + "label": "fwdBack" + }, + { + "scriptCanvasType": { + "m_type": 3 + }, + "isNullPointer": false, + "$type": "double", + "value": 0.0, + "label": "leftRight" + } + ], + "methodType": 2, + "methodName": "CreateFromValues", + "className": "NetworkTestPlayerComponentNetworkInput", + "resultSlotIDs": [ + {} + ], + "inputSlots": [ + { + "m_id": "{7CF3B6C0-FE59-4E16-ABC6-E7CC8AFA8EF3}" + }, + { + "m_id": "{02473A8A-A0F0-4E73-8999-0B16D17E8E2A}" + } + ], + "prettyClassName": "NetworkTestPlayerComponentNetworkInput" + } + } + }, + { + "Id": { + "id": 20252839879148 + }, + "Name": "EBusEventHandler", + "Components": { + "Component_[1734984895901771768]": { + "$type": "EBusEventHandler", + "Id": 1734984895901771768, + "Slots": [ + { + "id": { + "m_id": "{41899533-66B3-423F-8BE2-A624A1FB6FCB}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Connect", + "toolTip": "Connect this event handler to the specified entity.", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{65C09819-1B00-4BA8-B6E0-8DD6D8B44A36}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Disconnect", + "toolTip": "Disconnect this event handler.", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{B7437693-34D0-4BDE-8516-2CADB9F509BF}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "OnConnected", + "toolTip": "Signaled when a connection has taken place.", + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{BCE36ED4-0899-4403-826A-FE89BE033A0A}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "OnDisconnected", + "toolTip": "Signaled when this event handler is disconnected.", + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{796993F4-685D-4A85-A0CA-032E3466FB57}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "OnFailure", + "toolTip": "Signaled when it is not possible to connect this handler.", + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{680E9668-87B4-4FF2-AA24-EB8F11352A49}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Source", + "toolTip": "ID used to connect on a specific Event address (Type: EntityId)", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{ADF9B366-8324-4C1E-B601-C059DA70FDDE}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Result: NetworkTestPlayerComponentNetworkInput", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{3301102C-AD42-48A0-8764-D2D4D31E0C75}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Number", + "DisplayDataType": { + "m_type": 3 + }, + "Descriptor": { + "ConnectionType": 2, + "SlotType": 2 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{B831AC60-7641-4B74-9829-26A3576B4766}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "ExecutionSlot:CreateInput", + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + }, + "IsLatent": true + }, + { + "id": { + "m_id": "{8F69FA2E-28D8-4DF1-A4B5-AEF3985095C5}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "NetworkTestPlayerComponentNetworkInput", + "DisplayDataType": { + "m_type": 4, + "m_azType": "{12A1776B-61F6-4E5F-356A-AD718A62051F}" + }, + "Descriptor": { + "ConnectionType": 2, + "SlotType": 2 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{BE3E3F84-E91C-4F6C-B148-E13BF01B5FBC}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Number", + "DisplayDataType": { + "m_type": 3 + }, + "Descriptor": { + "ConnectionType": 2, + "SlotType": 2 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{4C8F2908-12B0-4C35-8468-31D3D4DF36AA}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "ExecutionSlot:ProcessInput", + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + }, + "IsLatent": true + } + ], + "Datums": [ + { + "scriptCanvasType": { + "m_type": 1 + }, + "isNullPointer": false, + "$type": "EntityId", + "value": { + "id": 2901262558 + }, + "label": "Source" + }, + { + "scriptCanvasType": { + "m_type": 4, + "m_azType": "{12A1776B-61F6-4E5F-356A-AD718A62051F}" + }, + "isNullPointer": false, + "$type": "NetworkTestPlayerComponentNetworkInput", + "label": "Result: NetworkTestPlayerComponentNetworkInput" + } + ], + "m_eventMap": [ + { + "Key": { + "Value": 78438309 + }, + "Value": { + "m_eventName": "CreateInput", + "m_eventId": { + "Value": 78438309 + }, + "m_eventSlotId": { + "m_id": "{B831AC60-7641-4B74-9829-26A3576B4766}" + }, + "m_resultSlotId": { + "m_id": "{ADF9B366-8324-4C1E-B601-C059DA70FDDE}" + }, + "m_parameterSlotIds": [ + { + "m_id": "{3301102C-AD42-48A0-8764-D2D4D31E0C75}" + } + ], + "m_numExpectedArguments": 1 + } + }, + { + "Key": { + "Value": 1793364217 + }, + "Value": { + "m_eventName": "ProcessInput", + "m_eventId": { + "Value": 1793364217 + }, + "m_eventSlotId": { + "m_id": "{4C8F2908-12B0-4C35-8468-31D3D4DF36AA}" + }, + "m_parameterSlotIds": [ + { + "m_id": "{8F69FA2E-28D8-4DF1-A4B5-AEF3985095C5}" + }, + { + "m_id": "{BE3E3F84-E91C-4F6C-B148-E13BF01B5FBC}" + } + ], + "m_numExpectedArguments": 2 + } + } + ], + "m_ebusName": "NetworkTestPlayerComponentBusHandler", + "m_busId": { + "Value": 3690077280 + } + } + } + }, + { + "Id": { + "id": 20248544911852 + }, + "Name": "SC-Node(ExtractProperty)", + "Components": { + "Component_[2395597035843331661]": { + "$type": "ExtractProperty", + "Id": 2395597035843331661, + "Slots": [ + { + "id": { + "m_id": "{C80C50EE-F216-4F44-B107-6B35354AFD52}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "In", + "toolTip": "When signaled assigns property values using the supplied source input", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{C69C098D-D667-4DC7-85E5-AFD119727D94}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Out", + "toolTip": "Signaled after all property haves have been pushed to the output slots", + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{D387C800-352B-4B01-8765-4F4B40DF45CB}" + }, + "DynamicTypeOverride": 1, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Source", + "toolTip": "The value on which to extract properties from.", + "DisplayDataType": { + "m_type": 4, + "m_azType": "{12A1776B-61F6-4E5F-356A-AD718A62051F}" + }, + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{0C03D491-DE25-46C2-BF09-14769FA49FDB}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "FwdBack: Number", + "DisplayDataType": { + "m_type": 3 + }, + "Descriptor": { + "ConnectionType": 2, + "SlotType": 2 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{4C13F9EF-60BF-4AD1-8FA9-66F46455411C}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "LeftRight: Number", + "DisplayDataType": { + "m_type": 3 + }, + "Descriptor": { + "ConnectionType": 2, + "SlotType": 2 + }, + "DataType": 1 + } + ], + "Datums": [ + { + "scriptCanvasType": { + "m_type": 4, + "m_azType": "{12A1776B-61F6-4E5F-356A-AD718A62051F}" + }, + "isNullPointer": false, + "$type": "NetworkTestPlayerComponentNetworkInput", + "label": "Source" + } + ], + "m_dataType": { + "m_type": 4, + "m_azType": "{12A1776B-61F6-4E5F-356A-AD718A62051F}" + }, + "m_propertyAccounts": [ + { + "m_propertySlotId": { + "m_id": "{0C03D491-DE25-46C2-BF09-14769FA49FDB}" + }, + "m_propertyType": { + "m_type": 3 + }, + "m_propertyName": "FwdBack" + }, + { + "m_propertySlotId": { + "m_id": "{4C13F9EF-60BF-4AD1-8FA9-66F46455411C}" + }, + "m_propertyType": { + "m_type": 3 + }, + "m_propertyName": "LeftRight" + } + ] + } + } + }, + { + "Id": { + "id": 20270019748332 + }, + "Name": "SC-Node(Print)", + "Components": { + "Component_[7568030783460446634]": { + "$type": "Print", + "Id": 7568030783460446634, + "Slots": [ + { + "id": { + "m_id": "{733AA75D-022C-45E9-9D0F-3EF9A1633ADC}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "In", + "toolTip": "Input signal", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{2E77B0FC-DE56-4D78-9A59-152B7A0666F3}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Out", + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + } + } + ], + "m_format": "AutoComponent_NetworkInput received bad fwdback!", + "m_unresolvedString": [ + "AutoComponent_NetworkInput received bad fwdback!" + ] + } + } + }, + { + "Id": { + "id": 20274314715628 + }, + "Name": "SC-Node(Print)", + "Components": { + "Component_[7568030783460446634]": { + "$type": "Print", + "Id": 7568030783460446634, + "Slots": [ + { + "id": { + "m_id": "{733AA75D-022C-45E9-9D0F-3EF9A1633ADC}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "In", + "toolTip": "Input signal", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{2E77B0FC-DE56-4D78-9A59-152B7A0666F3}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Out", + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + } + } + ], + "m_format": "AutoComponent_NetworkInput received bad leftright!", + "m_unresolvedString": [ + "AutoComponent_NetworkInput received bad leftright!" + ] + } + } + }, + { + "Id": { + "id": 20261429813740 + }, + "Name": "SC-Node(Print)", + "Components": { + "Component_[8131385522131771125]": { + "$type": "Print", + "Id": 8131385522131771125, + "Slots": [ + { + "id": { + "m_id": "{2B6DB3BC-AA87-4280-B4C3-42C1EE17CBA3}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "In", + "toolTip": "Input signal", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{68F1C47B-3127-4CBA-AC9B-5B9B736C70AD}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Out", + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + } + } + ], + "m_format": "AutoComponent_NetworkInput CreateInput called!", + "m_unresolvedString": [ + "AutoComponent_NetworkInput CreateInput called!" + ] + } + } + } + ], + "m_connections": [ + { + "Id": { + "id": 20282904650220 + }, + "Name": "srcEndpoint=(NetworkTestPlayerComponentBusHandler Handler: ExecutionSlot:CreateInput), destEndpoint=(Print: In)", + "Components": { + "Component_[3586317167340048684]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 3586317167340048684, + "sourceEndpoint": { + "nodeId": { + "id": 20252839879148 + }, + "slotId": { + "m_id": "{B831AC60-7641-4B74-9829-26A3576B4766}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 20261429813740 + }, + "slotId": { + "m_id": "{2B6DB3BC-AA87-4280-B4C3-42C1EE17CBA3}" + } + } + } + } + }, + { + "Id": { + "id": 20287199617516 + }, + "Name": "srcEndpoint=(NetworkTestPlayerComponentBusHandler Handler: ExecutionSlot:CreateInput), destEndpoint=(CreateFromValues: In)", + "Components": { + "Component_[15956251897822268937]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 15956251897822268937, + "sourceEndpoint": { + "nodeId": { + "id": 20252839879148 + }, + "slotId": { + "m_id": "{B831AC60-7641-4B74-9829-26A3576B4766}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 20244249944556 + }, + "slotId": { + "m_id": "{514CDDAA-290F-4758-B28F-4003E719E635}" + } + } + } + } + }, + { + "Id": { + "id": 20291494584812 + }, + "Name": "srcEndpoint=(CreateFromValues: Result: NetworkTestPlayerComponentNetworkInput), destEndpoint=(NetworkTestPlayerComponentBusHandler Handler: Result: NetworkTestPlayerComponentNetworkInput)", + "Components": { + "Component_[3864080489501353126]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 3864080489501353126, + "sourceEndpoint": { + "nodeId": { + "id": 20244249944556 + }, + "slotId": { + "m_id": "{90E52F81-54E0-4C63-9881-B661FB5D87D1}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 20252839879148 + }, + "slotId": { + "m_id": "{ADF9B366-8324-4C1E-B601-C059DA70FDDE}" + } + } + } + } + }, + { + "Id": { + "id": 20295789552108 + }, + "Name": "srcEndpoint=(NetworkTestPlayerComponentBusHandler Handler: ExecutionSlot:ProcessInput), destEndpoint=(Print: In)", + "Components": { + "Component_[8628095809445337119]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 8628095809445337119, + "sourceEndpoint": { + "nodeId": { + "id": 20252839879148 + }, + "slotId": { + "m_id": "{4C8F2908-12B0-4C35-8468-31D3D4DF36AA}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 20278609682924 + }, + "slotId": { + "m_id": "{D994D58A-DBF1-4929-B779-F0D2CBAD2F0D}" + } + } + } + } + }, + { + "Id": { + "id": 20300084519404 + }, + "Name": "srcEndpoint=(NetworkTestPlayerComponentBusHandler Handler: ExecutionSlot:ProcessInput), destEndpoint=(Extract Properties: In)", + "Components": { + "Component_[10621112306443381493]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 10621112306443381493, + "sourceEndpoint": { + "nodeId": { + "id": 20252839879148 + }, + "slotId": { + "m_id": "{4C8F2908-12B0-4C35-8468-31D3D4DF36AA}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 20248544911852 + }, + "slotId": { + "m_id": "{C80C50EE-F216-4F44-B107-6B35354AFD52}" + } + } + } + } + }, + { + "Id": { + "id": 20304379486700 + }, + "Name": "srcEndpoint=(NetworkTestPlayerComponentBusHandler Handler: NetworkTestPlayerComponentNetworkInput), destEndpoint=(Extract Properties: Source)", + "Components": { + "Component_[14013500888143163469]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 14013500888143163469, + "sourceEndpoint": { + "nodeId": { + "id": 20252839879148 + }, + "slotId": { + "m_id": "{8F69FA2E-28D8-4DF1-A4B5-AEF3985095C5}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 20248544911852 + }, + "slotId": { + "m_id": "{D387C800-352B-4B01-8765-4F4B40DF45CB}" + } + } + } + } + }, + { + "Id": { + "id": 20308674453996 + }, + "Name": "srcEndpoint=(Extract Properties: Out), destEndpoint=(Not Equal To (!=): In)", + "Components": { + "Component_[14597948098713219792]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 14597948098713219792, + "sourceEndpoint": { + "nodeId": { + "id": 20248544911852 + }, + "slotId": { + "m_id": "{C69C098D-D667-4DC7-85E5-AFD119727D94}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 20257134846444 + }, + "slotId": { + "m_id": "{AE7E453C-15DE-47D2-954A-05C3895B7AC6}" + } + } + } + } + }, + { + "Id": { + "id": 20312969421292 + }, + "Name": "srcEndpoint=(Extract Properties: FwdBack: Number), destEndpoint=(Not Equal To (!=): Value A)", + "Components": { + "Component_[14915522756837814768]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 14915522756837814768, + "sourceEndpoint": { + "nodeId": { + "id": 20248544911852 + }, + "slotId": { + "m_id": "{0C03D491-DE25-46C2-BF09-14769FA49FDB}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 20257134846444 + }, + "slotId": { + "m_id": "{57EBC15B-452E-49B3-8BD3-4FBBB07F1F14}" + } + } + } + } + }, + { + "Id": { + "id": 20317264388588 + }, + "Name": "srcEndpoint=(Extract Properties: Out), destEndpoint=(Not Equal To (!=): In)", + "Components": { + "Component_[6510282773353837676]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 6510282773353837676, + "sourceEndpoint": { + "nodeId": { + "id": 20248544911852 + }, + "slotId": { + "m_id": "{C69C098D-D667-4DC7-85E5-AFD119727D94}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 20265724781036 + }, + "slotId": { + "m_id": "{AE7E453C-15DE-47D2-954A-05C3895B7AC6}" + } + } + } + } + }, + { + "Id": { + "id": 20321559355884 + }, + "Name": "srcEndpoint=(Extract Properties: LeftRight: Number), destEndpoint=(Not Equal To (!=): Value A)", + "Components": { + "Component_[16150645152204311425]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 16150645152204311425, + "sourceEndpoint": { + "nodeId": { + "id": 20248544911852 + }, + "slotId": { + "m_id": "{4C13F9EF-60BF-4AD1-8FA9-66F46455411C}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 20265724781036 + }, + "slotId": { + "m_id": "{57EBC15B-452E-49B3-8BD3-4FBBB07F1F14}" + } + } + } + } + }, + { + "Id": { + "id": 20325854323180 + }, + "Name": "srcEndpoint=(Not Equal To (!=): True), destEndpoint=(Print: In)", + "Components": { + "Component_[3322355580364572639]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 3322355580364572639, + "sourceEndpoint": { + "nodeId": { + "id": 20257134846444 + }, + "slotId": { + "m_id": "{AC364E17-A9A1-42DB-A29D-7B2D666E4287}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 20270019748332 + }, + "slotId": { + "m_id": "{733AA75D-022C-45E9-9D0F-3EF9A1633ADC}" + } + } + } + } + }, + { + "Id": { + "id": 20330149290476 + }, + "Name": "srcEndpoint=(Not Equal To (!=): True), destEndpoint=(Print: In)", + "Components": { + "Component_[1975626970668030308]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 1975626970668030308, + "sourceEndpoint": { + "nodeId": { + "id": 20265724781036 + }, + "slotId": { + "m_id": "{AC364E17-A9A1-42DB-A29D-7B2D666E4287}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 20274314715628 + }, + "slotId": { + "m_id": "{733AA75D-022C-45E9-9D0F-3EF9A1633ADC}" + } + } + } + } + } + ] + }, + "m_assetType": "{3E2AC8CD-713F-453E-967F-29517F331784}", + "versionData": { + "_grammarVersion": 1, + "_runtimeVersion": 1, + "_fileVersion": 1 + }, + "m_variableCounter": 2, + "GraphCanvasData": [ + { + "Key": { + "id": 20239954977260 + }, + "Value": { + "ComponentData": { + "{5F84B500-8C45-40D1-8EFC-A5306B241444}": { + "$type": "SceneComponentSaveData", + "ViewParams": { + "Scale": 1.0097068678919363, + "AnchorX": 1086.4539794921875, + "AnchorY": 198.07728576660156 + } + } + } + } + }, + { + "Key": { + "id": 20244249944556 + }, + "Value": { + "ComponentData": { + "{24CB38BB-1705-4EC5-8F63-B574571B4DCD}": { + "$type": "NodeSaveData" + }, + "{328FF15C-C302-458F-A43D-E1794DE0904E}": { + "$type": "GeneralNodeTitleComponentSaveData", + "PaletteOverride": "MethodNodeTitlePalette" + }, + "{7CC444B1-F9B3-41B5-841B-0C4F2179F111}": { + "$type": "GeometrySaveData", + "Position": [ + 740.0, + 100.0 + ] + }, + "{B0B99C8A-03AF-4CF6-A926-F65C874C3D97}": { + "$type": "StylingComponentSaveData", + "SubStyle": ".method" + }, + "{B1F49A35-8408-40DA-B79E-F1E3B64322CE}": { + "$type": "PersistentIdComponentSaveData", + "PersistentId": "{7A7C96CB-4B5A-48DD-A0AD-0094A113549B}" + } + } + } + }, + { + "Key": { + "id": 20248544911852 + }, + "Value": { + "ComponentData": { + "{24CB38BB-1705-4EC5-8F63-B574571B4DCD}": { + "$type": "NodeSaveData" + }, + "{328FF15C-C302-458F-A43D-E1794DE0904E}": { + "$type": "GeneralNodeTitleComponentSaveData", + "PaletteOverride": "DefaultNodeTitlePalette" + }, + "{7CC444B1-F9B3-41B5-841B-0C4F2179F111}": { + "$type": "GeometrySaveData", + "Position": [ + 740.0, + 320.0 + ] + }, + "{B0B99C8A-03AF-4CF6-A926-F65C874C3D97}": { + "$type": "StylingComponentSaveData" + }, + "{B1F49A35-8408-40DA-B79E-F1E3B64322CE}": { + "$type": "PersistentIdComponentSaveData", + "PersistentId": "{ED8F0B6C-0811-4FB7-AEE8-B71DB87FABF0}" + } + } + } + }, + { + "Key": { + "id": 20252839879148 + }, + "Value": { + "ComponentData": { + "{24CB38BB-1705-4EC5-8F63-B574571B4DCD}": { + "$type": "NodeSaveData" + }, + "{7CC444B1-F9B3-41B5-841B-0C4F2179F111}": { + "$type": "GeometrySaveData", + "Position": [ + 160.0, + 100.0 + ] + }, + "{9E81C95F-89C0-4476-8E82-63CCC4E52E04}": { + "$type": "EBusHandlerNodeDescriptorSaveData", + "EventIds": [ + { + "Value": 78438309 + }, + { + "Value": 1793364217 + } + ] + }, + "{B0B99C8A-03AF-4CF6-A926-F65C874C3D97}": { + "$type": "StylingComponentSaveData" + }, + "{B1F49A35-8408-40DA-B79E-F1E3B64322CE}": { + "$type": "PersistentIdComponentSaveData", + "PersistentId": "{BB97BA3E-F2AB-4078-9BB3-2A0F31DC771C}" + } + } + } + }, + { + "Key": { + "id": 20257134846444 + }, + "Value": { + "ComponentData": { + "{24CB38BB-1705-4EC5-8F63-B574571B4DCD}": { + "$type": "NodeSaveData" + }, + "{328FF15C-C302-458F-A43D-E1794DE0904E}": { + "$type": "GeneralNodeTitleComponentSaveData", + "PaletteOverride": "MathNodeTitlePalette" + }, + "{7CC444B1-F9B3-41B5-841B-0C4F2179F111}": { + "$type": "GeometrySaveData", + "Position": [ + 1040.0, + 320.0 + ] + }, + "{B0B99C8A-03AF-4CF6-A926-F65C874C3D97}": { + "$type": "StylingComponentSaveData" + }, + "{B1F49A35-8408-40DA-B79E-F1E3B64322CE}": { + "$type": "PersistentIdComponentSaveData", + "PersistentId": "{71AB4748-41F4-4FEA-874C-F54037236F31}" + } + } + } + }, + { + "Key": { + "id": 20261429813740 + }, + "Value": { + "ComponentData": { + "{24CB38BB-1705-4EC5-8F63-B574571B4DCD}": { + "$type": "NodeSaveData" + }, + "{328FF15C-C302-458F-A43D-E1794DE0904E}": { + "$type": "GeneralNodeTitleComponentSaveData", + "PaletteOverride": "StringNodeTitlePalette" + }, + "{7CC444B1-F9B3-41B5-841B-0C4F2179F111}": { + "$type": "GeometrySaveData", + "Position": [ + 740.0, + -100.0 + ] + }, + "{B0B99C8A-03AF-4CF6-A926-F65C874C3D97}": { + "$type": "StylingComponentSaveData" + }, + "{B1F49A35-8408-40DA-B79E-F1E3B64322CE}": { + "$type": "PersistentIdComponentSaveData", + "PersistentId": "{26E363EE-F35A-4096-88EF-DF907A809894}" + } + } + } + }, + { + "Key": { + "id": 20265724781036 + }, + "Value": { + "ComponentData": { + "{24CB38BB-1705-4EC5-8F63-B574571B4DCD}": { + "$type": "NodeSaveData" + }, + "{328FF15C-C302-458F-A43D-E1794DE0904E}": { + "$type": "GeneralNodeTitleComponentSaveData", + "PaletteOverride": "MathNodeTitlePalette" + }, + "{7CC444B1-F9B3-41B5-841B-0C4F2179F111}": { + "$type": "GeometrySaveData", + "Position": [ + 1040.0, + 520.0 + ] + }, + "{B0B99C8A-03AF-4CF6-A926-F65C874C3D97}": { + "$type": "StylingComponentSaveData" + }, + "{B1F49A35-8408-40DA-B79E-F1E3B64322CE}": { + "$type": "PersistentIdComponentSaveData", + "PersistentId": "{F64E211C-8DDD-4223-9235-8DB802A017CB}" + } + } + } + }, + { + "Key": { + "id": 20270019748332 + }, + "Value": { + "ComponentData": { + "{24CB38BB-1705-4EC5-8F63-B574571B4DCD}": { + "$type": "NodeSaveData" + }, + "{328FF15C-C302-458F-A43D-E1794DE0904E}": { + "$type": "GeneralNodeTitleComponentSaveData", + "PaletteOverride": "StringNodeTitlePalette" + }, + "{7CC444B1-F9B3-41B5-841B-0C4F2179F111}": { + "$type": "GeometrySaveData", + "Position": [ + 1480.0, + 320.0 + ] + }, + "{B0B99C8A-03AF-4CF6-A926-F65C874C3D97}": { + "$type": "StylingComponentSaveData" + }, + "{B1F49A35-8408-40DA-B79E-F1E3B64322CE}": { + "$type": "PersistentIdComponentSaveData", + "PersistentId": "{9161B9FA-8493-479D-BE35-10D92644D5C0}" + } + } + } + }, + { + "Key": { + "id": 20274314715628 + }, + "Value": { + "ComponentData": { + "{24CB38BB-1705-4EC5-8F63-B574571B4DCD}": { + "$type": "NodeSaveData" + }, + "{328FF15C-C302-458F-A43D-E1794DE0904E}": { + "$type": "GeneralNodeTitleComponentSaveData", + "PaletteOverride": "StringNodeTitlePalette" + }, + "{7CC444B1-F9B3-41B5-841B-0C4F2179F111}": { + "$type": "GeometrySaveData", + "Position": [ + 1480.0, + 520.0 + ] + }, + "{B0B99C8A-03AF-4CF6-A926-F65C874C3D97}": { + "$type": "StylingComponentSaveData" + }, + "{B1F49A35-8408-40DA-B79E-F1E3B64322CE}": { + "$type": "PersistentIdComponentSaveData", + "PersistentId": "{1AF129A6-751C-4FC0-805E-65AC2D4B6D3D}" + } + } + } + }, + { + "Key": { + "id": 20278609682924 + }, + "Value": { + "ComponentData": { + "{24CB38BB-1705-4EC5-8F63-B574571B4DCD}": { + "$type": "NodeSaveData" + }, + "{328FF15C-C302-458F-A43D-E1794DE0904E}": { + "$type": "GeneralNodeTitleComponentSaveData", + "PaletteOverride": "StringNodeTitlePalette" + }, + "{7CC444B1-F9B3-41B5-841B-0C4F2179F111}": { + "$type": "GeometrySaveData", + "Position": [ + 740.0, + 740.0 + ] + }, + "{B0B99C8A-03AF-4CF6-A926-F65C874C3D97}": { + "$type": "StylingComponentSaveData" + }, + "{B1F49A35-8408-40DA-B79E-F1E3B64322CE}": { + "$type": "PersistentIdComponentSaveData", + "PersistentId": "{F7407B9B-C302-4B50-BFB0-D1208DF1D5AC}" + } + } + } + } + ], + "StatisticsHelper": { + "InstanceCounter": [ + { + "Key": 5842116704436214676, + "Value": 1 + }, + { + "Key": 5842116706017748280, + "Value": 1 + }, + { + "Key": 7441100700879769985, + "Value": 2 + }, + { + "Key": 10684225535275896474, + "Value": 4 + }, + { + "Key": 10715014621082578046, + "Value": 1 + }, + { + "Key": 14285852892804039565, + "Value": 1 + } + ] + } + } + } + } + } +} \ No newline at end of file diff --git a/AutomatedTesting/Levels/Multiplayer/AutoComponent_NetworkInput/Player.prefab b/AutomatedTesting/Levels/Multiplayer/AutoComponent_NetworkInput/Player.prefab new file mode 100644 index 0000000000..72fce2d291 --- /dev/null +++ b/AutomatedTesting/Levels/Multiplayer/AutoComponent_NetworkInput/Player.prefab @@ -0,0 +1,196 @@ +{ + "ContainerEntity": { + "Id": "ContainerEntity", + "Name": "Player", + "Components": { + "Component_[10591405285626521927]": { + "$type": "EditorLockComponent", + "Id": 10591405285626521927 + }, + "Component_[10962884071806037909]": { + "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent", + "Id": 10962884071806037909, + "Parent Entity": "" + }, + "Component_[14883697413991420474]": { + "$type": "EditorOnlyEntityComponent", + "Id": 14883697413991420474 + }, + "Component_[1497622121956209837]": { + "$type": "EditorVisibilityComponent", + "Id": 1497622121956209837 + }, + "Component_[16429314387772079347]": { + "$type": "EditorEntityIconComponent", + "Id": 16429314387772079347 + }, + "Component_[16665294301093657382]": { + "$type": "EditorDisabledCompositionComponent", + "Id": 16665294301093657382 + }, + "Component_[1706666252612720326]": { + "$type": "EditorInspectorComponent", + "Id": 1706666252612720326 + }, + "Component_[4216896820422195198]": { + "$type": "EditorPendingCompositionComponent", + "Id": 4216896820422195198 + }, + "Component_[4540089401187370610]": { + "$type": "EditorPrefabComponent", + "Id": 4540089401187370610 + }, + "Component_[6378576046601184103]": { + "$type": "EditorEntitySortComponent", + "Id": 6378576046601184103 + }, + "Component_[7745420981568587180]": { + "$type": "SelectionComponent", + "Id": 7745420981568587180 + } + } + }, + "Entities": { + "Entity_[1028733630164]": { + "Id": "Entity_[1028733630164]", + "Name": "Player", + "Components": { + "Component_[12294726333564087591]": { + "$type": "SelectionComponent", + "Id": 12294726333564087591 + }, + "Component_[13587084088242540786]": { + "$type": "EditorInspectorComponent", + "Id": 13587084088242540786, + "ComponentOrderEntryArray": [ + { + "ComponentId": 6819443882832501114 + }, + { + "ComponentId": 5577505593558922067, + "SortIndex": 1 + }, + { + "ComponentId": 2069554278758260821, + "SortIndex": 2 + }, + { + "ComponentId": 16508969730014660362, + "SortIndex": 3 + }, + { + "ComponentId": 8125406152674415588, + "SortIndex": 4 + }, + { + "ComponentId": 4337571454344109612, + "SortIndex": 5 + }, + { + "ComponentId": 16457408099527309065, + "SortIndex": 6 + } + ] + }, + "Component_[14335168881008289852]": { + "$type": "EditorEntitySortComponent", + "Id": 14335168881008289852 + }, + "Component_[16308902899170829847]": { + "$type": "EditorVisibilityComponent", + "Id": 16308902899170829847 + }, + "Component_[16457408099527309065]": { + "$type": "GenericComponentWrapper", + "Id": 16457408099527309065, + "m_template": { + "$type": "Multiplayer::NetworkTransformComponent" + } + }, + "Component_[16508969730014660362]": { + "$type": "GenericComponentWrapper", + "Id": 16508969730014660362, + "m_template": { + "$type": "AutomatedTesting::NetworkTestPlayerComponent" + } + }, + "Component_[16541569566865026527]": { + "$type": "EditorOnlyEntityComponent", + "Id": 16541569566865026527 + }, + "Component_[2002761223483048905]": { + "$type": "EditorPendingCompositionComponent", + "Id": 2002761223483048905 + }, + "Component_[2069554278758260821]": { + "$type": "EditorScriptCanvasComponent", + "Id": 2069554278758260821, + "m_name": "AutoComponent_NetworkInput", + "m_assetHolder": { + "m_asset": { + "assetId": { + "guid": "{D079F53D-CCAA-5C98-8E0C-B485B7821747}" + }, + "assetHint": "levels/multiplayer/autocomponent_networkinput/autocomponent_networkinput.scriptcanvas" + } + }, + "runtimeDataIsValid": true, + "runtimeDataOverrides": { + "source": { + "assetId": { + "guid": "{D079F53D-CCAA-5C98-8E0C-B485B7821747}" + }, + "assetHint": "levels/multiplayer/autocomponent_networkinput/autocomponent_networkinput.scriptcanvas" + } + } + }, + "Component_[4337571454344109612]": { + "$type": "GenericComponentWrapper", + "Id": 4337571454344109612, + "m_template": { + "$type": "NetBindComponent" + } + }, + "Component_[477591477979440744]": { + "$type": "EditorLockComponent", + "Id": 477591477979440744 + }, + "Component_[5577505593558922067]": { + "$type": "AZ::Render::EditorMeshComponent", + "Id": 5577505593558922067, + "Controller": { + "Configuration": { + "ModelAsset": { + "assetId": { + "guid": "{6DE0E9A8-A1C7-5D0F-9407-4E627C1F223C}", + "subId": 284780167 + }, + "assetHint": "models/sphere.azmodel" + } + } + } + }, + "Component_[5828214869455694702]": { + "$type": "EditorDisabledCompositionComponent", + "Id": 5828214869455694702 + }, + "Component_[6819443882832501114]": { + "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent", + "Id": 6819443882832501114, + "Parent Entity": "ContainerEntity" + }, + "Component_[8125406152674415588]": { + "$type": "GenericComponentWrapper", + "Id": 8125406152674415588, + "m_template": { + "$type": "Multiplayer::LocalPredictionPlayerInputComponent" + } + }, + "Component_[8838623765985560328]": { + "$type": "EditorEntityIconComponent", + "Id": 8838623765985560328 + } + } + } + } +} \ No newline at end of file diff --git a/AutomatedTesting/Levels/Multiplayer/AutoComponent_NetworkInput/tags.txt b/AutomatedTesting/Levels/Multiplayer/AutoComponent_NetworkInput/tags.txt new file mode 100644 index 0000000000..0d6c1880e7 --- /dev/null +++ b/AutomatedTesting/Levels/Multiplayer/AutoComponent_NetworkInput/tags.txt @@ -0,0 +1,12 @@ +0,0,0,0,0,0 +0,0,0,0,0,0 +0,0,0,0,0,0 +0,0,0,0,0,0 +0,0,0,0,0,0 +0,0,0,0,0,0 +0,0,0,0,0,0 +0,0,0,0,0,0 +0,0,0,0,0,0 +0,0,0,0,0,0 +0,0,0,0,0,0 +0,0,0,0,0,0 diff --git a/AutomatedTesting/Materials/MinimalBlue.material b/AutomatedTesting/Materials/MinimalBlue.material new file mode 100644 index 0000000000..7310a90250 --- /dev/null +++ b/AutomatedTesting/Materials/MinimalBlue.material @@ -0,0 +1,17 @@ +{ + "description": "", + "parentMaterial": "", + "materialType": "TestData/Materials/Types/MinimalPBR.materialtype", + "materialTypeVersion": 3, + "properties": { + "settings": { + "color": [ + 0.08522164076566696, + 0.11898985505104065, + 1.0, + 1.0 + ], + "roughness": 0.33000001311302185 + } + } +} \ No newline at end of file diff --git a/AutomatedTesting/Passes/MainPipeline.pass b/AutomatedTesting/Passes/MainPipeline.pass index aa9f3757c4..c34c556983 100644 --- a/AutomatedTesting/Passes/MainPipeline.pass +++ b/AutomatedTesting/Passes/MainPipeline.pass @@ -205,6 +205,99 @@ } ] }, + + { + // NOTE: HairParentPass does not write into Depth MSAA from Opaque Pass. If new passes downstream + // of HairParentPass will need to use Depth MSAA, HairParentPass will need to be updated to use Depth MSAA + // instead of regular Depth as DepthStencil. Specifically, HairResolvePPLL.pass and the associated + // .azsl file will need to be updated. + "Name": "HairParentPass", + // Note: The following two lines represent the choice of rendering pipeline for the hair. + // You can either choose to use PPLL or ShortCut and accordingly change the flag + // 'm_usePPLLRenderTechnique' in the class 'HairFeatureProcessor.cpp' +// "TemplateName": "HairParentPassTemplate", + "TemplateName": "HairParentShortCutPassTemplate", + "Enabled": true, + "Connections": [ + // Critical to keep DepthLinear as input - used to set the size of the Head PPLL image buffer. + // If DepthLinear is not available - connect to another viewport (non MSAA) image. + { + "LocalSlot": "DepthLinearInput", + "AttachmentRef": { + "Pass": "DepthPrePass", + "Attachment": "DepthLinear" + } + }, + { + "LocalSlot": "Depth", + "AttachmentRef": { + "Pass": "DepthPrePass", + "Attachment": "Depth" + } + }, + { + "LocalSlot": "RenderTargetInputOutput", + "AttachmentRef": { + "Pass": "OpaquePass", + "Attachment": "Output" + } + }, + { + "LocalSlot": "RenderTargetInputOnly", + "AttachmentRef": { + "Pass": "OpaquePass", + "Attachment": "Output" + } + }, + + // Shadows resources + { + "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" + } + }, + + // Lighting Resources + { + "LocalSlot": "TileLightData", + "AttachmentRef": { + "Pass": "LightCullingPass", + "Attachment": "TileLightData" + } + }, + { + "LocalSlot": "LightListRemapped", + "AttachmentRef": { + "Pass": "LightCullingPass", + "Attachment": "LightListRemapped" + } + } + ] + }, + { "Name": "TransparentPass", "TemplateName": "TransparentParentTemplate", @@ -254,22 +347,22 @@ { "LocalSlot": "InputLinearDepth", "AttachmentRef": { - "Pass": "DepthPrePass", + "Pass": "HairParentPass", "Attachment": "DepthLinear" } }, { "LocalSlot": "DepthStencil", "AttachmentRef": { - "Pass": "DepthPrePass", + "Pass": "HairParentPass", "Attachment": "Depth" } }, { "LocalSlot": "InputOutput", "AttachmentRef": { - "Pass": "OpaquePass", - "Attachment": "Output" + "Pass": "HairParentPass", + "Attachment": "RenderTargetInputOutput" } } ] @@ -282,22 +375,22 @@ { "LocalSlot": "InputLinearDepth", "AttachmentRef": { - "Pass": "DepthPrePass", + "Pass": "HairParentPass", "Attachment": "DepthLinear" } }, { "LocalSlot": "InputDepthStencil", "AttachmentRef": { - "Pass": "DepthPrePass", + "Pass": "HairParentPass", "Attachment": "Depth" } }, { "LocalSlot": "RenderTargetInputOutput", "AttachmentRef": { - "Pass": "TransparentPass", - "Attachment": "InputOutput" + "Pass": "HairParentPass", + "Attachment": "RenderTargetInputOutput" } } ], @@ -337,7 +430,7 @@ { "LocalSlot": "Depth", "AttachmentRef": { - "Pass": "DepthPrePass", + "Pass": "HairParentPass", "Attachment": "Depth" } }, @@ -372,7 +465,7 @@ { "LocalSlot": "DepthInputOutput", "AttachmentRef": { - "Pass": "DepthPrePass", + "Pass": "HairParentPass", "Attachment": "Depth" } } @@ -431,7 +524,7 @@ { "LocalSlot": "DepthInputOutput", "AttachmentRef": { - "Pass": "DepthPrePass", + "Pass": "HairParentPass", "Attachment": "Depth" } } @@ -451,7 +544,7 @@ { "LocalSlot": "DepthInputOutput", "AttachmentRef": { - "Pass": "DepthPrePass", + "Pass": "HairParentPass", "Attachment": "Depth" } } diff --git a/Code/Editor/AzAssetBrowser/AzAssetBrowserWindow.cpp b/Code/Editor/AzAssetBrowser/AzAssetBrowserWindow.cpp index 9fe4cfd0d2..bcaaa02b67 100644 --- a/Code/Editor/AzAssetBrowser/AzAssetBrowserWindow.cpp +++ b/Code/Editor/AzAssetBrowser/AzAssetBrowserWindow.cpp @@ -96,10 +96,6 @@ AzAssetBrowserWindow::AzAssetBrowserWindow(QWidget* parent) connect( m_filterModel.data(), &AzAssetBrowser::AssetBrowserFilterModel::filterChanged, this, &AzAssetBrowserWindow::SetTableViewVisibleAfterFilter); - - connect( - m_filterModel.data(), &AzAssetBrowser::AssetBrowserFilterModel::filterChanged, this, - &AzAssetBrowserWindow::UpdateTableModelAfterFilter); connect( m_ui->m_assetBrowserTableViewWidget, &AzAssetBrowser::AssetBrowserTableView::selectionChangedSignal, this, &AzAssetBrowserWindow::SelectionChangedSlot); @@ -251,24 +247,6 @@ void AzAssetBrowserWindow::SetExpandedAssetBrowserMode() m_assetBrowserDisplayState = AzAssetBrowser::AssetBrowserDisplayState::ExpandedMode; - disconnect( - m_filterModel.data(), &AzAssetBrowser::AssetBrowserFilterModel::filterChanged, this, - &AzAssetBrowserWindow::UpdateTableModelAfterFilter); - disconnect( - m_filterModel.data(), &AzAssetBrowser::AssetBrowserFilterModel::filterChanged, this, - &AzAssetBrowserWindow::SetTableViewVisibleAfterFilter); - - disconnect( - m_ui->m_assetBrowserTableViewWidget, &AzAssetBrowser::AssetBrowserTableView::selectionChangedSignal, this, - &AzAssetBrowserWindow::SelectionChangedSlot); - disconnect(m_ui->m_assetBrowserTableViewWidget, &QAbstractItemView::doubleClicked, this, &AzAssetBrowserWindow::DoubleClickedItem); - disconnect( - m_ui->m_assetBrowserTableViewWidget, &AzAssetBrowser::AssetBrowserTableView::ClearStringFilter, m_ui->m_searchWidget, - &AzAssetBrowser::SearchWidget::ClearStringFilter); - disconnect( - m_ui->m_assetBrowserTableViewWidget, &AzAssetBrowser::AssetBrowserTableView::ClearTypeFilter, m_ui->m_searchWidget, - &AzAssetBrowser::SearchWidget::ClearTypeFilter); - if (m_ui->m_assetBrowserTableViewWidget->isVisible()) { m_ui->m_assetBrowserTableViewWidget->setVisible(false); @@ -281,37 +259,9 @@ void AzAssetBrowserWindow::SetDefaultAssetBrowserMode() namespace AzAssetBrowser = AzToolsFramework::AssetBrowser; m_assetBrowserDisplayState = AzAssetBrowser::AssetBrowserDisplayState::DefaultMode; - - connect( - m_filterModel.data(), &AzAssetBrowser::AssetBrowserFilterModel::filterChanged, this, - &AzAssetBrowserWindow::SetTableViewVisibleAfterFilter); - - connect( - m_filterModel.data(), &AzAssetBrowser::AssetBrowserFilterModel::filterChanged, this, - &AzAssetBrowserWindow::UpdateTableModelAfterFilter); - connect( - m_ui->m_assetBrowserTableViewWidget, &AzAssetBrowser::AssetBrowserTableView::selectionChangedSignal, this, - &AzAssetBrowserWindow::SelectionChangedSlot); - connect(m_ui->m_assetBrowserTableViewWidget, &QAbstractItemView::doubleClicked, this, &AzAssetBrowserWindow::DoubleClickedItem); - connect( - m_ui->m_assetBrowserTableViewWidget, &AzAssetBrowser::AssetBrowserTableView::ClearStringFilter, m_ui->m_searchWidget, - &AzAssetBrowser::SearchWidget::ClearStringFilter); - connect( - m_ui->m_assetBrowserTableViewWidget, &AzAssetBrowser::AssetBrowserTableView::ClearTypeFilter, m_ui->m_searchWidget, - &AzAssetBrowser::SearchWidget::ClearTypeFilter); - - //If the filter is not empty we want to switch views and Update the model - UpdateTableModelAfterFilter(); SetTableViewVisibleAfterFilter(); } -void AzAssetBrowserWindow::UpdateTableModelAfterFilter() -{ - if (!m_ui->m_searchWidget->GetFilterString().isEmpty()) - { - m_tableModel->UpdateTableModelMaps(); - } -} void AzAssetBrowserWindow::SetTableViewVisibleAfterFilter() { @@ -389,8 +339,8 @@ void AzAssetBrowserWindow::SelectionChangedSlot(const QItemSelection& /*selected UpdatePreview(); } -// while its tempting to use Activated here, we dont actually want it to count as activation -// just becuase on some OS clicking once is activation. +// while its tempting to use Activated here, we don't actually want it to count as activation +// just because on some OS clicking once is activation. void AzAssetBrowserWindow::DoubleClickedItem([[maybe_unused]] const QModelIndex& element) { namespace AzAssetBrowser = AzToolsFramework::AssetBrowser; diff --git a/Code/Editor/AzAssetBrowser/AzAssetBrowserWindow.h b/Code/Editor/AzAssetBrowser/AzAssetBrowserWindow.h index 753f000300..34103316ab 100644 --- a/Code/Editor/AzAssetBrowser/AzAssetBrowserWindow.h +++ b/Code/Editor/AzAssetBrowser/AzAssetBrowserWindow.h @@ -68,7 +68,6 @@ protected slots: void CreateSwitchViewMenu(); void SetExpandedAssetBrowserMode(); void SetDefaultAssetBrowserMode(); - void UpdateTableModelAfterFilter(); void SetTableViewVisibleAfterFilter(); private: diff --git a/Code/Editor/CVarMenu.h b/Code/Editor/CVarMenu.h index efcc8e8caf..5195bd99d7 100644 --- a/Code/Editor/CVarMenu.h +++ b/Code/Editor/CVarMenu.h @@ -16,9 +16,12 @@ #include #include +struct ICVar; + class CVarMenu : public QMenu { + Q_OBJECT public: // CVar that can be toggled on and off struct CVarToggle diff --git a/Code/Editor/ConfigGroup.cpp b/Code/Editor/ConfigGroup.cpp index 74fe6f7b5c..42236e43dd 100644 --- a/Code/Editor/ConfigGroup.cpp +++ b/Code/Editor/ConfigGroup.cpp @@ -19,10 +19,9 @@ namespace Config CConfigGroup::~CConfigGroup() { - for (TConfigVariables::const_iterator it = m_vars.begin(); - it != m_vars.end(); ++it) + for (IConfigVar* var : m_vars) { - delete (*it); + delete var; } } @@ -31,17 +30,15 @@ namespace Config m_vars.push_back(var); } - uint32 CConfigGroup::GetVarCount() + AZ::u32 CConfigGroup::GetVarCount() { - return static_cast(m_vars.size()); + return aznumeric_cast(m_vars.size()); } IConfigVar* CConfigGroup::GetVar(const char* szName) { - for (TConfigVariables::const_iterator it = m_vars.begin(); - it != m_vars.end(); ++it) + for (IConfigVar* var : m_vars) { - IConfigVar* var = (*it); if (0 == _stricmp(szName, var->GetName().c_str())) { return var; @@ -53,20 +50,19 @@ namespace Config const IConfigVar* CConfigGroup::GetVar(const char* szName) const { - for (TConfigVariables::const_iterator it = m_vars.begin(); - it != m_vars.end(); ++it) + for (const IConfigVar* var : m_vars) { - IConfigVar* var = (*it); if (0 == _stricmp(szName, var->GetName().c_str())) { return var; } + } return nullptr; } - IConfigVar* CConfigGroup::GetVar(uint index) + IConfigVar* CConfigGroup::GetVar(AZ::u32 index) { if (index < m_vars.size()) { @@ -76,7 +72,7 @@ namespace Config return nullptr; } - const IConfigVar* CConfigGroup::GetVar(uint index) const + const IConfigVar* CConfigGroup::GetVar(AZ::u32 index) const { if (index < m_vars.size()) { @@ -89,114 +85,110 @@ namespace Config void CConfigGroup::SaveToXML(XmlNodeRef node) { // save only values that don't have default values - for (TConfigVariables::const_iterator it = m_vars.begin(); - it != m_vars.end(); ++it) + for (const IConfigVar* var : m_vars) { - IConfigVar* var = (*it); - if (!var->IsFlagSet(IConfigVar::eFlag_DoNotSave)) + if (var->IsFlagSet(IConfigVar::eFlag_DoNotSave) || var->IsDefault()) { - if (!var->IsDefault()) - { - const char* szName = var->GetName().c_str(); + continue; + } - switch (var->GetType()) - { - case IConfigVar::eType_BOOL: - { - bool currentValue = false; - var->Get(¤tValue); - node->setAttr(szName, currentValue); - break; - } + const char* szName = var->GetName().c_str(); - case IConfigVar::eType_INT: - { - int currentValue = 0; - var->Get(¤tValue); - node->setAttr(szName, currentValue); - break; - } + switch (var->GetType()) + { + case IConfigVar::eType_BOOL: + { + bool currentValue = false; + var->Get(¤tValue); + node->setAttr(szName, currentValue); + break; + } - case IConfigVar::eType_FLOAT: - { - float currentValue = 0; - var->Get(¤tValue); - node->setAttr(szName, currentValue); - break; - } + case IConfigVar::eType_INT: + { + int currentValue = 0; + var->Get(¤tValue); + node->setAttr(szName, currentValue); + break; + } - case IConfigVar::eType_STRING: - { - AZStd::string currentValue; - var->Get(¤tValue); - node->setAttr(szName, currentValue.c_str()); - break; - } - } - } + case IConfigVar::eType_FLOAT: + { + float currentValue = 0; + var->Get(¤tValue); + node->setAttr(szName, currentValue); + break; + } + + case IConfigVar::eType_STRING: + { + AZStd::string currentValue; + var->Get(¤tValue); + node->setAttr(szName, currentValue.c_str()); + break; + } } } } void CConfigGroup::LoadFromXML(XmlNodeRef node) { - // save only values that don't have default values - for (TConfigVariables::const_iterator it = m_vars.begin(); - it != m_vars.end(); ++it) + // load values that are save-able + for (IConfigVar* var : m_vars) { - IConfigVar* var = (*it); - if (!var->IsFlagSet(IConfigVar::eFlag_DoNotSave)) + if (var->IsFlagSet(IConfigVar::eFlag_DoNotSave)) { - const char* szName = var->GetName().c_str(); + continue; + } + const char* szName = var->GetName().c_str(); - switch (var->GetType()) + switch (var->GetType()) + { + case IConfigVar::eType_BOOL: + { + bool currentValue = false; + var->GetDefault(¤tValue); + if (node->getAttr(szName, currentValue)) { - case IConfigVar::eType_BOOL: - { - bool currentValue = false; - var->GetDefault(¤tValue); - if (node->getAttr(szName, currentValue)) - { - var->Set(¤tValue); - } - break; + var->Set(¤tValue); } + break; + } - case IConfigVar::eType_INT: + case IConfigVar::eType_INT: + { + int currentValue = 0; + var->GetDefault(¤tValue); + if (node->getAttr(szName, currentValue)) { - int currentValue = 0; - var->GetDefault(¤tValue); - if (node->getAttr(szName, currentValue)) - { - var->Set(¤tValue); - } - break; + var->Set(¤tValue); } + break; + } - case IConfigVar::eType_FLOAT: + case IConfigVar::eType_FLOAT: + { + float currentValue = 0; + var->GetDefault(¤tValue); + if (node->getAttr(szName, currentValue)) { - float currentValue = 0; - var->GetDefault(¤tValue); - if (node->getAttr(szName, currentValue)) - { - var->Set(¤tValue); - } - break; + var->Set(¤tValue); } + break; + } - case IConfigVar::eType_STRING: + case IConfigVar::eType_STRING: + { + AZStd::string currentValue; + var->GetDefault(¤tValue); + QString readValue(currentValue.c_str()); + if (node->getAttr(szName, readValue)) { - AZStd::string currentValue; - var->GetDefault(¤tValue); - QString readValue(currentValue.c_str()); - if (node->getAttr(szName, readValue)) - { - currentValue = readValue.toUtf8().data(); - var->Set(¤tValue); - } - break; - } + currentValue = readValue.toUtf8().data(); + var->Set(¤tValue); } + break; + } } } } diff --git a/Code/Editor/ConfigGroup.h b/Code/Editor/ConfigGroup.h index 769a29ba8a..004725e32c 100644 --- a/Code/Editor/ConfigGroup.h +++ b/Code/Editor/ConfigGroup.h @@ -8,8 +8,12 @@ #pragma once -#ifndef CRYINCLUDE_EDITOR_CONFIGGROUP_H -#define CRYINCLUDE_EDITOR_CONFIGGROUP_H +#include +#include +#include + +struct ICVar; +class XmlNodeRef; namespace Config { @@ -32,7 +36,7 @@ namespace Config eFlag_DoNotSave = 1 << 2, }; - IConfigVar(const char* szName, const char* szDescription, EType varType, uint8 flags) + IConfigVar(const char* szName, const char* szDescription, EType varType, AZ::u8 flags) : m_name(szName) , m_description(szDescription) , m_type(varType) @@ -42,22 +46,22 @@ namespace Config virtual ~IConfigVar() = default; - ILINE EType GetType() const + AZ_FORCE_INLINE EType GetType() const { return m_type; } - ILINE const AZStd::string& GetName() const + AZ_FORCE_INLINE const AZStd::string& GetName() const { return m_name; } - ILINE const AZStd::string& GetDescription() const + AZ_FORCE_INLINE const AZStd::string& GetDescription() const { return m_description; } - ILINE bool IsFlagSet(EFlags flag) const + AZ_FORCE_INLINE bool IsFlagSet(EFlags flag) const { return 0 != (m_flags & flag); } @@ -68,73 +72,28 @@ namespace Config virtual void GetDefault(void* outPtr) const = 0; virtual void Reset() = 0; - static EType TranslateType(const bool&) { return eType_BOOL; } - static EType TranslateType(const int&) { return eType_INT; } - static EType TranslateType(const float&) { return eType_FLOAT; } - static EType TranslateType(const AZStd::string&) { return eType_STRING; } + static constexpr EType TranslateType(const bool&) { return eType_BOOL; } + static constexpr EType TranslateType(const int&) { return eType_INT; } + static constexpr EType TranslateType(const float&) { return eType_FLOAT; } + static constexpr EType TranslateType(const AZStd::string&) { return eType_STRING; } protected: EType m_type; - uint8 m_flags; + AZ::u8 m_flags; AZStd::string m_name; AZStd::string m_description; void* m_ptr; ICVar* m_pCVar; }; - // Typed wrapper for config variable - template - class TConfigVar - : public IConfigVar - { - private: - T m_default; - - public: - TConfigVar(const char* szName, const char* szDescription, uint8 flags, T& ptr, const T& defaultValue) - : IConfigVar(szName, szDescription, IConfigVar::TranslateType(ptr), flags) - , m_default(defaultValue) - { - m_ptr = &ptr; - - // reset to default value on initializations - ptr = defaultValue; - } - - virtual void Get(void* outPtr) const - { - *reinterpret_cast(outPtr) = *reinterpret_cast(m_ptr); - } - - virtual void Set(const void* ptr) - { - *reinterpret_cast(m_ptr) = *reinterpret_cast(ptr); - } - - virtual void Reset() - { - *reinterpret_cast(m_ptr) = m_default; - } - - virtual void GetDefault(void* outPtr) const - { - *reinterpret_cast(outPtr) = m_default; - } - - virtual bool IsDefault() const - { - return *reinterpret_cast(m_ptr) == m_default; - } - }; - // Group of configuration variables with optional mapping to CVars class CConfigGroup { private: - typedef std::vector TConfigVariables; + using TConfigVariables = AZStd::vector ; TConfigVariables m_vars; - typedef std::vector TConsoleVariables; + using TConsoleVariables = AZStd::vector; TConsoleVariables m_consoleVars; public: @@ -142,20 +101,13 @@ namespace Config virtual ~CConfigGroup(); void AddVar(IConfigVar* var); - uint32 GetVarCount(); + AZ::u32 GetVarCount(); IConfigVar* GetVar(const char* szName); - IConfigVar* GetVar(uint index); + IConfigVar* GetVar(AZ::u32 index); const IConfigVar* GetVar(const char* szName) const; - const IConfigVar* GetVar(uint index) const; + const IConfigVar* GetVar(AZ::u32 index) const; void SaveToXML(XmlNodeRef node); void LoadFromXML(XmlNodeRef node); - - template - void AddVar(const char* szName, const char* szDescription, T& var, const T& defaultValue, uint8 flags = 0) - { - AddVar(new TConfigVar(szName, szDescription, flags, var, defaultValue)); - } }; }; -#endif // CRYINCLUDE_EDITOR_CONFIGGROUP_H diff --git a/Code/Editor/Controls/ReflectedPropertyControl/PropertyMiscCtrl.h b/Code/Editor/Controls/ReflectedPropertyControl/PropertyMiscCtrl.h index 5ec24b679d..849e44cedd 100644 --- a/Code/Editor/Controls/ReflectedPropertyControl/PropertyMiscCtrl.h +++ b/Code/Editor/Controls/ReflectedPropertyControl/PropertyMiscCtrl.h @@ -6,8 +6,6 @@ * */ -#ifndef CRYINCLUDE_EDITOR_UTILS_PROPERTYMISCCTRL_H -#define CRYINCLUDE_EDITOR_UTILS_PROPERTYMISCCTRL_H #pragma once #if !defined(Q_MOC_RUN) @@ -53,6 +51,7 @@ private: class UserPopupWidgetHandler : public QObject, public AzToolsFramework::PropertyHandler < CReflectedVarUser, UserPropertyEditor> { + Q_OBJECT public: AZ_CLASS_ALLOCATOR(UserPopupWidgetHandler, AZ::SystemAllocator, 0); bool IsDefaultHandler() const override { return false; } @@ -67,6 +66,7 @@ public: class FloatCurveHandler : public QObject, public AzToolsFramework::PropertyHandler < CReflectedVarSpline, CSplineCtrl> { + Q_OBJECT public: AZ_CLASS_ALLOCATOR(FloatCurveHandler, AZ::SystemAllocator, 0); bool IsDefaultHandler() const override { return false; } @@ -80,5 +80,3 @@ public: void OnSplineChange(CSplineCtrl*); }; - -#endif // CRYINCLUDE_EDITOR_UTILS_PROPERTYMISCCTRL_H diff --git a/Code/Editor/Controls/ReflectedPropertyControl/PropertyResourceCtrl.cpp b/Code/Editor/Controls/ReflectedPropertyControl/PropertyResourceCtrl.cpp index c5ccc599d6..d26e978ae8 100644 --- a/Code/Editor/Controls/ReflectedPropertyControl/PropertyResourceCtrl.cpp +++ b/Code/Editor/Controls/ReflectedPropertyControl/PropertyResourceCtrl.cpp @@ -58,17 +58,9 @@ private: void OnClicked() override { QString tempValue(""); - QString ext(""); - if (m_path.isEmpty() == false) + if (!m_path.isEmpty() && !Path::GetExt(m_path).isEmpty()) { - if (Path::GetExt(m_path) == "") - { - tempValue = ""; - } - else - { - tempValue = m_path; - } + tempValue = m_path; } AssetSelectionModel selection; diff --git a/Code/Editor/Controls/ReflectedPropertyControl/PropertyResourceCtrl.h b/Code/Editor/Controls/ReflectedPropertyControl/PropertyResourceCtrl.h index fa30eba034..087ee9f1db 100644 --- a/Code/Editor/Controls/ReflectedPropertyControl/PropertyResourceCtrl.h +++ b/Code/Editor/Controls/ReflectedPropertyControl/PropertyResourceCtrl.h @@ -99,6 +99,7 @@ class FileResourceSelectorWidgetHandler : QObject , public AzToolsFramework::PropertyHandler < CReflectedVarResource, FileResourceSelectorWidget > { + Q_OBJECT public: AZ_CLASS_ALLOCATOR(FileResourceSelectorWidgetHandler, AZ::SystemAllocator, 0); diff --git a/Code/Editor/Controls/ReflectedPropertyControl/ReflectedVarWrapper.cpp b/Code/Editor/Controls/ReflectedPropertyControl/ReflectedVarWrapper.cpp index b3f1b35461..2320938f08 100644 --- a/Code/Editor/Controls/ReflectedPropertyControl/ReflectedVarWrapper.cpp +++ b/Code/Editor/Controls/ReflectedPropertyControl/ReflectedVarWrapper.cpp @@ -446,41 +446,54 @@ void ReflectedVarUserAdapter::SetVariable(IVariable *pVariable) m_reflectedVar.reset(new CReflectedVarUser( pVariable->GetHumanName().toUtf8().data())); } -void ReflectedVarUserAdapter::SyncReflectedVarToIVar(IVariable *pVariable) +void ReflectedVarUserAdapter::SyncReflectedVarToIVar(IVariable* pVariable) { QString value; pVariable->Get(value); m_reflectedVar->m_value = value.toUtf8().data(); - //extract the list of custom items from the IVariable user data - IVariable::IGetCustomItems* pGetCustomItems = static_cast (pVariable->GetUserData().value()); - if (pGetCustomItems != nullptr) - { - std::vector items; - QString dlgTitle; - // call the user supplied callback to fill-in items and get dialog title - bool bShowIt = pGetCustomItems->GetItems(pVariable, items, dlgTitle); - if (bShowIt) // if func didn't veto, show the dialog - { - m_reflectedVar->m_enableEdit = true; - m_reflectedVar->m_useTree = pGetCustomItems->UseTree(); - m_reflectedVar->m_treeSeparator = pGetCustomItems->GetTreeSeparator(); - m_reflectedVar->m_dialogTitle = dlgTitle.toUtf8().data(); - m_reflectedVar->m_itemNames.resize(items.size()); - m_reflectedVar->m_itemDescriptions.resize(items.size()); - - QByteArray ba; - int i = -1; - std::generate(m_reflectedVar->m_itemNames.begin(), m_reflectedVar->m_itemNames.end(), [&items, &i, &ba]() { ++i; ba = items[i].name.toUtf8(); return ba.data(); }); - i = -1; - std::generate(m_reflectedVar->m_itemDescriptions.begin(), m_reflectedVar->m_itemDescriptions.end(), [&items, &i, &ba]() { ++i; ba = items[i].desc.toUtf8(); return ba.data(); }); - - } - } - else + // extract the list of custom items from the IVariable user data + IVariable::IGetCustomItems* pGetCustomItems = static_cast(pVariable->GetUserData().value()); + if (pGetCustomItems == nullptr) { m_reflectedVar->m_enableEdit = false; + return; } + + std::vector items; + QString dlgTitle; + // call the user supplied callback to fill-in items and get dialog title + bool bShowIt = pGetCustomItems->GetItems(pVariable, items, dlgTitle); + if (!bShowIt) // if func vetoed it, don't show the dialog + { + return; + } + m_reflectedVar->m_enableEdit = true; + m_reflectedVar->m_useTree = pGetCustomItems->UseTree(); + m_reflectedVar->m_treeSeparator = pGetCustomItems->GetTreeSeparator(); + m_reflectedVar->m_dialogTitle = dlgTitle.toUtf8().data(); + m_reflectedVar->m_itemNames.resize(items.size()); + m_reflectedVar->m_itemDescriptions.resize(items.size()); + + QByteArray ba; + int i = -1; + AZStd::generate( + m_reflectedVar->m_itemNames.begin(), m_reflectedVar->m_itemNames.end(), + [&items, &i, &ba]() + { + ++i; + ba = items[i].name.toUtf8(); + return ba.data(); + }); + i = -1; + AZStd::generate( + m_reflectedVar->m_itemDescriptions.begin(), m_reflectedVar->m_itemDescriptions.end(), + [&items, &i, &ba]() + { + ++i; + ba = items[i].desc.toUtf8(); + return ba.data(); + }); } void ReflectedVarUserAdapter::SyncIVarToReflectedVar(IVariable *pVariable) diff --git a/Code/Editor/Controls/TimelineCtrl.cpp b/Code/Editor/Controls/TimelineCtrl.cpp index aa30c326ac..3890b46b2b 100644 --- a/Code/Editor/Controls/TimelineCtrl.cpp +++ b/Code/Editor/Controls/TimelineCtrl.cpp @@ -126,7 +126,6 @@ void TimelineWidget::DrawTicks(QPainter* painter) const QPen pOldPen = painter->pen(); const QPen ltgray(QColor(110, 110, 110)); - const QPen black(palette().color(QPalette::Normal, QPalette::Text)); const QPen redpen(QColor(255, 0, 255)); // Draw time ticks every tick step seconds. @@ -598,7 +597,6 @@ void TimelineWidget::DrawSecondTicks(QPainter* painter) { const QPen ltgray(QColor(110, 110, 110)); const QPen black(palette().color(QPalette::Normal, QPalette::Text)); - const QPen redpen(QColor(255, 0, 255)); for (int gx = m_grid.firstGridLine.x(); gx < m_grid.firstGridLine.x() + m_grid.numGridLines.x() + 1; gx++) { diff --git a/Code/Editor/Core/LevelEditorMenuHandler.cpp b/Code/Editor/Core/LevelEditorMenuHandler.cpp index fdde1ac305..70aff10f87 100644 --- a/Code/Editor/Core/LevelEditorMenuHandler.cpp +++ b/Code/Editor/Core/LevelEditorMenuHandler.cpp @@ -832,7 +832,7 @@ QAction* LevelEditorMenuHandler::CreateViewPaneAction(const QtViewPane* view) if (view->m_options.showOnToolsToolbar) { - action->setIcon(QIcon(view->m_options.toolbarIcon)); + action->setIcon(QIcon(view->m_options.toolbarIcon.c_str())); } m_actionManager->AddAction(view->m_id, action); diff --git a/Code/Editor/CryEdit.cpp b/Code/Editor/CryEdit.cpp index e4138da932..b47febd5ed 100644 --- a/Code/Editor/CryEdit.cpp +++ b/Code/Editor/CryEdit.cpp @@ -45,6 +45,7 @@ AZ_POP_DISABLE_WARNING // AzCore #include +#include #include #include #include @@ -547,7 +548,6 @@ public: { "BatchMode", m_bConsoleMode }, { "NullRenderer", m_bNullRenderer }, { "devmode", m_bDeveloperMode }, - { "VTUNE", dummy }, { "runpython", m_bRunPythonScript }, { "runpythontest", m_bRunPythonTestScript }, { "version", m_bShowVersionInfo }, @@ -1686,6 +1686,11 @@ bool CCryEditApp::InitInstance() return false; } + if (AZ::SettingsRegistryInterface* settingsRegistry = AZ::SettingsRegistry::Get()) + { + AZ::ComponentApplicationLifecycle::SignalEvent(*settingsRegistry, "LegacySystemInterfaceCreated", R"({})"); + } + // Process some queued events come from system init // Such as asset catalog loaded notification. // There are some systems need to load configurations from assets for post initialization but before loading level @@ -4137,7 +4142,15 @@ extern "C" int AZ_DLL_EXPORT CryEditMain(int argc, char* argv[]) AzQtComponents::Utilities::HandleDpiAwareness(AzQtComponents::Utilities::SystemDpiAware); Editor::EditorQtApplication* app = Editor::EditorQtApplication::newInstance(argc, argv); - if (app->arguments().contains("-autotest_mode")) + QStringList qArgs = app->arguments(); + const bool is_automated_test = AZStd::any_of(qArgs.begin(), qArgs.end(), + [](const QString& elem) + { + return elem.endsWith("autotest_mode") || elem.endsWith("runpythontest"); + } + ); + + if (is_automated_test) { // Nullroute all stdout to null for automated tests, this way we make sure // that the test result output is not polluted with unrelated output data. diff --git a/Code/Editor/CryEdit.h b/Code/Editor/CryEdit.h index f68cfdc33d..53ee8f1905 100644 --- a/Code/Editor/CryEdit.h +++ b/Code/Editor/CryEdit.h @@ -431,6 +431,7 @@ public: class CCrySingleDocTemplate : public QObject { + Q_OBJECT private: explicit CCrySingleDocTemplate(const QMetaObject* pDocClass) : QObject() diff --git a/Code/Editor/CryEditDoc.cpp b/Code/Editor/CryEditDoc.cpp index 4944c3bff5..212606c737 100644 --- a/Code/Editor/CryEditDoc.cpp +++ b/Code/Editor/CryEditDoc.cpp @@ -60,15 +60,6 @@ #include #include // for LmbrCentral::EditorLightComponentRequestBus -//#define PROFILE_LOADING_WITH_VTUNE - -// profilers api. -//#include "pure.h" -#ifdef PROFILE_LOADING_WITH_VTUNE -#include "C:\Program Files\Intel\Vtune\Analyzer\Include\VTuneApi.h" -#pragma comment(lib,"C:\\Program Files\\Intel\\Vtune\\Analyzer\\Lib\\VTuneApi.lib") -#endif - static const char* kAutoBackupFolder = "_autobackup"; static const char* kHoldFolder = "$tmp_hold"; // conform to the ignored file types $tmp[0-9]*_ regex static const char* kSaveBackupFolder = "_savebackup"; @@ -408,9 +399,6 @@ void CCryEditDoc::Load(TDocMultiArchive& arrXmlAr, const QString& szFilename) int t0 = GetTickCount(); -#ifdef PROFILE_LOADING_WITH_VTUNE - VTResume(); -#endif // Load level-specific audio data. AZStd::string levelFileName{ fileName.toUtf8().constData() }; AZStd::to_lower(levelFileName.begin(), levelFileName.end()); @@ -484,10 +472,6 @@ void CCryEditDoc::Load(TDocMultiArchive& arrXmlAr, const QString& szFilename) CSurfaceTypeValidator().Validate(); -#ifdef PROFILE_LOADING_WITH_VTUNE - VTPause(); -#endif - LogLoadTime(GetTickCount() - t0); // Loaded with success, remove event from log file GetIEditor()->GetSettingsManager()->UnregisterEvent(loadEvent); diff --git a/Code/Editor/CustomResolutionDlg.h b/Code/Editor/CustomResolutionDlg.h index 5dd9acaae8..e1b8035c65 100644 --- a/Code/Editor/CustomResolutionDlg.h +++ b/Code/Editor/CustomResolutionDlg.h @@ -12,8 +12,6 @@ // Notice : Refer to ViewportTitleDlg.cpp for a use case. -#ifndef CRYINCLUDE_EDITOR_CUSTOMRESOLUTIONDLG_H -#define CRYINCLUDE_EDITOR_CUSTOMRESOLUTIONDLG_H #pragma once #if !defined(Q_MOC_RUN) @@ -28,6 +26,7 @@ namespace Ui class CCustomResolutionDlg : public QDialog { + Q_OBJECT public: CCustomResolutionDlg(int w, int h, QWidget* pParent = nullptr); ~CCustomResolutionDlg(); @@ -42,5 +41,3 @@ protected: QScopedPointer m_ui; }; - -#endif // CRYINCLUDE_EDITOR_CUSTOMRESOLUTIONDLG_H diff --git a/Code/Editor/CustomizeKeyboardDialog.cpp b/Code/Editor/CustomizeKeyboardDialog.cpp index ce09f8d878..280d6c5323 100644 --- a/Code/Editor/CustomizeKeyboardDialog.cpp +++ b/Code/Editor/CustomizeKeyboardDialog.cpp @@ -211,9 +211,9 @@ public: void Reset(QAction& action) { - emit beginResetModel(); + beginResetModel(); m_action = &action; - emit endResetModel(); + endResetModel(); } private: @@ -266,7 +266,7 @@ QStringList CustomizeKeyboardDialog::BuildModels(QWidget* parent) categories.append(category); QMenu* menu = menuAction->menu(); - m_menuActions[category] = GetAllActionsForMenu(menu, QStringLiteral("")); + m_menuActions[category] = GetAllActionsForMenu(menu, QString()); } return categories; diff --git a/Code/Editor/ErrorDialog.cpp b/Code/Editor/ErrorDialog.cpp index 0a45b2bf89..e43df75947 100644 --- a/Code/Editor/ErrorDialog.cpp +++ b/Code/Editor/ErrorDialog.cpp @@ -25,9 +25,9 @@ namespace SandboxEditor connect(m_ui->okButton, &QPushButton::clicked, this, &ErrorDialog::OnOK); connect( m_ui->messages, - SIGNAL(itemSelectionChanged()), + &QTreeWidget::itemSelectionChanged, this, - SLOT(MessageSelectionChanged())); + &ErrorDialog::MessageSelectionChanged); } ErrorDialog::~ErrorDialog() diff --git a/Code/Editor/KeyboardCustomizationSettings.cpp b/Code/Editor/KeyboardCustomizationSettings.cpp index c4f9133f66..81d9375850 100644 --- a/Code/Editor/KeyboardCustomizationSettings.cpp +++ b/Code/Editor/KeyboardCustomizationSettings.cpp @@ -240,7 +240,7 @@ QJsonObject KeyboardCustomizationSettings::ExportGroup() void KeyboardCustomizationSettings::ImportFromFile(QWidget* parent) { - QString fileName = QFileDialog::getOpenFileName(parent, QObject::tr("Export Keyboard Shortcuts"), QStringLiteral(""), QObject::tr("Keyboard Settings (*.keys)")); + QString fileName = QFileDialog::getOpenFileName(parent, QObject::tr("Export Keyboard Shortcuts"), QString(), QObject::tr("Keyboard Settings (*.keys)")); if (fileName.isEmpty()) { return; diff --git a/Code/Editor/Lib/Tests/test_ViewportManipulatorController.cpp b/Code/Editor/Lib/Tests/test_ViewportManipulatorController.cpp index 8c7023634e..63e59ea940 100644 --- a/Code/Editor/Lib/Tests/test_ViewportManipulatorController.cpp +++ b/Code/Editor/Lib/Tests/test_ViewportManipulatorController.cpp @@ -85,6 +85,7 @@ namespace UnitTest m_rootWidget = AZStd::make_unique(); m_rootWidget->setFixedSize(QSize(100, 100)); + QApplication::setActiveWindow(m_rootWidget.get()); m_controllerList = AZStd::make_shared(); m_controllerList->RegisterViewportContext(TestViewportId); @@ -100,6 +101,8 @@ namespace UnitTest m_controllerList.reset(); m_rootWidget.reset(); + QApplication::setActiveWindow(nullptr); + AllocatorsTestFixture::TearDown(); } @@ -110,7 +113,7 @@ namespace UnitTest const AzFramework::ViewportId ViewportManipulatorControllerFixture::TestViewportId = AzFramework::ViewportId(0); - TEST_F(ViewportManipulatorControllerFixture, An_event_is_not_propagated_to_the_viewport_when_a_manipulator_handles_it_first) + TEST_F(ViewportManipulatorControllerFixture, AnEventIsNotPropagatedToTheViewportWhenAManipulatorHandlesItFirst) { // forward input events to our controller list QObject::connect( @@ -151,4 +154,77 @@ namespace UnitTest editorInteractionViewportFake.Disconnect(); } + + TEST_F(ViewportManipulatorControllerFixture, ChangingFocusDoesNotClearInput) + { + bool endedEvent = false; + // detect input events and ensure that the Alt key press does not end before the end of the test + QObject::connect( + m_inputChannelMapper.get(), &AzToolsFramework::QtEventToAzInputMapper::InputChannelUpdated, m_rootWidget.get(), + [&endedEvent](const AzFramework::InputChannel* inputChannel, [[maybe_unused]] QEvent* event) + { + if (inputChannel->GetInputChannelId() == AzFramework::InputDeviceKeyboard::Key::ModifierAltL && + inputChannel->IsStateEnded()) + { + endedEvent = true; + } + }); + + // given + auto* secondaryWidget = new QWidget(m_rootWidget.get()); + + m_rootWidget->show(); + secondaryWidget->show(); + + m_rootWidget->setFocus(); + + // simulate a key press when root widget has focus + QTest::keyPress(m_rootWidget.get(), Qt::Key_Alt, Qt::KeyboardModifier::AltModifier); + + // when + // change focus to secondary widget + secondaryWidget->setFocus(); + + // then + // the alt key was not released (cleared) + EXPECT_FALSE(endedEvent); + } + + // note: Application State Change includes events such as switching to another application or minimizing + // the current application + TEST_F(ViewportManipulatorControllerFixture, ApplicationStateChangeDoesClearInput) + { + bool endedEvent = false; + // detect input events and ensure that the Alt key press does not end before the end of the test + QObject::connect( + m_inputChannelMapper.get(), &AzToolsFramework::QtEventToAzInputMapper::InputChannelUpdated, m_rootWidget.get(), + [&endedEvent](const AzFramework::InputChannel* inputChannel, [[maybe_unused]] QEvent* event) + { + if (inputChannel->GetInputChannelId() == AzFramework::InputDeviceKeyboard::Key::AlphanumericW && + inputChannel->IsStateEnded()) + { + endedEvent = true; + } + }); + + // given + auto* secondaryWidget = new QWidget(m_rootWidget.get()); + + m_rootWidget->show(); + secondaryWidget->show(); + + m_rootWidget->setFocus(); + + // simulate a key press when root widget has focus + QTest::keyPress(m_rootWidget.get(), Qt::Key_W); + + // when + // simulate changing the window state + QApplicationStateChangeEvent applicationStateChangeEvent(Qt::ApplicationState::ApplicationInactive); + QCoreApplication::sendEvent(m_rootWidget.get(), &applicationStateChangeEvent); + + // then + // the key was released (cleared) + EXPECT_TRUE(endedEvent); + } } // namespace UnitTest diff --git a/Code/Editor/MainStatusBar.cpp b/Code/Editor/MainStatusBar.cpp index f955035bd8..acc7f664df 100644 --- a/Code/Editor/MainStatusBar.cpp +++ b/Code/Editor/MainStatusBar.cpp @@ -419,7 +419,7 @@ void MemoryStatusItem::updateStatus() GeneralStatusItem::GeneralStatusItem(QString name, MainStatusBar* parent) : StatusBarItem(name, parent) { - connect(parent, SIGNAL(messageChanged(QString)), this, SLOT(update())); + connect(parent, &MainStatusBar::messageChanged, this, [this](const QString&) { update(); }); } QString GeneralStatusItem::CurrentText() const diff --git a/Code/Editor/NewLevelDialog.cpp b/Code/Editor/NewLevelDialog.cpp index 29445cef2b..c773acdb6f 100644 --- a/Code/Editor/NewLevelDialog.cpp +++ b/Code/Editor/NewLevelDialog.cpp @@ -100,9 +100,10 @@ CNewLevelDialog::CNewLevelDialog(QWidget* pParent /*=nullptr*/) m_level = ""; // First of all, keyboard focus is related to widget tab order, and the default tab order is based on the order in which // widgets are constructed. Therefore, creating more widgets changes the keyboard focus. That is why setFocus() is called last. - // Secondly, using singleShot() allows setFocus() slot of the QLineEdit instance to be invoked right after the event system - // is ready to do so. Therefore, it is better to use singleShot() than directly call setFocus(). - QTimer::singleShot(0, ui->LEVEL, SLOT(OnStartup())); + // in OnStartup() + // Secondly, using singleShot() allows OnStartup() slot of the QLineEdit instance to be invoked right after the event system + // is ready to do so. Therefore, it is better to use singleShot() than directly call OnStartup(). + QTimer::singleShot(0, this, &CNewLevelDialog::OnStartup); ReloadLevelFolder(); } diff --git a/Code/Editor/Platform/Mac/Editor/Core/QtEditorApplication_mac.mm b/Code/Editor/Platform/Mac/Editor/Core/QtEditorApplication_mac.mm index a7f59b7ac8..c664d13c79 100644 --- a/Code/Editor/Platform/Mac/Editor/Core/QtEditorApplication_mac.mm +++ b/Code/Editor/Platform/Mac/Editor/Core/QtEditorApplication_mac.mm @@ -9,7 +9,7 @@ #import #include "EditorDefs.h" -#include "QtEditorApplication.h" +#include "QtEditorApplication_mac.h" // AzFramework #include diff --git a/Code/Editor/Plugins/ComponentEntityEditorPlugin/SandboxIntegration.cpp b/Code/Editor/Plugins/ComponentEntityEditorPlugin/SandboxIntegration.cpp index 41e950a058..82d9f9ede2 100644 --- a/Code/Editor/Plugins/ComponentEntityEditorPlugin/SandboxIntegration.cpp +++ b/Code/Editor/Plugins/ComponentEntityEditorPlugin/SandboxIntegration.cpp @@ -38,6 +38,7 @@ #include #include #include +#include #include #include #include @@ -642,6 +643,9 @@ void SandboxIntegrationManager::PopulateEditorGlobalContextMenu(QMenu* menu, con AzToolsFramework::EntityIdList selected; GetSelectedOrHighlightedEntities(selected); + bool prefabSystemEnabled = false; + AzFramework::ApplicationRequests::Bus::BroadcastResult(prefabSystemEnabled, &AzFramework::ApplicationRequests::IsPrefabSystemEnabled); + QAction* action = nullptr; // when nothing is selected, entity is created at root level @@ -658,18 +662,20 @@ void SandboxIntegrationManager::PopulateEditorGlobalContextMenu(QMenu* menu, con // when a single entity is selected, entity is created as its child else if (selected.size() == 1) { - action = menu->addAction(QObject::tr("Create entity")); - QObject::connect( - action, &QAction::triggered, action, - [selected] - { - EBUS_EVENT(AzToolsFramework::EditorRequests::Bus, CreateNewEntityAsChild, selected.front()); - }); + auto containerEntityInterface = AZ::Interface::Get(); + if (!prefabSystemEnabled || (containerEntityInterface && containerEntityInterface->IsContainerOpen(selected.front()))) + { + action = menu->addAction(QObject::tr("Create entity")); + QObject::connect( + action, &QAction::triggered, action, + [selected] + { + AzToolsFramework::EditorRequestBus::Broadcast(&AzToolsFramework::EditorRequestBus::Handler::CreateNewEntityAsChild, selected.front()); + } + ); + } } - bool prefabSystemEnabled = false; - AzFramework::ApplicationRequests::Bus::BroadcastResult(prefabSystemEnabled, &AzFramework::ApplicationRequests::IsPrefabSystemEnabled); - if (!prefabSystemEnabled) { menu->addSeparator(); @@ -1788,6 +1794,11 @@ AZStd::string SandboxIntegrationManager::GetComponentEditorIcon(const AZ::Uuid& return iconPath; } +AZStd::string SandboxIntegrationManager::GetComponentTypeEditorIcon(const AZ::Uuid& componentType) +{ + return GetComponentEditorIcon(componentType, nullptr); +} + AZStd::string SandboxIntegrationManager::GetComponentIconPath(const AZ::Uuid& componentType, AZ::Crc32 componentIconAttrib, AZ::Component* component) { diff --git a/Code/Editor/Plugins/ComponentEntityEditorPlugin/SandboxIntegration.h b/Code/Editor/Plugins/ComponentEntityEditorPlugin/SandboxIntegration.h index 40962409a3..9afa944438 100644 --- a/Code/Editor/Plugins/ComponentEntityEditorPlugin/SandboxIntegration.h +++ b/Code/Editor/Plugins/ComponentEntityEditorPlugin/SandboxIntegration.h @@ -233,6 +233,7 @@ private: } AZStd::string GetComponentEditorIcon(const AZ::Uuid& componentType, AZ::Component* component) override; + AZStd::string GetComponentTypeEditorIcon(const AZ::Uuid& componentType) override; AZStd::string GetComponentIconPath(const AZ::Uuid& componentType, AZ::Crc32 componentIconAttrib, AZ::Component* component) override; ////////////////////////////////////////////////////////////////////////// diff --git a/Code/Editor/Plugins/ComponentEntityEditorPlugin/UI/AssetCatalogModel.cpp b/Code/Editor/Plugins/ComponentEntityEditorPlugin/UI/AssetCatalogModel.cpp index d0091f968e..df8d6db4a8 100644 --- a/Code/Editor/Plugins/ComponentEntityEditorPlugin/UI/AssetCatalogModel.cpp +++ b/Code/Editor/Plugins/ComponentEntityEditorPlugin/UI/AssetCatalogModel.cpp @@ -197,48 +197,46 @@ AssetCatalogModel::~AssetCatalogModel() AzFramework::AssetCatalogEventBus::Handler::BusDisconnect(); } -AZ::Data::AssetType AssetCatalogModel::GetAssetType(QString filename) const +AZ::Data::AssetType AssetCatalogModel::GetAssetType(const QString &filename) const { - AZ::Data::AssetType returnType = AZ::Uuid::CreateNull(); // Compare file extensions with the map created from the asset database. int dotIndex = filename.lastIndexOf('.'); - if (dotIndex >= 0) + if (dotIndex < 0) { - QString extension = filename.mid(dotIndex); - for (auto pair : m_extensionToAssetType) - { - QString qExtensions = pair.first.c_str(); - if (qExtensions.indexOf(extension) >= 0) - { - if (pair.second.size() > 1) - { - // There are multiple types with this extension. Check each handler to see if they can handle this data type. - AZStd::string azFilename = filename.toStdString().c_str(); - EBUS_EVENT(AzFramework::ApplicationRequests::Bus, MakePathAssetRootRelative, azFilename); - AZ::Data::AssetId assetId; - EBUS_EVENT_RESULT(assetId, AZ::Data::AssetCatalogRequestBus, GetAssetIdByPath, azFilename.c_str(), AZ::Data::s_invalidAssetType, false); + return AZ::Uuid::CreateNull(); + } - for (AZ::Uuid type : pair.second) - { - const AZ::Data::AssetHandler* handler = AZ::Data::AssetManager::Instance().GetHandler(type); - if (handler && handler->CanHandleAsset(assetId)) - { - returnType = type; - break; - } - } - } - else - { - returnType = pair.second[0]; - break; - } + QStringRef extension = filename.midRef(dotIndex); + for (const auto& pair : m_extensionToAssetType) + { + QString qExtensions = pair.first.c_str(); + if (qExtensions.indexOf(extension) < 0 || pair.second.empty()) + { + continue; + } + if (pair.second.size() == 1) + { + return pair.second[0]; + } + + // There are multiple types with this extension. Search for a handler that can handle this data type. + AZStd::string azFilename = filename.toStdString().c_str(); + EBUS_EVENT(AzFramework::ApplicationRequests::Bus, MakePathAssetRootRelative, azFilename); + AZ::Data::AssetId assetId; + EBUS_EVENT_RESULT(assetId, AZ::Data::AssetCatalogRequestBus, GetAssetIdByPath, azFilename.c_str(), AZ::Data::s_invalidAssetType, false); + + for (const AZ::Uuid& type : pair.second) + { + const AZ::Data::AssetHandler* handler = AZ::Data::AssetManager::Instance().GetHandler(type); + if (handler && handler->CanHandleAsset(assetId)) + { + return type; } } } - return returnType; + return AZ::Uuid::CreateNull(); } QStandardItem* AssetCatalogModel::GetPath(QString& path, bool createIfNeeded, QStandardItem* parent) @@ -419,7 +417,7 @@ AssetCatalogEntry* AssetCatalogModel::AddAsset(QString assetPath, AZ::Data::Asse // icons' memory being reclaimed and crashing the Editor. QSize size = fileIcon.actualSize(QSize(16, 16)); QIcon deepCopy = fileIcon.pixmap(size).copy(0, 0, size.width(), size.height()); - + if (!fileIcon.isNull()) { m_assetTypeToIcon[assetType] = deepCopy; diff --git a/Code/Editor/Plugins/ComponentEntityEditorPlugin/UI/AssetCatalogModel.h b/Code/Editor/Plugins/ComponentEntityEditorPlugin/UI/AssetCatalogModel.h index c9143bb259..1dffa81d67 100644 --- a/Code/Editor/Plugins/ComponentEntityEditorPlugin/UI/AssetCatalogModel.h +++ b/Code/Editor/Plugins/ComponentEntityEditorPlugin/UI/AssetCatalogModel.h @@ -110,7 +110,7 @@ protected: void SetFilterRegExp(const AZStd::string& filterType, const QRegExp& regExp); void ClearFilterRegExp(const AZStd::string& filterType = AZStd::string()); - AZ::Data::AssetType GetAssetType(QString filename) const; + AZ::Data::AssetType GetAssetType(const QString &filename) const; QStandardItem* GetPath(QString& path, bool createIfNeeded, QStandardItem* parent = nullptr); void ApplyFilter(QStandardItem* parent); diff --git a/Code/Editor/Plugins/ComponentEntityEditorPlugin/UI/ComponentPalette/ComponentDataModel.cpp b/Code/Editor/Plugins/ComponentEntityEditorPlugin/UI/ComponentPalette/ComponentDataModel.cpp index cd0fba350f..dcae6abfeb 100644 --- a/Code/Editor/Plugins/ComponentEntityEditorPlugin/UI/ComponentPalette/ComponentDataModel.cpp +++ b/Code/Editor/Plugins/ComponentEntityEditorPlugin/UI/ComponentPalette/ComponentDataModel.cpp @@ -139,7 +139,7 @@ ComponentDataModel::ComponentDataModel(QObject* parent) if (element.m_elementId == AZ::Edit::ClassElements::EditorData) { AZStd::string iconPath; - EBUS_EVENT_RESULT(iconPath, AzToolsFramework::EditorRequests::Bus, GetComponentEditorIcon, classData->m_typeId, nullptr); + AzToolsFramework::EditorRequestBus::BroadcastResult(iconPath, &AzToolsFramework::EditorRequests::GetComponentTypeEditorIcon, classData->m_typeId); if (!iconPath.empty()) { m_componentIcons[classData->m_typeId] = QIcon(iconPath.c_str()); diff --git a/Code/Editor/Plugins/ComponentEntityEditorPlugin/UI/Outliner/EntityOutliner.qss b/Code/Editor/Plugins/ComponentEntityEditorPlugin/UI/Outliner/EntityOutliner.qss index de35f91678..a7d3949527 100644 --- a/Code/Editor/Plugins/ComponentEntityEditorPlugin/UI/Outliner/EntityOutliner.qss +++ b/Code/Editor/Plugins/ComponentEntityEditorPlugin/UI/Outliner/EntityOutliner.qss @@ -11,7 +11,6 @@ OutlinerWidget #m_display_options { qproperty-icon: url(:/Menu/menu.svg); qproperty-iconSize: 16px 16px; - qproperty-flat: true; } OutlinerWidget QWidget[PulseHighlight="true"] diff --git a/Code/Editor/TrackView/TrackViewNodes.cpp b/Code/Editor/TrackView/TrackViewNodes.cpp index 2a2d584e46..db65abf9d2 100644 --- a/Code/Editor/TrackView/TrackViewNodes.cpp +++ b/Code/Editor/TrackView/TrackViewNodes.cpp @@ -408,7 +408,7 @@ CTrackViewNodesCtrl::CTrackViewNodesCtrl(QWidget* hParentWnd, CTrackViewDialog* serializeContext->EnumerateDerived([this](const AZ::SerializeContext::ClassData* classData, const AZ::Uuid&) -> bool { AZStd::string iconPath; - EBUS_EVENT_RESULT(iconPath, AzToolsFramework::EditorRequests::Bus, GetComponentEditorIcon, classData->m_typeId, nullptr); + AzToolsFramework::EditorRequestBus::BroadcastResult(iconPath, &AzToolsFramework::EditorRequests::GetComponentTypeEditorIcon, classData->m_typeId); if (!iconPath.empty()) { m_componentTypeToIconMap[classData->m_typeId] = QIcon(iconPath.c_str()); diff --git a/Code/Framework/AzCore/AzCore/Asset/AssetManagerBus.h b/Code/Framework/AzCore/AzCore/Asset/AssetManagerBus.h index f8e263d6b0..f76ea19589 100644 --- a/Code/Framework/AzCore/AzCore/Asset/AssetManagerBus.h +++ b/Code/Framework/AzCore/AzCore/Asset/AssetManagerBus.h @@ -65,8 +65,35 @@ namespace AZ ////////////////////////////////////////////////////////////////////////// // EBusTraits overrides - Application is a singleton - static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single; - typedef AZStd::recursive_mutex MutexType; + static constexpr AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single; + using MutexType = AZStd::recursive_mutex; + + static constexpr bool EnableEventQueue = true; + using EventQueueMutexType = AZStd::mutex; + struct PostThreadDispatchInvoker + { + ~PostThreadDispatchInvoker(); + }; + + template + struct ThreadDispatchLockGuard + { + ThreadDispatchLockGuard(DispatchMutex& contextMutex) + : m_lock{ contextMutex } + {} + ThreadDispatchLockGuard(DispatchMutex& contextMutex, AZStd::adopt_lock_t adopt_lock) + : m_lock{ contextMutex, adopt_lock } + {} + ThreadDispatchLockGuard(const ThreadDispatchLockGuard&) = delete; + ThreadDispatchLockGuard& operator=(const ThreadDispatchLockGuard&) = delete; + private: + PostThreadDispatchInvoker m_threadPolicyInvoker; + using LockType = AZStd::conditional_t, AZStd::scoped_lock>; + LockType m_lock; + }; + + template + using DispatchLockGuard = ThreadDispatchLockGuard; ////////////////////////////////////////////////////////////////////////// virtual ~AssetCatalogRequests() = default; @@ -200,6 +227,17 @@ namespace AZ using AssetCatalogRequestBus = AZ::EBus; + inline AssetCatalogRequests::PostThreadDispatchInvoker::~PostThreadDispatchInvoker() + { + if (!AssetCatalogRequestBus::IsInDispatchThisThread()) + { + if (AssetCatalogRequestBus::QueuedEventCount()) + { + AssetCatalogRequestBus::ExecuteQueuedEvents(); + } + } + } + /* * Events that AssetManager listens for */ diff --git a/Code/Framework/AzCore/AzCore/Component/ComponentApplication.cpp b/Code/Framework/AzCore/AzCore/Component/ComponentApplication.cpp index ba66d56379..df8db79db0 100644 --- a/Code/Framework/AzCore/AzCore/Component/ComponentApplication.cpp +++ b/Code/Framework/AzCore/AzCore/Component/ComponentApplication.cpp @@ -14,6 +14,7 @@ #include #include +#include #include #include @@ -44,8 +45,6 @@ #include #include -#include -#include #include #include @@ -216,11 +215,6 @@ namespace AZ m_oldProjectPath = newProjectPath; // Merge the project.json file into settings registry under ProjectSettingsRootKey path. - AZ::IO::FixedMaxPath projectMetadataFile{ AZ::SettingsRegistryMergeUtils::FindEngineRoot(m_registry) / newProjectPath }; - projectMetadataFile /= "project.json"; - m_registry.MergeSettingsFile(projectMetadataFile.Native(), - AZ::SettingsRegistryInterface::Format::JsonMergePatch, AZ::SettingsRegistryMergeUtils::ProjectSettingsRootKey); - // Update all the runtime file paths based on the new "project_path" value. AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(m_registry); } @@ -506,6 +500,16 @@ namespace AZ SettingsRegistryMergeUtils::MergeSettingsToRegistry_CommandLine(*m_settingsRegistry, m_commandLine, executeRegDumpCommands); SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(*m_settingsRegistry); + // The /O3DE/Application/LifecycleEvents array contains a valid set of lifecycle events + // Those lifecycle events are normally read from the /Registry + // which isn't merged until ComponentApplication::Create invokes MergeSettingsToRegistry + // So pre-populate the valid lifecycle even entries + ComponentApplicationLifecycle::RegisterEvent(*m_settingsRegistry, "SystemAllocatorCreated"); + ComponentApplicationLifecycle::RegisterEvent(*m_settingsRegistry, "SettingsRegistryAvailable"); + ComponentApplicationLifecycle::RegisterEvent(*m_settingsRegistry, "ConsoleAvailable"); + ComponentApplicationLifecycle::SignalEvent(*m_settingsRegistry, "SystemAllocatorCreated", R"({})"); + ComponentApplicationLifecycle::SignalEvent(*m_settingsRegistry, "SettingsRegistryAvailable", R"({})"); + // Create the Module Manager m_moduleManager = AZStd::make_unique(); @@ -520,6 +524,7 @@ namespace AZ m_ownsConsole = true; m_console->LinkDeferredFunctors(AZ::ConsoleFunctorBase::GetDeferredHead()); m_settingsRegistryConsoleFunctors = AZ::SettingsRegistryConsoleUtils::RegisterAzConsoleCommands(*m_settingsRegistry, *m_console); + ComponentApplicationLifecycle::SignalEvent(*m_settingsRegistry, "ConsoleAvailable", R"({})"); } } @@ -551,6 +556,7 @@ namespace AZ { AZ::Interface::Unregister(m_console); delete m_console; + ComponentApplicationLifecycle::SignalEvent(*m_settingsRegistry, "ConsoleUnavailable", R"({})"); } m_moduleManager.reset(); @@ -558,6 +564,8 @@ namespace AZ if (AZ::SettingsRegistry::Get() == m_settingsRegistry.get()) { SettingsRegistry::Unregister(m_settingsRegistry.get()); + ComponentApplicationLifecycle::SignalEvent(*m_settingsRegistry, "SettingsRegistryUnavailable", R"({})"); + ComponentApplicationLifecycle::SignalEvent(*m_settingsRegistry, "SystemAllocatorPendingDestruction", R"({})"); } m_settingsRegistry.reset(); @@ -672,6 +680,8 @@ namespace AZ ReflectionEnvironment::GetReflectionManager()->Reflect(azrtti_typeid(this), [this](ReflectContext* context) {Reflect(context); }); RegisterCoreComponents(); + ComponentApplicationLifecycle::SignalEvent(*m_settingsRegistry, "ReflectionManagerAvailable", R"({})"); + TickBus::AllowFunctionQueuing(true); SystemTickBus::AllowFunctionQueuing(true); @@ -691,6 +701,7 @@ namespace AZ // Load the actual modules LoadModules(); + ComponentApplicationLifecycle::SignalEvent(*m_settingsRegistry, "GemsLoaded", R"({})"); // Execute user.cfg after modules have been loaded but before processing any command-line overrides AZ::IO::FixedMaxPath platformCachePath; @@ -756,12 +767,14 @@ namespace AZ m_entities.rehash(0); // force free all memory DestroyReflectionManager(); + ComponentApplicationLifecycle::SignalEvent(*m_settingsRegistry, "ReflectionManagerUnavailable", R"({})"); static_cast(m_settingsRegistry.get())->ClearNotifiers(); static_cast(m_settingsRegistry.get())->ClearMergeEvents(); // Uninit and unload any dynamic modules. m_moduleManager->UnloadModules(); + ComponentApplicationLifecycle::SignalEvent(*m_settingsRegistry, "GemsUnloaded", R"({})"); NameDictionary::Destroy(); diff --git a/Code/Framework/AzCore/AzCore/Component/ComponentApplication.h b/Code/Framework/AzCore/AzCore/Component/ComponentApplication.h index f2b1bb8905..6df93aff4e 100644 --- a/Code/Framework/AzCore/AzCore/Component/ComponentApplication.h +++ b/Code/Framework/AzCore/AzCore/Component/ComponentApplication.h @@ -175,6 +175,8 @@ namespace AZ bool m_loadDynamicModules = true; //! Used by test fixtures to ensure reflection occurs to edit context. bool m_createEditContext = false; + //! Indicates whether the AssetCatalog.xml should be loaded by default in Application::StartCommon + bool m_loadAssetCatalog = true; }; ComponentApplication(); @@ -356,7 +358,7 @@ namespace AZ /// Calculates the root directory of the engine. void CalculateEngineRoot(); - /// Calculates the directory where the bootstrap.cfg file resides. + /// Deprecated: The term "AppRoot" has no meaning void CalculateAppRoot(); template diff --git a/Code/Framework/AzCore/AzCore/Component/ComponentApplicationLifecycle.cpp b/Code/Framework/AzCore/AzCore/Component/ComponentApplicationLifecycle.cpp new file mode 100644 index 0000000000..8e15871bc0 --- /dev/null +++ b/Code/Framework/AzCore/AzCore/Component/ComponentApplicationLifecycle.cpp @@ -0,0 +1,93 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include + +namespace AZ::ComponentApplicationLifecycle +{ + bool ValidateEvent(AZ::SettingsRegistryInterface& settingsRegistry, AZStd::string_view eventName) + { + using FixedValueString = SettingsRegistryInterface::FixedValueString; + using Type = SettingsRegistryInterface::Type; + FixedValueString eventRegistrationKey{ ApplicationLifecycleEventRegistrationKey }; + eventRegistrationKey += '/'; + eventRegistrationKey += eventName; + return settingsRegistry.GetType(eventRegistrationKey) == Type::Object; + } + + bool SignalEvent(AZ::SettingsRegistryInterface& settingsRegistry, AZStd::string_view eventName, AZStd::string_view eventValue) + { + using FixedValueString = AZ::SettingsRegistryInterface::FixedValueString; + using Format = AZ::SettingsRegistryInterface::Format; + + if (!ValidateEvent(settingsRegistry, eventName)) + { + AZ_Warning("ComponentApplicationLifecycle", false, R"(Cannot signal event %.*s. Name does is not a field of object "%.*s".)" + R"( Please make sure the entry exists in the '/Registry/application_lifecycle_events.setreg")" + " or in *.setreg within the project", AZ_STRING_ARG(eventName), AZ_STRING_ARG(ApplicationLifecycleEventRegistrationKey)); + return false; + } + auto eventRegistrationKey = FixedValueString::format("%.*s/%.*s", AZ_STRING_ARG(ApplicationLifecycleEventRegistrationKey), + AZ_STRING_ARG(eventName)); + + return settingsRegistry.MergeSettings(eventValue, Format::JsonMergePatch, eventRegistrationKey); + } + + bool RegisterEvent(AZ::SettingsRegistryInterface& settingsRegistry, AZStd::string_view eventName) + { + using FixedValueString = SettingsRegistryInterface::FixedValueString; + using Format = AZ::SettingsRegistryInterface::Format; + + if (!ValidateEvent(settingsRegistry, eventName)) + { + FixedValueString eventRegistrationKey{ ApplicationLifecycleEventRegistrationKey }; + eventRegistrationKey += '/'; + eventRegistrationKey += eventName; + return settingsRegistry.MergeSettings(R"({})", Format::JsonMergePatch, eventRegistrationKey); + } + + return true; + } + + bool RegisterHandler(AZ::SettingsRegistryInterface& settingsRegistry, AZ::SettingsRegistryInterface::NotifyEventHandler& handler, + AZ::SettingsRegistryInterface::NotifyCallback callback, AZStd::string_view eventName, bool autoRegisterEvent) + { + using FixedValueString = AZ::SettingsRegistryInterface::FixedValueString; + using Type = AZ::SettingsRegistryInterface::Type; + using NotifyEventHandler = AZ::SettingsRegistryInterface::NotifyEventHandler; + + // Some systems may attempt to register a handler before the settings registry has been loaded + // If so, this flag lets them automatically register an event if it hasn't yet been registered. + // RegisterEvent calls validate event. + if ((!autoRegisterEvent && !ValidateEvent(settingsRegistry, eventName)) || + (autoRegisterEvent && !RegisterEvent(settingsRegistry, eventName))) + { + AZ_Warning( + "ComponentApplicationLifecycle", false, + R"(Cannot register event %.*s. Name is not a field of object "%.*s".)" + R"( Please make sure the entry exists in the '/Registry/application_lifecycle_events.setreg")" + " or in *.setreg within the project", AZ_STRING_ARG(eventName), AZ_STRING_ARG(ApplicationLifecycleEventRegistrationKey)); + return false; + } + auto eventNameRegistrationKey = FixedValueString::format("%.*s/%.*s", AZ_STRING_ARG(ApplicationLifecycleEventRegistrationKey), + AZ_STRING_ARG(eventName)); + auto lifecycleCallback = [callback = AZStd::move(callback), eventNameRegistrationKey](AZStd::string_view path, Type type) + { + if (path == eventNameRegistrationKey) + { + callback(path, type); + } + }; + + handler = NotifyEventHandler(AZStd::move(lifecycleCallback)); + settingsRegistry.RegisterNotifier(handler); + + return true; + } +} diff --git a/Code/Framework/AzCore/AzCore/Component/ComponentApplicationLifecycle.h b/Code/Framework/AzCore/AzCore/Component/ComponentApplicationLifecycle.h new file mode 100644 index 0000000000..6f07c0da3f --- /dev/null +++ b/Code/Framework/AzCore/AzCore/Component/ComponentApplicationLifecycle.h @@ -0,0 +1,56 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include +#include + +namespace AZ::ComponentApplicationLifecycle +{ + //! Root Key where lifecycle events should be registered under + inline constexpr AZStd::string_view ApplicationLifecycleEventRegistrationKey = "/O3DE/Application/LifecycleEvents"; + + + //! Validates that the event @eventName is stored in the array at ApplicationLifecycleEventRegistrationKey + //! @param settingsRegistry registry where @eventName will be searched + //! @param eventName name of key that validated that exists as an element in the ApplicationLifecycleEventRegistrationKey array + //! @return true if the @eventName was found in the ApplicationLifecycleEventRegistrationKey array + bool ValidateEvent(AZ::SettingsRegistryInterface& settingsRegistry, AZStd::string_view eventName); + + //! Wrapper around setting a value underneath the ApplicationLifecycleEventRegistrationKey + //! It validates if the @eventName is is part of the ApplicationLifecycleEventRegistrationKey array + //! It then appends the @eventName to the ApplicationLifecycleEventRegistrationKey merges the @eventValue into + //! the SettingsRegistry at that key + //! NOTE: This function should only be invoked from ComponentApplication and its derived classes + //! @param settingsRegistry registry where eventName should be set + //! @param eventName name of key underneath the ApplicationLifecycleEventRegistrationKey to signal + //! @param eventValue JSON Object that will be merged into the SettingsRegistry at / + //! @return true if the eventValue was successfully merged at the / + bool SignalEvent(AZ::SettingsRegistryInterface& settingsRegistry, AZStd::string_view eventName, AZStd::string_view eventValue); + + //! Register that the event @eventName is stored in the array at ApplicationLifecycleEventRegistrationKey + //! @param settingsRegistry registry where @eventName will be searched + //! @param eventName name of key that will be stored in the ApplicationLifecycleEventRegistrationKey array + //! @return true if the event passed validation or the eventName was stored in the ApplicationLifecycleEventRegistrationKey array + bool RegisterEvent(AZ::SettingsRegistryInterface& settingsRegistry, AZStd::string_view eventName); + + //! Wrapper around registering the NotifyEventHandler with the SettingsRegistry for the specified event + //! It validates if the @eventName is is part of the ApplicationLifecycleEventRegistrationKey array and if + //! so moves the @callback into @handler and then registers the handler with the SettingsRegistry NotifyEvent + //! @param settingsRegistry registry where handler will be registered + //! @param handler handler where callback will be moved into and then registered with the SettingsRegistry + //! if the specified @eventName passes validation + //! @param callback will be moved into the handler if the specified @eventName is valid + //! @param eventName name of key underneath the ApplicationLifecycleEventRegistrationKey to register + //! @param autoRegisterEvent automatically register this event if it hasn't been registered yet. This is useful + //! when registering a handler before the settings registry has been loaded. + //! @return true if the handler was registered with the SettingsRegistry NotifyEvent + bool RegisterHandler(AZ::SettingsRegistryInterface& settingsRegistry, AZ::SettingsRegistryInterface::NotifyEventHandler& handler, + AZ::SettingsRegistryInterface::NotifyCallback callback, AZStd::string_view eventName, bool autoRegisterEvent = false); +} diff --git a/Code/Framework/AzCore/AzCore/Component/TransformBus.h b/Code/Framework/AzCore/AzCore/Component/TransformBus.h index 1af77da51b..f8e30147da 100644 --- a/Code/Framework/AzCore/AzCore/Component/TransformBus.h +++ b/Code/Framework/AzCore/AzCore/Component/TransformBus.h @@ -168,6 +168,11 @@ namespace AZ //! Rotation modifiers //! @{ + //! Set the world rotation matrix using the composition of rotations around + //! the principle axes in the order of z-axis first and y-axis and then x-axis. + //! @param eulerRadianAngles A Vector3 denoting radian angles of the rotations around each principle axis. + virtual void SetWorldRotation([[maybe_unused]] const AZ::Vector3& eulerAnglesRadian) {} + //! Sets the entity's rotation in the world in quaternion notation. //! The origin of the axes is the entity's position in world space. //! @param quaternion A quaternion that represents the rotation to use for the entity. diff --git a/Code/Framework/AzCore/AzCore/Console/IConsole.h b/Code/Framework/AzCore/AzCore/Console/IConsole.h index 73d17ac65a..aef163d22b 100644 --- a/Code/Framework/AzCore/AzCore/Console/IConsole.h +++ b/Code/Framework/AzCore/AzCore/Console/IConsole.h @@ -262,6 +262,6 @@ static constexpr AZ::ThreadSafety ConsoleThreadSafety<_TYPE, std::enable_if_t Functor##_FUNCTION(#_FUNCTION, _DESC, _FLAGS | AZ::ConsoleFunctorFlags::DontDuplicate, AZ::TypeId::CreateNull(), &_FUNCTION) + inline AZ::ConsoleFunctor Functor##_FUNCTION(_NAME, _DESC, _FLAGS | AZ::ConsoleFunctorFlags::DontDuplicate, AZ::TypeId::CreateNull(), &_FUNCTION) #define AZ_CONSOLEFREEFUNC(...) AZ_MACRO_SPECIALIZE(AZ_CONSOLEFREEFUNC_, AZ_VA_NUM_ARGS(__VA_ARGS__), (__VA_ARGS__)) diff --git a/Code/Framework/AzCore/AzCore/DOM/DomVisitor.cpp b/Code/Framework/AzCore/AzCore/DOM/DomVisitor.cpp new file mode 100644 index 0000000000..5d66bb6ac5 --- /dev/null +++ b/Code/Framework/AzCore/AzCore/DOM/DomVisitor.cpp @@ -0,0 +1,239 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include + +namespace AZ::DOM +{ + const char* VisitorError::CodeToString(VisitorErrorCode code) + { + switch (code) + { + case VisitorErrorCode::UnsupportedOperation: + return "operation not supported"; + case VisitorErrorCode::InvalidData: + return "invalid data specified"; + case VisitorErrorCode::InternalError: + return "internal error"; + default: + return "unknown error"; + } + } + + VisitorError::VisitorError(VisitorErrorCode code) + : m_code(code) + { + } + + VisitorError::VisitorError(VisitorErrorCode code, AZStd::string additionalInfo) + : m_code(code) + , m_additionalInfo(AZStd::move(additionalInfo)) + { + } + + VisitorErrorCode VisitorError::GetCode() const + { + return m_code; + } + + const AZStd::string& VisitorError::GetAdditionalInfo() const + { + return m_additionalInfo; + } + + AZStd::string VisitorError::FormatVisitorErrorMessage() const + { + if (m_additionalInfo.empty()) + { + return AZStd::string::format("VisitorError: %s.", CodeToString(m_code)); + } + return AZStd::string::format("VisitorError: %s. %s.", CodeToString(m_code), m_additionalInfo.c_str()); + } + + Visitor::Result Visitor::VisitorFailure(VisitorErrorCode code) + { + return AZ::Failure(VisitorError(code)); + } + + Visitor::Result Visitor::VisitorFailure(VisitorErrorCode code, AZStd::string additionalInfo) + { + return AZ::Failure(VisitorError(code, AZStd::move(additionalInfo))); + } + + Visitor::Result Visitor::VisitorFailure(VisitorError error) + { + return AZ::Failure(error); + } + + Visitor::Result Visitor::VisitorSuccess() + { + return AZ::Success(); + } + + Visitor::Result Visitor::Null() + { + return VisitorSuccess(); + } + + Visitor::Result Visitor::Bool([[maybe_unused]] bool value) + { + return VisitorSuccess(); + } + + Visitor::Result Visitor::Int64([[maybe_unused]] AZ::s64 value) + { + return VisitorSuccess(); + } + + Visitor::Result Visitor::Uint64([[maybe_unused]] AZ::u64 value) + { + return VisitorSuccess(); + } + + Visitor::Result Visitor::Double([[maybe_unused]] double value) + { + return VisitorSuccess(); + } + + Visitor::Result Visitor::String([[maybe_unused]] AZStd::string_view value, [[maybe_unused]] Lifetime lifetime) + { + return VisitorSuccess(); + } + + Visitor::Result Visitor::OpaqueValue([[maybe_unused]] const OpaqueType& value, [[maybe_unused]] Lifetime lifetime) + { + if (!SupportsOpaqueValues()) + { + return VisitorFailure(VisitorErrorCode::UnsupportedOperation, "Opaque values are not supported by this visitor"); + } + return VisitorSuccess(); + } + + Visitor::Result Visitor::RawValue([[maybe_unused]] AZStd::string_view value, [[maybe_unused]] Lifetime lifetime) + { + if (!SupportsRawValues()) + { + return VisitorFailure(VisitorErrorCode::UnsupportedOperation, "Raw values are not supported by this visitor"); + } + return VisitorSuccess(); + } + + Visitor::Result Visitor::StartObject() + { + if (!SupportsObjects()) + { + return VisitorFailure(VisitorErrorCode::UnsupportedOperation, "Objects are not supported by this visitor"); + } + return VisitorSuccess(); + } + + Visitor::Result Visitor::EndObject([[maybe_unused]] AZ::u64 attributeCount) + { + if (!SupportsObjects()) + { + return VisitorFailure(VisitorErrorCode::UnsupportedOperation, "Objects are not supported by this visitor"); + } + return VisitorSuccess(); + } + + Visitor::Result Visitor::Key([[maybe_unused]] AZ::Name key) + { + if (!SupportsObjects() && !SupportsNodes()) + { + return VisitorFailure(VisitorErrorCode::UnsupportedOperation, "Keys are not supported by this visitor"); + } + return VisitorSuccess(); + } + + Visitor::Result Visitor::RawKey(AZStd::string_view key, [[maybe_unused]] Lifetime lifetime) + { + if (!SupportsRawKeys()) + { + return VisitorFailure(VisitorErrorCode::UnsupportedOperation, "Raw keys are not supported by this visitor"); + } + return Key(AZ::Name(key)); + } + + Visitor::Result Visitor::StartArray() + { + if (!SupportsArrays()) + { + return VisitorFailure(VisitorErrorCode::UnsupportedOperation, "Arrays are not supported by this visitor"); + } + return VisitorSuccess(); + } + + Visitor::Result Visitor::EndArray([[maybe_unused]] AZ::u64 elementCount) + { + if (!SupportsArrays()) + { + return VisitorFailure(VisitorErrorCode::UnsupportedOperation, "Arrays are not supported by this visitor"); + } + return VisitorSuccess(); + } + + Visitor::Result Visitor::StartNode([[maybe_unused]] AZ::Name name) + { + if (!SupportsNodes()) + { + return VisitorFailure(VisitorErrorCode::UnsupportedOperation, "Nodes are not supported by this visitor"); + } + return VisitorSuccess(); + } + + Visitor::Result Visitor::RawStartNode(AZStd::string_view name, [[maybe_unused]] Lifetime lifetime) + { + return StartNode(AZ::Name(name)); + } + + Visitor::Result Visitor::EndNode([[maybe_unused]] AZ::u64 attributeCount, [[maybe_unused]] AZ::u64 elementCount) + { + if (!SupportsNodes()) + { + return VisitorFailure(VisitorErrorCode::UnsupportedOperation, "Nodes are not supported by this visitor"); + } + return VisitorSuccess(); + } + + VisitorFlags Visitor::GetVisitorFlags() const + { + // By default support raw keys (promoting them to AZ::Name) and support Array / Object / Node + // We leave Opaque type support and Raw Values to more specialized, implementation-specific cases + return VisitorFlags::SupportsRawKeys | VisitorFlags::SupportsArrays | VisitorFlags::SupportsObjects | VisitorFlags::SupportsNodes; + } + + bool Visitor::SupportsRawValues() const + { + return (GetVisitorFlags() & VisitorFlags::SupportsRawValues) != VisitorFlags::Null; + } + + bool Visitor::SupportsRawKeys() const + { + return (GetVisitorFlags() & VisitorFlags::SupportsRawKeys) != VisitorFlags::Null; + } + + bool Visitor::SupportsObjects() const + { + return (GetVisitorFlags() & VisitorFlags::SupportsObjects) != VisitorFlags::Null; + } + + bool Visitor::SupportsArrays() const + { + return (GetVisitorFlags() & VisitorFlags::SupportsArrays) != VisitorFlags::Null; + } + + bool Visitor::SupportsNodes() const + { + return (GetVisitorFlags() & VisitorFlags::SupportsNodes) != VisitorFlags::Null; + } + + bool Visitor::SupportsOpaqueValues() const + { + return (GetVisitorFlags() & VisitorFlags::SupportsOpaqueValues) != VisitorFlags::Null; + } +} // namespace AZ::DOM diff --git a/Code/Framework/AzCore/AzCore/DOM/DomVisitor.h b/Code/Framework/AzCore/AzCore/DOM/DomVisitor.h new file mode 100644 index 0000000000..584cfce4ed --- /dev/null +++ b/Code/Framework/AzCore/AzCore/DOM/DomVisitor.h @@ -0,0 +1,237 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include +#include +#include +#include + +namespace AZ::DOM +{ + // + // Lifetime enum + // + //! Specifies the period in which a reference value will still be alive and safe to read. + enum class Lifetime + { + //! Specifies that the value is safe to read and will remain so indefinitely. + //! This implies that the value will not be mutated for the duration of this storage. + Persistent, + //! Specifies that the value may change or be deallocated, and must be copied to be safely stored. + Temporary, + }; + + // + // VisitorErrorCode enum + // + //! Error code specifying the reason a Visitor operation failed. + enum class VisitorErrorCode + { + //! Set when a Visitor doesn't have an implementation for a given attribute type. + //! A pure-JSON serializer might reject a Node attribute, for example, and serialization visitors + //! can forbid non-serializable Opaque types. + UnsupportedOperation, + //! Set when a Visitor has received malformed or invalid data. + //! Potential sources include mismatching Begin/End call pairs or invalid attribute or element counts + //! being sent to End methods. + InvalidData, + //! The Visitor failed for some other reason not caused by invalid input. + //! If returning a custom error with this code, it's preferrable to also provide supplemental info + //! in the form of an explanatory string. + InternalError + }; + + // + // VisitorError class + // + //! Details of the reason for failure within a VisitorInterface operation. + class VisitorError final + { + public: + explicit VisitorError(VisitorErrorCode code); + VisitorError(VisitorErrorCode code, AZStd::string additionalInfo); + + //! Gets the error code associated with this error. + VisitorErrorCode GetCode() const; + //! Gets a supplemental error info string from the error. + //! Returns an empty string if no additional information was provided to the error. + const AZStd::string& GetAdditionalInfo() const; + //! Provides a formatted, human-readable error description that can be used for logging purposes. + AZStd::string FormatVisitorErrorMessage() const; + + //! Helper method, translates a VisitorErrorCode to a human readable string. + static const char* CodeToString(VisitorErrorCode code); + + private: + VisitorErrorCode m_code; + AZStd::string m_additionalInfo; + }; + + //! A type alias for opaque DOM types that aren't meant to be serializable. + //! /see VisitorInterface::OpaqueValue + using OpaqueType = AZStd::any; + + // + // VisitorFlags enum + // + //! Flags representning capabilities of a \ref Visitor. + enum class VisitorFlags : AZ::u16 + { + //! No flags are set. This can be used in conjunction with bitwise operators to check a flag. + Null = 0, + //! If set, this Visitor interface supports raw strings in place of specific value types. + //! Visitors with this flag accept RawValue calls in lieu of more specific value calls such as Int64 or String. + SupportsRawValues = (1 << 1), + //! If set, this Visitor interface supports raw strings in place of Name types for keys and Node names. + //! Visitors with this flag accept RawKey and RawStartNode in lieu of Key and StartNode calls. + SupportsRawKeys = (1 << 2), + //! If set, this Visitor interface supports Object types described via BeginObject and EndObject. + SupportsObjects = (1 << 3), + //! If set, this Visitor interface supports Array types described via BeginArray and EndArray. + SupportsArrays = (1 << 4), + //! If set, this Visitor interface supports Node types described BeginNode and EndNode. + SupportsNodes = (1 << 4), + //! If set, this Visitor interface supports opaque values described via OpaqueValue. + SupportsOpaqueValues = (1 << 5), + }; + + AZ_DEFINE_ENUM_BITWISE_OPERATORS(VisitorFlags); + + // + // Visitor class + // + //! An interface for performing operations on elements of a generic DOM (Document Object Model). + //! A Document Object Model is defined here as a tree structure comprised of one of the following values: + //! - Primitives: plain data types, including + //! - \ref Int64: 64 bit signed integer + //! - \ref Uint64: 64 bit unsigned integer + //! - \ref Bool: boolean value + //! - \ref Double: 64 bit double precision float + //! - \ref Null: sentinel "empty" type with no value representation + //! - \ref String: UTF8 encoded string + //! - \ref Object: an ordered container of key/value pairs where keys are AZ::Names and values may be any DOM type + //! (including Object) + //! - \ref Array: an ordered container of values, in which values are any DOM value type (including Array) + //! - \ref Node: a container + //! - \ref OpaqueValue: An arbitrary value stored in an AZStd::any. This is a non-serializable representation of an + //! entry useful for in-memory options. This is intended to be used as an intermediate value over the course of DOM + //! transformation and as a proxy to pass through types of which the DOM has no knowledge to other systems. + //! + //! Opaque values are rejected by the default VisitorInterface implementation. + //! + //! Care should be ensured that DOMs representing opaque types are only visited by consumers that understand them. + class Visitor + { + public: + virtual ~Visitor() = default; + + //! The result of a Visitor operation. + //! A failure indicates a non-recoverable issue and signals that no further visit calls may be made in the + //! current state. + using Result = AZ::Outcome; + + //! Returns a set of flags representing the operations this Visitor supports. + //! The base implementation supports raw keys (\see VisitorFlags::SupportsRawKeys) and + //! arrays (\see VisitorFlags::SupportsArrays), objects (\see VisitorFlags::SupportsObjects), and + //! nodes (\see VisitorFlags::SupportsNodes). + //! Raw (\see VisitorFlags::SupportsRawValues) and opaque values (\see VisitorFlags::SupportsOpaqueValues) + //! are disallowed by default, as their handling is intended to be implementation-specific. + virtual VisitorFlags GetVisitorFlags() const; + //! /see VisitorFlags::SupportsRawValues + bool SupportsRawValues() const; + //! /see VisitorFlags::SupportsRawKeys + bool SupportsRawKeys() const; + //! /see VisitorFlags::SupportsObjects + bool SupportsObjects() const; + //! /see VisitorFlags::SupportsArrays + bool SupportsArrays() const; + //! /see VisitorFlags::SupportsNodes + bool SupportsNodes() const; + //! /see VisitorFlags::SupportsOpaqueValues + bool SupportsOpaqueValues() const; + + //! Operates on an empty null value. + virtual Result Null(); + //! Operates on a bool value. + virtual Result Bool(bool value); + //! Operates on a signed, 64 bit integer value. + virtual Result Int64(AZ::s64 value); + //! Operates on an unsigned, 64 bit integer value. + virtual Result Uint64(AZ::u64 value); + //! Operates on a double precision, 64 bit floating point value. + virtual Result Double(double value); + //! Operates on a string value. As strings are a reference type. + //! Storage semantics are provided to indicate where the value may be stored persistently or requires a copy. + virtual Result String(AZStd::string_view value, Lifetime lifetime); + //! Operates on an opaque value. As opaque values are a reference type, storage semantics are provided to + //! indicate where the value may be stored persistently or requires a copy. + //! The base implementation of OpaqueValue rejects the operation, as opaque values are meant for special + //! cases with specific implementations, not generic usage. + //! Storage semantics are provided to indicate where the value may be stored persistently or requires a copy. + virtual Result OpaqueValue(const OpaqueType& value, Lifetime lifetime); + //! Operates on a raw value encoded as a UTF-8 string that hasn't had its type deduced. + //! Visitors that support raw values (\see VisitorFlags::SupportsRawValues) may parse the raw value and + //! forward it to the corresponding value call or calls of their choice. + //! The base implementation of RawValue rejects the operation, as raw values are meant to be handled on + //! a per-implementation basis. + virtual Result RawValue(AZStd::string_view value, Lifetime lifetime); + + //! Operates on an Object. + //! Callers may make any number of Key calls, followed by calls representing a value (including a nested + //! StartObject call) and then must call EndObject. + virtual Result StartObject(); + //! Finishes operating on an Object. + //! Callers must provide the number of attributes that were provided to the object, i.e. the number of key + //! and value calls made within the direct context of this object (but not any nested objects / nodes). + virtual Result EndObject(AZ::u64 attributeCount); + + //! Specifies a key for a key/value pair. + //! Key must be called subsequent to a call to \ref StartObject or \ref StartNode and immediately followed by + //! calls representing the key's associated value. + virtual Result Key(AZ::Name key); + //! Specifies a key for a key/value pair using a raw string instead of \ref AZ::Name. + //! \see Key + virtual Result RawKey(AZStd::string_view key, Lifetime lifetime); + + //! Operates on an Array. + //! Callers may make any number of subsequent value calls to represent the elements of the array, and then must + //! call EndArray. + virtual Result StartArray(); + //! Finishes operating on an Array. + //! Callers must provide the number of elements that were provided to the array, i.e. the number of value calls + //! made within the direct context of this array (but not any nested arrays / nodes). + virtual Result EndArray(AZ::u64 elementCount); + + //! Operates on a Node. + //! Callers may make any number of Key calls followed by value calls or value calls not prefixed with a Key + //! call, and then must call EndNode. See \ref StartObject and \ref StartArray as Node types combine the + //! functionality of both structures into a named Node structure. + virtual Result StartNode(AZ::Name name); + //! Operates on a Node using a raw string instead of \ref AZ::Name. + //! \see StartNode + virtual Result RawStartNode(AZStd::string_view name, Lifetime lifetime); + //! Finishes operating on a Node. + //! Callers must provide both the number of attributes the were provided and the number of elements that were + //! provided to the node, attributes being values prefaced by a call to Key. + virtual Result EndNode(AZ::u64 attributeCount, AZ::u64 elementCount); + + protected: + Visitor() = default; + + //! Helper method, constructs a failure \ref Result with the specified code. + static Result VisitorFailure(VisitorErrorCode code); + //! Helper method, constructs a failure \ref Result with the specified code and supplemental info. + static Result VisitorFailure(VisitorErrorCode code, AZStd::string additionalInfo); + //! Helper method, constructs a failure \ref Result with the specified error. + static Result VisitorFailure(VisitorError error); + //! Helper method, constructs a success \ref Result. + static Result VisitorSuccess(); + }; +} // namespace AZ::DOM diff --git a/Code/Framework/AzCore/AzCore/Debug/Profiler.cpp b/Code/Framework/AzCore/AzCore/Debug/Profiler.cpp index 0bb92b923d..0cb77b2d6e 100644 --- a/Code/Framework/AzCore/AzCore/Debug/Profiler.cpp +++ b/Code/Framework/AzCore/AzCore/Debug/Profiler.cpp @@ -7,4 +7,63 @@ */ #include +#include +#include +#include +#include +namespace AZ::Debug +{ + AZStd::string GenerateOutputFile(const char* nameHint) + { + AZ::IO::FixedMaxPathString captureOutput = GetProfilerCaptureLocation(); + return AZStd::string::format("%s/capture_%s_%lld.json", captureOutput.c_str(), nameHint, AZStd::GetTimeNowSecond()); + } + + void ProfilerCaptureFrame([[maybe_unused]] const AZ::ConsoleCommandContainer& arguments) + { + if (auto profilerSystem = ProfilerSystemInterface::Get(); profilerSystem) + { + AZStd::string captureFile = GenerateOutputFile("single"); + AZLOG_INFO("Setting capture file to %s", captureFile.c_str()); + profilerSystem->CaptureFrame(captureFile); + } + } + AZ_CONSOLEFREEFUNC(ProfilerCaptureFrame, AZ::ConsoleFunctorFlags::DontReplicate, "Capture a single frame of profiling data"); + + void ProfilerStartCapture([[maybe_unused]] const AZ::ConsoleCommandContainer& arguments) + { + if (auto profilerSystem = ProfilerSystemInterface::Get(); profilerSystem) + { + AZStd::string captureFile = GenerateOutputFile("multi"); + AZLOG_INFO("Setting capture file to %s", captureFile.c_str()); + profilerSystem->StartCapture(AZStd::move(captureFile)); + } + } + AZ_CONSOLEFREEFUNC(ProfilerStartCapture, AZ::ConsoleFunctorFlags::DontReplicate, "Start a multi-frame capture of profiling data"); + + void ProfilerEndCapture([[maybe_unused]] const AZ::ConsoleCommandContainer& arguments) + { + if (auto profilerSystem = ProfilerSystemInterface::Get(); profilerSystem) + { + profilerSystem->EndCapture(); + } + } + AZ_CONSOLEFREEFUNC(ProfilerEndCapture, AZ::ConsoleFunctorFlags::DontReplicate, "End and dump an in-progress continuous capture"); + + AZ::IO::FixedMaxPathString GetProfilerCaptureLocation() + { + AZ::IO::FixedMaxPathString captureOutput; + if (AZ::SettingsRegistryInterface* settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry) + { + settingsRegistry->Get(captureOutput, RegistryKey_ProfilerCaptureLocation); + } + + if (captureOutput.empty()) + { + captureOutput = ProfilerCaptureLocationFallback; + } + + return captureOutput; + } +} // namespace AZ::Debug diff --git a/Code/Framework/AzCore/AzCore/Debug/ProfilerBus.h b/Code/Framework/AzCore/AzCore/Debug/ProfilerBus.h index 1537be3b81..322bfb60c2 100644 --- a/Code/Framework/AzCore/AzCore/Debug/ProfilerBus.h +++ b/Code/Framework/AzCore/AzCore/Debug/ProfilerBus.h @@ -9,11 +9,20 @@ #pragma once #include +#include +#include +#include namespace AZ { namespace Debug { + //! settings registry entry for specifying where to output profiler captures + static constexpr const char* RegistryKey_ProfilerCaptureLocation = "/O3DE/AzCore/Debug/Profiler/CaptureLocation"; + + //! fallback value in the event the settings registry isn't ready or doesn't contain the key + static constexpr const char* ProfilerCaptureLocationFallback = "@user@/Profiler"; + /** * ProfilerNotifications provides a profiler event interface that can be used to update listeners on profiler status */ @@ -23,32 +32,38 @@ namespace AZ public: virtual ~ProfilerNotifications() = default; - virtual void OnProfileSystemInitialized() = 0; + //! Notify when the current profiler capture is finished + //! @param result Set to true if it's finished successfully + //! @param info The output file path or error information which depends on the return. + virtual void OnCaptureFinished(bool result, const AZStd::string& info) = 0; }; using ProfilerNotificationBus = AZ::EBus; - enum class ProfileFrameAdvanceType - { - Game, - Render, - Default = Game - }; - /** * ProfilerRequests provides an interface for making profiling system requests */ class ProfilerRequests - : public AZ::EBusTraits { public: - // Allow multiple threads to concurrently make requests - using MutexType = AZStd::mutex; - + AZ_RTTI(ProfilerRequests, "{90AEC117-14C1-4BAE-9704-F916E49EF13F}"); virtual ~ProfilerRequests() = default; - virtual bool IsActive() = 0; - virtual void FrameAdvance(ProfileFrameAdvanceType type) = 0; + //! Getter/setter for the profiler active state + virtual bool IsActive() const = 0; + virtual void SetActive(bool active) = 0; + + //! Capture a single frame of profiling data + virtual bool CaptureFrame(const AZStd::string& outputFilePath) = 0; + + //! Starting/ending a multi-frame capture of profiling data + virtual bool StartCapture(AZStd::string outputFilePath) = 0; + virtual bool EndCapture() = 0; }; - using ProfilerRequestBus = AZ::EBus; - } -} + + using ProfilerSystemInterface = AZ::Interface; + + //! helper function for getting the profiler capture location from the settings registry that + //! includes fallback handing in the event the registry value can't be determined + AZ::IO::FixedMaxPathString GetProfilerCaptureLocation(); + } // namespace Debug +} // namespace AZ diff --git a/Code/Framework/AzCore/AzCore/Debug/ProfilerReflection.cpp b/Code/Framework/AzCore/AzCore/Debug/ProfilerReflection.cpp new file mode 100644 index 0000000000..42151a6996 --- /dev/null +++ b/Code/Framework/AzCore/AzCore/Debug/ProfilerReflection.cpp @@ -0,0 +1,92 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include + +#include +#include +#include +#include + +namespace AZ::Debug +{ + static constexpr const char* ProfilerScriptCategory = "Profiler"; + static constexpr const char* ProfilerScriptModule = "debug"; + static constexpr AZ::Script::Attributes::ScopeFlags ProfilerScriptScope = AZ::Script::Attributes::ScopeFlags::Automation; + + class ProfilerNotificationBusHandler final + : public ProfilerNotificationBus::Handler + , public AZ::BehaviorEBusHandler + { + public: + AZ_EBUS_BEHAVIOR_BINDER(ProfilerNotificationBusHandler, "{44161459-B816-4876-95A4-BA16DEC767D6}", AZ::SystemAllocator, + OnCaptureFinished + ); + + void OnCaptureFinished(bool result, const AZStd::string& info) override + { + Call(FN_OnCaptureFinished, result, info); + } + + static void Reflect(AZ::ReflectContext* context) + { + if (AZ::BehaviorContext* behaviorContext = azrtti_cast(context)) + { + behaviorContext->EBus("ProfilerNotificationBus") + ->Attribute(AZ::Script::Attributes::Category, ProfilerScriptCategory) + ->Attribute(AZ::Script::Attributes::Module, ProfilerScriptModule) + ->Attribute(AZ::Script::Attributes::Scope, ProfilerScriptScope) + ->Handler(); + } + } + }; + + class ProfilerSystemScriptProxy + : public BehaviorInterfaceProxy + { + public: + AZ_RTTI(ProfilerSystemScriptProxy, "{D671FB70-8B09-4C3A-96CD-06A339F3138E}", BehaviorInterfaceProxy); + + AZ_BEHAVIOR_INTERFACE(ProfilerSystemScriptProxy, ProfilerRequests); + }; + + void ProfilerReflect(AZ::ReflectContext* context) + { + if (AZ::BehaviorContext* behaviorContext = azrtti_cast(context)) + { + behaviorContext->ConstantProperty("g_ProfilerSystem", ProfilerSystemScriptProxy::GetProxy) + ->Attribute(AZ::Script::Attributes::Category, ProfilerScriptCategory) + ->Attribute(AZ::Script::Attributes::Module, ProfilerScriptModule) + ->Attribute(AZ::Script::Attributes::Scope, ProfilerScriptScope); + + behaviorContext->Class("ProfilerSystemInterface") + ->Attribute(AZ::Script::Attributes::Category, ProfilerScriptCategory) + ->Attribute(AZ::Script::Attributes::Module, ProfilerScriptModule) + ->Attribute(AZ::Script::Attributes::Scope, ProfilerScriptScope) + + ->Method("IsValid", &ProfilerSystemScriptProxy::IsValid) + + ->Method("GetCaptureLocation", + [](ProfilerSystemScriptProxy*) -> AZStd::string + { + AZ::IO::FixedMaxPathString captureOutput = GetProfilerCaptureLocation(); + return AZStd::string(captureOutput.c_str(), captureOutput.length()); + }) + + ->Method("IsActive", ProfilerSystemScriptProxy::WrapMethod<&ProfilerRequests::IsActive>()) + ->Method("SetActive", ProfilerSystemScriptProxy::WrapMethod<&ProfilerRequests::SetActive>()) + + ->Method("CaptureFrame", ProfilerSystemScriptProxy::WrapMethod<&ProfilerRequests::CaptureFrame>()) + + ->Method("StartCapture", ProfilerSystemScriptProxy::WrapMethod<&ProfilerRequests::StartCapture>()) + ->Method("EndCapture", ProfilerSystemScriptProxy::WrapMethod<&ProfilerRequests::EndCapture>()); + } + + ProfilerNotificationBusHandler::Reflect(context); + } +} // namespace AZ::Debug diff --git a/Code/Framework/AzCore/AzCore/Debug/ProfilerReflection.h b/Code/Framework/AzCore/AzCore/Debug/ProfilerReflection.h new file mode 100644 index 0000000000..d6857defe5 --- /dev/null +++ b/Code/Framework/AzCore/AzCore/Debug/ProfilerReflection.h @@ -0,0 +1,19 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ +#pragma once + +namespace AZ +{ + class ReflectContext; + + namespace Debug + { + //! Reflects the profiler bus script bindings + void ProfilerReflect(AZ::ReflectContext* context); + } // namespace Debug +} // namespace AZ diff --git a/Code/Framework/AzCore/AzCore/Debug/Trace.cpp b/Code/Framework/AzCore/AzCore/Debug/Trace.cpp index 357149d096..b9e4003500 100644 --- a/Code/Framework/AzCore/AzCore/Debug/Trace.cpp +++ b/Code/Framework/AzCore/AzCore/Debug/Trace.cpp @@ -27,26 +27,21 @@ #include #include -namespace AZ +namespace AZ::Debug { - namespace Debug + struct StackFrame; + + namespace Platform { - struct StackFrame; - - namespace Platform - { #if defined(AZ_ENABLE_DEBUG_TOOLS) - bool AttachDebugger(); - bool IsDebuggerPresent(); - void HandleExceptions(bool isEnabled); - void DebugBreak(); + bool AttachDebugger(); + bool IsDebuggerPresent(); + void HandleExceptions(bool isEnabled); + void DebugBreak(); #endif - void Terminate(int exitCode); - } + void Terminate(int exitCode); } - using namespace AZ::Debug; - namespace DebugInternal { // other threads can trigger fatals and errors, but the same thread should not, to avoid stack overflow. @@ -60,7 +55,7 @@ namespace AZ // Globals const int g_maxMessageLength = 4096; static const char* g_dbgSystemWnd = "System"; - Trace Debug::g_tracer; + Trace g_tracer; void* g_exceptionInfo = nullptr; // Environment var needed to track ignored asserts across systems and disable native UI under certain conditions @@ -616,4 +611,4 @@ namespace AZ val.Set(level); } } -} // namspace AZ +} // namspace AZ::Debug diff --git a/Code/Framework/AzCore/AzCore/IO/Streamer/StreamerComponent.cpp b/Code/Framework/AzCore/AzCore/IO/Streamer/StreamerComponent.cpp index 9728866fb6..9a465021c2 100644 --- a/Code/Framework/AzCore/AzCore/IO/Streamer/StreamerComponent.cpp +++ b/Code/Framework/AzCore/AzCore/IO/Streamer/StreamerComponent.cpp @@ -156,7 +156,10 @@ namespace AZ void StreamerComponent::OnTick([[maybe_unused]] float deltaTime, [[maybe_unused]] AZ::ScriptTimePoint time) { bool isEnabled = false; - AZ::Debug::ProfilerRequestBus::BroadcastResult(isEnabled, &AZ::Debug::ProfilerRequests::IsActive); + if (auto profilerSystem = AZ::Debug::ProfilerSystemInterface::Get(); profilerSystem) + { + isEnabled = profilerSystem->IsActive(); + } if (isEnabled) { diff --git a/Code/Framework/AzCore/AzCore/Math/Internal/SimdMathCommon_sse.inl b/Code/Framework/AzCore/AzCore/Math/Internal/SimdMathCommon_sse.inl index e228a19d68..8355f9bfe0 100644 --- a/Code/Framework/AzCore/AzCore/Math/Internal/SimdMathCommon_sse.inl +++ b/Code/Framework/AzCore/AzCore/Math/Internal/SimdMathCommon_sse.inl @@ -383,36 +383,36 @@ namespace AZ AZ_MATH_INLINE bool CmpAllEq(__m128 arg1, __m128 arg2, int32_t mask) { - const __m128i compare = CastToInt(CmpNeq(arg1, arg2)); - return (_mm_movemask_epi8(compare) & mask) == 0; + const __m128 compare = CmpEq(arg1, arg2); + return (_mm_movemask_ps(compare) & mask) == mask; } AZ_MATH_INLINE bool CmpAllLt(__m128 arg1, __m128 arg2, int32_t mask) { - const __m128i compare = CastToInt(CmpGtEq(arg1, arg2)); - return (_mm_movemask_epi8(compare) & mask) == 0; + const __m128 compare = CmpLt(arg1, arg2); + return (_mm_movemask_ps(compare) & mask) == mask; } AZ_MATH_INLINE bool CmpAllLtEq(__m128 arg1, __m128 arg2, int32_t mask) { - const __m128i compare = CastToInt(CmpGt(arg1, arg2)); - return (_mm_movemask_epi8(compare) & mask) == 0; + const __m128 compare = CmpLtEq(arg1, arg2); + return (_mm_movemask_ps(compare) & mask) == mask; } AZ_MATH_INLINE bool CmpAllGt(__m128 arg1, __m128 arg2, int32_t mask) { - const __m128i compare = CastToInt(CmpLtEq(arg1, arg2)); - return (_mm_movemask_epi8(compare) & mask) == 0; + const __m128 compare = CmpGt(arg1, arg2); + return (_mm_movemask_ps(compare) & mask) == mask; } AZ_MATH_INLINE bool CmpAllGtEq(__m128 arg1, __m128 arg2, int32_t mask) { - const __m128i compare = CastToInt(CmpLt(arg1, arg2)); - return (_mm_movemask_epi8(compare) & mask) == 0; + const __m128 compare = CmpGtEq(arg1, arg2); + return (_mm_movemask_ps(compare) & mask) == mask; } diff --git a/Code/Framework/AzCore/AzCore/Math/Internal/SimdMathVec1_sse.inl b/Code/Framework/AzCore/AzCore/Math/Internal/SimdMathVec1_sse.inl index ecdcf40743..bb332c03a2 100644 --- a/Code/Framework/AzCore/AzCore/Math/Internal/SimdMathVec1_sse.inl +++ b/Code/Framework/AzCore/AzCore/Math/Internal/SimdMathVec1_sse.inl @@ -331,31 +331,32 @@ namespace AZ AZ_MATH_INLINE bool Vec1::CmpAllEq(FloatArgType arg1, FloatArgType arg2) { - return Sse::CmpAllEq(arg1, arg2, 0x000F); + // Only check the first bit for Vector1 + return Sse::CmpAllEq(arg1, arg2, 0b0001); } AZ_MATH_INLINE bool Vec1::CmpAllLt(FloatArgType arg1, FloatArgType arg2) { - return Sse::CmpAllLt(arg1, arg2, 0x000F); + return Sse::CmpAllLt(arg1, arg2, 0b0001); } AZ_MATH_INLINE bool Vec1::CmpAllLtEq(FloatArgType arg1, FloatArgType arg2) { - return Sse::CmpAllLtEq(arg1, arg2, 0x000F); + return Sse::CmpAllLtEq(arg1, arg2, 0b0001); } AZ_MATH_INLINE bool Vec1::CmpAllGt(FloatArgType arg1, FloatArgType arg2) { - return Sse::CmpAllGt(arg1, arg2, 0x000F); + return Sse::CmpAllGt(arg1, arg2, 0b0001); } AZ_MATH_INLINE bool Vec1::CmpAllGtEq(FloatArgType arg1, FloatArgType arg2) { - return Sse::CmpAllGtEq(arg1, arg2, 0x000F); + return Sse::CmpAllGtEq(arg1, arg2, 0b0001); } @@ -397,7 +398,7 @@ namespace AZ AZ_MATH_INLINE bool Vec1::CmpAllEq(Int32ArgType arg1, Int32ArgType arg2) { - return Sse::CmpAllEq(arg1, arg2, 0x000F); + return Sse::CmpAllEq(arg1, arg2, 0b0001); } diff --git a/Code/Framework/AzCore/AzCore/Math/Internal/SimdMathVec2_sse.inl b/Code/Framework/AzCore/AzCore/Math/Internal/SimdMathVec2_sse.inl index c890aa5eb7..90fb97e694 100644 --- a/Code/Framework/AzCore/AzCore/Math/Internal/SimdMathVec2_sse.inl +++ b/Code/Framework/AzCore/AzCore/Math/Internal/SimdMathVec2_sse.inl @@ -383,31 +383,32 @@ namespace AZ AZ_MATH_INLINE bool Vec2::CmpAllEq(FloatArgType arg1, FloatArgType arg2) { - return Sse::CmpAllEq(arg1, arg2, 0x00FF); + // Only check the first two bits for Vector2 + return Sse::CmpAllEq(arg1, arg2, 0b0011); } AZ_MATH_INLINE bool Vec2::CmpAllLt(FloatArgType arg1, FloatArgType arg2) { - return Sse::CmpAllLt(arg1, arg2, 0x00FF); + return Sse::CmpAllLt(arg1, arg2, 0b0011); } AZ_MATH_INLINE bool Vec2::CmpAllLtEq(FloatArgType arg1, FloatArgType arg2) { - return Sse::CmpAllLtEq(arg1, arg2, 0x00FF); + return Sse::CmpAllLtEq(arg1, arg2, 0b0011); } AZ_MATH_INLINE bool Vec2::CmpAllGt(FloatArgType arg1, FloatArgType arg2) { - return Sse::CmpAllGt(arg1, arg2, 0x00FF); + return Sse::CmpAllGt(arg1, arg2, 0b0011); } AZ_MATH_INLINE bool Vec2::CmpAllGtEq(FloatArgType arg1, FloatArgType arg2) { - return Sse::CmpAllGtEq(arg1, arg2, 0x00FF); + return Sse::CmpAllGtEq(arg1, arg2, 0b0011); } diff --git a/Code/Framework/AzCore/AzCore/Math/Internal/SimdMathVec3_sse.inl b/Code/Framework/AzCore/AzCore/Math/Internal/SimdMathVec3_sse.inl index a8ff962837..31e8d19d65 100644 --- a/Code/Framework/AzCore/AzCore/Math/Internal/SimdMathVec3_sse.inl +++ b/Code/Framework/AzCore/AzCore/Math/Internal/SimdMathVec3_sse.inl @@ -419,31 +419,32 @@ namespace AZ AZ_MATH_INLINE bool Vec3::CmpAllEq(FloatArgType arg1, FloatArgType arg2) { - return Sse::CmpAllEq(arg1, arg2, 0x0FFF); + // Only check the first three bits for Vector3 + return Sse::CmpAllEq(arg1, arg2, 0b0111); } AZ_MATH_INLINE bool Vec3::CmpAllLt(FloatArgType arg1, FloatArgType arg2) { - return Sse::CmpAllLt(arg1, arg2, 0x0FFF); + return Sse::CmpAllLt(arg1, arg2, 0b0111); } AZ_MATH_INLINE bool Vec3::CmpAllLtEq(FloatArgType arg1, FloatArgType arg2) { - return Sse::CmpAllLtEq(arg1, arg2, 0x0FFF); + return Sse::CmpAllLtEq(arg1, arg2, 0b0111); } AZ_MATH_INLINE bool Vec3::CmpAllGt(FloatArgType arg1, FloatArgType arg2) { - return Sse::CmpAllGt(arg1, arg2, 0x0FFF); + return Sse::CmpAllGt(arg1, arg2, 0b0111); } AZ_MATH_INLINE bool Vec3::CmpAllGtEq(FloatArgType arg1, FloatArgType arg2) { - return Sse::CmpAllGtEq(arg1, arg2, 0x0FFF); + return Sse::CmpAllGtEq(arg1, arg2, 0b0111); } @@ -485,7 +486,7 @@ namespace AZ AZ_MATH_INLINE bool Vec3::CmpAllEq(Int32ArgType arg1, Int32ArgType arg2) { - return Sse::CmpAllEq(arg1, arg2, 0x0FFF); + return Sse::CmpAllEq(arg1, arg2, 0b0111); } diff --git a/Code/Framework/AzCore/AzCore/Math/Internal/SimdMathVec4_sse.inl b/Code/Framework/AzCore/AzCore/Math/Internal/SimdMathVec4_sse.inl index 1cf0a35d7f..f3a4feb524 100644 --- a/Code/Framework/AzCore/AzCore/Math/Internal/SimdMathVec4_sse.inl +++ b/Code/Framework/AzCore/AzCore/Math/Internal/SimdMathVec4_sse.inl @@ -455,31 +455,32 @@ namespace AZ AZ_MATH_INLINE bool Vec4::CmpAllEq(FloatArgType arg1, FloatArgType arg2) { - return Sse::CmpAllEq(arg1, arg2, 0xFFFF); + // Check the first four bits for Vector4 + return Sse::CmpAllEq(arg1, arg2, 0b1111); } AZ_MATH_INLINE bool Vec4::CmpAllLt(FloatArgType arg1, FloatArgType arg2) { - return Sse::CmpAllLt(arg1, arg2, 0xFFFF); + return Sse::CmpAllLt(arg1, arg2, 0b1111); } AZ_MATH_INLINE bool Vec4::CmpAllLtEq(FloatArgType arg1, FloatArgType arg2) { - return Sse::CmpAllLtEq(arg1, arg2, 0xFFFF); + return Sse::CmpAllLtEq(arg1, arg2, 0b1111); } AZ_MATH_INLINE bool Vec4::CmpAllGt(FloatArgType arg1, FloatArgType arg2) { - return Sse::CmpAllGt(arg1, arg2, 0xFFFF); + return Sse::CmpAllGt(arg1, arg2, 0b1111); } AZ_MATH_INLINE bool Vec4::CmpAllGtEq(FloatArgType arg1, FloatArgType arg2) { - return Sse::CmpAllGtEq(arg1, arg2, 0xFFFF); + return Sse::CmpAllGtEq(arg1, arg2, 0b1111); } @@ -521,7 +522,7 @@ namespace AZ AZ_MATH_INLINE bool Vec4::CmpAllEq(Int32ArgType arg1, Int32ArgType arg2) { - return Sse::CmpAllEq(arg1, arg2, 0xFFFF); + return Sse::CmpAllEq(arg1, arg2, 0b1111); } diff --git a/Code/Framework/AzCore/AzCore/Module/ModuleManager.cpp b/Code/Framework/AzCore/AzCore/Module/ModuleManager.cpp index 1fdeafa309..cbf3ce5243 100644 --- a/Code/Framework/AzCore/AzCore/Module/ModuleManager.cpp +++ b/Code/Framework/AzCore/AzCore/Module/ModuleManager.cpp @@ -10,16 +10,13 @@ #include #include -#include -#include #include #include #include -#include #include +#include +#include #include -#include -#include #include #include @@ -221,11 +218,16 @@ namespace AZ } } + AZStd::string componentNamesArray = R"({ "SystemComponents":[)"; + const char* comma = ""; // For all system components, deactivate for (auto componentIt = m_systemComponents.rbegin(); componentIt != m_systemComponents.rend(); ++componentIt) { ModuleEntity::DeactivateComponent(**componentIt); + componentNamesArray += AZStd::string::format(R"(%s"%s")", comma, (*componentIt)->RTTI_GetTypeName()); + comma = ", "; } + componentNamesArray += R"(]})"; // For all modules that we created an entity for, set them to "Init" (meaning not Activated) for (auto& moduleData : m_ownedModules) @@ -239,6 +241,13 @@ namespace AZ // Since the system components have been deactivated clear out the vector. m_systemComponents.clear(); + + // Signal that the System Components have deactivated + if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr) + { + AZ::ComponentApplicationLifecycle::SignalEvent(*settingsRegistry, "SystemComponentsDeactivated", componentNamesArray); + } + } //========================================================================= @@ -284,7 +293,11 @@ namespace AZ { // Split the tag list AZStd::vector tagList; - AZStd::tokenize(tags, ",", tagList); + auto TokenizeTags = [&tagList](AZStd::string_view token) + { + tagList.push_back(token); + }; + AZ::StringFunc::TokenizeVisitor(tags, TokenizeTags, ','); m_systemComponentTags.resize(tagList.size()); AZStd::transform(tagList.begin(), tagList.end(), m_systemComponentTags.begin(), [](const AZStd::string_view& tag) @@ -737,11 +750,17 @@ namespace AZ } } + AZStd::string componentNamesArray = R"({ "SystemComponents":[)"; + const char* comma = ""; // Activate the entities in the appropriate order for (Component* component : componentsToActivate) { ModuleEntity::ActivateComponent(*component); + + componentNamesArray += AZStd::string::format(R"(%s"%s")", comma, component->RTTI_GetTypeName()); + comma = ", "; } + componentNamesArray += R"(]})"; // Done activating; set state to active for (auto& moduleData : modulesToInit) @@ -755,5 +774,12 @@ namespace AZ // Save the activated components for deactivation later m_systemComponents.insert(m_systemComponents.end(), componentsToActivate.begin(), componentsToActivate.end()); + + // Signal that the System Components are activated + if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr) + { + AZ::ComponentApplicationLifecycle::SignalEvent(*settingsRegistry, "SystemComponentsActivated", + componentNamesArray); + } } } // namespace AZ diff --git a/Code/Framework/AzCore/AzCore/RTTI/BehaviorInterfaceProxy.h b/Code/Framework/AzCore/AzCore/RTTI/BehaviorInterfaceProxy.h new file mode 100644 index 0000000000..0e7a4ac356 --- /dev/null +++ b/Code/Framework/AzCore/AzCore/RTTI/BehaviorInterfaceProxy.h @@ -0,0 +1,126 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include +#include +#include +#include + +namespace AZ +{ + /** + * Utility class for reflecting an AZ::Interface through the BehaviorContext + * + * Example: + * + * class MyInterface + * { + * public: + * AZ_RTTI(MyInterface, "{BADDF000D-CDCD-CDCD-CDCD-BAAAADF0000D}"); + * virtual ~MyInterface() = default; + * + * virtual AZStd::string Foo() = 0; + * virtual void Bar(float x, float y) = 0; + * }; + * + * class MySystemProxy + * : public BehaviorInterfaceProxy + * { + * public: + * AZ_RTTI(MySystemProxy, "{CDCDCDCD-BAAD-BADD-F00D-CDCDCDCDCDCD}", BehaviorInterfaceProxy); + * AZ_BEHAVIOR_INTERFACE(MySystemProxy, MyInterface); + * }; + * + * void Reflect(AZ::ReflectContext* context) + * { + * if (AZ::BehaviorContext* behaviorContext = azrtti_cast(context)) + * { + * behaviorContext->ConstantProperty("g_MySystem", MySystemProxy::GetProxy) + * ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common) + * ->Attribute(AZ::Script::Attributes::Module, "MyModule"); + * + * behaviorContext->Class("MySystemInterface") + * ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common) + * ->Attribute(AZ::Script::Attributes::Module, "MyModule") + * + * ->Method("Foo", MySystemProxy::WrapMethod<&MyInterface::Foo>()) + * ->Method("Bar", MySystemProxy::WrapMethod<&MyInterface::Bar>()); + * } + * } + */ + template + class BehaviorInterfaceProxy + { + public: + AZ_CLASS_ALLOCATOR(BehaviorInterfaceProxy, AZ::SystemAllocator, 0); + AZ_RTTI(BehaviorInterfaceProxy, "{E7CC8D27-4499-454E-A7DF-3F72FBECD30D}"); + + BehaviorInterfaceProxy() = default; + virtual ~BehaviorInterfaceProxy() = default; + + //! Stores the instance which will use the provided shared_ptr deleter when the reference count hits zero + BehaviorInterfaceProxy(AZStd::shared_ptr sharedInstance) + : m_instance(AZStd::move(sharedInstance)) + { + } + + //! Stores the instance which will perform a no-op deleter when the reference count hits zero + BehaviorInterfaceProxy(T* rawIntance) + : m_instance(rawIntance, [](T*) {}) + { + } + + //! Returns if the m_instance shared pointer is non-nullptr + bool IsValid() const { return m_instance; } + + protected: + //! Internal access for use in the derived GetProxy function + static T* GetInstance() + { + T* interfacePtr = AZ::Interface::Get(); + AZ_Warning("BehaviorInterfaceProxy", interfacePtr, + "There is currently no global %s registered with an AZ Interface", + AzTypeInfo::Name() + ); + // Don't delete the global instance, it is not owned by the behavior context + return interfacePtr; + } + + template + struct MethodWrapper + { + template + static auto WrapMethod() + { + using ReturnType = AZStd::function_traits_get_result_t>; + return [](Proxy* proxy, Args... params) -> ReturnType + { + if (proxy && proxy->IsValid()) + { + return AZStd::invoke(Method, proxy->m_instance, AZStd::forward(params)...); + } + return ReturnType(); + }; + } + }; + + AZStd::shared_ptr m_instance; + }; + + #define AZ_BEHAVIOR_INTERFACE(ProxyType, InterfaceType) \ + static ProxyType GetProxy() { return GetInstance(); } \ + template \ + static auto WrapMethod() { \ + using FuncTraits = AZStd::function_traits>; \ + return FuncTraits::template expand_args::template WrapMethod(); \ + } \ + ProxyType() = default; \ + ProxyType(AZStd::shared_ptr sharedInstance) : BehaviorInterfaceProxy(sharedInstance) {} \ + ProxyType(InterfaceType* rawIntance) : BehaviorInterfaceProxy(rawIntance) {} +} // namespace AZ diff --git a/Code/Framework/AzCore/AzCore/Script/ScriptSystemComponent.cpp b/Code/Framework/AzCore/AzCore/Script/ScriptSystemComponent.cpp index a83232c8cb..3b2aebdee6 100644 --- a/Code/Framework/AzCore/AzCore/Script/ScriptSystemComponent.cpp +++ b/Code/Framework/AzCore/AzCore/Script/ScriptSystemComponent.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -87,6 +88,8 @@ void ScriptSystemComponent::Activate() AZ::Data::AssetCatalogRequestBus::Broadcast(&AZ::Data::AssetCatalogRequests::AddExtension, "lua"); AZ::Data::AssetCatalogRequestBus::Broadcast(&AZ::Data::AssetCatalogRequests::AddExtension, "luac"); + AZ::Data::AssetCatalogRequestBus::Broadcast( + &AZ::Data::AssetCatalogRequests::EnableCatalogForAsset, AZ::AzTypeInfo::Uuid()); if (Data::AssetManager::Instance().IsReady()) { @@ -925,6 +928,7 @@ void ScriptSystemComponent::Reflect(ReflectContext* reflection) // reflect default entity MathReflect(behaviorContext); ScriptDebug::Reflect(behaviorContext); + Debug::ProfilerReflect(behaviorContext); Debug::TraceReflect(behaviorContext); behaviorContext->Class("Platform") diff --git a/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryMergeUtils.cpp b/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryMergeUtils.cpp index 36f66312d8..5458a3fadf 100644 --- a/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryMergeUtils.cpp +++ b/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryMergeUtils.cpp @@ -634,12 +634,18 @@ namespace AZ::SettingsRegistryMergeUtils } // Project name - if it was set via merging project.json use that value, otherwise use the project path's folder name. - auto projectNameKey = - AZ::SettingsRegistryInterface::FixedValueString(AZ::SettingsRegistryMergeUtils::ProjectSettingsRootKey) + constexpr auto projectNameKey = + FixedValueString(AZ::SettingsRegistryMergeUtils::ProjectSettingsRootKey) + "/project_name"; - AZ::SettingsRegistryInterface::FixedValueString projectName; - if (!registry.Get(projectName, projectNameKey)) + // Read the project name from the project.json file if it exists + if (AZ::IO::FixedMaxPath projectJsonPath = normalizedProjectPath / "project.json"; + AZ::IO::SystemFile::Exists(projectJsonPath.c_str())) + { + registry.MergeSettingsFile(projectJsonPath.Native(), + AZ::SettingsRegistryInterface::Format::JsonMergePatch, AZ::SettingsRegistryMergeUtils::ProjectSettingsRootKey); + } + if (FixedValueString projectName; !registry.Get(projectName, projectNameKey)) { projectName = path.Filename().Native(); registry.Set(projectNameKey, projectName); diff --git a/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryScriptUtils.cpp b/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryScriptUtils.cpp index c32591874b..5cd36785dc 100644 --- a/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryScriptUtils.cpp +++ b/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryScriptUtils.cpp @@ -15,20 +15,20 @@ namespace AZ::SettingsRegistryScriptUtils::Internal { - static void RegisterScriptProxyForNotify(SettingsRegistryScriptProxy& settingsRegistryProxy) + static void RegisterScriptProxyForNotify(SettingsRegistryInterface* settingsRegistry, + SettingsRegistryScriptProxy::NotifyEventProxy* notifyEventProxy) { - if (settingsRegistryProxy.IsValid()) + if (settingsRegistry != nullptr) { - auto ForwardSettingsUpdateToProxyEvent = [&settingsRegistryProxy](AZStd::string_view path, AZ::SettingsRegistryInterface::Type) + auto ForwardSettingsUpdateToProxyEvent = [notifyEventProxy](AZStd::string_view path, AZ::SettingsRegistryInterface::Type) { - if (settingsRegistryProxy.m_notifyEventProxy) + if (notifyEventProxy) { - settingsRegistryProxy.m_notifyEventProxy->m_scriptNotifyEvent.Signal(path); + notifyEventProxy->m_scriptNotifyEvent.Signal(path); } }; // Register the forwarding function with the BehaviorContext - settingsRegistryProxy.m_notifyEventProxy->m_settingsUpdatedHandler = - settingsRegistryProxy.m_settingsRegistry->RegisterNotifier(ForwardSettingsUpdateToProxyEvent); + notifyEventProxy->m_settingsUpdatedHandler = settingsRegistry->RegisterNotifier(ForwardSettingsUpdateToProxyEvent); } } @@ -37,7 +37,7 @@ namespace AZ::SettingsRegistryScriptUtils::Internal : m_settingsRegistry(AZStd::move(settingsRegistry)) , m_notifyEventProxy(AZStd::make_shared()) { - RegisterScriptProxyForNotify(*this); + RegisterScriptProxyForNotify(m_settingsRegistry.get(), m_notifyEventProxy.get()); } // Raw AZ::SettingsRegistryInterface pointer is not owned by the proxy, so it's deleter is a no-op @@ -45,7 +45,7 @@ namespace AZ::SettingsRegistryScriptUtils::Internal : m_settingsRegistry(settingsRegistry, [](AZ::SettingsRegistryInterface*) {}) , m_notifyEventProxy(AZStd::make_shared()) { - RegisterScriptProxyForNotify(*this); + RegisterScriptProxyForNotify(m_settingsRegistry.get(), m_notifyEventProxy.get()); } // SettingsRegistryScriptProxy function that determines if the SettingsRegistry object is valid diff --git a/Code/Framework/AzCore/AzCore/Task/TaskExecutor.cpp b/Code/Framework/AzCore/AzCore/Task/TaskExecutor.cpp index 4097348798..08cfb11d34 100644 --- a/Code/Framework/AzCore/AzCore/Task/TaskExecutor.cpp +++ b/Code/Framework/AzCore/AzCore/Task/TaskExecutor.cpp @@ -363,7 +363,11 @@ namespace AZ { ++m_graphsRemaining; - event->m_executor = this; // Used to validate event is not waited for inside a job + if (event) + { + event->IncWaitCount(); + event->m_executor = this; // Used to validate event is not waited for inside a job + } // Submit all tasks that have no inbound edges for (Internal::Task& task : graph.Tasks()) diff --git a/Code/Framework/AzCore/AzCore/Task/TaskGraph.cpp b/Code/Framework/AzCore/AzCore/Task/TaskGraph.cpp index f57b06890a..eeb46d6887 100644 --- a/Code/Framework/AzCore/AzCore/Task/TaskGraph.cpp +++ b/Code/Framework/AzCore/AzCore/Task/TaskGraph.cpp @@ -20,6 +20,46 @@ namespace AZ m_semaphore.acquire(); } + void TaskGraphEvent::IncWaitCount() + { + // guess zero to optimize for single task graph using an event, if multiple are using it then this will take 2+ comp_exch calls + int expectedValue = 0; + while(!m_waitCount.compare_exchange_weak(expectedValue, expectedValue + 1)) + { + // value will be negative once event is ready to signal or has been signaled. Shouldn't happen. + AZ_Assert(expectedValue >= 0, "Called TaskGraphEvent::IncWaitCount on a signalled event"); + if (expectedValue < 0) // event already signaled, skip + { + return; + } + }; + } + + void TaskGraphEvent::Signal() + { + // guess one to optimize for single task graph using an event, if multiple are using it then this will take 2+ comp_exch calls + int expectedValue = 1; + while(!m_waitCount.compare_exchange_weak(expectedValue, expectedValue - 1)) + { + // It's an error for Signal to be called if no one is waiting, or the event has already been signaled + AZ_Assert(expectedValue > 0, "Called TaskGraphEvent::Signal when event is either signaled or unused"); + if (expectedValue < 0) // return if already signaled + { + return; + } + }; + + if (expectedValue == 1) // This call to Signal decremented the value to 0. + { + expectedValue = 0; + // validate no one incremented the wait count and mark signalling state + if (m_waitCount.compare_exchange_strong(expectedValue, -1)) + { + m_semaphore.release(); + } + } + } + void TaskToken::PrecedesInternal(TaskToken& comesAfter) { AZ_Assert(!m_parent.m_submitted, "Cannot mutate a TaskGraph that was previously submitted."); diff --git a/Code/Framework/AzCore/AzCore/Task/TaskGraph.h b/Code/Framework/AzCore/AzCore/Task/TaskGraph.h index 9553013a4b..fa6f5dbe94 100644 --- a/Code/Framework/AzCore/AzCore/Task/TaskGraph.h +++ b/Code/Framework/AzCore/AzCore/Task/TaskGraph.h @@ -61,14 +61,14 @@ namespace AZ uint32_t m_index; }; - // A TaskGraphEvent may be used to block until a task graph has finished executing. Usage + // A TaskGraphEvent may be used to block until one or more task graphs has finished executing. Usage // is NOT recommended for the majority of tasks (prefer to simply containing expanding/contracting // the graph without synchronization over the course of the frame). However, the event // is useful for the edges of the computation graph. // // You are responsible for ensuring the event object lifetime exceeds the task graph lifetime. // - // After the TaskGraphEvent is signaled, you are allowed to reuse the same TaskGraphEvent + // After the TaskGraphEvent is signaled, you are NOT allowed to reuse the same TaskGraphEvent // for a future submission. class TaskGraphEvent { @@ -81,10 +81,12 @@ namespace AZ friend class TaskGraph; friend class TaskExecutor; + void IncWaitCount(); void Signal(); AZStd::binary_semaphore m_semaphore; - TaskExecutor* m_executor = nullptr; + AZStd::atomic_int m_waitCount = 0; + TaskExecutor* m_executor = nullptr; }; // The TaskGraph encapsulates a set of tasks and their interdependencies. After adding diff --git a/Code/Framework/AzCore/AzCore/Task/TaskGraph.inl b/Code/Framework/AzCore/AzCore/Task/TaskGraph.inl index 7b2f0cefdc..9a5289eb82 100644 --- a/Code/Framework/AzCore/AzCore/Task/TaskGraph.inl +++ b/Code/Framework/AzCore/AzCore/Task/TaskGraph.inl @@ -33,11 +33,6 @@ namespace AZ return m_semaphore.try_acquire_for(AZStd::chrono::milliseconds{ 0 }); } - inline void TaskGraphEvent::Signal() - { - m_semaphore.release(); - } - template TaskToken TaskGraph::AddTask(TaskDescriptor const& desc, Lambda&& lambda) { diff --git a/Code/Framework/AzCore/AzCore/azcore_files.cmake b/Code/Framework/AzCore/AzCore/azcore_files.cmake index 41229429f2..a5cc3fdcd4 100644 --- a/Code/Framework/AzCore/AzCore/azcore_files.cmake +++ b/Code/Framework/AzCore/AzCore/azcore_files.cmake @@ -41,6 +41,8 @@ set(FILES Component/ComponentApplication.cpp Component/ComponentApplication.h Component/ComponentApplicationBus.h + Component/ComponentApplicationLifecycle.cpp + Component/ComponentApplicationLifecycle.h Component/ComponentBus.cpp Component/ComponentBus.h Component/ComponentExport.h @@ -106,6 +108,8 @@ set(FILES Debug/Profiler.inl Debug/Profiler.h Debug/ProfilerBus.h + Debug/ProfilerReflection.cpp + Debug/ProfilerReflection.h Debug/StackTracer.h Debug/EventTrace.h Debug/EventTrace.cpp @@ -121,6 +125,8 @@ set(FILES Debug/TraceMessagesDrillerBus.h Debug/TraceReflection.cpp Debug/TraceReflection.h + DOM/DomVisitor.cpp + DOM/DomVisitor.h Driller/DefaultStringPool.h Driller/Driller.cpp Driller/Driller.h @@ -452,6 +458,7 @@ set(FILES RTTI/BehaviorContext.h RTTI/BehaviorContextUtilities.h RTTI/BehaviorContextUtilities.cpp + RTTI/BehaviorInterfaceProxy.h RTTI/BehaviorObjectSignals.h RTTI/TypeSafeIntegral.h Script/ScriptAsset.cpp diff --git a/Code/Framework/AzCore/Platform/Common/WinAPI/AzCore/Debug/Trace_WinAPI.cpp b/Code/Framework/AzCore/Platform/Common/WinAPI/AzCore/Debug/Trace_WinAPI.cpp index 28d3459ba3..4535387902 100644 --- a/Code/Framework/AzCore/Platform/Common/WinAPI/AzCore/Debug/Trace_WinAPI.cpp +++ b/Code/Framework/AzCore/Platform/Common/WinAPI/AzCore/Debug/Trace_WinAPI.cpp @@ -17,7 +17,7 @@ #include -namespace AZ +namespace AZ::Debug { #if defined(AZ_ENABLE_DEBUG_TOOLS) LONG WINAPI ExceptionHandler(PEXCEPTION_POINTERS ExceptionInfo); @@ -26,94 +26,91 @@ namespace AZ constexpr int g_maxMessageLength = 4096; - namespace Debug + namespace Platform { - namespace Platform - { #if defined(AZ_ENABLE_DEBUG_TOOLS) - bool IsDebuggerPresent() + bool IsDebuggerPresent() + { + return ::IsDebuggerPresent() ? true : false; + } + + void HandleExceptions(bool isEnabled) + { + if (isEnabled) { - return ::IsDebuggerPresent() ? true : false; + g_previousExceptionHandler = ::SetUnhandledExceptionFilter(&ExceptionHandler); } - - void HandleExceptions(bool isEnabled) + else { - if (isEnabled) - { - g_previousExceptionHandler = ::SetUnhandledExceptionFilter(&ExceptionHandler); - } - else - { - ::SetUnhandledExceptionFilter(g_previousExceptionHandler); - g_previousExceptionHandler = NULL; - } - } - - bool AttachDebugger() - { - if (IsDebuggerPresent()) - { - return true; - } - - // Launch vsjitdebugger.exe, this app is always present in System32 folder - // with an installation of any version of visual studio. - // It will open a debugging dialog asking the user what debugger to use - - STARTUPINFOW startupInfo = {0}; - startupInfo.cb = sizeof(startupInfo); - PROCESS_INFORMATION processInfo = {0}; - - wchar_t cmdline[MAX_PATH]; - swprintf_s(cmdline, L"vsjitdebugger.exe -p %li", ::GetCurrentProcessId()); - bool success = ::CreateProcessW( - NULL, // No module name (use command line) - cmdline, // Command line - NULL, // Process handle not inheritable - NULL, // Thread handle not inheritable - FALSE, // No handle inheritance - 0, // No creation flags - NULL, // Use parent's environment block - NULL, // Use parent's starting directory - &startupInfo, // Pointer to STARTUPINFO structure - &processInfo); // Pointer to PROCESS_INFORMATION structure - - if (success) - { - ::WaitForSingleObject(processInfo.hProcess, INFINITE); - ::CloseHandle(processInfo.hProcess); - ::CloseHandle(processInfo.hThread); - return true; - } - return false; - } - - void DebugBreak() - { - __debugbreak(); - } -#endif // AZ_ENABLE_DEBUG_TOOLS - - void Terminate(int exitCode) - { - TerminateProcess(GetCurrentProcess(), exitCode); - } - - void OutputToDebugger([[maybe_unused]] const char* window, const char* message) - { - AZStd::fixed_wstring tmpW; - if(window) - { - AZStd::to_wstring(tmpW, window); - tmpW += L": "; - OutputDebugStringW(tmpW.c_str()); - tmpW.clear(); - } - AZStd::to_wstring(tmpW, message); - OutputDebugStringW(tmpW.c_str()); + ::SetUnhandledExceptionFilter(g_previousExceptionHandler); + g_previousExceptionHandler = NULL; } } - } + + bool AttachDebugger() + { + if (IsDebuggerPresent()) + { + return true; + } + + // Launch vsjitdebugger.exe, this app is always present in System32 folder + // with an installation of any version of visual studio. + // It will open a debugging dialog asking the user what debugger to use + + STARTUPINFOW startupInfo = {0}; + startupInfo.cb = sizeof(startupInfo); + PROCESS_INFORMATION processInfo = {0}; + + wchar_t cmdline[MAX_PATH]; + swprintf_s(cmdline, L"vsjitdebugger.exe -p %li", ::GetCurrentProcessId()); + bool success = ::CreateProcessW( + NULL, // No module name (use command line) + cmdline, // Command line + NULL, // Process handle not inheritable + NULL, // Thread handle not inheritable + FALSE, // No handle inheritance + 0, // No creation flags + NULL, // Use parent's environment block + NULL, // Use parent's starting directory + &startupInfo, // Pointer to STARTUPINFO structure + &processInfo); // Pointer to PROCESS_INFORMATION structure + + if (success) + { + ::WaitForSingleObject(processInfo.hProcess, INFINITE); + ::CloseHandle(processInfo.hProcess); + ::CloseHandle(processInfo.hThread); + return true; + } + return false; + } + + void DebugBreak() + { + __debugbreak(); + } +#endif // AZ_ENABLE_DEBUG_TOOLS + + void Terminate(int exitCode) + { + TerminateProcess(GetCurrentProcess(), exitCode); + } + + void OutputToDebugger([[maybe_unused]] const char* window, const char* message) + { + AZStd::fixed_wstring tmpW; + if(window) + { + AZStd::to_wstring(tmpW, window); + tmpW += L": "; + OutputDebugStringW(tmpW.c_str()); + tmpW.clear(); + } + AZStd::to_wstring(tmpW, message); + OutputDebugStringW(tmpW.c_str()); + } + } // namespace Platform #if defined(AZ_ENABLE_DEBUG_TOOLS) @@ -187,6 +184,8 @@ namespace AZ azsnprintf(message, g_maxMessageLength, "Exception : 0x%lX - '%s' [%p]\n", ExceptionInfo->ExceptionRecord->ExceptionCode, GetExeptionName(ExceptionInfo->ExceptionRecord->ExceptionCode), ExceptionInfo->ExceptionRecord->ExceptionAddress); Debug::Trace::Instance().Output(nullptr, message); + Debug::Trace::Instance().PrintCallstack(nullptr, 0, ExceptionInfo->ContextRecord); + EBUS_EVENT(Debug::TraceMessageDrillerBus, OnException, message); bool result = false; @@ -198,7 +197,7 @@ namespace AZ // if someone ever returns TRUE we assume that they somehow handled this exception and continue. return EXCEPTION_CONTINUE_EXECUTION; } - Debug::Trace::Instance().PrintCallstack(nullptr, 0, ExceptionInfo->ContextRecord); + Debug::Trace::Instance().Output(nullptr, "==================================================================\n"); // allowing continue of execution is not valid here. This handler gets called for serious exceptions. @@ -211,4 +210,4 @@ namespace AZ } #endif -} +} // namspace AZ::Debug diff --git a/Code/Framework/AzCore/Tests/TaskTests.cpp b/Code/Framework/AzCore/Tests/TaskTests.cpp index 9e839f60ee..4f53772d51 100644 --- a/Code/Framework/AzCore/Tests/TaskTests.cpp +++ b/Code/Framework/AzCore/Tests/TaskTests.cpp @@ -610,15 +610,16 @@ namespace UnitTest g.Follows(e, f); g.Precedes(d); - TaskGraphEvent ev; - graph.SubmitOnExecutor(*m_executor, &ev); - ev.Wait(); + TaskGraphEvent ev1; + graph.SubmitOnExecutor(*m_executor, &ev1); + ev1.Wait(); EXPECT_EQ(3 | 0b100000, x); x = 0; - graph.SubmitOnExecutor(*m_executor, &ev); - ev.Wait(); + TaskGraphEvent ev2; + graph.SubmitOnExecutor(*m_executor, &ev2); + ev2.Wait(); EXPECT_EQ(3 | 0b100000, x); } diff --git a/Code/Framework/AzFramework/AzFramework/Application/Application.cpp b/Code/Framework/AzFramework/AzFramework/Application/Application.cpp index f8d0ea8bb1..323e834413 100644 --- a/Code/Framework/AzFramework/AzFramework/Application/Application.cpp +++ b/Code/Framework/AzFramework/AzFramework/Application/Application.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -120,6 +121,11 @@ namespace AzFramework m_archiveFileIO = AZStd::make_unique(m_archive.get()); AZ::IO::FileIOBase::SetInstance(m_archiveFileIO.get()); SetFileIOAliases(); + // The FileIOAvailable event needs to be registered here as this event is sent out + // before the settings registry has merged the .setreg files from the + // (That happens in MergeSettingsToRegistry + AZ::ComponentApplicationLifecycle::RegisterEvent(*m_settingsRegistry, "FileIOAvailable"); + AZ::ComponentApplicationLifecycle::SignalEvent(*m_settingsRegistry, "FileIOAvailable", R"({})"); } if (auto nativeUI = AZ::Interface::Get(); nativeUI == nullptr) @@ -172,6 +178,8 @@ namespace AzFramework // Archive classes relies on the FileIOBase DirectInstance to close // files properly m_directFileIO.reset(); + + AZ::ComponentApplicationLifecycle::SignalEvent(*m_settingsRegistry, "FileIOUnavailable", R"({})"); } void Application::Start(const Descriptor& descriptor, const StartupParameters& startupParameters) @@ -196,7 +204,24 @@ namespace AzFramework systemEntity->Activate(); AZ_Assert(systemEntity->GetState() == AZ::Entity::State::Active, "System Entity failed to activate."); - m_isStarted = (systemEntity->GetState() == AZ::Entity::State::Active); + if (m_isStarted = (systemEntity->GetState() == AZ::Entity::State::Active); m_isStarted) + { + if (m_startupParameters.m_loadAssetCatalog) + { + // Start Monitoring Asset changes over the network and load the AssetCatalog + auto StartMonitoringAssetsAndLoadCatalog = [this](AZ::Data::AssetCatalogRequests* assetCatalogRequests) + { + if (AZ::IO::FixedMaxPath assetCatalogPath; + m_settingsRegistry->Get(assetCatalogPath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_CacheRootFolder)) + { + assetCatalogPath /= "assetcatalog.xml"; + assetCatalogRequests->LoadCatalog(assetCatalogPath.c_str()); + } + }; + using AssetCatalogBus = AZ::Data::AssetCatalogRequestBus; + AssetCatalogBus::Broadcast(AZStd::move(StartMonitoringAssetsAndLoadCatalog)); + } + } } void Application::PreModuleLoad() @@ -210,6 +235,17 @@ namespace AzFramework { if (m_isStarted) { + if (m_startupParameters.m_loadAssetCatalog) + { + // Stop Monitoring Assets changes + auto StopMonitoringAssets = [](AZ::Data::AssetCatalogRequests* assetCatalogRequests) + { + assetCatalogRequests->StopMonitoringAssets(); + }; + using AssetCatalogBus = AZ::Data::AssetCatalogRequestBus; + AssetCatalogBus::Broadcast(AZStd::move(StopMonitoringAssets)); + } + ApplicationLifecycleEvents::Bus::Broadcast(&ApplicationLifecycleEvents::OnApplicationAboutToStop); m_pimpl.reset(); diff --git a/Code/Framework/AzFramework/AzFramework/Archive/Archive.cpp b/Code/Framework/AzFramework/AzFramework/Archive/Archive.cpp index cd30b753b5..c1c5775958 100644 --- a/Code/Framework/AzFramework/AzFramework/Archive/Archive.cpp +++ b/Code/Framework/AzFramework/AzFramework/Archive/Archive.cpp @@ -12,6 +12,7 @@ #include #include +#include #include #include #include @@ -363,6 +364,23 @@ namespace AZ::IO , m_mainThreadId{ AZStd::this_thread::get_id() } { CompressionBus::Handler::BusConnect(); + + // If the settings registry is not available at this point, + // then something catastrophic has happened in the application startup. + // That should have been caught and messaged out earlier in startup. + if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr) + { + // Automatically register the event if it's not registered, because + // this system is initialized before the settings registry has loaded the event list. + AZ::ComponentApplicationLifecycle::RegisterHandler( + *settingsRegistry, m_componentApplicationLifecycleHandler, + [this](AZStd::string_view /*path*/, AZ::SettingsRegistryInterface::Type /*type*/) + { + OnSystemEntityActivated(); + }, + "SystemComponentsActivated", + /*autoRegisterEvent*/ true); + } } ////////////////////////////////////////////////////////////////////////// @@ -1175,13 +1193,20 @@ namespace AZ::IO } } - auto bundleManifest = GetBundleManifest(desc.pZip); AZStd::shared_ptr bundleCatalog; + auto bundleManifest = GetBundleManifest(desc.pZip); if (bundleManifest) { bundleCatalog = GetBundleCatalog(desc.pZip, bundleManifest->GetCatalogName()); } + // If this archive is loaded before the serialize context is available, then the manifest and catalog will need to be loaded later. + if (!bundleManifest || !bundleCatalog) + { + m_archivesWithCatalogsToLoad.push_back( + ArchivesWithCatalogsToLoad(szFullPath, szBindRoot, flags, nextBundle, desc.m_strFileName)); + } + bool usePrefabSystemForLevels = false; AzFramework::ApplicationRequests::Bus::BroadcastResult( usePrefabSystemForLevels, &AzFramework::ApplicationRequests::IsPrefabSystemForLevelsEnabled); @@ -1219,12 +1244,17 @@ namespace AZ::IO m_levelOpenEvent.Signal(levelDirs); } - AZ::IO::ArchiveNotificationBus::Broadcast([](AZ::IO::ArchiveNotifications* archiveNotifications, const char* bundleName, - AZStd::shared_ptr bundleManifest, const AZ::IO::FixedMaxPath& nextBundle, AZStd::shared_ptr bundleCatalog) + if (bundleManifest && bundleCatalog) { - archiveNotifications->BundleOpened(bundleName, bundleManifest, nextBundle.c_str(), bundleCatalog); - }, desc.m_strFileName.c_str(), bundleManifest, nextBundle, bundleCatalog); - + AZ::IO::ArchiveNotificationBus::Broadcast( + [](AZ::IO::ArchiveNotifications* archiveNotifications, const char* bundleName, + AZStd::shared_ptr bundleManifest, const AZ::IO::FixedMaxPath& nextBundle, + AZStd::shared_ptr bundleCatalog) + { + archiveNotifications->BundleOpened(bundleName, bundleManifest, nextBundle.c_str(), bundleCatalog); + }, + desc.m_strFileName.c_str(), bundleManifest, nextBundle, bundleCatalog); + } return true; } @@ -2138,7 +2168,7 @@ namespace AZ::IO } currentDirPattern = currentDir + AZ_FILESYSTEM_SEPARATOR_WILDCARD; - currentFilePattern = currentDir + AZ_CORRECT_FILESYSTEM_SEPARATOR_STRING + "levels.pak"; + currentFilePattern = currentDir + AZ_CORRECT_FILESYSTEM_SEPARATOR_STRING + "level.pak"; ZipDir::FileEntry* fileEntry = findFile.FindExact(currentFilePattern.c_str()); if (fileEntry) @@ -2175,4 +2205,36 @@ namespace AZ::IO return catalogInfo; } + + void Archive::OnSystemEntityActivated() + { + for (const auto& archiveInfo : m_archivesWithCatalogsToLoad) + { + AZStd::intrusive_ptr archive = + OpenArchive(archiveInfo.m_fullPath, archiveInfo.m_bindRoot, archiveInfo.m_flags, nullptr); + if (!archive) + { + continue; + } + + ZipDir::CachePtr pZip = static_cast(archive.get())->GetCache(); + + AZStd::shared_ptr bundleCatalog; + auto bundleManifest = GetBundleManifest(pZip); + if (bundleManifest) + { + bundleCatalog = GetBundleCatalog(pZip, bundleManifest->GetCatalogName()); + } + + AZ::IO::ArchiveNotificationBus::Broadcast( + [](AZ::IO::ArchiveNotifications* archiveNotifications, const char* bundleName, + AZStd::shared_ptr bundleManifest, const AZ::IO::FixedMaxPath& nextBundle, + AZStd::shared_ptr bundleCatalog) + { + archiveNotifications->BundleOpened(bundleName, bundleManifest, nextBundle.c_str(), bundleCatalog); + }, + archiveInfo.m_strFileName.c_str(), bundleManifest, archiveInfo.m_nextBundle, bundleCatalog); + } + m_archivesWithCatalogsToLoad.clear(); + } } diff --git a/Code/Framework/AzFramework/AzFramework/Archive/Archive.h b/Code/Framework/AzFramework/AzFramework/Archive/Archive.h index f08d90a66e..279702b433 100644 --- a/Code/Framework/AzFramework/AzFramework/Archive/Archive.h +++ b/Code/Framework/AzFramework/AzFramework/Archive/Archive.h @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -271,6 +272,11 @@ namespace AZ::IO ZipDir::CachePtr* pZip = {}) const; private: + // Archives can't be fully mounted until the system entity has been activated, + // because mounting them requires the BundlingSystemComponent and the serialization system + // to both be available. + void OnSystemEntityActivated(); + bool OpenPackCommon(AZStd::string_view szBindRoot, AZStd::string_view pName, AZStd::intrusive_ptr pData = nullptr, bool addLevels = true); bool OpenPacksCommon(AZStd::string_view szDir, AZStd::string_view pWildcardIn, AZStd::vector* pFullPaths = nullptr, bool addLevels = true); @@ -313,6 +319,8 @@ namespace AZ::IO mutable AZStd::shared_mutex m_csZips; ZipArray m_arrZips; + AZ::SettingsRegistryInterface::NotifyEventHandler m_componentApplicationLifecycleHandler; + ////////////////////////////////////////////////////////////////////////// // Opened files collector. ////////////////////////////////////////////////////////////////////////// @@ -339,5 +347,34 @@ namespace AZ::IO // [LYN-2376] Remove once legacy slice support is removed LevelPackOpenEvent m_levelOpenEvent; LevelPackCloseEvent m_levelCloseEvent; + + // If pak files are loaded before the serialization and bundling system + // are ready to go, their asset catalogs can't be loaded. + // In this case, cache information about those archives, + // and attempt to load the catalogs later, when the required systems are enabled. + struct ArchivesWithCatalogsToLoad + { + ArchivesWithCatalogsToLoad( + AZStd::string_view fullPath, + AZStd::string_view bindRoot, + int flags, + AZ::IO::PathView nextBundle, + AZ::IO::Path strFileName) + : m_fullPath(fullPath) + , m_bindRoot(bindRoot) + , m_flags(flags) + , m_nextBundle(nextBundle) + , m_strFileName(strFileName) + { + } + + AZ::IO::Path m_strFileName; + AZStd::string m_fullPath; + AZStd::string m_bindRoot; + AZ::IO::PathView m_nextBundle; + int m_flags; + }; + + AZStd::vector m_archivesWithCatalogsToLoad; }; } diff --git a/Code/Framework/AzFramework/AzFramework/Asset/AssetCatalog.cpp b/Code/Framework/AzFramework/AzFramework/Asset/AssetCatalog.cpp index e6b8211c28..3d76bcefc4 100644 --- a/Code/Framework/AzFramework/AzFramework/Asset/AssetCatalog.cpp +++ b/Code/Framework/AzFramework/AzFramework/Asset/AssetCatalog.cpp @@ -565,7 +565,7 @@ namespace AzFramework if (!bytes.empty()) { - AZStd::shared_ptr < AzFramework::AssetRegistry> prevRegistry; + AZStd::shared_ptr prevRegistry; if (!m_initialized) { // First time initialization may have updates already processed which we want to apply @@ -589,7 +589,6 @@ namespace AzFramework AZ_TracePrintf("AssetCatalog", "Loaded registry containing %u assets.\n", m_registry->m_assetIdToInfo.size()); // It's currently possible in tools for us to have received updates from AP which were applied before the catalog was ready to load - // due to CryPak and CrySystem coming online later than our components if (!m_initialized) { ApplyDeltaCatalog(prevRegistry); @@ -611,12 +610,13 @@ namespace AzFramework // the mutex. If the listener tries to perform a blocking asset load via GetAsset() / BlockUntilLoadComplete(), the spawned asset // thread will make a call to the AssetCatalogRequestBus and block on the held mutex. This would cause a deadlock, since the listener // won't free the mutex until the load is complete. - // So instead, queue the notification until the next tick, so that it doesn't occur within the AssetCatalogRequestBus mutex, and also + // So instead, queue the notification until after the AssetCatalogRequestBus mutex is unlocked for the current thread, and also // so that the entire AssetCatalog initialization is complete. - AZ::TickBus::QueueFunction([catalogRegistryString = AZStd::string(catalogRegistryFile)]() - { - AssetCatalogEventBus::Broadcast(&AssetCatalogEventBus::Events::OnCatalogLoaded, catalogRegistryString.c_str()); - }); + auto OnCatalogLoaded = [catalogRegistryString = AZStd::string(catalogRegistryFile)]() + { + AssetCatalogEventBus::Broadcast(&AssetCatalogEventBus::Events::OnCatalogLoaded, catalogRegistryString.c_str()); + }; + AZ::Data::AssetCatalogRequestBus::QueueFunction(AZStd::move(OnCatalogLoaded)); } } @@ -978,6 +978,7 @@ namespace AzFramework AZStd::lock_guard lock(m_registryMutex); m_registry->Clear(); + m_initialized = false; } diff --git a/Code/Framework/AzFramework/AzFramework/Asset/AssetRegistry.cpp b/Code/Framework/AzFramework/AzFramework/Asset/AssetRegistry.cpp index f9eefa7639..26a22f4625 100644 --- a/Code/Framework/AzFramework/AzFramework/Asset/AssetRegistry.cpp +++ b/Code/Framework/AzFramework/AzFramework/Asset/AssetRegistry.cpp @@ -61,6 +61,7 @@ namespace AzFramework //========================================================================= void AssetRegistry::Clear() { + m_assetDependencies = {}; m_assetIdToInfo = AssetIdToInfoMap(); m_assetPathToId = AssetPathToIdMap(); } diff --git a/Code/Framework/AzFramework/AzFramework/Components/TransformComponent.cpp b/Code/Framework/AzFramework/AzFramework/Components/TransformComponent.cpp index 68c4b00243..bc109b73c2 100644 --- a/Code/Framework/AzFramework/AzFramework/Components/TransformComponent.cpp +++ b/Code/Framework/AzFramework/AzFramework/Components/TransformComponent.cpp @@ -323,6 +323,13 @@ namespace AzFramework return localZ; } + void TransformComponent::SetWorldRotation(const AZ::Vector3& eulerAnglesRadian) + { + AZ::Transform newWorldTransform = m_worldTM; + newWorldTransform.SetRotation(AZ::Quaternion::CreateFromEulerAnglesRadians(eulerAnglesRadian)); + SetWorldTM(newWorldTransform); + } + void TransformComponent::SetWorldRotationQuaternion(const AZ::Quaternion& quaternion) { AZ::Transform newWorldTransform = m_worldTM; diff --git a/Code/Framework/AzFramework/AzFramework/Components/TransformComponent.h b/Code/Framework/AzFramework/AzFramework/Components/TransformComponent.h index ac282b4d49..2375f28ed1 100644 --- a/Code/Framework/AzFramework/AzFramework/Components/TransformComponent.h +++ b/Code/Framework/AzFramework/AzFramework/Components/TransformComponent.h @@ -108,6 +108,7 @@ namespace AzFramework float GetLocalZ() override; // Rotation modifiers + void SetWorldRotation(const AZ::Vector3& eulerAnglesRadian) override; void SetWorldRotationQuaternion(const AZ::Quaternion& quaternion) override; AZ::Vector3 GetWorldRotation() override; diff --git a/Code/Framework/AzFramework/AzFramework/FileTag/FileTag.cpp b/Code/Framework/AzFramework/AzFramework/FileTag/FileTag.cpp index f820ee56ed..abbb9ec2f1 100644 --- a/Code/Framework/AzFramework/AzFramework/FileTag/FileTag.cpp +++ b/Code/Framework/AzFramework/AzFramework/FileTag/FileTag.cpp @@ -10,11 +10,11 @@ #include #include #include +#include #include #include #include #include -#include #include #include #include @@ -89,19 +89,19 @@ namespace AzFramework bool FileTagManager::Save(FileTagType fileTagType, const AZStd::string& destinationFilePath = AZStd::string()) { AzFramework::FileTag::FileTagAsset* fileTagAsset = GetFileTagAsset(fileTagType); - AZStd::string filePathToSave = destinationFilePath; + AZ::IO::Path filePathToSave = destinationFilePath; if (filePathToSave.empty()) { filePathToSave = FileTagQueryManager::GetDefaultFileTagFilePath(fileTagType); } - if (!AzFramework::StringFunc::EndsWith(filePathToSave, AzFramework::FileTag::FileTagAsset::Extension())) + if (!filePathToSave.Extension().Native().ends_with(AzFramework::FileTag::FileTagAsset::Extension())) { AZ_Error("FileTag", false, "Unable to save tag file (%s). Invalid file extension, file tag can only have (%s) extension.\n", filePathToSave.c_str(), AzFramework::FileTag::FileTagAsset::Extension()); return false; } - return AZ::Utils::SaveObjectToFile(filePathToSave, AZ::DataStream::StreamType::ST_XML, fileTagAsset); + return AZ::Utils::SaveObjectToFile(filePathToSave.Native(), AZ::DataStream::StreamType::ST_XML, fileTagAsset); } AZ::Outcome FileTagManager::AddTagsInternal(AZStd::string filePath, FileTagType fileTagType, AZStd::vector fileTags, AzFramework::FileTag::FilePatternType filePatternType) @@ -239,17 +239,22 @@ namespace AzFramework QueryFileTagsEventBus::Handler::BusDisconnect(); } - AZStd::string FileTagQueryManager::GetDefaultFileTagFilePath(FileTagType fileTagType) + AZ::IO::Path FileTagQueryManager::GetDefaultFileTagFilePath(FileTagType fileTagType) { - auto destinationFilePath = AZ::IO::FixedMaxPath(AZ::Utils::GetEnginePath()) / EngineAssetSourceRelPath; + AZ::IO::Path destinationFilePath; + if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr) + { + settingsRegistry->Get(destinationFilePath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder); + } + destinationFilePath /= EngineAssetSourceRelPath; destinationFilePath /= fileTagType == FileTagType::Exclude ? ExcludeFileName : IncludeFileName; destinationFilePath.ReplaceExtension(AzFramework::FileTag::FileTagAsset::Extension()); - return destinationFilePath.String(); + return destinationFilePath; } bool FileTagQueryManager::Load(const AZStd::string& filePath) { - AZStd::string fileToLoad = filePath; + AZ::IO::Path fileToLoad = filePath; if (fileToLoad.empty()) { fileToLoad = GetDefaultFileTagFilePath(m_fileTagType); diff --git a/Code/Framework/AzFramework/AzFramework/FileTag/FileTag.h b/Code/Framework/AzFramework/AzFramework/FileTag/FileTag.h index be3d6e6d08..d2dee3f629 100644 --- a/Code/Framework/AzFramework/AzFramework/FileTag/FileTag.h +++ b/Code/Framework/AzFramework/AzFramework/FileTag/FileTag.h @@ -11,6 +11,7 @@ #include #include #include +#include #include namespace AzFramework @@ -88,7 +89,7 @@ namespace AzFramework ///////////////////////////////////////////////////////////////////////// - static AZStd::string GetDefaultFileTagFilePath(FileTagType fileTagType); + static AZ::IO::Path GetDefaultFileTagFilePath(FileTagType fileTagType); protected: diff --git a/Code/Framework/AzFramework/AzFramework/FileTag/FileTagComponent.cpp b/Code/Framework/AzFramework/AzFramework/FileTag/FileTagComponent.cpp index 1e03a6df0d..1d09500415 100644 --- a/Code/Framework/AzFramework/AzFramework/FileTag/FileTagComponent.cpp +++ b/Code/Framework/AzFramework/AzFramework/FileTag/FileTagComponent.cpp @@ -16,6 +16,7 @@ #include #include +#include #include namespace AzFramework @@ -66,7 +67,8 @@ namespace AzFramework m_excludeFileQueryManager.reset(aznew FileTagQueryManager(FileTagType::Exclude)); if (!m_excludeFileQueryManager.get()->Load()) { - AZ_Error("FileTagQueryComponent", false, "Not able to load default exclude file (%s). Please make sure that it exists on disk.\n", FileTagQueryManager::GetDefaultFileTagFilePath(FileTagType::Exclude).c_str()); + AZ_Error("FileTagQueryComponent", false, "Not able to load default exclude file (%s). Please make sure that it exists on disk.\n", + FileTagQueryManager::GetDefaultFileTagFilePath(FileTagType::Exclude).c_str()); } AzFramework::AssetCatalogEventBus::Handler::BusConnect(); diff --git a/Code/Framework/AzFramework/AzFramework/Input/Buses/Requests/InputDeviceRequestBus.h b/Code/Framework/AzFramework/AzFramework/Input/Buses/Requests/InputDeviceRequestBus.h index 147ff7f0b8..74e2459ab6 100644 --- a/Code/Framework/AzFramework/AzFramework/Input/Buses/Requests/InputDeviceRequestBus.h +++ b/Code/Framework/AzFramework/AzFramework/Input/Buses/Requests/InputDeviceRequestBus.h @@ -229,17 +229,13 @@ namespace AzFramework //! Alias for the EBus implementation of this interface using Bus = AZ::EBus>; - //////////////////////////////////////////////////////////////////////////////////////////// - //! Alias for the function type used to create the custom implementations - using CreateFunctionType = typename InputDeviceType::Implementation*(*)(InputDeviceType&); - //////////////////////////////////////////////////////////////////////////////////////////// //! Set a custom implementation for this input device type, either for a specific instance //! by addressing the call to an InputDeviceId, or for all existing instances by broadcast. //! Passing InputDeviceType::Implementation::Create as the argument will create the default //! device implementation, while passing nullptr will delete any existing implementation. - //! \param[in] createFunction Pointer to the function that will create the implementation. - virtual void SetCustomImplementation(CreateFunctionType createFunction) = 0; + //! \param[in] implementationFactory Pointer to the function that creates the implementation. + virtual void SetCustomImplementation(typename InputDeviceType::ImplementationFactory implementationFactory) = 0; }; //////////////////////////////////////////////////////////////////////////////////////////////// @@ -267,18 +263,14 @@ namespace AzFramework AZ_DISABLE_COPY_MOVE(InputDeviceImplementationRequestHandler); protected: - //////////////////////////////////////////////////////////////////////////////////////////// - //! Alias for the function type used to create the custom implementations - using CreateFunctionType = typename InputDeviceType::Implementation*(*)(InputDeviceType&); - //////////////////////////////////////////////////////////////////////////////////////////// //! \ref InputDeviceImplementationRequest::SetCustomImplementation - AZ_INLINE void SetCustomImplementation(CreateFunctionType createFunction) override + AZ_INLINE void SetCustomImplementation(typename InputDeviceType::ImplementationFactory implementationFactory) override { AZStd::unique_ptr newImplementation; - if (createFunction) + if (implementationFactory) { - newImplementation.reset(createFunction(m_inputDevice)); + newImplementation.reset(implementationFactory(m_inputDevice)); } m_inputDevice.SetImplementation(AZStd::move(newImplementation)); } diff --git a/Code/Framework/AzFramework/AzFramework/Input/Devices/Gamepad/InputDeviceGamepad.cpp b/Code/Framework/AzFramework/AzFramework/Input/Devices/Gamepad/InputDeviceGamepad.cpp index 51aa008519..9759a95733 100644 --- a/Code/Framework/AzFramework/AzFramework/Input/Devices/Gamepad/InputDeviceGamepad.cpp +++ b/Code/Framework/AzFramework/AzFramework/Input/Devices/Gamepad/InputDeviceGamepad.cpp @@ -94,7 +94,14 @@ namespace AzFramework //////////////////////////////////////////////////////////////////////////////////////////////// InputDeviceGamepad::InputDeviceGamepad(AZ::u32 index) - : InputDevice(InputDeviceId(Name, index)) + : InputDeviceGamepad(InputDeviceId(Name, index)) // Delegated constructor + { + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + InputDeviceGamepad::InputDeviceGamepad(const InputDeviceId& inputDeviceId, + ImplementationFactory implementationFactory) + : InputDevice(inputDeviceId) , m_allChannelsById() , m_buttonChannelsById() , m_triggerChannelsById() @@ -144,8 +151,8 @@ namespace AzFramework m_thumbStickDirectionChannelsById[channelId] = channel; } - // Create the platform specific implementation - m_pimpl.reset(Implementation::Create(*this)); + // Create the platform specific or custom implementation + m_pimpl.reset(implementationFactory ? implementationFactory(*this) : nullptr); // Connect to the haptic feedback request bus InputHapticFeedbackRequestBus::Handler::BusConnect(GetInputDeviceId()); diff --git a/Code/Framework/AzFramework/AzFramework/Input/Devices/Gamepad/InputDeviceGamepad.h b/Code/Framework/AzFramework/AzFramework/Input/Devices/Gamepad/InputDeviceGamepad.h index 286b4f0df3..7be498830b 100644 --- a/Code/Framework/AzFramework/AzFramework/Input/Devices/Gamepad/InputDeviceGamepad.h +++ b/Code/Framework/AzFramework/AzFramework/Input/Devices/Gamepad/InputDeviceGamepad.h @@ -182,6 +182,14 @@ namespace AzFramework // Reflection static void Reflect(AZ::ReflectContext* context); + //////////////////////////////////////////////////////////////////////////////////////////// + // Foward declare the internal Implementation class so it can be passed into the constructor + class Implementation; + + //////////////////////////////////////////////////////////////////////////////////////////// + //! Alias for the function type used to create a custom implementation for this input device + using ImplementationFactory = Implementation*(InputDeviceGamepad&); + //////////////////////////////////////////////////////////////////////////////////////////// //! Constructor explicit InputDeviceGamepad(); @@ -191,6 +199,13 @@ namespace AzFramework //! \param[in] index Index of the game-pad device explicit InputDeviceGamepad(AZ::u32 index); + //////////////////////////////////////////////////////////////////////////////////////////// + //! Constructor + //! \param[in] inputDeviceId Id of the input device + //! \param[in] implementationFactory Optional override of the default Implementation::Create + explicit InputDeviceGamepad(const InputDeviceId& inputDeviceId, + ImplementationFactory implementationFactory = &Implementation::Create); + //////////////////////////////////////////////////////////////////////////////////////////// // Disable copying AZ_DISABLE_COPY_MOVE(InputDeviceGamepad); diff --git a/Code/Framework/AzFramework/AzFramework/Input/Devices/Keyboard/InputDeviceKeyboard.cpp b/Code/Framework/AzFramework/AzFramework/Input/Devices/Keyboard/InputDeviceKeyboard.cpp index d1117ea895..2a76cc6e1b 100644 --- a/Code/Framework/AzFramework/AzFramework/Input/Devices/Keyboard/InputDeviceKeyboard.cpp +++ b/Code/Framework/AzFramework/AzFramework/Input/Devices/Keyboard/InputDeviceKeyboard.cpp @@ -182,8 +182,9 @@ namespace AzFramework } //////////////////////////////////////////////////////////////////////////////////////////////// - InputDeviceKeyboard::InputDeviceKeyboard(AzFramework::InputDeviceId id) - : InputDevice(id) + InputDeviceKeyboard::InputDeviceKeyboard(const InputDeviceId& inputDeviceId, + ImplementationFactory implementationFactory) + : InputDevice(inputDeviceId) , m_modifierKeyStates(AZStd::make_shared()) , m_allChannelsById() , m_keyChannelsById() @@ -203,8 +204,8 @@ namespace AzFramework m_keyChannelsById[channelId] = channel; } - // Create the platform specific implementation - m_pimpl.reset(Implementation::Create(*this)); + // Create the platform specific or custom implementation + m_pimpl.reset(implementationFactory ? implementationFactory(*this) : nullptr); // Connect to the text entry request bus InputTextEntryRequestBus::Handler::BusConnect(GetInputDeviceId()); diff --git a/Code/Framework/AzFramework/AzFramework/Input/Devices/Keyboard/InputDeviceKeyboard.h b/Code/Framework/AzFramework/AzFramework/Input/Devices/Keyboard/InputDeviceKeyboard.h index e3c21ec326..c0c2f5d92b 100644 --- a/Code/Framework/AzFramework/AzFramework/Input/Devices/Keyboard/InputDeviceKeyboard.h +++ b/Code/Framework/AzFramework/AzFramework/Input/Devices/Keyboard/InputDeviceKeyboard.h @@ -370,9 +370,20 @@ namespace AzFramework // Reflection static void Reflect(AZ::ReflectContext* context); + //////////////////////////////////////////////////////////////////////////////////////////// + // Foward declare the internal Implementation class so it can be passed into the constructor + class Implementation; + + //////////////////////////////////////////////////////////////////////////////////////////// + //! Alias for the function type used to create a custom implementation for this input device + using ImplementationFactory = Implementation*(InputDeviceKeyboard&); + //////////////////////////////////////////////////////////////////////////////////////////// //! Constructor - InputDeviceKeyboard(AzFramework::InputDeviceId id = Id); + //! \param[in] inputDeviceId Optional override of the default input device id + //! \param[in] implementationFactory Optional override of the default Implementation::Create + explicit InputDeviceKeyboard(const InputDeviceId& inputDeviceId = Id, + ImplementationFactory implementationFactory = &Implementation::Create); //////////////////////////////////////////////////////////////////////////////////////////// // Disable copying diff --git a/Code/Framework/AzFramework/AzFramework/Input/Devices/Motion/InputDeviceMotion.cpp b/Code/Framework/AzFramework/AzFramework/Input/Devices/Motion/InputDeviceMotion.cpp index c144f63d2c..d4057bc5d3 100644 --- a/Code/Framework/AzFramework/AzFramework/Input/Devices/Motion/InputDeviceMotion.cpp +++ b/Code/Framework/AzFramework/AzFramework/Input/Devices/Motion/InputDeviceMotion.cpp @@ -60,8 +60,9 @@ namespace AzFramework } //////////////////////////////////////////////////////////////////////////////////////////////// - InputDeviceMotion::InputDeviceMotion() - : InputDevice(Id) + InputDeviceMotion::InputDeviceMotion(const InputDeviceId& inputDeviceId, + ImplementationFactory implementationFactory) + : InputDevice(inputDeviceId) , m_allChannelsById() , m_accelerationChannelsById() , m_rotationRateChannelsById() @@ -107,8 +108,8 @@ namespace AzFramework m_orientationChannelsById[channelId] = channel; } - // Create the platform specific implementation - m_pimpl.reset(Implementation::Create(*this)); + // Create the platform specific or custom implementation + m_pimpl.reset(implementationFactory ? implementationFactory(*this) : nullptr); // Connect to the motion sensor request bus InputMotionSensorRequestBus::Handler::BusConnect(GetInputDeviceId()); diff --git a/Code/Framework/AzFramework/AzFramework/Input/Devices/Motion/InputDeviceMotion.h b/Code/Framework/AzFramework/AzFramework/Input/Devices/Motion/InputDeviceMotion.h index 872bd1cfa0..f0fc976963 100644 --- a/Code/Framework/AzFramework/AzFramework/Input/Devices/Motion/InputDeviceMotion.h +++ b/Code/Framework/AzFramework/AzFramework/Input/Devices/Motion/InputDeviceMotion.h @@ -126,9 +126,20 @@ namespace AzFramework // Reflection static void Reflect(AZ::ReflectContext* context); + //////////////////////////////////////////////////////////////////////////////////////////// + // Foward declare the internal Implementation class so it can be passed into the constructor + class Implementation; + + //////////////////////////////////////////////////////////////////////////////////////////// + //! Alias for the function type used to create a custom implementation for this input device + using ImplementationFactory = Implementation*(InputDeviceMotion&); + //////////////////////////////////////////////////////////////////////////////////////////// //! Constructor - InputDeviceMotion(); + //! \param[in] inputDeviceId Optional override of the default input device id + //! \param[in] implementationFactory Optional override of the default Implementation::Create + explicit InputDeviceMotion(const InputDeviceId& inputDeviceId = Id, + ImplementationFactory implementationFactory = &Implementation::Create); //////////////////////////////////////////////////////////////////////////////////////////// // Disable copying diff --git a/Code/Framework/AzFramework/AzFramework/Input/Devices/Mouse/InputDeviceMouse.cpp b/Code/Framework/AzFramework/AzFramework/Input/Devices/Mouse/InputDeviceMouse.cpp index e9af4cc4ce..39504d9352 100644 --- a/Code/Framework/AzFramework/AzFramework/Input/Devices/Mouse/InputDeviceMouse.cpp +++ b/Code/Framework/AzFramework/AzFramework/Input/Devices/Mouse/InputDeviceMouse.cpp @@ -67,8 +67,9 @@ namespace AzFramework } //////////////////////////////////////////////////////////////////////////////////////////////// - InputDeviceMouse::InputDeviceMouse(AzFramework::InputDeviceId id) - : InputDevice(id) + InputDeviceMouse::InputDeviceMouse(const InputDeviceId& inputDeviceId, + ImplementationFactory implementationFactory) + : InputDevice(inputDeviceId) , m_allChannelsById() , m_buttonChannelsById() , m_movementChannelsById() @@ -97,8 +98,8 @@ namespace AzFramework m_cursorPositionChannel = aznew InputChannelDeltaWithSharedPosition2D(SystemCursorPosition, *this, m_cursorPositionData2D); m_allChannelsById[SystemCursorPosition] = m_cursorPositionChannel; - // Create the platform specific implementation - m_pimpl.reset(Implementation::Create(*this)); + // Create the platform specific or custom implementation + m_pimpl.reset(implementationFactory ? implementationFactory(*this) : nullptr); // Connect to the system cursor request bus InputSystemCursorRequestBus::Handler::BusConnect(GetInputDeviceId()); diff --git a/Code/Framework/AzFramework/AzFramework/Input/Devices/Mouse/InputDeviceMouse.h b/Code/Framework/AzFramework/AzFramework/Input/Devices/Mouse/InputDeviceMouse.h index 3b35e03f1b..3c519a3edb 100644 --- a/Code/Framework/AzFramework/AzFramework/Input/Devices/Mouse/InputDeviceMouse.h +++ b/Code/Framework/AzFramework/AzFramework/Input/Devices/Mouse/InputDeviceMouse.h @@ -122,9 +122,20 @@ namespace AzFramework // Reflection static void Reflect(AZ::ReflectContext* context); + //////////////////////////////////////////////////////////////////////////////////////////// + // Foward declare the internal Implementation class so it can be passed into the constructor + class Implementation; + + //////////////////////////////////////////////////////////////////////////////////////////// + //! Alias for the function type used to create a custom implementation for this input device + using ImplementationFactory = Implementation*(InputDeviceMouse&); + //////////////////////////////////////////////////////////////////////////////////////////// //! Constructor - explicit InputDeviceMouse(AzFramework::InputDeviceId id = Id); + //! \param[in] inputDeviceId Optional override of the default input device id + //! \param[in] implementationFactory Optional override of the default Implementation::Create + explicit InputDeviceMouse(const InputDeviceId& inputDeviceId = Id, + ImplementationFactory implementationFactory = &Implementation::Create); //////////////////////////////////////////////////////////////////////////////////////////// // Disable copying diff --git a/Code/Framework/AzFramework/AzFramework/Input/Devices/Touch/InputDeviceTouch.cpp b/Code/Framework/AzFramework/AzFramework/Input/Devices/Touch/InputDeviceTouch.cpp index be850e9289..2695e3bb08 100644 --- a/Code/Framework/AzFramework/AzFramework/Input/Devices/Touch/InputDeviceTouch.cpp +++ b/Code/Framework/AzFramework/AzFramework/Input/Devices/Touch/InputDeviceTouch.cpp @@ -59,8 +59,9 @@ namespace AzFramework } //////////////////////////////////////////////////////////////////////////////////////////////// - InputDeviceTouch::InputDeviceTouch() - : InputDevice(Id) + InputDeviceTouch::InputDeviceTouch(const InputDeviceId& inputDeviceId, + ImplementationFactory implementationFactory) + : InputDevice(inputDeviceId) , m_allChannelsById() , m_touchChannelsById() , m_pimpl(nullptr) @@ -75,8 +76,8 @@ namespace AzFramework m_touchChannelsById[channelId] = channel; } - // Create the platform specific implementation - m_pimpl.reset(Implementation::Create(*this)); + // Create the platform specific or custom implementation + m_pimpl.reset(implementationFactory ? implementationFactory(*this) : nullptr); } //////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/Code/Framework/AzFramework/AzFramework/Input/Devices/Touch/InputDeviceTouch.h b/Code/Framework/AzFramework/AzFramework/Input/Devices/Touch/InputDeviceTouch.h index d21834c22a..12b8aded4b 100644 --- a/Code/Framework/AzFramework/AzFramework/Input/Devices/Touch/InputDeviceTouch.h +++ b/Code/Framework/AzFramework/AzFramework/Input/Devices/Touch/InputDeviceTouch.h @@ -77,9 +77,20 @@ namespace AzFramework // Reflection static void Reflect(AZ::ReflectContext* context); + //////////////////////////////////////////////////////////////////////////////////////////// + // Foward declare the internal Implementation class so it can be passed into the constructor + class Implementation; + + //////////////////////////////////////////////////////////////////////////////////////////// + //! Alias for the function type used to create a custom implementation for this input device + using ImplementationFactory = Implementation*(InputDeviceTouch&); + //////////////////////////////////////////////////////////////////////////////////////////// //! Constructor - InputDeviceTouch(); + //! \param[in] inputDeviceId Optional override of the default input device id + //! \param[in] implementationFactory Optional override of the default Implementation::Create + explicit InputDeviceTouch(const InputDeviceId& inputDeviceId = Id, + ImplementationFactory implementationFactory = &Implementation::Create); //////////////////////////////////////////////////////////////////////////////////////////// // Disable copying diff --git a/Code/Framework/AzFramework/AzFramework/Input/Devices/VirtualKeyboard/InputDeviceVirtualKeyboard.cpp b/Code/Framework/AzFramework/AzFramework/Input/Devices/VirtualKeyboard/InputDeviceVirtualKeyboard.cpp index f3024f11ab..bcbdeaa478 100644 --- a/Code/Framework/AzFramework/AzFramework/Input/Devices/VirtualKeyboard/InputDeviceVirtualKeyboard.cpp +++ b/Code/Framework/AzFramework/AzFramework/Input/Devices/VirtualKeyboard/InputDeviceVirtualKeyboard.cpp @@ -51,8 +51,9 @@ namespace AzFramework } //////////////////////////////////////////////////////////////////////////////////////////////// - InputDeviceVirtualKeyboard::InputDeviceVirtualKeyboard() - : InputDevice(Id) + InputDeviceVirtualKeyboard::InputDeviceVirtualKeyboard(const InputDeviceId& inputDeviceId, + ImplementationFactory implementationFactory) + : InputDevice(inputDeviceId) , m_allChannelsById() , m_pimpl() , m_implementationRequestHandler(*this) @@ -65,8 +66,8 @@ namespace AzFramework m_commandChannelsById[channelId] = channel; } - // Create the platform specific implementation - m_pimpl.reset(Implementation::Create(*this)); + // Create the platform specific or custom implementation + m_pimpl.reset(implementationFactory ? implementationFactory(*this) : nullptr); // Connect to the text entry request bus InputTextEntryRequestBus::Handler::BusConnect(GetInputDeviceId()); diff --git a/Code/Framework/AzFramework/AzFramework/Input/Devices/VirtualKeyboard/InputDeviceVirtualKeyboard.h b/Code/Framework/AzFramework/AzFramework/Input/Devices/VirtualKeyboard/InputDeviceVirtualKeyboard.h index 6f62c3ec61..abe1312733 100644 --- a/Code/Framework/AzFramework/AzFramework/Input/Devices/VirtualKeyboard/InputDeviceVirtualKeyboard.h +++ b/Code/Framework/AzFramework/AzFramework/Input/Devices/VirtualKeyboard/InputDeviceVirtualKeyboard.h @@ -69,9 +69,20 @@ namespace AzFramework // Reflection static void Reflect(AZ::ReflectContext* context); + //////////////////////////////////////////////////////////////////////////////////////////// + // Foward declare the internal Implementation class so it can be passed into the constructor + class Implementation; + + //////////////////////////////////////////////////////////////////////////////////////////// + //! Alias for the function type used to create a custom implementation for this input device + using ImplementationFactory = Implementation*(InputDeviceVirtualKeyboard&); + //////////////////////////////////////////////////////////////////////////////////////////// //! Constructor - InputDeviceVirtualKeyboard(); + //! \param[in] inputDeviceId Optional override of the default input device id + //! \param[in] implementationFactory Optional override of the default Implementation::Create + explicit InputDeviceVirtualKeyboard(const InputDeviceId& inputDeviceId = Id, + ImplementationFactory implementationFactory = &Implementation::Create); //////////////////////////////////////////////////////////////////////////////////////////// // Disable copying diff --git a/Code/Framework/AzFramework/AzFramework/Matchmaking/IMatchmakingRequests.h b/Code/Framework/AzFramework/AzFramework/Matchmaking/IMatchmakingRequests.h index c657e0edc7..ccf9acdf8b 100644 --- a/Code/Framework/AzFramework/AzFramework/Matchmaking/IMatchmakingRequests.h +++ b/Code/Framework/AzFramework/AzFramework/Matchmaking/IMatchmakingRequests.h @@ -24,17 +24,17 @@ namespace AzFramework IMatchmakingRequests() = default; virtual ~IMatchmakingRequests() = default; - // Registers a player's acceptance or rejection of a proposed matchmaking. - // @param acceptMatchRequest The request of AcceptMatch operation + //! Registers a player's acceptance or rejection of a proposed matchmaking. + //! @param acceptMatchRequest The request of AcceptMatch operation virtual void AcceptMatch(const AcceptMatchRequest& acceptMatchRequest) = 0; - // Create a game match for a group of players. - // @param startMatchmakingRequest The request of StartMatchmaking operation - // @return A unique identifier for a matchmaking ticket + //! Create a game match for a group of players. + //! @param startMatchmakingRequest The request of StartMatchmaking operation + //! @return A unique identifier for a matchmaking ticket virtual AZStd::string StartMatchmaking(const StartMatchmakingRequest& startMatchmakingRequest) = 0; - // Cancels a matchmaking ticket that is currently being processed. - // @param stopMatchmakingRequest The request of StopMatchmaking operation + //! Cancels a matchmaking ticket that is currently being processed. + //! @param stopMatchmakingRequest The request of StopMatchmaking operation virtual void StopMatchmaking(const StopMatchmakingRequest& stopMatchmakingRequest) = 0; }; @@ -48,16 +48,16 @@ namespace AzFramework IMatchmakingAsyncRequests() = default; virtual ~IMatchmakingAsyncRequests() = default; - // AcceptMatch Async - // @param acceptMatchRequest The request of AcceptMatch operation + //! AcceptMatch Async + //! @param acceptMatchRequest The request of AcceptMatch operation virtual void AcceptMatchAsync(const AcceptMatchRequest& acceptMatchRequest) = 0; - // StartMatchmaking Async - // @param startMatchmakingRequest The request of StartMatchmaking operation + //! StartMatchmaking Async + //! @param startMatchmakingRequest The request of StartMatchmaking operation virtual void StartMatchmakingAsync(const StartMatchmakingRequest& startMatchmakingRequest) = 0; - // StopMatchmaking Async - // @param stopMatchmakingRequest The request of StopMatchmaking operation + //! StopMatchmaking Async + //! @param stopMatchmakingRequest The request of StopMatchmaking operation virtual void StopMatchmakingAsync(const StopMatchmakingRequest& stopMatchmakingRequest) = 0; }; @@ -76,14 +76,14 @@ namespace AzFramework static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single; ////////////////////////////////////////////////////////////////////////// - // OnAcceptMatchAsyncComplete is fired once AcceptMatchAsync completes + //! OnAcceptMatchAsyncComplete is fired once AcceptMatchAsync completes virtual void OnAcceptMatchAsyncComplete() = 0; - // OnStartMatchmakingAsyncComplete is fired once StartMatchmakingAsync completes - // @param matchmakingTicketId The unique identifier for the matchmaking ticket + //! OnStartMatchmakingAsyncComplete is fired once StartMatchmakingAsync completes + //! @param matchmakingTicketId The unique identifier for the matchmaking ticket virtual void OnStartMatchmakingAsyncComplete(const AZStd::string& matchmakingTicketId) = 0; - // OnStopMatchmakingAsyncComplete is fired once StopMatchmakingAsync completes + //! OnStopMatchmakingAsyncComplete is fired once StopMatchmakingAsync completes virtual void OnStopMatchmakingAsyncComplete() = 0; }; using MatchmakingAsyncRequestNotificationBus = AZ::EBus; diff --git a/Code/Framework/AzFramework/AzFramework/Matchmaking/MatchmakingNotifications.h b/Code/Framework/AzFramework/AzFramework/Matchmaking/MatchmakingNotifications.h index aa19b94b4a..0ec52c7215 100644 --- a/Code/Framework/AzFramework/AzFramework/Matchmaking/MatchmakingNotifications.h +++ b/Code/Framework/AzFramework/AzFramework/Matchmaking/MatchmakingNotifications.h @@ -29,17 +29,17 @@ namespace AzFramework static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single; ////////////////////////////////////////////////////////////////////////// - // OnMatchAcceptance is fired when match is found and pending on acceptance - // Use this notification to accept found match + //! OnMatchAcceptance is fired when match is found and pending on acceptance + //! Use this notification to accept found match virtual void OnMatchAcceptance() = 0; - // OnMatchComplete is fired when match is complete + //! OnMatchComplete is fired when match is complete virtual void OnMatchComplete() = 0; - // OnMatchError is fired when match is processed with error + //! OnMatchError is fired when match is processed with error virtual void OnMatchError() = 0; - // OnMatchFailure is fired when match is failed to complete + //! OnMatchFailure is fired when match is failed to complete virtual void OnMatchFailure() = 0; }; using MatchmakingNotificationBus = AZ::EBus; diff --git a/Code/Framework/AzFramework/AzFramework/Matchmaking/MatchmakingRequests.h b/Code/Framework/AzFramework/AzFramework/Matchmaking/MatchmakingRequests.h index 9169a83588..5f5dcc0643 100644 --- a/Code/Framework/AzFramework/AzFramework/Matchmaking/MatchmakingRequests.h +++ b/Code/Framework/AzFramework/AzFramework/Matchmaking/MatchmakingRequests.h @@ -29,11 +29,11 @@ namespace AzFramework AcceptMatchRequest() = default; virtual ~AcceptMatchRequest() = default; - // Player response to accept or reject match + //! Player response to accept or reject match bool m_acceptMatch; - // A list of unique identifiers for players delivering the response + //! A list of unique identifiers for players delivering the response AZStd::vector m_playerIds; - // A unique identifier for a matchmaking ticket + //! A unique identifier for a matchmaking ticket AZStd::string m_ticketId; }; @@ -47,7 +47,7 @@ namespace AzFramework StartMatchmakingRequest() = default; virtual ~StartMatchmakingRequest() = default; - // A unique identifier for a matchmaking ticket + //! A unique identifier for a matchmaking ticket AZStd::string m_ticketId; }; @@ -61,7 +61,7 @@ namespace AzFramework StopMatchmakingRequest() = default; virtual ~StopMatchmakingRequest() = default; - // A unique identifier for a matchmaking ticket + //! A unique identifier for a matchmaking ticket AZStd::string m_ticketId; }; } // namespace AzFramework diff --git a/Code/Framework/AzFramework/AzFramework/Physics/Configuration/JointConfiguration.cpp b/Code/Framework/AzFramework/AzFramework/Physics/Configuration/JointConfiguration.cpp index d2f74510e5..0a0e1bb8e3 100644 --- a/Code/Framework/AzFramework/AzFramework/Physics/Configuration/JointConfiguration.cpp +++ b/Code/Framework/AzFramework/AzFramework/Physics/Configuration/JointConfiguration.cpp @@ -10,6 +10,7 @@ #include #include +#include namespace AzPhysics { @@ -28,6 +29,71 @@ namespace AzPhysics ->Field("ChildLocalPosition", &JointConfiguration::m_childLocalPosition) ->Field("StartSimulationEnabled", &JointConfiguration::m_startSimulationEnabled) ; + + if (auto* editContext = serializeContext->GetEditContext()) + { + editContext->Class("Joint Configuration", "Joint configuration.") + ->ClassElement(AZ::Edit::ClassElements::EditorData, "") + ->Attribute(AZ::Edit::Attributes::AutoExpand, true) + ->DataElement(AZ::Edit::UIHandlers::Default, &JointConfiguration::m_parentLocalRotation, + "Parent local rotation", "Parent joint frame relative to parent body.") + ->Attribute(AZ::Edit::Attributes::Visibility, &JointConfiguration::GetParentLocalRotationVisibility) + ->DataElement(AZ::Edit::UIHandlers::Default, &JointConfiguration::m_parentLocalPosition, + "Parent local position", "Joint position relative to parent body.") + ->Attribute(AZ::Edit::Attributes::Visibility, &JointConfiguration::GetParentLocalPositionVisibility) + ->DataElement(AZ::Edit::UIHandlers::Default, &JointConfiguration::m_childLocalRotation, + "Child local rotation", "Child joint frame relative to child body.") + ->Attribute(AZ::Edit::Attributes::Visibility, &JointConfiguration::GetChildLocalRotationVisibility) + ->DataElement(AZ::Edit::UIHandlers::Default, &JointConfiguration::m_childLocalPosition, + "Child local position", "Joint position relative to child body.") + ->Attribute(AZ::Edit::Attributes::Visibility, &JointConfiguration::GetChildLocalPositionVisibility) + ->DataElement(AZ::Edit::UIHandlers::Default, &JointConfiguration::m_startSimulationEnabled, + "Start simulation enabled", "When active, the joint will be enabled when the simulation begins.") + ->Attribute(AZ::Edit::Attributes::Visibility, &JointConfiguration::GetStartSimulationEnabledVisibility) + ; + } } } -} + + AZ::Crc32 JointConfiguration::GetPropertyVisibility(JointConfiguration::PropertyVisibility property) const + { + return (m_propertyVisibilityFlags & property) != 0 ? AZ::Edit::PropertyVisibility::Show : AZ::Edit::PropertyVisibility::Hide; + } + + void JointConfiguration::SetPropertyVisibility(JointConfiguration::PropertyVisibility property, bool isVisible) + { + if (isVisible) + { + m_propertyVisibilityFlags |= property; + } + else + { + m_propertyVisibilityFlags &= ~property; + } + } + + AZ::Crc32 JointConfiguration::GetParentLocalRotationVisibility() const + { + return GetPropertyVisibility(JointConfiguration::PropertyVisibility::ParentLocalRotation); + } + + AZ::Crc32 JointConfiguration::GetParentLocalPositionVisibility() const + { + return GetPropertyVisibility(JointConfiguration::PropertyVisibility::ParentLocalPosition); + } + + AZ::Crc32 JointConfiguration::GetChildLocalRotationVisibility() const + { + return GetPropertyVisibility(JointConfiguration::PropertyVisibility::ChildLocalRotation); + } + + AZ::Crc32 JointConfiguration::GetChildLocalPositionVisibility() const + { + return GetPropertyVisibility(JointConfiguration::PropertyVisibility::ChildLocalPosition); + } + + AZ::Crc32 JointConfiguration::GetStartSimulationEnabledVisibility() const + { + return GetPropertyVisibility(JointConfiguration::PropertyVisibility::StartSimulationEnabled); + } +} // namespace AzPhysics diff --git a/Code/Framework/AzFramework/AzFramework/Physics/Configuration/JointConfiguration.h b/Code/Framework/AzFramework/AzFramework/Physics/Configuration/JointConfiguration.h index ff9bfb5bea..2a246692d7 100644 --- a/Code/Framework/AzFramework/AzFramework/Physics/Configuration/JointConfiguration.h +++ b/Code/Framework/AzFramework/AzFramework/Physics/Configuration/JointConfiguration.h @@ -31,6 +31,25 @@ namespace AzPhysics JointConfiguration() = default; virtual ~JointConfiguration() = default; + // Visibility helpers for use in the Editor when reflected. + enum PropertyVisibility : AZ::u8 + { + ParentLocalRotation = 1 << 0, //!< Whether the parent local rotation is visible. + ParentLocalPosition = 1 << 1, //!< Whether the parent local position is visible. + ChildLocalRotation = 1 << 2, //!< Whether the child local rotation is visible. + ChildLocalPosition = 1 << 3, //!< Whether the child local position is visible. + StartSimulationEnabled = 1 << 4 //!< Whether the start simulation enabled setting is visible. + }; + + AZ::Crc32 GetPropertyVisibility(PropertyVisibility property) const; + void SetPropertyVisibility(PropertyVisibility property, bool isVisible); + + AZ::Crc32 GetParentLocalRotationVisibility() const; + AZ::Crc32 GetParentLocalPositionVisibility() const; + AZ::Crc32 GetChildLocalRotationVisibility() const; + AZ::Crc32 GetChildLocalPositionVisibility() const; + AZ::Crc32 GetStartSimulationEnabledVisibility() const; + // Entity/object association. void* m_customUserData = nullptr; @@ -40,8 +59,11 @@ namespace AzPhysics AZ::Quaternion m_childLocalRotation = AZ::Quaternion::CreateIdentity(); ///< Child joint frame relative to child body. AZ::Vector3 m_childLocalPosition = AZ::Vector3::CreateZero(); ///< Joint position relative to child body. bool m_startSimulationEnabled = true; - + // For debugging/tracking purposes only. AZStd::string m_debugName; + + // Default all visibility settings to invisible, since most joint configurations don't need to display these. + AZ::u8 m_propertyVisibilityFlags = 0; }; } diff --git a/Code/Framework/AzFramework/AzFramework/Session/ISessionHandlingRequests.h b/Code/Framework/AzFramework/AzFramework/Session/ISessionHandlingRequests.h index 065d6bb9d5..188ea7b994 100644 --- a/Code/Framework/AzFramework/AzFramework/Session/ISessionHandlingRequests.h +++ b/Code/Framework/AzFramework/AzFramework/Session/ISessionHandlingRequests.h @@ -18,16 +18,16 @@ namespace AzFramework //! The properties for handling join session request. struct SessionConnectionConfig { - // A unique identifier for registered player in session. + //! A unique identifier for registered player in session. AZStd::string m_playerSessionId; - // The DNS identifier assigned to the instance that is running the session. + //! The DNS identifier assigned to the instance that is running the session. AZStd::string m_dnsName; - // The IP address of the session. + //! The IP address of the session. AZStd::string m_ipAddress; - // The port number for the session. + //! The port number for the session. uint16_t m_port = 0; }; @@ -35,10 +35,10 @@ namespace AzFramework //! The properties for handling player connect/disconnect struct PlayerConnectionConfig { - // A unique identifier for player connection. + //! A unique identifier for player connection. uint32_t m_playerConnectionId = 0; - // A unique identifier for registered player in session. + //! A unique identifier for registered player in session. AZStd::string m_playerSessionId; }; @@ -51,12 +51,12 @@ namespace AzFramework ISessionHandlingClientRequests() = default; virtual ~ISessionHandlingClientRequests() = default; - // Request the player join session - // @param sessionConnectionConfig The required properties to handle the player join session process - // @return The result of player join session process + //! Request the player join session + //! @param sessionConnectionConfig The required properties to handle the player join session process + //! @return The result of player join session process virtual bool RequestPlayerJoinSession(const SessionConnectionConfig& sessionConnectionConfig) = 0; - // Request the connected player leave session + //! Request the connected player leave session virtual void RequestPlayerLeaveSession() = 0; }; @@ -69,26 +69,26 @@ namespace AzFramework ISessionHandlingProviderRequests() = default; virtual ~ISessionHandlingProviderRequests() = default; - // Handle the destroy session process + //! Handle the destroy session process virtual void HandleDestroySession() = 0; - // Validate the player join session process - // @param playerConnectionConfig The required properties to validate the player join session process - // @return The result of player join session validation + //! Validate the player join session process + //! @param playerConnectionConfig The required properties to validate the player join session process + //! @return The result of player join session validation virtual bool ValidatePlayerJoinSession(const PlayerConnectionConfig& playerConnectionConfig) = 0; - // Handle the player leave session process - // @param playerConnectionConfig The required properties to handle the player leave session process + //! Handle the player leave session process + //! @param playerConnectionConfig The required properties to handle the player leave session process virtual void HandlePlayerLeaveSession(const PlayerConnectionConfig& playerConnectionConfig) = 0; - // Retrieves the file location of a pem-encoded TLS certificate for Client to Server communication - // @return If successful, returns the file location of TLS certificate file; if not successful, returns - // empty string. + //! Retrieves the file location of a pem-encoded TLS certificate for Client to Server communication + //! @return If successful, returns the file location of TLS certificate file; if not successful, returns + //! empty string. virtual AZ::IO::Path GetExternalSessionCertificate() = 0; - // Retrieves the file location of a pem-encoded TLS certificate for Server to Server communication - // @return If successful, returns the file location of TLS certificate file; if not successful, returns - // empty string. + //! Retrieves the file location of a pem-encoded TLS certificate for Server to Server communication + //! @return If successful, returns the file location of TLS certificate file; if not successful, returns + //! empty string. virtual AZ::IO::Path GetInternalSessionCertificate() = 0; }; } // namespace AzFramework diff --git a/Code/Framework/AzFramework/AzFramework/Session/ISessionRequests.h b/Code/Framework/AzFramework/AzFramework/Session/ISessionRequests.h index bdc3fe1444..8b985a3187 100644 --- a/Code/Framework/AzFramework/AzFramework/Session/ISessionRequests.h +++ b/Code/Framework/AzFramework/AzFramework/Session/ISessionRequests.h @@ -25,22 +25,22 @@ namespace AzFramework ISessionRequests() = default; virtual ~ISessionRequests() = default; - // Create a session for players to find and join. - // @param createSessionRequest The request of CreateSession operation - // @return The request id if session creation request succeeds; empty if it fails + //! Create a session for players to find and join. + //! @param createSessionRequest The request of CreateSession operation + //! @return The request id if session creation request succeeds; empty if it fails virtual AZStd::string CreateSession(const CreateSessionRequest& createSessionRequest) = 0; - // Retrieve all active sessions that match the given search criteria and sorted in specific order. - // @param searchSessionsRequest The request of SearchSessions operation - // @return The response of SearchSessions operation + //! Retrieve all active sessions that match the given search criteria and sorted in specific order. + //! @param searchSessionsRequest The request of SearchSessions operation + //! @return The response of SearchSessions operation virtual SearchSessionsResponse SearchSessions(const SearchSessionsRequest& searchSessionsRequest) const = 0; - // Reserve an open player slot in a session, and perform connection from client to server. - // @param joinSessionRequest The request of JoinSession operation - // @return True if joining session succeeds; False otherwise + //! Reserve an open player slot in a session, and perform connection from client to server. + //! @param joinSessionRequest The request of JoinSession operation + //! @return True if joining session succeeds; False otherwise virtual bool JoinSession(const JoinSessionRequest& joinSessionRequest) = 0; - // Disconnect player from session. + //! Disconnect player from session. virtual void LeaveSession() = 0; }; @@ -54,19 +54,19 @@ namespace AzFramework ISessionAsyncRequests() = default; virtual ~ISessionAsyncRequests() = default; - // CreateSession Async - // @param createSessionRequest The request of CreateSession operation + //! CreateSession Async + //! @param createSessionRequest The request of CreateSession operation virtual void CreateSessionAsync(const CreateSessionRequest& createSessionRequest) = 0; - // SearchSessions Async - // @param searchSessionsRequest The request of SearchSessions operation + //! SearchSessions Async + //! @param searchSessionsRequest The request of SearchSessions operation virtual void SearchSessionsAsync(const SearchSessionsRequest& searchSessionsRequest) const = 0; - // JoinSession Async - // @param joinSessionRequest The request of JoinSession operation + //! JoinSession Async + //! @param joinSessionRequest The request of JoinSession operation virtual void JoinSessionAsync(const JoinSessionRequest& joinSessionRequest) = 0; - // LeaveSession Async + //! LeaveSession Async virtual void LeaveSessionAsync() = 0; }; @@ -85,19 +85,19 @@ namespace AzFramework static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single; ////////////////////////////////////////////////////////////////////////// - // OnCreateSessionAsyncComplete is fired once CreateSessionAsync completes - // @param createSessionResponse The request id if session creation request succeeds; empty if it fails + //! OnCreateSessionAsyncComplete is fired once CreateSessionAsync completes + //! @param createSessionResponse The request id if session creation request succeeds; empty if it fails virtual void OnCreateSessionAsyncComplete(const AZStd::string& createSessionReponse) = 0; - // OnSearchSessionsAsyncComplete is fired once SearchSessionsAsync completes - // @param searchSessionsResponse The response of SearchSessions call + //! OnSearchSessionsAsyncComplete is fired once SearchSessionsAsync completes + //! @param searchSessionsResponse The response of SearchSessions call virtual void OnSearchSessionsAsyncComplete(const SearchSessionsResponse& searchSessionsResponse) = 0; - // OnJoinSessionAsyncComplete is fired once JoinSessionAsync completes - // @param joinSessionsResponse True if joining session succeeds; False otherwise + //! OnJoinSessionAsyncComplete is fired once JoinSessionAsync completes + //! @param joinSessionsResponse True if joining session succeeds; False otherwise virtual void OnJoinSessionAsyncComplete(bool joinSessionsResponse) = 0; - // OnLeaveSessionAsyncComplete is fired once LeaveSessionAsync completes + //! OnLeaveSessionAsyncComplete is fired once LeaveSessionAsync completes virtual void OnLeaveSessionAsyncComplete() = 0; }; using SessionAsyncRequestNotificationBus = AZ::EBus; diff --git a/Code/Framework/AzFramework/AzFramework/Session/SessionConfig.h b/Code/Framework/AzFramework/AzFramework/Session/SessionConfig.h index 45e40c2f29..f951cf58fa 100644 --- a/Code/Framework/AzFramework/AzFramework/Session/SessionConfig.h +++ b/Code/Framework/AzFramework/AzFramework/Session/SessionConfig.h @@ -24,46 +24,46 @@ namespace AzFramework SessionConfig() = default; virtual ~SessionConfig() = default; - // A time stamp indicating when this session was created. Format is a number expressed in Unix time as milliseconds. + //! A time stamp indicating when this session was created. Format is a number expressed in Unix time as milliseconds. uint64_t m_creationTime = 0; - // A time stamp indicating when this data object was terminated. Same format as creation time. + //! A time stamp indicating when this data object was terminated. Same format as creation time. uint64_t m_terminationTime = 0; - // A unique identifier for a player or entity creating the session. + //! A unique identifier for a player or entity creating the session. AZStd::string m_creatorId; - // A collection of custom properties for a session. + //! A collection of custom properties for a session. AZStd::unordered_map m_sessionProperties; - // The matchmaking process information that was used to create the session. + //! The matchmaking process information that was used to create the session. AZStd::string m_matchmakingData; - // A unique identifier for the session. + //! A unique identifier for the session. AZStd::string m_sessionId; - // A descriptive label that is associated with a session. + //! A descriptive label that is associated with a session. AZStd::string m_sessionName; - // The DNS identifier assigned to the instance that is running the session. + //! The DNS identifier assigned to the instance that is running the session. AZStd::string m_dnsName; - // The IP address of the session. + //! The IP address of the session. AZStd::string m_ipAddress; - // The port number for the session. + //! The port number for the session. uint16_t m_port = 0; - // The maximum number of players that can be connected simultaneously to the session. + //! The maximum number of players that can be connected simultaneously to the session. uint64_t m_maxPlayer = 0; - // Number of players currently in the session. + //! Number of players currently in the session. uint64_t m_currentPlayer = 0; - // Current status of the session. + //! Current status of the session. AZStd::string m_status; - // Provides additional information about session status. + //! Provides additional information about session status. AZStd::string m_statusReason; }; } // namespace AzFramework diff --git a/Code/Framework/AzFramework/AzFramework/Session/SessionNotifications.h b/Code/Framework/AzFramework/AzFramework/Session/SessionNotifications.h index 2213343aa2..cf1382aeba 100644 --- a/Code/Framework/AzFramework/AzFramework/Session/SessionNotifications.h +++ b/Code/Framework/AzFramework/AzFramework/Session/SessionNotifications.h @@ -29,42 +29,42 @@ namespace AzFramework static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single; ////////////////////////////////////////////////////////////////////////// - // OnSessionHealthCheck is fired in health check process - // Use this notification to perform any custom health check - // @return True if OnSessionHealthCheck succeeds, false otherwise + //! OnSessionHealthCheck is fired in health check process + //! Use this notification to perform any custom health check + //! @return True if OnSessionHealthCheck succeeds, false otherwise virtual bool OnSessionHealthCheck() = 0; - // OnCreateSessionBegin is fired at the beginning of session creation process - // Use this notification to perform any necessary configuration or initialization before - // creating session - // @param sessionConfig The properties to describe a session - // @return True if OnCreateSessionBegin succeeds, false otherwise + //! OnCreateSessionBegin is fired at the beginning of session creation process + //! Use this notification to perform any necessary configuration or initialization before + //! creating session + //! @param sessionConfig The properties to describe a session + //! @return True if OnCreateSessionBegin succeeds, false otherwise virtual bool OnCreateSessionBegin(const SessionConfig& sessionConfig) = 0; - // OnCreateSessionEnd is fired at the end of session creation process - // Use this notification to perform any follow-up operation after session is created and active + //! OnCreateSessionEnd is fired at the end of session creation process + //! Use this notification to perform any follow-up operation after session is created and active virtual void OnCreateSessionEnd() = 0; - // OnDestroySessionBegin is fired at the beginning of session termination process - // Use this notification to perform any cleanup operation before destroying session, - // like gracefully disconnect players, cleanup data, etc. - // @return True if OnDestroySessionBegin succeeds, false otherwise + //! OnDestroySessionBegin is fired at the beginning of session termination process + //! Use this notification to perform any cleanup operation before destroying session, + //! like gracefully disconnect players, cleanup data, etc. + //! @return True if OnDestroySessionBegin succeeds, false otherwise virtual bool OnDestroySessionBegin() = 0; - // OnDestroySessionEnd is fired at the end of session termination process - // Use this notification to perform any follow-up operation after session is destroyed, - // like shutdown application process, etc. + //! OnDestroySessionEnd is fired at the end of session termination process + //! Use this notification to perform any follow-up operation after session is destroyed, + //! like shutdown application process, etc. virtual void OnDestroySessionEnd() = 0; - // OnUpdateSessionBegin is fired at the beginning of session update process - // Use this notification to perform any configuration or initialization to handle - // the session settings changing - // @param sessionConfig The properties to describe a session - // @param updateReason The reason for session update + //! OnUpdateSessionBegin is fired at the beginning of session update process + //! Use this notification to perform any configuration or initialization to handle + //! the session settings changing + //! @param sessionConfig The properties to describe a session + //! @param updateReason The reason for session update virtual void OnUpdateSessionBegin(const SessionConfig& sessionConfig, const AZStd::string& updateReason) = 0; - // OnUpdateSessionBegin is fired at the end of session update process - // Use this notification to perform any follow-up operations after session is updated + //! OnUpdateSessionBegin is fired at the end of session update process + //! Use this notification to perform any follow-up operations after session is updated virtual void OnUpdateSessionEnd() = 0; }; using SessionNotificationBus = AZ::EBus; diff --git a/Code/Framework/AzFramework/AzFramework/Session/SessionRequests.h b/Code/Framework/AzFramework/AzFramework/Session/SessionRequests.h index 1ae018e1ef..d806e6fefa 100644 --- a/Code/Framework/AzFramework/AzFramework/Session/SessionRequests.h +++ b/Code/Framework/AzFramework/AzFramework/Session/SessionRequests.h @@ -31,16 +31,16 @@ namespace AzFramework CreateSessionRequest() = default; virtual ~CreateSessionRequest() = default; - // A unique identifier for a player or entity creating the session. + //! A unique identifier for a player or entity creating the session. AZStd::string m_creatorId; - // A collection of custom properties for a session. + //! A collection of custom properties for a session. AZStd::unordered_map m_sessionProperties; - // A descriptive label that is associated with a session. + //! A descriptive label that is associated with a session. AZStd::string m_sessionName; - // The maximum number of players that can be connected simultaneously to the session. + //! The maximum number of players that can be connected simultaneously to the session. uint64_t m_maxPlayer = 0; }; @@ -54,17 +54,17 @@ namespace AzFramework SearchSessionsRequest() = default; virtual ~SearchSessionsRequest() = default; - // String containing the search criteria for the session search. If no filter expression is included, the request returns results - // for all active sessions. + //! String containing the search criteria for the session search. If no filter expression is included, the request returns results + //! for all active sessions. AZStd::string m_filterExpression; - // Instructions on how to sort the search results. If no sort expression is included, the request returns results in random order. + //! Instructions on how to sort the search results. If no sort expression is included, the request returns results in random order. AZStd::string m_sortExpression; - // The maximum number of results to return. + //! The maximum number of results to return. uint8_t m_maxResult = 0; - // A token that indicates the start of the next sequential page of results. + //! A token that indicates the start of the next sequential page of results. AZStd::string m_nextToken; }; @@ -78,10 +78,10 @@ namespace AzFramework SearchSessionsResponse() = default; virtual ~SearchSessionsResponse() = default; - // A collection of sessions that match the search criteria and sorted in specific order. + //! A collection of sessions that match the search criteria and sorted in specific order. AZStd::vector m_sessionConfigs; - // A token that indicates the start of the next sequential page of results. + //! A token that indicates the start of the next sequential page of results. AZStd::string m_nextToken; }; @@ -95,13 +95,13 @@ namespace AzFramework JoinSessionRequest() = default; virtual ~JoinSessionRequest() = default; - // A unique identifier for the session. + //! A unique identifier for the session. AZStd::string m_sessionId; - // A unique identifier for a player. Player IDs are developer-defined. + //! A unique identifier for a player. Player IDs are developer-defined. AZStd::string m_playerId; - // Developer-defined information related to a player. + //! Developer-defined information related to a player. AZStd::string m_playerData; }; } // namespace AzFramework diff --git a/Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbNativeWindow.cpp b/Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbNativeWindow.cpp index 79a6333612..c7663dc1ab 100644 --- a/Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbNativeWindow.cpp +++ b/Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbNativeWindow.cpp @@ -258,10 +258,17 @@ namespace AzFramework //////////////////////////////////////////////////////////////////////////////////////////////// void XcbNativeWindow::SetWindowTitle(const AZStd::string& title) { + // Set the title of both the window and the task bar by using + // a buffer to hold the title twice, separated by a null-terminator + auto doubleTitleSize = (title.size() + 1) * 2; + AZStd::string doubleTitle(doubleTitleSize, '\0'); + azstrncpy(doubleTitle.data(), doubleTitleSize, title.c_str(), title.size()); + azstrncpy(&doubleTitle.data()[title.size() + 1], title.size(), title.c_str(), title.size()); + xcb_void_cookie_t xcbCheckResult; xcbCheckResult = xcb_change_property( - m_xcbConnection, XCB_PROP_MODE_REPLACE, m_xcbWindow, XCB_ATOM_WM_NAME, XCB_ATOM_STRING, 8, static_cast(title.size()), - title.c_str()); + m_xcbConnection, XCB_PROP_MODE_REPLACE, m_xcbWindow, XCB_ATOM_WM_CLASS, XCB_ATOM_STRING, 8, static_cast(doubleTitle.size()), + doubleTitle.c_str()); AZ_Assert(ValidateXcbResult(xcbCheckResult), "Failed to set window title."); } diff --git a/Code/Framework/AzFramework/Platform/Mac/AzFramework/Asset/AssetSystemComponentHelper_Mac.cpp b/Code/Framework/AzFramework/Platform/Mac/AzFramework/Asset/AssetSystemComponentHelper_Mac.cpp index c6f481b65b..a3381dabb8 100644 --- a/Code/Framework/AzFramework/Platform/Mac/AzFramework/Asset/AssetSystemComponentHelper_Mac.cpp +++ b/Code/Framework/AzFramework/Platform/Mac/AzFramework/Asset/AssetSystemComponentHelper_Mac.cpp @@ -10,6 +10,8 @@ #include #include +#include + #include #include @@ -24,14 +26,20 @@ namespace AzFramework::AssetSystem::Platform AZ::IO::FixedMaxPath assetProcessorPath{ executableDirectory }; // In Mac the Editor and game is within a bundle, so the path to the sibling app // has to go up from the Contents/MacOS folder the binary is in - assetProcessorPath /= "../../../AssetProcessor.app"; + assetProcessorPath /= "../../../AssetProcessor.app/Contents/MacOS/AssetProcessor"; assetProcessorPath = assetProcessorPath.LexicallyNormal(); if (!AZ::IO::SystemFile::Exists(assetProcessorPath.c_str())) { - // Check for existence of one under a "bin" directory, i.e. engineRoot is an SDK structure. - assetProcessorPath = - AZ::IO::FixedMaxPath{engineRoot} / "bin" / AZ_TRAIT_OS_PLATFORM_NAME / AZ_BUILD_CONFIGURATION_TYPE / "AssetProcessor.app"; + if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr) + { + if (AZ::IO::FixedMaxPath installedBinariesPath; + settingsRegistry->Get(installedBinariesPath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_InstalledBinaryFolder)) + { + // Check for existence of one under a "bin" directory, i.e. engineRoot is an SDK structure. + assetProcessorPath = AZ::IO::FixedMaxPath{ engineRoot } / installedBinariesPath / "AssetProcessor.app/Contents/MacOS/AssetProcessor"; + } + } if (!AZ::IO::SystemFile::Exists(assetProcessorPath.c_str())) { @@ -39,23 +47,21 @@ namespace AzFramework::AssetSystem::Platform } } - auto fullLaunchCommand = AZ::IO::FixedMaxPathString::format(R"(open -g "%s" --args --start-hidden)", assetProcessorPath.c_str()); + AZStd::string commandLineParams; // Add the engine path to the launch command if not empty if (!engineRoot.empty()) { - fullLaunchCommand += R"( --engine-path=")"; - fullLaunchCommand += engineRoot; - fullLaunchCommand += '"'; + commandLineParams += AZStd::string::format("\"--engine-path=\"%s\"\"", engineRoot.data()); } - // Add the active project path to the launch command if not empty if (!projectPath.empty()) { - fullLaunchCommand += R"( --project-path=")"; - fullLaunchCommand += projectPath; - fullLaunchCommand += '"'; + commandLineParams += AZStd::string::format(" \"--regset=/Amazon/AzCore/Bootstrap/project_path=\"%s\"\"", projectPath.data()); } - return system(fullLaunchCommand.c_str()) == 0; + AzFramework::ProcessLauncher::ProcessLaunchInfo processLaunchInfo; + processLaunchInfo.m_processExecutableString = AZStd::move(assetProcessorPath.Native()); + processLaunchInfo.m_commandlineParameters = commandLineParams; + return AzFramework::ProcessLauncher::LaunchUnwatchedProcess(processLaunchInfo); } } diff --git a/Code/Framework/AzFramework/Platform/Windows/AzFramework/Windowing/NativeWindow_Windows.cpp b/Code/Framework/AzFramework/Platform/Windows/AzFramework/Windowing/NativeWindow_Windows.cpp index 57b37037ae..3bd0abab8d 100644 --- a/Code/Framework/AzFramework/Platform/Windows/AzFramework/Windowing/NativeWindow_Windows.cpp +++ b/Code/Framework/AzFramework/Platform/Windows/AzFramework/Windowing/NativeWindow_Windows.cpp @@ -54,6 +54,7 @@ namespace AzFramework RECT m_windowRectToRestoreOnFullScreenExit; //!< The position and size of the window to restore when exiting full screen. UINT m_windowStyleToRestoreOnFullScreenExit; //!< The style(s) of the window to restore when exiting full screen. bool m_isInBorderlessWindowFullScreenState = false; //!< Was a borderless window used to enter full screen state? + bool m_shouldEnterFullScreenStateOnActivate = false; //!< Should we enter full screen state when the window is activated? using GetDpiForWindowType = UINT(HWND hwnd); GetDpiForWindowType* m_getDpiFunction = nullptr; @@ -249,6 +250,28 @@ namespace AzFramework AzFramework::RawInputNotificationBusWindows::Broadcast(&AzFramework::RawInputNotificationsWindows::OnRawInputCodeUnitUTF16Event, codeUnitUTF16); break; } + case WM_ACTIVATE: + { + // Alt-tabbing out of the app while it is in a full screen state does not + // work unless we explicitly exit the full screen state upon deactivation, + // in which case we want to enter full screen state again upon activation. + const bool windowIsNowInactive = (LOWORD(wParam) == WA_INACTIVE); + const bool windowFullScreenState = nativeWindowImpl->GetFullScreenState(); + if (windowIsNowInactive && + windowFullScreenState) + { + nativeWindowImpl->m_shouldEnterFullScreenStateOnActivate = true; + nativeWindowImpl->SetFullScreenState(false); + } + else if (!windowIsNowInactive && + !windowFullScreenState && + nativeWindowImpl->m_shouldEnterFullScreenStateOnActivate) + { + nativeWindowImpl->m_shouldEnterFullScreenStateOnActivate = false; + nativeWindowImpl->SetFullScreenState(true); + } + break; + } case WM_SYSKEYDOWN: { // Handle ALT+ENTER to toggle full screen unless exclsuive full screen diff --git a/Code/Framework/AzFramework/Tests/AssetCatalog.cpp b/Code/Framework/AzFramework/Tests/AssetCatalog.cpp index 8cdc54f34f..9e5c72f74c 100644 --- a/Code/Framework/AzFramework/Tests/AssetCatalog.cpp +++ b/Code/Framework/AzFramework/Tests/AssetCatalog.cpp @@ -308,7 +308,9 @@ namespace UnitTest registry->Set(projectPathKey, "AutomatedTesting"); AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(*registry); - m_app->Start(desc); + AZ::ComponentApplication::StartupParameters startupParameters; + startupParameters.m_loadAssetCatalog = false; + m_app->Start(desc, startupParameters); // Without this, the user settings component would attempt to save on finalize/shutdown. Since the file is // shared across the whole engine, if multiple tests are run in parallel, the saving could cause a crash diff --git a/Code/Framework/AzGameFramework/AzGameFramework/Application/GameApplication.cpp b/Code/Framework/AzGameFramework/AzGameFramework/Application/GameApplication.cpp index 0cce93d751..f0417d206e 100644 --- a/Code/Framework/AzGameFramework/AzGameFramework/Application/GameApplication.cpp +++ b/Code/Framework/AzGameFramework/AzGameFramework/Application/GameApplication.cpp @@ -45,6 +45,13 @@ namespace AzGameFramework enginePakPath = AZ::IO::FixedMaxPath(AZ::Utils::GetExecutableDirectory()) / "engine.pak"; m_archive->OpenPack("@products@", enginePakPath.Native()); } + + // By default, load all archives in the products folder. + // If you want to adjust this for your project, make sure that the archive containing + // the bootstrap for the settings registry is still loaded here, and any archives containing + // assets used early in startup, like default shaders, are loaded here. + constexpr AZStd::string_view paksFolder = "@products@/*.pak"; // (@products@ assumed) + m_archive->OpenPacks(paksFolder); } GameApplication::~GameApplication() @@ -82,7 +89,7 @@ namespace AzGameFramework // Used the lowercase the platform name since the bootstrap.game...setreg is being loaded // from the asset cache root where all the files are in lowercased from regardless of the filesystem case-sensitivity - static constexpr char filename[] = "bootstrap.game." AZ_BUILD_CONFIGURATION_TYPE "." AZ_TRAIT_OS_PLATFORM_CODENAME_LOWER ".setreg"; + static constexpr char filename[] = "bootstrap.game." AZ_BUILD_CONFIGURATION_TYPE ".setreg"; AZ::IO::FixedMaxPath cacheRootPath; if (registry.Get(cacheRootPath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_CacheRootFolder)) diff --git a/Code/Framework/AzQtComponents/AzQtComponents/Components/ToastNotification.cpp b/Code/Framework/AzQtComponents/AzQtComponents/Components/ToastNotification.cpp index f79f355ccb..11fd1e60d8 100644 --- a/Code/Framework/AzQtComponents/AzQtComponents/Components/ToastNotification.cpp +++ b/Code/Framework/AzQtComponents/AzQtComponents/Components/ToastNotification.cpp @@ -22,11 +22,11 @@ namespace AzQtComponents , m_closeOnClick(true) , m_ui(new Ui::ToastNotification()) , m_fadeAnimation(nullptr) + , m_configuration(toastConfiguration) { setProperty("HasNoWindowDecorations", true); setAttribute(Qt::WA_ShowWithoutActivating); - setAttribute(Qt::WA_DeleteOnClose); m_borderRadius = toastConfiguration.m_borderRadius; if (m_borderRadius > 0) @@ -80,7 +80,13 @@ namespace AzQtComponents } ToastNotification::~ToastNotification() - { + { + } + + bool ToastNotification::IsDuplicate(const ToastConfiguration& toastConfiguration) + { + return toastConfiguration.m_title == m_configuration.m_title + && toastConfiguration.m_description == m_configuration.m_description; } void ToastNotification::paintEvent(QPaintEvent* event) diff --git a/Code/Framework/AzQtComponents/AzQtComponents/Components/ToastNotification.h b/Code/Framework/AzQtComponents/AzQtComponents/Components/ToastNotification.h index 7f2701a803..81b1cb4055 100644 --- a/Code/Framework/AzQtComponents/AzQtComponents/Components/ToastNotification.h +++ b/Code/Framework/AzQtComponents/AzQtComponents/Components/ToastNotification.h @@ -31,7 +31,6 @@ namespace AzQtComponents { Q_OBJECT public: - AZ_CLASS_ALLOCATOR(ToastNotification, AZ::SystemAllocator, 0); ToastNotification(QWidget* parent, const ToastConfiguration& toastConfiguration); virtual ~ToastNotification(); @@ -45,6 +44,8 @@ namespace AzQtComponents void ShowToastAtPoint(const QPoint& screenPosition, const QPointF& anchorPoint); void UpdatePosition(const QPoint& screenPosition, const QPointF& anchorPoint); + + bool IsDuplicate(const ToastConfiguration& toastConfiguration); // QDialog void showEvent(QShowEvent* showEvent) override; @@ -64,14 +65,14 @@ namespace AzQtComponents private: QPropertyAnimation* m_fadeAnimation; - + ToastConfiguration m_configuration; bool m_closeOnClick; QTimer m_lifeSpan; uint32_t m_borderRadius = 0; AZ_PUSH_DISABLE_DLL_EXPORT_MEMBER_WARNING AZStd::chrono::milliseconds m_fadeDuration; - AZStd::unique_ptr m_ui; + QScopedPointer m_ui; AZ_POP_DISABLE_DLL_EXPORT_MEMBER_WARNING }; } // namespace AzQtComponents diff --git a/Code/Framework/AzQtComponents/AzQtComponents/Components/ToastNotificationConfiguration.h b/Code/Framework/AzQtComponents/AzQtComponents/Components/ToastNotificationConfiguration.h index 5ace9d1be2..2db374640e 100644 --- a/Code/Framework/AzQtComponents/AzQtComponents/Components/ToastNotificationConfiguration.h +++ b/Code/Framework/AzQtComponents/AzQtComponents/Components/ToastNotificationConfiguration.h @@ -27,7 +27,6 @@ namespace AzQtComponents class AZ_QT_COMPONENTS_API ToastConfiguration { public: - AZ_CLASS_ALLOCATOR(ToastConfiguration, AZ::SystemAllocator, 0); ToastConfiguration(ToastType toastType, const QString& title, const QString& description); bool m_closeOnClick = true; diff --git a/Code/Framework/AzQtComponents/AzQtComponents/Components/Widgets/SpinBox.cpp b/Code/Framework/AzQtComponents/AzQtComponents/Components/Widgets/SpinBox.cpp index 06361ceee9..8ace8d0f40 100644 --- a/Code/Framework/AzQtComponents/AzQtComponents/Components/Widgets/SpinBox.cpp +++ b/Code/Framework/AzQtComponents/AzQtComponents/Components/Widgets/SpinBox.cpp @@ -657,13 +657,18 @@ bool SpinBoxWatcher::handleMouseDragStepping(QAbstractSpinBox* spinBox, QEvent* QPoint screenPos = mouseEvent->screenPos().toPoint(); const int xPos = screenPos.x(); int newXPos = xPos; + // cursor bounces on the left and right side of the screen + // looks like buggy behaviour so mouse cursor is wrapped + // around to the other side of the screen. if (xPos >= screenRect.right()) { - newXPos = screenRect.right() - 1; + // wraps mouse cursor around to the left side of the screen + newXPos = screenRect.left() + 1; } else if (xPos <= screenRect.left()) { - newXPos = screenRect.left() + 1; + // wraps mouse cursor around to the right side of the screen + newXPos = screenRect.right() - 1; } if (newXPos != xPos) diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/API/ToolsApplicationAPI.h b/Code/Framework/AzToolsFramework/AzToolsFramework/API/ToolsApplicationAPI.h index 9e29b9813c..fd4196a296 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/API/ToolsApplicationAPI.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/API/ToolsApplicationAPI.h @@ -826,6 +826,10 @@ namespace AzToolsFramework /// Path will be empty if component should have no icon. virtual AZStd::string GetComponentEditorIcon(const AZ::Uuid& /*componentType*/, AZ::Component* /*component*/) { return AZStd::string(); } + //! Return path to icon for component type. + //! Path will be empty if component type should have no icon. + virtual AZStd::string GetComponentTypeEditorIcon(const AZ::Uuid& /*componentType*/) { return AZStd::string(); } + /** * Return the icon image path based on the component type and where it is used. * \param componentType component type diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/API/ViewPaneOptions.h b/Code/Framework/AzToolsFramework/AzToolsFramework/API/ViewPaneOptions.h index be93e1af1a..33b60f0f51 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/API/ViewPaneOptions.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/API/ViewPaneOptions.h @@ -40,7 +40,7 @@ namespace AzToolsFramework bool isDisabledInSimMode = false; ///< set to true if the view pane should not be openable from level editor menu when editor is in simulation mode. bool showOnToolsToolbar = false; ///< set to true if the view pane should create a button on the tools toolbar to open/close the pane - QString toolbarIcon; ///< path to the icon to use for the toolbar button - only used if showOnToolsToolbar is set to true + AZStd::string toolbarIcon; ///< path to the icon to use for the toolbar button - only used if showOnToolsToolbar is set to true }; } // namespace AzToolsFramework diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/API/ViewportEditorModeTrackerNotificationBus.h b/Code/Framework/AzToolsFramework/AzToolsFramework/API/ViewportEditorModeTrackerNotificationBus.h index 966b9f8478..2d25dbbafc 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/API/ViewportEditorModeTrackerNotificationBus.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/API/ViewportEditorModeTrackerNotificationBus.h @@ -43,6 +43,8 @@ namespace AzToolsFramework }; //! Provides a bus to notify when the different editor modes are entered/exit. + //! @note The editor modes are not discrete states but rather each progression of mode retain the active the parent + //! mode that the new mode progressed from. class ViewportEditorModeNotifications : public AZ::EBusTraits { public: diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Application/EditorEntityManager.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Application/EditorEntityManager.cpp index ce50fc7e00..6a926b89fd 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Application/EditorEntityManager.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Application/EditorEntityManager.cpp @@ -9,9 +9,27 @@ #include #include +#include namespace AzToolsFramework { + static bool AreEntitiesValidForDuplication(const EntityIdList& entityIds) + { + for (AZ::EntityId entityId : entityIds) + { + if (GetEntityById(entityId) == nullptr) + { + AZ_Error( + "Entity", false, + "Entity with id '%llu' is not found. This can happen when you try to duplicate the entity before it is created. Please " + "ensure entities are created before trying to duplicate them.", + static_cast(entityId)); + return false; + } + } + return true; + } + void EditorEntityManager::Start() { m_prefabPublicInterface = AZ::Interface::Get(); @@ -62,7 +80,11 @@ namespace AzToolsFramework EntityIdList selectedEntities; ToolsApplicationRequestBus::BroadcastResult(selectedEntities, &ToolsApplicationRequests::GetSelectedEntities); - m_prefabPublicInterface->DuplicateEntitiesInInstance(selectedEntities); + if (AreEntitiesValidForDuplication(selectedEntities)) + { + m_prefabPublicInterface->DuplicateEntitiesInInstance(selectedEntities); + } + } void EditorEntityManager::DuplicateEntityById(AZ::EntityId entityId) @@ -72,7 +94,10 @@ namespace AzToolsFramework void EditorEntityManager::DuplicateEntities(const EntityIdList& entities) { - m_prefabPublicInterface->DuplicateEntitiesInInstance(entities); + if (AreEntitiesValidForDuplication(entities)) + { + m_prefabPublicInterface->DuplicateEntitiesInInstance(entities); + } } } diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Application/EditorEntityManager.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Application/EditorEntityManager.h index 5786cc3fbc..c4311de672 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Application/EditorEntityManager.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Application/EditorEntityManager.h @@ -34,5 +34,4 @@ namespace AzToolsFramework private: Prefab::PrefabPublicInterface* m_prefabPublicInterface = nullptr; }; - } diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Application/ToolsApplication.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Application/ToolsApplication.cpp index fbd066ec6e..f203251407 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Application/ToolsApplication.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Application/ToolsApplication.cpp @@ -70,6 +70,7 @@ #include #include #include +#include #include AZ_PUSH_DISABLE_WARNING(4251, "-Wunknown-warning-option") // 4251: 'QFileInfo::d_ptr': class 'QSharedDataPointer' needs to have dll-interface to be used by clients of class 'QFileInfo' @@ -175,7 +176,7 @@ namespace AzToolsFramework , public AZ::BehaviorEBusHandler { AZ_EBUS_BEHAVIOR_BINDER(ToolsApplicationNotificationBusHandler, "{7EB67956-FF86-461A-91E2-7B08279CFACF}", AZ::SystemAllocator, - EntityRegistered, EntityDeregistered); + EntityRegistered, EntityDeregistered, AfterEntitySelectionChanged); void EntityRegistered(AZ::EntityId entityId) override { @@ -186,6 +187,11 @@ namespace AzToolsFramework { Call(FN_EntityDeregistered, entityId); } + + void AfterEntitySelectionChanged(const EntityIdList& newlySelectedEntities, const EntityIdList& newlyDeselectedEntities) override + { + Call(FN_AfterEntitySelectionChanged, newlySelectedEntities, newlyDeselectedEntities); + } }; struct ViewPaneCallbackBusHandler final @@ -273,7 +279,8 @@ namespace AzToolsFramework azrtti_typeid(), azrtti_typeid(), azrtti_typeid(), - azrtti_typeid() + azrtti_typeid(), + azrtti_typeid(), }); return components; @@ -408,6 +415,7 @@ namespace AzToolsFramework ->Handler() ->Event("EntityRegistered", &ToolsApplicationEvents::EntityRegistered) ->Event("EntityDeregistered", &ToolsApplicationEvents::EntityDeregistered) + ->Event("AfterEntitySelectionChanged", &ToolsApplicationEvents::AfterEntitySelectionChanged) ; behaviorContext->Class() @@ -418,6 +426,8 @@ namespace AzToolsFramework ->Property("showInMenu", BehaviorValueProperty(&ViewPaneOptions::showInMenu)) ->Property("canHaveMultipleInstances", BehaviorValueProperty(&ViewPaneOptions::canHaveMultipleInstances)) ->Property("isPreview", BehaviorValueProperty(&ViewPaneOptions::isPreview)) + ->Property("showOnToolsToolbar", BehaviorValueProperty(&ViewPaneOptions::showOnToolsToolbar)) + ->Property("toolbarIcon", BehaviorValueProperty(&ViewPaneOptions::toolbarIcon)) ; behaviorContext->EBus("EditorRequestBus") @@ -426,6 +436,7 @@ namespace AzToolsFramework ->Attribute(AZ::Script::Attributes::Module, "editor") ->Event("RegisterCustomViewPane", &EditorRequests::RegisterCustomViewPane) ->Event("UnregisterViewPane", &EditorRequests::UnregisterViewPane) + ->Event("GetComponentTypeEditorIcon", &EditorRequests::GetComponentTypeEditorIcon) ; behaviorContext->EBus("EditorEventBus") diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/AssetBrowser/AssetBrowserComponent.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/AssetBrowser/AssetBrowserComponent.cpp index 483faf03c2..7da6f794d6 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/AssetBrowser/AssetBrowserComponent.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/AssetBrowser/AssetBrowserComponent.cpp @@ -102,7 +102,7 @@ namespace AzToolsFramework AzFramework::AssetCatalogEventBus::Handler::BusDisconnect(); AZ::TickBus::Handler::BusDisconnect(); AssetSystemBus::Handler::BusDisconnect(); - m_assetBrowserModel.release(); + m_assetBrowserModel.reset(); EntryCache::DestroyInstance(); } diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/AssetBrowser/AssetBrowserTableModel.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/AssetBrowser/AssetBrowserTableModel.cpp index ec709aef8a..14c5e719b4 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/AssetBrowser/AssetBrowserTableModel.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/AssetBrowser/AssetBrowserTableModel.cpp @@ -24,24 +24,13 @@ namespace AzToolsFramework AZ_Assert( m_filterModel, "Error in AssetBrowserTableModel initialization, class expects source model to be an AssetBrowserFilterModel."); - connect(sourceModel, &QAbstractItemModel::rowsInserted, this, &AssetBrowserTableModel::UpdateTableModelMaps); - connect(sourceModel, &QAbstractItemModel::rowsRemoved, this, &AssetBrowserTableModel::UpdateTableModelMaps); - connect(sourceModel, &QAbstractItemModel::modelAboutToBeReset, this, &AssetBrowserTableModel::beginResetModel); - connect( - sourceModel, &QAbstractItemModel::modelReset, this, - [this]() - { - { - QSignalBlocker sb(this); - UpdateTableModelMaps(); - } - endResetModel(); - }); - connect(sourceModel, &QAbstractItemModel::layoutChanged, this, &AssetBrowserTableModel::UpdateTableModelMaps); - connect(sourceModel, &QAbstractItemModel::dataChanged, this, &AssetBrowserTableModel::SourceDataChanged); - - QSortFilterProxyModel::setSourceModel(sourceModel); + + connect(m_filterModel, &QAbstractItemModel::rowsInserted, this, &AssetBrowserTableModel::UpdateTableModelMaps); + connect(m_filterModel, &QAbstractItemModel::rowsRemoved, this, &AssetBrowserTableModel::UpdateTableModelMaps); + connect(m_filterModel, &QAbstractItemModel::layoutChanged, this, &AssetBrowserTableModel::UpdateTableModelMaps); + connect(m_filterModel, &AssetBrowserFilterModel::filterChanged, this, &AssetBrowserTableModel::beginResetModel); + connect(m_filterModel, &QAbstractItemModel::dataChanged, this, &AssetBrowserTableModel::SourceDataChanged); } QModelIndex AssetBrowserTableModel::mapToSource(const QModelIndex& proxyIndex) const @@ -112,7 +101,7 @@ namespace AzToolsFramework int AssetBrowserTableModel::rowCount(const QModelIndex& parent) const { - return !parent.isValid() ? m_indexMap.size() : sourceModel()->rowCount(parent); + return !parent.isValid() ? m_indexMap.size() : 0; } int AssetBrowserTableModel::BuildTableModelMap( @@ -162,28 +151,29 @@ namespace AzToolsFramework AssetBrowserEntry* AssetBrowserTableModel::GetAssetEntry(QModelIndex index) const { - if (index.isValid()) - { - return static_cast(index.internalPointer()); - } - else + if (!index.isValid()) { AZ_Error("AssetBrowser", false, "Invalid Source Index provided to GetAssetEntry."); return nullptr; } + return static_cast(index.internalPointer()); } void AssetBrowserTableModel::UpdateTableModelMaps() { + beginResetModel(); emit layoutAboutToBeChanged(); - m_indexMap.clear(); - m_rowMap.clear(); + if (!m_indexMap.isEmpty() || !m_rowMap.isEmpty()) + { + m_indexMap.clear(); + m_rowMap.clear(); + } AzToolsFramework::EditorSettingsAPIBus::BroadcastResult( m_numberOfItemsDisplayed, &AzToolsFramework::EditorSettingsAPIBus::Handler::GetMaxNumberOfItemsShownInSearchView); - BuildTableModelMap(sourceModel()); emit layoutChanged(); + endResetModel(); } } // namespace AssetBrowser } // namespace AzToolsFramework diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/AssetBrowser/AssetBrowserTableModel.h b/Code/Framework/AzToolsFramework/AzToolsFramework/AssetBrowser/AssetBrowserTableModel.h index f84e6bd81c..4048156b60 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/AssetBrowser/AssetBrowserTableModel.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/AssetBrowser/AssetBrowserTableModel.h @@ -21,7 +21,9 @@ namespace AzToolsFramework class AssetBrowserFilterModel; class AssetBrowserEntry; - class AssetBrowserTableModel : public QSortFilterProxyModel + class AssetBrowserTableModel + : public QSortFilterProxyModel + , public AssetBrowserComponentNotificationBus::Handler { Q_OBJECT @@ -43,7 +45,7 @@ namespace AzToolsFramework int rowCount(const QModelIndex& parent = QModelIndex()) const override; QVariant headerData(int section, Qt::Orientation orientation, int role /* = Qt::DisplayRole */) const override; //////////////////////////////////////////////////////////////////// - private: + AssetBrowserEntry* GetAssetEntry(QModelIndex index) const; int BuildTableModelMap(const QAbstractItemModel* model, const QModelIndex& parent = QModelIndex(), int row = 0); diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/AssetBrowser/Views/EntryDelegate.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/AssetBrowser/Views/EntryDelegate.cpp index b463381638..8b58791e68 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/AssetBrowser/Views/EntryDelegate.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/AssetBrowser/Views/EntryDelegate.cpp @@ -225,29 +225,36 @@ namespace AzToolsFramework const QModelIndex indexBelow = viewModel->index(index.row() + 1, index.column()); const QModelIndex indexAbove = viewModel->index(index.row() - 1, index.column()); - auto aboveEntry = qvariant_cast(indexBelow.data(AssetBrowserModel::Roles::EntryRole)); - auto belowEntry = qvariant_cast(indexAbove.data(AssetBrowserModel::Roles::EntryRole)); + auto belowEntry = qvariant_cast(indexBelow.data(AssetBrowserModel::Roles::EntryRole)); + auto aboveEntry = qvariant_cast(indexAbove.data(AssetBrowserModel::Roles::EntryRole)); - auto aboveSourceEntry = azrtti_cast(aboveEntry); auto belowSourceEntry = azrtti_cast(belowEntry); + auto aboveSourceEntry = azrtti_cast(aboveEntry); - // if current index is the last entry in the view - // or the index above it is a Source Entry and - // the index below is invalid or is valid but it is also a source entry - // then the current index is the only child. - if (index.row() == viewModel->rowCount() - 1 || - (indexBelow.isValid() && aboveSourceEntry && - (!indexAbove.isValid() || (indexAbove.isValid() && belowSourceEntry)))) + // Last item and the above entry is a source entry + // or indexBelow is a source entry and the index above is not + if (viewModel->rowCount() > 0 && index.row() == viewModel->rowCount() - 1) + { + if (aboveSourceEntry) + { + DrawBranchPixMap(EntryBranchType::OneChild, painter, branchIconTopLeft, iconSize); + } + else + { + DrawBranchPixMap(EntryBranchType::Last, painter, branchIconTopLeft, iconSize); + } + } + else if (belowSourceEntry && aboveSourceEntry) { DrawBranchPixMap(EntryBranchType::OneChild, painter, branchIconTopLeft, iconSize); // Draw One Child Icon } - else if (indexBelow.isValid() && aboveSourceEntry) // The index above is a source entry + else if (belowSourceEntry && !aboveSourceEntry) { - DrawBranchPixMap(EntryBranchType::Last, painter, branchIconTopLeft, iconSize); // Draw First child Icon + DrawBranchPixMap(EntryBranchType::Last, painter, branchIconTopLeft, iconSize); } - else if (indexAbove.isValid() && belowSourceEntry) // The index below is a source entry + else if (aboveSourceEntry) // The index above is a source entry { - DrawBranchPixMap(EntryBranchType::First, painter, branchIconTopLeft, iconSize); // Draw Last Child Icon + DrawBranchPixMap(EntryBranchType::First, painter, branchIconTopLeft, iconSize); // Draw First Child Icon } else //the index above and below are also child entries { @@ -286,7 +293,6 @@ namespace AzToolsFramework absoluteIconPath = AZ::IO::FixedMaxPath(AZ::Utils::GetEnginePath()) / TreeIconPathLast; break; case AzToolsFramework::AssetBrowser::EntryBranchType::OneChild: - default: absoluteIconPath = AZ::IO::FixedMaxPath(AZ::Utils::GetEnginePath()) / TreeIconPathOneChild; break; } @@ -311,5 +317,4 @@ namespace AzToolsFramework } // namespace AssetBrowser } // namespace AzToolsFramework - #include "AssetBrowser/Views/moc_EntryDelegate.cpp" diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/AzToolsFrameworkModule.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/AzToolsFrameworkModule.cpp index d2a88df544..2cdf409125 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/AzToolsFrameworkModule.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/AzToolsFrameworkModule.cpp @@ -54,6 +54,7 @@ #include #include #include +#include AZ_DEFINE_BUDGET(AzToolsFramework); @@ -106,6 +107,7 @@ namespace AzToolsFramework AzToolsFramework::Components::EditorIntersectorComponent::CreateDescriptor(), AzToolsFramework::AzToolsFrameworkConfigurationSystemComponent::CreateDescriptor(), AzToolsFramework::Components::EditorEntityUiSystemComponent::CreateDescriptor(), + AzToolsFramework::Script::LuaSymbolsReporterSystemComponent::CreateDescriptor(), }); } } diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ContainerEntity/ContainerEntitySystemComponent.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/ContainerEntity/ContainerEntitySystemComponent.cpp index 61b257a189..0a27a5cb90 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/ContainerEntity/ContainerEntitySystemComponent.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ContainerEntity/ContainerEntitySystemComponent.cpp @@ -26,8 +26,12 @@ namespace AzToolsFramework AZ::Interface::Unregister(this); } - void ContainerEntitySystemComponent::Reflect([[maybe_unused]] AZ::ReflectContext* context) + void ContainerEntitySystemComponent::Reflect(AZ::ReflectContext* context) { + if (auto serializeContext = azrtti_cast(context)) + { + serializeContext->Class()->Version(1); + } } void ContainerEntitySystemComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided) diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/FocusMode/FocusModeSystemComponent.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/FocusMode/FocusModeSystemComponent.cpp index 44ff603c0c..16640f4edf 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/FocusMode/FocusModeSystemComponent.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/FocusMode/FocusModeSystemComponent.cpp @@ -47,8 +47,12 @@ namespace AzToolsFramework AZ::Interface::Unregister(this); } - void FocusModeSystemComponent::Reflect([[maybe_unused]] AZ::ReflectContext* context) + void FocusModeSystemComponent::Reflect(AZ::ReflectContext* context) { + if (auto serializeContext = azrtti_cast(context)) + { + serializeContext->Class()->Version(1); + } } void FocusModeSystemComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided) diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Input/QtEventToAzInputManager.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Input/QtEventToAzInputManager.cpp index 574470ad6e..7ee20997f9 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Input/QtEventToAzInputManager.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Input/QtEventToAzInputManager.cpp @@ -210,8 +210,8 @@ namespace AzToolsFramework m_enabled = enabled; if (!enabled) { - // Send an internal focus change event to reset our input state to fresh if we're disabled. - HandleFocusChange(nullptr); + // Clear input channels to reset our input state if we're disabled. + ClearInputChannels(nullptr); } } @@ -246,7 +246,7 @@ namespace AzToolsFramework if (eventType == QEvent::Type::MouseMove) { - // clear override cursor when moving outside of the viewport + // Clear override cursor when moving outside of the viewport const auto* mouseEvent = static_cast(event); if (m_overrideCursor && !m_sourceWidget->geometry().contains(m_sourceWidget->mapFromGlobal(mouseEvent->globalPos()))) { @@ -255,6 +255,13 @@ namespace AzToolsFramework } } + // If the application state changes (e.g. we have alt-tabbed or minimized the + // main editor window) then ensure all input channels are cleared + if (eventType == QEvent::ApplicationStateChange) + { + ClearInputChannels(event); + } + // Only accept mouse & key release events that originate from an object that is not our target widget, // as we don't want to erroneously intercept user input meant for another component. if (object != m_sourceWidget && eventType != QEvent::Type::KeyRelease && eventType != QEvent::Type::MouseButtonRelease) @@ -264,9 +271,6 @@ namespace AzToolsFramework if (eventType == QEvent::FocusIn || eventType == QEvent::FocusOut) { - // If our focus changes, go ahead and reset all input devices. - HandleFocusChange(event); - // If we focus in on the source widget and the mouse is contained in its // bounds, refresh the cached cursor position to ensure it is up to date (this // ensures cursor positions are refreshed correctly with context menu focus changes) @@ -451,7 +455,7 @@ namespace AzToolsFramework NotifyUpdateChannelIfNotIdle(cursorZChannel, wheelEvent); } - void QtEventToAzInputMapper::HandleFocusChange(QEvent* event) + void QtEventToAzInputMapper::ClearInputChannels(QEvent* event) { for (auto& channelData : m_channels) { diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Input/QtEventToAzInputManager.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Input/QtEventToAzInputManager.h index 4cb391e63b..4c4e09ea05 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Input/QtEventToAzInputManager.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Input/QtEventToAzInputManager.h @@ -138,8 +138,9 @@ namespace AzToolsFramework void HandleKeyEvent(QKeyEvent* keyEvent); // Handles mouse wheel events. void HandleWheelEvent(QWheelEvent* wheelEvent); - // Handles focus change events. - void HandleFocusChange(QEvent* event); + + // Clear all input channels (set all channel states to 'ended'). + void ClearInputChannels(QEvent* event); // Populates m_keyMappings. void InitializeKeyMappings(); diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp index d976c91c3e..7c5a6ebb8a 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp @@ -12,11 +12,12 @@ #include #include +#include #include #include #include -#include #include +#include #include #include #include @@ -565,6 +566,7 @@ namespace AzToolsFramework parentId = m_prefabFocusPublicInterface->GetFocusedPrefabContainerEntityId(editorEntityContextId); } + // If the parent entity isn't owned by a prefab instance, bail. InstanceOptionalReference owningInstanceOfParentEntity = GetOwnerInstanceByEntityId(parentId); if (!owningInstanceOfParentEntity) { @@ -572,6 +574,14 @@ namespace AzToolsFramework "Cannot add entity because the owning instance of parent entity with id '%llu' could not be found.", static_cast(parentId))); } + + // If the parent entity is a closed container, bail. + if (auto containerEntityInterface = AZ::Interface::Get(); !containerEntityInterface->IsContainerOpen(parentId)) + { + return AZ::Failure(AZStd::string::format( + "Cannot add entity because the parent entity (id '%llu') is a closed container entity.", + static_cast(parentId))); + } EntityAlias entityAlias = Instance::GenerateEntityAlias(); @@ -1110,7 +1120,8 @@ namespace AzToolsFramework // Select the duplicated entities/instances auto selectionUndo = aznew SelectionCommand(duplicatedEntityAndInstanceIds, "Select Duplicated Entities/Instances"); selectionUndo->SetParent(undoBatch.GetUndoBatch()); - ToolsApplicationRequestBus::Broadcast(&ToolsApplicationRequestBus::Events::SetSelectedEntities, duplicatedEntityAndInstanceIds); + ToolsApplicationRequestBus::Broadcast( + &ToolsApplicationRequestBus::Events::SetSelectedEntities, duplicatedEntityAndInstanceIds); } return AZ::Success(AZStd::move(duplicatedEntityAndInstanceIds)); diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Script/LuaSymbolsReporterBus.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Script/LuaSymbolsReporterBus.h new file mode 100644 index 0000000000..f0bfecef38 --- /dev/null +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Script/LuaSymbolsReporterBus.h @@ -0,0 +1,110 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ +#pragma once + +#include +#include + +namespace AzToolsFramework +{ + namespace Script + { + struct LuaPropertySymbol + { + AZ_TYPE_INFO(LuaPropertySymbol, "{5AFB147F-50A4-4F00-9F82-D8D5BBC970D6}"); + static void Reflect(AZ::ReflectContext* context); + + AZStd::string m_name; + bool m_canRead; + bool m_canWrite; + + AZStd::string ToString() const; + }; + + struct LuaMethodSymbol + { + AZ_TYPE_INFO(LuaMethodSymbol, "{7B074A36-C81D-46A0-8D2F-62E426EBE38A}"); + static void Reflect(AZ::ReflectContext* context); + + AZStd::string m_name; + AZStd::string m_debugArgumentInfo; + + AZStd::string ToString() const; + }; + + struct LuaClassSymbol + { + AZ_TYPE_INFO(LuaClassSymbol, "{5FBE5841-A8E1-44B6-BEDA-22302CF8DF5F}"); + static void Reflect(AZ::ReflectContext* context); + + AZStd::string m_name; + AZ::Uuid m_typeId; + AZStd::vector m_properties; + AZStd::vector m_methods; + + AZStd::string ToString() const; + }; + + struct LuaEBusSender + { + AZ_TYPE_INFO(LuaEBusSender, "{23EE4188-0924-49DB-BF3F-EB7AAB6D5E5C}"); + static void Reflect(AZ::ReflectContext* context); + + AZStd::string m_name; + AZStd::string m_debugArgumentInfo; + AZStd::string m_category; + + AZStd::string ToString() const; + }; + + struct LuaEBusSymbol + { + AZ_TYPE_INFO(LuaEBusSymbol, "{381C5639-A916-4D2E-B825-50A3F2D93137}"); + static void Reflect(AZ::ReflectContext* context); + + AZStd::string m_name; + bool m_canBroadcast; + bool m_canQueue; + bool m_hasHandler; + + AZStd::vector m_senders; + + AZStd::string ToString() const; + }; + + // This is an EBus useful to scrape classes, globals and EBuses exposed to game scripting + // e.g: Lua. + class LuaSymbolsReporterRequests + { + public: + AZ_RTTI(LuaSymbolsReporterRequests, "{3FF9A105-3159-49FF-8DC6-4948AE7B4AB8}"); + virtual ~LuaSymbolsReporterRequests() = default; + // Put your public methods here + + virtual const AZStd::vector& GetListOfClasses() = 0; + virtual const AZStd::vector& GetListOfGlobalProperties() = 0; + virtual const AZStd::vector& GetListOfGlobalFunctions() = 0; + virtual const AZStd::vector& GetListOfEBuses() = 0; + + }; + + class LuaSymbolsReporterBusTraits + : public AZ::EBusTraits + { + public: + ////////////////////////////////////////////////////////////////////////// + // EBusTraits overrides + static constexpr AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single; + static constexpr AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single; + ////////////////////////////////////////////////////////////////////////// + }; + + using LuaSymbolsReporterRequestBus = AZ::EBus; + + } // namespace Script +} // namespace AzToolsFramework diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Script/LuaSymbolsReporterSystemComponent.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Script/LuaSymbolsReporterSystemComponent.cpp new file mode 100644 index 0000000000..81ba8ee398 --- /dev/null +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Script/LuaSymbolsReporterSystemComponent.cpp @@ -0,0 +1,475 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include +#include +#include + +#include "LuaSymbolsReporterSystemComponent.h" + +namespace AzToolsFramework +{ + namespace Script + { + AZStd::string LuaPropertySymbol::ToString() const + { + return AZStd::string::format("%s [%s/%s]", + m_name.c_str(), + m_canRead ? "R" : "_", + m_canWrite ? "W" : "_"); + } + + void LuaPropertySymbol::Reflect(AZ::ReflectContext* context) + { + auto behaviorContext = azrtti_cast(context); + if (behaviorContext) + { + behaviorContext->Class() + ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation) + ->Attribute(AZ::Script::Attributes::Module, "script") + ->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All) + ->Attribute(AZ::Script::Attributes::Storage, AZ::Script::Attributes::StorageType::Value) + ->Property("name", BehaviorValueProperty(&LuaPropertySymbol::m_name)) + ->Property("canRead", BehaviorValueProperty(&LuaPropertySymbol::m_canRead)) + ->Property("canWrite", BehaviorValueProperty(&LuaPropertySymbol::m_canWrite)) + ->Method("ToString", &LuaPropertySymbol::ToString) + ->Attribute(AZ::Script::Attributes::Operator, AZ::Script::Attributes::OperatorType::ToString) + ; + } + } + + AZStd::string LuaMethodSymbol::ToString() const + { + return AZStd::string::format("%s(%s)", m_name.c_str(), m_debugArgumentInfo.c_str()); + } + + void LuaMethodSymbol::Reflect(AZ::ReflectContext* context) + { + auto behaviorContext = azrtti_cast(context); + if (behaviorContext) + { + behaviorContext->Class() + ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation) + ->Attribute(AZ::Script::Attributes::Module, "script") + ->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All) + ->Attribute(AZ::Script::Attributes::Storage, AZ::Script::Attributes::StorageType::Value) + ->Property("name", BehaviorValueProperty(&LuaMethodSymbol::m_name)) + ->Property("debugArgumentInfo", BehaviorValueProperty(&LuaMethodSymbol::m_debugArgumentInfo)) + ->Method("ToString", &LuaMethodSymbol::ToString) + ->Attribute(AZ::Script::Attributes::Operator, AZ::Script::Attributes::OperatorType::ToString) + ; + } + } + + AZStd::string LuaClassSymbol::ToString() const + { + return AZStd::string::format("%s [%s]", m_name.c_str(), m_typeId.ToString().c_str()); + } + + void LuaClassSymbol::Reflect(AZ::ReflectContext* context) + { + auto behaviorContext = azrtti_cast(context); + if (behaviorContext) + { + behaviorContext->Class() + ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation) + ->Attribute(AZ::Script::Attributes::Module, "script") + ->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All) + ->Attribute(AZ::Script::Attributes::Storage, AZ::Script::Attributes::StorageType::Value) + ->Property("name", BehaviorValueProperty(&LuaClassSymbol::m_name)) + ->Property("typeId", BehaviorValueProperty(&LuaClassSymbol::m_typeId)) + ->Property("properties", BehaviorValueProperty(&LuaClassSymbol::m_properties)) + ->Property("methods", BehaviorValueProperty(&LuaClassSymbol::m_methods)) + ->Method("ToString", &LuaClassSymbol::ToString) + ->Attribute(AZ::Script::Attributes::Operator, AZ::Script::Attributes::OperatorType::ToString) + ; + } + } + + AZStd::string LuaEBusSender::ToString() const + { + return AZStd::string::format("%s(%s) - [%s]", m_name.c_str(), m_debugArgumentInfo.c_str(), m_category.c_str()); + } + + void LuaEBusSender::Reflect(AZ::ReflectContext* context) + { + auto behaviorContext = azrtti_cast(context); + if (behaviorContext) + { + behaviorContext->Class() + ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation) + ->Attribute(AZ::Script::Attributes::Module, "script") + ->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All) + ->Attribute(AZ::Script::Attributes::Storage, AZ::Script::Attributes::StorageType::Value) + ->Property("name", BehaviorValueProperty(&LuaEBusSender::m_name)) + ->Property("debugArgumentInfo", BehaviorValueProperty(&LuaEBusSender::m_debugArgumentInfo)) + ->Property("category", BehaviorValueProperty(&LuaEBusSender::m_category)) + ->Method("ToString", &LuaEBusSender::ToString) + ->Attribute(AZ::Script::Attributes::Operator, AZ::Script::Attributes::OperatorType::ToString) + ; + } + } + + AZStd::string LuaEBusSymbol::ToString() const + { + auto boolToStr = +[](bool val) { return val ? "true" : "false"; }; + return AZStd::string::format("%s: canBroadcast(%s), canQueue(%s), hasHandler(%s)", + m_name.c_str(), + boolToStr(m_canBroadcast), boolToStr(m_canQueue), boolToStr(m_hasHandler)); + } + + void LuaEBusSymbol::Reflect(AZ::ReflectContext* context) + { + auto behaviorContext = azrtti_cast(context); + if (behaviorContext) + { + behaviorContext->Class() + ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation) + ->Attribute(AZ::Script::Attributes::Module, "script") + ->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All) + ->Attribute(AZ::Script::Attributes::Storage, AZ::Script::Attributes::StorageType::Value) + ->Property("name", BehaviorValueProperty(&LuaEBusSymbol::m_name)) + ->Property("canBroadcast", BehaviorValueProperty(&LuaEBusSymbol::m_canBroadcast)) + ->Property("canQueue", BehaviorValueProperty(&LuaEBusSymbol::m_canQueue)) + ->Property("hasHandler", BehaviorValueProperty(&LuaEBusSymbol::m_hasHandler)) + ->Property("senders", BehaviorValueProperty(&LuaEBusSymbol::m_senders)) + ->Method("ToString", &LuaEBusSymbol::ToString) + ->Attribute(AZ::Script::Attributes::Operator, AZ::Script::Attributes::OperatorType::ToString) + ; + } + } + + //! This local class helps us keeping private the sensitive data in LuaSymbolsReporterSystemComponent + //! Used inside the function pointers for several AZ::SciptContextDebug::Enumerate* functions. + class IntrusiveHelper + { + public: + static AZStd::vector& GetClassSymbols(LuaSymbolsReporterSystemComponent& symbolsReporter) { return symbolsReporter.m_cachedClassSymbols; } + static AZStd::unordered_map& GetClassUuidToIndexMap(LuaSymbolsReporterSystemComponent& symbolsReporter) { return symbolsReporter.m_classUuidToIndexMap; } + static AZStd::vector& GetGlobalPropertySymbols(LuaSymbolsReporterSystemComponent& symbolsReporter) { return symbolsReporter.m_cachedGlobalPropertySymbols; } + static AZStd::vector& GetGlobalFunctionSymbols(LuaSymbolsReporterSystemComponent& symbolsReporter) { return symbolsReporter.m_cachedGlobalFunctionSymbols; } + static AZStd::vector& GetEBusSymbols(LuaSymbolsReporterSystemComponent& symbolsReporter) { return symbolsReporter.m_cachedEbusSymbols; } + static AZStd::unordered_map& GetEBusNameToIndexMap(LuaSymbolsReporterSystemComponent& symbolsReporter) { return symbolsReporter.m_ebusNameToIndexMap; } + }; + + void LuaSymbolsReporterSystemComponent::Reflect(AZ::ReflectContext* context) + { + LuaPropertySymbol::Reflect(context); + LuaMethodSymbol::Reflect(context); + LuaClassSymbol::Reflect(context); + LuaEBusSender::Reflect(context); + LuaEBusSymbol::Reflect(context); + + if (auto serializeContext = azrtti_cast(context)) + { + serializeContext->Class() + ->Version(0); + + serializeContext->RegisterGenericType>(); + serializeContext->RegisterGenericType>(); + serializeContext->RegisterGenericType>(); + serializeContext->RegisterGenericType>(); + serializeContext->RegisterGenericType>(); + } + + if (AZ::BehaviorContext* behaviorContext = azrtti_cast(context)) + { + behaviorContext->EBus("LuaSymbolsReporterBus") + ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation) + ->Attribute(AZ::Script::Attributes::Module, "script") + ->Event("GetListOfClasses", &LuaSymbolsReporterRequests::GetListOfClasses) + ->Event("GetListOfGlobalProperties", &LuaSymbolsReporterRequests::GetListOfGlobalProperties) + ->Event("GetListOfGlobalFunctions", &LuaSymbolsReporterRequests::GetListOfGlobalFunctions) + ->Event("GetListOfEBuses", &LuaSymbolsReporterRequests::GetListOfEBuses) + ; + } + } + + void LuaSymbolsReporterSystemComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided) + { + provided.push_back(AZ_CRC_CE("LuaSymbolsReporterSystemService")); + } + + void LuaSymbolsReporterSystemComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible) + { + incompatible.push_back(AZ_CRC_CE("LuaSymbolsReporterSystemService")); + } + + void LuaSymbolsReporterSystemComponent::GetRequiredServices([[maybe_unused]] AZ::ComponentDescriptor::DependencyArrayType& required) + { + required.push_back(AZ_CRC_CE("ScriptService")); + } + + void LuaSymbolsReporterSystemComponent::GetDependentServices([[maybe_unused]] AZ::ComponentDescriptor::DependencyArrayType& dependent) + { + // No dependent services. + } + + void LuaSymbolsReporterSystemComponent::Activate() + { + AzToolsFramework::EditorEvents::Bus::Handler::BusConnect(); + LuaSymbolsReporterRequestBus::Handler::BusConnect(); + } + + void LuaSymbolsReporterSystemComponent::Deactivate() + { + LuaSymbolsReporterRequestBus::Handler::BusDisconnect(); + AzToolsFramework::EditorEvents::Bus::Handler::BusDisconnect(); + } + + AZ::ScriptContext* LuaSymbolsReporterSystemComponent::InitScriptContext() + { + if (m_scriptContext) + { + return m_scriptContext; + } + + AZ::ScriptSystemRequestBus::BroadcastResult(m_scriptContext, &AZ::ScriptSystemRequests::GetContext, AZ::ScriptContextIds::DefaultScriptContextId); + return m_scriptContext; + } + + void LuaSymbolsReporterSystemComponent::LoadGlobalSymbols() + { + auto scriptContext = InitScriptContext(); + if (!scriptContext) + { + AZ_Error(LogName, false, "Invalid scriptContext"); + return; + } + + scriptContext->EnableDebug(); + + auto debugContext = scriptContext->GetDebugContext(); + if (!debugContext) + { + AZ_Error(LogName, false, "Invalid debugContext from scriptContext"); + return; + } + + auto enumMethodFunc = +[]([[maybe_unused]] const AZ::Uuid* classTypeId, const char* methodName, const char* debugArgumentInfo, void* userData) -> bool + { + auto& mySelf = *reinterpret_cast(userData); + auto& methodSymbols = IntrusiveHelper::GetGlobalFunctionSymbols(mySelf); + methodSymbols.push_back({}); + auto& methodSymbol = methodSymbols.back(); + methodSymbol.m_name = methodName; + if (debugArgumentInfo) + { + methodSymbol.m_debugArgumentInfo = debugArgumentInfo; + } + return true; + }; + + auto enumPropertyFunc = +[]([[maybe_unused]] const AZ::Uuid* classTypeId, const char* propertyName, bool canRead, bool canWrite, void* userData) -> bool + { + auto& mySelf = *reinterpret_cast(userData); + auto& propertySymbols = IntrusiveHelper::GetGlobalPropertySymbols(mySelf); + propertySymbols.push_back({}); + auto& propertySymbol = propertySymbols.back(); + propertySymbol.m_name = propertyName; + propertySymbol.m_canRead = canRead; + propertySymbol.m_canWrite = canWrite; + + return true; + }; + + debugContext->EnumRegisteredGlobals(enumMethodFunc, enumPropertyFunc, this); + + scriptContext->DisableDebug(); + } + + /////////////////////////////////////////////////////////////////////////// + /// LuaSymbolsReporterRequestBus::Handler + const AZStd::vector& LuaSymbolsReporterSystemComponent::GetListOfClasses() + { + if (!m_cachedClassSymbols.empty()) + { + return m_cachedClassSymbols; + } + + auto scriptContext = InitScriptContext(); + if (!scriptContext) + { + return m_cachedClassSymbols; + } + + scriptContext->EnableDebug(); + + auto debugContext = scriptContext->GetDebugContext(); + if (!debugContext) + { + return m_cachedClassSymbols; + } + + auto enumClassFunc = +[](const char* className, const AZ::Uuid& classTypeId, void* userData) -> bool + { + auto& mySelf = *reinterpret_cast(userData); + + auto& classSymbols = IntrusiveHelper::GetClassSymbols(mySelf); + classSymbols.push_back({}); + auto& classSymbol = classSymbols.back(); + classSymbol.m_name = className; + classSymbol.m_typeId = classTypeId; + + auto& uuidToClassMap = IntrusiveHelper::GetClassUuidToIndexMap(mySelf); + uuidToClassMap.emplace(classTypeId, classSymbols.size() - 1); + + return true; + }; + + auto enumMethodFunc = +[](const AZ::Uuid* classTypeId, const char* methodName, const char* debugArgumentInfo, void* userData) -> bool + { + auto& mySelf = *reinterpret_cast(userData); + auto& classUuidToIndexMap = IntrusiveHelper::GetClassUuidToIndexMap(mySelf); + auto itor = classUuidToIndexMap.find(*classTypeId); + if (itor == classUuidToIndexMap.end()) + { + AZ_Error(LogName, false, "Can not add method [%s] because class uuid [%s] is not registered", methodName, classTypeId->ToString().c_str()); + return false; + } + + auto classIndex = itor->second; + auto& classSymbols = IntrusiveHelper::GetClassSymbols(mySelf); + auto& classSymbol = classSymbols[classIndex]; + classSymbol.m_methods.push_back({}); + auto& methodSymbol = classSymbol.m_methods.back(); + methodSymbol.m_name = methodName; + if (debugArgumentInfo) + { + methodSymbol.m_debugArgumentInfo = debugArgumentInfo; + } + return true; + }; + + auto enumPropertyFunc = +[](const AZ::Uuid* classTypeId, const char* propertyName, bool canRead, bool canWrite, void* userData) -> bool + { + auto& mySelf = *reinterpret_cast(userData); + auto& classUuidToIndexMap = IntrusiveHelper::GetClassUuidToIndexMap(mySelf); + auto itor = classUuidToIndexMap.find(*classTypeId); + if (itor == classUuidToIndexMap.end()) + { + AZ_Error(LogName, false, "Can not add property [%s] because class uuid [%s] is not registered", propertyName, classTypeId->ToString().c_str()); + return false; + } + + auto classIndex = itor->second; + auto& classSymbols = IntrusiveHelper::GetClassSymbols(mySelf); + auto& classSymbol = classSymbols[classIndex]; + classSymbol.m_properties.push_back({}); + auto& propertySymbol = classSymbol.m_properties.back(); + propertySymbol.m_name = propertyName; + propertySymbol.m_canRead = canRead; + propertySymbol.m_canWrite = canWrite; + + return true; + }; + + debugContext->EnumRegisteredClasses(enumClassFunc, enumMethodFunc, enumPropertyFunc, this); + + scriptContext->DisableDebug(); + + return m_cachedClassSymbols; + } + + const AZStd::vector& LuaSymbolsReporterSystemComponent::GetListOfGlobalProperties() + { + if (!m_cachedGlobalPropertySymbols.empty()) + { + return m_cachedGlobalPropertySymbols; + } + + LoadGlobalSymbols(); + + return m_cachedGlobalPropertySymbols; + } + + const AZStd::vector& LuaSymbolsReporterSystemComponent::GetListOfGlobalFunctions() + { + if (!m_cachedGlobalFunctionSymbols.empty()) + { + return m_cachedGlobalFunctionSymbols; + } + + LoadGlobalSymbols(); + + return m_cachedGlobalFunctionSymbols; + } + + const AZStd::vector& LuaSymbolsReporterSystemComponent::GetListOfEBuses() + { + if (!m_cachedEbusSymbols.empty()) + { + return m_cachedEbusSymbols; + } + + auto scriptContext = InitScriptContext(); + if (!scriptContext) + { + return m_cachedEbusSymbols; + } + + scriptContext->EnableDebug(); + + auto debugContext = scriptContext->GetDebugContext(); + if (!debugContext) + { + return m_cachedEbusSymbols; + } + + auto enumEBusFunc = +[](const AZStd::string& ebusName, bool canBroadcast, bool canQueue, bool hasHandler, void* userData) -> bool + { + auto& mySelf = *reinterpret_cast(userData); + + auto& ebusSymbols = IntrusiveHelper::GetEBusSymbols(mySelf); + ebusSymbols.push_back({}); + auto& ebusSymbol = ebusSymbols.back(); + ebusSymbol.m_name = ebusName; + ebusSymbol.m_canBroadcast = canBroadcast; + ebusSymbol.m_canQueue = canQueue; + ebusSymbol.m_hasHandler = hasHandler; + + auto& nameToIndexMap = IntrusiveHelper::GetEBusNameToIndexMap(mySelf); + nameToIndexMap.emplace(ebusName, ebusSymbols.size() - 1); + + return true; + }; + + auto enumEBusSenderFunc = +[](const AZStd::string& ebusName, const AZStd::string& senderName, const AZStd::string& debugArgumentInfo, const AZStd::string& category, void* userData) -> bool + { + auto& mySelf = *reinterpret_cast(userData); + auto& nameToIndexMap = IntrusiveHelper::GetEBusNameToIndexMap(mySelf); + auto itor = nameToIndexMap.find(ebusName); + if (itor == nameToIndexMap.end()) + { + AZ_Error(LogName, false, "Can not add ebus sender [%s] because ebus [%s] is not registered", senderName.c_str(), ebusName.c_str()); + return false; + } + + auto ebusIndex = itor->second; + auto& ebusSymbols = IntrusiveHelper::GetEBusSymbols(mySelf); + auto& ebusSymbol = ebusSymbols[ebusIndex]; + + ebusSymbol.m_senders.push_back({}); + auto& ebusSender = ebusSymbol.m_senders.back(); + ebusSender.m_name = senderName; + ebusSender.m_debugArgumentInfo = debugArgumentInfo; + ebusSender.m_category = category; + return true; + }; + + debugContext->EnumRegisteredEBuses(enumEBusFunc, enumEBusSenderFunc, this); + + scriptContext->DisableDebug(); + + return m_cachedEbusSymbols; + } + /////////////////////////////////////////////////////////////////////////// + + } //namespace Script +} // namespace AzToolsFramework diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Script/LuaSymbolsReporterSystemComponent.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Script/LuaSymbolsReporterSystemComponent.h new file mode 100644 index 0000000000..2467c82ad7 --- /dev/null +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Script/LuaSymbolsReporterSystemComponent.h @@ -0,0 +1,73 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ +#pragma once + +#include +#include + +#include + +namespace AzToolsFramework +{ + namespace Script + { + /// System component for LuaSymbolsReporterRequestBus + class LuaSymbolsReporterSystemComponent + : public AZ::Component + , public LuaSymbolsReporterRequestBus::Handler + , private AzToolsFramework::EditorEvents::Bus::Handler + { + public: + AZ_COMPONENT(LuaSymbolsReporterSystemComponent, "{DB8D95BA-FECF-4D81-A45C-8C05E706E2AC}"); + static void Reflect(AZ::ReflectContext* context); + + static constexpr char LogName[] = "LuaSymbolsReporter"; + + LuaSymbolsReporterSystemComponent() = default; + ~LuaSymbolsReporterSystemComponent() = default; + + /////////////////////////////////////////////////////////////////////////// + /// LuaSymbolsReporterRequestBus::Handler + const AZStd::vector& GetListOfClasses() override; + const AZStd::vector& GetListOfGlobalProperties() override; + const AZStd::vector& GetListOfGlobalFunctions() override; + const AZStd::vector& GetListOfEBuses() override; + /////////////////////////////////////////////////////////////////////////// + + private: + friend class IntrusiveHelper; + + static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided); + static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible); + static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required); + static void GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent); + + // AZ::Component + void Activate() override; + void Deactivate() override; + + AZ::ScriptContext* InitScriptContext(); + void LoadGlobalSymbols(); + + AZ::ScriptContext* m_scriptContext = nullptr; + + AZStd::vector m_cachedClassSymbols; + // The key is a class uuid, the value is the index in @m_cachedClassSymbols + AZStd::unordered_map m_classUuidToIndexMap; + + AZStd::vector m_cachedGlobalPropertySymbols; + AZStd::vector m_cachedGlobalFunctionSymbols; + + AZStd::vector m_cachedEbusSymbols; + + // The key is the ebus name, the value is the index in @m_cachedEbusSymbols + AZStd::unordered_map m_ebusNameToIndexMap; + + }; + } // namespace Script +} // namespace AzToolsFramework diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/ComponentPalette/ComponentPaletteUtil.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/ComponentPalette/ComponentPaletteUtil.cpp index b6f5b41b65..888554d065 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/ComponentPalette/ComponentPaletteUtil.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/ComponentPalette/ComponentPaletteUtil.cpp @@ -90,7 +90,7 @@ namespace AzToolsFramework } AZStd::string componentIconPath; - EBUS_EVENT_RESULT(componentIconPath, AzToolsFramework::EditorRequests::Bus, GetComponentEditorIcon, componentClass->m_typeId, nullptr); + AzToolsFramework::EditorRequestBus::BroadcastResult(componentIconPath, &AzToolsFramework::EditorRequests::GetComponentTypeEditorIcon, componentClass->m_typeId); componentIconTable[componentClass] = QString::fromUtf8(componentIconPath.c_str()); } diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Notifications/ToastNotificationsView.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Notifications/ToastNotificationsView.cpp index e039230783..277d3fa3c5 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Notifications/ToastNotificationsView.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Notifications/ToastNotificationsView.cpp @@ -63,6 +63,12 @@ namespace AzToolsFramework ToastId ToastNotificationsView::ShowToastNotification(const AzQtComponents::ToastConfiguration& toastConfiguration) { + // reject duplicate messages + if (m_rejectDuplicates && DuplicateNotificationInQueue(toastConfiguration)) + { + return ToastId(); + } + ToastId toastId = CreateToastNotification(toastConfiguration); m_queuedNotifications.emplace_back(toastId); @@ -70,10 +76,28 @@ namespace AzToolsFramework { DisplayQueuedNotification(); } + else if (m_queuedNotifications.size() >= m_maxQueuedNotifications) + { + // hiding the active toast will cause the next toast to be displayed + HideToastNotification(m_activeNotification); + } return toastId; } + bool ToastNotificationsView::DuplicateNotificationInQueue(const AzQtComponents::ToastConfiguration& toastConfiguration) + { + for (auto iter : m_notifications) + { + if (iter.second && iter.second->IsDuplicate(toastConfiguration)) + { + return true; + } + } + + return false; + } + ToastId ToastNotificationsView::ShowToastAtCursor(const AzQtComponents::ToastConfiguration& toastConfiguration) { ToastId toastId = CreateToastNotification(toastConfiguration); @@ -105,7 +129,7 @@ namespace AzToolsFramework ToastId ToastNotificationsView::CreateToastNotification(const AzQtComponents::ToastConfiguration& toastConfiguration) { - AzQtComponents::ToastNotification* notification = aznew AzQtComponents::ToastNotification(parentWidget(), toastConfiguration); + AzQtComponents::ToastNotification* notification = new AzQtComponents::ToastNotification(this, toastConfiguration); ToastId toastId = AZ::Entity::MakeId(); m_notifications[toastId] = notification; @@ -187,4 +211,14 @@ namespace AzToolsFramework { m_anchorPoint = anchorPoint; } + + void ToastNotificationsView::SetMaxQueuedNotifications(AZ::u32 maxQueuedNotifications) + { + m_maxQueuedNotifications = maxQueuedNotifications; + } + + void ToastNotificationsView::SetRejectDuplicates(bool rejectDuplicates) + { + m_rejectDuplicates = rejectDuplicates; + } } diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Notifications/ToastNotificationsView.h b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Notifications/ToastNotificationsView.h index e13f129467..c64ce00f4c 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Notifications/ToastNotificationsView.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Notifications/ToastNotificationsView.h @@ -52,10 +52,13 @@ namespace AzToolsFramework void SetOffset(const QPoint& offset); void SetAnchorPoint(const QPointF& anchorPoint); + void SetMaxQueuedNotifications(AZ::u32 maxQueuedNotifications); + void SetRejectDuplicates(bool rejectDuplicates); private: ToastId CreateToastNotification(const AzQtComponents::ToastConfiguration& toastConfiguration); void DisplayQueuedNotification(); + bool DuplicateNotificationInQueue(const AzQtComponents::ToastConfiguration& toastConfiguration); QPoint GetGlobalPoint(); ToastId m_activeNotification; @@ -64,5 +67,7 @@ namespace AzToolsFramework QPoint m_offset = QPoint(10, 10); QPointF m_anchorPoint = QPointF(1, 0); + AZ::u32 m_maxQueuedNotifications = 5; + bool m_rejectDuplicates = true; }; } // AzToolsFramework diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutliner.qss b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutliner.qss index b3c5882334..93460f8816 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutliner.qss +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutliner.qss @@ -10,7 +10,6 @@ AzToolsFramework--EntityOutlinerWidget #m_display_options { qproperty-icon: url(:/stylesheet/img/UI20/menu-centered.svg); qproperty-iconSize: 16px 16px; - qproperty-flat: true; } AzToolsFramework--EntityOutlinerWidget QTreeView diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerListModel.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerListModel.cpp index a5f1e29942..13ec27c1b8 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerListModel.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerListModel.cpp @@ -43,6 +43,7 @@ #include #include #include +#include #include #include #include @@ -764,10 +765,21 @@ namespace AzToolsFramework return canHandleData; } - bool EntityOutlinerListModel::CanDropMimeDataAssets(const QMimeData* data, Qt::DropAction /*action*/, int /*row*/, int /*column*/, const QModelIndex& /*parent*/) const + bool EntityOutlinerListModel::CanDropMimeDataAssets( + const QMimeData* data, + [[maybe_unused]] Qt::DropAction action, + [[maybe_unused]] int row, + [[maybe_unused]] int column, + const QModelIndex& parent) const { - using namespace AzToolsFramework; - + // Disable dropping assets on closed container entities. + AZ::EntityId parentId = GetEntityFromIndex(parent); + if (auto containerEntityInterface = AZ::Interface::Get(); + !containerEntityInterface->IsContainerOpen(parentId)) + { + return false; + } + if (data->hasFormat(AssetBrowser::AssetBrowserEntry::GetMimeType())) { return DecodeAssetMimeData(data); @@ -788,8 +800,15 @@ namespace AzToolsFramework return false; } + // If the parent entity is a closed container, bail. + if (auto containerEntityInterface = AZ::Interface::Get(); + !containerEntityInterface->IsContainerOpen(assignParentId)) + { + return false; + } + // Source Files - if (sourceFiles.size() > 0) + if (!sourceFiles.empty()) { // Get position (center of viewport). If no viewport is available, (0,0,0) will be used. AZ::Vector3 viewportCenterPosition = AZ::Vector3::CreateZero(); @@ -943,13 +962,15 @@ namespace AzToolsFramework { return false; } - + + const int count = rowCount(parent); AZ::EntityId newParentId = GetEntityFromIndex(parent); - AZ::EntityId beforeEntityId = GetEntityFromIndex(index(row, 0, parent)); + AZ::EntityId beforeEntityId = (row >= 0 && row < count) ? GetEntityFromIndex(index(row, 0, parent)) : AZ::EntityId(); EntityIdList topLevelEntityIds; topLevelEntityIds.reserve(entityIdListContainer.m_entityIds.size()); ToolsApplicationRequestBus::Broadcast(&ToolsApplicationRequestBus::Events::FindTopLevelEntityIdsInactive, entityIdListContainer.m_entityIds, topLevelEntityIds); - if (!ReparentEntities(newParentId, topLevelEntityIds, beforeEntityId)) + const auto appendActionForInvalid = newParentId.IsValid() && (row >= count) ? AppendEnd : AppendBeginning; + if (!ReparentEntities(newParentId, topLevelEntityIds, beforeEntityId, appendActionForInvalid)) { return false; } @@ -971,6 +992,12 @@ namespace AzToolsFramework return false; } + // If the new parent is a closed container, bail. + if (auto containerEntityInterface = AZ::Interface::Get(); !containerEntityInterface->IsContainerOpen(newParentId)) + { + return false; + } + // Ignore entities not owned by the editor context. It is assumed that all entities belong // to the same context since multiple selection doesn't span across views. for (const AZ::EntityId& entityId : selectedEntityIds) @@ -1046,7 +1073,7 @@ namespace AzToolsFramework return true; } - bool EntityOutlinerListModel::ReparentEntities(const AZ::EntityId& newParentId, const EntityIdList &selectedEntityIds, const AZ::EntityId& beforeEntityId) + bool EntityOutlinerListModel::ReparentEntities(const AZ::EntityId& newParentId, const EntityIdList &selectedEntityIds, const AZ::EntityId& beforeEntityId, ReparentForInvalid forInvalid) { AZ_PROFILE_FUNCTION(AzToolsFramework); if (!CanReparentEntities(newParentId, selectedEntityIds)) @@ -1056,10 +1083,18 @@ namespace AzToolsFramework m_isFilterDirty = true; - ScopedUndoBatch undo("Reparent Entities"); //capture child entity order before re-parent operation, which will automatically add order info if not present EntityOrderArray entityOrderArray = GetEntityChildOrder(newParentId); + //search for the insertion entity in the order array + const auto beforeEntityItr = AZStd::find(entityOrderArray.begin(), entityOrderArray.end(), beforeEntityId); + const bool hasInvalidIndex = beforeEntityItr == entityOrderArray.end(); + if (hasInvalidIndex && forInvalid == None) + { + return false; + } + + ScopedUndoBatch undo("Reparent Entities"); // The new parent is dirty due to sort change(s) undo.MarkEntityDirty(GetEntityIdForSortInfo(newParentId)); @@ -1088,9 +1123,7 @@ namespace AzToolsFramework } } - //search for the insertion entity in the order array - auto beforeEntityItr = AZStd::find(entityOrderArray.begin(), entityOrderArray.end(), beforeEntityId); - + //replace order info matching selection with bad values rather than remove to preserve layout for (auto& id : entityOrderArray) { @@ -1100,17 +1133,25 @@ namespace AzToolsFramework } } - if (newParentId.IsValid()) + //if adding to a valid parent entity, insert at the found entity location or at the head/tail depending on placeAtTail flag + if (hasInvalidIndex) { - //if adding to a valid parent entity, insert at the found entity location or at the head of the container - auto insertItr = beforeEntityItr != entityOrderArray.end() ? beforeEntityItr : entityOrderArray.begin(); - entityOrderArray.insert(insertItr, processedEntityIds.begin(), processedEntityIds.end()); - } - else + switch(forInvalid) + { + case AppendEnd: + entityOrderArray.insert(entityOrderArray.end(), processedEntityIds.begin(), processedEntityIds.end()); + break; + case AppendBeginning: + entityOrderArray.insert(entityOrderArray.begin(), processedEntityIds.begin(), processedEntityIds.end()); + break; + default: + AZ_Assert(false, "Unexpected type for ReparentForInvalid"); + break; + } + } + else { - //if adding to an invalid parent entity (the root), insert at the found entity location or at the tail of the container - auto insertItr = beforeEntityItr != entityOrderArray.end() ? beforeEntityItr : entityOrderArray.end(); - entityOrderArray.insert(insertItr, processedEntityIds.begin(), processedEntityIds.end()); + entityOrderArray.insert(beforeEntityItr, processedEntityIds.begin(), processedEntityIds.end()); } //remove placeholder entity ids diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerListModel.hxx b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerListModel.hxx index 8176867038..0a46ee4850 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerListModel.hxx +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerListModel.hxx @@ -72,6 +72,13 @@ namespace AzToolsFramework ColumnCount //!< Total number of columns }; + enum ReparentForInvalid + { + None, //!< For an invalid location the entity does not change location + AppendEnd, //!< Append Item to end of target parent list + AppendBeginning, //!< Append Item to the beginning of target parent list + }; + // Note: the ColumnSortIndex column isn't shown, hence the -1 and the need for a separate counter. // A wrong column count number causes refresh issues and hover mismatch on model update. static const int VisibleColumnCount = ColumnCount - 1; @@ -162,7 +169,7 @@ namespace AzToolsFramework // Buffer Processing Slots - These are called using single-shot events when the buffers begin to fill. bool CanReparentEntities(const AZ::EntityId& newParentId, const EntityIdList& selectedEntityIds) const; - bool ReparentEntities(const AZ::EntityId& newParentId, const EntityIdList& selectedEntityIds, const AZ::EntityId& beforeEntityId = AZ::EntityId()); + bool ReparentEntities(const AZ::EntityId& newParentId, const EntityIdList& selectedEntityIds, const AZ::EntityId& beforeEntityId = AZ::EntityId(), ReparentForInvalid forInvalid = None); //! Use the current filter setting and re-evaluate the filter. void InvalidateFilter(); diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerTreeView.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerTreeView.cpp index d94ced392c..0609f8113d 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerTreeView.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerTreeView.cpp @@ -72,7 +72,7 @@ namespace AzToolsFramework void EntityOutlinerTreeView::leaveEvent([[maybe_unused]] QEvent* event) { - m_mousePosition = QPoint(); + m_mousePosition = QPoint(-1, -1); m_currentHoveredIndex = QModelIndex(); update(); } @@ -200,7 +200,7 @@ namespace AzToolsFramework const bool isEnabled = (this->model()->flags(index) & Qt::ItemIsEnabled); const bool isSelected = selectionModel()->isSelected(index); - const bool isHovered = (index == indexAt(m_mousePosition)) && isEnabled; + const bool isHovered = (index == indexAt(m_mousePosition).siblingAtColumn(0)) && isEnabled; // Paint the branch Selection/Hover Rect PaintBranchSelectionHoverRect(painter, rect, isSelected, isHovered); diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerWidget.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerWidget.cpp index 3e97f967fc..d6e9cac754 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerWidget.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerWidget.cpp @@ -153,6 +153,9 @@ namespace AzToolsFramework { initEntityOutlinerWidgetResources(); + m_editorEntityUiInterface = AZ::Interface::Get(); + AZ_Assert(m_editorEntityUiInterface != nullptr, "EntityOutlinerWidget requires a EditorEntityUiInterface instance on Initialize."); + m_gui = new Ui::EntityOutlinerWidgetUI(); m_gui->setupUi(this); @@ -282,12 +285,6 @@ namespace AzToolsFramework m_listModel->Initialize(); - m_editorEntityUiInterface = AZ::Interface::Get(); - - AZ_Assert( - m_editorEntityUiInterface != nullptr, - "EntityOutlinerWidget requires a EditorEntityUiInterface instance on Initialize."); - EditorPickModeNotificationBus::Handler::BusConnect(GetEntityContextId()); EntityHighlightMessages::Bus::Handler::BusConnect(); EntityOutlinerModelNotificationBus::Handler::BusConnect(); diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/ComponentEditor.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/ComponentEditor.cpp index 91d2359ca6..48dec6f225 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/ComponentEditor.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/ComponentEditor.cpp @@ -583,7 +583,7 @@ namespace AzToolsFramework } AZStd::string iconPath; - EBUS_EVENT_RESULT(iconPath, AzToolsFramework::EditorRequests::Bus, GetComponentEditorIcon, componentType, const_cast(&componentInstance)); + AzToolsFramework::EditorRequestBus::BroadcastResult(iconPath, &AzToolsFramework::EditorRequests::GetComponentEditorIcon, componentType, const_cast(&componentInstance)); GetHeader()->SetIcon(QIcon(iconPath.c_str())); bool isExpanded = true; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyAssetCtrl.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyAssetCtrl.cpp index 7f3b9e61fd..24b2c7466e 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyAssetCtrl.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyAssetCtrl.cpp @@ -1387,7 +1387,10 @@ namespace AzToolsFramework QDataStream stream(&pixmapBytes, QIODevice::ReadOnly); QPixmap pixmap; stream >> pixmap; - GUI->SetBrowseButtonIcon(pixmap); + if (!pixmap.isNull()) + { + GUI->SetBrowseButtonIcon(pixmap); + } } } } @@ -1417,6 +1420,8 @@ namespace AzToolsFramework } else if (attrib == AZ_CRC_CE("ThumbnailIcon")) { + GUI->SetCustomThumbnailEnabled(false); + AZStd::string iconPath; if (attrValue->Read(iconPath) && !iconPath.empty()) { @@ -1434,8 +1439,11 @@ namespace AzToolsFramework QDataStream stream(&pixmapBytes, QIODevice::ReadOnly); QPixmap pixmap; stream >> pixmap; - GUI->SetCustomThumbnailEnabled(true); - GUI->SetCustomThumbnailPixmap(pixmap); + if (!pixmap.isNull()) + { + GUI->SetCustomThumbnailEnabled(true); + GUI->SetCustomThumbnailPixmap(pixmap); + } } } } diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/ThumbnailPropertyCtrl.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/ThumbnailPropertyCtrl.cpp index d8ddee6b76..0bbb898196 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/ThumbnailPropertyCtrl.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/ThumbnailPropertyCtrl.cpp @@ -67,16 +67,9 @@ namespace AzToolsFramework void ThumbnailPropertyCtrl::SetThumbnailKey(Thumbnailer::SharedThumbnailKey key, const char* contextName) { - if (m_customThumbnailEnabled) - { - ClearThumbnail(); - } - else - { - m_key = key; - m_thumbnail->SetThumbnailKey(m_key, contextName); - m_thumbnailEnlarged->SetThumbnailKey(m_key, contextName); - } + m_key = key; + m_thumbnail->SetThumbnailKey(m_key, contextName); + m_thumbnailEnlarged->SetThumbnailKey(m_key, contextName); UpdateVisibility(); } diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/InvalidClicks.h b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/InvalidClicks.h index 55a6d614ea..fe54b5379b 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/InvalidClicks.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/InvalidClicks.h @@ -80,8 +80,9 @@ namespace AzToolsFramework private: AZStd::string m_message; //!< Message to display for fading text. - float m_opacity = 1.0f; //!< The opacity of the invalid click message. - AzFramework::ScreenPoint m_invalidClickPosition; //!< The position to display the invalid click message. + float m_opacity = 0.0f; //!< The opacity of the invalid click message. + //! The position to display the invalid click message. + AzFramework::ScreenPoint m_invalidClickPosition = AzFramework::ScreenPoint(0, 0); }; //! Interface to begin invalid click feedback (will run all added InvalidClick behaviors). diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportUi/ViewportUiDisplay.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportUi/ViewportUiDisplay.cpp index 43e2a6793f..0f79e3535d 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportUi/ViewportUiDisplay.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportUi/ViewportUiDisplay.cpp @@ -301,6 +301,7 @@ namespace AzToolsFramework::ViewportUi::Internal HighlightBorderSize + ViewportUiOverlayMargin, HighlightBorderSize + ViewportUiOverlayMargin); m_componentModeBorderText.setVisible(true); m_componentModeBorderText.setText(borderTitle.c_str()); + UpdateUiOverlayGeometry(); } void ViewportUiDisplay::RemoveViewportBorder() @@ -427,8 +428,8 @@ namespace AzToolsFramework::ViewportUi::Internal void ViewportUiDisplay::PositionUiOverlayOverRenderViewport() { QPoint offset = m_renderOverlay->mapToGlobal(QPoint()); - m_uiMainWindow.move(offset); - m_uiOverlay.setFixedSize(m_renderOverlay->width(), m_renderOverlay->height()); + m_uiMainWindow.setGeometry(offset.x(), offset.y(), m_renderOverlay->width(), m_renderOverlay->height()); + m_uiOverlay.setGeometry(m_uiMainWindow.rect()); UpdateUiOverlayGeometry(); } diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/aztoolsframework_files.cmake b/Code/Framework/AzToolsFramework/AzToolsFramework/aztoolsframework_files.cmake index b57eedcd81..286ef97418 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/aztoolsframework_files.cmake +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/aztoolsframework_files.cmake @@ -770,6 +770,9 @@ set(FILES PythonTerminal/ScriptTermDialog.ui Input/QtEventToAzInputManager.h Input/QtEventToAzInputManager.cpp + Script/LuaSymbolsReporterBus.h + Script/LuaSymbolsReporterSystemComponent.h + Script/LuaSymbolsReporterSystemComponent.cpp ) # Prevent the following files from being grouped in UNITY builds diff --git a/Code/Framework/AzToolsFramework/Tests/BoundsTestComponent.cpp b/Code/Framework/AzToolsFramework/Tests/BoundsTestComponent.cpp index 11a077f53c..1aa7b39593 100644 --- a/Code/Framework/AzToolsFramework/Tests/BoundsTestComponent.cpp +++ b/Code/Framework/AzToolsFramework/Tests/BoundsTestComponent.cpp @@ -28,9 +28,12 @@ namespace UnitTest return true; } - void BoundsTestComponent::Reflect([[maybe_unused]] AZ::ReflectContext* context) + void BoundsTestComponent::Reflect(AZ::ReflectContext* context) { - // noop + if (auto serializeContext = azrtti_cast(context)) + { + serializeContext->Class()->Version(1); + } } void BoundsTestComponent::Activate() diff --git a/Code/Framework/AzToolsFramework/Tests/BoundsTestComponent.h b/Code/Framework/AzToolsFramework/Tests/BoundsTestComponent.h index 1aabcfcd64..036f9c8798 100644 --- a/Code/Framework/AzToolsFramework/Tests/BoundsTestComponent.h +++ b/Code/Framework/AzToolsFramework/Tests/BoundsTestComponent.h @@ -42,5 +42,4 @@ namespace UnitTest AZ::Aabb GetWorldBounds() override; AZ::Aabb GetLocalBounds() override; }; - } // namespace UnitTest diff --git a/Code/Framework/AzToolsFramework/Tests/Prefab/Benchmark/Spawnable/SpawnAllEntitiesBenchmarks.cpp b/Code/Framework/AzToolsFramework/Tests/Prefab/Benchmark/Spawnable/SpawnAllEntitiesBenchmarks.cpp new file mode 100644 index 0000000000..ecbbe10c39 --- /dev/null +++ b/Code/Framework/AzToolsFramework/Tests/Prefab/Benchmark/Spawnable/SpawnAllEntitiesBenchmarks.cpp @@ -0,0 +1,130 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ +#if defined(HAVE_BENCHMARK) + +#include +#include +#include + +namespace Benchmark +{ + using BM_SpawnAllEntities = BM_Spawnable; + + BENCHMARK_DEFINE_F(BM_SpawnAllEntities, SingleEntitySpawnable_SpawnCallVariable)(::benchmark::State& state) + { + const uint64_t spawnAllEntitiesCallCount = aznumeric_cast(state.range()); + const uint64_t entityCountInSourcePrefab = 1; + + SetUpSpawnableAsset(entityCountInSourcePrefab); + + for (auto _ : state) + { + state.PauseTiming(); + m_spawnTicket = new AzFramework::EntitySpawnTicket(m_spawnableAsset); + state.ResumeTiming(); + + for (uint64_t spwanableCounter = 0; spwanableCounter < spawnAllEntitiesCallCount; spwanableCounter++) + { + AzFramework::SpawnableEntitiesInterface::Get()->SpawnAllEntities(*m_spawnTicket); + } + + m_rootSpawnableInterface->ProcessSpawnableQueue(); + + // Destroy the ticket so that this queues a request to delete all the entities spawned with this ticket. + state.PauseTiming(); + delete m_spawnTicket; + m_spawnTicket = nullptr; + + // This will process the request to delete all entities spawned with the ticket + m_rootSpawnableInterface->ProcessSpawnableQueue(); + state.ResumeTiming(); + } + + state.SetComplexityN(spawnAllEntitiesCallCount); + } + BENCHMARK_REGISTER_F(BM_SpawnAllEntities, SingleEntitySpawnable_SpawnCallVariable) + ->RangeMultiplier(10) + ->Range(100, 10000) + ->Unit(benchmark::kMillisecond) + ->Complexity(); + + BENCHMARK_DEFINE_F(BM_SpawnAllEntities, SingleSpawnCall_EntityCountVariable)(::benchmark::State& state) + { + const uint64_t entityCountInSpawnable = aznumeric_cast(state.range()); + + SetUpSpawnableAsset(entityCountInSpawnable); + + for (auto _ : state) + { + state.PauseTiming(); + m_spawnTicket = new AzFramework::EntitySpawnTicket(m_spawnableAsset); + state.ResumeTiming(); + + AzFramework::SpawnableEntitiesInterface::Get()->SpawnAllEntities(*m_spawnTicket); + m_rootSpawnableInterface->ProcessSpawnableQueue(); + + // Destroy the ticket so that this queues a request to delete all the entities spawned with this ticket. + state.PauseTiming(); + delete m_spawnTicket; + m_spawnTicket = nullptr; + + // This will process the request to delete all entities spawned with the ticket + m_rootSpawnableInterface->ProcessSpawnableQueue(); + state.ResumeTiming(); + } + + state.SetComplexityN(entityCountInSpawnable); + } + BENCHMARK_REGISTER_F(BM_SpawnAllEntities, SingleSpawnCall_EntityCountVariable) + ->RangeMultiplier(10) + ->Range(100, 10000) + ->Unit(benchmark::kMillisecond) + ->Complexity(); + + BENCHMARK_DEFINE_F(BM_SpawnAllEntities, EntityCountVariable_SpawnCallCountVariable)(::benchmark::State& state) + { + const uint64_t entityCountInSpawnable = aznumeric_cast(state.range(0)); + const uint64_t spawnCallCount = aznumeric_cast(state.range(1)); + + SetUpSpawnableAsset(entityCountInSpawnable); + + for (auto _ : state) + { + state.PauseTiming(); + m_spawnTicket = new AzFramework::EntitySpawnTicket(m_spawnableAsset); + state.ResumeTiming(); + + for (uint64_t spawnCallCounter = 0; spawnCallCounter < spawnCallCount; spawnCallCounter++) + { + AzFramework::SpawnableEntitiesInterface::Get()->SpawnAllEntities(*m_spawnTicket); + } + + m_rootSpawnableInterface->ProcessSpawnableQueue(); + + state.PauseTiming(); + delete m_spawnTicket; + m_spawnTicket = nullptr; + m_rootSpawnableInterface->ProcessSpawnableQueue(); + state.ResumeTiming(); + } + + state.SetComplexityN(entityCountInSpawnable * spawnCallCount); + } + // Provide ranges here to compare times for spawning the same number of entities by altering entityCountInSpawnable and spawnCallCount. + BENCHMARK_REGISTER_F(BM_SpawnAllEntities, EntityCountVariable_SpawnCallCountVariable) + ->Args({ 10, 100 }) + ->Args({ 100, 10 }) + ->Args({ 10, 1000 }) + ->Args({ 1000, 10 }) + ->Args({ 100, 1000 }) + ->Args({ 1000, 100 }) + ->Unit(benchmark::kMillisecond) + ->Complexity(); +} // namespace Benchmark + +#endif diff --git a/Code/Framework/AzToolsFramework/Tests/Prefab/Benchmark/Spawnable/SpawnableBenchmarkFixture.cpp b/Code/Framework/AzToolsFramework/Tests/Prefab/Benchmark/Spawnable/SpawnableBenchmarkFixture.cpp new file mode 100644 index 0000000000..fc90d96422 --- /dev/null +++ b/Code/Framework/AzToolsFramework/Tests/Prefab/Benchmark/Spawnable/SpawnableBenchmarkFixture.cpp @@ -0,0 +1,69 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ +#if defined(HAVE_BENCHMARK) + +#include +#include + +namespace Benchmark +{ + void BM_Spawnable::SetUp(const benchmark::State& state) + { + SetUpHelper(state); + } + + void BM_Spawnable::SetUp(benchmark::State& state) + { + SetUpHelper(state); + } + + void BM_Spawnable::SetUpHelper(const benchmark::State& state) + { + BM_Prefab::SetUp(state); + m_rootSpawnableInterface = AzFramework::RootSpawnableInterface::Get(); + AZ_Assert(m_rootSpawnableInterface != nullptr, "RootSpawnableInterface isn't found."); + } + + void BM_Spawnable::TearDown(const benchmark::State& state) + { + TearDownHelper(state); + } + + void BM_Spawnable::TearDown(benchmark::State& state) + { + TearDownHelper(state); + } + + void BM_Spawnable::TearDownHelper(const benchmark::State& state) + { + m_spawnableAsset.Release(); + BM_Prefab::TearDown(state); + } + + void BM_Spawnable::SetUpSpawnableAsset(uint64_t entityCount) + { + AZStd::vector entities; + entities.reserve(entityCount); + + for (uint64_t i = 0; i < entityCount; i++) + { + entities.emplace_back(CreateEntity("Entity")); + } + + AZStd::unique_ptr instance = m_prefabSystemComponent->CreatePrefab(AZStd::move(entities), {}, m_pathString); + const PrefabDom& prefabDom = m_prefabSystemComponent->FindTemplateDom(instance->GetTemplateId()); + + // Lifecycle of spawnable is managed by the asset that's created using it. + AzFramework::Spawnable* spawnable = new AzFramework::Spawnable( + AZ::Data::AssetId::CreateString("{612F2AB1-30DF-44BB-AFBE-17A85199F09E}:0"), AZ::Data::AssetData::AssetStatus::Ready); + AzToolsFramework::Prefab::SpawnableUtils::CreateSpawnable(*spawnable, prefabDom); + m_spawnableAsset = AZ::Data::Asset(spawnable, AZ::Data::AssetLoadBehavior::Default); + } +} // namespace Benchmark + +#endif diff --git a/Code/Framework/AzToolsFramework/Tests/Prefab/Benchmark/Spawnable/SpawnableBenchmarkFixture.h b/Code/Framework/AzToolsFramework/Tests/Prefab/Benchmark/Spawnable/SpawnableBenchmarkFixture.h new file mode 100644 index 0000000000..6c7fbf6263 --- /dev/null +++ b/Code/Framework/AzToolsFramework/Tests/Prefab/Benchmark/Spawnable/SpawnableBenchmarkFixture.h @@ -0,0 +1,44 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#if defined(HAVE_BENCHMARK) + +#pragma once + +#include +#include + +namespace AzFramework +{ + class EntitySpawnTicket; + class RootSpawnableDefinition; +} + +namespace Benchmark +{ + class BM_Spawnable + : public Benchmark::BM_Prefab + { + protected: + void SetUp(const benchmark::State& state) override; + void SetUp(benchmark::State& state) override; + void SetUpHelper(const benchmark::State& state); + + void TearDown(const benchmark::State& state) override; + void TearDown(benchmark::State& state) override; + void TearDownHelper(const benchmark::State& state); + + void SetUpSpawnableAsset(uint64_t entityCount); + + AZ::Data::Asset m_spawnableAsset; + AzFramework::EntitySpawnTicket* m_spawnTicket; + AzFramework::RootSpawnableDefinition* m_rootSpawnableInterface; + }; +} // namespace Benchmark + +#endif diff --git a/Code/Framework/AzToolsFramework/Tests/Viewport/ViewportEditorModeTests.cpp b/Code/Framework/AzToolsFramework/Tests/Viewport/ViewportEditorModeTests.cpp index db6a0b8571..85b65f3edf 100644 --- a/Code/Framework/AzToolsFramework/Tests/Viewport/ViewportEditorModeTests.cpp +++ b/Code/Framework/AzToolsFramework/Tests/Viewport/ViewportEditorModeTests.cpp @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -187,10 +188,13 @@ namespace UnitTest ASSERT_NE(m_viewportEditorModeTracker, nullptr); m_viewportEditorModes = m_viewportEditorModeTracker->GetViewportEditorModes({AzToolsFramework::GetEntityContextId()}); ASSERT_NE(m_viewportEditorModes, nullptr); + m_focusModeInterface = AZ::Interface::Get(); + ASSERT_NE(m_focusModeInterface, nullptr); } ViewportEditorModeTrackerInterface* m_viewportEditorModeTracker = nullptr; const ViewportEditorModesInterface* m_viewportEditorModes = nullptr; + AzToolsFramework::FocusModeInterface* m_focusModeInterface = nullptr; }; TEST_F(ViewportEditorModesTestsFixture, NumberOfEditorModesIsEqualTo4) @@ -522,32 +526,48 @@ namespace UnitTest } TEST_F( - ViewportEditorModeTrackerIntegrationTestFixture, EnteringComponentModeAfterInitialStateHasViewportEditorModesDefaultAndComponentModeActive) + ViewportEditorModeTrackerIntegrationTestFixture, + EnteringComponentModeAfterInitialStateHasViewportEditorModesDefaultAndComponentModeActive) { // When component mode is entered AzToolsFramework::ComponentModeFramework::ComponentModeSystemRequestBus::Broadcast( &AzToolsFramework::ComponentModeFramework::ComponentModeSystemRequests::BeginComponentMode, AZStd::vector{}); - bool inComponentMode = false; - AzToolsFramework::ComponentModeFramework::ComponentModeSystemRequestBus::BroadcastResult( - inComponentMode, &AzToolsFramework::ComponentModeFramework::ComponentModeSystemRequests::InComponentMode); - // Expect to be in component mode - EXPECT_TRUE(inComponentMode); + EXPECT_TRUE(AzToolsFramework::ComponentModeFramework::InComponentMode()); // Expect the default and component viewport editor modes to be active EXPECT_TRUE(m_viewportEditorModes->IsModeActive(ViewportEditorMode::Default)); EXPECT_TRUE(m_viewportEditorModes->IsModeActive(ViewportEditorMode::Component)); - - // Do not expect the pick and focus viewport editor modes to be active EXPECT_FALSE(m_viewportEditorModes->IsModeActive(ViewportEditorMode::Pick)); EXPECT_FALSE(m_viewportEditorModes->IsModeActive(ViewportEditorMode::Focus)); } TEST_F( ViewportEditorModeTrackerIntegrationTestFixture, - EnteringEditorPickEntitySelectionAfterInitialStateHasOnlyViewportEditorModePickModeActive) + ExitingComponentModeAfterEnteringFrominitialStateHasViewportEditorModesDefaultActive) + { + // When component mode is entered and exited + AzToolsFramework::ComponentModeFramework::ComponentModeSystemRequestBus::Broadcast( + &AzToolsFramework::ComponentModeFramework::ComponentModeSystemRequests::BeginComponentMode, + AZStd::vector{}); + + EXPECT_TRUE(AzToolsFramework::ComponentModeFramework::InComponentMode()); + + AzToolsFramework::ComponentModeFramework::ComponentModeSystemRequestBus::Broadcast( + &AzToolsFramework::ComponentModeFramework::ComponentModeSystemRequests::EndComponentMode); + + // Expect to not be in component mode + EXPECT_FALSE(AzToolsFramework::ComponentModeFramework::InComponentMode()); + + // Expect only the default viewport editor mode to be active + ExpectOnlyModeActive(*m_viewportEditorModes, ViewportEditorMode::Default); + } + + TEST_F( + ViewportEditorModeTrackerIntegrationTestFixture, + EnteringEditorPickEntitySelectionAfterInitialStateHasOnlyViewportEditorModePickActive) { // When entering pick mode using AzToolsFramework::EditorInteractionSystemViewportSelectionRequestBus; @@ -563,6 +583,96 @@ namespace UnitTest ExpectOnlyModeActive(*m_viewportEditorModes, ViewportEditorMode::Pick); } - // FocusMode integration tests will follow (LYN-6995) + TEST_F( + ViewportEditorModeTrackerIntegrationTestFixture, + EnteringEditorDefaultEntitySelectionFromEditorPickEntitySelectionHasOnlyViewportEditorModeDefaultActive) + { + // When pick mode is entered and exited + using AzToolsFramework::EditorInteractionSystemViewportSelectionRequestBus; + EditorInteractionSystemViewportSelectionRequestBus::Event( + AzToolsFramework::GetEntityContextId(), &EditorInteractionSystemViewportSelectionRequestBus::Events::SetHandler, + [](const AzToolsFramework::EditorVisibleEntityDataCache* entityDataCache, + [[maybe_unused]] AzToolsFramework::ViewportEditorModeTrackerInterface* viewportEditorModeTracker) + { + return AZStd::make_unique(entityDataCache, viewportEditorModeTracker); + }); + EditorInteractionSystemViewportSelectionRequestBus::Event( + AzToolsFramework::GetEntityContextId(), &EditorInteractionSystemViewportSelectionRequestBus::Events::SetHandler, + [](const AzToolsFramework::EditorVisibleEntityDataCache* entityDataCache, + [[maybe_unused]] AzToolsFramework::ViewportEditorModeTrackerInterface* viewportEditorModeTracker) + { + return AZStd::make_unique(entityDataCache, viewportEditorModeTracker); + }); + + // Expect only the default viewport editor mode to be active + ExpectOnlyModeActive(*m_viewportEditorModes, ViewportEditorMode::Default); + } + + TEST_F(ViewportEditorModeTrackerIntegrationTestFixture, EnteringFocusModeAfterInitialStateHasViewportEditorModeDefaultAndPickActive) + { + // When entering focus mode + m_focusModeInterface->SetFocusRoot(AZ::EntityId{ 1 }); + + // Expect the default and focus viewport editor modes to be active + EXPECT_TRUE(m_viewportEditorModes->IsModeActive(ViewportEditorMode::Default)); + EXPECT_TRUE(m_viewportEditorModes->IsModeActive(ViewportEditorMode::Focus)); + EXPECT_FALSE(m_viewportEditorModes->IsModeActive(ViewportEditorMode::Pick)); + EXPECT_FALSE(m_viewportEditorModes->IsModeActive(ViewportEditorMode::Component)); + } + + TEST_F( + ViewportEditorModeTrackerIntegrationTestFixture, + ExitingFocusModeAfterEnteringFromInitialStateHasOnlyViewportEditorModeDefaultActive) + { + // When entering and leaving focus mode + m_focusModeInterface->SetFocusRoot(AZ::EntityId{ 1 }); + m_focusModeInterface->SetFocusRoot(AZ::EntityId()); + + // Expect only the default mode to be active + ExpectOnlyModeActive(*m_viewportEditorModes, ViewportEditorMode::Default); + } + + TEST_F(ViewportEditorModeTrackerIntegrationTestFixture, EnteringComponentModeFromFocusModeStateHasViewportEditorModeDefaultAndFocusAndComponentActive) + { + // When entering component mode from focus mode + m_focusModeInterface->SetFocusRoot(AZ::EntityId{ 1 }); + AzToolsFramework::ComponentModeFramework::ComponentModeSystemRequestBus::Broadcast( + &AzToolsFramework::ComponentModeFramework::ComponentModeSystemRequests::BeginComponentMode, + AZStd::vector{}); + + // Expect to be in component mode + EXPECT_TRUE(AzToolsFramework::ComponentModeFramework::InComponentMode()); + + // Expect the default, focus and component viewport editor modes to be active + EXPECT_TRUE(m_viewportEditorModes->IsModeActive(ViewportEditorMode::Default)); + EXPECT_TRUE(m_viewportEditorModes->IsModeActive(ViewportEditorMode::Focus)); + EXPECT_TRUE(m_viewportEditorModes->IsModeActive(ViewportEditorMode::Component)); + EXPECT_FALSE(m_viewportEditorModes->IsModeActive(ViewportEditorMode::Pick)); + } + + TEST_F( + ViewportEditorModeTrackerIntegrationTestFixture, + ExitingComponentModeAfterEnteringFromFocusModeHasViewportEditorModeDefaultAndFocusActive) + { + // When entering and leaving component mode from focus mode + m_focusModeInterface->SetFocusRoot(AZ::EntityId{ 1 }); + AzToolsFramework::ComponentModeFramework::ComponentModeSystemRequestBus::Broadcast( + &AzToolsFramework::ComponentModeFramework::ComponentModeSystemRequests::BeginComponentMode, + AZStd::vector{}); + + EXPECT_TRUE(AzToolsFramework::ComponentModeFramework::InComponentMode()); + + AzToolsFramework::ComponentModeFramework::ComponentModeSystemRequestBus::Broadcast( + &AzToolsFramework::ComponentModeFramework::ComponentModeSystemRequests::EndComponentMode); + + // Expect to not be in component mode + EXPECT_FALSE(AzToolsFramework::ComponentModeFramework::InComponentMode()); + + // Expect the default and focus viewport editor modes to be active + EXPECT_TRUE(m_viewportEditorModes->IsModeActive(ViewportEditorMode::Default)); + EXPECT_TRUE(m_viewportEditorModes->IsModeActive(ViewportEditorMode::Focus)); + EXPECT_FALSE(m_viewportEditorModes->IsModeActive(ViewportEditorMode::Component)); + EXPECT_FALSE(m_viewportEditorModes->IsModeActive(ViewportEditorMode::Pick)); + } } // namespace UnitTest diff --git a/Code/Framework/AzToolsFramework/Tests/aztoolsframeworktests_files.cmake b/Code/Framework/AzToolsFramework/Tests/aztoolsframeworktests_files.cmake index 008c09188b..e3742041b4 100644 --- a/Code/Framework/AzToolsFramework/Tests/aztoolsframeworktests_files.cmake +++ b/Code/Framework/AzToolsFramework/Tests/aztoolsframeworktests_files.cmake @@ -60,6 +60,9 @@ set(FILES Prefab/Benchmark/PrefabLoadBenchmarks.cpp Prefab/Benchmark/PrefabUpdateInstancesBenchmarks.cpp Prefab/Benchmark/SpawnableCreateBenchmarks.cpp + Prefab/Benchmark/Spawnable/SpawnableBenchmarkFixture.h + Prefab/Benchmark/Spawnable/SpawnableBenchmarkFixture.cpp + Prefab/Benchmark/Spawnable/SpawnAllEntitiesBenchmarks.cpp Prefab/PrefabFocus/PrefabFocusTests.cpp Prefab/MockPrefabFileIOActionValidator.cpp Prefab/MockPrefabFileIOActionValidator.h diff --git a/Code/LauncherUnified/Launcher.cpp b/Code/LauncherUnified/Launcher.cpp index 0ef9cdfc3b..0f832ff1a5 100644 --- a/Code/LauncherUnified/Launcher.cpp +++ b/Code/LauncherUnified/Launcher.cpp @@ -9,6 +9,7 @@ #include #include +#include #include #include #include @@ -664,6 +665,8 @@ namespace O3DELauncher systemInitParams.pSystem = CreateSystemInterface(systemInitParams); #endif // !defined(AZ_MONOLITHIC_BUILD) + AZ::ComponentApplicationLifecycle::SignalEvent(*settingsRegistry, "LegacySystemInterfaceCreated", R"({})"); + ReturnCode status = ReturnCode::Success; if (systemInitParams.pSystem) diff --git a/Code/Legacy/CryCommon/ISystem.h b/Code/Legacy/CryCommon/ISystem.h index 948977d02a..103e7b50c2 100644 --- a/Code/Legacy/CryCommon/ISystem.h +++ b/Code/Legacy/CryCommon/ISystem.h @@ -739,24 +739,6 @@ public: #undef GetUserName #endif - -struct IProfilingSystem -{ - // - virtual ~IProfilingSystem() {} - ////////////////////////////////////////////////////////////////////////// - // VTune Profiling interface. - - // Summary: - // Resumes vtune data collection. - virtual void VTuneResume() = 0; - // Summary: - // Pauses vtune data collection. - virtual void VTunePause() = 0; - ////////////////////////////////////////////////////////////////////////// - // -}; - //////////////////////////////////////////////////////////////////////////////////////////////// // Description: @@ -851,7 +833,6 @@ struct ISystem virtual IMovieSystem* GetIMovieSystem() = 0; virtual ::IConsole* GetIConsole() = 0; virtual IRemoteConsole* GetIRemoteConsole() = 0; - virtual IProfilingSystem* GetIProfilingSystem() = 0; virtual ISystemEventDispatcher* GetISystemEventDispatcher() = 0; virtual ITimer* GetITimer() = 0; @@ -1121,8 +1102,8 @@ inline ISystem* GetISystem() // Description: // This function must be called once by each module at the beginning, to setup global pointers. -extern "C" AZ_DLL_EXPORT void ModuleInitISystem(ISystem* pSystem, const char* moduleName); -extern "C" AZ_DLL_EXPORT void ModuleShutdownISystem(ISystem* pSystem); +void ModuleInitISystem(ISystem* pSystem, const char* moduleName); +void ModuleShutdownISystem(ISystem* pSystem); extern "C" AZ_DLL_EXPORT void InjectEnvironment(void* env); extern "C" AZ_DLL_EXPORT void DetachEnvironment(); diff --git a/Code/Legacy/CryCommon/Mocks/ISystemMock.h b/Code/Legacy/CryCommon/Mocks/ISystemMock.h index 9d41b9e77d..a11b60fe68 100644 --- a/Code/Legacy/CryCommon/Mocks/ISystemMock.h +++ b/Code/Legacy/CryCommon/Mocks/ISystemMock.h @@ -76,8 +76,6 @@ public: ::IConsole * ()); MOCK_METHOD0(GetIRemoteConsole, IRemoteConsole * ()); - MOCK_METHOD0(GetIProfilingSystem, - IProfilingSystem * ()); MOCK_METHOD0(GetISystemEventDispatcher, ISystemEventDispatcher * ()); MOCK_METHOD0(GetITimer, diff --git a/Code/Legacy/CryCommon/StlUtils.h b/Code/Legacy/CryCommon/StlUtils.h index f5128a564f..ccbc854dc3 100644 --- a/Code/Legacy/CryCommon/StlUtils.h +++ b/Code/Legacy/CryCommon/StlUtils.h @@ -96,47 +96,6 @@ unsigned countElements (const std::vector& arrT, const T& x) */ namespace stl { - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - // Compare member of class/struct. - // - // e.g. Sort Vec3s by x component - // - // std::sort(vec3s.begin(), vec3s.end(), stl::member_compare()); - // - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - template > - struct member_compare - { - inline bool operator () (const OWNER_TYPE& lhs, const OWNER_TYPE& rhs) const - { - return EQUALITY()(lhs.*MEMBER_PTR, rhs.*MEMBER_PTR); - } - }; - - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - // Compare member of class/struct against parameter. - // - // e.g. Find Vec3 with x component less than 1.0 - // - // std::find_if(vec3s.begin(), vec3s.end(), stl::member_compare_param(1.0f)); - // - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - template > - struct member_compare_param - { - inline member_compare_param(const MEMBER_TYPE& _value) - : value(_value) - { - } - - inline bool operator () (const OWNER_TYPE& rhs) const - { - return EQUALITY()(rhs.*MEMBER_PTR, value); - } - - const MEMBER_TYPE& value; - }; - ////////////////////////////////////////////////////////////////////////// //! Searches the given entry in the map by key, and if there is none, returns the default value ////////////////////////////////////////////////////////////////////////// @@ -154,48 +113,6 @@ namespace stl } } - ////////////////////////////////////////////////////////////////////////// - //! Inserts and returns a reference to the given value in the map, or returns the current one if it's already there. - ////////////////////////////////////////////////////////////////////////// - template - inline typename Map::mapped_type& map_insert_or_get(Map& mapKeyToValue, const typename Map::key_type& key, const typename Map::mapped_type& defValue = typename Map::mapped_type()) - { - auto&& iresult = mapKeyToValue.insert(typename Map::value_type(key, defValue)); - return iresult.first->second; - } - - // searches the given entry in the map by key, and if there is none, returns the default value - // The values are taken/returned in REFERENCEs rather than values - template - inline mapped_type& find_in_map_ref(std::map& mapKeyToValue, const Key& key, mapped_type& valueDefault) - { - typedef std::map Map; - typename Map::iterator it = mapKeyToValue.find (key); - if (it == mapKeyToValue.end()) - { - return valueDefault; - } - else - { - return it->second; - } - } - - template - inline const mapped_type& find_in_map_ref(const std::map& mapKeyToValue, const Key& key, const mapped_type& valueDefault) - { - typedef std::map Map; - typename Map::const_iterator it = mapKeyToValue.find (key); - if (it == mapKeyToValue.end()) - { - return valueDefault; - } - else - { - return it->second; - } - } - ////////////////////////////////////////////////////////////////////////// //! Fills vector with contents of map. ////////////////////////////////////////////////////////////////////////// @@ -210,20 +127,6 @@ namespace stl } } - ////////////////////////////////////////////////////////////////////////// - //! Fills vector with contents of set. - ////////////////////////////////////////////////////////////////////////// - template - inline void set_to_vector(const Set& theSet, Vector& array) - { - array.resize(0); - array.reserve(theSet.size()); - for (typename Set::const_iterator it = theSet.begin(); it != theSet.end(); ++it) - { - array.push_back(*it); - } - } - ////////////////////////////////////////////////////////////////////////// //! Find and erase element from container. // @return true if item was find and erased, false if item not found. @@ -312,48 +215,6 @@ namespace stl return false; } - ////////////////////////////////////////////////////////////////////////// - //! Push back to container unique element. - // @return true if item added, false overwise. - template - inline bool push_back_unique_if(CONTAINER& container, const PREDICATE& predicate, const VALUE& value) - { - typename CONTAINER::iterator end = container.end(); - - if (AZStd::find_if(container.begin(), end, predicate) == end) - { - container.push_back(value); - - return true; - } - else - { - return false; - } - } - - ////////////////////////////////////////////////////////////////////////// - //! Push back to container contents of another container - template - inline void push_back_range(Container& container, Iter begin, Iter end) - { - for (Iter it = begin; it != end; ++it) - { - container.push_back(*it); - } - } - - ////////////////////////////////////////////////////////////////////////// - //! Push back to container contents of another container, if not already present - template - inline void push_back_range_unique(Container& container, Iter begin, Iter end) - { - for (Iter it = begin; it != end; ++it) - { - push_back_unique(container, *it); - } - } - ////////////////////////////////////////////////////////////////////////// //! Find element in container. // @return true if item found. @@ -373,107 +234,6 @@ namespace stl return (it == last || value != *it) ? last : it; } - ////////////////////////////////////////////////////////////////////////// - //! Find element in a sorted container using binary search with logarithmic efficiency. - // @return true if item was inserted. - template - inline bool binary_insert_unique(Container& container, const Value& value) - { - typename Container::iterator it = std::lower_bound(container.begin(), container.end(), value); - if (it != container.end()) - { - if (*it == value) - { - return false; - } - container.insert(it, value); - } - else - { - container.insert(container.end(), value); - } - return true; - } - ////////////////////////////////////////////////////////////////////////// - //! Find element in a sorted container using binary search with logarithmic efficiency. - // and erases if element found. - // @return true if item was erased. - template - inline bool binary_erase(Container& container, const Value& value) - { - typename Container::iterator it = std::lower_bound(container.begin(), container.end(), value); - if (it != container.end() && *it == value) - { - container.erase(it); - return true; - } - return false; - } - - template - ItT remove_from_heap(ItT begin, ItT end, ItT at, Func order) - { - using std::swap; - - --end; - if (at == end) - { - return at; - } - - size_t idx = std::distance(begin, at); - swap(*end, *at); - - size_t length = std::distance(begin, end); - size_t parent, child; - - if (idx > 0 && order(*(begin + idx / 2), *(begin + idx))) - { - do - { - parent = idx / 2; - swap(*(begin + idx), *(begin + parent)); - idx = parent; - - if (idx == 0 || order(*(begin + idx), *(begin + idx / 2))) - { - return end; - } - } - while (true); - } - else - { - do - { - child = idx * 2 + 1; - if (child >= length) - { - return end; - } - - ItT left = begin + child; - ItT right = begin + child + 1; - - if (right < end && order(*left, *right)) - { - ++child; - } - - if (order(*(begin + child), *(begin + idx))) - { - return end; - } - - swap(*(begin + child), *(begin + idx)); - idx = child; - } - while (true); - } - - return end; - } - struct container_object_deleter { template @@ -506,18 +266,6 @@ namespace stl return type.c_str(); } - ////////////////////////////////////////////////////////////////////////// - //! Case sensetive less key for any type convertable to const char*. - ////////////////////////////////////////////////////////////////////////// - template - struct less_strcmp - { - bool operator()(const Type& left, const Type& right) const - { - return strcmp(constchar_cast(left), constchar_cast(right)) < 0; - } - }; - ////////////////////////////////////////////////////////////////////////// //! Case insensetive less key for any type convertable to const char*. template @@ -690,89 +438,4 @@ namespace stl stl::free_container(container); } }; - - template - inline void for_each_array(T (&buffer)[Length], Func func) - { - std::for_each(&buffer[0], &buffer[Length], func); - } - - template - inline void for_each_array(StaticInstance(&buffer)[Length], Func func) - { - for (size_t idx = 0; idx < Length; ++idx) - { - func(*buffer[idx]); - } - } - - template - inline void destruct(T* p) - { - p->~T(); - } -} - -#define DEFINE_INTRUSIVE_LINKED_LIST(Class) \ - template<> \ - Class * stl::intrusive_linked_list_node::m_root_intrusive = nullptr; - -// define the maplikestruct, used to approximate the memory requirements for a map node -namespace stl -{ - struct MapLikeStruct - { - bool color; - void* parent; - void* left; - void* right; - }; -} -template -unsigned sizeOfMap(Map& map) -{ - unsigned size = 0; - for (typename Map::iterator it = map.begin(); it != map.end(); it++) - { - typename Map::mapped_type& T = it->second; - size += T.Size(); - } - size += map.size() * sizeof(stl::MapLikeStruct); - return size; -} -template -unsigned sizeOfMapStr(Map& map) -{ - unsigned size = 0; - for (typename Map::iterator it = map.begin(); it != map.end(); it++) - { - typename Map::mapped_type& T = it->second; - size += T.capacity(); - } - size += map.size() * sizeof(stl::MapLikeStruct); - return size; -} -template -unsigned sizeOfMapP(Map& map) -{ - unsigned size = 0; - for (typename Map::iterator it = map.begin(); it != map.end(); it++) - { - typename Map::mapped_type& T = it->second; - size += T->Size(); - } - size += map.size() * sizeof(stl::MapLikeStruct); - return size; -} -template -unsigned sizeOfMapS(Map& map) -{ - unsigned size = 0; - for (typename Map::iterator it = map.begin(); it != map.end(); it++) - { - typename Map::mapped_type& T = it->second; - size += sizeof(T); - } - size += map.size() * sizeof(stl::MapLikeStruct); - return size; } diff --git a/Code/Legacy/CryCommon/platform_impl.cpp b/Code/Legacy/CryCommon/platform_impl.cpp index a68a5150db..845a1101a4 100644 --- a/Code/Legacy/CryCommon/platform_impl.cpp +++ b/Code/Legacy/CryCommon/platform_impl.cpp @@ -74,7 +74,7 @@ void InitCRTHandlers() {} ////////////////////////////////////////////////////////////////////////// // This is an entry to DLL initialization function that must be called for each loaded module ////////////////////////////////////////////////////////////////////////// -extern "C" AZ_DLL_EXPORT void ModuleInitISystem(ISystem* pSystem, [[maybe_unused]] const char* moduleName) +void ModuleInitISystem(ISystem* pSystem, [[maybe_unused]] const char* moduleName) { if (gEnv) // Already registered. { @@ -96,7 +96,7 @@ extern "C" AZ_DLL_EXPORT void ModuleInitISystem(ISystem* pSystem, [[maybe_unused } // if pSystem } -extern "C" AZ_DLL_EXPORT void ModuleShutdownISystem([[maybe_unused]] ISystem* pSystem) +void ModuleShutdownISystem([[maybe_unused]] ISystem* pSystem) { // Unregister with AZ environment. AZ::Environment::Detach(); diff --git a/Code/Legacy/CrySystem/AZCoreLogSink.h b/Code/Legacy/CrySystem/AZCoreLogSink.h index 8c88752e9a..1b09c3198d 100644 --- a/Code/Legacy/CrySystem/AZCoreLogSink.h +++ b/Code/Legacy/CrySystem/AZCoreLogSink.h @@ -16,7 +16,7 @@ #include -namespace AZ +namespace AZ::Debug { AZ_CVAR_EXTERNED(int, bg_traceLogLevel); } @@ -64,7 +64,7 @@ public: if(!hasSetCVar && ready) { // AZ logging only has a concept of 3 levels (error, warning, info) but cry logging has 4 levels (..., messaging). If info level is set, we'll turn on messaging as well - int logLevel = AZ::bg_traceLogLevel == AZ::Debug::LogLevel::Info ? 4 : AZ::bg_traceLogLevel; + int logLevel = AZ::Debug::bg_traceLogLevel == AZ::Debug::LogLevel::Info ? 4 : AZ::Debug::bg_traceLogLevel; gEnv->pConsole->GetCVar("log_WriteToFileVerbosity")->Set(logLevel); hasSetCVar = true; diff --git a/Code/Legacy/CrySystem/CrySystem_precompiled.h b/Code/Legacy/CrySystem/CrySystem_precompiled.h index faa924931f..3ececa6514 100644 --- a/Code/Legacy/CrySystem/CrySystem_precompiled.h +++ b/Code/Legacy/CrySystem/CrySystem_precompiled.h @@ -60,13 +60,6 @@ #include -#if defined(WIN32) || defined(WIN64) || defined(APPLE) || defined(LINUX) -#if defined(DEDICATED_SERVER) -// enable/disable map load slicing functionality from the build -#define MAP_LOADING_SLICING -#endif -#endif - #ifdef WIN32 #include #include diff --git a/Code/Legacy/CrySystem/IDebugCallStack.cpp b/Code/Legacy/CrySystem/IDebugCallStack.cpp index deb9c11581..a02658d34a 100644 --- a/Code/Legacy/CrySystem/IDebugCallStack.cpp +++ b/Code/Legacy/CrySystem/IDebugCallStack.cpp @@ -242,7 +242,7 @@ void IDebugCallStack::WriteLineToLog(const char* format, ...) va_end(ArgList); AZ::IO::HandleType fileHandle = AZ::IO::InvalidHandle; - AZ::IO::FileIOBase::GetDirectInstance()->Open("@Log@\\error.log", AZ::IO::GetOpenModeFromStringMode("a+t"), fileHandle); + 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)); @@ -254,7 +254,7 @@ void IDebugCallStack::WriteLineToLog(const char* format, ...) ////////////////////////////////////////////////////////////////////////// void IDebugCallStack::StartMemLog() { - AZ::IO::FileIOBase::GetDirectInstance()->Open("@Log@\\memallocfile.log", AZ::IO::OpenMode::ModeWrite, m_memAllocFileHandle); + AZ::IO::FileIOBase::GetDirectInstance()->Open("@log@\\memallocfile.log", AZ::IO::OpenMode::ModeWrite, m_memAllocFileHandle); assert(m_memAllocFileHandle != AZ::IO::InvalidHandle); } diff --git a/Code/Legacy/CrySystem/System.cpp b/Code/Legacy/CrySystem/System.cpp index 2e18e13841..65facba4dd 100644 --- a/Code/Legacy/CrySystem/System.cpp +++ b/Code/Legacy/CrySystem/System.cpp @@ -143,9 +143,6 @@ LRESULT WINAPI WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) #include -// To enable profiling with vtune (https://software.intel.com/en-us/intel-vtune-amplifier-xe), make sure the line below is not commented out -//#define PROFILE_WITH_VTUNE - #include #include #endif @@ -154,10 +151,6 @@ LRESULT WINAPI WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) #include -// profilers api. -VTuneFunction VTResume = NULL; -VTuneFunction VTPause = NULL; - // Define global cvars. SSystemCVars g_cvars; @@ -516,8 +509,6 @@ void CSystem::ShutDown() ShutdownFileSystem(); - ShutdownModuleLibraries(); - EBUS_EVENT(CrySystemEventBus, OnCrySystemPostShutdown); } @@ -697,31 +688,6 @@ bool CSystem::UpdatePreTickBus(int updateFlags, int nPauseMode) m_bPaused = false; } -#ifdef PROFILE_WITH_VTUNE - if (m_bInDevMode) - { - if (VTPause != NULL && VTResume != NULL) - { - static bool bVtunePaused = true; - - const AzFramework::InputChannel* inputChannelScrollLock = AzFramework::InputChannelRequests::FindInputChannel(AzFramework::InputDeviceKeyboard::Key::WindowsSystemScrollLock); - const bool bPaused = (inputChannelScrollLock ? inputChannelScrollLock->IsActive() : false); - - { - if (bVtunePaused && !bPaused) - { - GetIProfilingSystem()->VTuneResume(); - } - if (!bVtunePaused && bPaused) - { - GetIProfilingSystem()->VTunePause(); - } - bVtunePaused = bPaused; - } - } - } -#endif //PROFILE_WITH_VTUNE - #ifndef EXCLUDE_UPDATE_ON_CONSOLE if (m_bIgnoreUpdates) { @@ -1255,30 +1221,6 @@ CPNoise3* CSystem::GetNoiseGen() return &m_pNoiseGen; } -////////////////////////////////////////////////////////////////////////// -void CProfilingSystem::VTuneResume() -{ -#ifdef PROFILE_WITH_VTUNE - if (VTResume) - { - CryLogAlways("VTune Resume"); - VTResume(); - } -#endif -} - -////////////////////////////////////////////////////////////////////////// -void CProfilingSystem::VTunePause() -{ -#ifdef PROFILE_WITH_VTUNE - if (VTPause) - { - VTPause(); - CryLogAlways("VTune Pause"); - } -#endif -} - ////////////////////////////////////////////////////////////////////// void CSystem::OnLanguageCVarChanged(ICVar* language) { diff --git a/Code/Legacy/CrySystem/System.h b/Code/Legacy/CrySystem/System.h index 015b09a69b..a10c1f8ed8 100644 --- a/Code/Legacy/CrySystem/System.h +++ b/Code/Legacy/CrySystem/System.h @@ -105,10 +105,6 @@ struct IDataProbe; #define PHSYICS_OBJECT_ENTITY 0 -using VTuneFunction = void (__cdecl *)(void); -extern VTuneFunction VTResume; -extern VTuneFunction VTPause; - #define MAX_STREAMING_POOL_INDEX 6 #define MAX_THREAD_POOL_INDEX 6 @@ -139,7 +135,6 @@ struct SSystemCVars int sys_ai; int sys_entitysystem; int sys_trackview; - int sys_vtune; float sys_update_profile_time; int sys_limit_phys_thread_count; int sys_MaxFPS; @@ -169,21 +164,6 @@ extern SSystemCVars g_cvars; class CSystem; -struct CProfilingSystem - : public IProfilingSystem -{ - ////////////////////////////////////////////////////////////////////////// - // VTune Profiling interface. - - // Summary: - // Resumes vtune data collection. - void VTuneResume() override; - // Summary: - // Pauses vtune data collection. - void VTunePause() override; - ////////////////////////////////////////////////////////////////////////// -}; - class AssetSystem; /* @@ -262,7 +242,6 @@ public: IViewSystem* GetIViewSystem() override; ILevelSystem* GetILevelSystem() override; ISystemEventDispatcher* GetISystemEventDispatcher() override { return m_pSystemEventDispatcher; } - IProfilingSystem* GetIProfilingSystem() override { return &m_ProfilingSystem; } ////////////////////////////////////////////////////////////////////////// // retrieves the perlin noise singleton instance CPNoise3* GetNoiseGen() override; @@ -324,8 +303,6 @@ public: void SetVersionInfo(const char* const szVersion); #endif - void ShutdownModuleLibraries(); - #if defined(WIN32) friend LRESULT WINAPI WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); #endif @@ -344,8 +321,6 @@ private: // Release all resources. void ShutDown(); - bool LoadEngineDLLs(); - //! @name Initialization routines //@{ bool InitConsole(); @@ -361,11 +336,8 @@ private: void CreateSystemVars(); void CreateAudioVars(); - AZStd::unique_ptr LoadDLL(const char* dllName); - void FreeLib(AZStd::unique_ptr& hLibModule); - bool UnloadDLL(const char* dllName); void QueryVersionInfo(); void LogVersion(); void LogBuildInfo(); @@ -380,8 +352,6 @@ private: void AddCVarGroupDirectory(const AZStd::string& sPath) override; - AZStd::unique_ptr LoadDynamiclibrary(const char* dllName) const; - #if defined(AZ_RESTRICTED_PLATFORM) #define AZ_RESTRICTED_SECTION SYSTEM_H_SECTION_3 #include AZ_RESTRICTED_FILE(System_h) @@ -437,9 +407,6 @@ private: // ------------------------------------------------------ bool m_bDrawConsole; //!< Set to true if OK to draw the console. bool m_bDrawUI; //!< Set to true if OK to draw UI. - - std::map > m_moduleDLLHandles; - //! current active process IProcess* m_pProcess; @@ -564,8 +531,6 @@ private: // ------------------------------------------------------ ESystemConfigSpec m_nMaxConfigSpec; ESystemConfigPlatform m_ConfigPlatform; - CProfilingSystem m_ProfilingSystem; - // Pause mode. bool m_bPaused; bool m_bNoUpdate; @@ -588,9 +553,7 @@ public: const SFileVersion& GetProductVersion() override; const SFileVersion& GetBuildVersion() override; - bool InitVTuneProfiler(); - - void OpenBasicPaks(); + void OpenPlatformPaks(); void OpenLanguagePak(const char* sLanguage); void OpenLanguageAudioPak(const char* sLanguage); void GetLocalizedPath(const char* sLanguage, AZStd::string& sLocalizedPath); diff --git a/Code/Legacy/CrySystem/SystemInit.cpp b/Code/Legacy/CrySystem/SystemInit.cpp index ee1c498a9a..d9ba3bb4c9 100644 --- a/Code/Legacy/CrySystem/SystemInit.cpp +++ b/Code/Legacy/CrySystem/SystemInit.cpp @@ -12,7 +12,6 @@ #if defined(AZ_RESTRICTED_PLATFORM) || defined(AZ_TOOLS_EXPAND_FOR_RESTRICTED_PLATFORMS) #undef AZ_RESTRICTED_SECTION -#define SYSTEMINIT_CPP_SECTION_1 1 #define SYSTEMINIT_CPP_SECTION_2 2 #define SYSTEMINIT_CPP_SECTION_3 3 #define SYSTEMINIT_CPP_SECTION_4 4 @@ -70,9 +69,6 @@ #include "windows.h" #include -// To enable profiling with vtune (https://software.intel.com/en-us/intel-vtune-amplifier-xe), make sure the line below is not commented out -//#define PROFILE_WITH_VTUNE - #endif //WIN32 #include @@ -171,34 +167,6 @@ void CryEngineSignalHandler(int signal) #define LOCALIZATION_TRANSLATIONS_LIST_FILE_NAME "Libs/Localization/localization.xml" -////////////////////////////////////////////////////////////////////////// -#if defined(WIN32) || defined(LINUX) || defined(APPLE) -# define DLL_MODULE_INIT_ISYSTEM "ModuleInitISystem" -# define DLL_MODULE_SHUTDOWN_ISYSTEM "ModuleShutdownISystem" -# define DLL_INITFUNC_RENDERER "PackageRenderConstructor" -# define DLL_INITFUNC_SOUND "CreateSoundSystem" -# define DLL_INITFUNC_FONT "CreateCryFontInterface" -# define DLL_INITFUNC_3DENGINE "CreateCry3DEngine" -# define DLL_INITFUNC_UI "CreateLyShineInterface" -#define AZ_RESTRICTED_SECTION_IMPLEMENTED -#elif defined(AZ_RESTRICTED_PLATFORM) -#define AZ_RESTRICTED_SECTION SYSTEMINIT_CPP_SECTION_1 -#include AZ_RESTRICTED_FILE(SystemInit_cpp) -#endif -#if defined(AZ_RESTRICTED_SECTION_IMPLEMENTED) -#undef AZ_RESTRICTED_SECTION_IMPLEMENTED -#else -# define DLL_MODULE_INIT_ISYSTEM (LPCSTR)2 -# define DLL_MODULE_SHUTDOWN_ISYSTEM (LPCSTR)3 -# define DLL_INITFUNC_RENDERER (LPCSTR)1 -# define DLL_INITFUNC_RENDERER (LPCSTR)1 -# define DLL_INITFUNC_SOUND (LPCSTR)1 -# define DLL_INITFUNC_PHYSIC (LPCSTR)1 -# define DLL_INITFUNC_FONT (LPCSTR)1 -# define DLL_INITFUNC_3DENGINE (LPCSTR)1 -# define DLL_INITFUNC_UI (LPCSTR)1 -#endif - #define AZ_TRACE_SYSTEM_WINDOW AZ::Debug::Trace::GetDefaultSystemWindow() #ifdef WIN32 @@ -292,96 +260,6 @@ static void CmdCrashTest(IConsoleCmdArgs* pArgs) } AZ_POP_DISABLE_WARNING -////////////////////////////////////////////////////////////////////////// -struct SysSpecOverrideSink - : public ILoadConfigurationEntrySink -{ - virtual void OnLoadConfigurationEntry(const char* szKey, const char* szValue, const char* szGroup) - { - ICVar* pCvar = gEnv->pConsole->GetCVar(szKey); - - if (pCvar) - { - const bool wasNotInConfig = ((pCvar->GetFlags() & VF_WASINCONFIG) == 0); - bool applyCvar = wasNotInConfig; - if (applyCvar == false) - { - // Special handling for sys_spec_full - if (azstricmp(szKey, "sys_spec_full") == 0) - { - // If it is set to 0 then ignore this request to set to something else - // If it is set to 0 then the user wants to changes system spec settings in system.cfg - if (pCvar->GetIVal() != 0) - { - applyCvar = true; - } - } - else - { - // This could bypass the restricted cvar checks that exist elsewhere depending on - // the calling code so we also need check here before setting. - bool isConst = pCvar->IsConstCVar(); - bool isCheat = ((pCvar->GetFlags() & (VF_CHEAT | VF_CHEAT_NOCHECK | VF_CHEAT_ALWAYS_CHECK)) != 0); - bool isReadOnly = ((pCvar->GetFlags() & VF_READONLY) != 0); - bool isDeprecated = ((pCvar->GetFlags() & VF_DEPRECATED) != 0); - bool allowApplyCvar = true; - - if ((isConst || isCheat || isReadOnly) || isDeprecated) - { - allowApplyCvar = !isDeprecated && (gEnv->pSystem->IsDevMode()) || (gEnv->IsEditor()); - } - - if ((allowApplyCvar) || ALLOW_CONST_CVAR_MODIFICATIONS) - { - applyCvar = true; - } - } - } - - if (applyCvar) - { - pCvar->Set(szValue); - } - else - { - CryLogAlways("NOT VF_WASINCONFIG Ignoring cvar '%s' new value '%s' old value '%s' group '%s'", szKey, szValue, pCvar->GetString(), szGroup); - } - } - else - { - CryLogAlways("Can't find cvar '%s' value '%s' group '%s'", szKey, szValue, szGroup); - } - } -}; - -#if !defined(CONSOLE) -struct SysSpecOverrideSinkConsole - : public ILoadConfigurationEntrySink -{ - virtual void OnLoadConfigurationEntry(const char* szKey, const char* szValue, const char* szGroup) - { - // Ignore platform-specific cvars that should just be executed on the console - if (azstricmp(szGroup, "Platform") == 0) - { - return; - } - - ICVar* pCvar = gEnv->pConsole->GetCVar(szKey); - if (pCvar) - { - pCvar->Set(szValue); - } - else - { - // If the cvar doesn't exist, calling this function only saves the value in case it's registered later where - // at that point it will be set from the stored value. This is required because otherwise registering the - // cvar bypasses any callbacks and uses values directly from the cvar group files. - gEnv->pConsole->LoadConfigVar(szKey, szValue); - } - } -}; -#endif - static ESystemConfigPlatform GetDevicePlatform() { #if defined(AZ_PLATFORM_WINDOWS) || defined(AZ_PLATFORM_LINUX) @@ -405,117 +283,6 @@ static ESystemConfigPlatform GetDevicePlatform() #endif } -////////////////////////////////////////////////////////////////////////// -#if !defined(AZ_MONOLITHIC_BUILD) - -AZStd::unique_ptr CSystem::LoadDynamiclibrary(const char* dllName) const -{ - AZStd::unique_ptr handle = AZ::DynamicModuleHandle::Create(dllName); - - bool libraryLoaded = handle->Load(false); - // We need to inject the environment first thing so that allocators are available immediately - InjectEnvironmentFunction injectEnv = handle->GetFunction(INJECT_ENVIRONMENT_FUNCTION); - if (injectEnv) - { - auto env = AZ::Environment::GetInstance(); - injectEnv(env); - } - - if (!libraryLoaded) - { - handle.release(); - } - return handle; -} - -////////////////////////////////////////////////////////////////////////// -AZStd::unique_ptr CSystem::LoadDLL(const char* dllName) -{ - AZ_TracePrintf(AZ_TRACE_SYSTEM_WINDOW, "Loading DLL: %s", dllName); - - AZStd::unique_ptr handle = LoadDynamiclibrary(dllName); - - if (!handle) - { -#if defined(LINUX) || defined(APPLE) - AZ_Assert(false, "Error loading dylib: %s, error : %s\n", dllName, dlerror()); -#else - AZ_Assert(false, "Error loading dll: %s, error code %d", dllName, GetLastError()); -#endif - return handle; - } - - ////////////////////////////////////////////////////////////////////////// - // After loading DLL initialize it by calling ModuleInitISystem - ////////////////////////////////////////////////////////////////////////// - AZStd::string moduleName = PathUtil::GetFileName(dllName); - - typedef void*(*PtrFunc_ModuleInitISystem)(ISystem* pSystem, const char* moduleName); - PtrFunc_ModuleInitISystem pfnModuleInitISystem = handle->GetFunction(DLL_MODULE_INIT_ISYSTEM); - if (pfnModuleInitISystem) - { - pfnModuleInitISystem(this, moduleName.c_str()); - } - - return handle; -} - -// TODO:DLL #endif //#if defined(AZ_HAS_DLL_SUPPORT) && !defined(AZ_MONOLITHIC_BUILD) -#endif //if !defined(AZ_MONOLITHIC_BUILD) -////////////////////////////////////////////////////////////////////////// -bool CSystem::LoadEngineDLLs() -{ - return true; -} - -////////////////////////////////////////////////////////////////////////// -bool CSystem::UnloadDLL(const char* dllName) -{ - bool isSuccess = false; - - AZ::Crc32 key(dllName); - AZStd::unique_ptr empty; - AZStd::unique_ptr& hModule = stl::find_in_map_ref(m_moduleDLLHandles, key, empty); - if ((hModule) && (hModule->IsLoaded())) - { - DetachEnvironmentFunction detachEnv = hModule->GetFunction(DETACH_ENVIRONMENT_FUNCTION); - if (detachEnv) - { - detachEnv(); - } - - isSuccess = hModule->Unload(); - hModule.release(); - } - - return isSuccess; -} - -////////////////////////////////////////////////////////////////////////// -void CSystem::ShutdownModuleLibraries() -{ -#if !defined(AZ_MONOLITHIC_BUILD) - for (auto iterator = m_moduleDLLHandles.begin(); iterator != m_moduleDLLHandles.end(); ++iterator) - { - typedef void*( * PtrFunc_ModuleShutdownISystem )(ISystem* pSystem); - - PtrFunc_ModuleShutdownISystem pfnModuleShutdownISystem = iterator->second->GetFunction(DLL_MODULE_SHUTDOWN_ISYSTEM); - if (pfnModuleShutdownISystem) - { - pfnModuleShutdownISystem(this); - } - if (iterator->second->IsLoaded()) - { - iterator->second->Unload(); - } - iterator->second.release(); - } - - m_moduleDLLHandles.clear(); - -#endif // !defined(AZ_MONOLITHIC_BUILD) -} - ///////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////// bool CSystem::InitConsole() @@ -649,7 +416,7 @@ bool CSystem::InitFileSystem_LoadEngineFolders(const SSystemInitParams&) auto projectName = AZ::Utils::GetProjectName(); AZ_Printf(AZ_TRACE_SYSTEM_WINDOW, "Project Name: %s\n", projectName.empty() ? "None specified" : projectName.c_str()); - OpenBasicPaks(); + OpenPlatformPaks(); // Load game-specific folder. LoadConfiguration("game.cfg"); @@ -704,33 +471,6 @@ bool CSystem::InitAudioSystem(const SSystemInitParams& initParams) return result; } -////////////////////////////////////////////////////////////////////////// -bool CSystem::InitVTuneProfiler() -{ -#ifdef PROFILE_WITH_VTUNE - - WIN_HMODULE hModule = LoadDLL("VTuneApi.dll"); - if (!hModule) - { - return false; - } - - VTPause = (VTuneFunction) CryGetProcAddress(hModule, "VTPause"); - VTResume = (VTuneFunction) CryGetProcAddress(hModule, "VTResume"); - if (!VTPause || !VTResume) - { - AZ_Assert(false, "VTune did not initialize correctly.") - return false; - } - else - { - AZ_TracePrintf(AZ_TRACE_SYSTEM_WINDOW, "VTune API Initialized"); - } -#endif //PROFILE_WITH_VTUNE - - return true; -} - ////////////////////////////////////////////////////////////////////////// void CSystem::InitLocalization() { @@ -786,29 +526,19 @@ void CSystem::InitLocalization() OpenLanguageAudioPak(language.c_str()); } -void CSystem::OpenBasicPaks() +void CSystem::OpenPlatformPaks() { - static bool bBasicPaksLoaded = false; - if (bBasicPaksLoaded) + static bool bPlatformPaksLoaded = false; + if (bPlatformPaksLoaded) { return; } - bBasicPaksLoaded = true; - - // open pak files - constexpr AZStd::string_view paksFolder = "@products@/*.pak"; // (@products@ assumed) - m_env.pCryPak->OpenPacks(paksFolder); - - InlineInitializationProcessing("CSystem::OpenBasicPaks OpenPacks( paksFolder.c_str() )"); + bPlatformPaksLoaded = true; ////////////////////////////////////////////////////////////////////////// // Open engine packs ////////////////////////////////////////////////////////////////////////// - const char* const assetsDir = "@products@"; - - // After game paks to have same search order as with files on disk - m_env.pCryPak->OpenPack(assetsDir, "engine.pak"); #if defined(AZ_RESTRICTED_PLATFORM) #define AZ_RESTRICTED_SECTION SYSTEMINIT_CPP_SECTION_15 @@ -816,6 +546,7 @@ void CSystem::OpenBasicPaks() #endif #ifdef AZ_PLATFORM_ANDROID + const char* const assetsDir = "@products@"; // Load Android Obb files if available const char* obbStorage = AZ::Android::Utils::GetObbStoragePath(); AZStd::string mainObbPath = AZStd::move(AZStd::string::format("%s/%s", obbStorage, AZ::Android::Utils::GetObbFileName(true))); @@ -824,7 +555,7 @@ void CSystem::OpenBasicPaks() m_env.pCryPak->OpenPack(assetsDir, patchObbPath.c_str()); #endif //AZ_PLATFORM_ANDROID - InlineInitializationProcessing("CSystem::OpenBasicPaks OpenPacks( Engine... )"); + InlineInitializationProcessing("CSystem::OpenPlatformPaks OpenPacks( Engine... )"); } ////////////////////////////////////////////////////////////////////////// @@ -1328,7 +1059,7 @@ AZ_POP_DISABLE_WARNING ////////////////////////////////////////////////////////////////////////// // Open basic pak files after intro movie playback started ////////////////////////////////////////////////////////////////////////// - OpenBasicPaks(); + OpenPlatformPaks(); ////////////////////////////////////////////////////////////////////////// // AUDIO @@ -1714,8 +1445,6 @@ void CSystem::CreateSystemVars() m_sys_memory_debug = REGISTER_INT("sys_memory_debug", 0, VF_CHEAT, "Enables to activate low memory situation is specific places in the code (argument defines which place), 0=off"); - REGISTER_CVAR2("sys_vtune", &g_cvars.sys_vtune, 0, VF_NULL, ""); - #if defined(AZ_RESTRICTED_PLATFORM) #define AZ_RESTRICTED_SECTION SYSTEMINIT_CPP_SECTION_17 #include AZ_RESTRICTED_FILE(SystemInit_cpp) diff --git a/Code/Legacy/CrySystem/ViewSystem/ViewSystem.cpp b/Code/Legacy/CrySystem/ViewSystem/ViewSystem.cpp index e83676827f..d7e5c081c0 100644 --- a/Code/Legacy/CrySystem/ViewSystem/ViewSystem.cpp +++ b/Code/Legacy/CrySystem/ViewSystem/ViewSystem.cpp @@ -115,17 +115,20 @@ CViewSystem::CViewSystem(ISystem* pSystem) , m_useDeferredViewSystemUpdate(false) , m_bControlsAudioListeners(true) { -#if !defined(_RELEASE) && !defined(DEDICATED_SERVER) - if (!s_debugCamera) +#if !defined(_RELEASE) + if (!gEnv->IsDedicated()) { - s_debugCamera = new DebugCamera; - } + if (!s_debugCamera) + { + s_debugCamera = new DebugCamera; + } - REGISTER_COMMAND("debugCameraToggle", ToggleDebugCamera, VF_DEV_ONLY, "Toggle the debug camera.\n"); - REGISTER_COMMAND("debugCameraInvertY", ToggleDebugCameraInvertY, VF_DEV_ONLY, "Toggle debug camera Y-axis inversion.\n"); - REGISTER_COMMAND("debugCameraMove", DebugCameraMove, VF_DEV_ONLY, "Move the debug camera the specified distance (x y z).\n"); - gEnv->pConsole->CreateKeyBind("ctrl_keyboard_key_punctuation_backslash", "debugCameraToggle"); - gEnv->pConsole->CreateKeyBind("alt_keyboard_key_punctuation_backslash", "debugCameraInvertY"); + REGISTER_COMMAND("debugCameraToggle", ToggleDebugCamera, VF_DEV_ONLY, "Toggle the debug camera.\n"); + REGISTER_COMMAND("debugCameraInvertY", ToggleDebugCameraInvertY, VF_DEV_ONLY, "Toggle debug camera Y-axis inversion.\n"); + REGISTER_COMMAND("debugCameraMove", DebugCameraMove, VF_DEV_ONLY, "Move the debug camera the specified distance (x y z).\n"); + gEnv->pConsole->CreateKeyBind("ctrl_keyboard_key_punctuation_backslash", "debugCameraToggle"); + gEnv->pConsole->CreateKeyBind("alt_keyboard_key_punctuation_backslash", "debugCameraInvertY"); + } #endif REGISTER_CVAR2("cl_camera_noise", &m_fCameraNoise, -1, 0, @@ -167,6 +170,21 @@ CViewSystem::~CViewSystem() { m_pSystem->GetILevelSystem()->RemoveListener(this); } + +#if !defined(_RELEASE) + if (!gEnv->IsDedicated()) + { + UNREGISTER_COMMAND("debugCameraToggle"); + UNREGISTER_COMMAND("debugCameraInvertY"); + UNREGISTER_COMMAND("debugCameraMove"); + + if (s_debugCamera) + { + delete s_debugCamera; + s_debugCamera = nullptr; + } + } +#endif } //------------------------------------------------------------------------ diff --git a/Code/Tools/AssetBundler/CMakeLists.txt b/Code/Tools/AssetBundler/CMakeLists.txt index d613b35ce5..dcc62595c9 100644 --- a/Code/Tools/AssetBundler/CMakeLists.txt +++ b/Code/Tools/AssetBundler/CMakeLists.txt @@ -47,6 +47,10 @@ ly_add_target( AZ::AssetBundlerBatch.Static ) +# Adds a specialized .setreg to identify gems enabled in the active project. +# This associates the AssetBundlerBatch target with the .Builders gem variants. +ly_set_gem_variant_to_load(TARGETS AssetBundlerBatch VARIANTS Builders) + # AssetBundler - Qt GUI Application ly_add_target( NAME AssetBundler ${PAL_TRAIT_BUILD_ASSETBUNDLER_APPLICATION_TYPE} @@ -73,6 +77,10 @@ ly_add_target( ${additional_dependencies} ) +# Adds a specialized .setreg to identify gems enabled in the active project. +# This associates the AssetBundler target with the .Builders gem variants. +ly_set_gem_variant_to_load(TARGETS AssetBundler VARIANTS Builders) + if(PAL_TRAIT_BUILD_TESTS_SUPPORTED) ly_add_target( diff --git a/Code/Tools/AssetBundler/source/models/AssetBundlerAbstractFileTableModel.cpp b/Code/Tools/AssetBundler/source/models/AssetBundlerAbstractFileTableModel.cpp index 45d01b9b5b..8def0a698c 100644 --- a/Code/Tools/AssetBundler/source/models/AssetBundlerAbstractFileTableModel.cpp +++ b/Code/Tools/AssetBundler/source/models/AssetBundlerAbstractFileTableModel.cpp @@ -61,8 +61,12 @@ namespace AssetBundler { AZStd::string absolutePath = filePath.toUtf8().data(); if (AZ::IO::FileIOBase::GetInstance()->Exists(absolutePath.c_str())) - { - AZStd::string projectName = pathToProjectNameMap.at(absolutePath); + { + AZStd::string projectName; + if (pathToProjectNameMap.contains(absolutePath)) + { + projectName = pathToProjectNameMap.at(absolutePath); + } // If a project name is already specified, then the associated file is a default file LoadFile(absolutePath, projectName, !projectName.empty()); diff --git a/Code/Tools/AssetBundler/source/utils/applicationManager.cpp b/Code/Tools/AssetBundler/source/utils/applicationManager.cpp index 0742035c1a..0388570fdd 100644 --- a/Code/Tools/AssetBundler/source/utils/applicationManager.cpp +++ b/Code/Tools/AssetBundler/source/utils/applicationManager.cpp @@ -54,7 +54,12 @@ namespace AssetBundler bool ApplicationManager::Init() { AZ::Debug::TraceMessageBus::Handler::BusConnect(); - Start(AzFramework::Application::Descriptor()); + + ComponentApplication::StartupParameters startupParameters; + // The AssetBundler does not need to load gems + startupParameters.m_loadDynamicModules = false; + Start(AzFramework::Application::Descriptor(), startupParameters); + AZ::SerializeContext* context; EBUS_EVENT_RESULT(context, AZ::ComponentApplicationBus, GetSerializeContext); AZ_Assert(context, "No serialize context"); diff --git a/Code/Tools/AssetBundler/tests/applicationManagerTests.cpp b/Code/Tools/AssetBundler/tests/applicationManagerTests.cpp index 35d3a5da1f..0d915dcc49 100644 --- a/Code/Tools/AssetBundler/tests/applicationManagerTests.cpp +++ b/Code/Tools/AssetBundler/tests/applicationManagerTests.cpp @@ -71,7 +71,11 @@ namespace AssetBundler m_data->m_applicationManager.reset(aznew MockApplicationManagerTest(0, 0)); - m_data->m_applicationManager->Start(AzFramework::Application::Descriptor()); + + AZ::ComponentApplication::StartupParameters startupParameters; + // The AssetBundler does not need to load gems + startupParameters.m_loadDynamicModules = false; + m_data->m_applicationManager->Start(AzFramework::Application::Descriptor(), startupParameters); // Without this, the user settings component would attempt to save on finalize/shutdown. Since the file is // shared across the whole engine, if multiple tests are run in parallel, the saving could cause a crash diff --git a/Code/Tools/AssetProcessor/AssetBuilderSDK/AssetBuilderSDK/AssetBuilderSDK.h b/Code/Tools/AssetProcessor/AssetBuilderSDK/AssetBuilderSDK/AssetBuilderSDK.h index c85c45dd3e..5a57fa1e72 100644 --- a/Code/Tools/AssetProcessor/AssetBuilderSDK/AssetBuilderSDK/AssetBuilderSDK.h +++ b/Code/Tools/AssetProcessor/AssetBuilderSDK/AssetBuilderSDK/AssetBuilderSDK.h @@ -10,12 +10,13 @@ #pragma once #include +#include +#include #include #include #include #include #include -#include #include #include #include diff --git a/Code/Tools/AssetProcessor/native/AssetDatabase/AssetDatabase.cpp b/Code/Tools/AssetProcessor/native/AssetDatabase/AssetDatabase.cpp index 9be991eb0b..8f7d542fcc 100644 --- a/Code/Tools/AssetProcessor/native/AssetDatabase/AssetDatabase.cpp +++ b/Code/Tools/AssetProcessor/native/AssetDatabase/AssetDatabase.cpp @@ -1067,12 +1067,15 @@ namespace AssetProcessor if (dropAllTables) { + AZ_TracePrintf("AssetDatabase", "Closing existing db connection\n"); // Temporary debug output to help with tracking down a crash // drop all tables by destroying the entire database. m_databaseConnection->Close(); + AZ_TracePrintf("AssetDatabase", "Getting db file path\n"); // Temporary debug output to help with tracking down a crash AZStd::string dbFilePath = GetAssetDatabaseFilePath(); if (dbFilePath != ":memory:") { + AZ_TracePrintf("AssetDatabase", "Deleting existing db %s\n", dbFilePath.c_str()); // Temporary debug output to help with tracking down a crash // you cannot delete a memory database, but it drops all data when you close it anyway. if (!AZ::IO::SystemFile::Delete(dbFilePath.c_str())) { @@ -1082,7 +1085,7 @@ namespace AssetProcessor return false; } } - + AZ_TracePrintf("AssetDatabase", "Re-opening connection\n"); // Temporary debug output to help with tracking down a crash if (!m_databaseConnection->Open(dbFilePath, IsReadOnly())) { delete m_databaseConnection; diff --git a/Code/Tools/AssetProcessor/native/AssetManager/assetProcessorManager.cpp b/Code/Tools/AssetProcessor/native/AssetManager/assetProcessorManager.cpp index fc806f6c9e..367a296bd4 100644 --- a/Code/Tools/AssetProcessor/native/AssetManager/assetProcessorManager.cpp +++ b/Code/Tools/AssetProcessor/native/AssetManager/assetProcessorManager.cpp @@ -3576,23 +3576,24 @@ namespace AssetProcessor // Absolute path, just check the 1 scan folder if (AZ::IO::PathView(encodedFileData.toUtf8().constData()).IsAbsolute()) { - QString scanFolderName; - if (!m_platformConfig->ConvertToRelativePath(encodedFileData, resultDatabaseSourceName, scanFolderName)) + auto scanFolderInfo = m_platformConfig->GetScanFolderForFile(encodedFileData); + + if (!m_platformConfig->ConvertToRelativePath(encodedFileData, scanFolderInfo, resultDatabaseSourceName)) { AZ_Warning( AssetProcessor::ConsoleChannel, false, "'%s' does not appear to be in any input folder. Use relative paths instead.", sourceDependency.m_sourceFileDependencyPath.c_str()); } + else + { + // Make an absolute path that is ScanFolderPath + Part of search path before the wildcard + QDir rooted(scanFolderInfo->ScanPath()); + QString scanFolderAndKnownSubPath = rooted.absoluteFilePath(knownPathBeforeWildcard); - auto scanFolderInfo = m_platformConfig->GetScanFolderByPath(scanFolderName); - - // Make an absolute path that is ScanFolderPath + Part of search path before the wildcard - QDir rooted(scanFolderName); - QString scanFolderAndKnownSubPath = rooted.absoluteFilePath(knownPathBeforeWildcard); - - resolvedDependencyList.append(m_platformConfig->FindWildcardMatches( - scanFolderAndKnownSubPath, relativeSearch, false, scanFolderInfo->RecurseSubFolders())); + resolvedDependencyList.append(m_platformConfig->FindWildcardMatches( + scanFolderAndKnownSubPath, relativeSearch, false, scanFolderInfo->RecurseSubFolders())); + } } else // Relative path, check every scan folder { @@ -4026,30 +4027,43 @@ namespace AssetProcessor // It is generally called when a source file modified in any way, including when it is added or deleted. // note that this is a "reverse" dependency query - it looks up what depends on a file, not what the file depends on using namespace AzToolsFramework::AssetDatabase; - QStringList absoluteSourceFilePathQueue; + QSet absoluteSourceFilePathQueue; QString databasePath; QString scanFolder; - auto callbackFunction = [&](AzToolsFramework::AssetDatabase::SourceFileDependencyEntry& entry) + auto callbackFunction = [this, &absoluteSourceFilePathQueue](SourceFileDependencyEntry& entry) { QString relativeDatabaseName = QString::fromUtf8(entry.m_source.c_str()); QString absolutePath = m_platformConfig->FindFirstMatchingFile(relativeDatabaseName); if (!absolutePath.isEmpty()) { - absoluteSourceFilePathQueue.push_back(absolutePath); + absoluteSourceFilePathQueue.insert(absolutePath); } return true; }; + auto callbackFunctionAbsoluteCheck = [&callbackFunction](SourceFileDependencyEntry& entry) + { + if (AZ::IO::PathView(entry.m_dependsOnSource.c_str()).IsAbsolute()) + { + return callbackFunction(entry); + } + + return true; + }; + // convert to a database path so that the standard function can be called. if (m_platformConfig->ConvertToRelativePath(sourcePath, databasePath, scanFolder)) { m_stateData->QuerySourceDependencyByDependsOnSource(databasePath.toUtf8().constData(), nullptr, SourceFileDependencyEntry::DEP_Any, callbackFunction); - } - return absoluteSourceFilePathQueue; + // We'll also check with the absolute path, because we support absolute path dependencies + m_stateData->QuerySourceDependencyByDependsOnSource( + sourcePath.toUtf8().constData(), nullptr, SourceFileDependencyEntry::DEP_Any, callbackFunctionAbsoluteCheck); + + return absoluteSourceFilePathQueue.values(); } void AssetProcessorManager::AddSourceToDatabase(AzToolsFramework::AssetDatabase::SourceDatabaseEntry& sourceDatabaseEntry, const ScanFolderInfo* scanFolder, QString relativeSourceFilePath) diff --git a/Code/Tools/AssetProcessor/native/InternalBuilders/SettingsRegistryBuilder.cpp b/Code/Tools/AssetProcessor/native/InternalBuilders/SettingsRegistryBuilder.cpp index c65ab24aed..7823a0582f 100644 --- a/Code/Tools/AssetProcessor/native/InternalBuilders/SettingsRegistryBuilder.cpp +++ b/Code/Tools/AssetProcessor/native/InternalBuilders/SettingsRegistryBuilder.cpp @@ -159,6 +159,7 @@ namespace AssetProcessor builderDesc.m_busId = m_builderId; builderDesc.m_createJobFunction = AZStd::bind(&SettingsRegistryBuilder::CreateJobs, this, AZStd::placeholders::_1, AZStd::placeholders::_2); builderDesc.m_processJobFunction = AZStd::bind(&SettingsRegistryBuilder::ProcessJob, this, AZStd::placeholders::_1, AZStd::placeholders::_2); + builderDesc.m_version = 1; AssetBuilderSDK::AssetBuilderBus::Broadcast(&AssetBuilderSDK::AssetBuilderBusTraits::RegisterBuilderInformation, builderDesc); @@ -259,6 +260,11 @@ namespace AssetProcessor scratchBuffer.reserve(512 * 1024); // Reserve 512kb to avoid repeatedly resizing the buffer; AZStd::fixed_vector platformCodes; AzFramework::PlatformHelper::AppendPlatformCodeNames(platformCodes, request.m_platformInfo.m_identifier); + AZ_Assert(platformCodes.size() <= 1, "A one-to-one mapping of asset type platform identifier" + " to platform codename is required in the SettingsRegistryBuilder." + " The bootstrap.game is now only produced per build configuration and doesn't take into account" + " different platforms names"); + const AZStd::string& assetPlatformIdentifier = request.m_jobDescription.GetPlatformIdentifier(); // Determines the suffix that will be used for the launcher based on processing server vs non-server assets const char* launcherType = assetPlatformIdentifier != AzFramework::PlatformHelper::GetPlatformName(AzFramework::PlatformId::SERVER) @@ -293,9 +299,9 @@ namespace AssetProcessor outputBuffer.Reserve(512 * 1024); // Reserve 512kb to avoid repeatedly resizing the buffer; SettingsExporter exporter(outputBuffer, excludes); - for (AZStd::string_view platform : platformCodes) + if (!platformCodes.empty()) { - AZ::u32 productSubID = static_cast(AZStd::hash{}(platform)); // Deliberately ignoring half the bits. + AZStd::string_view platform = platformCodes.front(); for (size_t i = 0; i < AZStd::size(specializations); ++i) { const AZ::SettingsRegistryInterface::Specializations& specialization = specializations[i]; @@ -337,7 +343,7 @@ namespace AssetProcessor // The purpose of this section is to copy the Gem's SourcePaths from the Global Settings Registry // the local SettingsRegistry. The reason this is needed is so that the call to // `MergeSettingsToRegistry_GemRegistries` below is able to locate each gem's "/Registry" folder - // that will be merged into the bootstrap.game...setreg file + // that will be merged into the bootstrap.game..setreg file // This is used by the GameLauncher applications to read from a single merged .setreg file // containing the settings needed to run a game/simulation without have access to the source code base registry AZStd::vector gemInfos; @@ -407,9 +413,8 @@ namespace AssetProcessor return; } - outputPath += specialization.GetSpecialization(0); // Append configuration - outputPath += '.'; - outputPath += platform; + AZStd::string_view specializationString(specialization.GetSpecialization(0)); + outputPath += specializationString; // Append configuration outputPath += ".setreg"; AZ::IO::SystemFile file; @@ -426,7 +431,10 @@ namespace AssetProcessor } file.Close(); - response.m_outputProducts.emplace_back(outputPath, m_assetType, productSubID + aznumeric_cast(i)); + AZ::u32 hashedSpecialization = static_cast(AZStd::hash{}(specializationString)); + AZ_Assert(hashedSpecialization != 0, "Product ID generation failed for specialization %.*s. This can result in a product ID collision with other builders for this asset.", + AZ_STRING_ARG(specializationString)); + response.m_outputProducts.emplace_back(outputPath, m_assetType, hashedSpecialization); response.m_outputProducts.back().m_dependenciesHandled = true; outputPath.erase(extensionOffset); diff --git a/Code/Tools/AssetProcessor/native/tests/AssetCatalog/AssetCatalogUnitTests.cpp b/Code/Tools/AssetProcessor/native/tests/AssetCatalog/AssetCatalogUnitTests.cpp index 2a1c4e4755..46f801223a 100644 --- a/Code/Tools/AssetProcessor/native/tests/AssetCatalog/AssetCatalogUnitTests.cpp +++ b/Code/Tools/AssetProcessor/native/tests/AssetCatalog/AssetCatalogUnitTests.cpp @@ -281,7 +281,7 @@ namespace AssetProcessor ExcludeAssetRecognizer excludeRecogniser; excludeRecogniser.m_name = "backup"; - excludeRecogniser.m_patternMatcher = AssetBuilderSDK::FilePatternMatcher(".*\\/savebackup\\/.*", AssetBuilderSDK::AssetBuilderPattern::Regex); + excludeRecogniser.m_patternMatcher = AssetBuilderSDK::FilePatternMatcher("(^|.+/)savebackup/.*", AssetBuilderSDK::AssetBuilderPattern::Regex); config.AddExcludeRecognizer(excludeRecogniser); } diff --git a/Code/Tools/AssetProcessor/native/tests/AssetProcessorTest.h b/Code/Tools/AssetProcessor/native/tests/AssetProcessorTest.h index 258268c182..df40683027 100644 --- a/Code/Tools/AssetProcessor/native/tests/AssetProcessorTest.h +++ b/Code/Tools/AssetProcessor/native/tests/AssetProcessorTest.h @@ -25,7 +25,7 @@ namespace AssetProcessor : public ::testing::Test { protected: - UnitTestUtils::AssertAbsorber* m_errorAbsorber; + AZStd::unique_ptr m_errorAbsorber{}; FileStatePassthrough m_fileStateCache; void SetUp() override @@ -40,7 +40,7 @@ namespace AssetProcessor m_ownsSysAllocator = true; AZ::AllocatorInstance::Create(); } - m_errorAbsorber = new UnitTestUtils::AssertAbsorber(); + m_errorAbsorber = AZStd::make_unique(); m_application = AZStd::make_unique(); @@ -60,8 +60,8 @@ namespace AssetProcessor AssetUtilities::ResetAssetRoot(); m_application.reset(); - delete m_errorAbsorber; - m_errorAbsorber = nullptr; + m_errorAbsorber.reset(); + if (m_ownsSysAllocator) { AZ::AllocatorInstance::Destroy(); diff --git a/Code/Tools/AssetProcessor/native/tests/assetmanager/AssetProcessorManagerTest.cpp b/Code/Tools/AssetProcessor/native/tests/assetmanager/AssetProcessorManagerTest.cpp index da6e995c97..0cbdc20f1c 100644 --- a/Code/Tools/AssetProcessor/native/tests/assetmanager/AssetProcessorManagerTest.cpp +++ b/Code/Tools/AssetProcessor/native/tests/assetmanager/AssetProcessorManagerTest.cpp @@ -88,6 +88,7 @@ public: friend struct DuplicateProductsTest; friend struct DuplicateProcessTest; friend struct AbsolutePathProductDependencyTest; + friend struct WildcardSourceDependencyTest; explicit AssetProcessorManager_Test(PlatformConfiguration* config, QObject* parent = nullptr); ~AssetProcessorManager_Test() override; @@ -240,8 +241,10 @@ void AssetProcessorManagerTest::SetUp() ASSERT_TRUE(m_mockApplicationManager->RegisterAssetRecognizerAsBuilder(rec)); m_mockApplicationManager->BusConnect(); + AZ_Printf("UnitTest", "Allocating APM\n") m_assetProcessorManager.reset(new AssetProcessorManager_Test(m_config.get())); - m_assertAbsorber.Clear(); + AZ_Printf("UnitTest", "APM ready\n"); + m_errorAbsorber->Clear(); m_isIdling = false; @@ -332,9 +335,9 @@ TEST_F(AssetProcessorManagerTest, UnitTestForGettingJobInfoBySourceUUIDSuccess) EXPECT_STRCASEEQ(relFileName.toUtf8().data(), response.m_jobList[0].m_sourceFile.c_str()); EXPECT_STRCASEEQ(tempPath.filePath("subfolder1").toUtf8().data(), response.m_jobList[0].m_watchFolder.c_str()); - ASSERT_EQ(m_assertAbsorber.m_numWarningsAbsorbed, 0); - ASSERT_EQ(m_assertAbsorber.m_numErrorsAbsorbed, 0); - ASSERT_EQ(m_assertAbsorber.m_numAssertsAbsorbed, 0); + ASSERT_EQ(m_errorAbsorber->m_numWarningsAbsorbed, 0); + ASSERT_EQ(m_errorAbsorber->m_numErrorsAbsorbed, 0); + ASSERT_EQ(m_errorAbsorber->m_numAssertsAbsorbed, 0); } TEST_F(AssetProcessorManagerTest, WarningsAndErrorsReported_SuccessfullySavedToDatabase) @@ -386,9 +389,9 @@ TEST_F(AssetProcessorManagerTest, WarningsAndErrorsReported_SuccessfullySavedToD ASSERT_EQ(response.m_jobList[0].m_warningCount, 11); ASSERT_EQ(response.m_jobList[0].m_errorCount, 22); - ASSERT_EQ(m_assertAbsorber.m_numWarningsAbsorbed, 0); - ASSERT_EQ(m_assertAbsorber.m_numErrorsAbsorbed, 0); - ASSERT_EQ(m_assertAbsorber.m_numAssertsAbsorbed, 0); + ASSERT_EQ(m_errorAbsorber->m_numWarningsAbsorbed, 0); + ASSERT_EQ(m_errorAbsorber->m_numErrorsAbsorbed, 0); + ASSERT_EQ(m_errorAbsorber->m_numAssertsAbsorbed, 0); } @@ -1310,8 +1313,8 @@ void PathDependencyTest::SetUp() void PathDependencyTest::TearDown() { - ASSERT_EQ(m_assertAbsorber.m_numAssertsAbsorbed, 0); - ASSERT_EQ(m_assertAbsorber.m_numErrorsAbsorbed, 0); + ASSERT_EQ(m_errorAbsorber->m_numAssertsAbsorbed, 0); + ASSERT_EQ(m_errorAbsorber->m_numErrorsAbsorbed, 0); AssetProcessorManagerTest::TearDown(); } @@ -1615,7 +1618,7 @@ TEST_F(PathDependencyTest, AssetProcessed_Impl_SelfReferrentialProductDependency mainFile.m_products.push_back(productAssetId); // tell the APM that the asset has been processed and allow it to bubble through its event queue: - m_assertAbsorber.Clear(); + m_errorAbsorber->Clear(); m_assetProcessorManager->AssetProcessed(jobDetails.m_jobEntry, processJobResponse); ASSERT_TRUE(BlockUntilIdle(5000)); @@ -1625,8 +1628,8 @@ TEST_F(PathDependencyTest, AssetProcessed_Impl_SelfReferrentialProductDependency ASSERT_TRUE(dependencyContainer.empty()); // We are testing 2 different dependencies, so we should get 2 warnings - ASSERT_EQ(m_assertAbsorber.m_numWarningsAbsorbed, 2); - m_assertAbsorber.Clear(); + ASSERT_EQ(m_errorAbsorber->m_numWarningsAbsorbed, 2); + m_errorAbsorber->Clear(); } // This test shows the process of deferring resolution of a path dependency works. @@ -1943,8 +1946,8 @@ TEST_F(PathDependencyTest, WildcardDependencies_ExcludePathsExisting_ResolveCorr ); // Test asset PrimaryFile1 has 4 conflict dependencies - ASSERT_EQ(m_assertAbsorber.m_numErrorsAbsorbed, 4); - m_assertAbsorber.Clear(); + ASSERT_EQ(m_errorAbsorber->m_numErrorsAbsorbed, 4); + m_errorAbsorber->Clear(); } TEST_F(PathDependencyTest, WildcardDependencies_Deferred_ResolveCorrectly) @@ -2091,8 +2094,8 @@ TEST_F(PathDependencyTest, WildcardDependencies_ExcludedPathDeferred_ResolveCorr // Test asset PrimaryFile1 has 4 conflict dependencies // After test assets dep2 and dep3 are processed, // another 2 errors will be raised because of the confliction - ASSERT_EQ(m_assertAbsorber.m_numErrorsAbsorbed, 6); - m_assertAbsorber.Clear(); + ASSERT_EQ(m_errorAbsorber->m_numErrorsAbsorbed, 6); + m_errorAbsorber->Clear(); } void PathDependencyTest::RunWildcardTest(bool useCorrectDatabaseSeparator, AssetBuilderSDK::ProductPathDependencyType pathDependencyType, bool buildDependenciesFirst) @@ -4137,11 +4140,19 @@ struct LockedFileTest switch (message.GetMessageType()) { case SourceFileNotificationMessage::MessageType: - if (const auto sourceFileMessage = azrtti_cast(&message); - sourceFileMessage != nullptr && sourceFileMessage->m_type == SourceFileNotificationMessage::NotificationType::FileRemoved - && m_callback) + if (const auto sourceFileMessage = azrtti_cast(&message); sourceFileMessage != nullptr && + sourceFileMessage->m_type == SourceFileNotificationMessage::NotificationType::FileRemoved) { - m_callback(); + // The File Remove message will occur before an attempt to delete the file + // Wait for more than 1 File Remove message. + // This indicates the AP has attempted to delete the file once, failed to do so and is now retrying + ++m_deleteCounter; + + if(m_deleteCounter > 1 && m_callback) + { + m_callback(); + m_callback = {}; // Unset it to be safe, we only intend to run the callback once + } } break; default: @@ -4165,6 +4176,7 @@ struct LockedFileTest ModtimeScanningTest::TearDown(); } + AZStd::atomic_int m_deleteCounter{ 0 }; AZStd::function m_callback; }; @@ -4204,6 +4216,10 @@ TEST_F(LockedFileTest, DeleteFile_LockedProduct_DeleteFails) TEST_F(LockedFileTest, DeleteFile_LockedProduct_DeletesWhenReleased) { + // This test is intended to verify the AP will successfully retry deleting a source asset + // when one of its product assets is locked temporarily + // We'll lock the file by holding it open + auto theFile = m_data->m_absolutePath[1].toUtf8(); const char* theFileString = theFile.constData(); auto [sourcePath, productPath] = *m_data->m_productPaths.find(theFileString); @@ -4216,19 +4232,22 @@ TEST_F(LockedFileTest, DeleteFile_LockedProduct_DeletesWhenReleased) ASSERT_GT(m_data->m_productPaths.size(), 0); QFile product(productPath); + // Open the file and keep it open to lock it + // We'll start a thread later to unlock the file + // This will allow us to test how AP handles trying to delete a locked file ASSERT_TRUE(product.open(QIODevice::ReadOnly)); // Check if we can delete the file now, if we can't, proceed with the test // If we can, it means the OS running this test doesn't lock open files so there's nothing to test if (!AZ::IO::SystemFile::Delete(productPath.toUtf8().constData())) { - AZStd::thread workerThread; + m_deleteCounter = 0; - m_callback = [&product, &workerThread]() { - workerThread = AZStd::thread([&product]() { - AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(60)); - product.close(); - }); + // Set up a callback which will fire after at least 1 retry + // Unlock the file at that point so AP can successfully delete it + m_callback = [&product]() + { + product.close(); }; QMetaObject::invokeMethod( @@ -4238,8 +4257,9 @@ TEST_F(LockedFileTest, DeleteFile_LockedProduct_DeletesWhenReleased) EXPECT_FALSE(QFile::exists(productPath)); EXPECT_EQ(m_data->m_deletedSources.size(), 1); - - workerThread.join(); + + EXPECT_GT(m_deleteCounter, 1); // Make sure the AP tried more than once to delete the file + m_errorAbsorber->ExpectAsserts(0); } else { @@ -4446,7 +4466,9 @@ AssetBuilderSDK::AssetBuilderDesc MockBuilderInfoHandler::CreateBuilderDesc(cons void FingerprintTest::SetUp() { + AZ_Printf("FingerprintTest", "SetUp start\n"); AssetProcessorManagerTest::SetUp(); + AZ_Printf("FingerprintTest", "SetUp self\n"); // We don't want the mock application manager to provide builder descriptors, mockBuilderInfoHandler will provide our own m_mockApplicationManager->BusDisconnect(); @@ -4465,18 +4487,23 @@ void FingerprintTest::SetUp() }); ASSERT_TRUE(UnitTestUtils::CreateDummyFile(m_absolutePath, "")); + AZ_Printf("FingerprintTest", "SetUp end\n"); } void FingerprintTest::TearDown() { + AZ_Printf("FingerprintTest", "TearDown start\n"); m_jobResults = AZStd::vector{}; m_mockBuilderInfoHandler = {}; + AZ_Printf("FingerprintTest", "TearDown parent\n"); AssetProcessorManagerTest::TearDown(); + AZ_Printf("FingerprintTest", "TearDown end\n"); } void FingerprintTest::RunFingerprintTest(QString builderFingerprint, QString jobFingerprint, bool expectedResult) { + AZ_Printf("FingerprintTest", "Fingerprint Test Start\n"); m_mockBuilderInfoHandler.m_builderDesc.m_analysisFingerprint = builderFingerprint.toUtf8().data(); m_mockBuilderInfoHandler.m_jobFingerprint = jobFingerprint; QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssessModifiedFile", Qt::QueuedConnection, Q_ARG(QString, m_absolutePath)); @@ -4485,6 +4512,7 @@ void FingerprintTest::RunFingerprintTest(QString builderFingerprint, QString job ASSERT_EQ(m_mockBuilderInfoHandler.m_createJobsCount, 1); ASSERT_EQ(m_jobResults.size(), 1); ASSERT_EQ(m_jobResults[0].m_autoFail, expectedResult); + AZ_Printf("FingerprintTest", "Fingerprint Test End\n"); } TEST_F(FingerprintTest, FingerprintChecking_JobFingerprint_NoBuilderFingerprint) @@ -5308,3 +5336,219 @@ TEST_F(MetadataFileTest, MetadataFile_SourceFileExtensionDifferentCase) ASSERT_TRUE(BlockUntilIdle(5000)); ASSERT_EQ(jobDetails.m_jobEntry.m_pathRelativeToWatchFolder, relFileName); } + +AZStd::vector QStringListToVector(const QStringList& qstringList) +{ + AZStd::vector azVector; + // Convert to a vector of AZStd::strings because GTest handles this type better when displaying errors + for (const QString& resolvedPath : qstringList) + { + azVector.emplace_back(resolvedPath.toUtf8().constData()); + } + + return azVector; +} + +bool WildcardSourceDependencyTest::Test( + const AZStd::string& dependencyPath, AZStd::vector& resolvedPaths) +{ + [[maybe_unused]] QString resolvedName; + QStringList stringlistPaths; + AssetBuilderSDK::SourceFileDependency dependency(dependencyPath, AZ::Uuid::CreateNull(), AssetBuilderSDK::SourceFileDependency::SourceFileDependencyType::Wildcards); + bool result = m_assetProcessorManager->ResolveSourceFileDependencyPath(dependency, resolvedName, stringlistPaths); + + resolvedPaths = QStringListToVector(stringlistPaths); + + return result; +} + +AZStd::vector WildcardSourceDependencyTest::FileAddedTest(const QString& path) +{ + auto result = m_assetProcessorManager->GetSourceFilesWhichDependOnSourceFile(path); + + return QStringListToVector(result); +} + +void WildcardSourceDependencyTest::SetUp() +{ + AssetProcessorManagerTest::SetUp(); + + QDir tempPath(m_tempDir.path()); + + // Add a non-recursive scan folder. Only files directly inside of this folder should be picked up, subfolders are ignored + m_config->AddScanFolder(ScanFolderInfo(tempPath.filePath("no_recurse"), "no_recurse", + "no_recurse", false, false, m_config->GetEnabledPlatforms(), 1)); + + UnitTestUtils::CreateDummyFile(tempPath.absoluteFilePath("subfolder1/1a.foo")); + UnitTestUtils::CreateDummyFile(tempPath.absoluteFilePath("subfolder1/1b.foo")); + UnitTestUtils::CreateDummyFile(tempPath.absoluteFilePath("subfolder2/redirected/a.foo")); + UnitTestUtils::CreateDummyFile(tempPath.absoluteFilePath("subfolder2/redirected/b.foo")); + UnitTestUtils::CreateDummyFile(tempPath.absoluteFilePath("subfolder2/redirected/folder/one/c.foo")); + UnitTestUtils::CreateDummyFile(tempPath.absoluteFilePath("subfolder2/redirected/folder/one/d.foo")); + + // Add a file that is not in a scanfolder. Should always be ignored + UnitTestUtils::CreateDummyFile(tempPath.absoluteFilePath("not/a/scanfolder/e.foo")); + + // Add a file in the non-recursive scanfolder. Since its not directly in the scan folder, it should always be ignored + UnitTestUtils::CreateDummyFile(tempPath.absoluteFilePath("no_recurse/one/two/three/f.foo")); + + AzToolsFramework::AssetDatabase::SourceFileDependencyEntryContainer dependencies; + + // Relative path wildcard dependency + dependencies.push_back(AzToolsFramework::AssetDatabase::SourceFileDependencyEntry( + AZ::Uuid::CreateRandom(), "a.foo", "%a.foo", + AzToolsFramework::AssetDatabase::SourceFileDependencyEntry::DEP_SourceLikeMatch, 0)); + + // Absolute path wildcard dependency + dependencies.push_back(AzToolsFramework::AssetDatabase::SourceFileDependencyEntry( + AZ::Uuid::CreateRandom(), "b.foo", tempPath.absoluteFilePath("%b.foo").toUtf8().constData(), + AzToolsFramework::AssetDatabase::SourceFileDependencyEntry::DEP_SourceLikeMatch, 0)); + + // Test what happens when we have 2 dependencies on the same file + dependencies.push_back(AzToolsFramework::AssetDatabase::SourceFileDependencyEntry( + AZ::Uuid::CreateRandom(), "folder/one/d.foo", "%c.foo", + AzToolsFramework::AssetDatabase::SourceFileDependencyEntry::DEP_SourceLikeMatch, 0)); + + dependencies.push_back(AzToolsFramework::AssetDatabase::SourceFileDependencyEntry( + AZ::Uuid::CreateRandom(), "folder/one/d.foo", tempPath.absoluteFilePath("%c.foo").toUtf8().constData(), + AzToolsFramework::AssetDatabase::SourceFileDependencyEntry::DEP_SourceLikeMatch, 0)); + +#ifdef AZ_PLATFORM_WINDOWS + // Test to make sure a relative wildcard dependency doesn't match an absolute path + // For example, if the input is C:/project/subfolder1/a.foo + // This should not match a wildcard of c%.foo + // Take the first character of the tempPath and append %.foo onto it for this test, which should produce something like c%.foo + // This only applies to windows because on other OSes if the dependency starts with /, then its an abs path dependency + auto test = (tempPath.absolutePath().left(1) + "%.foo"); + dependencies.push_back(AzToolsFramework::AssetDatabase::SourceFileDependencyEntry( + AZ::Uuid::CreateRandom(), "folder/one/d.foo", + (test).toUtf8().constData(), + AzToolsFramework::AssetDatabase::SourceFileDependencyEntry::DEP_SourceLikeMatch, 0)); +#endif + + ASSERT_TRUE(m_assetProcessorManager->m_stateData->SetSourceFileDependencies(dependencies)); +} + +TEST_F(WildcardSourceDependencyTest, Relative_Broad) +{ + // Expect all files except for the 2 invalid ones (e and f) + AZStd::vector resolvedPaths; + + ASSERT_TRUE(Test("*.foo", resolvedPaths)); + ASSERT_THAT(resolvedPaths, ::testing::UnorderedElementsAre("a.foo", "b.foo", "folder/one/c.foo", "folder/one/d.foo", "1a.foo", "1b.foo")); +} + +TEST_F(WildcardSourceDependencyTest, Relative_WithFolder) +{ + // Make sure we can filter to files under a folder + AZStd::vector resolvedPaths; + + ASSERT_TRUE(Test("folder/*.foo", resolvedPaths)); + ASSERT_THAT(resolvedPaths, ::testing::UnorderedElementsAre("folder/one/c.foo", "folder/one/d.foo")); +} + +TEST_F(WildcardSourceDependencyTest, Relative_WildcardPath) +{ + // Make sure the * wildcard works even if the full filename is given + AZStd::vector resolvedPaths; + + ASSERT_TRUE(Test("*a.foo", resolvedPaths)); + ASSERT_THAT(resolvedPaths, ::testing::UnorderedElementsAre("a.foo", "1a.foo")); +} + +TEST_F(WildcardSourceDependencyTest, Absolute_WithFolder) +{ + // Make sure we can use absolute paths to filter to files under a folder + AZStd::vector resolvedPaths; + QDir tempPath(m_tempDir.path()); + + ASSERT_TRUE(Test(tempPath.absoluteFilePath("subfolder2/redirected/*.foo").toUtf8().constData(), resolvedPaths)); + ASSERT_THAT(resolvedPaths, ::testing::UnorderedElementsAre("a.foo", "b.foo", "folder/one/c.foo", "folder/one/d.foo")); +} + +TEST_F(WildcardSourceDependencyTest, Absolute_NotInScanfolder) +{ + // Files outside a scanfolder should not be returned even with an absolute path + AZStd::vector resolvedPaths; + QDir tempPath(m_tempDir.path()); + + ASSERT_TRUE(Test(tempPath.absoluteFilePath("not/a/scanfolder/*.foo").toUtf8().constData(), resolvedPaths)); + ASSERT_THAT(resolvedPaths, ::testing::UnorderedElementsAre()); +} + +TEST_F(WildcardSourceDependencyTest, Relative_NotInScanfolder) +{ + // Files outside a scanfolder should not be returned + AZStd::vector resolvedPaths; + QDir tempPath(m_tempDir.path()); + + ASSERT_TRUE(Test("*/e.foo", resolvedPaths)); + ASSERT_THAT(resolvedPaths, ::testing::UnorderedElementsAre()); +} + +TEST_F(WildcardSourceDependencyTest, Relative_InNonRecursiveScanfolder) +{ + // Files deep inside non-recursive scanfolders should not be returned + AZStd::vector resolvedPaths; + QDir tempPath(m_tempDir.path()); + + ASSERT_TRUE(Test("*/f.foo", resolvedPaths)); + ASSERT_THAT(resolvedPaths, ::testing::UnorderedElementsAre()); +} + +TEST_F(WildcardSourceDependencyTest, Absolute_InNonRecursiveScanfolder) +{ + // Absolute paths to files deep inside non-recursive scanfolders should not be returned + AZStd::vector resolvedPaths; + QDir tempPath(m_tempDir.path()); + + ASSERT_TRUE(Test(tempPath.absoluteFilePath("one/two/three/*.foo").toUtf8().constData(), resolvedPaths)); + ASSERT_THAT(resolvedPaths, ::testing::UnorderedElementsAre()); +} + +TEST_F(WildcardSourceDependencyTest, Relative_NoWildcard) +{ + // No wildcard results in a failure + AZStd::vector resolvedPaths; + QDir tempPath(m_tempDir.path()); + + ASSERT_FALSE(Test("subfolder1/1a.foo", resolvedPaths)); + ASSERT_THAT(resolvedPaths, ::testing::UnorderedElementsAre()); +} + +TEST_F(WildcardSourceDependencyTest, Absolute_NoWildcard) +{ + // No wildcard results in a failure + AZStd::vector resolvedPaths; + QDir tempPath(m_tempDir.path()); + + ASSERT_FALSE(Test(tempPath.absoluteFilePath("subfolder1/1a.foo").toUtf8().constData(), resolvedPaths)); + ASSERT_THAT(resolvedPaths, ::testing::UnorderedElementsAre()); +} + +TEST_F(WildcardSourceDependencyTest, NewFile_MatchesSavedRelativeDependency) +{ + QDir tempPath(m_tempDir.path()); + + auto matches = FileAddedTest(tempPath.absoluteFilePath("subfolder1/1a.foo")); + + ASSERT_THAT(matches, ::testing::UnorderedElementsAre(tempPath.absoluteFilePath("subfolder2/redirected/a.foo").toUtf8().constData())); +} + +TEST_F(WildcardSourceDependencyTest, NewFile_MatchesSavedAbsoluteDependency) +{ + QDir tempPath(m_tempDir.path()); + + auto matches = FileAddedTest(tempPath.absoluteFilePath("subfolder1/1b.foo")); + + ASSERT_THAT(matches, ::testing::UnorderedElementsAre(tempPath.absoluteFilePath("subfolder2/redirected/b.foo").toUtf8().constData())); +} + +TEST_F(WildcardSourceDependencyTest, NewFile_MatchesDuplicatedDependenciesOnce) +{ + QDir tempPath(m_tempDir.path()); + + auto matches = FileAddedTest(tempPath.absoluteFilePath("subfolder2/redirected/folder/one/c.foo")); + + ASSERT_THAT(matches, ::testing::UnorderedElementsAre(tempPath.absoluteFilePath("subfolder2/redirected/folder/one/d.foo").toUtf8().constData())); +} diff --git a/Code/Tools/AssetProcessor/native/tests/assetmanager/AssetProcessorManagerTest.h b/Code/Tools/AssetProcessor/native/tests/assetmanager/AssetProcessorManagerTest.h index 3443a4c519..7bbef41f44 100644 --- a/Code/Tools/AssetProcessor/native/tests/assetmanager/AssetProcessorManagerTest.h +++ b/Code/Tools/AssetProcessor/native/tests/assetmanager/AssetProcessorManagerTest.h @@ -58,7 +58,6 @@ protected: AZStd::unique_ptr m_assetProcessorManager; AZStd::unique_ptr m_mockApplicationManager; AZStd::unique_ptr m_config; - UnitTestUtils::AssertAbsorber m_assertAbsorber; // absorb asserts/warnings/errors so that the unit test output is not cluttered QString m_gameName; QDir m_normalizedCacheRootDir; AZStd::atomic_bool m_isIdling; @@ -131,6 +130,15 @@ struct MultiplatformPathDependencyTest void SetUp() override; }; +struct WildcardSourceDependencyTest + : AssetProcessorManagerTest +{ + bool Test(const AZStd::string& dependencyPath, AZStd::vector& resolvedPaths); + AZStd::vector FileAddedTest(const QString& path); + + void SetUp() override; +}; + struct MockBuilderInfoHandler : public AssetProcessor::AssetBuilderInfoBus::Handler { diff --git a/Code/Tools/AssetProcessor/native/tests/assetscanner/AssetScannerTests.cpp b/Code/Tools/AssetProcessor/native/tests/assetscanner/AssetScannerTests.cpp index 7bf9eb3cc5..9b4c4e4f40 100644 --- a/Code/Tools/AssetProcessor/native/tests/assetscanner/AssetScannerTests.cpp +++ b/Code/Tools/AssetProcessor/native/tests/assetscanner/AssetScannerTests.cpp @@ -123,7 +123,7 @@ namespace AssetProcessor ExcludeAssetRecognizer excludeRecogniser; excludeRecogniser.m_name = "backup"; // we are excluding all the files in the folder but not the folder itself - excludeRecogniser.m_patternMatcher = AssetBuilderSDK::FilePatternMatcher(".*\\/subfolder2\\/aaa\\/.*", AssetBuilderSDK::AssetBuilderPattern::Regex); + excludeRecogniser.m_patternMatcher = AssetBuilderSDK::FilePatternMatcher("(^|[^/]+/)aaa/.*", AssetBuilderSDK::AssetBuilderPattern::Regex); m_platformConfig.get()->AddExcludeRecognizer(excludeRecogniser); m_assetScanner.get()->StartScan(); @@ -144,7 +144,7 @@ namespace AssetProcessor ExcludeAssetRecognizer excludeRecogniser; excludeRecogniser.m_name = "backup"; // we are excluding the complete folder here - excludeRecogniser.m_patternMatcher = AssetBuilderSDK::FilePatternMatcher(".*\\/subfolder2\\/aaa", AssetBuilderSDK::AssetBuilderPattern::Regex); + excludeRecogniser.m_patternMatcher = AssetBuilderSDK::FilePatternMatcher("(^|[^/]+/)aaa", AssetBuilderSDK::AssetBuilderPattern::Regex); m_platformConfig.get()->AddExcludeRecognizer(excludeRecogniser); m_assetScanner.get()->StartScan(); diff --git a/Code/Tools/AssetProcessor/native/tests/platformconfiguration/platformconfigurationtests.cpp b/Code/Tools/AssetProcessor/native/tests/platformconfiguration/platformconfigurationtests.cpp index 69397745f1..6fd05fa948 100644 --- a/Code/Tools/AssetProcessor/native/tests/platformconfiguration/platformconfigurationtests.cpp +++ b/Code/Tools/AssetProcessor/native/tests/platformconfiguration/platformconfigurationtests.cpp @@ -405,6 +405,8 @@ TEST_F(PlatformConfigurationUnitTests, TestFailReadConfigFile_RegularExcludes) auto configRoot = AZ::IO::FileIOBase::GetInstance()->ResolvePath("@exefolder@/testdata/config_regular"); ASSERT_TRUE(configRoot); UnitTestPlatformConfiguration config; + + config.AddScanFolder(ScanFolderInfo("blahblah", "Blah ScanFolder", "sf2", true, true), true); m_absorber.Clear(); ASSERT_TRUE(config.InitializeFromConfigFiles(configRoot->c_str(), testExeFolder->c_str(), EmptyDummyProjectName, false, false)); ASSERT_EQ(m_absorber.m_numErrorsAbsorbed, 0); diff --git a/Code/Tools/AssetProcessor/native/unittests/AssetProcessorManagerUnitTests.cpp b/Code/Tools/AssetProcessor/native/unittests/AssetProcessorManagerUnitTests.cpp index c0e2c0f1d2..9efe755f16 100644 --- a/Code/Tools/AssetProcessor/native/unittests/AssetProcessorManagerUnitTests.cpp +++ b/Code/Tools/AssetProcessor/native/unittests/AssetProcessorManagerUnitTests.cpp @@ -347,7 +347,7 @@ namespace AssetProcessor ExcludeAssetRecognizer excludeRecogniser; excludeRecogniser.m_name = "backup"; - excludeRecogniser.m_patternMatcher = AssetBuilderSDK::FilePatternMatcher(".*\\/savebackup\\/.*", AssetBuilderSDK::AssetBuilderPattern::Regex); + excludeRecogniser.m_patternMatcher = AssetBuilderSDK::FilePatternMatcher("(^|.+/)savebackup/.*", AssetBuilderSDK::AssetBuilderPattern::Regex); config.AddExcludeRecognizer(excludeRecogniser); AssetProcessorManager_Test apm(&config); // note, this will 'push' the scan folders in to the db. @@ -1791,13 +1791,8 @@ namespace AssetProcessor UNIT_TEST_EXPECT_TRUE((newfingerprintForPCAfterVersionChange != fingerprintForPC) || (newfingerprintForPCAfterVersionChange != newfingerprintForPC));//Fingerprints should be different UNIT_TEST_EXPECT_TRUE((newfingerprintForANDROIDAfterVersionChange != fingerprintForANDROID) || (newfingerprintForANDROIDAfterVersionChange != newfingerprintForANDROID));//Fingerprints should be different - //------Test for Files which are excluded processResults.clear(); - absolutePath = AssetUtilities::NormalizeFilePath(tempPath.absoluteFilePath("subfolder3/savebackup/test.txt")); - QMetaObject::invokeMethod(&apm, "AssessModifiedFile", Qt::QueuedConnection, Q_ARG(QString, absolutePath)); - UNIT_TEST_EXPECT_FALSE(BlockUntil(idling, 3000)); //Processing a file that will be excluded should not cause assetprocessor manager to emit the onBecameIdle signal because its state should not change - UNIT_TEST_EXPECT_TRUE(processResults.size() == 0); - + // ------------- Test querying asset status ------------------- { absolutePath = tempPath.absoluteFilePath("subfolder2/folder/ship.tiff"); diff --git a/Code/Tools/AssetProcessor/native/unittests/UnitTestRunner.h b/Code/Tools/AssetProcessor/native/unittests/UnitTestRunner.h index c281b3050a..0c4357f84a 100644 --- a/Code/Tools/AssetProcessor/native/unittests/UnitTestRunner.h +++ b/Code/Tools/AssetProcessor/native/unittests/UnitTestRunner.h @@ -21,6 +21,7 @@ #endif #include +#include //! These macros can be used for checking your unit tests, //! you can check AssetScannerUnitTest.cpp for usage @@ -155,6 +156,7 @@ namespace UnitTestUtils bool OnPreWarning([[maybe_unused]] const char* window, [[maybe_unused]] const char* fileName, [[maybe_unused]] int line, [[maybe_unused]] const char* func, [[maybe_unused]] const char* message) override { + UnitTest::ColoredPrintf(UnitTest::COLOR_YELLOW, message); ++m_numWarningsAbsorbed; if (m_debugMessages) { @@ -165,6 +167,7 @@ namespace UnitTestUtils bool OnPreAssert([[maybe_unused]] const char* fileName, [[maybe_unused]] int line, [[maybe_unused]] const char* func, [[maybe_unused]] const char* message) override { + UnitTest::ColoredPrintf(UnitTest::COLOR_YELLOW, message); ++m_numAssertsAbsorbed; if (m_debugMessages) { @@ -175,6 +178,7 @@ namespace UnitTestUtils bool OnPreError([[maybe_unused]] const char* window, [[maybe_unused]] const char* fileName, [[maybe_unused]] int line, [[maybe_unused]] const char* func, [[maybe_unused]] const char* message) override { + UnitTest::ColoredPrintf(UnitTest::COLOR_YELLOW, message); ++m_numErrorsAbsorbed; if (m_debugMessages) { @@ -183,8 +187,9 @@ namespace UnitTestUtils return true; // I handled this, do not forward it } - bool OnPrintf(const char* /*window*/, const char* /*message*/) override + bool OnPrintf(const char* /*window*/, const char* message) override { + UnitTest::ColoredPrintf(UnitTest::COLOR_YELLOW, message); ++m_numMessagesAbsorbed; return true; } diff --git a/Code/Tools/AssetProcessor/native/utilities/PlatformConfiguration.cpp b/Code/Tools/AssetProcessor/native/utilities/PlatformConfiguration.cpp index 9a60bf3110..bcadd0f103 100644 --- a/Code/Tools/AssetProcessor/native/utilities/PlatformConfiguration.cpp +++ b/Code/Tools/AssetProcessor/native/utilities/PlatformConfiguration.cpp @@ -1633,13 +1633,18 @@ namespace AssetProcessor bool AssetProcessor::PlatformConfiguration::IsFileExcluded(QString fileName) const { - for (const ExcludeAssetRecognizer& excludeRecognizer : m_excludeAssetRecognizers) + QString relPath, scanFolderName; + if (ConvertToRelativePath(fileName, relPath, scanFolderName)) { - if (excludeRecognizer.m_patternMatcher.MatchesPath(fileName.toUtf8().constData())) + for (const ExcludeAssetRecognizer& excludeRecognizer : m_excludeAssetRecognizers) { - return true; + if (excludeRecognizer.m_patternMatcher.MatchesPath(relPath.toUtf8().constData())) + { + return true; + } } } + return false; } diff --git a/Code/Tools/AssetProcessor/testdata/config_regular/AssetProcessorPlatformConfig.setreg b/Code/Tools/AssetProcessor/testdata/config_regular/AssetProcessorPlatformConfig.setreg index e4bee1b6de..11a77aefd4 100644 --- a/Code/Tools/AssetProcessor/testdata/config_regular/AssetProcessorPlatformConfig.setreg +++ b/Code/Tools/AssetProcessor/testdata/config_regular/AssetProcessorPlatformConfig.setreg @@ -50,10 +50,10 @@ "order": 6000 }, "Exclude HoldFiles": { - "pattern": ".*\\\\/Levels\\\\/.*_hold\\\\/.*" + "pattern": "(^|.+/)Levels/.*_hold(/.*)?$" }, "Exclude TempFiles": { - "pattern": ".*\\\\/\\\\$tmp[0-9]*_.*" + "pattern": "(^|.+/)\\\\$tmp[0-9]*_.*" }, "RC i_caf": { "glob": "*.i_caf", diff --git a/Code/Tools/AssetProcessor/testdata/config_regular_platform_scanfolder/AssetProcessorPlatformConfig.setreg b/Code/Tools/AssetProcessor/testdata/config_regular_platform_scanfolder/AssetProcessorPlatformConfig.setreg index c8fd13bf1f..a86a2168fd 100644 --- a/Code/Tools/AssetProcessor/testdata/config_regular_platform_scanfolder/AssetProcessorPlatformConfig.setreg +++ b/Code/Tools/AssetProcessor/testdata/config_regular_platform_scanfolder/AssetProcessorPlatformConfig.setreg @@ -74,10 +74,10 @@ "include": "test" }, "Exclude HoldFiles": { - "pattern": ".*\\\\/Levels\\\\/.*_hold\\\\/.*" + "pattern": "(^|.+/)Levels/.*_hold(/.*)?$" }, "Exclude TempFiles": { - "pattern": ".*\\\\/\\\\$tmp[0-9]*_.*" + "pattern": "(^|.+/)\\\\$tmp[0-9]*_.*" }, "RC i_caf": { "glob": "*.i_caf", diff --git a/Code/Tools/BundleLauncher/O3DE_SDK_Launcher.cpp b/Code/Tools/BundleLauncher/O3DE_SDK_Launcher.cpp index 0c362ac829..4dd7e184e9 100644 --- a/Code/Tools/BundleLauncher/O3DE_SDK_Launcher.cpp +++ b/Code/Tools/BundleLauncher/O3DE_SDK_Launcher.cpp @@ -49,6 +49,12 @@ int main(int argc, char* argv[]) AZStd::unique_ptr shellProcess(AzFramework::ProcessWatcher::LaunchProcess(shellProcessLaunch, AzFramework::ProcessCommunicationType::COMMUNICATOR_TYPE_NONE)); shellProcess->WaitForProcessToExit(120); shellProcess.reset(); + + parameters = AZStd::string::format("-c \"%s/scripts/o3de.sh register --this-engine\"", enginePath.c_str()); + shellProcessLaunch.m_commandlineParameters = parameters; + shellProcess.reset(AzFramework::ProcessWatcher::LaunchProcess(shellProcessLaunch, AzFramework::ProcessCommunicationType::COMMUNICATOR_TYPE_NONE)); + shellProcess->WaitForProcessToExit(120); + shellProcess.reset(); AZ::IO::FixedMaxPath projectManagerPath = installedBinariesFolder/"o3de.app"/"Contents"/"MacOS"/"o3de"; AzFramework::ProcessLauncher::ProcessLaunchInfo processLaunchInfo; diff --git a/Code/Tools/GridHub/CMakeLists.txt b/Code/Tools/GridHub/CMakeLists.txt index 26d8b8e557..8603c1e477 100644 --- a/Code/Tools/GridHub/CMakeLists.txt +++ b/Code/Tools/GridHub/CMakeLists.txt @@ -35,3 +35,5 @@ ly_add_target( AZ::AzCore AZ::GridMate ) + +ly_add_dependencies(LuaIDE GridHub) diff --git a/Code/Tools/ProjectManager/CMakeLists.txt b/Code/Tools/ProjectManager/CMakeLists.txt index d34abcbc6c..a47ccb62c9 100644 --- a/Code/Tools/ProjectManager/CMakeLists.txt +++ b/Code/Tools/ProjectManager/CMakeLists.txt @@ -34,6 +34,7 @@ ly_add_target( INCLUDE_DIRECTORIES PRIVATE Source + Platform/${PAL_PLATFORM_NAME} BUILD_DEPENDENCIES PRIVATE 3rdParty::Qt::Core diff --git a/Code/Tools/ProjectManager/Platform/Linux/PAL_linux_files.cmake b/Code/Tools/ProjectManager/Platform/Linux/PAL_linux_files.cmake index 11222602d5..c3acd44f9b 100644 --- a/Code/Tools/ProjectManager/Platform/Linux/PAL_linux_files.cmake +++ b/Code/Tools/ProjectManager/Platform/Linux/PAL_linux_files.cmake @@ -11,4 +11,6 @@ set(FILES ProjectBuilderWorker_linux.cpp ProjectUtils_linux.cpp ProjectManagerDefs_linux.cpp + ProjectManager_Traits_Platform.h + ProjectManager_Traits_Linux.h ) diff --git a/Code/Tools/ProjectManager/Platform/Linux/ProjectManager_Traits_Linux.h b/Code/Tools/ProjectManager/Platform/Linux/ProjectManager_Traits_Linux.h new file mode 100644 index 0000000000..7c0543361f --- /dev/null +++ b/Code/Tools/ProjectManager/Platform/Linux/ProjectManager_Traits_Linux.h @@ -0,0 +1,11 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#define AZ_TRAIT_PROJECT_MANAGER_CUSTOM_TITLEBAR false diff --git a/Code/Tools/ProjectManager/Platform/Linux/ProjectManager_Traits_Platform.h b/Code/Tools/ProjectManager/Platform/Linux/ProjectManager_Traits_Platform.h new file mode 100644 index 0000000000..97aee25507 --- /dev/null +++ b/Code/Tools/ProjectManager/Platform/Linux/ProjectManager_Traits_Platform.h @@ -0,0 +1,11 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include diff --git a/Code/Tools/ProjectManager/Platform/Linux/ProjectUtils_linux.cpp b/Code/Tools/ProjectManager/Platform/Linux/ProjectUtils_linux.cpp index 0d66009d90..e901d807b4 100644 --- a/Code/Tools/ProjectManager/Platform/Linux/ProjectUtils_linux.cpp +++ b/Code/Tools/ProjectManager/Platform/Linux/ProjectUtils_linux.cpp @@ -10,6 +10,8 @@ #include #include +#include + namespace O3DE::ProjectManager { namespace ProjectUtils @@ -94,5 +96,10 @@ namespace O3DE::ProjectManager QProcessEnvironment::systemEnvironment(), QObject::tr("Running get_python script...")); } + + AZ::IO::FixedMaxPath GetEditorDirectory() + { + return AZ::Utils::GetExecutableDirectory(); + } } // namespace ProjectUtils } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Platform/Mac/PAL_mac_files.cmake b/Code/Tools/ProjectManager/Platform/Mac/PAL_mac_files.cmake index 54b35f0d3c..6d4d453f21 100644 --- a/Code/Tools/ProjectManager/Platform/Mac/PAL_mac_files.cmake +++ b/Code/Tools/ProjectManager/Platform/Mac/PAL_mac_files.cmake @@ -11,4 +11,6 @@ set(FILES ProjectBuilderWorker_mac.cpp ProjectUtils_mac.cpp ProjectManagerDefs_mac.cpp + ProjectManager_Traits_Platform.h + ProjectManager_Traits_Mac.h ) diff --git a/Code/Tools/ProjectManager/Platform/Mac/ProjectManager_Traits_Mac.h b/Code/Tools/ProjectManager/Platform/Mac/ProjectManager_Traits_Mac.h new file mode 100644 index 0000000000..7c0543361f --- /dev/null +++ b/Code/Tools/ProjectManager/Platform/Mac/ProjectManager_Traits_Mac.h @@ -0,0 +1,11 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#define AZ_TRAIT_PROJECT_MANAGER_CUSTOM_TITLEBAR false diff --git a/Code/Tools/ProjectManager/Platform/Mac/ProjectManager_Traits_Platform.h b/Code/Tools/ProjectManager/Platform/Mac/ProjectManager_Traits_Platform.h new file mode 100644 index 0000000000..dc77e77fd0 --- /dev/null +++ b/Code/Tools/ProjectManager/Platform/Mac/ProjectManager_Traits_Platform.h @@ -0,0 +1,11 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include diff --git a/Code/Tools/ProjectManager/Platform/Mac/ProjectUtils_mac.cpp b/Code/Tools/ProjectManager/Platform/Mac/ProjectUtils_mac.cpp index e36f6cd0c8..b768200398 100644 --- a/Code/Tools/ProjectManager/Platform/Mac/ProjectUtils_mac.cpp +++ b/Code/Tools/ProjectManager/Platform/Mac/ProjectUtils_mac.cpp @@ -11,6 +11,9 @@ #include #include +#include +#include + namespace O3DE::ProjectManager { namespace ProjectUtils @@ -104,5 +107,35 @@ namespace O3DE::ProjectManager QProcessEnvironment::systemEnvironment(), QObject::tr("Running get_python script...")); } + + AZ::IO::FixedMaxPath GetEditorDirectory() + { + AZ::IO::FixedMaxPath executableDirectory = AZ::Utils::GetExecutableDirectory(); + AZ::IO::FixedMaxPath editorPath{ executableDirectory }; + editorPath /= "../../../Editor.app/Contents/MacOS"; + editorPath = editorPath.LexicallyNormal(); + if (!AZ::IO::SystemFile::IsDirectory(editorPath.c_str())) + { + if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr) + { + if (AZ::IO::FixedMaxPath installedBinariesPath; + settingsRegistry->Get(installedBinariesPath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_InstalledBinaryFolder)) + { + if (AZ::IO::FixedMaxPath engineRootFolder; + settingsRegistry->Get(engineRootFolder.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder)) + { + editorPath = engineRootFolder / installedBinariesPath / "Editor.app/Contents/MacOS"; + } + } + } + + if (!AZ::IO::SystemFile::IsDirectory(editorPath.c_str())) + { + AZ_Error("ProjectManager", false, "Unable to find the Editor app bundle!"); + } + } + + return editorPath; + } } // namespace ProjectUtils } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Platform/Windows/PAL_windows_files.cmake b/Code/Tools/ProjectManager/Platform/Windows/PAL_windows_files.cmake index d95b0d2502..22b4614ddf 100644 --- a/Code/Tools/ProjectManager/Platform/Windows/PAL_windows_files.cmake +++ b/Code/Tools/ProjectManager/Platform/Windows/PAL_windows_files.cmake @@ -11,4 +11,6 @@ set(FILES ProjectBuilderWorker_windows.cpp ProjectUtils_windows.cpp ProjectManagerDefs_windows.cpp + ProjectManager_Traits_Platform.h + ProjectManager_Traits_Windows.h ) diff --git a/Code/Tools/ProjectManager/Platform/Windows/ProjectBuilderWorker_windows.cpp b/Code/Tools/ProjectManager/Platform/Windows/ProjectBuilderWorker_windows.cpp index 075c6de774..b6b37b222d 100644 --- a/Code/Tools/ProjectManager/Platform/Windows/ProjectBuilderWorker_windows.cpp +++ b/Code/Tools/ProjectManager/Platform/Windows/ProjectBuilderWorker_windows.cpp @@ -21,8 +21,7 @@ namespace O3DE::ProjectManager return AZ::Success(QStringList{ ProjectCMakeCommand, "-B", targetBuildPath, "-S", m_projectInfo.m_path, - QString("-DLY_3RDPARTY_PATH=").append(thirdPartyPath), - "-DLY_UNITY_BUILD=ON" } ); + QString("-DLY_3RDPARTY_PATH=").append(thirdPartyPath) } ); } AZ::Outcome ProjectBuilderWorker::ConstructCmakeBuildCommandArguments() const diff --git a/Code/Tools/ProjectManager/Platform/Windows/ProjectManager_Traits_Platform.h b/Code/Tools/ProjectManager/Platform/Windows/ProjectManager_Traits_Platform.h new file mode 100644 index 0000000000..f5eac50dbc --- /dev/null +++ b/Code/Tools/ProjectManager/Platform/Windows/ProjectManager_Traits_Platform.h @@ -0,0 +1,11 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include diff --git a/Code/Tools/ProjectManager/Platform/Windows/ProjectManager_Traits_Windows.h b/Code/Tools/ProjectManager/Platform/Windows/ProjectManager_Traits_Windows.h new file mode 100644 index 0000000000..e6422b5a77 --- /dev/null +++ b/Code/Tools/ProjectManager/Platform/Windows/ProjectManager_Traits_Windows.h @@ -0,0 +1,11 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#define AZ_TRAIT_PROJECT_MANAGER_CUSTOM_TITLEBAR true diff --git a/Code/Tools/ProjectManager/Platform/Windows/ProjectUtils_windows.cpp b/Code/Tools/ProjectManager/Platform/Windows/ProjectUtils_windows.cpp index 831529d5e4..871f8e9567 100644 --- a/Code/Tools/ProjectManager/Platform/Windows/ProjectUtils_windows.cpp +++ b/Code/Tools/ProjectManager/Platform/Windows/ProjectUtils_windows.cpp @@ -14,6 +14,8 @@ #include #include +#include + namespace O3DE::ProjectManager { namespace ProjectUtils @@ -139,5 +141,10 @@ namespace O3DE::ProjectManager QProcessEnvironment::systemEnvironment(), QObject::tr("Running get_python script...")); } + + AZ::IO::FixedMaxPath GetEditorDirectory() + { + return AZ::Utils::GetExecutableDirectory(); + } } // namespace ProjectUtils } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Resources/ProjectManager.qss b/Code/Tools/ProjectManager/Resources/ProjectManager.qss index 6694168f2b..d3ec066be7 100644 --- a/Code/Tools/ProjectManager/Resources/ProjectManager.qss +++ b/Code/Tools/ProjectManager/Resources/ProjectManager.qss @@ -9,7 +9,7 @@ QMainWindow { #ScreensCtrl { min-width:1200px; - min-height:800px; + min-height:700px; } QPushButton:focus { @@ -242,11 +242,15 @@ QTabBar::tab:focus { /************** Project Settings **************/ #projectSettings { - margin-top:42px; + margin-top:30px; +} + +#projectPreviewLabel { + margin: 10px 0 5px 0; } #projectTemplate { - margin: 55px 0 0 50px; + margin: 25px 0 0 50px; } #projectTemplateLabel { font-size:16px; @@ -691,6 +695,12 @@ QProgressBar::chunk { #gemRepoAddDialogInstructionTitleLabel { font-size:14px; + font-weight:bold; +} + +#gemRepoAddDialogWarningLabel { + font-size:12px; + font-style:italic; } #addGemRepoDialog #formFrame { diff --git a/Code/Tools/ProjectManager/Source/Application.cpp b/Code/Tools/ProjectManager/Source/Application.cpp index a7e4805ce9..29e0df3c3a 100644 --- a/Code/Tools/ProjectManager/Source/Application.cpp +++ b/Code/Tools/ProjectManager/Source/Application.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #include @@ -194,8 +195,12 @@ namespace O3DE::ProjectManager // set stylesheet after creating the main window or their styles won't get updated AzQtComponents::StyleManager::setStyleSheet(m_mainWindow.data(), QStringLiteral("style:ProjectManager.qss")); - // the decoration wrapper is intended to remember window positioning and sizing + // the decoration wrapper is intended to remember window positioning and sizing +#if AZ_TRAIT_PROJECT_MANAGER_CUSTOM_TITLEBAR auto wrapper = new AzQtComponents::WindowDecorationWrapper(); +#else + auto wrapper = new AzQtComponents::WindowDecorationWrapper(AzQtComponents::WindowDecorationWrapper::OptionDisabled); +#endif wrapper->setGuest(m_mainWindow.data()); // show the main window here to apply the stylesheet before restoring geometry or we diff --git a/Code/Tools/ProjectManager/Source/CreateProjectCtrl.cpp b/Code/Tools/ProjectManager/Source/CreateProjectCtrl.cpp index 65e01803aa..51e1163713 100644 --- a/Code/Tools/ProjectManager/Source/CreateProjectCtrl.cpp +++ b/Code/Tools/ProjectManager/Source/CreateProjectCtrl.cpp @@ -240,7 +240,7 @@ namespace O3DE::ProjectManager PythonBindingsInterface::Get()->AddProject(projectInfo.m_path); #ifdef TEMPLATE_GEM_CONFIGURATION_ENABLED - const GemCatalogScreen::EnableDisableGemsResult gemResult = m_gemCatalogScreen->EnableDisableGemsForProject(m_projectInfo.m_path); + const GemCatalogScreen::EnableDisableGemsResult gemResult = m_gemCatalogScreen->EnableDisableGemsForProject(projectInfo.m_path); if (gemResult == GemCatalogScreen::EnableDisableGemsResult::Failed) { QMessageBox::critical(this, tr("Failed to configure gems"), tr("Failed to configure gems for template.")); diff --git a/Code/Tools/ProjectManager/Source/CreateProjectCtrl.h b/Code/Tools/ProjectManager/Source/CreateProjectCtrl.h index 40ddb14b83..58f4758edd 100644 --- a/Code/Tools/ProjectManager/Source/CreateProjectCtrl.h +++ b/Code/Tools/ProjectManager/Source/CreateProjectCtrl.h @@ -62,9 +62,6 @@ namespace O3DE::ProjectManager QPushButton* m_secondaryButton = nullptr; #endif // TEMPLATE_GEM_CONFIGURATION_ENABLED - QString m_projectTemplatePath; - ProjectInfo m_projectInfo; - NewProjectSettingsScreen* m_newProjectSettingsScreen = nullptr; GemCatalogScreen* m_gemCatalogScreen = nullptr; }; diff --git a/Code/Tools/ProjectManager/Source/DownloadController.cpp b/Code/Tools/ProjectManager/Source/DownloadController.cpp index fa3fdb10d1..224b90299c 100644 --- a/Code/Tools/ProjectManager/Source/DownloadController.cpp +++ b/Code/Tools/ProjectManager/Source/DownloadController.cpp @@ -8,10 +8,12 @@ #include #include +#include + +#include #include - namespace O3DE::ProjectManager { DownloadController::DownloadController(QWidget* parent) @@ -46,6 +48,24 @@ namespace O3DE::ProjectManager } } + void DownloadController::CancelGemDownload(const QString& gemName) + { + auto findResult = AZStd::find(m_gemNames.begin(), m_gemNames.end(), gemName); + + if (findResult != m_gemNames.end()) + { + if (findResult == m_gemNames.begin()) + { + // HandleResults will remove the gem upon cancelling + PythonBindingsInterface::Get()->CancelDownload(); + } + else + { + m_gemNames.erase(findResult); + } + } + } + void DownloadController::UpdateUIProgress(int progress) { m_lastProgress = progress; @@ -75,10 +95,4 @@ namespace O3DE::ProjectManager m_workerThread.wait(); } } - - void DownloadController::HandleCancel() - { - m_workerThread.quit(); - emit Done(false); - } } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/DownloadController.h b/Code/Tools/ProjectManager/Source/DownloadController.h index 608d9b1a2b..11ceaacddb 100644 --- a/Code/Tools/ProjectManager/Source/DownloadController.h +++ b/Code/Tools/ProjectManager/Source/DownloadController.h @@ -27,7 +27,8 @@ namespace O3DE::ProjectManager explicit DownloadController(QWidget* parent = nullptr); ~DownloadController(); - void AddGemDownload(const QString& m_gemName); + void AddGemDownload(const QString& gemName); + void CancelGemDownload(const QString& gemName); bool IsDownloadQueueEmpty() { @@ -54,7 +55,6 @@ namespace O3DE::ProjectManager public slots: void UpdateUIProgress(int progress); void HandleResults(const QString& result); - void HandleCancel(); signals: void StartGemDownload(const QString& gemName); diff --git a/Code/Tools/ProjectManager/Source/EngineSettingsScreen.cpp b/Code/Tools/ProjectManager/Source/EngineSettingsScreen.cpp index dec1c9a257..c7df00f423 100644 --- a/Code/Tools/ProjectManager/Source/EngineSettingsScreen.cpp +++ b/Code/Tools/ProjectManager/Source/EngineSettingsScreen.cpp @@ -11,19 +11,28 @@ #include #include #include +#include #include #include #include #include +#include namespace O3DE::ProjectManager { EngineSettingsScreen::EngineSettingsScreen(QWidget* parent) : ScreenWidget(parent) { - auto* layout = new QVBoxLayout(); + QScrollArea* scrollArea = new QScrollArea(this); + scrollArea->setWidgetResizable(true); + + QWidget* scrollWidget = new QWidget(this); + scrollArea->setWidget(scrollWidget); + + QVBoxLayout* layout = new QVBoxLayout(scrollWidget); layout->setAlignment(Qt::AlignTop); + scrollWidget->setLayout(layout); setObjectName("engineSettingsScreen"); @@ -39,9 +48,18 @@ namespace O3DE::ProjectManager formTitleLabel->setObjectName("formTitleLabel"); layout->addWidget(formTitleLabel); - m_engineVersion = new FormLineEditWidget(tr("Engine Version"), engineInfo.m_version, this); - m_engineVersion->lineEdit()->setReadOnly(true); - layout->addWidget(m_engineVersion); + FormLineEditWidget* engineName = new FormLineEditWidget(tr("Engine Name"), engineInfo.m_name, this); + engineName->lineEdit()->setReadOnly(true); + layout->addWidget(engineName); + + FormLineEditWidget* engineVersion = new FormLineEditWidget(tr("Engine Version"), engineInfo.m_version, this); + engineVersion->lineEdit()->setReadOnly(true); + layout->addWidget(engineVersion); + + FormBrowseEditWidget* engineFolder = new FormBrowseEditWidget(tr("Engine Folder"), engineInfo.m_path, this); + engineFolder->lineEdit()->setReadOnly(true); + connect( engineFolder, &FormBrowseEditWidget::OnBrowse, [engineInfo]{ AzQtComponents::ShowFileOnDesktop(engineInfo.m_path); }); + layout->addWidget(engineFolder); m_thirdParty = new FormFolderBrowseEditWidget(tr("3rd Party Software Folder"), engineInfo.m_thirdPartyPath, this); m_thirdParty->lineEdit()->setValidator(new PathValidator(PathValidator::PathMode::ExistingFolder, this)); @@ -71,7 +89,11 @@ namespace O3DE::ProjectManager connect(m_defaultProjectTemplates->lineEdit(), &QLineEdit::textChanged, this, &EngineSettingsScreen::OnTextChanged); layout->addWidget(m_defaultProjectTemplates); - setLayout(layout); + QVBoxLayout* mainLayout = new QVBoxLayout(); + mainLayout->setAlignment(Qt::AlignTop); + mainLayout->setMargin(0); + mainLayout->addWidget(scrollArea); + setLayout(mainLayout); } ProjectManagerScreen EngineSettingsScreen::GetScreenEnum() diff --git a/Code/Tools/ProjectManager/Source/EngineSettingsScreen.h b/Code/Tools/ProjectManager/Source/EngineSettingsScreen.h index 2f16400405..1efabd4b5e 100644 --- a/Code/Tools/ProjectManager/Source/EngineSettingsScreen.h +++ b/Code/Tools/ProjectManager/Source/EngineSettingsScreen.h @@ -29,7 +29,6 @@ namespace O3DE::ProjectManager void OnTextChanged(); private: - FormLineEditWidget* m_engineVersion; FormBrowseEditWidget* m_thirdParty; FormBrowseEditWidget* m_defaultProjects; FormBrowseEditWidget* m_defaultGems; diff --git a/Code/Tools/ProjectManager/Source/FormBrowseEditWidget.cpp b/Code/Tools/ProjectManager/Source/FormBrowseEditWidget.cpp index 8cd28fcbb4..fe101cf37a 100644 --- a/Code/Tools/ProjectManager/Source/FormBrowseEditWidget.cpp +++ b/Code/Tools/ProjectManager/Source/FormBrowseEditWidget.cpp @@ -20,7 +20,8 @@ namespace O3DE::ProjectManager setObjectName("formBrowseEditWidget"); QPushButton* browseButton = new QPushButton(this); - connect(browseButton, &QPushButton::pressed, this, &FormBrowseEditWidget::HandleBrowseButton); + connect( browseButton, &QPushButton::pressed, [this]{ emit OnBrowse(); }); + connect( this, &FormBrowseEditWidget::OnBrowse, this, &FormBrowseEditWidget::HandleBrowseButton); m_frameLayout->addWidget(browseButton); } @@ -34,7 +35,7 @@ namespace O3DE::ProjectManager int key = event->key(); if (key == Qt::Key_Return || key == Qt::Key_Enter) { - HandleBrowseButton(); + emit OnBrowse(); } } diff --git a/Code/Tools/ProjectManager/Source/FormBrowseEditWidget.h b/Code/Tools/ProjectManager/Source/FormBrowseEditWidget.h index a1f6948ce9..179fe03253 100644 --- a/Code/Tools/ProjectManager/Source/FormBrowseEditWidget.h +++ b/Code/Tools/ProjectManager/Source/FormBrowseEditWidget.h @@ -24,10 +24,13 @@ namespace O3DE::ProjectManager explicit FormBrowseEditWidget(const QString& labelText = "", QWidget* parent = nullptr); ~FormBrowseEditWidget() = default; + signals: + void OnBrowse(); + protected: void keyPressEvent(QKeyEvent* event) override; protected slots: - virtual void HandleBrowseButton() = 0; + virtual void HandleBrowseButton() {}; }; } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogHeaderWidget.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogHeaderWidget.cpp index 5da163bafe..5d65c740af 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogHeaderWidget.cpp +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogHeaderWidget.cpp @@ -155,6 +155,11 @@ namespace O3DE::ProjectManager update(); } + void CartOverlayWidget::OnCancelDownloadActivated(const QString& gemName) + { + m_downloadController->CancelGemDownload(gemName); + } + void CartOverlayWidget::CreateDownloadSection() { QWidget* widget = new QWidget(); @@ -235,7 +240,9 @@ namespace O3DE::ProjectManager nameProgressLayout->addWidget(progress); QSpacerItem* spacer = new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Minimum); nameProgressLayout->addSpacerItem(spacer); - QLabel* cancelText = new QLabel(tr("Cancel")); + QLabel* cancelText = new QLabel(QString("Cancel").arg(downloadQueue[downloadingGemNumber])); + cancelText->setTextInteractionFlags(Qt::LinksAccessibleByMouse); + connect(cancelText, &QLabel::linkActivated, this, &CartOverlayWidget::OnCancelDownloadActivated); nameProgressLayout->addWidget(cancelText); downloadingItemLayout->addLayout(nameProgressLayout); QProgressBar* downloadProgessBar = new QProgressBar(); diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogHeaderWidget.h b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogHeaderWidget.h index 19adec5607..4d17259840 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogHeaderWidget.h +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogHeaderWidget.h @@ -41,6 +41,7 @@ namespace O3DE::ProjectManager using GetTagIndicesCallback = AZStd::function()>; void CreateGemSection(const QString& singularTitle, const QString& pluralTitle, GetTagIndicesCallback getTagIndices); void CreateDownloadSection(); + void OnCancelDownloadActivated(const QString& link); QVBoxLayout* m_layout = nullptr; GemModel* m_gemModel = nullptr; diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp index b145363460..98121d7cd2 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp @@ -42,7 +42,9 @@ namespace O3DE::ProjectManager m_headerWidget = new GemCatalogHeaderWidget(m_gemModel, m_proxModel, m_downloadController); vLayout->addWidget(m_headerWidget); + connect(m_gemModel, &GemModel::gemStatusChanged, this, &GemCatalogScreen::OnGemStatusChanged); connect(m_headerWidget, &GemCatalogHeaderWidget::OpenGemsRepo, this, &GemCatalogScreen::HandleOpenGemRepo); + connect(m_headerWidget, &GemCatalogHeaderWidget::AddGem, this, &GemCatalogScreen::OnAddGemClicked); QHBoxLayout* hLayout = new QHBoxLayout(); hLayout->setMargin(0); @@ -73,11 +75,12 @@ namespace O3DE::ProjectManager m_notificationsView = AZStd::make_unique(this, AZ_CRC("GemCatalogNotificationsView")); m_notificationsView->SetOffset(QPoint(10, 70)); + m_notificationsView->SetMaxQueuedNotifications(1); } void GemCatalogScreen::ReinitForProject(const QString& projectPath) { - m_gemModel->clear(); + m_gemModel->Clear(); m_gemsToRegisterWithProject.clear(); FillModel(projectPath); @@ -94,48 +97,6 @@ namespace O3DE::ProjectManager m_headerWidget->ReinitForProject(); connect(m_gemModel, &GemModel::dataChanged, m_filterWidget, &GemFilterWidget::ResetGemStatusFilter); - connect(m_gemModel, &GemModel::gemStatusChanged, this, &GemCatalogScreen::OnGemStatusChanged); - connect( - m_headerWidget, &GemCatalogHeaderWidget::AddGem, - [&]() - { - EngineInfo engineInfo; - QString defaultPath; - - AZ::Outcome engineInfoResult = PythonBindingsInterface::Get()->GetEngineInfo(); - if (engineInfoResult.IsSuccess()) - { - engineInfo = engineInfoResult.GetValue(); - defaultPath = engineInfo.m_defaultGemsFolder; - } - - if (defaultPath.isEmpty()) - { - defaultPath = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); - } - - QString directory = QDir::toNativeSeparators(QFileDialog::getExistingDirectory(this, tr("Browse"), defaultPath)); - if (!directory.isEmpty()) - { - // register the gem to the o3de_manifest.json and to the project after the user confirms - // project creation/update - auto registerResult = PythonBindingsInterface::Get()->RegisterGem(directory); - if(!registerResult) - { - QMessageBox::critical(this, tr("Failed to add gem"), registerResult.GetError().c_str()); - } - else - { - m_gemsToRegisterWithProject.insert(directory); - AZ::Outcome gemInfoResult = PythonBindingsInterface::Get()->GetGemInfo(directory); - if (gemInfoResult) - { - m_gemModel->AddGem(gemInfoResult.GetValue()); - m_gemModel->UpdateGemDependencies(); - } - } - } - }); // Select the first entry after everything got correctly sized QTimer::singleShot(200, [=]{ @@ -144,10 +105,51 @@ namespace O3DE::ProjectManager }); } - void GemCatalogScreen::OnGemStatusChanged(const QModelIndex& modelIndex, uint32_t numChangedDependencies) + void GemCatalogScreen::OnAddGemClicked() + { + EngineInfo engineInfo; + QString defaultPath; + + AZ::Outcome engineInfoResult = PythonBindingsInterface::Get()->GetEngineInfo(); + if (engineInfoResult.IsSuccess()) + { + engineInfo = engineInfoResult.GetValue(); + defaultPath = engineInfo.m_defaultGemsFolder; + } + + if (defaultPath.isEmpty()) + { + defaultPath = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); + } + + QString directory = QDir::toNativeSeparators(QFileDialog::getExistingDirectory(this, tr("Browse"), defaultPath)); + if (!directory.isEmpty()) + { + // register the gem to the o3de_manifest.json and to the project after the user confirms + // project creation/update + auto registerResult = PythonBindingsInterface::Get()->RegisterGem(directory); + if(!registerResult) + { + QMessageBox::critical(this, tr("Failed to add gem"), registerResult.GetError().c_str()); + } + else + { + m_gemsToRegisterWithProject.insert(directory); + AZ::Outcome gemInfoResult = PythonBindingsInterface::Get()->GetGemInfo(directory); + if (gemInfoResult) + { + m_gemModel->AddGem(gemInfoResult.GetValue()); + m_gemModel->UpdateGemDependencies(); + } + } + } + } + + void GemCatalogScreen::OnGemStatusChanged(const QString& gemName, uint32_t numChangedDependencies) { if (m_notificationsEnabled) { + QModelIndex modelIndex = m_gemModel->FindIndexByNameString(gemName); bool added = GemModel::IsAdded(modelIndex); bool dependency = GemModel::IsAddedDependency(modelIndex); @@ -166,6 +168,10 @@ namespace O3DE::ProjectManager { notification += " " + tr("and") + " "; } + if (added && GemModel::GetDownloadStatus(modelIndex) == GemInfo::DownloadStatus::NotDownloaded) + { + m_downloadController->AddGemDownload(GemModel::GetName(modelIndex)); + } } if (numChangedDependencies == 1 ) @@ -174,7 +180,7 @@ namespace O3DE::ProjectManager } else if (numChangedDependencies > 1) { - notification += QString("%d Gem ").arg(numChangedDependencies) + tr("dependencies"); + notification += QString("%1 Gem ").arg(numChangedDependencies) + tr("dependencies"); } notification += " " + (added ? tr("activated") : tr("deactivated")); @@ -222,6 +228,24 @@ namespace O3DE::ProjectManager m_gemModel->AddGem(gemInfo); } + AZ::Outcome, AZStd::string> allRepoGemInfosResult = PythonBindingsInterface::Get()->GetAllGemRepoGemsInfos(); + if (allRepoGemInfosResult.IsSuccess()) + { + const QVector allRepoGemInfos = allRepoGemInfosResult.GetValue(); + for (const GemInfo& gemInfo : allRepoGemInfos) + { + // do not add gems that have already been downloaded + if (!m_gemModel->FindIndexByNameString(gemInfo.m_name).isValid()) + { + m_gemModel->AddGem(gemInfo); + } + } + } + else + { + QMessageBox::critical(nullptr, tr("Operation failed"), QString("Cannot retrieve gems from repos.

Error:
%1").arg(allRepoGemInfosResult.GetError().c_str())); + } + m_gemModel->UpdateGemDependencies(); m_notificationsEnabled = false; @@ -238,7 +262,8 @@ namespace O3DE::ProjectManager GemModel::SetWasPreviouslyAdded(*m_gemModel, modelIndex, true); GemModel::SetIsAdded(*m_gemModel, modelIndex, true); } - else + // ${Name} is a special name used in templates and is not really an error + else if (enabledGemName != "${Name}") { AZ_Warning("ProjectManager::GemCatalog", false, "Cannot find entry for gem with name '%s'. The CMake target name probably does not match the specified name in the gem.json.", @@ -248,14 +273,14 @@ namespace O3DE::ProjectManager } else { - QMessageBox::critical(nullptr, tr("Operation failed"), QString("Cannot retrieve enabled gems for project %1.\n\nError:\n%2").arg(projectPath, enabledGemNamesResult.GetError().c_str())); + QMessageBox::critical(nullptr, tr("Operation failed"), QString("Cannot retrieve enabled gems for project %1.

Error:
%2").arg(projectPath, enabledGemNamesResult.GetError().c_str())); } m_notificationsEnabled = true; } else { - QMessageBox::critical(nullptr, tr("Operation failed"), QString("Cannot retrieve gems for %1.\n\nError:\n%2").arg(projectPath, allGemInfosResult.GetError().c_str())); + QMessageBox::critical(nullptr, tr("Operation failed"), QString("Cannot retrieve gems for %1.

Error:
%2").arg(projectPath, allGemInfosResult.GetError().c_str())); } } diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.h b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.h index 8e9f31c710..1b34019d1a 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.h +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.h @@ -46,7 +46,8 @@ namespace O3DE::ProjectManager DownloadController* GetDownloadController() const { return m_downloadController; } public slots: - void OnGemStatusChanged(const QModelIndex& modelIndex, uint32_t numChangedDependencies); + void OnGemStatusChanged(const QString& gemName, uint32_t numChangedDependencies); + void OnAddGemClicked(); protected: void hideEvent(QHideEvent* event) override; diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemFilterWidget.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemFilterWidget.cpp index b425c15dee..4f737d8629 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemFilterWidget.cpp +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemFilterWidget.cpp @@ -225,21 +225,22 @@ namespace O3DE::ProjectManager QVector elementNames; QVector elementCounts; const int totalGems = m_gemModel->rowCount(); - const int selectedGemTotal = m_gemModel->TotalAddedGems(); + const int selectedGemTotal = m_gemModel->GatherGemsToBeAdded(/*includeDependencies=*/true).size(); + const int unselectedGemTotal = m_gemModel->GatherGemsToBeRemoved(/*includeDependencies=*/true).size(); const int enabledGemTotal = m_gemModel->TotalAddedGems(/*includeDependencies=*/true); - elementNames.push_back(GemSortFilterProxyModel::GetGemSelectedString(GemSortFilterProxyModel::GemSelected::Unselected)); - elementCounts.push_back(totalGems - selectedGemTotal); - elementNames.push_back(GemSortFilterProxyModel::GetGemSelectedString(GemSortFilterProxyModel::GemSelected::Selected)); elementCounts.push_back(selectedGemTotal); - elementNames.push_back(GemSortFilterProxyModel::GetGemActiveString(GemSortFilterProxyModel::GemActive::Inactive)); - elementCounts.push_back(totalGems - enabledGemTotal); + elementNames.push_back(GemSortFilterProxyModel::GetGemSelectedString(GemSortFilterProxyModel::GemSelected::Unselected)); + elementCounts.push_back(unselectedGemTotal); elementNames.push_back(GemSortFilterProxyModel::GetGemActiveString(GemSortFilterProxyModel::GemActive::Active)); elementCounts.push_back(enabledGemTotal); + elementNames.push_back(GemSortFilterProxyModel::GetGemActiveString(GemSortFilterProxyModel::GemActive::Inactive)); + elementCounts.push_back(totalGems - enabledGemTotal); + bool wasCollapsed = false; if (m_statusFilter) { @@ -262,44 +263,51 @@ namespace O3DE::ProjectManager const QList buttons = m_statusFilter->GetButtonGroup()->buttons(); - QAbstractButton* unselectedButton = buttons[0]; - QAbstractButton* selectedButton = buttons[1]; - unselectedButton->setChecked(m_filterProxyModel->GetGemSelected() == GemSortFilterProxyModel::GemSelected::Unselected); - selectedButton->setChecked(m_filterProxyModel->GetGemSelected() == GemSortFilterProxyModel::GemSelected::Selected); + QAbstractButton* selectedButton = buttons[0]; + QAbstractButton* unselectedButton = buttons[1]; + selectedButton->setChecked(m_filterProxyModel->GetGemSelected() == GemSortFilterProxyModel::GemSelected::Selected); + unselectedButton->setChecked(m_filterProxyModel->GetGemSelected() == GemSortFilterProxyModel::GemSelected::Unselected); auto updateGemSelection = [=]([[maybe_unused]] bool checked) { - if (unselectedButton->isChecked() && !selectedButton->isChecked()) - { - m_filterProxyModel->SetGemSelected(GemSortFilterProxyModel::GemSelected::Unselected); - } - else if (!unselectedButton->isChecked() && selectedButton->isChecked()) + if (!unselectedButton->isChecked() && selectedButton->isChecked()) { m_filterProxyModel->SetGemSelected(GemSortFilterProxyModel::GemSelected::Selected); } + else if (unselectedButton->isChecked() && !selectedButton->isChecked()) + { + m_filterProxyModel->SetGemSelected(GemSortFilterProxyModel::GemSelected::Unselected); + } else { - m_filterProxyModel->SetGemSelected(GemSortFilterProxyModel::GemSelected::NoFilter); + if (unselectedButton->isChecked() && selectedButton->isChecked()) + { + m_filterProxyModel->SetGemSelected(GemSortFilterProxyModel::GemSelected::Both); + } + else + { + m_filterProxyModel->SetGemSelected(GemSortFilterProxyModel::GemSelected::NoFilter); + } } }; connect(unselectedButton, &QAbstractButton::toggled, this, updateGemSelection); connect(selectedButton, &QAbstractButton::toggled, this, updateGemSelection); - QAbstractButton* inactiveButton = buttons[2]; - QAbstractButton* activeButton = buttons[3]; - inactiveButton->setChecked(m_filterProxyModel->GetGemActive() == GemSortFilterProxyModel::GemActive::Inactive); - activeButton->setChecked(m_filterProxyModel->GetGemActive() == GemSortFilterProxyModel::GemActive::Active); + QAbstractButton* activeButton = buttons[2]; + QAbstractButton* inactiveButton = buttons[3]; + activeButton->setChecked(m_filterProxyModel->GetGemActive() == GemSortFilterProxyModel::GemActive::Active); + inactiveButton->setChecked(m_filterProxyModel->GetGemActive() == GemSortFilterProxyModel::GemActive::Inactive); auto updateGemActive = [=]([[maybe_unused]] bool checked) { - if (inactiveButton->isChecked() && !activeButton->isChecked()) - { - m_filterProxyModel->SetGemActive(GemSortFilterProxyModel::GemActive::Inactive); - } - else if (!inactiveButton->isChecked() && activeButton->isChecked()) + if (!inactiveButton->isChecked() && activeButton->isChecked()) { m_filterProxyModel->SetGemActive(GemSortFilterProxyModel::GemActive::Active); } + else if (inactiveButton->isChecked() && !activeButton->isChecked()) + { + m_filterProxyModel->SetGemActive(GemSortFilterProxyModel::GemActive::Inactive); + } else { m_filterProxyModel->SetGemActive(GemSortFilterProxyModel::GemActive::NoFilter); diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.cpp index acdef483ae..fb228c0b4a 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.cpp +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.cpp @@ -27,6 +27,14 @@ namespace O3DE::ProjectManager void GemModel::AddGem(const GemInfo& gemInfo) { + if (FindIndexByNameString(gemInfo.m_name).isValid()) + { + // do not add gems with duplicate names + // this can happen by mistake or when a gem repo has a gem with the same name as a local gem + AZ_TracePrintf("GemModel", "Ignoring duplicate gem: %s", gemInfo.m_name.toUtf8().constData()); + return; + } + QStandardItem* item = new QStandardItem(); item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable); @@ -60,6 +68,7 @@ namespace O3DE::ProjectManager void GemModel::Clear() { clear(); + m_nameToIndexMap.clear(); } void GemModel::UpdateGemDependencies() @@ -276,9 +285,11 @@ namespace O3DE::ProjectManager void GemModel::SetIsAdded(QAbstractItemModel& model, const QModelIndex& modelIndex, bool isAdded) { + // get the gemName first, because the modelIndex data change after adding because of filters + QString gemName = modelIndex.data(RoleName).toString(); model.setData(modelIndex, isAdded, RoleIsAdded); - UpdateDependencies(model, modelIndex); + UpdateDependencies(model, gemName, isAdded); } bool GemModel::HasDependentGems(const QModelIndex& modelIndex) const @@ -294,15 +305,17 @@ namespace O3DE::ProjectManager return false; } - void GemModel::UpdateDependencies(QAbstractItemModel& model, const QModelIndex& modelIndex) + void GemModel::UpdateDependencies(QAbstractItemModel& model, const QString& gemName, bool isAdded) { GemModel* gemModel = GetSourceModel(&model); AZ_Assert(gemModel, "Failed to obtain GemModel"); + QModelIndex modelIndex = gemModel->FindIndexByNameString(gemName); + QVector dependencies = gemModel->GatherGemDependencies(modelIndex); uint32_t numChangedDependencies = 0; - if (IsAdded(modelIndex)) + if (isAdded) { for (const QModelIndex& dependency : dependencies) { @@ -324,7 +337,7 @@ namespace O3DE::ProjectManager bool hasDependentGems = gemModel->HasDependentGems(modelIndex); if (IsAddedDependency(modelIndex) != hasDependentGems) { - SetIsAddedDependency(model, modelIndex, hasDependentGems); + SetIsAddedDependency(*gemModel, modelIndex, hasDependentGems); } for (const QModelIndex& dependency : dependencies) @@ -343,7 +356,7 @@ namespace O3DE::ProjectManager } } - gemModel->emit gemStatusChanged(modelIndex, numChangedDependencies); + gemModel->emit gemStatusChanged(gemName, numChangedDependencies); } void GemModel::SetIsAddedDependency(QAbstractItemModel& model, const QModelIndex& modelIndex, bool isAdded) diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.h b/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.h index 938543eb39..35231cc105 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.h +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.h @@ -64,7 +64,7 @@ namespace O3DE::ProjectManager static bool NeedsToBeAdded(const QModelIndex& modelIndex, bool includeDependencies = false); static bool NeedsToBeRemoved(const QModelIndex& modelIndex, bool includeDependencies = false); static bool HasRequirement(const QModelIndex& modelIndex); - static void UpdateDependencies(QAbstractItemModel& model, const QModelIndex& modelIndex); + static void UpdateDependencies(QAbstractItemModel& model, const QString& gemName, bool isAdded); static void SetDownloadStatus(QAbstractItemModel& model, const QModelIndex& modelIndex, GemInfo::DownloadStatus status); bool DoGemsToBeAddedHaveRequirements() const; @@ -78,7 +78,7 @@ namespace O3DE::ProjectManager int TotalAddedGems(bool includeDependencies = false) const; signals: - void gemStatusChanged(const QModelIndex& modelIndex, uint32_t numChangedDependencies); + void gemStatusChanged(const QString& gemName, uint32_t numChangedDependencies); private: void FindGemDisplayNamesByNameStrings(QStringList& inOutGemNames); diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemSortFilterProxyModel.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemSortFilterProxyModel.cpp index 199692f200..7ec45ac721 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemSortFilterProxyModel.cpp +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemSortFilterProxyModel.cpp @@ -50,11 +50,26 @@ namespace O3DE::ProjectManager } } - // Gem selected - if (m_gemSelectedFilter != GemSelected::NoFilter) + // Gem selected + if (m_gemSelectedFilter == GemSelected::Selected) { - const GemSelected sourceGemStatus = static_cast(GemModel::IsAdded(sourceIndex)); - if (m_gemSelectedFilter != sourceGemStatus) + if (!GemModel::NeedsToBeAdded(sourceIndex, true)) + { + return false; + } + } + // Gem unselected + else if (m_gemSelectedFilter == GemSelected::Unselected) + { + if (!GemModel::NeedsToBeRemoved(sourceIndex, true)) + { + return false; + } + } + // Gem selected or unselected + else if (m_gemSelectedFilter == GemSelected::Both) + { + if (!GemModel::NeedsToBeAdded(sourceIndex, true) && !GemModel::NeedsToBeRemoved(sourceIndex, true)) { return false; } diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemSortFilterProxyModel.h b/Code/Tools/ProjectManager/Source/GemCatalog/GemSortFilterProxyModel.h index 74b1e915eb..ab739e62f9 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemSortFilterProxyModel.h +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemSortFilterProxyModel.h @@ -29,7 +29,8 @@ namespace O3DE::ProjectManager { NoFilter = -1, Unselected, - Selected + Selected, + Both }; enum class GemActive { diff --git a/Code/Tools/ProjectManager/Source/GemRepo/GemRepoAddDialog.cpp b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoAddDialog.cpp index 601c62d6e1..05a14b48d3 100644 --- a/Code/Tools/ProjectManager/Source/GemRepo/GemRepoAddDialog.cpp +++ b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoAddDialog.cpp @@ -27,6 +27,7 @@ namespace O3DE::ProjectManager QVBoxLayout* vLayout = new QVBoxLayout(); vLayout->setContentsMargins(30, 30, 25, 10); vLayout->setSpacing(0); + vLayout->setAlignment(Qt::AlignTop); setLayout(vLayout); QLabel* instructionTitleLabel = new QLabel(tr("Enter a valid path to add a new user repository")); @@ -41,9 +42,18 @@ namespace O3DE::ProjectManager vLayout->addWidget(instructionContextLabel); m_repoPath = new FormFolderBrowseEditWidget(tr("Repository Path"), "", this); - m_repoPath->setFixedWidth(600); + m_repoPath->setFixedSize(QSize(600, 100)); vLayout->addWidget(m_repoPath); + vLayout->addSpacing(10); + + QLabel* warningLabel = new QLabel(tr("Online repositories may contain files that could potentially harm your computer," + " please ensure you understand the risks before downloading Gems from third-party sources.")); + warningLabel->setObjectName("gemRepoAddDialogWarningLabel"); + warningLabel->setWordWrap(true); + warningLabel->setAlignment(Qt::AlignLeft); + vLayout->addWidget(warningLabel); + vLayout->addSpacing(40); QDialogButtonBox* dialogButtons = new QDialogButtonBox(); diff --git a/Code/Tools/ProjectManager/Source/GemRepo/GemRepoItemDelegate.cpp b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoItemDelegate.cpp index 8589337b87..fdfcf02155 100644 --- a/Code/Tools/ProjectManager/Source/GemRepo/GemRepoItemDelegate.cpp +++ b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoItemDelegate.cpp @@ -39,7 +39,6 @@ namespace O3DE::ProjectManager QRect fullRect, itemRect, contentRect; CalcRects(options, fullRect, itemRect, contentRect); - QRect buttonRect = CalcButtonRect(contentRect); QFont standardFont(options.font); standardFont.setPixelSize(static_cast(s_fontSize)); @@ -70,15 +69,12 @@ namespace O3DE::ProjectManager painter->restore(); } - // Repo enabled - DrawButton(painter, buttonRect, modelIndex); - // Repo name QString repoName = GemRepoModel::GetName(modelIndex); repoName = QFontMetrics(standardFont).elidedText(repoName, Qt::TextElideMode::ElideRight, s_nameMaxWidth); QRect repoNameRect = GetTextRect(standardFont, repoName, s_fontSize); - int currentHorizontalOffset = buttonRect.left() + s_buttonWidth + s_buttonSpacing; + int currentHorizontalOffset = contentRect.left(); repoNameRect.moveTo(currentHorizontalOffset, contentRect.center().y() - repoNameRect.height() / 2); repoNameRect = painter->boundingRect(repoNameRect, Qt::TextSingleLine, repoName); @@ -108,7 +104,7 @@ namespace O3DE::ProjectManager // Draw refresh button painter->drawPixmap( - repoUpdatedDateRect.left() + repoUpdatedDateRect.width() + s_refreshIconSpacing, + repoUpdatedDateRect.left() + s_updatedMaxWidth + s_refreshIconSpacing, contentRect.center().y() - s_refreshIconSize / 3, // Dividing size by 3 centers much better m_refreshIcon); @@ -126,7 +122,7 @@ namespace O3DE::ProjectManager initStyleOption(&options, modelIndex); int marginsHorizontal = s_itemMargins.left() + s_itemMargins.right() + s_contentMargins.left() + s_contentMargins.right(); - return QSize(marginsHorizontal + s_buttonWidth + s_buttonSpacing + s_nameMaxWidth + s_creatorMaxWidth + s_updatedMaxWidth + s_contentSpacing * 3, s_height); + return QSize(marginsHorizontal + s_nameMaxWidth + s_creatorMaxWidth + s_updatedMaxWidth + s_contentSpacing * 3, s_height); } bool GemRepoItemDelegate::editorEvent(QEvent* event, QAbstractItemModel* model, const QStyleOptionViewItem& option, const QModelIndex& modelIndex) @@ -139,17 +135,17 @@ namespace O3DE::ProjectManager if (event->type() == QEvent::KeyPress) { auto keyEvent = static_cast(event); - if (keyEvent->key() == Qt::Key_Space) - { - const bool isAdded = GemRepoModel::IsEnabled(modelIndex); - GemRepoModel::SetEnabled(*model, modelIndex, !isAdded); - return true; - } - else if (keyEvent->key() == Qt::Key_X) + + if (keyEvent->key() == Qt::Key_X) { emit RemoveRepo(modelIndex); return true; } + else if (keyEvent->key() == Qt::Key_R || keyEvent->key() == Qt::Key_F5) + { + emit RefreshRepo(modelIndex); + return true; + } } if (event->type() == QEvent::MouseButtonPress) @@ -158,20 +154,19 @@ namespace O3DE::ProjectManager QRect fullRect, itemRect, contentRect; CalcRects(option, fullRect, itemRect, contentRect); - const QRect buttonRect = CalcButtonRect(contentRect); const QRect deleteButtonRect = CalcDeleteButtonRect(contentRect); + const QRect refreshButtonRect = CalcRefreshButtonRect(contentRect); - if (buttonRect.contains(mouseEvent->pos())) - { - const bool isAdded = GemRepoModel::IsEnabled(modelIndex); - GemRepoModel::SetEnabled(*model, modelIndex, !isAdded); - return true; - } - else if (deleteButtonRect.contains(mouseEvent->pos())) + if (deleteButtonRect.contains(mouseEvent->pos())) { emit RemoveRepo(modelIndex); return true; } + else if (refreshButtonRect.contains(mouseEvent->pos())) + { + emit RefreshRepo(modelIndex); + return true; + } } return QStyledItemDelegate::editorEvent(event, model, option, modelIndex); @@ -190,47 +185,19 @@ namespace O3DE::ProjectManager return QFontMetrics(font).boundingRect(text); } - QRect GemRepoItemDelegate::CalcButtonRect(const QRect& contentRect) const - { - const QPoint topLeft = QPoint(contentRect.left(), contentRect.top() + contentRect.height() / 2 - s_buttonHeight / 2); - const QSize size = QSize(s_buttonWidth, s_buttonHeight); - return QRect(topLeft, size); - } - - void GemRepoItemDelegate::DrawButton(QPainter* painter, const QRect& buttonRect, const QModelIndex& modelIndex) const - { - painter->save(); - QPoint circleCenter; - - const bool isEnabled = GemRepoModel::IsEnabled(modelIndex); - if (isEnabled) - { - painter->setBrush(m_buttonEnabledColor); - painter->setPen(m_buttonEnabledColor); - - circleCenter = buttonRect.center() + QPoint(buttonRect.width() / 2 - s_buttonBorderRadius + 1, 1); - } - else - { - circleCenter = buttonRect.center() + QPoint(-buttonRect.width() / 2 + s_buttonBorderRadius + 1, 1); - } - - // Rounded rect - painter->drawRoundedRect(buttonRect, s_buttonBorderRadius, s_buttonBorderRadius); - - // Circle - painter->setBrush(m_textColor); - painter->drawEllipse(circleCenter, s_buttonCircleRadius, s_buttonCircleRadius); - - painter->restore(); - } - QRect GemRepoItemDelegate::CalcDeleteButtonRect(const QRect& contentRect) const { const QPoint topLeft = QPoint(contentRect.right() - s_iconSize, contentRect.center().y() - s_iconSize / 2); return QRect(topLeft, QSize(s_iconSize, s_iconSize)); } + QRect GemRepoItemDelegate::CalcRefreshButtonRect(const QRect& contentRect) const + { + const int topLeftX = contentRect.left() + s_nameMaxWidth + s_creatorMaxWidth + s_updatedMaxWidth + s_contentSpacing * 2 + s_refreshIconSpacing; + const QPoint topLeft = QPoint(topLeftX, contentRect.center().y() - s_refreshIconSize / 3); + return QRect(topLeft, QSize(s_refreshIconSize, s_refreshIconSize)); + } + void GemRepoItemDelegate::DrawEditButtons(QPainter* painter, const QRect& contentRect) const { painter->drawPixmap(contentRect.right() - s_iconSize, contentRect.center().y() - s_iconSize / 2, m_deleteIcon); diff --git a/Code/Tools/ProjectManager/Source/GemRepo/GemRepoItemDelegate.h b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoItemDelegate.h index 560616bf9d..69d943001d 100644 --- a/Code/Tools/ProjectManager/Source/GemRepo/GemRepoItemDelegate.h +++ b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoItemDelegate.h @@ -36,7 +36,6 @@ namespace O3DE::ProjectManager const QColor m_backgroundColor = QColor("#333333"); // Outside of the actual repo item const QColor m_itemBackgroundColor = QColor("#404040"); // Background color of the repo item const QColor m_borderColor = QColor("#1E70EB"); - const QColor m_buttonEnabledColor = QColor("#1E70EB"); // Item inline constexpr static int s_height = 72; // Repo item total height @@ -53,13 +52,6 @@ namespace O3DE::ProjectManager inline constexpr static int s_creatorMaxWidth = 115; inline constexpr static int s_updatedMaxWidth = 125; - // Button - inline constexpr static int s_buttonWidth = 32; - inline constexpr static int s_buttonHeight = 16; - inline constexpr static int s_buttonBorderRadius = 8; - inline constexpr static int s_buttonCircleRadius = s_buttonBorderRadius - 2; - inline constexpr static int s_buttonSpacing = 20; - // Icon inline constexpr static int s_iconSize = 24; inline constexpr static int s_iconSpacing = 16; @@ -68,13 +60,14 @@ namespace O3DE::ProjectManager signals: void RemoveRepo(const QModelIndex& modelIndex); + void RefreshRepo(const QModelIndex& modelIndex); protected: void CalcRects(const QStyleOptionViewItem& option, QRect& outFullRect, QRect& outItemRect, QRect& outContentRect) const; QRect GetTextRect(QFont& font, const QString& text, qreal fontSize) const; QRect CalcButtonRect(const QRect& contentRect) const; QRect CalcDeleteButtonRect(const QRect& contentRect) const; - void DrawButton(QPainter* painter, const QRect& contentRect, const QModelIndex& modelIndex) const; + QRect CalcRefreshButtonRect(const QRect& contentRect) const; void DrawEditButtons(QPainter* painter, const QRect& contentRect) const; QAbstractItemModel* m_model = nullptr; diff --git a/Code/Tools/ProjectManager/Source/GemRepo/GemRepoListView.cpp b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoListView.cpp index 53d6478954..9adf3e6e3f 100644 --- a/Code/Tools/ProjectManager/Source/GemRepo/GemRepoListView.cpp +++ b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoListView.cpp @@ -24,6 +24,7 @@ namespace O3DE::ProjectManager GemRepoItemDelegate* itemDelegate = new GemRepoItemDelegate(model, this); connect(itemDelegate, &GemRepoItemDelegate::RemoveRepo, this, &GemRepoListView::RemoveRepo); + connect(itemDelegate, &GemRepoItemDelegate::RefreshRepo, this, &GemRepoListView::RefreshRepo); setItemDelegate(itemDelegate); } } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/GemRepo/GemRepoListView.h b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoListView.h index be2af4b5a6..50bcf8daa6 100644 --- a/Code/Tools/ProjectManager/Source/GemRepo/GemRepoListView.h +++ b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoListView.h @@ -28,5 +28,6 @@ namespace O3DE::ProjectManager signals: void RemoveRepo(const QModelIndex& modelIndex); + void RefreshRepo(const QModelIndex& modelIndex); }; } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/GemRepo/GemRepoScreen.cpp b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoScreen.cpp index 82fa13505a..0ddfe41434 100644 --- a/Code/Tools/ProjectManager/Source/GemRepo/GemRepoScreen.cpp +++ b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoScreen.cpp @@ -126,6 +126,36 @@ namespace O3DE::ProjectManager } } + void GemRepoScreen::HandleRefreshAllButton() + { + bool refreshResult = PythonBindingsInterface::Get()->RefreshAllGemRepos(); + Reinit(); + + if (!refreshResult) + { + QMessageBox::critical( + this, tr("Operation failed"), QString("Some repos failed to refresh.")); + } + } + + void GemRepoScreen::HandleRefreshRepoButton(const QModelIndex& modelIndex) + { + const QString repoUri = m_gemRepoModel->GetRepoUri(modelIndex); + + AZ::Outcome refreshResult = PythonBindingsInterface::Get()->RefreshGemRepo(repoUri); + if (refreshResult.IsSuccess()) + { + Reinit(); + } + else + { + QMessageBox::critical( + this, tr("Operation failed"), + QString("Failed to refresh gem repo %1
Error:
%2") + .arg(m_gemRepoModel->GetName(modelIndex), refreshResult.GetError().c_str())); + } + } + void GemRepoScreen::FillModel() { AZ::Outcome, AZStd::string> allGemRepoInfosResult = PythonBindingsInterface::Get()->GetAllGemRepoInfos(); @@ -237,6 +267,8 @@ namespace O3DE::ProjectManager m_AllUpdateButton->setObjectName("gemRepoHeaderRefreshButton"); topMiddleHLayout->addWidget(m_AllUpdateButton); + connect(m_AllUpdateButton, &QPushButton::clicked, this, &GemRepoScreen::HandleRefreshAllButton); + topMiddleHLayout->addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Minimum)); QPushButton* addRepoButton = new QPushButton(tr("Add Repository"), this); @@ -257,29 +289,29 @@ namespace O3DE::ProjectManager m_gemRepoHeaderTable->setObjectName("gemRepoHeaderTable"); m_gemRepoListHeader = m_gemRepoHeaderTable->horizontalHeader(); m_gemRepoListHeader->setObjectName("gemRepoListHeader"); + m_gemRepoListHeader->setDefaultAlignment(Qt::AlignLeft); m_gemRepoListHeader->setSectionResizeMode(QHeaderView::ResizeMode::Fixed); // Insert columns so the header labels will show up m_gemRepoHeaderTable->insertColumn(0); m_gemRepoHeaderTable->insertColumn(1); m_gemRepoHeaderTable->insertColumn(2); - m_gemRepoHeaderTable->insertColumn(3); - m_gemRepoHeaderTable->setHorizontalHeaderLabels({ tr("Enabled"), tr("Repository Name"), tr("Creator"), tr("Updated") }); + m_gemRepoHeaderTable->setHorizontalHeaderLabels({ tr("Repository Name"), tr("Creator"), tr("Updated") }); - const int headerExtraMargin = 10; - m_gemRepoListHeader->resizeSection(0, GemRepoItemDelegate::s_buttonWidth + GemRepoItemDelegate::s_buttonSpacing - 3); - m_gemRepoListHeader->resizeSection(1, GemRepoItemDelegate::s_nameMaxWidth + GemRepoItemDelegate::s_contentSpacing - headerExtraMargin); - m_gemRepoListHeader->resizeSection(2, GemRepoItemDelegate::s_creatorMaxWidth + GemRepoItemDelegate::s_contentSpacing - headerExtraMargin); - m_gemRepoListHeader->resizeSection(3, GemRepoItemDelegate::s_updatedMaxWidth + GemRepoItemDelegate::s_contentSpacing - headerExtraMargin); + const int headerExtraMargin = 18; + m_gemRepoListHeader->resizeSection(0, GemRepoItemDelegate::s_nameMaxWidth + GemRepoItemDelegate::s_contentSpacing + headerExtraMargin); + m_gemRepoListHeader->resizeSection(1, GemRepoItemDelegate::s_creatorMaxWidth + GemRepoItemDelegate::s_contentSpacing); + m_gemRepoListHeader->resizeSection(2, GemRepoItemDelegate::s_updatedMaxWidth + GemRepoItemDelegate::s_contentSpacing); // Required to set stylesheet in code as it will not be respected if set in qss - m_gemRepoHeaderTable->horizontalHeader()->setStyleSheet("QHeaderView::section { background-color:transparent; color:white; font-size:12px; text-align:left; border-style:none; }"); + m_gemRepoHeaderTable->horizontalHeader()->setStyleSheet("QHeaderView::section { background-color:transparent; color:white; font-size:12px; border-style:none; }"); middleVLayout->addWidget(m_gemRepoHeaderTable); m_gemRepoListView = new GemRepoListView(m_gemRepoModel, m_gemRepoModel->GetSelectionModel(), this); middleVLayout->addWidget(m_gemRepoListView); connect(m_gemRepoListView, &GemRepoListView::RemoveRepo, this, &GemRepoScreen::HandleRemoveRepoButton); + connect(m_gemRepoListView, &GemRepoListView::RefreshRepo, this, &GemRepoScreen::HandleRefreshRepoButton); hLayout->addLayout(middleVLayout); diff --git a/Code/Tools/ProjectManager/Source/GemRepo/GemRepoScreen.h b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoScreen.h index 3ac89f6813..46a733362a 100644 --- a/Code/Tools/ProjectManager/Source/GemRepo/GemRepoScreen.h +++ b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoScreen.h @@ -40,6 +40,8 @@ namespace O3DE::ProjectManager public slots: void HandleAddRepoButton(); void HandleRemoveRepoButton(const QModelIndex& modelIndex); + void HandleRefreshAllButton(); + void HandleRefreshRepoButton(const QModelIndex& modelIndex); private: void FillModel(); diff --git a/Code/Tools/ProjectManager/Source/ProjectUtils.h b/Code/Tools/ProjectManager/Source/ProjectUtils.h index 1fdf76913e..890d50d2de 100644 --- a/Code/Tools/ProjectManager/Source/ProjectUtils.h +++ b/Code/Tools/ProjectManager/Source/ProjectUtils.h @@ -14,6 +14,7 @@ #include #include +#include #include namespace O3DE::ProjectManager @@ -67,7 +68,8 @@ namespace O3DE::ProjectManager AZ::Outcome GetProjectBuildPath(const QString& projectPath); AZ::Outcome OpenCMakeGUI(const QString& projectPath); AZ::Outcome RunGetPythonScript(const QString& enginePath); - + + AZ::IO::FixedMaxPath GetEditorDirectory(); } // namespace ProjectUtils } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/ProjectsScreen.cpp b/Code/Tools/ProjectManager/Source/ProjectsScreen.cpp index cf42da88fa..f86a689e59 100644 --- a/Code/Tools/ProjectManager/Source/ProjectsScreen.cpp +++ b/Code/Tools/ProjectManager/Source/ProjectsScreen.cpp @@ -392,11 +392,11 @@ namespace O3DE::ProjectManager { if (!WarnIfInBuildQueue(projectPath)) { - AZ::IO::FixedMaxPath executableDirectory = AZ::Utils::GetExecutableDirectory(); + AZ::IO::FixedMaxPath executableDirectory = ProjectUtils::GetEditorDirectory(); AZStd::string executableFilename = "Editor"; AZ::IO::FixedMaxPath editorExecutablePath = executableDirectory / (executableFilename + AZ_TRAIT_OS_EXECUTABLE_EXTENSION); auto cmdPath = AZ::IO::FixedMaxPathString::format( - "%s -regset=\"/Amazon/AzCore/Bootstrap/project_path=%s\"", editorExecutablePath.c_str(), + "%s --regset=\"/Amazon/AzCore/Bootstrap/project_path=%s\"", editorExecutablePath.c_str(), projectPath.toStdString().c_str()); AzFramework::ProcessLauncher::ProcessLaunchInfo processLaunchInfo; diff --git a/Code/Tools/ProjectManager/Source/PythonBindings.cpp b/Code/Tools/ProjectManager/Source/PythonBindings.cpp index f001195e85..740393fec0 100644 --- a/Code/Tools/ProjectManager/Source/PythonBindings.cpp +++ b/Code/Tools/ProjectManager/Source/PythonBindings.cpp @@ -223,6 +223,7 @@ namespace RedirectOutput } } // namespace RedirectOutput + namespace O3DE::ProjectManager { PythonBindings::PythonBindings(const AZ::IO::PathView& enginePath) @@ -302,6 +303,7 @@ namespace O3DE::ProjectManager m_disableGemProject = pybind11::module::import("o3de.disable_gem"); m_editProjectProperties = pybind11::module::import("o3de.project_properties"); m_download = pybind11::module::import("o3de.download"); + m_repo = pybind11::module::import("o3de.repo"); m_pathlib = pybind11::module::import("pathlib"); // make sure the engine is registered @@ -981,6 +983,46 @@ namespace O3DE::ProjectManager } } + AZ::Outcome PythonBindings::RefreshGemRepo(const QString& repoUri) + { + bool refreshResult = false; + AZ::Outcome result = ExecuteWithLockErrorHandling( + [&] + { + auto pyUri = QString_To_Py_String(repoUri); + auto pythonRefreshResult = m_repo.attr("refresh_repo")(pyUri); + + // Returns an exit code so boolify it then invert result + refreshResult = !pythonRefreshResult.cast(); + }); + + if (!result.IsSuccess()) + { + return result; + } + else if (!refreshResult) + { + return AZ::Failure("Failed to refresh repo."); + } + + return AZ::Success(); + } + + bool PythonBindings::RefreshAllGemRepos() + { + bool refreshResult = false; + bool result = ExecuteWithLock( + [&] + { + auto pythonRefreshResult = m_repo.attr("refresh_repos")(); + + // Returns an exit code so boolify it then invert result + refreshResult = !pythonRefreshResult.cast(); + }); + + return result && refreshResult; + } + bool PythonBindings::AddGemRepo(const QString& repoUri) { bool registrationResult = false; @@ -1120,18 +1162,29 @@ namespace O3DE::ProjectManager AZ::Outcome PythonBindings::DownloadGem(const QString& gemName, std::function gemProgressCallback) { + // This process is currently limited to download a single gem at a time. bool downloadSucceeded = false; + + m_requestCancelDownload = false; auto result = ExecuteWithLockErrorHandling( [&] { auto downloadResult = m_download.attr("download_gem")( QString_To_Py_String(gemName), // gem name pybind11::none(), // destination path - false// skip auto register + false, // skip auto register + pybind11::cpp_function( + [this, gemProgressCallback](int progress) + { + gemProgressCallback(progress); + + return m_requestCancelDownload; + }) // Callback for download progress and cancelling ); downloadSucceeded = (downloadResult.cast() == 0); }); + if (!result.IsSuccess()) { return result; @@ -1143,4 +1196,36 @@ namespace O3DE::ProjectManager return AZ::Success(); } + + void PythonBindings::CancelDownload() + { + m_requestCancelDownload = true; + } + + AZ::Outcome, AZStd::string> PythonBindings::GetAllGemRepoGemsInfos() + { + QVector gemInfos; + AZ::Outcome result = ExecuteWithLockErrorHandling( + [&] + { + auto gemPaths = m_repo.attr("get_gem_json_paths_from_all_cached_repos")(); + + if (pybind11::isinstance(gemPaths)) + { + for (auto path : gemPaths) + { + GemInfo gemInfo = GemInfoFromPath(path, pybind11::none()); + gemInfo.m_downloadStatus = GemInfo::DownloadStatus::NotDownloaded; + gemInfos.push_back(gemInfo); + } + } + }); + + if (!result.IsSuccess()) + { + return AZ::Failure(result.GetError()); + } + + return AZ::Success(AZStd::move(gemInfos)); + } } diff --git a/Code/Tools/ProjectManager/Source/PythonBindings.h b/Code/Tools/ProjectManager/Source/PythonBindings.h index 9d2c8c850f..4375d56d02 100644 --- a/Code/Tools/ProjectManager/Source/PythonBindings.h +++ b/Code/Tools/ProjectManager/Source/PythonBindings.h @@ -59,10 +59,14 @@ namespace O3DE::ProjectManager AZ::Outcome> GetProjectTemplates(const QString& projectPath = {}) override; // Gem Repos + AZ::Outcome RefreshGemRepo(const QString& repoUri) override; + bool RefreshAllGemRepos() override; bool AddGemRepo(const QString& repoUri) override; bool RemoveGemRepo(const QString& repoUri) override; AZ::Outcome, AZStd::string> GetAllGemRepoInfos() override; AZ::Outcome DownloadGem(const QString& gemName, std::function gemProgressCallback) override; + void CancelDownload() override; + AZ::Outcome, AZStd::string> GetAllGemRepoGemsInfos() override; private: AZ_DISABLE_COPY_MOVE(PythonBindings); @@ -90,6 +94,9 @@ namespace O3DE::ProjectManager pybind11::handle m_disableGemProject; pybind11::handle m_editProjectProperties; pybind11::handle m_download; + pybind11::handle m_repo; pybind11::handle m_pathlib; + + bool m_requestCancelDownload = false; }; } diff --git a/Code/Tools/ProjectManager/Source/PythonBindingsInterface.h b/Code/Tools/ProjectManager/Source/PythonBindingsInterface.h index 6b9ac39213..1134804f1f 100644 --- a/Code/Tools/ProjectManager/Source/PythonBindingsInterface.h +++ b/Code/Tools/ProjectManager/Source/PythonBindingsInterface.h @@ -176,6 +176,19 @@ namespace O3DE::ProjectManager // Gem Repos + /** + * Refresh gem repo in the current engine. + * @param repoUri the absolute filesystem path or url to the gem repo. + * @return An outcome with the success flag as well as an error message in case of a failure. + */ + virtual AZ::Outcome RefreshGemRepo(const QString& repoUri) = 0; + + /** + * Refresh all gem repos in the current engine. + * @return true on success, false on failure. + */ + virtual bool RefreshAllGemRepos() = 0; + /** * Registers this gem repo with the current engine. * @param repoUri the absolute filesystem path or url to the gem repo. @@ -196,7 +209,24 @@ namespace O3DE::ProjectManager */ virtual AZ::Outcome, AZStd::string> GetAllGemRepoInfos() = 0; + /** + * Downloads and registers a Gem. + * @param gemName the name of the Gem to download + * @param gemProgressCallback a callback function that is called with an int percentage download value + * @return an outcome with a string error message on failure. + */ virtual AZ::Outcome DownloadGem(const QString& gemName, std::function gemProgressCallback) = 0; + + /** + * Cancels the current download. + */ + virtual void CancelDownload() = 0; + + /** + * Gathers all gem infos for all gems registered from repos. + * @return A list of gem infos. + */ + virtual AZ::Outcome, AZStd::string> GetAllGemRepoGemsInfos() = 0; }; using PythonBindingsInterface = AZ::Interface; diff --git a/Code/Tools/ProjectManager/Source/ScreensCtrl.cpp b/Code/Tools/ProjectManager/Source/ScreensCtrl.cpp index 314765def0..fd8fbc970a 100644 --- a/Code/Tools/ProjectManager/Source/ScreensCtrl.cpp +++ b/Code/Tools/ProjectManager/Source/ScreensCtrl.cpp @@ -133,6 +133,11 @@ namespace O3DE::ProjectManager return true; } + else + { + // If we are already on this screen still notify we are on this screen to refresh it + newScreen->NotifyCurrentScreen(); + } } return false; diff --git a/Code/Tools/ProjectManager/Source/UpdateProjectSettingsScreen.cpp b/Code/Tools/ProjectManager/Source/UpdateProjectSettingsScreen.cpp index 6f7f7e1bed..3bfc07c5b0 100644 --- a/Code/Tools/ProjectManager/Source/UpdateProjectSettingsScreen.cpp +++ b/Code/Tools/ProjectManager/Source/UpdateProjectSettingsScreen.cpp @@ -36,6 +36,7 @@ namespace O3DE::ProjectManager QLabel* projectPreviewLabel = new QLabel(tr("Select an image (PNG). Minimum %1 x %2 pixels.") .arg(QString::number(ProjectPreviewImageWidth), QString::number(ProjectPreviewImageHeight))); + projectPreviewLabel->setObjectName("projectPreviewLabel"); previewExtrasLayout->addWidget(projectPreviewLabel); m_projectPreviewImage = new QLabel(this); diff --git a/Code/Tools/SceneAPI/SceneCore/DllMain.cpp b/Code/Tools/SceneAPI/SceneCore/DllMain.cpp index ad875f5538..51f081d8fa 100644 --- a/Code/Tools/SceneAPI/SceneCore/DllMain.cpp +++ b/Code/Tools/SceneAPI/SceneCore/DllMain.cpp @@ -187,6 +187,7 @@ namespace AZ // Register utilities AZ::SceneAPI::SceneCore::PatternMatcher::Reflect(context); + AZ::SceneAPI::Utilities::DebugSceneGraph::Reflect(context); } } diff --git a/Code/Tools/SceneAPI/SceneCore/Utilities/DebugOutput.cpp b/Code/Tools/SceneAPI/SceneCore/Utilities/DebugOutput.cpp index de727498fc..41a030760e 100644 --- a/Code/Tools/SceneAPI/SceneCore/Utilities/DebugOutput.cpp +++ b/Code/Tools/SceneAPI/SceneCore/Utilities/DebugOutput.cpp @@ -10,6 +10,7 @@ #include #include +#include #include #include #include @@ -20,6 +21,36 @@ namespace AZ::SceneAPI::Utilities { + void DebugNode::Reflect(AZ::ReflectContext* context) + { + AZ::SerializeContext* serialize = azrtti_cast(context); + + if (serialize) + { + serialize->Class() + ->Field("Name", &DebugNode::m_name) + ->Field("Path", &DebugNode::m_path) + ->Field("Type", &DebugNode::m_type) + ->Field("Data", &DebugNode::m_data); + } + } + + void DebugSceneGraph::Reflect(AZ::ReflectContext* context) + { + DebugNode::Reflect(context); + + AZ::SerializeContext* serialize = azrtti_cast(context); + + if (serialize) + { + serialize->Class() + ->Field("Version", &DebugSceneGraph::m_version) + ->Field("ProductName", &DebugSceneGraph::m_productName) + ->Field("SceneName", &DebugSceneGraph::m_sceneName) + ->Field("Nodes", &DebugSceneGraph::m_nodes); + } + } + void DebugOutput::Write(const char* name, const char* data) { m_output += AZStd::string::format("\t%s: %s\n", name, data); @@ -38,21 +69,29 @@ namespace AZ::SceneAPI::Utilities void DebugOutput::Write(const char* name, const AZStd::string& data) { Write(name, data.c_str()); + + AddToNode(name, data); } void DebugOutput::Write(const char* name, double data) { m_output += AZStd::string::format("\t%s: %f\n", name, data); + + AddToNode(name, data); } void DebugOutput::Write(const char* name, uint64_t data) { m_output += AZStd::string::format("\t%s: %" PRIu64 "\n", name, data); + + AddToNode(name, data); } void DebugOutput::Write(const char* name, int64_t data) { m_output += AZStd::string::format("\t%s: %" PRId64 "\n", name, data); + + AddToNode(name, data); } void DebugOutput::Write(const char* name, const DataTypes::MatrixType& data) @@ -63,6 +102,7 @@ namespace AZ::SceneAPI::Utilities AZ::Vector3 translation{}; data.GetBasisAndTranslation(&basisX, &basisY, &basisZ, &translation); + m_pauseNodeData = true; m_output += AZStd::string::format("\t%s:\n", name); m_output += "\t"; @@ -76,16 +116,23 @@ namespace AZ::SceneAPI::Utilities m_output += "\t"; Write("Transl", translation); + m_pauseNodeData = false; + + AddToNode(name, data); } void DebugOutput::Write(const char* name, bool data) { m_output += AZStd::string::format("\t%s: %s\n", name, data ? "true" : "false"); + + AddToNode(name, data); } void DebugOutput::Write(const char* name, Vector3 data) { m_output += AZStd::string::format("\t%s: <% f, % f, % f>\n", name, data.GetX(), data.GetY(), data.GetZ()); + + AddToNode(name, data); } void DebugOutput::Write(const char* name, AZStd::optional data) @@ -128,6 +175,11 @@ namespace AZ::SceneAPI::Utilities return m_output; } + DebugNode DebugOutput::GetDebugNode() const + { + return m_currentNode; + } + void WriteAndLog(AZ::IO::SystemFile& dbgFile, const char* strToWrite) { AZ_TracePrintf(AZ::SceneAPI::Utilities::LogWindow, "%s", strToWrite); @@ -137,7 +189,6 @@ namespace AZ::SceneAPI::Utilities void DebugOutput::BuildDebugSceneGraph(const char* outputFolder, AZ::SceneAPI::Events::ExportProductList& productList, const AZStd::shared_ptr& scene, AZStd::string productName) { - const int debugSceneGraphVersion = 1; AZStd::string debugSceneFile; AzFramework::StringFunc::Path::ConstructFull(outputFolder, productName.c_str(), debugSceneFile); @@ -147,7 +198,7 @@ namespace AZ::SceneAPI::Utilities if (dbgFile.Open(debugSceneFile.c_str(), AZ::IO::SystemFile::SF_OPEN_CREATE | AZ::IO::SystemFile::SF_OPEN_WRITE_ONLY)) { WriteAndLog(dbgFile, AZStd::string::format("ProductName: %s", productName.c_str()).c_str()); - WriteAndLog(dbgFile, AZStd::string::format("debugSceneGraphVersion: %d", debugSceneGraphVersion).c_str()); + WriteAndLog(dbgFile, AZStd::string::format("debugSceneGraphVersion: %d", SceneGraphVersion).c_str()); WriteAndLog(dbgFile, scene->GetName().c_str()); const AZ::SceneAPI::Containers::SceneGraph& sceneGraph = scene->GetGraph(); @@ -158,6 +209,11 @@ namespace AZ::SceneAPI::Utilities AZ::SceneAPI::Containers::Views::BreadthFirst>( sceneGraph, sceneGraph.GetRoot(), pairView.cbegin(), true); + DebugSceneGraph debugSceneGraph; + debugSceneGraph.m_version = SceneGraphVersion; + debugSceneGraph.m_productName = productName; + debugSceneGraph.m_sceneName = scene->GetName().c_str(); + for (auto&& viewIt : view) { if (viewIt.second == nullptr) @@ -170,20 +226,31 @@ namespace AZ::SceneAPI::Utilities WriteAndLog(dbgFile, AZStd::string::format("Node Name: %s", viewIt.first.GetName()).c_str()); WriteAndLog(dbgFile, AZStd::string::format("Node Path: %s", viewIt.first.GetPath()).c_str()); WriteAndLog(dbgFile, AZStd::string::format("Node Type: %s", graphObject->RTTI_GetTypeName()).c_str()); - - AZ::SceneAPI::Utilities::DebugOutput debugOutput; + + AZ::SceneAPI::Utilities::DebugOutput debugOutput( + DebugNode(viewIt.first.GetName(), viewIt.first.GetPath(), graphObject->RTTI_GetTypeName())); + viewIt.second->GetDebugOutput(debugOutput); if (!debugOutput.GetOutput().empty()) { WriteAndLog(dbgFile, debugOutput.GetOutput().c_str()); } + + debugSceneGraph.m_nodes.push_back(debugOutput.GetDebugNode()); } dbgFile.Close(); + Utils::SaveObjectToFile((debugSceneFile + ".xml").c_str(), DataStream::StreamType::ST_XML, &debugSceneGraph); + static const AZ::Data::AssetType dbgSceneGraphAssetType("{07F289D1-4DC7-4C40-94B4-0A53BBCB9F0B}"); productList.AddProduct(productName, AZ::Uuid::CreateName(productName.c_str()), dbgSceneGraphAssetType, AZStd::nullopt, AZStd::nullopt); + + static const AZ::Data::AssetType dbgSceneGraphXmlAssetType("{51F37614-0D77-4F36-9AC6-7ED70A0AC868}"); + productList.AddProduct( + (productName + ".xml"), AZ::Uuid::CreateName((productName + ".xml").c_str()), dbgSceneGraphXmlAssetType, + AZStd::nullopt, AZStd::nullopt); } } } diff --git a/Code/Tools/SceneAPI/SceneCore/Utilities/DebugOutput.h b/Code/Tools/SceneAPI/SceneCore/Utilities/DebugOutput.h index e05d10e2fa..533fb03a67 100644 --- a/Code/Tools/SceneAPI/SceneCore/Utilities/DebugOutput.h +++ b/Code/Tools/SceneAPI/SceneCore/Utilities/DebugOutput.h @@ -15,6 +15,7 @@ #include #include #include +#include namespace AZ { @@ -34,15 +35,63 @@ namespace AZ namespace AZ::SceneAPI::Utilities { + constexpr int SceneGraphVersion = 1; + + struct DebugNode + { + AZ_TYPE_INFO(DebugNode, "{490B9D4C-1847-46EB-BEBC-49812E104626}"); + + AZStd::string m_name; + AZStd::string m_path; + AZStd::string m_type; + + DebugNode() = default; + + DebugNode(AZStd::string name, AZStd::string path, AZStd::string type) + : m_name(AZStd::move(name)), + m_path(AZStd::move(path)), + m_type(AZStd::move(type)) + { + } + + static void Reflect(AZ::ReflectContext* context); + + using DataItem = AZStd::pair; + AZStd::vector m_data; + }; + + struct DebugSceneGraph + { + AZ_TYPE_INFO(DebugSceneGraph, "{375F6558-5709-409F-881E-8ED575D56C92}"); + + int m_version = SceneGraphVersion; + AZStd::string m_productName; + AZStd::string m_sceneName; + AZStd::vector m_nodes; + + static void Reflect(AZ::ReflectContext* context); + }; + class DebugOutput { public: + DebugOutput(DebugNode node) : m_currentNode(AZStd::move(node)){} + template void Write(const char* name, const AZStd::vector& data); template void Write(const char* name, const AZStd::vector>& data); + template + void AddToNode(const char* name, const T& data) + { + if (!m_pauseNodeData) + { + m_currentNode.m_data.emplace_back(name, AZStd::make_any>(data)); + } + } + SCENE_CORE_API void Write(const char* name, const char* data); SCENE_CORE_API void WriteArray(const char* name, const unsigned int* data, int size); SCENE_CORE_API void Write(const char* name, const AZStd::string& data); @@ -57,11 +106,15 @@ namespace AZ::SceneAPI::Utilities SCENE_CORE_API void Write(const char* name, AZStd::optional data); SCENE_CORE_API const AZStd::string& GetOutput() const; + SCENE_CORE_API DebugNode GetDebugNode() const; SCENE_CORE_API static void BuildDebugSceneGraph(const char* outputFolder, AZ::SceneAPI::Events::ExportProductList& productList, const AZStd::shared_ptr& scene, AZStd::string productName); protected: AZStd::string m_output; + DebugSceneGraph m_graph; + DebugNode m_currentNode; + bool m_pauseNodeData = false; // If true, don't append any data to the DebugNode. Useful when a Write function calls other Write functions }; } diff --git a/Code/Tools/SceneAPI/SceneCore/Utilities/DebugOutput.inl b/Code/Tools/SceneAPI/SceneCore/Utilities/DebugOutput.inl index a139b4bce2..bba1d42ee9 100644 --- a/Code/Tools/SceneAPI/SceneCore/Utilities/DebugOutput.inl +++ b/Code/Tools/SceneAPI/SceneCore/Utilities/DebugOutput.inl @@ -13,7 +13,11 @@ namespace AZ::SceneAPI::Utilities template void DebugOutput::Write(const char* name, const AZStd::vector& data) { - m_output += AZStd::string::format("\t%s: Count %zu. Hash: %zu\n", name, data.size(), AZStd::hash_range(data.begin(), data.end())); + size_t hash = AZStd::hash_range(data.begin(), data.end()); + m_output += AZStd::string::format("\t%s: Count %zu. Hash: %zu\n", name, data.size(), hash); + + AddToNode(AZStd::string::format("%s - Count", name).c_str(), data.size()); + AddToNode(AZStd::string::format("%s - Hash", name).c_str(), hash); } template @@ -27,5 +31,9 @@ namespace AZ::SceneAPI::Utilities } m_output += AZStd::string::format("\t%s: Count %zu. Hash: %zu\n", name, data.size(), hash); + + AddToNode(AZStd::string::format("%s - Count", name).c_str(), data.size()); + AddToNode(AZStd::string::format("%s - Hash", name).c_str(), hash); } + } diff --git a/Code/Tools/SceneAPI/SceneData/GraphData/BoneData.cpp b/Code/Tools/SceneAPI/SceneData/GraphData/BoneData.cpp index 148b8a280c..8a3807c31a 100644 --- a/Code/Tools/SceneAPI/SceneData/GraphData/BoneData.cpp +++ b/Code/Tools/SceneAPI/SceneData/GraphData/BoneData.cpp @@ -8,6 +8,7 @@ #include #include +#include #include namespace AZ @@ -41,6 +42,16 @@ namespace AZ ->DataElement(AZ::Edit::UIHandlers::Default, &BoneData::m_worldTransform, "World", "World transform this bone contributes to the overall skeleton."); } } + + BehaviorContext* behaviorContext = azrtti_cast(context); + if (behaviorContext) + { + behaviorContext->Class() + ->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All) + ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common) + ->Attribute(AZ::Script::Attributes::Module, "scene") + ->Method("GetWorldTransform", &BoneData::GetWorldTransform); + } } } // namespace GraphData } // namespace SceneData diff --git a/Code/Tools/SceneAPI/SceneData/GraphData/RootBoneData.cpp b/Code/Tools/SceneAPI/SceneData/GraphData/RootBoneData.cpp index 99cfbf48b9..3d7c8ec376 100644 --- a/Code/Tools/SceneAPI/SceneData/GraphData/RootBoneData.cpp +++ b/Code/Tools/SceneAPI/SceneData/GraphData/RootBoneData.cpp @@ -8,6 +8,7 @@ #include #include +#include #include namespace AZ @@ -30,6 +31,16 @@ namespace AZ editContext->Class("Root Bone data", "First bone in the skeletal hierarchy."); } } + + BehaviorContext* behaviorContext = azrtti_cast(context); + if (behaviorContext) + { + behaviorContext->Class() + ->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All) + ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common) + ->Attribute(AZ::Script::Attributes::Module, "scene") + ->Method("GetWorldTransform", &RootBoneData::GetWorldTransform); + } } } // namespace GraphData } // namespace SceneData diff --git a/Code/Tools/SceneAPI/SceneData/Tests/GraphData/GraphDataBehaviorTests.cpp b/Code/Tools/SceneAPI/SceneData/Tests/GraphData/GraphDataBehaviorTests.cpp index 4a2d206cc7..59df74a12c 100644 --- a/Code/Tools/SceneAPI/SceneData/Tests/GraphData/GraphDataBehaviorTests.cpp +++ b/Code/Tools/SceneAPI/SceneData/Tests/GraphData/GraphDataBehaviorTests.cpp @@ -29,6 +29,8 @@ #include #include #include +#include +#include namespace AZ { @@ -178,7 +180,18 @@ namespace AZ materialDataData->SetTexture(AZ::SceneAPI::DataTypes::IMaterialData::TextureMapType::Specular, "specular"); return true; } - + else if (data.get_type_info().m_id == azrtti_typeid()) + { + auto* boneData = AZStd::any_cast(&data); + boneData->SetWorldTransform(SceneAPI::DataTypes::MatrixType::CreateDiagonal({1.0, 2.0, 3.0})); + return true; + } + else if (data.get_type_info().m_id == azrtti_typeid()) + { + auto* boneData = AZStd::any_cast(&data); + boneData->SetWorldTransform(SceneAPI::DataTypes::MatrixType::CreateDiagonal({2.0, 3.0, 4.0})); + return true; + } return false; } @@ -526,6 +539,44 @@ namespace AZ ExpectExecute("TestExpectTrue(materialData:GetTexture(MaterialData.Roughness) == 'roughness')"); ExpectExecute("TestExpectTrue(materialData:GetTexture(MaterialData.Specular) == 'specular')"); } + + TEST_F(GrapDatahBehaviorScriptTest, SceneGraph_BoneData_AccessWorks) + { + ExpectExecute("boneData = BoneData()"); + ExpectExecute("TestExpectTrue(boneData ~= nil)"); + ExpectExecute("MockGraphData.FillData(boneData)"); + ExpectExecute("TestExpectFloatEquals(boneData:GetWorldTransform():GetRow(0).x, 1.0)"); + ExpectExecute("TestExpectFloatEquals(boneData:GetWorldTransform():GetRow(0).y, 0.0)"); + ExpectExecute("TestExpectFloatEquals(boneData:GetWorldTransform():GetRow(0).z, 0.0)"); + ExpectExecute("TestExpectFloatEquals(boneData:GetWorldTransform():GetRow(0).w, 0.0)"); + ExpectExecute("TestExpectFloatEquals(boneData:GetWorldTransform():GetRow(1).x, 0.0)"); + ExpectExecute("TestExpectFloatEquals(boneData:GetWorldTransform():GetRow(1).y, 2.0)"); + ExpectExecute("TestExpectFloatEquals(boneData:GetWorldTransform():GetRow(1).z, 0.0)"); + ExpectExecute("TestExpectFloatEquals(boneData:GetWorldTransform():GetRow(1).w, 0.0)"); + ExpectExecute("TestExpectFloatEquals(boneData:GetWorldTransform():GetRow(2).x, 0.0)"); + ExpectExecute("TestExpectFloatEquals(boneData:GetWorldTransform():GetRow(2).y, 0.0)"); + ExpectExecute("TestExpectFloatEquals(boneData:GetWorldTransform():GetRow(2).z, 3.0)"); + ExpectExecute("TestExpectFloatEquals(boneData:GetWorldTransform():GetRow(2).w, 0.0)"); + } + + TEST_F(GrapDatahBehaviorScriptTest, SceneGraph_RootBoneData_AccessWorks) + { + ExpectExecute("rootBoneData = RootBoneData()"); + ExpectExecute("TestExpectTrue(rootBoneData ~= nil)"); + ExpectExecute("MockGraphData.FillData(rootBoneData)"); + ExpectExecute("TestExpectFloatEquals(rootBoneData:GetWorldTransform():GetRow(0).x, 2.0)"); + ExpectExecute("TestExpectFloatEquals(rootBoneData:GetWorldTransform():GetRow(0).y, 0.0)"); + ExpectExecute("TestExpectFloatEquals(rootBoneData:GetWorldTransform():GetRow(0).z, 0.0)"); + ExpectExecute("TestExpectFloatEquals(rootBoneData:GetWorldTransform():GetRow(0).w, 0.0)"); + ExpectExecute("TestExpectFloatEquals(rootBoneData:GetWorldTransform():GetRow(1).x, 0.0)"); + ExpectExecute("TestExpectFloatEquals(rootBoneData:GetWorldTransform():GetRow(1).y, 3.0)"); + ExpectExecute("TestExpectFloatEquals(rootBoneData:GetWorldTransform():GetRow(1).z, 0.0)"); + ExpectExecute("TestExpectFloatEquals(rootBoneData:GetWorldTransform():GetRow(1).w, 0.0)"); + ExpectExecute("TestExpectFloatEquals(rootBoneData:GetWorldTransform():GetRow(2).x, 0.0)"); + ExpectExecute("TestExpectFloatEquals(rootBoneData:GetWorldTransform():GetRow(2).y, 0.0)"); + ExpectExecute("TestExpectFloatEquals(rootBoneData:GetWorldTransform():GetRow(2).z, 4.0)"); + ExpectExecute("TestExpectFloatEquals(rootBoneData:GetWorldTransform():GetRow(2).w, 0.0)"); + } } } } diff --git a/Code/Tools/SerializeContextTools/SliceConverter.cpp b/Code/Tools/SerializeContextTools/SliceConverter.cpp index 6cfe072f80..d0528a9cad 100644 --- a/Code/Tools/SerializeContextTools/SliceConverter.cpp +++ b/Code/Tools/SerializeContextTools/SliceConverter.cpp @@ -81,8 +81,6 @@ namespace AZ // Load the asset catalog so that we can find any nested assets successfully. We also need to tick the tick bus // so that the OnCatalogLoaded event gets processed now, instead of during application shutdown. - AZ::Data::AssetCatalogRequestBus::Broadcast( - &AZ::Data::AssetCatalogRequestBus::Events::LoadCatalog, "@products@/assetcatalog.xml"); application.Tick(); AZStd::string logggingScratchBuffer; diff --git a/Gems/AWSGameLift/Code/AWSGameLiftClient/Include/Request/AWSGameLiftCreateSessionOnQueueRequest.h b/Gems/AWSGameLift/Code/AWSGameLiftClient/Include/Request/AWSGameLiftCreateSessionOnQueueRequest.h index a9b06c5bc9..24dfce1dfa 100644 --- a/Gems/AWSGameLift/Code/AWSGameLiftClient/Include/Request/AWSGameLiftCreateSessionOnQueueRequest.h +++ b/Gems/AWSGameLift/Code/AWSGameLiftClient/Include/Request/AWSGameLiftCreateSessionOnQueueRequest.h @@ -25,11 +25,11 @@ namespace AWSGameLift AWSGameLiftCreateSessionOnQueueRequest() = default; virtual ~AWSGameLiftCreateSessionOnQueueRequest() = default; - // Name of the queue to use to place the new game session. You can use either the queue name or ARN value. + //! Name of the queue to use to place the new game session. You can use either the queue name or ARN value. AZStd::string m_queueName; - // A unique identifier to assign to the new game session placement. This value is developer-defined. - // The value must be unique across all Regions and cannot be reused unless you are resubmitting a canceled or timed-out placement request. + //! A unique identifier to assign to the new game session placement. This value is developer-defined. + //! The value must be unique across all Regions and cannot be reused unless you are resubmitting a canceled or timed-out placement request. AZStd::string m_placementId; }; } // namespace AWSGameLift diff --git a/Gems/AWSGameLift/Code/AWSGameLiftClient/Include/Request/AWSGameLiftCreateSessionRequest.h b/Gems/AWSGameLift/Code/AWSGameLiftClient/Include/Request/AWSGameLiftCreateSessionRequest.h index 4fab16ca70..97ffd0b321 100644 --- a/Gems/AWSGameLift/Code/AWSGameLiftClient/Include/Request/AWSGameLiftCreateSessionRequest.h +++ b/Gems/AWSGameLift/Code/AWSGameLiftClient/Include/Request/AWSGameLiftCreateSessionRequest.h @@ -25,14 +25,14 @@ namespace AWSGameLift AWSGameLiftCreateSessionRequest() = default; virtual ~AWSGameLiftCreateSessionRequest() = default; - // A unique identifier for the alias associated with the fleet to create a game session in. + //! A unique identifier for the alias associated with the fleet to create a game session in. AZStd::string m_aliasId; - // A unique identifier for the fleet to create a game session in. + //! A unique identifier for the fleet to create a game session in. AZStd::string m_fleetId; - // Custom string that uniquely identifies the new game session request. - // This is useful for ensuring that game session requests with the same idempotency token are processed only once. + //! Custom string that uniquely identifies the new game session request. + //! This is useful for ensuring that game session requests with the same idempotency token are processed only once. AZStd::string m_idempotencyToken; }; } // namespace AWSGameLift diff --git a/Gems/AWSGameLift/Code/AWSGameLiftClient/Include/Request/AWSGameLiftSearchSessionsRequest.h b/Gems/AWSGameLift/Code/AWSGameLiftClient/Include/Request/AWSGameLiftSearchSessionsRequest.h index bfc2dc630e..02f3638998 100644 --- a/Gems/AWSGameLift/Code/AWSGameLiftClient/Include/Request/AWSGameLiftSearchSessionsRequest.h +++ b/Gems/AWSGameLift/Code/AWSGameLiftClient/Include/Request/AWSGameLiftSearchSessionsRequest.h @@ -25,13 +25,13 @@ namespace AWSGameLift AWSGameLiftSearchSessionsRequest() = default; virtual ~AWSGameLiftSearchSessionsRequest() = default; - // A unique identifier for the alias associated with the fleet to search for active game sessions. + //! A unique identifier for the alias associated with the fleet to search for active game sessions. AZStd::string m_aliasId; - // A unique identifier for the fleet to search for active game sessions. + //! A unique identifier for the fleet to search for active game sessions. AZStd::string m_fleetId; - // A fleet location to search for game sessions. + //! A fleet location to search for game sessions. AZStd::string m_location; }; } // namespace AWSGameLift diff --git a/Gems/AWSGameLift/Code/AWSGameLiftClient/Include/Request/AWSGameLiftStartMatchmakingRequest.h b/Gems/AWSGameLift/Code/AWSGameLiftClient/Include/Request/AWSGameLiftStartMatchmakingRequest.h index ec3719c6d3..b2060032e8 100644 --- a/Gems/AWSGameLift/Code/AWSGameLiftClient/Include/Request/AWSGameLiftStartMatchmakingRequest.h +++ b/Gems/AWSGameLift/Code/AWSGameLiftClient/Include/Request/AWSGameLiftStartMatchmakingRequest.h @@ -30,9 +30,10 @@ namespace AWSGameLift AWSGameLiftStartMatchmakingRequest() = default; virtual ~AWSGameLiftStartMatchmakingRequest() = default; - // Name of the matchmaking configuration to use for this request + //! Name of the matchmaking configuration to use for this request AZStd::string m_configurationName; - // Information on each player to be matched + + //! Information on each player to be matched AZStd::vector m_players; }; } // namespace AWSGameLift diff --git a/Gems/AWSGameLift/Code/AWSGameLiftClient/Tests/AWSGameLiftClientLocalTicketTrackerTest.cpp b/Gems/AWSGameLift/Code/AWSGameLiftClient/Tests/AWSGameLiftClientLocalTicketTrackerTest.cpp index be72de555e..bc147fe7f6 100644 --- a/Gems/AWSGameLift/Code/AWSGameLiftClient/Tests/AWSGameLiftClientLocalTicketTrackerTest.cpp +++ b/Gems/AWSGameLift/Code/AWSGameLiftClient/Tests/AWSGameLiftClientLocalTicketTrackerTest.cpp @@ -108,7 +108,7 @@ TEST_F(AWSGameLiftClientLocalTicketTrackerTest, StartPolling_CallWithoutClientSe MatchmakingNotificationsHandlerMock matchmakingHandlerMock; AZ_TEST_START_TRACE_SUPPRESSION; m_gameliftClientTicketTracker->StartPolling("ticket1", "player1"); - WaitForProcessFinish([](){ return ::UnitTest::TestRunner::Instance().m_numAssertsFailed == 1; }); + WaitForProcessFinish([&](){ return matchmakingHandlerMock.m_numMatchError == 1; }); AZ_TEST_STOP_TRACE_SUPPRESSION(1); ASSERT_TRUE(matchmakingHandlerMock.m_numMatchError == 1); ASSERT_FALSE(m_gameliftClientTicketTracker->IsTrackerIdle()); @@ -122,7 +122,7 @@ TEST_F(AWSGameLiftClientLocalTicketTrackerTest, StartPolling_MultipleCallsWithou AZ_TEST_START_TRACE_SUPPRESSION; m_gameliftClientTicketTracker->StartPolling("ticket1", "player1"); m_gameliftClientTicketTracker->StartPolling("ticket1", "player1"); - WaitForProcessFinish([](){ return ::UnitTest::TestRunner::Instance().m_numAssertsFailed == 1; }); + WaitForProcessFinish([&](){ return matchmakingHandlerMock.m_numMatchError == 1; }); AZ_TEST_STOP_TRACE_SUPPRESSION(1); ASSERT_TRUE(matchmakingHandlerMock.m_numMatchError == 1); ASSERT_FALSE(m_gameliftClientTicketTracker->IsTrackerIdle()); @@ -140,7 +140,7 @@ TEST_F(AWSGameLiftClientLocalTicketTrackerTest, StartPolling_CallButWithFailedOu MatchmakingNotificationsHandlerMock matchmakingHandlerMock; AZ_TEST_START_TRACE_SUPPRESSION; m_gameliftClientTicketTracker->StartPolling("ticket1", "player1"); - WaitForProcessFinish([](){ return ::UnitTest::TestRunner::Instance().m_numAssertsFailed == 1; }); + WaitForProcessFinish([&](){ return matchmakingHandlerMock.m_numMatchError == 1; }); AZ_TEST_STOP_TRACE_SUPPRESSION(1); ASSERT_TRUE(matchmakingHandlerMock.m_numMatchError == 1); ASSERT_FALSE(m_gameliftClientTicketTracker->IsTrackerIdle()); @@ -160,7 +160,7 @@ TEST_F(AWSGameLiftClientLocalTicketTrackerTest, StartPolling_CallWithMoreThanOne MatchmakingNotificationsHandlerMock matchmakingHandlerMock; AZ_TEST_START_TRACE_SUPPRESSION; m_gameliftClientTicketTracker->StartPolling("ticket1", "player1"); - WaitForProcessFinish([](){ return ::UnitTest::TestRunner::Instance().m_numAssertsFailed == 1; }); + WaitForProcessFinish([&](){ return matchmakingHandlerMock.m_numMatchError == 1; }); AZ_TEST_STOP_TRACE_SUPPRESSION(1); ASSERT_TRUE(matchmakingHandlerMock.m_numMatchError == 1); ASSERT_FALSE(m_gameliftClientTicketTracker->IsTrackerIdle()); diff --git a/Gems/AWSMetrics/Code/Include/Private/IdentityProvider.h b/Gems/AWSMetrics/Code/Include/Private/IdentityProvider.h index 1309e2749c..f70b2c7e10 100644 --- a/Gems/AWSMetrics/Code/Include/Private/IdentityProvider.h +++ b/Gems/AWSMetrics/Code/Include/Private/IdentityProvider.h @@ -14,6 +14,8 @@ namespace AWSMetrics { + constexpr const char* EngineVersionJsonKey = "O3DEVersion"; + //! Base class to be implemented by IdentityProvider to retrive an ID for identity. class IdentityProvider { diff --git a/Gems/AWSMetrics/Code/Source/IdentityProvider.cpp b/Gems/AWSMetrics/Code/Source/IdentityProvider.cpp index bc8ff9a42e..d0ab8a1a8c 100644 --- a/Gems/AWSMetrics/Code/Source/IdentityProvider.cpp +++ b/Gems/AWSMetrics/Code/Source/IdentityProvider.cpp @@ -8,10 +8,10 @@ #include -#include #include -#include -#include +#include +#include +#include namespace AWSMetrics { @@ -22,37 +22,23 @@ namespace AWSMetrics AZStd::string IdentityProvider::GetEngineVersion() { - static constexpr const char* EngineConfigFilePath = "@products@/engine.json"; - static constexpr const char* EngineVersionJsonKey = "O3DEVersion"; - - AZ::IO::FileIOBase* fileIO = AZ::IO::FileIOBase::GetDirectInstance(); - if (!fileIO) + constexpr auto engineVersionKey = AZ::SettingsRegistryInterface::FixedValueString(AZ::SettingsRegistryMergeUtils::EngineSettingsRootKey) + "/" + EngineVersionJsonKey; + AZStd::string engineVersion; + if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr && settingsRegistry->Get(engineVersion, engineVersionKey)) { - AZ_Error("AWSMetrics", false, "No FileIoBase Instance"); - return ""; + return engineVersion; } - char resolvedPath[AZ_MAX_PATH_LEN] = { 0 }; - if (!fileIO->ResolvePath(EngineConfigFilePath, resolvedPath, AZ_MAX_PATH_LEN)) + auto engineSettingsPath = AZ::IO::FixedMaxPath{ AZ::Utils::GetEnginePath() } / "engine.json"; + if (AZ::IO::SystemFile::Exists(engineSettingsPath.c_str())) { - AZ_Error("AWSMetrics", false, "Failed to resolve the engine config file directory"); - return ""; + AZ::SettingsRegistryImpl settingsRegistry; + if (settingsRegistry.MergeSettingsFile( + engineSettingsPath.Native(), AZ::SettingsRegistryInterface::Format::JsonMergePatch, AZ::SettingsRegistryMergeUtils::EngineSettingsRootKey)) + { + settingsRegistry.Get(engineVersion, engineVersionKey); + } } - - auto readOutcome = AZ::JsonSerializationUtils::ReadJsonFile(resolvedPath); - if (!readOutcome.IsSuccess()) - { - AZ_Error("AWSMetrics", false, readOutcome.GetError().c_str()); - return ""; - } - - rapidjson_ly::Document& jsonDoc = readOutcome.GetValue(); - auto memberIt = jsonDoc.FindMember(EngineVersionJsonKey); - if (memberIt != jsonDoc.MemberEnd()) - { - return memberIt->value.GetString(); - } - - return ""; + return engineVersion; } } diff --git a/Gems/AWSMetrics/Code/Tests/AWSMetricsGemMock.h b/Gems/AWSMetrics/Code/Tests/AWSMetricsGemMock.h index 4f40fd6ff1..f8fb9e174c 100644 --- a/Gems/AWSMetrics/Code/Tests/AWSMetricsGemMock.h +++ b/Gems/AWSMetrics/Code/Tests/AWSMetricsGemMock.h @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -47,9 +48,6 @@ namespace AWSMetrics m_localFileIO->SetAlias("@engroot@", engineRoot.c_str()); m_localFileIO->SetAlias("@products@", productAssetPath.c_str()); m_localFileIO->SetAlias("@user@", userPath.c_str()); - // Copy engine.json to the cache - EXPECT_TRUE(m_localFileIO->Copy((engineRoot / "engine.json").c_str(), "engine.json")); - m_serializeContext = AZStd::make_unique(); m_registrationContext = AZStd::make_unique(); @@ -61,6 +59,8 @@ namespace AWSMetrics m_settingsRegistry->SetContext(m_serializeContext.get()); m_settingsRegistry->SetContext(m_registrationContext.get()); + m_settingsRegistry->Set(AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder, engineRoot.c_str()); + AZ::SettingsRegistry::Register(m_settingsRegistry.get()); } diff --git a/Gems/Atom/Asset/Shader/Code/Source/Editor/AzslShaderBuilderSystemComponent.cpp b/Gems/Atom/Asset/Shader/Code/Source/Editor/AzslShaderBuilderSystemComponent.cpp index 56f2fdec62..e41c04a0be 100644 --- a/Gems/Atom/Asset/Shader/Code/Source/Editor/AzslShaderBuilderSystemComponent.cpp +++ b/Gems/Atom/Asset/Shader/Code/Source/Editor/AzslShaderBuilderSystemComponent.cpp @@ -23,6 +23,7 @@ #include #include +#include #include #include @@ -91,19 +92,31 @@ namespace AZ m_shaderAssetBuilder.BusConnect(shaderAssetBuilderDescriptor.m_busId); AssetBuilderSDK::AssetBuilderBus::Broadcast(&AssetBuilderSDK::AssetBuilderBus::Handler::RegisterBuilderInformation, shaderAssetBuilderDescriptor); - // Register Shader Variant Asset Builder - AssetBuilderSDK::AssetBuilderDesc shaderVariantAssetBuilderDescriptor; - shaderVariantAssetBuilderDescriptor.m_name = "Shader Variant Asset Builder"; - // Both "Shader Variant Asset Builder" and "Shader Asset Builder" produce ShaderVariantAsset products. If you update - // ShaderVariantAsset you will need to update BOTH version numbers, not just "Shader Variant Asset Builder". - shaderVariantAssetBuilderDescriptor.m_version = 26; // [AZSL] Changing inlineConstant to rootConstant keyword work. - shaderVariantAssetBuilderDescriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern(AZStd::string::format("*.%s", RPI::ShaderVariantListSourceData::Extension), AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard)); - shaderVariantAssetBuilderDescriptor.m_busId = azrtti_typeid(); - shaderVariantAssetBuilderDescriptor.m_createJobFunction = AZStd::bind(&ShaderVariantAssetBuilder::CreateJobs, &m_shaderVariantAssetBuilder, AZStd::placeholders::_1, AZStd::placeholders::_2); - shaderVariantAssetBuilderDescriptor.m_processJobFunction = AZStd::bind(&ShaderVariantAssetBuilder::ProcessJob, &m_shaderVariantAssetBuilder, AZStd::placeholders::_1, AZStd::placeholders::_2); + // If, either the SettingsRegistry doesn't exist, or the property @EnableShaderVariantAssetBuilderRegistryKey is not found, + // the default is to enable the ShaderVariantAssetBuilder. + m_enableShaderVariantAssetBuilder = true; + auto settingsRegistry = AZ::SettingsRegistry::Get(); + if (settingsRegistry) + { + settingsRegistry->Get(m_enableShaderVariantAssetBuilder, EnableShaderVariantAssetBuilderRegistryKey); + } - m_shaderVariantAssetBuilder.BusConnect(shaderVariantAssetBuilderDescriptor.m_busId); - AssetBuilderSDK::AssetBuilderBus::Broadcast(&AssetBuilderSDK::AssetBuilderBus::Handler::RegisterBuilderInformation, shaderVariantAssetBuilderDescriptor); + if (m_enableShaderVariantAssetBuilder) + { + // Register Shader Variant Asset Builder + AssetBuilderSDK::AssetBuilderDesc shaderVariantAssetBuilderDescriptor; + shaderVariantAssetBuilderDescriptor.m_name = "Shader Variant Asset Builder"; + // Both "Shader Variant Asset Builder" and "Shader Asset Builder" produce ShaderVariantAsset products. If you update + // ShaderVariantAsset you will need to update BOTH version numbers, not just "Shader Variant Asset Builder". + shaderVariantAssetBuilderDescriptor.m_version = 26; // [AZSL] Changing inlineConstant to rootConstant keyword work. + shaderVariantAssetBuilderDescriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern(AZStd::string::format("*.%s", RPI::ShaderVariantListSourceData::Extension), AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard)); + shaderVariantAssetBuilderDescriptor.m_busId = azrtti_typeid(); + shaderVariantAssetBuilderDescriptor.m_createJobFunction = AZStd::bind(&ShaderVariantAssetBuilder::CreateJobs, &m_shaderVariantAssetBuilder, AZStd::placeholders::_1, AZStd::placeholders::_2); + shaderVariantAssetBuilderDescriptor.m_processJobFunction = AZStd::bind(&ShaderVariantAssetBuilder::ProcessJob, &m_shaderVariantAssetBuilder, AZStd::placeholders::_1, AZStd::placeholders::_2); + + m_shaderVariantAssetBuilder.BusConnect(shaderVariantAssetBuilderDescriptor.m_busId); + AssetBuilderSDK::AssetBuilderBus::Broadcast(&AssetBuilderSDK::AssetBuilderBus::Handler::RegisterBuilderInformation, shaderVariantAssetBuilderDescriptor); + } // Register Precompiled Shader Builder AssetBuilderSDK::AssetBuilderDesc precompiledShaderBuilderDescriptor; @@ -121,7 +134,10 @@ namespace AZ void AzslShaderBuilderSystemComponent::Deactivate() { m_shaderAssetBuilder.BusDisconnect(); - m_shaderVariantAssetBuilder.BusDisconnect(); + if (m_enableShaderVariantAssetBuilder) + { + m_shaderVariantAssetBuilder.BusDisconnect(); + } m_precompiledShaderBuilder.BusDisconnect(); RHI::ShaderPlatformInterfaceRegisterBus::Handler::BusDisconnect(); diff --git a/Gems/Atom/Asset/Shader/Code/Source/Editor/AzslShaderBuilderSystemComponent.h b/Gems/Atom/Asset/Shader/Code/Source/Editor/AzslShaderBuilderSystemComponent.h index 9ff5bd8282..f502e4c329 100644 --- a/Gems/Atom/Asset/Shader/Code/Source/Editor/AzslShaderBuilderSystemComponent.h +++ b/Gems/Atom/Asset/Shader/Code/Source/Editor/AzslShaderBuilderSystemComponent.h @@ -61,7 +61,17 @@ namespace AZ private: ShaderAssetBuilder m_shaderAssetBuilder; + + // The ShaderVariantAssetBuilder can be disabled with this registry key. + // By default it is enabled. A user might want to disable it when doing look development + // work with shaders or doing lots of iterative changes to shaders. In these cases + // GPU performance doesn't matter at all so it is important to not waste time + // building ShaderVariantAssets (Other than the Root ShaderVariantAsset, of course.). + static constexpr char EnableShaderVariantAssetBuilderRegistryKey[] = "/O3DE/Atom/Shaders/BuildVariants"; + bool m_enableShaderVariantAssetBuilder = true; + ShaderVariantAssetBuilder m_shaderVariantAssetBuilder; + PrecompiledShaderBuilder m_precompiledShaderBuilder; /// Contains the ShaderPlatformInterface for all registered RHIs diff --git a/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderAssetBuilder.cpp b/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderAssetBuilder.cpp index a0205efa72..89e202a4bc 100644 --- a/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderAssetBuilder.cpp +++ b/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderAssetBuilder.cpp @@ -477,8 +477,8 @@ namespace AZ preprocessorOptions.m_predefinedMacros.end(), macroDefinitionsToAdd.begin(), macroDefinitionsToAdd.end()); // Run the preprocessor. PreprocessorData output; - PreprocessFile(prependedAzslFilePath, output, preprocessorOptions, true, true); - RHI::ReportErrorMessages(ShaderAssetBuilderName, output.diagnostics); + const bool preprocessorSuccess = PreprocessFile(prependedAzslFilePath, output, preprocessorOptions, true, true); + RHI::ReportMessages(ShaderAssetBuilderName, output.diagnostics, !preprocessorSuccess); // Dump the preprocessed string as a flat AZSL file with extension .azslin, which will be given to AZSLc to generate the HLSL file. AZStd::string superVariantAzslinStemName = shaderFileName; if (!supervariantInfo.m_name.IsEmpty()) diff --git a/Gems/Atom/Asset/Shader/Registry/atom_shaders.setreg b/Gems/Atom/Asset/Shader/Registry/atom_shaders.setreg new file mode 100644 index 0000000000..31d108f47a --- /dev/null +++ b/Gems/Atom/Asset/Shader/Registry/atom_shaders.setreg @@ -0,0 +1,10 @@ +{ + "O3DE": { + "Atom": { + "Shaders": { + "BuildVariants": true + } + } + } + } +} diff --git a/Gems/Atom/Bootstrap/Code/Source/BootstrapSystemComponent.cpp b/Gems/Atom/Bootstrap/Code/Source/BootstrapSystemComponent.cpp index 8f2ff264e5..84fc58718c 100644 --- a/Gems/Atom/Bootstrap/Code/Source/BootstrapSystemComponent.cpp +++ b/Gems/Atom/Bootstrap/Code/Source/BootstrapSystemComponent.cpp @@ -10,10 +10,12 @@ #include #include +#include #include #include #include #include +#include #include #include @@ -115,7 +117,9 @@ namespace AZ { // GFX TODO - investigate window creation being part of the GameApplication. - m_nativeWindow = AZStd::make_unique("O3DELauncher", AzFramework::WindowGeometry(0, 0, 1920, 1080)); + auto projectTitle = AZ::Utils::GetProjectName(); + + m_nativeWindow = AZStd::make_unique(projectTitle.c_str(), AzFramework::WindowGeometry(0, 0, 1920, 1080)); AZ_Assert(m_nativeWindow, "Failed to create the game window\n"); m_nativeWindow->Activate(); @@ -129,7 +133,6 @@ namespace AZ m_createDefaultScene = false; } - AzFramework::AssetCatalogEventBus::Handler::BusConnect(); TickBus::Handler::BusConnect(); // Listen for window system requests (e.g. requests for default window handle) @@ -140,6 +143,20 @@ namespace AZ Render::Bootstrap::DefaultWindowBus::Handler::BusConnect(); Render::Bootstrap::RequestBus::Handler::BusConnect(); + + // If the settings registry isn't available, something earlier in startup will report that failure. + if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr) + { + // Automatically register the event if it's not registered, because + // this system is initialized before the settings registry has loaded the event list. + AZ::ComponentApplicationLifecycle::RegisterHandler( + *settingsRegistry, m_componentApplicationLifecycleHandler, + [this](AZStd::string_view /*path*/, AZ::SettingsRegistryInterface::Type /*type*/) + { + Initialize(); + }, + "LegacySystemInterfaceCreated"); + } } void BootstrapSystemComponent::Deactivate() @@ -150,7 +167,6 @@ namespace AZ AzFramework::WindowSystemRequestBus::Handler::BusDisconnect(); AzFramework::WindowSystemNotificationBus::Handler::BusDisconnect(); TickBus::Handler::BusDisconnect(); - AzFramework::AssetCatalogEventBus::Handler::BusDisconnect(); m_brdfTexture = nullptr; RemoveRenderPipeline(); @@ -161,14 +177,14 @@ namespace AZ m_windowHandle = nullptr; } - void BootstrapSystemComponent::OnCatalogLoaded(const char* /*catalogFile*/) + void BootstrapSystemComponent::Initialize() { - if (m_isAssetCatalogLoaded) + if (m_isInitialized) { return; } - m_isAssetCatalogLoaded = true; + m_isInitialized = true; if (!RPI::RPISystemInterface::Get()->IsInitialized()) { @@ -213,7 +229,7 @@ namespace AZ { m_windowHandle = windowHandle; - if (m_isAssetCatalogLoaded) + if (m_isInitialized) { CreateWindowContext(); if (m_createDefaultScene) @@ -256,6 +272,7 @@ namespace AZ // Create and register a scene with all available feature processors RPI::SceneDescriptor sceneDesc; + sceneDesc.m_nameId = AZ::Name("Main"); AZ::RPI::ScenePtr atomScene = RPI::Scene::CreateScene(sceneDesc); atomScene->EnableAllFeatureProcessors(); atomScene->Activate(); diff --git a/Gems/Atom/Bootstrap/Code/Source/BootstrapSystemComponent.h b/Gems/Atom/Bootstrap/Code/Source/BootstrapSystemComponent.h index 566d19b1a4..74679f5753 100644 --- a/Gems/Atom/Bootstrap/Code/Source/BootstrapSystemComponent.h +++ b/Gems/Atom/Bootstrap/Code/Source/BootstrapSystemComponent.h @@ -8,10 +8,10 @@ #pragma once #include -#include #include +#include +#include -#include #include #include #include @@ -29,7 +29,6 @@ #include #include - namespace AZ { namespace Render @@ -40,7 +39,6 @@ namespace AZ : public Component , public TickBus::Handler , public AzFramework::WindowNotificationBus::Handler - , public AzFramework::AssetCatalogEventBus::Handler , public AzFramework::WindowSystemNotificationBus::Handler , public AzFramework::WindowSystemRequestBus::Handler , public Render::Bootstrap::DefaultWindowBus::Handler @@ -82,13 +80,12 @@ namespace AZ void OnTick(float deltaTime, AZ::ScriptTimePoint time) override; int GetTickOrder() override; - // AzFramework::AssetCatalogEventBus::Handler overrides ... - void OnCatalogLoaded(const char* catalogFile) override; - // AzFramework::WindowSystemNotificationBus::Handler overrides ... void OnWindowCreated(AzFramework::NativeWindowHandle windowHandle) override; private: + void Initialize(); + void CreateDefaultRenderPipeline(); void CreateDefaultScene(); void DestroyDefaultScene(); @@ -105,7 +102,7 @@ namespace AZ RPI::ScenePtr m_defaultScene = nullptr; AZStd::shared_ptr m_defaultFrameworkScene = nullptr; - bool m_isAssetCatalogLoaded = false; + bool m_isInitialized = false; // The id of the render pipeline created by this component RPI::RenderPipelineId m_renderPipelineId; @@ -119,6 +116,8 @@ namespace AZ // Maps AZ scenes to RPI scene weak pointers to allow looking up a ScenePtr instead of a raw Scene* AZStd::unordered_map> m_azSceneToAtomSceneMap; + + AZ::SettingsRegistryInterface::NotifyEventHandler m_componentApplicationLifecycleHandler; }; } // namespace Bootstrap } // namespace Render diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR.materialtype b/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR.materialtype index f4bcfb2673..5de756067a 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR.materialtype +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR.materialtype @@ -1,6 +1,14 @@ { "description": "Material Type with properties used to define Enhanced PBR, a metallic-roughness Physically-Based Rendering (PBR) material shading model, with advanced features like subsurface scattering, transmission, and anisotropy.", - "version": 3, + "version": 4, + "versionUpdates": [ + { + "toVersion": 4, + "actions": [ + {"op": "rename", "from": "opacity.doubleSided", "to": "general.doubleSided"} + ] + } + ], "propertyLayout": { "groups": [ { @@ -92,6 +100,12 @@ ], "properties": { "general": [ + { + "name": "doubleSided", + "displayName": "Double-sided", + "description": "Whether to render back-faces or just front-faces.", + "type": "Bool" + }, { "name": "applySpecularAA", "displayName": "Apply Specular AA", @@ -709,12 +723,6 @@ "name": "m_opacityFactor" } }, - { - "name": "doubleSided", - "displayName": "Double-sided", - "description": "Whether to render back-faces or just front-faces.", - "type": "Bool" - }, { "name": "alphaAffectsSpecular", "displayName": "Alpha affects specular", @@ -1566,15 +1574,6 @@ "shaderOption": "o_baseColor_useTexture" } }, - { - "type": "UseTexture", - "args": { - "textureProperty": "metallic.textureMap", - "useTextureProperty": "metallic.useTexture", - "dependentProperties": ["metallic.textureMapUv"], - "shaderOption": "o_metallic_useTexture" - } - }, { "type": "UseTexture", "args": { @@ -1641,6 +1640,12 @@ "file": "StandardPBR_Roughness.lua" } }, + { + "type": "Lua", + "args": { + "file": "StandardPBR_Metallic.lua" + } + }, { "type": "Lua", "args": { diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR.materialtype b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR.materialtype index 107b525ae4..52befce2b2 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR.materialtype +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR.materialtype @@ -2698,16 +2698,12 @@ } }, { - "type": "UseTexture", + "type": "Lua", "args": { - "textureProperty": "layer1_metallic.textureMap", - "useTextureProperty": "layer1_metallic.useTexture", - "dependentProperties": ["layer1_metallic.textureMapUv"], - "shaderTags": [ - "ForwardPass", - "ForwardPass_EDS" - ], - "shaderOption": "o_layer1_o_metallic_useTexture" + "file": "StandardPBR_Metallic.lua", + "propertyNamePrefix": "layer1_", + "srgNamePrefix": "m_layer1_", + "optionsNamePrefix": "o_layer1_" } }, { @@ -2835,16 +2831,12 @@ } }, { - "type": "UseTexture", + "type": "Lua", "args": { - "textureProperty": "layer2_metallic.textureMap", - "useTextureProperty": "layer2_metallic.useTexture", - "dependentProperties": ["layer2_metallic.textureMapUv"], - "shaderTags": [ - "ForwardPass", - "ForwardPass_EDS" - ], - "shaderOption": "o_layer2_o_metallic_useTexture" + "file": "StandardPBR_Metallic.lua", + "propertyNamePrefix": "layer2_", + "srgNamePrefix": "m_layer2_", + "optionsNamePrefix": "o_layer2_" } }, { @@ -2963,8 +2955,8 @@ "args": { "textureProperty": "layer3_baseColor.textureMap", "useTextureProperty": "layer3_baseColor.useTexture", - "dependentProperties": ["layer3_baseColor.textureMapUv", "layer3_baseColor.textureBlendMode"], - "shaderTags": [ + "dependentProperties": [ "layer3_baseColor.textureMapUv", "layer3_baseColor.textureBlendMode" ], + "shaderTags": [ "ForwardPass", "ForwardPass_EDS" ], @@ -2972,16 +2964,12 @@ } }, { - "type": "UseTexture", + "type": "Lua", "args": { - "textureProperty": "layer3_metallic.textureMap", - "useTextureProperty": "layer3_metallic.useTexture", - "dependentProperties": ["layer3_metallic.textureMapUv"], - "shaderTags": [ - "ForwardPass", - "ForwardPass_EDS" - ], - "shaderOption": "o_layer3_o_metallic_useTexture" + "file": "StandardPBR_Metallic.lua", + "propertyNamePrefix": "layer3_", + "srgNamePrefix": "m_layer3_", + "optionsNamePrefix": "o_layer3_" } }, { diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR.materialtype b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR.materialtype index 7527a7658a..f324394309 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR.materialtype +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR.materialtype @@ -1,6 +1,14 @@ { "description": "Material Type with properties used to define Standard PBR, a metallic-roughness Physically-Based Rendering (PBR) material shading model.", - "version": 3, + "version": 4, + "versionUpdates": [ + { + "toVersion": 4, + "actions": [ + {"op": "rename", "from": "opacity.doubleSided", "to": "general.doubleSided"} + ] + } + ], "propertyLayout": { "groups": [ { @@ -72,6 +80,12 @@ ], "properties": { "general": [ + { + "name": "doubleSided", + "displayName": "Double-sided", + "description": "Whether to render back-faces or just front-faces.", + "type": "Bool" + }, { "name": "applySpecularAA", "displayName": "Apply Specular AA", @@ -650,12 +664,6 @@ "name": "m_opacityFactor" } }, - { - "name": "doubleSided", - "displayName": "Double-sided", - "description": "Whether to render back-faces or just front-faces.", - "type": "Bool" - }, { "name": "alphaAffectsSpecular", "displayName": "Alpha affects specular", @@ -1096,15 +1104,6 @@ "shaderOption": "o_baseColor_useTexture" } }, - { - "type": "UseTexture", - "args": { - "textureProperty": "metallic.textureMap", - "useTextureProperty": "metallic.useTexture", - "dependentProperties": ["metallic.textureMapUv"], - "shaderOption": "o_metallic_useTexture" - } - }, { "type": "UseTexture", "args": { @@ -1171,6 +1170,12 @@ "file": "StandardPBR_Roughness.lua" } }, + { + "type": "Lua", + "args": { + "file": "StandardPBR_Metallic.lua" + } + }, { "type": "Lua", "args": { diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_HandleOpacityDoubleSided.lua b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_HandleOpacityDoubleSided.lua index 2382f3f0f0..9584698532 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_HandleOpacityDoubleSided.lua +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_HandleOpacityDoubleSided.lua @@ -10,14 +10,14 @@ ---------------------------------------------------------------------------------------------------- function GetMaterialPropertyDependencies() - return {"opacity.doubleSided"} + return {"general.doubleSided"} end ForwardPassIndex = 0 ForwardPassEdsIndex = 1 function Process(context) - local doubleSided = context:GetMaterialPropertyValue_bool("opacity.doubleSided") + local doubleSided = context:GetMaterialPropertyValue_bool("general.doubleSided") local lastShader = context:GetShaderCount() - 1; if(doubleSided) then diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_HandleOpacityMode.lua b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_HandleOpacityMode.lua index c86fbfe0b7..9315131e44 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_HandleOpacityMode.lua +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_HandleOpacityMode.lua @@ -81,7 +81,6 @@ function ProcessEditor(context) context:SetMaterialPropertyVisibility("opacity.textureMap", mainVisibility) context:SetMaterialPropertyVisibility("opacity.textureMapUv", mainVisibility) context:SetMaterialPropertyVisibility("opacity.factor", mainVisibility) - context:SetMaterialPropertyVisibility("opacity.doubleSided", mainVisibility) if(opacityMode == OpacityMode_Blended or opacityMode == OpacityMode_TintedTransparent) then context:SetMaterialPropertyVisibility("opacity.alphaAffectsSpecular", MaterialPropertyVisibility_Enabled) diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_Metallic.lua b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_Metallic.lua new file mode 100644 index 0000000000..69ddbf9c57 --- /dev/null +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_Metallic.lua @@ -0,0 +1,43 @@ +-------------------------------------------------------------------------------------- +-- +-- Copyright (c) Contributors to the Open 3D Engine Project. +-- For complete copyright and license terms please see the LICENSE at the root of this distribution. +-- +-- SPDX-License-Identifier: Apache-2.0 OR MIT +-- +-- +-- +---------------------------------------------------------------------------------------------------- + +function GetMaterialPropertyDependencies() + return {"metallic.textureMap", "metallic.useTexture"} +end + +function GetShaderOptionDependencies() + return {"o_metallic_useTexture"} +end + +function Process(context) + local textureMap = context:GetMaterialPropertyValue_Image("metallic.textureMap") + local useTexture = context:GetMaterialPropertyValue_bool("metallic.useTexture") + context:SetShaderOptionValue_bool("o_metallic_useTexture", useTexture and textureMap ~= nil) +end + +function ProcessEditor(context) + local textureMap = context:GetMaterialPropertyValue_Image("metallic.textureMap") + local useTexture = context:GetMaterialPropertyValue_bool("metallic.useTexture") + + if(nil == textureMap) then + context:SetMaterialPropertyVisibility("metallic.useTexture", MaterialPropertyVisibility_Hidden) + context:SetMaterialPropertyVisibility("metallic.textureMapUv", MaterialPropertyVisibility_Hidden) + context:SetMaterialPropertyVisibility("metallic.factor", MaterialPropertyVisibility_Enabled) + elseif(not useTexture) then + context:SetMaterialPropertyVisibility("metallic.useTexture", MaterialPropertyVisibility_Enabled) + context:SetMaterialPropertyVisibility("metallic.textureMapUv", MaterialPropertyVisibility_Disabled) + context:SetMaterialPropertyVisibility("metallic.factor", MaterialPropertyVisibility_Enabled) + else + context:SetMaterialPropertyVisibility("metallic.useTexture", MaterialPropertyVisibility_Enabled) + context:SetMaterialPropertyVisibility("metallic.textureMapUv", MaterialPropertyVisibility_Enabled) + context:SetMaterialPropertyVisibility("metallic.factor", MaterialPropertyVisibility_Hidden) + end +end diff --git a/Gems/Atom/Feature/Common/Assets/Passes/NewDepthOfField.pass b/Gems/Atom/Feature/Common/Assets/Passes/NewDepthOfField.pass new file mode 100644 index 0000000000..70cebab5a1 --- /dev/null +++ b/Gems/Atom/Feature/Common/Assets/Passes/NewDepthOfField.pass @@ -0,0 +1,170 @@ +{ + "Type": "JsonSerialization", + "Version": 1, + "ClassName": "PassAsset", + "ClassData": { + "PassTemplate": { + "Name": "NewDepthOfFieldTemplate", + "PassClass": "NewDepthOfFieldParentPass", + "Slots": [ + { + "Name": "Depth", + "SlotType": "Input" + }, + { + "Name": "LightingBuffer", + "SlotType": "InputOutput", + "ScopeAttachmentUsage": "RenderTarget" + } + ], + "PassRequests": [ + { + "Name": "AutoFocus", + "TemplateName": "DepthOfFieldReadBackFocusDepthTemplate", + "Connections": [ + { + "LocalSlot": "DepthInput", + "AttachmentRef": { + "Pass": "Parent", + "Attachment": "Depth" + } + } + ] + }, + { + "Name": "Downsample", + "TemplateName": "NewDepthOfFieldDownsampleTemplate", + + "Connections": [ + { + "LocalSlot": "ColorInput", + "AttachmentRef": { + "Pass": "Parent", + "Attachment": "LightingBuffer" + } + }, + { + "LocalSlot": "DepthInput", + "AttachmentRef": { + "Pass": "Parent", + "Attachment": "Depth" + } + } + ] + }, + { + "Name": "TileReduce", + "TemplateName": "NewDepthOfFieldTileReduceTemplate", + + "Connections": [ + { + "LocalSlot": "ColorAndCocInput", + "AttachmentRef": { + "Pass": "Downsample", + "Attachment": "OutputColorAndCoC" + } + } + ] + }, + { + "Name": "Tile3x3", + "TemplateName": "NewDepthOfFieldTile3x3Template", + + "Connections": [ + { + "LocalSlot": "Input", + "AttachmentRef": { + "Pass": "TileReduce", + "Attachment": "MinMaxCoC" + } + } + ] + }, + { + "Name": "Tile5x5", + "TemplateName": "NewDepthOfFieldTile3x3Template", + + "Connections": [ + { + "LocalSlot": "Input", + "AttachmentRef": { + "Pass": "Tile3x3", + "Attachment": "Output" + } + } + ] + }, + { + "Name": "LargeFilter", + "TemplateName": "NewDepthOfFieldFilterLargeTemplate", + + "Connections": [ + { + "LocalSlot": "ColorAndCoc", + "AttachmentRef": { + "Pass": "Downsample", + "Attachment": "OutputColorAndCoC" + } + }, + { + "LocalSlot": "CocTile", + "AttachmentRef": { + "Pass": "Tile5x5", + "Attachment": "Output" + } + } + ] + }, + { + "Name": "SmallFilter", + "TemplateName": "NewDepthOfFieldFilterSmallTemplate", + + "Connections": [ + { + "LocalSlot": "ColorAndCoc", + "AttachmentRef": { + "Pass": "LargeFilter", + "Attachment": "OutputColorAndCoc" + } + }, + { + "LocalSlot": "CocTile", + "AttachmentRef": { + "Pass": "Tile3x3", + "Attachment": "Output" + } + } + ] + }, + { + "Name": "Composite", + "TemplateName": "NewDepthOfFieldCompositeTemplate", + + "Connections": [ + { + "LocalSlot": "Depth", + "AttachmentRef": { + "Pass": "Parent", + "Attachment": "Depth" + } + }, + { + "LocalSlot": "HalfResColorAndCoC", + "AttachmentRef": { + "Pass": "SmallFilter", + "Attachment": "OutputColorAndCoc" + } + }, + { + "LocalSlot": "ColorInputOutput", + "AttachmentRef": { + "Pass": "Parent", + "Attachment": "LightingBuffer" + } + } + ] + } + ] + } + } +} diff --git a/Gems/Atom/Feature/Common/Assets/Passes/NewDepthOfFieldComposite.pass b/Gems/Atom/Feature/Common/Assets/Passes/NewDepthOfFieldComposite.pass new file mode 100644 index 0000000000..522ae78fa5 --- /dev/null +++ b/Gems/Atom/Feature/Common/Assets/Passes/NewDepthOfFieldComposite.pass @@ -0,0 +1,37 @@ +{ + "Type": "JsonSerialization", + "Version": 1, + "ClassName": "PassAsset", + "ClassData": { + "PassTemplate": { + "Name": "NewDepthOfFieldCompositeTemplate", + "PassClass": "FullScreenTriangle", + "Slots": [ + { + "Name": "Depth", + "SlotType": "Input", + "ScopeAttachmentUsage": "Shader", + "ShaderImageDimensionsConstant": "m_fullResDimensions" + }, + { + "Name": "HalfResColorAndCoC", + "SlotType": "Input", + "ScopeAttachmentUsage": "Shader", + "ShaderImageDimensionsConstant": "m_halfResDimensions" + }, + { + "Name": "ColorInputOutput", + "SlotType": "InputOutput", + "ScopeAttachmentUsage": "RenderTarget" + } + ], + "PassData": { + "$type": "FullscreenTrianglePassData", + "ShaderAsset": { + "FilePath": "Shaders/PostProcessing/NewDepthOfFieldComposite.shader" + }, + "PipelineViewTag": "MainCamera" + } + } + } +} diff --git a/Gems/Atom/Feature/Common/Assets/Passes/NewDepthOfFieldDownsample.pass b/Gems/Atom/Feature/Common/Assets/Passes/NewDepthOfFieldDownsample.pass new file mode 100644 index 0000000000..2f1787c6cd --- /dev/null +++ b/Gems/Atom/Feature/Common/Assets/Passes/NewDepthOfFieldDownsample.pass @@ -0,0 +1,72 @@ +{ + "Type": "JsonSerialization", + "Version": 1, + "ClassName": "PassAsset", + "ClassData": { + "PassTemplate": { + "Name": "NewDepthOfFieldDownsampleTemplate", + "PassClass": "FullScreenTriangle", + "Slots": [ + { + "Name": "ColorInput", + "SlotType": "Input", + "ScopeAttachmentUsage": "Shader", + "ShaderImageDimensionsConstant": "m_inputDimensions" + }, + { + "Name": "DepthInput", + "SlotType": "Input", + "ScopeAttachmentUsage": "Shader", + "ImageViewDesc": { + "AspectFlags": [ + "Depth" + ] + } + }, + { + "Name": "OutputColorAndCoC", + "SlotType": "Output", + "ScopeAttachmentUsage": "RenderTarget", + "ShaderImageDimensionsConstant": "m_outputDimensions", + "LoadStoreAction": { + "LoadAction": "Clear" + } + } + ], + "ImageAttachments": [ + { + "Name": "OutputAttachment", + "SizeSource": { + "Source": { + "Pass": "This", + "Attachment": "ColorInput" + }, + "Multipliers": { + "WidthMultiplier": 0.5, + "HeightMultiplier": 0.5 + } + }, + "ImageDescriptor": { + "Format": "R16G16B16A16_FLOAT" + } + } + ], + "Connections": [ + { + "LocalSlot": "OutputColorAndCoC", + "AttachmentRef": { + "Pass": "This", + "Attachment": "OutputAttachment" + } + } + ], + "PassData": { + "$type": "FullscreenTrianglePassData", + "ShaderAsset": { + "FilePath": "Shaders/PostProcessing/NewDepthOfFieldDownsample.shader" + }, + "PipelineViewTag": "MainCamera" + } + } + } +} diff --git a/Gems/Atom/Feature/Common/Assets/Passes/NewDepthOfFieldFilterLarge.pass b/Gems/Atom/Feature/Common/Assets/Passes/NewDepthOfFieldFilterLarge.pass new file mode 100644 index 0000000000..7b40009cec --- /dev/null +++ b/Gems/Atom/Feature/Common/Assets/Passes/NewDepthOfFieldFilterLarge.pass @@ -0,0 +1,62 @@ +{ + "Type": "JsonSerialization", + "Version": 1, + "ClassName": "PassAsset", + "ClassData": { + "PassTemplate": { + "Name": "NewDepthOfFieldFilterLargeTemplate", + "PassClass": "NewDepthOfFieldFilterPass", + "Slots": [ + { + "Name": "ColorAndCoc", + "SlotType": "Input", + "ScopeAttachmentUsage": "Shader", + "ShaderImageDimensionsConstant": "m_textureDimensions" + }, + { + "Name": "CocTile", + "SlotType": "Input", + "ScopeAttachmentUsage": "Shader" + }, + { + "Name": "OutputColorAndCoc", + "SlotType": "Output", + "ScopeAttachmentUsage": "RenderTarget", + "LoadStoreAction": { + "LoadAction": "Clear" + } + } + ], + "ImageAttachments": [ + { + "Name": "OutputAttachment", + "SizeSource": { + "Source": { + "Pass": "This", + "Attachment": "ColorAndCoc" + } + }, + "ImageDescriptor": { + "Format": "R16G16B16A16_FLOAT" + } + } + ], + "Connections": [ + { + "LocalSlot": "OutputColorAndCoc", + "AttachmentRef": { + "Pass": "This", + "Attachment": "OutputAttachment" + } + } + ], + "PassData": { + "$type": "FullscreenTrianglePassData", + "ShaderAsset": { + "FilePath": "Shaders/PostProcessing/NewDepthOfFieldFilterLarge.shader" + }, + "PipelineViewTag": "MainCamera" + } + } + } +} diff --git a/Gems/Atom/Feature/Common/Assets/Passes/NewDepthOfFieldFilterSmall.pass b/Gems/Atom/Feature/Common/Assets/Passes/NewDepthOfFieldFilterSmall.pass new file mode 100644 index 0000000000..35a658a3b1 --- /dev/null +++ b/Gems/Atom/Feature/Common/Assets/Passes/NewDepthOfFieldFilterSmall.pass @@ -0,0 +1,62 @@ +{ + "Type": "JsonSerialization", + "Version": 1, + "ClassName": "PassAsset", + "ClassData": { + "PassTemplate": { + "Name": "NewDepthOfFieldFilterSmallTemplate", + "PassClass": "NewDepthOfFieldFilterPass", + "Slots": [ + { + "Name": "ColorAndCoc", + "SlotType": "Input", + "ScopeAttachmentUsage": "Shader", + "ShaderImageDimensionsConstant": "m_textureDimensions" + }, + { + "Name": "CocTile", + "SlotType": "Input", + "ScopeAttachmentUsage": "Shader" + }, + { + "Name": "OutputColorAndCoc", + "SlotType": "Output", + "ScopeAttachmentUsage": "RenderTarget", + "LoadStoreAction": { + "LoadAction": "Clear" + } + } + ], + "ImageAttachments": [ + { + "Name": "OutputAttachment", + "SizeSource": { + "Source": { + "Pass": "This", + "Attachment": "ColorAndCoc" + } + }, + "ImageDescriptor": { + "Format": "R16G16B16A16_FLOAT" + } + } + ], + "Connections": [ + { + "LocalSlot": "OutputColorAndCoc", + "AttachmentRef": { + "Pass": "This", + "Attachment": "OutputAttachment" + } + } + ], + "PassData": { + "$type": "FullscreenTrianglePassData", + "ShaderAsset": { + "FilePath": "Shaders/PostProcessing/NewDepthOfFieldFilterSmall.shader" + }, + "PipelineViewTag": "MainCamera" + } + } + } +} diff --git a/Gems/Atom/Feature/Common/Assets/Passes/NewDepthOfFieldTile3x3.pass b/Gems/Atom/Feature/Common/Assets/Passes/NewDepthOfFieldTile3x3.pass new file mode 100644 index 0000000000..93ae645c83 --- /dev/null +++ b/Gems/Atom/Feature/Common/Assets/Passes/NewDepthOfFieldTile3x3.pass @@ -0,0 +1,57 @@ +{ + "Type": "JsonSerialization", + "Version": 1, + "ClassName": "PassAsset", + "ClassData": { + "PassTemplate": { + "Name": "NewDepthOfFieldTile3x3Template", + "PassClass": "FullScreenTriangle", + "Slots": [ + { + "Name": "Input", + "SlotType": "Input", + "ScopeAttachmentUsage": "Shader", + "ShaderImageDimensionsConstant": "m_textureDimensions" + }, + { + "Name": "Output", + "SlotType": "Output", + "ScopeAttachmentUsage": "RenderTarget", + "LoadStoreAction": { + "LoadAction": "Clear" + } + } + ], + "ImageAttachments": [ + { + "Name": "OutputAttachment", + "SizeSource": { + "Source": { + "Pass": "This", + "Attachment": "Input" + } + }, + "ImageDescriptor": { + "Format": "R16G16_SNORM" + } + } + ], + "Connections": [ + { + "LocalSlot": "Output", + "AttachmentRef": { + "Pass": "This", + "Attachment": "OutputAttachment" + } + } + ], + "PassData": { + "$type": "FullscreenTrianglePassData", + "ShaderAsset": { + "FilePath": "Shaders/PostProcessing/NewDepthOfFieldTile3x3.shader" + }, + "PipelineViewTag": "MainCamera" + } + } + } +} diff --git a/Gems/Atom/Feature/Common/Assets/Passes/NewDepthOfFieldTileReduce.pass b/Gems/Atom/Feature/Common/Assets/Passes/NewDepthOfFieldTileReduce.pass new file mode 100644 index 0000000000..2bcf0ef32d --- /dev/null +++ b/Gems/Atom/Feature/Common/Assets/Passes/NewDepthOfFieldTileReduce.pass @@ -0,0 +1,63 @@ +{ + "Type": "JsonSerialization", + "Version": 1, + "ClassName": "PassAsset", + "ClassData": { + "PassTemplate": { + "Name": "NewDepthOfFieldTileReduceTemplate", + "PassClass": "NewDepthOfFieldTileReducePass", + "Slots": [ + { + "Name": "ColorAndCocInput", + "SlotType": "Input", + "ScopeAttachmentUsage": "Shader", + "ShaderImageDimensionsConstant": "m_inputDimensions" + }, + { + "Name": "MinMaxCoC", + "SlotType": "Output", + "ScopeAttachmentUsage": "Shader", + "ShaderImageDimensionsConstant": "m_outputDimensions", + "LoadStoreAction": { + "LoadAction": "Clear" + } + } + ], + "ImageAttachments": [ + { + "Name": "MinMaxCoCAttachment", + "SizeSource": { + "Source": { + "Pass": "This", + "Attachment": "ColorAndCocInput" + }, + "Multipliers": { + // 1/16 = 0.0625 + "WidthMultiplier": 0.0625, + "HeightMultiplier": 0.0625 + } + }, + "ImageDescriptor": { + "Format": "R16G16_SNORM" + } + } + ], + "Connections": [ + { + "LocalSlot": "MinMaxCoC", + "AttachmentRef": { + "Pass": "This", + "Attachment": "MinMaxCoCAttachment" + } + } + ], + "PassData": { + "$type": "ComputePassData", + "ShaderAsset": { + "FilePath": "Shaders/PostProcessing/NewDepthOfFieldTileReduce.shader" + }, + "PipelineViewTag": "MainCamera" + } + } + } +} diff --git a/Gems/Atom/Feature/Common/Assets/Passes/PassTemplates.azasset b/Gems/Atom/Feature/Common/Assets/Passes/PassTemplates.azasset index 81b7d36484..f2df085228 100644 --- a/Gems/Atom/Feature/Common/Assets/Passes/PassTemplates.azasset +++ b/Gems/Atom/Feature/Common/Assets/Passes/PassTemplates.azasset @@ -185,6 +185,34 @@ "Path": "Passes/DepthOfFieldWriteFocusDepthFromGpu.pass" }, { + "Name": "NewDepthOfFieldTemplate", + "Path": "Passes/NewDepthOfField.pass" + }, + { + "Name": "NewDepthOfFieldDownsampleTemplate", + "Path": "Passes/NewDepthOfFieldDownsample.pass" + }, + { + "Name": "NewDepthOfFieldTileReduceTemplate", + "Path": "Passes/NewDepthOfFieldTileReduce.pass" + }, + { + "Name": "NewDepthOfFieldTile3x3Template", + "Path": "Passes/NewDepthOfFieldTile3x3.pass" + }, + { + "Name": "NewDepthOfFieldFilterLargeTemplate", + "Path": "Passes/NewDepthOfFieldFilterLarge.pass" + }, + { + "Name": "NewDepthOfFieldFilterSmallTemplate", + "Path": "Passes/NewDepthOfFieldFilterSmall.pass" + }, + { + "Name": "NewDepthOfFieldCompositeTemplate", + "Path": "Passes/NewDepthOfFieldComposite.pass" + }, + { "Name": "EsmShadowmapsTemplate", "Path": "Passes/EsmShadowmaps.pass" }, diff --git a/Gems/Atom/Feature/Common/Assets/Passes/PostProcessParent.pass b/Gems/Atom/Feature/Common/Assets/Passes/PostProcessParent.pass index fb27770da3..67c5072513 100644 --- a/Gems/Atom/Feature/Common/Assets/Passes/PostProcessParent.pass +++ b/Gems/Atom/Feature/Common/Assets/Passes/PostProcessParent.pass @@ -112,20 +112,42 @@ } ] }, + // Todo: remove the old depth of field implementation and rename NewDepthOfField -> DepthOfField + //{ + // "Name": "DepthOfFieldPass", + // "TemplateName": "DepthOfFieldTemplate", + // "Enabled": true, + // "Connections": [ + // { + // "LocalSlot": "DoFColorInput", + // "AttachmentRef": { + // "Pass": "TaaPass", + // "Attachment": "OutputColor" + // } + // }, + // { + // "LocalSlot": "DoFDepthInput", + // "AttachmentRef": { + // "Pass": "Parent", + // "Attachment": "Depth" + // } + // } + // ] + //}, { "Name": "DepthOfFieldPass", - "TemplateName": "DepthOfFieldTemplate", + "TemplateName": "NewDepthOfFieldTemplate", "Enabled": true, "Connections": [ { - "LocalSlot": "DoFColorInput", + "LocalSlot": "LightingBuffer", "AttachmentRef": { "Pass": "TaaPass", "Attachment": "OutputColor" } }, { - "LocalSlot": "DoFDepthInput", + "LocalSlot": "Depth", "AttachmentRef": { "Pass": "Parent", "Attachment": "Depth" @@ -142,7 +164,7 @@ "LocalSlot": "InputOutput", "AttachmentRef": { "Pass": "DepthOfFieldPass", - "Attachment": "DoFOutput" + "Attachment": "LightingBuffer" } } ] diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/PostProcessing/DepthOfField.azsli b/Gems/Atom/Feature/Common/Assets/Shaders/PostProcessing/DepthOfField.azsli index bc94b4a793..f4c396253f 100644 --- a/Gems/Atom/Feature/Common/Assets/Shaders/PostProcessing/DepthOfField.azsli +++ b/Gems/Atom/Feature/Common/Assets/Shaders/PostProcessing/DepthOfField.azsli @@ -14,6 +14,14 @@ inline float InvertDepth(float depth) return 1.0f - depth; } +inline float4 InvertDepth(float4 depth) +{ + // Convert depth from [1.0 - 0.0] to [0.0 - 1.0]. + // Set the front(near side) to 0.0 and the back(far side) to 1.0. + + return float4(1, 1, 1, 1) - depth; +} + inline float ConvertDofFactor(float depth, float far, float near, float focusDistance) { // dofFactor : The value Calculated from depth. diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/PostProcessing/NewDepthOfFieldCommon.azsli b/Gems/Atom/Feature/Common/Assets/Shaders/PostProcessing/NewDepthOfFieldCommon.azsli new file mode 100644 index 0000000000..cd0f334f29 --- /dev/null +++ b/Gems/Atom/Feature/Common/Assets/Shaders/PostProcessing/NewDepthOfFieldCommon.azsli @@ -0,0 +1,27 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + + +#include + +#define COC_EPSILON 0.0001 + +#define SAMPLES_LOOP_1 8 +#define SAMPLES_LOOP_2 16 +#define SAMPLES_LOOP_3 24 + +#define SAMPLES_LOOP_TOTAL 48 + +// Must match the struct in NewDepthOfFieldPasses.cpp +struct NewDepthOfFieldConstants +{ + float4 samplePositions[60]; // XY are sample positions (normalized so max lenght is 1) + // Z is the length of XY (0 - 1) + // W is unused +}; + diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/PostProcessing/NewDepthOfFieldComposite.azsl b/Gems/Atom/Feature/Common/Assets/Shaders/PostProcessing/NewDepthOfFieldComposite.azsl new file mode 100644 index 0000000000..468f93db78 --- /dev/null +++ b/Gems/Atom/Feature/Common/Assets/Shaders/PostProcessing/NewDepthOfFieldComposite.azsl @@ -0,0 +1,120 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include +#include +#include "NewDepthOfFieldCommon.azsli" +#include "DepthOfField.azsli" + +#include + +#define COC_EPSILON 0.0001 + +ShaderResourceGroup PassSrg : SRG_PerPass +{ + Texture2D m_depth; + Texture2D m_halfResColorAndCoc; + + // Texture dimensions. XY channels are width and height and ZW channels are 1 / width and 1 / height + // Auto-filled by the pass system when "ShaderImageDimensionsConstant" is specified in the .pass file + float4 m_fullResDimensions; + float4 m_halfResDimensions; + + Sampler LinearSampler + { + MinFilter = Linear; + MagFilter = Linear; + MipFilter = Linear; + AddressU = Clamp; + AddressV = Clamp; + AddressW = Clamp; + }; +} + +PSOutput MainPS(VSOutput IN) +{ + // Sampling positions + float2 fullResPixelPos = IN.m_position.xy; + float2 halfResPixelPos = fullResPixelPos * 0.5f; + float2 fullResUV = fullResPixelPos * PassSrg::m_fullResDimensions.zw; + float2 halfResUV = halfResPixelPos * PassSrg::m_halfResDimensions.zw; + + // Full res CoC (Circle of Confusion) + float depth = PassSrg::m_depth.Sample(PassSrg::LinearSampler, fullResUV); + float far = ViewSrg::m_dof.m_cameraParameters.x; + float near = ViewSrg::m_dof.m_cameraParameters.y; + float focusDistance = ViewSrg::m_dof.m_cameraParameters.z; + float coc = ConvertDofFactor(InvertDepth(depth), far, near, focusDistance); + + // --- Weights based on CoC similarity --- + + // Gather CoCs + float4 cocGather = PassSrg::m_halfResColorAndCoc.GatherAlpha(PassSrg::LinearSampler, halfResUV); + + // Calculate differences + float4 diff = saturate(cocGather - coc); + + // Slide differences such that small difference (i.e. most similar CoC) will become 0 + // (which then gets inverted in the next step) + float minDiff = min4(diff); + diff -= minDiff; + + // Invert the differences with a slope multiplier of 2 + float4 cocDiffWeights = saturate(1 - (2 * diff)); + + // --- Weights based on pixel proximity --- + + // Based on which pixel we're shading, we'll be closer/farther to half res pixels + // Here are the pre-calculated weights, arranged to match the Gather pattern + // + // W Z + // X Y + // + // Note: These weights come down to the same contributions as if we did a linear sample + int2 pixel = int2(fullResPixelPos); + float4 weights = (pixel.x & 1) + ? ( (pixel.y & 1) ? float4(0.1875f, 0.0625f, 0.1875f, 0.5625f) + : float4(0.5625f, 0.1875f, 0.0625f, 0.1875f) ) + : ( (pixel.y & 1) ? float4(0.0625f, 0.1875f, 0.5625f, 0.1875f) + : float4(0.1875f, 0.5625f, 0.1875f, 0.0625f) ); + + // Combine and normalize weights + weights *= cocDiffWeights; + weights /= (weights.x + weights.y + weights.z + weights.w); + + // --- Color --- + + // For each color channel, do a gather and multiply the samples by the weights calculated above + float3 color; + float4 red = PassSrg::m_halfResColorAndCoc.GatherRed(PassSrg::LinearSampler, halfResUV); + color.r = dot(red, weights); + float4 blue = PassSrg::m_halfResColorAndCoc.GatherBlue(PassSrg::LinearSampler, halfResUV); + color.b = dot(blue, weights); + float4 green = PassSrg::m_halfResColorAndCoc.GatherGreen(PassSrg::LinearSampler, halfResUV); + color.g = dot(green, weights); + + + // --- Alpha --- + + // Calculate alpha such that we fully take the half res texture value if the CoC of the full + // resolution pixel is greater than the size of a half resolution pixel + float cocRadius = abs(coc) * ViewSrg::m_dof.m_cocToScreenRatio * 0.5f; + float alpha = saturate(cocRadius / PassSrg::m_halfResDimensions.w); + + // We may have objects in focus (CoC = 0) but that receive contribution from background bokeh + // (which have a negative CoC that we calculated in the large filter). Take the max here. + float minCoc = min4(cocGather); + alpha = max(alpha, -minCoc); + + PSOutput OUT; + OUT.m_color.rgb = color.rgb; + OUT.m_color.a = alpha; + return OUT; +} + diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/PostProcessing/NewDepthOfFieldComposite.shader b/Gems/Atom/Feature/Common/Assets/Shaders/PostProcessing/NewDepthOfFieldComposite.shader new file mode 100644 index 0000000000..42130d9ba8 --- /dev/null +++ b/Gems/Atom/Feature/Common/Assets/Shaders/PostProcessing/NewDepthOfFieldComposite.shader @@ -0,0 +1,31 @@ +{ + "Source" : "NewDepthOfFieldComposite.azsl", + + "DepthStencilState" : + { + "Depth" : { "Enable" : false }, + "Stencil" : { "Enable" : false } + }, + + "BlendState" : { + "Enable" : true, + "BlendSource" : "AlphaSource", + "BlendDest" : "AlphaSourceInverse", + "BlendOp" : "Add" + }, + + "ProgramSettings": + { + "EntryPoints": + [ + { + "name": "MainVS", + "type": "Vertex" + }, + { + "name": "MainPS", + "type": "Fragment" + } + ] + } +} diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/PostProcessing/NewDepthOfFieldDownsample.azsl b/Gems/Atom/Feature/Common/Assets/Shaders/PostProcessing/NewDepthOfFieldDownsample.azsl new file mode 100644 index 0000000000..01ff4b9493 --- /dev/null +++ b/Gems/Atom/Feature/Common/Assets/Shaders/PostProcessing/NewDepthOfFieldDownsample.azsl @@ -0,0 +1,101 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include +#include +#include "NewDepthOfFieldCommon.azsli" +#include "DepthOfField.azsli" + +#include + +#define COC_EPSILON 0.0001 + +ShaderResourceGroup PassSrg : SRG_PerPass +{ + Texture2D m_colorTexture; + Texture2D m_depth; + + // Texture dimensions. XY channels are width and height and ZW channels are 1 / width and 1 / height + // Auto-filled by the pass system when "ShaderImageDimensionsConstant" is specified in the .pass file + float4 m_inputDimensions; + float4 m_outputDimensions; + + Sampler PointSampler + { + MinFilter = Point; + MagFilter = Point; + MipFilter = Point; + AddressU = Clamp; + AddressV = Clamp; + AddressW = Clamp; + }; +} + +PSOutput MainPS(VSOutput IN) +{ + // Sampling positions + float2 outputPixelPos = IN.m_position.xy; + float2 inputPixelPos = outputPixelPos * 2.0f; + float2 inputUV = inputPixelPos * PassSrg::m_inputDimensions.zw; + + // Gather Depth + float4 depthGather = PassSrg::m_depth.Gather(PassSrg::PointSampler, inputUV); + depthGather = InvertDepth(depthGather); + + // Calculate CoC (Circle of Confusion) + float far = ViewSrg::m_dof.m_cameraParameters.x; + float near = ViewSrg::m_dof.m_cameraParameters.y; + float focusDistance = ViewSrg::m_dof.m_cameraParameters.z; + + float4 cocGather; + cocGather.x = ConvertDofFactor(depthGather.x, far, near, focusDistance); + cocGather.y = ConvertDofFactor(depthGather.y, far, near, focusDistance); + cocGather.z = ConvertDofFactor(depthGather.z, far, near, focusDistance); + cocGather.w = ConvertDofFactor(depthGather.w, far, near, focusDistance); + + // Clamp CoC + cocGather = clamp(cocGather, -1.0f, 1.0f); + + // Weight samples by CoC to avoid in focus pixels bleeding into bokeh effect + float4 weights = abs(cocGather) + COC_EPSILON; + weights = weights / (weights.x + weights.y + weights.z + weights.w); + + PSOutput OUT; + + // Red + float4 redGather = PassSrg::m_colorTexture.GatherRed(PassSrg::PointSampler, inputUV); + OUT.m_color.r = dot(redGather, weights); + + // Green + float4 greenGather = PassSrg::m_colorTexture.GatherGreen(PassSrg::PointSampler, inputUV); + OUT.m_color.g = dot(greenGather, weights); + + // Blue + float4 blueGather = PassSrg::m_colorTexture.GatherBlue(PassSrg::PointSampler, inputUV); + OUT.m_color.b = dot(blueGather, weights); + + // CoC - Take the CoC with the maximum absolute value (note CoC can be negative) to get the fullest bokeh effect + // The above weighting by CoC mitigates bokeh bleeding from taking the max CoC + float coc = cocGather.x; + coc = abs(coc) < abs(cocGather.y) ? cocGather.y : coc; + coc = abs(coc) < abs(cocGather.z) ? cocGather.z : coc; + coc = abs(coc) < abs(cocGather.w) ? cocGather.w : coc; + + // CoC weighting #2: we use linear sampling when we compute the CoC blur + // This can lead to in focus pixels bleeding into the bokeh blur + // Pre-multiply the color values by the CoC to avoid this type of bleeding + // Because we then re-multiply by the CoC value after the linear sampling, we want to avoid coc values of 0 + // See http://advances.realtimerendering.com/s2013/Sousa_Graphics_Gems_CryENGINE3.pptx + coc = abs(coc) > COC_EPSILON ? coc : -COC_EPSILON; + OUT.m_color.rgb *= abs(coc); + OUT.m_color.a = coc; + + + return OUT; +} diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/PostProcessing/NewDepthOfFieldDownsample.shader b/Gems/Atom/Feature/Common/Assets/Shaders/PostProcessing/NewDepthOfFieldDownsample.shader new file mode 100644 index 0000000000..8a9fe62fd7 --- /dev/null +++ b/Gems/Atom/Feature/Common/Assets/Shaders/PostProcessing/NewDepthOfFieldDownsample.shader @@ -0,0 +1,22 @@ +{ + "Source" : "NewDepthOfFieldDownsample.azsl", + + "DepthStencilState" : { + "Depth" : { "Enable" : false } + }, + + "ProgramSettings": + { + "EntryPoints": + [ + { + "name": "MainVS", + "type": "Vertex" + }, + { + "name": "MainPS", + "type": "Fragment" + } + ] + } +} diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/PostProcessing/NewDepthOfFieldFilterLarge.azsl b/Gems/Atom/Feature/Common/Assets/Shaders/PostProcessing/NewDepthOfFieldFilterLarge.azsl new file mode 100644 index 0000000000..3b979b9c39 --- /dev/null +++ b/Gems/Atom/Feature/Common/Assets/Shaders/PostProcessing/NewDepthOfFieldFilterLarge.azsl @@ -0,0 +1,170 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include +#include "DepthOfField.azsli" +#include "NewDepthOfFieldCommon.azsli" + +#include + +ShaderResourceGroup PassSrg : SRG_PerPass +{ + Texture2D m_colorAndCoc; + Texture2D m_minMaxCocTile; + + // Texture dimensions. XY channels are width and height and ZW channels are 1 / width and 1 / height + // Auto-filled by the pass system when "ShaderImageDimensionsConstant" is specified in the .pass file + float4 m_textureDimensions; + + NewDepthOfFieldConstants m_dofConstants; + + Sampler LinearSampler + { + MinFilter = Linear; + MagFilter = Linear; + MipFilter = Linear; + AddressU = Clamp; + AddressV = Clamp; + AddressW = Clamp; + }; + + Sampler PointSampler + { + MinFilter = Point; + MagFilter = Point; + MipFilter = Point; + AddressU = Clamp; + AddressV = Clamp; + AddressW = Clamp; + }; +} + +float3 GetOffset(uint index, float2 offsetUVMultiplier) +{ + float3 offset = PassSrg::m_dofConstants.samplePositions[index].xyz; + offset.xy *= offsetUVMultiplier; + return offset; +} + +float CalculateWeight(float offsetRadius, float samplingRadius, float sampleCoc, float centerCoc) +{ + // The maximum distance for which samples are valid is the min of the sample CoC and the center CoC + float maxRadius = abs(min(sampleCoc, centerCoc)); + + // Easy human readable calculations: + // radius = samplingRadius * offsetRadius; + // falloff = maxRadius - radius; + // weight = 1 + (4 * falloff) + // + // The same thing in mad form: + float falloff = mad(-samplingRadius, offsetRadius, maxRadius); + return saturate(mad(4, falloff, 1)); +} + +// This shader blurs by sampling 48 pixels in a circle around the center pixel +// See http://advances.realtimerendering.com/s2013/Sousa_Graphics_Gems_CryENGINE3.pptx +// for a detailed explanation. +PSOutput MainPS(VSOutput IN) +{ + // Get center sample + float2 pixelUV = IN.m_texCoord; + float4 color = PassSrg::m_colorAndCoc.Sample(PassSrg::LinearSampler, pixelUV).rgba; + float centerCoc = color.a; + + // Get tile min CoC + int2 tile = int2(IN.m_position.xy) / 16; + float minCoc = PassSrg::m_minMaxCocTile[tile].x; + + // Aspect ratio is needed because sample offsets are calculated in a perfect circle, but + // UV space is stretched due to normalized device coordinates. Correct this with aspect ratio + // Aspect ratio = texture.x / texture.y = dimensions.x * dimensions.w + float aspectRatio = PassSrg::m_textureDimensions.x * PassSrg::m_textureDimensions.w; + + // Sampling radius + float cocRadius = max( abs(centerCoc), -minCoc); + float screenRadius = cocRadius * ViewSrg::m_dof.m_cocToScreenRatio * 0.5f; + float2 offsetMultiplier = float2(screenRadius / aspectRatio, screenRadius); + + // Background samples are samples behind the current pixel. Because of how depth of field works, + // these pixels can contribute to the center pixel even if they are out of range of the center pixel's CoC + // We accumulate them seperately and calculate a new estimated alpha value based on the ratio of samples + // that were background pixels. + float4 backgroundColor = float4(0, 0, 0, 0); + + // If there are only positive CoCs in our region, we don't need to consider background blur + // Do the faster and nicer approach + if(minCoc >= 0) + { + for(uint i = 0; i < SAMPLES_LOOP_TOTAL; ++i) + { + // Calculate sample offset + float3 offset = GetOffset(i, offsetMultiplier); + + // Get sample + float4 sampleColorCoc = PassSrg::m_colorAndCoc.Sample(PassSrg::LinearSampler, pixelUV + offset.xy).rgba; + + // Calculate weight for sample + float weight = CalculateWeight(offset.z, cocRadius, sampleColorCoc.a, centerCoc); + + // Accumulate + color += weight * sampleColorCoc; + } + } + else // Some CoCs in the region are negative, need to consider possible background bokeh contribution + { + // Distance behind which samples are considered background samples + float backgroundMin = min(0, centerCoc); + + // Linear sampling colors pre-multiplied with CoC yields artefacts when combined with this background technique + // We therefore do point sampling and unpack the original color value per sample + color.rgb /= abs(color.a); + color.a = 1; + + for(uint i = 0; i < SAMPLES_LOOP_TOTAL; ++i) + { + // Calculate sample offset + float3 offset = GetOffset(i, offsetMultiplier); + + // Get sample + unpack + float4 sampleColorCoc = PassSrg::m_colorAndCoc.Sample(PassSrg::PointSampler, pixelUV + offset.xy).rgba; + sampleColorCoc.rgb /= abs(sampleColorCoc.a); + + // Calculate weight for sample + float weight = CalculateWeight(offset.z, cocRadius, sampleColorCoc.a, centerCoc); + sampleColorCoc.rgb *= weight; + + bool isBackground = (sampleColorCoc.a < backgroundMin); + + // We accumulate weight in alpha channel of color and backgroundColor + sampleColorCoc.a = weight; + + // Accumulate + backgroundColor += isBackground * sampleColorCoc; + color += !isBackground * sampleColorCoc; + } + + // Average background samples + backgroundColor.rgb /= max(backgroundColor.a, COC_EPSILON); + } + + // Calculate background ratio. If greater than the current CoC, replace the current CoC with + // background ratio. This is so background bokeh effects will still render on in-focus objects + float backgroundRatio = saturate( backgroundColor.a / float(SAMPLES_LOOP_TOTAL) ); + float alpha = backgroundRatio > abs(centerCoc) ? -backgroundRatio : centerCoc; + + // Average accumulated color samples and combine with background samples + color.rgb /= max(color.a, COC_EPSILON); + color = lerp(color, backgroundColor, backgroundRatio); + + PSOutput OUT = (PSOutput)0; + OUT.m_color.rgb = color.rgb; + OUT.m_color.a = alpha; + return OUT; +} + diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/PostProcessing/NewDepthOfFieldFilterLarge.shader b/Gems/Atom/Feature/Common/Assets/Shaders/PostProcessing/NewDepthOfFieldFilterLarge.shader new file mode 100644 index 0000000000..f11ac01e5c --- /dev/null +++ b/Gems/Atom/Feature/Common/Assets/Shaders/PostProcessing/NewDepthOfFieldFilterLarge.shader @@ -0,0 +1,22 @@ +{ + "Source" : "NewDepthOfFieldFilterLarge.azsl", + + "DepthStencilState" : { + "Depth" : { "Enable" : false } + }, + + "ProgramSettings": + { + "EntryPoints": + [ + { + "name": "MainVS", + "type": "Vertex" + }, + { + "name": "MainPS", + "type": "Fragment" + } + ] + } +} diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/PostProcessing/NewDepthOfFieldFilterSmall.azsl b/Gems/Atom/Feature/Common/Assets/Shaders/PostProcessing/NewDepthOfFieldFilterSmall.azsl new file mode 100644 index 0000000000..38762254a8 --- /dev/null +++ b/Gems/Atom/Feature/Common/Assets/Shaders/PostProcessing/NewDepthOfFieldFilterSmall.azsl @@ -0,0 +1,121 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include +#include "DepthOfField.azsli" +#include "NewDepthOfFieldCommon.azsli" + +#include + +ShaderResourceGroup PassSrg : SRG_PerPass +{ + Texture2D m_colorAndCoc; + Texture2D m_minMaxCocTile; + + // Texture dimensions. XY channels are width and height and ZW channels are 1 / width and 1 / height + // Auto-filled by the pass system when "ShaderImageDimensionsConstant" is specified in the .pass file + float4 m_textureDimensions; + + NewDepthOfFieldConstants m_dofConstants; + + Sampler LinearSampler + { + MinFilter = Linear; + MagFilter = Linear; + MipFilter = Linear; + AddressU = Clamp; + AddressV = Clamp; + AddressW = Clamp; + }; + + Sampler PointSampler + { + MinFilter = Point; + MagFilter = Point; + MipFilter = Point; + AddressU = Clamp; + AddressV = Clamp; + AddressW = Clamp; + }; +} + +float3 GetOffset(uint index, float2 offsetUVMultiplier) +{ + float3 offset = PassSrg::m_dofConstants.samplePositions[index].xyz; + offset.xy *= offsetUVMultiplier; + return offset; +} + +float CalculateWeight(float offsetRadius, float samplingRadius, float sampleCoc, float centerCoc) +{ + // The maximum distance for which samples are valid is the min of the sample CoC and the center CoC + float maxRadius = abs(min(sampleCoc, centerCoc)); + + // Easy human readable calculations: + // radius = samplingRadius * offsetRadius; + // falloff = maxRadius - radius; + // weight = 1 + (4 * falloff) + // + // The same thing in mad form: + float falloff = mad(-samplingRadius, offsetRadius, maxRadius); + return saturate(mad(4, falloff, 1)); +} + +// This shader attempts to fill the gaps left by the large filter by sampling 8 pixels around the center pixel +// See http://advances.realtimerendering.com/s2013/Sousa_Graphics_Gems_CryENGINE3.pptx +// for a detailed overview of the technique +PSOutput MainPS(VSOutput IN) +{ + // Get center sample + float2 pixelUV = IN.m_texCoord.xy; + float4 color = PassSrg::m_colorAndCoc.Sample(PassSrg::PointSampler, pixelUV).rgba; + float centerCoc = color.a; + + // Get tile min CoC + int2 tile = int2(IN.m_position.xy) / 16; + float minCoc = PassSrg::m_minMaxCocTile[tile].x; + + // Aspect ratio is needed because sample offsets are calculated in a perfect circle, but + // UV space is stretched due to normalized device coordinates. Correct this with aspect ratio + // Aspect ratio = texture.x / texture.y = dimensions.x * dimensions.w + float aspectRatio = PassSrg::m_textureDimensions.x * PassSrg::m_textureDimensions.w; + + // Sampling radius + float cocRadius = max( abs(centerCoc), -minCoc) * 0.5f; // Small filter pass so half the radius + float screenRadius = cocRadius * ViewSrg::m_dof.m_cocToScreenRatio * 0.5f; + float2 offsetMultiplier = float2(screenRadius / aspectRatio, screenRadius); + + // Weight accumulation. Start with 1 for center pixel. + float totalWeight = 1; + + for(uint i = 0; i < SAMPLES_LOOP_1; ++i) + { + // Calculate sample offset + float3 offset = GetOffset(i, offsetMultiplier); + + // Get sample + float4 sampleColorCoc = PassSrg::m_colorAndCoc.Sample(PassSrg::PointSampler, pixelUV + offset.xy).rgba; + + // Calculate weight + float weight = CalculateWeight(offset.z, cocRadius, sampleColorCoc.a, centerCoc); + + // Accumulate sample and weight + color.rgb += sampleColorCoc.rgb * weight; + totalWeight += weight; + } + + + // Normalize accumulated sample + color.rgb /= totalWeight; + + PSOutput OUT; + OUT.m_color.rgb = color.rgb; + OUT.m_color.a = centerCoc; + return OUT; +} diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/PostProcessing/NewDepthOfFieldFilterSmall.shader b/Gems/Atom/Feature/Common/Assets/Shaders/PostProcessing/NewDepthOfFieldFilterSmall.shader new file mode 100644 index 0000000000..17387a4390 --- /dev/null +++ b/Gems/Atom/Feature/Common/Assets/Shaders/PostProcessing/NewDepthOfFieldFilterSmall.shader @@ -0,0 +1,22 @@ +{ + "Source" : "NewDepthOfFieldFilterSmall.azsl", + + "DepthStencilState" : { + "Depth" : { "Enable" : false } + }, + + "ProgramSettings": + { + "EntryPoints": + [ + { + "name": "MainVS", + "type": "Vertex" + }, + { + "name": "MainPS", + "type": "Fragment" + } + ] + } +} diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/PostProcessing/NewDepthOfFieldTile3x3.azsl b/Gems/Atom/Feature/Common/Assets/Shaders/PostProcessing/NewDepthOfFieldTile3x3.azsl new file mode 100644 index 0000000000..6c56b0b79c --- /dev/null +++ b/Gems/Atom/Feature/Common/Assets/Shaders/PostProcessing/NewDepthOfFieldTile3x3.azsl @@ -0,0 +1,68 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include +#include "NewDepthOfFieldCommon.azsli" + +ShaderResourceGroup PassSrg : SRG_PerPass +{ + Texture2D m_minMaxSource; + + // Texture dimensions. XY channels are width and height and ZW channels are 1 / width and 1 / height + // Auto-filled by the pass system when "ShaderImageDimensionsConstant" is specified in the .pass file + float4 m_textureDimensions; + + Sampler PointSampler + { + MinFilter = Point; + MagFilter = Point; + MipFilter = Point; + AddressU = Clamp; + AddressV = Clamp; + AddressW = Clamp; + }; +} + +struct PSOutput +{ + float2 m_color : SV_Target0; +}; + +// Expands the min and max tiles so each tile contains the min and max of it's 3x3 neighborhood +PSOutput MainPS(VSOutput IN) +{ + // We want the min/max in a 3x3 region. Start sampling up left. + float2 startPixelPos = IN.m_position.xy - float2(1, 1); + + float2 pixelSizeInUV = PassSrg::m_textureDimensions.zw; + float2 startUV = startPixelPos * pixelSizeInUV; + + float cocMin = 1.0f; + float cocMax = -1.0f; + + // Gather min/max in 3x3 region + [unroll] + for(float Y = 0.0f; Y < 3.0f; Y += 1.0f) + { + [unroll] + for(float X = 0.0f; X < 3.0f; X += 1.0f) + { + float2 sampleUV = mad(float2(X, Y), pixelSizeInUV, startUV); + float2 minMax = PassSrg::m_minMaxSource.SampleLevel(PassSrg::PointSampler, sampleUV, 0).xy; + + cocMin = min(cocMin, minMax.x); + cocMax = max(cocMax, minMax.y); + } + } + + PSOutput output; + output.m_color.x = cocMin; + output.m_color.y = cocMax; + return output; +} diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/PostProcessing/NewDepthOfFieldTile3x3.shader b/Gems/Atom/Feature/Common/Assets/Shaders/PostProcessing/NewDepthOfFieldTile3x3.shader new file mode 100644 index 0000000000..6ee990c5a7 --- /dev/null +++ b/Gems/Atom/Feature/Common/Assets/Shaders/PostProcessing/NewDepthOfFieldTile3x3.shader @@ -0,0 +1,22 @@ +{ + "Source" : "NewDepthOfFieldTile3x3.azsl", + + "DepthStencilState" : { + "Depth" : { "Enable" : false } + }, + + "ProgramSettings": + { + "EntryPoints": + [ + { + "name": "MainVS", + "type": "Vertex" + }, + { + "name": "MainPS", + "type": "Fragment" + } + ] + } +} diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/PostProcessing/NewDepthOfFieldTileReduce.azsl b/Gems/Atom/Feature/Common/Assets/Shaders/PostProcessing/NewDepthOfFieldTileReduce.azsl new file mode 100644 index 0000000000..6d55648f22 --- /dev/null +++ b/Gems/Atom/Feature/Common/Assets/Shaders/PostProcessing/NewDepthOfFieldTileReduce.azsl @@ -0,0 +1,87 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include +#include "NewDepthOfFieldCommon.azsli" + +ShaderResourceGroup PassSrg : SRG_PerPass +{ + // For this shader we're only interested in the CoC values, which are in the alpha channel + Texture2D m_colorAndCoC; + + // Tiled min/max CoC values + RWTexture2D m_minMaxCoC; + + // Texture dimensions. XY channels are width and height and ZW channels are 1 / width and 1 / height + // Auto-filled by the pass system when "ShaderImageDimensionsConstant" is specified in the .pass file + float4 m_inputDimensions; + float4 m_outputDimensions; + + Sampler LinearSampler + { + MinFilter = Linear; + MagFilter = Linear; + MipFilter = Linear; + AddressU = Clamp; + AddressV = Clamp; + AddressW = Clamp; + }; +} + +groupshared uint LDS_MIN_COC[8]; +groupshared uint LDS_MAX_COC[8]; + +// Calculates the min and max CoC (Circle of Confusion) for 16x16 pixel tiles +[numthreads(8, 8, 1)] +void MainCS(uint3 group_thread_id : SV_GroupThreadID, uint3 group_id : SV_GroupID, uint3 dispatch_id: SV_DispatchThreadID, uint linear_id : SV_GroupIndex) +{ + // Initialize groupshared mem for atomic min/max operations + if(group_thread_id.y == 0) + { + LDS_MIN_COC[group_thread_id.x] = 0xFFFFFFFF; + LDS_MAX_COC[group_thread_id.x] = 0; + } + + // We use gather to get 2x2 values at once, so thread samples are spaced 2 pixels apart (+1 so the sample position is in between the four pixels) + float2 samplePos = float2(dispatch_id.xy) * 2 + float2(1, 1); + float2 sampleUV = samplePos * PassSrg::m_inputDimensions.zw; + + // Gather CoC + float4 cocGather = PassSrg::m_colorAndCoC.GatherAlpha(PassSrg::LinearSampler, sampleUV); + float cocMin = min4(cocGather); + float cocMax = max4(cocGather); + + // For atomic min/max to work with uints, floating point values should be positive + // Map from [-1, 1] range to [0, 2] and cast as uint + InterlockedMin( LDS_MIN_COC[group_thread_id.x], asuint(cocMin + 1) ); + InterlockedMax( LDS_MAX_COC[group_thread_id.x], asuint(cocMax + 1) ); + + // Sync LDS + GroupMemoryBarrierWithGroupSync(); + + if(group_thread_id.y != 0) + { + return; + } + + // Min the mins and max the maxs + InterlockedMin( LDS_MIN_COC[0], LDS_MIN_COC[group_thread_id.x] ); + InterlockedMax( LDS_MAX_COC[0], LDS_MAX_COC[group_thread_id.x] ); + + // Each group write to just one pixel. If we're the last thread in the group, write out + if(group_thread_id.x == 0) + { + // Unpack uints + cocMin = asfloat(LDS_MIN_COC[0]) - 1; + cocMax = asfloat(LDS_MAX_COC[0]) - 1; + + // Output min/max coc of 16x16 region + PassSrg::m_minMaxCoC[group_id.xy] = float2(cocMin, cocMax); + } +} diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/PostProcessing/NewDepthOfFieldTileReduce.shader b/Gems/Atom/Feature/Common/Assets/Shaders/PostProcessing/NewDepthOfFieldTileReduce.shader new file mode 100644 index 0000000000..06313cdbf2 --- /dev/null +++ b/Gems/Atom/Feature/Common/Assets/Shaders/PostProcessing/NewDepthOfFieldTileReduce.shader @@ -0,0 +1,14 @@ +{ + "Source" : "NewDepthOfFieldTileReduce.azsl", + + "ProgramSettings" : + { + "EntryPoints": + [ + { + "name" : "MainCS", + "type" : "Compute" + } + ] + } +} diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/ScreenSpace/DeferredFog.shader b/Gems/Atom/Feature/Common/Assets/Shaders/ScreenSpace/DeferredFog.shader index 5e2ca9bff5..06c991400e 100644 --- a/Gems/Atom/Feature/Common/Assets/Shaders/ScreenSpace/DeferredFog.shader +++ b/Gems/Atom/Feature/Common/Assets/Shaders/ScreenSpace/DeferredFog.shader @@ -23,8 +23,8 @@ "DrawList" : "forward", "CompilerHints" : { - "DxcDisableOptimizations" : false, - "DxcGenerateDebugInfo" : false + "DisableOptimizations" : false, + "GenerateDebugInfo" : false }, "ProgramSettings": diff --git a/Gems/Atom/Feature/Common/Assets/Textures/Default/checker_basecolor.tif b/Gems/Atom/Feature/Common/Assets/Textures/Default/checker_basecolor.tif new file mode 100644 index 0000000000..5abe5bbd49 --- /dev/null +++ b/Gems/Atom/Feature/Common/Assets/Textures/Default/checker_basecolor.tif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:57d6744696768f9fb8a5fe5fee9aa36fee1eb87a9dbc1e60d4a35ed3c39d68e6 +size 810620 diff --git a/Gems/Atom/Feature/Common/Assets/Textures/Default/checker_uv_basecolor.png b/Gems/Atom/Feature/Common/Assets/Textures/Default/checker_uv_basecolor.png new file mode 100644 index 0000000000..07e240baf9 --- /dev/null +++ b/Gems/Atom/Feature/Common/Assets/Textures/Default/checker_uv_basecolor.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:513f47f6fea5105f603170a8881b7e3b1cd2c4258636d64a6399c725032b500d +size 38689 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 e13465307a..94b711e86c 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 @@ -1,9 +1,8 @@ # -# Copyright (c) Contributors to the Open 3D Engine Project. -# For complete copyright and license terms please see the LICENSE at the root of this distribution. -# -# SPDX-License-Identifier: Apache-2.0 OR MIT -# +# Copyright (c) Contributors to the Open 3D Engine Project. +# For complete copyright and license terms please see the LICENSE at the root of this distribution. +# +# SPDX-License-Identifier: Apache-2.0 OR MIT # set(FILES @@ -161,8 +160,6 @@ set(FILES Passes/LutGeneration.pass Passes/MainPipeline.pass Passes/MainPipelineRenderToTexture.pass - Passes/ToolsPipeline.pass - Passes/ToolsPipelineRenderToTexture.pass Passes/MeshMotionVector.pass Passes/ModulateTexture.pass Passes/MorphTarget.pass @@ -170,6 +167,13 @@ set(FILES Passes/MSAAResolveColor.pass Passes/MSAAResolveCustom.pass Passes/MSAAResolveDepth.pass + Passes/NewDepthOfField.pass + Passes/NewDepthOfFieldComposite.pass + Passes/NewDepthOfFieldDownsample.pass + Passes/NewDepthOfFieldFilterLarge.pass + Passes/NewDepthOfFieldFilterSmall.pass + Passes/NewDepthOfFieldTile3x3.pass + Passes/NewDepthOfFieldTileReduce.pass Passes/OpaqueParent.pass Passes/PostProcessParent.pass Passes/ProjectedShadowmaps.pass @@ -205,11 +209,17 @@ set(FILES Passes/SsaoParent.pass Passes/SubsurfaceScattering.pass Passes/Taa.pass + Passes/ToolsPipeline.pass + Passes/ToolsPipelineRenderToTexture.pass Passes/Transparent.pass Passes/TransparentParent.pass Passes/UI.pass Passes/UIParent.pass + Scripts/material_find_overrides_demo.lua Scripts/material_property_overrides_demo.lua + ShaderLib/3rdParty/Features/PostProcessing/KelvinToRgb.azsli + ShaderLib/3rdParty/Features/PostProcessing/PSstyleColorBlends_NonSeparable.azsli + ShaderLib/3rdParty/Features/PostProcessing/PSstyleColorBlends_Separable.azsli ShaderLib/Atom/Features/BlendUtility.azsli ShaderLib/Atom/Features/IndirectRendering.azsli ShaderLib/Atom/Features/MatrixUtility.azsli @@ -218,6 +228,8 @@ set(FILES ShaderLib/Atom/Features/SphericalHarmonicsUtility.azsli ShaderLib/Atom/Features/SrgSemantics.azsli ShaderLib/Atom/Features/ColorManagement/TransformColor.azsli + ShaderLib/Atom/Features/ColorManagement/GeneratedTransforms/AcesCcToAcesCg.azsli + ShaderLib/Atom/Features/ColorManagement/GeneratedTransforms/AcesCgToAcesCc.azsli ShaderLib/Atom/Features/ColorManagement/GeneratedTransforms/AcesCg_To_LinearSrgb.azsli ShaderLib/Atom/Features/ColorManagement/GeneratedTransforms/Aces_To_AcesCg.azsli ShaderLib/Atom/Features/ColorManagement/GeneratedTransforms/CalculateLuminance_AcesCg.azsli @@ -225,8 +237,6 @@ set(FILES ShaderLib/Atom/Features/ColorManagement/GeneratedTransforms/LinearSrgb_To_AcesCg.azsli ShaderLib/Atom/Features/ColorManagement/GeneratedTransforms/LinearSrgb_To_Srgb.azsli ShaderLib/Atom/Features/ColorManagement/GeneratedTransforms/Srgb_To_LinearSrgb.azsli - ShaderLib/Atom/Features/ColorManagement/GeneratedTransforms/AcesCcToAcesCg.azsli - ShaderLib/Atom/Features/ColorManagement/GeneratedTransforms/AcesCgToAcesCc.azsli ShaderLib/Atom/Features/CoreLights/PhotometricValue.azsli ShaderLib/Atom/Features/Decals/DecalTextureUtil.azsli ShaderLib/Atom/Features/LightCulling/LightCullingShared.azsli @@ -284,17 +294,23 @@ set(FILES ShaderLib/Atom/Features/PostProcessing/GlyphRender.azsli ShaderLib/Atom/Features/PostProcessing/HDRColorGradingCommon.azsl ShaderLib/Atom/Features/PostProcessing/PostProcessUtil.azsli + ShaderLib/Atom/Features/PostProcessing/Shapers.azsli + ShaderLib/Atom/Features/RayTracing/RayTracingMaterialSrg.azsli + ShaderLib/Atom/Features/RayTracing/RayTracingMaterialUtils.azsli ShaderLib/Atom/Features/RayTracing/RayTracingSceneSrg.azsli + ShaderLib/Atom/Features/RayTracing/RayTracingSceneUtils.azsli + ShaderLib/Atom/Features/RayTracing/RayTracingSrgs.azsl + ShaderLib/Atom/Features/RayTracing/RayTracingSrgs.shader ShaderLib/Atom/Features/ScreenSpace/ScreenSpaceUtil.azsli ShaderLib/Atom/Features/Shadow/BicubicPcfFilters.azsli ShaderLib/Atom/Features/Shadow/DirectionalLightShadow.azsli + ShaderLib/Atom/Features/Shadow/NormalOffsetShadows.azsli ShaderLib/Atom/Features/Shadow/ProjectedShadow.azsli + ShaderLib/Atom/Features/Shadow/ReceiverPlaneDepthBias.azsli ShaderLib/Atom/Features/Shadow/Shadow.azsli ShaderLib/Atom/Features/Shadow/ShadowmapAtlasLib.azsli + ShaderLib/Atom/Features/Skin/SkinObjectSrg.azsli ShaderLib/Atom/Features/Vertex/VertexHelper.azsli - ShaderLib/3rdParty/Features/PostProcessing/KelvinToRgb.azsli - ShaderLib/3rdParty/Features/PostProcessing/PSstyleColorBlends_NonSeparable.azsli - ShaderLib/3rdParty/Features/PostProcessing/PSstyleColorBlends_Separable.azsli ShaderResourceGroups/SceneSrg.azsli ShaderResourceGroups/SceneSrgAll.azsli ShaderResourceGroups/ViewSrg.azsli @@ -305,6 +321,8 @@ set(FILES ShaderResourceGroups/PostProcessing/SceneSrg.azsli ShaderResourceGroups/PostProcessing/ViewSrg.azsli ShaderResourceGroups/SkyBox/SceneSrg.azsli + Shaders/ForwardPassSrg.azsl + Shaders/ForwardPassSrg.shader Shaders/AuxGeom/AuxGeomObject.azsl Shaders/AuxGeom/AuxGeomObject.shader Shaders/AuxGeom/AuxGeomObjectLit.azsl @@ -321,14 +339,20 @@ set(FILES Shaders/ColorGrading/LutGeneration.shader Shaders/Depth/DepthPass.azsl Shaders/Depth/DepthPass.shader + Shaders/Depth/DepthPassCommon.azsli + Shaders/Depth/DepthPassSkin.azsl + Shaders/Depth/DepthPassSkin.shader Shaders/Depth/DepthPassTransparentMax.shader Shaders/Depth/DepthPassTransparentMin.shader Shaders/DiffuseGlobalIllumination/DiffuseComposite.azsl Shaders/DiffuseGlobalIllumination/DiffuseComposite.shader + Shaders/DiffuseGlobalIllumination/DiffuseComposite_nomsaa.azsl Shaders/DiffuseGlobalIllumination/DiffuseGlobalFullscreen.azsl Shaders/DiffuseGlobalIllumination/DiffuseGlobalFullscreen.shader + Shaders/DiffuseGlobalIllumination/DiffuseGlobalFullscreen_nomsaa.azsl Shaders/DiffuseGlobalIllumination/DiffuseProbeGridDownsample.azsl Shaders/DiffuseGlobalIllumination/DiffuseProbeGridDownsample.shader + Shaders/DiffuseGlobalIllumination/DiffuseProbeGridDownsample_nomsaa.azsl Shaders/ImGui/ImGui.azsl Shaders/ImGui/ImGui.shader Shaders/LightCulling/LightCulling.azsl @@ -348,6 +372,9 @@ set(FILES Shaders/MotionVector/CameraMotionVector.shader Shaders/MotionVector/MeshMotionVector.azsl Shaders/MotionVector/MeshMotionVector.shader + Shaders/MotionVector/MeshMotionVectorCommon.azsli + Shaders/MotionVector/MeshMotionVectorSkin.azsl + Shaders/MotionVector/MeshMotionVectorSkin.shader Shaders/PostProcessing/AcesOutputTransformLut.azsl Shaders/PostProcessing/AcesOutputTransformLut.shader Shaders/PostProcessing/ApplyShaperLookupTable.azsl @@ -362,6 +389,8 @@ set(FILES Shaders/PostProcessing/BloomCompositeCS.shader Shaders/PostProcessing/BloomDownsampleCS.azsl Shaders/PostProcessing/BloomDownsampleCS.shader + Shaders/PostProcessing/ContrastAdaptiveSharpening.azsl + Shaders/PostProcessing/ContrastAdaptiveSharpening.shader Shaders/PostProcessing/ConvertToAcescg.azsl Shaders/PostProcessing/ConvertToAcescg.shader Shaders/PostProcessing/DepthDownsample.azsl @@ -418,6 +447,19 @@ set(FILES Shaders/PostProcessing/MSAAResolveCustom.shader Shaders/PostProcessing/MSAAResolveDepth.azsl Shaders/PostProcessing/MSAAResolveDepth.shader + Shaders/PostProcessing/NewDepthOfFieldCommon.azsli + Shaders/PostProcessing/NewDepthOfFieldComposite.azsl + Shaders/PostProcessing/NewDepthOfFieldComposite.shader + Shaders/PostProcessing/NewDepthOfFieldDownsample.azsl + Shaders/PostProcessing/NewDepthOfFieldDownsample.shader + Shaders/PostProcessing/NewDepthOfFieldFilterLarge.azsl + Shaders/PostProcessing/NewDepthOfFieldFilterLarge.shader + Shaders/PostProcessing/NewDepthOfFieldFilterSmall.azsl + Shaders/PostProcessing/NewDepthOfFieldFilterSmall.shader + Shaders/PostProcessing/NewDepthOfFieldTile3x3.azsl + Shaders/PostProcessing/NewDepthOfFieldTile3x3.shader + Shaders/PostProcessing/NewDepthOfFieldTileReduce.azsl + Shaders/PostProcessing/NewDepthOfFieldTileReduce.shader Shaders/PostProcessing/OutputTransform.azsl Shaders/PostProcessing/OutputTransform.shader Shaders/PostProcessing/ScreenSpaceSubsurfaceScatteringCS.azsl @@ -434,13 +476,17 @@ set(FILES Shaders/PostProcessing/SMAAUtils.azsli Shaders/PostProcessing/SsaoCompute.azsl Shaders/PostProcessing/SsaoCompute.shader + Shaders/PostProcessing/Taa.azsl + Shaders/PostProcessing/Taa.shader Shaders/PostProcessing/UniformColor.azsl Shaders/PostProcessing/UniformColor.shader Shaders/Reflections/ReflectionCommon.azsli Shaders/Reflections/ReflectionComposite.azsl Shaders/Reflections/ReflectionComposite.shader + Shaders/Reflections/ReflectionComposite_nomsaa.azsl Shaders/Reflections/ReflectionGlobalFullscreen.azsl Shaders/Reflections/ReflectionGlobalFullscreen.shader + Shaders/Reflections/ReflectionGlobalFullscreen_nomsaa.azsl Shaders/Reflections/ReflectionProbeBlendWeight.azsl Shaders/Reflections/ReflectionProbeBlendWeight.shader Shaders/Reflections/ReflectionProbeRenderCommon.azsli @@ -469,6 +515,9 @@ set(FILES Shaders/Shadow/KawaseShadowBlur.shader Shaders/Shadow/Shadowmap.azsl Shaders/Shadow/Shadowmap.shader + Shaders/Shadow/ShadowmapCommon.azsli + Shaders/Shadow/ShadowmapSkin.azsl + Shaders/Shadow/ShadowmapSkin.shader Shaders/SkinnedMesh/LinearSkinningCS.azsl Shaders/SkinnedMesh/LinearSkinningCS.shader Shaders/SkinnedMesh/LinearSkinningPassSRG.azsli diff --git a/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/ImGui/ImGuiUtils.h b/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/ImGui/ImGuiUtils.h index a200f30c98..52e272a9c4 100644 --- a/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/ImGui/ImGuiUtils.h +++ b/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/ImGui/ImGuiUtils.h @@ -50,12 +50,12 @@ namespace AZ return scope; } - //! Sets the active context based on the provided PassHierarchyFilter. If the filter doesn't match exactly one pass, then do nothing. - static ImGuiActiveContextScope FromPass(const RPI::PassHierarchyFilter& passHierarchyFilter) + //! Sets the active context based on the provided pass hierarchy filter. If the filter doesn't match exactly one pass, then do nothing. + static ImGuiActiveContextScope FromPass(const AZStd::vector& passHierarchy) { ImGuiActiveContextScope scope; scope.ConnectToImguiNotificationBus(); - ImGuiSystemRequestBus::BroadcastResult(scope.m_isEnabled, &ImGuiSystemRequests::PushActiveContextFromPass, passHierarchyFilter); + ImGuiSystemRequestBus::BroadcastResult(scope.m_isEnabled, &ImGuiSystemRequests::PushActiveContextFromPass, passHierarchy); return scope; } diff --git a/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/ImGui/SystemBus.h b/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/ImGui/SystemBus.h index 289cb178d4..78f71b39ba 100644 --- a/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/ImGui/SystemBus.h +++ b/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/ImGui/SystemBus.h @@ -15,11 +15,6 @@ namespace AZ { - namespace RPI - { - class PassHierarchyFilter; - } - namespace Render { class ImGuiPass; @@ -51,7 +46,7 @@ namespace AZ //! Pushes whichever ImGui pass is default on the top of the active context stack. Returns true/false for success/fail. virtual bool PushActiveContextFromDefaultPass() = 0; //! Pushes whichever ImGui pass is provided in passHierarchy on the top of the active context stack. Returns true/false for success/fail. - virtual bool PushActiveContextFromPass(const RPI::PassHierarchyFilter& passHierarchy) = 0; + virtual bool PushActiveContextFromPass(const AZStd::vector& passHierarchy) = 0; //! Pops the active context off the top of the active context stack. Returns true if there's a context to pop. virtual bool PopActiveContext() = 0; //! Gets the context at the top of the active context stack. Returns nullptr if the stack is emtpy. diff --git a/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Material/MaterialAssignment.h b/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Material/MaterialAssignment.h index 40555bae00..21d9ec1bba 100644 --- a/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Material/MaterialAssignment.h +++ b/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Material/MaterialAssignment.h @@ -78,5 +78,10 @@ namespace AZ //! Find an assignment id corresponding to the lod and label substring filters MaterialAssignmentId FindMaterialAssignmentIdInModel( const Data::Instance& model, const MaterialAssignmentLodIndex lodFilter, const AZStd::string& labelFilter); + + //! Special case handling to convert script values to supported types + AZ::RPI::MaterialPropertyValue ConvertMaterialPropertyValueFromScript( + const AZ::RPI::MaterialPropertyDescriptor* propertyDescriptor, const AZStd::any& value); + } // namespace Render } // namespace AZ diff --git a/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Utils/FrameCaptureBus.h b/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Utils/FrameCaptureBus.h index 93600ec08f..c80926700e 100644 --- a/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Utils/FrameCaptureBus.h +++ b/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Utils/FrameCaptureBus.h @@ -50,7 +50,7 @@ namespace AZ virtual bool CaptureScreenshotWithPreview(const AZStd::string& outputFilePath) = 0; //! Save a buffer attachment or a image attachment binded to a pass's slot to a data file. - //! @param passHierarchy For finding the pass by using PassHierarchyFilter + //! @param passHierarchy For finding the pass by using a pass hierarchy filter. Check PassFilter::CreateWithPassHierarchy() function for detail //! @param slotName Name of the pass's slot. The attachment bound to this slot will be captured. //! @param option Only valid for an InputOutput attachment. Use PassAttachmentReadbackOption::Input to capture the input state //! and use PassAttachmentReadbackOption::Output to capture the output state diff --git a/Gems/Atom/Feature/Common/Code/Source/AuxGeom/AuxGeomDrawQueue.cpp b/Gems/Atom/Feature/Common/Code/Source/AuxGeom/AuxGeomDrawQueue.cpp index 29db7d6673..b755a6efee 100644 --- a/Gems/Atom/Feature/Common/Code/Source/AuxGeom/AuxGeomDrawQueue.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/AuxGeom/AuxGeomDrawQueue.cpp @@ -659,16 +659,17 @@ namespace AZ AuxGeomIndex vertexOffset = aznumeric_cast(primBuffer.m_vertexBuffer.size()); AuxGeomIndex indexOffset = aznumeric_cast(primBuffer.m_indexBuffer.size()); + const size_t vertexCountTotal = aznumeric_cast(vertexOffset) + vertexCount; - if (aznumeric_cast(vertexOffset) + vertexCount > MaxDynamicVertexCount) + if (vertexCountTotal > MaxDynamicVertexCount) { AZ_WarningOnce("AuxGeom", false, "Draw function ignored, would exceed maximum allowed index of %d", MaxDynamicVertexCount); return; } AZ::Vector3 center(0.0f, 0.0f, 0.0f); - primBuffer.m_vertexBuffer.reserve(vertexCount); - primBuffer.m_indexBuffer.reserve(vertexCount); + primBuffer.m_vertexBuffer.reserve(vertexCountTotal); + primBuffer.m_indexBuffer.reserve(vertexCountTotal); for (uint32_t vertexIndex = 0; vertexIndex < vertexCount; ++vertexIndex) { AZ::u32 packedColor = packedColorFunction(vertexIndex); @@ -734,15 +735,16 @@ namespace AZ AuxGeomIndex vertexOffset = aznumeric_cast(primBuffer.m_vertexBuffer.size()); AuxGeomIndex indexOffset = aznumeric_cast(primBuffer.m_indexBuffer.size()); + const size_t vertexCountTotal = aznumeric_cast(vertexOffset) + vertexCount; - if (aznumeric_cast(vertexOffset) + vertexCount > MaxDynamicVertexCount) + if (vertexCountTotal > MaxDynamicVertexCount) { AZ_WarningOnce("AuxGeom", false, "Draw function ignored, would exceed maximum allowed index of %d", MaxDynamicVertexCount); return; } AZ::Vector3 center(0.0f, 0.0f, 0.0f); - primBuffer.m_vertexBuffer.reserve(vertexCount); + primBuffer.m_vertexBuffer.reserve(vertexCountTotal); for (uint32_t vertexIndex = 0; vertexIndex < vertexCount; ++vertexIndex) { AZ::u32 packedColor = packedColorFunction(vertexIndex); @@ -753,7 +755,7 @@ namespace AZ } center /= aznumeric_cast(vertexCount); - primBuffer.m_indexBuffer.reserve(indexCount); + primBuffer.m_indexBuffer.reserve(indexCount + indexOffset); for (uint32_t index = 0; index < indexCount; ++index) { primBuffer.m_indexBuffer.push_back(vertexOffset + indexFunction(index)); diff --git a/Gems/Atom/Feature/Common/Code/Source/CommonSystemComponent.cpp b/Gems/Atom/Feature/Common/Code/Source/CommonSystemComponent.cpp index 4ddf609f41..2e5db39880 100644 --- a/Gems/Atom/Feature/Common/Code/Source/CommonSystemComponent.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/CommonSystemComponent.cpp @@ -51,6 +51,7 @@ #include #include #include +#include #include #include #include @@ -250,6 +251,10 @@ namespace AZ passSystem->AddPassCreator(Name("DepthOfFieldReadBackFocusDepthPass"), &DepthOfFieldReadBackFocusDepthPass::Create); passSystem->AddPassCreator(Name("DepthOfFieldWriteFocusDepthFromGpuPass"), &DepthOfFieldWriteFocusDepthFromGpuPass::Create); + passSystem->AddPassCreator(Name("NewDepthOfFieldParentPass"), &NewDepthOfFieldParentPass::Create); + passSystem->AddPassCreator(Name("NewDepthOfFieldTileReducePass"), &NewDepthOfFieldTileReducePass::Create); + passSystem->AddPassCreator(Name("NewDepthOfFieldFilterPass"), &NewDepthOfFieldFilterPass::Create); + // Add FastDepthAwareBlur passes passSystem->AddPassCreator(Name("FastDepthAwareBlurHorPass"), &FastDepthAwareBlurHorPass::Create); passSystem->AddPassCreator(Name("FastDepthAwareBlurVerPass"), &FastDepthAwareBlurVerPass::Create); diff --git a/Gems/Atom/Feature/Common/Code/Source/CoreLights/DirectionalLightFeatureProcessor.cpp b/Gems/Atom/Feature/Common/Code/Source/CoreLights/DirectionalLightFeatureProcessor.cpp index 6c24b7d35b..b6c6910fd3 100644 --- a/Gems/Atom/Feature/Common/Code/Source/CoreLights/DirectionalLightFeatureProcessor.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/CoreLights/DirectionalLightFeatureProcessor.cpp @@ -647,54 +647,46 @@ namespace AZ UpdateViewsOfCascadeSegments(); } - void DirectionalLightFeatureProcessor::CacheCascadedShadowmapsPass() { - const AZStd::vector& passes = RPI::PassSystemInterface::Get()->GetPassesForTemplateName(Name("CascadedShadowmapsTemplate")); + void DirectionalLightFeatureProcessor::CacheCascadedShadowmapsPass() + { m_cascadedShadowmapsPasses.clear(); - for (RPI::Pass* pass : passes) - { - if (RPI::RenderPipeline* pipeline = pass->GetRenderPipeline()) + + RPI::PassFilter passFilter = RPI::PassFilter::CreateWithTemplateName(Name("CascadedShadowmapsTemplate"), GetParentScene()); + RPI::PassSystemInterface::Get()->ForEachPass(passFilter, [this](RPI::Pass* pass) -> RPI::PassFilterExecutionFlow { + RPI::RenderPipeline* pipeline = pass->GetRenderPipeline(); const RPI::RenderPipelineId pipelineId = pipeline->GetId(); - // This function can be called when the pipeline is not attached to the scene. - // So we check it is attached to the scene. - if (GetParentScene()->GetRenderPipeline(pipelineId).get() == pipeline) + + CascadedShadowmapsPass* shadowPass = azrtti_cast(pass); + AZ_Assert(shadowPass, "It is not a CascadedShadowmapPass."); + if (pipeline->GetDefaultView()) { - CascadedShadowmapsPass* shadowPass = azrtti_cast(pass); - AZ_Assert(shadowPass, "It is not a CascadedShadowmapPass."); - if (pipeline->GetDefaultView()) - { - m_cascadedShadowmapsPasses[pipelineId].push_back(shadowPass); - } + m_cascadedShadowmapsPasses[pipelineId].push_back(shadowPass); } - } - } + return RPI::PassFilterExecutionFlow::ContinueVisitingPasses; + }); } void DirectionalLightFeatureProcessor::CacheEsmShadowmapsPass() { - const AZStd::vector& passes = RPI::PassSystemInterface::Get()->GetPassesForTemplateName(Name("EsmShadowmapsTemplate")); m_esmShadowmapsPasses.clear(); - for (RPI::Pass* pass : passes) - { - if (RPI::RenderPipeline* pipeline = pass->GetRenderPipeline()) + + RPI::PassFilter passFilter = RPI::PassFilter::CreateWithTemplateName(Name("EsmShadowmapsTemplate"), GetParentScene()); + RPI::PassSystemInterface::Get()->ForEachPass(passFilter, [this](RPI::Pass* pass) -> RPI::PassFilterExecutionFlow { - const RPI::RenderPipelineId pipelineId = pipeline->GetId(); - // checking the render pipeline is just removed from the scene. - if (GetParentScene()->GetRenderPipeline(pipelineId).get() == pipeline) + const RPI::RenderPipelineId pipelineId = pass->GetRenderPipeline()->GetId(); + + if (m_cascadedShadowmapsPasses.find(pipelineId) != m_cascadedShadowmapsPasses.end()) { - if (m_cascadedShadowmapsPasses.find(pipelineId) != m_cascadedShadowmapsPasses.end()) + EsmShadowmapsPass* esmPass = azrtti_cast(pass); + AZ_Assert(esmPass, "It is not an EsmShadowmapPass."); + if (esmPass->GetLightTypeName() == m_lightTypeName) { - EsmShadowmapsPass* esmPass = azrtti_cast(pass); - AZ_Assert(esmPass, "It is not an EsmShadowmapPass."); - if (m_cascadedShadowmapsPasses.find(esmPass->GetRenderPipeline()->GetId()) != m_cascadedShadowmapsPasses.end() && - esmPass->GetLightTypeName() == m_lightTypeName) - { - m_esmShadowmapsPasses[pipelineId].push_back(esmPass); - } + m_esmShadowmapsPasses[pipelineId].push_back(esmPass); } } - } - } + return RPI::PassFilterExecutionFlow::ContinueVisitingPasses; + }); } void DirectionalLightFeatureProcessor::PrepareCameraViews() @@ -1063,12 +1055,13 @@ namespace AZ // if the shadow is rendering in an EnvironmentCubeMapPass it also needs to be a ReflectiveCubeMap view, // to filter out shadows from objects that are excluded from the cubemap - RPI::PassClassFilter passFilter; - AZStd::vector cubeMapPasses = AZ::RPI::PassSystemInterface::Get()->FindPasses(passFilter); - if (!cubeMapPasses.empty()) - { - usageFlags |= RPI::View::UsageReflectiveCubeMap; - } + RPI::PassFilter passFilter = RPI::PassFilter::CreateWithPassClass(); + passFilter.SetOwenrScene(GetParentScene()); // only handles passes for this scene + RPI::PassSystemInterface::Get()->ForEachPass(passFilter, [&usageFlags]([[maybe_unused]] RPI::Pass* pass) -> RPI::PassFilterExecutionFlow + { + usageFlags |= RPI::View::UsageReflectiveCubeMap; + return RPI::PassFilterExecutionFlow::StopVisitingPasses; + }); segment.m_view = RPI::View::CreateView(viewName, usageFlags); } diff --git a/Gems/Atom/Feature/Common/Code/Source/DiffuseGlobalIllumination/DiffuseGlobalIlluminationFeatureProcessor.cpp b/Gems/Atom/Feature/Common/Code/Source/DiffuseGlobalIllumination/DiffuseGlobalIlluminationFeatureProcessor.cpp index c085b26e31..453dbbc0ab 100644 --- a/Gems/Atom/Feature/Common/Code/Source/DiffuseGlobalIllumination/DiffuseGlobalIlluminationFeatureProcessor.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/DiffuseGlobalIllumination/DiffuseGlobalIlluminationFeatureProcessor.cpp @@ -80,35 +80,48 @@ namespace AZ } // update the size multiplier on the DiffuseProbeGridDownsamplePass output - AZStd::vector downsamplePassHierarchy = { Name("DiffuseGlobalIlluminationPass"), Name("DiffuseProbeGridDownsamplePass") }; - RPI::PassHierarchyFilter downsamplePassFilter(downsamplePassHierarchy); - const AZStd::vector& downsamplePasses = RPI::PassSystemInterface::Get()->FindPasses(downsamplePassFilter); - for (RPI::Pass* pass : downsamplePasses) + // NOTE: The ownerScene wasn't added to both filters. This is because the passes from the non-owner scene may have invalid SRG values which could lead to + // GPU error if the scene doesn't have this feature processor enabled. + // For example, the ASV MultiScene sample may have TDR. { - for (uint32_t outputIndex = 0; outputIndex < pass->GetOutputCount(); ++outputIndex) - { - RPI::Ptr outputAttachment = pass->GetOutputBinding(outputIndex).m_attachment; - RPI::PassAttachmentSizeMultipliers& sizeMultipliers = outputAttachment->m_sizeMultipliers; + AZStd::vector downsamplePassHierarchy = { Name("DiffuseGlobalIlluminationPass"), Name("DiffuseProbeGridDownsamplePass") }; + RPI::PassFilter downsamplePassFilter = RPI::PassFilter::CreateWithPassHierarchy(downsamplePassHierarchy); + RPI::PassSystemInterface::Get()->ForEachPass( + downsamplePassFilter, + [sizeMultiplier](RPI::Pass* pass) -> RPI::PassFilterExecutionFlow + { + for (uint32_t outputIndex = 0; outputIndex < pass->GetOutputCount(); ++outputIndex) + { + RPI::Ptr outputAttachment = pass->GetOutputBinding(outputIndex).m_attachment; + RPI::PassAttachmentSizeMultipliers& sizeMultipliers = outputAttachment->m_sizeMultipliers; - sizeMultipliers.m_widthMultiplier = sizeMultiplier; - sizeMultipliers.m_heightMultiplier = sizeMultiplier; - } + sizeMultipliers.m_widthMultiplier = sizeMultiplier; + sizeMultipliers.m_heightMultiplier = sizeMultiplier; + } - // set the output scale on the PassSrg - RPI::FullscreenTrianglePass* downsamplePass = static_cast(pass); - auto constantIndex = downsamplePass->GetShaderResourceGroup()->FindShaderInputConstantIndex(Name("m_outputImageScale")); - downsamplePass->GetShaderResourceGroup()->SetConstant(constantIndex, aznumeric_cast(1.0f / sizeMultiplier)); + // set the output scale on the PassSrg + RPI::FullscreenTrianglePass* downsamplePass = static_cast(pass); + RHI::ShaderInputNameIndex outputImageScaleShaderInput = "m_outputImageScale"; + downsamplePass->GetShaderResourceGroup()->SetConstant( + outputImageScaleShaderInput, aznumeric_cast(1.0f / sizeMultiplier)); + + // handle all downsample passes + return RPI::PassFilterExecutionFlow::ContinueVisitingPasses; + }); } // update the image scale on the DiffuseComposite pass - AZStd::vector compositePassHierarchy = { Name("DiffuseGlobalIlluminationPass"), Name("DiffuseCompositePass") }; - RPI::PassHierarchyFilter compositePassFilter(compositePassHierarchy); - const AZStd::vector& compositePasses = RPI::PassSystemInterface::Get()->FindPasses(compositePassFilter); - for (RPI::Pass* pass : compositePasses) { - RPI::FullscreenTrianglePass* compositePass = static_cast(pass); - auto constantIndex = compositePass->GetShaderResourceGroup()->FindShaderInputConstantIndex(Name("m_imageScale")); - compositePass->GetShaderResourceGroup()->SetConstant(constantIndex, aznumeric_cast(1.0f / sizeMultiplier)); + AZStd::vector compositePassHierarchy = { Name("DiffuseGlobalIlluminationPass"), Name("DiffuseCompositePass") }; + RPI::PassFilter compositePassFilter = RPI::PassFilter::CreateWithPassHierarchy(compositePassHierarchy); + RPI::PassSystemInterface::Get()->ForEachPass(compositePassFilter, [sizeMultiplier](RPI::Pass* pass) -> RPI::PassFilterExecutionFlow + { + RPI::FullscreenTrianglePass* compositePass = static_cast(pass); + RHI::ShaderInputNameIndex imageScaleShaderInput = "m_imageScale"; + compositePass->GetShaderResourceGroup()->SetConstant(imageScaleShaderInput, aznumeric_cast(1.0f / sizeMultiplier)); + + return RPI::PassFilterExecutionFlow::ContinueVisitingPasses; + }); } } } // namespace Render diff --git a/Gems/Atom/Feature/Common/Code/Source/DiffuseGlobalIllumination/DiffuseProbeGridFeatureProcessor.cpp b/Gems/Atom/Feature/Common/Code/Source/DiffuseGlobalIllumination/DiffuseProbeGridFeatureProcessor.cpp index 4329fd556c..5ea4748fc9 100644 --- a/Gems/Atom/Feature/Common/Code/Source/DiffuseGlobalIllumination/DiffuseProbeGridFeatureProcessor.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/DiffuseGlobalIllumination/DiffuseProbeGridFeatureProcessor.cpp @@ -603,12 +603,12 @@ namespace AZ RHI::Ptr device = RHI::RHISystemInterface::Get()->GetDevice(); if (device->GetFeatures().m_rayTracing == false) { - RPI::PassHierarchyFilter updatePassFilter(AZ::Name("DiffuseProbeGridUpdatePass")); - const AZStd::vector& updatePasses = RPI::PassSystemInterface::Get()->FindPasses(updatePassFilter); - for (RPI::Pass* pass : updatePasses) - { - pass->SetEnabled(false); - } + RPI::PassFilter passFilter = RPI::PassFilter::CreateWithPassName(AZ::Name("DiffuseProbeGridUpdatePass"), GetParentScene()); + RPI::PassSystemInterface::Get()->ForEachPass(passFilter, [](RPI::Pass* pass) -> RPI::PassFilterExecutionFlow + { + pass->SetEnabled(false); + return RPI::PassFilterExecutionFlow::ContinueVisitingPasses; + }); } } diff --git a/Gems/Atom/Feature/Common/Code/Source/DisplayMapper/DisplayMapperPass.cpp b/Gems/Atom/Feature/Common/Code/Source/DisplayMapper/DisplayMapperPass.cpp index 3ec8840a1c..5b5599383e 100644 --- a/Gems/Atom/Feature/Common/Code/Source/DisplayMapper/DisplayMapperPass.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/DisplayMapper/DisplayMapperPass.cpp @@ -10,9 +10,10 @@ #include #include #include -#include +#include #include #include +#include #include #include #include @@ -66,22 +67,14 @@ namespace AZ { // Need to invalidate the CopyToSwapChain pass so that it updates the pipeline state in the event that // the swapchain format changed (for example, moving from LDR to HDR display) - auto* passSystem = RPI::PassSystemInterface::Get(); - const Name fullscreenCopyTemplateName("FullscreenCopyTemplate"); - - if (passSystem->HasPassesForTemplateName(fullscreenCopyTemplateName)) - { - const AZStd::vector& passes = passSystem->GetPassesForTemplateName(fullscreenCopyTemplateName); - for (RPI::Pass* pass : passes) + const Name copyToSwapChainPassName("CopyToSwapChain"); + RPI::PassFilter passFilter = RPI::PassFilter::CreateWithPassName(copyToSwapChainPassName, GetRenderPipeline()); + RPI::PassSystemInterface::Get()->ForEachPass(passFilter, [](RPI::Pass* pass) -> RPI::PassFilterExecutionFlow { - RPI::FullscreenTrianglePass* fullscreenTrianglePass = azrtti_cast(pass); - const Name& passName = fullscreenTrianglePass->GetName(); - if (passName.GetStringView() == "CopyToSwapChain") - { - fullscreenTrianglePass->QueueForInitialization(); - } - } - } + pass->QueueForInitialization(); + return RPI::PassFilterExecutionFlow::StopVisitingPasses; + }); + ConfigureDisplayParameters(); } diff --git a/Gems/Atom/Feature/Common/Code/Source/EditorCommonSystemComponent.cpp b/Gems/Atom/Feature/Common/Code/Source/EditorCommonSystemComponent.cpp index 2f73269bdf..c9d1e5ae3d 100644 --- a/Gems/Atom/Feature/Common/Code/Source/EditorCommonSystemComponent.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/EditorCommonSystemComponent.cpp @@ -19,7 +19,6 @@ #include #include #include -#include #include #include @@ -101,13 +100,6 @@ namespace AZ materialFunctorRegistration->RegisterMaterialFunctor("ConvertEmissiveUnit", azrtti_typeid()); materialFunctorRegistration->RegisterMaterialFunctor("HandleSubsurfaceScatteringParameters", azrtti_typeid()); materialFunctorRegistration->RegisterMaterialFunctor("Lua", azrtti_typeid()); - - // Add asset types and extensions to AssetCatalog. Uses "AssetCatalogService". - auto assetCatalog = AZ::Data::AssetCatalogRequestBus::FindFirstHandler(); - if (assetCatalog) - { - assetCatalog->EnableCatalogForAsset(AZ::AzTypeInfo::Uuid()); - } } void EditorCommonSystemComponent::Deactivate() diff --git a/Gems/Atom/Feature/Common/Code/Source/FrameCaptureSystemComponent.cpp b/Gems/Atom/Feature/Common/Code/Source/FrameCaptureSystemComponent.cpp index 8c0360dec2..8b016410aa 100644 --- a/Gems/Atom/Feature/Common/Code/Source/FrameCaptureSystemComponent.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/FrameCaptureSystemComponent.cpp @@ -12,7 +12,7 @@ #include #include -#include +#include #include #include @@ -29,6 +29,8 @@ #include #include +#include + #include #include @@ -50,6 +52,15 @@ namespace AZ "Sets the compression level for saving png screenshots. Valid values are from 0 to 8" ); + AZ_CVAR(int, + r_pngCompressionNumThreads, + 8, // Number of threads to use for the png r<->b channel data swap + nullptr, + ConsoleFunctorFlags::Null, + "Sets the number of threads for saving png screenshots. Valid values are from 1 to 128, although less than or equal the number of hw threads is recommended" + ); + + FrameCaptureOutputResult PngFrameCaptureOutput( const AZStd::string& outputFilePath, const AZ::RPI::AttachmentReadback::ReadbackResult& readbackResult) { @@ -65,33 +76,67 @@ namespace AZ buffer = AZStd::make_shared>(readbackResult.m_dataBuffer->size()); AZStd::copy(readbackResult.m_dataBuffer->begin(), readbackResult.m_dataBuffer->end(), buffer->begin()); - - AZ::JobCompletion jobCompletion; - const int numThreads = 8; + const int numThreads = r_pngCompressionNumThreads; const int numPixelsPerThread = static_cast(buffer->size() / numChannels / numThreads); - for (int i = 0; i < numThreads; ++i) + + AZ::TaskGraphActiveInterface* taskGraphActiveInterface = AZ::Interface::Get(); + bool taskGraphActive = taskGraphActiveInterface && taskGraphActiveInterface->IsTaskGraphActive(); + + if (taskGraphActive) { - int startPixel = i * numPixelsPerThread; + static const AZ::TaskDescriptor pngTaskDescriptor{"PngWriteOutChannelSwap", "Graphics"}; + AZ::TaskGraph taskGraph; + for (int i = 0; i < numThreads; ++i) + { + int startPixel = i * numPixelsPerThread; - AZ::Job* job = AZ::CreateJobFunction( - [&, startPixel, numPixelsPerThread]() - { - for (int pixelOffset = 0; pixelOffset < numPixelsPerThread; ++pixelOffset) + taskGraph.AddTask( + pngTaskDescriptor, + [&, startPixel]() { - if (startPixel * numChannels + numChannels < buffer->size()) + for (int pixelOffset = 0; pixelOffset < numPixelsPerThread; ++pixelOffset) { - AZStd::swap( - buffer->data()[(startPixel + pixelOffset) * numChannels], - buffer->data()[(startPixel + pixelOffset) * numChannels + 2] - ); + if (startPixel * numChannels + numChannels < buffer->size()) + { + AZStd::swap( + buffer->data()[(startPixel + pixelOffset) * numChannels], + buffer->data()[(startPixel + pixelOffset) * numChannels + 2] + ); + } } - } - }, true, nullptr); - - job->SetDependent(&jobCompletion); - job->Start(); + }); + } + AZ::TaskGraphEvent taskGraphFinishedEvent; + taskGraph.Submit(&taskGraphFinishedEvent); + taskGraphFinishedEvent.Wait(); + } + else + { + AZ::JobCompletion jobCompletion; + for (int i = 0; i < numThreads; ++i) + { + int startPixel = i * numPixelsPerThread; + + AZ::Job* job = AZ::CreateJobFunction( + [&, startPixel]() + { + for (int pixelOffset = 0; pixelOffset < numPixelsPerThread; ++pixelOffset) + { + if (startPixel * numChannels + numChannels < buffer->size()) + { + AZStd::swap( + buffer->data()[(startPixel + pixelOffset) * numChannels], + buffer->data()[(startPixel + pixelOffset) * numChannels + 2] + ); + } + } + }, true, nullptr); + + job->SetDependent(&jobCompletion); + job->Start(); + } + jobCompletion.StartAndWaitForCompletion(); } - jobCompletion.StartAndWaitForCompletion(); } Utils::PngFile image = Utils::PngFile::Create(readbackResult.m_imageDescriptor.m_size, format, *buffer); @@ -372,29 +417,25 @@ namespace AZ } m_latestCaptureInfo.clear(); - // Find the pass first - RPI::PassClassFilter passFilter; - AZStd::vector foundPasses = AZ::RPI::PassSystemInterface::Get()->FindPasses(passFilter); - - if (foundPasses.size() == 0) + RPI::PassFilter passFilter = RPI::PassFilter::CreateWithPassClass(); + AZ::RPI::ImageAttachmentPreviewPass* previewPass = azrtti_cast(RPI::PassSystemInterface::Get()->FindFirstPass(passFilter)); + if (!previewPass) { - AZ_Warning("FrameCaptureSystemComponent", false, "Failed to find an ImageAttachmentPreviewPass pass "); + AZ_Warning("FrameCaptureSystemComponent", false, "Failed to find an ImageAttachmentPreviewPass"); return false; } - AZ::RPI::ImageAttachmentPreviewPass* previewPass = azrtti_cast(foundPasses[0]); bool result = previewPass->ReadbackOutput(m_readback); if (result) { m_state = State::Pending; m_result = FrameCaptureResult::None; SystemTickBus::Handler::BusConnect(); + return true; } - else - { - AZ_Warning("FrameCaptureSystemComponent", false, "CaptureScreenshotWithPreview. Failed to readback output from the ImageAttachmentPreviewPass");; - } - return result; + + AZ_Warning("FrameCaptureSystemComponent", false, "CaptureScreenshotWithPreview. Failed to readback output from the ImageAttachmentPreviewPass"); + return false; } bool FrameCaptureSystemComponent::CapturePassAttachment(const AZStd::vector& passHierarchy, const AZStd::string& slot, @@ -405,6 +446,12 @@ namespace AZ return false; } + if (passHierarchy.size() == 0) + { + AZ_Warning("FrameCaptureSystemComponent", false, "Empty data in passHierarchy"); + return false; + } + InitReadback(); if (m_state != State::Idle) @@ -426,17 +473,15 @@ namespace AZ } m_latestCaptureInfo.clear(); - // Find the pass first - AZ::RPI::PassHierarchyFilter passFilter(passHierarchy); - AZStd::vector foundPasses = AZ::RPI::PassSystemInterface::Get()->FindPasses(passFilter); + RPI::PassFilter passFilter = RPI::PassFilter::CreateWithPassHierarchy(passHierarchy); + RPI::Pass* pass = RPI::PassSystemInterface::Get()->FindFirstPass(passFilter); - if (foundPasses.size() == 0) + if (!pass) { - AZ_Warning("FrameCaptureSystemComponent", false, "Failed to find pass from %s", passFilter.ToString().c_str()); + AZ_Warning("FrameCaptureSystemComponent", false, "Failed to find pass from %s", passHierarchy[0].c_str()); return false; } - AZ::RPI::Pass* pass = foundPasses[0]; if (pass->ReadbackAttachment(m_readback, Name(slot), option)) { m_state = State::Pending; @@ -444,6 +489,7 @@ namespace AZ SystemTickBus::Handler::BusConnect(); return true; } + AZ_Warning("FrameCaptureSystemComponent", false, "Failed to readback the attachment bound to pass [%s] slot [%s]", pass->GetName().GetCStr(), slot.c_str()); return false; } diff --git a/Gems/Atom/Feature/Common/Code/Source/ImGui/ImGuiSystemComponent.cpp b/Gems/Atom/Feature/Common/Code/Source/ImGui/ImGuiSystemComponent.cpp index afca20c5cb..3783752e67 100644 --- a/Gems/Atom/Feature/Common/Code/Source/ImGui/ImGuiSystemComponent.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/ImGui/ImGuiSystemComponent.cpp @@ -109,15 +109,15 @@ namespace AZ void ImGuiSystemComponent::ForAllImGuiPasses(PassFunction func) { ImGuiContext* contextToRestore = ImGui::GetCurrentContext(); - RPI::PassClassFilter filter; - auto imguiPasses = RPI::PassSystemInterface::Get()->FindPasses(filter); - - for (RPI::Pass* pass : imguiPasses) - { - ImGuiPass* imguiPass = azrtti_cast(pass); - ImGui::SetCurrentContext(imguiPass->GetContext()); - func(imguiPass); - } + + RPI::PassFilter passFilter = RPI::PassFilter::CreateWithPassClass(); + RPI::PassSystemInterface::Get()->ForEachPass(passFilter, [func](RPI::Pass* pass) -> RPI::PassFilterExecutionFlow + { + ImGuiPass* imguiPass = azrtti_cast(pass); + ImGui::SetCurrentContext(imguiPass->GetContext()); + func(imguiPass); + return RPI::PassFilterExecutionFlow::ContinueVisitingPasses; + }); ImGui::SetCurrentContext(contextToRestore); } @@ -169,29 +169,37 @@ namespace AZ return false; } - bool ImGuiSystemComponent::PushActiveContextFromPass(const RPI::PassHierarchyFilter& passHierarchyFilter) + bool ImGuiSystemComponent::PushActiveContextFromPass(const AZStd::vector& passHierarchyFilter) { - AZStd::vector foundPasses = AZ::RPI::PassSystemInterface::Get()->FindPasses(passHierarchyFilter); + if (passHierarchyFilter.size() == 0) + { + AZ_Warning("ImGuiSystemComponent", false, "passHierarchyFilter is empty"); + return false; + } + AZStd::vector foundImGuiPasses; - for (RPI::Pass* pass : foundPasses) - { - ImGuiPass* imGuiPass = azrtti_cast(pass); - if (imGuiPass) + RPI::PassFilter passFilter = RPI::PassFilter::CreateWithPassHierarchy(passHierarchyFilter); + RPI::PassSystemInterface::Get()->ForEachPass(passFilter, [&foundImGuiPasses](RPI::Pass* pass) -> RPI::PassFilterExecutionFlow { - foundImGuiPasses.push_back(imGuiPass); - } - } + ImGuiPass* imGuiPass = azrtti_cast(pass); + if (imGuiPass) + { + foundImGuiPasses.push_back(imGuiPass); + } + + return RPI::PassFilterExecutionFlow::ContinueVisitingPasses; + }); if (foundImGuiPasses.size() == 0) { - AZ_Warning("ImGuiSystemComponent", false, "Failed to find ImGui pass to activate from %s", passHierarchyFilter.ToString().c_str()); + AZ_Warning("ImGuiSystemComponent", false, "Failed to find ImGui pass to activate from %s", passHierarchyFilter[0].c_str()); return false; } if (foundImGuiPasses.size() > 1) { - AZ_Warning("ImGuiSystemComponent", false, "Found more than one ImGui pass to activate from %s, only activating first one.", passHierarchyFilter.ToString().c_str()); + AZ_Warning("ImGuiSystemComponent", false, "Found more than one ImGui pass to activate from %s, only activating first one.", passHierarchyFilter[0].c_str()); } ImGuiContext* context = foundImGuiPasses.at(0)->GetContext(); diff --git a/Gems/Atom/Feature/Common/Code/Source/ImGui/ImGuiSystemComponent.h b/Gems/Atom/Feature/Common/Code/Source/ImGui/ImGuiSystemComponent.h index 838cf0b3c2..d59124890f 100644 --- a/Gems/Atom/Feature/Common/Code/Source/ImGui/ImGuiSystemComponent.h +++ b/Gems/Atom/Feature/Common/Code/Source/ImGui/ImGuiSystemComponent.h @@ -56,7 +56,7 @@ namespace AZ ImGuiPass* GetDefaultImGuiPass() override; bool PushActiveContextFromDefaultPass() override; - bool PushActiveContextFromPass(const RPI::PassHierarchyFilter& passHierarchy) override; + bool PushActiveContextFromPass(const AZStd::vector& passHierarchy) override; bool PopActiveContext() override; ImGuiContext* GetActiveContext() override; diff --git a/Gems/Atom/Feature/Common/Code/Source/LuxCore/LuxCoreTexture.cpp b/Gems/Atom/Feature/Common/Code/Source/LuxCore/LuxCoreTexture.cpp index aa906a06f7..1fd8f0d91c 100644 --- a/Gems/Atom/Feature/Common/Code/Source/LuxCore/LuxCoreTexture.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/LuxCore/LuxCoreTexture.cpp @@ -32,7 +32,7 @@ namespace AZ { if (m_rtPipeline) { - AZ::RPI::RPISystemInterface::Get()->GetDefaultScene()->RemoveRenderPipeline(m_rtPipeline->GetId()); + m_rtPipeline->RemoveFromScene(); m_rtPipeline = nullptr; } @@ -111,8 +111,12 @@ namespace AZ parentPass->SetSourceTexture(m_texture, RHI::Format::R8G8B8A8_UNORM); break; } - - AZ::RPI::RPISystemInterface::Get()->GetDefaultScene()->AddRenderPipeline(m_rtPipeline); + + const auto mainScene = AZ::RPI::RPISystemInterface::Get()->GetSceneByName(AZ::Name("RPI")); + if (mainScene) + { + mainScene->AddRenderPipeline(m_rtPipeline); + } } bool LuxCoreTexture::IsIBLTexture() diff --git a/Gems/Atom/Feature/Common/Code/Source/Material/MaterialAssignment.cpp b/Gems/Atom/Feature/Common/Code/Source/Material/MaterialAssignment.cpp index 8984ec160b..d99ce211bf 100644 --- a/Gems/Atom/Feature/Common/Code/Source/Material/MaterialAssignment.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/Material/MaterialAssignment.cpp @@ -141,11 +141,36 @@ namespace AZ { if (!propertyPair.second.empty()) { - const auto& materialPropertyIndex = m_materialInstance->FindPropertyIndex(propertyPair.first); + bool wasRenamed = false; + Name newName; + RPI::MaterialPropertyIndex materialPropertyIndex = + m_materialInstance->FindPropertyIndex(propertyPair.first, &wasRenamed, &newName); + + // FindPropertyIndex will have already reported a message about what the old and new names are. Here we just add + // some extra info to help the user resolve it. + AZ_Warning( + "MaterialAssignment", !wasRenamed, + "Consider running \"Apply Automatic Property Updates\" to use the latest property names.", + propertyPair.first.GetCStr(), newName.GetCStr()); + + if (wasRenamed && m_propertyOverrides.find(newName) != m_propertyOverrides.end()) + { + materialPropertyIndex.Reset(); + + AZ_Warning( + "MaterialAssignment", false, + "Material property '%s' has been renamed to '%s', and a property override exists for both. The one with " + "the old name will be ignored.", + propertyPair.first.GetCStr(), newName.GetCStr()); + } + if (!materialPropertyIndex.IsNull()) { + const auto propertyDescriptor = + m_materialInstance->GetMaterialPropertiesLayout()->GetPropertyDescriptor(materialPropertyIndex); + m_materialInstance->SetPropertyValue( - materialPropertyIndex, AZ::RPI::MaterialPropertyValue::FromAny(propertyPair.second)); + materialPropertyIndex, ConvertMaterialPropertyValueFromScript(propertyDescriptor, propertyPair.second)); } } } @@ -265,5 +290,58 @@ namespace AZ return MaterialAssignmentId(); } + + template + AZ::RPI::MaterialPropertyValue ConvertMaterialPropertyValueNumericType(const AZStd::any& value) + { + if (value.is()) + { + return aznumeric_cast(AZStd::any_cast(value)); + } + if (value.is()) + { + return aznumeric_cast(AZStd::any_cast(value)); + } + if (value.is()) + { + return aznumeric_cast(AZStd::any_cast(value)); + } + if (value.is()) + { + return aznumeric_cast(AZStd::any_cast(value)); + } + + return AZ::RPI::MaterialPropertyValue::FromAny(value); + } + + AZ::RPI::MaterialPropertyValue ConvertMaterialPropertyValueFromScript( + const AZ::RPI::MaterialPropertyDescriptor* propertyDescriptor, const AZStd::any& value) + { + switch (propertyDescriptor->GetDataType()) + { + case AZ::RPI::MaterialPropertyDataType::Enum: + if (value.is()) + { + return propertyDescriptor->GetEnumValue(AZStd::any_cast(value)); + } + if (value.is()) + { + return propertyDescriptor->GetEnumValue(AZ::Name(AZStd::any_cast(value))); + } + return ConvertMaterialPropertyValueNumericType(value); + case AZ::RPI::MaterialPropertyDataType::Int: + return ConvertMaterialPropertyValueNumericType(value); + case AZ::RPI::MaterialPropertyDataType::UInt: + return ConvertMaterialPropertyValueNumericType(value); + case AZ::RPI::MaterialPropertyDataType::Float: + return ConvertMaterialPropertyValueNumericType(value); + case AZ::RPI::MaterialPropertyDataType::Bool: + return ConvertMaterialPropertyValueNumericType(value); + default: + break; + } + + return AZ::RPI::MaterialPropertyValue::FromAny(value); + } } // namespace Render } // namespace AZ diff --git a/Gems/Atom/Feature/Common/Code/Source/Platform/Windows/platform_windows.cmake b/Gems/Atom/Feature/Common/Code/Source/Platform/Windows/platform_windows.cmake index 7c594fc945..7a325ca97e 100644 --- a/Gems/Atom/Feature/Common/Code/Source/Platform/Windows/platform_windows.cmake +++ b/Gems/Atom/Feature/Common/Code/Source/Platform/Windows/platform_windows.cmake @@ -5,12 +5,3 @@ # SPDX-License-Identifier: Apache-2.0 OR MIT # # - -if(LY_MONOLITHIC_GAME) # Do not use OpenImageIO in monolithic game - return() -endif() - -set(LY_BUILD_DEPENDENCIES - PRIVATE - 3rdParty::ilmbase -) diff --git a/Gems/Atom/Feature/Common/Code/Source/PostProcess/DepthOfField/DepthOfFieldSettings.cpp b/Gems/Atom/Feature/Common/Code/Source/PostProcess/DepthOfField/DepthOfFieldSettings.cpp index fe1ce8a281..98b527868d 100644 --- a/Gems/Atom/Feature/Common/Code/Source/PostProcess/DepthOfField/DepthOfFieldSettings.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/PostProcess/DepthOfField/DepthOfFieldSettings.cpp @@ -15,6 +15,7 @@ #include #include +#include #include #include #include @@ -259,24 +260,19 @@ namespace AZ // [GFX TODO][ATOM-3035]This function is temporary and will change with improvement to the draw list tag system void DepthOfFieldSettings::UpdateAutoFocusDepth(bool enabled) - { - auto* passSystem = AZ::RPI::PassSystemInterface::Get(); + { const Name TemplateNameReadBackFocusDepth = Name("DepthOfFieldReadBackFocusDepthTemplate"); - if (passSystem->HasPassesForTemplateName(TemplateNameReadBackFocusDepth)) - { - const AZStd::vector& dofPasses = passSystem->GetPassesForTemplateName(TemplateNameReadBackFocusDepth); - for (RPI::Pass* pass : dofPasses) + // [GFX TODO][ATOM-4908] multiple camera should be distingushed. + RPI::PassFilter passFilter = RPI::PassFilter::CreateWithTemplateName(TemplateNameReadBackFocusDepth, GetParentScene()); + RPI::PassSystemInterface::Get()->ForEachPass(passFilter, [this, enabled](RPI::Pass* pass) -> RPI::PassFilterExecutionFlow { auto* dofPass = azrtti_cast(pass); - // Check this pass belongs to a render pipeline of the scene. - // [GFX TODO][ATOM-4908] multiple camera should be distingushed. - const RPI::RenderPipelineId pipelineId = dofPass->GetRenderPipeline()->GetId(); - if (enabled && GetParentScene()->GetRenderPipeline(pipelineId)) + if (enabled) { m_normalizedFocusDistanceForAutoFocus = dofPass->GetNormalizedFocusDistanceForAutoFocus(); } - } - } + return RPI::PassFilterExecutionFlow::ContinueVisitingPasses; + }); } void DepthOfFieldSettings::SetCameraEntityId(EntityId cameraEntityId) diff --git a/Gems/Atom/Feature/Common/Code/Source/PostProcess/ExposureControl/ExposureControlSettings.cpp b/Gems/Atom/Feature/Common/Code/Source/PostProcess/ExposureControl/ExposureControlSettings.cpp index 96ca424307..26c2a61d54 100644 --- a/Gems/Atom/Feature/Common/Code/Source/PostProcess/ExposureControl/ExposureControlSettings.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/PostProcess/ExposureControl/ExposureControlSettings.cpp @@ -10,6 +10,7 @@ #include #include +#include #include #include #include @@ -188,21 +189,21 @@ namespace AZ void ExposureControlSettings::UpdateLuminanceHeatmap() { - auto* passSystem = AZ::RPI::PassSystemInterface::Get(); - // [GFX-TODO][ATOM-13194] Support multiple views for the luminance heatmap - // [GFX-TODO][ATOM-13224] Remove UpdateLuminanceHeatmap and UpdateEyeAdaptationPass - const RPI::Ptr luminanceHeatmap = passSystem->GetRootPass()->FindPassByNameRecursive(m_luminanceHeatmapNameId); - if (luminanceHeatmap) - { - luminanceHeatmap->SetEnabled(m_heatmapEnabled); - } + // [GFX-TODO][ATOM-13224] Remove UpdateLuminanceHeatmap and UpdateEyeAdaptationPass + RPI::PassFilter heatmapPassFilter = RPI::PassFilter::CreateWithPassName(m_luminanceHeatmapNameId, GetParentScene()); + RPI::PassSystemInterface::Get()->ForEachPass(heatmapPassFilter, [this](RPI::Pass* pass) -> RPI::PassFilterExecutionFlow + { + pass->SetEnabled(m_heatmapEnabled); + return RPI::PassFilterExecutionFlow::ContinueVisitingPasses; + }); - const RPI::Ptr histogramGenerator = passSystem->GetRootPass()->FindPassByNameRecursive(m_luminanceHistogramGeneratorNameId); - if (histogramGenerator) - { - histogramGenerator->SetEnabled(m_heatmapEnabled); - } + RPI::PassFilter histogramPassFilter = RPI::PassFilter::CreateWithPassName(m_luminanceHistogramGeneratorNameId, GetParentScene()); + RPI::PassSystemInterface::Get()->ForEachPass(histogramPassFilter, [this](RPI::Pass* pass) -> RPI::PassFilterExecutionFlow + { + pass->SetEnabled(m_heatmapEnabled); + return RPI::PassFilterExecutionFlow::ContinueVisitingPasses; + }); } void ExposureControlSettings::UpdateBuffer() diff --git a/Gems/Atom/Feature/Common/Code/Source/PostProcessing/LookModificationCompositePass.cpp b/Gems/Atom/Feature/Common/Code/Source/PostProcessing/LookModificationCompositePass.cpp index 85c45de96b..aac3d1d941 100644 --- a/Gems/Atom/Feature/Common/Code/Source/PostProcessing/LookModificationCompositePass.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/PostProcessing/LookModificationCompositePass.cpp @@ -31,12 +31,14 @@ namespace AZ 0, [](const uint8_t& value) { - auto passes = RPI::PassSystem::Get()->FindPasses(RPI::PassClassFilter()); - for (auto* pass : passes) - { - LookModificationCompositePass* lookModPass = azrtti_cast(pass); - lookModPass->SetSampleQuality(LookModificationCompositePass::SampleQuality(value)); - } + RPI::PassFilter passFilter = RPI::PassFilter::CreateWithPassClass(); + RPI::PassSystemInterface::Get()->ForEachPass(passFilter, [value](RPI::Pass* pass) -> RPI::PassFilterExecutionFlow + { + LookModificationCompositePass* lookModPass = azrtti_cast(pass); + lookModPass->SetSampleQuality(LookModificationCompositePass::SampleQuality(value)); + + return RPI::PassFilterExecutionFlow::ContinueVisitingPasses; + }); }, ConsoleFunctorFlags::Null, "This can be increased to deal with particularly tricky luts. Range (0-2). 0 (default) - Standard linear sampling. 1 - 7 tap b-spline sampling. 2 - 19 tap b-spline sampling." diff --git a/Gems/Atom/Feature/Common/Code/Source/PostProcessing/NewDepthOfFieldPasses.cpp b/Gems/Atom/Feature/Common/Code/Source/PostProcessing/NewDepthOfFieldPasses.cpp new file mode 100644 index 0000000000..69ee2a6751 --- /dev/null +++ b/Gems/Atom/Feature/Common/Code/Source/PostProcessing/NewDepthOfFieldPasses.cpp @@ -0,0 +1,176 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include + +#include +#include +#include + +#include +#include +#include + +namespace AZ +{ + namespace Render + { + + // Must match the struct in NewDepthOfFieldCommon.azsli + struct NewDepthOfFieldConstants + { + static constexpr uint32_t numberOfLoops = 3; + static constexpr float loopCounts[] = { 8.0f, 16.0f, 24.0f }; + + AZStd::array m_samplePositions; // XY are sample positions (normalized so max lenght is 1) + // Z is the length of XY (0 - 1) + // W is unused + }; + + // --- Depth of Field Parent Pass --- + + RPI::Ptr NewDepthOfFieldParentPass::Create(const RPI::PassDescriptor& descriptor) + { + RPI::Ptr pass = aznew NewDepthOfFieldParentPass(descriptor); + return AZStd::move(pass); + } + + NewDepthOfFieldParentPass::NewDepthOfFieldParentPass(const RPI::PassDescriptor& descriptor) + : RPI::ParentPass(descriptor) + { } + + bool NewDepthOfFieldParentPass::IsEnabled() const + { + if (!ParentPass::IsEnabled()) + { + return false; + } + RPI::Scene* scene = GetScene(); + if (!scene) + { + return false; + } + PostProcessFeatureProcessor* fp = scene->GetFeatureProcessor(); + AZ::RPI::ViewPtr view = GetRenderPipeline()->GetDefaultView(); + if (!fp) + { + return false; + } + PostProcessSettings* postProcessSettings = fp->GetLevelSettingsFromView(view); + if (!postProcessSettings) + { + return false; + } + DepthOfFieldSettings* dofSettings = postProcessSettings->GetDepthOfFieldSettings(); + return (dofSettings != nullptr) && dofSettings->GetEnabled(); + } + + void NewDepthOfFieldParentPass::FrameBeginInternal(FramePrepareParams params) + { + RPI::Scene* scene = GetScene(); + PostProcessFeatureProcessor* fp = scene->GetFeatureProcessor(); + AZ::RPI::ViewPtr view = GetRenderPipeline()->GetDefaultView(); + if (fp) + { + PostProcessSettings* postProcessSettings = fp->GetLevelSettingsFromView(view); + if (postProcessSettings) + { + DepthOfFieldSettings* dofSettings = postProcessSettings->GetDepthOfFieldSettings(); + if (dofSettings) + { + dofSettings->SetValuesToViewSrg(view->GetShaderResourceGroup()); + } + } + } + + ParentPass::FrameBeginInternal(params); + } + + // --- Tile Reduce Pass --- + + RPI::Ptr NewDepthOfFieldTileReducePass::Create(const RPI::PassDescriptor& descriptor) + { + RPI::Ptr pass = aznew NewDepthOfFieldTileReducePass(descriptor); + return AZStd::move(pass); + } + + NewDepthOfFieldTileReducePass::NewDepthOfFieldTileReducePass(const RPI::PassDescriptor& descriptor) + : RPI::ComputePass(descriptor) + { + // Though this is a fullscreen pass, the shader computes 16x16 tiles with groups of 8x8 threads, + // each thread outputting to a single pixel in the tiled min/max texture + m_isFullscreenPass = false; + } + + void NewDepthOfFieldTileReducePass::FrameBeginInternal(FramePrepareParams params) + { + AZ_Assert(GetOutputCount() > 0, "NewDepthOfFieldTileReducePass: No output bindings!"); + RPI::PassAttachment* outputAttachment = GetOutputBinding(0).m_attachment.get(); + + AZ_Assert(outputAttachment != nullptr, "NewDepthOfFieldTileReducePass: Output binding has no attachment!"); + RHI::Size outputSize = outputAttachment->m_descriptor.m_image.m_size; + + // The algorithm outputs the min/max CoC values from a 16x16 region using 8x8 threads + u32 targetThreadCountX = outputSize.m_width * 8; + u32 targetThreadCountY = outputSize.m_height * 8; + SetTargetThreadCounts(targetThreadCountX, targetThreadCountY, 1); + + RPI::ComputePass::FrameBeginInternal(params); + } + + // --- Filter Pass --- + + RPI::Ptr NewDepthOfFieldFilterPass::Create(const RPI::PassDescriptor& descriptor) + { + RPI::Ptr pass = aznew NewDepthOfFieldFilterPass(descriptor); + return AZStd::move(pass); + } + + NewDepthOfFieldFilterPass::NewDepthOfFieldFilterPass(const RPI::PassDescriptor& descriptor) + : RPI::FullscreenTrianglePass(descriptor) + { } + + void NewDepthOfFieldFilterPass::FrameBeginInternal(FramePrepareParams params) + { + NewDepthOfFieldConstants dofConstants; + + uint32_t sampleIndex = 0; + + // Calculate all the offset positions + for (uint32_t loop = 0; loop < NewDepthOfFieldConstants::numberOfLoops; ++loop) + { + float radius = (loop + 1.0f) / float(NewDepthOfFieldConstants::numberOfLoops); + float loopCount = NewDepthOfFieldConstants::loopCounts[loop]; + + float angleStep = Constants::TwoPi / loopCount; + + // Every other loop slightly rotate sample ring so they don't line up + float angle = (loop & 1) ? (angleStep * 0.5f) : 0; + + for (float i = 0.0f; i < loopCount; ++i) + { + Vector2 pos = Vector2::CreateFromAngle(angle); + pos = pos * radius; + + dofConstants.m_samplePositions[sampleIndex][0] = pos.GetX(); + dofConstants.m_samplePositions[sampleIndex][1] = pos.GetY(); + dofConstants.m_samplePositions[sampleIndex][2] = radius; + dofConstants.m_samplePositions[sampleIndex][3] = 0.0f; + + ++sampleIndex; + angle += angleStep; + } + } + + m_shaderResourceGroup->SetConstant(m_constantsIndex, dofConstants); + + RPI::FullscreenTrianglePass::FrameBeginInternal(params); + } + + } // namespace Render +} // namespace AZ diff --git a/Gems/Atom/Feature/Common/Code/Source/PostProcessing/NewDepthOfFieldPasses.h b/Gems/Atom/Feature/Common/Code/Source/PostProcessing/NewDepthOfFieldPasses.h new file mode 100644 index 0000000000..159f91e744 --- /dev/null +++ b/Gems/Atom/Feature/Common/Code/Source/PostProcessing/NewDepthOfFieldPasses.h @@ -0,0 +1,119 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ +#pragma once + +#include +#include +#include + +#include + +namespace AZ +{ + namespace Render + { + // Technique + // + // 1. This Depth of Field technique starts by downsampling the lighting buffer and calculating + // the circle of confusion (CoC) for each downsampled pixel. + // + // 2. It then computes the min and max CoC for tiles of 16x16 pixels + // + // 3. It expands the min and max in a 3x3 region (twice, so 5x5 at the end) so that each tile + // tile pixel has the min and max CoCs of the 5x5 tile region around it + // + // 4. We perform a 48 tap scatter-as-gather blur around each pixel + // + // 5. We perform a follow up 8 tap scatter-as-gather blur to fill the holes from the first blur + // + // 6. We composite the blurred half resolution image onto the full resolution lighting buffer + // + // See http://advances.realtimerendering.com/s2013/Sousa_Graphics_Gems_CryENGINE3.pptx + // for a more detailed explanation. + // + // Notes: The name NewDepthOfField is in contrast to the previously implemented depth of field method + // That method will be removed in a follow up change and at that point NewDepthOfField will be renamed + // to simple DepthOfField. + + //! Parent pass for the new depth of field technique + //! Main updates the view srg via the depth of field settings + //! And enables/disables all depth of field passes based on component activation + class NewDepthOfFieldParentPass final + : public RPI::ParentPass + { + AZ_RPI_PASS(NewDepthOfFieldParentPass); + + public: + AZ_RTTI(AZ::Render::NewDepthOfFieldParentPass, "{71F4998B-447C-4BAC-A5BE-2D2850FABB57}", AZ::RPI::ParentPass); + AZ_CLASS_ALLOCATOR(NewDepthOfFieldParentPass, SystemAllocator, 0); + virtual ~NewDepthOfFieldParentPass() = default; + + static RPI::Ptr Create(const RPI::PassDescriptor& descriptor); + + bool IsEnabled() const override; + + protected: + // Behavior functions override... + void FrameBeginInternal(FramePrepareParams params) override; + + private: + NewDepthOfFieldParentPass(const RPI::PassDescriptor& descriptor); + }; + + + //! Need a class for the tile reduce pass because it dispatches a non-trivial number of threads + class NewDepthOfFieldTileReducePass final + : public RPI::ComputePass + { + AZ_RPI_PASS(NewDepthOfFieldTileReducePass); + + public: + AZ_RTTI(AZ::Render::NewDepthOfFieldTileReducePass, "{2E072695-0847-43A6-9BE4-D6D85CFFBA41}", AZ::RPI::ComputePass); + AZ_CLASS_ALLOCATOR(NewDepthOfFieldTileReducePass, SystemAllocator, 0); + virtual ~NewDepthOfFieldTileReducePass() = default; + + static RPI::Ptr Create(const RPI::PassDescriptor& descriptor); + + protected: + // Behavior functions override... + void FrameBeginInternal(FramePrepareParams params) override; + + private: + NewDepthOfFieldTileReducePass(const RPI::PassDescriptor& descriptor); + }; + + + //! Filter pass used to render the bokeh blur effect on downsampled image buffer + //! This class is used for both the large filter and the small filter + //! It's main purpose is calculating the sample positions and setting srg constants + class NewDepthOfFieldFilterPass final + : public RPI::FullscreenTrianglePass + { + AZ_RPI_PASS(NewDepthOfFieldFilterPass); + + public: + AZ_RTTI(AZ::Render::NewDepthOfFieldFilterPass, "{F8A98E53-1A50-4178-A6EB-2BD0148C038B}", AZ::RPI::FullscreenTrianglePass); + AZ_CLASS_ALLOCATOR(NewDepthOfFieldFilterPass, SystemAllocator, 0); + virtual ~NewDepthOfFieldFilterPass() = default; + + static RPI::Ptr Create(const RPI::PassDescriptor& descriptor); + + protected: + // Behavior functions override... + void FrameBeginInternal(FramePrepareParams params) override; + + private: + NewDepthOfFieldFilterPass(const RPI::PassDescriptor& descriptor); + + // SRG binding indices... + AZ::RHI::ShaderInputNameIndex m_constantsIndex = "m_dofConstants"; + }; + + + } // namespace Render +} // namespace AZ diff --git a/Gems/Atom/Feature/Common/Code/Source/PostProcessing/SMAAFeatureProcessor.cpp b/Gems/Atom/Feature/Common/Code/Source/PostProcessing/SMAAFeatureProcessor.cpp index eef0c51e95..ad059fbec0 100644 --- a/Gems/Atom/Feature/Common/Code/Source/PostProcessing/SMAAFeatureProcessor.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/PostProcessing/SMAAFeatureProcessor.cpp @@ -16,6 +16,7 @@ #include +#include #include #include #include @@ -71,26 +72,18 @@ namespace AZ void SMAAFeatureProcessor::UpdateConvertToPerceptualPass() { - auto* passSystem = AZ::RPI::PassSystemInterface::Get(); - - if (passSystem->HasPassesForTemplateName(m_convertToPerceptualColorPassTemplateNameId)) - { - const AZStd::vector& convertToPerceptualColorPasses = passSystem->GetPassesForTemplateName(m_convertToPerceptualColorPassTemplateNameId); - for (RPI::Pass* pass : convertToPerceptualColorPasses) + RPI::PassFilter passFilter = RPI::PassFilter::CreateWithTemplateName(m_convertToPerceptualColorPassTemplateNameId, GetParentScene()); + RPI::PassSystemInterface::Get()->ForEachPass(passFilter, [this](RPI::Pass* pass) -> RPI::PassFilterExecutionFlow { pass->SetEnabled(m_data.m_enable); - } - } + return RPI::PassFilterExecutionFlow::ContinueVisitingPasses; + }); } void SMAAFeatureProcessor::UpdateEdgeDetectionPass() - { - auto* passSystem = AZ::RPI::PassSystemInterface::Get(); - - if (passSystem->HasPassesForTemplateName(m_edgeDetectioPassTemplateNameId)) - { - const AZStd::vector& edgeDetectionPasses = passSystem->GetPassesForTemplateName(m_edgeDetectioPassTemplateNameId); - for (RPI::Pass* pass : edgeDetectionPasses) + { + RPI::PassFilter passFilter = RPI::PassFilter::CreateWithTemplateName(m_edgeDetectioPassTemplateNameId, GetParentScene()); + RPI::PassSystemInterface::Get()->ForEachPass(passFilter, [this](RPI::Pass* pass) -> RPI::PassFilterExecutionFlow { auto* edgeDetectionPass = azrtti_cast(pass); @@ -106,18 +99,14 @@ namespace AZ edgeDetectionPass->SetPredicationScale(m_data.m_predicationScale); edgeDetectionPass->SetPredicationStrength(m_data.m_predicationStrength); } - } - } + return RPI::PassFilterExecutionFlow::ContinueVisitingPasses; + }); } void SMAAFeatureProcessor::UpdateBlendingWeightCalculationPass() { - auto* passSystem = AZ::RPI::PassSystemInterface::Get(); - - if (passSystem->HasPassesForTemplateName(m_blendingWeightCalculationPassTemplateNameId)) - { - const AZStd::vector& blendingWeightCalculationPasses = passSystem->GetPassesForTemplateName(m_blendingWeightCalculationPassTemplateNameId); - for (RPI::Pass* pass : blendingWeightCalculationPasses) + RPI::PassFilter passFilter = RPI::PassFilter::CreateWithTemplateName(m_blendingWeightCalculationPassTemplateNameId, GetParentScene()); + RPI::PassSystemInterface::Get()->ForEachPass(passFilter, [this](RPI::Pass* pass) -> RPI::PassFilterExecutionFlow { auto* blendingWeightCalculationPass = azrtti_cast(pass); @@ -130,18 +119,14 @@ namespace AZ blendingWeightCalculationPass->SetDiagonalDetectionEnable(m_data.m_enableDiagonalDetection); blendingWeightCalculationPass->SetCornerDetectionEnable(m_data.m_enableCornerDetection); } - } - } + return RPI::PassFilterExecutionFlow::ContinueVisitingPasses; + }); } void SMAAFeatureProcessor::UpdateNeighborhoodBlendingPass() { - auto* passSystem = AZ::RPI::PassSystemInterface::Get(); - - if (passSystem->HasPassesForTemplateName(m_neighborhoodBlendingPassTemplateNameId)) - { - const AZStd::vector& neighborhoodBlendingPasses = passSystem->GetPassesForTemplateName(m_neighborhoodBlendingPassTemplateNameId); - for (RPI::Pass* pass : neighborhoodBlendingPasses) + RPI::PassFilter passFilter = RPI::PassFilter::CreateWithTemplateName(m_neighborhoodBlendingPassTemplateNameId, GetParentScene()); + RPI::PassSystemInterface::Get()->ForEachPass(passFilter, [this](RPI::Pass* pass) -> RPI::PassFilterExecutionFlow { auto* neighborhoodBlendingPass = azrtti_cast(pass); @@ -153,8 +138,8 @@ namespace AZ { neighborhoodBlendingPass->SetOutputMode(SMAAOutputMode::PassThrough); } - } - } + return RPI::PassFilterExecutionFlow::ContinueVisitingPasses; + }); } void SMAAFeatureProcessor::Render([[maybe_unused]] const SMAAFeatureProcessor::RenderPacket& packet) diff --git a/Gems/Atom/Feature/Common/Code/Source/PostProcessing/TaaPass.cpp b/Gems/Atom/Feature/Common/Code/Source/PostProcessing/TaaPass.cpp index 779f02cba0..574d959c07 100644 --- a/Gems/Atom/Feature/Common/Code/Source/PostProcessing/TaaPass.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/PostProcessing/TaaPass.cpp @@ -168,7 +168,6 @@ namespace AZ::Render // The ImageViewDescriptor must be specified to make sure the frame graph compiler doesn't treat this as a transient image. RHI::ImageViewDescriptor viewDesc = RHI::ImageViewDescriptor::Create(imageDesc.m_format, 0, 0); viewDesc.m_aspectFlags = RHI::ImageAspectFlags::Color; - viewDesc.m_overrideBindFlags = RHI::ImageBindFlags::ShaderReadWrite; // The full path name is needed for the attachment image so it's not deduplicated from accumulation images in different pipelines. AZStd::string imageName = RPI::ConcatPassString(GetPathName(), attachment->m_path); diff --git a/Gems/Atom/Feature/Common/Code/Source/ProfilingCaptureSystemComponent.cpp b/Gems/Atom/Feature/Common/Code/Source/ProfilingCaptureSystemComponent.cpp index 66adfe9985..9bfb2b7e9e 100644 --- a/Gems/Atom/Feature/Common/Code/Source/ProfilingCaptureSystemComponent.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/ProfilingCaptureSystemComponent.cpp @@ -377,14 +377,7 @@ namespace AZ bool ProfilingCaptureSystemComponent::CapturePassTimestamp(const AZStd::string& outputFilePath) { - // Find the root pass. - AZStd::vector passes = FindPasses({ "Root" }); - if (passes.empty()) - { - return false; - } - - RPI::Pass* root = passes[0]; + RPI::Pass* root = AZ::RPI::PassSystemInterface::Get()->GetRootPass().get(); // Enable all the Timestamp queries in passes. root->SetTimestampQueryEnabled(true); @@ -465,14 +458,7 @@ namespace AZ bool ProfilingCaptureSystemComponent::CapturePassPipelineStatistics(const AZStd::string& outputFilePath) { - // Find the root pass. - AZStd::vector passes = FindPasses({ "Root" }); - if (passes.empty()) - { - return false; - } - - RPI::Pass* root = passes[0]; + RPI::Pass* root = AZ::RPI::PassSystemInterface::Get()->GetRootPass().get(); // Enable all the PipelineStatistics queries in passes. root->SetPipelineStatisticsQueryEnabled(true); @@ -572,19 +558,6 @@ namespace AZ return passes; } - AZStd::vector ProfilingCaptureSystemComponent::FindPasses(AZStd::vector&& passHierarchy) const - { - // Find the pass first. - RPI::PassHierarchyFilter passFilter(passHierarchy); - AZStd::vector foundPasses = AZ::RPI::PassSystemInterface::Get()->FindPasses(passFilter); - if (foundPasses.size() == 0) - { - AZ_Warning("ProfilingCaptureSystemComponent", false, "Failed to find pass from %s", passFilter.ToString().c_str()); - } - - return foundPasses; - } - void ProfilingCaptureSystemComponent::OnTick([[maybe_unused]] float deltaTime, [[maybe_unused]] ScriptTimePoint time) { // Update the delayed captures diff --git a/Gems/Atom/Feature/Common/Code/Source/ProfilingCaptureSystemComponent.h b/Gems/Atom/Feature/Common/Code/Source/ProfilingCaptureSystemComponent.h index a9bb8c585f..af1d2f5643 100644 --- a/Gems/Atom/Feature/Common/Code/Source/ProfilingCaptureSystemComponent.h +++ b/Gems/Atom/Feature/Common/Code/Source/ProfilingCaptureSystemComponent.h @@ -78,8 +78,6 @@ namespace AZ // Recursively collect all the passes from the root pass. AZStd::vector CollectPassesRecursively(const RPI::Pass* root) const; - AZStd::vector FindPasses(AZStd::vector&& passHierarchy) const; - DelayedQueryCaptureHelper m_timestampCapture; DelayedQueryCaptureHelper m_cpuFrameTimeStatisticsCapture; DelayedQueryCaptureHelper m_pipelineStatisticsCapture; diff --git a/Gems/Atom/Feature/Common/Code/Source/ReflectionScreenSpace/ReflectionCopyFrameBufferPass.cpp b/Gems/Atom/Feature/Common/Code/Source/ReflectionScreenSpace/ReflectionCopyFrameBufferPass.cpp index 37b1885ee3..a1858a7e9d 100644 --- a/Gems/Atom/Feature/Common/Code/Source/ReflectionScreenSpace/ReflectionCopyFrameBufferPass.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/ReflectionScreenSpace/ReflectionCopyFrameBufferPass.cpp @@ -28,16 +28,17 @@ namespace AZ void ReflectionCopyFrameBufferPass::BuildInternal() { - RPI::PassHierarchyFilter passFilter(AZ::Name("ReflectionScreenSpaceBlurPass")); - const AZStd::vector& passes = RPI::PassSystemInterface::Get()->FindPasses(passFilter); - if (!passes.empty()) - { - Render::ReflectionScreenSpaceBlurPass* blurPass = azrtti_cast(passes.front()); - Data::Instance& frameBufferAttachment = blurPass->GetFrameBufferImageAttachment(); + RPI::PassFilter passFilter = RPI::PassFilter::CreateWithPassName(AZ::Name("ReflectionScreenSpaceBlurPass"), GetRenderPipeline()); + RPI::PassSystemInterface::Get()->ForEachPass(passFilter, [this](RPI::Pass* pass) -> RPI::PassFilterExecutionFlow + { + Render::ReflectionScreenSpaceBlurPass* blurPass = azrtti_cast(pass); + Data::Instance& frameBufferAttachment = blurPass->GetFrameBufferImageAttachment(); - RPI::PassAttachmentBinding& outputBinding = GetOutputBinding(0); - AttachImageToSlot(outputBinding.m_name, frameBufferAttachment); - } + RPI::PassAttachmentBinding& outputBinding = GetOutputBinding(0); + AttachImageToSlot(outputBinding.m_name, frameBufferAttachment); + + return RPI::PassFilterExecutionFlow::StopVisitingPasses; + }); FullscreenTrianglePass::BuildInternal(); } diff --git a/Gems/Atom/Feature/Common/Code/Source/ReflectionScreenSpace/ReflectionScreenSpaceBlurPass.cpp b/Gems/Atom/Feature/Common/Code/Source/ReflectionScreenSpace/ReflectionScreenSpaceBlurPass.cpp index 394a6fd406..edd7ad1013 100644 --- a/Gems/Atom/Feature/Common/Code/Source/ReflectionScreenSpace/ReflectionScreenSpaceBlurPass.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/ReflectionScreenSpace/ReflectionScreenSpaceBlurPass.cpp @@ -150,7 +150,7 @@ namespace AZ auto transientImageDesc = RHI::ImageDescriptor::Create2D(imageBindFlags, mipSize.m_width, mipSize.m_height, RHI::Format::R16G16B16A16_FLOAT); RPI::PassAttachment* transientPassAttachment = aznew RPI::PassAttachment(); - AZStd::string transientAttachmentName = AZStd::string::format("ReflectionScreenSpace_BlurImage%d", mip); + AZStd::string transientAttachmentName = AZStd::string::format("%s.ReflectionScreenSpace_BlurImage%d", GetPathName().GetCStr(), mip); transientPassAttachment->m_name = transientAttachmentName; transientPassAttachment->m_path = transientAttachmentName; transientPassAttachment->m_lifetime = RHI::AttachmentLifetimeType::Transient; diff --git a/Gems/Atom/Feature/Common/Code/Source/ReflectionScreenSpace/ReflectionScreenSpaceCompositePass.cpp b/Gems/Atom/Feature/Common/Code/Source/ReflectionScreenSpace/ReflectionScreenSpaceCompositePass.cpp index 1cf227650c..1362191691 100644 --- a/Gems/Atom/Feature/Common/Code/Source/ReflectionScreenSpace/ReflectionScreenSpaceCompositePass.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/ReflectionScreenSpace/ReflectionScreenSpaceCompositePass.cpp @@ -33,20 +33,22 @@ namespace AZ return; } - RPI::PassHierarchyFilter passFilter(AZ::Name("ReflectionScreenSpaceBlurPass")); - const AZStd::vector& passes = RPI::PassSystemInterface::Get()->FindPasses(passFilter); - if (!passes.empty()) - { - Render::ReflectionScreenSpaceBlurPass* blurPass = azrtti_cast(passes.front()); + RPI::PassFilter passFilter = RPI::PassFilter::CreateWithPassName(AZ::Name("ReflectionScreenSpaceBlurPass"), GetRenderPipeline()); - // compute the max mip level based on the available mips in the previous frame image, and capping it - // to stay within a range that has reasonable data - const uint32_t MaxNumRoughnessMips = 8; - uint32_t maxMipLevel = AZStd::min(MaxNumRoughnessMips, blurPass->GetNumBlurMips()) - 1; + RPI::PassSystemInterface::Get()->ForEachPass(passFilter, [this](RPI::Pass* pass) -> RPI::PassFilterExecutionFlow + { + Render::ReflectionScreenSpaceBlurPass* blurPass = azrtti_cast(pass); - auto constantIndex = m_shaderResourceGroup->FindShaderInputConstantIndex(Name("m_maxMipLevel")); - m_shaderResourceGroup->SetConstant(constantIndex, maxMipLevel); - } + // compute the max mip level based on the available mips in the previous frame image, and capping it + // to stay within a range that has reasonable data + const uint32_t MaxNumRoughnessMips = 8; + uint32_t maxMipLevel = AZStd::min(MaxNumRoughnessMips, blurPass->GetNumBlurMips()) - 1; + + auto constantIndex = m_shaderResourceGroup->FindShaderInputConstantIndex(Name("m_maxMipLevel")); + m_shaderResourceGroup->SetConstant(constantIndex, maxMipLevel); + + return RPI::PassFilterExecutionFlow::StopVisitingPasses; + }); FullscreenTrianglePass::CompileResources(context); } diff --git a/Gems/Atom/Feature/Common/Code/Source/Shadows/ProjectedShadowFeatureProcessor.cpp b/Gems/Atom/Feature/Common/Code/Source/Shadows/ProjectedShadowFeatureProcessor.cpp index 7c0b3563c7..70ccaa5702 100644 --- a/Gems/Atom/Feature/Common/Code/Source/Shadows/ProjectedShadowFeatureProcessor.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/Shadows/ProjectedShadowFeatureProcessor.cpp @@ -313,52 +313,38 @@ namespace AZ::Render void ProjectedShadowFeatureProcessor::CachePasses() { - const AZStd::vector validPipelineIds = CacheProjectedShadowmapsPass(); - CacheEsmShadowmapsPass(validPipelineIds); + CacheProjectedShadowmapsPass(); + CacheEsmShadowmapsPass(); m_shadowmapPassNeedsUpdate = true; } - AZStd::vector ProjectedShadowFeatureProcessor::CacheProjectedShadowmapsPass() + void ProjectedShadowFeatureProcessor::CacheProjectedShadowmapsPass() { - const AZStd::vector& renderPipelines = GetParentScene()->GetRenderPipelines(); - const auto* passSystem = RPI::PassSystemInterface::Get();; - const AZStd::vector& passes = passSystem->GetPassesForTemplateName(Name("ProjectedShadowmapsTemplate")); - - AZStd::vector validPipelineIds; m_projectedShadowmapsPasses.clear(); - for (RPI::Pass* pass : passes) - { - ProjectedShadowmapsPass* shadowPass = static_cast(pass); - for (const RPI::RenderPipelinePtr& pipeline : renderPipelines) + RPI::PassFilter passFilter = RPI::PassFilter::CreateWithTemplateName(Name("ProjectedShadowmapsTemplate"), GetParentScene()); + RPI::PassSystemInterface::Get()->ForEachPass(passFilter, [this](RPI::Pass* pass) -> RPI::PassFilterExecutionFlow { - if (pipeline.get() == shadowPass->GetRenderPipeline()) - { - m_projectedShadowmapsPasses.emplace_back(shadowPass); - validPipelineIds.push_back(shadowPass->GetRenderPipeline()->GetId()); - } - } - } - return validPipelineIds; + ProjectedShadowmapsPass* shadowPass = static_cast(pass); + m_projectedShadowmapsPasses.emplace_back(shadowPass); + return RPI::PassFilterExecutionFlow::ContinueVisitingPasses; + }); } - void ProjectedShadowFeatureProcessor::CacheEsmShadowmapsPass(const AZStd::vector& validPipelineIds) + void ProjectedShadowFeatureProcessor::CacheEsmShadowmapsPass() { const Name LightTypeName = Name("projected"); - - const auto* passSystem = RPI::PassSystemInterface::Get(); - const AZStd::vector passes = passSystem->GetPassesForTemplateName(Name("EsmShadowmapsTemplate")); - + m_esmShadowmapsPasses.clear(); - for (RPI::Pass* pass : passes) - { - EsmShadowmapsPass* esmPass = static_cast(pass); - if (esmPass->GetRenderPipeline() && - AZStd::find(validPipelineIds.begin(), validPipelineIds.end(), esmPass->GetRenderPipeline()->GetId()) != validPipelineIds.end() && - esmPass->GetLightTypeName() == LightTypeName) + RPI::PassFilter passFilter = RPI::PassFilter::CreateWithTemplateName(Name("EsmShadowmapsTemplate"), GetParentScene()); + RPI::PassSystemInterface::Get()->ForEachPass(passFilter, [this, LightTypeName](RPI::Pass* pass) -> RPI::PassFilterExecutionFlow { - m_esmShadowmapsPasses.emplace_back(esmPass); - } - } + EsmShadowmapsPass* esmPass = static_cast(pass); + if (esmPass->GetLightTypeName() == LightTypeName) + { + m_esmShadowmapsPasses.emplace_back(esmPass); + } + return RPI::PassFilterExecutionFlow::ContinueVisitingPasses; + }); } void ProjectedShadowFeatureProcessor::UpdateFilterParameters() diff --git a/Gems/Atom/Feature/Common/Code/Source/Shadows/ProjectedShadowFeatureProcessor.h b/Gems/Atom/Feature/Common/Code/Source/Shadows/ProjectedShadowFeatureProcessor.h index fafcb25a08..8939f1845d 100644 --- a/Gems/Atom/Feature/Common/Code/Source/Shadows/ProjectedShadowFeatureProcessor.h +++ b/Gems/Atom/Feature/Common/Code/Source/Shadows/ProjectedShadowFeatureProcessor.h @@ -97,8 +97,8 @@ namespace AZ::Render // Functions for caching the ProjectedShadowmapsPass and EsmShadowmapsPass. void CachePasses(); - AZStd::vector CacheProjectedShadowmapsPass(); - void CacheEsmShadowmapsPass(const AZStd::vector& validPipelineIds); + void CacheProjectedShadowmapsPass(); + void CacheEsmShadowmapsPass(); //! Functions to update the parameter of Gaussian filter used in ESM. void UpdateFilterParameters(); diff --git a/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshFeatureProcessor.cpp b/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshFeatureProcessor.cpp index e0209702dc..4c379c4239 100644 --- a/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshFeatureProcessor.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshFeatureProcessor.cpp @@ -17,6 +17,7 @@ #include #include +#include #include #include #include @@ -241,12 +242,12 @@ namespace AZ void SkinnedMeshFeatureProcessor::OnRenderPipelineAdded(RPI::RenderPipelinePtr pipeline) { - InitSkinningAndMorphPass(pipeline->GetRootPass()); + InitSkinningAndMorphPass(pipeline.get()); } void SkinnedMeshFeatureProcessor::OnRenderPipelinePassesChanged(RPI::RenderPipeline* renderPipeline) { - InitSkinningAndMorphPass(renderPipeline->GetRootPass()); + InitSkinningAndMorphPass(renderPipeline); } void SkinnedMeshFeatureProcessor::OnBeginPrepareRender() @@ -289,9 +290,10 @@ namespace AZ return false; } - void SkinnedMeshFeatureProcessor::InitSkinningAndMorphPass(const RPI::Ptr pipelineRootPass) + void SkinnedMeshFeatureProcessor::InitSkinningAndMorphPass(RPI::RenderPipeline* renderPipeline) { - RPI::Ptr skinningPass = pipelineRootPass->FindPassByNameRecursive(AZ::Name{ "SkinningPass" }); + RPI::PassFilter skinPassFilter = RPI::PassFilter::CreateWithPassName(AZ::Name{ "SkinningPass" }, renderPipeline); + RPI::Ptr skinningPass = RPI::PassSystemInterface::Get()->FindFirstPass(skinPassFilter); if (skinningPass) { SkinnedMeshComputePass* skinnedMeshComputePass = azdynamic_cast(skinningPass.get()); @@ -310,7 +312,8 @@ namespace AZ } } - RPI::Ptr morphTargetPass = pipelineRootPass->FindPassByNameRecursive(AZ::Name{ "MorphTargetPass" }); + RPI::PassFilter morphPassFilter = RPI::PassFilter::CreateWithPassName(AZ::Name{ "MorphTargetPass" }, renderPipeline); + RPI::Ptr morphTargetPass = RPI::PassSystemInterface::Get()->FindFirstPass(morphPassFilter); if (morphTargetPass) { MorphTargetComputePass* morphTargetComputePass = azdynamic_cast(morphTargetPass.get()); diff --git a/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshFeatureProcessor.h b/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshFeatureProcessor.h index 2e93acf2cd..5b7ab943e1 100644 --- a/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshFeatureProcessor.h +++ b/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshFeatureProcessor.h @@ -66,7 +66,7 @@ namespace AZ private: AZ_DISABLE_COPY_MOVE(SkinnedMeshFeatureProcessor); - void InitSkinningAndMorphPass(const RPI::Ptr pipelineRootPass); + void InitSkinningAndMorphPass(RPI::RenderPipeline* renderPipeline); SkinnedMeshRenderProxyInterfaceHandle AcquireRenderProxyInterface(const SkinnedMeshRenderProxyDesc& desc) override; bool ReleaseRenderProxyInterface(SkinnedMeshRenderProxyInterfaceHandle& handle) override; diff --git a/Gems/Atom/Feature/Common/Code/atom_feature_common_files.cmake b/Gems/Atom/Feature/Common/Code/atom_feature_common_files.cmake index c875f7c177..184460b210 100644 --- a/Gems/Atom/Feature/Common/Code/atom_feature_common_files.cmake +++ b/Gems/Atom/Feature/Common/Code/atom_feature_common_files.cmake @@ -243,6 +243,8 @@ set(FILES Source/PostProcessing/LookModificationTransformPass.h Source/PostProcessing/LuminanceHistogramGeneratorPass.h Source/PostProcessing/LuminanceHistogramGeneratorPass.cpp + Source/PostProcessing/NewDepthOfFieldPasses.cpp + Source/PostProcessing/NewDepthOfFieldPasses.h Source/PostProcessing/PostProcessingShaderOptionBase.cpp Source/PostProcessing/PostProcessingShaderOptionBase.h Source/PostProcessing/SMAABasePass.cpp diff --git a/Gems/Atom/Feature/Common/Editor/Scripts/ColorGrading/activate_lut_asset.py b/Gems/Atom/Feature/Common/Editor/Scripts/ColorGrading/activate_lut_asset.py index 016ad024aa..8fe3126a77 100644 --- a/Gems/Atom/Feature/Common/Editor/Scripts/ColorGrading/activate_lut_asset.py +++ b/Gems/Atom/Feature/Common/Editor/Scripts/ColorGrading/activate_lut_asset.py @@ -60,6 +60,11 @@ def activate_look_modification_lut(look_modification_component, asset_relative_p LOOK_MODIFICATION_ENABLE_PROPERTY_PATH, True ) + azlmbr.editor.EditorComponentAPIBus( + azlmbr.bus.Broadcast, + "EnableComponents", + [look_modification_component] + ) def activate_lut_asset(entity_id, asset_relative_path): disable_hdr_color_grading_component(entity_id) diff --git a/Gems/Atom/RHI/Code/Include/Atom/RHI.Edit/ShaderCompilerArguments.h b/Gems/Atom/RHI/Code/Include/Atom/RHI.Edit/ShaderCompilerArguments.h index d0960a7fde..83a868ab65 100644 --- a/Gems/Atom/RHI/Code/Include/Atom/RHI.Edit/ShaderCompilerArguments.h +++ b/Gems/Atom/RHI/Code/Include/Atom/RHI.Edit/ShaderCompilerArguments.h @@ -57,12 +57,19 @@ namespace AZ AZStd::string m_azslcAdditionalFreeArguments; // note: if you add new sort of arguments here, don't forget to update HasDifferentAzslcArguments() - //! DXC - bool m_dxcDisableWarnings = false; - bool m_dxcWarningAsError = false; - bool m_dxcDisableOptimizations = false; - bool m_dxcGenerateDebugInfo = false; - uint8_t m_dxcOptimizationLevel = LevelUnset; + //! Remark: To the user, the following parameters are exposed without the + //! "Dxc" prefix because these are common options for the "main" compiler + //! for the given RHI. At the moment the only "main" compiler is Dxc, but in + //! the future AZSLc may transpile from AZSL to some other proprietary language + //! and in that case the "main" compiler won't be DXC + bool m_disableWarnings = false; + bool m_warningAsError = false; + bool m_disableOptimizations = false; + bool m_generateDebugInfo = false; + uint8_t m_optimizationLevel = LevelUnset; + //! "DxcAdditionalFreeArguments" keeps the "Dxc" prefix because these arguments + //! are specific to DXC, and it will be relevant only if DXC is the "main" compiler + //! for a given RHI, otherwise this parameter won't matter. AZStd::string m_dxcAdditionalFreeArguments; //! both diff --git a/Gems/Atom/RHI/Code/Include/Atom/RHI.Edit/Utils.h b/Gems/Atom/RHI/Code/Include/Atom/RHI.Edit/Utils.h index 0624dce5f8..d648f05b46 100644 --- a/Gems/Atom/RHI/Code/Include/Atom/RHI.Edit/Utils.h +++ b/Gems/Atom/RHI/Code/Include/Atom/RHI.Edit/Utils.h @@ -83,11 +83,12 @@ namespace AZ const AZStd::string& shaderSourcePathForDebug, const char* toolNameForLog); - //! Reports error messages to AZ_Error and/or AZ_Warning, given a text blob that potentially contains many lines of errors and warnings. - //! @param window Debug window name used for AZ Trace functions - //! @param errorMessages String that may contain many lines of errors and warnings - //! @param return true if Errors were detected and reported (Warnings don't count) - bool ReportErrorMessages(AZStd::string_view window, AZStd::string_view errorMessages); + //! Reports messages with AZ_Error or AZ_Warning (See @reportAsErrors). + //! @param window Debug window name used for AZ Trace functions. + //! @param errorMessages Message string. + //! @param reportAsErrors If true, messages are traced with AZ_Error, otherwise AZ_Warning is used. + //! @returns true If the input text blob contains at least one line with the "error" string. + bool ReportMessages(AZStd::string_view window, AZStd::string_view errorMessages, bool reportAsErrors); //! Converts from a RHI::ShaderHardwareStage to an RHI::ShaderStage ShaderStage ToRHIShaderStage(ShaderHardwareStage stageType); diff --git a/Gems/Atom/RHI/Code/Include/Atom/RHI.Reflect/ShaderInputNameIndex.h b/Gems/Atom/RHI/Code/Include/Atom/RHI.Reflect/ShaderInputNameIndex.h index 03062afd52..5e9b1a9c77 100644 --- a/Gems/Atom/RHI/Code/Include/Atom/RHI.Reflect/ShaderInputNameIndex.h +++ b/Gems/Atom/RHI/Code/Include/Atom/RHI.Reflect/ShaderInputNameIndex.h @@ -67,6 +67,10 @@ namespace AZ bool IsInitialized() const; void AssetInialized() const; + // Retrieves the underlying name. Should only be used for debug purposes like printing the name when we fail to bind to the SRG. + // All regular functionality should go through the above functions. + const Name& GetNameForDebug() const; + private: enum class IndexType : u32 diff --git a/Gems/Atom/RHI/Code/Source/RHI.Edit/ShaderCompilerArguments.cpp b/Gems/Atom/RHI/Code/Source/RHI.Edit/ShaderCompilerArguments.cpp index ef330e7902..5411e6655e 100644 --- a/Gems/Atom/RHI/Code/Source/RHI.Edit/ShaderCompilerArguments.cpp +++ b/Gems/Atom/RHI/Code/Source/RHI.Edit/ShaderCompilerArguments.cpp @@ -33,17 +33,17 @@ namespace AZ RegisterEnumerators(serializeContext); serializeContext->Class() - ->Version(2) + ->Version(3) ->Field("AzslcWarningLevel", &ShaderCompilerArguments::m_azslcWarningLevel) ->Field("AzslcWarningAsError", &ShaderCompilerArguments::m_azslcWarningAsError) ->Field("AzslcAdditionalFreeArguments", &ShaderCompilerArguments::m_azslcAdditionalFreeArguments) - ->Field("DxcDisableWarnings", &ShaderCompilerArguments::m_dxcDisableWarnings) - ->Field("DxcWarningAsError", &ShaderCompilerArguments::m_dxcWarningAsError) - ->Field("DxcDisableOptimizations", &ShaderCompilerArguments::m_dxcDisableOptimizations) - ->Field("DxcGenerateDebugInfo", &ShaderCompilerArguments::m_dxcGenerateDebugInfo) - ->Field("DxcOptimizationLevel", &ShaderCompilerArguments::m_dxcOptimizationLevel) - ->Field("DxcAdditionalFreeArguments", &ShaderCompilerArguments::m_dxcAdditionalFreeArguments) + ->Field("DisableWarnings", &ShaderCompilerArguments::m_disableWarnings) + ->Field("WarningAsError", &ShaderCompilerArguments::m_warningAsError) + ->Field("DisableOptimizations", &ShaderCompilerArguments::m_disableOptimizations) + ->Field("GenerateDebugInfo", &ShaderCompilerArguments::m_generateDebugInfo) + ->Field("OptimizationLevel", &ShaderCompilerArguments::m_optimizationLevel) ->Field("DefaultMatrixOrder", &ShaderCompilerArguments::m_defaultMatrixOrder) + ->Field("DxcAdditionalFreeArguments", &ShaderCompilerArguments::m_dxcAdditionalFreeArguments) ; } } @@ -62,13 +62,13 @@ namespace AZ } m_azslcWarningAsError = m_azslcWarningAsError || right.m_azslcWarningAsError; m_azslcAdditionalFreeArguments = CommandLineArgumentUtils::MergeCommandLineArguments(m_azslcAdditionalFreeArguments, right.m_azslcAdditionalFreeArguments); - m_dxcDisableWarnings = m_dxcDisableWarnings || right.m_dxcDisableWarnings; - m_dxcWarningAsError = m_dxcWarningAsError || right.m_dxcWarningAsError; - m_dxcDisableOptimizations = m_dxcDisableOptimizations || right.m_dxcDisableOptimizations; - m_dxcGenerateDebugInfo = m_dxcGenerateDebugInfo || right.m_dxcGenerateDebugInfo; - if (right.m_dxcOptimizationLevel != LevelUnset) + m_disableWarnings = m_disableWarnings || right.m_disableWarnings; + m_warningAsError = m_warningAsError || right.m_warningAsError; + m_disableOptimizations = m_disableOptimizations || right.m_disableOptimizations; + m_generateDebugInfo = m_generateDebugInfo || right.m_generateDebugInfo; + if (right.m_optimizationLevel != LevelUnset) { - m_dxcOptimizationLevel = right.m_dxcOptimizationLevel; + m_optimizationLevel = right.m_optimizationLevel; } m_dxcAdditionalFreeArguments = CommandLineArgumentUtils::MergeCommandLineArguments(m_dxcAdditionalFreeArguments, right.m_dxcAdditionalFreeArguments); if (right.m_defaultMatrixOrder != MatrixOrder::Default) @@ -131,21 +131,21 @@ namespace AZ AZStd::string ShaderCompilerArguments::MakeAdditionalDxcCommandLineString() const { AZStd::string arguments; - if (m_dxcDisableWarnings) + if (m_disableWarnings) { arguments += " -no-warnings"; } - else if (m_dxcWarningAsError) + else if (m_warningAsError) { arguments += " -WX"; } - if (m_dxcDisableOptimizations) + if (m_disableOptimizations) { arguments += " -Od"; } - else if (m_dxcOptimizationLevel <= 3) + else if (m_optimizationLevel <= 3) { - arguments = " -O" + AZStd::to_string(m_dxcOptimizationLevel); + arguments = " -O" + AZStd::to_string(m_optimizationLevel); } if (m_defaultMatrixOrder == MatrixOrder::Column) { diff --git a/Gems/Atom/RHI/Code/Source/RHI.Edit/Utils.cpp b/Gems/Atom/RHI/Code/Source/RHI.Edit/Utils.cpp index b4c68b3f75..dc203ef731 100644 --- a/Gems/Atom/RHI/Code/Source/RHI.Edit/Utils.cpp +++ b/Gems/Atom/RHI/Code/Source/RHI.Edit/Utils.cpp @@ -330,7 +330,7 @@ namespace AZ // Pump one last time to make sure the streams have been flushed pumpOuputStreams(); - const bool reportedErrors = ReportErrorMessages(toolNameForLog, errorMessages); + const bool reportedErrors = ReportMessages(toolNameForLog, errorMessages, exitCode != 0); if (timedOut) { @@ -367,32 +367,20 @@ namespace AZ return true; } - bool ReportErrorMessages([[maybe_unused]] AZStd::string_view window, AZStd::string_view errorMessages) + bool ReportMessages([[maybe_unused]] AZStd::string_view window, AZStd::string_view errorMessages, bool reportAsErrors) { - // There are more efficient ways to do this, but this approach is simple and gets us moving for now. - AZStd::vector lines; - AzFramework::StringFunc::Tokenize(errorMessages.data(), lines, "\n\r"); - - bool foundErrors = false; - - for (auto& line : lines) + if (reportAsErrors) { - if (AZStd::string::npos != AzFramework::StringFunc::Find(line, "error")) - { - AZ_Error(window.data(), false, "%s", line.data()); - foundErrors = true; - } - else if (AZStd::string::npos != AzFramework::StringFunc::Find(line, "warning")) - { - AZ_Warning(window.data(), false, "%s", line.data()); - } - else - { - AZ_TracePrintf(window.data(), "%s", line.data()); - } + AZ_Error(window.data(), false, "%.*s", aznumeric_cast(errorMessages.size()), errorMessages.data()); } - - return foundErrors; + else + { + // Using AZ_Warning instead of AZ_TracePrintf because this function is commonly + // used to report messages from stderr when executing applications. Applications + // when ran successfully, only output to stderr for errors or warnings. + AZ_Warning(window.data(), false, "%.*s", aznumeric_cast(errorMessages.size()), errorMessages.data()); + } + return AZStd::string::npos != AzFramework::StringFunc::Find(errorMessages, "error"); } ShaderStage ToRHIShaderStage(ShaderHardwareStage stageType) diff --git a/Gems/Atom/RHI/Code/Source/RHI.Reflect/ShaderInputNameIndex.cpp b/Gems/Atom/RHI/Code/Source/RHI.Reflect/ShaderInputNameIndex.cpp index 2eb23895f8..b7ed2dbfe7 100644 --- a/Gems/Atom/RHI/Code/Source/RHI.Reflect/ShaderInputNameIndex.cpp +++ b/Gems/Atom/RHI/Code/Source/RHI.Reflect/ShaderInputNameIndex.cpp @@ -227,5 +227,11 @@ namespace AZ } + const Name& ShaderInputNameIndex::GetNameForDebug() const + { + AZ_Assert(HasName(), "GetNameForDebug() called on ShaderInputNameIndex that doesn't have a name set. Please initialize it with a name.", m_name.GetCStr()); + return m_name; + } + } } diff --git a/Gems/Atom/RHI/DX12/Code/CMakeLists.txt b/Gems/Atom/RHI/DX12/Code/CMakeLists.txt index b913ad58bf..975b838ffa 100644 --- a/Gems/Atom/RHI/DX12/Code/CMakeLists.txt +++ b/Gems/Atom/RHI/DX12/Code/CMakeLists.txt @@ -107,7 +107,7 @@ ly_add_target( Gem::Atom_RHI.Reflect Gem::Atom_RHI_DX12.Reflect 3rdParty::d3dx12 - ${AFTERMATH_BUILD_DEPENDENCY} + ${AFTERMATH_BUILD_DEPENDENCY} COMPILE_DEFINITIONS PRIVATE ${USE_NSIGHT_AFTERMATH_DEFINE} @@ -128,7 +128,6 @@ ly_add_target( BUILD_DEPENDENCIES PRIVATE AZ::AzCore - Gem::Atom_RHI.Reflect Gem::Atom_RHI.Public Gem::Atom_RHI_DX12.Reflect diff --git a/Gems/Atom/RHI/DX12/Code/Source/Platform/Windows/RHI/SwapChain_Windows.cpp b/Gems/Atom/RHI/DX12/Code/Source/Platform/Windows/RHI/SwapChain_Windows.cpp index ab14f9b5d3..06477135f4 100644 --- a/Gems/Atom/RHI/DX12/Code/Source/Platform/Windows/RHI/SwapChain_Windows.cpp +++ b/Gems/Atom/RHI/DX12/Code/Source/Platform/Windows/RHI/SwapChain_Windows.cpp @@ -82,9 +82,6 @@ namespace AZ // ALT+ENTER fullscreen switching using IDXGIFactory::MakeWindowAssociation (see also implementation of SwapChain::PresentInternal). // You must call the MakeWindowAssociation method after the creation of the swap chain, and on the factory object associated with the // target HWND swap chain, which you can guarantee by calling the IDXGIObject::GetParent method on the swap chain to locate the factory. - // - // ToDo: ATOM-14673 We should handle ALT+ENTER in the windows message loop and call AzFramework::NativeWindow::ToggleFullScreenState in - // response, but that will have to wait until the WndProc function moves out of CrySystem (ideally into AzFramework::ApplicationWindows). IDXGIFactoryX* parentFactory = nullptr; m_swapChain->GetParent(__uuidof(IDXGIFactoryX), (void **)&parentFactory); DX12::AssertSuccess(parentFactory->MakeWindowAssociation(reinterpret_cast(window), DXGI_MWA_NO_ALT_ENTER)); diff --git a/Gems/Atom/RHI/DX12/Code/Source/RHI.Builders/ShaderPlatformInterface.cpp b/Gems/Atom/RHI/DX12/Code/Source/RHI.Builders/ShaderPlatformInterface.cpp index f30fc72ace..ee293292e1 100644 --- a/Gems/Atom/RHI/DX12/Code/Source/RHI.Builders/ShaderPlatformInterface.cpp +++ b/Gems/Atom/RHI/DX12/Code/Source/RHI.Builders/ShaderPlatformInterface.cpp @@ -114,7 +114,7 @@ namespace AZ } } - if (shaderCompilerArguments.m_dxcDisableOptimizations) + if (shaderCompilerArguments.m_disableOptimizations) { // When optimizations are disabled (-Od), all resources declared in the source file are available to all stages // (when enabled only the resources which are referenced in a stage are bound to the stage) @@ -195,7 +195,7 @@ namespace AZ bool ShaderPlatformInterface::BuildHasDebugInfo(const RHI::ShaderCompilerArguments& shaderCompilerArguments) const { - return shaderCompilerArguments.m_dxcGenerateDebugInfo; + return shaderCompilerArguments.m_generateDebugInfo; } const char* ShaderPlatformInterface::GetAzslHeader(const AssetBuilderSDK::PlatformInfo& platform) const diff --git a/Gems/Atom/RHI/Metal/Code/Source/RHI.Builders/ShaderPlatformInterface.cpp b/Gems/Atom/RHI/Metal/Code/Source/RHI.Builders/ShaderPlatformInterface.cpp index 7e41e50892..d43d88a7e1 100644 --- a/Gems/Atom/RHI/Metal/Code/Source/RHI.Builders/ShaderPlatformInterface.cpp +++ b/Gems/Atom/RHI/Metal/Code/Source/RHI.Builders/ShaderPlatformInterface.cpp @@ -167,7 +167,7 @@ namespace AZ bool ShaderPlatformInterface::BuildHasDebugInfo(const RHI::ShaderCompilerArguments& shaderCompilerArguments) const { - return shaderCompilerArguments.m_dxcGenerateDebugInfo; + return shaderCompilerArguments.m_generateDebugInfo; } const char* ShaderPlatformInterface::GetAzslHeader(const AssetBuilderSDK::PlatformInfo& platform) const diff --git a/Gems/Atom/RHI/Vulkan/Code/Source/RHI.Builders/ShaderPlatformInterface.cpp b/Gems/Atom/RHI/Vulkan/Code/Source/RHI.Builders/ShaderPlatformInterface.cpp index 8b99b7b510..c5f1060ca3 100644 --- a/Gems/Atom/RHI/Vulkan/Code/Source/RHI.Builders/ShaderPlatformInterface.cpp +++ b/Gems/Atom/RHI/Vulkan/Code/Source/RHI.Builders/ShaderPlatformInterface.cpp @@ -109,7 +109,7 @@ namespace AZ bool ShaderPlatformInterface::BuildHasDebugInfo(const RHI::ShaderCompilerArguments& shaderCompilerArguments) const { - return shaderCompilerArguments.m_dxcGenerateDebugInfo; + return shaderCompilerArguments.m_generateDebugInfo; } const char* ShaderPlatformInterface::GetAzslHeader(const AssetBuilderSDK::PlatformInfo& platform) const diff --git a/Gems/Atom/RHI/Vulkan/Code/Source/RHI/CommandList.cpp b/Gems/Atom/RHI/Vulkan/Code/Source/RHI/CommandList.cpp index 2620aa5f7b..ffa02ac3c4 100644 --- a/Gems/Atom/RHI/Vulkan/Code/Source/RHI/CommandList.cpp +++ b/Gems/Atom/RHI/Vulkan/Code/Source/RHI/CommandList.cpp @@ -729,8 +729,7 @@ namespace AZ void CommandList::SetStencilRef(uint8_t stencilRef) { - vkCmdSetStencilReference(m_nativeCommandBuffer, VK_STENCIL_FACE_FRONT_BIT, static_cast(stencilRef)); - vkCmdSetStencilReference(m_nativeCommandBuffer, VK_STENCIL_FACE_BACK_BIT, static_cast(stencilRef)); + vkCmdSetStencilReference(m_nativeCommandBuffer, VK_STENCIL_FACE_FRONT_AND_BACK, aznumeric_cast(stencilRef)); } void CommandList::BindPipeline(const PipelineState* pipelineState) diff --git a/Gems/Atom/RPI/Assets/ShaderLib/Atom/RPI/Math.azsli b/Gems/Atom/RPI/Assets/ShaderLib/Atom/RPI/Math.azsli index 80f8a6cc36..6ffdb6e815 100644 --- a/Gems/Atom/RPI/Assets/ShaderLib/Atom/RPI/Math.azsli +++ b/Gems/Atom/RPI/Assets/ShaderLib/Atom/RPI/Math.azsli @@ -31,6 +31,8 @@ void swap(inout float a, inout float b) b = c; } +// ---------- Power ----------- + float Pow2(float x) { return x * x; @@ -48,6 +50,44 @@ float Pow5(float x) { return x * Pow4(x); } + +// ---------- Min & Max ----------- + +float min3(float a, float b, float c) +{ + return min(min(a, b), c); +} +float min4(float a, float b, float c, float d) +{ + return min(min3(a, b, c), d); +} +float max3(float a, float b, float c) +{ + return max(max(a, b), c); +} +float max4(float a, float b, float c, float d) +{ + return max(max3(a, b, c), d); +} + +float min3(float3 abc) +{ + return min3(abc.x, abc.y, abc.z); +} +float min4(float4 abcd) +{ + return min4(abcd.x, abcd.y, abcd.z, abcd.w); +} +float max3(float3 abc) +{ + return max3(abc.x, abc.y, abc.z); +} +float max4(float4 abcd) +{ + return max4(abcd.x, abcd.y, abcd.z, abcd.w); +} + + // ---------- Intersection ----------- // a simple ray sphere intersection function, didn't take limited precision diff --git a/Gems/Atom/RPI/Assets/Shader/DecomposeMsImage.azsl b/Gems/Atom/RPI/Assets/Shaders/DecomposeMsImage.azsl similarity index 100% rename from Gems/Atom/RPI/Assets/Shader/DecomposeMsImage.azsl rename to Gems/Atom/RPI/Assets/Shaders/DecomposeMsImage.azsl diff --git a/Gems/Atom/RPI/Assets/Shader/DecomposeMsImage.shader b/Gems/Atom/RPI/Assets/Shaders/DecomposeMsImage.shader similarity index 100% rename from Gems/Atom/RPI/Assets/Shader/DecomposeMsImage.shader rename to Gems/Atom/RPI/Assets/Shaders/DecomposeMsImage.shader diff --git a/Gems/Atom/RPI/Assets/Shader/ImagePreview.azsl b/Gems/Atom/RPI/Assets/Shaders/ImagePreview.azsl similarity index 100% rename from Gems/Atom/RPI/Assets/Shader/ImagePreview.azsl rename to Gems/Atom/RPI/Assets/Shaders/ImagePreview.azsl diff --git a/Gems/Atom/RPI/Assets/Shader/ImagePreview.shader b/Gems/Atom/RPI/Assets/Shaders/ImagePreview.shader similarity index 100% rename from Gems/Atom/RPI/Assets/Shader/ImagePreview.shader rename to Gems/Atom/RPI/Assets/Shaders/ImagePreview.shader diff --git a/Gems/Atom/RPI/Assets/Shader/ImagePreview.shadervariantlist b/Gems/Atom/RPI/Assets/Shaders/ImagePreview.shadervariantlist similarity index 100% rename from Gems/Atom/RPI/Assets/Shader/ImagePreview.shadervariantlist rename to Gems/Atom/RPI/Assets/Shaders/ImagePreview.shadervariantlist diff --git a/Gems/Atom/RPI/Assets/Shader/SceneAndViewSrgs.azsl b/Gems/Atom/RPI/Assets/Shaders/SceneAndViewSrgs.azsl similarity index 100% rename from Gems/Atom/RPI/Assets/Shader/SceneAndViewSrgs.azsl rename to Gems/Atom/RPI/Assets/Shaders/SceneAndViewSrgs.azsl diff --git a/Gems/Atom/RPI/Assets/Shader/SceneAndViewSrgs.shader b/Gems/Atom/RPI/Assets/Shaders/SceneAndViewSrgs.shader similarity index 100% rename from Gems/Atom/RPI/Assets/Shader/SceneAndViewSrgs.shader rename to Gems/Atom/RPI/Assets/Shaders/SceneAndViewSrgs.shader diff --git a/Gems/Atom/RPI/Assets/atom_rpi_asset_files.cmake b/Gems/Atom/RPI/Assets/atom_rpi_asset_files.cmake index 9e89427a70..40478da961 100644 --- a/Gems/Atom/RPI/Assets/atom_rpi_asset_files.cmake +++ b/Gems/Atom/RPI/Assets/atom_rpi_asset_files.cmake @@ -7,10 +7,10 @@ # set(FILES - Shader/DecomposeMsImage.azsl - Shader/DecomposeMsImage.shader - Shader/ImagePreview.azsl - Shader/ImagePreview.shader + Shaders/DecomposeMsImage.azsl + Shaders/DecomposeMsImage.shader + Shaders/ImagePreview.azsl + Shaders/ImagePreview.shader ShaderLib/Atom/RPI/Math.azsli ShaderLib/Atom/RPI/TangentSpace.azsli ShaderLib/Atom/RPI/ShaderResourceGroups/DefaultDrawSrg.azsli diff --git a/Gems/Atom/RPI/Code/CMakeLists.txt b/Gems/Atom/RPI/Code/CMakeLists.txt index 521178be20..f0459a23c2 100644 --- a/Gems/Atom/RPI/Code/CMakeLists.txt +++ b/Gems/Atom/RPI/Code/CMakeLists.txt @@ -160,6 +160,7 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS) Gem::Atom_RPI.Public Gem::Atom_RHI.Public Gem::Atom_RPI.Edit + Gem::Atom_Utils.TestUtils.Static ) ly_add_googletest( NAME Gem::Atom_RPI.Tests diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Material/MaterialTypeSourceData.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Material/MaterialTypeSourceData.h index 1234b15f95..f5336807c7 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Material/MaterialTypeSourceData.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Material/MaterialTypeSourceData.h @@ -209,10 +209,6 @@ namespace AZ //! Traversal will stop once all properties have been enumerated or the callback function returns false void EnumeratePropertiesInDisplayOrder(const EnumeratePropertiesCallback& callback) const; - //! Convert the property value into the format that will be stored in the source data - //! This is primarily needed to support conversions of special types like enums and images - bool ConvertPropertyValueToSourceDataFormat(const PropertyDefinition& propertyDefinition, MaterialPropertyValue& propertyValue) const; - Outcome> CreateMaterialTypeAsset(Data::AssetId assetId, AZStd::string_view materialTypeSourceFilePath = "", bool elevateWarnings = true) const; //! Possibly renames @propertyId based on the material version update steps. diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Culling.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Culling.h index 82e9c733c8..44b1425c7a 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Culling.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Culling.h @@ -38,6 +38,8 @@ namespace AZ { class Job; + class TaskGraphActiveInterface; + class TaskGraph; namespace RHI { @@ -256,7 +258,13 @@ namespace AZ //! Must be called between BeginCulling() and EndCulling(), once for each active scene/view pair. //! Will create child jobs under the parentJob to do the processing in parallel. //! Can be called in parallel (i.e. to perform culling on multiple views at the same time). - void ProcessCullables(const Scene& scene, View& view, AZ::Job& parentJob); + void ProcessCullablesJobs(const Scene& scene, View& view, AZ::Job& parentJob); + + //! Performs render culling and lod selection for a View, then adds the visible renderpackets to that View. + //! Must be called between BeginCulling() and EndCulling(), once for each active scene/view pair. + //! Will create child task graphs that signal the TaskGraphEvent to do the processing in parallel. + //! Can be called in parallel (i.e. to perform culling on multiple views at the same time). + void ProcessCullablesTG(const Scene& scene, View& view, AZ::TaskGraph& taskGraph); //! Adds a Cullable to the underlying visibility system(s). //! Must be called at least once on initialization and whenever a Cullable's position or bounds is changed. @@ -276,17 +284,20 @@ namespace AZ return m_debugCtx; } - static const size_t WorkListCapacity = 5; - using WorkListType = AZStd::fixed_vector; - protected: size_t CountObjectsInScene(); + private: + void BeginCullingTaskGraph(const AZStd::vector& views); + void BeginCullingJobs(const AZStd::vector& views); + void ProcessCullablesCommon(const Scene& scene, View& view, AZ::Frustum& frustum, void*& maskedOcclusionCulling); + const Scene* m_parentScene = nullptr; AzFramework::IVisibilityScene* m_visScene = nullptr; CullingDebugContext m_debugCtx; AZStd::concurrency_checker m_cullDataConcurrencyCheck; OcclusionPlaneVector m_occlusionPlanes; + AZ::TaskGraphActiveInterface* m_taskGraphActive = nullptr; }; diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Material/Material.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Material/Material.h index d8a0cf0e7d..72c8616a73 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Material/Material.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Material/Material.h @@ -71,7 +71,9 @@ namespace AZ virtual ~Material(); //! Finds the material property index from the material property ID - MaterialPropertyIndex FindPropertyIndex(const Name& propertyId) const; + //! @param wasRenamed optional parameter that is set to true if @propertyId is an old name and an automatic rename was applied to find the index. + //! @param newName optional parameter that is set to the new property name, if the property was renamed. + MaterialPropertyIndex FindPropertyIndex(const Name& propertyId, bool* wasRenamed = nullptr, Name* newName = nullptr) const; //! Sets the value of a material property. The template data type must match the property's data type. //! @return true if property value was changed diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Pass/ParentPass.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Pass/ParentPass.h index 36994ec03b..6523f0a6d8 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Pass/ParentPass.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Pass/ParentPass.h @@ -68,9 +68,6 @@ namespace AZ template Ptr FindChildPass() const; - //! Searches the tree for the first pass that has same pass name (Depth-first search). Return nullptr if none found. - Ptr FindPassByNameRecursive(const Name& passName) const; - //! Gets the list of children. Useful for validating hierarchies AZStd::array_view> GetChildren() const; 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 34eaae4495..be7ac9e7a4 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 @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -59,6 +60,7 @@ namespace AZ struct PassRequest; struct PassValidationResults; class AttachmentReadback; + class ImageAttachmentCopy; using SortedPipelineViewTags = AZStd::set; using PassesByDrawList = AZStd::map; @@ -94,6 +96,8 @@ namespace AZ { AZ_RPI_PASS(Pass); + friend class ImageAttachmentPreviewPass; + public: using ChildPassIndex = RHI::Handle; @@ -139,6 +143,10 @@ namespace AZ //! Returns the number of output attachment bindings uint32_t GetOutputCount() const; + //! Returns the pass template which was used for create this pass. + //! It may return nullptr if the pass wasn't create from a template + const PassTemplate* GetPassTemplate() const; + //! Enable/disable this pass //! If the pass is disabled, it (and any children if it's a ParentPass) won't be rendered. void SetEnabled(bool enabled); @@ -365,6 +373,9 @@ namespace AZ void UpdateReadbackAttachment(FramePrepareParams params, bool beforeAddScopes); + // Setup ImageAttachmentCopy + void UpdateAttachmentCopy(FramePrepareParams params); + // --- Protected Members --- const Name PassNameThis{"This"}; @@ -462,6 +473,9 @@ namespace AZ AZStd::shared_ptr m_attachmentReadback; PassAttachmentReadbackOption m_readbackOption; + // For image attachment preview + AZStd::weak_ptr m_attachmentCopy; + private: // Return the Timestamp result of this pass virtual TimestampResult GetTimestampResultInternal() const; diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Pass/PassAttachment.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Pass/PassAttachment.h index babcf752e9..d60a5a8064 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Pass/PassAttachment.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Pass/PassAttachment.h @@ -10,6 +10,8 @@ #include #include +#include + #include namespace AZ @@ -145,7 +147,11 @@ namespace AZ //! Name of the SRG member this binds to (see PassSlot::m_shaderInputName for more details) Name m_shaderInputName = Name("AutoBind"); - + + //! Name index of the SRG constant to which, if specified, we automatically calculate + //! and bind the image dimensions (if this binding is of type image) + RHI::ShaderInputNameIndex m_shaderImageDimensionsNameIndex; + //! Whether binding is an input, output or inputOutput PassSlotType m_slotType = PassSlotType::Uninitialized; diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Pass/PassFilter.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Pass/PassFilter.h index c31f353adb..c42991725e 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Pass/PassFilter.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Pass/PassFilter.h @@ -16,95 +16,85 @@ namespace AZ { namespace RPI { - // A base class for a filter which can be used to filter passes + class Scene; + class RenderPipeline; + class PassFilter { public: - //! Whether the input pass matches with the filter - virtual bool Matches(const Pass* pass) const = 0; + static PassFilter CreateWithPassName(Name passName, const Scene* scene); + static PassFilter CreateWithPassName(Name passName, const RenderPipeline* renderPipeline); - //! Return the pass' name if a pass name is used for the filter. - //! Return nullptr if the filter doesn't have pass name used for matching - virtual const Name* GetPassName() const = 0; + //! Create a PassFilter with pass hierarchy information + //! Filter for passes which have a matching name and also with ordered parents. + //! For example, if the filter is initialized with + //! pass name: "ShadowPass1" + //! pass parents names: "MainPipeline", "Shadow" + //! Passes with these names match the filter: + //! "Root.MainPipeline.SwapChainPass.Shadow.ShadowPass1" + //! or "Root.MainPipeline.Shadow.ShadowPass1" + //! or "MainPipeline.Shadow.Group1.ShadowPass1" + //! + //! Passes with these names wont match: + //! "MainPipeline.ShadowPass1" + //! or "Shadow.MainPipeline.ShadowPass1" + static PassFilter CreateWithPassHierarchy(const AZStd::vector& passHierarchy); + static PassFilter CreateWithPassHierarchy(const AZStd::vector& passHierarchy); + static PassFilter CreateWithTemplateName(Name templateName, const Scene* scene); + static PassFilter CreateWithTemplateName(Name templateName, const RenderPipeline* renderPipeline); + template + static PassFilter CreateWithPassClass(); - //! Return this filter's info as a string - virtual AZStd::string ToString() const = 0; - }; + enum FilterOptions : uint32_t + { + Empty = 0, + PassName = AZ_BIT(0), + PassTemplateName = AZ_BIT(1), + PassClass = AZ_BIT(2), + PassHierarchy = AZ_BIT(3), + OwnerScene = AZ_BIT(4), + OwnerRenderPipeline = AZ_BIT(5) + }; - //! Filter for passes which have a matching name and also with ordered parents. - //! For example, if the filter is initialized with - //! pass name: "ShadowPass1" - //! pass parents names: "MainPipeline", "Shadow" - //! Passes with these names match the filter: - //! "Root.MainPipeline.SwapChainPass.Shadow.ShadowPass1" - //! or "Root.MainPipeline.Shadow.ShadowPass1" - //! or "MainPipeline.Shadow.Group1.ShadowPass1" - //! - //! Passes with these names wont match: - //! "MainPipeline.ShadowPass1" - //! or "Shadow.MainPipeline.ShadowPass1" - class PassHierarchyFilter - : public PassFilter - { - public: - AZ_RTTI(PassHierarchyFilter, "{478F169F-BA97-4321-AC34-EDE823997159}", PassFilter); - AZ_CLASS_ALLOCATOR(PassHierarchyFilter, SystemAllocator, 0); + void SetOwenrScene(const Scene* scene); + void SetOwenrRenderPipeline(const RenderPipeline* renderPipeline); + void SetPassName(Name passName); + void SetTemplateName(Name passTemplateName); + void SetPassClass(TypeId passClassTypeId); - //! Construct filter with only pass name. - PassHierarchyFilter(const Name& passName); + const Name& GetPassName() const; + const Name& GetPassTemplateName() const; - virtual ~PassHierarchyFilter() = default; + uint32_t GetEnabledFilterOptions() const; - //! Construct filter with pass name and its parents' names in the order of the hierarchy - //! This means k-th element is always an ancestor of the (k-1)-th element. - //! And the last element is the pass name. - PassHierarchyFilter(const AZStd::vector& passHierarchy); - PassHierarchyFilter(const AZStd::vector& passHierarchy); + //! Return true if the input pass matches the filter + bool Matches(const Pass* pass) const; - // PassFilter overrides... - bool Matches(const Pass* pass) const override; - const Name* GetPassName() const override; - AZStd::string ToString() const override; + //! Return true if the input pass matches the filter with selected filter options + //! The input filter options should be a subset of options returned by GetEnabledFilterOptions() + //! This function is used to avoid extra checks for passes which was already filtered. + //! Check PassLibrary::ForEachPass() function's implementation for more details + bool Matches(const Pass* pass, uint32_t options) const; private: - PassHierarchyFilter() = delete; + void UpdateFilterOptions(); - AZStd::vector m_parentNames; Name m_passName; + Name m_templateName; + TypeId m_passClassTypeId = TypeId::CreateNull(); + AZStd::vector m_parentNames; + const RenderPipeline* m_ownerRenderPipeline = nullptr; + const Scene* m_ownerScene = nullptr; + uint32_t m_filterOptions = 0; }; - //! Filter for passes based on their class. - template - class PassClassFilter - : public PassFilter - { - public: - AZ_RTTI(PassClassFilter, "{AF6E3AD5-433A-462A-997A-F36D8A551D02}", PassFilter); - AZ_CLASS_ALLOCATOR(PassHierarchyFilter, SystemAllocator, 0); - PassClassFilter() = default; - - // PassFilter overrides... - bool Matches(const Pass* pass) const override; - const Name* GetPassName() const override; - AZStd::string ToString() const override; - }; - - template - bool PassClassFilter::Matches(const Pass* pass) const - { - return pass->RTTI_IsTypeOf(PassClass::RTTI_Type()); - } - - template - const Name* PassClassFilter::GetPassName() const - { - return nullptr; - } - - template - AZStd::string PassClassFilter::ToString() const - { - return AZStd::string::format("PassClassFilter<%s>", PassClass::RTTI_TypeName()); + template + PassFilter PassFilter::CreateWithPassClass() + { + PassFilter filter; + filter.m_passClassTypeId = PassClass::RTTI_Type(); + filter.UpdateFilterOptions(); + return filter; } } // namespace RPI } // namespace AZ diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Pass/PassLibrary.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Pass/PassLibrary.h index 0a4b1c4399..f79da54636 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Pass/PassLibrary.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Pass/PassLibrary.h @@ -77,6 +77,16 @@ namespace AZ const AZStd::shared_ptr GetPassTemplate(const Name& name) const; const AZStd::vector& GetPassesForTemplate(const Name& templateName) const; + //! Removes a PassTemplate by name, only if the following two conditions are met: + //! 1- The template was NOT created from an Asset. This means the template will be erasable + //! only if it was created at runtime with C++. + //! 2- The are no instantiated Passes referencing such template. + //! If the template exists but both conditions are not met then the function will assert. + //! If a template with the given name doesn't exist the function does nothing. + //! This function should be used judiciously, and under rare circumstances. For example, + //! Applications that iteratively create and need to delete templates at runtime. + void RemovePassTemplate(const Name& name); + //! Removes a pass from both it's associated template (if it has one) and from the pass name mapping void RemovePassFromLibrary(Pass* pass); @@ -84,8 +94,8 @@ namespace AZ bool LoadPassTemplateMappings(const AZStd::string& templateMappingPath); bool LoadPassTemplateMappings(Data::Asset mappingAsset); - //! Returns a list of passes found in the pass name mapping using the provided pass filter - AZStd::vector FindPasses(const PassFilter& passFilter) const; + //! Visit each pass which matches the filter + void ForEachPass(const PassFilter& passFilter, AZStd::function passFunction); private: diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Pass/PassSystem.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Pass/PassSystem.h index 30fa27e64b..6412bf6166 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Pass/PassSystem.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Pass/PassSystem.h @@ -92,13 +92,14 @@ namespace AZ // PassSystemInterface library related functions... bool HasPassesForTemplateName(const Name& templateName) const override; - const AZStd::vector& GetPassesForTemplateName(const Name& templateName) const override; bool AddPassTemplate(const Name& name, const AZStd::shared_ptr& passTemplate) override; const AZStd::shared_ptr GetPassTemplate(const Name& name) const override; + void RemovePassTemplate(const Name& name) override; void RemovePassFromLibrary(Pass* pass) override; void RegisterPass(Pass* pass) override; void UnregisterPass(Pass* pass) override; - AZStd::vector FindPasses(const PassFilter& passFilter) const override; + void ForEachPass(const PassFilter& filter, AZStd::function passFunction) override; + Pass* FindFirstPass(const PassFilter& filter) override; private: // Returns the root of the pass tree hierarchy diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Pass/PassSystemInterface.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Pass/PassSystemInterface.h index 0e9386f3bb..b91a31cfd5 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Pass/PassSystemInterface.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Pass/PassSystemInterface.h @@ -75,6 +75,13 @@ namespace AZ u32 m_maxDrawItemsRenderedInAPass = 0; }; + + enum PassFilterExecutionFlow : uint8_t + { + StopVisitingPasses, + ContinueVisitingPasses, + }; + class PassSystemInterface { friend class Pass; @@ -186,20 +193,27 @@ namespace AZ //! Returns true if the pass factory contains passes created with the given template name virtual bool HasPassesForTemplateName(const Name& templateName) const = 0; - //! Get the passes created with the given template name. - virtual const AZStd::vector& GetPassesForTemplateName(const Name& templateName) const = 0; - //! Adds a PassTemplate to the library virtual bool AddPassTemplate(const Name& name, const AZStd::shared_ptr& passTemplate) = 0; //! Retrieves a PassTemplate from the library virtual const AZStd::shared_ptr GetPassTemplate(const Name& name) const = 0; + //! See remarks in PassLibrary.h for the function with this name. + virtual void RemovePassTemplate(const Name& name) = 0; + //! Removes all references to the given pass from the pass library virtual void RemovePassFromLibrary(Pass* pass) = 0; + + //! Visit the matching passes from registered passes with specified filter + //! The return value of the passFunction decides if the search continues or not + //! Note: this function will find all the passes which match the pass filter even they are for render pipelines which are not added to a scene + //! This function is fast if a pass name or a pass template name is specified. + virtual void ForEachPass(const PassFilter& filter, AZStd::function passFunction) = 0; - //! Find matching passes from registered passes with specified filter - virtual AZStd::vector FindPasses(const PassFilter& passFilter) const = 0; + //! Find the first matching pass from registered passes with specified filter + //! Note: this function SHOULD ONLY be used when you are certain you only need to handle the first pass found + virtual Pass* FindFirstPass(const PassFilter& filter) = 0; private: // These functions are only meant to be used by the Pass class diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Pass/RenderPass.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Pass/RenderPass.h index cbff297021..ec51e34897 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Pass/RenderPass.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Pass/RenderPass.h @@ -13,9 +13,7 @@ #include #include -#include #include -#include #include namespace AZ @@ -29,7 +27,6 @@ namespace AZ namespace RPI { - class ImageAttachmentCopy; class RenderPass; class Query; @@ -41,8 +38,6 @@ namespace AZ { AZ_RPI_PASS(RenderPass); - friend class ImageAttachmentPreviewPass; - using ScopeQuery = AZStd::array, static_cast(ScopeQueryType::Count)>; public: @@ -119,7 +114,7 @@ namespace AZ private: // Helper function that binds a single attachment to the pass shader resource group - void BindAttachment(const RHI::FrameGraphCompileContext& context, const PassAttachmentBinding& binding, int16_t& imageIndex, int16_t& bufferIndex); + void BindAttachment(const RHI::FrameGraphCompileContext& context, PassAttachmentBinding& binding, int16_t& imageIndex, int16_t& bufferIndex); // Helper function to get the query by the scope index and query type RHI::Ptr GetQuery(ScopeQueryType queryType); @@ -143,8 +138,6 @@ namespace AZ // Readback the results from the ScopeQueries void ReadbackScopeQueryResults(); - AZStd::weak_ptr m_attachmentCopy; - // Readback results from the Timestamp queries TimestampResult m_timestampResult; // Readback results from the PipelineStatistics queries diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Pass/Specific/ImageAttachmentPreviewPass.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Pass/Specific/ImageAttachmentPreviewPass.h index 2e2b14a699..8ee7a44b6f 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Pass/Specific/ImageAttachmentPreviewPass.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Pass/Specific/ImageAttachmentPreviewPass.h @@ -78,7 +78,7 @@ namespace AZ ~ImageAttachmentPreviewPass(); //! Preview the PassAttachment of a pass' PassAttachmentBinding - void PreviewImageAttachmentForPass(RenderPass* pass, const PassAttachment* passAttachment); + void PreviewImageAttachmentForPass(Pass* pass, const PassAttachment* passAttachment); //! Set the output color attachment for this pass void SetOutputColorAttachment(RHI::Ptr outputImageAttachment); diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/RPISystem.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/RPISystem.h index 4914e4b6fe..92370c5a82 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/RPISystem.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/RPISystem.h @@ -70,7 +70,8 @@ namespace AZ void InitializeSystemAssets() override; void RegisterScene(ScenePtr scene) override; void UnregisterScene(ScenePtr scene) override; - ScenePtr GetScene(const SceneId& sceneId) const override; + Scene* GetScene(const SceneId& sceneId) const override; + Scene* GetSceneByName(const AZ::Name& name) const override; ScenePtr GetDefaultScene() const override; RenderPipelinePtr GetRenderPipelineForWindow(AzFramework::NativeWindowHandle windowHandle) override; Data::Asset GetCommonShaderAssetForSrgs() const override; diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/RPISystemInterface.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/RPISystemInterface.h index fe81596bd7..3f30d498cf 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/RPISystemInterface.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/RPISystemInterface.h @@ -13,6 +13,7 @@ #include +#include #include namespace AZ @@ -46,11 +47,14 @@ namespace AZ //! Unregister a scene from RPISystem. The scene won't be simulated or rendered. virtual void UnregisterScene(ScenePtr scene) = 0; - // [GFX TODO] to be removed when we have scene setup in AZ Core - virtual ScenePtr GetDefaultScene() const = 0; - + //! Deprecated. Use GetSceneByName(name), GetSceneForEntityContextId(entityContextId) or Scene::GetSceneForEntityId(AZ::EntityId entityId) instead + AZ_DEPRECATED(virtual ScenePtr GetDefaultScene() const = 0;, "This method has been deprecated. Please use GetSceneByName(name), GetSceneForEntityContextId(entityContextId) or Scene::GetSceneForEntityId(AZ::EntityId entityId) instead."); + //! Get scene by using scene id. - virtual ScenePtr GetScene(const SceneId& sceneId) const = 0; + virtual Scene* GetScene(const SceneId& sceneId) const = 0; + + //! Get scene by using scene name. + virtual Scene* GetSceneByName(const AZ::Name& name) const = 0; //! Get the render pipeline created for a window virtual RenderPipelinePtr GetRenderPipelineForWindow(AzFramework::NativeWindowHandle windowHandle) = 0; diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Scene.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Scene.h index fc81368331..21e446feda 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Scene.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Scene.h @@ -80,6 +80,9 @@ namespace AZ //! Gets the RPI::Scene for a given entityContextId. //! May return nullptr if there is no RPI::Scene created for that entityContext. static Scene* GetSceneForEntityContextId(AzFramework::EntityContextId entityContextId); + + //! Gets the RPI::Scene for a given entityId. + static Scene* GetSceneForEntityId(AZ::EntityId entityId); ~Scene(); @@ -135,6 +138,8 @@ namespace AZ const SceneId& GetId() const; + AZ::Name GetName() const; + //! Set default pipeline by render pipeline ID. //! It returns true if the default render pipeline was set from the input ID. //! If the specified render pipeline doesn't exist in this scene then it won't do anything and returns false. @@ -195,8 +200,8 @@ namespace AZ // This function is called every time scene's render pipelines change. void RebuildPipelineStatesLookup(); - // Helper function to wait for end of TaskGraph - void WaitTGEvent(AZ::TaskGraphEvent& completionTGEvent, AZStd::atomic_bool* workToWaitOn = nullptr); + // Helper function to wait for end of TaskGraph and then delete the TaskGraphEvent + void WaitAndCleanTGEvent(AZStd::unique_ptr&& completionTGEvent); // Helper function for wait and clean up a completion job void WaitAndCleanCompletionJob(AZ::JobCompletion*& completionJob); @@ -225,8 +230,7 @@ namespace AZ AZStd::vector m_pipelines; // CPU simulation TaskGraphEvent to wait for completion of all the simulation tasks - AZ::TaskGraphEvent m_simulationFinishedTGEvent; - AZStd::atomic_bool m_simulationFinishedWorkActive = false; + AZStd::unique_ptr m_simulationFinishedTGEvent; // CPU simulation job completion for track all feature processors' simulation jobs AZ::JobCompletion* m_simulationCompletion = nullptr; @@ -245,6 +249,9 @@ namespace AZ // The uuid to identify this scene. SceneId m_id; + // Scene's name which is set at initialization. Can be empty + AZ::Name m_name; + bool m_activated = false; bool m_taskGraphActive = false; // update during tick, to ensure it only changes on frame boundaries @@ -286,13 +293,10 @@ namespace AZ template FeatureProcessorType* Scene::GetFeatureProcessorForEntity(AZ::EntityId entityId) { - // Find the entity context for the entity ID. - AzFramework::EntityContextId entityContextId = AzFramework::EntityContextId::CreateNull(); - AzFramework::EntityIdContextQueryBus::EventResult(entityContextId, entityId, &AzFramework::EntityIdContextQueryBus::Events::GetOwningContextId); - - if (!entityContextId.IsNull()) + RPI::Scene* renderScene = GetSceneForEntityId(entityId); + if (renderScene) { - return GetFeatureProcessorForEntityContextId(entityContextId); + return renderScene->GetFeatureProcessor(); } return nullptr; }; diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Shader/Shader.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Shader/Shader.h index e2568f3b61..bae9c4d8cc 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Shader/Shader.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Shader/Shader.h @@ -154,6 +154,8 @@ namespace AZ ConstPtr LoadPipelineLibrary() const; void SavePipelineLibrary() const; + + const ShaderVariant& GetVariantInternal(ShaderVariantStableId shaderVariantStableId); /////////////////////////////////////////////////////////////////// /// AssetBus overrides diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Shader/ShaderReloadDebugTracker.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Shader/ShaderReloadDebugTracker.h index 27c43afd12..dcbc4a5774 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Shader/ShaderReloadDebugTracker.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Shader/ShaderReloadDebugTracker.h @@ -24,6 +24,9 @@ namespace AZ class ShaderReloadDebugTracker final { public: + static void Init(); + static void Shutdown(); + static bool IsEnabled(); //! Begin a code section. Will print a "[BEGIN] " header, and all subsequent calls will be indented. @@ -34,8 +37,8 @@ namespace AZ if (IsEnabled()) { const AZStd::string sectionName = AZStd::string::format(sectionNameFormat, args...); - AZ_TracePrintf("ShaderReloadDebug", "%*s [BEGIN] %s \n", s_indent, "", sectionName.c_str()); - s_indent += IndentSpaces; + AZ_TracePrintf("ShaderReloadDebug", "%*s [BEGIN] %s \n", GetIndent(), "", sectionName.c_str()); + AddIndent(); } #endif } @@ -48,8 +51,8 @@ namespace AZ if (IsEnabled()) { const AZStd::string sectionName = AZStd::string::format(sectionNameFormat, args...); - s_indent -= IndentSpaces; - AZ_TracePrintf("ShaderReloadDebug", "%*s [_END_] %s \n", s_indent, "", sectionName.c_str()); + RemoveIndent(); + AZ_TracePrintf("ShaderReloadDebug", "%*s [_END_] %s \n", GetIndent(), "", sectionName.c_str()); } #endif } @@ -63,7 +66,7 @@ namespace AZ { const AZStd::string message = AZStd::string::format(format, args...); - AZ_TracePrintf("ShaderReloadDebug", "%*s %s \n", s_indent, "", message.c_str()); + AZ_TracePrintf("ShaderReloadDebug", "%*s %s \n", GetIndent(), "", message.c_str()); } #endif } @@ -86,9 +89,12 @@ namespace AZ }; private: - static bool s_enabled; - static int s_indent; static constexpr int IndentSpaces = 4; + + static void MakeReady(); + static void AddIndent(); + static void RemoveIndent(); + static int GetIndent(); }; } // namespace RPI diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Material/MaterialPropertyDescriptor.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Material/MaterialPropertyDescriptor.h index 085256f6d8..76bb2a6113 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Material/MaterialPropertyDescriptor.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Material/MaterialPropertyDescriptor.h @@ -106,6 +106,9 @@ namespace AZ static constexpr uint32_t InvalidEnumValue = std::numeric_limits::max(); uint32_t GetEnumValue(const AZ::Name& enumName) const; + //! Returns the name of the enum from its index. An empty name is returned for an invalid id. + const AZ::Name& GetEnumName(uint32_t enumValue) const; + //! Returns the unique name ID of this property const Name& GetName() const; diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Material/MaterialTypeAsset.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Material/MaterialTypeAsset.h index 9bc0e018d3..f194d84263 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Material/MaterialTypeAsset.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Material/MaterialTypeAsset.h @@ -129,6 +129,10 @@ namespace AZ const AZStd::vector& GetMaterialVersionUpdateList() const { return m_materialVersionUpdates; } + //! Possibly renames @propertyId based on the material version update steps. + //! @return true if the property was renamed + bool ApplyPropertyRenames(AZ::Name& propertyId) const; + private: bool PostLoadInit() override; diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Material/MaterialVersionUpdate.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Material/MaterialVersionUpdate.h index eb71ad9cb7..3ecfe7b5e0 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Material/MaterialVersionUpdate.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Material/MaterialVersionUpdate.h @@ -44,6 +44,10 @@ namespace AZ uint32_t GetVersion() const; void SetVersion(uint32_t toVersion); + + //! Possibly renames @propertyId based on the material version update steps. + //! @return true if the property was renamed + bool ApplyPropertyRenames(AZ::Name& propertyId) const; //! Apply version updates to the given material asset. //! @return true if any changes were made diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Pass/PassAttachmentReflect.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Pass/PassAttachmentReflect.h index 1131f2ff70..0de3676abe 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Pass/PassAttachmentReflect.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Pass/PassAttachmentReflect.h @@ -87,6 +87,13 @@ namespace AZ //! The keyword "NoBind" means the slot will not bind it's attachment to the SRG Name m_shaderInputName = Name("AutoBind"); + //! Name of the shader resource group constant (must be float4) to which the pass can automatically bind the following: + //! X component = image width + //! Y component = image height + //! Z component = 1 / image width + //! W component = 1 / image height + Name m_shaderImageDimensionsName; + //! This is to specify an array index if the shader input is an array. //! e.g. Texture2DMS m_color[4]; uint16_t m_shaderInputArrayIndex = 0; diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/RPISystemDescriptor.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/RPISystemDescriptor.h index 1b7b32c1e9..7f57b30326 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/RPISystemDescriptor.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/RPISystemDescriptor.h @@ -32,7 +32,7 @@ namespace AZ //! The asset cache relative path of the only common shader asset for the RPI system that is used //! as means to load the layout for scene srg and view srg. This is used to create any RPI::Scene. - AZStd::string m_commonSrgsShaderAssetPath = "shader/sceneandviewsrgs.azshader"; + AZStd::string m_commonSrgsShaderAssetPath = "shaders/sceneandviewsrgs.azshader"; ImageSystemDescriptor m_imageSystemDescriptor; GpuQuerySystemDescriptor m_gpuQuerySystemDescriptor; diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Shader/ShaderAsset.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Shader/ShaderAsset.h index 2c24d6052a..5da990eedb 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Shader/ShaderAsset.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Shader/ShaderAsset.h @@ -96,7 +96,7 @@ namespace AZ //! Return the timestamp when the shader asset was built. //! This is used to synchronize versions of the ShaderAsset and ShaderVariantTreeAsset, especially during hot-reload. - AZStd::sys_time_t GetShaderAssetBuildTimestamp() const; + AZStd::sys_time_t GetBuildTimestamp() const; //! Returns the shader option group layout. const ShaderOptionGroupLayout* GetShaderOptionGroupLayout() const; @@ -297,7 +297,7 @@ namespace AZ Name m_drawListName; //! Use to synchronize versions of the ShaderAsset and ShaderVariantTreeAsset, especially during hot-reload. - AZStd::sys_time_t m_shaderAssetBuildTimestamp = 0; + AZStd::sys_time_t m_buildTimestamp = 0; /////////////////////////////////////////////////////////////////// diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Shader/ShaderVariantKey.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Shader/ShaderVariantKey.h index 74826d9f24..e7a0672931 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Shader/ShaderVariantKey.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Shader/ShaderVariantKey.h @@ -7,6 +7,8 @@ */ #pragma once +#include +#include #include #include diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/System/SceneDescriptor.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/System/SceneDescriptor.h index 2072568d59..eb7a2b6cb7 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/System/SceneDescriptor.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/System/SceneDescriptor.h @@ -9,6 +9,7 @@ #pragma once #include +#include #include #include @@ -25,6 +26,9 @@ namespace AZ //! List of feature processors which the scene will initially enable. AZStd::vector m_featureProcessorNames; + + //! A name used as scene id. It can be used to search a registered scene via RPISystemInterface::GetScene() + AZ::Name m_nameId; }; } // namespace RPI } // namespace AZ diff --git a/Gems/Atom/RPI/Code/Source/RPI.Builders/Pass/PassBuilder.cpp b/Gems/Atom/RPI/Code/Source/RPI.Builders/Pass/PassBuilder.cpp index d5e243c687..6a0b10633e 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Builders/Pass/PassBuilder.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Builders/Pass/PassBuilder.cpp @@ -10,8 +10,8 @@ #include #include - #include +#include #include #include @@ -33,11 +33,27 @@ namespace AZ static const char* PassAssetExtension = "pass"; } + namespace PassBuilderNamespace + { + enum PassDependencies + { + Shader, + AttachmentImage, + Count + }; + + static const AZStd::tuple DependencyExtensionJobKeyTable[PassDependencies::Count] = + { + {".shader", "Shader Asset"}, + {".attimage", "Any Asset Builder"} + }; + } + void PassBuilder::RegisterBuilder() { AssetBuilderSDK::AssetBuilderDesc builder; builder.m_name = PassBuilderJobKey; - builder.m_version = 13; // antonmic: making .pass files declare dependency on shaders they reference + builder.m_version = 14; // making .pass files emit product dependencies for the shaders they reference so they are picked up by the asset bundler builder.m_busId = azrtti_typeid(); builder.m_createJobFunction = AZStd::bind(&PassBuilder::CreateJobs, this, AZStd::placeholders::_1, AZStd::placeholders::_2); builder.m_processJobFunction = AZStd::bind(&PassBuilder::ProcessJob, this, AZStd::placeholders::_1, AZStd::placeholders::_2); @@ -104,8 +120,27 @@ namespace AZ } } + bool SetJobKeyForExtension(const AZStd::string& filePath, FindPassReferenceAssetParams& params) + { + AZStd::string extension; + StringFunc::Path::GetExtension(filePath.c_str(), extension); + for (const auto& [dependencyExtension, jobKey] : PassBuilderNamespace::DependencyExtensionJobKeyTable) + { + if (extension == dependencyExtension) + { + params.jobKey = jobKey; + return true; + } + } + + AZ_Error(PassBuilderName, false, "PassBuilder found a dependency with extension '%s', but does not know the corresponding job key. Add the job key for that extension to SetJobKeyForExtension in PassBuilder.cpp", extension.c_str()); + params.jobKey = "Unknown"; + return false; + } + // Helper function to find all assetId's and object references - bool FindReferencedAssets(FindPassReferenceAssetParams& params, AssetBuilderSDK::JobDescriptor* job) + bool FindReferencedAssets( + FindPassReferenceAssetParams& params, AssetBuilderSDK::JobDescriptor* job, AZStd::vector* productDependencies) { SerializeContext::ErrorHandler errorLogger; errorLogger.Reset(); @@ -129,8 +164,8 @@ namespace AZ if (job != nullptr) // Create Job Phase { params.dependencySourceFile = path; - bool dependencyAddedSuccessfully = AddDependency(params, job); - success = dependencyAddedSuccessfully && success; + success &= SetJobKeyForExtension(path, params); + success &= AddDependency(params, job); } else // Process Job Phase { @@ -139,6 +174,9 @@ namespace AZ if (assetIdOutcome) { assetReference->m_assetId = assetIdOutcome.GetValue(); + productDependencies->push_back( + AssetBuilderSDK::ProductDependency{assetReference->m_assetId, AZ::Data::ProductDependencyInfo::CreateFlags(Data::AssetLoadBehavior::NoLoad)} + ); } else { @@ -223,9 +261,9 @@ namespace AZ params.passAssetSourceFile = request.m_sourceFile; params.passAssetUuid = passAssetUuid; params.serializeContext = serializeContext; - params.jobKey = "Shader Asset"; + params.jobKey = "Unknown"; - if (!FindReferencedAssets(params, &job)) + if (!FindReferencedAssets(params, &job, nullptr)) { return; } @@ -287,9 +325,10 @@ namespace AZ params.passAssetSourceFile = request.m_sourceFile; params.passAssetUuid = passAssetUuid; params.serializeContext = serializeContext; - params.jobKey = "Shader Asset"; + params.jobKey = "Unknown"; - if (!FindReferencedAssets(params, nullptr)) + AZStd::vector productDependencies; + if (!FindReferencedAssets(params, nullptr, &productDependencies)) { return; } @@ -313,6 +352,7 @@ namespace AZ // --- Save output product(s) to response --- AssetBuilderSDK::JobProduct jobProduct(destPath, PassAsset::RTTI_Type(), 0); + jobProduct.m_dependencies = productDependencies; jobProduct.m_dependenciesHandled = true; response.m_outputProducts.push_back(jobProduct); response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success; diff --git a/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialSourceData.cpp b/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialSourceData.cpp index 5aed6b2993..1467b017d5 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialSourceData.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialSourceData.cpp @@ -93,31 +93,28 @@ namespace AZ // Note that the only kind of property update currently supported is rename... + PropertyGroupMap newPropertyGroups; for (auto& groupPair : m_properties) { PropertyMap& propertyMap = groupPair.second; - PropertyMap newPropertyMap; - for (auto& propertyPair : propertyMap) { MaterialPropertyId propertyId{groupPair.first, propertyPair.first}; + if (materialTypeSourceData.ApplyPropertyRenames(propertyId, m_materialTypeVersion)) { - newPropertyMap[propertyId.GetPropertyName().GetStringView()] = propertyPair.second; changesWereApplied = true; } - else - { - newPropertyMap[propertyPair.first] = propertyPair.second; - } + + newPropertyGroups[propertyId.GetGroupName().GetStringView()][propertyId.GetPropertyName().GetStringView()] = propertyPair.second; } - - propertyMap = newPropertyMap; } if (changesWereApplied) { + m_properties = AZStd::move(newPropertyGroups); + AZ_Warning("MaterialSourceData", false, "This material is based on version '%u' of '%s', but the material type is now at version '%u'. " "Automatic updates are available. Consider updating the .material source file.", diff --git a/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialTypeSourceData.cpp b/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialTypeSourceData.cpp index 74250647c3..b208a49111 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialTypeSourceData.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialTypeSourceData.cpp @@ -164,16 +164,14 @@ namespace AZ const MaterialTypeSourceData::PropertyDefinition* MaterialTypeSourceData::FindProperty(AZStd::string_view groupName, AZStd::string_view propertyName, uint32_t materialTypeVersion) const { auto groupIter = m_propertyLayout.m_properties.find(groupName); - if (groupIter == m_propertyLayout.m_properties.end()) + if (groupIter != m_propertyLayout.m_properties.end()) { - return nullptr; - } - - for (const PropertyDefinition& property : groupIter->second) - { - if (property.m_name == propertyName) + for (const PropertyDefinition& property : groupIter->second) { - return &property; + if (property.m_name == propertyName) + { + return &property; + } } } @@ -185,16 +183,14 @@ namespace AZ // Do the search again with the new names groupIter = m_propertyLayout.m_properties.find(propertyId.GetGroupName().GetStringView()); - if (groupIter == m_propertyLayout.m_properties.end()) + if (groupIter != m_propertyLayout.m_properties.end()) { - return nullptr; - } - - for (const PropertyDefinition& property : groupIter->second) - { - if (property.m_name == propertyId.GetPropertyName().GetStringView()) + for (const PropertyDefinition& property : groupIter->second) { - return &property; + if (property.m_name == propertyId.GetPropertyName().GetStringView()) + { + return &property; + } } } @@ -304,48 +300,6 @@ namespace AZ } } - bool MaterialTypeSourceData::ConvertPropertyValueToSourceDataFormat(const PropertyDefinition& propertyDefinition, MaterialPropertyValue& propertyValue) const - { - if (propertyDefinition.m_dataType == AZ::RPI::MaterialPropertyDataType::Enum && propertyValue.Is()) - { - const uint32_t index = propertyValue.GetValue(); - if (index >= propertyDefinition.m_enumValues.size()) - { - AZ_Error("Material source data", false, "Invalid value for material enum property: '%s'.", propertyDefinition.m_name.c_str()); - return false; - } - - propertyValue = propertyDefinition.m_enumValues[index]; - return true; - } - - // Image asset references must be converted from asset IDs to a relative source file path - if (propertyDefinition.m_dataType == AZ::RPI::MaterialPropertyDataType::Image && propertyValue.Is>()) - { - const Data::Asset& imageAsset = propertyValue.GetValue>(); - - Data::AssetInfo imageAssetInfo; - if (imageAsset.GetId().IsValid()) - { - bool result = false; - AZStd::string rootFilePath; - const AZStd::string platformName = ""; // Empty for default - AzToolsFramework::AssetSystemRequestBus::BroadcastResult(result, &AzToolsFramework::AssetSystem::AssetSystemRequest::GetAssetInfoById, - imageAsset.GetId(), imageAsset.GetType(), platformName, imageAssetInfo, rootFilePath); - if (!result) - { - AZ_Error("Material source data", false, "Image asset could not be found for property: '%s'.", propertyDefinition.m_name.c_str()); - return false; - } - } - - propertyValue = imageAssetInfo.m_relativePath; - return true; - } - - return true; - } - Outcome> MaterialTypeSourceData::CreateMaterialTypeAsset(Data::AssetId assetId, AZStd::string_view materialTypeSourceFilePath, bool elevateWarnings) const { MaterialTypeAssetCreator materialTypeAssetCreator; diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/Culling.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/Culling.cpp index 348f0aa57a..65508b6371 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/Culling.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/Culling.cpp @@ -23,6 +23,8 @@ #include #include #include +#include +#include #include #if AZ_TRAIT_MASKED_OCCLUSION_CULLING_SUPPORTED @@ -265,89 +267,71 @@ namespace AZ return m_visScene->GetEntryCount(); } - class AddObjectsToViewJob final - : public Job + + struct WorklistData { - public: - AZ_CLASS_ALLOCATOR(AddObjectsToViewJob, ThreadPoolAllocator, 0); - - struct JobData - { - CullingDebugContext* m_debugCtx = nullptr; - const Scene* m_scene = nullptr; - View* m_view = nullptr; - Frustum m_frustum; + CullingDebugContext* m_debugCtx = nullptr; + const Scene* m_scene = nullptr; + View* m_view = nullptr; + Frustum m_frustum; #if AZ_TRAIT_MASKED_OCCLUSION_CULLING_SUPPORTED - MaskedOcclusionCulling* m_maskedOcclusionCulling = nullptr; + MaskedOcclusionCulling* m_maskedOcclusionCulling = nullptr; #endif - }; + }; - private: - const AZStd::shared_ptr m_jobData; - CullingScene::WorkListType m_worklist; + static AZStd::shared_ptr MakeWorklistData( + CullingDebugContext& debugCtx, + const Scene& scene, + View& view, + Frustum& frustum, + void* maskedOcclusionCulling) + { + AZStd::shared_ptr worklistData = AZStd::make_shared(); + worklistData->m_debugCtx = &debugCtx; + worklistData->m_scene = &scene; + worklistData->m_view = &view; + worklistData->m_frustum = frustum; +#if AZ_TRAIT_MASKED_OCCLUSION_CULLING_SUPPORTED + worklistData->m_maskedOcclusionCulling = static_cast(maskedOcclusionCulling); +#endif + return worklistData; + } + + constexpr size_t WorkListCapacity = 5; + using WorkListType = AZStd::fixed_vector; - public: - AddObjectsToViewJob(const AZStd::shared_ptr& jobData, CullingScene::WorkListType& worklist) - : Job(true, nullptr) //auto-deletes, no JobContext - , m_jobData(jobData) - , m_worklist(worklist) +#if AZ_TRAIT_MASKED_OCCLUSION_CULLING_SUPPORTED + static MaskedOcclusionCulling::CullingResult TestOcclusionCulling( + const AZStd::shared_ptr& worklistData, + AzFramework::VisibilityEntry* visibleEntry); +#endif + + static void ProcessWorklist(const AZStd::shared_ptr& worklistData, const WorkListType& worklist) + { + AZ_PROFILE_SCOPE(RPI, "AddObjectsToViewJob: Process"); + + const View::UsageFlags viewFlags = worklistData->m_view->GetUsageFlags(); + const RHI::DrawListMask drawListMask = worklistData->m_view->GetDrawListMask(); + uint32_t numDrawPackets = 0; + uint32_t numVisibleCullables = 0; + + AZ_Assert(worklist.size() > 0, "Received empty worklist in ProcessWorklist"); + + for (const AzFramework::IVisibilityScene::NodeData& nodeData : worklist) { - } - - //work function - void Process() override - { - AZ_PROFILE_SCOPE(RPI, "AddObjectsToViewJob: Process"); - - const View::UsageFlags viewFlags = m_jobData->m_view->GetUsageFlags(); - const RHI::DrawListMask drawListMask = m_jobData->m_view->GetDrawListMask(); - uint32_t numDrawPackets = 0; - uint32_t numVisibleCullables = 0; - - for (const AzFramework::IVisibilityScene::NodeData& nodeData : m_worklist) - { - //If a node is entirely contained within the frustum, then we can skip the fine grained culling. - bool nodeIsContainedInFrustum = ShapeIntersection::Contains(m_jobData->m_frustum, nodeData.m_bounds); + //If a node is entirely contained within the frustum, then we can skip the fine grained culling. + bool nodeIsContainedInFrustum = ShapeIntersection::Contains(worklistData->m_frustum, nodeData.m_bounds); #ifdef AZ_CULL_PROFILE_VERBOSE - AZ_PROFILE_SCOPE(RPI, "process node (view: %s, skip fine cull: %d", - m_view->GetName().GetCStr(), nodeIsContainedInFrustum ? 1 : 0); + AZ_PROFILE_SCOPE(RPI, "process node (view: %s, skip fine cull: %d", + m_view->GetName().GetCStr(), nodeIsContainedInFrustum ? 1 : 0); #endif - if (nodeIsContainedInFrustum || !m_jobData->m_debugCtx->m_enableFrustumCulling) + if (nodeIsContainedInFrustum || !worklistData->m_debugCtx->m_enableFrustumCulling) + { + //Add all objects within this node to the view, without any extra culling + for (AzFramework::VisibilityEntry* visibleEntry : nodeData.m_entries) { - //Add all objects within this node to the view, without any extra culling - for (AzFramework::VisibilityEntry* visibleEntry : nodeData.m_entries) - { - { - if (visibleEntry->m_typeFlags & AzFramework::VisibilityEntry::TYPE_RPI_Cullable) - { - Cullable* c = static_cast(visibleEntry->m_userData); - - if ((c->m_cullData.m_drawListMask & drawListMask).none() || - c->m_cullData.m_hideFlags & viewFlags || - c->m_cullData.m_scene != m_jobData->m_scene || //[GFX_TODO][ATOM-13796] once the IVisibilitySystem supports multiple octree scenes, remove this - c->m_isHidden) - { - continue; - } - -#if AZ_TRAIT_MASKED_OCCLUSION_CULLING_SUPPORTED - if (TestOcclusionCulling(visibleEntry) == MaskedOcclusionCulling::CullingResult::VISIBLE) -#endif - { - numDrawPackets += AddLodDataToView(c->m_cullData.m_boundingSphere.GetCenter(), c->m_lodData, *m_jobData->m_view); - ++numVisibleCullables; - c->m_isVisible = true; - } - } - } - } - } - else - { - //Do fine-grained culling before adding objects to the view - for (AzFramework::VisibilityEntry* visibleEntry : nodeData.m_entries) { if (visibleEntry->m_typeFlags & AzFramework::VisibilityEntry::TYPE_RPI_Cullable) { @@ -355,160 +339,188 @@ namespace AZ if ((c->m_cullData.m_drawListMask & drawListMask).none() || c->m_cullData.m_hideFlags & viewFlags || - c->m_cullData.m_scene != m_jobData->m_scene || //[GFX_TODO][ATOM-13796] once the IVisibilitySystem supports multiple octree scenes, remove this + c->m_cullData.m_scene != worklistData->m_scene || //[GFX_TODO][ATOM-13796] once the IVisibilitySystem supports multiple octree scenes, remove this c->m_isHidden) { continue; } - IntersectResult res = ShapeIntersection::Classify(m_jobData->m_frustum, c->m_cullData.m_boundingSphere); - if (res == IntersectResult::Exterior) - { - continue; - } - else if (res == IntersectResult::Interior || ShapeIntersection::Overlaps(m_jobData->m_frustum, c->m_cullData.m_boundingObb)) - { #if AZ_TRAIT_MASKED_OCCLUSION_CULLING_SUPPORTED - if (TestOcclusionCulling(visibleEntry) == MaskedOcclusionCulling::CullingResult::VISIBLE) + if (TestOcclusionCulling(worklistData, visibleEntry) == MaskedOcclusionCulling::CullingResult::VISIBLE) #endif - { - numDrawPackets += AddLodDataToView(c->m_cullData.m_boundingSphere.GetCenter(), c->m_lodData, *m_jobData->m_view); - ++numVisibleCullables; - c->m_isVisible = true; - } + { + numDrawPackets += AddLodDataToView(c->m_cullData.m_boundingSphere.GetCenter(), c->m_lodData, *worklistData->m_view); + ++numVisibleCullables; + c->m_isVisible = true; } } } } - - if (m_jobData->m_debugCtx->m_debugDraw && (m_jobData->m_view->GetName() == m_jobData->m_debugCtx->m_currentViewSelectionName)) + } + else + { + //Do fine-grained culling before adding objects to the view + for (AzFramework::VisibilityEntry* visibleEntry : nodeData.m_entries) { - AZ_PROFILE_SCOPE(RPI, "debug draw culling"); - - AuxGeomDrawPtr auxGeomPtr = AuxGeomFeatureProcessorInterface::GetDrawQueueForScene(m_jobData->m_scene); - if (auxGeomPtr) + if (visibleEntry->m_typeFlags & AzFramework::VisibilityEntry::TYPE_RPI_Cullable) { - //Draw the node bounds - // "Fully visible" nodes are nodes that are fully inside the frustum. "Partially visible" nodes intersect the edges of the frustum. - // Since the nodes of an octree have lots of overlapping boxes with coplanar edges, it's easier to view these separately, so - // we have a few debug booleans to toggle which ones to draw. - if (nodeIsContainedInFrustum && m_jobData->m_debugCtx->m_drawFullyVisibleNodes) + Cullable* c = static_cast(visibleEntry->m_userData); + + if ((c->m_cullData.m_drawListMask & drawListMask).none() || + c->m_cullData.m_hideFlags & viewFlags || + c->m_cullData.m_scene != worklistData->m_scene || //[GFX_TODO][ATOM-13796] once the IVisibilitySystem supports multiple octree scenes, remove this + c->m_isHidden) { - auxGeomPtr->DrawAabb(nodeData.m_bounds, Colors::Lime, RPI::AuxGeomDraw::DrawStyle::Line, RPI::AuxGeomDraw::DepthTest::Off); - } - else if (!nodeIsContainedInFrustum && m_jobData->m_debugCtx->m_drawPartiallyVisibleNodes) - { - auxGeomPtr->DrawAabb(nodeData.m_bounds, Colors::Yellow, RPI::AuxGeomDraw::DrawStyle::Line, RPI::AuxGeomDraw::DepthTest::Off); + continue; } - //Draw bounds on individual objects - if (m_jobData->m_debugCtx->m_drawBoundingBoxes || m_jobData->m_debugCtx->m_drawBoundingSpheres || m_jobData->m_debugCtx->m_drawLodRadii) + IntersectResult res = ShapeIntersection::Classify(worklistData->m_frustum, c->m_cullData.m_boundingSphere); + if (res == IntersectResult::Exterior) { - for (AzFramework::VisibilityEntry* visibleEntry : nodeData.m_entries) + continue; + } + else if (res == IntersectResult::Interior || ShapeIntersection::Overlaps(worklistData->m_frustum, c->m_cullData.m_boundingObb)) + { +#if AZ_TRAIT_MASKED_OCCLUSION_CULLING_SUPPORTED + if (TestOcclusionCulling(worklistData, visibleEntry) == MaskedOcclusionCulling::CullingResult::VISIBLE) +#endif { - if (visibleEntry->m_typeFlags & AzFramework::VisibilityEntry::TYPE_RPI_Cullable) - { - Cullable* c = static_cast(visibleEntry->m_userData); - if (m_jobData->m_debugCtx->m_drawBoundingBoxes) - { - auxGeomPtr->DrawObb(c->m_cullData.m_boundingObb, Matrix3x4::Identity(), - nodeIsContainedInFrustum ? Colors::Lime : Colors::Yellow, AuxGeomDraw::DrawStyle::Line); - } - - if (m_jobData->m_debugCtx->m_drawBoundingSpheres) - { - auxGeomPtr->DrawSphere(c->m_cullData.m_boundingSphere.GetCenter(), c->m_cullData.m_boundingSphere.GetRadius(), - Color(0.5f, 0.5f, 0.5f, 0.3f), AuxGeomDraw::DrawStyle::Shaded); - } - - if (m_jobData->m_debugCtx->m_drawLodRadii) - { - auxGeomPtr->DrawSphere(c->m_cullData.m_boundingSphere.GetCenter(), - c->m_lodData.m_lodSelectionRadius, - Color(1.0f, 0.5f, 0.0f, 0.3f), RPI::AuxGeomDraw::DrawStyle::Shaded); - } - } + numDrawPackets += AddLodDataToView(c->m_cullData.m_boundingSphere.GetCenter(), c->m_lodData, *worklistData->m_view); + ++numVisibleCullables; + c->m_isVisible = true; } } } } } - if (m_jobData->m_debugCtx->m_enableStats) + if (worklistData->m_debugCtx->m_debugDraw && (worklistData->m_view->GetName() == worklistData->m_debugCtx->m_currentViewSelectionName)) { - CullingDebugContext::CullStats& cullStats = m_jobData->m_debugCtx->GetCullStatsForView(m_jobData->m_view); + AZ_PROFILE_SCOPE(RPI, "debug draw culling"); - //no need for mutex here since these are all atomics - cullStats.m_numVisibleDrawPackets += numDrawPackets; - cullStats.m_numVisibleCullables += numVisibleCullables; - ++cullStats.m_numJobs; + AuxGeomDrawPtr auxGeomPtr = AuxGeomFeatureProcessorInterface::GetDrawQueueForScene(worklistData->m_scene); + if (auxGeomPtr) + { + //Draw the node bounds + // "Fully visible" nodes are nodes that are fully inside the frustum. "Partially visible" nodes intersect the edges of the frustum. + // Since the nodes of an octree have lots of overlapping boxes with coplanar edges, it's easier to view these separately, so + // we have a few debug booleans to toggle which ones to draw. + if (nodeIsContainedInFrustum && worklistData->m_debugCtx->m_drawFullyVisibleNodes) + { + auxGeomPtr->DrawAabb(nodeData.m_bounds, Colors::Lime, RPI::AuxGeomDraw::DrawStyle::Line, RPI::AuxGeomDraw::DepthTest::Off); + } + else if (!nodeIsContainedInFrustum && worklistData->m_debugCtx->m_drawPartiallyVisibleNodes) + { + auxGeomPtr->DrawAabb(nodeData.m_bounds, Colors::Yellow, RPI::AuxGeomDraw::DrawStyle::Line, RPI::AuxGeomDraw::DepthTest::Off); + } + + //Draw bounds on individual objects + if (worklistData->m_debugCtx->m_drawBoundingBoxes || worklistData->m_debugCtx->m_drawBoundingSpheres || worklistData->m_debugCtx->m_drawLodRadii) + { + for (AzFramework::VisibilityEntry* visibleEntry : nodeData.m_entries) + { + if (visibleEntry->m_typeFlags & AzFramework::VisibilityEntry::TYPE_RPI_Cullable) + { + Cullable* c = static_cast(visibleEntry->m_userData); + if (worklistData->m_debugCtx->m_drawBoundingBoxes) + { + auxGeomPtr->DrawObb(c->m_cullData.m_boundingObb, Matrix3x4::Identity(), + nodeIsContainedInFrustum ? Colors::Lime : Colors::Yellow, AuxGeomDraw::DrawStyle::Line); + } + + if (worklistData->m_debugCtx->m_drawBoundingSpheres) + { + auxGeomPtr->DrawSphere(c->m_cullData.m_boundingSphere.GetCenter(), c->m_cullData.m_boundingSphere.GetRadius(), + Color(0.5f, 0.5f, 0.5f, 0.3f), AuxGeomDraw::DrawStyle::Shaded); + } + + if (worklistData->m_debugCtx->m_drawLodRadii) + { + auxGeomPtr->DrawSphere(c->m_cullData.m_boundingSphere.GetCenter(), + c->m_lodData.m_lodSelectionRadius, + Color(1.0f, 0.5f, 0.0f, 0.3f), RPI::AuxGeomDraw::DrawStyle::Shaded); + } + } + } + } + } } } -#if AZ_TRAIT_MASKED_OCCLUSION_CULLING_SUPPORTED - MaskedOcclusionCulling::CullingResult TestOcclusionCulling(AzFramework::VisibilityEntry* visibleEntry) + if (worklistData->m_debugCtx->m_enableStats) { - if (!m_jobData->m_maskedOcclusionCulling) - { - return MaskedOcclusionCulling::CullingResult::VISIBLE; - } + CullingDebugContext::CullStats& cullStats = worklistData->m_debugCtx->GetCullStatsForView(worklistData->m_view); - if (visibleEntry->m_boundingVolume.Contains(m_jobData->m_view->GetCameraTransform().GetTranslation())) - { - // camera is inside bounding volume - return MaskedOcclusionCulling::CullingResult::VISIBLE; - } + //no need for mutex here since these are all atomics + cullStats.m_numVisibleDrawPackets += numDrawPackets; + cullStats.m_numVisibleCullables += numVisibleCullables; + ++cullStats.m_numJobs; + } + } - const Vector3& minBound = visibleEntry->m_boundingVolume.GetMin(); - const Vector3& maxBound = visibleEntry->m_boundingVolume.GetMax(); +#if AZ_TRAIT_MASKED_OCCLUSION_CULLING_SUPPORTED + static MaskedOcclusionCulling::CullingResult TestOcclusionCulling( + const AZStd::shared_ptr& worklistData, + AzFramework::VisibilityEntry* visibleEntry) + { + if (!worklistData->m_maskedOcclusionCulling) + { + return MaskedOcclusionCulling::CullingResult::VISIBLE; + } - // compute bounding volume corners - Vector4 corners[8]; - corners[0] = m_jobData->m_view->GetWorldToClipMatrix() * Vector4(minBound.GetX(), minBound.GetY(), minBound.GetZ(), 1.0f); - corners[1] = m_jobData->m_view->GetWorldToClipMatrix() * Vector4(minBound.GetX(), minBound.GetY(), maxBound.GetZ(), 1.0f); - corners[2] = m_jobData->m_view->GetWorldToClipMatrix() * Vector4(maxBound.GetX(), minBound.GetY(), maxBound.GetZ(), 1.0f); - corners[3] = m_jobData->m_view->GetWorldToClipMatrix() * Vector4(maxBound.GetX(), minBound.GetY(), minBound.GetZ(), 1.0f); - corners[4] = m_jobData->m_view->GetWorldToClipMatrix() * Vector4(minBound.GetX(), maxBound.GetY(), minBound.GetZ(), 1.0f); - corners[5] = m_jobData->m_view->GetWorldToClipMatrix() * Vector4(minBound.GetX(), maxBound.GetY(), maxBound.GetZ(), 1.0f); - corners[6] = m_jobData->m_view->GetWorldToClipMatrix() * Vector4(maxBound.GetX(), maxBound.GetY(), maxBound.GetZ(), 1.0f); - corners[7] = m_jobData->m_view->GetWorldToClipMatrix() * Vector4(maxBound.GetX(), maxBound.GetY(), minBound.GetZ(), 1.0f); + if (visibleEntry->m_boundingVolume.Contains(worklistData->m_view->GetCameraTransform().GetTranslation())) + { + // camera is inside bounding volume + return MaskedOcclusionCulling::CullingResult::VISIBLE; + } - // find min clip-space depth and NDC min/max - float minDepth = FLT_MAX; - float ndcMinX = FLT_MAX; - float ndcMinY = FLT_MAX; - float ndcMaxX = -FLT_MAX; - float ndcMaxY = -FLT_MAX; - for (uint32_t index = 0; index < 8; ++index) - { - minDepth = AZStd::min(minDepth, corners[index].GetW()); + const Vector3& minBound = visibleEntry->m_boundingVolume.GetMin(); + const Vector3& maxBound = visibleEntry->m_boundingVolume.GetMax(); - // convert to NDC - corners[index] /= corners[index].GetW(); - - ndcMinX = AZStd::min(ndcMinX, corners[index].GetX()); - ndcMinY = AZStd::min(ndcMinY, corners[index].GetY()); - ndcMaxX = AZStd::max(ndcMaxX, corners[index].GetX()); - ndcMaxY = AZStd::max(ndcMaxY, corners[index].GetY()); - } + // compute bounding volume corners + Vector4 corners[8]; + corners[0] = worklistData->m_view->GetWorldToClipMatrix() * Vector4(minBound.GetX(), minBound.GetY(), minBound.GetZ(), 1.0f); + corners[1] = worklistData->m_view->GetWorldToClipMatrix() * Vector4(minBound.GetX(), minBound.GetY(), maxBound.GetZ(), 1.0f); + corners[2] = worklistData->m_view->GetWorldToClipMatrix() * Vector4(maxBound.GetX(), minBound.GetY(), maxBound.GetZ(), 1.0f); + corners[3] = worklistData->m_view->GetWorldToClipMatrix() * Vector4(maxBound.GetX(), minBound.GetY(), minBound.GetZ(), 1.0f); + corners[4] = worklistData->m_view->GetWorldToClipMatrix() * Vector4(minBound.GetX(), maxBound.GetY(), minBound.GetZ(), 1.0f); + corners[5] = worklistData->m_view->GetWorldToClipMatrix() * Vector4(minBound.GetX(), maxBound.GetY(), maxBound.GetZ(), 1.0f); + corners[6] = worklistData->m_view->GetWorldToClipMatrix() * Vector4(maxBound.GetX(), maxBound.GetY(), maxBound.GetZ(), 1.0f); + corners[7] = worklistData->m_view->GetWorldToClipMatrix() * Vector4(maxBound.GetX(), maxBound.GetY(), minBound.GetZ(), 1.0f); + // find min clip-space depth and NDC min/max + float minDepth = FLT_MAX; + float ndcMinX = FLT_MAX; + float ndcMinY = FLT_MAX; + float ndcMaxX = -FLT_MAX; + float ndcMaxY = -FLT_MAX; + for (uint32_t index = 0; index < 8; ++index) + { + minDepth = AZStd::min(minDepth, corners[index].GetW()); if (minDepth < 0.00000001f) { - return MaskedOcclusionCulling::VISIBLE; + return MaskedOcclusionCulling::CullingResult::VISIBLE; } - // test against the occlusion buffer, which contains only the manually placed occlusion planes - return m_jobData->m_maskedOcclusionCulling->TestRect(ndcMinX, ndcMinY, ndcMaxX, ndcMaxY, minDepth); + + // convert to NDC + corners[index] /= corners[index].GetW(); + + ndcMinX = AZStd::min(ndcMinX, corners[index].GetX()); + ndcMinY = AZStd::min(ndcMinY, corners[index].GetY()); + ndcMaxX = AZStd::max(ndcMaxX, corners[index].GetX()); + ndcMaxY = AZStd::max(ndcMaxY, corners[index].GetY()); } + + // test against the occlusion buffer, which contains only the manually placed occlusion planes + return worklistData->m_maskedOcclusionCulling->TestRect(ndcMinX, ndcMinY, ndcMaxX, ndcMaxY, minDepth); + } #endif - }; - void CullingScene::ProcessCullables(const Scene& scene, View& view, AZ::Job& parentJob) + void CullingScene::ProcessCullablesCommon(const Scene& scene, View& view, AZ::Frustum& frustum, void*& maskedOcclusionCulling) { - AZ_PROFILE_SCOPE(RPI, "CullingScene::ProcessCullables() - %s", view.GetName().GetCStr()); + AZ_PROFILE_SCOPE(RPI, "CullingScene::ProcessCullablesCommon() - %s", view.GetName().GetCStr()); - const Matrix4x4& worldToClip = view.GetWorldToClipMatrix(); - Frustum frustum = Frustum::CreateFromMatrixColumnMajor(worldToClip); if (m_debugCtx.m_freezeFrustums) { AZStd::lock_guard lock(m_debugCtx.m_frozenFrustumsMutex); @@ -536,7 +548,7 @@ namespace AZ #if AZ_TRAIT_MASKED_OCCLUSION_CULLING_SUPPORTED // setup occlusion culling, if necessary - MaskedOcclusionCulling* maskedOcclusionCulling = m_occlusionPlanes.empty() ? nullptr : view.GetMaskedOcclusionCulling(); + maskedOcclusionCulling = m_occlusionPlanes.empty() ? nullptr : view.GetMaskedOcclusionCulling(); if (maskedOcclusionCulling) { // frustum cull occlusion planes @@ -578,23 +590,27 @@ namespace AZ static uint32_t indices[6] = { 0, 1, 2, 2, 3, 0 }; // render into the occlusion buffer, specifying BACKFACE_NONE so it functions as a double-sided occluder - maskedOcclusionCulling->RenderTriangles((float*)verts, indices, 2, nullptr, MaskedOcclusionCulling::BACKFACE_NONE); + static_cast(maskedOcclusionCulling)->RenderTriangles(verts, indices, 2, nullptr, MaskedOcclusionCulling::BACKFACE_NONE); } } #endif + } + + void CullingScene::ProcessCullablesJobs(const Scene& scene, View& view, AZ::Job& parentJob) + { + AZ_PROFILE_SCOPE(RPI, "CullingScene::ProcessCullablesJobs() - %s", view.GetName().GetCStr()); + + const Matrix4x4& worldToClip = view.GetWorldToClipMatrix(); + AZ::Frustum frustum = Frustum::CreateFromMatrixColumnMajor(worldToClip); + + void* maskedOcclusionCulling = nullptr; + ProcessCullablesCommon(scene, view, frustum, maskedOcclusionCulling); WorkListType worklist; - AZStd::shared_ptr jobData = AZStd::make_shared(); - jobData->m_debugCtx = &m_debugCtx; - jobData->m_scene = &scene; - jobData->m_view = &view; - jobData->m_frustum = frustum; -#if AZ_TRAIT_MASKED_OCCLUSION_CULLING_SUPPORTED - jobData->m_maskedOcclusionCulling = maskedOcclusionCulling; -#endif + AZStd::shared_ptr worklistData = MakeWorklistData(m_debugCtx, scene, view, frustum, maskedOcclusionCulling); - auto nodeVisitorLambda = [jobData, &parentJob, &worklist](const AzFramework::IVisibilityScene::NodeData& nodeData) -> void + auto nodeVisitorLambda = [worklistData, &parentJob, &worklist](const AzFramework::IVisibilityScene::NodeData& nodeData) -> void { AZ_PROFILE_SCOPE(RPI, "nodeVisitorLambda()"); AZ_Assert(nodeData.m_entries.size() > 0, "should not get called with 0 entries"); @@ -606,8 +622,13 @@ namespace AZ if (worklist.size() == worklist.capacity()) { + // capture worklistData & worklist by value + auto processWorklist = [worklistData, worklist]() + { + ProcessWorklist(worklistData, worklist); + }; //Kick off a job to process the (full) worklist - AddObjectsToViewJob* job = aznew AddObjectsToViewJob(jobData, worklist); //pool allocated (cheap), auto-deletes when job finishes + AZ::Job* job = AZ::CreateJobFunction(processWorklist, true); worklist.clear(); parentJob.SetContinuation(job); job->Start(); @@ -616,7 +637,7 @@ namespace AZ if (m_debugCtx.m_enableFrustumCulling) { - m_visScene->Enumerate(frustum, nodeVisitorLambda); + m_visScene->Enumerate(frustum, nodeVisitorLambda); } else { @@ -625,21 +646,76 @@ namespace AZ if (worklist.size() > 0) { - AZStd::shared_ptr remainingJobData = AZStd::make_shared(); - remainingJobData->m_debugCtx = &m_debugCtx; - remainingJobData->m_scene = &scene; - remainingJobData->m_view = &view; - remainingJobData->m_frustum = frustum; -#if AZ_TRAIT_MASKED_OCCLUSION_CULLING_SUPPORTED - remainingJobData->m_maskedOcclusionCulling = maskedOcclusionCulling; -#endif - //Kick off a job to process any remaining workitems - AddObjectsToViewJob* job = aznew AddObjectsToViewJob(remainingJobData, worklist); //pool allocated (cheap), auto-deletes when job finishes + // capture worklistData & worklist by value + auto processWorklist = [worklistData, worklist]() + { + ProcessWorklist(worklistData, worklist); + }; + //Kick off a job to process the (full) worklist + AZ::Job* job = AZ::CreateJobFunction(processWorklist, true); parentJob.SetContinuation(job); job->Start(); } } + void CullingScene::ProcessCullablesTG(const Scene& scene, View& view, AZ::TaskGraph& taskGraph) + { + AZ_PROFILE_SCOPE(RPI, "CullingScene::ProcessCullablesTG() - %s", view.GetName().GetCStr()); + + const Matrix4x4& worldToClip = view.GetWorldToClipMatrix(); + AZ::Frustum frustum = Frustum::CreateFromMatrixColumnMajor(worldToClip); + + void* maskedOcclusionCulling = nullptr; + ProcessCullablesCommon(scene, view, frustum, maskedOcclusionCulling); + + AZStd::unique_ptr worklist = AZStd::make_unique(); + + AZStd::shared_ptr worklistData = MakeWorklistData(m_debugCtx, scene, view, frustum, maskedOcclusionCulling); + static const AZ::TaskDescriptor descriptor{ "AZ::RPI::ProcessWorklist", "Graphics" }; + + auto nodeVisitorLambda = [worklistData, &taskGraph, &worklist](const AzFramework::IVisibilityScene::NodeData& nodeData) -> void + { + AZ_PROFILE_SCOPE(RPI, "nodeVisitorLambda()"); + AZ_Assert(nodeData.m_entries.size() > 0, "should not get called with 0 entries"); + AZ_Assert(worklist->size() < worklist->capacity(), "we should always have room to push a node on the queue"); + + //Queue up a small list of work items (NodeData*) which will be pushed to a worker task once the queue is full. + //This reduces the number of tasks in flight, reducing task-system overhead. + worklist->emplace_back(AZStd::move(nodeData)); + + if (worklist->size() == worklist->capacity()) + { + //Task takes ownership of the worklist unique ptr + taskGraph.AddTask( descriptor, [worklistData, worklist = AZStd::move(worklist)]() + { + ProcessWorklist(worklistData, *worklist.get()); + // allow worklist to go out of scope and be deleted + }); + worklist = AZStd::make_unique(); + } + }; + + if (m_debugCtx.m_enableFrustumCulling) + { + m_visScene->Enumerate(frustum, nodeVisitorLambda); + } + else + { + m_visScene->EnumerateNoCull(nodeVisitorLambda); + } + + if (worklist->size() > 0) + { + //Task takes ownership of the worklist unique ptr + taskGraph.AddTask( descriptor, [worklistData, worklist = AZStd::move(worklist)]() + { + ProcessWorklist(worklistData, *worklist.get()); + // allow worklist to go out of scope and be deleted + }); + } + } + + uint32_t AddLodDataToView(const Vector3& pos, const Cullable::LodData& lodData, RPI::View& view) { #ifdef AZ_CULL_PROFILE_DETAILED @@ -699,11 +775,11 @@ namespace AZ m_parentScene = parentScene; AZ_Assert(m_visScene == nullptr, "IVisibilityScene already created for this RPI::Scene"); - char sceneIdBuf[40] = ""; - m_parentScene->GetId().ToString(sceneIdBuf); - AZ::Name visSceneName(AZStd::string::format("RenderCullScene[%s]", sceneIdBuf)); + AZ::Name visSceneName(AZStd::string::format("RenderCullScene[%s]", m_parentScene->GetName().GetCStr())); m_visScene = AZ::Interface::Get()->CreateVisibilityScene(visSceneName); + m_taskGraphActive = AZ::Interface::Get(); + #ifdef AZ_CULL_DEBUG_ENABLED AZ_Assert(CountObjectsInScene() == 0, "The culling system should start with 0 entries in this scene."); #endif @@ -721,13 +797,27 @@ namespace AZ } } - void CullingScene::BeginCulling(const AZStd::vector& views) + void CullingScene::BeginCullingTaskGraph(const AZStd::vector& views) { - AZ_PROFILE_SCOPE(RPI, "CullingScene: BeginCulling"); - m_cullDataConcurrencyCheck.soft_lock(); + AZ::TaskGraph taskGraph; + AZ::TaskDescriptor beginCullingDescriptor{"RPI_CullingScene_BeginCullingView", "Graphics"}; + for (auto& view : views) + { + taskGraph.AddTask( + beginCullingDescriptor, + [&view]() + { + view->BeginCulling(); + }); + } - m_debugCtx.ResetCullStats(); - m_debugCtx.m_numCullablesInScene = GetNumCullables(); + AZ::TaskGraphEvent waitForCompletion; + taskGraph.Submit(&waitForCompletion); + waitForCompletion.Wait(); + } + + void CullingScene::BeginCullingJobs(const AZStd::vector& views) + { AZ::JobCompletion beginCullingCompletion; for (auto& view : views) @@ -743,6 +833,26 @@ namespace AZ } beginCullingCompletion.StartAndWaitForCompletion(); + } + + void CullingScene::BeginCulling(const AZStd::vector& views) + { + AZ_PROFILE_SCOPE(RPI, "CullingScene: BeginCulling"); + m_cullDataConcurrencyCheck.soft_lock(); + + m_debugCtx.ResetCullStats(); + m_debugCtx.m_numCullablesInScene = GetNumCullables(); + + m_taskGraphActive = AZ::Interface::Get(); + + if (m_taskGraphActive && m_taskGraphActive->IsTaskGraphActive()) + { + BeginCullingTaskGraph(views); + } + else + { + BeginCullingJobs(views); + } AuxGeomDrawPtr auxGeom; if (m_debugCtx.m_debugDraw) diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/Material/Material.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/Material/Material.cpp index c87646e17d..050ae47749 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/Material/Material.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/Material/Material.cpp @@ -386,9 +386,39 @@ namespace AZ return m_currentChangeId; } - MaterialPropertyIndex Material::FindPropertyIndex(const Name& propertyId) const + MaterialPropertyIndex Material::FindPropertyIndex(const Name& propertyId, bool* wasRenamed, Name* newName) const { - return m_layout->FindPropertyIndex(propertyId); + if (wasRenamed) + { + *wasRenamed = false; + } + + MaterialPropertyIndex index = m_layout->FindPropertyIndex(propertyId); + if (!index.IsValid()) + { + Name renamedId = propertyId; + + if (m_materialAsset->GetMaterialTypeAsset()->ApplyPropertyRenames(renamedId)) + { + index = m_layout->FindPropertyIndex(renamedId); + + if (wasRenamed) + { + *wasRenamed = true; + } + + if (newName) + { + *newName = renamedId; + } + + AZ_Warning("Material", false, + "Material property '%s' has been renamed to '%s'. Consider updating the corresponding source data.", + propertyId.GetCStr(), + renamedId.GetCStr()); + } + } + return index; } template diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/AttachmentReadback.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/AttachmentReadback.cpp index c19ae565d0..890e87a7a5 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/AttachmentReadback.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/AttachmentReadback.cpp @@ -121,7 +121,7 @@ namespace AZ m_fence->Init(*device, RHI::FenceState::Reset); // Load shader and srg - const char* ShaderPath = "shader/decomposemsimage.azshader"; + const char* ShaderPath = "shaders/decomposemsimage.azshader"; m_decomposeShader = LoadCriticalShader(ShaderPath); if (m_decomposeShader == nullptr) diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/FullscreenTrianglePass.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/FullscreenTrianglePass.cpp index 40dce7d138..0bd32344d6 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/FullscreenTrianglePass.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/FullscreenTrianglePass.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -46,16 +47,19 @@ namespace AZ void FullscreenTrianglePass::OnShaderReinitialized(const Shader&) { + ShaderReloadDebugTracker::ScopedSection reloadSection("{%p}->FullscreenTrianglePass::OnShaderReinitialized", this); LoadShader(); } void FullscreenTrianglePass::OnShaderAssetReinitialized(const Data::Asset&) { + ShaderReloadDebugTracker::ScopedSection reloadSection("{%p}->FullscreenTrianglePass::OnShaderAssetReinitialized", this); LoadShader(); } void FullscreenTrianglePass::OnShaderVariantReinitialized(const ShaderVariant&) { + ShaderReloadDebugTracker::ScopedSection reloadSection("{%p}->FullscreenTrianglePass::OnShaderVariantReinitialized", this); LoadShader(); } @@ -129,6 +133,8 @@ namespace AZ void FullscreenTrianglePass::InitializeInternal() { RenderPass::InitializeInternal(); + + ShaderReloadDebugTracker::ScopedSection reloadSection("{%p}->FullscreenTrianglePass::InitializeInternal", this); // This draw item purposefully does not reference any geometry buffers. // Instead it's expected that the extended class uses a vertex shader diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/ParentPass.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/ParentPass.cpp index 36c28877ea..dccf5dbc2e 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/ParentPass.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/ParentPass.cpp @@ -149,29 +149,6 @@ namespace AZ return index.IsValid() ? m_children[index.GetIndex()] : Ptr(nullptr); } - Ptr ParentPass::FindPassByNameRecursive(const Name& passName) const - { - for (const Ptr& child : m_children) - { - if (child->GetName() == passName) - { - return child.get(); - } - - ParentPass* asParent = child->AsParent(); - if (asParent) - { - auto pass = asParent->FindPassByNameRecursive(passName); - if (pass) - { - return pass; - } - } - } - - return nullptr; - } - const Pass* ParentPass::FindPass(RHI::DrawListTag drawListTag) const { if (HasDrawListTag() && GetDrawListTag() == drawListTag) 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 a8d94e9a91..d04a35a10b 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/Pass.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/Pass.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include @@ -238,6 +239,11 @@ namespace AZ return m_attachmentBindings[bindingIndex]; } + const PassTemplate* Pass::GetPassTemplate() const + { + return m_template.get(); + } + void Pass::AddAttachmentBinding(PassAttachmentBinding attachmentBinding) { // Add the index of the binding to the input, output or input/output list based on the slot type @@ -1210,6 +1216,12 @@ namespace AZ m_queueState = PassQueueState::NoQueue; InitializeInternal(); + + // Need to recreate the dest attachment because the source attachment might be changed + if (!m_attachmentCopy.expired()) + { + m_attachmentCopy.lock()->InvalidateDestImage(); + } m_state = PassState::Initialized; } @@ -1296,6 +1308,9 @@ namespace AZ // readback attachment with output state UpdateReadbackAttachment(params, false); + // update attachment copy for preview + UpdateAttachmentCopy(params); + UpdateConnectedOutputBindings(); } @@ -1484,6 +1499,14 @@ namespace AZ } } + void Pass::UpdateAttachmentCopy(FramePrepareParams params) + { + if (!m_attachmentCopy.expired()) + { + m_attachmentCopy.lock()->FrameBegin(params); + } + } + bool Pass::IsTimestampQueryEnabled() const { return m_flags.m_timestampQueryEnabled; diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/PassAttachment.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/PassAttachment.cpp index e0831381e7..3b4f10a968 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/PassAttachment.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/PassAttachment.cpp @@ -187,6 +187,7 @@ namespace AZ { m_name = slot.m_name; m_shaderInputName = slot.m_shaderInputName; + m_shaderImageDimensionsNameIndex = slot.m_shaderImageDimensionsName; m_shaderInputArrayIndex = slot.m_shaderInputArrayIndex; m_slotType = slot.m_slotType; m_scopeAttachmentUsage = slot.m_scopeAttachmentUsage; diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/PassFilter.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/PassFilter.cpp index 7bd1abc0a7..d9e458c615 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/PassFilter.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/PassFilter.cpp @@ -8,101 +8,264 @@ #include #include +#include namespace AZ { namespace RPI { - PassHierarchyFilter::PassHierarchyFilter(const Name& passName) + PassFilter PassFilter::CreateWithPassName(Name passName, const Scene* scene) + { + PassFilter filter; + filter.m_passName = passName; + filter.m_ownerScene = scene; + filter.UpdateFilterOptions(); + return filter; + } + + PassFilter PassFilter::CreateWithPassName(Name passName, const RenderPipeline* renderPipeline) + { + PassFilter filter; + filter.m_passName = passName; + filter.m_ownerRenderPipeline = renderPipeline; + filter.UpdateFilterOptions(); + return filter; + } + + PassFilter PassFilter::CreateWithTemplateName(Name templateName, const Scene* scene) + { + PassFilter filter; + filter.m_templateName = templateName; + filter.m_ownerScene = scene; + filter.UpdateFilterOptions(); + return filter; + } + + PassFilter PassFilter::CreateWithTemplateName(Name templateName, const RenderPipeline* renderPipeline) + { + PassFilter filter; + filter.m_templateName = templateName; + filter.m_ownerRenderPipeline = renderPipeline; + filter.UpdateFilterOptions(); + return filter; + } + + PassFilter PassFilter::CreateWithPassHierarchy(const AZStd::vector& passHierarchy) + { + PassFilter filter; + if (passHierarchy.size() == 0) + { + AZ_Assert(false, "passHierarchy should have at least one element"); + return filter; + } + + filter.m_passName = passHierarchy.back(); + + filter.m_parentNames.resize(passHierarchy.size() - 1); + for (uint32_t index = 0; index < filter.m_parentNames.size(); index++) + { + filter.m_parentNames[index] = passHierarchy[index]; + } + filter.UpdateFilterOptions(); + return filter; + } + + PassFilter PassFilter::CreateWithPassHierarchy(const AZStd::vector& passHierarchy) + { + PassFilter filter; + if (passHierarchy.size() == 0) + { + AZ_Assert(false, "passHierarchy should have at least one element"); + return filter; + } + + filter.m_passName = Name(passHierarchy.back()); + + filter.m_parentNames.resize(passHierarchy.size() - 1); + for (uint32_t index = 0; index < filter.m_parentNames.size(); index++) + { + filter.m_parentNames[index] = Name(passHierarchy[index]); + } + filter.UpdateFilterOptions(); + return filter; + } + + void PassFilter::SetOwenrScene(const Scene* scene) + { + m_ownerScene = scene; + UpdateFilterOptions(); + } + + void PassFilter::SetOwenrRenderPipeline(const RenderPipeline* renderPipeline) + { + m_ownerRenderPipeline = renderPipeline; + UpdateFilterOptions(); + } + + void PassFilter::SetPassName(Name passName) { m_passName = passName; + UpdateFilterOptions(); } - PassHierarchyFilter::PassHierarchyFilter(const AZStd::vector& passHierarchy) + void PassFilter::SetTemplateName(Name passTemplateName) { - if (passHierarchy.size() == 0) - { - AZ_Assert(false, "passHierarchy should have at least one element"); - return; - } - - m_passName = Name(passHierarchy.back()); - - m_parentNames.resize(passHierarchy.size() - 1); - for (uint32_t index = 0; index < m_parentNames.size(); index++) - { - m_parentNames[index] = Name(passHierarchy[index]); - } + m_templateName = passTemplateName; + UpdateFilterOptions(); } - PassHierarchyFilter::PassHierarchyFilter(const AZStd::vector& passHierarchy) + void PassFilter::SetPassClass(TypeId passClassTypeId) { - if (passHierarchy.size() == 0) - { - AZ_Assert(false, "passHierarchy should have at least one element"); - return; - } - - m_passName = passHierarchy.back(); - - m_parentNames.resize(passHierarchy.size() - 1); - for (uint32_t index = 0; index < m_parentNames.size(); index++) - { - m_parentNames[index] = passHierarchy[index]; - } + m_passClassTypeId = passClassTypeId; + UpdateFilterOptions(); } - bool PassHierarchyFilter::Matches(const Pass* pass) const + const Name& PassFilter::GetPassName() const { - if (pass->GetName() != m_passName) + return m_passName; + } + + const Name& PassFilter::GetPassTemplateName() const + { + return m_templateName; + } + + uint32_t PassFilter::GetEnabledFilterOptions() const + { + return m_filterOptions; + } + + bool PassFilter::Matches(const Pass* pass) const + { + return Matches(pass, m_filterOptions); + } + + bool PassFilter::Matches(const Pass* pass, uint32_t options) const + { + AZ_Assert( (options&m_filterOptions) == options, "options should be a subset of m_filterOptions"); + + // return false if the pass doesn't have a pass template or the template's name is not matching + if (options & FilterOptions::PassTemplateName && (!pass->GetPassTemplate() || pass->GetPassTemplate()->m_name != m_templateName)) { return false; } - ParentPass* parent = pass->GetParent(); - - // search from the back of the array with the most close parent - for (int32_t index = static_cast(m_parentNames.size() - 1); index >= 0; index--) + if ((options & FilterOptions::PassName) && pass->GetName() != m_passName) { - const Name& parentName = m_parentNames[index]; - while (parent) - { - if (parent->GetName() == parentName) - { - break; - } - parent = parent->GetParent(); - } + return false; + } - // if parent is nullptr the it didn't find a parent has matching current parentName - if (!parent) + if ((options & FilterOptions::PassClass) && pass->RTTI_GetType() != m_passClassTypeId) + { + return false; + } + + if ((options & FilterOptions::OwnerRenderPipeline) && m_ownerRenderPipeline != pass->GetRenderPipeline()) + { + return false; + } + + // If the owner render pipeline was checked, the owner scene check can be skipped + if (options & FilterOptions::OwnerScene) + { + if (pass->GetRenderPipeline()) { + // return false if the owner scene doesn't match + if (m_ownerScene != pass->GetRenderPipeline()->GetScene()) + { + return false; + } + } + else + { + // return false if the pass doesn't have an owner scene return false; } + } - // move to next parent - parent = parent->GetParent(); + if ((options & FilterOptions::PassHierarchy)) + { + // Filter for passes which have a matching name and also with ordered parents. + // For example, if the filter is initialized with + // pass name: "ShadowPass1" + // pass parents names: "MainPipeline", "Shadow" + // Passes with these names match the filter: + // "Root.MainPipeline.SwapChainPass.Shadow.ShadowPass1" + // or "Root.MainPipeline.Shadow.ShadowPass1" + // or "MainPipeline.Shadow.Group1.ShadowPass1" + // + // Passes with these names wont match: + // "MainPipeline.ShadowPass1" + // or "Shadow.MainPipeline.ShadowPass1" + + ParentPass* parent = pass->GetParent(); + + // search from the back of the array with the most close parent + for (int32_t index = static_cast(m_parentNames.size() - 1); index >= 0; index--) + { + const Name& parentName = m_parentNames[index]; + while (parent) + { + if (parent->GetName() == parentName) + { + break; + } + parent = parent->GetParent(); + } + + // if parent is nullptr the it didn't find a parent has matching current parentName + if (!parent) + { + return false; + } + + // move to next parent + parent = parent->GetParent(); + } } return true; } - const Name* PassHierarchyFilter::GetPassName() const + void PassFilter::UpdateFilterOptions() { - return &m_passName; - } - - AZStd::string PassHierarchyFilter::ToString() const - { - AZStd::string result = "PassHierarchyFilter"; - for (uint32_t index = 0; index < m_parentNames.size(); index++) + m_filterOptions = FilterOptions::Empty; + if (!m_passName.IsEmpty()) { - result += AZStd::string::format(" [%s]", m_parentNames[index].GetCStr()); + m_filterOptions |= FilterOptions::PassName; + } + if (!m_templateName.IsEmpty()) + { + m_filterOptions |= FilterOptions::PassTemplateName; + } + if (m_parentNames.size() > 0) + { + m_filterOptions |= FilterOptions::PassHierarchy; + } + if (m_ownerRenderPipeline) + { + m_filterOptions |= FilterOptions::OwnerRenderPipeline; + } + if (m_ownerScene) + { + // If the OwnerRenderPipeline exists, we shouldn't need to filter owner scene + // Validate the owner render pipeline belongs to the owner scene + if (m_filterOptions & FilterOptions::OwnerRenderPipeline) + { + if (m_ownerRenderPipeline->GetScene() != m_ownerScene) + { + AZ_Warning("RPI", false, "The owner scene filter doesn't match owner render pipeline. It will be skipped."); + } + } + else + { + m_filterOptions |= FilterOptions::OwnerScene; + } + } + if (!m_passClassTypeId.IsNull()) + { + m_filterOptions |= FilterOptions::PassClass; } - - result += AZStd::string::format(" [%s]", m_passName.GetCStr()); - return result; } - } // namespace RPI } // namespace AZ diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/PassLibrary.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/PassLibrary.cpp index c43edafd6b..13b2fa391f 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/PassLibrary.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/PassLibrary.cpp @@ -85,47 +85,80 @@ namespace AZ return (GetPassesForTemplate(templateName).size() > 0); } - AZStd::vector PassLibrary::FindPasses(const PassFilter& passFilter) const + void PassLibrary::ForEachPass(const PassFilter& passFilter, AZStd::function passFunction) { - const Name* passName = passFilter.GetPassName(); + uint32_t filterOptions = passFilter.GetEnabledFilterOptions(); - AZStd::vector result; - - if (passName) + // A lambda function which visits each pass in a pass list, if the pass matches the pass filter, then call the pass function + auto visitList = [passFilter, passFunction](const AZStd::vector& passList, uint32_t options) -> PassFilterExecutionFlow { - // If the pass' name is known, find passes with matching names first - const auto constItr = m_passNameMapping.find(*passName); - if (constItr == m_passNameMapping.end()) + if (passList.size() == 0) { - return result; + return PassFilterExecutionFlow::ContinueVisitingPasses; } - - const AZStd::vector& passes = constItr->second; - - for (Pass* pass : passes) + // if there is not other filter options enabled, skip the filter and call pass functions directly + if (options == PassFilter::FilterOptions::Empty) { - if (passFilter.Matches(pass)) + for (Pass* pass : passList) { - result.push_back(pass); - } - } - } - else - { - // If the filter doesn't know matching pass' name, need to go through all registered passes - for (auto& namePasses : m_passNameMapping) - { - for (Pass* pass : namePasses.second) - { - if (passFilter.Matches(pass)) + // If user want to skip processing, return directly. + if (passFunction(pass) == PassFilterExecutionFlow::StopVisitingPasses) { - result.push_back(pass); + return PassFilterExecutionFlow::StopVisitingPasses; + } + } + return PassFilterExecutionFlow::ContinueVisitingPasses; + } + + // Check with the pass filter and call pass functions + for (Pass* pass : passList) + { + if (passFilter.Matches(pass, options)) + { + if (passFunction(pass) == PassFilterExecutionFlow::StopVisitingPasses) + { + return PassFilterExecutionFlow::StopVisitingPasses; } } } + return PassFilterExecutionFlow::ContinueVisitingPasses; + }; + + // Check pass template name first + if (filterOptions & PassFilter::FilterOptions::PassTemplateName) + { + auto entry = GetEntry(passFilter.GetPassTemplateName()); + if (!entry) + { + return; + } + + filterOptions &= ~(PassFilter::FilterOptions::PassTemplateName); + visitList(entry->m_passes, filterOptions); + return; + } + else if (filterOptions & PassFilter::FilterOptions::PassName) + { + const auto constItr = m_passNameMapping.find(passFilter.GetPassName()); + if (constItr == m_passNameMapping.end()) + { + return; + } + + filterOptions &= ~(PassFilter::FilterOptions::PassName); + visitList(constItr->second, filterOptions); + return; } - return result; + // check againest every passes. This might be slow + AZ_PROFILE_SCOPE(RPI, "PassLibrary::ForEachPass"); + for (auto& namePasses : m_passNameMapping) + { + if (visitList(namePasses.second, filterOptions) == PassFilterExecutionFlow::StopVisitingPasses) + { + return; + } + } } // Add Functions... @@ -203,6 +236,19 @@ namespace AZ return true; } + void PassLibrary::RemovePassTemplate(const Name& name) + { + auto itr = m_templateEntries.find(name); + if (itr != m_templateEntries.end()) + { + AZ_Assert(itr->second.m_passes.empty(), "Can not delete PassTemplate '%s' because there are %zu Passes referencing it", + name.GetCStr(), itr->second.m_passes.size()); + AZ_Assert(!itr->second.m_mappingAssetId.IsValid(), "Can not delete PassTemplate '%s' because it was created from an asset", + name.GetCStr()); + m_templateEntries.erase(itr); + } + } + void PassLibrary::RemovePassFromLibrary(Pass* pass) { if (m_isShuttingDown) @@ -419,3 +465,4 @@ namespace AZ } // namespace RPI } // namespace AZ + diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/PassSystem.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/PassSystem.cpp index f4f51f97b7..39d0e879e1 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/PassSystem.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/PassSystem.cpp @@ -456,11 +456,6 @@ namespace AZ return m_passLibrary.HasPassesForTemplate(templateName); } - const AZStd::vector& PassSystem::GetPassesForTemplateName(const Name& templateName) const - { - return m_passLibrary.GetPassesForTemplate(templateName); - } - bool PassSystem::AddPassTemplate(const Name& name, const AZStd::shared_ptr& passTemplate) { return m_passLibrary.AddPassTemplate(name, passTemplate); @@ -471,6 +466,11 @@ namespace AZ return m_passLibrary.GetPassTemplate(name); } + void PassSystem::RemovePassTemplate(const Name& name) + { + m_passLibrary.RemovePassTemplate(name); + } + void PassSystem::RemovePassFromLibrary(Pass* pass) { m_passLibrary.RemovePassFromLibrary(pass); @@ -487,10 +487,21 @@ namespace AZ RemovePassFromLibrary(pass); --m_passCounter; } - - AZStd::vector PassSystem::FindPasses(const PassFilter& passFilter) const + + void PassSystem::ForEachPass(const PassFilter& filter, AZStd::function passFunction) { - return m_passLibrary.FindPasses(passFilter); + return m_passLibrary.ForEachPass(filter, passFunction); + } + + Pass* PassSystem::FindFirstPass(const PassFilter& filter) + { + Pass* foundPass = nullptr; + m_passLibrary.ForEachPass(filter, [&foundPass](RPI::Pass* pass) ->PassFilterExecutionFlow + { + foundPass = pass; + return PassFilterExecutionFlow::StopVisitingPasses; + }); + return foundPass; } SwapChainPass* PassSystem::FindSwapChainPass(AzFramework::NativeWindowHandle windowHandle) const diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/RenderPass.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/RenderPass.cpp index 318ea7d11f..8353762c0f 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/RenderPass.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/RenderPass.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #include @@ -176,12 +177,6 @@ namespace AZ } } } - - // Need to recreate the dest attachment because the source attachment might be changed - if (!m_attachmentCopy.expired()) - { - m_attachmentCopy.lock()->InvalidateDestImage(); - } } void RenderPass::FrameBeginInternal(FramePrepareParams params) @@ -195,11 +190,7 @@ namespace AZ // Read back the ScopeQueries submitted from previous frames ReadbackScopeQueryResults(); - - if (!m_attachmentCopy.expired()) - { - m_attachmentCopy.lock()->FrameBegin(params); - } + CollectSrgs(); PassSystemInterface::Get()->IncrementFrameRenderPassCount(); @@ -272,15 +263,8 @@ namespace AZ } } - void RenderPass::BindAttachment(const RHI::FrameGraphCompileContext& context, const PassAttachmentBinding& binding, int16_t& imageIndex, int16_t& bufferIndex) + void RenderPass::BindAttachment(const RHI::FrameGraphCompileContext& context, PassAttachmentBinding& binding, int16_t& imageIndex, int16_t& bufferIndex) { - if (binding.m_shaderInputIndex == PassAttachmentBinding::ShaderInputNoBind || - binding.m_scopeAttachmentUsage == RHI::ScopeAttachmentUsage::RenderTarget || - binding.m_scopeAttachmentUsage == RHI::ScopeAttachmentUsage::DepthStencil) - { - return; - } - PassAttachment* attachment = binding.m_attachment.get(); if (attachment) { @@ -293,11 +277,40 @@ namespace AZ inputIndex = imageIndex; } const RHI::ImageView* imageView = context.GetImageView(attachment->GetAttachmentId(), binding.m_attachmentUsageIndex); - m_shaderResourceGroup->SetImageView(RHI::ShaderInputImageIndex(inputIndex), imageView, arrayIndex); - ++imageIndex; + + if (binding.m_shaderImageDimensionsNameIndex.HasName()) + { + RHI::Size size = attachment->m_descriptor.m_image.m_size; + + AZ::Vector4 imageDimensions; + imageDimensions.SetX(float(size.m_width)); + imageDimensions.SetY(float(size.m_height)); + imageDimensions.SetZ(1.0f / float(size.m_width)); + imageDimensions.SetW(1.0f / float(size.m_height)); + + [[maybe_unused]] + bool success = m_shaderResourceGroup->SetConstant(binding.m_shaderImageDimensionsNameIndex, imageDimensions); + AZ_Assert(success, "Pass [%s] Could not find float4 constant [%s] in Shader Resource Group [%s]", + GetPathName().GetCStr(), + binding.m_shaderImageDimensionsNameIndex.GetNameForDebug().GetCStr(), + m_shaderResourceGroup->GetDatabaseName()); + } + + if (binding.m_shaderInputIndex != PassAttachmentBinding::ShaderInputNoBind && + binding.m_scopeAttachmentUsage != RHI::ScopeAttachmentUsage::RenderTarget && + binding.m_scopeAttachmentUsage != RHI::ScopeAttachmentUsage::DepthStencil) + { + m_shaderResourceGroup->SetImageView(RHI::ShaderInputImageIndex(inputIndex), imageView, arrayIndex); + ++imageIndex; + } } else if (attachment->GetAttachmentType() == RHI::AttachmentType::Buffer) { + if (binding.m_shaderInputIndex == PassAttachmentBinding::ShaderInputNoBind) + { + return; + } + if (inputIndex == PassAttachmentBinding::ShaderInputAutoBind) { inputIndex = bufferIndex; diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/Specific/ImageAttachmentPreviewPass.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/Specific/ImageAttachmentPreviewPass.cpp index 105936f64d..fc1ea2ce1b 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/Specific/ImageAttachmentPreviewPass.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/Specific/ImageAttachmentPreviewPass.cpp @@ -14,7 +14,7 @@ #include #include #include -#include +#include #include #include #include @@ -131,7 +131,7 @@ namespace AZ Data::AssetBus::Handler::BusDisconnect(); } - void ImageAttachmentPreviewPass::PreviewImageAttachmentForPass(RenderPass* pass, const PassAttachment* passAttachment) + void ImageAttachmentPreviewPass::PreviewImageAttachmentForPass(Pass* pass, const PassAttachment* passAttachment) { if (passAttachment->GetAttachmentType() != RHI::AttachmentType::Image) { @@ -244,7 +244,7 @@ namespace AZ m_needsShaderLoad = false; // Load Shader - const char* ShaderPath = "shader/imagepreview.azshader"; + const char* ShaderPath = "shaders/imagepreview.azshader"; Data::Asset shaderAsset = RPI::FindShaderAsset(ShaderPath); m_shader = Shader::FindOrCreate(shaderAsset); if (m_shader == nullptr) diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/RPISystem.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/RPISystem.cpp index eb74a81e3e..943966c13b 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/RPISystem.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/RPISystem.cpp @@ -159,6 +159,11 @@ namespace AZ AZ_Assert(false, "Scene was already registered"); return; } + else if (!scene->GetName().IsEmpty() && scene->GetName() == sceneItem->GetName()) + { + // only report a warning if there is a scene with duplicated name + AZ_Warning("RPISystem", false, "There is a registered scene with same name [%s]", scene->GetName().GetCStr()); + } } m_scenes.push_back(scene); @@ -177,11 +182,35 @@ namespace AZ AZ_Assert(false, "Can't unregister scene which wasn't registered"); } - ScenePtr RPISystem::GetScene(const SceneId& sceneId) const + Scene* RPISystem::GetScene(const SceneId& sceneId) const { for (const auto& scene : m_scenes) { if (scene->GetId() == sceneId) + { + return scene.get(); + } + } + return nullptr; + } + + Scene* RPISystem::GetSceneByName(const AZ::Name& name) const + { + for (const auto& scene : m_scenes) + { + if (scene->GetName() == name) + { + return scene.get(); + } + } + return nullptr; + } + + ScenePtr RPISystem::GetDefaultScene() const + { + for (const auto& scene : m_scenes) + { + if (scene->GetName() == AZ::Name("Main")) { return scene; } @@ -189,16 +218,6 @@ namespace AZ return nullptr; } - ScenePtr RPISystem::GetDefaultScene() const - { - if (m_scenes.size() > 0) - { - return m_scenes[0]; - } - return nullptr; - } - - RenderPipelinePtr RPISystem::GetRenderPipelineForWindow(AzFramework::NativeWindowHandle windowHandle) { RenderPipelinePtr renderPipeline; diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/RPIUtils.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/RPIUtils.cpp index e70885daa1..fda8f967da 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/RPIUtils.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/RPIUtils.cpp @@ -119,7 +119,12 @@ namespace AZ AzFramework::AssetSystem::AssetStatus status = AzFramework::AssetSystem::AssetStatus_Unknown; AzFramework::AssetSystemRequestBus::BroadcastResult( status, &AzFramework::AssetSystemRequestBus::Events::CompileAssetSync, path); - AZ_Error("RPIUtils", status == AzFramework::AssetSystem::AssetStatus_Compiled, "Could not compile image at '%s'", path.data()); + + // When running with no Asset Processor (for example in release), CompileAssetSync will return AssetStatus_Unknown. + AZ_Error( + "RPIUtils", + status == AzFramework::AssetSystem::AssetStatus_Compiled || status == AzFramework::AssetSystem::AssetStatus_Unknown, + "Could not compile image at '%s'", path.data()); Data::AssetId streamingImageAssetId; Data::AssetCatalogRequestBus::BroadcastResult( diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/Scene.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/Scene.cpp index c5d82c6f29..8ec968943d 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/Scene.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/Scene.cpp @@ -45,7 +45,9 @@ namespace AZ auto shaderAsset = RPISystemInterface::Get()->GetCommonShaderAssetForSrgs(); scene->m_srg = ShaderResourceGroup::Create(shaderAsset, sceneSrgLayout->GetName()); } - + + scene->m_name = sceneDescriptor.m_nameId; + return ScenePtr(scene); } @@ -83,10 +85,23 @@ namespace AZ return nullptr; } + Scene* Scene::GetSceneForEntityId(AZ::EntityId entityId) + { + // Find the entity context for the entity ID. + AzFramework::EntityContextId entityContextId = AzFramework::EntityContextId::CreateNull(); + AzFramework::EntityIdContextQueryBus::EventResult(entityContextId, entityId, &AzFramework::EntityIdContextQueryBus::Events::GetOwningContextId); + + if (!entityContextId.IsNull()) + { + return GetSceneForEntityContextId(entityContextId); + } + return nullptr; + } + Scene::Scene() { - m_id = Uuid::CreateRandom(); + m_id = AZ::Uuid::CreateRandom(); m_cullingScene = aznew CullingScene(); SceneRequestBus::Handler::BusConnect(m_id); m_drawFilterTagRegistry = RHI::DrawFilterTagRegistry::Create(); @@ -96,7 +111,7 @@ namespace AZ { if (m_taskGraphActive) { - WaitTGEvent(m_simulationFinishedTGEvent, &m_simulationFinishedWorkActive); + WaitAndCleanTGEvent(AZStd::move(m_simulationFinishedTGEvent)); } else { @@ -299,7 +314,6 @@ namespace AZ // Force to update the lookup table since adding render pipeline would effect any pipeline states created before pass system tick RebuildPipelineStatesLookup(); - AZ_Assert(!m_id.IsNull(), "RPI::Scene needs to have a valid uuid."); SceneNotificationBus::Event(m_id, &SceneNotification::OnRenderPipelineAdded, pipeline); } @@ -371,8 +385,8 @@ namespace AZ }); } simulationTG.Detach(); - m_simulationFinishedWorkActive = true; - simulationTG.Submit(&m_simulationFinishedTGEvent); + m_simulationFinishedTGEvent = AZStd::make_unique(); + simulationTG.Submit(m_simulationFinishedTGEvent.get()); } void Scene::SimulateJobs() @@ -405,7 +419,7 @@ namespace AZ // If previous simulation job wasn't done, wait for it to finish. if (m_taskGraphActive) { - WaitTGEvent(m_simulationFinishedTGEvent, &m_simulationFinishedWorkActive); + WaitAndCleanTGEvent(AZStd::move(m_simulationFinishedTGEvent)); } else { @@ -435,17 +449,14 @@ namespace AZ } } - void Scene::WaitTGEvent(AZ::TaskGraphEvent& completionTGEvent, AZStd::atomic_bool* workToWaitOn ) + void Scene::WaitAndCleanTGEvent(AZStd::unique_ptr&& completionTGEvent) { - AZ_PROFILE_SCOPE(RPI, "Scene: WaitAndCleanCompletionJob"); - if (!workToWaitOn || workToWaitOn->load()) + AZ_PROFILE_SCOPE(RPI, "Scene: WaitAndCleanTGEvent"); + if (completionTGEvent) { - completionTGEvent.Wait(); - } - if (workToWaitOn) - { - workToWaitOn->store(false); + completionTGEvent->Wait(); } + // allow completionTGEvent to go out of scope and be deleted } void Scene::WaitAndCleanCompletionJob(AZ::JobCompletion*& completionJob) @@ -485,12 +496,12 @@ namespace AZ void Scene::CollectDrawPacketsTaskGraph() { - AZ_PROFILE_SCOPE(RPI, "CollectDrawPackets"); + AZ_PROFILE_SCOPE(RPI, "CollectDrawPacketsTaskGraph"); AZ::TaskGraphEvent collectDrawPacketsTGEvent; static const AZ::TaskDescriptor collectDrawPacketsTGDesc{"RPI_Scene_PrepareRender_CollectDrawPackets", "Graphics"}; - AZ::TaskGraph collectDrawPacketsTG; - // Launch FeatureProcessor::Render() jobs + + // Launch FeatureProcessor::Render() taskgraphs for (auto& fp : m_featureProcessors) { collectDrawPacketsTG.AddTask( @@ -506,32 +517,48 @@ namespace AZ // Launch CullingSystem::ProcessCullables() jobs (will run concurrently with FeatureProcessor::Render() jobs if m_parallelOctreeTraversal) bool parallelOctreeTraversal = m_cullingScene->GetDebugContext().m_parallelOctreeTraversal; m_cullingScene->BeginCulling(m_renderPacket.m_views); - AZ::JobCompletion processCullablesCompletion; - for (ViewPtr& viewPtr : m_renderPacket.m_views) + static const AZ::TaskDescriptor processCullablesDescriptor{"AZ::RPI::Scene::ProcessCullables", "Graphics"}; + AZ::TaskGraphEvent processCullablesTGEvent; + AZ::TaskGraph processCullablesTG; + if (parallelOctreeTraversal) { - AZ::Job* processCullablesJob = AZ::CreateJobFunction([this, &viewPtr](AZ::Job& thisJob) - { - m_cullingScene->ProcessCullables(*this, *viewPtr, thisJob); // can't call directly because ProcessCullables needs a parent job - }, - true, nullptr); //auto-deletes - if (parallelOctreeTraversal) + for (ViewPtr& viewPtr : m_renderPacket.m_views) { - processCullablesJob->SetDependent(&processCullablesCompletion); - processCullablesJob->Start(); - } - else - { - processCullablesJob->StartAndWaitForCompletion(); + processCullablesTG.AddTask(processCullablesDescriptor, [this, &viewPtr, &processCullablesTGEvent]() + { + AZ::TaskGraph subTaskGraph; + m_cullingScene->ProcessCullablesTG(*this, *viewPtr, subTaskGraph); + if (!subTaskGraph.IsEmpty()) + { + subTaskGraph.Detach(); + subTaskGraph.Submit(&processCullablesTGEvent); + } + }); } } + else + { + for (ViewPtr& viewPtr : m_renderPacket.m_views) + { + m_cullingScene->ProcessCullablesTG(*this, *viewPtr, processCullablesTG); + } + } + bool processCullablesHasWork = !processCullablesTG.IsEmpty(); + if (processCullablesHasWork) + { + processCullablesTG.Submit(&processCullablesTGEvent); + } - WaitTGEvent(collectDrawPacketsTGEvent); - processCullablesCompletion.StartAndWaitForCompletion(); + collectDrawPacketsTGEvent.Wait(); + if (processCullablesHasWork) // skip the wait if there is no work to do + { + processCullablesTGEvent.Wait(); + } } void Scene::CollectDrawPacketsJobs() { - AZ_PROFILE_SCOPE(RPI, "CollectDrawPackets"); + AZ_PROFILE_SCOPE(RPI, "CollectDrawPacketsJobs"); AZ::JobCompletion* collectDrawPacketsCompletion = aznew AZ::JobCompletion(); // Launch FeatureProcessor::Render() jobs @@ -553,7 +580,7 @@ namespace AZ { AZ::Job* processCullablesJob = AZ::CreateJobFunction([this, &viewPtr](AZ::Job& thisJob) { - m_cullingScene->ProcessCullables(*this, *viewPtr, thisJob); // can't call directly because ProcessCullables needs a parent job + m_cullingScene->ProcessCullablesJobs(*this, *viewPtr, thisJob); // can't call directly because ProcessCullables needs a parent job }, true, nullptr); //auto-deletes if (m_cullingScene->GetDebugContext().m_parallelOctreeTraversal) @@ -586,7 +613,7 @@ namespace AZ }); } finalizeDrawListsTG.Submit(&finalizeDrawListsTGEvent); - WaitTGEvent(finalizeDrawListsTGEvent); + finalizeDrawListsTGEvent.Wait(); } void Scene::FinalizeDrawListsJobs() @@ -612,7 +639,7 @@ namespace AZ if (m_taskGraphActive) { - WaitTGEvent(m_simulationFinishedTGEvent, &m_simulationFinishedWorkActive); + WaitAndCleanTGEvent(AZStd::move(m_simulationFinishedTGEvent)); } else { @@ -785,6 +812,11 @@ namespace AZ { return m_id; } + + AZ::Name Scene::GetName() const + { + return m_name; + } bool Scene::SetDefaultRenderPipeline(const RenderPipelineId& pipelineId) { diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/Shader/Shader.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/Shader/Shader.cpp index 51dd9c36d3..bee9200c07 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/Shader/Shader.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/Shader/Shader.cpp @@ -320,6 +320,30 @@ namespace AZ } const ShaderVariant& Shader::GetVariant(ShaderVariantStableId shaderVariantStableId) + { + const ShaderVariant& variant = GetVariantInternal(shaderVariantStableId); + + if (ShaderReloadDebugTracker::IsEnabled()) + { + auto makeTimeString = [](AZStd::sys_time_t timestamp, AZStd::sys_time_t now) + { + AZStd::sys_time_t elapsedMicroseconds = now - timestamp; + double elapsedSeconds = aznumeric_cast(elapsedMicroseconds / 1'000'000); + AZStd::string timeString = AZStd::string::format("%lld (%f seconds ago)", timestamp, elapsedSeconds); + return timeString; + }; + + AZStd::sys_time_t now = AZStd::GetTimeNowMicroSecond(); + + ShaderReloadDebugTracker::Printf("{%p}->Shader::GetVariant for shader '%s' [build time %s] found variant '%s' [build time %s]", this, + m_asset.GetHint().c_str(), makeTimeString(m_asset->GetBuildTimestamp(), now).c_str(), + variant.GetShaderVariantAsset().GetHint().c_str(), makeTimeString(variant.GetShaderVariantAsset()->GetBuildTimestamp(), now).c_str()); + } + + return variant; + } + + const ShaderVariant& Shader::GetVariantInternal(ShaderVariantStableId shaderVariantStableId) { if (!shaderVariantStableId.IsValid() || shaderVariantStableId == ShaderAsset::RootShaderVariantStableId) { @@ -336,7 +360,7 @@ namespace AZ // reloaded, but some (or all) shader variants haven't been built yet. Since we want to use the latest version of the // shader code, ignore the old variants and fall back to the newer root variant instead. There's no need to report a // warning here because m_asset->GetVariant below will report one. - if (findIt->second.GetBuildTimestamp() >= m_asset->GetShaderAssetBuildTimestamp()) + if (findIt->second.GetBuildTimestamp() >= m_asset->GetBuildTimestamp()) { return findIt->second; } @@ -359,7 +383,7 @@ namespace AZ auto findIt = m_shaderVariants.find(shaderVariantStableId); if (findIt != m_shaderVariants.end()) { - if (findIt->second.GetBuildTimestamp() >= m_asset->GetShaderAssetBuildTimestamp()) + if (findIt->second.GetBuildTimestamp() >= m_asset->GetBuildTimestamp()) { return findIt->second; } diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/Shader/ShaderReloadDebugTracker.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/Shader/ShaderReloadDebugTracker.cpp index 6b97e43cbd..3e7ff826cf 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/Shader/ShaderReloadDebugTracker.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/Shader/ShaderReloadDebugTracker.cpp @@ -7,24 +7,75 @@ */ #include +#include namespace AZ { namespace RPI { - bool ShaderReloadDebugTracker::s_enabled = false; - int ShaderReloadDebugTracker::s_indent = 0; + namespace ShaderReloadDebugTrackerInternal + { + static const char EnabledVariableName[] = "ShaderReloadDebugTracker enabled"; + static const char IndentVariableName[] = "ShaderReloadDebugTracker indent"; + + static EnvironmentVariable s_enabled; + static EnvironmentVariable s_indent; + } + + void ShaderReloadDebugTracker::Init() + { + ShaderReloadDebugTrackerInternal::s_enabled = AZ::Environment::CreateVariable(ShaderReloadDebugTrackerInternal::EnabledVariableName); + ShaderReloadDebugTrackerInternal::s_indent = AZ::Environment::CreateVariable(ShaderReloadDebugTrackerInternal::IndentVariableName); + + ShaderReloadDebugTrackerInternal::s_enabled.Get() = false; + ShaderReloadDebugTrackerInternal::s_indent.Get() = 0; + } + + void ShaderReloadDebugTracker::Shutdown() + { + ShaderReloadDebugTrackerInternal::s_enabled.Reset(); + ShaderReloadDebugTrackerInternal::s_indent.Reset(); + } + + void ShaderReloadDebugTracker::MakeReady() + { + if (!ShaderReloadDebugTrackerInternal::s_enabled.IsValid()) + { + ShaderReloadDebugTrackerInternal::s_enabled = AZ::Environment::FindVariable(ShaderReloadDebugTrackerInternal::EnabledVariableName); + ShaderReloadDebugTrackerInternal::s_indent = AZ::Environment::FindVariable(ShaderReloadDebugTrackerInternal::IndentVariableName); + } + } bool ShaderReloadDebugTracker::IsEnabled() { #ifdef AZ_ENABLE_SHADER_RELOAD_DEBUG_TRACKER + MakeReady(); + // Set this to true in the debugger to turn on hot reload tracing. // If needed, we could hook this up to a CVar. - return s_enabled; + return ShaderReloadDebugTrackerInternal::s_enabled.Get(); #else return false; #endif } + + void ShaderReloadDebugTracker::AddIndent() + { + MakeReady(); + ShaderReloadDebugTrackerInternal::s_indent.Get() += IndentSpaces; + } + + void ShaderReloadDebugTracker::RemoveIndent() + { + MakeReady(); + ShaderReloadDebugTrackerInternal::s_indent.Get() -= IndentSpaces; + } + + int ShaderReloadDebugTracker::GetIndent() + { + MakeReady(); + return ShaderReloadDebugTrackerInternal::s_indent.Get(); + } ShaderReloadDebugTracker::ScopedSection::~ScopedSection() { diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/Shader/ShaderSystem.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/Shader/ShaderSystem.cpp index ec6aeb3771..2e66aaca99 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/Shader/ShaderSystem.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/Shader/ShaderSystem.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -86,10 +87,13 @@ namespace AZ }; Data::InstanceDatabase::Create(azrtti_typeid(), handler, false); } + + ShaderReloadDebugTracker::Init(); } void ShaderSystem::Shutdown() { + ShaderReloadDebugTracker::Shutdown(); Data::InstanceDatabase::Destroy(); Data::InstanceDatabase::Destroy(); Data::InstanceDatabase::Destroy(); diff --git a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Material/MaterialAsset.cpp b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Material/MaterialAsset.cpp index e9d8a42641..36f4947e3d 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Material/MaterialAsset.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Material/MaterialAsset.cpp @@ -208,7 +208,7 @@ namespace AZ return; } - const uint32_t originalVersion = m_materialTypeVersion; + [[maybe_unused]] const uint32_t originalVersion = m_materialTypeVersion; bool changesWereApplied = false; diff --git a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Material/MaterialPropertyDescriptor.cpp b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Material/MaterialPropertyDescriptor.cpp index 700ac41003..5d0a88a6d3 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Material/MaterialPropertyDescriptor.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Material/MaterialPropertyDescriptor.cpp @@ -203,6 +203,16 @@ namespace AZ return InvalidEnumValue; } + + const AZ::Name& MaterialPropertyDescriptor::GetEnumName(uint32_t enumValue) const + { + if (enumValue < m_enumNames.size()) + { + return m_enumNames.at(enumValue); + } + static AZ::Name EmptyName = AZ::Name(); + return EmptyName; + } } // namespace RPI } // namespace AZ diff --git a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Material/MaterialTypeAsset.cpp b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Material/MaterialTypeAsset.cpp index 522ba74119..76634201eb 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Material/MaterialTypeAsset.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Material/MaterialTypeAsset.cpp @@ -169,6 +169,22 @@ namespace AZ return m_version; } + + bool MaterialTypeAsset::ApplyPropertyRenames(AZ::Name& propertyId) const + { + bool renamed = false; + + for (const auto& versionUpdates : m_materialVersionUpdates) + { + if (versionUpdates.ApplyPropertyRenames(propertyId)) + { + renamed = true; + } + } + + return renamed; + } + void MaterialTypeAsset::SetReady() { m_status = AssetStatus::Ready; diff --git a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Material/MaterialVersionUpdate.cpp b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Material/MaterialVersionUpdate.cpp index b387e502e2..f5dfe9e80c 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Material/MaterialVersionUpdate.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Material/MaterialVersionUpdate.cpp @@ -56,6 +56,22 @@ namespace AZ { m_toVersion = toVersion; } + + bool MaterialVersionUpdate::ApplyPropertyRenames(AZ::Name& propertyId) const + { + bool renamed = false; + + for (const auto& action : m_actions) + { + if (action.m_fromPropertyId == propertyId) + { + propertyId = action.m_toPropertyId; + renamed = true; + } + } + + return renamed; + } bool MaterialVersionUpdate::ApplyVersionUpdates(MaterialAsset& materialAsset) const { diff --git a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Pass/PassAttachmentReflect.cpp b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Pass/PassAttachmentReflect.cpp index 39be08d299..602793ba19 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Pass/PassAttachmentReflect.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Pass/PassAttachmentReflect.cpp @@ -56,9 +56,10 @@ namespace AZ ; serializeContext->Class() - ->Version(1) + ->Version(2) ->Field("Name", &PassSlot::m_name) ->Field("ShaderInputName", &PassSlot::m_shaderInputName) + ->Field("ShaderImageDimensionsConstant", &PassSlot::m_shaderImageDimensionsName) ->Field("ShaderInputArrayIndex", &PassSlot::m_shaderInputArrayIndex) ->Field("SlotType", &PassSlot::m_slotType) ->Field("ScopeAttachmentUsage", &PassSlot::m_scopeAttachmentUsage) diff --git a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Shader/ShaderAsset.cpp b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Shader/ShaderAsset.cpp index 692087d93b..9fa76ac5ee 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Shader/ShaderAsset.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Shader/ShaderAsset.cpp @@ -100,7 +100,7 @@ namespace AZ ->Field("pipelineStateType", &ShaderAsset::m_pipelineStateType) ->Field("shaderOptionGroupLayout", &ShaderAsset::m_shaderOptionGroupLayout) ->Field("drawListName", &ShaderAsset::m_drawListName) - ->Field("shaderAssetBuildTimestamp", &ShaderAsset::m_shaderAssetBuildTimestamp) + ->Field("shaderAssetBuildTimestamp", &ShaderAsset::m_buildTimestamp) ->Field("perAPIShaderData", &ShaderAsset::m_perAPIShaderData) ; } @@ -134,11 +134,11 @@ namespace AZ return m_drawListName; } - AZStd::sys_time_t ShaderAsset::GetShaderAssetBuildTimestamp() const + AZStd::sys_time_t ShaderAsset::GetBuildTimestamp() const { - return m_shaderAssetBuildTimestamp; + return m_buildTimestamp; } - + void ShaderAsset::SetReady() { m_status = AssetStatus::Ready; @@ -256,7 +256,7 @@ namespace AZ } return GetRootVariant(supervariantIndex); } - else if (variant->GetBuildTimestamp() >= m_shaderAssetBuildTimestamp) + else if (variant->GetBuildTimestamp() >= m_buildTimestamp) { return variant; } @@ -264,7 +264,6 @@ namespace AZ { // When rebuilding shaders we may be in a state where the ShaderAsset and root ShaderVariantAsset have been rebuilt and reloaded, but some (or all) // shader variants haven't been built yet. Since we want to use the latest version of the shader code, ignore the old variants and fall back to the newer root variant instead. - AZ_Warning("ShaderAsset", false, "ShaderAsset and ShaderVariantAsset are out of sync; defaulting to root shader variant. (This is common while reloading shaders)."); return GetRootVariant(supervariantIndex); } } diff --git a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Shader/ShaderAssetCreator.cpp b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Shader/ShaderAssetCreator.cpp index 87338f2185..5df37aa211 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Shader/ShaderAssetCreator.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Shader/ShaderAssetCreator.cpp @@ -21,7 +21,7 @@ namespace AZ { if (ValidateIsReady()) { - m_asset->m_shaderAssetBuildTimestamp = shaderAssetBuildTimestamp; + m_asset->m_buildTimestamp = shaderAssetBuildTimestamp; } } @@ -390,7 +390,7 @@ namespace AZ m_asset->m_pipelineStateType = sourceShaderAsset.m_pipelineStateType; m_asset->m_drawListName = sourceShaderAsset.m_drawListName; m_asset->m_shaderOptionGroupLayout = sourceShaderAsset.m_shaderOptionGroupLayout; - m_asset->m_shaderAssetBuildTimestamp = sourceShaderAsset.m_shaderAssetBuildTimestamp; + m_asset->m_buildTimestamp = sourceShaderAsset.m_buildTimestamp; // copy root variant assets for (auto& perAPIShaderData : sourceShaderAsset.m_perAPIShaderData) diff --git a/Gems/Atom/RPI/Code/Tests/Common/RPITestFixture.h b/Gems/Atom/RPI/Code/Tests/Common/RPITestFixture.h index 48d404d268..0707528d7f 100644 --- a/Gems/Atom/RPI/Code/Tests/Common/RPITestFixture.h +++ b/Gems/Atom/RPI/Code/Tests/Common/RPITestFixture.h @@ -21,7 +21,7 @@ #include #include #include -#include +#include namespace UnitTest { diff --git a/Gems/Atom/RPI/Code/Tests/Material/MaterialSourceDataTests.cpp b/Gems/Atom/RPI/Code/Tests/Material/MaterialSourceDataTests.cpp index acce52ae8e..fa8eed35de 100644 --- a/Gems/Atom/RPI/Code/Tests/Material/MaterialSourceDataTests.cpp +++ b/Gems/Atom/RPI/Code/Tests/Material/MaterialSourceDataTests.cpp @@ -105,6 +105,13 @@ namespace UnitTest {"op": "rename", "from": "general.testColorNameB", "to": "general.testColorNameC"} ] }, + { + "toVersion": 6, + "actions": [ + {"op": "rename", "from": "oldGroup.MyFloat", "to": "general.MyFloat"}, + {"op": "rename", "from": "oldGroup.MyIntOldName", "to": "general.MyInt"} + ] + }, { "toVersion": 10, "actions": [ @@ -751,6 +758,72 @@ namespace UnitTest material.ApplyVersionUpdates(); } + TEST_F(MaterialSourceDataTests, Load_MaterialTypeVersionUpdate_MovePropertiesToAnotherGroup) + { + const AZStd::string inputJson = R"( + { + "materialType": "@exefolder@/Temp/test.materialtype", + "materialTypeVersion": 3, + "properties": { + "oldGroup": { + "MyFloat": 1.2, + "MyIntOldName": 5 + } + } + } + )"; + + MaterialSourceData material; + JsonTestResult loadResult = LoadTestDataFromJson(material, inputJson); + + EXPECT_EQ(AZ::JsonSerializationResult::Tasks::ReadField, loadResult.m_jsonResultCode.GetTask()); + EXPECT_EQ(AZ::JsonSerializationResult::Processing::Completed, loadResult.m_jsonResultCode.GetProcessing()); + + // Initially, the loaded material data will match the .material file exactly. This gives us the accurate representation of + // what's actually saved on disk. + + EXPECT_NE(material.m_properties["oldGroup"].find("MyFloat"), material.m_properties["oldGroup"].end()); + EXPECT_NE(material.m_properties["oldGroup"].find("MyIntOldName"), material.m_properties["oldGroup"].end()); + EXPECT_EQ(material.m_properties["general"].find("MyFloat"), material.m_properties["general"].end()); + EXPECT_EQ(material.m_properties["general"].find("MyInt"), material.m_properties["general"].end()); + + float myFloat = material.m_properties["oldGroup"]["MyFloat"].m_value.GetValue(); + EXPECT_EQ(myFloat, 1.2f); + + int32_t myInt = material.m_properties["oldGroup"]["MyIntOldName"].m_value.GetValue(); + EXPECT_EQ(myInt, 5); + + EXPECT_EQ(3, material.m_materialTypeVersion); + + // Then we force the material data to update to the latest material type version specification + ErrorMessageFinder warningFinder; // Note this finds errors and warnings, and we're looking for a warning. + warningFinder.AddExpectedErrorMessage("Automatic updates are available. Consider updating the .material source file"); + warningFinder.AddExpectedErrorMessage("This material is based on version '3'"); + warningFinder.AddExpectedErrorMessage("material type is now at version '10'"); + material.ApplyVersionUpdates(); + warningFinder.CheckExpectedErrorsFound(); + + // Now the material data should match the latest material type. + // Look for the property under the latest name in the material type, not the name used in the .material file. + + EXPECT_EQ(material.m_properties["oldGroup"].find("MyFloat"), material.m_properties["oldGroup"].end()); + EXPECT_EQ(material.m_properties["oldGroup"].find("MyIntOldName"), material.m_properties["oldGroup"].end()); + EXPECT_NE(material.m_properties["general"].find("MyFloat"), material.m_properties["general"].end()); + EXPECT_NE(material.m_properties["general"].find("MyInt"), material.m_properties["general"].end()); + + myFloat = material.m_properties["general"]["MyFloat"].m_value.GetValue(); + EXPECT_EQ(myFloat, 1.2f); + + myInt = material.m_properties["general"]["MyInt"].m_value.GetValue(); + EXPECT_EQ(myInt, 5); + + EXPECT_EQ(10, material.m_materialTypeVersion); + + // Calling ApplyVersionUpdates() again should not report the warning again, since the material has already been updated. + warningFinder.Reset(); + material.ApplyVersionUpdates(); + } + TEST_F(MaterialSourceDataTests, Load_MaterialTypeVersionPartialUpdate) { // This case is similar to Load_MaterialTypeVersionUpdate but we start at a later diff --git a/Gems/Atom/RPI/Code/Tests/Material/MaterialTests.cpp b/Gems/Atom/RPI/Code/Tests/Material/MaterialTests.cpp index f202747bf6..9c3e08ee08 100644 --- a/Gems/Atom/RPI/Code/Tests/Material/MaterialTests.cpp +++ b/Gems/Atom/RPI/Code/Tests/Material/MaterialTests.cpp @@ -857,4 +857,35 @@ namespace UnitTest EXPECT_EQ(material->GetPropertyValue(material->FindPropertyIndex(Name{ "MyInt" })), -7); EXPECT_EQ(srgData->GetConstant(srgData->FindShaderInputConstantIndex(Name{ "m_int" })), -7); } + + TEST_F(MaterialTests, TestFindPropertyIndexUsingOldName) + { + MaterialTypeAssetCreator materialTypeCreator; + materialTypeCreator.Begin(Uuid::CreateRandom()); + materialTypeCreator.AddShader(m_testMaterialShaderAsset); + AddCommonTestMaterialProperties(materialTypeCreator); + materialTypeCreator.SetVersion(2); + MaterialVersionUpdate versionUpdate(2); + versionUpdate.AddAction(MaterialVersionUpdate::RenamePropertyAction({Name{ "OldName" },Name{ "MyInt" }})); + materialTypeCreator.AddVersionUpdate(versionUpdate); + materialTypeCreator.End(m_testMaterialTypeAsset); + + MaterialAssetCreator materialCreator; + materialCreator.Begin(Uuid::CreateRandom(), *m_testMaterialTypeAsset); + materialCreator.End(m_testMaterialAsset); + + Data::Instance material = Material::FindOrCreate(m_testMaterialAsset); + + bool wasRenamed = false; + Name newName; + MaterialPropertyIndex indexFromOldName = material->FindPropertyIndex(Name{"OldName"}, &wasRenamed, &newName); + EXPECT_TRUE(wasRenamed); + EXPECT_EQ(newName, Name{"MyInt"}); + + MaterialPropertyIndex indexFromNewName = material->FindPropertyIndex(Name{"MyInt"}, &wasRenamed, &newName); + EXPECT_FALSE(wasRenamed); + + EXPECT_EQ(indexFromOldName, indexFromNewName); + } + } diff --git a/Gems/Atom/RPI/Code/Tests/Material/MaterialTypeAssetTests.cpp b/Gems/Atom/RPI/Code/Tests/Material/MaterialTypeAssetTests.cpp index 81b723ec04..382ab4e149 100644 --- a/Gems/Atom/RPI/Code/Tests/Material/MaterialTypeAssetTests.cpp +++ b/Gems/Atom/RPI/Code/Tests/Material/MaterialTypeAssetTests.cpp @@ -1041,5 +1041,88 @@ namespace UnitTest EXPECT_FALSE(materialTypeAsset->GetShaderCollection()[1].MaterialOwnsShaderOption(Name{"o_globalOption_inShaderB"})); } + TEST_F(MaterialTypeAssetTests, ApplyPropertyRenames) + { + Data::Asset materialTypeAsset; + + auto addRenameAction = [](MaterialVersionUpdate& versionUpdate, const char* from, const char* to) + { + versionUpdate.AddAction(MaterialVersionUpdate::RenamePropertyAction( + { + Name{ from }, + Name{ to } + })); + }; + + MaterialTypeAssetCreator materialTypeCreator; + materialTypeCreator.Begin(Uuid::CreateRandom()); + + // Version updates + materialTypeCreator.SetVersion(10); + + MaterialVersionUpdate versionUpdate2(2); + addRenameAction(versionUpdate2, "general.fooA", "general.fooB"); + materialTypeCreator.AddVersionUpdate(versionUpdate2); + + MaterialVersionUpdate versionUpdate4(4); + addRenameAction(versionUpdate4, "general.barA", "general.barB"); + materialTypeCreator.AddVersionUpdate(versionUpdate4); + + MaterialVersionUpdate versionUpdate6(6); + addRenameAction(versionUpdate6, "general.fooB", "general.fooC"); + addRenameAction(versionUpdate6, "general.barB", "general.barC"); + materialTypeCreator.AddVersionUpdate(versionUpdate6); + + MaterialVersionUpdate versionUpdate7(7); + addRenameAction(versionUpdate7, "general.bazA", "otherGroup.bazB"); + materialTypeCreator.AddVersionUpdate(versionUpdate7); + + materialTypeCreator.BeginMaterialProperty(Name{ "general.fooC" }, MaterialPropertyDataType::Bool); + materialTypeCreator.EndMaterialProperty(); + materialTypeCreator.BeginMaterialProperty(Name{ "general.barC" }, MaterialPropertyDataType::Bool); + materialTypeCreator.EndMaterialProperty(); + materialTypeCreator.BeginMaterialProperty(Name{ "otherGroup.bazB" }, MaterialPropertyDataType::Bool); + materialTypeCreator.EndMaterialProperty(); + + EXPECT_TRUE(materialTypeCreator.End(materialTypeAsset)); + + AZ::Name propertyId; + + propertyId = AZ::Name{"doesNotExist"}; + EXPECT_FALSE(materialTypeAsset->ApplyPropertyRenames(propertyId)); + EXPECT_STREQ(propertyId.GetCStr(), "doesNotExist"); + + propertyId = AZ::Name{"general.fooA"}; + EXPECT_TRUE(materialTypeAsset->ApplyPropertyRenames(propertyId)); + EXPECT_STREQ(propertyId.GetCStr(), "general.fooC"); + + propertyId = AZ::Name{"general.fooB"}; + EXPECT_TRUE(materialTypeAsset->ApplyPropertyRenames(propertyId)); + EXPECT_STREQ(propertyId.GetCStr(), "general.fooC"); + + propertyId = AZ::Name{"general.fooC"}; + EXPECT_FALSE(materialTypeAsset->ApplyPropertyRenames(propertyId)); + EXPECT_STREQ(propertyId.GetCStr(), "general.fooC"); + + propertyId = AZ::Name{"general.barA"}; + EXPECT_TRUE(materialTypeAsset->ApplyPropertyRenames(propertyId)); + EXPECT_STREQ(propertyId.GetCStr(), "general.barC"); + + propertyId = AZ::Name{"general.barB"}; + EXPECT_TRUE(materialTypeAsset->ApplyPropertyRenames(propertyId)); + EXPECT_STREQ(propertyId.GetCStr(), "general.barC"); + + propertyId = AZ::Name{"general.barC"}; + EXPECT_FALSE(materialTypeAsset->ApplyPropertyRenames(propertyId)); + EXPECT_STREQ(propertyId.GetCStr(), "general.barC"); + + propertyId = AZ::Name{"general.bazA"}; + EXPECT_TRUE(materialTypeAsset->ApplyPropertyRenames(propertyId)); + EXPECT_STREQ(propertyId.GetCStr(), "otherGroup.bazB"); + + propertyId = AZ::Name{"otherGroup.bazB"}; + EXPECT_FALSE(materialTypeAsset->ApplyPropertyRenames(propertyId)); + EXPECT_STREQ(propertyId.GetCStr(), "otherGroup.bazB"); + } } diff --git a/Gems/Atom/RPI/Code/Tests/Material/MaterialTypeSourceDataTests.cpp b/Gems/Atom/RPI/Code/Tests/Material/MaterialTypeSourceDataTests.cpp index b811e630d0..2fe7b1dbe5 100644 --- a/Gems/Atom/RPI/Code/Tests/Material/MaterialTypeSourceDataTests.cpp +++ b/Gems/Atom/RPI/Code/Tests/Material/MaterialTypeSourceDataTests.cpp @@ -1346,7 +1346,8 @@ namespace UnitTest { "toVersion": 7, "actions": [ - { "op": "rename", "from": "general.bazA", "to": "otherGroup.bazB" } + { "op": "rename", "from": "general.bazA", "to": "otherGroup.bazB" }, + { "op": "rename", "from": "onlyOneProperty.bopA", "to": "otherGroup.bopB" } // This tests a group 'onlyOneProperty' that no longer exists in the material type ] } ], @@ -1370,6 +1371,10 @@ namespace UnitTest { "name": "bazB", "type": "Float" + }, + { + "name": "bopB", + "type": "Float" } ] } @@ -1386,13 +1391,16 @@ namespace UnitTest const MaterialTypeSourceData::PropertyDefinition* foo = materialType.FindProperty("general", "fooC"); const MaterialTypeSourceData::PropertyDefinition* bar = materialType.FindProperty("general", "barC"); const MaterialTypeSourceData::PropertyDefinition* baz = materialType.FindProperty("otherGroup", "bazB"); + const MaterialTypeSourceData::PropertyDefinition* bop = materialType.FindProperty("otherGroup", "bopB"); EXPECT_TRUE(foo); EXPECT_TRUE(bar); EXPECT_TRUE(baz); + EXPECT_TRUE(bop); EXPECT_EQ(foo->m_name, "fooC"); EXPECT_EQ(bar->m_name, "barC"); EXPECT_EQ(baz->m_name, "bazB"); + EXPECT_EQ(bop->m_name, "bopB"); // Now try doing the property lookup using old versions of the name and make sure the same property can be found @@ -1401,12 +1409,15 @@ namespace UnitTest EXPECT_EQ(bar, materialType.FindProperty("general", "barA")); EXPECT_EQ(bar, materialType.FindProperty("general", "barB")); EXPECT_EQ(baz, materialType.FindProperty("general", "bazA")); + EXPECT_EQ(bop, materialType.FindProperty("onlyOneProperty", "bopA")); EXPECT_EQ(nullptr, materialType.FindProperty("general", "fooX")); EXPECT_EQ(nullptr, materialType.FindProperty("general", "barX")); EXPECT_EQ(nullptr, materialType.FindProperty("general", "bazX")); EXPECT_EQ(nullptr, materialType.FindProperty("general", "bazB")); EXPECT_EQ(nullptr, materialType.FindProperty("otherGroup", "bazA")); + EXPECT_EQ(nullptr, materialType.FindProperty("onlyOneProperty", "bopB")); + EXPECT_EQ(nullptr, materialType.FindProperty("otherGroup", "bopA")); } TEST_F(MaterialTypeSourceDataTests, FindPropertyUsingOldName_Error_UnsupportedVersionUpdate) diff --git a/Gems/Atom/RPI/Code/Tests/Pass/PassTests.cpp b/Gems/Atom/RPI/Code/Tests/Pass/PassTests.cpp index 420ab1798a..690f212ec7 100644 --- a/Gems/Atom/RPI/Code/Tests/Pass/PassTests.cpp +++ b/Gems/Atom/RPI/Code/Tests/Pass/PassTests.cpp @@ -19,6 +19,8 @@ #include #include +#include + #include #include @@ -573,7 +575,7 @@ namespace UnitTest EXPECT_TRUE(pass != nullptr); } - TEST_F(PassTests, PassHierarchyFilter) + TEST_F(PassTests, PassFilter_PassHierarchy) { m_data->AddPassTemplatesToLibrary(); @@ -587,62 +589,55 @@ namespace UnitTest parent2->AsParent()->AddChild(parent1); parent1->AsParent()->AddChild(pass); - { - // Filter with only pass name - PassHierarchyFilter filter(Name("pass1")); - EXPECT_TRUE(filter.Matches(pass.get())); - } - { // Filter with pass hierarchy which has only one element - PassHierarchyFilter filter({ Name("pass1") }); + PassFilter filter = PassFilter::CreateWithPassHierarchy({Name("pass1")}); EXPECT_TRUE(filter.Matches(pass.get())); } { - // Filter with empty pass hierarchy. Result one assert + // Filter with empty pass hierarchy, triggers one assert AZ_TEST_START_TRACE_SUPPRESSION; - PassHierarchyFilter filter(AZStd::vector{}); + PassFilter filter = PassFilter::CreateWithPassHierarchy(AZStd::vector{}); AZ_TEST_STOP_TRACE_SUPPRESSION(1); - EXPECT_FALSE(filter.Matches(pass.get())); } { // Filters with partial hierarchy by using string vector AZStd::vector passHierarchy1 = { "parent1", "pass1" }; - PassHierarchyFilter filter1(passHierarchy1); + PassFilter filter1 = PassFilter::CreateWithPassHierarchy(passHierarchy1); EXPECT_TRUE(filter1.Matches(pass.get())); AZStd::vector passHierarchy2 = { "parent2", "pass1" }; - PassHierarchyFilter filter2(passHierarchy2); + PassFilter filter2 = PassFilter::CreateWithPassHierarchy(passHierarchy2); EXPECT_TRUE(filter2.Matches(pass.get())); AZStd::vector passHierarchy3 = { "parent3", "parent2", "pass1" }; - PassHierarchyFilter filter3(passHierarchy3); + PassFilter filter3 = PassFilter::CreateWithPassHierarchy(passHierarchy3); EXPECT_TRUE(filter3.Matches(pass.get())); } { // Filters with partial hierarchy by using Name vector AZStd::vector passHierarchy1 = { Name("parent1"), Name("pass1") }; - PassHierarchyFilter filter1(passHierarchy1); + PassFilter filter1 = PassFilter::CreateWithPassHierarchy(passHierarchy1); EXPECT_TRUE(filter1.Matches(pass.get())); AZStd::vector passHierarchy2 = { Name("parent2"), Name("pass1")}; - PassHierarchyFilter filter2(passHierarchy2); + PassFilter filter2 = PassFilter::CreateWithPassHierarchy(passHierarchy2); EXPECT_TRUE(filter2.Matches(pass.get())); AZStd::vector passHierarchy3 = { Name("parent3"), Name("parent2"), Name("pass1") }; - PassHierarchyFilter filter3(passHierarchy3); + PassFilter filter3 = PassFilter::CreateWithPassHierarchy(passHierarchy3); EXPECT_TRUE(filter3.Matches(pass.get())); } { // Find non-leaf pass - PassHierarchyFilter filter1(AZStd::vector{"parent3", "parent1"}); + PassFilter filter1 = PassFilter::CreateWithPassHierarchy(AZStd::vector{"parent3", "parent1"}); EXPECT_TRUE(filter1.Matches(parent1.get())); - - PassHierarchyFilter filter2(Name("parent1")); + + PassFilter filter2 = PassFilter::CreateWithPassHierarchy({ Name("parent1") }); EXPECT_TRUE(filter2.Matches(parent1.get())); EXPECT_FALSE(filter2.Matches(pass.get())); } @@ -650,11 +645,131 @@ namespace UnitTest { // Failed to find pass // Mis-matching hierarchy - PassHierarchyFilter filter1(AZStd::vector{"Parent1", "Parent3", "pass1"}); + PassFilter filter1 = PassFilter::CreateWithPassHierarchy(AZStd::vector{"Parent1", "Parent3", "pass1"}); EXPECT_FALSE(filter1.Matches(pass.get())); // Mis-matching name - PassHierarchyFilter filter2(AZStd::vector{"Parent1", "pass1"}); + PassFilter filter2 = PassFilter::CreateWithPassHierarchy(AZStd::vector{"Parent1", "pass1"}); EXPECT_FALSE(filter2.Matches(parent1.get())); } } + + TEST_F(PassTests, PassFilter_Empty_Success) + { + m_data->AddPassTemplatesToLibrary(); + + // create a pass tree + Ptr pass = m_passSystem->CreatePassFromClass(Name("Pass"), Name("pass1")); + Ptr parent1 = m_passSystem->CreatePassFromTemplate(Name("ParentPass"), Name("parent1")); + Ptr parent2 = m_passSystem->CreatePassFromTemplate(Name("ParentPass"), Name("parent2")); + Ptr parent3 = m_passSystem->CreatePassFromTemplate(Name("ParentPass"), Name("parent3")); + + parent3->AsParent()->AddChild(parent2); + parent2->AsParent()->AddChild(parent1); + parent1->AsParent()->AddChild(pass); + + PassFilter filter; + + // Any pass can match an empty filter + EXPECT_TRUE(filter.Matches(pass.get())); + EXPECT_TRUE(filter.Matches(parent1.get())); + EXPECT_TRUE(filter.Matches(parent2.get())); + EXPECT_TRUE(filter.Matches(parent3.get())); + } + + TEST_F(PassTests, PassFilter_PassClass_Success) + { + m_data->AddPassTemplatesToLibrary(); + + // create a pass tree + Ptr pass = m_passSystem->CreatePassFromClass(Name("Pass"), Name("pass1")); + Ptr depthPass = m_passSystem->CreatePassFromTemplate(Name("DepthPrePass"), Name("depthPass")); + Ptr parent1 = m_passSystem->CreatePassFromTemplate(Name("ParentPass"), Name("parent1")); + + parent1->AsParent()->AddChild(pass); + parent1->AsParent()->AddChild(depthPass); + + PassFilter filter1 = PassFilter::CreateWithPassClass(); + + EXPECT_TRUE(filter1.Matches(pass.get())); + EXPECT_FALSE(filter1.Matches(parent1.get())); + + PassFilter filter2 = PassFilter::CreateWithPassClass(); + EXPECT_FALSE(filter2.Matches(pass.get())); + EXPECT_TRUE(filter2.Matches(parent1.get())); + } + + TEST_F(PassTests, PassFilter_PassTemplate_Success) + { + m_data->AddPassTemplatesToLibrary(); + + // create a pass tree + Ptr childPass = m_passSystem->CreatePassFromClass(Name("Pass"), Name("pass1")); + Ptr parent1 = m_passSystem->CreatePassFromTemplate(Name("ParentPass"), Name("parent1")); + + PassFilter filter1 = PassFilter::CreateWithTemplateName(Name("Pass"), (Scene*) nullptr); + // childPass doesn't have a template + EXPECT_FALSE(filter1.Matches(childPass.get())); + + PassFilter filter2 = PassFilter::CreateWithTemplateName(Name("ParentPass"), (Scene*) nullptr); + EXPECT_TRUE(filter2.Matches(parent1.get())); + } + + TEST_F(PassTests, ForEachPass_PassTemplateFilter_Success) + { + m_data->AddPassTemplatesToLibrary(); + + // create a pass tree + Ptr pass = m_passSystem->CreatePassFromClass(Name("Pass"), Name("pass1")); + Ptr parent1 = m_passSystem->CreatePassFromTemplate(Name("ParentPass"), Name("parent1")); + Ptr parent2 = m_passSystem->CreatePassFromTemplate(Name("ParentPass"), Name("parent2")); + Ptr parent3 = m_passSystem->CreatePassFromTemplate(Name("ParentPass"), Name("parent3")); + + parent3->AsParent()->AddChild(parent2); + parent2->AsParent()->AddChild(parent1); + parent1->AsParent()->AddChild(pass); + + // Create render pipeline + const RPI::PipelineViewTag viewTag{ "viewTag1" }; + RPI::RenderPipelineDescriptor desc; + desc.m_mainViewTagName = viewTag.GetStringView(); + desc.m_name = "TestPipeline"; + RPI::RenderPipelinePtr pipeline = RPI::RenderPipeline::CreateRenderPipeline(desc); + Ptr parent4 = m_passSystem->CreatePassFromTemplate(Name("ParentPass"), Name("parent4")); + pipeline->GetRootPass()->AddChild(parent4); + + Name templateName = Name("ParentPass"); + PassFilter filter1 = PassFilter::CreateWithTemplateName(templateName, (RenderPipeline*)nullptr); + + int count = 0; + m_passSystem->ForEachPass(filter1, [&count, templateName](RPI::Pass* pass) -> PassFilterExecutionFlow + { + EXPECT_TRUE(pass->GetPassTemplate()->m_name == templateName); + count++; + return PassFilterExecutionFlow::ContinueVisitingPasses; + }); + + // three from CreatePassFromTemplate() calls and one from Render Pipeline. + EXPECT_TRUE(count == 4); + + count = 0; + m_passSystem->ForEachPass(filter1, [&count, templateName](RPI::Pass* pass) -> PassFilterExecutionFlow + { + EXPECT_TRUE(pass->GetPassTemplate()->m_name == templateName); + count++; + return PassFilterExecutionFlow::StopVisitingPasses; + }); + EXPECT_TRUE(count == 1); + + PassFilter filter2 = PassFilter::CreateWithTemplateName(templateName, pipeline.get()); + count = 0; + m_passSystem->ForEachPass(filter2, [&count]([[maybe_unused]] RPI::Pass* pass) -> PassFilterExecutionFlow + { + count++; + return PassFilterExecutionFlow::ContinueVisitingPasses; + }); + + // only the ParentPass in the render pipeline was found + EXPECT_TRUE(count == 1); + + } } diff --git a/Gems/Atom/RPI/Code/atom_rpi_tests_files.cmake b/Gems/Atom/RPI/Code/atom_rpi_tests_files.cmake index e99e4e456b..5d67948ac8 100644 --- a/Gems/Atom/RPI/Code/atom_rpi_tests_files.cmake +++ b/Gems/Atom/RPI/Code/atom_rpi_tests_files.cmake @@ -10,8 +10,6 @@ set(FILES Tests/Buffer/BufferTests.cpp Tests/Common/AssetManagerTestFixture.cpp Tests/Common/AssetManagerTestFixture.h - Tests/Common/AssetSystemStub.cpp - Tests/Common/AssetSystemStub.h Tests/Common/ErrorMessageFinder.cpp Tests/Common/ErrorMessageFinder.h Tests/Common/ErrorMessageFinderTests.cpp diff --git a/Gems/Atom/RPI/Registry/atom_rpi.setreg b/Gems/Atom/RPI/Registry/atom_rpi.setreg index bcbade5d38..16ce8b3bd7 100644 --- a/Gems/Atom/RPI/Registry/atom_rpi.setreg +++ b/Gems/Atom/RPI/Registry/atom_rpi.setreg @@ -3,7 +3,7 @@ "Atom": { "RPI": { "Initialization": { - "CommonSrgsShaderAssetPath": "shader/sceneandviewsrgs.azshader", + "CommonSrgsShaderAssetPath": "shaders/sceneandviewsrgs.azshader", "ImageSystemDescriptor": { "AssetStreamingImagePoolSize": 2147483648, // 2 * 1024 * 1024 * 1024 "SystemStreamingImagePoolSize": 134217728, // 128 * 1024 * 1024 diff --git a/Gems/Atom/TestData/TestData/Materials/StandardPbrTestCases/009_Opacity_Opaque_DoubleSided.material b/Gems/Atom/TestData/TestData/Materials/StandardPbrTestCases/009_Opacity_Opaque_DoubleSided.material new file mode 100644 index 0000000000..a26bf6e045 --- /dev/null +++ b/Gems/Atom/TestData/TestData/Materials/StandardPbrTestCases/009_Opacity_Opaque_DoubleSided.material @@ -0,0 +1,14 @@ +{ + "description": "", + "materialType": "Materials/Types/StandardPBR.materialtype", + "parentMaterial": "", + "propertyLayoutVersion": 3, + "properties": { + "baseColor": { + "textureMap": "Textures/Default/checker_uv_basecolor.png" + }, + "general": { + "doubleSided": true + } + } +} \ No newline at end of file diff --git a/Gems/Atom/TestData/TestData/Materials/Types/AutoBrick_ForwardPass.shader b/Gems/Atom/TestData/TestData/Materials/Types/AutoBrick_ForwardPass.shader index 4f1c45d235..6418cc392e 100644 --- a/Gems/Atom/TestData/TestData/Materials/Types/AutoBrick_ForwardPass.shader +++ b/Gems/Atom/TestData/TestData/Materials/Types/AutoBrick_ForwardPass.shader @@ -24,7 +24,7 @@ }, "CompilerHints" : { - "DxcDisableOptimizations" : false + "DisableOptimizations" : false }, "ProgramSettings": diff --git a/Gems/Atom/TestData/TestData/Materials/Types/MinimalPBR_ForwardPass.shader b/Gems/Atom/TestData/TestData/Materials/Types/MinimalPBR_ForwardPass.shader index 13ba0ce547..f87a56daa2 100644 --- a/Gems/Atom/TestData/TestData/Materials/Types/MinimalPBR_ForwardPass.shader +++ b/Gems/Atom/TestData/TestData/Materials/Types/MinimalPBR_ForwardPass.shader @@ -24,7 +24,7 @@ }, "CompilerHints" : { - "DxcDisableOptimizations" : false + "DisableOptimizations" : false }, "ProgramSettings": diff --git a/Gems/Atom/TestData/TestData/Objects/plane.fbx b/Gems/Atom/TestData/TestData/Objects/plane.fbx new file mode 100644 index 0000000000..b274bfa282 --- /dev/null +++ b/Gems/Atom/TestData/TestData/Objects/plane.fbx @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0a1f8d75dcd85e8b4aa57f6c0c81af0300ff96915ba3c2b591095c215d5e1d8c +size 12072 diff --git a/Gems/Atom/TestData/TestData/Objects/tube.fbx b/Gems/Atom/TestData/TestData/Objects/tube.fbx new file mode 100644 index 0000000000..f9034e7641 --- /dev/null +++ b/Gems/Atom/TestData/TestData/Objects/tube.fbx @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b2ecc32cd3052f3cb5836c8be7bf5cba54d98f46e6a0eeac95aaef00a123411a +size 27340 diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/CMakeLists.txt b/Gems/Atom/Tools/AtomToolsFramework/Code/CMakeLists.txt index e64c9b80e7..40c8d7956e 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/CMakeLists.txt +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/CMakeLists.txt @@ -61,3 +61,31 @@ ly_add_target( PRIVATE Gem::AtomToolsFramework.Static ) + +################################################################################ +# Tests +################################################################################ +if(PAL_TRAIT_BUILD_TESTS_SUPPORTED) + + ly_add_target( + NAME AtomToolsFramework.Tests ${PAL_TRAIT_TEST_TARGET_TYPE} + NAMESPACE Gem + FILES_CMAKE + atomtoolsframework_tests_files.cmake + INCLUDE_DIRECTORIES + PRIVATE + . + Tests + BUILD_DEPENDENCIES + PRIVATE + AZ::AzTest + AZ::AzTestShared + Gem::AtomToolsFramework.Static + Gem::Atom_Utils.TestUtils.Static + ) + + ly_add_googletest( + NAME Gem::AtomToolsFramework.Tests + ) + +endif() \ No newline at end of file diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Util/MaterialPropertyUtil.h b/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Util/MaterialPropertyUtil.h index 333796493d..08f59f9e0b 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Util/MaterialPropertyUtil.h +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Util/MaterialPropertyUtil.h @@ -7,11 +7,12 @@ */ #pragma once -#include -#include #include #include #include +#include +#include +#include namespace AzToolsFramework { @@ -35,12 +36,31 @@ namespace AtomToolsFramework //! Convert and assign material property meta data fields to editor dynamic property configuration void ConvertToPropertyConfig(AtomToolsFramework::DynamicPropertyConfig& propertyConfig, const AZ::RPI::MaterialPropertyDynamicMetadata& propertyMetaData); - //! Convert and assign editor dynamic property configuration fields to material property meta data + //! Convert and assign editor dynamic property configuration fields to material property meta data void ConvertToPropertyMetaData(AZ::RPI::MaterialPropertyDynamicMetadata& propertyMetaData, const AtomToolsFramework::DynamicPropertyConfig& propertyConfig); //! Compare equality of data types and values of editor property stored in AZStd::any bool ArePropertyValuesEqual(const AZStd::any& valueA, const AZStd::any& valueB); + //! Convert the property value into the format that will be stored in the source data + //! This is primarily needed to support conversions of special types like enums and images + //! @param exportPath absolute path of the file being saved + //! @param propertyDefinition describes type information and other details about propertyValue + //! @param propertyValue the value being converted before saving + bool ConvertToExportFormat( + const AZStd::string& exportPath, + const AZ::RPI::MaterialTypeSourceData::PropertyDefinition& propertyDefinition, + AZ::RPI::MaterialPropertyValue& propertyValue); + + //! Generate a file path from the exported file to the external reference. + //! This function returns a relative path from the export file to the reference file. + //! If the relative path is too different or distant from the export path then we return the asset folder relative path. + //! @param exportPath absolute path of the file being saved + //! @param referencePath absolute path of a file that will be treated as an external reference + //! @param maxPathDepth the maximum relative depth or number of parent or child folders between the export path and the reference path + AZStd::string GetExteralReferencePath( + const AZStd::string& exportPath, const AZStd::string& referencePath, const uint32_t maxPathDepth = 2); + //! Traverse up the instance data node hierarchy to find the containing dynamic property object const AtomToolsFramework::DynamicProperty* FindDynamicPropertyForInstanceDataNode(const AzToolsFramework::InstanceDataNode* pNode); } // namespace AtomToolsFramework diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Application/AtomToolsApplication.cpp b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Application/AtomToolsApplication.cpp index e8ec77e7bd..ba3bfe4718 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Application/AtomToolsApplication.cpp +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Application/AtomToolsApplication.cpp @@ -175,8 +175,6 @@ namespace AtomToolsFramework AzToolsFramework::AssetBrowser::AssetDatabaseLocationNotificationBus::Broadcast( &AzToolsFramework::AssetBrowser::AssetDatabaseLocationNotifications::OnDatabaseInitialized); - AZ::Data::AssetCatalogRequestBus::Broadcast(&AZ::Data::AssetCatalogRequestBus::Events::LoadCatalog, "@products@/assetcatalog.xml"); - if (!AZ::RPI::RPISystemInterface::Get()->IsInitialized()) { AZ::RPI::RPISystemInterface::Get()->InitializeSystemAssets(); diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/PreviewRenderer/PreviewRenderer.cpp b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/PreviewRenderer/PreviewRenderer.cpp index 2b39a87623..27d468e64b 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/PreviewRenderer/PreviewRenderer.cpp +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/PreviewRenderer/PreviewRenderer.cpp @@ -43,6 +43,7 @@ namespace AtomToolsFramework &PreviewerFeatureProcessorProviderBus::Handler::GetRequiredFeatureProcessors, featureProcessors); AZ::RPI::SceneDescriptor sceneDesc; + sceneDesc.m_nameId = AZ::Name("PreviewRenderer"); sceneDesc.m_featureProcessorNames.assign(featureProcessors.begin(), featureProcessors.end()); m_scene = AZ::RPI::Scene::CreateScene(sceneDesc); diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/PreviewRenderer/PreviewRendererSystemComponent.cpp b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/PreviewRenderer/PreviewRendererSystemComponent.cpp index fc5b31297a..d46e7b27be 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/PreviewRenderer/PreviewRendererSystemComponent.cpp +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/PreviewRenderer/PreviewRendererSystemComponent.cpp @@ -47,30 +47,27 @@ namespace AtomToolsFramework void PreviewRendererSystemComponent::Activate() { - AzFramework::AssetCatalogEventBus::Handler::BusConnect(); AzFramework::ApplicationLifecycleEvents::Bus::Handler::BusConnect(); PreviewRendererSystemRequestBus::Handler::BusConnect(); + + AZ::TickBus::QueueFunction( + [this]() + { + if (!m_previewRenderer) + { + m_previewRenderer.reset(aznew AtomToolsFramework::PreviewRenderer( + "PreviewRendererSystemComponent Preview Scene", "PreviewRendererSystemComponent Preview Pipeline")); + } + }); } void PreviewRendererSystemComponent::Deactivate() { PreviewRendererSystemRequestBus::Handler::BusDisconnect(); AzFramework::ApplicationLifecycleEvents::Bus::Handler::BusDisconnect(); - AzFramework::AssetCatalogEventBus::Handler::BusDisconnect(); m_previewRenderer.reset(); } - void PreviewRendererSystemComponent::OnCatalogLoaded([[maybe_unused]] const char* catalogFile) - { - AZ::TickBus::QueueFunction([this](){ - if (!m_previewRenderer) - { - m_previewRenderer.reset(aznew AtomToolsFramework::PreviewRenderer( - "PreviewRendererSystemComponent Preview Scene", "PreviewRendererSystemComponent Preview Pipeline")); - } - }); - } - void PreviewRendererSystemComponent::OnApplicationAboutToStop() { m_previewRenderer.reset(); diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/PreviewRenderer/PreviewRendererSystemComponent.h b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/PreviewRenderer/PreviewRendererSystemComponent.h index 8110d84794..0b145bbdf1 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/PreviewRenderer/PreviewRendererSystemComponent.h +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/PreviewRenderer/PreviewRendererSystemComponent.h @@ -9,7 +9,6 @@ #pragma once #include -#include #include #include #include @@ -19,7 +18,6 @@ namespace AtomToolsFramework //! System component that manages a global PreviewRenderer. class PreviewRendererSystemComponent final : public AZ::Component - , public AzFramework::AssetCatalogEventBus::Handler , public AzFramework::ApplicationLifecycleEvents::Bus::Handler , public PreviewRendererSystemRequestBus::Handler { @@ -38,9 +36,6 @@ namespace AtomToolsFramework void Deactivate() override; private: - // AzFramework::AssetCatalogEventBus::Handler overrides ... - void OnCatalogLoaded(const char* catalogFile) override; - // AzFramework::ApplicationLifecycleEvents overrides... void OnApplicationAboutToStop() override; diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Util/MaterialPropertyUtil.cpp b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Util/MaterialPropertyUtil.cpp index 3f8a173918..c49cd3fafb 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Util/MaterialPropertyUtil.cpp +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Util/MaterialPropertyUtil.cpp @@ -6,9 +6,10 @@ * */ -#include #include +#include +#include #include #include #include @@ -18,6 +19,7 @@ #include #include #include +#include #include namespace AtomToolsFramework @@ -163,6 +165,88 @@ namespace AtomToolsFramework return false; } + bool ConvertToExportFormat( + const AZStd::string& exportPath, + const AZ::RPI::MaterialTypeSourceData::PropertyDefinition& propertyDefinition, + AZ::RPI::MaterialPropertyValue& propertyValue) + { + if (propertyDefinition.m_dataType == AZ::RPI::MaterialPropertyDataType::Enum && propertyValue.Is()) + { + const uint32_t index = propertyValue.GetValue(); + if (index >= propertyDefinition.m_enumValues.size()) + { + AZ_Error("AtomToolsFramework", false, "Invalid value for material enum property: '%s'.", propertyDefinition.m_name.c_str()); + return false; + } + + propertyValue = propertyDefinition.m_enumValues[index]; + return true; + } + + // Image asset references must be converted from asset IDs to a relative source file path + if (propertyDefinition.m_dataType == AZ::RPI::MaterialPropertyDataType::Image) + { + if (propertyValue.Is>()) + { + const auto& imageAsset = propertyValue.GetValue>(); + const auto& imagePath = AZ::RPI::AssetUtils::GetSourcePathByAssetId(imageAsset.GetId()); + propertyValue = GetExteralReferencePath(exportPath, imagePath); + return true; + } + + if (propertyValue.Is>()) + { + const auto& image = propertyValue.GetValue>(); + const auto& imagePath = image ? AZ::RPI::AssetUtils::GetSourcePathByAssetId(image->GetAssetId()) : ""; + propertyValue = GetExteralReferencePath(exportPath, imagePath); + return true; + } + } + + return true; + } + + AZStd::string GetExteralReferencePath(const AZStd::string& exportPath, const AZStd::string& referencePath, const uint32_t maxPathDepth) + { + if (referencePath.empty()) + { + return {}; + } + + AZ::IO::BasicPath exportFolder(exportPath); + exportFolder.RemoveFilename(); + + const AZStd::string relativePath = AZ::IO::PathView(referencePath).LexicallyRelative(exportFolder).StringAsPosix(); + + // Count the difference in depth between the export file path and the referenced file path. + uint32_t parentFolderCount = 0; + AZStd::string::size_type pos = 0; + const AZStd::string parentFolderToken = ".."; + while ((pos = relativePath.find(parentFolderToken, pos)) != AZStd::string::npos) + { + parentFolderCount++; + pos += parentFolderToken.length(); + } + + // If the difference in depth is too great then revert to using the asset folder relative path. + // We could change this to only use relative paths for references in subfolders. + if (parentFolderCount > maxPathDepth) + { + AZStd::string watchFolder; + AZ::Data::AssetInfo assetInfo; + bool sourceInfoFound = false; + AzToolsFramework::AssetSystemRequestBus::BroadcastResult( + sourceInfoFound, &AzToolsFramework::AssetSystemRequestBus::Events::GetSourceInfoBySourcePath, referencePath.c_str(), + assetInfo, watchFolder); + if (sourceInfoFound) + { + return assetInfo.m_relativePath; + } + } + + return relativePath; + } + const AtomToolsFramework::DynamicProperty* FindDynamicPropertyForInstanceDataNode(const AzToolsFramework::InstanceDataNode* pNode) { // Traverse up the hierarchy from the input node to search for an instance corresponding to material inspector property @@ -172,7 +256,8 @@ namespace AtomToolsFramework const AZ::SerializeContext::ClassData* classData = currentNode->GetClassMetadata(); if (context && classData) { - if (context->CanDowncast(classData->m_typeId, azrtti_typeid(), classData->m_azRtti, nullptr)) + if (context->CanDowncast( + classData->m_typeId, azrtti_typeid(), classData->m_azRtti, nullptr)) { return static_cast(currentNode->FirstInstance()); } diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Tests/AtomToolsFrameworkTest.cpp b/Gems/Atom/Tools/AtomToolsFramework/Code/Tests/AtomToolsFrameworkTest.cpp index aee8e2a775..df16cfbc43 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Tests/AtomToolsFrameworkTest.cpp +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Tests/AtomToolsFrameworkTest.cpp @@ -7,25 +7,71 @@ */ #include +#include +#include -class AtomToolsFrameworkTest - : public ::testing::Test +namespace UnitTest { -protected: - void SetUp() override + class AtomToolsFrameworkTest : public ::testing::Test { + protected: + void SetUp() override + { + if (!AZ::AllocatorInstance::IsReady()) + { + AZ::AllocatorInstance::Create(AZ::SystemAllocator::Descriptor()); + } + m_assetSystemStub.Activate(); + + RegisterSourceAsset("objects/upgrades/materials/supercondor.material"); + RegisterSourceAsset("materials/condor.material"); + RegisterSourceAsset("materials/talisman.material"); + RegisterSourceAsset("materials/city.material"); + RegisterSourceAsset("materials/totem.material"); + RegisterSourceAsset("textures/orange.png"); + RegisterSourceAsset("textures/red.png"); + RegisterSourceAsset("textures/gold.png"); + RegisterSourceAsset("textures/fuzz.png"); + } + + void TearDown() override + { + m_assetSystemStub.Deactivate(); + + if (AZ::AllocatorInstance::IsReady()) + { + AZ::AllocatorInstance::Destroy(); + } + } + + void RegisterSourceAsset(const AZStd::string& path) + { + const AZ::IO::BasicPath assetRootPath = AZ::IO::PathView(m_assetRoot).LexicallyNormal(); + const AZ::IO::BasicPath normalizedPath = AZ::IO::BasicPath(assetRootPath).Append(path).LexicallyNormal(); + + AZ::Data::AssetInfo assetInfo = {}; + assetInfo.m_assetId = AZ::Uuid::CreateRandom(); + assetInfo.m_relativePath = normalizedPath.LexicallyRelative(assetRootPath).StringAsPosix(); + m_assetSystemStub.RegisterSourceInfo(normalizedPath.StringAsPosix().c_str(), assetInfo, assetRootPath.StringAsPosix().c_str()); + } + + static constexpr const char* m_assetRoot = "d:/project/assets/"; + AssetSystemStub m_assetSystemStub; + }; + + TEST_F(AtomToolsFrameworkTest, GetExteralReferencePath_Succeeds) + { + ASSERT_EQ(AtomToolsFramework::GetExteralReferencePath("", "", 2), ""); + ASSERT_EQ(AtomToolsFramework::GetExteralReferencePath("d:/project/assets/materials/condor.material", "", 2), ""); + ASSERT_EQ(AtomToolsFramework::GetExteralReferencePath("d:/project/assets/materials/talisman.material", "", 2), ""); + ASSERT_EQ(AtomToolsFramework::GetExteralReferencePath("d:/project/assets/materials/talisman.material", "d:/project/assets/textures/gold.png", 2), "../textures/gold.png"); + ASSERT_EQ(AtomToolsFramework::GetExteralReferencePath("d:/project/assets/materials/talisman.material", "d:/project/assets/textures/gold.png", 0), "textures/gold.png"); + ASSERT_EQ(AtomToolsFramework::GetExteralReferencePath("d:/project/assets/objects/upgrades/materials/supercondor.material", "d:/project/assets/materials/condor.material", 3), "../../../materials/condor.material"); + ASSERT_EQ(AtomToolsFramework::GetExteralReferencePath("d:/project/assets/objects/upgrades/materials/supercondor.material", "d:/project/assets/materials/condor.material", 2), "materials/condor.material"); + ASSERT_EQ(AtomToolsFramework::GetExteralReferencePath("d:/project/assets/objects/upgrades/materials/supercondor.material", "d:/project/assets/materials/condor.material", 1), "materials/condor.material"); + ASSERT_EQ(AtomToolsFramework::GetExteralReferencePath("d:/project/assets/objects/upgrades/materials/supercondor.material", "d:/project/assets/materials/condor.material", 0), "materials/condor.material"); } - void TearDown() override - { - - } -}; - -TEST_F(AtomToolsFrameworkTest, SanityTest) -{ - ASSERT_TRUE(true); -} - -AZ_UNIT_TEST_HOOK(DEFAULT_UNIT_TEST_ENV); + AZ_UNIT_TEST_HOOK(DEFAULT_UNIT_TEST_ENV); +} // namespace UnitTest diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/atomtoolsframework_tests_files.cmake b/Gems/Atom/Tools/AtomToolsFramework/Code/atomtoolsframework_tests_files.cmake new file mode 100644 index 0000000000..bd9ad9b3d8 --- /dev/null +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/atomtoolsframework_tests_files.cmake @@ -0,0 +1,11 @@ +# +# Copyright (c) Contributors to the Open 3D Engine Project. +# For complete copyright and license terms please see the LICENSE at the root of this distribution. +# +# SPDX-License-Identifier: Apache-2.0 OR MIT +# +# + +set(FILES + Tests/AtomToolsFrameworkTest.cpp +) \ No newline at end of file diff --git a/Gems/Atom/Tools/MaterialEditor/Assets/MaterialEditor/ViewportModels/_dev_shaderball_00_basecolor.png b/Gems/Atom/Tools/MaterialEditor/Assets/MaterialEditor/ViewportModels/_dev_shaderball_00_basecolor.png index 415ca3e521..07e240baf9 100644 --- a/Gems/Atom/Tools/MaterialEditor/Assets/MaterialEditor/ViewportModels/_dev_shaderball_00_basecolor.png +++ b/Gems/Atom/Tools/MaterialEditor/Assets/MaterialEditor/ViewportModels/_dev_shaderball_00_basecolor.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:93a7e033d9fb0fcac221647322bde03716643d789390f79078c4fcc37ecfd005 -size 68327 +oid sha256:513f47f6fea5105f603170a8881b7e3b1cd2c4258636d64a6399c725032b500d +size 38689 diff --git a/Gems/Atom/Tools/MaterialEditor/Code/Source/Document/MaterialDocument.cpp b/Gems/Atom/Tools/MaterialEditor/Code/Source/Document/MaterialDocument.cpp index 98af749261..26c2a6145e 100644 --- a/Gems/Atom/Tools/MaterialEditor/Code/Source/Document/MaterialDocument.cpp +++ b/Gems/Atom/Tools/MaterialEditor/Code/Source/Document/MaterialDocument.cpp @@ -230,18 +230,13 @@ namespace MaterialEditor // create source data from properties MaterialSourceData sourceData; - sourceData.m_materialType = m_materialSourceData.m_materialType; - sourceData.m_parentMaterial = m_materialSourceData.m_parentMaterial; - - AZ_Assert(m_materialAsset && m_materialAsset->GetMaterialTypeAsset(), "When IsOpen() is true, these assets should not be null."); sourceData.m_materialTypeVersion = m_materialAsset->GetMaterialTypeAsset()->GetVersion(); - - // Force save data to store forward slashes - AzFramework::StringFunc::Replace(sourceData.m_materialType, "\\", "/"); - AzFramework::StringFunc::Replace(sourceData.m_parentMaterial, "\\", "/"); + sourceData.m_materialType = AtomToolsFramework::GetExteralReferencePath(m_absolutePath, m_materialSourceData.m_materialType); + sourceData.m_parentMaterial = AtomToolsFramework::GetExteralReferencePath(m_absolutePath, m_materialSourceData.m_parentMaterial); // populate sourceData with modified or overwritten properties - const bool savedProperties = SavePropertiesToSourceData(sourceData, [](const AtomToolsFramework::DynamicProperty& property) { + const bool savedProperties = SavePropertiesToSourceData(m_absolutePath, sourceData, [](const AtomToolsFramework::DynamicProperty& property) + { return !AtomToolsFramework::ArePropertyValuesEqual(property.GetValue(), property.GetConfig().m_parentValue); }); @@ -304,18 +299,13 @@ namespace MaterialEditor // create source data from properties MaterialSourceData sourceData; - sourceData.m_materialType = m_materialSourceData.m_materialType; - sourceData.m_parentMaterial = m_materialSourceData.m_parentMaterial; - - AZ_Assert(m_materialAsset && m_materialAsset->GetMaterialTypeAsset(), "When IsOpen() is true, these assets should not be null."); sourceData.m_materialTypeVersion = m_materialAsset->GetMaterialTypeAsset()->GetVersion(); - - // Force save data to store forward slashes - AzFramework::StringFunc::Replace(sourceData.m_materialType, "\\", "/"); - AzFramework::StringFunc::Replace(sourceData.m_parentMaterial, "\\", "/"); + sourceData.m_materialType = AtomToolsFramework::GetExteralReferencePath(normalizedSavePath, m_materialSourceData.m_materialType); + sourceData.m_parentMaterial = AtomToolsFramework::GetExteralReferencePath(normalizedSavePath, m_materialSourceData.m_parentMaterial); // populate sourceData with modified or overwritten properties - const bool savedProperties = SavePropertiesToSourceData(sourceData, [](const AtomToolsFramework::DynamicProperty& property) { + const bool savedProperties = SavePropertiesToSourceData(normalizedSavePath, sourceData, [](const AtomToolsFramework::DynamicProperty& property) + { return !AtomToolsFramework::ArePropertyValuesEqual(property.GetValue(), property.GetConfig().m_parentValue); }); @@ -377,23 +367,18 @@ namespace MaterialEditor // create source data from properties MaterialSourceData sourceData; - sourceData.m_materialType = m_materialSourceData.m_materialType; - - AZ_Assert(m_materialAsset && m_materialAsset->GetMaterialTypeAsset(), "When IsOpen() is true, these assets should not be null."); sourceData.m_materialTypeVersion = m_materialAsset->GetMaterialTypeAsset()->GetVersion(); + sourceData.m_materialType = AtomToolsFramework::GetExteralReferencePath(normalizedSavePath, m_materialSourceData.m_materialType); // Only assign a parent path if the source was a .material if (AzFramework::StringFunc::Path::IsExtension(m_relativePath.c_str(), MaterialSourceData::Extension)) { - sourceData.m_parentMaterial = m_relativePath; + sourceData.m_parentMaterial = AtomToolsFramework::GetExteralReferencePath(normalizedSavePath, m_absolutePath); } - // Force save data to store forward slashes - AzFramework::StringFunc::Replace(sourceData.m_materialType, "\\", "/"); - AzFramework::StringFunc::Replace(sourceData.m_parentMaterial, "\\", "/"); - // populate sourceData with modified properties - const bool savedProperties = SavePropertiesToSourceData(sourceData, [](const AtomToolsFramework::DynamicProperty& property) { + const bool savedProperties = SavePropertiesToSourceData(normalizedSavePath, sourceData, [](const AtomToolsFramework::DynamicProperty& property) + { return !AtomToolsFramework::ArePropertyValuesEqual(property.GetValue(), property.GetConfig().m_originalValue); }); @@ -590,7 +575,8 @@ namespace MaterialEditor } } - bool MaterialDocument::SavePropertiesToSourceData(AZ::RPI::MaterialSourceData& sourceData, PropertyFilterFunction propertyFilter) const + bool MaterialDocument::SavePropertiesToSourceData( + const AZStd::string& exportPath, AZ::RPI::MaterialSourceData& sourceData, PropertyFilterFunction propertyFilter) const { using namespace AZ; using namespace RPI; @@ -598,7 +584,7 @@ namespace MaterialEditor bool result = true; // populate sourceData with properties that meet the filter - m_materialTypeSourceData.EnumerateProperties([this, &sourceData, &propertyFilter, &result](const AZStd::string& groupName, const AZStd::string& propertyName, const auto& propertyDefinition) { + m_materialTypeSourceData.EnumerateProperties([&](const AZStd::string& groupName, const AZStd::string& propertyName, const auto& propertyDefinition) { const MaterialPropertyId propertyId(groupName, propertyName); @@ -608,7 +594,7 @@ namespace MaterialEditor MaterialPropertyValue propertyValue = AtomToolsFramework::ConvertToRuntimeType(it->second.GetValue()); if (propertyValue.IsValid()) { - if (!m_materialTypeSourceData.ConvertPropertyValueToSourceDataFormat(propertyDefinition, propertyValue)) + if (!AtomToolsFramework::ConvertToExportFormat(exportPath, propertyDefinition, propertyValue)) { AZ_Error("MaterialDocument", false, "Material document property could not be converted: '%s' in '%s'.", propertyId.GetFullName().GetCStr(), m_absolutePath.c_str()); result = false; @@ -663,8 +649,6 @@ namespace MaterialEditor return false; } - AZStd::string materialTypeSourceFilePath; - // The material document and inspector are constructed from source data if (AzFramework::StringFunc::Path::IsExtension(m_absolutePath.c_str(), MaterialSourceData::Extension)) { @@ -675,13 +659,24 @@ namespace MaterialEditor return false; } - // We must also always load the material type data for a complete, ordered set of the - // groups and properties that will be needed for comparison and building the inspector - materialTypeSourceFilePath = AssetUtils::ResolvePathReference(m_absolutePath, m_materialSourceData.m_materialType); - auto materialTypeOutcome = MaterialUtils::LoadMaterialTypeSourceData(materialTypeSourceFilePath); + // We always need the absolute path for the material type and parent material to load source data and resolving + // relative paths when saving. This will convert and store them as absolute paths for use within the document. + if (!m_materialSourceData.m_parentMaterial.empty()) + { + m_materialSourceData.m_parentMaterial = + AssetUtils::ResolvePathReference(m_absolutePath, m_materialSourceData.m_parentMaterial); + } + + if (!m_materialSourceData.m_materialType.empty()) + { + m_materialSourceData.m_materialType = AssetUtils::ResolvePathReference(m_absolutePath, m_materialSourceData.m_materialType); + } + + // Load the material type source data which provides the layout and default values of all of the properties + auto materialTypeOutcome = MaterialUtils::LoadMaterialTypeSourceData(m_materialSourceData.m_materialType); if (!materialTypeOutcome.IsSuccess()) { - AZ_Error("MaterialDocument", false, "Material type source data could not be loaded: '%s'.", materialTypeSourceFilePath.c_str()); + AZ_Error("MaterialDocument", false, "Material type source data could not be loaded: '%s'.", m_materialSourceData.m_materialType.c_str()); return false; } m_materialTypeSourceData = materialTypeOutcome.GetValue(); @@ -694,10 +689,10 @@ namespace MaterialEditor } else if (AzFramework::StringFunc::Path::IsExtension(m_absolutePath.c_str(), MaterialTypeSourceData::Extension)) { - materialTypeSourceFilePath = m_absolutePath; - - // Load the material type source data, which will be used for enumerating properties and building material source data - auto materialTypeOutcome = MaterialUtils::LoadMaterialTypeSourceData(materialTypeSourceFilePath); + // A material document can be created or loaded from material or material type source data. If we are attempting to load + // material type source data then the material source data object can be created just by referencing the document path as the + // material type path. + auto materialTypeOutcome = MaterialUtils::LoadMaterialTypeSourceData(m_absolutePath); if (!materialTypeOutcome.IsSuccess()) { AZ_Error("MaterialDocument", false, "Material type source data could not be loaded: '%s'.", m_absolutePath.c_str()); @@ -705,9 +700,8 @@ namespace MaterialEditor } m_materialTypeSourceData = materialTypeOutcome.GetValue(); - // The document represents a material, not a material type. - // If the input data is a material type file we have to generate the material source data by referencing it. - m_materialSourceData.m_materialType = m_relativePath; + // We are storing absolute paths in the loaded version of the source data so that the files can be resolved at all times. + m_materialSourceData.m_materialType = m_absolutePath; m_materialSourceData.m_parentMaterial.clear(); } else @@ -870,7 +864,8 @@ namespace MaterialEditor m_properties[propertyConfig.m_id] = AtomToolsFramework::DynamicProperty(propertyConfig); } - const MaterialFunctorSourceData::EditorContext editorContext = MaterialFunctorSourceData::EditorContext(materialTypeSourceFilePath, m_materialAsset->GetMaterialPropertiesLayout()); + const MaterialFunctorSourceData::EditorContext editorContext = + MaterialFunctorSourceData::EditorContext(m_materialSourceData.m_materialType, m_materialAsset->GetMaterialPropertiesLayout()); for (Ptr functorData : m_materialTypeSourceData.m_materialFunctorSourceData) { MaterialFunctorSourceData::FunctorResult result2 = functorData->CreateFunctor(editorContext); diff --git a/Gems/Atom/Tools/MaterialEditor/Code/Source/Document/MaterialDocument.h b/Gems/Atom/Tools/MaterialEditor/Code/Source/Document/MaterialDocument.h index 03997a2a91..14538ba867 100644 --- a/Gems/Atom/Tools/MaterialEditor/Code/Source/Document/MaterialDocument.h +++ b/Gems/Atom/Tools/MaterialEditor/Code/Source/Document/MaterialDocument.h @@ -110,7 +110,8 @@ namespace MaterialEditor void OnAssetReloaded(AZ::Data::Asset asset) override; ////////////////////////////////////////////////////////////////////////// - bool SavePropertiesToSourceData(AZ::RPI::MaterialSourceData& sourceData, PropertyFilterFunction propertyFilter) const; + bool SavePropertiesToSourceData( + const AZStd::string& exportPath, AZ::RPI::MaterialSourceData& sourceData, PropertyFilterFunction propertyFilter) const; bool OpenInternal(AZStd::string_view loadPath); diff --git a/Gems/Atom/Tools/MaterialEditor/Code/Source/Viewport/InputController/MaterialEditorViewportInputController.cpp b/Gems/Atom/Tools/MaterialEditor/Code/Source/Viewport/InputController/MaterialEditorViewportInputController.cpp index 932e2e7436..1784405ffa 100644 --- a/Gems/Atom/Tools/MaterialEditor/Code/Source/Viewport/InputController/MaterialEditorViewportInputController.cpp +++ b/Gems/Atom/Tools/MaterialEditor/Code/Source/Viewport/InputController/MaterialEditorViewportInputController.cpp @@ -279,9 +279,9 @@ namespace MaterialEditor // reset environment AZ::Transform iblTransform = AZ::Transform::CreateIdentity(); AZ::TransformBus::Event(m_iblEntityId, &AZ::TransformBus::Events::SetLocalTM, iblTransform); + const AZ::Matrix4x4 rotationMatrix = AZ::Matrix4x4::CreateIdentity(); - AZ::RPI::ScenePtr scene = AZ::RPI::RPISystemInterface::Get()->GetDefaultScene(); - auto skyBoxFeatureProcessorInterface = scene->GetFeatureProcessor(); + auto skyBoxFeatureProcessorInterface = AZ::RPI::Scene::GetFeatureProcessorForEntity(m_iblEntityId); skyBoxFeatureProcessorInterface->SetCubemapRotationMatrix(rotationMatrix); if (m_behavior) diff --git a/Gems/Atom/Tools/MaterialEditor/Code/Source/Viewport/InputController/RotateEnvironmentBehavior.cpp b/Gems/Atom/Tools/MaterialEditor/Code/Source/Viewport/InputController/RotateEnvironmentBehavior.cpp index 21d7dee51b..17fb3f3e52 100644 --- a/Gems/Atom/Tools/MaterialEditor/Code/Source/Viewport/InputController/RotateEnvironmentBehavior.cpp +++ b/Gems/Atom/Tools/MaterialEditor/Code/Source/Viewport/InputController/RotateEnvironmentBehavior.cpp @@ -25,8 +25,7 @@ namespace MaterialEditor m_iblEntityId, &MaterialEditorViewportInputControllerRequestBus::Handler::GetIblEntityId); AZ_Assert(m_iblEntityId.IsValid(), "Failed to find m_iblEntityId"); - AZ::RPI::ScenePtr scene = AZ::RPI::RPISystemInterface::Get()->GetDefaultScene(); - m_skyBoxFeatureProcessorInterface = scene->GetFeatureProcessor(); + m_skyBoxFeatureProcessorInterface = AZ::RPI::Scene::GetFeatureProcessorForEntity(m_iblEntityId); } void RotateEnvironmentBehavior::TickInternal(float x, float y, float z) diff --git a/Gems/Atom/Tools/MaterialEditor/Code/Source/Viewport/MaterialViewportRenderer.cpp b/Gems/Atom/Tools/MaterialEditor/Code/Source/Viewport/MaterialViewportRenderer.cpp index 6d6fd377e3..4ad87492e3 100644 --- a/Gems/Atom/Tools/MaterialEditor/Code/Source/Viewport/MaterialViewportRenderer.cpp +++ b/Gems/Atom/Tools/MaterialEditor/Code/Source/Viewport/MaterialViewportRenderer.cpp @@ -67,6 +67,7 @@ namespace MaterialEditor // Create and register a scene with all available feature processors AZ::RPI::SceneDescriptor sceneDesc; + sceneDesc.m_nameId = AZ::Name("MaterialViewport"); m_scene = AZ::RPI::Scene::CreateScene(sceneDesc); m_scene->EnableAllFeatureProcessors(); diff --git a/Gems/Atom/Utils/Code/CMakeLists.txt b/Gems/Atom/Utils/Code/CMakeLists.txt index 89bc8dd0a5..90d7ce501b 100644 --- a/Gems/Atom/Utils/Code/CMakeLists.txt +++ b/Gems/Atom/Utils/Code/CMakeLists.txt @@ -27,6 +27,27 @@ ly_add_target( 3rdParty::libpng ) +if(PAL_TRAIT_BUILD_HOST_TOOLS) + + ly_add_target( + NAME Atom_Utils.TestUtils.Static STATIC + NAMESPACE Gem + FILES_CMAKE + atom_utils_editor_files.cmake + INCLUDE_DIRECTORIES + PRIVATE + Source + PUBLIC + Include + BUILD_DEPENDENCIES + PRIVATE + AZ::AtomCore + AZ::AzCore + AZ::AzFramework + AZ::AzToolsFramework + ) +endif() + ################################################################################ # Tests ################################################################################ diff --git a/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiGpuProfiler.h b/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiGpuProfiler.h index 1bed2b5715..7413390357 100644 --- a/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiGpuProfiler.h +++ b/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiGpuProfiler.h @@ -249,7 +249,7 @@ namespace AZ // Controls how often the timestamp data is refreshed RefreshType m_refreshType = RefreshType::Realtime; - AZStd::sys_time_t m_lastUpdateTimeMicroSecond; + AZStd::sys_time_t m_lastUpdateTimeMicroSecond = 0; }; diff --git a/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiGpuProfiler.inl b/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiGpuProfiler.inl index 80dcace2df..405848aa35 100644 --- a/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiGpuProfiler.inl +++ b/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiGpuProfiler.inl @@ -651,7 +651,7 @@ namespace AZ if (m_refreshType == RefreshType::OncePerSecond) { auto now = AZStd::GetTimeNowMicroSecond(); - if (m_lastUpdateTimeMicroSecond == 0 || now - m_lastUpdateTimeMicroSecond > 1000000) + if (now - m_lastUpdateTimeMicroSecond > 1000000) { needEnable = true; m_lastUpdateTimeMicroSecond = now; diff --git a/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiPassTree.h b/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiPassTree.h index 12e9776ae2..0e942bca58 100644 --- a/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiPassTree.h +++ b/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiPassTree.h @@ -39,6 +39,8 @@ namespace AZ bool m_showAttachments = false; AZ::RPI::Pass* m_selectedPass = nullptr; + AZ::RPI::Pass* m_lastSelectedPass = nullptr; + AZ::Name m_selectedPassPath; AZ::RHI::AttachmentId m_attachmentId; AZ::Name m_slotName; bool m_selectedChanged = false; diff --git a/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiPassTree.inl b/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiPassTree.inl index fa649ed47b..55e457926a 100644 --- a/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiPassTree.inl +++ b/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiPassTree.inl @@ -31,7 +31,7 @@ namespace AZ::Render { - inline AZ::RPI::PassAttachment* FindPassAttachment(AZ::RPI::RenderPass* pass, AZ::RHI::AttachmentId attachmentId) + inline AZ::RPI::PassAttachment* FindPassAttachment(AZ::RPI::Pass* pass, AZ::RHI::AttachmentId attachmentId) { for (auto& binding : pass->GetAttachmentBindings()) { @@ -47,6 +47,10 @@ namespace AZ::Render { using namespace AZ; + // always set m_selectedPass to empty and use m_selectedPassPath to find it when render the pass tree + m_selectedPass = nullptr; + bool needSaveAttachment = false; + ImGui::SetNextWindowSize(ImVec2(200.f, 200.f), ImGuiCond_FirstUseEver); if (ImGui::Begin("PassTree View", &draw, ImGuiWindowFlags_None)) { @@ -83,60 +87,16 @@ namespace AZ::Render if (Scriptable_ImGui::Button("Save Attachment")) { - m_attachmentReadbackInfo = ""; - if (!m_readback) - { - m_readback = AZStd::make_shared(AZ::RHI::ScopeId{ "AttachmentReadback" }); - m_readback->SetCallback(AZStd::bind(&ImGuiPassTree::ReadbackCallback, this, AZStd::placeholders::_1)); - } - - if (m_selectedPass && !m_slotName.IsEmpty()) - { - bool readbackResult = m_selectedPass->ReadbackAttachment(m_readback, m_slotName); - if (!readbackResult) - { - AZ_Error("ImGuiPassTree", false, "Failed to readback attachment from pass [%s] slot [%s]", m_selectedPass->GetName().GetCStr(), m_slotName.GetCStr()); - } - } + needSaveAttachment = true; } ImGui::TextWrapped("%s", m_attachmentReadbackInfo.c_str()); } - if (m_previewAttachment && m_selectedChanged) - { - m_selectedChanged = false; - if (!m_attachmentId.IsEmpty() && m_selectedPass) - { - AZ::RPI::RenderPass* renderPass = azrtti_cast(m_selectedPass); - if (renderPass) - { - if (!m_previewPass->GetParent()) - { - RPI::PassSystemInterface::Get()->GetRootPass()->AddChild(m_previewPass); - } - AZ::RPI::PassAttachment* attachment = FindPassAttachment(renderPass, m_attachmentId); - if (attachment) - { - // Reset output attachment to empty so the preview will use pass's owner render pipeline's output - m_previewPass->SetOutputColorAttachment(nullptr); - m_previewPass->PreviewImageAttachmentForPass(renderPass, attachment); - } - } - else - { - m_previewPass->ClearPreviewAttachment(); - if (m_previewPass->GetParent()) - { - m_previewPass->QueueForRemoval(); - } - } - } - } - ImGui::End(); // Draw the hierarchical view + // It will assign m_seletedPass if there is a pass matches m_seletedPassPath ImGui::SetNextWindowPos(ImVec2(300, 60), ImGuiCond_FirstUseEver); ImGui::SetNextWindowSize(ImVec2(300, 500), ImGuiCond_FirstUseEver); if (ImGui::Begin("PassTree", nullptr, ImGuiWindowFlags_None)) @@ -144,6 +104,63 @@ namespace AZ::Render DrawTreeView(rootPass); } ImGui::End(); + + // It's possible that the pass pointer changed but selected pass path wasn't changed + if (m_selectedPass != m_lastSelectedPass) + { + m_selectedChanged = true; + if (m_selectedPass == nullptr) + { + m_selectedPassPath = AZ::Name{}; + } + } + m_lastSelectedPass = m_selectedPass; + + if (m_previewAttachment && m_selectedChanged) + { + m_selectedChanged = false; + if (!m_attachmentId.IsEmpty() && m_selectedPass) + { + if (!m_previewPass->GetParent()) + { + RPI::PassSystemInterface::Get()->GetRootPass()->AddChild(m_previewPass); + } + AZ::RPI::PassAttachment* attachment = FindPassAttachment(m_selectedPass, m_attachmentId); + if (attachment) + { + // Reset output attachment to empty so the preview will use pass's owner render pipeline's output + m_previewPass->SetOutputColorAttachment(nullptr); + m_previewPass->PreviewImageAttachmentForPass(m_selectedPass, attachment); + } + } + else + { + m_previewPass->ClearPreviewAttachment(); + if (m_previewPass->GetParent()) + { + m_previewPass->QueueForRemoval(); + } + } + } + + if (needSaveAttachment) + { + m_attachmentReadbackInfo = ""; + if (!m_readback) + { + m_readback = AZStd::make_shared(AZ::RHI::ScopeId{ "AttachmentReadback" }); + m_readback->SetCallback(AZStd::bind(&ImGuiPassTree::ReadbackCallback, this, AZStd::placeholders::_1)); + } + + if (m_selectedPass && !m_slotName.IsEmpty()) + { + bool readbackResult = m_selectedPass->ReadbackAttachment(m_readback, m_slotName); + if (!readbackResult) + { + AZ_Error("ImGuiPassTree", false, "Failed to readback attachment from pass [%s] slot [%s]", m_selectedPass->GetName().GetCStr(), m_slotName.GetCStr()); + } + } + } } inline void ImGuiPassTree::DrawPassAttachments(AZ::RPI::Pass* pass) @@ -202,6 +219,7 @@ namespace AZ::Render if (Scriptable_ImGui::Selectable(label.c_str(), m_attachmentId == binding.m_attachment->GetAttachmentId())) { + m_selectedPassPath = pass->GetPathName(); m_selectedPass = pass; m_attachmentId = binding.m_attachment->GetAttachmentId(); m_slotName = binding.m_name; @@ -232,9 +250,9 @@ namespace AZ::Render if (!m_showAttachments) { // Only draw the leaf pass as selectable if we are not showing attachments as its children - if (Scriptable_ImGui::Selectable(pass->GetName().GetCStr(), m_selectedPass == pass)) + if (Scriptable_ImGui::Selectable(pass->GetName().GetCStr(), m_selectedPassPath == pass->GetPathName())) { - m_selectedPass = pass; + m_selectedPassPath = pass->GetPathName(); m_attachmentId = AZ::RHI::AttachmentId{}; m_slotName = AZ::Name{}; m_selectedChanged = true; @@ -244,13 +262,13 @@ namespace AZ::Render { // Draw the pass as a tree node which has attachments as its children ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick | ImGuiTreeNodeFlags_DefaultOpen - | ((m_selectedPass == pass) ? ImGuiTreeNodeFlags_Selected : 0); + | ((m_selectedPassPath == pass->GetPathName()) ? ImGuiTreeNodeFlags_Selected : 0); bool nodeOpen = Scriptable_ImGui::TreeNodeEx(pass->GetName().GetCStr(), flags); if (ImGui::IsItemClicked()) { - m_selectedPass = pass; + m_selectedPassPath = pass->GetPathName(); m_attachmentId = AZ::RHI::AttachmentId{}; m_slotName = AZ::Name{}; m_selectedChanged = true; @@ -259,7 +277,6 @@ namespace AZ::Render if (nodeOpen) { DrawPassAttachments(pass); - Scriptable_ImGui::TreePop(); } } @@ -268,13 +285,13 @@ namespace AZ::Render { // For a ParentPasse, draw it as a tree node ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick | ImGuiTreeNodeFlags_DefaultOpen - | ((m_selectedPass == pass) ? ImGuiTreeNodeFlags_Selected : 0); + | ((m_selectedPassPath == pass->GetPathName()) ? ImGuiTreeNodeFlags_Selected : 0); bool nodeOpen = ImGui::TreeNodeEx(pass->GetName().GetCStr(), flags); if (ImGui::IsItemClicked()) { - m_selectedPass = pass; + m_selectedPassPath = pass->GetPathName(); m_attachmentId = AZ::RHI::AttachmentId{}; m_slotName = AZ::Name{}; m_selectedChanged = true; @@ -282,7 +299,10 @@ namespace AZ::Render if (nodeOpen) { - DrawPassAttachments(pass); + if (m_showAttachments) + { + DrawPassAttachments(pass); + } for (const auto& child : asParent->GetChildren()) { DrawTreeView(child.get()); @@ -296,6 +316,12 @@ namespace AZ::Render { ImGui::PopStyleColor(); } + + // set m_selectedPass if pass path matches + if (pass->GetPathName() == m_selectedPassPath) + { + m_selectedPass = pass; + } } inline void ImGuiPassTree::ReadbackCallback(const AZ::RPI::AttachmentReadback::ReadbackResult& readbackResult) @@ -364,7 +390,9 @@ namespace AZ::Render m_previewAttachment = false; m_showAttachments = false; + m_selectedPassPath = AZ::Name{}; m_selectedPass = nullptr; + m_lastSelectedPass = nullptr; m_attachmentId = AZ::RHI::AttachmentId{}; m_slotName = AZ::Name{}; m_selectedChanged = false; diff --git a/Gems/Atom/RPI/Code/Tests/Common/AssetSystemStub.h b/Gems/Atom/Utils/Code/Include/Atom/Utils/TestUtils/AssetSystemStub.h similarity index 100% rename from Gems/Atom/RPI/Code/Tests/Common/AssetSystemStub.h rename to Gems/Atom/Utils/Code/Include/Atom/Utils/TestUtils/AssetSystemStub.h diff --git a/Gems/Atom/RPI/Code/Tests/Common/AssetSystemStub.cpp b/Gems/Atom/Utils/Code/Source/TestUtils/AssetSystemStub.cpp similarity index 98% rename from Gems/Atom/RPI/Code/Tests/Common/AssetSystemStub.cpp rename to Gems/Atom/Utils/Code/Source/TestUtils/AssetSystemStub.cpp index 31a46e9a6f..d57969ea9c 100644 --- a/Gems/Atom/RPI/Code/Tests/Common/AssetSystemStub.cpp +++ b/Gems/Atom/Utils/Code/Source/TestUtils/AssetSystemStub.cpp @@ -6,7 +6,7 @@ * */ -#include +#include #include namespace UnitTest diff --git a/Gems/Atom/Utils/Code/atom_utils_editor_files.cmake b/Gems/Atom/Utils/Code/atom_utils_editor_files.cmake new file mode 100644 index 0000000000..6c4202fe09 --- /dev/null +++ b/Gems/Atom/Utils/Code/atom_utils_editor_files.cmake @@ -0,0 +1,12 @@ +# +# Copyright (c) Contributors to the Open 3D Engine Project. +# For complete copyright and license terms please see the LICENSE at the root of this distribution. +# +# SPDX-License-Identifier: Apache-2.0 OR MIT +# +# + +set(FILES + Include/Atom/Utils/TestUtils/AssetSystemStub.h + Source/TestUtils/AssetSystemStub.cpp +) diff --git a/Gems/AtomContent/Sponza/Assets/Prefabs/test_sponza_material_conversion.prefab b/Gems/AtomContent/Sponza/Assets/Prefabs/test_sponza_material_conversion.prefab index b5aed9d14b..92874574e4 100644 --- a/Gems/AtomContent/Sponza/Assets/Prefabs/test_sponza_material_conversion.prefab +++ b/Gems/AtomContent/Sponza/Assets/Prefabs/test_sponza_material_conversion.prefab @@ -581,7 +581,7 @@ { "id": { "materialAssetId": { - "guid": "{935F694A-8639-515B-8133-81CDC7948E5B}", + "guid": "{0CD745C0-6AA8-569A-A68A-73A3270986C4}", "subId": 803645540 } } @@ -593,7 +593,7 @@ "id": { "lodIndex": 0, "materialAssetId": { - "guid": "{935F694A-8639-515B-8133-81CDC7948E5B}", + "guid": "{0CD745C0-6AA8-569A-A68A-73A3270986C4}", "subId": 803645540 } } @@ -608,10 +608,10 @@ "Configuration": { "ModelAsset": { "assetId": { - "guid": "{935F694A-8639-515B-8133-81CDC7948E5B}", - "subId": 277333723 + "guid": "{0CD745C0-6AA8-569A-A68A-73A3270986C4}", + "subId": 277889906 }, - "assetHint": "objects/groudplane/groundplane_521x521m.azmodel" + "assetHint": "objects/groudplane/groundplane_512x512m.azmodel" } } } diff --git a/Gems/AtomContent/Sponza/Registry/AssetProcessorPlatformConfig.setreg b/Gems/AtomContent/Sponza/Registry/AssetProcessorPlatformConfig.setreg index 778d05dee9..206a0a2a87 100644 --- a/Gems/AtomContent/Sponza/Registry/AssetProcessorPlatformConfig.setreg +++ b/Gems/AtomContent/Sponza/Registry/AssetProcessorPlatformConfig.setreg @@ -6,7 +6,7 @@ // Sample Gems, Block source folders // ------------------------------------------------------------------------------ "Exclude Work In Progress Folders": { - "pattern": ".*\\\\/.[Ss]rc\\\\/.*" + "pattern": "(^|.+/).[Ss]rc(/.*)?$" } } } diff --git a/Gems/AtomLyIntegration/AtomBridge/Code/Source/AtomBridgeSystemComponent.cpp b/Gems/AtomLyIntegration/AtomBridge/Code/Source/AtomBridgeSystemComponent.cpp index b439ac475c..d822b16486 100644 --- a/Gems/AtomLyIntegration/AtomBridge/Code/Source/AtomBridgeSystemComponent.cpp +++ b/Gems/AtomLyIntegration/AtomBridge/Code/Source/AtomBridgeSystemComponent.cpp @@ -108,7 +108,7 @@ namespace AZ { m_dynamicDrawManager.reset(); AZ::RPI::ViewportContextManagerNotificationsBus::Handler::BusDisconnect(); - RPI::Scene* scene = RPI::RPISystemInterface::Get()->GetDefaultScene().get(); + RPI::Scene* scene = AZ::RPI::Scene::GetSceneForEntityContextId(m_entityContextId); // Check if scene is emptry since scene might be released already when running AtomSampleViewer if (scene) { @@ -157,9 +157,9 @@ namespace AZ void AtomBridgeSystemComponent::OnBootstrapSceneReady(AZ::RPI::Scene* bootstrapScene) { - AZ_UNUSED(bootstrapScene); // Make default AtomDebugDisplayViewportInterface - AZStd::shared_ptr mainEntityDebugDisplay = AZStd::make_shared(AzFramework::g_defaultSceneEntityDebugDisplayId); + AZStd::shared_ptr mainEntityDebugDisplay = + AZStd::make_shared(AzFramework::g_defaultSceneEntityDebugDisplayId, bootstrapScene); m_activeViewportsList[AzFramework::g_defaultSceneEntityDebugDisplayId] = mainEntityDebugDisplay; } diff --git a/Gems/AtomLyIntegration/AtomBridge/Code/Source/AtomDebugDisplayViewportInterface.cpp b/Gems/AtomLyIntegration/AtomBridge/Code/Source/AtomDebugDisplayViewportInterface.cpp index 39d8933863..14f1c5aa0d 100644 --- a/Gems/AtomLyIntegration/AtomBridge/Code/Source/AtomDebugDisplayViewportInterface.cpp +++ b/Gems/AtomLyIntegration/AtomBridge/Code/Source/AtomDebugDisplayViewportInterface.cpp @@ -256,12 +256,11 @@ namespace AZ::AtomBridge viewportContextPtr->ConnectSceneChangedHandler(m_sceneChangeHandler); } - AtomDebugDisplayViewportInterface::AtomDebugDisplayViewportInterface(uint32_t defaultInstanceAddress) + AtomDebugDisplayViewportInterface::AtomDebugDisplayViewportInterface(uint32_t defaultInstanceAddress, RPI::Scene* scene) { ResetRenderState(); m_viewportId = defaultInstanceAddress; m_defaultInstance = true; - RPI::Scene* scene = RPI::RPISystemInterface::Get()->GetDefaultScene().get(); InitInternal(scene, nullptr); } diff --git a/Gems/AtomLyIntegration/AtomBridge/Code/Source/AtomDebugDisplayViewportInterface.h b/Gems/AtomLyIntegration/AtomBridge/Code/Source/AtomDebugDisplayViewportInterface.h index 07f4efdf50..5f902c5884 100644 --- a/Gems/AtomLyIntegration/AtomBridge/Code/Source/AtomDebugDisplayViewportInterface.h +++ b/Gems/AtomLyIntegration/AtomBridge/Code/Source/AtomDebugDisplayViewportInterface.h @@ -124,7 +124,7 @@ namespace AZ::AtomBridge AZ_RTTI(AtomDebugDisplayViewportInterface, "{09AF6A46-0100-4FBF-8F94-E6B221322D14}", AzFramework::DebugDisplayRequestBus::Handler); explicit AtomDebugDisplayViewportInterface(AZ::RPI::ViewportContextPtr viewportContextPtr); - explicit AtomDebugDisplayViewportInterface(uint32_t defaultInstanceAddress); + explicit AtomDebugDisplayViewportInterface(uint32_t defaultInstanceAddress, RPI::Scene* scene); ~AtomDebugDisplayViewportInterface(); void ResetRenderState(); diff --git a/Gems/AtomLyIntegration/AtomFont/Code/Include/AtomLyIntegration/AtomFont/FFont.h b/Gems/AtomLyIntegration/AtomFont/Code/Include/AtomLyIntegration/AtomFont/FFont.h index 2cc8a67cfa..ff6ad7c151 100644 --- a/Gems/AtomLyIntegration/AtomFont/Code/Include/AtomLyIntegration/AtomFont/FFont.h +++ b/Gems/AtomLyIntegration/AtomFont/Code/Include/AtomLyIntegration/AtomFont/FFont.h @@ -133,18 +133,6 @@ namespace AZ typedef std::vector FontEffects; typedef FontEffects::iterator FontEffectsIterator; - struct FontPipelineStateMapKey - { - AZ::RPI::SceneId m_sceneId; // which scene pipeline state is attached to (via Render Pipeline) - AZ::RHI::DrawListTag m_drawListTag; // which render pass this pipeline draws in by default - - bool operator<(const FontPipelineStateMapKey& other) const - { - return m_sceneId < other.m_sceneId - || (m_sceneId == other.m_sceneId && m_drawListTag < other.m_drawListTag); - } - }; - struct FontShaderData { AZ::RHI::ShaderInputNameIndex m_imageInputIndex = "m_texture"; diff --git a/Gems/AtomLyIntegration/AtomViewportDisplayInfo/Code/Source/AtomViewportDisplayInfoSystemComponent.cpp b/Gems/AtomLyIntegration/AtomViewportDisplayInfo/Code/Source/AtomViewportDisplayInfoSystemComponent.cpp index 7d659ebb7a..91844c9b2f 100644 --- a/Gems/AtomLyIntegration/AtomViewportDisplayInfo/Code/Source/AtomViewportDisplayInfoSystemComponent.cpp +++ b/Gems/AtomLyIntegration/AtomViewportDisplayInfo/Code/Source/AtomViewportDisplayInfoSystemComponent.cpp @@ -130,7 +130,8 @@ namespace AZ::Render } AZ::RPI::ViewportContextPtr viewportContext = GetViewportContext(); - if (!m_fontDrawInterface || !viewportContext || !viewportContext->GetRenderScene()) + if (!m_fontDrawInterface || !viewportContext || !viewportContext->GetRenderScene() || + !AZ::Interface::Get()) { return; } diff --git a/Gems/AtomLyIntegration/CommonFeatures/Assets/LevelAssets/default.slice b/Gems/AtomLyIntegration/CommonFeatures/Assets/LevelAssets/default.slice index b4c9eac10f..e0c7c9e456 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Assets/LevelAssets/default.slice +++ b/Gems/AtomLyIntegration/CommonFeatures/Assets/LevelAssets/default.slice @@ -836,7 +836,7 @@
- + diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/CMakeLists.txt b/Gems/AtomLyIntegration/CommonFeatures/Code/CMakeLists.txt index e68681315e..95331a2f3f 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/CMakeLists.txt +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/CMakeLists.txt @@ -86,6 +86,8 @@ if(PAL_TRAIT_BUILD_HOST_TOOLS) FILES_CMAKE atomlyintegration_commonfeatures_editor_files.cmake ${pal_source_dir}/platform_${PAL_PLATFORM_NAME_LOWERCASE}_files.cmake + PLATFORM_INCLUDE_FILES + ${pal_source_dir}/platform_${PAL_PLATFORM_NAME_LOWERCASE}.cmake INCLUDE_DIRECTORIES PRIVATE . diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/Material/MaterialComponentBus.h b/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/Material/MaterialComponentBus.h index ed7f1564ac..d8d0c2dbc3 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/Material/MaterialComponentBus.h +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/Material/MaterialComponentBus.h @@ -43,6 +43,9 @@ namespace AZ virtual void ClearInvalidMaterialOverrides() = 0; //! Repair materials that reference missing assets by assigning the default asset virtual void RepairInvalidMaterialOverrides() = 0; + //! Repair material property overrides that reference missing properties by auto-renaming them where possible + //! @return the number of properties that were updated + virtual uint32_t ApplyAutomaticPropertyUpdates() = 0; //! Set default material override virtual void SetDefaultMaterialOverride(const AZ::Data::AssetId& materialAssetId) = 0; //! Get default material override @@ -57,52 +60,8 @@ namespace AZ virtual void ClearMaterialOverride(const MaterialAssignmentId& materialAssignmentId) = 0; //! Set a material property override value wrapped by an AZStd::any virtual void SetPropertyOverride(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName, const AZStd::any& value) = 0; - //! Set a material property override value to a bool - virtual void SetPropertyOverrideBool(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName, const bool& value) = 0; - //! Set a material property override value to a integer - virtual void SetPropertyOverrideInt32(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName, const int32_t& value) = 0; - //! Set a material property override value to a unsigned integer - virtual void SetPropertyOverrideUInt32(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName, const uint32_t& value) = 0; - //! Set a material property override value to a float - virtual void SetPropertyOverrideFloat(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName, const float& value) = 0; - //! Set a material property override value to a Vector2 - virtual void SetPropertyOverrideVector2(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName, const AZ::Vector2& value) = 0; - //! Set a material property override value to a Vector3 - virtual void SetPropertyOverrideVector3(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName, const AZ::Vector3& value) = 0; - //! Set a material property override value to a Vector4 - virtual void SetPropertyOverrideVector4(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName, const AZ::Vector4& value) = 0; - //! Set a material property override value to a color - virtual void SetPropertyOverrideColor(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName, const AZ::Color& value) = 0; - //! Set a material property override value to an image asset - virtual void SetPropertyOverrideImageAsset(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName, const AZ::Data::Asset& value) = 0; - //! Set a material property override value to an image instance - virtual void SetPropertyOverrideImageInstance(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName, const AZ::Data::Instance& value) = 0; - //! Set a material property override value to a string - virtual void SetPropertyOverrideString(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName, const AZStd::string& value) = 0; //! Get a material property override value wrapped by an AZStd::any virtual AZStd::any GetPropertyOverride(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const = 0; - //! Get a material property override value as a bool - virtual bool GetPropertyOverrideBool(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const = 0; - //! Get a material property override value as an integer - virtual int32_t GetPropertyOverrideInt32(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const = 0; - //! Get a material property override value as an unsigned integer - virtual uint32_t GetPropertyOverrideUInt32(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const = 0; - //! Get a material property override value as a float - virtual float GetPropertyOverrideFloat(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const = 0; - //! Get a material property override value as a Vector2 - virtual AZ::Vector2 GetPropertyOverrideVector2(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const = 0; - //! Get a material property override value as a Vector3 - virtual AZ::Vector3 GetPropertyOverrideVector3(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const = 0; - //! Get a material property override value as a Vector4 - virtual AZ::Vector4 GetPropertyOverrideVector4(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const = 0; - //! Get a material property override value as a Color - virtual AZ::Color GetPropertyOverrideColor(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const = 0; - //! Get a material property override value as an image asset - virtual AZ::Data::Asset GetPropertyOverrideImageAsset(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const = 0; - //! Get a material property override value as an image instance - virtual AZ::Data::Instance GetPropertyOverrideImageInstance(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const = 0; - //! Get a material property override value as a string - virtual AZStd::string GetPropertyOverrideString(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const = 0; //! Clear property override for a specific material assignment virtual void ClearPropertyOverride(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) = 0; //! Clear property overrides for a specific material assignment @@ -119,6 +78,21 @@ namespace AZ const MaterialAssignmentId& materialAssignmentId, const AZ::RPI::MaterialModelUvOverrideMap& modelUvOverrides) = 0; //! Get Model UV overrides for a specific material assignment virtual AZ::RPI::MaterialModelUvOverrideMap GetModelUvOverrides(const MaterialAssignmentId& materialAssignmentId) const = 0; + + //! Set material property override value with a specific type + template + void SetPropertyOverrideT(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName, const T& value) + { + SetPropertyOverride(materialAssignmentId, propertyName, AZStd::any(value)); + } + + //! Get material property override value with a specific type + template + T GetPropertyOverrideT(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const + { + const AZStd::any& value = GetPropertyOverride(materialAssignmentId, propertyName); + return !value.empty() && value.is() ? AZStd::any_cast(value) : T{}; + } }; using MaterialComponentRequestBus = EBus; diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/DiffuseGlobalIllumination/DiffuseGlobalIlluminationComponentController.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/DiffuseGlobalIllumination/DiffuseGlobalIlluminationComponentController.cpp index ee322c8afd..e844219811 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/DiffuseGlobalIllumination/DiffuseGlobalIlluminationComponentController.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/DiffuseGlobalIllumination/DiffuseGlobalIlluminationComponentController.cpp @@ -48,11 +48,7 @@ namespace AZ void DiffuseGlobalIlluminationComponentController::Activate(EntityId entityId) { - AZ_UNUSED(entityId); - - const RPI::Scene* scene = AZ::RPI::RPISystemInterface::Get()->GetDefaultScene().get(); - m_featureProcessor = scene->GetFeatureProcessor(); - + m_featureProcessor = AZ::RPI::Scene::GetFeatureProcessorForEntity(entityId); OnConfigChanged(); } diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Grid/GridComponentController.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Grid/GridComponentController.cpp index b4131894f4..2611021359 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Grid/GridComponentController.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Grid/GridComponentController.cpp @@ -79,7 +79,7 @@ namespace AZ m_entityId = entityId; m_dirty = true; - RPI::ScenePtr scene = RPI::RPISystemInterface::Get()->GetDefaultScene(); + RPI::Scene* scene = RPI::Scene::GetSceneForEntityId(m_entityId); if (scene) { AZ::RPI::SceneNotificationBus::Handler::BusConnect(scene->GetId()); diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponent.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponent.cpp index a3ae61b14b..7e6b3e4e4c 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponent.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponent.cpp @@ -6,23 +6,24 @@ * */ -#include -#include - -#include -#include -#include #include #include #include #include #include #include +#include +#include +#include +#include +#include +#include +#include AZ_PUSH_DISABLE_WARNING(4251 4800, "-Wunknown-warning-option") // disable warnings spawned by QT -#include #include #include +#include AZ_POP_DISABLE_WARNING namespace AZ @@ -59,7 +60,12 @@ namespace AZ BaseClass::Reflect(context); EditorMaterialComponentSlot::Reflect(context); - if (AZ::SerializeContext* serializeContext = azrtti_cast(context)) + if (auto jsonContext = azrtti_cast(context)) + { + jsonContext->Serializer()->HandlesType(); + } + + if (auto serializeContext = azrtti_cast(context)) { serializeContext->RegisterGenericType(); serializeContext->RegisterGenericType(); @@ -76,7 +82,7 @@ namespace AZ serializeContext->RegisterGenericType, AZStd::equal_to, AZStd::allocator>>(); serializeContext->RegisterGenericType, AZStd::equal_to, AZStd::allocator>>(); - if (AZ::EditContext* editContext = serializeContext->GetEditContext()) + if (auto editContext = serializeContext->GetEditContext()) { editContext->Class( "Material", "The material component specifies the material to use for this entity") @@ -129,7 +135,7 @@ namespace AZ } } - if (AZ::BehaviorContext* behaviorContext = azrtti_cast(context)) + if (auto behaviorContext = azrtti_cast(context)) { behaviorContext->ConstantProperty("EditorMaterialComponentTypeId", BehaviorConstant(Uuid(EditorMaterialComponentTypeId))) ->Attribute(AZ::Script::Attributes::Module, "render") @@ -240,6 +246,19 @@ namespace AZ UpdateMaterialSlots(); }); action->setToolTip("Repair materials that reference missing assets by assigning the default asset."); + + action = menu->addAction("Apply Automatic Property Updates", [this]() { + AzToolsFramework::ScopedUndoBatch undoBatch("Applying automatic property updates."); + SetDirty(); + + uint32_t propertiesUpdated = 0; + MaterialComponentRequestBus::EventResult(propertiesUpdated, GetEntityId(), &MaterialComponentRequestBus::Events::ApplyAutomaticPropertyUpdates); + + AZ_Printf("EditorMaterialComponent", "Updated %u property(s).", propertiesUpdated); + + UpdateMaterialSlots(); + }); + action->setToolTip("Repair material property overrides that reference missing properties by auto-renaming them where possible."); } void EditorMaterialComponent::SetPrimaryAsset(const AZ::Data::AssetId& assetId) diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponent.h b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponent.h index ce21ae82ac..4cd7870be5 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponent.h +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponent.h @@ -27,6 +27,8 @@ namespace AZ , public EditorMaterialSystemComponentNotificationBus::Handler { public: + friend class JsonEditorMaterialComponentSerializer; + using BaseClass = EditorRenderComponentAdapter; AZ_EDITOR_COMPONENT(EditorMaterialComponent, EditorMaterialComponentTypeId, BaseClass); diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentInspector.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentInspector.cpp index b6875e2e9c..bfa9f1d36f 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentInspector.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentInspector.cpp @@ -314,12 +314,21 @@ namespace AZ AtomToolsFramework::ConvertToPropertyConfig(propertyConfig, propertyDefinition); + const auto& propertyIndex = + m_editData.m_materialAsset->GetMaterialPropertiesLayout()->FindPropertyIndex(propertyConfig.m_id); + propertyConfig.m_groupName = groupDisplayName; - const auto& propertyIndex = m_editData.m_materialAsset->GetMaterialPropertiesLayout()->FindPropertyIndex(propertyConfig.m_id); propertyConfig.m_showThumbnail = true; - propertyConfig.m_defaultValue = AtomToolsFramework::ConvertToEditableType(m_editData.m_materialTypeAsset->GetDefaultPropertyValues()[propertyIndex.GetIndex()]); - propertyConfig.m_parentValue = AtomToolsFramework::ConvertToEditableType(m_editData.m_materialTypeAsset->GetDefaultPropertyValues()[propertyIndex.GetIndex()]); - propertyConfig.m_originalValue = AtomToolsFramework::ConvertToEditableType(m_editData.m_materialAsset->GetPropertyValues()[propertyIndex.GetIndex()]); + + propertyConfig.m_defaultValue = AtomToolsFramework::ConvertToEditableType( + m_editData.m_materialTypeAsset->GetDefaultPropertyValues()[propertyIndex.GetIndex()]); + + // There is no explicit parent material here. Material instance property overrides replace the values from the + // assigned material asset. Its values should be treated as parent, for comparison, in this case. + propertyConfig.m_parentValue = AtomToolsFramework::ConvertToEditableType( + m_editData.m_materialAsset->GetPropertyValues()[propertyIndex.GetIndex()]); + propertyConfig.m_originalValue = AtomToolsFramework::ConvertToEditableType( + m_editData.m_materialAsset->GetPropertyValues()[propertyIndex.GetIndex()]); group.m_properties.emplace_back(propertyConfig); } } @@ -352,6 +361,25 @@ namespace AZ m_editData.m_materialPropertyOverrideMap, m_entityId, &MaterialComponentRequestBus::Events::GetPropertyOverrides, m_materialAssignmentId); + // Apply any automatic property renames so that the material inspector will be properly initialized with the right values + // for properties that have new names. + { + AZStd::vector> renamedProperties; + for (auto& propertyOverridePair : m_editData.m_materialPropertyOverrideMap) + { + Name name = propertyOverridePair.first; + if (m_materialInstance->GetAsset()->GetMaterialTypeAsset()->ApplyPropertyRenames(name)) + { + renamedProperties.emplace_back(propertyOverridePair.first, name); + } + } + for (const auto& [oldName, newName] : renamedProperties) + { + m_editData.m_materialPropertyOverrideMap[newName] = m_editData.m_materialPropertyOverrideMap[oldName]; + m_editData.m_materialPropertyOverrideMap.erase(oldName); + } + } + for (auto& group : m_groups) { for (auto& property : group.second.m_properties) diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentSerializer.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentSerializer.cpp new file mode 100644 index 0000000000..8ae6e44a93 --- /dev/null +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentSerializer.cpp @@ -0,0 +1,109 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include +#include + +namespace AZ +{ + namespace Render + { + AZ_CLASS_ALLOCATOR_IMPL(JsonEditorMaterialComponentSerializer, AZ::SystemAllocator, 0); + + AZ::JsonSerializationResult::Result JsonEditorMaterialComponentSerializer::Load( + void* outputValue, + [[maybe_unused]] const AZ::Uuid& outputValueTypeId, + const rapidjson::Value& inputValue, + AZ::JsonDeserializerContext& context) + { + namespace JSR = AZ::JsonSerializationResult; + + AZ_Assert( + azrtti_typeid() == outputValueTypeId, + "Unable to deserialize EditorMaterialComponent from json because the provided type is %s.", + outputValueTypeId.ToString().c_str()); + + auto componentInstance = reinterpret_cast(outputValue); + AZ_Assert(componentInstance, "Output value for JsonEditorMaterialComponentSerializer can't be null."); + + JSR::ResultCode result(JSR::Tasks::ReadField); + + result.Combine(ContinueLoadingFromJsonObjectField( + &componentInstance->m_id, azrtti_typeidm_id)>(), inputValue, "Id", context)); + + result.Combine(ContinueLoadingFromJsonObjectField( + &componentInstance->m_controller, azrtti_typeidm_controller)>(), inputValue, "Controller", + context)); + + result.Combine(ContinueLoadingFromJsonObjectField( + &componentInstance->m_materialSlotsByLodEnabled, azrtti_typeidm_materialSlotsByLodEnabled)>(), + inputValue, "materialSlotsByLodEnabled", context)); + + return context.Report( + result, + result.GetProcessing() != JSR::Processing::Halted ? "Successfully loaded EditorMaterialComponent information." + : "Failed to load EditorMaterialComponent information."); + } + + AZ::JsonSerializationResult::Result JsonEditorMaterialComponentSerializer::Store( + rapidjson::Value& outputValue, + const void* inputValue, + const void* defaultValue, + [[maybe_unused]] const AZ::Uuid& valueTypeId, + AZ::JsonSerializerContext& context) + { + namespace JSR = AZ::JsonSerializationResult; + + AZ_Assert( + azrtti_typeid() == valueTypeId, + "Unable to Serialize EditorMaterialComponent because the provided type is %s.", + valueTypeId.ToString().c_str()); + + auto componentInstance = reinterpret_cast(inputValue); + AZ_Assert(componentInstance, "Input value for JsonEditorMaterialComponentSerializer can't be null."); + auto defaultComponentInstance = reinterpret_cast(defaultValue); + + JSR::ResultCode result(JSR::Tasks::WriteValue); + { + AZ::ScopedContextPath subPathName(context, "m_id"); + const auto componentId = &componentInstance->m_id; + const auto defaultComponentId = defaultComponentInstance ? &defaultComponentInstance->m_id : nullptr; + + result.Combine(ContinueStoringToJsonObjectField( + outputValue, "Id", componentId, defaultComponentId, azrtti_typeidm_id)>(), context)); + } + + { + AZ::ScopedContextPath subPathName(context, "Controller"); + const auto controller = &componentInstance->m_controller; + const auto defaultController = defaultComponentInstance ? &defaultComponentInstance->m_controller : nullptr; + + result.Combine(ContinueStoringToJsonObjectField( + outputValue, "Controller", controller, defaultController, azrtti_typeidm_controller)>(), + context)); + } + + { + AZ::ScopedContextPath subPathName(context, "materialSlotsByLodEnabled"); + const auto enabled = &componentInstance->m_materialSlotsByLodEnabled; + const auto defaultEnabled = defaultComponentInstance ? &defaultComponentInstance->m_materialSlotsByLodEnabled : nullptr; + + result.Combine(ContinueStoringToJsonObjectField( + outputValue, "materialSlotsByLodEnabled", enabled, defaultEnabled, + azrtti_typeidm_materialSlotsByLodEnabled)>(), context)); + } + + return context.Report( + result, + result.GetProcessing() != JSR::Processing::Halted ? "Successfully stored EditorMaterialComponent information." + : "Failed to store EditorMaterialComponent information."); + } + + } // namespace Render +} // namespace AZ diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentSerializer.h b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentSerializer.h new file mode 100644 index 0000000000..2b6401c67f --- /dev/null +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentSerializer.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include +#include + +namespace AZ +{ + namespace Render + { + // JsonEditorMaterialComponentSerializer skips serialization of EditorMaterialComponentSlot(s) which are only needed at runtime in + // the editor + class JsonEditorMaterialComponentSerializer : public AZ::BaseJsonSerializer + { + public: + AZ_RTTI(JsonEditorMaterialComponentSerializer, "{D354FE3C-34D2-4E80-B3F9-49450D252336}", BaseJsonSerializer); + AZ_CLASS_ALLOCATOR_DECL; + + AZ::JsonSerializationResult::Result Load( + void* outputValue, + const AZ::Uuid& outputValueTypeId, + const rapidjson::Value& inputValue, + AZ::JsonDeserializerContext& context) override; + + AZ::JsonSerializationResult::Result Store( + rapidjson::Value& outputValue, + const void* inputValue, + const void* defaultValue, + const AZ::Uuid& valueTypeId, + AZ::JsonSerializerContext& context) override; + }; + + } // namespace Render +} // namespace AZ diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentUtil.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentUtil.cpp index d15db886d6..0fd7b7164d 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentUtil.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentUtil.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -97,51 +98,28 @@ namespace AZ bool SaveSourceMaterialFromEditData(const AZStd::string& path, const MaterialEditData& editData) { - // Construct the material source data object that will be exported - AZ::RPI::MaterialSourceData exportData; - - // Converting absolute material paths to relative paths - bool result = false; - AZ::Data::AssetInfo info; - AZStd::string watchFolder; - AzToolsFramework::AssetSystemRequestBus::BroadcastResult( - result, &AzToolsFramework::AssetSystemRequestBus::Events::GetSourceInfoBySourcePath, - editData.m_materialTypeSourcePath.c_str(), info, watchFolder); - if (!result) + if (path.empty() || !editData.m_materialAsset.IsReady() || !editData.m_materialTypeAsset.IsReady() || + editData.m_materialTypeSourcePath.empty()) { - AZ_Error( - "AZ::Render::EditorMaterialComponentUtil", false, - "Failed to get material type source file info while attempting to export: %s", path.c_str()); + AZ_Error("AZ::Render::EditorMaterialComponentUtil", false, "Can not export: %s", path.c_str()); return false; } - exportData.m_materialType = info.m_relativePath; - - if (!editData.m_materialParentSourcePath.empty()) - { - result = false; - AzToolsFramework::AssetSystemRequestBus::BroadcastResult( - result, &AzToolsFramework::AssetSystemRequestBus::Events::GetSourceInfoBySourcePath, - editData.m_materialParentSourcePath.c_str(), info, watchFolder); - if (!result) - { - AZ_Error( - "AZ::Render::EditorMaterialComponentUtil", false, - "Failed to get parent material source file info while attempting to export: %s", path.c_str()); - return false; - } - - exportData.m_parentMaterial = info.m_relativePath; - } + // Construct the material source data object that will be exported + AZ::RPI::MaterialSourceData exportData; + exportData.m_materialTypeVersion = editData.m_materialTypeAsset->GetVersion(); + exportData.m_materialType = AtomToolsFramework::GetExteralReferencePath(path, editData.m_materialTypeSourcePath); + exportData.m_parentMaterial = AtomToolsFramework::GetExteralReferencePath(path, editData.m_materialParentSourcePath); // Copy all of the properties from the material asset to the source data that will be exported - result = true; - editData.m_materialTypeSourceData.EnumerateProperties([&](const AZStd::string& groupName, const AZStd::string& propertyName, const auto& propertyDefinition) { + bool result = true; + editData.m_materialTypeSourceData.EnumerateProperties([&](const AZStd::string& groupName, const AZStd::string& propertyName, const auto& propertyDefinition){ const AZ::RPI::MaterialPropertyId propertyId(groupName, propertyName); const AZ::RPI::MaterialPropertyIndex propertyIndex = editData.m_materialAsset->GetMaterialPropertiesLayout()->FindPropertyIndex(propertyId.GetFullName()); - AZ::RPI::MaterialPropertyValue propertyValue = editData.m_materialAsset->GetPropertyValues()[propertyIndex.GetIndex()]; + AZ::RPI::MaterialPropertyValue propertyValue = + editData.m_materialAsset->GetPropertyValues()[propertyIndex.GetIndex()]; AZ::RPI::MaterialPropertyValue propertyValueDefault = propertyDefinition.m_value; if (editData.m_materialParentAsset.IsReady()) @@ -151,12 +129,12 @@ namespace AZ // Check for and apply any property overrides before saving property values auto propertyOverrideItr = editData.m_materialPropertyOverrideMap.find(propertyId.GetFullName()); - if(propertyOverrideItr != editData.m_materialPropertyOverrideMap.end()) + if (propertyOverrideItr != editData.m_materialPropertyOverrideMap.end()) { propertyValue = AZ::RPI::MaterialPropertyValue::FromAny(propertyOverrideItr->second); } - if (!editData.m_materialTypeSourceData.ConvertPropertyValueToSourceDataFormat(propertyDefinition, propertyValue)) + if (!AtomToolsFramework::ConvertToExportFormat(path, propertyDefinition, propertyValue)) { AZ_Error("AZ::Render::EditorMaterialComponentUtil", false, "Failed to export: %s", path.c_str()); result = false; diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialSystemComponent.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialSystemComponent.cpp index e53010d747..adaebfb889 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialSystemComponent.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialSystemComponent.cpp @@ -196,6 +196,7 @@ namespace AZ { AZ_UNUSED(entityId); AZ_UNUSED(materialAssignmentId); + AZ_Warning( "EditorMaterialSystemComponent", false, "RenderMaterialPreview capture failed for entity %s slot %s.", entityId.ToString().c_str(), materialAssignmentId.ToString().c_str()); diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/MaterialComponentController.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/MaterialComponentController.cpp index 4c26042a26..0ccdae28de 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/MaterialComponentController.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/MaterialComponentController.cpp @@ -49,33 +49,34 @@ namespace AZ ->Event("ClearIncompatibleMaterialOverrides", &MaterialComponentRequestBus::Events::ClearIncompatibleMaterialOverrides) ->Event("ClearInvalidMaterialOverrides", &MaterialComponentRequestBus::Events::ClearInvalidMaterialOverrides) ->Event("RepairInvalidMaterialOverrides", &MaterialComponentRequestBus::Events::RepairInvalidMaterialOverrides) + ->Event("ApplyAutomaticPropertyUpdates", &MaterialComponentRequestBus::Events::ApplyAutomaticPropertyUpdates) ->Event("SetMaterialOverride", &MaterialComponentRequestBus::Events::SetMaterialOverride) ->Event("GetMaterialOverride", &MaterialComponentRequestBus::Events::GetMaterialOverride) ->Event("ClearMaterialOverride", &MaterialComponentRequestBus::Events::ClearMaterialOverride) ->Event("SetPropertyOverride", &MaterialComponentRequestBus::Events::SetPropertyOverride) - ->Event("SetPropertyOverrideBool", &MaterialComponentRequestBus::Events::SetPropertyOverrideBool) - ->Event("SetPropertyOverrideInt32", &MaterialComponentRequestBus::Events::SetPropertyOverrideInt32) - ->Event("SetPropertyOverrideUInt32", &MaterialComponentRequestBus::Events::SetPropertyOverrideUInt32) - ->Event("SetPropertyOverrideFloat", &MaterialComponentRequestBus::Events::SetPropertyOverrideFloat) - ->Event("SetPropertyOverrideVector2", &MaterialComponentRequestBus::Events::SetPropertyOverrideVector2) - ->Event("SetPropertyOverrideVector3", &MaterialComponentRequestBus::Events::SetPropertyOverrideVector3) - ->Event("SetPropertyOverrideVector4", &MaterialComponentRequestBus::Events::SetPropertyOverrideVector4) - ->Event("SetPropertyOverrideColor", &MaterialComponentRequestBus::Events::SetPropertyOverrideColor) - ->Event("SetPropertyOverrideImageAsset", &MaterialComponentRequestBus::Events::SetPropertyOverrideImageAsset) - ->Event("SetPropertyOverrideImageInstance", &MaterialComponentRequestBus::Events::SetPropertyOverrideImageInstance) - ->Event("SetPropertyOverrideString", &MaterialComponentRequestBus::Events::SetPropertyOverrideString) + ->Event("SetPropertyOverrideBool", &MaterialComponentRequestBus::Events::SetPropertyOverrideT) + ->Event("SetPropertyOverrideInt32", &MaterialComponentRequestBus::Events::SetPropertyOverrideT) + ->Event("SetPropertyOverrideUInt32", &MaterialComponentRequestBus::Events::SetPropertyOverrideT) + ->Event("SetPropertyOverrideFloat", &MaterialComponentRequestBus::Events::SetPropertyOverrideT) + ->Event("SetPropertyOverrideVector2", &MaterialComponentRequestBus::Events::SetPropertyOverrideT) + ->Event("SetPropertyOverrideVector3", &MaterialComponentRequestBus::Events::SetPropertyOverrideT) + ->Event("SetPropertyOverrideVector4", &MaterialComponentRequestBus::Events::SetPropertyOverrideT) + ->Event("SetPropertyOverrideColor", &MaterialComponentRequestBus::Events::SetPropertyOverrideT) + ->Event("SetPropertyOverrideImage", &MaterialComponentRequestBus::Events::SetPropertyOverrideT) + ->Event("SetPropertyOverrideString", &MaterialComponentRequestBus::Events::SetPropertyOverrideT) + ->Event("SetPropertyOverrideEnum", &MaterialComponentRequestBus::Events::SetPropertyOverrideT) ->Event("GetPropertyOverride", &MaterialComponentRequestBus::Events::GetPropertyOverride) - ->Event("GetPropertyOverrideBool", &MaterialComponentRequestBus::Events::GetPropertyOverrideBool) - ->Event("GetPropertyOverrideInt32", &MaterialComponentRequestBus::Events::GetPropertyOverrideInt32) - ->Event("GetPropertyOverrideUInt32", &MaterialComponentRequestBus::Events::GetPropertyOverrideUInt32) - ->Event("GetPropertyOverrideFloat", &MaterialComponentRequestBus::Events::GetPropertyOverrideFloat) - ->Event("GetPropertyOverrideVector2", &MaterialComponentRequestBus::Events::GetPropertyOverrideVector2) - ->Event("GetPropertyOverrideVector3", &MaterialComponentRequestBus::Events::GetPropertyOverrideVector3) - ->Event("GetPropertyOverrideVector4", &MaterialComponentRequestBus::Events::GetPropertyOverrideVector4) - ->Event("GetPropertyOverrideColor", &MaterialComponentRequestBus::Events::GetPropertyOverrideColor) - ->Event("GetPropertyOverrideImageAsset", &MaterialComponentRequestBus::Events::GetPropertyOverrideImageAsset) - ->Event("GetPropertyOverrideImageInstance", &MaterialComponentRequestBus::Events::GetPropertyOverrideImageInstance) - ->Event("GetPropertyOverrideString", &MaterialComponentRequestBus::Events::GetPropertyOverrideString) + ->Event("GetPropertyOverrideBool", &MaterialComponentRequestBus::Events::GetPropertyOverrideT) + ->Event("GetPropertyOverrideInt32", &MaterialComponentRequestBus::Events::GetPropertyOverrideT) + ->Event("GetPropertyOverrideUInt32", &MaterialComponentRequestBus::Events::GetPropertyOverrideT) + ->Event("GetPropertyOverrideFloat", &MaterialComponentRequestBus::Events::GetPropertyOverrideT) + ->Event("GetPropertyOverrideVector2", &MaterialComponentRequestBus::Events::GetPropertyOverrideT) + ->Event("GetPropertyOverrideVector3", &MaterialComponentRequestBus::Events::GetPropertyOverrideT) + ->Event("GetPropertyOverrideVector4", &MaterialComponentRequestBus::Events::GetPropertyOverrideT) + ->Event("GetPropertyOverrideColor", &MaterialComponentRequestBus::Events::GetPropertyOverrideT) + ->Event("GetPropertyOverrideImage", &MaterialComponentRequestBus::Events::GetPropertyOverrideT) + ->Event("GetPropertyOverrideString", &MaterialComponentRequestBus::Events::GetPropertyOverrideT) + ->Event("GetPropertyOverrideEnum", &MaterialComponentRequestBus::Events::GetPropertyOverrideT) ->Event("ClearPropertyOverride", &MaterialComponentRequestBus::Events::ClearPropertyOverride) ->Event("ClearPropertyOverrides", &MaterialComponentRequestBus::Events::ClearPropertyOverrides) ->Event("ClearAllPropertyOverrides", &MaterialComponentRequestBus::Events::ClearAllPropertyOverrides) @@ -120,8 +121,13 @@ namespace AZ MaterialComponentRequestBus::Handler::BusDisconnect(); MaterialReceiverNotificationBus::Handler::BusDisconnect(); TickBus::Handler::BusDisconnect(); + ReleaseMaterials(); + // Sending notification to wipe any previously assigned material overrides + MaterialComponentNotificationBus::Event( + m_entityId, &MaterialComponentNotifications::OnMaterialsUpdated, MaterialAssignmentMap()); + m_queuedMaterialUpdateNotification = false; m_entityId = AZ::EntityId(AZ::EntityId::InvalidEntityId); } @@ -220,6 +226,11 @@ namespace AZ if (!anyQueued) { ReleaseMaterials(); + + // If no other materials were loaded, the notification must still be sent in case there are externally managed material + // instances in the configuration + MaterialComponentNotificationBus::Event( + m_entityId, &MaterialComponentNotifications::OnMaterialsUpdated, m_configuration.m_materials); } } @@ -267,8 +278,6 @@ namespace AZ { materialPair.second.Release(); } - - MaterialComponentNotificationBus::Event(m_entityId, &MaterialComponentNotifications::OnMaterialsUpdated, m_configuration.m_materials); } MaterialAssignmentMap MaterialComponentController::GetOriginalMaterialAssignments() const @@ -406,6 +415,37 @@ namespace AZ } LoadMaterials(); } + + uint32_t MaterialComponentController::ApplyAutomaticPropertyUpdates() + { + uint32_t propertiesUpdated = 0; + + for (auto& materialAssignmentPair : m_configuration.m_materials) + { + MaterialAssignment& materialAssignment = materialAssignmentPair.second; + + AZStd::vector> renamedProperties; + + for (const auto& propertyPair : materialAssignment.m_propertyOverrides) + { + Name propertyId = propertyPair.first; + + if (materialAssignment.m_materialInstance->GetAsset()->GetMaterialTypeAsset()->ApplyPropertyRenames(propertyId)) + { + renamedProperties.emplace_back(propertyPair.first, propertyId); + ++propertiesUpdated; + } + } + + for (const auto& [oldName, newName] : renamedProperties) + { + materialAssignment.m_propertyOverrides[newName] = materialAssignment.m_propertyOverrides[oldName]; + materialAssignment.m_propertyOverrides.erase(oldName); + } + } + + return propertiesUpdated; + } void MaterialComponentController::SetDefaultMaterialOverride(const AZ::Data::AssetId& materialAssetId) { @@ -467,76 +507,6 @@ namespace AZ QueuePropertyChanges(materialAssignmentId); } - void MaterialComponentController::SetPropertyOverrideBool( - const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName, const bool& value) - { - SetPropertyOverride(materialAssignmentId, propertyName, AZStd::any(value)); - } - - void MaterialComponentController::SetPropertyOverrideInt32( - const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName, const int32_t& value) - { - SetPropertyOverride(materialAssignmentId, propertyName, AZStd::any(value)); - } - - void MaterialComponentController::SetPropertyOverrideUInt32( - const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName, const uint32_t& value) - { - SetPropertyOverride(materialAssignmentId, propertyName, AZStd::any(value)); - } - - void MaterialComponentController::SetPropertyOverrideFloat( - const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName, const float& value) - { - SetPropertyOverride(materialAssignmentId, propertyName, AZStd::any(value)); - } - - void MaterialComponentController::SetPropertyOverrideVector2( - const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName, const AZ::Vector2& value) - { - SetPropertyOverride(materialAssignmentId, propertyName, AZStd::any(value)); - } - - void MaterialComponentController::SetPropertyOverrideVector3( - const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName, const AZ::Vector3& value) - { - SetPropertyOverride(materialAssignmentId, propertyName, AZStd::any(value)); - } - - void MaterialComponentController::SetPropertyOverrideVector4( - const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName, const AZ::Vector4& value) - { - SetPropertyOverride(materialAssignmentId, propertyName, AZStd::any(value)); - } - - void MaterialComponentController::SetPropertyOverrideColor( - const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName, const AZ::Color& value) - { - SetPropertyOverride(materialAssignmentId, propertyName, AZStd::any(value)); - } - - void MaterialComponentController::SetPropertyOverrideImageAsset( - const MaterialAssignmentId& materialAssignmentId, - const AZStd::string& propertyName, - const AZ::Data::Asset& value) - { - SetPropertyOverride(materialAssignmentId, propertyName, AZStd::any(value)); - } - - void MaterialComponentController::SetPropertyOverrideImageInstance( - const MaterialAssignmentId& materialAssignmentId, - const AZStd::string& propertyName, - const AZ::Data::Instance& value) - { - SetPropertyOverride(materialAssignmentId, propertyName, AZStd::any(value)); - } - - void MaterialComponentController::SetPropertyOverrideString( - const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName, const AZStd::string& value) - { - SetPropertyOverride(materialAssignmentId, propertyName, AZStd::any(value)); - } - AZStd::any MaterialComponentController::GetPropertyOverride(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const { const auto materialIt = m_configuration.m_materials.find(materialAssignmentId); @@ -554,83 +524,6 @@ namespace AZ return propertyIt->second; } - bool MaterialComponentController::GetPropertyOverrideBool( - const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const - { - const AZStd::any& value = GetPropertyOverride(materialAssignmentId, propertyName); - return !value.empty() && value.is() ? AZStd::any_cast(value) : false; - } - - int32_t MaterialComponentController::GetPropertyOverrideInt32( - const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const - { - const AZStd::any& value = GetPropertyOverride(materialAssignmentId, propertyName); - return !value.empty() && value.is() ? AZStd::any_cast(value) : 0; - } - - uint32_t MaterialComponentController::GetPropertyOverrideUInt32( - const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const - { - const AZStd::any& value = GetPropertyOverride(materialAssignmentId, propertyName); - return !value.empty() && value.is() ? AZStd::any_cast(value) : 0; - } - - float MaterialComponentController::GetPropertyOverrideFloat( - const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const - { - const AZStd::any& value = GetPropertyOverride(materialAssignmentId, propertyName); - return !value.empty() && value.is() ? AZStd::any_cast(value) : 0.0f; - } - - AZ::Vector2 MaterialComponentController::GetPropertyOverrideVector2( - const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const - { - const AZStd::any& value = GetPropertyOverride(materialAssignmentId, propertyName); - return !value.empty() && value.is() ? AZStd::any_cast(value) : AZ::Vector2::CreateZero(); - } - - AZ::Vector3 MaterialComponentController::GetPropertyOverrideVector3( - const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const - { - const AZStd::any& value = GetPropertyOverride(materialAssignmentId, propertyName); - return !value.empty() && value.is() ? AZStd::any_cast(value) : AZ::Vector3::CreateZero(); - } - - AZ::Vector4 MaterialComponentController::GetPropertyOverrideVector4( - const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const - { - const AZStd::any& value = GetPropertyOverride(materialAssignmentId, propertyName); - return !value.empty() && value.is() ? AZStd::any_cast(value) : AZ::Vector4::CreateZero(); - } - - AZ::Color MaterialComponentController::GetPropertyOverrideColor( - const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const - { - const AZStd::any& value = GetPropertyOverride(materialAssignmentId, propertyName); - return !value.empty() && value.is() ? AZStd::any_cast(value) : AZ::Color::CreateZero(); - } - - AZ::Data::Asset MaterialComponentController::GetPropertyOverrideImageAsset( - const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const - { - const AZStd::any& value = GetPropertyOverride(materialAssignmentId, propertyName); - return !value.empty() && value.is>() ? AZStd::any_cast>(value) : AZ::Data::Asset(); - } - - AZ::Data::Instance MaterialComponentController::GetPropertyOverrideImageInstance( - const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const - { - const AZStd::any& value = GetPropertyOverride(materialAssignmentId, propertyName); - return !value.empty() && value.is>() ? AZStd::any_cast>(value) : AZ::Data::Instance(); - } - - AZStd::string MaterialComponentController::GetPropertyOverrideString( - const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const - { - const AZStd::any& value = GetPropertyOverride(materialAssignmentId, propertyName); - return !value.empty() && value.is() ? AZStd::any_cast(value) : AZStd::string(); - } - void MaterialComponentController::ClearPropertyOverride(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) { auto materialIt = m_configuration.m_materials.find(materialAssignmentId); diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/MaterialComponentController.h b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/MaterialComponentController.h index 1594456a31..74b1cfda4d 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/MaterialComponentController.h +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/MaterialComponentController.h @@ -58,39 +58,15 @@ namespace AZ void ClearIncompatibleMaterialOverrides() override; void ClearInvalidMaterialOverrides() override; void RepairInvalidMaterialOverrides() override; + uint32_t ApplyAutomaticPropertyUpdates() override; void SetDefaultMaterialOverride(const AZ::Data::AssetId& materialAssetId) override; const AZ::Data::AssetId GetDefaultMaterialOverride() const override; void ClearDefaultMaterialOverride() override; void SetMaterialOverride(const MaterialAssignmentId& materialAssignmentId, const AZ::Data::AssetId& materialAssetId) override; AZ::Data::AssetId GetMaterialOverride(const MaterialAssignmentId& materialAssignmentId) const override; void ClearMaterialOverride(const MaterialAssignmentId& materialAssignmentId) override; - void SetPropertyOverride(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName, const AZStd::any& value) override; - void SetPropertyOverrideBool(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName, const bool& value) override; - void SetPropertyOverrideInt32(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName, const int32_t& value) override; - void SetPropertyOverrideUInt32(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName, const uint32_t& value) override; - void SetPropertyOverrideFloat(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName, const float& value) override; - void SetPropertyOverrideVector2(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName, const AZ::Vector2& value) override; - void SetPropertyOverrideVector3(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName, const AZ::Vector3& value) override; - void SetPropertyOverrideVector4(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName, const AZ::Vector4& value) override; - void SetPropertyOverrideColor(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName, const AZ::Color& value) override; - void SetPropertyOverrideImageAsset(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName, const AZ::Data::Asset& value) override; - void SetPropertyOverrideImageInstance(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName, const AZ::Data::Instance& value) override; - void SetPropertyOverrideString(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName, const AZStd::string& value) override; - AZStd::any GetPropertyOverride(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const override; - bool GetPropertyOverrideBool(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const override; - int32_t GetPropertyOverrideInt32(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const override; - uint32_t GetPropertyOverrideUInt32(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const override; - float GetPropertyOverrideFloat(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const override; - AZ::Vector2 GetPropertyOverrideVector2(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const override; - AZ::Vector3 GetPropertyOverrideVector3(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const override; - AZ::Vector4 GetPropertyOverrideVector4(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const override; - AZ::Color GetPropertyOverrideColor(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const override; - AZ::Data::Asset GetPropertyOverrideImageAsset(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const override; - AZ::Data::Instance GetPropertyOverrideImageInstance(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const override; - AZStd::string GetPropertyOverrideString(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const override; - void ClearPropertyOverride(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) override; void ClearPropertyOverrides(const MaterialAssignmentId& materialAssignmentId) override; void ClearAllPropertyOverrides() override; diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Platform/Android/platform_android.cmake b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Platform/Android/platform_android.cmake new file mode 100644 index 0000000000..7a325ca97e --- /dev/null +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Platform/Android/platform_android.cmake @@ -0,0 +1,7 @@ +# +# Copyright (c) Contributors to the Open 3D Engine Project. +# For complete copyright and license terms please see the LICENSE at the root of this distribution. +# +# SPDX-License-Identifier: Apache-2.0 OR MIT +# +# diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Platform/AppleTV/platform_appletv.cmake b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Platform/AppleTV/platform_appletv.cmake new file mode 100644 index 0000000000..5cd1fb5a22 --- /dev/null +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Platform/AppleTV/platform_appletv.cmake @@ -0,0 +1,8 @@ +# +# Copyright (c) Contributors to the Open 3D Engine Project. +# For complete copyright and license terms please see the LICENSE at the root of this distribution. +# +# SPDX-License-Identifier: Apache-2.0 OR MIT +# +# + diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Platform/Linux/platform_linux.cmake b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Platform/Linux/platform_linux.cmake new file mode 100644 index 0000000000..7a325ca97e --- /dev/null +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Platform/Linux/platform_linux.cmake @@ -0,0 +1,7 @@ +# +# Copyright (c) Contributors to the Open 3D Engine Project. +# For complete copyright and license terms please see the LICENSE at the root of this distribution. +# +# SPDX-License-Identifier: Apache-2.0 OR MIT +# +# diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Platform/Mac/platform_mac.cmake b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Platform/Mac/platform_mac.cmake new file mode 100644 index 0000000000..7a325ca97e --- /dev/null +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Platform/Mac/platform_mac.cmake @@ -0,0 +1,7 @@ +# +# Copyright (c) Contributors to the Open 3D Engine Project. +# For complete copyright and license terms please see the LICENSE at the root of this distribution. +# +# SPDX-License-Identifier: Apache-2.0 OR MIT +# +# diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Platform/Windows/platform_windows.cmake b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Platform/Windows/platform_windows.cmake new file mode 100644 index 0000000000..3beda63de7 --- /dev/null +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Platform/Windows/platform_windows.cmake @@ -0,0 +1,13 @@ +# +# Copyright (c) Contributors to the Open 3D Engine Project. +# For complete copyright and license terms please see the LICENSE at the root of this distribution. +# +# SPDX-License-Identifier: Apache-2.0 OR MIT +# +# + +if(NOT LY_MONOLITHIC_GAME) # Do not use OpenImageIO in monolithic game + set(LY_RUNTIME_DEPENDENCIES + 3rdParty::OpenImageIO + ) +endif() diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Platform/iOS/platform_ios.cmake b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Platform/iOS/platform_ios.cmake new file mode 100644 index 0000000000..7a325ca97e --- /dev/null +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Platform/iOS/platform_ios.cmake @@ -0,0 +1,7 @@ +# +# Copyright (c) Contributors to the Open 3D Engine Project. +# For complete copyright and license terms please see the LICENSE at the root of this distribution. +# +# SPDX-License-Identifier: Apache-2.0 OR MIT +# +# diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/PostProcess/DisplayMapper/DisplayMapperComponentController.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/PostProcess/DisplayMapper/DisplayMapperComponentController.cpp index e86c909121..a0577c6240 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/PostProcess/DisplayMapper/DisplayMapperComponentController.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/PostProcess/DisplayMapper/DisplayMapperComponentController.cpp @@ -357,8 +357,7 @@ namespace AZ void DisplayMapperComponentController::OnConfigChanged() { // Register the configuration with the AcesDisplayMapperFeatureProcessor for this scene. - const AZ::RPI::Scene* scene = AZ::RPI::RPISystemInterface::Get()->GetDefaultScene().get(); - DisplayMapperFeatureProcessorInterface* fp = scene->GetFeatureProcessor(); + DisplayMapperFeatureProcessorInterface* fp = AZ::RPI::Scene::GetFeatureProcessorForEntity(m_entityId); DisplayMapperConfigurationDescriptor desc; desc.m_operationType = m_configuration.m_displayMapperOperation; desc.m_ldrGradingLutEnabled = m_configuration.m_ldrColorGradingLutEnabled; diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/SkinnedMesh/SkinnedMeshDebugDisplay.h b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/SkinnedMesh/SkinnedMeshDebugDisplay.h index e789b6dc80..5b060198dc 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/SkinnedMesh/SkinnedMeshDebugDisplay.h +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/SkinnedMesh/SkinnedMeshDebugDisplay.h @@ -48,7 +48,7 @@ namespace AZ // CVar for toggling the display of the scene stats int r_skinnedMeshDisplaySceneStats = 0; // SceneId to query for the stats - RPI::SceneId m_sceneId = RPI::SceneId::CreateNull(); + RPI::SceneId m_sceneId; }; }// namespace Render }// namespace AZ diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/atomlyintegration_commonfeatures_editor_files.cmake b/Gems/AtomLyIntegration/CommonFeatures/Code/atomlyintegration_commonfeatures_editor_files.cmake index 2714b65a56..0dea725d70 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/atomlyintegration_commonfeatures_editor_files.cmake +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/atomlyintegration_commonfeatures_editor_files.cmake @@ -31,6 +31,8 @@ set(FILES Source/ImageBasedLights/EditorImageBasedLightComponent.cpp Source/Material/EditorMaterialComponent.cpp Source/Material/EditorMaterialComponent.h + Source/Material/EditorMaterialComponentSerializer.cpp + Source/Material/EditorMaterialComponentSerializer.h Source/Material/EditorMaterialComponentUtil.cpp Source/Material/EditorMaterialComponentUtil.h Source/Material/EditorMaterialComponentSlot.cpp diff --git a/Gems/AtomLyIntegration/EMotionFXAtom/Assets/Icons/Resources.qrc b/Gems/AtomLyIntegration/EMotionFXAtom/Assets/Icons/Resources.qrc index 28ae322d5b..7924ef1c4e 100644 --- a/Gems/AtomLyIntegration/EMotionFXAtom/Assets/Icons/Resources.qrc +++ b/Gems/AtomLyIntegration/EMotionFXAtom/Assets/Icons/Resources.qrc @@ -1,5 +1,6 @@ Camera_category.svg + Visualization.svg diff --git a/Gems/AtomLyIntegration/EMotionFXAtom/Assets/Icons/Visualization.svg b/Gems/AtomLyIntegration/EMotionFXAtom/Assets/Icons/Visualization.svg new file mode 100644 index 0000000000..3d1b40d1b6 --- /dev/null +++ b/Gems/AtomLyIntegration/EMotionFXAtom/Assets/Icons/Visualization.svg @@ -0,0 +1,9 @@ + + + + Icons / System / View + Created with Sketch. + + + + diff --git a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorDebugDraw.cpp b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorDebugDraw.cpp new file mode 100644 index 0000000000..50af91020a --- /dev/null +++ b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorDebugDraw.cpp @@ -0,0 +1,490 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace AZ::Render +{ + AtomActorDebugDraw::AtomActorDebugDraw(AZ::EntityId entityId) + { + m_auxGeomFeatureProcessor = RPI::Scene::GetFeatureProcessorForEntity(entityId); + } + + void AtomActorDebugDraw::DebugDraw(const EMotionFX::ActorRenderFlagBitset& renderFlags, EMotionFX::ActorInstance* instance) + { + if (!m_auxGeomFeatureProcessor || !instance) + { + return; + } + + RPI::AuxGeomDrawPtr auxGeom = m_auxGeomFeatureProcessor->GetDrawQueue(); + if (!auxGeom) + { + return; + } + + const AZ::Render::RenderActorSettings& renderActorSettings = EMotionFX::GetRenderActorSettings(); + + // Render aabb + if (renderFlags[EMotionFX::ActorRenderFlag::RENDER_AABB]) + { + RenderAABB(instance, renderActorSettings.m_staticAABBColor); + } + + // Render skeleton + if (renderFlags[EMotionFX::ActorRenderFlag::RENDER_LINESKELETON]) + { + RenderSkeleton(instance, renderActorSettings.m_skeletonColor); + } + + // Render internal EMFX debug lines. + if (renderFlags[EMotionFX::ActorRenderFlag::RENDER_EMFX_DEBUG]) + { + RenderEMFXDebugDraw(instance); + } + + // Render vertex normal, face normal, tagent and wireframe. + const bool renderVertexNormals = renderFlags[EMotionFX::ActorRenderFlag::RENDER_VERTEXNORMALS]; + const bool renderFaceNormals = renderFlags[EMotionFX::ActorRenderFlag::RENDER_FACENORMALS]; + const bool renderTangents = renderFlags[EMotionFX::ActorRenderFlag::RENDER_TANGENTS]; + const bool renderWireframe = renderFlags[EMotionFX::ActorRenderFlag::RENDER_WIREFRAME]; + + if (renderVertexNormals || renderFaceNormals || renderTangents || renderWireframe) + { + // Iterate through all enabled nodes + const EMotionFX::Pose* pose = instance->GetTransformData()->GetCurrentPose(); + const size_t geomLODLevel = instance->GetLODLevel(); + const size_t numEnabled = instance->GetNumEnabledNodes(); + const float scaleMultiplier = CalculateScaleMultiplier(instance); + for (size_t i = 0; i < numEnabled; ++i) + { + EMotionFX::Node* node = instance->GetActor()->GetSkeleton()->GetNode(instance->GetEnabledNode(i)); + EMotionFX::Mesh* mesh = instance->GetActor()->GetMesh(geomLODLevel, node->GetNodeIndex()); + const AZ::Transform globalTM = pose->GetWorldSpaceTransform(node->GetNodeIndex()).ToAZTransform(); + + m_currentMesh = nullptr; + + if (!mesh) + { + continue; + } + + RenderNormals(mesh, globalTM, renderVertexNormals, renderFaceNormals, renderActorSettings.m_vertexNormalsScale, + renderActorSettings.m_faceNormalsScale, scaleMultiplier, renderActorSettings.m_vertexNormalsColor, renderActorSettings.m_faceNormalsColor); + if (renderTangents) + { + RenderTangents(mesh, globalTM, renderActorSettings.m_tangentsScale, scaleMultiplier, + renderActorSettings.m_tangentsColor, renderActorSettings.m_mirroredBitangentsColor, renderActorSettings.m_bitangentsColor); + } + if (renderWireframe) + { + RenderWireframe(mesh, globalTM, renderActorSettings.m_wireframeScale, scaleMultiplier, renderActorSettings.m_wireframeColor); + } + } + } + } + + float AtomActorDebugDraw::CalculateScaleMultiplier(EMotionFX::ActorInstance* instance) const + { + const AZ::Aabb aabb = instance->GetAabb(); + const float aabbRadius = aabb.GetExtents().GetLength() * 0.5f; + // Scale the multiplier down to 1% of the character size, that looks pretty nice on most of the models. + return aabbRadius * 0.01f; + } + + void AtomActorDebugDraw::PrepareForMesh(EMotionFX::Mesh* mesh, const AZ::Transform& worldTM) + { + // Check if we have already prepared for the given mesh + if (m_currentMesh == mesh) + { + return; + } + + // Set our new current mesh + m_currentMesh = mesh; + + // Get the number of vertices and the data + const uint32 numVertices = m_currentMesh->GetNumVertices(); + AZ::Vector3* positions = (AZ::Vector3*)m_currentMesh->FindVertexData(EMotionFX::Mesh::ATTRIB_POSITIONS); + + // Check if the vertices fits in our buffer + if (m_worldSpacePositions.size() < numVertices) + { + m_worldSpacePositions.resize(numVertices); + } + + // Pre-calculate the world space positions + for (uint32 i = 0; i < numVertices; ++i) + { + m_worldSpacePositions[i] = worldTM.TransformPoint(positions[i]); + } + } + + void AtomActorDebugDraw::RenderAABB(EMotionFX::ActorInstance* instance, const AZ::Color& aabbColor) + { + RPI::AuxGeomDrawPtr auxGeom = m_auxGeomFeatureProcessor->GetDrawQueue(); + const AZ::Aabb& aabb = instance->GetAabb(); + auxGeom->DrawAabb(aabb, aabbColor, RPI::AuxGeomDraw::DrawStyle::Line); + } + + void AtomActorDebugDraw::RenderSkeleton(EMotionFX::ActorInstance* instance, const AZ::Color& skeletonColor) + { + RPI::AuxGeomDrawPtr auxGeom = m_auxGeomFeatureProcessor->GetDrawQueue(); + + const EMotionFX::TransformData* transformData = instance->GetTransformData(); + const EMotionFX::Skeleton* skeleton = instance->GetActor()->GetSkeleton(); + const EMotionFX::Pose* pose = transformData->GetCurrentPose(); + + const size_t lodLevel = instance->GetLODLevel(); + const size_t numJoints = skeleton->GetNumNodes(); + + m_auxVertices.clear(); + m_auxVertices.reserve(numJoints * 2); + + for (size_t jointIndex = 0; jointIndex < numJoints; ++jointIndex) + { + const EMotionFX::Node* joint = skeleton->GetNode(jointIndex); + if (!joint->GetSkeletalLODStatus(lodLevel)) + { + continue; + } + + const size_t parentIndex = joint->GetParentIndex(); + if (parentIndex == InvalidIndex) + { + continue; + } + + const AZ::Vector3 parentPos = pose->GetWorldSpaceTransform(parentIndex).m_position; + m_auxVertices.emplace_back(parentPos); + + const AZ::Vector3 bonePos = pose->GetWorldSpaceTransform(jointIndex).m_position; + m_auxVertices.emplace_back(bonePos); + } + + RPI::AuxGeomDraw::AuxGeomDynamicDrawArguments lineArgs; + lineArgs.m_verts = m_auxVertices.data(); + lineArgs.m_vertCount = static_cast(m_auxVertices.size()); + lineArgs.m_colors = &skeletonColor; + lineArgs.m_colorCount = 1; + lineArgs.m_depthTest = RPI::AuxGeomDraw::DepthTest::Off; + auxGeom->DrawLines(lineArgs); + } + + void AtomActorDebugDraw::RenderEMFXDebugDraw(EMotionFX::ActorInstance* instance) + { + RPI::AuxGeomDrawPtr auxGeom = m_auxGeomFeatureProcessor->GetDrawQueue(); + + EMotionFX::DebugDraw& debugDraw = EMotionFX::GetDebugDraw(); + debugDraw.Lock(); + EMotionFX::DebugDraw::ActorInstanceData* actorInstanceData = debugDraw.GetActorInstanceData(instance); + actorInstanceData->Lock(); + const AZStd::vector& lines = actorInstanceData->GetLines(); + if (lines.empty()) + { + actorInstanceData->Unlock(); + debugDraw.Unlock(); + return; + } + + m_auxVertices.clear(); + m_auxVertices.reserve(lines.size() * 2); + m_auxColors.clear(); + m_auxColors.reserve(m_auxVertices.size()); + + for (const EMotionFX::DebugDraw::Line& line : actorInstanceData->GetLines()) + { + m_auxVertices.emplace_back(line.m_start); + m_auxColors.emplace_back(line.m_startColor); + m_auxVertices.emplace_back(line.m_end); + m_auxColors.emplace_back(line.m_endColor); + } + + AZ_Assert(m_auxVertices.size() == m_auxColors.size(), "Number of vertices and number of colors need to match."); + actorInstanceData->Unlock(); + debugDraw.Unlock(); + + RPI::AuxGeomDraw::AuxGeomDynamicDrawArguments lineArgs; + lineArgs.m_verts = m_auxVertices.data(); + lineArgs.m_vertCount = static_cast(m_auxVertices.size()); + lineArgs.m_colors = m_auxColors.data(); + lineArgs.m_colorCount = static_cast(m_auxColors.size()); + lineArgs.m_depthTest = RPI::AuxGeomDraw::DepthTest::Off; + auxGeom->DrawLines(lineArgs); + } + + void AtomActorDebugDraw::RenderNormals( + EMotionFX::Mesh* mesh, + const AZ::Transform& worldTM, + bool vertexNormals, + bool faceNormals, + float vertexNormalsScale, + float faceNormalsScale, + float scaleMultiplier, + const AZ::Color& vertexNormalsColor, + const AZ::Color& faceNormalsColor) + { + if (!mesh) + { + return; + } + + if (!vertexNormals && !faceNormals) + { + return; + } + + RPI::AuxGeomDrawPtr auxGeom = m_auxGeomFeatureProcessor->GetDrawQueue(); + if (!auxGeom) + { + return; + } + + PrepareForMesh(mesh, worldTM); + + AZ::Vector3* normals = (AZ::Vector3*)mesh->FindVertexData(EMotionFX::Mesh::ATTRIB_NORMALS); + + // Render face normals + if (faceNormals) + { + const size_t numSubMeshes = mesh->GetNumSubMeshes(); + for (uint32 subMeshIndex = 0; subMeshIndex < numSubMeshes; ++subMeshIndex) + { + EMotionFX::SubMesh* subMesh = mesh->GetSubMesh(subMeshIndex); + const uint32 numTriangles = subMesh->GetNumPolygons(); + const uint32 startVertex = subMesh->GetStartVertex(); + const uint32* indices = subMesh->GetIndices(); + + m_auxVertices.clear(); + m_auxVertices.reserve(numTriangles * 2); + + for (uint32 triangleIndex = 0; triangleIndex < numTriangles; ++triangleIndex) + { + const uint32 triangleStartIndex = triangleIndex * 3; + const uint32 indexA = indices[triangleStartIndex + 0] + startVertex; + const uint32 indexB = indices[triangleStartIndex + 1] + startVertex; + const uint32 indexC = indices[triangleStartIndex + 2] + startVertex; + + const AZ::Vector3& posA = m_worldSpacePositions[indexA]; + const AZ::Vector3& posB = m_worldSpacePositions[indexB]; + const AZ::Vector3& posC = m_worldSpacePositions[indexC]; + + const AZ::Vector3 normalDir = (posB - posA).Cross(posC - posA).GetNormalized(); + + // Calculate the center pos + const AZ::Vector3 normalPos = (posA + posB + posC) * (1.0f / 3.0f); + + m_auxVertices.emplace_back(normalPos); + m_auxVertices.emplace_back(normalPos + (normalDir * faceNormalsScale * scaleMultiplier)); + } + } + + RPI::AuxGeomDraw::AuxGeomDynamicDrawArguments lineArgs; + lineArgs.m_verts = m_auxVertices.data(); + lineArgs.m_vertCount = static_cast(m_auxVertices.size()); + lineArgs.m_colors = &faceNormalsColor; + lineArgs.m_colorCount = 1; + lineArgs.m_depthTest = RPI::AuxGeomDraw::DepthTest::Off; + auxGeom->DrawLines(lineArgs); + } + + // render vertex normals + if (vertexNormals) + { + const size_t numSubMeshes = mesh->GetNumSubMeshes(); + for (uint32 subMeshIndex = 0; subMeshIndex < numSubMeshes; ++subMeshIndex) + { + EMotionFX::SubMesh* subMesh = mesh->GetSubMesh(subMeshIndex); + const uint32 numVertices = subMesh->GetNumVertices(); + const uint32 startVertex = subMesh->GetStartVertex(); + + m_auxVertices.clear(); + m_auxVertices.reserve(numVertices * 2); + + for (uint32 j = 0; j < numVertices; ++j) + { + const uint32 vertexIndex = j + startVertex; + const AZ::Vector3& position = m_worldSpacePositions[vertexIndex]; + const AZ::Vector3 normal = worldTM.TransformVector(normals[vertexIndex]).GetNormalizedSafe() * + vertexNormalsScale * scaleMultiplier; + + m_auxVertices.emplace_back(position); + m_auxVertices.emplace_back(position + normal); + } + } + + RPI::AuxGeomDraw::AuxGeomDynamicDrawArguments lineArgs; + lineArgs.m_verts = m_auxVertices.data(); + lineArgs.m_vertCount = static_cast(m_auxVertices.size()); + lineArgs.m_colors = &vertexNormalsColor; + lineArgs.m_colorCount = 1; + lineArgs.m_depthTest = RPI::AuxGeomDraw::DepthTest::Off; + auxGeom->DrawLines(lineArgs); + } + } + + void AtomActorDebugDraw::RenderTangents( + EMotionFX::Mesh* mesh, + const AZ::Transform& worldTM, + float tangentsScale, + float scaleMultiplier, + const AZ::Color& tangentsColor, + const AZ::Color& mirroredBitangentsColor, + const AZ::Color& bitangentsColor) + { + if (!mesh) + { + return; + } + + RPI::AuxGeomDrawPtr auxGeom = m_auxGeomFeatureProcessor->GetDrawQueue(); + if (!auxGeom) + { + return; + } + + // Get the tangents and check if this mesh actually has tangents + AZ::Vector4* tangents = static_cast(mesh->FindVertexData(EMotionFX::Mesh::ATTRIB_TANGENTS)); + if (!tangents) + { + return; + } + + AZ::Vector3* bitangents = static_cast(mesh->FindVertexData(EMotionFX::Mesh::ATTRIB_BITANGENTS)); + + PrepareForMesh(mesh, worldTM); + + AZ::Vector3* normals = (AZ::Vector3*)mesh->FindVertexData(EMotionFX::Mesh::ATTRIB_NORMALS); + const uint32 numVertices = mesh->GetNumVertices(); + + m_auxVertices.clear(); + m_auxVertices.reserve(numVertices * 2); + m_auxColors.clear(); + m_auxColors.reserve(m_auxVertices.size()); + + // Render the tangents and bitangents + AZ::Vector3 orgTangent, tangent, bitangent; + for (uint32 i = 0; i < numVertices; ++i) + { + orgTangent.Set(tangents[i].GetX(), tangents[i].GetY(), tangents[i].GetZ()); + tangent = (worldTM.TransformVector(orgTangent)).GetNormalized(); + + if (bitangents) + { + bitangent = bitangents[i]; + } + else + { + bitangent = tangents[i].GetW() * normals[i].Cross(orgTangent); + } + bitangent = (worldTM.TransformVector(bitangent)).GetNormalizedSafe(); + + m_auxVertices.emplace_back(m_worldSpacePositions[i]); + m_auxColors.emplace_back(tangentsColor); + m_auxVertices.emplace_back(m_worldSpacePositions[i] + (tangent * tangentsScale * scaleMultiplier)); + m_auxColors.emplace_back(tangentsColor); + + if (tangents[i].GetW() < 0.0f) + { + m_auxVertices.emplace_back(m_worldSpacePositions[i]); + m_auxColors.emplace_back(mirroredBitangentsColor); + m_auxVertices.emplace_back(m_worldSpacePositions[i] + (bitangent * tangentsScale * scaleMultiplier)); + m_auxColors.emplace_back(mirroredBitangentsColor); + } + else + { + m_auxVertices.emplace_back(m_worldSpacePositions[i]); + m_auxColors.emplace_back(bitangentsColor); + m_auxVertices.emplace_back(m_worldSpacePositions[i] + (bitangent * tangentsScale * scaleMultiplier)); + m_auxColors.emplace_back(bitangentsColor); + } + } + + RPI::AuxGeomDraw::AuxGeomDynamicDrawArguments lineArgs; + lineArgs.m_verts = m_auxVertices.data(); + lineArgs.m_vertCount = static_cast(m_auxVertices.size()); + lineArgs.m_colors = m_auxColors.data(); + lineArgs.m_colorCount = static_cast(m_auxColors.size()); + lineArgs.m_depthTest = RPI::AuxGeomDraw::DepthTest::Off; + auxGeom->DrawLines(lineArgs); + } + + void AtomActorDebugDraw::RenderWireframe( + EMotionFX::Mesh* mesh, const AZ::Transform& worldTM, float wireframeScale, float scaleMultiplier, const AZ::Color& wireframeColor) + { + // Check if the mesh is valid and skip the node in case it's not + if (!mesh) + { + return; + } + + RPI::AuxGeomDrawPtr auxGeom = m_auxGeomFeatureProcessor->GetDrawQueue(); + if (!auxGeom) + { + return; + } + + PrepareForMesh(mesh, worldTM); + + const AZ::Vector3* normals = (AZ::Vector3*)mesh->FindVertexData(EMotionFX::Mesh::ATTRIB_NORMALS); + + const size_t numSubMeshes = mesh->GetNumSubMeshes(); + for (uint32 subMeshIndex = 0; subMeshIndex < numSubMeshes; ++subMeshIndex) + { + EMotionFX::SubMesh* subMesh = mesh->GetSubMesh(subMeshIndex); + const uint32 numTriangles = subMesh->GetNumPolygons(); + const uint32 startVertex = subMesh->GetStartVertex(); + const uint32* indices = subMesh->GetIndices(); + + m_auxVertices.clear(); + m_auxVertices.reserve(numTriangles * 6); + + for (uint32 triangleIndex = 0; triangleIndex < numTriangles; ++triangleIndex) + { + const uint32 triangleStartIndex = triangleIndex * 3; + const uint32 indexA = indices[triangleStartIndex + 0] + startVertex; + const uint32 indexB = indices[triangleStartIndex + 1] + startVertex; + const uint32 indexC = indices[triangleStartIndex + 2] + startVertex; + + const AZ::Vector3 posA = m_worldSpacePositions[indexA] + normals[indexA] * wireframeScale * scaleMultiplier; + const AZ::Vector3 posB = m_worldSpacePositions[indexB] + normals[indexB] * wireframeScale * scaleMultiplier; + const AZ::Vector3 posC = m_worldSpacePositions[indexC] + normals[indexC] * wireframeScale * scaleMultiplier; + + m_auxVertices.emplace_back(posA); + m_auxVertices.emplace_back(posB); + + m_auxVertices.emplace_back(posB); + m_auxVertices.emplace_back(posC); + + m_auxVertices.emplace_back(posC); + m_auxVertices.emplace_back(posA); + } + + RPI::AuxGeomDraw::AuxGeomDynamicDrawArguments lineArgs; + lineArgs.m_verts = m_auxVertices.data(); + lineArgs.m_vertCount = static_cast(m_auxVertices.size()); + lineArgs.m_colors = &wireframeColor; + lineArgs.m_colorCount = 1; + lineArgs.m_depthTest = RPI::AuxGeomDraw::DepthTest::Off; + auxGeom->DrawLines(lineArgs); + } + } +} // namespace AZ::Render diff --git a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorDebugDraw.h b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorDebugDraw.h new file mode 100644 index 0000000000..4178ad25f1 --- /dev/null +++ b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorDebugDraw.h @@ -0,0 +1,70 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include +#include +#include +#include + +namespace EMotionFX +{ + class Mesh; + class ActorInstance; +} + +namespace AZ::RPI +{ + class AuxGeomDraw; + class AuxGeomFeatureProcessorInterface; +} + +namespace AZ::Render +{ + // Ultility class for atom debug render on actor + class AtomActorDebugDraw + { + public: + AtomActorDebugDraw(AZ::EntityId entityId); + + void DebugDraw(const EMotionFX::ActorRenderFlagBitset& renderFlags, EMotionFX::ActorInstance* instance); + + private: + + float CalculateScaleMultiplier(EMotionFX::ActorInstance* instance) const; + void PrepareForMesh(EMotionFX::Mesh* mesh, const AZ::Transform& worldTM); + void RenderAABB(EMotionFX::ActorInstance* instance, const AZ::Color& aabbColor); + void RenderSkeleton(EMotionFX::ActorInstance* instance, const AZ::Color& skeletonColor); + void RenderEMFXDebugDraw(EMotionFX::ActorInstance* instance); + void RenderNormals( + EMotionFX::Mesh* mesh, + const AZ::Transform& worldTM, + bool vertexNormals, + bool faceNormals, + float vertexNormalsScale, + float faceNormalsScale, + float scaleMultiplier, + const AZ::Color& vertexNormalsColor, + const AZ::Color& faceNormalsColor); + void RenderTangents( + EMotionFX::Mesh* mesh, const AZ::Transform& worldTM, float tangentsScale, float scaleMultiplier, + const AZ::Color& tangentsColor, const AZ::Color& mirroredBitangentsColor, const AZ::Color& bitangentsColor); + void RenderWireframe(EMotionFX::Mesh* mesh, const AZ::Transform& worldTM, float wireframeScale, float scaleMultiplier, + const AZ::Color& wireframeColor); + + EMotionFX::Mesh* m_currentMesh = nullptr; /**< A pointer to the mesh whose world space positions are in the pre-calculated positions buffer. + NULL in case we haven't pre-calculated any positions yet. */ + AZStd::vector m_worldSpacePositions; /**< The buffer used to store world space positions for rendering normals + tangents and the wireframe. */ + + RPI::AuxGeomFeatureProcessorInterface* m_auxGeomFeatureProcessor = nullptr; + AZStd::vector m_auxVertices; + AZStd::vector m_auxColors; + }; +} diff --git a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorInstance.cpp b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorInstance.cpp index a3161002a0..4d1b42a0eb 100644 --- a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorInstance.cpp +++ b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorInstance.cpp @@ -8,6 +8,7 @@ #include #include +#include #include #include @@ -59,7 +60,7 @@ namespace AZ AzFramework::BoundsRequestBus::Handler::BusConnect(m_entityId); } - m_auxGeomFeatureProcessor = RPI::Scene::GetFeatureProcessorForEntity(m_entityId); + m_atomActorDebugDraw = AZStd::make_unique(entityId); } AtomActorInstance::~AtomActorInstance() @@ -78,6 +79,11 @@ namespace AZ UpdateBounds(); } + void AtomActorInstance::DebugDraw(const EMotionFX::ActorRenderFlagBitset& renderFlags) + { + m_atomActorDebugDraw->DebugDraw(renderFlags, m_actorInstance); + } + void AtomActorInstance::UpdateBounds() { // Update RenderActorInstance world bounding box @@ -99,116 +105,6 @@ namespace AZ AZ::Interface::Get()->RefreshEntityLocalBoundsUnion(m_entityId); } - void AtomActorInstance::DebugDraw(const DebugOptions& debugOptions) - { - if (m_auxGeomFeatureProcessor) - { - if (RPI::AuxGeomDrawPtr auxGeom = m_auxGeomFeatureProcessor->GetDrawQueue()) - { - if (debugOptions.m_drawAABB) - { - const AZ::Aabb& aabb = m_actorInstance->GetAabb(); - auxGeom->DrawAabb(aabb, AZ::Color(0.0f, 1.0f, 1.0f, 1.0f), RPI::AuxGeomDraw::DrawStyle::Line); - } - - if (debugOptions.m_drawSkeleton) - { - RenderSkeleton(auxGeom.get()); - } - - if (debugOptions.m_emfxDebugDraw) - { - RenderEMFXDebugDraw(auxGeom.get()); - } - } - } - } - - void AtomActorInstance::RenderSkeleton(RPI::AuxGeomDraw* auxGeom) - { - AZ_Assert(m_actorInstance, "Valid actor instance required."); - const EMotionFX::TransformData* transformData = m_actorInstance->GetTransformData(); - const EMotionFX::Skeleton* skeleton = m_actorInstance->GetActor()->GetSkeleton(); - const EMotionFX::Pose* pose = transformData->GetCurrentPose(); - - const size_t lodLevel = m_actorInstance->GetLODLevel(); - const size_t numJoints = skeleton->GetNumNodes(); - - m_auxVertices.clear(); - m_auxVertices.reserve(numJoints * 2); - - for (size_t jointIndex = 0; jointIndex < numJoints; ++jointIndex) - { - const EMotionFX::Node* joint = skeleton->GetNode(jointIndex); - if (!joint->GetSkeletalLODStatus(lodLevel)) - { - continue; - } - - const size_t parentIndex = joint->GetParentIndex(); - if (parentIndex == InvalidIndex) - { - continue; - } - - const AZ::Vector3 parentPos = pose->GetWorldSpaceTransform(parentIndex).m_position; - m_auxVertices.emplace_back(parentPos); - - const AZ::Vector3 bonePos = pose->GetWorldSpaceTransform(jointIndex).m_position; - m_auxVertices.emplace_back(bonePos); - } - - const AZ::Color skeletonColor(0.604f, 0.804f, 0.196f, 1.0f); - RPI::AuxGeomDraw::AuxGeomDynamicDrawArguments lineArgs; - lineArgs.m_verts = m_auxVertices.data(); - lineArgs.m_vertCount = static_cast(m_auxVertices.size()); - lineArgs.m_colors = &skeletonColor; - lineArgs.m_colorCount = 1; - lineArgs.m_depthTest = RPI::AuxGeomDraw::DepthTest::Off; - auxGeom->DrawLines(lineArgs); - } - - void AtomActorInstance::RenderEMFXDebugDraw(RPI::AuxGeomDraw* auxGeom) - { - EMotionFX::DebugDraw& debugDraw = EMotionFX::GetDebugDraw(); - debugDraw.Lock(); - EMotionFX::DebugDraw::ActorInstanceData* actorInstanceData = debugDraw.GetActorInstanceData(m_actorInstance); - actorInstanceData->Lock(); - const AZStd::vector& lines = actorInstanceData->GetLines(); - if (lines.empty()) - { - actorInstanceData->Unlock(); - debugDraw.Unlock(); - return; - } - - m_auxVertices.clear(); - m_auxVertices.reserve(lines.size() * 2); - m_auxColors.clear(); - m_auxColors.reserve(m_auxVertices.size()); - - for (const EMotionFX::DebugDraw::Line& line : actorInstanceData->GetLines()) - { - m_auxVertices.emplace_back(line.m_start); - m_auxColors.emplace_back(line.m_startColor); - m_auxVertices.emplace_back(line.m_end); - m_auxColors.emplace_back(line.m_endColor); - } - - AZ_Assert(m_auxVertices.size() == m_auxColors.size(), - "Number of vertices and number of colors need to match."); - actorInstanceData->Unlock(); - debugDraw.Unlock(); - - RPI::AuxGeomDraw::AuxGeomDynamicDrawArguments lineArgs; - lineArgs.m_verts = m_auxVertices.data(); - lineArgs.m_vertCount = static_cast(m_auxVertices.size()); - lineArgs.m_colors = m_auxColors.data(); - lineArgs.m_colorCount = static_cast(m_auxColors.size()); - lineArgs.m_depthTest = RPI::AuxGeomDraw::DepthTest::Off; - auxGeom->DrawLines(lineArgs); - } - AZ::Aabb AtomActorInstance::GetWorldBounds() { return m_worldAABB; diff --git a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorInstance.h b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorInstance.h index 5ddab8bc61..7f646466a5 100644 --- a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorInstance.h +++ b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorInstance.h @@ -53,6 +53,7 @@ namespace AZ class SkinnedMeshInputBuffers; class MeshFeatureProcessorInterface; class AtomActor; + class AtomActorDebugDraw; //! Render node for managing and rendering actor instances. Each Actor Component //! creates an ActorRenderNode. The render node is responsible for drawing meshes and @@ -85,8 +86,8 @@ namespace AZ // RenderActorInstance overrides ... void OnTick(float timeDelta) override; + void DebugDraw(const EMotionFX::ActorRenderFlagBitset& renderFlags); void UpdateBounds() override; - void DebugDraw(const DebugOptions& debugOptions) override; void SetMaterials(const EMotionFX::Integration::ActorAsset::MaterialList& materialPerLOD) override { AZ_UNUSED(materialPerLOD); }; void SetSkinningMethod(EMotionFX::Integration::SkinningMethod emfxSkinningMethod) override; SkinningMethod GetAtomSkinningMethod() const; @@ -184,12 +185,8 @@ namespace AZ void InitWrinkleMasks(); void UpdateWrinkleMasks(); - // Helper and debug geometry rendering - void RenderSkeleton(RPI::AuxGeomDraw* auxGeom); - void RenderEMFXDebugDraw(RPI::AuxGeomDraw* auxGeom); - RPI::AuxGeomFeatureProcessorInterface* m_auxGeomFeatureProcessor = nullptr; - AZStd::vector m_auxVertices; - AZStd::vector m_auxColors; + // Debug geometry rendering + AZStd::unique_ptr m_atomActorDebugDraw; AZStd::intrusive_ptr m_skinnedMeshInputBuffers = nullptr; AZStd::intrusive_ptr m_skinnedMeshInstance; diff --git a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AnimViewportRenderer.cpp b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AnimViewportRenderer.cpp index faa033956a..4c17eaf06a 100644 --- a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AnimViewportRenderer.cpp +++ b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AnimViewportRenderer.cpp @@ -37,14 +37,13 @@ #include #include #include - +#include namespace EMStudio { - static constexpr float DepthNear = 0.01f; - - AnimViewportRenderer::AnimViewportRenderer(AZ::RPI::ViewportContextPtr viewportContext) + AnimViewportRenderer::AnimViewportRenderer(AZ::RPI::ViewportContextPtr viewportContext, const RenderOptions* renderOptions) : m_windowContext(viewportContext->GetWindowContext()) + , m_renderOptions(renderOptions) { // Create a new entity context m_entityContext = AZStd::make_unique(); @@ -60,6 +59,7 @@ namespace EMStudio // Create and register a scene with all available feature processors AZ::RPI::SceneDescriptor sceneDesc; + sceneDesc.m_nameId = AZ::Name("AnimViewport"); m_scene = AZ::RPI::Scene::CreateScene(sceneDesc); m_scene->EnableAllFeatureProcessors(); @@ -128,10 +128,10 @@ namespace EMStudio AZ_Assert(m_gridEntity != nullptr, "Failed to create grid entity."); AZ::Render::GridComponentConfig gridConfig; - gridConfig.m_gridSize = 4.0f; - gridConfig.m_axisColor = AZ::Color(0.5f, 0.5f, 0.5f, 1.0f); - gridConfig.m_primaryColor = AZ::Color(0.3f, 0.3f, 0.3f, 1.0f); - gridConfig.m_secondaryColor = AZ::Color(0.5f, 0.1f, 0.1f, 1.0f); + gridConfig.m_secondarySpacing = m_renderOptions->GetGridUnitSize(); + gridConfig.m_axisColor = m_renderOptions->GetMainAxisColor(); + gridConfig.m_primaryColor = m_renderOptions->GetGridColor(); + gridConfig.m_secondaryColor = m_renderOptions->GetSubStepColor(); auto gridComponent = m_gridEntity->CreateComponent(AZ::Render::GridComponentTypeId); gridComponent->SetConfiguration(gridConfig); @@ -206,6 +206,20 @@ namespace EMStudio return result; } + void AnimViewportRenderer::UpdateActorRenderFlag(EMotionFX::ActorRenderFlagBitset renderFlags) + { + for (AZ::Entity* entity : m_actorEntities) + { + EMotionFX::Integration::ActorComponent* actorComponent = entity->FindComponent(); + if (!actorComponent) + { + AZ_Assert(false, "Found entity without actor component in the actor entity list."); + continue; + } + actorComponent->SetRenderFlag(renderFlags); + } + } + void AnimViewportRenderer::ResetEnvironment() { // Reset environment @@ -213,8 +227,7 @@ namespace EMStudio AZ::TransformBus::Event(m_iblEntity->GetId(), &AZ::TransformBus::Events::SetLocalTM, iblTransform); const AZ::Matrix4x4 rotationMatrix = AZ::Matrix4x4::CreateIdentity(); - AZ::RPI::ScenePtr scene = AZ::RPI::RPISystemInterface::Get()->GetDefaultScene(); - auto skyBoxFeatureProcessorInterface = scene->GetFeatureProcessor(); + auto skyBoxFeatureProcessorInterface = m_scene->GetFeatureProcessor(); skyBoxFeatureProcessorInterface->SetCubemapRotationMatrix(rotationMatrix); } @@ -312,8 +325,8 @@ namespace EMStudio ->GetOrCreateExposureControlSettingsInterface(); Camera::Configuration cameraConfig; - cameraConfig.m_fovRadians = AZ::Constants::HalfPi; - cameraConfig.m_nearClipDistance = DepthNear; + cameraConfig.m_fovRadians = AZ::DegToRad(m_renderOptions->GetFOV()); + cameraConfig.m_nearClipDistance = m_renderOptions->GetNearClipPlaneDistance(); preset->ApplyLightingPreset( iblFeatureProcessor, m_skyboxFeatureProcessor, exposureControlSettingInterface, m_directionalLightFeatureProcessor, diff --git a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AnimViewportRenderer.h b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AnimViewportRenderer.h index 1de4cbdb1c..df69856355 100644 --- a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AnimViewportRenderer.h +++ b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AnimViewportRenderer.h @@ -39,12 +39,14 @@ namespace AZ namespace EMStudio { + class RenderOptions; + class AnimViewportRenderer { public: AZ_CLASS_ALLOCATOR(AnimViewportRenderer, AZ::SystemAllocator, 0); - AnimViewportRenderer(AZ::RPI::ViewportContextPtr viewportContext); + AnimViewportRenderer(AZ::RPI::ViewportContextPtr viewportContext, const RenderOptions* renderOptions); ~AnimViewportRenderer(); void Reinit(); @@ -52,6 +54,8 @@ namespace EMStudio //! Return the center position of the existing objects. AZ::Vector3 GetCharacterCenter() const; + void UpdateActorRenderFlag(EMotionFX::ActorRenderFlagBitset renderFlags); + private: // This function resets the light, camera and other environment settings. @@ -79,12 +83,9 @@ namespace EMStudio AZ::Entity* m_postProcessEntity = nullptr; AZ::Entity* m_iblEntity = nullptr; - AZ::Entity* m_cameraEntity = nullptr; - AZ::Component* m_cameraComponent = nullptr; - AZ::Entity* m_modelEntity = nullptr; - AZ::Data::AssetId m_modelAssetId; AZ::Entity* m_gridEntity = nullptr; AZStd::vector m_actorEntities; + const RenderOptions* m_renderOptions; AZStd::vector m_lightHandles; }; diff --git a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AnimViewportRequestBus.h b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AnimViewportRequestBus.h index 03784c2158..da4a054c53 100644 --- a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AnimViewportRequestBus.h +++ b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AnimViewportRequestBus.h @@ -8,7 +8,7 @@ #pragma once #include - +#include namespace EMStudio { @@ -35,6 +35,9 @@ namespace EMStudio //! Set the camera view mode. virtual void SetCameraViewMode(CameraViewMode mode) = 0; + + //! Toggle render option flag + virtual void ToggleRenderFlag(EMotionFX::ActorRenderFlag flag) = 0; }; using AnimViewportRequestBus = AZ::EBus; diff --git a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AnimViewportToolBar.cpp b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AnimViewportToolBar.cpp index a47a773e10..50cd088f5d 100644 --- a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AnimViewportToolBar.cpp +++ b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AnimViewportToolBar.cpp @@ -13,7 +13,6 @@ #include #include - namespace EMStudio { AnimViewportToolBar::AnimViewportToolBar(QWidget* parent) @@ -21,40 +20,107 @@ namespace EMStudio { AzQtComponents::ToolBar::addMainToolBarStyle(this); - // Add the camera button - QToolButton* cameraButton = new QToolButton(this); - QMenu* cameraMenu = new QMenu(cameraButton); - - // Add the camera option - const AZStd::vector> cameraOptionNames = { - { CameraViewMode::FRONT, "Front" }, { CameraViewMode::BACK, "Back" }, { CameraViewMode::TOP, "Top" }, - { CameraViewMode::BOTTOM, "Bottom" }, { CameraViewMode::LEFT, "Left" }, { CameraViewMode::RIGHT, "Right" }, - }; - - for (const auto& pair : cameraOptionNames) + // Add the render view options button + QToolButton* renderOptionsButton = new QToolButton(this); { - CameraViewMode mode = pair.first; - cameraMenu->addAction( - pair.second.c_str(), - [mode]() - { - // Send the reset camera event. - AnimViewportRequestBus::Broadcast(&AnimViewportRequestBus::Events::SetCameraViewMode, mode); - }); + QMenu* contextMenu = new QMenu(renderOptionsButton); + + renderOptionsButton->setText("Render Options"); + renderOptionsButton->setMenu(contextMenu); + renderOptionsButton->setPopupMode(QToolButton::InstantPopup); + renderOptionsButton->setVisible(true); + renderOptionsButton->setIcon(QIcon(":/EMotionFXAtom/Visualization.svg")); + addWidget(renderOptionsButton); + + CreateViewOptionEntry(contextMenu, "Solid", EMotionFX::ActorRenderFlag::RENDER_SOLID); + CreateViewOptionEntry(contextMenu, "Wireframe", EMotionFX::ActorRenderFlag::RENDER_WIREFRAME); + CreateViewOptionEntry(contextMenu, "Lighting", EMotionFX::ActorRenderFlag::RENDER_LIGHTING); + CreateViewOptionEntry(contextMenu, "Backface Culling", EMotionFX::ActorRenderFlag::RENDER_BACKFACECULLING); + contextMenu->addSeparator(); + CreateViewOptionEntry(contextMenu, "Vertex Normals", EMotionFX::ActorRenderFlag::RENDER_VERTEXNORMALS); + CreateViewOptionEntry(contextMenu, "Face Normals", EMotionFX::ActorRenderFlag::RENDER_FACENORMALS); + CreateViewOptionEntry(contextMenu, "Tangents", EMotionFX::ActorRenderFlag::RENDER_TANGENTS); + CreateViewOptionEntry(contextMenu, "Actor Bounding Boxes", EMotionFX::ActorRenderFlag::RENDER_AABB); + contextMenu->addSeparator(); + CreateViewOptionEntry(contextMenu, "Line Skeleton", EMotionFX::ActorRenderFlag::RENDER_LINESKELETON); + CreateViewOptionEntry(contextMenu, "Solid Skeleton", EMotionFX::ActorRenderFlag::RENDER_SKELETON); + CreateViewOptionEntry(contextMenu, "Joint Names", EMotionFX::ActorRenderFlag::RENDER_NODENAMES); + CreateViewOptionEntry(contextMenu, "Joint Orientations", EMotionFX::ActorRenderFlag::RENDER_NODEORIENTATION); + CreateViewOptionEntry(contextMenu, "Actor Bind Pose", EMotionFX::ActorRenderFlag::RENDER_ACTORBINDPOSE); + contextMenu->addSeparator(); } - cameraMenu->addSeparator(); - cameraMenu->addAction("Reset Camera", - []() + // Add the camera button + QToolButton* cameraButton = new QToolButton(this); + { + QMenu* cameraMenu = new QMenu(cameraButton); + + // Add the camera option + const AZStd::vector> cameraOptionNames = { + { CameraViewMode::FRONT, "Front" }, { CameraViewMode::BACK, "Back" }, { CameraViewMode::TOP, "Top" }, + { CameraViewMode::BOTTOM, "Bottom" }, { CameraViewMode::LEFT, "Left" }, { CameraViewMode::RIGHT, "Right" }, + }; + + for (const auto& pair : cameraOptionNames) + { + CameraViewMode mode = pair.first; + cameraMenu->addAction( + pair.second.c_str(), + [mode]() + { + // Send the reset camera event. + AnimViewportRequestBus::Broadcast(&AnimViewportRequestBus::Events::SetCameraViewMode, mode); + }); + } + + cameraMenu->addSeparator(); + cameraMenu->addAction( + "Reset Camera", + []() + { + // Send the reset camera event. + AnimViewportRequestBus::Broadcast(&AnimViewportRequestBus::Events::ResetCamera); + }); + cameraButton->setMenu(cameraMenu); + cameraButton->setText("Camera Option"); + cameraButton->setPopupMode(QToolButton::InstantPopup); + cameraButton->setVisible(true); + cameraButton->setIcon(QIcon(":/EMotionFXAtom/Camera_category.svg")); + addWidget(cameraButton); + } + } + + void AnimViewportToolBar::CreateViewOptionEntry( + QMenu* menu, const char* menuEntryName, uint32_t actionIndex, bool visible, char* iconFileName) + { + QAction* action = menu->addAction( + menuEntryName, + [actionIndex]() { // Send the reset camera event. - AnimViewportRequestBus::Broadcast(&AnimViewportRequestBus::Events::ResetCamera); + AnimViewportRequestBus::Broadcast( + &AnimViewportRequestBus::Events::ToggleRenderFlag, (EMotionFX::ActorRenderFlag)actionIndex); }); - cameraButton->setMenu(cameraMenu); - cameraButton->setText("Camera Option"); - cameraButton->setPopupMode(QToolButton::InstantPopup); - cameraButton->setVisible(true); - cameraButton->setIcon(QIcon(":/EMotionFXAtom/Camera_category.svg")); - addWidget(cameraButton); + action->setCheckable(true); + action->setVisible(visible); + + if (iconFileName) + { + action->setIcon(QIcon(iconFileName)); + } + + m_actions[actionIndex] = action; + } + + void AnimViewportToolBar::SetRenderFlags(EMotionFX::ActorRenderFlagBitset renderFlags) + { + for (size_t i = 0; i < renderFlags.size(); ++i) + { + QAction* action = m_actions[i]; + if (action) + { + action->setChecked(renderFlags[i]); + } + } } } // namespace EMStudio diff --git a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AnimViewportToolBar.h b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AnimViewportToolBar.h index 23ef5fdcd8..57633e5284 100644 --- a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AnimViewportToolBar.h +++ b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AnimViewportToolBar.h @@ -11,8 +11,11 @@ #if !defined(Q_MOC_RUN) #include #include +#include #endif +#include + namespace EMStudio { class AnimViewportToolBar : public QToolBar @@ -20,5 +23,13 @@ namespace EMStudio public: AnimViewportToolBar(QWidget* parent = nullptr); ~AnimViewportToolBar() = default; + + void SetRenderFlags(EMotionFX::ActorRenderFlagBitset renderFlags); + + private: + void CreateViewOptionEntry( + QMenu* menu, const char* menuEntryName, uint32_t actionIndex, bool visible = true, char* iconFileName = nullptr); + + QAction* m_actions[EMotionFX::ActorRenderFlag::NUM_RENDERFLAGS] = { nullptr }; }; } diff --git a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AnimViewportWidget.cpp b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AnimViewportWidget.cpp index 2e05864adc..cf7341e9ab 100644 --- a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AnimViewportWidget.cpp +++ b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AnimViewportWidget.cpp @@ -6,20 +6,24 @@ * */ -#include +#include #include #include #include +#include #include +#include #include #include #include +#include namespace EMStudio { - AnimViewportWidget::AnimViewportWidget(QWidget* parent) - : AtomToolsFramework::RenderViewportWidget(parent) + AnimViewportWidget::AnimViewportWidget(AtomRenderPlugin* parentPlugin) + : AtomToolsFramework::RenderViewportWidget(parentPlugin->GetInnerWidget()) + , m_plugin(parentPlugin) { setObjectName(QString::fromUtf8("AtomViewportWidget")); QSizePolicy qSize(QSizePolicy::Preferred, QSizePolicy::Preferred); @@ -30,8 +34,9 @@ namespace EMStudio setAutoFillBackground(false); setStyleSheet(QString::fromUtf8("")); - m_renderer = AZStd::make_unique(GetViewportContext()); + m_renderer = AZStd::make_unique(GetViewportContext(), m_plugin->GetRenderOptions()); + LoadRenderFlags(); SetupCameras(); SetupCameraController(); Reinit(); @@ -41,6 +46,7 @@ namespace EMStudio AnimViewportWidget::~AnimViewportWidget() { + SaveRenderFlags(); AnimViewportRequestBus::Handler::BusDisconnect(); } @@ -50,7 +56,14 @@ namespace EMStudio { ResetCamera(); } + m_renderer->Reinit(); + m_renderer->UpdateActorRenderFlag(m_renderFlags); + } + + EMotionFX::ActorRenderFlagBitset AnimViewportWidget::GetRenderFlags() const + { + return m_renderFlags; } void AnimViewportWidget::SetupCameras() @@ -123,7 +136,7 @@ namespace EMStudio SetCameraViewMode(CameraViewMode::DEFAULT); } - void AnimViewportWidget::SetCameraViewMode([[maybe_unused]]CameraViewMode mode) + void AnimViewportWidget::SetCameraViewMode(CameraViewMode mode) { // Set the camera view mode. const AZ::Vector3 targetPosition = m_renderer->GetCharacterCenter(); @@ -155,4 +168,60 @@ namespace EMStudio } GetViewportContext()->SetCameraTransform(AZ::Transform::CreateLookAt(cameraPosition, targetPosition)); } + + void AnimViewportWidget::OnTick(float deltaTime, AZ::ScriptTimePoint time) + { + RenderViewportWidget::OnTick(deltaTime, time); + CalculateCameraProjection(); + } + + void AnimViewportWidget::CalculateCameraProjection() + { + auto viewportContext = GetViewportContext(); + auto windowSize = viewportContext->GetViewportSize(); + // Prevent devided by zero + const float height = AZStd::max(aznumeric_cast(windowSize.m_height), 1.0f); + const float aspectRatio = aznumeric_cast(windowSize.m_width) / height; + + const RenderOptions* renderOptions = m_plugin->GetRenderOptions(); + AZ::Matrix4x4 viewToClipMatrix; + AZ::MakePerspectiveFovMatrixRH(viewToClipMatrix, AZ::DegToRad(renderOptions->GetFOV()), aspectRatio, + renderOptions->GetNearClipPlaneDistance(), renderOptions->GetFarClipPlaneDistance(), true); + + viewportContext->GetDefaultView()->SetViewToClipMatrix(viewToClipMatrix); + } + + void AnimViewportWidget::ToggleRenderFlag(EMotionFX::ActorRenderFlag flag) + { + m_renderFlags[flag] = !m_renderFlags[flag]; + m_renderer->UpdateActorRenderFlag(m_renderFlags); + } + + void AnimViewportWidget::LoadRenderFlags() + { + AZStd::string renderFlagsFilename(EMStudioManager::GetInstance()->GetAppDataFolder()); + renderFlagsFilename += "AnimViewportRenderFlags.cfg"; + QSettings settings(renderFlagsFilename.c_str(), QSettings::IniFormat, this); + + for (uint32 i = 0; i < EMotionFX::ActorRenderFlag::NUM_RENDERFLAGS; ++i) + { + QString name = QString(i); + const bool isEnabled = settings.value(name).toBool(); + m_renderFlags[i] = isEnabled; + } + m_renderer->UpdateActorRenderFlag(m_renderFlags); + } + + void AnimViewportWidget::SaveRenderFlags() + { + AZStd::string renderFlagsFilename(EMStudioManager::GetInstance()->GetAppDataFolder()); + renderFlagsFilename += "AnimViewportRenderFlags.cfg"; + QSettings settings(renderFlagsFilename.c_str(), QSettings::IniFormat, this); + + for (uint32 i = 0; i < EMotionFX::ActorRenderFlag::NUM_RENDERFLAGS; ++i) + { + QString name = QString(i); + settings.setValue(name, (bool)m_renderFlags[i]); + } + } } // namespace EMStudio diff --git a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AnimViewportWidget.h b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AnimViewportWidget.h index 6d708b0996..7a8ae2cf52 100644 --- a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AnimViewportWidget.h +++ b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AnimViewportWidget.h @@ -7,12 +7,15 @@ */ #pragma once +#include #include #include #include +#include namespace EMStudio { + class AtomRenderPlugin; class AnimViewportRenderer; class AnimViewportWidget @@ -20,25 +23,35 @@ namespace EMStudio , private AnimViewportRequestBus::Handler { public: - AnimViewportWidget(QWidget* parent = nullptr); + AnimViewportWidget(AtomRenderPlugin* parentPlugin); ~AnimViewportWidget() override; AnimViewportRenderer* GetAnimViewportRenderer() { return m_renderer.get(); } void Reinit(bool resetCamera = true); + EMotionFX::ActorRenderFlagBitset GetRenderFlags() const; private: + void OnTick(float deltaTime, AZ::ScriptTimePoint time) override; + + void CalculateCameraProjection(); void SetupCameras(); void SetupCameraController(); + void LoadRenderFlags(); + void SaveRenderFlags(); + // AnimViewportRequestBus::Handler overrides void ResetCamera(); void SetCameraViewMode(CameraViewMode mode); + void ToggleRenderFlag(EMotionFX::ActorRenderFlag flag); static constexpr float CameraDistance = 2.0f; + AtomRenderPlugin* m_plugin; AZStd::unique_ptr m_renderer; AZStd::shared_ptr m_rotateCamera; AZStd::shared_ptr m_translateCamera; AZStd::shared_ptr m_orbitDollyScrollCamera; + EMotionFX::ActorRenderFlagBitset m_renderFlags; }; } diff --git a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AtomRenderPlugin.cpp b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AtomRenderPlugin.cpp index bc74592485..297bfea760 100644 --- a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AtomRenderPlugin.cpp +++ b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AtomRenderPlugin.cpp @@ -27,7 +27,10 @@ namespace EMStudio AtomRenderPlugin::~AtomRenderPlugin() { - + GetCommandManager()->RemoveCommandCallback(m_importActorCallback, false); + GetCommandManager()->RemoveCommandCallback(m_removeActorCallback, false); + delete m_importActorCallback; + delete m_removeActorCallback; } const char* AtomRenderPlugin::GetName() const @@ -75,6 +78,11 @@ namespace EMStudio return EMStudioPlugin::PLUGINTYPE_RENDERING; } + QWidget* AtomRenderPlugin::GetInnerWidget() + { + return m_innerWidget; + } + void AtomRenderPlugin::ReinitRenderer() { m_animViewportWidget->Reinit(); @@ -82,6 +90,8 @@ namespace EMStudio bool AtomRenderPlugin::Init() { + LoadRenderOptions(); + m_innerWidget = new QWidget(); m_dock->setWidget(m_innerWidget); @@ -90,12 +100,14 @@ namespace EMStudio verticalLayout->setSpacing(1); verticalLayout->setMargin(0); + // Add the viewport widget + m_animViewportWidget = new AnimViewportWidget(this); + // Add the tool bar AnimViewportToolBar* toolBar = new AnimViewportToolBar(m_innerWidget); - verticalLayout->addWidget(toolBar); + toolBar->SetRenderFlags(m_animViewportWidget->GetRenderFlags()); - // Add the viewport widget - m_animViewportWidget = new AnimViewportWidget(m_innerWidget); + verticalLayout->addWidget(toolBar); verticalLayout->addWidget(m_animViewportWidget); // Register command callbacks. @@ -107,6 +119,19 @@ namespace EMStudio return true; } + void AtomRenderPlugin::LoadRenderOptions() + { + AZStd::string renderOptionsFilename(GetManager()->GetAppDataFolder()); + renderOptionsFilename += "EMStudioRenderOptions.cfg"; + QSettings settings(renderOptionsFilename.c_str(), QSettings::IniFormat, this); + m_renderOptions = RenderOptions::Load(&settings); + } + + const RenderOptions* AtomRenderPlugin::GetRenderOptions() const + { + return &m_renderOptions; + } + // Command callbacks bool ReinitAtomRenderPlugin() { diff --git a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AtomRenderPlugin.h b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AtomRenderPlugin.h index 1516b45e77..d87e039487 100644 --- a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AtomRenderPlugin.h +++ b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AtomRenderPlugin.h @@ -11,6 +11,7 @@ #if !defined(Q_MOC_RUN) #include #include +#include #include #include @@ -47,15 +48,22 @@ namespace EMStudio bool Init() override; EMStudioPlugin* Clone(); EMStudioPlugin::EPluginType GetPluginType() const override; + QWidget* GetInnerWidget(); void ReinitRenderer(); + void LoadRenderOptions(); + const RenderOptions* GetRenderOptions() const; + private: + + QWidget* m_innerWidget = nullptr; + AnimViewportWidget* m_animViewportWidget = nullptr; + RenderOptions m_renderOptions; + MCORE_DEFINECOMMANDCALLBACK(ImportActorCallback); MCORE_DEFINECOMMANDCALLBACK(RemoveActorCallback); ImportActorCallback* m_importActorCallback = nullptr; RemoveActorCallback* m_removeActorCallback = nullptr; - QWidget* m_innerWidget = nullptr; - AnimViewportWidget* m_animViewportWidget = nullptr; }; }// namespace EMStudio diff --git a/Gems/AtomLyIntegration/EMotionFXAtom/Code/emotionfx_atom_files.cmake b/Gems/AtomLyIntegration/EMotionFXAtom/Code/emotionfx_atom_files.cmake index 4ada953caf..413984b96d 100644 --- a/Gems/AtomLyIntegration/EMotionFXAtom/Code/emotionfx_atom_files.cmake +++ b/Gems/AtomLyIntegration/EMotionFXAtom/Code/emotionfx_atom_files.cmake @@ -17,4 +17,6 @@ set(FILES Source/AtomActor.cpp Source/AtomActorInstance.h Source/AtomActorInstance.cpp + Source/AtomActorDebugDraw.h + Source/AtomActorDebugDraw.cpp ) diff --git a/Gems/AtomTressFX/Code/Rendering/HairFeatureProcessor.cpp b/Gems/AtomTressFX/Code/Rendering/HairFeatureProcessor.cpp index a0be18f0e2..74ba99c26c 100644 --- a/Gems/AtomTressFX/Code/Rendering/HairFeatureProcessor.cpp +++ b/Gems/AtomTressFX/Code/Rendering/HairFeatureProcessor.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -142,12 +143,13 @@ namespace AZ EnablePasses(true); } - void HairFeatureProcessor::EnablePasses([[maybe_unused]] bool enable) + void HairFeatureProcessor::EnablePasses(bool enable) { - RPI::Ptr desiredPass = m_renderPipeline->GetRootPass()->FindPassByNameRecursive(HairParentPassName); - if (desiredPass) + RPI::PassFilter passFilter = RPI::PassFilter::CreateWithPassName(HairParentPassName, GetParentScene()); + RPI::Pass* pass = RPI::PassSystemInterface::Get()->FindFirstPass(passFilter); + if (pass) { - desiredPass->SetEnabled(enable); + pass->SetEnabled(enable); } } @@ -309,10 +311,17 @@ namespace AZ m_forceClearRenderData = true; } + bool HairFeatureProcessor::HasHairParentPass(RPI::RenderPipeline* renderPipeline) + { + RPI::PassFilter passFilter = RPI::PassFilter::CreateWithPassName(HairParentPassName, renderPipeline); + RPI::Pass* pass = RPI::PassSystemInterface::Get()->FindFirstPass(passFilter); + return pass ? true : false; + } + void HairFeatureProcessor::OnRenderPipelineAdded(RPI::RenderPipelinePtr renderPipeline) { // Proceed only if this is the main pipeline that contains the parent pass - if (!renderPipeline.get()->GetRootPass()->FindPassByNameRecursive(HairParentPassName)) + if (!HasHairParentPass(renderPipeline.get())) { return; } @@ -323,10 +332,10 @@ namespace AZ m_forceRebuildRenderData = true; } - void HairFeatureProcessor::OnRenderPipelineRemoved(RPI::RenderPipeline* renderPipeline) + void HairFeatureProcessor::OnRenderPipelineRemoved([[maybe_unused]] RPI::RenderPipeline* renderPipeline) { // Proceed only if this is the main pipeline that contains the parent pass - if (!renderPipeline->GetRootPass()->FindPassByNameRecursive(HairParentPassName)) + if (!HasHairParentPass(renderPipeline)) { return; } @@ -338,7 +347,7 @@ namespace AZ void HairFeatureProcessor::OnRenderPipelinePassesChanged(RPI::RenderPipeline* renderPipeline) { // Proceed only if this is the main pipeline that contains the parent pass - if (!renderPipeline->GetRootPass()->FindPassByNameRecursive(HairParentPassName)) + if (!HasHairParentPass(renderPipeline)) { return; } @@ -457,7 +466,8 @@ namespace AZ { m_computePasses[passName] = nullptr; - RPI::Ptr desiredPass = m_renderPipeline->GetRootPass()->FindPassByNameRecursive(passName); + RPI::PassFilter passFilter = RPI::PassFilter::CreateWithPassName(passName, m_renderPipeline); + RPI::Ptr desiredPass = RPI::PassSystemInterface::Get()->FindFirstPass(passFilter); if (desiredPass) { m_computePasses[passName] = static_cast(desiredPass.get()); @@ -478,8 +488,9 @@ namespace AZ bool HairFeatureProcessor::InitPPLLFillPass() { m_hairPPLLRasterPass = nullptr; // reset it to null, just in case it fails to load the assets properly - - RPI::Ptr desiredPass = m_renderPipeline->GetRootPass()->FindPassByNameRecursive(HairPPLLRasterPassName); + + RPI::PassFilter passFilter = RPI::PassFilter::CreateWithPassName(HairPPLLRasterPassName, m_renderPipeline); + RPI::Ptr desiredPass = RPI::PassSystemInterface::Get()->FindFirstPass(passFilter); if (desiredPass) { m_hairPPLLRasterPass = static_cast(desiredPass.get()); @@ -497,7 +508,8 @@ namespace AZ { m_hairPPLLResolvePass = nullptr; // reset it to null, just in case it fails to load the assets properly - RPI::Ptr desiredPass = m_renderPipeline->GetRootPass()->FindPassByNameRecursive(HairPPLLResolvePassName); + RPI::PassFilter passFilter = RPI::PassFilter::CreateWithPassName(HairPPLLResolvePassName, m_renderPipeline); + RPI::Ptr desiredPass = RPI::PassSystemInterface::Get()->FindFirstPass(passFilter); if (desiredPass) { m_hairPPLLResolvePass = static_cast(desiredPass.get()); @@ -518,8 +530,8 @@ namespace AZ m_hairShortCutGeometryDepthAlphaPass = nullptr; m_hairShortCutGeometryShadingPass = nullptr; - m_hairShortCutGeometryDepthAlphaPass = static_cast( - m_renderPipeline->GetRootPass()->FindPassByNameRecursive(HairShortCutGeometryDepthAlphaPassName).get()); + RPI::PassFilter depthAlphaPassFilter = RPI::PassFilter::CreateWithPassName(HairShortCutGeometryDepthAlphaPassName, m_renderPipeline); + m_hairShortCutGeometryDepthAlphaPass = static_cast(RPI::PassSystemInterface::Get()->FindFirstPass(depthAlphaPassFilter)); if (m_hairShortCutGeometryDepthAlphaPass) { m_hairShortCutGeometryDepthAlphaPass->SetFeatureProcessor(this); @@ -530,8 +542,8 @@ namespace AZ return false; } - m_hairShortCutGeometryShadingPass = static_cast( - m_renderPipeline->GetRootPass()->FindPassByNameRecursive(HairShortCutGeometryShadingPassName).get()); + RPI::PassFilter shaderingPassFilter = RPI::PassFilter::CreateWithPassName(HairShortCutGeometryShadingPassName, m_renderPipeline); + m_hairShortCutGeometryShadingPass = static_cast(RPI::PassSystemInterface::Get()->FindFirstPass(shaderingPassFilter)); if (m_hairShortCutGeometryShadingPass) { m_hairShortCutGeometryShadingPass->SetFeatureProcessor(this); @@ -611,3 +623,4 @@ namespace AZ } // namespace Hair } // namespace Render } // namespace AZ + diff --git a/Gems/AtomTressFX/Code/Rendering/HairFeatureProcessor.h b/Gems/AtomTressFX/Code/Rendering/HairFeatureProcessor.h index 46660a6623..8c19b706a9 100644 --- a/Gems/AtomTressFX/Code/Rendering/HairFeatureProcessor.h +++ b/Gems/AtomTressFX/Code/Rendering/HairFeatureProcessor.h @@ -165,6 +165,8 @@ namespace AZ void EnablePasses(bool enable); + bool HasHairParentPass(RPI::RenderPipeline* renderPipeline); + //! The following will serve to register the FP in the Thumbnail system AZStd::vector m_hairFeatureProcessorRegistryName; @@ -215,7 +217,7 @@ namespace AZ bool m_forceClearRenderData = false; bool m_initialized = false; bool m_isEnabled = true; - bool m_usePPLLRenderTechnique = true; + bool m_usePPLLRenderTechnique = false; static uint32_t s_instanceCount; HairGlobalSettings m_hairGlobalSettings; diff --git a/Gems/AudioEngineWwise/AssetProcessorGemConfig.setreg b/Gems/AudioEngineWwise/AssetProcessorGemConfig.setreg index 9b4d86791c..6d7995b230 100644 --- a/Gems/AudioEngineWwise/AssetProcessorGemConfig.setreg +++ b/Gems/AudioEngineWwise/AssetProcessorGemConfig.setreg @@ -3,10 +3,10 @@ "AssetProcessor": { "Settings": { "Exclude AudioProject": { - "pattern": ".*\\\\/Sounds\\\\/.+_project.*" + "pattern": "(^|.+/)Sounds/.+_project.*" }, "Exclude AudioTemp": { - "pattern": ".*\\\\/Sounds\\\\/.+\\\\.(txt|xml|dat)" + "pattern": "(^|.+/)Sounds/.+\\\\.(txt|xml|dat)" }, "RC audio": { "pattern": ".*\\\\.(wav|pcm)", diff --git a/Gems/Blast/Code/Source/Components/BlastSystemComponent.cpp b/Gems/Blast/Code/Source/Components/BlastSystemComponent.cpp index b7ee805b3c..4c0f15463a 100644 --- a/Gems/Blast/Code/Source/Components/BlastSystemComponent.cpp +++ b/Gems/Blast/Code/Source/Components/BlastSystemComponent.cpp @@ -143,6 +143,7 @@ namespace Blast void BlastSystemComponent::Deactivate() { AZ_PROFILE_FUNCTION(Physics); + AZ::Data::AssetBus::MultiHandler::BusDisconnect(); CrySystemEventBus::Handler::BusDisconnect(); AZ::TickBus::Handler::BusDisconnect(); BlastSystemRequestBus::Handler::BusDisconnect(); @@ -254,19 +255,22 @@ namespace Blast BlastFamilyComponentRequestBus::Broadcast( &BlastFamilyComponentRequests::FillDebugRenderBuffer, buffer, m_debugRenderMode); - // This is a system component, and thus is not associated with a specific scene, so use the default scene + // This is a system component, and thus is not associated with a specific scene, so use the bootstrap scene // for the debug drawing - const auto defaultScene = AZ::RPI::RPISystemInterface::Get()->GetDefaultScene(); - auto drawQueue = AZ::RPI::AuxGeomFeatureProcessorInterface::GetDrawQueueForScene(defaultScene); - - for (DebugLine& line : buffer.m_lines) + const auto mainScene = AZ::RPI::RPISystemInterface::Get()->GetSceneByName(AZ::Name("Main")); + if (mainScene) { - AZ::RPI::AuxGeomDraw::AuxGeomDynamicDrawArguments drawArguments; - drawArguments.m_verts = &line.m_p0; - drawArguments.m_vertCount = 2; - drawArguments.m_colors = &line.m_color; - drawArguments.m_colorCount = 1; - drawQueue->DrawLines(drawArguments); + auto drawQueue = AZ::RPI::AuxGeomFeatureProcessorInterface::GetDrawQueueForScene(mainScene); + + for (DebugLine& line : buffer.m_lines) + { + AZ::RPI::AuxGeomDraw::AuxGeomDynamicDrawArguments drawArguments; + drawArguments.m_verts = &line.m_p0; + drawArguments.m_vertCount = 2; + drawArguments.m_colors = &line.m_color; + drawArguments.m_colorCount = 1; + drawQueue->DrawLines(drawArguments); + } } } } diff --git a/Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/AnimGraphNodeGroupCommands.h b/Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/AnimGraphNodeGroupCommands.h index 014d3b0398..3623bd5cd5 100644 --- a/Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/AnimGraphNodeGroupCommands.h +++ b/Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/AnimGraphNodeGroupCommands.h @@ -60,7 +60,7 @@ namespace CommandSystem const char* GetDescription() const override; MCore::Command* Create() override { - return new CommandAnimGraphAdjustNodeGroup(this); + return aznew CommandAnimGraphAdjustNodeGroup(this); } static AZStd::vector GenerateNodeNameVector(EMotionFX::AnimGraph* animGraph, const AZStd::vector& nodeIDs); diff --git a/Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/CommandManager.cpp b/Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/CommandManager.cpp index be4626f592..2c646b3504 100644 --- a/Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/CommandManager.cpp +++ b/Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/CommandManager.cpp @@ -104,7 +104,7 @@ namespace CommandSystem RegisterCommand(new CommandMotionSetAdjustMotion()); // register node group commands - RegisterCommand(new CommandAdjustNodeGroup()); + RegisterCommand(aznew CommandAdjustNodeGroup()); RegisterCommand(new CommandAddNodeGroup()); RegisterCommand(new CommandRemoveNodeGroup()); @@ -134,7 +134,7 @@ namespace CommandSystem RegisterCommand(aznew CommandAdjustTransitionCondition()); RegisterCommand(new CommandAnimGraphAddNodeGroup()); RegisterCommand(new CommandAnimGraphRemoveNodeGroup()); - RegisterCommand(new CommandAnimGraphAdjustNodeGroup()); + RegisterCommand(aznew CommandAnimGraphAdjustNodeGroup()); RegisterCommand(new CommandAnimGraphAddGroupParameter()); RegisterCommand(new CommandAnimGraphRemoveGroupParameter()); RegisterCommand(new CommandAnimGraphAdjustGroupParameter()); diff --git a/Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/NodeGroupCommands.h b/Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/NodeGroupCommands.h index 6c57877bd5..640a670e44 100644 --- a/Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/NodeGroupCommands.h +++ b/Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/NodeGroupCommands.h @@ -63,7 +63,7 @@ namespace CommandSystem const char* GetDescription() const override; MCore::Command* Create() override { - return new CommandAdjustNodeGroup(this); + return aznew CommandAdjustNodeGroup(this); } private: diff --git a/Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/RagdollCommands.cpp b/Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/RagdollCommands.cpp index e51c9ece0b..d7c7bf69e5 100644 --- a/Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/RagdollCommands.cpp +++ b/Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/RagdollCommands.cpp @@ -105,6 +105,8 @@ namespace EMotionFX *jointTypeId, parentBindRotationWorld, nodeBindRotationWorld, boneDirection, exampleRotationsLocal); AZ_Assert(jointLimitConfig, "Could not create joint limit configuration."); + jointLimitConfig->SetPropertyVisibility(AzPhysics::JointConfiguration::PropertyVisibility::ParentLocalRotation, true); + jointLimitConfig->SetPropertyVisibility(AzPhysics::JointConfiguration::PropertyVisibility::ChildLocalRotation, true); return jointLimitConfig; } } diff --git a/Gems/EMotionFX/Code/EMotionFX/Source/EMotionFXManager.cpp b/Gems/EMotionFX/Code/EMotionFX/Source/EMotionFXManager.cpp index d5fa3867e0..4ecd1c0117 100644 --- a/Gems/EMotionFX/Code/EMotionFX/Source/EMotionFXManager.cpp +++ b/Gems/EMotionFX/Code/EMotionFX/Source/EMotionFXManager.cpp @@ -29,6 +29,7 @@ #include #include #include +#include namespace EMotionFX { @@ -135,6 +136,8 @@ namespace EMotionFX { RegisterMemoryCategories(MCore::GetMemoryTracker()); } + + m_renderActorSettings = AZStd::make_unique(); } @@ -167,6 +170,7 @@ namespace EMotionFX delete m_debugDraw; m_debugDraw = nullptr; + m_renderActorSettings.reset(); m_eventManager->Destroy(); m_eventManager = nullptr; diff --git a/Gems/EMotionFX/Code/EMotionFX/Source/EMotionFXManager.h b/Gems/EMotionFX/Code/EMotionFX/Source/EMotionFXManager.h index d5c5247de6..4d55143b2f 100644 --- a/Gems/EMotionFX/Code/EMotionFX/Source/EMotionFXManager.h +++ b/Gems/EMotionFX/Code/EMotionFX/Source/EMotionFXManager.h @@ -19,6 +19,10 @@ MCORE_FORWARD_DECLARE(MemoryTracker); +namespace AZ::Render +{ + class RenderActorSettings; +} namespace EMotionFX { @@ -184,6 +188,15 @@ namespace EMotionFX */ MCORE_INLINE DebugDraw* GetDebugDraw() const { return m_debugDraw; } + /** + * Get the render actor settings + * @result A pointer to global render actor settings. + */ + AZ::Render::RenderActorSettings* GetRenderActorSettings() const + { + return m_renderActorSettings.get(); + } + /** * Set the path of the media root directory. * @param path The path of the media root folder. @@ -347,6 +360,8 @@ namespace EMotionFX Recorder* m_recorder; /**< The recorder. */ MotionInstancePool* m_motionInstancePool; /**< The motion instance pool. */ DebugDraw* m_debugDraw; /**< The debug drawing system. */ + AZStd::unique_ptr m_renderActorSettings; /**< The global render actor settings. */ + AZStd::vector m_threadDatas; /**< The per thread data. */ MCore::Distance::EUnitType m_unitType; /**< The unit type, on default it is MCore::Distance::UNITTYPE_METERS. */ float m_globalSimulationSpeed; /**< The global simulation speed, default is 1.0. */ @@ -505,4 +520,5 @@ namespace EMotionFX MCORE_INLINE Recorder& GetRecorder() { return *GetEMotionFX().GetRecorder(); } /**< Get the recorder. */ MCORE_INLINE MotionInstancePool& GetMotionInstancePool() { return *GetEMotionFX().GetMotionInstancePool(); } /**< Get the motion instance pool. */ MCORE_INLINE DebugDraw& GetDebugDraw() { return *GetEMotionFX().GetDebugDraw(); } /**< Get the debug drawing. */ + MCORE_INLINE AZ::Render::RenderActorSettings& GetRenderActorSettings() { return *GetEMotionFX().GetRenderActorSettings(); }/**< Get the render actor settings. */ } // namespace EMotionFX diff --git a/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/EMStudioSDK/Source/RenderPlugin/RenderOptions.cpp b/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/EMStudioSDK/Source/RenderPlugin/RenderOptions.cpp index af2cb2c8e8..496781d316 100644 --- a/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/EMStudioSDK/Source/RenderPlugin/RenderOptions.cpp +++ b/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/EMStudioSDK/Source/RenderPlugin/RenderOptions.cpp @@ -12,9 +12,7 @@ #include #include #include -#include -#include -#include +#include #include #include @@ -318,6 +316,8 @@ namespace EMStudio options.m_manipulatorMode = static_cast(settings->value("manipulatorMode", options.m_manipulatorMode).toInt()); + options.CopyToRenderActorSettings(EMotionFX::GetRenderActorSettings()); + return options; } @@ -1057,6 +1057,23 @@ namespace EMStudio return m_manipulatorMode; } + void RenderOptions::CopyToRenderActorSettings(AZ::Render::RenderActorSettings& settings) const + { + settings.m_vertexNormalsScale = m_vertexNormalsScale; + settings.m_faceNormalsScale = m_faceNormalsScale; + settings.m_tangentsScale = m_tangentsScale; + + settings.m_vertexNormalsColor = m_vertexNormalsColor; + settings.m_faceNormalsColor = m_faceNormalsColor; + settings.m_tangentsColor = m_tangentsColor; + settings.m_mirroredBitangentsColor = m_mirroredBitangentsColor; + settings.m_bitangentsColor = m_bitangentsColor; + settings.m_wireframeColor = m_wireframeColor; + settings.m_staticAABBColor = m_staticAABBColor; + settings.m_skeletonColor = m_skeletonColor; + settings.m_lineSkeletonColor = m_lineSkeletonColor; + } + void RenderOptions::OnGridUnitSizeChangedCallback() const { PluginOptionsNotificationsBus::Event(s_gridUnitSizeOptionName, &PluginOptionsNotificationsBus::Events::OnOptionChanged, s_gridUnitSizeOptionName); @@ -1065,16 +1082,19 @@ namespace EMStudio void RenderOptions::OnVertexNormalsScaleChangedCallback() const { PluginOptionsNotificationsBus::Event(s_vertexNormalsScaleOptionName, &PluginOptionsNotificationsBus::Events::OnOptionChanged, s_vertexNormalsScaleOptionName); + CopyToRenderActorSettings(EMotionFX::GetRenderActorSettings()); } void RenderOptions::OnFaceNormalsScaleChangedCallback() const { PluginOptionsNotificationsBus::Event(s_faceNormalsScaleOptionName, &PluginOptionsNotificationsBus::Events::OnOptionChanged, s_faceNormalsScaleOptionName); + CopyToRenderActorSettings(EMotionFX::GetRenderActorSettings()); } void RenderOptions::OnTangentsScaleChangedCallback() const { PluginOptionsNotificationsBus::Event(s_tangentsScaleOptionName, &PluginOptionsNotificationsBus::Events::OnOptionChanged, s_tangentsScaleOptionName); + CopyToRenderActorSettings(EMotionFX::GetRenderActorSettings()); } void RenderOptions::OnNodeOrientationScaleChangedCallback() const @@ -1175,6 +1195,7 @@ namespace EMStudio void RenderOptions::OnWireframeColorChangedCallback() const { PluginOptionsNotificationsBus::Event(s_wireframeColorOptionName, &PluginOptionsNotificationsBus::Events::OnOptionChanged, s_wireframeColorOptionName); + CopyToRenderActorSettings(EMotionFX::GetRenderActorSettings()); } void RenderOptions::OnCollisionMeshColorChangedCallback() const @@ -1185,26 +1206,31 @@ namespace EMStudio void RenderOptions::OnVertexNormalsColorChangedCallback() const { PluginOptionsNotificationsBus::Event(s_vertexNormalsColorOptionName, &PluginOptionsNotificationsBus::Events::OnOptionChanged, s_vertexNormalsColorOptionName); + CopyToRenderActorSettings(EMotionFX::GetRenderActorSettings()); } void RenderOptions::OnFaceNormalsColorChangedCallback() const { PluginOptionsNotificationsBus::Event(s_faceNormalsColorOptionName, &PluginOptionsNotificationsBus::Events::OnOptionChanged, s_faceNormalsColorOptionName); + CopyToRenderActorSettings(EMotionFX::GetRenderActorSettings()); } void RenderOptions::OnTangentsColorChangedCallback() const { PluginOptionsNotificationsBus::Event(s_tangentsColorOptionName, &PluginOptionsNotificationsBus::Events::OnOptionChanged, s_tangentsColorOptionName); + CopyToRenderActorSettings(EMotionFX::GetRenderActorSettings()); } void RenderOptions::OnMirroredBitangentsColorChangedCallback() const { PluginOptionsNotificationsBus::Event(s_mirroredBitangentsColorOptionName, &PluginOptionsNotificationsBus::Events::OnOptionChanged, s_mirroredBitangentsColorOptionName); + CopyToRenderActorSettings(EMotionFX::GetRenderActorSettings()); } void RenderOptions::OnBitangentsColorChangedCallback() const { PluginOptionsNotificationsBus::Event(s_bitangentsColorOptionName, &PluginOptionsNotificationsBus::Events::OnOptionChanged, s_bitangentsColorOptionName); + CopyToRenderActorSettings(EMotionFX::GetRenderActorSettings()); } void RenderOptions::OnNodeAABBColorChangedCallback() const @@ -1215,6 +1241,7 @@ namespace EMStudio void RenderOptions::OnStaticAABBColorChangedCallback() const { PluginOptionsNotificationsBus::Event(s_staticAABBColorOptionName, &PluginOptionsNotificationsBus::Events::OnOptionChanged, s_staticAABBColorOptionName); + CopyToRenderActorSettings(EMotionFX::GetRenderActorSettings()); } void RenderOptions::OnMeshAABBColorChangedCallback() const @@ -1225,6 +1252,7 @@ namespace EMStudio void RenderOptions::OnLineSkeletonColorChangedCallback() const { PluginOptionsNotificationsBus::Event(s_lineSkeletonColorOptionName, &PluginOptionsNotificationsBus::Events::OnOptionChanged, s_lineSkeletonColorOptionName); + CopyToRenderActorSettings(EMotionFX::GetRenderActorSettings()); } void RenderOptions::OnSkeletonColorChangedCallback() const diff --git a/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/EMStudioSDK/Source/RenderPlugin/RenderOptions.h b/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/EMStudioSDK/Source/RenderPlugin/RenderOptions.h index f146f62215..20d521813d 100644 --- a/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/EMStudioSDK/Source/RenderPlugin/RenderOptions.h +++ b/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/EMStudioSDK/Source/RenderPlugin/RenderOptions.h @@ -18,6 +18,11 @@ QT_FORWARD_DECLARE_CLASS(QSettings); +namespace AZ::Render +{ + class RenderActorSettings; +} + namespace EMStudio { class EMSTUDIO_API RenderOptions @@ -314,6 +319,9 @@ namespace EMStudio void OnLastUsedLayoutChangedCallback() const; void OnRenderSelectionBoxChangedCallback() const; + // Copy render actor related settings to the global settings in emfx. + void CopyToRenderActorSettings(AZ::Render::RenderActorSettings& settings) const; + // Maintain the order between here and the reflect method. // The order in the SerializeContext defines the order it is shown in the UI float m_gridUnitSize; diff --git a/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/Plugins/StandardPlugins/Source/AnimGraph/NodeGraph.cpp b/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/Plugins/StandardPlugins/Source/AnimGraph/NodeGraph.cpp index 49b986887c..e97ff19113 100644 --- a/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/Plugins/StandardPlugins/Source/AnimGraph/NodeGraph.cpp +++ b/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/Plugins/StandardPlugins/Source/AnimGraph/NodeGraph.cpp @@ -6,7 +6,8 @@ * */ -#include "AzCore/std/numeric.h" +#include +#include #include #include #include @@ -231,7 +232,7 @@ namespace EMStudio // add the playspeed if (plugin->GetIsDisplayFlagEnabled(AnimGraphPlugin::DISPLAYFLAG_PLAYSPEED)) { - m_qtTempString.asprintf("Play Speed = %.2f", emfxNode->GetPlaySpeed(animGraphInstance)); + m_qtTempString = AZStd::fixed_string<24>::format("Play Speed = %.2f", emfxNode->GetPlaySpeed(animGraphInstance)).c_str(); painter.drawText(textPosition, m_qtTempString); textPosition.setY(textPosition.y() + heightSpacing); } @@ -239,7 +240,7 @@ namespace EMStudio // add the global weight if (plugin->GetIsDisplayFlagEnabled(AnimGraphPlugin::DISPLAYFLAG_GLOBALWEIGHT)) { - m_qtTempString.asprintf("Global Weight = %.2f", uniqueData->GetGlobalWeight()); + m_qtTempString = AZStd::fixed_string<24>::format("Global Weight = %.2f", uniqueData->GetGlobalWeight()).c_str(); painter.drawText(textPosition, m_qtTempString); textPosition.setY(textPosition.y() + heightSpacing); } @@ -247,7 +248,7 @@ namespace EMStudio // add the sync if (plugin->GetIsDisplayFlagEnabled(AnimGraphPlugin::DISPLAYFLAG_SYNCSTATUS)) { - m_qtTempString.asprintf("Synced = %s", animGraphInstance->GetIsSynced(emfxNode->GetObjectIndex()) ? "Yes" : "No"); + m_qtTempString = AZStd::fixed_string<24>::format("Synced = %s", animGraphInstance->GetIsSynced(emfxNode->GetObjectIndex()) ? "Yes" : "No").c_str(); painter.drawText(textPosition, m_qtTempString); textPosition.setY(textPosition.y() + heightSpacing); } @@ -255,7 +256,7 @@ namespace EMStudio // add the play position if (plugin->GetIsDisplayFlagEnabled(AnimGraphPlugin::DISPLAYFLAG_PLAYPOSITION)) { - m_qtTempString.asprintf("Play Time = %.3f / %.3f", uniqueData->GetCurrentPlayTime(), uniqueData->GetDuration()); + m_qtTempString = AZStd::fixed_string<32>::format("Play Time = %.3f / %.3f", uniqueData->GetCurrentPlayTime(), uniqueData->GetDuration()).c_str(); painter.drawText(textPosition, m_qtTempString); textPosition.setY(textPosition.y() + heightSpacing); } diff --git a/Gems/EMotionFX/Code/Include/Integration/ActorComponentBus.h b/Gems/EMotionFX/Code/Include/Integration/ActorComponentBus.h index 4161b3c77d..1fd3e55f12 100644 --- a/Gems/EMotionFX/Code/Include/Integration/ActorComponentBus.h +++ b/Gems/EMotionFX/Code/Include/Integration/ActorComponentBus.h @@ -85,9 +85,6 @@ namespace EMotionFX /// Detach from parent entity, if attached. virtual void DetachFromEntity() {} - /// Enables debug-drawing of the actor's root. - virtual void DebugDrawRoot(bool /*enable*/) {} - /// Enables rendering of the actor. virtual bool GetRenderCharacter() const = 0; virtual void SetRenderCharacter(bool enable) = 0; diff --git a/Gems/EMotionFX/Code/Source/Integration/Components/ActorComponent.cpp b/Gems/EMotionFX/Code/Source/Integration/Components/ActorComponent.cpp index 206cae3f59..e38e15d124 100644 --- a/Gems/EMotionFX/Code/Source/Integration/Components/ActorComponent.cpp +++ b/Gems/EMotionFX/Code/Source/Integration/Components/ActorComponent.cpp @@ -209,7 +209,6 @@ namespace EMotionFX ->Event("GetJointTransform", &ActorComponentRequestBus::Events::GetJointTransform) ->Event("AttachToEntity", &ActorComponentRequestBus::Events::AttachToEntity) ->Event("DetachFromEntity", &ActorComponentRequestBus::Events::DetachFromEntity) - ->Event("DebugDrawRoot", &ActorComponentRequestBus::Events::DebugDrawRoot) ->Event("GetRenderCharacter", &ActorComponentRequestBus::Events::GetRenderCharacter) ->Event("SetRenderCharacter", &ActorComponentRequestBus::Events::SetRenderCharacter) ->Event("GetRenderActorVisible", &ActorComponentRequestBus::Events::GetRenderActorVisible) @@ -238,8 +237,7 @@ namespace EMotionFX ////////////////////////////////////////////////////////////////////////// ActorComponent::ActorComponent(const Configuration* configuration) - : m_debugDrawRoot(false) - , m_sceneFinishSimHandler([this]([[maybe_unused]] AzPhysics::SceneHandle sceneHandle, + : m_sceneFinishSimHandler([this]([[maybe_unused]] AzPhysics::SceneHandle sceneHandle, float fixedDeltatime) { if (m_actorInstance) @@ -252,6 +250,8 @@ namespace EMotionFX { m_configuration = *configuration; } + + m_debugRenderFlags[RENDER_SOLID] = true; } ////////////////////////////////////////////////////////////////////////// @@ -341,12 +341,6 @@ namespace EMotionFX } } - ////////////////////////////////////////////////////////////////////////// - void ActorComponent::DebugDrawRoot(bool enable) - { - m_debugDrawRoot = enable; - } - ////////////////////////////////////////////////////////////////////////// bool ActorComponent::GetRenderCharacter() const { @@ -400,6 +394,11 @@ namespace EMotionFX return m_sceneFinishSimHandler.IsConnected(); } + void ActorComponent::SetRenderFlag(ActorRenderFlagBitset renderFlags) + { + m_debugRenderFlags = renderFlags; + } + void ActorComponent::CheckActorCreation() { // Create actor instance. @@ -573,13 +572,13 @@ namespace EMotionFX m_actorInstance->SetIsVisible(isInCameraFrustum && m_configuration.m_renderCharacter); } - RenderActorInstance::DebugOptions debugOptions; - debugOptions.m_drawAABB = m_configuration.m_renderBounds; - debugOptions.m_drawSkeleton = m_configuration.m_renderSkeleton; - debugOptions.m_drawRootTransform = m_debugDrawRoot; - debugOptions.m_rootWorldTransform = GetEntity()->GetTransform()->GetWorldTM(); - debugOptions.m_emfxDebugDraw = true; - m_renderActorInstance->DebugDraw(debugOptions); + m_renderActorInstance->SetIsVisible(m_debugRenderFlags[RENDER_SOLID]); + + // The configuration stores some debug option. When that is enabled, we override it on top of the render flags. + m_debugRenderFlags[RENDER_AABB] = m_debugRenderFlags[RENDER_AABB] || m_configuration.m_renderBounds; + m_debugRenderFlags[RENDER_LINESKELETON] = m_debugRenderFlags[RENDER_LINESKELETON] || m_configuration.m_renderSkeleton; + m_debugRenderFlags[RENDER_EMFX_DEBUG] = true; + m_renderActorInstance->DebugDraw(m_debugRenderFlags); } } diff --git a/Gems/EMotionFX/Code/Source/Integration/Components/ActorComponent.h b/Gems/EMotionFX/Code/Source/Integration/Components/ActorComponent.h index 15d9736f34..9eecea2f54 100644 --- a/Gems/EMotionFX/Code/Source/Integration/Components/ActorComponent.h +++ b/Gems/EMotionFX/Code/Source/Integration/Components/ActorComponent.h @@ -116,7 +116,6 @@ namespace EMotionFX ActorInstance* GetActorInstance() override { return m_actorInstance.get(); } void AttachToEntity(AZ::EntityId targetEntityId, AttachmentType attachmentType) override; void DetachFromEntity() override; - void DebugDrawRoot(bool enable) override; bool GetRenderCharacter() const override; void SetRenderCharacter(bool enable) override; bool GetRenderActorVisible() const override; @@ -181,6 +180,8 @@ namespace EMotionFX bool IsPhysicsSceneSimulationFinishEventConnected() const; AZ::Data::Asset GetActorAsset() const { return m_configuration.m_actorAsset; } + void SetRenderFlag(ActorRenderFlagBitset renderFlags); + private: // AZ::TransformNotificationBus::MultiHandler void OnTransformChanged(const AZ::Transform& local, const AZ::Transform& world) override; @@ -201,7 +202,7 @@ namespace EMotionFX AZStd::vector m_attachments; AZStd::unique_ptr m_renderActorInstance; - bool m_debugDrawRoot; ///< Enables drawing of actor root and facing. + ActorRenderFlagBitset m_debugRenderFlags; ///< Actor debug render flag AzPhysics::SceneEvents::OnSceneSimulationFinishHandler m_sceneFinishSimHandler; }; diff --git a/Gems/EMotionFX/Code/Source/Integration/Editor/Components/EditorActorComponent.cpp b/Gems/EMotionFX/Code/Source/Integration/Editor/Components/EditorActorComponent.cpp index 5da9716d15..78470a1fa5 100644 --- a/Gems/EMotionFX/Code/Source/Integration/Editor/Components/EditorActorComponent.cpp +++ b/Gems/EMotionFX/Code/Source/Integration/Editor/Components/EditorActorComponent.cpp @@ -180,6 +180,7 @@ namespace EMotionFX , m_lodLevel(0) , m_actorAsset(AZ::Data::AssetLoadBehavior::NoLoad) { + m_debugRenderFlags[RENDER_SOLID] = true; } ////////////////////////////////////////////////////////////////////////// @@ -604,11 +605,10 @@ namespace EMotionFX m_renderActorInstance->OnTick(deltaTime); m_renderActorInstance->UpdateBounds(); - RenderActorInstance::DebugOptions debugOptions; - debugOptions.m_drawAABB = m_renderBounds; - debugOptions.m_drawSkeleton = m_renderSkeleton; - debugOptions.m_emfxDebugDraw = true; - m_renderActorInstance->DebugDraw(debugOptions); + m_debugRenderFlags[RENDER_AABB] = m_renderBounds; + m_debugRenderFlags[RENDER_LINESKELETON] = m_renderSkeleton; + m_debugRenderFlags[RENDER_EMFX_DEBUG] = true; + m_renderActorInstance->DebugDraw(m_debugRenderFlags); } } @@ -951,5 +951,10 @@ namespace EMotionFX LmbrCentral::AttachmentComponentRequestBus::Event(attachment, &LmbrCentral::AttachmentComponentRequestBus::Events::Reattach, true); } } + + void EditorActorComponent::SetRenderFlag(ActorRenderFlagBitset renderFlags) + { + m_debugRenderFlags = renderFlags; + } } //namespace Integration } // namespace EMotionFX diff --git a/Gems/EMotionFX/Code/Source/Integration/Editor/Components/EditorActorComponent.h b/Gems/EMotionFX/Code/Source/Integration/Editor/Components/EditorActorComponent.h index 1d682b47d4..f4c663a92f 100644 --- a/Gems/EMotionFX/Code/Source/Integration/Editor/Components/EditorActorComponent.h +++ b/Gems/EMotionFX/Code/Source/Integration/Editor/Components/EditorActorComponent.h @@ -104,6 +104,8 @@ namespace EMotionFX ActorComponent::GetRequiredServices(required); } + void SetRenderFlag(ActorRenderFlagBitset renderFlags); + static void Reflect(AZ::ReflectContext* context); private: @@ -162,6 +164,7 @@ namespace EMotionFX size_t m_lodLevel; ActorComponent::BoundingBoxConfiguration m_bboxConfig; bool m_forceUpdateJointsOOV = false; + ActorRenderFlagBitset m_debugRenderFlags; ///< Actor debug render flag // \todo attachmentTarget node nr // Note: LOD work in progress. For now we use one material instead of a list of material, because we don't have the support for LOD with multiple scene files. diff --git a/Gems/EMotionFX/Code/Source/Integration/Rendering/RenderActorInstance.h b/Gems/EMotionFX/Code/Source/Integration/Rendering/RenderActorInstance.h index 47da0d4cca..8afb2a2f9a 100644 --- a/Gems/EMotionFX/Code/Source/Integration/Rendering/RenderActorInstance.h +++ b/Gems/EMotionFX/Code/Source/Integration/Rendering/RenderActorInstance.h @@ -16,6 +16,7 @@ #include #include +#include namespace EMotionFX { @@ -33,16 +34,7 @@ namespace EMotionFX virtual ~RenderActorInstance() = default; virtual void OnTick(float timeDelta) = 0; - - struct DebugOptions - { - bool m_drawAABB = false; - bool m_drawSkeleton = false; - bool m_drawRootTransform = false; - AZ::Transform m_rootWorldTransform = AZ::Transform::CreateIdentity(); - bool m_emfxDebugDraw = false; - }; - virtual void DebugDraw(const DebugOptions& debugOptions) = 0; + virtual void DebugDraw(const EMotionFX::ActorRenderFlagBitset& renderFlags) = 0; SkinningMethod GetSkinningMethod() const; virtual void SetSkinningMethod(SkinningMethod skinningMethod); diff --git a/Gems/EMotionFX/Code/Source/Integration/Rendering/RenderActorSettings.h b/Gems/EMotionFX/Code/Source/Integration/Rendering/RenderActorSettings.h new file mode 100644 index 0000000000..3e4f5ff5c9 --- /dev/null +++ b/Gems/EMotionFX/Code/Source/Integration/Rendering/RenderActorSettings.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ +#pragma once + +#include +#include + +namespace AZ::Render +{ + // RenderActorSettings is a subset of RenderOptions. The goal is eventually move those actor render related settings out of render options since + // it will be shared between main editor and animation editor. + class RenderActorSettings + { + public: + AZ_RTTI(RenderActorSettings, "{240BDFE2-D7F5-4927-A8CA-D2945E41AFFD}"); + AZ_CLASS_ALLOCATOR(RenderActorSettings, AZ::SystemAllocator, 0) + + virtual ~RenderActorSettings() = default; + + float m_vertexNormalsScale = 1.0f; + float m_faceNormalsScale = 1.0f; + float m_tangentsScale = 1.0f; + float m_wireframeScale = 1.0f; + + AZ::Color m_vertexNormalsColor{ 0.0f, 1.0f, 0.0f, 1.0f }; + AZ::Color m_faceNormalsColor{ 0.5f, 0.5f, 1.0f, 1.0f }; + AZ::Color m_tangentsColor{ 1.0f, 0.0f, 0.0f, 1.0f }; + AZ::Color m_mirroredBitangentsColor{ 1.0f, 1.0f, 0.0f, 1.0f }; + AZ::Color m_bitangentsColor{ 1.0f, 1.0f, 1.0f, 1.0f }; + AZ::Color m_wireframeColor{ 0.0f, 0.0f, 0.0f, 1.0f }; + AZ::Color m_staticAABBColor{ 0.0f, 0.7f, 0.7f, 1.0f }; + AZ::Color m_lineSkeletonColor{ 0.33333f, 1.0f, 0.0f, 1.0f }; + AZ::Color m_skeletonColor{ 0.19f, 0.58f, 0.19f, 1.0f }; + }; +} // namespace AZ::Render diff --git a/Gems/EMotionFX/Code/Source/Integration/Rendering/RenderFlag.h b/Gems/EMotionFX/Code/Source/Integration/Rendering/RenderFlag.h new file mode 100644 index 0000000000..e053eae6d5 --- /dev/null +++ b/Gems/EMotionFX/Code/Source/Integration/Rendering/RenderFlag.h @@ -0,0 +1,44 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ +#pragma once + +#include + +namespace EMotionFX +{ + enum ActorRenderFlag + { + RENDER_SOLID = 0, + RENDER_WIREFRAME = 1, + RENDER_LIGHTING = 2, + RENDER_SHADOWS = 3, + RENDER_FACENORMALS = 4, + RENDER_VERTEXNORMALS = 5, + RENDER_TANGENTS = 6, + RENDER_AABB = 7, + RENDER_SKELETON = 8, + RENDER_LINESKELETON = 9, + RENDER_NODEORIENTATION = 10, + RENDER_NODENAMES = 11, + RENDER_GRID = 12, + RENDER_BACKFACECULLING = 13, + RENDER_ACTORBINDPOSE = 14, + RENDER_RAGDOLL_COLLIDERS = 15, + RENDER_RAGDOLL_JOINTLIMITS = 16, + RENDER_HITDETECTION_COLLIDERS = 17, + RENDER_USE_GRADIENTBACKGROUND = 18, + RENDER_MOTIONEXTRACTION = 19, + RENDER_CLOTH_COLLIDERS = 20, + RENDER_SIMULATEDOBJECT_COLLIDERS = 21, + RENDER_SIMULATEJOINTS = 22, + RENDER_EMFX_DEBUG = 23, + NUM_RENDERFLAGS = 24 + }; + + using ActorRenderFlagBitset = AZStd::bitset; +} diff --git a/Gems/EMotionFX/Code/Tests/RenderBackendManagerTests.cpp b/Gems/EMotionFX/Code/Tests/RenderBackendManagerTests.cpp index 89d6fab93b..be990f444c 100644 --- a/Gems/EMotionFX/Code/Tests/RenderBackendManagerTests.cpp +++ b/Gems/EMotionFX/Code/Tests/RenderBackendManagerTests.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -64,7 +65,7 @@ namespace EMotionFX } MOCK_METHOD1(OnTick, void(float)); - MOCK_METHOD1(DebugDraw, void(const DebugOptions&)); + MOCK_METHOD1(DebugDraw, void(const EMotionFX::ActorRenderFlagBitset&)); MOCK_CONST_METHOD0(IsVisible, bool()); MOCK_METHOD1(SetIsVisible, void(bool)); MOCK_METHOD1(SetMaterials, void(const ActorAsset::MaterialList&)); diff --git a/Gems/EMotionFX/Code/emotionfx_shared_files.cmake b/Gems/EMotionFX/Code/emotionfx_shared_files.cmake index b45f15a5be..26b51f280b 100644 --- a/Gems/EMotionFX/Code/emotionfx_shared_files.cmake +++ b/Gems/EMotionFX/Code/emotionfx_shared_files.cmake @@ -42,4 +42,5 @@ set(FILES Source/Integration/Rendering/RenderActorInstance.cpp Source/Integration/Rendering/RenderBackendManager.h Source/Integration/Rendering/RenderBackendManager.cpp + Source/Integration/Rendering/RenderActorSettings.h ) diff --git a/Gems/GraphCanvas/Code/StaticLib/GraphCanvas/Widgets/GraphCanvasEditor/GraphCanvasAssetEditorMainWindow.h b/Gems/GraphCanvas/Code/StaticLib/GraphCanvas/Widgets/GraphCanvasEditor/GraphCanvasAssetEditorMainWindow.h index 139c540767..1c83fb7717 100644 --- a/Gems/GraphCanvas/Code/StaticLib/GraphCanvas/Widgets/GraphCanvasEditor/GraphCanvasAssetEditorMainWindow.h +++ b/Gems/GraphCanvas/Code/StaticLib/GraphCanvas/Widgets/GraphCanvasEditor/GraphCanvasAssetEditorMainWindow.h @@ -89,7 +89,7 @@ namespace GraphCanvas explicit AssetEditorMainWindow(AssetEditorWindowConfig* config, QWidget* parent = nullptr); virtual ~AssetEditorMainWindow(); - virtual void SetupUI(); + void SetupUI(); void SetDropAreaText(AZStd::string_view text); const EditorId& GetEditorId() const; diff --git a/Gems/InAppPurchases/Code/inapppurchases_files.cmake b/Gems/InAppPurchases/Code/inapppurchases_files.cmake index 5eab792206..2669cbf930 100644 --- a/Gems/InAppPurchases/Code/inapppurchases_files.cmake +++ b/Gems/InAppPurchases/Code/inapppurchases_files.cmake @@ -10,6 +10,7 @@ set(FILES Include/InAppPurchases/InAppPurchasesBus.h Include/InAppPurchases/InAppPurchasesInterface.h Include/InAppPurchases/InAppPurchasesResponseBus.h + Source/InAppPurchasesSystemComponent.h Source/InAppPurchasesSystemComponent.cpp Source/InAppPurchasesInterface.cpp ) diff --git a/Gems/LmbrCentral/Code/Source/Bundling/BundlingSystemComponent.cpp b/Gems/LmbrCentral/Code/Source/Bundling/BundlingSystemComponent.cpp index c9fc656488..168a3640a8 100644 --- a/Gems/LmbrCentral/Code/Source/Bundling/BundlingSystemComponent.cpp +++ b/Gems/LmbrCentral/Code/Source/Bundling/BundlingSystemComponent.cpp @@ -9,43 +9,40 @@ #include "BundlingSystemComponent.h" #include +#include +#include #include #include +#include #include -#include - -#include -#include - #include + namespace LmbrCentral { const char bundleRoot[] = "@products@"; + // Calls the LoadBundles method + static void ConsoleCommandLoadBundles(const AZ::ConsoleCommandContainer& commandArgs); + // Calls the UnloadBundles method + static void ConsoleCommandUnloadBundles(const AZ::ConsoleCommandContainer& commandArgs); + + AZ_CONSOLEFREEFUNC("loadbundles", ConsoleCommandLoadBundles, AZ::ConsoleFunctorFlags::Null, "Load Asset Bundles"); + AZ_CONSOLEFREEFUNC("unloadbundles", ConsoleCommandUnloadBundles, AZ::ConsoleFunctorFlags::Null, "Unload Asset Bundles"); + void BundlingSystemComponent::Activate() { BundlingSystemRequestBus::Handler::BusConnect(); - CrySystemEventBus::Handler::BusConnect(); AZ::IO::ArchiveNotificationBus::Handler::BusConnect(); } void BundlingSystemComponent::Deactivate() { AZ::IO::ArchiveNotificationBus::Handler::BusDisconnect(); - CrySystemEventBus::Handler::BusDisconnect(); BundlingSystemRequestBus::Handler::BusDisconnect(); } - void BundlingSystemComponent::OnCrySystemInitialized(ISystem& system, const SSystemInitParams& systemInitParams) - { - AZ_UNUSED(systemInitParams); - - system.GetIConsole()->AddCommand("loadbundles", ConsoleCommandLoadBundles); - system.GetIConsole()->AddCommand("unloadbundles", ConsoleCommandUnloadBundles); - } - void BundlingSystemComponent::Reflect(AZ::ReflectContext* context) { if (auto serializeContext = azrtti_cast(context)) @@ -58,7 +55,7 @@ namespace LmbrCentral AZStd::vector BundlingSystemComponent::GetBundleList(const char* bundlePath, const char* bundleExtension) const { - AZStd::string fileFilter{ AZStd::string::format("*%s",bundleExtension) }; + AZStd::string fileFilter{ AZStd::string::format("*%s", bundleExtension) }; AZStd::vector bundleList; AZ::IO::FileIOBase::GetInstance()->FindFiles(bundlePath, fileFilter.c_str(), [&bundleList](const char* foundPath) -> bool @@ -73,29 +70,28 @@ namespace LmbrCentral return bundleList; } - void BundlingSystemComponent::ConsoleCommandLoadBundles(IConsoleCmdArgs* pCmdArgs) + void ConsoleCommandLoadBundles(const AZ::ConsoleCommandContainer& commandArgs) { const char defaultBundleFolder[] = "bundles"; const char defaultBundleExtension[] = ".pak"; - const char* bundleFolder = pCmdArgs->GetArgCount() > 1 ? pCmdArgs->GetArg(1) : defaultBundleFolder; - const char* bundleExtension = pCmdArgs->GetArgCount() > 2 ? pCmdArgs->GetArg(2) : defaultBundleExtension; + AZ::CVarFixedString bundleFolder = commandArgs.size() > 0 ? AZ::CVarFixedString(commandArgs[0]) : defaultBundleFolder; + AZ::CVarFixedString bundleExtension = commandArgs.size() > 1 ? AZ::CVarFixedString(commandArgs[1]) : defaultBundleExtension; - BundlingSystemRequestBus::Broadcast(&BundlingSystemRequestBus::Events::LoadBundles, bundleFolder, bundleExtension); + BundlingSystemRequestBus::Broadcast(&BundlingSystemRequestBus::Events::LoadBundles, bundleFolder.c_str(), bundleExtension.c_str()); } - void BundlingSystemComponent::ConsoleCommandUnloadBundles(IConsoleCmdArgs* pCmdArgs) + void ConsoleCommandUnloadBundles([[maybe_unused]] const AZ::ConsoleCommandContainer& commandArgs) { - AZ_UNUSED(pCmdArgs); BundlingSystemRequestBus::Broadcast(&BundlingSystemRequestBus::Events::UnloadBundles); } void BundlingSystemComponent::UnloadBundles() { - ISystem* crySystem{ GetISystem() }; - if (!crySystem) + auto archive = AZ::Interface::Get(); + if (!archive) { - AZ_Error("BundlingSystem", false, "Couldn't Get ISystem to unload bundles!"); + AZ_Error("BundlingSystem", false, "Couldn't Get IArchive to load bundles!"); return; } if (!m_bundleModeBundles.size()) @@ -106,7 +102,7 @@ namespace LmbrCentral AZStd::lock_guard openBundleLock(m_bundleModeMutex); for (const auto& thisBundle : m_bundleModeBundles) { - if (crySystem->GetIPak()->ClosePack(thisBundle.c_str())) + if (archive->ClosePack(thisBundle.c_str())) { AZ_TracePrintf("BundlingSystem", "Unloaded %s\n",thisBundle.c_str()); } @@ -128,15 +124,8 @@ namespace LmbrCentral return; } - ISystem* crySystem{ GetISystem() }; - if (!crySystem) - { - AZ_Error("BundlingSystem", false, "Couldn't Get ISystem to load bundles!"); - return; - } - - auto cryPak = crySystem->GetIPak(); - if (!cryPak) + auto archive = AZ::Interface::Get(); + if (!archive) { AZ_Error("BundlingSystem", false, "Couldn't Get IArchive to load bundles!"); return; @@ -152,8 +141,8 @@ namespace LmbrCentral } } AZStd::string bundlePath; - AzFramework::StringFunc::Path::Join(bundleRoot, thisBundle.c_str(), bundlePath); - if (cryPak->OpenPack(bundleRoot, thisBundle.c_str())) + AZ::StringFunc::Path::Join(bundleRoot, thisBundle.c_str(), bundlePath); + if (archive->OpenPack(bundleRoot, thisBundle.c_str())) { AZ_TracePrintf("BundlingSystem", "Loaded bundle %s\n",bundlePath.c_str()); m_bundleModeBundles.emplace_back(AZStd::move(bundlePath)); @@ -230,28 +219,21 @@ namespace LmbrCentral void BundlingSystemComponent::OpenDependentBundles(const char* bundleName, AZStd::shared_ptr bundleManifest) { - ISystem* crySystem{ GetISystem() }; - if (!crySystem) - { - AZ_Error("BundlingSystem", false, "Couldn't Get ISystem to load dependent bundles for %s", bundleName); - return; - } - - auto cryPak{ crySystem->GetIPak() }; - if (!cryPak) + auto archive = AZ::Interface::Get(); + if (!archive) { AZ_Error("BundlingSystem", false, "Couldn't Get IArchive to load dependent bundles for %s", bundleName); return; } AZStd::string folderPath; - AzFramework::StringFunc::Path::GetFolderPath(bundleName, folderPath); + AZ::StringFunc::Path::GetFolderPath(bundleName, folderPath); for (const auto& thisBundle : bundleManifest->GetDependentBundleNames()) { AZStd::string bundlePath; - AzFramework::StringFunc::Path::Join(folderPath.c_str(), thisBundle.c_str(), bundlePath); + AZ::StringFunc::Path::Join(folderPath.c_str(), thisBundle.c_str(), bundlePath); - if (!cryPak->OpenPack(bundleRoot, bundlePath.c_str())) + if (!archive->OpenPack(bundleRoot, bundlePath.c_str())) { // We're not bailing here intentionally - try to open the remaining bundles AZ_Warning("BundlingSystem", false, "Failed to open dependent bundle %s of bundle %s", bundlePath.c_str(), bundleName); @@ -300,28 +282,21 @@ namespace LmbrCentral void BundlingSystemComponent::CloseDependentBundles(const char* bundleName, AZStd::shared_ptr bundleManifest) { - ISystem* crySystem{ GetISystem() }; - if (!crySystem) - { - AZ_Error("BundlingSystem", false, "Couldn't get ISystem to close dependent bundles for %s", bundleName); - return; - } - - auto cryPak{ crySystem->GetIPak() }; - if (!cryPak) + auto archive = AZ::Interface::Get(); + if (!archive) { AZ_Error("BundlingSystem", false, "Couldn't get IArchive to close dependent bundles for %s", bundleName); return; } AZStd::string folderPath; - AzFramework::StringFunc::Path::GetFolderPath(bundleName, folderPath); + AZ::StringFunc::Path::GetFolderPath(bundleName, folderPath); for (const auto& thisBundle : bundleManifest->GetDependentBundleNames()) { AZStd::string bundlePath; - AzFramework::StringFunc::Path::Join(folderPath.c_str(), thisBundle.c_str(), bundlePath); + AZ::StringFunc::Path::Join(folderPath.c_str(), thisBundle.c_str(), bundlePath); - if (!cryPak->ClosePack(bundlePath.c_str())) + if (!archive->ClosePack(bundlePath.c_str())) { // We're not bailing here intentionally - try to close the remaining bundles AZ_Warning("BundlingSystem", false, "Failed to close dependent bundle %s of bundle %s", bundlePath.c_str(), bundleName); diff --git a/Gems/LmbrCentral/Code/Source/Bundling/BundlingSystemComponent.h b/Gems/LmbrCentral/Code/Source/Bundling/BundlingSystemComponent.h index 15fdf1103c..e7c8775530 100644 --- a/Gems/LmbrCentral/Code/Source/Bundling/BundlingSystemComponent.h +++ b/Gems/LmbrCentral/Code/Source/Bundling/BundlingSystemComponent.h @@ -19,11 +19,8 @@ #include -#include #include -struct IConsoleCmdArgs; - namespace AzFramework { class AssetBundleManifest; @@ -42,10 +39,9 @@ namespace LmbrCentral * System component for managing bundles */ class BundlingSystemComponent - : public AZ::Component, - public BundlingSystemRequestBus::Handler, - public CrySystemEventBus::Handler, - public AZ::IO::ArchiveNotificationBus::Handler + : public AZ::Component + , public BundlingSystemRequestBus::Handler + , public AZ::IO::ArchiveNotificationBus::Handler { public: AZ_COMPONENT(BundlingSystemComponent, "{0FB7153D-EE80-4B1C-9584-134270401AAF}"); @@ -70,13 +66,6 @@ namespace LmbrCentral void BundleOpened(const char* bundleName, AZStd::shared_ptr bundleManifest, const char* nextBundle, AZStd::shared_ptr bundleCatalog) override; void BundleClosed(const char* bundleName) override; - // CrySystemEventBus - void OnCrySystemInitialized(ISystem& system, const SSystemInitParams& systemInitParams) override; - - // Calls the LoadBundles method - static void ConsoleCommandLoadBundles(IConsoleCmdArgs* pCmdArgs); - // Calls the UnloadBundles method - static void ConsoleCommandUnloadBundles(IConsoleCmdArgs* pCmdArgs); AZStd::vector GetBundleList(const char* bundlePath, const char* bundleExtension) const; diff --git a/Gems/LmbrCentral/Code/Source/LmbrCentral.cpp b/Gems/LmbrCentral/Code/Source/LmbrCentral.cpp index a5884ccce9..027d19fdef 100644 --- a/Gems/LmbrCentral/Code/Source/LmbrCentral.cpp +++ b/Gems/LmbrCentral/Code/Source/LmbrCentral.cpp @@ -61,7 +61,6 @@ // Asset types #include -#include #include #include #include @@ -84,8 +83,6 @@ namespace LmbrCentral { - static const char* s_assetCatalogFilename = "assetcatalog.xml"; - using LmbrCentralAllocatorScope = AZ::AllocatorScope; // This component boots the required allocators for LmbrCentral everywhere but AssetBuilders @@ -354,10 +351,8 @@ namespace LmbrCentral AZ_Assert(AZ::Data::AssetManager::IsReady(), "Asset manager isn't ready!"); // Add asset types and extensions to AssetCatalog. Uses "AssetCatalogService". - auto assetCatalog = AZ::Data::AssetCatalogRequestBus::FindFirstHandler(); - if (assetCatalog) + if (auto assetCatalog = AZ::Data::AssetCatalogRequestBus::FindFirstHandler(); assetCatalog) { - assetCatalog->EnableCatalogForAsset(AZ::AzTypeInfo::Uuid()); assetCatalog->EnableCatalogForAsset(AZ::AzTypeInfo::Uuid()); assetCatalog->EnableCatalogForAsset(AZ::AzTypeInfo::Uuid()); assetCatalog->EnableCatalogForAsset(AZ::AzTypeInfo::Uuid()); @@ -371,12 +366,10 @@ namespace LmbrCentral assetCatalog->AddExtension("xml"); assetCatalog->AddExtension("mtl"); assetCatalog->AddExtension("dccmtl"); - assetCatalog->AddExtension("lua"); assetCatalog->AddExtension("sprite"); assetCatalog->AddExtension("cax"); } - CrySystemEventBus::Handler::BusConnect(); AZ::Data::AssetManagerNotificationBus::Handler::BusConnect(); @@ -448,7 +441,6 @@ namespace LmbrCentral m_unhandledAssetInfo.clear(); AZ::Data::AssetManagerNotificationBus::Handler::BusDisconnect(); - CrySystemEventBus::Handler::BusDisconnect(); // AssetHandler's destructor calls Unregister() m_assetHandlers.clear(); @@ -459,42 +451,6 @@ namespace LmbrCentral } m_allocatorShutdowns.clear(); } - - void LmbrCentralSystemComponent::OnCrySystemPreInitialize([[maybe_unused]] ISystem& system, [[maybe_unused]] const SSystemInitParams& systemInitParams) - { - EBUS_EVENT(AZ::Data::AssetCatalogRequestBus, StartMonitoringAssets); - } - - void LmbrCentralSystemComponent::OnCrySystemInitialized(ISystem& system, const SSystemInitParams& systemInitParams) - { -#if !defined(AZ_MONOLITHIC_BUILD) - // When module is linked dynamically, we must set our gEnv pointer. - // When module is linked statically, we'll share the application's gEnv pointer. - gEnv = system.GetGlobalEnvironment(); -#endif - - // Enable catalog now that application's asset root is set. - if (system.GetGlobalEnvironment()->IsEditor()) - { - // In the editor, we build the catalog by scanning the disk. - if (systemInitParams.pUserCallback) - { - systemInitParams.pUserCallback->OnInitProgress("Refreshing asset catalog..."); - } - } - - // load the catalog from disk (supported over VFS). - EBUS_EVENT(AZ::Data::AssetCatalogRequestBus, LoadCatalog, AZStd::string::format("@products@/%s", s_assetCatalogFilename).c_str()); - } - - void LmbrCentralSystemComponent::OnCrySystemShutdown([[maybe_unused]] ISystem& system) - { - EBUS_EVENT(AZ::Data::AssetCatalogRequestBus, StopMonitoringAssets); - -#if !defined(AZ_MONOLITHIC_BUILD) - gEnv = nullptr; -#endif - } } // namespace LmbrCentral #if !defined(LMBR_CENTRAL_EDITOR) diff --git a/Gems/LmbrCentral/Code/Source/LmbrCentral.h b/Gems/LmbrCentral/Code/Source/LmbrCentral.h index 9a0c327ea7..650061dce6 100644 --- a/Gems/LmbrCentral/Code/Source/LmbrCentral.h +++ b/Gems/LmbrCentral/Code/Source/LmbrCentral.h @@ -15,8 +15,6 @@ #include #include -#include - /*! * \namespace LmbrCentral * LmbrCentral ties together systems from CryEngine and systems from the AZ framework. @@ -49,7 +47,6 @@ namespace LmbrCentral */ class LmbrCentralSystemComponent : public AZ::Component - , private CrySystemEventBus::Handler , private AZ::Data::AssetManagerNotificationBus::Handler { public: @@ -71,13 +68,6 @@ namespace LmbrCentral void Deactivate() override; //////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////// - // CrySystemEvents - void OnCrySystemPreInitialize(ISystem& system, const SSystemInitParams& systemInitParams) override; - void OnCrySystemInitialized(ISystem& system, const SSystemInitParams& systemInitParams) override; - void OnCrySystemShutdown(ISystem& system) override; - //////////////////////////////////////////////////////////////////////////// - AZStd::vector > m_assetHandlers; AZStd::vector > m_unhandledAssetInfo; AZStd::vector> m_allocatorShutdowns; diff --git a/Gems/LyShine/Code/CMakeLists.txt b/Gems/LyShine/Code/CMakeLists.txt index f8c2ebfd07..77aaa681e0 100644 --- a/Gems/LyShine/Code/CMakeLists.txt +++ b/Gems/LyShine/Code/CMakeLists.txt @@ -52,8 +52,9 @@ ly_add_target( Gem::TextureAtlas ) -# by default, load the above "Gem::LyShine" module in Client applications: +# by default, load the above "Gem::LyShine" module in Client and Server applications: ly_create_alias(NAME LyShine.Clients NAMESPACE Gem TARGETS Gem::LyShine) +ly_create_alias(NAME LyShine.Servers NAMESPACE Gem TARGETS Gem::LyShine) if (PAL_TRAIT_BUILD_HOST_TOOLS) ly_add_target( diff --git a/Gems/LyShine/Code/Editor/ViewportInteraction.cpp b/Gems/LyShine/Code/Editor/ViewportInteraction.cpp index e6bdb4d8d5..a39a7b9cac 100644 --- a/Gems/LyShine/Code/Editor/ViewportInteraction.cpp +++ b/Gems/LyShine/Code/Editor/ViewportInteraction.cpp @@ -739,14 +739,32 @@ void ViewportInteraction::MouseWheelEvent(QWheelEvent* ev) bool ViewportInteraction::KeyPressEvent(QKeyEvent* ev) { - if (ev->key() == Qt::Key_Space) + switch (ev->key()) { + case Qt::Key_Space: if (!ev->isAutoRepeat()) { ActivateSpaceBar(); } - return true; + case Qt::Key_Up: + Nudge(ViewportInteraction::NudgeDirection::Up, + (ev->modifiers() & Qt::ShiftModifier) ? ViewportInteraction::NudgeSpeed::Fast : ViewportInteraction::NudgeSpeed::Slow); + return true; + case Qt::Key_Down: + Nudge(ViewportInteraction::NudgeDirection::Down, + (ev->modifiers() & Qt::ShiftModifier) ? ViewportInteraction::NudgeSpeed::Fast : ViewportInteraction::NudgeSpeed::Slow); + return true; + case Qt::Key_Left: + Nudge(ViewportInteraction::NudgeDirection::Left, + (ev->modifiers() & Qt::ShiftModifier) ? ViewportInteraction::NudgeSpeed::Fast : ViewportInteraction::NudgeSpeed::Slow); + return true; + case Qt::Key_Right: + Nudge(ViewportInteraction::NudgeDirection::Right, + (ev->modifiers() & Qt::ShiftModifier) ? ViewportInteraction::NudgeSpeed::Fast : ViewportInteraction::NudgeSpeed::Slow); + return true; + default: + break; } return false; diff --git a/Gems/LyShine/Code/Editor/ViewportWidget.cpp b/Gems/LyShine/Code/Editor/ViewportWidget.cpp index e172d43b60..bf7447585c 100644 --- a/Gems/LyShine/Code/Editor/ViewportWidget.cpp +++ b/Gems/LyShine/Code/Editor/ViewportWidget.cpp @@ -220,6 +220,7 @@ ViewportWidget::ViewportWidget(EditorWindow* parent) InitUiRenderer(); SetupShortcuts(); + installEventFilter(m_editorWindow); // Setup a timer for the maximum refresh rate we want. // Refresh is actually triggered by interaction events and by the IdleUpdate. This avoids the UI @@ -258,6 +259,8 @@ ViewportWidget::~ViewportWidget() LyShinePassDataRequestBus::Handler::BusDisconnect(); AZ::RPI::ViewportContextNotificationBus::Handler::BusDisconnect(); + removeEventFilter(m_editorWindow); + m_uiRenderer.reset(); // Notify LyShine that this is no longer a valid UiRenderer. @@ -688,9 +691,9 @@ void ViewportWidget::wheelEvent(QWheelEvent* ev) Refresh(); } -bool ViewportWidget::event(QEvent* ev) +bool ViewportWidget::eventFilter([[maybe_unused]] QObject* watched, QEvent* event) { - if (ev->type() == QEvent::ShortcutOverride) + if (event->type() == QEvent::ShortcutOverride) { // When a shortcut is matched, Qt's event processing sends out a shortcut override event // to allow other systems to override it. If it's not overridden, then the key events @@ -698,40 +701,48 @@ bool ViewportWidget::event(QEvent* ev) // handler. In our case this causes a problem in preview mode for the Key_Delete event. // So, if we are preview mode avoid treating Key_Delete as a shortcut. - QKeyEvent* keyEvent = static_cast(ev); + QKeyEvent* keyEvent = static_cast(event); int key = keyEvent->key(); // Override the space bar shortcut so that the key gets handled by the viewport's KeyPress/KeyRelease // events when the viewport has the focus. The space bar is set up as a shortcut in order to give the // viewport the focus and activate the space bar when another widget has the focus. Once the shortcut // is pressed and focus is given to the viewport, the viewport takes over handling the space bar via - // the KeyPress/KeyRelease events - if (key == Qt::Key_Space) + // the KeyPress/KeyRelease events. + // Also ignore nudge shortcuts in edit/preview mode so that the KeyPressEvent will be sent. + switch (key) { - ev->accept(); + case Qt::Key_Space: + case Qt::Key_Up: + case Qt::Key_Down: + case Qt::Key_Left: + case Qt::Key_Right: + { + event->accept(); return true; } + default: + { + break; + } + } UiEditorMode editorMode = m_editorWindow->GetEditorMode(); if (editorMode == UiEditorMode::Preview) { - switch (key) + if (key == Qt::Key_Delete) { - case Qt::Key_Delete: - // Ignore nudge shortcuts in preview mode so that the KeyPressEvent will be sent - case Qt::Key_Up: - case Qt::Key_Down: - case Qt::Key_Left: - case Qt::Key_Right: - { - ev->accept(); + event->accept(); return true; } - break; - }; } } - + + return false; +} + +bool ViewportWidget::event(QEvent* ev) +{ bool result = RenderViewportWidget::event(ev); return result; } @@ -742,8 +753,7 @@ void ViewportWidget::keyPressEvent(QKeyEvent* event) if (editorMode == UiEditorMode::Edit) { // in Edit mode just send input to ViewportInteraction - bool handled = m_viewportInteraction->KeyPressEvent(event); - if (!handled) + if (!m_viewportInteraction->KeyPressEvent(event)) { RenderViewportWidget::keyPressEvent(event); } @@ -1246,115 +1256,6 @@ void ViewportWidget::SetupShortcuts() { // Actions with shortcuts are created instead of direct shortcuts because the shortcut dispatcher only looks for matching actions - // Create nudge shortcuts that are active across the entire UI Editor window. Any widgets (such as the spin box widget) that - // handle the same keys and want the shortcut to be ignored need to handle that with a shortcut override event. - // In preview mode, the nudge shortcuts are ignored via the shortcut override event. KeyPressEvents are sent instead, - // and passed along to the canvas - - // Nudge up - { - QAction* action = new QAction("Up", this); - action->setShortcut(QKeySequence(Qt::Key_Up)); - QObject::connect(action, - &QAction::triggered, - [this]() - { - m_viewportInteraction->Nudge(ViewportInteraction::NudgeDirection::Up, ViewportInteraction::NudgeSpeed::Slow); - }); - addAction(action); - } - - // Nudge up fast - { - QAction* action = new QAction("Up Fast", this); - action->setShortcut(QKeySequence(Qt::SHIFT + Qt::Key_Up)); - QObject::connect(action, - &QAction::triggered, - [this]() - { - m_viewportInteraction->Nudge(ViewportInteraction::NudgeDirection::Up, ViewportInteraction::NudgeSpeed::Fast); - }); - addAction(action); - } - - // Nudge down - { - QAction* action = new QAction("Down", this); - action->setShortcut(QKeySequence(Qt::Key_Down)); - QObject::connect(action, - &QAction::triggered, - [this]() - { - m_viewportInteraction->Nudge(ViewportInteraction::NudgeDirection::Down, ViewportInteraction::NudgeSpeed::Slow); - }); - addAction(action); - } - - // Nudge down fast - { - QAction* action = new QAction("Down Fast", this); - action->setShortcut(QKeySequence(Qt::SHIFT + Qt::Key_Down)); - QObject::connect(action, - &QAction::triggered, - [this]() - { - m_viewportInteraction->Nudge(ViewportInteraction::NudgeDirection::Down, ViewportInteraction::NudgeSpeed::Fast); - }); - addAction(action); - } - - // Nudge left - { - QAction* action = new QAction("Left", this); - action->setShortcut(QKeySequence(Qt::Key_Left)); - QObject::connect(action, - &QAction::triggered, - [this]() - { - m_viewportInteraction->Nudge(ViewportInteraction::NudgeDirection::Left, ViewportInteraction::NudgeSpeed::Slow); - }); - addAction(action); - } - - // Nudge left fast - { - QAction* action = new QAction("Left Fast", this); - action->setShortcut(QKeySequence(Qt::SHIFT + Qt::Key_Left)); - QObject::connect(action, - &QAction::triggered, - [this]() - { - m_viewportInteraction->Nudge(ViewportInteraction::NudgeDirection::Left, ViewportInteraction::NudgeSpeed::Fast); - }); - addAction(action); - } - - // Nudge right - { - QAction* action = new QAction("Right", this); - action->setShortcut(QKeySequence(Qt::Key_Right)); - QObject::connect(action, - &QAction::triggered, - [this]() - { - m_viewportInteraction->Nudge(ViewportInteraction::NudgeDirection::Right, ViewportInteraction::NudgeSpeed::Slow); - }); - addAction(action); - } - - // Nudge right fast - { - QAction* action = new QAction("Right Fast", this); - action->setShortcut(QKeySequence(Qt::SHIFT + Qt::Key_Right)); - QObject::connect(action, - &QAction::triggered, - [this]() - { - m_viewportInteraction->Nudge(ViewportInteraction::NudgeDirection::Right, ViewportInteraction::NudgeSpeed::Fast); - }); - addAction(action); - } - // Give the viewport focus and activate the space bar { QAction* action = new QAction("Viewport Focus", this); diff --git a/Gems/LyShine/Code/Editor/ViewportWidget.h b/Gems/LyShine/Code/Editor/ViewportWidget.h index 620cb8fb35..722335aa93 100644 --- a/Gems/LyShine/Code/Editor/ViewportWidget.h +++ b/Gems/LyShine/Code/Editor/ViewportWidget.h @@ -122,12 +122,15 @@ protected: void wheelEvent(QWheelEvent* ev) override; //! Prevents shortcuts from interfering with preview mode. + bool eventFilter(QObject* watched, QEvent* event) override; + + //! Handle events from Qt. bool event(QEvent* ev) override; - //! Key press event from Qt + //! Key press event from Qt. void keyPressEvent(QKeyEvent* event) override; - //! Key release event from Qt + //! Key release event from Qt. void keyReleaseEvent(QKeyEvent* event) override; void focusOutEvent(QFocusEvent* ev) override; diff --git a/Gems/LyShine/Code/Source/Draw2d.cpp b/Gems/LyShine/Code/Source/Draw2d.cpp index b0539900e2..3476c530b2 100644 --- a/Gems/LyShine/Code/Source/Draw2d.cpp +++ b/Gems/LyShine/Code/Source/Draw2d.cpp @@ -65,7 +65,7 @@ CDraw2d::~CDraw2d() } //////////////////////////////////////////////////////////////////////////////////////////////////// -void CDraw2d::OnBootstrapSceneReady([[maybe_unused]] AZ::RPI::Scene* bootstrapScene) +void CDraw2d::OnBootstrapSceneReady(AZ::RPI::Scene* bootstrapScene) { // At this point the RPI is ready for use @@ -74,16 +74,16 @@ void CDraw2d::OnBootstrapSceneReady([[maybe_unused]] AZ::RPI::Scene* bootstrapSc AZ::Data::Instance shader = AZ::RPI::LoadCriticalShader(shaderFilepath); // Set scene to be associated with the dynamic draw context - AZ::RPI::ScenePtr scene; + AZ::RPI::Scene* scene = nullptr; if (m_viewportContext) { // Use scene associated with the specified viewport context - scene = m_viewportContext->GetRenderScene(); + scene = m_viewportContext->GetRenderScene().get(); } else { - // No viewport context specified, use default scene - scene = AZ::RPI::RPISystemInterface::Get()->GetDefaultScene(); + // No viewport context specified, use main scene + scene = bootstrapScene; } AZ_Assert(scene != nullptr, "Attempting to create a DynamicDrawContext for a viewport context that has not been associated with a scene yet."); @@ -113,7 +113,7 @@ void CDraw2d::OnBootstrapSceneReady([[maybe_unused]] AZ::RPI::Scene* bootstrapSc else { // Render target support is disabled - m_dynamicDraw->SetOutputScope(scene.get()); + m_dynamicDraw->SetOutputScope(scene); } m_dynamicDraw->EndInit(); diff --git a/Gems/LyShine/Code/Source/LyShine.cpp b/Gems/LyShine/Code/Source/LyShine.cpp index 2cee4e92eb..19c6e4281e 100644 --- a/Gems/LyShine/Code/Source/LyShine.cpp +++ b/Gems/LyShine/Code/Source/LyShine.cpp @@ -653,12 +653,12 @@ void CLyShine::OnRenderTick() } //////////////////////////////////////////////////////////////////////////////////////////////////// -void CLyShine::OnBootstrapSceneReady([[maybe_unused]] AZ::RPI::Scene* bootstrapScene) +void CLyShine::OnBootstrapSceneReady(AZ::RPI::Scene* bootstrapScene) { // Load cursor if its path was set before RPI was initialized LoadUiCursor(); - LyShinePassDataRequestBus::Handler::BusConnect(AZ::RPI::RPISystemInterface::Get()->GetDefaultScene()->GetId()); + LyShinePassDataRequestBus::Handler::BusConnect(bootstrapScene->GetId()); } //////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/Gems/LyShine/Code/Source/UiRenderer.cpp b/Gems/LyShine/Code/Source/UiRenderer.cpp index 3111f31615..98b376ec8b 100644 --- a/Gems/LyShine/Code/Source/UiRenderer.cpp +++ b/Gems/LyShine/Code/Source/UiRenderer.cpp @@ -52,7 +52,7 @@ bool UiRenderer::IsReady() return m_isRPIReady; } -void UiRenderer::OnBootstrapSceneReady([[maybe_unused]] AZ::RPI::Scene* bootstrapScene) +void UiRenderer::OnBootstrapSceneReady(AZ::RPI::Scene* bootstrapScene) { // At this point the RPI is ready for use @@ -64,16 +64,17 @@ void UiRenderer::OnBootstrapSceneReady([[maybe_unused]] AZ::RPI::Scene* bootstra if (m_viewportContext) { // Create a new scene based on the user specified viewport context - m_scene = CreateScene(m_viewportContext); + m_ownedScene = CreateScene(m_viewportContext); + m_scene = m_ownedScene.get(); } else { // No viewport context specified, use default scene - m_scene = AZ::RPI::RPISystemInterface::Get()->GetDefaultScene(); + m_scene = bootstrapScene; } // Create a dynamic draw context for UI Canvas drawing for the scene - m_dynamicDraw = CreateDynamicDrawContext(m_scene, uiShader); + m_dynamicDraw = CreateDynamicDrawContext(uiShader); if (m_dynamicDraw) { @@ -93,6 +94,7 @@ AZ::RPI::ScenePtr UiRenderer::CreateScene(AZStd::shared_ptrEnableAllFeatureProcessors(); // LYSHINE_ATOM_TODO - have a UI pipeline and enable only needed fps @@ -116,7 +118,6 @@ AZ::RPI::ScenePtr UiRenderer::CreateScene(AZStd::shared_ptr UiRenderer::CreateDynamicDrawContext( - AZ::RPI::ScenePtr scene, AZ::Data::Instance uiShader) { // Find the pass that renders the UI canvases after the rtt passes @@ -144,7 +145,7 @@ AZ::RHI::Ptr UiRenderer::CreateDynamicDrawContext( else { // Render target support is disabled - dynamicDraw->SetOutputScope(m_scene.get()); + dynamicDraw->SetOutputScope(m_scene); } dynamicDraw->EndInit(); diff --git a/Gems/LyShine/Code/Source/UiRenderer.h b/Gems/LyShine/Code/Source/UiRenderer.h index 3be3c832d3..0e15d41907 100644 --- a/Gems/LyShine/Code/Source/UiRenderer.h +++ b/Gems/LyShine/Code/Source/UiRenderer.h @@ -152,7 +152,6 @@ private: // member functions //! Create a dynamic draw context for this renderer AZ::RHI::Ptr CreateDynamicDrawContext( - AZ::RPI::ScenePtr scene, AZ::Data::Instance uiShader); //! Bind the global white texture for all the texture units we use @@ -175,7 +174,8 @@ protected: // attributes // Set by user when viewport context is not the main/default viewport AZStd::shared_ptr m_viewportContext; - AZ::RPI::ScenePtr m_scene; + AZ::RPI::ScenePtr m_ownedScene; + AZ::RPI::Scene* m_scene = nullptr; #ifndef _RELEASE int m_debugTextureDataRecordLevel = 0; diff --git a/Gems/LyShine/Code/Source/World/UiCanvasAssetRefComponent.cpp b/Gems/LyShine/Code/Source/World/UiCanvasAssetRefComponent.cpp index d2a307ecaf..59d764f297 100644 --- a/Gems/LyShine/Code/Source/World/UiCanvasAssetRefComponent.cpp +++ b/Gems/LyShine/Code/Source/World/UiCanvasAssetRefComponent.cpp @@ -239,15 +239,16 @@ void UiCanvasAssetRefComponent::Activate() //////////////////////////////////////////////////////////////////////////////////////////////////// void UiCanvasAssetRefComponent::Deactivate() { -#if !defined(DEDICATED_SERVER) - if (m_canvasEntityId.IsValid()) + if (!gEnv->IsDedicated()) { - gEnv->pLyShine->ReleaseCanvasDeferred(m_canvasEntityId); - m_canvasEntityId.SetInvalid(); - } + if (m_canvasEntityId.IsValid()) + { + gEnv->pLyShine->ReleaseCanvasDeferred(m_canvasEntityId); + m_canvasEntityId.SetInvalid(); + } - UiCanvasAssetRefBus::Handler::BusDisconnect(); - UiCanvasRefBus::Handler::BusDisconnect(); - UiCanvasManagerNotificationBus::Handler::BusDisconnect(); -#endif + UiCanvasAssetRefBus::Handler::BusDisconnect(); + UiCanvasRefBus::Handler::BusDisconnect(); + UiCanvasManagerNotificationBus::Handler::BusDisconnect(); + } } diff --git a/Gems/Multiplayer/Code/CMakeLists.txt b/Gems/Multiplayer/Code/CMakeLists.txt index b971e6a2ce..559fa23553 100644 --- a/Gems/Multiplayer/Code/CMakeLists.txt +++ b/Gems/Multiplayer/Code/CMakeLists.txt @@ -149,6 +149,8 @@ if (PAL_TRAIT_BUILD_HOST_TOOLS) AZ::AzFramework AZ::AzNetworking AZ::AzToolsFramework + Gem::Atom_RPI.Public + Gem::Atom_RHI.Reflect Gem::Multiplayer.Static Gem::Multiplayer.Builders ) diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/Editor/MultiplayerPythonEditorEventsBus.h b/Gems/Multiplayer/Code/Include/Multiplayer/Editor/MultiplayerPythonEditorEventsBus.h new file mode 100644 index 0000000000..82b63d94f0 --- /dev/null +++ b/Gems/Multiplayer/Code/Include/Multiplayer/Editor/MultiplayerPythonEditorEventsBus.h @@ -0,0 +1,32 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ +#pragma once + +#include + +namespace Multiplayer +{ + /** + * This bus can be used to send commands to the editor. + */ + class MultiplayerEditorLayerPythonRequests + : public AZ::ComponentBus + { + public: + /* + * Enters the editor game mode and launches/connects to the server launcher. + */ + virtual void EnterGameMode() = 0; + + /* + * Queries if the Editor is in game mode, the editor-server has finished connecting, and the default network player has spawned. + */ + virtual bool IsInGameMode() = 0; + }; + using MultiplayerEditorLayerPythonRequestBus = AZ::EBus; +} diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/MultiplayerEditorServerBus.h b/Gems/Multiplayer/Code/Include/Multiplayer/MultiplayerEditorServerBus.h new file mode 100644 index 0000000000..1c6d2152bb --- /dev/null +++ b/Gems/Multiplayer/Code/Include/Multiplayer/MultiplayerEditorServerBus.h @@ -0,0 +1,27 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include + +namespace Multiplayer +{ + class MultiplayerEditorServerRequests : public AZ::EBusTraits + { + public: + static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single; + static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single; + + //! Sends a packet that initializes a local server launched from the editor. + //! The editor will package the data required for loading the current editor level on the editor-server; data includes entities and asset data. + //! @param connection The connection to the editor-server + virtual void SendEditorServerLevelDataPacket(AzNetworking::IConnection* connection) = 0; + }; + using MultiplayerEditorServerRequestBus = AZ::EBus; +} // namespace Multiplayer diff --git a/Gems/Multiplayer/Code/Source/AutoGen/MultiplayerEditor.AutoPackets.xml b/Gems/Multiplayer/Code/Source/AutoGen/MultiplayerEditor.AutoPackets.xml index dd553a2413..b8b880d03c 100644 --- a/Gems/Multiplayer/Code/Source/AutoGen/MultiplayerEditor.AutoPackets.xml +++ b/Gems/Multiplayer/Code/Source/AutoGen/MultiplayerEditor.AutoPackets.xml @@ -4,13 +4,15 @@ - - + + + + - + diff --git a/Gems/Multiplayer/Code/Source/Components/NetworkHierarchyChildComponent.cpp b/Gems/Multiplayer/Code/Source/Components/NetworkHierarchyChildComponent.cpp index 59782b1f4d..aa90b1b17b 100644 --- a/Gems/Multiplayer/Code/Source/Components/NetworkHierarchyChildComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Components/NetworkHierarchyChildComponent.cpp @@ -45,6 +45,7 @@ namespace Multiplayer void NetworkHierarchyChildComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided) { provided.push_back(AZ_CRC_CE("NetworkHierarchyChildComponent")); + provided.push_back(AZ_CRC_CE("MultiplayerInputDriver")); } void NetworkHierarchyChildComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible) diff --git a/Gems/Multiplayer/Code/Source/Components/NetworkHierarchyRootComponent.cpp b/Gems/Multiplayer/Code/Source/Components/NetworkHierarchyRootComponent.cpp index 76f4bddb1a..1404484d5c 100644 --- a/Gems/Multiplayer/Code/Source/Components/NetworkHierarchyRootComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Components/NetworkHierarchyRootComponent.cpp @@ -53,6 +53,7 @@ namespace Multiplayer void NetworkHierarchyRootComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided) { provided.push_back(AZ_CRC_CE("NetworkHierarchyRootComponent")); + provided.push_back(AZ_CRC_CE("MultiplayerInputDriver")); } void NetworkHierarchyRootComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible) diff --git a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.cpp b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.cpp index a30ab207b4..3c3c4f0664 100644 --- a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.cpp +++ b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -17,7 +18,6 @@ #include #include #include -#include #include #include @@ -26,6 +26,7 @@ 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."); + AZ_CVAR(uint16_t, editorsv_port, DefaultServerEditorPort, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The port that the multiplayer editor gem will bind to for traffic."); MultiplayerEditorConnection::MultiplayerEditorConnection() : m_byteStream(&m_buffer) @@ -33,23 +34,42 @@ namespace Multiplayer m_networkEditorInterface = AZ::Interface::Get()->CreateNetworkInterface( AZ::Name(MpEditorInterfaceName), ProtocolType::Tcp, TrustZone::ExternalClientToServer, *this); m_networkEditorInterface->SetTimeoutMs(AZ::TimeMs{ 0 }); // Disable timeouts on this network interface - if (editorsv_isDedicated) + ActivateDedicatedEditorServer(); + } + + void MultiplayerEditorConnection::ActivateDedicatedEditorServer() const + { + if (m_isActivated || !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); + return; + } + m_isActivated = true; + + AZ_Assert(m_networkEditorInterface, "MP Editor Network Interface was unregistered before Editor Server could start listening.") + + // Check if there's already an Editor out there waiting to connect + const ConnectionId editorServerToEditorConnectionId = + m_networkEditorInterface->Connect(IpAddress(LocalHost.data(), editorsv_port, ProtocolType::Tcp)); + + // If there wasn't an Editor waiting for this server to start, then assume this is an editor-server launched by hand... listen + // and wait for the editor to request a connection + if (editorServerToEditorConnectionId == InvalidConnectionId) + { + m_networkEditorInterface->Listen(editorsv_port); + AZ_Printf("MultiplayerEditorConnection", "Editor-server activation did not find an editor in game-mode willing to connect; we'll instead wait and listen for an editor trying to connect to us.") + } + else + { + m_networkEditorInterface->SendReliablePacket(editorServerToEditorConnectionId, MultiplayerEditorPackets::EditorServerReadyForLevelData()); + AZ_Printf("MultiplayerEditorConnection", "Editor-server activation has found and connected to the editor.") } } - + bool MultiplayerEditorConnection::HandleRequest ( [[maybe_unused]] AzNetworking::IConnection* connection, [[maybe_unused]] const IPacketHeader& packetHeader, - [[maybe_unused]] MultiplayerEditorPackets::EditorServerInit& packet + [[maybe_unused]] MultiplayerEditorPackets::EditorServerLevelData& packet ) { // Editor Server Init is intended for non-release targets @@ -76,7 +96,7 @@ namespace Multiplayer AZ::Data::AssetData* assetDatum = AZ::Utils::LoadObjectFromStream(m_byteStream, nullptr); if (!assetDatum) { - AZLOG_ERROR("EditorServerInit packet contains no asset data. Asset: %s", assetHint.c_str()); + AZLOG_ERROR("EditorServerLevelData packet contains no asset data. Asset: %s", assetHint.c_str()) return false; } assetSize = m_byteStream.GetCurPos() - assetSize; @@ -105,26 +125,38 @@ namespace Multiplayer // Load the level via the root spawnable that was registered const AZ::CVarFixedString loadLevelString = "LoadLevel Root.spawnable"; - AZ::Interface::Get()->PerformCommand(loadLevelString.c_str()); + const auto console = AZ::Interface::Get(); + console->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) + uint16_t sv_port = DefaultServerPort; + if (console->GetCvarValue("sv_port", sv_port) != AZ::GetValueResult::Success) { - console->GetCvarValue("sv_port", serverPort); + AZ_Assert(false, + "MultiplayerEditorConnection::HandleRequest for EditorServerLevelData failed! Could not find the sv_port cvar; we won't be able to listen on the correct port for incoming network messages! Please update this code to use a valid cvar!") } - networkInterface->Listen(serverPort); + + networkInterface->Listen(sv_port); - AZLOG_INFO("Editor Server completed asset receive, responding to Editor..."); + AZLOG_INFO("Editor Server completed receiving the editor's level assets, responding to Editor..."); return connection->SendReliablePacket(MultiplayerEditorPackets::EditorServerReady()); } return true; } + bool MultiplayerEditorConnection::HandleRequest( + [[maybe_unused]] AzNetworking::IConnection* connection, + [[maybe_unused]] const AzNetworking::IPacketHeader& packetHeader, + [[maybe_unused]] MultiplayerEditorPackets::EditorServerReadyForLevelData& packet) + { + MultiplayerEditorServerRequestBus::Broadcast(&MultiplayerEditorServerRequestBus::Events::SendEditorServerLevelDataPacket, connection); + return true; + } + bool MultiplayerEditorConnection::HandleRequest ( [[maybe_unused]] AzNetworking::IConnection* connection, @@ -132,22 +164,34 @@ namespace Multiplayer [[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); + // Receiving this packet means Editor sync is done, disconnect + connection->Disconnect(AzNetworking::DisconnectReason::TerminatedByClient, AzNetworking::TerminationEndpoint::Local); + const auto console = AZ::Interface::Get(); + AZ::CVarFixedString editorsv_serveraddr = AZ::CVarFixedString(LocalHost); + uint16_t sv_port = DefaultServerEditorPort; - if (auto console = AZ::Interface::Get(); console) - { - AZ::CVarFixedString remoteAddress; - uint16_t remotePort; - if (console->GetCvarValue("editorsv_serveraddr", remoteAddress) != AZ::GetValueResult::ConsoleVarNotFound && - console->GetCvarValue("sv_port", remotePort) != AZ::GetValueResult::ConsoleVarNotFound) - { - // Connect the Editor to the editor server for Multiplayer simulation - AZ::Interface::Get()->Connect(remoteAddress.c_str(), remotePort); - } - } + if (console->GetCvarValue("sv_port", sv_port) != AZ::GetValueResult::Success) + { + AZ_Assert(false, + "MultiplayerEditorConnection::HandleRequest for EditorServerReady failed! Could not find the sv_port cvar; we may not be able to " + "connect to the correct port for incoming network messages! Please update this code to use a valid cvar!") + } + + if (console->GetCvarValue("editorsv_serveraddr", editorsv_serveraddr) != AZ::GetValueResult::Success) + { + AZ_Assert(false, + "MultiplayerEditorConnection::HandleRequest for EditorServerReady failed! Could not find the editorsv_serveraddr cvar; we may not be able to " + "connect to the correct port for incoming network messages! Please update this code to use a valid cvar!") + } + + // Connect the Editor to the editor server for Multiplayer simulation + if (AZ::Interface::Get()->Connect(editorsv_serveraddr.c_str(), sv_port)) + { + AZ_Printf("MultiplayerEditorConnection", "Editor-server ready. Editor has successfully connected to the editor-server's network simulation.") + } + else + { + AZ_Warning("MultiplayerEditorConnection", false, "MultiplayerEditorConnection::HandleRequest for EditorServerReady failed! Connecting to the editor-server's network simulation failed.") } return true; } @@ -171,26 +215,5 @@ namespace Multiplayer { 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) - { - bool editorLaunch = false; - if (auto console = AZ::Interface::Get(); console) - { - console->GetCvarValue("editorsv_launch", editorLaunch); - } - - if (editorsv_isDedicated && editorLaunch && m_networkEditorInterface->GetConnectionSet().GetConnectionCount() == 1) - { - if (m_networkEditorInterface->GetPort() != 0) - { - m_networkEditorInterface->StopListening(); - } - } - } + } diff --git a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.h b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.h index b93c830cd0..f6510896fe 100644 --- a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.h +++ b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.h @@ -10,8 +10,6 @@ #include -#include -#include #include #include #include @@ -33,7 +31,8 @@ namespace Multiplayer 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::EditorServerReadyForLevelData& packet); + bool HandleRequest(AzNetworking::IConnection* connection, const AzNetworking::IPacketHeader& packetHeader, MultiplayerEditorPackets::EditorServerLevelData& packet); bool HandleRequest(AzNetworking::IConnection* connection, const AzNetworking::IPacketHeader& packetHeader, MultiplayerEditorPackets::EditorServerReady& packet); //! IConnectionListener interface @@ -41,14 +40,16 @@ namespace Multiplayer AzNetworking::ConnectResult ValidateConnect(const AzNetworking::IpAddress& remoteAddress, const AzNetworking::IPacketHeader& packetHeader, AzNetworking::ISerializer& serializer) override; void OnConnect(AzNetworking::IConnection* connection) override; AzNetworking::PacketDispatchResult 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; + void OnPacketLost([[maybe_unused]]AzNetworking::IConnection* connection, [[maybe_unused]]AzNetworking::PacketId packetId) override {} + void OnDisconnect([[maybe_unused]]AzNetworking::IConnection* connection, [[maybe_unused]]AzNetworking::DisconnectReason reason, [[maybe_unused]]AzNetworking::TerminationEndpoint endpoint) override {} //! @} private: + void ActivateDedicatedEditorServer() const; AzNetworking::INetworkInterface* m_networkEditorInterface = nullptr; AZStd::vector m_buffer; AZ::IO::ByteContainerStream> m_byteStream; + mutable bool m_isActivated = false; }; } diff --git a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorGem.cpp b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorGem.cpp index 65d1f43a64..c922a8683f 100644 --- a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorGem.cpp +++ b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorGem.cpp @@ -25,6 +25,7 @@ namespace Multiplayer m_descriptors.end(), { MultiplayerEditorSystemComponent::CreateDescriptor(), + PythonEditorFuncs::CreateDescriptor() }); } diff --git a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp index 55a05e33a7..11aca101b3 100644 --- a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp @@ -12,6 +12,7 @@ #include #include +#include #include #include @@ -23,6 +24,7 @@ #include #include #include +#include namespace Multiplayer { @@ -35,8 +37,47 @@ namespace Multiplayer 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"); + AZ_CVAR(AZ::CVarFixedString, editorsv_rhi_override, "", nullptr, AZ::ConsoleFunctorFlags::DontReplicate, + "Override the default rendering hardware interface (rhi) when launching the Editor server. For example, you may be running an Editor using 'dx12', but want to launch a headless server using 'null'. If empty the server will launch using the same rhi as the Editor."); + AZ_CVAR_EXTERNED(uint16_t, editorsv_port); + + ////////////////////////////////////////////////////////////////////////// + void PyEnterGameMode() + { + editorsv_enabled = true; + editorsv_launch = true; + AzToolsFramework::EditorLayerPythonRequestBus::Broadcast(&AzToolsFramework::EditorLayerPythonRequestBus::Events::EnterGameMode); + } + bool PyIsInGameMode() + { + // If the network entity manager is tracking at least 1 entity then the editor has connected and the autonomous player exists and is being replicated. + if (const INetworkEntityManager* networkEntityManager = AZ::Interface::Get()) + { + return networkEntityManager->GetEntityCount() > 0; + } + + AZ_Warning("MultiplayerEditorSystemComponent", false, "PyIsInGameMode returning false; NetworkEntityManager has not been created yet.") + return false; + } + + void PythonEditorFuncs::Reflect(AZ::ReflectContext* context) + { + if (AZ::BehaviorContext* behaviorContext = azrtti_cast(context)) + { + // This will create static python methods in the 'azlmbr.multiplayer' module + // Note: The methods will be prefixed with the class name, PythonEditorFuncs + // Example Hydra Python: azlmbr.multiplayer.PythonEditorFuncs_enter_game_mode() + behaviorContext->Class() + ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation) + ->Attribute(AZ::Script::Attributes::Module, "multiplayer") + ->Method("enter_game_mode", PyEnterGameMode, nullptr, "Enters the editor game mode and launches/connects to the server launcher.") + ->Method("is_in_game_mode", PyIsInGameMode, nullptr, "Queries if it's in the game mode and the server has finished connecting and the default network player has spawned.") + ; + + } + } + void MultiplayerEditorSystemComponent::Reflect(AZ::ReflectContext* context) { if (AZ::SerializeContext* serializeContext = azrtti_cast(context)) @@ -44,6 +85,18 @@ namespace Multiplayer serializeContext->Class() ->Version(1); } + + // Reflect Python Editor Functions + if (AZ::BehaviorContext* behaviorContext = azrtti_cast(context)) + { + // This will add the MultiplayerPythonEditorBus into the 'azlmbr.multiplayer' module + behaviorContext->EBus("MultiplayerPythonEditorBus") + ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation) + ->Attribute(AZ::Script::Attributes::Module, "multiplayer") + ->Event("EnterGameMode", &MultiplayerEditorLayerPythonRequestBus::Events::EnterGameMode) + ->Event("IsInGameMode", &MultiplayerEditorLayerPythonRequestBus::Events::IsInGameMode) + ; + } } void MultiplayerEditorSystemComponent::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required) @@ -71,6 +124,7 @@ namespace Multiplayer { AzFramework::GameEntityContextEventBus::Handler::BusConnect(); AzToolsFramework::EditorEvents::Bus::Handler::BusConnect(); + MultiplayerEditorServerRequestBus::Handler::BusConnect(); AZ::Interface::Get()->AddServerAcceptanceReceivedHandler(m_serverAcceptanceReceivedHandler); } @@ -78,6 +132,7 @@ namespace Multiplayer { AzToolsFramework::EditorEvents::Bus::Handler::BusDisconnect(); AzFramework::GameEntityContextEventBus::Handler::BusDisconnect(); + MultiplayerEditorServerRequestBus::Handler::BusDisconnect(); } void MultiplayerEditorSystemComponent::NotifyRegisterViews() @@ -107,8 +162,8 @@ namespace Multiplayer m_serverProcess->TerminateProcess(0); m_serverProcess = nullptr; } - INetworkInterface* editorNetworkInterface = AZ::Interface::Get()->RetrieveNetworkInterface(AZ::Name(MpEditorInterfaceName)); - if (editorNetworkInterface) + + if (INetworkInterface* editorNetworkInterface = AZ::Interface::Get()->RetrieveNetworkInterface(AZ::Name(MpEditorInterfaceName))) { editorNetworkInterface->Disconnect(m_editorConnId, AzNetworking::DisconnectReason::TerminatedByClient); } @@ -145,11 +200,21 @@ namespace Multiplayer // Start the configured server if it's available AzFramework::ProcessLauncher::ProcessLaunchInfo processLaunchInfo; + + // Open the server launcher using the same rhi as the editor (or launch with the override rhi) + AZ::Name server_rhi = AZ::RPI::RPISystemInterface::Get()->GetRenderApiName(); + if (!static_cast(editorsv_rhi_override).empty()) + { + server_rhi = static_cast(editorsv_rhi_override); + } + processLaunchInfo.m_commandlineParameters = AZStd::string::format( - R"("%s" --project-path "%s" --editorsv_isDedicated true --sv_defaultPlayerSpawnAsset "%s")", + R"("%s" --project-path "%s" --editorsv_isDedicated true --sv_defaultPlayerSpawnAsset "%s" --rhi "%s")", serverPath.c_str(), AZ::Utils::GetProjectPath().c_str(), - static_cast(sv_defaultPlayerSpawnAsset).c_str()); + static_cast(sv_defaultPlayerSpawnAsset).c_str(), + server_rhi.GetCStr() + ); processLaunchInfo.m_showWindow = true; processLaunchInfo.m_processPriority = AzFramework::ProcessPriority::PROCESSPRIORITY_NORMAL; @@ -157,6 +222,10 @@ namespace Multiplayer AzFramework::ProcessWatcher* outProcess = AzFramework::ProcessWatcher::LaunchProcess( processLaunchInfo, AzFramework::ProcessCommunicationType::COMMUNICATOR_TYPE_NONE); + AZ_Error( + "MultiplayerEditor", processLaunchInfo.m_launchResult != AzFramework::ProcessLauncher::ProcessLaunchResult::PLR_MissingFile, + "LaunchEditorServer failed! The ServerLauncher binary is missing! (%s) Please build server launcher.", serverPath.c_str()) + return outProcess; } @@ -191,53 +260,49 @@ namespace Multiplayer } const AZ::CVarFixedString remoteAddress = editorsv_serveraddr; - if (editorsv_launch && LocalHost == remoteAddress) + if (editorsv_launch) { + if (LocalHost != remoteAddress) + { + AZ_Warning( + "MultiplayerEditor", false, + "Launching EditorServer skipped because incompatible cvars. editorsv_launch=true, meaning you want to launch an editor-server on this machine, but the editorsv_serveraddr is %s instead of the local address (127.0.0.1). " + "Please either set editorsv_launch=false and keep the remote editor-server, or set editorsv_launch=true and editorsv_serveraddr=127.0.0.1.", + remoteAddress.c_str()) + return; + } + + // Begin listening for MPEditor packets before we launch the editor-server. + // The editor-server will send us (the editor) an "EditorServerReadyForLevelData" packet to let us know it's ready to receive data. + INetworkInterface* editorNetworkInterface = + AZ::Interface::Get()->RetrieveNetworkInterface(AZ::Name(MpEditorInterfaceName)); + AZ_Assert(editorNetworkInterface, "MP Editor Network Interface was unregistered before Editor could connect."); + editorNetworkInterface->Listen(editorsv_port); + + // Launch the editor-server m_serverProcess = LaunchEditorServer(); } - - // Spawnable library needs to be rebuilt since now we have newly registered in-memory spawnable assets - AZ::Interface::Get()->BuildSpawnablesList(); - - // 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) + else { - 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; - } + // Editorsv_launch=false, so we're expecting an editor-server already exists. + // Connect to the editor-server and then send the EditorServerLevelData packet. + 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)); - // 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; - auto& outBuffer = packet.ModifyAssetData(); - - // Size the packet's buffer appropriately - size_t readSize = outBuffer.GetCapacity(); - size_t byteStreamSize = byteStream.GetLength() - byteStream.GetCurPos(); - if (byteStreamSize < readSize) + if (m_editorConnId == AzNetworking::InvalidConnectionId) { - readSize = byteStreamSize; + AZ_Warning( + "MultiplayerEditor", false, + "Editor multiplayer game-mode failed! Could not connect to an editor-server. editorsv_launch is false so we're assuming you're running your own editor-server at editorsv_serveraddr(%s) on editorsv_port(%i). " + "Either set editorsv_launch=true so the editor launches an editor-server for you, or launch your own editor-server by hand before entering game-mode. Remember editor-servers must use editorsv_isDedicated=true.", + remoteAddress.c_str(), + static_cast(editorsv_port)) + return; } - 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); + SendEditorServerLevelDataPacket(editorNetworkInterface->GetConnectionSet().GetConnection(m_editorConnId)); } } } @@ -253,4 +318,75 @@ namespace Multiplayer // but since we're in Editor, we're already in the level. AZ::Interface::Get()->SendReadyForEntityUpdates(true); } + + void MultiplayerEditorSystemComponent::SendEditorServerLevelDataPacket(AzNetworking::IConnection* connection) + { + const auto prefabEditorEntityOwnershipInterface = AZ::Interface::Get(); + if (!prefabEditorEntityOwnershipInterface) + { + AZ_Error("MultiplayerEditor", prefabEditorEntityOwnershipInterface != nullptr, "PrefabEditorEntityOwnershipInterface unavailable") + return; + } + + AZ_Printf("MultiplayerEditor", "Editor is sending the editor-server the level data packet.") + + 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(); + auto 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()); + } + + // Spawnable library needs to be rebuilt since now we have newly registered in-memory spawnable assets + AZ::Interface::Get()->BuildSpawnablesList(); + + // Read the buffer into EditorServerLevelData packets until we've flushed the whole thing + byteStream.Seek(0, AZ::IO::GenericStream::SeekMode::ST_SEEK_BEGIN); + + while (byteStream.GetCurPos() < byteStream.GetLength()) + { + MultiplayerEditorPackets::EditorServerLevelData editorServerLevelDataPacket; + auto& outBuffer = editorServerLevelDataPacket.ModifyAssetData(); + + // Size the packet's buffer appropriately + size_t readSize = outBuffer.GetCapacity(); + const 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()) + { + editorServerLevelDataPacket.SetLastUpdate(true); + } + + connection->SendReliablePacket(editorServerLevelDataPacket); + } + } + + void MultiplayerEditorSystemComponent::EnterGameMode() + { + PyEnterGameMode(); + } + + bool MultiplayerEditorSystemComponent::IsInGameMode() + { + return PyIsInGameMode(); + } } diff --git a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.h b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.h index 4e4c6b677f..77b41a5dc4 100644 --- a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.h +++ b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.h @@ -9,7 +9,8 @@ #pragma once #include - +#include +#include #include #include @@ -29,12 +30,28 @@ namespace AzNetworking namespace Multiplayer { + //! A component to reflect scriptable commands for the Editor + class PythonEditorFuncs : public AZ::Component + { + public: + AZ_COMPONENT(PythonEditorFuncs, "{22AEEA59-94E6-4033-B67D-7C8FBB84DF0D}") + + SANDBOX_API static void Reflect(AZ::ReflectContext* context); + + // AZ::Component ... + void Activate() override {} + void Deactivate() override {} + }; + + //! Multiplayer system component wraps the bridging logic between the game and transport layer. class MultiplayerEditorSystemComponent final : public AZ::Component + , public MultiplayerEditorLayerPythonRequestBus::Handler , private AzFramework::GameEntityContextEventBus::Handler , private AzToolsFramework::EditorEvents::Bus::Handler , private IEditorNotifyListener + , private MultiplayerEditorServerRequestBus::Handler { public: AZ_COMPONENT(MultiplayerEditorSystemComponent, "{9F335CC0-5574-4AD3-A2D8-2FAEF356946C}"); @@ -61,6 +78,12 @@ namespace Multiplayer void NotifyRegisterViews() override; //! @} + //! MultiplayerEditorLayerPythonRequestBus::Handler overrides. + //! @{ + void EnterGameMode() override; + bool IsInGameMode() override; + //! @} + private: //! EditorEvents::Handler overrides //! @{ @@ -73,6 +96,11 @@ namespace Multiplayer void OnGameEntitiesReset() override; //! @} + //! MultiplayerEditorServerRequestBus::Handler + //! @{ + void SendEditorServerLevelDataPacket(AzNetworking::IConnection* connection) 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 1bc3404292..503a8128b0 100644 --- a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp +++ b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp @@ -720,11 +720,6 @@ namespace Multiplayer void MultiplayerSystemComponent::OnConnect(AzNetworking::IConnection* connection) { - MultiplayerAgentDatum datum; - datum.m_id = connection->GetConnectionId(); - datum.m_isInvited = false; - datum.m_agentType = MultiplayerAgentType::Client; - AZStd::string providerTicket; if (connection->GetConnectionRole() == ConnectionRole::Connector) { @@ -738,7 +733,12 @@ namespace Multiplayer } else { - AZLOG_INFO("New incoming connection from remote address: %s", connection->GetRemoteAddress().GetString().c_str()); + AZLOG_INFO("New incoming connection from remote address: %s", connection->GetRemoteAddress().GetString().c_str()) + + MultiplayerAgentDatum datum; + datum.m_id = connection->GetConnectionId(); + datum.m_isInvited = false; + datum.m_agentType = MultiplayerAgentType::Client; m_connectionAcquiredEvent.Signal(datum); } @@ -772,15 +772,14 @@ namespace Multiplayer AZLOG_INFO("%s from remote address %s due to %s", endpointString, connection->GetRemoteAddress().GetString().c_str(), reasonString.c_str()); // The client is disconnecting - if (GetAgentType() == MultiplayerAgentType::Client) + if (m_agentType == MultiplayerAgentType::Client) { AZ_Assert(connection->GetConnectionRole() == ConnectionRole::Connector, "Client connection role should only ever be Connector"); m_clientDisconnectedEvent.Signal(); } - - // Signal to session management that a user has left the server - if (m_agentType == MultiplayerAgentType::DedicatedServer || m_agentType == MultiplayerAgentType::ClientServer) + else if (m_agentType == MultiplayerAgentType::DedicatedServer || m_agentType == MultiplayerAgentType::ClientServer) { + // Signal to session management that a user has left the server if (AZ::Interface::Get() != nullptr && connection->GetConnectionRole() == ConnectionRole::Acceptor) { @@ -1133,7 +1132,10 @@ namespace Multiplayer return m_networkEntityManager.GetNetworkEntityTracker()->Get(node->second); } - PrefabEntityId playerPrefabEntityId(AZ::Name(static_cast(sv_defaultPlayerSpawnAsset).c_str())); + // make sure the player prefab path is lowercase (how it's stored in the cache folder) + auto sv_defaultPlayerSpawnAssetLowerCase = static_cast(sv_defaultPlayerSpawnAsset); + AZStd::to_lower(sv_defaultPlayerSpawnAssetLowerCase.begin(), sv_defaultPlayerSpawnAssetLowerCase.end()); + PrefabEntityId playerPrefabEntityId(AZ::Name(static_cast(sv_defaultPlayerSpawnAssetLowerCase).c_str())); INetworkEntityManager::EntityList entityList = m_networkEntityManager.CreateEntitiesImmediate(playerPrefabEntityId, NetEntityRole::Authority, AZ::Transform::CreateIdentity(), Multiplayer::AutoActivate::DoNotActivate); for (NetworkEntityHandle subEntity : entityList) diff --git a/Gems/Multiplayer/Code/Source/Pipeline/NetworkPrefabProcessor.cpp b/Gems/Multiplayer/Code/Source/Pipeline/NetworkPrefabProcessor.cpp index 27e59c0bf4..bec659180c 100644 --- a/Gems/Multiplayer/Code/Source/Pipeline/NetworkPrefabProcessor.cpp +++ b/Gems/Multiplayer/Code/Source/Pipeline/NetworkPrefabProcessor.cpp @@ -31,8 +31,10 @@ namespace Multiplayer mpTools->SetDidProcessNetworkPrefabs(false); } - context.ListPrefabs([&context](AZStd::string_view prefabName, PrefabDom& prefab) { - ProcessPrefab(context, prefabName, prefab); + AZ::DataStream::StreamType serializationFormat = GetAzSerializationFormat(); + + context.ListPrefabs([&context, serializationFormat](AZStd::string_view prefabName, PrefabDom& prefab) { + ProcessPrefab(context, prefabName, prefab, serializationFormat); }); if (mpTools && !context.GetProcessedObjects().empty()) @@ -45,7 +47,15 @@ namespace Multiplayer { if (auto* serializeContext = azrtti_cast(context); serializeContext != nullptr) { - serializeContext->Class()->Version(2); + serializeContext->Enum() + ->Value("Binary", SerializationFormats::Binary) + ->Value("Text", SerializationFormats::Text) + ; + + serializeContext->Class() + ->Version(3) + ->Field("SerializationFormat", &NetworkPrefabProcessor::m_serializationFormat) + ; } } @@ -93,7 +103,7 @@ namespace Multiplayer }); } - void NetworkPrefabProcessor::ProcessPrefab(PrefabProcessorContext& context, AZStd::string_view prefabName, PrefabDom& prefab) + void NetworkPrefabProcessor::ProcessPrefab(PrefabProcessorContext& context, AZStd::string_view prefabName, PrefabDom& prefab, AZ::DataStream::StreamType serializationFormat) { using namespace AzToolsFramework::Prefab; @@ -107,10 +117,10 @@ namespace Multiplayer AZStd::string uniqueName = prefabName; uniqueName += ".network.spawnable"; - auto serializer = [](AZStd::vector& output, const ProcessedObjectStore& object) -> bool { + auto serializer = [serializationFormat](AZStd::vector& output, const ProcessedObjectStore& object) -> bool { AZ::IO::ByteContainerStream stream(&output); auto& asset = object.GetAsset(); - return AZ::Utils::SaveObjectToStream(stream, AZ::DataStream::ST_BINARY, &asset, asset.GetType()); + return AZ::Utils::SaveObjectToStream(stream, serializationFormat, &asset, asset.GetType()); }; auto&& [object, networkSpawnable] = @@ -178,4 +188,14 @@ namespace Multiplayer context.GetProcessedObjects().push_back(AZStd::move(object)); } + + AZ::DataStream::StreamType NetworkPrefabProcessor::GetAzSerializationFormat() const + { + if (m_serializationFormat == SerializationFormats::Text) + { + return AZ::DataStream::StreamType::ST_JSON; + } + + return AZ::DataStream::StreamType::ST_BINARY; + } } diff --git a/Gems/Multiplayer/Code/Source/Pipeline/NetworkPrefabProcessor.h b/Gems/Multiplayer/Code/Source/Pipeline/NetworkPrefabProcessor.h index 6eb0c2b4de..0fd3529db7 100644 --- a/Gems/Multiplayer/Code/Source/Pipeline/NetworkPrefabProcessor.h +++ b/Gems/Multiplayer/Code/Source/Pipeline/NetworkPrefabProcessor.h @@ -9,6 +9,7 @@ #pragma once #include +#include namespace AzToolsFramework::Prefab::PrefabConversionUtils { @@ -33,7 +34,23 @@ namespace Multiplayer static void Reflect(AZ::ReflectContext* context); + //! The format the network spawnables are going to be stored in. + enum class SerializationFormats + { + Binary, //!< Binary is generally preferable for performance. + Text //!< Store in text format which is usually slower but helps with debugging. + }; + + AZ::DataStream::StreamType GetAzSerializationFormat() const; + protected: - static void ProcessPrefab(PrefabProcessorContext& context, AZStd::string_view prefabName, PrefabDom& prefab); + static void ProcessPrefab(PrefabProcessorContext& context, AZStd::string_view prefabName, PrefabDom& prefab, AZ::DataStream::StreamType serializationFormat); + + SerializationFormats m_serializationFormat = SerializationFormats::Binary; }; } + +namespace AZ +{ + AZ_TYPE_INFO_SPECIALIZE(Multiplayer::NetworkPrefabProcessor::SerializationFormats, "{F69B49EB-9D67-4D9C-99E7-DFA35D4ACCD2}"); +} diff --git a/Gems/Multiplayer/Code/multiplayer_editor_shared_files.cmake b/Gems/Multiplayer/Code/multiplayer_editor_shared_files.cmake index 693485143f..cadd908caa 100644 --- a/Gems/Multiplayer/Code/multiplayer_editor_shared_files.cmake +++ b/Gems/Multiplayer/Code/multiplayer_editor_shared_files.cmake @@ -13,4 +13,5 @@ set(FILES Source/Editor/MultiplayerEditorGem.h Source/Editor/MultiplayerEditorSystemComponent.cpp Source/Editor/MultiplayerEditorSystemComponent.h + Include/Multiplayer/Editor/MultiplayerPythonEditorEventsBus.h ) diff --git a/Gems/Multiplayer/Code/multiplayer_files.cmake b/Gems/Multiplayer/Code/multiplayer_files.cmake index d417304877..7c5f3a946b 100644 --- a/Gems/Multiplayer/Code/multiplayer_files.cmake +++ b/Gems/Multiplayer/Code/multiplayer_files.cmake @@ -13,6 +13,7 @@ set(FILES Include/Multiplayer/MultiplayerConstants.h Include/Multiplayer/MultiplayerStats.h Include/Multiplayer/MultiplayerTypes.h + Include/Multiplayer/MultiplayerEditorServerBus.h Include/Multiplayer/Components/LocalPredictionPlayerInputComponent.h Include/Multiplayer/Components/MultiplayerComponent.h Include/Multiplayer/Components/MultiplayerComponentRegistry.h diff --git a/Gems/Multiplayer/Registry/prefab.tools.setreg b/Gems/Multiplayer/Registry/prefab.tools.setreg index 7f25cf9a43..256f2d189a 100644 --- a/Gems/Multiplayer/Registry/prefab.tools.setreg +++ b/Gems/Multiplayer/Registry/prefab.tools.setreg @@ -18,8 +18,14 @@ "GameObjectCreation": [ { "$type": "AzToolsFramework::Prefab::PrefabConversionUtils::EditorInfoRemover" }, - { "$type": "Multiplayer::NetworkPrefabProcessor" }, - { "$type": "AzToolsFramework::Prefab::PrefabConversionUtils::PrefabCatchmentProcessor" } + { + "$type": "Multiplayer::NetworkPrefabProcessor", + "SerializationFormat": "Binary" // Options are "Binary" (default) or "Text". Prefer "Binary" for performance. + }, + { + "$type": "AzToolsFramework::Prefab::PrefabConversionUtils::PrefabCatchmentProcessor", + "SerializationFormat": "Binary" // Options are "Binary" (default) or "Text". Prefer "Binary" for performance. + } ] } } diff --git a/Gems/PhysX/Code/CMakeLists.txt b/Gems/PhysX/Code/CMakeLists.txt index 1acfae3bfd..bb3d7c08a2 100644 --- a/Gems/PhysX/Code/CMakeLists.txt +++ b/Gems/PhysX/Code/CMakeLists.txt @@ -11,12 +11,13 @@ add_subdirectory(NumericalMethods) ly_get_list_relative_pal_filename(pal_source_dir ${CMAKE_CURRENT_LIST_DIR}/Source/Platform/${PAL_PLATFORM_NAME}) include(${pal_source_dir}/PAL_${PAL_PLATFORM_NAME_LOWERCASE}.cmake) # for PAL_TRAIT_PHYSX_SUPPORTED -set(PHYSX_ENABLE_RUNNING_BENCHMARKS OFF CACHE BOOL "Adds a target to allow running of the physx benchmarks.") +set(LY_PHYSX_ENABLE_RUNNING_BENCHMARKS OFF CACHE BOOL "Adds a target to allow running of the physx benchmarks.") if(PAL_TRAIT_PHYSX_SUPPORTED) set(physx_dependency 3rdParty::PhysX) set(physx_files physx_files.cmake) set(physx_shared_files physx_shared_files.cmake) + set(physx_mock_files physx_mocks_files.cmake) set(physx_editor_files physx_editor_files.cmake) else() set(physx_files physx_unsupported_files.cmake) @@ -151,6 +152,17 @@ endif() # Tests ################################################################################ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED) + ly_add_target( + NAME PhysX.Mocks HEADERONLY + NAMESPACE Gem + OUTPUT_NAME PhysX.Mocks.Gem + FILES_CMAKE + physx_mocks_files.cmake + INCLUDE_DIRECTORIES + INTERFACE + Mocks + ) + ly_add_target( NAME PhysX.Tests ${PAL_TRAIT_TEST_TARGET_TYPE} NAMESPACE Gem @@ -185,7 +197,7 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED) # Only add the physx benchmarks if this flag is set. The benchmark code is still built, as it is part of the PhysX.Tests project. # Currently jenkins has a 1500sec(25min) timeout, our benchmarks can sometimes take over 1500sec and cause a build failure for timeout. # Jenkins currently doesn't upload the results of the benchmarks, so this is ok. - if(PHYSX_ENABLE_RUNNING_BENCHMARKS) + if(LY_PHYSX_ENABLE_RUNNING_BENCHMARKS) ly_add_googlebenchmark( NAME Gem::PhysX.Benchmarks TARGET Gem::PhysX.Tests @@ -213,6 +225,7 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED) AZ::AzTest AZ::AzToolsFrameworkTestCommon Gem::PhysX.Static + Gem::PhysX.Mocks Gem::PhysX.Editor.Static RUNTIME_DEPENDENCIES Gem::LmbrCentral.Editor diff --git a/Gems/PhysX/Code/Mocks/PhysX/MockPhysXHeightfieldProviderComponent.h b/Gems/PhysX/Code/Mocks/PhysX/MockPhysXHeightfieldProviderComponent.h new file mode 100644 index 0000000000..7e500881c0 --- /dev/null +++ b/Gems/PhysX/Code/Mocks/PhysX/MockPhysXHeightfieldProviderComponent.h @@ -0,0 +1,74 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ +#pragma once + +#include +#include +#include +#include +#include + +namespace UnitTest +{ + class MockPhysXHeightfieldProviderComponent + : public AZ::Component + { + public: + AZ_COMPONENT(MockPhysXHeightfieldProviderComponent, "{C5F7CCCF-FDB2-40DF-992D-CF028F4A1B59}"); + + static void Reflect([[maybe_unused]] AZ::ReflectContext* context) + { + if (auto serializeContext = azrtti_cast(context)) + { + serializeContext->Class() + ->Version(1); + } + } + + void Activate() override + { + } + + void Deactivate() override + { + } + + static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided) + { + provided.push_back(AZ_CRC_CE("PhysicsHeightfieldProviderService")); + } + + }; + + class MockPhysXHeightfieldProvider + : protected Physics::HeightfieldProviderRequestsBus::Handler + { + public: + MockPhysXHeightfieldProvider(AZ::EntityId entityId) + { + Physics::HeightfieldProviderRequestsBus::Handler::BusConnect(entityId); + } + + ~MockPhysXHeightfieldProvider() + { + Physics::HeightfieldProviderRequestsBus::Handler::BusDisconnect(); + } + + MOCK_CONST_METHOD0(GetHeightsAndMaterials, AZStd::vector()); + MOCK_CONST_METHOD0(GetHeightfieldGridSpacing, AZ::Vector2()); + MOCK_CONST_METHOD2(GetHeightfieldGridSize, void(int32_t&, int32_t&)); + MOCK_CONST_METHOD2(GetHeightfieldHeightBounds, void(float&, float&)); + MOCK_CONST_METHOD0(GetHeightfieldTransform, AZ::Transform()); + MOCK_CONST_METHOD0(GetMaterialList, AZStd::vector()); + MOCK_CONST_METHOD0(GetHeights, AZStd::vector()); + MOCK_CONST_METHOD1(UpdateHeights, AZStd::vector(const AZ::Aabb& dirtyRegion)); + MOCK_CONST_METHOD1(UpdateHeightsAndMaterials, AZStd::vector(const AZ::Aabb& dirtyRegion)); + MOCK_CONST_METHOD0(GetHeightfieldAabb, AZ::Aabb()); + }; + +} // namespace UnitTest diff --git a/Gems/PhysX/Code/Source/EditorShapeColliderComponent.cpp b/Gems/PhysX/Code/Source/EditorShapeColliderComponent.cpp index 832145d016..59b659a0bf 100644 --- a/Gems/PhysX/Code/Source/EditorShapeColliderComponent.cpp +++ b/Gems/PhysX/Code/Source/EditorShapeColliderComponent.cpp @@ -348,19 +348,7 @@ namespace PhysX LmbrCentral::BoxShapeComponentRequestsBus::EventResult(boxDimensions, GetEntityId(), &LmbrCentral::BoxShapeComponentRequests::GetBoxDimensions); - if (m_shapeType != ShapeType::Box) - { - m_shapeConfigs.clear(); - m_shapeConfigs.emplace_back(AZStd::make_shared(boxDimensions)); - - m_shapeType = ShapeType::Box; - } - else - { - Physics::BoxShapeConfiguration& configuration = - static_cast(*m_shapeConfigs.back()); - configuration = Physics::BoxShapeConfiguration(boxDimensions); - } + SetShapeConfig(ShapeType::Box, Physics::BoxShapeConfiguration(boxDimensions)); m_shapeConfigs.back()->m_scale = scale; m_geometryCache.m_boxDimensions = scale * boxDimensions; @@ -374,19 +362,7 @@ namespace PhysX const Physics::CapsuleShapeConfiguration& capsuleShapeConfig = Utils::ConvertFromLmbrCentralCapsuleConfig(lmbrCentralCapsuleShapeConfig); - if (m_shapeType != ShapeType::Capsule) - { - m_shapeConfigs.clear(); - m_shapeConfigs.emplace_back(AZStd::make_shared(capsuleShapeConfig)); - - m_shapeType = ShapeType::Capsule; - } - else - { - Physics::CapsuleShapeConfiguration& configuration = - static_cast(*m_shapeConfigs.back()); - configuration = capsuleShapeConfig; - } + SetShapeConfig(ShapeType::Capsule, capsuleShapeConfig); m_shapeConfigs.back()->m_scale = scale; const float scalarScale = scale.GetMaxElement(); @@ -400,19 +376,7 @@ namespace PhysX LmbrCentral::SphereShapeComponentRequestsBus::EventResult(radius, GetEntityId(), &LmbrCentral::SphereShapeComponentRequests::GetRadius); - if (m_shapeType != ShapeType::Sphere) - { - m_shapeConfigs.clear(); - m_shapeConfigs.emplace_back(AZStd::make_shared(radius)); - - m_shapeType = ShapeType::Sphere; - } - else - { - Physics::SphereShapeConfiguration& configuration = - static_cast(*m_shapeConfigs.back()); - configuration = Physics::SphereShapeConfiguration(radius); - } + SetShapeConfig(ShapeType::Sphere, Physics::SphereShapeConfiguration(radius)); m_shapeConfigs.back()->m_scale = scale; m_geometryCache.m_radius = scale.GetMaxElement() * radius; @@ -455,19 +419,7 @@ namespace PhysX if (shapeConfig.has_value()) { - if (m_shapeType != ShapeType::Cylinder) - { - m_shapeConfigs.clear(); - m_shapeConfigs.push_back(AZStd::make_shared(shapeConfig.value())); - - m_shapeType = ShapeType::Cylinder; - } - else - { - Physics::CookedMeshShapeConfiguration& configuration = - static_cast(*m_shapeConfigs.back()); - configuration = Physics::CookedMeshShapeConfiguration(shapeConfig.value()); - } + SetShapeConfig(ShapeType::Cylinder, shapeConfig.value()); CreateStaticEditorCollider(); } diff --git a/Gems/PhysX/Code/Source/EditorShapeColliderComponent.h b/Gems/PhysX/Code/Source/EditorShapeColliderComponent.h index 431b34b1ae..3422d4959f 100644 --- a/Gems/PhysX/Code/Source/EditorShapeColliderComponent.h +++ b/Gems/PhysX/Code/Source/EditorShapeColliderComponent.h @@ -8,6 +8,7 @@ #pragma once +#include #include #include #include @@ -90,12 +91,15 @@ namespace PhysX void UpdateBoxConfig(const AZ::Vector3& scale); void UpdateCapsuleConfig(const AZ::Vector3& scale); void UpdateSphereConfig(const AZ::Vector3& scale); + void UpdateCylinderConfig(const AZ::Vector3& scale); void UpdatePolygonPrismDecomposition(); void UpdatePolygonPrismDecomposition(const AZ::PolygonPrismPtr polygonPrismPtr); - void RefreshUiProperties(); + // Helper function to set a specific shape configuration + template + void SetShapeConfig(ShapeType shapeType, const ConfigType& shapeConfig); - void UpdateCylinderConfig(const AZ::Vector3& scale); + void RefreshUiProperties(); AZ::u32 OnSubdivisionCountChange(); AZ::Crc32 SubdivisionCountVisibility(); @@ -154,4 +158,28 @@ namespace PhysX AZ::NonUniformScaleChangedEvent::Handler m_nonUniformScaleChangedHandler; //!< Responds to changes in non-uniform scale. AZ::Vector3 m_currentNonUniformScale = AZ::Vector3::CreateOne(); //!< Caches the current non-uniform scale. }; + + template + void EditorShapeColliderComponent::SetShapeConfig(ShapeType shapeType, const ConfigType& shapeConfig) + { + if (m_shapeType != shapeType) + { + m_shapeConfigs.clear(); + m_shapeType = shapeType; + } + + if (m_shapeConfigs.empty()) + { + m_shapeConfigs.emplace_back(AZStd::make_shared(shapeConfig)); + } + else + { + AZ_Assert(m_shapeConfigs.back()->GetShapeType() == shapeConfig.GetShapeType(), + "Expected Physics shape configuration with shape type %d but found one with shape type %d.", + static_cast(shapeConfig.GetShapeType()), static_cast(m_shapeConfigs.back()->GetShapeType())); + ConfigType& configuration = + static_cast(*m_shapeConfigs.back()); + configuration = shapeConfig; + } + } } // namespace PhysX diff --git a/Gems/PhysX/Code/Source/Scene/PhysXScene.cpp b/Gems/PhysX/Code/Source/Scene/PhysXScene.cpp index 86e3ceb98f..22fc665eec 100644 --- a/Gems/PhysX/Code/Source/Scene/PhysXScene.cpp +++ b/Gems/PhysX/Code/Source/Scene/PhysXScene.cpp @@ -1175,7 +1175,10 @@ namespace PhysX using physx::PxGeometryType; bool isProfilingActive = false; - AZ::Debug::ProfilerRequestBus::BroadcastResult(isProfilingActive, &AZ::Debug::ProfilerRequests::IsActive); + if (auto profilerSystem = AZ::Debug::ProfilerSystemInterface::Get(); profilerSystem) + { + isProfilingActive = profilerSystem->IsActive(); + } if (!isProfilingActive) { diff --git a/Gems/PhysX/Code/Source/Utils.cpp b/Gems/PhysX/Code/Source/Utils.cpp index b4e1e768c3..fecea57d9c 100644 --- a/Gems/PhysX/Code/Source/Utils.cpp +++ b/Gems/PhysX/Code/Source/Utils.cpp @@ -102,7 +102,7 @@ namespace PhysX const float scaleFactor = (maxHeightBounds <= minHeightBounds) ? 1.0f : AZStd::numeric_limits::max() / halfBounds; const float heightScale{ 1.0f / scaleFactor }; - const uint8_t physxMaximumMaterialIndex = 0x7f; + [[maybe_unused]] constexpr uint8_t physxMaximumMaterialIndex = 0x7f; // Delete the cached heightfield object if it is there, and create a new one and save in the shape configuration heightfieldConfig.SetCachedNativeHeightfield(nullptr); diff --git a/Gems/PhysX/Code/Tests/EditorHeightfieldColliderComponentTests.cpp b/Gems/PhysX/Code/Tests/EditorHeightfieldColliderComponentTests.cpp new file mode 100644 index 0000000000..ef112d8623 --- /dev/null +++ b/Gems/PhysX/Code/Tests/EditorHeightfieldColliderComponentTests.cpp @@ -0,0 +1,215 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using ::testing::NiceMock; +using ::testing::Return; + +namespace PhysXEditorTests +{ + AZStd::vector GetSamples() + { + AZStd::vector samples{ { 3.0f, Physics::QuadMeshType::SubdivideUpperLeftToBottomRight }, + { 2.0f, Physics::QuadMeshType::SubdivideUpperLeftToBottomRight }, + { 1.5f, Physics::QuadMeshType::SubdivideUpperLeftToBottomRight }, + { 1.0f, Physics::QuadMeshType::SubdivideUpperLeftToBottomRight }, + { 3.0f, Physics::QuadMeshType::SubdivideUpperLeftToBottomRight }, + { 1.0f, Physics::QuadMeshType::SubdivideUpperLeftToBottomRight }, + { 3.0f, Physics::QuadMeshType::SubdivideUpperLeftToBottomRight }, + { 0.0f, Physics::QuadMeshType::SubdivideUpperLeftToBottomRight }, + { 3.0f, Physics::QuadMeshType::SubdivideUpperLeftToBottomRight } }; + return samples; + } + + EntityPtr SetupHeightfieldComponent() + { + // create an editor entity with a shape collider component and a box shape component + EntityPtr editorEntity = CreateInactiveEditorEntity("HeightfieldColliderComponentEditorEntity"); + editorEntity->CreateComponent(); + editorEntity->CreateComponent(LmbrCentral::EditorAxisAlignedBoxShapeComponentTypeId); + editorEntity->CreateComponent(); + AZ::ComponentApplicationBus::Broadcast( + &AZ::ComponentApplicationRequests::RegisterComponentDescriptor, + UnitTest::MockPhysXHeightfieldProviderComponent::CreateDescriptor()); + return editorEntity; + } + + void CleanupHeightfieldComponent() + { + AZ::ComponentApplicationBus::Broadcast( + &AZ::ComponentApplicationRequests::UnregisterComponentDescriptor, + UnitTest::MockPhysXHeightfieldProviderComponent::CreateDescriptor()); + } + + void SetupMockMethods(NiceMock& mockShapeRequests) + { + ON_CALL(mockShapeRequests, GetHeightfieldTransform).WillByDefault(Return(AZ::Transform::CreateTranslation({ 1, 2, 0 }))); + ON_CALL(mockShapeRequests, GetHeightfieldGridSpacing).WillByDefault(Return(AZ::Vector2(1, 1))); + ON_CALL(mockShapeRequests, GetHeightsAndMaterials).WillByDefault(Return(GetSamples())); + ON_CALL(mockShapeRequests, GetHeightfieldGridSize) + .WillByDefault( + [](int32_t& numColumns, int32_t& numRows) + { + numColumns = 3; + numRows = 3; + }); + ON_CALL(mockShapeRequests, GetHeightfieldHeightBounds) + .WillByDefault( + [](float& x, float& y) + { + x = -3.0f; + y = 3.0f; + }); + } + + EntityPtr TestCreateActiveGameEntityFromEditorEntity(AZ::Entity* editorEntity) + { + EntityPtr gameEntity = AZStd::make_unique(); + AzToolsFramework::ToolsApplicationRequestBus::Broadcast( + &AzToolsFramework::ToolsApplicationRequests::PreExportEntity, *editorEntity, *gameEntity); + gameEntity->Init(); + return gameEntity; + } + + + TEST_F(PhysXEditorFixture, EditorHeightfieldColliderComponentDependenciesSatisfiedEntityIsValid) + { + EntityPtr entity = CreateInactiveEditorEntity("HeightfieldColliderComponentEditorEntity"); + entity->CreateComponent(); + entity->CreateComponent(LmbrCentral::EditorAxisAlignedBoxShapeComponentTypeId); + entity->CreateComponent()->CreateDescriptor(); + + // the entity should be in a valid state because the shape component and + // the Terrain Physics Collider Component requirement is satisfied. + AZ::Entity::DependencySortOutcome sortOutcome = entity->EvaluateDependenciesGetDetails(); + EXPECT_TRUE(sortOutcome.IsSuccess()); + } + + TEST_F(PhysXEditorFixture, EditorHeightfieldColliderComponentDependenciesMissingEntityIsInvalid) + { + EntityPtr entity = CreateInactiveEditorEntity("HeightfieldColliderComponentEditorEntity"); + entity->CreateComponent(); + + // the entity should not be in a valid state because the heightfield collider component requires + // a shape component and the Terrain Physics Collider Component + AZ::Entity::DependencySortOutcome sortOutcome = entity->EvaluateDependenciesGetDetails(); + EXPECT_FALSE(sortOutcome.IsSuccess()); + EXPECT_TRUE(sortOutcome.GetError().m_code == AZ::Entity::DependencySortResult::MissingRequiredService); + } + + TEST_F(PhysXEditorFixture, EditorHeightfieldColliderComponentMultipleHeightfieldColliderComponentsEntityIsInvalid) + { + EntityPtr entity = CreateInactiveEditorEntity("HeightfieldColliderComponentEditorEntity"); + entity->CreateComponent(); + entity->CreateComponent(LmbrCentral::EditorAxisAlignedBoxShapeComponentTypeId); + + // adding a second heightfield collider component should make the entity invalid + entity->CreateComponent(); + + AZ::Entity::DependencySortOutcome sortOutcome = entity->EvaluateDependenciesGetDetails(); + EXPECT_FALSE(sortOutcome.IsSuccess()); + EXPECT_TRUE(sortOutcome.GetError().m_code == AZ::Entity::DependencySortResult::HasIncompatibleServices); + } + + TEST_F(PhysXEditorFixture, EditorHeightfieldColliderComponentHeightfieldColliderWithCorrectComponentsCorrectRuntimeComponents) + { + EntityPtr editorEntity = SetupHeightfieldComponent(); + NiceMock mockShapeRequests(editorEntity->GetId()); + SetupMockMethods(mockShapeRequests); + editorEntity->Activate(); + + EntityPtr gameEntity = TestCreateActiveGameEntityFromEditorEntity(editorEntity.get()); + NiceMock mockShapeRequests2(gameEntity->GetId()); + SetupMockMethods(mockShapeRequests2); + gameEntity->Activate(); + + // check that the runtime entity has the expected components + EXPECT_TRUE(gameEntity->FindComponent() != nullptr); + EXPECT_TRUE(gameEntity->FindComponent() != nullptr); + EXPECT_TRUE(gameEntity->FindComponent(LmbrCentral::AxisAlignedBoxShapeComponentTypeId) != nullptr); + + CleanupHeightfieldComponent(); + } + + TEST_F(PhysXEditorFixture, EditorHeightfieldColliderComponentHeightfieldColliderWithAABoxCorrectRuntimeGeometry) + { + EntityPtr editorEntity = SetupHeightfieldComponent(); + NiceMock mockShapeRequests(editorEntity->GetId()); + SetupMockMethods(mockShapeRequests); + editorEntity->Activate(); + + EntityPtr gameEntity = TestCreateActiveGameEntityFromEditorEntity(editorEntity.get()); + NiceMock mockShapeRequests2(gameEntity->GetId()); + SetupMockMethods(mockShapeRequests2); + gameEntity->Activate(); + + AzPhysics::SimulatedBody* staticBody = nullptr; + AzPhysics::SimulatedBodyComponentRequestsBus::EventResult( + staticBody, gameEntity->GetId(), &AzPhysics::SimulatedBodyComponentRequests::GetSimulatedBody); + const auto* pxRigidStatic = static_cast(staticBody->GetNativePointer()); + + PHYSX_SCENE_READ_LOCK(pxRigidStatic->getScene()); + + // there should be a single shape on the rigid body and it should be a heightfield + EXPECT_EQ(pxRigidStatic->getNbShapes(), 1); + + physx::PxShape* shape = nullptr; + pxRigidStatic->getShapes(&shape, 1, 0); + EXPECT_EQ(shape->getGeometryType(), physx::PxGeometryType::eHEIGHTFIELD); + + physx::PxHeightFieldGeometry heightfieldGeometry; + shape->getHeightFieldGeometry(heightfieldGeometry); + + physx::PxHeightField* heightfield = heightfieldGeometry.heightField; + + int32_t numRows{ 0 }; + int32_t numColumns{ 0 }; + Physics::HeightfieldProviderRequestsBus::Event( + gameEntity->GetId(), &Physics::HeightfieldProviderRequestsBus::Events::GetHeightfieldGridSize, numColumns, numRows); + EXPECT_EQ(numColumns, heightfield->getNbColumns()); + EXPECT_EQ(numRows, heightfield->getNbRows()); + + for (int sampleRow = 0; sampleRow < numRows; ++sampleRow) + { + for (int sampleColumn = 0; sampleColumn < numColumns; ++sampleColumn) + { + float minHeightBounds{ 0.0f }; + float maxHeightBounds{ 0.0f }; + Physics::HeightfieldProviderRequestsBus::Event( + gameEntity->GetId(), &Physics::HeightfieldProviderRequestsBus::Events::GetHeightfieldHeightBounds, minHeightBounds, + maxHeightBounds); + + AZStd::vector samples; + Physics::HeightfieldProviderRequestsBus::EventResult( + samples, gameEntity->GetId(), &Physics::HeightfieldProviderRequestsBus::Events::GetHeightsAndMaterials); + const float halfBounds{ (maxHeightBounds - minHeightBounds) / 2.0f }; + const float scaleFactor = (maxHeightBounds <= minHeightBounds) ? 1.0f : AZStd::numeric_limits::max() / halfBounds; + + physx::PxHeightFieldSample samplePhysX = heightfield->getSample(sampleRow, sampleColumn); + Physics::HeightMaterialPoint samplePhysics = samples[sampleRow * numColumns + sampleColumn]; + EXPECT_EQ(samplePhysX.height, azlossy_cast(samplePhysics.m_height * scaleFactor)); + } + } + CleanupHeightfieldComponent(); + } + +} // namespace PhysXEditorTests + diff --git a/Gems/PhysX/Code/Tests/ShapeColliderComponentTests.cpp b/Gems/PhysX/Code/Tests/ShapeColliderComponentTests.cpp index fce427d9d3..06ba4f3e9c 100644 --- a/Gems/PhysX/Code/Tests/ShapeColliderComponentTests.cpp +++ b/Gems/PhysX/Code/Tests/ShapeColliderComponentTests.cpp @@ -320,7 +320,7 @@ namespace PhysXEditorTests TEST_F(PhysXEditorFixture, EditorShapeColliderComponent_ShapeColliderWithCylinderWithNullHeight_HandledGracefully) { - ValidateInvalidEditorShapeColliderComponentParams(0.f, 1.f); + ValidateInvalidEditorShapeColliderComponentParams(1.f, 0.f); } TEST_F(PhysXEditorFixture, EditorShapeColliderComponent_ShapeColliderWithCylinderWithNullRadiusAndNullHeight_HandledGracefully) @@ -338,6 +338,44 @@ namespace PhysXEditorTests ValidateInvalidEditorShapeColliderComponentParams(0.f, -1.f); } + TEST_F(PhysXEditorFixture, EditorShapeColliderComponent_ShapeColliderWithCylinderSwitchingFromNullHeightToValidHeight_HandledGracefully) + { + // create an editor entity with a shape collider component and a cylinder shape component + EntityPtr editorEntity = CreateInactiveEditorEntity("ShapeColliderComponentEditorEntity"); + editorEntity->CreateComponent(); + editorEntity->CreateComponent(LmbrCentral::EditorCylinderShapeComponentTypeId); + editorEntity->Activate(); + + const float validRadius = 1.0f; + const float nullHeight = 0.0f; + const float validHeight = 1.0f; + + LmbrCentral::CylinderShapeComponentRequestsBus::Event(editorEntity->GetId(), + &LmbrCentral::CylinderShapeComponentRequests::SetRadius, validRadius); + + { + UnitTest::ErrorHandler dimensionWarningHandler("Negative or zero cylinder dimensions are invalid"); + UnitTest::ErrorHandler colliderWarningHandler("No Collider or Shape information found when creating Rigid body"); + + LmbrCentral::CylinderShapeComponentRequestsBus::Event(editorEntity->GetId(), + &LmbrCentral::CylinderShapeComponentRequests::SetHeight, nullHeight); + + EXPECT_EQ(dimensionWarningHandler.GetExpectedWarningCount(), 1); + EXPECT_EQ(colliderWarningHandler.GetExpectedWarningCount(), 1); + } + + { + UnitTest::ErrorHandler dimensionWarningHandler("Negative or zero cylinder dimensions are invalid"); + UnitTest::ErrorHandler colliderWarningHandler("No Collider or Shape information found when creating Rigid body"); + + LmbrCentral::CylinderShapeComponentRequestsBus::Event(editorEntity->GetId(), + &LmbrCentral::CylinderShapeComponentRequests::SetHeight, validHeight); + + EXPECT_EQ(dimensionWarningHandler.GetExpectedWarningCount(), 0); + EXPECT_EQ(colliderWarningHandler.GetExpectedWarningCount(), 0); + } + } + TEST_F(PhysXEditorFixture, EditorShapeColliderComponent_ShapeColliderWithBoxAndRigidBody_CorrectRuntimeComponents) { // create an editor entity with a shape collider component and a box shape component diff --git a/Gems/PhysX/Code/physx_editor_tests_files.cmake b/Gems/PhysX/Code/physx_editor_tests_files.cmake index 36fb139514..953e2a167e 100644 --- a/Gems/PhysX/Code/physx_editor_tests_files.cmake +++ b/Gems/PhysX/Code/physx_editor_tests_files.cmake @@ -18,6 +18,7 @@ set(FILES Tests/PolygonPrismMeshUtilsTest.cpp Tests/PhysXColliderComponentModeTests.cpp Tests/ShapeColliderComponentTests.cpp + Tests/EditorHeightfieldColliderComponentTests.cpp Tests/TestColliderComponent.h Tests/SystemComponentTest.cpp Tests/RigidBodyComponentTests.cpp diff --git a/Gems/PhysX/Code/physx_mocks_files.cmake b/Gems/PhysX/Code/physx_mocks_files.cmake new file mode 100644 index 0000000000..49843eeb6f --- /dev/null +++ b/Gems/PhysX/Code/physx_mocks_files.cmake @@ -0,0 +1,11 @@ +# +# Copyright (c) Contributors to the Open 3D Engine Project. +# For complete copyright and license terms please see the LICENSE at the root of this distribution. +# +# SPDX-License-Identifier: Apache-2.0 OR MIT +# +# + +set(FILES + Mocks/PhysX/MockPhysXHeightfieldProviderComponent.h +) diff --git a/Gems/Prefab/PrefabBuilder/PrefabGroup/PrefabGroupBehavior.cpp b/Gems/Prefab/PrefabBuilder/PrefabGroup/PrefabGroupBehavior.cpp index 3bc8e9bff9..af2a54f7a5 100644 --- a/Gems/Prefab/PrefabBuilder/PrefabGroup/PrefabGroupBehavior.cpp +++ b/Gems/Prefab/PrefabBuilder/PrefabGroup/PrefabGroupBehavior.cpp @@ -81,7 +81,7 @@ namespace AZ::SceneAPI::Behaviors m_exportEventHandler.reset(); } - AZStd::unique_ptr PrefabGroupBehavior::CreateProductAssetData(const SceneData::PrefabGroup* prefabGroup) const + AZStd::unique_ptr PrefabGroupBehavior::CreateProductAssetData(const SceneData::PrefabGroup* prefabGroup, const AZ::IO::Path& relativePath) const { using namespace AzToolsFramework::Prefab; @@ -109,8 +109,12 @@ namespace AZ::SceneAPI::Behaviors return {}; } - // validate the PrefabDom will make a valid Prefab template instance - auto templateId = prefabLoaderInterface->LoadTemplateFromString(sb.GetString(), prefabGroup->GetName().c_str()); + // The originPath we pass to LoadTemplateFromString must be the relative path of the file + AZ::IO::Path templateName(prefabGroup->GetName()); + templateName.ReplaceExtension(AZ::Prefab::PrefabGroupAssetHandler::s_Extension); + templateName = relativePath / templateName; + + auto templateId = prefabLoaderInterface->LoadTemplateFromString(sb.GetString(), templateName.Native().c_str()); if (templateId == InvalidTemplateId) { AZ_Error("prefab", false, "PrefabGroup(%s) Could not write load template", prefabGroup->GetName().c_str()); @@ -136,22 +140,8 @@ namespace AZ::SceneAPI::Behaviors const SceneData::PrefabGroup* prefabGroup, const rapidjson::Document& doc) const { - // Retrieve source asset info so we can get a string with the relative path to the asset - bool assetInfoResult; - Data::AssetInfo info; - AZStd::string watchFolder; - AzToolsFramework::AssetSystemRequestBus::BroadcastResult( - assetInfoResult, - &AzToolsFramework::AssetSystemRequestBus::Events::GetSourceInfoBySourcePath, - context.GetScene().GetSourceFilename().c_str(), - info, - watchFolder); - - AZ::IO::FixedMaxPath assetPath(info.m_relativePath); - assetPath.ReplaceFilename(prefabGroup->GetName().c_str()); - AZStd::string filePath = AZ::SceneAPI::Utilities::FileUtilities::CreateOutputFileName( - assetPath.c_str(), + prefabGroup->GetName().c_str(), context.GetOutputDirectory(), AZ::Prefab::PrefabGroupAssetHandler::s_Extension); @@ -174,7 +164,7 @@ namespace AZ::SceneAPI::Behaviors const auto bytesWritten = fileStream.Write(sb.GetSize(), sb.GetString()); if (bytesWritten > 1) { - AZ::u32 subId = AZ::Crc32(assetPath.c_str()); + AZ::u32 subId = AZ::Crc32(prefabGroup->GetName().c_str()); context.GetProductList().AddProduct( filePath, context.GetScene().GetSourceGuid(), @@ -206,9 +196,14 @@ namespace AZ::SceneAPI::Behaviors return AZ::SceneAPI::Events::ProcessingResult::Ignored; } + // Get the relative path of the source and then take just the path portion of it (no file name) + AZ::IO::Path relativePath = context.GetScene().GetSourceFilename(); + relativePath = relativePath.LexicallyRelative(AZStd::string_view(context.GetScene().GetWatchFolder())); + relativePath = relativePath.ParentPath(); + for (const auto* prefabGroup : prefabGroupCollection) { - auto result = CreateProductAssetData(prefabGroup); + auto result = CreateProductAssetData(prefabGroup, relativePath); if (!result) { return Events::ProcessingResult::Failure; diff --git a/Gems/Prefab/PrefabBuilder/PrefabGroup/PrefabGroupBehavior.h b/Gems/Prefab/PrefabBuilder/PrefabGroup/PrefabGroupBehavior.h index b57d30695a..5e787c316b 100644 --- a/Gems/Prefab/PrefabBuilder/PrefabGroup/PrefabGroupBehavior.h +++ b/Gems/Prefab/PrefabBuilder/PrefabGroup/PrefabGroupBehavior.h @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -42,7 +43,7 @@ namespace AZ::SceneAPI::Behaviors private: Events::ProcessingResult OnPrepareForExport(Events::PreExportEventContext& context) const; - AZStd::unique_ptr CreateProductAssetData(const SceneData::PrefabGroup* prefabGroup) const; + AZStd::unique_ptr CreateProductAssetData(const SceneData::PrefabGroup* prefabGroup, const AZ::IO::Path& relativePath) const; bool WriteOutProductAsset( Events::PreExportEventContext& context, diff --git a/Gems/Profiler/Code/Include/Profiler/ProfilerBus.h b/Gems/Profiler/Code/Include/Profiler/ProfilerBus.h deleted file mode 100644 index 22352185d3..0000000000 --- a/Gems/Profiler/Code/Include/Profiler/ProfilerBus.h +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (c) Contributors to the Open 3D Engine Project. - * For complete copyright and license terms please see the LICENSE at the root of this distribution. - * - * SPDX-License-Identifier: Apache-2.0 OR MIT - * - */ -#pragma once - -#include -#include -#include - -namespace Profiler -{ - class ProfilerRequests - { - public: - AZ_RTTI(ProfilerRequests, "{3757c4e5-1941-457c-85ae-16305e17a4c6}"); - virtual ~ProfilerRequests() = default; - - //! Enable/Disable the CpuProfiler - virtual void SetProfilerEnabled(bool enabled) = 0; - - //! Dump a single frame of Cpu profiling data - virtual bool CaptureCpuProfilingStatistics(const AZStd::string& outputFilePath) = 0; - - //! Start a multiframe capture of CPU profiling data. - virtual bool BeginContinuousCpuProfilingCapture() = 0; - - //! End and dump an in-progress continuous capture. - virtual bool EndContinuousCpuProfilingCapture(const AZStd::string& outputFilePath) = 0; - }; - - class ProfilerBusTraits - : public AZ::EBusTraits - { - public: - // EBusTraits overrides - static constexpr AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single; - static constexpr AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single; - }; - - class ProfilerNotifications - : public AZ::EBusTraits - { - public: - virtual ~ProfilerNotifications() = default; - - //! Notify when the current CpuProfilingStatistics capture is finished - //! @param result Set to true if it's finished successfully - //! @param info The output file path or error information which depends on the return. - virtual void OnCaptureCpuProfilingStatisticsFinished(bool result, const AZStd::string& info) = 0; - }; - - using ProfilerInterface = AZ::Interface; - using ProfilerRequestBus = AZ::EBus; - using ProfilerNotificationBus = AZ::EBus; -} // namespace Profiler diff --git a/Gems/Profiler/Code/Source/ImGuiCpuProfiler.cpp b/Gems/Profiler/Code/Source/ImGuiCpuProfiler.cpp index 3f364f99e0..26c2f9f974 100644 --- a/Gems/Profiler/Code/Source/ImGuiCpuProfiler.cpp +++ b/Gems/Profiler/Code/Source/ImGuiCpuProfiler.cpp @@ -10,9 +10,9 @@ #include -#include #include +#include #include #include #include @@ -26,8 +26,6 @@ namespace Profiler { - static constexpr const char* defaultSaveLocation = "@user@/Profiler"; - namespace CpuProfilerImGuiHelper { float TicksToMs(double ticks) @@ -156,16 +154,7 @@ namespace Profiler if (m_captureToFile) { - AZStd::string timeString; - AZStd::to_string(timeString, AZStd::GetTimeNowSecond()); - - const AZStd::string frameDataFilePath = AZStd::string::format("%s/cpu_single_%s.json", defaultSaveLocation, timeString.c_str()); - - char resolvedPath[AZ::IO::MaxPathLength]; - AZ::IO::FileIOBase::GetInstance()->ResolvePath(frameDataFilePath.c_str(), resolvedPath, AZ::IO::MaxPathLength); - m_lastCapturedFilePath = resolvedPath; - - ProfilerRequestBus::Broadcast(&ProfilerRequestBus::Events::CaptureCpuProfilingStatistics, frameDataFilePath); + AZ::Debug::ProfilerSystemInterface::Get()->CaptureFrame(GenerateOutputFile("single")); } m_captureToFile = false; @@ -206,24 +195,15 @@ namespace Profiler bool isInProgress = CpuProfiler::Get()->IsContinuousCaptureInProgress(); if (ImGui::Button(isInProgress ? "End" : "Begin")) { + auto profilerSystem = AZ::Debug::ProfilerSystemInterface::Get(); if (isInProgress) { - AZStd::string timeString; - AZStd::to_string(timeString, AZStd::GetTimeNowSecond()); - - const AZStd::string frameDataFilePath = AZStd::string::format("%s/cpu_multi_%s.json", defaultSaveLocation, timeString.c_str()); - - char resolvedPath[AZ::IO::MaxPathLength]; - AZ::IO::FileIOBase::GetInstance()->ResolvePath(frameDataFilePath.c_str(), resolvedPath, AZ::IO::MaxPathLength); - m_lastCapturedFilePath = resolvedPath; - - ProfilerRequestBus::Broadcast(&ProfilerRequestBus::Events::EndContinuousCpuProfilingCapture, frameDataFilePath); - + profilerSystem->EndCapture(); m_paused = true; } else { - ProfilerRequestBus::Broadcast(&ProfilerRequestBus::Events::BeginContinuousCpuProfilingCapture); + profilerSystem->StartCapture(GenerateOutputFile("multi")); } } @@ -235,8 +215,10 @@ namespace Profiler // Only update the cached file list when opened so that we aren't making IO calls on every frame. m_cachedCapturePaths.clear(); + AZ::IO::FixedMaxPathString captureOutput = AZ::Debug::GetProfilerCaptureLocation(); + auto* base = AZ::IO::FileIOBase::GetInstance(); - base->FindFiles(defaultSaveLocation, "*.json", + base->FindFiles(captureOutput.c_str(), "*.json", [&paths = m_cachedCapturePaths](const char* path) -> bool { auto foundPath = AZ::IO::Path(path); @@ -418,6 +400,18 @@ namespace Profiler ImGui::End(); } + AZStd::string ImGuiCpuProfiler::GenerateOutputFile(const char* nameHint) + { + AZ::IO::FixedMaxPathString captureOutput = AZ::Debug::GetProfilerCaptureLocation(); + + const AZ::IO::FixedMaxPathString frameDataFilePath = + AZ::IO::FixedMaxPathString::format("%s/cpu_%s_%lld.json", captureOutput.c_str(), nameHint, AZStd::GetTimeNowSecond()); + + AZ::IO::FileIOBase::GetInstance()->ResolvePath(m_lastCapturedFilePath, frameDataFilePath.c_str()); + + return m_lastCapturedFilePath.String(); + } + void ImGuiCpuProfiler::LoadFile() { const AZ::IO::Path& pathToLoad = m_cachedCapturePaths[m_currentFileIndex]; diff --git a/Gems/Profiler/Code/Source/ImGuiCpuProfiler.h b/Gems/Profiler/Code/Source/ImGuiCpuProfiler.h index 2c6a3e470a..2e01b8fd6b 100644 --- a/Gems/Profiler/Code/Source/ImGuiCpuProfiler.h +++ b/Gems/Profiler/Code/Source/ImGuiCpuProfiler.h @@ -107,6 +107,9 @@ namespace Profiler //! Draws the statistical view of the CPU profiling data. void DrawStatisticsView(); + //! Generates the full output timestamped file path based on nameHint + AZStd::string GenerateOutputFile(const char* nameHint); + //! Callback invoked when the "Load File" button is pressed in the file picker. void LoadFile(); @@ -214,7 +217,7 @@ namespace Profiler AZStd::vector m_cpuTimingStatisticsWhenPause; AZStd::sys_time_t m_frameToFrameTime{}; - AZStd::string m_lastCapturedFilePath; + AZ::IO::FixedMaxPath m_lastCapturedFilePath; bool m_showFilePicker = false; diff --git a/Gems/Profiler/Code/Source/ProfilerSystemComponent.cpp b/Gems/Profiler/Code/Source/ProfilerSystemComponent.cpp index bc51ffd0a7..24da9e050e 100644 --- a/Gems/Profiler/Code/Source/ProfilerSystemComponent.cpp +++ b/Gems/Profiler/Code/Source/ProfilerSystemComponent.cpp @@ -51,32 +51,6 @@ namespace Profiler int m_framesLeft{ 0 }; }; - class ProfilerNotificationBusHandler final - : public ProfilerNotificationBus::Handler - , public AZ::BehaviorEBusHandler - { - public: - AZ_EBUS_BEHAVIOR_BINDER(ProfilerNotificationBusHandler, "{44161459-B816-4876-95A4-BA16DEC767D6}", AZ::SystemAllocator, - OnCaptureCpuProfilingStatisticsFinished - ); - - void OnCaptureCpuProfilingStatisticsFinished(bool result, const AZStd::string& info) override - { - Call(FN_OnCaptureCpuProfilingStatisticsFinished, result, info); - } - - static void Reflect(AZ::ReflectContext* context) - { - if (AZ::BehaviorContext* behaviorContext = azrtti_cast(context)) - { - behaviorContext->EBus("ProfilerNotificationBus") - ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation) - ->Attribute(AZ::Script::Attributes::Module, "profiler") - ->Handler(); - } - } - }; - bool SerializeCpuProfilingData(const AZStd::ring_buffer& data, AZStd::string outputFilePath, bool wasEnabled) { AZ_TracePrintf("ProfilerSystemComponent", "Beginning serialization of %zu frames of profiling data\n", data.size()); @@ -107,8 +81,8 @@ namespace Profiler CpuProfiler::Get()->SetProfilerEnabled(false); } - // Notify listeners that the pass' PipelineStatistics queries capture has finished. - ProfilerNotificationBus::Broadcast(&ProfilerNotificationBus::Events::OnCaptureCpuProfilingStatisticsFinished, + // Notify listeners that the profiler capture has finished. + AZ::Debug::ProfilerNotificationBus::Broadcast(&AZ::Debug::ProfilerNotificationBus::Events::OnCaptureFinished, saveResult.IsSuccess(), captureInfo); @@ -128,21 +102,9 @@ namespace Profiler ->ClassElement(AZ::Edit::ClassElements::EditorData, "") ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC_CE("System")) ->Attribute(AZ::Edit::Attributes::AutoExpand, true); - - ProfilerNotificationBusHandler::Reflect(context); } } - if (AZ::BehaviorContext* behaviorContext = azrtti_cast(context)) - { - behaviorContext->EBus("ProfilerRequestBus") - ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation) - ->Attribute(AZ::Script::Attributes::Module, "profiler") - ->Event("CaptureCpuProfilingStatistics", &ProfilerRequestBus::Events::CaptureCpuProfilingStatistics); - - ProfilerNotificationBusHandler::Reflect(context); - } - CpuProfilingStatisticsSerializer::Reflect(context); } @@ -166,24 +128,22 @@ namespace Profiler ProfilerSystemComponent::ProfilerSystemComponent() { - if (ProfilerInterface::Get() == nullptr) + if (AZ::Debug::ProfilerSystemInterface::Get() == nullptr) { - ProfilerInterface::Register(this); + AZ::Debug::ProfilerSystemInterface::Register(this); } } ProfilerSystemComponent::~ProfilerSystemComponent() { - if (ProfilerInterface::Get() == this) + if (AZ::Debug::ProfilerSystemInterface::Get() == this) { - ProfilerInterface::Unregister(this); + AZ::Debug::ProfilerSystemInterface::Unregister(this); } } void ProfilerSystemComponent::Activate() { - ProfilerRequestBus::Handler::BusConnect(); - m_cpuProfiler.Init(); } @@ -191,8 +151,6 @@ namespace Profiler { m_cpuProfiler.Shutdown(); - ProfilerRequestBus::Handler::BusDisconnect(); - // Block deactivation until the IO thread has finished serializing the CPU data if (m_cpuDataSerializationThread.joinable()) { @@ -200,12 +158,17 @@ namespace Profiler } } - void ProfilerSystemComponent::SetProfilerEnabled(bool enabled) + bool ProfilerSystemComponent::IsActive() const + { + return m_cpuProfiler.IsProfilerEnabled(); + } + + void ProfilerSystemComponent::SetActive(bool enabled) { m_cpuProfiler.SetProfilerEnabled(enabled); } - bool ProfilerSystemComponent::CaptureCpuProfilingStatistics(const AZStd::string& outputFilePath) + bool ProfilerSystemComponent::CaptureFrame(const AZStd::string& outputFilePath) { bool expected = false; if (!m_cpuCaptureInProgress.compare_exchange_strong(expected, true)) @@ -236,12 +199,13 @@ namespace Profiler return true; } - bool ProfilerSystemComponent::BeginContinuousCpuProfilingCapture() + bool ProfilerSystemComponent::StartCapture(AZStd::string outputFilePath) { + m_captureFile = AZStd::move(outputFilePath); return m_cpuProfiler.BeginContinuousCapture(); } - bool ProfilerSystemComponent::EndContinuousCpuProfilingCapture(const AZStd::string& outputFilePath) + bool ProfilerSystemComponent::EndCapture() { bool expected = false; if (!m_cpuDataSerializationInProgress.compare_exchange_strong(expected, true)) @@ -263,7 +227,7 @@ namespace Profiler // cpuProfilingData could be 1GB+ once saved, so use an IO thread to write it to disk. auto threadIoFunction = - [data = AZStd::move(captureResult), filePath = AZStd::string(outputFilePath), &flag = m_cpuDataSerializationInProgress]() + [data = AZStd::move(captureResult), filePath = m_captureFile, &flag = m_cpuDataSerializationInProgress]() { SerializeCpuProfilingData(data, filePath, true); flag.store(false); diff --git a/Gems/Profiler/Code/Source/ProfilerSystemComponent.h b/Gems/Profiler/Code/Source/ProfilerSystemComponent.h index 76121be04f..a1c8c47479 100644 --- a/Gems/Profiler/Code/Source/ProfilerSystemComponent.h +++ b/Gems/Profiler/Code/Source/ProfilerSystemComponent.h @@ -8,17 +8,17 @@ #pragma once -#include #include #include +#include #include namespace Profiler { class ProfilerSystemComponent : public AZ::Component - , protected ProfilerRequestBus::Handler + , protected AZ::Debug::ProfilerRequests { public: AZ_COMPONENT(ProfilerSystemComponent, "{3f52c1d7-d920-4781-8ed7-88077ec4f305}"); @@ -38,11 +38,12 @@ namespace Profiler void Activate() override; void Deactivate() override; - // ProfilerRequestBus interface implementation - void SetProfilerEnabled(bool enabled) override; - bool CaptureCpuProfilingStatistics(const AZStd::string& outputFilePath) override; - bool BeginContinuousCpuProfilingCapture() override; - bool EndContinuousCpuProfilingCapture(const AZStd::string& outputFilePath) override; + // ProfilerRequests interface implementation + bool IsActive() const override; + void SetActive(bool active) override; + bool CaptureFrame(const AZStd::string& outputFilePath) override; + bool StartCapture(AZStd::string outputFilePath) override; + bool EndCapture() override; AZStd::thread m_cpuDataSerializationThread; @@ -51,6 +52,7 @@ namespace Profiler AZStd::atomic_bool m_cpuCaptureInProgress{ false }; CpuProfilerImpl m_cpuProfiler; + AZStd::string m_captureFile; }; } // namespace Profiler diff --git a/Gems/Profiler/Code/profiler_files.cmake b/Gems/Profiler/Code/profiler_files.cmake index 51ceb00139..ebbca1cd78 100644 --- a/Gems/Profiler/Code/profiler_files.cmake +++ b/Gems/Profiler/Code/profiler_files.cmake @@ -7,7 +7,6 @@ # set(FILES - Include/Profiler/ProfilerBus.h Include/Profiler/ProfilerImGuiBus.h Source/CpuProfiler.h Source/CpuProfilerImpl.cpp diff --git a/Gems/QtForPython/Editor/Scripts/az_qt_helpers.py b/Gems/QtForPython/Editor/Scripts/az_qt_helpers.py index 758cc539d7..96f55f14b0 100755 --- a/Gems/QtForPython/Editor/Scripts/az_qt_helpers.py +++ b/Gems/QtForPython/Editor/Scripts/az_qt_helpers.py @@ -27,7 +27,7 @@ def get_editor_main_window(): return editor_main_window # Helper method for registering a Python widget as a tool/view pane with the Editor -def register_view_pane(name, widget_type, options=editor.ViewPaneOptions()): +def register_view_pane(name, widget_type, category="Tools", options=editor.ViewPaneOptions()): global view_pane_handlers # The view pane names are unique in the Editor, so make sure one with the same name doesn't exist already @@ -45,10 +45,10 @@ def register_view_pane(name, widget_type, options=editor.ViewPaneOptions()): return new_widget.winId() - def on_notify_register_views(parameters, my_name=name, my_options=options): + def on_notify_register_views(parameters, my_name=name, my_category=category, my_options=options): # Register our widget as an Editor view pane print('Calling on_notify_register_views RegisterCustomViewPane') - editor.EditorRequestBus(azlmbr.bus.Broadcast, 'RegisterCustomViewPane', my_name, 'Tools', my_options) + editor.EditorRequestBus(azlmbr.bus.Broadcast, 'RegisterCustomViewPane', my_name, my_category, my_options) # We keep a handler around in case a request for registering custom view panes comes later print('Initializing callback for RegisterCustomViewPane') @@ -57,7 +57,7 @@ def register_view_pane(name, widget_type, options=editor.ViewPaneOptions()): registration_handler.add_callback("NotifyRegisterViews", on_notify_register_views) global registration_handlers registration_handlers[name] = registration_handler - editor.EditorRequestBus(azlmbr.bus.Broadcast, 'RegisterCustomViewPane', name, 'Tools', options) + editor.EditorRequestBus(azlmbr.bus.Broadcast, 'RegisterCustomViewPane', name, category, options) # Connect to the ViewPaneCallbackBus in order to respond to requests to create our widget # We also need to store our handler so it will exist for the life of the Editor diff --git a/Gems/Terrain/Assets/Materials/Terrain/DefaultTerrainMacroMaterial.material b/Gems/Terrain/Assets/Materials/Terrain/DefaultTerrainMacroMaterial.material deleted file mode 100644 index afaf7e1947..0000000000 --- a/Gems/Terrain/Assets/Materials/Terrain/DefaultTerrainMacroMaterial.material +++ /dev/null @@ -1,8 +0,0 @@ -{ - "description": "", - "materialType": "TerrainMacroMaterial.materialtype", - "parentMaterial": "", - "propertyLayoutVersion": 1, - "properties": { - } -} diff --git a/Gems/Terrain/Assets/Materials/Terrain/PbrTerrain.materialtype b/Gems/Terrain/Assets/Materials/Terrain/PbrTerrain.materialtype index 01b862c4f8..235079d95e 100644 --- a/Gems/Terrain/Assets/Materials/Terrain/PbrTerrain.materialtype +++ b/Gems/Terrain/Assets/Materials/Terrain/PbrTerrain.materialtype @@ -92,13 +92,58 @@ { "id": "heightmapImage", "displayName": "Heightmap Image", - "description": "Heightmap of the terrain, controlled by the runtime.", + "description": "Heightmap of the terrain. Controlled by the runtime.", + "visibility": "Hidden", "type": "Image", "connection": { "type": "ShaderInput", "id": "m_heightmapImage" } }, + { + "id": "detailMaterialIdImage", + "displayName": "Detail Material Id Image", + "description": "Texture containing detail material Ids and weights. Controlled by the runtime.", + "visibility": "Hidden", + "type": "Image", + "connection": { + "type": "ShaderInput", + "id": "m_detailMaterialIdImage" + } + }, + { + "id": "detailMaterialIdCenter", + "displayName": "Detail Material Id Image Center", + "description": "The center position of the detail material Id image. Controlled by the runtime.", + "visibility": "Hidden", + "type": "Vector2", + "connection": { + "type": "ShaderInput", + "id": "m_detailMaterialIdImageCenter" + } + }, + { + "id": "detailAabb", + "displayName": "Detail material bounds in 2d", + "description": "The 2d world space bounds of the detail id material. Controlled by the runtime.", + "visibility": "Hidden", + "type": "Vector4", + "connection": { + "type": "ShaderInput", + "id": "m_detailAabb" + } + }, + { + "id": "detailHalfPixelUv", + "displayName": "Detail texture half pixel uv size", + "description": "Uv size of a half pixel in the detail material id texture. Controlled by the runtime.", + "visibility": "Hidden", + "type": "float", + "connection": { + "type": "ShaderInput", + "id": "m_detailHalfPixelUv" + } + }, { "id": "detailTextureMultiplier", "displayName": "Detail Texture UV Multiplier", diff --git a/Gems/Terrain/Assets/Materials/Terrain/TerrainMacroMaterial.materialtype b/Gems/Terrain/Assets/Materials/Terrain/TerrainMacroMaterial.materialtype deleted file mode 100644 index 17769ffb92..0000000000 --- a/Gems/Terrain/Assets/Materials/Terrain/TerrainMacroMaterial.materialtype +++ /dev/null @@ -1,63 +0,0 @@ -{ - "description": "A material for providing terrain with low-fidelity color and normals. This material will get blended with surface detail materials.", - "version": 1, - "propertyLayout": { - "groups": [ - { - "name": "baseColor", - "displayName": "Base Color", - "description": "Properties for configuring the surface reflected color for dielectrics or reflectance values for metals." - }, - { - "name": "normal", - "displayName": "Normal", - "description": "Properties related to configuring surface normal." - } - ], - "properties": { - "baseColor": [ - { - "name": "textureMap", - "displayName": "Texture", - "description": "Base color of the macro material", - "type": "Image" - } - ], - "normal": [ - { - "name": "textureMap", - "displayName": "Texture", - "description": "Texture for defining surface normal direction. These will override normals generated from the geometry.", - "type": "Image" - }, - { - "name": "flipX", - "displayName": "Flip X Channel", - "description": "Flip tangent direction for this normal map.", - "type": "Bool", - "defaultValue": false - }, - { - "name": "flipY", - "displayName": "Flip Y Channel", - "description": "Flip bitangent direction for this normal map.", - "type": "Bool", - "defaultValue": false - }, - { - "name": "factor", - "displayName": "Factor", - "description": "Strength factor for scaling the values", - "type": "Float", - "defaultValue": 1.0, - "min": 0.0, - "softMax": 2.0 - } - ] - } - }, - "shaders": [ - ], - "functors": [ - ] -} diff --git a/Gems/Terrain/Assets/Shaders/Terrain/TerrainCommon.azsli b/Gems/Terrain/Assets/Shaders/Terrain/TerrainCommon.azsli index 72c1af953c..dc6f65207b 100644 --- a/Gems/Terrain/Assets/Shaders/Terrain/TerrainCommon.azsli +++ b/Gems/Terrain/Assets/Shaders/Terrain/TerrainCommon.azsli @@ -93,10 +93,15 @@ ShaderResourceGroup ObjectSrg : SRG_PerObject ShaderResourceGroup TerrainMaterialSrg : SRG_PerMaterial { Texture2D m_heightmapImage; + Texture2D m_detailMaterialIdImage; + float2 m_detailMaterialIdImageCenter; float m_detailTextureMultiplier; float m_detailFadeDistance; float m_detailFadeLength; + float4 m_detailAabb; + float m_detailHalfPixelUv; + Sampler HeightmapSampler { MinFilter = Linear; @@ -117,6 +122,15 @@ ShaderResourceGroup TerrainMaterialSrg : SRG_PerMaterial MaxAnisotropy = 16; }; + Sampler m_detailSampler + { + AddressU = Wrap; + AddressV = Wrap; + MinFilter = Point; + MagFilter = Point; + MipFilter = Point; + }; + // Base Color float3 m_baseColor; float m_baseColorFactor; diff --git a/Gems/Terrain/Assets/Shaders/Terrain/TerrainPBR_ForwardPass.azsl b/Gems/Terrain/Assets/Shaders/Terrain/TerrainPBR_ForwardPass.azsl index 750cd2fb29..5a43fe1c37 100644 --- a/Gems/Terrain/Assets/Shaders/Terrain/TerrainPBR_ForwardPass.azsl +++ b/Gems/Terrain/Assets/Shaders/Terrain/TerrainPBR_ForwardPass.azsl @@ -17,18 +17,19 @@ #include #include #include +#include struct VSOutput { float4 m_position : SV_Position; float3 m_normal: NORMAL; - float3 m_tangent : TANGENT; - float3 m_bitangent : BITANGENT; float3 m_worldPosition : UV0; - float3 m_shadowCoords[ViewSrg::MaxCascadeCount] : UV2; float2 m_uv : UV1; + float3 m_shadowCoords[ViewSrg::MaxCascadeCount] : UV2; }; +option bool o_debugDetailMaterialIds = false; + VSOutput TerrainPBR_MainPassVS(VertexInput IN) { VSOutput OUT; @@ -47,9 +48,9 @@ VSOutput TerrainPBR_MainPassVS(VertexInput IN) float down = GetHeight(origUv + terrainData.m_uvStep * float2( 0.0f, 1.0f)); float left = GetHeight(origUv + terrainData.m_uvStep * float2(-1.0f, 0.0f)); - OUT.m_bitangent = normalize(float3(0.0, terrainData.m_sampleSpacing * 2.0f, down - up)); - OUT.m_tangent = normalize(float3(terrainData.m_sampleSpacing * 2.0f, 0.0, right - left)); - OUT.m_normal = cross(OUT.m_tangent, OUT.m_bitangent); + float3 bitangent = normalize(float3(0.0, terrainData.m_sampleSpacing * 2.0f, down - up)); + float3 tangent = normalize(float3(terrainData.m_sampleSpacing * 2.0f, 0.0, right - left)); + OUT.m_normal = normalize(cross(tangent, bitangent)); OUT.m_uv = uv; // directional light shadow @@ -75,18 +76,14 @@ ForwardPassOutput TerrainPBR_MainPassPS(VSOutput IN) surface.position = IN.m_worldPosition.xyz; float viewDistance = length(ViewSrg::m_worldPosition - surface.position); float detailFactor = saturate((viewDistance - TerrainMaterialSrg::m_detailFadeDistance) / max(TerrainMaterialSrg::m_detailFadeLength, EPSILON)); - - ObjectSrg::TerrainData terrainData = ObjectSrg::m_terrainData; - float2 origUv = lerp(terrainData.m_uvMin, terrainData.m_uvMax, IN.m_uv); - origUv.y = 1.0 - origUv.y; float2 detailUv = IN.m_uv * TerrainMaterialSrg::m_detailTextureMultiplier; // ------- Normal ------- - float3 macroNormal = IN.m_normal; + float3 macroNormal = normalize(IN.m_normal); // ------- Macro Color / Normal ------- float3 macroColor = TerrainMaterialSrg::m_baseColor.rgb; - [unroll] for (uint i = 0; i < 4; ++i) + [unroll] for (uint i = 0; i < 4 && (i < ObjectSrg::m_macroMaterialCount); ++i) { float2 macroUvMin = ObjectSrg::m_macroMaterialData[i].m_uvMin; float2 macroUvMax = ObjectSrg::m_macroMaterialData[i].m_uvMax; @@ -121,6 +118,48 @@ ForwardPassOutput TerrainPBR_MainPassPS(VSOutput IN) float3 detailColor = GetBaseColorInput(TerrainMaterialSrg::m_baseColorMap, TerrainMaterialSrg::m_sampler, detailUv, TerrainMaterialSrg::m_baseColor.rgb, o_baseColor_useTexture); float3 blendedColor = BlendBaseColor(lerp(detailColor, TerrainMaterialSrg::m_baseColor.rgb, detailFactor), macroColor, TerrainMaterialSrg::m_baseColorFactor, o_baseColorTextureBlendMode, o_baseColor_useTexture); + // ------- Debug detail materials using random colors ------- + // This assigns a random color to each material, turns off any kind of distance fading, and draws a black line at the texture edges. + if (o_debugDetailMaterialIds) + { + float2 detailRegionMin = TerrainMaterialSrg::m_detailAabb.xy; + float2 detailRegionMax = TerrainMaterialSrg::m_detailAabb.zw; + float2 detailRegionUv = (surface.position.xy - detailRegionMin) / (detailRegionMax - detailRegionMin); + if (all(detailRegionUv > TerrainMaterialSrg::m_detailHalfPixelUv) && all(detailRegionUv < 1.0 - TerrainMaterialSrg::m_detailHalfPixelUv)) + { + detailRegionUv += TerrainMaterialSrg::m_detailMaterialIdImageCenter - (0.5); + + uint material1 = TerrainMaterialSrg::m_detailMaterialIdImage.GatherRed(TerrainMaterialSrg::m_detailSampler, detailRegionUv, 0).r; + uint material2 = TerrainMaterialSrg::m_detailMaterialIdImage.GatherGreen(TerrainMaterialSrg::m_detailSampler, detailRegionUv, 0).r; + float blend = float(TerrainMaterialSrg::m_detailMaterialIdImage.GatherBlue(TerrainMaterialSrg::m_detailSampler, detailRegionUv, 0).r) / 0xFF; + + float3 material1Color = float3(0.1, 0.1, 0.1); + float3 material2Color = float3(0.1, 0.1, 0.1); + + // Get a reasonably random hue for the material id + if (material1 != 255) + { + float hue1 = (material1 * 25043 % 256) / 256.0; + material1Color = HsvToRgb(float3(hue1, 1.0, 1.0)); + } + if (material2 != 255) + { + float hue2 = (material2 * 25043 % 256) / 256.0; + material2Color = HsvToRgb(float3(hue2, 1.0, 1.0)); + } + + blendedColor = lerp(material1Color, material2Color, blend); + float seamBlend = 0.0; + const float halfLineWidth = 1.0 / 2048.0; + if (any(abs(detailRegionUv) % 1.0 < halfLineWidth) || any(abs(detailRegionUv) % 1.0 > 1.0 - halfLineWidth)) + { + seamBlend = 1.0; + } + blendedColor = lerp(blendedColor, float3(0.0, 0.0, 0.0), seamBlend); // draw texture seams + blendedColor = pow(blendedColor , 2.2); + } + } + // ------- Specular ------- float specularF0Factor = GetSpecularInput(TerrainMaterialSrg::m_specularF0Map, TerrainMaterialSrg::m_sampler, detailUv, TerrainMaterialSrg::m_specularF0Factor, o_specularF0_useTexture); specularF0Factor = lerp(specularF0Factor, 0.5, detailFactor); diff --git a/Gems/Terrain/Code/Mocks/Terrain/MockTerrain.h b/Gems/Terrain/Code/Mocks/Terrain/MockTerrain.h index 53d93be8ea..f9607fdafb 100644 --- a/Gems/Terrain/Code/Mocks/Terrain/MockTerrain.h +++ b/Gems/Terrain/Code/Mocks/Terrain/MockTerrain.h @@ -34,7 +34,8 @@ namespace UnitTest MOCK_METHOD1(RegisterArea, void(AZ::EntityId areaId)); MOCK_METHOD1(UnregisterArea, void(AZ::EntityId areaId)); - MOCK_METHOD1(RefreshArea, void(AZ::EntityId areaId)); + MOCK_METHOD2(RefreshArea, + void(AZ::EntityId areaId, AzFramework::Terrain::TerrainDataNotifications::TerrainDataChangedMask changeMask)); }; class MockTerrainDataNotificationListener : public AzFramework::Terrain::TerrainDataNotificationBus::Handler diff --git a/Gems/Terrain/Code/Source/Components/TerrainHeightGradientListComponent.cpp b/Gems/Terrain/Code/Source/Components/TerrainHeightGradientListComponent.cpp index 01ad0bb77f..231d5abc28 100644 --- a/Gems/Terrain/Code/Source/Components/TerrainHeightGradientListComponent.cpp +++ b/Gems/Terrain/Code/Source/Components/TerrainHeightGradientListComponent.cpp @@ -119,7 +119,9 @@ namespace Terrain LmbrCentral::DependencyNotificationBus::Handler::BusDisconnect(); // Since this height data will no longer exist, notify the terrain system to refresh the area. - TerrainSystemServiceRequestBus::Broadcast(&TerrainSystemServiceRequestBus::Events::RefreshArea, GetEntityId()); + TerrainSystemServiceRequestBus::Broadcast( + &TerrainSystemServiceRequestBus::Events::RefreshArea, GetEntityId(), + AzFramework::Terrain::TerrainDataNotifications::HeightData); } bool TerrainHeightGradientListComponent::ReadInConfig(const AZ::ComponentConfig* baseConfig) @@ -176,7 +178,9 @@ namespace Terrain void TerrainHeightGradientListComponent::OnCompositionChanged() { RefreshMinMaxHeights(); - TerrainSystemServiceRequestBus::Broadcast(&TerrainSystemServiceRequestBus::Events::RefreshArea, GetEntityId()); + TerrainSystemServiceRequestBus::Broadcast( + &TerrainSystemServiceRequestBus::Events::RefreshArea, GetEntityId(), + AzFramework::Terrain::TerrainDataNotifications::HeightData); } void TerrainHeightGradientListComponent::RefreshMinMaxHeights() diff --git a/Gems/Terrain/Code/Source/Components/TerrainLayerSpawnerComponent.cpp b/Gems/Terrain/Code/Source/Components/TerrainLayerSpawnerComponent.cpp index 00ed9c004f..c3803c25e8 100644 --- a/Gems/Terrain/Code/Source/Components/TerrainLayerSpawnerComponent.cpp +++ b/Gems/Terrain/Code/Source/Components/TerrainLayerSpawnerComponent.cpp @@ -157,6 +157,12 @@ namespace Terrain void TerrainLayerSpawnerComponent::RefreshArea() { - TerrainSystemServiceRequestBus::Broadcast(&TerrainSystemServiceRequestBus::Events::RefreshArea, GetEntityId()); + using Terrain = AzFramework::Terrain::TerrainDataNotifications; + + // Notify the terrain system that the entire layer has changed, so both height and surface data can be affected. + TerrainSystemServiceRequestBus::Broadcast( + &TerrainSystemServiceRequestBus::Events::RefreshArea, GetEntityId(), + static_cast(Terrain::HeightData | Terrain::SurfaceData) + ); } } diff --git a/Gems/Terrain/Code/Source/Components/TerrainSurfaceGradientListComponent.cpp b/Gems/Terrain/Code/Source/Components/TerrainSurfaceGradientListComponent.cpp index 5b76f15d74..748221d69e 100644 --- a/Gems/Terrain/Code/Source/Components/TerrainSurfaceGradientListComponent.cpp +++ b/Gems/Terrain/Code/Source/Components/TerrainSurfaceGradientListComponent.cpp @@ -184,7 +184,9 @@ namespace Terrain void TerrainSurfaceGradientListComponent::OnCompositionChanged() { - TerrainSystemServiceRequestBus::Broadcast(&TerrainSystemServiceRequestBus::Events::RefreshArea, GetEntityId()); + TerrainSystemServiceRequestBus::Broadcast( + &TerrainSystemServiceRequestBus::Events::RefreshArea, GetEntityId(), + AzFramework::Terrain::TerrainDataNotifications::SurfaceData); } } // namespace Terrain diff --git a/Gems/Terrain/Code/Source/Components/TerrainWorldComponent.cpp b/Gems/Terrain/Code/Source/Components/TerrainWorldComponent.cpp index d8b7308ef7..bd65cf6abc 100644 --- a/Gems/Terrain/Code/Source/Components/TerrainWorldComponent.cpp +++ b/Gems/Terrain/Code/Source/Components/TerrainWorldComponent.cpp @@ -32,16 +32,22 @@ namespace Terrain AZ::EditContext* edit = serialize->GetEditContext(); if (edit) { - edit->Class( - "Terrain World Component", "Data required for the terrain system to run") + edit->Class("Terrain World Component", "Data required for the terrain system to run") ->ClassElement(AZ::Edit::ClassElements::EditorData, "") ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZStd::vector({ AZ_CRC_CE("Level") })) ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly) ->Attribute(AZ::Edit::Attributes::AutoExpand, true) ->DataElement(AZ::Edit::UIHandlers::Default, &TerrainWorldConfig::m_worldMin, "World Bounds (Min)", "") + // Temporary constraint until the rest of the Terrain system is updated to support larger worlds. + ->Attribute(AZ::Edit::Attributes::Min, -2048.0f) + ->Attribute(AZ::Edit::Attributes::Max, 2048.0f) ->DataElement(AZ::Edit::UIHandlers::Default, &TerrainWorldConfig::m_worldMax, "World Bounds (Max)", "") - ->DataElement(AZ::Edit::UIHandlers::Default, &TerrainWorldConfig::m_heightQueryResolution, "Height Query Resolution (m)", "") + // Temporary constraint until the rest of the Terrain system is updated to support larger worlds. + ->Attribute(AZ::Edit::Attributes::Min, -2048.0f) + ->Attribute(AZ::Edit::Attributes::Max, 2048.0f) + ->DataElement( + AZ::Edit::UIHandlers::Default, &TerrainWorldConfig::m_heightQueryResolution, "Height Query Resolution (m)", "") ; } } diff --git a/Gems/Terrain/Code/Source/Components/TerrainWorldRendererComponent.h b/Gems/Terrain/Code/Source/Components/TerrainWorldRendererComponent.h index 354b2fde34..140b830da1 100644 --- a/Gems/Terrain/Code/Source/Components/TerrainWorldRendererComponent.h +++ b/Gems/Terrain/Code/Source/Components/TerrainWorldRendererComponent.h @@ -46,7 +46,7 @@ namespace Terrain WorldSizeCount, }; - WorldSize m_worldSize; + WorldSize m_worldSize = WorldSize::_1024Meters; }; diff --git a/Gems/Terrain/Code/Source/TerrainRenderer/Components/TerrainMacroMaterialComponent.cpp b/Gems/Terrain/Code/Source/TerrainRenderer/Components/TerrainMacroMaterialComponent.cpp index 55653c64fa..a65cbad50b 100644 --- a/Gems/Terrain/Code/Source/TerrainRenderer/Components/TerrainMacroMaterialComponent.cpp +++ b/Gems/Terrain/Code/Source/TerrainRenderer/Components/TerrainMacroMaterialComponent.cpp @@ -16,87 +16,65 @@ #include #include +#include + namespace Terrain { - AZ::Data::AssetId TerrainMacroMaterialConfig::s_macroMaterialTypeAssetId{}; + bool TerrainMacroMaterialConfig::NormalMapAttributesAreReadOnly() const + { + return !m_macroNormalAsset.GetId().IsValid(); + } void TerrainMacroMaterialConfig::Reflect(AZ::ReflectContext* context) { - AZ::SerializeContext* serialize = azrtti_cast(context); - if (serialize) + if (auto* serialize = azrtti_cast(context); serialize) { serialize->Class() ->Version(1) - ->Field("MacroMaterial", &TerrainMacroMaterialConfig::m_materialAsset) - ; + ->Field("MacroColor", &TerrainMacroMaterialConfig::m_macroColorAsset) + ->Field("MacroNormal", &TerrainMacroMaterialConfig::m_macroNormalAsset) + ->Field("NormalFlipX", &TerrainMacroMaterialConfig::m_normalFlipX) + ->Field("NormalFlipY", &TerrainMacroMaterialConfig::m_normalFlipY) + ->Field("NormalFactor", &TerrainMacroMaterialConfig::m_normalFactor) + ; - // The edit context for this appears in EditorTerrainMacroMaterialComponent.cpp. - } - } - - AZ::Data::AssetId TerrainMacroMaterialConfig::GetTerrainMacroMaterialTypeAssetId() - { - // Get the Asset ID for the TerrainMacroMaterial material type and store it in a class static so that we don't have to look it - // up again. - if (!s_macroMaterialTypeAssetId.IsValid()) - { - AZ::Data::AssetCatalogRequestBus::BroadcastResult( - s_macroMaterialTypeAssetId, &AZ::Data::AssetCatalogRequestBus::Events::GetAssetIdByPath, TerrainMacroMaterialTypeAsset, - azrtti_typeid(), false); - AZ_Assert(s_macroMaterialTypeAssetId.IsValid(), "The asset '%s' couldn't be found.", TerrainMacroMaterialTypeAsset); - } - - return s_macroMaterialTypeAssetId; - } - - bool TerrainMacroMaterialConfig::IsMaterialTypeCorrect(const AZ::Data::AssetId& assetId) - { - // We'll verify that whatever material we try to load has this material type as a dependency, as a way to implicitly detect - // that we're only trying to use terrain macro materials even before we load the asset. - auto macroMaterialTypeAssetId = GetTerrainMacroMaterialTypeAssetId(); - - // Get the dependencies for the requested asset. - AZ::Outcome, AZStd::string> result; - AZ::Data::AssetCatalogRequestBus::BroadcastResult( - result, &AZ::Data::AssetCatalogRequestBus::Events::GetDirectProductDependencies, assetId); - - // If any of the dependencies match the TerrainMacroMaterial materialtype asset, then this should be the correct type of material. - if (result) - { - for (auto& dependency : result.GetValue()) + if (auto* editContext = serialize->GetEditContext(); editContext) { - if (dependency.m_assetId == macroMaterialTypeAssetId) - { - return true; - } + editContext + ->Class( + "Terrain Macro Material Component", "Provide a terrain macro material for a region of the world") + ->ClassElement(AZ::Edit::ClassElements::EditorData, "") + ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly) + ->Attribute(AZ::Edit::Attributes::AutoExpand, true) + + ->DataElement( + AZ::Edit::UIHandlers::Default, &TerrainMacroMaterialConfig::m_macroColorAsset, "Color Texture", + "Terrain macro color texture for use by any terrain inside the bounding box on this entity.") + ->DataElement( + AZ::Edit::UIHandlers::Default, &TerrainMacroMaterialConfig::m_macroNormalAsset, "Normal Texture", + "Texture for defining surface normal direction. These will override normals generated from the geometry.") + ->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ::Edit::PropertyRefreshLevels::AttributesAndValues) + ->DataElement( + AZ::Edit::UIHandlers::Default, &TerrainMacroMaterialConfig::m_normalFlipX, "Normal Flip X", + "Flip tangent direction for this normal map.") + ->Attribute(AZ::Edit::Attributes::ReadOnly, &TerrainMacroMaterialConfig::NormalMapAttributesAreReadOnly) + ->DataElement( + AZ::Edit::UIHandlers::Default, &TerrainMacroMaterialConfig::m_normalFlipY, "Normal Flip Y", + "Flip bitangent direction for this normal map.") + ->Attribute(AZ::Edit::Attributes::ReadOnly, &TerrainMacroMaterialConfig::NormalMapAttributesAreReadOnly) + ->DataElement( + AZ::Edit::UIHandlers::Slider, &TerrainMacroMaterialConfig::m_normalFactor, "Normal Factor", + "Strength factor for scaling the normal map values.") + ->Attribute(AZ::Edit::Attributes::Min, 0.0f) + ->Attribute(AZ::Edit::Attributes::Max, 10.0f) + ->Attribute(AZ::Edit::Attributes::SoftMin, 0.0f) + ->Attribute(AZ::Edit::Attributes::SoftMax, 2.0f) + ->Attribute(AZ::Edit::Attributes::ReadOnly, &TerrainMacroMaterialConfig::NormalMapAttributesAreReadOnly) + ; } } - - // Didn't have the expected dependency, so it must not be the right material type. - return false; } - AZ::Outcome TerrainMacroMaterialConfig::ValidateMaterialAsset(void* newValue, const AZ::Uuid& valueType) - { - if (azrtti_typeid>() != valueType) - { - AZ_Assert(false, "Unexpected value type"); - return AZ::Failure(AZStd::string("Unexpectedly received something other than a material asset for the MacroMaterial!")); - } - - auto newMaterialAsset = *static_cast*>(newValue); - - if (!IsMaterialTypeCorrect(newMaterialAsset.GetId())) - { - return AZ::Failure(AZStd::string::format( - "The selected MacroMaterial ('%s') needs to use the TerrainMacroMaterial material type.", - newMaterialAsset.GetHint().c_str())); - } - - return AZ::Success(); - } - - void TerrainMacroMaterialComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& services) { services.push_back(AZ_CRC_CE("TerrainMacroMaterialProviderService")); @@ -133,25 +111,29 @@ namespace Terrain void TerrainMacroMaterialComponent::Activate() { - // Clear out our shape bounds and make sure the material is queued to load. + // Clear out our shape bounds and make sure the texture assets are queued to load. m_cachedShapeBounds = AZ::Aabb::CreateNull(); - m_configuration.m_materialAsset.QueueLoad(); + m_configuration.m_macroColorAsset.QueueLoad(); + m_configuration.m_macroNormalAsset.QueueLoad(); // Don't mark our material as active until it's finished loading and is valid. m_macroMaterialActive = false; - // Listen for the material asset to complete loading. - AZ::Data::AssetBus::Handler::BusConnect(m_configuration.m_materialAsset.GetId()); + // Listen for the texture assets to complete loading. + AZ::Data::AssetBus::MultiHandler::BusConnect(m_configuration.m_macroColorAsset.GetId()); + AZ::Data::AssetBus::MultiHandler::BusConnect(m_configuration.m_macroNormalAsset.GetId()); } void TerrainMacroMaterialComponent::Deactivate() { TerrainMacroMaterialRequestBus::Handler::BusDisconnect(); - AZ::Data::AssetBus::Handler::BusDisconnect(); - m_configuration.m_materialAsset.Release(); + AZ::Data::AssetBus::MultiHandler::BusDisconnect(); + m_configuration.m_macroColorAsset.Release(); + m_configuration.m_macroNormalAsset.Release(); - m_macroMaterialInstance.reset(); + m_colorImage.reset(); + m_normalImage.reset(); // Send out any notifications as appropriate based on the macro material destruction. HandleMaterialStateChange(); @@ -195,12 +177,18 @@ namespace Terrain void TerrainMacroMaterialComponent::HandleMaterialStateChange() { - // We only want our component to appear active during the time that the macro material is loaded and valid. The logic below + // We only want our component to appear active during the time that the macro material is fully loaded and valid. The logic below // will handle all transition possibilities to notify if we've become active, inactive, or just changed. We'll also only // keep a valid up-to-date copy of the shape bounds while the material is valid, since we don't need it any other time. + // Color and normal data is considered ready if it's finished loading or if we don't have a texture specified + bool colorReady = m_colorImage || (!m_configuration.m_macroColorAsset.GetId().IsValid()); + bool normalReady = m_normalImage || (!m_configuration.m_macroNormalAsset.GetId().IsValid()); + // If we don't have color or normal data, then we don't have *any* useful data, so don't activate the macro material. + bool hasAnyData = m_configuration.m_macroColorAsset.GetId().IsValid() || m_configuration.m_macroNormalAsset.GetId().IsValid(); + bool wasPreviouslyActive = m_macroMaterialActive; - bool isNowActive = (m_macroMaterialInstance != nullptr); + bool isNowActive = colorReady && normalReady && hasAnyData; // Set our state to active or inactive, based on whether or not the macro material instance is now valid. m_macroMaterialActive = isNowActive; @@ -226,9 +214,10 @@ namespace Terrain // Start listening for shape changes. LmbrCentral::ShapeComponentNotificationsBus::Handler::BusConnect(GetEntityId()); + MacroMaterialData material = GetTerrainMacroMaterialData(); + TerrainMacroMaterialNotificationBus::Broadcast( - &TerrainMacroMaterialNotificationBus::Events::OnTerrainMacroMaterialCreated, GetEntityId(), m_macroMaterialInstance, - m_cachedShapeBounds); + &TerrainMacroMaterialNotificationBus::Events::OnTerrainMacroMaterialCreated, GetEntityId(), material); } else if (wasPreviouslyActive && !isNowActive) { @@ -246,30 +235,37 @@ namespace Terrain else { // We were active both before and after, so just send out a material changed event. + MacroMaterialData material = GetTerrainMacroMaterialData(); TerrainMacroMaterialNotificationBus::Broadcast( - &TerrainMacroMaterialNotificationBus::Events::OnTerrainMacroMaterialChanged, GetEntityId(), m_macroMaterialInstance); + &TerrainMacroMaterialNotificationBus::Events::OnTerrainMacroMaterialChanged, GetEntityId(), material); } } void TerrainMacroMaterialComponent::OnAssetReady(AZ::Data::Asset asset) { - m_configuration.m_materialAsset = asset; - - if (m_configuration.m_materialAsset.Get()->GetMaterialTypeAsset().GetId() == - TerrainMacroMaterialConfig::GetTerrainMacroMaterialTypeAssetId()) + if (asset.GetId() == m_configuration.m_macroColorAsset.GetId()) { - m_macroMaterialInstance = AZ::RPI::Material::FindOrCreate(m_configuration.m_materialAsset); + m_configuration.m_macroColorAsset = asset; + m_colorImage = AZ::RPI::StreamingImage::FindOrCreate(m_configuration.m_macroColorAsset); + m_colorImage->GetRHIImage()->SetName(AZ::Name(m_configuration.m_macroColorAsset.GetHint())); + + // Clear the texture asset reference to make sure we don't prevent hot-reloading. + m_configuration.m_macroColorAsset.Release(); + } + else if (asset.GetId() == m_configuration.m_macroNormalAsset.GetId()) + { + m_configuration.m_macroNormalAsset = asset; + m_normalImage = AZ::RPI::StreamingImage::FindOrCreate(m_configuration.m_macroNormalAsset); + m_normalImage->GetRHIImage()->SetName(AZ::Name(m_configuration.m_macroNormalAsset.GetHint())); + + // Clear the texture asset reference to make sure we don't prevent hot-reloading. + m_configuration.m_macroColorAsset.Release(); } else { - AZ_Error("Terrain", false, "Material '%s' has the wrong material type.", m_configuration.m_materialAsset.GetHint().c_str()); - m_macroMaterialInstance.reset(); } - // Clear the material asset reference to make sure we don't prevent hot-reloading. - m_configuration.m_materialAsset.Release(); - HandleMaterialStateChange(); } @@ -278,10 +274,18 @@ namespace Terrain OnAssetReady(asset); } - void TerrainMacroMaterialComponent::GetTerrainMacroMaterialData( - AZ::Data::Instance& macroMaterial, AZ::Aabb& macroMaterialRegion) + MacroMaterialData TerrainMacroMaterialComponent::GetTerrainMacroMaterialData() { - macroMaterial = m_macroMaterialInstance; - macroMaterialRegion = m_cachedShapeBounds; + MacroMaterialData macroMaterial; + + macroMaterial.m_entityId = GetEntityId(); + macroMaterial.m_bounds = m_cachedShapeBounds; + macroMaterial.m_colorImage = m_colorImage; + macroMaterial.m_normalImage = m_normalImage; + macroMaterial.m_normalFactor = m_configuration.m_normalFactor; + macroMaterial.m_normalFlipX = m_configuration.m_normalFlipX; + macroMaterial.m_normalFlipY = m_configuration.m_normalFlipY; + + return macroMaterial; } } diff --git a/Gems/Terrain/Code/Source/TerrainRenderer/Components/TerrainMacroMaterialComponent.h b/Gems/Terrain/Code/Source/TerrainRenderer/Components/TerrainMacroMaterialComponent.h index b60f6b2a94..cd82316ff6 100644 --- a/Gems/Terrain/Code/Source/TerrainRenderer/Components/TerrainMacroMaterialComponent.h +++ b/Gems/Terrain/Code/Source/TerrainRenderer/Components/TerrainMacroMaterialComponent.h @@ -11,10 +11,9 @@ #include #include #include -#include #include #include - +#include namespace LmbrCentral { @@ -32,23 +31,20 @@ namespace Terrain AZ_RTTI(TerrainMacroMaterialConfig, "{9DBAFFF0-FD20-4594-8884-E3266D8CCAC8}", AZ::ComponentConfig); static void Reflect(AZ::ReflectContext* context); - AZ::Data::Asset m_materialAsset = { AZ::Data::AssetLoadBehavior::QueueLoad }; - - static AZ::Data::AssetId GetTerrainMacroMaterialTypeAssetId(); - static bool IsMaterialTypeCorrect(const AZ::Data::AssetId&); - AZ::Outcome ValidateMaterialAsset(void* newValue, const AZ::Uuid& valueType); - - private: - static inline constexpr const char* TerrainMacroMaterialTypeAsset = "materials/terrain/terrainmacromaterial.azmaterialtype"; - static AZ::Data::AssetId s_macroMaterialTypeAssetId; + bool NormalMapAttributesAreReadOnly() const; + AZ::Data::Asset m_macroColorAsset = { AZ::Data::AssetLoadBehavior::QueueLoad }; + AZ::Data::Asset m_macroNormalAsset = { AZ::Data::AssetLoadBehavior::QueueLoad }; + bool m_normalFlipX = false; + bool m_normalFlipY = false; + float m_normalFactor = 1.0f; }; class TerrainMacroMaterialComponent : public AZ::Component , public TerrainMacroMaterialRequestBus::Handler , private LmbrCentral::ShapeComponentNotificationsBus::Handler - , private AZ::Data::AssetBus::Handler + , private AZ::Data::AssetBus::MultiHandler { public: template @@ -70,7 +66,7 @@ namespace Terrain bool ReadInConfig(const AZ::ComponentConfig* baseConfig) override; bool WriteOutConfig(AZ::ComponentConfig* outBaseConfig) const override; - void GetTerrainMacroMaterialData(AZ::Data::Instance& macroMaterial, AZ::Aabb& macroMaterialRegion) override; + MacroMaterialData GetTerrainMacroMaterialData() override; private: //////////////////////////////////////////////////////////////////////// @@ -86,7 +82,8 @@ namespace Terrain TerrainMacroMaterialConfig m_configuration; AZ::Aabb m_cachedShapeBounds; - AZ::Data::Instance m_macroMaterialInstance; bool m_macroMaterialActive{ false }; + AZ::Data::Instance m_colorImage; + AZ::Data::Instance m_normalImage; }; } diff --git a/Gems/Terrain/Code/Source/TerrainRenderer/Components/TerrainSurfaceMaterialsListComponent.cpp b/Gems/Terrain/Code/Source/TerrainRenderer/Components/TerrainSurfaceMaterialsListComponent.cpp index 271919070c..3c6a213b02 100644 --- a/Gems/Terrain/Code/Source/TerrainRenderer/Components/TerrainSurfaceMaterialsListComponent.cpp +++ b/Gems/Terrain/Code/Source/TerrainRenderer/Components/TerrainSurfaceMaterialsListComponent.cpp @@ -123,20 +123,23 @@ namespace Terrain { surfaceMaterialMapping.m_active = false; surfaceMaterialMapping.m_materialAsset.QueueLoad(); - AZ::Data::AssetBus::Handler::BusConnect(surfaceMaterialMapping.m_materialAsset.GetId()); + AZ::Data::AssetBus::MultiHandler::BusConnect(surfaceMaterialMapping.m_materialAsset.GetId()); } } + + // Announce initial shape using OnShapeChanged + OnShapeChanged(LmbrCentral::ShapeComponentNotifications::ShapeChangeReasons::ShapeChanged); } void TerrainSurfaceMaterialsListComponent::Deactivate() { TerrainAreaMaterialRequestBus::Handler::BusDisconnect(); + AZ::Data::AssetBus::MultiHandler::BusDisconnect(); for (auto& surfaceMaterialMapping : m_configuration.m_surfaceMaterials) { if (surfaceMaterialMapping.m_materialAsset.GetId().IsValid()) { - AZ::Data::AssetBus::Handler::BusDisconnect(surfaceMaterialMapping.m_materialAsset.GetId()); surfaceMaterialMapping.m_materialAsset.Release(); surfaceMaterialMapping.m_materialInstance.reset(); surfaceMaterialMapping.m_activeMaterialAssetId = AZ::Data::AssetId(); @@ -202,7 +205,7 @@ namespace Terrain // Don't disconnect from the AssetBus if this material is mapped more than once. if (CountMaterialIDInstances(surfaceMaterialMapping.m_activeMaterialAssetId) == 1) { - AZ::Data::AssetBus::Handler::BusDisconnect(surfaceMaterialMapping.m_activeMaterialAssetId); + AZ::Data::AssetBus::MultiHandler::BusDisconnect(surfaceMaterialMapping.m_activeMaterialAssetId); } surfaceMaterialMapping.m_activeMaterialAssetId = AZ::Data::AssetId(); @@ -273,12 +276,14 @@ namespace Terrain &TerrainAreaMaterialNotificationBus::Events::OnTerrainSurfaceMaterialMappingRegionChanged, GetEntityId(), oldAabb, m_cachedAabb); } - - const AZStd::vector& TerrainSurfaceMaterialsListComponent::GetSurfaceMaterialMappings( - AZ::Aabb& region) const + + const AZ::Aabb& TerrainSurfaceMaterialsListComponent::GetTerrainSurfaceMaterialRegion() const { - region = m_cachedAabb; + return m_cachedAabb; + } + const AZStd::vector& TerrainSurfaceMaterialsListComponent::GetSurfaceMaterialMappings() const + { return m_configuration.m_surfaceMaterials; } diff --git a/Gems/Terrain/Code/Source/TerrainRenderer/Components/TerrainSurfaceMaterialsListComponent.h b/Gems/Terrain/Code/Source/TerrainRenderer/Components/TerrainSurfaceMaterialsListComponent.h index 7c36033c41..0e32cb12c0 100644 --- a/Gems/Terrain/Code/Source/TerrainRenderer/Components/TerrainSurfaceMaterialsListComponent.h +++ b/Gems/Terrain/Code/Source/TerrainRenderer/Components/TerrainSurfaceMaterialsListComponent.h @@ -53,7 +53,7 @@ namespace Terrain class TerrainSurfaceMaterialsListComponent : public AZ::Component , private TerrainAreaMaterialRequestBus::Handler - , private AZ::Data::AssetBus::Handler + , private AZ::Data::AssetBus::MultiHandler , private LmbrCentral::ShapeComponentNotificationsBus::Handler { public: @@ -86,7 +86,8 @@ namespace Terrain ////////////////////////////////////////////////////////////////////////// // TerrainAreaMaterialRequestBus - const AZStd::vector& GetSurfaceMaterialMappings(AZ::Aabb& region) const override; + const AZ::Aabb& GetTerrainSurfaceMaterialRegion() const override; + const AZStd::vector& GetSurfaceMaterialMappings() const override; ////////////////////////////////////////////////////////////////////////// // AZ::Data::AssetBus::Handler diff --git a/Gems/Terrain/Code/Source/TerrainRenderer/EditorComponents/EditorTerrainMacroMaterialComponent.cpp b/Gems/Terrain/Code/Source/TerrainRenderer/EditorComponents/EditorTerrainMacroMaterialComponent.cpp index 07472d1b85..65500af42d 100644 --- a/Gems/Terrain/Code/Source/TerrainRenderer/EditorComponents/EditorTerrainMacroMaterialComponent.cpp +++ b/Gems/Terrain/Code/Source/TerrainRenderer/EditorComponents/EditorTerrainMacroMaterialComponent.cpp @@ -17,36 +17,7 @@ namespace Terrain { BaseClassType::ReflectSubClass( context, 1, - &LmbrCentral::EditorWrappedComponentBaseVersionConverter - ); - - AZ::SerializeContext* serializeContext = azrtti_cast(context); - - if (serializeContext) - { - AZ::EditContext* editContext = serializeContext->GetEditContext(); - - // The edit context for TerrainMacroMaterialConfig is specified here to make it easier to add custom filtering to the - // asset picker for the material asset so that we can eventually only display materials that inherit from the proper - // material type. - if (editContext) - { - editContext - ->Class( - "Terrain Macro Material Component", "Provide a terrain macro material for a region of the world") - ->ClassElement(AZ::Edit::ClassElements::EditorData, "") - ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly) - ->Attribute(AZ::Edit::Attributes::AutoExpand, true) - - ->DataElement( - AZ::Edit::UIHandlers::Default, &TerrainMacroMaterialConfig::m_materialAsset, "Macro Material", - "Terrain macro material for use by any terrain inside the bounding box on this entity.") - // This is disabled until ChangeValidate can support the Asset type. :( - //->Attribute(AZ::Edit::Attributes::ChangeValidate, &TerrainMacroMaterialConfig::ValidateMaterialAsset) - ; - } - } - + &LmbrCentral::EditorWrappedComponentBaseVersionConverter< + typename BaseClassType::WrappedComponentType, typename BaseClassType::WrappedConfigType, 1>); } } diff --git a/Gems/Terrain/Code/Source/TerrainRenderer/TerrainAreaMaterialRequestBus.h b/Gems/Terrain/Code/Source/TerrainRenderer/TerrainAreaMaterialRequestBus.h index dfedf0b34a..ae36a2639e 100644 --- a/Gems/Terrain/Code/Source/TerrainRenderer/TerrainAreaMaterialRequestBus.h +++ b/Gems/Terrain/Code/Source/TerrainRenderer/TerrainAreaMaterialRequestBus.h @@ -9,13 +9,9 @@ #pragma once #include - #include - #include -#include - namespace Terrain { //! This bus provides retrieval of information from Terrain Surfaces. @@ -30,8 +26,11 @@ namespace Terrain virtual ~TerrainAreaMaterialRequests() = default; + //! Get the Aabb for the region where a TerrainSurfaceMaterialMapping exists + virtual const AZ::Aabb& GetTerrainSurfaceMaterialRegion() const = 0; + //! Get the Material asset assigned to a particular surface tag. - virtual const AZStd::vector& GetSurfaceMaterialMappings(AZ::Aabb& region) const = 0; + virtual const AZStd::vector& GetSurfaceMaterialMappings() const = 0; }; using TerrainAreaMaterialRequestBus = AZ::EBus; diff --git a/Gems/Terrain/Code/Source/TerrainRenderer/TerrainFeatureProcessor.cpp b/Gems/Terrain/Code/Source/TerrainRenderer/TerrainFeatureProcessor.cpp index 8c85e21490..0ed70d949f 100644 --- a/Gems/Terrain/Code/Source/TerrainRenderer/TerrainFeatureProcessor.cpp +++ b/Gems/Terrain/Code/Source/TerrainRenderer/TerrainFeatureProcessor.cpp @@ -7,11 +7,13 @@ */ #include +#include +#include +#include #include #include #include -#include #include @@ -45,19 +47,49 @@ namespace Terrain { [[maybe_unused]] const char* TerrainFPName = "TerrainFeatureProcessor"; const char* TerrainHeightmapChars = "TerrainHeightmap"; + const char* TerrainDetailChars = "TerrainDetail"; } namespace MaterialInputs { // Terrain material static const char* const HeightmapImage("settings.heightmapImage"); + static const char* const DetailMaterialIdImage("settings.detailMaterialIdImage"); + static const char* const DetailCenter("settings.detailMaterialIdCenter"); + static const char* const DetailAabb("settings.detailAabb"); + static const char* const DetailHalfPixelUv("settings.detailHalfPixelUv"); + } - // Macro material - static const char* const MacroColorTextureMap("baseColor.textureMap"); - static const char* const MacroNormalTextureMap("normal.textureMap"); - static const char* const MacroNormalFlipX("normal.flipX"); - static const char* const MacroNormalFlipY("normal.flipY"); - static const char* const MacroNormalFactor("normal.factor"); + namespace DetailMaterialInputs + { + static const char* const BaseColorMap("baseColor.textureMap"); + static const char* const BaseColorUseTexture("baseColor.useTexture"); + static const char* const BaseColorFactor("baseColor.factor"); + static const char* const BaseColorBlendMode("baseColor.textureBlendMode"); + static const char* const MetallicMap("metallic.textureMap"); + static const char* const MetallicUseTexture("metallic.useTexture"); + static const char* const MetallicFactor("metallic.factor"); + static const char* const RoughnessMap("roughness.textureMap"); + static const char* const RoughnessUseTexture("roughness.useTexture"); + static const char* const RoughnessFactor("roughness.factor"); + static const char* const RoughnessUpperBound("roughness.lowerBound"); + static const char* const RoughnessLowerBound("roughness.upperBound"); + static const char* const SpecularF0Map("specularF0.textureMap"); + static const char* const SpecularF0UseTexture("specularF0.useTexture"); + static const char* const SpecularF0Factor("specularF0.factor"); + static const char* const NormalMap("normal.textureMap"); + static const char* const NormalUseTexture("normal.useTexture"); + static const char* const NormalFactor("normal.factor"); + static const char* const NormalFlipX("normal.flipX"); + static const char* const NormalFlipY("normal.flipY"); + static const char* const DiffuseOcclusionMap("occlusion.diffuseTextureMap"); + static const char* const DiffuseOcclusionUseTexture("occlusion.diffuseUseTexture"); + static const char* const DiffuseOcclusionFactor("occlusion.diffuseFactor"); + static const char* const HeightMap("parallax.textureMap"); + static const char* const HeightUseTexture("parallax.useTexture"); + static const char* const HeightFactor("parallax.factor"); + static const char* const HeightOffset("parallax.offset"); + static const char* const HeightBlendFactor("parallax.blendFactor"); } namespace ShaderInputs @@ -69,6 +101,17 @@ namespace Terrain static const char* const MacroColorMap("m_macroColorMap"); static const char* const MacroNormalMap("m_macroNormalMap"); } + + AZ_CVAR(bool, + r_terrainDebugDetailMaterials, + false, + [](const bool& value) + { + AZ::RPI::ShaderSystemInterface::Get()->SetGlobalShaderOption(AZ::Name{ "o_debugDetailMaterialIds" }, AZ::RPI::ShaderOptionValue{ value }); + }, + AZ::ConsoleFunctorFlags::Null, + "Turns on debugging for detail material ids for terrain." + ); void TerrainFeatureProcessor::Reflect(AZ::ReflectContext* context) @@ -85,6 +128,12 @@ namespace Terrain { Initialize(); AzFramework::Terrain::TerrainDataNotificationBus::Handler::BusConnect(); + + m_handleGlobalShaderOptionUpdate = AZ::RPI::ShaderSystemInterface::GlobalShaderOptionUpdatedEvent::Handler + { + [this](const AZ::Name&, AZ::RPI::ShaderOptionValue) { m_forceRebuildDrawPackets = true; } + }; + AZ::RPI::ShaderSystemInterface::Get()->Connect(m_handleGlobalShaderOptionUpdate); } void TerrainFeatureProcessor::Initialize() @@ -146,11 +195,18 @@ namespace Terrain void TerrainFeatureProcessor::OnTerrainDataChanged(const AZ::Aabb& dirtyRegion, TerrainDataChangedMask dataChangedMask) { - if ((dataChangedMask & (TerrainDataChangedMask::HeightData | TerrainDataChangedMask::Settings)) == 0) + if ((dataChangedMask & (TerrainDataChangedMask::HeightData | TerrainDataChangedMask::Settings)) != 0) { - return; + TerrainHeightOrSettingsUpdated(dirtyRegion); } + if ((dataChangedMask & TerrainDataChangedMask::SurfaceData) != 0) + { + TerrainSurfaceDataUpdated(dirtyRegion); + } + } + void TerrainFeatureProcessor::TerrainHeightOrSettingsUpdated(const AZ::Aabb& dirtyRegion) + { AZ::Aabb worldBounds = AZ::Aabb::CreateNull(); AzFramework::Terrain::TerrainDataRequestBus::BroadcastResult( worldBounds, &AzFramework::Terrain::TerrainDataRequests::GetTerrainAabb); @@ -162,9 +218,11 @@ namespace Terrain const AZ::Transform transform = AZ::Transform::CreateTranslation(worldBounds.GetCenter()); - AZ::Vector2 queryResolution = AZ::Vector2(1.0f); + AZ::Vector2 queryResolution2D = AZ::Vector2(1.0f); AzFramework::Terrain::TerrainDataRequestBus::BroadcastResult( - queryResolution, &AzFramework::Terrain::TerrainDataRequests::GetTerrainHeightQueryResolution); + queryResolution2D, &AzFramework::Terrain::TerrainDataRequests::GetTerrainHeightQueryResolution); + // Currently query resolution is multidimensional but the rendering system only supports this changing in one dimension. + float queryResolution = queryResolution2D.GetX(); // Sectors need to be rebuilt if the world bounds change in the x/y, or the sample spacing changes. m_areaData.m_rebuildSectors = m_areaData.m_rebuildSectors || @@ -172,25 +230,24 @@ namespace Terrain m_areaData.m_terrainBounds.GetMin().GetY() != worldBounds.GetMin().GetY() || m_areaData.m_terrainBounds.GetMax().GetX() != worldBounds.GetMax().GetX() || m_areaData.m_terrainBounds.GetMax().GetY() != worldBounds.GetMax().GetY() || - m_areaData.m_sampleSpacing != queryResolution.GetX(); + m_areaData.m_sampleSpacing != queryResolution; m_areaData.m_transform = transform; m_areaData.m_terrainBounds = worldBounds; - m_areaData.m_heightmapImageWidth = aznumeric_cast(worldBounds.GetXExtent() / queryResolution.GetX()); - m_areaData.m_heightmapImageHeight = aznumeric_cast(worldBounds.GetYExtent() / queryResolution.GetY()); - m_areaData.m_updateWidth = aznumeric_cast(m_dirtyRegion.GetXExtent() / queryResolution.GetX()); - m_areaData.m_updateHeight = aznumeric_cast(m_dirtyRegion.GetYExtent() / queryResolution.GetY()); - // Currently query resolution is multidimensional but the rendering system only supports this changing in one dimension. - m_areaData.m_sampleSpacing = queryResolution.GetX(); + m_areaData.m_sampleSpacing = queryResolution; m_areaData.m_heightmapUpdated = true; } - - void TerrainFeatureProcessor::OnTerrainMacroMaterialCreated(AZ::EntityId entityId, MaterialInstance material, const AZ::Aabb& region) - { - MacroMaterialData& materialData = FindOrCreateMacroMaterial(entityId); - materialData.m_bounds = region; - UpdateMacroMaterialData(materialData, material); + void TerrainFeatureProcessor::TerrainSurfaceDataUpdated(const AZ::Aabb& dirtyRegion) + { + m_dirtyDetailRegion.AddAabb(dirtyRegion); + } + + void TerrainFeatureProcessor::OnTerrainMacroMaterialCreated(AZ::EntityId entityId, const MacroMaterialData& newMaterialData) + { + MacroMaterialData& materialData = FindOrCreateByEntityId(entityId, m_macroMaterials); + + UpdateMacroMaterialData(materialData, newMaterialData); // Update all sectors in region. ForOverlappingSectors(materialData.m_bounds, @@ -203,22 +260,16 @@ namespace Terrain ); } - void TerrainFeatureProcessor::OnTerrainMacroMaterialChanged(AZ::EntityId entityId, MaterialInstance macroMaterial) + void TerrainFeatureProcessor::OnTerrainMacroMaterialChanged(AZ::EntityId entityId, const MacroMaterialData& newMaterialData) { - if (macroMaterial) - { - MacroMaterialData& data = FindOrCreateMacroMaterial(entityId); - UpdateMacroMaterialData(data, macroMaterial); - } - else - { - RemoveMacroMaterial(entityId); - } + MacroMaterialData& data = FindOrCreateByEntityId(entityId, m_macroMaterials); + UpdateMacroMaterialData(data, newMaterialData); } - void TerrainFeatureProcessor::OnTerrainMacroMaterialRegionChanged(AZ::EntityId entityId, [[maybe_unused]] const AZ::Aabb& oldRegion, const AZ::Aabb& newRegion) + void TerrainFeatureProcessor::OnTerrainMacroMaterialRegionChanged( + AZ::EntityId entityId, [[maybe_unused]] const AZ::Aabb& oldRegion, const AZ::Aabb& newRegion) { - MacroMaterialData& materialData = FindOrCreateMacroMaterial(entityId); + MacroMaterialData& materialData = FindOrCreateByEntityId(entityId, m_macroMaterials); for (SectorData& sectorData : m_sectorData) { bool overlapsOld = sectorData.m_aabb.Overlaps(materialData.m_bounds); @@ -250,7 +301,7 @@ namespace Terrain void TerrainFeatureProcessor::OnTerrainMacroMaterialDestroyed(AZ::EntityId entityId) { - MacroMaterialData* materialData = FindMacroMaterial(entityId); + const MacroMaterialData* materialData = FindByEntityId(entityId, m_macroMaterials); if (materialData) { @@ -269,36 +320,498 @@ namespace Terrain } m_areaData.m_macroMaterialsUpdated = true; + RemoveByEntityId(entityId, m_macroMaterials); + } + + void TerrainFeatureProcessor::OnTerrainSurfaceMaterialMappingCreated(AZ::EntityId entityId, SurfaceData::SurfaceTag surfaceTag, MaterialInstance material) + { + DetailMaterialListRegion& materialRegion = FindOrCreateByEntityId(entityId, m_detailMaterialRegions); + + // Validate that the surface tag is new + for (DetailMaterialSurface& surface : materialRegion.m_materialsForSurfaces) + { + if (surface.m_surfaceTag == surfaceTag) + { + AZ_Error(TerrainFPName, false, "Already have a surface material mapping for this surface tag."); + return; + } + } + + uint16_t detailMaterialId = CreateOrUpdateDetailMaterial(material); + materialRegion.m_materialsForSurfaces.push_back({ surfaceTag, detailMaterialId }); + m_dirtyDetailRegion.AddAabb(materialRegion.m_region); + } + + void TerrainFeatureProcessor::OnTerrainSurfaceMaterialMappingDestroyed(AZ::EntityId entityId, SurfaceData::SurfaceTag surfaceTag) + { + DetailMaterialListRegion& materialRegion = FindOrCreateByEntityId(entityId, m_detailMaterialRegions); + + for (DetailMaterialSurface& surface : materialRegion.m_materialsForSurfaces) + { + if (surface.m_surfaceTag == surfaceTag) + { + if (surface.m_surfaceTag != materialRegion.m_materialsForSurfaces.back().m_surfaceTag) + { + AZStd::swap(surface, materialRegion.m_materialsForSurfaces.back()); + } + materialRegion.m_materialsForSurfaces.pop_back(); + m_dirtyDetailRegion.AddAabb(materialRegion.m_region); + return; + } + } + AZ_Error(TerrainFPName, false, "Could not find surface tag to destroy for OnTerrainSurfaceMaterialMappingDestroyed()."); + } + + void TerrainFeatureProcessor::OnTerrainSurfaceMaterialMappingChanged(AZ::EntityId entityId, SurfaceData::SurfaceTag surfaceTag, MaterialInstance material) + { + DetailMaterialListRegion& materialRegion = FindOrCreateByEntityId(entityId, m_detailMaterialRegions); + + bool found = false; + uint16_t materialId = CreateOrUpdateDetailMaterial(material); + for (DetailMaterialSurface& surface : materialRegion.m_materialsForSurfaces) + { + if (surface.m_surfaceTag == surfaceTag) + { + found = true; + surface.m_detailMaterialId = materialId; + break; + } + } + + if (!found) + { + materialRegion.m_materialsForSurfaces.push_back({ surfaceTag, materialId }); + } + m_dirtyDetailRegion.AddAabb(materialRegion.m_region); + } + + void TerrainFeatureProcessor::OnTerrainSurfaceMaterialMappingRegionChanged(AZ::EntityId entityId, const AZ::Aabb& oldRegion, const AZ::Aabb& newRegion) + { + DetailMaterialListRegion& materialRegion = FindOrCreateByEntityId(entityId, m_detailMaterialRegions); + materialRegion.m_region = newRegion; + m_dirtyDetailRegion.AddAabb(oldRegion); + m_dirtyDetailRegion.AddAabb(newRegion); + } + + uint16_t TerrainFeatureProcessor::CreateOrUpdateDetailMaterial(MaterialInstance material) + { + static constexpr uint16_t InvalidDetailMaterial = 0xFFFF; + uint16_t detailMaterialId = InvalidDetailMaterial; + + for (DetailMaterialData& detailMaterial : m_detailMaterials.GetDataVector()) + { + if (detailMaterial.m_assetId == material->GetAssetId()) + { + UpdateDetailMaterialData(detailMaterial, material); + detailMaterialId = m_detailMaterials.GetIndexForData(&detailMaterial); + break; + } + } + + if (detailMaterialId == InvalidDetailMaterial) + { + detailMaterialId = m_detailMaterials.GetFreeSlotIndex(); + UpdateDetailMaterialData(m_detailMaterials.GetData(detailMaterialId), material); + } + return detailMaterialId; + } + + void TerrainFeatureProcessor::UpdateDetailMaterialData(DetailMaterialData& materialData, MaterialInstance material) + { + if (materialData.m_materialChangeId != material->GetCurrentChangeId()) + { + materialData = DetailMaterialData(); + DetailTextureFlags& flags = materialData.m_properties.m_flags; + materialData.m_materialChangeId = material->GetCurrentChangeId(); + materialData.m_assetId = material->GetAssetId(); + + auto getIndex = [&](const char* const indexName) -> AZ::RPI::MaterialPropertyIndex + { + const AZ::RPI::MaterialPropertyIndex index = material->FindPropertyIndex(AZ::Name(indexName)); + AZ_Warning(TerrainFPName, index.IsValid(), "Failed to find shader input constant %s.", indexName); + return index; + }; + + auto applyProperty = [&](const char* const indexName, auto& ref) -> void + { + const auto index = getIndex(indexName); + if (index.IsValid()) + { + using TypeRefRemoved = AZStd::remove_cvref_t; + ref = material->GetPropertyValue(index).GetValue(); + } + }; + + auto applyFlag = [&](const char* const indexName, DetailTextureFlags flagToSet) -> void + { + const auto index = getIndex(indexName); + if (index.IsValid()) + { + bool flagValue = material->GetPropertyValue(index).GetValue(); + flags = DetailTextureFlags(flagValue ? flags | flagToSet : flags); + } + }; + + auto getEnumName = [&](const char* const indexName) -> const AZStd::string_view + { + const auto index = getIndex(indexName); + if (index.IsValid()) + { + uint32_t enumIndex = material->GetPropertyValue(index).GetValue(); + const AZ::Name& enumName = material->GetMaterialPropertiesLayout()->GetPropertyDescriptor(index)->GetEnumName(enumIndex); + return enumName.GetStringView(); + } + return ""; + }; + + using namespace DetailMaterialInputs; + applyProperty(BaseColorMap, materialData.m_colorImage); + applyFlag(BaseColorUseTexture, DetailTextureFlags::UseTextureBaseColor); + applyProperty(BaseColorFactor, materialData.m_properties.m_baseColorFactor); + + const AZStd::string_view& blendModeString = getEnumName(BaseColorBlendMode); + if (blendModeString == "Multiply") + { + flags = DetailTextureFlags(flags | DetailTextureFlags::BlendModeMultiply); + } + else if (blendModeString == "LinearLight") + { + flags = DetailTextureFlags(flags | DetailTextureFlags::BlendModeLinearLight); + } + else if (blendModeString == "Lerp") + { + flags = DetailTextureFlags(flags | DetailTextureFlags::BlendModeLerp); + } + else if (blendModeString == "Overlay") + { + flags = DetailTextureFlags(flags | DetailTextureFlags::BlendModeOverlay); + } + + applyProperty(MetallicMap, materialData.m_metalnessImage); + applyFlag(MetallicUseTexture, DetailTextureFlags::UseTextureMetallic); + applyProperty(MetallicFactor, materialData.m_properties.m_metalFactor); + + applyProperty(RoughnessMap, materialData.m_roughnessImage); + applyFlag(RoughnessUseTexture, DetailTextureFlags::UseTextureRoughness); + + if ((flags & DetailTextureFlags::UseTextureRoughness) > 0) + { + float lowerBound = 0.0; + float upperBound = 1.0; + applyProperty(RoughnessLowerBound, lowerBound); + applyProperty(RoughnessUpperBound, upperBound); + materialData.m_properties.m_roughnessBias = lowerBound; + materialData.m_properties.m_roughnessScale = upperBound - lowerBound; + } + else + { + materialData.m_properties.m_roughnessBias = 0.0; + applyProperty(RoughnessFactor, materialData.m_properties.m_roughnessScale); + } + + applyProperty(SpecularF0Map, materialData.m_specularF0Image); + applyFlag(SpecularF0UseTexture, DetailTextureFlags::UseTextureSpecularF0); + applyProperty(SpecularF0Factor, materialData.m_properties.m_specularF0Factor); + + applyProperty(NormalMap, materialData.m_normalImage); + applyFlag(NormalUseTexture, DetailTextureFlags::UseTextureNormal); + applyProperty(NormalFactor, materialData.m_properties.m_normalFactor); + applyFlag(NormalFlipX, DetailTextureFlags::FlipNormalX); + applyFlag(NormalFlipY, DetailTextureFlags::FlipNormalY); + + applyProperty(DiffuseOcclusionMap, materialData.m_occlusionImage); + applyFlag(DiffuseOcclusionUseTexture, DetailTextureFlags::UseTextureOcclusion); + applyProperty(DiffuseOcclusionFactor, materialData.m_properties.m_occlusionFactor); + + applyProperty(HeightMap, materialData.m_heightImage); + applyFlag(HeightUseTexture, DetailTextureFlags::UseTextureHeight); + applyProperty(HeightFactor, materialData.m_properties.m_heightFactor); + applyProperty(HeightOffset, materialData.m_properties.m_heightOffset); + applyProperty(HeightBlendFactor, materialData.m_properties.m_heightBlendFactor); + + } + } + + void TerrainFeatureProcessor::CheckUpdateDetailTexture(const Aabb2i& newBounds, const Vector2i& newCenter) + { + if (!m_detailTextureImage) + { + // If the m_detailTextureImage doesn't exist, create it and populate the entire texture + + const AZ::Data::Instance imagePool = AZ::RPI::ImageSystemInterface::Get()->GetSystemAttachmentPool(); + AZ::RHI::ImageDescriptor imageDescriptor = AZ::RHI::ImageDescriptor::Create2D( + AZ::RHI::ImageBindFlags::ShaderRead, DetailTextureSize, DetailTextureSize, AZ::RHI::Format::R8G8B8A8_UINT + ); + const AZ::Name TerrainDetailName = AZ::Name(TerrainDetailChars); + m_detailTextureImage = AZ::RPI::AttachmentImage::Create(*imagePool.get(), imageDescriptor, TerrainDetailName, nullptr, nullptr); + AZ_Error(TerrainFPName, m_detailTextureImage, "Failed to initialize the detail texture image."); + + UpdateDetailTexture(newBounds, newBounds, newCenter); + } + else + { + // If the new bounds of the detail texture are different than the old bounds, then the edges of the texture need to be updated. + + int32_t offsetX = m_detailTextureBounds.m_min.m_x - newBounds.m_min.m_x; + + // Horizontal edge update + if (newBounds.m_min.m_x != m_detailTextureBounds.m_min.m_x) + { + Aabb2i updateBounds; + if (newBounds.m_min.m_x < m_detailTextureBounds.m_min.m_x) + { + updateBounds.m_min.m_x = newBounds.m_min.m_x; + updateBounds.m_max.m_x = m_detailTextureBounds.m_min.m_x; + } + else + { + updateBounds.m_min.m_x = m_detailTextureBounds.m_max.m_x; + updateBounds.m_max.m_x = newBounds.m_max.m_x; + } + updateBounds.m_min.m_y = newBounds.m_min.m_y; + updateBounds.m_max.m_y = newBounds.m_max.m_y; + UpdateDetailTexture(updateBounds, newBounds, newCenter); + } + + // Vertical edge update + if (newBounds.m_min.m_y != m_detailTextureBounds.m_min.m_y) + { + Aabb2i updateBounds; + // Don't update areas that have already been updated in the horizontal update. + updateBounds.m_min.m_x = newBounds.m_min.m_x + AZ::GetMax(0, offsetX); + updateBounds.m_max.m_x = newBounds.m_max.m_x + AZ::GetMin(0, offsetX); + if (newBounds.m_min.m_y < m_detailTextureBounds.m_min.m_y) + { + updateBounds.m_min.m_y = newBounds.m_min.m_y; + updateBounds.m_max.m_y = m_detailTextureBounds.m_min.m_y; + } + else + { + updateBounds.m_min.m_y = m_detailTextureBounds.m_max.m_y; + updateBounds.m_max.m_y = newBounds.m_max.m_y; + } + UpdateDetailTexture(updateBounds, newBounds, newCenter); + } + + if (m_dirtyDetailRegion.IsValid()) + { + // If any regions are marked as dirty, then they should be updated. + + AZ::Vector3 currentMin = AZ::Vector3(newBounds.m_min.m_x * DetailTextureScale, newBounds.m_min.m_y * DetailTextureScale, -0.5f); + AZ::Vector3 currentMax = AZ::Vector3(newBounds.m_max.m_x * DetailTextureScale, newBounds.m_max.m_y * DetailTextureScale, 0.5f); + AZ::Aabb detailTextureCoverage = AZ::Aabb::CreateFromMinMax(currentMin, currentMax); + AZ::Vector3 previousMin = AZ::Vector3(m_detailTextureBounds.m_min.m_x * DetailTextureScale, m_detailTextureBounds.m_min.m_y * DetailTextureScale, -0.5f); + AZ::Vector3 previousMax = AZ::Vector3(m_detailTextureBounds.m_max.m_x * DetailTextureScale, m_detailTextureBounds.m_max.m_y * DetailTextureScale, 0.5f); + AZ::Aabb previousCoverage = AZ::Aabb::CreateFromMinMax(previousMin, previousMax); + + // Area of texture not already updated by camera movement above. + AZ::Aabb clampedCoverage = previousCoverage.GetClamped(detailTextureCoverage); + + // Clamp the dirty region to the area of the detail texture that is visible and not already updated. + clampedCoverage.Clamp(m_dirtyDetailRegion); + + if (clampedCoverage.IsValid()) + { + Aabb2i updateBounds; + updateBounds.m_min.m_x = aznumeric_cast(AZStd::roundf(clampedCoverage.GetMin().GetX() / DetailTextureScale)); + updateBounds.m_min.m_y = aznumeric_cast(AZStd::roundf(clampedCoverage.GetMin().GetY() / DetailTextureScale)); + updateBounds.m_max.m_x = aznumeric_cast(AZStd::roundf(clampedCoverage.GetMax().GetX() / DetailTextureScale)); + updateBounds.m_max.m_y = aznumeric_cast(AZStd::roundf(clampedCoverage.GetMax().GetY() / DetailTextureScale)); + if (updateBounds.m_min.m_x < updateBounds.m_max.m_x && updateBounds.m_min.m_y < updateBounds.m_max.m_y) + { + UpdateDetailTexture(updateBounds, newBounds, newCenter); + } + } + } + } + + } + + uint8_t TerrainFeatureProcessor::CalculateUpdateRegions(const Aabb2i& updateArea, const Aabb2i& textureBounds, const Vector2i& centerPixel, + AZStd::array& textureSpaceAreas, AZStd::array& scaledWorldSpaceAreas) + { + Vector2i centerOffset = { centerPixel.m_x - DetailTextureSizeHalf, centerPixel.m_y - DetailTextureSizeHalf }; + + int32_t quadrantXOffset = centerPixel.m_x < DetailTextureSizeHalf ? DetailTextureSize : -DetailTextureSize; + int32_t quadrantYOffset = centerPixel.m_y < DetailTextureSizeHalf ? DetailTextureSize : -DetailTextureSize; + + uint8_t numQuadrants = 0; + + // For each of the 4 quadrants: + auto calculateQuadrant = [&](Vector2i quadrantOffset) + { + Aabb2i offsetUpdateArea = updateArea + centerOffset + quadrantOffset; + Aabb2i updateSectionBounds = textureBounds.GetClamped(offsetUpdateArea); + if (updateSectionBounds.IsValid()) + { + textureSpaceAreas[numQuadrants] = updateSectionBounds - textureBounds.m_min; + scaledWorldSpaceAreas[numQuadrants] = updateSectionBounds - centerOffset - quadrantOffset; + ++numQuadrants; + } + }; + + calculateQuadrant({ 0, 0 }); + calculateQuadrant({ quadrantXOffset, 0 }); + calculateQuadrant({ 0, quadrantYOffset }); + calculateQuadrant({ quadrantXOffset, quadrantYOffset }); + + return numQuadrants; + } + + void TerrainFeatureProcessor::UpdateDetailTexture(const Aabb2i& updateArea, const Aabb2i& textureBounds, const Vector2i& centerPixel) + { + if (!m_detailTextureImage) + { + return; + } + + struct DetailMaterialPixel + { + uint8_t m_material1{ 255 }; + uint8_t m_material2{ 255 }; + uint8_t m_blend{ 0 }; // 0 = full weight on material1, 255 = full weight on material2 + uint8_t m_padding{ 0 }; + }; + + // Because the center of the detail texture may be offset, each update area may actually need to be split into + // up to 4 separate update areas in each sector of the quadrant. + AZStd::array textureSpaceAreas; + AZStd::array scaledWorldSpaceAreas; + uint8_t updateAreaCount = CalculateUpdateRegions(updateArea, textureBounds, centerPixel, textureSpaceAreas, scaledWorldSpaceAreas); + + // Pull the data for each area updated and use it to construct an update for the detail material id texture. + for (uint8_t i = 0; i < updateAreaCount; ++i) + { + const Aabb2i& quadrantTextureArea = textureSpaceAreas[i]; + const Aabb2i& quadrantWorldArea = scaledWorldSpaceAreas[i]; + + AZStd::vector pixels; + pixels.resize((quadrantWorldArea.m_max.m_x - quadrantWorldArea.m_min.m_x) * (quadrantWorldArea.m_max.m_y - quadrantWorldArea.m_min.m_y)); + uint32_t index = 0; + + for (int yPos = quadrantWorldArea.m_min.m_y; yPos < quadrantWorldArea.m_max.m_y; ++yPos) + { + for (int xPos = quadrantWorldArea.m_min.m_x; xPos < quadrantWorldArea.m_max.m_x; ++xPos) + { + AZ::Vector2 position = AZ::Vector2(xPos * DetailTextureScale, yPos * DetailTextureScale); + AzFramework::SurfaceData::SurfaceTagWeightList surfaceWeights; + AzFramework::Terrain::TerrainDataRequestBus::Broadcast(&AzFramework::Terrain::TerrainDataRequests::GetSurfaceWeightsFromVector2, position, surfaceWeights, AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT, nullptr); + + // Store the top two surface weights in the texture with m_blend storing the relative weight. + bool isFirstMaterial = true; + float firstWeight = 0.0f; + for (const auto& surfaceTagWeight : surfaceWeights) + { + if (surfaceTagWeight.m_weight > 0.0f) + { + AZ::Crc32 surfaceType = surfaceTagWeight.m_surfaceType; + uint16_t materialId = GetDetailMaterialForSurfaceTypeAndPosition(surfaceType, position); + if (materialId != m_detailMaterials.NoFreeSlot && materialId < 255) + { + if (isFirstMaterial) + { + pixels.at(index).m_material1 = aznumeric_cast(materialId); + firstWeight = surfaceTagWeight.m_weight; + // m_blend only needs to be calculated is material 2 is found, otherwise the initial value of 0 is correct. + isFirstMaterial = false; + } + else + { + pixels.at(index).m_material2 = aznumeric_cast(materialId); + float totalWeight = firstWeight + surfaceTagWeight.m_weight; + float blendWeight = 1.0f - (firstWeight / totalWeight); + pixels.at(index).m_blend = aznumeric_cast(AZStd::round(blendWeight * 255.0f)); + break; + } + } + } + else + { + break; // since the list is ordered, no other materials are in the list with positive weights. + } + } + ++index; + } + } + + const int32_t left = quadrantTextureArea.m_min.m_x; + const int32_t top = quadrantTextureArea.m_min.m_y; + const int32_t width = quadrantTextureArea.m_max.m_x - quadrantTextureArea.m_min.m_x; + const int32_t height = quadrantTextureArea.m_max.m_y - quadrantTextureArea.m_min.m_y; + + AZ::RHI::ImageUpdateRequest imageUpdateRequest; + imageUpdateRequest.m_imageSubresourcePixelOffset.m_left = aznumeric_cast(left); + imageUpdateRequest.m_imageSubresourcePixelOffset.m_top = aznumeric_cast(top); + imageUpdateRequest.m_sourceSubresourceLayout.m_bytesPerRow = width * sizeof(DetailMaterialPixel); + imageUpdateRequest.m_sourceSubresourceLayout.m_bytesPerImage = width * height * sizeof(DetailMaterialPixel); + imageUpdateRequest.m_sourceSubresourceLayout.m_rowCount = height; + imageUpdateRequest.m_sourceSubresourceLayout.m_size.m_width = width; + imageUpdateRequest.m_sourceSubresourceLayout.m_size.m_height = height; + imageUpdateRequest.m_sourceSubresourceLayout.m_size.m_depth = 1; + imageUpdateRequest.m_sourceData = pixels.data(); + imageUpdateRequest.m_image = m_detailTextureImage->GetRHIImage(); + + m_detailTextureImage->UpdateImageContents(imageUpdateRequest); + } + } + + uint16_t TerrainFeatureProcessor::GetDetailMaterialForSurfaceTypeAndPosition(AZ::Crc32 surfaceType, const AZ::Vector2& position) + { + for (const auto& materialRegion : m_detailMaterialRegions.GetDataVector()) + { + if (materialRegion.m_region.Contains(AZ::Vector3(position.GetX(), position.GetY(), 0.0f))) + { + for (const auto& materialSurface : materialRegion.m_materialsForSurfaces) + { + if (materialSurface.m_surfaceTag == surfaceType) + { + return materialSurface.m_detailMaterialId; + } + } + } + } + return m_detailMaterials.NoFreeSlot; } void TerrainFeatureProcessor::UpdateTerrainData() { - static const AZ::Name TerrainHeightmapName = AZ::Name(TerrainHeightmapChars); - uint32_t width = m_areaData.m_updateWidth; - uint32_t height = m_areaData.m_updateHeight; - const AZ::Aabb& worldBounds = m_areaData.m_terrainBounds; const float queryResolution = m_areaData.m_sampleSpacing; + const AZ::Aabb& worldBounds = m_areaData.m_terrainBounds; - const AZ::RHI::Size worldSize = AZ::RHI::Size(m_areaData.m_heightmapImageWidth, m_areaData.m_heightmapImageHeight, 1); + int32_t heightmapImageXStart = aznumeric_cast(AZStd::ceilf(worldBounds.GetMin().GetX() / queryResolution)); + int32_t heightmapImageXEnd = aznumeric_cast(AZStd::floorf(worldBounds.GetMax().GetX() / queryResolution)) + 1; + int32_t heightmapImageYStart = aznumeric_cast(AZStd::ceilf(worldBounds.GetMin().GetY() / queryResolution)); + int32_t heightmapImageYEnd = aznumeric_cast(AZStd::floorf(worldBounds.GetMax().GetY() / queryResolution)) + 1; + uint32_t heightmapImageWidth = heightmapImageXEnd - heightmapImageXStart; + uint32_t heightmapImageHeight = heightmapImageYEnd - heightmapImageYStart; - if (!m_areaData.m_heightmapImage || m_areaData.m_heightmapImage->GetDescriptor().m_size != worldSize) + const AZ::RHI::Size heightmapSize = AZ::RHI::Size(heightmapImageWidth, heightmapImageHeight, 1); + + if (!m_areaData.m_heightmapImage || m_areaData.m_heightmapImage->GetDescriptor().m_size != heightmapSize) { - // World size changed, so the whole world needs updating. - width = worldSize.m_width; - height = worldSize.m_height; - m_dirtyRegion = worldBounds; - const AZ::Data::Instance imagePool = AZ::RPI::ImageSystemInterface::Get()->GetSystemAttachmentPool(); AZ::RHI::ImageDescriptor imageDescriptor = AZ::RHI::ImageDescriptor::Create2D( - AZ::RHI::ImageBindFlags::ShaderRead, width, height, AZ::RHI::Format::R16_UNORM + AZ::RHI::ImageBindFlags::ShaderRead, heightmapSize.m_width, heightmapSize.m_height, AZ::RHI::Format::R16_UNORM ); + + const AZ::Name TerrainHeightmapName = AZ::Name(TerrainHeightmapChars); m_areaData.m_heightmapImage = AZ::RPI::AttachmentImage::Create(*imagePool.get(), imageDescriptor, TerrainHeightmapName, nullptr, nullptr); - AZ_Error(TerrainFPName, m_areaData.m_heightmapImage, "Failed to initialize the heightmap image!"); + AZ_Error(TerrainFPName, m_areaData.m_heightmapImage, "Failed to initialize the heightmap image."); + + // World size changed, so the whole height map needs updating. + m_dirtyRegion = worldBounds; } + + int32_t xStart = aznumeric_cast(AZStd::ceilf(m_dirtyRegion.GetMin().GetX() / queryResolution)); + int32_t xEnd = aznumeric_cast(AZStd::floorf(m_dirtyRegion.GetMax().GetX() / queryResolution)) + 1; + int32_t yStart = aznumeric_cast(AZStd::ceilf(m_dirtyRegion.GetMin().GetY() / queryResolution)); + int32_t yEnd = aznumeric_cast(AZStd::floorf(m_dirtyRegion.GetMax().GetY() / queryResolution)) + 1; + uint32_t updateWidth = xEnd - xStart; + uint32_t updateHeight = yEnd - yStart; AZStd::vector pixels; - pixels.reserve(width * height); + pixels.reserve(updateWidth * updateHeight); { // Block other threads from accessing the surface data bus while we are in GetHeightFromFloats (which may call into the SurfaceData bus). @@ -310,18 +823,17 @@ namespace Terrain auto& surfaceDataContext = SurfaceData::SurfaceDataSystemRequestBus::GetOrCreateContext(false); typename SurfaceData::SurfaceDataSystemRequestBus::Context::DispatchLockGuard scopeLock(surfaceDataContext.m_contextMutex); - for (uint32_t y = 0; y < height; y++) + for (int32_t y = yStart; y < yEnd; y++) { - for (uint32_t x = 0; x < width; x++) + for (int32_t x = xStart; x < xEnd; x++) { bool terrainExists = true; float terrainHeight = 0.0f; + float xPos = x * queryResolution; + float yPos = y * queryResolution; AzFramework::Terrain::TerrainDataRequestBus::BroadcastResult( terrainHeight, &AzFramework::Terrain::TerrainDataRequests::GetHeightFromFloats, - (x * queryResolution) + m_dirtyRegion.GetMin().GetX(), - (y * queryResolution) + m_dirtyRegion.GetMin().GetY(), - AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT, - &terrainExists); + xPos, yPos, AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT, &terrainExists); const float clampedHeight = AZ::GetClamp((terrainHeight - worldBounds.GetMin().GetZ()) / worldBounds.GetExtents().GetZ(), 0.0f, 1.0f); const float expandedHeight = AZStd::roundf(clampedHeight * AZStd::numeric_limits::max()); @@ -334,16 +846,18 @@ namespace Terrain if (m_areaData.m_heightmapImage) { - const float left = (m_dirtyRegion.GetMin().GetX() - worldBounds.GetMin().GetX()) / queryResolution; - const float top = (m_dirtyRegion.GetMin().GetY() - worldBounds.GetMin().GetY()) / queryResolution; + constexpr uint32_t BytesPerPixel = sizeof(uint16_t); + const float left = xStart - (worldBounds.GetMin().GetX() / queryResolution); + const float top = yStart - (worldBounds.GetMin().GetY() / queryResolution); + AZ::RHI::ImageUpdateRequest imageUpdateRequest; imageUpdateRequest.m_imageSubresourcePixelOffset.m_left = aznumeric_cast(left); imageUpdateRequest.m_imageSubresourcePixelOffset.m_top = aznumeric_cast(top); - imageUpdateRequest.m_sourceSubresourceLayout.m_bytesPerRow = width * sizeof(uint16_t); - imageUpdateRequest.m_sourceSubresourceLayout.m_bytesPerImage = width * height * sizeof(uint16_t); - imageUpdateRequest.m_sourceSubresourceLayout.m_rowCount = height; - imageUpdateRequest.m_sourceSubresourceLayout.m_size.m_width = width; - imageUpdateRequest.m_sourceSubresourceLayout.m_size.m_height = height; + imageUpdateRequest.m_sourceSubresourceLayout.m_bytesPerRow = updateWidth * BytesPerPixel; + imageUpdateRequest.m_sourceSubresourceLayout.m_bytesPerImage = updateWidth * updateHeight * BytesPerPixel; + imageUpdateRequest.m_sourceSubresourceLayout.m_rowCount = updateHeight; + imageUpdateRequest.m_sourceSubresourceLayout.m_size.m_width = updateWidth; + imageUpdateRequest.m_sourceSubresourceLayout.m_size.m_height = updateHeight; imageUpdateRequest.m_sourceSubresourceLayout.m_size.m_depth = 1; imageUpdateRequest.m_sourceData = pixels.data(); imageUpdateRequest.m_image = m_areaData.m_heightmapImage->GetRHIImage(); @@ -379,45 +893,58 @@ namespace Terrain m_heightmapPropertyIndex = m_materialInstance->GetMaterialPropertiesLayout()->FindPropertyIndex(AZ::Name(MaterialInputs::HeightmapImage)); AZ_Error(TerrainFPName, m_heightmapPropertyIndex.IsValid(), "Failed to find material input constant %s.", MaterialInputs::HeightmapImage); + m_detailMaterialIdPropertyIndex = m_materialInstance->GetMaterialPropertiesLayout()->FindPropertyIndex(AZ::Name(MaterialInputs::DetailMaterialIdImage)); + AZ_Error(TerrainFPName, m_detailMaterialIdPropertyIndex.IsValid(), "Failed to find material input constant %s.", MaterialInputs::DetailMaterialIdImage); + + m_detailCenterPropertyIndex = m_materialInstance->GetMaterialPropertiesLayout()->FindPropertyIndex(AZ::Name(MaterialInputs::DetailCenter)); + AZ_Error(TerrainFPName, m_detailCenterPropertyIndex.IsValid(), "Failed to find material input constant %s.", MaterialInputs::DetailCenter); + + m_detailAabbPropertyIndex = m_materialInstance->GetMaterialPropertiesLayout()->FindPropertyIndex(AZ::Name(MaterialInputs::DetailAabb)); + AZ_Error(TerrainFPName, m_detailAabbPropertyIndex.IsValid(), "Failed to find material input constant %s.", MaterialInputs::DetailAabb); + + m_detailHalfPixelUvPropertyIndex = m_materialInstance->GetMaterialPropertiesLayout()->FindPropertyIndex(AZ::Name(MaterialInputs::DetailHalfPixelUv)); + AZ_Error(TerrainFPName, m_detailHalfPixelUvPropertyIndex.IsValid(), "Failed to find material input constant %s.", MaterialInputs::DetailHalfPixelUv); + + // Find any macro materials that have already been created. TerrainMacroMaterialRequestBus::EnumerateHandlers( [&](TerrainMacroMaterialRequests* handler) { - MaterialInstance macroMaterial; - AZ::Aabb bounds; - handler->GetTerrainMacroMaterialData(macroMaterial, bounds); + MacroMaterialData macroMaterial = handler->GetTerrainMacroMaterialData(); AZ::EntityId entityId = *(Terrain::TerrainMacroMaterialRequestBus::GetCurrentBusId()); - OnTerrainMacroMaterialCreated(entityId, macroMaterial, bounds); + OnTerrainMacroMaterialCreated(entityId, macroMaterial); return true; } ); TerrainMacroMaterialNotificationBus::Handler::BusConnect(); + + // Find any detail material areas that have already been created. + TerrainAreaMaterialRequestBus::EnumerateHandlers( + [&](TerrainAreaMaterialRequests* handler) + { + const AZ::Aabb& bounds = handler->GetTerrainSurfaceMaterialRegion(); + const AZStd::vector materialMappings = handler->GetSurfaceMaterialMappings(); + AZ::EntityId entityId = *(Terrain::TerrainAreaMaterialRequestBus::GetCurrentBusId()); + + DetailMaterialListRegion& materialRegion = FindOrCreateByEntityId(entityId, m_detailMaterialRegions); + materialRegion.m_region = bounds; + + for (const auto& materialMapping : materialMappings) + { + if (materialMapping.m_materialInstance) + { + OnTerrainSurfaceMaterialMappingCreated(entityId, materialMapping.m_surfaceTag, materialMapping.m_materialInstance); + } + } + return true; + } + ); + TerrainAreaMaterialNotificationBus::Handler::BusConnect(); + } - void TerrainFeatureProcessor::UpdateMacroMaterialData(MacroMaterialData& macroMaterialData, MaterialInstance material) + void TerrainFeatureProcessor::UpdateMacroMaterialData(MacroMaterialData& macroMaterialData, const MacroMaterialData& newMaterialData) { - // Since we're using an actual macro material instance for now, get the values from it that we care about. - const auto materialLayout = material->GetMaterialPropertiesLayout(); - - const AZ::RPI::MaterialPropertyIndex macroColorTextureMapIndex = materialLayout->FindPropertyIndex(AZ::Name(MaterialInputs::MacroColorTextureMap)); - AZ_Error(TerrainFPName, macroColorTextureMapIndex.IsValid(), "Failed to find shader input constant %s.", MaterialInputs::MacroColorTextureMap); - - const AZ::RPI::MaterialPropertyIndex macroNormalTextureMapIndex = materialLayout->FindPropertyIndex(AZ::Name(MaterialInputs::MacroNormalTextureMap)); - AZ_Error(TerrainFPName, macroNormalTextureMapIndex.IsValid(), "Failed to find shader input constant %s.", MaterialInputs::MacroNormalTextureMap); - - const AZ::RPI::MaterialPropertyIndex macroNormalFlipXIndex = materialLayout->FindPropertyIndex(AZ::Name(MaterialInputs::MacroNormalFlipX)); - AZ_Error(TerrainFPName, macroNormalFlipXIndex.IsValid(), "Failed to find shader input constant %s.", MaterialInputs::MacroNormalFlipX); - - const AZ::RPI::MaterialPropertyIndex macroNormalFlipYIndex = materialLayout->FindPropertyIndex(AZ::Name(MaterialInputs::MacroNormalFlipY)); - AZ_Error(TerrainFPName, macroNormalFlipYIndex.IsValid(), "Failed to find shader input constant %s.", MaterialInputs::MacroNormalFlipY); - - const AZ::RPI::MaterialPropertyIndex macroNormalFactorIndex = materialLayout->FindPropertyIndex(AZ::Name(MaterialInputs::MacroNormalFactor)); - AZ_Error(TerrainFPName, macroNormalFactorIndex.IsValid(), "Failed to find shader input constant %s.", MaterialInputs::MacroNormalFactor); - - macroMaterialData.m_colorImage = material->GetPropertyValue(macroColorTextureMapIndex).GetValue>(); - macroMaterialData.m_normalImage = material->GetPropertyValue(macroNormalTextureMapIndex).GetValue>(); - macroMaterialData.m_normalFlipX = material->GetPropertyValue(macroNormalFlipXIndex).GetValue(); - macroMaterialData.m_normalFlipY = material->GetPropertyValue(macroNormalFlipYIndex).GetValue(); - macroMaterialData.m_normalFactor = material->GetPropertyValue(macroNormalFactorIndex).GetValue(); + macroMaterialData = newMaterialData; if (macroMaterialData.m_bounds.IsValid()) { @@ -511,14 +1038,73 @@ namespace Terrain } } } + else if (m_forceRebuildDrawPackets) + { + for (auto& sectorData : m_sectorData) + { + for (auto& drawPacket : sectorData.m_drawPackets) + { + drawPacket.Update(*GetParentScene(), true); + } + } + } + m_forceRebuildDrawPackets = false; if (m_areaData.m_heightmapUpdated) { UpdateTerrainData(); - - const AZ::Data::Instance heightmapImage = m_areaData.m_heightmapImage; + + const AZ::Data::Instance heightmapImage = m_areaData.m_heightmapImage; // cast StreamingImage to Image m_materialInstance->SetPropertyValue(m_heightmapPropertyIndex, heightmapImage); - m_materialInstance->Compile(); + } + + AZ::Vector3 cameraPosition = AZ::Vector3::CreateZero(); + for (auto& view : process.m_views) + { + if ((view->GetUsageFlags() & AZ::RPI::View::UsageFlags::UsageCamera) > 0) + { + cameraPosition = view->GetCameraTransform().GetTranslation(); + break; + } + } + + if (m_dirtyDetailRegion.IsValid() || !cameraPosition.IsClose(m_previousCameraPosition)) + { + int32_t newDetailTexturePosX = aznumeric_cast(AZStd::roundf(cameraPosition.GetX() / DetailTextureScale)); + int32_t newDetailTexturePosY = aznumeric_cast(AZStd::roundf(cameraPosition.GetY() / DetailTextureScale)); + + Aabb2i newBounds; + newBounds.m_min.m_x = newDetailTexturePosX - DetailTextureSizeHalf; + newBounds.m_min.m_y = newDetailTexturePosY - DetailTextureSizeHalf; + newBounds.m_max.m_x = newDetailTexturePosX + DetailTextureSizeHalf; + newBounds.m_max.m_y = newDetailTexturePosY + DetailTextureSizeHalf; + + // Use modulo to find the center point in texture space. Care must be taken so negative values are + // handled appropriately (ie, we want -1 % 1024 to equal 1023, not -1) + Vector2i newCenter; + newCenter.m_x = (DetailTextureSize + (newDetailTexturePosX % DetailTextureSize)) % DetailTextureSize; + newCenter.m_y = (DetailTextureSize + (newDetailTexturePosY % DetailTextureSize)) % DetailTextureSize; + + CheckUpdateDetailTexture(newBounds, newCenter); + + m_detailTextureBounds = newBounds; + m_dirtyDetailRegion = AZ::Aabb::CreateNull(); + + m_previousCameraPosition = cameraPosition; + const AZ::Data::Instance detailTextureImage = m_detailTextureImage; // cast StreamingImage to Image + m_materialInstance->SetPropertyValue(m_detailMaterialIdPropertyIndex, detailTextureImage); + + AZ::Vector4 detailAabb = AZ::Vector4( + m_detailTextureBounds.m_min.m_x * DetailTextureScale, + m_detailTextureBounds.m_min.m_y * DetailTextureScale, + m_detailTextureBounds.m_max.m_x * DetailTextureScale, + m_detailTextureBounds.m_max.m_y * DetailTextureScale + ); + m_materialInstance->SetPropertyValue(m_detailAabbPropertyIndex, detailAabb); + m_materialInstance->SetPropertyValue(m_detailHalfPixelUvPropertyIndex, 0.5f / DetailTextureSize); + + AZ::Vector2 detailUvOffset = AZ::Vector2(float(newCenter.m_x) / DetailTextureSize, float(newCenter.m_y) / DetailTextureSize); + m_materialInstance->SetPropertyValue(m_detailCenterPropertyIndex, detailUvOffset); } if (m_areaData.m_heightmapUpdated || m_areaData.m_macroMaterialsUpdated) @@ -529,6 +1115,12 @@ namespace Terrain m_areaData.m_heightmapUpdated = false; m_areaData.m_macroMaterialsUpdated = false; + AZStd::array uvStep = + { + 1.0f / aznumeric_cast(m_areaData.m_terrainBounds.GetXExtent() / m_areaData.m_sampleSpacing), + 1.0f / aznumeric_cast(m_areaData.m_terrainBounds.GetYExtent() / m_areaData.m_sampleSpacing), + }; + for (SectorData& sectorData : m_sectorData) { ShaderTerrainData terrainDataForSrg; @@ -546,11 +1138,7 @@ namespace Terrain ((yPatch + GridMeters) - terrainBounds.GetMin().GetY()) / terrainBounds.GetYExtent() }; - terrainDataForSrg.m_uvStep = - { - 1.0f / m_areaData.m_heightmapImageWidth, - 1.0f / m_areaData.m_heightmapImageHeight, - }; + terrainDataForSrg.m_uvStep = uvStep; AZ::Transform transform = m_areaData.m_transform; transform.SetTranslation(xPatch, yPatch, m_areaData.m_transform.GetTranslation().GetZ()); @@ -561,7 +1149,9 @@ namespace Terrain sectorData.m_srg->SetConstant(m_terrainDataIndex, terrainDataForSrg); AZStd::array macroMaterialData; - for (uint32_t i = 0; i < sectorData.m_macroMaterials.size(); ++i) + + uint32_t i = 0; + for (; i < sectorData.m_macroMaterials.size(); ++i) { const MacroMaterialData& materialData = m_macroMaterials.GetData(sectorData.m_macroMaterials.at(i)); ShaderMacroMaterialData& shaderData = macroMaterialData.at(i); @@ -590,6 +1180,11 @@ namespace Terrain // set flags for which images are used. shaderData.m_mapsInUse = (colorImageView ? ColorImageUsed : 0) | (normalImageView ? NormalImageUsed : 0); } + for (; i < sectorData.m_macroMaterials.capacity(); ++i) + { + sectorData.m_srg->SetImageView(m_macroColorMapIndex, nullptr, i); + sectorData.m_srg->SetImageView(m_macroNormalMapIndex, nullptr, i); + } sectorData.m_srg->SetConstantArray(m_macroMaterialDataIndex, macroMaterialData); sectorData.m_srg->SetConstant(m_macroMaterialCountIndex, aznumeric_cast(sectorData.m_macroMaterials.size())); @@ -640,6 +1235,11 @@ namespace Terrain } } } + + if (m_materialInstance) + { + m_materialInstance->Compile(); + } } void TerrainFeatureProcessor::InitializeTerrainPatch(uint16_t gridSize, float gridSpacing, PatchData& patchdata) @@ -783,9 +1383,10 @@ namespace Terrain // larger but this will limit how much is rendered. } - TerrainFeatureProcessor::MacroMaterialData* TerrainFeatureProcessor::FindMacroMaterial(AZ::EntityId entityId) + template + T* TerrainFeatureProcessor::FindByEntityId(AZ::EntityId entityId, AZ::Render::IndexedDataVector& container) { - for (MacroMaterialData& data : m_macroMaterials.GetDataVector()) + for (T& data : container.GetDataVector()) { if (data.m_entityId == entityId) { @@ -794,34 +1395,36 @@ namespace Terrain } return nullptr; } - - TerrainFeatureProcessor::MacroMaterialData& TerrainFeatureProcessor::FindOrCreateMacroMaterial(AZ::EntityId entityId) + + template + T& TerrainFeatureProcessor::FindOrCreateByEntityId(AZ::EntityId entityId, AZ::Render::IndexedDataVector& container) { - MacroMaterialData* dataPtr = FindMacroMaterial(entityId); + T* dataPtr = FindByEntityId(entityId, container); if (dataPtr != nullptr) { return *dataPtr; } - const uint16_t slotId = m_macroMaterials.GetFreeSlotIndex(); - AZ_Assert(slotId != m_macroMaterials.NoFreeSlot, "Ran out of indices for macro materials"); + const uint16_t slotId = container.GetFreeSlotIndex(); + AZ_Assert(slotId != AZ::Render::IndexedDataVector::NoFreeSlot, "Ran out of indices"); - MacroMaterialData& data = m_macroMaterials.GetData(slotId); + T& data = container.GetData(slotId); data.m_entityId = entityId; return data; } - - void TerrainFeatureProcessor::RemoveMacroMaterial(AZ::EntityId entityId) + + template + void TerrainFeatureProcessor::RemoveByEntityId(AZ::EntityId entityId, AZ::Render::IndexedDataVector& container) { - for (MacroMaterialData& data : m_macroMaterials.GetDataVector()) + for (T& data : container.GetDataVector()) { if (data.m_entityId == entityId) { - m_macroMaterials.RemoveData(&data); + container.RemoveData(&data); return; } } - AZ_Assert(false, "Entity Id not found in m_macroMaterials.") + AZ_Assert(false, "Entity Id not found in container.") } template @@ -835,4 +1438,60 @@ namespace Terrain } } } + + auto TerrainFeatureProcessor::Vector2i::operator+(const Vector2i& rhs) const -> Vector2i + { + Vector2i offsetPoint = *this; + offsetPoint += rhs; + return offsetPoint; + } + + auto TerrainFeatureProcessor::Vector2i::operator+=(const Vector2i& rhs) -> Vector2i& + { + m_x += rhs.m_x; + m_y += rhs.m_y; + return *this; + } + + auto TerrainFeatureProcessor::Vector2i::operator-(const Vector2i& rhs) const -> Vector2i + { + return *this + -rhs; + } + + auto TerrainFeatureProcessor::Vector2i::operator-=(const Vector2i& rhs) -> Vector2i& + { + return *this += -rhs; + } + + auto TerrainFeatureProcessor::Vector2i::operator-() const -> Vector2i + { + return {-m_x, -m_y}; + } + + auto TerrainFeatureProcessor::Aabb2i::operator+(const Vector2i& rhs) const -> Aabb2i + { + return { m_min + rhs, m_max + rhs }; + } + + auto TerrainFeatureProcessor::Aabb2i::operator-(const Vector2i& rhs) const -> Aabb2i + { + return *this + -rhs; + } + + auto TerrainFeatureProcessor::Aabb2i::GetClamped(Aabb2i rhs) const -> Aabb2i + { + Aabb2i ret; + ret.m_min.m_x = AZ::GetMax(m_min.m_x, rhs.m_min.m_x); + ret.m_min.m_y = AZ::GetMax(m_min.m_y, rhs.m_min.m_y); + ret.m_max.m_x = AZ::GetMin(m_max.m_x, rhs.m_max.m_x); + ret.m_max.m_y = AZ::GetMin(m_max.m_y, rhs.m_max.m_y); + return ret; + } + + bool TerrainFeatureProcessor::Aabb2i::IsValid() const + { + // Intentionally strict, equal min/max not valid. + return m_min.m_x < m_max.m_x && m_min.m_y < m_max.m_y; + } + } diff --git a/Gems/Terrain/Code/Source/TerrainRenderer/TerrainFeatureProcessor.h b/Gems/Terrain/Code/Source/TerrainRenderer/TerrainFeatureProcessor.h index d9f15f2d47..9b66881ec6 100644 --- a/Gems/Terrain/Code/Source/TerrainRenderer/TerrainFeatureProcessor.h +++ b/Gems/Terrain/Code/Source/TerrainRenderer/TerrainFeatureProcessor.h @@ -12,11 +12,13 @@ #include #include +#include #include #include #include #include +#include #include namespace AZ::RPI @@ -37,6 +39,7 @@ namespace Terrain , private AZ::RPI::MaterialReloadNotificationBus::Handler , private AzFramework::Terrain::TerrainDataNotificationBus::Handler , private TerrainMacroMaterialNotificationBus::Handler + , private TerrainAreaMaterialNotificationBus::Handler { public: AZ_RTTI(TerrainFeatureProcessor, "{D7DAC1F9-4A9F-4D3C-80AE-99579BF8AB1C}", AZ::RPI::FeatureProcessor); @@ -68,18 +71,18 @@ namespace Terrain struct ShaderTerrainData // Must align with struct in Object Srg { - AZStd::array m_uvMin; - AZStd::array m_uvMax; - AZStd::array m_uvStep; - float m_sampleSpacing; - float m_heightScale; + AZStd::array m_uvMin{ 0.0f, 0.0f }; + AZStd::array m_uvMax{ 1.0f, 1.0f }; + AZStd::array m_uvStep{ 1.0f, 1.0f }; + float m_sampleSpacing{ 1.0f }; + float m_heightScale{ 1.0f }; }; - struct ShaderMacroMaterialData + struct ShaderMacroMaterialData // Must align with struct in Object Srg { - AZStd::array m_uvMin; - AZStd::array m_uvMax; - float m_normalFactor; + AZStd::array m_uvMin{ 0.0f, 0.0f }; + AZStd::array m_uvMax{ 1.0f, 1.0f }; + float m_normalFactor{ 0.0f }; uint32_t m_flipNormalX{ 0 }; // bool in shader uint32_t m_flipNormalY{ 0 }; // bool in shader uint32_t m_mapsInUse{ 0b00 }; // 0b01 = color, 0b10 = normal @@ -112,16 +115,107 @@ namespace Terrain AZStd::fixed_vector m_macroMaterials; }; - struct MacroMaterialData + enum DetailTextureFlags : uint32_t { - AZ::EntityId m_entityId; - AZ::Aabb m_bounds = AZ::Aabb::CreateNull(); + UseTextureBaseColor = 0b0000'0000'0000'0000'0000'0000'0000'0001, + UseTextureNormal = 0b0000'0000'0000'0000'0000'0000'0000'0010, + UseTextureMetallic = 0b0000'0000'0000'0000'0000'0000'0000'0100, + UseTextureRoughness = 0b0000'0000'0000'0000'0000'0000'0000'1000, + UseTextureOcclusion = 0b0000'0000'0000'0000'0000'0000'0001'0000, + UseTextureHeight = 0b0000'0000'0000'0000'0000'0000'0010'0000, + UseTextureSpecularF0 = 0b0000'0000'0000'0000'0000'0000'0100'0000, + + FlipNormalX = 0b0000'0000'0000'0000'0000'0000'1000'0000, + FlipNormalY = 0b0000'0000'0000'0000'0000'0001'0000'0000, + + BlendModeMask = 0b0000'0000'0000'0000'0000'0110'0000'0000, + BlendModeLerp = 0b0000'0000'0000'0000'0000'0000'0000'0000, + BlendModeLinearLight = 0b0000'0000'0000'0000'0000'0010'0000'0000, + BlendModeMultiply = 0b0000'0000'0000'0000'0000'0100'0000'0000, + BlendModeOverlay = 0b0000'0000'0000'0000'0000'0110'0000'0000, + }; + + struct DetailMaterialShaderProperties + { + // Uv + AZStd::array m_uvTransform + { + 1.0, 0.0, 0.0, 0.0, + 0.0, 1.0, 0.0, 0.0, + 0.0, 0.0, 1.0, 0.0, + }; + + // Factor / Scale / Bias for input textures + float m_baseColorFactor{ 1.0f }; + float m_normalFactor{ 1.0f }; + float m_metalFactor{ 1.0f }; + float m_roughnessScale{ 1.0f }; + + float m_roughnessBias{ 0.0f }; + float m_specularF0Factor{ 1.0f }; + float m_occlusionFactor{ 1.0f }; + float m_heightFactor{ 1.0f }; + + float m_heightOffset{ 0.0f }; + float m_heightBlendFactor{ 0.5f }; + + // Flags + DetailTextureFlags m_flags{ 0 }; + + float m_padding; // 16 byte aligned + }; + + struct DetailMaterialData + { + AZ::Data::AssetId m_assetId; + AZ::RPI::Material::ChangeId m_materialChangeId{AZ::RPI::Material::DEFAULT_CHANGE_ID}; AZ::Data::Instance m_colorImage; AZ::Data::Instance m_normalImage; - bool m_normalFlipX{ false }; - bool m_normalFlipY{ false }; - float m_normalFactor{ 0.0f }; + AZ::Data::Instance m_roughnessImage; + AZ::Data::Instance m_metalnessImage; + AZ::Data::Instance m_specularF0Image; + AZ::Data::Instance m_occlusionImage; + AZ::Data::Instance m_heightImage; + + DetailMaterialShaderProperties m_properties; // maps directly to shader + }; + + struct DetailMaterialSurface + { + AZ::Crc32 m_surfaceTag; + uint16_t m_detailMaterialId; + }; + + struct DetailMaterialListRegion + { + AZ::EntityId m_entityId; + AZ::Aabb m_region{AZ::Aabb::CreateNull()}; + AZStd::vector m_materialsForSurfaces; + }; + + struct Vector2i + { + int32_t m_x{ 0 }; + int32_t m_y{ 0 }; + + Vector2i operator+(const Vector2i& rhs) const; + Vector2i& operator+=(const Vector2i& rhs); + Vector2i operator-(const Vector2i& rhs) const; + Vector2i& operator-=(const Vector2i& rhs); + Vector2i operator-() const; + }; + + struct Aabb2i + { + Vector2i m_min; + Vector2i m_max; + + Aabb2i operator+(const Vector2i& offset) const; + Aabb2i operator-(const Vector2i& offset) const; + + Aabb2i GetClamped(Aabb2i rhs) const; + bool IsValid() const; }; // AZ::RPI::MaterialReloadNotificationBus::Handler overrides... @@ -132,10 +226,16 @@ namespace Terrain void OnTerrainDataChanged(const AZ::Aabb& dirtyRegion, TerrainDataChangedMask dataChangedMask) override; // TerrainMacroMaterialNotificationBus overrides... - void OnTerrainMacroMaterialCreated(AZ::EntityId entityId, MaterialInstance material, const AZ::Aabb& region) override; - void OnTerrainMacroMaterialChanged(AZ::EntityId entityId, MaterialInstance material) override; + void OnTerrainMacroMaterialCreated(AZ::EntityId entityId, const MacroMaterialData& material) override; + void OnTerrainMacroMaterialChanged(AZ::EntityId entityId, const MacroMaterialData& material) override; void OnTerrainMacroMaterialRegionChanged(AZ::EntityId entityId, const AZ::Aabb& oldRegion, const AZ::Aabb& newRegion) override; void OnTerrainMacroMaterialDestroyed(AZ::EntityId entityId) override; + + // TerrainAreaMaterialNotificationBus overrides... + void OnTerrainSurfaceMaterialMappingCreated(AZ::EntityId entityId, SurfaceData::SurfaceTag surfaceTag, MaterialInstance material) override; + void OnTerrainSurfaceMaterialMappingDestroyed(AZ::EntityId entityId, SurfaceData::SurfaceTag surfaceTag) override; + void OnTerrainSurfaceMaterialMappingChanged(AZ::EntityId entityId, SurfaceData::SurfaceTag surfaceTag, MaterialInstance material) override; + void OnTerrainSurfaceMaterialMappingRegionChanged(AZ::EntityId entityId, const AZ::Aabb& oldRegion, const AZ::Aabb& newRegion) override; void Initialize(); void InitializeTerrainPatch(uint16_t gridSize, float gridSpacing, PatchData& patchdata); @@ -143,13 +243,27 @@ namespace Terrain void UpdateTerrainData(); void PrepareMaterialData(); - void UpdateMacroMaterialData(MacroMaterialData& macroMaterialData, MaterialInstance material); + void UpdateMacroMaterialData(MacroMaterialData& macroMaterialData, const MacroMaterialData& newMaterialData); + + void TerrainHeightOrSettingsUpdated(const AZ::Aabb& dirtyRegion); + void TerrainSurfaceDataUpdated(const AZ::Aabb& dirtyRegion); + + uint16_t CreateOrUpdateDetailMaterial(MaterialInstance material); + void UpdateDetailMaterialData(DetailMaterialData& materialData, MaterialInstance material); + void CheckUpdateDetailTexture(const Aabb2i& newBounds, const Vector2i& newCenter); + void UpdateDetailTexture(const Aabb2i& updateArea, const Aabb2i& textureBounds, const Vector2i& centerPixel); + uint16_t GetDetailMaterialForSurfaceTypeAndPosition(AZ::Crc32 surfaceType, const AZ::Vector2& position); + uint8_t CalculateUpdateRegions(const Aabb2i& updateArea, const Aabb2i& textureBounds, const Vector2i& centerPixel, + AZStd::array& textureSpaceAreas, AZStd::array& scaledWorldSpaceAreas); void ProcessSurfaces(const FeatureProcessor::RenderPacket& process); - - MacroMaterialData* FindMacroMaterial(AZ::EntityId entityId); - MacroMaterialData& FindOrCreateMacroMaterial(AZ::EntityId entityId); - void RemoveMacroMaterial(AZ::EntityId entityId); + + template + T* FindByEntityId(AZ::EntityId entityId, AZ::Render::IndexedDataVector& container); + template + T& FindOrCreateByEntityId(AZ::EntityId entityId, AZ::Render::IndexedDataVector& container); + template + void RemoveByEntityId(AZ::EntityId entityId, AZ::Render::IndexedDataVector& container); template void ForOverlappingSectors(const AZ::Aabb& bounds, Callback callback); @@ -159,8 +273,11 @@ namespace Terrain // System-level parameters static constexpr float GridSpacing{ 1.0f }; - static constexpr uint32_t GridSize{ 64 }; // number of terrain quads (vertices are m_gridSize + 1) + static constexpr int32_t GridSize{ 64 }; // number of terrain quads (vertices are m_gridSize + 1) static constexpr float GridMeters{ GridSpacing * GridSize }; + static constexpr int32_t DetailTextureSize{ 1024 }; + static constexpr int32_t DetailTextureSizeHalf{ DetailTextureSize / 2 }; + static constexpr float DetailTextureScale{ 0.5f }; AZStd::unique_ptr m_materialAssetLoader; MaterialInstance m_materialInstance; @@ -172,8 +289,13 @@ namespace Terrain AZ::RHI::ShaderInputImageIndex m_macroColorMapIndex; AZ::RHI::ShaderInputImageIndex m_macroNormalMapIndex; AZ::RPI::MaterialPropertyIndex m_heightmapPropertyIndex; + AZ::RPI::MaterialPropertyIndex m_detailMaterialIdPropertyIndex; + AZ::RPI::MaterialPropertyIndex m_detailCenterPropertyIndex; + AZ::RPI::MaterialPropertyIndex m_detailAabbPropertyIndex; + AZ::RPI::MaterialPropertyIndex m_detailHalfPixelUvPropertyIndex; AZ::Data::Instance m_patchModel; + AZ::Vector3 m_previousCameraPosition = AZ::Vector3(AZStd::numeric_limits::max(), 0.0, 0.0); // Per-area data struct TerrainAreaData @@ -181,21 +303,26 @@ namespace Terrain AZ::Transform m_transform{ AZ::Transform::CreateIdentity() }; AZ::Aabb m_terrainBounds{ AZ::Aabb::CreateNull() }; AZ::Data::Instance m_heightmapImage; - uint32_t m_heightmapImageWidth{ 0 }; - uint32_t m_heightmapImageHeight{ 0 }; - uint32_t m_updateWidth{ 0 }; - uint32_t m_updateHeight{ 0 }; float m_sampleSpacing{ 0.0f }; bool m_heightmapUpdated{ true }; bool m_macroMaterialsUpdated{ true }; bool m_rebuildSectors{ true }; }; - + TerrainAreaData m_areaData; AZ::Aabb m_dirtyRegion{ AZ::Aabb::CreateNull() }; + AZ::Aabb m_dirtyDetailRegion{ AZ::Aabb::CreateNull() }; + + Aabb2i m_detailTextureBounds; + Vector2i m_detailTextureCenter; + AZ::Data::Instance m_detailTextureImage; + AZ::RPI::ShaderSystemInterface::GlobalShaderOptionUpdatedEvent::Handler m_handleGlobalShaderOptionUpdate; + bool m_forceRebuildDrawPackets = false; AZStd::vector m_sectorData; AZ::Render::IndexedDataVector m_macroMaterials; + AZ::Render::IndexedDataVector m_detailMaterials; + AZ::Render::IndexedDataVector m_detailMaterialRegions; }; } diff --git a/Gems/Terrain/Code/Source/TerrainRenderer/TerrainMacroMaterialBus.h b/Gems/Terrain/Code/Source/TerrainRenderer/TerrainMacroMaterialBus.h index c0618a7f66..af1e755b32 100644 --- a/Gems/Terrain/Code/Source/TerrainRenderer/TerrainMacroMaterialBus.h +++ b/Gems/Terrain/Code/Source/TerrainRenderer/TerrainMacroMaterialBus.h @@ -12,11 +12,22 @@ #include #include #include - -#include +#include namespace Terrain { + struct MacroMaterialData + { + AZ::EntityId m_entityId; + AZ::Aabb m_bounds = AZ::Aabb::CreateNull(); + + AZ::Data::Instance m_colorImage; + AZ::Data::Instance m_normalImage; + bool m_normalFlipX{ false }; + bool m_normalFlipY{ false }; + float m_normalFactor{ 0.0f }; + }; + /** * Request terrain macro material data. */ @@ -32,7 +43,7 @@ namespace Terrain virtual ~TerrainMacroMaterialRequests() = default; // Get the terrain macro material and the region that it covers. - virtual void GetTerrainMacroMaterialData(AZ::Data::Instance& macroMaterial, AZ::Aabb& macroMaterialRegion) = 0; + virtual MacroMaterialData GetTerrainMacroMaterialData() = 0; }; using TerrainMacroMaterialRequestBus = AZ::EBus; @@ -51,14 +62,12 @@ namespace Terrain virtual void OnTerrainMacroMaterialCreated( [[maybe_unused]] AZ::EntityId macroMaterialEntity, - [[maybe_unused]] AZ::Data::Instance macroMaterial, - [[maybe_unused]] const AZ::Aabb& macroMaterialRegion) + [[maybe_unused]] const MacroMaterialData& macroMaterial) { } virtual void OnTerrainMacroMaterialChanged( - [[maybe_unused]] AZ::EntityId macroMaterialEntity, - [[maybe_unused]] AZ::Data::Instance macroMaterial) + [[maybe_unused]] AZ::EntityId macroMaterialEntity, [[maybe_unused]] const MacroMaterialData& macroMaterial) { } diff --git a/Gems/Terrain/Code/Source/TerrainSystem/TerrainSystem.cpp b/Gems/Terrain/Code/Source/TerrainSystem/TerrainSystem.cpp index fa1d483a5d..8d39340b06 100644 --- a/Gems/Terrain/Code/Source/TerrainSystem/TerrainSystem.cpp +++ b/Gems/Terrain/Code/Source/TerrainSystem/TerrainSystem.cpp @@ -76,6 +76,7 @@ void TerrainSystem::Activate() m_dirtyRegion = AZ::Aabb::CreateNull(); m_terrainHeightDirty = true; m_terrainSettingsDirty = true; + m_terrainSurfacesDirty = true; m_requestedSettings.m_systemActive = true; { @@ -115,6 +116,7 @@ void TerrainSystem::Deactivate() m_dirtyRegion = AZ::Aabb::CreateNull(); m_terrainHeightDirty = true; m_terrainSettingsDirty = true; + m_terrainSurfacesDirty = true; m_requestedSettings.m_systemActive = false; AzFramework::Terrain::TerrainDataNotificationBus::Broadcast( @@ -549,6 +551,7 @@ void TerrainSystem::RegisterArea(AZ::EntityId areaId) m_registeredAreas[areaId] = aabb; m_dirtyRegion.AddAabb(aabb); m_terrainHeightDirty = true; + m_terrainSurfacesDirty = true; } void TerrainSystem::UnregisterArea(AZ::EntityId areaId) @@ -567,14 +570,17 @@ void TerrainSystem::UnregisterArea(AZ::EntityId areaId) { m_dirtyRegion.AddAabb(aabb); m_terrainHeightDirty = true; + m_terrainSurfacesDirty = true; return true; } return false; }); } -void TerrainSystem::RefreshArea(AZ::EntityId areaId) +void TerrainSystem::RefreshArea(AZ::EntityId areaId, AzFramework::Terrain::TerrainDataNotifications::TerrainDataChangedMask changeMask) { + using Terrain = AzFramework::Terrain::TerrainDataNotifications; + AZStd::unique_lock lock(m_areaMutex); auto areaAabb = m_registeredAreas.find(areaId); @@ -588,11 +594,18 @@ void TerrainSystem::RefreshArea(AZ::EntityId areaId) expandedAabb.AddAabb(newAabb); m_dirtyRegion.AddAabb(expandedAabb); - m_terrainHeightDirty = true; + + // Keep track of which types of data have changed so that we can send out the appropriate notifications later. + + m_terrainHeightDirty = m_terrainHeightDirty || ((changeMask & Terrain::HeightData) == Terrain::HeightData); + + m_terrainSurfacesDirty = m_terrainSurfacesDirty || ((changeMask & Terrain::SurfaceData) == Terrain::SurfaceData); } void TerrainSystem::OnTick(float /*deltaTime*/, AZ::ScriptTimePoint /*time*/) { + using Terrain = AzFramework::Terrain::TerrainDataNotifications; + bool terrainSettingsChanged = false; if (m_terrainSettingsDirty) @@ -607,6 +620,7 @@ void TerrainSystem::OnTick(float /*deltaTime*/, AZ::ScriptTimePoint /*time*/) m_dirtyRegion = m_currentSettings.m_worldBounds; m_dirtyRegion.AddAabb(m_requestedSettings.m_worldBounds); m_terrainHeightDirty = true; + m_terrainSurfacesDirty = true; m_currentSettings.m_worldBounds = m_requestedSettings.m_worldBounds; } @@ -614,12 +628,13 @@ void TerrainSystem::OnTick(float /*deltaTime*/, AZ::ScriptTimePoint /*time*/) { m_dirtyRegion = AZ::Aabb::CreateNull(); m_terrainHeightDirty = true; + m_terrainSurfacesDirty = true; } m_currentSettings = m_requestedSettings; } - if (terrainSettingsChanged || m_terrainHeightDirty) + if (terrainSettingsChanged || m_terrainHeightDirty || m_terrainSurfacesDirty) { // Block other threads from accessing the surface data bus while we are in GetValue (which may call into the SurfaceData bus). // We lock our surface data mutex *before* checking / setting "isRequestInProgress" so that we prevent race conditions @@ -629,24 +644,27 @@ void TerrainSystem::OnTick(float /*deltaTime*/, AZ::ScriptTimePoint /*time*/) auto& surfaceDataContext = SurfaceData::SurfaceDataSystemRequestBus::GetOrCreateContext(false); typename SurfaceData::SurfaceDataSystemRequestBus::Context::DispatchLockGuard scopeLock(surfaceDataContext.m_contextMutex); - AzFramework::Terrain::TerrainDataNotifications::TerrainDataChangedMask changeMask = - AzFramework::Terrain::TerrainDataNotifications::TerrainDataChangedMask::None; + Terrain::TerrainDataChangedMask changeMask = Terrain::TerrainDataChangedMask::None; if (terrainSettingsChanged) { - changeMask = static_cast( - changeMask | AzFramework::Terrain::TerrainDataNotifications::TerrainDataChangedMask::Settings); + changeMask = static_cast(changeMask | Terrain::TerrainDataChangedMask::Settings); } if (m_terrainHeightDirty) { - changeMask = static_cast( - changeMask | AzFramework::Terrain::TerrainDataNotifications::TerrainDataChangedMask::HeightData); + changeMask = static_cast(changeMask | Terrain::TerrainDataChangedMask::HeightData); + } + + if (m_terrainSurfacesDirty) + { + changeMask = static_cast(changeMask | Terrain::TerrainDataChangedMask::SurfaceData); } // Make sure to set these *before* calling OnTerrainDataChanged, since it's possible that subsystems reacting to that call will // cause the data to become dirty again. AZ::Aabb dirtyRegion = m_dirtyRegion; m_terrainHeightDirty = false; + m_terrainSurfacesDirty = false; m_dirtyRegion = AZ::Aabb::CreateNull(); AzFramework::Terrain::TerrainDataNotificationBus::Broadcast( diff --git a/Gems/Terrain/Code/Source/TerrainSystem/TerrainSystem.h b/Gems/Terrain/Code/Source/TerrainSystem/TerrainSystem.h index 956424f048..022cd218cc 100644 --- a/Gems/Terrain/Code/Source/TerrainSystem/TerrainSystem.h +++ b/Gems/Terrain/Code/Source/TerrainSystem/TerrainSystem.h @@ -47,7 +47,8 @@ namespace Terrain void RegisterArea(AZ::EntityId areaId) override; void UnregisterArea(AZ::EntityId areaId) override; - void RefreshArea(AZ::EntityId areaId) override; + void RefreshArea( + AZ::EntityId areaId, AzFramework::Terrain::TerrainDataNotifications::TerrainDataChangedMask changeMask) override; /////////////////////////////////////////// // TerrainDataRequestBus::Handler Impl @@ -164,6 +165,7 @@ namespace Terrain bool m_terrainSettingsDirty = true; bool m_terrainHeightDirty = false; + bool m_terrainSurfacesDirty = false; AZ::Aabb m_dirtyRegion; mutable AZStd::shared_mutex m_areaMutex; diff --git a/Gems/Terrain/Code/Source/TerrainSystem/TerrainSystemBus.h b/Gems/Terrain/Code/Source/TerrainSystem/TerrainSystemBus.h index cda6d65a1e..013d82d94d 100644 --- a/Gems/Terrain/Code/Source/TerrainSystem/TerrainSystemBus.h +++ b/Gems/Terrain/Code/Source/TerrainSystem/TerrainSystemBus.h @@ -44,7 +44,7 @@ namespace Terrain // register an area to override terrain virtual void RegisterArea(AZ::EntityId areaId) = 0; virtual void UnregisterArea(AZ::EntityId areaId) = 0; - virtual void RefreshArea(AZ::EntityId areaId) = 0; + virtual void RefreshArea(AZ::EntityId areaId, AzFramework::Terrain::TerrainDataNotifications::TerrainDataChangedMask changeMask) = 0; }; using TerrainSystemServiceRequestBus = AZ::EBus; diff --git a/Gems/Terrain/Code/Tests/LayerSpawnerTests.cpp b/Gems/Terrain/Code/Tests/LayerSpawnerTests.cpp index 0ca5e7d99a..3778ba860f 100644 --- a/Gems/Terrain/Code/Tests/LayerSpawnerTests.cpp +++ b/Gems/Terrain/Code/Tests/LayerSpawnerTests.cpp @@ -190,7 +190,7 @@ TEST_F(LayerSpawnerComponentTest, LayerSpawnerTransformChangedUpdatesTerrainSyst CreateMockTerrainSystem(); // The TransformChanged call should refresh the area. - EXPECT_CALL(*m_terrainSystem, RefreshArea(_)).Times(1); + EXPECT_CALL(*m_terrainSystem, RefreshArea(_, _)).Times(1); AddLayerSpawnerAndShapeComponentToEntity(); @@ -211,7 +211,7 @@ TEST_F(LayerSpawnerComponentTest, LayerSpawnerShapeChangedUpdatesTerrainSystem) CreateMockTerrainSystem(); // The ShapeChanged call should refresh the area. - EXPECT_CALL(*m_terrainSystem, RefreshArea(_)).Times(1); + EXPECT_CALL(*m_terrainSystem, RefreshArea(_, _)).Times(1); AddLayerSpawnerAndShapeComponentToEntity(); diff --git a/Gems/Terrain/Code/Tests/TerrainHeightGradientListTests.cpp b/Gems/Terrain/Code/Tests/TerrainHeightGradientListTests.cpp index c314e4e968..ec500d6ada 100644 --- a/Gems/Terrain/Code/Tests/TerrainHeightGradientListTests.cpp +++ b/Gems/Terrain/Code/Tests/TerrainHeightGradientListTests.cpp @@ -93,7 +93,7 @@ TEST_F(TerrainHeightGradientListComponentTest, TerrainHeightGradientRefreshesTer // As the TerrainHeightGradientListComponent subscribes to the dependency monitor, RefreshArea will be called twice: // once due to OnCompositionChanged being picked up by the the dependency monitor and resending the notification, // and once when the HeightGradientListComponent gets the OnCompositionChanged directly through the DependencyNotificationBus. - EXPECT_CALL(terrainSystem, RefreshArea(_)).Times(2); + EXPECT_CALL(terrainSystem, RefreshArea(_, _)).Times(2); LmbrCentral::DependencyNotificationBus::Event(m_entity->GetId(), &LmbrCentral::DependencyNotificationBus::Events::OnCompositionChanged); diff --git a/Gems/WhiteBox/Code/Source/Platform/Linux/platform_linux_tools.cmake b/Gems/WhiteBox/Code/Source/Platform/Linux/platform_linux_tools.cmake new file mode 100644 index 0000000000..ca99817d82 --- /dev/null +++ b/Gems/WhiteBox/Code/Source/Platform/Linux/platform_linux_tools.cmake @@ -0,0 +1,16 @@ +# +# Copyright (c) Contributors to the Open 3D Engine Project. +# For complete copyright and license terms please see the LICENSE at the root of this distribution. +# +# SPDX-License-Identifier: Apache-2.0 OR MIT +# +# + +if(PAL_TRAIT_BUILD_HOST_TOOLS) + + ly_associate_package(PACKAGE_NAME OpenMesh-8.1-rev3-linux TARGETS OpenMesh PACKAGE_HASH 805bd0b24911bb00c7f575b8c3f10d7ea16548a5014c40811894a9445f17a126) + + set(LY_BUILD_DEPENDENCIES + PRIVATE + 3rdParty::OpenMesh) +endif() diff --git a/Gems/WhiteBox/Code/Source/Platform/Mac/platform_mac_tools.cmake b/Gems/WhiteBox/Code/Source/Platform/Mac/platform_mac_tools.cmake new file mode 100644 index 0000000000..030b621642 --- /dev/null +++ b/Gems/WhiteBox/Code/Source/Platform/Mac/platform_mac_tools.cmake @@ -0,0 +1,16 @@ +# +# Copyright (c) Contributors to the Open 3D Engine Project. +# For complete copyright and license terms please see the LICENSE at the root of this distribution. +# +# SPDX-License-Identifier: Apache-2.0 OR MIT +# +# + +if(PAL_TRAIT_BUILD_HOST_TOOLS) + + ly_associate_package(PACKAGE_NAME OpenMesh-8.1-rev3-mac TARGETS OpenMesh PACKAGE_HASH af92db02a25c1f7e1741ec898f49d81d52631e00336bf9bddd1e191590063c2f) + + set(LY_BUILD_DEPENDENCIES + PRIVATE + 3rdParty::OpenMesh) +endif() diff --git a/Gems/WhiteBox/Code/Source/Platform/Windows/platform_windows_tools.cmake b/Gems/WhiteBox/Code/Source/Platform/Windows/platform_windows_tools.cmake index 437dbc192d..aa56fe7b23 100644 --- a/Gems/WhiteBox/Code/Source/Platform/Windows/platform_windows_tools.cmake +++ b/Gems/WhiteBox/Code/Source/Platform/Windows/platform_windows_tools.cmake @@ -8,7 +8,7 @@ if(PAL_TRAIT_BUILD_HOST_TOOLS) - ly_associate_package(PACKAGE_NAME OpenMesh-8.1-rev1-windows TARGETS OpenMesh PACKAGE_HASH 1c1df639358526c368e790dfce40c45cbdfcfb1c9a041b9d7054a8949d88ee77) + ly_associate_package(PACKAGE_NAME OpenMesh-8.1-rev3-windows TARGETS OpenMesh PACKAGE_HASH 7a6309323ad03bfc646bd04ecc79c3711de6790e4ff5a72f83a8f5a8f496d684) set(LY_BUILD_DEPENDENCIES PRIVATE diff --git a/README.md b/README.md index 1191a0f0ee..6f500a3dd3 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ For the latest details and system requirements, refer to [System Requirements](h * Game Development with C++ * MSVC v142 - VS 2019 C++ x64/x86 * C++ 2019 redistributable update -* CMake 3.20.5 minimum: [https://cmake.org/download/](https://cmake.org/download/) +* CMake 3.20.5 minimum: [https://cmake.org/download/#latest](https://cmake.org/download/#latest) (Release Candidate versions are not supported) #### Optional diff --git a/Registry/AssetProcessorPlatformConfig.setreg b/Registry/AssetProcessorPlatformConfig.setreg index ddc465c201..c8d8dd9a80 100644 --- a/Registry/AssetProcessorPlatformConfig.setreg +++ b/Registry/AssetProcessorPlatformConfig.setreg @@ -137,64 +137,66 @@ // Excludes files that match the pattern or glob // if you use a pattern, remember to escape your backslashes (\\) + // The patterns are checked against a path relative to the entry's + // root scan folder. "Exclude _LevelBackups": { - "pattern": ".*\\\\/Levels\\\\/.*\\\\/_savebackup\\\\/.*" + "pattern": "(^|.+/)Levels/.*/_savebackup(/.*)?$" }, "Exclude _LevelAutoBackups": { - "pattern": ".*\\\\/Levels\\\\/.*\\\\/_autobackup\\\\/.*" + "pattern": "(^|.+/)Levels/.*/_autobackup(/.*)?$" }, "Exclude HoldFiles": { - "pattern": ".*\\\\/Levels\\\\/.*_hold\\\\/.*" + "pattern": "(^|.+/)Levels/.*_hold(/.*)?$" }, // note that $ has meaning to regex, so we escape it. "Exclude TempFiles": { - "pattern": ".*\\\\/\\\\$tmp[0-9]*_.*" + "pattern": "(^|.+/)\\\\$tmp[0-9]*_.*" }, "Exclude TmpAnimationCompression": { - "pattern": ".*\\\\/Editor\\\\/Tmp\\\\/AnimationCompression\\\\/.*" + "pattern": "(^|.+/)Editor/Tmp/AnimationCompression(/.*)?$" }, "Exclude EventLog": { - "pattern": ".*\\\\/Editor\\\\/.*eventlog\\\\.xml" + "pattern": "(^|.+/)Editor/.*eventlog\\\\.xml" }, "Exclude GameGemsCode": { - "pattern": ".*\\\\/Gem\\\\/Code\\\\/.*" + "pattern": "(^|.+/)Gem/Code(/.*)?$" }, "Exclude GameGemsResources": { - "pattern": ".*\\\\/Gem\\\\/Resources\\\\/.*" + "pattern": "(^|.+/)Gem/Resources(/.*)?$" }, "Exclude Private Certs": { - "pattern": ".*\\DynamicContent\\\\/Certificates\\\\/Private\\\\/.*" + "pattern": "(^|.+/)DynamicContent/Certificates/Private(/.*)?$" }, "Exclude CMakeLists": { - "pattern": ".*\\\\/CMakeLists.txt" + "pattern": "(^|.+/)CMakeLists\\\\.txt" }, "Exclude CMakeFiles": { - "pattern": ".*\\\\/.*\\\\.cmake" + "pattern": "(^|.+/).+\\\\.cmake" }, "Exclude User": { - "pattern": ".*/[Uu]ser/.*" + "pattern": "^[Uu]ser(/.*)?$" }, "Exclude Build": { - "pattern": ".*/[Bb]uild/.*" + "pattern": "^[Bb]uild(/.*)?$" }, "Exclude Install": { - "pattern": ".*/[Ii]nstall/.*" + "pattern": "^[Ii]nstall(/.*)?$" }, "Exclude UserSettings": { - "pattern": ".*/UserSettings.xml" + "pattern": "(^|[^/]+/)UserSettings\\\\.xml" }, // ------------------------------------------------------------------------------ // Large Worlds Test // ------------------------------------------------------------------------------ "Exclude Work In Progress Folders": { - "pattern": ".*\\\\/WIP\\\\/.*" + "pattern": "(^|[^/]+/)WIP(/.*)?" }, "Exclude Content Source Folders": { - "pattern": ".*\\\\/CONTENT_SOURCE\\\\/.*" + "pattern": "(^|[^/]+/)CONTENT_SOURCE(/.*)?" }, "Exclude Art Source Folders": { - "pattern": ".*\\\\/ArtSource\\\\/.*" + "pattern": "(^|[^/]+/)ArtSource(/.*)?" }, //------------------------------------------------------------------------------ // Copying Files Automatically Into the Cache diff --git a/Registry/CMakeLists.txt b/Registry/CMakeLists.txt index 773adac07f..df309ba657 100644 --- a/Registry/CMakeLists.txt +++ b/Registry/CMakeLists.txt @@ -12,6 +12,11 @@ endif() ly_install_directory(DIRECTORIES .) -ly_install_directory(DIRECTORIES ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/$/Registry - DESTINATION ${runtime_output_directory} -) +foreach(conf IN LISTS CMAKE_CONFIGURATION_TYPES) + string(TOUPPER ${conf} UCONF) + string(REPLACE "$" "${conf}" output ${runtime_output_directory}) + ly_install_directory(DIRECTORIES ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${conf}/Registry + DESTINATION ${output} + COMPONENT ${LY_INSTALL_PERMUTATION_COMPONENT}_${UCONF} + ) +endforeach() diff --git a/Registry/application_lifecycle_events.setreg b/Registry/application_lifecycle_events.setreg new file mode 100644 index 0000000000..0d9cd0f170 --- /dev/null +++ b/Registry/application_lifecycle_events.setreg @@ -0,0 +1,30 @@ +// The Lifecycle events contains the name of the event as a string +// ComponentApplication derived classes +// will set these these keys to a JSON Object indicate an event has occured +// A callback can be registered with the SettingsRegistry +// to be notified when that key is set +// The JSON object that is set will contain any payload data +// related to the event +{ + "O3DE" : { + "Application": { + "LifecycleEvents": { + "SystemComponentsActivated": {}, + "SystemComponentsDeactivated": {}, + "ReflectionManagerAvailable": {}, + "ReflectionManagerUnavailable": {}, + "SystemAllocatorCreated": {}, + "SystemAllocatorPendingDestruction": {}, + "SettingsRegistryAvailable": {}, + "SettingsRegistryUnavailable": {}, + "ConsoleAvailable": {}, + "ConsoleUnavailable": {}, + "GemsLoaded": {}, + "GemsUnloaded": {}, + "FileIOAvailable": {}, + "FileIOUnavailable": {}, + "LegacySystemInterfaceCreated": {} + } + } + } +} diff --git a/Registry/profiler.setreg b/Registry/profiler.setreg new file mode 100644 index 0000000000..b46bfd8beb --- /dev/null +++ b/Registry/profiler.setreg @@ -0,0 +1,15 @@ +{ + "O3DE": + { + "AzCore": + { + "Debug": + { + "Profiler": + { + "CaptureLocation" : "@user@/Profiler" + } + } + } + } +} diff --git a/Templates/CMakeLists.txt b/Templates/CMakeLists.txt index 1a3a45b5ec..9735907a6a 100644 --- a/Templates/CMakeLists.txt +++ b/Templates/CMakeLists.txt @@ -9,6 +9,8 @@ ly_install_directory( DIRECTORIES AssetGem + CppToolGem + PythonToolGem DefaultGem DefaultProject MinimalProject diff --git a/Templates/CustomTool/Template/CMakeLists.txt b/Templates/CppToolGem/Template/CMakeLists.txt similarity index 100% rename from Templates/CustomTool/Template/CMakeLists.txt rename to Templates/CppToolGem/Template/CMakeLists.txt diff --git a/Templates/CustomTool/Template/Code/${NameLower}_editor_files.cmake b/Templates/CppToolGem/Template/Code/${NameLower}_editor_files.cmake similarity index 100% rename from Templates/CustomTool/Template/Code/${NameLower}_editor_files.cmake rename to Templates/CppToolGem/Template/Code/${NameLower}_editor_files.cmake diff --git a/Templates/CustomTool/Template/Code/${NameLower}_editor_shared_files.cmake b/Templates/CppToolGem/Template/Code/${NameLower}_editor_shared_files.cmake similarity index 100% rename from Templates/CustomTool/Template/Code/${NameLower}_editor_shared_files.cmake rename to Templates/CppToolGem/Template/Code/${NameLower}_editor_shared_files.cmake diff --git a/Templates/CustomTool/Template/Code/${NameLower}_editor_tests_files.cmake b/Templates/CppToolGem/Template/Code/${NameLower}_editor_tests_files.cmake similarity index 100% rename from Templates/CustomTool/Template/Code/${NameLower}_editor_tests_files.cmake rename to Templates/CppToolGem/Template/Code/${NameLower}_editor_tests_files.cmake diff --git a/Templates/CustomTool/Template/Code/${NameLower}_files.cmake b/Templates/CppToolGem/Template/Code/${NameLower}_files.cmake similarity index 100% rename from Templates/CustomTool/Template/Code/${NameLower}_files.cmake rename to Templates/CppToolGem/Template/Code/${NameLower}_files.cmake diff --git a/Templates/CustomTool/Template/Code/${NameLower}_shared_files.cmake b/Templates/CppToolGem/Template/Code/${NameLower}_shared_files.cmake similarity index 100% rename from Templates/CustomTool/Template/Code/${NameLower}_shared_files.cmake rename to Templates/CppToolGem/Template/Code/${NameLower}_shared_files.cmake diff --git a/Templates/CustomTool/Template/Code/${NameLower}_tests_files.cmake b/Templates/CppToolGem/Template/Code/${NameLower}_tests_files.cmake similarity index 100% rename from Templates/CustomTool/Template/Code/${NameLower}_tests_files.cmake rename to Templates/CppToolGem/Template/Code/${NameLower}_tests_files.cmake diff --git a/Templates/CustomTool/Template/Code/CMakeLists.txt b/Templates/CppToolGem/Template/Code/CMakeLists.txt similarity index 100% rename from Templates/CustomTool/Template/Code/CMakeLists.txt rename to Templates/CppToolGem/Template/Code/CMakeLists.txt diff --git a/Templates/CustomTool/Template/Code/Include/${Name}/${Name}Bus.h b/Templates/CppToolGem/Template/Code/Include/${Name}/${Name}Bus.h similarity index 100% rename from Templates/CustomTool/Template/Code/Include/${Name}/${Name}Bus.h rename to Templates/CppToolGem/Template/Code/Include/${Name}/${Name}Bus.h diff --git a/Templates/CustomTool/Template/Code/Platform/Android/${NameLower}_android_files.cmake b/Templates/CppToolGem/Template/Code/Platform/Android/${NameLower}_android_files.cmake similarity index 100% rename from Templates/CustomTool/Template/Code/Platform/Android/${NameLower}_android_files.cmake rename to Templates/CppToolGem/Template/Code/Platform/Android/${NameLower}_android_files.cmake diff --git a/Templates/CustomTool/Template/Code/Platform/Android/${NameLower}_shared_android_files.cmake b/Templates/CppToolGem/Template/Code/Platform/Android/${NameLower}_shared_android_files.cmake similarity index 100% rename from Templates/CustomTool/Template/Code/Platform/Android/${NameLower}_shared_android_files.cmake rename to Templates/CppToolGem/Template/Code/Platform/Android/${NameLower}_shared_android_files.cmake diff --git a/Templates/CustomTool/Template/Code/Platform/Android/PAL_android.cmake b/Templates/CppToolGem/Template/Code/Platform/Android/PAL_android.cmake similarity index 100% rename from Templates/CustomTool/Template/Code/Platform/Android/PAL_android.cmake rename to Templates/CppToolGem/Template/Code/Platform/Android/PAL_android.cmake diff --git a/Templates/CustomTool/Template/Code/Platform/Linux/${NameLower}_linux_files.cmake b/Templates/CppToolGem/Template/Code/Platform/Linux/${NameLower}_linux_files.cmake similarity index 100% rename from Templates/CustomTool/Template/Code/Platform/Linux/${NameLower}_linux_files.cmake rename to Templates/CppToolGem/Template/Code/Platform/Linux/${NameLower}_linux_files.cmake diff --git a/Templates/CustomTool/Template/Code/Platform/Linux/${NameLower}_shared_linux_files.cmake b/Templates/CppToolGem/Template/Code/Platform/Linux/${NameLower}_shared_linux_files.cmake similarity index 100% rename from Templates/CustomTool/Template/Code/Platform/Linux/${NameLower}_shared_linux_files.cmake rename to Templates/CppToolGem/Template/Code/Platform/Linux/${NameLower}_shared_linux_files.cmake diff --git a/Templates/CustomTool/Template/Code/Platform/Linux/PAL_linux.cmake b/Templates/CppToolGem/Template/Code/Platform/Linux/PAL_linux.cmake similarity index 100% rename from Templates/CustomTool/Template/Code/Platform/Linux/PAL_linux.cmake rename to Templates/CppToolGem/Template/Code/Platform/Linux/PAL_linux.cmake diff --git a/Templates/CustomTool/Template/Code/Platform/Mac/${NameLower}_mac_files.cmake b/Templates/CppToolGem/Template/Code/Platform/Mac/${NameLower}_mac_files.cmake similarity index 100% rename from Templates/CustomTool/Template/Code/Platform/Mac/${NameLower}_mac_files.cmake rename to Templates/CppToolGem/Template/Code/Platform/Mac/${NameLower}_mac_files.cmake diff --git a/Templates/CustomTool/Template/Code/Platform/Mac/${NameLower}_shared_mac_files.cmake b/Templates/CppToolGem/Template/Code/Platform/Mac/${NameLower}_shared_mac_files.cmake similarity index 100% rename from Templates/CustomTool/Template/Code/Platform/Mac/${NameLower}_shared_mac_files.cmake rename to Templates/CppToolGem/Template/Code/Platform/Mac/${NameLower}_shared_mac_files.cmake diff --git a/Templates/CustomTool/Template/Code/Platform/Mac/PAL_mac.cmake b/Templates/CppToolGem/Template/Code/Platform/Mac/PAL_mac.cmake similarity index 100% rename from Templates/CustomTool/Template/Code/Platform/Mac/PAL_mac.cmake rename to Templates/CppToolGem/Template/Code/Platform/Mac/PAL_mac.cmake diff --git a/Templates/CustomTool/Template/Code/Platform/Windows/${NameLower}_shared_windows_files.cmake b/Templates/CppToolGem/Template/Code/Platform/Windows/${NameLower}_shared_windows_files.cmake similarity index 100% rename from Templates/CustomTool/Template/Code/Platform/Windows/${NameLower}_shared_windows_files.cmake rename to Templates/CppToolGem/Template/Code/Platform/Windows/${NameLower}_shared_windows_files.cmake diff --git a/Templates/CustomTool/Template/Code/Platform/Windows/${NameLower}_windows_files.cmake b/Templates/CppToolGem/Template/Code/Platform/Windows/${NameLower}_windows_files.cmake similarity index 100% rename from Templates/CustomTool/Template/Code/Platform/Windows/${NameLower}_windows_files.cmake rename to Templates/CppToolGem/Template/Code/Platform/Windows/${NameLower}_windows_files.cmake diff --git a/Templates/CustomTool/Template/Code/Platform/Windows/PAL_windows.cmake b/Templates/CppToolGem/Template/Code/Platform/Windows/PAL_windows.cmake similarity index 100% rename from Templates/CustomTool/Template/Code/Platform/Windows/PAL_windows.cmake rename to Templates/CppToolGem/Template/Code/Platform/Windows/PAL_windows.cmake diff --git a/Templates/CustomTool/Template/Code/Platform/iOS/${NameLower}_ios_files.cmake b/Templates/CppToolGem/Template/Code/Platform/iOS/${NameLower}_ios_files.cmake similarity index 100% rename from Templates/CustomTool/Template/Code/Platform/iOS/${NameLower}_ios_files.cmake rename to Templates/CppToolGem/Template/Code/Platform/iOS/${NameLower}_ios_files.cmake diff --git a/Templates/CustomTool/Template/Code/Platform/iOS/${NameLower}_shared_ios_files.cmake b/Templates/CppToolGem/Template/Code/Platform/iOS/${NameLower}_shared_ios_files.cmake similarity index 100% rename from Templates/CustomTool/Template/Code/Platform/iOS/${NameLower}_shared_ios_files.cmake rename to Templates/CppToolGem/Template/Code/Platform/iOS/${NameLower}_shared_ios_files.cmake diff --git a/Templates/CustomTool/Template/Code/Platform/iOS/PAL_ios.cmake b/Templates/CppToolGem/Template/Code/Platform/iOS/PAL_ios.cmake similarity index 100% rename from Templates/CustomTool/Template/Code/Platform/iOS/PAL_ios.cmake rename to Templates/CppToolGem/Template/Code/Platform/iOS/PAL_ios.cmake diff --git a/Templates/CustomTool/Template/Code/Source/${Name}.qrc b/Templates/CppToolGem/Template/Code/Source/${Name}.qrc similarity index 100% rename from Templates/CustomTool/Template/Code/Source/${Name}.qrc rename to Templates/CppToolGem/Template/Code/Source/${Name}.qrc diff --git a/Templates/CustomTool/Template/Code/Source/${Name}EditorModule.cpp b/Templates/CppToolGem/Template/Code/Source/${Name}EditorModule.cpp similarity index 100% rename from Templates/CustomTool/Template/Code/Source/${Name}EditorModule.cpp rename to Templates/CppToolGem/Template/Code/Source/${Name}EditorModule.cpp diff --git a/Templates/CustomTool/Template/Code/Source/${Name}EditorSystemComponent.cpp b/Templates/CppToolGem/Template/Code/Source/${Name}EditorSystemComponent.cpp similarity index 97% rename from Templates/CustomTool/Template/Code/Source/${Name}EditorSystemComponent.cpp rename to Templates/CppToolGem/Template/Code/Source/${Name}EditorSystemComponent.cpp index f12fa04929..4206340c57 100644 --- a/Templates/CustomTool/Template/Code/Source/${Name}EditorSystemComponent.cpp +++ b/Templates/CppToolGem/Template/Code/Source/${Name}EditorSystemComponent.cpp @@ -71,8 +71,8 @@ namespace ${SanitizedCppName} options.showOnToolsToolbar = true; options.toolbarIcon = ":/${Name}/toolbar_icon.svg"; - // Register our custom widget as a dockable tool with the Editor - AzToolsFramework::RegisterViewPane<${SanitizedCppName}Widget>("${Name}", "Tools", options); + // Register our custom widget as a dockable tool with the Editor under an Examples sub-menu + AzToolsFramework::RegisterViewPane<${SanitizedCppName}Widget>("${Name}", "Examples", options); } } // namespace ${SanitizedCppName} diff --git a/Templates/CustomTool/Template/Code/Source/${Name}EditorSystemComponent.h b/Templates/CppToolGem/Template/Code/Source/${Name}EditorSystemComponent.h similarity index 100% rename from Templates/CustomTool/Template/Code/Source/${Name}EditorSystemComponent.h rename to Templates/CppToolGem/Template/Code/Source/${Name}EditorSystemComponent.h diff --git a/Templates/CustomTool/Template/Code/Source/${Name}Module.cpp b/Templates/CppToolGem/Template/Code/Source/${Name}Module.cpp similarity index 100% rename from Templates/CustomTool/Template/Code/Source/${Name}Module.cpp rename to Templates/CppToolGem/Template/Code/Source/${Name}Module.cpp diff --git a/Templates/CustomTool/Template/Code/Source/${Name}ModuleInterface.h b/Templates/CppToolGem/Template/Code/Source/${Name}ModuleInterface.h similarity index 100% rename from Templates/CustomTool/Template/Code/Source/${Name}ModuleInterface.h rename to Templates/CppToolGem/Template/Code/Source/${Name}ModuleInterface.h diff --git a/Templates/CustomTool/Template/Code/Source/${Name}SystemComponent.cpp b/Templates/CppToolGem/Template/Code/Source/${Name}SystemComponent.cpp similarity index 100% rename from Templates/CustomTool/Template/Code/Source/${Name}SystemComponent.cpp rename to Templates/CppToolGem/Template/Code/Source/${Name}SystemComponent.cpp diff --git a/Templates/CustomTool/Template/Code/Source/${Name}SystemComponent.h b/Templates/CppToolGem/Template/Code/Source/${Name}SystemComponent.h similarity index 100% rename from Templates/CustomTool/Template/Code/Source/${Name}SystemComponent.h rename to Templates/CppToolGem/Template/Code/Source/${Name}SystemComponent.h diff --git a/Templates/CustomTool/Template/Code/Source/${Name}Widget.cpp b/Templates/CppToolGem/Template/Code/Source/${Name}Widget.cpp similarity index 96% rename from Templates/CustomTool/Template/Code/Source/${Name}Widget.cpp rename to Templates/CppToolGem/Template/Code/Source/${Name}Widget.cpp index bd6dd6c86a..a5128fb192 100644 --- a/Templates/CustomTool/Template/Code/Source/${Name}Widget.cpp +++ b/Templates/CppToolGem/Template/Code/Source/${Name}Widget.cpp @@ -20,8 +20,6 @@ namespace ${SanitizedCppName} ${SanitizedCppName}Widget::${SanitizedCppName}Widget(QWidget* parent) : QWidget(parent) { - setWindowTitle(QObject::tr("${Name}")); - QVBoxLayout* mainLayout = new QVBoxLayout(this); QLabel* introLabel = new QLabel(QObject::tr("Put your cool stuff here!"), this); diff --git a/Templates/CustomTool/Template/Code/Source/${Name}Widget.h b/Templates/CppToolGem/Template/Code/Source/${Name}Widget.h similarity index 100% rename from Templates/CustomTool/Template/Code/Source/${Name}Widget.h rename to Templates/CppToolGem/Template/Code/Source/${Name}Widget.h diff --git a/Templates/CustomTool/Template/Code/Source/toolbar_icon.svg b/Templates/CppToolGem/Template/Code/Source/toolbar_icon.svg similarity index 100% rename from Templates/CustomTool/Template/Code/Source/toolbar_icon.svg rename to Templates/CppToolGem/Template/Code/Source/toolbar_icon.svg diff --git a/Templates/CustomTool/Template/Code/Tests/${Name}EditorTest.cpp b/Templates/CppToolGem/Template/Code/Tests/${Name}EditorTest.cpp similarity index 100% rename from Templates/CustomTool/Template/Code/Tests/${Name}EditorTest.cpp rename to Templates/CppToolGem/Template/Code/Tests/${Name}EditorTest.cpp diff --git a/Templates/CustomTool/Template/Code/Tests/${Name}Test.cpp b/Templates/CppToolGem/Template/Code/Tests/${Name}Test.cpp similarity index 100% rename from Templates/CustomTool/Template/Code/Tests/${Name}Test.cpp rename to Templates/CppToolGem/Template/Code/Tests/${Name}Test.cpp diff --git a/Templates/CustomTool/Template/Platform/Android/android_gem.cmake b/Templates/CppToolGem/Template/Platform/Android/android_gem.cmake similarity index 100% rename from Templates/CustomTool/Template/Platform/Android/android_gem.cmake rename to Templates/CppToolGem/Template/Platform/Android/android_gem.cmake diff --git a/Templates/CustomTool/Template/Platform/Android/android_gem.json b/Templates/CppToolGem/Template/Platform/Android/android_gem.json similarity index 100% rename from Templates/CustomTool/Template/Platform/Android/android_gem.json rename to Templates/CppToolGem/Template/Platform/Android/android_gem.json diff --git a/Templates/CustomTool/Template/Platform/Linux/linux_gem.cmake b/Templates/CppToolGem/Template/Platform/Linux/linux_gem.cmake similarity index 100% rename from Templates/CustomTool/Template/Platform/Linux/linux_gem.cmake rename to Templates/CppToolGem/Template/Platform/Linux/linux_gem.cmake diff --git a/Templates/CustomTool/Template/Platform/Linux/linux_gem.json b/Templates/CppToolGem/Template/Platform/Linux/linux_gem.json similarity index 100% rename from Templates/CustomTool/Template/Platform/Linux/linux_gem.json rename to Templates/CppToolGem/Template/Platform/Linux/linux_gem.json diff --git a/Templates/CustomTool/Template/Platform/Mac/mac_gem.cmake b/Templates/CppToolGem/Template/Platform/Mac/mac_gem.cmake similarity index 100% rename from Templates/CustomTool/Template/Platform/Mac/mac_gem.cmake rename to Templates/CppToolGem/Template/Platform/Mac/mac_gem.cmake diff --git a/Templates/CustomTool/Template/Platform/Mac/mac_gem.json b/Templates/CppToolGem/Template/Platform/Mac/mac_gem.json similarity index 100% rename from Templates/CustomTool/Template/Platform/Mac/mac_gem.json rename to Templates/CppToolGem/Template/Platform/Mac/mac_gem.json diff --git a/Templates/CustomTool/Template/Platform/Windows/windows_gem.cmake b/Templates/CppToolGem/Template/Platform/Windows/windows_gem.cmake similarity index 100% rename from Templates/CustomTool/Template/Platform/Windows/windows_gem.cmake rename to Templates/CppToolGem/Template/Platform/Windows/windows_gem.cmake diff --git a/Templates/CustomTool/Template/Platform/Windows/windows_gem.json b/Templates/CppToolGem/Template/Platform/Windows/windows_gem.json similarity index 100% rename from Templates/CustomTool/Template/Platform/Windows/windows_gem.json rename to Templates/CppToolGem/Template/Platform/Windows/windows_gem.json diff --git a/Templates/CustomTool/Template/Platform/iOS/ios_gem.cmake b/Templates/CppToolGem/Template/Platform/iOS/ios_gem.cmake similarity index 100% rename from Templates/CustomTool/Template/Platform/iOS/ios_gem.cmake rename to Templates/CppToolGem/Template/Platform/iOS/ios_gem.cmake diff --git a/Templates/CustomTool/Template/Platform/iOS/ios_gem.json b/Templates/CppToolGem/Template/Platform/iOS/ios_gem.json similarity index 100% rename from Templates/CustomTool/Template/Platform/iOS/ios_gem.json rename to Templates/CppToolGem/Template/Platform/iOS/ios_gem.json diff --git a/Templates/CustomTool/Template/gem.json b/Templates/CppToolGem/Template/gem.json similarity index 100% rename from Templates/CustomTool/Template/gem.json rename to Templates/CppToolGem/Template/gem.json diff --git a/Templates/CustomTool/Template/preview.png b/Templates/CppToolGem/Template/preview.png similarity index 100% rename from Templates/CustomTool/Template/preview.png rename to Templates/CppToolGem/Template/preview.png diff --git a/Templates/CustomTool/template.json b/Templates/CppToolGem/template.json similarity index 98% rename from Templates/CustomTool/template.json rename to Templates/CppToolGem/template.json index e3221db106..b516dbaec3 100644 --- a/Templates/CustomTool/template.json +++ b/Templates/CppToolGem/template.json @@ -1,12 +1,12 @@ { - "template_name": "CustomTool", - "origin": "The primary repo for CustomTool goes here: i.e. http://www.mydomain.com", - "license": "What license CustomTool uses goes here: i.e. https://opensource.org/licenses/MIT", - "display_name": "CustomTool", + "template_name": "CppToolGem", + "origin": "The primary repo for CppToolGem goes here: i.e. http://www.mydomain.com", + "license": "What license CppToolGem uses goes here: i.e. https://opensource.org/licenses/MIT", + "display_name": "CppToolGem", "summary": "A gem template for a custom tool in C++ that gets registered with the Editor.", "canonical_tags": [], "user_tags": [ - "CustomTool" + "CppToolGem" ], "icon_path": "preview.png", "copyFiles": [ diff --git a/Templates/PythonToolGem/Template/.gitignore b/Templates/PythonToolGem/Template/.gitignore new file mode 100644 index 0000000000..7a60b85e14 --- /dev/null +++ b/Templates/PythonToolGem/Template/.gitignore @@ -0,0 +1,2 @@ +__pycache__/ +*.pyc diff --git a/Templates/PythonToolGem/Template/CMakeLists.txt b/Templates/PythonToolGem/Template/CMakeLists.txt new file mode 100644 index 0000000000..d61bbd9e7d --- /dev/null +++ b/Templates/PythonToolGem/Template/CMakeLists.txt @@ -0,0 +1,14 @@ +# {BEGIN_LICENSE} +# Copyright (c) Contributors to the Open 3D Engine Project. +# For complete copyright and license terms please see the LICENSE at the root of this distribution. +# +# SPDX-License-Identifier: Apache-2.0 OR MIT +# +# {END_LICENSE} + +set(o3de_gem_path ${CMAKE_CURRENT_LIST_DIR}) +set(o3de_gem_json ${o3de_gem_path}/gem.json) +o3de_read_json_key(o3de_gem_name ${o3de_gem_json} "gem_name") +o3de_restricted_path(${o3de_gem_json} o3de_gem_restricted_path) + +add_subdirectory(Code) diff --git a/Templates/PythonToolGem/Template/Code/${NameLower}_editor_files.cmake b/Templates/PythonToolGem/Template/Code/${NameLower}_editor_files.cmake new file mode 100644 index 0000000000..88aaea843f --- /dev/null +++ b/Templates/PythonToolGem/Template/Code/${NameLower}_editor_files.cmake @@ -0,0 +1,15 @@ +# {BEGIN_LICENSE} +# Copyright (c) Contributors to the Open 3D Engine Project. +# For complete copyright and license terms please see the LICENSE at the root of this distribution. +# +# SPDX-License-Identifier: Apache-2.0 OR MIT +# +# {END_LICENSE} + +set(FILES + Include/${Name}/${Name}Bus.h + Source/${Name}ModuleInterface.h + Source/${Name}EditorSystemComponent.cpp + Source/${Name}EditorSystemComponent.h + Source/${Name}.qrc +) diff --git a/Templates/PythonToolGem/Template/Code/${NameLower}_editor_shared_files.cmake b/Templates/PythonToolGem/Template/Code/${NameLower}_editor_shared_files.cmake new file mode 100644 index 0000000000..2d4ceae97d --- /dev/null +++ b/Templates/PythonToolGem/Template/Code/${NameLower}_editor_shared_files.cmake @@ -0,0 +1,11 @@ +# {BEGIN_LICENSE} +# Copyright (c) Contributors to the Open 3D Engine Project. +# For complete copyright and license terms please see the LICENSE at the root of this distribution. +# +# SPDX-License-Identifier: Apache-2.0 OR MIT +# +# {END_LICENSE} + +set(FILES + Source/${Name}EditorModule.cpp +) diff --git a/Templates/PythonToolGem/Template/Code/${NameLower}_editor_tests_files.cmake b/Templates/PythonToolGem/Template/Code/${NameLower}_editor_tests_files.cmake new file mode 100644 index 0000000000..ff45c2fc1c --- /dev/null +++ b/Templates/PythonToolGem/Template/Code/${NameLower}_editor_tests_files.cmake @@ -0,0 +1,11 @@ +# {BEGIN_LICENSE} +# Copyright (c) Contributors to the Open 3D Engine Project. +# For complete copyright and license terms please see the LICENSE at the root of this distribution. +# +# SPDX-License-Identifier: Apache-2.0 OR MIT +# +# {END_LICENSE} + +set(FILES + Tests/${Name}EditorTest.cpp +) diff --git a/Templates/PythonToolGem/Template/Code/CMakeLists.txt b/Templates/PythonToolGem/Template/Code/CMakeLists.txt new file mode 100644 index 0000000000..a6044e717b --- /dev/null +++ b/Templates/PythonToolGem/Template/Code/CMakeLists.txt @@ -0,0 +1,77 @@ +# {BEGIN_LICENSE} +# Copyright (c) Contributors to the Open 3D Engine Project. +# For complete copyright and license terms please see the LICENSE at the root of this distribution. +# +# SPDX-License-Identifier: Apache-2.0 OR MIT +# +# {END_LICENSE} + +# Currently we are in the Code folder: ${CMAKE_CURRENT_LIST_DIR} +# Get the platform specific folder ${pal_dir} for the current folder: ${CMAKE_CURRENT_LIST_DIR}/Platform/${PAL_PLATFORM_NAME} +# Note: ly_get_list_relative_pal_filename will take care of the details for us, as this may be a restricted platform +# in which case it will see if that platform is present here or in the restricted folder. +# i.e. It could here in our gem : Gems/${Name}/Code/Platform/ or +# //Gems/${Name}/Code +ly_get_list_relative_pal_filename(pal_dir ${CMAKE_CURRENT_LIST_DIR}/Platform/${PAL_PLATFORM_NAME} ${o3de_gem_restricted_path} ${o3de_gem_path} ${o3de_gem_name}) + +# Now that we have the platform abstraction layer (PAL) folder for this folder, thats where we will find the +# traits for this platform. Traits for a platform are defines for things like whether or not something in this gem +# is supported by this platform. +include(${pal_dir}/PAL_${PAL_PLATFORM_NAME_LOWERCASE}.cmake) + + +# If we are on a host platform, we want to add the host tools targets like the ${Name}.Editor target which +# will also depend on ${Name}.Static +if(PAL_TRAIT_BUILD_HOST_TOOLS) + ly_add_target( + NAME ${Name}.Editor.Static STATIC + NAMESPACE Gem + AUTORCC + FILES_CMAKE + ${NameLower}_editor_files.cmake + INCLUDE_DIRECTORIES + PRIVATE + Source + PUBLIC + Include + BUILD_DEPENDENCIES + PUBLIC + AZ::AzToolsFramework + ) + + ly_add_target( + NAME ${Name}.Editor GEM_MODULE + NAMESPACE Gem + AUTOMOC + FILES_CMAKE + ${NameLower}_editor_shared_files.cmake + INCLUDE_DIRECTORIES + PRIVATE + Source + PUBLIC + Include + BUILD_DEPENDENCIES + PUBLIC + Gem::${Name}.Editor.Static + ) + + # By default, we will specify that the above target ${Name} would be used by + # Tool and Builder type targets when this gem is enabled. If you don't want it + # active in Tools or Builders by default, delete one of both of the following lines: + ly_create_alias(NAME ${Name}.Tools NAMESPACE Gem TARGETS Gem::${Name}.Editor) + ly_create_alias(NAME ${Name}.Builders NAMESPACE Gem TARGETS Gem::${Name}.Editor) + + +endif() + +################################################################################ +# Tests +################################################################################ +# See if globally, tests are supported +if(PAL_TRAIT_BUILD_TESTS_SUPPORTED) + # We globally support tests, see if we support tests on this platform for ${Name}.Static + + # If we are a host platform we want to add tools test like editor tests here + if(PAL_TRAIT_BUILD_HOST_TOOLS) + endif() +endif() diff --git a/Templates/PythonToolGem/Template/Code/Include/${Name}/${Name}Bus.h b/Templates/PythonToolGem/Template/Code/Include/${Name}/${Name}Bus.h new file mode 100644 index 0000000000..d09bb2b009 --- /dev/null +++ b/Templates/PythonToolGem/Template/Code/Include/${Name}/${Name}Bus.h @@ -0,0 +1,40 @@ +// {BEGIN_LICENSE} +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ +// {END_LICENSE} + +#pragma once + +#include +#include + +namespace ${SanitizedCppName} +{ + class ${SanitizedCppName}Requests + { + public: + AZ_RTTI(${SanitizedCppName}Requests, "{${Random_Uuid}}"); + virtual ~${SanitizedCppName}Requests() = default; + // Put your public methods here + }; + + class ${SanitizedCppName}BusTraits + : public AZ::EBusTraits + { + public: + ////////////////////////////////////////////////////////////////////////// + // EBusTraits overrides + static constexpr AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single; + static constexpr AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single; + ////////////////////////////////////////////////////////////////////////// + }; + + using ${SanitizedCppName}RequestBus = AZ::EBus<${SanitizedCppName}Requests, ${SanitizedCppName}BusTraits>; + using ${SanitizedCppName}Interface = AZ::Interface<${SanitizedCppName}Requests>; + +} // namespace ${SanitizedCppName} diff --git a/Templates/PythonToolGem/Template/Code/Platform/Linux/${NameLower}_linux_files.cmake b/Templates/PythonToolGem/Template/Code/Platform/Linux/${NameLower}_linux_files.cmake new file mode 100644 index 0000000000..2f58a2e6f5 --- /dev/null +++ b/Templates/PythonToolGem/Template/Code/Platform/Linux/${NameLower}_linux_files.cmake @@ -0,0 +1,15 @@ +# {BEGIN_LICENSE} +# Copyright (c) Contributors to the Open 3D Engine Project. +# For complete copyright and license terms please see the LICENSE at the root of this distribution. +# +# SPDX-License-Identifier: Apache-2.0 OR MIT +# +# {END_LICENSE} + +# Platform specific files for Linux +# i.e. ../Source/Linux/${Name}Linux.cpp +# ../Source/Linux/${Name}Linux.h +# ../Include/Linux/${Name}Linux.h + +set(FILES +) diff --git a/Templates/PythonToolGem/Template/Code/Platform/Linux/${NameLower}_shared_linux_files.cmake b/Templates/PythonToolGem/Template/Code/Platform/Linux/${NameLower}_shared_linux_files.cmake new file mode 100644 index 0000000000..2f58a2e6f5 --- /dev/null +++ b/Templates/PythonToolGem/Template/Code/Platform/Linux/${NameLower}_shared_linux_files.cmake @@ -0,0 +1,15 @@ +# {BEGIN_LICENSE} +# Copyright (c) Contributors to the Open 3D Engine Project. +# For complete copyright and license terms please see the LICENSE at the root of this distribution. +# +# SPDX-License-Identifier: Apache-2.0 OR MIT +# +# {END_LICENSE} + +# Platform specific files for Linux +# i.e. ../Source/Linux/${Name}Linux.cpp +# ../Source/Linux/${Name}Linux.h +# ../Include/Linux/${Name}Linux.h + +set(FILES +) diff --git a/Templates/PythonToolGem/Template/Code/Platform/Linux/PAL_linux.cmake b/Templates/PythonToolGem/Template/Code/Platform/Linux/PAL_linux.cmake new file mode 100644 index 0000000000..0abcd887e8 --- /dev/null +++ b/Templates/PythonToolGem/Template/Code/Platform/Linux/PAL_linux.cmake @@ -0,0 +1,11 @@ +# {BEGIN_LICENSE} +# Copyright (c) Contributors to the Open 3D Engine Project. +# For complete copyright and license terms please see the LICENSE at the root of this distribution. +# +# SPDX-License-Identifier: Apache-2.0 OR MIT +# +# {END_LICENSE} + +set(PAL_TRAIT_${NameUpper}_SUPPORTED TRUE) +set(PAL_TRAIT_${NameUpper}_TEST_SUPPORTED TRUE) +set(PAL_TRAIT_${NameUpper}_EDITOR_TEST_SUPPORTED TRUE) \ No newline at end of file diff --git a/Templates/PythonToolGem/Template/Code/Platform/Mac/${NameLower}_mac_files.cmake b/Templates/PythonToolGem/Template/Code/Platform/Mac/${NameLower}_mac_files.cmake new file mode 100644 index 0000000000..1cf737a2f1 --- /dev/null +++ b/Templates/PythonToolGem/Template/Code/Platform/Mac/${NameLower}_mac_files.cmake @@ -0,0 +1,15 @@ +# {BEGIN_LICENSE} +# Copyright (c) Contributors to the Open 3D Engine Project. +# For complete copyright and license terms please see the LICENSE at the root of this distribution. +# +# SPDX-License-Identifier: Apache-2.0 OR MIT +# +# {END_LICENSE} + +# Platform specific files for Mac +# i.e. ../Source/Mac/${Name}Mac.cpp +# ../Source/Mac/${Name}Mac.h +# ../Include/Mac/${Name}Mac.h + +set(FILES +) diff --git a/Templates/PythonToolGem/Template/Code/Platform/Mac/${NameLower}_shared_mac_files.cmake b/Templates/PythonToolGem/Template/Code/Platform/Mac/${NameLower}_shared_mac_files.cmake new file mode 100644 index 0000000000..1cf737a2f1 --- /dev/null +++ b/Templates/PythonToolGem/Template/Code/Platform/Mac/${NameLower}_shared_mac_files.cmake @@ -0,0 +1,15 @@ +# {BEGIN_LICENSE} +# Copyright (c) Contributors to the Open 3D Engine Project. +# For complete copyright and license terms please see the LICENSE at the root of this distribution. +# +# SPDX-License-Identifier: Apache-2.0 OR MIT +# +# {END_LICENSE} + +# Platform specific files for Mac +# i.e. ../Source/Mac/${Name}Mac.cpp +# ../Source/Mac/${Name}Mac.h +# ../Include/Mac/${Name}Mac.h + +set(FILES +) diff --git a/Templates/PythonToolGem/Template/Code/Platform/Mac/PAL_mac.cmake b/Templates/PythonToolGem/Template/Code/Platform/Mac/PAL_mac.cmake new file mode 100644 index 0000000000..0abcd887e8 --- /dev/null +++ b/Templates/PythonToolGem/Template/Code/Platform/Mac/PAL_mac.cmake @@ -0,0 +1,11 @@ +# {BEGIN_LICENSE} +# Copyright (c) Contributors to the Open 3D Engine Project. +# For complete copyright and license terms please see the LICENSE at the root of this distribution. +# +# SPDX-License-Identifier: Apache-2.0 OR MIT +# +# {END_LICENSE} + +set(PAL_TRAIT_${NameUpper}_SUPPORTED TRUE) +set(PAL_TRAIT_${NameUpper}_TEST_SUPPORTED TRUE) +set(PAL_TRAIT_${NameUpper}_EDITOR_TEST_SUPPORTED TRUE) \ No newline at end of file diff --git a/Templates/PythonToolGem/Template/Code/Platform/Windows/${NameLower}_shared_windows_files.cmake b/Templates/PythonToolGem/Template/Code/Platform/Windows/${NameLower}_shared_windows_files.cmake new file mode 100644 index 0000000000..712aad1207 --- /dev/null +++ b/Templates/PythonToolGem/Template/Code/Platform/Windows/${NameLower}_shared_windows_files.cmake @@ -0,0 +1,15 @@ +# {BEGIN_LICENSE} +# Copyright (c) Contributors to the Open 3D Engine Project. +# For complete copyright and license terms please see the LICENSE at the root of this distribution. +# +# SPDX-License-Identifier: Apache-2.0 OR MIT +# +# {END_LICENSE} + +# Platform specific files for Windows +# i.e. ../Source/Windows/${Name}Windows.cpp +# ../Source/Windows/${Name}Windows.h +# ../Include/Windows/${Name}Windows.h + +set(FILES +) diff --git a/Templates/PythonToolGem/Template/Code/Platform/Windows/${NameLower}_windows_files.cmake b/Templates/PythonToolGem/Template/Code/Platform/Windows/${NameLower}_windows_files.cmake new file mode 100644 index 0000000000..712aad1207 --- /dev/null +++ b/Templates/PythonToolGem/Template/Code/Platform/Windows/${NameLower}_windows_files.cmake @@ -0,0 +1,15 @@ +# {BEGIN_LICENSE} +# Copyright (c) Contributors to the Open 3D Engine Project. +# For complete copyright and license terms please see the LICENSE at the root of this distribution. +# +# SPDX-License-Identifier: Apache-2.0 OR MIT +# +# {END_LICENSE} + +# Platform specific files for Windows +# i.e. ../Source/Windows/${Name}Windows.cpp +# ../Source/Windows/${Name}Windows.h +# ../Include/Windows/${Name}Windows.h + +set(FILES +) diff --git a/Templates/PythonToolGem/Template/Code/Platform/Windows/PAL_windows.cmake b/Templates/PythonToolGem/Template/Code/Platform/Windows/PAL_windows.cmake new file mode 100644 index 0000000000..0abcd887e8 --- /dev/null +++ b/Templates/PythonToolGem/Template/Code/Platform/Windows/PAL_windows.cmake @@ -0,0 +1,11 @@ +# {BEGIN_LICENSE} +# Copyright (c) Contributors to the Open 3D Engine Project. +# For complete copyright and license terms please see the LICENSE at the root of this distribution. +# +# SPDX-License-Identifier: Apache-2.0 OR MIT +# +# {END_LICENSE} + +set(PAL_TRAIT_${NameUpper}_SUPPORTED TRUE) +set(PAL_TRAIT_${NameUpper}_TEST_SUPPORTED TRUE) +set(PAL_TRAIT_${NameUpper}_EDITOR_TEST_SUPPORTED TRUE) \ No newline at end of file diff --git a/Templates/PythonToolGem/Template/Code/Source/${Name}.qrc b/Templates/PythonToolGem/Template/Code/Source/${Name}.qrc new file mode 100644 index 0000000000..90d7695b88 --- /dev/null +++ b/Templates/PythonToolGem/Template/Code/Source/${Name}.qrc @@ -0,0 +1,5 @@ + + + toolbar_icon.svg + + diff --git a/Templates/PythonToolGem/Template/Code/Source/${Name}EditorModule.cpp b/Templates/PythonToolGem/Template/Code/Source/${Name}EditorModule.cpp new file mode 100644 index 0000000000..0027af011a --- /dev/null +++ b/Templates/PythonToolGem/Template/Code/Source/${Name}EditorModule.cpp @@ -0,0 +1,55 @@ +// {BEGIN_LICENSE} +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ +// {END_LICENSE} + +#include <${Name}ModuleInterface.h> +#include <${Name}EditorSystemComponent.h> + +void Init${SanitizedCppName}Resources() +{ + // We must register our Qt resources (.qrc file) since this is being loaded from a separate module (gem) + Q_INIT_RESOURCE(${SanitizedCppName}); +} + +namespace ${SanitizedCppName} +{ + class ${SanitizedCppName}EditorModule + : public ${SanitizedCppName}ModuleInterface + { + public: + AZ_RTTI(${SanitizedCppName}EditorModule, "${ModuleClassId}", ${SanitizedCppName}ModuleInterface); + AZ_CLASS_ALLOCATOR(${SanitizedCppName}EditorModule, AZ::SystemAllocator, 0); + + ${SanitizedCppName}EditorModule() + { + Init${SanitizedCppName}Resources(); + + // Push results of [MyComponent]::CreateDescriptor() into m_descriptors here. + // Add ALL components descriptors associated with this gem to m_descriptors. + // This will associate the AzTypeInfo information for the components with the the SerializeContext, BehaviorContext and EditContext. + // This happens through the [MyComponent]::Reflect() function. + m_descriptors.insert(m_descriptors.end(), { + ${SanitizedCppName}EditorSystemComponent::CreateDescriptor(), + }); + } + + /** + * Add required SystemComponents to the SystemEntity. + * Non-SystemComponents should not be added here + */ + AZ::ComponentTypeList GetRequiredSystemComponents() const override + { + return AZ::ComponentTypeList { + azrtti_typeid<${SanitizedCppName}EditorSystemComponent>(), + }; + } + }; +}// namespace ${SanitizedCppName} + +AZ_DECLARE_MODULE_CLASS(Gem_${SanitizedCppName}, ${SanitizedCppName}::${SanitizedCppName}EditorModule) diff --git a/Templates/PythonToolGem/Template/Code/Source/${Name}EditorSystemComponent.cpp b/Templates/PythonToolGem/Template/Code/Source/${Name}EditorSystemComponent.cpp new file mode 100644 index 0000000000..1493c98e68 --- /dev/null +++ b/Templates/PythonToolGem/Template/Code/Source/${Name}EditorSystemComponent.cpp @@ -0,0 +1,70 @@ +// {BEGIN_LICENSE} +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + // {END_LICENSE} + +#include +#include <${Name}EditorSystemComponent.h> + +namespace ${SanitizedCppName} +{ + void ${SanitizedCppName}EditorSystemComponent::Reflect(AZ::ReflectContext* context) + { + if (auto serializeContext = azrtti_cast(context)) + { + serializeContext->Class<${SanitizedCppName}EditorSystemComponent, AZ::Component>(); + } + } + + ${SanitizedCppName}EditorSystemComponent::${SanitizedCppName}EditorSystemComponent() + { + if (${SanitizedCppName}Interface::Get() == nullptr) + { + ${SanitizedCppName}Interface::Register(this); + } + } + + ${SanitizedCppName}EditorSystemComponent::~${SanitizedCppName}EditorSystemComponent() + { + if (${SanitizedCppName}Interface::Get() == this) + { + ${SanitizedCppName}Interface::Unregister(this); + } + } + + void ${SanitizedCppName}EditorSystemComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided) + { + provided.push_back(AZ_CRC_CE("${SanitizedCppName}EditorService")); + } + + void ${SanitizedCppName}EditorSystemComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible) + { + incompatible.push_back(AZ_CRC_CE("${SanitizedCppName}EditorService")); + } + + void ${SanitizedCppName}EditorSystemComponent::GetRequiredServices([[maybe_unused]] AZ::ComponentDescriptor::DependencyArrayType& required) + { + } + + void ${SanitizedCppName}EditorSystemComponent::GetDependentServices([[maybe_unused]] AZ::ComponentDescriptor::DependencyArrayType& dependent) + { + } + + void ${SanitizedCppName}EditorSystemComponent::Activate() + { + ${SanitizedCppName}RequestBus::Handler::BusConnect(); + AzToolsFramework::EditorEvents::Bus::Handler::BusConnect(); + } + + void ${SanitizedCppName}EditorSystemComponent::Deactivate() + { + AzToolsFramework::EditorEvents::Bus::Handler::BusDisconnect(); + ${SanitizedCppName}RequestBus::Handler::BusDisconnect(); + } + +} // namespace ${SanitizedCppName} diff --git a/Templates/PythonToolGem/Template/Code/Source/${Name}EditorSystemComponent.h b/Templates/PythonToolGem/Template/Code/Source/${Name}EditorSystemComponent.h new file mode 100644 index 0000000000..1db8725a9e --- /dev/null +++ b/Templates/PythonToolGem/Template/Code/Source/${Name}EditorSystemComponent.h @@ -0,0 +1,42 @@ +// {BEGIN_LICENSE} +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + // {END_LICENSE} + +#pragma once +#include +#include <${Name}/${Name}Bus.h> + +#include + +namespace ${SanitizedCppName} +{ + /// System component for ${SanitizedCppName} editor + class ${SanitizedCppName}EditorSystemComponent + : public ${SanitizedCppName}RequestBus::Handler + , private AzToolsFramework::EditorEvents::Bus::Handler + , public AZ::Component + { + public: + AZ_COMPONENT(${SanitizedCppName}EditorSystemComponent, "${EditorSysCompClassId}"); + static void Reflect(AZ::ReflectContext* context); + + ${SanitizedCppName}EditorSystemComponent(); + ~${SanitizedCppName}EditorSystemComponent(); + + private: + static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided); + static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible); + static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required); + static void GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent); + + // AZ::Component + void Activate(); + void Deactivate(); + }; +} // namespace ${SanitizedCppName} diff --git a/Templates/PythonToolGem/Template/Code/Source/${Name}ModuleInterface.h b/Templates/PythonToolGem/Template/Code/Source/${Name}ModuleInterface.h new file mode 100644 index 0000000000..4ddfc9c007 --- /dev/null +++ b/Templates/PythonToolGem/Template/Code/Source/${Name}ModuleInterface.h @@ -0,0 +1,36 @@ +// {BEGIN_LICENSE} +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ +// {END_LICENSE} + +#include +#include + +namespace ${SanitizedCppName} +{ + class ${SanitizedCppName}ModuleInterface + : public AZ::Module + { + public: + AZ_RTTI(${SanitizedCppName}ModuleInterface, "{${Random_Uuid}}", AZ::Module); + AZ_CLASS_ALLOCATOR(${SanitizedCppName}ModuleInterface, AZ::SystemAllocator, 0); + + ${SanitizedCppName}ModuleInterface() + { + } + + /** + * Add required SystemComponents to the SystemEntity. + */ + AZ::ComponentTypeList GetRequiredSystemComponents() const override + { + return AZ::ComponentTypeList{ + }; + } + }; +}// namespace ${SanitizedCppName} diff --git a/Templates/PythonToolGem/Template/Code/Source/toolbar_icon.svg b/Templates/PythonToolGem/Template/Code/Source/toolbar_icon.svg new file mode 100644 index 0000000000..59de66961c --- /dev/null +++ b/Templates/PythonToolGem/Template/Code/Source/toolbar_icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Templates/PythonToolGem/Template/Code/Tests/${Name}EditorTest.cpp b/Templates/PythonToolGem/Template/Code/Tests/${Name}EditorTest.cpp new file mode 100644 index 0000000000..9b84575fa0 --- /dev/null +++ b/Templates/PythonToolGem/Template/Code/Tests/${Name}EditorTest.cpp @@ -0,0 +1,13 @@ +// {BEGIN_LICENSE} +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ +// {END_LICENSE} + +#include + +AZ_UNIT_TEST_HOOK(DEFAULT_UNIT_TEST_ENV); diff --git a/Templates/PythonToolGem/Template/Editor/Scripts/${NameLower}_dialog.py b/Templates/PythonToolGem/Template/Editor/Scripts/${NameLower}_dialog.py new file mode 100644 index 0000000000..19194ec97f --- /dev/null +++ b/Templates/PythonToolGem/Template/Editor/Scripts/${NameLower}_dialog.py @@ -0,0 +1,35 @@ +""" +Copyright (c) Contributors to the Open 3D Engine Project. +For complete copyright and license terms please see the LICENSE at the root of this distribution. + +SPDX-License-Identifier: Apache-2.0 OR MIT +""" +# ------------------------------------------------------------------------- +"""${SanitizedCppName}\\editor\\scripts\\${SanitizedCppName}_dialog.py +Generated from O3DE PythonToolGem Template""" + +from PySide2.QtCore import Qt +from PySide2.QtWidgets import QDialog, QLabel, QVBoxLayout + +class ${SanitizedCppName}Dialog(QDialog): + def __init__(self, parent=None): + super(${SanitizedCppName}Dialog, self).__init__(parent) + + self.mainLayout = QVBoxLayout(self) + + self.introLabel = QLabel("Put your cool stuff here!") + + self.mainLayout.addWidget(self.introLabel, 0, Qt.AlignCenter) + + self.helpText = str("For help getting started," + "visit the UI Development documentation
" + "or come ask a question in the sig-ui-ux channel on Discord") + + self.helpLabel = QLabel() + self.helpLabel.setTextFormat(Qt.RichText) + self.helpLabel.setText(self.helpText) + self.helpLabel.setOpenExternalLinks(True) + + self.mainLayout.addWidget(self.helpLabel, 0, Qt.AlignCenter) + + self.setLayout(self.mainLayout) diff --git a/Templates/PythonToolGem/Template/Editor/Scripts/__init__.py b/Templates/PythonToolGem/Template/Editor/Scripts/__init__.py new file mode 100644 index 0000000000..b5da0c7ff0 --- /dev/null +++ b/Templates/PythonToolGem/Template/Editor/Scripts/__init__.py @@ -0,0 +1,9 @@ +""" +Copyright (c) Contributors to the Open 3D Engine Project. +For complete copyright and license terms please see the LICENSE at the root of this distribution. + +SPDX-License-Identifier: Apache-2.0 OR MIT +""" +# ------------------------------------------------------------------------- + +__ALL__ = ['bootstrap','${NameLower}_dialog'] \ No newline at end of file diff --git a/Templates/PythonToolGem/Template/Editor/Scripts/bootstrap.py b/Templates/PythonToolGem/Template/Editor/Scripts/bootstrap.py new file mode 100644 index 0000000000..d49babfa96 --- /dev/null +++ b/Templates/PythonToolGem/Template/Editor/Scripts/bootstrap.py @@ -0,0 +1,22 @@ +""" +Copyright (c) Contributors to the Open 3D Engine Project. +For complete copyright and license terms please see the LICENSE at the root of this distribution. + +SPDX-License-Identifier: Apache-2.0 OR MIT +""" +# ------------------------------------------------------------------------- +"""${SanitizedCppName}\\editor\\scripts\\boostrap.py +Generated from O3DE PythonToolGem Template""" + +import az_qt_helpers +import azlmbr.editor as editor +from ${NameLower}_dialog import ${SanitizedCppName}Dialog + +if __name__ == "__main__": + print("${SanitizedCppName}.boostrap, Generated from O3DE PythonToolGem Template") + + # Register our custom widget as a dockable tool with the Editor under an Examples sub-menu + options = editor.ViewPaneOptions() + options.showOnToolsToolbar = True + options.toolbarIcon = ":/${Name}/toolbar_icon.svg" + az_qt_helpers.register_view_pane('${SanitizedCppName}', ${SanitizedCppName}Dialog, category="Examples", options=options) diff --git a/Templates/PythonToolGem/Template/gem.json b/Templates/PythonToolGem/Template/gem.json new file mode 100644 index 0000000000..353ad6bf8d --- /dev/null +++ b/Templates/PythonToolGem/Template/gem.json @@ -0,0 +1,16 @@ +{ + "gem_name": "${Name}", + "display_name": "${Name}", + "license": "What license ${Name} uses goes here: i.e. https://opensource.org/licenses/MIT", + "origin": "The primary repo for ${Name} goes here: i.e. http://www.mydomain.com", + "type": "Code", + "summary": "A short description of ${Name}.", + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + "${Name}" + ], + "icon_path": "preview.png", + "requirements": "" +} diff --git a/Templates/PythonToolGem/Template/preview.png b/Templates/PythonToolGem/Template/preview.png new file mode 100644 index 0000000000..0f393ac886 --- /dev/null +++ b/Templates/PythonToolGem/Template/preview.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7ac9dd09bde78f389e3725ac49d61eff109857e004840bc0bc3881739df9618d +size 2217 diff --git a/Templates/PythonToolGem/template.json b/Templates/PythonToolGem/template.json new file mode 100644 index 0000000000..6dc68de3fc --- /dev/null +++ b/Templates/PythonToolGem/template.json @@ -0,0 +1,234 @@ +{ + "template_name": "PythonToolGem", + "restricted_name": "o3de", + "restricted_platform_relative_path": "Templates", + "origin": "The primary repo for PythonToolGem goes here: i.e. http://www.mydomain.com", + "license": "What license PythonToolGem uses goes here: i.e. https://opensource.org/licenses/MIT", + "display_name": "PythonToolGem", + "summary": "A gem template for a custom tool in Python that gets registered with the Editor.", + "canonical_tags": [], + "user_tags": [ + "PythonToolGem" + ], + "icon_path": "preview.png", + "copyFiles": [ + { + "file": ".gitignore", + "origin": ".gitignore", + "isTemplated": false, + "isOptional": false + }, + { + "file": "CMakeLists.txt", + "origin": "CMakeLists.txt", + "isTemplated": true, + "isOptional": false + }, + { + "file": "Code/${NameLower}_editor_files.cmake", + "origin": "Code/${NameLower}_editor_files.cmake", + "isTemplated": true, + "isOptional": false + }, + { + "file": "Code/${NameLower}_editor_shared_files.cmake", + "origin": "Code/${NameLower}_editor_shared_files.cmake", + "isTemplated": true, + "isOptional": false + }, + { + "file": "Code/${NameLower}_editor_tests_files.cmake", + "origin": "Code/${NameLower}_editor_tests_files.cmake", + "isTemplated": true, + "isOptional": false + }, + { + "file": "Code/CMakeLists.txt", + "origin": "Code/CMakeLists.txt", + "isTemplated": true, + "isOptional": false + }, + { + "file": "Code/Include/${Name}/${Name}Bus.h", + "origin": "Code/Include/${Name}/${Name}Bus.h", + "isTemplated": true, + "isOptional": false + }, + { + "file": "Code/Platform/Linux/${NameLower}_linux_files.cmake", + "origin": "Code/Platform/Linux/${NameLower}_linux_files.cmake", + "isTemplated": true, + "isOptional": false + }, + { + "file": "Code/Platform/Linux/${NameLower}_shared_linux_files.cmake", + "origin": "Code/Platform/Linux/${NameLower}_shared_linux_files.cmake", + "isTemplated": true, + "isOptional": false + }, + { + "file": "Code/Platform/Linux/PAL_linux.cmake", + "origin": "Code/Platform/Linux/PAL_linux.cmake", + "isTemplated": true, + "isOptional": false + }, + { + "file": "Code/Platform/Mac/${NameLower}_mac_files.cmake", + "origin": "Code/Platform/Mac/${NameLower}_mac_files.cmake", + "isTemplated": true, + "isOptional": false + }, + { + "file": "Code/Platform/Mac/${NameLower}_shared_mac_files.cmake", + "origin": "Code/Platform/Mac/${NameLower}_shared_mac_files.cmake", + "isTemplated": true, + "isOptional": false + }, + { + "file": "Code/Platform/Mac/PAL_mac.cmake", + "origin": "Code/Platform/Mac/PAL_mac.cmake", + "isTemplated": true, + "isOptional": false + }, + { + "file": "Code/Platform/Windows/${NameLower}_shared_windows_files.cmake", + "origin": "Code/Platform/Windows/${NameLower}_shared_windows_files.cmake", + "isTemplated": true, + "isOptional": false + }, + { + "file": "Code/Platform/Windows/${NameLower}_windows_files.cmake", + "origin": "Code/Platform/Windows/${NameLower}_windows_files.cmake", + "isTemplated": true, + "isOptional": false + }, + { + "file": "Code/Platform/Windows/PAL_windows.cmake", + "origin": "Code/Platform/Windows/PAL_windows.cmake", + "isTemplated": true, + "isOptional": false + }, + { + "file": "Code/Source/${Name}.qrc", + "origin": "Code/Source/${Name}.qrc", + "isTemplated": true, + "isOptional": false + }, + { + "file": "Code/Source/${Name}EditorModule.cpp", + "origin": "Code/Source/${Name}EditorModule.cpp", + "isTemplated": true, + "isOptional": false + }, + { + "file": "Code/Source/${Name}EditorSystemComponent.cpp", + "origin": "Code/Source/${Name}EditorSystemComponent.cpp", + "isTemplated": true, + "isOptional": false + }, + { + "file": "Code/Source/${Name}EditorSystemComponent.h", + "origin": "Code/Source/${Name}EditorSystemComponent.h", + "isTemplated": true, + "isOptional": false + }, + { + "file": "Code/Source/${Name}ModuleInterface.h", + "origin": "Code/Source/${Name}ModuleInterface.h", + "isTemplated": true, + "isOptional": false + }, + { + "file": "Code/Source/toolbar_icon.svg", + "origin": "Code/Source/toolbar_icon.svg", + "isTemplated": false, + "isOptional": false + }, + { + "file": "Code/Tests/${Name}EditorTest.cpp", + "origin": "Code/Tests/${Name}EditorTest.cpp", + "isTemplated": true, + "isOptional": false + }, + { + "file": "Editor/Scripts/__init__.py", + "origin": "Editor/Scripts/__init__.py", + "isTemplated": true, + "isOptional": false + }, + { + "file": "Editor/Scripts/bootstrap.py", + "origin": "Editor/Scripts/bootstrap.py", + "isTemplated": true, + "isOptional": false + }, + { + "file": "Editor/Scripts/${NameLower}_dialog.py", + "origin": "Editor/Scripts/${NameLower}_dialog.py", + "isTemplated": true, + "isOptional": false + }, + { + "file": "gem.json", + "origin": "gem.json", + "isTemplated": true, + "isOptional": false + }, + { + "file": "preview.png", + "origin": "preview.png", + "isTemplated": false, + "isOptional": false + } + ], + "createDirectories": [ + { + "dir": "Assets", + "origin": "Assets" + }, + { + "dir": "Code", + "origin": "Code" + }, + { + "dir": "Editor", + "origin": "Editor" + }, + { + "dir": "Editor/Scripts", + "origin": "Editor/Scripts" + }, + { + "dir": "Code/Include", + "origin": "Code/Include" + }, + { + "dir": "Code/Include/${Name}", + "origin": "Code/Include/${Name}" + }, + { + "dir": "Code/Platform", + "origin": "Code/Platform" + }, + { + "dir": "Code/Platform/Linux", + "origin": "Code/Platform/Linux" + }, + { + "dir": "Code/Platform/Mac", + "origin": "Code/Platform/Mac" + }, + { + "dir": "Code/Platform/Windows", + "origin": "Code/Platform/Windows" + }, + { + "dir": "Code/Source", + "origin": "Code/Source" + }, + { + "dir": "Code/Tests", + "origin": "Code/Tests" + } + ] +} diff --git a/Tools/LyTestTools/ly_test_tools/_internal/pytest_plugin/editor_test.py b/Tools/LyTestTools/ly_test_tools/_internal/pytest_plugin/editor_test.py index 80a562977b..1e9d4c3654 100644 --- a/Tools/LyTestTools/ly_test_tools/_internal/pytest_plugin/editor_test.py +++ b/Tools/LyTestTools/ly_test_tools/_internal/pytest_plugin/editor_test.py @@ -3,34 +3,49 @@ Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution. SPDX-License-Identifier: Apache-2.0 OR MIT -""" +Utility for specifying an Editor test, supports seamless parallelization and/or batching of tests. This is not a set of +tools to directly invoke, but a plugin with functions intended to be called by only the Pytest framework. """ -Utility for specifying an Editor test, supports seamless parallelization and/or batching of tests. -""" - +from __future__ import annotations import pytest import inspect __test__ = False -def pytest_addoption(parser): +def pytest_addoption(parser: argparse.ArgumentParser) -> None: + """ + Options when running editor tests in batches or parallel. + :param parser: The ArgumentParser object + :return: None + """ parser.addoption("--no-editor-batch", action="store_true", help="Don't batch multiple tests in single editor") parser.addoption("--no-editor-parallel", action="store_true", help="Don't run multiple editors in parallel") parser.addoption("--editors-parallel", type=int, action="store", help="Override the number editors to run at the same time") -# Create a custom custom item collection if the class defines pytest_custom_makeitem function -# This is used for automtically generating test functions with a custom collector -def pytest_pycollect_makeitem(collector, name, obj): +def pytest_pycollect_makeitem(collector: PyCollector, name: str, obj: object) -> PyCollector: + """ + Create a custom custom item collection if the class defines pytest_custom_makeitem function. This is used for + automatically generating test functions with a custom collector. + :param collector: The Pytest collector + :param name: Name of the collector + :param obj: The custom collector, normally an EditorTestSuite.EditorTestClass object + :return: Returns the custom collector + """ if inspect.isclass(obj): for base in obj.__bases__: if hasattr(base, "pytest_custom_makeitem"): return base.pytest_custom_makeitem(collector, name, obj) -# Add custom modification of items. -# This is used for adding the runners into the item list @pytest.hookimpl(hookwrapper=True) -def pytest_collection_modifyitems(session, items, config): +def pytest_collection_modifyitems(session: Session, items: list[EditorTestBase], config: Config) -> None: + """ + Add custom modification of items. This is used for adding the runners into the item list. + :param session: The Pytest Session + :param items: The test case functions + :param config: The Pytest Config object + :return: None + """ all_classes = set() for item in items: all_classes.add(item.instance.__class__) @@ -40,4 +55,4 @@ def pytest_collection_modifyitems(session, items, config): for cls in all_classes: if hasattr(cls, "pytest_custom_modify_items"): cls.pytest_custom_modify_items(session, items, config) - + diff --git a/Tools/LyTestTools/ly_test_tools/_internal/pytest_plugin/test_tools_fixtures.py b/Tools/LyTestTools/ly_test_tools/_internal/pytest_plugin/test_tools_fixtures.py index f4bda9b293..af29064766 100755 --- a/Tools/LyTestTools/ly_test_tools/_internal/pytest_plugin/test_tools_fixtures.py +++ b/Tools/LyTestTools/ly_test_tools/_internal/pytest_plugin/test_tools_fixtures.py @@ -55,14 +55,6 @@ def pytest_configure(config): ly_test_tools._internal.pytest_plugin.build_directory = _get_build_directory(config) ly_test_tools._internal.pytest_plugin.output_path = _get_output_path(config) - -def pytest_pycollect_makeitem(collector, name, obj): - import inspect - if inspect.isclass(obj): - for base in obj.__bases__: - if hasattr(base, "pytest_custom_makeitem"): - return base.pytest_custom_makeitem(collector, name, obj) - def _get_build_directory(config): """ Fetch and verify the cmake build directory CLI arg, without creating an error when unset diff --git a/Tools/LyTestTools/ly_test_tools/launchers/launcher_helper.py b/Tools/LyTestTools/ly_test_tools/launchers/launcher_helper.py index d37623f352..5e96f34e95 100755 --- a/Tools/LyTestTools/ly_test_tools/launchers/launcher_helper.py +++ b/Tools/LyTestTools/ly_test_tools/launchers/launcher_helper.py @@ -9,14 +9,15 @@ Main launchers module, provides a facade for creating launchers. import logging -import ly_test_tools._internal.managers.workspace import ly_test_tools +import ly_test_tools._internal.managers.workspace as workspace_manager +import ly_test_tools.launchers.platforms.base as base_launcher log = logging.getLogger(__name__) def create_launcher(workspace, launcher_platform=ly_test_tools.HOST_OS_PLATFORM, args=None): - # type: (ly_test_tools.managers.workspace.WorkspaceManager, str, List[str]) -> Launcher + # type: (workspace_manager.AbstractWorkspaceManager, str, list[str]) -> base_launcher.Launcher """ Create a launcher compatible with the specified workspace, if no specific launcher is found return a generic one. @@ -25,12 +26,16 @@ def create_launcher(workspace, launcher_platform=ly_test_tools.HOST_OS_PLATFORM, :param args: List of arguments to pass to the launcher's 'args' argument during construction :return: Launcher instance """ - launcher_class = ly_test_tools.LAUNCHERS.get(launcher_platform, ly_test_tools.HOST_OS_PLATFORM) + launcher_class = ly_test_tools.LAUNCHERS.get(launcher_platform) + if not launcher_class: + log.warning(f"Using default launcher for '{ly_test_tools.HOST_OS_PLATFORM}' " + f"as no option is available for '{launcher_platform}'") + launcher_class = ly_test_tools.LAUNCHERS.get(ly_test_tools.HOST_OS_PLATFORM) return launcher_class(workspace, args) def create_dedicated_launcher(workspace, launcher_platform=ly_test_tools.HOST_OS_DEDICATED_SERVER, args=None): - # type: (ly_test_tools.managers.workspace.WorkspaceManager, str, List[str]) -> Launcher + # type: (workspace_manager.AbstractWorkspaceManager, str, list[str]) -> base_launcher.Launcher """ Create a dedicated launcher compatible with the specified workspace. Dedicated Launcher is only supported on the Linux and Windows Platform @@ -40,27 +45,35 @@ def create_dedicated_launcher(workspace, launcher_platform=ly_test_tools.HOST_OS :param args: List of arguments to pass to the launcher's 'args' argument during construction :return: Launcher instance """ - launcher_class = ly_test_tools.LAUNCHERS.get(launcher_platform, ly_test_tools.HOST_OS_DEDICATED_SERVER) + launcher_class = ly_test_tools.LAUNCHERS.get(launcher_platform) + if not launcher_class: + log.warning(f"Using default dedicated launcher for '{ly_test_tools.HOST_OS_DEDICATED_SERVER}' " + f"as no option is available for '{launcher_platform}'") + launcher_class = ly_test_tools.LAUNCHERS.get(ly_test_tools.HOST_OS_DEDICATED_SERVER) return launcher_class(workspace, args) def create_editor(workspace, launcher_platform=ly_test_tools.HOST_OS_EDITOR, args=None): - # type: (ly_test_tools.managers.workspace.WorkspaceManager, str, List[str]) -> Launcher + # type: (workspace_manager.AbstractWorkspaceManager, str, list[str]) -> base_launcher.Launcher """ Create an Editor compatible with the specified workspace. Editor is only officially supported on the Windows Platform. :param workspace: lumberyard workspace to use - :param launcher_platform: the platform to target for a launcher (i.e. 'windows_dedicated' for DedicatedWinLauncher) + :param launcher_platform: the platform to target for a launcher (i.e. 'windows_dedicated' for DedicatedWinLauncher) :param args: List of arguments to pass to the launcher's 'args' argument during construction :return: Editor instance """ - launcher_class = ly_test_tools.LAUNCHERS.get(launcher_platform, ly_test_tools.HOST_OS_EDITOR) + launcher_class = ly_test_tools.LAUNCHERS.get(launcher_platform) + if not launcher_class: + log.warning(f"Using default editor launcher for '{ly_test_tools.HOST_OS_EDITOR}' " + f"as no option is available for '{launcher_platform}'") + launcher_class = ly_test_tools.LAUNCHERS.get(ly_test_tools.HOST_OS_EDITOR) return launcher_class(workspace, args) def create_generic_launcher(workspace, launcher_platform, exe_file_name, args=None): - # type: (ly_test_tools.managers.workspace.WorkspaceManager, str, str, List[str]) -> Launcher + # type: (workspace_manager.AbstractWorkspaceManager, str, str, list[str]) -> base_launcher.Launcher """ Create a generic launcher compatible with the specified workspace. Allows custom .exe files to serve as the launcher instead of ones listed in the ly_test_tools.LAUNCHERS constant @@ -71,5 +84,9 @@ def create_generic_launcher(workspace, launcher_platform, exe_file_name, args=No :param args: List of arguments to pass to the launcher's 'args' argument during construction :return: Launcher instance. """ - launcher_class = ly_test_tools.LAUNCHERS.get(launcher_platform, ly_test_tools.HOST_OS_GENERIC_EXECUTABLE) + launcher_class = ly_test_tools.LAUNCHERS.get(launcher_platform) + if not launcher_class: + log.warning(f"Using default generic executable launcher for '{ly_test_tools.HOST_OS_GENERIC_EXECUTABLE}' " + f"as no option is available for '{launcher_platform}'") + launcher_class = ly_test_tools.LAUNCHERS.get(ly_test_tools.HOST_OS_GENERIC_EXECUTABLE) return launcher_class(workspace, exe_file_name, args) diff --git a/Tools/LyTestTools/ly_test_tools/o3de/asset_processor.py b/Tools/LyTestTools/ly_test_tools/o3de/asset_processor.py index 682fcd3560..9f8ee9649e 100644 --- a/Tools/LyTestTools/ly_test_tools/o3de/asset_processor.py +++ b/Tools/LyTestTools/ly_test_tools/o3de/asset_processor.py @@ -195,8 +195,11 @@ class AssetProcessor(object): logger.debug("Failed to read port from file", exc_info=ex) return False + # the timeout needs to be large enough to load all the dynamic libraries the AP-GUI loads since the control port + # is opened after all the DLL loads, this can take a long time in a Debug build + ap_max_activate_time = 60 err = AssetProcessorError(f"Failed to read port type {port_type} from {self._workspace.paths.ap_gui_log()}") - waiter.wait_for(_get_port_from_log, timeout=10, exc=err) + waiter.wait_for(_get_port_from_log, timeout=ap_max_activate_time, exc=err) return port def set_control_connection(self, connection): @@ -424,8 +427,9 @@ class AssetProcessor(object): def batch_process(self, timeout=DEFAULT_TIMEOUT_SECONDS, fastscan=True, capture_output=False, platforms=None, extra_params=None, add_gem_scan_folders=None, add_config_scan_folders=None, decode=True, - expect_failure=False, scan_folder_pattern=None): - self.create_temp_log_root() + expect_failure=False, scan_folder_pattern=None, create_temp_log=True): + if create_temp_log: + self.create_temp_log_root() ap_path = self._workspace.paths.asset_processor_batch() command = self.build_ap_command(ap_path=ap_path, fastscan=fastscan, platforms=platforms, extra_params=extra_params, add_gem_scan_folders=add_gem_scan_folders, @@ -439,7 +443,7 @@ class AssetProcessor(object): def gui_process(self, timeout=DEFAULT_TIMEOUT_SECONDS, fastscan=True, capture_output=False, platforms=None, extra_params=None, add_gem_scan_folders=None, add_config_scan_folders=None, decode=True, expect_failure=False, quitonidle=False, connect_to_ap=False, accept_input=True, run_until_idle=True, - scan_folder_pattern=None): + scan_folder_pattern=None, create_temp_log=True): ap_path = os.path.abspath(self._workspace.paths.asset_processor()) ap_exe_path = os.path.dirname(ap_path) extra_gui_params = [] @@ -469,7 +473,8 @@ class AssetProcessor(object): else: extra_gui_params.append(extra_params) - self.create_temp_log_root() + if create_temp_log: + self.create_temp_log_root() command = self.build_ap_command(ap_path=ap_path, fastscan=fastscan, platforms=platforms, extra_params=extra_gui_params, add_gem_scan_folders=add_gem_scan_folders, add_config_scan_folders=add_config_scan_folders, @@ -577,7 +582,7 @@ class AssetProcessor(object): if isinstance(platforms, list): platforms = ','.join(platforms) command.append(f'--platforms={platforms}') - for key, value in self._enabled_platform_overrides: + for key, value in self._enabled_platform_overrides.items(): command.append(f'--regset="f{ASSET_PROCESSOR_SETTINGS_ROOT_KEY}/Platforms/{key}={value}"') if extra_params: diff --git a/Tools/LyTestTools/ly_test_tools/o3de/editor_test.py b/Tools/LyTestTools/ly_test_tools/o3de/editor_test.py index 781977cf33..ae6ed16321 100644 --- a/Tools/LyTestTools/ly_test_tools/o3de/editor_test.py +++ b/Tools/LyTestTools/ly_test_tools/o3de/editor_test.py @@ -3,8 +3,29 @@ Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution. SPDX-License-Identifier: Apache-2.0 OR MIT -""" +This file provides editor testing functionality to easily write automated editor tests for O3DE. +For using these utilities, you can subclass your test suite from EditorTestSuite, this allows an easy way of +specifying python test scripts that the editor will run without needing to write any boilerplace code. +It supports out of the box parallelization(running multiple editor instances at once), batching(running multiple tests +in the same editor instance) and crash detection. +Usage example: + class MyTestSuite(EditorTestSuite): + + class MyFirstTest(EditorSingleTest): + from . import script_to_be_run_by_editor as test_module + + class MyTestInParallel_1(EditorParallelTest): + from . import another_script_to_be_run_by_editor as test_module + + class MyTestInParallel_2(EditorParallelTest): + from . import yet_another_script_to_be_run_by_editor as test_module + + +EditorTestSuite does introspection of the defined classes inside of it and automatically prepares the tests, +parallelizing/batching as required +""" +from __future__ import annotations import pytest from _pytest.skipping import pytest_runtest_setup as skipping_pytest_runtest_setup @@ -25,30 +46,11 @@ import re import ly_test_tools.environment.file_system as file_system import ly_test_tools.environment.waiter as waiter import ly_test_tools.environment.process_utils as process_utils +import ly_test_tools.o3de.editor_test +import ly_test_tools.o3de.editor_test_utils as editor_utils from ly_test_tools.o3de.asset_processor import AssetProcessor from ly_test_tools.launchers.exceptions import WaitTimeoutError -from . import editor_test_utils as editor_utils - -# This file provides editor testing functionality to easily write automated editor tests for O3DE. -# For using these utilities, you can subclass your test suite from EditorTestSuite, this allows an easy way of specifying -# python test scripts that the editor will run without needing to write any boilerplace code. -# It supports out of the box parallelization(running multiple editor instances at once), batching(running multiple tests in the same editor instance) and -# crash detection. -# Usage example: -# class MyTestSuite(EditorTestSuite): -# -# class MyFirstTest(EditorSingleTest): -# from . import script_to_be_run_by_editor as test_module -# -# class MyTestInParallel_1(EditorParallelTest): -# from . import another_script_to_be_run_by_editor as test_module -# -# class MyTestInParallel_2(EditorParallelTest): -# from . import yet_another_script_to_be_run_by_editor as test_module -# -# -# EditorTestSuite does introspection of the defined classes inside of it and automatically prepares the tests, parallelizing/batching as required # This file contains no tests, but with this we make sure it won't be picked up by the runner since the file ends with _test __test__ = False @@ -109,12 +111,22 @@ class EditorBatchedTest(EditorSharedTest): class Result: class Base: def get_output_str(self): + # type () -> str + """ + Checks if the output attribute exists and returns it. + :return: Either the output string or a no output message + """ if hasattr(self, "output") and self.output is not None: return self.output else: return "-- No output --" def get_editor_log_str(self): + # type () -> str + """ + Checks if the editor_log attribute exists and returns it. + :return: Either the editor_log string or a no output message + """ if hasattr(self, "editor_log") and self.editor_log is not None: return self.editor_log else: @@ -122,7 +134,14 @@ class Result: class Pass(Base): @classmethod - def create(cls, test_spec : EditorTestBase, output : str, editor_log : str): + def create(cls, test_spec: EditorTestBase, output: str, editor_log: str) -> Pass: + """ + Creates a Pass object with a given test spec, output string, and editor log string. + :test_spec: The type of EditorTestBase + :output: The test output + :editor_log: The editor log's output + :return: the Pass object + """ r = cls() r.test_spec = test_spec r.output = output @@ -141,7 +160,14 @@ class Result: class Fail(Base): @classmethod - def create(cls, test_spec : EditorTestBase, output, editor_log : str): + def create(cls, test_spec: EditorTestBase, output: str, editor_log: str) -> Fail: + """ + Creates a Fail object with a given test spec, output string, and editor log string. + :test_spec: The type of EditorTestBase + :output: The test output + :editor_log: The editor log's output + :return: the Fail object + """ r = cls() r.test_spec = test_spec r.output = output @@ -164,7 +190,17 @@ class Result: class Crash(Base): @classmethod - def create(cls, test_spec : EditorTestBase, output : str, ret_code : int, stacktrace : str, editor_log : str): + def create(cls, test_spec: EditorTestBase, output: str, ret_code: int, stacktrace: str, editor_log: str) -> Crash: + """ + Creates a Crash object with a given test spec, output string, and editor log string. This also includes the + return code and stacktrace. + :test_spec: The type of EditorTestBase + :output: The test output + :ret_code: The test's return code + :stacktrace: The test's stacktrace if available + :editor_log: The editor log's output + :return: The Crash object + """ r = cls() r.output = output r.test_spec = test_spec @@ -190,12 +226,20 @@ class Result: f"--------------\n" f"{self.get_editor_log_str()}\n" ) - crash_str = "-- No crash information found --" return output class Timeout(Base): @classmethod - def create(cls, test_spec : EditorTestBase, output : str, time_secs : float, editor_log : str): + def create(cls, test_spec: EditorTestBase, output: str, time_secs: float, editor_log: str) -> Timeout: + """ + Creates a Timeout object with a given test spec, output string, and editor log string. The timeout time + should be provided in seconds + :test_spec: The type of EditorTestBase + :output: The test output + :time_secs: The timeout duration in seconds + :editor_log: The editor log's output + :return: The Timeout object + """ r = cls() r.output = output r.test_spec = test_spec @@ -219,14 +263,22 @@ class Result: class Unknown(Base): @classmethod - def create(cls, test_spec : EditorTestBase, output : str, extra_info : str, editor_log : str): + def create(cls, test_spec: EditorTestBase, output: str, extra_info: str, editor_log: str) -> Unknown: + """ + Creates an Unknown test results object if something goes wrong. + :test_spec: The type of EditorTestBase + :output: The test output + :extra_info: Any extra information as a string + :editor_log: The editor log's output + :return: The Unknown object + """ r = cls() r.output = output r.test_spec = test_spec r.editor_log = editor_log r.extra_info = extra_info return r - + def __str__(self): output = ( f"Unknown test result, possible cause: {self.extra_info}\n" @@ -262,7 +314,19 @@ class EditorTestSuite(): _TEST_FAIL_RETCODE = 0xF # Return code for test failure @pytest.fixture(scope="class") - def editor_test_data(self, request): + def editor_test_data(self, request: Request) -> TestData: + """ + Yields a per-testsuite structure to store the data of each test result and an AssetProcessor object that will be + re-used on the whole suite + :request: The Pytest request + :yield: The TestData object + """ + yield from self._editor_test_data(request) + + def _editor_test_data(self, request: Request) -> TestData: + """ + A wrapper function for unit testing to call directly + """ class TestData(): def __init__(self): self.results = {} # Dict of str(test_spec.__name__) -> Result @@ -444,9 +508,15 @@ class EditorTestSuite(): return EditorTestSuite.EditorTestClass(name, collector) @classmethod - def pytest_custom_modify_items(cls, session, items, config): - # Add here the runners functions and filter the tests that will be run. - # The runners will be added if they have any selected tests + def pytest_custom_modify_items(cls, session: Session, items: list[EditorTestBase], config: Config) -> None: + """ + Adds the runners' functions and filters the tests that will run. The runners will be added if they have any + selected tests + :param session: The Pytest Session + :param items: The test case functions + :param config: The Pytest Config object + :return: None + """ new_items = [] for runner in cls._runners: runner.tests[:] = cls.filter_session_shared_tests(items, runner.tests) @@ -462,24 +532,50 @@ class EditorTestSuite(): items[:] = items + new_items @classmethod - def get_single_tests(cls): + def get_single_tests(cls) -> list[EditorSingleTest]: + """ + Grabs all of the EditorSingleTests subclassed tests from the EditorTestSuite class + Usage example: + class MyTestSuite(EditorTestSuite): + class MyFirstTest(EditorSingleTest): + from . import script_to_be_run_by_editor as test_module + :return: The list of single tests + """ single_tests = [c[1] for c in cls.__dict__.items() if inspect.isclass(c[1]) and issubclass(c[1], EditorSingleTest)] return single_tests @classmethod - def get_shared_tests(cls): + def get_shared_tests(cls) -> list[EditorSharedTest]: + """ + Grabs all of the EditorSharedTests from the EditorTestSuite + Usage example: + class MyTestSuite(EditorTestSuite): + class MyFirstTest(EditorSharedTest): + from . import script_to_be_run_by_editor as test_module + :return: The list of shared tests + """ shared_tests = [c[1] for c in cls.__dict__.items() if inspect.isclass(c[1]) and issubclass(c[1], EditorSharedTest)] return shared_tests @classmethod - def get_session_shared_tests(cls, session): + def get_session_shared_tests(cls, session: Session) -> list[EditorTestBase]: + """ + Filters and returns all of the shared tests in a given session. + :session: The test session + :return: The list of tests + """ shared_tests = cls.get_shared_tests() return cls.filter_session_shared_tests(session, shared_tests) @staticmethod - def filter_session_shared_tests(session_items, shared_tests): - # Retrieve the test sub-set that was collected - # this can be less than the original set if were overriden via -k argument or similars + def filter_session_shared_tests(session_items: list[EditorTestBase], shared_tests: list[EditorSharedTest]) -> list[EditorTestBase]: + """ + Retrieve the test sub-set that was collected this can be less than the original set if were overriden via -k + argument or similars + :session_items: The tests in a session to run + :shared_tests: All of the shared tests + :return: The list of filtered tests + """ def will_run(item): try: skipping_pytest_runtest_setup(item) @@ -488,13 +584,20 @@ class EditorTestSuite(): return False session_items_by_name = { item.originalname:item for item in session_items } - selected_shared_tests = [test for test in shared_tests if test.__name__ in session_items_by_name.keys() and will_run(session_items_by_name[test.__name__])] + selected_shared_tests = [test for test in shared_tests if test.__name__ in session_items_by_name.keys() and + will_run(session_items_by_name[test.__name__])] return selected_shared_tests @staticmethod - def filter_shared_tests(shared_tests, is_batchable=False, is_parallelizable=False): - # Retrieve the test sub-set that was collected - # this can be less than the original set if were overriden via -k argument or similars + def filter_shared_tests(shared_tests: list[EditorSharedTest], is_batchable: bool = False, + is_parallelizable: bool = False) -> list[EditorSharedTest]: + """ + Filters and returns all tests based off of if they are batchable and/or parallelizable + :shared_tests: All shared tests + :is_batchable: Filter to batchable tests + :is_parallelizable: Filter to parallelizable tests + :return: The list of filtered tests + """ return [ t for t in shared_tests if ( getattr(t, "is_batchable", None) is is_batchable @@ -504,9 +607,14 @@ class EditorTestSuite(): ] ### Utils ### - - # Prepares the asset processor for the test - def _prepare_asset_processor(self, workspace, editor_test_data): + def _prepare_asset_processor(self, workspace: AbstractWorkspace, editor_test_data: TestData) -> None: + """ + Prepares the asset processor for the test depending on whether or not the process is open and if the current + test owns it. + :workspace: The workspace object in case an AssetProcessor object needs to be created + :editor_test_data: The test data from calling editor_test_data() + :return: None + """ try: # Start-up an asset processor if we are not running one # If another AP process exist, don't kill it, as we don't own it @@ -524,15 +632,28 @@ class EditorTestSuite(): editor_test_data.asset_processor = None raise ex - def _setup_editor_test(self, editor, workspace, editor_test_data): + def _setup_editor_test(self, editor: Editor, workspace: AbstractWorkspace, editor_test_data: TestData) -> None: + """ + Sets up an editor test by preparing the Asset Processor, killing all other O3DE processes, and configuring + :editor: The launcher Editor object + :workspace: The test Workspace object + :editor_test_data: The TestData from calling editor_test_data() + :return: None + """ self._prepare_asset_processor(workspace, editor_test_data) editor_utils.kill_all_ly_processes(include_asset_processor=False) editor.configure_settings() - # Utility function for parsing the output information from the editor. - # It deserializes the JSON content printed in the output for every test and returns that information. @staticmethod - def _get_results_using_output(test_spec_list, output, editor_log_content): + def _get_results_using_output(test_spec_list: list[EditorTestBase], output: str, editor_log_content: str) -> dict[str, Result]: + """ + Utility function for parsing the output information from the editor. It deserializes the JSON content printed in + the output for every test and returns that information. + :test_spec_list: The list of EditorTests + :output: The Editor from Editor.get_output() + :editor_log_content: The contents of the editor log as a string + :return: A dict of the tests and their respective Result objects + """ results = {} pattern = re.compile(r"JSON_START\((.+?)\)JSON_END") out_matches = pattern.finditer(output) @@ -558,7 +679,9 @@ class EditorTestSuite(): for test_spec in test_spec_list: name = editor_utils.get_module_filename(test_spec.test_module) if name not in found_jsons.keys(): - results[test_spec.__name__] = Result.Unknown.create(test_spec, output, "Couldn't find any test run information on stdout", editor_log_content) + results[test_spec.__name__] = Result.Unknown.create(test_spec, output, + "Couldn't find any test run information on stdout", + editor_log_content) else: result = None json_result = found_jsons[name] @@ -581,9 +704,14 @@ class EditorTestSuite(): return results - # Fails the test if the test result is not a PASS, specifying the information @staticmethod - def _report_result(name : str, result : Result.Base): + def _report_result(name: str, result: Result) -> None: + """ + Fails the test if the test result is not a PASS, specifying the information + :name: Name of the test + :result: The Result object which denotes if the test passed or not + :return: None + """ if isinstance(result, Result.Pass): output_str = f"Test {name}:\n{str(result)}" print(output_str) @@ -592,10 +720,19 @@ class EditorTestSuite(): pytest.fail(error_str) ### Running tests ### - # Starts the editor with the given test and retuns an result dict with a single element specifying the result - def _exec_editor_test(self, request, workspace, editor, run_id : int, log_name : str, - test_spec : EditorTestBase, cmdline_args : List[str] = []): - + def _exec_editor_test(self, request: Request, workspace: AbstractWorkspace, editor: Editor, run_id: int, + log_name: str, test_spec: EditorTestBase, cmdline_args: list[str] = []) -> dict[str, Result]: + """ + Starts the editor with the given test and retuns an result dict with a single element specifying the result + :request: The pytest request + :workspace: The LyTestTools Workspace object + :editor: The LyTestTools Editor object + :run_id: The unique run id + :log_name: The name of the editor log to retrieve + :test_spec: The type of EditorTestBase + :cmdline_args: Any additional command line args + :return: a dictionary of Result objects + """ test_cmdline_args = self.global_extra_cmdline_args + cmdline_args test_spec_uses_null_renderer = getattr(test_spec, "use_null_renderer", None) if test_spec_uses_null_renderer or (test_spec_uses_null_renderer is None and self.use_null_renderer): @@ -629,12 +766,14 @@ class EditorTestSuite(): else: has_crashed = return_code != EditorTestSuite._TEST_FAIL_RETCODE if has_crashed: - test_result = Result.Crash.create(test_spec, output, return_code, editor_utils.retrieve_crash_output(run_id, workspace, self._TIMEOUT_CRASH_LOG), None) + test_result = Result.Crash.create(test_spec, output, return_code, editor_utils.retrieve_crash_output + (run_id, workspace, self._TIMEOUT_CRASH_LOG), None) editor_utils.cycle_crash_report(run_id, workspace) else: test_result = Result.Fail.create(test_spec, output, editor_log_content) except WaitTimeoutError: - editor.kill() + output = editor.get_output() + editor.kill() editor_log_content = editor_utils.retrieve_editor_log_content(run_id, log_name, workspace) test_result = Result.Timeout.create(test_spec, output, test_spec.timeout, editor_log_content) @@ -643,11 +782,21 @@ class EditorTestSuite(): results[test_spec.__name__] = test_result return results - # Starts an editor executable with a list of tests and returns a dict of the result of every test ran within that editor - # instance. In case of failure this function also parses the editor output to find out what specific tests failed - def _exec_editor_multitest(self, request, workspace, editor, run_id : int, log_name : str, - test_spec_list : List[EditorTestBase], cmdline_args=[]): - + def _exec_editor_multitest(self, request: Request, workspace: AbstractWorkspace, editor: Editor, run_id: int, log_name: str, + test_spec_list: list[EditorTestBase], cmdline_args: list[str] = []) -> dict[str, Result]: + """ + Starts an editor executable with a list of tests and returns a dict of the result of every test ran within that + editor instance. In case of failure this function also parses the editor output to find out what specific tests + failed. + :request: The pytest request + :workspace: The LyTestTools Workspace object + :editor: The LyTestTools Editor object + :run_id: The unique run id + :log_name: The name of the editor log to retrieve + :test_spec_list: A list of EditorTestBase tests to run in the same editor instance + :cmdline_args: Any additional command line args + :return: A dict of Result objects + """ test_cmdline_args = self.global_extra_cmdline_args + cmdline_args if self.use_null_renderer: test_cmdline_args += ["-rhi=null"] @@ -695,50 +844,66 @@ class EditorTestSuite(): if isinstance(result, Result.Unknown): if not crashed_result: # The first test with "Unknown" result (no data in output) is likely the one that crashed - crash_error = editor_utils.retrieve_crash_output(run_id, workspace, self._TIMEOUT_CRASH_LOG) + crash_error = editor_utils.retrieve_crash_output(run_id, workspace, + self._TIMEOUT_CRASH_LOG) editor_utils.cycle_crash_report(run_id, workspace) - results[test_spec_name] = Result.Crash.create(result.test_spec, output, return_code, crash_error, result.editor_log) + results[test_spec_name] = Result.Crash.create(result.test_spec, output, return_code, + crash_error, result.editor_log) crashed_result = result else: - # If there are remaning "Unknown" results, these couldn't execute because of the crash, update with info about the offender - results[test_spec_name].extra_info = f"This test has unknown result, test '{crashed_result.test_spec.__name__}' crashed before this test could be executed" - + # If there are remaning "Unknown" results, these couldn't execute because of the crash, + # update with info about the offender + results[test_spec_name].extra_info = f"This test has unknown result," \ + f"test '{crashed_result.test_spec.__name__}'" \ + f"crashed before this test could be executed" # if all the tests ran, the one that has caused the crash is the last test if not crashed_result: crash_error = editor_utils.retrieve_crash_output(run_id, workspace, self._TIMEOUT_CRASH_LOG) editor_utils.cycle_crash_report(run_id, workspace) - results[test_spec_name] = Result.Crash.create(crashed_result.test_spec, output, return_code, crash_error, crashed_result.editor_log) - - + results[test_spec_name] = Result.Crash.create(crashed_result.test_spec, output, return_code, + crash_error, crashed_result.editor_log) except WaitTimeoutError: editor.kill() - output = editor.get_output() editor_log_content = editor_utils.retrieve_editor_log_content(run_id, log_name, workspace) # The editor timed out when running the tests, get the data from the output to find out which ones ran results = self._get_results_using_output(test_spec_list, output, editor_log_content) assert len(results) == len(test_spec_list), "bug in _get_results_using_output(), the number of results don't match the tests ran" - # Similar logic here as crashes, the first test that has no result is the one that timed out timed_out_result = None for test_spec_name, result in results.items(): if isinstance(result, Result.Unknown): if not timed_out_result: - results[test_spec_name] = Result.Timeout.create(result.test_spec, result.output, self.timeout_editor_shared_test, result.editor_log) + results[test_spec_name] = Result.Timeout.create(result.test_spec, result.output, + self.timeout_editor_shared_test, + result.editor_log) timed_out_result = result else: - # If there are remaning "Unknown" results, these couldn't execute because of the timeout, update with info about the offender - results[test_spec_name].extra_info = f"This test has unknown result, test '{timed_out_result.test_spec.__name__}' timed out before this test could be executed" - + # If there are remaning "Unknown" results, these couldn't execute because of the timeout, + # update with info about the offender + results[test_spec_name].extra_info = f"This test has unknown result, test " \ + f"'{timed_out_result.test_spec.__name__}' timed out " \ + f"before this test could be executed" # if all the tests ran, the one that has caused the timeout is the last test, as it didn't close the editor if not timed_out_result: - results[test_spec_name] = Result.Timeout.create(timed_out_result.test_spec, results[test_spec_name].output, self.timeout_editor_shared_test, result.editor_log) + results[test_spec_name] = Result.Timeout.create(timed_out_result.test_spec, + results[test_spec_name].output, + self.timeout_editor_shared_test, result.editor_log) return results - # Runs a single test (one editor, one test) with the given specs - def _run_single_test(self, request, workspace, editor, editor_test_data, test_spec : EditorSingleTest): + def _run_single_test(self, request: Request, workspace: AbstractWorkspace, editor: Editor, + editor_test_data: TestData, test_spec: EditorSingleTest) -> None: + """ + Runs a single test (one editor, one test) with the given specs + :request: The Pytest Request + :workspace: The LyTestTools Workspace object + :editor: The LyTestTools Editor object + :editor_test_data: The TestData from calling editor_test_data() + :test_spec: The test class that should be a subclass of EditorSingleTest + :return: None + """ self._setup_editor_test(editor, workspace, editor_test_data) extra_cmdline_args = [] if hasattr(test_spec, "extra_cmdline_args"): @@ -749,18 +914,39 @@ class EditorTestSuite(): test_name, test_result = next(iter(results.items())) self._report_result(test_name, test_result) - # Runs a batch of tests in one single editor with the given spec list (one editor, multiple tests) - def _run_batched_tests(self, request, workspace, editor, editor_test_data, test_spec_list : List[EditorSharedTest], extra_cmdline_args=[]): + def _run_batched_tests(self, request: Request, workspace: AbstractWorkspace, editor: Editor, editor_test_data: TestData, + test_spec_list: list[EditorSharedTest], extra_cmdline_args: list[str] = []) -> None: + """ + Runs a batch of tests in one single editor with the given spec list (one editor, multiple tests) + :request: The Pytest Request + :workspace: The LyTestTools Workspace object + :editor: The LyTestTools Editor object + :editor_test_data: The TestData from calling editor_test_data() + :test_spec_list: A list of EditorSharedTest tests to run + :extra_cmdline_args: Any extra command line args in a list + :return: None + """ if not test_spec_list: return self._setup_editor_test(editor, workspace, editor_test_data) - results = self._exec_editor_multitest(request, workspace, editor, 1, "editor_test.log", test_spec_list, extra_cmdline_args) + results = self._exec_editor_multitest(request, workspace, editor, 1, "editor_test.log", test_spec_list, + extra_cmdline_args) assert results is not None editor_test_data.results.update(results) - # Runs multiple editors with one test on each editor (multiple editor, one test each) - def _run_parallel_tests(self, request, workspace, editor, editor_test_data, test_spec_list : List[EditorSharedTest], extra_cmdline_args=[]): + def _run_parallel_tests(self, request: Request, workspace: AbstractWorkspace, editor: Editor, editor_test_data: TestData, + test_spec_list: list[EditorSharedTest], extra_cmdline_args: list[str] = []) -> None: + """ + Runs multiple editors with one test on each editor (multiple editor, one test each) + :request: The Pytest Request + :workspace: The LyTestTools Workspace object + :editor: The LyTestTools Editor object + :editor_test_data: The TestData from calling editor_test_data() + :test_spec_list: A list of EditorSharedTest tests to run + :extra_cmdline_args: Any extra command line args in a list + :return: None + """ if not test_spec_list: return @@ -778,7 +964,8 @@ class EditorTestSuite(): for i in range(total_threads): def make_func(test_spec, index, my_editor): def run(request, workspace, extra_cmdline_args): - results = self._exec_editor_test(request, workspace, my_editor, index+1, f"editor_test.log", test_spec, extra_cmdline_args) + results = self._exec_editor_test(request, workspace, my_editor, index+1, f"editor_test.log", + test_spec, extra_cmdline_args) assert results is not None results_per_thread[index] = results return run @@ -796,8 +983,18 @@ class EditorTestSuite(): for result in results_per_thread: editor_test_data.results.update(result) - # Runs multiple editors with a batch of tests for each editor (multiple editor, multiple tests each) - def _run_parallel_batched_tests(self, request, workspace, editor, editor_test_data, test_spec_list : List[EditorSharedTest], extra_cmdline_args=[]): + def _run_parallel_batched_tests(self, request: Request, workspace: AbstractWorkspace, editor: Editor, editor_test_data: TestData, + test_spec_list: list[EditorSharedTest], extra_cmdline_args: list[str] = []) -> None: + """ + Runs multiple editors with a batch of tests for each editor (multiple editor, multiple tests each) + :request: The Pytest Request + :workspace: The LyTestTools Workspace object + :editor: The LyTestTools Editor object + :editor_test_data: The TestData from calling editor_test_data() + :test_spec_list: A list of EditorSharedTest tests to run + :extra_cmdline_args: Any extra command line args in a list + :return: None + """ if not test_spec_list: return @@ -813,7 +1010,9 @@ class EditorTestSuite(): def run(request, workspace, extra_cmdline_args): results = None if len(test_spec_list_for_editor) > 0: - results = self._exec_editor_multitest(request, workspace, my_editor, index+1, f"editor_test.log", test_spec_list_for_editor, extra_cmdline_args) + results = self._exec_editor_multitest(request, workspace, my_editor, index+1, + f"editor_test.log", test_spec_list_for_editor, + extra_cmdline_args) assert results is not None else: results = {} @@ -833,8 +1032,12 @@ class EditorTestSuite(): for result in results_per_thread: editor_test_data.results.update(result) - # Retrieves the number of parallel preference cmdline overrides - def _get_number_parallel_editors(self, request): + def _get_number_parallel_editors(self, request: Request) -> int: + """ + Retrieves the number of parallel preference cmdline overrides + :request: The Pytest Request + :return: The number of parallel editors to use + """ parallel_editors_value = request.config.getoption("--editors-parallel", None) if parallel_editors_value: return int(parallel_editors_value) diff --git a/Tools/LyTestTools/ly_test_tools/o3de/editor_test_utils.py b/Tools/LyTestTools/ly_test_tools/o3de/editor_test_utils.py index feff78d866..35fbe93d37 100644 --- a/Tools/LyTestTools/ly_test_tools/o3de/editor_test_utils.py +++ b/Tools/LyTestTools/ly_test_tools/o3de/editor_test_utils.py @@ -3,8 +3,10 @@ Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution. SPDX-License-Identifier: Apache-2.0 OR MIT -""" +Utility functions mostly for the editor_test module. They can also be used for assisting Editor tests. +""" +from __future__ import annotations import os import time import logging @@ -14,7 +16,13 @@ import ly_test_tools.environment.waiter as waiter logger = logging.getLogger(__name__) -def kill_all_ly_processes(include_asset_processor=True): +def kill_all_ly_processes(include_asset_processor: bool = True) -> None: + """ + Kills all common O3DE processes such as the Editor, Game Launchers, and optionally Asset Processor. Defaults to + killing the Asset Processor. + :param include_asset_processor: Boolean flag whether or not to kill the AP + :return: None + """ LY_PROCESSES = [ 'Editor', 'Profiler', 'RemoteConsole', ] @@ -27,8 +35,7 @@ def kill_all_ly_processes(include_asset_processor=True): else: process_utils.kill_processes_named(LY_PROCESSES, ignore_extensions=True) -def get_testcase_module_filepath(testcase_module): - # type: (Module) -> str +def get_testcase_module_filepath(testcase_module: Module) -> str: """ return the full path of the test module using always '.py' extension :param testcase_module: The testcase python module being tested @@ -36,8 +43,7 @@ def get_testcase_module_filepath(testcase_module): """ return os.path.splitext(testcase_module.__file__)[0] + ".py" -def get_module_filename(testcase_module): - # type: (Module) -> str +def get_module_filename(testcase_module: Module): """ return The filename of the module without path Note: This is differs from module.__name__ in the essence of not having the package directory. @@ -47,7 +53,7 @@ def get_module_filename(testcase_module): """ return os.path.splitext(os.path.basename(testcase_module.__file__))[0] -def retrieve_log_path(run_id : int, workspace): +def retrieve_log_path(run_id: int, workspace: AbstractWorkspaceManager) -> str: """ return the log/ project path for this test run. :param run_id: editor id that will be used for differentiating paths @@ -56,7 +62,7 @@ def retrieve_log_path(run_id : int, workspace): """ return os.path.join(workspace.paths.project(), "user", f"log_test_{run_id}") -def retrieve_crash_output(run_id : int, workspace, timeout : float): +def retrieve_crash_output(run_id: int, workspace: AbstractWorkspaceManager, timeout: float = 10) -> str: """ returns the crash output string for the given test run. :param run_id: editor id that will be used for differentiating paths @@ -79,7 +85,7 @@ def retrieve_crash_output(run_id : int, workspace, timeout : float): crash_info += f"\n{str(ex)}" return crash_info -def cycle_crash_report(run_id : int, workspace): +def cycle_crash_report(run_id: int, workspace: AbstractWorkspaceManager) -> None: """ Attempts to rename error.log and error.dmp(crash files) into new names with the timestamp on it. :param run_id: editor id that will be used for differentiating paths @@ -99,10 +105,11 @@ def cycle_crash_report(run_id : int, workspace): except Exception as ex: logger.warning(f"Couldn't cycle file {filepath}. Error: {str(ex)}") -def retrieve_editor_log_content(run_id : int, log_name : str, workspace, timeout=10): +def retrieve_editor_log_content(run_id: int, log_name: str, workspace: AbstractWorkspaceManager, timeout: int = 10) -> str: """ Retrieves the contents of the given editor log file. :param run_id: editor id that will be used for differentiating paths + :log_name: The name of the editor log to retrieve :param workspace: Workspace fixture :timeout: Maximum time to wait for the log file to appear :return str: The contents of the log @@ -124,7 +131,7 @@ def retrieve_editor_log_content(run_id : int, log_name : str, workspace, timeout editor_info = f"-- Error reading editor.log: {str(ex)} --" return editor_info -def retrieve_last_run_test_index_from_output(test_spec_list, output : str): +def retrieve_last_run_test_index_from_output(test_spec_list: list[EditorTestBase], output: str) -> int: """ Finds out what was the last test that was run by inspecting the input. This is used for determining what was the batched test has crashed the editor diff --git a/Tools/LyTestTools/tests/unit/test_editor_test_utils.py b/Tools/LyTestTools/tests/unit/test_editor_test_utils.py new file mode 100644 index 0000000000..bf7fae7189 --- /dev/null +++ b/Tools/LyTestTools/tests/unit/test_editor_test_utils.py @@ -0,0 +1,162 @@ +""" +Copyright (c) Contributors to the Open 3D Engine Project. +For complete copyright and license terms please see the LICENSE at the root of this distribution. + +SPDX-License-Identifier: Apache-2.0 OR MIT +""" +import pytest +import os +import unittest.mock as mock +import unittest + +import ly_test_tools.o3de.editor_test_utils as editor_test_utils + +pytestmark = pytest.mark.SUITE_smoke + +class TestEditorTestUtils(unittest.TestCase): + + @mock.patch('ly_test_tools.environment.process_utils.kill_processes_named') + def test_KillAllLyProcesses_IncludeAP_CallsCorrectly(self, mock_kill_processes_named): + process_list = ['Editor', 'Profiler', 'RemoteConsole', 'AssetProcessor', 'AssetProcessorBatch', 'AssetBuilder'] + + editor_test_utils.kill_all_ly_processes(include_asset_processor=True) + mock_kill_processes_named.assert_called_once_with(process_list, ignore_extensions=True) + + @mock.patch('ly_test_tools.environment.process_utils.kill_processes_named') + def test_KillAllLyProcesses_NotIncludeAP_CallsCorrectly(self, mock_kill_processes_named): + process_list = ['Editor', 'Profiler', 'RemoteConsole'] + ap_process_list = ['AssetProcessor', 'AssetProcessorBatch', 'AssetBuilder'] + + editor_test_utils.kill_all_ly_processes(include_asset_processor=False) + mock_kill_processes_named.assert_called_once() + assert ap_process_list not in mock_kill_processes_named.call_args[0] + + def test_GetTestcaseModuleFilepath_NoExtension_ReturnsPYExtension(self): + mock_module = mock.MagicMock() + file_path = os.path.join('path', 'under_test') + mock_module.__file__ = file_path + + assert file_path + '.py' == editor_test_utils.get_testcase_module_filepath(mock_module) + + def test_GetTestcaseModuleFilepath_PYExtension_ReturnsPYExtension(self): + mock_module = mock.MagicMock() + file_path = os.path.join('path', 'under_test.py') + mock_module.__file__ = file_path + + assert file_path == editor_test_utils.get_testcase_module_filepath(mock_module) + + def test_GetModuleFilename_PythonModule_ReturnsFilename(self): + mock_module = mock.MagicMock() + file_path = os.path.join('path', 'under_test.py') + mock_module.__file__ = file_path + + assert 'under_test' == editor_test_utils.get_module_filename(mock_module) + + def test_RetrieveLogPath_NormalProject_ReturnsLogPath(self): + mock_workspace = mock.MagicMock() + mock_workspace.paths.project.return_value = 'mock_project_path' + expected = os.path.join('mock_project_path', 'user', 'log_test_0') + + assert expected == editor_test_utils.retrieve_log_path(0, mock_workspace) + + @mock.patch('ly_test_tools.o3de.editor_test_utils.retrieve_log_path') + @mock.patch('ly_test_tools.environment.waiter.wait_for', mock.MagicMock()) + def test_RetrieveCrashOutput_CrashLogExists_ReturnsLogInfo(self, mock_retrieve_log_path): + mock_retrieve_log_path.return_value = 'mock_log_path' + mock_workspace = mock.MagicMock() + mock_log = 'mock crash info' + + with mock.patch('builtins.open', mock.mock_open(read_data=mock_log)) as mock_file: + assert mock_log == editor_test_utils.retrieve_crash_output(0, mock_workspace, 0) + + @mock.patch('ly_test_tools.o3de.editor_test_utils.retrieve_log_path') + @mock.patch('ly_test_tools.environment.waiter.wait_for', mock.MagicMock()) + def test_RetrieveCrashOutput_CrashLogNotExists_ReturnsError(self, mock_retrieve_log_path): + mock_retrieve_log_path.return_value = 'mock_log_path' + mock_workspace = mock.MagicMock() + expected = "-- No crash log available --\n[Errno 2] No such file or directory: 'mock_log_path\\\\error.log'" + + assert expected == editor_test_utils.retrieve_crash_output(0, mock_workspace, 0) + + @mock.patch('os.path.getmtime', mock.MagicMock()) + @mock.patch('os.rename') + @mock.patch('time.strftime') + @mock.patch('ly_test_tools.o3de.editor_test_utils.retrieve_log_path') + @mock.patch('os.path.exists') + def test_CycleCrashReport_DmpExists_NamedCorrectly(self, mock_exists, mock_retrieve_log_path, mock_strftime, + mock_rename): + mock_exists.side_effect = [False, True] + mock_retrieve_log_path.return_value = 'mock_log_path' + mock_workspace = mock.MagicMock() + mock_strftime.return_value = 'mock_strftime' + + editor_test_utils.cycle_crash_report(0, mock_workspace) + mock_rename.assert_called_once_with(os.path.join('mock_log_path', 'error.dmp'), + os.path.join('mock_log_path', 'error_mock_strftime.dmp')) + + @mock.patch('os.path.getmtime', mock.MagicMock()) + @mock.patch('os.rename') + @mock.patch('time.strftime') + @mock.patch('ly_test_tools.o3de.editor_test_utils.retrieve_log_path') + @mock.patch('os.path.exists') + def test_CycleCrashReport_LogExists_NamedCorrectly(self, mock_exists, mock_retrieve_log_path, mock_strftime, + mock_rename): + mock_exists.side_effect = [True, False] + mock_retrieve_log_path.return_value = 'mock_log_path' + mock_workspace = mock.MagicMock() + mock_strftime.return_value = 'mock_strftime' + + editor_test_utils.cycle_crash_report(0, mock_workspace) + mock_rename.assert_called_once_with(os.path.join('mock_log_path', 'error.log'), + os.path.join('mock_log_path', 'error_mock_strftime.log')) + + @mock.patch('ly_test_tools.o3de.editor_test_utils.retrieve_log_path') + @mock.patch('ly_test_tools.environment.waiter.wait_for', mock.MagicMock()) + def test_RetrieveEditorLogContent_CrashLogExists_ReturnsLogInfo(self, mock_retrieve_log_path): + mock_retrieve_log_path.return_value = 'mock_log_path' + mock_logname = 'mock_log.log' + mock_workspace = mock.MagicMock() + mock_log = 'mock log info' + + with mock.patch('builtins.open', mock.mock_open(read_data=mock_log)) as mock_file: + assert f'[editor.log] {mock_log}' == editor_test_utils.retrieve_editor_log_content(0, mock_logname, mock_workspace) + + @mock.patch('ly_test_tools.o3de.editor_test_utils.retrieve_log_path') + @mock.patch('ly_test_tools.environment.waiter.wait_for', mock.MagicMock()) + def test_RetrieveEditorLogContent_CrashLogNotExists_ReturnsError(self, mock_retrieve_log_path): + mock_retrieve_log_path.return_value = 'mock_log_path' + mock_logname = 'mock_log.log' + mock_workspace = mock.MagicMock() + expected = f"-- Error reading editor.log" + + assert expected in editor_test_utils.retrieve_editor_log_content(0, mock_logname, mock_workspace) + + def test_RetrieveLastRunTestIndexFromOutput_SecondTestFailed_Returns0(self): + mock_test = mock.MagicMock() + mock_test.__name__ = 'mock_test_name' + mock_test_list = [mock_test] + mock_editor_output = 'mock_test_name\n' \ + 'mock_test_name_1' + + assert 0 == editor_test_utils.retrieve_last_run_test_index_from_output(mock_test_list, mock_editor_output) + + def test_RetrieveLastRunTestIndexFromOutput_TenthTestFailed_Returns9(self): + mock_test_list = [] + mock_editor_output = '' + for x in range(10): + mock_test = mock.MagicMock() + mock_test.__name__ = f'mock_test_name_{x}' + mock_test_list.append(mock_test) + mock_editor_output += f'{mock_test.__name__}\n' + mock_editor_output += 'mock_test_name_x' + assert 9 == editor_test_utils.retrieve_last_run_test_index_from_output(mock_test_list, mock_editor_output) + + def test_RetrieveLastRunTestIndexFromOutput_FirstItemFailed_Returns0(self): + mock_test_list = [] + mock_editor_output = '' + for x in range(10): + mock_test = mock.MagicMock() + mock_test.__name__ = f'mock_test_name_{x}' + mock_test_list.append(mock_test) + + assert 0 == editor_test_utils.retrieve_last_run_test_index_from_output(mock_test_list, mock_editor_output) diff --git a/Tools/LyTestTools/tests/unit/test_launcher_base.py b/Tools/LyTestTools/tests/unit/test_launcher_base.py index 1ab9129a7d..5264e1bc28 100755 --- a/Tools/LyTestTools/tests/unit/test_launcher_base.py +++ b/Tools/LyTestTools/tests/unit/test_launcher_base.py @@ -221,3 +221,10 @@ class TestLauncherBuilder(object): under_test = ly_test_tools.launchers.launcher_helper.create_editor( dummy_workspace, ly_test_tools.HOST_OS_GENERIC_EXECUTABLE) assert isinstance(under_test, ly_test_tools.launchers.Launcher) + + def test_CreateEditor_InvalidPlatform_ValidLauncherStillReturned(self): + dummy_workspace = mock.MagicMock() + dummy_workspace.paths.build_directory.return_value = 'dummy' + under_test = ly_test_tools.launchers.launcher_helper.create_editor( + dummy_workspace, 'does not exist') + assert isinstance(under_test, ly_test_tools.launchers.Launcher) diff --git a/Tools/LyTestTools/tests/unit/test_o3de_editor_test.py b/Tools/LyTestTools/tests/unit/test_o3de_editor_test.py new file mode 100644 index 0000000000..3f6c23ea3d --- /dev/null +++ b/Tools/LyTestTools/tests/unit/test_o3de_editor_test.py @@ -0,0 +1,1099 @@ +""" +Copyright (c) Contributors to the Open 3D Engine Project. +For complete copyright and license terms please see the LICENSE at the root of this distribution. + +SPDX-License-Identifier: Apache-2.0 OR MIT +""" +import unittest + +import pytest +import unittest.mock as mock + +import ly_test_tools +import ly_test_tools.o3de.editor_test as editor_test + +pytestmark = pytest.mark.SUITE_smoke + +class TestEditorTestBase(unittest.TestCase): + + def test_EditorSharedTest_Init_CorrectAttributes(self): + mock_editorsharedtest = editor_test.EditorSharedTest() + assert mock_editorsharedtest.is_batchable == True + assert mock_editorsharedtest.is_parallelizable == True + + def test_EditorParallelTest_Init_CorrectAttributes(self): + mock_editorsharedtest = editor_test.EditorParallelTest() + assert mock_editorsharedtest.is_batchable == False + assert mock_editorsharedtest.is_parallelizable == True + + def test_EditorBatchedTest_Init_CorrectAttributes(self): + mock_editorsharedtest = editor_test.EditorBatchedTest() + assert mock_editorsharedtest.is_batchable == True + assert mock_editorsharedtest.is_parallelizable == False + +class TestResultBase(unittest.TestCase): + + def setUp(self): + self.mock_result = editor_test.Result.Base() + + def test_GetOutputStr_HasOutput_ReturnsCorrectly(self): + self.mock_result.output = 'expected output' + assert self.mock_result.get_output_str() == 'expected output' + + def test_GetOutputStr_NoOutput_ReturnsCorrectly(self): + self.mock_result.output = None + assert self.mock_result.get_output_str() == '-- No output --' + + def test_GetEditorLogStr_HasOutput_ReturnsCorrectly(self): + self.mock_result.editor_log = 'expected log output' + assert self.mock_result.get_editor_log_str() == 'expected log output' + + def test_GetEditorLogStr_NoOutput_ReturnsCorrectly(self): + self.mock_result.editor_log = None + assert self.mock_result.get_editor_log_str() == '-- No editor log found --' + +class TestResultPass(unittest.TestCase): + + def test_Create_ValidArgs_CorrectAttributes(self): + mock_test_spec = mock.MagicMock() + mock_output = mock.MagicMock() + mock_editor_log = mock.MagicMock() + + mock_pass = editor_test.Result.Pass.create(mock_test_spec, mock_output, mock_editor_log) + assert mock_pass.test_spec == mock_test_spec + assert mock_pass.output == mock_output + assert mock_pass.editor_log == mock_editor_log + + def test_Str_ValidString_ReturnsOutput(self): + mock_test_spec = mock.MagicMock() + mock_output = 'mock_output' + mock_editor_log = mock.MagicMock() + + mock_pass = editor_test.Result.Pass.create(mock_test_spec, mock_output, mock_editor_log) + assert mock_output in str(mock_pass) + +class TestResultFail(unittest.TestCase): + + def test_Create_ValidArgs_CorrectAttributes(self): + mock_test_spec = mock.MagicMock() + mock_output = mock.MagicMock() + mock_editor_log = mock.MagicMock() + + mock_pass = editor_test.Result.Fail.create(mock_test_spec, mock_output, mock_editor_log) + assert mock_pass.test_spec == mock_test_spec + assert mock_pass.output == mock_output + assert mock_pass.editor_log == mock_editor_log + + def test_Str_ValidString_ReturnsOutput(self): + mock_test_spec = mock.MagicMock() + mock_output = 'mock_output' + mock_editor_log = 'mock_editor_log' + + mock_pass = editor_test.Result.Fail.create(mock_test_spec, mock_output, mock_editor_log) + assert mock_output in str(mock_pass) + assert mock_editor_log in str(mock_pass) + +class TestResultCrash(unittest.TestCase): + + def test_Create_ValidArgs_CorrectAttributes(self): + mock_test_spec = mock.MagicMock() + mock_output = mock.MagicMock() + mock_editor_log = mock.MagicMock() + mock_ret_code = mock.MagicMock() + mock_stacktrace = mock.MagicMock() + + mock_pass = editor_test.Result.Crash.create(mock_test_spec, mock_output, mock_ret_code, mock_stacktrace, + mock_editor_log) + assert mock_pass.test_spec == mock_test_spec + assert mock_pass.output == mock_output + assert mock_pass.editor_log == mock_editor_log + assert mock_pass.ret_code == mock_ret_code + assert mock_pass.stacktrace == mock_stacktrace + + def test_Str_ValidString_ReturnsOutput(self): + mock_test_spec = mock.MagicMock() + mock_output = 'mock_output' + mock_editor_log = 'mock_editor_log' + mock_return_code = 0 + mock_stacktrace = 'mock stacktrace' + + mock_pass = editor_test.Result.Crash.create(mock_test_spec, mock_output, mock_return_code, mock_stacktrace, + mock_editor_log) + assert mock_stacktrace in str(mock_pass) + assert mock_output in str(mock_pass) + assert mock_editor_log in str(mock_pass) + + def test_Str_MissingStackTrace_ReturnsCorrectly(self): + mock_test_spec = mock.MagicMock() + mock_output = 'mock_output' + mock_editor_log = 'mock_editor_log' + mock_return_code = 0 + mock_stacktrace = None + mock_pass = editor_test.Result.Crash.create(mock_test_spec, mock_output, mock_return_code, mock_stacktrace, + mock_editor_log) + assert mock_output in str(mock_pass) + assert mock_editor_log in str(mock_pass) + +class TestResultTimeout(unittest.TestCase): + + def test_Create_ValidArgs_CorrectAttributes(self): + mock_test_spec = mock.MagicMock() + mock_output = mock.MagicMock() + mock_editor_log = mock.MagicMock() + mock_timeout = mock.MagicMock() + + mock_pass = editor_test.Result.Timeout.create(mock_test_spec, mock_output, mock_timeout, mock_editor_log) + assert mock_pass.test_spec == mock_test_spec + assert mock_pass.output == mock_output + assert mock_pass.editor_log == mock_editor_log + assert mock_pass.time_secs == mock_timeout + + def test_Str_ValidString_ReturnsOutput(self): + mock_test_spec = mock.MagicMock() + mock_output = 'mock_output' + mock_editor_log = 'mock_editor_log' + mock_timeout = 0 + + mock_pass = editor_test.Result.Timeout.create(mock_test_spec, mock_output, mock_timeout, mock_editor_log) + assert mock_output in str(mock_pass) + assert mock_editor_log in str(mock_pass) + +class TestResultUnknown(unittest.TestCase): + + def test_Create_ValidArgs_CorrectAttributes(self): + mock_test_spec = mock.MagicMock() + mock_output = mock.MagicMock() + mock_editor_log = mock.MagicMock() + mock_extra_info = mock.MagicMock() + + mock_pass = editor_test.Result.Unknown.create(mock_test_spec, mock_output, mock_extra_info, mock_editor_log) + assert mock_pass.test_spec == mock_test_spec + assert mock_pass.output == mock_output + assert mock_pass.editor_log == mock_editor_log + assert mock_pass.extra_info == mock_extra_info + + def test_Str_ValidString_ReturnsOutput(self): + mock_test_spec = mock.MagicMock() + mock_output = 'mock_output' + mock_editor_log = 'mock_editor_log' + mock_extra_info = 'mock extra info' + + mock_pass = editor_test.Result.Unknown.create(mock_test_spec, mock_output, mock_extra_info, mock_editor_log) + assert mock_output in str(mock_pass) + assert mock_editor_log in str(mock_pass) + +class TestEditorTestSuite(unittest.TestCase): + + @mock.patch('ly_test_tools.o3de.editor_test_utils.kill_all_ly_processes') + def test_EditorTestData_ValidAP_TeardownAPOnce(self, mock_kill_processes): + mock_editor_test_suite = editor_test.EditorTestSuite() + mock_test_data_generator = mock_editor_test_suite._editor_test_data(mock.MagicMock()) + mock_asset_processor = mock.MagicMock() + for test_data in mock_test_data_generator: + test_data.asset_processor = mock_asset_processor + mock_asset_processor.stop.assert_called_once_with(1) + mock_asset_processor.teardown.assert_called() + assert test_data.asset_processor is None + mock_kill_processes.assert_called_once_with(include_asset_processor=True) + + @mock.patch('ly_test_tools.o3de.editor_test_utils.kill_all_ly_processes') + def test_EditorTestData_NoAP_NoTeardownAP(self, mock_kill_processes): + mock_editor_test_suite = editor_test.EditorTestSuite() + mock_test_data_generator = mock_editor_test_suite._editor_test_data(mock.MagicMock()) + for test_data in mock_test_data_generator: + test_data.asset_processor = None + mock_kill_processes.assert_called_once_with(include_asset_processor=False) + + @mock.patch('ly_test_tools.o3de.editor_test.EditorTestSuite.filter_session_shared_tests') + def test_PytestCustomModifyItems_FunctionsMatch_AddsRunners(self, mock_filter_tests): + class MockTestSuite(editor_test.EditorTestSuite): + pass + mock_func_1 = mock.MagicMock() + mock_test = mock.MagicMock() + runner_1 = editor_test.EditorTestSuite.Runner('mock_runner_1', mock_func_1, [mock_test]) + mock_run_pytest_func = mock.MagicMock() + runner_1.run_pytestfunc = mock_run_pytest_func + mock_result_pytestfuncs = [mock.MagicMock()] + runner_1.result_pytestfuncs = mock_result_pytestfuncs + mock_items = [] + mock_items.extend(mock_result_pytestfuncs) + + MockTestSuite._runners = [runner_1] + mock_test_1 = mock.MagicMock() + mock_test_2 = mock.MagicMock() + mock_filter_tests.return_value = [mock_test_1, mock_test_2] + + MockTestSuite.pytest_custom_modify_items(mock.MagicMock(), mock_items, mock.MagicMock()) + assert mock_items == [mock_run_pytest_func, mock_result_pytestfuncs[0]] + + def test_GetSingleTests_NoSingleTests_EmptyList(self): + class MockTestSuite(editor_test.EditorTestSuite): + pass + mock_test_suite = MockTestSuite() + tests = mock_test_suite.get_single_tests() + assert len(tests) == 0 + + def test_GetSingleTests_OneSingleTests_ReturnsOne(self): + class MockTestSuite(editor_test.EditorTestSuite): + class MockSingleTest(editor_test.EditorSingleTest): + pass + mock_test_suite = MockTestSuite() + tests = mock_test_suite.get_single_tests() + assert len(tests) == 1 + assert tests[0].__name__ == "MockSingleTest" + assert issubclass(tests[0], editor_test.EditorSingleTest) + + def test_GetSingleTests_AllTests_ReturnsOnlySingles(self): + class MockTestSuite(editor_test.EditorTestSuite): + class MockSingleTest(editor_test.EditorSingleTest): + pass + class MockAnotherSingleTest(editor_test.EditorSingleTest): + pass + class MockNotSingleTest(editor_test.EditorSharedTest): + pass + mock_test_suite = MockTestSuite() + tests = mock_test_suite.get_single_tests() + assert len(tests) == 2 + for test in tests: + assert issubclass(test, editor_test.EditorSingleTest) + + def test_GetSharedTests_NoSharedTests_EmptyList(self): + class MockTestSuite(editor_test.EditorTestSuite): + pass + mock_test_suite = MockTestSuite() + tests = mock_test_suite.get_shared_tests() + assert len(tests) == 0 + + def test_GetSharedTests_OneSharedTests_ReturnsOne(self): + class MockTestSuite(editor_test.EditorTestSuite): + class MockSharedTest(editor_test.EditorSharedTest): + pass + mock_test_suite = MockTestSuite() + tests = mock_test_suite.get_shared_tests() + assert len(tests) == 1 + assert tests[0].__name__ == 'MockSharedTest' + assert issubclass(tests[0], editor_test.EditorSharedTest) + + def test_GetSharedTests_AllTests_ReturnsOnlyShared(self): + class MockTestSuite(editor_test.EditorTestSuite): + class MockSharedTest(editor_test.EditorSharedTest): + pass + class MockAnotherSharedTest(editor_test.EditorSharedTest): + pass + class MockNotSharedTest(editor_test.EditorSingleTest): + pass + mock_test_suite = MockTestSuite() + tests = mock_test_suite.get_shared_tests() + assert len(tests) == 2 + for test in tests: + assert issubclass(test, editor_test.EditorSharedTest) + + @mock.patch('ly_test_tools.o3de.editor_test.EditorTestSuite.filter_session_shared_tests') + @mock.patch('ly_test_tools.o3de.editor_test.EditorTestSuite.get_shared_tests') + def test_GetSessionSharedTests_Valid_CallsCorrectly(self, mock_get_shared_tests, mock_filter_session): + editor_test.EditorTestSuite.get_session_shared_tests(mock.MagicMock()) + assert mock_get_shared_tests.called + assert mock_filter_session.called + + @mock.patch('ly_test_tools.o3de.editor_test.skipping_pytest_runtest_setup', mock.MagicMock()) + def test_FilterSessionSharedTests_OneSharedTest_ReturnsOne(self): + def mock_test(): + pass + mock_test.originalname = 'mock_test' + mock_test.__name__ = mock_test.originalname + mock_session_items = [mock_test] + mock_shared_tests = [mock_test] + + selected_tests = editor_test.EditorTestSuite.filter_session_shared_tests(mock_session_items, mock_shared_tests) + assert selected_tests == mock_session_items + assert len(selected_tests) == 1 + + @mock.patch('ly_test_tools.o3de.editor_test.skipping_pytest_runtest_setup', mock.MagicMock()) + def test_FilterSessionSharedTests_ManyTests_ReturnsCorrectTests(self): + def mock_test(): + pass + def mock_test_2(): + pass + def mock_test_3(): + pass + mock_test.originalname = 'mock_test' + mock_test.__name__ = mock_test.originalname + mock_test_2.originalname = 'mock_test_2' + mock_test_2.__name__ = mock_test_2.originalname + mock_test_3.originalname = 'mock_test_3' + mock_test_3.__name__ = mock_test_3.originalname + mock_session_items = [mock_test, mock_test_2] + mock_shared_tests = [mock_test, mock_test_2, mock_test_3] + + selected_tests = editor_test.EditorTestSuite.filter_session_shared_tests(mock_session_items, mock_shared_tests) + assert selected_tests == mock_session_items + + @mock.patch('ly_test_tools.o3de.editor_test.skipping_pytest_runtest_setup') + def test_FilterSessionSharedTests_SkipOneTest_ReturnsCorrectTests(self, mock_skip): + def mock_test(): + pass + def mock_test_2(): + pass + def mock_test_3(): + pass + mock_skip.side_effect = [True, Exception] + mock_test.originalname = 'mock_test' + mock_test.__name__ = mock_test.originalname + mock_test_2.originalname = 'mock_test_2' + mock_test_2.__name__ = mock_test_2.originalname + mock_test_3.originalname = 'mock_test_3' + mock_test_3.__name__ = mock_test_3.originalname + mock_session_items = [mock_test, mock_test_2] + mock_shared_tests = [mock_test, mock_test_2, mock_test_3] + + selected_tests = editor_test.EditorTestSuite.filter_session_shared_tests(mock_session_items, mock_shared_tests) + assert selected_tests == [mock_test] + + @mock.patch('ly_test_tools.o3de.editor_test.skipping_pytest_runtest_setup', mock.MagicMock(side_effect=Exception)) + def test_FilterSessionSharedTests_ExceptionDuringSkipSetup_SkipsAddingTest(self): + def mock_test(): + pass + mock_test.originalname = 'mock_test' + mock_test.__name__ = mock_test.originalname + mock_session_items = [mock_test] + mock_shared_tests = [mock_test] + + selected_tests = editor_test.EditorTestSuite.filter_session_shared_tests(mock_session_items, mock_shared_tests) + assert len(selected_tests) == 0 + + def test_FilterSharedTests_TrueParams_ReturnsTrueTests(self): + mock_test = mock.MagicMock() + mock_test.is_batchable = True + mock_test.is_parallelizable = True + mock_test_2 = mock.MagicMock() + mock_test_2.is_batchable = False + mock_test_2.is_parallelizable = False + mock_shared_tests = [mock_test, mock_test_2] + + filtered_tests = editor_test.EditorTestSuite.filter_shared_tests( + mock_shared_tests, is_batchable=True, is_parallelizable=True) + assert filtered_tests == [mock_test] + + def test_FilterSharedTests_FalseParams_ReturnsFalseTests(self): + mock_test = mock.MagicMock() + mock_test.is_batchable = True + mock_test.is_parallelizable = True + mock_test_2 = mock.MagicMock() + mock_test_2.is_batchable = False + mock_test_2.is_parallelizable = False + mock_shared_tests = [mock_test, mock_test_2] + + filtered_tests = editor_test.EditorTestSuite.filter_shared_tests( + mock_shared_tests, is_batchable=False, is_parallelizable=False) + assert filtered_tests == [mock_test_2] + +class TestUtils(unittest.TestCase): + + @mock.patch('ly_test_tools.o3de.editor_test_utils.kill_all_ly_processes') + def test_PrepareAssetProcessor_APExists_StartsAP(self, mock_kill_processes): + mock_test_suite = editor_test.EditorTestSuite() + mock_workspace = mock.MagicMock() + mock_editor_data = mock.MagicMock() + mock_ap = mock.MagicMock() + mock_editor_data.asset_processor = mock_ap + + mock_test_suite._prepare_asset_processor(mock_workspace, mock_editor_data) + assert mock_ap.start.called + assert not mock_kill_processes.called + + @mock.patch('ly_test_tools.o3de.asset_processor.AssetProcessor.start') + @mock.patch('ly_test_tools.environment.process_utils.process_exists') + @mock.patch('ly_test_tools.o3de.editor_test_utils.kill_all_ly_processes') + def test_PrepareAssetProcessor_NoAP_KillAndCreateAP(self, mock_kill_processes, mock_proc_exists, mock_start): + mock_test_suite = editor_test.EditorTestSuite() + mock_workspace = mock.MagicMock() + mock_editor_data = mock.MagicMock() + mock_editor_data.asset_processor = None + mock_proc_exists.return_value = False + + mock_test_suite._prepare_asset_processor(mock_workspace, mock_editor_data) + mock_kill_processes.assert_called_with(include_asset_processor=True) + assert isinstance(mock_editor_data.asset_processor, ly_test_tools.o3de.asset_processor.AssetProcessor) + assert mock_start.called + + @mock.patch('ly_test_tools.o3de.asset_processor.AssetProcessor.start') + @mock.patch('ly_test_tools.environment.process_utils.process_exists') + @mock.patch('ly_test_tools.o3de.editor_test_utils.kill_all_ly_processes') + def test_PrepareAssetProcessor_NoAPButProcExists_NoKill(self, mock_kill_processes, mock_proc_exists, mock_start): + mock_test_suite = editor_test.EditorTestSuite() + mock_workspace = mock.MagicMock() + mock_editor_data = mock.MagicMock() + mock_editor_data.asset_processor = None + mock_proc_exists.return_value = True + + mock_test_suite._prepare_asset_processor(mock_workspace, mock_editor_data) + mock_kill_processes.assert_called_with(include_asset_processor=False) + assert not mock_start.called + assert mock_editor_data.asset_processor is None + + + @mock.patch('ly_test_tools.o3de.asset_processor.AssetProcessor.start') + @mock.patch('ly_test_tools.environment.process_utils.process_exists') + @mock.patch('ly_test_tools.o3de.editor_test_utils.kill_all_ly_processes') + def test_PrepareAssetProcessor_NoAPButProcExists_NoKill(self, mock_kill_processes, mock_proc_exists, mock_start): + mock_test_suite = editor_test.EditorTestSuite() + mock_workspace = mock.MagicMock() + mock_editor_data = mock.MagicMock() + mock_editor_data.asset_processor = None + mock_proc_exists.return_value = True + + mock_test_suite._prepare_asset_processor(mock_workspace, mock_editor_data) + mock_kill_processes.assert_called_with(include_asset_processor=False) + assert not mock_start.called + assert mock_editor_data.asset_processor is None + + @mock.patch('ly_test_tools.o3de.editor_test_utils.kill_all_ly_processes') + @mock.patch('ly_test_tools.o3de.editor_test.EditorTestSuite._prepare_asset_processor') + def test_SetupEditorTest_ValidArgs_CallsCorrectly(self, mock_prepare_ap, mock_kill_processes): + mock_test_suite = editor_test.EditorTestSuite() + mock_editor = mock.MagicMock() + mock_test_suite._setup_editor_test(mock_editor, mock.MagicMock(), mock.MagicMock()) + + assert mock_editor.configure_settings.called + assert mock_prepare_ap.called + mock_kill_processes.assert_called_once_with(include_asset_processor=False) + + @mock.patch('ly_test_tools.o3de.editor_test.Result.Pass.create') + @mock.patch('ly_test_tools.o3de.editor_test_utils.get_module_filename') + def test_GetResultsUsingOutput_ValidJsonSuccess_CreatesPassResult(self, mock_get_module, mock_create): + mock_get_module.return_value = 'mock_module_name' + mock_test_suite = editor_test.EditorTestSuite() + mock_test = mock.MagicMock() + mock_test.__name__ = 'mock_test_name' + mock_test_list = [mock_test] + mock_output = 'JSON_START(' \ + '{"name": "mock_module_name", "output": "mock_std_out", "success": "mock_success_data"}' \ + ')JSON_END' + mock_pass = mock.MagicMock() + mock_create.return_value = mock_pass + + results = mock_test_suite._get_results_using_output(mock_test_list, mock_output, '') + assert mock_create.called + assert len(results) == 1 + assert results[mock_test.__name__] == mock_pass + + @mock.patch('ly_test_tools.o3de.editor_test.Result.Fail.create') + @mock.patch('ly_test_tools.o3de.editor_test_utils.get_module_filename') + def test_GetResultsUsingOutput_ValidJsonFail_CreatesFailResult(self, mock_get_module, mock_create): + mock_get_module.return_value = 'mock_module_name' + mock_test_suite = editor_test.EditorTestSuite() + mock_test = mock.MagicMock() + mock_test.__name__ = 'mock_test_name' + mock_test_list = [mock_test] + mock_output = 'JSON_START(' \ + '{"name": "mock_module_name", "output": "mock_std_out", "failed": "mock_fail_data", "success": ""}' \ + ')JSON_END' + mock_fail = mock.MagicMock() + mock_create.return_value = mock_fail + + results = mock_test_suite._get_results_using_output(mock_test_list, mock_output, '') + assert mock_create.called + assert len(results) == 1 + assert results[mock_test.__name__] == mock_fail + + @mock.patch('ly_test_tools.o3de.editor_test.Result.Unknown.create') + @mock.patch('ly_test_tools.o3de.editor_test_utils.get_module_filename') + def test_GetResultsUsingOutput_ModuleNotInLog_CreatesUnknownResult(self, mock_get_module, mock_create): + mock_get_module.return_value = 'different_module_name' + mock_test_suite = editor_test.EditorTestSuite() + mock_test = mock.MagicMock() + mock_test.__name__ = 'mock_test_name' + mock_test_list = [mock_test] + mock_output = 'JSON_START(' \ + '{"name": "mock_module_name", "output": "mock_std_out", "failed": "mock_fail_data"}' \ + ')JSON_END' + mock_unknown = mock.MagicMock() + mock_create.return_value = mock_unknown + + results = mock_test_suite._get_results_using_output(mock_test_list, mock_output, '') + assert mock_create.called + assert len(results) == 1 + assert results[mock_test.__name__] == mock_unknown + + @mock.patch('ly_test_tools.o3de.editor_test.Result.Pass.create') + @mock.patch('ly_test_tools.o3de.editor_test.Result.Fail.create') + @mock.patch('ly_test_tools.o3de.editor_test.Result.Unknown.create') + @mock.patch('ly_test_tools.o3de.editor_test_utils.get_module_filename') + def test_GetResultsUsingOutput_MultipleTests_CreatesCorrectResults(self, mock_get_module, mock_create_unknown, + mock_create_fail, mock_create_pass): + mock_get_module.side_effect = ['mock_module_name_pass', 'mock_module_name_fail', 'different_module_name'] + mock_test_suite = editor_test.EditorTestSuite() + mock_test_pass = mock.MagicMock() + mock_test_pass.__name__ = 'mock_test_name_pass' + mock_test_fail = mock.MagicMock() + mock_test_fail.__name__ = 'mock_test_name_fail' + mock_test_unknown = mock.MagicMock() + mock_test_unknown.__name__ = 'mock_test_name_unknown' + mock_test_list = [mock_test_pass, mock_test_fail, mock_test_unknown] + mock_output = 'JSON_START(' \ + '{"name": "mock_module_name_pass", "output": "mock_std_out", "success": "mock_success_data"}' \ + ')JSON_END' \ + 'JSON_START(' \ + '{"name": "mock_module_name_fail", "output": "mock_std_out", "failed": "mock_fail_data", "success": ""}' \ + ')JSON_END' \ + 'JSON_START(' \ + '{"name": "mock_module_name_unknown", "output": "mock_std_out", "failed": "mock_fail_data", "success": ""}' \ + ')JSON_END' + mock_editor_log = 'JSON_START(' \ + '{"name": "mock_module_name_pass"}' \ + ')JSON_END' \ + 'JSON_START(' \ + '{"name": "mock_module_name_fail"}' \ + ')JSON_END' + mock_unknown = mock.MagicMock() + mock_pass = mock.MagicMock() + mock_fail = mock.MagicMock() + mock_create_unknown.return_value = mock_unknown + mock_create_pass.return_value = mock_pass + mock_create_fail.return_value = mock_fail + + results = mock_test_suite._get_results_using_output(mock_test_list, mock_output, mock_editor_log) + mock_create_pass.assert_called_with( + mock_test_pass, 'mock_std_out', 'JSON_START({"name": "mock_module_name_pass"})JSON_END') + mock_create_fail.assert_called_with( + mock_test_fail, 'mock_std_out', 'JSON_START({"name": "mock_module_name_fail"})JSON_END') + mock_create_unknown.assert_called_with( + mock_test_unknown, mock_output, "Couldn't find any test run information on stdout", mock_editor_log) + assert len(results) == 3 + assert results[mock_test_pass.__name__] == mock_pass + assert results[mock_test_fail.__name__] == mock_fail + assert results[mock_test_unknown.__name__] == mock_unknown + + @mock.patch('builtins.print') + def test_ReportResult_TestPassed_ReportsCorrectly(self, mock_print): + mock_test_name = 'mock name' + mock_pass = ly_test_tools.o3de.editor_test.Result.Pass() + ly_test_tools.o3de.editor_test.EditorTestSuite._report_result(mock_test_name, mock_pass) + mock_print.assert_called_with(f'Test {mock_test_name}:\nTest Passed\n------------\n| Output |\n------------\n' + f'-- No output --\n') + + @mock.patch('pytest.fail') + def test_ReportResult_TestFailed_FailsCorrectly(self, mock_pytest_fail): + mock_fail = ly_test_tools.o3de.editor_test.Result.Fail() + + ly_test_tools.o3de.editor_test.EditorTestSuite._report_result('mock_test_name', mock_fail) + mock_pytest_fail.assert_called_with('Test mock_test_name:\nTest FAILED\n------------\n| Output |' + '\n------------\n-- No output --\n--------------\n| Editor log |' + '\n--------------\n-- No editor log found --\n') + +class TestRunningTests(unittest.TestCase): + + @mock.patch('ly_test_tools.o3de.editor_test.Result.Pass.create') + @mock.patch('ly_test_tools.o3de.editor_test.EditorTestSuite._get_results_using_output') + @mock.patch('ly_test_tools.o3de.editor_test_utils.retrieve_editor_log_content') + @mock.patch('ly_test_tools.o3de.editor_test_utils.retrieve_log_path') + @mock.patch('ly_test_tools.o3de.editor_test_utils.get_testcase_module_filepath') + @mock.patch('ly_test_tools.o3de.editor_test_utils.cycle_crash_report') + def test_ExecEditorTest_TestSucceeds_ReturnsPass(self, mock_cycle_crash, mock_get_testcase_filepath, + mock_retrieve_log, mock_retrieve_editor_log, + mock_get_output_results, mock_create): + mock_test_suite = ly_test_tools.o3de.editor_test.EditorTestSuite() + mock_workspace = mock.MagicMock() + mock_editor = mock.MagicMock() + mock_test_spec = mock.MagicMock() + mock_test_spec.__name__ = 'mock_test_name' + mock_editor.get_returncode.return_value = 0 + mock_get_output_results.return_value = {} + mock_pass = mock.MagicMock() + mock_create.return_value = mock_pass + + results = mock_test_suite._exec_editor_test(mock.MagicMock(), mock_workspace, mock_editor, 0, + 'mock_log_name', mock_test_spec, []) + assert mock_cycle_crash.called + assert mock_editor.start.called + assert mock_create.called + assert results == {mock_test_spec.__name__: mock_pass} + + @mock.patch('ly_test_tools.o3de.editor_test.Result.Fail.create') + @mock.patch('ly_test_tools.o3de.editor_test.EditorTestSuite._get_results_using_output') + @mock.patch('ly_test_tools.o3de.editor_test_utils.retrieve_editor_log_content') + @mock.patch('ly_test_tools.o3de.editor_test_utils.retrieve_log_path') + @mock.patch('ly_test_tools.o3de.editor_test_utils.get_testcase_module_filepath') + @mock.patch('ly_test_tools.o3de.editor_test_utils.cycle_crash_report') + def test_ExecEditorTest_TestFails_ReturnsFail(self, mock_cycle_crash, mock_get_testcase_filepath, + mock_retrieve_log, mock_retrieve_editor_log, + mock_get_output_results, mock_create): + mock_test_suite = ly_test_tools.o3de.editor_test.EditorTestSuite() + mock_workspace = mock.MagicMock() + mock_editor = mock.MagicMock() + mock_test_spec = mock.MagicMock() + mock_test_spec.__name__ = 'mock_test_name' + mock_editor.get_returncode.return_value = 15 + mock_get_output_results.return_value = {} + mock_fail = mock.MagicMock() + mock_create.return_value = mock_fail + + results = mock_test_suite._exec_editor_test(mock.MagicMock(), mock_workspace, mock_editor, 0, + 'mock_log_name', mock_test_spec, []) + assert mock_cycle_crash.called + assert mock_editor.start.called + assert mock_create.called + assert results == {mock_test_spec.__name__: mock_fail} + + @mock.patch('ly_test_tools.o3de.editor_test.Result.Crash.create') + @mock.patch('ly_test_tools.o3de.editor_test_utils.retrieve_crash_output') + @mock.patch('ly_test_tools.o3de.editor_test.EditorTestSuite._get_results_using_output') + @mock.patch('ly_test_tools.o3de.editor_test_utils.retrieve_editor_log_content') + @mock.patch('ly_test_tools.o3de.editor_test_utils.retrieve_log_path') + @mock.patch('ly_test_tools.o3de.editor_test_utils.get_testcase_module_filepath') + @mock.patch('ly_test_tools.o3de.editor_test_utils.cycle_crash_report') + def test_ExecEditorTest_TestCrashes_ReturnsCrash(self, mock_cycle_crash, mock_get_testcase_filepath, + mock_retrieve_log, mock_retrieve_editor_log, + mock_get_output_results, mock_retrieve_crash, mock_create): + mock_test_suite = ly_test_tools.o3de.editor_test.EditorTestSuite() + mock_workspace = mock.MagicMock() + mock_editor = mock.MagicMock() + mock_test_spec = mock.MagicMock() + mock_test_spec.__name__ = 'mock_test_name' + mock_editor.get_returncode.return_value = 1 + mock_get_output_results.return_value = {} + mock_crash = mock.MagicMock() + mock_create.return_value = mock_crash + + results = mock_test_suite._exec_editor_test(mock.MagicMock(), mock_workspace, mock_editor, 0, + 'mock_log_name', mock_test_spec, []) + assert mock_cycle_crash.call_count == 2 + assert mock_editor.start.called + assert mock_retrieve_crash.called + assert mock_create.called + assert results == {mock_test_spec.__name__: mock_crash} + + @mock.patch('ly_test_tools.o3de.editor_test.Result.Timeout.create') + @mock.patch('ly_test_tools.o3de.editor_test.EditorTestSuite._get_results_using_output') + @mock.patch('ly_test_tools.o3de.editor_test_utils.retrieve_editor_log_content') + @mock.patch('ly_test_tools.o3de.editor_test_utils.retrieve_log_path') + @mock.patch('ly_test_tools.o3de.editor_test_utils.get_testcase_module_filepath') + @mock.patch('ly_test_tools.o3de.editor_test_utils.cycle_crash_report') + def test_ExecEditorTest_TestTimeout_ReturnsTimeout(self, mock_cycle_crash, mock_get_testcase_filepath, + mock_retrieve_log, mock_retrieve_editor_log, + mock_get_output_results, mock_create): + mock_test_suite = ly_test_tools.o3de.editor_test.EditorTestSuite() + mock_workspace = mock.MagicMock() + mock_editor = mock.MagicMock() + mock_test_spec = mock.MagicMock() + mock_test_spec.__name__ = 'mock_test_name' + mock_editor.wait.side_effect = ly_test_tools.launchers.exceptions.WaitTimeoutError() + mock_get_output_results.return_value = {} + mock_timeout = mock.MagicMock() + mock_create.return_value = mock_timeout + + results = mock_test_suite._exec_editor_test(mock.MagicMock(), mock_workspace, mock_editor, 0, + 'mock_log_name', mock_test_spec, []) + assert mock_cycle_crash.called + assert mock_editor.start.called + assert mock_editor.kill.called + assert mock_create.called + assert results == {mock_test_spec.__name__: mock_timeout} + + @mock.patch('ly_test_tools.o3de.editor_test.Result.Pass.create') + @mock.patch('ly_test_tools.o3de.editor_test_utils.retrieve_editor_log_content') + @mock.patch('ly_test_tools.o3de.editor_test_utils.retrieve_log_path') + @mock.patch('ly_test_tools.o3de.editor_test_utils.get_testcase_module_filepath') + @mock.patch('ly_test_tools.o3de.editor_test_utils.cycle_crash_report') + def test_ExecEditorMultitest_AllTestsPass_ReturnsPasses(self, mock_cycle_crash, mock_get_testcase_filepath, + mock_retrieve_log, mock_retrieve_editor_log, mock_create): + mock_test_suite = ly_test_tools.o3de.editor_test.EditorTestSuite() + mock_workspace = mock.MagicMock() + mock_editor = mock.MagicMock() + mock_editor.get_returncode.return_value = 0 + mock_test_spec = mock.MagicMock() + mock_test_spec.__name__ = 'mock_test_name' + mock_test_spec_2 = mock.MagicMock() + mock_test_spec_2.__name__ = 'mock_test_name_2' + mock_test_spec_list = [mock_test_spec, mock_test_spec_2] + mock_get_testcase_filepath.side_effect = ['mock_path', 'mock_path_2'] + mock_pass = mock.MagicMock() + mock_pass_2 = mock.MagicMock() + mock_create.side_effect = [mock_pass, mock_pass_2] + + results = mock_test_suite._exec_editor_multitest(mock.MagicMock(), mock_workspace, mock_editor, 0, + 'mock_log_name', mock_test_spec_list, []) + assert results == {mock_test_spec.__name__: mock_pass, mock_test_spec_2.__name__: mock_pass_2} + assert mock_cycle_crash.called + assert mock_create.call_count == 2 + + @mock.patch('ly_test_tools.o3de.editor_test.EditorTestSuite._get_results_using_output') + @mock.patch('ly_test_tools.o3de.editor_test_utils.retrieve_editor_log_content') + @mock.patch('ly_test_tools.o3de.editor_test_utils.retrieve_log_path') + @mock.patch('ly_test_tools.o3de.editor_test_utils.get_testcase_module_filepath') + @mock.patch('ly_test_tools.o3de.editor_test_utils.cycle_crash_report') + def test_ExecEditorMultitest_OneFailure_CallsCorrectFunc(self, mock_cycle_crash, mock_get_testcase_filepath, + mock_retrieve_log, mock_retrieve_editor_log, mock_get_results): + mock_test_suite = ly_test_tools.o3de.editor_test.EditorTestSuite() + mock_workspace = mock.MagicMock() + mock_editor = mock.MagicMock() + mock_editor.get_returncode.return_value = 15 + mock_test_spec = mock.MagicMock() + mock_test_spec_2 = mock.MagicMock() + mock_test_spec_list = [mock_test_spec, mock_test_spec_2] + mock_get_testcase_filepath.side_effect = ['mock_path', 'mock_path_2'] + mock_get_results.return_value = {'mock_test_name': mock.MagicMock(), 'mock_test_name_2': mock.MagicMock()} + + results = mock_test_suite._exec_editor_multitest(mock.MagicMock(), mock_workspace, mock_editor, 0, + 'mock_log_name', mock_test_spec_list, []) + assert mock_cycle_crash.called + assert mock_get_results.called + + @mock.patch('ly_test_tools.o3de.editor_test.Result.Crash.create') + @mock.patch('ly_test_tools.o3de.editor_test_utils.retrieve_crash_output') + @mock.patch('ly_test_tools.o3de.editor_test.EditorTestSuite._get_results_using_output') + @mock.patch('ly_test_tools.o3de.editor_test_utils.retrieve_editor_log_content') + @mock.patch('ly_test_tools.o3de.editor_test_utils.retrieve_log_path') + @mock.patch('ly_test_tools.o3de.editor_test_utils.get_testcase_module_filepath') + @mock.patch('ly_test_tools.o3de.editor_test_utils.cycle_crash_report') + def test_ExecEditorMultitest_OneCrash_ReportsOnUnknownResult(self, mock_cycle_crash, mock_get_testcase_filepath, + mock_retrieve_log, mock_retrieve_editor_log, + mock_get_results, mock_retrieve_crash, mock_create): + mock_test_suite = ly_test_tools.o3de.editor_test.EditorTestSuite() + mock_workspace = mock.MagicMock() + mock_editor = mock.MagicMock() + mock_editor.get_returncode.return_value = 1 + mock_test_spec = mock.MagicMock() + mock_test_spec.__name__ = 'mock_test_name' + mock_test_spec_2 = mock.MagicMock() + mock_test_spec_2.__name__ = 'mock_test_name_2' + mock_test_spec_list = [mock_test_spec, mock_test_spec_2] + mock_unknown_result = ly_test_tools.o3de.editor_test.Result.Unknown() + mock_unknown_result.test_spec = mock.MagicMock() + mock_unknown_result.editor_log = mock.MagicMock() + mock_get_testcase_filepath.side_effect = ['mock_path', 'mock_path_2'] + mock_get_results.return_value = {mock_test_spec.__name__: mock_unknown_result, + mock_test_spec_2.__name__: mock.MagicMock()} + mock_crash = mock.MagicMock() + mock_create.return_value = mock_crash + + results = mock_test_suite._exec_editor_multitest(mock.MagicMock(), mock_workspace, mock_editor, 0, + 'mock_log_name', mock_test_spec_list, []) + assert mock_cycle_crash.call_count == 2 + assert mock_get_results.called + assert results[mock_test_spec.__name__] == mock_crash + + @mock.patch('ly_test_tools.o3de.editor_test.Result.Crash.create') + @mock.patch('ly_test_tools.o3de.editor_test_utils.retrieve_crash_output') + @mock.patch('ly_test_tools.o3de.editor_test.EditorTestSuite._get_results_using_output') + @mock.patch('ly_test_tools.o3de.editor_test_utils.retrieve_editor_log_content') + @mock.patch('ly_test_tools.o3de.editor_test_utils.retrieve_log_path') + @mock.patch('ly_test_tools.o3de.editor_test_utils.get_testcase_module_filepath') + @mock.patch('ly_test_tools.o3de.editor_test_utils.cycle_crash_report') + def test_ExecEditorMultitest_ManyUnknown_ReportsUnknownResults(self, mock_cycle_crash, mock_get_testcase_filepath, + mock_retrieve_log, mock_retrieve_editor_log, + mock_get_results, mock_retrieve_crash, mock_create): + mock_test_suite = ly_test_tools.o3de.editor_test.EditorTestSuite() + mock_workspace = mock.MagicMock() + mock_editor = mock.MagicMock() + mock_editor.get_returncode.return_value = 1 + mock_test_spec = mock.MagicMock() + mock_test_spec.__name__ = 'mock_test_name' + mock_test_spec_2 = mock.MagicMock() + mock_test_spec_2.__name__ = 'mock_test_name_2' + mock_test_spec_list = [mock_test_spec, mock_test_spec_2] + mock_unknown_result = ly_test_tools.o3de.editor_test.Result.Unknown() + mock_unknown_result.__name__ = 'mock_test_name' + mock_unknown_result.test_spec = mock.MagicMock() + mock_unknown_result.test_spec.__name__ = 'mock_test_spec_name' + mock_unknown_result.editor_log = mock.MagicMock() + mock_get_testcase_filepath.side_effect = ['mock_path', 'mock_path_2'] + mock_get_results.return_value = {mock_test_spec.__name__: mock_unknown_result, + mock_test_spec_2.__name__: mock_unknown_result} + mock_crash = mock.MagicMock() + mock_create.return_value = mock_crash + + results = mock_test_suite._exec_editor_multitest(mock.MagicMock(), mock_workspace, mock_editor, 0, + 'mock_log_name', mock_test_spec_list, []) + assert mock_cycle_crash.call_count == 2 + assert mock_get_results.called + assert results[mock_test_spec.__name__] == mock_crash + assert results[mock_test_spec_2.__name__].extra_info + + @mock.patch('ly_test_tools.o3de.editor_test.Result.Timeout.create') + @mock.patch('ly_test_tools.o3de.editor_test.EditorTestSuite._get_results_using_output') + @mock.patch('ly_test_tools.o3de.editor_test_utils.retrieve_editor_log_content') + @mock.patch('ly_test_tools.o3de.editor_test_utils.retrieve_log_path') + @mock.patch('ly_test_tools.o3de.editor_test_utils.get_testcase_module_filepath') + @mock.patch('ly_test_tools.o3de.editor_test_utils.cycle_crash_report') + def test_ExecEditorMultitest_EditorTimeout_ReportsCorrectly(self, mock_cycle_crash, mock_get_testcase_filepath, + mock_retrieve_log, mock_retrieve_editor_log, + mock_get_results, mock_create): + mock_test_suite = ly_test_tools.o3de.editor_test.EditorTestSuite() + mock_workspace = mock.MagicMock() + mock_editor = mock.MagicMock() + mock_editor.wait.side_effect = ly_test_tools.launchers.exceptions.WaitTimeoutError() + mock_test_spec = mock.MagicMock() + mock_test_spec.__name__ = 'mock_test_name' + mock_test_spec_2 = mock.MagicMock() + mock_test_spec_2.__name__ = 'mock_test_name_2' + mock_test_spec_list = [mock_test_spec, mock_test_spec_2] + mock_unknown_result = ly_test_tools.o3de.editor_test.Result.Unknown() + mock_unknown_result.test_spec = mock.MagicMock() + mock_unknown_result.test_spec.__name__ = 'mock_test_spec_name' + mock_unknown_result.output = mock.MagicMock() + mock_unknown_result.editor_log = mock.MagicMock() + mock_get_testcase_filepath.side_effect = ['mock_path', 'mock_path_2'] + mock_get_results.return_value = {mock_test_spec.__name__: mock_unknown_result, + mock_test_spec_2.__name__: mock_unknown_result} + mock_timeout = mock.MagicMock() + mock_create.return_value = mock_timeout + + results = mock_test_suite._exec_editor_multitest(mock.MagicMock(), mock_workspace, mock_editor, 0, + 'mock_log_name', mock_test_spec_list, []) + assert mock_cycle_crash.called + assert mock_get_results.called + assert results[mock_test_spec_2.__name__].extra_info + assert results[mock_test_spec.__name__] == mock_timeout + + @mock.patch('ly_test_tools.o3de.editor_test.EditorTestSuite._report_result') + @mock.patch('ly_test_tools.o3de.editor_test.EditorTestSuite._exec_editor_test') + @mock.patch('ly_test_tools.o3de.editor_test.EditorTestSuite._setup_editor_test') + def test_RunSingleTest_ValidTest_ReportsResults(self, mock_setup_test, mock_exec_editor_test, mock_report_result): + mock_test_suite = ly_test_tools.o3de.editor_test.EditorTestSuite() + mock_test_data = mock.MagicMock() + mock_test_spec = mock.MagicMock() + mock_result = mock.MagicMock() + mock_test_name = 'mock_test_result' + mock_exec_editor_test.return_value = {mock_test_name: mock_result} + + mock_test_suite._run_single_test(mock.MagicMock(), mock.MagicMock(), mock.MagicMock(), mock_test_data, + mock_test_spec) + + assert mock_setup_test.called + assert mock_exec_editor_test.called + assert mock_test_data.results.update.called + mock_report_result.assert_called_with(mock_test_name, mock_result) + + @mock.patch('ly_test_tools.o3de.editor_test.EditorTestSuite._exec_editor_multitest') + @mock.patch('ly_test_tools.o3de.editor_test.EditorTestSuite._setup_editor_test') + def test_RunBatchedTests_ValidTests_CallsCorrectly(self, mock_setup_test, mock_exec_multitest): + mock_test_suite = ly_test_tools.o3de.editor_test.EditorTestSuite() + mock_test_data = mock.MagicMock() + + mock_test_suite._run_batched_tests(mock.MagicMock(), mock.MagicMock(), mock.MagicMock(), mock_test_data, + mock.MagicMock(), []) + + assert mock_setup_test.called + assert mock_exec_multitest.called + assert mock_test_data.results.update.called + + @mock.patch('threading.Thread') + @mock.patch('ly_test_tools.o3de.editor_test.EditorTestSuite._get_number_parallel_editors') + @mock.patch('ly_test_tools.o3de.editor_test.EditorTestSuite._setup_editor_test') + def test_RunParallelTests_TwoTestsAndEditors_TwoThreads(self, mock_setup_test, mock_get_num_editors, mock_thread): + mock_test_suite = ly_test_tools.o3de.editor_test.EditorTestSuite() + mock_get_num_editors.return_value = 2 + mock_test_spec_list = [mock.MagicMock(), mock.MagicMock()] + mock_test_data = mock.MagicMock() + + mock_test_suite._run_parallel_tests(mock.MagicMock(), mock.MagicMock(), mock.MagicMock(), mock_test_data, + mock_test_spec_list, []) + + assert mock_setup_test.called + assert mock_test_data.results.update.call_count == len(mock_test_spec_list) + assert mock_thread.call_count == len(mock_test_spec_list) + + @mock.patch('threading.Thread') + @mock.patch('ly_test_tools.o3de.editor_test.EditorTestSuite._get_number_parallel_editors') + @mock.patch('ly_test_tools.o3de.editor_test.EditorTestSuite._setup_editor_test') + def test_RunParallelTests_TenTestsAndTwoEditors_TenThreads(self, mock_setup_test, mock_get_num_editors, mock_thread): + mock_test_suite = ly_test_tools.o3de.editor_test.EditorTestSuite() + mock_get_num_editors.return_value = 2 + mock_test_spec_list = [] + for i in range(10): + mock_test_spec_list.append(mock.MagicMock()) + mock_test_data = mock.MagicMock() + + mock_test_suite._run_parallel_tests(mock.MagicMock(), mock.MagicMock(), mock.MagicMock(), mock_test_data, + mock_test_spec_list, []) + + assert mock_setup_test.called + assert mock_test_data.results.update.call_count == len(mock_test_spec_list) + assert mock_thread.call_count == len(mock_test_spec_list) + + @mock.patch('threading.Thread') + @mock.patch('ly_test_tools.o3de.editor_test.EditorTestSuite._get_number_parallel_editors') + @mock.patch('ly_test_tools.o3de.editor_test.EditorTestSuite._setup_editor_test') + def test_RunParallelTests_TenTestsAndThreeEditors_TenThreads(self, mock_setup_test, mock_get_num_editors, + mock_thread): + mock_test_suite = ly_test_tools.o3de.editor_test.EditorTestSuite() + mock_get_num_editors.return_value = 3 + mock_test_spec_list = [] + for i in range(10): + mock_test_spec_list.append(mock.MagicMock()) + mock_test_data = mock.MagicMock() + + mock_test_suite._run_parallel_tests(mock.MagicMock(), mock.MagicMock(), mock.MagicMock(), mock_test_data, + mock_test_spec_list, []) + + assert mock_setup_test.called + assert mock_test_data.results.update.call_count == len(mock_test_spec_list) + assert mock_thread.call_count == len(mock_test_spec_list) + + @mock.patch('threading.Thread') + @mock.patch('ly_test_tools.o3de.editor_test.EditorTestSuite._get_number_parallel_editors') + @mock.patch('ly_test_tools.o3de.editor_test.EditorTestSuite._setup_editor_test') + def test_RunParallelBatchedTests_TwoTestsAndEditors_TwoThreads(self, mock_setup_test, mock_get_num_editors, + mock_thread): + mock_test_suite = ly_test_tools.o3de.editor_test.EditorTestSuite() + mock_get_num_editors.return_value = 2 + mock_test_spec_list = [mock.MagicMock(), mock.MagicMock()] + mock_test_data = mock.MagicMock() + + mock_test_suite._run_parallel_batched_tests(mock.MagicMock(), mock.MagicMock(), mock.MagicMock(), + mock_test_data, mock_test_spec_list, []) + + assert mock_setup_test.called + assert mock_test_data.results.update.call_count == len(mock_test_spec_list) + assert mock_thread.call_count == len(mock_test_spec_list) + + @mock.patch('threading.Thread') + @mock.patch('ly_test_tools.o3de.editor_test.EditorTestSuite._get_number_parallel_editors') + @mock.patch('ly_test_tools.o3de.editor_test.EditorTestSuite._setup_editor_test') + def test_RunParallelBatchedTests_TenTestsAndTwoEditors_2Threads(self, mock_setup_test, mock_get_num_editors, + mock_thread): + mock_test_suite = ly_test_tools.o3de.editor_test.EditorTestSuite() + mock_get_num_editors.return_value = 2 + mock_test_spec_list = [] + for i in range(10): + mock_test_spec_list.append(mock.MagicMock()) + mock_test_data = mock.MagicMock() + + mock_test_suite._run_parallel_batched_tests(mock.MagicMock(), mock.MagicMock(), mock.MagicMock(), + mock_test_data, mock_test_spec_list, []) + + assert mock_setup_test.called + assert mock_test_data.results.update.call_count == 2 + assert mock_thread.call_count == 2 + + @mock.patch('threading.Thread') + @mock.patch('ly_test_tools.o3de.editor_test.EditorTestSuite._get_number_parallel_editors') + @mock.patch('ly_test_tools.o3de.editor_test.EditorTestSuite._setup_editor_test') + def test_RunParallelBatchedTests_TenTestsAndThreeEditors_ThreeThreads(self, mock_setup_test, mock_get_num_editors, + mock_thread): + mock_test_suite = ly_test_tools.o3de.editor_test.EditorTestSuite() + mock_get_num_editors.return_value = 3 + mock_test_spec_list = [] + for i in range(10): + mock_test_spec_list.append(mock.MagicMock()) + mock_test_data = mock.MagicMock() + + mock_test_suite._run_parallel_batched_tests(mock.MagicMock(), mock.MagicMock(), mock.MagicMock(), + mock_test_data, mock_test_spec_list, []) + + assert mock_setup_test.called + assert mock_test_data.results.update.call_count == 3 + assert mock_thread.call_count == 3 + + def test_GetNumberParallelEditors_ConfigExists_ReturnsConfig(self): + mock_test_suite = ly_test_tools.o3de.editor_test.EditorTestSuite() + mock_request = mock.MagicMock() + mock_request.config.getoption.return_value = 1 + + num_of_editors = mock_test_suite._get_number_parallel_editors(mock_request) + assert num_of_editors == 1 + + def test_GetNumberParallelEditors_ConfigNotExists_ReturnsDefault(self): + mock_test_suite = ly_test_tools.o3de.editor_test.EditorTestSuite() + mock_request = mock.MagicMock() + mock_request.config.getoption.return_value = None + + num_of_editors = mock_test_suite._get_number_parallel_editors(mock_request) + assert num_of_editors == mock_test_suite.get_number_parallel_editors() + +@mock.patch('_pytest.python.Class.collect') +class TestEditorTestClass(unittest.TestCase): + + def setUp(self): + mock_name = mock.MagicMock() + mock_collector = mock.MagicMock() + self.mock_test_class = ly_test_tools.o3de.editor_test.EditorTestSuite.EditorTestClass(mock_name, mock_collector) + self.mock_test_class.obj = mock.MagicMock() + single_1 = mock.MagicMock() + single_1.__name__ = 'single_1_name' + single_2 = mock.MagicMock() + single_2.__name__ = 'single_2_name' + self.mock_test_class.obj.get_single_tests.return_value = [single_1, single_2] + batch_1 = mock.MagicMock() + batch_1.__name__ = 'batch_1_name' + batch_2 = mock.MagicMock() + batch_2.__name__ = 'batch_2_name' + parallel_1 = mock.MagicMock() + parallel_1.__name__ = 'parallel_1_name' + parallel_2 = mock.MagicMock() + parallel_2.__name__ = 'parallel_2_name' + both_1 = mock.MagicMock() + both_1.__name__ = 'both_1_name' + both_2 = mock.MagicMock() + both_2.__name__ = 'both_2_name' + self.mock_test_class.obj.filter_shared_tests.side_effect = [ [batch_1, batch_2], + [parallel_1, parallel_2], + [both_1, both_2] ] + + def test_Collect_NoParallelNoBatched_RunsAsSingleTests(self, mock_collect): + self.mock_test_class.config.getoption.return_value = True + self.mock_test_class.collect() + assert self.mock_test_class.obj.single_1_name.__name__ == 'single_run' + assert self.mock_test_class.obj.single_2_name.__name__ == 'single_run' + assert self.mock_test_class.obj.batch_1_name.__name__ == 'single_run' + assert self.mock_test_class.obj.batch_2_name.__name__ == 'single_run' + assert self.mock_test_class.obj.parallel_1_name.__name__ == 'single_run' + assert self.mock_test_class.obj.parallel_2_name.__name__ == 'single_run' + assert self.mock_test_class.obj.both_1_name.__name__ == 'single_run' + assert self.mock_test_class.obj.both_2_name.__name__ == 'single_run' + + def test_Collect_AllValidTests_RunsAsInteded(self, mock_collect): + self.mock_test_class.config.getoption.return_value = False + self.mock_test_class.collect() + assert self.mock_test_class.obj.single_1_name.__name__ == 'single_run' + assert self.mock_test_class.obj.single_2_name.__name__ == 'single_run' + assert self.mock_test_class.obj.batch_1_name.__name__ == 'result' + assert self.mock_test_class.obj.batch_2_name.__name__ == 'result' + assert self.mock_test_class.obj.parallel_1_name.__name__ == 'result' + assert self.mock_test_class.obj.parallel_2_name.__name__ == 'result' + assert self.mock_test_class.obj.both_1_name.__name__ == 'result' + assert self.mock_test_class.obj.both_2_name.__name__ == 'result' + + def test_Collect_AllValidTests_CallsCollect(self, mock_collect): + self.mock_test_class.collect() + assert mock_collect.called + + def test_Collect_NormalCollection_ReturnsFilteredRuns(self, mock_collect): + mock_run = mock.MagicMock() + mock_run.obj.marks = {"run_type": 'run_shared'} + mock_run_2 = mock.MagicMock() + mock_run_2.obj.marks = {"run_type": 'result'} + mock_instance = mock.MagicMock() + mock_instance.collect.return_value = [mock_run, mock_run_2] + mock_collect.return_value = [mock_instance] + + collection = self.mock_test_class.collect() + assert collection == [mock_run_2] + + def test_Collect_NormalRun_ReturnsRunners(self, mock_collect): + self.mock_test_class.collect() + runners = self.mock_test_class.obj._runners + + assert runners[0].name == 'run_batched_tests' + assert runners[1].name == 'run_parallel_tests' + assert runners[2].name == 'run_parallel_batched_tests' + + def test_Collect_NormalCollection_StoresRunners(self, mock_collect): + mock_runner = mock.MagicMock() + mock_run = mock.MagicMock() + mock_run.obj.marks = {"run_type": 'run_shared'} + mock_run.function.marks = {"runner": mock_runner} + mock_runner_2 = mock.MagicMock() + mock_runner_2.result_pytestfuncs = [] + mock_run_2 = mock.MagicMock() + mock_run_2.obj.marks = {"run_type": 'result'} + mock_run_2.function.marks = {"runner": mock_runner_2} + mock_instance = mock.MagicMock() + mock_instance.collect.return_value = [mock_run, mock_run_2] + mock_collect.return_value = [mock_instance] + + self.mock_test_class.collect() + + assert mock_runner.run_pytestfunc == mock_run + assert mock_run_2 in mock_runner_2.result_pytestfuncs diff --git a/Tools/LyTestTools/tests/unit/test_pytest_plugin_editor_test.py b/Tools/LyTestTools/tests/unit/test_pytest_plugin_editor_test.py new file mode 100644 index 0000000000..10c779e546 --- /dev/null +++ b/Tools/LyTestTools/tests/unit/test_pytest_plugin_editor_test.py @@ -0,0 +1,41 @@ +""" +Copyright (c) Contributors to the Open 3D Engine Project. +For complete copyright and license terms please see the LICENSE at the root of this distribution. + +SPDX-License-Identifier: Apache-2.0 OR MIT +""" +import pytest +import os +import unittest.mock as mock +import unittest + +import ly_test_tools._internal.pytest_plugin.editor_test as editor_test + +pytestmark = pytest.mark.SUITE_smoke + +class TestEditorTest(unittest.TestCase): + + @mock.patch('inspect.isclass', mock.MagicMock(return_value=True)) + def test_PytestPycollectMakeitem_ValidArgs_CallsCorrectly(self): + mock_collector = mock.MagicMock() + mock_name = mock.MagicMock() + mock_obj = mock.MagicMock() + mock_base = mock.MagicMock() + mock_obj.__bases__ = [mock_base] + + editor_test.pytest_pycollect_makeitem(mock_collector, mock_name, mock_obj) + mock_base.pytest_custom_makeitem.assert_called_once_with(mock_collector, mock_name, mock_obj) + + def test_PytestCollectionModifyitem_OneValidClass_CallsOnce(self): + mock_item = mock.MagicMock() + mock_class = mock.MagicMock() + mock_class.pytest_custom_modify_items = mock.MagicMock() + mock_item.instance.__class__ = mock_class + mock_session = mock.MagicMock() + mock_items = [mock_item, mock.MagicMock()] + mock_config = mock.MagicMock() + + generator = editor_test.pytest_collection_modifyitems(mock_session, mock_items, mock_config) + for x in generator: + pass + assert mock_class.pytest_custom_modify_items.call_count == 1 diff --git a/cmake/3rdParty/Platform/Linux/BuiltInPackages_linux.cmake b/cmake/3rdParty/Platform/Linux/BuiltInPackages_linux.cmake index 6210b9cc18..25424a348c 100644 --- a/cmake/3rdParty/Platform/Linux/BuiltInPackages_linux.cmake +++ b/cmake/3rdParty/Platform/Linux/BuiltInPackages_linux.cmake @@ -33,9 +33,10 @@ ly_associate_package(PACKAGE_NAME mikkelsen-1.0.0.4-linux ly_associate_package(PACKAGE_NAME googletest-1.8.1-rev4-linux TARGETS googletest PACKAGE_HASH 7b7ad330f369450c316a4c4592d17fbb4c14c731c95bd8f37757203e8c2bbc1b) ly_associate_package(PACKAGE_NAME googlebenchmark-1.5.0-rev2-linux TARGETS GoogleBenchmark PACKAGE_HASH 4038878f337fc7e0274f0230f71851b385b2e0327c495fc3dd3d1c18a807928d) ly_associate_package(PACKAGE_NAME unwind-1.2.1-linux TARGETS unwind PACKAGE_HASH 3453265fb056e25432f611a61546a25f60388e315515ad39007b5925dd054a77) -ly_associate_package(PACKAGE_NAME qt-5.15.2-rev5-linux TARGETS Qt PACKAGE_HASH 76b395897b941a173002845c7219a5f8a799e44b269ffefe8091acc048130f28) +ly_associate_package(PACKAGE_NAME qt-5.15.2-rev6-linux TARGETS Qt PACKAGE_HASH a37bd9989f1e8fe57d94b98cbf9bd5c3caaea740e2f314e5162fa77300551531) ly_associate_package(PACKAGE_NAME libpng-1.6.37-rev1-linux TARGETS libpng PACKAGE_HASH 896451999f1de76375599aec4b34ae0573d8d34620d9ab29cc30b8739c265ba6) ly_associate_package(PACKAGE_NAME libsamplerate-0.2.1-rev2-linux TARGETS libsamplerate PACKAGE_HASH 41643c31bc6b7d037f895f89d8d8d6369e906b92eff42b0fe05ee6a100f06261) +ly_associate_package(PACKAGE_NAME OpenMesh-8.1-rev3-linux TARGETS OpenMesh PACKAGE_HASH 805bd0b24911bb00c7f575b8c3f10d7ea16548a5014c40811894a9445f17a126) ly_associate_package(PACKAGE_NAME OpenSSL-1.1.1b-rev2-linux TARGETS OpenSSL PACKAGE_HASH b779426d1e9c5ddf71160d5ae2e639c3b956e0fb5e9fcaf9ce97c4526024e3bc) ly_associate_package(PACKAGE_NAME DirectXShaderCompilerDxc-1.6.2104-o3de-rev3-linux TARGETS DirectXShaderCompilerDxc PACKAGE_HASH 88c4a359325d749bc34090b9ac466424847f3b71ba0de15045cf355c17c07099) ly_associate_package(PACKAGE_NAME SPIRVCross-2021.04.29-rev1-linux TARGETS SPIRVCross PACKAGE_HASH 7889ee5460a688e9b910c0168b31445c0079d363affa07b25d4c8aeb608a0b80) diff --git a/cmake/3rdParty/Platform/Mac/BuiltInPackages_mac.cmake b/cmake/3rdParty/Platform/Mac/BuiltInPackages_mac.cmake index d054ba22e1..1ac7568a98 100644 --- a/cmake/3rdParty/Platform/Mac/BuiltInPackages_mac.cmake +++ b/cmake/3rdParty/Platform/Mac/BuiltInPackages_mac.cmake @@ -33,6 +33,7 @@ ly_associate_package(PACKAGE_NAME mcpp-2.7.2_az.2-rev1-mac ly_associate_package(PACKAGE_NAME mikkelsen-1.0.0.4-mac TARGETS mikkelsen PACKAGE_HASH 83af99ca8bee123684ad254263add556f0cf49486c0b3e32e6d303535714e505) ly_associate_package(PACKAGE_NAME googletest-1.8.1-rev4-mac TARGETS googletest PACKAGE_HASH cbf020d5ef976c5db8b6e894c6c63151ade85ed98e7c502729dd20172acae5a8) ly_associate_package(PACKAGE_NAME googlebenchmark-1.5.0-rev2-mac TARGETS GoogleBenchmark PACKAGE_HASH ad25de0146769c91e179953d845de2bec8ed4a691f973f47e3eb37639381f665) +ly_associate_package(PACKAGE_NAME OpenMesh-8.1-rev3-mac TARGETS OpenMesh PACKAGE_HASH af92db02a25c1f7e1741ec898f49d81d52631e00336bf9bddd1e191590063c2f) ly_associate_package(PACKAGE_NAME OpenSSL-1.1.1b-rev1-mac TARGETS OpenSSL PACKAGE_HASH 28adc1c0616ac0482b2a9d7b4a3a3635a1020e87b163f8aba687c501cf35f96c) ly_associate_package(PACKAGE_NAME qt-5.15.2-rev5-mac TARGETS Qt PACKAGE_HASH 9d25918351898b308ded3e9e571fff6f26311b2071aeafd00dd5b249fdf53f7e) ly_associate_package(PACKAGE_NAME libpng-1.6.37-mac TARGETS libpng PACKAGE_HASH 1ad76cd038ccc1f288f83c5fe2859a0f35c5154e1fe7658e1230cc428d318a8b) diff --git a/cmake/3rdParty/Platform/Windows/BuiltInPackages_windows.cmake b/cmake/3rdParty/Platform/Windows/BuiltInPackages_windows.cmake index fce2229771..3189625611 100644 --- a/cmake/3rdParty/Platform/Windows/BuiltInPackages_windows.cmake +++ b/cmake/3rdParty/Platform/Windows/BuiltInPackages_windows.cmake @@ -40,7 +40,7 @@ ly_associate_package(PACKAGE_NAME openimageio-2.1.16.0-rev2-windows ly_associate_package(PACKAGE_NAME qt-5.15.2-rev4-windows TARGETS Qt PACKAGE_HASH a4634caaf48192cad5c5f408504746e53d338856148285057274f6a0ccdc071d) ly_associate_package(PACKAGE_NAME libpng-1.6.37-rev1-windows TARGETS libpng PACKAGE_HASH aa20c894fbd7cdaea585a54e37620b3454a7e414a58128acd68ccf6fe76c47d6) ly_associate_package(PACKAGE_NAME libsamplerate-0.2.1-rev2-windows TARGETS libsamplerate PACKAGE_HASH dcf3c11a96f212a52e2c9241abde5c364ee90b0f32fe6eeb6dcdca01d491829f) -ly_associate_package(PACKAGE_NAME OpenMesh-8.1-rev1-windows TARGETS OpenMesh PACKAGE_HASH 1c1df639358526c368e790dfce40c45cbdfcfb1c9a041b9d7054a8949d88ee77) +ly_associate_package(PACKAGE_NAME OpenMesh-8.1-rev3-windows TARGETS OpenMesh PACKAGE_HASH 7a6309323ad03bfc646bd04ecc79c3711de6790e4ff5a72f83a8f5a8f496d684) ly_associate_package(PACKAGE_NAME civetweb-1.8-rev1-windows TARGETS civetweb PACKAGE_HASH 36d0e58a59bcdb4dd70493fb1b177aa0354c945b06c30416348fd326cf323dd4) ly_associate_package(PACKAGE_NAME OpenSSL-1.1.1b-rev2-windows TARGETS OpenSSL PACKAGE_HASH 9af1c50343f89146b4053101a7aeb20513319a3fe2f007e356d7ce25f9241040) ly_associate_package(PACKAGE_NAME Crashpad-0.8.0-rev1-windows TARGETS Crashpad PACKAGE_HASH d162aa3070147bc0130a44caab02c5fe58606910252caf7f90472bd48d4e31e2) diff --git a/cmake/Install.cmake b/cmake/Install.cmake index 3b74e6c654..b558590b9e 100644 --- a/cmake/Install.cmake +++ b/cmake/Install.cmake @@ -8,16 +8,38 @@ set(LY_INSTALL_ENABLED TRUE CACHE BOOL "Indicates if the install process is enabled") -if(LY_INSTALL_ENABLED) - ly_get_absolute_pal_filename(pal_dir ${CMAKE_CURRENT_SOURCE_DIR}/cmake/Platform/${PAL_PLATFORM_NAME}) - include(${pal_dir}/Install_${PAL_PLATFORM_NAME_LOWERCASE}.cmake) -endif() +#! ly_install: wrapper to install that handles common functionality +# +# \notes: +# - this wrapper handles the case where common installs are called multiple times from different +# build folders (when using LY_INSTALL_EXTERNAL_BUILD_DIRS) to generate install layouts that +# have multiple build permutations +# +function(ly_install) + + if(NOT LY_INSTALL_ENABLED) + return() + endif() + + cmake_parse_arguments(ly_install "" "COMPONENT" "" ${ARGN}) + if (NOT ly_install_COMPONENT OR "${ly_install_COMPONENT}" STREQUAL "${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME}") + # if it is installing under the default component, we need to de-duplicate since we can have + # cases coming from different build directories (when using LY_INSTALL_EXTERNAL_BUILD_DIRS) + install(CODE "if(NOT LY_CORE_COMPONENT_ALREADY_INCLUDED)" ALL_COMPONENTS) + install(${ARGN}) + install(CODE "endif()\n" ALL_COMPONENTS) + else() + install(${ARGN}) + endif() + +endfunction() #! ly_install_directory: specifies a directory to be copied to the install layout at install time # # \arg:DIRECTORIES directories to install # \arg:DESTINATION (optional) destination to install the directory to (relative to CMAKE_PREFIX_PATH) # \arg:EXCLUDE_PATTERNS (optional) patterns to exclude +# \arg:COMPONENT (optional) component to use (defaults to CMAKE_INSTALL_DEFAULT_COMPONENT_NAME) # \arg:VERBATIM (optional) copies the directories as they are, this excludes the default exclude patterns # # \notes: @@ -34,7 +56,7 @@ function(ly_install_directory) endif() set(options VERBATIM) - set(oneValueArgs DESTINATION) + set(oneValueArgs DESTINATION COMPONENT) set(multiValueArgs DIRECTORIES EXCLUDE_PATTERNS) cmake_parse_arguments(ly_install_directory "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) @@ -42,6 +64,10 @@ function(ly_install_directory) if(NOT ly_install_directory_DIRECTORIES) message(FATAL_ERROR "You must provide at least a directory to install") endif() + + if(NOT ly_install_directory_COMPONENT) + set(ly_install_directory_COMPONENT ${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME}) + endif() foreach(directory ${ly_install_directory_DIRECTORIES}) @@ -77,11 +103,12 @@ function(ly_install_directory) list(APPEND exclude_patterns PATTERN *.egg-info EXCLUDE) endif() - install(DIRECTORY ${directory} + ly_install(DIRECTORY ${directory} DESTINATION ${ly_install_directory_DESTINATION} - COMPONENT ${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME} # use the deafult for the time being + COMPONENT ${ly_install_directory_COMPONENT} ${exclude_patterns} ) + endforeach() endfunction() @@ -126,7 +153,7 @@ function(ly_install_files) set(install_type FILES) endif() - install(${install_type} ${files} + ly_install(${install_type} ${files} DESTINATION ${ly_install_files_DESTINATION} COMPONENT ${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME} # use the default for the time being ) @@ -144,7 +171,7 @@ function(ly_install_run_code CODE) return() endif() - install(CODE ${CODE} + ly_install(CODE ${CODE} COMPONENT ${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME} # use the default for the time being ) @@ -161,8 +188,13 @@ function(ly_install_run_script SCRIPT) return() endif() - install(SCRIPT ${SCRIPT} + ly_install(SCRIPT ${SCRIPT} COMPONENT ${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME} # use the default for the time being ) -endfunction() \ No newline at end of file +endfunction() + +if(LY_INSTALL_ENABLED) + ly_get_absolute_pal_filename(pal_dir ${CMAKE_CURRENT_SOURCE_DIR}/cmake/Platform/${PAL_PLATFORM_NAME}) + include(${pal_dir}/Install_${PAL_PLATFORM_NAME_LOWERCASE}.cmake) +endif() diff --git a/cmake/LYWrappers.cmake b/cmake/LYWrappers.cmake index fb3d420c26..0416d41ece 100644 --- a/cmake/LYWrappers.cmake +++ b/cmake/LYWrappers.cmake @@ -403,7 +403,7 @@ function(ly_target_link_libraries TARGET) message(FATAL_ERROR "You must provide a target") endif() - set_property(GLOBAL APPEND PROPERTY LY_DELAYED_LINK_${TARGET} ${ARGN}) + set_property(TARGET ${TARGET} APPEND PROPERTY LY_DELAYED_LINK ${ARGN}) set_property(GLOBAL APPEND PROPERTY LY_DELAYED_LINK_TARGETS ${TARGET}) # to walk them at the end endfunction() @@ -430,7 +430,7 @@ function(ly_delayed_target_link_libraries) get_property(delayed_targets GLOBAL PROPERTY LY_DELAYED_LINK_TARGETS) foreach(target ${delayed_targets}) - get_property(delayed_link GLOBAL PROPERTY LY_DELAYED_LINK_${target}) + get_property(delayed_link TARGET ${target} PROPERTY LY_DELAYED_LINK) if(delayed_link) cmake_parse_arguments(ly_delayed_target_link_libraries "" "" "${visibilities}" ${delayed_link}) @@ -458,7 +458,6 @@ function(ly_delayed_target_link_libraries) endforeach() endforeach() - set_property(GLOBAL PROPERTY LY_DELAYED_LINK_${target}) endif() diff --git a/cmake/Packaging.cmake b/cmake/Packaging.cmake index 006689549b..6a1e2d8cef 100644 --- a/cmake/Packaging.cmake +++ b/cmake/Packaging.cmake @@ -134,8 +134,9 @@ if(NOT EXISTS ${_cmake_package_dest}) endif() endif() -install(FILES ${_cmake_package_dest} +ly_install(FILES ${_cmake_package_dest} DESTINATION ./Tools/Redistributables/CMake + COMPONENT ${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME} ) # the version string and git tags are intended to be synchronized so it should be safe to use that instead @@ -157,8 +158,9 @@ if(${CPACK_PACKAGE_VERSION} VERSION_GREATER "0.0.0.0") list(POP_FRONT _status _status_code) if (${_status_code} EQUAL 0 AND EXISTS ${_3rd_party_license_dest}) - install(FILES ${_3rd_party_license_dest} + ly_install(FILES ${_3rd_party_license_dest} DESTINATION . + COMPONENT ${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME} ) else() file(REMOVE ${_3rd_party_license_dest}) @@ -202,44 +204,69 @@ endif() # IMPORTANT: required to be included AFTER setting all property overrides include(CPack REQUIRED) -function(ly_configure_cpack_component ly_configure_cpack_component_NAME) - - set(options REQUIRED) - set(oneValueArgs DISPLAY_NAME DESCRIPTION LICENSE_NAME LICENSE_FILE) - set(multiValueArgs) - - cmake_parse_arguments(ly_configure_cpack_component "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) - - # default to optional - set(component_type DISABLED) - - if(ly_configure_cpack_component_REQUIRED) - set(component_type REQUIRED) - endif() - - set(license_name ${DEFAULT_LICENSE_NAME}) - set(license_file ${DEFAULT_LICENSE_FILE}) - - if(ly_configure_cpack_component_LICENSE_NAME AND ly_configure_cpack_component_LICENSE_FILE) - set(license_name ${ly_configure_cpack_component_LICENSE_NAME}) - set(license_file ${ly_configure_cpack_component_LICENSE_FILE}) - elseif(ly_configure_cpack_component_LICENSE_NAME OR ly_configure_cpack_component_LICENSE_FILE) - message(FATAL_ERROR "Invalid argument configuration. Both LICENSE_NAME and LICENSE_FILE must be set for ly_configure_cpack_component") - endif() - - cpack_add_component( - ${ly_configure_cpack_component_NAME} ${component_type} - DISPLAY_NAME ${ly_configure_cpack_component_DISPLAY_NAME} - DESCRIPTION ${ly_configure_cpack_component_DESCRIPTION} - ) -endfunction() - # configure ALL components here -ly_configure_cpack_component( - ${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME} REQUIRED - DISPLAY_NAME "${PROJECT_NAME} Core" - DESCRIPTION "${PROJECT_NAME} Headers, Libraries and Tools" -) +file(APPEND "${CPACK_OUTPUT_CONFIG_FILE}" " +set(CPACK_COMPONENTS_ALL ${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME}) +set(CPACK_COMPONENT_${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME}_DISPLAY_NAME \"Common files\") +set(CPACK_COMPONENT_${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME}_DESCRIPTION \"${PROJECT_NAME} Headers, scripts and common files\") +set(CPACK_COMPONENT_${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME}_REQUIRED TRUE) +set(CPACK_COMPONENT_${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME}_DISABLED FALSE) + +include(CPackComponents.cmake) +") + +# Generate a file (CPackComponents.config) that we will include that defines the components +# for this build permutation. This way we can get components for other permutations being passed +# through LY_INSTALL_EXTERNAL_BUILD_DIRS +unset(cpack_components_contents) + +set(required "FALSE") +set(disabled "FALSE") +if(${LY_INSTALL_PERMUTATION_COMPONENT} STREQUAL DEFAULT) + set(required "TRUE") +else() + set(disabled "TRUE") +endif() +string(APPEND cpack_components_contents " +list(APPEND CPACK_COMPONENTS_ALL ${LY_INSTALL_PERMUTATION_COMPONENT}) +set(CPACK_COMPONENT_${LY_INSTALL_PERMUTATION_COMPONENT}_DISPLAY_NAME \"${LY_BUILD_PERMUTATION} common files\") +set(CPACK_COMPONENT_${LY_INSTALL_PERMUTATION_COMPONENT}_DESCRIPTION \"${PROJECT_NAME} scripts and common files for ${LY_BUILD_PERMUTATION}\") +set(CPACK_COMPONENT_${LY_INSTALL_PERMUTATION_COMPONENT}_DEPENDS ${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME}) +set(CPACK_COMPONENT_${LY_INSTALL_PERMUTATION_COMPONENT}_REQUIRED ${required}) +set(CPACK_COMPONENT_${LY_INSTALL_PERMUTATION_COMPONENT}_DISABLED ${disabled}) +") + +foreach(conf IN LISTS CMAKE_CONFIGURATION_TYPES) + string(TOUPPER ${conf} UCONF) + set(required "FALSE") + set(disabled "FALSE") + if(${conf} STREQUAL profile AND ${LY_INSTALL_PERMUTATION_COMPONENT} STREQUAL DEFAULT) + set(required "TRUE") + else() + set(disabled "TRUE") + endif() + + # Inject a check to not declare components that have not been built. We are using AzCore since that is a + # common target that will always be build, in every permutation and configuration + string(APPEND cpack_components_contents " +if(EXISTS \"${CMAKE_ARCHIVE_OUTPUT_DIRECTORY}/${conf}/${CMAKE_STATIC_LIBRARY_PREFIX}AzCore${CMAKE_STATIC_LIBRARY_SUFFIX}\") + list(APPEND CPACK_COMPONENTS_ALL ${LY_INSTALL_PERMUTATION_COMPONENT}_${UCONF}) + set(CPACK_COMPONENT_${LY_INSTALL_PERMUTATION_COMPONENT}_${UCONF}_DISPLAY_NAME \"Binaries for ${LY_BUILD_PERMUTATION} ${conf}\") + set(CPACK_COMPONENT_${LY_INSTALL_PERMUTATION_COMPONENT}_${UCONF}_DESCRIPTION \"${PROJECT_NAME} libraries and applications for ${LY_BUILD_PERMUTATION} ${conf}\") + set(CPACK_COMPONENT_${LY_INSTALL_PERMUTATION_COMPONENT}_${UCONF}_DEPENDS ${LY_INSTALL_PERMUTATION_COMPONENT}) + set(CPACK_COMPONENT_${LY_INSTALL_PERMUTATION_COMPONENT}_${UCONF}_REQUIRED ${required}) + set(CPACK_COMPONENT_${LY_INSTALL_PERMUTATION_COMPONENT}_${UCONF}_DISABLED ${disabled}) +endif() +") +endforeach() +file(WRITE "${CMAKE_BINARY_DIR}/CPackComponents.cmake" ${cpack_components_contents}) + +# Inject other build directories +foreach(external_dir ${LY_INSTALL_EXTERNAL_BUILD_DIRS}) + file(APPEND "${CPACK_OUTPUT_CONFIG_FILE}" + "include(${external_dir}/CPackComponents.cmake)\n" + ) +endforeach() if(LY_INSTALLER_DOWNLOAD_URL) strip_trailing_slash(${LY_INSTALLER_DOWNLOAD_URL} LY_INSTALLER_DOWNLOAD_URL) diff --git a/cmake/Platform/Common/Install_common.cmake b/cmake/Platform/Common/Install_common.cmake index 44cdeb1994..5e9f6ad0ce 100644 --- a/cmake/Platform/Common/Install_common.cmake +++ b/cmake/Platform/Common/Install_common.cmake @@ -8,6 +8,14 @@ include(cmake/FileUtil.cmake) +set(LY_INSTALL_EXTERNAL_BUILD_DIRS "" CACHE PATH "External build directories to be included in the install process. This allows to package non-monolithic and monolithic.") +unset(normalized_external_build_dirs) +foreach(external_dir ${LY_INSTALL_EXTERNAL_BUILD_DIRS}) + cmake_path(ABSOLUTE_PATH external_dir BASE_DIRECTORY ${LY_ROOT_FOLDER} NORMALIZE) + list(APPEND normalized_external_build_dirs ${external_dir}) +endforeach() +set(LY_INSTALL_EXTERNAL_BUILD_DIRS ${normalized_external_build_dirs}) + set(CMAKE_INSTALL_MESSAGE NEVER) # Simplify messages to reduce output noise define_property(TARGET PROPERTY LY_INSTALL_GENERATE_RUN_TARGET @@ -19,13 +27,25 @@ define_property(TARGET PROPERTY LY_INSTALL_GENERATE_RUN_TARGET ]] ) -ly_set(CMAKE_INSTALL_DEFAULT_COMPONENT_NAME Core) +# We can have elements being installed under the following components: +# - Core (required for all) (default) +# - Default +# - Default_$ +# - Monolithic +# - Monolithic_$ +# Debug/Monolithic are build permutations, so for a CMake run, it can only generate +# one of the permutations. Each build permutation can generate only one cmake_install.cmake. +# Each build permutation will generate the same elements in Core. +# CPack is able to put the two together by taking Core from one permutation and then taking +# each permutation. +ly_set(CMAKE_INSTALL_DEFAULT_COMPONENT_NAME CORE) if(LY_MONOLITHIC_GAME) set(LY_BUILD_PERMUTATION Monolithic) else() set(LY_BUILD_PERMUTATION Default) endif() +string(TOUPPER ${LY_BUILD_PERMUTATION} LY_INSTALL_PERMUTATION_COMPONENT) cmake_path(RELATIVE_PATH CMAKE_RUNTIME_OUTPUT_DIRECTORY BASE_DIRECTORY ${CMAKE_BINARY_DIR} OUTPUT_VARIABLE runtime_output_directory) cmake_path(RELATIVE_PATH CMAKE_LIBRARY_OUTPUT_DIRECTORY BASE_DIRECTORY ${CMAKE_BINARY_DIR} OUTPUT_VARIABLE library_output_directory) @@ -65,14 +85,24 @@ function(ly_setup_target OUTPUT_CONFIGURED_TARGET ALIAS_TARGET_NAME absolute_tar continue() endif() + # For some cases (e.g. codegen) we generate headers that end up in the BUILD_DIR. Since the BUILD_DIR + # is per-permutation, we need to install such headers per permutation. For the other cases, we can install + # under the default component since they are shared across permutations/configs. + cmake_path(IS_PREFIX CMAKE_BINARY_DIR ${include_directory} NORMALIZE include_directory_child_of_build) + if(NOT include_directory_child_of_build) + set(include_directory_component ${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME}) + else() + set(include_directory_component ${LY_INSTALL_PERMUTATION_COMPONENT}) + endif() + unset(rel_include_dir) cmake_path(RELATIVE_PATH include_directory BASE_DIRECTORY ${LY_ROOT_FOLDER} OUTPUT_VARIABLE rel_include_dir) cmake_path(APPEND rel_include_dir "..") cmake_path(NORMAL_PATH rel_include_dir OUTPUT_VARIABLE destination_dir) - - install(DIRECTORY ${include_directory} + + ly_install(DIRECTORY ${include_directory} DESTINATION ${destination_dir} - COMPONENT ${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME} + COMPONENT ${include_directory_component} FILES_MATCHING PATTERN *.h PATTERN *.hpp @@ -94,9 +124,9 @@ function(ly_setup_target OUTPUT_CONFIGURED_TARGET ALIAS_TARGET_NAME absolute_tar cmake_path(RELATIVE_PATH target_library_output_directory BASE_DIRECTORY ${CMAKE_LIBRARY_OUTPUT_DIRECTORY} OUTPUT_VARIABLE target_library_output_subdirectory) endif() - if(COMMAND ly_install_target_override) + if(COMMAND ly_setup_target_install_targets_override) # Mac needs special handling because of a cmake issue - ly_install_target_override(TARGET ${TARGET_NAME} + ly_setup_target_install_targets_override(TARGET ${TARGET_NAME} ARCHIVE_DIR ${archive_output_directory} LIBRARY_DIR ${library_output_directory} RUNTIME_DIR ${runtime_output_directory} @@ -104,18 +134,23 @@ function(ly_setup_target OUTPUT_CONFIGURED_TARGET ALIAS_TARGET_NAME absolute_tar RUNTIME_SUBDIR ${target_runtime_output_subdirectory} ) else() - install( - TARGETS ${TARGET_NAME} - ARCHIVE - DESTINATION ${archive_output_directory} - COMPONENT ${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME} - LIBRARY - DESTINATION ${library_output_directory}/${target_library_output_subdirectory} - COMPONENT ${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME} - RUNTIME - DESTINATION ${runtime_output_directory}/${target_runtime_output_subdirectory} - COMPONENT ${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME} - ) + foreach(conf IN LISTS CMAKE_CONFIGURATION_TYPES) + string(TOUPPER ${conf} UCONF) + ly_install(TARGETS ${TARGET_NAME} + ARCHIVE + DESTINATION ${archive_output_directory} + COMPONENT ${LY_INSTALL_PERMUTATION_COMPONENT}_${UCONF} + CONFIGURATIONS ${conf} + LIBRARY + DESTINATION ${library_output_directory}/${target_library_output_subdirectory} + COMPONENT ${LY_INSTALL_PERMUTATION_COMPONENT}_${UCONF} + CONFIGURATIONS ${conf} + RUNTIME + DESTINATION ${runtime_output_directory}/${target_runtime_output_subdirectory} + COMPONENT ${LY_INSTALL_PERMUTATION_COMPONENT}_${UCONF} + CONFIGURATIONS ${conf} + ) + endforeach() endif() # CMakeLists.txt related files @@ -157,16 +192,15 @@ function(ly_setup_target OUTPUT_CONFIGURED_TARGET ALIAS_TARGET_NAME absolute_tar endif() # Includes need additional processing to add the install root - if(include_directories) - foreach(include ${include_directories}) - string(GENEX_STRIP ${include} include_genex_expr) - if(include_genex_expr STREQUAL include) # only for cases where there are no generation expressions - # Make the include path relative to the source dir where the target will be declared - cmake_path(RELATIVE_PATH include BASE_DIRECTORY ${absolute_target_source_dir} OUTPUT_VARIABLE target_include) - string(APPEND INCLUDE_DIRECTORIES_PLACEHOLDER "${PLACEHOLDER_INDENT}${target_include}\n") - endif() - endforeach() - endif() + foreach(include IN LISTS include_directories) + string(GENEX_STRIP ${include} include_genex_expr) + if(include_genex_expr STREQUAL include) # only for cases where there are no generation expressions + # Make the include path relative to the source dir where the target will be declared + cmake_path(RELATIVE_PATH include BASE_DIRECTORY ${absolute_target_source_dir} OUTPUT_VARIABLE target_include) + list(APPEND INCLUDE_DIRECTORIES_PLACEHOLDER "${PLACEHOLDER_INDENT}${target_include}") + endif() + endforeach() + list(JOIN INCLUDE_DIRECTORIES_PLACEHOLDER "\n" INCLUDE_DIRECTORIES_PLACEHOLDER) string(REPEAT " " 8 PLACEHOLDER_INDENT) get_target_property(RUNTIME_DEPENDENCIES_PLACEHOLDER ${TARGET_NAME} MANUALLY_ADDED_DEPENDENCIES) @@ -178,27 +212,27 @@ function(ly_setup_target OUTPUT_CONFIGURED_TARGET ALIAS_TARGET_NAME absolute_tar endif() string(REPEAT " " 12 PLACEHOLDER_INDENT) - get_target_property(inteface_build_dependencies_props ${TARGET_NAME} INTERFACE_LINK_LIBRARIES) + get_property(interface_build_dependencies_props TARGET ${TARGET_NAME} PROPERTY LY_DELAYED_LINK) unset(INTERFACE_BUILD_DEPENDENCIES_PLACEHOLDER) - if(inteface_build_dependencies_props) - foreach(build_dependency ${inteface_build_dependencies_props}) + if(interface_build_dependencies_props) + cmake_parse_arguments(build_deps "" "" "PRIVATE;PUBLIC;INTERFACE" ${interface_build_dependencies_props}) + # Interface and public dependencies should always be exposed + set(build_deps_target ${build_deps_INTERFACE}) + if(build_deps_PUBLIC) + set(build_deps_target "${build_deps_target};${build_deps_PUBLIC}") + endif() + # Private dependencies should only be exposed if it is a static library, since in those cases, link + # dependencies are transfered to the downstream dependencies + if("${target_type}" STREQUAL "STATIC_LIBRARY") + set(build_deps_target "${build_deps_target};${build_deps_PRIVATE}") + endif() + foreach(build_dependency IN LISTS build_deps_target) # Skip wrapping produced when targets are not created in the same directory - if(NOT ${build_dependency} MATCHES "^::@") + if(build_dependency) list(APPEND INTERFACE_BUILD_DEPENDENCIES_PLACEHOLDER "${PLACEHOLDER_INDENT}${build_dependency}") endif() endforeach() endif() - # We also need to pass the private link libraries since we will use that to generate the runtime dependencies - get_target_property(private_build_dependencies_props ${TARGET_NAME} LINK_LIBRARIES) - if(private_build_dependencies_props) - foreach(build_dependency ${private_build_dependencies_props}) - # Skip wrapping produced when targets are not created in the same directory - if(NOT ${build_dependency} MATCHES "^::@") - list(APPEND INTERFACE_BUILD_DEPENDENCIES_PLACEHOLDER "${PLACEHOLDER_INDENT}${build_dependency}") - endif() - endforeach() - endif() - list(REMOVE_DUPLICATES INTERFACE_BUILD_DEPENDENCIES_PLACEHOLDER) list(JOIN INTERFACE_BUILD_DEPENDENCIES_PLACEHOLDER "\n" INTERFACE_BUILD_DEPENDENCIES_PLACEHOLDER) string(REPEAT " " 8 PLACEHOLDER_INDENT) @@ -269,10 +303,15 @@ set_property(TARGET ${NAME_PLACEHOLDER} set(target_install_source_dir ${CMAKE_CURRENT_BINARY_DIR}/install/${relative_target_source_dir}) file(GENERATE OUTPUT "${target_install_source_dir}/Platform/${PAL_PLATFORM_NAME}/${LY_BUILD_PERMUTATION}/${NAME_PLACEHOLDER}_$.cmake" CONTENT "${target_file_contents}") - install(FILES "${target_install_source_dir}/Platform/${PAL_PLATFORM_NAME}/${LY_BUILD_PERMUTATION}/${NAME_PLACEHOLDER}_$.cmake" - DESTINATION ${relative_target_source_dir}/Platform/${PAL_PLATFORM_NAME}/${LY_BUILD_PERMUTATION} - COMPONENT ${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME} - ) + + foreach(conf IN LISTS CMAKE_CONFIGURATION_TYPES) + string(TOUPPER ${conf} UCONF) + ly_install(FILES "${target_install_source_dir}/Platform/${PAL_PLATFORM_NAME}/${LY_BUILD_PERMUTATION}/${NAME_PLACEHOLDER}_${conf}.cmake" + DESTINATION ${relative_target_source_dir}/Platform/${PAL_PLATFORM_NAME}/${LY_BUILD_PERMUTATION} + COMPONENT ${LY_INSTALL_PERMUTATION_COMPONENT}_${UCONF} + CONFIGURATIONS ${conf} + ) + endforeach() # Since a CMakeLists.txt could contain multiple targets, we generate it in a folder per target ly_file_read(${LY_ROOT_FOLDER}/cmake/install/InstalledTarget.in target_cmakelists_template) @@ -312,7 +351,8 @@ function(ly_setup_subdirectory absolute_target_source_dir) @cmake_copyright_comment@ include(Platform/${PAL_PLATFORM_NAME}/platform_${PAL_PLATFORM_NAME_LOWERCASE}.cmake) ]] @ONLY) - install(FILES "${target_install_source_dir}/CMakeLists.txt" + + ly_install(FILES "${target_install_source_dir}/CMakeLists.txt" DESTINATION ${relative_target_source_dir} COMPONENT ${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME} ) @@ -322,12 +362,12 @@ include(Platform/${PAL_PLATFORM_NAME}/platform_${PAL_PLATFORM_NAME_LOWERCASE}.cm file(CONFIGURE OUTPUT "${target_install_source_dir}/Platform/${PAL_PLATFORM_NAME}/platform_${PAL_PLATFORM_NAME_LOWERCASE}.cmake" CONTENT [[ @cmake_copyright_comment@ if(LY_MONOLITHIC_GAME) - include(Platform/${PAL_PLATFORM_NAME}/Monolithic/permutation.cmake) + include(Platform/${PAL_PLATFORM_NAME}/Monolithic/permutation.cmake OPTIONAL) else() include(Platform/${PAL_PLATFORM_NAME}/Default/permutation.cmake) endif() ]]) - install(FILES "${target_install_source_dir}/Platform/${PAL_PLATFORM_NAME}/platform_${PAL_PLATFORM_NAME_LOWERCASE}.cmake" + ly_install(FILES "${target_install_source_dir}/Platform/${PAL_PLATFORM_NAME}/platform_${PAL_PLATFORM_NAME_LOWERCASE}.cmake" DESTINATION ${relative_target_source_dir}/Platform/${PAL_PLATFORM_NAME} COMPONENT ${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME} ) @@ -347,63 +387,45 @@ endif() "${GEM_VARIANT_TO_LOAD_PLACEHOLDER}" "${ENABLE_GEMS_PLACEHOLDER}" ) - install(FILES "${target_install_source_dir}/Platform/${PAL_PLATFORM_NAME}/${LY_BUILD_PERMUTATION}/permutation.cmake" - DESTINATION ${relative_target_source_dir}//Platform/${PAL_PLATFORM_NAME}/${LY_BUILD_PERMUTATION} - COMPONENT ${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME} + + ly_install(FILES "${target_install_source_dir}/Platform/${PAL_PLATFORM_NAME}/${LY_BUILD_PERMUTATION}/permutation.cmake" + DESTINATION ${relative_target_source_dir}/Platform/${PAL_PLATFORM_NAME}/${LY_BUILD_PERMUTATION} + COMPONENT ${LY_INSTALL_PERMUTATION_COMPONENT} ) endfunction() -#! ly_setup_o3de_install: orchestrates the installation of the different parts. This is the entry point from the root CMakeLists.txt -function(ly_setup_o3de_install) - - ly_setup_subdirectories() - ly_setup_cmake_install() - ly_setup_runtime_dependencies() - ly_setup_assets() - - # Misc - install(FILES - ${LY_ROOT_FOLDER}/pytest.ini - ${LY_ROOT_FOLDER}/LICENSE.txt - ${LY_ROOT_FOLDER}/README.md - DESTINATION . - COMPONENT ${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME} - ) - - if(COMMAND ly_post_install_steps) - ly_post_install_steps() - endif() - -endfunction() - #! ly_setup_cmake_install: install the "cmake" folder function(ly_setup_cmake_install) - install(DIRECTORY "${LY_ROOT_FOLDER}/cmake" + ly_install(DIRECTORY "${LY_ROOT_FOLDER}/cmake" DESTINATION . COMPONENT ${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME} PATTERN "__pycache__" EXCLUDE PATTERN "Findo3de.cmake" EXCLUDE + PATTERN "cmake/ConfigurationTypes.cmake" EXCLUDE REGEX "3rdParty/Platform\/.*\/BuiltInPackages_.*\.cmake" EXCLUDE ) + # Connect configuration types - install(FILES "${LY_ROOT_FOLDER}/cmake/install/ConfigurationTypes.cmake" + ly_install(FILES "${LY_ROOT_FOLDER}/cmake/install/ConfigurationTypes.cmake" DESTINATION cmake COMPONENT ${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME} ) - # Inject code that will generate each ConfigurationType_.cmake file - set(install_configuration_type_template [=[ - configure_file(@LY_ROOT_FOLDER@/cmake/install/ConfigurationType_config.cmake.in - ${CMAKE_INSTALL_PREFIX}/cmake/Platform/@PAL_PLATFORM_NAME@/@LY_BUILD_PERMUTATION@/ConfigurationTypes_${CMAKE_INSTALL_CONFIG_NAME}.cmake + + # generate each ConfigurationType_.cmake file and install it under that configuration + foreach(conf IN LISTS CMAKE_CONFIGURATION_TYPES) + string(TOUPPER ${conf} UCONF) + configure_file("${LY_ROOT_FOLDER}/cmake/install/ConfigurationType_config.cmake.in" + "${CMAKE_BINARY_DIR}/cmake/Platform/${PAL_PLATFORM_NAME}/${LY_BUILD_PERMUTATION}/ConfigurationTypes_${conf}.cmake" @ONLY ) - message(STATUS "Generated ${CMAKE_INSTALL_PREFIX}/cmake/Platform/@PAL_PLATFORM_NAME@/@LY_BUILD_PERMUTATION@/ConfigurationTypes_${CMAKE_INSTALL_CONFIG_NAME}.cmake") - ]=]) - string(CONFIGURE "${install_configuration_type_template}" install_configuration_type @ONLY) - install(CODE "${install_configuration_type}" - COMPONENT ${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME} - ) + ly_install(FILES "${CMAKE_BINARY_DIR}/cmake/Platform/${PAL_PLATFORM_NAME}/${LY_BUILD_PERMUTATION}/ConfigurationTypes_${conf}.cmake" + DESTINATION cmake/Platform/${PAL_PLATFORM_NAME}/${LY_BUILD_PERMUTATION} + COMPONENT ${LY_INSTALL_PERMUTATION_COMPONENT}_${UCONF} + CONFIGURATIONS ${conf} + ) + endforeach() # Transform the LY_EXTERNAL_SUBDIRS list into a json array set(indent " ") @@ -422,8 +444,7 @@ function(ly_setup_cmake_install) configure_file(${LY_ROOT_FOLDER}/cmake/install/engine.json.in ${CMAKE_CURRENT_BINARY_DIR}/cmake/engine.json @ONLY) - install( - FILES + ly_install(FILES "${LY_ROOT_FOLDER}/CMakeLists.txt" "${CMAKE_CURRENT_BINARY_DIR}/cmake/engine.json" DESTINATION . @@ -446,50 +467,79 @@ function(ly_setup_cmake_install) list(APPEND additional_platform_files "${plat_files}") endforeach() endforeach() - install(FILES ${additional_find_files} + + ly_install(FILES ${additional_find_files} DESTINATION cmake/3rdParty COMPONENT ${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME} ) - install(FILES ${additional_platform_files} + ly_install(FILES ${additional_platform_files} DESTINATION cmake/3rdParty/Platform/${PAL_PLATFORM_NAME} COMPONENT ${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME} ) - # Findo3de.cmake file: we generate a different Findo3de.camke file than the one we have in cmake. This one is going to expose all - # targets that are pre-built - unset(FIND_PACKAGES_PLACEHOLDER) - - # Add to the FIND_PACKAGES_PLACEHOLDER all directories in which ly_add_target were called in - get_property(all_subdirectories GLOBAL PROPERTY LY_ALL_TARGET_DIRECTORIES) - foreach(target_subdirectory IN LISTS all_subdirectories) - cmake_path(RELATIVE_PATH target_subdirectory BASE_DIRECTORY ${LY_ROOT_FOLDER} OUTPUT_VARIABLE relative_target_subdirectory) - string(APPEND FIND_PACKAGES_PLACEHOLDER " add_subdirectory(${relative_target_subdirectory})\n") - endforeach() - + # Findo3de.cmake file: we generate a different Findo3de.cmake file than the one we have in the source dir. configure_file(${LY_ROOT_FOLDER}/cmake/install/Findo3de.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/cmake/Findo3de.cmake @ONLY) - install(FILES "${CMAKE_CURRENT_BINARY_DIR}/cmake/Findo3de.cmake" + ly_install(FILES "${CMAKE_CURRENT_BINARY_DIR}/cmake/Findo3de.cmake" DESTINATION cmake COMPONENT ${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME} ) - # BuiltInPackage_.cmake: since associations could happen in any cmake file across the engine. We collect - # all the associations in ly_associate_package and then generate them into BuiltInPackages_.cmake. This - # will consolidate all associations in one file + unset(find_subdirectories) + # Add to find_subdirectories all directories in which ly_add_target were called in + get_property(all_subdirectories GLOBAL PROPERTY LY_ALL_TARGET_DIRECTORIES) + foreach(target_subdirectory IN LISTS all_subdirectories) + cmake_path(RELATIVE_PATH target_subdirectory BASE_DIRECTORY ${LY_ROOT_FOLDER} OUTPUT_VARIABLE relative_target_subdirectory) + string(APPEND find_subdirectories "add_subdirectory(${relative_target_subdirectory})\n") + endforeach() + set(permutation_find_subdirectories ${CMAKE_CURRENT_BINARY_DIR}/cmake/Platform/${PAL_PLATFORM_NAME}/${LY_BUILD_PERMUTATION}/o3de_subdirectories_${PAL_PLATFORM_NAME_LOWERCASE}.cmake) + file(GENERATE OUTPUT ${permutation_find_subdirectories} + CONTENT +"# Generated by O3DE install\n +${find_subdirectories} +" + ) + ly_install(FILES "${permutation_find_subdirectories}" + DESTINATION cmake/Platform/${PAL_PLATFORM_NAME}/${LY_BUILD_PERMUTATION} + COMPONENT ${LY_INSTALL_PERMUTATION_COMPONENT} + ) + + set(pal_builtin_file ${CMAKE_CURRENT_BINARY_DIR}/cmake/3rdParty/Platform/${PAL_PLATFORM_NAME}/BuiltInPackages_${PAL_PLATFORM_NAME_LOWERCASE}.cmake) + file(GENERATE OUTPUT ${pal_builtin_file} + CONTENT +"# Generated by O3DE install\n +if(LY_MONOLITHIC_GAME) + include(cmake/3rdParty/Platform/${PAL_PLATFORM_NAME}/Monolithic/BuiltInPackages_${PAL_PLATFORM_NAME_LOWERCASE}.cmake) +else() + include(cmake/3rdParty/Platform/${PAL_PLATFORM_NAME}/Default/BuiltInPackages_${PAL_PLATFORM_NAME_LOWERCASE}.cmake) +endif() +" + ) + ly_install(FILES "${pal_builtin_file}" + DESTINATION cmake/3rdParty/Platform/${PAL_PLATFORM_NAME} + COMPONENT ${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME} + ) + + # ${LY_BUILD_PERMUTATION}/BuiltInPackage_.cmake: since associations could happen in any cmake file across the engine. We collect + # all the associations in ly_associate_package and then generate them into BuiltInPackages_.cmake. This will consolidate all + # associations in one file + # Associations are sensitive to platform and build permutation, so we make different files for each. get_property(all_package_names GLOBAL PROPERTY LY_PACKAGE_NAMES) + list(REMOVE_DUPLICATES all_package_names) set(builtinpackages "# Generated by O3DE install\n\n") foreach(package_name IN LISTS all_package_names) get_property(package_hash GLOBAL PROPERTY LY_PACKAGE_HASH_${package_name}) get_property(targets GLOBAL PROPERTY LY_PACKAGE_TARGETS_${package_name}) + list(REMOVE_DUPLICATES targets) string(APPEND builtinpackages "ly_associate_package(PACKAGE_NAME ${package_name} TARGETS ${targets} PACKAGE_HASH ${package_hash})\n") endforeach() - set(pal_builtin_file ${CMAKE_CURRENT_BINARY_DIR}/cmake/3rdParty/Platform/${PAL_PLATFORM_NAME}/BuiltInPackages_${PAL_PLATFORM_NAME_LOWERCASE}.cmake) - file(GENERATE OUTPUT ${pal_builtin_file} + set(permutation_builtin_file ${CMAKE_CURRENT_BINARY_DIR}/cmake/3rdParty/Platform/${PAL_PLATFORM_NAME}/${LY_BUILD_PERMUTATION}/BuiltInPackages_${PAL_PLATFORM_NAME_LOWERCASE}.cmake) + file(GENERATE OUTPUT ${permutation_builtin_file} CONTENT ${builtinpackages} ) - install(FILES "${pal_builtin_file}" - DESTINATION cmake/3rdParty/Platform/${PAL_PLATFORM_NAME} - COMPONENT ${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME} + ly_install(FILES "${permutation_builtin_file}" + DESTINATION cmake/3rdParty/Platform/${PAL_PLATFORM_NAME}/${LY_BUILD_PERMUTATION} + COMPONENT ${LY_INSTALL_PERMUTATION_COMPONENT} ) endfunction() @@ -498,15 +548,23 @@ endfunction() function(ly_setup_runtime_dependencies) # Common functions used by the bellow code - if(COMMAND ly_install_code_function_override) - ly_install_code_function_override() + if(COMMAND ly_setup_runtime_dependencies_copy_function_override) + ly_setup_runtime_dependencies_copy_function_override() else() - install(CODE + # despite this copy function being the same, we need to install it per component that uses it + # (which is per-configuration per-permutation component) + foreach(conf IN LISTS CMAKE_CONFIGURATION_TYPES) + string(TOUPPER ${conf} UCONF) + ly_install(CODE "function(ly_copy source_file target_directory) - file(COPY \"\${source_file}\" DESTINATION \"\${target_directory}\" FILE_PERMISSIONS ${LY_COPY_PERMISSIONS}) + cmake_path(GET source_file FILENAME file_name) + if(NOT EXISTS \${target_directory}/\${file_name}) + file(COPY \"\${source_file}\" DESTINATION \"\${target_directory}\" FILE_PERMISSIONS ${LY_COPY_PERMISSIONS}) + endif() endfunction()" - COMPONENT ${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME} - ) + COMPONENT ${LY_INSTALL_PERMUTATION_COMPONENT}_${UCONF} + ) + endforeach() endif() unset(runtime_commands) @@ -545,9 +603,12 @@ endfunction()" list(REMOVE_DUPLICATES runtime_commands) list(JOIN runtime_commands " " runtime_commands_str) # the spaces are just to see the right identation in the cmake_install.cmake file - install(CODE "${runtime_commands_str}" - COMPONENT ${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME} - ) + foreach(conf IN LISTS CMAKE_CONFIGURATION_TYPES) + string(TOUPPER ${conf} UCONF) + ly_install(CODE "${runtime_commands_str}" + COMPONENT ${LY_INSTALL_PERMUTATION_COMPONENT}_${UCONF} + ) + endforeach() endfunction() @@ -632,17 +693,19 @@ function(ly_setup_assets) if (NOT gem_install_dest_dir) cmake_path(SET gem_install_dest_dir .) endif() + if(IS_DIRECTORY ${gem_absolute_path}) - install(DIRECTORY "${gem_absolute_path}" + ly_install(DIRECTORY "${gem_absolute_path}" DESTINATION ${gem_install_dest_dir} COMPONENT ${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME} ) elseif (EXISTS ${gem_absolute_path}) - install(FILES ${gem_absolute_path} + ly_install(FILES ${gem_absolute_path} DESTINATION ${gem_install_dest_dir} COMPONENT ${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME} ) endif() + endforeach() endforeach() @@ -702,4 +765,37 @@ function(ly_setup_subdirectory_enable_gems absolute_target_source_dir output_scr string(APPEND enable_gems_calls ${enable_gems_command}) endforeach() set(${output_script} ${enable_gems_calls} PARENT_SCOPE) +endfunction() + +#! ly_setup_o3de_install: orchestrates the installation of the different parts. This is the entry point from the root CMakeLists.txt +function(ly_setup_o3de_install) + + ly_setup_subdirectories() + ly_setup_cmake_install() + ly_setup_runtime_dependencies() + ly_setup_assets() + + # Misc + ly_install(FILES + ${LY_ROOT_FOLDER}/pytest.ini + ${LY_ROOT_FOLDER}/LICENSE.txt + ${LY_ROOT_FOLDER}/README.md + DESTINATION . + COMPONENT ${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME} + ) + + # Inject other build directories + foreach(external_dir ${LY_INSTALL_EXTERNAL_BUILD_DIRS}) + ly_install(CODE +"set(LY_CORE_COMPONENT_ALREADY_INCLUDED TRUE) +include(${external_dir}/cmake_install.cmake) +set(LY_CORE_COMPONENT_ALREADY_INCLUDED FALSE)" + ALL_COMPONENTS + ) + endforeach() + + if(COMMAND ly_post_install_steps) + ly_post_install_steps() + endif() + endfunction() \ No newline at end of file diff --git a/cmake/Platform/Common/MSVC/Configurations_msvc.cmake b/cmake/Platform/Common/MSVC/Configurations_msvc.cmake index a4d8533626..66a5b7b01f 100644 --- a/cmake/Platform/Common/MSVC/Configurations_msvc.cmake +++ b/cmake/Platform/Common/MSVC/Configurations_msvc.cmake @@ -139,11 +139,20 @@ endif() # Configure system includes ly_set(LY_CXX_SYSTEM_INCLUDE_CONFIGURATION_FLAG - /experimental:external # Turns on "external" headers feature for MSVC compilers + /experimental:external # Turns on "external" headers feature for MSVC compilers, required for MSVC < 16.10 /external:W0 # Set warning level in external headers to 0. This is used to suppress warnings 3rdParty libraries which uses the "system_includes" option in their json configuration ) + +# CMake 3.22rc added a definition for CMAKE_INCLUDE_SYSTEM_FLAG_CXX. However, its defined as "-external:I ", that space causes +# issues when trying to use in TargetIncludeSystemDirectories_unsupported.cmake. +# CMake 3.22rc has also not added support for external directories in MSVC through target_include_directories(... SYSTEM +# So we will just fix the flag that was added by 3.22rc so it works with our TargetIncludeSystemDirectories_unsupported.cmake +# Once target_include_directories(... SYSTEM is supported, we can branch and use TargetIncludeSystemDirectories_supported.cmake +# Reported this here: https://gitlab.kitware.com/cmake/cmake/-/issues/17904#note_1078281 if(NOT CMAKE_INCLUDE_SYSTEM_FLAG_CXX) - ly_set(CMAKE_INCLUDE_SYSTEM_FLAG_CXX /external:I) + ly_set(CMAKE_INCLUDE_SYSTEM_FLAG_CXX "/external:I") +else() + string(STRIP ${CMAKE_INCLUDE_SYSTEM_FLAG_CXX} CMAKE_INCLUDE_SYSTEM_FLAG_CXX) endif() include(cmake/Platform/Common/TargetIncludeSystemDirectories_unsupported.cmake) diff --git a/cmake/Platform/Linux/Install_linux.cmake b/cmake/Platform/Linux/Install_linux.cmake index dea26e5872..02baa6e61e 100644 --- a/cmake/Platform/Linux/Install_linux.cmake +++ b/cmake/Platform/Linux/Install_linux.cmake @@ -6,7 +6,7 @@ # # -#! ly_install_code_function_override: Linux-specific copy function to handle RPATH fixes +#! ly_setup_runtime_dependencies_copy_function_override: Linux-specific copy function to handle RPATH fixes set(ly_copy_template [[ function(ly_copy source_file target_directory) file(COPY "${source_file}" DESTINATION "${target_directory}" FILE_PERMISSIONS @LY_COPY_PERMISSIONS@ FOLLOW_SYMLINK_CHAIN) @@ -20,11 +20,14 @@ function(ly_copy source_file target_directory) endif() endfunction()]]) -function(ly_install_code_function_override) +function(ly_setup_runtime_dependencies_copy_function_override) string(CONFIGURE "${ly_copy_template}" ly_copy_function_linux @ONLY) - install(CODE "${ly_copy_function_linux}" - COMPONENT ${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME} - ) + foreach(conf IN LISTS CMAKE_CONFIGURATION_TYPES) + string(TOUPPER ${conf} UCONF) + ly_install(CODE "${ly_copy_function_linux}" + COMPONENT ${LY_INSTALL_PERMUTATION_COMPONENT}_${UCONF} + ) + endforeach() endfunction() include(cmake/Platform/Common/Install_common.cmake) diff --git a/cmake/Platform/Mac/Install_mac.cmake b/cmake/Platform/Mac/Install_mac.cmake index bdc2300131..c75d860c3e 100644 --- a/cmake/Platform/Mac/Install_mac.cmake +++ b/cmake/Platform/Mac/Install_mac.cmake @@ -39,10 +39,10 @@ file(GENERATE # This needs to be done here because it needs to update the install prefix # before cmake does anything else in the install process. configure_file(${LY_ROOT_FOLDER}/cmake/Platform/Mac/PreInstallSteps_mac.cmake.in ${CMAKE_BINARY_DIR}/runtime_install/PreInstallSteps_mac.cmake @ONLY) -install(SCRIPT ${CMAKE_BINARY_DIR}/runtime_install/PreInstallSteps_mac.cmake COMPONENT ${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME}) +ly_install(SCRIPT ${CMAKE_BINARY_DIR}/runtime_install/PreInstallSteps_mac.cmake COMPONENT ${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME}) -#! ly_install_target_override: Mac specific target installation -function(ly_install_target_override) +#! ly_setup_target_install_targets_override: Mac specific target installation +function(ly_setup_target_install_targets_override) set(options) set(oneValueArgs TARGET ARCHIVE_DIR LIBRARY_DIR RUNTIME_DIR LIBRARY_SUBDIR RUNTIME_SUBDIR) @@ -58,24 +58,31 @@ function(ly_install_target_override) set_property(TARGET ${ly_platform_install_target_TARGET} PROPERTY RESOURCE "") endif() - install( - TARGETS ${ly_platform_install_target_TARGET} - ARCHIVE - DESTINATION ${ly_platform_install_target_ARCHIVE_DIR} - COMPONENT ${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME} - LIBRARY - DESTINATION ${ly_platform_install_target_LIBRARY_DIR}/${ly_platform_install_target_LIBRARY_SUBDIR} - COMPONENT ${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME} - RUNTIME - DESTINATION ${ly_platform_install_target_RUNTIME_DIR}/${ly_platform_install_target_RUNTIME_SUBDIR} - COMPONENT ${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME} - BUNDLE - DESTINATION ${ly_platform_install_target_RUNTIME_DIR}/${ly_platform_install_target_RUNTIME_SUBDIR} - COMPONENT ${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME} - RESOURCE - DESTINATION ${ly_platform_install_target_RUNTIME_DIR}/${ly_platform_install_target_RUNTIME_SUBDIR} - COMPONENT ${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME} - ) + foreach(conf IN LISTS CMAKE_CONFIGURATION_TYPES) + string(TOUPPER ${conf} UCONF) + ly_install(TARGETS ${TARGET_NAME} + ARCHIVE + DESTINATION ${ly_platform_install_target_ARCHIVE_DIR} + COMPONENT ${LY_INSTALL_PERMUTATION_COMPONENT}_${UCONF} + CONFIGURATIONS ${conf} + LIBRARY + DESTINATION ${ly_platform_install_target_LIBRARY_DIR}/${ly_platform_install_target_LIBRARY_SUBDIR} + COMPONENT ${LY_INSTALL_PERMUTATION_COMPONENT}_${UCONF} + CONFIGURATIONS ${conf} + RUNTIME + DESTINATION ${ly_platform_install_target_RUNTIME_DIR}/${ly_platform_install_target_RUNTIME_SUBDIR} + COMPONENT ${LY_INSTALL_PERMUTATION_COMPONENT}_${UCONF} + CONFIGURATIONS ${conf} + BUNDLE + DESTINATION ${ly_platform_install_target_RUNTIME_DIR}/${ly_platform_install_target_RUNTIME_SUBDIR} + COMPONENT ${LY_INSTALL_PERMUTATION_COMPONENT}_${UCONF} + CONFIGURATIONS ${conf} + RESOURCE + DESTINATION ${ly_platform_install_target_RUNTIME_DIR}/${ly_platform_install_target_RUNTIME_SUBDIR} + COMPONENT ${LY_INSTALL_PERMUTATION_COMPONENT}_${UCONF} + CONFIGURATIONS ${conf} + ) + endforeach() set(install_relative_binaries_path "${ly_platform_install_target_RUNTIME_DIR}/${ly_platform_install_target_RUNTIME_SUBDIR}") @@ -102,11 +109,16 @@ function(ly_install_target_override) endif() endfunction() -#! ly_install_code_function_override: Mac specific copy function to handle frameworks -function(ly_install_code_function_override) +#! ly_setup_runtime_dependencies_copy_function_override: Mac specific copy function to handle frameworks +function(ly_setup_runtime_dependencies_copy_function_override) configure_file(${LY_ROOT_FOLDER}/cmake/Platform/Mac/InstallUtils_mac.cmake.in ${CMAKE_BINARY_DIR}/runtime_install/InstallUtils_mac.cmake @ONLY) - ly_install_run_script(${CMAKE_BINARY_DIR}/runtime_install/InstallUtils_mac.cmake) + foreach(conf IN LISTS CMAKE_CONFIGURATION_TYPES) + string(TOUPPER ${conf} UCONF) + ly_install(SCRIPT "${CMAKE_BINARY_DIR}/runtime_install/InstallUtils_mac.cmake" + COMPONENT ${LY_INSTALL_PERMUTATION_COMPONENT}_${UCONF} + ) + endforeach() endfunction() diff --git a/cmake/Platform/Windows/Packaging/Shortcuts.wxs b/cmake/Platform/Windows/Packaging/Shortcuts.wxs index fbfa174d06..1299626bf4 100644 --- a/cmake/Platform/Windows/Packaging/Shortcuts.wxs +++ b/cmake/Platform/Windows/Packaging/Shortcuts.wxs @@ -29,13 +29,13 @@ diff --git a/cmake/Platform/Windows/PackagingPostBuild.cmake b/cmake/Platform/Windows/PackagingPostBuild.cmake index 377a9fb221..ac457bea87 100644 --- a/cmake/Platform/Windows/PackagingPostBuild.cmake +++ b/cmake/Platform/Windows/PackagingPostBuild.cmake @@ -32,9 +32,6 @@ set(_addtional_defines -dCPACK_RESOURCE_PATH=${CPACK_SOURCE_DIR}/Platform/Windows/Packaging ) -file(REAL_PATH "${CPACK_SOURCE_DIR}/.." _root_path) -file(TO_NATIVE_PATH "${_root_path}/scripts/signer/Platform/Windows/signer.ps1" _sign_script) - if(CPACK_LICENSE_URL) list(APPEND _addtional_defines -dCPACK_LICENSE_URL=${CPACK_LICENSE_URL}) endif() @@ -58,28 +55,41 @@ set(_light_command -o "${_bootstrap_output_file}" ) -set(_signing_command - psexec.exe - -accepteula - -nobanner - -s - powershell.exe - -NoLogo - -ExecutionPolicy Bypass - -File ${_sign_script} -) +if(CPACK_UPLOAD_URL) # Skip signing if we are not uploading the package + file(REAL_PATH "${CPACK_SOURCE_DIR}/.." _root_path) + file(TO_NATIVE_PATH "${_root_path}/scripts/signer/Platform/Windows/signer.ps1" _sign_script) -message(STATUS "Signing package files in ${_cpack_wix_out_dir}") -execute_process( - COMMAND ${_signing_command} -packagePath ${_cpack_wix_out_dir} - RESULT_VARIABLE _signing_result - ERROR_VARIABLE _signing_errors - OUTPUT_VARIABLE _signing_output - ECHO_OUTPUT_VARIABLE -) + unset(_signing_command) + find_program(_psiexec_path psexec.exe) + if(_psiexec_path) + list(APPEND _signing_command + ${_psiexec_path} + -accepteula + -nobanner + -s + ) + endif() -if(NOT ${_signing_result} EQUAL 0) - message(FATAL_ERROR "An error occurred during signing package files. ${_signing_errors}") + find_program(_powershell_path powershell.exe REQUIRED) + list(APPEND _signing_command + ${_powershell_path} + -NoLogo + -ExecutionPolicy Bypass + -File ${_sign_script} + ) + + message(STATUS "Signing package files in ${_cpack_wix_out_dir}") + execute_process( + COMMAND ${_signing_command} -packagePath ${_cpack_wix_out_dir} + RESULT_VARIABLE _signing_result + ERROR_VARIABLE _signing_errors + OUTPUT_VARIABLE _signing_output + ECHO_OUTPUT_VARIABLE + ) + + if(NOT ${_signing_result} EQUAL 0) + message(FATAL_ERROR "An error occurred during signing package files. ${_signing_errors}") + endif() endif() message(STATUS "Creating Bootstrap Installer...") @@ -107,17 +117,19 @@ file(COPY ${_bootstrap_output_file} message(STATUS "Bootstrap installer generated to ${CPACK_PACKAGE_DIRECTORY}/${_bootstrap_filename}") -message(STATUS "Signing bootstrap installer in ${CPACK_PACKAGE_DIRECTORY}") -execute_process( - COMMAND ${_signing_command} -bootstrapPath ${CPACK_PACKAGE_DIRECTORY}/${_bootstrap_filename} - RESULT_VARIABLE _signing_result - ERROR_VARIABLE _signing_errors - OUTPUT_VARIABLE _signing_output - ECHO_OUTPUT_VARIABLE -) +if(CPACK_UPLOAD_URL) # Skip signing if we are not uploading the package + message(STATUS "Signing bootstrap installer in ${CPACK_PACKAGE_DIRECTORY}") + execute_process( + COMMAND ${_signing_command} -bootstrapPath ${CPACK_PACKAGE_DIRECTORY}/${_bootstrap_filename} + RESULT_VARIABLE _signing_result + ERROR_VARIABLE _signing_errors + OUTPUT_VARIABLE _signing_output + ECHO_OUTPUT_VARIABLE + ) -if(NOT ${_signing_result} EQUAL 0) - message(FATAL_ERROR "An error occurred during signing bootstrap installer. ${_signing_errors}") + if(NOT ${_signing_result} EQUAL 0) + message(FATAL_ERROR "An error occurred during signing bootstrap installer. ${_signing_errors}") + endif() endif() # use the internal default path if somehow not specified from cpack_configure_downloads diff --git a/cmake/Platform/Windows/PackagingPreBuild.cmake b/cmake/Platform/Windows/PackagingPreBuild.cmake index d3924c7a02..7f2eedf352 100644 --- a/cmake/Platform/Windows/PackagingPreBuild.cmake +++ b/cmake/Platform/Windows/PackagingPreBuild.cmake @@ -6,21 +6,41 @@ # # +if(NOT CPACK_UPLOAD_URL) # Skip signing if we are not uploading the package + return() +endif() + file(REAL_PATH "${CPACK_SOURCE_DIR}/.." _root_path) set(_cpack_wix_out_dir ${CPACK_TOPLEVEL_DIRECTORY}) file(TO_NATIVE_PATH "${_root_path}/scripts/signer/Platform/Windows/signer.ps1" _sign_script) -set(_signing_command - psexec.exe - -accepteula - -nobanner - -s - powershell.exe +unset(_signing_command) +find_program(_psiexec_path psexec.exe) +if(_psiexec_path) + list(APPEND _signing_command + ${_psiexec_path} + -accepteula + -nobanner + -s + ) +endif() + +find_program(_powershell_path powershell.exe REQUIRED) +list(APPEND _signing_command + ${_powershell_path} -NoLogo - -ExecutionPolicy Bypass + -ExecutionPolicy Bypass -File ${_sign_script} ) +# This requires to have a valid local certificate. In continuous integration, these certificates are stored +# in the machine directly. +# You can generate a test certificate to be able to run this in a PowerShell elevated promp with: +# New-SelfSignedCertificate -DnsName foo.o3de.com -Type CodeSigning -CertStoreLocation Cert:\CurrentUser\My +# Export-Certificate -Cert (Get-ChildItem Cert:\CurrentUser\My\) -Filepath "c:\selfsigned.crt" +# Import-Certificate -FilePath "c:\selfsigned.crt" -Cert Cert:\CurrentUser\TrustedPublisher +# Import-Certificate -FilePath "c:\selfsigned.crt" -Cert Cert:\CurrentUser\Root + message(STATUS "Signing executable files in ${_cpack_wix_out_dir}") execute_process( COMMAND ${_signing_command} -exePath ${_cpack_wix_out_dir} @@ -32,6 +52,6 @@ execute_process( if(NOT ${_signing_result} EQUAL 0) message(FATAL_ERROR "An error occurred during signing executable files. ${_signing_errors}") +else() + message(STATUS "Signing exes complete!") endif() - -message(STATUS "Signing exes complete!") diff --git a/cmake/Platform/Windows/Packaging_windows.cmake b/cmake/Platform/Windows/Packaging_windows.cmake index 4a03df2fd2..7b8f5a6c19 100644 --- a/cmake/Platform/Windows/Packaging_windows.cmake +++ b/cmake/Platform/Windows/Packaging_windows.cmake @@ -28,11 +28,8 @@ set(_cmake_package_name "cmake-${CPACK_DESIRED_CMAKE_VERSION}-windows-x86_64") set(CPACK_CMAKE_PACKAGE_FILE "${_cmake_package_name}.zip") set(CPACK_CMAKE_PACKAGE_HASH "15a49e2ab81c1822d75b1b1a92f7863f58e31f6d6aac1c4103eef2b071be3112") -# workaround for shortening the path cpack installs to by stripping the platform directory and forcing monolithic -# mode to strip out component folders. this unfortunately is the closest we can get to changing the install location -# as CPACK_PACKAGING_INSTALL_PREFIX/CPACK_SET_DESTDIR isn't supported for the WiX generator +# workaround for shortening the path cpack installs to by stripping the platform directory set(CPACK_TOPLEVEL_TAG "") -set(CPACK_MONOLITHIC_INSTALL ON) # CPack will generate the WiX product/upgrade GUIDs further down the chain if they weren't supplied # however, they are unique for each run. instead, let's do the auto generation here and add it to @@ -108,6 +105,8 @@ set(_raw_text_license [[ #(loc.InstallEulaAcceptance) ]]) +# The offline installer generation will be a single monolithic MSI. The WIX burn tool for the bootstrapper EXE has a size limitation. +# So we will exclude the generation of the boostrapper EXE in the offline case. if(LY_INSTALLER_DOWNLOAD_URL) set(WIX_THEME_WARNING_IMAGE ${CPACK_SOURCE_DIR}/Platform/Windows/Packaging/warning.png) diff --git a/cmake/Projects.cmake b/cmake/Projects.cmake index c09fe0fc6f..34ef3efd9b 100644 --- a/cmake/Projects.cmake +++ b/cmake/Projects.cmake @@ -150,22 +150,43 @@ foreach(project ${LY_PROJECTS}) # Get project name o3de_read_json_key(project_name ${full_directory_path}/project.json "project_name") + # The cmake tar command has a bit of a flaw + # Any paths within the archive files it creates are relative to the current working directory. + # That means with the setup of: + # cwd = "/Cache/pc" + # project product assets = "/Cache/pc/*" + # cmake dependency registry files = "/build/bin/Release/Registry/*" + # Running the tar command would result in the assets being placed in the to layout + # correctly, but the registry files + # engine.pak/ + # ../...build/bin/Release/Registry/cmake_dependencies.*.setreg -> Not correct + # project.json -> Correct + # Generate pak for project in release installs cmake_path(RELATIVE_PATH CMAKE_RUNTIME_OUTPUT_DIRECTORY BASE_DIRECTORY ${CMAKE_BINARY_DIR} OUTPUT_VARIABLE install_base_runtime_output_directory) set(install_engine_pak_template [=[ if("${CMAKE_INSTALL_CONFIG_NAME}" MATCHES "^([Rr][Ee][Ll][Ee][Aa][Ss][Ee])$") set(install_output_folder "${CMAKE_INSTALL_PREFIX}/@install_base_runtime_output_directory@/@PAL_PLATFORM_NAME@/${CMAKE_INSTALL_CONFIG_NAME}/@LY_BUILD_PERMUTATION@") set(install_pak_output_folder "${install_output_folder}/Cache/@LY_ASSET_DEPLOY_ASSET_TYPE@") + set(runtime_output_directory_RELEASE @CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE@) if(NOT DEFINED LY_ASSET_DEPLOY_ASSET_TYPE) set(LY_ASSET_DEPLOY_ASSET_TYPE @LY_ASSET_DEPLOY_ASSET_TYPE@) endif() message(STATUS "Generating ${install_pak_output_folder}/engine.pak from @full_directory_path@/Cache/${LY_ASSET_DEPLOY_ASSET_TYPE}") file(MAKE_DIRECTORY "${install_pak_output_folder}") cmake_path(SET cache_product_path "@full_directory_path@/Cache/${LY_ASSET_DEPLOY_ASSET_TYPE}") + # Copy the generated cmake_dependencies.*.setreg files for loading gems in non-monolithic to the cache + file(GLOB gem_source_paths_setreg "${runtime_output_directory_RELEASE}/Registry/*.setreg") + # The MergeSettingsToRegistry_TargetBuildDependencyRegistry function looks for lowercase "registry" + # So make sure the to copy it to a lowercase path, so that it works on non-case sensitive filesystems + file(MAKE_DIRECTORY "${cache_product_path}/registry") + file(COPY ${gem_source_paths_setreg} DESTINATION "${cache_product_path}/registry") + file(GLOB product_assets "${cache_product_path}/*") - if(product_assets) + list(APPEND pak_artifacts ${product_assets}) + if(pak_artifacts) execute_process( - COMMAND ${CMAKE_COMMAND} -E tar "cf" "${install_pak_output_folder}/engine.pak" --format=zip -- ${product_assets} + COMMAND ${CMAKE_COMMAND} -E tar "cf" "${install_pak_output_folder}/engine.pak" --format=zip -- ${pak_artifacts} WORKING_DIRECTORY "${cache_product_path}" RESULT_VARIABLE archive_creation_result ) diff --git a/cmake/install/ConfigurationType_config.cmake.in b/cmake/install/ConfigurationType_config.cmake.in index 074e034899..0a6940d7ff 100644 --- a/cmake/install/ConfigurationType_config.cmake.in +++ b/cmake/install/ConfigurationType_config.cmake.in @@ -8,4 +8,4 @@ include_guard(GLOBAL) -list(APPEND CMAKE_CONFIGURATION_TYPES @CMAKE_INSTALL_CONFIG_NAME@) +list(APPEND CMAKE_CONFIGURATION_TYPES @conf@) diff --git a/cmake/install/Findo3de.cmake.in b/cmake/install/Findo3de.cmake.in index e267b6b5c6..c3db7f1ec6 100644 --- a/cmake/install/Findo3de.cmake.in +++ b/cmake/install/Findo3de.cmake.in @@ -12,7 +12,15 @@ include(FindPackageHandleStandardArgs) # This will be called from within the installed engine's CMakeLists.txt macro(ly_find_o3de_packages) -@FIND_PACKAGES_PLACEHOLDER@ + if(LY_MONOLITHIC_GAME) + set(monolithic_file "${LY_ROOT_FOLDER}/cmake/Platform/${PAL_PLATFORM_NAME}/Monolithic/o3de_subdirectories_${PAL_PLATFORM_NAME_LOWERCASE}.cmake") + if(NOT EXISTS ${monolithic_file}) + message(FATAL_ERROR "O3DE SDK was not generated to support monolithic builds") + endif() + include("${monolithic_file}") + else() + include("${LY_ROOT_FOLDER}/cmake/Platform/${PAL_PLATFORM_NAME}/Default/o3de_subdirectories_${PAL_PLATFORM_NAME_LOWERCASE}.cmake") + endif() find_package(LauncherGenerator) endmacro() diff --git a/engine.json b/engine.json index 14deffecb8..05ccd0abfd 100644 --- a/engine.json +++ b/engine.json @@ -92,8 +92,9 @@ "templates": [ "Templates/AssetGem", "Templates/DefaultGem", - "Templates/CustomTool", "Templates/DefaultProject", - "Templates/MinimalProject" + "Templates/CppToolGem", + "Templates/MinimalProject", + "Templates/PythonToolGem" ] } diff --git a/scripts/build/Jenkins/Jenkinsfile b/scripts/build/Jenkins/Jenkinsfile index 1388366661..8eddb61c33 100644 --- a/scripts/build/Jenkins/Jenkinsfile +++ b/scripts/build/Jenkins/Jenkinsfile @@ -16,7 +16,7 @@ EMPTY_JSON = readJSON text: '{}' ENGINE_REPOSITORY_NAME = 'o3de' // Branches with build snapshots -BUILD_SNAPSHOTS = ['development', 'stabilization/2106'] +BUILD_SNAPSHOTS = ['development', 'stabilization/2110'] // Build snapshots with empty snapshot (for use with 'SNAPSHOT' pipeline paramater) BUILD_SNAPSHOTS_WITH_EMPTY = BUILD_SNAPSHOTS + '' @@ -102,6 +102,10 @@ def IsJobEnabled(branchName, buildTypeMap, pipelineName, platformName) { } } +def IsAPLogUpload(branchName, jobName) { + return !IsPullRequest(branchName) && jobName.toLowerCase().contains('asset') && env.AP_LOGS_S3_BUCKET +} + def GetRunningPipelineName(JENKINS_JOB_NAME) { // If the job name has an underscore def job_parts = JENKINS_JOB_NAME.tokenize('/')[0].tokenize('_') @@ -267,7 +271,7 @@ def CheckoutRepo(boolean disableSubmodules = false) { palRm('commitdate') } -def HandleDriveMount(String snapshot, String repositoryName, String projectName, String pipeline, String branchName, String platform, String buildType, String workspace, boolean recreateVolume = false) { +def HandleDriveMount(String snapshot, String repositoryName, String projectName, String pipeline, String branchName, String platform, String buildType, String workspace, boolean recreateVolume = false) { unstash name: 'incremental_build_script' def pythonCmd = '' @@ -431,6 +435,27 @@ def ExportTestScreenshots(Map options, String branchName, String platformName, S } } +def UploadAPLogs(Map options, String branchName, String platformName, String jobName, String workspace, Map params) { + dir("${workspace}/${ENGINE_REPOSITORY_NAME}") { + projects = params.CMAKE_LY_PROJECTS.split(",") + projects.each{ project -> + def apLogsPath = "${project}/user/log" + def s3UploadScriptPath = "scripts/build/tools/upload_to_s3.py" + if(env.IS_UNIX) { + pythonPath = "${options.PYTHON_DIR}/python.sh" + } + else { + pythonPath = "${options.PYTHON_DIR}/python.cmd" + } + def command = "${pythonPath} -u ${s3UploadScriptPath} --base_dir ${apLogsPath} " + + "--file_regex \".*\" --bucket ${env.AP_LOGS_S3_BUCKET} " + + "--search_subdirectories True --key_prefix ${env.JENKINS_JOB_NAME}/${branchName}/${env.BUILD_NUMBER}/${platformName}/${jobName} " + + '--extra_args {\\"ACL\\":\\"bucket-owner-full-control\\"}' + palSh(command, "Uploading AP logs for job ${jobName} for branch ${branchName}", false) + } + } + } + def PostBuildCommonSteps(String workspace, boolean mount = true) { echo 'Starting post-build common steps...' @@ -494,6 +519,14 @@ def CreateExportTestScreenshotsStage(Map pipelineConfig, String branchName, Stri } } +def CreateUploadAPLogsStage(Map pipelineConfig, String branchName, String platformName, String jobName, String workspace, Map params) { + return { + stage("${jobName}_upload_ap_logs") { + UploadAPLogs(pipelineConfig, branchName, platformName, jobName, workspace, params) + } + } +} + def CreateTeardownStage(Map environmentVars) { return { stage('Teardown') { @@ -518,9 +551,11 @@ def CreateSingleNode(Map pipelineConfig, def platform, def build_job, Map envVar CreateSetupStage(pipelineConfig, snapshot, repositoryName, projectName, pipelineName, branchName, platform.key, build_job.key, envVars, onlyMountEBSVolume).call() if(build_job.value.steps) { //this is a pipe with many steps so create all the build stages + pipelineEnvVars = GetBuildEnvVars(platform.value.PIPELINE_ENV ?: EMPTY_JSON, build_job.value.PIPELINE_ENV ?: EMPTY_JSON, pipelineName) build_job.value.steps.each { build_step -> build_job_name = build_step - envVars = GetBuildEnvVars(platform.value.PIPELINE_ENV ?: EMPTY_JSON, platform.value.build_types[build_step].PIPELINE_ENV ?: EMPTY_JSON, pipelineName) + // This addition of maps makes it that the right operand will override entries if they overlap with the left operand + envVars = pipelineEnvVars + GetBuildEnvVars(platform.value.PIPELINE_ENV ?: EMPTY_JSON, platform.value.build_types[build_step].PIPELINE_ENV ?: EMPTY_JSON, pipelineName) try { CreateBuildStage(pipelineConfig, platform.key, build_step, envVars).call() } @@ -543,6 +578,9 @@ def CreateSingleNode(Map pipelineConfig, def platform, def build_job, Map envVar error "Node disconnected during build: ${e}" // Error raised to retry stage on a new node } } + if (IsAPLogUpload(branchName, build_job_name)) { + CreateUploadAPLogsStage(pipelineConfig, branchName, platform.key, build_job_name, envVars['WORKSPACE'], platform.value.build_types[build_job_name].PARAMETERS).call() + } // All other errors will be raised outside the retry block currentResult = envVars['ON_FAILURE_MARK'] ?: 'FAILURE' currentException = e.toString() @@ -770,6 +808,7 @@ try { platform.value.build_types.each { build_job -> if (IsJobEnabled(branchName, build_job, pipelineName, platform.key)) { // User can filter jobs, jobs are tagged by pipeline def envVars = GetBuildEnvVars(platform.value.PIPELINE_ENV ?: EMPTY_JSON, build_job.value.PIPELINE_ENV ?: EMPTY_JSON, pipelineName) + envVars['JENKINS_JOB_NAME'] = env.JOB_NAME // Save original Jenkins job name to JENKINS_JOB_NAME envVars['JOB_NAME'] = "${branchName}_${platform.key}_${build_job.key}" // backwards compatibility, some scripts rely on this someBuildHappened = true diff --git a/scripts/build/Platform/Android/build_config.json b/scripts/build/Platform/Android/build_config.json index c1fbb1dd87..5e2da44a3c 100644 --- a/scripts/build/Platform/Android/build_config.json +++ b/scripts/build/Platform/Android/build_config.json @@ -35,7 +35,7 @@ "PARAMETERS": { "CONFIGURATION":"debug", "OUTPUT_DIRECTORY":"build\\android", - "CMAKE_OPTIONS":"-G \"Ninja Multi-Config\" -DCMAKE_TOOLCHAIN_FILE=cmake\\Platform\\Android\\Toolchain_android.cmake -DANDROID_ABI=arm64-v8a -DANDROID_ARM_MODE=arm -DANDROID_ARM_NEON=FALSE -DANDROID_NATIVE_API_LEVEL=21 -DLY_NDK_DIR=\"!LY_3RDPARTY_PATH!\\android-ndk\\r21d\" -DLY_UNITY_BUILD=TRUE", + "CMAKE_OPTIONS":"-G \"Ninja Multi-Config\" -DCMAKE_TOOLCHAIN_FILE=cmake\\Platform\\Android\\Toolchain_android.cmake -DANDROID_ABI=arm64-v8a -DANDROID_ARM_MODE=arm -DANDROID_ARM_NEON=FALSE -DANDROID_NATIVE_API_LEVEL=21 -DLY_NDK_DIR=\"!LY_3RDPARTY_PATH!\\android-ndk\\r21d\"", "CMAKE_LY_PROJECTS":"AutomatedTesting", "CMAKE_TARGET":"all", "CMAKE_BUILD_ARGS":"-j!NUMBER_OF_PROCESSORS!" @@ -60,7 +60,7 @@ "PARAMETERS": { "CONFIGURATION":"profile", "OUTPUT_DIRECTORY":"build\\android", - "CMAKE_OPTIONS":"-G \"Ninja Multi-Config\" -DCMAKE_TOOLCHAIN_FILE=cmake\\Platform\\Android\\Toolchain_android.cmake -DANDROID_ABI=arm64-v8a -DANDROID_ARM_MODE=arm -DANDROID_ARM_NEON=FALSE -DANDROID_NATIVE_API_LEVEL=21 -DLY_NDK_DIR=\"!LY_3RDPARTY_PATH!\\android-ndk\\r21d\" -DLY_UNITY_BUILD=TRUE", + "CMAKE_OPTIONS":"-G \"Ninja Multi-Config\" -DCMAKE_TOOLCHAIN_FILE=cmake\\Platform\\Android\\Toolchain_android.cmake -DANDROID_ABI=arm64-v8a -DANDROID_ARM_MODE=arm -DANDROID_ARM_NEON=FALSE -DANDROID_NATIVE_API_LEVEL=21 -DLY_NDK_DIR=\"!LY_3RDPARTY_PATH!\\android-ndk\\r21d\"", "CMAKE_LY_PROJECTS":"AutomatedTesting", "CMAKE_TARGET":"all", "CMAKE_BUILD_ARGS":"-j!NUMBER_OF_PROCESSORS!" @@ -93,7 +93,7 @@ "PARAMETERS": { "CONFIGURATION":"profile", "OUTPUT_DIRECTORY":"build\\windows_vs2019", - "CMAKE_OPTIONS":"-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_UNITY_BUILD=TRUE", + "CMAKE_OPTIONS":"-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0", "CMAKE_LY_PROJECTS":"AutomatedTesting", "CMAKE_TARGET":"AssetProcessorBatch", "CMAKE_NATIVE_BUILD_ARGS": "/m /nologo", @@ -112,7 +112,7 @@ "PARAMETERS": { "CONFIGURATION":"release", "OUTPUT_DIRECTORY":"build\\android", - "CMAKE_OPTIONS":"-G \"Ninja Multi-Config\" -DCMAKE_TOOLCHAIN_FILE=cmake\\Platform\\Android\\Toolchain_android.cmake -DANDROID_ABI=arm64-v8a -DANDROID_ARM_MODE=arm -DANDROID_ARM_NEON=FALSE -DANDROID_NATIVE_API_LEVEL=21 -DLY_NDK_DIR=\"!LY_3RDPARTY_PATH!\\android-ndk\\r21d\" -DLY_UNITY_BUILD=TRUE", + "CMAKE_OPTIONS":"-G \"Ninja Multi-Config\" -DCMAKE_TOOLCHAIN_FILE=cmake\\Platform\\Android\\Toolchain_android.cmake -DANDROID_ABI=arm64-v8a -DANDROID_ARM_MODE=arm -DANDROID_ARM_NEON=FALSE -DANDROID_NATIVE_API_LEVEL=21 -DLY_NDK_DIR=\"!LY_3RDPARTY_PATH!\\android-ndk\\r21d\"", "CMAKE_LY_PROJECTS":"AutomatedTesting", "CMAKE_TARGET":"all", "CMAKE_BUILD_ARGS":"-j!NUMBER_OF_PROCESSORS!" @@ -128,7 +128,7 @@ "PARAMETERS": { "CONFIGURATION":"release", "OUTPUT_DIRECTORY":"build\\mono_android", - "CMAKE_OPTIONS":"-G \"Ninja Multi-Config\" -DCMAKE_TOOLCHAIN_FILE=cmake\\Platform\\Android\\Toolchain_android.cmake -DANDROID_ABI=arm64-v8a -DANDROID_ARM_MODE=arm -DANDROID_ARM_NEON=FALSE -DANDROID_NATIVE_API_LEVEL=21 -DLY_NDK_DIR=\"!LY_3RDPARTY_PATH!\\android-ndk\\r21d\" -DLY_MONOLITHIC_GAME=TRUE -DLY_UNITY_BUILD=TRUE", + "CMAKE_OPTIONS":"-G \"Ninja Multi-Config\" -DCMAKE_TOOLCHAIN_FILE=cmake\\Platform\\Android\\Toolchain_android.cmake -DANDROID_ABI=arm64-v8a -DANDROID_ARM_MODE=arm -DANDROID_ARM_NEON=FALSE -DANDROID_NATIVE_API_LEVEL=21 -DLY_NDK_DIR=\"!LY_3RDPARTY_PATH!\\android-ndk\\r21d\" -DLY_MONOLITHIC_GAME=TRUE", "CMAKE_LY_PROJECTS":"AutomatedTesting", "CMAKE_TARGET":"all", "CMAKE_BUILD_ARGS":"-j!NUMBER_OF_PROCESSORS!" diff --git a/scripts/build/Platform/Linux/build_config.json b/scripts/build/Platform/Linux/build_config.json index b76a950beb..8551c3dcb3 100644 --- a/scripts/build/Platform/Linux/build_config.json +++ b/scripts/build/Platform/Linux/build_config.json @@ -37,7 +37,7 @@ "PARAMETERS": { "CONFIGURATION": "debug", "OUTPUT_DIRECTORY": "build/linux", - "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", + "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-6.0 -DCMAKE_CXX_COMPILER=clang++-6.0 -DLY_PARALLEL_LINK_JOBS=4", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "all" } @@ -53,7 +53,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build/linux", - "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", + "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-6.0 -DCMAKE_CXX_COMPILER=clang++-6.0 -DLY_PARALLEL_LINK_JOBS=4", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "all" } @@ -80,7 +80,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build/linux", - "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", + "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-6.0 -DCMAKE_CXX_COMPILER=clang++-6.0 -DLY_PARALLEL_LINK_JOBS=4", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "all", "CTEST_OPTIONS": "-E Gem::EMotionFX.Editor.Tests -LE (SUITE_sandbox|SUITE_awsi) -L FRAMEWORK_googletest --no-tests=error", @@ -110,7 +110,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build/linux", - "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", + "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-6.0 -DCMAKE_CXX_COMPILER=clang++-6.0 -DLY_PARALLEL_LINK_JOBS=4", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "AssetProcessorBatch", "ASSET_PROCESSOR_BINARY": "bin/profile/AssetProcessorBatch", @@ -142,7 +142,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build/linux", - "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", + "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-6.0 -DCMAKE_CXX_COMPILER=clang++-6.0 -DLY_PARALLEL_LINK_JOBS=4", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "TEST_SUITE_periodic", "CTEST_OPTIONS": "-L (SUITE_periodic) --no-tests=error", @@ -162,7 +162,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build/linux", - "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_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-6.0 -DCMAKE_CXX_COMPILER=clang++-6.0 -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": "-L (SUITE_sandbox) --no-tests=error" @@ -178,7 +178,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build/linux", - "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", + "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-6.0 -DCMAKE_CXX_COMPILER=clang++-6.0 -DLY_PARALLEL_LINK_JOBS=4", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "TEST_SUITE_benchmark", "CTEST_OPTIONS": "-L (SUITE_benchmark) --no-tests=error", @@ -195,7 +195,7 @@ "PARAMETERS": { "CONFIGURATION": "release", "OUTPUT_DIRECTORY": "build/linux", - "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", + "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-6.0 -DCMAKE_CXX_COMPILER=clang++-6.0 -DLY_PARALLEL_LINK_JOBS=4", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "all" } @@ -210,9 +210,52 @@ "PARAMETERS": { "CONFIGURATION": "release", "OUTPUT_DIRECTORY": "build/mono_linux", - "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-6.0 -DCMAKE_CXX_COMPILER=clang++-6.0 -DLY_MONOLITHIC_GAME=TRUE -DLY_UNITY_BUILD=TRUE -DLY_PARALLEL_LINK_JOBS=4", + "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-6.0 -DCMAKE_CXX_COMPILER=clang++-6.0 -DLY_MONOLITHIC_GAME=TRUE -DLY_PARALLEL_LINK_JOBS=4", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "all" } + }, + "install_profile": { + "TAGS": [], + "COMMAND": "build_linux.sh", + "PARAMETERS": { + "CONFIGURATION": "profile", + "OUTPUT_DIRECTORY": "build/linux", + "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-6.0 -DCMAKE_CXX_COMPILER=clang++-6.0 -DLY_PARALLEL_LINK_JOBS=4 -DLY_DISABLE_TEST_MODULES=TRUE", + "CMAKE_TARGET": "install" + } + }, + "install_profile_pipe": { + "TAGS": [ + "nightly-incremental", + "nightly-clean" + ], + "PIPELINE_ENV": { + "PROJECT_REPOSITORY_NAME": "TestProject" + }, + "steps": [ + "install_profile", + "project_generate", + "project_engineinstall_profile" + ] + }, + "project_generate": { + "TAGS": [], + "COMMAND": "python_linux.sh", + "PARAMETERS": { + "SCRIPT_PATH": "install/scripts/o3de.py", + "SCRIPT_PARAMETERS": "create-project -pp ${WORKSPACE}/${PROJECT_REPOSITORY_NAME} --force" + } + }, + "project_engineinstall_profile": { + "TAGS": [], + "COMMAND": "build_linux.sh", + "PARAMETERS": { + "COMMAND_CWD": "${WORKSPACE}/${PROJECT_REPOSITORY_NAME}", + "CONFIGURATION": "profile", + "OUTPUT_DIRECTORY": "build/linux", + "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-6.0 -DCMAKE_CXX_COMPILER=clang++-6.0 -DLY_PARALLEL_LINK_JOBS=4 -DCMAKE_MODULE_PATH=${WORKSPACE}/o3de/install/cmake", + "CMAKE_TARGET": "all" + } } } diff --git a/scripts/build/Platform/Linux/build_linux.sh b/scripts/build/Platform/Linux/build_linux.sh index fd73e17a12..ab51913550 100755 --- a/scripts/build/Platform/Linux/build_linux.sh +++ b/scripts/build/Platform/Linux/build_linux.sh @@ -17,7 +17,10 @@ SOURCE_DIRECTORY=${PWD} pushd $OUTPUT_DIRECTORY LAST_CONFIGURE_CMD_FILE=ci_last_configure_cmd.txt -CONFIGURE_CMD="cmake ${SOURCE_DIRECTORY} ${CMAKE_OPTIONS} ${EXTRA_CMAKE_OPTIONS} -DLY_3RDPARTY_PATH=${LY_3RDPARTY_PATH} -DLY_PROJECTS='${CMAKE_LY_PROJECTS}'" +CONFIGURE_CMD="cmake ${SOURCE_DIRECTORY} ${CMAKE_OPTIONS} ${EXTRA_CMAKE_OPTIONS} -DLY_3RDPARTY_PATH=${LY_3RDPARTY_PATH}" +if [[ -n "$CMAKE_LY_PROJECTS" ]]; then + CONFIGURE_CMD="${CONFIGURE_CMD} -DLY_PROJECTS='${CMAKE_LY_PROJECTS}'" +fi if [[ ! -e "CMakeCache.txt" ]]; then echo [ci_build] First run, generating RUN_CONFIGURE=1 diff --git a/scripts/build/Platform/Linux/env_linux.sh b/scripts/build/Platform/Linux/env_linux.sh index a03b9642fb..059bb119ff 100755 --- a/scripts/build/Platform/Linux/env_linux.sh +++ b/scripts/build/Platform/Linux/env_linux.sh @@ -18,3 +18,8 @@ if ! command -v ninja &> /dev/null; then echo "[ci_build] Ninja not found" exit 1 fi + +if [[ -n "${COMMAND_CWD}" ]]; then + echo $(eval echo [ci_build] Changing CWD to $COMMAND_CWD) + cd $(eval echo ${COMMAND_CWD}) +fi diff --git a/scripts/build/Platform/Mac/build_config.json b/scripts/build/Platform/Mac/build_config.json index 7eb3cb5699..34b02a1aec 100644 --- a/scripts/build/Platform/Mac/build_config.json +++ b/scripts/build/Platform/Mac/build_config.json @@ -37,7 +37,7 @@ "PARAMETERS": { "CONFIGURATION": "debug", "OUTPUT_DIRECTORY": "build/mac", - "CMAKE_OPTIONS": "-G Xcode -DLY_UNITY_BUILD=TRUE", + "CMAKE_OPTIONS": "-G Xcode", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "ALL_BUILD" } @@ -51,7 +51,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build/mac", - "CMAKE_OPTIONS": "-G Xcode -DLY_UNITY_BUILD=TRUE", + "CMAKE_OPTIONS": "-G Xcode", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "ALL_BUILD" } @@ -81,7 +81,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build/mac", - "CMAKE_OPTIONS": "-G Xcode -DLY_UNITY_BUILD=TRUE", + "CMAKE_OPTIONS": "-G Xcode", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "AssetProcessorBatch", "ASSET_PROCESSOR_BINARY": "bin/profile/AssetProcessorBatch", @@ -99,7 +99,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build/mac", - "CMAKE_OPTIONS": "-G Xcode -DLY_UNITY_BUILD=TRUE", + "CMAKE_OPTIONS": "-G Xcode", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "TEST_SUITE_periodic", "CTEST_OPTIONS": "-L \"(SUITE_periodic)\"", @@ -116,7 +116,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build/mac", - "CMAKE_OPTIONS": "-G Xcode -DLY_UNITY_BUILD=TRUE", + "CMAKE_OPTIONS": "-G Xcode", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "TEST_SUITE_benchmark", "CTEST_OPTIONS": "-L \"(SUITE_benchmark)\"", @@ -133,7 +133,7 @@ "PARAMETERS": { "CONFIGURATION": "release", "OUTPUT_DIRECTORY": "build/mac", - "CMAKE_OPTIONS": "-G Xcode -DLY_UNITY_BUILD=TRUE", + "CMAKE_OPTIONS": "-G Xcode", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "ALL_BUILD" } @@ -148,7 +148,7 @@ "PARAMETERS": { "CONFIGURATION": "release", "OUTPUT_DIRECTORY": "build/mono_mac", - "CMAKE_OPTIONS": "-G Xcode -DLY_MONOLITHIC_GAME=TRUE -DLY_UNITY_BUILD=TRUE", + "CMAKE_OPTIONS": "-G Xcode -DLY_MONOLITHIC_GAME=TRUE", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "ALL_BUILD" } @@ -162,5 +162,50 @@ "SCRIPT_PATH": "scripts/build/package/package.py", "SCRIPT_PARAMETERS": "--platform Mac --type all" } + }, + "install_profile": { + "TAGS": [], + "COMMAND": "build_mac.sh", + "PARAMETERS": { + "CONFIGURATION": "profile", + "OUTPUT_DIRECTORY": "build/mac", + "CMAKE_OPTIONS": "-G Xcode -DLY_DISABLE_TEST_MODULES=TRUE", + "CMAKE_LY_PROJECTS": "", + "CMAKE_TARGET": "install" + } + }, + "install_profile_pipe": { + "TAGS": [ + "nightly-incremental", + "nightly-clean" + ], + "PIPELINE_ENV": { + "PROJECT_REPOSITORY_NAME": "TestProject" + }, + "steps": [ + "install_profile", + "project_generate", + "project_engineinstall_profile" + ] + }, + "project_generate": { + "TAGS": [], + "COMMAND": "python_mac.sh", + "PARAMETERS": { + "SCRIPT_PATH": "install/O3DE_SDK.app/Contents/Engine/scripts/o3de.py", + "SCRIPT_PARAMETERS": "create-project -pp ${WORKSPACE}/${PROJECT_REPOSITORY_NAME} --force" + } + }, + "project_engineinstall_profile": { + "TAGS": [], + "COMMAND": "build_mac.sh", + "PARAMETERS": { + "COMMAND_CWD": "${WORKSPACE}/${PROJECT_REPOSITORY_NAME}", + "CONFIGURATION": "profile", + "OUTPUT_DIRECTORY": "build/mac", + "CMAKE_OPTIONS": "-G Xcode -DCMAKE_MODULE_PATH=${WORKSPACE}/o3de/install/O3DE_SDK.app/Contents/Engine/cmake", + "CMAKE_LY_PROJECTS": "", + "CMAKE_TARGET": "ALL_BUILD" + } } } diff --git a/scripts/build/Platform/Mac/build_mac.sh b/scripts/build/Platform/Mac/build_mac.sh index cb271212d6..169e91c24f 100755 --- a/scripts/build/Platform/Mac/build_mac.sh +++ b/scripts/build/Platform/Mac/build_mac.sh @@ -17,7 +17,10 @@ SOURCE_DIRECTORY=${PWD} pushd $OUTPUT_DIRECTORY LAST_CONFIGURE_CMD_FILE=ci_last_configure_cmd.txt -CONFIGURE_CMD="cmake ${SOURCE_DIRECTORY} ${CMAKE_OPTIONS} ${EXTRA_CMAKE_OPTIONS} -DLY_3RDPARTY_PATH=${LY_3RDPARTY_PATH} -DLY_PROJECTS='${CMAKE_LY_PROJECTS}'" +CONFIGURE_CMD="cmake ${SOURCE_DIRECTORY} ${CMAKE_OPTIONS} ${EXTRA_CMAKE_OPTIONS} -DLY_3RDPARTY_PATH=${LY_3RDPARTY_PATH}" +if [[ -n "$CMAKE_LY_PROJECTS" ]]; then + CONFIGURE_CMD="${CONFIGURE_CMD} -DLY_PROJECTS='${CMAKE_LY_PROJECTS}'" +fi if [[ ! -e "CMakeCache.txt" ]]; then echo [ci_build] First run, generating RUN_CONFIGURE=1 diff --git a/scripts/build/Platform/Mac/env_mac.sh b/scripts/build/Platform/Mac/env_mac.sh index 2c974a1efe..f5fd9f5773 100755 --- a/scripts/build/Platform/Mac/env_mac.sh +++ b/scripts/build/Platform/Mac/env_mac.sh @@ -13,3 +13,8 @@ if ! command -v cmake &> /dev/null; then echo "[ci_build] CMake not found" exit 1 fi + +if [[ -n "${COMMAND_CWD}" ]]; then + echo $(eval echo [ci_build] Changing CWD to $COMMAND_CWD) + cd $(eval echo ${COMMAND_CWD}) +fi diff --git a/scripts/build/Platform/Windows/build_config.json b/scripts/build/Platform/Windows/build_config.json index de83294639..b0bb79b5dd 100644 --- a/scripts/build/Platform/Windows/build_config.json +++ b/scripts/build/Platform/Windows/build_config.json @@ -56,7 +56,7 @@ "COMMAND": "python_windows.cmd", "PARAMETERS": { "SCRIPT_PATH": "scripts/build/ci_build_metrics.py", - "SCRIPT_PARAMETERS": "--platform=Windows --repository=!REPOSITORY_NAME! --jobname=!JOB_NAME! --jobnumber=!BUILD_NUMBER! --jobnode=!NODE_LABEL! --changelist=!CHANGE_ID!" + "SCRIPT_PARAMETERS": "--platform=Windows --repository=%REPOSITORY_NAME% --jobname=%JOB_NAME% --jobnumber=%BUILD_NUMBER% --jobnode=%NODE_LABEL% --changelist=%CHANGE_ID%" } }, "windows_packaging_all": { @@ -88,7 +88,7 @@ "CONFIGURATION": "profile", "SCRIPT_PATH": "scripts/build/TestImpactAnalysis/tiaf_driver.py", "SCRIPT_PARAMETERS": - "--config=\"!OUTPUT_DIRECTORY!/bin/TestImpactFramework/profile/Persistent/tiaf.json\" --src-branch=!BRANCH_NAME! --dst-branch=!CHANGE_TARGET! --commit=!CHANGE_ID! --s3-bucket=!TEST_IMPACT_S3_BUCKET! --mars-index-prefix=jonawals --s3-top-level-dir=!REPOSITORY_NAME! --build-number=!BUILD_NUMBER! --suite=main --test-failure-policy=continue" + "--config=\"%OUTPUT_DIRECTORY%/bin/TestImpactFramework/profile/Persistent/tiaf.json\" --src-branch=%BRANCH_NAME% --dst-branch=%CHANGE_TARGET% --commit=%CHANGE_ID% --s3-bucket=%TEST_IMPACT_S3_BUCKET% --mars-index-prefix=jonawals --s3-top-level-dir=%REPOSITORY_NAME% --build-number=%BUILD_NUMBER% --suite=main --test-failure-policy=continue" } }, "debug_vs2019": { @@ -99,7 +99,7 @@ "PARAMETERS": { "CONFIGURATION": "debug", "OUTPUT_DIRECTORY": "build\\windows_vs2019", - "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_UNITY_BUILD=TRUE", + "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "ALL_BUILD", "CMAKE_NATIVE_BUILD_ARGS": "/m /nologo" @@ -113,7 +113,7 @@ "PARAMETERS": { "CONFIGURATION": "debug", "OUTPUT_DIRECTORY": "build\\windows_vs2019", - "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_UNITY_BUILD=TRUE", + "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "TEST_SUITE_smoke TEST_SUITE_main", "CMAKE_NATIVE_BUILD_ARGS": "/m /nologo", @@ -131,7 +131,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build\\windows_vs2019", - "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_UNITY_BUILD=TRUE -DLY_TEST_IMPACT_INSTRUMENTATION_BIN=!TEST_IMPACT_WIN_BINARY!", + "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_TEST_IMPACT_INSTRUMENTATION_BIN=%TEST_IMPACT_WIN_BINARY%", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "ALL_BUILD", "CMAKE_NATIVE_BUILD_ARGS": "/m /nologo" @@ -162,7 +162,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build\\windows_vs2019", - "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_UNITY_BUILD=TRUE", + "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "TEST_SUITE_smoke TEST_SUITE_main", "CMAKE_NATIVE_BUILD_ARGS": "/m /nologo", @@ -183,7 +183,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build\\windows_vs2019", - "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_UNITY_BUILD=TRUE", + "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "TEST_SUITE_smoke TEST_SUITE_main", "CMAKE_NATIVE_BUILD_ARGS": "/m /nologo", @@ -203,7 +203,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build\\windows_vs2019", - "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_UNITY_BUILD=TRUE", + "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "AssetProcessorBatch", "CMAKE_NATIVE_BUILD_ARGS": "/m /nologo", @@ -234,7 +234,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build\\windows_vs2019", - "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_UNITY_BUILD=TRUE", + "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "TEST_SUITE_awsi", "CMAKE_NATIVE_BUILD_ARGS": "/m /nologo", @@ -253,7 +253,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build\\windows_vs2019", - "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_UNITY_BUILD=TRUE", + "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "TEST_SUITE_periodic", "CMAKE_NATIVE_BUILD_ARGS": "/m /nologo", @@ -275,7 +275,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build\\windows_vs2019", - "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_UNITY_BUILD=TRUE", + "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "TEST_SUITE_sandbox", "CMAKE_NATIVE_BUILD_ARGS": "/m /nologo", @@ -294,7 +294,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build\\windows_vs2019", - "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_UNITY_BUILD=TRUE", + "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "TEST_SUITE_benchmark", "CMAKE_NATIVE_BUILD_ARGS": "/m /nologo", @@ -314,7 +314,7 @@ "PARAMETERS": { "CONFIGURATION": "release", "OUTPUT_DIRECTORY": "build\\windows_vs2019", - "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_UNITY_BUILD=TRUE", + "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "ALL_BUILD", "CMAKE_NATIVE_BUILD_ARGS": "/m /nologo" @@ -330,23 +330,19 @@ "PARAMETERS": { "CONFIGURATION": "release", "OUTPUT_DIRECTORY": "build\\mono_windows_vs2019", - "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_MONOLITHIC_GAME=TRUE -DLY_UNITY_BUILD=TRUE", + "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_MONOLITHIC_GAME=TRUE", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "ALL_BUILD", "CMAKE_NATIVE_BUILD_ARGS": "/m /nologo" } }, "install_profile_vs2019": { - "TAGS": [ - "nightly-incremental", - "nightly-clean" - ], + "TAGS": [], "COMMAND": "build_windows.cmd", "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build\\windows_vs2019", - "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_UNITY_BUILD=TRUE -DLY_DISABLE_TEST_MODULES=TRUE", - "CMAKE_LY_PROJECTS": "", + "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_DISABLE_TEST_MODULES=TRUE", "CMAKE_TARGET": "INSTALL", "CMAKE_NATIVE_BUILD_ARGS": "/m /nologo" } @@ -363,14 +359,35 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build\\windows_vs2019", - "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_UNITY_BUILD=TRUE -DLY_DISABLE_TEST_MODULES=TRUE -DLY_VERSION_ENGINE_NAME=o3de-sdk -DLY_INSTALLER_WIX_ROOT=\"!WIX! \"", - "EXTRA_CMAKE_OPTIONS": "-DLY_INSTALLER_AUTO_GEN_TAG=ON -DLY_INSTALLER_DOWNLOAD_URL=!INSTALLER_DOWNLOAD_URL! -DLY_INSTALLER_LICENSE_URL=!INSTALLER_DOWNLOAD_URL!/license", - "CPACK_BUCKET": "!INSTALLER_BUCKET!", - "CMAKE_LY_PROJECTS": "", + "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_DISABLE_TEST_MODULES=TRUE -DLY_VERSION_ENGINE_NAME=o3de-sdk -DLY_INSTALLER_WIX_ROOT=\"%WIX% \"", + "EXTRA_CMAKE_OPTIONS": "-DLY_INSTALLER_AUTO_GEN_TAG=ON -DLY_INSTALLER_DOWNLOAD_URL=%INSTALLER_DOWNLOAD_URL% -DLY_INSTALLER_LICENSE_URL=%INSTALLER_DOWNLOAD_URL%/license", + "CPACK_BUCKET": "%INSTALLER_BUCKET%", "CMAKE_TARGET": "ALL_BUILD", "CMAKE_NATIVE_BUILD_ARGS": "/m /nologo" } }, + "install_profile_vs2019_pipe": { + "TAGS": [ + "nightly-incremental", + "nightly-clean" + ], + "PIPELINE_ENV": { + "PROJECT_REPOSITORY_NAME": "TestProject" + }, + "steps": [ + "install_profile_vs2019", + "project_generate", + "project_engineinstall_profile_vs2019" + ] + }, + "project_generate": { + "TAGS": [], + "COMMAND": "python_windows.cmd", + "PARAMETERS": { + "SCRIPT_PATH": "install\\scripts\\o3de.py", + "SCRIPT_PARAMETERS": "create-project -pp %WORKSPACE%\\%PROJECT_REPOSITORY_NAME% --force" + } + }, "project_enginesource_profile_vs2019": { "TAGS": [ "project" @@ -382,8 +399,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build\\windows_vs2019", - "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_UNITY_BUILD=TRUE -DCMAKE_MODULE_PATH=!WORKSPACE!/o3de/cmake", - "CMAKE_LY_PROJECTS": "", + "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DCMAKE_MODULE_PATH=%WORKSPACE%/o3de/cmake", "CMAKE_TARGET": "ALL_BUILD", "CMAKE_NATIVE_BUILD_ARGS": "/m /nologo" } @@ -395,10 +411,10 @@ }, "COMMAND": "build_windows.cmd", "PARAMETERS": { + "COMMAND_CWD": "%WORKSPACE%\\%PROJECT_REPOSITORY_NAME%", "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build\\windows_vs2019", - "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_UNITY_BUILD=TRUE -DCMAKE_MODULE_PATH=!WORKSPACE!/o3de/install/cmake", - "CMAKE_LY_PROJECTS": "", + "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DCMAKE_MODULE_PATH=%WORKSPACE%/o3de/install/cmake", "CMAKE_TARGET": "ALL_BUILD", "CMAKE_NATIVE_BUILD_ARGS": "/m /nologo" } diff --git a/scripts/build/Platform/Windows/build_windows.cmd b/scripts/build/Platform/Windows/build_windows.cmd index b5a0245d1a..b9e862e04f 100644 --- a/scripts/build/Platform/Windows/build_windows.cmd +++ b/scripts/build/Platform/Windows/build_windows.cmd @@ -9,6 +9,13 @@ REM SETLOCAL EnableDelayedExpansion +REM Jenkins reports MSB8029 when TMP/TEMP is not defined, define a dummy folder +SET TMP=%cd%/temp +SET TEMP=%cd%/temp +IF NOT EXIST %TMP% ( + MKDIR temp +) + CALL %~dp0env_windows.cmd IF NOT EXIST "%OUTPUT_DIRECTORY%" ( @@ -25,18 +32,14 @@ IF ERRORLEVEL 1 ( exit /b 1 ) -REM Jenkins reports MSB8029 when TMP/TEMP is not defined, define a dummy folder -SET TMP=%cd%/temp -SET TEMP=%cd%/temp -IF NOT EXIST %TMP% ( - MKDIR temp -) - REM Compute half the amount of processors so some jobs can run SET /a HALF_PROCESSORS = NUMBER_OF_PROCESSORS / 2 SET LAST_CONFIGURE_CMD_FILE=ci_last_configure_cmd.txt -SET CONFIGURE_CMD=cmake %SOURCE_DIRECTORY% %CMAKE_OPTIONS% %EXTRA_CMAKE_OPTIONS% -DLY_3RDPARTY_PATH="%LY_3RDPARTY_PATH%" -DLY_PROJECTS=%CMAKE_LY_PROJECTS% +SET CONFIGURE_CMD=cmake %SOURCE_DIRECTORY% %CMAKE_OPTIONS% %EXTRA_CMAKE_OPTIONS% -DLY_3RDPARTY_PATH="%LY_3RDPARTY_PATH%" +IF NOT "%CMAKE_LY_PROJECTS%"=="" ( + SET CONFIGURE_CMD=!CONFIGURE_CMD! -DLY_PROJECTS="%CMAKE_LY_PROJECTS%" +) IF NOT EXIST CMakeCache.txt ( ECHO [ci_build] First run, generating SET RUN_CONFIGURE=1 diff --git a/scripts/build/Platform/Windows/env_windows.cmd b/scripts/build/Platform/Windows/env_windows.cmd index 1c54e36bfc..f11d394519 100644 --- a/scripts/build/Platform/Windows/env_windows.cmd +++ b/scripts/build/Platform/Windows/env_windows.cmd @@ -13,6 +13,11 @@ IF NOT %ERRORLEVEL%==0 ( GOTO :error ) +IF NOT "%COMMAND_CWD%"=="" ( + ECHO [ci_build] Changing CWD to %COMMAND_CWD% + CD %COMMAND_CWD% +) + EXIT /b 0 :error diff --git a/scripts/build/Platform/Windows/package_build_config.json b/scripts/build/Platform/Windows/package_build_config.json index 89431ad96e..5eb96f1e38 100644 --- a/scripts/build/Platform/Windows/package_build_config.json +++ b/scripts/build/Platform/Windows/package_build_config.json @@ -4,7 +4,7 @@ "PARAMETERS": { "CONFIGURATION":"profile", "OUTPUT_DIRECTORY":"windows_vs2019", - "CMAKE_OPTIONS":"-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_UNITY_BUILD=TRUE", + "CMAKE_OPTIONS":"-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0", "CMAKE_LY_PROJECTS":"AtomTest;AtomSampleViewer", "CMAKE_TARGET":"ALL_BUILD", "CMAKE_NATIVE_BUILD_ARGS": "/m /nologo" diff --git a/scripts/build/Platform/iOS/build_config.json b/scripts/build/Platform/iOS/build_config.json index 01f545ffb6..895f74daae 100644 --- a/scripts/build/Platform/iOS/build_config.json +++ b/scripts/build/Platform/iOS/build_config.json @@ -27,7 +27,7 @@ "PARAMETERS": { "CONFIGURATION": "debug", "OUTPUT_DIRECTORY": "build/ios", - "CMAKE_OPTIONS": "-G Xcode -DCMAKE_TOOLCHAIN_FILE=cmake/Platform/iOS/Toolchain_ios.cmake -DLY_MONOLITHIC_GAME=TRUE -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED=FALSE -DLY_IOS_CODE_SIGNING_IDENTITY=\"\" -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS=\"\" -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED=FALSE -DLY_UNITY_BUILD=TRUE", + "CMAKE_OPTIONS": "-G Xcode -DCMAKE_TOOLCHAIN_FILE=cmake/Platform/iOS/Toolchain_ios.cmake -DLY_MONOLITHIC_GAME=TRUE -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED=FALSE -DLY_IOS_CODE_SIGNING_IDENTITY=\"\" -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS=\"\" -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED=FALSE", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "ALL_BUILD", "CMAKE_NATIVE_BUILD_ARGS": "-destination generic/platform=iOS" @@ -44,7 +44,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build/ios", - "CMAKE_OPTIONS": "-G Xcode -DCMAKE_TOOLCHAIN_FILE=cmake/Platform/iOS/Toolchain_ios.cmake -DLY_MONOLITHIC_GAME=TRUE -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED=FALSE -DLY_IOS_CODE_SIGNING_IDENTITY=\"\" -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS=\"\" -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED=FALSE -DLY_UNITY_BUILD=TRUE", + "CMAKE_OPTIONS": "-G Xcode -DCMAKE_TOOLCHAIN_FILE=cmake/Platform/iOS/Toolchain_ios.cmake -DLY_MONOLITHIC_GAME=TRUE -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED=FALSE -DLY_IOS_CODE_SIGNING_IDENTITY=\"\" -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS=\"\" -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED=FALSE", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "ALL_BUILD", "CMAKE_NATIVE_BUILD_ARGS": "-destination generic/platform=iOS" @@ -76,7 +76,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build/mac", - "CMAKE_OPTIONS": "-G Xcode -DLY_UNITY_BUILD=TRUE -DO3DE_HOME_PATH=\"${WORKSPACE}/home\" -DO3DE_REGISTER_ENGINE_PATH=\"${WORKSPACE}/o3de\" -DO3DE_REGISTER_THIS_ENGINE=TRUE", + "CMAKE_OPTIONS": "-G Xcode -DO3DE_HOME_PATH=\"${WORKSPACE}/home\" -DO3DE_REGISTER_ENGINE_PATH=\"${WORKSPACE}/o3de\" -DO3DE_REGISTER_THIS_ENGINE=TRUE", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "AssetProcessorBatch", "ASSET_PROCESSOR_BINARY": "bin/profile/AssetProcessorBatch", @@ -94,7 +94,7 @@ "PARAMETERS": { "CONFIGURATION": "release", "OUTPUT_DIRECTORY": "build/ios", - "CMAKE_OPTIONS": "-G Xcode -DCMAKE_TOOLCHAIN_FILE=cmake/Platform/iOS/Toolchain_ios.cmake -DLY_MONOLITHIC_GAME=TRUE -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED=FALSE -DLY_IOS_CODE_SIGNING_IDENTITY=\"\" -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS=\"\" -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED=FALSE -DLY_UNITY_BUILD=TRUE", + "CMAKE_OPTIONS": "-G Xcode -DCMAKE_TOOLCHAIN_FILE=cmake/Platform/iOS/Toolchain_ios.cmake -DLY_MONOLITHIC_GAME=TRUE -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED=FALSE -DLY_IOS_CODE_SIGNING_IDENTITY=\"\" -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS=\"\" -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED=FALSE", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "ALL_BUILD", "CMAKE_NATIVE_BUILD_ARGS": "-destination generic/platform=iOS" @@ -112,7 +112,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build/ios_test", - "CMAKE_OPTIONS": "-G Xcode -DCMAKE_TOOLCHAIN_FILE=cmake/Platform/iOS/Toolchain_ios.cmake -DLY_MONOLITHIC_GAME=FALSE -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED=TRUE -DLY_IOS_CODE_SIGNING_IDENTITY=\"\" -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS=\"\" -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED=TRUE -DLY_UNITY_BUILD=TRUE -DO3DE_HOME_PATH=\"${WORKSPACE}/home\" -DO3DE_REGISTER_ENGINE_PATH=\"${WORKSPACE}/o3de\" -DO3DE_REGISTER_THIS_ENGINE=TRUE", + "CMAKE_OPTIONS": "-G Xcode -DCMAKE_TOOLCHAIN_FILE=cmake/Platform/iOS/Toolchain_ios.cmake -DLY_MONOLITHIC_GAME=FALSE -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED=TRUE -DLY_IOS_CODE_SIGNING_IDENTITY=\"\" -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS=\"\" -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED=TRUE -DO3DE_HOME_PATH=\"${WORKSPACE}/home\" -DO3DE_REGISTER_ENGINE_PATH=\"${WORKSPACE}/o3de\" -DO3DE_REGISTER_THIS_ENGINE=TRUE", "CMAKE_TARGET": "ALL_BUILD", "CMAKE_NATIVE_BUILD_ARGS": "", "TARGET_DEVICE_NAME": "Lumberyard", diff --git a/scripts/build/build_node/Platform/Linux/package-list.ubuntu-bionic.txt b/scripts/build/build_node/Platform/Linux/package-list.ubuntu-bionic.txt index 625909e214..a09d823950 100644 --- a/scripts/build/build_node/Platform/Linux/package-list.ubuntu-bionic.txt +++ b/scripts/build/build_node/Platform/Linux/package-list.ubuntu-bionic.txt @@ -12,7 +12,12 @@ libxcb-xinerama0 # For Qt plugins at runtime libxcb-xinput0 # For Qt plugins at runtime libfontconfig1-dev # For Qt plugins at runtime libcurl4-openssl-dev # For HttpRequestor -libsdl2-dev # for WWise/Audio +libsdl2-dev # For WWise/Audio +libxcb-xkb-dev # For xcb keyboard input +libxkbcommon-x11-dev # For xcb keyboard input +libxkbcommon-dev # For xcb keyboard input +libxcb-xfixes0-dev # For mouse input +libxcb-xinput-dev # For mouse input zlib1g-dev mesa-common-dev diff --git a/scripts/build/build_node/Platform/Linux/package-list.ubuntu-focal.txt b/scripts/build/build_node/Platform/Linux/package-list.ubuntu-focal.txt index 2cbbe58b38..e0e03cda90 100644 --- a/scripts/build/build_node/Platform/Linux/package-list.ubuntu-focal.txt +++ b/scripts/build/build_node/Platform/Linux/package-list.ubuntu-focal.txt @@ -16,5 +16,7 @@ libsdl2-dev # for WWise/Audio libxcb-xkb-dev # For xcb keyboard input libxkbcommon-x11-dev # For xcb keyboard input libxkbcommon-dev # For xcb keyboard input +libxcb-xfixes0-dev # For mouse input +libxcb-xinput-dev # For mouse input zlib1g-dev mesa-common-dev diff --git a/scripts/commit_validation/commit_validation/commit_validation.py b/scripts/commit_validation/commit_validation/commit_validation.py index b9d992fc9c..513244a735 100755 --- a/scripts/commit_validation/commit_validation/commit_validation.py +++ b/scripts/commit_validation/commit_validation/commit_validation.py @@ -173,16 +173,12 @@ EXCLUDED_VALIDATION_PATTERNS = [ '*/3rdParty/*', '*/__pycache__/*', '*/External/*', - 'build', - 'Cache', - '*/Code/Framework/AzCore/azgnmx/azgnmx/*', - 'Code/Tools/CryFXC', - 'Code/Tools/HLSLCrossCompiler', - 'Code/Tools/HLSLCrossCompilerMETAL', - 'Docs', + 'build', # build artifacts + '*/Cache/*', # Asset processing artifacts + 'install', # install layout artifacts 'python/runtime', + 'restricted/*/Code/Framework/AzCore/azgnmx/azgnmx/*', 'restricted/*/Tools/*RemoteControl', - 'Tools/3dsmax', '*/user/Cache/*', '*/user/log/*', ] diff --git a/scripts/commit_validation/commit_validation/pal_allowedlist.txt b/scripts/commit_validation/commit_validation/pal_allowedlist.txt index 4c90bcb6b4..6fd5aab62e 100644 --- a/scripts/commit_validation/commit_validation/pal_allowedlist.txt +++ b/scripts/commit_validation/commit_validation/pal_allowedlist.txt @@ -23,7 +23,6 @@ */Code/Framework/AzCore/AzCore/std/parallel/binary_semaphore.h */Code/Framework/AzCore/AzCore/std/parallel/semaphore.h */Code/Framework/AzCore/Platform/Android/AzCore/AzCore_Traits_Android.h -*/Code/Framework/AzCore/Platform/AppleTV/AzCore/AzCore_Traits_AppleTV.h */Code/Framework/AzCore/Platform/iOS/AzCore/AzCore_Traits_iOS.h */Code/Framework/AzCore/Platform/Jasper/AzCore/AzCore_Traits_Jasper.h */Code/Framework/AzCore/Platform/Linux/AzCore/AzCore_Traits_Linux.h @@ -49,7 +48,6 @@ */Code/Tools/* */Gems/*/3rdParty/* */Gems/*/External/* -*/Gems/CryLegacy* */Gems/EMotionFX/Code/EMotionFX/Rendering/OpenGL2/Source/GLInclude.h */Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/EMStudioSDK/Source/MainWindow.cpp */Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/EMStudioSDK/Source/PluginManager.cpp @@ -61,4 +59,3 @@ */Gems/SaveData/Code/Tests/SaveDataTest.cpp */Gems/WhiteBox/Code/Source/Rendering/Legacy/WhiteBoxLegacyRenderMesh.cpp */restricted/*/Code/Framework/AzCore/AzCore/AzCore_Traits_*.h -*/Tools/CryDeprecation/precompile_check_defines.h diff --git a/scripts/license_scanner/license_scanner.py b/scripts/license_scanner/license_scanner.py index c0e3c1f1ba..2936e343f9 100644 --- a/scripts/license_scanner/license_scanner.py +++ b/scripts/license_scanner/license_scanner.py @@ -6,6 +6,7 @@ # import argparse +from collections import OrderedDict import fnmatch import json import os @@ -24,15 +25,19 @@ class LicenseScanner: """ DEFAULT_CONFIG_FILE = 'scanner_config.json' + DEFAULT_EXCLUDE_FILE = '.gitignore' + DEFAULT_PACKAGE_INFO_FILE = 'PackageInfo.json' def __init__(self, config_file=None): self.config_file = config_file self.config_data = self._load_config() - self.license_regex = self._load_license_regex() + self.file_regex = self._load_file_regex(self.config_data['license_patterns']) + self.package_info = self._load_file_regex(self.config_data['package_patterns']) + self.excluded_directories = self._load_file_regex(self.config_data['excluded_directories']) def _load_config(self): """Load config from the provided file. Sets default file if one is not provided.""" - if self.config_file is None: + if not self.config_file: script_directory = os.path.dirname(os.path.abspath(__file__)) # Default file expected in same dir as script self.config_file = os.path.join(script_directory, self.DEFAULT_CONFIG_FILE) @@ -43,45 +48,68 @@ class LicenseScanner: print('Config file cannot be found') raise - def _load_license_regex(self): + def _load_file_regex(self, patterns): """Returns regex object with case-insensitive matching from the list of filename patterns.""" regex_patterns = [] - for pattern in self.config_data['license_patterns']: + for pattern in patterns: regex_patterns.append(fnmatch.translate(pattern)) + + if not regex_patterns: + print(f'Warning: No patterns from {patterns} found') + return None + return re.compile('|'.join(regex_patterns), re.IGNORECASE) - def scan(self, path=os.curdir): - """Scan directory tree for filenames matching license_regex. + def scan(self, paths=os.curdir): + """Scan directory tree for filenames matching file_regex, package info, and exclusion files. - :param path: Path of the directory to run scanner - :return: Package paths and their corresponding license file contents - :rtype: dict + :param paths: Paths of the directory to run scanner + :return: Package paths and their corresponding file contents + :rtype: Ordered dict """ - licenses = 0 - license_files = {} + files = 0 + matching_files = OrderedDict() + excluded_directories = None - for dirpath, dirnames, filenames in os.walk(path): - for file in filenames: - if self.license_regex.match(file): - license_file_content = self._get_license_file_contents(os.path.join(dirpath, file)) - rel_dirpath = os.path.relpath(dirpath, path) # Limit path inside scanned directory - license_files[rel_dirpath] = license_file_content - licenses += 1 - print(f'License file: {os.path.join(dirpath, file)}') + if not self.package_info: + self.package_info = self.DEFAULT_PACKAGE_INFO_FILE - # Remove directories that should not be scanned - for dir in self.config_data['excluded_directories']: - if dir in dirnames: - dirnames.remove(dir) - print(f'{licenses} license files found.') - return license_files + if not self.excluded_directories: + print(f'No excluded directory in config, looking for {self.DEFAULT_EXCLUDE_FILE} instead') - def _get_license_file_contents(self, filepath): + for path in paths: + for dirpath, dirnames, filenames in os.walk(path, topdown=True): + dirnames.sort(key=str.casefold) # Ensure that results are sorted + for file in filenames: + if self.file_regex.match(file) or self.package_info.match(file): + file_path = os.path.join(dirpath, file) + matching_file_content = self._get_file_contents(file_path) + matching_files[file_path] = matching_file_content + files += 1 + print(f'Matching file: {file_path}') + if self.package_info.match(file): + dirnames[:] = [] # Stop scanning subdirectories if package info file found + if self.DEFAULT_EXCLUDE_FILE in file and not self.excluded_directories: + ignore_list = self._get_file_contents(os.path.join(dirpath, file)).splitlines() + ignore_list.append('.git') # .gitignore doesn't usually have .git in its exclusions + excluded_directories = self._load_file_regex(ignore_list) + + # Remove directories that should not be scanned + if self.excluded_directories: + excluded_directories = self.excluded_directories + for dir in dirnames: + if excluded_directories.match(dir): + dirnames.remove(dir) + + print(f'{files} files found.') + return matching_files + + def _get_file_contents(self, filepath): try: with open(filepath, encoding='utf8') as f: return f.read() except UnicodeDecodeError: - print(f'Unable to read license file: {filepath}') + print(f'Unable to read file: {filepath}') pass def create_license_file(self, licenses, filepath='NOTICES.txt'): @@ -89,18 +117,44 @@ class LicenseScanner: :param licenses: Dict with package paths and their corresponding license file contents :param filepath: Path to write the file - """ - package_separator = '------------------------------------' - with open(filepath, 'w', encoding='utf8') as f: + """ + license_separator = '------------------------------------' + with open(filepath, 'w', encoding='utf8') as lf: for directory, license in licenses.items(): - license_output = '\n\n'.join([ - f'{package_separator}', - f'Package path: {directory}', - 'License:', - f'{license}\n' - ]) - f.write(license_output) + if not self.package_info.match(os.path.basename(directory)): + license_output = '\n\n'.join([ + f'{license_separator}', + f'Package path: {os.path.relpath(directory)}', + 'License:', + f'{license}\n' + ]) + lf.write(license_output) return None + + def create_package_file(self, packages, filepath='SPDX-Licenses.json', get_contents=False): + """Creates file with all the provided SPDX package info summaries in json. + Optional dirpath parameter will follow the license file path in the package info and return its contents in a dictionary + + :param licenses: Dict with package info paths and their corresponding file contents + :param filepath: Path to write the file + :param dirpath: Root path for packages + :rtype: Ordered dict + """ + licenses = OrderedDict() + package_json = [] + + with open(filepath, 'w', encoding='utf8') as pf: + for directory, package in packages.items(): + if self.package_info.match(os.path.basename(directory)): + package_obj = json.loads(package) + package_json.append(package_obj) + if get_contents: + license_path = os.path.join(os.path.dirname(directory), pathlib.Path(package_obj['LicenseFile'])) + licenses[license_path] = self._get_file_contents(license_path) + else: + licenses[directory] = package + pf.write(json.dumps(package_json, indent=4)) + return licenses def parse_args(): @@ -108,7 +162,8 @@ def parse_args(): description='Script to run LicenseScanner and generate license file') parser.add_argument('--config-file', '-c', type=pathlib.Path, help='Config file for LicenseScanner') parser.add_argument('--license-file-path', '-l', type=pathlib.Path, help='Create license file in the provided path') - parser.add_argument('--scan-path', '-s', default=os.curdir, type=pathlib.Path, help='Path to scan') + parser.add_argument('--package-file-path', '-p', type=pathlib.Path, help='Create package summary file in the provided path') + parser.add_argument('--scan-path', '-s', default=os.curdir, type=pathlib.Path, nargs='+', help='Path to scan, multiple space separated paths can be used') return parser.parse_args() @@ -116,10 +171,15 @@ def main(): try: args = parse_args() ls = LicenseScanner(args.config_file) - licenses = ls.scan(args.scan_path) + scanned_path_data = ls.scan(args.scan_path) if args.license_file_path: - ls.create_license_file(licenses, args.license_file_path) + ls.create_license_file(scanned_path_data, args.license_file_path) + if args.package_file_path: + ls.create_package_file(scanned_path_data, args.package_file_path) + if args.license_file_path and args.package_file_path: + license_files = ls.create_package_file(scanned_path_data, args.package_file_path, True) + ls.create_license_file(license_files, args.license_file_path) except FileNotFoundError as e: print(f'Type: {type(e).__name__}, Error: {e}') return 1 diff --git a/scripts/license_scanner/scanner_config.json b/scripts/license_scanner/scanner_config.json index b5863a7d31..2e8f5206db 100644 --- a/scripts/license_scanner/scanner_config.json +++ b/scripts/license_scanner/scanner_config.json @@ -8,5 +8,8 @@ "license_patterns": [ "LICENSE*", "COPYING*" + ], + "package_patterns": [ + "PackageInfo.json" ] } diff --git a/scripts/o3de/o3de/download.py b/scripts/o3de/o3de/download.py index f8868dd460..98f5d051a4 100644 --- a/scripts/o3de/o3de/download.py +++ b/scripts/o3de/o3de/download.py @@ -90,7 +90,8 @@ def get_downloadable(engine_name: str = None, def download_o3de_object(object_name: str, default_folder_name: str, dest_path: str or pathlib.Path, - object_type: str, downloadable_kwarg_key, skip_auto_register: bool) -> int: + object_type: str, downloadable_kwarg_key, skip_auto_register: bool, + download_progress_callback = None) -> int: download_path = manifest.get_o3de_cache_folder() / default_folder_name / object_name download_path.mkdir(parents=True, exist_ok=True) @@ -104,7 +105,7 @@ def download_o3de_object(object_name: str, default_folder_name: str, dest_path: origin_uri = downloadable_object_data['originuri'] parsed_uri = urllib.parse.urlparse(origin_uri) - download_zip_result = utils.download_zip_file(parsed_uri, download_zip_path) + download_zip_result = utils.download_zip_file(parsed_uri, download_zip_path, download_progress_callback) if download_zip_result != 0: return download_zip_result @@ -147,33 +148,38 @@ def download_o3de_object(object_name: str, default_folder_name: str, dest_path: def download_engine(engine_name: str, dest_path: str or pathlib.Path, - skip_auto_register: bool) -> int: - return download_o3de_object(engine_name, 'engines', dest_path, 'engine', 'engine_name', skip_auto_register) + skip_auto_register: bool, + download_progress_callback = None) -> int: + return download_o3de_object(engine_name, 'engines', dest_path, 'engine', 'engine_name', skip_auto_register, download_progress_callback) def download_project(project_name: str, dest_path: str or pathlib.Path, - skip_auto_register: bool) -> int: - return download_o3de_object(project_name, 'projects', dest_path, 'project', 'project_name', skip_auto_register) + skip_auto_register: bool, + download_progress_callback = None) -> int: + return download_o3de_object(project_name, 'projects', dest_path, 'project', 'project_name', skip_auto_register, download_progress_callback) def download_gem(gem_name: str, dest_path: str or pathlib.Path, - skip_auto_register: bool) -> int: - return download_o3de_object(gem_name, 'gems', dest_path, 'gem', 'gem_name', skip_auto_register) + skip_auto_register: bool, + download_progress_callback = None) -> int: + return download_o3de_object(gem_name, 'gems', dest_path, 'gem', 'gem_name', skip_auto_register, download_progress_callback) def download_template(template_name: str, dest_path: str or pathlib.Path, - skip_auto_register: bool) -> int: - return download_o3de_object(template_name, 'templates', dest_path, 'template', 'template_name', skip_auto_register) + skip_auto_register: bool, + download_progress_callback = None) -> int: + return download_o3de_object(template_name, 'templates', dest_path, 'template', 'template_name', skip_auto_register, download_progress_callback) def download_restricted(restricted_name: str, dest_path: str or pathlib.Path, - skip_auto_register: bool) -> int: - return download_o3de_object(restricted_name, 'restricted', dest_path, 'restricted', 'restricted_name', skip_auto_register) + skip_auto_register: bool, + download_progress_callback = None) -> int: + return download_o3de_object(restricted_name, 'restricted', dest_path, 'restricted', 'restricted_name', skip_auto_register, download_progress_callback) def _run_download(args: argparse) -> int: diff --git a/scripts/o3de/o3de/manifest.py b/scripts/o3de/o3de/manifest.py index fc778591c4..297d8e39d2 100644 --- a/scripts/o3de/o3de/manifest.py +++ b/scripts/o3de/o3de/manifest.py @@ -502,7 +502,10 @@ def get_gem_json_data(gem_name: str = None, gem_path: str or pathlib.Path = None if gem_name and not gem_path: gem_path = get_registered(gem_name=gem_name, project_path=project_path) - return get_json_data('gem', gem_path, validation.valid_o3de_gem_json) + if pathlib.Path(gem_path).is_file(): + return get_json_data_file(gem_path, 'gem', validation.valid_o3de_gem_json) + else: + return get_json_data('gem', gem_path, validation.valid_o3de_gem_json) def get_template_json_data(template_name: str = None, template_path: str or pathlib.Path = None, diff --git a/scripts/o3de/o3de/register.py b/scripts/o3de/o3de/register.py index 6ad54a11f3..8a2bb788aa 100644 --- a/scripts/o3de/o3de/register.py +++ b/scripts/o3de/o3de/register.py @@ -477,11 +477,11 @@ def register_repo(json_data: dict, parsed_uri = urllib.parse.urlparse(url) if parsed_uri.scheme in ['http', 'https', 'ftp', 'ftps']: - while repo_uri in json_data['repos']: + while repo_uri in json_data.get('repos', []): json_data['repos'].remove(repo_uri) else: repo_uri = pathlib.Path(repo_uri).resolve().as_posix() - while repo_uri in json_data['repos']: + while repo_uri in json_data.get('repos', []): json_data['repos'].remove(repo_uri) if remove: @@ -492,7 +492,7 @@ def register_repo(json_data: dict, result = utils.download_file(parsed_uri, cache_file) if result == 0: - json_data['repos'].insert(0, repo_uri) + json_data.setdefault('repos', []).insert(0, repo_uri) repo_set = set() result = repo.process_add_o3de_repo(cache_file, repo_set) diff --git a/scripts/o3de/o3de/repo.py b/scripts/o3de/o3de/repo.py index 5c47c2bee8..a6b1505761 100644 --- a/scripts/o3de/o3de/repo.py +++ b/scripts/o3de/o3de/repo.py @@ -13,7 +13,7 @@ import shutil import urllib.parse import urllib.request import hashlib - +from datetime import datetime from o3de import manifest, utils, validation logger = logging.getLogger() @@ -27,6 +27,7 @@ def process_add_o3de_repo(file_name: str or pathlib.Path, return 1 cache_folder = manifest.get_o3de_cache_folder() + repo_data = {} with file_name.open('r') as f: try: repo_data = json.load(f) @@ -34,83 +35,94 @@ def process_add_o3de_repo(file_name: str or pathlib.Path, logger.error(f'{file_name} failed to load: {str(e)}') return 1 - # A repo may not contain all types of object. - manifest_download_list = [] + with file_name.open('w') as f: try: - manifest_download_list.append((repo_data['engines'], 'engine.json')) - except KeyError: - pass - try: - manifest_download_list.append((repo_data['projects'], 'project.json')) - except KeyError: - pass - try: - manifest_download_list.append((repo_data['gems'], 'gem.json')) - except KeyError: - pass - try: - manifest_download_list.append((repo_data['templates'], 'template.json')) - except KeyError: - pass - try: - manifest_download_list.append((repo_data['restricted'], 'restricted.json')) - except KeyError: - pass + time_now = datetime.now() + # Convert to lower case because AM/PM is capitalized + time_str = time_now.strftime('%d/%m/%Y %I:%M%p').lower() + repo_data.update({'last_updated': time_str}) + f.write(json.dumps(repo_data, indent=4) + '\n') + except Exception as e: + logger.error(f'{file_name} failed to save: {str(e)}') + return 1 - for o3de_object_uris, manifest_json in manifest_download_list: + # A repo may not contain all types of object. + manifest_download_list = [] + try: + manifest_download_list.append((repo_data['engines'], 'engine.json')) + except KeyError: + pass + try: + manifest_download_list.append((repo_data['projects'], 'project.json')) + except KeyError: + pass + try: + manifest_download_list.append((repo_data['gems'], 'gem.json')) + except KeyError: + pass + try: + manifest_download_list.append((repo_data['templates'], 'template.json')) + except KeyError: + pass + try: + manifest_download_list.append((repo_data['restricted'], 'restricted.json')) + except KeyError: + pass + + for o3de_object_uris, manifest_json in manifest_download_list: + for o3de_object_uri in o3de_object_uris: + manifest_json_uri = f'{o3de_object_uri}/{manifest_json}' + manifest_json_sha256 = hashlib.sha256(manifest_json_uri.encode()) + cache_file = cache_folder / str(manifest_json_sha256.hexdigest() + '.json') + if not cache_file.is_file(): + parsed_uri = urllib.parse.urlparse(manifest_json_uri) + download_file_result = utils.download_file(parsed_uri, cache_file) + if download_file_result != 0: + return download_file_result + + # Having a repo is also optional + repo_list = [] + try: + repo_list.append(repo_data['repos']) + except KeyError: + pass + + for repo in repo_list: + if repo not in repo_set: + repo_set.add(repo) for o3de_object_uri in o3de_object_uris: - manifest_json_uri = f'{o3de_object_uri}/{manifest_json}' - manifest_json_sha256 = hashlib.sha256(manifest_json_uri.encode()) + parsed_uri = urllib.parse.urlparse(f'{repo}/repo.json') + manifest_json_sha256 = hashlib.sha256(parsed_uri.geturl().encode()) cache_file = cache_folder / str(manifest_json_sha256.hexdigest() + '.json') - if not cache_file.is_file(): - parsed_uri = urllib.parse.urlparse(manifest_json_uri) - download_file_result = utils.download_file(parsed_uri, cache_file) - if download_file_result != 0: - return download_file_result + if cache_file.is_file(): + cache_file.unlink() + download_file_result = utils.download_file(parsed_uri, cache_file) + if download_file_result != 0: + return download_file_result - # Having a repo is also optional - repo_list = [] - try: - repo_list.append(repo_data['repos']) - except KeyError: - pass - - for repo in repo_list: - if repo not in repo_set: - repo_set.add(repo) - for o3de_object_uri in o3de_object_uris: - parsed_uri = urllib.parse.urlparse(f'{repo}/repo.json') - manifest_json_sha256 = hashlib.sha256(parsed_uri.geturl().encode()) - cache_file = cache_folder / str(manifest_json_sha256.hexdigest() + '.json') - if cache_file.is_file(): - cache_file.unlink() - download_file_result = utils.download_file(parsed_uri, cache_file) - if download_file_result != 0: - return download_file_result - - return process_add_o3de_repo(parsed_uri.geturl(), repo_set) + return process_add_o3de_repo(parsed_uri.geturl(), repo_set) return 0 -def get_gem_json_paths_from_cached_repo(repo_uri: str) -> list: +def get_gem_json_paths_from_cached_repo(repo_uri: str) -> set: url = f'{repo_uri}/repo.json' repo_sha256 = hashlib.sha256(url.encode()) cache_folder = manifest.get_o3de_cache_folder() cache_filename = cache_folder / str(repo_sha256.hexdigest() + '.json') - gem_list = [] + gem_set = set() file_name = pathlib.Path(cache_filename).resolve() if not file_name.is_file(): logger.error(f'Could not find cached repo json file for {repo_uri}') - return gem_list + return gem_set with file_name.open('r') as f: try: repo_data = json.load(f) except json.JSONDecodeError as e: logger.error(f'{file_name} failed to load: {str(e)}') - return gem_list + return gem_set # Get list of gems, then add all json paths to the list if they exist in the cache repo_gems = [] @@ -125,10 +137,44 @@ def get_gem_json_paths_from_cached_repo(repo_uri: str) -> list: manifest_json_sha256 = hashlib.sha256(manifest_json_uri.encode()) cache_gem_json_filepath = cache_folder / str(manifest_json_sha256.hexdigest() + '.json') if cache_gem_json_filepath.is_file(): - logger.warn(f'Could not find cached gem json file for {o3de_object_uri} in repo {repo_uri}') - gem_list.append(cache_gem_json_filepath) + gem_set.add(cache_gem_json_filepath) + else: + logger.warn(f'Could not find cached gem json file {cache_gem_json_filepath} for {o3de_object_uri} in repo {repo_uri}') - return gem_list + return gem_set + +def get_gem_json_paths_from_all_cached_repos() -> set: + json_data = manifest.load_o3de_manifest() + gem_set = set() + + for repo_uri in json_data.get('repos', []): + gem_set.update(get_gem_json_paths_from_cached_repo(repo_uri)) + + return gem_set + + +def refresh_repo(repo_uri: str, + cache_folder: str = None, + repo_set: set = None) -> int: + if not cache_folder: + cache_folder = manifest.get_o3de_cache_folder() + if not repo_set: + repo_set = set() + + parsed_uri = urllib.parse.urlparse(f'{repo_uri}/repo.json') + repo_sha256 = hashlib.sha256(parsed_uri.geturl().encode()) + cache_file = cache_folder / str(repo_sha256.hexdigest() + '.json') + + download_file_result = utils.download_file(parsed_uri, cache_file) + if download_file_result != 0: + return download_file_result + + if not validation.valid_o3de_repo_json(cache_file): + logger.error(f'Repo json {repo_uri} is not valid.') + cache_file.unlink() + return 1 + + return process_add_o3de_repo(cache_file, repo_set) def refresh_repos() -> int: json_data = manifest.load_o3de_manifest() @@ -143,26 +189,13 @@ def refresh_repos() -> int: # set will stop circular references repo_set = set() - for repo_uri in json_data['repos']: + for repo_uri in json_data.get('repos', []): if repo_uri not in repo_set: repo_set.add(repo_uri) - parsed_uri = urllib.parse.urlparse(f'{repo_uri}/repo.json') - repo_sha256 = hashlib.sha256(parsed_uri.geturl().encode()) - cache_file = cache_folder / str(repo_sha256.hexdigest() + '.json') - if not cache_file.is_file(): - download_file_result = utils.download_file(parsed_uri, cache_file) - if download_file_result != 0: - return download_file_result - - if not validation.valid_o3de_repo_json(cache_file): - logger.error(f'Repo json {repo_uri} is not valid.') - cache_file.unlink() - return 1 - - last_failure = process_add_o3de_repo(cache_file, repo_set) - if last_failure: - result = last_failure + last_failure = refresh_repo(repo_uri, cache_folder, repo_set) + if last_failure: + result = last_failure return result diff --git a/scripts/o3de/o3de/utils.py b/scripts/o3de/o3de/utils.py index c5be21c60f..8663502a3f 100755 --- a/scripts/o3de/o3de/utils.py +++ b/scripts/o3de/o3de/utils.py @@ -10,6 +10,7 @@ This file contains utility functions """ import sys import uuid +import os import pathlib import shutil import urllib.request @@ -19,6 +20,29 @@ import zipfile logger = logging.getLogger() logging.basicConfig() +COPY_BUFSIZE = 64 * 1024 + +def copyfileobj(fsrc, fdst, callback, length=0): + # This is functionally the same as the python shutil copyfileobj but + # allows for a callback to return the download progress in blocks and allows + # to early out to cancel the copy. + if not length: + length = COPY_BUFSIZE + + fsrc_read = fsrc.read + fdst_write = fdst.write + + copied = 0 + while True: + buf = fsrc_read(length) + if not buf: + break + fdst_write(buf) + copied += len(buf) + if callback(copied): + return 1 + return 0 + def validate_identifier(identifier: str) -> bool: """ Determine if the identifier supplied is valid. @@ -93,18 +117,29 @@ def backup_folder(folder: str or pathlib.Path) -> None: if backup_folder_name.is_dir(): renamed = True - -def download_file(parsed_uri, download_path: pathlib.Path) -> int: +def download_file(parsed_uri, download_path: pathlib.Path, download_progress_callback = None) -> int: """ :param parsed_uri: uniform resource identifier to zip file to download :param download_path: location path on disk to download file + :download_progress_callback: callback called with the download progress as a percentage, returns true to request to cancel the download """ if download_path.is_file(): logger.warn(f'File already downloaded to {download_path}.') elif parsed_uri.scheme in ['http', 'https', 'ftp', 'ftps']: with urllib.request.urlopen(parsed_uri.geturl()) as s: + download_file_size = 0 + try: + download_file_size = s.headers['content-length'] + except KeyError: + pass + def download_progress(blocks): + if download_progress_callback and download_file_size: + return download_progress_callback(int(blocks/int(download_file_size) * 100)) + return False with download_path.open('wb') as f: - shutil.copyfileobj(s, f) + download_cancelled = copyfileobj(s, f, download_progress) + if download_cancelled: + return 1 else: origin_file = pathlib.Path(parsed_uri.geturl()).resolve() if not origin_file.is_file(): @@ -114,12 +149,12 @@ def download_file(parsed_uri, download_path: pathlib.Path) -> int: return 0 -def download_zip_file(parsed_uri, download_zip_path: pathlib.Path) -> int: +def download_zip_file(parsed_uri, download_zip_path: pathlib.Path, download_progress_callback = None) -> int: """ :param parsed_uri: uniform resource identifier to zip file to download :param download_zip_path: path to output zip file """ - download_file_result = download_file(parsed_uri, download_zip_path) + download_file_result = download_file(parsed_uri, download_zip_path, download_progress_callback) if download_file_result != 0: return download_file_result diff --git a/scripts/o3de/tests/unit_test_register.py b/scripts/o3de/tests/unit_test_register.py index 36139c0afa..954dd852f8 100644 --- a/scripts/o3de/tests/unit_test_register.py +++ b/scripts/o3de/tests/unit_test_register.py @@ -192,9 +192,9 @@ class TestRegisterGem: def test_register_gem_auto_detects_manifest_update(self, gem_path, expected_manifest_file,expected_result): def save_o3de_manifest(manifest_data: dict, manifest_path: pathlib.Path = None) -> bool: - if manifest_path == TestRegisterGem.project_path / 'project.json': + if manifest_path == pathlib.Path(TestRegisterGem.project_path).resolve() / 'project.json': self.project_data = manifest_data - elif manifest_path == TestRegisterGem.engine_path / 'engine.json': + elif manifest_path == pathlib.Path(TestRegisterGem.engine_path).resolve() / 'engine.json': self.engine_data = manifest_data else: self.o3de_manifest_data = manifest_data @@ -240,11 +240,11 @@ class TestRegisterGem: assert result == expected_result if expected_manifest_file == pathlib.PurePath('o3de_manifest.json'): - assert gem_path in map(lambda subdir: pathlib.PurePath(subdir), + assert pathlib.Path(gem_path).resolve() in map(lambda subdir: pathlib.PurePath(subdir), self.o3de_manifest_data.get('external_subdirectories', [])) elif expected_manifest_file == pathlib.PurePath('project.json'): - assert gem_path in map(lambda subdir: pathlib.PurePath(TestRegisterGem.project_path) / subdir, + assert gem_path in map(lambda subdir: TestRegisterGem.project_path / subdir, self.project_data.get('external_subdirectories', [])) elif expected_manifest_file == pathlib.PurePath('engine.json'): - assert gem_path in map(lambda subdir: pathlib.PurePath(TestRegisterGem.engine_path) / subdir, + assert gem_path in map(lambda subdir: TestRegisterGem.engine_path / subdir, self.engine_data.get('external_subdirectories', [])) diff --git a/scripts/scrubbing/validator_data_LEGAL_REVIEW_REQUIRED.py b/scripts/scrubbing/validator_data_LEGAL_REVIEW_REQUIRED.py index 666e04a2b0..0c008cbe65 100755 --- a/scripts/scrubbing/validator_data_LEGAL_REVIEW_REQUIRED.py +++ b/scripts/scrubbing/validator_data_LEGAL_REVIEW_REQUIRED.py @@ -100,14 +100,10 @@ def get_prohibited_platforms_for_package(package): def get_bypassed_directories(is_all): # Temporarily exempt folders to not fail validation while people is fixing validation errors, they will be removed once the errors are fixed. temp_bypass_directories = [ - 'commit_validation', - 'LauncherTestTools', - 'AutomatedTesting', - 'Atom' + 'commit_validation' ] bypassed_directories = [ - 'python', - 'AWSPythonSDK' + 'python' ] if not is_all: bypassed_directories.extend([ @@ -116,13 +112,7 @@ def get_bypassed_directories(is_all): 'Cache', 'logs', 'AssetProcessorTemp', - 'JenkinsScripts', - 'BuildLambdaFunctions', - 'layouts', - '.idea', 'user/log', - 'DirectXShaderCompiler', - 'v-hacd', 'External' ]) bypassed_directories.extend(temp_bypass_directories)