diff --git a/AutomatedTesting/Gem/PythonTests/AWS/Windows/client_auth/test_password_signin.py b/AutomatedTesting/Gem/PythonTests/AWS/Windows/client_auth/test_password_signin.py
new file mode 100644
index 0000000000..da4898b8a9
--- /dev/null
+++ b/AutomatedTesting/Gem/PythonTests/AWS/Windows/client_auth/test_password_signin.py
@@ -0,0 +1,97 @@
+"""
+All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
+its licensors.
+For complete copyright and license terms please see the LICENSE at the root of this
+distribution (the "License"). All use of this software is governed by the License,
+or, if provided, by the license below or the license accompanying this file. Do not
+remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+"""
+import pytest
+import os
+import logging
+import ly_test_tools.log.log_monitor
+
+from AWS.Windows.resource_mappings.resource_mappings import resource_mappings
+from AWS.Windows.cdk.cdk import cdk
+from AWS.common.aws_utils import aws_utils
+from assetpipeline.ap_fixtures.asset_processor_fixture import asset_processor as asset_processor
+
+AWS_PROJECT_NAME = 'AWS-AutomationTest'
+AWS_CLIENT_AUTH_FEATURE_NAME = 'AWSClientAuth'
+AWS_CLIENT_AUTH_DEFAULT_PROFILE_NAME = 'default'
+
+GAME_LOG_NAME = 'Game.log'
+
+logger = logging.getLogger(__name__)
+
+
+@pytest.mark.SUITE_periodic
+@pytest.mark.usefixtures('automatic_process_killer')
+@pytest.mark.usefixtures('asset_processor')
+@pytest.mark.usefixtures('workspace')
+@pytest.mark.parametrize('project', ['AutomatedTesting'])
+@pytest.mark.usefixtures('cdk')
+@pytest.mark.parametrize('feature_name', [AWS_CLIENT_AUTH_FEATURE_NAME])
+@pytest.mark.usefixtures('resource_mappings')
+@pytest.mark.parametrize('resource_mappings_filename', ['aws_resource_mappings.json'])
+@pytest.mark.usefixtures('aws_utils')
+@pytest.mark.parametrize('region_name', ['us-west-2'])
+@pytest.mark.parametrize('assume_role_arn', ['arn:aws:iam::645075835648:role/o3de-automation-tests'])
+@pytest.mark.parametrize('session_name', ['o3de-Automation-session'])
+class TestAWSClientAuthPasswordSignIn(object):
+ """
+ Test class to verify AWS Cognito IDP Password sign in and Cognito Identity pool authenticated authorization.
+ """
+
+ def test_password_signin_credentials(self,
+ launcher: pytest.fixture,
+ cdk: pytest.fixture,
+ resource_mappings: pytest.fixture,
+ workspace: pytest.fixture,
+ asset_processor: pytest.fixture,
+ aws_utils: pytest.fixture
+ ):
+ """
+ Setup: Deploys cdk and updates resource mapping file.
+ Tests: Sign up new test user, admin confirm the user, sign in and get aws credentials.
+ Verification: Log monitor looks for success credentials log.
+ """
+ logger.info(f'Cdk stack names:\n{cdk.list()}')
+ stacks = cdk.deploy()
+ resource_mappings.populate_output_keys(stacks)
+ asset_processor.start()
+ asset_processor.wait_for_idle()
+
+ file_to_monitor = os.path.join(launcher.workspace.paths.project_log(), GAME_LOG_NAME)
+ log_monitor = ly_test_tools.log.log_monitor.LogMonitor(launcher=launcher, log_file_path=file_to_monitor)
+
+ launcher.args = ['+LoadLevel', 'AWS/ClientAuthPasswordSignUp']
+
+ with launcher.start(launch_ap=False):
+ result = log_monitor.monitor_log_for_lines(
+ expected_lines=['(Script) - Signup Success'],
+ unexpected_lines=['(Script) - Signup Fail'],
+ halt_on_unexpected=True,
+ )
+ assert result, 'Sign Up Success.'
+
+ launcher.stop()
+
+ cognito_idp = aws_utils.client('cognito-idp')
+ user_pool_id = resource_mappings.get_resource_name_id(f'{AWS_CLIENT_AUTH_FEATURE_NAME}.CognitoUserPoolId')
+ print(f'UserPoolId:{user_pool_id}')
+ cognito_idp.admin_confirm_sign_up(
+ UserPoolId=user_pool_id,
+ Username='test1'
+ )
+
+ launcher.args = ['+LoadLevel', 'AWS/ClientAuthPasswordSignIn']
+
+ with launcher.start(launch_ap=False):
+ result = log_monitor.monitor_log_for_lines(
+ expected_lines=['(Script) - SignIn Success', '(Script) - Success credentials'],
+ unexpected_lines=['(Script) - SignIn Fail', '(Script) - Fail credentials'],
+ halt_on_unexpected=True,
+ )
+ assert result, 'Sign in Success, fetched authenticated AWS temp credentials.'
diff --git a/AutomatedTesting/Gem/PythonTests/AWS/Windows/resource_mappings/resource_mappings.py b/AutomatedTesting/Gem/PythonTests/AWS/Windows/resource_mappings/resource_mappings.py
index c8d8cff828..b3fa3011ce 100644
--- a/AutomatedTesting/Gem/PythonTests/AWS/Windows/resource_mappings/resource_mappings.py
+++ b/AutomatedTesting/Gem/PythonTests/AWS/Windows/resource_mappings/resource_mappings.py
@@ -39,6 +39,7 @@ class ResourceMappings:
self._region = region
self._feature_name = feature_name
self._account_id = account_id
+ self._resource_mappings = {}
assert os.path.exists(self._resource_mapping_file_path), \
f'Invalid resource mapping file path {self._resource_mapping_file_path}'
@@ -79,6 +80,7 @@ class ResourceMappings:
resource_mappings[AWS_RESOURCE_MAPPINGS_KEY][resource_key]['Name/ID'] = output.get('OutputValue',
'InvalidId')
+ self._resource_mappings = resource_mappings
with open(self._resource_mapping_file_path, 'w') as file_content:
json.dump(resource_mappings, file_content, indent=4)
@@ -103,6 +105,9 @@ class ResourceMappings:
self._region = ''
self._client = None
+ def get_resource_name_id(self, resource_key: str):
+ return self._resource_mappings[AWS_RESOURCE_MAPPINGS_KEY][resource_key]['Name/ID']
+
@pytest.fixture(scope='function')
def resource_mappings(
diff --git a/AutomatedTesting/Gem/PythonTests/atom_renderer/CMakeLists.txt b/AutomatedTesting/Gem/PythonTests/atom_renderer/CMakeLists.txt
index 7ffc2072f1..91228bc71c 100644
--- a/AutomatedTesting/Gem/PythonTests/atom_renderer/CMakeLists.txt
+++ b/AutomatedTesting/Gem/PythonTests/atom_renderer/CMakeLists.txt
@@ -20,7 +20,7 @@ if(PAL_TRAIT_BUILD_HOST_TOOLS AND PAL_TRAIT_BUILD_TESTS_SUPPORTED AND AutomatedT
TEST_SUITE main
PATH ${CMAKE_CURRENT_LIST_DIR}/test_Atom_MainSuite.py
TEST_SERIAL
- TIMEOUT 300
+ TIMEOUT 400
RUNTIME_DEPENDENCIES
AssetProcessor
AutomatedTesting.Assets
@@ -31,7 +31,7 @@ if(PAL_TRAIT_BUILD_HOST_TOOLS AND PAL_TRAIT_BUILD_TESTS_SUPPORTED AND AutomatedT
TEST_SUITE sandbox
PATH ${CMAKE_CURRENT_LIST_DIR}/test_Atom_SandboxSuite.py
TEST_SERIAL
- TIMEOUT 300
+ TIMEOUT 400
RUNTIME_DEPENDENCIES
AssetProcessor
AutomatedTesting.Assets
diff --git a/AutomatedTesting/Gem/PythonTests/atom_renderer/test_Atom_MainSuite.py b/AutomatedTesting/Gem/PythonTests/atom_renderer/test_Atom_MainSuite.py
index b64a592c1d..3da1c27e67 100644
--- a/AutomatedTesting/Gem/PythonTests/atom_renderer/test_Atom_MainSuite.py
+++ b/AutomatedTesting/Gem/PythonTests/atom_renderer/test_Atom_MainSuite.py
@@ -27,6 +27,7 @@ TEST_DIRECTORY = os.path.join(os.path.dirname(__file__), "atom_hydra_scripts")
@pytest.mark.parametrize("level", ["auto_test"])
class TestAtomEditorComponentsMain(object):
+ @pytest.mark.xfail(reason="Timing out sporadically, LYN-3956")
@pytest.mark.test_case_id(
"C32078130", # Display Mapper
"C32078129", # Light
diff --git a/AutomatedTesting/Levels/AWS/ClientAuthPasswordSignIn/ClientAuthPasswordSignIn.ly b/AutomatedTesting/Levels/AWS/ClientAuthPasswordSignIn/ClientAuthPasswordSignIn.ly
new file mode 100644
index 0000000000..24fe4f2482
--- /dev/null
+++ b/AutomatedTesting/Levels/AWS/ClientAuthPasswordSignIn/ClientAuthPasswordSignIn.ly
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:43b1a23b62fe2ffa05545ac99524f40b6fff49d6e35925b9d6138c00d8082e86
+size 9073
diff --git a/AutomatedTesting/Levels/AWS/ClientAuthPasswordSignIn/PasswordSignIn.scriptcanvas b/AutomatedTesting/Levels/AWS/ClientAuthPasswordSignIn/PasswordSignIn.scriptcanvas
new file mode 100644
index 0000000000..ffc3064084
--- /dev/null
+++ b/AutomatedTesting/Levels/AWS/ClientAuthPasswordSignIn/PasswordSignIn.scriptcanvas
@@ -0,0 +1,6642 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/AutomatedTesting/Levels/AWS/ClientAuthPasswordSignIn/filelist.xml b/AutomatedTesting/Levels/AWS/ClientAuthPasswordSignIn/filelist.xml
new file mode 100644
index 0000000000..454b94a80a
--- /dev/null
+++ b/AutomatedTesting/Levels/AWS/ClientAuthPasswordSignIn/filelist.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/AutomatedTesting/Levels/AWS/ClientAuthPasswordSignIn/level.pak b/AutomatedTesting/Levels/AWS/ClientAuthPasswordSignIn/level.pak
new file mode 100644
index 0000000000..14e6b3274b
--- /dev/null
+++ b/AutomatedTesting/Levels/AWS/ClientAuthPasswordSignIn/level.pak
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:f583e0b1b7016a11583383e6c6fcd29f9e796c1a9cd4b6ddb10f7dc91deec17a
+size 3557
diff --git a/AutomatedTesting/Levels/AWS/ClientAuthPasswordSignIn/tags.txt b/AutomatedTesting/Levels/AWS/ClientAuthPasswordSignIn/tags.txt
new file mode 100644
index 0000000000..0d6c1880e7
--- /dev/null
+++ b/AutomatedTesting/Levels/AWS/ClientAuthPasswordSignIn/tags.txt
@@ -0,0 +1,12 @@
+0,0,0,0,0,0
+0,0,0,0,0,0
+0,0,0,0,0,0
+0,0,0,0,0,0
+0,0,0,0,0,0
+0,0,0,0,0,0
+0,0,0,0,0,0
+0,0,0,0,0,0
+0,0,0,0,0,0
+0,0,0,0,0,0
+0,0,0,0,0,0
+0,0,0,0,0,0
diff --git a/AutomatedTesting/Levels/AWS/ClientAuthPasswordSignUp/ClientAuthPasswordSignUp.ly b/AutomatedTesting/Levels/AWS/ClientAuthPasswordSignUp/ClientAuthPasswordSignUp.ly
new file mode 100644
index 0000000000..f853ec3890
--- /dev/null
+++ b/AutomatedTesting/Levels/AWS/ClientAuthPasswordSignUp/ClientAuthPasswordSignUp.ly
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:3b948461412d201b3a80abafa60e916f860e46e28109333fbd263a2d5fc53c5a
+size 9103
diff --git a/AutomatedTesting/Levels/AWS/ClientAuthPasswordSignUp/PasswordSignUp.scriptcanvas b/AutomatedTesting/Levels/AWS/ClientAuthPasswordSignUp/PasswordSignUp.scriptcanvas
new file mode 100644
index 0000000000..632d27d5b0
--- /dev/null
+++ b/AutomatedTesting/Levels/AWS/ClientAuthPasswordSignUp/PasswordSignUp.scriptcanvas
@@ -0,0 +1,4408 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/AutomatedTesting/Levels/AWS/ClientAuthPasswordSignUp/filelist.xml b/AutomatedTesting/Levels/AWS/ClientAuthPasswordSignUp/filelist.xml
new file mode 100644
index 0000000000..5e47a51414
--- /dev/null
+++ b/AutomatedTesting/Levels/AWS/ClientAuthPasswordSignUp/filelist.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/AutomatedTesting/Levels/AWS/ClientAuthPasswordSignUp/level.pak b/AutomatedTesting/Levels/AWS/ClientAuthPasswordSignUp/level.pak
new file mode 100644
index 0000000000..72ac9c767f
--- /dev/null
+++ b/AutomatedTesting/Levels/AWS/ClientAuthPasswordSignUp/level.pak
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:8cdb456f6eb348be27249d80e9d2262e1e0bdabf2c1ff02c1a64a5609dcd823c
+size 3553
diff --git a/AutomatedTesting/Levels/AWS/ClientAuthPasswordSignUp/tags.txt b/AutomatedTesting/Levels/AWS/ClientAuthPasswordSignUp/tags.txt
new file mode 100644
index 0000000000..0d6c1880e7
--- /dev/null
+++ b/AutomatedTesting/Levels/AWS/ClientAuthPasswordSignUp/tags.txt
@@ -0,0 +1,12 @@
+0,0,0,0,0,0
+0,0,0,0,0,0
+0,0,0,0,0,0
+0,0,0,0,0,0
+0,0,0,0,0,0
+0,0,0,0,0,0
+0,0,0,0,0,0
+0,0,0,0,0,0
+0,0,0,0,0,0
+0,0,0,0,0,0
+0,0,0,0,0,0
+0,0,0,0,0,0
diff --git a/AutomatedTesting/Registry/authenticationProvider.setreg b/AutomatedTesting/Registry/authenticationProvider.setreg
new file mode 100644
index 0000000000..c90433468c
--- /dev/null
+++ b/AutomatedTesting/Registry/authenticationProvider.setreg
@@ -0,0 +1,5 @@
+{
+ "AWS":
+ {
+ }
+}
\ No newline at end of file
diff --git a/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.cpp b/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.cpp
index 79c1a28e5d..bd826544a1 100644
--- a/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.cpp
+++ b/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.cpp
@@ -193,13 +193,12 @@ namespace AzFramework
bool handling = false;
for (auto& cameraInput : m_activeCameraInputs)
{
- cameraInput->HandleEvents(event, cursorDelta, scrollDelta);
- handling = !cameraInput->Idle() || handling;
+ handling = cameraInput->HandleEvents(event, cursorDelta, scrollDelta) || handling;
}
for (auto& cameraInput : m_idleCameraInputs)
{
- cameraInput->HandleEvents(event, cursorDelta, scrollDelta);
+ handling = cameraInput->HandleEvents(event, cursorDelta, scrollDelta) || handling;
}
return handling;
@@ -262,17 +261,26 @@ namespace AzFramework
{
m_activeCameraInputs[i]->Reset();
m_idleCameraInputs.push_back(m_activeCameraInputs[i]);
- m_activeCameraInputs[i] = m_activeCameraInputs[m_activeCameraInputs.size() - 1];
+ using AZStd::swap;
+ swap(m_activeCameraInputs[i], m_activeCameraInputs[m_activeCameraInputs.size() - 1]);
m_activeCameraInputs.pop_back();
}
}
+ void Cameras::Clear()
+ {
+ Reset();
+ AZ_Assert(m_activeCameraInputs.empty(), "Active Camera Inputs is not empty");
+
+ m_idleCameraInputs.clear();
+ }
+
RotateCameraInput::RotateCameraInput(const InputChannelId rotateChannelId)
: m_rotateChannelId(rotateChannelId)
{
}
- void RotateCameraInput::HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, [[maybe_unused]] float scrollDelta)
+ bool RotateCameraInput::HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, [[maybe_unused]] float scrollDelta)
{
const ClickDetector::ClickEvent clickEvent = [&event, this] {
if (const auto& input = AZStd::get_if(&event))
@@ -304,6 +312,11 @@ namespace AzFramework
// noop
break;
}
+
+ // note - must also check !ending to ensure the mouse up (release) event
+ // is not consumed and can be propagated to other systems.
+ // (don't swallow mouse up events)
+ return !Idle() && !Ending();
}
Camera RotateCameraInput::StepCamera(
@@ -330,7 +343,7 @@ namespace AzFramework
{
}
- void PanCameraInput::HandleEvents(
+ bool PanCameraInput::HandleEvents(
const InputEvent& event, [[maybe_unused]] const ScreenVector& cursorDelta, [[maybe_unused]] float scrollDelta)
{
if (const auto& input = AZStd::get_if(&event))
@@ -347,6 +360,8 @@ namespace AzFramework
}
}
}
+
+ return !Idle();
}
Camera PanCameraInput::StepCamera(
@@ -411,7 +426,7 @@ namespace AzFramework
{
}
- void TranslateCameraInput::HandleEvents(
+ bool TranslateCameraInput::HandleEvents(
const InputEvent& event, [[maybe_unused]] const ScreenVector& cursorDelta, [[maybe_unused]] float scrollDelta)
{
if (const auto& input = AZStd::get_if(&event))
@@ -429,7 +444,8 @@ namespace AzFramework
m_boost = true;
}
}
- else if (input->m_state == InputChannel::State::Ended)
+ // ensure we don't process end events in the idle state
+ else if (input->m_state == InputChannel::State::Ended && !Idle())
{
m_translation &= ~(translationFromKey(input->m_channelId));
if (m_translation == TranslationType::Nil)
@@ -442,6 +458,8 @@ namespace AzFramework
}
}
}
+
+ return !Idle();
}
Camera TranslateCameraInput::StepCamera(
@@ -503,7 +521,7 @@ namespace AzFramework
m_boost = false;
}
- void OrbitCameraInput::HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta)
+ bool OrbitCameraInput::HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta)
{
if (const auto* input = AZStd::get_if(&event))
{
@@ -522,8 +540,10 @@ namespace AzFramework
if (Active())
{
- m_orbitCameras.HandleEvents(event, cursorDelta, scrollDelta);
+ return m_orbitCameras.HandleEvents(event, cursorDelta, scrollDelta);
}
+
+ return !Idle();
}
Camera OrbitCameraInput::StepCamera(
@@ -533,7 +553,7 @@ namespace AzFramework
if (Beginning())
{
- const auto hasLookAt = [&nextCamera, &targetCamera, lookAtFn = m_lookAtFn] {
+ const auto hasLookAt = [&nextCamera, &targetCamera, &lookAtFn = m_lookAtFn] {
if (lookAtFn)
{
if (const auto lookAt = lookAtFn())
@@ -585,13 +605,15 @@ namespace AzFramework
return nextCamera;
}
- void OrbitDollyScrollCameraInput::HandleEvents(
+ bool OrbitDollyScrollCameraInput::HandleEvents(
const InputEvent& event, [[maybe_unused]] const ScreenVector& cursorDelta, [[maybe_unused]] float scrollDelta)
{
if (const auto* scroll = AZStd::get_if(&event))
{
BeginActivation();
}
+
+ return !Idle();
}
Camera OrbitDollyScrollCameraInput::StepCamera(
@@ -609,7 +631,7 @@ namespace AzFramework
{
}
- void OrbitDollyCursorMoveCameraInput::HandleEvents(
+ bool OrbitDollyCursorMoveCameraInput::HandleEvents(
const InputEvent& event, [[maybe_unused]] const ScreenVector& cursorDelta, [[maybe_unused]] float scrollDelta)
{
if (const auto& input = AZStd::get_if(&event))
@@ -626,6 +648,8 @@ namespace AzFramework
}
}
}
+
+ return !Idle();
}
Camera OrbitDollyCursorMoveCameraInput::StepCamera(
@@ -637,13 +661,15 @@ namespace AzFramework
return nextCamera;
}
- void ScrollTranslationCameraInput::HandleEvents(
+ bool ScrollTranslationCameraInput::HandleEvents(
const InputEvent& event, [[maybe_unused]] const ScreenVector& cursorDelta, [[maybe_unused]] float scrollDelta)
{
if (const auto* scroll = AZStd::get_if(&event))
{
BeginActivation();
}
+
+ return !Idle();
}
Camera ScrollTranslationCameraInput::StepCamera(
diff --git a/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.h b/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.h
index b6b2bc1e6a..582fb5a6de 100644
--- a/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.h
+++ b/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.h
@@ -149,7 +149,7 @@ namespace AzFramework
ResetImpl();
}
- virtual void HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) = 0;
+ virtual bool HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) = 0;
virtual Camera StepCamera(const Camera& targetCamera, const ScreenVector& cursorDelta, float scrollDelta, float deltaTime) = 0;
virtual bool Exclusive() const
@@ -171,16 +171,29 @@ namespace AzFramework
class Cameras
{
public:
- void AddCamera(AZStd::shared_ptr cameraInput);
bool HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta);
Camera StepCamera(const Camera& targetCamera, const ScreenVector& cursorDelta, float scrollDelta, float deltaTime);
+
+ void AddCamera(AZStd::shared_ptr cameraInput);
+ //! Reset the state of all cameras.
void Reset();
+ //! Remove all cameras that were added.
+ void Clear();
+ //! Is one of the cameras in the active camera inputs marked as 'exclusive'.
+ //! @note This implies no other sibling cameras can begin while the exclusive camera is running.
+ bool Exclusive() const;
private:
AZStd::vector> m_activeCameraInputs;
AZStd::vector> m_idleCameraInputs;
};
+ inline bool Cameras::Exclusive() const
+ {
+ return AZStd::any_of(
+ m_activeCameraInputs.begin(), m_activeCameraInputs.end(), [](const auto& cameraInput) { return cameraInput->Exclusive(); });
+ }
+
class CameraSystem
{
public:
@@ -200,7 +213,7 @@ namespace AzFramework
explicit RotateCameraInput(InputChannelId rotateChannelId);
// CameraInput overrides ...
- void HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) override;
+ bool HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) override;
Camera StepCamera(const Camera& targetCamera, const ScreenVector& cursorDelta, float scrollDelta, float deltaTime) override;
private:
@@ -241,7 +254,7 @@ namespace AzFramework
PanCameraInput(InputChannelId panChannelId, PanAxesFn panAxesFn);
// CameraInput overrides ...
- void HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) override;
+ bool HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) override;
Camera StepCamera(const Camera& targetCamera, const ScreenVector& cursorDelta, float scrollDelta, float deltaTime) override;
private:
@@ -282,7 +295,7 @@ namespace AzFramework
explicit TranslateCameraInput(TranslationAxesFn translationAxesFn);
// CameraInput overrides ...
- void HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) override;
+ bool HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) override;
Camera StepCamera(const Camera& targetCamera, const ScreenVector& cursorDelta, float scrollDelta, float deltaTime) override;
void ResetImpl() override;
@@ -352,7 +365,7 @@ namespace AzFramework
{
public:
// CameraInput overrides ...
- void HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) override;
+ bool HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) override;
Camera StepCamera(const Camera& targetCamera, const ScreenVector& cursorDelta, float scrollDelta, float deltaTime) override;
};
@@ -362,7 +375,7 @@ namespace AzFramework
explicit OrbitDollyCursorMoveCameraInput(InputChannelId dollyChannelId);
// CameraInput overrides ...
- void HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) override;
+ bool HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) override;
Camera StepCamera(const Camera& targetCamera, const ScreenVector& cursorDelta, float scrollDelta, float deltaTime) override;
private:
@@ -373,7 +386,7 @@ namespace AzFramework
{
public:
// CameraInput overrides ...
- void HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) override;
+ bool HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) override;
Camera StepCamera(const Camera& targetCamera, const ScreenVector& cursorDelta, float scrollDelta, float deltaTime) override;
};
@@ -383,7 +396,7 @@ namespace AzFramework
using LookAtFn = AZStd::function()>;
// CameraInput overrides ...
- void HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) override;
+ bool HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) override;
Camera StepCamera(const Camera& targetCamera, const ScreenVector& cursorDelta, float scrollDelta, float deltaTime) override;
bool Exclusive() const override;
diff --git a/Code/Framework/AzFramework/AzFramework/Viewport/ClickDetector.cpp b/Code/Framework/AzFramework/AzFramework/Viewport/ClickDetector.cpp
index 4b8fbca36a..c276463554 100644
--- a/Code/Framework/AzFramework/AzFramework/Viewport/ClickDetector.cpp
+++ b/Code/Framework/AzFramework/AzFramework/Viewport/ClickDetector.cpp
@@ -17,6 +17,17 @@ namespace AzFramework
{
ClickDetector::ClickOutcome ClickDetector::DetectClick(const ClickEvent clickEvent, const ScreenVector& cursorDelta)
{
+ const auto previousDetectionState = m_detectionState;
+ if (previousDetectionState == DetectionState::WaitingForMove)
+ {
+ // only allow the action to begin if the mouse has been moved a small amount
+ m_moveAccumulator += ScreenVectorLength(cursorDelta);
+ if (m_moveAccumulator > m_deadZone)
+ {
+ m_detectionState = DetectionState::Moved;
+ }
+ }
+
if (clickEvent == ClickEvent::Down)
{
const auto now = std::chrono::steady_clock::now();
@@ -52,15 +63,9 @@ namespace AzFramework
return clickOutcome;
}
- if (m_detectionState == DetectionState::WaitingForMove)
+ if (previousDetectionState == DetectionState::WaitingForMove && m_detectionState == DetectionState::Moved)
{
- // only allow the action to begin if the mouse has been moved a small amount
- m_moveAccumulator += ScreenVectorLength(cursorDelta);
- if (m_moveAccumulator > m_deadZone)
- {
- m_detectionState = DetectionState::Moved;
- return ClickOutcome::Move;
- }
+ return ClickOutcome::Move;
}
return ClickOutcome::Nil;
diff --git a/Code/Framework/AzFramework/AzFramework/Viewport/ClickDetector.h b/Code/Framework/AzFramework/AzFramework/Viewport/ClickDetector.h
index 997ccd07d9..a595735d28 100644
--- a/Code/Framework/AzFramework/AzFramework/Viewport/ClickDetector.h
+++ b/Code/Framework/AzFramework/AzFramework/Viewport/ClickDetector.h
@@ -50,7 +50,11 @@ namespace AzFramework
//! Called from any type of 'handle event' function.
ClickOutcome DetectClick(ClickEvent clickEvent, const ScreenVector& cursorDelta);
+ //! Override the default double click interval.
+ //! @note Default is 400ms - system default.
void SetDoubleClickInterval(float doubleClickInterval);
+ //! Override the dead zone before a 'move' outcome will be triggered.
+ void SetDeadZone(float deadZone);
private:
//! Internal state of ClickDetector based on incoming events.
@@ -72,4 +76,9 @@ namespace AzFramework
{
m_doubleClickInterval = doubleClickInterval;
}
+
+ inline void ClickDetector::SetDeadZone(const float deadZone)
+ {
+ m_deadZone = deadZone;
+ }
} // namespace AzFramework
diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Viewport/ViewportMessages.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Viewport/ViewportMessages.h
index ee95412376..8e91dc945d 100644
--- a/Code/Framework/AzToolsFramework/AzToolsFramework/Viewport/ViewportMessages.h
+++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Viewport/ViewportMessages.h
@@ -18,6 +18,7 @@
#include
#include
#include
+#include
#include
#include
@@ -304,4 +305,24 @@ namespace AzToolsFramework
return entityContextId;
}
+
+ //! Maps a mouse interaction event to a ClickDetector event.
+ //! @note Function only cares about up or down events, all other events are mapped to Nil (ignored).
+ inline AzFramework::ClickDetector::ClickEvent ClickDetectorEventFromViewportInteraction(
+ const ViewportInteraction::MouseInteractionEvent& mouseInteraction)
+ {
+ if (mouseInteraction.m_mouseInteraction.m_mouseButtons.Left())
+ {
+ if (mouseInteraction.m_mouseEvent == ViewportInteraction::MouseEvent::Down)
+ {
+ return AzFramework::ClickDetector::ClickEvent::Down;
+ }
+
+ if (mouseInteraction.m_mouseEvent == ViewportInteraction::MouseEvent::Up)
+ {
+ return AzFramework::ClickDetector::ClickEvent::Up;
+ }
+ }
+ return AzFramework::ClickDetector::ClickEvent::Nil;
+ }
} // namespace AzToolsFramework
diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorBoxSelect.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorBoxSelect.cpp
index 2e467caa4c..531cffb561 100644
--- a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorBoxSelect.cpp
+++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorBoxSelect.cpp
@@ -14,6 +14,7 @@
#include
#include
+#include
#include
@@ -27,8 +28,11 @@ namespace AzToolsFramework
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzToolsFramework);
- if (mouseInteraction.m_mouseInteraction.m_mouseButtons.Left() &&
- mouseInteraction.m_mouseEvent == ViewportInteraction::MouseEvent::Down)
+ m_cursorState.SetCurrentPosition(mouseInteraction.m_mouseInteraction.m_mousePick.m_screenCoordinates);
+
+ const auto selectClickEvent = ClickDetectorEventFromViewportInteraction(mouseInteraction);
+ const auto clickOutcome = m_clickDetector.DetectClick(selectClickEvent, m_cursorState.CursorDelta());
+ if (clickOutcome == AzFramework::ClickDetector::ClickOutcome::Move)
{
if (m_leftMouseDown)
{
@@ -58,8 +62,7 @@ namespace AzToolsFramework
}
}
- if (mouseInteraction.m_mouseInteraction.m_mouseButtons.Left() &&
- mouseInteraction.m_mouseEvent == ViewportInteraction::MouseEvent::Up)
+ if (clickOutcome == AzFramework::ClickDetector::ClickOutcome::Release)
{
if (m_leftMouseUp)
{
@@ -77,6 +80,8 @@ namespace AzToolsFramework
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzToolsFramework);
+ m_cursorState.Update();
+
if (m_boxSelectRegion)
{
debugDisplay.DepthTestOff();
diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorBoxSelect.h b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorBoxSelect.h
index 7f50b16325..c115220755 100644
--- a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorBoxSelect.h
+++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorBoxSelect.h
@@ -14,6 +14,8 @@
#include
#include
+#include
+#include
#include
#include
@@ -26,49 +28,49 @@ namespace AzFramework
namespace AzToolsFramework
{
- /// Utility to provide box select (click and drag) support for viewport types.
- /// Users can override the mouse event callbacks and display scene function to customize behavior.
+ //! Utility to provide box select (click and drag) support for viewport types.
+ //! Users can override the mouse event callbacks and display scene function to customize behavior.
class EditorBoxSelect
{
public:
EditorBoxSelect() = default;
- /// Return if a box select action is currently taking place.
+ //! Return if a box select action is currently taking place.
bool Active() const { return m_boxSelectRegion.has_value(); }
- /// Update the box select for various mouse events.
- /// Call HandleMouseInteraction from type/system implementing MouseViewportRequests interface.
+ //! Update the box select for various mouse events.
+ //! Call HandleMouseInteraction from type/system implementing MouseViewportRequests interface.
void HandleMouseInteraction(
const ViewportInteraction::MouseInteractionEvent& mouseInteraction);
- /// Responsible for drawing the 2d box representing the selection in screen space.
+ //! Responsible for drawing the 2d box representing the selection in screen space.
void Display2d(const AzFramework::ViewportInfo& viewportInfo, AzFramework::DebugDisplayRequests& debugDisplay);
- /// Custom drawing behavior to happen during a box select.
+ //! Custom drawing behavior to happen during a box select.
void DisplayScene(
const AzFramework::ViewportInfo& viewportInfo, AzFramework::DebugDisplayRequests& debugDisplay);
- /// Set the left mouse down callback.
+ //! Set the left mouse down callback.
void InstallLeftMouseDown(
const AZStd::function& leftMouseDown);
- /// Set the mouse move callback.
+ //! Set the mouse move callback.
void InstallMouseMove(
const AZStd::function& mouseMove);
- /// Set the left mouse up callback.
+ //! Set the left mouse up callback.
void InstallLeftMouseUp(
const AZStd::function& leftMouseUp);
- /// Set the display scene callback.
+ //! Set the display scene callback.
void InstallDisplayScene(
const AZStd::function& displayScene);
- /// Return the box select region.
- /// If a box selection is being made, return the current rectangle representing the area.
- /// If there is currently no active box select, then the Maybe type will be empty (there will be no region/area).
+ //! Return the box select region.
+ //! If a box selection is being made, return the current rectangle representing the area.
+ //! If there is currently no active box select, then the Maybe type will be empty (there will be no region/area).
const AZStd::optional& BoxRegion() const { return m_boxSelectRegion; }
- /// Return the active modifiers from the previous frame.
+ //! Return the active modifiers from the previous frame.
ViewportInteraction::KeyboardModifiers PreviousModifiers() const { return m_previousModifiers; }
private:
@@ -79,7 +81,9 @@ namespace AzToolsFramework
AZStd::function m_displayScene;
- AZStd::optional m_boxSelectRegion; ///< Maybe/optional value to store box select region while active.
- ViewportInteraction::KeyboardModifiers m_previousModifiers; ///< Modifier keys active on the previous frame.
+ AZStd::optional m_boxSelectRegion; //!< Maybe/optional value to store box select region while active.
+ ViewportInteraction::KeyboardModifiers m_previousModifiers; //!< Modifier keys active on the previous frame.
+ AzFramework::ClickDetector m_clickDetector; //!< Utility type to detect if a mouse click or move has occurred.
+ AzFramework::CursorState m_cursorState; //!< Utility type to track the current cursor position (and movement/delta).
};
} // namespace AzToolsFramework
diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.cpp
index 6e49f7c601..91644dc6ac 100644
--- a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.cpp
+++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.cpp
@@ -1782,22 +1782,7 @@ namespace AzToolsFramework
m_cachedEntityIdUnderCursor = m_editorHelpers->HandleMouseInteraction(cameraState, mouseInteraction);
- const AzFramework::ClickDetector::ClickEvent selectClickEvent = [&mouseInteraction] {
- if (mouseInteraction.m_mouseInteraction.m_mouseButtons.Left())
- {
- if (mouseInteraction.m_mouseEvent == ViewportInteraction::MouseEvent::Down)
- {
- return AzFramework::ClickDetector::ClickEvent::Down;
- }
-
- if (mouseInteraction.m_mouseEvent == ViewportInteraction::MouseEvent::Up)
- {
- return AzFramework::ClickDetector::ClickEvent::Up;
- }
- }
- return AzFramework::ClickDetector::ClickEvent::Nil;
- }();
-
+ const auto selectClickEvent = ClickDetectorEventFromViewportInteraction(mouseInteraction);
m_cursorState.SetCurrentPosition(mouseInteraction.m_mouseInteraction.m_mousePick.m_screenCoordinates);
const auto clickOutcome = m_clickDetector.DetectClick(selectClickEvent, m_cursorState.CursorDelta());
diff --git a/Code/Framework/Tests/CameraInputTests.cpp b/Code/Framework/Tests/CameraInputTests.cpp
new file mode 100644
index 0000000000..6fe9837c22
--- /dev/null
+++ b/Code/Framework/Tests/CameraInputTests.cpp
@@ -0,0 +1,90 @@
+/*
+ * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
+ * its licensors.
+ *
+ * For complete copyright and license terms please see the LICENSE at the root of this
+ * distribution (the "License"). All use of this software is governed by the License,
+ * or, if provided, by the license below or the license accompanying this file. Do not
+ * remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *
+ */
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace UnitTest
+{
+ class CameraInputFixture : public AllocatorsTestFixture
+ {
+ public:
+ AzFramework::Camera m_camera;
+ AzFramework::Camera m_targetCamera;
+ AZStd::shared_ptr m_cameraSystem;
+
+ bool HandleEventAndUpdate(const AzFramework::InputEvent& event)
+ {
+ constexpr float deltaTime = 0.01666f; // 60fps
+ const bool consumed = m_cameraSystem->HandleEvents(event);
+ m_camera = m_cameraSystem->StepCamera(m_targetCamera, deltaTime);
+ return consumed;
+ }
+
+ void SetUp() override
+ {
+ AllocatorsTestFixture::SetUp();
+
+ AzFramework::ReloadCameraKeyBindings();
+
+ m_cameraSystem = AZStd::make_shared();
+
+ auto firstPersonRotateCamera = AZStd::make_shared(AzFramework::InputDeviceMouse::Button::Right);
+ auto firstPersonTranslateCamera = AZStd::make_shared(AzFramework::LookTranslation);
+
+ auto orbitCamera = AZStd::make_shared();
+ auto orbitRotateCamera = AZStd::make_shared(AzFramework::InputDeviceMouse::Button::Left);
+ auto orbitTranslateCamera = AZStd::make_shared(AzFramework::OrbitTranslation);
+
+ orbitCamera->m_orbitCameras.AddCamera(orbitRotateCamera);
+ orbitCamera->m_orbitCameras.AddCamera(orbitTranslateCamera);
+
+ m_cameraSystem->m_cameras.AddCamera(firstPersonRotateCamera);
+ m_cameraSystem->m_cameras.AddCamera(firstPersonTranslateCamera);
+ m_cameraSystem->m_cameras.AddCamera(orbitCamera);
+ }
+
+ void TearDown() override
+ {
+ m_cameraSystem->m_cameras.Clear();
+ m_cameraSystem.reset();
+
+ AllocatorsTestFixture::TearDown();
+ }
+ };
+
+ TEST_F(CameraInputFixture, BeginEndOrbitCameraConsumesCorrectEvents)
+ {
+ // set initial mouse position
+ const bool consumed1 = HandleEventAndUpdate(AzFramework::CursorEvent{AzFramework::ScreenPoint(5, 5)});
+ // begin orbit camera
+ const bool consumed2 = HandleEventAndUpdate(
+ AzFramework::DiscreteInputEvent{AzFramework::InputDeviceKeyboard::Key::ModifierAltL, AzFramework::InputChannel::State::Began});
+ // begin listening for orbit rotate (click detector) - event is not consumed
+ const bool consumed3 = HandleEventAndUpdate(
+ AzFramework::DiscreteInputEvent{AzFramework::InputDeviceMouse::Button::Left, AzFramework::InputChannel::State::Began});
+ // begin orbit rotate (mouse has moved sufficient distance to initiate)
+ const bool consumed4 = HandleEventAndUpdate(AzFramework::CursorEvent{AzFramework::ScreenPoint(10, 10)});
+ // end orbit (mouse up) - event is not consumed
+ const bool consumed5 = HandleEventAndUpdate(
+ AzFramework::DiscreteInputEvent{AzFramework::InputDeviceMouse::Button::Left, AzFramework::InputChannel::State::Ended});
+
+ const auto allConsumed = AZStd::vector{consumed1, consumed2, consumed3, consumed4, consumed5};
+
+ using ::testing::ElementsAre;
+ EXPECT_THAT(allConsumed, ElementsAre(false, true, false, true, false));
+ }
+} // namespace UnitTest
diff --git a/Code/Framework/Tests/ClickDetectorTests.cpp b/Code/Framework/Tests/ClickDetectorTests.cpp
index 7e6f9634c8..64f06ee66c 100644
--- a/Code/Framework/Tests/ClickDetectorTests.cpp
+++ b/Code/Framework/Tests/ClickDetectorTests.cpp
@@ -139,4 +139,21 @@ namespace UnitTest
EXPECT_THAT(secondaryDownOutcome, Eq(ClickDetector::ClickOutcome::Nil)); // ignored double click
EXPECT_THAT(secondaryUpOutcome, Eq(ClickDetector::ClickOutcome::Nil)); // click not registered
}
+
+ // if the click detector registers a mouse down event, but then all intermediate calls are ignored
+ // (another system may start intercepting events and swallowing them) then when we do receive a mouse
+ // up event we should ensure we take into account the current delta - if the delta is large, then the
+ // outcome will be release
+ TEST_F(ClickDetectorFixture, ClickIsNotRegisteredAfterIgnoringMouseMovesBeforeMouseUpWithLargeDelta)
+ {
+ using ::testing::Eq;
+
+ const ClickDetector::ClickOutcome downOutcome =
+ m_clickDetector.DetectClick(ClickDetector::ClickEvent::Down, ScreenVector(0, 0));
+ const ClickDetector::ClickOutcome upOutcome =
+ m_clickDetector.DetectClick(ClickDetector::ClickEvent::Up, ScreenVector(50, 50));
+
+ EXPECT_THAT(downOutcome, Eq(ClickDetector::ClickOutcome::Nil));
+ EXPECT_THAT(upOutcome, Eq(ClickDetector::ClickOutcome::Release));
+ }
} // namespace UnitTest
diff --git a/Code/Framework/Tests/frameworktests_files.cmake b/Code/Framework/Tests/frameworktests_files.cmake
index e249cf6e64..197bcc9fce 100644
--- a/Code/Framework/Tests/frameworktests_files.cmake
+++ b/Code/Framework/Tests/frameworktests_files.cmake
@@ -17,6 +17,7 @@ set(FILES
BinToTextEncode.cpp
ComponentAddRemove.cpp
ComponentAdapterTests.cpp
+ CameraInputTests.cpp
ClickDetectorTests.cpp
CursorStateTests.cpp
EntityContext.cpp
diff --git a/Code/Sandbox/Editor/ModernViewportCameraController.cpp b/Code/Sandbox/Editor/ModernViewportCameraController.cpp
index af161af493..83ab2ef0b5 100644
--- a/Code/Sandbox/Editor/ModernViewportCameraController.cpp
+++ b/Code/Sandbox/Editor/ModernViewportCameraController.cpp
@@ -97,17 +97,38 @@ namespace SandboxEditor
AzFramework::ViewportDebugDisplayEventBus::Handler::BusDisconnect();
}
+ // should the camera system respond to this particular event
+ static bool ShouldHandle(const AzFramework::ViewportControllerPriority priority, const bool exclusive)
+ {
+ // ModernViewportCameraControllerInstance receives events at all priorities, it should only respond
+ // to normal priority events if it is not in 'exclusive' mode and when in 'exclusive' mode it should
+ // only respond to the highest priority events
+ return !exclusive && priority == AzFramework::ViewportControllerPriority::Normal ||
+ exclusive && priority == AzFramework::ViewportControllerPriority::Highest;
+ }
+
bool ModernViewportCameraControllerInstance::HandleInputChannelEvent(const AzFramework::ViewportControllerInputEvent& event)
{
AzFramework::WindowSize windowSize;
AzFramework::WindowRequestBus::EventResult(
windowSize, event.m_windowHandle, &AzFramework::WindowRequestBus::Events::GetClientAreaSize);
- return m_cameraSystem.HandleEvents(AzFramework::BuildInputEvent(event.m_inputChannel, windowSize));
+ if (ShouldHandle(event.m_priority, m_cameraSystem.m_cameras.Exclusive()))
+ {
+ return m_cameraSystem.HandleEvents(AzFramework::BuildInputEvent(event.m_inputChannel, windowSize));
+ }
+
+ return false;
}
void ModernViewportCameraControllerInstance::UpdateViewport(const AzFramework::ViewportControllerUpdateEvent& event)
{
+ // only update for a single priority (normal is the default)
+ if (event.m_priority != AzFramework::ViewportControllerPriority::Normal)
+ {
+ return;
+ }
+
if (auto viewportContext = RetrieveViewportContext(GetViewportId()))
{
m_updatingTransform = true;
diff --git a/Code/Sandbox/Editor/ModernViewportCameraController.h b/Code/Sandbox/Editor/ModernViewportCameraController.h
index 066c8efaa8..39e3c9cbb3 100644
--- a/Code/Sandbox/Editor/ModernViewportCameraController.h
+++ b/Code/Sandbox/Editor/ModernViewportCameraController.h
@@ -22,7 +22,9 @@
namespace SandboxEditor
{
class ModernViewportCameraControllerInstance;
- class ModernViewportCameraController : public AzFramework::MultiViewportController
+ class ModernViewportCameraController
+ : public AzFramework::MultiViewportController<
+ ModernViewportCameraControllerInstance, AzFramework::ViewportControllerPriority::DispatchToAllPriorities>
{
public:
using CameraListBuilder = AZStd::function;
diff --git a/Code/Tools/ProjectManager/Resources/ArrowDownLine.svg b/Code/Tools/ProjectManager/Resources/ArrowDownLine.svg
new file mode 100644
index 0000000000..8418431f11
--- /dev/null
+++ b/Code/Tools/ProjectManager/Resources/ArrowDownLine.svg
@@ -0,0 +1,3 @@
+
diff --git a/Code/Tools/ProjectManager/Resources/ArrowUpLine.svg b/Code/Tools/ProjectManager/Resources/ArrowUpLine.svg
new file mode 100644
index 0000000000..d7f26fdad5
--- /dev/null
+++ b/Code/Tools/ProjectManager/Resources/ArrowUpLine.svg
@@ -0,0 +1,3 @@
+
diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp
index 5737a188de..3c221d6055 100644
--- a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp
+++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp
@@ -58,13 +58,6 @@ namespace O3DE::ProjectManager
hLayout->addWidget(m_gemListView);
hLayout->addWidget(m_gemInspector);
-
-
- // Select the first entry after everything got correctly sized
- QTimer::singleShot(100, [=]{
- QModelIndex firstModelIndex = m_gemListView->model()->index(0,0);
- m_gemListView->selectionModel()->select(firstModelIndex, QItemSelectionModel::ClearAndSelect);
- });
}
QVector GemCatalogScreen::GenerateTestData()
diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemInfo.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemInfo.cpp
index 729935fc8e..5b7127bdbe 100644
--- a/Code/Tools/ProjectManager/Source/GemCatalog/GemInfo.cpp
+++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemInfo.cpp
@@ -32,21 +32,36 @@ namespace O3DE::ProjectManager
{
switch (platform)
{
- case O3DE::ProjectManager::GemInfo::Android:
+ case Android:
return "Android";
- case O3DE::ProjectManager::GemInfo::iOS:
+ case iOS:
return "iOS";
- case O3DE::ProjectManager::GemInfo::Linux:
+ case Linux:
return "Linux";
- case O3DE::ProjectManager::GemInfo::macOS:
+ case macOS:
return "macOS";
- case O3DE::ProjectManager::GemInfo::Windows:
+ case Windows:
return "Windows";
default:
return "";
}
}
+ QString GemInfo::GetTypeString(Type type)
+ {
+ switch (type)
+ {
+ case Asset:
+ return "Asset";
+ case Code:
+ return "Code";
+ case Tool:
+ return "Tool";
+ default:
+ return "";
+ }
+ }
+
bool GemInfo::IsPlatformSupported(Platform platform) const
{
return (m_platforms & platform);
diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemInfo.h b/Code/Tools/ProjectManager/Source/GemCatalog/GemInfo.h
index 7ee619702f..28b2fab451 100644
--- a/Code/Tools/ProjectManager/Source/GemCatalog/GemInfo.h
+++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemInfo.h
@@ -36,6 +36,16 @@ namespace O3DE::ProjectManager
Q_DECLARE_FLAGS(Platforms, Platform)
static QString GetPlatformString(Platform platform);
+ enum Type
+ {
+ Asset = 1 << 0,
+ Code = 1 << 1,
+ Tool = 1 << 2,
+ NumTypes = 3
+ };
+ Q_DECLARE_FLAGS(Types, Type)
+ static QString GetTypeString(Type type);
+
GemInfo() = default;
GemInfo(const QString& name, const QString& creator, const QString& summary, Platforms platforms, bool isAdded);
bool IsPlatformSupported(Platform platform) const;
@@ -50,6 +60,7 @@ namespace O3DE::ProjectManager
bool m_isAdded = false; //! Is the gem currently added and enabled in the project?
QString m_summary;
Platforms m_platforms;
+ Types m_types; //! Asset and/or Code and/or Tool
QStringList m_features;
QString m_directoryLink;
QString m_documentationLink;
@@ -62,3 +73,4 @@ namespace O3DE::ProjectManager
} // namespace O3DE::ProjectManager
Q_DECLARE_OPERATORS_FOR_FLAGS(O3DE::ProjectManager::GemInfo::Platforms)
+Q_DECLARE_OPERATORS_FOR_FLAGS(O3DE::ProjectManager::GemInfo::Types)
diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.cpp
index 1112c656f3..addf59783d 100644
--- a/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.cpp
+++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.cpp
@@ -10,7 +10,8 @@
*
*/
-#include "GemModel.h"
+#include
+#include
namespace O3DE::ProjectManager
{
@@ -32,8 +33,11 @@ namespace O3DE::ProjectManager
item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable);
item->setData(gemInfo.m_name, RoleName);
+ const QString uuidString = gemInfo.m_uuid.ToString().c_str();
+ item->setData(uuidString, RoleUuid);
item->setData(gemInfo.m_creator, RoleCreator);
- item->setData(static_cast(gemInfo.m_platforms), RolePlatforms);
+ item->setData(aznumeric_cast(gemInfo.m_platforms), RolePlatforms);
+ item->setData(aznumeric_cast(gemInfo.m_types), RoleTypes);
item->setData(gemInfo.m_summary, RoleSummary);
item->setData(gemInfo.m_isAdded, RoleIsAdded);
@@ -48,6 +52,8 @@ namespace O3DE::ProjectManager
item->setData(gemInfo.m_features, RoleFeatures);
appendRow(item);
+
+ m_uuidToNameMap[uuidString] = gemInfo.m_displayName;
}
void GemModel::Clear()
@@ -65,11 +71,21 @@ namespace O3DE::ProjectManager
return modelIndex.data(RoleCreator).toString();
}
+ QString GemModel::GetUuidString(const QModelIndex& modelIndex)
+ {
+ return modelIndex.data(RoleUuid).toString();
+ }
+
GemInfo::Platforms GemModel::GetPlatforms(const QModelIndex& modelIndex)
{
return static_cast(modelIndex.data(RolePlatforms).toInt());
}
+ GemInfo::Types GemModel::GetTypes(const QModelIndex& modelIndex)
+ {
+ return static_cast(modelIndex.data(RoleTypes).toInt());
+ }
+
QString GemModel::GetSummary(const QModelIndex& modelIndex)
{
return modelIndex.data(RoleSummary).toString();
@@ -90,9 +106,35 @@ namespace O3DE::ProjectManager
return modelIndex.data(RoleDocLink).toString();
}
+ AZ::Outcome GemModel::FindGemNameByUuidString(const QString& uuidString) const
+ {
+ const auto iterator = m_uuidToNameMap.find(uuidString);
+ if (iterator != m_uuidToNameMap.end())
+ {
+ return AZ::Success(iterator.value());
+ }
+
+ return AZ::Failure();
+ }
+
QStringList GemModel::GetDependingGems(const QModelIndex& modelIndex)
{
- return modelIndex.data(RoleDependingGems).toStringList();
+ QStringList result = modelIndex.data(RoleDependingGems).toStringList();
+ if (result.isEmpty())
+ {
+ return {};
+ }
+
+ for (QString& dependingGemString : result)
+ {
+ AZ::Outcome gemNameOutcome = FindGemNameByUuidString(dependingGemString);
+ if (gemNameOutcome.IsSuccess())
+ {
+ dependingGemString = gemNameOutcome.GetValue();
+ }
+ }
+
+ return result;
}
QStringList GemModel::GetConflictingGems(const QModelIndex& modelIndex)
diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.h b/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.h
index fba65e7009..76211b1f22 100644
--- a/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.h
+++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.h
@@ -13,7 +13,8 @@
#pragma once
#if !defined(Q_MOC_RUN)
-#include "GemInfo.h"
+#include
+#include
#include
#include
#include
@@ -33,14 +34,18 @@ namespace O3DE::ProjectManager
void AddGem(const GemInfo& gemInfo);
void Clear();
+ AZ::Outcome FindGemNameByUuidString(const QString& uuidString) const;
+ QStringList GetDependingGems(const QModelIndex& modelIndex);
+
static QString GetName(const QModelIndex& modelIndex);
static QString GetCreator(const QModelIndex& modelIndex);
+ static QString GetUuidString(const QModelIndex& modelIndex);
static GemInfo::Platforms GetPlatforms(const QModelIndex& modelIndex);
+ static GemInfo::Types GetTypes(const QModelIndex& modelIndex);
static QString GetSummary(const QModelIndex& modelIndex);
static bool IsAdded(const QModelIndex& modelIndex);
static QString GetDirectoryLink(const QModelIndex& modelIndex);
static QString GetDocLink(const QModelIndex& modelIndex);
- static QStringList GetDependingGems(const QModelIndex& modelIndex);
static QStringList GetConflictingGems(const QModelIndex& modelIndex);
static QString GetVersion(const QModelIndex& modelIndex);
static QString GetLastUpdated(const QModelIndex& modelIndex);
@@ -51,6 +56,7 @@ namespace O3DE::ProjectManager
enum UserRole
{
RoleName = Qt::UserRole,
+ RoleUuid,
RoleCreator,
RolePlatforms,
RoleSummary,
@@ -63,8 +69,10 @@ namespace O3DE::ProjectManager
RoleLastUpdated,
RoleBinarySize,
RoleFeatures,
+ RoleTypes
};
+ QHash m_uuidToNameMap;
QItemSelectionModel* m_selectionModel = nullptr;
};
} // namespace O3DE::ProjectManager
diff --git a/Code/Tools/ProjectManager/Source/LinkWidget.cpp b/Code/Tools/ProjectManager/Source/LinkWidget.cpp
index fddc4cd8c9..a6308f6c62 100644
--- a/Code/Tools/ProjectManager/Source/LinkWidget.cpp
+++ b/Code/Tools/ProjectManager/Source/LinkWidget.cpp
@@ -27,7 +27,12 @@ namespace O3DE::ProjectManager
void LinkLabel::mousePressEvent([[maybe_unused]] QMouseEvent* event)
{
- QDesktopServices::openUrl(m_url);
+ if (m_url.isValid())
+ {
+ QDesktopServices::openUrl(m_url);
+ }
+
+ emit clicked();
}
void LinkLabel::enterEvent([[maybe_unused]] QEvent* event)
diff --git a/Code/Tools/ProjectManager/Source/LinkWidget.h b/Code/Tools/ProjectManager/Source/LinkWidget.h
index 7055dce2af..b3a34cd63a 100644
--- a/Code/Tools/ProjectManager/Source/LinkWidget.h
+++ b/Code/Tools/ProjectManager/Source/LinkWidget.h
@@ -26,10 +26,16 @@ namespace O3DE::ProjectManager
class LinkLabel
: public QLabel
{
+ Q_OBJECT // AUTOMOC
+
public:
- LinkLabel(const QString& text, const QUrl& url = {}, QWidget* parent = nullptr);
+ LinkLabel(const QString& text = {}, const QUrl& url = {}, QWidget* parent = nullptr);
void SetUrl(const QUrl& url);
+
+ signals:
+ void clicked();
+
private:
void mousePressEvent(QMouseEvent* event) override;
void enterEvent(QEvent* event) override;
diff --git a/Code/Tools/ProjectManager/Source/PythonBindings.cpp b/Code/Tools/ProjectManager/Source/PythonBindings.cpp
index e925c81032..2c2c143845 100644
--- a/Code/Tools/ProjectManager/Source/PythonBindings.cpp
+++ b/Code/Tools/ProjectManager/Source/PythonBindings.cpp
@@ -426,7 +426,7 @@ namespace O3DE::ProjectManager
{
// required
gemInfo.m_name = Py_To_String(data["Name"]);
- gemInfo.m_uuid = AZ::Uuid(Py_To_String(data["Uuid"]));
+ gemInfo.m_uuid = AZ::Uuid(Py_To_String(data["Uuid"]));
// optional
gemInfo.m_displayName = Py_To_String_Optional(data, "DisplayName", gemInfo.m_name);
@@ -437,7 +437,8 @@ namespace O3DE::ProjectManager
{
for (auto dependency : data["Dependencies"])
{
- gemInfo.m_dependingGemUuids.push_back(Py_To_String(dependency["Uuid"]));
+ const AZ::Uuid uuid = Py_To_String(dependency["Uuid"]);
+ gemInfo.m_dependingGemUuids.push_back(uuid.ToString().c_str());
}
}
if (data.contains("Tags"))
diff --git a/Code/Tools/ProjectManager/project_manager.qrc b/Code/Tools/ProjectManager/project_manager.qrc
index 3c23bc24ff..f36633142f 100644
--- a/Code/Tools/ProjectManager/project_manager.qrc
+++ b/Code/Tools/ProjectManager/project_manager.qrc
@@ -9,6 +9,8 @@
Resources/iOS.svg
Resources/Linux.svg
Resources/macOS.svg
+ Resources/ArrowDownLine.svg
+ Resources/ArrowUpLine.svg
Resources/Backgrounds/FirstTimeBackgroundImage.jpg
diff --git a/Code/Tools/SceneAPI/FbxSceneBuilder/DllMain.cpp b/Code/Tools/SceneAPI/FbxSceneBuilder/DllMain.cpp
index 3dc14814de..d2818f3653 100644
--- a/Code/Tools/SceneAPI/FbxSceneBuilder/DllMain.cpp
+++ b/Code/Tools/SceneAPI/FbxSceneBuilder/DllMain.cpp
@@ -41,18 +41,6 @@ namespace AZ
static AZ::SceneAPI::FbxSceneImporter::FbxImportRequestHandler* g_fbxImporter = nullptr;
static AZStd::vector g_componentDescriptors;
- void Initialize()
- {
- // Currently it's still needed to explicitly create an instance of this instead of letting
- // it be a normal component. This is because ResourceCompilerScene needs to return
- // the list of available extensions before it can start the application.
- if (!g_fbxImporter)
- {
- g_fbxImporter = aznew AZ::SceneAPI::FbxSceneImporter::FbxImportRequestHandler();
- g_fbxImporter->Activate();
- }
- }
-
void Reflect(AZ::SerializeContext* /*context*/)
{
// Descriptor registration is done in Reflect instead of Initialize because the ResourceCompilerScene initializes the libraries before
@@ -64,6 +52,7 @@ namespace AZ
{
// Global importer and behavior
g_componentDescriptors.push_back(FbxSceneBuilder::FbxImporter::CreateDescriptor());
+ g_componentDescriptors.push_back(FbxSceneImporter::FbxImportRequestHandler::CreateDescriptor());
// Node and attribute importers
g_componentDescriptors.push_back(AssImpBitangentStreamImporter::CreateDescriptor());
@@ -125,7 +114,6 @@ namespace AZ
extern "C" AZ_DLL_EXPORT void InitializeDynamicModule(void* env)
{
AZ::Environment::Attach(static_cast(env));
- AZ::SceneAPI::FbxSceneBuilder::Initialize();
}
extern "C" AZ_DLL_EXPORT void Reflect(AZ::SerializeContext* context)
{
diff --git a/Code/Tools/SceneAPI/FbxSceneBuilder/FbxImportRequestHandler.cpp b/Code/Tools/SceneAPI/FbxSceneBuilder/FbxImportRequestHandler.cpp
index 155209f1b5..a43f1e16b8 100644
--- a/Code/Tools/SceneAPI/FbxSceneBuilder/FbxImportRequestHandler.cpp
+++ b/Code/Tools/SceneAPI/FbxSceneBuilder/FbxImportRequestHandler.cpp
@@ -10,12 +10,16 @@
*
*/
+#include
+#include
#include
-#include
+#include
+#include
+#include
+#include
#include
#include
#include
-#include
namespace AZ
{
@@ -23,10 +27,25 @@ namespace AZ
{
namespace FbxSceneImporter
{
- const char* FbxImportRequestHandler::s_extension = ".fbx";
+ void SceneImporterSettings::Reflect(AZ::ReflectContext* context)
+ {
+ if (auto serializeContext = azrtti_cast(context); serializeContext)
+ {
+ serializeContext->Class()
+ ->Version(1)
+ ->Field("SupportedFileTypeExtensions", &SceneImporterSettings::m_supportedFileTypeExtensions);
+ }
+ }
void FbxImportRequestHandler::Activate()
{
+ auto settingsRegistry = AZ::SettingsRegistry::Get();
+
+ if (settingsRegistry)
+ {
+ settingsRegistry->GetObject(m_settings, "/O3DE/SceneAPI/AssetImporter");
+ }
+
BusConnect();
}
@@ -37,21 +56,29 @@ namespace AZ
void FbxImportRequestHandler::Reflect(ReflectContext* context)
{
+ SceneImporterSettings::Reflect(context);
+
SerializeContext* serializeContext = azrtti_cast(context);
if (serializeContext)
{
- serializeContext->Class()->Version(1);
+ serializeContext->Class()->Version(1)->Attribute(
+ AZ::Edit::Attributes::SystemComponentTags,
+ AZStd::vector({AssetBuilderSDK::ComponentTags::AssetBuilder}));
+
}
}
void FbxImportRequestHandler::GetSupportedFileExtensions(AZStd::unordered_set& extensions)
{
- extensions.insert(s_extension);
+ extensions.insert(m_settings.m_supportedFileTypeExtensions.begin(), m_settings.m_supportedFileTypeExtensions.end());
}
Events::LoadingResult FbxImportRequestHandler::LoadAsset(Containers::Scene& scene, const AZStd::string& path, const Uuid& guid, [[maybe_unused]] RequestingApplication requester)
{
- if (!AzFramework::StringFunc::Path::IsExtension(path.c_str(), s_extension))
+ AZStd::string extension;
+ StringFunc::Path::GetExtension(path.c_str(), extension);
+
+ if (!m_settings.m_supportedFileTypeExtensions.contains(extension))
{
return Events::LoadingResult::Ignored;
}
@@ -73,6 +100,11 @@ namespace AZ
return Events::LoadingResult::AssetFailure;
}
}
+
+ void FbxImportRequestHandler::GetProvidedServices(ComponentDescriptor::DependencyArrayType& provided)
+ {
+ provided.emplace_back(AZ_CRC_CE("AssetImportRequestHandler"));
+ }
} // namespace Import
} // namespace SceneAPI
} // namespace AZ
diff --git a/Code/Tools/SceneAPI/FbxSceneBuilder/FbxImportRequestHandler.h b/Code/Tools/SceneAPI/FbxSceneBuilder/FbxImportRequestHandler.h
index 8b33051f1e..12c7c6f877 100644
--- a/Code/Tools/SceneAPI/FbxSceneBuilder/FbxImportRequestHandler.h
+++ b/Code/Tools/SceneAPI/FbxSceneBuilder/FbxImportRequestHandler.h
@@ -21,12 +21,21 @@ namespace AZ
{
namespace FbxSceneImporter
{
+ struct SceneImporterSettings
+ {
+ AZ_TYPE_INFO(SceneImporterSettings, "{8BB6C7AD-BF99-44DC-9DA1-E7AD3F03DC10}");
+
+ static void Reflect(AZ::ReflectContext* context);
+
+ AZStd::unordered_set m_supportedFileTypeExtensions;
+ };
+
class FbxImportRequestHandler
- : public SceneCore::BehaviorComponent
+ : public AZ::Component
, public Events::AssetImportRequestBus::Handler
{
public:
- AZ_COMPONENT(FbxImportRequestHandler, "{9F4B189C-0A96-4F44-A5F0-E087FF1561F8}", SceneCore::BehaviorComponent);
+ AZ_COMPONENT(FbxImportRequestHandler, "{9F4B189C-0A96-4F44-A5F0-E087FF1561F8}");
~FbxImportRequestHandler() override = default;
@@ -38,8 +47,13 @@ namespace AZ
Events::LoadingResult LoadAsset(Containers::Scene& scene, const AZStd::string& path, const Uuid& guid,
RequestingApplication requester) override;
+ static void GetProvidedServices(ComponentDescriptor::DependencyArrayType& provided);
+
private:
- static const char* s_extension;
+
+ SceneImporterSettings m_settings;
+
+ static constexpr const char* SettingsFilename = "AssetImporterSettings.json";
};
} // namespace FbxSceneImporter
} // namespace SceneAPI
diff --git a/Gems/AWSClientAuth/cdk/cognito/cognito_user_pool.py b/Gems/AWSClientAuth/cdk/cognito/cognito_user_pool.py
index a903217f40..f0a2acf208 100755
--- a/Gems/AWSClientAuth/cdk/cognito/cognito_user_pool.py
+++ b/Gems/AWSClientAuth/cdk/cognito/cognito_user_pool.py
@@ -76,7 +76,7 @@ class CognitoUserPool:
scope,
'CognitoUserPoolId',
description="Cognito User pool id",
- value=self._user_pool.attr_provider_name)
+ value=self._user_pool.ref)
core.CfnOutput(
scope,
diff --git a/Gems/Atom/Asset/Shader/Code/Source/Editor/AzslBuilder.cpp b/Gems/Atom/Asset/Shader/Code/Source/Editor/AzslBuilder.cpp
index 0ce7109067..668d6866d3 100644
--- a/Gems/Atom/Asset/Shader/Code/Source/Editor/AzslBuilder.cpp
+++ b/Gems/Atom/Asset/Shader/Code/Source/Editor/AzslBuilder.cpp
@@ -185,7 +185,8 @@ namespace AZ
// we can't use a temporary folder because CreateJobs API does not warrant side effects, and does not prepare a temp folder.
// we can't use the OS temp folder anyway, because many includes (eg #include "../RPI/Shadow.h") are relative and will only work from the original location
AZStd::string prependedPath = ShaderBuilderUtility::DumpAzslPrependedCode(
- BuilderName, prependedAzslSourceCode, originalLocation, ShaderBuilderUtility::ExtractStemName(fullPath.c_str()), shaderPlatformInterface->GetAPIName().GetStringView());
+ BuilderName, prependedAzslSourceCode, originalLocation, ShaderBuilderUtility::ExtractStemName(fullPath.c_str()),
+ shaderPlatformInterface->GetAPIName().GetStringView());
// run mcpp
PreprocessorData preprocessorData = PreprocessSource(prependedPath, fullPath, buildOptions.m_preprocessorSettings);
jobDescriptor.m_jobParameters[(u32)JobParameterIndices::PreprocessorError] = preprocessorData.diagnostics; // save for ProcessJob
@@ -221,7 +222,7 @@ namespace AZ
}
// eg: ("D:/p/x.a", "D:/p/x.b") -> yes
- static bool HasSameStemName(const AZStd::string& lhsPath, const AZStd::string& rhsPath)
+ static bool HasSameFileName(const AZStd::string& lhsPath, const AZStd::string& rhsPath)
{
using namespace StringFunc::Path;
AZStd::string stem1;
@@ -307,7 +308,8 @@ namespace AZ
buildOptions.m_compilerArguments.Merge(shaderAssetSource.m_compiler);
// Earlier, we declared a job dependency on the .azsl's job, let's access the produced assets:
- uint32_t subId = ShaderBuilderUtility::MakeAzslBuildProductSubId(RPI::ShaderAssetSubId::GeneratedSource, platformInterface->GetAPIType());
+ uint32_t subId = ShaderBuilderUtility::MakeAzslBuildProductSubId(
+ RPI::ShaderAssetSubId::GeneratedHlslSource, platformInterface->GetAPIType());
auto assetIdOutcome = RPI::AssetUtils::MakeAssetId(inputFiles->m_azslSourceFullPath, subId);
AZ_Warning(BuilderName, assetIdOutcome.IsSuccess(), "Product of dependency %s not found: this is an oddity but build can continue.", inputFiles->m_azslSourceFullPath.c_str());
if (assetIdOutcome.IsSuccess())
@@ -325,7 +327,7 @@ namespace AZ
AZ_TracePrintf(BuilderName, "Product output already built by %s is not reusable because of incompatible azslc CompilerHints: launching independent build", inputFiles->m_azslSourceFullPath.c_str());
}
- if (HasSameStemName(fullSourcePath, inputFiles->m_azslSourceFullPath))
+ if (HasSameFileName(fullSourcePath, inputFiles->m_azslSourceFullPath))
{
// let's add a "distinguisher" to the names of the outproduct artifacts of this build round.*
// Because otherwise the asset processor is not going to accept an overwrite of the ones output by the .azsl job
diff --git a/Gems/Atom/Asset/Shader/Code/Source/Editor/AzslCompiler.cpp b/Gems/Atom/Asset/Shader/Code/Source/Editor/AzslCompiler.cpp
index 417a5b4a88..e3b482742b 100644
--- a/Gems/Atom/Asset/Shader/Code/Source/Editor/AzslCompiler.cpp
+++ b/Gems/Atom/Asset/Shader/Code/Source/Editor/AzslCompiler.cpp
@@ -26,6 +26,7 @@
#include
#include
+#include // [GFX TODO] Remove when [ATOM-15472]
#include
#include
@@ -122,7 +123,7 @@ namespace AZ
namespace SubProducts = ShaderBuilderUtility::AzslSubProducts;
- Outcome AzslCompiler::EmitFullData(const AZStd::string& parameters, const AZStd::string& outputFile /* = ""*/) const
+ Outcome AzslCompiler::EmitFullData(const AZStd::string& parameters, const AZStd::string& outputFile /* = ""*/, const char * addSuffix) const
{
bool success = Compile("--full " + parameters, outputFile);
if (!success)
@@ -133,11 +134,22 @@ namespace AZ
SubProducts::Paths productPaths = SubProducts::Paths(SubProducts::Paths::capacity());
for (auto subProduct : SubProducts::SuffixListMembers)
{
- productPaths[subProduct.m_value] = outputFile.empty() ? m_inputFilePath : outputFile; // that's a reproduction of azslc's behavior (no "-o" = input name is used)
- AzFramework::StringFunc::Path::ReplaceExtension(productPaths[subProduct.m_value], subProduct.m_string.data());
+ AZStd::string subProductFilePath = outputFile.empty() ? m_inputFilePath : outputFile; // that's a reproduction of azslc's behavior (no "-o" = input name is used)
+ AzFramework::StringFunc::Path::ReplaceExtension(subProductFilePath, subProduct.m_string.data());
// append .json if it's one of those subs:
auto listOfJsons = { SubProducts::ia, SubProducts::om, SubProducts::srg, SubProducts::options, SubProducts::bindingdep };
- productPaths[subProduct.m_value] += AZStd::any_of(AZ_BEGIN_END(listOfJsons), [&](auto v) { return v == subProduct.m_value; }) ? ".json" : "";
+ subProductFilePath += AZStd::any_of(AZ_BEGIN_END(listOfJsons), [&](auto v) { return v == subProduct.m_value; }) ? ".json" : "";
+
+ // [GFX TODO] Remove when [ATOM-15472]
+ if (addSuffix)
+ {
+ // Rename the product file.
+ AZStd::string finalSubProductFilePath = AZStd::string::format("%s%s", subProductFilePath.c_str(), addSuffix);
+ AZ::IO::Move(subProductFilePath.c_str(), finalSubProductFilePath.c_str());
+ subProductFilePath = finalSubProductFilePath;
+ }
+
+ productPaths[subProduct.m_value] = subProductFilePath;
}
productPaths[SubProducts::azslin] = GetInputFilePath(); // post-fixup this one after the loop, because it's not an output of azslc, it's an output of the builder though.
return { productPaths };
diff --git a/Gems/Atom/Asset/Shader/Code/Source/Editor/AzslCompiler.h b/Gems/Atom/Asset/Shader/Code/Source/Editor/AzslCompiler.h
index cb30da068f..8da03cd6e5 100644
--- a/Gems/Atom/Asset/Shader/Code/Source/Editor/AzslCompiler.h
+++ b/Gems/Atom/Asset/Shader/Code/Source/Editor/AzslCompiler.h
@@ -38,8 +38,9 @@ namespace AZ
//! @param inputFilePath The target input file to compile. Should be a valid AZSL file with no preprocessing directives.
AzslCompiler(const AZStd::string& inputFilePath);
+ //! [GFX TODO] Remove @addSuffix when [ATOM-15472]
//! compile with --full and generate all .json files
- Outcome EmitFullData(const AZStd::string& parameters, const AZStd::string& outputFile = "") const;
+ Outcome EmitFullData(const AZStd::string& parameters, const AZStd::string& outputFile = "", const char * addSuffix = nullptr) const;
//! compile to HLSL independently
bool EmitShader(AZ::IO::GenericStream& outputStream, const AZStd::string& extraCompilerParams) const;
//! compile with --ia independently and populate document @output
diff --git a/Gems/Atom/Asset/Shader/Code/Source/Editor/AzslData.h b/Gems/Atom/Asset/Shader/Code/Source/Editor/AzslData.h
index 4ea7a51fb1..303e2f355f 100644
--- a/Gems/Atom/Asset/Shader/Code/Source/Editor/AzslData.h
+++ b/Gems/Atom/Asset/Shader/Code/Source/Editor/AzslData.h
@@ -83,23 +83,41 @@ namespace AZ
AZStd::string m_azslFileName; //!< Name for the source .azsl file
};
- struct AzslCodeTopData
- {
- SrgDataContainer m_srgData;
- AzslFunctions m_functions;
- StructContainer m_structs;
- RootConstantData m_rootConstantData;
- };
+ //! DEPRECATED [ATOM-15472]
+ //! This class is used to collect all the json files produced by the compilation
+ //! of an AZSL file as objects.
struct AzslData
{
AzslData(const AZStd::shared_ptr& a_sources) : m_sources(a_sources) { }
AZStd::shared_ptr m_sources;
AZStd::string m_preprocessedFullPath; // Full path to a preprocessed version of the original AZSL file
- AZStd::string m_shaderCodePrefix; // AssetProcessor generated shader code which is added to the
- // AZSLc emitted code prior to invoking the native shader compiler
- AzslCodeTopData m_topData;
+ AZStd::string m_shaderCodePrefix; // AssetProcessor generated shader code which is added to the
+ // AZSLc emitted code prior to invoking the native shader compiler
+
+ SrgDataContainer m_srgData;
+ AzslFunctions m_functions;
+ StructContainer m_structs;
+ RootConstantData m_rootConstantData;
+ };
+
+ //! This class is used to collect all the json files produced by the compilation
+ //! of an AZSL file as objects.
+ struct AzslData2
+ {
+ AzslData2(const AZStd::shared_ptr& a_sources)
+ : m_sources(a_sources)
+ {
+ }
+
+ AZStd::shared_ptr m_sources;
+ AZStd::string m_preprocessedFullPath; // Full path to a preprocessed version of the original AZSL file
+
+ SrgDataContainer m_srgData;
+ AzslFunctions m_functions;
+ StructContainer m_structs;
+ RootConstantData m_rootConstantData;
};
} // ShaderBuilder
} // AZ
diff --git a/Gems/Atom/Asset/Shader/Code/Source/Editor/AzslShaderBuilderSystemComponent.cpp b/Gems/Atom/Asset/Shader/Code/Source/Editor/AzslShaderBuilderSystemComponent.cpp
index a0f9f7db22..fb4d370621 100644
--- a/Gems/Atom/Asset/Shader/Code/Source/Editor/AzslShaderBuilderSystemComponent.cpp
+++ b/Gems/Atom/Asset/Shader/Code/Source/Editor/AzslShaderBuilderSystemComponent.cpp
@@ -86,7 +86,7 @@ namespace AZ
// Register AZSL's compilation products Builder
AssetBuilderSDK::AssetBuilderDesc azslBuilderDescriptor;
azslBuilderDescriptor.m_name = "AZSL Builder";
- azslBuilderDescriptor.m_version = 7; // LKG Merge
+ azslBuilderDescriptor.m_version = 8; // ATOM-15276
// register all extensions thay may carry azsl code. header. main shader. or SRG
azslBuilderDescriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern(AZStd::string::format("*.%s", RPI::ShaderSourceData::Extension), AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard));
azslBuilderDescriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern("*.azsl", AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard));
@@ -102,7 +102,7 @@ namespace AZ
// Register Shader Resource Group Layout Builder
AssetBuilderSDK::AssetBuilderDesc srgLayoutBuilderDescriptor;
srgLayoutBuilderDescriptor.m_name = "Shader Resource Group Layout Builder";
- srgLayoutBuilderDescriptor.m_version = 54; // Enable Null Rhi for AutomatedTesting
+ srgLayoutBuilderDescriptor.m_version = 55; // ATOM-15276
srgLayoutBuilderDescriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern("*.azsl", AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard));
srgLayoutBuilderDescriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern("*.azsli", AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard));
@@ -118,7 +118,7 @@ namespace AZ
// Register Shader Asset Builder
AssetBuilderSDK::AssetBuilderDesc shaderAssetBuilderDescriptor;
shaderAssetBuilderDescriptor.m_name = "Shader Asset Builder";
- shaderAssetBuilderDescriptor.m_version = 98; // Enable Null Rhi for AutomatedTesting
+ shaderAssetBuilderDescriptor.m_version = 99; // ATOM-15276
// .shader file changes trigger rebuilds
shaderAssetBuilderDescriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern( AZStd::string::format("*.%s", RPI::ShaderSourceData::Extension), AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard));
shaderAssetBuilderDescriptor.m_busId = azrtti_typeid();
@@ -133,7 +133,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 = 19; // Enable Null Rhi for AutomatedTesting
+ shaderVariantAssetBuilderDescriptor.m_version = 20; // ATOM-15276
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);
@@ -145,7 +145,7 @@ namespace AZ
// Register Precompiled Shader Builder
AssetBuilderSDK::AssetBuilderDesc precompiledShaderBuilderDescriptor;
precompiledShaderBuilderDescriptor.m_name = "Precompiled Shader Builder";
- precompiledShaderBuilderDescriptor.m_version = 7; // ATOM-14780
+ precompiledShaderBuilderDescriptor.m_version = 8; // ATOM-15276
precompiledShaderBuilderDescriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern(AZStd::string::format("*.%s", AZ::PrecompiledShaderBuilder::Extension), AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard));
precompiledShaderBuilderDescriptor.m_busId = azrtti_typeid();
precompiledShaderBuilderDescriptor.m_createJobFunction = AZStd::bind(&PrecompiledShaderBuilder::CreateJobs, &m_precompiledShaderBuilder, AZStd::placeholders::_1, AZStd::placeholders::_2);
@@ -153,6 +153,43 @@ namespace AZ
m_precompiledShaderBuilder.BusConnect(precompiledShaderBuilderDescriptor.m_busId);
AssetBuilderSDK::AssetBuilderBus::Broadcast(&AssetBuilderSDK::AssetBuilderBus::Handler::RegisterBuilderInformation, precompiledShaderBuilderDescriptor);
+
+ // Register Shader Asset Builder 2
+ AssetBuilderSDK::AssetBuilderDesc shaderAssetBuilder2Descriptor;
+ shaderAssetBuilder2Descriptor.m_name = "Shader Asset Builder 2";
+ shaderAssetBuilder2Descriptor.m_version = 1; // ATOM-15276
+ // .shader2 file changes trigger rebuilds
+ shaderAssetBuilder2Descriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern(
+ AZStd::string::format("*.%s", RPI::ShaderSourceData::Extension2),
+ AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard));
+ shaderAssetBuilder2Descriptor.m_busId = azrtti_typeid();
+ shaderAssetBuilder2Descriptor.m_createJobFunction =
+ AZStd::bind(&ShaderAssetBuilder2::CreateJobs, &m_shaderAssetBuilder2, AZStd::placeholders::_1, AZStd::placeholders::_2);
+ shaderAssetBuilder2Descriptor.m_processJobFunction =
+ AZStd::bind(&ShaderAssetBuilder2::ProcessJob, &m_shaderAssetBuilder2, AZStd::placeholders::_1, AZStd::placeholders::_2);
+
+ m_shaderAssetBuilder2.BusConnect(shaderAssetBuilder2Descriptor.m_busId);
+ AssetBuilderSDK::AssetBuilderBus::Broadcast(
+ &AssetBuilderSDK::AssetBuilderBus::Handler::RegisterBuilderInformation, shaderAssetBuilder2Descriptor);
+
+ // Register Shader Variant Asset Builder 2
+ AssetBuilderSDK::AssetBuilderDesc shaderVariantAssetBuilder2Descriptor;
+ shaderVariantAssetBuilder2Descriptor.m_name = "Shader Variant Asset Builder 2";
+ // 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".
+ shaderVariantAssetBuilder2Descriptor.m_version = 1; // ATOM-15276
+ shaderVariantAssetBuilder2Descriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern(
+ AZStd::string::format("*.%s", RPI::ShaderVariantListSourceData::Extension2),
+ AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard));
+ shaderVariantAssetBuilder2Descriptor.m_busId = azrtti_typeid();
+ shaderVariantAssetBuilder2Descriptor.m_createJobFunction = AZStd::bind(
+ &ShaderVariantAssetBuilder2::CreateJobs, &m_shaderVariantAssetBuilder2, AZStd::placeholders::_1, AZStd::placeholders::_2);
+ shaderVariantAssetBuilder2Descriptor.m_processJobFunction = AZStd::bind(
+ &ShaderVariantAssetBuilder2::ProcessJob, &m_shaderVariantAssetBuilder2, AZStd::placeholders::_1, AZStd::placeholders::_2);
+
+ m_shaderVariantAssetBuilder2.BusConnect(shaderVariantAssetBuilder2Descriptor.m_busId);
+ AssetBuilderSDK::AssetBuilderBus::Broadcast(
+ &AssetBuilderSDK::AssetBuilderBus::Handler::RegisterBuilderInformation, shaderVariantAssetBuilder2Descriptor);
}
void AzslShaderBuilderSystemComponent::Deactivate()
@@ -161,6 +198,8 @@ namespace AZ
m_srgLayoutBuilder.BusDisconnect();
m_shaderVariantAssetBuilder.BusDisconnect();
m_precompiledShaderBuilder.BusDisconnect();
+ m_shaderAssetBuilder2.BusDisconnect();
+ m_shaderVariantAssetBuilder2.BusDisconnect();
RHI::ShaderPlatformInterfaceRegisterBus::Handler::BusDisconnect();
ShaderPlatformInterfaceRequestBus::Handler::BusDisconnect();
diff --git a/Gems/Atom/Asset/Shader/Code/Source/Editor/AzslShaderBuilderSystemComponent.h b/Gems/Atom/Asset/Shader/Code/Source/Editor/AzslShaderBuilderSystemComponent.h
index 9d685c0d12..2f881f45d2 100644
--- a/Gems/Atom/Asset/Shader/Code/Source/Editor/AzslShaderBuilderSystemComponent.h
+++ b/Gems/Atom/Asset/Shader/Code/Source/Editor/AzslShaderBuilderSystemComponent.h
@@ -18,12 +18,14 @@
#include
-#include
-#include
-#include
-#include
-#include
-#include
+#include "AzslBuilder.h"
+#include "SrgLayoutBuilder.h"
+#include "ShaderAssetBuilder.h"
+#include "ShaderVariantAssetBuilder.h"
+#include "PrecompiledShaderBuilder.h"
+#include "ShaderPlatformInterfaceRequest.h"
+#include "ShaderAssetBuilder2.h"
+#include "ShaderVariantAssetBuilder2.h"
namespace AZ
{
@@ -71,6 +73,8 @@ namespace AZ
ShaderAssetBuilder m_shaderAssetBuilder;
ShaderVariantAssetBuilder m_shaderVariantAssetBuilder;
PrecompiledShaderBuilder m_precompiledShaderBuilder;
+ ShaderAssetBuilder2 m_shaderAssetBuilder2;
+ ShaderVariantAssetBuilder2 m_shaderVariantAssetBuilder2;
/// Contains the ShaderPlatformInterface for all registered RHIs
AZStd::unordered_map m_shaderPlatformInterfaces;
diff --git a/Gems/Atom/Asset/Shader/Code/Source/Editor/CommonFiles/GlobalBuildOptions.cpp b/Gems/Atom/Asset/Shader/Code/Source/Editor/CommonFiles/GlobalBuildOptions.cpp
index c9d23aecd1..5f2acf251c 100644
--- a/Gems/Atom/Asset/Shader/Code/Source/Editor/CommonFiles/GlobalBuildOptions.cpp
+++ b/Gems/Atom/Asset/Shader/Code/Source/Editor/CommonFiles/GlobalBuildOptions.cpp
@@ -62,7 +62,7 @@ namespace AZ
}
}
- GlobalBuildOptions ReadBuildOptions(const char* builderName)
+ GlobalBuildOptions ReadBuildOptions(const char* builderName, const char* optionalIncludeFolder)
{
GlobalBuildOptions output;
// try to parse some config file for eventual additional options
@@ -79,7 +79,7 @@ namespace AZ
{
AZ_TracePrintf(builderName, "config file [%s] not found.", globalBuildOption.c_str());
}
- InitializePreprocessorOptions(output.m_preprocessorSettings, builderName);
+ InitializePreprocessorOptions(output.m_preprocessorSettings, builderName, optionalIncludeFolder);
return output;
}
}
diff --git a/Gems/Atom/Asset/Shader/Code/Source/Editor/CommonFiles/GlobalBuildOptions.h b/Gems/Atom/Asset/Shader/Code/Source/Editor/CommonFiles/GlobalBuildOptions.h
index f55d395609..9e85f5bcdd 100644
--- a/Gems/Atom/Asset/Shader/Code/Source/Editor/CommonFiles/GlobalBuildOptions.h
+++ b/Gems/Atom/Asset/Shader/Code/Source/Editor/CommonFiles/GlobalBuildOptions.h
@@ -33,6 +33,9 @@ namespace AZ
RHI::ShaderCompilerArguments m_compilerArguments;
};
- GlobalBuildOptions ReadBuildOptions(const char* builderName);
+ //! Reads the global options used when compiling shaders. The options are defined in /Config/shader_global_build_options.json
+ //! @param builderName: A string with the name of the builder calling this API. Used for trace debugging.
+ //! @param optionalIncludeFolder: An additional directory to add to the list of include folders for the C-preprocessor.
+ GlobalBuildOptions ReadBuildOptions(const char* builderName, const char* optionalIncludeFolder = nullptr);
}
}
diff --git a/Gems/Atom/Asset/Shader/Code/Source/Editor/CommonFiles/Preprocessor.cpp b/Gems/Atom/Asset/Shader/Code/Source/Editor/CommonFiles/Preprocessor.cpp
index 84a95b2c54..1e471f8644 100644
--- a/Gems/Atom/Asset/Shader/Code/Source/Editor/CommonFiles/Preprocessor.cpp
+++ b/Gems/Atom/Asset/Shader/Code/Source/Editor/CommonFiles/Preprocessor.cpp
@@ -58,6 +58,37 @@ namespace AZ
}
}
+ void PreprocessorOptions::RemovePredefinedMacros(const AZStd::vector& macroNames)
+ {
+ m_predefinedMacros.erase(
+ AZStd::remove_if(
+ m_predefinedMacros.begin(), m_predefinedMacros.end(),
+ [&](const AZStd::string& predefinedMacro)
+ {
+ for (const auto& macroName : macroNames)
+ {
+ // Haystack, needle, bCaseSensitive
+ if (!AzFramework::StringFunc::StartsWith(predefinedMacro, macroName, true))
+ {
+ return false;
+ }
+ // If found, let's make sure it is not just a substring.
+ if (predefinedMacro.size() == macroName.size())
+ {
+ return true;
+ }
+ // The predefinedMacro can be a string like "macro=value". If we find '=' it is a match.
+ if (predefinedMacro.c_str()[macroName.size()] == '=')
+ {
+ return true;
+ }
+ return false;
+ }
+ return false;
+ }),
+ m_predefinedMacros.end());
+ }
+
//! Binder helper to Matsui C-Pre-Processor library
class McppBinder
{
@@ -286,7 +317,8 @@ namespace AZ
}
// populate options with scan folders and contents of parsing shader_global_build_options.json
- void InitializePreprocessorOptions(PreprocessorOptions& options, [[maybe_unused]] const char* builderName)
+ void InitializePreprocessorOptions(
+ PreprocessorOptions& options, [[maybe_unused]] const char* builderName, const char* optionalIncludeFolder)
{
AZ_TraceContext("Init include-paths lookup options", "preprocessor");
@@ -303,6 +335,10 @@ namespace AZ
// Add the project path to list of include paths
AZ::IO::FixedMaxPathString projectPath = AZ::Utils::GetProjectPath();
scanFoldersSet.emplace(projectPath.c_str(), projectPath.size());
+ if (optionalIncludeFolder)
+ {
+ scanFoldersSet.emplace(optionalIncludeFolder, strnlen(optionalIncludeFolder, AZ::IO::MaxPathLength));
+ }
// but while we transfer to the set, we're going to keep only folders where +/ShaderLib exists
for (AZStd::string folder : scanFoldersVector)
{
diff --git a/Gems/Atom/Asset/Shader/Code/Source/Editor/CommonFiles/Preprocessor.h b/Gems/Atom/Asset/Shader/Code/Source/Editor/CommonFiles/Preprocessor.h
index 7718dba6c4..b5fbf438a3 100644
--- a/Gems/Atom/Asset/Shader/Code/Source/Editor/CommonFiles/Preprocessor.h
+++ b/Gems/Atom/Asset/Shader/Code/Source/Editor/CommonFiles/Preprocessor.h
@@ -47,9 +47,13 @@ namespace AZ
//! folders are relative to the dev folder of the project
AZStd::vector m_projectIncludePaths;
- //! passed as -D macro1[=value1] -D macro2 ...
+ //! Each string is of the type "name[=value]"
+ //! passed as -Dmacro1[=value1] -Dmacro2 ... to MCPP.
AZStd::vector m_predefinedMacros;
+ //! Removes all macros from @m_predefinedMacros that appear in @macroNames
+ void RemovePredefinedMacros(const AZStd::vector& macroNames);
+
//! if needed, we may add configurations like
//! "keep comments" or "don't predefine non-standard macros"
//! or "output diagnostics to std.err" or "enable digraphs/trigraphs"...
@@ -59,7 +63,10 @@ namespace AZ
//! It will populate your option with a default base of include folders given by the Asset Processor scan folders.
//! This is going to look for a Config/shader_global_build_options.json in one of the scan folders
//! (that file can specify additional include files and preprocessor macros).
- void InitializePreprocessorOptions(PreprocessorOptions& options, const char* builderName);
+ //! @param options: Outout parameter, will contain the preprocessor options.
+ //! @param builderName: Used for debugging.
+ //! @param optionalIncludeFolder: If not null, will be added to the list of include folders for the c-preprocessor in @options.
+ void InitializePreprocessorOptions(PreprocessorOptions& options, const char* builderName, const char* optionalIncludeFolder = nullptr);
/**
* Runs the preprocessor on the given source file path, and stores results in outputData.
diff --git a/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderAssetBuilder.cpp b/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderAssetBuilder.cpp
index d6197ff279..8d491d5d4d 100644
--- a/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderAssetBuilder.cpp
+++ b/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderAssetBuilder.cpp
@@ -166,17 +166,6 @@ namespace AZ
response.m_result = AssetBuilderSDK::CreateJobsResultCode::Success;
}
- static uint32_t GetRootVariantAssetSubId(const RHI::ShaderPlatformInterface& shaderPlatformInterface)
- {
- //The 2 Most significant bits encode the the RHI::API unique index.
- const uint32_t apiUniqueIndex = shaderPlatformInterface.GetAPIUniqueIndex();
- AZ_Assert(apiUniqueIndex <= RHI::Limits::APIType::PerPlatformApiUniqueIndexMax,
- "Invalid api unique index [%u] from ShaderPlatformInterface [%s]", apiUniqueIndex, shaderPlatformInterface.GetAPIName().GetCStr());
- const uint32_t rhiApiSubId = apiUniqueIndex << 30;
- const uint32_t productSubID = rhiApiSubId | static_cast(RPI::ShaderAssetSubId::RootShaderVariantAsset);
- return productSubID;
- }
-
static AssetBuilderSDK::ProcessJobResultCode CompileForAPI(
const ShaderBuilderUtility::AzslSubProducts::Paths& pathOfProductFiles,
RPI::ShaderAssetCreator& shaderAssetCreator,
@@ -201,7 +190,7 @@ namespace AZ
if (shaderSourceDataDescriptor.m_programSettings.m_entryPoints.empty())
{
AZ_TracePrintf(ShaderAssetBuilderName, "ProgramSettings do not specify entry points, will use GetDefaultEntryPointsFromShader()\n");
- ShaderVariantAssetBuilder::GetDefaultEntryPointsFromAzslData(azslData, shaderEntryPoints);
+ ShaderBuilderUtility::GetDefaultEntryPointsFromFunctionDataList(azslData.m_functions, shaderEntryPoints);
}
else
{
@@ -249,7 +238,9 @@ namespace AZ
// so the root ShaderVariantAsset is found when the ShaderAsset is deserialized.
AZStd::string fullSourcePath;
AzFramework::StringFunc::Path::ConstructFull(request.m_watchFolder.c_str(), request.m_sourceFile.c_str(), fullSourcePath, true);
- const uint32_t productSubID = GetRootVariantAssetSubId(*shaderPlatformInterface);
+ const uint32_t productSubID = RPI::ShaderAsset::MakeAssetProductSubId(
+ shaderPlatformInterface->GetAPIUniqueIndex(),
+ aznumeric_cast(RPI::ShaderAssetSubId::RootShaderVariantAsset));
auto assetIdOutcome = RPI::AssetUtils::MakeAssetId(fullSourcePath, productSubID);
AZ_Assert(assetIdOutcome.IsSuccess(), "Failed to get AssetId from shader %s", fullSourcePath.c_str());
const Data::AssetId variantAssetId = assetIdOutcome.TakeValue();
@@ -288,12 +279,13 @@ namespace AZ
// add byproducts as job output products:
if (variantCreationContext.m_outputByproducts)
{
+ uint32_t subProductType = aznumeric_cast(RPI::ShaderAssetSubId::GeneratedHlslSource) + 1;
for (const AZStd::string& byproduct : variantCreationContext.m_outputByproducts->m_intermediatePaths)
{
AssetBuilderSDK::JobProduct jobProduct;
jobProduct.m_productFileName = byproduct;
jobProduct.m_productAssetType = Uuid::CreateName("DebugInfoByProduct-PdbOrDxilTxt");
- jobProduct.m_productSubID = ShaderBuilderUtility::MakeDebugByproductSubId(shaderPlatformInterface->GetAPIType(), byproduct);
+ jobProduct.m_productSubID = RPI::ShaderAsset::MakeAssetProductSubId(shaderPlatformInterface->GetAPIUniqueIndex(), subProductType++);
response.m_outputProducts.push_back(AZStd::move(jobProduct));
}
}
@@ -305,12 +297,12 @@ namespace AZ
attributeMaps.resize(RHI::ShaderStageCount);
for (const auto& shaderEntry : shaderSourceDataDescriptor.m_programSettings.m_entryPoints)
{
- auto findId = AZStd::find_if(AZ_BEGIN_END(azslData.m_topData.m_functions), [&shaderEntry](const auto& func)
+ auto findId = AZStd::find_if(AZ_BEGIN_END(azslData.m_functions), [&shaderEntry](const auto& func)
{
return func.m_name == shaderEntry.m_name;
});
- if (findId == azslData.m_topData.m_functions.end())
+ if (findId == azslData.m_functions.end())
{
// shaderData.m_functions only contains Vertex, Fragment and Compute entries for now
// Tessellation shaders will need to be handled too
diff --git a/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderAssetBuilder2.cpp b/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderAssetBuilder2.cpp
new file mode 100644
index 0000000000..1668b57866
--- /dev/null
+++ b/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderAssetBuilder2.cpp
@@ -0,0 +1,684 @@
+/*
+* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
+* its licensors.
+*
+* For complete copyright and license terms please see the LICENSE at the root of this
+* distribution (the "License"). All use of this software is governed by the License,
+* or, if provided, by the license below or the license accompanying this file. Do not
+* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+*/
+
+#include "ShaderAssetBuilder2.h"
+
+#include
+#include
+
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+
+#include
+#include
+
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "AzslBuilder.h"
+#include "ShaderVariantAssetBuilder2.h"
+#include "ShaderBuilderUtility.h"
+#include "ShaderPlatformInterfaceRequest.h"
+#include "AtomShaderConfig.h"
+
+#include
+#include
+namespace AZ
+{
+ namespace ShaderBuilder
+ {
+ static constexpr char ShaderAssetBuilder2Name[] = "ShaderAssetBuilder2";
+ static constexpr uint32_t ShaderAssetBuildTimestampParam = 0;
+
+ void ShaderAssetBuilder2::CreateJobs(const AssetBuilderSDK::CreateJobsRequest& request, AssetBuilderSDK::CreateJobsResponse& response) const
+ {
+ AZStd::string fullPath;
+ AzFramework::StringFunc::Path::ConstructFull(request.m_watchFolder.data(), request.m_sourceFile.data(), fullPath, true);
+
+ AZ_TracePrintf(ShaderAssetBuilder2Name, "CreateJobs for Shader \"%s\"\n", fullPath.data());
+
+ // Used to synchronize versions of the ShaderAsset and ShaderVariantTreeAsset, especially during hot-reload.
+ // Note it's probably important for this to be set once outside the platform loop so every platform's ShaderAsset
+ // has the same value, because later the ShaderVariantTreeAsset job will fetch this value from the local ShaderAsset
+ // which could cross platforms (i.e. building an android ShaderVariantTreeAsset on PC would fetch the tiemstamp from
+ // the PC's ShaderAsset).
+ AZStd::sys_time_t shaderAssetBuildTimestamp = AZStd::GetTimeNowMicroSecond();
+
+ // Need to get the name of the azsl file from the .shader source asset, to be able to declare a dependency to SRG Layout Job.
+ // and the macro options to preprocess.
+ auto descriptorParseOutcome = ShaderBuilderUtility::LoadShaderDataJson(fullPath);
+ if (!descriptorParseOutcome.IsSuccess())
+ {
+ AZ_Error(
+ ShaderAssetBuilder2Name, false, "Failed to parse Shader Descriptor JSON: %s",
+ descriptorParseOutcome.GetError().c_str());
+ return;
+ }
+
+ RPI::ShaderSourceData shaderSourceData = descriptorParseOutcome.TakeValue();
+
+ AZStd::string azslFullPath;
+ ShaderBuilderUtility::GetAbsolutePathToAzslFile(fullPath, shaderSourceData.m_source, azslFullPath);
+ if (!IO::FileIOBase::GetInstance()->Exists(azslFullPath.c_str()))
+ {
+ AZ_Error(
+ ShaderAssetBuilder2Name, false, "Shader program listed as the source entry does not exist: %s.", azslFullPath.c_str());
+ response.m_result = AssetBuilderSDK::CreateJobsResultCode::Failed;
+ return;
+ }
+
+
+ GlobalBuildOptions buildOptions = ReadBuildOptions(ShaderAssetBuilder2Name);
+
+ // [GFX TODO] [ATOM-14966] In principle, based on macro definitions, included files can change per supervariant.
+ // So, the list of source asset dependencies must be collected by running MCPP on each supervariant.
+ // For now, we will run MCPP only once because CreateJobs() should be as light as possible.
+ //
+ // Regardless of the PlatformInfo and enabled ShaderPlatformInterfaces, the azsl file will be preprocessed
+ // with the sole purpose of extracting all included files. For each included file a SourceDependency will be declared.
+ PreprocessorData output;
+ buildOptions.m_compilerArguments.Merge(shaderSourceData.m_compiler);
+ PreprocessFile(azslFullPath, output, buildOptions.m_preprocessorSettings, true, true);
+ for (auto includePath : output.includedPaths)
+ {
+ // m_sourceFileDependencyList does not support paths with "." or ".." for relative lookup, but the preprocessor
+ // may produce path strings like "C:/a/b/c/../../d/file.azsli" so we have to normalize
+ AzFramework::StringFunc::Path::Normalize(includePath);
+
+ AssetBuilderSDK::SourceFileDependency includeFileDependency;
+ includeFileDependency.m_sourceFileDependencyPath = includePath;
+ response.m_sourceFileDependencyList.emplace_back(includeFileDependency);
+ }
+
+ {
+ // Add the AZSL as source dependency
+ AssetBuilderSDK::SourceFileDependency azslFileDependency;
+ azslFileDependency.m_sourceFileDependencyPath = azslFullPath;
+ response.m_sourceFileDependencyList.emplace_back(azslFileDependency);
+ }
+
+ for (const AssetBuilderSDK::PlatformInfo& platformInfo : request.m_enabledPlatforms)
+ {
+ AZ_TraceContext("For platform", platformInfo.m_identifier.data());
+
+ // Get the platform interfaces to be able to access the prepend file
+ AZStd::vector platformInterfaces = ShaderBuilderUtility::DiscoverValidShaderPlatformInterfaces(platformInfo);
+ if (platformInterfaces.empty())
+ {
+ continue;
+ }
+
+ AssetBuilderSDK::JobDescriptor jobDescriptor;
+ jobDescriptor.m_priority = 2;
+ // [GFX TODO][ATOM-2830] Set 'm_critical' back to 'false' once proper fix for Atom startup issues are in
+ jobDescriptor.m_critical = true;
+ jobDescriptor.m_jobKey = ShaderAssetBuilder2JobKey;
+ jobDescriptor.SetPlatformIdentifier(platformInfo.m_identifier.c_str());
+ jobDescriptor.m_jobParameters.emplace(ShaderAssetBuildTimestampParam, AZStd::to_string(shaderAssetBuildTimestamp));
+
+ response.m_createJobOutputs.push_back(jobDescriptor);
+ } // for all request.m_enabledPlatforms
+
+ response.m_result = AssetBuilderSDK::CreateJobsResultCode::Success;
+ }
+
+ static bool SerializeOutShaderAsset(Data::Asset shaderAsset,
+ const AZStd::string& tempDirPath,
+ AssetBuilderSDK::ProcessJobResponse& response)
+ {
+ AZStd::string shaderAssetFileName = AZStd::string::format("%s.%s", shaderAsset->GetName().GetCStr(), RPI::ShaderAsset2::Extension);
+ AZStd::string shaderAssetOutputPath;
+ AzFramework::StringFunc::Path::ConstructFull(tempDirPath.data(), shaderAssetFileName.data(), shaderAssetOutputPath, true);
+
+ if (!Utils::SaveObjectToFile(shaderAssetOutputPath, DataStream::ST_BINARY, shaderAsset.Get()))
+ {
+ AZ_Error(ShaderAssetBuilder2Name, false, "Failed to output Shader Descriptor");
+ return false;
+ }
+
+ AssetBuilderSDK::JobProduct shaderJobProduct;
+ if (!AssetBuilderSDK::OutputObject(shaderAsset.Get(), shaderAssetOutputPath, azrtti_typeid(),
+ aznumeric_cast(RPI::ShaderAsset2ProductSubId::ShaderAsset2), shaderJobProduct))
+ {
+ AZ_Error(ShaderAssetBuilder2Name, false, "Failed to output product dependencies.");
+ return false;
+ }
+ response.m_outputProducts.push_back(AZStd::move(shaderJobProduct));
+
+ return true;
+ }
+
+ static AZ::Outcome BuildAttributesMap(
+ const RHI::ShaderPlatformInterface* shaderPlatformInterface,
+ const AzslData& azslData,
+ const MapOfStringToStageType& shaderEntryPoints,
+ bool& hasRasterProgram)
+ {
+ hasRasterProgram = false;
+ bool hasComputeProgram = false;
+ bool hasRayTracingProgram = false;
+ RHI::ShaderStageAttributeMapList attributeMaps;
+ attributeMaps.resize(RHI::ShaderStageCount);
+ for (const auto& shaderEntryPoint : shaderEntryPoints)
+ {
+ auto shaderEntryName = shaderEntryPoint.first;
+ auto shaderStageType = shaderEntryPoint.second;
+ auto assetBuilderShaderType = ShaderBuilderUtility::ToAssetBuilderShaderType(shaderStageType);
+ hasRasterProgram |= shaderPlatformInterface->IsShaderStageForRaster(assetBuilderShaderType);
+ hasComputeProgram |= shaderPlatformInterface->IsShaderStageForCompute(assetBuilderShaderType);
+ hasRayTracingProgram |= shaderPlatformInterface->IsShaderStageForRayTracing(assetBuilderShaderType);
+
+ auto findId = AZStd::find_if(AZ_BEGIN_END(azslData.m_functions), [&shaderEntryPoint](const auto& func) {
+ return func.m_name == shaderEntryPoint.first;
+ });
+
+ if (findId == azslData.m_functions.end())
+ {
+ // shaderData.m_functions only contains Vertex, Fragment and Compute entries for now
+ // Tessellation shaders will need to be handled too
+ continue;
+ }
+
+ const auto shaderStage = ToRHIShaderStage(assetBuilderShaderType);
+ for (const auto& attr : findId->attributesList)
+ {
+ // Some stages like RHI::ShaderStage::Tessellation are compound and consist of two or more shader entries
+ const Name& attributeName = attr.first;
+ const RHI::ShaderStageAttributeArguments& args = attr.second;
+ const auto stageIndex = static_cast(shaderStage);
+ AZ_Assert(stageIndex < RHI::ShaderStageCount, "Invalid shader stage specified!");
+ attributeMaps[stageIndex][attributeName] = args;
+ }
+ }
+
+ if (hasRasterProgram && hasComputeProgram)
+ {
+ return AZ::Failure(AZStd::string(" Shader asset descriptor defines both a raster entry point and a compute entry point."));
+ }
+
+ if (!hasRasterProgram && !hasComputeProgram && !hasRayTracingProgram)
+ {
+ AZStd::string entryPointNames = ShaderBuilderUtility::GetAcceptableDefaultEntryPointNames(azslData);
+ return AZ::Failure(
+ AZStd::string::format( "Shader asset descriptor has a program variant that does not define any entry points. Either declare entry "
+ "points in the .shader file, or use one of the available default names (not case-sensitive): [%s]",
+ entryPointNames.c_str()));
+ }
+
+ return AZ::Success(attributeMaps);
+ }
+
+ void ShaderAssetBuilder2::ProcessJob(const AssetBuilderSDK::ProcessJobRequest& request, AssetBuilderSDK::ProcessJobResponse& response) const
+ {
+ const AZStd::sys_time_t startTime = AZStd::GetTimeNowTicks();
+ AZStd::string shaderFullPath;
+ AzFramework::StringFunc::Path::ConstructFull(request.m_watchFolder.c_str(), request.m_sourceFile.c_str(), shaderFullPath, true);
+ // Save .shader file name (no extension and no parent directory path)
+ AZStd::string shaderFileName;
+ AzFramework::StringFunc::Path::GetFileName(request.m_sourceFile.c_str(), shaderFileName);
+
+ // No error checking because the same calls were already executed during CreateJobs()
+ auto descriptorParseOutcome = ShaderBuilderUtility::LoadShaderDataJson(shaderFullPath);
+ RPI::ShaderSourceData shaderSourceData = descriptorParseOutcome.TakeValue();
+ AZStd::string azslFullPath;
+ ShaderBuilderUtility::GetAbsolutePathToAzslFile(shaderFullPath, shaderSourceData.m_source, azslFullPath);
+ AZ_TracePrintf(ShaderAssetBuilder2Name, "Original AZSL File: %s \n", azslFullPath.c_str());
+
+ // The directory where the Azsl file was found must be added to the list of include paths
+ AZStd::string azslFolderPath;
+ AzFramework::StringFunc::Path::GetFolderPath(azslFullPath.c_str(), azslFolderPath);
+ GlobalBuildOptions buildOptions = ReadBuildOptions(ShaderAssetBuilder2Name, azslFolderPath.c_str());
+
+ // Request the list of valid shader platform interfaces for the target platform.
+ AZStd::vector platformInterfaces = ShaderBuilderUtility::DiscoverEnabledShaderPlatformInterfaces(
+ request.m_platformInfo, shaderSourceData);
+ if (platformInterfaces.empty())
+ {
+ //No work to do. Exit gracefully.
+ AZ_TracePrintf(ShaderAssetBuilder2Name,
+ "No azshader is produced on behalf of %s because all valid RHI backends were disabled for this shader.\n",
+ shaderFullPath.c_str());
+ response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
+ return;
+ }
+
+ // Get the time stamp string as sys_time_t, and also convert back to string to make sure it was converted correctly.
+ AZStd::sys_time_t shaderAssetBuildTimestamp = 0;
+ auto shaderAssetBuildTimestampIterator = request.m_jobDescription.m_jobParameters.find(ShaderAssetBuildTimestampParam);
+ if (shaderAssetBuildTimestampIterator != request.m_jobDescription.m_jobParameters.end())
+ {
+ shaderAssetBuildTimestamp = AZStd::stoull(shaderAssetBuildTimestampIterator->second);
+
+ if (AZStd::to_string(shaderAssetBuildTimestamp) != shaderAssetBuildTimestampIterator->second)
+ {
+ response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
+ AZ_Assert(false, "Incorrect conversion of ShaderAssetBuildTimestampParam");
+ return;
+ }
+ }
+
+ auto supervariantList = ShaderBuilderUtility::GetSupervariantListFromShaderSourceData(shaderSourceData);
+
+ RPI::ShaderAssetCreator2 shaderAssetCreator;
+ shaderAssetCreator.Begin(Uuid::CreateRandom());
+
+ shaderAssetCreator.SetName(AZ::Name{shaderFileName.c_str()});
+ shaderAssetCreator.SetDrawListName(Name(shaderSourceData.m_drawListName));
+ shaderAssetCreator.SetShaderAssetBuildTimestamp(shaderAssetBuildTimestamp);
+
+ // The ShaderOptionGroupLayout must be the same across all supervariants because
+ // there can be only a single ShaderVariantTreeAsset per ShaderAsset.
+ // We will store here the one that results when the *.azslin file is
+ // compiled for the default, nameless, supervariant.
+ // For all other supervariants we just make sure the hashes are the same
+ // as this one.
+ RPI::Ptr finalShaderOptionGroupLayout = nullptr;
+
+
+ // Time to describe the big picture.
+ // 1- Preprocess an AZSL file with MCPP (a C-Preprocessor), and generate a flat AZSL file without #include lines and any macros in it.
+ // Let's call it the Flat-AZSL file. There are two levels of macro definition that need to be merged before we can invoke MCPP:
+ // 1.1- From /Config/shader_global_build_options.json, which we have stored in the local variable @buildOptions.
+ // 1.2- From the "Supervariant" definition key, which can be different for each supervariant.
+ // 2- There will be one Flat-AZSL per supervariant. Each Flat-AZSL will be transpiled to HLSL with AZSLc. This means there will be one HLSL file
+ // per supervariant.
+ // 3- The generated HLSL (one HLSL per supervariant) file may contain C-Preprocessor Macros inserted by AZSLc. And that file will be given to DXC.
+ // DXC has a preprocessor embedded in it. DXC will be executed once for each entry function listed in the .shader file.
+ // There will be one DXIL compiled binary for each entry function. All the DXIL compiled binaries for each supervariant will be combined
+ // in the ROOT ShaderVariantAsset.
+
+ // Remark: In general, the work done by the ShaderVariantAssetBuilder is similar, but it will start from the HLSL file created; in step 2, mentioned above; by this builder,
+ // for each supervariant.
+
+ // At this moment We have global build options that should be merged with the build options that are common
+ // to all the supervariants of this shader.
+ buildOptions.m_compilerArguments.Merge(shaderSourceData.m_compiler);
+
+ for (RHI::ShaderPlatformInterface* shaderPlatformInterface : platformInterfaces)
+ {
+ AZStd::string apiName(shaderPlatformInterface->GetAPIName().GetCStr());
+ AZ_TraceContext("Platform API", apiName);
+ // Signal the begin of shader data for an RHI API.
+ shaderAssetCreator.BeginAPI(shaderPlatformInterface->GetAPIType());
+
+ // Each shaderPlatformInterface has its own azsli header that needs to be prepended to the AZSL file before
+ // preprocessing. We will create a new temporary file that contains the combined data.
+ RHI::PrependArguments args;
+ args.m_sourceFile = azslFullPath.c_str();
+ args.m_prependFile = shaderPlatformInterface->GetAzslHeader(request.m_platformInfo);
+ args.m_addSuffixToFileName = apiName.c_str();
+ args.m_destinationFolder = request.m_tempDirPath.c_str();
+
+ AZStd::string prependedAzslFilePath = RHI::PrependFile(args);
+ if (prependedAzslFilePath == azslFullPath)
+ {
+ // For some reason the combined azsl file was not created in the temporary
+ // directory assigned to this job.
+ response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
+ return;
+ }
+
+ // Cache common AZSLC invokation arguments related with the current RHI Backend.
+ // Each supervariant can, optionally, remove or add more arguments for AZSLc.
+ AZStd::string commonAzslcCompilerParameters =
+ shaderPlatformInterface->GetAzslCompilerParameters(buildOptions.m_compilerArguments);
+ commonAzslcCompilerParameters += " ";
+ commonAzslcCompilerParameters +=
+ shaderPlatformInterface->GetAzslCompilerWarningParameters(buildOptions.m_compilerArguments);
+ AtomShaderConfig::AddParametersFromConfigFile(commonAzslcCompilerParameters, request.m_platformInfo);
+
+ // The register number only makes sense if the platform uses "spaces",
+ // since the register Id of the resource will not change even if the pipeline layout changes.
+ // We can pass in a default ShaderCompilerArguments because all we care about is whether the shaderPlatformInterface
+ // appends the "--use-spaces" flag.
+ const bool platformUsesRegisterSpaces =
+ (AzFramework::StringFunc::Find(commonAzslcCompilerParameters, "--use-spaces") != AZStd::string::npos);
+
+ uint32_t supervariantIndex = 0;
+ for (const auto& supervariantInfo : supervariantList)
+ {
+ AssetBuilderSDK::JobCancelListener jobCancelListener(request.m_jobId);
+ if (jobCancelListener.IsCancelled())
+ {
+ response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Cancelled;
+ return;
+ }
+
+ shaderAssetCreator.BeginSupervariant(supervariantInfo.m_name);
+
+ // Let's combine the global macro definitions, with the macro definitions particular to this
+ // supervariant. Two steps:
+ // 1- Supervariants can specify which macros to remove from the global definitions.
+ AZStd::vector macroDefinitionNamesToRemove = supervariantInfo.GetCombinedListOfMacroDefinitionNamesToRemove();
+ PreprocessorOptions preprocessorOptions = buildOptions.m_preprocessorSettings;
+ preprocessorOptions.RemovePredefinedMacros(macroDefinitionNamesToRemove);
+ // 2- Supervariants can specify which macros to add.
+ AZStd::vector macroDefinitionsToAdd = supervariantInfo.GetMacroDefinitionsToAdd();
+ preprocessorOptions.m_predefinedMacros.insert(
+ preprocessorOptions.m_predefinedMacros.end(), macroDefinitionsToAdd.begin(), macroDefinitionsToAdd.end());
+ // Run the preprocessor.
+ PreprocessorData output;
+ PreprocessFile(prependedAzslFilePath, output, preprocessorOptions, true, true);
+ RHI::ReportErrorMessages(ShaderAssetBuilder2Name, output.diagnostics);
+ // Dump the preprocessed string as a flat AZSL file with extension .azslin, which will be given to AZSLc to generate the HLSL file.
+ AZStd::string superVariantAzslinStemName = shaderFileName;
+ if (!supervariantInfo.m_name.IsEmpty())
+ {
+ superVariantAzslinStemName += AZStd::string::format("-%s", supervariantInfo.m_name.GetCStr());
+ }
+ AZStd::string azslinFullPath = ShaderBuilderUtility::DumpPreprocessedCode(
+ ShaderAssetBuilder2Name, output.code, request.m_tempDirPath, superVariantAzslinStemName,
+ apiName, true /*add2*/);
+ if (azslinFullPath.empty())
+ {
+ response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
+ return;
+ }
+ AZ_TracePrintf(ShaderAssetBuilder2Name, "Preprocessed AZSL File: %s \n", prependedAzslFilePath.c_str());
+
+ // Before transpiling the flat-AZSL(.azslin) file into HLSL it is necessary
+ // to setup the AZSLc arguments as required by the current supervariant.
+ AZStd::string azslcCompilerParameters = supervariantInfo.GetCustomizedArgumentsForAzslc(commonAzslcCompilerParameters);
+
+ // Ready to transpile the azslin file into HLSL.
+ ShaderBuilder::AzslCompiler azslc(azslinFullPath);
+ AZStd::string hlslFullPath = AZStd::string::format("%s_%s.hlsl2", superVariantAzslinStemName.c_str(), apiName.c_str());
+ AzFramework::StringFunc::Path::Join(request.m_tempDirPath.c_str(), hlslFullPath.c_str(), hlslFullPath, true);
+ auto emitFullOutcome = azslc.EmitFullData(azslcCompilerParameters, hlslFullPath, "2");
+ if (!emitFullOutcome.IsSuccess())
+ {
+ response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
+ return;
+ }
+ ShaderBuilderUtility::AzslSubProducts::Paths subProductsPaths = emitFullOutcome.TakeValue();
+
+ // In addition to the hlsl file, there are other json files that were generated.
+ // Each output file will become a product.
+ for (int i = 0; i < subProductsPaths.size(); ++i)
+ {
+ AssetBuilderSDK::JobProduct jobProduct;
+ jobProduct.m_productFileName = subProductsPaths[i];
+ static const AZ::Uuid AzslOutcomeType = "{6977AEB1-17AD-4992-957B-23BB2E85B18B}";
+ jobProduct.m_productAssetType = AzslOutcomeType;
+ // uint32_t rhiApiUniqueIndex, uint32_t supervariantIndex, uint32_t subProductType
+ jobProduct.m_productSubID = RPI::ShaderAsset2::MakeProductAssetSubId(
+ shaderPlatformInterface->GetAPIUniqueIndex(), supervariantIndex,
+ aznumeric_cast(ShaderBuilderUtility::AzslSubProducts::SubList[i]));
+ jobProduct.m_dependenciesHandled = true;
+ // Note that the output products are not traditional product assets that will be used by the game project.
+ // They are artifacts that are produced once, cached, and used later by other AssetBuilders as a way to centralize
+ // build organization.
+ response.m_outputProducts.push_back(AZStd::move(jobProduct));
+ }
+
+ AZStd::shared_ptr files(new ShaderFiles);
+ AzslData azslData(files);
+ azslData.m_preprocessedFullPath = azslinFullPath;
+ RPI::ShaderResourceGroupLayoutList srgLayoutList;
+ RPI::Ptr shaderOptionGroupLayout = RPI::ShaderOptionGroupLayout::Create();
+ BindingDependencies bindingDependencies;
+ RootConstantData rootConstantData;
+ AssetBuilderSDK::ProcessJobResultCode azslJsonReadResult = ShaderBuilderUtility::PopulateAzslDataFromJsonFiles(
+ ShaderAssetBuilder2Name, subProductsPaths, platformUsesRegisterSpaces, azslData, srgLayoutList, shaderOptionGroupLayout,
+ bindingDependencies, rootConstantData);
+ if (azslJsonReadResult != AssetBuilderSDK::ProcessJobResult_Success)
+
+ {
+ response.m_resultCode = azslJsonReadResult;
+ return;
+ }
+
+ shaderAssetCreator.SetSrgLayoutList(srgLayoutList);
+
+ if (!finalShaderOptionGroupLayout)
+ {
+ finalShaderOptionGroupLayout = shaderOptionGroupLayout;
+ shaderAssetCreator.SetShaderOptionGroupLayout(finalShaderOptionGroupLayout);
+ const uint32_t usedShaderOptionBits = shaderOptionGroupLayout->GetBitSize();
+ AZ_TracePrintf(
+ ShaderAssetBuilder2Name, "Note: This shader uses %u of %u available shader variant key bits. \n",
+ usedShaderOptionBits, RPI::ShaderVariantKeyBitCount);
+ }
+ else
+ {
+ if (finalShaderOptionGroupLayout->GetHash() != shaderOptionGroupLayout->GetHash())
+ {
+ AZ_Error(
+ ShaderAssetBuilder2Name, false, "Supervariant %s has a different ShaderOptionGroupLayout",
+ supervariantInfo.m_name.GetCStr())
+ response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
+ return;
+ }
+ }
+
+ // Discover entry points & type of programs.
+ MapOfStringToStageType shaderEntryPoints;
+ if (shaderSourceData.m_programSettings.m_entryPoints.empty())
+ {
+ AZ_TracePrintf(
+ ShaderAssetBuilder2Name,
+ "ProgramSettings do not specify entry points, will use GetDefaultEntryPointsFromShader()\n");
+ ShaderBuilderUtility::GetDefaultEntryPointsFromFunctionDataList(azslData.m_functions, shaderEntryPoints);
+ }
+ else
+ {
+ for (const auto& entryPoint : shaderSourceData.m_programSettings.m_entryPoints)
+ {
+ shaderEntryPoints[entryPoint.m_name] = entryPoint.m_type;
+ }
+ }
+
+ bool hasRasterProgram = false;
+ auto attributeMapsOutcome = BuildAttributesMap(shaderPlatformInterface, azslData, shaderEntryPoints, hasRasterProgram);
+ if (!attributeMapsOutcome.IsSuccess())
+ {
+ AZ_Error(ShaderAssetBuilder2Name, false, "%s\n", attributeMapsOutcome.GetError().c_str());
+ response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
+ return;
+ }
+ shaderAssetCreator.SetShaderStageAttributeMapList(attributeMapsOutcome.TakeValue());
+
+ // Check if we were canceled before we do any heavy processing of
+ // the shader data (compiling the shader kernels, processing SRG
+ // and pipeline layout data, etc.).
+ if (jobCancelListener.IsCancelled())
+ {
+ response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Cancelled;
+ return;
+ }
+
+ RHI::Ptr pipelineLayoutDescriptor =
+ ShaderBuilderUtility::BuildPipelineLayoutDescriptorForApi(
+ ShaderAssetBuilder2Name, srgLayoutList, shaderEntryPoints, buildOptions.m_compilerArguments, rootConstantData,
+ shaderPlatformInterface, bindingDependencies);
+ if (!pipelineLayoutDescriptor)
+ {
+ AZ_Error(
+ ShaderAssetBuilder2Name, false, "Failed to build pipeline layout descriptor for api=[%s]",
+ shaderPlatformInterface->GetAPIName().GetCStr());
+ response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
+ return;
+ }
+
+ shaderAssetCreator.SetPipelineLayout(pipelineLayoutDescriptor);
+
+
+ RPI::ShaderInputContract shaderInputContract;
+ RPI::ShaderOutputContract shaderOutputContract;
+ size_t colorAttachmentCount = 0;
+ ShaderBuilderUtility::CreateShaderInputAndOutputContracts(
+ azslData, shaderEntryPoints, *shaderOptionGroupLayout.get(),
+ subProductsPaths[ShaderBuilderUtility::AzslSubProducts::om],
+ subProductsPaths[ShaderBuilderUtility::AzslSubProducts::ia],
+ shaderInputContract, shaderOutputContract, colorAttachmentCount);
+ shaderAssetCreator.SetInputContract(shaderInputContract);
+ shaderAssetCreator.SetOutputContract(shaderOutputContract);
+
+ if (hasRasterProgram)
+ {
+ // Set the various states to what is in the descriptor.
+ const RHI::TargetBlendState& targetBlendState = shaderSourceData.m_blendState;
+ RHI::RenderStates renderStates;
+ renderStates.m_rasterState = shaderSourceData.m_rasterState;
+ renderStates.m_depthStencilState = shaderSourceData.m_depthStencilState;
+ // [GFX TODO][ATOM-930] We should support unique blend states per RT
+ for (size_t i = 0; i < colorAttachmentCount; ++i)
+ {
+ renderStates.m_blendState.m_targets[i] = targetBlendState;
+ }
+
+ shaderAssetCreator.SetRenderStates(renderStates);
+ }
+
+ Outcome hlslSourceCodeOutcome = Utils::ReadFile(hlslFullPath);
+ if (!hlslSourceCodeOutcome.IsSuccess())
+ {
+ AZ_Error(
+ ShaderAssetBuilder2Name, false, "Failed to obtain shader source from %s. [%s]", hlslFullPath.c_str(),
+ hlslSourceCodeOutcome.GetError().c_str());
+ response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
+ return;
+ }
+ AZStd::string hlslSourceCode = hlslSourceCodeOutcome.TakeValue();
+
+ // The root ShaderVariantAsset needs to be created with the known uuid of the source .shader asset because
+ // the ShaderAsset owns a Data::Asset<> reference that gets serialized. It must have the correct uuid
+ // so the root ShaderVariantAsset is found when the ShaderAsset is deserialized.
+ uint32_t rootVariantProductSubId = RPI::ShaderAsset2::MakeProductAssetSubId(
+ shaderPlatformInterface->GetAPIUniqueIndex(), supervariantIndex,
+ aznumeric_cast(RPI::ShaderAsset2ProductSubId::RootShaderVariantAsset));
+ auto assetIdOutcome = RPI::AssetUtils::MakeAssetId(shaderFullPath, rootVariantProductSubId);
+ AZ_Assert(assetIdOutcome.IsSuccess(), "Failed to get AssetId from shader %s", shaderFullPath.c_str());
+ const Data::AssetId variantAssetId = assetIdOutcome.TakeValue();
+
+ RPI::ShaderVariantListSourceData::VariantInfo rootVariantInfo;
+ ShaderVariantCreationContext2 shaderVariantCreationContext = {
+ *shaderPlatformInterface,
+ request.m_platformInfo,
+ buildOptions.m_compilerArguments,
+ request.m_tempDirPath,
+ startTime,
+ shaderSourceData,
+ *shaderOptionGroupLayout.get(),
+ shaderEntryPoints,
+ variantAssetId,
+ superVariantAzslinStemName,
+ hlslFullPath,
+ hlslSourceCode};
+
+
+ AZStd::optional outputByproducts;
+ auto rootShaderVariantAssetOutcome = ShaderVariantAssetBuilder2::CreateShaderVariantAsset(rootVariantInfo, shaderVariantCreationContext, outputByproducts);
+ if (!rootShaderVariantAssetOutcome.IsSuccess())
+ {
+ AZ_Error(ShaderAssetBuilder2Name, false, "%s\n", rootShaderVariantAssetOutcome.GetError().c_str())
+ response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
+ return;
+ }
+ Data::Asset rootShaderVariantAsset = rootShaderVariantAssetOutcome.TakeValue();
+
+ shaderAssetCreator.SetRootShaderVariantAsset(rootShaderVariantAsset);
+
+ if (!shaderAssetCreator.EndSupervariant())
+ {
+ AZ_Error(
+ ShaderAssetBuilder2Name, false, "Failed to create shader asset for supervariant [%s]", supervariantInfo.m_name.GetCStr())
+ response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
+ return;
+ }
+
+ // Time to save the root variant related assets in the cache.
+ AssetBuilderSDK::JobProduct assetProduct;
+ if (!ShaderVariantAssetBuilder2::SerializeOutShaderVariantAsset(
+ rootShaderVariantAsset, superVariantAzslinStemName, request.m_tempDirPath, *shaderPlatformInterface,
+ rootVariantProductSubId,
+ assetProduct))
+ {
+ response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
+ return;
+ }
+ response.m_outputProducts.push_back(assetProduct);
+
+ if (outputByproducts)
+ {
+ // add byproducts as job output products:
+ uint32_t subProductType = aznumeric_cast(RPI::ShaderAsset2ProductSubId::FirstByProduct);
+ for (const AZStd::string& byproduct : outputByproducts.value().m_intermediatePaths)
+ {
+ AssetBuilderSDK::JobProduct jobProduct;
+ jobProduct.m_productFileName = byproduct;
+ jobProduct.m_productAssetType = Uuid::CreateName("DebugInfoByProduct-PdbOrDxilTxt");
+ jobProduct.m_productSubID = RPI::ShaderAsset2::MakeProductAssetSubId(
+ shaderPlatformInterface->GetAPIUniqueIndex(), supervariantIndex,
+ subProductType++);
+ response.m_outputProducts.push_back(AZStd::move(jobProduct));
+ }
+ }
+
+
+ supervariantIndex++;
+
+ } // end for the supervariant
+
+ shaderAssetCreator.EndAPI();
+
+ } // end for all ShaderPlatformInterfaces
+
+ Data::Asset shaderAsset;
+ if (!shaderAssetCreator.End(shaderAsset))
+ {
+ response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
+ return;
+ }
+
+ if (!SerializeOutShaderAsset(shaderAsset, request.m_tempDirPath, response))
+ {
+ response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
+ return;
+ }
+
+ response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
+
+ const AZStd::sys_time_t endTime = AZStd::GetTimeNowTicks();
+ const AZStd::sys_time_t deltaTime = endTime - startTime;
+ const float elapsedTimeSeconds = (float)(deltaTime) / (float)AZStd::GetTimeTicksPerSecond();
+
+ AZ_TracePrintf(ShaderAssetBuilder2Name, "Finished processing %s in %.2f seconds\n", request.m_sourceFile.c_str(), elapsedTimeSeconds);
+
+ ShaderBuilderUtility::LogProfilingData(ShaderAssetBuilder2Name, shaderFileName);
+ }
+
+ } // ShaderBuilder
+} // AZ
diff --git a/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderAssetBuilder2.h b/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderAssetBuilder2.h
new file mode 100644
index 0000000000..915d4e53d0
--- /dev/null
+++ b/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderAssetBuilder2.h
@@ -0,0 +1,60 @@
+/*
+* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
+* its licensors.
+*
+* For complete copyright and license terms please see the LICENSE at the root of this
+* distribution (the "License"). All use of this software is governed by the License,
+* or, if provided, by the license below or the license accompanying this file. Do not
+* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+*
+*/
+
+#pragma once
+
+#include
+#include
+#include
+
+#include
+
+namespace AZ
+{
+ namespace Data
+ {
+ class AssetHandler;
+ }
+
+ namespace RHI
+ {
+ class ShaderPlatformInterface;
+ }
+
+ namespace ShaderBuilder
+ {
+ struct AzslData;
+
+ class ShaderAssetBuilder2
+ : public AssetBuilderSDK::AssetBuilderCommandBus::Handler
+ {
+ public:
+ AZ_TYPE_INFO(ShaderAssetBuilder2, "{C94DA151-82BC-4475-86FA-E6C92A0BD6F8}");
+
+ static constexpr const char* ShaderAssetBuilder2JobKey = "Shader Asset 2";
+
+ ShaderAssetBuilder2() = default;
+ ~ShaderAssetBuilder2() = default;
+
+ // Asset Builder Callback Functions ...
+ void CreateJobs(const AssetBuilderSDK::CreateJobsRequest& request, AssetBuilderSDK::CreateJobsResponse& response) const;
+ void ProcessJob(const AssetBuilderSDK::ProcessJobRequest& request, AssetBuilderSDK::ProcessJobResponse& response) const;
+
+ // AssetBuilderSDK::AssetBuilderCommandBus interface overrides ...
+ void ShutDown() override { };
+
+ private:
+ AZ_DISABLE_COPY_MOVE(ShaderAssetBuilder2);
+ };
+
+ } // ShaderBuilder
+} // AZ
diff --git a/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderBuilderUtility.cpp b/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderBuilderUtility.cpp
index 19b2f328c1..a20b9c869b 100644
--- a/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderBuilderUtility.cpp
+++ b/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderBuilderUtility.cpp
@@ -29,7 +29,8 @@
#include
#include
-#include
+#include // DEPRECATED - [ATOM-15472]
+#include
#include
#include
@@ -41,13 +42,15 @@
#include "ShaderPlatformInterfaceRequest.h"
#include "AtomShaderConfig.h"
+#include "SrgLayoutUtility.h"
+
namespace AZ
{
namespace ShaderBuilder
{
namespace ShaderBuilderUtility
{
- static const char* ShaderBuilderUtilityName = "ShaderBuilderUtility";
+ static constexpr char ShaderBuilderUtilityName[] = "ShaderBuilderUtility";
Outcome LoadShaderDataJson(const AZStd::string& fullPathToJsonFile)
{
@@ -84,22 +87,8 @@ namespace AZ
AzFramework::StringFunc::Path::ReplaceExtension(absoluteAzslPath, "azsl");
}
- uint32_t MakeDebugByproductSubId(RHI::APIType apiType, const AZStd::string& productFileName)
- {
- // bits: ----- 24 -----|- 4 -|- 4 -
- // fn hash | id + api | 0xF
- uint32_t subId = 0xF; // to avoid collisions with subid of other source outputs using RPI::ShaderAssetSubId::GeneratedSource + api
- uint32_t id_api = static_cast(RPI::ShaderAssetSubId::DebugByProduct);
- id_api += apiType;
- id_api <<= 4;
- subId |= id_api;
- size_t fnHash = AZStd::hash()(productFileName);
- subId |= static_cast(fnHash) & 0xFFFFFF00;
- return subId;
- }
-
static bool LoadShaderResourceGroupAssets(
- [[maybe_unused]] const char* BuilderName,
+ [[maybe_unused]] const char* builderName,
const SrgDataContainer& resourceGroups,
ShaderResourceGroupAssets& srgAssets)
{
@@ -121,7 +110,7 @@ namespace AZ
if (!assetFound)
{
- AZ_Error(BuilderName, false, "Could not find asset identified by path '%s'", srgFilePath.c_str());
+ AZ_Error(builderName, false, "Could not find asset identified by path '%s'", srgFilePath.c_str());
readSRGsSuccessfuly = false;
continue;
}
@@ -139,7 +128,7 @@ namespace AZ
: asset.GetStatus() == Status::ReadyPreNotify ? "ready-pre-notify"
: asset.GetStatus() == Status::Error ? "error" : "not-loaded/ready/unknown";
- AZ_Error(BuilderName, false, "Searching SRG [%s]: Could not load SRG asset. (asset status [%s]) AssetId='%s' Path='%s'",
+ AZ_Error(builderName, false, "Searching SRG [%s]: Could not load SRG asset. (asset status [%s]) AssetId='%s' Path='%s'",
srgData.m_name.c_str(),
statusString.c_str(),
assetId.ToString().c_str(), srgFilePath.c_str());
@@ -148,7 +137,7 @@ namespace AZ
}
else if (!asset->IsValid())
{
- AZ_Error(BuilderName, false, "SRG asset has no layout information. AssetId='%s' Path='%s'",
+ AZ_Error(builderName, false, "SRG asset has no layout information. AssetId='%s' Path='%s'",
assetId.ToString().c_str(), srgFilePath.c_str());
readSRGsSuccessfuly = false;
continue;
@@ -182,8 +171,10 @@ namespace AZ
return files;
}
+
+ //! [GFX TODO] [ATOM-15472] Deprecated, remove when this ticket is addressed.
AssetBuilderSDK::ProcessJobResultCode PopulateAzslDataFromJsonFiles(
- const char* BuilderName,
+ const char* builderName,
const AzslSubProducts::Paths& pathOfJsonFiles,
AzslData& azslData,
ShaderResourceGroupAssets& srgAssets,
@@ -204,7 +195,7 @@ namespace AZ
outcomes[i] = JsonSerializationUtils::ReadJsonFile(pathOfJsonFiles[i]);
if (!outcomes[i].IsSuccess())
{
- AZ_Error(BuilderName, false, "%s", outcomes[i].GetError().c_str());
+ AZ_Error(builderName, false, "%s", outcomes[i].GetError().c_str());
allReadSuccess = false;
}
}
@@ -215,22 +206,22 @@ namespace AZ
// Get full list of functions eligible for vertex shader entry points
// along with metadata for constructing the InputAssembly for each of them
- if (!azslc.ParseIaPopulateFunctionData(outcomes[AzslSubProducts::ia].GetValue(), azslData.m_topData.m_functions))
+ if (!azslc.ParseIaPopulateFunctionData(outcomes[AzslSubProducts::ia].GetValue(), azslData.m_functions))
{
return AssetBuilderSDK::ProcessJobResult_Failed;
}
// Each SRG is built as a separate asset in the SrgLayoutBuilder, here we just
// build the list and load the data from multiple dependency assets.
- if (!azslc.ParseSrgPopulateSrgData(outcomes[AzslSubProducts::srg].GetValue(), azslData.m_topData.m_srgData))
+ if (!azslc.ParseSrgPopulateSrgData(outcomes[AzslSubProducts::srg].GetValue(), azslData.m_srgData))
{
return AssetBuilderSDK::ProcessJobResult_Failed;
}
// Add all Shader Resource Group Assets that were defined in the shader code to the shader asset
- if (!LoadShaderResourceGroupAssets(BuilderName, azslData.m_topData.m_srgData, srgAssets))
+ if (!LoadShaderResourceGroupAssets(builderName, azslData.m_srgData, srgAssets))
{
- AZ_Error(BuilderName, false, "Failed to obtain shader resource group assets");
+ AZ_Error(builderName, false, "Failed to obtain shader resource group assets");
return AssetBuilderSDK::ProcessJobResult_Failed;
}
@@ -238,7 +229,7 @@ namespace AZ
// for each option and what is its default value.
if (!azslc.ParseOptionsPopulateOptionGroupLayout(outcomes[AzslSubProducts::options].GetValue(), shaderOptionGroupLayout))
{
- AZ_Error(BuilderName, false, "Failed to find a valid list of shader options!");
+ AZ_Error(builderName, false, "Failed to find a valid list of shader options!");
return AssetBuilderSDK::ProcessJobResult_Failed;
}
@@ -246,14 +237,100 @@ namespace AZ
// and informs us on register indexes and shader stages using these resources
if (!azslc.ParseBindingdepPopulateBindingDependencies(outcomes[AzslSubProducts::bindingdep].GetValue(), bindingDependencies)) // consuming data from binding-dep
{
- AZ_Error(BuilderName, false, "Failed to obtain shader resource binding reflection");
+ AZ_Error(builderName, false, "Failed to obtain shader resource binding reflection");
return AssetBuilderSDK::ProcessJobResult_Failed;
}
// access the root constants reflection
if (!azslc.ParseSrgPopulateRootConstantData(outcomes[AzslSubProducts::srg].GetValue(), rootConstantData)) // consuming data from --srg ("InlineConstantBuffer" subjson section)
{
- AZ_Error(BuilderName, false, "Failed to obtain root constant data reflection");
+ AZ_Error(builderName, false, "Failed to obtain root constant data reflection");
+ return AssetBuilderSDK::ProcessJobResult_Failed;
+ }
+
+ return AssetBuilderSDK::ProcessJobResult_Success;
+ }
+
+
+ AssetBuilderSDK::ProcessJobResultCode PopulateAzslDataFromJsonFiles(
+ const char* builderName,
+ const AzslSubProducts::Paths& pathOfJsonFiles,
+ const bool platformUsesRegisterSpaces,
+ AzslData& azslData,
+ RPI::ShaderResourceGroupLayoutList& srgLayoutList,
+ RPI::Ptr shaderOptionGroupLayout,
+ BindingDependencies& bindingDependencies,
+ RootConstantData& rootConstantData)
+ {
+ AzslCompiler azslc(
+ azslData
+ .m_preprocessedFullPath); // set the input file for eventual error messages, but the compiler won't be called on it.
+ bool allReadSuccess = true;
+ // read: input assembly reflection
+ // shader resource group reflection
+ // options reflection
+ // binding dependencies reflection
+ int indicesOfInterest[] = {
+ AzslSubProducts::ia, AzslSubProducts::srg, AzslSubProducts::options, AzslSubProducts::bindingdep};
+ AZStd::unordered_map> outcomes;
+ for (int i : indicesOfInterest)
+ {
+ outcomes[i] = JsonSerializationUtils::ReadJsonFile(pathOfJsonFiles[i]);
+ if (!outcomes[i].IsSuccess())
+ {
+ AZ_Error(builderName, false, "%s", outcomes[i].GetError().c_str());
+ allReadSuccess = false;
+ }
+ }
+ if (!allReadSuccess)
+ {
+ return AssetBuilderSDK::ProcessJobResult_Failed;
+ }
+
+ // Get full list of functions eligible for vertex shader entry points
+ // along with metadata for constructing the InputAssembly for each of them
+ if (!azslc.ParseIaPopulateFunctionData(outcomes[AzslSubProducts::ia].GetValue(), azslData.m_functions))
+ {
+ return AssetBuilderSDK::ProcessJobResult_Failed;
+ }
+
+ // Each SRG is built as a separate asset in the SrgLayoutBuilder, here we just
+ // build the list and load the data from multiple dependency assets.
+ if (!azslc.ParseSrgPopulateSrgData(outcomes[AzslSubProducts::srg].GetValue(), azslData.m_srgData))
+ {
+ return AssetBuilderSDK::ProcessJobResult_Failed;
+ }
+
+ // Add all Shader Resource Group Assets that were defined in the shader code to the shader asset
+ if (!SrgLayoutUtility::LoadShaderResourceGroupLayouts(builderName, azslData.m_srgData, platformUsesRegisterSpaces, srgLayoutList))
+ {
+ AZ_Error(builderName, false, "Failed to obtain shader resource group assets");
+ return AssetBuilderSDK::ProcessJobResult_Failed;
+ }
+
+ // The shader options define what options are available, what are the allowed values/range
+ // for each option and what is its default value.
+ if (!azslc.ParseOptionsPopulateOptionGroupLayout(outcomes[AzslSubProducts::options].GetValue(), shaderOptionGroupLayout))
+ {
+ AZ_Error(builderName, false, "Failed to find a valid list of shader options!");
+ return AssetBuilderSDK::ProcessJobResult_Failed;
+ }
+
+ // It analyzes the shader external bindings (all SRG contents)
+ // and informs us on register indexes and shader stages using these resources
+ if (!azslc.ParseBindingdepPopulateBindingDependencies(
+ outcomes[AzslSubProducts::bindingdep].GetValue(), bindingDependencies)) // consuming data from binding-dep
+ {
+ AZ_Error(builderName, false, "Failed to obtain shader resource binding reflection");
+ return AssetBuilderSDK::ProcessJobResult_Failed;
+ }
+
+ // access the root constants reflection
+ if (!azslc.ParseSrgPopulateRootConstantData(
+ outcomes[AzslSubProducts::srg].GetValue(),
+ rootConstantData)) // consuming data from --srg ("InlineConstantBuffer" subjson section)
+ {
+ AZ_Error(builderName, false, "Failed to obtain root constant data reflection");
return AssetBuilderSDK::ProcessJobResult_Failed;
}
@@ -312,7 +389,7 @@ namespace AZ
}
RHI::Ptr BuildPipelineLayoutDescriptorForApi(
- [[maybe_unused]] const char* BuilderName,
+ [[maybe_unused]] const char* builderName,
RHI::ShaderPlatformInterface* shaderPlatformInterface,
BindingDependencies& bindingDependencies /*inout*/,
const ShaderResourceGroupAssets& srgAssets,
@@ -356,7 +433,7 @@ namespace AZ
const BindingDependencies::SrgResources* srgResources = bindingDependencies.GetSrg(srgName);
if (!srgResources)
{
- AZ_Error(BuilderName, false, "SRG %s not found in the dependency dataset", srgName.data());
+ AZ_Error(builderName, false, "SRG %s not found in the dependency dataset", srgName.data());
return nullptr;
}
@@ -385,23 +462,21 @@ namespace AZ
for (const auto& constantData : rootConstantData->m_constants)
{
RHI::ShaderInputConstantDescriptor rootConstantDesc(
- constantData.m_nameId,
- constantData.m_constantByteOffset,
- constantData.m_constantByteSize,
+ constantData.m_nameId, constantData.m_constantByteOffset, constantData.m_constantByteSize,
rootConstantData->m_bindingInfo.m_registerId);
-
+
rootConstantsLayout->AddShaderInput(rootConstantDesc);
}
}
-
+
if (!rootConstantsLayout->Finalize())
{
- AZ_Error(BuilderName, false, "Failed to finalize root constants layout");
+ AZ_Error(builderName, false, "Failed to finalize root constants layout");
return nullptr;
}
pipelineLayoutDescriptor->SetRootConstantsLayout(*rootConstantsLayout);
-
+
RHI::ShaderPlatformInterface::RootConstantsInfo rootConstantInfo;
if (rootConstantData)
{
@@ -415,14 +490,15 @@ namespace AZ
rootConstantInfo.m_registerId = dummyRootConstantData.m_bindingInfo.m_registerId;
}
rootConstantInfo.m_totalSizeInBytes = rootConstantsLayout->GetDataSize();
-
+
// Build platform-specific PipelineLayoutDescriptor data, and finalize
- if (!shaderPlatformInterface->BuildPipelineLayoutDescriptor(pipelineLayoutDescriptor, srgInfos, rootConstantInfo, shaderCompilerArguments))
+ if (!shaderPlatformInterface->BuildPipelineLayoutDescriptor(
+ pipelineLayoutDescriptor, srgInfos, rootConstantInfo, shaderCompilerArguments))
{
- AZ_Error(BuilderName, false, "Failed to build pipeline layout descriptor");
+ AZ_Error(builderName, false, "Failed to build pipeline layout descriptor");
return nullptr;
}
-
+
return pipelineLayoutDescriptor;
}
@@ -442,7 +518,7 @@ namespace AZ
}
else
{
- formatted = AZStd::string::format("%s.%s.%s", stemName.c_str(), apiTypeString.c_str(), extension.c_str());
+ formatted = AZStd::string::format("%s_%s.%s", stemName.c_str(), apiTypeString.c_str(), extension.c_str());
}
AzFramework::StringFunc::Path::Join(dumpDirectory.c_str(), formatted.c_str(), finalFilePath, true, true);
AZ::IO::FileIOStream outFileStream(finalFilePath.data(), AZ::IO::OpenMode::ModeWrite);
@@ -463,14 +539,20 @@ namespace AZ
return finalFilePath;
}
- AZStd::string DumpPreprocessedCode(const char* builderName, const AZStd::string& preprocessedCode, const AZStd::string& tempDirPath, const AZStd::string& stemName, const AZStd::string& apiTypeString)
+ // [GFX TODO] Remove 'add2' when [ATOM-15472]
+ AZStd::string DumpPreprocessedCode(const char* builderName, const AZStd::string& preprocessedCode, const AZStd::string& tempDirPath, const AZStd::string& stemName, const AZStd::string& apiTypeString, bool add2)
{
+ if (add2)
+ {
+ return DumpCode(builderName, preprocessedCode, tempDirPath, stemName, apiTypeString, "azslin2");
+ }
+
return DumpCode(builderName, preprocessedCode, tempDirPath, stemName, apiTypeString, "azslin");
}
AZStd::string DumpAzslPrependedCode(const char* builderName, const AZStd::string& nonPreprocessedYetAzslSource, const AZStd::string& tempDirPath, const AZStd::string& stemName, const AZStd::string& apiTypeString)
{
- return DumpCode(builderName, nonPreprocessedYetAzslSource, tempDirPath, stemName, apiTypeString, "azsl.prepend");
+ return DumpCode(builderName, nonPreprocessedYetAzslSource, tempDirPath, stemName, apiTypeString, "azslprepend");
}
AZStd::string ExtractStemName(const char* path)
@@ -489,6 +571,83 @@ namespace AZ
return platformInterfaces;
}
+
+ AZStd::vector DiscoverEnabledShaderPlatformInterfaces(const AssetBuilderSDK::PlatformInfo& info, const RPI::ShaderSourceData& shaderSourceData)
+ {
+ // Request the list of valid shader platform interfaces for the target platform.
+ AZStd::vector platformInterfaces;
+ ShaderPlatformInterfaceRequestBus::BroadcastResult(
+ platformInterfaces, &ShaderPlatformInterfaceRequest::GetShaderPlatformInterface, info);
+
+ // Let's remove the unwanted RHI interfaces from the list.
+ platformInterfaces.erase(
+ AZStd::remove_if(AZ_BEGIN_END(platformInterfaces),
+ [&](const RHI::ShaderPlatformInterface* shaderPlatformInterface) {
+ return !shaderPlatformInterface ||
+ shaderSourceData.IsRhiBackendDisabled(shaderPlatformInterface->GetAPIName()) ||
+ (shaderPlatformInterface->GetAPIUniqueIndex() == static_cast(AZ::RHI::APIIndex::Null));
+ }),
+ platformInterfaces.end());
+ return platformInterfaces;
+ }
+
+ static bool IsValidSupervariantName(const AZStd::string& supervariantName)
+ {
+ return AZStd::all_of(AZ_BEGIN_END(supervariantName),
+ [](AZStd::string::value_type ch)
+ {
+ return AZStd::is_alnum(ch); // allow alpha numeric only
+ }
+ );
+ }
+
+ AZStd::vector GetSupervariantListFromShaderSourceData(
+ const RPI::ShaderSourceData& shaderSourceData)
+ {
+ AZStd::vector supervariants;
+ supervariants.reserve(shaderSourceData.m_supervariants.size() + 1);
+
+ // Add the supervariants, always making sure that:
+ // 1- The default, nameless, supervariant goes to the front.
+ // 2- Each supervariant has a unique name
+ AZStd::unordered_set uniqueSuperVariants; // This set helps duplicate detection.
+ // Although it is not common, it is possible to declare a nameless supervariant.
+ bool addedNamelessSupervariant = false;
+ for (const auto& supervariantInfo : shaderSourceData.m_supervariants)
+ {
+ if (!IsValidSupervariantName(supervariantInfo.m_name.GetStringView()))
+ {
+ AZ_Error(
+ ShaderBuilderUtilityName, false, "The supervariant name: [%s] contains invalid characters. Only [a-zA-Z0-9] are supported",
+ supervariantInfo.m_name.GetCStr());
+ return {}; // Return an empty vector.
+ }
+ if (uniqueSuperVariants.count(supervariantInfo.m_name))
+ {
+ AZ_Error(
+ ShaderBuilderUtilityName, false, "It is invalid to specify more than one supervariant with the same name: [%s]",
+ supervariantInfo.m_name.GetCStr());
+ return {}; // Return an empty vector.
+ }
+ uniqueSuperVariants.emplace(supervariantInfo.m_name);
+ supervariants.push_back(supervariantInfo);
+ if (supervariantInfo.m_name.IsEmpty())
+ {
+ addedNamelessSupervariant = true;
+ // Always move the default, nameless, variant to the begining of the list.
+ AZStd::swap(supervariants.front(), supervariants.back());
+ }
+ }
+ if (!addedNamelessSupervariant)
+ {
+ supervariants.push_back({});
+ // Always move the default, nameless, variant to the begining of the list.
+ AZStd::swap(supervariants.front(), supervariants.back());
+ }
+
+ return supervariants;
+ }
+
static void ReadShaderCompilerProfiling([[maybe_unused]] const char* builderName, RHI::ShaderCompilerProfiling& shaderCompilerProfiling, AZStd::string_view shaderPath)
{
AZStd::string folderPath;
@@ -561,12 +720,64 @@ namespace AZ
uint32_t MakeAzslBuildProductSubId(RPI::ShaderAssetSubId subId, RHI::APIType apiType)
{
- auto subIdMaxEnumerator = RPI::ShaderAssetSubId::GeneratedSource;
+ auto subIdMaxEnumerator = RPI::ShaderAssetSubId::GeneratedHlslSource;
// separate bit space between subid enum, and api-type:
int shiftLeft = static_cast(log2(static_cast(subIdMaxEnumerator))) + 1;
return static_cast(subId) + (apiType << shiftLeft);
}
+ Outcome ObtainBuildArtifactPathFromShaderAssetBuilder2(
+ const uint32_t rhiUniqueIndex, const AZStd::string& platformIdentifier, const AZStd::string& shaderJsonPath,
+ const uint32_t supervariantIndex, RPI::ShaderAssetSubId shaderAssetSubId)
+ {
+ // platform id from identifier
+ AzFramework::PlatformId platformId = AzFramework::PlatformId::PC;
+ if (platformIdentifier == "pc")
+ {
+ platformId = AzFramework::PlatformId::PC;
+ }
+ else if (platformIdentifier == "osx_gl")
+ {
+ platformId = AzFramework::PlatformId::OSX;
+ }
+ else if (platformIdentifier == "es3")
+ {
+ platformId = AzFramework::PlatformId::ES3;
+ }
+ else if (platformIdentifier == "ios")
+ {
+ platformId = AzFramework::PlatformId::IOS;
+ }
+
+ uint32_t assetSubId = RPI::ShaderAsset2::MakeProductAssetSubId(rhiUniqueIndex, supervariantIndex, aznumeric_cast(shaderAssetSubId));
+ auto assetIdOutcome = RPI::AssetUtils::MakeAssetId(shaderJsonPath, assetSubId);
+ if (!assetIdOutcome.IsSuccess())
+ {
+ return Failure(AZStd::string::format(
+ "Missing ShaderAssetBuilder2 product %s, for sub %d", shaderJsonPath.c_str(), (uint32_t)shaderAssetSubId));
+ }
+
+ Data::AssetId assetId = assetIdOutcome.TakeValue();
+ // get the relative path:
+ AZStd::string assetPath;
+ Data::AssetCatalogRequestBus::BroadcastResult(assetPath, &Data::AssetCatalogRequests::GetAssetPathById, assetId);
+
+ // get the root:
+ AZStd::string assetRoot = AzToolsFramework::PlatformAddressedAssetCatalog::GetAssetRootForPlatform(platformId);
+ // join
+ AZStd::string assetFullPath;
+ AzFramework::StringFunc::Path::Join(assetRoot.c_str(), assetPath.c_str(), assetFullPath);
+ bool fileExists = IO::FileIOBase::GetInstance()->Exists(assetFullPath.c_str()) &&
+ !IO::FileIOBase::GetInstance()->IsDirectory(assetFullPath.c_str());
+ if (!fileExists)
+ {
+ return Failure(AZStd::string::format(
+ "asset [%s] from shader source %s and subId %d doesn't exist", assetFullPath.c_str(), shaderJsonPath.c_str(),
+ (uint32_t)shaderAssetSubId));
+ }
+ return AZ::Success(assetFullPath);
+ }
+
Outcome ObtainBuildArtifactsFromAzslBuilder([[maybe_unused]] const char* builderName, const AZStd::string& sourceFullPath, RHI::APIType apiType, const AZStd::string& platform)
{
AzslSubProducts::Paths products;
@@ -619,6 +830,7 @@ namespace AZ
return AZ::Success(products);
}
+ // DEPRECATED [ATOM-15472]
// See header for info.
// REMARK: The approach to string searching and matching done in this function is kind of naive
// because the strings can match text within a comment block, etc. So it is not 100% fool proof.
@@ -672,6 +884,399 @@ namespace AZ
return SrgSkipFileResult::ContinueProcess;
}
+
+ RHI::Ptr BuildPipelineLayoutDescriptorForApi(
+ const char* builderName, const RPI::ShaderResourceGroupLayoutList& srgLayoutList, const MapOfStringToStageType& shaderEntryPoints,
+ const RHI::ShaderCompilerArguments& shaderCompilerArguments, const RootConstantData& rootConstantData,
+ RHI::ShaderPlatformInterface* shaderPlatformInterface, BindingDependencies& bindingDependencies /*inout*/)
+ {
+ PruneNonEntryFunctions(bindingDependencies, shaderEntryPoints);
+
+ // Translates from a list of function names that use a resource to a shader stage mask.
+ auto getRHIShaderStageMask = [&shaderEntryPoints](const BindingDependencies::FunctionsNameVector& functions) {
+ RHI::ShaderStageMask mask = RHI::ShaderStageMask::None;
+ // Iterate through all the functions that are using the resource.
+ for (const auto& functionName : functions)
+ {
+ // Search the function name into the list of valid entry points into the shader.
+ auto findId =
+ AZStd::find_if(shaderEntryPoints.begin(), shaderEntryPoints.end(), [&functionName, &mask](const auto& item) {
+ return item.first == functionName;
+ });
+
+ if (findId != shaderEntryPoints.end())
+ {
+ // Use the entry point shader stage type to calculate the mask.
+ RHI::ShaderHardwareStage hardwareStage = ToAssetBuilderShaderType(findId->second);
+ mask |= static_cast(AZ_BIT(static_cast(RHI::ToRHIShaderStage(hardwareStage))));
+ }
+ }
+
+ return mask;
+ };
+
+ // Build general PipelineLayoutDescriptor data that is provided for all platforms
+ RHI::Ptr pipelineLayoutDescriptor =
+ shaderPlatformInterface->CreatePipelineLayoutDescriptor();
+ RHI::ShaderPlatformInterface::ShaderResourceGroupInfoList srgInfos;
+ for (const auto& srgLayout : srgLayoutList)
+ {
+ // Search the binding info for a Shader Resource Group.
+ AZStd::string_view srgName = srgLayout->GetName().GetStringView();
+ const BindingDependencies::SrgResources* srgResources = bindingDependencies.GetSrg(srgName);
+ if (!srgResources)
+ {
+ AZ_Error(builderName, false, "SRG %s not found in the dependency dataset", srgName.data());
+ return nullptr;
+ }
+
+ RHI::ShaderResourceGroupBindingInfo srgBindingInfo;
+ srgBindingInfo.m_spaceId = srgResources->m_registerSpace;
+ const RHI::ShaderResourceGroupLayout* layout = srgLayout.get();
+ // Calculate the binding in for the constant data. All constant data share the same binding info.
+ srgBindingInfo.m_constantDataBindingInfo = {
+ getRHIShaderStageMask(srgResources->m_srgConstantsDependencies.m_binding.m_dependentFunctions),
+ srgResources->m_srgConstantsDependencies.m_binding.m_registerId};
+ // Calculate the binding info for each resource of the Shader Resource Group.
+ for (auto const& resource : srgResources->m_resources)
+ {
+ auto const& resourceInfo = resource.second;
+ srgBindingInfo.m_resourcesRegisterMap.insert(
+ {AZ::Name(resourceInfo.m_selfName),
+ RHI::ResourceBindingInfo(
+ getRHIShaderStageMask(resourceInfo.m_dependentFunctions), resourceInfo.m_registerId)});
+ }
+ pipelineLayoutDescriptor->AddShaderResourceGroupLayoutInfo(*layout, srgBindingInfo);
+ srgInfos.push_back(RHI::ShaderPlatformInterface::ShaderResourceGroupInfo{layout, srgBindingInfo});
+ }
+
+ RHI::Ptr rootConstantsLayout = RHI::ConstantsLayout::Create();
+ for (const auto& constantData : rootConstantData.m_constants)
+ {
+ RHI::ShaderInputConstantDescriptor rootConstantDesc(
+ constantData.m_nameId, constantData.m_constantByteOffset, constantData.m_constantByteSize,
+ rootConstantData.m_bindingInfo.m_registerId);
+
+ rootConstantsLayout->AddShaderInput(rootConstantDesc);
+ }
+
+
+ if (!rootConstantsLayout->Finalize())
+ {
+ AZ_Error(builderName, false, "Failed to finalize root constants layout");
+ return nullptr;
+ }
+
+ pipelineLayoutDescriptor->SetRootConstantsLayout(*rootConstantsLayout);
+
+ RHI::ShaderPlatformInterface::RootConstantsInfo rootConstantInfo;
+ rootConstantInfo.m_spaceId = rootConstantData.m_bindingInfo.m_space;
+ rootConstantInfo.m_registerId = rootConstantData.m_bindingInfo.m_registerId;
+ rootConstantInfo.m_totalSizeInBytes = rootConstantsLayout->GetDataSize();
+
+ // Build platform-specific PipelineLayoutDescriptor data, and finalize
+ if (!shaderPlatformInterface->BuildPipelineLayoutDescriptor(
+ pipelineLayoutDescriptor, srgInfos, rootConstantInfo, shaderCompilerArguments))
+ {
+ AZ_Error(builderName, false, "Failed to build pipeline layout descriptor");
+ return nullptr;
+ }
+
+ return pipelineLayoutDescriptor;
+ }
+
+ static bool IsSystemValueSemantic(const AZStd::string_view semantic)
+ {
+ // https://docs.microsoft.com/en-us/windows/win32/direct3dhlsl/dx-graphics-hlsl-semantics#system-value-semantics
+ return AzFramework::StringFunc::StartsWith(semantic, "sv_", false);
+ }
+
+ static bool CreateShaderInputContract(
+ const AzslData& azslData,
+ const AZStd::string& vertexShaderName,
+ const RPI::ShaderOptionGroupLayout& shaderOptionGroupLayout,
+ const AZStd::string& pathToIaJson,
+ RPI::ShaderInputContract& contract)
+ {
+ StructData inputStruct;
+ inputStruct.m_id = "";
+
+ auto jsonOutcome = JsonSerializationUtils::ReadJsonFile(pathToIaJson);
+ if (!jsonOutcome.IsSuccess())
+ {
+ AZ_Error(ShaderBuilderUtilityName, false, "%s", jsonOutcome.GetError().c_str());
+ return AssetBuilderSDK::ProcessJobResult_Failed;
+ }
+
+ AzslCompiler azslc(azslData.m_preprocessedFullPath);
+ if (!azslc.ParseIaPopulateStructData(jsonOutcome.GetValue(), vertexShaderName, inputStruct))
+ {
+ AZ_Error(ShaderBuilderUtilityName, false, "Failed to parse input layout\n");
+ return false;
+ }
+
+ if (inputStruct.m_id.empty())
+ {
+ AZ_Error(
+ ShaderBuilderUtilityName, false, "Failed to find the input struct for vertex shader %s.",
+ vertexShaderName.c_str());
+ return false;
+ }
+
+ for (const auto& member : inputStruct.m_members)
+ {
+ RHI::ShaderSemantic streamChannelSemantic{Name{member.m_semanticText}, static_cast(member.m_semanticIndex)};
+
+ // Semantics that represent a system-generated value do not map to an input stream
+ if (IsSystemValueSemantic(streamChannelSemantic.m_name.GetStringView()))
+ {
+ continue;
+ }
+
+ contract.m_streamChannels.push_back();
+ contract.m_streamChannels.back().m_semantic = streamChannelSemantic;
+
+ if (member.m_variable.m_typeModifier == MatrixMajor::ColumnMajor)
+ {
+ contract.m_streamChannels.back().m_componentCount = member.m_variable.m_cols;
+ }
+ else
+ {
+ contract.m_streamChannels.back().m_componentCount = member.m_variable.m_rows;
+ }
+
+ // [GFX_TODO][ATOM-14475]: Come up with a more elegant way to mark optional channels and their corresponding shader
+ // option
+ static const char OptionalInputStreamPrefix[] = "m_optional_";
+ if (AzFramework::StringFunc::StartsWith(member.m_variable.m_name, OptionalInputStreamPrefix, true))
+ {
+ AZStd::string expectedOptionName = AZStd::string::format(
+ "o_%s_isBound", member.m_variable.m_name.substr(strlen(OptionalInputStreamPrefix)).c_str());
+
+ RPI::ShaderOptionIndex shaderOptionIndex = shaderOptionGroupLayout.FindShaderOptionIndex(Name{expectedOptionName});
+ if (!shaderOptionIndex.IsValid())
+ {
+ AZ_Error(
+ ShaderBuilderUtilityName, false, "Shader option '%s' not found for optional input stream '%s'",
+ expectedOptionName.c_str(), member.m_variable.m_name.c_str());
+ return false;
+ }
+
+ const RPI::ShaderOptionDescriptor& option = shaderOptionGroupLayout.GetShaderOption(shaderOptionIndex);
+ if (option.GetType() != RPI::ShaderOptionType::Boolean)
+ {
+ AZ_Error(ShaderBuilderUtilityName, false, "Shader option '%s' must be a bool.", expectedOptionName.c_str());
+ return false;
+ }
+
+ if (option.GetDefaultValue().GetStringView() != "false")
+ {
+ AZ_Error(
+ ShaderBuilderUtilityName, false, "Shader option '%s' must default to false.",
+ expectedOptionName.c_str());
+ return false;
+ }
+
+ contract.m_streamChannels.back().m_isOptional = true;
+ contract.m_streamChannels.back().m_streamBoundIndicatorIndex = shaderOptionIndex;
+ }
+ }
+
+ return true;
+ }
+
+ static bool CreateShaderOutputContract(
+ const AzslData& azslData,
+ const AZStd::string& fragmentShaderName,
+ const AZStd::string& pathToOmJson,
+ RPI::ShaderOutputContract& contract)
+ {
+ StructData outputStruct;
+ outputStruct.m_id = "";
+
+ auto jsonOutcome = JsonSerializationUtils::ReadJsonFile(pathToOmJson);
+ if (!jsonOutcome.IsSuccess())
+ {
+ AZ_Error(ShaderBuilderUtilityName, false, "%s", jsonOutcome.GetError().c_str());
+ return AssetBuilderSDK::ProcessJobResult_Failed;
+ }
+
+ AzslCompiler azslc(azslData.m_preprocessedFullPath);
+ if (!azslc.ParseOmPopulateStructData(jsonOutcome.GetValue(), fragmentShaderName, outputStruct))
+ {
+ AZ_Error(ShaderBuilderUtilityName, false, "Failed to parse output layout\n");
+ return false;
+ }
+
+ for (const auto& member : outputStruct.m_members)
+ {
+ RHI::ShaderSemantic semantic = RHI::ShaderSemantic::Parse(member.m_semanticText);
+
+ bool depthFound = false;
+
+ if (semantic.m_name.GetStringView() == "SV_Target")
+ {
+ contract.m_requiredColorAttachments.push_back();
+ // Render targets only support 1-D vector types and those are always column-major (per DXC)
+ contract.m_requiredColorAttachments.back().m_componentCount = member.m_variable.m_cols;
+ }
+ else if (
+ semantic.m_name.GetStringView() == "SV_Depth" || semantic.m_name.GetStringView() == "SV_DepthGreaterEqual" ||
+ semantic.m_name.GetStringView() == "SV_DepthLessEqual")
+ {
+ if (depthFound)
+ {
+ AZ_Error(
+ ShaderBuilderUtilityName, false,
+ "SV_Depth specified more than once in the fragment shader output structure");
+ return false;
+ }
+ depthFound = true;
+ }
+ else
+ {
+ AZ_Error(
+ ShaderBuilderUtilityName, false, "Unsupported shader output semantic '%s'.", semantic.m_name.GetCStr());
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ bool CreateShaderInputAndOutputContracts(
+ const AzslData& azslData,
+ const MapOfStringToStageType& shaderEntryPoints,
+ const RPI::ShaderOptionGroupLayout& shaderOptionGroupLayout,
+ const AZStd::string& pathToOmJson,
+ const AZStd::string& pathToIaJson,
+ RPI::ShaderInputContract& shaderInputContract,
+ RPI::ShaderOutputContract& shaderOutputContract,
+ size_t& colorAttachmentCount)
+ {
+ bool success = true;
+ for (const auto& shaderEntryPoint : shaderEntryPoints)
+ {
+ auto shaderEntryName = shaderEntryPoint.first;
+ auto shaderStageType = shaderEntryPoint.second;
+
+ if (shaderStageType == RPI::ShaderStageType::Vertex)
+ {
+ const bool layoutCreated = CreateShaderInputContract(azslData, shaderEntryName, shaderOptionGroupLayout, pathToIaJson, shaderInputContract);
+ if (!layoutCreated)
+ {
+ success = false;
+ AZ_Error(
+ ShaderBuilderUtilityName, false, "Could not create the input contract for the vertex function %s",
+ shaderEntryName.c_str());
+ continue; // Using continue to report all the errors found
+ }
+ }
+
+ if (shaderStageType == RPI::ShaderStageType::Fragment)
+ {
+ const bool layoutCreated =
+ CreateShaderOutputContract(azslData, shaderEntryName, pathToOmJson, shaderOutputContract);
+ if (!layoutCreated)
+ {
+ success = false;
+ AZ_Error(
+ ShaderBuilderUtilityName, false, "Could not create the output contract for the fragment function %s",
+ shaderEntryName.c_str());
+ continue; // Using continue to report all the errors found
+ }
+
+ colorAttachmentCount = shaderOutputContract.m_requiredColorAttachments.size();
+ }
+ }
+ return success;
+ }
+
+
+ //! Returns a list of acceptable default entry point names
+ static void GetAcceptableDefaultEntryPoints(
+ const AZStd::vector& azslFunctionDataList,
+ AZStd::unordered_map& defaultEntryPoints)
+ {
+ for (const auto& func : azslFunctionDataList)
+ {
+ if (!func.m_hasShaderStageVaryings)
+ {
+ // Not declaring any semantics for a shader entry is valid, but unusual.
+ // A shader entry with no semantics must be explicitly listed and won't be selected by default.
+ continue;
+ }
+
+ if (func.m_name.starts_with("VS") || func.m_name.ends_with("VS"))
+ {
+ defaultEntryPoints[func.m_name] = RPI::ShaderStageType::Vertex;
+ AZ_TracePrintf(
+ ShaderBuilderUtilityName, "Assuming \"%s\" is a valid Vertex shader entry point.\n", func.m_name.c_str());
+ }
+ else if (func.m_name.starts_with("PS") || func.m_name.ends_with("PS"))
+ {
+ defaultEntryPoints[func.m_name] = RPI::ShaderStageType::Fragment;
+ AZ_TracePrintf(
+ ShaderBuilderUtilityName, "Assuming \"%s\" is a valid Fragment shader entry point.\n",
+ func.m_name.c_str());
+ }
+ else if (func.m_name.starts_with("CS") || func.m_name.ends_with("CS"))
+ {
+ defaultEntryPoints[func.m_name] = RPI::ShaderStageType::Compute;
+ AZ_TracePrintf(
+ ShaderBuilderUtilityName, "Assuming \"%s\" is a valid Compute shader entry point.\n", func.m_name.c_str());
+ }
+ }
+ }
+
+
+ // DEPRECATED [ATOM-15472
+ //! Returns a list of acceptable default entry point names
+ //! This function
+ static void GetAcceptableDefaultEntryPoints(
+ const AzslData& azslData, AZStd::unordered_map& defaultEntryPoints)
+ {
+ return GetAcceptableDefaultEntryPoints(azslData.m_functions, defaultEntryPoints);
+ }
+
+
+ void GetDefaultEntryPointsFromFunctionDataList(
+ const AZStd::vector azslFunctionDataList,
+ AZStd::unordered_map& shaderEntryPoints)
+ {
+ AZStd::unordered_map defaultEntryPoints;
+ GetAcceptableDefaultEntryPoints(azslFunctionDataList, defaultEntryPoints);
+
+ for (const auto& functionData : azslFunctionDataList)
+ {
+ for (const auto& defaultEntryPoint : defaultEntryPoints)
+ {
+ // Equal defaults to case insensitive compares...
+ if (AzFramework::StringFunc::Equal(defaultEntryPoint.first.c_str(), functionData.m_name.c_str()))
+ {
+ shaderEntryPoints[defaultEntryPoint.first] = defaultEntryPoint.second;
+ break; // stop looping default entry points and go to the next shader function
+ }
+ }
+ }
+ }
+
+ AZStd::string GetAcceptableDefaultEntryPointNames(const AzslData& azslData)
+ {
+ AZStd::unordered_map defaultEntryPointList;
+ GetAcceptableDefaultEntryPoints(azslData, defaultEntryPointList);
+
+ AZStd::vector defaultEntryPointNamesList;
+ for (const auto& shaderEntryPoint : defaultEntryPointList)
+ {
+ defaultEntryPointNamesList.push_back(shaderEntryPoint.first);
+ }
+ AZStd::string shaderEntryPoints;
+ AzFramework::StringFunc::Join(
+ shaderEntryPoints, defaultEntryPointNamesList.begin(), defaultEntryPointNamesList.end(), ", ");
+ return AZStd::move(shaderEntryPoints);
+ }
+
} // namespace ShaderBuilderUtility
} // namespace ShaderBuilder
} // AZ
diff --git a/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderBuilderUtility.h b/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderBuilderUtility.h
index d6926d0086..e31c6c70a1 100644
--- a/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderBuilderUtility.h
+++ b/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderBuilderUtility.h
@@ -18,8 +18,10 @@
#include
#include
+#include
#include
+#include "AzslData.h"
namespace AZ
{
@@ -27,7 +29,6 @@ namespace AZ
{
class AzslCompiler;
struct ShaderFiles;
- struct AzslData;
struct BindingDependencies;
struct RootConstantData;
@@ -40,8 +41,6 @@ namespace AZ
void GetAbsolutePathToAzslFile(const AZStd::string& shaderTemplatePathAndFile, AZStd::string specifiedShaderPathAndName, AZStd::string& absoluteShaderPath);
- uint32_t MakeDebugByproductSubId(RHI::APIType apiType, const AZStd::string& productFileName);
-
//! Opens and read the .shader, returns expanded file paths
AZStd::shared_ptr PrepareSourceInput(
const char* builderName,
@@ -54,13 +53,20 @@ namespace AZ
using SubId = RPI::ShaderAssetSubId;
// product sub id enumerators:
- static constexpr SubId SubList[] = { SubId::PostPreprocessingPureAzsl, SubId::IaJson, SubId::OmJson, SubId::SrgJson, SubId::OptionsJson, SubId::BindingdepJson, SubId::GeneratedSource };
+ static constexpr SubId SubList[] = {SubId::PostPreprocessingPureAzsl,
+ SubId::IaJson,
+ SubId::OmJson,
+ SubId::SrgJson,
+ SubId::OptionsJson,
+ SubId::BindingdepJson,
+ SubId::GeneratedHlslSource};
// in the same order, their file name suffix (they replicate what's in AzslcMain.cpp. and hlsl corresponds to what's in AzslBuilder.cpp)
// a type to declare variables holding the full paths of their files
using Paths = AZStd::fixed_vector;
};
+ //! [GFX TODO] [ATOM-15472] Deprecated, remove when this ticket is addressed.
//! Collects and generates the necessary data for compiling a shader.
//! @azslData must have paths correctly set.
//! shaderOptionGroupLayout, azslData, srgAssets get the output data.
@@ -74,6 +80,16 @@ namespace AZ
RootConstantData& rootConstantData
);
+ //! Collects all the JSON files generated during AZSL compilation and loads the data as objects.
+ //! @azslData must have paths correctly set.
+ //! @azslData, @srgLayoutList, @shaderOptionGroupLayout, @bindingDependencies and @rootConstantData get the output data.
+ AssetBuilderSDK::ProcessJobResultCode PopulateAzslDataFromJsonFiles(
+ const char* builderName, const AzslSubProducts::Paths& pathOfJsonFiles,
+ const bool platformUsesRegisterSpaces, AzslData& azslData,
+ RPI::ShaderResourceGroupLayoutList& srgLayoutList, RPI::Ptr shaderOptionGroupLayout,
+ BindingDependencies& bindingDependencies, RootConstantData& rootConstantData);
+
+
RHI::ShaderHardwareStage ToAssetBuilderShaderType(RPI::ShaderStageType stageType);
//! Must be called before shaderPlatformInterface->CompilePlatformInternal()
@@ -82,7 +98,7 @@ namespace AZ
//! The pipeline layout descriptor is returned, but the same data will also be set into the @shaderPlatformInterface
//! object, which is why it is important to call this method before calling shaderPlatformInterface->CompilePlatformInternal().
RHI::Ptr BuildPipelineLayoutDescriptorForApi(
- const char* BuilderName,
+ const char* builderName,
RHI::ShaderPlatformInterface* shaderPlatformInterface,
BindingDependencies& bindingDependencies /*inout*/,
const ShaderResourceGroupAssets& srgAssets,
@@ -91,6 +107,33 @@ namespace AZ
const RootConstantData* rootConstantData = nullptr
);
+
+ //! Must be called before shaderPlatformInterface->CompilePlatformInternal()
+ //! This function will prune non entry functions from BindingDependencies and use the
+ //! rest of input data to create a pipeline layout descriptor.
+ //! The pipeline layout descriptor is returned, but the same data will also be set into the @shaderPlatformInterface
+ //! object, which is why it is important to call this method before calling shaderPlatformInterface->CompilePlatformInternal().
+ RHI::Ptr BuildPipelineLayoutDescriptorForApi(
+ const char* builderName,
+ const RPI::ShaderResourceGroupLayoutList& srgLayoutList,
+ const MapOfStringToStageType& shaderEntryPoints,
+ const RHI::ShaderCompilerArguments& shaderCompilerArguments,
+ const RootConstantData& rootConstantData,
+ RHI::ShaderPlatformInterface* shaderPlatformInterface,
+ BindingDependencies& bindingDependencies /*inout*/);
+
+
+ bool CreateShaderInputAndOutputContracts(
+ const AzslData& azslData, const MapOfStringToStageType& shaderEntryPoints,
+ const RPI::ShaderOptionGroupLayout& shaderOptionGroupLayout, const AZStd::string& pathToOmJson,
+ const AZStd::string& pathToIaJson, RPI::ShaderInputContract& shaderInputContract,
+ RPI::ShaderOutputContract& shaderOutputContract, size_t& colorAttachmentCount);
+
+
+ //! Returns a list of acceptable default entry point names as a single string for debug messages.
+ AZStd::string GetAcceptableDefaultEntryPointNames(const AzslData& shaderData);
+
+
//! Create a file from a string's content.
//! That file will be named filename.api.azslin
//! This is meant to be used at this stage:
@@ -102,7 +145,8 @@ namespace AZ
const AZStd::string& preprocessedCode,
const AZStd::string& tempDirPath,
const AZStd::string& preprocessedFileName,
- const AZStd::string& apiTypeString = "");
+ const AZStd::string& apiTypeString = "",
+ bool add2 = false); // [GFX TODO] Remove add2 when [ATOM-15472]
//! Create a file from a string's content.
//! That file will be named filename.api.azsl.prepend
@@ -121,12 +165,30 @@ namespace AZ
AZStd::string ExtractStemName(const char* path);
AZStd::vector DiscoverValidShaderPlatformInterfaces(const AssetBuilderSDK::PlatformInfo& info);
+ AZStd::vector DiscoverEnabledShaderPlatformInterfaces(
+ const AssetBuilderSDK::PlatformInfo& info, const RPI::ShaderSourceData& shaderSourceData);
+
+ // The idea is that the "Supervariants" json property is optional in .shader files,
+ // For cases when it is not specified, this function will return a vector with one item, the default, nameless, supervariant.
+ // If "Supervariants" is not empty, then this function will make sure the first supervariant in the list
+ // is the default, nameless, supervariant.
+ AZStd::vector GetSupervariantListFromShaderSourceData(
+ const RPI::ShaderSourceData& shaderSourceData);
+
+ void GetDefaultEntryPointsFromFunctionDataList(
+ const AZStd::vector azslFunctionDataList,
+ AZStd::unordered_map& shaderEntryPoints);
void LogProfilingData(const char* builderName, AZStd::string_view shaderPath);
//! Job products sub id generation helper for AzslBuilder
uint32_t MakeAzslBuildProductSubId(RPI::ShaderAssetSubId subId, RHI::APIType apiType);
+ //! Returns the asset path of a product artifact produced by ShaderAssetBuilder2.
+ Outcome ObtainBuildArtifactPathFromShaderAssetBuilder2(
+ const uint32_t rhiUniqueIndex, const AZStd::string& platformIdentifier, const AZStd::string& shaderJsonPath,
+ const uint32_t supervariantIndex, RPI::ShaderAssetSubId shaderAssetSubId);
+
//! Reconstructs the expected output product paths of the AzslBuilder (from the 2 arguments @azslSourceFullPath and @apiType)
Outcome ObtainBuildArtifactsFromAzslBuilder(const char* builderName, const AZStd::string& azslSourceFullPath, RHI::APIType apiType, const AZStd::string& platform);
diff --git a/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderVariantAssetBuilder.cpp b/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderVariantAssetBuilder.cpp
index 02cb8bd242..7114b50906 100644
--- a/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderVariantAssetBuilder.cpp
+++ b/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderVariantAssetBuilder.cpp
@@ -276,12 +276,6 @@ namespace AZ
return LoadResult{LoadResult::Code::DeferredError, AZStd::string::format("ShaderSourceData file does not exist: %s.", shaderSourceFileFullPath.c_str())};
}
- // Let's open the shader source, because We need the source code of its AZSL file
- auto outcomeShaderData = ShaderBuilderUtility::LoadShaderDataJson(shaderSourceFileFullPath);
- if (!outcomeShaderData.IsSuccess())
- {
- return LoadResult{LoadResult::Code::DeferredError, AZStd::string::format("Failed to parse Shader Descriptor JSON: %s", outcomeShaderData.GetError().c_str())};
- }
return LoadResult{LoadResult::Code::Success};
} // LoadShaderVariantListAndAzslSource
@@ -420,15 +414,6 @@ namespace AZ
return;
}
- if (jobParameters.find(ShouldExitEarlyFromProcessJobParam) != jobParameters.end())
- {
- AZ_TracePrintf(
- ShaderVariantAssetBuilderName, "Doing nothing on behalf of [%s] because it's been overriden by game project.",
- jobParameters.at(ShaderVariantLoadErrorParam).c_str());
- response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
- return;
- }
-
AssetBuilderSDK::JobCancelListener jobCancelListener(request.m_jobId);
if (jobCancelListener.IsCancelled())
{
@@ -589,7 +574,7 @@ namespace AZ
if (shaderSourceDataDescriptor.m_programSettings.m_entryPoints.empty())
{
AZ_TracePrintf(ShaderVariantAssetBuilderName, "ProgramSettings do not specify entry points, will use GetDefaultEntryPointsFromShader()\n");
- ShaderVariantAssetBuilder::GetDefaultEntryPointsFromAzslData(azslData, shaderEntryPoints);
+ ShaderBuilderUtility::GetDefaultEntryPointsFromFunctionDataList(azslData.m_functions, shaderEntryPoints);
}
else
{
@@ -778,7 +763,7 @@ namespace AZ
}
// Time to save the asset in the cache tmp folder.
- const uint32_t productSubID = RPI::ShaderVariantAsset::GetAssetSubId(shaderPlatformInterface->GetAPIUniqueIndex(), shaderVariantAsset->GetStableId());
+ const uint32_t productSubID = RPI::ShaderVariantAsset::MakeAssetProductSubId(shaderPlatformInterface->GetAPIUniqueIndex(), shaderVariantAsset->GetStableId());
AssetBuilderSDK::JobProduct assetProduct;
if (!SerializeOutShaderVariantAsset(shaderVariantAsset, shaderSourceFileFullPath, request.m_tempDirPath, *shaderPlatformInterface, productSubID, assetProduct))
{
@@ -788,12 +773,14 @@ namespace AZ
response.m_outputProducts.push_back(assetProduct);
// add byproducts as job output products:
+ uint32_t subProductType = aznumeric_cast(RPI::ShaderAssetSubId::GeneratedHlslSource) + 1;
for (const AZStd::string& byproduct : byproducts.m_intermediatePaths)
{
AssetBuilderSDK::JobProduct jobProduct;
jobProduct.m_productFileName = byproduct;
jobProduct.m_productAssetType = Uuid::CreateName("DebugInfoByProduct-PdbOrDxilTxt");
- jobProduct.m_productSubID = ShaderBuilderUtility::MakeDebugByproductSubId(shaderPlatformInterface->GetAPIType(), byproduct);
+ jobProduct.m_productSubID = RPI::ShaderVariantAsset::MakeAssetProductSubId(
+ shaderPlatformInterface->GetAPIType(), shaderVariantAsset->GetStableId(), subProductType++);
response.m_outputProducts.push_back(AZStd::move(jobProduct));
}
}
@@ -801,53 +788,6 @@ namespace AZ
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
}
-
- /// Returns a list of acceptable default entry point names
- static void GetAcceptableDefaultEntryPoints(const AzslData& shaderData, AZStd::unordered_map& defaultEntryPoints)
- {
- for (const auto& func : shaderData.m_topData.m_functions)
- {
- if (!func.m_hasShaderStageVaryings)
- {
- // Not declaring any semantics for a shader entry is valid, but unusual.
- // A shader entry with no semantics must be explicitly listed and won't be selected by default.
- continue;
- }
-
- if (func.m_name.starts_with("VS") || func.m_name.ends_with("VS"))
- {
- defaultEntryPoints[func.m_name] = RPI::ShaderStageType::Vertex;
- AZ_TracePrintf(ShaderVariantAssetBuilderName, "Assuming \"%s\" is a valid Vertex shader entry point.\n", func.m_name.c_str());
- }
- else if (func.m_name.starts_with("PS") || func.m_name.ends_with("PS"))
- {
- defaultEntryPoints[func.m_name] = RPI::ShaderStageType::Fragment;
- AZ_TracePrintf(ShaderVariantAssetBuilderName, "Assuming \"%s\" is a valid Fragment shader entry point.\n", func.m_name.c_str());
- }
- else if (func.m_name.starts_with("CS") || func.m_name.ends_with("CS"))
- {
- defaultEntryPoints[func.m_name] = RPI::ShaderStageType::Compute;
- AZ_TracePrintf(ShaderVariantAssetBuilderName, "Assuming \"%s\" is a valid Compute shader entry point.\n", func.m_name.c_str());
- }
- }
- }
-
- /// Returns a list of acceptable default entry point names as a single string for messages
- static AZStd::string GetAcceptableDefaultEntryPointNames(const AzslData& shaderData)
- {
- AZStd::unordered_map defaultEntryPointList;
- GetAcceptableDefaultEntryPoints(shaderData, defaultEntryPointList);
-
- AZStd::vector defaultEntryPointNamesList;
- for (const auto& shaderEntryPoint : defaultEntryPointList)
- {
- defaultEntryPointNamesList.push_back(shaderEntryPoint.first);
- }
- AZStd::string shaderEntryPoints;
- AzFramework::StringFunc::Join(shaderEntryPoints, defaultEntryPointNamesList.begin(), defaultEntryPointNamesList.end(), ", ");
- return AZStd::move(shaderEntryPoints);
- }
-
static bool CreateShaderVariant(
ShaderVariantCreationContext& variantCreationContext,
const AzslData& azslData,
@@ -945,7 +885,7 @@ namespace AZ
if (!hasRasterProgram && !hasComputeProgram && !hasRayTracingProgram)
{
- AZStd::string entryPointNames = GetAcceptableDefaultEntryPointNames(azslData);
+ AZStd::string entryPointNames = ShaderBuilderUtility::GetAcceptableDefaultEntryPointNames(azslData);
AZ_Error(ShaderVariantAssetBuilderName, false, "Shader asset descriptor has a program variant that does not define any entry points. Either declare entry points in the .shader file, or use one of the available default names (not case-sensitive): [%s]", entryPointNames.data());
@@ -990,198 +930,6 @@ namespace AZ
return isVariantValid;
}
- static bool IsSystemValueSemantic(const AZStd::string_view semantic)
- {
- // https://docs.microsoft.com/en-us/windows/win32/direct3dhlsl/dx-graphics-hlsl-semantics#system-value-semantics
- return AzFramework::StringFunc::StartsWith(semantic, "sv_", false);
- }
-
- static bool CreateShaderInputContract(
- const AzslData& azslData,
- const AZStd::string& vertexShaderName,
- const RPI::ShaderOptionGroupLayout& shaderOptionGroupLayout,
- RPI::ShaderInputContract& contract,
- const AZStd::string& pathToIaJson)
- {
- StructData inputStruct;
- inputStruct.m_id = "";
-
- auto jsonOutcome = JsonSerializationUtils::ReadJsonFile(pathToIaJson);
- if (!jsonOutcome.IsSuccess())
- {
- AZ_Error(ShaderVariantAssetBuilderName, false, "%s", jsonOutcome.GetError().c_str());
- return AssetBuilderSDK::ProcessJobResult_Failed;
- }
-
- AzslCompiler azslc(azslData.m_preprocessedFullPath);
- if (!azslc.ParseIaPopulateStructData(jsonOutcome.GetValue(), vertexShaderName, inputStruct))
- {
- AZ_Error(ShaderVariantAssetBuilderName, false, "Failed to parse input layout\n");
- return false;
- }
-
- if (inputStruct.m_id.empty())
- {
- AZ_Error(ShaderVariantAssetBuilderName, false, "Failed to find the input struct for vertex shader %s.", vertexShaderName.c_str());
- return false;
- }
-
- for (const auto& member : inputStruct.m_members)
- {
- RHI::ShaderSemantic streamChannelSemantic{
- Name{ member.m_semanticText },
- static_cast(member.m_semanticIndex) };
-
- // Semantics that represent a system-generated value do not map to an input stream
- if (IsSystemValueSemantic(streamChannelSemantic.m_name.GetStringView()))
- {
- continue;
- }
-
- contract.m_streamChannels.push_back();
- contract.m_streamChannels.back().m_semantic = streamChannelSemantic;
-
- if (member.m_variable.m_typeModifier == MatrixMajor::ColumnMajor)
- {
- contract.m_streamChannels.back().m_componentCount = member.m_variable.m_cols;
- }
- else
- {
- contract.m_streamChannels.back().m_componentCount = member.m_variable.m_rows;
- }
-
- // [GFX_TODO][ATOM-14475]: Come up with a more elegant way to mark optional channels and their corresponding shader option
- static const char OptionalInputStreamPrefix[] = "m_optional_";
- if (AzFramework::StringFunc::StartsWith(member.m_variable.m_name, OptionalInputStreamPrefix, true))
- {
- AZStd::string expectedOptionName = AZStd::string::format("o_%s_isBound", member.m_variable.m_name.substr(strlen(OptionalInputStreamPrefix)).c_str());
-
- RPI::ShaderOptionIndex shaderOptionIndex = shaderOptionGroupLayout.FindShaderOptionIndex(Name{expectedOptionName});
- if (!shaderOptionIndex.IsValid())
- {
- AZ_Error(ShaderVariantAssetBuilderName, false, "Shader option '%s' not found for optional input stream '%s'", expectedOptionName.c_str(), member.m_variable.m_name.c_str());
- return false;
- }
-
- const RPI::ShaderOptionDescriptor& option = shaderOptionGroupLayout.GetShaderOption(shaderOptionIndex);
- if (option.GetType() != RPI::ShaderOptionType::Boolean)
- {
- AZ_Error(ShaderVariantAssetBuilderName, false, "Shader option '%s' must be a bool.", expectedOptionName.c_str());
- return false;
- }
-
- if (option.GetDefaultValue().GetStringView() != "false")
- {
- AZ_Error(ShaderVariantAssetBuilderName, false, "Shader option '%s' must default to false.", expectedOptionName.c_str());
- return false;
- }
-
- contract.m_streamChannels.back().m_isOptional = true;
- contract.m_streamChannels.back().m_streamBoundIndicatorIndex = shaderOptionIndex;
- }
- }
-
- return true;
- }
-
- static bool CreateShaderOutputContract(
- const AzslData& azslData,
- const AZStd::string& fragmentShaderName,
- RPI::ShaderOutputContract& contract,
- const AZStd::string& pathToOmJson)
- {
- StructData outputStruct;
- outputStruct.m_id = "";
-
- auto jsonOutcome = JsonSerializationUtils::ReadJsonFile(pathToOmJson);
- if (!jsonOutcome.IsSuccess())
- {
- AZ_Error(ShaderVariantAssetBuilderName, false, "%s", jsonOutcome.GetError().c_str());
- return AssetBuilderSDK::ProcessJobResult_Failed;
- }
-
- AzslCompiler azslc(azslData.m_preprocessedFullPath);
- if (!azslc.ParseOmPopulateStructData(jsonOutcome.GetValue(), fragmentShaderName, outputStruct))
- {
- AZ_Error(ShaderVariantAssetBuilderName, false, "Failed to parse output layout\n");
- return false;
- }
-
- for (const auto& member : outputStruct.m_members)
- {
- RHI::ShaderSemantic semantic = RHI::ShaderSemantic::Parse(member.m_semanticText);
-
- bool depthFound = false;
-
- if (semantic.m_name.GetStringView() == "SV_Target")
- {
- contract.m_requiredColorAttachments.push_back();
- // Render targets only support 1-D vector types and those are always column-major (per DXC)
- contract.m_requiredColorAttachments.back().m_componentCount = member.m_variable.m_cols;
- }
- else if (semantic.m_name.GetStringView() == "SV_Depth" ||
- semantic.m_name.GetStringView() == "SV_DepthGreaterEqual" ||
- semantic.m_name.GetStringView() == "SV_DepthLessEqual")
- {
- if (depthFound)
- {
- AZ_Error(ShaderVariantAssetBuilderName, false, "SV_Depth specified more than once in the fragment shader output structure");
- return false;
- }
- depthFound = true;
- }
- else
- {
- AZ_Error(ShaderVariantAssetBuilderName, false, "Unsupported shader output semantic '%s'.", semantic.m_name.GetCStr());
- return false;
- }
- }
-
- return true;
- }
-
- static bool CreateShaderInputAndOutputContracts(
- const AzslData& azslData,
- const MapOfStringToStageType& shaderEntryPoints,
- const RPI::ShaderOptionGroupLayout& shaderOptionGroupLayout,
- RPI::ShaderInputContract& shaderInputContract,
- RPI::ShaderOutputContract& shaderOutputContract,
- size_t& colorAttachmentCount,
- const AZStd::string& pathToOmJson,
- const AZStd::string& pathToIaJson)
- {
- bool success = true;
- for (const auto& shaderEntryPoint : shaderEntryPoints)
- {
- auto shaderEntryName = shaderEntryPoint.first;
- auto shaderStageType = shaderEntryPoint.second;
-
- if (shaderStageType == RPI::ShaderStageType::Vertex)
- {
- const bool layoutCreated = CreateShaderInputContract(azslData, shaderEntryName, shaderOptionGroupLayout, shaderInputContract, pathToIaJson);
- if (!layoutCreated)
- {
- success = false;
- AZ_Error(ShaderVariantAssetBuilderName, false, "Could not create the input contract for the vertex function %s", shaderEntryName.c_str());
- continue; // Using continue to report all the errors found
- }
- }
-
- if (shaderStageType == RPI::ShaderStageType::Fragment)
- {
- const bool layoutCreated = CreateShaderOutputContract(azslData, shaderEntryName, shaderOutputContract, pathToOmJson);
- if (!layoutCreated)
- {
- success = false;
- AZ_Error(ShaderVariantAssetBuilderName, false, "Could not create the output contract for the fragment function %s", shaderEntryName.c_str());
- continue; // Using continue to report all the errors found
- }
-
- colorAttachmentCount = shaderOutputContract.m_requiredColorAttachments.size();
- }
- }
- return success;
- }
AZ::Outcome, AZStd::string> ShaderVariantAssetBuilder::CreateShaderVariantAssetForAPI(
const RPI::ShaderVariantListSourceData::VariantInfo& variantInfo,
@@ -1195,8 +943,8 @@ namespace AZ
RPI::ShaderInputContract shaderInputContract;
RPI::ShaderOutputContract shaderOutputContract;
size_t colorAttachmentCount = 0;
- CreateShaderInputAndOutputContracts(azslData, variantCreationContext.m_shaderEntryPoints, variantCreationContext.m_shaderOptionGroupLayout,
- shaderInputContract, shaderOutputContract, colorAttachmentCount, pathToOmJson, pathToIaJson);
+ ShaderBuilderUtility::CreateShaderInputAndOutputContracts(azslData, variantCreationContext.m_shaderEntryPoints, variantCreationContext.m_shaderOptionGroupLayout, pathToOmJson,
+ pathToIaJson, shaderInputContract, shaderOutputContract, colorAttachmentCount);
const RPI::ShaderOptionGroupLayout& shaderOptionGroupLayout = variantCreationContext.m_shaderOptionGroupLayout;
// Temporary structure used for sorting and caching intermediate results
@@ -1284,25 +1032,6 @@ namespace AZ
return AZ::Success(AZStd::move(shaderVariantAsset));
}
- void ShaderVariantAssetBuilder::GetDefaultEntryPointsFromAzslData(const AzslData& shaderData, AZStd::unordered_map& shaderEntryPoints)
- {
- AZStd::unordered_map defaultEntryPoints;
- GetAcceptableDefaultEntryPoints(shaderData, defaultEntryPoints);
-
- for (const auto& functionData : shaderData.m_topData.m_functions)
- {
- for (const auto& defaultEntryPoint : defaultEntryPoints)
- {
- // Equal defaults to case insensitive compares...
- if (AzFramework::StringFunc::Equal(defaultEntryPoint.first.c_str(), functionData.m_name.c_str()))
- {
- shaderEntryPoints[defaultEntryPoint.first] = defaultEntryPoint.second;
- break; // stop looping default entry points and go to the next shader function
- }
- }
- }
- }
-
bool ShaderVariantAssetBuilder::SerializeOutShaderVariantAsset(const Data::Asset shaderVariantAsset, const AZStd::string& shaderSourceFileFullPath, const AZStd::string& tempDirPath,
const RHI::ShaderPlatformInterface& shaderPlatformInterface, const uint32_t productSubID, AssetBuilderSDK::JobProduct& assetProduct)
{
diff --git a/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderVariantAssetBuilder.h b/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderVariantAssetBuilder.h
index 7dd76f7ef1..84ab4fbc70 100644
--- a/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderVariantAssetBuilder.h
+++ b/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderVariantAssetBuilder.h
@@ -73,8 +73,6 @@ namespace AZ
static bool SerializeOutShaderVariantAsset(const Data::Asset shaderVariantAsset, const AZStd::string& shaderFullPath, const AZStd::string& tempDirPath,
const RHI::ShaderPlatformInterface& shaderPlatformInterface, const uint32_t productSubID, AssetBuilderSDK::JobProduct& assetProduct);
- static void GetDefaultEntryPointsFromAzslData(const AzslData& shaderData, AZStd::unordered_map& shaderEntryPoints);
-
// AssetBuilderSDK::AssetBuilderCommandBus interface overrides ...
void ShutDown() override { };
diff --git a/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderVariantAssetBuilder2.cpp b/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderVariantAssetBuilder2.cpp
new file mode 100644
index 0000000000..961e54c7a8
--- /dev/null
+++ b/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderVariantAssetBuilder2.cpp
@@ -0,0 +1,978 @@
+/*
+* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
+* its licensors.
+*
+* For complete copyright and license terms please see the LICENSE at the root of this
+* distribution (the "License"). All use of this software is governed by the License,
+* or, if provided, by the license below or the license accompanying this file. Do not
+* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+*/
+
+#include