From 6308d01456252a1a12b23c844a8235ef93b07252 Mon Sep 17 00:00:00 2001 From: evanchia Date: Wed, 30 Jun 2021 15:29:06 -0700 Subject: [PATCH 01/21] Minor changes to system example test to match docs Signed-off-by: evanchia --- .../tests/example/test_system_example.py | 94 +++++++++---------- 1 file changed, 46 insertions(+), 48 deletions(-) diff --git a/Tools/LyTestTools/tests/example/test_system_example.py b/Tools/LyTestTools/tests/example/test_system_example.py index 3cb6128802..c4f4812428 100755 --- a/Tools/LyTestTools/tests/example/test_system_example.py +++ b/Tools/LyTestTools/tests/example/test_system_example.py @@ -3,7 +3,7 @@ Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright SPDX-License-Identifier: Apache-2.0 OR MIT -Example test using LyTestTools to test Lumberyard. +Example test using LyTestTools to test Open 3D Engine. """ # Python built-in dependencies. import logging @@ -11,7 +11,7 @@ import logging # Third party dependencies. import pytest -# ly_test_tools dependencies. +# Optional ly_test_tools dependencies. import ly_test_tools.log.log_monitor import ly_remote_console.remote_console_commands as remote_console_commands @@ -22,28 +22,6 @@ import ly_remote_console.remote_console_commands as remote_console_commands logger = logging.getLogger(__name__) -@pytest.fixture -def remote_console(request): - """ - Creates a RemoteConsole() class instance to send console commands to the - Lumberyard client console. - :param request: _pytest.fixtures.SubRequest class that handles getting - a pytest fixture from a pytest function/fixture. - :return: ly_remote_console.remote_console_commands.RemoteConsole class instance - representing the Lumberyard remote console executable. - """ - # Initialize the RemoteConsole object to send commands to the Lumberyard client console. - console = remote_console_commands.RemoteConsole() - - # Custom teardown method for this remote_console fixture. - def teardown(): - console.stop() - - # Utilize request.addfinalizer() to add custom teardown() methods. - request.addfinalizer(teardown) # This pattern must be used in pytest version - - return console - # Shared parameters & fixtures for all test methods inside the TestSystemExample class. @pytest.mark.usefixtures("automatic_process_killer") @pytest.mark.parametrize('project', ['AutomatedTesting']) @@ -55,6 +33,41 @@ class TestSystemExample(object): For this test, we placed unique test values in test methods and shared test values in the test class. We also assume building has already been done, but the test should error if the build is mis-configured. """ + + # This test method only needs pytest.mark report values and shared test class parameters. + @pytest.mark.parametrize('processes_to_kill', ['Editor.exe']) + @pytest.mark.parametrize("launcher_platform", ['windows_editor']) + def test_SystemTestExample_WindowsPlatform_LaunchEditor(self, editor, processes_to_kill, launcher_platform): + """ + Tests launching the O3DE Editor is successful with the current build. + """ + # Launch the O3DE Editor & verify load is successful: + with editor.start(): + assert editor.is_alive(), ( + 'Editor failed to launch for the current O3DE build.') + + @pytest.fixture + def remote_console(request): + """ + Creates a RemoteConsole() class instance to send console commands to the + O3DE client console. + :param request: _pytest.fixtures.SubRequest class that handles getting + a pytest fixture from a pytest function/fixture. + :return: ly_remote_console.remote_console_commands.RemoteConsole class instance + representing the O3DE Remote Console executable. + """ + # Initialize the RemoteConsole object to send commands to the O3DE client console. + console = remote_console_commands.RemoteConsole() + + # Custom teardown method for this remote_console fixture. + def teardown(): + console.stop() + + # Utilize request.addfinalizer() to add custom teardown() methods. + request.addfinalizer(teardown) # This pattern must be used in pytest version + + return console + # This test method needs specific parameters not shared by all other tests in the class. # For targeting specific launchers, use the 'launcher_platform' pytest param like below: # @pytest.mark.parametrize("launcher_platform", ['android']) @@ -62,39 +75,24 @@ class TestSystemExample(object): # @pytest.mark.parametrize("asset_processor_platform", ['android']) @pytest.mark.parametrize('level', ['simple_jacklocomotion']) @pytest.mark.parametrize('load_wait', [120]) - @pytest.mark.test_case_id('C16806863') - def test_SystemTestExample_AllSupportedPlatforms_LaunchAutomatedTesting( - # launcher_platform, asset_processor_platform, # Re-add these here if you plan to use them. - self, launcher, remote_console, level, load_wait): + def test_SystemTestExample_AllSupportedPlatforms_LaunchAutomatedTesting(self, launcher, remote_console, level, + load_wait): """ - Tests launching the AutomatedTesting then launches the Lumberyard client & + Tests launching the AutomatedTesting then launches the O3DE client & loads the "simple_jacklocomotion" level using the remote console. Assumes the user already setup & built their machine for the test. """ - # Launch the Lumberyard client & remote console test case: + # Launch the O3DE client & remote console test case: with launcher.start(): remote_console.start() + # Wait for the expected log line to appear launcher_load = remote_console.expect_log_line( match_string='Level system is loading "simple_jacklocomotion"', timeout=load_wait) # Assert loading was successful using remote console logs: - assert launcher_load, ( - 'Launcher failed to load Lumberyard client with the ' - f'"{level}" level - waited "{load_wait}" seconds.') - - # This test method only needs pytest.mark report values and shared test class parameters. - @pytest.mark.parametrize('processes_to_kill', ['Editor.exe']) - @pytest.mark.parametrize("launcher_platform", ['windows_editor']) - @pytest.mark.test_case_id('C16806864') - def test_SystemTestExample_AllSupportedPlatforms_LaunchEditor(self, editor, processes_to_kill, launcher_platform): - """ - Tests launching the Lumberyard Editor is successful with the current build. - """ - # Launch the Lumberyard editor & verify load is successful: - with editor.start(): - assert editor.is_alive(), ( - 'Editor failed to launch for the current Lumberyard build.') + assert launcher_load, ('Launcher failed to load O3DE client with the "{level}" level - waited "{load_wait}" ' + 'seconds.') # Log monitoring example test. @pytest.mark.parametrize('level', ['simple_jacklocomotion']) @@ -106,7 +104,7 @@ class TestSystemExample(object): """ Tests that the logging paths created by LyTestTools can be monitored for results using the log monitor. """ - # Launch the Lumberyard client & initialize the log monitor. + # Launch the O3DE client & initialize the log monitor. file_to_monitor = launcher.workspace.info_log_path log_monitor = ly_test_tools.log.log_monitor.LogMonitor(launcher=launcher, log_file_path=file_to_monitor) @@ -115,7 +113,7 @@ class TestSystemExample(object): for expected_line in expected_lines: logger.info(expected_line) - # Start the Lumberyard client & test that the lines we logged can be viewed by the log monitor. + # Start the O3DE client & test that the lines we logged can be viewed by the log monitor. with launcher.start(): log_test = log_monitor.monitor_log_for_lines( expected_lines=expected_lines, # Defaults to None. From 9067fc3b99af1ff01384a873136e05262f1ed1c2 Mon Sep 17 00:00:00 2001 From: evanchia Date: Thu, 8 Jul 2021 10:24:49 -0700 Subject: [PATCH 02/21] Updating/Removing LyTestTools sample tests Signed-off-by: evanchia --- Tools/LyTestTools/tests/example/__init__.py | 5 - .../tests/example/test_system_example.py | 127 ------------------ Tools/LyTestTools/tests/integ/sanity_tests.py | 17 ++- 3 files changed, 14 insertions(+), 135 deletions(-) delete mode 100755 Tools/LyTestTools/tests/example/__init__.py delete mode 100755 Tools/LyTestTools/tests/example/test_system_example.py diff --git a/Tools/LyTestTools/tests/example/__init__.py b/Tools/LyTestTools/tests/example/__init__.py deleted file mode 100755 index e200fa77d0..0000000000 --- a/Tools/LyTestTools/tests/example/__init__.py +++ /dev/null @@ -1,5 +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 -""" diff --git a/Tools/LyTestTools/tests/example/test_system_example.py b/Tools/LyTestTools/tests/example/test_system_example.py deleted file mode 100755 index c4f4812428..0000000000 --- a/Tools/LyTestTools/tests/example/test_system_example.py +++ /dev/null @@ -1,127 +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 - -Example test using LyTestTools to test Open 3D Engine. -""" -# Python built-in dependencies. -import logging - -# Third party dependencies. -import pytest - -# Optional ly_test_tools dependencies. -import ly_test_tools.log.log_monitor -import ly_remote_console.remote_console_commands as remote_console_commands - -# Configuring the logging is done in ly_test_tools at the following location: -# ~/dev/Tools/LyTestTools/ly_test_tools/_internal/log/py_logging_util.py - -# Use the following logging pattern to hook all test logging together: -logger = logging.getLogger(__name__) - - -# Shared parameters & fixtures for all test methods inside the TestSystemExample class. -@pytest.mark.usefixtures("automatic_process_killer") -@pytest.mark.parametrize('project', ['AutomatedTesting']) -class TestSystemExample(object): - """ - Example test case class to hold a set of test case methods. - - The amount of tests run is based on the parametrization stacking made in each test method or class. - For this test, we placed unique test values in test methods and shared test values in the test class. - We also assume building has already been done, but the test should error if the build is mis-configured. - """ - - # This test method only needs pytest.mark report values and shared test class parameters. - @pytest.mark.parametrize('processes_to_kill', ['Editor.exe']) - @pytest.mark.parametrize("launcher_platform", ['windows_editor']) - def test_SystemTestExample_WindowsPlatform_LaunchEditor(self, editor, processes_to_kill, launcher_platform): - """ - Tests launching the O3DE Editor is successful with the current build. - """ - # Launch the O3DE Editor & verify load is successful: - with editor.start(): - assert editor.is_alive(), ( - 'Editor failed to launch for the current O3DE build.') - - @pytest.fixture - def remote_console(request): - """ - Creates a RemoteConsole() class instance to send console commands to the - O3DE client console. - :param request: _pytest.fixtures.SubRequest class that handles getting - a pytest fixture from a pytest function/fixture. - :return: ly_remote_console.remote_console_commands.RemoteConsole class instance - representing the O3DE Remote Console executable. - """ - # Initialize the RemoteConsole object to send commands to the O3DE client console. - console = remote_console_commands.RemoteConsole() - - # Custom teardown method for this remote_console fixture. - def teardown(): - console.stop() - - # Utilize request.addfinalizer() to add custom teardown() methods. - request.addfinalizer(teardown) # This pattern must be used in pytest version - - return console - - # This test method needs specific parameters not shared by all other tests in the class. - # For targeting specific launchers, use the 'launcher_platform' pytest param like below: - # @pytest.mark.parametrize("launcher_platform", ['android']) - # If you want to target different AssetProcessor platforms, use asset_processor_platform: - # @pytest.mark.parametrize("asset_processor_platform", ['android']) - @pytest.mark.parametrize('level', ['simple_jacklocomotion']) - @pytest.mark.parametrize('load_wait', [120]) - def test_SystemTestExample_AllSupportedPlatforms_LaunchAutomatedTesting(self, launcher, remote_console, level, - load_wait): - """ - Tests launching the AutomatedTesting then launches the O3DE client & - loads the "simple_jacklocomotion" level using the remote console. - Assumes the user already setup & built their machine for the test. - """ - # Launch the O3DE client & remote console test case: - with launcher.start(): - remote_console.start() - # Wait for the expected log line to appear - launcher_load = remote_console.expect_log_line( - match_string='Level system is loading "simple_jacklocomotion"', - timeout=load_wait) - - # Assert loading was successful using remote console logs: - assert launcher_load, ('Launcher failed to load O3DE client with the "{level}" level - waited "{load_wait}" ' - 'seconds.') - - # Log monitoring example test. - @pytest.mark.parametrize('level', ['simple_jacklocomotion']) - @pytest.mark.parametrize('expected_lines', [['Log Monitoring test 1', 'Log Monitoring test 2']]) - @pytest.mark.parametrize('unexpected_lines', [['Unexpected test 1', 'Unexpected test 2']]) - @pytest.mark.test_case_id('C21202585') - def test_SystemTestExample_AllSupportedPlatforms_LogMonitoring(self, level, launcher, expected_lines, - unexpected_lines): - """ - Tests that the logging paths created by LyTestTools can be monitored for results using the log monitor. - """ - # Launch the O3DE client & initialize the log monitor. - file_to_monitor = launcher.workspace.info_log_path - log_monitor = ly_test_tools.log.log_monitor.LogMonitor(launcher=launcher, - log_file_path=file_to_monitor) - - # Generate log lines to the info log using logger. - for expected_line in expected_lines: - logger.info(expected_line) - - # Start the O3DE client & test that the lines we logged can be viewed by the log monitor. - with launcher.start(): - log_test = log_monitor.monitor_log_for_lines( - expected_lines=expected_lines, # Defaults to None. - unexpected_lines=unexpected_lines, # Defaults to None. - halt_on_unexpected=True, # Defaults to False. - timeout=60) # Defaults to 30 - - # Assert the log monitor detected expected lines and did not detect any unexpected lines. - assert log_test, ( - f'Log monitoring failed. Used expected_lines values: {expected_lines} & ' - f'unexpected_lines values: {unexpected_lines}') diff --git a/Tools/LyTestTools/tests/integ/sanity_tests.py b/Tools/LyTestTools/tests/integ/sanity_tests.py index c6c5ce0c31..6c1f1079a9 100755 --- a/Tools/LyTestTools/tests/integ/sanity_tests.py +++ b/Tools/LyTestTools/tests/integ/sanity_tests.py @@ -15,8 +15,6 @@ import ly_test_tools.builtin.helpers as helpers import ly_test_tools.environment.process_utils as process_utils import ly_test_tools.environment.waiter as waiter -pytestmark = pytest.mark.SUITE_smoke - logger = logging.getLogger(__name__) # Note: For device testing, device ids must exist in ~/ly_test_tools/devices.ini, see README.txt for more info. @@ -24,31 +22,44 @@ logger = logging.getLogger(__name__) @pytest.mark.parametrize("project", ["AutomatedTesting"]) class TestAutomatedTestingProject(object): + def test_StartGameLauncher_Sanity(self, project): + # Kill processes that may interfere with the test process_utils.kill_processes_named(names=process_utils.LY_PROCESS_KILL_LIST, ignore_extensions=True) try: + # Create the Workspace object workspace = helpers.create_builtin_workspace(project=project) + # Create the Launcher object and add args launcher = launcher_helper.create_launcher(workspace) - launcher.args.extend(['-NullRenderer', '-BatchMode']) + launcher.args.extend(['-NullRenderer']) + # Call the game client executable with launcher.start(): + # Wait for the process to exist waiter.wait_for(lambda: process_utils.process_exists(f"{project}.GameLauncher.exe", ignore_extensions=True)) finally: + # Clean up processes after the test is finished process_utils.kill_processes_named(names=process_utils.LY_PROCESS_KILL_LIST, ignore_extensions=True) @pytest.mark.skipif(not ly_test_tools.WINDOWS, reason="Editor currently only functions on Windows") def test_StartEditor_Sanity(self, project): + # Kill processes that may interfere with the test process_utils.kill_processes_named(names=process_utils.LY_PROCESS_KILL_LIST, ignore_extensions=True) try: + # Create the Workspace object workspace = helpers.create_builtin_workspace(project=project) + # Create the Launcher object and add args editor = launcher_helper.create_editor(workspace) editor.args.extend(['-NullRenderer', '-autotest_mode']) + # Call the Editor executable with editor.start(): + # Wait for the process to exist waiter.wait_for(lambda: process_utils.process_exists("Editor", ignore_extensions=True)) finally: + # Clean up processes after the test is finished process_utils.kill_processes_named(names=process_utils.LY_PROCESS_KILL_LIST, ignore_extensions=True) From bc7e12278cd39d4b495fa9dde226fe74423d8bdb Mon Sep 17 00:00:00 2001 From: evanchia Date: Thu, 8 Jul 2021 15:55:06 -0700 Subject: [PATCH 03/21] changed nullrenderer arg for sanity test Signed-off-by: evanchia --- Tools/LyTestTools/tests/integ/sanity_tests.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tools/LyTestTools/tests/integ/sanity_tests.py b/Tools/LyTestTools/tests/integ/sanity_tests.py index 6c1f1079a9..0aacfff8c6 100755 --- a/Tools/LyTestTools/tests/integ/sanity_tests.py +++ b/Tools/LyTestTools/tests/integ/sanity_tests.py @@ -33,7 +33,7 @@ class TestAutomatedTestingProject(object): # Create the Launcher object and add args launcher = launcher_helper.create_launcher(workspace) - launcher.args.extend(['-NullRenderer']) + launcher.args.extend(['-rhi=Null']) # Call the game client executable with launcher.start(): @@ -54,7 +54,7 @@ class TestAutomatedTestingProject(object): # Create the Launcher object and add args editor = launcher_helper.create_editor(workspace) - editor.args.extend(['-NullRenderer', '-autotest_mode']) + editor.args.extend(['-rhi=Null', '-autotest_mode']) # Call the Editor executable with editor.start(): From 12a8cc7cd75edabb235086e92be2ab93eb673117 Mon Sep 17 00:00:00 2001 From: evanchia Date: Mon, 12 Jul 2021 15:31:09 -0700 Subject: [PATCH 04/21] Removing deleted test from CMakeLists and updated in line comments for sample test Signed-off-by: evanchia --- Tools/LyTestTools/tests/CMakeLists.txt | 14 ---------- Tools/LyTestTools/tests/integ/sanity_tests.py | 26 +++++++++++++++++++ 2 files changed, 26 insertions(+), 14 deletions(-) diff --git a/Tools/LyTestTools/tests/CMakeLists.txt b/Tools/LyTestTools/tests/CMakeLists.txt index e5f270197c..5e57cbf123 100644 --- a/Tools/LyTestTools/tests/CMakeLists.txt +++ b/Tools/LyTestTools/tests/CMakeLists.txt @@ -52,18 +52,4 @@ if(PAL_TRAIT_BUILD_HOST_TOOLS AND PAL_TRAIT_BUILD_TESTS_SUPPORTED AND AutomatedT AutomatedTesting.Assets COMPONENT TestTools ) - - # Example tests. - ly_add_pytest( - NAME LyTestTools_ExampleTests_periodic_no_gpu - PATH ${CMAKE_CURRENT_LIST_DIR}/example/test_system_example.py - TEST_SERIAL - TEST_SUITE periodic - RUNTIME_DEPENDENCIES - Legacy::Editor - AssetProcessor - AutomatedTesting.GameLauncher - AutomatedTesting.Assets - COMPONENT TestTools - ) endif() diff --git a/Tools/LyTestTools/tests/integ/sanity_tests.py b/Tools/LyTestTools/tests/integ/sanity_tests.py index 0aacfff8c6..f68bc9ae57 100755 --- a/Tools/LyTestTools/tests/integ/sanity_tests.py +++ b/Tools/LyTestTools/tests/integ/sanity_tests.py @@ -6,24 +6,43 @@ SPDX-License-Identifier: Apache-2.0 OR MIT A sanity test for the built-in fixtures. Launch the windows launcher attached to the currently installed instance. """ +# Import any dependencies for the test. import logging import pytest +# Import any desired LTT modules from the package `ly_test_tools`. All LTT modules can be viewed at `Tools/LyTestTools/ly_test_tools`. import ly_test_tools +# The `launchers.launcher_helper` module helps create Launcher objects which control the Open 3D Engine (O3DE) Editor and game clients. import ly_test_tools.launchers.launcher_helper as launcher_helper +# The `builtin.helpers` module helps create the Workspace object, which controls the testing workspace in LTT. import ly_test_tools.builtin.helpers as helpers +# The `environment` module contains tools that involve the system's environment such as processes or timed waiters. import ly_test_tools.environment.process_utils as process_utils import ly_test_tools.environment.waiter as waiter +# Initialize a logger instance to hook all test logs together. The sub-logger pattern below makes it easy to track which file creates a log line. logger = logging.getLogger(__name__) # Note: For device testing, device ids must exist in ~/ly_test_tools/devices.ini, see README.txt for more info. +# First define the class `TestAutomatedTestingProject` to group test functions together. +# The example test contains two test functions: `test_StartGameLauncher_Sanity` and `test_StartEditor_Sanity`. @pytest.mark.parametrize("project", ["AutomatedTesting"]) +# The example test utilizes Pytest parameterization. The following sets the `project` parameter to `AutomatedTesting` +# for both test functions. Notice that the Pytest mark is defined at the class level to affect both test functions. class TestAutomatedTestingProject(object): def test_StartGameLauncher_Sanity(self, project): + """ + The `test_StartGameLauncher_Sanity` test function verifies that the O3DE game client launches successfully. + Start the test by utilizing the `kill_processes_named` function to close any open O3DE processes that may + interfere with the test. The Workspace object emulates the O3DE package by locating the engine and project + directories. The Launcher object controls the O3DE game client and requires a Workspace object for + initialization. Add the `-rhi=Null` arg to the executable call to disable GPU rendering. This allows the + test to run on instances without a GPU. We launch the game client executable and wait for the process to exist. + A try/finally block ensures proper test cleanup if issues occur during the test. + """ # Kill processes that may interfere with the test process_utils.kill_processes_named(names=process_utils.LY_PROCESS_KILL_LIST, ignore_extensions=True) @@ -45,6 +64,13 @@ class TestAutomatedTestingProject(object): @pytest.mark.skipif(not ly_test_tools.WINDOWS, reason="Editor currently only functions on Windows") def test_StartEditor_Sanity(self, project): + """ + The `test_StartEditor_Sanity` test function is similar to the previous example with minor adjustments. A + PyTest mark skips the test if the operating system is not Windows. We use the `create_editor` function instead + of `create_launcher` to create an Editor type launcher instead of a game client type launcher. The additional + `-autotest_mode` arg supresses modal dialogs from interfering with our test. We launch the Editor executable and + wait for the process to exist. + """ # Kill processes that may interfere with the test process_utils.kill_processes_named(names=process_utils.LY_PROCESS_KILL_LIST, ignore_extensions=True) From 6b6bd1149d007330c30f3531737ef53178c2ccf0 Mon Sep 17 00:00:00 2001 From: onecent1101 Date: Mon, 12 Jul 2021 14:17:33 -0700 Subject: [PATCH 05/21] [SPEC-7510] Remove unnecessary static container for AWSCore scriptcanvas nodes Signed-off-by: onecent1101 --- .../ScriptCanvas/AWSScriptBehaviorBase.h | 42 ------------- .../ScriptCanvas/AWSScriptBehaviorDynamoDB.h | 10 +-- .../ScriptCanvas/AWSScriptBehaviorLambda.h | 10 +-- .../Public/ScriptCanvas/AWSScriptBehaviorS3.h | 10 +-- .../AWSScriptBehaviorsComponent.h | 16 ----- .../AWSScriptBehaviorDynamoDB.cpp | 43 +++++-------- .../ScriptCanvas/AWSScriptBehaviorLambda.cpp | 43 +++++-------- .../ScriptCanvas/AWSScriptBehaviorS3.cpp | 62 ++++++++----------- .../AWSScriptBehaviorsComponent.cpp | 56 +---------------- .../AWSScriptBehaviorsComponentTest.cpp | 28 ++------- Gems/AWSCore/Code/awscore_files.cmake | 1 - 11 files changed, 87 insertions(+), 234 deletions(-) delete mode 100644 Gems/AWSCore/Code/Include/Public/ScriptCanvas/AWSScriptBehaviorBase.h diff --git a/Gems/AWSCore/Code/Include/Public/ScriptCanvas/AWSScriptBehaviorBase.h b/Gems/AWSCore/Code/Include/Public/ScriptCanvas/AWSScriptBehaviorBase.h deleted file mode 100644 index 90163dd363..0000000000 --- a/Gems/AWSCore/Code/Include/Public/ScriptCanvas/AWSScriptBehaviorBase.h +++ /dev/null @@ -1,42 +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 - -namespace AZ -{ - class BehaviorContext; - class EditContext; - class SerializeContext; -} // namespace AZ - -// this just provides a convenient template to avoid the necessary boilerplate when you derive from AWSScriptBehaviorBase -#define AWS_SCRIPT_BEHAVIOR_DEFINITION(className, guidString) \ - AZ_TYPE_INFO(className, guidString) \ - AZ_CLASS_ALLOCATOR(className, AZ::SystemAllocator, 0) \ - void ReflectSerialization(AZ::SerializeContext* serializeContext) override; \ - void ReflectBehaviors(AZ::BehaviorContext* behaviorContext) override; \ - void ReflectEditParameters(AZ::EditContext* editContext) override; \ - className(); \ - -namespace AWSCore -{ - //! An interface for AWS ScriptCanvas Behaviors to inherit from - class AWSScriptBehaviorBase - { - public: - virtual ~AWSScriptBehaviorBase() = default; - - virtual void ReflectSerialization(AZ::SerializeContext* reflectContext) = 0; - virtual void ReflectBehaviors(AZ::BehaviorContext* behaviorContext) = 0; - virtual void ReflectEditParameters(AZ::EditContext* editContext) = 0; - - virtual void Init() {} - virtual void Activate() {} - virtual void Deactivate() {} - }; -} // namespace AWSCore diff --git a/Gems/AWSCore/Code/Include/Public/ScriptCanvas/AWSScriptBehaviorDynamoDB.h b/Gems/AWSCore/Code/Include/Public/ScriptCanvas/AWSScriptBehaviorDynamoDB.h index 317d16dbe4..7244f57f6a 100644 --- a/Gems/AWSCore/Code/Include/Public/ScriptCanvas/AWSScriptBehaviorDynamoDB.h +++ b/Gems/AWSCore/Code/Include/Public/ScriptCanvas/AWSScriptBehaviorDynamoDB.h @@ -10,8 +10,6 @@ #include #include -#include - namespace AWSCore { using DynamoDBAttributeValueMap = AZStd::unordered_map; @@ -55,10 +53,14 @@ namespace AWSCore }; class AWSScriptBehaviorDynamoDB - : public AWSScriptBehaviorBase { public: - AWS_SCRIPT_BEHAVIOR_DEFINITION(AWSScriptBehaviorDynamoDB, "{569E74F6-1268-4199-9653-A3B603FC9F4F}"); + AZ_RTTI(AWSScriptBehaviorDynamoDB, "{569E74F6-1268-4199-9653-A3B603FC9F4F}"); + + AWSScriptBehaviorDynamoDB() = default; + virtual ~AWSScriptBehaviorDynamoDB() = default; + + static void Reflect(AZ::ReflectContext* context); static void GetItem(const AZStd::string& tableResourceKey, const DynamoDBAttributeValueMap& keyMap); static void GetItemRaw(const AZStd::string& table, const DynamoDBAttributeValueMap& keyMap, const AZStd::string& region); diff --git a/Gems/AWSCore/Code/Include/Public/ScriptCanvas/AWSScriptBehaviorLambda.h b/Gems/AWSCore/Code/Include/Public/ScriptCanvas/AWSScriptBehaviorLambda.h index a8a807e7fb..64601d8e59 100644 --- a/Gems/AWSCore/Code/Include/Public/ScriptCanvas/AWSScriptBehaviorLambda.h +++ b/Gems/AWSCore/Code/Include/Public/ScriptCanvas/AWSScriptBehaviorLambda.h @@ -10,8 +10,6 @@ #include #include -#include - namespace AWSCore { //! AWS Script Behavior notifications for ScriptCanvas behaviors that interact with AWS Lambda @@ -53,10 +51,14 @@ namespace AWSCore }; class AWSScriptBehaviorLambda - : public AWSScriptBehaviorBase { public: - AWS_SCRIPT_BEHAVIOR_DEFINITION(AWSScriptBehaviorLambda, "{9E71534D-34B3-4723-B180-2552513DDA3D}"); + AZ_RTTI(AWSScriptBehaviorLambda, "{9E71534D-34B3-4723-B180-2552513DDA3D}"); + + AWSScriptBehaviorLambda() = default; + virtual ~AWSScriptBehaviorLambda() = default; + + static void Reflect(AZ::ReflectContext* context); static void Invoke(const AZStd::string& functionResourceKey, const AZStd::string& payload); static void InvokeRaw(const AZStd::string& functionName, const AZStd::string& payload, const AZStd::string& region); diff --git a/Gems/AWSCore/Code/Include/Public/ScriptCanvas/AWSScriptBehaviorS3.h b/Gems/AWSCore/Code/Include/Public/ScriptCanvas/AWSScriptBehaviorS3.h index a75c3bce31..ac2df1c822 100644 --- a/Gems/AWSCore/Code/Include/Public/ScriptCanvas/AWSScriptBehaviorS3.h +++ b/Gems/AWSCore/Code/Include/Public/ScriptCanvas/AWSScriptBehaviorS3.h @@ -10,8 +10,6 @@ #include #include -#include - namespace AWSCore { //! AWS Script Behavior notifications for ScriptCanvas behaviors that interact with AWS S3 @@ -68,7 +66,6 @@ namespace AWSCore }; class AWSScriptBehaviorS3 - : public AWSScriptBehaviorBase { static constexpr const char AWSScriptBehaviorS3Name[] = "AWSScriptBehaviorS3"; static constexpr const char OutputFileIsEmptyErrorMessage[] = "Request validation failed, output file is empty."; @@ -81,7 +78,12 @@ namespace AWSCore static constexpr const char RegionNameIsEmptyErrorMessage[] = "Request validation failed, region name is empty."; public: - AWS_SCRIPT_BEHAVIOR_DEFINITION(AWSScriptBehaviorS3, "{7F4E956C-7463-4236-B320-C992D36A9C6E}"); + AZ_RTTI(AWSScriptBehaviorS3, "{7F4E956C-7463-4236-B320-C992D36A9C6E}"); + + AWSScriptBehaviorS3() = default; + virtual ~AWSScriptBehaviorS3() = default; + + static void Reflect(AZ::ReflectContext* context); static void GetObject(const AZStd::string& bucketResourceKey, const AZStd::string& objectKey, const AZStd::string& outFile); static void GetObjectRaw(const AZStd::string& bucket, const AZStd::string& objectKey, const AZStd::string& region, const AZStd::string& outFile); diff --git a/Gems/AWSCore/Code/Include/Public/ScriptCanvas/AWSScriptBehaviorsComponent.h b/Gems/AWSCore/Code/Include/Public/ScriptCanvas/AWSScriptBehaviorsComponent.h index 8958425615..43bd15d726 100644 --- a/Gems/AWSCore/Code/Include/Public/ScriptCanvas/AWSScriptBehaviorsComponent.h +++ b/Gems/AWSCore/Code/Include/Public/ScriptCanvas/AWSScriptBehaviorsComponent.h @@ -10,7 +10,6 @@ #include #include #include -#include namespace AWSCore { @@ -30,23 +29,8 @@ namespace AWSCore static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required); static void GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent); - static bool AddedBehaviours() - { - return m_alreadyAddedBehaviors; - } - - protected: - - //////////////////////////////////////////////////////////////////////// // AZ::Component interface implementation - void Init() override; void Activate() override; void Deactivate() override; - //////////////////////////////////////////////////////////////////////// - - static void AddBehaviors(); // Add any behaviors you derived from AWSScriptBehaviorBase to the implementation of this function - - static AZStd::vector> m_behaviors; - static bool m_alreadyAddedBehaviors; }; } // namespace AWSCore diff --git a/Gems/AWSCore/Code/Source/ScriptCanvas/AWSScriptBehaviorDynamoDB.cpp b/Gems/AWSCore/Code/Source/ScriptCanvas/AWSScriptBehaviorDynamoDB.cpp index c25df555fc..41c1aff34d 100644 --- a/Gems/AWSCore/Code/Source/ScriptCanvas/AWSScriptBehaviorDynamoDB.cpp +++ b/Gems/AWSCore/Code/Source/ScriptCanvas/AWSScriptBehaviorDynamoDB.cpp @@ -20,39 +20,30 @@ namespace AWSCore { - AWSScriptBehaviorDynamoDB::AWSScriptBehaviorDynamoDB() + void AWSScriptBehaviorDynamoDB::Reflect(AZ::ReflectContext* context) { - } - - void AWSScriptBehaviorDynamoDB::ReflectSerialization(AZ::SerializeContext* serializeContext) - { - if (serializeContext) + if (AZ::SerializeContext* serializeContext = azrtti_cast(context)) { serializeContext->Class() ->Version(0); } - } - void AWSScriptBehaviorDynamoDB::ReflectBehaviors(AZ::BehaviorContext* behaviorContext) - { - behaviorContext->Class("AWSScriptBehaviorDynamoDB") - ->Attribute(AZ::Script::Attributes::Category, "AWSCore") - ->Method("GetItem", &AWSScriptBehaviorDynamoDB::GetItem, - {{{"Table Resource KeyName", "The name of the table containing the requested item."}, - {"Key Map", "A map of attribute names to AttributeValue objects, representing the primary key of the item to retrieve."}}}) - ->Method("GetItemRaw", &AWSScriptBehaviorDynamoDB::GetItemRaw, - {{{"Table Name", "The name of the table containing the requested item."}, - {"Key Map", "A map of attribute names to AttributeValue objects, representing the primary key of the item to retrieve."}, - {"Region Name", "The region of the table located in."}}}); + if (AZ::BehaviorContext* behaviorContext = azrtti_cast(context)) + { + behaviorContext->Class("AWSScriptBehaviorDynamoDB") + ->Attribute(AZ::Script::Attributes::Category, "AWSCore") + ->Method("GetItem", &AWSScriptBehaviorDynamoDB::GetItem, + {{{"Table Resource KeyName", "The name of the table containing the requested item."}, + {"Key Map", "A map of attribute names to AttributeValue objects, representing the primary key of the item to retrieve."}}}) + ->Method("GetItemRaw", &AWSScriptBehaviorDynamoDB::GetItemRaw, + {{{"Table Name", "The name of the table containing the requested item."}, + {"Key Map", "A map of attribute names to AttributeValue objects, representing the primary key of the item to retrieve."}, + {"Region Name", "The region of the table located in."}}}); - behaviorContext->EBus("AWSDynamoDBBehaviorNotificationBus") - ->Attribute(AZ::Script::Attributes::Category, "AWSCore") - ->Handler(); - } - - void AWSScriptBehaviorDynamoDB::ReflectEditParameters(AZ::EditContext* editContext) - { - AZ_UNUSED(editContext); + behaviorContext->EBus("AWSDynamoDBBehaviorNotificationBus") + ->Attribute(AZ::Script::Attributes::Category, "AWSCore") + ->Handler(); + } } void AWSScriptBehaviorDynamoDB::GetItem(const AZStd::string& tableResourceKey, const DynamoDBAttributeValueMap& keyMap) diff --git a/Gems/AWSCore/Code/Source/ScriptCanvas/AWSScriptBehaviorLambda.cpp b/Gems/AWSCore/Code/Source/ScriptCanvas/AWSScriptBehaviorLambda.cpp index a7420654de..d4137c313b 100644 --- a/Gems/AWSCore/Code/Source/ScriptCanvas/AWSScriptBehaviorLambda.cpp +++ b/Gems/AWSCore/Code/Source/ScriptCanvas/AWSScriptBehaviorLambda.cpp @@ -21,39 +21,30 @@ namespace AWSCore { - AWSScriptBehaviorLambda::AWSScriptBehaviorLambda() + void AWSScriptBehaviorLambda::Reflect(AZ::ReflectContext* context) { - } - - void AWSScriptBehaviorLambda::ReflectSerialization(AZ::SerializeContext* serializeContext) - { - if (serializeContext) + if (AZ::SerializeContext* serializeContext = azrtti_cast(context)) { serializeContext->Class() ->Version(0); } - } - void AWSScriptBehaviorLambda::ReflectBehaviors(AZ::BehaviorContext* behaviorContext) - { - behaviorContext->Class("AWSScriptBehaviorLambda") - ->Attribute(AZ::Script::Attributes::Category, "AWSCore") - ->Method("Invoke", &AWSScriptBehaviorLambda::Invoke, - {{{"Function Resource KeyName", "The resource key name of the lambda function in resource mapping config file."}, - {"Payload", "The JSON that you want to provide to your Lambda function as input."}}}) - ->Method("InvokeRaw", &AWSScriptBehaviorLambda::InvokeRaw, - {{{"Function Name", "The name of the Lambda function, version, or alias."}, - {"Payload", "The JSON that you want to provide to your Lambda function as input."}, - {"Region Name", "The region of the lambda function located in."}}}); + if (AZ::BehaviorContext* behaviorContext = azrtti_cast(context)) + { + behaviorContext->Class("AWSScriptBehaviorLambda") + ->Attribute(AZ::Script::Attributes::Category, "AWSCore") + ->Method("Invoke", &AWSScriptBehaviorLambda::Invoke, + {{{"Function Resource KeyName", "The resource key name of the lambda function in resource mapping config file."}, + {"Payload", "The JSON that you want to provide to your Lambda function as input."}}}) + ->Method("InvokeRaw", &AWSScriptBehaviorLambda::InvokeRaw, + {{{"Function Name", "The name of the Lambda function, version, or alias."}, + {"Payload", "The JSON that you want to provide to your Lambda function as input."}, + {"Region Name", "The region of the lambda function located in."}}}); - behaviorContext->EBus("AWSLambdaBehaviorNotificationBus") - ->Attribute(AZ::Script::Attributes::Category, "AWSCore") - ->Handler(); - } - - void AWSScriptBehaviorLambda::ReflectEditParameters(AZ::EditContext* editContext) - { - AZ_UNUSED(editContext); + behaviorContext->EBus("AWSLambdaBehaviorNotificationBus") + ->Attribute(AZ::Script::Attributes::Category, "AWSCore") + ->Handler(); + } } void AWSScriptBehaviorLambda::Invoke(const AZStd::string& functionResourceKey, const AZStd::string& payload) diff --git a/Gems/AWSCore/Code/Source/ScriptCanvas/AWSScriptBehaviorS3.cpp b/Gems/AWSCore/Code/Source/ScriptCanvas/AWSScriptBehaviorS3.cpp index f47dda8046..e82fff0c31 100644 --- a/Gems/AWSCore/Code/Source/ScriptCanvas/AWSScriptBehaviorS3.cpp +++ b/Gems/AWSCore/Code/Source/ScriptCanvas/AWSScriptBehaviorS3.cpp @@ -24,49 +24,39 @@ namespace AWSCore { - AWSScriptBehaviorS3::AWSScriptBehaviorS3() + void AWSScriptBehaviorS3::Reflect(AZ::ReflectContext* context) { - } - - void AWSScriptBehaviorS3::ReflectSerialization(AZ::SerializeContext* serializeContext) - { - if (serializeContext) + if (AZ::SerializeContext* serializeContext = azrtti_cast(context)) { serializeContext->Class() ->Version(0); } - } - void AWSScriptBehaviorS3::ReflectBehaviors(AZ::BehaviorContext* behaviorContext) - { - behaviorContext->Class(AWSScriptBehaviorS3Name) - ->Attribute(AZ::Script::Attributes::Category, "AWSCore") - ->Method("GetObject", &AWSScriptBehaviorS3::GetObject, - {{{"Bucket Resource KeyName", "The resource key name of the bucket in resource mapping config file."}, - {"Object KeyName", "The object key."}, - {"Outfile Name", "Filename where the content will be saved."}}}) - ->Method("GetObjectRaw", &AWSScriptBehaviorS3::GetObjectRaw, - {{{"Bucket Name", "The name of the bucket containing the object."}, - {"Object KeyName", "The object key."}, - {"Region Name", "The region of the bucket located in."}, - {"Outfile Name", "Filename where the content will be saved."}}}) - ->Method("HeadObject", &AWSScriptBehaviorS3::HeadObject, - {{{"Bucket Resource KeyName", "The resource key name of the bucket in resource mapping config file."}, - {"Object KeyName", "The object key."}}}) - ->Method("HeadObjectRaw", &AWSScriptBehaviorS3::HeadObjectRaw, - {{{"Bucket Name", "The name of the bucket containing the object."}, - {"Object KeyName", "The object key."}, - {"Region Name", "The region of the bucket located in."}}}) - ; + if (AZ::BehaviorContext* behaviorContext = azrtti_cast(context)) + { + behaviorContext->Class(AWSScriptBehaviorS3Name) + ->Attribute(AZ::Script::Attributes::Category, "AWSCore") + ->Method("GetObject", &AWSScriptBehaviorS3::GetObject, + {{{"Bucket Resource KeyName", "The resource key name of the bucket in resource mapping config file."}, + {"Object KeyName", "The object key."}, + {"Outfile Name", "Filename where the content will be saved."}}}) + ->Method("GetObjectRaw", &AWSScriptBehaviorS3::GetObjectRaw, + {{{"Bucket Name", "The name of the bucket containing the object."}, + {"Object KeyName", "The object key."}, + {"Region Name", "The region of the bucket located in."}, + {"Outfile Name", "Filename where the content will be saved."}}}) + ->Method("HeadObject", &AWSScriptBehaviorS3::HeadObject, + {{{"Bucket Resource KeyName", "The resource key name of the bucket in resource mapping config file."}, + {"Object KeyName", "The object key."}}}) + ->Method("HeadObjectRaw", &AWSScriptBehaviorS3::HeadObjectRaw, + {{{"Bucket Name", "The name of the bucket containing the object."}, + {"Object KeyName", "The object key."}, + {"Region Name", "The region of the bucket located in."}}}); - behaviorContext->EBus("AWSS3BehaviorNotificationBus") - ->Attribute(AZ::Script::Attributes::Category, "AWSCore") - ->Handler(); - } - - void AWSScriptBehaviorS3::ReflectEditParameters(AZ::EditContext* editContext) - { - AZ_UNUSED(editContext); + behaviorContext->EBus("AWSS3BehaviorNotificationBus") + ->Attribute(AZ::Script::Attributes::Category, "AWSCore") + ->Handler(); + } } void AWSScriptBehaviorS3::GetObject( diff --git a/Gems/AWSCore/Code/Source/ScriptCanvas/AWSScriptBehaviorsComponent.cpp b/Gems/AWSCore/Code/Source/ScriptCanvas/AWSScriptBehaviorsComponent.cpp index 530c35cde2..4c41dc3f0e 100644 --- a/Gems/AWSCore/Code/Source/ScriptCanvas/AWSScriptBehaviorsComponent.cpp +++ b/Gems/AWSCore/Code/Source/ScriptCanvas/AWSScriptBehaviorsComponent.cpp @@ -12,35 +12,17 @@ namespace AWSCore { - AZStd::vector> AWSScriptBehaviorsComponent::m_behaviors; - bool AWSScriptBehaviorsComponent::m_alreadyAddedBehaviors = false; - - void AWSScriptBehaviorsComponent::AddBehaviors() - { - if (!m_alreadyAddedBehaviors) - { - // Add new script behaviors here - m_behaviors.push_back(AZStd::make_unique()); - m_behaviors.push_back(AZStd::make_unique()); - m_behaviors.push_back(AZStd::make_unique()); - m_alreadyAddedBehaviors = true; - } - } - void AWSScriptBehaviorsComponent::Reflect(AZ::ReflectContext* context) { - AddBehaviors(); + AWSScriptBehaviorDynamoDB::Reflect(context); + AWSScriptBehaviorLambda::Reflect(context); + AWSScriptBehaviorS3::Reflect(context); if (AZ::SerializeContext* serialize = azrtti_cast(context)) { serialize->Class() ->Version(0); - for (auto&& behavior : m_behaviors) - { - behavior->ReflectSerialization(serialize); - } - if (AZ::EditContext* editContext = serialize->GetEditContext()) { editContext->Class("AWSScriptBehaviors", "Provides ScriptCanvas functions for calling AWS") @@ -49,19 +31,6 @@ namespace AWSCore ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("AWS")) ->Attribute(AZ::Edit::Attributes::AutoExpand, true) ; - - for (auto&& behavior : m_behaviors) - { - behavior->ReflectEditParameters(editContext); - } - } - } - - if (AZ::BehaviorContext* behaviorContext = azrtti_cast(context)) - { - for (auto&& behavior : m_behaviors) - { - behavior->ReflectBehaviors(behaviorContext); } } } @@ -86,31 +55,12 @@ namespace AWSCore AZ_UNUSED(dependent); } - void AWSScriptBehaviorsComponent::Init() - { - for (auto&& behavior : m_behaviors) - { - behavior->Init(); - } - } - void AWSScriptBehaviorsComponent::Activate() { - for (auto&& behavior : m_behaviors) - { - behavior->Activate(); - } } void AWSScriptBehaviorsComponent::Deactivate() { - for (auto&& behavior : m_behaviors) - { - behavior->Deactivate(); - } - - // this forces the vector to release its capacity, clear/shrink_to_fit is not - m_behaviors.swap(AZStd::vector>()); } } diff --git a/Gems/AWSCore/Code/Tests/ScriptCanvas/AWSScriptBehaviorsComponentTest.cpp b/Gems/AWSCore/Code/Tests/ScriptCanvas/AWSScriptBehaviorsComponentTest.cpp index 15ad297f9b..2283caa69e 100644 --- a/Gems/AWSCore/Code/Tests/ScriptCanvas/AWSScriptBehaviorsComponentTest.cpp +++ b/Gems/AWSCore/Code/Tests/ScriptCanvas/AWSScriptBehaviorsComponentTest.cpp @@ -15,18 +15,6 @@ using namespace AWSCore; -class AWSScriptBehaviorsComponentMock - : public AWSScriptBehaviorsComponent -{ -public: - AZ_COMPONENT(AWSScriptBehaviorsComponentMock, "{78579706-E1B2-4788-A34D-A58D3F273FF9}"); - - int GetBehaviorsNum() - { - return m_behaviors.size(); - } -}; - class AWSScriptBehaviorsComponentTest : public UnitTest::ScopedAllocatorSetupFixture { @@ -38,7 +26,7 @@ public: m_behaviorContext = AZStd::make_unique(); m_entity = AZStd::make_unique(); - m_scriptBehaviorsComponent.reset(m_entity->CreateComponent()); + m_scriptBehaviorsComponent.reset(m_entity->CreateComponent()); } void TearDown() override @@ -56,20 +44,16 @@ protected: AZStd::unique_ptr m_serializeContext; AZStd::unique_ptr m_behaviorContext; AZStd::unique_ptr m_componentDescriptor; - AZStd::unique_ptr m_scriptBehaviorsComponent; + AZStd::unique_ptr m_scriptBehaviorsComponent; AZStd::unique_ptr m_entity; }; -TEST_F(AWSScriptBehaviorsComponentTest, InitActivateDeactivate_Call_GetExpectedNumOfAddedBehaviors) +TEST_F(AWSScriptBehaviorsComponentTest, Reflect) { - m_componentDescriptor.reset(AWSScriptBehaviorsComponentMock::CreateDescriptor()); + int oldEBusNum = m_behaviorContext->m_ebuses.size(); + m_componentDescriptor.reset(AWSScriptBehaviorsComponent::CreateDescriptor()); m_componentDescriptor->Reflect(m_serializeContext.get()); m_componentDescriptor->Reflect(m_behaviorContext.get()); - EXPECT_TRUE(AWSScriptBehaviorsComponentMock::AddedBehaviours()); - EXPECT_TRUE(m_scriptBehaviorsComponent->GetBehaviorsNum() == 3); - m_entity->Init(); - m_entity->Activate(); - m_entity->Deactivate(); - EXPECT_TRUE(m_scriptBehaviorsComponent->GetBehaviorsNum() == 0); + EXPECT_TRUE(m_behaviorContext->m_ebuses.size() - oldEBusNum == 3); } diff --git a/Gems/AWSCore/Code/awscore_files.cmake b/Gems/AWSCore/Code/awscore_files.cmake index c1577ec1eb..8e8ed280f8 100644 --- a/Gems/AWSCore/Code/awscore_files.cmake +++ b/Gems/AWSCore/Code/awscore_files.cmake @@ -32,7 +32,6 @@ set(FILES Include/Public/Framework/ServiceRequestJobConfig.h Include/Public/Framework/Util.h Include/Public/ResourceMapping/AWSResourceMappingBus.h - Include/Public/ScriptCanvas/AWSScriptBehaviorBase.h Include/Public/ScriptCanvas/AWSScriptBehaviorDynamoDB.h Include/Public/ScriptCanvas/AWSScriptBehaviorLambda.h Include/Public/ScriptCanvas/AWSScriptBehaviorS3.h From 8f4ded8c643febdf1364860bfedd255a01309060 Mon Sep 17 00:00:00 2001 From: Chris Galvan Date: Wed, 14 Jul 2021 12:10:49 -0500 Subject: [PATCH 06/21] Use the proper undo sequence so that viewport camera changes are saved properly. Signed-off-by: Chris Galvan --- Code/Editor/EditorViewportWidget.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Code/Editor/EditorViewportWidget.cpp b/Code/Editor/EditorViewportWidget.cpp index 3f86260f8d..c2f16a84b1 100644 --- a/Code/Editor/EditorViewportWidget.cpp +++ b/Code/Editor/EditorViewportWidget.cpp @@ -1896,7 +1896,7 @@ void EditorViewportWidget::SetViewTM(const Matrix34& viewTM, bool bMoveOnly) if (m_pressedKeyState != KeyPressedState::PressedInPreviousFrame) { - CUndo undo("Move Camera"); + AzToolsFramework::ScopedUndoBatch undo("Move Camera"); if (bMoveOnly) { // specify eObjectUpdateFlags_UserInput so that an undo command gets logged @@ -1932,7 +1932,7 @@ void EditorViewportWidget::SetViewTM(const Matrix34& viewTM, bool bMoveOnly) if (m_pressedKeyState != KeyPressedState::PressedInPreviousFrame) { - CUndo undo("Move Camera"); + AzToolsFramework::ScopedUndoBatch undo("Move Camera"); if (bMoveOnly) { AZ::TransformBus::Event( @@ -1945,6 +1945,8 @@ void EditorViewportWidget::SetViewTM(const Matrix34& viewTM, bool bMoveOnly) m_viewEntityId, &AZ::TransformInterface::SetWorldTM, LYTransformToAZTransform(camMatrix)); } + + AzToolsFramework::ToolsApplicationRequestBus::Broadcast(&AzToolsFramework::ToolsApplicationRequests::AddDirtyEntity, m_viewEntityId); } else { From cd819062f9336f0ad313b4a7273f97b9d44059c1 Mon Sep 17 00:00:00 2001 From: AMZN-tpeng <82184807+AMZN-tpeng@users.noreply.github.com> Date: Wed, 14 Jul 2021 10:20:39 -0700 Subject: [PATCH 07/21] [ATOM][RHI][Vulkan][Android] - use bit conversion from enum to check for gpu query type support (#2141) ATOM-15742 Signed-off-by: Tony Peng Signed-off-by: Peng --- .../Atom/RPI/Code/Source/RPI.Public/GpuQuery/GpuQuerySystem.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/GpuQuery/GpuQuerySystem.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/GpuQuery/GpuQuerySystem.cpp index bf3107cfdf..5d621094ea 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/GpuQuery/GpuQuerySystem.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/GpuQuery/GpuQuerySystem.cpp @@ -116,7 +116,7 @@ namespace AZ { AZ_Assert(IsQueryTypeValid(queryType), "Provided QueryType is invalid"); - return static_cast(m_queryTypeSupport) & static_cast(queryType); + return static_cast(m_queryTypeSupport) & AZ_BIT(static_cast(queryType)); } RPI::QueryPool* GpuQuerySystem::GetQueryPoolByType(RHI::QueryType queryType) From d6d644501a26272adb3fddbf90ca5bfa733a5b48 Mon Sep 17 00:00:00 2001 From: AMZN-tpeng <82184807+AMZN-tpeng@users.noreply.github.com> Date: Wed, 14 Jul 2021 10:22:49 -0700 Subject: [PATCH 08/21] [ATOM][RHI][Vulkan][Android] - Fix sample count validation error for android. (#2145) ATOM-15755 Signed-off-by: Tony Peng Signed-off-by: Peng --- Gems/Atom/RHI/Vulkan/Code/Source/RHI/Image.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Gems/Atom/RHI/Vulkan/Code/Source/RHI/Image.cpp b/Gems/Atom/RHI/Vulkan/Code/Source/RHI/Image.cpp index 7586183b63..61149c19f7 100644 --- a/Gems/Atom/RHI/Vulkan/Code/Source/RHI/Image.cpp +++ b/Gems/Atom/RHI/Vulkan/Code/Source/RHI/Image.cpp @@ -218,7 +218,8 @@ namespace AZ createInfo.extent = extent; createInfo.mipLevels = AZStd::min(descriptor.m_mipLevels, formatProps.maxMipLevels); createInfo.arrayLayers = AZStd::min(descriptor.m_arraySize, formatProps.maxArrayLayers); - createInfo.samples = static_cast(RHI::FilterBits(static_cast(ConvertSampleCount(descriptor.m_multisampleState.m_samples)), formatProps.sampleCounts)); + VkSampleCountFlagBits sampleCountFlagBits = static_cast(RHI::FilterBits(static_cast(ConvertSampleCount(descriptor.m_multisampleState.m_samples)), formatProps.sampleCounts)); + createInfo.samples = (static_cast(sampleCountFlagBits) > 0) ? sampleCountFlagBits : VK_SAMPLE_COUNT_1_BIT; createInfo.tiling = VK_IMAGE_TILING_OPTIMAL; createInfo.usage = GetImageUsageFlags(); createInfo.sharingMode = exclusiveOwnership ? VK_SHARING_MODE_EXCLUSIVE : VK_SHARING_MODE_CONCURRENT; From 8f706c09d64b8d000b19d9b5066b1a6d6485e37c Mon Sep 17 00:00:00 2001 From: amzn-sean <75276488+amzn-sean@users.noreply.github.com> Date: Wed, 14 Jul 2021 20:16:56 +0100 Subject: [PATCH 09/21] update sc that is used in physics tests to use the 'PhysicsSystemInterface' instead of 'System Interface' (#2162) Signed-off-by: amzn-sean <75276488+amzn-sean@users.noreply.github.com> --- .../onpostphysicsupdate.scriptcanvas | 4 ++-- .../C14195074_ScriptCanvas_PostUpdateEvent.scriptcanvas | 4 ++-- .../C14902097_ScriptCanvas_PreUpdateEvent.scriptcanvas | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/AutomatedTesting/Levels/Physics/C14902098_ScriptCanvas_PostPhysicsUpdate/onpostphysicsupdate.scriptcanvas b/AutomatedTesting/Levels/Physics/C14902098_ScriptCanvas_PostPhysicsUpdate/onpostphysicsupdate.scriptcanvas index f4ca23b882..10c40fbb9f 100644 --- a/AutomatedTesting/Levels/Physics/C14902098_ScriptCanvas_PostPhysicsUpdate/onpostphysicsupdate.scriptcanvas +++ b/AutomatedTesting/Levels/Physics/C14902098_ScriptCanvas_PostPhysicsUpdate/onpostphysicsupdate.scriptcanvas @@ -742,14 +742,14 @@ - + - + diff --git a/AutomatedTesting/ScriptCanvas/C14195074_ScriptCanvas_PostUpdateEvent.scriptcanvas b/AutomatedTesting/ScriptCanvas/C14195074_ScriptCanvas_PostUpdateEvent.scriptcanvas index b9f438a4f3..ffdac5dd8c 100644 --- a/AutomatedTesting/ScriptCanvas/C14195074_ScriptCanvas_PostUpdateEvent.scriptcanvas +++ b/AutomatedTesting/ScriptCanvas/C14195074_ScriptCanvas_PostUpdateEvent.scriptcanvas @@ -1045,14 +1045,14 @@ - + - + diff --git a/AutomatedTesting/ScriptCanvas/C14902097_ScriptCanvas_PreUpdateEvent.scriptcanvas b/AutomatedTesting/ScriptCanvas/C14902097_ScriptCanvas_PreUpdateEvent.scriptcanvas index 7db4f3a35c..4954eb4684 100644 --- a/AutomatedTesting/ScriptCanvas/C14902097_ScriptCanvas_PreUpdateEvent.scriptcanvas +++ b/AutomatedTesting/ScriptCanvas/C14902097_ScriptCanvas_PreUpdateEvent.scriptcanvas @@ -1085,14 +1085,14 @@ - + - + From ffcfa44b49825fe3305577f6635c72d09639ec4d Mon Sep 17 00:00:00 2001 From: jackalbe <23512001+jackalbe@users.noreply.github.com> Date: Wed, 14 Jul 2021 14:54:22 -0500 Subject: [PATCH 10/21] {LYN-4514} Engine updates to enable PAB for the Blast Gem (#2140) * {LYN-4514} Engine updates to enable PAB for the Blast Gem * Engine updates to enable Python Asset Building for the Blast Gem * added API to detect IsPythonActive() * ExportProductList behavior * scene manifest usage of generated assetinfo * updated ScriptProcessorRuleBehavior to handle OnPrepareForExport Tests: new tests for scene behavior via ExportProduct Signed-off-by: Jackson <23512001+jackalbe@users.noreply.github.com> * updated base on PR feedback added more comments for IsPythonActive() added a serializeContext for export product list Signed-off-by: Jackson <23512001+jackalbe@users.noreply.github.com> --- .../AzCore/AzCore/Math/MathReflection.cpp | 2 +- .../API/EditorPythonConsoleBus.h | 3 + .../Components/ExportingComponent.cpp | 8 + Code/Tools/SceneAPI/SceneCore/DllMain.cpp | 1 + .../SceneCore/Events/ExportProductList.cpp | 41 + .../SceneCore/Events/ExportProductList.h | 11 + .../Import/ManifestImportRequestHandler.cpp | 12 +- .../Tests/Containers/SceneBehaviorTests.cpp | 1421 +++++++++-------- .../Behaviors/ScriptProcessorRuleBehavior.cpp | 478 +++--- .../Behaviors/ScriptProcessorRuleBehavior.h | 66 +- .../Code/Source/PythonSystemComponent.cpp | 17 +- .../Code/Source/PythonSystemComponent.h | 1 + 12 files changed, 1138 insertions(+), 923 deletions(-) diff --git a/Code/Framework/AzCore/AzCore/Math/MathReflection.cpp b/Code/Framework/AzCore/AzCore/Math/MathReflection.cpp index 46717440d5..82e230d54a 100644 --- a/Code/Framework/AzCore/AzCore/Math/MathReflection.cpp +++ b/Code/Framework/AzCore/AzCore/Math/MathReflection.cpp @@ -116,7 +116,7 @@ namespace AZ { const char* uuidString = nullptr; unsigned int uuidStringLength = 0; - if (dc.ReadArg(0, uuidString) && dc.ReadValue(1, uuidStringLength)) + if (dc.ReadArg(0, uuidString) && dc.ReadArg(1, uuidStringLength)) { dc.PushResult(Uuid(uuidString, uuidStringLength)); } diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/API/EditorPythonConsoleBus.h b/Code/Framework/AzToolsFramework/AzToolsFramework/API/EditorPythonConsoleBus.h index 51aecbd8e7..6dd6b0b448 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/API/EditorPythonConsoleBus.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/API/EditorPythonConsoleBus.h @@ -63,6 +63,9 @@ namespace AzToolsFramework //! Signal the Python handler to stop virtual bool StopPython(bool silenceWarnings = false) = 0; + //! Query to determine if the Python VM has been initialized indicating an active state + virtual bool IsPythonActive() = 0; + //! Determines if the caller needs to wait for the Python VM to initialize (non-main thread only) virtual void WaitForInitialization() {} diff --git a/Code/Tools/SceneAPI/SceneCore/Components/ExportingComponent.cpp b/Code/Tools/SceneAPI/SceneCore/Components/ExportingComponent.cpp index 62daba1197..911f469b2f 100644 --- a/Code/Tools/SceneAPI/SceneCore/Components/ExportingComponent.cpp +++ b/Code/Tools/SceneAPI/SceneCore/Components/ExportingComponent.cpp @@ -7,6 +7,8 @@ #include #include +#include +#include namespace AZ { @@ -31,6 +33,12 @@ namespace AZ { serializeContext->Class()->Version(2); } + + AZ::BehaviorContext* behaviorContext = azrtti_cast(context); + if (behaviorContext) + { + Events::ExportProductList::Reflect(behaviorContext); + } } } // namespace SceneCore } // namespace SceneAPI diff --git a/Code/Tools/SceneAPI/SceneCore/DllMain.cpp b/Code/Tools/SceneAPI/SceneCore/DllMain.cpp index 7874ada7be..ac3c307b6c 100644 --- a/Code/Tools/SceneAPI/SceneCore/DllMain.cpp +++ b/Code/Tools/SceneAPI/SceneCore/DllMain.cpp @@ -211,6 +211,7 @@ namespace AZ AZ::SceneAPI::Containers::SceneGraph::Reflect(context); AZ::SceneAPI::Containers::SceneManifest::Reflect(context); AZ::SceneAPI::Containers::RuleContainer::Reflect(context); + AZ::SceneAPI::SceneCore::ExportingComponent::Reflect(context); } void Activate() diff --git a/Code/Tools/SceneAPI/SceneCore/Events/ExportProductList.cpp b/Code/Tools/SceneAPI/SceneCore/Events/ExportProductList.cpp index c3a9abcbd2..798978024e 100644 --- a/Code/Tools/SceneAPI/SceneCore/Events/ExportProductList.cpp +++ b/Code/Tools/SceneAPI/SceneCore/Events/ExportProductList.cpp @@ -6,6 +6,8 @@ */ #include +#include +#include namespace AZ { @@ -49,6 +51,45 @@ namespace AZ return *this; } + void ExportProductList::Reflect(ReflectContext* context) + { + if (auto* serializeContext = azrtti_cast(context)) + { + serializeContext->Class()->Version(1); + serializeContext->Class()->Version(1); + } + + if (auto* behaviorContext = azrtti_cast(context)) + { + behaviorContext->Class("ExportProduct") + ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common) + ->Attribute(AZ::Script::Attributes::Module, "scene") + ->Property("filename", BehaviorValueProperty(&ExportProduct::m_filename)) + ->Property("sourceId", BehaviorValueProperty(&ExportProduct::m_id)) + ->Property("assetType", BehaviorValueProperty(&ExportProduct::m_assetType)) + ->Property("productDependencies", BehaviorValueProperty(&ExportProduct::m_productDependencies)) + ->Property("subId", + [](ExportProduct* self) { return self->m_subId.has_value() ? self->m_subId.value() : 0; }, + [](ExportProduct* self, u32 subId) { self->m_subId = AZStd::optional(subId); }); + + behaviorContext->Class("ExportProductList") + ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common) + ->Attribute(AZ::Script::Attributes::Module, "scene") + ->Method("AddProduct", [](ExportProductList& self, ExportProduct& product) + { + self.AddProduct( + product.m_filename, + product.m_id, + product.m_assetType, + product.m_lod, + product.m_subId, + product.m_dependencyFlags); + }) + ->Method("GetProducts", &ExportProductList::GetProducts) + ->Method("AddDependencyToProduct", &ExportProductList::AddDependencyToProduct); + } + } + ExportProduct& ExportProductList::AddProduct(const AZStd::string& filename, Uuid id, Data::AssetType assetType, AZStd::optional lod, AZStd::optional subId, Data::ProductDependencyInfo::ProductDependencyFlags dependencyFlags) { diff --git a/Code/Tools/SceneAPI/SceneCore/Events/ExportProductList.h b/Code/Tools/SceneAPI/SceneCore/Events/ExportProductList.h index 38deea3f9f..a99303e08b 100644 --- a/Code/Tools/SceneAPI/SceneCore/Events/ExportProductList.h +++ b/Code/Tools/SceneAPI/SceneCore/Events/ExportProductList.h @@ -14,6 +14,8 @@ namespace AZ { + class ReflectContext; + namespace SceneAPI { namespace Events @@ -24,6 +26,7 @@ namespace AZ Data::ProductDependencyInfo::ProductDependencyFlags dependencyFlags = Data::ProductDependencyInfo::CreateFlags(Data::AssetLoadBehavior::NoLoad)); SCENE_CORE_API ExportProduct(AZStd::string&& filename, Uuid id, Data::AssetType assetType, AZStd::optional lod, AZStd::optional subId, Data::ProductDependencyInfo::ProductDependencyFlags dependencyFlags = Data::ProductDependencyInfo::CreateFlags(Data::AssetLoadBehavior::NoLoad)); + ExportProduct() = default; ExportProduct(const ExportProduct& rhs) = default; SCENE_CORE_API ExportProduct(ExportProduct&& rhs); @@ -54,6 +57,8 @@ namespace AZ class ExportProductList { public: + static void Reflect(ReflectContext* context); + SCENE_CORE_API ExportProduct& AddProduct(const AZStd::string& filename, Uuid id, Data::AssetType assetType, AZStd::optional lod, AZStd::optional subId, Data::ProductDependencyInfo::ProductDependencyFlags dependencyFlags = Data::ProductDependencyInfo::CreateFlags(Data::AssetLoadBehavior::NoLoad)); SCENE_CORE_API ExportProduct& AddProduct(AZStd::string&& filename, Uuid id, Data::AssetType assetType, AZStd::optional lod, AZStd::optional subId, @@ -69,3 +74,9 @@ namespace AZ } // namespace Events } // namespace SceneAPI } // namespace AZ + +namespace AZ +{ + AZ_TYPE_INFO_SPECIALIZE(SceneAPI::Events::ExportProduct, "{6054EDCB-4C04-4D96-BF26-704999FFB725}"); + AZ_TYPE_INFO_SPECIALIZE(SceneAPI::Events::ExportProductList, "{1C76A51F-431B-4987-B653-CFCC940D0D0F}"); +} diff --git a/Code/Tools/SceneAPI/SceneCore/Import/ManifestImportRequestHandler.cpp b/Code/Tools/SceneAPI/SceneCore/Import/ManifestImportRequestHandler.cpp index a4b002db7d..073f357ee6 100644 --- a/Code/Tools/SceneAPI/SceneCore/Import/ManifestImportRequestHandler.cpp +++ b/Code/Tools/SceneAPI/SceneCore/Import/ManifestImportRequestHandler.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -75,15 +76,16 @@ namespace AZ filename += s_extension; filename += s_generated; - AZStd::string altManifestPath = path; + AZStd::string altManifestFolder = path; AzFramework::ApplicationRequests::Bus::Broadcast( - &AzFramework::ApplicationRequests::Bus::Events::MakePathRootRelative, - altManifestPath); + &AzFramework::ApplicationRequests::Bus::Events::MakePathRelative, + altManifestFolder, + AZ::Utils::GetProjectPath().c_str()); - AZ::StringFunc::Path::GetFolderPath(altManifestPath.c_str(), altManifestPath); + AZ::StringFunc::Path::GetFolderPath(altManifestFolder.c_str(), altManifestFolder); AZStd::string generatedAssetInfoPath; - AZ::StringFunc::Path::Join(assetCacheRoot.c_str(), altManifestPath.c_str(), generatedAssetInfoPath); + AZ::StringFunc::Path::Join(assetCacheRoot.c_str(), altManifestFolder.c_str(), generatedAssetInfoPath); AZ::StringFunc::Path::ConstructFull(generatedAssetInfoPath.c_str(), filename.c_str(), generatedAssetInfoPath); if (!AZ::IO::FileIOBase::GetInstance()->Exists(generatedAssetInfoPath.c_str())) diff --git a/Code/Tools/SceneAPI/SceneCore/Tests/Containers/SceneBehaviorTests.cpp b/Code/Tools/SceneAPI/SceneCore/Tests/Containers/SceneBehaviorTests.cpp index e1c72bc8b4..4e08181b83 100644 --- a/Code/Tools/SceneAPI/SceneCore/Tests/Containers/SceneBehaviorTests.cpp +++ b/Code/Tools/SceneAPI/SceneCore/Tests/Containers/SceneBehaviorTests.cpp @@ -28,710 +28,731 @@ extern "C" AZ_DLL_EXPORT void ReflectBehavior(AZ::BehaviorContext* context); // the DLL entry point for SceneCore to reflect its serialize context extern "C" AZ_DLL_EXPORT void ReflectTypes(AZ::SerializeContext* context); -namespace AZ +namespace AZ::SceneAPI::Containers { - namespace SceneAPI + class MockManifestRule : public DataTypes::IManifestObject { - namespace Containers + public: + AZ_RTTI(MockManifestRule, "{D6F96B48-4E6F-4EE8-A5A3-959B76F90DA8}", IManifestObject); + AZ_CLASS_ALLOCATOR(MockManifestRule, AZ::SystemAllocator, 0); + + MockManifestRule() = default; + + MockManifestRule(double value) + : m_value(value) { - class MockManifestRule : public DataTypes::IManifestObject + } + + double GetValue() const + { + return m_value; + } + + void SetValue(double value) + { + m_value = value; + } + + static void Reflect(AZ::ReflectContext* context) + { + AZ::SerializeContext* serializeContext = azrtti_cast(context); + if (serializeContext) { - public: - AZ_RTTI(MockManifestRule, "{D6F96B48-4E6F-4EE8-A5A3-959B76F90DA8}", IManifestObject); - AZ_CLASS_ALLOCATOR(MockManifestRule, AZ::SystemAllocator, 0); - - MockManifestRule() = default; - - MockManifestRule(double value) - : m_value(value) - { - } - - double GetValue() const - { - return m_value; - } - - void SetValue(double value) - { - m_value = value; - } - - static void Reflect(AZ::ReflectContext* context) - { - AZ::SerializeContext* serializeContext = azrtti_cast(context); - if (serializeContext) - { - serializeContext->Class() - ->Version(1) - ->Field("value", &MockManifestRule::m_value); - } - } - - private: - double m_value = 0.0; - }; - - struct MockBuilder final - { - AZ_TYPE_INFO(MockBuilder, "{ECF0FB2C-E5C0-4B89-993C-8511A7EF6894}"); - - AZStd::unique_ptr m_scene; - - MockBuilder() - { - m_scene = AZStd::make_unique("unit_scene"); - } - - ~MockBuilder() - { - m_scene.reset(); - } - - void BuildSceneGraph() - { - m_scene->SetManifestFilename("manifest_filename"); - m_scene->SetSource("unit_source_filename", azrtti_typeid()); - - auto& graph = m_scene->GetGraph(); - - /*----------------------------\ - | Root | - | / \ | - | | | | - | A B | - | | /|\ | - | C I J K | - | / | \ \ | - | D E F L | - | / \ | - | G H | - \----------------------------*/ - - //Build up the graph - const auto indexA = graph.AddChild(graph.GetRoot(), "A", AZStd::make_shared(1)); - const auto indexC = graph.AddChild(indexA, "C", AZStd::make_shared(3)); - const auto indexE = graph.AddChild(indexC, "E", AZStd::make_shared(4)); - graph.AddChild(indexC, "D", AZStd::make_shared(5)); - graph.AddChild(indexC, "F", AZStd::make_shared(6)); - graph.AddChild(indexE, "G", AZStd::make_shared(7)); - graph.AddChild(indexE, "H", AZStd::make_shared(8)); - const auto indexB = graph.AddChild(graph.GetRoot(), "B", AZStd::make_shared(2)); - const auto indexK = graph.AddChild(indexB, "K", AZStd::make_shared(2)); - graph.AddChild(indexB, "I", AZStd::make_shared(9)); - graph.AddChild(indexB, "J", AZStd::make_shared(10)); - graph.AddChild(indexK, "L", AZStd::make_shared(12)); - - m_scene->GetManifest().AddEntry(AZStd::make_shared(0.1)); - m_scene->GetManifest().AddEntry(AZStd::make_shared(2.3)); - m_scene->GetManifest().AddEntry(AZStd::make_shared(4.5)); - } - - static void Reflect(ReflectContext* context) - { - BehaviorContext* behaviorContext = azrtti_cast(context); - if (behaviorContext) - { - behaviorContext->Class() - ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common) - ->Attribute(AZ::Script::Attributes::Module, "scene") - ->Method("BuildSceneGraph", [](MockBuilder& self) - { - return self.BuildSceneGraph(); - }) - ->Method("GetScene", [](MockBuilder& self) - { - return self.m_scene.get(); - }); - } - } - }; - - class SceneGraphBehaviorTest - : public ::testing::Test - { - public: - void SetUp() override - { - m_behaviorContext = AZStd::make_unique(); - ReflectBehavior(m_behaviorContext.get()); - } - - void TearDown() override - { - m_behaviorContext.reset(); - } - - AZ::BehaviorClass* GetBehaviorClass(const AZ::TypeId& behaviorClassType) - { - auto entry = m_behaviorContext->m_typeToClassMap.find(behaviorClassType); - return (entry != m_behaviorContext->m_typeToClassMap.end()) ? entry->second : nullptr; - } - - AZ::BehaviorProperty* GetBehaviorProperty(AZ::BehaviorClass& behaviorClass, AZStd::string_view propertyName) - { - auto entry = behaviorClass.m_properties.find(propertyName); - return (entry != behaviorClass.m_properties.end()) ? entry->second : nullptr; - } - - bool HasBehaviorClass(const AZ::TypeId& behaviorClassType) - { - return GetBehaviorClass(behaviorClassType) != nullptr; - } - - bool HasProperty(AZ::BehaviorClass& behaviorClass, AZStd::string_view propertyName, const AZ::TypeId& propertyClassType) - { - AZ::BehaviorProperty* behaviorProperty = GetBehaviorProperty(behaviorClass, propertyName); - if (behaviorProperty) - { - return behaviorProperty->m_getter->GetResult()->m_typeId == propertyClassType; - } - return false; - } - - using ArgList = AZStd::vector; - - bool HasMethodWithInput(AZ::BehaviorClass& behaviorClass, AZStd::string_view methodName, const ArgList& input) - { - auto entry = behaviorClass.m_methods.find(methodName); - if (entry == behaviorClass.m_methods.end()) - { - return false; - } - AZ::BehaviorMethod* method = entry->second; - - const size_t methodArgsCount = method->IsMember() ? method->GetNumArguments() - 1 : method->GetNumArguments(); - if (input.size() != methodArgsCount) - { - return false; - } - - for (size_t argIndex = 0; argIndex < input.size(); ++argIndex) - { - const size_t thisPointerOffset = method->IsMember() ? 1 : 0; - const auto argType = method->GetArgument(argIndex + thisPointerOffset)->m_typeId; - const auto inputType = input[argIndex]; - if (inputType != argType) - { - return false; - } - } - return true; - } - - bool HasMethodWithOutput(AZ::BehaviorClass& behaviorClass, AZStd::string_view methodName, const AZ::TypeId& output, const ArgList& input) - { - auto entry = behaviorClass.m_methods.find(methodName); - if (entry == behaviorClass.m_methods.end()) - { - return false; - } - AZ::BehaviorMethod* method = entry->second; - if (method->HasResult()) - { - if (method->GetResult()->m_typeId != output) - { - return false; - } - } - else - { - return false; - } - return HasMethodWithInput(behaviorClass, methodName, input); - } - - AZStd::unique_ptr m_behaviorContext; - }; - - TEST_F(SceneGraphBehaviorTest, SceneClass_BehaviorContext_Exists) - { - EXPECT_TRUE(HasBehaviorClass(azrtti_typeid())); - } - - TEST_F(SceneGraphBehaviorTest, SceneClass_BehaviorContext_HasExpectedProperties) - { - AZ::BehaviorClass* behaviorClass = GetBehaviorClass(azrtti_typeid()); - ASSERT_NE(nullptr, behaviorClass); - EXPECT_TRUE(HasProperty(*behaviorClass, "name", azrtti_typeid())); - EXPECT_TRUE(HasProperty(*behaviorClass, "manifestFilename", azrtti_typeid())); - EXPECT_TRUE(HasProperty(*behaviorClass, "sourceFilename", azrtti_typeid())); - EXPECT_TRUE(HasProperty(*behaviorClass, "sourceGuid", azrtti_typeid())); - EXPECT_TRUE(HasProperty(*behaviorClass, "graph", azrtti_typeid())); - EXPECT_TRUE(HasProperty(*behaviorClass, "manifest", azrtti_typeid())); - } - - TEST_F(SceneGraphBehaviorTest, SceneGraphClass_BehaviorContext_Exists) - { - EXPECT_TRUE(HasBehaviorClass(azrtti_typeid())); - EXPECT_TRUE(HasBehaviorClass(azrtti_typeid())); - EXPECT_TRUE(HasBehaviorClass(azrtti_typeid())); - } - - TEST_F(SceneGraphBehaviorTest, SceneGraphClass_BehaviorContext_HasExpectedProperties) - { - using namespace AZ::SceneAPI::Containers; - - AZ::BehaviorClass* behaviorClass = GetBehaviorClass(azrtti_typeid()); - ASSERT_NE(nullptr, behaviorClass); - EXPECT_TRUE(HasMethodWithOutput(*behaviorClass, "GetNodeName", azrtti_typeid(), { azrtti_typeid() })); - EXPECT_TRUE(HasMethodWithOutput(*behaviorClass, "GetRoot", azrtti_typeid(), {})); - EXPECT_TRUE(HasMethodWithOutput(*behaviorClass, "HasNodeContent", azrtti_typeid(), { azrtti_typeid() })); - EXPECT_TRUE(HasMethodWithOutput(*behaviorClass, "HasNodeSibling", azrtti_typeid(), { azrtti_typeid() })); - EXPECT_TRUE(HasMethodWithOutput(*behaviorClass, "HasNodeChild", azrtti_typeid(), { azrtti_typeid() })); - EXPECT_TRUE(HasMethodWithOutput(*behaviorClass, "HasNodeParent", azrtti_typeid(), { azrtti_typeid() })); - EXPECT_TRUE(HasMethodWithOutput(*behaviorClass, "IsNodeEndPoint", azrtti_typeid(), { azrtti_typeid() })); - EXPECT_TRUE(HasMethodWithOutput(*behaviorClass, "GetNodeCount", azrtti_typeid(), {})); - EXPECT_TRUE(HasMethodWithOutput( - *behaviorClass, - "GetNodeParent", - azrtti_typeid(), - { azrtti_typeid(), azrtti_typeid() } - )); - EXPECT_TRUE(HasMethodWithOutput( - *behaviorClass, - "GetNodeSibling", - azrtti_typeid(), - { azrtti_typeid(), azrtti_typeid() } - )); - EXPECT_TRUE(HasMethodWithOutput( - *behaviorClass, - "GetNodeChild", - azrtti_typeid(), - { azrtti_typeid(), azrtti_typeid() } - )); - EXPECT_TRUE(HasMethodWithOutput( - *behaviorClass, - "FindWithPath", - azrtti_typeid(), - { azrtti_typeid(), azrtti_typeid() } - )); - EXPECT_TRUE(HasMethodWithOutput( - *behaviorClass, - "FindWithRootAndPath", - azrtti_typeid(), - { azrtti_typeid(), azrtti_typeid(), azrtti_typeid() } - )); - } - - TEST_F(SceneGraphBehaviorTest, SceneGraphNodeIndexClass_BehaviorContext_HasExpectedProperties) - { - using namespace AZ::SceneAPI::Containers; - - AZ::BehaviorClass* behaviorClass = GetBehaviorClass(azrtti_typeid()); - ASSERT_NE(nullptr, behaviorClass); - EXPECT_TRUE(HasMethodWithOutput(*behaviorClass, "AsNumber", azrtti_typeid(), {})); - EXPECT_TRUE(HasMethodWithOutput(*behaviorClass, "Distance", azrtti_typeid(), { azrtti_typeid() })); - EXPECT_TRUE(HasMethodWithOutput(*behaviorClass, "IsValid", azrtti_typeid(), {})); - EXPECT_TRUE(HasMethodWithOutput(*behaviorClass, "Equal", azrtti_typeid(), { azrtti_typeid() })); - } - - TEST_F(SceneGraphBehaviorTest, SceneGraphNameClass_BehaviorContext_HasExpectedProperties) - { - using namespace AZ::SceneAPI::Containers; - - AZ::BehaviorClass* behaviorClass = GetBehaviorClass(azrtti_typeid()); - ASSERT_NE(nullptr, behaviorClass); - EXPECT_TRUE(HasMethodWithOutput(*behaviorClass, "GetPath", azrtti_typeid(), {})); - EXPECT_TRUE(HasMethodWithOutput(*behaviorClass, "GetName", azrtti_typeid(), {})); - } - - class MockSceneComponentApplication - : public AZ::ComponentApplicationBus::Handler - { - public: - MockSceneComponentApplication() - { - AZ::ComponentApplicationBus::Handler::BusConnect(); - AZ::Interface::Register(this); - } - - ~MockSceneComponentApplication() - { - AZ::Interface::Unregister(this); - AZ::ComponentApplicationBus::Handler::BusDisconnect(); - } - - MOCK_METHOD1(FindEntity, AZ::Entity* (const AZ::EntityId&)); - MOCK_METHOD1(AddEntity, bool(AZ::Entity*)); - MOCK_METHOD0(Destroy, void()); - MOCK_METHOD1(RegisterComponentDescriptor, void(const AZ::ComponentDescriptor*)); - MOCK_METHOD1(UnregisterComponentDescriptor, void(const AZ::ComponentDescriptor*)); - MOCK_METHOD1(RegisterEntityAddedEventHandler, void(AZ::EntityAddedEvent::Handler&)); - MOCK_METHOD1(RegisterEntityRemovedEventHandler, void(AZ::EntityRemovedEvent::Handler&)); - MOCK_METHOD1(RegisterEntityActivatedEventHandler, void(AZ::EntityActivatedEvent::Handler&)); - MOCK_METHOD1(RegisterEntityDeactivatedEventHandler, void(AZ::EntityDeactivatedEvent::Handler&)); - MOCK_METHOD1(SignalEntityActivated, void(AZ::Entity*)); - MOCK_METHOD1(SignalEntityDeactivated, void(AZ::Entity*)); - MOCK_METHOD1(RemoveEntity, bool(AZ::Entity*)); - MOCK_METHOD1(DeleteEntity, bool(const AZ::EntityId&)); - MOCK_METHOD1(GetEntityName, AZStd::string(const AZ::EntityId&)); - MOCK_METHOD1(EnumerateEntities, void(const ComponentApplicationRequests::EntityCallback&)); - MOCK_METHOD0(GetApplication, AZ::ComponentApplication* ()); - MOCK_METHOD0(GetSerializeContext, AZ::SerializeContext* ()); - MOCK_METHOD0(GetJsonRegistrationContext, AZ::JsonRegistrationContext* ()); - MOCK_METHOD0(GetBehaviorContext, AZ::BehaviorContext* ()); - MOCK_CONST_METHOD0(GetAppRoot, const char*()); - MOCK_CONST_METHOD0(GetEngineRoot, const char*()); - MOCK_CONST_METHOD0(GetExecutableFolder, const char* ()); - MOCK_METHOD0(GetDrillerManager, AZ::Debug::DrillerManager* ()); - MOCK_CONST_METHOD1(QueryApplicationType, void(AZ::ApplicationTypeQuery&)); - }; - - class MockEditorPythonConsoleInterface final - : public AzToolsFramework::EditorPythonConsoleInterface - { - public: - MockEditorPythonConsoleInterface() - { - AZ::Interface::Register(this); - } - - ~MockEditorPythonConsoleInterface() - { - AZ::Interface::Unregister(this); - } - - MOCK_CONST_METHOD1(GetModuleList, void(AZStd::vector&)); - MOCK_CONST_METHOD1(GetGlobalFunctionList, void(GlobalFunctionCollection&)); - MOCK_METHOD1(FetchPythonTypeName, AZStd::string(const AZ::BehaviorParameter&)); - }; - - // - // SceneGraphBehaviorScriptTest - // - class SceneGraphBehaviorScriptTest - : public UnitTest::AllocatorsFixture - { - public: - AZStd::unique_ptr m_componentApplication; - AZStd::unique_ptr m_editorPythonConsoleInterface; - AZStd::unique_ptr m_scriptContext; - AZStd::unique_ptr m_behaviorContext; - AZStd::unique_ptr m_serializeContext; - - static void TestExpectTrue(bool value) - { - EXPECT_TRUE(value); - } - - static void TestExpectEquals(AZ::s64 lhs, AZ::s64 rhs) - { - EXPECT_EQ(lhs, rhs); - } - - static void ReflectTestTypes(AZ::ReflectContext* context) - { - AZ::BehaviorContext* behaviorContext = azrtti_cast(context); - if (behaviorContext) - { - behaviorContext->Class() - ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common) - ->Attribute(AZ::Script::Attributes::Module, "scene.graph.test") - ->Method("GetId", [](const DataTypes::MockIGraphObject& self) - { - return self.m_id; - }) - ->Method("SetId", [](DataTypes::MockIGraphObject& self, int value) - { - self.m_id = value; - }) - ->Method("AddAndSet", [](DataTypes::MockIGraphObject& self, int lhs, int rhs) - { - self.m_id = lhs + rhs; - }); - } - } - - void SetUp() override - { - UnitTest::AllocatorsFixture::SetUp(); - - m_serializeContext = AZStd::make_unique(); - - m_behaviorContext = AZStd::make_unique(); - m_behaviorContext->Method("TestExpectTrue", &TestExpectTrue); - m_behaviorContext->Method("TestExpectEquals", &TestExpectEquals); - - AZ::MathReflect(m_behaviorContext.get()); - ReflectBehavior(m_behaviorContext.get()); - ReflectTestTypes(m_behaviorContext.get()); - MockBuilder::Reflect(m_behaviorContext.get()); - - m_scriptContext = AZStd::make_unique(); - m_scriptContext->BindTo(m_behaviorContext.get()); - - m_componentApplication = AZStd::make_unique<::testing::NiceMock>(); - - ON_CALL(*m_componentApplication, GetBehaviorContext()) - .WillByDefault(::testing::Invoke([this]() - { - return this->m_behaviorContext.get(); - })); - - ON_CALL(*m_componentApplication, GetSerializeContext()) - .WillByDefault(::testing::Invoke([this]() - { - return this->m_serializeContext.get(); - })); - - m_editorPythonConsoleInterface = AZStd::make_unique(); - } - - void SetupEditorPythonConsoleInterface() - { - EXPECT_CALL(*m_editorPythonConsoleInterface, FetchPythonTypeName(::testing::_)) - .Times(4) - .WillRepeatedly(::testing::Invoke([](const AZ::BehaviorParameter&) {return "int"; })); - } - - void TearDown() override - { - m_scriptContext.reset(); - m_serializeContext.reset(); - m_behaviorContext.reset(); - - UnitTest::AllocatorsFixture::TearDown(); - } - - void ExpectExecute(AZStd::string_view script) - { - EXPECT_TRUE(m_scriptContext->Execute(script.data())); - } - }; - - TEST_F(SceneGraphBehaviorScriptTest, Scene_ScriptContext_Access) - { - ExpectExecute("builder = MockBuilder()"); - ExpectExecute("builder:BuildSceneGraph()"); - ExpectExecute("scene = builder:GetScene()"); - ExpectExecute("TestExpectTrue(scene ~= nil)"); - ExpectExecute("TestExpectTrue(scene.name == 'unit_scene')"); - ExpectExecute("TestExpectTrue(scene.manifestFilename == 'manifest_filename')"); - ExpectExecute("TestExpectTrue(scene.sourceFilename == 'unit_source_filename')"); - ExpectExecute("TestExpectTrue(tostring(scene.sourceGuid) == '{1F2E6142-B0D8-42C6-A6E5-CD726DAA9EF0}')"); - ExpectExecute("TestExpectTrue(scene:GetOriginalSceneOrientation() == Scene.SceneOrientation_YUp)"); - } - - TEST_F(SceneGraphBehaviorScriptTest, SceneGraph_ScriptContext_AccessMockNodes) - { - ExpectExecute("builder = MockBuilder()"); - ExpectExecute("builder:BuildSceneGraph()"); - ExpectExecute("scene = builder:GetScene()"); - - // instance methods - ExpectExecute("TestExpectTrue(scene.graph ~= nil)"); - ExpectExecute("TestExpectTrue(scene.graph:GetRoot():IsValid())"); - ExpectExecute("TestExpectEquals(scene.graph:GetNodeCount(), 13)"); - ExpectExecute("nodeRoot = scene.graph:GetRoot()"); - ExpectExecute("nodeA = scene.graph:GetNodeChild(nodeRoot); TestExpectTrue(nodeA:IsValid())"); - ExpectExecute("TestExpectTrue(scene.graph:HasNodeContent(nodeA))"); - ExpectExecute("nodeC = scene.graph:GetNodeChild(nodeA); TestExpectTrue(nodeC:IsValid())"); - ExpectExecute("nodeNameC = scene.graph:GetNodeName(nodeC); TestExpectTrue(nodeNameC ~= nil)"); - ExpectExecute("nodeE = scene.graph:GetNodeChild(nodeC); TestExpectTrue(nodeE:IsValid())"); - ExpectExecute("TestExpectTrue(scene.graph:HasNodeSibling(nodeE))"); - ExpectExecute("TestExpectTrue(scene.graph:HasNodeChild(nodeE))"); - ExpectExecute("TestExpectTrue(scene.graph:HasNodeParent(nodeE))"); - ExpectExecute("nodeG = scene.graph:GetNodeChild(nodeE); TestExpectTrue(nodeG:IsValid())"); - ExpectExecute("TestExpectTrue(scene.graph:GetNodeParent(nodeG) == nodeE)"); - ExpectExecute("nodeH = scene.graph:GetNodeSibling(nodeG); TestExpectTrue(nodeH:IsValid())"); - ExpectExecute("TestExpectTrue(scene.graph:GetNodeName(nodeH):GetPath() == 'A.C.E.H')"); - ExpectExecute("nodeB = scene.graph:GetNodeSibling(nodeA); TestExpectTrue(nodeB:IsValid())"); - ExpectExecute("nodeK = scene.graph:GetNodeChild(nodeB); TestExpectTrue(nodeK:IsValid())"); - ExpectExecute("TestExpectTrue(scene.graph:FindWithPath('B.K') == nodeK)"); - ExpectExecute("nodeL = scene.graph:GetNodeChild(nodeK); TestExpectTrue(nodeL:IsValid())"); - ExpectExecute("TestExpectTrue(scene.graph:FindWithRootAndPath(nodeK, 'L') == nodeL)"); - - // static methods - ExpectExecute("TestExpectTrue(scene.graph.IsValidName('A'))"); - ExpectExecute("TestExpectTrue(scene.graph.GetNodeSeperationCharacter() == string.byte('.'))"); - } - - TEST_F(SceneGraphBehaviorScriptTest, SceneGraphNodeIndex_ScriptContext_AccessMockNodes) - { - ExpectExecute("builder = MockBuilder()"); - ExpectExecute("builder:BuildSceneGraph()"); - ExpectExecute("scene = builder:GetScene()"); - ExpectExecute("nodeA = scene.graph:GetNodeChild(scene.graph:GetRoot())"); - ExpectExecute("TestExpectTrue(nodeA:IsValid())"); - ExpectExecute("TestExpectEquals(nodeA:AsNumber(), 1)"); - ExpectExecute("TestExpectEquals(scene.graph:GetRoot():Distance(nodeA), 1)"); - ExpectExecute("TestExpectEquals(nodeA:Distance(scene.graph:GetRoot()), -1)"); - ExpectExecute("TestExpectTrue(nodeA == scene.graph:FindWithPath('A'))"); - } - - TEST_F(SceneGraphBehaviorScriptTest, SceneGraphName_ScriptContext_AccessMockNodes) - { - ExpectExecute("builder = MockBuilder()"); - ExpectExecute("builder:BuildSceneGraph()"); - ExpectExecute("scene = builder:GetScene()"); - ExpectExecute("nodeG = scene.graph:FindWithPath('A.C.E.G')"); - ExpectExecute("nodeNameG = scene.graph:GetNodeName(nodeG)"); - ExpectExecute("TestExpectTrue(nodeNameG:GetPath() == 'A.C.E.G')"); - ExpectExecute("TestExpectTrue(nodeNameG:GetName() == 'G')"); - } - - TEST_F(SceneGraphBehaviorScriptTest, SceneGraphIGraphNode_ScriptContext_AccessMockNodes) - { - ExpectExecute("builder = MockBuilder()"); - ExpectExecute("builder:BuildSceneGraph()"); - ExpectExecute("scene = builder:GetScene()"); - ExpectExecute("nodeG = scene.graph:FindWithPath('A.C.E.G')"); - ExpectExecute("proxy = scene.graph:GetNodeContent(nodeG)"); - ExpectExecute("TestExpectTrue(proxy:CastWithTypeName('MockIGraphObject'))"); - ExpectExecute("value = proxy:Invoke('GetId', vector_any())"); - ExpectExecute("TestExpectEquals(value, 7)"); - ExpectExecute("setIdArgs = vector_any(); setIdArgs:push_back(8);"); - ExpectExecute("proxy:Invoke('SetId', setIdArgs)"); - ExpectExecute("value = proxy:Invoke('GetId', vector_any())"); - ExpectExecute("TestExpectEquals(value, 8)"); - ExpectExecute("addArgs = vector_any(); addArgs:push_back(8); addArgs:push_back(9)"); - ExpectExecute("proxy:Invoke('AddAndSet', addArgs)"); - ExpectExecute("value = proxy:Invoke('GetId', vector_any())"); - ExpectExecute("TestExpectEquals(value, 17)"); - } - - TEST_F(SceneGraphBehaviorScriptTest, GraphObjectProxy_GetClassInfo_Loads) - { - SetupEditorPythonConsoleInterface(); - - ExpectExecute("builder = MockBuilder()"); - ExpectExecute("builder:BuildSceneGraph()"); - ExpectExecute("scene = builder:GetScene()"); - ExpectExecute("nodeG = scene.graph:FindWithPath('A.C.E.G')"); - ExpectExecute("proxy = scene.graph:GetNodeContent(nodeG)"); - ExpectExecute("TestExpectTrue(proxy:CastWithTypeName('MockIGraphObject'))"); - ExpectExecute("info = proxy:GetClassInfo()"); - ExpectExecute("TestExpectTrue(info ~= nil)"); - } - - TEST_F(SceneGraphBehaviorScriptTest, GraphObjectProxy_GetClassInfo_CorrectFormats) - { - SetupEditorPythonConsoleInterface(); - - ExpectExecute("builder = MockBuilder()"); - ExpectExecute("builder:BuildSceneGraph()"); - ExpectExecute("scene = builder:GetScene()"); - ExpectExecute("nodeG = scene.graph:FindWithPath('A.C.E.G')"); - ExpectExecute("proxy = scene.graph:GetNodeContent(nodeG)"); - ExpectExecute("TestExpectTrue(proxy:CastWithTypeName('MockIGraphObject'))"); - ExpectExecute("info = proxy:GetClassInfo()"); - ExpectExecute("TestExpectTrue(info.className == 'MockIGraphObject')"); - ExpectExecute("TestExpectTrue(info.classUuid == '{66A082CC-851D-4E1F-ABBD-45B58A216CFA}')"); - ExpectExecute("TestExpectTrue(info.methodList[1] == 'def GetId(self) -> int')"); - ExpectExecute("TestExpectTrue(info.methodList[2] == 'def SetId(self, arg1: int) -> None')"); - ExpectExecute("TestExpectTrue(info.methodList[3] == 'def AddAndSet(self, arg1: int, arg2: int) -> None')"); - } - - // - // SceneManifestBehaviorScriptTest is meant to test the script abilities of the SceneManifest - // - class SceneManifestBehaviorScriptTest - : public UnitTest::AllocatorsFixture - { - public: - AZStd::unique_ptr m_componentApplication; - AZStd::unique_ptr m_scriptContext; - AZStd::unique_ptr m_behaviorContext; - AZStd::unique_ptr m_serializeContext; - AZStd::unique_ptr m_jsonRegistrationContext; - AZStd::string_view m_jsonMockData = R"JSON('{"values":[{"$type":"MockManifestRule","value":0.1},{"$type":"MockManifestRule","value":2.3},{"$type":"MockManifestRule","value":4.5}]}')JSON"; - - static void TestAssertTrue(bool value) - { - EXPECT_TRUE(value); - } - - void SetUp() override - { - UnitTest::AllocatorsFixture::SetUp(); - - m_serializeContext = AZStd::make_unique(); - MockBuilder::Reflect(m_serializeContext.get()); - MockManifestRule::Reflect(m_serializeContext.get()); - ReflectTypes(m_serializeContext.get()); - - m_behaviorContext = AZStd::make_unique(); - m_behaviorContext->Method("TestAssertTrue", &TestAssertTrue); - AZ::MathReflect(m_behaviorContext.get()); - MockBuilder::Reflect(m_behaviorContext.get()); - MockManifestRule::Reflect(m_behaviorContext.get()); - ReflectBehavior(m_behaviorContext.get()); - - m_jsonRegistrationContext = AZStd::make_unique(); - AZ::JsonSystemComponent::Reflect(m_jsonRegistrationContext.get()); - - m_scriptContext = AZStd::make_unique(); - m_scriptContext->BindTo(m_behaviorContext.get()); - - m_componentApplication = AZStd::make_unique<::testing::NiceMock>(); - - ON_CALL(*m_componentApplication, GetBehaviorContext()).WillByDefault(::testing::Invoke([this]() - { - return this->m_behaviorContext.get(); - })); - - ON_CALL(*m_componentApplication, GetSerializeContext()).WillByDefault(::testing::Invoke([this]() - { - return this->m_serializeContext.get(); - })); - - ON_CALL(*m_componentApplication, GetJsonRegistrationContext()).WillByDefault(::testing::Invoke([this]() - { - return this->m_jsonRegistrationContext.get(); - })); - } - - void TearDown() override - { - m_jsonRegistrationContext->EnableRemoveReflection(); - AZ::JsonSystemComponent::Reflect(m_jsonRegistrationContext.get()); - m_jsonRegistrationContext->DisableRemoveReflection(); - - m_jsonRegistrationContext.reset(); - m_serializeContext.reset(); - m_scriptContext.reset(); - m_behaviorContext.reset(); - m_componentApplication.reset(); - UnitTest::AllocatorsFixture::TearDown(); - } - - void ExpectExecute(AZStd::string_view script) - { - EXPECT_TRUE(m_scriptContext->Execute(script.data())); - } - }; - - TEST_F(SceneManifestBehaviorScriptTest, SceneManifest_ScriptContext_GetDefaultJSON) - { - ExpectExecute("builder = MockBuilder()"); - ExpectExecute("scene = builder:GetScene()"); - ExpectExecute("manifest = scene.manifest:ExportToJson()"); - ExpectExecute(R"JSON(TestAssertTrue(manifest == '{}'))JSON"); - } - - TEST_F(SceneManifestBehaviorScriptTest, SceneManifest_ScriptContext_GetComplexJSON) - { - ExpectExecute("builder = MockBuilder()"); - ExpectExecute("scene = builder:GetScene()"); - ExpectExecute("builder:BuildSceneGraph()"); - ExpectExecute("manifest = scene.manifest:ExportToJson()"); - auto read = AZStd::fixed_string<1024>::format("TestAssertTrue(manifest == %s)", m_jsonMockData.data()); - ExpectExecute(read); - } - - TEST_F(SceneManifestBehaviorScriptTest, SceneManifest_ScriptContext_SetComplexJSON) - { - ExpectExecute("builder = MockBuilder()"); - ExpectExecute("scene = builder:GetScene()"); - ExpectExecute("manifest = scene.manifest:ExportToJson()"); - ExpectExecute(R"JSON(TestAssertTrue(manifest == '{}'))JSON"); - auto load = AZStd::fixed_string<1024>::format("TestAssertTrue(scene.manifest:ImportFromJson(%s))", m_jsonMockData.data()); - ExpectExecute(load); - ExpectExecute("manifest = scene.manifest:ExportToJson()"); - auto read = AZStd::fixed_string<1024>::format("TestAssertTrue(manifest == %s)", m_jsonMockData.data()); - ExpectExecute(read); + serializeContext->Class() + ->Version(1) + ->Field("value", &MockManifestRule::m_value); } } + + private: + double m_value = 0.0; + }; + + struct MockBuilder final + { + AZ_TYPE_INFO(MockBuilder, "{ECF0FB2C-E5C0-4B89-993C-8511A7EF6894}"); + + AZStd::unique_ptr m_scene; + + MockBuilder() + { + m_scene = AZStd::make_unique("unit_scene"); + } + + ~MockBuilder() + { + m_scene.reset(); + } + + void BuildSceneGraph() + { + m_scene->SetManifestFilename("manifest_filename"); + m_scene->SetSource("unit_source_filename", azrtti_typeid()); + + auto& graph = m_scene->GetGraph(); + + /*----------------------------\ + | Root | + | / \ | + | | | | + | A B | + | | /|\ | + | C I J K | + | / | \ \ | + | D E F L | + | / \ | + | G H | + \----------------------------*/ + + //Build up the graph + const auto indexA = graph.AddChild(graph.GetRoot(), "A", AZStd::make_shared(1)); + const auto indexC = graph.AddChild(indexA, "C", AZStd::make_shared(3)); + const auto indexE = graph.AddChild(indexC, "E", AZStd::make_shared(4)); + graph.AddChild(indexC, "D", AZStd::make_shared(5)); + graph.AddChild(indexC, "F", AZStd::make_shared(6)); + graph.AddChild(indexE, "G", AZStd::make_shared(7)); + graph.AddChild(indexE, "H", AZStd::make_shared(8)); + const auto indexB = graph.AddChild(graph.GetRoot(), "B", AZStd::make_shared(2)); + const auto indexK = graph.AddChild(indexB, "K", AZStd::make_shared(2)); + graph.AddChild(indexB, "I", AZStd::make_shared(9)); + graph.AddChild(indexB, "J", AZStd::make_shared(10)); + graph.AddChild(indexK, "L", AZStd::make_shared(12)); + + m_scene->GetManifest().AddEntry(AZStd::make_shared(0.1)); + m_scene->GetManifest().AddEntry(AZStd::make_shared(2.3)); + m_scene->GetManifest().AddEntry(AZStd::make_shared(4.5)); + } + + static void Reflect(ReflectContext* context) + { + BehaviorContext* behaviorContext = azrtti_cast(context); + if (behaviorContext) + { + behaviorContext->Class() + ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common) + ->Attribute(AZ::Script::Attributes::Module, "scene") + ->Method("BuildSceneGraph", [](MockBuilder& self) + { + return self.BuildSceneGraph(); + }) + ->Method("GetScene", [](MockBuilder& self) + { + return self.m_scene.get(); + }); + } + } + }; + + class SceneGraphBehaviorTest + : public ::testing::Test + { + public: + void SetUp() override + { + m_behaviorContext = AZStd::make_unique(); + ReflectBehavior(m_behaviorContext.get()); + } + + void TearDown() override + { + m_behaviorContext.reset(); + } + + AZ::BehaviorClass* GetBehaviorClass(const AZ::TypeId& behaviorClassType) + { + auto entry = m_behaviorContext->m_typeToClassMap.find(behaviorClassType); + return (entry != m_behaviorContext->m_typeToClassMap.end()) ? entry->second : nullptr; + } + + AZ::BehaviorProperty* GetBehaviorProperty(AZ::BehaviorClass& behaviorClass, AZStd::string_view propertyName) + { + auto entry = behaviorClass.m_properties.find(propertyName); + return (entry != behaviorClass.m_properties.end()) ? entry->second : nullptr; + } + + bool HasBehaviorClass(const AZ::TypeId& behaviorClassType) + { + return GetBehaviorClass(behaviorClassType) != nullptr; + } + + bool HasProperty(AZ::BehaviorClass& behaviorClass, AZStd::string_view propertyName, const AZ::TypeId& propertyClassType) + { + AZ::BehaviorProperty* behaviorProperty = GetBehaviorProperty(behaviorClass, propertyName); + if (behaviorProperty) + { + return behaviorProperty->m_getter->GetResult()->m_typeId == propertyClassType; + } + return false; + } + + using ArgList = AZStd::vector; + + bool HasMethodWithInput(AZ::BehaviorClass& behaviorClass, AZStd::string_view methodName, const ArgList& input) + { + auto entry = behaviorClass.m_methods.find(methodName); + if (entry == behaviorClass.m_methods.end()) + { + return false; + } + AZ::BehaviorMethod* method = entry->second; + + const size_t methodArgsCount = method->IsMember() ? method->GetNumArguments() - 1 : method->GetNumArguments(); + if (input.size() != methodArgsCount) + { + return false; + } + + for (size_t argIndex = 0; argIndex < input.size(); ++argIndex) + { + const size_t thisPointerOffset = method->IsMember() ? 1 : 0; + const auto argType = method->GetArgument(argIndex + thisPointerOffset)->m_typeId; + const auto inputType = input[argIndex]; + if (inputType != argType) + { + return false; + } + } + return true; + } + + bool HasMethodWithOutput(AZ::BehaviorClass& behaviorClass, AZStd::string_view methodName, const AZ::TypeId& output, const ArgList& input) + { + auto entry = behaviorClass.m_methods.find(methodName); + if (entry == behaviorClass.m_methods.end()) + { + return false; + } + AZ::BehaviorMethod* method = entry->second; + if (method->HasResult()) + { + if (method->GetResult()->m_typeId != output) + { + return false; + } + } + else + { + return false; + } + return HasMethodWithInput(behaviorClass, methodName, input); + } + + AZStd::unique_ptr m_behaviorContext; + }; + + TEST_F(SceneGraphBehaviorTest, SceneClass_BehaviorContext_Exists) + { + EXPECT_TRUE(HasBehaviorClass(azrtti_typeid())); } -} + + TEST_F(SceneGraphBehaviorTest, SceneClass_BehaviorContext_HasExpectedProperties) + { + AZ::BehaviorClass* behaviorClass = GetBehaviorClass(azrtti_typeid()); + ASSERT_NE(nullptr, behaviorClass); + EXPECT_TRUE(HasProperty(*behaviorClass, "name", azrtti_typeid())); + EXPECT_TRUE(HasProperty(*behaviorClass, "manifestFilename", azrtti_typeid())); + EXPECT_TRUE(HasProperty(*behaviorClass, "sourceFilename", azrtti_typeid())); + EXPECT_TRUE(HasProperty(*behaviorClass, "sourceGuid", azrtti_typeid())); + EXPECT_TRUE(HasProperty(*behaviorClass, "graph", azrtti_typeid())); + EXPECT_TRUE(HasProperty(*behaviorClass, "manifest", azrtti_typeid())); + } + + TEST_F(SceneGraphBehaviorTest, SceneGraphClass_BehaviorContext_Exists) + { + EXPECT_TRUE(HasBehaviorClass(azrtti_typeid())); + EXPECT_TRUE(HasBehaviorClass(azrtti_typeid())); + EXPECT_TRUE(HasBehaviorClass(azrtti_typeid())); + } + + TEST_F(SceneGraphBehaviorTest, SceneGraphClass_BehaviorContext_HasExpectedProperties) + { + using namespace AZ::SceneAPI::Containers; + + AZ::BehaviorClass* behaviorClass = GetBehaviorClass(azrtti_typeid()); + ASSERT_NE(nullptr, behaviorClass); + EXPECT_TRUE(HasMethodWithOutput(*behaviorClass, "GetNodeName", azrtti_typeid(), { azrtti_typeid() })); + EXPECT_TRUE(HasMethodWithOutput(*behaviorClass, "GetRoot", azrtti_typeid(), {})); + EXPECT_TRUE(HasMethodWithOutput(*behaviorClass, "HasNodeContent", azrtti_typeid(), { azrtti_typeid() })); + EXPECT_TRUE(HasMethodWithOutput(*behaviorClass, "HasNodeSibling", azrtti_typeid(), { azrtti_typeid() })); + EXPECT_TRUE(HasMethodWithOutput(*behaviorClass, "HasNodeChild", azrtti_typeid(), { azrtti_typeid() })); + EXPECT_TRUE(HasMethodWithOutput(*behaviorClass, "HasNodeParent", azrtti_typeid(), { azrtti_typeid() })); + EXPECT_TRUE(HasMethodWithOutput(*behaviorClass, "IsNodeEndPoint", azrtti_typeid(), { azrtti_typeid() })); + EXPECT_TRUE(HasMethodWithOutput(*behaviorClass, "GetNodeCount", azrtti_typeid(), {})); + EXPECT_TRUE(HasMethodWithOutput( + *behaviorClass, + "GetNodeParent", + azrtti_typeid(), + { azrtti_typeid(), azrtti_typeid() } + )); + EXPECT_TRUE(HasMethodWithOutput( + *behaviorClass, + "GetNodeSibling", + azrtti_typeid(), + { azrtti_typeid(), azrtti_typeid() } + )); + EXPECT_TRUE(HasMethodWithOutput( + *behaviorClass, + "GetNodeChild", + azrtti_typeid(), + { azrtti_typeid(), azrtti_typeid() } + )); + EXPECT_TRUE(HasMethodWithOutput( + *behaviorClass, + "FindWithPath", + azrtti_typeid(), + { azrtti_typeid(), azrtti_typeid() } + )); + EXPECT_TRUE(HasMethodWithOutput( + *behaviorClass, + "FindWithRootAndPath", + azrtti_typeid(), + { azrtti_typeid(), azrtti_typeid(), azrtti_typeid() } + )); + } + + TEST_F(SceneGraphBehaviorTest, SceneGraphNodeIndexClass_BehaviorContext_HasExpectedProperties) + { + using namespace AZ::SceneAPI::Containers; + + AZ::BehaviorClass* behaviorClass = GetBehaviorClass(azrtti_typeid()); + ASSERT_NE(nullptr, behaviorClass); + EXPECT_TRUE(HasMethodWithOutput(*behaviorClass, "AsNumber", azrtti_typeid(), {})); + EXPECT_TRUE(HasMethodWithOutput(*behaviorClass, "Distance", azrtti_typeid(), { azrtti_typeid() })); + EXPECT_TRUE(HasMethodWithOutput(*behaviorClass, "IsValid", azrtti_typeid(), {})); + EXPECT_TRUE(HasMethodWithOutput(*behaviorClass, "Equal", azrtti_typeid(), { azrtti_typeid() })); + } + + TEST_F(SceneGraphBehaviorTest, SceneGraphNameClass_BehaviorContext_HasExpectedProperties) + { + using namespace AZ::SceneAPI::Containers; + + AZ::BehaviorClass* behaviorClass = GetBehaviorClass(azrtti_typeid()); + ASSERT_NE(nullptr, behaviorClass); + EXPECT_TRUE(HasMethodWithOutput(*behaviorClass, "GetPath", azrtti_typeid(), {})); + EXPECT_TRUE(HasMethodWithOutput(*behaviorClass, "GetName", azrtti_typeid(), {})); + } + + class MockSceneComponentApplication + : public AZ::ComponentApplicationBus::Handler + { + public: + MockSceneComponentApplication() + { + AZ::ComponentApplicationBus::Handler::BusConnect(); + AZ::Interface::Register(this); + } + + ~MockSceneComponentApplication() + { + AZ::Interface::Unregister(this); + AZ::ComponentApplicationBus::Handler::BusDisconnect(); + } + + MOCK_METHOD1(FindEntity, AZ::Entity* (const AZ::EntityId&)); + MOCK_METHOD1(AddEntity, bool(AZ::Entity*)); + MOCK_METHOD0(Destroy, void()); + MOCK_METHOD1(RegisterComponentDescriptor, void(const AZ::ComponentDescriptor*)); + MOCK_METHOD1(UnregisterComponentDescriptor, void(const AZ::ComponentDescriptor*)); + MOCK_METHOD1(RegisterEntityAddedEventHandler, void(AZ::EntityAddedEvent::Handler&)); + MOCK_METHOD1(RegisterEntityRemovedEventHandler, void(AZ::EntityRemovedEvent::Handler&)); + MOCK_METHOD1(RegisterEntityActivatedEventHandler, void(AZ::EntityActivatedEvent::Handler&)); + MOCK_METHOD1(RegisterEntityDeactivatedEventHandler, void(AZ::EntityDeactivatedEvent::Handler&)); + MOCK_METHOD1(SignalEntityActivated, void(AZ::Entity*)); + MOCK_METHOD1(SignalEntityDeactivated, void(AZ::Entity*)); + MOCK_METHOD1(RemoveEntity, bool(AZ::Entity*)); + MOCK_METHOD1(DeleteEntity, bool(const AZ::EntityId&)); + MOCK_METHOD1(GetEntityName, AZStd::string(const AZ::EntityId&)); + MOCK_METHOD1(EnumerateEntities, void(const ComponentApplicationRequests::EntityCallback&)); + MOCK_METHOD0(GetApplication, AZ::ComponentApplication* ()); + MOCK_METHOD0(GetSerializeContext, AZ::SerializeContext* ()); + MOCK_METHOD0(GetJsonRegistrationContext, AZ::JsonRegistrationContext* ()); + MOCK_METHOD0(GetBehaviorContext, AZ::BehaviorContext* ()); + MOCK_CONST_METHOD0(GetAppRoot, const char*()); + MOCK_CONST_METHOD0(GetEngineRoot, const char*()); + MOCK_CONST_METHOD0(GetExecutableFolder, const char* ()); + MOCK_METHOD0(GetDrillerManager, AZ::Debug::DrillerManager* ()); + MOCK_CONST_METHOD1(QueryApplicationType, void(AZ::ApplicationTypeQuery&)); + }; + + class MockEditorPythonConsoleInterface final + : public AzToolsFramework::EditorPythonConsoleInterface + { + public: + MockEditorPythonConsoleInterface() + { + AZ::Interface::Register(this); + } + + ~MockEditorPythonConsoleInterface() + { + AZ::Interface::Unregister(this); + } + + MOCK_CONST_METHOD1(GetModuleList, void(AZStd::vector&)); + MOCK_CONST_METHOD1(GetGlobalFunctionList, void(GlobalFunctionCollection&)); + MOCK_METHOD1(FetchPythonTypeName, AZStd::string(const AZ::BehaviorParameter&)); + }; + + // + // SceneGraphBehaviorScriptTest + // + class SceneGraphBehaviorScriptTest + : public UnitTest::AllocatorsFixture + { + public: + AZStd::unique_ptr m_componentApplication; + AZStd::unique_ptr m_editorPythonConsoleInterface; + AZStd::unique_ptr m_scriptContext; + AZStd::unique_ptr m_behaviorContext; + AZStd::unique_ptr m_serializeContext; + + static void TestExpectTrue(bool value) + { + EXPECT_TRUE(value); + } + + static void TestExpectEquals(AZ::s64 lhs, AZ::s64 rhs) + { + EXPECT_EQ(lhs, rhs); + } + + static void ReflectTestTypes(AZ::ReflectContext* context) + { + AZ::BehaviorContext* behaviorContext = azrtti_cast(context); + if (behaviorContext) + { + behaviorContext->Class() + ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common) + ->Attribute(AZ::Script::Attributes::Module, "scene.graph.test") + ->Method("GetId", [](const DataTypes::MockIGraphObject& self) + { + return self.m_id; + }) + ->Method("SetId", [](DataTypes::MockIGraphObject& self, int value) + { + self.m_id = value; + }) + ->Method("AddAndSet", [](DataTypes::MockIGraphObject& self, int lhs, int rhs) + { + self.m_id = lhs + rhs; + }); + } + } + + void SetUp() override + { + UnitTest::AllocatorsFixture::SetUp(); + + m_serializeContext = AZStd::make_unique(); + + m_behaviorContext = AZStd::make_unique(); + m_behaviorContext->Method("TestExpectTrue", &TestExpectTrue); + m_behaviorContext->Method("TestExpectEquals", &TestExpectEquals); + + AZ::MathReflect(m_behaviorContext.get()); + ReflectBehavior(m_behaviorContext.get()); + ReflectTestTypes(m_behaviorContext.get()); + MockBuilder::Reflect(m_behaviorContext.get()); + + m_scriptContext = AZStd::make_unique(); + m_scriptContext->BindTo(m_behaviorContext.get()); + + m_componentApplication = AZStd::make_unique<::testing::NiceMock>(); + + ON_CALL(*m_componentApplication, GetBehaviorContext()) + .WillByDefault(::testing::Invoke([this]() + { + return this->m_behaviorContext.get(); + })); + + ON_CALL(*m_componentApplication, GetSerializeContext()) + .WillByDefault(::testing::Invoke([this]() + { + return this->m_serializeContext.get(); + })); + + m_editorPythonConsoleInterface = AZStd::make_unique(); + } + + void SetupEditorPythonConsoleInterface() + { + EXPECT_CALL(*m_editorPythonConsoleInterface, FetchPythonTypeName(::testing::_)) + .Times(4) + .WillRepeatedly(::testing::Invoke([](const AZ::BehaviorParameter&) {return "int"; })); + } + + void TearDown() override + { + m_scriptContext.reset(); + m_serializeContext.reset(); + m_behaviorContext.reset(); + + UnitTest::AllocatorsFixture::TearDown(); + } + + void ExpectExecute(AZStd::string_view script) + { + EXPECT_TRUE(m_scriptContext->Execute(script.data())); + } + }; + + TEST_F(SceneGraphBehaviorScriptTest, Scene_ScriptContext_Access) + { + ExpectExecute("builder = MockBuilder()"); + ExpectExecute("builder:BuildSceneGraph()"); + ExpectExecute("scene = builder:GetScene()"); + ExpectExecute("TestExpectTrue(scene ~= nil)"); + ExpectExecute("TestExpectTrue(scene.name == 'unit_scene')"); + ExpectExecute("TestExpectTrue(scene.manifestFilename == 'manifest_filename')"); + ExpectExecute("TestExpectTrue(scene.sourceFilename == 'unit_source_filename')"); + ExpectExecute("TestExpectTrue(tostring(scene.sourceGuid) == '{1F2E6142-B0D8-42C6-A6E5-CD726DAA9EF0}')"); + ExpectExecute("TestExpectTrue(scene:GetOriginalSceneOrientation() == Scene.SceneOrientation_YUp)"); + } + + TEST_F(SceneGraphBehaviorScriptTest, SceneGraph_ScriptContext_AccessMockNodes) + { + ExpectExecute("builder = MockBuilder()"); + ExpectExecute("builder:BuildSceneGraph()"); + ExpectExecute("scene = builder:GetScene()"); + + // instance methods + ExpectExecute("TestExpectTrue(scene.graph ~= nil)"); + ExpectExecute("TestExpectTrue(scene.graph:GetRoot():IsValid())"); + ExpectExecute("TestExpectEquals(scene.graph:GetNodeCount(), 13)"); + ExpectExecute("nodeRoot = scene.graph:GetRoot()"); + ExpectExecute("nodeA = scene.graph:GetNodeChild(nodeRoot); TestExpectTrue(nodeA:IsValid())"); + ExpectExecute("TestExpectTrue(scene.graph:HasNodeContent(nodeA))"); + ExpectExecute("nodeC = scene.graph:GetNodeChild(nodeA); TestExpectTrue(nodeC:IsValid())"); + ExpectExecute("nodeNameC = scene.graph:GetNodeName(nodeC); TestExpectTrue(nodeNameC ~= nil)"); + ExpectExecute("nodeE = scene.graph:GetNodeChild(nodeC); TestExpectTrue(nodeE:IsValid())"); + ExpectExecute("TestExpectTrue(scene.graph:HasNodeSibling(nodeE))"); + ExpectExecute("TestExpectTrue(scene.graph:HasNodeChild(nodeE))"); + ExpectExecute("TestExpectTrue(scene.graph:HasNodeParent(nodeE))"); + ExpectExecute("nodeG = scene.graph:GetNodeChild(nodeE); TestExpectTrue(nodeG:IsValid())"); + ExpectExecute("TestExpectTrue(scene.graph:GetNodeParent(nodeG) == nodeE)"); + ExpectExecute("nodeH = scene.graph:GetNodeSibling(nodeG); TestExpectTrue(nodeH:IsValid())"); + ExpectExecute("TestExpectTrue(scene.graph:GetNodeName(nodeH):GetPath() == 'A.C.E.H')"); + ExpectExecute("nodeB = scene.graph:GetNodeSibling(nodeA); TestExpectTrue(nodeB:IsValid())"); + ExpectExecute("nodeK = scene.graph:GetNodeChild(nodeB); TestExpectTrue(nodeK:IsValid())"); + ExpectExecute("TestExpectTrue(scene.graph:FindWithPath('B.K') == nodeK)"); + ExpectExecute("nodeL = scene.graph:GetNodeChild(nodeK); TestExpectTrue(nodeL:IsValid())"); + ExpectExecute("TestExpectTrue(scene.graph:FindWithRootAndPath(nodeK, 'L') == nodeL)"); + + // static methods + ExpectExecute("TestExpectTrue(scene.graph.IsValidName('A'))"); + ExpectExecute("TestExpectTrue(scene.graph.GetNodeSeperationCharacter() == string.byte('.'))"); + } + + TEST_F(SceneGraphBehaviorScriptTest, SceneGraphNodeIndex_ScriptContext_AccessMockNodes) + { + ExpectExecute("builder = MockBuilder()"); + ExpectExecute("builder:BuildSceneGraph()"); + ExpectExecute("scene = builder:GetScene()"); + ExpectExecute("nodeA = scene.graph:GetNodeChild(scene.graph:GetRoot())"); + ExpectExecute("TestExpectTrue(nodeA:IsValid())"); + ExpectExecute("TestExpectEquals(nodeA:AsNumber(), 1)"); + ExpectExecute("TestExpectEquals(scene.graph:GetRoot():Distance(nodeA), 1)"); + ExpectExecute("TestExpectEquals(nodeA:Distance(scene.graph:GetRoot()), -1)"); + ExpectExecute("TestExpectTrue(nodeA == scene.graph:FindWithPath('A'))"); + } + + TEST_F(SceneGraphBehaviorScriptTest, SceneGraphName_ScriptContext_AccessMockNodes) + { + ExpectExecute("builder = MockBuilder()"); + ExpectExecute("builder:BuildSceneGraph()"); + ExpectExecute("scene = builder:GetScene()"); + ExpectExecute("nodeG = scene.graph:FindWithPath('A.C.E.G')"); + ExpectExecute("nodeNameG = scene.graph:GetNodeName(nodeG)"); + ExpectExecute("TestExpectTrue(nodeNameG:GetPath() == 'A.C.E.G')"); + ExpectExecute("TestExpectTrue(nodeNameG:GetName() == 'G')"); + } + + TEST_F(SceneGraphBehaviorScriptTest, SceneGraphIGraphNode_ScriptContext_AccessMockNodes) + { + ExpectExecute("builder = MockBuilder()"); + ExpectExecute("builder:BuildSceneGraph()"); + ExpectExecute("scene = builder:GetScene()"); + ExpectExecute("nodeG = scene.graph:FindWithPath('A.C.E.G')"); + ExpectExecute("proxy = scene.graph:GetNodeContent(nodeG)"); + ExpectExecute("TestExpectTrue(proxy:CastWithTypeName('MockIGraphObject'))"); + ExpectExecute("value = proxy:Invoke('GetId', vector_any())"); + ExpectExecute("TestExpectEquals(value, 7)"); + ExpectExecute("setIdArgs = vector_any(); setIdArgs:push_back(8);"); + ExpectExecute("proxy:Invoke('SetId', setIdArgs)"); + ExpectExecute("value = proxy:Invoke('GetId', vector_any())"); + ExpectExecute("TestExpectEquals(value, 8)"); + ExpectExecute("addArgs = vector_any(); addArgs:push_back(8); addArgs:push_back(9)"); + ExpectExecute("proxy:Invoke('AddAndSet', addArgs)"); + ExpectExecute("value = proxy:Invoke('GetId', vector_any())"); + ExpectExecute("TestExpectEquals(value, 17)"); + } + + TEST_F(SceneGraphBehaviorScriptTest, GraphObjectProxy_GetClassInfo_Loads) + { + SetupEditorPythonConsoleInterface(); + + ExpectExecute("builder = MockBuilder()"); + ExpectExecute("builder:BuildSceneGraph()"); + ExpectExecute("scene = builder:GetScene()"); + ExpectExecute("nodeG = scene.graph:FindWithPath('A.C.E.G')"); + ExpectExecute("proxy = scene.graph:GetNodeContent(nodeG)"); + ExpectExecute("TestExpectTrue(proxy:CastWithTypeName('MockIGraphObject'))"); + ExpectExecute("info = proxy:GetClassInfo()"); + ExpectExecute("TestExpectTrue(info ~= nil)"); + } + + TEST_F(SceneGraphBehaviorScriptTest, GraphObjectProxy_GetClassInfo_CorrectFormats) + { + SetupEditorPythonConsoleInterface(); + + ExpectExecute("builder = MockBuilder()"); + ExpectExecute("builder:BuildSceneGraph()"); + ExpectExecute("scene = builder:GetScene()"); + ExpectExecute("nodeG = scene.graph:FindWithPath('A.C.E.G')"); + ExpectExecute("proxy = scene.graph:GetNodeContent(nodeG)"); + ExpectExecute("TestExpectTrue(proxy:CastWithTypeName('MockIGraphObject'))"); + ExpectExecute("info = proxy:GetClassInfo()"); + ExpectExecute("TestExpectTrue(info.className == 'MockIGraphObject')"); + ExpectExecute("TestExpectTrue(info.classUuid == '{66A082CC-851D-4E1F-ABBD-45B58A216CFA}')"); + ExpectExecute("TestExpectTrue(info.methodList[1] == 'def GetId(self) -> int')"); + ExpectExecute("TestExpectTrue(info.methodList[2] == 'def SetId(self, arg1: int) -> None')"); + ExpectExecute("TestExpectTrue(info.methodList[3] == 'def AddAndSet(self, arg1: int, arg2: int) -> None')"); + } + + TEST_F(SceneGraphBehaviorScriptTest, ExportProduct_ExpectedClassesAndFields_Work) + { + ExpectExecute("mockAssetType = Uuid.CreateString('{B7AD6A54-963F-4F0F-A70E-1CFC0364BE6B}')"); + ExpectExecute("exportProduct = ExportProduct()"); + ExpectExecute("exportProduct.filename = 'some/file.name'"); + ExpectExecute("exportProduct.sourceId = Uuid.CreateString('{A19F5FDB-C5FB-478F-A0B0-B697D2C10DB5}', 0)"); + ExpectExecute("exportProduct.assetType = mockAssetType"); + ExpectExecute("exportProduct.subId = 10101"); + ExpectExecute("TestExpectEquals(exportProduct.subId, 10101)"); + ExpectExecute("TestExpectEquals(exportProduct.productDependencies:GetSize(), 0)"); + + ExpectExecute("exportProductDep = ExportProduct()"); + ExpectExecute("exportProductDep.filename = 'some/file.dep'"); + ExpectExecute("exportProductDep.sourceId = Uuid.CreateString('{A19F5FDB-C5FB-478F-A0B0-B697D2C10DB5}', 0)"); + ExpectExecute("exportProductDep.assetType = mockAssetType"); + ExpectExecute("exportProductDep.subId = 2"); + + ExpectExecute("exportProductList = ExportProductList()"); + ExpectExecute("exportProductList:AddProduct(exportProduct)"); + ExpectExecute("exportProductList:AddProduct(exportProductDep)"); + ExpectExecute("productList = exportProductList:GetProducts()"); + ExpectExecute("TestExpectEquals(productList:GetSize(), 2)"); + ExpectExecute("exportProductList:AddDependencyToProduct(exportProduct.filename, exportProductDep)"); + ExpectExecute("TestExpectEquals(productList:Front().productDependencies:GetSize(), 1)"); + } + + // + // SceneManifestBehaviorScriptTest is meant to test the script abilities of the SceneManifest + // + class SceneManifestBehaviorScriptTest + : public UnitTest::AllocatorsFixture + { + public: + AZStd::unique_ptr m_componentApplication; + AZStd::unique_ptr m_scriptContext; + AZStd::unique_ptr m_behaviorContext; + AZStd::unique_ptr m_serializeContext; + AZStd::unique_ptr m_jsonRegistrationContext; + AZStd::string_view m_jsonMockData = R"JSON('{"values":[{"$type":"MockManifestRule","value":0.1},{"$type":"MockManifestRule","value":2.3},{"$type":"MockManifestRule","value":4.5}]}')JSON"; + + static void TestAssertTrue(bool value) + { + EXPECT_TRUE(value); + } + + void SetUp() override + { + UnitTest::AllocatorsFixture::SetUp(); + + m_serializeContext = AZStd::make_unique(); + MockBuilder::Reflect(m_serializeContext.get()); + MockManifestRule::Reflect(m_serializeContext.get()); + ReflectTypes(m_serializeContext.get()); + + m_behaviorContext = AZStd::make_unique(); + m_behaviorContext->Method("TestAssertTrue", &TestAssertTrue); + AZ::MathReflect(m_behaviorContext.get()); + MockBuilder::Reflect(m_behaviorContext.get()); + MockManifestRule::Reflect(m_behaviorContext.get()); + ReflectBehavior(m_behaviorContext.get()); + + m_jsonRegistrationContext = AZStd::make_unique(); + AZ::JsonSystemComponent::Reflect(m_jsonRegistrationContext.get()); + + m_scriptContext = AZStd::make_unique(); + m_scriptContext->BindTo(m_behaviorContext.get()); + + m_componentApplication = AZStd::make_unique<::testing::NiceMock>(); + + ON_CALL(*m_componentApplication, GetBehaviorContext()).WillByDefault(::testing::Invoke([this]() + { + return this->m_behaviorContext.get(); + })); + + ON_CALL(*m_componentApplication, GetSerializeContext()).WillByDefault(::testing::Invoke([this]() + { + return this->m_serializeContext.get(); + })); + + ON_CALL(*m_componentApplication, GetJsonRegistrationContext()).WillByDefault(::testing::Invoke([this]() + { + return this->m_jsonRegistrationContext.get(); + })); + } + + void TearDown() override + { + m_jsonRegistrationContext->EnableRemoveReflection(); + AZ::JsonSystemComponent::Reflect(m_jsonRegistrationContext.get()); + m_jsonRegistrationContext->DisableRemoveReflection(); + + m_jsonRegistrationContext.reset(); + m_serializeContext.reset(); + m_scriptContext.reset(); + m_behaviorContext.reset(); + m_componentApplication.reset(); + UnitTest::AllocatorsFixture::TearDown(); + } + + void ExpectExecute(AZStd::string_view script) + { + EXPECT_TRUE(m_scriptContext->Execute(script.data())); + } + }; + + TEST_F(SceneManifestBehaviorScriptTest, SceneManifest_ScriptContext_GetDefaultJSON) + { + ExpectExecute("builder = MockBuilder()"); + ExpectExecute("scene = builder:GetScene()"); + ExpectExecute("manifest = scene.manifest:ExportToJson()"); + ExpectExecute(R"JSON(TestAssertTrue(manifest == '{}'))JSON"); + } + + TEST_F(SceneManifestBehaviorScriptTest, SceneManifest_ScriptContext_GetComplexJSON) + { + ExpectExecute("builder = MockBuilder()"); + ExpectExecute("scene = builder:GetScene()"); + ExpectExecute("builder:BuildSceneGraph()"); + ExpectExecute("manifest = scene.manifest:ExportToJson()"); + auto read = AZStd::fixed_string<1024>::format("TestAssertTrue(manifest == %s)", m_jsonMockData.data()); + ExpectExecute(read); + } + + TEST_F(SceneManifestBehaviorScriptTest, SceneManifest_ScriptContext_SetComplexJSON) + { + ExpectExecute("builder = MockBuilder()"); + ExpectExecute("scene = builder:GetScene()"); + ExpectExecute("manifest = scene.manifest:ExportToJson()"); + ExpectExecute(R"JSON(TestAssertTrue(manifest == '{}'))JSON"); + auto load = AZStd::fixed_string<1024>::format("TestAssertTrue(scene.manifest:ImportFromJson(%s))", m_jsonMockData.data()); + ExpectExecute(load); + ExpectExecute("manifest = scene.manifest:ExportToJson()"); + auto read = AZStd::fixed_string<1024>::format("TestAssertTrue(manifest == %s)", m_jsonMockData.data()); + ExpectExecute(read); + } + +} // namespace AZ::SceneAPI::Containers diff --git a/Code/Tools/SceneAPI/SceneData/Behaviors/ScriptProcessorRuleBehavior.cpp b/Code/Tools/SceneAPI/SceneData/Behaviors/ScriptProcessorRuleBehavior.cpp index 5ae547b577..b7fda1e0dc 100644 --- a/Code/Tools/SceneAPI/SceneData/Behaviors/ScriptProcessorRuleBehavior.cpp +++ b/Code/Tools/SceneAPI/SceneData/Behaviors/ScriptProcessorRuleBehavior.cpp @@ -25,213 +25,319 @@ #include #include #include +#include -namespace AZ +namespace AZ::SceneAPI::Behaviors { - namespace SceneAPI + class EditorPythonConsoleNotificationHandler final + : protected AzToolsFramework::EditorPythonConsoleNotificationBus::Handler { - namespace Behaviors + public: + EditorPythonConsoleNotificationHandler() { - class EditorPythonConsoleNotificationHandler final - : protected AzToolsFramework::EditorPythonConsoleNotificationBus::Handler + BusConnect(); + } + + ~EditorPythonConsoleNotificationHandler() + { + BusDisconnect(); + } + + //////////////////////////////////////////////////////////////////////////////////////////// + // AzToolsFramework::EditorPythonConsoleNotifications + void OnTraceMessage([[maybe_unused]] AZStd::string_view message) override + { + using namespace AZ::SceneAPI::Utilities; + AZ_TracePrintf(LogWindow, "%.*s \n", AZ_STRING_ARG(message)); + } + + void OnErrorMessage([[maybe_unused]] AZStd::string_view message) override + { + using namespace AZ::SceneAPI::Utilities; + AZ_TracePrintf(ErrorWindow, "[ERROR] %.*s \n", AZ_STRING_ARG(message)); + } + + void OnExceptionMessage([[maybe_unused]] AZStd::string_view message) override + { + using namespace AZ::SceneAPI::Utilities; + AZ_TracePrintf(ErrorWindow, "[EXCEPTION] %.*s \n", AZ_STRING_ARG(message)); + } + }; + + using ExportProductList = AZ::SceneAPI::Events::ExportProductList; + + // a event bus to signal during scene building + struct ScriptBuildingNotifications + : public AZ::EBusTraits + { + virtual AZStd::string OnUpdateManifest(Containers::Scene& scene) = 0; + virtual ExportProductList OnPrepareForExport( + const Containers::Scene& scene, + AZStd::string_view outputDirectory, + AZStd::string_view platformIdentifier, + const ExportProductList& productList) = 0; + }; + using ScriptBuildingNotificationBus = AZ::EBus; + + // a back end to handle scene builder events for a script + struct ScriptBuildingNotificationBusHandler final + : public ScriptBuildingNotificationBus::Handler + , public AZ::BehaviorEBusHandler + { + AZ_EBUS_BEHAVIOR_BINDER( + ScriptBuildingNotificationBusHandler, + "{DF2B51DE-A4D0-4139-B5D0-DF185832380D}", + AZ::SystemAllocator, + OnUpdateManifest, + OnPrepareForExport); + + virtual ~ScriptBuildingNotificationBusHandler() = default; + + AZStd::string OnUpdateManifest(Containers::Scene& scene) override + { + AZStd::string result; + CallResult(result, FN_OnUpdateManifest, scene); + return result; + } + + ExportProductList OnPrepareForExport( + const Containers::Scene& scene, + AZStd::string_view outputDirectory, + AZStd::string_view platformIdentifier, + const ExportProductList& productList) override + { + ExportProductList result; + CallResult(result, FN_OnPrepareForExport, scene, outputDirectory, platformIdentifier, productList); + return result; + } + + static void Reflect(AZ::ReflectContext* context) + { + if (AZ::BehaviorContext* behaviorContext = azrtti_cast(context)) { - public: - EditorPythonConsoleNotificationHandler() - { - BusConnect(); - } + behaviorContext->EBus("ScriptBuildingNotificationBus") + ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation) + ->Attribute(AZ::Script::Attributes::Module, "scene") + ->Handler() + ->Event("OnUpdateManifest", &ScriptBuildingNotificationBus::Events::OnUpdateManifest) + ->Event("OnPrepareForExport", &ScriptBuildingNotificationBus::Events::OnPrepareForExport); + } + } + }; - ~EditorPythonConsoleNotificationHandler() - { - BusDisconnect(); - } + struct ScriptProcessorRuleBehavior::ExportEventHandler final + : public AZ::SceneAPI::SceneCore::ExportingComponent + { + using PreExportEventContextFunction = AZStd::function; + PreExportEventContextFunction m_preExportEventContextFunction; - //////////////////////////////////////////////////////////////////////////////////////////// - // AzToolsFramework::EditorPythonConsoleNotifications - void OnTraceMessage([[maybe_unused]] AZStd::string_view message) override - { - using namespace AZ::SceneAPI::Utilities; - AZ_TracePrintf(LogWindow, "%.*s \n", AZ_STRING_ARG(message)); - } + ExportEventHandler(PreExportEventContextFunction preExportEventContextFunction) + : m_preExportEventContextFunction(preExportEventContextFunction) + { + BindToCall(&ExportEventHandler::PrepareForExport); + AZ::SceneAPI::SceneCore::ExportingComponent::Activate(); + } - void OnErrorMessage([[maybe_unused]] AZStd::string_view message) override - { - using namespace AZ::SceneAPI::Utilities; - AZ_TracePrintf(ErrorWindow, "[ERROR] %.*s \n", AZ_STRING_ARG(message)); - } + ~ExportEventHandler() + { + AZ::SceneAPI::SceneCore::ExportingComponent::Deactivate(); + } - void OnExceptionMessage([[maybe_unused]] AZStd::string_view message) override - { - using namespace AZ::SceneAPI::Utilities; - AZ_TracePrintf(ErrorWindow, "[EXCEPTION] %.*s \n", AZ_STRING_ARG(message)); - } - }; + // this allows a Python script to add product assets on "scene export" + Events::ProcessingResult PrepareForExport(Events::PreExportEventContext& context) + { + return m_preExportEventContextFunction(context) ? Events::ProcessingResult::Success : Events::ProcessingResult::Failure; + } + }; - // a event bus to signal during scene building - struct ScriptBuildingNotifications - : public AZ::EBusTraits + void ScriptProcessorRuleBehavior::Activate() + { + Events::AssetImportRequestBus::Handler::BusConnect(); + m_exportEventHandler = AZStd::make_shared([this](Events::PreExportEventContext& context) + { + return this->DoPrepareForExport(context); + }); + } + + void ScriptProcessorRuleBehavior::Deactivate() + { + m_exportEventHandler.reset(); + Events::AssetImportRequestBus::Handler::BusDisconnect(); + UnloadPython(); + } + + bool ScriptProcessorRuleBehavior::LoadPython(const AZ::SceneAPI::Containers::Scene& scene) + { + if (m_editorPythonEventsInterface && !m_scriptFilename.empty()) + { + return true; + } + + // get project folder + auto settingsRegistry = AZ::SettingsRegistry::Get(); + AZ::IO::FixedMaxPath projectPath; + if (!settingsRegistry->Get(projectPath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_ProjectPath)) + { + return false; + } + + const AZ::SceneAPI::Containers::SceneManifest& manifest = scene.GetManifest(); + auto view = Containers::MakeDerivedFilterView(manifest.GetValueStorage()); + for (const auto& scriptItem : view) + { + AZ::IO::FixedMaxPath scriptFilename(scriptItem.GetScriptFilename()); + if (scriptFilename.empty()) { - virtual AZStd::string OnUpdateManifest(Containers::Scene& scene) = 0; - }; - using ScriptBuildingNotificationBus = AZ::EBus; + AZ_Warning("scene", false, "Skipping an empty script filename in (%s)", scene.GetManifestFilename().c_str()); + continue; + } - // a back end to handle scene builder events for a script - struct ScriptBuildingNotificationBusHandler final - : public ScriptBuildingNotificationBus::Handler - , public AZ::BehaviorEBusHandler + // check for file exist via absolute path + if (!IO::FileIOBase::GetInstance()->Exists(scriptFilename.c_str())) { - AZ_EBUS_BEHAVIOR_BINDER( - ScriptBuildingNotificationBusHandler, - "{DF2B51DE-A4D0-4139-B5D0-DF185832380D}", - AZ::SystemAllocator, - OnUpdateManifest); - - virtual ~ScriptBuildingNotificationBusHandler() = default; - - AZStd::string OnUpdateManifest(Containers::Scene& scene) override + // check for script in the project folder + AZ::IO::FixedMaxPath projectScriptPath = projectPath / scriptFilename; + if (!IO::FileIOBase::GetInstance()->Exists(projectScriptPath.c_str())) { - AZStd::string result; - CallResult(result, FN_OnUpdateManifest, scene); - return result; + AZ_Warning("scene", false, "Skipping a missing script (%s) in manifest file (%s)", + scriptFilename.c_str(), + scene.GetManifestFilename().c_str()); + continue; } + scriptFilename = AZStd::move(projectScriptPath); + } - static void Reflect(AZ::ReflectContext* context) + // lazy load the Python interface + auto editorPythonEventsInterface = AZ::Interface::Get(); + if (editorPythonEventsInterface->IsPythonActive() == false) + { + const bool silenceWarnings = false; + if (editorPythonEventsInterface->StartPython(silenceWarnings) == false) { - if (AZ::BehaviorContext* behaviorContext = azrtti_cast(context)) - { - behaviorContext->EBus("ScriptBuildingNotificationBus") - ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation) - ->Attribute(AZ::Script::Attributes::Module, "scene") - ->Handler() - ->Event("OnUpdateManifest", &ScriptBuildingNotificationBus::Events::OnUpdateManifest); - } + editorPythonEventsInterface = nullptr; } + } + + // both Python and the script need to be ready + if (editorPythonEventsInterface == nullptr || scriptFilename.empty()) + { + AZ_Warning("scene", false,"The scene manifest (%s) attempted to use script(%s) but Python is not enabled;" + "please add the EditorPythonBinding gem & PythonAssetBuilder gem to your project.", + scene.GetManifestFilename().c_str(), scriptFilename.c_str()); + + return false; + } + + m_editorPythonEventsInterface = editorPythonEventsInterface; + m_scriptFilename = scriptFilename.c_str(); + return true; + } + return false; + } + + void ScriptProcessorRuleBehavior::UnloadPython() + { + if (m_editorPythonEventsInterface) + { + const bool silenceWarnings = true; + m_editorPythonEventsInterface->StopPython(silenceWarnings); + m_editorPythonEventsInterface = nullptr; + } + } + + bool ScriptProcessorRuleBehavior::DoPrepareForExport(Events::PreExportEventContext& context) + { + using namespace AzToolsFramework; + + auto executeCallback = [this, &context]() + { + // set up script's hook callback + EditorPythonRunnerRequestBus::Broadcast(&EditorPythonRunnerRequestBus::Events::ExecuteByFilename, + m_scriptFilename.c_str()); + + // call script's callback to allow extra products + ExportProductList extraProducts; + ScriptBuildingNotificationBus::BroadcastResult(extraProducts, &ScriptBuildingNotificationBus::Events::OnPrepareForExport, + context.GetScene(), + context.GetOutputDirectory(), + context.GetPlatformIdentifier(), + context.GetProductList() + ); + + // add new products + for (const auto& product : extraProducts.GetProducts()) + { + context.GetProductList().AddProduct( + product.m_filename, + product.m_id, + product.m_assetType, + product.m_lod, + product.m_subId, + product.m_dependencyFlags); + } + }; + + if (LoadPython(context.GetScene())) + { + EditorPythonConsoleNotificationHandler logger; + m_editorPythonEventsInterface->ExecuteWithLock(executeCallback); + } + + return true; + } + + void ScriptProcessorRuleBehavior::Reflect(ReflectContext* context) + { + ScriptBuildingNotificationBusHandler::Reflect(context); + + SerializeContext* serializeContext = azrtti_cast(context); + if (serializeContext) + { + serializeContext->Class()->Version(1); + } + } + + Events::ProcessingResult ScriptProcessorRuleBehavior::UpdateManifest( + Containers::Scene& scene, + Events::AssetImportRequest::ManifestAction action, + [[maybe_unused]] Events::AssetImportRequest::RequestingApplication requester) + { + using namespace AzToolsFramework; + + if (action != ManifestAction::Update) + { + return Events::ProcessingResult::Ignored; + } + + if (LoadPython(scene)) + { + AZStd::string manifestUpdate; + auto executeCallback = [this, &scene, &manifestUpdate]() + { + EditorPythonRunnerRequestBus::Broadcast(&EditorPythonRunnerRequestBus::Events::ExecuteByFilename, + m_scriptFilename.c_str()); + + ScriptBuildingNotificationBus::BroadcastResult(manifestUpdate, &ScriptBuildingNotificationBus::Events::OnUpdateManifest, + scene); }; - void ScriptProcessorRuleBehavior::Activate() + EditorPythonConsoleNotificationHandler logger; + m_editorPythonEventsInterface->ExecuteWithLock(executeCallback); + + // attempt to load the manifest string back to a JSON-scene-manifest + auto sceneManifestLoader = AZStd::make_unique(); + auto loadOutcome = sceneManifestLoader->LoadFromString(manifestUpdate); + if (loadOutcome.IsSuccess()) { - Events::AssetImportRequestBus::Handler::BusConnect(); + scene.GetManifest().Clear(); + for (size_t entryIndex = 0; entryIndex < sceneManifestLoader->GetEntryCount(); ++entryIndex) + { + scene.GetManifest().AddEntry(sceneManifestLoader->GetValue(entryIndex)); + } + return Events::ProcessingResult::Success; } + } + return Events::ProcessingResult::Ignored; + } - void ScriptProcessorRuleBehavior::Deactivate() - { - Events::AssetImportRequestBus::Handler::BusDisconnect(); - if (m_editorPythonEventsInterface) - { - const bool silenceWarnings = true; - m_editorPythonEventsInterface->StopPython(silenceWarnings); - m_editorPythonEventsInterface = nullptr; - - } - } - - void ScriptProcessorRuleBehavior::Reflect(ReflectContext* context) - { - ScriptBuildingNotificationBusHandler::Reflect(context); - - SerializeContext* serializeContext = azrtti_cast(context); - if (serializeContext) - { - serializeContext->Class()->Version(1); - } - } - - Events::ProcessingResult ScriptProcessorRuleBehavior::UpdateManifest( - Containers::Scene& scene, - Events::AssetImportRequest::ManifestAction action, - [[maybe_unused]] Events::AssetImportRequest::RequestingApplication requester) - { - using namespace AzToolsFramework; - - if (action != ManifestAction::Update) - { - return Events::ProcessingResult::Ignored; - } - - // get project folder - auto settingsRegistry = AZ::SettingsRegistry::Get(); - AZ::IO::FixedMaxPath projectPath; - if (!settingsRegistry->Get(projectPath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_ProjectPath)) - { - return Events::ProcessingResult::Ignored; - } - - auto& sceneManifest = scene.GetManifest(); - auto view = Containers::MakeDerivedFilterView(sceneManifest.GetValueStorage()); - for (const auto& scriptItem : view) - { - AZ::IO::FixedMaxPath scriptFilename(scriptItem.GetScriptFilename()); - if (scriptFilename.empty()) - { - AZ_Warning("scene", false, "Skipping an empty script filename in (%s)", scene.GetManifestFilename().c_str()); - continue; - } - - // check for file exist via absolute path - if (!IO::FileIOBase::GetInstance()->Exists(scriptFilename.c_str())) - { - // check for script in the project folder - AZ::IO::FixedMaxPath projectScriptPath = projectPath / scriptFilename; - if (!IO::FileIOBase::GetInstance()->Exists(projectScriptPath.c_str())) - { - AZ_Warning("scene", false, "Skipping a missing script (%s) in manifest file (%s)", - scriptFilename.c_str(), - scene.GetManifestFilename().c_str()); - - continue; - } - scriptFilename = AZStd::move(projectScriptPath); - } - - // lazy load the Python interface - if (!m_editorPythonEventsInterface) - { - m_editorPythonEventsInterface = AZ::Interface::Get(); - const bool silenceWarnings = true; - m_editorPythonEventsInterface->StartPython(silenceWarnings); - } - - if (!m_editorPythonEventsInterface && !scriptFilename.empty()) - { - AZ_Warning("scene", false, - "The scene manifest (%s) attempted to use script(%s) but Python is not enabled;" - "please add the EditorPythonBinding gem & PythonAssetBuilder gem to your project.", - scene.GetManifestFilename().c_str(), scriptFilename.c_str()); - - return Events::ProcessingResult::Ignored; - } - - AZStd::string manifestUpdate; - auto executeCallback = [&scene, &scriptFilename, &manifestUpdate]() - { - EditorPythonRunnerRequestBus::Broadcast( - &EditorPythonRunnerRequestBus::Events::ExecuteByFilename, - scriptFilename.c_str()); - - ScriptBuildingNotificationBus::BroadcastResult( - manifestUpdate, - &ScriptBuildingNotificationBus::Events::OnUpdateManifest, - scene); - }; - EditorPythonConsoleNotificationHandler logger; - m_editorPythonEventsInterface->ExecuteWithLock(executeCallback); - - // attempt to load the manifest string back to a JSON-scene-manifest - auto sceneManifestLoader = AZStd::make_unique(); - auto loadOutcome = sceneManifestLoader->LoadFromString(manifestUpdate); - if (loadOutcome.IsSuccess()) - { - sceneManifest.Clear(); - for (size_t entryIndex = 0; entryIndex < sceneManifestLoader->GetEntryCount(); ++entryIndex) - { - sceneManifest.AddEntry(sceneManifestLoader->GetValue(entryIndex)); - } - return Events::ProcessingResult::Success; - } - } - return Events::ProcessingResult::Ignored; - } - - } // namespace Behaviors - } // namespace SceneAPI } // namespace AZ diff --git a/Code/Tools/SceneAPI/SceneData/Behaviors/ScriptProcessorRuleBehavior.h b/Code/Tools/SceneAPI/SceneData/Behaviors/ScriptProcessorRuleBehavior.h index da11614459..9dd1f5b970 100644 --- a/Code/Tools/SceneAPI/SceneData/Behaviors/ScriptProcessorRuleBehavior.h +++ b/Code/Tools/SceneAPI/SceneData/Behaviors/ScriptProcessorRuleBehavior.h @@ -11,40 +11,56 @@ #include #include #include +#include +#include namespace AzToolsFramework { class EditorPythonEventsInterface; } -namespace AZ +namespace AZ::SceneAPI::Events { - namespace SceneAPI + class PreExportEventContext; +} + +namespace AZ::SceneAPI::Containers +{ + class Scene; +} + +namespace AZ::SceneAPI::Behaviors +{ + class SCENE_DATA_CLASS ScriptProcessorRuleBehavior + : public SceneCore::BehaviorComponent + , public Events::AssetImportRequestBus::Handler { - namespace Behaviors - { - class SCENE_DATA_CLASS ScriptProcessorRuleBehavior - : public SceneCore::BehaviorComponent - , public Events::AssetImportRequestBus::Handler - { - public: - AZ_COMPONENT(ScriptProcessorRuleBehavior, "{24054E73-1B92-43B0-AC13-174B2F0E3F66}", SceneCore::BehaviorComponent); + public: + AZ_COMPONENT(ScriptProcessorRuleBehavior, "{24054E73-1B92-43B0-AC13-174B2F0E3F66}", SceneCore::BehaviorComponent); - ~ScriptProcessorRuleBehavior() override = default; + ~ScriptProcessorRuleBehavior() override = default; - SCENE_DATA_API void Activate() override; - SCENE_DATA_API void Deactivate() override; - static void Reflect(ReflectContext* context); + SCENE_DATA_API void Activate() override; + SCENE_DATA_API void Deactivate() override; + static void Reflect(ReflectContext* context); - // AssetImportRequestBus::Handler - SCENE_DATA_API Events::ProcessingResult UpdateManifest( - Containers::Scene& scene, - ManifestAction action, - RequestingApplication requester) override; + // AssetImportRequestBus::Handler + SCENE_DATA_API Events::ProcessingResult UpdateManifest( + Containers::Scene& scene, + ManifestAction action, + RequestingApplication requester) override; - private: - AzToolsFramework::EditorPythonEventsInterface* m_editorPythonEventsInterface = nullptr; - }; - } // namespace SceneData - } // namespace SceneAPI -} // namespace AZ + + protected: + bool LoadPython(const AZ::SceneAPI::Containers::Scene& scene); + void UnloadPython(); + bool DoPrepareForExport(Events::PreExportEventContext& context); + + private: + AzToolsFramework::EditorPythonEventsInterface* m_editorPythonEventsInterface = nullptr; + AZStd::string m_scriptFilename; + + struct ExportEventHandler; + AZStd::shared_ptr m_exportEventHandler; + }; +} // namespace AZ::SceneAPI::Behaviors diff --git a/Gems/EditorPythonBindings/Code/Source/PythonSystemComponent.cpp b/Gems/EditorPythonBindings/Code/Source/PythonSystemComponent.cpp index a2241b9953..a9e344f4ee 100644 --- a/Gems/EditorPythonBindings/Code/Source/PythonSystemComponent.cpp +++ b/Gems/EditorPythonBindings/Code/Source/PythonSystemComponent.cpp @@ -237,8 +237,8 @@ namespace EditorPythonBindings { ec->Class("PythonSystemComponent", "The Python interpreter") ->ClassElement(AZ::Edit::ClassElements::EditorData, "") - ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC_CE("System")) - ->Attribute(AZ::Edit::Attributes::AutoExpand, true) + ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC_CE("System")) + ->Attribute(AZ::Edit::Attributes::AutoExpand, true) ; } } @@ -284,10 +284,10 @@ namespace EditorPythonBindings ReleaseFunction m_releaseFunction; }; ReleaseInitalizeWaiterScope scope([this]() - { - m_initalizeWaiter.release(m_initalizeWaiterCount); - m_initalizeWaiterCount = 0; - }); + { + m_initalizeWaiter.release(m_initalizeWaiterCount); + m_initalizeWaiterCount = 0; + }); if (Py_IsInitialized()) { @@ -327,6 +327,11 @@ namespace EditorPythonBindings return result; } + bool PythonSystemComponent::IsPythonActive() + { + return Py_IsInitialized() != 0; + } + void PythonSystemComponent::WaitForInitialization() { m_initalizeWaiterCount++; diff --git a/Gems/EditorPythonBindings/Code/Source/PythonSystemComponent.h b/Gems/EditorPythonBindings/Code/Source/PythonSystemComponent.h index 82f44e674d..8e616ff85b 100644 --- a/Gems/EditorPythonBindings/Code/Source/PythonSystemComponent.h +++ b/Gems/EditorPythonBindings/Code/Source/PythonSystemComponent.h @@ -44,6 +44,7 @@ namespace EditorPythonBindings // AzToolsFramework::EditorPythonEventsInterface bool StartPython(bool silenceWarnings = false) override; bool StopPython(bool silenceWarnings = false) override; + bool IsPythonActive() override; void WaitForInitialization() override; void ExecuteWithLock(AZStd::function executionCallback) override; //////////////////////////////////////////////////////////////////////// From e32b7a7c870e87f1524c6255a302ed596b411d66 Mon Sep 17 00:00:00 2001 From: rgba16f <82187279+rgba16f@users.noreply.github.com> Date: Wed, 14 Jul 2021 15:14:42 -0500 Subject: [PATCH 11/21] Mac m1 fixes (#2150) * Update the Mac Editor plist application bundle identifier to be different than Lumberyard Fix the editor to have runtime dependencies on the scene modules. Signed-off-by: rgba16f <82187279+rgba16f@users.noreply.github.com> * Fix Metal RHI calling unsupported timestamp query api on M1 Macs. Add Query for Timestamps supported on device initialization. Signed-off-by: rgba16f <82187279+rgba16f@users.noreply.github.com> * Remove AZ::Scene* runtime dependencies from EmotionFX gem and add AZ::SceneUI as a runtime dependency of SceneProcessing::Editor module. Signed-off-by: rgba16f <82187279+rgba16f@users.noreply.github.com> --- Code/Editor/Platform/Mac/gui_info.plist | 2 +- .../Plugins/EditorCommon/CMakeLists.txt | 4 ++++ .../Metal/Code/Source/RHI/CommandListBase.cpp | 23 +++++++++++++++--- .../Metal/Code/Source/RHI/CommandListBase.h | 2 ++ .../Atom/RHI/Metal/Code/Source/RHI/Device.cpp | 24 +++++++++++++++---- Gems/SceneProcessing/Code/CMakeLists.txt | 1 + 6 files changed, 47 insertions(+), 9 deletions(-) diff --git a/Code/Editor/Platform/Mac/gui_info.plist b/Code/Editor/Platform/Mac/gui_info.plist index 0ec7b59e98..5b5f94e977 100644 --- a/Code/Editor/Platform/Mac/gui_info.plist +++ b/Code/Editor/Platform/Mac/gui_info.plist @@ -5,7 +5,7 @@ CFBundleExecutable Editor CFBundleIdentifier - com.Amazon.Lumberyard.Editor + org.O3DE.Editor CFBundlePackageType APPL CFBundleSignature diff --git a/Code/Editor/Plugins/EditorCommon/CMakeLists.txt b/Code/Editor/Plugins/EditorCommon/CMakeLists.txt index 96d9cce5b7..048f77cd0c 100644 --- a/Code/Editor/Plugins/EditorCommon/CMakeLists.txt +++ b/Code/Editor/Plugins/EditorCommon/CMakeLists.txt @@ -46,4 +46,8 @@ ly_add_target( AZ::AzCore AZ::AzToolsFramework AZ::AzQtComponents + RUNTIME_DEPENDENCIES + AZ::AzCore + AZ::AzToolsFramework + AZ::AzQtComponents ) diff --git a/Gems/Atom/RHI/Metal/Code/Source/RHI/CommandListBase.cpp b/Gems/Atom/RHI/Metal/Code/Source/RHI/CommandListBase.cpp index 675a593bc8..9df5f3362d 100644 --- a/Gems/Atom/RHI/Metal/Code/Source/RHI/CommandListBase.cpp +++ b/Gems/Atom/RHI/Metal/Code/Source/RHI/CommandListBase.cpp @@ -30,6 +30,7 @@ namespace AZ { AZ_UNUSED(device); m_hardwareQueueClass = hardwareQueueClass; + m_supportsInterDrawTimestamps = AZ::RHI::QueryTypeFlags::Timestamp == (device->GetFeatures().m_queryTypesMask[static_cast(hardwareQueueClass)] & AZ::RHI::QueryTypeFlags::Timestamp); } void CommandListBase::Reset() @@ -64,7 +65,10 @@ namespace AZ [m_encoder endEncoding]; m_encoder = nil; #if AZ_TRAIT_ATOM_METAL_COUNTER_SAMPLING - m_timeStampQueue.clear(); + if (m_supportsInterDrawTimestamps) + { + m_timeStampQueue.clear(); + } #endif } } @@ -144,9 +148,12 @@ namespace AZ m_isEncoded = true; #if AZ_TRAIT_ATOM_METAL_COUNTER_SAMPLING - for(auto& timeStamp: m_timeStampQueue) + if (m_supportsInterDrawTimestamps) { - SampleCounters(timeStamp.m_counterSampleBuffer, timeStamp.m_timeStampIndex); + for(auto& timeStamp: m_timeStampQueue) + { + SampleCounters(timeStamp.m_counterSampleBuffer, timeStamp.m_timeStampIndex); + } } #endif } @@ -195,6 +202,11 @@ namespace AZ #if AZ_TRAIT_ATOM_METAL_COUNTER_SAMPLING void CommandListBase::SampleCounters(id counterSampleBuffer, uint32_t sampleIndex) { + if (!m_supportsInterDrawTimestamps) + { + return; + } + AZ_Assert(sampleIndex >= 0, "Invalid sample index"); //useBarrier - Inserting a barrier ensures that encoded work is complete before the GPU samples the hardware counters. //If it is true there is a performance penalty but you will get consistent results @@ -231,6 +243,11 @@ namespace AZ void CommandListBase::SamplePassCounters(id counterSampleBuffer, uint32_t sampleIndex) { + if (!m_supportsInterDrawTimestamps) + { + return; + } + if(m_encoder == nil) { //Queue the query to be activated upon encoder creation. Applies to timestamp queries diff --git a/Gems/Atom/RHI/Metal/Code/Source/RHI/CommandListBase.h b/Gems/Atom/RHI/Metal/Code/Source/RHI/CommandListBase.h index 70678aed01..a344222e63 100644 --- a/Gems/Atom/RHI/Metal/Code/Source/RHI/CommandListBase.h +++ b/Gems/Atom/RHI/Metal/Code/Source/RHI/CommandListBase.h @@ -101,6 +101,8 @@ namespace AZ const AZStd::set>* m_residentHeaps = nullptr; + bool m_supportsInterDrawTimestamps = AZ_TRAIT_ATOM_METAL_COUNTER_SAMPLING; // iOS/TVOS = false, MacOS = defaults to true + #if AZ_TRAIT_ATOM_METAL_COUNTER_SAMPLING struct TimeStampData { diff --git a/Gems/Atom/RHI/Metal/Code/Source/RHI/Device.cpp b/Gems/Atom/RHI/Metal/Code/Source/RHI/Device.cpp index 5e23353b65..0da2ea6aaa 100644 --- a/Gems/Atom/RHI/Metal/Code/Source/RHI/Device.cpp +++ b/Gems/Atom/RHI/Metal/Code/Source/RHI/Device.cpp @@ -328,14 +328,28 @@ namespace AZ m_features.m_indirectDrawSupport = false; RHI::QueryTypeFlags counterSamplingFlags = RHI::QueryTypeFlags::None; - -#if AZ_TRAIT_ATOM_METAL_COUNTER_SAMPLING - counterSamplingFlags |= (RHI::QueryTypeFlags::Timestamp | RHI::QueryTypeFlags::PipelineStatistics); - m_features.m_queryTypesMask[static_cast(RHI::HardwareQueueClass::Copy)] = RHI::QueryTypeFlags::Timestamp; + + bool supportsInterDrawTimestamps = true; +#if defined(__IPHONE_14_0) || defined(__MAC_11_0) || defined(__TVOS_14_0) + if (@available(macOS 11.0, iOS 14, tvOS 14, *)) + { + supportsInterDrawTimestamps = [m_metalDevice supportsCounterSampling:MTLCounterSamplingPointAtDrawBoundary]; + } + else #endif + { + supportsInterDrawTimestamps = ![m_metalDevice.name containsString:@"Apple"]; // Apple GPU's don't support inter draw timestamps at the M1/A14 generation + } + + if (supportsInterDrawTimestamps) + { + counterSamplingFlags |= (RHI::QueryTypeFlags::Timestamp | RHI::QueryTypeFlags::PipelineStatistics); + m_features.m_queryTypesMask[static_cast(RHI::HardwareQueueClass::Copy)] = RHI::QueryTypeFlags::Timestamp; + } + m_features.m_queryTypesMask[static_cast(RHI::HardwareQueueClass::Graphics)] = RHI::QueryTypeFlags::Occlusion | counterSamplingFlags; //Compute queue can do gfx work - m_features.m_queryTypesMask[static_cast(RHI::HardwareQueueClass::Compute)] = RHI::QueryTypeFlags::Occlusion |counterSamplingFlags; + m_features.m_queryTypesMask[static_cast(RHI::HardwareQueueClass::Compute)] = RHI::QueryTypeFlags::Occlusion | counterSamplingFlags; m_features.m_occlusionQueryPrecise = true; //Values taken from https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf diff --git a/Gems/SceneProcessing/Code/CMakeLists.txt b/Gems/SceneProcessing/Code/CMakeLists.txt index 9aa5bc6763..b33e157e51 100644 --- a/Gems/SceneProcessing/Code/CMakeLists.txt +++ b/Gems/SceneProcessing/Code/CMakeLists.txt @@ -61,6 +61,7 @@ if (PAL_TRAIT_BUILD_HOST_TOOLS) RUNTIME_DEPENDENCIES AZ::SceneCore AZ::SceneData + AZ::SceneUI ) # the SceneProcessing.Editor module above is only used in Builders and Tools. ly_create_alias(NAME SceneProcessing.Builders NAMESPACE Gem TARGETS Gem::SceneProcessing.Editor) From 32a27966202fbc18e74d760775602f2b28d318b7 Mon Sep 17 00:00:00 2001 From: galibzon <66021303+galibzon@users.noreply.github.com> Date: Wed, 14 Jul 2021 15:38:08 -0500 Subject: [PATCH 12/21] [ATOM-15978] ShaderVariantAssetBuilder needs to fill up ShaderPlatformInterface::m_srgLayouts for Metal (#2156) ShaderVariantAssetBuilder builds SRG Layout data only for Metal. Signed-off-by: garrieta --- .../AzslShaderBuilderSystemComponent.cpp | 2 +- .../Editor/ShaderVariantAssetBuilder.cpp | 135 ++++++++++++++++++ .../Atom/RHI.Edit/ShaderPlatformInterface.h | 6 + .../RHI.Edit/ShaderCompilerArguments.cpp | 8 +- .../RHI.Builders/ShaderPlatformInterface.h | 2 + 5 files changed, 151 insertions(+), 2 deletions(-) diff --git a/Gems/Atom/Asset/Shader/Code/Source/Editor/AzslShaderBuilderSystemComponent.cpp b/Gems/Atom/Asset/Shader/Code/Source/Editor/AzslShaderBuilderSystemComponent.cpp index afa11d8e16..f6b99cac12 100644 --- a/Gems/Atom/Asset/Shader/Code/Source/Editor/AzslShaderBuilderSystemComponent.cpp +++ b/Gems/Atom/Asset/Shader/Code/Source/Editor/AzslShaderBuilderSystemComponent.cpp @@ -95,7 +95,7 @@ namespace AZ shaderVariantAssetBuilderDescriptor.m_name = "Shader Variant Asset Builder"; // Both "Shader Variant Asset Builder" and "Shader Asset Builder" produce ShaderVariantAsset products. If you update // ShaderVariantAsset you will need to update BOTH version numbers, not just "Shader Variant Asset Builder". - shaderVariantAssetBuilderDescriptor.m_version = 23; // ATOM-15472 + shaderVariantAssetBuilderDescriptor.m_version = 24; // ATOM-15978 shaderVariantAssetBuilderDescriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern(AZStd::string::format("*.%s", RPI::ShaderVariantListSourceData::Extension), AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard)); shaderVariantAssetBuilderDescriptor.m_busId = azrtti_typeid(); shaderVariantAssetBuilderDescriptor.m_createJobFunction = AZStd::bind(&ShaderVariantAssetBuilder::CreateJobs, &m_shaderVariantAssetBuilder, AZStd::placeholders::_1, AZStd::placeholders::_2); diff --git a/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderVariantAssetBuilder.cpp b/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderVariantAssetBuilder.cpp index 7be98ab056..010778575e 100644 --- a/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderVariantAssetBuilder.cpp +++ b/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderVariantAssetBuilder.cpp @@ -48,6 +48,7 @@ #include "ShaderAssetBuilder.h" #include "ShaderBuilderUtility.h" +#include "SrgLayoutUtility.h" #include "AzslData.h" #include "AzslCompiler.h" #include @@ -520,6 +521,96 @@ namespace AZ return; } } + + static bool LoadSrgLayoutListFromShaderAssetBuilder( + const RHI::ShaderPlatformInterface* shaderPlatformInterface, + const AssetBuilderSDK::PlatformInfo& platformInfo, + const AzslCompiler& azslCompiler, const AZStd::string& shaderSourceFileFullPath, + const RPI::SupervariantIndex supervariantIndex, + const bool platformUsesRegisterSpaces, + RPI::ShaderResourceGroupLayoutList& srgLayoutList, + RootConstantData& rootConstantData) + { + auto srgJsonPathOutcome = ShaderBuilderUtility::ObtainBuildArtifactPathFromShaderAssetBuilder( + shaderPlatformInterface->GetAPIUniqueIndex(), platformInfo.m_identifier, shaderSourceFileFullPath, supervariantIndex.GetIndex(), AZ::RPI::ShaderAssetSubId::SrgJson); + if (!srgJsonPathOutcome.IsSuccess()) + { + AZ_Error(ShaderVariantAssetBuilderName, false, "%s", srgJsonPathOutcome.GetError().c_str()); + return false; + } + + auto srgJsonPath = srgJsonPathOutcome.TakeValue(); + auto jsonOutcome = JsonSerializationUtils::ReadJsonFile(srgJsonPath); + if (!jsonOutcome.IsSuccess()) + { + AZ_Error(ShaderVariantAssetBuilderName, false, "%s", jsonOutcome.GetError().c_str()); + return false; + } + SrgDataContainer srgData; + if (!azslCompiler.ParseSrgPopulateSrgData(jsonOutcome.GetValue(), srgData)) + { + AZ_Error(ShaderVariantAssetBuilderName, false, "Failed to parse srg data"); + return false; + } + // Add all Shader Resource Group Assets that were defined in the shader code to the shader asset + if (!SrgLayoutUtility::LoadShaderResourceGroupLayouts(ShaderVariantAssetBuilderName, srgData, platformUsesRegisterSpaces, srgLayoutList)) + { + AZ_Error(ShaderVariantAssetBuilderName, false, "Failed to load ShaderResourceGroupLayouts"); + return false; + } + + for (auto srgLayout : srgLayoutList) + { + if (!srgLayout->Finalize()) + { + AZ_Error(ShaderVariantAssetBuilderName, false, + "Failed to finalize SrgLayout %s", srgLayout->GetName().GetCStr()); + return false; + } + } + + // Access the root constants reflection + if (!azslCompiler.ParseSrgPopulateRootConstantData( + jsonOutcome.GetValue(), + rootConstantData)) // consuming data from --srg ("InlineConstantBuffer" subjson section) + { + AZ_Error(ShaderVariantAssetBuilderName, false, "Failed to obtain root constant data reflection"); + return false; + } + + return true; + } + + static bool LoadBindingDependenciesFromShaderAssetBuilder( + const RHI::ShaderPlatformInterface* shaderPlatformInterface, + const AssetBuilderSDK::PlatformInfo& platformInfo, + const AzslCompiler& azslCompiler, const AZStd::string& shaderSourceFileFullPath, + const RPI::SupervariantIndex supervariantIndex, + BindingDependencies& bindingDependencies) + { + auto bindingsJsonPathOutcome = ShaderBuilderUtility::ObtainBuildArtifactPathFromShaderAssetBuilder( + shaderPlatformInterface->GetAPIUniqueIndex(), platformInfo.m_identifier, shaderSourceFileFullPath, supervariantIndex.GetIndex(), AZ::RPI::ShaderAssetSubId::BindingdepJson); + if (!bindingsJsonPathOutcome.IsSuccess()) + { + AZ_Error(ShaderVariantAssetBuilderName, false, "%s", bindingsJsonPathOutcome.GetError().c_str()); + return false; + } + + auto bindingsJsonPath = bindingsJsonPathOutcome.TakeValue(); + auto jsonOutcome = JsonSerializationUtils::ReadJsonFile(bindingsJsonPath); + if (!jsonOutcome.IsSuccess()) + { + AZ_Error(ShaderVariantAssetBuilderName, false, "%s", jsonOutcome.GetError().c_str()); + return false; + } + if (!azslCompiler.ParseBindingdepPopulateBindingDependencies(jsonOutcome.GetValue(), bindingDependencies)) + { + AZ_Error(ShaderVariantAssetBuilderName, false, "Failed to parse binding dependencies data"); + return false; + } + + return true; + } // Returns the content of the hlsl file for the given supervariant as produced by ShaderAsssetBuilder. @@ -773,6 +864,50 @@ namespace AZ response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed; return; } + + //! It is important to keep this refcounted pointer outside of the if block to prevent it from being destroyed. + RHI::Ptr pipelineLayoutDescriptor; + if (shaderPlatformInterface->VariantCompilationRequiresSrgLayoutData()) + { + AZStd::string azslcCompilerParameters = + shaderPlatformInterface->GetAzslCompilerParameters(buildOptions.m_compilerArguments); + const bool platformUsesRegisterSpaces = + (AzFramework::StringFunc::Find(azslcCompilerParameters, "--use-spaces") != AZStd::string::npos); + + RPI::ShaderResourceGroupLayoutList srgLayoutList; + RootConstantData rootConstantData; + if (!LoadSrgLayoutListFromShaderAssetBuilder( + shaderPlatformInterface, request.m_platformInfo, azslc, shaderSourceFileFullPath, supervariantIndex, + platformUsesRegisterSpaces, + srgLayoutList, + rootConstantData)) + { + response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed; + return; + } + + BindingDependencies bindingDependencies; + if (!LoadBindingDependenciesFromShaderAssetBuilder( + shaderPlatformInterface, request.m_platformInfo, azslc, shaderSourceFileFullPath, supervariantIndex, + bindingDependencies)) + { + response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed; + return; + } + + pipelineLayoutDescriptor = + ShaderBuilderUtility::BuildPipelineLayoutDescriptorForApi( + ShaderVariantAssetBuilderName, srgLayoutList, shaderEntryPoints, buildOptions.m_compilerArguments, rootConstantData, + shaderPlatformInterface, bindingDependencies); + if (!pipelineLayoutDescriptor) + { + AZ_Error( + ShaderVariantAssetBuilderName, false, "Failed to build pipeline layout descriptor for api=[%s]", + shaderPlatformInterface->GetAPIName().GetCStr()); + response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed; + return; + } + } // Setup the shader variant creation context: ShaderVariantCreationContext shaderVariantCreationContext = diff --git a/Gems/Atom/RHI/Code/Include/Atom/RHI.Edit/ShaderPlatformInterface.h b/Gems/Atom/RHI/Code/Include/Atom/RHI.Edit/ShaderPlatformInterface.h index d83fbf2267..f7db5f865b 100644 --- a/Gems/Atom/RHI/Code/Include/Atom/RHI.Edit/ShaderPlatformInterface.h +++ b/Gems/Atom/RHI/Code/Include/Atom/RHI.Edit/ShaderPlatformInterface.h @@ -144,6 +144,12 @@ namespace AZ const ShaderResourceGroupInfoList& srgInfoList, const RootConstantsInfo& rootConstantsInfo, const ShaderCompilerArguments& shaderCompilerArguments) = 0; + + //! In general, shader compilation doesn't require SRG Layout data, but RHIs like + //! Metal don't do well if unused resources (descriptors) are not bound. If this function returns TRUE + //! the ShaderVariantAssetBuilder will invoke BuildPipelineLayoutDescriptor() so the RHI gets the chance to + //! build SRG Layout data which will be useful when compiling MetalISL to Metal byte code. + virtual bool VariantCompilationRequiresSrgLayoutData() const { return false; } //! See AZ::RHI::Factory::GetAPIUniqueIndex() for details. //! See AZ::RHI::Limits::APIType::PerPlatformApiUniqueIndexMax. diff --git a/Gems/Atom/RHI/Code/Source/RHI.Edit/ShaderCompilerArguments.cpp b/Gems/Atom/RHI/Code/Source/RHI.Edit/ShaderCompilerArguments.cpp index 0801101178..2f648acc05 100644 --- a/Gems/Atom/RHI/Code/Source/RHI.Edit/ShaderCompilerArguments.cpp +++ b/Gems/Atom/RHI/Code/Source/RHI.Edit/ShaderCompilerArguments.cpp @@ -159,7 +159,13 @@ namespace AZ arguments += " -Zi"; // Generate debug information arguments += " -Zss"; // Compute Shader Hash considering source information } - arguments += " " + m_dxcAdditionalFreeArguments; + // strip spaces at both sides + AZStd::string dxcAdditionalFreeArguments = m_dxcAdditionalFreeArguments; + AzFramework::StringFunc::TrimWhiteSpace(dxcAdditionalFreeArguments, true, true); + if (!dxcAdditionalFreeArguments.empty()) + { + arguments += " " + dxcAdditionalFreeArguments; + } return arguments; } } diff --git a/Gems/Atom/RHI/Metal/Code/Source/RHI.Builders/ShaderPlatformInterface.h b/Gems/Atom/RHI/Metal/Code/Source/RHI.Builders/ShaderPlatformInterface.h index 935d2e6471..0be02053cd 100644 --- a/Gems/Atom/RHI/Metal/Code/Source/RHI.Builders/ShaderPlatformInterface.h +++ b/Gems/Atom/RHI/Metal/Code/Source/RHI.Builders/ShaderPlatformInterface.h @@ -40,6 +40,8 @@ namespace AZ const ShaderResourceGroupInfoList& srgInfoList, const RootConstantsInfo& rootConstantsInfo, const RHI::ShaderCompilerArguments& shaderCompilerArguments) override; + + bool VariantCompilationRequiresSrgLayoutData() const override { return true; } bool CompilePlatformInternal( const AssetBuilderSDK::PlatformInfo& platform, From 59e9f108537b84d6a37718fc8a637892a546b8dc Mon Sep 17 00:00:00 2001 From: michabr <82236305+michabr@users.noreply.github.com> Date: Wed, 14 Jul 2021 15:57:02 -0700 Subject: [PATCH 13/21] Split LyShine .Editor target to .Tools and .Builders (#2161) * Split LyShine .Editor target to .Tools and .Builders Signed-off-by: abrmich * Fix compile error for Tests target Signed-off-by: abrmich --- Gems/LyShine/Code/CMakeLists.txt | 66 +++++++++++++++++-- Gems/LyShine/Code/Source/LyShineModule.cpp | 12 ++-- .../Code/lyshine_common_module_files.cmake | 2 + Gems/LyShine/Code/lyshine_static_files.cmake | 2 - 4 files changed, 68 insertions(+), 14 deletions(-) diff --git a/Gems/LyShine/Code/CMakeLists.txt b/Gems/LyShine/Code/CMakeLists.txt index fe5e2e77df..905228e414 100644 --- a/Gems/LyShine/Code/CMakeLists.txt +++ b/Gems/LyShine/Code/CMakeLists.txt @@ -63,7 +63,6 @@ if (PAL_TRAIT_BUILD_HOST_TOOLS) AUTORCC FILES_CMAKE lyshine_uicanvaseditor_files.cmake - lyshine_editor_builder_files.cmake INCLUDE_DIRECTORIES PRIVATE . @@ -78,7 +77,6 @@ if (PAL_TRAIT_BUILD_HOST_TOOLS) 3rdParty::Qt::Widgets AZ::AzCore AZ::AzToolsFramework - AZ::AssetBuilderSDK Legacy::EditorCommon Legacy::EditorCore Gem::LyShine.Static @@ -98,7 +96,6 @@ if (PAL_TRAIT_BUILD_HOST_TOOLS) ly_add_target( NAME LyShine.Editor GEM_MODULE - NAMESPACE Gem FILES_CMAKE lyshine_common_module_files.cmake @@ -115,17 +112,72 @@ if (PAL_TRAIT_BUILD_HOST_TOOLS) BUILD_DEPENDENCIES PRIVATE Legacy::CryCommon - AZ::AssetBuilderSDK + AZ::AzToolsFramework Gem::LyShine.Editor.Static Gem::LmbrCentral.Editor Gem::TextureAtlas.Editor RUNTIME_DEPENDENCIES Gem::LmbrCentral.Editor Gem::TextureAtlas.Editor -) + ) + + # by naming this target LyShine.Builders it ensures that it is loaded + # in any pipeline tools (Like Asset Processor, AssetBuilder, etc) + ly_add_target( + NAME LyShine.Builders.Static STATIC + NAMESPACE Gem + FILES_CMAKE + lyshine_editor_builder_files.cmake + INCLUDE_DIRECTORIES + PRIVATE + . + Source + PUBLIC + Include + BUILD_DEPENDENCIES + PRIVATE + AZ::AzCore + AZ::AzToolsFramework + AZ::AssetBuilderSDK + Gem::LyShine.Static + Legacy::CryCommon + Gem::LmbrCentral.Editor + Gem::TextureAtlas.Editor + Gem::AtomToolsFramework.Static + Gem::AtomToolsFramework.Editor + ${additional_dependencies} + PUBLIC + Gem::Atom_RPI.Public + Gem::Atom_Utils.Static + Gem::Atom_Bootstrap.Headers + ) + + ly_add_target( + NAME LyShine.Builders GEM_MODULE + NAMESPACE Gem + OUTPUT_NAME Gem.LyShine.Builders + FILES_CMAKE + lyshine_common_module_files.cmake + COMPILE_DEFINITIONS + PRIVATE + LYSHINE_BUILDER + INCLUDE_DIRECTORIES + PRIVATE + . + Source + Editor + PUBLIC + Include + BUILD_DEPENDENCIES + PRIVATE + Legacy::CryCommon + AZ::AssetBuilderSDK + Gem::LyShine.Builders.Static + Gem::LmbrCentral.Editor + Gem::TextureAtlas.Editor + ) # by default, load the above "Gem::LyShine.Editor" module in dev tools: - ly_create_alias(NAME LyShine.Builders NAMESPACE Gem TARGETS Gem::LyShine.Editor) ly_create_alias(NAME LyShine.Tools NAMESPACE Gem TARGETS Gem::LyShine.Editor) endif() @@ -169,6 +221,7 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED) COMPILE_DEFINITIONS PRIVATE LYSHINE_EDITOR + LYSHINE_BUILDER INCLUDE_DIRECTORIES PRIVATE Tests @@ -182,6 +235,7 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED) Legacy::CryCommon AZ::AssetBuilderSDK Gem::LyShine.Editor.Static + Gem::LyShine.Builders.Static Gem::LmbrCentral.Editor Gem::TextureAtlas RUNTIME_DEPENDENCIES diff --git a/Gems/LyShine/Code/Source/LyShineModule.cpp b/Gems/LyShine/Code/Source/LyShineModule.cpp index 011bb9f203..64ce61b07a 100644 --- a/Gems/LyShine/Code/Source/LyShineModule.cpp +++ b/Gems/LyShine/Code/Source/LyShineModule.cpp @@ -52,9 +52,9 @@ #include "World/UiCanvasProxyRefComponent.h" #include "World/UiCanvasOnMeshComponent.h" -#if defined (LYSHINE_EDITOR) -# include "Pipeline/LyShineBuilder/LyShineBuilderComponent.h" -#endif // LYSHINE_EDITOR +#if defined(LYSHINE_BUILDER) +#include "Pipeline/LyShineBuilder/LyShineBuilderComponent.h" +#endif // LYSHINE_BUILDER namespace LyShine { @@ -64,7 +64,7 @@ namespace LyShine // Push results of [MyComponent]::CreateDescriptor() into m_descriptors here. m_descriptors.insert(m_descriptors.end(), { LyShineSystemComponent::CreateDescriptor(), -#if defined (LYSHINE_EDITOR) +#if defined(LYSHINE_EDITOR) LyShineEditor::LyShineEditorSystemComponent::CreateDescriptor(), #endif UiCanvasAssetRefComponent::CreateDescriptor(), @@ -103,7 +103,7 @@ namespace LyShine UiRadioButtonComponent::CreateDescriptor(), UiRadioButtonGroupComponent::CreateDescriptor(), UiParticleEmitterComponent::CreateDescriptor(), - #if defined(LYSHINE_EDITOR) + #if defined(LYSHINE_BUILDER) // Builder LyShineBuilder::LyShineBuilderComponent::CreateDescriptor(), #endif @@ -123,7 +123,7 @@ namespace LyShine { return AZ::ComponentTypeList{ azrtti_typeid(), - #if defined (LYSHINE_EDITOR) + #if defined(LYSHINE_EDITOR) azrtti_typeid(), #endif #if AZ_LOADSCREENCOMPONENT_ENABLED diff --git a/Gems/LyShine/Code/lyshine_common_module_files.cmake b/Gems/LyShine/Code/lyshine_common_module_files.cmake index e725112e53..38d01eb181 100644 --- a/Gems/LyShine/Code/lyshine_common_module_files.cmake +++ b/Gems/LyShine/Code/lyshine_common_module_files.cmake @@ -8,4 +8,6 @@ set(FILES Source/LyShineModule.cpp Source/LyShineModule.h + Source/LyShineSystemComponent.cpp + Source/LyShineSystemComponent.h ) diff --git a/Gems/LyShine/Code/lyshine_static_files.cmake b/Gems/LyShine/Code/lyshine_static_files.cmake index 8c2275ab92..749ce06125 100644 --- a/Gems/LyShine/Code/lyshine_static_files.cmake +++ b/Gems/LyShine/Code/lyshine_static_files.cmake @@ -26,8 +26,6 @@ set(FILES Source/EditorPropertyTypes.h Source/LyShineLoadScreen.cpp Source/LyShineLoadScreen.h - Source/LyShineSystemComponent.cpp - Source/LyShineSystemComponent.h Source/RenderGraph.cpp Source/RenderGraph.h Source/TextMarkup.cpp From a872111ca5718ae4e6948e1cdd5c92ff30670a3e Mon Sep 17 00:00:00 2001 From: michabr <82236305+michabr@users.noreply.github.com> Date: Wed, 14 Jul 2021 15:57:16 -0700 Subject: [PATCH 14/21] Fix LyShine bus attempt to connect twice (#2175) Signed-off-by: abrmich --- Gems/LyShine/Code/Source/LyShineSystemComponent.cpp | 2 -- Gems/LyShine/Code/Source/LyShineSystemComponent.h | 2 -- 2 files changed, 4 deletions(-) diff --git a/Gems/LyShine/Code/Source/LyShineSystemComponent.cpp b/Gems/LyShine/Code/Source/LyShineSystemComponent.cpp index 7521c8b6c1..b420e551b5 100644 --- a/Gems/LyShine/Code/Source/LyShineSystemComponent.cpp +++ b/Gems/LyShine/Code/Source/LyShineSystemComponent.cpp @@ -148,7 +148,6 @@ namespace LyShine { LyShineAllocatorScope::ActivateAllocators(); - LyShineRequestBus::Handler::BusConnect(); UiSystemBus::Handler::BusConnect(); UiSystemToolsBus::Handler::BusConnect(); UiFrameworkBus::Handler::BusConnect(); @@ -196,7 +195,6 @@ namespace LyShine UiSystemBus::Handler::BusDisconnect(); UiSystemToolsBus::Handler::BusDisconnect(); UiFrameworkBus::Handler::BusDisconnect(); - LyShineRequestBus::Handler::BusDisconnect(); CrySystemEventBus::Handler::BusDisconnect(); LyShineAllocatorScope::DeactivateAllocators(); diff --git a/Gems/LyShine/Code/Source/LyShineSystemComponent.h b/Gems/LyShine/Code/Source/LyShineSystemComponent.h index 5b455715ee..70e7eb5fe5 100644 --- a/Gems/LyShine/Code/Source/LyShineSystemComponent.h +++ b/Gems/LyShine/Code/Source/LyShineSystemComponent.h @@ -13,7 +13,6 @@ #include -#include #include #include #include @@ -28,7 +27,6 @@ namespace LyShine class LyShineSystemComponent : public AZ::Component - , protected LyShineRequestBus::Handler , protected UiSystemBus::Handler , protected UiSystemToolsBus::Handler , protected LyShineAllocatorScope From 67ea31363d9f8f87e7006a0ddcbb70c899e25d3e Mon Sep 17 00:00:00 2001 From: Nicholas Van Sickle Date: Wed, 14 Jul 2021 16:22:08 -0700 Subject: [PATCH 15/21] Fix gesture recognizer spamming errors when clicking the mouse (#2181) The synthetic input devices created by the Qt -> AZ input system were being detected by the gesture recognizer, which confused it - this is a potential general issue for anything listening to the AZ input system out of game mode, but this is the only recorded issue so far. I've added a check in this case to ensure the event comes from the real, physical mouse device instead of one of our synthetic ones, stopping the spam. Signed-off-by: nvsickle --- .../Code/Include/Gestures/IGestureRecognizer.h | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/Gems/Gestures/Code/Include/Gestures/IGestureRecognizer.h b/Gems/Gestures/Code/Include/Gestures/IGestureRecognizer.h index 69e28df102..e606d68a3e 100644 --- a/Gems/Gestures/Code/Include/Gestures/IGestureRecognizer.h +++ b/Gems/Gestures/Code/Include/Gestures/IGestureRecognizer.h @@ -209,12 +209,17 @@ namespace Gestures //////////////////////////////////////////////////////////////////////////////////////////////// inline uint32_t IRecognizer::GetGesturePointerIndex(const AzFramework::InputChannel& inputChannel) { - const auto& mouseButtonIt = AZStd::find(AzFramework::InputDeviceMouse::Button::All.cbegin(), - AzFramework::InputDeviceMouse::Button::All.cend(), - inputChannel.GetInputChannelId()); - if (mouseButtonIt != AzFramework::InputDeviceMouse::Button::All.cend()) + // Only recognize gestures for the default mouse input device. The Editor may register synthetic + // mouse input devices with the same mouse input channels, which can confuse gesture recognition. + if (inputChannel.GetInputDevice().GetInputDeviceId() == AzFramework::InputDeviceMouse::Id) { - return static_cast(mouseButtonIt - AzFramework::InputDeviceMouse::Button::All.cbegin()); + const auto& mouseButtonIt = AZStd::find(AzFramework::InputDeviceMouse::Button::All.cbegin(), + AzFramework::InputDeviceMouse::Button::All.cend(), + inputChannel.GetInputChannelId()); + if (mouseButtonIt != AzFramework::InputDeviceMouse::Button::All.cend()) + { + return static_cast(mouseButtonIt - AzFramework::InputDeviceMouse::Button::All.cbegin()); + } } const auto& touchIndexIt = AZStd::find(AzFramework::InputDeviceTouch::Touch::All.cbegin(), From 72ea2ca1b4509364f9ab0629f8ea904ad6d55d21 Mon Sep 17 00:00:00 2001 From: bosnichd Date: Wed, 14 Jul 2021 17:44:44 -0600 Subject: [PATCH 16/21] Change the InputDeviceImplementationRequest to be addressable by an InputDevcieId to account for the situation where someone may want to swap out the implementation for a single instance of a devcie type rather than all instances of that device type (the latter can still be achieved using Broadcast instead of Event when calling the EBus method). (#2180) Signed-off-by: bosnichd --- .../Buses/Requests/InputDeviceRequestBus.h | 30 +++++++++++++++---- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/Code/Framework/AzFramework/AzFramework/Input/Buses/Requests/InputDeviceRequestBus.h b/Code/Framework/AzFramework/AzFramework/Input/Buses/Requests/InputDeviceRequestBus.h index a1ec4a05f0..a122fe3133 100644 --- a/Code/Framework/AzFramework/AzFramework/Input/Buses/Requests/InputDeviceRequestBus.h +++ b/Code/Framework/AzFramework/AzFramework/Input/Buses/Requests/InputDeviceRequestBus.h @@ -205,6 +205,25 @@ namespace AzFramework class InputDeviceImplementationRequest : public AZ::EBusTraits { public: + //////////////////////////////////////////////////////////////////////////////////////////// + //! EBus Trait: requests can be addressed to a specific InputDeviceId so that they are only + //! handled by one input device that has connected to the bus using that unique id, or they + //! can be broadcast to all input devices that have connected to the bus, regardless of id. + //! Connected input devices are ordered by their local player index from lowest to highest. + static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ByIdAndOrdered; + + //////////////////////////////////////////////////////////////////////////////////////////// + //! EBus Trait: requests should be handled by only one input device connected to each id + static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single; + + //////////////////////////////////////////////////////////////////////////////////////////// + //! EBus Trait: requests can be addressed to a specific InputDeviceId + using BusIdType = InputDeviceId; + + //////////////////////////////////////////////////////////////////////////////////////////// + //! EBus Trait: requests are handled by connected devices in the order of local player index + using BusIdOrderCompare = AZStd::less; + //////////////////////////////////////////////////////////////////////////////////////////// //! Alias for the EBus implementation of this interface using Bus = AZ::EBus>; @@ -214,11 +233,12 @@ namespace AzFramework using CreateFunctionType = typename InputDeviceType::Implementation*(*)(InputDeviceType&); //////////////////////////////////////////////////////////////////////////////////////////// - //! Create a custom implementation for all the existing instances of this input device type. + //! 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 CreateCustomImplementation(CreateFunctionType createFunction) = 0; + virtual void SetCustomImplementation(CreateFunctionType createFunction) = 0; }; //////////////////////////////////////////////////////////////////////////////////////////////// @@ -238,7 +258,7 @@ namespace AzFramework AZ_INLINE InputDeviceImplementationRequestHandler(InputDeviceType& inputDevice) : m_inputDevice(inputDevice) { - InputDeviceImplementationRequest::Bus::Handler::BusConnect(); + InputDeviceImplementationRequest::Bus::Handler::BusConnect(m_inputDevice.GetInputDeviceId()); } //////////////////////////////////////////////////////////////////////////////////////////// @@ -251,8 +271,8 @@ namespace AzFramework using CreateFunctionType = typename InputDeviceType::Implementation*(*)(InputDeviceType&); //////////////////////////////////////////////////////////////////////////////////////////// - //! \ref InputDeviceImplementationRequest::CreateCustomImplementation - AZ_INLINE void CreateCustomImplementation(CreateFunctionType createFunction) override + //! \ref InputDeviceImplementationRequest::SetCustomImplementation + AZ_INLINE void SetCustomImplementation(CreateFunctionType createFunction) override { AZStd::unique_ptr newImplementation; if (createFunction) From 7eeaf7dad5d1c28b0e817cb26bad88df58d0c914 Mon Sep 17 00:00:00 2001 From: SJ Date: Wed, 14 Jul 2021 17:32:07 -0700 Subject: [PATCH 17/21] Generate install layout for Mac (#2144) * 1. Move call to install(TARGETS...) to its own function to create a platform specific implementation for Mac. 2. Add linker-signed flag to work around cmake install issue. Signed-off-by: amzn-sj * Add newline Signed-off-by: amzn-sj * 1. Rename function to ly_install_target_override and only call it if it has been implemented. 2. Pass in runtime, archive, and library directory paths as params to clean it up a bit. Signed-off-by: amzn-sj --- cmake/Platform/Common/Install_common.cmake | 45 ++++++++++++++------ cmake/Platform/Mac/Configurations_mac.cmake | 4 +- cmake/Platform/Mac/Install_mac.cmake | 46 +++++++++++++++++++-- 3 files changed, 78 insertions(+), 17 deletions(-) diff --git a/cmake/Platform/Common/Install_common.cmake b/cmake/Platform/Common/Install_common.cmake index 7ab6006220..20b7a7c81d 100644 --- a/cmake/Platform/Common/Install_common.cmake +++ b/cmake/Platform/Common/Install_common.cmake @@ -100,18 +100,29 @@ 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() - install( - TARGETS ${TARGET_NAME} - ARCHIVE - DESTINATION ${archive_output_directory}/${PAL_PLATFORM_NAME}/$ - COMPONENT ${install_component} - LIBRARY - DESTINATION ${library_output_directory}/${PAL_PLATFORM_NAME}/$/${target_library_output_subdirectory} - COMPONENT ${install_component} - RUNTIME - DESTINATION ${runtime_output_directory}/${PAL_PLATFORM_NAME}/$/${target_runtime_output_subdirectory} - COMPONENT ${install_component} - ) + if(COMMAND ly_install_target_override) + # Mac needs special handling because of a cmake issue + ly_install_target_override(TARGET ${TARGET_NAME} + ARCHIVE_DIR ${archive_output_directory} + LIBRARY_DIR ${library_output_directory} + RUNTIME_DIR ${runtime_output_directory} + LIBRARY_SUBDIR ${target_library_output_subdirectory} + RUNTIME_SUBDIR ${target_runtime_output_subdirectory} + ) + else() + install( + TARGETS ${TARGET_NAME} + ARCHIVE + DESTINATION ${archive_output_directory}/${PAL_PLATFORM_NAME}/$ + COMPONENT ${install_component} + LIBRARY + DESTINATION ${library_output_directory}/${PAL_PLATFORM_NAME}/$/${target_library_output_subdirectory} + COMPONENT ${install_component} + RUNTIME + DESTINATION ${runtime_output_directory}/${PAL_PLATFORM_NAME}/$/${target_runtime_output_subdirectory} + COMPONENT ${install_component} + ) + endif() # CMakeLists.txt file string(REGEX MATCH "(.*)::(.*)$" match ${ALIAS_TARGET_NAME}) @@ -487,7 +498,7 @@ function(ly_setup_others) # Scripts file(GLOB o3de_scripts "${LY_ROOT_FOLDER}/scripts/o3de.*") - install(FILES + install(PROGRAMS ${o3de_scripts} DESTINATION ./scripts ) @@ -505,6 +516,14 @@ function(ly_setup_others) DESTINATION . REGEX "downloaded_packages" EXCLUDE REGEX "runtime" EXCLUDE + REGEX ".*$\.sh" EXCLUDE + ) + + # For Mac/Linux shell scripts need to be installed as PROGRAMS to have execute permission + file(GLOB python_scripts "${LY_ROOT_FOLDER}/python/*.sh") + install(PROGRAMS + ${python_scripts} + DESTINATION ./python ) # Registry diff --git a/cmake/Platform/Mac/Configurations_mac.cmake b/cmake/Platform/Mac/Configurations_mac.cmake index a3551be3b9..bdb358e9d2 100644 --- a/cmake/Platform/Mac/Configurations_mac.cmake +++ b/cmake/Platform/Mac/Configurations_mac.cmake @@ -28,7 +28,9 @@ else() endif() # Signing -ly_set(CMAKE_XCODE_ATTRIBUTE_OTHER_CODE_SIGN_FLAGS --deep) +# The "-o linker-signed" flag is required as a work-around for the following CMake issue: +# https://gitlab.kitware.com/cmake/cmake/-/issues/21854 +ly_set(CMAKE_XCODE_ATTRIBUTE_OTHER_CODE_SIGN_FLAGS "--deep -o linker-signed") # Generate scheme files for Xcode ly_set(CMAKE_XCODE_GENERATE_SCHEME TRUE) diff --git a/cmake/Platform/Mac/Install_mac.cmake b/cmake/Platform/Mac/Install_mac.cmake index 74ebd293ae..40f09a16b0 100644 --- a/cmake/Platform/Mac/Install_mac.cmake +++ b/cmake/Platform/Mac/Install_mac.cmake @@ -5,7 +5,47 @@ # # -# Empty implementations for untested platforms to fix build errors. +#! ly_install_target_override: Mac specific target installation +function(ly_install_target_override) -function(ly_setup_o3de_install) -endfunction() \ No newline at end of file + set(options) + set(oneValueArgs TARGET ARCHIVE_DIR LIBRARY_DIR RUNTIME_DIR LIBRARY_SUBDIR RUNTIME_SUBDIR) + set(multiValueArgs) + cmake_parse_arguments(ly_platform_install_target "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + get_property(install_component TARGET ${ly_platform_install_target_TARGET} PROPERTY INSTALL_COMPONENT) + + # For bundles on Mac, we set the icons by passing in a path to the Images.xcassets directory. + # However, the CMake install command expects paths to files for the the RESOURCE property. + # More details can be found in the CMake issue: https://gitlab.kitware.com/cmake/cmake/-/issues/22409 + get_target_property(is_bundle ${ly_platform_install_target_TARGET} MACOSX_BUNDLE) + if (${is_bundle}) + get_target_property(cached_resources_dir ${ly_platform_install_target_TARGET} RESOURCE) + 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}/${PAL_PLATFORM_NAME}/$ + COMPONENT ${install_component} + LIBRARY + DESTINATION ${ly_platform_install_target_LIBRARY_DIR}/${PAL_PLATFORM_NAME}/$/${ly_platform_install_target_LIBRARY_SUBDIR} + COMPONENT ${install_component} + RUNTIME + DESTINATION ${ly_platform_install_target_RUNTIME_DIR}/${PAL_PLATFORM_NAME}/$/${ly_platform_install_target_RUNTIME_SUBDIR} + COMPONENT ${install_component} + BUNDLE + DESTINATION ${ly_platform_install_target_RUNTIME_DIR}/${PAL_PLATFORM_NAME}/$/${ly_platform_install_target_RUNTIME_SUBDIR} + COMPONENT ${install_component} + RESOURCE + DESTINATION ${ly_platform_install_target_RUNTIME_DIR}/${PAL_PLATFORM_NAME}/$/${ly_platform_install_target_RUNTIME_SUBDIR}/ + COMPONENT ${install_component} + ) + + if (${is_bundle}) + set_property(TARGET ${ly_platform_install_target_TARGET} PROPERTY RESOURCE ${cached_resources_dir}) + endif() +endfunction() + +include(cmake/Platform/Common/Install_common.cmake) From 104e0f73bf66345df328771d94c351353e1066ff Mon Sep 17 00:00:00 2001 From: lsemp3d <58790905+lsemp3d@users.noreply.github.com> Date: Wed, 14 Jul 2021 18:31:50 -0700 Subject: [PATCH 18/21] Fixed Lua class enumeration, there was a crash when traversing into methods Signed-off-by: lsemp3d <58790905+lsemp3d@users.noreply.github.com> --- .../AzCore/Script/ScriptContextDebug.cpp | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/Code/Framework/AzCore/AzCore/Script/ScriptContextDebug.cpp b/Code/Framework/AzCore/AzCore/Script/ScriptContextDebug.cpp index b68b19ddda..0444c4ce2a 100644 --- a/Code/Framework/AzCore/AzCore/Script/ScriptContextDebug.cpp +++ b/Code/Framework/AzCore/AzCore/Script/ScriptContextDebug.cpp @@ -157,8 +157,11 @@ ScriptContextDebug::EnumRegisteredClasses(EnumClass enumClass, EnumMethod enumMe lua_pop(l, 2); // pop the Class name and behaviorClass lua_pushnil(l); + + // iterate over the key/value pairs while (lua_next(l, -2) != 0) { + // if key: string value: function if (lua_isstring(l, -2) && lua_isfunction(l, -1)) { const char* name = lua_tostring(l, -2); @@ -167,6 +170,7 @@ ScriptContextDebug::EnumRegisteredClasses(EnumClass enumClass, EnumMethod enumMe bool isRead = true; bool isWrite = true; + // check if there is a getter provided lua_getupvalue(l, -1, 1); if (lua_isnil(l, -1)) { @@ -174,6 +178,7 @@ ScriptContextDebug::EnumRegisteredClasses(EnumClass enumClass, EnumMethod enumMe } lua_pop(l, 1); + // check if there is a setter provided lua_getupvalue(l, -1, 2); if (lua_isnil(l, -1)) { @@ -181,6 +186,7 @@ ScriptContextDebug::EnumRegisteredClasses(EnumClass enumClass, EnumMethod enumMe } lua_pop(l, 1); + // enumerate the remaining property if (!enumProperty(&behaviorClass->m_typeId, name, isRead, isWrite, userData)) { lua_pop(l, 5); @@ -189,21 +195,30 @@ ScriptContextDebug::EnumRegisteredClasses(EnumClass enumClass, EnumMethod enumMe } else { + // for any non-built in methods if (strncmp(name, "__", 2) != 0) { const char* dbgParamInfo = NULL; - lua_getupvalue(l, -1, 2); + + // attempt to get the name + bool popDebugName = lua_getupvalue(l, -1, 2) != nullptr; if (lua_isstring(l, -1)) { dbgParamInfo = lua_tostring(l, -1); } + // enumerate the method's parameters if (!enumMethod(&behaviorClass->m_typeId, name, dbgParamInfo, userData)) { lua_pop(l, 6); return; } - lua_pop(l, 1); // pop the DBG name + + // if we were able to get the name, pop it from the stack + if (popDebugName) + { + lua_pop(l, 1); + } } } } From a077a88d3f7b066fd26446b7e6e821f66dcc9b9a Mon Sep 17 00:00:00 2001 From: Chris Burel Date: Wed, 14 Jul 2021 23:44:40 -0700 Subject: [PATCH 19/21] [MeshOptimizer] Determine the original vertex index based on the position (#2008) * Determine the original vertex index based on the position The Assimp library does not expose the FBX control point indices. This change causes vertices that are close enough in their position to be considered as coming from the same control point. This allows the mesh optimizer to consider vertices with the same control point index (or "original vertex index" as it is called in the code) for deduplication. Signed-off-by: Chris Burel * Use a filter view instead of reimplementing a filter view Signed-off-by: Chris Burel * Don't attempt to weld similar vertices if there's blendshapes Signed-off-by: Chris Burel * Add test for the mesh optimizer's ability to weld nearby vertices Signed-off-by: Chris Burel * Add logging call to show mesh optimizer effect on vertex count Signed-off-by: Chris Burel * Use a bunch of temporaries in order to make `position` `const` Signed-off-by: Chris Burel * Supply the vertex index remapping to the optimized skin weights This ensures that the optimized skin weights use the vertex indexes from the optimized mesh Signed-off-by: Chris Burel --- .../SceneAPI/SceneData/Groups/MeshGroup.h | 24 +- Gems/SceneProcessing/Code/CMakeLists.txt | 1 + .../MeshOptimizer/MeshOptimizerComponent.cpp | 127 ++++++++-- .../MeshOptimizerComponentTests.cpp | 221 ++++++++++++++++++ .../sceneprocessing_editor_tests_files.cmake | 1 + 5 files changed, 340 insertions(+), 34 deletions(-) create mode 100644 Gems/SceneProcessing/Code/Tests/MeshBuilder/MeshOptimizerComponentTests.cpp diff --git a/Code/Tools/SceneAPI/SceneData/Groups/MeshGroup.h b/Code/Tools/SceneAPI/SceneData/Groups/MeshGroup.h index c06c91a808..5b4c4612ad 100644 --- a/Code/Tools/SceneAPI/SceneData/Groups/MeshGroup.h +++ b/Code/Tools/SceneAPI/SceneData/Groups/MeshGroup.h @@ -28,27 +28,27 @@ namespace AZ } namespace SceneData { - class MeshGroup + class SCENE_DATA_CLASS MeshGroup : public DataTypes::IMeshGroup { public: AZ_RTTI(MeshGroup, "{07B356B7-3635-40B5-878A-FAC4EFD5AD86}", DataTypes::IMeshGroup); AZ_CLASS_ALLOCATOR(MeshGroup, SystemAllocator, 0) - MeshGroup(); - ~MeshGroup() override = default; + SCENE_DATA_API MeshGroup(); + SCENE_DATA_API ~MeshGroup() override = default; - const AZStd::string& GetName() const override; - void SetName(const AZStd::string& name); - void SetName(AZStd::string&& name) override; - const Uuid& GetId() const override; - void OverrideId(const Uuid& id) override; + SCENE_DATA_API const AZStd::string& GetName() const override; + SCENE_DATA_API void SetName(const AZStd::string& name); + SCENE_DATA_API void SetName(AZStd::string&& name) override; + SCENE_DATA_API const Uuid& GetId() const override; + SCENE_DATA_API void OverrideId(const Uuid& id) override; - Containers::RuleContainer& GetRuleContainer() override; - const Containers::RuleContainer& GetRuleContainerConst() const override; + SCENE_DATA_API Containers::RuleContainer& GetRuleContainer() override; + SCENE_DATA_API const Containers::RuleContainer& GetRuleContainerConst() const override; - DataTypes::ISceneNodeSelectionList& GetSceneNodeSelectionList() override; - const DataTypes::ISceneNodeSelectionList& GetSceneNodeSelectionList() const override; + SCENE_DATA_API DataTypes::ISceneNodeSelectionList& GetSceneNodeSelectionList() override; + SCENE_DATA_API const DataTypes::ISceneNodeSelectionList& GetSceneNodeSelectionList() const override; static void Reflect(AZ::ReflectContext* context); static bool VersionConverter(SerializeContext& context, SerializeContext::DataElementNode& classElement); diff --git a/Gems/SceneProcessing/Code/CMakeLists.txt b/Gems/SceneProcessing/Code/CMakeLists.txt index b33e157e51..57fc458d70 100644 --- a/Gems/SceneProcessing/Code/CMakeLists.txt +++ b/Gems/SceneProcessing/Code/CMakeLists.txt @@ -112,6 +112,7 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED) PRIVATE Gem::SceneProcessing.Editor.Static AZ::AzTest + AZ::SceneData ) ly_add_googletest( NAME Gem::SceneProcessing.Editor.Tests diff --git a/Gems/SceneProcessing/Code/Source/Generation/Components/MeshOptimizer/MeshOptimizerComponent.cpp b/Gems/SceneProcessing/Code/Source/Generation/Components/MeshOptimizer/MeshOptimizerComponent.cpp index e3eb617601..634dafa99c 100644 --- a/Gems/SceneProcessing/Code/Source/Generation/Components/MeshOptimizer/MeshOptimizerComponent.cpp +++ b/Gems/SceneProcessing/Code/Source/Generation/Components/MeshOptimizer/MeshOptimizerComponent.cpp @@ -96,6 +96,85 @@ namespace AZ::SceneGenerationComponents namespace Containers = AZ::SceneAPI::Containers; namespace Views = Containers::Views; + // @brief A class to map from a mesh's vertex index to it's welded vertex index + // + // When the mesh optimizer runs, it welds nearby vertices (if there are no blendshapes). This class provides a + // constant time lookup to map from an unwelded vertex index to the welded one. + // The welding works by rounding the vertex's position to the given position tolerance, then uses that rounded + // Vector3 as a key into a unordered_map. + template + class Vector3Map + : private AZStd::unordered_map + { + public: + Vector3Map(const MeshDataType* meshData, bool hasBlendShapes, float positionTolerance) + : m_meshData(meshData) + , m_hasBlendShapes(hasBlendShapes) + , m_positionTolerance(positionTolerance) + , m_positionToleranceReciprocal(1.0f / positionTolerance) + { + } + + using AZStd::unordered_map::reserve; + + AZ::u32 operator[](const AZ::u32 vertexIndex) + { + if (m_hasBlendShapes) + { + // Don't attempt to weld similar vertices if there's blendshapes + // Welding the vertices here based on position could cause the vertices of a base shape to be welded, + // and the vertices of the blendshape to not be welded, resulting in a vertex count mismatch between + // the two + return m_meshData->GetUsedPointIndexForControlPoint(m_meshData->GetControlPointIndex(vertexIndex)); + } + + const auto& [iter, didInsert] = try_emplace(GetPositionForIndex(vertexIndex), m_currentOriginalVertexIndex); + if (didInsert) + { + ++m_currentOriginalVertexIndex; + } + return iter->second; + } + + [[nodiscard]] AZ::u32 at(const AZ::u32 vertexIndex) const + { + if (m_hasBlendShapes) + { + // Don't attempt to weld similar vertices if there's blendshapes + // Welding the vertices here based on position could cause the vertices of a base shape to be welded, + // and the vertices of the blendshape to not be welded, resulting in a vertex count mismatch between + // the two + return m_meshData->GetUsedPointIndexForControlPoint(m_meshData->GetControlPointIndex(vertexIndex)); + } + + auto iter = find(GetPositionForIndex(vertexIndex)); + AZSTD_CONTAINER_ASSERT(iter != end(), "Element with key is not present"); + return iter->second; + } + + private: + + AZ::Vector3 GetPositionForIndex(const AZ::u32 vertexIndex) const + { + // Round the vertex position so that a float comparison can be made with entires in the map + // pos = floor( x * 10 + 0.5) * 0.1 + return AZ::Vector3( + AZ::Simd::Vec3::Floor( + (m_meshData->GetPosition(vertexIndex) * m_positionToleranceReciprocal + AZ::Vector3(0.5f)).GetSimdValue() + ) + ) * m_positionTolerance; + } + + const MeshDataType* m_meshData; + bool m_hasBlendShapes; + float m_positionTolerance; + float m_positionToleranceReciprocal; + AZ::u32 m_currentOriginalVertexIndex = 0; + }; + + template + Vector3Map(const MeshDataType*) -> Vector3Map; + MeshOptimizerComponent::MeshOptimizerComponent() { BindToCall(&MeshOptimizerComponent::OptimizeMeshes); @@ -106,7 +185,7 @@ namespace AZ::SceneGenerationComponents auto* serializeContext = azrtti_cast(context); if (serializeContext) { - serializeContext->Class()->Version(2); + serializeContext->Class()->Version(4); } } @@ -115,7 +194,8 @@ namespace AZ::SceneGenerationComponents const MeshDataType* meshData, const SkinWeightDataView& skinWeights, AZ::u32 maxWeightsPerVertex, - float weightThreshold) + float weightThreshold, + const Vector3Map& positionMap) { if (skinWeights.empty()) { @@ -141,15 +221,12 @@ namespace AZ::SceneGenerationComponents for (size_t linkIndex = 0; linkIndex < linkCount; ++linkIndex) { const ISkinWeightData::Link& link = skinData.get().GetLink(controlPointIndex, linkIndex); - skinningInfo->AddInfluence(usedPointIndex, {aznumeric_caster(link.boneId), link.weight}); + skinningInfo->AddInfluence(positionMap.at(usedPointIndex), {aznumeric_caster(link.boneId), link.weight}); } } } - if (skinningInfo) - { - skinningInfo->Optimize(maxWeightsPerVertex, weightThreshold); - } + skinningInfo->Optimize(maxWeightsPerVertex, weightThreshold); return skinningInfo; } @@ -192,17 +269,12 @@ namespace AZ::SceneGenerationComponents const AZStd::vector> meshes = [](const SceneGraph& graph) { AZStd::vector> meshes; - for (auto it = graph.GetContentStorage().cbegin(); it != graph.GetContentStorage().cend(); ++it) + const auto meshNodes = Containers::MakeDerivedFilterView(graph.GetContentStorage()); + for (auto it = meshNodes.cbegin(); it != meshNodes.cend(); ++it) { - // Skip anything that isn't a mesh. - const auto* mesh = azdynamic_cast(it->get()); - if (!mesh) - { - continue; - } - // Get the mesh data and node index and store them in the vector as a pair, so we can iterate over them later. - meshes.emplace_back(mesh, graph.ConvertToNodeIndex(it)); + // The sequential calls to GetBaseIterator unwrap the layers of FilterIterators from the MakeDerivedFilterView + meshes.emplace_back(&(*it), graph.ConvertToNodeIndex(it.GetBaseIterator().GetBaseIterator().GetBaseIterator())); } return meshes; }(graph); @@ -287,6 +359,12 @@ namespace AZ::SceneGenerationComponents auto [optimizedMesh, optimizedUVs, optimizedTangents, optimizedBitangents, optimizedVertexColors, optimizedSkinWeights] = OptimizeMesh(mesh, mesh, uvDatas, tangentDatas, bitangentDatas, colorDatas, skinWeightDatas, meshGroup, hasBlendShapes); + AZ_TracePrintf(AZ::SceneAPI::Utilities::LogWindow, "Base mesh: %zu vertices, optimized mesh: %zu vertices, %0.02f%% of the original", + mesh->GetUsedControlPointCount(), + optimizedMesh->GetUsedControlPointCount(), + ((float)optimizedMesh->GetUsedControlPointCount() / (float)mesh->GetUsedControlPointCount()) * 100.0f + ); + const NodeIndex optimizedMeshNodeIndex = graph.AddChild(graph.GetNodeParent(nodeIndex), name.c_str(), AZStd::move(optimizedMesh)); auto addOptimizedNodes = [&graph, &optimizedMeshNodeIndex](const auto& originalNodeIndexes, auto& optimizedNodes) @@ -428,10 +506,9 @@ namespace AZ::SceneGenerationComponents const AZStd::vector bitangentLayers = makeLayersForData(bitangents); const AZStd::vector vertexColorLayers = makeLayersForData(vertexColors); - const auto* skinRule = meshGroup.GetRuleContainerConst().FindFirstByType().get(); - const AZ::u32 maxWeightsPerVertex = skinRule ? skinRule->GetMaxWeightsPerVertex() : 4; - const float weightThreshold = skinRule ? skinRule->GetWeightThreshold() : 0.001f; - meshBuilder.SetSkinningInfo(ExtractSkinningInfo(meshData, skinWeights, maxWeightsPerVertex, weightThreshold)); + constexpr float positionTolerance = 0.0001f; + Vector3Map positionMap(meshData, hasBlendShapes, positionTolerance); + positionMap.reserve(vertexCount); // Add the vertex data to all the layers const AZ::u32 faceCount = meshData->GetFaceCount(); @@ -440,8 +517,8 @@ namespace AZ::SceneGenerationComponents meshBuilder.BeginPolygon(baseMesh->GetFaceMaterialId(faceIndex)); for (const AZ::u32 vertexIndex : meshData->GetFaceInfo(faceIndex).vertexIndex) { - const int orgVertexNumber = meshData->GetUsedPointIndexForControlPoint(meshData->GetControlPointIndex(vertexIndex)); - AZ_Assert(orgVertexNumber >= 0, "Invalid vertex number"); + const AZ::u32 orgVertexNumber = positionMap[vertexIndex]; + orgVtxLayer->SetCurrentVertexValue(orgVertexNumber); posLayer->SetCurrentVertexValue(meshData->GetPosition(vertexIndex)); @@ -471,6 +548,12 @@ namespace AZ::SceneGenerationComponents meshBuilder.EndPolygon(); } + + const auto* skinRule = meshGroup.GetRuleContainerConst().FindFirstByType().get(); + const AZ::u32 maxWeightsPerVertex = skinRule ? skinRule->GetMaxWeightsPerVertex() : 4; + const float weightThreshold = skinRule ? skinRule->GetWeightThreshold() : 0.001f; + meshBuilder.SetSkinningInfo(ExtractSkinningInfo(meshData, skinWeights, maxWeightsPerVertex, weightThreshold, positionMap)); + meshBuilder.GenerateSubMeshVertexOrders(); // Create the resulting nodes diff --git a/Gems/SceneProcessing/Code/Tests/MeshBuilder/MeshOptimizerComponentTests.cpp b/Gems/SceneProcessing/Code/Tests/MeshBuilder/MeshOptimizerComponentTests.cpp new file mode 100644 index 0000000000..81528fc3e4 --- /dev/null +++ b/Gems/SceneProcessing/Code/Tests/MeshBuilder/MeshOptimizerComponentTests.cpp @@ -0,0 +1,221 @@ +/* + * 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 +#include +#include +#include +#include + +#include + +namespace AZ::SceneAPI::DataTypes +{ + void PrintTo(const ISkinWeightData::Link& link, ::std::ostream* os) + { + *os << '{' << link.boneId << ", " << link.weight << '}'; + } +} + +namespace SceneProcessing +{ + class VertexDeduplicationFixture + : public SceneProcessing::InitSceneAPIFixture + { + public: + void SetUp() override + { + SceneProcessing::InitSceneAPIFixture::SetUp(); + + m_systemEntity = m_app.Create({}, {}); + m_systemEntity->AddComponent(aznew AZ::MemoryComponent()); + m_systemEntity->AddComponent(aznew AZ::JobManagerComponent()); + m_systemEntity->Init(); + m_systemEntity->Activate(); + } + void TearDown() override + { + m_systemEntity->Deactivate(); + SceneProcessing::InitSceneAPIFixture::TearDown(); + } + + static AZStd::unique_ptr MakePlaneMesh() + { + // Create a simple plane with 2 triangles, 6 total vertices, 2 shared vertices + // 0 --- 1 + // | / | + // | / | + // | / | + // 2 --- 3 + const AZStd::array planeVertexPositions = { + AZ::Vector3{0.0f, 0.0f, 0.0f}, + AZ::Vector3{0.0f, 0.0f, 1.0f}, + AZ::Vector3{1.0f, 0.0f, 1.0f}, + AZ::Vector3{1.0f, 0.0f, 1.0f}, + AZ::Vector3{1.0f, 0.0f, 0.0f}, + AZ::Vector3{0.0f, 0.0f, 0.0f}, + }; + + auto mesh = AZStd::make_unique(); + + int i = 0; + for (const AZ::Vector3& position : planeVertexPositions) + { + mesh->AddPosition(position); + mesh->AddNormal(AZ::Vector3::CreateAxisY()); + + // This assumes that the data coming from the import process gives a unique control point + // index to every vertex. This follows the behavior of the AssImp library. + mesh->SetVertexIndexToControlPointIndexMap(i, i); + ++i; + } + + mesh->AddFace({0, 1, 2}, 0); + mesh->AddFace({3, 4, 5}, 0); + + return mesh; + } + + static AZStd::unique_ptr MakeSkinData() + { + auto skinWeights = AZStd::make_unique(); + + skinWeights->ResizeContainerSpace(6); + + // Add bones 0 and 1 to the skin weights + skinWeights->GetBoneId("0"); + skinWeights->GetBoneId("1"); + + skinWeights->AppendLink(0, {/*.boneId=*/0, /*.weight=*/1}); + skinWeights->AppendLink(1, {/*.boneId=*/0, /*.weight=*/1}); + skinWeights->AppendLink(2, {/*.boneId=*/0, /*.weight=*/1}); + skinWeights->AppendLink(3, {/*.boneId=*/1, /*.weight=*/1}); + skinWeights->AppendLink(4, {/*.boneId=*/1, /*.weight=*/1}); + skinWeights->AppendLink(5, {/*.boneId=*/1, /*.weight=*/1}); + + return skinWeights; + } + + private: + AZ::ComponentApplication m_app; + AZ::Entity* m_systemEntity; + }; + + TEST_F(VertexDeduplicationFixture, CanDeduplicateVertices) + { + AZ::SceneAPI::Containers::Scene scene("testScene"); + AZ::SceneAPI::Containers::SceneGraph& graph = scene.GetGraph(); + + const auto meshNodeIndex = graph.AddChild(graph.GetRoot(), "testMesh", MakePlaneMesh()); + + // The original source mesh should have 6 vertices + EXPECT_EQ(AZStd::rtti_pointer_cast(graph.GetNodeContent(meshNodeIndex))->GetVertexCount(), 6); + + auto meshGroup = AZStd::make_unique(); + meshGroup->GetSceneNodeSelectionList().AddSelectedNode("testMesh"); + scene.GetManifest().AddEntry(AZStd::move(meshGroup)); + + AZ::SceneGenerationComponents::MeshOptimizerComponent component; + AZ::SceneAPI::Events::GenerateSimplificationEventContext context(scene, "pc"); + component.OptimizeMeshes(context); + + AZ::SceneAPI::Containers::SceneGraph::NodeIndex optimizedNodeIndex = graph.Find(AZStd::string("testMesh").append(AZ::SceneAPI::Utilities::OptimizedMeshSuffix)); + ASSERT_TRUE(optimizedNodeIndex.IsValid()) << "Mesh optimizer did not add an optimized version of the mesh"; + + const auto& optimizedMesh = AZStd::rtti_pointer_cast(graph.GetNodeContent(optimizedNodeIndex)); + ASSERT_TRUE(optimizedMesh); + + // The optimized mesh should have 4 vertices, the 2 shared vertices are welded together + EXPECT_EQ(optimizedMesh->GetVertexCount(), 4); + } + + MATCHER(VectorOfLinksEq, "") + { + return testing::ExplainMatchResult( + testing::AllOf( + testing::Field(&AZ::SceneData::GraphData::SkinWeightData::Link::boneId, testing::Eq(testing::get<0>(arg).boneId)), + testing::Field(&AZ::SceneData::GraphData::SkinWeightData::Link::weight, testing::FloatEq(testing::get<0>(arg).weight)) + ), + testing::get<1>(arg), + result_listener + ); + } + + MATCHER(VectorOfVectorOfLinksEq, "") + { + return testing::ExplainMatchResult( + testing::UnorderedPointwise(VectorOfLinksEq(), testing::get<0>(arg)), + testing::get<1>(arg), + result_listener + ); + } + + TEST_F(VertexDeduplicationFixture, DeduplicatedVerticesRemapSkinning) + { + AZ::SceneAPI::Containers::Scene scene("testScene"); + AZ::SceneAPI::Containers::SceneGraph& graph = scene.GetGraph(); + + const auto meshNodeIndex = graph.AddChild(graph.GetRoot(), "testMesh", MakePlaneMesh()); + const auto skinDataNodeIndex = graph.AddChild(meshNodeIndex, "skinData", MakeSkinData()); + graph.MakeEndPoint(skinDataNodeIndex); + + // The original source mesh should have 6 vertices + EXPECT_EQ(AZStd::rtti_pointer_cast(graph.GetNodeContent(meshNodeIndex))->GetVertexCount(), 6); + + auto meshGroup = AZStd::make_unique(); + meshGroup->GetSceneNodeSelectionList().AddSelectedNode("testMesh"); + scene.GetManifest().AddEntry(AZStd::move(meshGroup)); + + AZ::SceneGenerationComponents::MeshOptimizerComponent component; + AZ::SceneAPI::Events::GenerateSimplificationEventContext context(scene, "pc"); + component.OptimizeMeshes(context); + + AZ::SceneAPI::Containers::SceneGraph::NodeIndex optimizedNodeIndex = graph.Find(AZStd::string("testMesh").append(AZ::SceneAPI::Utilities::OptimizedMeshSuffix)); + ASSERT_TRUE(optimizedNodeIndex.IsValid()) << "Mesh optimizer did not add an optimized version of the mesh"; + + const auto& optimizedMesh = AZStd::rtti_pointer_cast(graph.GetNodeContent(optimizedNodeIndex)); + ASSERT_TRUE(optimizedMesh); + + AZ::SceneAPI::Containers::SceneGraph::NodeIndex optimizedSkinDataNodeIndex = graph.Find(AZStd::string("testMesh").append(AZ::SceneAPI::Utilities::OptimizedMeshSuffix).append(".skinWeights")); + ASSERT_TRUE(optimizedSkinDataNodeIndex.IsValid()) << "Mesh optimizer did not add an optimized version of the skin data"; + + const auto& optimizedSkinWeights = AZStd::rtti_pointer_cast(graph.GetNodeContent(optimizedSkinDataNodeIndex)); + ASSERT_TRUE(optimizedSkinWeights); + + const AZStd::vector> expectedLinks + { + /*0*/ { {0, 0.5f}, {1, 0.5f} }, + /*1*/ { {0, 1.0f} }, + /*2*/ { {0, 0.5f}, {1, 0.5f} }, + /*3*/ { {1, 1.0f} }, + }; + + AZStd::vector> gotLinks(optimizedMesh->GetVertexCount()); + for (unsigned int vertexIndex = 0; vertexIndex < optimizedMesh->GetVertexCount(); ++vertexIndex) + { + for (size_t linkIndex = 0; linkIndex < optimizedSkinWeights->GetLinkCount(vertexIndex); ++linkIndex) + { + gotLinks[vertexIndex].emplace_back(optimizedSkinWeights->GetLink(vertexIndex, linkIndex)); + } + } + EXPECT_THAT(gotLinks, testing::Pointwise(VectorOfVectorOfLinksEq(), expectedLinks)); + } +} // namespace SceneProcessing diff --git a/Gems/SceneProcessing/Code/sceneprocessing_editor_tests_files.cmake b/Gems/SceneProcessing/Code/sceneprocessing_editor_tests_files.cmake index 9ebe2291a3..c06d21233d 100644 --- a/Gems/SceneProcessing/Code/sceneprocessing_editor_tests_files.cmake +++ b/Gems/SceneProcessing/Code/sceneprocessing_editor_tests_files.cmake @@ -7,6 +7,7 @@ set(FILES Tests/InitSceneAPIFixture.h + Tests/MeshBuilder/MeshOptimizerComponentTests.cpp Tests/MeshBuilder/MeshBuilderTests.cpp Tests/MeshBuilder/MeshVerticesTests.cpp Tests/MeshBuilder/SkinInfluencesTests.cpp From 0863a2449086d9ee4a5d7e2ad40dfc2ad348d603 Mon Sep 17 00:00:00 2001 From: Benjamin Jillich <43751992+amzn-jillich@users.noreply.github.com> Date: Thu, 15 Jul 2021 03:18:10 -0700 Subject: [PATCH 20/21] Revert "[MeshOptimizer] Determine the original vertex index based on the position (#2008)" (#2188) This reverts commit a077a88d3f7b066fd26446b7e6e821f66dcc9b9a due to assets failing on Jenkins. Signed-off-by: Benjamin Jillich --- .../SceneAPI/SceneData/Groups/MeshGroup.h | 24 +- Gems/SceneProcessing/Code/CMakeLists.txt | 1 - .../MeshOptimizer/MeshOptimizerComponent.cpp | 127 ++-------- .../MeshOptimizerComponentTests.cpp | 221 ------------------ .../sceneprocessing_editor_tests_files.cmake | 1 - 5 files changed, 34 insertions(+), 340 deletions(-) delete mode 100644 Gems/SceneProcessing/Code/Tests/MeshBuilder/MeshOptimizerComponentTests.cpp diff --git a/Code/Tools/SceneAPI/SceneData/Groups/MeshGroup.h b/Code/Tools/SceneAPI/SceneData/Groups/MeshGroup.h index 5b4c4612ad..c06c91a808 100644 --- a/Code/Tools/SceneAPI/SceneData/Groups/MeshGroup.h +++ b/Code/Tools/SceneAPI/SceneData/Groups/MeshGroup.h @@ -28,27 +28,27 @@ namespace AZ } namespace SceneData { - class SCENE_DATA_CLASS MeshGroup + class MeshGroup : public DataTypes::IMeshGroup { public: AZ_RTTI(MeshGroup, "{07B356B7-3635-40B5-878A-FAC4EFD5AD86}", DataTypes::IMeshGroup); AZ_CLASS_ALLOCATOR(MeshGroup, SystemAllocator, 0) - SCENE_DATA_API MeshGroup(); - SCENE_DATA_API ~MeshGroup() override = default; + MeshGroup(); + ~MeshGroup() override = default; - SCENE_DATA_API const AZStd::string& GetName() const override; - SCENE_DATA_API void SetName(const AZStd::string& name); - SCENE_DATA_API void SetName(AZStd::string&& name) override; - SCENE_DATA_API const Uuid& GetId() const override; - SCENE_DATA_API void OverrideId(const Uuid& id) override; + const AZStd::string& GetName() const override; + void SetName(const AZStd::string& name); + void SetName(AZStd::string&& name) override; + const Uuid& GetId() const override; + void OverrideId(const Uuid& id) override; - SCENE_DATA_API Containers::RuleContainer& GetRuleContainer() override; - SCENE_DATA_API const Containers::RuleContainer& GetRuleContainerConst() const override; + Containers::RuleContainer& GetRuleContainer() override; + const Containers::RuleContainer& GetRuleContainerConst() const override; - SCENE_DATA_API DataTypes::ISceneNodeSelectionList& GetSceneNodeSelectionList() override; - SCENE_DATA_API const DataTypes::ISceneNodeSelectionList& GetSceneNodeSelectionList() const override; + DataTypes::ISceneNodeSelectionList& GetSceneNodeSelectionList() override; + const DataTypes::ISceneNodeSelectionList& GetSceneNodeSelectionList() const override; static void Reflect(AZ::ReflectContext* context); static bool VersionConverter(SerializeContext& context, SerializeContext::DataElementNode& classElement); diff --git a/Gems/SceneProcessing/Code/CMakeLists.txt b/Gems/SceneProcessing/Code/CMakeLists.txt index 57fc458d70..b33e157e51 100644 --- a/Gems/SceneProcessing/Code/CMakeLists.txt +++ b/Gems/SceneProcessing/Code/CMakeLists.txt @@ -112,7 +112,6 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED) PRIVATE Gem::SceneProcessing.Editor.Static AZ::AzTest - AZ::SceneData ) ly_add_googletest( NAME Gem::SceneProcessing.Editor.Tests diff --git a/Gems/SceneProcessing/Code/Source/Generation/Components/MeshOptimizer/MeshOptimizerComponent.cpp b/Gems/SceneProcessing/Code/Source/Generation/Components/MeshOptimizer/MeshOptimizerComponent.cpp index 634dafa99c..e3eb617601 100644 --- a/Gems/SceneProcessing/Code/Source/Generation/Components/MeshOptimizer/MeshOptimizerComponent.cpp +++ b/Gems/SceneProcessing/Code/Source/Generation/Components/MeshOptimizer/MeshOptimizerComponent.cpp @@ -96,85 +96,6 @@ namespace AZ::SceneGenerationComponents namespace Containers = AZ::SceneAPI::Containers; namespace Views = Containers::Views; - // @brief A class to map from a mesh's vertex index to it's welded vertex index - // - // When the mesh optimizer runs, it welds nearby vertices (if there are no blendshapes). This class provides a - // constant time lookup to map from an unwelded vertex index to the welded one. - // The welding works by rounding the vertex's position to the given position tolerance, then uses that rounded - // Vector3 as a key into a unordered_map. - template - class Vector3Map - : private AZStd::unordered_map - { - public: - Vector3Map(const MeshDataType* meshData, bool hasBlendShapes, float positionTolerance) - : m_meshData(meshData) - , m_hasBlendShapes(hasBlendShapes) - , m_positionTolerance(positionTolerance) - , m_positionToleranceReciprocal(1.0f / positionTolerance) - { - } - - using AZStd::unordered_map::reserve; - - AZ::u32 operator[](const AZ::u32 vertexIndex) - { - if (m_hasBlendShapes) - { - // Don't attempt to weld similar vertices if there's blendshapes - // Welding the vertices here based on position could cause the vertices of a base shape to be welded, - // and the vertices of the blendshape to not be welded, resulting in a vertex count mismatch between - // the two - return m_meshData->GetUsedPointIndexForControlPoint(m_meshData->GetControlPointIndex(vertexIndex)); - } - - const auto& [iter, didInsert] = try_emplace(GetPositionForIndex(vertexIndex), m_currentOriginalVertexIndex); - if (didInsert) - { - ++m_currentOriginalVertexIndex; - } - return iter->second; - } - - [[nodiscard]] AZ::u32 at(const AZ::u32 vertexIndex) const - { - if (m_hasBlendShapes) - { - // Don't attempt to weld similar vertices if there's blendshapes - // Welding the vertices here based on position could cause the vertices of a base shape to be welded, - // and the vertices of the blendshape to not be welded, resulting in a vertex count mismatch between - // the two - return m_meshData->GetUsedPointIndexForControlPoint(m_meshData->GetControlPointIndex(vertexIndex)); - } - - auto iter = find(GetPositionForIndex(vertexIndex)); - AZSTD_CONTAINER_ASSERT(iter != end(), "Element with key is not present"); - return iter->second; - } - - private: - - AZ::Vector3 GetPositionForIndex(const AZ::u32 vertexIndex) const - { - // Round the vertex position so that a float comparison can be made with entires in the map - // pos = floor( x * 10 + 0.5) * 0.1 - return AZ::Vector3( - AZ::Simd::Vec3::Floor( - (m_meshData->GetPosition(vertexIndex) * m_positionToleranceReciprocal + AZ::Vector3(0.5f)).GetSimdValue() - ) - ) * m_positionTolerance; - } - - const MeshDataType* m_meshData; - bool m_hasBlendShapes; - float m_positionTolerance; - float m_positionToleranceReciprocal; - AZ::u32 m_currentOriginalVertexIndex = 0; - }; - - template - Vector3Map(const MeshDataType*) -> Vector3Map; - MeshOptimizerComponent::MeshOptimizerComponent() { BindToCall(&MeshOptimizerComponent::OptimizeMeshes); @@ -185,7 +106,7 @@ namespace AZ::SceneGenerationComponents auto* serializeContext = azrtti_cast(context); if (serializeContext) { - serializeContext->Class()->Version(4); + serializeContext->Class()->Version(2); } } @@ -194,8 +115,7 @@ namespace AZ::SceneGenerationComponents const MeshDataType* meshData, const SkinWeightDataView& skinWeights, AZ::u32 maxWeightsPerVertex, - float weightThreshold, - const Vector3Map& positionMap) + float weightThreshold) { if (skinWeights.empty()) { @@ -221,12 +141,15 @@ namespace AZ::SceneGenerationComponents for (size_t linkIndex = 0; linkIndex < linkCount; ++linkIndex) { const ISkinWeightData::Link& link = skinData.get().GetLink(controlPointIndex, linkIndex); - skinningInfo->AddInfluence(positionMap.at(usedPointIndex), {aznumeric_caster(link.boneId), link.weight}); + skinningInfo->AddInfluence(usedPointIndex, {aznumeric_caster(link.boneId), link.weight}); } } } - skinningInfo->Optimize(maxWeightsPerVertex, weightThreshold); + if (skinningInfo) + { + skinningInfo->Optimize(maxWeightsPerVertex, weightThreshold); + } return skinningInfo; } @@ -269,12 +192,17 @@ namespace AZ::SceneGenerationComponents const AZStd::vector> meshes = [](const SceneGraph& graph) { AZStd::vector> meshes; - const auto meshNodes = Containers::MakeDerivedFilterView(graph.GetContentStorage()); - for (auto it = meshNodes.cbegin(); it != meshNodes.cend(); ++it) + for (auto it = graph.GetContentStorage().cbegin(); it != graph.GetContentStorage().cend(); ++it) { + // Skip anything that isn't a mesh. + const auto* mesh = azdynamic_cast(it->get()); + if (!mesh) + { + continue; + } + // Get the mesh data and node index and store them in the vector as a pair, so we can iterate over them later. - // The sequential calls to GetBaseIterator unwrap the layers of FilterIterators from the MakeDerivedFilterView - meshes.emplace_back(&(*it), graph.ConvertToNodeIndex(it.GetBaseIterator().GetBaseIterator().GetBaseIterator())); + meshes.emplace_back(mesh, graph.ConvertToNodeIndex(it)); } return meshes; }(graph); @@ -359,12 +287,6 @@ namespace AZ::SceneGenerationComponents auto [optimizedMesh, optimizedUVs, optimizedTangents, optimizedBitangents, optimizedVertexColors, optimizedSkinWeights] = OptimizeMesh(mesh, mesh, uvDatas, tangentDatas, bitangentDatas, colorDatas, skinWeightDatas, meshGroup, hasBlendShapes); - AZ_TracePrintf(AZ::SceneAPI::Utilities::LogWindow, "Base mesh: %zu vertices, optimized mesh: %zu vertices, %0.02f%% of the original", - mesh->GetUsedControlPointCount(), - optimizedMesh->GetUsedControlPointCount(), - ((float)optimizedMesh->GetUsedControlPointCount() / (float)mesh->GetUsedControlPointCount()) * 100.0f - ); - const NodeIndex optimizedMeshNodeIndex = graph.AddChild(graph.GetNodeParent(nodeIndex), name.c_str(), AZStd::move(optimizedMesh)); auto addOptimizedNodes = [&graph, &optimizedMeshNodeIndex](const auto& originalNodeIndexes, auto& optimizedNodes) @@ -506,9 +428,10 @@ namespace AZ::SceneGenerationComponents const AZStd::vector bitangentLayers = makeLayersForData(bitangents); const AZStd::vector vertexColorLayers = makeLayersForData(vertexColors); - constexpr float positionTolerance = 0.0001f; - Vector3Map positionMap(meshData, hasBlendShapes, positionTolerance); - positionMap.reserve(vertexCount); + const auto* skinRule = meshGroup.GetRuleContainerConst().FindFirstByType().get(); + const AZ::u32 maxWeightsPerVertex = skinRule ? skinRule->GetMaxWeightsPerVertex() : 4; + const float weightThreshold = skinRule ? skinRule->GetWeightThreshold() : 0.001f; + meshBuilder.SetSkinningInfo(ExtractSkinningInfo(meshData, skinWeights, maxWeightsPerVertex, weightThreshold)); // Add the vertex data to all the layers const AZ::u32 faceCount = meshData->GetFaceCount(); @@ -517,8 +440,8 @@ namespace AZ::SceneGenerationComponents meshBuilder.BeginPolygon(baseMesh->GetFaceMaterialId(faceIndex)); for (const AZ::u32 vertexIndex : meshData->GetFaceInfo(faceIndex).vertexIndex) { - const AZ::u32 orgVertexNumber = positionMap[vertexIndex]; - + const int orgVertexNumber = meshData->GetUsedPointIndexForControlPoint(meshData->GetControlPointIndex(vertexIndex)); + AZ_Assert(orgVertexNumber >= 0, "Invalid vertex number"); orgVtxLayer->SetCurrentVertexValue(orgVertexNumber); posLayer->SetCurrentVertexValue(meshData->GetPosition(vertexIndex)); @@ -548,12 +471,6 @@ namespace AZ::SceneGenerationComponents meshBuilder.EndPolygon(); } - - const auto* skinRule = meshGroup.GetRuleContainerConst().FindFirstByType().get(); - const AZ::u32 maxWeightsPerVertex = skinRule ? skinRule->GetMaxWeightsPerVertex() : 4; - const float weightThreshold = skinRule ? skinRule->GetWeightThreshold() : 0.001f; - meshBuilder.SetSkinningInfo(ExtractSkinningInfo(meshData, skinWeights, maxWeightsPerVertex, weightThreshold, positionMap)); - meshBuilder.GenerateSubMeshVertexOrders(); // Create the resulting nodes diff --git a/Gems/SceneProcessing/Code/Tests/MeshBuilder/MeshOptimizerComponentTests.cpp b/Gems/SceneProcessing/Code/Tests/MeshBuilder/MeshOptimizerComponentTests.cpp deleted file mode 100644 index 81528fc3e4..0000000000 --- a/Gems/SceneProcessing/Code/Tests/MeshBuilder/MeshOptimizerComponentTests.cpp +++ /dev/null @@ -1,221 +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 - * - */ - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -namespace AZ::SceneAPI::DataTypes -{ - void PrintTo(const ISkinWeightData::Link& link, ::std::ostream* os) - { - *os << '{' << link.boneId << ", " << link.weight << '}'; - } -} - -namespace SceneProcessing -{ - class VertexDeduplicationFixture - : public SceneProcessing::InitSceneAPIFixture - { - public: - void SetUp() override - { - SceneProcessing::InitSceneAPIFixture::SetUp(); - - m_systemEntity = m_app.Create({}, {}); - m_systemEntity->AddComponent(aznew AZ::MemoryComponent()); - m_systemEntity->AddComponent(aznew AZ::JobManagerComponent()); - m_systemEntity->Init(); - m_systemEntity->Activate(); - } - void TearDown() override - { - m_systemEntity->Deactivate(); - SceneProcessing::InitSceneAPIFixture::TearDown(); - } - - static AZStd::unique_ptr MakePlaneMesh() - { - // Create a simple plane with 2 triangles, 6 total vertices, 2 shared vertices - // 0 --- 1 - // | / | - // | / | - // | / | - // 2 --- 3 - const AZStd::array planeVertexPositions = { - AZ::Vector3{0.0f, 0.0f, 0.0f}, - AZ::Vector3{0.0f, 0.0f, 1.0f}, - AZ::Vector3{1.0f, 0.0f, 1.0f}, - AZ::Vector3{1.0f, 0.0f, 1.0f}, - AZ::Vector3{1.0f, 0.0f, 0.0f}, - AZ::Vector3{0.0f, 0.0f, 0.0f}, - }; - - auto mesh = AZStd::make_unique(); - - int i = 0; - for (const AZ::Vector3& position : planeVertexPositions) - { - mesh->AddPosition(position); - mesh->AddNormal(AZ::Vector3::CreateAxisY()); - - // This assumes that the data coming from the import process gives a unique control point - // index to every vertex. This follows the behavior of the AssImp library. - mesh->SetVertexIndexToControlPointIndexMap(i, i); - ++i; - } - - mesh->AddFace({0, 1, 2}, 0); - mesh->AddFace({3, 4, 5}, 0); - - return mesh; - } - - static AZStd::unique_ptr MakeSkinData() - { - auto skinWeights = AZStd::make_unique(); - - skinWeights->ResizeContainerSpace(6); - - // Add bones 0 and 1 to the skin weights - skinWeights->GetBoneId("0"); - skinWeights->GetBoneId("1"); - - skinWeights->AppendLink(0, {/*.boneId=*/0, /*.weight=*/1}); - skinWeights->AppendLink(1, {/*.boneId=*/0, /*.weight=*/1}); - skinWeights->AppendLink(2, {/*.boneId=*/0, /*.weight=*/1}); - skinWeights->AppendLink(3, {/*.boneId=*/1, /*.weight=*/1}); - skinWeights->AppendLink(4, {/*.boneId=*/1, /*.weight=*/1}); - skinWeights->AppendLink(5, {/*.boneId=*/1, /*.weight=*/1}); - - return skinWeights; - } - - private: - AZ::ComponentApplication m_app; - AZ::Entity* m_systemEntity; - }; - - TEST_F(VertexDeduplicationFixture, CanDeduplicateVertices) - { - AZ::SceneAPI::Containers::Scene scene("testScene"); - AZ::SceneAPI::Containers::SceneGraph& graph = scene.GetGraph(); - - const auto meshNodeIndex = graph.AddChild(graph.GetRoot(), "testMesh", MakePlaneMesh()); - - // The original source mesh should have 6 vertices - EXPECT_EQ(AZStd::rtti_pointer_cast(graph.GetNodeContent(meshNodeIndex))->GetVertexCount(), 6); - - auto meshGroup = AZStd::make_unique(); - meshGroup->GetSceneNodeSelectionList().AddSelectedNode("testMesh"); - scene.GetManifest().AddEntry(AZStd::move(meshGroup)); - - AZ::SceneGenerationComponents::MeshOptimizerComponent component; - AZ::SceneAPI::Events::GenerateSimplificationEventContext context(scene, "pc"); - component.OptimizeMeshes(context); - - AZ::SceneAPI::Containers::SceneGraph::NodeIndex optimizedNodeIndex = graph.Find(AZStd::string("testMesh").append(AZ::SceneAPI::Utilities::OptimizedMeshSuffix)); - ASSERT_TRUE(optimizedNodeIndex.IsValid()) << "Mesh optimizer did not add an optimized version of the mesh"; - - const auto& optimizedMesh = AZStd::rtti_pointer_cast(graph.GetNodeContent(optimizedNodeIndex)); - ASSERT_TRUE(optimizedMesh); - - // The optimized mesh should have 4 vertices, the 2 shared vertices are welded together - EXPECT_EQ(optimizedMesh->GetVertexCount(), 4); - } - - MATCHER(VectorOfLinksEq, "") - { - return testing::ExplainMatchResult( - testing::AllOf( - testing::Field(&AZ::SceneData::GraphData::SkinWeightData::Link::boneId, testing::Eq(testing::get<0>(arg).boneId)), - testing::Field(&AZ::SceneData::GraphData::SkinWeightData::Link::weight, testing::FloatEq(testing::get<0>(arg).weight)) - ), - testing::get<1>(arg), - result_listener - ); - } - - MATCHER(VectorOfVectorOfLinksEq, "") - { - return testing::ExplainMatchResult( - testing::UnorderedPointwise(VectorOfLinksEq(), testing::get<0>(arg)), - testing::get<1>(arg), - result_listener - ); - } - - TEST_F(VertexDeduplicationFixture, DeduplicatedVerticesRemapSkinning) - { - AZ::SceneAPI::Containers::Scene scene("testScene"); - AZ::SceneAPI::Containers::SceneGraph& graph = scene.GetGraph(); - - const auto meshNodeIndex = graph.AddChild(graph.GetRoot(), "testMesh", MakePlaneMesh()); - const auto skinDataNodeIndex = graph.AddChild(meshNodeIndex, "skinData", MakeSkinData()); - graph.MakeEndPoint(skinDataNodeIndex); - - // The original source mesh should have 6 vertices - EXPECT_EQ(AZStd::rtti_pointer_cast(graph.GetNodeContent(meshNodeIndex))->GetVertexCount(), 6); - - auto meshGroup = AZStd::make_unique(); - meshGroup->GetSceneNodeSelectionList().AddSelectedNode("testMesh"); - scene.GetManifest().AddEntry(AZStd::move(meshGroup)); - - AZ::SceneGenerationComponents::MeshOptimizerComponent component; - AZ::SceneAPI::Events::GenerateSimplificationEventContext context(scene, "pc"); - component.OptimizeMeshes(context); - - AZ::SceneAPI::Containers::SceneGraph::NodeIndex optimizedNodeIndex = graph.Find(AZStd::string("testMesh").append(AZ::SceneAPI::Utilities::OptimizedMeshSuffix)); - ASSERT_TRUE(optimizedNodeIndex.IsValid()) << "Mesh optimizer did not add an optimized version of the mesh"; - - const auto& optimizedMesh = AZStd::rtti_pointer_cast(graph.GetNodeContent(optimizedNodeIndex)); - ASSERT_TRUE(optimizedMesh); - - AZ::SceneAPI::Containers::SceneGraph::NodeIndex optimizedSkinDataNodeIndex = graph.Find(AZStd::string("testMesh").append(AZ::SceneAPI::Utilities::OptimizedMeshSuffix).append(".skinWeights")); - ASSERT_TRUE(optimizedSkinDataNodeIndex.IsValid()) << "Mesh optimizer did not add an optimized version of the skin data"; - - const auto& optimizedSkinWeights = AZStd::rtti_pointer_cast(graph.GetNodeContent(optimizedSkinDataNodeIndex)); - ASSERT_TRUE(optimizedSkinWeights); - - const AZStd::vector> expectedLinks - { - /*0*/ { {0, 0.5f}, {1, 0.5f} }, - /*1*/ { {0, 1.0f} }, - /*2*/ { {0, 0.5f}, {1, 0.5f} }, - /*3*/ { {1, 1.0f} }, - }; - - AZStd::vector> gotLinks(optimizedMesh->GetVertexCount()); - for (unsigned int vertexIndex = 0; vertexIndex < optimizedMesh->GetVertexCount(); ++vertexIndex) - { - for (size_t linkIndex = 0; linkIndex < optimizedSkinWeights->GetLinkCount(vertexIndex); ++linkIndex) - { - gotLinks[vertexIndex].emplace_back(optimizedSkinWeights->GetLink(vertexIndex, linkIndex)); - } - } - EXPECT_THAT(gotLinks, testing::Pointwise(VectorOfVectorOfLinksEq(), expectedLinks)); - } -} // namespace SceneProcessing diff --git a/Gems/SceneProcessing/Code/sceneprocessing_editor_tests_files.cmake b/Gems/SceneProcessing/Code/sceneprocessing_editor_tests_files.cmake index c06d21233d..9ebe2291a3 100644 --- a/Gems/SceneProcessing/Code/sceneprocessing_editor_tests_files.cmake +++ b/Gems/SceneProcessing/Code/sceneprocessing_editor_tests_files.cmake @@ -7,7 +7,6 @@ set(FILES Tests/InitSceneAPIFixture.h - Tests/MeshBuilder/MeshOptimizerComponentTests.cpp Tests/MeshBuilder/MeshBuilderTests.cpp Tests/MeshBuilder/MeshVerticesTests.cpp Tests/MeshBuilder/SkinInfluencesTests.cpp From 7ef383580e486a93df633482e7ec6275c7fb7e9e Mon Sep 17 00:00:00 2001 From: moudgils <47460854+moudgils@users.noreply.github.com> Date: Thu, 15 Jul 2021 11:50:19 -0700 Subject: [PATCH 21/21] Metal sync interval support (#2187) * Metal sync interval support Add shader name to SRGPool for debugging purposes Set default value of rpi_vsync_interval to 1 Signed-off-by: moudgils --- .../Source/Platform/Mac/RHI/Metal_RHI_Mac.cpp | 33 ++++++++++++++++--- .../Source/Platform/iOS/RHI/Metal_RHI_iOS.cpp | 16 ++++++--- .../RHI/Metal/Code/Source/RHI/SwapChain.cpp | 17 +++++++--- .../RHI/Metal/Code/Source/RHI/SwapChain.h | 1 + .../Shader/ShaderResourceGroupPool.cpp | 4 +-- .../Code/Source/RPI.Public/WindowContext.cpp | 2 +- 6 files changed, 57 insertions(+), 16 deletions(-) diff --git a/Gems/Atom/RHI/Metal/Code/Source/Platform/Mac/RHI/Metal_RHI_Mac.cpp b/Gems/Atom/RHI/Metal/Code/Source/Platform/Mac/RHI/Metal_RHI_Mac.cpp index de3bc23a11..cb9e72118a 100644 --- a/Gems/Atom/RHI/Metal/Code/Source/Platform/Mac/RHI/Metal_RHI_Mac.cpp +++ b/Gems/Atom/RHI/Metal/Code/Source/Platform/Mac/RHI/Metal_RHI_Mac.cpp @@ -43,11 +43,36 @@ namespace Platform } return physicalDeviceList; } - - void PresentInternal(id mtlCommandBuffer, id drawable, float syncInterval) + + float GetRefreshRate() { - AZ_UNUSED(syncInterval); - [mtlCommandBuffer presentDrawable:drawable]; + CGDirectDisplayID display = CGMainDisplayID(); + CGDisplayModeRef currentMode = CGDisplayCopyDisplayMode(display); + return CGDisplayModeGetRefreshRate(currentMode); + } + + void PresentInternal(id mtlCommandBuffer, id drawable, float syncInterval, float refreshRate) + { + bool framePresented = false; + + //seconds per frame (1/refreshrate) * num frames (sync interval) + float presentAfterMinimumDuration = syncInterval / refreshRate; + +#if defined(__MAC_10_15_4) + if(@available(macOS 10.15.4, *)) + { + if(presentAfterMinimumDuration > 0.0f) + { + [mtlCommandBuffer presentDrawable:drawable afterMinimumDuration:presentAfterMinimumDuration]; + framePresented = true; + } + } +#endif + + if(!framePresented) + { + [mtlCommandBuffer presentDrawable:drawable]; + } } CGRect GetScreenBounds(NativeWindowType* nativeWindow) diff --git a/Gems/Atom/RHI/Metal/Code/Source/Platform/iOS/RHI/Metal_RHI_iOS.cpp b/Gems/Atom/RHI/Metal/Code/Source/Platform/iOS/RHI/Metal_RHI_iOS.cpp index b7c309fba2..007655a93e 100644 --- a/Gems/Atom/RHI/Metal/Code/Source/Platform/iOS/RHI/Metal_RHI_iOS.cpp +++ b/Gems/Atom/RHI/Metal/Code/Source/Platform/iOS/RHI/Metal_RHI_iOS.cpp @@ -29,13 +29,19 @@ namespace Platform return physicalDeviceList; } - void PresentInternal(id mtlCommandBuffer, id drawable, float syncInterval) + float GetRefreshRate() { - bool hasPresentAfterMinimumDuration = [drawable respondsToSelector:@selector(presentAfterMinimumDuration:)]; - - if (hasPresentAfterMinimumDuration && syncInterval > 0.0f) + NativeScreenType* nativeScreen = [NativeScreenType mainScreen]; + return [nativeScreen maximumFramesPerSecond]; + } + + void PresentInternal(id mtlCommandBuffer, id drawable, float syncInterval, float refreshRate) + { + //seconds per frame (1/refreshrate) * num frames (sync interval) + float presentAfterMinimumDuration = syncInterval / refreshRate; + if (hasPresentAfterMinimumDuration > 0.0f) { - [mtlCommandBuffer presentDrawable:drawable afterMinimumDuration:syncInterval]; + [mtlCommandBuffer presentDrawable:drawable afterMinimumDuration:presentAfterMinimumDuration]; } else { diff --git a/Gems/Atom/RHI/Metal/Code/Source/RHI/SwapChain.cpp b/Gems/Atom/RHI/Metal/Code/Source/RHI/SwapChain.cpp index 31d23e5100..08d5d2be87 100644 --- a/Gems/Atom/RHI/Metal/Code/Source/RHI/SwapChain.cpp +++ b/Gems/Atom/RHI/Metal/Code/Source/RHI/SwapChain.cpp @@ -19,8 +19,9 @@ namespace Platform CGFloat GetScreenScale(); void AttachViewController(NativeWindowType* nativeWindow, NativeViewControllerType* viewController, RHIMetalView* metalView); void UnAttachViewController(NativeWindowType* nativeWindow, NativeViewControllerType* viewController); - void PresentInternal(id mtlCommandBuffer, id drawable, float syncInterval); + void PresentInternal(id mtlCommandBuffer, id drawable, float syncInterval, float refreshRate); void ResizeInternal(RHIMetalView* metalView, CGSize viewSize); + float GetRefreshRate(); RHIMetalView* GetMetalView(NativeWindowType* nativeWindow); } @@ -73,6 +74,15 @@ namespace AZ AddSubView(); } + m_refreshRate = Platform::GetRefreshRate(); + + //Assume 60hz if 0 is returned. + //Internal OSX displays have 'flexible' refresh rates, with a max of 60Hz - but report 0hz + if (m_refreshRate < 0.1f) + { + m_refreshRate = 60.0f; + } + m_drawables.resize(descriptor.m_dimensions.m_imageCount); if (nativeDimensions) @@ -148,10 +158,9 @@ namespace AZ uint32_t SwapChain::PresentInternal() { const uint32_t currentImageIndex = GetCurrentImageIndex(); - //GFX TODO][ATOM-432] - Hardcoding to 30fps for now. Only used by ios. This needs to be driven by higher level code. - float syncInterval = 1.0f/30.0f; + //Preset the drawable - Platform::PresentInternal(m_mtlCommandBuffer, m_drawables[currentImageIndex], syncInterval); + Platform::PresentInternal(m_mtlCommandBuffer, m_drawables[currentImageIndex], GetDescriptor().m_verticalSyncInterval, m_refreshRate); [m_drawables[currentImageIndex] release]; m_drawables[currentImageIndex] = nil; diff --git a/Gems/Atom/RHI/Metal/Code/Source/RHI/SwapChain.h b/Gems/Atom/RHI/Metal/Code/Source/RHI/SwapChain.h index c86bd1f7a5..72014005ac 100644 --- a/Gems/Atom/RHI/Metal/Code/Source/RHI/SwapChain.h +++ b/Gems/Atom/RHI/Metal/Code/Source/RHI/SwapChain.h @@ -52,6 +52,7 @@ namespace AZ id m_mtlDevice = nil; NativeWindowType* m_nativeWindow = nullptr; AZStd::vector> m_drawables; + float m_refreshRate = 0.0f; }; } } diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/Shader/ShaderResourceGroupPool.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/Shader/ShaderResourceGroupPool.cpp index d8c69cf8a0..4e858e4880 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/Shader/ShaderResourceGroupPool.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/Shader/ShaderResourceGroupPool.cpp @@ -58,8 +58,8 @@ namespace AZ RHI::ShaderResourceGroupPoolDescriptor poolDescriptor; poolDescriptor.m_layout = shaderAsset.FindShaderResourceGroupLayout(srgName, supervariantIndex).get(); - - m_pool->SetName(srgName); + m_pool->SetName(AZ::Name(AZStd::string::format("%s_%s",shaderAsset.GetName().GetCStr(),srgName.GetCStr()))); + const RHI::ResultCode resultCode = m_pool->Init(*device, poolDescriptor); return resultCode; } diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/WindowContext.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/WindowContext.cpp index 16ad7a1155..4b8c061b68 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/WindowContext.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/WindowContext.cpp @@ -27,7 +27,7 @@ void OnVsyncIntervalChanged(uint32_t const& interval) // NOTE: On change, broadcasts the new requested vsync interval to all windows. // The value of the vsync interval is constrained between 0 and 4 // Vsync intervals greater than 1 are not currently supported on the Vulkan RHI (see #2061 for discussion) -AZ_CVAR(uint32_t, rpi_vsync_interval, 0, OnVsyncIntervalChanged, AZ::ConsoleFunctorFlags::Null, "Set swapchain vsync interval"); +AZ_CVAR(uint32_t, rpi_vsync_interval, 1, OnVsyncIntervalChanged, AZ::ConsoleFunctorFlags::Null, "Set swapchain vsync interval"); namespace AZ {