Merge branch 'main' into SPEC6477_NetworkContextScriptCanvas
This commit is contained in:
@@ -43,6 +43,7 @@ set(GEM_DEPENDENCIES
|
||||
Gem::GradientSignal
|
||||
Gem::Vegetation
|
||||
Gem::Atom_AtomBridge
|
||||
Gem::AtomFont
|
||||
Gem::NvCloth
|
||||
Gem::Blast
|
||||
Gem::AWSCore
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.'
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:43b1a23b62fe2ffa05545ac99524f40b6fff49d6e35925b9d6138c00d8082e86
|
||||
size 9073
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,6 @@
|
||||
<download name="ClientAuthPasswordSignIn" type="Map">
|
||||
<index src="filelist.xml" dest="filelist.xml"/>
|
||||
<files>
|
||||
<file src="level.pak" dest="level.pak" size="DE5" md5="29bd2aae87e99a8fd026fe0cf8156071"/>
|
||||
</files>
|
||||
</download>
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:f583e0b1b7016a11583383e6c6fcd29f9e796c1a9cd4b6ddb10f7dc91deec17a
|
||||
size 3557
|
||||
@@ -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
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:3b948461412d201b3a80abafa60e916f860e46e28109333fbd263a2d5fc53c5a
|
||||
size 9103
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,6 @@
|
||||
<download name="ClientAuthPasswordSignUp" type="Map">
|
||||
<index src="filelist.xml" dest="filelist.xml"/>
|
||||
<files>
|
||||
<file src="level.pak" dest="level.pak" size="DE1" md5="0795859fc4ac009a50dd08ffabb03dec"/>
|
||||
</files>
|
||||
</download>
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:8cdb456f6eb348be27249d80e9d2262e1e0bdabf2c1ff02c1a64a5609dcd823c
|
||||
size 3553
|
||||
@@ -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
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"AWS":
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -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<DiscreteInputEvent>(&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<DiscreteInputEvent>(&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<DiscreteInputEvent>(&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<DiscreteInputEvent>(&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<ScrollEvent>(&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<DiscreteInputEvent>(&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<ScrollEvent>(&event))
|
||||
{
|
||||
BeginActivation();
|
||||
}
|
||||
|
||||
return !Idle();
|
||||
}
|
||||
|
||||
Camera ScrollTranslationCameraInput::StepCamera(
|
||||
|
||||
@@ -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> 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> 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<AZStd::shared_ptr<CameraInput>> m_activeCameraInputs;
|
||||
AZStd::vector<AZStd::shared_ptr<CameraInput>> 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<AZStd::optional<AZ::Vector3>()>;
|
||||
|
||||
// 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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -84,6 +84,7 @@ namespace AzToolsFramework
|
||||
|
||||
AZStd::vector<AZ::Entity*> entities;
|
||||
AZStd::vector<AZStd::unique_ptr<Instance>> instances;
|
||||
AZStd::unordered_map<Instance*, PrefabDom> 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<Instance>& 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<AZ::u64>(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(
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include <AzFramework/Entity/EntityDebugDisplayBus.h>
|
||||
#include <AzFramework/Viewport/CameraState.h>
|
||||
#include <AzFramework/Viewport/ViewportId.h>
|
||||
#include <AzFramework/Viewport/ClickDetector.h>
|
||||
#include <AzToolsFramework/Entity/EditorEntityContextBus.h>
|
||||
#include <AzToolsFramework/Viewport/ViewportTypes.h>
|
||||
|
||||
@@ -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
|
||||
|
||||
+9
-4
@@ -14,6 +14,7 @@
|
||||
|
||||
#include <AzFramework/Entity/EntityDebugDisplayBus.h>
|
||||
#include <AzToolsFramework/ViewportSelection/EditorSelectionUtil.h>
|
||||
#include <AzToolsFramework/Viewport/ViewportMessages.h>
|
||||
|
||||
#include <QApplication>
|
||||
|
||||
@@ -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();
|
||||
|
||||
+21
-17
@@ -14,6 +14,8 @@
|
||||
|
||||
#include <AzCore/std/functional.h>
|
||||
#include <AzCore/std/optional.h>
|
||||
#include <AzFramework/Viewport/ClickDetector.h>
|
||||
#include <AzFramework/Viewport/CursorState.h>
|
||||
#include <AzToolsFramework/Viewport/ViewportTypes.h>
|
||||
|
||||
#include <QRect>
|
||||
@@ -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<void(const ViewportInteraction::MouseInteractionEvent& mouseInteraction)>& leftMouseDown);
|
||||
/// Set the mouse move callback.
|
||||
//! Set the mouse move callback.
|
||||
void InstallMouseMove(
|
||||
const AZStd::function<void(const ViewportInteraction::MouseInteractionEvent& mouseInteraction)>& mouseMove);
|
||||
/// Set the left mouse up callback.
|
||||
//! Set the left mouse up callback.
|
||||
void InstallLeftMouseUp(
|
||||
const AZStd::function<void()>& leftMouseUp);
|
||||
/// Set the display scene callback.
|
||||
//! Set the display scene callback.
|
||||
void InstallDisplayScene(
|
||||
const AZStd::function<void(
|
||||
const AzFramework::ViewportInfo& viewportInfo,
|
||||
AzFramework::DebugDisplayRequests& debugDisplay)>& 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<QRect>& 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<void(
|
||||
const AzFramework::ViewportInfo& viewportInfo, AzFramework::DebugDisplayRequests& debugDisplay)> m_displayScene;
|
||||
|
||||
AZStd::optional<QRect> 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<QRect> 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
|
||||
|
||||
+1
-16
@@ -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());
|
||||
|
||||
|
||||
@@ -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 <AzCore/UnitTest/TestTypes.h>
|
||||
#include <AzCore/std/smart_ptr/make_shared.h>
|
||||
#include <AzFramework/Input/Devices/Keyboard/InputDeviceKeyboard.h>
|
||||
#include <AzFramework/Input/Devices/Mouse/InputDeviceMouse.h>
|
||||
#include <AzFramework/Viewport/CameraInput.h>
|
||||
#include <AzFramework/Windowing/WindowBus.h>
|
||||
|
||||
namespace UnitTest
|
||||
{
|
||||
class CameraInputFixture : public AllocatorsTestFixture
|
||||
{
|
||||
public:
|
||||
AzFramework::Camera m_camera;
|
||||
AzFramework::Camera m_targetCamera;
|
||||
AZStd::shared_ptr<AzFramework::CameraSystem> 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<AzFramework::CameraSystem>();
|
||||
|
||||
auto firstPersonRotateCamera = AZStd::make_shared<AzFramework::RotateCameraInput>(AzFramework::InputDeviceMouse::Button::Right);
|
||||
auto firstPersonTranslateCamera = AZStd::make_shared<AzFramework::TranslateCameraInput>(AzFramework::LookTranslation);
|
||||
|
||||
auto orbitCamera = AZStd::make_shared<AzFramework::OrbitCameraInput>();
|
||||
auto orbitRotateCamera = AZStd::make_shared<AzFramework::RotateCameraInput>(AzFramework::InputDeviceMouse::Button::Left);
|
||||
auto orbitTranslateCamera = AZStd::make_shared<AzFramework::TranslateCameraInput>(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<bool>{consumed1, consumed2, consumed3, consumed4, consumed5};
|
||||
|
||||
using ::testing::ElementsAre;
|
||||
EXPECT_THAT(allConsumed, ElementsAre(false, true, false, true, false));
|
||||
}
|
||||
} // namespace UnitTest
|
||||
@@ -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
|
||||
|
||||
@@ -17,6 +17,7 @@ set(FILES
|
||||
BinToTextEncode.cpp
|
||||
ComponentAddRemove.cpp
|
||||
ComponentAdapterTests.cpp
|
||||
CameraInputTests.cpp
|
||||
ClickDetectorTests.cpp
|
||||
CursorStateTests.cpp
|
||||
EntityContext.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<AzFramework::NativeWindowHandle>(winId()));
|
||||
}
|
||||
|
||||
m_updatingCameraPosition = true;
|
||||
if (!ed_useNewCameraSystem)
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -22,7 +22,9 @@
|
||||
namespace SandboxEditor
|
||||
{
|
||||
class ModernViewportCameraControllerInstance;
|
||||
class ModernViewportCameraController : public AzFramework::MultiViewportController<ModernViewportCameraControllerInstance>
|
||||
class ModernViewportCameraController
|
||||
: public AzFramework::MultiViewportController<
|
||||
ModernViewportCameraControllerInstance, AzFramework::ViewportControllerPriority::DispatchToAllPriorities>
|
||||
{
|
||||
public:
|
||||
using CameraListBuilder = AZStd::function<void(AzFramework::Cameras&)>;
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.1359 15.3956L19.6728 7.8125L21.087 9.22671L12 18.5245L12 18.5245L2.86273 9.27696L4.27695 7.86275L12.1359 15.3956Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 286 B |
@@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.2732 10.1288L19.8101 17.7119L21.2243 16.2977L12.1373 6.99994L12.1373 6.99995L3 16.2475L4.41421 17.6617L12.2732 10.1288Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 292 B |
@@ -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<GemInfo> GemCatalogScreen::GenerateTestData()
|
||||
|
||||
@@ -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 "<Unknown Platform>";
|
||||
}
|
||||
}
|
||||
|
||||
QString GemInfo::GetTypeString(Type type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case Asset:
|
||||
return "Asset";
|
||||
case Code:
|
||||
return "Code";
|
||||
case Tool:
|
||||
return "Tool";
|
||||
default:
|
||||
return "<Unknown Type>";
|
||||
}
|
||||
}
|
||||
|
||||
bool GemInfo::IsPlatformSupported(Platform platform) const
|
||||
{
|
||||
return (m_platforms & platform);
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -10,7 +10,8 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#include "GemModel.h"
|
||||
#include <AzCore/std/string/string.h>
|
||||
#include <GemCatalog/GemModel.h>
|
||||
|
||||
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<AZStd::string>().c_str();
|
||||
item->setData(uuidString, RoleUuid);
|
||||
item->setData(gemInfo.m_creator, RoleCreator);
|
||||
item->setData(static_cast<int>(gemInfo.m_platforms), RolePlatforms);
|
||||
item->setData(aznumeric_cast<int>(gemInfo.m_platforms), RolePlatforms);
|
||||
item->setData(aznumeric_cast<int>(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<GemInfo::Platforms>(modelIndex.data(RolePlatforms).toInt());
|
||||
}
|
||||
|
||||
GemInfo::Types GemModel::GetTypes(const QModelIndex& modelIndex)
|
||||
{
|
||||
return static_cast<GemInfo::Types>(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<QString> 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<QString> gemNameOutcome = FindGemNameByUuidString(dependingGemString);
|
||||
if (gemNameOutcome.IsSuccess())
|
||||
{
|
||||
dependingGemString = gemNameOutcome.GetValue();
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
QStringList GemModel::GetConflictingGems(const QModelIndex& modelIndex)
|
||||
|
||||
@@ -13,7 +13,8 @@
|
||||
#pragma once
|
||||
|
||||
#if !defined(Q_MOC_RUN)
|
||||
#include "GemInfo.h"
|
||||
#include <AzCore/Outcome/Outcome.h>
|
||||
#include <GemCatalog/GemInfo.h>
|
||||
#include <QAbstractItemModel>
|
||||
#include <QStandardItemModel>
|
||||
#include <QItemSelectionModel>
|
||||
@@ -33,14 +34,18 @@ namespace O3DE::ProjectManager
|
||||
void AddGem(const GemInfo& gemInfo);
|
||||
void Clear();
|
||||
|
||||
AZ::Outcome<QString> 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<QString, QString> m_uuidToNameMap;
|
||||
QItemSelectionModel* m_selectionModel = nullptr;
|
||||
};
|
||||
} // namespace O3DE::ProjectManager
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<AZStd::string>().c_str());
|
||||
}
|
||||
}
|
||||
if (data.contains("Tags"))
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
<file>Resources/iOS.svg</file>
|
||||
<file>Resources/Linux.svg</file>
|
||||
<file>Resources/macOS.svg</file>
|
||||
<file>Resources/ArrowDownLine.svg</file>
|
||||
<file>Resources/ArrowUpLine.svg</file>
|
||||
<file>Resources/Backgrounds/FirstTimeBackgroundImage.jpg</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
||||
@@ -41,18 +41,6 @@ namespace AZ
|
||||
static AZ::SceneAPI::FbxSceneImporter::FbxImportRequestHandler* g_fbxImporter = nullptr;
|
||||
static AZStd::vector<AZ::ComponentDescriptor*> 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<AZ::EnvironmentInstance>(env));
|
||||
AZ::SceneAPI::FbxSceneBuilder::Initialize();
|
||||
}
|
||||
extern "C" AZ_DLL_EXPORT void Reflect(AZ::SerializeContext* context)
|
||||
{
|
||||
|
||||
@@ -10,12 +10,16 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#include <AssetProcessor/AssetBuilderSDK/AssetBuilderSDK/AssetBuilderSDK.h>
|
||||
#include <AzCore/Serialization/EditContextConstants.inl>
|
||||
#include <AzCore/Serialization/SerializeContext.h>
|
||||
#include <AzFramework/StringFunc/StringFunc.h>
|
||||
#include <AzCore/Serialization/Json/JsonSerialization.h>
|
||||
#include <AzCore/Settings/SettingsRegistry.h>
|
||||
#include <AzCore/StringFunc/StringFunc.h>
|
||||
#include <SceneAPI/FbxSceneBuilder/FbxImportRequestHandler.h>
|
||||
#include <SceneAPI/SceneCore/Containers/Scene.h>
|
||||
#include <SceneAPI/SceneCore/Events/CallProcessorBus.h>
|
||||
#include <SceneAPI/SceneCore/Events/ImportEventContext.h>
|
||||
#include <SceneAPI/FbxSceneBuilder/FbxImportRequestHandler.h>
|
||||
|
||||
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<AZ::SerializeContext*>(context); serializeContext)
|
||||
{
|
||||
serializeContext->Class<SceneImporterSettings>()
|
||||
->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<SerializeContext*>(context);
|
||||
if (serializeContext)
|
||||
{
|
||||
serializeContext->Class<FbxImportRequestHandler, SceneCore::BehaviorComponent>()->Version(1);
|
||||
serializeContext->Class<FbxImportRequestHandler, AZ::Component>()->Version(1)->Attribute(
|
||||
AZ::Edit::Attributes::SystemComponentTags,
|
||||
AZStd::vector<AZ::Crc32>({AssetBuilderSDK::ComponentTags::AssetBuilder}));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void FbxImportRequestHandler::GetSupportedFileExtensions(AZStd::unordered_set<AZStd::string>& 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
|
||||
|
||||
@@ -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<AZStd::string> 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
|
||||
|
||||
@@ -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,
|
||||
|
||||
+41
@@ -13,8 +13,14 @@
|
||||
#pragma once
|
||||
|
||||
#include <AzCore/EBus/EBus.h>
|
||||
#include <AzCore/Asset/AssetCommon.h>
|
||||
#include <Atom/ImageProcessing/ImageObject.h>
|
||||
|
||||
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<ImageProcessingRequests>;
|
||||
|
||||
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<AssetBuilderSDK::JobProduct> 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<ImageBuilderRequests>;
|
||||
} // namespace ImageProcessingAtom
|
||||
|
||||
-2
@@ -13,8 +13,6 @@
|
||||
|
||||
#include <AzCore/EBus/EBus.h>
|
||||
|
||||
class QString;
|
||||
|
||||
namespace ImageProcessingAtomEditor
|
||||
{
|
||||
class ImageProcessingEditorRequests
|
||||
|
||||
@@ -88,11 +88,13 @@ namespace ImageProcessingAtom
|
||||
m_assetHandlers.emplace_back(AZ::RPI::MakeAssetHandler<AZ::RPI::StreamingImageAssetHandler>());
|
||||
|
||||
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<AssetBuilderSDK::JobProduct> 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<AssetBuilderSDK::JobProduct> 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<ImageConvertProcessDescriptor> desc = AZStd::make_unique<ImageConvertProcessDescriptor>();
|
||||
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
|
||||
|
||||
@@ -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<AssetBuilderSDK::JobProduct> 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;
|
||||
|
||||
|
||||
@@ -98,7 +98,6 @@ ly_add_target(
|
||||
Gem::Atom_RPI.Edit
|
||||
RUNTIME_DEPENDENCIES
|
||||
3rdParty::DirectXShaderCompilerDxc
|
||||
3rdParty::DirectXShaderCompilerDxcAz
|
||||
3rdParty::SPIRVCross
|
||||
3rdParty::azslc
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
#include <AtomCore/Serialization/Json/JsonUtils.h>
|
||||
|
||||
#include <AzFramework/IO/LocalFileIO.h>
|
||||
#include <AzFramework/IO/FileOperations.h> // [GFX TODO] Remove when [ATOM-15472]
|
||||
#include <AzFramework/StringFunc/StringFunc.h>
|
||||
|
||||
#include <AzFramework/Process/ProcessCommunicator.h>
|
||||
@@ -122,7 +123,7 @@ namespace AZ
|
||||
|
||||
namespace SubProducts = ShaderBuilderUtility::AzslSubProducts;
|
||||
|
||||
Outcome<SubProducts::Paths> AzslCompiler::EmitFullData(const AZStd::string& parameters, const AZStd::string& outputFile /* = ""*/) const
|
||||
Outcome<SubProducts::Paths> 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 };
|
||||
|
||||
@@ -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<ShaderBuilderUtility::AzslSubProducts::Paths> EmitFullData(const AZStd::string& parameters, const AZStd::string& outputFile = "") const;
|
||||
Outcome<ShaderBuilderUtility::AzslSubProducts::Paths> 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
|
||||
|
||||
@@ -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<ShaderFiles>& a_sources) : m_sources(a_sources) { }
|
||||
|
||||
AZStd::shared_ptr<ShaderFiles> 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<ShaderFiles>& a_sources)
|
||||
: m_sources(a_sources)
|
||||
{
|
||||
}
|
||||
|
||||
AZStd::shared_ptr<ShaderFiles> 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
|
||||
|
||||
@@ -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<ShaderAssetBuilder>();
|
||||
@@ -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<ShaderVariantAssetBuilder>();
|
||||
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<PrecompiledShaderBuilder>();
|
||||
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<ShaderAssetBuilder2>();
|
||||
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<ShaderVariantAssetBuilder2>();
|
||||
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();
|
||||
|
||||
@@ -18,12 +18,14 @@
|
||||
|
||||
#include <Atom/RPI.Reflect/Shader/ShaderAsset.h>
|
||||
|
||||
#include <AzslBuilder.h>
|
||||
#include <SrgLayoutBuilder.h>
|
||||
#include <ShaderAssetBuilder.h>
|
||||
#include <ShaderVariantAssetBuilder.h>
|
||||
#include <PrecompiledShaderBuilder.h>
|
||||
#include <ShaderPlatformInterfaceRequest.h>
|
||||
#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<RHI::APIType, RHI::ShaderPlatformInterface*> m_shaderPlatformInterfaces;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 <GameProject>/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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,6 +58,37 @@ namespace AZ
|
||||
}
|
||||
}
|
||||
|
||||
void PreprocessorOptions::RemovePredefinedMacros(const AZStd::vector<AZStd::string>& 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)
|
||||
{
|
||||
|
||||
@@ -47,9 +47,13 @@ namespace AZ
|
||||
//! folders are relative to the dev folder of the project
|
||||
AZStd::vector<AZStd::string> 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<AZStd::string> m_predefinedMacros;
|
||||
|
||||
//! Removes all macros from @m_predefinedMacros that appear in @macroNames
|
||||
void RemovePredefinedMacros(const AZStd::vector<AZStd::string>& 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.
|
||||
|
||||
@@ -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<uint32_t>(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<uint32_t>(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<uint32_t>(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
|
||||
|
||||
@@ -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 <CommonFiles/Preprocessor.h>
|
||||
#include <CommonFiles/GlobalBuildOptions.h>
|
||||
|
||||
#include <Atom/RPI.Reflect/Shader/ShaderAsset2.h>
|
||||
#include <Atom/RPI.Reflect/Shader/ShaderAssetCreator2.h>
|
||||
#include <Atom/RPI.Reflect/Shader/ShaderOptionGroup.h>
|
||||
#include <Atom/RPI.Reflect/Shader/ShaderVariantKey.h>
|
||||
|
||||
#include <Atom/RHI.Edit/Utils.h>
|
||||
#include <Atom/RHI.Edit/ShaderPlatformInterface.h>
|
||||
#include <Atom/RPI.Edit/Common/JsonReportingHelper.h>
|
||||
#include <Atom/RPI.Edit/Common/AssetUtils.h>
|
||||
#include <Atom/RHI.Reflect/ConstantsLayout.h>
|
||||
#include <Atom/RHI.Reflect/PipelineLayoutDescriptor.h>
|
||||
#include <Atom/RHI.Reflect/ShaderStageFunction.h>
|
||||
|
||||
#include <AtomCore/Serialization/Json/JsonUtils.h>
|
||||
|
||||
#include <AzToolsFramework/API/EditorAssetSystemAPI.h>
|
||||
#include <AzToolsFramework/Debug/TraceContext.h>
|
||||
|
||||
#include <AzFramework/API/ApplicationAPI.h>
|
||||
#include <AzFramework/StringFunc/StringFunc.h>
|
||||
#include <AzFramework/IO/LocalFileIO.h>
|
||||
#include <AzFramework/Platform/PlatformDefaults.h>
|
||||
|
||||
#include <AzCore/Asset/AssetManager.h>
|
||||
#include <AzCore/JSON/document.h>
|
||||
#include <AzCore/IO/FileIO.h>
|
||||
#include <AzCore/IO/IOUtils.h>
|
||||
#include <AzCore/IO/SystemFile.h>
|
||||
#include <AzCore/std/algorithm.h>
|
||||
#include <AzCore/std/string/string.h>
|
||||
#include <AzCore/std/sort.h>
|
||||
#include <AzCore/Serialization/Json/JsonSerialization.h>
|
||||
|
||||
#include "AzslBuilder.h"
|
||||
#include "ShaderVariantAssetBuilder2.h"
|
||||
#include "ShaderBuilderUtility.h"
|
||||
#include "ShaderPlatformInterfaceRequest.h"
|
||||
#include "AtomShaderConfig.h"
|
||||
|
||||
#include <AssetBuilderSDK/AssetBuilderSDK.h>
|
||||
#include <AssetBuilderSDK/SerializationDependencies.h>
|
||||
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<RHI::ShaderPlatformInterface*> 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<RPI::ShaderAsset2> 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<RPI::ShaderAsset2>(),
|
||||
aznumeric_cast<uint32_t>(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<RHI::ShaderStageAttributeMapList, AZStd::string> 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<uint32_t>(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<RHI::ShaderPlatformInterface*> 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<RPI::ShaderOptionGroupLayout> 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 <GameProject>/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<AZStd::string> macroDefinitionNamesToRemove = supervariantInfo.GetCombinedListOfMacroDefinitionNamesToRemove();
|
||||
PreprocessorOptions preprocessorOptions = buildOptions.m_preprocessorSettings;
|
||||
preprocessorOptions.RemovePredefinedMacros(macroDefinitionNamesToRemove);
|
||||
// 2- Supervariants can specify which macros to add.
|
||||
AZStd::vector<AZStd::string> 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<uint32_t>(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<ShaderFiles> files(new ShaderFiles);
|
||||
AzslData azslData(files);
|
||||
azslData.m_preprocessedFullPath = azslinFullPath;
|
||||
RPI::ShaderResourceGroupLayoutList srgLayoutList;
|
||||
RPI::Ptr<RPI::ShaderOptionGroupLayout> 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<RHI::PipelineLayoutDescriptor> 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<AZStd::string, AZStd::string> 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<uint32_t>(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<RHI::ShaderPlatformInterface::ByProducts> 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<RPI::ShaderVariantAsset2> 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<uint32_t>(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<RPI::ShaderAsset2> 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
|
||||
@@ -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 <AzCore/base.h>
|
||||
#include <AssetBuilderSDK/AssetBuilderBusses.h>
|
||||
#include <AssetBuilderSDK/AssetBuilderSDK.h>
|
||||
|
||||
#include <Atom/RHI.Reflect/Base.h>
|
||||
|
||||
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
|
||||
@@ -29,7 +29,8 @@
|
||||
|
||||
#include <Atom/RPI.Edit/Common/JsonReportingHelper.h>
|
||||
#include <Atom/RPI.Edit/Common/AssetUtils.h>
|
||||
#include <Atom/RPI.Reflect/Shader/ShaderAsset.h>
|
||||
#include <Atom/RPI.Reflect/Shader/ShaderAsset.h> // DEPRECATED - [ATOM-15472]
|
||||
#include <Atom/RPI.Reflect/Shader/ShaderAsset2.h>
|
||||
#include <Atom/RPI.Reflect/Shader/ShaderOptionGroup.h>
|
||||
|
||||
#include <Atom/RHI.Edit/Utils.h>
|
||||
@@ -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<RPI::ShaderSourceData, AZStd::string> 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<uint32_t>(RPI::ShaderAssetSubId::DebugByProduct);
|
||||
id_api += apiType;
|
||||
id_api <<= 4;
|
||||
subId |= id_api;
|
||||
size_t fnHash = AZStd::hash<AZStd::string>()(productFileName);
|
||||
subId |= static_cast<uint32_t>(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<AZStd::string>().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<AZStd::string>().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<RPI::ShaderOptionGroupLayout> 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<int, Outcome<rapidjson::Document, AZStd::string>> 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<RHI::PipelineLayoutDescriptor> 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<RHI::ShaderPlatformInterface*> DiscoverEnabledShaderPlatformInterfaces(const AssetBuilderSDK::PlatformInfo& info, const RPI::ShaderSourceData& shaderSourceData)
|
||||
{
|
||||
// Request the list of valid shader platform interfaces for the target platform.
|
||||
AZStd::vector<RHI::ShaderPlatformInterface*> 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<uint32_t>(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<RPI::ShaderSourceData::SupervariantInfo> GetSupervariantListFromShaderSourceData(
|
||||
const RPI::ShaderSourceData& shaderSourceData)
|
||||
{
|
||||
AZStd::vector<RPI::ShaderSourceData::SupervariantInfo> 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<AZ::Name> 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<uint32_t>(log2(static_cast<uint32_t>(subIdMaxEnumerator))) + 1;
|
||||
return static_cast<uint32_t>(subId) + (apiType << shiftLeft);
|
||||
}
|
||||
|
||||
Outcome<AZStd::string, AZStd::string> 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<uint32_t>(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<AzslSubProducts::Paths> 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<RHI::PipelineLayoutDescriptor> 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<RHI::ShaderStageMask>(AZ_BIT(static_cast<uint32_t>(RHI::ToRHIShaderStage(hardwareStage))));
|
||||
}
|
||||
}
|
||||
|
||||
return mask;
|
||||
};
|
||||
|
||||
// Build general PipelineLayoutDescriptor data that is provided for all platforms
|
||||
RHI::Ptr<RHI::PipelineLayoutDescriptor> 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<RHI::ConstantsLayout> 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<uint32_t>(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<FunctionData>& azslFunctionDataList,
|
||||
AZStd::unordered_map<AZStd::string, RPI::ShaderStageType>& 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<AZStd::string, RPI::ShaderStageType>& defaultEntryPoints)
|
||||
{
|
||||
return GetAcceptableDefaultEntryPoints(azslData.m_functions, defaultEntryPoints);
|
||||
}
|
||||
|
||||
|
||||
void GetDefaultEntryPointsFromFunctionDataList(
|
||||
const AZStd::vector<FunctionData> azslFunctionDataList,
|
||||
AZStd::unordered_map<AZStd::string, RPI::ShaderStageType>& shaderEntryPoints)
|
||||
{
|
||||
AZStd::unordered_map<AZStd::string, RPI::ShaderStageType> 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<AZStd::string, RPI::ShaderStageType> defaultEntryPointList;
|
||||
GetAcceptableDefaultEntryPoints(azslData, defaultEntryPointList);
|
||||
|
||||
AZStd::vector<AZStd::string> 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
|
||||
|
||||
@@ -18,8 +18,10 @@
|
||||
#include <AssetBuilderSDK/AssetBuilderSDK.h>
|
||||
|
||||
#include <Atom/RPI.Edit/Shader/ShaderSourceData.h>
|
||||
#include <Atom/RPI.Reflect/Shader/ShaderAsset2.h>
|
||||
#include <Atom/RPI.Reflect/Shader/ShaderResourceGroupAsset.h>
|
||||
|
||||
#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<ShaderFiles> 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<AZStd::string, AZ_ARRAY_SIZE(SubList)>;
|
||||
};
|
||||
|
||||
//! [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<RPI::ShaderOptionGroupLayout> 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<RHI::PipelineLayoutDescriptor> 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<RHI::PipelineLayoutDescriptor> 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<RHI::ShaderPlatformInterface*> DiscoverValidShaderPlatformInterfaces(const AssetBuilderSDK::PlatformInfo& info);
|
||||
AZStd::vector<RHI::ShaderPlatformInterface*> 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<RPI::ShaderSourceData::SupervariantInfo> GetSupervariantListFromShaderSourceData(
|
||||
const RPI::ShaderSourceData& shaderSourceData);
|
||||
|
||||
void GetDefaultEntryPointsFromFunctionDataList(
|
||||
const AZStd::vector<FunctionData> azslFunctionDataList,
|
||||
AZStd::unordered_map<AZStd::string, RPI::ShaderStageType>& 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<AZStd::string, AZStd::string> 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<AzslSubProducts::Paths> ObtainBuildArtifactsFromAzslBuilder(const char* builderName, const AZStd::string& azslSourceFullPath, RHI::APIType apiType, const AZStd::string& platform);
|
||||
|
||||
|
||||
@@ -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<uint32_t>(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<AZStd::string, RPI::ShaderStageType>& 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<AZStd::string, RPI::ShaderStageType> defaultEntryPointList;
|
||||
GetAcceptableDefaultEntryPoints(shaderData, defaultEntryPointList);
|
||||
|
||||
AZStd::vector<AZStd::string> 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<uint32_t>(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<Data::Asset<RPI::ShaderVariantAsset>, 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<AZStd::string, RPI::ShaderStageType>& shaderEntryPoints)
|
||||
{
|
||||
AZStd::unordered_map<AZStd::string, RPI::ShaderStageType> 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<RPI::ShaderVariantAsset> shaderVariantAsset, const AZStd::string& shaderSourceFileFullPath, const AZStd::string& tempDirPath,
|
||||
const RHI::ShaderPlatformInterface& shaderPlatformInterface, const uint32_t productSubID, AssetBuilderSDK::JobProduct& assetProduct)
|
||||
{
|
||||
|
||||
@@ -73,8 +73,6 @@ namespace AZ
|
||||
static bool SerializeOutShaderVariantAsset(const Data::Asset<RPI::ShaderVariantAsset> 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<AZStd::string, RPI::ShaderStageType>& shaderEntryPoints);
|
||||
|
||||
// AssetBuilderSDK::AssetBuilderCommandBus interface overrides ...
|
||||
void ShutDown() override { };
|
||||
|
||||
|
||||
@@ -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 <ShaderVariantAssetBuilder2.h>
|
||||
|
||||
#include <Atom/RPI.Reflect/Shader/ShaderAsset2.h>
|
||||
#include <Atom/RPI.Reflect/Shader/ShaderVariantAsset2.h>
|
||||
#include <Atom/RPI.Reflect/Shader/ShaderVariantTreeAsset.h>
|
||||
#include <Atom/RPI.Reflect/Shader/ShaderOptionGroup.h>
|
||||
|
||||
#include <Atom/RPI.Edit/Shader/ShaderVariantListSourceData.h>
|
||||
#include <Atom/RPI.Edit/Shader/ShaderVariantAssetCreator2.h>
|
||||
#include <Atom/RPI.Edit/Shader/ShaderVariantTreeAssetCreator.h>
|
||||
#include <Atom/RPI.Edit/Common/JsonUtils.h>
|
||||
|
||||
#include <AtomCore/Serialization/Json/JsonUtils.h>
|
||||
#include <Atom/RPI.Reflect/Shader/ShaderResourceGroupAsset.h>
|
||||
#include <Atom/RPI.Reflect/Shader/ShaderVariantKey.h>
|
||||
|
||||
#include <Atom/RHI.Edit/Utils.h>
|
||||
#include <Atom/RHI.Edit/ShaderPlatformInterface.h>
|
||||
#include <Atom/RPI.Edit/Common/JsonReportingHelper.h>
|
||||
#include <Atom/RPI.Edit/Common/AssetUtils.h>
|
||||
#include <Atom/RHI.Reflect/ConstantsLayout.h>
|
||||
#include <Atom/RHI.Reflect/PipelineLayoutDescriptor.h>
|
||||
#include <Atom/RHI.Reflect/ShaderStageFunction.h>
|
||||
|
||||
#include <AzToolsFramework/API/EditorAssetSystemAPI.h>
|
||||
#include <AzToolsFramework/Debug/TraceContext.h>
|
||||
|
||||
#include <AzFramework/API/ApplicationAPI.h>
|
||||
#include <AzFramework/StringFunc/StringFunc.h>
|
||||
#include <AzFramework/IO/LocalFileIO.h>
|
||||
#include <AzFramework/Platform/PlatformDefaults.h>
|
||||
|
||||
#include <AzCore/Asset/AssetManager.h>
|
||||
#include <AzCore/JSON/document.h>
|
||||
#include <AzCore/IO/FileIO.h>
|
||||
#include <AzCore/IO/IOUtils.h>
|
||||
#include <AzCore/IO/SystemFile.h>
|
||||
#include <AzCore/std/algorithm.h>
|
||||
#include <AzCore/std/string/string.h>
|
||||
#include <AzCore/std/sort.h>
|
||||
#include <AzCore/Serialization/Json/JsonSerialization.h>
|
||||
|
||||
#include "ShaderAssetBuilder2.h"
|
||||
#include "ShaderBuilderUtility.h"
|
||||
#include "AzslData.h"
|
||||
#include "AzslCompiler.h"
|
||||
#include "AzslBuilder.h"
|
||||
#include <CommonFiles/Preprocessor.h>
|
||||
#include <CommonFiles/GlobalBuildOptions.h>
|
||||
#include <ShaderPlatformInterfaceRequest.h>
|
||||
#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<AZStd::string> 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<AZStd::string> 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: <DEVROOT>/<GAME>/ShaderVariants/<Same Scan Folder Subpath as the .shader file>.
|
||||
//! 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: "<DEVROOT>/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_ForwardPass.shader"
|
||||
//! - In this example the Scan Folder is "<DEVROOT>/Gems/Atom/Feature/Common/Assets", while the subfolder is "Materials/Types".
|
||||
//! The "Higher Precedence" expected valid location for the .shadervariantlist would be:
|
||||
//! - <DEVROOT>/<GameProject>/ShaderVariants/Materials/Types/StandardPBR_ForwardPass.shadervariantlist.
|
||||
//! The "Lower Precedence" valid location would be:
|
||||
//! - <DEVROOT>/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<RPI::ShaderOptionGroupLayout> 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<RPI::ShaderOptionGroupLayout> 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<AZStd::string, AZStd::string> 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<RPI::ShaderOptionGroupLayout> shaderOptionGroupLayout;
|
||||
|
||||
// Request the list of valid shader platform interfaces for the target platform.
|
||||
AZStd::vector<RHI::ShaderPlatformInterface*> 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<RPI::ShaderOptionGroupLayout> 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<RPI::ShaderVariantTreeAsset> 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<RPI::ShaderVariantTreeAsset>();
|
||||
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<ShaderFiles> 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<RHI::ShaderPlatformInterface*> 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<RPI::ShaderOptionGroupLayout> 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<RHI::ShaderPlatformInterface::ByProducts> 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<RPI::ShaderVariantAsset2> 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<RPI::ShaderVariantAsset2> 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<RPI::ShaderVariantAsset2>();
|
||||
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<Data::Asset<RPI::ShaderVariantAsset2>, AZStd::string> ShaderVariantAssetBuilder2::CreateShaderVariantAsset(
|
||||
const RPI::ShaderVariantListSourceData::VariantInfo& shaderVariantInfo,
|
||||
ShaderVariantCreationContext2& creationContext,
|
||||
AZStd::optional<RHI::ShaderPlatformInterface::ByProducts>& 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<OptionCache> 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<AZStd::string, RPI::ShaderStageType>& 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<RHI::ShaderStageFunction> 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<RPI::ShaderVariantAsset2> shaderVariantAsset;
|
||||
variantCreator.End(shaderVariantAsset);
|
||||
return AZ::Success(AZStd::move(shaderVariantAsset));
|
||||
}
|
||||
|
||||
} // ShaderBuilder
|
||||
} // AZ
|
||||
@@ -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 <AzCore/base.h>
|
||||
#include <AssetBuilderSDK/AssetBuilderBusses.h>
|
||||
#include <AssetBuilderSDK/AssetBuilderSDK.h>
|
||||
|
||||
#include <Atom/RHI.Reflect/Base.h>
|
||||
#include <Atom/RPI.Reflect/Shader/ShaderAsset2.h>
|
||||
#include <Atom/RPI.Edit/Shader/ShaderSourceData.h>
|
||||
#include <Atom/RPI.Edit/Shader/ShaderVariantListSourceData.h>
|
||||
|
||||
#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; //<shaderName>-<supervariantName>
|
||||
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<Data::Asset<RPI::ShaderVariantAsset2>, AZStd::string> CreateShaderVariantAsset(
|
||||
const RPI::ShaderVariantListSourceData::VariantInfo& shaderVariantInfo,
|
||||
ShaderVariantCreationContext2& creationContext,
|
||||
AZStd::optional<RHI::ShaderPlatformInterface::ByProducts>& outputByproducts);
|
||||
|
||||
static bool SerializeOutShaderVariantAsset(
|
||||
const Data::Asset<RPI::ShaderVariantAsset2> 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
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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 <Atom/RHI.Edit/Utils.h>
|
||||
|
||||
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<RHI::ShaderResourceGroupLayout> 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<uint32_t>(-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<uint32_t>(-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
|
||||
@@ -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 <AzCore/base.h>
|
||||
|
||||
#include "CommonFiles/CommonTypes.h"
|
||||
#include <Atom/RPI.Reflect/Shader/ShaderAsset2.h>
|
||||
#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
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -125,6 +125,7 @@ namespace AZ
|
||||
Data::Instance<RPI::AttachmentImage> 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<AzFramework::Scene*, AZStd::weak_ptr<AZ::RPI::Scene>> m_azSceneToAtomSceneMap;
|
||||
|
||||
@@ -31,8 +31,7 @@
|
||||
},
|
||||
|
||||
"CompilerHints" : {
|
||||
"DisableOptimizations" : true,
|
||||
"DxcGenerateDebugInfo" : true
|
||||
"DisableOptimizations" : false
|
||||
},
|
||||
|
||||
"ProgramSettings":
|
||||
|
||||
+4
-4
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
+25
-204
@@ -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
|
||||
//##############################################################################################
|
||||
|
||||
+79
-56
@@ -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);
|
||||
}
|
||||
|
||||
+1
-1
@@ -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;
|
||||
|
||||
|
||||
+252
-175
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
+2
-2
@@ -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
|
||||
|
||||
+1
-1
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
-1
@@ -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()
|
||||
|
||||
+4
-4
@@ -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;
|
||||
}
|
||||
|
||||
+2
-2
@@ -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)
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:f74ffab6ee15906158d27cbd2e5556e8fcdfd7820c0d0fd4b403de8a7af81662
|
||||
size 5651
|
||||
oid sha256:6660fa05dbf1e90298472fb41d99fff80a80de64ee88d17af4d0df3bdafb1ff6
|
||||
size 52877
|
||||
|
||||
@@ -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<bool> 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<AZ::Vector3> 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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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<ShaderInputStaticSamplerDescriptor> m_staticSamplers;
|
||||
|
||||
AZStd::vector<ShaderInputBufferDescriptor> m_inputsForBuffers;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user