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/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/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 index 45298ed563..950bd44199 100644 --- a/AutomatedTesting/Gem/PythonTests/Atom/TestSuite_Main_Optimized.py +++ b/AutomatedTesting/Gem/PythonTests/Atom/TestSuite_Main_Optimized.py @@ -42,6 +42,10 @@ class TestAutomation(EditorTestSuite): 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 @@ -54,6 +58,10 @@ class TestAutomation(EditorTestSuite): 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 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..c365999335 100644 --- a/AutomatedTesting/Gem/PythonTests/Atom/atom_utils/atom_constants.py +++ b/AutomatedTesting/Gem/PythonTests/Atom/atom_utils/atom_constants.py @@ -218,6 +218,7 @@ class AtomComponentProperties: 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_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_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_PostFXLayerAdded.py b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_PostFXLayerAdded.py index a81864f379..efef4c0a43 100644 --- a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_PostFXLayerAdded.py +++ b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_PostFXLayerAdded.py @@ -70,8 +70,6 @@ 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 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..5e74d1e93b --- /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_Main + TEST_SUITE main + TEST_SERIAL + PATH ${CMAKE_CURRENT_LIST_DIR}/TestSuite_Main.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..9cecaa7fe8 --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/Multiplayer/TestSuite_Main.py @@ -0,0 +1,33 @@ +""" +Copyright (c) Contributors to the Open 3D Engine Project. +For complete copyright and license terms please see the LICENSE at the root of this distribution. + +SPDX-License-Identifier: Apache-2.0 OR MIT + +""" + +# 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) + + 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/asset_processor_tests/asset_processor_batch_dependency_tests.py b/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/asset_processor_batch_dependency_tests.py index 264f690534..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/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/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/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/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/Plugins/ComponentEntityEditorPlugin/SandboxIntegration.cpp b/Code/Editor/Plugins/ComponentEntityEditorPlugin/SandboxIntegration.cpp index 5b849dcbe7..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(); 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/Framework/AzCore/AzCore/Component/ComponentApplication.cpp b/Code/Framework/AzCore/AzCore/Component/ComponentApplication.cpp index dabf02e260..df8db79db0 100644 --- a/Code/Framework/AzCore/AzCore/Component/ComponentApplication.cpp +++ b/Code/Framework/AzCore/AzCore/Component/ComponentApplication.cpp @@ -215,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); } diff --git a/Code/Framework/AzCore/AzCore/Component/ComponentApplicationLifecycle.cpp b/Code/Framework/AzCore/AzCore/Component/ComponentApplicationLifecycle.cpp index 533d55ed27..8e15871bc0 100644 --- a/Code/Framework/AzCore/AzCore/Component/ComponentApplicationLifecycle.cpp +++ b/Code/Framework/AzCore/AzCore/Component/ComponentApplicationLifecycle.cpp @@ -56,15 +56,21 @@ namespace AZ::ComponentApplicationLifecycle } bool RegisterHandler(AZ::SettingsRegistryInterface& settingsRegistry, AZ::SettingsRegistryInterface::NotifyEventHandler& handler, - AZ::SettingsRegistryInterface::NotifyCallback callback, AZStd::string_view eventName) + 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; - if (!ValidateEvent(settingsRegistry, eventName)) + // 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 does is not a field of object "%.*s".)" + 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; diff --git a/Code/Framework/AzCore/AzCore/Component/ComponentApplicationLifecycle.h b/Code/Framework/AzCore/AzCore/Component/ComponentApplicationLifecycle.h index 5fc6c4b2c3..6f07c0da3f 100644 --- a/Code/Framework/AzCore/AzCore/Component/ComponentApplicationLifecycle.h +++ b/Code/Framework/AzCore/AzCore/Component/ComponentApplicationLifecycle.h @@ -14,7 +14,7 @@ namespace AZ::ComponentApplicationLifecycle { //! Root Key where lifecycle events should be registered under - inline constexpr AZStd::string_view ApplicationLifecycleEventRegistrationKey = "/O3DE/Runtime/Application/LifecycleEvents"; + inline constexpr AZStd::string_view ApplicationLifecycleEventRegistrationKey = "/O3DE/Application/LifecycleEvents"; //! Validates that the event @eventName is stored in the array at ApplicationLifecycleEventRegistrationKey @@ -48,7 +48,9 @@ namespace AZ::ComponentApplicationLifecycle //! 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); + 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/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/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/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 6a9e5a29d6..a5cc3fdcd4 100644 --- a/Code/Framework/AzCore/AzCore/azcore_files.cmake +++ b/Code/Framework/AzCore/AzCore/azcore_files.cmake @@ -108,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 @@ -456,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 8fb986802e..323e834413 100644 --- a/Code/Framework/AzFramework/AzFramework/Application/Application.cpp +++ b/Code/Framework/AzFramework/AzFramework/Application/Application.cpp @@ -204,7 +204,6 @@ namespace AzFramework systemEntity->Activate(); AZ_Assert(systemEntity->GetState() == AZ::Entity::State::Active, "System Entity failed to activate."); - if (m_isStarted = (systemEntity->GetState() == AZ::Entity::State::Active); m_isStarted) { if (m_startupParameters.m_loadAssetCatalog) 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/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/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/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/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 8831bef89c..11fd1e60d8 100644 --- a/Code/Framework/AzQtComponents/AzQtComponents/Components/ToastNotification.cpp +++ b/Code/Framework/AzQtComponents/AzQtComponents/Components/ToastNotification.cpp @@ -27,7 +27,6 @@ namespace AzQtComponents setProperty("HasNoWindowDecorations", true); setAttribute(Qt::WA_ShowWithoutActivating); - setAttribute(Qt::WA_DeleteOnClose); m_borderRadius = toastConfiguration.m_borderRadius; if (m_borderRadius > 0) diff --git a/Code/Framework/AzQtComponents/AzQtComponents/Components/ToastNotification.h b/Code/Framework/AzQtComponents/AzQtComponents/Components/ToastNotification.h index 4343f37df4..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(); @@ -73,7 +72,7 @@ namespace AzQtComponents 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/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/Application/ToolsApplication.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Application/ToolsApplication.cpp index 67563aaf69..f203251407 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Application/ToolsApplication.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Application/ToolsApplication.cpp @@ -426,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") 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/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 a6a80e58d3..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(); diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Notifications/ToastNotificationsView.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Notifications/ToastNotificationsView.cpp index a88711a9ed..277d3fa3c5 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Notifications/ToastNotificationsView.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Notifications/ToastNotificationsView.cpp @@ -129,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; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerListModel.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerListModel.cpp index 434a1d8303..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(); @@ -973,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) 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 5155de3087..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() 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/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/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/System.cpp b/Code/Legacy/CrySystem/System.cpp index 2e18e13841..eebe626918 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; @@ -697,31 +690,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 +1223,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..74ce1cbbc2 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; @@ -564,8 +543,6 @@ private: // ------------------------------------------------------ ESystemConfigSpec m_nMaxConfigSpec; ESystemConfigPlatform m_ConfigPlatform; - CProfilingSystem m_ProfilingSystem; - // Pause mode. bool m_bPaused; bool m_bNoUpdate; @@ -588,9 +565,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..7ce9fcd342 100644 --- a/Code/Legacy/CrySystem/SystemInit.cpp +++ b/Code/Legacy/CrySystem/SystemInit.cpp @@ -70,9 +70,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 @@ -173,8 +170,6 @@ void CryEngineSignalHandler(int signal) ////////////////////////////////////////////////////////////////////////// #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" @@ -188,8 +183,6 @@ void CryEngineSignalHandler(int signal) #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 @@ -445,18 +438,6 @@ AZStd::unique_ptr CSystem::LoadDLL(const char* dllName) 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; } @@ -497,13 +478,6 @@ 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(); @@ -649,7 +623,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 +678,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 +733,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 +753,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 +762,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 +1266,7 @@ AZ_POP_DISABLE_WARNING ////////////////////////////////////////////////////////////////////////// // Open basic pak files after intro movie playback started ////////////////////////////////////////////////////////////////////////// - OpenBasicPaks(); + OpenPlatformPaks(); ////////////////////////////////////////////////////////////////////////// // AUDIO @@ -1714,8 +1652,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/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/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 5e061491f0..367a296bd4 100644 --- a/Code/Tools/AssetProcessor/native/AssetManager/assetProcessorManager.cpp +++ b/Code/Tools/AssetProcessor/native/AssetManager/assetProcessorManager.cpp @@ -4027,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/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 6c19213414..0cbdc20f1c 100644 --- a/Code/Tools/AssetProcessor/native/tests/assetmanager/AssetProcessorManagerTest.cpp +++ b/Code/Tools/AssetProcessor/native/tests/assetmanager/AssetProcessorManagerTest.cpp @@ -241,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; @@ -333,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) @@ -387,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); } @@ -1311,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(); } @@ -1616,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)); @@ -1626,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. @@ -1944,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) @@ -2092,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) @@ -4138,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: @@ -4166,6 +4176,7 @@ struct LockedFileTest ModtimeScanningTest::TearDown(); } + AZStd::atomic_int m_deleteCounter{ 0 }; AZStd::function m_callback; }; @@ -4205,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); @@ -4217,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( @@ -4239,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 { @@ -4447,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(); @@ -4466,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)); @@ -4486,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) @@ -5310,6 +5337,18 @@ TEST_F(MetadataFileTest, MetadataFile_SourceFileExtensionDifferentCase) 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) { @@ -5318,15 +5357,18 @@ bool WildcardSourceDependencyTest::Test( AssetBuilderSDK::SourceFileDependency dependency(dependencyPath, AZ::Uuid::CreateNull(), AssetBuilderSDK::SourceFileDependency::SourceFileDependencyType::Wildcards); bool result = m_assetProcessorManager->ResolveSourceFileDependencyPath(dependency, resolvedName, stringlistPaths); - // Convert to a vector of AZStd::strings because GTest handles this type better when displaying errors - for (const QString& resolvedPath : stringlistPaths) - { - resolvedPaths.emplace_back(resolvedPath.toUtf8().constData()); - } + 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(); @@ -5349,6 +5391,42 @@ void WildcardSourceDependencyTest::SetUp() // 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) @@ -5447,3 +5525,30 @@ TEST_F(WildcardSourceDependencyTest, Absolute_NoWildcard) 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 94cc997938..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; @@ -135,6 +134,7 @@ struct WildcardSourceDependencyTest : AssetProcessorManagerTest { bool Test(const AZStd::string& dependencyPath, AZStd::vector& resolvedPaths); + AZStd::vector FileAddedTest(const QString& path); void SetUp() override; }; 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/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/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/Resources/ProjectManager.qss b/Code/Tools/ProjectManager/Resources/ProjectManager.qss index fecdc67920..d3ec066be7 100644 --- a/Code/Tools/ProjectManager/Resources/ProjectManager.qss +++ b/Code/Tools/ProjectManager/Resources/ProjectManager.qss @@ -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; 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/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/GemCatalogScreen.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp index a22f41d054..98121d7cd2 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp @@ -80,7 +80,7 @@ namespace O3DE::ProjectManager void GemCatalogScreen::ReinitForProject(const QString& projectPath) { - m_gemModel->clear(); + m_gemModel->Clear(); m_gemsToRegisterWithProject.clear(); FillModel(projectPath); @@ -145,10 +145,11 @@ namespace O3DE::ProjectManager } } - void GemCatalogScreen::OnGemStatusChanged(const QModelIndex& modelIndex, uint32_t numChangedDependencies) + 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); @@ -233,7 +234,11 @@ namespace O3DE::ProjectManager const QVector allRepoGemInfos = allRepoGemInfosResult.GetValue(); for (const GemInfo& gemInfo : allRepoGemInfos) { - m_gemModel->AddGem(gemInfo); + // do not add gems that have already been downloaded + if (!m_gemModel->FindIndexByNameString(gemInfo.m_name).isValid()) + { + m_gemModel->AddGem(gemInfo); + } } } else @@ -257,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.", diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.h b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.h index 1ade87af0c..1b34019d1a 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.h +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.h @@ -46,7 +46,7 @@ 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: 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/GemRepo/GemRepoItemDelegate.cpp b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoItemDelegate.cpp index 576a4aff6e..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); @@ -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,13 +135,8 @@ 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; @@ -163,17 +154,10 @@ 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, buttonRect); + 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; @@ -201,50 +185,15 @@ 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 QRect& buttonRect) const + QRect GemRepoItemDelegate::CalcRefreshButtonRect(const QRect& contentRect) const { - const int topLeftX = buttonRect.left() + s_buttonWidth + s_buttonSpacing + s_nameMaxWidth + s_creatorMaxWidth + s_updatedMaxWidth + s_contentSpacing * 2 + s_refreshIconSpacing; + 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)); } diff --git a/Code/Tools/ProjectManager/Source/GemRepo/GemRepoItemDelegate.h b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoItemDelegate.h index 69f2eb582d..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; @@ -75,8 +67,7 @@ namespace O3DE::ProjectManager QRect GetTextRect(QFont& font, const QString& text, qreal fontSize) const; QRect CalcButtonRect(const QRect& contentRect) const; QRect CalcDeleteButtonRect(const QRect& contentRect) const; - QRect CalcRefreshButtonRect(const QRect& contentRect, const QRect& buttonRect) 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/GemRepoScreen.cpp b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoScreen.cpp index 490b509474..0ddfe41434 100644 --- a/Code/Tools/ProjectManager/Source/GemRepo/GemRepoScreen.cpp +++ b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoScreen.cpp @@ -289,23 +289,22 @@ 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); 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/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/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/Atom/Bootstrap/Code/Source/BootstrapSystemComponent.cpp b/Gems/Atom/Bootstrap/Code/Source/BootstrapSystemComponent.cpp index 3f5761270a..84fc58718c 100644 --- a/Gems/Atom/Bootstrap/Code/Source/BootstrapSystemComponent.cpp +++ b/Gems/Atom/Bootstrap/Code/Source/BootstrapSystemComponent.cpp @@ -10,6 +10,7 @@ #include #include +#include #include #include #include @@ -132,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) @@ -143,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() @@ -153,7 +167,6 @@ namespace AZ AzFramework::WindowSystemRequestBus::Handler::BusDisconnect(); AzFramework::WindowSystemNotificationBus::Handler::BusDisconnect(); TickBus::Handler::BusDisconnect(); - AzFramework::AssetCatalogEventBus::Handler::BusDisconnect(); m_brdfTexture = nullptr; RemoveRenderPipeline(); @@ -164,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()) { @@ -216,7 +229,7 @@ namespace AZ { m_windowHandle = windowHandle; - if (m_isAssetCatalogLoaded) + if (m_isInitialized) { CreateWindowContext(); if (m_createDefaultScene) @@ -259,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 d36213694b..5de756067a 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR.materialtype +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR.materialtype @@ -1574,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": { @@ -1649,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 e0b1949058..f324394309 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR.materialtype +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR.materialtype @@ -1104,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": { @@ -1179,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_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/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/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/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 ed2d8a1d9f..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); 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 c79ceb39ba..d99ce211bf 100644 --- a/Gems/Atom/Feature/Common/Code/Source/Material/MaterialAssignment.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/Material/MaterialAssignment.cpp @@ -143,28 +143,34 @@ namespace AZ { bool wasRenamed = false; Name newName; - RPI::MaterialPropertyIndex materialPropertyIndex = m_materialInstance->FindPropertyIndex(propertyPair.first, &wasRenamed, &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, + // 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()); + 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()); + + 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)); } } } @@ -284,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/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/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/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/Pass/Pass.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Pass/Pass.h index e7b9825ecb..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; @@ -369,6 +373,9 @@ namespace AZ void UpdateReadbackAttachment(FramePrepareParams params, bool beforeAddScopes); + // Setup ImageAttachmentCopy + void UpdateAttachmentCopy(FramePrepareParams params); + // --- Protected Members --- const Name PassNameThis{"This"}; @@ -466,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/PassLibrary.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Pass/PassLibrary.h index 66c3c205ab..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); 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 8390b0f7e2..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 @@ -94,6 +94,7 @@ namespace AZ bool HasPassesForTemplateName(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; 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 7f944df88f..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 @@ -199,6 +199,9 @@ namespace AZ //! 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; 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 5fb90d044e..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: @@ -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/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/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/MaterialTypeSourceData.cpp b/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialTypeSourceData.cpp index 08f57c7cd3..b208a49111 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialTypeSourceData.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialTypeSourceData.cpp @@ -300,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/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/Pass.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/Pass.cpp index 3c1de28d6a..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 @@ -1215,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; } @@ -1301,6 +1308,9 @@ namespace AZ // readback attachment with output state UpdateReadbackAttachment(params, false); + // update attachment copy for preview + UpdateAttachmentCopy(params); + UpdateConnectedOutputBindings(); } @@ -1489,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/PassLibrary.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/PassLibrary.cpp index 6a6f5f3ff9..13b2fa391f 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/PassLibrary.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/PassLibrary.cpp @@ -236,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) 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 7f2948c13a..39d0e879e1 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/PassSystem.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/PassSystem.cpp @@ -466,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); 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 b8115dff10..8353762c0f 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/RenderPass.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/RenderPass.cpp @@ -177,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) @@ -196,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(); 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 74d52fd64d..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) { 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/Shader/ShaderAsset.cpp b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Shader/ShaderAsset.cpp index 84757d58ab..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; } 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/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/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/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/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/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/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/Code/Include/AtomLyIntegration/CommonFeatures/Material/MaterialComponentBus.h b/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/Material/MaterialComponentBus.h index 293080367d..d8d0c2dbc3 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/Material/MaterialComponentBus.h +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/Material/MaterialComponentBus.h @@ -60,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 @@ -122,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 94beeb2430..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") 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 9af3b41b12..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); } } 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 07082a87d5..0ccdae28de 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/MaterialComponentController.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/MaterialComponentController.cpp @@ -54,29 +54,29 @@ namespace AZ ->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) @@ -121,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); } @@ -221,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); } } @@ -268,8 +278,6 @@ namespace AZ { materialPair.second.Release(); } - - MaterialComponentNotificationBus::Event(m_entityId, &MaterialComponentNotifications::OnMaterialsUpdated, m_configuration.m_materials); } MaterialAssignmentMap MaterialComponentController::GetOriginalMaterialAssignments() const @@ -499,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); @@ -586,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 ec542c377c..74b1cfda4d 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/MaterialComponentController.h +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/MaterialComponentController.h @@ -65,33 +65,8 @@ namespace AZ 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/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/Code/Source/AtomActorDebugDraw.cpp b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorDebugDraw.cpp index 1325455cd9..50af91020a 100644 --- a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorDebugDraw.cpp +++ b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorDebugDraw.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -40,16 +41,18 @@ namespace AZ::Render return; } + const AZ::Render::RenderActorSettings& renderActorSettings = EMotionFX::GetRenderActorSettings(); + // Render aabb if (renderFlags[EMotionFX::ActorRenderFlag::RENDER_AABB]) { - RenderAABB(instance); + RenderAABB(instance, renderActorSettings.m_staticAABBColor); } // Render skeleton if (renderFlags[EMotionFX::ActorRenderFlag::RENDER_LINESKELETON]) { - RenderSkeleton(instance); + RenderSkeleton(instance, renderActorSettings.m_skeletonColor); } // Render internal EMFX debug lines. @@ -70,6 +73,7 @@ namespace AZ::Render 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)); @@ -83,19 +87,29 @@ namespace AZ::Render continue; } - RenderNormals(mesh, globalTM, renderVertexNormals, renderFaceNormals); + RenderNormals(mesh, globalTM, renderVertexNormals, renderFaceNormals, renderActorSettings.m_vertexNormalsScale, + renderActorSettings.m_faceNormalsScale, scaleMultiplier, renderActorSettings.m_vertexNormalsColor, renderActorSettings.m_faceNormalsColor); if (renderTangents) { - RenderTangents(mesh, globalTM); + RenderTangents(mesh, globalTM, renderActorSettings.m_tangentsScale, scaleMultiplier, + renderActorSettings.m_tangentsColor, renderActorSettings.m_mirroredBitangentsColor, renderActorSettings.m_bitangentsColor); } if (renderWireframe) { - RenderWireframe(mesh, globalTM); + 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 @@ -124,14 +138,14 @@ namespace AZ::Render } } - void AtomActorDebugDraw::RenderAABB(EMotionFX::ActorInstance* instance) + 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, AZ::Color(0.0f, 1.0f, 1.0f, 1.0f), RPI::AuxGeomDraw::DrawStyle::Line); + auxGeom->DrawAabb(aabb, aabbColor, RPI::AuxGeomDraw::DrawStyle::Line); } - void AtomActorDebugDraw::RenderSkeleton(EMotionFX::ActorInstance* instance) + void AtomActorDebugDraw::RenderSkeleton(EMotionFX::ActorInstance* instance, const AZ::Color& skeletonColor) { RPI::AuxGeomDrawPtr auxGeom = m_auxGeomFeatureProcessor->GetDrawQueue(); @@ -166,7 +180,6 @@ namespace AZ::Render 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()); @@ -218,7 +231,16 @@ namespace AZ::Render auxGeom->DrawLines(lineArgs); } - void AtomActorDebugDraw::RenderNormals(EMotionFX::Mesh* mesh, const AZ::Transform& worldTM, bool vertexNormals, bool faceNormals) + 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) { @@ -236,12 +258,6 @@ namespace AZ::Render return; } - // TODO: Move line color to a render setting. - const float faceNormalsScale = 0.01f; - const AZ::Color colorFaceNormals = AZ::Colors::Lime; - const float vertexNormalsScale = 0.01f; - const AZ::Color colorVertexNormals = AZ::Colors::Orange; - PrepareForMesh(mesh, worldTM); AZ::Vector3* normals = (AZ::Vector3*)mesh->FindVertexData(EMotionFX::Mesh::ATTRIB_NORMALS); @@ -277,14 +293,14 @@ namespace AZ::Render const AZ::Vector3 normalPos = (posA + posB + posC) * (1.0f / 3.0f); m_auxVertices.emplace_back(normalPos); - m_auxVertices.emplace_back(normalPos + (normalDir * faceNormalsScale)); + 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 = &colorFaceNormals; + lineArgs.m_colors = &faceNormalsColor; lineArgs.m_colorCount = 1; lineArgs.m_depthTest = RPI::AuxGeomDraw::DepthTest::Off; auxGeom->DrawLines(lineArgs); @@ -307,7 +323,8 @@ namespace AZ::Render { const uint32 vertexIndex = j + startVertex; const AZ::Vector3& position = m_worldSpacePositions[vertexIndex]; - const AZ::Vector3 normal = worldTM.TransformVector(normals[vertexIndex]).GetNormalizedSafe() * vertexNormalsScale; + const AZ::Vector3 normal = worldTM.TransformVector(normals[vertexIndex]).GetNormalizedSafe() * + vertexNormalsScale * scaleMultiplier; m_auxVertices.emplace_back(position); m_auxVertices.emplace_back(position + normal); @@ -317,14 +334,21 @@ namespace AZ::Render RPI::AuxGeomDraw::AuxGeomDynamicDrawArguments lineArgs; lineArgs.m_verts = m_auxVertices.data(); lineArgs.m_vertCount = static_cast(m_auxVertices.size()); - lineArgs.m_colors = &colorVertexNormals; + 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) + 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) { @@ -337,12 +361,6 @@ namespace AZ::Render return; } - // TODO: Move line color to a render setting. - const AZ::Color colorTangents = AZ::Colors::Red; - const AZ::Color mirroredBitangentColor = AZ::Colors::Yellow; - const AZ::Color colorBitangents = AZ::Colors::White; - const float scale = 0.01f; - // Get the tangents and check if this mesh actually has tangents AZ::Vector4* tangents = static_cast(mesh->FindVertexData(EMotionFX::Mesh::ATTRIB_TANGENTS)); if (!tangents) @@ -380,23 +398,23 @@ namespace AZ::Render bitangent = (worldTM.TransformVector(bitangent)).GetNormalizedSafe(); m_auxVertices.emplace_back(m_worldSpacePositions[i]); - m_auxColors.emplace_back(colorTangents); - m_auxVertices.emplace_back(m_worldSpacePositions[i] + (tangent * scale)); - m_auxColors.emplace_back(colorTangents); + 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(mirroredBitangentColor); - m_auxVertices.emplace_back(m_worldSpacePositions[i] + (bitangent * scale)); - m_auxColors.emplace_back(mirroredBitangentColor); + 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(colorBitangents); - m_auxVertices.emplace_back(m_worldSpacePositions[i] + (bitangent * scale)); - m_auxColors.emplace_back(colorBitangents); + m_auxColors.emplace_back(bitangentsColor); + m_auxVertices.emplace_back(m_worldSpacePositions[i] + (bitangent * tangentsScale * scaleMultiplier)); + m_auxColors.emplace_back(bitangentsColor); } } @@ -409,7 +427,8 @@ namespace AZ::Render auxGeom->DrawLines(lineArgs); } - void AtomActorDebugDraw::RenderWireframe(EMotionFX::Mesh* mesh, const AZ::Transform& worldTM) + 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) @@ -425,10 +444,7 @@ namespace AZ::Render PrepareForMesh(mesh, worldTM); - const float scale = 0.01f; - const AZ::Vector3* normals = (AZ::Vector3*)mesh->FindVertexData(EMotionFX::Mesh::ATTRIB_NORMALS); - const AZ::Color vertexColor = AZ::Color(0.8f, 0.24f, 0.88f, 1.0f); const size_t numSubMeshes = mesh->GetNumSubMeshes(); for (uint32 subMeshIndex = 0; subMeshIndex < numSubMeshes; ++subMeshIndex) @@ -448,9 +464,9 @@ namespace AZ::Render const uint32 indexB = indices[triangleStartIndex + 1] + startVertex; const uint32 indexC = indices[triangleStartIndex + 2] + startVertex; - const AZ::Vector3 posA = m_worldSpacePositions[indexA] + normals[indexA] * scale; - const AZ::Vector3 posB = m_worldSpacePositions[indexB] + normals[indexB] * scale; - const AZ::Vector3 posC = m_worldSpacePositions[indexC] + normals[indexC] * scale; + 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); @@ -465,7 +481,7 @@ namespace AZ::Render RPI::AuxGeomDraw::AuxGeomDynamicDrawArguments lineArgs; lineArgs.m_verts = m_auxVertices.data(); lineArgs.m_vertCount = static_cast(m_auxVertices.size()); - lineArgs.m_colors = &vertexColor; + lineArgs.m_colors = &wireframeColor; lineArgs.m_colorCount = 1; lineArgs.m_depthTest = RPI::AuxGeomDraw::DepthTest::Off; auxGeom->DrawLines(lineArgs); diff --git a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorDebugDraw.h b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorDebugDraw.h index 797de16351..4178ad25f1 100644 --- a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorDebugDraw.h +++ b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorDebugDraw.h @@ -37,13 +37,26 @@ namespace AZ::Render private: + float CalculateScaleMultiplier(EMotionFX::ActorInstance* instance) const; void PrepareForMesh(EMotionFX::Mesh* mesh, const AZ::Transform& worldTM); - void RenderAABB(EMotionFX::ActorInstance* instance); - void RenderSkeleton(EMotionFX::ActorInstance* instance); + 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); - void RenderTangents(EMotionFX::Mesh* mesh, const AZ::Transform& worldTM); - void RenderWireframe(EMotionFX::Mesh* mesh, const AZ::Transform& worldTM); + 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. */ diff --git a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AnimViewportRenderer.cpp b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AnimViewportRenderer.cpp index facf7a7b12..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 = 20.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.5f, 0.5f, 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); @@ -227,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); } @@ -326,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 a4f67ddfd1..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(); @@ -83,6 +85,7 @@ namespace EMStudio AZ::Entity* m_iblEntity = nullptr; 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/AnimViewportWidget.cpp b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AnimViewportWidget.cpp index c6e349236f..cf7341e9ab 100644 --- a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AnimViewportWidget.cpp +++ b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AnimViewportWidget.cpp @@ -17,11 +17,13 @@ #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); @@ -32,7 +34,7 @@ namespace EMStudio setAutoFillBackground(false); setStyleSheet(QString::fromUtf8("")); - m_renderer = AZStd::make_unique(GetViewportContext()); + m_renderer = AZStd::make_unique(GetViewportContext(), m_plugin->GetRenderOptions()); LoadRenderFlags(); SetupCameras(); @@ -181,8 +183,10 @@ namespace EMStudio 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::Constants::HalfPi, aspectRatio, DepthNear, DepthFar, true); + AZ::MakePerspectiveFovMatrixRH(viewToClipMatrix, AZ::DegToRad(renderOptions->GetFOV()), aspectRatio, + renderOptions->GetNearClipPlaneDistance(), renderOptions->GetFarClipPlaneDistance(), true); viewportContext->GetDefaultView()->SetViewToClipMatrix(viewToClipMatrix); } diff --git a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AnimViewportWidget.h b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AnimViewportWidget.h index e2099ea2ab..7a8ae2cf52 100644 --- a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AnimViewportWidget.h +++ b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AnimViewportWidget.h @@ -15,6 +15,7 @@ namespace EMStudio { + class AtomRenderPlugin; class AnimViewportRenderer; class AnimViewportWidget @@ -22,7 +23,7 @@ namespace EMStudio , private AnimViewportRequestBus::Handler { public: - AnimViewportWidget(QWidget* parent = nullptr); + AnimViewportWidget(AtomRenderPlugin* parentPlugin); ~AnimViewportWidget() override; AnimViewportRenderer* GetAnimViewportRenderer() { return m_renderer.get(); } @@ -45,9 +46,8 @@ namespace EMStudio void ToggleRenderFlag(EMotionFX::ActorRenderFlag flag); static constexpr float CameraDistance = 2.0f; - static constexpr float DepthNear = 0.01f; - static constexpr float DepthFar = 100.0f; + AtomRenderPlugin* m_plugin; AZStd::unique_ptr m_renderer; AZStd::shared_ptr m_rotateCamera; AZStd::shared_ptr m_translateCamera; diff --git a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AtomRenderPlugin.cpp b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AtomRenderPlugin.cpp index ce2e76a6c7..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); @@ -91,7 +101,7 @@ namespace EMStudio verticalLayout->setMargin(0); // Add the viewport widget - m_animViewportWidget = new AnimViewportWidget(m_innerWidget); + m_animViewportWidget = new AnimViewportWidget(this); // Add the tool bar AnimViewportToolBar* toolBar = new AnimViewportToolBar(m_innerWidget); @@ -109,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/AtomTressFX/Code/Rendering/HairFeatureProcessor.h b/Gems/AtomTressFX/Code/Rendering/HairFeatureProcessor.h index 70e37a7863..8c19b706a9 100644 --- a/Gems/AtomTressFX/Code/Rendering/HairFeatureProcessor.h +++ b/Gems/AtomTressFX/Code/Rendering/HairFeatureProcessor.h @@ -217,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/Blast/Code/Source/Components/BlastSystemComponent.cpp b/Gems/Blast/Code/Source/Components/BlastSystemComponent.cpp index 00629eb65d..4c0f15463a 100644 --- a/Gems/Blast/Code/Source/Components/BlastSystemComponent.cpp +++ b/Gems/Blast/Code/Source/Components/BlastSystemComponent.cpp @@ -255,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/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/Source/Integration/Components/ActorComponent.cpp b/Gems/EMotionFX/Code/Source/Integration/Components/ActorComponent.cpp index 8520896dd2..e38e15d124 100644 --- a/Gems/EMotionFX/Code/Source/Integration/Components/ActorComponent.cpp +++ b/Gems/EMotionFX/Code/Source/Integration/Components/ActorComponent.cpp @@ -576,7 +576,7 @@ namespace EMotionFX // 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_SKELETON] = m_debugRenderFlags[RENDER_SKELETON] || m_configuration.m_renderSkeleton; + 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/Editor/Components/EditorActorComponent.cpp b/Gems/EMotionFX/Code/Source/Integration/Editor/Components/EditorActorComponent.cpp index d0274b3c68..78470a1fa5 100644 --- a/Gems/EMotionFX/Code/Source/Integration/Editor/Components/EditorActorComponent.cpp +++ b/Gems/EMotionFX/Code/Source/Integration/Editor/Components/EditorActorComponent.cpp @@ -606,7 +606,7 @@ namespace EMotionFX m_renderActorInstance->UpdateBounds(); m_debugRenderFlags[RENDER_AABB] = m_renderBounds; - m_debugRenderFlags[RENDER_SKELETON] = m_renderSkeleton; + m_debugRenderFlags[RENDER_LINESKELETON] = m_renderSkeleton; m_debugRenderFlags[RENDER_EMFX_DEBUG] = true; m_renderActorInstance->DebugDraw(m_debugRenderFlags); } 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/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/LmbrCentral.cpp b/Gems/LmbrCentral/Code/Source/LmbrCentral.cpp index f167be8b84..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 @@ -354,7 +353,6 @@ namespace LmbrCentral // Add asset types and extensions to AssetCatalog. Uses "AssetCatalogService". 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()); @@ -368,7 +366,6 @@ namespace LmbrCentral assetCatalog->AddExtension("xml"); assetCatalog->AddExtension("mtl"); assetCatalog->AddExtension("dccmtl"); - assetCatalog->AddExtension("lua"); assetCatalog->AddExtension("sprite"); assetCatalog->AddExtension("cax"); } 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/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/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 d2b0ca7095..3c3c4f0664 100644 --- a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.cpp +++ b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.cpp @@ -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,33 +34,37 @@ 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 editorsv_port = DefaultServerEditorPort; - const auto console = AZ::Interface::Get(); - if (console->GetCvarValue("editorsv_port", editorsv_port) != AZ::GetValueResult::Success) - { - AZ_Assert( false, - "MultiplayerEditorConnection failed! Could not find the editorsv_port cvar; we may not be able to connect to the editor's port! Please update this code to use a valid cvar!") - } + return; + } + m_isActivated = true; + + AZ_Assert(m_networkEditorInterface, "MP Editor Network Interface was unregistered before Editor Server could start listening.") - 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)); - // 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); - } - else - { - m_networkEditorInterface->SendReliablePacket(editorServerToEditorConnectionId, MultiplayerEditorPackets::EditorServerReadyForLevelData()); - } + // 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, @@ -136,7 +141,7 @@ namespace Multiplayer 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()); } @@ -180,8 +185,14 @@ namespace Multiplayer } // Connect the Editor to the editor server for Multiplayer simulation - AZ::Interface::Get()->Connect(editorsv_serveraddr.c_str(), sv_port); - + 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; } diff --git a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.h b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.h index 3828d751f0..f6510896fe 100644 --- a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.h +++ b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.h @@ -45,9 +45,11 @@ namespace Multiplayer //! @} 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 2651981bce..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) @@ -147,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; @@ -159,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; } @@ -231,7 +298,7 @@ namespace Multiplayer "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 < uint16_t>(editorsv_port)) + static_cast(editorsv_port)) return; } @@ -261,6 +328,8 @@ namespace Multiplayer return; } + AZ_Printf("MultiplayerEditor", "Editor is sending the editor-server the level data packet.") + const AZStd::vector>& assetData = prefabEditorEntityOwnershipInterface->GetPlayInEditorAssetData(); AZStd::vector buffer; @@ -311,4 +380,13 @@ namespace Multiplayer } } + 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 a008afc873..77b41a5dc4 100644 --- a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.h +++ b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.h @@ -10,6 +10,7 @@ #include #include +#include #include #include @@ -29,9 +30,24 @@ 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 @@ -62,6 +78,12 @@ namespace Multiplayer void NotifyRegisterViews() override; //! @} + //! MultiplayerEditorLayerPythonRequestBus::Handler overrides. + //! @{ + void EnterGameMode() override; + bool IsInGameMode() override; + //! @} + private: //! EditorEvents::Handler overrides //! @{ diff --git a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp index fdd7602f71..6df5e4611f 100644 --- a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp +++ b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp @@ -719,11 +719,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) { @@ -737,7 +732,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); } @@ -771,15 +771,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) { @@ -1130,7 +1129,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/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/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 3e77ef257a..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 }; - [[maybe_unused]] 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/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/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/Shaders/Terrain/TerrainPBR_ForwardPass.azsl b/Gems/Terrain/Assets/Shaders/Terrain/TerrainPBR_ForwardPass.azsl index a15ad931f6..5a43fe1c37 100644 --- a/Gems/Terrain/Assets/Shaders/Terrain/TerrainPBR_ForwardPass.azsl +++ b/Gems/Terrain/Assets/Shaders/Terrain/TerrainPBR_ForwardPass.azsl @@ -23,11 +23,9 @@ 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; @@ -50,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 @@ -78,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; 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/TerrainRenderer/Components/TerrainMacroMaterialComponent.cpp b/Gems/Terrain/Code/Source/TerrainRenderer/Components/TerrainMacroMaterialComponent.cpp index 0d161b6b2b..a65cbad50b 100644 --- a/Gems/Terrain/Code/Source/TerrainRenderer/Components/TerrainMacroMaterialComponent.cpp +++ b/Gems/Terrain/Code/Source/TerrainRenderer/Components/TerrainMacroMaterialComponent.cpp @@ -248,6 +248,7 @@ namespace Terrain { 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(); @@ -256,6 +257,7 @@ namespace Terrain { 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(); diff --git a/Gems/Terrain/Code/Source/TerrainRenderer/TerrainFeatureProcessor.cpp b/Gems/Terrain/Code/Source/TerrainRenderer/TerrainFeatureProcessor.cpp index 571d109fd8..0ed70d949f 100644 --- a/Gems/Terrain/Code/Source/TerrainRenderer/TerrainFeatureProcessor.cpp +++ b/Gems/Terrain/Code/Source/TerrainRenderer/TerrainFeatureProcessor.cpp @@ -218,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 || @@ -228,16 +230,11 @@ 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; } @@ -778,32 +775,43 @@ namespace Terrain void TerrainFeatureProcessor::UpdateTerrainData() { - 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."); + + // 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). @@ -815,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()); @@ -839,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(); @@ -1106,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; @@ -1123,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()); @@ -1138,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); @@ -1167,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())); diff --git a/Gems/Terrain/Code/Source/TerrainRenderer/TerrainFeatureProcessor.h b/Gems/Terrain/Code/Source/TerrainRenderer/TerrainFeatureProcessor.h index 962ffe13bf..9b66881ec6 100644 --- a/Gems/Terrain/Code/Source/TerrainRenderer/TerrainFeatureProcessor.h +++ b/Gems/Terrain/Code/Source/TerrainRenderer/TerrainFeatureProcessor.h @@ -71,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 @@ -303,10 +303,6 @@ 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 }; 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/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 index 58baf267b0..0d9cd0f170 100644 --- a/Registry/application_lifecycle_events.setreg +++ b/Registry/application_lifecycle_events.setreg @@ -7,24 +7,23 @@ // related to the event { "O3DE" : { - "Runtime": { - "Application": { - "LifecycleEvents": { - "SystemComponentsActivated": {}, - "SystemComponentsDeactivated": {}, - "ReflectionManagerAvailable": {}, - "ReflectionManagerUnavailable": {}, - "SystemAllocatorCreated": {}, - "SystemAllocatorPendingDestruction": {}, - "SettingsRegistryAvailable": {}, - "SettingsRegistryUnavailable": {}, - "ConsoleAvailable": {}, - "ConsoleUnavailable": {}, - "GemsLoaded": {}, - "GemsUnloaded": {}, - "FileIOAvailable": {}, - "FileIOUnavailable": {} - } + "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 84a708989a..9735907a6a 100644 --- a/Templates/CMakeLists.txt +++ b/Templates/CMakeLists.txt @@ -9,8 +9,8 @@ ly_install_directory( DIRECTORIES AssetGem - CustomTool - PythonGem + 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/PythonGem/Template/Editor/Scripts/bootstrap.py b/Templates/PythonGem/Template/Editor/Scripts/bootstrap.py deleted file mode 100644 index 060116d36c..0000000000 --- a/Templates/PythonGem/Template/Editor/Scripts/bootstrap.py +++ /dev/null @@ -1,117 +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 -""" -# ------------------------------------------------------------------------- -"""${SanitizedCppName}\\editor\\scripts\\boostrap.py -Generated from O3DE PythonGem Template""" - -import azlmbr -import az_qt_helpers -from PySide2 import QtCore, QtWidgets, QtGui -from PySide2.QtCore import QEvent, Qt -from PySide2.QtWidgets import QMainWindow, QAction, QDialog, QHeaderView, QLabel, QLineEdit, QPushButton, QSplitter, QTreeWidget, QTreeWidgetItem, QWidget, QAbstractButton -# ------------------------------------------------------------------------- - - -# ------------------------------------------------------------------------- -class SampleUI(QtWidgets.QDialog): - """Lightweight UI Test Class created a button""" - def __init__(self, parent, title='Not Set'): - super(SampleUI, self).__init__(parent) - self.setWindowTitle(title) - self.initUI() - - def initUI(self): - mainLayout = QtWidgets.QHBoxLayout() - testBtn = QtWidgets.QPushButton("I am just a Button man!") - mainLayout.addWidget(testBtn) - self.setLayout(mainLayout) -# ------------------------------------------------------------------------- - -if __name__ == "__main__": - print("${SanitizedCppName}.boostrap, Generated from O3DE PythonGem Template") - - # --------------------------------------------------------------------- - # validate pyside before continuing - try: - azlmbr.qt.QtForPythonRequestBus(azlmbr.bus.Broadcast, 'IsActive') - params = azlmbr.qt.QtForPythonRequestBus(azlmbr.bus.Broadcast, 'GetQtBootstrapParameters') - params is not None and params.mainWindowId is not 0 - from PySide2 import QtWidgets - except Exception as e: - _LOGGER.error(f'Pyside not available, exception: {e}') - raise e - - # keep going, import the other PySide2 bits we will use - from PySide2 import QtGui - from PySide2.QtCore import Slot - from shiboken2 import wrapInstance, getCppPointer - - # Get our Editor main window - _widget_main_window = None - try: - _widget_main_window = az_qt_helpers.get_editor_main_window() - except: - pass # may be booting in the AP? - # --------------------------------------------------------------------- - - - # --------------------------------------------------------------------- - if _widget_main_window: - # creat a custom menu - _tag_str = '${SanitizedCppName}' - - # create our own menuBar - ${SanitizedCppName}_menu = _widget_main_window.menuBar().addMenu(f"&{_tag_str}") - - # nest a menu for util/tool launching - ${SanitizedCppName}_launch_menu = ${SanitizedCppName}_menu.addMenu("examples") - else: - print('No O3DE MainWindow') - # --------------------------------------------------------------------- - - - # --------------------------------------------------------------------- - if _widget_main_window: - # (1) add the first SampleUI - action_launch_sample_ui = ${SanitizedCppName}_launch_menu.addAction("O3DE:SampleUI") - - @Slot() - def clicked_sample_ui(): - while 1: # simple PySide2 test, set to 0 to disable - ui = SampleUI(parent=_widget_main_window, title='O3DE:SampleUI') - ui.show() - break - return - # Add click event to menu bar - action_launch_sample_ui.triggered.connect(clicked_sample_ui) - # --------------------------------------------------------------------- - - - # --------------------------------------------------------------------- - if _widget_main_window: - # (1) and custom external module Qwidget - action_launch_${SanitizedCppName}_dialog = ${SanitizedCppName}_launch_menu.addAction("O3DE:${SanitizedCppName}_dialog") - - @Slot() - def clicked_${SanitizedCppName}_dialog(): - while 1: # simple PySide2 test, set to 0 to disable - try: - import az_qt_helpers - from ${NameLower}_dialog import ${SanitizedCppName}Dialog - az_qt_helpers.register_view_pane('${SanitizedCppName} Popup', ${SanitizedCppName}Dialog) - except Exception as e: - print(f'Error: {e}') - print('Skipping register our ${SanitizedCppName}Dialog with the Editor.') - ${SanitizedCppName}_dialog = ${SanitizedCppName}Dialog(parent=_widget_main_window) - ${SanitizedCppName}_dialog.show() - break - return - # Add click event to menu bar - action_launch_${SanitizedCppName}_dialog.triggered.connect(clicked_${SanitizedCppName}_dialog) - # --------------------------------------------------------------------- - - # end \ No newline at end of file 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/PythonGem/Template/CMakeLists.txt b/Templates/PythonToolGem/Template/CMakeLists.txt similarity index 100% rename from Templates/PythonGem/Template/CMakeLists.txt rename to Templates/PythonToolGem/Template/CMakeLists.txt diff --git a/Templates/PythonGem/Template/Code/${NameLower}_editor_files.cmake b/Templates/PythonToolGem/Template/Code/${NameLower}_editor_files.cmake similarity index 94% rename from Templates/PythonGem/Template/Code/${NameLower}_editor_files.cmake rename to Templates/PythonToolGem/Template/Code/${NameLower}_editor_files.cmake index 8362d37f52..88aaea843f 100644 --- a/Templates/PythonGem/Template/Code/${NameLower}_editor_files.cmake +++ b/Templates/PythonToolGem/Template/Code/${NameLower}_editor_files.cmake @@ -11,4 +11,5 @@ set(FILES Source/${Name}ModuleInterface.h Source/${Name}EditorSystemComponent.cpp Source/${Name}EditorSystemComponent.h + Source/${Name}.qrc ) diff --git a/Templates/PythonGem/Template/Code/${NameLower}_editor_shared_files.cmake b/Templates/PythonToolGem/Template/Code/${NameLower}_editor_shared_files.cmake similarity index 100% rename from Templates/PythonGem/Template/Code/${NameLower}_editor_shared_files.cmake rename to Templates/PythonToolGem/Template/Code/${NameLower}_editor_shared_files.cmake diff --git a/Templates/PythonGem/Template/Code/${NameLower}_editor_tests_files.cmake b/Templates/PythonToolGem/Template/Code/${NameLower}_editor_tests_files.cmake similarity index 100% rename from Templates/PythonGem/Template/Code/${NameLower}_editor_tests_files.cmake rename to Templates/PythonToolGem/Template/Code/${NameLower}_editor_tests_files.cmake diff --git a/Templates/PythonGem/Template/Code/CMakeLists.txt b/Templates/PythonToolGem/Template/Code/CMakeLists.txt similarity index 99% rename from Templates/PythonGem/Template/Code/CMakeLists.txt rename to Templates/PythonToolGem/Template/Code/CMakeLists.txt index b7a5ac89a9..a6044e717b 100644 --- a/Templates/PythonGem/Template/Code/CMakeLists.txt +++ b/Templates/PythonToolGem/Template/Code/CMakeLists.txt @@ -26,6 +26,7 @@ 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 diff --git a/Templates/PythonGem/Template/Code/Include/${Name}/${Name}Bus.h b/Templates/PythonToolGem/Template/Code/Include/${Name}/${Name}Bus.h similarity index 100% rename from Templates/PythonGem/Template/Code/Include/${Name}/${Name}Bus.h rename to Templates/PythonToolGem/Template/Code/Include/${Name}/${Name}Bus.h diff --git a/Templates/PythonGem/Template/Code/Platform/Linux/${NameLower}_linux_files.cmake b/Templates/PythonToolGem/Template/Code/Platform/Linux/${NameLower}_linux_files.cmake similarity index 100% rename from Templates/PythonGem/Template/Code/Platform/Linux/${NameLower}_linux_files.cmake rename to Templates/PythonToolGem/Template/Code/Platform/Linux/${NameLower}_linux_files.cmake diff --git a/Templates/PythonGem/Template/Code/Platform/Linux/${NameLower}_shared_linux_files.cmake b/Templates/PythonToolGem/Template/Code/Platform/Linux/${NameLower}_shared_linux_files.cmake similarity index 100% rename from Templates/PythonGem/Template/Code/Platform/Linux/${NameLower}_shared_linux_files.cmake rename to Templates/PythonToolGem/Template/Code/Platform/Linux/${NameLower}_shared_linux_files.cmake diff --git a/Templates/PythonGem/Template/Code/Platform/Linux/PAL_linux.cmake b/Templates/PythonToolGem/Template/Code/Platform/Linux/PAL_linux.cmake similarity index 100% rename from Templates/PythonGem/Template/Code/Platform/Linux/PAL_linux.cmake rename to Templates/PythonToolGem/Template/Code/Platform/Linux/PAL_linux.cmake diff --git a/Templates/PythonGem/Template/Code/Platform/Mac/${NameLower}_mac_files.cmake b/Templates/PythonToolGem/Template/Code/Platform/Mac/${NameLower}_mac_files.cmake similarity index 100% rename from Templates/PythonGem/Template/Code/Platform/Mac/${NameLower}_mac_files.cmake rename to Templates/PythonToolGem/Template/Code/Platform/Mac/${NameLower}_mac_files.cmake diff --git a/Templates/PythonGem/Template/Code/Platform/Mac/${NameLower}_shared_mac_files.cmake b/Templates/PythonToolGem/Template/Code/Platform/Mac/${NameLower}_shared_mac_files.cmake similarity index 100% rename from Templates/PythonGem/Template/Code/Platform/Mac/${NameLower}_shared_mac_files.cmake rename to Templates/PythonToolGem/Template/Code/Platform/Mac/${NameLower}_shared_mac_files.cmake diff --git a/Templates/PythonGem/Template/Code/Platform/Mac/PAL_mac.cmake b/Templates/PythonToolGem/Template/Code/Platform/Mac/PAL_mac.cmake similarity index 100% rename from Templates/PythonGem/Template/Code/Platform/Mac/PAL_mac.cmake rename to Templates/PythonToolGem/Template/Code/Platform/Mac/PAL_mac.cmake diff --git a/Templates/PythonGem/Template/Code/Platform/Windows/${NameLower}_shared_windows_files.cmake b/Templates/PythonToolGem/Template/Code/Platform/Windows/${NameLower}_shared_windows_files.cmake similarity index 100% rename from Templates/PythonGem/Template/Code/Platform/Windows/${NameLower}_shared_windows_files.cmake rename to Templates/PythonToolGem/Template/Code/Platform/Windows/${NameLower}_shared_windows_files.cmake diff --git a/Templates/PythonGem/Template/Code/Platform/Windows/${NameLower}_windows_files.cmake b/Templates/PythonToolGem/Template/Code/Platform/Windows/${NameLower}_windows_files.cmake similarity index 100% rename from Templates/PythonGem/Template/Code/Platform/Windows/${NameLower}_windows_files.cmake rename to Templates/PythonToolGem/Template/Code/Platform/Windows/${NameLower}_windows_files.cmake diff --git a/Templates/PythonGem/Template/Code/Platform/Windows/PAL_windows.cmake b/Templates/PythonToolGem/Template/Code/Platform/Windows/PAL_windows.cmake similarity index 100% rename from Templates/PythonGem/Template/Code/Platform/Windows/PAL_windows.cmake rename to Templates/PythonToolGem/Template/Code/Platform/Windows/PAL_windows.cmake 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/PythonGem/Template/Code/Source/${Name}EditorModule.cpp b/Templates/PythonToolGem/Template/Code/Source/${Name}EditorModule.cpp similarity index 88% rename from Templates/PythonGem/Template/Code/Source/${Name}EditorModule.cpp rename to Templates/PythonToolGem/Template/Code/Source/${Name}EditorModule.cpp index 644c513747..0027af011a 100644 --- a/Templates/PythonGem/Template/Code/Source/${Name}EditorModule.cpp +++ b/Templates/PythonToolGem/Template/Code/Source/${Name}EditorModule.cpp @@ -11,6 +11,12 @@ #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 @@ -22,6 +28,8 @@ namespace ${SanitizedCppName} ${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. diff --git a/Templates/PythonGem/Template/Code/Source/${Name}EditorSystemComponent.cpp b/Templates/PythonToolGem/Template/Code/Source/${Name}EditorSystemComponent.cpp similarity index 100% rename from Templates/PythonGem/Template/Code/Source/${Name}EditorSystemComponent.cpp rename to Templates/PythonToolGem/Template/Code/Source/${Name}EditorSystemComponent.cpp diff --git a/Templates/PythonGem/Template/Code/Source/${Name}EditorSystemComponent.h b/Templates/PythonToolGem/Template/Code/Source/${Name}EditorSystemComponent.h similarity index 100% rename from Templates/PythonGem/Template/Code/Source/${Name}EditorSystemComponent.h rename to Templates/PythonToolGem/Template/Code/Source/${Name}EditorSystemComponent.h diff --git a/Templates/PythonGem/Template/Code/Source/${Name}ModuleInterface.h b/Templates/PythonToolGem/Template/Code/Source/${Name}ModuleInterface.h similarity index 100% rename from Templates/PythonGem/Template/Code/Source/${Name}ModuleInterface.h rename to Templates/PythonToolGem/Template/Code/Source/${Name}ModuleInterface.h 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/PythonGem/Template/Code/Tests/${Name}EditorTest.cpp b/Templates/PythonToolGem/Template/Code/Tests/${Name}EditorTest.cpp similarity index 100% rename from Templates/PythonGem/Template/Code/Tests/${Name}EditorTest.cpp rename to Templates/PythonToolGem/Template/Code/Tests/${Name}EditorTest.cpp diff --git a/Templates/PythonGem/Template/Editor/Scripts/${NameLower}_dialog.py b/Templates/PythonToolGem/Template/Editor/Scripts/${NameLower}_dialog.py similarity index 67% rename from Templates/PythonGem/Template/Editor/Scripts/${NameLower}_dialog.py rename to Templates/PythonToolGem/Template/Editor/Scripts/${NameLower}_dialog.py index 39515711ae..19194ec97f 100644 --- a/Templates/PythonGem/Template/Editor/Scripts/${NameLower}_dialog.py +++ b/Templates/PythonToolGem/Template/Editor/Scripts/${NameLower}_dialog.py @@ -6,24 +6,15 @@ SPDX-License-Identifier: Apache-2.0 OR MIT """ # ------------------------------------------------------------------------- """${SanitizedCppName}\\editor\\scripts\\${SanitizedCppName}_dialog.py -Generated from O3DE PythonGem Template""" +Generated from O3DE PythonToolGem Template""" -import azlmbr -from shiboken2 import wrapInstance, getCppPointer -from PySide2 import QtCore, QtWidgets, QtGui -from PySide2.QtCore import QEvent, Qt -from PySide2.QtWidgets import QVBoxLayout, QAction, QDialog, QHeaderView, QLabel, QLineEdit, QPushButton, QSplitter, QTreeWidget, QTreeWidgetItem, QWidget, QAbstractButton - -# Once PySide2 has been bootstrapped, register our ${SanitizedCppName}Dialog with the Editor +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.setObjectName("${SanitizedCppName}Dialog") - - self.setWindowTitle("HelloWorld, ${SanitizedCppName} Dialog") - self.mainLayout = QVBoxLayout(self) self.introLabel = QLabel("Put your cool stuff here!") @@ -42,5 +33,3 @@ class ${SanitizedCppName}Dialog(QDialog): self.mainLayout.addWidget(self.helpLabel, 0, Qt.AlignCenter) self.setLayout(self.mainLayout) - - return \ No newline at end of file diff --git a/Templates/PythonGem/Template/Editor/Scripts/__init__.py b/Templates/PythonToolGem/Template/Editor/Scripts/__init__.py similarity index 100% rename from Templates/PythonGem/Template/Editor/Scripts/__init__.py rename to Templates/PythonToolGem/Template/Editor/Scripts/__init__.py 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/PythonGem/Template/gem.json b/Templates/PythonToolGem/Template/gem.json similarity index 100% rename from Templates/PythonGem/Template/gem.json rename to Templates/PythonToolGem/Template/gem.json diff --git a/Templates/PythonGem/Template/preview.png b/Templates/PythonToolGem/Template/preview.png similarity index 100% rename from Templates/PythonGem/Template/preview.png rename to Templates/PythonToolGem/Template/preview.png diff --git a/Templates/PythonGem/template.json b/Templates/PythonToolGem/template.json similarity index 87% rename from Templates/PythonGem/template.json rename to Templates/PythonToolGem/template.json index 75be757abb..6dc68de3fc 100644 --- a/Templates/PythonGem/template.json +++ b/Templates/PythonToolGem/template.json @@ -1,17 +1,23 @@ { - "template_name": "PythonGem", + "template_name": "PythonToolGem", "restricted_name": "o3de", "restricted_platform_relative_path": "Templates", - "origin": "The primary repo for PythonGem goes here: i.e. http://www.mydomain.com", - "license": "What license PythonGem uses goes here: i.e. https://opensource.org/licenses/MIT", - "display_name": "PythonGem", - "summary": "A short description of PythonGem.", + "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": [ - "PythonGem" + "PythonToolGem" ], "icon_path": "preview.png", "copyFiles": [ + { + "file": ".gitignore", + "origin": ".gitignore", + "isTemplated": false, + "isOptional": false + }, { "file": "CMakeLists.txt", "origin": "CMakeLists.txt", @@ -102,6 +108,12 @@ "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", @@ -126,6 +138,12 @@ "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", 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 ac4ad621da..5e96f34e95 100755 --- a/Tools/LyTestTools/ly_test_tools/launchers/launcher_helper.py +++ b/Tools/LyTestTools/ly_test_tools/launchers/launcher_helper.py @@ -60,7 +60,7 @@ def create_editor(workspace, launcher_platform=ly_test_tools.HOST_OS_EDITOR, arg 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 """ 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_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 af7afff5dc..25424a348c 100644 --- a/cmake/3rdParty/Platform/Linux/BuiltInPackages_linux.cmake +++ b/cmake/3rdParty/Platform/Linux/BuiltInPackages_linux.cmake @@ -36,6 +36,7 @@ ly_associate_package(PACKAGE_NAME unwind-1.2.1-linux 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/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/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 f5e5edcf66..ff04902861 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 + '' @@ -449,8 +449,8 @@ def UploadAPLogs(Map options, String branchName, String jobName, String workspac } def command = "${pythonPath} -u ${s3UploadScriptPath} --base_dir ${apLogsPath} " + "--file_regex \".*\" --bucket ${env.AP_LOGS_S3_BUCKET} " + - "--search_subdirectories True --key_prefix ${env.JOB_NAME}/${branchName}/${env.BUILD_NUMBER}/${jobName}" + - "--extra-args {\"ACL\": \"bucket-owner-full-control\"}" + "--search_subdirectories True --key_prefix ${env.JENKINS_JOB_NAME}/${branchName}/${env.BUILD_NUMBER}/${jobName} " + + '--extra_args {\\"ACL\\":\\"bucket-owner-full-control\\"}' palSh(command, "Uploading AP logs for job ${jobName} for branch ${branchName}", false) } } @@ -806,6 +806,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..c0307de23d 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,7 +210,7 @@ "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" } diff --git a/scripts/build/Platform/Mac/build_config.json b/scripts/build/Platform/Mac/build_config.json index 7eb3cb5699..b57cc522bc 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" } diff --git a/scripts/build/Platform/Windows/build_config.json b/scripts/build/Platform/Windows/build_config.json index de83294639..b185a845ec 100644 --- a/scripts/build/Platform/Windows/build_config.json +++ b/scripts/build/Platform/Windows/build_config.json @@ -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,7 +330,7 @@ "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" @@ -345,7 +345,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_DISABLE_TEST_MODULES=TRUE", + "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_DISABLE_TEST_MODULES=TRUE", "CMAKE_LY_PROJECTS": "", "CMAKE_TARGET": "INSTALL", "CMAKE_NATIVE_BUILD_ARGS": "/m /nologo" @@ -363,7 +363,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_DISABLE_TEST_MODULES=TRUE -DLY_VERSION_ENGINE_NAME=o3de-sdk -DLY_INSTALLER_WIX_ROOT=\"!WIX! \"", + "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_LY_PROJECTS": "", @@ -382,7 +382,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_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DCMAKE_MODULE_PATH=!WORKSPACE!/o3de/cmake", "CMAKE_LY_PROJECTS": "", "CMAKE_TARGET": "ALL_BUILD", "CMAKE_NATIVE_BUILD_ARGS": "/m /nologo" @@ -397,7 +397,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/install/cmake", + "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DCMAKE_MODULE_PATH=!WORKSPACE!/o3de/install/cmake", "CMAKE_LY_PROJECTS": "", "CMAKE_TARGET": "ALL_BUILD", "CMAKE_NATIVE_BUILD_ARGS": "/m /nologo" 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/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/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 32c2cba428..a6b1505761 100644 --- a/scripts/o3de/o3de/repo.py +++ b/scripts/o3de/o3de/repo.py @@ -147,7 +147,7 @@ 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['repos']: + for repo_uri in json_data.get('repos', []): gem_set.update(get_gem_json_paths_from_cached_repo(repo_uri)) return gem_set @@ -189,7 +189,7 @@ 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) 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)