diff --git a/AutomatedTesting/Gem/Code/runtime_dependencies.cmake b/AutomatedTesting/Gem/Code/runtime_dependencies.cmake index 33c2bf8d5f..15715f2136 100644 --- a/AutomatedTesting/Gem/Code/runtime_dependencies.cmake +++ b/AutomatedTesting/Gem/Code/runtime_dependencies.cmake @@ -43,6 +43,7 @@ set(GEM_DEPENDENCIES Gem::GradientSignal Gem::Vegetation Gem::Atom_AtomBridge + Gem::AtomFont Gem::NvCloth Gem::Blast Gem::AWSCore diff --git a/AutomatedTesting/Gem/Code/tool_dependencies.cmake b/AutomatedTesting/Gem/Code/tool_dependencies.cmake index c8eccab947..1c0db5753b 100644 --- a/AutomatedTesting/Gem/Code/tool_dependencies.cmake +++ b/AutomatedTesting/Gem/Code/tool_dependencies.cmake @@ -55,6 +55,7 @@ set(GEM_DEPENDENCIES Gem::Atom_RHI.Private Gem::Atom_Feature_Common.Editor Gem::Atom_AtomBridge.Editor + Gem::AtomFont Gem::NvCloth.Editor Gem::Blast.Editor Gem::AWSCore.Editor 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/Prefab/PrefabPublicHandler.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp index e6bb8c7dee..571aea5875 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp @@ -84,6 +84,7 @@ namespace AzToolsFramework AZStd::vector entities; AZStd::vector> instances; + AZStd::unordered_map nestedInstanceLinkPatchesMap; // Retrieve all entities affected and identify Instances if (!RetrieveAndSortPrefabEntitiesAndInstances(inputEntityList, commonRootEntityOwningInstance->get(), entities, instances)) @@ -96,6 +97,16 @@ namespace AzToolsFramework // target templates of the other instances. for (auto& nestedInstance : instances) { + auto linkRef = m_prefabSystemComponentInterface->FindLink(nestedInstance->GetLinkId()); + + if (linkRef.has_value()) + { + PrefabDom oldLinkPatches; + oldLinkPatches.CopyFrom(linkRef->get().GetLinkDom(), oldLinkPatches.GetAllocator()); + + nestedInstanceLinkPatchesMap.emplace(nestedInstance.get(), AZStd::move(oldLinkPatches)); + } + RemoveLink(nestedInstance, commonRootEntityOwningInstance->get().GetTemplateId(), undoBatch.GetUndoBatch()); } @@ -122,6 +133,9 @@ namespace AzToolsFramework AZ::EntityId containerEntityId = instanceToCreate->get().GetContainerEntityId(); + // Apply the correct transform to the container for the new instance, and store the patch for use when creating the link. + PrefabDom patch = ApplyContainerTransformAndGeneratePatch(containerEntityId, commonRootEntityId, topLevelEntities); + // Parent the non-container top level entities to the container entity. // Parenting the top level container entities will be done during the creation of links. for (AZ::Entity* topLevelEntity : topLevelEntities) @@ -141,35 +155,55 @@ namespace AzToolsFramework instanceToCreate->get().GetNestedInstances([&](AZStd::unique_ptr& nestedInstance) { AZ_Assert(nestedInstance, "Invalid nested instance found in the new prefab created."); + EntityOptionalReference nestedInstanceContainerEntity = nestedInstance->GetContainerEntity(); AZ_Assert( nestedInstanceContainerEntity, "Invalid container entity found for the nested instance used in prefab creation."); - AZ::EntityId parentId; - AZ::TransformBus::EventResult( - parentId, nestedInstanceContainerEntity->get().GetId(), &AZ::TransformBus::Events::GetParentId); + AZ::EntityId nestedInstanceContainerEntityId = nestedInstanceContainerEntity->get().GetId(); + PrefabDom previousPatch; - auto entityIterator = AZStd::find_if( - entities.begin(), entities.end(), [parentId](AZ::Entity* entity) { return entity->GetId() == parentId; }); - - // If the previous parent entity of the nested instance is not part of the entities of the newly created prefab, - // then set the parent of the nested prefab as the container entity of the newly created prefab. - if (entityIterator == entities.end()) + // Retrieve the previous patch if it exists + if (nestedInstanceLinkPatchesMap.contains(nestedInstance.get())) { - parentId = containerEntityId; + previousPatch = AZStd::move(nestedInstanceLinkPatchesMap[nestedInstance.get()]); } // These link creations shouldn't be undone because that would put the template in a non-usable state if a user // chooses to instantiate the template after undoing the creation. - CreateLink( - {&nestedInstanceContainerEntity->get()}, *nestedInstance, instanceToCreate->get().GetTemplateId(), - undoBatch.GetUndoBatch(), parentId, false); + CreateLink(*nestedInstance, instanceToCreate->get().GetTemplateId(), undoBatch.GetUndoBatch(), AZStd::move(previousPatch), false); + + // If this nested instance's container is a top level entity in the new prefab, re-parent it and apply the change. + if (AZStd::find(topLevelEntities.begin(), topLevelEntities.end(), &nestedInstanceContainerEntity->get()) != topLevelEntities.end()) + { + Prefab::PrefabDom containerEntityDomBefore; + m_instanceToTemplateInterface->GenerateDomForEntity(containerEntityDomBefore, *nestedInstanceContainerEntity); + + AZ::TransformBus::Event(nestedInstanceContainerEntityId, &AZ::TransformBus::Events::SetParent, containerEntityId); + + PrefabDom containerEntityDomAfter; + m_instanceToTemplateInterface->GenerateDomForEntity(containerEntityDomAfter, *nestedInstanceContainerEntity); + + PrefabDom reparentPatch; + m_instanceToTemplateInterface->GeneratePatch(reparentPatch, containerEntityDomBefore, containerEntityDomAfter); + m_instanceToTemplateInterface->AppendEntityAliasToPatchPaths(reparentPatch, nestedInstanceContainerEntityId); + + // Update the cache - this prevents these changes from being stored in the regular undo/redo nodes as a separate step + m_prefabUndoCache.Store(nestedInstanceContainerEntityId, AZStd::move(containerEntityDomAfter)); + + // Save these changes as patches to the link + PrefabUndoLinkUpdate* linkUpdate = aznew PrefabUndoLinkUpdate(AZStd::to_string(static_cast(nestedInstanceContainerEntityId))); + linkUpdate->SetParent(undoBatch.GetUndoBatch()); + linkUpdate->Capture(reparentPatch, nestedInstance->GetLinkId()); + + linkUpdate->Redo(); + } }); // Create a link between the templates of the newly created instance and the instance it's being parented under. CreateLink( - topLevelEntities, instanceToCreate->get(), commonRootEntityOwningInstance->get().GetTemplateId(), - undoBatch.GetUndoBatch(), commonRootEntityId); + instanceToCreate->get(), commonRootEntityOwningInstance->get().GetTemplateId(), undoBatch.GetUndoBatch(), + AZStd::move(patch)); for (AZ::Entity* topLevelEntity : topLevelEntities) { @@ -199,6 +233,40 @@ namespace AzToolsFramework return AZ::Success(); } + PrefabDom PrefabPublicHandler::ApplyContainerTransformAndGeneratePatch(AZ::EntityId containerEntityId, AZ::EntityId parentEntityId, const EntityList& childEntities) + { + AZ::Entity* containerEntity = GetEntityById(containerEntityId); + AZ_Assert(containerEntity, "Invalid container entity passed to ApplyContainerTransformAndGeneratePatch."); + + // Generate the transform for the container entity out of the top level entities, and set it + // This step needs to be done before anything is parented to the container, else children position will be wrong + Prefab::PrefabDom containerEntityDomBefore; + m_instanceToTemplateInterface->GenerateDomForEntity(containerEntityDomBefore, *containerEntity); + + AZ::Vector3 containerEntityTranslation(AZ::Vector3::CreateZero()); + AZ::Quaternion containerEntityRotation(AZ::Quaternion::CreateZero()); + + // Set container entity to be child of common root + AZ::TransformBus::Event(containerEntityId, &AZ::TransformBus::Events::SetParent, parentEntityId); + + // Set the transform (translation, rotation) of the container entity + GenerateContainerEntityTransform(childEntities, containerEntityTranslation, containerEntityRotation); + AZ::TransformBus::Event(containerEntityId, &AZ::TransformBus::Events::SetLocalTranslation, containerEntityTranslation); + AZ::TransformBus::Event(containerEntityId, &AZ::TransformBus::Events::SetLocalRotationQuaternion, containerEntityRotation); + + PrefabDom containerEntityDomAfter; + m_instanceToTemplateInterface->GenerateDomForEntity(containerEntityDomAfter, *containerEntity); + + PrefabDom patch; + m_instanceToTemplateInterface->GeneratePatch(patch, containerEntityDomBefore, containerEntityDomAfter); + m_instanceToTemplateInterface->AppendEntityAliasToPatchPaths(patch, containerEntityId); + + // Update the cache - this prevents these changes from being stored in the regular undo/redo nodes + m_prefabUndoCache.Store(containerEntityId, AZStd::move(containerEntityDomAfter)); + + return AZStd::move(patch); + } + PrefabOperationResult PrefabPublicHandler::InstantiatePrefab( AZStd::string_view filePath, AZ::EntityId parent, const AZ::Vector3& position) { @@ -249,10 +317,10 @@ namespace AzToolsFramework // Initialize Undo Batch object ScopedUndoBatch undoBatch("Instantiate Prefab"); + // Instantiate the Prefab PrefabDom instanceToParentUnderDomBeforeCreate; m_instanceToTemplateInterface->GenerateDomForInstance(instanceToParentUnderDomBeforeCreate, instanceToParentUnder->get()); - // Instantiate the Prefab auto instanceToCreate = prefabEditorEntityOwnershipInterface->InstantiatePrefab(relativePath, instanceToParentUnder); if (!instanceToCreate) @@ -264,11 +332,32 @@ namespace AzToolsFramework PrefabUndoHelpers::UpdatePrefabInstance( instanceToParentUnder->get(), "Update prefab instance", instanceToParentUnderDomBeforeCreate, undoBatch.GetUndoBatch()); - CreateLink({}, instanceToCreate->get(), instanceToParentUnder->get().GetTemplateId(), undoBatch.GetUndoBatch(), parent); + // Create Link with correct container patches AZ::EntityId containerEntityId = instanceToCreate->get().GetContainerEntityId(); + AZ::Entity* containerEntity = GetEntityById(containerEntityId); + AZ_Assert(containerEntity, "Invalid container entity detected in InstantiatePrefab."); - // Apply position + Prefab::PrefabDom containerEntityDomBefore; + m_instanceToTemplateInterface->GenerateDomForEntity(containerEntityDomBefore, *containerEntity); + + // Set container entity's parent + AZ::TransformBus::Event(containerEntityId, &AZ::TransformBus::Events::SetParent, parent); + + // Set the position of the container entity AZ::TransformBus::Event(containerEntityId, &AZ::TransformBus::Events::SetWorldTranslation, position); + + PrefabDom containerEntityDomAfter; + m_instanceToTemplateInterface->GenerateDomForEntity(containerEntityDomAfter, *containerEntity); + + // Generate patch to be stored in the link + PrefabDom patch; + m_instanceToTemplateInterface->GeneratePatch(patch, containerEntityDomBefore, containerEntityDomAfter); + m_instanceToTemplateInterface->AppendEntityAliasToPatchPaths(patch, containerEntityId); + + CreateLink(instanceToCreate->get(), instanceToParentUnder->get().GetTemplateId(), undoBatch.GetUndoBatch(), AZStd::move(patch)); + + // Update the cache - this prevents these changes from being stored in the regular undo/redo nodes + m_prefabUndoCache.Store(containerEntityId, AZStd::move(containerEntityDomAfter)); } return AZ::Success(); @@ -335,33 +424,9 @@ namespace AzToolsFramework } void PrefabPublicHandler::CreateLink( - const EntityList& topLevelEntities, Instance& sourceInstance, TemplateId targetTemplateId, - UndoSystem::URSequencePoint* undoBatch, AZ::EntityId commonRootEntityId, const bool isUndoRedoSupportNeeded) + Instance& sourceInstance, TemplateId targetTemplateId, + UndoSystem::URSequencePoint* undoBatch, PrefabDom patch, const bool isUndoRedoSupportNeeded) { - AZ::EntityId containerEntityId = sourceInstance.GetContainerEntityId(); - AZ::Entity* containerEntity = GetEntityById(containerEntityId); - Prefab::PrefabDom containerEntityDomBefore; - m_instanceToTemplateInterface->GenerateDomForEntity(containerEntityDomBefore, *containerEntity); - - AZ::Vector3 containerEntityTranslation(AZ::Vector3::CreateZero()); - AZ::Quaternion containerEntityRotation(AZ::Quaternion::CreateZero()); - - // Set the transform (translation, rotation) of the container entity - GenerateContainerEntityTransform(topLevelEntities, containerEntityTranslation, containerEntityRotation); - - // Set container entity to be child of common root - AZ::TransformBus::Event(containerEntityId, &AZ::TransformBus::Events::SetParent, commonRootEntityId); - - AZ::TransformBus::Event(containerEntityId, &AZ::TransformBus::Events::SetLocalTranslation, containerEntityTranslation); - AZ::TransformBus::Event(containerEntityId, &AZ::TransformBus::Events::SetLocalRotationQuaternion, containerEntityRotation); - - PrefabDom containerEntityDomAfter; - m_instanceToTemplateInterface->GenerateDomForEntity(containerEntityDomAfter, *containerEntity); - - PrefabDom patch; - m_instanceToTemplateInterface->GeneratePatch(patch, containerEntityDomBefore, containerEntityDomAfter); - m_instanceToTemplateInterface->AppendEntityAliasToPatchPaths(patch, containerEntityId); - LinkId linkId; if (isUndoRedoSupportNeeded) { @@ -377,9 +442,6 @@ namespace AzToolsFramework } sourceInstance.SetLinkId(linkId); - - // Update the cache - this prevents these changes from being stored in the regular undo/redo nodes - m_prefabUndoCache.Store(containerEntityId, AZStd::move(containerEntityDomAfter)); } void PrefabPublicHandler::RemoveLink( diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.h index d88086dda9..5ad5b4a9cf 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.h @@ -68,20 +68,32 @@ namespace AzToolsFramework InstanceOptionalReference GetOwnerInstanceByEntityId(AZ::EntityId entityId) const; bool EntitiesBelongToSameInstance(const EntityIdList& entityIds) const; + + /** + * Applies the correct transform changes to the container entity based on the parent and child entities provided, and returns an appropriate patch. + * The container will be parented to parentId, moved to the average transform of the future direct children and its cache will be updated. + * This helper function won't support undo/redo, update the templates or create any links. All that needs to be done by the caller. + * + * \param containerEntityId The container to apply the changes to. + * \param parentEntityId The id of the entity the container should be parented to. + * \param childEntities A list of entities that will subsequently be parented to this container. + * \return The PrefabDom containing the patches that should be stored in the parent link. + */ + PrefabDom ApplyContainerTransformAndGeneratePatch( + AZ::EntityId containerEntityId, AZ::EntityId parentEntityId, const EntityList& childEntities); /** * Creates a link between the templates of an instance and its parent. * - * \param topLevelEntities The list of entities that are immediate children to the container entity of the instance. * \param sourceInstance The instance that corresponds to the source template of the link. * \param targetInstance The id of the target template. * \param undoBatch The undo batch to set as parent for this create link action. - * \param commonRootEntityId The id of the entity that the source instance should be parented under. + * \param patch The patch to store in the newly created link dom. * \param isUndoRedoSupportNeeded The flag indicating whether the link should be created with undo/redo support or not. */ void CreateLink( - const EntityList& topLevelEntities, Instance& sourceInstance, TemplateId targetTemplateId, - UndoSystem::URSequencePoint* undoBatch, AZ::EntityId commonRootEntityId, const bool isUndoRedoSupportNeeded = true); + Instance& sourceInstance, TemplateId targetTemplateId, UndoSystem::URSequencePoint* undoBatch, + PrefabDom patch, const bool isUndoRedoSupportNeeded = true); /** * Removes the link between template of the sourceInstance and the template corresponding to targetTemplateId. 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/EditorViewportWidget.cpp b/Code/Sandbox/Editor/EditorViewportWidget.cpp index 85c40f2092..f72801a4d3 100644 --- a/Code/Sandbox/Editor/EditorViewportWidget.cpp +++ b/Code/Sandbox/Editor/EditorViewportWidget.cpp @@ -457,15 +457,6 @@ void EditorViewportWidget::Update() return; } - static bool sentOnWindowCreated = false; - if (!sentOnWindowCreated && windowHandle()->isActive()) - { - sentOnWindowCreated = true; - AzFramework::WindowSystemNotificationBus::Broadcast( - &AzFramework::WindowSystemNotificationBus::Handler::OnWindowCreated, - reinterpret_cast(winId())); - } - m_updatingCameraPosition = true; if (!ed_useNewCameraSystem) { 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/ImageProcessingAtom/Code/Include/Atom/ImageProcessing/ImageProcessingBus.h b/Gems/Atom/Asset/ImageProcessingAtom/Code/Include/Atom/ImageProcessing/ImageProcessingBus.h index b8deb50cc5..9f6b6e629f 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Code/Include/Atom/ImageProcessing/ImageProcessingBus.h +++ b/Gems/Atom/Asset/ImageProcessingAtom/Code/Include/Atom/ImageProcessing/ImageProcessingBus.h @@ -13,8 +13,14 @@ #pragma once #include +#include #include +namespace AssetBuilderSDK +{ + struct JobProduct; +} + namespace ImageProcessingAtom { class ImageProcessingRequests @@ -35,4 +41,39 @@ namespace ImageProcessingAtom virtual IImageObjectPtr LoadImagePreview(const AZStd::string& filePath) = 0; }; using ImageProcessingRequestBus = AZ::EBus; + + class ImageBuilderRequests + : public AZ::EBusTraits + { + public: + ////////////////////////////////////////////////////////////////////////// + // EBusTraits overrides + static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single; + static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single; + ///////////////////////////////////////////////////////////////////////// + + //! Create an image object + virtual IImageObjectPtr CreateImage( + AZ::u32 width, + AZ::u32 height, + AZ::u32 maxMipCount, + EPixelFormat pixelFormat) = 0; + + //! Convert an image and save its products to the specified folder + virtual AZStd::vector ConvertImageObject( + IImageObjectPtr imageObject, + const AZStd::string& presetName, + const AZStd::string& platformName, + const AZStd::string& outputDir, + const AZ::Data::AssetId& sourceAssetId, + const AZStd::string& sourceAssetName) = 0; + + //! Return whether the specified platform is supported by the image builder + virtual bool DoesSupportPlatform(const AZStd::string& platformId) = 0; + + //! Return whether the specified preset requires an image to be square and a power of 2 + virtual bool IsPresetFormatSquarePow2(const AZStd::string& presetName, const AZStd::string& platformName) = 0; + }; + + using ImageBuilderRequestBus = AZ::EBus; } // namespace ImageProcessingAtom diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Code/Include/Atom/ImageProcessing/ImageProcessingEditorBus.h b/Gems/Atom/Asset/ImageProcessingAtom/Code/Include/Atom/ImageProcessing/ImageProcessingEditorBus.h index acd1d7a2da..bf5289bc45 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Code/Include/Atom/ImageProcessing/ImageProcessingEditorBus.h +++ b/Gems/Atom/Asset/ImageProcessingAtom/Code/Include/Atom/ImageProcessing/ImageProcessingEditorBus.h @@ -13,8 +13,6 @@ #include -class QString; - namespace ImageProcessingAtomEditor { class ImageProcessingEditorRequests diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/ImageBuilderComponent.cpp b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/ImageBuilderComponent.cpp index 59c799a601..34d18a26eb 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/ImageBuilderComponent.cpp +++ b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/ImageBuilderComponent.cpp @@ -88,11 +88,13 @@ namespace ImageProcessingAtom m_assetHandlers.emplace_back(AZ::RPI::MakeAssetHandler()); ImageProcessingRequestBus::Handler::BusConnect(); + ImageBuilderRequestBus::Handler::BusConnect(); } void BuilderPluginComponent::Deactivate() { ImageProcessingRequestBus::Handler::BusDisconnect(); + ImageBuilderRequestBus::Handler::BusDisconnect(); m_imageBuilder.BusDisconnect(); BuilderSettingManager::DestroyInstance(); CPixelFormats::DestroyInstance(); @@ -146,6 +148,78 @@ namespace ImageProcessingAtom return image; } + IImageObjectPtr BuilderPluginComponent::CreateImage( + AZ::u32 width, + AZ::u32 height, + AZ::u32 maxMipCount, + EPixelFormat pixelFormat) + { + IImageObjectPtr image(IImageObject::CreateImage(width, height, maxMipCount, pixelFormat)); + return image; + } + + AZStd::vector BuilderPluginComponent::ConvertImageObject( + IImageObjectPtr imageObject, + const AZStd::string& presetName, + const AZStd::string& platformName, + const AZStd::string& outputDir, + const AZ::Data::AssetId& sourceAssetId, + const AZStd::string& sourceAssetName) + { + AZStd::vector outProducts; + + AZStd::string_view presetFilePath; + const PresetSettings* preset = BuilderSettingManager::Instance()->GetPreset(presetName, platformName, &presetFilePath); + if (preset == nullptr) + { + AZ_Assert(false, "Cannot find preset with name %s.", presetName.c_str()); + return outProducts; + } + + AZStd::unique_ptr desc = AZStd::make_unique(); + TextureSettings& textureSettings = desc->m_textureSetting; + textureSettings.m_preset = preset->m_uuid; + desc->m_inputImage = imageObject; + desc->m_presetSetting = *preset; + desc->m_isPreview = false; + desc->m_platform = platformName; + desc->m_filePath = presetFilePath; + desc->m_isStreaming = BuilderSettingManager::Instance()->GetBuilderSetting(platformName)->m_enableStreaming; + desc->m_imageName = sourceAssetName; + desc->m_outputFolder = outputDir; + desc->m_sourceAssetId = sourceAssetId; + + // Create an image convert process + ImageConvertProcess process(AZStd::move(desc)); + process.ProcessAll(); + bool result = process.IsSucceed(); + if (result) + { + process.GetAppendOutputProducts(outProducts); + } + + return outProducts; + } + + bool BuilderPluginComponent::DoesSupportPlatform(const AZStd::string& platformId) + { + return ImageProcessingAtom::BuilderSettingManager::Instance()->DoesSupportPlatform(platformId); + } + + bool BuilderPluginComponent::IsPresetFormatSquarePow2(const AZStd::string& presetName, const AZStd::string& platformName) + { + AZStd::string_view filePath; + const PresetSettings* preset = BuilderSettingManager::Instance()->GetPreset(presetName, platformName, &filePath); + if (preset == nullptr) + { + AZ_Assert(false, "Cannot find preset with name %s.", presetName.c_str()); + return false; + } + + const PixelFormatInfo* info = CPixelFormats::GetInstance().GetPixelFormatInfo(preset->m_pixelFormat); + return info->bSquarePow2; + } + void ImageBuilderWorker::ShutDown() { // it is important to note that this will be called on a different thread than your process job thread diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/ImageBuilderComponent.h b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/ImageBuilderComponent.h index fd099eb08a..a88ad3f587 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/ImageBuilderComponent.h +++ b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/ImageBuilderComponent.h @@ -47,6 +47,7 @@ namespace ImageProcessingAtom class BuilderPluginComponent : public AZ::Component , protected ImageProcessingRequestBus::Handler + , protected ImageBuilderRequestBus::Handler { public: AZ_COMPONENT(BuilderPluginComponent, "{A227F803-D2E4-406E-93EC-121EF45A64A1}") @@ -71,6 +72,24 @@ namespace ImageProcessingAtom IImageObjectPtr LoadImagePreview(const AZStd::string& filePath) override; //////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////// + // ImageBuilderRequestBus interface implementation + IImageObjectPtr CreateImage( + AZ::u32 width, + AZ::u32 height, + AZ::u32 maxMipCount, + EPixelFormat pixelFormat) override; + AZStd::vector ConvertImageObject( + IImageObjectPtr imageObject, + const AZStd::string& presetName, + const AZStd::string& platformName, + const AZStd::string& outputDir, + const AZ::Data::AssetId& sourceAssetId, + const AZStd::string& sourceAssetName) override; + bool DoesSupportPlatform(const AZStd::string& platformId) override; + bool IsPresetFormatSquarePow2(const AZStd::string& presetName, const AZStd::string& platformName) override; + //////////////////////////////////////////////////////////////////////// + private: BuilderPluginComponent(const BuilderPluginComponent&) = delete; diff --git a/Gems/Atom/Asset/Shader/Code/CMakeLists.txt b/Gems/Atom/Asset/Shader/Code/CMakeLists.txt index 7d82ca5869..a06aa79d24 100644 --- a/Gems/Atom/Asset/Shader/Code/CMakeLists.txt +++ b/Gems/Atom/Asset/Shader/Code/CMakeLists.txt @@ -98,7 +98,6 @@ ly_add_target( Gem::Atom_RPI.Edit RUNTIME_DEPENDENCIES 3rdParty::DirectXShaderCompilerDxc - 3rdParty::DirectXShaderCompilerDxcAz 3rdParty::SPIRVCross 3rdParty::azslc ) 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..bc2e810f84 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 = 100; // ATOM-14298 // .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 = 21; // ATOM-14298 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 + +#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 +#include +#include +#include + +#include "ShaderAssetBuilder2.h" +#include "ShaderBuilderUtility.h" +#include "AzslData.h" +#include "AzslCompiler.h" +#include "AzslBuilder.h" +#include +#include +#include +#include "AtomShaderConfig.h" + +namespace AZ +{ + namespace ShaderBuilder + { + static constexpr char ShaderVariantAssetBuilder2Name[] = "ShaderVariantAssetBuilder2"; + + static void AddShaderAssetJobDependency2( + AssetBuilderSDK::JobDescriptor& jobDescriptor, const AssetBuilderSDK::PlatformInfo& platformInfo, + const AZStd::string& shaderVariantListFilePath, const AZStd::string& shaderFilePath) + { + AZStd::vector possibleDependencies = + AZ::RPI::AssetUtils::GetPossibleDepenencyPaths(shaderVariantListFilePath, shaderFilePath); + for (auto& file : possibleDependencies) + { + AssetBuilderSDK::JobDependency jobDependency; + jobDependency.m_jobKey = ShaderAssetBuilder2::ShaderAssetBuilder2JobKey; + jobDependency.m_platformIdentifier = platformInfo.m_identifier; + jobDependency.m_type = AssetBuilderSDK::JobDependencyType::Order; + jobDependency.m_sourceFile.m_sourceFileDependencyPath = file; + jobDescriptor.m_jobDependencyList.push_back(jobDependency); + } + } + + //! Returns true if @sourceFileFullPath starts with a valid asset processor scan folder, false otherwise. + //! In case of true, it splits @sourceFileFullPath into @scanFolderFullPath and @filePathFromScanFolder. + //! @sourceFileFullPath The full path to a source asset file. + //! @scanFolderFullPath [out] Gets the full path of the scan folder where the source file is located. + //! @filePathFromScanFolder [out] Get the file path relative to @scanFolderFullPath. + static bool SplitSourceAssetPathIntoScanFolderFullPathAndRelativeFilePath2(const AZStd::string& sourceFileFullPath, AZStd::string& scanFolderFullPath, AZStd::string& filePathFromScanFolder) + { + AZStd::vector scanFolders; + bool success = false; + AzToolsFramework::AssetSystemRequestBus::BroadcastResult(success, &AzToolsFramework::AssetSystem::AssetSystemRequest::GetAssetSafeFolders, scanFolders); + if (!success) + { + AZ_Error(ShaderVariantAssetBuilder2Name, false, "Couldn't get the scan folders"); + return false; + } + + for (AZStd::string scanFolder : scanFolders) + { + AzFramework::StringFunc::Path::Normalize(scanFolder); + if (!AZ::StringFunc::StartsWith(sourceFileFullPath, scanFolder)) + { + continue; + } + const size_t scanFolderSize = scanFolder.size(); + const size_t sourcePathSize = sourceFileFullPath.size(); + scanFolderFullPath = scanFolder; + filePathFromScanFolder = sourceFileFullPath.substr(scanFolderSize + 1, sourcePathSize - scanFolderSize - 1); + return true; + } + + return false; + } + + //! Validates if a given .shadervariantlist file is located at the correct path for a given .shader full path. + //! There are two valid paths: + //! 1- Lower Precedence: The same folder where the .shader file is located. + //! 2- Higher Precedence: //ShaderVariants/. + //! The "Higher Precedence" path gives the option to game projects to override what variants to generate. If this + //! file exists then the "Lower Precedence" path is disregarded. + //! A .shader full path is located under an AP scan folder. + //! Example: "/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_ForwardPass.shader" + //! - In this example the Scan Folder is "/Gems/Atom/Feature/Common/Assets", while the subfolder is "Materials/Types". + //! The "Higher Precedence" expected valid location for the .shadervariantlist would be: + //! - //ShaderVariants/Materials/Types/StandardPBR_ForwardPass.shadervariantlist. + //! The "Lower Precedence" valid location would be: + //! - /Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_ForwardPass.shadervariantlist. + //! @shouldExitEarlyFromProcessJob [out] Set to true if ProcessJob should do no work but return successfully. + //! Set to false if ProcessJob should do work and create assets. + //! When @shaderVariantListFileFullPath is provided by a Gem/Feature instead of the Game Project + //! We check if the game project already defined the shader variant list, and if it did it means + //! ProcessJob should do no work, but return successfully nonetheless. + static bool ValidateShaderVariantListLocation2(const AZStd::string& shaderVariantListFileFullPath, + const AZStd::string& shaderFileFullPath, bool& shouldExitEarlyFromProcessJob) + { + AZStd::string scanFolderFullPath; + AZStd::string shaderProductFileRelativePath; + if (!SplitSourceAssetPathIntoScanFolderFullPathAndRelativeFilePath2(shaderFileFullPath, scanFolderFullPath, shaderProductFileRelativePath)) + { + AZ_Error(ShaderVariantAssetBuilder2Name, false, "Couldn't get the scan folder for shader [%s]", shaderFileFullPath.c_str()); + return false; + } + AZ_TracePrintf(ShaderVariantAssetBuilder2Name, "For shader [%s], Scan folder full path [%s], relative file path [%s]", shaderFileFullPath.c_str(), scanFolderFullPath.c_str(), shaderProductFileRelativePath.c_str()); + + AZStd::string shaderVariantListFileRelativePath = shaderProductFileRelativePath; + AzFramework::StringFunc::Path::ReplaceExtension(shaderVariantListFileRelativePath, RPI::ShaderVariantListSourceData::Extension); + + const char * gameProjectPath = nullptr; + AzToolsFramework::AssetSystemRequestBus::BroadcastResult(gameProjectPath, &AzToolsFramework::AssetSystem::AssetSystemRequest::GetAbsoluteDevGameFolderPath); + + AZStd::string expectedHigherPrecedenceFileFullPath; + AzFramework::StringFunc::Path::Join(gameProjectPath, RPI::ShaderVariantTreeAsset::CommonSubFolder, expectedHigherPrecedenceFileFullPath, false /* handle directory overlap? */, false /* be case insensitive? */); + AzFramework::StringFunc::Path::Join(expectedHigherPrecedenceFileFullPath.c_str(), shaderProductFileRelativePath.c_str(), expectedHigherPrecedenceFileFullPath, false /* handle directory overlap? */, false /* be case insensitive? */); + AzFramework::StringFunc::Path::ReplaceExtension(expectedHigherPrecedenceFileFullPath, AZ::RPI::ShaderVariantListSourceData::Extension); + AzFramework::StringFunc::Path::Normalize(expectedHigherPrecedenceFileFullPath); + + AZStd::string normalizedShaderVariantListFileFullPath = shaderVariantListFileFullPath; + AzFramework::StringFunc::Path::Normalize(normalizedShaderVariantListFileFullPath); + + if (expectedHigherPrecedenceFileFullPath == normalizedShaderVariantListFileFullPath) + { + // Whenever the Game Project declares a *.shadervariantlist file we always do work. + shouldExitEarlyFromProcessJob = false; + return true; + } + + AZ::Data::AssetInfo assetInfo; + AZStd::string watchFolder; + bool foundHigherPrecedenceAsset = false; + AzToolsFramework::AssetSystemRequestBus::BroadcastResult(foundHigherPrecedenceAsset + , &AzToolsFramework::AssetSystem::AssetSystemRequest::GetSourceInfoBySourcePath + , expectedHigherPrecedenceFileFullPath.c_str(), assetInfo, watchFolder); + if (foundHigherPrecedenceAsset) + { + AZ_TracePrintf(ShaderVariantAssetBuilder2Name, "The shadervariantlist [%s] has been overriden by the game project with [%s]", + normalizedShaderVariantListFileFullPath.c_str(), expectedHigherPrecedenceFileFullPath.c_str()); + shouldExitEarlyFromProcessJob = true; + return true; + } + + // Check the "Lower Precedence" case, .shader path == .shadervariantlist path. + AZStd::string normalizedShaderFileFullPath = shaderFileFullPath; + AzFramework::StringFunc::Path::Normalize(normalizedShaderFileFullPath); + + AZStd::string normalizedShaderFileFullPathWithoutExtension = normalizedShaderFileFullPath; + AzFramework::StringFunc::Path::StripExtension(normalizedShaderFileFullPathWithoutExtension); + + AZStd::string normalizedShaderVariantListFileFullPathWithoutExtension = normalizedShaderVariantListFileFullPath; + AzFramework::StringFunc::Path::StripExtension(normalizedShaderVariantListFileFullPathWithoutExtension); + +#if AZ_TRAIT_OS_USE_WINDOWS_FILE_PATHS + //In certain circumstances, the capitalization of the drive letter may not match + const bool caseSensitive = false; +#else + //On the other platforms there's no drive letter, so it should be a non-issue. + const bool caseSensitive = true; +#endif + if (!StringFunc::Equal(normalizedShaderFileFullPathWithoutExtension.c_str(), normalizedShaderVariantListFileFullPathWithoutExtension.c_str(), caseSensitive)) + { + AZ_Error(ShaderVariantAssetBuilder2Name, false, "For shader file at path [%s], the shader variant list [%s] is expected to be located at [%s.%s] or [%s]" + , normalizedShaderFileFullPath.c_str(), normalizedShaderVariantListFileFullPath.c_str(), + normalizedShaderFileFullPathWithoutExtension.c_str(), RPI::ShaderVariantListSourceData::Extension, + expectedHigherPrecedenceFileFullPath.c_str()); + return false; + } + + shouldExitEarlyFromProcessJob = false; + return true; + } + + // We treat some issues as warnings and return "Success" from CreateJobs allows us to report the dependency. + // If/when a valid dependency file appears, that will trigger the ShaderVariantAssetBuilder2 to run again. + // Since CreateJobs will pass, we forward this message to ProcessJob which will report it as an error. + struct LoadResult2 + { + enum class Code + { + Error, + DeferredError, + Success + }; + + Code m_code; + AZStd::string m_deferredMessage; // Only used when m_code == DeferredError + }; + + static LoadResult2 LoadShaderVariantList2(const AZStd::string& variantListFullPath, RPI::ShaderVariantListSourceData& shaderVariantList, AZStd::string& shaderSourceFileFullPath, + bool& shouldExitEarlyFromProcessJob) + { + // Need to get the name of the shader file from the template so that we can preprocess the shader data and setup + // source file dependencies. + if (!RPI::JsonUtils::LoadObjectFromFile(variantListFullPath, shaderVariantList)) + { + AZ_Error(ShaderVariantAssetBuilder2Name, false, "Failed to parse Shader Variant List Descriptor JSON from [%s]", variantListFullPath.c_str()); + return LoadResult2{LoadResult2::Code::Error}; + } + + const AZStd::string resolvedShaderPath = AZ::RPI::AssetUtils::ResolvePathReference(variantListFullPath, shaderVariantList.m_shaderFilePath); + if (!AZ::IO::LocalFileIO::GetInstance()->Exists(resolvedShaderPath.c_str())) + { + return LoadResult2{LoadResult2::Code::DeferredError, AZStd::string::format("The shader path [%s] was not found.", resolvedShaderPath.c_str())}; + } + + shaderSourceFileFullPath = resolvedShaderPath; + + if (!ValidateShaderVariantListLocation2(variantListFullPath, shaderSourceFileFullPath, shouldExitEarlyFromProcessJob)) + { + return LoadResult2{LoadResult2::Code::Error}; + } + + if (shouldExitEarlyFromProcessJob) + { + return LoadResult2{LoadResult2::Code::Success}; + } + + auto resultOutcome = RPI::ShaderVariantTreeAssetCreator::ValidateStableIdsAreUnique(shaderVariantList.m_shaderVariants); + if (!resultOutcome.IsSuccess()) + { + AZ_Error(ShaderVariantAssetBuilder2Name, false, "Variant info validation error: %s", resultOutcome.GetError().c_str()); + return LoadResult2{LoadResult2::Code::Error}; + } + + if (!IO::FileIOBase::GetInstance()->Exists(shaderSourceFileFullPath.c_str())) + { + return LoadResult2{LoadResult2::Code::DeferredError, AZStd::string::format("ShaderSourceData file does not exist: %s.", shaderSourceFileFullPath.c_str())}; + } + + return LoadResult2{LoadResult2::Code::Success}; + } // LoadShaderVariantListAndAzslSource + + void ShaderVariantAssetBuilder2::CreateJobs(const AssetBuilderSDK::CreateJobsRequest& request, AssetBuilderSDK::CreateJobsResponse& response) const + { + AZStd::string variantListFullPath; + AzFramework::StringFunc::Path::ConstructFull(request.m_watchFolder.data(), request.m_sourceFile.data(), variantListFullPath, true); + + AZ_TracePrintf(ShaderVariantAssetBuilder2Name, "CreateJobs for Shader Variant List \"%s\"\n", variantListFullPath.data()); + + RPI::ShaderVariantListSourceData shaderVariantList; + AZStd::string shaderSourceFileFullPath; + bool shouldExitEarlyFromProcessJob = false; + const LoadResult2 loadResult = LoadShaderVariantList2(variantListFullPath, shaderVariantList, shaderSourceFileFullPath, shouldExitEarlyFromProcessJob); + + if (loadResult.m_code == LoadResult2::Code::Error) + { + response.m_result = AssetBuilderSDK::CreateJobsResultCode::Failed; + return; + } + + if (loadResult.m_code == LoadResult2::Code::DeferredError || shouldExitEarlyFromProcessJob) + { + for (const AssetBuilderSDK::PlatformInfo& info : request.m_enabledPlatforms) + { + // Let's create fake jobs that will fail ProcessJob, but are useful to establish dependency on the shader file. + AssetBuilderSDK::JobDescriptor jobDescriptor; + + jobDescriptor.m_priority = -5000; + jobDescriptor.m_critical = false; + jobDescriptor.m_jobKey = ShaderVariantAssetBuilder2JobKey; + jobDescriptor.SetPlatformIdentifier(info.m_identifier.data()); + + AddShaderAssetJobDependency2(jobDescriptor, info, variantListFullPath, shaderVariantList.m_shaderFilePath); + + if (loadResult.m_code == LoadResult2::Code::DeferredError) + { + jobDescriptor.m_jobParameters.emplace(ShaderVariantLoadErrorParam, loadResult.m_deferredMessage); + } + + if (shouldExitEarlyFromProcessJob) + { + // The value doesn't matter, what matters is the presence of the key which will + // signal that no assets should be produced on behalf of this shadervariantlist because + // the game project overrode it. + jobDescriptor.m_jobParameters.emplace(ShouldExitEarlyFromProcessJobParam, variantListFullPath); + } + + response.m_createJobOutputs.push_back(jobDescriptor); + } + response.m_result = AssetBuilderSDK::CreateJobsResultCode::Success; + return; + } + + for (const AssetBuilderSDK::PlatformInfo& info : request.m_enabledPlatforms) + { + AZ_TraceContext("For platform", info.m_identifier.data()); + + // First job is for the ShaderVariantTreeAsset. + { + AssetBuilderSDK::JobDescriptor jobDescriptor; + + // The ShaderVariantTreeAsset is high priority, but must be generated after the ShaderAsset + jobDescriptor.m_priority = 1; + jobDescriptor.m_critical = false; + + jobDescriptor.m_jobKey = GetShaderVariantTreeAssetJobKey(); + jobDescriptor.SetPlatformIdentifier(info.m_identifier.data()); + + AddShaderAssetJobDependency2(jobDescriptor, info, variantListFullPath, shaderVariantList.m_shaderFilePath); + + jobDescriptor.m_jobParameters.emplace(ShaderSourceFilePathJobParam, shaderSourceFileFullPath); + + response.m_createJobOutputs.push_back(jobDescriptor); + } + + // One job for each variant. Each job will produce one ".azshadervariant" per RHI per supervariant. + for (const AZ::RPI::ShaderVariantListSourceData::VariantInfo& variantInfo : shaderVariantList.m_shaderVariants) + { + AZStd::string variantInfoAsJsonString; + const bool convertSuccess = AZ::RPI::JsonUtils::SaveObjectToJsonString(variantInfo, variantInfoAsJsonString); + AZ_Assert(convertSuccess, "Failed to convert VariantInfo to json string"); + + AssetBuilderSDK::JobDescriptor jobDescriptor; + + // There can be tens/hundreds of thousands of shader variants. By default each shader will get + // a root variant that can be used at runtime. In order to prevent the AssetProcessor from + // being overtaken by shader variant compilation We mark all non-root shader variant generation + // as non critical and very low priority. + jobDescriptor.m_priority = -5000; + jobDescriptor.m_critical = false; + + jobDescriptor.m_jobKey = GetShaderVariantAssetJobKey(RPI::ShaderVariantStableId{variantInfo.m_stableId}); + jobDescriptor.SetPlatformIdentifier(info.m_identifier.data()); + + // The ShaderVariantAssets are job dependent on the ShaderVariantTreeAsset. + AssetBuilderSDK::SourceFileDependency fileDependency; + fileDependency.m_sourceFileDependencyPath = variantListFullPath; + AssetBuilderSDK::JobDependency variantTreeJobDependency; + variantTreeJobDependency.m_jobKey = GetShaderVariantTreeAssetJobKey(); + variantTreeJobDependency.m_platformIdentifier = info.m_identifier; + variantTreeJobDependency.m_sourceFile = fileDependency; + variantTreeJobDependency.m_type = AssetBuilderSDK::JobDependencyType::Order; + jobDescriptor.m_jobDependencyList.emplace_back(variantTreeJobDependency); + + jobDescriptor.m_jobParameters.emplace(ShaderVariantJobVariantParam, variantInfoAsJsonString); + jobDescriptor.m_jobParameters.emplace(ShaderSourceFilePathJobParam, shaderSourceFileFullPath); + + response.m_createJobOutputs.push_back(jobDescriptor); + } + + } + response.m_result = AssetBuilderSDK::CreateJobsResultCode::Success; + } // CreateJobs + + void ShaderVariantAssetBuilder2::ProcessJob(const AssetBuilderSDK::ProcessJobRequest& request, AssetBuilderSDK::ProcessJobResponse& response) const + { + const auto& jobParameters = request.m_jobDescription.m_jobParameters; + + if (jobParameters.find(ShaderVariantLoadErrorParam) != jobParameters.end()) + { + AZ_Error(ShaderVariantAssetBuilder2Name, false, "Error during CreateJobs: %s", jobParameters.at(ShaderVariantLoadErrorParam).c_str()); + response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed; + return; + } + + if (jobParameters.find(ShouldExitEarlyFromProcessJobParam) != jobParameters.end()) + { + AZ_TracePrintf(ShaderVariantAssetBuilder2Name, "Doing nothing on behalf of [%s] because it's been overridden by game project.", jobParameters.at(ShaderVariantLoadErrorParam).c_str()); + response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success; + return; + } + + AssetBuilderSDK::JobCancelListener jobCancelListener(request.m_jobId); + if (jobCancelListener.IsCancelled()) + { + response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Cancelled; + return; + } + + if (request.m_jobDescription.m_jobKey == GetShaderVariantTreeAssetJobKey()) + { + ProcessShaderVariantTreeJob(request, response); + } + else + { + ProcessShaderVariantJob(request, response); + } + } + + + static RPI::Ptr LoadShaderOptionsGroupLayoutFromShaderAssetBuilder2( + const RHI::ShaderPlatformInterface* shaderPlatformInterface, + const AssetBuilderSDK::PlatformInfo& platformInfo, + const AzslCompiler& azslCompiler, + const AZStd::string& shaderSourceFileFullPath, + const RPI::SupervariantIndex supervariantIndex) + { + auto optionsGroupPathOutcome = ShaderBuilderUtility::ObtainBuildArtifactPathFromShaderAssetBuilder2( + shaderPlatformInterface->GetAPIUniqueIndex(), platformInfo.m_identifier, shaderSourceFileFullPath, supervariantIndex.GetIndex(), + AZ::RPI::ShaderAssetSubId::OptionsJson); + if (!optionsGroupPathOutcome.IsSuccess()) + { + AZ_Error(ShaderVariantAssetBuilder2Name, false, "%s", optionsGroupPathOutcome.GetError().c_str()); + return nullptr; + } + auto optionsGroupJsonPath = optionsGroupPathOutcome.TakeValue(); + RPI::Ptr shaderOptionGroupLayout = RPI::ShaderOptionGroupLayout::Create(); + // The shader options define what options are available, what are the allowed values/range + // for each option and what is its default value. + auto jsonOutcome = JsonSerializationUtils::ReadJsonFile(optionsGroupJsonPath); + if (!jsonOutcome.IsSuccess()) + { + AZ_Error(ShaderVariantAssetBuilder2Name, false, "%s", jsonOutcome.GetError().c_str()); + return nullptr; + } + if (!azslCompiler.ParseOptionsPopulateOptionGroupLayout(jsonOutcome.GetValue(), shaderOptionGroupLayout)) + { + AZ_Error(ShaderVariantAssetBuilder2Name, false, "Failed to find a valid list of shader options!"); + return nullptr; + } + + return shaderOptionGroupLayout; + } + + static void LoadShaderFunctionsFromShaderAssetBuilder2( + const RHI::ShaderPlatformInterface* shaderPlatformInterface, const AssetBuilderSDK::PlatformInfo& platformInfo, + const AzslCompiler& azslCompiler, const AZStd::string& shaderSourceFileFullPath, + const RPI::SupervariantIndex supervariantIndex, + AzslFunctions& functions) + { + auto functionsJsonPathOutcome = ShaderBuilderUtility::ObtainBuildArtifactPathFromShaderAssetBuilder2( + shaderPlatformInterface->GetAPIUniqueIndex(), platformInfo.m_identifier, shaderSourceFileFullPath, supervariantIndex.GetIndex(), + AZ::RPI::ShaderAssetSubId::IaJson); + if (!functionsJsonPathOutcome.IsSuccess()) + { + AZ_Error(ShaderVariantAssetBuilder2Name, false, "%s", functionsJsonPathOutcome.GetError().c_str()); + return; + } + + auto functionsJsonPath = functionsJsonPathOutcome.TakeValue(); + auto jsonOutcome = JsonSerializationUtils::ReadJsonFile(functionsJsonPath); + if (!jsonOutcome.IsSuccess()) + { + AZ_Error(ShaderVariantAssetBuilder2Name, false, "%s", jsonOutcome.GetError().c_str()); + return; + } + if (!azslCompiler.ParseIaPopulateFunctionData(jsonOutcome.GetValue(), functions)) + { + functions.clear(); + AZ_Error(ShaderVariantAssetBuilder2Name, false, "Failed to find shader functions."); + return; + } + } + + + // Returns the content of the hlsl file for the given supervariant as produced by ShaderAsssetBuilder2. + // In addition to the content it also returns the full path of the hlsl file in @hlslSourcePath. + static AZStd::string LoadHlslFileFromShaderAssetBuilder2( + const RHI::ShaderPlatformInterface* shaderPlatformInterface, const AssetBuilderSDK::PlatformInfo& platformInfo, + const AZStd::string& shaderSourceFileFullPath, const RPI::SupervariantIndex supervariantIndex, AZStd::string& hlslSourcePath) + { + auto hlslSourcePathOutcome = ShaderBuilderUtility::ObtainBuildArtifactPathFromShaderAssetBuilder2( + shaderPlatformInterface->GetAPIUniqueIndex(), platformInfo.m_identifier, shaderSourceFileFullPath, supervariantIndex.GetIndex(), + AZ::RPI::ShaderAssetSubId::GeneratedHlslSource); + if (!hlslSourcePathOutcome.IsSuccess()) + { + AZ_Error(ShaderVariantAssetBuilder2Name, false, "%s", hlslSourcePathOutcome.GetError().c_str()); + return ""; + } + + hlslSourcePath = hlslSourcePathOutcome.TakeValue(); + Outcome hlslSourceOutcome = Utils::ReadFile(hlslSourcePath); + if (!hlslSourceOutcome.IsSuccess()) + { + AZ_Error( + ShaderVariantAssetBuilder2Name, false, "Failed to obtain shader source from %s. [%s]", hlslSourcePath.c_str(), + hlslSourceOutcome.TakeError().c_str()); + return ""; + } + return hlslSourceOutcome.TakeValue(); + } + + void ShaderVariantAssetBuilder2::ProcessShaderVariantTreeJob(const AssetBuilderSDK::ProcessJobRequest& request, AssetBuilderSDK::ProcessJobResponse& response) const + { + AZStd::string variantListFullPath; + AzFramework::StringFunc::Path::ConstructFull(request.m_watchFolder.data(), request.m_sourceFile.data(), variantListFullPath, true); + + RPI::ShaderVariantListSourceData shaderVariantListDescriptor; + if (!RPI::JsonUtils::LoadObjectFromFile(variantListFullPath, shaderVariantListDescriptor)) + { + AZ_Assert(false, "Failed to parse Shader Variant List Descriptor JSON [%s]", variantListFullPath.c_str()); + response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed; + return; + } + + const AZStd::string& shaderSourceFileFullPath = request.m_jobDescription.m_jobParameters.at(ShaderSourceFilePathJobParam); + + //For debugging purposes will create a dummy azshadervarianttree file. + AZStd::string shaderName; + AzFramework::StringFunc::Path::GetFileName(shaderSourceFileFullPath.c_str(), shaderName); + + // No error checking because the same calls were already executed during CreateJobs() + auto descriptorParseOutcome = ShaderBuilderUtility::LoadShaderDataJson(shaderSourceFileFullPath); + RPI::ShaderSourceData shaderSourceDescriptor = descriptorParseOutcome.TakeValue(); + RPI::Ptr shaderOptionGroupLayout; + + // Request the list of valid shader platform interfaces for the target platform. + AZStd::vector platformInterfaces = + ShaderBuilderUtility::DiscoverEnabledShaderPlatformInterfaces(request.m_platformInfo, shaderSourceDescriptor); + if (platformInterfaces.empty()) + { + // No work to do. Exit gracefully. + AZ_TracePrintf( + ShaderVariantAssetBuilder2Name, + "No azshadervarianttree is produced on behalf of %s because all valid RHI backends were disabled for this shader.\n", + shaderSourceFileFullPath.c_str()); + response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success; + return; + } + + + // set the input file for eventual error messages, but the compiler won't be called on it. + AZStd::string azslFullPath; + ShaderBuilderUtility::GetAbsolutePathToAzslFile(shaderSourceFileFullPath, shaderSourceDescriptor.m_source, azslFullPath); + AzslCompiler azslc(azslFullPath); + + AZStd::string previousLoopApiName; + for (RHI::ShaderPlatformInterface* shaderPlatformInterface : platformInterfaces) + { + auto thisLoopApiName = shaderPlatformInterface->GetAPIName().GetStringView(); + RPI::Ptr loopLocal_ShaderOptionGroupLayout = + LoadShaderOptionsGroupLayoutFromShaderAssetBuilder2( + shaderPlatformInterface, request.m_platformInfo, azslc, shaderSourceFileFullPath, RPI::DefaultSupervariantIndex); + if (!loopLocal_ShaderOptionGroupLayout) + { + response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed; + return; + } + if (shaderOptionGroupLayout && shaderOptionGroupLayout->GetHash() != loopLocal_ShaderOptionGroupLayout->GetHash()) + { + AZ_Error(ShaderVariantAssetBuilder2Name, false, "There was a discrepancy in shader options between %s and %s", previousLoopApiName.c_str(), thisLoopApiName.data()); + response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed; + return; + } + shaderOptionGroupLayout = loopLocal_ShaderOptionGroupLayout; + previousLoopApiName = thisLoopApiName; + } + + RPI::ShaderVariantTreeAssetCreator shaderVariantTreeAssetCreator; + shaderVariantTreeAssetCreator.Begin(Uuid::CreateRandom()); + shaderVariantTreeAssetCreator.SetShaderOptionGroupLayout(*shaderOptionGroupLayout); + shaderVariantTreeAssetCreator.SetVariantInfos(shaderVariantListDescriptor.m_shaderVariants); + Data::Asset shaderVariantTreeAsset; + if (!shaderVariantTreeAssetCreator.End(shaderVariantTreeAsset)) + { + AZ_Error(ShaderVariantAssetBuilder2Name, false, "Failed to build Shader Variant Tree Asset"); + response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed; + return; + } + + AZStd::string filename = AZStd::string::format("%s.%s", shaderName.c_str(), RPI::ShaderVariantTreeAsset::Extension); + AZStd::string assetPath; + AzFramework::StringFunc::Path::ConstructFull(request.m_tempDirPath.c_str(), filename.c_str(), assetPath, true); + if (!AZ::Utils::SaveObjectToFile(assetPath, AZ::DataStream::ST_BINARY, shaderVariantTreeAsset.Get())) + { + AZ_Error(ShaderVariantAssetBuilder2Name, false, "Failed to save Shader Variant Tree Asset to \"%s\"", assetPath.c_str()); + response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed; + return; + } + + AssetBuilderSDK::JobProduct assetProduct; + assetProduct.m_productSubID = RPI::ShaderVariantTreeAsset::ProductSubID; + assetProduct.m_productFileName = assetPath; + assetProduct.m_productAssetType = azrtti_typeid(); + assetProduct.m_dependenciesHandled = true; // This builder has no dependencies to output + response.m_outputProducts.push_back(assetProduct); + + AZ_TracePrintf(ShaderVariantAssetBuilder2Name, "Shader Variant Tree Asset [%s] compiled successfully.\n", assetPath.c_str()); + + response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success; + } + + void ShaderVariantAssetBuilder2::ProcessShaderVariantJob(const AssetBuilderSDK::ProcessJobRequest& request, AssetBuilderSDK::ProcessJobResponse& response) const + { + const AZStd::sys_time_t startTime = AZStd::GetTimeNowTicks(); + AssetBuilderSDK::JobCancelListener jobCancelListener(request.m_jobId); + + AZStd::string fullPath; + AzFramework::StringFunc::Path::ConstructFull(request.m_watchFolder.data(), request.m_sourceFile.data(), fullPath, true); + + const auto& jobParameters = request.m_jobDescription.m_jobParameters; + const AZStd::string& shaderSourceFileFullPath = jobParameters.at(ShaderSourceFilePathJobParam); + AZStd::string shaderFileName; + AzFramework::StringFunc::Path::GetFileName(shaderSourceFileFullPath.c_str(), shaderFileName); + + const AZStd::string& variantJsonString = jobParameters.at(ShaderVariantJobVariantParam); + RPI::ShaderVariantListSourceData::VariantInfo variantInfo; + const bool fromJsonStringSuccess = AZ::RPI::JsonUtils::LoadObjectFromJsonString(variantJsonString, variantInfo); + AZ_Assert(fromJsonStringSuccess, "Failed to convert json string to VariantInfo"); + + RPI::ShaderSourceData shaderSourceDescriptor; + AZStd::shared_ptr sources = ShaderBuilderUtility::PrepareSourceInput(ShaderVariantAssetBuilder2Name, shaderSourceFileFullPath, shaderSourceDescriptor); + + // set the input file for eventual error messages, but the compiler won't be called on it. + AzslCompiler azslc(sources->m_azslSourceFullPath); + + // Request the list of valid shader platform interfaces for the target platform. + AZStd::vector platformInterfaces = + ShaderBuilderUtility::DiscoverEnabledShaderPlatformInterfaces(request.m_platformInfo, shaderSourceDescriptor); + if (platformInterfaces.empty()) + { + // No work to do. Exit gracefully. + AZ_TracePrintf(ShaderVariantAssetBuilder2Name, + "No azshader is produced on behalf of %s because all valid RHI backends were disabled for this shader.\n", + shaderSourceFileFullPath.c_str()); + response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success; + return; + } + + auto supervariantList = ShaderBuilderUtility::GetSupervariantListFromShaderSourceData(shaderSourceDescriptor); + + GlobalBuildOptions buildOptions = ReadBuildOptions(ShaderVariantAssetBuilder2Name); + // 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(shaderSourceDescriptor.m_compiler); + + //! The ShaderOptionGroupLayout is common across all RHIs & Supervariants + RPI::Ptr shaderOptionGroupLayout = nullptr; + + // Generate shaders for each of those ShaderPlatformInterfaces. + for (RHI::ShaderPlatformInterface* shaderPlatformInterface : platformInterfaces) + { + AZ_TraceContext("ShaderPlatformInterface", shaderPlatformInterface->GetAPIName().GetCStr()); + + // Loop through all the Supervariants. + uint32_t supervariantIndexCounter = 0; + for (const auto& supervariantInfo : supervariantList) + { + RPI::SupervariantIndex supervariantIndex(supervariantIndexCounter); + + // Check if we were canceled before we do any heavy processing of + // the shader variant data. + if (jobCancelListener.IsCancelled()) + { + response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Cancelled; + return; + } + + AZStd::string shaderStemNamePrefix = shaderFileName; + if (supervariantIndex.GetIndex() > 0) + { + shaderStemNamePrefix += supervariantInfo.m_name.GetStringView(); + } + + // We need these additional pieces of information To build a shader variant asset: + // 1- ShaderOptionsGroupLayout (Need to load it once, because it's the same acrosss all supervariants + RHIs) + // 2- entryFunctions + // 3- hlsl code. + + // 1- ShaderOptionsGroupLayout + if (!shaderOptionGroupLayout) + { + shaderOptionGroupLayout = + LoadShaderOptionsGroupLayoutFromShaderAssetBuilder2( + shaderPlatformInterface, request.m_platformInfo, azslc, shaderSourceFileFullPath, supervariantIndex); + if (!shaderOptionGroupLayout) + { + response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed; + return; + } + } + + // 2- entryFunctions. + AzslFunctions azslFunctions; + LoadShaderFunctionsFromShaderAssetBuilder2( + shaderPlatformInterface, request.m_platformInfo, azslc, shaderSourceFileFullPath, supervariantIndex, azslFunctions); + if (azslFunctions.empty()) + { + response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed; + return; + } + MapOfStringToStageType shaderEntryPoints; + if (shaderSourceDescriptor.m_programSettings.m_entryPoints.empty()) + { + AZ_TracePrintf( + ShaderVariantAssetBuilder2Name, + "ProgramSettings do not specify entry points, will use GetDefaultEntryPointsFromShader()\n"); + ShaderBuilderUtility::GetDefaultEntryPointsFromFunctionDataList(azslFunctions, shaderEntryPoints); + } + else + { + for (const auto& entryPoint : shaderSourceDescriptor.m_programSettings.m_entryPoints) + { + shaderEntryPoints[entryPoint.m_name] = entryPoint.m_type; + } + } + + // 3- hlslCode + AZStd::string hlslSourcePath; + AZStd::string hlslCode = LoadHlslFileFromShaderAssetBuilder2( + shaderPlatformInterface, request.m_platformInfo, shaderSourceFileFullPath, supervariantIndex, hlslSourcePath); + if (hlslCode.empty() || hlslSourcePath.empty()) + { + response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed; + return; + } + + // Setup the shader variant creation context: + ShaderVariantCreationContext2 shaderVariantCreationContext = + { + *shaderPlatformInterface, request.m_platformInfo, buildOptions.m_compilerArguments, request.m_tempDirPath, + startTime, + shaderSourceDescriptor, + *shaderOptionGroupLayout.get(), + shaderEntryPoints, + Uuid::CreateRandom(), + shaderStemNamePrefix, + hlslSourcePath, hlslCode + }; + + AZStd::optional outputByproducts; + auto shaderVariantAssetOutcome = CreateShaderVariantAsset(variantInfo, shaderVariantCreationContext, outputByproducts); + if (!shaderVariantAssetOutcome.IsSuccess()) + { + AZ_Error(ShaderVariantAssetBuilder2Name, false, "%s\n", shaderVariantAssetOutcome.GetError().c_str()); + response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed; + return; + } + Data::Asset shaderVariantAsset = shaderVariantAssetOutcome.TakeValue(); + + + // Time to save the asset in the tmp folder so it ends up in the Cache folder. + const uint32_t productSubID = RPI::ShaderVariantAsset2::MakeAssetProductSubId( + shaderPlatformInterface->GetAPIUniqueIndex(), supervariantIndex.GetIndex(), + shaderVariantAsset->GetStableId()); + AssetBuilderSDK::JobProduct assetProduct; + if (!SerializeOutShaderVariantAsset(shaderVariantAsset, shaderStemNamePrefix, + request.m_tempDirPath, *shaderPlatformInterface, productSubID, + 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 = RPI::ShaderVariantAsset2::ShaderVariantAsset2SubProductType; + 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::ShaderVariantAsset2::MakeAssetProductSubId( + shaderPlatformInterface->GetAPIUniqueIndex(), supervariantIndex.GetIndex(), shaderVariantAsset->GetStableId(), + subProductType++); + response.m_outputProducts.push_back(AZStd::move(jobProduct)); + } + } + supervariantIndexCounter++; + } // End of supervariant for block + + } + + response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success; + } + + bool ShaderVariantAssetBuilder2::SerializeOutShaderVariantAsset( + const Data::Asset shaderVariantAsset, const AZStd::string& shaderStemNamePrefix, + const AZStd::string& tempDirPath, + const RHI::ShaderPlatformInterface& shaderPlatformInterface, const uint32_t productSubID, AssetBuilderSDK::JobProduct& assetProduct) + { + AZStd::string filename = AZStd::string::format( + "%s_%s_%u.%s", shaderStemNamePrefix.c_str(), shaderPlatformInterface.GetAPIName().GetCStr(), + shaderVariantAsset->GetStableId().GetIndex(), RPI::ShaderVariantAsset2::Extension); + + AZStd::string assetPath; + AzFramework::StringFunc::Path::ConstructFull(tempDirPath.c_str(), filename.c_str(), assetPath, true); + + if (!AZ::Utils::SaveObjectToFile(assetPath, AZ::DataStream::ST_BINARY, shaderVariantAsset.Get())) + { + AZ_Error(ShaderVariantAssetBuilder2Name, false, "Failed to save Shader Variant Asset to \"%s\"", assetPath.c_str()); + return false; + } + + assetProduct.m_productSubID = productSubID; + assetProduct.m_productFileName = assetPath; + assetProduct.m_productAssetType = azrtti_typeid(); + assetProduct.m_dependenciesHandled = true; // This builder has no dependencies to output + + AZ_TracePrintf(ShaderVariantAssetBuilder2Name, "Shader Variant Asset [%s] compiled successfully.\n", assetPath.c_str()); + return true; + } + + + AZ::Outcome, AZStd::string> ShaderVariantAssetBuilder2::CreateShaderVariantAsset( + const RPI::ShaderVariantListSourceData::VariantInfo& shaderVariantInfo, + ShaderVariantCreationContext2& creationContext, + AZStd::optional& outputByproducts) + { + // Temporary structure used for sorting and caching intermediate results + struct OptionCache + { + AZ::Name m_optionName; + AZ::Name m_valueName; + RPI::ShaderOptionIndex m_optionIndex; // Cached m_optionName + RPI::ShaderOptionValue m_value; // Cached m_valueName + }; + AZStd::vector optionList; + // We can not have more options than the number of options in the layout: + optionList.reserve(creationContext.m_shaderOptionGroupLayout.GetShaderOptionCount()); + + // This loop will validate and cache the indices for each option value: + for (const auto& shaderOption : shaderVariantInfo.m_options) + { + Name optionName{shaderOption.first}; + Name optionValue{shaderOption.second}; + + RPI::ShaderOptionIndex optionIndex = creationContext.m_shaderOptionGroupLayout.FindShaderOptionIndex(optionName); + if (optionIndex.IsNull()) + { + return AZ::Failure(AZStd::string::format("Invalid shader option: %s", optionName.GetCStr())); + } + + const RPI::ShaderOptionDescriptor& option = creationContext.m_shaderOptionGroupLayout.GetShaderOption(optionIndex); + RPI::ShaderOptionValue value = option.FindValue(optionValue); + if (value.IsNull()) + { + return AZ::Failure( + AZStd::string::format("Invalid value (%s) for shader option: %s", optionValue.GetCStr(), optionName.GetCStr())); + } + + optionList.push_back(OptionCache{optionName, optionValue, optionIndex, value}); + } + + // Create one instance of the shader variant + RPI::ShaderOptionGroup optionGroup(&creationContext.m_shaderOptionGroupLayout); + + //! Contains the series of #define macro values that define a variant. Can be empty (root variant). + //! If this string is NOT empty, a new temporary hlsl file will be created that will be the combination + //! of this string + @m_hlslSourceContent. + AZStd::string hlslCodeToPrependForVariant; + + // We want to go over all options listed in the variant and set their respective values + // This loop will populate the optionGroup and m_shaderCodePrefix in order of the option priority + for (const auto& optionCache : optionList) + { + const RPI::ShaderOptionDescriptor& option = creationContext.m_shaderOptionGroupLayout.GetShaderOption(optionCache.m_optionIndex); + + // Assign the option value specified in the variant: + option.Set(optionGroup, optionCache.m_value); + + // Populate all shader option defines. We have already confirmed they're valid. + hlslCodeToPrependForVariant += AZStd::string::format( + "#define %s_OPTION_DEF %s\n", optionCache.m_optionName.GetCStr(), optionCache.m_valueName.GetCStr()); + } + + AZStd::string variantShaderSourcePath; + // Check if we need to prepend any code prefix + if (!hlslCodeToPrependForVariant.empty()) + { + // Prepend any shader code prefix that we should apply to this variant + // and save it back to a file. + AZStd::string variantShaderSourceString(hlslCodeToPrependForVariant); + variantShaderSourceString += creationContext.m_hlslSourceContent; + + AZStd::string shaderAssetName = AZStd::string::format( + "%s_%s_%u.hlsl", creationContext.m_shaderStemNamePrefix.c_str(), + creationContext.m_shaderPlatformInterface.GetAPIName().GetCStr(), shaderVariantInfo.m_stableId); + AzFramework::StringFunc::Path::Join( + creationContext.m_tempDirPath.c_str(), shaderAssetName.c_str(), variantShaderSourcePath, true, true); + + auto outcome = Utils::WriteFile(variantShaderSourceString, variantShaderSourcePath); + if (!outcome.IsSuccess()) + { + return AZ::Failure(AZStd::string::format("Failed to create file %s", variantShaderSourcePath.c_str())); + } + } + else + { + variantShaderSourcePath = creationContext.m_hlslSourcePath; + } + + AZ_TracePrintf(ShaderVariantAssetBuilder2Name, "Variant StableId: %u", shaderVariantInfo.m_stableId); + AZ_TracePrintf(ShaderVariantAssetBuilder2Name, "Variant Shader Options: %s", optionGroup.ToString().c_str()); + + const RPI::ShaderVariantStableId shaderVariantStableId{shaderVariantInfo.m_stableId}; + + // By this time the optionGroup was populated with all option values for the variant and + // the m_shaderCodePrefix contains all option related preprocessing macros + // Let's add the requested variant: + RPI::ShaderVariantAssetCreator2 variantCreator; + RPI::ShaderOptionGroup shaderOptions{&creationContext.m_shaderOptionGroupLayout, optionGroup.GetShaderVariantId()}; + variantCreator.Begin( + creationContext.m_shaderVariantAssetId, optionGroup.GetShaderVariantId(), shaderVariantStableId, + shaderOptions.IsFullySpecified()); + + const AZStd::unordered_map& shaderEntryPoints = creationContext.m_shaderEntryPoints; + for (const auto& shaderEntryPoint : shaderEntryPoints) + { + auto shaderEntryName = shaderEntryPoint.first; + auto shaderStageType = shaderEntryPoint.second; + + AZ_TracePrintf(ShaderVariantAssetBuilder2Name, "Entry Point: %s", shaderEntryName.c_str()); + AZ_TracePrintf(ShaderVariantAssetBuilder2Name, "Begin compiling shader function \"%s\"", shaderEntryName.c_str()); + + auto assetBuilderShaderType = ShaderBuilderUtility::ToAssetBuilderShaderType(shaderStageType); + + // Compile HLSL to the platform specific shader. + RHI::ShaderPlatformInterface::StageDescriptor descriptor; + bool shaderWasCompiled = creationContext.m_shaderPlatformInterface.CompilePlatformInternal( + creationContext.m_platformInfo, variantShaderSourcePath, shaderEntryName, assetBuilderShaderType, + creationContext.m_tempDirPath, descriptor, creationContext.m_shaderCompilerArguments); + + if (!shaderWasCompiled) + { + return AZ::Failure(AZStd::string::format("Could not compile the shader function %s", shaderEntryName.c_str())); + } + // bubble up the byproducts to the caller by moving them to the context. + outputByproducts.emplace(AZStd::move(descriptor.m_byProducts)); + + RHI::Ptr shaderStageFunction = creationContext.m_shaderPlatformInterface.CreateShaderStageFunction(descriptor); + variantCreator.SetShaderFunction(ToRHIShaderStage(assetBuilderShaderType), shaderStageFunction); + + if (descriptor.m_byProducts.m_dynamicBranchCount != AZ::RHI::ShaderPlatformInterface::ByProducts::UnknownDynamicBranchCount) + { + AZ_TracePrintf( + ShaderVariantAssetBuilder2Name, "Finished compiling shader function. Number of dynamic branches: %u", + descriptor.m_byProducts.m_dynamicBranchCount); + } + else + { + AZ_TracePrintf( + ShaderVariantAssetBuilder2Name, "Finished compiling shader function. Number of dynamic branches: unknown"); + } + } + + Data::Asset shaderVariantAsset; + variantCreator.End(shaderVariantAsset); + return AZ::Success(AZStd::move(shaderVariantAsset)); + } + + } // ShaderBuilder +} // AZ diff --git a/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderVariantAssetBuilder2.h b/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderVariantAssetBuilder2.h new file mode 100644 index 0000000000..c0b632d9bd --- /dev/null +++ b/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderVariantAssetBuilder2.h @@ -0,0 +1,107 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#pragma once + +#include +#include +#include + +#include +#include +#include +#include + +#include "ShaderBuilderUtility.h" + +namespace AZ +{ + namespace ShaderBuilder + { + struct AzslData; + + //! This is nothing more than a class to help consolidate all + //! the data needed to generate a shader variant and prevent + //! all the functions involved in the process to have too many + //! arguments. + struct ShaderVariantCreationContext2 + { + RHI::ShaderPlatformInterface& m_shaderPlatformInterface; + const AssetBuilderSDK::PlatformInfo& m_platformInfo; + const RHI::ShaderCompilerArguments& m_shaderCompilerArguments; + //! Used to write temporary files during shader compilation, like *.hlsl, or *.air, or *.metallib, etc. + const AZStd::string& m_tempDirPath; + //! Used to synchronize versions of the ShaderAsset and ShaderVariantAsset, + //! especially during hot-reload. A (ShaderVariantAsset.timestamp) >= (ShaderAsset.timestamp). + const AZStd::sys_time_t m_assetBuildTimestamp; + const RPI::ShaderSourceData& m_shaderSourceDataDescriptor; + const RPI::ShaderOptionGroupLayout& m_shaderOptionGroupLayout; + const MapOfStringToStageType& m_shaderEntryPoints; + const Data::AssetId m_shaderVariantAssetId; + const AZStd::string& m_shaderStemNamePrefix; //- + const AZStd::string& m_hlslSourcePath; + const AZStd::string& m_hlslSourceContent; + }; + + class ShaderVariantAssetBuilder2 + : public AssetBuilderSDK::AssetBuilderCommandBus::Handler + { + public: + AZ_TYPE_INFO(ShaderVariantAssetBuilder2, "{C959AEC2-2083-4488-AD88-F61B1144535B}"); + + static constexpr char ShaderVariantAssetBuilder2JobKey[] = "Shader Variant Asset 2"; + + ShaderVariantAssetBuilder2() = default; + ~ShaderVariantAssetBuilder2() = 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; + + //! The ShaderVariantAsset returned by this function won't be written to the filesystem. + //! You should call SerializeOutShaderVariantAsset to write it to the temp folder assigned + //! by the asset processor. + static AZ::Outcome, AZStd::string> CreateShaderVariantAsset( + const RPI::ShaderVariantListSourceData::VariantInfo& shaderVariantInfo, + ShaderVariantCreationContext2& creationContext, + AZStd::optional& outputByproducts); + + static bool SerializeOutShaderVariantAsset( + const Data::Asset shaderVariantAsset, + const AZStd::string& shaderStemNamePrefix, const AZStd::string& tempDirPath, + const RHI::ShaderPlatformInterface& shaderPlatformInterface, const uint32_t productSubID, AssetBuilderSDK::JobProduct& assetProduct); + + // AssetBuilderSDK::AssetBuilderCommandBus interface overrides ... + void ShutDown() override { }; + + private: + AZ_DISABLE_COPY_MOVE(ShaderVariantAssetBuilder2); + + static constexpr uint32_t ShaderVariantLoadErrorParam = 0; + static constexpr uint32_t ShaderSourceFilePathJobParam = 2; + static constexpr uint32_t ShaderVariantJobVariantParam = 3; + static constexpr uint32_t ShouldExitEarlyFromProcessJobParam = 4; + + //! Called from ProcessJob when the job is supposed to create a ShaderVariantTreeAsset. + void ProcessShaderVariantTreeJob(const AssetBuilderSDK::ProcessJobRequest& request, AssetBuilderSDK::ProcessJobResponse& response) const; + + //! Called from ProcessJob when the job is supposed to create ShaderVariantAssets. One ShaderVariantAsset will be produced per RHI::APIType + //! supported by the platform. + void ProcessShaderVariantJob(const AssetBuilderSDK::ProcessJobRequest& request, AssetBuilderSDK::ProcessJobResponse& response) const; + + static AZStd::string GetShaderVariantTreeAssetJobKey() { return AZStd::string::format("%s_varianttree", ShaderVariantAssetBuilder2JobKey); } + static AZStd::string GetShaderVariantAssetJobKey(RPI::ShaderVariantStableId variantStableId) { return AZStd::string::format("%s_variant_%u", ShaderVariantAssetBuilder2JobKey, variantStableId.GetIndex()); } + + }; + + } // ShaderBuilder +} // AZ diff --git a/Gems/Atom/Asset/Shader/Code/Source/Editor/SrgLayoutBuilder.cpp b/Gems/Atom/Asset/Shader/Code/Source/Editor/SrgLayoutBuilder.cpp index 0b9b813815..a7721c84a2 100644 --- a/Gems/Atom/Asset/Shader/Code/Source/Editor/SrgLayoutBuilder.cpp +++ b/Gems/Atom/Asset/Shader/Code/Source/Editor/SrgLayoutBuilder.cpp @@ -345,17 +345,21 @@ namespace AZ for(const SrgDataEntry& srgDataEntry : entry.second) { RHI::ShaderPlatformInterface* shaderPlatformInterface = srgDataEntry.first; + + // 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. + AZStd::string azslCompilerParameters = + shaderPlatformInterface->GetAzslCompilerParameters(RHI::ShaderCompilerArguments{}); + bool useRegisterId = (AzFramework::StringFunc::Find(azslCompilerParameters, "--use-spaces") != AZStd::string::npos); + const SrgData& srgData = srgDataEntry.second; srgAssetCreator.BeginAPI(shaderPlatformInterface->GetAPIType()); srgAssetCreator.SetBindingSlot(srgData.m_bindingSlot.m_index); - // 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. - AZStd::string azslCompilerParameters = shaderPlatformInterface->GetAzslCompilerParameters(RHI::ShaderCompilerArguments{}); - bool useRegisterId = (AzFramework::StringFunc::Find(azslCompilerParameters, "--use-spaces") != AZStd::string::npos); - // Samplers for (const SamplerSrgData& samplerData : srgData.m_samplers) { diff --git a/Gems/Atom/Asset/Shader/Code/Source/Editor/SrgLayoutUtility.cpp b/Gems/Atom/Asset/Shader/Code/Source/Editor/SrgLayoutUtility.cpp new file mode 100644 index 0000000000..fc3fbfc32c --- /dev/null +++ b/Gems/Atom/Asset/Shader/Code/Source/Editor/SrgLayoutUtility.cpp @@ -0,0 +1,231 @@ +/* +* 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 "SrgLayoutUtility.h" + +#include + +namespace AZ +{ + namespace ShaderBuilder + { + namespace SrgLayoutUtility + { + static constexpr char SrgLayoutUtilityName[] = "SrgLayoutUtility"; + + RHI::ShaderInputImageType ToShaderInputImageType(TextureType textureType) + { + switch (textureType) + { + case TextureType::Texture1D: + return RHI::ShaderInputImageType::Image1D; + case TextureType::Texture1DArray: + return RHI::ShaderInputImageType::Image1DArray; + case TextureType::Texture2D: + return RHI::ShaderInputImageType::Image2D; + case TextureType::Texture2DArray: + return RHI::ShaderInputImageType::Image2DArray; + case TextureType::Texture2DMS: + return RHI::ShaderInputImageType::Image2DMultisample; + case TextureType::Texture2DMSArray: + return RHI::ShaderInputImageType::Image2DMultisampleArray; + case TextureType::Texture3D: + return RHI::ShaderInputImageType::Image3D; + case TextureType::TextureCube: + return RHI::ShaderInputImageType::ImageCube; + case TextureType::RwTexture1D: + return RHI::ShaderInputImageType::Image1D; + case TextureType::RwTexture1DArray: + return RHI::ShaderInputImageType::Image1DArray; + case TextureType::RwTexture2D: + return RHI::ShaderInputImageType::Image2D; + case TextureType::RwTexture2DArray: + return RHI::ShaderInputImageType::Image2DArray; + case TextureType::RwTexture3D: + return RHI::ShaderInputImageType::Image3D; + case TextureType::RasterizerOrderedTexture1D: + return RHI::ShaderInputImageType::Image1D; + case TextureType::RasterizerOrderedTexture1DArray: + return RHI::ShaderInputImageType::Image1DArray; + case TextureType::RasterizerOrderedTexture2D: + return RHI::ShaderInputImageType::Image2D; + case TextureType::RasterizerOrderedTexture2DArray: + return RHI::ShaderInputImageType::Image2DArray; + case TextureType::RasterizerOrderedTexture3D: + return RHI::ShaderInputImageType::Image3D; + case TextureType::SubpassInput: + return RHI::ShaderInputImageType::SubpassInput; + default: + AZ_Assert(false, "Unhandled TextureType"); + return RHI::ShaderInputImageType::Unknown; + } + } + + RHI::ShaderInputBufferType ToShaderInputBufferType(BufferType bufferType) + { + switch (bufferType) + { + case BufferType::Buffer: + case BufferType::RwBuffer: + case BufferType::RasterizerOrderedBuffer: + return RHI::ShaderInputBufferType::Typed; + case BufferType::AppendStructuredBuffer: + case BufferType::ConsumeStructuredBuffer: + case BufferType::RasterizerOrderedStructuredBuffer: + case BufferType::RwStructuredBuffer: + case BufferType::StructuredBuffer: + return RHI::ShaderInputBufferType::Structured; + case BufferType::RasterizerOrderedByteAddressBuffer: + case BufferType::ByteAddressBuffer: + case BufferType::RwByteAddressBuffer: + return RHI::ShaderInputBufferType::Raw; + case BufferType::RaytracingAccelerationStructure: + return RHI::ShaderInputBufferType::AccelerationStructure; + default: + AZ_Assert(false, "Unhandled BufferType"); + return RHI::ShaderInputBufferType::Unknown; + } + } + + bool LoadShaderResourceGroupLayouts( + [[maybe_unused]] const char* builderName, const SrgDataContainer& resourceGroups, + const bool platformUsesRegisterSpaces, RPI::ShaderResourceGroupLayoutList& srgLayoutList) + { + // 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. + // All we care about is whether the shaderPlatformInterface appends the "--use-spaces" flag. + bool useRegisterId = platformUsesRegisterSpaces; + + // Load all SRGs included in source file + for (const SrgData& srgData : resourceGroups) + { + RHI::Ptr newSrgLayout = RHI::ShaderResourceGroupLayout::Create(); + newSrgLayout->SetName(AZ::Name{srgData.m_name.c_str()}); + newSrgLayout->SetBindingSlot(srgData.m_bindingSlot.m_index); + + // Samplers + for (const SamplerSrgData& samplerData : srgData.m_samplers) + { + if (samplerData.m_isDynamic) + { + newSrgLayout->AddShaderInput( + {samplerData.m_nameId, samplerData.m_count, + useRegisterId ? samplerData.m_registerId : RHI::UndefinedRegisterSlot}); + } + else + { + newSrgLayout->AddStaticSampler( + {samplerData.m_nameId, samplerData.m_descriptor, + useRegisterId ? samplerData.m_registerId : RHI::UndefinedRegisterSlot}); + } + } + + // Images + for (const TextureSrgData& textureData : srgData.m_textures) + { + const RHI::ShaderInputImageAccess imageAccess = + textureData.m_isReadOnlyType ? RHI::ShaderInputImageAccess::Read : RHI::ShaderInputImageAccess::ReadWrite; + + const RHI::ShaderInputImageType imageType = SrgLayoutUtility::ToShaderInputImageType(textureData.m_type); + + if (imageType != RHI::ShaderInputImageType::Unknown) + { + if (textureData.m_count != aznumeric_cast(-1)) + { + newSrgLayout->AddShaderInput( + {textureData.m_nameId, imageAccess, imageType, textureData.m_count, + useRegisterId ? textureData.m_registerId : RHI::UndefinedRegisterSlot}); + } + else + { + // unbounded array + newSrgLayout->AddShaderInput( + {textureData.m_nameId, imageAccess, imageType, + useRegisterId ? textureData.m_registerId : RHI::UndefinedRegisterSlot}); + } + } + else + { + AZ_Error( + builderName, false, "Failed to build Shader Resource Group Asset: Image %s has an unknown type.", + textureData.m_nameId.GetCStr()); + return false; + } + } + + // Buffers + { + for (const ConstantBufferData& cbData : srgData.m_constantBuffers) + { + newSrgLayout->AddShaderInput( + {cbData.m_nameId, RHI::ShaderInputBufferAccess::Constant, RHI::ShaderInputBufferType::Constant, + cbData.m_count, cbData.m_strideSize, useRegisterId ? cbData.m_registerId : RHI::UndefinedRegisterSlot}); + } + + for (const BufferSrgData& bufferData : srgData.m_buffers) + { + const RHI::ShaderInputBufferAccess bufferAccess = + bufferData.m_isReadOnlyType ? RHI::ShaderInputBufferAccess::Read : RHI::ShaderInputBufferAccess::ReadWrite; + + const RHI::ShaderInputBufferType bufferType = SrgLayoutUtility::ToShaderInputBufferType(bufferData.m_type); + + if (bufferType != RHI::ShaderInputBufferType::Unknown) + { + if (bufferData.m_count != aznumeric_cast(-1)) + { + newSrgLayout->AddShaderInput( + {bufferData.m_nameId, bufferAccess, bufferType, bufferData.m_count, bufferData.m_strideSize, + useRegisterId ? bufferData.m_registerId : RHI::UndefinedRegisterSlot}); + } + else + { + // unbounded array + newSrgLayout->AddShaderInput( + {bufferData.m_nameId, bufferAccess, bufferType, bufferData.m_strideSize, + useRegisterId ? bufferData.m_registerId : RHI::UndefinedRegisterSlot}); + } + } + else + { + AZ_Error( + builderName, false, + "Failed to build Shader Resource Group Asset: Buffer %s has un unknown type.", + bufferData.m_nameId.GetCStr()); + return false; + } + } + } + + // SRG Constants + uint32_t constantDataRegisterId = useRegisterId ? srgData.m_srgConstantDataRegisterId : RHI::UndefinedRegisterSlot; + for (const SrgConstantData& srgConstants : srgData.m_srgConstantData) + { + newSrgLayout->AddShaderInput( + {srgConstants.m_nameId, srgConstants.m_constantByteOffset, srgConstants.m_constantByteSize, + constantDataRegisterId}); + } + + // Shader Variant Key fallback + if (srgData.m_fallbackSize > 0) + { + // Designates this SRG as a ShaderVariantKey fallback + newSrgLayout->SetShaderVariantKeyFallback(srgData.m_fallbackName, srgData.m_fallbackSize); + } + + srgLayoutList.push_back(newSrgLayout); + } + + return true; + } + + } // namespace SrgLayoutUtility + } // namespace ShaderBuilder +} // AZ diff --git a/Gems/Atom/Asset/Shader/Code/Source/Editor/SrgLayoutUtility.h b/Gems/Atom/Asset/Shader/Code/Source/Editor/SrgLayoutUtility.h new file mode 100644 index 0000000000..43607f600c --- /dev/null +++ b/Gems/Atom/Asset/Shader/Code/Source/Editor/SrgLayoutUtility.h @@ -0,0 +1,34 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#pragma once + +#include + +#include "CommonFiles/CommonTypes.h" +#include +#include "ShaderBuilderUtility.h" + +namespace AZ +{ + namespace ShaderBuilder + { + namespace SrgLayoutUtility + { + + bool LoadShaderResourceGroupLayouts( + [[maybe_unused]] const char* builderName, const SrgDataContainer& resourceGroups, const bool platformUsesRegisterSpaces, + RPI::ShaderResourceGroupLayoutList& srgLayoutList); + + } // SrgLayoutUtility namespace + } // ShaderBuilder namespace +} // AZ diff --git a/Gems/Atom/Asset/Shader/Code/atom_asset_shader_builders_files.cmake b/Gems/Atom/Asset/Shader/Code/atom_asset_shader_builders_files.cmake index 2032838f94..b3b2032c54 100644 --- a/Gems/Atom/Asset/Shader/Code/atom_asset_shader_builders_files.cmake +++ b/Gems/Atom/Asset/Shader/Code/atom_asset_shader_builders_files.cmake @@ -34,8 +34,14 @@ set(FILES Source/Editor/AzslCompiler.h Source/Editor/ShaderVariantAssetBuilder.cpp Source/Editor/ShaderVariantAssetBuilder.h + Source/Editor/ShaderVariantAssetBuilder2.cpp + Source/Editor/ShaderVariantAssetBuilder2.h Source/Editor/AtomShaderConfig.cpp Source/Editor/AtomShaderConfig.h Source/Editor/PrecompiledShaderBuilder.cpp Source/Editor/PrecompiledShaderBuilder.h + Source/Editor/ShaderAssetBuilder2.cpp + Source/Editor/ShaderAssetBuilder2.h + Source/Editor/SrgLayoutUtility.cpp + Source/Editor/SrgLayoutUtility.h ) diff --git a/Gems/Atom/Bootstrap/Code/Source/BootstrapSystemComponent.cpp b/Gems/Atom/Bootstrap/Code/Source/BootstrapSystemComponent.cpp index e3bdb28046..012a229fe8 100644 --- a/Gems/Atom/Bootstrap/Code/Source/BootstrapSystemComponent.cpp +++ b/Gems/Atom/Bootstrap/Code/Source/BootstrapSystemComponent.cpp @@ -123,6 +123,12 @@ namespace AZ m_windowHandle = m_nativeWindow->GetWindowHandle(); } + else + { + // Disable default scene creation for non-games projects + // This can be manually overridden via the DefaultWindowBus. + m_createDefaultScene = false; + } AzFramework::AssetCatalogEventBus::Handler::BusConnect(); TickBus::Handler::BusConnect(); @@ -351,6 +357,17 @@ namespace AZ scene->AddRenderPipeline(brdfTexturePipeline); } + // Send notification when the scene and its pipeline are ready. + // Use the first created pipeline's scene as our default scene for now to allow + // consumers waiting on scene availability to initialize. + if (!m_defaultSceneReady) + { + m_defaultScene = scene; + Render::Bootstrap::NotificationBus::Broadcast( + &Render::Bootstrap::NotificationBus::Handler::OnBootstrapSceneReady, m_defaultScene.get()); + m_defaultSceneReady = true; + } + return true; } @@ -364,9 +381,6 @@ namespace AZ { m_renderPipelineId = pipeline->GetId(); } - - // Send notification when the scene and its pipeline are ready - Render::Bootstrap::NotificationBus::Broadcast(&Render::Bootstrap::NotificationBus::Handler::OnBootstrapSceneReady, m_defaultScene.get()); } void BootstrapSystemComponent::DestroyDefaultScene() diff --git a/Gems/Atom/Bootstrap/Code/Source/BootstrapSystemComponent.h b/Gems/Atom/Bootstrap/Code/Source/BootstrapSystemComponent.h index 7323a54221..65390ff153 100644 --- a/Gems/Atom/Bootstrap/Code/Source/BootstrapSystemComponent.h +++ b/Gems/Atom/Bootstrap/Code/Source/BootstrapSystemComponent.h @@ -125,6 +125,7 @@ namespace AZ Data::Instance m_brdfTexture; bool m_createDefaultScene = true; + bool m_defaultSceneReady = false; // Maps AZ scenes to RPI scene weak pointers to allow looking up a ScenePtr instead of a raw Scene* AZStd::unordered_map> m_azSceneToAtomSceneMap; diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_ForwardPass.shader b/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_ForwardPass.shader index 7964e3c84a..a0e9708468 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_ForwardPass.shader +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_ForwardPass.shader @@ -31,8 +31,7 @@ }, "CompilerHints" : { - "DisableOptimizations" : true, - "DxcGenerateDebugInfo" : true + "DisableOptimizations" : false }, "ProgramSettings": diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialInputs/DetailMapsCommonFunctor.lua b/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialInputs/DetailMapsCommonFunctor.lua index 15e4b4f416..e1a3dd6f29 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialInputs/DetailMapsCommonFunctor.lua +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialInputs/DetailMapsCommonFunctor.lua @@ -35,16 +35,16 @@ end function Process(context) local isFeatureEnabled = context:GetMaterialPropertyValue_bool("detailLayerGroup.enableDetailLayer") - local blendMaskTexture = context:GetMaterialPropertyValue_image("detailLayerGroup.blendDetailMask") + local blendMaskTexture = context:GetMaterialPropertyValue_Image("detailLayerGroup.blendDetailMask") local blendMaskTextureEnabled = context:GetMaterialPropertyValue_bool("detailLayerGroup.enableDetailMaskTexture") context:SetShaderOptionValue_bool("o_detail_blendMask_useTexture", isFeatureEnabled and blendMaskTextureEnabled and blendMaskTexture ~= nil) local baseColorDetailEnabled = context:GetMaterialPropertyValue_bool("detailLayerGroup.enableBaseColor") - local baseColorDetailTexture = context:GetMaterialPropertyValue_image("detailLayerGroup.baseColorDetailMap") + local baseColorDetailTexture = context:GetMaterialPropertyValue_Image("detailLayerGroup.baseColorDetailMap") context:SetShaderOptionValue_bool("o_detail_baseColor_useTexture", isFeatureEnabled and baseColorDetailEnabled and baseColorDetailTexture ~= nil) local normalDetailEnabled = context:GetMaterialPropertyValue_bool("detailLayerGroup.enableNormals") - local normalDetailTexture = context:GetMaterialPropertyValue_image("detailLayerGroup.normalDetailMap") + local normalDetailTexture = context:GetMaterialPropertyValue_Image("detailLayerGroup.normalDetailMap") context:SetShaderOptionValue_bool("o_detail_normal_useTexture", isFeatureEnabled and normalDetailEnabled and normalDetailTexture ~= nil) end @@ -78,7 +78,7 @@ function ProcessEditor(context) context:SetMaterialPropertyVisibility("detailUV.rotateDegrees", mainVisibility) context:SetMaterialPropertyVisibility("detailUV.scale", mainVisibility) - local blendMaskTexture = context:GetMaterialPropertyValue_image("detailLayerGroup.blendDetailMask") + local blendMaskTexture = context:GetMaterialPropertyValue_Image("detailLayerGroup.blendDetailMask") if(nil == blendMaskTexture) then context:SetMaterialPropertyVisibility("detailLayerGroup.enableDetailMaskTexture", MaterialPropertyVisibility_Hidden) context:SetMaterialPropertyVisibility("detailLayerGroup.blendDetailMaskUv", MaterialPropertyVisibility_Hidden) diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/Skin_WrinkleMaps.lua b/Gems/Atom/Feature/Common/Assets/Materials/Types/Skin_WrinkleMaps.lua index d77918f521..9e1bc3763a 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/Skin_WrinkleMaps.lua +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/Skin_WrinkleMaps.lua @@ -65,8 +65,8 @@ function Process(context) for i=1,MAX_WRINKLE_LAYER_COUNT do if(i <= count) then - isBaseColorTextureMissing = isBaseColorEnabled and nil == context:GetMaterialPropertyValue_image("wrinkleLayers.baseColorMap" .. i) - isNormalTextureMissing = isNormalEnabled and nil == context:GetMaterialPropertyValue_image("wrinkleLayers.normalMap" .. i) + isBaseColorTextureMissing = isBaseColorEnabled and nil == context:GetMaterialPropertyValue_Image("wrinkleLayers.baseColorMap" .. i) + isNormalTextureMissing = isNormalEnabled and nil == context:GetMaterialPropertyValue_Image("wrinkleLayers.normalMap" .. i) context:SetShaderOptionValue_bool("o_wrinkleLayers_baseColor_useTexture" .. i, not isBaseColorTextureMissing) context:SetShaderOptionValue_bool("o_wrinkleLayers_normal_useTexture" .. i, not isNormalTextureMissing) else diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR.materialtype b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR.materialtype index e2119dcf12..a2854e8902 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR.materialtype +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR.materialtype @@ -23,11 +23,6 @@ "displayName": "UVs", "description": "Properties for configuring UV transforms for the entire material, including the blend masks." }, - { - "id": "subsurfaceScattering", - "displayName": "Subsurface Scattering", - "description": "Properties for configuring subsurface scattering effects." - }, { // Note: this property group is used in the DiffuseGlobalIllumination pass, it is not read by the StandardPBR shader "id": "irradiance", @@ -204,7 +199,7 @@ "displayName": "Debug Draw Mode", "description": "Enables various debug view features.", "type": "Enum", - "enumValues": [ "None", "BlendMaskValues", "DepthMaps" ], + "enumValues": [ "None", "BlendSource", "DepthMaps" ], "defaultValue": "None", "connection": { "type": "ShaderOption", @@ -300,6 +295,28 @@ } ], "blend": [ + { + "id": "enableLayer2", + "displayName": "Enable Layer 2", + "description": "Whether to enable layer 2.", + "type": "Bool", + "defaultValue": false, + "connection": { + "type": "ShaderOption", + "id": "o_layer2_enabled" + } + }, + { + "id": "enableLayer3", + "displayName": "Enable Layer 3", + "description": "Whether to enable layer 3.", + "type": "Bool", + "defaultValue": false, + "connection": { + "type": "ShaderOption", + "id": "o_layer3_enabled" + } + }, { "id": "blendSource", "displayName": "Blend Source", @@ -467,183 +484,6 @@ "step": 0.1 } ], - "subsurfaceScattering": [ - { - "id": "enableSubsurfaceScattering", - "displayName": "Subsurface Scattering", - "description": "Enable subsurface scattering feature, this will disable metallic and parallax mapping property due to incompatibility", - "type": "Bool", - "defaultValue": false, - "connection": { - "type": "ShaderOption", - "id": "o_enableSubsurfaceScattering" - } - }, - { - "id": "subsurfaceScatterFactor", - "displayName": " Factor", - "description": "Strength factor for scaling percentage of subsurface scattering effect applied", - "type": "float", - "defaultValue": 1.0, - "min": 0.0, - "max": 1.0, - "connection": { - "type": "ShaderInput", - "id": "m_subsurfaceScatteringFactor" - } - }, - { - "id": "influenceMap", - "displayName": " Influence Map", - "description": "Use texture map to control the strength of subsurface scattering", - "type": "Image", - "connection": { - "type": "ShaderInput", - "id": "m_subsurfaceScatteringInfluenceMap" - } - }, - { - "id": "useInfluenceMap", - "displayName": " Use Influence Map", - "description": "Whether to use the texture map as influence mask.", - "type": "Bool", - "defaultValue": true - }, - { - "id": "influenceMapUv", - "displayName": " UV", - "description": "Influence map UV set", - "type": "Enum", - "enumIsUv": true, - "defaultValue": "Tiled", - "connection": { - "type": "ShaderInput", - "id": "m_subsurfaceScatteringInfluenceMapUvIndex" - } - }, - { - "id": "scatterColor", - "displayName": " Scatter color", - "description": "Color of volume light traveled through", - "type": "Color", - "defaultValue": [ 1.0, 0.27, 0.13 ] - }, - { - "id": "scatterDistance", - "displayName": " Scatter distance", - "description": "How far light traveled inside the volume", - "type": "float", - "defaultValue": 8, - "min": 0.0, - "softMax": 20.0 - }, - { - "id": "quality", - "displayName": " Quality", - "description": "How much percent of sample will be used for each pixel, more samples improve quality and reduce artifacts, especially when the scatter distance is relatively large, but slow down computation time, 1.0 = full set 200 samples per pixel", - "type": "float", - "defaultValue": 0.4, - "min": 0.2, - "max": 1.0, - "connection": { - "type": "ShaderInput", - "id": "m_subsurfaceScatteringQuality" - } - }, - { - "id": "transmissionMode", - "displayName": "Transmission", - "description": "Algorithm used for calculating transmission", - "type": "Enum", - "enumValues": [ "None", "ThickObject", "ThinObject" ], - "defaultValue": "None", - "connection": { - "type": "ShaderOption", - "id": "o_transmission_mode" - } - }, - { - "id": "thickness", - "displayName": " Thickness", - "description": "Normalized global thickness, the maxima between this value (multiplied by thickness map if enabled) and thickness from shadow map (if applicable) will be used as final thickness of pixel", - "type": "float", - "defaultValue": 0.5, - "min": 0.0, - "max": 1.0 - }, - { - "id": "thicknessMap", - "displayName": " Thickness Map", - "description": "Use a greyscale texture for per pixel thickness", - "type": "Image", - "connection": { - "type": "ShaderInput", - "id": "m_transmissionThicknessMap" - } - }, - { - "id": "useThicknessMap", - "displayName": " Use Thickness Map", - "description": "Whether to use the thickness map", - "type": "Bool", - "defaultValue": true - }, - { - "id": "thicknessMapUv", - "displayName": " UV", - "description": "Thickness map UV set", - "type": "Enum", - "enumIsUv": true, - "defaultValue": "Tiled", - "connection": { - "type": "ShaderInput", - "id": "m_transmissionThicknessMapUvIndex" - } - }, - { - "id": "transmissionTint", - "displayName": " Transmission Tint", - "description": "Color of the volume light travelling through", - "type": "Color", - "defaultValue": [ 1.0, 0.8, 0.6 ] - }, - { - "id": "transmissionPower", - "displayName": " Power", - "description": "How much transmitted light scatter radially ", - "type": "float", - "defaultValue": 6.0, - "min": 0.0, - "softMax": 20.0 - }, - { - "id": "transmissionDistortion", - "displayName": " Distortion", - "description": "How much light direction distorted towards surface normal", - "type": "float", - "defaultValue": 0.1, - "min": 0.0, - "max": 1.0 - }, - { - "id": "transmissionAttenuation", - "displayName": " Attenuation", - "description": "How fast transmitted light fade with thickness", - "type": "float", - "defaultValue": 4.0, - "min": 0.0, - "softMax": 20.0 - }, - { - "id": "transmissionScale", - "displayName": " Scale", - "description": "Strength of transmission", - "type": "float", - "defaultValue": 3.0, - "min": 0.0, - "softMax": 20.0 - } - ], "irradiance": [ // Note: this property group is used in the DiffuseGlobalIllumination pass, it is not read by the StandardPBR shader { @@ -2845,22 +2685,9 @@ } }, { - // Preprocess & build parameter set for subsurface scattering and translucency - "type": "HandleSubsurfaceScatteringParameters", + "type": "Lua", "args": { - "mode": "subsurfaceScattering.transmissionMode", - "scale" : "subsurfaceScattering.transmissionScale", - "power" : "subsurfaceScattering.transmissionPower", - "distortion" : "subsurfaceScattering.transmissionDistortion", - "attenuation" : "subsurfaceScattering.transmissionAttenuation", - "tintColor" : "subsurfaceScattering.transmissionTint", - "thickness" : "subsurfaceScattering.thickness", - "enabled": "subsurfaceScattering.enableSubsurfaceScattering", - "scatterDistanceColor" : "subsurfaceScattering.scatterColor", - "scatterDistanceIntensity" : "subsurfaceScattering.scatterDistance", - "scatterDistanceShaderInput" : "m_scatterDistance", - "parametersShaderInput" : "m_transmissionParams", - "tintThickenssShaderInput" : "m_transmissionTintThickness" + "file": "StandardMultilayerPBR_LayerEnable.lua" } }, { @@ -2875,12 +2702,6 @@ "file": "StandardMultilayerPBR_Parallax.lua" } }, - { - "type": "Lua", - "args": { - "file": "StandardPBR_SubsurfaceState.lua" - } - }, //############################################################################################## // Layer 1 Functors //############################################################################################## diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_Common.azsli b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_Common.azsli index ba0eaf2ac1..d2314efefe 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_Common.azsli +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_Common.azsli @@ -42,7 +42,7 @@ COMMON_SRG_INPUTS_PARALLAX(prefix) ShaderResourceGroup MaterialSrg : SRG_PerMaterial { - Texture2D m_blendMaskTexture; + Texture2D m_blendMaskTexture; uint m_blendMaskUvIndex; // Auto-generate material SRG fields for common inputs for each layer @@ -91,29 +91,14 @@ ShaderResourceGroup MaterialSrg : SRG_PerMaterial MipFilter = Linear; }; - // Parameters for subsurface scattering - float m_subsurfaceScatteringFactor; - float m_subsurfaceScatteringQuality; - float3 m_scatterDistance; - Texture2D m_subsurfaceScatteringInfluenceMap; - uint m_subsurfaceScatteringInfluenceMapUvIndex; - - // Parameters for transmission - - // Elements of m_transmissionParams: - // Thick object mode: (attenuation coefficient, power, distortion, scale) - // Thin object mode: (float3 scatter distance, scale) - float4 m_transmissionParams; - - // (float3 TintColor, thickness) - float4 m_transmissionTintThickness; - Texture2D m_transmissionThicknessMap; - uint m_transmissionThicknessMapUvIndex; } // ------ Shader Options ---------------------------------------- -enum class DebugDrawMode { None, BlendMaskValues, DepthMaps }; +option bool o_layer2_enabled; +option bool o_layer3_enabled; + +enum class DebugDrawMode { None, BlendSource, DepthMaps }; option DebugDrawMode o_debugDrawMode; enum class BlendMaskSource { TextureMap, VertexColors, Fallback }; @@ -127,6 +112,10 @@ option bool o_blendMask_isBound; // ------ Blend Utilities ---------------------------------------- +// This is mainly used to pass extra data to the GetDepth callback function during the parallax depth search. +// But since we have it, we use it in some other functions as well rather than passing it around. +static float3 s_blendMaskFromVertexStream; + //! Returns the BlendMaskSource that will actually be used when rendering (not necessarily the same BlendMaskSource specified by the user) BlendMaskSource GetFinalBlendMaskSource() { @@ -151,40 +140,84 @@ BlendMaskSource GetFinalBlendMaskSource() } } -//! Return the final blend mask values to be used for rendering, based on the available data and configuration. -float3 GetBlendMaskValues(float2 uv, float3 vertexBlendMask) +//! Return the raw blend source values directly from the blend mask or vertex colors, depending on the available data and configuration. +//! layer1 is an implicit base layer +//! layer2 is weighted by r +//! layer3 is weighted by g +//! b is reserved for perhaps a dedicated puddle layer +float3 GetBlendSourceValues(float2 uv) { - float3 blendMaskValues; + float3 blendSourceValues = float3(0,0,0); - switch(GetFinalBlendMaskSource()) + if(o_layer2_enabled || o_layer3_enabled) { - case BlendMaskSource::TextureMap: - blendMaskValues = MaterialSrg::m_blendMaskTexture.Sample(MaterialSrg::m_sampler, uv).rgb; - break; - case BlendMaskSource::VertexColors: - blendMaskValues = vertexBlendMask; - break; - case BlendMaskSource::Fallback: - blendMaskValues = float3(1,1,1); - break; + switch(GetFinalBlendMaskSource()) + { + case BlendMaskSource::TextureMap: + blendSourceValues = MaterialSrg::m_blendMaskTexture.Sample(MaterialSrg::m_sampler, uv).rgb; + break; + case BlendMaskSource::VertexColors: + blendSourceValues = s_blendMaskFromVertexStream; + break; + } + + if(!o_layer2_enabled) + { + blendSourceValues.r = 0.0; + } + + if(!o_layer3_enabled) + { + blendSourceValues.g = 0.0; + } } - blendMaskValues = blendMaskValues / (blendMaskValues.r + blendMaskValues.g + blendMaskValues.b); - - return blendMaskValues; + return blendSourceValues; } -float BlendLayers(float layer1, float layer2, float layer3, float3 blendMaskValues) +//! Return the final blend mask values to be used for rendering, based on the available data and configuration. +//! @return The blend weights for each layer. +//! Even though layer1 not explicitly specified in the blend source data, it is explicitly included with the returned values. +//! layer1 = r +//! layer2 = g +//! layer3 = b +float3 GetBlendWeights(float2 uv) { - return dot(float3(layer1, layer2, layer3), blendMaskValues); + float3 blendWeights; + + if(o_layer2_enabled || o_layer3_enabled) + { + float3 blendSourceValues = GetBlendSourceValues(uv); + + // Calculate blend weights such that multiplying and adding them with layer data is equivalent + // to lerping between each layer. + // final = lerp(final, layer1, blendWeights.r) + // final = lerp(final, layer2, blendWeights.g) + // final = lerp(final, layer3, blendWeights.b) + + blendWeights.b = blendSourceValues.g; + blendWeights.g = (1.0 - blendSourceValues.g) * blendSourceValues.r; + blendWeights.r = (1.0 - blendSourceValues.g) * (1.0 - blendSourceValues.r); + } + else + { + blendWeights = float3(1,0,0); + } + + return blendWeights; } -float2 BlendLayers(float2 layer1, float2 layer2, float2 layer3, float3 blendMaskValues) + +float BlendLayers(float layer1, float layer2, float layer3, float3 blendWeights) { - return layer1 * blendMaskValues.r + layer2 * blendMaskValues.g + layer3 * blendMaskValues.b; + return dot(float3(layer1, layer2, layer3), blendWeights); } -float3 BlendLayers(float3 layer1, float3 layer2, float3 layer3, float3 blendMaskValues) +float2 BlendLayers(float2 layer1, float2 layer2, float2 layer3, float3 blendWeights) { - return layer1 * blendMaskValues.r + layer2 * blendMaskValues.g + layer3 * blendMaskValues.b; + return layer1 * blendWeights.r + layer2 * blendWeights.g + layer3 * blendWeights.b; +} +float3 BlendLayers(float3 layer1, float3 layer2, float3 layer3, float3 blendWeights) +{ + return layer1 * blendWeights.r + layer2 * blendWeights.g + layer3 * blendWeights.b; } // ------ Parallax Utilities ---------------------------------------- @@ -203,16 +236,6 @@ bool ShouldHandleParallaxInDepthShaders() return ShouldHandleParallax() && o_parallax_enablePixelDepthOffset; } -// These static values are used to pass extra data to the GetDepth callback function during the parallax depth search. -static float3 s_blendMaskFromVertexStream; - -//! Setup static variables that are needed by the GetDepth callback function -//! @param vertexBlendMask the blend mask values from the vertex input stream. -void GetDepth_Setup(float3 vertexBlendMask) -{ - s_blendMaskFromVertexStream = vertexBlendMask; -} - // Callback function for ParallaxMapping.azsli DepthResult GetDepth(float2 uv, float2 uv_ddx, float2 uv_ddy) { @@ -231,7 +254,7 @@ DepthResult GetDepth(float2 uv, float2 uv_ddx, float2 uv_ddy) layerDepthValues.r -= MaterialSrg::m_layer1_m_depthOffset; } - if(o_layer2_o_useDepthMap) + if(o_layer2_enabled && o_layer2_o_useDepthMap) { float2 layerUv = uv; if(MaterialSrg::m_parallaxUvIndex == 0) @@ -244,7 +267,7 @@ DepthResult GetDepth(float2 uv, float2 uv_ddx, float2 uv_ddy) layerDepthValues.g -= MaterialSrg::m_layer2_m_depthOffset; } - if(o_layer3_o_useDepthMap) + if(o_layer3_enabled && o_layer3_o_useDepthMap) { float2 layerUv = uv; if(MaterialSrg::m_parallaxUvIndex == 0) @@ -260,8 +283,8 @@ DepthResult GetDepth(float2 uv, float2 uv_ddx, float2 uv_ddy) // Note, when the blend source is BlendMaskSource::VertexColors, parallax will not be able to blend correctly between layers. It will end up using the same blend mask values // for every UV position when searching for the intersection. This leads to smearing artifacts at the transition point, but these won't be so noticeable as long as // you have a small depth factor relative to the size of the blend transition. - float3 blendMaskValues = GetBlendMaskValues(uv, s_blendMaskFromVertexStream); + float3 blendWeights = GetBlendWeights(uv); - float depth = BlendLayers(layerDepthValues.r, layerDepthValues.g, layerDepthValues.b, blendMaskValues); + float depth = BlendLayers(layerDepthValues.r, layerDepthValues.g, layerDepthValues.b, blendWeights); return DepthResultAbsolute(depth); } diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_DepthPass_WithPS.azsl b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_DepthPass_WithPS.azsl index ae156d7313..178deca8a2 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_DepthPass_WithPS.azsl +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_DepthPass_WithPS.azsl @@ -108,7 +108,7 @@ PSDepthOutput MainPS(VSDepthOutput IN, bool isFrontFace : SV_IsFrontFace) float3 bitangents[UvSetCount] = { IN.m_bitangent.xyz, float3(0, 0, 0) }; PrepareGeneratedTangent(IN.m_normal, IN.m_worldPosition, isFrontFace, IN.m_uv, UvSetCount, tangents, bitangents, 1); - GetDepth_Setup(IN.m_blendMask); + s_blendMaskFromVertexStream = IN.m_blendMask; float depth; diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_ForwardPass.azsl b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_ForwardPass.azsl index bc32aa1370..580e5206ab 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_ForwardPass.azsl +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_ForwardPass.azsl @@ -55,7 +55,6 @@ DEFINE_LAYER_OPTIONS(o_layer1_) DEFINE_LAYER_OPTIONS(o_layer2_) DEFINE_LAYER_OPTIONS(o_layer3_) -#include "MaterialInputs/SubsurfaceInput.azsli" #include "MaterialInputs/TransmissionInput.azsli" #include "StandardMultilayerPBR_Common.azsli" @@ -127,6 +126,181 @@ VSOutput ForwardPassVS(VSInput IN) return OUT; } +//! Collects all the raw Standard material inputs for a single layer. See ProcessStandardMaterialInputs(). +struct StandardMaterialInputs +{ + COMMON_SRG_INPUTS_BASE_COLOR() + COMMON_SRG_INPUTS_ROUGHNESS() + COMMON_SRG_INPUTS_METALLIC() + COMMON_SRG_INPUTS_SPECULAR_F0() + COMMON_SRG_INPUTS_NORMAL() + COMMON_SRG_INPUTS_CLEAR_COAT() + COMMON_SRG_INPUTS_OCCLUSION() + COMMON_SRG_INPUTS_EMISSIVE() + // Note parallax is omitted here because that requires special handling. + + bool m_normal_useTexture; + bool m_baseColor_useTexture; + bool m_metallic_useTexture; + bool m_specularF0_useTexture; + bool m_roughness_useTexture; + bool m_emissiveEnabled; + bool m_emissive_useTexture; + bool m_diffuseOcclusion_useTexture; + bool m_specularOcclusion_useTexture; + bool m_clearCoatEnabled; + bool m_clearCoat_factor_useTexture; + bool m_clearCoat_roughness_useTexture; + bool m_clearCoat_normal_useTexture; + + TextureBlendMode m_baseColorTextureBlendMode; + + float2 m_vertexUv[UvSetCount]; + float3x3 m_uvMatrix; + float m_normal; + float3 m_tangents[UvSetCount]; + float3 m_bitangents[UvSetCount]; + + sampler m_sampler; + + bool m_isFrontFace; +}; + +//! Holds the final processed material inputs, after all flags have been checked, textures have been sampled, factors have been applied, etc. +//! This data is ready to be copied into a Surface and/or LightingData struct for the lighting system to consume. +class ProcessedMaterialInputs +{ + float3 m_normalTS; //!< Normal in tangent-space + float3 m_baseColor; + float3 m_specularF0Factor; + float m_metallic; + float m_roughness; + float3 m_emissiveLighting; + float m_diffuseAmbientOcclusion; + float m_specularOcclusion; + ClearCoatSurfaceData m_clearCoat; + + void InitializeToZero() + { + m_normalTS = float3(0,0,0); + m_baseColor = float3(0,0,0); + m_specularF0Factor = float3(0,0,0); + m_metallic = 0.0f; + m_roughness = 0.0f; + m_emissiveLighting = float3(0,0,0); + m_diffuseAmbientOcclusion = 0; + m_specularOcclusion = 0; + m_clearCoat.InitializeToZero(); + } +}; + +//! Processes the set of Standard material inputs for a single layer. +//! The FILL_STANDARD_MATERIAL_INPUTS() macro below can be used to fill the StandardMaterialInputs struct. +ProcessedMaterialInputs ProcessStandardMaterialInputs(StandardMaterialInputs inputs) +{ + ProcessedMaterialInputs result; + + float2 transformedUv[UvSetCount]; + transformedUv[0] = mul(inputs.m_uvMatrix, float3(inputs.m_vertexUv[0], 1.0)).xy; + transformedUv[1] = inputs.m_vertexUv[1]; + + float3x3 normalUvMatrix = inputs.m_normalMapUvIndex == 0 ? inputs.m_uvMatrix : CreateIdentity3x3(); + result.m_normalTS = GetNormalInputTS(inputs.m_normalMap, inputs.m_sampler, transformedUv[inputs.m_normalMapUvIndex], inputs.m_flipNormalX, inputs.m_flipNormalY, normalUvMatrix, inputs.m_normal_useTexture, inputs.m_normalFactor); + + float3 sampledBaseColor = GetBaseColorInput(inputs.m_baseColorMap, inputs.m_sampler, transformedUv[inputs.m_baseColorMapUvIndex], inputs.m_baseColor.rgb, inputs.m_baseColor_useTexture); + result.m_baseColor = BlendBaseColor(sampledBaseColor, inputs.m_baseColor.rgb, inputs.m_baseColorFactor, inputs.m_baseColorTextureBlendMode, inputs.m_baseColor_useTexture); + result.m_specularF0Factor = GetSpecularInput(inputs.m_specularF0Map, inputs.m_sampler, transformedUv[inputs.m_specularF0MapUvIndex], inputs.m_specularF0Factor, inputs.m_specularF0_useTexture); + result.m_metallic = GetMetallicInput(inputs.m_metallicMap, inputs.m_sampler, transformedUv[inputs.m_metallicMapUvIndex], inputs.m_metallicFactor, inputs.m_metallic_useTexture); + result.m_roughness = GetRoughnessInput(inputs.m_roughnessMap, MaterialSrg::m_sampler, transformedUv[inputs.m_roughnessMapUvIndex], inputs.m_roughnessFactor, inputs.m_roughnessLowerBound, inputs.m_roughnessUpperBound, inputs.m_roughness_useTexture); + + result.m_emissiveLighting = GetEmissiveInput(inputs.m_emissiveMap, inputs.m_sampler, transformedUv[inputs.m_emissiveMapUvIndex], inputs.m_emissiveIntensity, inputs.m_emissiveColor.rgb, inputs.m_emissiveEnabled, inputs.m_emissive_useTexture); + result.m_diffuseAmbientOcclusion = GetOcclusionInput(inputs.m_diffuseOcclusionMap, inputs.m_sampler, transformedUv[inputs.m_diffuseOcclusionMapUvIndex], inputs.m_diffuseOcclusionFactor, inputs.m_diffuseOcclusion_useTexture); + result.m_specularOcclusion = GetOcclusionInput(inputs.m_specularOcclusionMap, MaterialSrg::m_sampler, transformedUv[inputs.m_specularOcclusionMapUvIndex], inputs.m_specularOcclusionFactor, inputs.m_specularOcclusion_useTexture); + + result.m_clearCoat.InitializeToZero(); + if(inputs.m_clearCoatEnabled) + { + float3x3 clearCoatUvMatrix = inputs.m_clearCoatNormalMapUvIndex == 0 ? inputs.m_uvMatrix : CreateIdentity3x3(); + + GetClearCoatInputs(inputs.m_clearCoatInfluenceMap, transformedUv[inputs.m_clearCoatInfluenceMapUvIndex], inputs.m_clearCoatFactor, inputs.m_clearCoat_factor_useTexture, + inputs.m_clearCoatRoughnessMap, transformedUv[inputs.m_clearCoatRoughnessMapUvIndex], inputs.m_clearCoatRoughness, inputs.m_clearCoat_roughness_useTexture, + inputs.m_clearCoatNormalMap, transformedUv[inputs.m_clearCoatNormalMapUvIndex], inputs.m_normal, inputs.m_clearCoat_normal_useTexture, inputs.m_clearCoatNormalStrength, + clearCoatUvMatrix, inputs.m_tangents[inputs.m_clearCoatNormalMapUvIndex], inputs.m_bitangents[inputs.m_clearCoatNormalMapUvIndex], + inputs.m_sampler, inputs.m_isFrontFace, + result.m_clearCoat.factor, result.m_clearCoat.roughness, result.m_clearCoat.normal); + } + + return result; +} + +//! Fills a StandardMaterialInputs struct with data from the MaterialSrg, shader options, and local vertex data. +#define FILL_STANDARD_MATERIAL_INPUTS(inputs, srgLayerPrefix, optionsLayerPrefix, blendWeight) \ + inputs.m_sampler = MaterialSrg::m_sampler; \ + inputs.m_vertexUv = IN.m_uv; \ + inputs.m_uvMatrix = srgLayerPrefix##m_uvMatrix; \ + inputs.m_normal = IN.m_normal; \ + inputs.m_tangents = tangents; \ + inputs.m_bitangents = bitangents; \ + inputs.m_isFrontFace = isFrontFace; \ + \ + inputs.m_normalMapUvIndex = srgLayerPrefix##m_normalMapUvIndex; \ + inputs.m_normalMap = srgLayerPrefix##m_normalMap; \ + inputs.m_flipNormalX = srgLayerPrefix##m_flipNormalX; \ + inputs.m_flipNormalY = srgLayerPrefix##m_flipNormalY; \ + inputs.m_normal_useTexture = optionsLayerPrefix##o_normal_useTexture; \ + inputs.m_normalFactor = srgLayerPrefix##m_normalFactor * blendWeight; \ + inputs.m_baseColorMap = srgLayerPrefix##m_baseColorMap; \ + inputs.m_baseColorMapUvIndex = srgLayerPrefix##m_baseColorMapUvIndex; \ + inputs.m_baseColor = srgLayerPrefix##m_baseColor; \ + inputs.m_baseColor_useTexture = optionsLayerPrefix##o_baseColor_useTexture; \ + inputs.m_baseColorFactor = srgLayerPrefix##m_baseColorFactor; \ + inputs.m_baseColorTextureBlendMode = optionsLayerPrefix##o_baseColorTextureBlendMode; \ + inputs.m_metallicMap = srgLayerPrefix##m_metallicMap; \ + inputs.m_metallicMapUvIndex = srgLayerPrefix##m_metallicMapUvIndex; \ + inputs.m_metallicFactor = srgLayerPrefix##m_metallicFactor; \ + inputs.m_metallic_useTexture = optionsLayerPrefix##o_metallic_useTexture; \ + inputs.m_specularF0Map = srgLayerPrefix##m_specularF0Map; \ + inputs.m_specularF0MapUvIndex = srgLayerPrefix##m_specularF0MapUvIndex; \ + inputs.m_specularF0Factor = srgLayerPrefix##m_specularF0Factor; \ + inputs.m_specularF0_useTexture = optionsLayerPrefix##o_specularF0_useTexture; \ + inputs.m_roughnessMap = srgLayerPrefix##m_roughnessMap; \ + inputs.m_roughnessMapUvIndex = srgLayerPrefix##m_roughnessMapUvIndex; \ + inputs.m_roughnessFactor = srgLayerPrefix##m_roughnessFactor; \ + inputs.m_roughnessLowerBound = srgLayerPrefix##m_roughnessLowerBound; \ + inputs.m_roughnessUpperBound = srgLayerPrefix##m_roughnessUpperBound; \ + inputs.m_roughness_useTexture = optionsLayerPrefix##o_roughness_useTexture; \ + \ + inputs.m_emissiveMap = srgLayerPrefix##m_emissiveMap; \ + inputs.m_emissiveMapUvIndex = srgLayerPrefix##m_emissiveMapUvIndex; \ + inputs.m_emissiveIntensity = srgLayerPrefix##m_emissiveIntensity; \ + inputs.m_emissiveColor = srgLayerPrefix##m_emissiveColor; \ + inputs.m_emissiveEnabled = optionsLayerPrefix##o_emissiveEnabled; \ + inputs.m_emissive_useTexture = optionsLayerPrefix##o_emissive_useTexture; \ + \ + inputs.m_diffuseOcclusionMap = srgLayerPrefix##m_diffuseOcclusionMap; \ + inputs.m_diffuseOcclusionMapUvIndex = srgLayerPrefix##m_diffuseOcclusionMapUvIndex; \ + inputs.m_diffuseOcclusionFactor = srgLayerPrefix##m_diffuseOcclusionFactor; \ + inputs.m_diffuseOcclusion_useTexture = optionsLayerPrefix##o_diffuseOcclusion_useTexture; \ + \ + inputs.m_specularOcclusionMap = srgLayerPrefix##m_specularOcclusionMap; \ + inputs.m_specularOcclusionMapUvIndex = srgLayerPrefix##m_specularOcclusionMapUvIndex; \ + inputs.m_specularOcclusionFactor = srgLayerPrefix##m_specularOcclusionFactor; \ + inputs.m_specularOcclusion_useTexture = optionsLayerPrefix##o_specularOcclusion_useTexture; \ + \ + inputs.m_clearCoatEnabled = o_clearCoat_feature_enabled && optionsLayerPrefix##o_clearCoat_enabled; \ + inputs.m_clearCoatInfluenceMap = srgLayerPrefix##m_clearCoatInfluenceMap; \ + inputs.m_clearCoatInfluenceMapUvIndex = srgLayerPrefix##m_clearCoatInfluenceMapUvIndex; \ + inputs.m_clearCoatFactor = srgLayerPrefix##m_clearCoatFactor; \ + inputs.m_clearCoat_factor_useTexture = optionsLayerPrefix##o_clearCoat_factor_useTexture; \ + inputs.m_clearCoatRoughnessMap = srgLayerPrefix##m_clearCoatRoughnessMap; \ + inputs.m_clearCoatRoughnessMapUvIndex = srgLayerPrefix##m_clearCoatRoughnessMapUvIndex; \ + inputs.m_clearCoatRoughness = srgLayerPrefix##m_clearCoatRoughness; \ + inputs.m_clearCoat_roughness_useTexture = optionsLayerPrefix##o_clearCoat_roughness_useTexture; \ + inputs.m_clearCoatNormalMap = srgLayerPrefix##m_clearCoatNormalMap; \ + inputs.m_clearCoatNormalMapUvIndex = srgLayerPrefix##m_clearCoatNormalMapUvIndex; \ + inputs.m_clearCoat_normal_useTexture = optionsLayerPrefix##o_clearCoat_normal_useTexture; \ + inputs.m_clearCoatNormalStrength = srgLayerPrefix##m_clearCoatNormalStrength; + // ---------- Pixel Shader ---------- @@ -134,6 +308,8 @@ PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace, out float { depthNDC = IN.m_position.z; + s_blendMaskFromVertexStream = IN.m_blendMask; + // ------- Tangents & Bitangets ------- // We support two UV streams, but only a single stream of tangent/bitangent. So for UV[1+] we generated the tangent/bitangent in screen-space. @@ -156,15 +332,14 @@ PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace, out float // ------- Debug Modes ------- - if(o_debugDrawMode == DebugDrawMode::BlendMaskValues) + if(o_debugDrawMode == DebugDrawMode::BlendSource) { - float3 blendMaskValues = GetBlendMaskValues(IN.m_uv[MaterialSrg::m_blendMaskUvIndex], IN.m_blendMask); - return DebugOutput(blendMaskValues); + float3 blendSource = GetBlendSourceValues(IN.m_uv[MaterialSrg::m_blendMaskUvIndex]); + return DebugOutput(blendSource); } if(o_debugDrawMode == DebugDrawMode::DepthMaps) { - GetDepth_Setup(IN.m_blendMask); float depth = GetNormalizedDepth(-MaterialSrg::m_displacementMax, -MaterialSrg::m_displacementMin, IN.m_uv[MaterialSrg::m_parallaxUvIndex], float2(0,0), float2(0,0)); return DebugOutput(float3(depth,depth,depth)); } @@ -173,11 +348,8 @@ PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace, out float bool displacementIsClipped = false; - // Parallax mapping's non uniform uv transformations break screen space subsurface scattering, disable it when subsurface scatteirng is enabled if(ShouldHandleParallax()) { - GetDepth_Setup(IN.m_blendMask); - float3x3 uvMatrix = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrix : CreateIdentity3x3(); float3x3 uvMatrixInverse = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrixInverse : CreateIdentity3x3(); @@ -198,108 +370,86 @@ PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace, out float } } - Surface surface; - surface.position = IN.m_worldPosition; - - // ------- Setup the per-layer UV transforms ------- - - float2 uvLayer1[UvSetCount]; - float2 uvLayer2[UvSetCount]; - float2 uvLayer3[UvSetCount]; - - // Only UV0 will be applied transforms from each layer. - uvLayer1[0] = mul(MaterialSrg::m_layer1_m_uvMatrix, float3(IN.m_uv[0], 1.0)).xy; - uvLayer2[0] = mul(MaterialSrg::m_layer2_m_uvMatrix, float3(IN.m_uv[0], 1.0)).xy; - uvLayer3[0] = mul(MaterialSrg::m_layer3_m_uvMatrix, float3(IN.m_uv[0], 1.0)).xy; - uvLayer1[1] = IN.m_uv[1]; - uvLayer2[1] = IN.m_uv[1]; - uvLayer3[1] = IN.m_uv[1]; - // ------- Calculate Layer Blend Mask Values ------- // Now that any parallax has been calculated, we calculate the blend factors for any layers that are impacted by the parallax. - float3 blendMaskValues = GetBlendMaskValues(IN.m_uv[MaterialSrg::m_blendMaskUvIndex], IN.m_blendMask); + float3 blendWeights = GetBlendWeights(IN.m_uv[MaterialSrg::m_blendMaskUvIndex]); - // ------- Normal ------- + // ------- Layer 1 (base layer) ----------- + + ProcessedMaterialInputs lightingInputLayer1; + { + StandardMaterialInputs inputs; + FILL_STANDARD_MATERIAL_INPUTS(inputs, MaterialSrg::m_layer1_, o_layer1_, blendWeights.r) + lightingInputLayer1 = ProcessStandardMaterialInputs(inputs); + } - float3 layer1_normalFactor = MaterialSrg::m_layer1_m_normalFactor * blendMaskValues.r; - float3 layer2_normalFactor = MaterialSrg::m_layer2_m_normalFactor * blendMaskValues.g; - float3 layer3_normalFactor = MaterialSrg::m_layer3_m_normalFactor * blendMaskValues.b; - float3x3 layer1_uvMatrix = MaterialSrg::m_layer1_m_normalMapUvIndex == 0 ? MaterialSrg::m_layer1_m_uvMatrix : CreateIdentity3x3(); - float3x3 layer2_uvMatrix = MaterialSrg::m_layer2_m_normalMapUvIndex == 0 ? MaterialSrg::m_layer2_m_uvMatrix : CreateIdentity3x3(); - float3x3 layer3_uvMatrix = MaterialSrg::m_layer3_m_normalMapUvIndex == 0 ? MaterialSrg::m_layer3_m_uvMatrix : CreateIdentity3x3(); - float3 layer1_normalTS = GetNormalInputTS(MaterialSrg::m_layer1_m_normalMap, MaterialSrg::m_sampler, uvLayer1[MaterialSrg::m_layer1_m_normalMapUvIndex], MaterialSrg::m_layer1_m_flipNormalX, MaterialSrg::m_layer1_m_flipNormalY, layer1_uvMatrix, o_layer1_o_normal_useTexture, layer1_normalFactor); - float3 layer2_normalTS = GetNormalInputTS(MaterialSrg::m_layer2_m_normalMap, MaterialSrg::m_sampler, uvLayer2[MaterialSrg::m_layer2_m_normalMapUvIndex], MaterialSrg::m_layer2_m_flipNormalX, MaterialSrg::m_layer2_m_flipNormalY, layer2_uvMatrix, o_layer2_o_normal_useTexture, layer2_normalFactor); - float3 layer3_normalTS = GetNormalInputTS(MaterialSrg::m_layer3_m_normalMap, MaterialSrg::m_sampler, uvLayer3[MaterialSrg::m_layer3_m_normalMapUvIndex], MaterialSrg::m_layer3_m_flipNormalX, MaterialSrg::m_layer3_m_flipNormalY, layer3_uvMatrix, o_layer3_o_normal_useTexture, layer3_normalFactor); + // ----------- Layer 2 ----------- + + ProcessedMaterialInputs lightingInputLayer2; + if(o_layer2_enabled) + { + StandardMaterialInputs inputs; + FILL_STANDARD_MATERIAL_INPUTS(inputs, MaterialSrg::m_layer2_, o_layer2_, blendWeights.g) + lightingInputLayer2 = ProcessStandardMaterialInputs(inputs); + } + else + { + lightingInputLayer2.InitializeToZero(); + } - float3 normalTS = ReorientTangentSpaceNormal(layer1_normalTS, layer2_normalTS); - normalTS = ReorientTangentSpaceNormal(normalTS, layer3_normalTS); + // ----------- Layer 3 ----------- + + ProcessedMaterialInputs lightingInputLayer3; + if(o_layer3_enabled) + { + StandardMaterialInputs inputs; + FILL_STANDARD_MATERIAL_INPUTS(inputs, MaterialSrg::m_layer3_, o_layer3_, blendWeights.b) + lightingInputLayer3 = ProcessStandardMaterialInputs(inputs); + } + else + { + lightingInputLayer3.InitializeToZero(); + } + + // ------- Combine all layers --------- + + Surface surface; + surface.position = IN.m_worldPosition; + surface.transmission.InitializeToZero(); + + // ------- Combine Normals --------- + + float3 normalTS = lightingInputLayer1.m_normalTS; + if(o_layer2_enabled) + { + normalTS = ReorientTangentSpaceNormal(normalTS, lightingInputLayer2.m_normalTS); + } + if(o_layer3_enabled) + { + normalTS = ReorientTangentSpaceNormal(normalTS, lightingInputLayer3.m_normalTS); + } // [GFX TODO][ATOM-14591]: This will only work if the normal maps all use the same UV stream. We would need to add support for having them in different UV streams. surface.normal = normalize(TangentSpaceToWorld(normalTS, IN.m_normal, tangents[MaterialSrg::m_parallaxUvIndex], bitangents[MaterialSrg::m_parallaxUvIndex])); - - // ------- Base Color ------- - - float2 layer1_baseColorUv = uvLayer1[MaterialSrg::m_layer1_m_baseColorMapUvIndex]; - float2 layer2_baseColorUv = uvLayer2[MaterialSrg::m_layer2_m_baseColorMapUvIndex]; - float2 layer3_baseColorUv = uvLayer3[MaterialSrg::m_layer3_m_baseColorMapUvIndex]; + + // ------- Combine Albedo, roughness, specular, roughness --------- - float3 layer1_sampledColor = GetBaseColorInput(MaterialSrg::m_layer1_m_baseColorMap, MaterialSrg::m_sampler, layer1_baseColorUv, MaterialSrg::m_layer1_m_baseColor.rgb, o_layer1_o_baseColor_useTexture); - float3 layer2_sampledColor = GetBaseColorInput(MaterialSrg::m_layer2_m_baseColorMap, MaterialSrg::m_sampler, layer2_baseColorUv, MaterialSrg::m_layer2_m_baseColor.rgb, o_layer2_o_baseColor_useTexture); - float3 layer3_sampledColor = GetBaseColorInput(MaterialSrg::m_layer3_m_baseColorMap, MaterialSrg::m_sampler, layer3_baseColorUv, MaterialSrg::m_layer3_m_baseColor.rgb, o_layer3_o_baseColor_useTexture); - float3 layer1_baseColor = BlendBaseColor(layer1_sampledColor, MaterialSrg::m_layer1_m_baseColor.rgb, MaterialSrg::m_layer1_m_baseColorFactor, o_layer1_o_baseColorTextureBlendMode, o_layer1_o_baseColor_useTexture); - float3 layer2_baseColor = BlendBaseColor(layer2_sampledColor, MaterialSrg::m_layer2_m_baseColor.rgb, MaterialSrg::m_layer2_m_baseColorFactor, o_layer2_o_baseColorTextureBlendMode, o_layer2_o_baseColor_useTexture); - float3 layer3_baseColor = BlendBaseColor(layer3_sampledColor, MaterialSrg::m_layer3_m_baseColor.rgb, MaterialSrg::m_layer3_m_baseColorFactor, o_layer3_o_baseColorTextureBlendMode, o_layer3_o_baseColor_useTexture); - float3 baseColor = BlendLayers(layer1_baseColor, layer2_baseColor, layer3_baseColor, blendMaskValues); + float3 baseColor = BlendLayers(lightingInputLayer1.m_baseColor, lightingInputLayer2.m_baseColor, lightingInputLayer3.m_baseColor, blendWeights); + float3 specularF0Factor = BlendLayers(lightingInputLayer1.m_specularF0Factor, lightingInputLayer2.m_specularF0Factor, lightingInputLayer3.m_specularF0Factor, blendWeights); + float3 metallic = BlendLayers(lightingInputLayer1.m_metallic, lightingInputLayer2.m_metallic, lightingInputLayer3.m_metallic, blendWeights); if(o_parallax_highlightClipping && displacementIsClipped) { ApplyParallaxClippingHighlight(baseColor); } - // ------- Metallic ------- - - float metallic = 0; - if(!o_enableSubsurfaceScattering) // If subsurface scattering is enabled skip texture lookup for metallic, as this quantity won't be used anyway - { - float layer1_metallic = GetMetallicInput(MaterialSrg::m_layer1_m_metallicMap, MaterialSrg::m_sampler, uvLayer1[MaterialSrg::m_layer1_m_metallicMapUvIndex], MaterialSrg::m_layer1_m_metallicFactor, o_layer1_o_metallic_useTexture); - float layer2_metallic = GetMetallicInput(MaterialSrg::m_layer2_m_metallicMap, MaterialSrg::m_sampler, uvLayer2[MaterialSrg::m_layer2_m_metallicMapUvIndex], MaterialSrg::m_layer2_m_metallicFactor, o_layer2_o_metallic_useTexture); - float layer3_metallic = GetMetallicInput(MaterialSrg::m_layer3_m_metallicMap, MaterialSrg::m_sampler, uvLayer3[MaterialSrg::m_layer3_m_metallicMapUvIndex], MaterialSrg::m_layer3_m_metallicFactor, o_layer3_o_metallic_useTexture); - metallic = BlendLayers(layer1_metallic, layer2_metallic, layer3_metallic, blendMaskValues); - } - - // ------- Specular ------- - - float layer1_specularF0Factor = GetSpecularInput(MaterialSrg::m_layer1_m_specularF0Map, MaterialSrg::m_sampler, uvLayer1[MaterialSrg::m_layer1_m_specularF0MapUvIndex], MaterialSrg::m_layer1_m_specularF0Factor, o_layer1_o_specularF0_useTexture); - float layer2_specularF0Factor = GetSpecularInput(MaterialSrg::m_layer2_m_specularF0Map, MaterialSrg::m_sampler, uvLayer2[MaterialSrg::m_layer2_m_specularF0MapUvIndex], MaterialSrg::m_layer2_m_specularF0Factor, o_layer2_o_specularF0_useTexture); - float layer3_specularF0Factor = GetSpecularInput(MaterialSrg::m_layer3_m_specularF0Map, MaterialSrg::m_sampler, uvLayer3[MaterialSrg::m_layer3_m_specularF0MapUvIndex], MaterialSrg::m_layer3_m_specularF0Factor, o_layer3_o_specularF0_useTexture); - float specularF0Factor = BlendLayers(layer1_specularF0Factor, layer2_specularF0Factor, layer3_specularF0Factor, blendMaskValues); - surface.SetAlbedoAndSpecularF0(baseColor, specularF0Factor, metallic); - // ------- Roughness ------- - - float layer1_roughness = GetRoughnessInput(MaterialSrg::m_layer1_m_roughnessMap, MaterialSrg::m_sampler, uvLayer1[MaterialSrg::m_layer1_m_roughnessMapUvIndex], MaterialSrg::m_layer1_m_roughnessFactor, MaterialSrg::m_layer1_m_roughnessLowerBound, MaterialSrg::m_layer1_m_roughnessUpperBound, o_layer1_o_roughness_useTexture); - float layer2_roughness = GetRoughnessInput(MaterialSrg::m_layer2_m_roughnessMap, MaterialSrg::m_sampler, uvLayer2[MaterialSrg::m_layer2_m_roughnessMapUvIndex], MaterialSrg::m_layer2_m_roughnessFactor, MaterialSrg::m_layer2_m_roughnessLowerBound, MaterialSrg::m_layer2_m_roughnessUpperBound, o_layer2_o_roughness_useTexture); - float layer3_roughness = GetRoughnessInput(MaterialSrg::m_layer3_m_roughnessMap, MaterialSrg::m_sampler, uvLayer3[MaterialSrg::m_layer3_m_roughnessMapUvIndex], MaterialSrg::m_layer3_m_roughnessFactor, MaterialSrg::m_layer3_m_roughnessLowerBound, MaterialSrg::m_layer3_m_roughnessUpperBound, o_layer3_o_roughness_useTexture); - surface.roughnessLinear = BlendLayers(layer1_roughness, layer2_roughness, layer3_roughness, blendMaskValues); - + surface.roughnessLinear = BlendLayers(lightingInputLayer1.m_roughness, lightingInputLayer2.m_roughness, lightingInputLayer3.m_roughness, blendWeights); surface.CalculateRoughnessA(); - - // ------- Subsurface ------- - - float2 subsurfaceUv = IN.m_uv[MaterialSrg::m_subsurfaceScatteringInfluenceMapUvIndex]; - float surfaceScatteringFactor = GetSubsurfaceInput(MaterialSrg::m_subsurfaceScatteringInfluenceMap, MaterialSrg::m_sampler, subsurfaceUv, MaterialSrg::m_subsurfaceScatteringFactor); - - // ------- Transmission ------- - - float2 transmissionUv = IN.m_uv[MaterialSrg::m_transmissionThicknessMapUvIndex]; - float4 transmissionTintThickness = GeTransmissionInput(MaterialSrg::m_transmissionThicknessMap, MaterialSrg::m_sampler, transmissionUv, MaterialSrg::m_transmissionTintThickness); - surface.transmission.tint = transmissionTintThickness.rgb; - surface.transmission.thickness = transmissionTintThickness.w; - surface.transmission.transmissionParams = MaterialSrg::m_transmissionParams; - // ------- Lighting Data ------- - + // ------- Init and Combine Lighting Data ------- + LightingData lightingData; // Light iterator @@ -308,88 +458,22 @@ PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace, out float // Directional light shadow coordinates lightingData.shadowCoords = IN.m_shadowCoords; - - // ------- Emissive ------- - float3 layer1_emissive = GetEmissiveInput(MaterialSrg::m_layer1_m_emissiveMap, MaterialSrg::m_sampler, uvLayer1[MaterialSrg::m_layer1_m_emissiveMapUvIndex], MaterialSrg::m_layer1_m_emissiveIntensity, MaterialSrg::m_layer1_m_emissiveColor.rgb, o_layer1_o_emissiveEnabled, o_layer1_o_emissive_useTexture); - float3 layer2_emissive = GetEmissiveInput(MaterialSrg::m_layer2_m_emissiveMap, MaterialSrg::m_sampler, uvLayer2[MaterialSrg::m_layer2_m_emissiveMapUvIndex], MaterialSrg::m_layer2_m_emissiveIntensity, MaterialSrg::m_layer2_m_emissiveColor.rgb, o_layer2_o_emissiveEnabled, o_layer2_o_emissive_useTexture); - float3 layer3_emissive = GetEmissiveInput(MaterialSrg::m_layer3_m_emissiveMap, MaterialSrg::m_sampler, uvLayer3[MaterialSrg::m_layer3_m_emissiveMapUvIndex], MaterialSrg::m_layer3_m_emissiveIntensity, MaterialSrg::m_layer3_m_emissiveColor.rgb, o_layer3_o_emissiveEnabled, o_layer3_o_emissive_useTexture); - lightingData.emissiveLighting = BlendLayers(layer1_emissive, layer2_emissive, layer3_emissive, blendMaskValues); + lightingData.emissiveLighting = BlendLayers(lightingInputLayer1.m_emissiveLighting, lightingInputLayer2.m_emissiveLighting, lightingInputLayer3.m_emissiveLighting, blendWeights); + lightingData.specularOcclusion = BlendLayers(lightingInputLayer1.m_specularOcclusion, lightingInputLayer2.m_specularOcclusion, lightingInputLayer3.m_specularOcclusion, blendWeights); + lightingData.diffuseAmbientOcclusion = BlendLayers(lightingInputLayer1.m_diffuseAmbientOcclusion, lightingInputLayer2.m_diffuseAmbientOcclusion, lightingInputLayer3.m_diffuseAmbientOcclusion, blendWeights); - // ------- Occlusion ------- - - float layer1_diffuseAmbientOcclusion = GetOcclusionInput(MaterialSrg::m_layer1_m_diffuseOcclusionMap, MaterialSrg::m_sampler, uvLayer1[MaterialSrg::m_layer1_m_diffuseOcclusionMapUvIndex], MaterialSrg::m_layer1_m_diffuseOcclusionFactor, o_layer1_o_diffuseOcclusion_useTexture); - float layer2_diffuseAmbientOcclusion = GetOcclusionInput(MaterialSrg::m_layer2_m_diffuseOcclusionMap, MaterialSrg::m_sampler, uvLayer2[MaterialSrg::m_layer2_m_diffuseOcclusionMapUvIndex], MaterialSrg::m_layer2_m_diffuseOcclusionFactor, o_layer2_o_diffuseOcclusion_useTexture); - float layer3_diffuseAmbientOcclusion = GetOcclusionInput(MaterialSrg::m_layer3_m_diffuseOcclusionMap, MaterialSrg::m_sampler, uvLayer3[MaterialSrg::m_layer3_m_diffuseOcclusionMapUvIndex], MaterialSrg::m_layer3_m_diffuseOcclusionFactor, o_layer3_o_diffuseOcclusion_useTexture); - lightingData.diffuseAmbientOcclusion = BlendLayers(layer1_diffuseAmbientOcclusion, layer2_diffuseAmbientOcclusion, layer3_diffuseAmbientOcclusion, blendMaskValues); + lightingData.CalculateMultiscatterCompensation(surface.specularF0, o_specularF0_enableMultiScatterCompensation); - float layer1_specularOcclusion = GetOcclusionInput(MaterialSrg::m_layer1_m_specularOcclusionMap, MaterialSrg::m_sampler, uvLayer1[MaterialSrg::m_layer1_m_specularOcclusionMapUvIndex], MaterialSrg::m_layer1_m_specularOcclusionFactor, o_layer1_o_specularOcclusion_useTexture); - float layer2_specularOcclusion = GetOcclusionInput(MaterialSrg::m_layer2_m_specularOcclusionMap, MaterialSrg::m_sampler, uvLayer2[MaterialSrg::m_layer2_m_specularOcclusionMapUvIndex], MaterialSrg::m_layer2_m_specularOcclusionFactor, o_layer2_o_specularOcclusion_useTexture); - float layer3_specularOcclusion = GetOcclusionInput(MaterialSrg::m_layer3_m_specularOcclusionMap, MaterialSrg::m_sampler, uvLayer3[MaterialSrg::m_layer3_m_specularOcclusionMapUvIndex], MaterialSrg::m_layer3_m_specularOcclusionFactor, o_layer3_o_specularOcclusion_useTexture); - lightingData.specularOcclusion = BlendLayers(layer1_specularOcclusion, layer2_specularOcclusion, layer3_specularOcclusion, blendMaskValues); - - // ------- Clearcoat ------- + // ------- Combine Clearcoat ------- if(o_clearCoat_feature_enabled) { - // --- Layer 1 --- - - float layer1_clearCoatFactor = 0.0f; - float layer1_clearCoatRoughness = 0.0f; - float3 layer1_clearCoatNormal = float3(0.0, 0.0, 0.0); - if(o_layer1_o_clearCoat_enabled) - { - float3x3 layer1_uvMatrix = MaterialSrg::m_layer1_m_clearCoatNormalMapUvIndex == 0 ? MaterialSrg::m_layer1_m_uvMatrix : CreateIdentity3x3(); - - GetClearCoatInputs(MaterialSrg::m_layer1_m_clearCoatInfluenceMap, uvLayer1[MaterialSrg::m_layer1_m_clearCoatInfluenceMapUvIndex], MaterialSrg::m_layer1_m_clearCoatFactor, o_layer1_o_clearCoat_factor_useTexture, - MaterialSrg::m_layer1_m_clearCoatRoughnessMap, uvLayer1[MaterialSrg::m_layer1_m_clearCoatRoughnessMapUvIndex], MaterialSrg::m_layer1_m_clearCoatRoughness, o_layer1_o_clearCoat_roughness_useTexture, - MaterialSrg::m_layer1_m_clearCoatNormalMap, uvLayer1[MaterialSrg::m_layer1_m_clearCoatNormalMapUvIndex], IN.m_normal, o_layer1_o_clearCoat_normal_useTexture, MaterialSrg::m_layer1_m_clearCoatNormalStrength, - layer1_uvMatrix, tangents[MaterialSrg::m_layer1_m_clearCoatNormalMapUvIndex], bitangents[MaterialSrg::m_layer1_m_clearCoatNormalMapUvIndex], - MaterialSrg::m_sampler, isFrontFace, - layer1_clearCoatFactor, layer1_clearCoatRoughness, layer1_clearCoatNormal); - } - - // --- Layer 2 --- - - float layer2_clearCoatFactor = 0.0f; - float layer2_clearCoatRoughness = 0.0f; - float3 layer2_clearCoatNormal = float3(0.0, 0.0, 0.0); - if(o_layer2_o_clearCoat_enabled) - { - float3x3 layer2_uvMatrix = MaterialSrg::m_layer2_m_clearCoatNormalMapUvIndex == 0 ? MaterialSrg::m_layer2_m_uvMatrix : CreateIdentity3x3(); - - GetClearCoatInputs(MaterialSrg::m_layer2_m_clearCoatInfluenceMap, uvLayer2[MaterialSrg::m_layer2_m_clearCoatInfluenceMapUvIndex], MaterialSrg::m_layer2_m_clearCoatFactor, o_layer2_o_clearCoat_factor_useTexture, - MaterialSrg::m_layer2_m_clearCoatRoughnessMap, uvLayer2[MaterialSrg::m_layer2_m_clearCoatRoughnessMapUvIndex], MaterialSrg::m_layer2_m_clearCoatRoughness, o_layer2_o_clearCoat_roughness_useTexture, - MaterialSrg::m_layer2_m_clearCoatNormalMap, uvLayer2[MaterialSrg::m_layer2_m_clearCoatNormalMapUvIndex], IN.m_normal, o_layer2_o_clearCoat_normal_useTexture, MaterialSrg::m_layer2_m_clearCoatNormalStrength, - layer2_uvMatrix, tangents[MaterialSrg::m_layer2_m_clearCoatNormalMapUvIndex], bitangents[MaterialSrg::m_layer2_m_clearCoatNormalMapUvIndex], - MaterialSrg::m_sampler, isFrontFace, - layer2_clearCoatFactor, layer2_clearCoatRoughness, layer2_clearCoatNormal); - } - - // --- Layer 3 --- - - float layer3_clearCoatFactor = 0.0f; - float layer3_clearCoatRoughness = 0.0f; - float3 layer3_clearCoatNormal = float3(0.0, 0.0, 0.0); - if(o_layer3_o_clearCoat_enabled) - { - float3x3 layer3_uvMatrix = MaterialSrg::m_layer3_m_clearCoatNormalMapUvIndex == 0 ? MaterialSrg::m_layer3_m_uvMatrix : CreateIdentity3x3(); - - GetClearCoatInputs(MaterialSrg::m_layer3_m_clearCoatInfluenceMap, uvLayer3[MaterialSrg::m_layer3_m_clearCoatInfluenceMapUvIndex], MaterialSrg::m_layer3_m_clearCoatFactor, o_layer3_o_clearCoat_factor_useTexture, - MaterialSrg::m_layer3_m_clearCoatRoughnessMap, uvLayer3[MaterialSrg::m_layer3_m_clearCoatRoughnessMapUvIndex], MaterialSrg::m_layer3_m_clearCoatRoughness, o_layer3_o_clearCoat_roughness_useTexture, - MaterialSrg::m_layer3_m_clearCoatNormalMap, uvLayer3[MaterialSrg::m_layer3_m_clearCoatNormalMapUvIndex], IN.m_normal, o_layer3_o_clearCoat_normal_useTexture, MaterialSrg::m_layer3_m_clearCoatNormalStrength, - layer3_uvMatrix, tangents[MaterialSrg::m_layer3_m_clearCoatNormalMapUvIndex], bitangents[MaterialSrg::m_layer3_m_clearCoatNormalMapUvIndex], - MaterialSrg::m_sampler, isFrontFace, - layer3_clearCoatFactor, layer3_clearCoatRoughness, layer3_clearCoatNormal); - } - - // --- Blend Layers --- - - surface.clearCoat.factor = BlendLayers(layer1_clearCoatFactor, layer2_clearCoatFactor, layer3_clearCoatFactor, blendMaskValues); - surface.clearCoat.roughness = BlendLayers(layer1_clearCoatRoughness, layer2_clearCoatRoughness, layer3_clearCoatRoughness, blendMaskValues); + surface.clearCoat.factor = BlendLayers(lightingInputLayer1.m_clearCoat.factor, lightingInputLayer2.m_clearCoat.factor, lightingInputLayer3.m_clearCoat.factor, blendWeights); + surface.clearCoat.roughness = BlendLayers(lightingInputLayer1.m_clearCoat.roughness, lightingInputLayer2.m_clearCoat.roughness, lightingInputLayer3.m_clearCoat.roughness, blendWeights); // [GFX TODO][ATOM-14592] This is not the right way to blend the normals. We need to use ReorientTangentSpaceNormal(), and that requires GetClearCoatInputs() to return the normal in TS instead of WS. - surface.clearCoat.normal = BlendLayers(layer1_clearCoatNormal, layer2_clearCoatNormal, layer3_clearCoatNormal, blendMaskValues); + surface.clearCoat.normal = BlendLayers(lightingInputLayer1.m_clearCoat.normal, lightingInputLayer2.m_clearCoat.normal, lightingInputLayer3.m_clearCoat.normal, blendWeights); surface.clearCoat.normal = normalize(surface.clearCoat.normal); // manipulate base layer f0 if clear coat is enabled @@ -409,11 +493,7 @@ PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace, out float // Clear coat layer has fixed IOR = 1.5 and transparent => F0 = (1.5 - 1)^2 / (1.5 + 1)^2 = 0.04 lightingData.diffuseResponse *= 1.0 - (FresnelSchlickWithRoughness(lightingData.NdotV, float3(0.04, 0.04, 0.04), surface.clearCoat.roughness) * surface.clearCoat.factor); } - - // ------- Multiscatter ------- - - lightingData.CalculateMultiscatterCompensation(surface.specularF0, o_specularF0_enableMultiScatterCompensation); - + // ------- Lighting Calculation ------- // Apply Decals @@ -426,17 +506,14 @@ PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace, out float ApplyIBL(surface, lightingData); // Finalize Lighting - lightingData.FinalizeLighting(surface.transmission.tint); + lightingData.FinalizeLighting(0); const float alpha = 1.0; PbrLightingOutput lightingOutput = GetPbrLightingOutput(surface, lightingData, alpha); - // Pack factor and quality, drawback: because of precision limit of float16 cannot represent exact 1, maximum representable value is 0.9961 - uint factorAndQuality = dot(round(float2(saturate(surfaceScatteringFactor), MaterialSrg::m_subsurfaceScatteringQuality) * 255), float2(256, 1)); - lightingOutput.m_diffuseColor.w = factorAndQuality * (o_enableSubsurfaceScattering ? 1.0 : -1.0); - + lightingOutput.m_diffuseColor.w = -1; // Disable subsurface scattering return lightingOutput; } diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_LayerEnable.lua b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_LayerEnable.lua new file mode 100644 index 0000000000..f60aac6149 --- /dev/null +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_LayerEnable.lua @@ -0,0 +1,49 @@ +-------------------------------------------------------------------------------------- +-- +-- 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. +-- +-- +---------------------------------------------------------------------------------------------------- + +-- This functor hides the properties for disabled material layers. + +function GetMaterialPropertyDependencies() + return { + "blend.enableLayer2", + "blend.enableLayer3" + } +end + +function SetLayerVisibility(context, layerNamePrefix, isVisible) + + local visibility = MaterialPropertyGroupVisibility_Enabled + if(not isVisible) then + visibility = MaterialPropertyGroupVisibility_Hidden + end + + context:SetMaterialPropertyGroupVisibility(layerNamePrefix .. "baseColor", visibility) + context:SetMaterialPropertyGroupVisibility(layerNamePrefix .. "metallic", visibility) + context:SetMaterialPropertyGroupVisibility(layerNamePrefix .. "roughness", visibility) + context:SetMaterialPropertyGroupVisibility(layerNamePrefix .. "specularF0", visibility) + context:SetMaterialPropertyGroupVisibility(layerNamePrefix .. "normal", visibility) + context:SetMaterialPropertyGroupVisibility(layerNamePrefix .. "clearCoat", visibility) + context:SetMaterialPropertyGroupVisibility(layerNamePrefix .. "occlusion", visibility) + context:SetMaterialPropertyGroupVisibility(layerNamePrefix .. "emissive", visibility) + context:SetMaterialPropertyGroupVisibility(layerNamePrefix .. "parallax", visibility) + context:SetMaterialPropertyGroupVisibility(layerNamePrefix .. "uv", visibility) +end + +function ProcessEditor(context) + local enableLayer2 = context:GetMaterialPropertyValue_bool("blend.enableLayer2") + local enableLayer3 = context:GetMaterialPropertyValue_bool("blend.enableLayer3") + + SetLayerVisibility(context, "layer2_", context:GetMaterialPropertyValue_bool("blend.enableLayer2")) + SetLayerVisibility(context, "layer3_", context:GetMaterialPropertyValue_bool("blend.enableLayer3")) +end diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_ParallaxPerLayer.lua b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_ParallaxPerLayer.lua index 119dfed436..bd56292229 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_ParallaxPerLayer.lua +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_ParallaxPerLayer.lua @@ -24,7 +24,7 @@ end function Process(context) local enable = context:GetMaterialPropertyValue_bool("parallax.enable") - local textureMap = context:GetMaterialPropertyValue_image("parallax.textureMap") + local textureMap = context:GetMaterialPropertyValue_Image("parallax.textureMap") context:SetShaderOptionValue_bool("o_useDepthMap", enable and textureMap ~= nil) end @@ -37,7 +37,7 @@ function ProcessEditor(context) context:SetMaterialPropertyVisibility("parallax.textureMap", MaterialPropertyVisibility_Hidden) end - local textureMap = context:GetMaterialPropertyValue_image("parallax.textureMap") + local textureMap = context:GetMaterialPropertyValue_Image("parallax.textureMap") local visibility = MaterialPropertyVisibility_Enabled if(not enable or textureMap == nil) then visibility = MaterialPropertyVisibility_Hidden diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_Shadowmap_WithPS.azsl b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_Shadowmap_WithPS.azsl index c76dd15975..02ccd78735 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_Shadowmap_WithPS.azsl +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_Shadowmap_WithPS.azsl @@ -107,7 +107,7 @@ PSDepthOutput MainPS(VertexOutput IN, bool isFrontFace : SV_IsFrontFace) float3 bitangents[UvSetCount] = { IN.m_bitangent.xyz, float3(0, 0, 0) }; PrepareGeneratedTangent(IN.m_normal, IN.m_worldPosition, isFrontFace, IN.m_uv, UvSetCount, tangents, bitangents, 1); - GetDepth_Setup(IN.m_blendMask); + s_blendMaskFromVertexStream = IN.m_blendMask; float depthNDC; diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_ClearCoatState.lua b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_ClearCoatState.lua index 8289291ef4..ffa00d7efd 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_ClearCoatState.lua +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_ClearCoatState.lua @@ -34,7 +34,7 @@ function GetShaderOptionDependencies() end function UpdateUseTextureState(context, clearCoatEnabled, textureMapPropertyName, useTexturePropertyName, shaderOptionName) - local textureMap = context:GetMaterialPropertyValue_image(textureMapPropertyName) + local textureMap = context:GetMaterialPropertyValue_Image(textureMapPropertyName) local useTextureMap = context:GetMaterialPropertyValue_bool(useTexturePropertyName) context:SetShaderOptionValue_bool(shaderOptionName, clearCoatEnabled and useTextureMap and textureMap ~= nil) end @@ -50,7 +50,7 @@ end -- Note this logic matches that of the UseTextureFunctor class. function UpdateTextureDependentPropertyVisibility(context, textureMapPropertyName, useTexturePropertyName, uvPropertyName) - local textureMap = context:GetMaterialPropertyValue_image(textureMapPropertyName) + local textureMap = context:GetMaterialPropertyValue_Image(textureMapPropertyName) local useTexture = context:GetMaterialPropertyValue_bool(useTexturePropertyName) if(textureMap == nil) then diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_EmissiveState.lua b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_EmissiveState.lua index a8ac2e8a4a..7ee5876adb 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_EmissiveState.lua +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_EmissiveState.lua @@ -22,7 +22,7 @@ end function Process(context) local enable = context:GetMaterialPropertyValue_bool("emissive.enable") - local textureMap = context:GetMaterialPropertyValue_image("emissive.textureMap") + local textureMap = context:GetMaterialPropertyValue_Image("emissive.textureMap") local useTextureMap = context:GetMaterialPropertyValue_bool("emissive.useTexture") context:SetShaderOptionValue_bool("o_emissiveEnabled", enable) @@ -47,7 +47,7 @@ function ProcessEditor(context) context:SetMaterialPropertyVisibility("emissive.textureMapUv", mainVisibility) if(enable) then - local textureMap = context:GetMaterialPropertyValue_image("emissive.textureMap") + local textureMap = context:GetMaterialPropertyValue_Image("emissive.textureMap") local useTextureMap = context:GetMaterialPropertyValue_bool("emissive.useTexture") if(textureMap == nil) then diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_HandleOpacityMode.lua b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_HandleOpacityMode.lua index 6cc595d712..541b1ac1ce 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_HandleOpacityMode.lua +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_HandleOpacityMode.lua @@ -90,7 +90,7 @@ function ProcessEditor(context) context:SetMaterialPropertyVisibility("opacity.textureMap", MaterialPropertyVisibility_Hidden) context:SetMaterialPropertyVisibility("opacity.textureMapUv", MaterialPropertyVisibility_Hidden) else - local textureMap = context:GetMaterialPropertyValue_image("opacity.textureMap") + local textureMap = context:GetMaterialPropertyValue_Image("opacity.textureMap") if(nil == textureMap) then context:SetMaterialPropertyVisibility("opacity.textureMapUv", MaterialPropertyVisibility_Disabled) diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_ParallaxState.lua b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_ParallaxState.lua index 0287e1105e..e6689da327 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_ParallaxState.lua +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_ParallaxState.lua @@ -22,7 +22,7 @@ end function Process(context) local enable = context:GetMaterialPropertyValue_bool("parallax.enable") - local textureMap = context:GetMaterialPropertyValue_image("parallax.textureMap") + local textureMap = context:GetMaterialPropertyValue_Image("parallax.textureMap") context:SetShaderOptionValue_bool("o_parallax_feature_enabled", enable) context:SetShaderOptionValue_bool("o_useDepthMap", enable and textureMap ~= nil) end @@ -36,7 +36,7 @@ function ProcessEditor(context) context:SetMaterialPropertyVisibility("parallax.textureMap", MaterialPropertyVisibility_Hidden) end - local textureMap = context:GetMaterialPropertyValue_image("parallax.textureMap") + local textureMap = context:GetMaterialPropertyValue_Image("parallax.textureMap") local visibility = MaterialPropertyVisibility_Enabled if(not enable or textureMap == nil) then visibility = MaterialPropertyVisibility_Hidden diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_Roughness.lua b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_Roughness.lua index 4887bc47e8..222e69cd3d 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_Roughness.lua +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_Roughness.lua @@ -21,13 +21,13 @@ function GetShaderOptionDependencies() end function Process(context) - local textureMap = context:GetMaterialPropertyValue_image("roughness.textureMap") + local textureMap = context:GetMaterialPropertyValue_Image("roughness.textureMap") local useTexture = context:GetMaterialPropertyValue_bool("roughness.useTexture") context:SetShaderOptionValue_bool("o_roughness_useTexture", useTexture and textureMap ~= nil) end function ProcessEditor(context) - local textureMap = context:GetMaterialPropertyValue_image("roughness.textureMap") + local textureMap = context:GetMaterialPropertyValue_Image("roughness.textureMap") local useTexture = context:GetMaterialPropertyValue_bool("roughness.useTexture") if(nil == textureMap) then diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_SubsurfaceState.lua b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_SubsurfaceState.lua index fb07ac89a3..d8a69ba355 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_SubsurfaceState.lua +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_SubsurfaceState.lua @@ -35,7 +35,7 @@ TransmissionMode_ThickObject = 1 TransmissionMode_ThinObject = 2 function UpdateUseTextureState(context, subsurfaceScatteringEnabled, textureMapPropertyName, useTexturePropertyName, shaderOptionName) - local textureMap = context:GetMaterialPropertyValue_image(textureMapPropertyName) + local textureMap = context:GetMaterialPropertyValue_Image(textureMapPropertyName) local useTextureMap = context:GetMaterialPropertyValue_bool(useTexturePropertyName) context:SetShaderOptionValue_bool(shaderOptionName, subsurfaceScatteringEnabled and useTextureMap and textureMap ~= nil) end @@ -53,7 +53,7 @@ function UpdateTextureDependentPropertyVisibility(context, featureEnabled, textu context:SetMaterialPropertyVisibility(useTexturePropertyName, MaterialPropertyVisibility_Hidden) context:SetMaterialPropertyVisibility(uvPropertyName, MaterialPropertyVisibility_Hidden) else - local textureMap = context:GetMaterialPropertyValue_image(textureMapPropertyName) + local textureMap = context:GetMaterialPropertyValue_Image(textureMapPropertyName) local useTextureMap = context:GetMaterialPropertyValue_bool(useTexturePropertyName) if(textureMap == nil) then diff --git a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Surfaces/StandardSurface.azsli b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Surfaces/StandardSurface.azsli index bb63d27df0..1a74a68e96 100644 --- a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Surfaces/StandardSurface.azsli +++ b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Surfaces/StandardSurface.azsli @@ -43,7 +43,6 @@ class Surface }; - // Specular Anti-Aliasing technique from this paper: // http://www.jp.square-enix.com/tech/library/pdf/ImprovedGeometricSpecularAA.pdf void Surface::ApplySpecularAA() diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/DiffuseComposite_nomsaa.azsl b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/DiffuseComposite_nomsaa.azsl index a5d761d815..c96828e23f 100644 --- a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/DiffuseComposite_nomsaa.azsl +++ b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/DiffuseComposite_nomsaa.azsl @@ -91,7 +91,7 @@ float3 SampleProbeIrradiance(uint2 probeIrradianceCoords, float depth, float3 no { for (int x = -extent; x <= extent; ++x) { - float3 downsampledNormal = PassSrg::m_downsampledNormal.Load(int3(probeIrradianceCoords, 0), int2(x, y)).rgb; + float3 downsampledNormal = PassSrg::m_downsampledNormal.Load(int3(probeIrradianceCoords + int2(x, y), 0)).rgb; downsampledNormal = downsampledNormal * 2.0f - 1.0f; float normalDot = dot(downsampledNormal, normal); @@ -100,10 +100,10 @@ float3 SampleProbeIrradiance(uint2 probeIrradianceCoords, float depth, float3 no if (normalDot > NormalMatchTolerance) { // the normals are almost identical, if the depth is within the tolerance we can optimize by just taking this sample - float downsampledDepth = PassSrg::m_downsampledDepth.Load(int3(probeIrradianceCoords, 0), int2(x, y)).r; + float downsampledDepth = PassSrg::m_downsampledDepth.Load(int3(probeIrradianceCoords + int2(x, y), 0)).r; if (abs(depth - downsampledDepth) <= DepthTolerance) { - float3 probeIrradiance = PassSrg::m_downsampledProbeIrradiance.Load(int3(probeIrradianceCoords,0), int2(x, y)).rgb; + float3 probeIrradiance = PassSrg::m_downsampledProbeIrradiance.Load(int3(probeIrradianceCoords + int2(x, y),0)).rgb; probeIrradiance = saturate(probeIrradiance); return probeIrradiance; } @@ -115,7 +115,7 @@ float3 SampleProbeIrradiance(uint2 probeIrradianceCoords, float depth, float3 no } } - float3 probeIrradiance = PassSrg::m_downsampledProbeIrradiance.Load(int3(probeIrradianceCoords, 0), closestOffset).rgb; + float3 probeIrradiance = PassSrg::m_downsampledProbeIrradiance.Load(int3(probeIrradianceCoords + closestOffset, 0)).rgb; probeIrradiance = saturate(probeIrradiance); return probeIrradiance; } diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/DiffuseProbeGridDownsample_nomsaa.azsl b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/DiffuseProbeGridDownsample_nomsaa.azsl index f10fc67da5..d2e73927e0 100644 --- a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/DiffuseProbeGridDownsample_nomsaa.azsl +++ b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/DiffuseProbeGridDownsample_nomsaa.azsl @@ -70,8 +70,8 @@ PSOutput MainPS(VSOutput IN) { for (uint x = 0; x < ImageScale; ++x) { - float depth = PassSrg::m_depth.Load(int3(screenCoords, 0), int2(x, y)).r; - float4 encodedNormal = PassSrg::m_normal.Load(int3(screenCoords, 0), int2(x, y)); + float depth = PassSrg::m_depth.Load(int3(screenCoords + int2(x, y), 0)).r; + float4 encodedNormal = PassSrg::m_normal.Load(int3(screenCoords + int2(x, y), 0)); // take the closest depth sample to ensure we're getting the normal closest to the viewer // (larger depth value due to reverse depth) diff --git a/Gems/Atom/Feature/Common/Assets/Textures/DefaultBlendMask_layers.png b/Gems/Atom/Feature/Common/Assets/Textures/DefaultBlendMask_layers.png index d1606516af..5e60261dd7 100644 --- a/Gems/Atom/Feature/Common/Assets/Textures/DefaultBlendMask_layers.png +++ b/Gems/Atom/Feature/Common/Assets/Textures/DefaultBlendMask_layers.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f74ffab6ee15906158d27cbd2e5556e8fcdfd7820c0d0fd4b403de8a7af81662 -size 5651 +oid sha256:6660fa05dbf1e90298472fb41d99fff80a80de64ee88d17af4d0df3bdafb1ff6 +size 52877 diff --git a/Gems/Atom/Feature/Common/Code/Source/Material/MaterialConverterSystemComponent.cpp b/Gems/Atom/Feature/Common/Code/Source/Material/MaterialConverterSystemComponent.cpp index dbbf666967..8397dcf9b3 100644 --- a/Gems/Atom/Feature/Common/Code/Source/Material/MaterialConverterSystemComponent.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/Material/MaterialConverterSystemComponent.cpp @@ -77,12 +77,16 @@ namespace AZ } }; + // If PBR material properties aren't in use, fall back to legacy properties. Don't do that if some PBR material properties are set, though. + bool anyPBRInUse = false; + handleTexture("specularF0", SceneAPI::DataTypes::IMaterialData::TextureMapType::Specular); handleTexture("normal", SceneAPI::DataTypes::IMaterialData::TextureMapType::Normal); AZStd::optional useColorMap = materialData.GetUseColorMap(); // If the useColorMap property exists, this is a PBR material and the color should be set to baseColor. if (useColorMap.has_value()) { + anyPBRInUse = true; handleTexture("baseColor", SceneAPI::DataTypes::IMaterialData::TextureMapType::BaseColor); } else @@ -97,17 +101,19 @@ namespace AZ AZStd::optional baseColor = materialData.GetBaseColor(); if (baseColor.has_value()) { + anyPBRInUse = true; sourceData.m_properties["baseColor"]["color"].m_value = toColor(baseColor.value()); } sourceData.m_properties["opacity"]["factor"].m_value = materialData.GetOpacity(); - auto applyOptionalPropertiesFunc = [&sourceData](const auto& propertyGroup, const auto& propertyName, const auto& propertyOptional) + auto applyOptionalPropertiesFunc = [&sourceData, &anyPBRInUse](const auto& propertyGroup, const auto& propertyName, const auto& propertyOptional) { // Only set PBR settings if they were specifically set in the scene's data. // Otherwise, leave them unset so the data driven default properties are used. if (propertyOptional.has_value()) { + anyPBRInUse = true; sourceData.m_properties[propertyGroup][propertyName].m_value = propertyOptional.value(); } }; @@ -127,6 +133,13 @@ namespace AZ handleTexture("ambientOcclusion", SceneAPI::DataTypes::IMaterialData::TextureMapType::AmbientOcclusion); applyOptionalPropertiesFunc("ambientOcclusion", "useTexture", materialData.GetUseAOMap()); + + if (!anyPBRInUse) + { + // If it doesn't have the useColorMap property, then it's a non-PBR material and the baseColor + // texture needs to be set to the diffuse color. + sourceData.m_properties["baseColor"]["color"].m_value = toColor(materialData.GetDiffuseColor()); + } return true; } diff --git a/Gems/Atom/RHI/Code/Include/Atom/RHI.Reflect/ShaderResourceGroupLayout.h b/Gems/Atom/RHI/Code/Include/Atom/RHI.Reflect/ShaderResourceGroupLayout.h index c1d4fb49f0..fd14cadde8 100644 --- a/Gems/Atom/RHI/Code/Include/Atom/RHI.Reflect/ShaderResourceGroupLayout.h +++ b/Gems/Atom/RHI/Code/Include/Atom/RHI.Reflect/ShaderResourceGroupLayout.h @@ -75,6 +75,8 @@ namespace AZ */ bool Finalize(); + void SetName(const Name& name) { m_name = name; } + const Name& GetName() const { return m_name; } /** * Designates this SRG as ShaderVariantKey fallback by providing the generated @@ -272,6 +274,9 @@ namespace AZ AZ_SERIALIZE_FRIEND(); + //! Name of the ShaderResourceGroup as specified in the original *.azsl/*.azsli file. + Name m_name; + AZStd::vector m_staticSamplers; AZStd::vector m_inputsForBuffers; diff --git a/Gems/Atom/RHI/Code/Source/RHI.Reflect/ShaderResourceGroupLayout.cpp b/Gems/Atom/RHI/Code/Source/RHI.Reflect/ShaderResourceGroupLayout.cpp index 210fe7ad45..2c552d7c56 100644 --- a/Gems/Atom/RHI/Code/Source/RHI.Reflect/ShaderResourceGroupLayout.cpp +++ b/Gems/Atom/RHI/Code/Source/RHI.Reflect/ShaderResourceGroupLayout.cpp @@ -22,7 +22,8 @@ namespace AZ if (SerializeContext* serializeContext = azrtti_cast(context)) { serializeContext->Class() - ->Version(6) + ->Version(7) + ->Field("m_name", &ShaderResourceGroupLayout::m_name) ->Field("m_staticSamplers", &ShaderResourceGroupLayout::m_staticSamplers) ->Field("m_inputsForBuffers", &ShaderResourceGroupLayout::m_inputsForBuffers) ->Field("m_inputsForImages", &ShaderResourceGroupLayout::m_inputsForImages) diff --git a/Gems/Atom/RHI/Vulkan/Code/Source/Platform/Windows/Vulkan_Traits_Windows.h b/Gems/Atom/RHI/Vulkan/Code/Source/Platform/Windows/Vulkan_Traits_Windows.h index c7c9902115..c1314aaf66 100644 --- a/Gems/Atom/RHI/Vulkan/Code/Source/Platform/Windows/Vulkan_Traits_Windows.h +++ b/Gems/Atom/RHI/Vulkan/Code/Source/Platform/Windows/Vulkan_Traits_Windows.h @@ -11,7 +11,7 @@ */ #pragma once -#define AZ_TRAIT_ATOM_SHADERBUILDER_DXC "Builders/DirectXShaderCompilerAz/dxc.exe" +#define AZ_TRAIT_ATOM_SHADERBUILDER_DXC "Builders/DirectXShaderCompiler/dxc.exe" #define AZ_TRAIT_ATOM_VULKAN_DISABLE_DUAL_SOURCE_BLENDING 0 #define AZ_TRAIT_ATOM_VULKAN_DLL "vulkan.dll" #define AZ_TRAIT_ATOM_VULKAN_DLL_1 "vulkan-1.dll" diff --git a/Gems/Atom/RHI/Vulkan/Code/Source/RHI/PipelineLayout.cpp b/Gems/Atom/RHI/Vulkan/Code/Source/RHI/PipelineLayout.cpp index 457768b357..9e6c38e7ac 100644 --- a/Gems/Atom/RHI/Vulkan/Code/Source/RHI/PipelineLayout.cpp +++ b/Gems/Atom/RHI/Vulkan/Code/Source/RHI/PipelineLayout.cpp @@ -66,21 +66,21 @@ namespace AZ } } - RHI::ConstPtr PipelineLayout::MergeShaderResourceGroupLayouts(const AZStd::vector& srgLayouts) const + RHI::ConstPtr PipelineLayout::MergeShaderResourceGroupLayouts(const AZStd::vector& srgLayoutList) const { - if (srgLayouts.empty()) + if (srgLayoutList.empty()) { return nullptr; } - if (srgLayouts.size() == 1) + if (srgLayoutList.size() == 1) { - return srgLayouts.front(); + return srgLayoutList.front(); } RHI::Ptr mergedLayout = RHI::ShaderResourceGroupLayout::Create(); - mergedLayout->SetBindingSlot(srgLayouts.front()->GetBindingSlot()); - for (const RHI::ShaderResourceGroupLayout* srgLayout : srgLayouts) + mergedLayout->SetBindingSlot(srgLayoutList.front()->GetBindingSlot()); + for (const RHI::ShaderResourceGroupLayout* srgLayout : srgLayoutList) { const uint32_t bindingSlot = srgLayout->GetBindingSlot(); const auto& srgBindingInfo = m_layoutDescriptor->GetShaderResourceGroupBindingInfo(m_layoutDescriptor->GetShaderResourceGroupIndexFromBindingSlot(bindingSlot)); diff --git a/Gems/Atom/RHI/Vulkan/Code/Source/RHI/PipelineLayout.h b/Gems/Atom/RHI/Vulkan/Code/Source/RHI/PipelineLayout.h index 8af703fefb..4b7b344074 100644 --- a/Gems/Atom/RHI/Vulkan/Code/Source/RHI/PipelineLayout.h +++ b/Gems/Atom/RHI/Vulkan/Code/Source/RHI/PipelineLayout.h @@ -85,7 +85,7 @@ namespace AZ RHI::ResultCode BuildMergedShaderResourceGroupPools(); // Creates a merged SRG layout from a list of SRG layouts. - RHI::ConstPtr MergeShaderResourceGroupLayouts(const AZStd::vector& srgLayouts) const; + RHI::ConstPtr MergeShaderResourceGroupLayouts(const AZStd::vector& srgLayoutList) const; VkPipelineLayout m_nativePipelineLayout = VK_NULL_HANDLE; diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Shader/ShaderSourceData.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Shader/ShaderSourceData.h index 8184f8b5e6..428898a17e 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Shader/ShaderSourceData.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Shader/ShaderSourceData.h @@ -16,7 +16,7 @@ #include #include -#include +#include #include #include @@ -38,12 +38,13 @@ namespace AZ AZ_TYPE_INFO(AZ::RPI::ShaderSourceData, "{B7F00402-872B-4F82-A210-E1A79A366686}"); AZ_CLASS_ALLOCATOR(ShaderSourceData, AZ::SystemAllocator, 0); - static const char* Extension; + static constexpr char Extension[] = "shader"; + static constexpr char Extension2[] = "shader2"; static void Reflect(ReflectContext* context); //! Helper function. Returns true if @rhiName is present in m_disabledRhiBackends - bool IsRhiBackendDisabled(const AZ::Name& rhiName); + bool IsRhiBackendDisabled(const AZ::Name& rhiName) const; struct EntryPoint { @@ -71,12 +72,49 @@ namespace AZ RHI::DepthStencilState m_depthStencilState; RHI::RasterState m_rasterState; RHI::TargetBlendState m_blendState; - - // Hints for building the shader option group layout - RPI::ShaderOptionGroupHints m_shaderOptionGroupHints; //! List of RHI Backends (aka ShaderPlatformInterface) for which this shader should not be compiled. AZStd::vector m_disabledRhiBackends; + + struct SupervariantInfo + { + AZ_TYPE_INFO(AZ::RPI::ShaderSourceData::SupervariantInfo, "{1132CF2A-C8AB-4DD2-AA90-3021D49AB955}"); + + //! Unique name of the supervariant. + //! If left empty, the data refers to the default supervariant. + AZ::Name m_name; + + //! + MCPP Macro definition arguments + AZSLc arguments. + //! These arguments are added after shader_global_build_options.json & m_compiler.m_azslcAdditionalFreeArguments. + //! Arguments that start with "-D" are given to MCPP. + //! Example: "-DMACRO1 -DMACRO2=3". + //! all other arguments are given to AZSLc. + //! Note the arguments are added in addition to the arguments + //! in /Config/shader_global_build_options.json + AZStd::string m_plusArguments; + + //! Opposite to @m_plusArguments. + //! - MCPP Macro definition arguments - AZSLc arguments. + //! Because there are global compilation arguments, this one is useful to remove some of those arguments + //! in order to customize the compilation of a particular supervariant. + AZStd::string m_minusArguments; + + //! Helper function. Parses @m_minusArguments and @m_plusArguments, looks for arguments of type -D[=] and returns + //! a list of to remove. + AZStd::vector GetCombinedListOfMacroDefinitionNamesToRemove() const; + + //! Helper function. Parses @m_plusArguments, looks for arguments of type "-D[=]" and returns + //! a list of "[=]". + AZStd::vector GetMacroDefinitionsToAdd() const; + + //! Helper function. Takes AZSLc arguments from @m_minusArguments and @m_plusArguments, removes them from @initialAzslcCompilerArguments. + //! Takes AZSLc arguments from @m_plusArguments and appends them to @initialAzslcCompilerArguments. + //! Returns a new string with customized arguments. + AZStd::string GetCustomizedArgumentsForAzslc(const AZStd::string& initialAzslcCompilerArguments) const; + }; + + //! Optional list of supervariants. + AZStd::vector m_supervariants; }; } // namespace RPI diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Shader/ShaderVariantAssetCreator2.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Shader/ShaderVariantAssetCreator2.h new file mode 100644 index 0000000000..07cec9a01f --- /dev/null +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Shader/ShaderVariantAssetCreator2.h @@ -0,0 +1,54 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ +#pragma once + +#include +#include +#include + +namespace AZ +{ + namespace RPI + { + //! The "builder" pattern class that creates a ShaderVariantAsset2. + class ShaderVariantAssetCreator2 final + : public AssetCreator + { + public: + //! Begins construction of the shader variant asset. + //! @param assetId The "initial" assetId that the resulting ShaderVariantAsset will get. + //! "initial" was quoted because in the end the asset processor will assign another assetId + //! because on the UUID of the source asset (a *.shadervariantlist file) and the product subid + //! that gets assign when returning the Job Response. + //! It is still useful, because when creating the Root Variant for the ShaderAsset this assetId should + //! match the value that will be assigned by the asset processor because the Root Variant is serialized + //! as a Data::Asset inside the ShaderAsset. + void Begin(const AZ::Data::AssetId& assetId, const ShaderVariantId& shaderVariantId, RPI::ShaderVariantStableId stableId, bool isFullyBaked); + + //! Finalizes and assigns ownership of the asset to result, if successful. + //! Otherwise false is returned and result is left untouched. + bool End(Data::Asset& result); + + ///////////////////////////////////////////////////////////////////// + // Methods for all shader variant types + + //! Set the timestamp value when the ProcessJob() started. + //! This is needed to synchronize between the ShaderAsset and ShaderVariantAsset when hot-reloading shaders. + //! The idea is that this timestamp must be greater or equal than the ShaderAsset. + void SetBuildTimestamp(AZStd::sys_time_t buildTimestamp); + + //! Assigns a shaderStageFunction, which contains the byte code, to the slot dictated by the shader stage. + void SetShaderFunction(RHI::ShaderStage shaderStage, RHI::Ptr shaderStageFunction); + + }; + } // namespace RPI +} // namespace AZ diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Shader/ShaderVariantListSourceData.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Shader/ShaderVariantListSourceData.h index eaef4e093b..c284627cc5 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Shader/ShaderVariantListSourceData.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Shader/ShaderVariantListSourceData.h @@ -30,6 +30,7 @@ namespace AZ AZ_CLASS_ALLOCATOR(ShaderVariantListSourceData, AZ::SystemAllocator, 0); static constexpr const char* Extension = "shadervariantlist"; + static constexpr const char* Extension2 = "shadervariantlist2"; static void Reflect(ReflectContext* context); diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Shader/Shader2.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Shader/Shader2.h new file mode 100644 index 0000000000..eb82c2d31a --- /dev/null +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Shader/Shader2.h @@ -0,0 +1,194 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ +#pragma once + +#include + +#include +#include +#include + +#include +#include + +#include + +#include + +namespace AZ +{ + namespace RHI + { + class PipelineStateCache; + } + + namespace RPI + { + /** + * Shader2 is effectively an 'uber-shader' containing a collection of 'variants'. Variants are + * designed to be 'variations' on the same core shader technique. To enforce this, every variant + * in the shader shares the same pipeline layout (i.e. set of shader resource groups). + * + * A shader owns a library of pipeline states. When a variant is resolved to a pipeline state, its + * lifetime is determined by the lifetime of the Shader2 (unless an explicit reference is taken). If + * an asset reload event occurs, the pipeline state cache is reset. + * + * To use Shader2: + * 1) Construct a ShaderOptionGroup instance using CreateShaderOptionGroup. + * 2) Configure the group by setting values on shader options. + * 3) Find the ShaderVariantStableId using the ShaderVariantId generated from the configured ShaderOptionGroup. + * 4) Acquire the ShaderVariant2 instance using the ShaderVariantStableId. + * 5) Configure a pipeline state descriptor on the variant; make local overrides as necessary (e.g. to configure runtime render state). + * 6) Acquire a RHI::PipelineState instance from the shader using the configured pipeline state descriptor. + * + * Remember that the returned RHI::PipelineState instance lifetime is tied to the Shader2 lifetime. + * If you need guarantee lifetime, it is safe to take a reference on the returned pipeline state. + */ + class Shader2 final + : public Data::InstanceData + , public Data::AssetBus::Handler + , public ShaderVariantFinderNotificationBus2::Handler + { + friend class ShaderSystem; + public: + AZ_INSTANCE_DATA(Shader2, "{232D8BD6-3BD4-4842-ABD2-F380BD5B0863}"); + AZ_CLASS_ALLOCATOR(Shader2, SystemAllocator, 0); + + /// Returns the shader instance associated with the provided asset. + static Data::Instance FindOrCreate(const Data::Asset& shaderAsset, const Name& supervariantName); + + ~Shader2(); + AZ_DISABLE_COPY_MOVE(Shader2); + + /// Constructs a shader option group suitable to generate a shader variant key for this shader. + ShaderOptionGroup CreateShaderOptionGroup() const; + + /// Finds the best matching ShaderVariant2 for the given shaderVariantId, + /// If the variant is loaded and ready it will return the corresponding ShaderVariant2. + /// If the variant is not yet available it will return the root ShaderVariant2. + /// Callers should listen to ShaderReloadNotificationBus to get notified whenever the exact + /// variant is loaded and available or if a variant changes, etc. + /// This function should be your one stop shop to get a ShaderVariant2 from a ShaderVariantId. + /// Alternatively: You can call FindVariantStableId() followed by GetVariant(shaderVariantStableId). + const ShaderVariant2& GetVariant(const ShaderVariantId& shaderVariantId); + + /// Finds the best matching shader variant asset and returns its StableId. + /// In cases where you can't cache the ShaderVariant2, and recurrently you may need + /// the same ShaderVariant2 at different times, then it can be convenient (and more performant) to call + /// this method to cache the ShaderVariantStableId and call GetVariant(ShaderVariantStableId) + /// when needed. + /// If the asset is not immediately found in the file system, it will return the StableId + /// of the root variant. + /// Callers should listen to ShaderReloadNotificationBus to get notified whenever the exact + /// variant is loaded and available or if a variant changes, etc. + ShaderVariantSearchResult FindVariantStableId(const ShaderVariantId& shaderVariantId) const; + + /// Returns the variant associated with the provided StableId. + /// You should call FindVariantStableId() which caches the variant, later + /// when this function is called the variant is fetched from a local map. + /// If the variant is not found, the root variant is returned. + /// "Alternatively: a more convenient approach is to call GetVariant(ShaderVariantId) which does both, the find and the get." + const ShaderVariant2& GetVariant(ShaderVariantStableId shaderVariantStableId); + + /// Convenient function that returns the root variant. + const ShaderVariant2& GetRootVariant(); + + /// Returns the pipeline state type generated by variants of this shader. + RHI::PipelineStateType GetPipelineStateType() const; + + //! Returns the ShaderInputContract which describes which inputs the shader requires + const ShaderInputContract& GetInputContract() const; + + //! Returns the ShaderOutputContract which describes which outputs the shader requires + const ShaderOutputContract& GetOutputContract() const; + + /// Acquires a pipeline state directly from a descriptor. + const RHI::PipelineState* AcquirePipelineState(const RHI::PipelineStateDescriptor& descriptor) const; + + /// Finds and returns the shader resource group asset with the requested name. Returns an empty handle if no matching group was found. + const RHI::Ptr FindShaderResourceGroupLayout(const Name& shaderResourceGroupName) const; + + /// Finds and returns the shader resource group asset associated with the requested binding slot. Returns an empty handle if no matching group was found. + const RHI::Ptr FindShaderResourceGroupLayout(uint32_t bindingSlot) const; + + /// Finds and returns the shader resource group asset designated as a ShaderVariantKey fallback. + const RHI::Ptr FindFallbackShaderResourceGroupLayout() const; + + /// Returns the set of shader resource groups referenced by all variants in the shader asset. + AZStd::array_view> GetShaderResourceGroupLayouts() const; + + /// Returns a reference to the asset used to initialize this shader. + const Data::Asset& GetAsset() const; + + //! Returns the DrawListTag that identifies which Pass and View objects will process this shader. + //! This tag corresponds to the ShaderAsset2 object's DrawListName. + RHI::DrawListTag GetDrawListTag() const; + + private: + Shader2() = default; + + static Data::Instance CreateInternal(ShaderAsset2& shaderAsset); + + bool SelectSupervariant(const Name& supervariantName); + + RHI::ResultCode Init(ShaderAsset2& shaderAsset); + + void Shutdown(); + + ConstPtr LoadPipelineLibrary() const; + void SavePipelineLibrary() const; + + /////////////////////////////////////////////////////////////////// + /// AssetBus overrides + void OnAssetReloaded(Data::Asset asset) override; + /////////////////////////////////////////////////////////////////// + + /////////////////////////////////////////////////////////////////// + /// ShaderVariantFinderNotificationBus overrides + void OnShaderVariantTreeAssetReady(Data::Asset /*shaderVariantTreeAsset*/, bool /*isError*/) override {}; + void OnShaderVariantAssetReady(Data::Asset shaderVariantAsset, bool IsError) override; + /////////////////////////////////////////////////////////////////// + + //! Returns the path to the pipeline library cache file. + AZStd::string GetPipelineLibraryPath() const; + + //! A strong reference to the shader asset. + Data::Asset m_asset; + + //! Selects current supervariant to be used. + //! This value is defined at instantiation. + SupervariantIndex m_supervariantIndex; + + //! The pipeline state type required by this shader. + RHI::PipelineStateType m_pipelineStateType = RHI::PipelineStateType::Draw; + + //! A cached pointer to the pipeline state cache owned by RHISystem. + RHI::PipelineStateCache* m_pipelineStateCache = nullptr; + + //! A handle to the pipeline library in the pipeline state cache. + RHI::PipelineLibraryHandle m_pipelineLibraryHandle; + + //! Used for thread safety for FindVariantStableId() and GetVariant(). + AZStd::shared_mutex m_variantCacheMutex; + + //! The root variant always exist. + ShaderVariant2 m_rootVariant; + + //! Local cache of ShaderVariants (except for the root variant), searchable by StableId. + //! Gets populated when GetVariant() is called. + AZStd::unordered_map m_shaderVariants; + + //! DrawListTag associated with this shader. + RHI::DrawListTag m_drawListTag; + }; + } +} diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Shader/ShaderReloadNotificationBus2.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Shader/ShaderReloadNotificationBus2.h new file mode 100644 index 0000000000..0822336ffa --- /dev/null +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Shader/ShaderReloadNotificationBus2.h @@ -0,0 +1,58 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ +#pragma once + +#include +#include + +//#include +#include + +namespace AZ +{ + namespace RPI + { + class Shader2; + class ShaderAsset2; + + /** + * Connect to this EBus to get notifications whenever a Data::Instance reloads its ShaderAsset. + * The bus address is the AssetId of the ShaderAsset. + */ + class ShaderReloadNotifications2 + : public EBusTraits + { + + public: + ////////////////////////////////////////////////////////////////////////// + // EBusTraits overrides + static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById; + typedef Data::AssetId BusIdType; + ////////////////////////////////////////////////////////////////////////// + + virtual ~ShaderReloadNotifications2() {} + + //! Called when the ShaderAsset reinitializes itself in response to another asset being reloaded. + virtual void OnShaderAssetReinitialized(const Data::Asset& shaderAsset) { AZ_UNUSED(shaderAsset); } + + //! Called when the Shader instance reinitializes itself in response to the ShaderAsset being reloaded. + virtual void OnShaderReinitialized(const Shader2& shader) { AZ_UNUSED(shader); } + + //! Called when a particular shader variant is reinitialized. + virtual void OnShaderVariantReinitialized(const Shader2& shader, const ShaderVariantId& shaderVariantId, ShaderVariantStableId shaderVariantStableId) + { AZ_UNUSED(shader); AZ_UNUSED(shaderVariantId); AZ_UNUSED(shaderVariantStableId) } + }; + + typedef EBus ShaderReloadNotificationBus2; + + } // namespace RPI +} //namespace AZ diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Shader/ShaderResourceGroup.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Shader/ShaderResourceGroup.h index 6d99d287f3..d99b8f51b5 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Shader/ShaderResourceGroup.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Shader/ShaderResourceGroup.h @@ -13,6 +13,8 @@ #include #include +#include +#include #include #include @@ -63,6 +65,10 @@ namespace AZ /// Instantiates a unique shader resource group instance using its paired asset. static Data::Instance Create(const Data::Asset& srgAsset); + /// [GFX TODO] [ATOM-15472] Shader Build Pipeline: Remove Deprecated Files And Functions That Predate The Shader Supervariants + /// This is a temporary hack to enable integration of the new supervariant system. + bool ReplaceSrgLayoutUsingShaderAsset(Data::Asset shaderAsset, const Name& supervariantName, const Name& srgName); + /// Queues a request that the underlying hardware shader resource group be compiled. void Compile(); @@ -278,6 +284,7 @@ namespace AZ ShaderResourceGroup() = default; RHI::ResultCode Init(ShaderResourceGroupAsset& shaderResourceGroupAsset); + static AZ::Data::Instance CreateInternal(ShaderResourceGroupAsset& srgAsset); /// A name to be used in error messages @@ -298,9 +305,12 @@ namespace AZ /// The shader resource group that can be submitted to the renderer RHI::Ptr m_shaderResourceGroup; - /// A reference to the parent template asset used to initialize and manipulate this group. + /// A reference to the SRG asset used to initialize and manipulate this group. AZ::Data::Asset m_asset; + /// A reference to the shader asset used to initialize and manipulate this group. + AZ::Data::Asset m_shaderAsset; + /// A pointer to the layout inside of m_srgAsset const RHI::ShaderResourceGroupLayout* m_layout = nullptr; diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Shader/ShaderVariant.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Shader/ShaderVariant.h index 30c1a6b18d..d189d26b13 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Shader/ShaderVariant.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Shader/ShaderVariant.h @@ -61,9 +61,6 @@ namespace AZ const ShaderAsset& shaderAsset, Data::Asset shaderVariantAsset); - // Returns a shader stage function associated with the provided enum value, or null if no function exists. - const RHI::ShaderStageFunction* GetShaderStageFunction(RHI::ShaderStage shaderStage) const; - // Cached state from the asset to avoid an indirection. RHI::PipelineStateType m_pipelineStateType = RHI::PipelineStateType::Count; diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Shader/ShaderVariant2.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Shader/ShaderVariant2.h new file mode 100644 index 0000000000..524ebf6a3c --- /dev/null +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Shader/ShaderVariant2.h @@ -0,0 +1,71 @@ +/* +* 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 + +namespace AZ +{ + namespace RPI + { + //! Represents the concrete state to configure a PipelineStateDescriptor. ShaderVariant2's match + //! the RHI::PipelineStateType of the parent Shader instance. For shaders on the raster + //! pipeline, the RHI::DrawFilterTag is also provided. + class ShaderVariant2 final + { + friend class Shader2; + public: + ShaderVariant2() = default; + AZ_DEFAULT_COPY_MOVE(ShaderVariant2); + + //! Fills a pipeline state descriptor with settings provided by the ShaderVariant2. (Note that + //! this does not fill the InputStreamLayout or OutputAttachmentLayout as that also requires + //! information from the mesh data and pass system and must be done as a separate step). + void ConfigurePipelineState(RHI::PipelineStateDescriptor& descriptor) const; + + const ShaderVariantId& GetShaderVariantId() const { return m_shaderVariantAsset->GetShaderVariantId(); } + + //! Returns whether the variant is fully baked variant (all options are static branches), or false if the + //! variant uses dynamic branches for some shader options. + //! If the shader variant is not fully baked, the ShaderVariantKeyFallbackValue must be correctly set when drawing. + bool IsFullyBaked() const { return m_shaderVariantAsset->IsFullyBaked(); } + + //! Return the timestamp when this asset was built. + //! This is used to synchronize versions of the ShaderAsset and ShaderVariantAsset, especially during hot-reload. + //! This timestamp must be >= than the ShaderAsset timestamp. + AZStd::sys_time_t GetBuildTimestamp() const { return m_shaderVariantAsset->GetBuildTimestamp(); } + + bool IsRootVariant() const { return m_shaderVariantAsset->IsRootVariant(); } + + ShaderVariantStableId GetStableId() const { return m_shaderVariantAsset->GetStableId(); } + + private: + // Called by Shader. Initializes runtime data from asset data. Returns whether the call succeeded. + bool Init( + const ShaderAsset2& shaderAsset, + Data::Asset shaderVariantAsset, + SupervariantIndex supervariantIndex); + + // Cached state from the asset to avoid an indirection. + RHI::PipelineStateType m_pipelineStateType = RHI::PipelineStateType::Count; + + // State assigned to the pipeline state descriptor. + RHI::ConstPtr m_pipelineLayoutDescriptor; + + Data::Asset m_shaderVariantAsset; + + const RHI::RenderStates* m_renderStates = nullptr; // Cached from ShaderAsset2. + }; + } +} diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Material/LuaMaterialFunctor.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Material/LuaMaterialFunctor.h index 44de79ff22..396ba14810 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Material/LuaMaterialFunctor.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Material/LuaMaterialFunctor.h @@ -329,6 +329,8 @@ namespace AZ bool SetMaterialPropertySoftMaxValue(const char* name, Type value); bool SetMaterialPropertyDescription(const char* name, const char* description); + + bool SetMaterialPropertyGroupVisibility(const char* name, MaterialPropertyGroupVisibility visibility); private: diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Material/MaterialDynamicMetadata.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Material/MaterialDynamicMetadata.h new file mode 100644 index 0000000000..b74e330665 --- /dev/null +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Material/MaterialDynamicMetadata.h @@ -0,0 +1,100 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ +#pragma once + +#include +#include +#include + +namespace AZ +{ + namespace RPI + { + // Normally we wouldn't want editor-related code mixed in with runtime code, but + // since this data can be modified dynamically, keeping it in the runtime makes + // the overall material functor design simpler and more user-friendly. + + + //! Visibility for each material property. + //! If the data field is empty, use default as editable. + enum class MaterialPropertyVisibility : uint32_t + { + Enabled, //!< The property is visible and editable + Disabled, //!< The property is visible but non-editable + Hidden, //!< The property is invisible + + Default = Enabled + }; + + struct MaterialPropertyRange + { + MaterialPropertyRange() = default; + MaterialPropertyRange( + const MaterialPropertyValue& max, + const MaterialPropertyValue& min, + const MaterialPropertyValue& softMax, + const MaterialPropertyValue& softMin + ) + : m_max(max) + , m_min(min) + , m_softMax(softMax) + , m_softMin(softMin) + {} + + MaterialPropertyValue m_max; + MaterialPropertyValue m_min; + MaterialPropertyValue m_softMax; + MaterialPropertyValue m_softMin; + }; + + //! Used by material functors to dynamically control property metadata in tools. + //! For example, show/hide a property based on some other 'enable' flag property. + struct MaterialPropertyDynamicMetadata + { + AZ_TYPE_INFO(MaterialPropertyDynamicMetadata, "{A89F215F-3235-499F-896C-9E63ACC1D657}"); + + AZ::RPI::MaterialPropertyVisibility m_visibility; + AZStd::string m_description; + AZ::RPI::MaterialPropertyRange m_propertyRange; + }; + + //! Visibility for each material property group. + enum class MaterialPropertyGroupVisibility : uint32_t + { + // Note it's helpful to keep these values aligned with MaterialPropertyVisibility in part because in lua it would be easy to accidentally use + // MaterialPropertyVisibility instead of MaterialPropertyGroupVisibility resulting in sneaky bugs. Also, if the enums end up being the same in + // the future, we could just merge them into one. + + Enabled, //!< The property is visible and editable + //Disabled, //!< The property is visible but non-editable (reserved for possible future use, to match MaterialPropertyVisibility) + Hidden=2, //!< The property is invisible + + Default = Enabled + }; + + //! Used by material functors to dynamically control property group metadata in tools. + //! For example, show/hide an entire property group based on some 'enable' flag property. + struct MaterialPropertyGroupDynamicMetadata + { + AZ_TYPE_INFO(MaterialPropertyGroupDynamicMetadata, "{F94009F7-48A3-4CE0-AF64-D5A86890ACD4}"); + + AZ::RPI::MaterialPropertyGroupVisibility m_visibility; + }; + + void ReflectMaterialDynamicMetadata(ReflectContext* context); + + } // namespace RPI + + AZ_TYPE_INFO_SPECIALIZE(RPI::MaterialPropertyVisibility, "{318B43A2-79E3-4502-8FD0-5815209EA123}"); + AZ_TYPE_INFO_SPECIALIZE(RPI::MaterialPropertyGroupVisibility, "{B803958B-DE64-4FBF-AC00-CF781611BE37}"); +} // namespace AZ + diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Material/MaterialFunctor.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Material/MaterialFunctor.h index d17c7634a2..83472ade77 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Material/MaterialFunctor.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Material/MaterialFunctor.h @@ -18,6 +18,7 @@ #include #include #include +#include namespace AZ { @@ -147,6 +148,8 @@ namespace AZ public: const MaterialPropertyDynamicMetadata* GetMaterialPropertyMetadata(const Name& propertyName) const; const MaterialPropertyDynamicMetadata* GetMaterialPropertyMetadata(const MaterialPropertyIndex& index) const; + + const MaterialPropertyGroupDynamicMetadata* GetMaterialPropertyGroupMetadata(const Name& propertyName) const; //! Get the property value. The type must be one of those in MaterialPropertyValue. //! Otherwise, a compile error will be reported. @@ -178,6 +181,8 @@ namespace AZ bool SetMaterialPropertySoftMaxValue(const Name& propertyName, const MaterialPropertyValue& max); bool SetMaterialPropertySoftMaxValue(const MaterialPropertyIndex& index, const MaterialPropertyValue& max); + + bool SetMaterialPropertyGroupVisibility(const Name& propertyGroupName, MaterialPropertyGroupVisibility visibility); // [GFX TODO][ATOM-4168] Replace the workaround for unlink-able RPI.Public classes in MaterialFunctor // const AZStd::vector&, AZStd::unordered_map&, RHI::ConstPtr @@ -185,18 +190,23 @@ namespace AZ EditorContext( const AZStd::vector& propertyValues, RHI::ConstPtr materialPropertiesLayout, - AZStd::unordered_map& metadata, - AZStd::unordered_set& outChangedProperties, + AZStd::unordered_map& propertyMetadata, + AZStd::unordered_map& propertyGroupMetadata, + AZStd::unordered_set& updatedPropertiesOut, + AZStd::unordered_set& updatedPropertyGroupsOut, const MaterialPropertyFlags* materialPropertyDependencies ); private: - AZStd::list_iterator> QueryMaterialMetadata(const Name& propertyName) const; + MaterialPropertyDynamicMetadata* QueryMaterialPropertyMetadata(const Name& propertyName) const; + MaterialPropertyGroupDynamicMetadata* QueryMaterialPropertyGroupMetadata(const Name& propertyGroupName) const; const AZStd::vector& m_materialPropertyValues; RHI::ConstPtr m_materialPropertiesLayout; - AZStd::unordered_map& m_metadata; - AZStd::unordered_set& m_outChangedProperties; + AZStd::unordered_map& m_propertyMetadata; + AZStd::unordered_map& m_propertyGroupMetadata; + AZStd::unordered_set& m_updatedPropertiesOut; + AZStd::unordered_set& m_updatedPropertyGroupsOut; const MaterialPropertyFlags* m_materialPropertyDependencies = nullptr; }; diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Material/MaterialPropertyDescriptor.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Material/MaterialPropertyDescriptor.h index d73ce2334b..fab72d5f96 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Material/MaterialPropertyDescriptor.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Material/MaterialPropertyDescriptor.h @@ -81,52 +81,6 @@ namespace AZ AZStd::string GetMaterialPropertyDataTypeString(AZ::TypeId typeId); - //! Visibility for each material property. - //! If the data field is empty, use default as editable. - enum class MaterialPropertyVisibility : uint32_t - { - Enabled, //< The property is visible and editable - Disabled, //< The property is visible but non-editable - Hidden, //< The property is invisible - - Default = Enabled - }; - - struct MaterialPropertyRange - { - MaterialPropertyRange() = default; - MaterialPropertyRange( - const MaterialPropertyValue& max, - const MaterialPropertyValue& min, - const MaterialPropertyValue& softMax, - const MaterialPropertyValue& softMin - ) - : m_max(max) - , m_min(min) - , m_softMax(softMax) - , m_softMin(softMin) - {} - - MaterialPropertyValue m_max; - MaterialPropertyValue m_min; - MaterialPropertyValue m_softMax; - MaterialPropertyValue m_softMin; - }; - - //! Used by material functors to dynamically control property metadata in tools. - //! For example, show/hide a property based on some other 'enable' flag property. - //! Normally we wouldn't want editor-related code mixed in with runtime code, but - //! since this data can be modified dynamically, keeping it in the runtime makes - //! the overall material functor design simpler and more user-friendly. - struct MaterialPropertyDynamicMetadata - { - AZ_TYPE_INFO(MaterialPropertyDynamicMetadata, "{A89F215F-3235-499F-896C-9E63ACC1D657}"); - - AZ::RPI::MaterialPropertyVisibility m_visibility; - AZStd::string m_description; - AZ::RPI::MaterialPropertyRange m_propertyRange; - }; - //! A material property is any data input to a material, like a bool, float, Vector, Image, Buffer, etc. //! This descriptor defines a single input property, including it's name ID, and how it maps //! to the shader system. @@ -171,7 +125,6 @@ namespace AZ } // namespace RPI AZ_TYPE_INFO_SPECIALIZE(RPI::MaterialPropertyOutputType, "{42A6E5E8-0FE6-4D7B-884A-1F478E4ADD97}"); - AZ_TYPE_INFO_SPECIALIZE(RPI::MaterialPropertyVisibility, "{318B43A2-79E3-4502-8FD0-5815209EA123}"); AZ_TYPE_INFO_SPECIALIZE(RPI::MaterialPropertyDataType, "{3D903D5C-C6AA-452E-A2F8-8948D30833FF}"); } // namespace AZ diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Shader/IShaderVariantFinder2.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Shader/IShaderVariantFinder2.h new file mode 100644 index 0000000000..e09169c9a0 --- /dev/null +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Shader/IShaderVariantFinder2.h @@ -0,0 +1,113 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ +#pragma once + +#include + +#include +#include + +namespace AZ +{ + namespace RPI + { + class ShaderAsset2; + class ShaderVariantTreeAsset; + class ShaderVariantAsset2; + + //! This is the AZ::Interface<> declaration for the singleton responsible + //! for finding the best ShaderVariantAsset a shader can use. + //! This interface is public only to the ShaderAsset class. + //! The expectation is that when in need of shader variants the developer + //! should use AZ::RPI::Shader::GetVariant(). + class IShaderVariantFinder2 + { + public: + AZ_TYPE_INFO(IShaderVariantFinder2, "{4E041C2C-F158-412E-8961-76987EC75692}"); + + static constexpr const char* LogName = "IShaderVariantFinder2"; + + virtual ~IShaderVariantFinder2() = default; + + //! This function should be your one stop shop. + //! It simply queues the request to load a shader variant asset. + //! This function will automatically queue the ShaderVariantTreeAsset for loading if not available. + //! Afther the ShaderVariantTreeAsset is loaded and ready, it is used to find the best matching ShaderVariantStableId + //! from the given ShaderVariantId. If a valid ShaderVariantStableId is found, it will be queued for loading. + //! Eventually the caller will be notified via ShaderVariantFinderNotificationBus::OnShaderVariantAssetReady() + //! The notification will occur on the Main Thread. + virtual bool QueueLoadShaderVariantAssetByVariantId( + Data::Asset shaderAsset, const ShaderVariantId& shaderVariantId, SupervariantIndex supervariantIndex) = 0; + + //! This function does the first half of the work. It simply queues the loading of the ShaderVariantTreeAsset. + //! Given the AssetId of a ShaderAsset it will try to find and load its corresponding ShaderVariantTreeAsset from + //! the asset cache. If found, the asset will be loaded asynchronously and the caller will be notified via + //! ShaderVariantFinderNotificationBus on main thread when the ShaderVariantTreeAsset is fully loaded. + //! It is possible the requested ShaderVariantTreeAsset will never come into existence and in such + //! case the caller will NEVER be notified. + //! Returns true if the request was queued successfully. + virtual bool QueueLoadShaderVariantTreeAsset(const Data::AssetId& shaderAssetId) = 0; + + //! This function does the second half of the work. + //! Given the AssetId of a ShaderVariantTreeAsset and the stable id of a ShaderVariantAsset it will try to + //! find its corresponding ShaderVariantAsset from the asset cache. If found, the asset will be loaded + //! asynchronously and the caller will be notified via ShaderVariantFinderNotificationBus on main thread when the + //! ShaderVariantAsset is fully loaded. + //! Returns true if the request was queued successfully. + virtual bool QueueLoadShaderVariantAsset( + const Data::AssetId& shaderVariantTreeAssetId, ShaderVariantStableId variantStableId, + SupervariantIndex supervariantIndex) = 0; + + //! This is a quick blocking call that will return a valid asset only if it's been fully loaded already, + //! Otherwise it returns an invalid asset and the caller is supposed to call QueueLoadShaderVariantAssetByVariantId(). + virtual Data::Asset GetShaderVariantAssetByVariantId( + Data::Asset shaderAsset, const ShaderVariantId& shaderVariantId, SupervariantIndex supervariantIndex) = 0; + + virtual Data::Asset GetShaderVariantAssetByStableId( + Data::Asset shaderAsset, ShaderVariantStableId shaderVariantStableId, SupervariantIndex supervariantIndex) = 0; + + //! This is a quick blocking call that will return a valid asset only if it's been fully loaded already, + //! Otherwise it returns an invalid asset and the caller is supposed to call QueueLoadShaderVariantTreeAsset(). + virtual Data::Asset GetShaderVariantTreeAsset(const Data::AssetId& shaderAssetId) = 0; + + //! This is a quick blocking call that will return a valid asset only if i's been fully loaded already, + //! Otherwise it returns an invalid asset and the caller is supposed to call QueueLoadShaderVariantAsset(). + virtual Data::Asset GetShaderVariantAsset( + const Data::AssetId& shaderVariantTreeAssetId, ShaderVariantStableId variantStableId, + SupervariantIndex supervariantIndex) = 0; + + //! Clears the cache of loaded ShaderVariantTreeAsset and ShaderVariantAsset objects. + //! This is intended for testing. + virtual void Reset() = 0; + }; + + //! IShaderVariantFinder2 will call on this notification bus on the main thread. + //! Only the following classes are supposed to register to this notification bus: + //! AZ::RPI::ShaderAsset & AZ::RPI::Shader + class ShaderVariantFinderNotification2 + : public EBusTraits + { + public: + ////////////////////////////////////////////////////////////////////////// + // EBusTraits overrides + static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById; + using MutexType = AZStd::recursive_mutex; + typedef Data::AssetId BusIdType; // The AssetId of the shader asset. + ////////////////////////////////////////////////////////////////////////// + + virtual void OnShaderVariantTreeAssetReady(Data::Asset shaderVariantTreeAsset, bool isError) = 0; + virtual void OnShaderVariantAssetReady(Data::Asset shaderVariantAsset, bool isError) = 0; + }; + using ShaderVariantFinderNotificationBus2 = AZ::EBus; + + } // namespace RPI +}// namespace AZ diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Shader/ShaderAsset.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Shader/ShaderAsset.h index 4afc3a1a46..cc31b0735a 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Shader/ShaderAsset.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Shader/ShaderAsset.h @@ -21,6 +21,7 @@ #include #include #include +#include #include #include @@ -54,6 +55,8 @@ namespace AZ //! The default shader variant (i.e. the one without any options set). static const ShaderVariantStableId RootShaderVariantStableId; + // @subProductType is one of ShaderAssetSubId, or (ShaderAssetSubId::GeneratedHlslSource + 1)+ + static uint32_t MakeAssetProductSubId(uint32_t rhiApiUniqueIndex, uint32_t subProductType); ShaderAsset() = default; ~ShaderAsset(); @@ -218,83 +221,21 @@ namespace AZ ////////////////////////////////////////////////////////////////////////// // Deprecated System - enum class ShaderStageType : uint32_t - { - Vertex, - Geometry, - TessellationControl, - TessellationEvaluation, - Fragment, - Compute, - RayTracing - }; - - const char* ToString(ShaderStageType shaderStageType); - - void ReflectShaderStageType(ReflectContext* context); - enum class ShaderAssetSubId : uint32_t { ShaderAsset = 0, - StreamLayout, - GraphicsPipelineState, - OutputMergerState, RootShaderVariantAsset, - //[GFX TODO][LY-82895] (arsentuf) These shader stages are going to get reworked when virtual stages are implemented - AzVertexShader, - AzGeometryShader, - AzTessellationControlShader, - AzTessellationEvaluationShader, - AzFragmentShader, - AzComputeShader, - AzRayTracingShader, - DebugByProduct, PostPreprocessingPureAzsl, // .azslin IaJson, OmJson, SrgJson, OptionsJson, BindingdepJson, - GeneratedSource // This must be last because we use this as a base for adding the RHI::APIType when generating shadersource for multiple RHI APIs. + GeneratedHlslSource // This must be last because we use this as a base for adding the RHI::APIType when generating shadersource for multiple RHI APIs. }; - ShaderAssetSubId ShaderStageToSubId(ShaderStageType stageType); - - class ShaderStageDescriptor final - { - public: - AZ_TYPE_INFO(ShaderStageDescriptor, "{3E7822F7-B952-4379-B0A0-48507681845A}"); - AZ_CLASS_ALLOCATOR(ShaderStageDescriptor, AZ::SystemAllocator, 0); - - static void Reflect(ReflectContext* context); - - ShaderStageType m_stageType; - AZStd::vector m_byteCode; - AZStd::vector m_sourceCode; - AZStd::string m_entryFunctionName; - }; - - //[GFX TODO][LY-82803] (arsentuf) Remove this when we've fleshed out Virtual Shader stages - class ShaderStageAsset final - : public AZ::Data::AssetData - { - public: - AZ_RTTI(ShaderStageAsset, "{975F48B5-1577-41C9-B8F5-A1024E2D01F1}", AZ::Data::AssetData); - AZ_CLASS_ALLOCATOR(ShaderStageAsset, AZ::SystemAllocator, 0); - - static void Reflect(ReflectContext* context); - - ShaderStageAsset() = default; - ShaderStageAsset(const ShaderStageAsset&); - ShaderStageAsset& operator= (const ShaderStageAsset&); - ShaderStageAsset(ShaderStageAsset&& rhs); - - AZStd::shared_ptr m_descriptor; - AZStd::vector m_srgLayouts; - }; ////////////////////////////////////////////////////////////////////////// } // namespace RPI - AZ_TYPE_INFO_SPECIALIZE(RPI::ShaderStageType, "{A6408508-748B-4963-B618-E1E6ECA3629A}"); } // namespace AZ diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Shader/ShaderAsset2.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Shader/ShaderAsset2.h new file mode 100644 index 0000000000..632c35bb82 --- /dev/null +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Shader/ShaderAsset2.h @@ -0,0 +1,339 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ +#pragma once + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include + +namespace AZ +{ + namespace RPI + { + using ShaderResourceGroupLayoutList = AZStd::fixed_vector, RHI::Limits::Pipeline::ShaderResourceGroupCountMax>; + + enum class ShaderAsset2ProductSubId : uint32_t + { + ShaderAsset2 = 0, //!< for .azshader file, One per .shader. + RootShaderVariantAsset, //!< for .azshadervariant, one per supervariant and referenced inside the .azshader. + AzslFlat, //!< .azslin, this file contains the result of preprocessing an azsl file with MCPP, along with prepending the per-RHI azsli header. + IaJson, //!< .ia.json, Input Assembly reflection data. + OmJson, //!< .om.json, Output Merger reflection data. + SrgJson, //!< .srg.json, Shader Resource Group reflection data. + OptionsJson, //!< .options.json, Shader Options reflection data. + BindingdepJson, //!<.bindingdep.json, Binding dependencies. + GeneratedHlslSource, //!<.hlsl code generated with AZSLc. + FirstByProduct, //!< This must be last because we use this as a base for adding all the debug byProducts generated + //!< with dxc, or spirv-cross, etc. + }; + + class ShaderAsset2 final + : public Data::AssetData + , public ShaderVariantFinderNotificationBus2::Handler + , public Data::AssetBus::Handler + { + friend class ShaderAssetCreator2; + friend class ShaderAssetHandler2; + friend class ShaderAssetTester2; + public: + AZ_RTTI(ShaderAsset2, "{823395A3-D570-49F4-99A9-D820CD1DEF98}", Data::AssetData); + static void Reflect(ReflectContext* context); + + static constexpr char DisplayName[] = "Shader"; + static constexpr char Extension[] = "azshader2"; + static constexpr char Group[] = "Shader"; + + //! The default shader variant (i.e. the one without any options set). + static const ShaderVariantStableId RootShaderVariantStableId; + + // @subProductType is one of ShaderAsset2ProductSubId, or ShaderAsset2ProductSubId::FirstByProduct+ + static uint32_t MakeProductAssetSubId(uint32_t rhiApiUniqueIndex, uint32_t supervariantIndex, uint32_t subProductType); + static SupervariantIndex GetSupervariantIndexFromProductAssetSubId(uint32_t assetProducSubId); + static SupervariantIndex GetSupervariantIndexFromAssetId(const Data::AssetId& assetId); + + + ShaderAsset2() = default; + ~ShaderAsset2(); + + AZ_DISABLE_COPY_MOVE(ShaderAsset2); + + + //! Returns the name of the shader. + const Name& GetName() const; + + //! Returns the pipeline state type generated by variants of this shader. + RHI::PipelineStateType GetPipelineStateType() const; + + //! Returns the draw list tag name. + //! To get the corresponding DrawListTag use DrawListTagRegistry's FindTag() or AcquireTag() (see + //! RHISystemInterface::GetDrawListTagRegistry()). The DrawListTag is also available in the Shader that corresponds to this + //! ShaderAsset2. + const Name& GetDrawListName() const; + + //! Return the timestamp when the shader asset was built. + //! This is used to synchronize versions of the ShaderAsset2 and ShaderVariantTreeAsset, especially during hot-reload. + AZStd::sys_time_t GetShaderAssetBuildTimestamp() const; + + //! Returns the shader option group layout. + const ShaderOptionGroupLayout* GetShaderOptionGroupLayout() const; + + SupervariantIndex GetSupervariantIndex(const AZ::Name& supervariantName) const; + + //! This function should be your one stop shop to get a ShaderVariantAsset. + //! Finds and returns the best matching ShaderVariantAsset given a ShaderVariantId. + //! If the ShaderVariantAsset is not fully loaded and ready at the moment, this function + //! will QueueLoad the ShaderVariantTreeAsset and subsequently will QueueLoad the ShaderVariantAsset. + //! The called will be notified via the ShaderVariantFinderNotificationBus when the + //! ShaderVariantAsset is loaded and ready. + //! In the mean time, if the required variant is not available this function + //! returns the Root Variant. + Data::Asset GetVariant( + const ShaderVariantId& shaderVariantId, SupervariantIndex supervariantIndex); + Data::Asset GetVariant(const ShaderVariantId& shaderVariantId) { return GetVariant(shaderVariantId, DefaultSupervariantIndex); } + + //! Finds the best matching shader variant and returns its StableId. + //! This function first loads and caches the ShaderVariantTreeAsset (if not done before). + //! If the ShaderVariantTreeAsset is not found (either the AssetProcessor has not generated it yet, or it simply doesn't exist), then + //! it returns a search result that identifies the root variant. + //! This function is thread safe. + ShaderVariantSearchResult FindVariantStableId(const ShaderVariantId& shaderVariantId); + + //! Returns the variant asset associated with the provided StableId. + //! The user should call FindVariantStableId() first to get a ShaderVariantStableId from a ShaderVariantId, + //! Or better yet, call GetVariant(ShaderVariantId) for maximum convenience. + //! If the requested variant is not found, the root variant will be returned AND the requested variant will be queued for loading. + //! Next time around if the variant has been loaded this function will return it. Alternatively + //! the caller can register with the ShaderVariantFinderNotificationBus to get the asset as soon as is available. + //! This function is thread safe. + Data::Asset GetVariant( + ShaderVariantStableId shaderVariantStableId, SupervariantIndex supervariantIndex) const; + Data::Asset GetVariant(ShaderVariantStableId shaderVariantStableId) const { return GetVariant(shaderVariantStableId, DefaultSupervariantIndex); } + + Data::Asset GetRootVariant(SupervariantIndex supervariantIndex) const; + Data::Asset GetRootVariant() const { return GetRootVariant(DefaultSupervariantIndex); } + + + //! Finds and returns the shader resource group asset with the requested name. Returns an empty handle if no matching group was + //! found. + const RHI::Ptr FindShaderResourceGroupLayout( + const Name& shaderResourceGroupName, SupervariantIndex supervariantIndex) const; + const RHI::Ptr FindShaderResourceGroupLayout(const Name& shaderResourceGroupName) const + { + return FindShaderResourceGroupLayout(shaderResourceGroupName, DefaultSupervariantIndex); + } + + //! Finds and returns the shader resource group layout associated with the requested binding slot. Returns an empty handle if no matching srg was found. + const RHI::Ptr FindShaderResourceGroupLayout( + uint32_t bindingSlot, SupervariantIndex supervariantIndex) const; + const RHI::Ptr FindShaderResourceGroupLayout(uint32_t bindingSlot) const + { + return FindShaderResourceGroupLayout(bindingSlot, DefaultSupervariantIndex); + } + + //! Finds and returns the shader resource group layout designated as a ShaderVariantKey fallback. + const RHI::Ptr FindFallbackShaderResourceGroupLayout( SupervariantIndex supervariantIndex) const; + const RHI::Ptr FindFallbackShaderResourceGroupLayout() const + { + return FindFallbackShaderResourceGroupLayout(DefaultSupervariantIndex); + } + + + //! Returns the set of shader resource group layouts owned by a given supervariant. + AZStd::array_view> GetShaderResourceGroupLayouts( SupervariantIndex supervariantIndex) const; + AZStd::array_view> GetShaderResourceGroupLayouts() const + { + return GetShaderResourceGroupLayouts(DefaultSupervariantIndex); + } + + //! Returns the pipeline layout descriptor shared by all variants in the asset. + const RHI::PipelineLayoutDescriptor* GetPipelineLayoutDescriptor(SupervariantIndex supervariantIndex) const; + const RHI::PipelineLayoutDescriptor* GetPipelineLayoutDescriptor() const + { + return GetPipelineLayoutDescriptor(DefaultSupervariantIndex); + } + + //! Returns the shader resource group asset that has per-draw frequency, which is added to every draw packet. + const RHI::Ptr GetDrawSrgLayout(SupervariantIndex supervariantIndex) const; + const RHI::Ptr GetDrawSrgLayout() const + { + return GetDrawSrgLayout(DefaultSupervariantIndex); + } + + + //! Returns the ShaderInputContract which describes which inputs the shader requires + const ShaderInputContract& GetInputContract(SupervariantIndex supervariantIndex) const; + const ShaderInputContract& GetInputContract() const + { + return GetInputContract(DefaultSupervariantIndex); + } + + + //! Returns the ShaderOuputContract which describes which outputs the shader requires + const ShaderOutputContract& GetOutputContract(SupervariantIndex supervariantIndex) const; + const ShaderOutputContract& GetOutputContract() const + { + return GetOutputContract(DefaultSupervariantIndex); + } + + + //! Returns the render states for the draw pipeline. Only used for draw pipelines. + const RHI::RenderStates& GetRenderStates(SupervariantIndex supervariantIndex) const; + const RHI::RenderStates& GetRenderStates() const + { + return GetRenderStates(DefaultSupervariantIndex); + } + + + //! Returns a list of arguments for the specified attribute, or nullopt_t if the attribute is not found. The list can be empty which is still valid. + AZStd::optional GetAttribute( + const RHI::ShaderStage& shaderStage, const Name& attributeName, SupervariantIndex supervariantIndex) const; + AZStd::optional GetAttribute( + const RHI::ShaderStage& shaderStage, const Name& attributeName) const + { + return GetAttribute(shaderStage, attributeName, DefaultSupervariantIndex); + } + + + private: + /////////////////////////////////////////////////////////////////// + /// AssetBus overrides + void OnAssetReloaded(Data::Asset asset) override; + /////////////////////////////////////////////////////////////////// + + /////////////////////////////////////////////////////////////////// + /// ShaderVariantFinderNotificationBus2 overrides + void OnShaderVariantTreeAssetReady(Data::Asset shaderVariantTreeAsset, bool isError) override; + void OnShaderVariantAssetReady(Data::Asset /*shaderVariantAsset*/, bool /*isError*/) override {}; + /////////////////////////////////////////////////////////////////// + + //! A Supervariant represents a set of static shader compilation parameters. + //! Those parameters can be predefined c-preprocessor macros or specific arguments + //! for AZSLc. + //! For each Supervariant there's a unique Root ShaderVariantAsset, and possibly an N amount + //! of ShaderVariantAssets. The 'N' amount is the same across all Supervariants because all Supervariants + //! share the same ShaderVariantTreeAsset. + struct Supervariant + { + AZ_TYPE_INFO(Supervariant, "{850826EF-B267-4752-92F6-A85E4175CAB8}"); + static void Reflect(AZ::ReflectContext* context); + + AZ::Name m_name; + ShaderResourceGroupLayoutList m_srgLayoutList; + RHI::Ptr m_pipelineLayoutDescriptor; + ShaderInputContract m_inputContract; + ShaderOutputContract m_outputContract; + RHI::RenderStates m_renderStates; + RHI::ShaderStageAttributeMapList m_attributeMaps; + Data::Asset m_rootShaderVariantAsset; + }; + + //! Container of shader data that is specific to an RHI API. + //! A ShaderAsset2 can contain shader data for multiple RHI APIs if + //! the platform support multiple RHIs. + struct ShaderApiDataContainer + { + AZ_TYPE_INFO(ShaderApiDataContainer, "{C636722C-60B9-421C-ACAD-9750BF634A27}"); + static void Reflect(AZ::ReflectContext* context); + + //! RHI API Type for this shader data. + RHI::APIType m_APIType; + // Index 0, will always be the default Supervariant. (see DefaultSupervariantIndex) + AZStd::vector m_supervariants; + }; + + bool FinalizeAfterLoad(); + void SetReady(); + ShaderApiDataContainer& GetCurrentShaderApiData(); + const ShaderApiDataContainer& GetCurrentShaderApiData() const; + + //! Returning pointers instead of references to allow for error checking + //! and not having to assert. + Supervariant* GetSupervariant(SupervariantIndex supervariantIndex); + const Supervariant* GetSupervariant(SupervariantIndex supervariantIndex) const; + + + //! The name is the stem of the source .shader file. + Name m_name; + + //! Dictates the type of pipeline state generated by this asset (Draw / Dispatch / etc.). + //! All shader variants, across all supervariants, in the asset adhere to this type. + RHI::PipelineStateType m_pipelineStateType = RHI::PipelineStateType::Count; + + //! Defines the layout of the shader options in the asset. + Ptr m_shaderOptionGroupLayout; + + //! List with shader data per RHI backend. + AZStd::vector m_perAPIShaderData; + + Name m_drawListName; + + //! Use to synchronize versions of the ShaderAsset2 and ShaderVariantTreeAsset, especially during hot-reload. + AZStd::sys_time_t m_shaderAssetBuildTimestamp = 0; + + + /////////////////////////////////////////////////////////////////// + //! Do Not Serialize! + + static constexpr size_t InvalidAPITypeIndex = std::numeric_limits::max(); + + //! Index that indicates which ShaderDataContainer to use. + //! At runtime, the asset checks the current active RHI Backend + //! and based on the results this variable gets set on asset load. + //! The vector @m_perAPIShaderData will be indexed with this variable. + size_t m_currentAPITypeIndex = InvalidAPITypeIndex; + + //! We can not know the ShaderVariantTreeAsset by the time this asset is being created. + //! This is a value that is discovered at run time. It becomes valid when FindVariantStableId is called at least once. + Data::Asset m_shaderVariantTree; + + //! Used for thread safety for FindVariantStableId(). + mutable AZStd::shared_mutex m_variantTreeMutex; + + bool m_shaderVariantTreeLoadWasRequested = false; + }; + + class ShaderAssetHandler2 final + : public AssetHandler + { + using Base = AssetHandler; + public: + ShaderAssetHandler2() = default; + + private: + Data::AssetHandler::LoadResult LoadAssetData( + const Data::Asset& asset, + AZStd::shared_ptr stream, + const Data::AssetFilterCB& assetLoadFilterCB) override; + Data::AssetHandler::LoadResult PostLoadInit(const Data::Asset& asset); + }; + + ////////////////////////////////////////////////////////////////////////// + } // namespace RPI + +} // namespace AZ diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Shader/ShaderAssetCreator2.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Shader/ShaderAssetCreator2.h new file mode 100644 index 0000000000..263d2e5107 --- /dev/null +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Shader/ShaderAssetCreator2.h @@ -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. +* +*/ +#pragma once + +#include +#include + +#include + +namespace AZ +{ + namespace RPI + { + class ShaderAssetCreator2 + : public AssetCreator + { + public: + //! Begins creation of a shader asset. + void Begin(const Data::AssetId& assetId); + + //! [Optional] Set the timestamp for when the ShaderAsset build process began. + //! This is needed to synchronize between the ShaderAsset and ShaderVariantTreeAsset when hot-reloading shaders. + void SetShaderAssetBuildTimestamp(AZStd::sys_time_t shaderAssetBuildTimestamp); + + //! [Optional] Sets the name of the shader asset from content. + void SetName(const Name& name); + + //! [Optional] Sets the DrawListTag name associated with this shader. + void SetDrawListName(const Name& name); + + //! [Required] Assigns the layout used to construct and parse shader options packed into shader variant keys. + //! Requires that the keys assigned to shader variants were constructed using the same layout. + void SetShaderOptionGroupLayout(const Ptr& shaderOptionGroupLayout); + + //! Begins the shader creation for a specific RHI API. + //! Begin must be called before the BeginAPI function is called. + //! @param type The target RHI API type. + void BeginAPI(RHI::APIType type); + + //! Begins the creation of a Supervariant for the current RHI::APIType. + //! If this is the first supervariant its name must be empty. The first + //! supervariant is always the default, nameless, supervariant. + void BeginSupervariant(const Name& name); + + void SetSrgLayoutList(const ShaderResourceGroupLayoutList& srgLayoutList); + + //! [Required] Assigns the pipeline layout descriptor shared by all variants in the shader. Shader variants + //! embedded in a single shader asset are required to use the same pipeline layout. It is not necessary to call + //! Finalize() on the pipeline layout prior to assignment, but still permitted. + void SetPipelineLayout(RHI::Ptr m_pipelineLayoutDescriptor); + + //! Assigns the contract for inputs required by the shader. + void SetInputContract(const ShaderInputContract& contract); + + //! Assigns the contract for outputs required by the shader. + void SetOutputContract(const ShaderOutputContract& contract); + + //! Assigns the render states for the draw pipeline. Ignored for non-draw pipelines. + void SetRenderStates(const RHI::RenderStates& renderStates); + + //! [Optional] Not all shaders have attributes before functions. Some attributes do not exist for all RHI::APIType either. + void SetShaderStageAttributeMapList(const RHI::ShaderStageAttributeMapList& shaderStageAttributeMapList); + + //! [Required] There's always a root variant for each supervariant. + void SetRootShaderVariantAsset(Data::Asset shaderVariantAsset); + + bool EndSupervariant(); + + bool EndAPI(); + + bool End(Data::Asset& shaderAsset); + + //! Clones an existing ShaderAsset. + void Clone(const Data::AssetId& assetId, + const ShaderAsset2& sourceShaderAsset); + + private: + + // Shader variants will use this draw list when they don't specify one. + Name m_defaultDrawList; + + // The current supervariant is cached here to facilitate asset + // construction. Additionally, prevents BeginSupervariant to be called more than once before calling EndSupervariant. + ShaderAsset2::Supervariant* m_currentSupervariant = nullptr; + + }; + } // namespace RPI +} // namespace AZ diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Shader/ShaderCommonTypes.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Shader/ShaderCommonTypes.h new file mode 100644 index 0000000000..35a12fbf45 --- /dev/null +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Shader/ShaderCommonTypes.h @@ -0,0 +1,56 @@ +/* +* 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 + +namespace AZ +{ + namespace RPI + { + // Common bit positions for ShaderAsset2 and ShaderVariantAsset2 product SubIds. + static constexpr uint32_t RhiIndexBitPosition = 30; + static constexpr uint32_t RhiIndexNumBits = 32 - RhiIndexBitPosition; + static constexpr uint32_t RhiIndexMaxValue = (1 << RhiIndexNumBits) - 1; + + static constexpr uint32_t SupervariantIndexBitPosition = 22; + static constexpr uint32_t SupervariantIndexNumBits = RhiIndexBitPosition - SupervariantIndexBitPosition; + static constexpr uint32_t SupervariantIndexMaxValue = (1 << SupervariantIndexNumBits) - 1; + + //! A wrapper around a supervariant index for type conformity. + //! A supervariant index is required to find shader data from + //! Shader2 and ShaderAsset2 related APIs. + using SupervariantIndex = RHI::Handle; + static const SupervariantIndex DefaultSupervariantIndex(0); + static const SupervariantIndex InvalidSupervariantIndex; + + enum class ShaderStageType : uint32_t + { + Vertex, + Geometry, + TessellationControl, + TessellationEvaluation, + Fragment, + Compute, + RayTracing + }; + + const char* ToString(ShaderStageType shaderStageType); + + void ReflectShaderStageType(ReflectContext* context); + + } // namespace RPI + + AZ_TYPE_INFO_SPECIALIZE(RPI::ShaderStageType, "{A6408508-748B-4963-B618-E1E6ECA3629A}"); + +} // namespace AZ diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Shader/ShaderVariantAsset.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Shader/ShaderVariantAsset.h index 6a1f2af651..22ce16b9f8 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Shader/ShaderVariantAsset.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Shader/ShaderVariantAsset.h @@ -43,8 +43,13 @@ namespace AZ static constexpr const char* DisplayName = "ShaderVariant"; static constexpr const char* Group = "Shader"; + static constexpr uint32_t ShaderVariantAssetSubProductType = 0; //! @rhiApiUniqueIndex comes from RHI::Factory::GetAPIUniqueIndex() - static uint32_t GetAssetSubId(uint32_t rhiApiUniqueIndex, ShaderVariantStableId variantStableId); + //! @subProductType is always 0 for a regular ShaderVariantAsset, for all other debug subProducts created + //! by ShaderVariantAssetBuilder this is 1+. + static uint32_t MakeAssetProductSubId( + uint32_t rhiApiUniqueIndex, ShaderVariantStableId variantStableId, + uint32_t subProductType = ShaderVariantAssetSubProductType); ShaderVariantAsset() = default; ~ShaderVariantAsset() = default; diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Shader/ShaderVariantAsset2.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Shader/ShaderVariantAsset2.h new file mode 100644 index 0000000000..82e868fda8 --- /dev/null +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Shader/ShaderVariantAsset2.h @@ -0,0 +1,104 @@ +/* +* 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 RPI + { + //! A ShaderVariantAsset2 contains the shader byte code for each shader stage (Vertex, Fragment, Tessellation, etc) for a given RHI::APIType (dx12, vulkan, metal, etc). + //! One independent file per RHI::APIType. + class ShaderVariantAsset2 final + : public Data::AssetData + { + friend class ShaderVariantAssetHandler2; + friend class ShaderVariantAssetCreator2; + + public: + AZ_RTTI(ShaderVariantAsset2, "{51BED815-36D8-410E-90F0-1FA9FF765FBA}", Data::AssetData); + + static void Reflect(ReflectContext* context); + + static constexpr const char* Extension = "azshadervariant2"; + static constexpr const char* DisplayName = "ShaderVariant"; + static constexpr const char* Group = "Shader"; + + static constexpr uint32_t ShaderVariantAsset2SubProductType = 1; + //! @rhiApiUniqueIndex comes from RHI::Factory::GetAPIUniqueIndex() + //! @subProductType is always 0 for a regular ShaderVariantAsset2, for all other debug subProducts created + //! by ShaderVariantAssetBuilder2 this is 1+. + static uint32_t MakeAssetProductSubId( + uint32_t rhiApiUniqueIndex, uint32_t supervariantIndex, ShaderVariantStableId variantStableId, + uint32_t subProductType = ShaderVariantAsset2SubProductType); + + ShaderVariantAsset2() = default; + ~ShaderVariantAsset2() = default; + + AZ_DISABLE_COPY_MOVE(ShaderVariantAsset2); + + RPI::ShaderVariantStableId GetStableId() const { return m_stableId; } + + const ShaderVariantId& GetShaderVariantId() const { return m_shaderVariantId; } + + //! Returns the shader stage function associated with the provided stage enum value. + const RHI::ShaderStageFunction* GetShaderStageFunction(RHI::ShaderStage shaderStage) const; + + //! Returns whether the variant is fully baked variant (all options are static branches), or false if the + //! variant uses dynamic branches for some shader options. + //! If the shader variant is not fully baked, the ShaderVariantKeyFallbackValue must be correctly set when drawing. + bool IsFullyBaked() const; + + //! Return the timestamp when this asset was built, and it must be >= than the timestamp of the main ShaderAsset. + //! This is used to synchronize versions of the ShaderAsset and ShaderVariantAsset2, especially during hot-reload. + AZStd::sys_time_t GetBuildTimestamp() const; + + bool IsRootVariant() const { return m_stableId == RPI::RootShaderVariantStableId; } + + private: + //! Called by asset creators to assign the asset to a ready state. + void SetReady(); + bool FinalizeAfterLoad(); + + //! See AZ::RPI::ShaderVariantListSourceData::VariantInfo::m_stableId for details. + RPI::ShaderVariantStableId m_stableId; + + ShaderVariantId m_shaderVariantId; + + bool m_isFullyBaked = false; + + AZStd::array, RHI::ShaderStageCount> m_functionsByStage; + + //! Used to synchronize versions of the ShaderAsset and ShaderVariantAsset2, especially during hot-reload. + AZStd::sys_time_t m_buildTimestamp = 0; + }; + + class ShaderVariantAssetHandler2 final + : public AssetHandler + { + using Base = AssetHandler; + public: + ShaderVariantAssetHandler2() = default; + + private: + LoadResult LoadAssetData(const Data::Asset& asset, AZStd::shared_ptr stream, const AZ::Data::AssetFilterCB& assetLoadFilterCB) override; + bool PostLoadInit(const Data::Asset& asset); + }; + + } // namespace RPI + +} // namespace AZ diff --git a/Gems/Atom/RPI/Code/Source/RPI.Builders/BuilderComponent.cpp b/Gems/Atom/RPI/Code/Source/RPI.Builders/BuilderComponent.cpp index e3386ebb5c..70752bf02b 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Builders/BuilderComponent.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Builders/BuilderComponent.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -35,6 +36,7 @@ #include #include #include +#include #include #include @@ -88,6 +90,7 @@ namespace AZ m_assetWorkers.emplace_back(MakeAssetBuilder()); m_assetHandlers.emplace_back(MakeAssetHandler()); + m_assetHandlers.emplace_back(MakeAssetHandler()); m_assetHandlers.emplace_back(MakeAssetHandler()); m_assetHandlers.emplace_back(MakeAssetHandler()); m_assetHandlers.emplace_back(MakeAssetHandler()); @@ -98,6 +101,7 @@ namespace AZ m_assetHandlers.emplace_back(MakeAssetHandler()); m_assetHandlers.emplace_back(MakeAssetHandler()); m_assetHandlers.emplace_back(MakeAssetHandler()); + m_assetHandlers.emplace_back(MakeAssetHandler()); m_assetHandlers.emplace_back(MakeAssetHandler()); m_assetHandlers.emplace_back(MakeAssetHandler()); m_assetHandlers.emplace_back(MakeAssetHandler()); diff --git a/Gems/Atom/RPI/Code/Source/RPI.Edit/Shader/ShaderSourceData.cpp b/Gems/Atom/RPI/Code/Source/RPI.Edit/Shader/ShaderSourceData.cpp index e67480e6f9..aac81a6e26 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Edit/Shader/ShaderSourceData.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Edit/Shader/ShaderSourceData.cpp @@ -11,19 +11,19 @@ */ #include +#include +#include namespace AZ { namespace RPI { - const char* ShaderSourceData::Extension = "shader"; - void ShaderSourceData::Reflect(ReflectContext* context) { if (auto* serializeContext = azrtti_cast(context)) { serializeContext->Class() - ->Version(3) + ->Version(4) ->Field("Source", &ShaderSourceData::m_source) ->Field("DrawList", &ShaderSourceData::m_drawListName) ->Field("DepthStencilState", &ShaderSourceData::m_depthStencilState) @@ -31,8 +31,8 @@ namespace AZ ->Field("BlendState", &ShaderSourceData::m_blendState) ->Field("ProgramSettings", &ShaderSourceData::m_programSettings) ->Field("CompilerHints", &ShaderSourceData::m_compiler) - ->Field("ShaderVariantHints", &ShaderSourceData::m_shaderOptionGroupHints) ->Field("DisabledRHIBackends", &ShaderSourceData::m_disabledRhiBackends) + ->Field("Supervariants", &ShaderSourceData::m_supervariants) ; serializeContext->Class() @@ -45,15 +45,145 @@ namespace AZ ->Field("Name", &EntryPoint::m_name) ->Field("Type", &EntryPoint::m_type) ; + + serializeContext->Class() + ->Version(1) + ->Field("Name", &SupervariantInfo::m_name) + ->Field("PlusArguments", &SupervariantInfo::m_plusArguments) + ->Field("MinusArguments", &SupervariantInfo::m_minusArguments); + } } - bool ShaderSourceData::IsRhiBackendDisabled(const AZ::Name& rhiName) + bool ShaderSourceData::IsRhiBackendDisabled(const AZ::Name& rhiName) const { return AZStd::any_of(m_disabledRhiBackends.begin(), m_disabledRhiBackends.end(), [&](const AZStd::string& currentRhiName) { return currentRhiName == rhiName.GetStringView(); }); } + + + //! Helper function. + //! Parses a string of command line arguments looking for c-preprocessor macro definitions and appends the name of macro definition arguments. + //! Example: + //! Input string: "--switch1 -DMACRO1 -v -DMACRO2=23" + //! append the following items: ["MACRO1", "MACRO2"] + static void GetListOfMacroDefinitionNames( + const AZStd::string& stringWithArguments, AZStd::vector& macroDefinitionNames) + { + static const AZStd::regex macroRegex("-D\\s*(\\w+)", AZStd::regex::ECMAScript); + + AZStd::cmatch match; + if (AZStd::regex_search(stringWithArguments.c_str(), match, macroRegex)) + { + // First pattern is always the entire string + for (unsigned i = 1; i < match.size(); ++i) + { + if (match[i].matched) + { + macroDefinitionNames.push_back(match[i].str().c_str()); + } + } + } + } + + AZStd::vector ShaderSourceData::SupervariantInfo::GetCombinedListOfMacroDefinitionNamesToRemove() const + { + AZStd::vector macroDefinitionNames; + GetListOfMacroDefinitionNames(m_minusArguments, macroDefinitionNames); + GetListOfMacroDefinitionNames(m_plusArguments, macroDefinitionNames); + return macroDefinitionNames; + } + + + //! Helper function. + //! Parses a string of command line arguments looking for c-preprocessor macro definitions and appends macro definition + //! arguments. Example: Input string: "--switch1 -DMACRO1 -v -DMACRO2=23" append the following items: ["MACRO1", "MACRO2=23"] + static void GetListOfMacroDefinitions( + const AZStd::string& stringWithArguments, AZStd::vector& macroDefinitions) + { + static const AZStd::regex macroRegex("-D\\s*(\\w+(=\\w+)?)", AZStd::regex::ECMAScript); + + AZStd::cmatch match; + if (AZStd::regex_search(stringWithArguments.c_str(), match, macroRegex)) + { + // First pattern is always the entire string + for (unsigned i = 1; i < match.size(); ++i) + { + if (match[i].matched) + { + macroDefinitions.push_back(match[i].str().c_str()); + } + } + } + } + + AZStd::vector ShaderSourceData::SupervariantInfo::GetMacroDefinitionsToAdd() const + { + AZStd::vector parsedMacroDefinitions; + GetListOfMacroDefinitions(m_plusArguments, parsedMacroDefinitions); + return parsedMacroDefinitions; + } + + + // Helper. + // @arguments: A string with command line arguments for a console application of the form: + // "- -- --[=] ..." + // Example: "--use-spaces --namespace=vk" + // Returns: A list with just the [-|--]: + // ["-", "--", "--arg3"] + // For the example shown above it will return this vector: + // ["--use-spaces", "--namespace"] + AZStd::vector GetListOfArgumentNames(const AZStd::string& arguments) + { + AZStd::vector listOfTokens; + AzFramework::StringFunc::Tokenize(arguments, listOfTokens); + AZStd::vector listOfArguments; + for (const AZStd::string& token : listOfTokens) + { + AZStd::vector splitArguments; + AzFramework::StringFunc::Tokenize(token, splitArguments, "="); + listOfArguments.push_back(splitArguments[0]); + } + return listOfArguments; + } + + AZStd::string ShaderSourceData::SupervariantInfo::GetCustomizedArgumentsForAzslc( + const AZStd::string& initialAzslcCompilerArguments) const + { + static const AZStd::regex macroRegex("-D\\s*(\\w+(=\\S+)?)", AZStd::regex::ECMAScript); + + // We are only concerned with AZSLc arguments. Let's remove the C-Preprocessor macro definitions + // from @minusArguments. + const AZStd::string minusArguments = AZStd::regex_replace(m_minusArguments, macroRegex, ""); + const AZStd::string plusArguments = AZStd::regex_replace(m_plusArguments, macroRegex, ""); + AZStd::string azslcArgumentsToRemove = minusArguments + " " + plusArguments; + AZStd::vector azslcArgumentNamesToRemove = GetListOfArgumentNames(azslcArgumentsToRemove); + + // At this moment @azslcArgumentsToRemove contains arguments for AZSLc that can be of the form: + // - + // --[=] + // We need to remove those from @initialAzslcCompilerArguments. + AZStd::string customizedArguments = initialAzslcCompilerArguments; + for (const AZStd::string& azslcArgumentName : azslcArgumentNamesToRemove) + { + AZStd::string regexStr = AZStd::string::format("%s(=\\S+)?", azslcArgumentName.c_str()); + AZStd::regex replaceRegex(regexStr, AZStd::regex::ECMAScript); + customizedArguments = AZStd::regex_replace(customizedArguments, replaceRegex, ""); + } + + customizedArguments += " " + plusArguments; + + // Will contain the results that will be joined by a space. + // This is used to get a clean string to return without excess spaces. + AZStd::vector argumentList; + AzFramework::StringFunc::Tokenize(customizedArguments, argumentList, " \t\n"); + customizedArguments.clear(); // Need to clear because Join appends. + AzFramework::StringFunc::Join(customizedArguments, argumentList.begin(), argumentList.end(), " "); + return customizedArguments; + } + + } // namespace RPI } // namespace AZ diff --git a/Gems/Atom/RPI/Code/Source/RPI.Edit/Shader/ShaderVariantAssetCreator2.cpp b/Gems/Atom/RPI/Code/Source/RPI.Edit/Shader/ShaderVariantAssetCreator2.cpp new file mode 100644 index 0000000000..2936ed50f9 --- /dev/null +++ b/Gems/Atom/RPI/Code/Source/RPI.Edit/Shader/ShaderVariantAssetCreator2.cpp @@ -0,0 +1,112 @@ +/* +* 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 + +namespace AZ +{ + namespace RPI + { + void ShaderVariantAssetCreator2::Begin(const AZ::Data::AssetId& assetId, const ShaderVariantId& shaderVariantId, RPI::ShaderVariantStableId stableId, bool isFullyBaked) + { + BeginCommon(assetId); + + if (ValidateIsReady()) + { + m_asset->m_stableId = stableId; + m_asset->m_shaderVariantId = shaderVariantId; + m_asset->m_isFullyBaked = isFullyBaked; + } + } + + bool ShaderVariantAssetCreator2::End(Data::Asset& result) + { + if (!ValidateIsReady()) + { + return false; + } + + if (!m_asset->FinalizeAfterLoad()) + { + ReportError("Failed to finalize the ShaderResourceGroupAsset."); + return false; + } + + bool foundDrawFunctions = false; + bool foundDispatchFunctions = false; + + if (m_asset->GetShaderStageFunction(RHI::ShaderStage::Vertex) || + m_asset->GetShaderStageFunction(RHI::ShaderStage::Tessellation) || + m_asset->GetShaderStageFunction(RHI::ShaderStage::Fragment)) + { + foundDrawFunctions = true; + } + + if (m_asset->GetShaderStageFunction(RHI::ShaderStage::Compute)) + { + foundDispatchFunctions = true; + } + + + if (foundDrawFunctions && foundDispatchFunctions) + { + ReportError("ShaderVariant contains both Draw functions and Dispatch functions."); + return false; + } + + if (m_asset->GetShaderStageFunction(RHI::ShaderStage::Fragment) && + !m_asset->GetShaderStageFunction(RHI::ShaderStage::Vertex)) + { + ReportError("Shader Variant with StableId '%u' has a fragment function but no vertex function.", m_asset->m_stableId); + return false; + } + + if (m_asset->GetShaderStageFunction(RHI::ShaderStage::Tessellation) && + !m_asset->GetShaderStageFunction(RHI::ShaderStage::Vertex)) + { + ReportError("Shader Variant with StableId '%u' has a tessellation function but no vertex function.", m_asset->m_stableId); + return false; + } + + + + m_asset->SetReady(); + return EndCommon(result); + } + + + ///////////////////////////////////////////////////////////////////// + // Methods for all shader variant types + + void ShaderVariantAssetCreator2::SetBuildTimestamp(AZStd::sys_time_t buildTimestamp) + { + if (ValidateIsReady()) + { + m_asset->m_buildTimestamp = buildTimestamp; + } + } + + void ShaderVariantAssetCreator2::SetShaderFunction(RHI::ShaderStage shaderStage, RHI::Ptr shaderStageFunction) + { + if (ValidateIsReady()) + { + m_asset->m_functionsByStage[static_cast(shaderStage)] = shaderStageFunction; + } + } + + } // namespace RPI +} // namespace AZ diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/Material/MaterialSystem.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/Material/MaterialSystem.cpp index e7e04b271d..f1751a02e2 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/Material/MaterialSystem.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/Material/MaterialSystem.cpp @@ -32,6 +32,7 @@ namespace AZ MaterialPropertiesLayout::Reflect(context); MaterialFunctor::Reflect(context); LuaMaterialFunctor::Reflect(context); + ReflectMaterialDynamicMetadata(context); } void MaterialSystem::GetAssetHandlers(AssetHandlerPtrList& assetHandlers) diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/Shader/Shader2.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/Shader/Shader2.cpp new file mode 100644 index 0000000000..f56a18e2b9 --- /dev/null +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/Shader/Shader2.cpp @@ -0,0 +1,413 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ +#include + +#include + +#include + +#include +#include + +#include + +#include +#include +#include + +namespace AZ +{ + namespace RPI + { + Data::Instance Shader2::FindOrCreate(const Data::Asset& shaderAsset, const Name& supervariantName) + { + Data::Instance shaderInstance = Data::InstanceDatabase::Instance().FindOrCreate( + Data::InstanceId::CreateFromAssetId(shaderAsset.GetId()), + shaderAsset); + if (!shaderInstance) + { + return nullptr; + } + + if (!shaderInstance->SelectSupervariant(supervariantName)) + { + return nullptr; + } + + const RHI::ResultCode resultCode = shaderInstance->Init(*shaderAsset.Get()); + if (resultCode != RHI::ResultCode::Success) + { + return nullptr; + } + return shaderInstance; + } + + Data::Instance Shader2::CreateInternal([[maybe_unused]] ShaderAsset2& shaderAsset) + { + Data::Instance shader = aznew Shader2(); + return shader; + } + + Shader2::~Shader2() + { + Shutdown(); + } + + bool Shader2::SelectSupervariant(const Name& supervariantName) + { + if (supervariantName.IsEmpty()) + { + m_supervariantIndex = DefaultSupervariantIndex; + return true; + } + + auto supervariantIndex = m_asset->GetSupervariantIndex(supervariantName); + if (supervariantIndex == InvalidSupervariantIndex) + { + return false; + } + + m_supervariantIndex = supervariantIndex; + return true; + } + + RHI::ResultCode Shader2::Init(ShaderAsset2& shaderAsset) + { + AZ_Assert(m_supervariantIndex != InvalidSupervariantIndex, "Invalid supervariant index"); + + ShaderVariantFinderNotificationBus2::Handler::BusDisconnect(); + ShaderVariantFinderNotificationBus2::Handler::BusConnect(shaderAsset.GetId()); + + RHI::RHISystemInterface* rhiSystem = RHI::RHISystemInterface::Get(); + RHI::DrawListTagRegistry* drawListTagRegistry = rhiSystem->GetDrawListTagRegistry(); + + m_asset = { &shaderAsset, AZ::Data::AssetLoadBehavior::PreLoad }; + m_pipelineStateType = shaderAsset.GetPipelineStateType(); + + { + AZStd::unique_lock lock(m_variantCacheMutex); + m_shaderVariants.clear(); + } + m_rootVariant.Init(shaderAsset, shaderAsset.GetRootVariant(m_supervariantIndex), m_supervariantIndex); + + if (m_pipelineLibraryHandle.IsNull()) + { + // We set up a pipeline library only once for the lifetime of the Shader2 instance. + // This should allow the Shader2 to be reloaded at runtime many times, and cache and reuse PipelineState objects rather than rebuild them. + // It also fixes a particular TDR crash that occurred on some hardware when hot-reloading shaders and building pipeline states + // in a new pipeline library every time. + + RHI::PipelineStateCache* pipelineStateCache = rhiSystem->GetPipelineStateCache(); + ConstPtr serializedData = LoadPipelineLibrary(); + RHI::PipelineLibraryHandle pipelineLibraryHandle = pipelineStateCache->CreateLibrary(serializedData.get()); + + if (pipelineLibraryHandle.IsNull()) + { + AZ_Error("Shader2", false, "Failed to create pipeline library from pipeline state cache."); + return RHI::ResultCode::Fail; + } + + m_pipelineLibraryHandle = pipelineLibraryHandle; + m_pipelineStateCache = pipelineStateCache; + } + + const Name& drawListName = shaderAsset.GetDrawListName(); + if (!drawListName.IsEmpty()) + { + m_drawListTag = drawListTagRegistry->AcquireTag(drawListName); + if (!m_drawListTag.IsValid()) + { + AZ_Error("Shader2", false, "Failed to acquire a DrawListTag. Entries are full."); + } + } + + Data::AssetBus::Handler::BusConnect(m_asset.GetId()); + + return RHI::ResultCode::Success; + } + + void Shader2::Shutdown() + { + ShaderVariantFinderNotificationBus2::Handler::BusDisconnect(); + Data::AssetBus::Handler::BusDisconnect(); + + if (m_pipelineLibraryHandle.IsValid()) + { + SavePipelineLibrary(); + + m_pipelineStateCache->ReleaseLibrary(m_pipelineLibraryHandle); + m_pipelineStateCache = nullptr; + m_pipelineLibraryHandle = {}; + } + + if (m_drawListTag.IsValid()) + { + RHI::DrawListTagRegistry* drawListTagRegistry = RHI::RHISystemInterface::Get()->GetDrawListTagRegistry(); + drawListTagRegistry->ReleaseTag(m_drawListTag); + m_drawListTag.Reset(); + } + } + + /////////////////////////////////////////////////////////////////////// + // AssetBus overrides + void Shader2::OnAssetReloaded(Data::Asset asset) + { + ShaderReloadDebugTracker::ScopedSection reloadSection("Shader2::OnAssetReloaded %s", asset.GetHint().c_str()); + + if (asset->GetId() == m_asset->GetId()) + { + Data::Asset newAsset = { asset.GetAs(), AZ::Data::AssetLoadBehavior::PreLoad }; + AZ_Assert(newAsset, "Reloaded ShaderAsset2 is null"); + + Data::AssetBus::Handler::BusDisconnect(); + Init(*newAsset.Get()); + ShaderReloadNotificationBus2::Event(asset.GetId(), &ShaderReloadNotificationBus2::Events::OnShaderReinitialized, *this); + } + } + /////////////////////////////////////////////////////////////////////// + + /////////////////////////////////////////////////////////////////// + /// ShaderVariantFinderNotificationBus2 overrides + void Shader2::OnShaderVariantAssetReady(Data::Asset shaderVariantAsset, bool isError) + { + AZ_Assert(shaderVariantAsset, "Reloaded ShaderVariantAsset is null"); + const ShaderVariantStableId stableId = shaderVariantAsset->GetStableId(); + const ShaderVariantId& shaderVariantId = shaderVariantAsset->GetShaderVariantId(); + + if (isError) + { + //Remark: We do not assert if the stableId == RootShaderVariantStableId, because we can not trust in the asset data + //on error. so it is possible that on error the stbleId == RootShaderVariantStableId; + if (stableId == RootShaderVariantStableId) + { + return; + } + AZStd::unique_lock lock(m_variantCacheMutex); + m_shaderVariants.erase(stableId); + } + else + { + AZ_Assert(stableId != RootShaderVariantStableId, + "The root variant is expected to be updated by the ShaderAsset2."); + AZStd::unique_lock lock(m_variantCacheMutex); + + auto iter = m_shaderVariants.find(stableId); + if (iter != m_shaderVariants.end()) + { + ShaderVariant2& shaderVariant = iter->second; + + if (!shaderVariant.Init(*m_asset.Get(), shaderVariantAsset, m_supervariantIndex)) + { + AZ_Error("Shader2", false, "Failed to init shaderVariant with StableId=%u", shaderVariantAsset->GetStableId()); + m_shaderVariants.erase(stableId); + } + } + else + { + //This is the first time the shader variant asset comes to life. + ShaderVariant2 newVariant; + newVariant.Init(*m_asset, shaderVariantAsset, m_supervariantIndex); + m_shaderVariants.emplace(stableId, newVariant); + } + } + + //Even if there was an error, the interested parties should be notified. + ShaderReloadNotificationBus2::Event(m_asset.GetId(), &ShaderReloadNotificationBus2::Events::OnShaderVariantReinitialized, *this, shaderVariantId, stableId); + } + /////////////////////////////////////////////////////////////////// + + ConstPtr Shader2::LoadPipelineLibrary() const + { + if (IO::FileIOBase::GetInstance()) + { + return Utils::LoadObjectFromFile(GetPipelineLibraryPath()); + } + return nullptr; + } + + void Shader2::SavePipelineLibrary() const + { + if (auto* fileIOBase = IO::FileIOBase::GetInstance()) + { + RHI::ConstPtr serializedData = m_pipelineStateCache->GetLibrarySerializedData(m_pipelineLibraryHandle); + if (serializedData) + { + const AZStd::string pipelineLibraryPath = GetPipelineLibraryPath(); + + char pipelineLibraryPathResolved[AZ_MAX_PATH_LEN] = { 0 }; + fileIOBase->ResolvePath(pipelineLibraryPath.c_str(), pipelineLibraryPathResolved, AZ_MAX_PATH_LEN); + Utils::SaveObjectToFile(pipelineLibraryPathResolved, DataStream::ST_BINARY, serializedData.get()); + } + } + else + { + AZ_Error("Shader2", false, "FileIOBase is not initialized"); + } + } + + AZStd::string Shader2::GetPipelineLibraryPath() const + { + const Data::InstanceId& instanceId = GetId(); + Name platformName = RHI::Factory::Get().GetName(); + Name shaderName = m_asset->GetName(); + + AZStd::string uuidString; + instanceId.m_guid.ToString(uuidString, false, false); + + return AZStd::string::format("@user@/Atom/PipelineStateCache/%s/%s_%s_%d.bin", platformName.GetCStr(), shaderName.GetCStr(), uuidString.data(), instanceId.m_subId); + } + + ShaderOptionGroup Shader2::CreateShaderOptionGroup() const + { + return ShaderOptionGroup(m_asset->GetShaderOptionGroupLayout()); + } + + const ShaderVariant2& Shader2::GetVariant(const ShaderVariantId& shaderVariantId) + { + AZ_PROFILE_FUNCTION(Debug::ProfileCategory::AzRender); + Data::Asset shaderVariantAsset = m_asset->GetVariant(shaderVariantId, m_supervariantIndex); + if (!shaderVariantAsset || shaderVariantAsset->IsRootVariant()) + { + return m_rootVariant; + } + + return GetVariant(shaderVariantAsset->GetStableId()); + } + + const ShaderVariant2& Shader2::GetRootVariant() + { + return m_rootVariant; + } + + ShaderVariantSearchResult Shader2::FindVariantStableId(const ShaderVariantId& shaderVariantId) const + { + AZ_PROFILE_FUNCTION(Debug::ProfileCategory::AzRender); + ShaderVariantSearchResult variantSearchResult = m_asset->FindVariantStableId(shaderVariantId); + return variantSearchResult; + } + + const ShaderVariant2& Shader2::GetVariant(ShaderVariantStableId shaderVariantStableId) + { + AZ_PROFILE_FUNCTION(Debug::ProfileCategory::AzRender); + + if (!shaderVariantStableId.IsValid() || shaderVariantStableId == ShaderAsset2::RootShaderVariantStableId) + { + return m_rootVariant; + } + + { + AZStd::shared_lock lock(m_variantCacheMutex); + + auto findIt = m_shaderVariants.find(shaderVariantStableId); + if (findIt != m_shaderVariants.end()) + { + // When rebuilding shaders we may be in a state where the ShaderAsset2 and root ShaderVariantAsset have been rebuilt and + // reloaded, but some (or all) shader variants haven't been built yet. Since we want to use the latest version of the + // shader code, ignore the old variants and fall back to the newer root variant instead. There's no need to report a + // warning here because m_asset->GetVariant below will report one. + if (findIt->second.GetBuildTimestamp() >= m_asset->GetShaderAssetBuildTimestamp()) + { + return findIt->second; + } + } + } + + // By calling GetVariant, an asynchronous asset load request is enqueued if the variant + // is not fully ready. + Data::Asset shaderVariantAsset = m_asset->GetVariant(shaderVariantStableId, m_supervariantIndex); + if (!shaderVariantAsset || shaderVariantAsset == m_asset->GetRootVariant()) + { + // Return the root variant when the requested variant is not ready. + return m_rootVariant; + } + + AZStd::unique_lock lock(m_variantCacheMutex); + + // For performance reasons We are breaking this function into two locking steps. + // which means We must check again if the variant is already in the cache. + auto findIt = m_shaderVariants.find(shaderVariantStableId); + if (findIt != m_shaderVariants.end()) + { + if (findIt->second.GetBuildTimestamp() >= m_asset->GetShaderAssetBuildTimestamp()) + { + return findIt->second; + } + else + { + // This is probably very rare, but if the variant was loaded on another thread and it's out of date + // we just return the root variant. Otherwise we could end up replacing the variant in the map below while + // it's being used for rendering. + AZ_Warning( + "Shader2", false, + "Detected an uncommon state during shader reload. Returning the root variant instead of replacing the old one."); + return m_rootVariant; + } + } + + ShaderVariant2 newVariant; + newVariant.Init(*m_asset, shaderVariantAsset, m_supervariantIndex); + m_shaderVariants.emplace(shaderVariantStableId, newVariant); + + return m_shaderVariants.at(shaderVariantStableId); + } + + RHI::PipelineStateType Shader2::GetPipelineStateType() const + { + return m_pipelineStateType; + } + + const ShaderInputContract& Shader2::GetInputContract() const + { + return m_asset->GetInputContract(m_supervariantIndex); + } + + const ShaderOutputContract& Shader2::GetOutputContract() const + { + return m_asset->GetOutputContract(m_supervariantIndex); + } + + const RHI::PipelineState* Shader2::AcquirePipelineState(const RHI::PipelineStateDescriptor& descriptor) const + { + return m_pipelineStateCache->AcquirePipelineState(m_pipelineLibraryHandle, descriptor); + } + + const RHI::Ptr Shader2::FindShaderResourceGroupLayout(const Name& shaderResourceGroupName) const + { + return m_asset->FindShaderResourceGroupLayout(shaderResourceGroupName, m_supervariantIndex); + } + + const RHI::Ptr Shader2::FindShaderResourceGroupLayout(uint32_t bindingSlot) const + { + return m_asset->FindShaderResourceGroupLayout(bindingSlot, m_supervariantIndex); + } + + const RHI::Ptr Shader2::FindFallbackShaderResourceGroupLayout() const + { + return m_asset->FindFallbackShaderResourceGroupLayout(m_supervariantIndex); + } + + AZStd::array_view> Shader2::GetShaderResourceGroupLayouts() const + { + return m_asset->GetShaderResourceGroupLayouts(m_supervariantIndex); + } + + const Data::Asset& Shader2::GetAsset() const + { + return m_asset; + } + + RHI::DrawListTag Shader2::GetDrawListTag() const + { + return m_drawListTag; + } + } // namespace RPI +} // namespace AZ diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/Shader/ShaderResourceGroup.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/Shader/ShaderResourceGroup.cpp index 77ea0b9494..86cb3cd0a4 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/Shader/ShaderResourceGroup.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/Shader/ShaderResourceGroup.cpp @@ -88,6 +88,41 @@ namespace AZ return RHI::ResultCode::Success; } + bool ShaderResourceGroup::ReplaceSrgLayoutUsingShaderAsset( + Data::Asset shaderAsset, const Name& supervariantName, const Name& srgName) + { + AZ_TRACE_METHOD(); + + SupervariantIndex supervariantIndex = shaderAsset->GetSupervariantIndex(supervariantName); + if (supervariantIndex == InvalidSupervariantIndex) + { + AZ_Assert( + false, "Supervariant with name [%s] not found in shader asset [%s]", supervariantName.GetCStr(), + shaderAsset->GetName().GetCStr()); + return false; + } + + m_layout = shaderAsset->FindShaderResourceGroupLayout(srgName, supervariantIndex).get(); + + if (!m_layout) + { + AZ_Assert(false, "ShaderResourceGroup cannot be initialized due to invalid ShaderResourceGroupLayout"); + return false; + } + + m_shaderResourceGroup->SetName(m_layout->GetName()); + m_data = RHI::ShaderResourceGroupData(m_layout); + m_shaderAsset = shaderAsset; + + // The RPI groups match the same dimensions as the RHI group. + m_imageGroup.clear(); + m_imageGroup.resize(m_layout->GetGroupSizeForImages()); + m_bufferGroup.clear(); + m_bufferGroup.resize(m_layout->GetGroupSizeForBuffers()); + + return true; + } + void ShaderResourceGroup::Compile() { m_shaderResourceGroup->Compile(m_data); diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/Shader/ShaderSystem.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/Shader/ShaderSystem.cpp index 1527e81744..b5125ba964 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/Shader/ShaderSystem.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/Shader/ShaderSystem.cpp @@ -12,15 +12,18 @@ #include #include +#include #include #include #include #include #include +#include #include #include #include +#include #include #include @@ -42,9 +45,11 @@ namespace AZ ShaderVariantId::Reflect(context); ShaderVariantStableId::Reflect(context); ShaderAsset::Reflect(context); + ShaderAsset2::Reflect(context); ShaderInputContract::Reflect(context); ShaderOutputContract::Reflect(context); ShaderVariantAsset::Reflect(context); + ShaderVariantAsset2::Reflect(context); ShaderVariantTreeAsset::Reflect(context); ReflectShaderStageType(context); PrecompiledShaderAssetSourceData::Reflect(context); @@ -58,8 +63,10 @@ namespace AZ void ShaderSystem::GetAssetHandlers(AssetHandlerPtrList& assetHandlers) { assetHandlers.emplace_back(MakeAssetHandler()); + assetHandlers.emplace_back(MakeAssetHandler()); assetHandlers.emplace_back(MakeAssetHandler()); assetHandlers.emplace_back(MakeAssetHandler()); + assetHandlers.emplace_back(MakeAssetHandler()); assetHandlers.emplace_back(MakeAssetHandler()); } @@ -78,6 +85,14 @@ namespace AZ Data::InstanceDatabase::Create(azrtti_typeid(), handler); } + { + Data::InstanceHandler handler; + handler.m_createFunction = [](Data::AssetData* shaderAsset) { + return Shader2::CreateInternal(*(azrtti_cast(shaderAsset))); + }; + Data::InstanceDatabase::Create(azrtti_typeid(), handler); + } + { Data::InstanceHandler handler; handler.m_createFunction = [](Data::AssetData* srgAsset) @@ -100,6 +115,7 @@ namespace AZ void ShaderSystem::Shutdown() { Data::InstanceDatabase::Destroy(); + Data::InstanceDatabase::Destroy(); Data::InstanceDatabase::Destroy(); Data::InstanceDatabase::Destroy(); Interface::Unregister(this); diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/Shader/ShaderVariant2.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/Shader/ShaderVariant2.cpp new file mode 100644 index 0000000000..d25b87fab3 --- /dev/null +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/Shader/ShaderVariant2.cpp @@ -0,0 +1,76 @@ +/* +* 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 + +namespace AZ +{ + namespace RPI + { + bool ShaderVariant2::Init( + const ShaderAsset2& shaderAsset, + Data::Asset shaderVariantAsset, + SupervariantIndex supervariantIndex) + { + m_pipelineStateType = shaderAsset.GetPipelineStateType(); + m_pipelineLayoutDescriptor = shaderAsset.GetPipelineLayoutDescriptor(supervariantIndex); + m_shaderVariantAsset = shaderVariantAsset; + m_renderStates = &shaderAsset.GetRenderStates(supervariantIndex); + return true; + } + + void ShaderVariant2::ConfigurePipelineState(RHI::PipelineStateDescriptor& descriptor) const + { + descriptor.m_pipelineLayoutDescriptor = m_pipelineLayoutDescriptor; + + switch (descriptor.GetType()) + { + case RHI::PipelineStateType::Draw: + { + AZ_Assert(m_pipelineStateType == RHI::PipelineStateType::Draw, "ShaderVariant2 is not intended for the raster pipeline."); + AZ_Assert(m_renderStates, "Invalid RenderStates"); + RHI::PipelineStateDescriptorForDraw& descriptorForDraw = static_cast(descriptor); + descriptorForDraw.m_vertexFunction = m_shaderVariantAsset->GetShaderStageFunction(RHI::ShaderStage::Vertex); + descriptorForDraw.m_tessellationFunction = m_shaderVariantAsset->GetShaderStageFunction(RHI::ShaderStage::Tessellation); + descriptorForDraw.m_fragmentFunction = m_shaderVariantAsset->GetShaderStageFunction(RHI::ShaderStage::Fragment); + descriptorForDraw.m_renderStates = *m_renderStates; + break; + } + + case RHI::PipelineStateType::Dispatch: + { + AZ_Assert(m_pipelineStateType == RHI::PipelineStateType::Dispatch, "ShaderVariant2 is not intended for the compute pipeline."); + RHI::PipelineStateDescriptorForDispatch& descriptorForDispatch = static_cast(descriptor); + descriptorForDispatch.m_computeFunction = m_shaderVariantAsset->GetShaderStageFunction(RHI::ShaderStage::Compute); + break; + } + + case RHI::PipelineStateType::RayTracing: + { + AZ_Assert(m_pipelineStateType == RHI::PipelineStateType::RayTracing, "ShaderVariant2 is not intended for the ray tracing pipeline."); + RHI::PipelineStateDescriptorForRayTracing& descriptorForRayTracing = static_cast(descriptor); + descriptorForRayTracing.m_rayTracingFunction = m_shaderVariantAsset->GetShaderStageFunction(RHI::ShaderStage::RayTracing); + break; + } + + default: + AZ_Assert(false, "Unexpected PipelineStateType"); + break; + } + } + + } // namespace RPI +} // namespace AZ diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/Shader/ShaderVariantAsyncLoader.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/Shader/ShaderVariantAsyncLoader.cpp index 53bad2f05c..6579e6a98d 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/Shader/ShaderVariantAsyncLoader.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/Shader/ShaderVariantAsyncLoader.cpp @@ -111,7 +111,7 @@ namespace AZ ShaderMetricsSystem::Get()->RequestShaderVariant(pairItor->m_shaderAsset.Get(), pairItor->m_shaderVariantId, searchResult); uint32_t shaderVariantProductSubId = - ShaderVariantAsset::GetAssetSubId(RHI::Factory::Get().GetAPIUniqueIndex(), searchResult.GetStableId()); + ShaderVariantAsset::MakeAssetProductSubId(RHI::Factory::Get().GetAPIUniqueIndex(), searchResult.GetStableId()); Data::AssetId shaderVariantAssetId(shaderVariantTreeAsset.GetId().m_guid, shaderVariantProductSubId); shaderVariantPendingRequests.insert(shaderVariantAssetId); pairItor = newShaderVariantPendingRequests.erase(pairItor); @@ -211,7 +211,7 @@ namespace AZ AZ_Assert(variantStableId != RootShaderVariantStableId, "Root Variants Are Found inside ShaderAssets"); uint32_t shaderVariantProductSubId = - ShaderVariantAsset::GetAssetSubId(RHI::Factory::Get().GetAPIUniqueIndex(), variantStableId); + ShaderVariantAsset::MakeAssetProductSubId(RHI::Factory::Get().GetAPIUniqueIndex(), variantStableId); Data::AssetId shaderVariantAssetId(shaderVariantTreeAssetId.m_guid, shaderVariantProductSubId); { AZStd::unique_lock lock(m_mutex); @@ -299,7 +299,7 @@ namespace AZ { AZ_Assert(variantStableId != RootShaderVariantStableId, "Root Variants Are Found inside ShaderAssets"); - uint32_t shaderVariantProductSubId = ShaderVariantAsset::GetAssetSubId(RHI::Factory::Get().GetAPIUniqueIndex(), variantStableId); + uint32_t shaderVariantProductSubId = ShaderVariantAsset::MakeAssetProductSubId(RHI::Factory::Get().GetAPIUniqueIndex(), variantStableId); Data::AssetId shaderVariantAssetId(shaderVariantTreeAssetId.m_guid, shaderVariantProductSubId); AZStd::unique_lock lock(m_mutex); diff --git a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Material/LuaMaterialFunctor.cpp b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Material/LuaMaterialFunctor.cpp index 7ab3489666..7db5f12560 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Material/LuaMaterialFunctor.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Material/LuaMaterialFunctor.cpp @@ -63,6 +63,7 @@ namespace AZ behaviorContext->Class(); MaterialPropertyDescriptor::Reflect(behaviorContext); + ReflectMaterialDynamicMetadata(behaviorContext); LuaMaterialFunctorRenderStates::Reflect(behaviorContext); LuaMaterialFunctorShaderItem::Reflect(behaviorContext); @@ -255,7 +256,7 @@ namespace AZ // Specialize for type Image* because that will be more intuitive within Lua. // The script can then check the result for nil without calling "get()". - // For example, "GetMaterialPropertyValue_image(name) == nil" rather than "GetMaterialPropertyValue_image(name):get() == nil" + // For example, "GetMaterialPropertyValue_Image(name) == nil" rather than "GetMaterialPropertyValue_Image(name):get() == nil" template<> Image* LuaMaterialFunctorCommonContext::GetMaterialPropertyValue(const char* name) const { @@ -278,7 +279,7 @@ namespace AZ ->Method("GetMaterialPropertyValue_Vector3", &LuaMaterialFunctorRuntimeContext::GetMaterialPropertyValue) ->Method("GetMaterialPropertyValue_Vector4", &LuaMaterialFunctorRuntimeContext::GetMaterialPropertyValue) ->Method("GetMaterialPropertyValue_Color", &LuaMaterialFunctorRuntimeContext::GetMaterialPropertyValue) - ->Method("GetMaterialPropertyValue_image", &LuaMaterialFunctorRuntimeContext::GetMaterialPropertyValue) + ->Method("GetMaterialPropertyValue_Image", &LuaMaterialFunctorRuntimeContext::GetMaterialPropertyValue) ->Method("SetShaderConstant_bool", &LuaMaterialFunctorRuntimeContext::SetShaderConstant) ->Method("SetShaderConstant_int", &LuaMaterialFunctorRuntimeContext::SetShaderConstant) ->Method("SetShaderConstant_uint", &LuaMaterialFunctorRuntimeContext::SetShaderConstant) @@ -436,7 +437,7 @@ namespace AZ ->Method("GetMaterialPropertyValue_Vector3", &LuaMaterialFunctorEditorContext::GetMaterialPropertyValue) ->Method("GetMaterialPropertyValue_Vector4", &LuaMaterialFunctorEditorContext::GetMaterialPropertyValue) ->Method("GetMaterialPropertyValue_Color", &LuaMaterialFunctorEditorContext::GetMaterialPropertyValue) - ->Method("GetMaterialPropertyValue_image", &LuaMaterialFunctorEditorContext::GetMaterialPropertyValue) + ->Method("GetMaterialPropertyValue_Image", &LuaMaterialFunctorEditorContext::GetMaterialPropertyValue) ->Method("SetMaterialPropertyVisibility", &LuaMaterialFunctorEditorContext::SetMaterialPropertyVisibility) ->Method("SetMaterialPropertyDescription", &LuaMaterialFunctorEditorContext::SetMaterialPropertyDescription) ->Method("SetMaterialPropertyMinValue_int", &LuaMaterialFunctorEditorContext::SetMaterialPropertyMinValue) @@ -451,6 +452,7 @@ namespace AZ ->Method("SetMaterialPropertySoftMaxValue_int", &LuaMaterialFunctorEditorContext::SetMaterialPropertySoftMaxValue) ->Method("SetMaterialPropertySoftMaxValue_uint", &LuaMaterialFunctorEditorContext::SetMaterialPropertySoftMaxValue) ->Method("SetMaterialPropertySoftMaxValue_float", &LuaMaterialFunctorEditorContext::SetMaterialPropertySoftMaxValue) + ->Method("SetMaterialPropertyGroupVisibility", &LuaMaterialFunctorEditorContext::SetMaterialPropertyGroupVisibility) ; } @@ -524,6 +526,15 @@ namespace AZ return m_editorContextImpl->SetMaterialPropertySoftMaxValue(index, value); } + + bool LuaMaterialFunctorEditorContext::SetMaterialPropertyGroupVisibility(const char* name, MaterialPropertyGroupVisibility visibility) + { + if (m_editorContextImpl) + { + return m_editorContextImpl->SetMaterialPropertyGroupVisibility(Name{m_propertyNamePrefix + name}, visibility); + } + return false; + } bool LuaMaterialFunctorEditorContext::SetMaterialPropertyVisibility(const char* name, MaterialPropertyVisibility visibility) { diff --git a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Material/MaterialDynamicMetadata.cpp b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Material/MaterialDynamicMetadata.cpp new file mode 100644 index 0000000000..5c8f4ccc02 --- /dev/null +++ b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Material/MaterialDynamicMetadata.cpp @@ -0,0 +1,50 @@ +/* +* 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 + +namespace AZ +{ + namespace RPI + { + void ReflectMaterialDynamicMetadata(ReflectContext* context) + { + if (auto* serializeContext = azrtti_cast(context)) + { + serializeContext->Enum() + ->Value("Enabled", MaterialPropertyVisibility::Enabled) + ->Value("Disabled", MaterialPropertyVisibility::Disabled) + ->Value("Hidden", MaterialPropertyVisibility::Hidden) + ; + + serializeContext->Enum() + ->Value("Enabled", MaterialPropertyGroupVisibility::Enabled) + ->Value("Hidden", MaterialPropertyGroupVisibility::Hidden) + ; + } + + if (auto* behaviorContext = azrtti_cast(context)) + { + behaviorContext + ->Enum<(int)MaterialPropertyVisibility::Enabled>("MaterialPropertyVisibility_Enabled") + ->Enum<(int)MaterialPropertyVisibility::Disabled>("MaterialPropertyVisibility_Disabled") + ->Enum<(int)MaterialPropertyVisibility::Hidden>("MaterialPropertyVisibility_Hidden"); + + behaviorContext + ->Enum<(int)MaterialPropertyGroupVisibility::Enabled>("MaterialPropertyGroupVisibility_Enabled") + ->Enum<(int)MaterialPropertyGroupVisibility::Hidden>("MaterialPropertyGroupVisibility_Hidden"); + } + } + + } // namespace RPI +} // namespace AZ diff --git a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Material/MaterialFunctor.cpp b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Material/MaterialFunctor.cpp index 32772e9dd5..a41ab9eea6 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Material/MaterialFunctor.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Material/MaterialFunctor.cpp @@ -142,25 +142,24 @@ namespace AZ MaterialFunctor::EditorContext::EditorContext( const AZStd::vector& propertyValues, RHI::ConstPtr materialPropertiesLayout, - AZStd::unordered_map& metadata, - AZStd::unordered_set& outChangedProperties, + AZStd::unordered_map& propertyMetadata, + AZStd::unordered_map& propertyGroupMetadata, + AZStd::unordered_set& updatedPropertiesOut, + AZStd::unordered_set& updatedPropertyGroupsOut, const MaterialPropertyFlags* materialPropertyDependencies ) : m_materialPropertyValues(propertyValues) , m_materialPropertiesLayout(materialPropertiesLayout) - , m_metadata(metadata) - , m_outChangedProperties(outChangedProperties) + , m_propertyMetadata(propertyMetadata) + , m_propertyGroupMetadata(propertyGroupMetadata) + , m_updatedPropertiesOut(updatedPropertiesOut) + , m_updatedPropertyGroupsOut(updatedPropertyGroupsOut) , m_materialPropertyDependencies(materialPropertyDependencies) {} const MaterialPropertyDynamicMetadata* MaterialFunctor::EditorContext::GetMaterialPropertyMetadata(const Name& propertyName) const { - auto it = QueryMaterialMetadata(propertyName); - if (it == m_metadata.end()) - { - return nullptr; - } - return &(it->second); + return QueryMaterialPropertyMetadata(propertyName); } const MaterialPropertyDynamicMetadata* MaterialFunctor::EditorContext::GetMaterialPropertyMetadata(const MaterialPropertyIndex& index) const @@ -168,19 +167,41 @@ namespace AZ const Name& name = m_materialPropertiesLayout->GetPropertyDescriptor(index)->GetName(); return GetMaterialPropertyMetadata(name); } - - bool MaterialFunctor::EditorContext::SetMaterialPropertyVisibility(const Name& propertyName, MaterialPropertyVisibility visibility) + + const MaterialPropertyGroupDynamicMetadata* MaterialFunctor::EditorContext::GetMaterialPropertyGroupMetadata(const Name& propertyName) const { - auto it = QueryMaterialMetadata(propertyName); - if (it == m_metadata.end()) + return QueryMaterialPropertyGroupMetadata(propertyName); + } + + bool MaterialFunctor::EditorContext::SetMaterialPropertyGroupVisibility(const Name& propertyGroupName, MaterialPropertyGroupVisibility visibility) + { + MaterialPropertyGroupDynamicMetadata* metadata = QueryMaterialPropertyGroupMetadata(propertyGroupName); + if (!metadata) { return false; } - MaterialPropertyVisibility originValue = it->second.m_visibility; - it->second.m_visibility = visibility; - if (originValue != visibility) + + if (metadata->m_visibility != visibility) { - m_outChangedProperties.insert(propertyName); + metadata->m_visibility = visibility; + m_updatedPropertyGroupsOut.insert(propertyGroupName); + } + + return true; + } + + bool MaterialFunctor::EditorContext::SetMaterialPropertyVisibility(const Name& propertyName, MaterialPropertyVisibility visibility) + { + MaterialPropertyDynamicMetadata* metadata = QueryMaterialPropertyMetadata(propertyName); + if (!metadata) + { + return false; + } + + if (metadata->m_visibility != visibility) + { + metadata->m_visibility = visibility; + m_updatedPropertiesOut.insert(propertyName); } return true; @@ -194,17 +215,16 @@ namespace AZ bool MaterialFunctor::EditorContext::SetMaterialPropertyDescription(const Name& propertyName, AZStd::string description) { - auto it = QueryMaterialMetadata(propertyName); - if (it == m_metadata.end()) + MaterialPropertyDynamicMetadata* metadata = QueryMaterialPropertyMetadata(propertyName); + if (!metadata) { return false; } - AZStd::string origin = it->second.m_description; - it->second.m_description = description; - if (origin != description) + if (metadata->m_description != description) { - m_outChangedProperties.insert(propertyName); + metadata->m_description = description; + m_updatedPropertiesOut.insert(propertyName); } return true; @@ -218,18 +238,16 @@ namespace AZ bool MaterialFunctor::EditorContext::SetMaterialPropertyMinValue(const Name& propertyName, const MaterialPropertyValue& min) { - auto it = QueryMaterialMetadata(propertyName); - if (it == m_metadata.end()) + MaterialPropertyDynamicMetadata* metadata = QueryMaterialPropertyMetadata(propertyName); + if (!metadata) { return false; } - MaterialPropertyValue origin = it->second.m_propertyRange.m_min; - it->second.m_propertyRange.m_min = min; - - if(origin != min) + if(metadata->m_propertyRange.m_min != min) { - m_outChangedProperties.insert(propertyName); + metadata->m_propertyRange.m_min = min; + m_updatedPropertiesOut.insert(propertyName); } return true; @@ -243,18 +261,16 @@ namespace AZ bool MaterialFunctor::EditorContext::SetMaterialPropertyMaxValue(const Name& propertyName, const MaterialPropertyValue& max) { - auto it = QueryMaterialMetadata(propertyName); - if (it == m_metadata.end()) + MaterialPropertyDynamicMetadata* metadata = QueryMaterialPropertyMetadata(propertyName); + if (!metadata) { return false; } - MaterialPropertyValue origin = it->second.m_propertyRange.m_max; - it->second.m_propertyRange.m_max = max; - - if (origin != max) + if (metadata->m_propertyRange.m_max != max) { - m_outChangedProperties.insert(propertyName); + metadata->m_propertyRange.m_max = max; + m_updatedPropertiesOut.insert(propertyName); } return true; @@ -268,18 +284,16 @@ namespace AZ bool MaterialFunctor::EditorContext::SetMaterialPropertySoftMinValue(const Name& propertyName, const MaterialPropertyValue& min) { - auto it = QueryMaterialMetadata(propertyName); - if (it == m_metadata.end()) + MaterialPropertyDynamicMetadata* metadata = QueryMaterialPropertyMetadata(propertyName); + if (!metadata) { return false; } - MaterialPropertyValue origin = it->second.m_propertyRange.m_softMin; - it->second.m_propertyRange.m_softMin = min; - - if (origin != min) + if (metadata->m_propertyRange.m_softMin != min) { - m_outChangedProperties.insert(propertyName); + metadata->m_propertyRange.m_softMin = min; + m_updatedPropertiesOut.insert(propertyName); } return true; @@ -293,18 +307,16 @@ namespace AZ bool MaterialFunctor::EditorContext::SetMaterialPropertySoftMaxValue(const Name& propertyName, const MaterialPropertyValue& max) { - auto it = QueryMaterialMetadata(propertyName); - if (it == m_metadata.end()) + MaterialPropertyDynamicMetadata* metadata = QueryMaterialPropertyMetadata(propertyName); + if (!metadata) { return false; } - MaterialPropertyValue origin = it->second.m_propertyRange.m_softMax; - it->second.m_propertyRange.m_softMax = max; - - if (origin != max) + if (metadata->m_propertyRange.m_softMax != max) { - m_outChangedProperties.insert(propertyName); + metadata->m_propertyRange.m_softMax = max; + m_updatedPropertiesOut.insert(propertyName); } return true; @@ -316,15 +328,26 @@ namespace AZ return SetMaterialPropertySoftMaxValue(name, max); } - AZStd::list_iterator> MaterialFunctor::EditorContext::QueryMaterialMetadata(const Name& propertyName) const + MaterialPropertyDynamicMetadata* MaterialFunctor::EditorContext::QueryMaterialPropertyMetadata(const Name& propertyName) const { - auto it = m_metadata.find(propertyName); - if (it == m_metadata.end()) + auto it = m_propertyMetadata.find(propertyName); + if (it == m_propertyMetadata.end()) { AZ_Error("MaterialFunctor", false, "Couldn't find metadata for material property: %s.", propertyName.GetCStr()); } - return it; + return &it->second; + } + + MaterialPropertyGroupDynamicMetadata* MaterialFunctor::EditorContext::QueryMaterialPropertyGroupMetadata(const Name& propertyGroupName) const + { + auto it = m_propertyGroupMetadata.find(propertyGroupName); + if (it == m_propertyGroupMetadata.end()) + { + AZ_Error("MaterialFunctor", false, "Couldn't find metadata for material property group: %s.", propertyGroupName.GetCStr()); + } + + return &it->second; } template diff --git a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Material/MaterialPropertyDescriptor.cpp b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Material/MaterialPropertyDescriptor.cpp index 367d95357b..72d658db63 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Material/MaterialPropertyDescriptor.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Material/MaterialPropertyDescriptor.cpp @@ -124,12 +124,6 @@ namespace AZ ->Value(ToString(MaterialPropertyOutputType::ShaderOption), MaterialPropertyOutputType::ShaderOption) ; - serializeContext->Enum() - ->Value("Enabled", MaterialPropertyVisibility::Enabled) - ->Value("Disabled", MaterialPropertyVisibility::Disabled) - ->Value("Hidden", MaterialPropertyVisibility::Hidden) - ; - serializeContext->Enum() ->Value(ToString(MaterialPropertyDataType::Invalid), MaterialPropertyDataType::Invalid) ->Value(ToString(MaterialPropertyDataType::Bool), MaterialPropertyDataType::Bool) @@ -153,14 +147,6 @@ namespace AZ ; } - if (auto* behaviorContext = azrtti_cast(context)) - { - behaviorContext - ->Enum<(int)MaterialPropertyVisibility::Enabled>("MaterialPropertyVisibility_Enabled") - ->Enum<(int)MaterialPropertyVisibility::Disabled>("MaterialPropertyVisibility_Disabled") - ->Enum<(int)MaterialPropertyVisibility::Hidden>("MaterialPropertyVisibility_Hidden"); - } - MaterialPropertyIndex::Reflect(context); } diff --git a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Shader/ShaderAsset.cpp b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Shader/ShaderAsset.cpp index fa791537e7..0a59772d6c 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Shader/ShaderAsset.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Shader/ShaderAsset.cpp @@ -32,6 +32,25 @@ namespace AZ const ShaderVariantStableId ShaderAsset::RootShaderVariantStableId{ 0 }; + uint32_t ShaderAsset::MakeAssetProductSubId(uint32_t rhiApiUniqueIndex, uint32_t subProductType) + { + static constexpr uint32_t RhiIndexBitPosition = 30; + static constexpr uint32_t RhiIndexNumBits = 32 - RhiIndexBitPosition; + static constexpr uint32_t RhiIndexMaxValue = (1 << RhiIndexNumBits) - 1; + + static constexpr uint32_t SubProductTypeBitPosition = 0; + static constexpr uint32_t SubProductTypeNumBits = RhiIndexBitPosition - SubProductTypeBitPosition; + static constexpr uint32_t SubProductTypeMaxValue = (1 << SubProductTypeNumBits) - 1; + + static_assert(RhiIndexMaxValue == RHI::Limits::APIType::PerPlatformApiUniqueIndexMax); + AZ_Assert(rhiApiUniqueIndex <= RhiIndexMaxValue, "Invalid rhiApiUniqueIndex [%u]", rhiApiUniqueIndex); + AZ_Assert(subProductType <= SubProductTypeMaxValue, "Invalid subProductType [%u]", subProductType); + + const uint32_t assetProductSubId = (rhiApiUniqueIndex << RhiIndexBitPosition) | + (subProductType << SubProductTypeBitPosition); + return assetProductSubId; + } + void ShaderAsset::ShaderApiDataContainer::Reflect(AZ::ReflectContext* context) { if (auto* serializeContext = azrtti_cast(context)) @@ -430,115 +449,5 @@ namespace AZ /////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////// - // Deprecated System - ////////////////////////////////////////////////////////////////////////// - - const char* ToString(ShaderStageType shaderStageType) - { - switch (shaderStageType) - { - case ShaderStageType::Vertex: return "Vertex"; - case ShaderStageType::Geometry: return "Geometry"; - case ShaderStageType::TessellationControl: return "TessellationControl"; - case ShaderStageType::TessellationEvaluation: return "TessellationEvaluation"; - case ShaderStageType::Fragment: return "Fragment"; - case ShaderStageType::Compute: return "Compute"; - case ShaderStageType::RayTracing: return "RayTracing"; - default: - AZ_Assert(false, "Unhandled type"); - return ""; - } - } - - void ReflectShaderStageType(ReflectContext* context) - { - if (auto* serializeContext = azrtti_cast(context)) - { - serializeContext->Enum() - ->Value(ToString(ShaderStageType::Vertex), ShaderStageType::Vertex) - ->Value(ToString(ShaderStageType::Geometry), ShaderStageType::Geometry) - ->Value(ToString(ShaderStageType::TessellationControl), ShaderStageType::TessellationControl) - ->Value(ToString(ShaderStageType::TessellationEvaluation), ShaderStageType::TessellationEvaluation) - ->Value(ToString(ShaderStageType::Fragment), ShaderStageType::Fragment) - ->Value(ToString(ShaderStageType::Compute), ShaderStageType::Compute) - ->Value(ToString(ShaderStageType::RayTracing), ShaderStageType::RayTracing) - ; - } - } - - ShaderAssetSubId ShaderStageToSubId(ShaderStageType stageType) - { - switch (stageType) - { - case RPI::ShaderStageType::Vertex: - return ShaderAssetSubId::AzVertexShader; - case RPI::ShaderStageType::Geometry: - return ShaderAssetSubId::AzGeometryShader; - case RPI::ShaderStageType::TessellationControl: - return ShaderAssetSubId::AzTessellationControlShader; - case RPI::ShaderStageType::TessellationEvaluation: - return ShaderAssetSubId::AzTessellationEvaluationShader; - case RPI::ShaderStageType::Fragment: - return ShaderAssetSubId::AzFragmentShader; - case RPI::ShaderStageType::Compute: - return ShaderAssetSubId::AzComputeShader; - case RPI::ShaderStageType::RayTracing: - return ShaderAssetSubId::AzRayTracingShader; - default: - AZ_Assert(false, "Trying to get a ShaderAssetSubId from an unknown ShaderStageType. Defaulting to a vertex shader."); - break; - } - - return ShaderAssetSubId::AzVertexShader; - } - void ShaderStageDescriptor::Reflect(ReflectContext* context) - { - if (auto* serializeContext = azrtti_cast(context)) - { - serializeContext->Class() - ->Version(1) - ->Field("m_stageType", &ShaderStageDescriptor::m_stageType) - ->Field("m_byteCode", &ShaderStageDescriptor::m_byteCode) - ; - } - } - - - /////////////////////////////////////////////////////////////////////// - // ShaderStageAsset - - void ShaderStageAsset::Reflect(ReflectContext* context) - { - ShaderStageDescriptor::Reflect(context); - - if (auto* serializeContext = azrtti_cast(context)) - { - serializeContext->Class() - ->Version(1) - ->Field("m_descriptor", &ShaderStageAsset::m_descriptor) - ->Field("m_srgLayouts", &ShaderStageAsset::m_srgLayouts) - ; - } - } - - ShaderStageAsset::ShaderStageAsset(const ShaderStageAsset& rhs) - { - *this = rhs; - } - - ShaderStageAsset::ShaderStageAsset(ShaderStageAsset&& rhs) - : m_descriptor(AZStd::move(rhs.m_descriptor)) - , m_srgLayouts(AZStd::move(rhs.m_srgLayouts)) - {} - - ShaderStageAsset& ShaderStageAsset::operator= (const ShaderStageAsset& rhs) - { - m_descriptor = rhs.m_descriptor; - m_srgLayouts = rhs.m_srgLayouts; - return *this; - } - /////////////////////////////////////////////////////////////////////// - } // namespace RPI } // namespace AZ diff --git a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Shader/ShaderAsset2.cpp b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Shader/ShaderAsset2.cpp new file mode 100644 index 0000000000..749f53aae7 --- /dev/null +++ b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Shader/ShaderAsset2.cpp @@ -0,0 +1,589 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ +#include + +#include +#include +#include + +#include + +#include +#include +#include +#include + +namespace AZ +{ + namespace RPI + { + const ShaderVariantStableId ShaderAsset2::RootShaderVariantStableId{0}; + + static constexpr uint32_t SubProductTypeBitPosition = 0; + static constexpr uint32_t SubProductTypeNumBits = SupervariantIndexBitPosition - SubProductTypeBitPosition; + static constexpr uint32_t SubProductTypeMaxValue = (1 << SubProductTypeNumBits) - 1; + + static_assert(RhiIndexMaxValue == RHI::Limits::APIType::PerPlatformApiUniqueIndexMax); + + uint32_t ShaderAsset2::MakeProductAssetSubId( + uint32_t rhiApiUniqueIndex, uint32_t supervariantIndex, uint32_t subProductType) + { + AZ_Assert(rhiApiUniqueIndex <= RhiIndexMaxValue, "Invalid rhiApiUniqueIndex [%u]", rhiApiUniqueIndex); + AZ_Assert(supervariantIndex <= SupervariantIndexMaxValue, "Invalid supervariantIndex [%u]", supervariantIndex); + AZ_Assert(subProductType <= SubProductTypeMaxValue, "Invalid subProductType [%u]", subProductType); + + const uint32_t assetProductSubId = (rhiApiUniqueIndex << RhiIndexBitPosition) | + (supervariantIndex << SupervariantIndexBitPosition) | (subProductType << SubProductTypeBitPosition); + return assetProductSubId; + } + + SupervariantIndex ShaderAsset2::GetSupervariantIndexFromProductAssetSubId(uint32_t assetProducSubId) + { + const uint32_t supervariantIndex = assetProducSubId >> SupervariantIndexBitPosition; + return SupervariantIndex{supervariantIndex & SupervariantIndexMaxValue}; + } + + SupervariantIndex ShaderAsset2::GetSupervariantIndexFromAssetId(const Data::AssetId& assetId) + { + return GetSupervariantIndexFromProductAssetSubId(assetId.m_subId); + } + + void ShaderAsset2::Supervariant::Reflect(AZ::ReflectContext* context) + { + if (auto* serializeContext = azrtti_cast(context)) + { + serializeContext->Class() + ->Version(1) + ->Field("Name", &Supervariant::m_name) + ->Field("SrgLayoutList", &Supervariant::m_srgLayoutList) + ->Field("PipelineLayout", &Supervariant::m_pipelineLayoutDescriptor) + ->Field("InputContract", &Supervariant::m_inputContract) + ->Field("OutputContract", &Supervariant::m_outputContract) + ->Field("RenderStates", &Supervariant::m_renderStates) + ->Field("AttributeMapList", &Supervariant::m_attributeMaps) + ->Field("RootVariantAsset", &Supervariant::m_rootShaderVariantAsset) + ; + } + } + + void ShaderAsset2::ShaderApiDataContainer::Reflect(AZ::ReflectContext* context) + { + if (auto* serializeContext = azrtti_cast(context)) + { + serializeContext->Class() + ->Version(1) + ->Field("APIType", &ShaderApiDataContainer::m_APIType) + ->Field("Supervariants", &ShaderApiDataContainer::m_supervariants) + ; + } + } + + void ShaderAsset2::Reflect(ReflectContext* context) + { + Supervariant::Reflect(context); + + ShaderApiDataContainer::Reflect(context); + + if (auto* serializeContext = azrtti_cast(context)) + { + serializeContext->Class() + ->Version(1) + ->Field("name", &ShaderAsset2::m_name) + ->Field("pipelineStateType", &ShaderAsset2::m_pipelineStateType) + ->Field("shaderOptionGroupLayout", &ShaderAsset2::m_shaderOptionGroupLayout) + ->Field("drawListName", &ShaderAsset2::m_drawListName) + ->Field("shaderAssetBuildTimestamp", &ShaderAsset2::m_shaderAssetBuildTimestamp) + ->Field("perAPIShaderData", &ShaderAsset2::m_perAPIShaderData) + ; + } + } + + ShaderAsset2::~ShaderAsset2() + { + Data::AssetBus::Handler::BusDisconnect(); + ShaderVariantFinderNotificationBus2::Handler::BusDisconnect(); + } + + const Name& ShaderAsset2::GetName() const + { + return m_name; + } + + RHI::PipelineStateType ShaderAsset2::GetPipelineStateType() const + { + return m_pipelineStateType; + } + + const ShaderOptionGroupLayout* ShaderAsset2::GetShaderOptionGroupLayout() const + { + AZ_Assert(m_shaderOptionGroupLayout, "m_shaderOptionGroupLayout is null"); + return m_shaderOptionGroupLayout.get(); + } + + const Name& ShaderAsset2::GetDrawListName() const + { + return m_drawListName; + } + + AZStd::sys_time_t ShaderAsset2::GetShaderAssetBuildTimestamp() const + { + return m_shaderAssetBuildTimestamp; + } + + void ShaderAsset2::SetReady() + { + m_status = AssetStatus::Ready; + } + + + SupervariantIndex ShaderAsset2::GetSupervariantIndex(const AZ::Name& supervariantName) const + { + const auto& supervariants = GetCurrentShaderApiData().m_supervariants; + const uint32_t supervariantCount = supervariants.size(); + for (uint32_t index = 0; index < supervariantCount; ++index) + { + if (supervariants[index].m_name == supervariantName) + { + return SupervariantIndex{index}; + } + } + return InvalidSupervariantIndex; + } + + + Data::Asset ShaderAsset2::GetVariant( + const ShaderVariantId& shaderVariantId, SupervariantIndex supervariantIndex) + { + AZ_PROFILE_FUNCTION(Debug::ProfileCategory::AzRender); + + auto variantFinder = AZ::Interface::Get(); + AZ_Assert(variantFinder, "The IShaderVariantFinder doesn't exist"); + + Data::Asset thisAsset(this, Data::AssetLoadBehavior::Default); + Data::Asset shaderVariantAsset = + variantFinder->GetShaderVariantAssetByVariantId(thisAsset, shaderVariantId, supervariantIndex); + if (!shaderVariantAsset) + { + variantFinder->QueueLoadShaderVariantAssetByVariantId(thisAsset, shaderVariantId, supervariantIndex); + } + return shaderVariantAsset; + } + + ShaderVariantSearchResult ShaderAsset2::FindVariantStableId(const ShaderVariantId& shaderVariantId) + { + AZ_PROFILE_FUNCTION(Debug::ProfileCategory::AzRender); + + uint32_t dynamicOptionCount = aznumeric_cast(GetShaderOptionGroupLayout()->GetShaderOptions().size()); + ShaderVariantSearchResult variantSearchResult{RootShaderVariantStableId, dynamicOptionCount }; + + if (!dynamicOptionCount) + { + // The shader has no options at all. There's nothing to search. + return variantSearchResult; + } + + auto variantFinder = AZ::Interface::Get(); + AZ_Assert(variantFinder, "The IShaderVariantFinder doesn't exist"); + + { + AZStd::shared_lock lock(m_variantTreeMutex); + if (m_shaderVariantTree) + { + return m_shaderVariantTree->FindVariantStableId(GetShaderOptionGroupLayout(), shaderVariantId); + } + } + + AZStd::unique_lock lock(m_variantTreeMutex); + if (!m_shaderVariantTree) + { + m_shaderVariantTree = variantFinder->GetShaderVariantTreeAsset(GetId()); + if (!m_shaderVariantTree) + { + if (!m_shaderVariantTreeLoadWasRequested) + { + variantFinder->QueueLoadShaderVariantTreeAsset(GetId()); + m_shaderVariantTreeLoadWasRequested = true; + } + + // The variant tree could be under construction or simply doesn't exist at all. + return variantSearchResult; + } + } + return m_shaderVariantTree->FindVariantStableId(GetShaderOptionGroupLayout(), shaderVariantId); + } + + Data::Asset ShaderAsset2::GetVariant( + ShaderVariantStableId shaderVariantStableId, SupervariantIndex supervariantIndex) const + { + if (!shaderVariantStableId.IsValid() || shaderVariantStableId == RootShaderVariantStableId) + { + return GetRootVariant(supervariantIndex); + } + + auto variantFinder = AZ::Interface::Get(); + AZ_Assert(variantFinder, "No Variant Finder For shaderAsset with name [%s] and stableId [%u]", GetName().GetCStr(), shaderVariantStableId.GetIndex()); + Data::Asset variant = + variantFinder->GetShaderVariantAsset(m_shaderVariantTree.GetId(), shaderVariantStableId, supervariantIndex); + if (!variant.IsReady()) + { + // Enqueue a request to load the variant, next time around the caller will get the asset. + Data::AssetId variantTreeAssetId; + { + AZStd::shared_lock lock(m_variantTreeMutex); + if (m_shaderVariantTree) + { + variantTreeAssetId = m_shaderVariantTree.GetId(); + } + } + if (variantTreeAssetId.IsValid()) + { + variantFinder->QueueLoadShaderVariantAsset(variantTreeAssetId, shaderVariantStableId, supervariantIndex); + } + return GetRootVariant(supervariantIndex); + } + else if (variant->GetBuildTimestamp() >= m_shaderAssetBuildTimestamp) + { + return variant; + } + else + { + // When rebuilding shaders we may be in a state where the ShaderAsset2 and root ShaderVariantAsset have been rebuilt and reloaded, but some (or all) + // shader variants haven't been built yet. Since we want to use the latest version of the shader code, ignore the old variants and fall back to the newer root variant instead. + AZ_Warning("ShaderAsset2", false, "ShaderAsset2 and ShaderVariantAsset are out of sync; defaulting to root shader variant. (This is common while reloading shaders)."); + return GetRootVariant(supervariantIndex); + } + } + + Data::Asset ShaderAsset2::GetRootVariant(SupervariantIndex supervariantIndex) const + { + auto supervariant = GetSupervariant(supervariantIndex); + if (!supervariant) + { + return Data::Asset(); + } + return supervariant->m_rootShaderVariantAsset; + } + + const RHI::Ptr ShaderAsset2::FindShaderResourceGroupLayout( + const Name& shaderResourceGroupName, SupervariantIndex supervariantIndex) const + { + auto supervariant = GetSupervariant(supervariantIndex); + if (!supervariant) + { + return nullptr; + } + const auto& srgLayoutList = supervariant->m_srgLayoutList; + const auto findIt = AZStd::find_if(srgLayoutList.begin(), srgLayoutList.end(), [&](const RHI::Ptr& layout) + { + return layout->GetName() == shaderResourceGroupName; + }); + + if (findIt != srgLayoutList.end()) + { + return *findIt; + } + + return nullptr; + } + + const RHI::Ptr ShaderAsset2::FindShaderResourceGroupLayout( + uint32_t bindingSlot, SupervariantIndex supervariantIndex) const + { + auto supervariant = GetSupervariant(supervariantIndex); + if (!supervariant) + { + return nullptr; + } + const auto& srgLayoutList = supervariant->m_srgLayoutList; + const auto findIt = + AZStd::find_if(srgLayoutList.begin(), srgLayoutList.end(), [&](const RHI::Ptr& layout) + { + return layout && layout->GetBindingSlot() == bindingSlot; + }); + + if (findIt != srgLayoutList.end()) + { + return *findIt; + } + + return nullptr; + } + + const RHI::Ptr ShaderAsset2::FindFallbackShaderResourceGroupLayout( + SupervariantIndex supervariantIndex) const + { + auto supervariant = GetSupervariant(supervariantIndex); + if (!supervariant) + { + return nullptr; + } + const auto& srgLayoutList = supervariant->m_srgLayoutList; + const auto findIt = + AZStd::find_if(srgLayoutList.begin(), srgLayoutList.end(), [&](const RHI::Ptr& layout) + { + return layout && layout->HasShaderVariantKeyFallbackEntry(); + }); + + if (findIt != srgLayoutList.end()) + { + return *findIt; + } + + return nullptr; + } + + AZStd::array_view> ShaderAsset2::GetShaderResourceGroupLayouts( + SupervariantIndex supervariantIndex) const + { + auto supervariant = GetSupervariant(supervariantIndex); + if (!supervariant) + { + return {}; + } + return supervariant->m_srgLayoutList; + } + + + const RHI::Ptr ShaderAsset2::GetDrawSrgLayout(SupervariantIndex supervariantIndex) const + { + return FindShaderResourceGroupLayout(SrgBindingSlot::Draw, supervariantIndex); + } + + const ShaderInputContract& ShaderAsset2::GetInputContract(SupervariantIndex supervariantIndex) const + { + auto supervariant = GetSupervariant(supervariantIndex); + return supervariant->m_inputContract; + } + + const ShaderOutputContract& ShaderAsset2::GetOutputContract(SupervariantIndex supervariantIndex) const + { + auto supervariant = GetSupervariant(supervariantIndex); + return supervariant->m_outputContract; + } + + const RHI::RenderStates& ShaderAsset2::GetRenderStates(SupervariantIndex supervariantIndex) const + { + auto supervariant = GetSupervariant(supervariantIndex); + return supervariant->m_renderStates; + } + + const RHI::PipelineLayoutDescriptor* ShaderAsset2::GetPipelineLayoutDescriptor(SupervariantIndex supervariantIndex) const + { + auto supervariant = GetSupervariant(supervariantIndex); + if (!supervariant) + { + return nullptr; + } + AZ_Assert(supervariant->m_pipelineLayoutDescriptor, "m_pipelineLayoutDescriptor is null"); + return supervariant->m_pipelineLayoutDescriptor.get(); + } + + AZStd::optional ShaderAsset2::GetAttribute(const RHI::ShaderStage& shaderStage, const Name& attributeName, + SupervariantIndex supervariantIndex) const + { + auto supervariant = GetSupervariant(supervariantIndex); + if (!supervariant) + { + return AZStd::nullopt; + } + const auto stageIndex = static_cast(shaderStage); + AZ_Assert(stageIndex < RHI::ShaderStageCount, "Invalid shader stage specified!"); + + const auto& attributeMaps = supervariant->m_attributeMaps; + const auto& attrPair = attributeMaps[stageIndex].find(attributeName); + if (attrPair == attributeMaps[stageIndex].end()) + { + return AZStd::nullopt; + } + + return attrPair->second; + } + + ShaderAsset2::ShaderApiDataContainer& ShaderAsset2::GetCurrentShaderApiData() + { + const size_t perApiShaderDataCount = m_perAPIShaderData.size(); + AZ_Assert(perApiShaderDataCount > 0, "Invalid m_perAPIShaderData"); + + if (m_currentAPITypeIndex < perApiShaderDataCount) + { + return m_perAPIShaderData[m_currentAPITypeIndex]; + } + + // We may only endup here when running in a Builder context. + return m_perAPIShaderData[0]; + } + + const ShaderAsset2::ShaderApiDataContainer& ShaderAsset2::GetCurrentShaderApiData() const + { + const size_t perApiShaderDataCount = m_perAPIShaderData.size(); + AZ_Assert(perApiShaderDataCount > 0, "Invalid m_perAPIShaderData"); + + if (m_currentAPITypeIndex < perApiShaderDataCount) + { + return m_perAPIShaderData[m_currentAPITypeIndex]; + } + + // We may only endup here when running in a Builder context. + return m_perAPIShaderData[0]; + } + + ShaderAsset2::Supervariant* ShaderAsset2::GetSupervariant(SupervariantIndex supervariantIndex) + { + auto& supervariants = GetCurrentShaderApiData().m_supervariants; + auto index = supervariantIndex.GetIndex(); + if (index >= supervariants.size()) + { + AZ_Error( + "ShaderAsset2", false, "Supervariant index = %u is invalid because there are only %zu supervariants", index, + supervariants.size()); + return nullptr; + } + + return &supervariants[index]; + } + + const ShaderAsset2::Supervariant* ShaderAsset2::GetSupervariant(SupervariantIndex supervariantIndex) const + { + const auto& supervariants = GetCurrentShaderApiData().m_supervariants; + auto index = supervariantIndex.GetIndex(); + if (index >= supervariants.size()) + { + AZ_Error( + "ShaderAsset2", false, "Supervariant index = %u is invalid because there are only %zu supervariants", index, + supervariants.size()); + return nullptr; + } + + return &supervariants[index]; + } + + bool ShaderAsset2::FinalizeAfterLoad() + { + // Use the current RHI that is active to select which shader data to use. + // We don't assert if the Factory is not available because this method could be called during build time, + // when no Factory is available. Some assets (like the material asset) need to load the ShaderAsset2 + // in order to get some non API specific data (like a ShaderResourceGroup) during their build + // process. If they try to access any RHI API specific data, an assert will be trigger because the + // correct API index will not set. + if (RHI::Factory::IsReady()) + { + auto rhiType = RHI::Factory::Get().GetType(); + auto findIt = AZStd::find_if(m_perAPIShaderData.begin(), m_perAPIShaderData.end(), [&rhiType](const auto& shaderData) + { + return shaderData.m_APIType == rhiType; + }); + + if (findIt != m_perAPIShaderData.end()) + { + m_currentAPITypeIndex = AZStd::distance(m_perAPIShaderData.begin(), findIt); + } + else + { + AZ_Error("ShaderAsset2", false, "Could not find shader for API %s in shader %s", RHI::Factory::Get().GetName().GetCStr(), GetName().GetCStr()); + return false; + } + } + + // Common finalize check + for (const auto& shaderApiData : m_perAPIShaderData) + { + const auto& supervariants = shaderApiData.m_supervariants; + for (const auto& supervariant : supervariants) + { + bool beTrue = supervariant.m_attributeMaps.size() == RHI::ShaderStageCount; + if (!beTrue) + { + AZ_Error("ShaderAsset2", false, "Unexpected number of shader stages at supervariant with name [%s]!", supervariant.m_name.GetCStr()); + return false; + } + } + } + + // Once the ShaderAsset2 is loaded, it is necessary to listen for changes in the Root Variant Asset. + Data::AssetBus::Handler::BusConnect(GetRootVariant().GetId()); + ShaderVariantFinderNotificationBus2::Handler::BusConnect(GetId()); + + return true; + } + + /////////////////////////////////////////////////////////////////////// + // AssetBus overrides... + void ShaderAsset2::OnAssetReloaded(Data::Asset asset) + { + ShaderReloadDebugTracker::ScopedSection reloadSection("ShaderAsset2::OnAssetReloaded %s", asset.GetHint().c_str()); + + Data::Asset shaderVariantAsset = { asset.GetAs(), AZ::Data::AssetLoadBehavior::PreLoad }; + AZ_Assert(shaderVariantAsset->GetStableId() == RootShaderVariantStableId, + "Was expecting to update the root variant"); + SupervariantIndex supervariantIndex = GetSupervariantIndexFromAssetId(asset.GetId()); + GetCurrentShaderApiData().m_supervariants[supervariantIndex.GetIndex()].m_rootShaderVariantAsset = asset; + + ShaderReloadNotificationBus2::Event(GetId(), &ShaderReloadNotificationBus2::Events::OnShaderAssetReinitialized, Data::Asset{ this, AZ::Data::AssetLoadBehavior::PreLoad } ); + } + /////////////////////////////////////////////////////////////////////// + + /////////////////////////////////////////////////////////////////// + /// ShaderVariantFinderNotificationBus2 overrides + void ShaderAsset2::OnShaderVariantTreeAssetReady(Data::Asset shaderVariantTreeAsset, bool isError) + { + ShaderReloadDebugTracker::ScopedSection reloadSection("ShaderAsset2::OnShaderVariantTreeAssetReady %s", shaderVariantTreeAsset.GetHint().c_str()); + + AZStd::unique_lock lock(m_variantTreeMutex); + if (isError) + { + m_shaderVariantTree = {}; //This will force to attempt to reload later. + m_shaderVariantTreeLoadWasRequested = false; + } + else + { + m_shaderVariantTree = shaderVariantTreeAsset; + } + lock.unlock(); + ShaderReloadNotificationBus2::Event(GetId(), &ShaderReloadNotificationBus2::Events::OnShaderAssetReinitialized, Data::Asset{ this, AZ::Data::AssetLoadBehavior::PreLoad }); + } + + /////////////////////////////////////////////////////////////////// + + + /////////////////////////////////////////////////////////////////////// + // ShaderAssetHandler + + Data::AssetHandler::LoadResult ShaderAssetHandler2::LoadAssetData( + const Data::Asset& asset, + AZStd::shared_ptr stream, + const Data::AssetFilterCB& assetLoadFilterCB) + { + if (Base::LoadAssetData(asset, stream, assetLoadFilterCB) == Data::AssetHandler::LoadResult::LoadComplete) + { + return PostLoadInit(asset); + } + return Data::AssetHandler::LoadResult::Error; + } + + Data::AssetHandler::LoadResult ShaderAssetHandler2::PostLoadInit(const Data::Asset& asset) + { + if (ShaderAsset2* shaderAsset = asset.GetAs()) + { + if (!shaderAsset->FinalizeAfterLoad()) + { + AZ_Error("ShaderAssetHandler", false, "Shader asset failed to finalize."); + return Data::AssetHandler::LoadResult::Error; + } + return Data::AssetHandler::LoadResult::LoadComplete; + } + return Data::AssetHandler::LoadResult::Error; + } + + /////////////////////////////////////////////////////////////////////// + + } // namespace RPI +} // namespace AZ diff --git a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Shader/ShaderAssetCreator2.cpp b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Shader/ShaderAssetCreator2.cpp new file mode 100644 index 0000000000..af8340a343 --- /dev/null +++ b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Shader/ShaderAssetCreator2.cpp @@ -0,0 +1,404 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#include + +namespace AZ +{ + namespace RPI + { + void ShaderAssetCreator2::Begin(const Data::AssetId& assetId) + { + BeginCommon(assetId); + } + + void ShaderAssetCreator2::SetShaderAssetBuildTimestamp(AZStd::sys_time_t shaderAssetBuildTimestamp) + { + if (ValidateIsReady()) + { + m_asset->m_shaderAssetBuildTimestamp = shaderAssetBuildTimestamp; + } + } + + void ShaderAssetCreator2::SetName(const Name& name) + { + if (ValidateIsReady()) + { + m_asset->m_name = name; + } + } + + void ShaderAssetCreator2::SetDrawListName(const Name& name) + { + if (ValidateIsReady()) + { + m_asset->m_drawListName = name; + } + } + + void ShaderAssetCreator2::SetShaderOptionGroupLayout(const Ptr& shaderOptionGroupLayout) + { + if (ValidateIsReady()) + { + m_asset->m_shaderOptionGroupLayout = shaderOptionGroupLayout; + } + } + + void ShaderAssetCreator2::BeginAPI(RHI::APIType type) + { + if (ValidateIsReady()) + { + ShaderAsset2::ShaderApiDataContainer shaderData; + shaderData.m_APIType = type; + m_asset->m_currentAPITypeIndex = m_asset->m_perAPIShaderData.size(); + m_asset->m_perAPIShaderData.push_back(shaderData); + } + } + + void ShaderAssetCreator2::BeginSupervariant(const Name& name) + { + if (!ValidateIsReady()) + { + return; + } + + if (m_currentSupervariant) + { + ReportError("Call EndSupervariant() before calling BeginSupervariant again."); + return; + } + + if (m_asset->m_currentAPITypeIndex == ShaderAsset2::InvalidAPITypeIndex) + { + ReportError("Can not begin supervariant with name [%s] because this function must be called between BeginAPI()/EndAPI()", name.GetCStr()); + return; + } + + if (m_asset->m_perAPIShaderData.empty()) + { + ReportError("Can not add supervariant with name [%s] because there's no per API shader data", name.GetCStr()); + return; + } + + ShaderAsset2::ShaderApiDataContainer& perAPIShaderData = m_asset->m_perAPIShaderData[m_asset->m_perAPIShaderData.size() - 1]; + if (perAPIShaderData.m_supervariants.empty()) + { + if (!name.IsEmpty()) + { + ReportError("The first supervariant must be nameless. Name [%s] is invalid", name.GetCStr()); + return; + } + } + else + { + if (name.IsEmpty()) + { + ReportError( + "Only the first supervariant can be nameless. So far there are %zu supervariants", + perAPIShaderData.m_supervariants.size()); + return; + } + } + + perAPIShaderData.m_supervariants.push_back({}); + m_currentSupervariant = &perAPIShaderData.m_supervariants[perAPIShaderData.m_supervariants.size() - 1]; + m_currentSupervariant->m_name = name; + } + + void ShaderAssetCreator2::SetSrgLayoutList(const ShaderResourceGroupLayoutList& srgLayoutList) + { + if (!ValidateIsReady()) + { + return; + } + + if (!m_currentSupervariant) + { + ReportError("BeginSupervariant() should be called first before calling %s", __FUNCTION__); + return; + } + + m_currentSupervariant->m_srgLayoutList = srgLayoutList; + for (auto srgLayout : m_currentSupervariant->m_srgLayoutList) + { + if (!srgLayout->Finalize()) + { + ReportError( + "The current supervariant [%s], failed to finalize SRG Layout [%s]", m_currentSupervariant->m_name.GetCStr(), + srgLayout->GetName().GetCStr()); + return; + } + } + } + + //! [Required] Assigns the pipeline layout descriptor shared by all variants in the shader. Shader variants + //! embedded in a single shader asset are required to use the same pipeline layout. It is not necessary to call + //! Finalize() on the pipeline layout prior to assignment, but still permitted. + void ShaderAssetCreator2::SetPipelineLayout(RHI::Ptr pipelineLayoutDescriptor) + { + if (!ValidateIsReady()) + { + return; + } + if (!m_currentSupervariant) + { + ReportError("BeginSupervariant() should be called first before calling %s", __FUNCTION__); + return; + } + if (m_currentSupervariant->m_srgLayoutList.empty()) + { + ReportError( + "Before setting the pipeline layout, the supervariant [%s] needs the SRG layouts", + m_currentSupervariant->m_name.GetCStr()); + return; + } + m_currentSupervariant->m_pipelineLayoutDescriptor = pipelineLayoutDescriptor; + } + + //! Assigns the contract for inputs required by the shader. + void ShaderAssetCreator2::SetInputContract(const ShaderInputContract& contract) + { + if (!ValidateIsReady()) + { + return; + } + if (!m_currentSupervariant) + { + ReportError("BeginSupervariant() should be called first before calling %s", __FUNCTION__); + return; + } + m_currentSupervariant->m_inputContract = contract; + } + + //! Assigns the contract for outputs required by the shader. + void ShaderAssetCreator2::SetOutputContract(const ShaderOutputContract& contract) + { + if (!ValidateIsReady()) + { + return; + } + if (!m_currentSupervariant) + { + ReportError("BeginSupervariant() should be called first before calling %s", __FUNCTION__); + return; + } + m_currentSupervariant->m_outputContract = contract; + } + + //! Assigns the render states for the draw pipeline. Ignored for non-draw pipelines. + void ShaderAssetCreator2::SetRenderStates(const RHI::RenderStates& renderStates) + { + if (!ValidateIsReady()) + { + return; + } + if (!m_currentSupervariant) + { + ReportError("BeginSupervariant() should be called first before calling %s", __FUNCTION__); + return; + } + m_currentSupervariant->m_renderStates = renderStates; + } + + //! [Optional] Not all shaders have attributes before functions. Some attributes do not exist for all RHI::APIType either. + void ShaderAssetCreator2::SetShaderStageAttributeMapList(const RHI::ShaderStageAttributeMapList& shaderStageAttributeMapList) + { + if (!ValidateIsReady()) + { + return; + } + if (!m_currentSupervariant) + { + ReportError("BeginSupervariant() should be called first before calling %s", __FUNCTION__); + return; + } + m_currentSupervariant->m_attributeMaps = shaderStageAttributeMapList; + } + + //! [Required] There's always a root variant for each supervariant. + void ShaderAssetCreator2::SetRootShaderVariantAsset(Data::Asset shaderVariantAsset) + { + if (!ValidateIsReady()) + { + return; + } + if (!m_currentSupervariant) + { + ReportError("BeginSupervariant() should be called first before calling %s", __FUNCTION__); + return; + } + m_currentSupervariant->m_rootShaderVariantAsset = shaderVariantAsset; + } + + static RHI::PipelineStateType GetPipelineStateType(const Data::Asset& shaderVariantAsset) + { + if (shaderVariantAsset->GetShaderStageFunction(RHI::ShaderStage::Vertex) || + shaderVariantAsset->GetShaderStageFunction(RHI::ShaderStage::Tessellation) || + shaderVariantAsset->GetShaderStageFunction(RHI::ShaderStage::Fragment)) + { + return RHI::PipelineStateType::Draw; + } + + if (shaderVariantAsset->GetShaderStageFunction(RHI::ShaderStage::Compute)) + { + return RHI::PipelineStateType::Dispatch; + } + + if (shaderVariantAsset->GetShaderStageFunction(RHI::ShaderStage::RayTracing)) + { + return RHI::PipelineStateType::RayTracing; + } + + return RHI::PipelineStateType::Count; + } + + bool ShaderAssetCreator2::EndSupervariant() + { + if (!ValidateIsReady()) + { + return false; + } + + if (!m_currentSupervariant) + { + ReportError("Can not end a supervariant that has not started"); + return false; + } + + if (!m_currentSupervariant->m_rootShaderVariantAsset.IsReady()) + { + ReportError( + "The current supervariant [%s], is missing the root ShaderVariantAsset", m_currentSupervariant->m_name.GetCStr()); + return false; + } + + // Supervariant specific resources + if (m_currentSupervariant->m_pipelineLayoutDescriptor) + { + if (!m_currentSupervariant->m_pipelineLayoutDescriptor->IsFinalized()) + { + if (m_currentSupervariant->m_pipelineLayoutDescriptor->Finalize() != RHI::ResultCode::Success) + { + ReportError("Failed to finalize pipeline layout descriptor."); + return false; + } + } + } + else + { + ReportError("PipelineLayoutDescriptor not specified."); + return false; + } + + const ShaderInputContract& shaderInputContract = m_currentSupervariant->m_inputContract; + // Validate that each stream ID appears only once. + for (const auto& channel : shaderInputContract.m_streamChannels) + { + int count = 0; + + for (const auto& searchChannel : shaderInputContract.m_streamChannels) + { + if (channel.m_semantic == searchChannel.m_semantic) + { + ++count; + } + } + + if (count > 1) + { + ReportError( + "Input stream channel [%s] appears multiple times. For supervariant with name [%s]", + channel.m_semantic.ToString().c_str(), m_currentSupervariant->m_name.GetCStr()); + return false; + } + } + + auto pipelineStateType = GetPipelineStateType(m_currentSupervariant->m_rootShaderVariantAsset); + if (pipelineStateType == RHI::PipelineStateType::Count) + { + ReportError("Invalid pipelineStateType for supervariant [%s]", m_currentSupervariant->m_name.GetCStr()); + return false; + } + + + if (m_currentSupervariant->m_name.IsEmpty()) + { + m_asset->m_pipelineStateType = pipelineStateType; + } + else + { + if (m_asset->m_pipelineStateType != pipelineStateType) + { + ReportError("All supervariants must be of the same pipelineStateType. Current pipelineStateType is [%d], but for supervariant [%s] the pipelineStateType is [%d]", + m_asset->m_pipelineStateType, m_currentSupervariant->m_name.GetCStr(), pipelineStateType); + return false; + } + } + + m_currentSupervariant = nullptr; + return true; + } + + bool ShaderAssetCreator2::EndAPI() + { + if (!ValidateIsReady()) + { + return false; + } + if (m_currentSupervariant) + { + ReportError("EndSupervariant() must be called before calling EndAPI()"); + return false; + } + + m_asset->m_currentAPITypeIndex = ShaderAsset2::InvalidAPITypeIndex; + return true; + } + + bool ShaderAssetCreator2::End(Data::Asset& shaderAsset) + { + if (!ValidateIsReady()) + { + return false; + } + + if (m_asset->m_perAPIShaderData.empty()) + { + ReportError("Empty shader data. Check that a valid RHI is enabled for this platform."); + return false; + } + + if (!m_asset->FinalizeAfterLoad()) + { + ReportError("Failed to finalize the ShaderAsset2."); + return false; + } + + m_asset->SetReady(); + + return EndCommon(shaderAsset); + } + + void ShaderAssetCreator2::Clone(const Data::AssetId& assetId, const ShaderAsset2& sourceShaderAsset) + { + BeginCommon(assetId); + + m_asset->m_name = sourceShaderAsset.m_name; + m_asset->m_pipelineStateType = sourceShaderAsset.m_pipelineStateType; + m_asset->m_drawListName = sourceShaderAsset.m_drawListName; + m_asset->m_shaderOptionGroupLayout = sourceShaderAsset.m_shaderOptionGroupLayout; + m_asset->m_shaderAssetBuildTimestamp = sourceShaderAsset.m_shaderAssetBuildTimestamp; + m_asset->m_perAPIShaderData = sourceShaderAsset.m_perAPIShaderData; + + } + } // namespace RPI +} // namespace AZ diff --git a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Shader/ShaderStageType.cpp b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Shader/ShaderStageType.cpp new file mode 100644 index 0000000000..845966708a --- /dev/null +++ b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Shader/ShaderStageType.cpp @@ -0,0 +1,54 @@ +/* +* 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 + +namespace AZ +{ + namespace RPI + { + const char* ToString(ShaderStageType shaderStageType) + { + switch (shaderStageType) + { + case ShaderStageType::Vertex: return "Vertex"; + case ShaderStageType::Geometry: return "Geometry"; + case ShaderStageType::TessellationControl: return "TessellationControl"; + case ShaderStageType::TessellationEvaluation: return "TessellationEvaluation"; + case ShaderStageType::Fragment: return "Fragment"; + case ShaderStageType::Compute: return "Compute"; + case ShaderStageType::RayTracing: return "RayTracing"; + default: + AZ_Assert(false, "Unhandled type"); + return ""; + } + } + + void ReflectShaderStageType(ReflectContext* context) + { + if (auto* serializeContext = azrtti_cast(context)) + { + serializeContext->Enum() + ->Value(ToString(ShaderStageType::Vertex), ShaderStageType::Vertex) + ->Value(ToString(ShaderStageType::Geometry), ShaderStageType::Geometry) + ->Value(ToString(ShaderStageType::TessellationControl), ShaderStageType::TessellationControl) + ->Value(ToString(ShaderStageType::TessellationEvaluation), ShaderStageType::TessellationEvaluation) + ->Value(ToString(ShaderStageType::Fragment), ShaderStageType::Fragment) + ->Value(ToString(ShaderStageType::Compute), ShaderStageType::Compute) + ->Value(ToString(ShaderStageType::RayTracing), ShaderStageType::RayTracing) + ; + } + } + + } // namespace RPI +} // namespace AZ diff --git a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Shader/ShaderVariantAsset.cpp b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Shader/ShaderVariantAsset.cpp index 3b52dda326..7864768351 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Shader/ShaderVariantAsset.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Shader/ShaderVariantAsset.cpp @@ -21,6 +21,34 @@ namespace AZ { namespace RPI { + uint32_t ShaderVariantAsset::MakeAssetProductSubId( + uint32_t rhiApiUniqueIndex, ShaderVariantStableId variantStableId, uint32_t subProductType) + { + static constexpr uint32_t RhiIndexBitPosition = 30; + static constexpr uint32_t RhiIndexNumBits = 32 - RhiIndexBitPosition; + static constexpr uint32_t RhiIndexMaxValue = (1 << RhiIndexNumBits) - 1; + + static constexpr uint32_t SubProductTypeBitPosition = 17; + static constexpr uint32_t SubProductTypeNumBits = RhiIndexBitPosition - SubProductTypeBitPosition; + static constexpr uint32_t SubProductTypeMaxValue = (1 << SubProductTypeNumBits) - 1; + + static constexpr uint32_t StableIdBitPosition = 0; + static constexpr uint32_t StableIdNumBits = SubProductTypeBitPosition - StableIdBitPosition; + static constexpr uint32_t StableIdMaxValue = (1 << StableIdNumBits) - 1; + + static_assert(RhiIndexMaxValue == RHI::Limits::APIType::PerPlatformApiUniqueIndexMax); + + // The 2 Most significant bits encode the the RHI::API unique index. + AZ_Assert(rhiApiUniqueIndex <= RhiIndexMaxValue, "Invalid rhiApiUniqueIndex [%u]", rhiApiUniqueIndex); + AZ_Assert(subProductType <= SubProductTypeMaxValue, "Invalid subProductType [%u]", subProductType); + AZ_Assert(variantStableId.GetIndex() <= StableIdMaxValue, "Invalid variantStableId [%u]", variantStableId.GetIndex()); + + const uint32_t assetProductSubId = (rhiApiUniqueIndex << RhiIndexBitPosition) | + (subProductType << SubProductTypeBitPosition) | + (variantStableId.GetIndex() << StableIdBitPosition); + return assetProductSubId; + } + void ShaderVariantAsset::Reflect(ReflectContext* context) { if (auto* serializeContext = azrtti_cast(context)) @@ -44,16 +72,6 @@ namespace AZ return m_shaderAssetBuildTimestamp; } - uint32_t ShaderVariantAsset::GetAssetSubId(uint32_t rhiApiUniqueIndex, ShaderVariantStableId variantStableId) - { - //The 2 Most significant bits encode the the RHI::API unique index. - AZ_Assert(rhiApiUniqueIndex <= RHI::Limits::APIType::PerPlatformApiUniqueIndexMax, "Invalid rhiApiUniqueIndex [%u]", rhiApiUniqueIndex); - AZ_Assert(variantStableId != RootShaderVariantStableId, "The product subId for the root variant is built differently."); - const uint32_t rhiApiSubId = rhiApiUniqueIndex << 30; - const uint32_t productSubId = rhiApiSubId | variantStableId.GetIndex(); - return productSubId; - } - const RHI::ShaderStageFunction* ShaderVariantAsset::GetShaderStageFunction(RHI::ShaderStage shaderStage) const { return m_functionsByStage[static_cast(shaderStage)].get(); diff --git a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Shader/ShaderVariantAsset2.cpp b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Shader/ShaderVariantAsset2.cpp new file mode 100644 index 0000000000..34daff560a --- /dev/null +++ b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Shader/ShaderVariantAsset2.cpp @@ -0,0 +1,114 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ +#include + +#include +#include +#include + +#include +#include +#include + +namespace AZ +{ + namespace RPI + { + uint32_t ShaderVariantAsset2::MakeAssetProductSubId( + uint32_t rhiApiUniqueIndex, uint32_t supervariantIndex, ShaderVariantStableId variantStableId, uint32_t subProductType) + { + static constexpr uint32_t SubProductTypeBitPosition = 17; + static constexpr uint32_t SubProductTypeNumBits = SupervariantIndexBitPosition - SubProductTypeBitPosition; + static constexpr uint32_t SubProductTypeMaxValue = (1 << SubProductTypeNumBits) - 1; + + static constexpr uint32_t StableIdBitPosition = 0; + static constexpr uint32_t StableIdNumBits = SubProductTypeBitPosition - StableIdBitPosition; + static constexpr uint32_t StableIdMaxValue = (1 << StableIdNumBits) - 1; + + static_assert(RhiIndexMaxValue == RHI::Limits::APIType::PerPlatformApiUniqueIndexMax); + + // The 2 Most significant bits encode the the RHI::API unique index. + AZ_Assert(rhiApiUniqueIndex <= RhiIndexMaxValue, "Invalid rhiApiUniqueIndex [%u]", rhiApiUniqueIndex); + AZ_Assert(supervariantIndex <= SupervariantIndexMaxValue, "Invalid supervariantIndex [%u]", supervariantIndex); + AZ_Assert(subProductType <= SubProductTypeMaxValue, "Invalid subProductType [%u]", subProductType); + AZ_Assert(variantStableId.GetIndex() <= StableIdMaxValue, "Invalid variantStableId [%u]", variantStableId.GetIndex()); + + const uint32_t assetProductSubId = (rhiApiUniqueIndex << RhiIndexBitPosition) | + (supervariantIndex << SupervariantIndexBitPosition) | (subProductType << SubProductTypeBitPosition) | + (variantStableId.GetIndex() << StableIdBitPosition); + return assetProductSubId; + } + + void ShaderVariantAsset2::Reflect(ReflectContext* context) + { + if (auto* serializeContext = azrtti_cast(context)) + { + serializeContext->Class() + ->Version(1) + ->Field("StableId", &ShaderVariantAsset2::m_stableId) + ->Field("ShaderVariantId", &ShaderVariantAsset2::m_shaderVariantId) + ->Field("IsFullyBaked", &ShaderVariantAsset2::m_isFullyBaked) + ->Field("FunctionsByStage", &ShaderVariantAsset2::m_functionsByStage) + ->Field("BuildTimestamp", &ShaderVariantAsset2::m_buildTimestamp) + ; + } + } + + AZStd::sys_time_t ShaderVariantAsset2::GetBuildTimestamp() const + { + return m_buildTimestamp; + } + + const RHI::ShaderStageFunction* ShaderVariantAsset2::GetShaderStageFunction(RHI::ShaderStage shaderStage) const + { + return m_functionsByStage[static_cast(shaderStage)].get(); + } + + bool ShaderVariantAsset2::IsFullyBaked() const + { + return m_isFullyBaked; + } + + void ShaderVariantAsset2::SetReady() + { + m_status = AssetStatus::Ready; + } + + bool ShaderVariantAsset2::FinalizeAfterLoad() + { + return true; + } + + ShaderVariantAssetHandler2::LoadResult ShaderVariantAssetHandler2::LoadAssetData(const Data::Asset& asset, AZStd::shared_ptr stream, const AZ::Data::AssetFilterCB& assetLoadFilterCB) + { + if (Base::LoadAssetData(asset, stream, assetLoadFilterCB) == LoadResult::LoadComplete) + { + return PostLoadInit(asset) ? LoadResult::LoadComplete : LoadResult::Error; + } + return LoadResult::Error; + } + + bool ShaderVariantAssetHandler2::PostLoadInit(const Data::Asset& asset) + { + if (ShaderVariantAsset2* shaderVariantAsset = asset.GetAs()) + { + if (!shaderVariantAsset->FinalizeAfterLoad()) + { + AZ_Error("ShaderVariantAssetHandler", false, "Shader asset failed to finalize."); + return false; + } + return true; + } + return false; + } + } // namespace RPI +} // namespace AZ diff --git a/Gems/Atom/RPI/Code/Tests/Material/LuaMaterialFunctorTests.cpp b/Gems/Atom/RPI/Code/Tests/Material/LuaMaterialFunctorTests.cpp index 76fa6bfb0e..3dcbdfce07 100644 --- a/Gems/Atom/RPI/Code/Tests/Material/LuaMaterialFunctorTests.cpp +++ b/Gems/Atom/RPI/Code/Tests/Material/LuaMaterialFunctorTests.cpp @@ -748,12 +748,15 @@ namespace UnitTest MaterialPropertyDataType::UInt, "general.mode", MaterialPropertyDataType::Float, "general.value", functorScript); - + AZStd::unordered_set changedPropertyNames; - AZStd::unordered_map propertyDynamicMetadata; propertyDynamicMetadata[Name{"general.mode"}] = {}; propertyDynamicMetadata[Name{"general.value"}] = {}; + + AZStd::unordered_set changedPropertyGroupNames; + AZStd::unordered_map propertyGroupDynamicMetadata; + propertyGroupDynamicMetadata[Name{"general"}] = {}; Ptr functor = testData.GetMaterialTypeAsset()->GetMaterialFunctors()[0]; @@ -761,7 +764,9 @@ namespace UnitTest testData.GetMaterial()->GetPropertyValues(), testData.GetMaterial()->GetMaterialPropertiesLayout(), propertyDynamicMetadata, + propertyGroupDynamicMetadata, changedPropertyNames, + changedPropertyGroupNames, &functor->GetMaterialPropertyDependencies() ); @@ -814,10 +819,13 @@ namespace UnitTest functorScript); AZStd::unordered_set changedPropertyNames; - AZStd::unordered_map propertyDynamicMetadata; propertyDynamicMetadata[Name{"general.units"}] = {}; propertyDynamicMetadata[Name{"general.distance"}] = {}; + + AZStd::unordered_set changedPropertyGroupNames; + AZStd::unordered_map propertyGroupDynamicMetadata; + propertyGroupDynamicMetadata[Name{"general"}] = {}; Ptr functor = testData.GetMaterialTypeAsset()->GetMaterialFunctors()[0]; @@ -825,7 +833,9 @@ namespace UnitTest testData.GetMaterial()->GetPropertyValues(), testData.GetMaterial()->GetMaterialPropertiesLayout(), propertyDynamicMetadata, + propertyGroupDynamicMetadata, changedPropertyNames, + changedPropertyGroupNames, &functor->GetMaterialPropertyDependencies() ); @@ -845,6 +855,66 @@ namespace UnitTest EXPECT_EQ(-100.0f, propertyDynamicMetadata[Name{"general.distance"}].m_propertyRange.m_softMin.GetValue()); EXPECT_EQ(100.0f, propertyDynamicMetadata[Name{"general.distance"}].m_propertyRange.m_softMax.GetValue()); } + + TEST_F(LuaMaterialFunctorTests, LuaMaterialFunctor_EditorContext_SetMaterialPropertyGroupVisibility) + { + using namespace AZ::RPI; + + const char* functorScript = + R"( + function GetMaterialPropertyDependencies() + return { "general.mode" } + end + + function ProcessEditor(context) + local mode = context:GetMaterialPropertyValue_uint("general.mode") + + if (mode == 1) then + context:SetMaterialPropertyGroupVisibility("otherGroup", MaterialPropertyGroupVisibility_Enabled) + else + context:SetMaterialPropertyGroupVisibility("otherGroup", MaterialPropertyGroupVisibility_Hidden) + end + end + )"; + + TestMaterialData testData; + testData.Setup( + MaterialPropertyDataType::UInt, "general.mode", + MaterialPropertyDataType::Float, "otherGroup.value", + functorScript); + + AZStd::unordered_set changedPropertyNames; + AZStd::unordered_map propertyDynamicMetadata; + propertyDynamicMetadata[Name{"general.mode"}] = {}; + propertyDynamicMetadata[Name{"otherGroup.value"}] = {}; + + AZStd::unordered_set changedPropertyGroupNames; + AZStd::unordered_map propertyGroupDynamicMetadata; + propertyGroupDynamicMetadata[Name{"general"}] = {}; + propertyGroupDynamicMetadata[Name{"otherGroup"}] = {}; + + Ptr functor = testData.GetMaterialTypeAsset()->GetMaterialFunctors()[0]; + + AZ::RPI::MaterialFunctor::EditorContext context = AZ::RPI::MaterialFunctor::EditorContext( + testData.GetMaterial()->GetPropertyValues(), + testData.GetMaterial()->GetMaterialPropertiesLayout(), + propertyDynamicMetadata, + propertyGroupDynamicMetadata, + changedPropertyNames, + changedPropertyGroupNames, + &functor->GetMaterialPropertyDependencies() + ); + + testData.GetMaterial()->SetPropertyValue(testData.GetMaterialPropertyIndex(), MaterialPropertyValue{0u}); + functor->Process(context); + EXPECT_EQ(MaterialPropertyGroupVisibility::Enabled, propertyGroupDynamicMetadata[Name{"general"}].m_visibility); + EXPECT_EQ(MaterialPropertyGroupVisibility::Hidden, propertyGroupDynamicMetadata[Name{"otherGroup"}].m_visibility); + + testData.GetMaterial()->SetPropertyValue(testData.GetMaterialPropertyIndex(), MaterialPropertyValue{1u}); + functor->Process(context); + EXPECT_EQ(MaterialPropertyGroupVisibility::Enabled, propertyGroupDynamicMetadata[Name{"general"}].m_visibility); + EXPECT_EQ(MaterialPropertyGroupVisibility::Enabled, propertyGroupDynamicMetadata[Name{"otherGroup"}].m_visibility); + } TEST_F(LuaMaterialFunctorTests, LuaMaterialFunctor_RuntimeContext_SetRenderStates) { diff --git a/Gems/Atom/RPI/Code/Tests/Material/MaterialPropertySerializerTests.cpp b/Gems/Atom/RPI/Code/Tests/Material/MaterialPropertySerializerTests.cpp index 5a81ebb74d..d32c285780 100644 --- a/Gems/Atom/RPI/Code/Tests/Material/MaterialPropertySerializerTests.cpp +++ b/Gems/Atom/RPI/Code/Tests/Material/MaterialPropertySerializerTests.cpp @@ -29,6 +29,7 @@ namespace JsonSerializationTests { AZ::RPI::MaterialTypeSourceData::Reflect(context.get()); AZ::RPI::MaterialPropertyDescriptor::Reflect(context.get()); + AZ::RPI::ReflectMaterialDynamicMetadata(context.get()); } void Reflect(AZStd::unique_ptr& context) diff --git a/Gems/Atom/RPI/Code/atom_rpi_edit_files.cmake b/Gems/Atom/RPI/Code/atom_rpi_edit_files.cmake index a8d3a0230d..2fcae8be90 100644 --- a/Gems/Atom/RPI/Code/atom_rpi_edit_files.cmake +++ b/Gems/Atom/RPI/Code/atom_rpi_edit_files.cmake @@ -35,6 +35,7 @@ set(FILES Include/Atom/RPI.Edit/Shader/ShaderSourceData.h Include/Atom/RPI.Edit/Shader/ShaderVariantListSourceData.h Include/Atom/RPI.Edit/Shader/ShaderVariantAssetCreator.h + Include/Atom/RPI.Edit/Shader/ShaderVariantAssetCreator2.h Include/Atom/RPI.Edit/Shader/ShaderVariantTreeAssetCreator.h Source/RPI.Edit/Material/LuaMaterialFunctorSourceData.cpp Source/RPI.Edit/Material/MaterialTypeSourceData.cpp @@ -52,6 +53,7 @@ set(FILES Source/RPI.Edit/Shader/ShaderSourceData.cpp Source/RPI.Edit/Shader/ShaderVariantListSourceData.cpp Source/RPI.Edit/Shader/ShaderVariantAssetCreator.cpp + Source/RPI.Edit/Shader/ShaderVariantAssetCreator2.cpp Source/RPI.Edit/Shader/ShaderVariantTreeAssetCreator.cpp Source/RPI.Edit/Common/AssetUtils.cpp Source/RPI.Edit/Common/AssetAliasesSourceData.cpp diff --git a/Gems/Atom/RPI/Code/atom_rpi_public_files.cmake b/Gems/Atom/RPI/Code/atom_rpi_public_files.cmake index 71c3190a2e..0d5c19758b 100644 --- a/Gems/Atom/RPI/Code/atom_rpi_public_files.cmake +++ b/Gems/Atom/RPI/Code/atom_rpi_public_files.cmake @@ -81,8 +81,11 @@ set(FILES Include/Atom/RPI.Public/Pass/Specific/SelectorPass.h Include/Atom/RPI.Public/Pass/Specific/SwapChainPass.h Include/Atom/RPI.Public/Shader/Shader.h + Include/Atom/RPI.Public/Shader/Shader2.h Include/Atom/RPI.Public/Shader/ShaderReloadNotificationBus.h + Include/Atom/RPI.Public/Shader/ShaderReloadNotificationBus2.h Include/Atom/RPI.Public/Shader/ShaderVariant.h + Include/Atom/RPI.Public/Shader/ShaderVariant2.h Include/Atom/RPI.Public/Shader/ShaderReloadDebugTracker.h Include/Atom/RPI.Public/Shader/ShaderResourceGroup.h Include/Atom/RPI.Public/Shader/ShaderResourceGroupPool.h @@ -155,7 +158,9 @@ set(FILES Source/RPI.Public/Pass/Specific/SelectorPass.cpp Source/RPI.Public/Pass/Specific/SwapChainPass.cpp Source/RPI.Public/Shader/Shader.cpp + Source/RPI.Public/Shader/Shader2.cpp Source/RPI.Public/Shader/ShaderVariant.cpp + Source/RPI.Public/Shader/ShaderVariant2.cpp Source/RPI.Public/Shader/ShaderReloadDebugTracker.cpp Source/RPI.Public/Shader/ShaderResourceGroup.cpp Source/RPI.Public/Shader/ShaderResourceGroupPool.cpp diff --git a/Gems/Atom/RPI/Code/atom_rpi_reflect_files.cmake b/Gems/Atom/RPI/Code/atom_rpi_reflect_files.cmake index d1db00aa34..3a4e1cacc6 100644 --- a/Gems/Atom/RPI/Code/atom_rpi_reflect_files.cmake +++ b/Gems/Atom/RPI/Code/atom_rpi_reflect_files.cmake @@ -55,6 +55,7 @@ set(FILES Include/Atom/RPI.Reflect/Material/MaterialAsset.h Include/Atom/RPI.Reflect/Material/MaterialAssetCreatorCommon.h Include/Atom/RPI.Reflect/Material/MaterialAssetCreator.h + Include/Atom/RPI.Reflect/Material/MaterialDynamicMetadata.h Include/Atom/RPI.Reflect/Material/MaterialPropertyDescriptor.h Include/Atom/RPI.Reflect/Material/MaterialPropertiesLayout.h Include/Atom/RPI.Reflect/Material/MaterialPropertyValue.h @@ -75,8 +76,11 @@ set(FILES Include/Atom/RPI.Reflect/Pass/PassTemplate.h Include/Atom/RPI.Reflect/Pass/RasterPassData.h Include/Atom/RPI.Reflect/Pass/RenderPassData.h + Include/Atom/RPI.Reflect/Shader/ShaderCommonTypes.h Include/Atom/RPI.Reflect/Shader/ShaderAsset.h Include/Atom/RPI.Reflect/Shader/ShaderAssetCreator.h + Include/Atom/RPI.Reflect/Shader/ShaderAsset2.h + Include/Atom/RPI.Reflect/Shader/ShaderAssetCreator2.h Include/Atom/RPI.Reflect/Shader/ShaderInputContract.h Include/Atom/RPI.Reflect/Shader/ShaderOptionGroup.h Include/Atom/RPI.Reflect/Shader/ShaderOptionGroupLayout.h @@ -87,7 +91,9 @@ set(FILES Include/Atom/RPI.Reflect/Shader/ShaderVariantKey.h Include/Atom/RPI.Reflect/Shader/ShaderVariantTreeAsset.h Include/Atom/RPI.Reflect/Shader/ShaderVariantAsset.h + Include/Atom/RPI.Reflect/Shader/ShaderVariantAsset2.h Include/Atom/RPI.Reflect/Shader/IShaderVariantFinder.h + Include/Atom/RPI.Reflect/Shader/IShaderVariantFinder2.h Include/Atom/RPI.Reflect/Shader/PrecompiledShaderAssetSourceData.h Include/Atom/RPI.Reflect/System/AnyAsset.h Include/Atom/RPI.Reflect/System/AssetAliases.h @@ -135,6 +141,7 @@ set(FILES Source/RPI.Reflect/Material/MaterialAssetCreatorCommon.cpp Source/RPI.Reflect/Material/MaterialAssetCreator.cpp Source/RPI.Reflect/Material/LuaMaterialFunctor.cpp + Source/RPI.Reflect/Material/MaterialDynamicMetadata.cpp Source/RPI.Reflect/Material/MaterialPropertyDescriptor.cpp Source/RPI.Reflect/Material/MaterialPropertiesLayout.cpp Source/RPI.Reflect/Material/MaterialTypeAsset.cpp @@ -145,8 +152,11 @@ set(FILES Source/RPI.Reflect/Pass/PassAttachmentReflect.cpp Source/RPI.Reflect/Pass/PassRequest.cpp Source/RPI.Reflect/Pass/PassTemplate.cpp + Source/RPI.Reflect/Shader/ShaderStageType.cpp Source/RPI.Reflect/Shader/ShaderAsset.cpp Source/RPI.Reflect/Shader/ShaderAssetCreator.cpp + Source/RPI.Reflect/Shader/ShaderAsset2.cpp + Source/RPI.Reflect/Shader/ShaderAssetCreator2.cpp Source/RPI.Reflect/Shader/ShaderInputContract.cpp Source/RPI.Reflect/Shader/ShaderOptionGroup.cpp Source/RPI.Reflect/Shader/ShaderOptionGroupLayout.cpp @@ -156,6 +166,7 @@ set(FILES Source/RPI.Reflect/Shader/ShaderVariantKey.cpp Source/RPI.Reflect/Shader/ShaderVariantTreeAsset.cpp Source/RPI.Reflect/Shader/ShaderVariantAsset.cpp + Source/RPI.Reflect/Shader/ShaderVariantAsset2.cpp Source/RPI.Reflect/Shader/PrecompiledShaderAssetSourceData.cpp Source/RPI.Reflect/System/AnyAsset.cpp Source/RPI.Reflect/System/AssetAliases.cpp diff --git a/Gems/Atom/TestData/TestData/Materials/StandardMultilayerPbrTestCases/001_ManyFeatures.material b/Gems/Atom/TestData/TestData/Materials/StandardMultilayerPbrTestCases/001_ManyFeatures.material index b2a3eac890..d9a4aabe2a 100644 --- a/Gems/Atom/TestData/TestData/Materials/StandardMultilayerPbrTestCases/001_ManyFeatures.material +++ b/Gems/Atom/TestData/TestData/Materials/StandardMultilayerPbrTestCases/001_ManyFeatures.material @@ -4,6 +4,10 @@ "parentMaterial": "", "propertyLayoutVersion": 3, "properties": { + "blend": { + "enableLayer2": true, + "enableLayer3": true + }, "layer1_baseColor": { "color": [ 0.3495536744594574, @@ -134,8 +138,14 @@ "enable": true }, "uv": { - "offsetU": -0.2800000011920929, - "rotateDegrees": 39.599998474121097 + "center": [ + 0.10000000149011612, + 0.20000000298023225 + ], + "offsetU": 0.23000000417232514, + "offsetV": -0.23999999463558198, + "rotateDegrees": 39.599998474121097, + "scale": 1.100000023841858 } } -} +} \ No newline at end of file diff --git a/Gems/Atom/TestData/TestData/Materials/StandardMultilayerPbrTestCases/001_ManyFeatures_Layer2Off.material b/Gems/Atom/TestData/TestData/Materials/StandardMultilayerPbrTestCases/001_ManyFeatures_Layer2Off.material new file mode 100644 index 0000000000..d91cfb34eb --- /dev/null +++ b/Gems/Atom/TestData/TestData/Materials/StandardMultilayerPbrTestCases/001_ManyFeatures_Layer2Off.material @@ -0,0 +1,11 @@ +{ + "description": "", + "materialType": "Materials/Types/StandardMultilayerPBR.materialtype", + "parentMaterial": "TestData/Materials/StandardMultilayerPbrTestCases/001_ManyFeatures.material", + "propertyLayoutVersion": 3, + "properties": { + "blend": { + "enableLayer2": false + } + } +} \ No newline at end of file diff --git a/Gems/Atom/TestData/TestData/Materials/StandardMultilayerPbrTestCases/001_ManyFeatures_Layer3Off.material b/Gems/Atom/TestData/TestData/Materials/StandardMultilayerPbrTestCases/001_ManyFeatures_Layer3Off.material new file mode 100644 index 0000000000..3ee48df612 --- /dev/null +++ b/Gems/Atom/TestData/TestData/Materials/StandardMultilayerPbrTestCases/001_ManyFeatures_Layer3Off.material @@ -0,0 +1,11 @@ +{ + "description": "", + "materialType": "Materials/Types/StandardMultilayerPBR.materialtype", + "parentMaterial": "TestData/Materials/StandardMultilayerPbrTestCases/001_ManyFeatures.material", + "propertyLayoutVersion": 3, + "properties": { + "blend": { + "enableLayer3": false + } + } +} \ No newline at end of file diff --git a/Gems/Atom/TestData/TestData/Materials/StandardMultilayerPbrTestCases/002_ParallaxPdo.material b/Gems/Atom/TestData/TestData/Materials/StandardMultilayerPbrTestCases/002_ParallaxPdo.material index e7903a8c91..0bf4177db9 100644 --- a/Gems/Atom/TestData/TestData/Materials/StandardMultilayerPbrTestCases/002_ParallaxPdo.material +++ b/Gems/Atom/TestData/TestData/Materials/StandardMultilayerPbrTestCases/002_ParallaxPdo.material @@ -4,6 +4,10 @@ "parentMaterial": "", "propertyLayoutVersion": 3, "properties": { + "blend": { + "enableLayer2": true, + "enableLayer3": true + }, "layer1_baseColor": { "textureMap": "TestData/Textures/cc0/bark1_col.jpg" }, @@ -30,6 +34,7 @@ "layer2_parallax": { "enable": true, "factor": 0.05299999937415123, + "offset": -0.024000000208616258, "textureMap": "TestData/Textures/cc0/Rock030_2K_Displacement.jpg" }, "layer2_roughness": { @@ -49,4 +54,4 @@ "pdo": true } } -} +} \ No newline at end of file diff --git a/Gems/Atom/TestData/TestData/Materials/StandardMultilayerPbrTestCases/003_Debug_BlendMaskValues.material b/Gems/Atom/TestData/TestData/Materials/StandardMultilayerPbrTestCases/003_Debug_BlendSource.material similarity index 86% rename from Gems/Atom/TestData/TestData/Materials/StandardMultilayerPbrTestCases/003_Debug_BlendMaskValues.material rename to Gems/Atom/TestData/TestData/Materials/StandardMultilayerPbrTestCases/003_Debug_BlendSource.material index 94a1ec6a30..cdd21212c9 100644 --- a/Gems/Atom/TestData/TestData/Materials/StandardMultilayerPbrTestCases/003_Debug_BlendMaskValues.material +++ b/Gems/Atom/TestData/TestData/Materials/StandardMultilayerPbrTestCases/003_Debug_BlendSource.material @@ -5,7 +5,7 @@ "propertyLayoutVersion": 3, "properties": { "general": { - "debugDrawMode": "BlendMaskValues" + "debugDrawMode": "BlendSource" } } } diff --git a/Gems/Atom/TestData/TestData/Objects/PaintedPlane.fbx b/Gems/Atom/TestData/TestData/Objects/PaintedPlane.fbx new file mode 100644 index 0000000000..be30ca4639 --- /dev/null +++ b/Gems/Atom/TestData/TestData/Objects/PaintedPlane.fbx @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:053a7cd73b37c815900f87abd524830e6f23eee75d3b72fb866e86498d528159 +size 36156 diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/DynamicProperty/DynamicProperty.h b/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/DynamicProperty/DynamicProperty.h index b17a25a675..17ca1c8d04 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/DynamicProperty/DynamicProperty.h +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/DynamicProperty/DynamicProperty.h @@ -38,8 +38,8 @@ namespace AtomToolsFramework Count }; - // Configures the initial state, data type, attributes, and values that describe - // the dynamic property and how it is presented + //! Configures the initial state, data type, attributes, and values that describe + //! the dynamic property and how it is presented struct DynamicPropertyConfig { AZ_TYPE_INFO(DynamicPropertyConfig, "{9CA40E92-7F03-42BE-B6AA-51F30EE5796C}"); @@ -98,7 +98,7 @@ namespace AtomToolsFramework //! Returns true if the property has a valid value. bool IsValid() const; - //! Returns the ID of the property. + //! Returns the ID of the property. const AZ::Name GetId() const; //! Returns the current property visibility. diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Inspector/InspectorRequestBus.h b/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Inspector/InspectorRequestBus.h index e76837137a..d9b626f631 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Inspector/InspectorRequestBus.h +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Inspector/InspectorRequestBus.h @@ -44,6 +44,17 @@ namespace AtomToolsFramework const AZStd::string& groupDescription, QWidget* groupWidget) = 0; + //! Sets the visibility of a specific property group. This impacts both the header and the widget. + virtual void SetGroupVisible(const AZStd::string& groupNameId, bool visible) = 0; + + //! Returns whether a specific property is visible. + //! Note this follows the same rules as QWidget::isVisible(), meaning a group could be not visible due to the widget's parents being not visible. + virtual bool IsGroupVisible(const AZStd::string& groupNameId) const = 0; + + //! Returns whether a specific property is explicitly hidden. + //! Note this follows the same rules as QWidget::isHidden(), meaning a group that is hidden will not become visible automatically when the parent becomes visible. + virtual bool IsGroupHidden(const AZStd::string& groupNameId) const = 0; + //! Calls Refresh for a specific InspectorGroupWidget, allowing for non-destructive UI changes virtual void RefreshGroup(const AZStd::string& groupNameId) = 0; diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Inspector/InspectorWidget.h b/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Inspector/InspectorWidget.h index 5fb0b731d6..5e41121b37 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Inspector/InspectorWidget.h +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Inspector/InspectorWidget.h @@ -57,6 +57,10 @@ namespace AtomToolsFramework const AZStd::string& groupDescription, QWidget* groupWidget) override; + void SetGroupVisible(const AZStd::string& groupNameId, bool visible) override; + bool IsGroupVisible(const AZStd::string& groupNameId) const override; + bool IsGroupHidden(const AZStd::string& groupNameId) const override; + void RefreshGroup(const AZStd::string& groupNameId) override; void RebuildGroup(const AZStd::string& groupNameId) override; @@ -79,6 +83,13 @@ namespace AtomToolsFramework private: QVBoxLayout* m_layout = nullptr; QScopedPointer m_ui; - AZStd::unordered_map> m_groups; + + struct GroupWidgetPair + { + InspectorGroupHeaderWidget* m_header; + QWidget* m_panel; + }; + + AZStd::unordered_map m_groups; }; } // namespace AtomToolsFramework diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Inspector/InspectorWidget.cpp b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Inspector/InspectorWidget.cpp index 0259120d9b..097a819e49 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Inspector/InspectorWidget.cpp +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Inspector/InspectorWidget.cpp @@ -74,7 +74,7 @@ namespace AtomToolsFramework groupWidget->setParent(m_ui->m_propertyContent); m_layout->addWidget(groupWidget); - m_groups[groupNameId] = AZStd::make_pair(groupHeader, groupWidget); + m_groups[groupNameId] = {groupHeader, groupWidget}; connect(groupHeader, &InspectorGroupHeaderWidget::clicked, this, [this, groupNameId](QMouseEvent* event) { OnHeaderClicked(groupNameId, event); @@ -91,6 +91,38 @@ namespace AtomToolsFramework CollapseGroup(groupNameId); } } + + void InspectorWidget::SetGroupVisible(const AZStd::string& groupNameId, bool visible) + { + auto groupItr = m_groups.find(groupNameId); + if (groupItr != m_groups.end()) + { + groupItr->second.m_header->setVisible(visible); + groupItr->second.m_panel->setVisible(visible && groupItr->second.m_header->IsExpanded()); + } + } + + bool InspectorWidget::IsGroupVisible(const AZStd::string& groupNameId) const + { + auto groupItr = m_groups.find(groupNameId); + if (groupItr != m_groups.end()) + { + return groupItr->second.m_header->isVisible(); + } + + return false; + } + + bool InspectorWidget::IsGroupHidden(const AZStd::string& groupNameId) const + { + auto groupItr = m_groups.find(groupNameId); + if (groupItr != m_groups.end()) + { + return groupItr->second.m_header->isHidden(); + } + + return false; + } void InspectorWidget::RefreshGroup(const AZStd::string& groupNameId) { @@ -129,8 +161,8 @@ namespace AtomToolsFramework auto groupItr = m_groups.find(groupNameId); if (groupItr != m_groups.end()) { - groupItr->second.first->SetExpanded(true); - groupItr->second.second->setVisible(true); + groupItr->second.m_header->SetExpanded(true); + groupItr->second.m_panel->setVisible(true); } } @@ -139,23 +171,23 @@ namespace AtomToolsFramework auto groupItr = m_groups.find(groupNameId); if (groupItr != m_groups.end()) { - groupItr->second.first->SetExpanded(false); - groupItr->second.second->setVisible(false); + groupItr->second.m_header->SetExpanded(false); + groupItr->second.m_panel->setVisible(false); } } bool InspectorWidget::IsGroupExpanded(const AZStd::string& groupNameId) const { auto groupItr = m_groups.find(groupNameId); - return groupItr != m_groups.end() ? groupItr->second.first->IsExpanded() : false; + return groupItr != m_groups.end() ? groupItr->second.m_header->IsExpanded() : false; } void InspectorWidget::ExpandAll() { for (auto& groupPair : m_groups) { - groupPair.second.first->SetExpanded(true); - groupPair.second.second->setVisible(true); + groupPair.second.m_header->SetExpanded(true); + groupPair.second.m_panel->setVisible(true); } } @@ -163,8 +195,8 @@ namespace AtomToolsFramework { for (auto& groupPair : m_groups) { - groupPair.second.first->SetExpanded(false); - groupPair.second.second->setVisible(false); + groupPair.second.m_header->SetExpanded(false); + groupPair.second.m_panel->setVisible(false); } } diff --git a/Gems/Atom/Tools/MaterialEditor/Code/Include/Atom/Document/MaterialDocumentNotificationBus.h b/Gems/Atom/Tools/MaterialEditor/Code/Include/Atom/Document/MaterialDocumentNotificationBus.h index 3c3c77a628..80e8054ec5 100644 --- a/Gems/Atom/Tools/MaterialEditor/Code/Include/Atom/Document/MaterialDocumentNotificationBus.h +++ b/Gems/Atom/Tools/MaterialEditor/Code/Include/Atom/Document/MaterialDocumentNotificationBus.h @@ -18,6 +18,7 @@ #include #include +#include namespace MaterialEditor { @@ -77,6 +78,12 @@ namespace MaterialEditor //! @param documentId unique id of material document for which the notification is sent //! @param property object containing the property value and configuration that was modified virtual void OnDocumentPropertyConfigModified([[maybe_unused]] const AZ::Uuid& documentId, [[maybe_unused]] const AtomToolsFramework::DynamicProperty& property) {} + + //! Signal that the property group visibility has been changed. + //! @param documentId unique id of material document for which the notification is sent + //! @param groupId id of the group that changed + //! @param visible whether the property group is visible + virtual void OnDocumentPropertyGroupVisibilityChanged([[maybe_unused]] const AZ::Uuid& documentId, [[maybe_unused]] const AZ::Name& groupId, [[maybe_unused]] bool visible) {} }; using MaterialDocumentNotificationBus = AZ::EBus; diff --git a/Gems/Atom/Tools/MaterialEditor/Code/Include/Atom/Document/MaterialDocumentRequestBus.h b/Gems/Atom/Tools/MaterialEditor/Code/Include/Atom/Document/MaterialDocumentRequestBus.h index dad21a6348..c71d500d8c 100644 --- a/Gems/Atom/Tools/MaterialEditor/Code/Include/Atom/Document/MaterialDocumentRequestBus.h +++ b/Gems/Atom/Tools/MaterialEditor/Code/Include/Atom/Document/MaterialDocumentRequestBus.h @@ -19,6 +19,7 @@ #include #include +#include namespace AZ { @@ -67,6 +68,10 @@ namespace MaterialEditor //! Returns a property object //! If the document is not open or the id can't be found, an invalid property is returned. virtual const AtomToolsFramework::DynamicProperty& GetProperty(const AZ::Name& propertyFullName) const = 0; + + //! Returns whether a property group is visible + //! If the document is not open or the id can't be found, returns false. + virtual bool IsPropertyGroupVisible(const AZ::Name& propertyGroupFullName) const = 0; //! Modify material property value virtual void SetPropertyValue(const AZ::Name& propertyFullName, const AZStd::any& value) = 0; diff --git a/Gems/Atom/Tools/MaterialEditor/Code/Source/Document/MaterialDocument.cpp b/Gems/Atom/Tools/MaterialEditor/Code/Source/Document/MaterialDocument.cpp index 288530a4e4..fec70763dd 100644 --- a/Gems/Atom/Tools/MaterialEditor/Code/Source/Document/MaterialDocument.cpp +++ b/Gems/Atom/Tools/MaterialEditor/Code/Source/Document/MaterialDocument.cpp @@ -117,6 +117,24 @@ namespace MaterialEditor const AtomToolsFramework::DynamicProperty& property = it->second; return property; } + + bool MaterialDocument::IsPropertyGroupVisible(const AZ::Name& propertyGroupFullName) const + { + if (!IsOpen()) + { + AZ_Error("MaterialDocument", false, "Material document is not open."); + return false; + } + + const auto it = m_propertyGroupVisibility.find(propertyGroupFullName); + if (it == m_propertyGroupVisibility.end()) + { + AZ_Error("MaterialDocument", false, "Material document property group could not be found: '%s'.", propertyGroupFullName.GetCStr()); + return false; + } + + return it->second; + } void MaterialDocument::SetPropertyValue(const AZ::Name& propertyFullName, const AZStd::any& value) { @@ -153,8 +171,12 @@ namespace MaterialEditor Recompile(); - AZStd::unordered_set changedPropertyNames = RunEditorMaterialFunctors(dirtyFlags); - for (const Name& changedPropertyName : changedPropertyNames) + EditorMaterialFunctorResult result = RunEditorMaterialFunctors(dirtyFlags); + for (const Name& changedPropertyGroupName : result.m_updatedPropertyGroups) + { + MaterialDocumentNotificationBus::Broadcast(&MaterialDocumentNotificationBus::Events::OnDocumentPropertyGroupVisibilityChanged, m_id, changedPropertyGroupName, IsPropertyGroupVisible(changedPropertyGroupName)); + } + for (const Name& changedPropertyName : result.m_updatedProperties) { MaterialDocumentNotificationBus::Broadcast(&MaterialDocumentNotificationBus::Events::OnDocumentPropertyConfigModified, m_id, GetProperty(changedPropertyName)); } @@ -782,6 +804,12 @@ namespace MaterialEditor return true; }); + // Populate the property group visibility map + for (MaterialTypeSourceData::GroupDefinition& group : m_materialTypeSourceData.GetGroupDefinitionsInDisplayOrder()) + { + m_propertyGroupVisibility[AZ::Name{group.m_nameId}] = true; + } + // Adding properties for material type and parent as part of making dynamic // properties and the inspector more general purpose. // This allows the read only properties to appear in the inspector like any @@ -914,16 +942,26 @@ namespace MaterialEditor } } - AZStd::unordered_set MaterialDocument::RunEditorMaterialFunctors(AZ::RPI::MaterialPropertyFlags dirtyFlags) + MaterialDocument::EditorMaterialFunctorResult MaterialDocument::RunEditorMaterialFunctors(AZ::RPI::MaterialPropertyFlags dirtyFlags) { - AZStd::unordered_set changedPropertyNames; + EditorMaterialFunctorResult result; + AZStd::unordered_map propertyDynamicMetadata; + AZStd::unordered_map propertyGroupDynamicMetadata; for (auto& propertyPair : m_properties) { AtomToolsFramework::DynamicProperty& property = propertyPair.second; AtomToolsFramework::ConvertToPropertyMetaData(propertyDynamicMetadata[property.GetId()], property.GetConfig()); } + for (auto& groupPair : m_propertyGroupVisibility) + { + AZ::RPI::MaterialPropertyGroupDynamicMetadata& metadata = propertyGroupDynamicMetadata[AZ::Name{groupPair.first}]; + bool visible = groupPair.second; + metadata.m_visibility = visible ? + AZ::RPI::MaterialPropertyGroupVisibility::Enabled : AZ::RPI::MaterialPropertyGroupVisibility::Hidden; + } + for (AZ::RPI::Ptr& functor : m_editorFunctors) { const AZ::RPI::MaterialPropertyFlags& materialPropertyDependencies = functor->GetMaterialPropertyDependencies(); @@ -935,7 +973,9 @@ namespace MaterialEditor m_materialInstance->GetPropertyValues(), m_materialInstance->GetMaterialPropertiesLayout(), propertyDynamicMetadata, - changedPropertyNames, + propertyGroupDynamicMetadata, + result.m_updatedProperties, + result.m_updatedPropertyGroups, &materialPropertyDependencies ); functor->Process(context); @@ -950,7 +990,13 @@ namespace MaterialEditor property.SetConfig(propertyConfig); } - return changedPropertyNames; + for (auto& updatedPropertyGroup : result.m_updatedPropertyGroups) + { + bool visible = propertyGroupDynamicMetadata[updatedPropertyGroup].m_visibility == AZ::RPI::MaterialPropertyGroupVisibility::Enabled; + m_propertyGroupVisibility[updatedPropertyGroup] = visible; + } + + return result; } } // namespace MaterialEditor diff --git a/Gems/Atom/Tools/MaterialEditor/Code/Source/Document/MaterialDocument.h b/Gems/Atom/Tools/MaterialEditor/Code/Source/Document/MaterialDocument.h index 3959e45ad7..42f48c95cd 100644 --- a/Gems/Atom/Tools/MaterialEditor/Code/Source/Document/MaterialDocument.h +++ b/Gems/Atom/Tools/MaterialEditor/Code/Source/Document/MaterialDocument.h @@ -56,6 +56,7 @@ namespace MaterialEditor const AZ::RPI::MaterialTypeSourceData* GetMaterialTypeSourceData() const override; const AZStd::any& GetPropertyValue(const AZ::Name& propertyFullName) const override; const AtomToolsFramework::DynamicProperty& GetProperty(const AZ::Name& propertyFullName) const override; + bool IsPropertyGroupVisible(const AZ::Name& propertyGroupFullName) const override; void SetPropertyValue(const AZ::Name& propertyFullName, const AZStd::any& value) override; bool Open(AZStd::string_view loadPath) override; bool Rebuild() override; @@ -79,11 +80,14 @@ namespace MaterialEditor // Predicate for evaluating properties using PropertyFilterFunction = AZStd::function; - // Map of documenmt's property + // Map of document's properties using PropertyMap = AZStd::unordered_map; // Map of raw property values for undo/redo comparison and storage using PropertyValueMap = AZStd::unordered_map; + + // Map of document's property group visibility flags + using PropertyGroupVisibilityMap = AZStd::unordered_map; // Function to be bound for undo and redo using UndoRedoFunction = AZStd::function; @@ -119,10 +123,16 @@ namespace MaterialEditor void RestorePropertyValues(const PropertyValueMap& propertyValues); + struct EditorMaterialFunctorResult + { + AZStd::unordered_set m_updatedProperties; + AZStd::unordered_set m_updatedPropertyGroups; + }; + // Run editor material functor to update editor metadata. // @param dirtyFlags indicates which properties have changed, and thus which MaterialFunctors need to be run. - // @return names for the set of properties that have been changed or need update. - AZStd::unordered_set RunEditorMaterialFunctors(AZ::RPI::MaterialPropertyFlags dirtyFlags); + // @return names for the set of properties and groups that have been changed or need update. + EditorMaterialFunctorResult RunEditorMaterialFunctors(AZ::RPI::MaterialPropertyFlags dirtyFlags); // Unique id of this material document AZ::Uuid m_id = AZ::Uuid::CreateRandom(); @@ -153,6 +163,9 @@ namespace MaterialEditor // Collection of all material's properties PropertyMap m_properties; + + // Collection of all material's property groups + PropertyGroupVisibilityMap m_propertyGroupVisibility; // Material functors that run in editor. See MaterialFunctor.h for details. AZStd::vector> m_editorFunctors; @@ -175,7 +188,7 @@ namespace MaterialEditor int m_undoHistoryIndex = 0; AZStd::any m_invalidValue; - + AtomToolsFramework::DynamicProperty m_invalidProperty; }; } // namespace MaterialEditor diff --git a/Gems/Atom/Tools/MaterialEditor/Code/Source/Window/MaterialInspector/MaterialInspector.cpp b/Gems/Atom/Tools/MaterialEditor/Code/Source/Window/MaterialInspector/MaterialInspector.cpp index 55e098962a..706d365027 100644 --- a/Gems/Atom/Tools/MaterialEditor/Code/Source/Window/MaterialInspector/MaterialInspector.cpp +++ b/Gems/Atom/Tools/MaterialEditor/Code/Source/Window/MaterialInspector/MaterialInspector.cpp @@ -198,6 +198,11 @@ namespace MaterialEditor &group, &group, group.TYPEINFO_Uuid(), this, this, GetGroupSaveStateKey(groupNameId), [this](const auto source, const auto target) { return CompareInstanceNodeProperties(source, target); }); AddGroup(groupNameId, groupDisplayName, groupDescription, propertyGroupWidget); + + bool isGroupVisible = false; + MaterialDocumentRequestBus::EventResult( + isGroupVisible, m_documentId, &MaterialDocumentRequestBus::Events::IsPropertyGroupVisible, AZ::Name{groupNameId}); + SetGroupVisible(groupNameId, isGroupVisible); } } @@ -221,8 +226,7 @@ namespace MaterialEditor } } - void MaterialInspector::OnDocumentPropertyConfigModified( - const AZ::Uuid& documentId, const AtomToolsFramework::DynamicProperty& property) + void MaterialInspector::OnDocumentPropertyConfigModified(const AZ::Uuid&, const AtomToolsFramework::DynamicProperty& property) { for (auto& groupPair : m_groups) { @@ -234,20 +238,23 @@ namespace MaterialEditor if (reflectedProperty.GetVisibility() != property.GetVisibility()) { reflectedProperty.SetConfig(property.GetConfig()); - AtomToolsFramework::InspectorRequestBus::Event( - documentId, &AtomToolsFramework::InspectorRequestBus::Events::RebuildGroup, groupPair.first); + RebuildGroup(groupPair.first); } else { reflectedProperty.SetConfig(property.GetConfig()); - AtomToolsFramework::InspectorRequestBus::Event( - documentId, &AtomToolsFramework::InspectorRequestBus::Events::RefreshGroup, groupPair.first); + RefreshGroup(groupPair.first); } return; } } } } + + void MaterialInspector::OnDocumentPropertyGroupVisibilityChanged(const AZ::Uuid&, const AZ::Name& groupId, bool visible) + { + SetGroupVisible(groupId.GetStringView(), visible); + } void MaterialInspector::BeforePropertyModified(AzToolsFramework::InstanceDataNode* pNode) { diff --git a/Gems/Atom/Tools/MaterialEditor/Code/Source/Window/MaterialInspector/MaterialInspector.h b/Gems/Atom/Tools/MaterialEditor/Code/Source/Window/MaterialInspector/MaterialInspector.h index f53e93a2f9..4080430ff5 100644 --- a/Gems/Atom/Tools/MaterialEditor/Code/Source/Window/MaterialInspector/MaterialInspector.h +++ b/Gems/Atom/Tools/MaterialEditor/Code/Source/Window/MaterialInspector/MaterialInspector.h @@ -60,6 +60,7 @@ namespace MaterialEditor void OnDocumentOpened(const AZ::Uuid& documentId) override; void OnDocumentPropertyValueModified(const AZ::Uuid& documentId, const AtomToolsFramework::DynamicProperty& property) override; void OnDocumentPropertyConfigModified(const AZ::Uuid& documentId, const AtomToolsFramework::DynamicProperty& property) override; + void OnDocumentPropertyGroupVisibilityChanged(const AZ::Uuid& documentId, const AZ::Name& groupId, bool visible) override; // AzToolsFramework::IPropertyEditorNotify overrides... void BeforePropertyModified(AzToolsFramework::InstanceDataNode* pNode) override; diff --git a/Gems/Atom/Tools/ShaderManagementConsole/Code/Include/Atom/Document/ShaderManagementConsoleDocumentRequestBus.h b/Gems/Atom/Tools/ShaderManagementConsole/Code/Include/Atom/Document/ShaderManagementConsoleDocumentRequestBus.h index 06b8f13f65..d575049b23 100644 --- a/Gems/Atom/Tools/ShaderManagementConsole/Code/Include/Atom/Document/ShaderManagementConsoleDocumentRequestBus.h +++ b/Gems/Atom/Tools/ShaderManagementConsole/Code/Include/Atom/Document/ShaderManagementConsoleDocumentRequestBus.h @@ -16,6 +16,7 @@ #include #include +#include #include #include diff --git a/Gems/Atom/Tools/ShaderManagementConsole/Code/Source/Document/ShaderManagementConsoleDocument.h b/Gems/Atom/Tools/ShaderManagementConsole/Code/Source/Document/ShaderManagementConsoleDocument.h index f0570841f3..6c3a2f55bf 100644 --- a/Gems/Atom/Tools/ShaderManagementConsole/Code/Source/Document/ShaderManagementConsoleDocument.h +++ b/Gems/Atom/Tools/ShaderManagementConsole/Code/Source/Document/ShaderManagementConsoleDocument.h @@ -14,6 +14,7 @@ #include #include +#include #include #include diff --git a/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiTransientAttachmentProfiler.inl b/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiTransientAttachmentProfiler.inl index e7e743adda..23eb79650a 100644 --- a/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiTransientAttachmentProfiler.inl +++ b/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiTransientAttachmentProfiler.inl @@ -269,13 +269,13 @@ namespace AZ { ImGui::BeginChild(heapMemoryId.c_str()); ImGui::SetScrollY(scrollingY); - ImGui::End(); + ImGui::EndChild(); } { ImGui::BeginChild(scopesId.c_str()); ImGui::SetScrollX(scrollingX); - ImGui::End(); + ImGui::EndChild(); } ImGui::PopStyleVar(3); diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentInspector.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentInspector.cpp index 7f54e386fc..31d69569db 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentInspector.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentInspector.cpp @@ -296,15 +296,20 @@ namespace AZ void MaterialPropertyInspector::RunEditorMaterialFunctors() { AZStd::unordered_set changedPropertyNames; + AZStd::unordered_set changedPropertyGroupNames; // Convert editor property configuration data into material property meta data so that it can be used to execute functors AZStd::unordered_map propertyDynamicMetadata; - for (auto& group : m_groups) + AZStd::unordered_map propertyGroupDynamicMetadata; + for (auto& groupPair : m_groups) { - for (auto& property : group.second.m_properties) - { - AtomToolsFramework::ConvertToPropertyMetaData(propertyDynamicMetadata[property.GetId()], property.GetConfig()); - } + AZ::RPI::MaterialPropertyGroupDynamicMetadata& metadata = propertyGroupDynamicMetadata[AZ::Name{groupPair.first}]; + + // It's significant that we check IsGroupHidden rather than IsGroupVisisble, because it follows the same rules as QWidget::isHidden(). + // We don't care whether the widget and all its parents are visible, we only care about whether the group was hidden within the context + // of the material property inspector. + metadata.m_visibility = IsGroupHidden(groupPair.first) ? + AZ::RPI::MaterialPropertyGroupVisibility::Hidden : AZ::RPI::MaterialPropertyGroupVisibility::Enabled; } for (AZ::RPI::Ptr& functor : m_editorFunctors) @@ -318,7 +323,9 @@ namespace AZ m_materialInstance->GetPropertyValues(), m_materialInstance->GetMaterialPropertiesLayout(), propertyDynamicMetadata, + propertyGroupDynamicMetadata, changedPropertyNames, + changedPropertyGroupNames, &materialPropertyDependencies ); functor->Process(context); @@ -327,9 +334,16 @@ namespace AZ m_dirtyPropertyFlags.reset(); // Apply any changes to material property meta data back to the editor property configurations - for (auto& group : m_groups) + for (auto& groupPair : m_groups) { - for (auto& property : group.second.m_properties) + AZ::Name groupName{groupPair.first}; + + if (changedPropertyGroupNames.find(groupName) != changedPropertyGroupNames.end()) + { + SetGroupVisible(groupPair.first, propertyGroupDynamicMetadata[groupName].m_visibility == AZ::RPI::MaterialPropertyGroupVisibility::Enabled); + } + + for (auto& property : groupPair.second.m_properties) { AtomToolsFramework::DynamicPropertyConfig propertyConfig = property.GetConfig(); diff --git a/Gems/Multiplayer/Code/Source/Pipeline/NetworkPrefabProcessor.cpp b/Gems/Multiplayer/Code/Source/Pipeline/NetworkPrefabProcessor.cpp index 0bbfe17801..31884d4c94 100644 --- a/Gems/Multiplayer/Code/Source/Pipeline/NetworkPrefabProcessor.cpp +++ b/Gems/Multiplayer/Code/Source/Pipeline/NetworkPrefabProcessor.cpp @@ -54,34 +54,15 @@ namespace Multiplayer } } - static AZStd::vector GetEntitiesFromInstance(AZStd::unique_ptr& instance) - { - AZStd::vector result; - - instance->GetNestedEntities([&result](const AZStd::unique_ptr& entity) { - result.emplace_back(entity.get()); - return true; - }); - - if (instance->HasContainerEntity()) - { - auto containerEntityReference = instance->GetContainerEntity(); - result.emplace_back(&containerEntityReference->get()); - } - - return result; - } - - void NetworkPrefabProcessor::ProcessPrefab(PrefabProcessorContext& context, AZStd::string_view prefabName, PrefabDom& prefab) + static AZStd::unique_ptr LoadInstanceFromPrefab(const PrefabDom& prefab) { using namespace AzToolsFramework::Prefab; // convert Prefab DOM into Prefab Instance. AZStd::unique_ptr sourceInstance(aznew Instance()); - if (!PrefabDomUtils::LoadInstanceFromPrefabDom(*sourceInstance, prefab, - PrefabDomUtils::LoadInstanceFlags::AssignRandomEntityId)) + if (!PrefabDomUtils::LoadInstanceFromPrefabDom(*sourceInstance, prefab, PrefabDomUtils::LoadInstanceFlags::AssignRandomEntityId)) { - PrefabDomValueReference sourceReference = PrefabDomUtils::FindPrefabDomValue(prefab, PrefabDomUtils::SourceName); + PrefabDomValueConstReference sourceReference = PrefabDomUtils::FindPrefabDomValue(prefab, PrefabDomUtils::SourceName); AZStd::string errorMessage("NetworkPrefabProcessor: Failed to Load Prefab Instance from given Prefab Dom."); if (sourceReference.has_value() && sourceReference->get().IsString() && sourceReference->get().GetStringLength() != 0) @@ -90,6 +71,38 @@ namespace Multiplayer errorMessage += AZStd::string::format("Prefab Source: %.*s", AZ_STRING_ARG(source)); } AZ_Error("NetworkPrefabProcessor", false, errorMessage.c_str()); + return nullptr; + } + return sourceInstance; + } + + static void GatherNetEntities( + AzToolsFramework::Prefab::Instance* instance, + AZStd::vector>& output) + { + instance->GetEntities([instance, &output](AZStd::unique_ptr& prefabEntity) + { + if (prefabEntity->FindComponent()) + { + output.push_back(AZStd::make_pair(prefabEntity.get(), instance)); + } + return true; + }); + + instance->GetNestedInstances([&output](AZStd::unique_ptr& nestedInstance) + { + GatherNetEntities(nestedInstance.get(), output); + }); + } + + void NetworkPrefabProcessor::ProcessPrefab(PrefabProcessorContext& context, AZStd::string_view prefabName, PrefabDom& prefab) + { + using namespace AzToolsFramework::Prefab; + + // convert Prefab DOM into Prefab Instance. + AZStd::unique_ptr sourceInstance = LoadInstanceFromPrefab(prefab); + if (!sourceInstance) + { return; } @@ -105,36 +118,37 @@ namespace Multiplayer auto&& [object, networkSpawnable] = ProcessedObjectStore::Create(uniqueName, context.GetSourceUuid(), AZStd::move(serializer)); - // grab all nested entities from the Instance as source entities. - AZStd::vector sourceEntities = GetEntitiesFromInstance(sourceInstance); - AZStd::vector networkedEntityIds; - networkedEntityIds.reserve(sourceEntities.size()); + // Grab all net entities with their corresponding Instances to handle nested prefabs correctly + AZStd::vector> netEntities; + GatherNetEntities(sourceInstance.get(), netEntities); - for (auto* sourceEntity : sourceEntities) - { - if (sourceEntity->FindComponent()) - { - networkedEntityIds.push_back(sourceEntity->GetId()); - } - } - - if (networkedEntityIds.empty()) + if (netEntities.empty()) { // No networked entities in the prefab, no need to do anything in this processor. return; } + // Instance container for net entities AZStd::unique_ptr networkInstance(aznew Instance()); + // Create an asset for our future network spawnable: this allows us to put references to the asset in the components AZ::Data::Asset networkSpawnableAsset; networkSpawnableAsset.Create(networkSpawnable->GetId()); networkSpawnableAsset.SetAutoLoadBehavior(AZ::Data::AssetLoadBehavior::PreLoad); - for (size_t entityIndex = 0; entityIndex < networkedEntityIds.size(); ++entityIndex) - { - AZ::EntityId entityId = networkedEntityIds[entityIndex]; + // Each spawnable has a root meta-data entity at position 0, so starting net indices from 1 + size_t netEntitiesIndexCounter = 1; + + for (auto& entityInstancePair : netEntities) + { + AZ::Entity* prefabEntity = entityInstancePair.first; + Instance* instance = entityInstancePair.second; + + AZ::EntityId entityId = prefabEntity->GetId(); + AZ::Entity* netEntity = instance->DetachEntity(entityId).release(); + AZ_Assert(netEntity, "Unable to detach entity %s [%s] from the source prefab instance", + prefabEntity->GetName().c_str(), entityId.ToString().c_str()); - AZ::Entity* netEntity = sourceInstance->DetachEntity(entityId).release(); // Net entity will need a new ID to avoid IDs collision netEntity->SetId(AZ::Entity::MakeId()); networkInstance->AddEntity(*netEntity); @@ -143,17 +157,21 @@ namespace Multiplayer AZ::Entity* breadcrumbEntity = aznew AZ::Entity(entityId, netEntity->GetName()); breadcrumbEntity->SetRuntimeActiveByDefault(netEntity->IsRuntimeActiveByDefault()); + // Marker component is responsible to spawning entities based on the index. NetBindMarkerComponent* netBindMarkerComponent = breadcrumbEntity->CreateComponent(); - // Each spawnable has a root meta-data entity at position 0, so starting net indices from 1 - netBindMarkerComponent->SetNetEntityIndex(entityIndex + 1); + netBindMarkerComponent->SetNetEntityIndex(netEntitiesIndexCounter); netBindMarkerComponent->SetNetworkSpawnableAsset(networkSpawnableAsset); + + // Copy the transform component from the original entity to have the correct transform and parent-child relationship AzFramework::TransformComponent* transformComponent = netEntity->FindComponent(); breadcrumbEntity->CreateComponent(*transformComponent); - sourceInstance->AddEntity(*breadcrumbEntity); + instance->AddEntity(*breadcrumbEntity); + + netEntitiesIndexCounter++; } - // Add net spawnable asset holder + // Add net spawnable asset holder to the prefab root { EntityOptionalReference containerEntityRef = sourceInstance->GetContainerEntity(); if (containerEntityRef.has_value()) @@ -184,7 +202,6 @@ namespace Multiplayer return; } - bool result = SpawnableUtils::CreateSpawnable(*networkSpawnable, networkPrefab); if (result) { diff --git a/Gems/PhysX/Code/Source/System/PhysXSystem.cpp b/Gems/PhysX/Code/Source/System/PhysXSystem.cpp index 1622d04aae..8df9e9a86f 100644 --- a/Gems/PhysX/Code/Source/System/PhysXSystem.cpp +++ b/Gems/PhysX/Code/Source/System/PhysXSystem.cpp @@ -21,10 +21,25 @@ #include +// only enable physx timestep warning when not running debug or in Release +#if !defined(DEBUG) && !defined(RELEASE) +#define ENABLE_PHYSX_TIMESTEP_WARNING +#endif + namespace PhysX { AZ_CLASS_ALLOCATOR_IMPL(PhysXSystem, AZ::SystemAllocator, 0); +#ifdef ENABLE_PHYSX_TIMESTEP_WARNING + namespace FrameTimeWarning + { + static constexpr int MaxSamples = 1000; + static int NumSamples = 0; + static int NumSamplesOverLimit = 0; + static float LostTime = 0.0f; + } +#endif + PhysXSystem::MaterialLibraryAssetHelper::MaterialLibraryAssetHelper(PhysXSystem* physXSystem) : m_physXSystem(physXSystem) { @@ -140,9 +155,26 @@ namespace PhysX } }; - AZ_Warning("PhysXSystem", deltaTime <= m_systemConfig.m_maxTimestep, - "Frame delta time of [%.6f seconds] exceeds Physics max frame timestep, physics timestep will be clamped to [%.6f seconds].", - deltaTime, m_systemConfig.m_maxTimestep); +#ifdef ENABLE_PHYSX_TIMESTEP_WARNING + if (FrameTimeWarning::NumSamples < FrameTimeWarning::MaxSamples) + { + FrameTimeWarning::NumSamples++; + if (deltaTime > m_systemConfig.m_maxTimestep) + { + FrameTimeWarning::NumSamplesOverLimit++; + FrameTimeWarning::LostTime += deltaTime - m_systemConfig.m_maxTimestep; + } + } + else + { + AZ_Warning("PhysXSystem", FrameTimeWarning::NumSamplesOverLimit <= 0, + "[%d] of [%d] frames had a deltatime over the Max physics timestep[%.6f]. Physx timestep was clamped on those frames, losing [%.6f] seconds.", + FrameTimeWarning::NumSamplesOverLimit, FrameTimeWarning::NumSamples, m_systemConfig.m_maxTimestep, FrameTimeWarning::LostTime); + FrameTimeWarning::NumSamples = 0; + FrameTimeWarning::NumSamplesOverLimit = 0; + FrameTimeWarning::LostTime = 0.0f; + } +#endif deltaTime = AZ::GetClamp(deltaTime, 0.0f, m_systemConfig.m_maxTimestep); AZ_Assert(m_systemConfig.m_fixedTimestep >= 0.0f, "PhysXSystem - fixed timestep is negitive."); diff --git a/Gems/SceneProcessing/Code/Source/SceneBuilder/SceneBuilderComponent.cpp b/Gems/SceneProcessing/Code/Source/SceneBuilder/SceneBuilderComponent.cpp index e71a5207d0..25faca3667 100644 --- a/Gems/SceneProcessing/Code/Source/SceneBuilder/SceneBuilderComponent.cpp +++ b/Gems/SceneProcessing/Code/Source/SceneBuilder/SceneBuilderComponent.cpp @@ -72,6 +72,11 @@ namespace SceneBuilder m_sceneBuilder.BusDisconnect(); } + void BuilderPluginComponent::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required) + { + required.emplace_back(AZ_CRC_CE("AssetImportRequestHandler")); + } + void BuilderPluginComponent::Reflect(AZ::ReflectContext* context) { AZ::SerializeContext* serializeContext = azrtti_cast(context); @@ -81,5 +86,4 @@ namespace SceneBuilder ->Attribute(AZ::Edit::Attributes::SystemComponentTags, AZStd::vector({ AssetBuilderSDK::ComponentTags::AssetBuilder })); } } - } // namespace SceneBuilder diff --git a/Gems/SceneProcessing/Code/Source/SceneBuilder/SceneBuilderComponent.h b/Gems/SceneProcessing/Code/Source/SceneBuilder/SceneBuilderComponent.h index c1fc6ebb36..aed5e1b026 100644 --- a/Gems/SceneProcessing/Code/Source/SceneBuilder/SceneBuilderComponent.h +++ b/Gems/SceneProcessing/Code/Source/SceneBuilder/SceneBuilderComponent.h @@ -32,6 +32,8 @@ namespace SceneBuilder void Activate() override; void Deactivate() override; + static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required); + private: SceneBuilderWorker m_sceneBuilder; }; diff --git a/Gems/TextureAtlas/Code/Source/Editor/AtlasBuilderComponent.cpp b/Gems/TextureAtlas/Code/Source/Editor/AtlasBuilderComponent.cpp new file mode 100644 index 0000000000..e8d4948cc9 --- /dev/null +++ b/Gems/TextureAtlas/Code/Source/Editor/AtlasBuilderComponent.cpp @@ -0,0 +1,99 @@ +/* +* 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 "ImageProcessing_precompiled.h" +#include "AtlasBuilderComponent.h" + +#include + +namespace TextureAtlasBuilder +{ + // AZ Components should only initialize their members to null and empty in constructor + // Allocation of data should occur in Init(), once we can guarantee reflection and registration of types + AtlasBuilderComponent::AtlasBuilderComponent() + { + } + + // Handle deallocation of your memory allocated in Init() + AtlasBuilderComponent::~AtlasBuilderComponent() + { + } + + // Init is where you'll actually allocate memory or create objects + // This ensures that any dependency components will have been been created and serialized + void AtlasBuilderComponent::Init() + { + } + + // Activate is where you'd perform registration with other objects and systems. + // All builder classes owned by this component should be registered here + // Any EBuses for the builder classes should also be connected at this point + void AtlasBuilderComponent::Activate() + { + AssetBuilderSDK::AssetBuilderDesc builderDescriptor; + builderDescriptor.m_name = "Atlas Worker Builder"; + builderDescriptor.m_version = 1; + builderDescriptor.m_patterns.emplace_back(AssetBuilderSDK::AssetBuilderPattern("*.texatlas", AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard)); + builderDescriptor.m_busId = azrtti_typeid(); + builderDescriptor.m_createJobFunction = AZStd::bind(&AtlasBuilderWorker::CreateJobs, &m_atlasBuilder, AZStd::placeholders::_1, AZStd::placeholders::_2); + builderDescriptor.m_processJobFunction = AZStd::bind(&AtlasBuilderWorker::ProcessJob, &m_atlasBuilder, AZStd::placeholders::_1, AZStd::placeholders::_2); + + m_atlasBuilder.BusConnect(builderDescriptor.m_busId); + + AssetBuilderSDK::AssetBuilderBus::Broadcast(&AssetBuilderSDK::AssetBuilderBusTraits::RegisterBuilderInformation, builderDescriptor); + } + + // Disconnects from any EBuses we connected to in Activate() + // Unregisters from objects and systems we register with in Activate() + void AtlasBuilderComponent::Deactivate() + { + m_atlasBuilder.BusDisconnect(); + + // We don't need to unregister the builder - the AP will handle this for us, because it is managing the lifecycle of this component + } + + // Reflect the input and output formats for the serializer + void AtlasBuilderComponent::Reflect(AZ::ReflectContext* context) + { + // components also get Reflect called automatically + // this is your opportunity to perform static reflection or type registration of any types you want the serializer to know about + if (AZ::SerializeContext* serialize = azrtti_cast(context)) + { + serialize->Class() + ->Version(0) + ->Attribute(AZ::Edit::Attributes::SystemComponentTags, AZStd::vector({ AssetBuilderSDK::ComponentTags::AssetBuilder })) + ; + } + + AtlasBuilderInput::Reflect(context); + } + + void AtlasBuilderComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided) + { + provided.push_back(AZ_CRC("Atlas Builder Plugin Service", 0x35974d0d)); + } + + void AtlasBuilderComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible) + { + incompatible.push_back(AZ_CRC("Atlas Builder Plugin Service", 0x35974d0d)); + } + + void AtlasBuilderComponent::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required) + { + AZ_UNUSED(required); + } + + void AtlasBuilderComponent::GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent) + { + AZ_UNUSED(dependent); + } +} diff --git a/Gems/TextureAtlas/Code/Source/Editor/AtlasBuilderComponent.h b/Gems/TextureAtlas/Code/Source/Editor/AtlasBuilderComponent.h new file mode 100644 index 0000000000..eb8b85dfcf --- /dev/null +++ b/Gems/TextureAtlas/Code/Source/Editor/AtlasBuilderComponent.h @@ -0,0 +1,44 @@ +/* + * 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 "AtlasBuilderWorker.h" + +namespace TextureAtlasBuilder +{ + class AtlasBuilderComponent : public AZ::Component + { + public: + AZ_COMPONENT(AtlasBuilderComponent, "{F49987FB-3375-4417-AB83-97B44C78B335}"); + + AtlasBuilderComponent(); + ~AtlasBuilderComponent() override; + + void Init() override; + void Activate() override; + void Deactivate() override; + + //! Reflect formats for input and output + static void Reflect(AZ::ReflectContext* context); + + static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided); + static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible); + static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required); + static void GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent); + + private: + AtlasBuilderWorker m_atlasBuilder; + }; +} // namespace TextureAtlasBuilder diff --git a/Gems/TextureAtlas/Code/Source/Editor/AtlasBuilderWorker.cpp b/Gems/TextureAtlas/Code/Source/Editor/AtlasBuilderWorker.cpp new file mode 100644 index 0000000000..81e98251cc --- /dev/null +++ b/Gems/TextureAtlas/Code/Source/Editor/AtlasBuilderWorker.cpp @@ -0,0 +1,1607 @@ +/* +* 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 "ImageProcessing_precompiled.h" +#include "AtlasBuilderWorker.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 + +namespace TextureAtlasBuilder +{ + //! Counts leading zeros + uint32 CountLeadingZeros32(uint32 x) + { + return x == 0 ? 32 : az_clz_u32(x); + } + + //! Integer log2 + uint32 IntegerLog2(uint32 x) + { + return 31 - CountLeadingZeros32(x); + } + + bool IsFolderPath(const AZStd::string& path) + { + bool hasExtension = AzFramework::StringFunc::Path::HasExtension(path.c_str()); + return !hasExtension; + } + + bool HasTrailingSlash(const AZStd::string& path) + { + size_t pathLength = path.size(); + return (pathLength > 0 && (path.at(pathLength - 1) == '/' || path.at(pathLength - 1) == '\\')); + } + + bool GetCanonicalPathFromFullPath(const AZStd::string& fullPath, AZStd::string& canonicalPathOut) + { + AZStd::string curPath = fullPath; + + // We avoid using LocalFileIO::ConvertToAbsolutePath for this because it does not behave consistently across platforms. + // On non-Windows platforms, LocalFileIO::ConvertToAbsolutePath requires that the path exist, otherwise the path + // remains unchanged. This won't work for paths that include wildcards. + // Also, on non-Windows platforms, if the path is already a full path, it will remain unchanged even if it contains + // "./" or "../" somewhere other than the beginning of the path + + // Normalize path + AzFramework::ApplicationRequests::Bus::Broadcast(&AzFramework::ApplicationRequests::NormalizePathKeepCase, curPath); + + const AZStd::string slash("/"); + + // Replace "/./" occurrances with "/" + const AZStd::string slashDotSlash("/./"); + bool replaced = false; + do + { + // Replace first occurrance + replaced = AzFramework::StringFunc::Replace(curPath, slashDotSlash.c_str(), slash.c_str(), false, true, false); + } while (replaced); + + // Replace "/xxx/../" with "/" + const AZStd::regex slashDotDotSlash("\\/[^/.]*\\/\\.\\.\\/"); + AZStd::string prevPath; + while (prevPath != curPath) + { + prevPath = curPath; + curPath = AZStd::regex_replace(prevPath, slashDotDotSlash, slash, AZStd::regex_constants::match_flag_type::format_first_only); + } + + if ((curPath.find("..") != AZStd::string::npos) || (curPath.find("./") != AZStd::string::npos) || (curPath.find("/.") != AZStd::string::npos)) + { + return false; + } + + canonicalPathOut = curPath; + return true; + } + + bool ResolveRelativePath(const AZStd::string& relativePath, const AZStd::string& watchDirectory, AZStd::string& resolvedFullPathOut) + { + bool resolved = false; + + // Get full path by appending the relative path to the watch directory + AZStd::string fullPath = watchDirectory; + fullPath.append("/"); + fullPath.append(relativePath); + + // Resolve to canonical path (remove "./" and "../") + resolved = GetCanonicalPathFromFullPath(fullPath, resolvedFullPathOut); + + return resolved; + } + + bool GetAbsoluteSourcePathFromRelativePath(const AZStd::string& relativeSourcePath, AZStd::string& absoluteSourcePathOut) + { + bool result = false; + AZ::Data::AssetInfo info; + AZStd::string watchFolder; + AzToolsFramework::AssetSystemRequestBus::BroadcastResult(result, &AzToolsFramework::AssetSystemRequestBus::Events::GetSourceInfoBySourcePath, relativeSourcePath.c_str(), info, watchFolder); + if (result) + { + absoluteSourcePathOut = AZStd::string::format("%s/%s", watchFolder.c_str(), info.m_relativePath.c_str()); + + // Normalize path + AzFramework::ApplicationRequests::Bus::Broadcast(&AzFramework::ApplicationRequests::NormalizePathKeepCase, absoluteSourcePathOut); + } + return result; + } + + const ImageProcessing::PresetSettings* GetImageProcessPresetSettings(const AZStd::string& presetName, const AZStd::string& platformIdentifier) + { + // Get the specified presetId + AZ::Uuid presetId = ImageProcessing::BuilderSettingManager::Instance()->GetPresetIdFromName(presetName); + if (presetId.IsNull()) + { + AZ_Error("Texture Editor", false, "Texture Preset %s has no associated UUID.", presetName.c_str()); + return nullptr; + } + + // Get the preset settings for the platform this job is building for + const ImageProcessing::PresetSettings* presetSettings = ImageProcessing::BuilderSettingManager::Instance()->GetPreset( + presetId, platformIdentifier); + + return presetSettings; + } + + // Reflect the input parameters + void AtlasBuilderInput::Reflect(AZ::ReflectContext* context) + { + if (AZ::SerializeContext* serialize = azrtti_cast(context)) + { + serialize->Class() + ->Version(1) + ->Field("Force Square", &AtlasBuilderInput::m_forceSquare) + ->Field("Force Power of Two", &AtlasBuilderInput::m_forcePowerOf2) + ->Field("Include White Texture", &AtlasBuilderInput::m_includeWhiteTexture) + ->Field("Maximum Dimension", &AtlasBuilderInput::m_maxDimension) + ->Field("Padding", &AtlasBuilderInput::m_padding) + ->Field("UnusedColor", &AtlasBuilderInput::m_unusedColor) + ->Field("PresetName", &AtlasBuilderInput::m_presetName) + ->Field("Textures to Add", &AtlasBuilderInput::m_filePaths); + } + } + + // Supports a custom parser format + AtlasBuilderInput AtlasBuilderInput::ReadFromFile(const AZStd::string& path, const AZStd::string& directory, bool& valid) + { + // Open the file + AZ::IO::FileIOBase* input = AZ::IO::FileIOBase::GetInstance(); + AZ::IO::HandleType handle; + input->Open(path.c_str(), AZ::IO::OpenMode::ModeRead, handle); + + // Read the file + AZ::u64 size; + input->Size(handle, size); + char* buffer = new char[size + 1]; + input->Read(handle, buffer, size); + buffer[size] = 0; + + // Close the file + input->Close(handle); + + // Prepare the output + AtlasBuilderInput data; + + // Parse the input into lines + AZStd::vector lines; + AzFramework::StringFunc::Tokenize(buffer, lines, "\n\t"); + delete[] buffer; + + // Parse the individual lines + for (auto line : lines) + { + line = AzFramework::StringFunc::TrimWhiteSpace(line, true, true); + // Check for comments and empty lines + if ((line.length() >= 2 && line[0] == '/' && line[1] == '/') || line.length() < 1) + { + continue; + } + else if (line.find('=') != -1) + { + AZStd::vector args; + AzFramework::StringFunc::Tokenize(line.c_str(), args, '=', true, true); + + if (args.size() > 2) + { + AZ_Error("AtlasBuilder", false, AZStd::string::format("Atlas Builder unable to parse line: Excessive '=' symbols were found: \"%s\"", line.c_str()).c_str()); + valid = false; + } + + // Trim whitespace + args[0] = AzFramework::StringFunc::TrimWhiteSpace(args[0], true, true); + args[1] = AzFramework::StringFunc::TrimWhiteSpace(args[1], true, true); + + // No case sensitivity for property names + AZStd::to_lower(args[0].begin(), args[0].end()); + + // Keep track of if the value is rejected + bool accepted = false; + + if (args[0] == "square") + { + accepted = AzFramework::StringFunc::LooksLikeBool(args[1].c_str()); + if (accepted) + { + data.m_forceSquare = AzFramework::StringFunc::ToBool(args[1].c_str()); + } + } + else if (args[0] == "poweroftwo") + { + accepted = AzFramework::StringFunc::LooksLikeBool(args[1].c_str()); + if (accepted) + { + data.m_forcePowerOf2 = AzFramework::StringFunc::ToBool(args[1].c_str()); + } + } + else if (args[0] == "whitetexture") + { + accepted = AzFramework::StringFunc::LooksLikeBool(args[1].c_str()); + if (accepted) + { + data.m_includeWhiteTexture = AzFramework::StringFunc::ToBool(args[1].c_str()); + } + } + else if (args[0] == "maxdimension") + { + accepted = AzFramework::StringFunc::LooksLikeInt(args[1].c_str()); + if (accepted) + { + data.m_maxDimension = AzFramework::StringFunc::ToInt(args[1].c_str()); + } + } + else if (args[0] == "padding") + { + accepted = AzFramework::StringFunc::LooksLikeInt(args[1].c_str()); + if (accepted) + { + data.m_padding = AzFramework::StringFunc::ToInt(args[1].c_str()); + } + } + else if (args[0] == "unusedcolor") + { + accepted = args[1].at(0) == '#' && args[1].length() == 9; + if (accepted) + { + AZStd::string color = AZStd::string::format("%s%s%s%s", args[1].substr(7).c_str(), args[1].substr(5, 2).c_str(), + args[1].substr(3, 2).c_str(), args[1].substr(1, 2).c_str()); + data.m_unusedColor.FromU32(AZStd::stoul(color, nullptr, 16)); + } + } + else if (args[0] == "presetname") + { + accepted = true; + data.m_presetName = args[1]; + } + else + { + // Supress accepted error because this error superceeds it + accepted = true; + valid = false; + AZ_Error("AtlasBuilder", false, AZStd::string::format("Atlas Builder unable to parse line: Unrecognized property: \"%s\"", args[0].c_str()).c_str()); + } + + // If the property is recognized but the value is rejected, fail the job + if (!accepted) + { + valid = false; + AZ_Error("AtlasBuilder", false, AZStd::string::format("Atlas Builder unable to parse line: Invalid value assigned to property: Property: \"%s\" Value: \"%s\"", args[0].c_str(), args[1].c_str()).c_str()); + } + } + else if ((line[0] == '-')) + { + // Remove image files + AZStd::string remove = line.substr(1); + remove = AzFramework::StringFunc::TrimWhiteSpace(remove, true, true); + if (remove.find('*') != -1) + { + AZStd::string resolvedAbsolutePath; + bool resolved = ResolveRelativePath(remove, directory, resolvedAbsolutePath); + if (resolved) + { + RemoveFilesUsingWildCard(data.m_filePaths, resolvedAbsolutePath); + } + else + { + valid = false; + AZ_Error("AtlasBuilder", false, AZStd::string::format("Atlas Builder unable to resolve relative path: %s", remove.c_str()).c_str()); + } + } + else if (IsFolderPath(remove)) + { + AZStd::string resolvedAbsolutePath; + bool resolved = ResolveRelativePath(remove, directory, resolvedAbsolutePath); + if (resolved) + { + RemoveFolderContents(data.m_filePaths, resolvedAbsolutePath); + } + else + { + valid = false; + AZ_Error("AtlasBuilder", false, AZStd::string::format("Atlas Builder unable to resolve relative path: %s", remove.c_str()).c_str()); + } + } + else + { + // Get the full path to the source image from the relative source path + AZStd::string fullSourceAssetPathName; + bool fullPathFound = GetAbsoluteSourcePathFromRelativePath(remove, fullSourceAssetPathName); + + if (!fullPathFound) + { + // Try to resolve relative path as it might be using "./" or "../" + fullPathFound = ResolveRelativePath(remove, directory, fullSourceAssetPathName); + } + + if (fullPathFound) + { + for (size_t i = 0; i < data.m_filePaths.size(); ++i) + { + if (data.m_filePaths[i] == fullSourceAssetPathName) + { + data.m_filePaths.erase(data.m_filePaths.begin() + i); + } + } + } + else + { + valid = false; + AZ_Error("AtlasBuilder", false, AZStd::string::format("Atlas Builder unable to get source asset path for image: %s", remove.c_str()).c_str()); + } + } + } + else + { + // Add image files + AzFramework::ApplicationRequests::Bus::Broadcast(&AzFramework::ApplicationRequests::NormalizePathKeepCase, line); + bool duplicate = false; + if (line.find('*') != -1) + { + AZStd::string resolvedAbsolutePath; + bool resolved = ResolveRelativePath(line, directory, resolvedAbsolutePath); + if (resolved) + { + AddFilesUsingWildCard(data.m_filePaths, resolvedAbsolutePath); + } + else + { + valid = false; + AZ_Error("AtlasBuilder", false, AZStd::string::format("Atlas Builder unable to resolve relative path: %s", line.c_str()).c_str()); + } + } + else if (IsFolderPath(line)) + { + AZStd::string resolvedAbsolutePath; + bool resolved = ResolveRelativePath(line, directory, resolvedAbsolutePath); + if (resolved) + { + AddFolderContents(data.m_filePaths, resolvedAbsolutePath, valid); + } + else + { + valid = false; + AZ_Error("AtlasBuilder", false, AZStd::string::format("Atlas Builder unable to resolve relative path: %s", line.c_str()).c_str()); + } + } + else + { + // Get the full path to the source image from the relative source path + AZStd::string fullSourceAssetPathName; + bool fullPathFound = GetAbsoluteSourcePathFromRelativePath(line, fullSourceAssetPathName); + + if (!fullPathFound) + { + // Try to resolve relative path as it might be using "./" or "../" + fullPathFound = ResolveRelativePath(line, directory, fullSourceAssetPathName); + } + + if (fullPathFound) + { + // Prevent duplicates + for (size_t i = 0; i < data.m_filePaths.size() && !duplicate; ++i) + { + duplicate = data.m_filePaths[i] == fullSourceAssetPathName; + } + if (!duplicate) + { + data.m_filePaths.push_back(fullSourceAssetPathName); + } + } + else + { + valid = false; + AZ_Error("AtlasBuilder", false, AZStd::string::format("Atlas Builder unable to get source asset path for image: %s", line.c_str()).c_str()); + } + } + } + } + + return data; + } + + void AtlasBuilderInput::AddFilesUsingWildCard(AZStd::vector& paths, const AZStd::string& insert) + { + const AZStd::string& fullPath = insert; + + AZStd::vector candidates; + AZStd::string fixedPath = fullPath.substr(0, fullPath.find('*')); + fixedPath = fixedPath.substr(0, fixedPath.find_last_of('/')); + candidates.push_back(fixedPath); + + AZStd::vector wildPath; + AzFramework::StringFunc::Tokenize(fullPath.substr(fixedPath.length()).c_str(), wildPath, "/"); + + for (size_t i = 0; i < wildPath.size() && candidates.size() > 0; ++i) + { + AZStd::vector nextCandidates; + for (size_t j = 0; j < candidates.size(); ++j) + { + AZStd::string compare = AZStd::string::format("%s/%s", candidates[j].c_str(), wildPath[i].c_str()); + QDir inputFolder(candidates[j].c_str()); + if (inputFolder.exists()) + { + QFileInfoList entries = inputFolder.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot | QDir::Files); + for (const QFileInfo& entry : entries) + { + AZStd::string child = (entry.filePath().toStdString()).c_str(); + AzFramework::ApplicationRequests::Bus::Broadcast(&AzFramework::ApplicationRequests::NormalizePathKeepCase, child); + if (DoesPathnameMatchWildCard(compare, child)) + { + nextCandidates.push_back(child); + } + } + } + } + candidates = nextCandidates; + } + + for (size_t i = 0; i < candidates.size(); ++i) + { + if (!IsFolderPath(candidates[i]) && !HasTrailingSlash(fullPath)) + { + AZStd::string ext; + AzFramework::StringFunc::Path::GetExtension(candidates[i].c_str(), ext, false); + if (ImageProcessing::IsExtensionSupported(ext.c_str()) && ext != "dds") + { + bool duplicate = false; + for (size_t j = 0; j < paths.size() && !duplicate; ++j) + { + duplicate = paths[j] == candidates[i]; + } + if (!duplicate) + { + paths.push_back(candidates[i]); + } + } + } + else if (IsFolderPath(candidates[i]) && HasTrailingSlash(fullPath)) + { + bool waste = true; + AddFolderContents(paths, candidates[i], waste); + } + } + } + + void AtlasBuilderInput::RemoveFilesUsingWildCard(AZStd::vector& paths, const AZStd::string& remove) + { + bool isDir = (remove.at(remove.length() - 1) == '/'); + for (size_t i = 0; i < paths.size(); ++i) + { + if (isDir ? DoesWildCardDirectoryIncludePathname(remove, paths[i]) : DoesPathnameMatchWildCard(remove, paths[i])) + { + paths.erase(paths.begin() + i); + --i; + } + } + } + + // Tells us if the child follows the rule + bool AtlasBuilderInput::DoesPathnameMatchWildCard(const AZStd::string& rule, const AZStd::string& child) + { + AZStd::vector rulePathTokens; + AzFramework::StringFunc::Tokenize(rule.c_str(), rulePathTokens, "/"); + AZStd::vector pathTokens; + AzFramework::StringFunc::Tokenize(child.c_str(), pathTokens, "/"); + if (rulePathTokens.size() != pathTokens.size()) + { + return false; + } + for (size_t i = 0; i < rulePathTokens.size(); ++i) + { + if (!TokenMatchesWildcard(rulePathTokens[i], pathTokens[i])) + { + return false; + } + } + return true; + } + + bool AtlasBuilderInput::DoesWildCardDirectoryIncludePathname(const AZStd::string& rule, const AZStd::string& child) + { + AZStd::vector rulePathTokens; + AzFramework::StringFunc::Tokenize(rule.c_str(), rulePathTokens, "/"); + AZStd::vector pathTokens; + AzFramework::StringFunc::Tokenize(child.c_str(), pathTokens, "/"); + if (rulePathTokens.size() >= pathTokens.size()) + { + return false; + } + for (size_t i = 0; i < rulePathTokens.size(); ++i) + { + if (!TokenMatchesWildcard(rulePathTokens[i], pathTokens[i])) + { + return false; + } + } + return true; + } + + bool AtlasBuilderInput::TokenMatchesWildcard(const AZStd::string& rule, const AZStd::string& child) + { + AZStd::vector ruleTokens; + AzFramework::StringFunc::Tokenize(rule.c_str(), ruleTokens, "*"); + size_t pos = 0; + int token = 0; + if (rule.at(0) != '*' && child.find(ruleTokens[0]) != 0) + { + return false; + } + + while (pos != AZStd::string::npos && token < ruleTokens.size()) + { + pos = child.find(ruleTokens[token], pos); + if (pos != AZStd::string::npos) + { + pos += ruleTokens[token].size(); + } + ++token; + } + return pos == child.size() || (pos != AZStd::string::npos && rule.at(rule.length() - 1) == '*'); + } + + // Replaces all folder paths with the files they contain + void AtlasBuilderInput::AddFolderContents(AZStd::vector& paths, const AZStd::string& insert, bool& valid) + { + QDir inputFolder(insert.c_str()); + + if (inputFolder.exists()) + { + QFileInfoList entries = inputFolder.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot | QDir::Files); + for (const QFileInfo& entry : entries) + { + AZStd::string child = (entry.filePath().toStdString()).c_str(); + AZStd::string ext; + bool isDir = !AzFramework::StringFunc::Path::GetExtension(child.c_str(), ext, false); + if (isDir) + { + AddFolderContents(paths, child, valid); + } + else if (ImageProcessing::IsExtensionSupported(ext.c_str()) && ext != "dds") + { + AzFramework::ApplicationRequests::Bus::Broadcast(&AzFramework::ApplicationRequests::NormalizePathKeepCase, child); + bool duplicate = false; + for (size_t i = 0; i < paths.size() && !duplicate; ++i) + { + duplicate = paths[i] == child; + } + if (!duplicate) + { + paths.push_back(child); + } + } + } + } + else + { + valid = false; + AZ_Error("AtlasBuilder", false, AZStd::string::format("Atlas Builder unable to find requested directory: %s", insert.c_str()).c_str()); + } + } + + // Removes all of the contents of a folder + void AtlasBuilderInput::RemoveFolderContents(AZStd::vector& paths, const AZStd::string& remove) + { + AZStd::string folder = remove; + AzFramework::StringFunc::Strip(folder, "/", false, false, true); + folder.append("/"); + for (size_t i = 0; i < paths.size(); ++i) + { + if (paths[i].find(folder) == 0) + { + paths.erase(paths.begin() + i); + --i; + } + } + } + + // Note - Shutdown will be called on a different thread than your process job thread + void AtlasBuilderWorker::ShutDown() { m_isShuttingDown = true; } + + void AtlasBuilderWorker::CreateJobs(const AssetBuilderSDK::CreateJobsRequest& request, + AssetBuilderSDK::CreateJobsResponse& response) + { + // Read in settings/filepaths to set dependencies + AZStd::string fullPath; + AzFramework::StringFunc::Path::Join( + request.m_watchFolder.c_str(), request.m_sourceFile.c_str(), fullPath, true, true); + // Check if input is valid + bool valid = true; + AtlasBuilderInput input = AtlasBuilderInput::ReadFromFile(fullPath, request.m_watchFolder, valid); + + // Set dependencies + for (int i = 0; i < input.m_filePaths.size(); ++i) + { + AssetBuilderSDK::SourceFileDependency dependency; + dependency.m_sourceFileDependencyPath = input.m_filePaths[i].c_str(); + response.m_sourceFileDependencyList.push_back(dependency); + } + + // We process the same file for all platforms + for (const AssetBuilderSDK::PlatformInfo& info : request.m_enabledPlatforms) + { + if (ImageProcessing::BuilderSettingManager::Instance()->DoesSupportPlatform(info.m_identifier)) + { + AssetBuilderSDK::JobDescriptor descriptor = GetJobDescriptor(request.m_sourceFile, input); + descriptor.SetPlatformIdentifier(info.m_identifier.c_str()); + response.m_createJobOutputs.push_back(descriptor); + } + } + + if (valid) + { + response.m_result = AssetBuilderSDK::CreateJobsResultCode::Success; + } + + return; + } + + AssetBuilderSDK::JobDescriptor AtlasBuilderWorker::GetJobDescriptor(const AZStd::string& sourceFile, const AtlasBuilderInput& input) + { + // Get the extension of the file + AZStd::string ext; + AzFramework::StringFunc::Path::GetExtension(sourceFile.c_str(), ext, false); + AZStd::to_upper(ext.begin(), ext.end()); + + AssetBuilderSDK::JobDescriptor descriptor; + descriptor.m_jobKey = ext + " Atlas"; + descriptor.m_critical = false; + descriptor.m_jobParameters[AZ_CRC("forceSquare")] = input.m_forceSquare ? "true" : "false"; + descriptor.m_jobParameters[AZ_CRC("forcePowerOf2")] = input.m_forcePowerOf2 ? "true" : "false"; + descriptor.m_jobParameters[AZ_CRC("includeWhiteTexture")] = input.m_includeWhiteTexture ? "true" : "false"; + descriptor.m_jobParameters[AZ_CRC("padding")] = AZStd::to_string(input.m_padding); + descriptor.m_jobParameters[AZ_CRC("maxDimension")] = AZStd::to_string(input.m_maxDimension); + descriptor.m_jobParameters[AZ_CRC("filePaths")] = AZStd::to_string(input.m_filePaths.size()); + + AZ::u32 col = input.m_unusedColor.ToU32(); + descriptor.m_jobParameters[AZ_CRC("unusedColor")] = AZStd::to_string(*reinterpret_cast(&col)); + descriptor.m_jobParameters[AZ_CRC("presetName")] = input.m_presetName; + + // The starting point for the list + const int start = static_cast(descriptor.m_jobParameters.size()) + 1; + descriptor.m_jobParameters[AZ_CRC("startPoint")] = AZStd::to_string(start); + + for (int i = 0; i < input.m_filePaths.size(); ++i) + { + descriptor.m_jobParameters[start + i] = input.m_filePaths[i]; + } + + return descriptor; + } + + void AtlasBuilderWorker::ProcessJob(const AssetBuilderSDK::ProcessJobRequest& request, + AssetBuilderSDK::ProcessJobResponse& response) + { + // Before we begin, let's make sure we are not meant to abort. + AssetBuilderSDK::JobCancelListener jobCancelListener(request.m_jobId); + + AZStd::vector productFilepaths; + + const AZStd::string path = request.m_fullPath; + + bool imageProcessingSuccessful = false; + + // read in settings/filepaths + AtlasBuilderInput input; + input.m_forceSquare = AzFramework::StringFunc::ToBool(request.m_jobDescription.m_jobParameters.find(AZ_CRC("forceSquare"))->second.c_str()); + input.m_forcePowerOf2 = AzFramework::StringFunc::ToBool(request.m_jobDescription.m_jobParameters.find(AZ_CRC("forcePowerOf2"))->second.c_str()); + input.m_includeWhiteTexture = AzFramework::StringFunc::ToBool(request.m_jobDescription.m_jobParameters.find(AZ_CRC("includeWhiteTexture"))->second.c_str()); + input.m_padding = AzFramework::StringFunc::ToInt(request.m_jobDescription.m_jobParameters.find(AZ_CRC("padding"))->second.c_str()); + input.m_maxDimension = AzFramework::StringFunc::ToInt(request.m_jobDescription.m_jobParameters.find(AZ_CRC("maxDimension"))->second.c_str()); + int startAsInt = AzFramework::StringFunc::ToInt(request.m_jobDescription.m_jobParameters.find(AZ_CRC("startPoint"))->second.c_str()); + int sizeAsInt = AzFramework::StringFunc::ToInt(request.m_jobDescription.m_jobParameters.find(AZ_CRC("filePaths"))->second.c_str()); + AZ::u32 start = static_cast(AZStd::max(0, startAsInt)); + AZ::u32 size = static_cast(AZStd::max(0, sizeAsInt)); + + int col = AzFramework::StringFunc::ToInt(request.m_jobDescription.m_jobParameters.find(AZ_CRC("unusedColor"))->second.c_str()); + input.m_unusedColor.FromU32(*reinterpret_cast(&col)); + + input.m_presetName = request.m_jobDescription.m_jobParameters.find(AZ_CRC("presetName"))->second; + + for (AZ::u32 i = 0; i < size; ++i) + { + input.m_filePaths.push_back(request.m_jobDescription.m_jobParameters.find(start + i)->second); + } + + if (input.m_filePaths.empty()) + { + AZ_Error("AtlasBuilder", false, "No image files specified. Cannot create an empty atlas."); + return; + } + + // Don't allow padding to be less than zero + if (input.m_padding < 0) + { + input.m_padding = 0; + } + + if (input.m_presetName.empty()) + { + // Default to the TextureAtlas preset which is currently set to use compression for all platforms except for iOS. + // Currently the only fully supported compression for iOS is PVRTC which requires the texture to be square and a power of 2. + // Due to this limitation, we default to using no compression for iOS until ASTC is fully supported + const AZStd::string defaultPresetName = "TextureAtlas"; + input.m_presetName = defaultPresetName; + } + + // Get a preset to use for the output image + const ImageProcessing::PresetSettings* preset = GetImageProcessPresetSettings(input.m_presetName, request.m_platformInfo.m_identifier); + if (preset) + { + // Check the preset's pixel format requirements + const ImageProcessing::PixelFormatInfo* pixelFormatInfo = ImageProcessing::CPixelFormats::GetInstance().GetPixelFormatInfo(preset->m_pixelFormat); + if (pixelFormatInfo && pixelFormatInfo->bSquarePow2) + { + // Override the user config settings to force square and power of 2. + // Otherwise the image conversion process will stretch the image to satisfy these requirements + input.m_forceSquare = true; + input.m_forcePowerOf2 = true; + } + } + else + { + AZ_Error("AtlasBuilder", false, "Could not find a preset setting for the output image."); + return; + } + + // Read in images + AZStd::vector images; + AZ::u64 totalArea = 0; + int maxArea = input.m_maxDimension * input.m_maxDimension; + bool sizeFailure = false; + for (int i = 0; i < input.m_filePaths.size() && !jobCancelListener.IsCancelled(); ++i) + { + ImageProcessing::IImageObject* inputImage = ImageProcessing::LoadImageFromFile(input.m_filePaths[i]); + // Check if we were able to load the image + if (inputImage) + { + ImageProcessing::IImageObjectPtr image = ImageProcessing::IImageObjectPtr(inputImage); + images.push_back(image); + totalArea += inputImage->GetWidth(0) * inputImage->GetHeight(0); + } + else + { + AZ_Error("AtlasBuilder", false, AZStd::string::format("Atlas Builder unable to load file: %s", input.m_filePaths[i].c_str()).c_str()); + return; + } + if (maxArea < totalArea) + { + sizeFailure = true; + } + } + // If we get cancelled, return + if (jobCancelListener.IsCancelled()) + { + return; + } + + if (sizeFailure) + { + AZ_Error("AtlasBuilder", false, AZStd::string::format("Total image area exceeds maximum alotted area. %llu > %d", totalArea, maxArea).c_str()); + return; + } + + // Convert all image paths to their output format referenced at runtime + for (auto& filePath : input.m_filePaths) + { + // Get path relative to the watch folder + bool result = false; + AZ::Data::AssetInfo info; + AZStd::string watchFolder; + AzToolsFramework::AssetSystemRequestBus::BroadcastResult(result, &AzToolsFramework::AssetSystemRequestBus::Events::GetSourceInfoBySourcePath, filePath.c_str(), info, watchFolder); + if (!result) + { + AZ_Error("AtlasBuilder", false, AZStd::string::format("Atlas Builder unable to get relative source path for image: %s", filePath.c_str()).c_str()); + return; + } + + // Remove extension + filePath = info.m_relativePath.substr(0, info.m_relativePath.find_last_of('.')); + + // Normalize path + AzFramework::ApplicationRequests::Bus::Broadcast(&AzFramework::ApplicationRequests::NormalizePathKeepCase, filePath); + } + + // Add white texture if we need to + if (input.m_includeWhiteTexture) + { + ImageProcessing::IImageObjectPtr texture(ImageProcessing::IImageObject::CreateImage( + cellSize, cellSize, 1, ImageProcessing::EPixelFormat::ePixelFormat_R8G8B8A8)); + + // Make the texture white + texture->ClearColor(1, 1, 1, 1); + images.push_back(texture); + input.m_filePaths.push_back("WhiteTexture"); + } + + // Generate algorithm inputs + ImageDimensionData data; + for (int i = 0; i < images.size(); ++i) + { + data.push_back(IndexImageDimension(i, + ImageDimension(images[i]->GetWidth(0), + images[i]->GetHeight(0)))); + } + AZStd::sort(data.begin(), data.end()); + + // Run algorithm + + // Variables that keep track of the optimal solution + int resultWidth = -1; + int resultHeight = -1; + + // Check that the max dimension is not large enough for the area to loop past the maximum integer + // This is important because we do not want the area to be calculated negative + if (input.m_maxDimension > 65535) + { + input.m_maxDimension = 65535; + } + + // Get the optimal mappings based on the input settings + AZStd::vector paddedMap; + size_t amountFit = 0; + if (!TryTightening( + input, data, GetWidest(data), GetTallest(data), aznumeric_cast(totalArea), input.m_padding, resultWidth, resultHeight, amountFit, paddedMap)) + { + AZ_Error("AtlasBuilder", false, AZStd::string::format("Cannot fit images into given maximum atlas size (%dx%d). Only %zu out of %zu images fit.", input.m_maxDimension, input.m_maxDimension, amountFit, input.m_filePaths.size()).c_str()); + // For some reason, failing the assert isn't enough to stop the Asset builder. It will still fail further + // down when it tries to assemble the atlas, but returning here is cleaner. + return; + } + + // Move coordinates from algorithm space to padded result space + TextureAtlasNamespace::AtlasCoordinateSets output; + resultWidth = 0; + resultHeight = 0; + AZStd::vector map; + for (int i = 0; i < paddedMap.size(); ++i) + { + map.push_back(AtlasCoordinates(paddedMap[i].GetLeft(), paddedMap[i].GetLeft() + images[data[i].first]->GetWidth(0), paddedMap[i].GetTop(), paddedMap[i].GetTop() + images[data[i].first]->GetHeight(0))); + resultHeight = resultHeight > map[i].GetBottom() ? resultHeight : map[i].GetBottom(); + resultWidth = resultWidth > map[i].GetRight() ? resultWidth : map[i].GetRight(); + + const AZStd::string& outputFilePath = input.m_filePaths[data[i].first]; + output.push_back(AZStd::pair(outputFilePath, map[i])); + } + if (input.m_forcePowerOf2) + { + resultWidth = aznumeric_cast(pow(2, 1 + IntegerLog2(static_cast(resultWidth - 1)))); + resultHeight = aznumeric_cast(pow(2, 1 + IntegerLog2(static_cast(resultHeight - 1)))); + } + else + { + resultWidth = (resultWidth + (cellSize - 1)) / cellSize * cellSize; + resultHeight = (resultHeight + (cellSize - 1)) / cellSize * cellSize; + } + if (input.m_forceSquare) + { + if (resultWidth > resultHeight) + { + resultHeight = resultWidth; + } + else + { + resultWidth = resultHeight; + } + } + + // Process texture sheet + ImageProcessing::IImageObjectPtr outImage(ImageProcessing::IImageObject::CreateImage( + resultWidth, resultHeight, 1, ImageProcessing::EPixelFormat::ePixelFormat_R8G8B8A8)); + + // Clear the sheet + outImage->ClearColor(input.m_unusedColor.GetR(), input.m_unusedColor.GetG(), input.m_unusedColor.GetB(), input.m_unusedColor.GetA()); + + AZ::u8* outBuffer = nullptr; + AZ::u32 outPitch; + outImage->GetImagePointer(0, outBuffer, outPitch); + + // Copy images over + for (int i = 0; i < map.size() && !jobCancelListener.IsCancelled(); ++i) + { + AZ::u8* inBuffer = nullptr; + AZ::u32 inPitch; + images[data[i].first]->GetImagePointer(0, inBuffer, inPitch); + int j = 0; + + // The padding calculated here is the amount of excess horizontal space measured in bytes that are in each + // row of the destination space AFTER the placement of the source row. + int rightPadding = (paddedMap[i].GetRight() - map[i].GetRight() - input.m_padding); + if (map[i].GetRight() + rightPadding > resultWidth) + { + rightPadding = resultWidth - map[i].GetRight(); + } + rightPadding *= bytesPerPixel; + int bottomPadding = (paddedMap[i].GetBottom() - map[i].GetBottom() - input.m_padding); + if (map[i].GetBottom() + bottomPadding > resultHeight) + { + bottomPadding = resultHeight - map[i].GetBottom(); + } + + int leftPadding = 0; + if (map[i].GetLeft() - input.m_padding >= 0) + { + leftPadding = input.m_padding * bytesPerPixel; + } + + int topPadding = 0; + if (map[i].GetTop() - input.m_padding >= 0) + { + topPadding = input.m_padding; + } + + for (j = 0; j < map[i].GetHeight(); ++j) + { + // When we multiply `map[i].GetLeft()` by 4, we are changing the measure from atlas space, to byte array + // space. The number is 4 because in this format, each pixel is 4 bytes long. + memcpy(outBuffer + (map[i].GetTop() + j) * outPitch + (map[i].GetLeft() * bytesPerPixel), + inBuffer + inPitch * j, + inPitch); + // Fill in the last bit of the row in the destination space with the same colors + SetPixels(outBuffer + (map[i].GetTop() + j) * outPitch + (map[i].GetLeft() * bytesPerPixel) + inPitch, + outBuffer + (map[i].GetTop() + j) * outPitch + (map[i].GetLeft() * bytesPerPixel) + inPitch - bytesPerPixel, + rightPadding); + // Fill in the first bit of the row in the destination space with the same colors + SetPixels(outBuffer + (map[i].GetTop() + j) * outPitch + (map[i].GetLeft() * bytesPerPixel) - leftPadding, + outBuffer + (map[i].GetTop() + j) * outPitch + (map[i].GetLeft() * bytesPerPixel), + leftPadding); + } + // Fill in the last few rows of the buffer with the same colors + for (; j < map[i].GetHeight() + bottomPadding; ++j) + { + memcpy(outBuffer + (map[i].GetTop() + j) * outPitch + (map[i].GetLeft() * bytesPerPixel) - leftPadding, + outBuffer + (map[i].GetBottom() - 1) * outPitch + (map[i].GetLeft() * bytesPerPixel) - leftPadding, + inPitch + leftPadding + rightPadding); + } + for (j = 1; j <= topPadding; ++j) + { + memcpy(outBuffer + (map[i].GetTop() - j) * outPitch + (map[i].GetLeft() * bytesPerPixel) - leftPadding, + outBuffer + map[i].GetTop() * outPitch + (map[i].GetLeft() * bytesPerPixel) - leftPadding, + inPitch + rightPadding + leftPadding); + } + } + + // If we get cancelled, return + if (jobCancelListener.IsCancelled()) + { + return; + } + + // Output Atlas Coordinates + AZStd::string fileName; + AZStd::string outputPath; + AzFramework::StringFunc::Path::GetFullFileName(request.m_sourceFile.c_str(), fileName); + fileName = fileName.append("idx"); + AzFramework::StringFunc::Path::Join( + request.m_tempDirPath.c_str(), fileName.c_str(), outputPath, true, true); + + // Output texture sheet + AZStd::string imageFileName, imageOutputPath; + AzFramework::StringFunc::Path::GetFileName(request.m_sourceFile.c_str(), imageFileName); + imageFileName += ".dds"; + AzFramework::StringFunc::Path::Join( + request.m_tempDirPath.c_str(), imageFileName.c_str(), imageOutputPath, true, true); + + // Let the ImageProcessor do the rest of the work. + ImageProcessing::TextureSettings textureSettings; + textureSettings.m_preset = preset->m_uuid; + + // Mipmaps for the texture atlas would require more work than the Image Processor does. This is because if we + // let the Image Processor make mipmaps, it might bleed the textures in the atlas together. + textureSettings.m_enableMipmap = false; + + // Check if the ImageBuilder wants to enable streaming + bool isStreaming = ImageProcessing::BuilderSettingManager::Instance() + ->GetBuilderSetting(request.m_platformInfo.m_identifier) + ->m_enableStreaming; + + bool canOverridePreset = false; + ImageProcessing::ImageConvertProcess* process = + new ImageProcessing::ImageConvertProcess(outImage, + textureSettings, + *preset, + false, + isStreaming, + canOverridePreset, + imageOutputPath, + request.m_platformInfo.m_identifier); + + if (process != nullptr) + { + // the process can be stopped if the job is cancelled or the worker is shutting down + while (!process->IsFinished() && !m_isShuttingDown && !jobCancelListener.IsCancelled()) + { + process->UpdateProcess(); + } + + // get process result + imageProcessingSuccessful = process->IsSucceed(); + process->GetAppendOutputFilePaths(productFilepaths); + + delete process; + } + else + { + imageProcessingSuccessful = false; + } + + if (imageProcessingSuccessful) + { + TextureAtlasNamespace::TextureAtlasRequestBus::Broadcast( + &TextureAtlasNamespace::TextureAtlasRequests::SaveAtlasToFile, outputPath, output, resultWidth, resultHeight); + response.m_outputProducts.push_back(AssetBuilderSDK::JobProduct(outputPath)); + response.m_outputProducts[static_cast(Product::TexatlasidxProduct)].m_productAssetType = azrtti_typeid(); + response.m_outputProducts[static_cast(Product::TexatlasidxProduct)].m_productSubID = 0; + + // The Image Processing Gem can produce multiple output files under certain + // circumstances, but the texture atlas is not expected to produce such output + if (productFilepaths.size() > 1) + { + AZ_Error("AtlasBuilder", false, "Image processing resulted in multiple output files. Texture atlas is expected to produce one output."); + response.m_outputProducts.clear(); + return; + } + + if (productFilepaths.size() > 0) + { + response.m_outputProducts.push_back(AssetBuilderSDK::JobProduct(productFilepaths[0])); + response.m_outputProducts.back().m_productAssetType = azrtti_typeid(); + response.m_outputProducts.back().m_productSubID = 1; + + // The texatlasidx file is a data file that indicates where the original parts are inside the atlas, + // and this would usually imply that it refers to its dds file in some way or needs it to function. + // The texatlasidx file should be the one that depends on the DDS because its possible to use the DDS + // without the texatlasid, but not the other way around + AZ::Data::AssetId productAssetId(request.m_sourceFileUUID, response.m_outputProducts.back().m_productSubID); + response.m_outputProducts[static_cast(Product::TexatlasidxProduct)].m_dependencies.push_back(AssetBuilderSDK::ProductDependency(productAssetId, 0)); + response.m_outputProducts[static_cast(Product::TexatlasidxProduct)].m_dependenciesHandled = true; // We've populated the dependencies immediately above so it's OK to tell the AP we've handled dependencies + } + response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success; + } + } + + bool AtlasBuilderWorker::TryPack(const ImageDimensionData& images, + int targetWidth, + int targetHeight, + int padding, + size_t& amountFit, + AZStd::vector& out) + { + // Start with one open slot and initialize a vector to store the closed products + AZStd::vector open; + AZStd::vector closed; + open.push_back(AtlasCoordinates(0, targetWidth, 0, targetHeight)); + bool slotNotFound = false; + for (size_t i = 0; i < images.size() && !slotNotFound; ++i) + { + slotNotFound = true; + // Try to place the image in every open slot + for (size_t j = 0; j < open.size(); ++j) + { + if (CanInsert(open[j], images[i].second, padding, targetWidth, targetHeight)) + { + // if it fits, subdivide the excess space in the slot, add it back to the open list and place the + // filled space into the closed vector + slotNotFound = false; + AtlasCoordinates spent(open[j].GetLeft(), + open[j].GetLeft() + images[i].second.m_width, + open[j].GetTop(), + open[j].GetTop() + images[i].second.m_height); + + // We are going to try pushing the object up / left to try to avoid creating tight open spaces. + bool needTrim = false; + AtlasCoordinates coords = spent; + // Modifying left will preserve width + coords.SetLeft(coords.GetLeft() - 1); + AddPadding(coords, padding, targetWidth, targetHeight); + while (spent.GetLeft() > 0 && !Collides(coords, closed)) + { + spent.SetLeft(coords.GetLeft()); + coords = spent; + coords.SetLeft(coords.GetLeft() - 1); + AddPadding(coords, padding, targetWidth, targetHeight); + needTrim = true; + } + // Refocus the search to see if we can push up + coords = spent; + coords.SetTop(coords.GetTop() - 1); + AddPadding(coords, padding, targetWidth, targetHeight); + while (spent.GetTop() > 0 && !Collides(coords, closed)) + { + spent.SetTop(coords.GetTop()); + coords = spent; + coords.SetTop(coords.GetTop() - 1); + AddPadding(coords, padding, targetWidth, targetHeight); + needTrim = true; + } + AddPadding(spent, padding, targetWidth, targetHeight); + if (needTrim) + { + TrimOverlap(open, spent); + closed.push_back(spent); + break; + } + AtlasCoordinates bigCoords; + AtlasCoordinates smallCoords; + + // Create the largest possible subdivision and another subdivision that uses the left over space + if (open[j].GetBottom() - spent.GetBottom() < open[j].GetRight() - spent.GetRight()) + { + smallCoords = AtlasCoordinates( + open[j].GetLeft(), spent.GetRight(), spent.GetBottom(), open[j].GetBottom()); + bigCoords = AtlasCoordinates(spent.GetRight(), open[j].GetRight(), open[j].GetTop(), smallCoords.GetBottom()); + } + else + { + bigCoords = AtlasCoordinates( + open[j].GetLeft(), open[j].GetRight(), spent.GetBottom(), open[j].GetBottom()); + smallCoords = AtlasCoordinates(spent.GetRight(), open[j].GetRight(), open[j].GetTop(), bigCoords.GetTop()); + } + + open.erase(open.begin() + j, open.begin() + j + 1); + if (bigCoords.GetHeight() > 0 && bigCoords.GetHeight() > 0) + { + InsertInOrder(open, bigCoords); + } + if (smallCoords.GetHeight() > 0 && smallCoords.GetHeight() > 0) + { + InsertInOrder(open, smallCoords); + } + + closed.push_back(spent); + break; + } + } + if (slotNotFound) + { + // If no single open slot can fit the object, do one last check to see if we can fit it in at any open + // corner. The reason we perform this check is in case the object can be fit across multiple different + // open spaces. If there is a space that an object can be fit in, it will probably involve the top left + // corner of that object in the top left corner of an open slot. This may miss some odd fits, but due to + // the nature of the packing algorithm, such solutions are highly unlikely to exist. If we wanted to + // expand the algorithm, we could theoretically base it on edges instead of corners to find all results, + // but it would not be time efficient. + for (size_t j = 0; j < open.size(); ++j) + { + AtlasCoordinates insert = AtlasCoordinates(open[j].GetLeft(), + open[j].GetLeft() + images[i].second.m_width, + open[j].GetTop(), + open[j].GetTop() + images[i].second.m_height); + AddPadding(insert, padding, targetWidth, targetHeight); + if (insert.GetRight() <= targetWidth && insert.GetBottom() <= targetHeight) + { + bool collision = Collides(insert, closed); + if (!collision) + { + closed.push_back(insert); + // Trim overlapping open slots + TrimOverlap(open, insert); + slotNotFound = false; + break; + } + } + } + } + } + // If we succeeded, update the output + if (!slotNotFound) + { + out = closed; + } + amountFit = amountFit > closed.size() ? amountFit : closed.size(); + return !slotNotFound; + } + + // Modifies slotList so that no items in slotList overlap with item + void AtlasBuilderWorker::TrimOverlap(AZStd::vector& slotList, AtlasCoordinates item) + { + for (size_t i = 0; i < slotList.size(); ++i) + { + if (Collides(slotList[i], item)) + { + // Subdivide the overlapping slot to seperate overlapping and non overlapping portions + AtlasCoordinates overlap = GetOverlap(item, slotList[i]); + AZStd::vector excess; + excess.push_back(AtlasCoordinates( + slotList[i].GetLeft(), overlap.GetRight(), slotList[i].GetTop(), overlap.GetTop())); + excess.push_back(AtlasCoordinates( + slotList[i].GetLeft(), overlap.GetLeft(), overlap.GetTop(), slotList[i].GetBottom())); + excess.push_back(AtlasCoordinates( + overlap.GetRight(), slotList[i].GetRight(), slotList[i].GetTop(), overlap.GetBottom())); + excess.push_back(AtlasCoordinates( + overlap.GetLeft(), slotList[i].GetRight(), overlap.GetBottom(), slotList[i].GetBottom())); + slotList.erase(slotList.begin() + i); + for (size_t j = 0; j < excess.size(); ++j) + { + if (excess[j].GetWidth() > 0 && excess[j].GetHeight() > 0) + { + InsertInOrder(slotList, excess[j]); + } + } + --i; + } + } + } + + // This function interprets input and performs the proper tightening option + bool AtlasBuilderWorker::TryTightening(AtlasBuilderInput input, + const ImageDimensionData& images, + int smallestWidth, + int smallestHeight, + int targetArea, + int padding, + int& resultWidth, + int& resultHeight, + size_t& amountFit, + AZStd::vector& out) + { + if (input.m_forceSquare) + { + return TryTighteningSquare(images, + smallestWidth > smallestHeight ? smallestWidth : smallestHeight, + input.m_maxDimension, + targetArea, + input.m_forcePowerOf2, + padding, + resultWidth, + resultHeight, + amountFit, + out); + } + else + { + return TryTighteningOptimal(images, + smallestWidth, + smallestHeight, + input.m_maxDimension, + targetArea, + input.m_forcePowerOf2, + padding, + resultWidth, + resultHeight, + amountFit, + out); + } + } + + // Finds the optimal square solution by starting with the ideal solution and expanding the size of the space until everything fits + bool AtlasBuilderWorker::TryTighteningSquare(const ImageDimensionData& images, + int lowerBound, + int maxDimension, + int targetArea, + bool powerOfTwo, + int padding, + int& resultWidth, + int& resultHeight, + size_t& amountFit, + AZStd::vector& out) + { + // Square solution cannot be smaller than the target area + int dimension = aznumeric_cast(sqrt(static_cast(targetArea))); + // Solution cannot be smaller than the smallest side + dimension = dimension > lowerBound ? dimension : lowerBound; + if (powerOfTwo) + { + // Starting dimension needs to be rounded up to the nearest power of two + dimension = aznumeric_cast(pow(2, 1 + IntegerLog2(static_cast(dimension - 1)))); + } + + AZStd::vector track; + // Expand the square until the contents fit + while (!TryPack(images, dimension, dimension, padding, amountFit, track) && dimension <= maxDimension) + { + // Step to the next valid value + dimension = powerOfTwo ? dimension * 2 : dimension + cellSize; + } + // Make sure we found a solution + if (dimension > maxDimension) + { + return false; + } + + resultHeight = dimension; + resultWidth = dimension; + out = track; + return true; + } + + // Finds the optimal solution by starting with a somewhat optimal solution and searching for better solutions + bool AtlasBuilderWorker::TryTighteningOptimal(const ImageDimensionData& images, + int smallestWidth, + int smallestHeight, + int maxDimension, + int targetArea, + bool powerOfTwo, + int padding, + int& resultWidth, + int& resultHeight, + size_t& amountFit, + AZStd::vector& out) + { + AZStd::vector track; + + // round max dimension down to a multiple of cellSize + AZ::u32 maxDimensionRounded = maxDimension - (maxDimension % cellSize); + + // The starting width is the larger of the widest individual texture and the width required + // to fit the total texture area given the max dimension + AZ::u32 smallestWidthDueToArea = targetArea / maxDimensionRounded; + AZ::u32 minWidth = AZStd::max(static_cast(smallestWidth), smallestWidthDueToArea); + + if (powerOfTwo) + { + // Starting dimension needs to be rounded up to the nearest power of two + minWidth = aznumeric_cast(pow(2, 1 + IntegerLog2(static_cast(minWidth - 1)))); + } + + // Round min width up to the nearest compression unit + minWidth = (minWidth + (cellSize - 1)) / cellSize * cellSize; + + AZ::u32 height = 0; + // Finds the optimal thin solution + // This uses a standard binary search to find the smallest width that can pack everything + AZ::u32 lower = minWidth; + AZ::u32 upper = maxDimensionRounded; + AZ::u32 width = 0; + while (lower <= upper) + { + AZ::u32 testWidth = (lower + upper) / 2; // must be divisible by cellSize because lower and upper are + bool canPack = TryPack(images, testWidth, maxDimension, padding, amountFit, track); + if (canPack) + { + // it packed, continue looking for smaller widths that pack + width = testWidth; // best fit so far + upper = testWidth - cellSize; + } + else + { + // it failed to pack, don't try any widths smaller than this + lower = testWidth + cellSize; + } + } + // Make sure we found a solution + if (width == 0) + { + return false; + } + + // Find the height of the solution + for (int i = 0; i < track.size(); ++i) + { + uint32 bottom = static_cast(AZStd::max(0, track[i].GetBottom())); + if (height < bottom) + { + height = bottom; + } + } + + // Fix height for power of two when applicable + if (powerOfTwo) + { + // Starting dimensions need to be rounded up to the nearest power of two + height = aznumeric_cast(pow(2, 1 + IntegerLog2(static_cast(height - 1)))); + } + + AZ::u32 resultArea = height * width; + // This for loop starts with the optimal thin width and makes it wider at each step. For each width, it + // calculates what height would be neccesary to have a more optimal solution than the stored solution. If the + // more optimal solution is valid, it tries shrinking the height until the solution fails. The loop ends when it + // is determined that a valid solution cannot exist at further steps + for (AZ::u32 testWidth = width; testWidth <= maxDimensionRounded && resultArea / testWidth >= static_cast(smallestHeight); + testWidth = powerOfTwo ? testWidth * 2 : testWidth + cellSize) + { + // The area of test height and width should be equal or less than resultArea + // Note: We don't need to force powers of two here because the Area and the width are already powers of two + int testHeight = resultArea / testWidth * cellSize / cellSize; + // Try the tighter pack + while (TryPack(images, static_cast(testWidth), testHeight, padding, amountFit, track)) + { + // Loop and continue to shrink the height until you cannot do so any further + width = testWidth; + height = testHeight; + resultArea = height * width; + // Try to step down a level + testHeight = powerOfTwo ? testHeight / 2 : testHeight - cellSize; + } + } + // Output the results of the function + out = track; + resultHeight = height; + resultWidth = width; + return true; + } + + // Allows us to keep the list of open spaces in order from lowest to highest area + void AtlasBuilderWorker::InsertInOrder(AZStd::vector& slotList, AtlasCoordinates item) + { + int area = item.GetWidth() * item.GetHeight(); + for (size_t i = 0; i < slotList.size(); ++i) + { + if (area < slotList[i].GetWidth() * slotList[i].GetHeight()) + { + slotList.insert(slotList.begin() + i, item); + return; + } + } + slotList.push_back(item); + } + + // Defines priority so that sorting can be meaningful. It may seem odd that larger items are "less than" smaller + // ones, but as this is a deduction of priority, not value, it is correct. + static bool operator<(ImageDimension a, ImageDimension b) + { + // Prioritize first by longest size + if ((a.m_width > a.m_height ? a.m_width : a.m_height) != (b.m_width > b.m_height ? b.m_width : b.m_height)) + { + return (a.m_width > a.m_height ? a.m_width : a.m_height) > (b.m_width > b.m_height ? b.m_width : b.m_height); + } + // Prioritize second by the length of the smaller side + if (a.m_width * a.m_height != b.m_width * b.m_height) + { + return a.m_width * a.m_height > b.m_width * b.m_height; + } + // Prioritize wider objects over taller objects for objects of the same size + else + { + return a.m_width > b.m_width; + } + } + + // Exposes priority logic to the sorting algorithm + static bool operator<(IndexImageDimension a, IndexImageDimension b) { return a.second < b.second; } + + // Tests if two coordinate sets intersect + bool Collides(AtlasCoordinates a, AtlasCoordinates b) + { + return !((a.GetRight() <= b.GetLeft()) || (a.GetBottom() <= b.GetTop()) || (b.GetRight() <= a.GetLeft()) + || (b.GetBottom() <= a.GetTop())); + } + + // Tests if an item collides with any items in a list + bool Collides(AtlasCoordinates item, AZStd::vector list) + { + for (size_t i = 0; i < list.size(); ++i) + { + if (Collides(list[i], item)) + { + return true; + } + } + return false; + } + + // Returns the overlap of two intersecting coordinate sets + AtlasCoordinates GetOverlap(AtlasCoordinates a, AtlasCoordinates b) + { + return AtlasCoordinates(b.GetLeft() > a.GetLeft() ? b.GetLeft() : a.GetLeft(), + b.GetRight() < a.GetRight() ? b.GetRight() : a.GetRight(), + b.GetTop() > a.GetTop() ? b.GetTop() : a.GetTop(), + b.GetBottom() < a.GetBottom() ? b.GetBottom() : a.GetBottom()); + } + + // Returns the width of the widest element in imageList + int AtlasBuilderWorker::GetWidest(const ImageDimensionData& imageList) + { + int max = 0; + for (size_t i = 0; i < imageList.size(); ++i) + { + if (max < imageList[i].second.m_width) + { + max = imageList[i].second.m_width; + } + } + return max; + } + + // Returns the height of the tallest element in imageList + int AtlasBuilderWorker::GetTallest(const ImageDimensionData& imageList) + { + int max = 0; + for (size_t i = 0; i < imageList.size(); ++i) + { + if (max < imageList[i].second.m_height) + { + max = imageList[i].second.m_height; + } + } + return max; + } + + // Performs an operation that copies a pixel to the output + void SetPixels(AZ::u8* dest, const AZ::u8* source, int destBytes) + { + if (destBytes >= bytesPerPixel) + { + memcpy(dest, source, bytesPerPixel); + int bytesCopied = bytesPerPixel; + while (bytesCopied * 2 < destBytes) + { + memcpy(dest + bytesCopied, dest, bytesCopied); + bytesCopied *= 2; + } + memcpy(dest + bytesCopied, dest, destBytes - bytesCopied); + } + } + + // Checks if we can insert an image into a slot + bool CanInsert(AtlasCoordinates slot, ImageDimension image, int padding, int farRight, int farBot) + { + int right = slot.GetLeft() + image.m_width; + if (slot.GetRight() < farRight) + { + // Add padding for my right border + right += padding; + // Round up to the nearest compression unit + right = (right + (cellSize - 1)) / cellSize * cellSize; + // Add padding for an adjacent unit's left border + right += padding; + } + + int bot = slot.GetTop() + image.m_height; + if (slot.GetBottom() < farBot) + { + // Add padding for my right border + bot += padding; + // Round up to the nearest compression unit + bot = (bot + (cellSize - 1)) / cellSize * cellSize; + // Add padding for an adjacent unit's left border + bot += padding; + } + + return slot.GetRight() >= right && slot.GetBottom() >= bot; + } + + // Adds the necessary padding to an Atlas Coordinate + void AddPadding(AtlasCoordinates& slot, int padding, [[maybe_unused]] int farRight, [[maybe_unused]] int farBot) + { + // Add padding for my right border + int right = slot.GetRight() + padding; + // Round up to the nearest compression unit + right = (right + (cellSize - 1)) / cellSize * cellSize; + // Add padding for an adjacent unit's left border + right += padding; + + // Add padding for my right border + int bot = slot.GetBottom() + padding; + // Round up to the nearest compression unit + bot = (bot + (cellSize - 1)) / cellSize * cellSize; + // Add padding for an adjacent unit's left border + bot += padding; + + slot.SetRight(right); + slot.SetBottom(bot); + } + +} diff --git a/Gems/TextureAtlas/Code/Source/Editor/AtlasBuilderWorker.h b/Gems/TextureAtlas/Code/Source/Editor/AtlasBuilderWorker.h new file mode 100644 index 0000000000..94e2b5b226 --- /dev/null +++ b/Gems/TextureAtlas/Code/Source/Editor/AtlasBuilderWorker.h @@ -0,0 +1,230 @@ +/* +* All or portions of this file Copyright(c) Amazon.com, Inc.or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +*or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +*WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#pragma once + +#include +#include +#include +#include +#include + +namespace TextureAtlasBuilder +{ + //! Struct that is used to communicate input commands + struct AtlasBuilderInput + { + AZ_CLASS_ALLOCATOR(AtlasBuilderInput, AZ::SystemAllocator, 0); + AZ_TYPE_INFO(AtlasBuilderInput, "{F54477F9-1BDE-4274-8CC0-8320A3EF4A42}"); + + bool m_forceSquare; + bool m_forcePowerOf2; + // Includes a white default texture for the UI to use under certain circumstances + bool m_includeWhiteTexture; + int m_maxDimension; + // At least this much padding will surround each texture except on the edges of the atlas + int m_padding; + // Color used in wasted space + AZ::Color m_unusedColor; + // A preset to use for the texture atlas image processing + AZStd::string m_presetName; + + AZStd::vector m_filePaths; + AtlasBuilderInput(): + m_forceSquare(false), + m_forcePowerOf2(false), + m_includeWhiteTexture(true), + m_maxDimension(4096), + m_padding(1), + // Default color should be a non-transparent color that isn't used often in uis + m_unusedColor(.235f, .702f, .443f, 1) + { + } + + static void Reflect(AZ::ReflectContext* context); + + //! Attempts to read the input from a .texatlas file. "valid" is for reporting exceptions and telling the asset + //! proccesor to fail the job. Supports parsing through a human readable custom parser. + static AtlasBuilderInput ReadFromFile(const AZStd::string& path, const AZStd::string& directory, bool& valid); + + //! Resolves any wild cards in paths + static void AddFilesUsingWildCard(AZStd::vector& paths, const AZStd::string& insert); + + //! Removes anything that matches the wildcard + static void RemoveFilesUsingWildCard(AZStd::vector& paths, const AZStd::string& remove); + + //! Compare considering wildcards + static bool DoesPathnameMatchWildCard(const AZStd::string& rule, const AZStd::string& path); + + //! As FollowsRule but allows extra items after the last '/' + static bool DoesWildCardDirectoryIncludePathname(const AZStd::string& rule, const AZStd::string& path); + + //! Helper function for DoesPathnameMatchWildCard + static bool TokenMatchesWildcard(const AZStd::string& rule, const AZStd::string& token); + + //! Resolves any folder paths into image file paths + static void AddFolderContents(AZStd::vector& paths, const AZStd::string& insert, bool& valid); + + //! Resolves remove commands for folders + static void RemoveFolderContents(AZStd::vector& paths, const AZStd::string& remove); + }; + + //! Struct that is used to represent an object with a width and height in pixels + struct ImageDimension + { + int m_width; + int m_height; + + ImageDimension(int width, int height) + { + m_width = width; + m_height = height; + } + }; + + //! Typedef for an ImageDimension paired with an integer + using IndexImageDimension = AZStd::pair; + + //! Typedef for a list of ImageDimensions paired with integers + using ImageDimensionData = AZStd::vector; + + //! Typedef to simplify references to TextureAtlas::AtlasCoordinates + using AtlasCoordinates = TextureAtlasNamespace::AtlasCoordinates; + + //! Number of bytes in a pixel + const int bytesPerPixel = 4; + + //! The size of the padded sorting units (important for compression) + const int cellSize = 4; + + //! Indexes of the products + enum class Product + { + TexatlasidxProduct = 0, + DdsProduct = 1 + }; + + //! An asset builder for texture atlases + class AtlasBuilderWorker : public AssetBuilderSDK::AssetBuilderCommandBus::Handler + { + public: + AZ_RTTI(AtlasBuilderWorker, "{79036188-E017-4575-9EC0-8D39CB560EA6}"); + + AtlasBuilderWorker() = default; + ~AtlasBuilderWorker() = default; + + //! Asset Builder Callback Functions + + //! Called by asset processor to gather information on a job for a ".texatlas" file + void CreateJobs(const AssetBuilderSDK::CreateJobsRequest& request, + AssetBuilderSDK::CreateJobsResponse& response); + //! Called by asset proccessor when it wants us to execute a job + void ProcessJob(const AssetBuilderSDK::ProcessJobRequest& request, + AssetBuilderSDK::ProcessJobResponse& response); + + //! Returns the job related information used by the builder + static AssetBuilderSDK::JobDescriptor GetJobDescriptor(const AZStd::string& sourceFile, const AtlasBuilderInput& input); + + ////////////////////////////////////////////////////////////////////////// + //! AssetBuilderSDK::AssetBuilderCommandBus interface + void ShutDown() override; // if you get this you must fail all existing jobs and return. + ////////////////////////////////////////////////////////////////////////// + + private: + bool m_isShuttingDown = false; + + //! This is the main function that takes a set of inputs and attempts to pack them into an atlas of a given + //! size. Returns true if succesful, does not update out on failure. + static bool TryPack(const ImageDimensionData& images, + int targetWidth, + int targetHeight, + int padding, + size_t& amountFit, + AZStd::vector& out); + + //! Removes any overlap between slotList and the given item + static void TrimOverlap(AZStd::vector& slotList, AtlasCoordinates item); + + //! Uses the proper tightening method based on the input and returns the maximum number of items that were able to be fit + bool TryTightening(AtlasBuilderInput input, + const ImageDimensionData& images, + int smallestWidth, + int smallestHeight, + int targetArea, + int padding, + int& resultWidth, + int& resultHeight, + size_t& amountFit, + AZStd::vector& out); + + //! Finds the tightest square fit achievable by expanding a square area until a valid fit is found + bool TryTighteningSquare(const ImageDimensionData& images, + int lowerBound, + int maxDimension, + int targetArea, + bool powerOfTwo, + int padding, + int& resultWidth, + int& resultHeight, + size_t& amountFit, + AZStd::vector& out); + + //! Finds the tightest fit achievable by starting with the optimal thin solution and attempting to resize to be + //! a better shape + bool TryTighteningOptimal(const ImageDimensionData& images, + int smallestWidth, + int smallestHeight, + int maxDimension, + int targetArea, + bool powerOfTwo, + int padding, + int& resultWidth, + int& resultHeight, + size_t& amountFit, + AZStd::vector& out); + + //! Sorting logic for adding a slot to a sorted list in order to maintain increasing order + static void InsertInOrder(AZStd::vector& slotList, AtlasCoordinates item); + + //! Misc Logic For Estimating Target Shape + + //! Returns the width of the widest element + static int GetWidest(const ImageDimensionData& imageList); + + //! Returns the height of the tallest area + static int GetTallest(const ImageDimensionData& imageList); + }; + + //! Used for sorting ImageDimensions + static bool operator<(ImageDimension a, ImageDimension b); + + //! Used to expose the ImageDimension in a pair to AZStd::Sort + static bool operator<(IndexImageDimension a, IndexImageDimension b); + + //! Returns true if two coordinate sets overlap + static bool Collides(AtlasCoordinates a, AtlasCoordinates b); + + //! Returns true if item collides with any object in list + static bool Collides(AtlasCoordinates item, AZStd::vector list); + + //! Returns the portion of the second item that overlaps with the first + static AtlasCoordinates GetOverlap(AtlasCoordinates a, AtlasCoordinates b); + + //! Performs an operation that copies a pixel to the output + static void SetPixels(AZ::u8* dest, const AZ::u8* source, int destBytes); + + //! Checks if we can insert an image into a slot + static bool CanInsert(AtlasCoordinates slot, ImageDimension image, int padding, int farRight, int farBot); + + //! Adds the necessary padding to an Atlas Coordinate + static void AddPadding(AtlasCoordinates& slot, int padding, int farRight, int farBot); +} diff --git a/Registry/sceneassetimporter.setreg b/Registry/sceneassetimporter.setreg new file mode 100644 index 0000000000..bd7c4d0705 --- /dev/null +++ b/Registry/sceneassetimporter.setreg @@ -0,0 +1,16 @@ +{ + "O3DE": + { + "SceneAPI": + { + "AssetImporter": + { + "SupportedFileTypeExtensions": + [ + ".fbx", + ".stl" + ] + } + } + } +} \ No newline at end of file diff --git a/cmake/3rdParty/Platform/Mac/BuiltInPackages_mac.cmake b/cmake/3rdParty/Platform/Mac/BuiltInPackages_mac.cmake index 4ef0f8af85..8df46e2b1a 100644 --- a/cmake/3rdParty/Platform/Mac/BuiltInPackages_mac.cmake +++ b/cmake/3rdParty/Platform/Mac/BuiltInPackages_mac.cmake @@ -27,9 +27,8 @@ ly_associate_package(PACKAGE_NAME expat-2.1.0-multiplatform ly_associate_package(PACKAGE_NAME zstd-1.35-multiplatform TARGETS zstd PACKAGE_HASH 45d466c435f1095898578eedde85acf1fd27190e7ea99aeaa9acfd2f09e12665) ly_associate_package(PACKAGE_NAME SQLite-3.32.2-rev3-multiplatform TARGETS SQLite PACKAGE_HASH dd4d3de6cbb4ce3d15fc504ba0ae0587e515dc89a25228037035fc0aef4831f4) ly_associate_package(PACKAGE_NAME SPIRVCross-2020.04.20-rev1-multiplatform TARGETS SPIRVCross PACKAGE_HASH 7c8c0eaa0166c26745c62d2238525af7e27ac058a5db3defdbaec1878e8798dd) -ly_associate_package(PACKAGE_NAME DirectXShaderCompilerDxc-2020.08.07-rev1-multiplatform TARGETS DirectXShaderCompilerDxc PACKAGE_HASH 04a6850ce03d4c16e19ed206f7093d885276dfb74047e6aa99f0a834c8b7cc73) -ly_associate_package(PACKAGE_NAME DirectXShaderCompilerDxcAz-5.0.0_az-rev1-multiplatform TARGETS DirectXShaderCompilerDxcAz PACKAGE_HASH 94f24989a7a371d840b513aa5ffaff02747b3d19b119bc1f899427e29978f753) -ly_associate_package(PACKAGE_NAME azslc-1.7.20-rev1-multiplatform TARGETS azslc PACKAGE_HASH 45d55f28bea2ef823ed3204f60df52e5e329f42923923d4555fdbdf3bea0af60) +ly_associate_package(PACKAGE_NAME DirectXShaderCompiler-1.6.2104-o3de-rev1-mac TARGETS DirectXShaderCompilerDxc PACKAGE_HASH 4e97484f8fcf73fc39f22fc85ae86933a8f2e3ba0748fcec128bce05795035a6) +ly_associate_package(PACKAGE_NAME azslc-1.7.21-rev1-multiplatform TARGETS azslc PACKAGE_HASH 772b7a2d9cc68aa1da4f0ee7db57ee1b4e7a8f20b81961fc5849af779582f4df) ly_associate_package(PACKAGE_NAME glad-2.0.0-beta-rev2-multiplatform TARGETS glad PACKAGE_HASH ff97ee9664e97d0854b52a3734c2289329d9f2b4cd69478df6d0ca1f1c9392ee) ly_associate_package(PACKAGE_NAME lux_core-2.2-rev5-multiplatform TARGETS lux_core PACKAGE_HASH c8c13cf7bc351643e1abd294d0841b24dee60e51647dff13db7aec396ad1e0b5) ly_associate_package(PACKAGE_NAME xxhash-0.7.4-rev1-multiplatform TARGETS xxhash PACKAGE_HASH e81f3e6c4065975833996dd1fcffe46c3cf0f9e3a4207ec5f4a1b564ba75861e) diff --git a/cmake/3rdParty/Platform/Windows/BuiltInPackages_windows.cmake b/cmake/3rdParty/Platform/Windows/BuiltInPackages_windows.cmake index b36248b929..4f3b91c633 100644 --- a/cmake/3rdParty/Platform/Windows/BuiltInPackages_windows.cmake +++ b/cmake/3rdParty/Platform/Windows/BuiltInPackages_windows.cmake @@ -27,9 +27,8 @@ ly_associate_package(PACKAGE_NAME expat-2.1.0-multiplatform ly_associate_package(PACKAGE_NAME zstd-1.35-multiplatform TARGETS zstd PACKAGE_HASH 45d466c435f1095898578eedde85acf1fd27190e7ea99aeaa9acfd2f09e12665) ly_associate_package(PACKAGE_NAME SQLite-3.32.2-rev3-multiplatform TARGETS SQLite PACKAGE_HASH dd4d3de6cbb4ce3d15fc504ba0ae0587e515dc89a25228037035fc0aef4831f4) ly_associate_package(PACKAGE_NAME SPIRVCross-2020.04.20-rev1-multiplatform TARGETS SPIRVCross PACKAGE_HASH 7c8c0eaa0166c26745c62d2238525af7e27ac058a5db3defdbaec1878e8798dd) -ly_associate_package(PACKAGE_NAME DirectXShaderCompilerDxc-2020.08.07-rev1-multiplatform TARGETS DirectXShaderCompilerDxc PACKAGE_HASH 04a6850ce03d4c16e19ed206f7093d885276dfb74047e6aa99f0a834c8b7cc73) -ly_associate_package(PACKAGE_NAME DirectXShaderCompilerDxcAz-5.0.0_az-rev1-multiplatform TARGETS DirectXShaderCompilerDxcAz PACKAGE_HASH 94f24989a7a371d840b513aa5ffaff02747b3d19b119bc1f899427e29978f753) -ly_associate_package(PACKAGE_NAME azslc-1.7.20-rev1-multiplatform TARGETS azslc PACKAGE_HASH 45d55f28bea2ef823ed3204f60df52e5e329f42923923d4555fdbdf3bea0af60) +ly_associate_package(PACKAGE_NAME DirectXShaderCompiler-1.6.2104-o3de-rev1-windows TARGETS DirectXShaderCompilerDxc PACKAGE_HASH 2c60297758d73f7833911e5ae3006fe0b10ced6e0b1b54764b33ae2b86e0d41d) +ly_associate_package(PACKAGE_NAME azslc-1.7.21-rev1-multiplatform TARGETS azslc PACKAGE_HASH 772b7a2d9cc68aa1da4f0ee7db57ee1b4e7a8f20b81961fc5849af779582f4df) ly_associate_package(PACKAGE_NAME glad-2.0.0-beta-rev2-multiplatform TARGETS glad PACKAGE_HASH ff97ee9664e97d0854b52a3734c2289329d9f2b4cd69478df6d0ca1f1c9392ee) ly_associate_package(PACKAGE_NAME lux_core-2.2-rev5-multiplatform TARGETS lux_core PACKAGE_HASH c8c13cf7bc351643e1abd294d0841b24dee60e51647dff13db7aec396ad1e0b5) ly_associate_package(PACKAGE_NAME xxhash-0.7.4-rev1-multiplatform TARGETS xxhash PACKAGE_HASH e81f3e6c4065975833996dd1fcffe46c3cf0f9e3a4207ec5f4a1b564ba75861e)