Merge branch 'main' into pruiksma/ATOM-15561

main
pruiksma 5 years ago
commit 5802c4e1dc

1
.gitattributes vendored

@ -115,7 +115,6 @@
*.wav filter=lfs diff=lfs merge=lfs -text
*.webm filter=lfs diff=lfs merge=lfs -text
*.wem filter=lfs diff=lfs merge=lfs -text
*.wxs filter=lfs diff=lfs merge=lfs -text
*.zip filter=lfs diff=lfs merge=lfs -text
*.tbscene filter=lfs diff=lfs merge=lfs -text
*.spp filter=lfs diff=lfs merge=lfs -text

@ -0,0 +1,6 @@
{
"AWSResourceMappings": {},
"AccountId": "",
"Region": "us-west-2",
"Version": "1.0.0"
}

@ -45,4 +45,7 @@ set(GEM_DEPENDENCIES
Gem::Atom_AtomBridge
Gem::NvCloth
Gem::Blast
Gem::AWSCore
Gem::AWSClientAuth
Gem::AWSMetrics
)

@ -55,4 +55,7 @@ set(GEM_DEPENDENCIES
Gem::Atom_AtomBridge.Editor
Gem::NvCloth.Editor
Gem::Blast.Editor
Gem::AWSCore.Editor
Gem::AWSClientAuth
Gem::AWSMetrics
)

@ -0,0 +1,31 @@
#
# 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.
#
################################################################################
# AWS Automated Tests
# Runs AWS Gems automation tests.
################################################################################
if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS)
# Enable after installing NodeJS and CDK on jenkins Windows AMI.
#ly_add_pytest(
# NAME AutomatedTesting::AWSTests
# TEST_SUITE periodic
# TEST_SERIAL
# PATH ${CMAKE_CURRENT_LIST_DIR}/AWS/${PAL_PLATFORM_NAME}/
# RUNTIME_DEPENDENCIES
# Legacy::Editor
# AZ::AssetProcessor
# AutomatedTesting.Assets
# COMPONENT
# AWS
#)
endif()

@ -0,0 +1,155 @@
"""
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 os
import pytest
import boto3
import ly_test_tools.environment.process_utils as process_utils
from typing import List
class Cdk:
"""
Cdk class that provides methods to run cdk application commands.
Expects system to have NodeJS, AWS CLI and CDK installed globally and have their paths setup as env variables.
"""
def __init__(self, cdk_path: str, project: str, account_id: str,
workspace: pytest.fixture, session: boto3.session.Session):
"""
:param cdk_path: Path where cdk app.py is stored.
:param project: Project name used for cdk project name env variable.
:param account_id: AWS account id to use with cdk application.
:param workspace: ly_test_tools workspace fixture.
"""
self._cdk_env = os.environ.copy()
self._cdk_env['O3DE_AWS_PROJECT_NAME'] = project
self._cdk_env['O3DE_AWS_DEPLOY_REGION'] = session.region_name
self._cdk_env['O3DE_AWS_DEPLOY_ACCOUNT'] = account_id
self._cdk_env['PATH'] = f'{workspace.paths.engine_root()}\\python;' + self._cdk_env['PATH']
credentials = session.get_credentials().get_frozen_credentials()
self._cdk_env['AWS_ACCESS_KEY_ID'] = credentials.access_key
self._cdk_env['AWS_SECRET_ACCESS_KEY'] = credentials.secret_key
self._cdk_env['AWS_SESSION_TOKEN'] = credentials.token
self._stacks = []
self._cdk_path = cdk_path
output = process_utils.check_output(
'python -m pip install -r requirements.txt',
cwd=self._cdk_path,
env=self._cdk_env,
shell=True)
def list(self) -> List[str]:
"""
lists cdk stack names
:return List of cdk stack names
"""
if not self._cdk_path:
return []
list_cdk_application_cmd = ['cdk', 'list']
output = process_utils.check_output(
list_cdk_application_cmd,
cwd=self._cdk_path,
env=self._cdk_env,
shell=True)
return output.splitlines()
def synthesize(self) -> None:
"""
Synthesizes all cdk stacks
"""
if not self._cdk_path:
return
list_cdk_application_cmd = ['cdk', 'synth']
process_utils.check_output(
list_cdk_application_cmd,
cwd=self._cdk_path,
env=self._cdk_env,
shell=True)
def deploy(self, context_variable: str = '') -> List[str]:
"""
Deploys all the CDK stacks.
:param context_variable: Context variable for enabling optional features.
:return List of deployed stack arns.
"""
if not self._cdk_path:
return []
deploy_cdk_application_cmd = ['cdk', 'deploy', '--require-approval', 'never']
if context_variable:
deploy_cdk_application_cmd.extend(['-c', f'{context_variable}'])
output = process_utils.check_output(
deploy_cdk_application_cmd,
cwd=self._cdk_path,
env=self._cdk_env,
shell=True)
stacks = []
for line in output.splitlines():
line_sections = line.split('/')
assert len(line_sections), 3
stacks.append(line.split('/')[-2])
return stacks
def destroy(self) -> None:
"""
Destroys the cdk application.
"""
destroy_cdk_application_cmd = ['cdk', 'destroy', '-f']
process_utils.check_output(
destroy_cdk_application_cmd,
cwd=self._cdk_path,
env=self._cdk_env,
shell=True)
self._stacks = []
self._cdk_path = ''
@pytest.fixture(scope='function')
def cdk(
request: pytest.fixture,
project: str,
feature_name: str,
workspace: pytest.fixture,
aws_utils: pytest.fixture,
destroy_stacks_on_teardown: bool = True) -> Cdk:
"""
Fixture for setting up a Cdk
:param request: _pytest.fixtures.SubRequest class that handles getting
a pytest fixture from a pytest function/fixture.
:param project: Project name used for cdk project name env variable.
:param feature_name: Feature gem name to expect cdk folder in.
:param workspace: ly_test_tools workspace fixture.
:param aws_utils: aws_utils fixture.
:param destroy_stacks_on_teardown: option to control calling destroy ot the end of test.
:return Cdk class object.
"""
cdk_path = f'{workspace.paths.engine_root()}/Gems/{feature_name}/cdk'
cdk_obj = Cdk(cdk_path, project, aws_utils.assume_account_id(), workspace, aws_utils.assume_session())
def teardown():
if destroy_stacks_on_teardown:
cdk_obj.destroy()
request.addfinalizer(teardown)
return cdk_obj

@ -0,0 +1,78 @@
"""
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.parametrize('level', ['AWS/ClientAuth'])
@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 TestAWSClientAuthAnonymousCredentials(object):
"""
Test class to verify AWS Cognito Identity pool anonymous authorization.
"""
def test_anonymous_credentials(self,
level: str,
launcher: pytest.fixture,
cdk: pytest.fixture,
resource_mappings: pytest.fixture,
workspace: pytest.fixture,
asset_processor: pytest.fixture
):
"""
Setup: Deploys cdk and updates resource mapping file.
Tests: Getting AWS credentials for no signed in user.
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', level]
with launcher.start(launch_ap=False):
result = log_monitor.monitor_log_for_lines(
expected_lines=['(Script) - Success anonymous credentials'],
unexpected_lines=['(Script) - Fail anonymous credentials'],
halt_on_unexpected=True,
)
assert result, 'Anonymous credentials fetched successfully.'

@ -0,0 +1,10 @@
"""
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.
"""

@ -0,0 +1,137 @@
"""
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 os
import pytest
import json
AWS_RESOURCE_MAPPINGS_KEY = 'AWSResourceMappings'
AWS_RESOURCE_MAPPINGS_ACCOUNT_ID_KEY = 'AccountId'
AWS_RESOURCE_MAPPINGS_REGION_KEY = 'Region'
class ResourceMappings:
"""
ResourceMappings class that handles writing Cloud formation outputs to resource mappings json file in a project.
"""
def __init__(self, file_path: str, region: str, feature_name: str, account_id: str, workspace: pytest.fixture,
cloud_formation_client):
"""
:param file_path: Path for the resource mapping file.
:param region: Region value for the resource mapping file.
:param feature_name: Feature gem name to use to append name to mappings key.
:param account_id: AWS account id value for the resource mapping file.
:param workspace: ly_test_tools workspace fixture.
:param cloud_formation_client: AWS cloud formation client.
"""
self._cdk_env = os.environ.copy()
self._cdk_env['PATH'] = f'{workspace.paths.engine_root()}\\python;' + self._cdk_env['PATH']
self._resource_mapping_file_path = file_path
self._region = region
self._feature_name = feature_name
self._account_id = account_id
assert os.path.exists(self._resource_mapping_file_path), \
f'Invalid resource mapping file path {self._resource_mapping_file_path}'
self._client = cloud_formation_client
def populate_output_keys(self, stacks=[]) -> None:
"""
Calls describe stacks on cloud formation service and persists outputs to resource mappings file.
:param stacks List of stack arns to describe and populate resource mappings with.
"""
for stack_name in stacks:
response = self._client.describe_stacks(
StackName=stack_name
)
stacks = response.get('Stacks', [])
assert len(stacks) == 1, f'{stack_name} is invalid.'
self.__write_resource_mappings(stacks[0].get('Outputs', []))
def __write_resource_mappings(self, outputs, append_feature_name = True) -> None:
with open(self._resource_mapping_file_path) as file_content:
resource_mappings = json.load(file_content)
resource_mappings[AWS_RESOURCE_MAPPINGS_ACCOUNT_ID_KEY] = self._account_id
resource_mappings[AWS_RESOURCE_MAPPINGS_REGION_KEY] = self._region
# Append new mappings.
resource_mappings[AWS_RESOURCE_MAPPINGS_KEY] = resource_mappings.get(AWS_RESOURCE_MAPPINGS_KEY, {})
for output in outputs:
if append_feature_name:
resource_key = f'{self._feature_name}.{output.get("OutputKey", "InvalidKey")}'
else:
resource_key = output.get("OutputKey", "InvalidKey")
resource_mappings[AWS_RESOURCE_MAPPINGS_KEY][resource_key] = resource_mappings[
AWS_RESOURCE_MAPPINGS_KEY].get(resource_key, {})
resource_mappings[AWS_RESOURCE_MAPPINGS_KEY][resource_key]['Type'] = 'AutomationTestType'
resource_mappings[AWS_RESOURCE_MAPPINGS_KEY][resource_key]['Name/ID'] = output.get('OutputValue',
'InvalidId')
with open(self._resource_mapping_file_path, 'w') as file_content:
json.dump(resource_mappings, file_content, indent=4)
def clear_output_keys(self) -> None:
"""
Clears values of all resource mapping keys. Sets region to default to us-west-2
"""
with open(self._resource_mapping_file_path) as file_content:
resource_mappings = json.load(file_content)
resource_mappings[AWS_RESOURCE_MAPPINGS_ACCOUNT_ID_KEY] = ''
resource_mappings[AWS_RESOURCE_MAPPINGS_REGION_KEY] = 'us-west-2'
# Append new mappings.
resource_mappings[AWS_RESOURCE_MAPPINGS_KEY] = resource_mappings.get(AWS_RESOURCE_MAPPINGS_KEY, {})
resource_mappings[AWS_RESOURCE_MAPPINGS_KEY] = {}
with open(self._resource_mapping_file_path, 'w') as file_content:
json.dump(resource_mappings, file_content, indent=4)
self._resource_mapping_file_path = ''
self._region = ''
self._client = None
@pytest.fixture(scope='function')
def resource_mappings(
request: pytest.fixture,
project: str,
feature_name: str,
resource_mappings_filename: str,
workspace: pytest.fixture,
aws_utils: pytest.fixture) -> ResourceMappings:
"""
Fixture for setting up resource mappings file.
:param request: _pytest.fixtures.SubRequest class that handles getting
a pytest fixture from a pytest function/fixture.
:param project: Project to find resource mapping file.
:param feature_name: AWS Gem name that is prepended to resource mapping keys.
:param resource_mappings_filename: Name of resource mapping file.
:param workspace: ly_test_tools workspace fixture.
:param aws_utils: AWS utils fixture.
:return: ResourceMappings class object.
"""
path = f'{workspace.paths.engine_root()}\\{project}\\Config\\{resource_mappings_filename}'
resource_mappings_obj = ResourceMappings(path, aws_utils.assume_session().region_name, feature_name,
aws_utils.assume_account_id(), workspace,
aws_utils.client('cloudformation'))
def teardown():
resource_mappings_obj.clear_output_keys()
request.addfinalizer(teardown)
return resource_mappings_obj

@ -0,0 +1,11 @@
"""
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.
"""

@ -0,0 +1,82 @@
"""
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 boto3
import pytest
import logging
logger = logging.getLogger(__name__)
class AwsUtils:
def __init__(self, arn: str, session_name: str, region_name: str):
local_session = boto3.Session(profile_name='default')
local_sts_client = local_session.client('sts')
self._local_account_id = local_sts_client.get_caller_identity()["Account"]
logger.info(f'Local Account Id: {self._local_account_id}')
response = local_sts_client.assume_role(RoleArn=arn, RoleSessionName=session_name)
self._assume_session = boto3.Session(aws_access_key_id=response['Credentials']['AccessKeyId'],
aws_secret_access_key=response['Credentials']['SecretAccessKey'],
aws_session_token=response['Credentials']['SessionToken'],
region_name=region_name)
assume_sts_client = self._assume_session.client('sts')
assume_account_id = assume_sts_client.get_caller_identity()["Account"]
logger.info(f'Assume Account Id: {assume_account_id}')
self._assume_account_id = assume_account_id
def client(self, service: str):
"""
Get the client for a specific AWS service from configured session
:return: Client for the AWS service.
"""
return self._assume_session.client(service)
def assume_session(self):
return self._assume_session
def local_account_id(self):
return self._local_account_id
def assume_account_id(self):
return self._assume_account_id
def destroy(self) -> None:
"""
clears stored session
"""
self._assume_session = None
@pytest.fixture(scope='function')
def aws_utils(
request: pytest.fixture,
assume_role_arn: str,
session_name: str,
region_name: str):
"""
Fixture for setting up a Cdk
:param request: _pytest.fixtures.SubRequest class that handles getting
a pytest fixture from a pytest function/fixture.
:param assume_role_arn: Role used to fetch temporary aws credentials, configure service clients with obtained credentials.
:param session_name: Session name to set.
:param region_name: AWS account region to set for session.
:return AWSUtils class object.
"""
aws_utils_obj = AwsUtils(assume_role_arn, session_name, region_name)
def teardown():
aws_utils_obj.destroy()
request.addfinalizer(teardown)
return aws_utils_obj

@ -56,5 +56,8 @@ add_subdirectory(editor)
## Streaming ##
add_subdirectory(streaming)
## Streaming ##
## Smoke ##
add_subdirectory(smoke)
## AWS ##
add_subdirectory(AWS)

@ -18,7 +18,6 @@ from PySide2 import QtCore, QtWidgets, QtGui, QtTest
from PySide2.QtWidgets import QAction, QWidget
from PySide2.QtCore import Qt
from PySide2.QtTest import QTest
import azlmbr.legacy.general as general
import traceback
import threading
import types

@ -0,0 +1,161 @@
"""
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.
"""
class Tests:
test_panes_visible = "All the test panes are opened"
close_pane_1 = "Test pane 1 is closed"
resize_pane_3 = "Test pane 3 resized successfully"
location_changed = "Location of test pane 2 changed successfully"
visiblity_retained = "Test pane retained its visiblity on Editor restart"
location_retained = "Test pane retained its location on Editor restart"
size_retained = "Test pane retained its size on Editor restart"
def Pane_PropertiesChanged_RetainsOnRestart():
"""
Summary:
The Script Canvas window is opened to verify if Script canvas panes can retain its visibility, size and location
upon Editor restart.
Expected Behavior:
The ScriptCanvas pane retain it's visiblity, size and location upon Editor restart.
Test Steps:
1) Open Script Canvas window (Tools > Script Canvas)
2) Make sure test panes are open and visible
3) Close test pane 1
4) Change dock location of test pane 2
5) Resize test pane 3
6) Restart Editor
7) Verify if test pane 1 retain its visiblity
8) Verify if location of test pane 2 is retained
9) Verify if size of test pane 3 is retained
10) Restore default layout and close SC window
Note:
- This test file must be called from the Open 3D Engine Editor command terminal
- Any passed and failed tests are written to the Editor.log file.
Parsing the file or running a log_monitor are required to observe the test results.
:return: None
"""
import sys
# Helper imports
from utils import Report
from utils import TestHelper as helper
import pyside_utils
# Lumberyard Imports
import azlmbr.legacy.general as general
# Pyside imports
from PySide2 import QtCore, QtWidgets
from PySide2.QtCore import Qt
# Constants
TEST_CONDITION = sys.argv[1]
TEST_PANE_1 = "NodePalette" # pane used to test visibility
TEST_PANE_2 = "VariableManager" # pane used to test location
TEST_PANE_3 = "NodeInspector" # pane used to test size
SCALE_INT = 10 # Random resize scale integer
DOCKAREA = Qt.TopDockWidgetArea # Preferred top area since no widget is docked on top
def click_menu_option(window, option_text):
action = pyside_utils.find_child_by_pattern(window, {"text": option_text, "type": QtWidgets.QAction})
action.trigger()
def find_pane(window, pane_name):
return window.findChild(QtWidgets.QDockWidget, pane_name)
# Test starts here
general.idle_enable(True)
# 1) Open Script Canvas window (Tools > Script Canvas)
general.open_pane("Script Canvas")
helper.wait_for_condition(lambda: general.is_pane_visible("Script Canvas"), 5.0)
if TEST_CONDITION == "before_restart":
# 2) Make sure test panes are open and visible
editor_window = pyside_utils.get_editor_main_window()
sc = editor_window.findChild(QtWidgets.QDockWidget, "Script Canvas")
click_menu_option(sc, "Restore Default Layout")
test_pane_1 = sc.findChild(QtWidgets.QDockWidget, TEST_PANE_1)
test_pane_2 = sc.findChild(QtWidgets.QDockWidget, TEST_PANE_2)
test_pane_3 = sc.findChild(QtWidgets.QDockWidget, TEST_PANE_3)
result = test_pane_1.isVisible() and test_pane_2.isVisible() and test_pane_3.isVisible()
Report.info(f"{Tests.test_panes_visible}: {result}")
# 3) Close test pane
test_pane_1.close()
Report.info(f"{Tests.close_pane_1}: {not test_pane_1.isVisible()}")
# 4) Change dock location of test pane 2
sc_main = sc.findChild(QtWidgets.QMainWindow)
sc_main.addDockWidget(DOCKAREA, find_pane(sc_main, TEST_PANE_2), QtCore.Qt.Vertical)
Report.info(f"{Tests.location_changed}: {sc_main.dockWidgetArea(find_pane(sc_main, TEST_PANE_2)) == DOCKAREA}")
# 5) Resize test pane 3
initial_size = test_pane_3.frameSize()
test_pane_3.resize(initial_size.width() + SCALE_INT, initial_size.height() + SCALE_INT)
new_size = test_pane_3.frameSize()
resize_success = (
abs(initial_size.width() - new_size.width()) == abs(initial_size.height() - new_size.height()) == SCALE_INT
)
Report.info(f"{Tests.resize_pane_3}: {resize_success}")
if TEST_CONDITION == "after_restart":
try:
# 6) Restart Editor
# Restart is not possible through script and hence it is done by running the same file as 2 tests with a
# condition as before_test and after_test
# 7) Verify if test pane 1 retain its visiblity
# This pane closed before restart and expected that pane should not be visible.
editor_window = pyside_utils.get_editor_main_window()
sc = editor_window.findChild(QtWidgets.QDockWidget, "Script Canvas")
Report.info(f"{Tests.visiblity_retained}: {not find_pane(sc, TEST_PANE_1).isVisible()}")
# 8) Verify if location of test pane 2 is retained
# This pane was set at DOCKAREA lcoation before restart
sc_main = sc.findChild(QtWidgets.QMainWindow)
Report.info(
f"{Tests.location_retained}: {sc_main.dockWidgetArea(find_pane(sc_main, TEST_PANE_2)) == DOCKAREA}"
)
# 9) Verify if size of test pane 3 is retained
# Verifying if size retained by checking current size not matching with default size
test_pane_3 = find_pane(sc, TEST_PANE_3)
retained_size = test_pane_3.frameSize()
click_menu_option(sc, "Restore Default Layout")
actual_size = test_pane_3.frameSize()
Report.info(f"{Tests.size_retained}: {retained_size != actual_size}")
finally:
# 10) Restore default layout and close SC window
general.open_pane("Script Canvas")
helper.wait_for_condition(lambda: general.is_pane_visible("Script Canvas"), 5.0)
sc = editor_window.findChild(QtWidgets.QDockWidget, "Script Canvas")
click_menu_option(sc, "Restore Default Layout")
sc.close()
if __name__ == "__main__":
import ImportPathHelper as imports
imports.init()
from utils import Report
Report.start_test(Pane_PropertiesChanged_RetainsOnRestart)

@ -76,14 +76,8 @@ class TestAutomation(TestAutomationBase):
from . import ScriptCanvasComponent_OnEntityActivatedDeactivated_PrintMessage as test_module
self._run_test(request, workspace, editor, test_module)
<<<<<<< HEAD
def test_NodePalette_HappyPath_ClearSelection(self, request, workspace, editor, launcher_platform, project):
from . import NodePalette_HappyPath_ClearSelection as test_module
=======
@pytest.mark.test_case_id("T92562993")
def test_NodePalette_ClearSelection(self, request, workspace, editor, launcher_platform, project):
from . import NodePalette_ClearSelection as test_module
>>>>>>> main
self._run_test(request, workspace, editor, test_module)
@pytest.mark.parametrize("level", ["tmp_level"])
@ -119,7 +113,6 @@ class TestAutomation(TestAutomationBase):
from . import Debugger_HappyPath_TargetMultipleGraphs as test_module
self._run_test(request, workspace, editor, test_module)
@pytest.mark.test_case_id("T92569137")
def test_Debugging_TargetMultipleGraphs(self, request, workspace, editor, launcher_platform, project):
from . import Debugging_TargetMultipleGraphs as test_module
self._run_test(request, workspace, editor, test_module)
@ -262,3 +255,37 @@ class TestScriptCanvasTests(object):
auto_test_mode=False,
timeout=60,
)
@pytest.mark.parametrize(
"config",
[
{
"cfg_args": "before_restart",
"expected_lines": [
"All the test panes are opened: True",
"Test pane 1 is closed: True",
"Location of test pane 2 changed successfully: True",
"Test pane 3 resized successfully: True",
],
},
{
"cfg_args": "after_restart",
"expected_lines": [
"Test pane retained its visiblity on Editor restart: True",
"Test pane retained its location on Editor restart: True",
"Test pane retained its size on Editor restart: True",
],
},
],
)
def test_Pane_PropertiesChanged_RetainsOnRestart(self, request, editor, config, project, launcher_platform):
hydra.launch_and_validate_results(
request,
TEST_DIRECTORY,
editor,
"Pane_PropertiesChanged_RetainsOnRestart.py",
config.get('expected_lines'),
cfg_args=[config.get('cfg_args')],
auto_test_mode=False,
timeout=60,
)

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:f0f4d4e0155feaa76c80a14128000a0fd9570ab76e79f4847eaef9006324a4d2
size 9084

@ -0,0 +1,6 @@
<download name="ClientAuth" type="Map">
<index src="filelist.xml" dest="filelist.xml"/>
<files>
<file src="level.pak" dest="level.pak" size="ED3" md5="3487525589271b5744ca83734fe3baa6"/>
</files>
</download>

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:4900bdf28654e21032e69957f2762fa0a3b93a4b82163267a1f10f19f6d78692
size 3795

@ -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,10 @@
{
"Amazon":
{
"AWSCore":
{
"ProfileName": "default",
"ResourceMappingConfigFileName": "aws_resource_mappings.json"
}
}
}

@ -0,0 +1,900 @@
/*
* 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.
*
*/
// Original file Copyright Crytek GMBH or its affiliates, used under license.
#include "CrySystem_precompiled.h"
#include "DebugCallStack.h"
#if defined(WIN32) || defined(WIN64)
#include <IConsole.h>
#include <CryPath.h>
#include "System.h"
#include <AzCore/Debug/StackTracer.h>
#include <AzCore/Debug/EventTraceDrillerBus.h>
#define VS_VERSION_INFO 1
#define IDD_CRITICAL_ERROR 101
#define IDB_CONFIRM_SAVE 102
#define IDB_DONT_SAVE 103
#define IDD_CONFIRM_SAVE_LEVEL 127
#define IDB_CRASH_FACE 128
#define IDD_EXCEPTION 245
#define IDC_CALLSTACK 1001
#define IDC_EXCEPTION_CODE 1002
#define IDC_EXCEPTION_ADDRESS 1003
#define IDC_EXCEPTION_MODULE 1004
#define IDC_EXCEPTION_DESC 1005
#define IDB_EXIT 1008
#define IDB_IGNORE 1010
__pragma(comment(lib, "version.lib"))
//! Needs one external of DLL handle.
extern HMODULE gDLLHandle;
#include <DbgHelp.h>
#define MAX_PATH_LENGTH 1024
#define MAX_SYMBOL_LENGTH 512
static HWND hwndException = 0;
static bool g_bUserDialog = true; // true=on crash show dialog box, false=supress user interaction
static int PrintException(EXCEPTION_POINTERS* pex);
static bool IsFloatingPointException(EXCEPTION_POINTERS* pex);
extern LONG WINAPI CryEngineExceptionFilterWER(struct _EXCEPTION_POINTERS* pExceptionPointers);
extern LONG WINAPI CryEngineExceptionFilterMiniDump(struct _EXCEPTION_POINTERS* pExceptionPointers, const char* szDumpPath, MINIDUMP_TYPE mdumpValue);
//=============================================================================
CONTEXT CaptureCurrentContext()
{
CONTEXT context;
memset(&context, 0, sizeof(context));
context.ContextFlags = CONTEXT_FULL;
RtlCaptureContext(&context);
return context;
}
LONG __stdcall CryUnhandledExceptionHandler(EXCEPTION_POINTERS* pex)
{
return DebugCallStack::instance()->handleException(pex);
}
BOOL CALLBACK EnumModules(
PCSTR ModuleName,
DWORD64 BaseOfDll,
PVOID UserContext)
{
DebugCallStack::TModules& modules = *static_cast<DebugCallStack::TModules*>(UserContext);
modules[(void*)BaseOfDll] = ModuleName;
return TRUE;
}
//=============================================================================
// Class Statics
//=============================================================================
// Return single instance of class.
IDebugCallStack* IDebugCallStack::instance()
{
static DebugCallStack sInstance;
return &sInstance;
}
//------------------------------------------------------------------------------------------------------------------------
// Sets up the symbols for functions in the debug file.
//------------------------------------------------------------------------------------------------------------------------
DebugCallStack::DebugCallStack()
: prevExceptionHandler(0)
, m_pSystem(0)
, m_nSkipNumFunctions(0)
, m_bCrash(false)
, m_szBugMessage(NULL)
{
}
DebugCallStack::~DebugCallStack()
{
}
void DebugCallStack::RemoveOldFiles()
{
RemoveFile("error.log");
RemoveFile("error.bmp");
RemoveFile("error.dmp");
}
void DebugCallStack::RemoveFile(const char* szFileName)
{
FILE* pFile = nullptr;
azfopen(&pFile, szFileName, "r");
const bool bFileExists = (pFile != NULL);
if (bFileExists)
{
fclose(pFile);
WriteLineToLog("Removing file \"%s\"...", szFileName);
if (remove(szFileName) == 0)
{
WriteLineToLog("File successfully removed.");
}
else
{
WriteLineToLog("Couldn't remove file!");
}
}
}
void DebugCallStack::installErrorHandler(ISystem* pSystem)
{
m_pSystem = pSystem;
prevExceptionHandler = (void*)SetUnhandledExceptionFilter(CryUnhandledExceptionHandler);
}
//////////////////////////////////////////////////////////////////////////
void DebugCallStack::SetUserDialogEnable(const bool bUserDialogEnable)
{
g_bUserDialog = bUserDialogEnable;
}
DWORD g_idDebugThreads[10];
const char* g_nameDebugThreads[10];
int g_nDebugThreads = 0;
volatile int g_lockThreadDumpList = 0;
void MarkThisThreadForDebugging(const char* name)
{
EBUS_EVENT(AZ::Debug::EventTraceDrillerSetupBus, SetThreadName, AZStd::this_thread::get_id(), name);
WriteLock lock(g_lockThreadDumpList);
DWORD id = GetCurrentThreadId();
if (g_nDebugThreads == sizeof(g_idDebugThreads) / sizeof(g_idDebugThreads[0]))
{
return;
}
for (int i = 0; i < g_nDebugThreads; i++)
{
if (g_idDebugThreads[i] == id)
{
return;
}
}
g_nameDebugThreads[g_nDebugThreads] = name;
g_idDebugThreads[g_nDebugThreads++] = id;
((CSystem*)gEnv->pSystem)->EnableFloatExceptions(g_cvars.sys_float_exceptions);
}
void UnmarkThisThreadFromDebugging()
{
WriteLock lock(g_lockThreadDumpList);
DWORD id = GetCurrentThreadId();
for (int i = g_nDebugThreads - 1; i >= 0; i--)
{
if (g_idDebugThreads[i] == id)
{
memmove(g_idDebugThreads + i, g_idDebugThreads + i + 1, (g_nDebugThreads - 1 - i) * sizeof(g_idDebugThreads[0]));
memmove(g_nameDebugThreads + i, g_nameDebugThreads + i + 1, (g_nDebugThreads - 1 - i) * sizeof(g_nameDebugThreads[0]));
--g_nDebugThreads;
}
}
}
extern int prev_sys_float_exceptions;
void UpdateFPExceptionsMaskForThreads()
{
int mask = -iszero(g_cvars.sys_float_exceptions);
CONTEXT ctx;
for (int i = 0; i < g_nDebugThreads; i++)
{
if (g_idDebugThreads[i] != GetCurrentThreadId())
{
HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, TRUE, g_idDebugThreads[i]);
ctx.ContextFlags = CONTEXT_ALL;
SuspendThread(hThread);
GetThreadContext(hThread, &ctx);
#ifndef WIN64
(ctx.FloatSave.ControlWord |= 7) &= ~5 | mask;
(*(WORD*)(ctx.ExtendedRegisters + 24) |= 0x280) &= ~0x280 | mask;
#else
(ctx.FltSave.ControlWord |= 7) &= ~5 | mask;
(ctx.FltSave.MxCsr |= 0x280) &= ~0x280 | mask;
#endif
SetThreadContext(hThread, &ctx);
ResumeThread(hThread);
}
}
}
//////////////////////////////////////////////////////////////////////////
int DebugCallStack::handleException(EXCEPTION_POINTERS* exception_pointer)
{
if (gEnv == NULL)
{
return EXCEPTION_EXECUTE_HANDLER;
}
ResetFPU(exception_pointer);
prev_sys_float_exceptions = 0;
const int cached_sys_float_exceptions = g_cvars.sys_float_exceptions;
((CSystem*)gEnv->pSystem)->EnableFloatExceptions(0);
if (g_cvars.sys_WER)
{
gEnv->pLog->FlushAndClose();
return CryEngineExceptionFilterWER(exception_pointer);
}
if (g_cvars.sys_no_crash_dialog)
{
DWORD dwMode = SetErrorMode(SEM_NOGPFAULTERRORBOX);
SetErrorMode(dwMode | SEM_NOGPFAULTERRORBOX);
}
m_bCrash = true;
if (g_cvars.sys_no_crash_dialog)
{
DWORD dwMode = SetErrorMode(SEM_NOGPFAULTERRORBOX);
SetErrorMode(dwMode | SEM_NOGPFAULTERRORBOX);
}
static bool firstTime = true;
if (g_cvars.sys_dump_aux_threads)
{
for (int i = 0; i < g_nDebugThreads; i++)
{
if (g_idDebugThreads[i] != GetCurrentThreadId())
{
SuspendThread(OpenThread(THREAD_ALL_ACCESS, TRUE, g_idDebugThreads[i]));
}
}
}
// uninstall our exception handler.
SetUnhandledExceptionFilter((LPTOP_LEVEL_EXCEPTION_FILTER)prevExceptionHandler);
if (!firstTime)
{
WriteLineToLog("Critical Exception! Called Multiple Times!");
gEnv->pLog->FlushAndClose();
// Exception called more then once.
return EXCEPTION_EXECUTE_HANDLER;
}
// Print exception info:
{
char excCode[80];
char excAddr[80];
WriteLineToLog("<CRITICAL EXCEPTION>");
sprintf_s(excAddr, "0x%04X:0x%p", exception_pointer->ContextRecord->SegCs, exception_pointer->ExceptionRecord->ExceptionAddress);
sprintf_s(excCode, "0x%08X", exception_pointer->ExceptionRecord->ExceptionCode);
WriteLineToLog("Exception: %s, at Address: %s", excCode, excAddr);
}
firstTime = false;
const int ret = SubmitBug(exception_pointer);
if (ret != IDB_IGNORE)
{
CryEngineExceptionFilterWER(exception_pointer);
}
gEnv->pLog->FlushAndClose();
if (exception_pointer->ExceptionRecord->ExceptionFlags & EXCEPTION_NONCONTINUABLE)
{
// This is non continuable exception. abort application now.
exit(exception_pointer->ExceptionRecord->ExceptionCode);
}
//typedef long (__stdcall *ExceptionFunc)(EXCEPTION_POINTERS*);
//ExceptionFunc prevFunc = (ExceptionFunc)prevExceptionHandler;
//return prevFunc( (EXCEPTION_POINTERS*)exception_pointer );
if (ret == IDB_EXIT)
{
// Immediate exit.
// on windows, exit() and _exit() do all sorts of things, unfortuantely
// TerminateProcess is the only way to die.
TerminateProcess(GetCurrentProcess(), exception_pointer->ExceptionRecord->ExceptionCode); // we crashed, so don't return a zero exit code!
// on linux based systems, _exit will not call ATEXIT and other things, which makes it more suitable for termination in an emergency such
// as an unhandled exception.
// however, this function is a windows exception handler.
}
else if (ret == IDB_IGNORE)
{
#ifndef WIN64
exception_pointer->ContextRecord->FloatSave.StatusWord &= ~31;
exception_pointer->ContextRecord->FloatSave.ControlWord |= 7;
(*(WORD*)(exception_pointer->ContextRecord->ExtendedRegisters + 24) &= 31) |= 0x1F80;
#else
exception_pointer->ContextRecord->FltSave.StatusWord &= ~31;
exception_pointer->ContextRecord->FltSave.ControlWord |= 7;
(exception_pointer->ContextRecord->FltSave.MxCsr &= 31) |= 0x1F80;
#endif
firstTime = true;
prevExceptionHandler = (void*)SetUnhandledExceptionFilter(CryUnhandledExceptionHandler);
g_cvars.sys_float_exceptions = cached_sys_float_exceptions;
((CSystem*)gEnv->pSystem)->EnableFloatExceptions(g_cvars.sys_float_exceptions);
return EXCEPTION_CONTINUE_EXECUTION;
}
// Continue;
return EXCEPTION_EXECUTE_HANDLER;
}
void DebugCallStack::ReportBug(const char* szErrorMessage)
{
WriteLineToLog("Reporting bug: %s", szErrorMessage);
m_szBugMessage = szErrorMessage;
m_context = CaptureCurrentContext();
SubmitBug(NULL);
m_szBugMessage = NULL;
}
void DebugCallStack::dumpCallStack(std::vector<string>& funcs)
{
WriteLineToLog("=============================================================================");
int len = (int)funcs.size();
for (int i = 0; i < len; i++)
{
const char* str = funcs[i].c_str();
WriteLineToLog("%2d) %s", len - i, str);
}
WriteLineToLog("=============================================================================");
}
//////////////////////////////////////////////////////////////////////////
void DebugCallStack::LogExceptionInfo(EXCEPTION_POINTERS* pex)
{
string path("");
if ((gEnv) && (gEnv->pFileIO))
{
const char* logAlias = gEnv->pFileIO->GetAlias("@log@");
if (!logAlias)
{
logAlias = gEnv->pFileIO->GetAlias("@root@");
}
if (logAlias)
{
path = logAlias;
path += "/";
}
}
string fileName = path;
fileName += "error.log";
struct stat fileInfo;
string timeStamp;
string backupPath;
if (gEnv->IsDedicated())
{
backupPath = PathUtil::ToUnixPath(PathUtil::AddSlash(path + "DumpBackups"));
gEnv->pFileIO->CreatePath(backupPath.c_str());
if (stat(fileName.c_str(), &fileInfo) == 0)
{
// Backup log
tm creationTime;
localtime_s(&creationTime, &fileInfo.st_mtime);
char tempBuffer[32];
strftime(tempBuffer, sizeof(tempBuffer), "%d %b %Y (%H %M %S)", &creationTime);
timeStamp = tempBuffer;
string backupFileName = backupPath + timeStamp + " error.log";
CopyFile(fileName.c_str(), backupFileName.c_str(), true);
}
}
FILE* f = nullptr;
azfopen(&f, fileName.c_str(), "wt");
static char errorString[s_iCallStackSize];
errorString[0] = 0;
// Time and Version.
char versionbuf[1024];
azstrcpy(versionbuf, AZ_ARRAY_SIZE(versionbuf), "");
PutVersion(versionbuf, AZ_ARRAY_SIZE(versionbuf));
cry_strcat(errorString, versionbuf);
cry_strcat(errorString, "\n");
char excCode[MAX_WARNING_LENGTH];
char excAddr[80];
char desc[1024];
char excDesc[MAX_WARNING_LENGTH];
// make sure the mouse cursor is visible
ShowCursor(TRUE);
const char* excName;
if (m_bIsFatalError || !pex)
{
const char* const szMessage = m_bIsFatalError ? s_szFatalErrorCode : m_szBugMessage;
excName = szMessage;
cry_strcpy(excCode, szMessage);
cry_strcpy(excAddr, "");
cry_strcpy(desc, "");
cry_strcpy(m_excModule, "");
cry_strcpy(excDesc, szMessage);
}
else
{
sprintf_s(excAddr, "0x%04X:0x%p", pex->ContextRecord->SegCs, pex->ExceptionRecord->ExceptionAddress);
sprintf_s(excCode, "0x%08X", pex->ExceptionRecord->ExceptionCode);
excName = TranslateExceptionCode(pex->ExceptionRecord->ExceptionCode);
cry_strcpy(desc, "");
sprintf_s(excDesc, "%s\r\n%s", excName, desc);
if (pex->ExceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION)
{
if (pex->ExceptionRecord->NumberParameters > 1)
{
ULONG_PTR iswrite = pex->ExceptionRecord->ExceptionInformation[0];
DWORD64 accessAddr = pex->ExceptionRecord->ExceptionInformation[1];
if (iswrite)
{
sprintf_s(desc, "Attempt to write data to address 0x%08llu\r\nThe memory could not be \"written\"", accessAddr);
}
else
{
sprintf_s(desc, "Attempt to read from address 0x%08llu\r\nThe memory could not be \"read\"", accessAddr);
}
}
}
}
WriteLineToLog("Exception Code: %s", excCode);
WriteLineToLog("Exception Addr: %s", excAddr);
WriteLineToLog("Exception Module: %s", m_excModule);
WriteLineToLog("Exception Name : %s", excName);
WriteLineToLog("Exception Description: %s", desc);
cry_strcpy(m_excDesc, excDesc);
cry_strcpy(m_excAddr, excAddr);
cry_strcpy(m_excCode, excCode);
char errs[32768];
sprintf_s(errs, "Exception Code: %s\nException Addr: %s\nException Module: %s\nException Description: %s, %s\n",
excCode, excAddr, m_excModule, excName, desc);
cry_strcat(errs, "\nCall Stack Trace:\n");
std::vector<string> funcs;
{
AZ::Debug::StackFrame frames[25];
AZ::Debug::SymbolStorage::StackLine lines[AZ_ARRAY_SIZE(frames)];
unsigned int numFrames = AZ::Debug::StackRecorder::Record(frames, AZ_ARRAY_SIZE(frames), 3);
if (numFrames)
{
AZ::Debug::SymbolStorage::DecodeFrames(frames, numFrames, lines);
for (unsigned int i = 0; i < numFrames; i++)
{
funcs.push_back(lines[i]);
}
}
dumpCallStack(funcs);
// Fill call stack.
char str[s_iCallStackSize];
cry_strcpy(str, "");
for (unsigned int i = 0; i < funcs.size(); i++)
{
char temp[s_iCallStackSize];
sprintf_s(temp, "%2zd) %s", funcs.size() - i, (const char*)funcs[i].c_str());
cry_strcat(str, temp);
cry_strcat(str, "\r\n");
cry_strcat(errs, temp);
cry_strcat(errs, "\n");
}
cry_strcpy(m_excCallstack, str);
}
cry_strcat(errorString, errs);
if (f)
{
fwrite(errorString, strlen(errorString), 1, f);
{
if (g_cvars.sys_dump_aux_threads)
{
for (int i = 0; i < g_nDebugThreads; i++)
{
if (g_idDebugThreads[i] != GetCurrentThreadId())
{
fprintf(f, "\n\nSuspended thread (%s):\n", g_nameDebugThreads[i]);
HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, TRUE, g_idDebugThreads[i]);
// mirrors the AZ::Debug::Trace::PrintCallstack() functionality, but prints to a file
{
AZ::Debug::StackFrame frames[10];
// Without StackFrame explicit alignment frames array is aligned to 4 bytes
// which causes the stack tracing to fail.
AZ::Debug::SymbolStorage::StackLine lines[AZ_ARRAY_SIZE(frames)];
unsigned int numFrames = AZ::Debug::StackRecorder::Record(frames, AZ_ARRAY_SIZE(frames), 0, hThread);
if (numFrames)
{
AZ::Debug::SymbolStorage::DecodeFrames(frames, numFrames, lines);
for (unsigned int i2 = 0; i2 < numFrames; ++i2)
{
fprintf(f, "%2d) %s\n", numFrames - i2, lines[i2]);
}
}
}
ResumeThread(hThread);
}
}
}
}
fflush(f);
fclose(f);
}
if (pex)
{
MINIDUMP_TYPE mdumpValue;
bool bDump = true;
switch (g_cvars.sys_dump_type)
{
case 0:
bDump = false;
break;
case 1:
mdumpValue = MiniDumpNormal;
break;
case 2:
mdumpValue = (MINIDUMP_TYPE)(MiniDumpWithIndirectlyReferencedMemory | MiniDumpWithDataSegs);
break;
case 3:
mdumpValue = MiniDumpWithFullMemory;
break;
default:
mdumpValue = (MINIDUMP_TYPE)g_cvars.sys_dump_type;
break;
}
if (bDump)
{
fileName = path + "error.dmp";
if (gEnv->IsDedicated() && stat(fileName.c_str(), &fileInfo) == 0)
{
// Backup dump (use timestamp from error.log if available)
if (timeStamp.empty())
{
tm creationTime;
localtime_s(&creationTime, &fileInfo.st_mtime);
char tempBuffer[32];
strftime(tempBuffer, sizeof(tempBuffer), "%d %b %Y (%H %M %S)", &creationTime);
timeStamp = tempBuffer;
}
string backupFileName = backupPath + timeStamp + " error.dmp";
CopyFile(fileName.c_str(), backupFileName.c_str(), true);
}
CryEngineExceptionFilterMiniDump(pex, fileName.c_str(), mdumpValue);
}
}
//if no crash dialog don't even submit the bug
if (m_postBackupProcess && g_cvars.sys_no_crash_dialog == 0 && g_bUserDialog)
{
m_postBackupProcess();
}
else
{
// lawsonn: Disabling the JIRA-based crash reporter for now
// we'll need to deal with it our own way, pending QA.
// if you're customizing the engine this is also your opportunity to deal with it.
if (g_cvars.sys_no_crash_dialog != 0 || !g_bUserDialog)
{
// ------------ place custom crash handler here ---------------------
// it should launch an executable!
/// by this time, error.bmp will be in the engine root folder
// error.log and error.dmp will also be present in the engine root folder
// if your error dumper wants those, it should zip them up and send them or offer to do so.
// ------------------------------------------------------------------
}
}
const bool bQuitting = !gEnv || !gEnv->pSystem || gEnv->pSystem->IsQuitting();
//[AlexMcC|16.04.10] When the engine is shutting down, MessageBox doesn't display a box
// and immediately returns IDYES. Avoid this by just not trying to save if we're quitting.
// Don't ask to save if this isn't a real crash (a real crash has exception pointers)
if (g_cvars.sys_no_crash_dialog == 0 && g_bUserDialog && gEnv->IsEditor() && !bQuitting && pex)
{
BackupCurrentLevel();
const INT_PTR res = DialogBoxParam(gDLLHandle, MAKEINTRESOURCE(IDD_CONFIRM_SAVE_LEVEL), NULL, DebugCallStack::ConfirmSaveDialogProc, NULL);
if (res == IDB_CONFIRM_SAVE)
{
if (SaveCurrentLevel())
{
MessageBox(NULL, "Level has been successfully saved!\r\nPress Ok to terminate Editor.", "Save", MB_OK);
}
else
{
MessageBox(NULL, "Error saving level.\r\nPress Ok to terminate Editor.", "Save", MB_OK | MB_ICONWARNING);
}
}
}
if (g_cvars.sys_no_crash_dialog != 0 || !g_bUserDialog)
{
// terminate immediately - since we're in a crash, there is no point unwinding stack, we've already done access violation or worse.
// calling exit will only cause further death down the line...
TerminateProcess(GetCurrentProcess(), pex->ExceptionRecord->ExceptionCode);
}
}
INT_PTR CALLBACK DebugCallStack::ExceptionDialogProc(HWND hwndDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
static EXCEPTION_POINTERS* pex;
static char errorString[32768] = "";
switch (message)
{
case WM_INITDIALOG:
{
pex = (EXCEPTION_POINTERS*)lParam;
HWND h;
if (pex->ExceptionRecord->ExceptionFlags & EXCEPTION_NONCONTINUABLE)
{
// Disable continue button for non continuable exceptions.
//h = GetDlgItem( hwndDlg,IDB_CONTINUE );
//if (h) EnableWindow( h,FALSE );
}
DebugCallStack* pDCS = static_cast<DebugCallStack*>(DebugCallStack::instance());
h = GetDlgItem(hwndDlg, IDC_EXCEPTION_DESC);
if (h)
{
SendMessage(h, EM_REPLACESEL, FALSE, (LONG_PTR)pDCS->m_excDesc);
}
h = GetDlgItem(hwndDlg, IDC_EXCEPTION_CODE);
if (h)
{
SendMessage(h, EM_REPLACESEL, FALSE, (LONG_PTR)pDCS->m_excCode);
}
h = GetDlgItem(hwndDlg, IDC_EXCEPTION_MODULE);
if (h)
{
SendMessage(h, EM_REPLACESEL, FALSE, (LONG_PTR)pDCS->m_excModule);
}
h = GetDlgItem(hwndDlg, IDC_EXCEPTION_ADDRESS);
if (h)
{
SendMessage(h, EM_REPLACESEL, FALSE, (LONG_PTR)pDCS->m_excAddr);
}
// Fill call stack.
HWND callStack = GetDlgItem(hwndDlg, IDC_CALLSTACK);
if (callStack)
{
SendMessage(callStack, WM_SETTEXT, FALSE, (LPARAM)pDCS->m_excCallstack);
}
if (hwndException)
{
DestroyWindow(hwndException);
hwndException = 0;
}
if (IsFloatingPointException(pex))
{
EnableWindow(GetDlgItem(hwndDlg, IDB_IGNORE), TRUE);
}
}
break;
case WM_COMMAND:
switch (LOWORD(wParam))
{
case IDB_EXIT:
case IDB_IGNORE:
// Fall through.
EndDialog(hwndDlg, wParam);
return TRUE;
}
}
return FALSE;
}
INT_PTR CALLBACK DebugCallStack::ConfirmSaveDialogProc(HWND hwndDlg, UINT message, WPARAM wParam, [[maybe_unused]] LPARAM lParam)
{
switch (message)
{
case WM_INITDIALOG:
{
// The user might be holding down the spacebar while the engine crashes.
// If we don't remove keyboard focus from this dialog, the keypress will
// press the default button before the dialog actually appears, even if
// the user has already released the key, which is bad.
SetFocus(NULL);
} break;
case WM_COMMAND:
{
switch (LOWORD(wParam))
{
case IDB_CONFIRM_SAVE: // Fall through
case IDB_DONT_SAVE:
{
EndDialog(hwndDlg, wParam);
return TRUE;
}
}
} break;
}
return FALSE;
}
bool DebugCallStack::BackupCurrentLevel()
{
CSystem* pSystem = static_cast<CSystem*>(m_pSystem);
if (pSystem && pSystem->GetUserCallback())
{
return pSystem->GetUserCallback()->OnBackupDocument();
}
return false;
}
bool DebugCallStack::SaveCurrentLevel()
{
CSystem* pSystem = static_cast<CSystem*>(m_pSystem);
if (pSystem && pSystem->GetUserCallback())
{
return pSystem->GetUserCallback()->OnSaveDocument();
}
return false;
}
int DebugCallStack::SubmitBug(EXCEPTION_POINTERS* exception_pointer)
{
int ret = IDB_EXIT;
assert(!hwndException);
RemoveOldFiles();
AZ::Debug::Trace::PrintCallstack("", 2);
LogExceptionInfo(exception_pointer);
if (IsFloatingPointException(exception_pointer))
{
//! Print exception dialog.
ret = PrintException(exception_pointer);
}
return ret;
}
void DebugCallStack::ResetFPU(EXCEPTION_POINTERS* pex)
{
if (IsFloatingPointException(pex))
{
// How to reset FPU: http://www.experts-exchange.com/Programming/System/Windows__Programming/Q_10310953.html
_clearfp();
#ifndef WIN64
pex->ContextRecord->FloatSave.ControlWord |= 0x2F;
pex->ContextRecord->FloatSave.StatusWord &= ~0x8080;
#endif
}
}
string DebugCallStack::GetModuleNameForAddr(void* addr)
{
if (m_modules.empty())
{
return "[unknown]";
}
if (addr < m_modules.begin()->first)
{
return "[unknown]";
}
TModules::const_iterator it = m_modules.begin();
TModules::const_iterator end = m_modules.end();
for (; ++it != end; )
{
if (addr < it->first)
{
return (--it)->second;
}
}
//if address is higher than the last module, we simply assume it is in the last module.
return m_modules.rbegin()->second;
}
void DebugCallStack::GetProcNameForAddr(void* addr, string& procName, void*& baseAddr, string& filename, int& line)
{
AZ::Debug::SymbolStorage::StackLine func, file, module;
AZ::Debug::SymbolStorage::FindFunctionFromIP(addr, &func, &file, &module, line, baseAddr);
procName = func;
filename = file;
}
string DebugCallStack::GetCurrentFilename()
{
char fullpath[MAX_PATH_LENGTH + 1];
GetModuleFileName(NULL, fullpath, MAX_PATH_LENGTH);
return fullpath;
}
static bool IsFloatingPointException(EXCEPTION_POINTERS* pex)
{
if (!pex)
{
return false;
}
DWORD exceptionCode = pex->ExceptionRecord->ExceptionCode;
switch (exceptionCode)
{
case EXCEPTION_FLT_DENORMAL_OPERAND:
case EXCEPTION_FLT_DIVIDE_BY_ZERO:
case EXCEPTION_FLT_INEXACT_RESULT:
case EXCEPTION_FLT_INVALID_OPERATION:
case EXCEPTION_FLT_OVERFLOW:
case EXCEPTION_FLT_UNDERFLOW:
case STATUS_FLOAT_MULTIPLE_FAULTS:
case STATUS_FLOAT_MULTIPLE_TRAPS:
return true;
default:
return false;
}
}
int DebugCallStack::PrintException(EXCEPTION_POINTERS* exception_pointer)
{
return (int)DialogBoxParam(gDLLHandle, MAKEINTRESOURCE(IDD_CRITICAL_ERROR), NULL, DebugCallStack::ExceptionDialogProc, (LPARAM)exception_pointer);
}
#else
void MarkThisThreadForDebugging(const char*) {}
void UnmarkThisThreadFromDebugging() {}
void UpdateFPExceptionsMaskForThreads() {}
#endif //WIN32

@ -0,0 +1,95 @@
/*
* 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.
*
*/
// Original file Copyright Crytek GMBH or its affiliates, used under license.
#ifndef CRYINCLUDE_CRYSYSTEM_DEBUGCALLSTACK_H
#define CRYINCLUDE_CRYSYSTEM_DEBUGCALLSTACK_H
#pragma once
#include "IDebugCallStack.h"
#if defined (WIN32) || defined (WIN64)
//! Limits the maximal number of functions in call stack.
const int MAX_DEBUG_STACK_ENTRIES_FILE_DUMP = 12;
struct ISystem;
//!============================================================================
//!
//! DebugCallStack class, capture call stack information from symbol files.
//!
//!============================================================================
class DebugCallStack
: public IDebugCallStack
{
public:
DebugCallStack();
virtual ~DebugCallStack();
ISystem* GetSystem() { return m_pSystem; };
virtual string GetModuleNameForAddr(void* addr);
virtual void GetProcNameForAddr(void* addr, string& procName, void*& baseAddr, string& filename, int& line);
virtual string GetCurrentFilename();
void installErrorHandler(ISystem* pSystem);
virtual int handleException(EXCEPTION_POINTERS* exception_pointer);
virtual void ReportBug(const char*);
void dumpCallStack(std::vector<string>& functions);
void SetUserDialogEnable(const bool bUserDialogEnable);
typedef std::map<void*, string> TModules;
protected:
static void RemoveOldFiles();
static void RemoveFile(const char* szFileName);
static int PrintException(EXCEPTION_POINTERS* exception_pointer);
static INT_PTR CALLBACK ExceptionDialogProc(HWND hwndDlg, UINT message, WPARAM wParam, LPARAM lParam);
static INT_PTR CALLBACK ConfirmSaveDialogProc(HWND hwndDlg, UINT message, WPARAM wParam, LPARAM lParam);
void LogExceptionInfo(EXCEPTION_POINTERS* exception_pointer);
bool BackupCurrentLevel();
bool SaveCurrentLevel();
int SubmitBug(EXCEPTION_POINTERS* exception_pointer);
void ResetFPU(EXCEPTION_POINTERS* pex);
static const int s_iCallStackSize = 32768;
char m_excLine[256];
char m_excModule[128];
char m_excDesc[MAX_WARNING_LENGTH];
char m_excCode[MAX_WARNING_LENGTH];
char m_excAddr[80];
char m_excCallstack[s_iCallStackSize];
void* prevExceptionHandler;
bool m_bCrash;
const char* m_szBugMessage;
ISystem* m_pSystem;
int m_nSkipNumFunctions;
CONTEXT m_context;
TModules m_modules;
};
#endif //WIN32
#endif // CRYINCLUDE_CRYSYSTEM_DEBUGCALLSTACK_H

@ -14,6 +14,7 @@
#include "CrySystem_precompiled.h"
#include "System.h"
#include <AZCrySystemInitLogSink.h>
#include "DebugCallStack.h"
#if defined(AZ_RESTRICTED_PLATFORM)
#undef AZ_RESTRICTED_SECTION
@ -87,6 +88,16 @@ CRYSYSTEM_API ISystem* CreateSystemInterface(const SSystemInitParams& startupPar
startupParams.pUserCallback->OnSystemConnect(pSystem);
}
#if defined(WIN32)
// Environment Variable to signal we don't want to override our exception handler - our crash report system will set this
auto envVar = AZ::Environment::FindVariable<bool>("ExceptionHandlerIsSet");
const bool handlerIsSet = (envVar && *envVar);
if (!handlerIsSet)
{
((DebugCallStack*)IDebugCallStack::instance())->installErrorHandler(pSystem);
}
#endif
bool retVal = false;
{
AZ::Debug::StartupLogSinkReporter<AZ::Debug::CrySystemInitLogSink> initLogSink;

@ -0,0 +1,275 @@
/*
* 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.
*
*/
// Original file Copyright Crytek GMBH or its affiliates, used under license.
// Description : A multiplatform base class for handling errors and collecting call stacks
#include "CrySystem_precompiled.h"
#include "IDebugCallStack.h"
#include "System.h"
#include <AzFramework/IO/FileOperations.h>
#include <AzCore/NativeUI/NativeUIRequests.h>
#include <AzCore/StringFunc/StringFunc.h>
#include <AzCore/Utils/Utils.h>
//#if !defined(LINUX)
#include <ISystem.h>
const char* const IDebugCallStack::s_szFatalErrorCode = "FATAL_ERROR";
IDebugCallStack::IDebugCallStack()
: m_bIsFatalError(false)
, m_postBackupProcess(0)
, m_memAllocFileHandle(AZ::IO::InvalidHandle)
{
}
IDebugCallStack::~IDebugCallStack()
{
StopMemLog();
}
#if AZ_LEGACY_CRYSYSTEM_TRAIT_DEBUGCALLSTACK_SINGLETON
IDebugCallStack* IDebugCallStack::instance()
{
static IDebugCallStack sInstance;
return &sInstance;
}
#endif
void IDebugCallStack::FileCreationCallback(void (* postBackupProcess)())
{
m_postBackupProcess = postBackupProcess;
}
//////////////////////////////////////////////////////////////////////////
void IDebugCallStack::LogCallstack()
{
AZ::Debug::Trace::PrintCallstack("", 2);
}
const char* IDebugCallStack::TranslateExceptionCode(DWORD dwExcept)
{
switch (dwExcept)
{
#if AZ_LEGACY_CRYSYSTEM_TRAIT_DEBUGCALLSTACK_TRANSLATE
case EXCEPTION_ACCESS_VIOLATION:
return "EXCEPTION_ACCESS_VIOLATION";
break;
case EXCEPTION_DATATYPE_MISALIGNMENT:
return "EXCEPTION_DATATYPE_MISALIGNMENT";
break;
case EXCEPTION_BREAKPOINT:
return "EXCEPTION_BREAKPOINT";
break;
case EXCEPTION_SINGLE_STEP:
return "EXCEPTION_SINGLE_STEP";
break;
case EXCEPTION_ARRAY_BOUNDS_EXCEEDED:
return "EXCEPTION_ARRAY_BOUNDS_EXCEEDED";
break;
case EXCEPTION_FLT_DENORMAL_OPERAND:
return "EXCEPTION_FLT_DENORMAL_OPERAND";
break;
case EXCEPTION_FLT_DIVIDE_BY_ZERO:
return "EXCEPTION_FLT_DIVIDE_BY_ZERO";
break;
case EXCEPTION_FLT_INEXACT_RESULT:
return "EXCEPTION_FLT_INEXACT_RESULT";
break;
case EXCEPTION_FLT_INVALID_OPERATION:
return "EXCEPTION_FLT_INVALID_OPERATION";
break;
case EXCEPTION_FLT_OVERFLOW:
return "EXCEPTION_FLT_OVERFLOW";
break;
case EXCEPTION_FLT_STACK_CHECK:
return "EXCEPTION_FLT_STACK_CHECK";
break;
case EXCEPTION_FLT_UNDERFLOW:
return "EXCEPTION_FLT_UNDERFLOW";
break;
case EXCEPTION_INT_DIVIDE_BY_ZERO:
return "EXCEPTION_INT_DIVIDE_BY_ZERO";
break;
case EXCEPTION_INT_OVERFLOW:
return "EXCEPTION_INT_OVERFLOW";
break;
case EXCEPTION_PRIV_INSTRUCTION:
return "EXCEPTION_PRIV_INSTRUCTION";
break;
case EXCEPTION_IN_PAGE_ERROR:
return "EXCEPTION_IN_PAGE_ERROR";
break;
case EXCEPTION_ILLEGAL_INSTRUCTION:
return "EXCEPTION_ILLEGAL_INSTRUCTION";
break;
case EXCEPTION_NONCONTINUABLE_EXCEPTION:
return "EXCEPTION_NONCONTINUABLE_EXCEPTION";
break;
case EXCEPTION_STACK_OVERFLOW:
return "EXCEPTION_STACK_OVERFLOW";
break;
case EXCEPTION_INVALID_DISPOSITION:
return "EXCEPTION_INVALID_DISPOSITION";
break;
case EXCEPTION_GUARD_PAGE:
return "EXCEPTION_GUARD_PAGE";
break;
case EXCEPTION_INVALID_HANDLE:
return "EXCEPTION_INVALID_HANDLE";
break;
//case EXCEPTION_POSSIBLE_DEADLOCK: return "EXCEPTION_POSSIBLE_DEADLOCK"; break ;
case STATUS_FLOAT_MULTIPLE_FAULTS:
return "STATUS_FLOAT_MULTIPLE_FAULTS";
break;
case STATUS_FLOAT_MULTIPLE_TRAPS:
return "STATUS_FLOAT_MULTIPLE_TRAPS";
break;
#endif
default:
return "Unknown";
break;
}
}
void IDebugCallStack::PutVersion(char* str, size_t length)
{
AZ_PUSH_DISABLE_WARNING(4996, "-Wunknown-warning-option")
if (!gEnv || !gEnv->pSystem)
{
return;
}
char sFileVersion[128];
gEnv->pSystem->GetFileVersion().ToString(sFileVersion, sizeof(sFileVersion));
char sProductVersion[128];
gEnv->pSystem->GetProductVersion().ToString(sProductVersion, sizeof(sFileVersion));
//! Get time.
time_t ltime;
time(&ltime);
tm* today = localtime(&ltime);
char s[1024];
//! Use strftime to build a customized time string.
strftime(s, 128, "Logged at %#c\n", today);
azstrcat(str, length, s);
sprintf_s(s, "FileVersion: %s\n", sFileVersion);
azstrcat(str, length, s);
sprintf_s(s, "ProductVersion: %s\n", sProductVersion);
azstrcat(str, length, s);
if (gEnv->pLog)
{
const char* logfile = gEnv->pLog->GetFileName();
if (logfile)
{
sprintf (s, "LogFile: %s\n", logfile);
azstrcat(str, length, s);
}
}
AZ::IO::FixedMaxPathString projectPath = AZ::Utils::GetProjectPath();
azstrcat(str, length, "ProjectDir: ");
azstrcat(str, length, projectPath.c_str());
azstrcat(str, length, "\n");
#if AZ_LEGACY_CRYSYSTEM_TRAIT_DEBUGCALLSTACK_APPEND_MODULENAME
GetModuleFileNameA(NULL, s, sizeof(s));
// Log EXE filename only if possible (not full EXE path which could contain sensitive info)
AZStd::string exeName;
if (AZ::StringFunc::Path::GetFullFileName(s, exeName))
{
azstrcat(str, length, "Executable: ");
azstrcat(str, length, exeName.c_str());
# ifdef AZ_DEBUG_BUILD
azstrcat(str, length, " (debug: yes");
# else
azstrcat(str, length, " (debug: no");
# endif
}
#endif
AZ_POP_DISABLE_WARNING
}
//Crash the application, in this way the debug callstack routine will be called and it will create all the necessary files (error.log, dump, and eventually screenshot)
void IDebugCallStack::FatalError(const char* description)
{
m_bIsFatalError = true;
WriteLineToLog(description);
#ifndef _RELEASE
bool bShowDebugScreen = g_cvars.sys_no_crash_dialog == 0;
// showing the debug screen is not safe when not called from mainthread
// it normally leads to a infinity recursion followed by a stack overflow, preventing
// useful call stacks, thus they are disabled
bShowDebugScreen = bShowDebugScreen && gEnv->mMainThreadId == CryGetCurrentThreadId();
if (bShowDebugScreen)
{
EBUS_EVENT(AZ::NativeUI::NativeUIRequestBus, DisplayOkDialog, "Open 3D Engine Fatal Error", description, false);
}
#endif
#if defined(WIN32) || !defined(_RELEASE)
int* p = 0x0;
PREFAST_SUPPRESS_WARNING(6011) * p = 1; // we're intentionally crashing here
#endif
}
void IDebugCallStack::WriteLineToLog(const char* format, ...)
{
va_list ArgList;
char szBuffer[MAX_WARNING_LENGTH];
va_start(ArgList, format);
vsnprintf_s(szBuffer, sizeof(szBuffer), sizeof(szBuffer) - 1, format, ArgList);
cry_strcat(szBuffer, "\n");
szBuffer[sizeof(szBuffer) - 1] = '\0';
va_end(ArgList);
AZ::IO::HandleType fileHandle = AZ::IO::InvalidHandle;
AZ::IO::FileIOBase::GetDirectInstance()->Open("@Log@\\error.log", AZ::IO::GetOpenModeFromStringMode("a+t"), fileHandle);
if (fileHandle != AZ::IO::InvalidHandle)
{
AZ::IO::FileIOBase::GetDirectInstance()->Write(fileHandle, szBuffer, strlen(szBuffer));
AZ::IO::FileIOBase::GetDirectInstance()->Flush(fileHandle);
AZ::IO::FileIOBase::GetDirectInstance()->Close(fileHandle);
}
}
//////////////////////////////////////////////////////////////////////////
void IDebugCallStack::StartMemLog()
{
AZ::IO::FileIOBase::GetDirectInstance()->Open("@Log@\\memallocfile.log", AZ::IO::OpenMode::ModeWrite, m_memAllocFileHandle);
assert(m_memAllocFileHandle != AZ::IO::InvalidHandle);
}
//////////////////////////////////////////////////////////////////////////
void IDebugCallStack::StopMemLog()
{
if (m_memAllocFileHandle != AZ::IO::InvalidHandle)
{
AZ::IO::FileIOBase::GetDirectInstance()->Close(m_memAllocFileHandle);
m_memAllocFileHandle = AZ::IO::InvalidHandle;
}
}
//#endif //!defined(LINUX)

@ -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.
*
*/
// Original file Copyright Crytek GMBH or its affiliates, used under license.
// Description : A multiplatform base class for handling errors and collecting call stacks
#ifndef CRYINCLUDE_CRYSYSTEM_IDEBUGCALLSTACK_H
#define CRYINCLUDE_CRYSYSTEM_IDEBUGCALLSTACK_H
#pragma once
#include "System.h"
#if AZ_LEGACY_CRYSYSTEM_TRAIT_FORWARD_EXCEPTION_POINTERS
struct EXCEPTION_POINTERS;
#endif
//! Limits the maximal number of functions in call stack.
enum
{
MAX_DEBUG_STACK_ENTRIES = 80
};
class IDebugCallStack
{
public:
// Returns single instance of DebugStack
static IDebugCallStack* instance();
virtual int handleException([[maybe_unused]] EXCEPTION_POINTERS* exception_pointer){return 0; }
// returns the module name of a given address
virtual string GetModuleNameForAddr([[maybe_unused]] void* addr) { return "[unknown]"; }
// returns the function name of a given address together with source file and line number (if available) of a given address
virtual void GetProcNameForAddr(void* addr, string& procName, void*& baseAddr, string& filename, int& line)
{
filename = "[unknown]";
line = 0;
baseAddr = addr;
#if defined(PLATFORM_64BIT)
procName.Format("[%016llX]", addr);
#else
procName.Format("[%08X]", addr);
#endif
}
// returns current filename
virtual string GetCurrentFilename() { return "[unknown]"; }
//! Dumps Current Call Stack to log.
virtual void LogCallstack();
//triggers a fatal error, so the DebugCallstack can create the error.log and terminate the application
void FatalError(const char*);
//Reports a bug and continues execution
virtual void ReportBug(const char*) {}
virtual void FileCreationCallback(void (* postBackupProcess)());
static void WriteLineToLog(const char* format, ...);
virtual void StartMemLog();
virtual void StopMemLog();
protected:
IDebugCallStack();
virtual ~IDebugCallStack();
static const char* TranslateExceptionCode(DWORD dwExcept);
static void PutVersion(char* str, size_t length);
bool m_bIsFatalError;
static const char* const s_szFatalErrorCode;
void (* m_postBackupProcess)();
AZ::IO::HandleType m_memAllocFileHandle;
};
#endif // CRYINCLUDE_CRYSYSTEM_IDEBUGCALLSTACK_H

@ -208,6 +208,7 @@ struct SSystemCVars
int sys_no_crash_dialog;
int sys_no_error_report_window;
int sys_dump_aux_threads;
int sys_WER;
int sys_dump_type;
int sys_ai;
int sys_entitysystem;

@ -121,6 +121,10 @@
# include <AzFramework/Network/AssetProcessorConnection.h>
#endif
#ifdef WIN32
extern LONG WINAPI CryEngineExceptionFilterWER(struct _EXCEPTION_POINTERS* pExceptionPointers);
#endif
#if defined(AZ_RESTRICTED_PLATFORM)
#define AZ_RESTRICTED_SECTION SYSTEMINIT_CPP_SECTION_14
#include AZ_RESTRICTED_FILE(SystemInit_cpp)
@ -1484,6 +1488,13 @@ AZ_POP_DISABLE_WARNING
InlineInitializationProcessing("CSystem::Init LoadConfigurations");
#ifdef WIN32
if (g_cvars.sys_WER)
{
SetUnhandledExceptionFilter(CryEngineExceptionFilterWER);
}
#endif
//////////////////////////////////////////////////////////////////////////
// Localization
//////////////////////////////////////////////////////////////////////////
@ -2020,6 +2031,14 @@ void CSystem::CreateSystemVars()
REGISTER_CVAR2("sys_update_profile_time", &g_cvars.sys_update_profile_time, 1.0f, 0, "Time to keep updates timings history for.");
REGISTER_CVAR2("sys_no_crash_dialog", &g_cvars.sys_no_crash_dialog, m_bNoCrashDialog, VF_NULL, "Whether to disable the crash dialog window");
REGISTER_CVAR2("sys_no_error_report_window", &g_cvars.sys_no_error_report_window, m_bNoErrorReportWindow, VF_NULL, "Whether to disable the error report list");
#if defined(_RELEASE)
if (!gEnv->IsDedicated())
{
REGISTER_CVAR2("sys_WER", &g_cvars.sys_WER, 1, 0, "Enables Windows Error Reporting");
}
#else
REGISTER_CVAR2("sys_WER", &g_cvars.sys_WER, 0, 0, "Enables Windows Error Reporting");
#endif
#ifdef USE_HTTP_WEBSOCKETS
REGISTER_CVAR2("sys_simple_http_base_port", &g_cvars.sys_simple_http_base_port, 1880, VF_REQUIRE_APP_RESTART,

@ -46,6 +46,8 @@
#include <shlobj.h>
#endif
#include "IDebugCallStack.h"
#if defined(APPLE) || defined(LINUX)
#include <pwd.h>
#endif
@ -355,6 +357,7 @@ void CSystem::FatalError(const char* format, ...)
}
// Dump callstack.
IDebugCallStack::instance()->FatalError(szBuffer);
#endif
CryDebugBreak();
@ -396,6 +399,8 @@ void CSystem::ReportBug([[maybe_unused]] const char* format, ...)
va_start(ArgList, format);
azvsnprintf(szBuffer + strlen(sPrefix), MAX_WARNING_LENGTH - strlen(sPrefix), format, ArgList);
va_end(ArgList);
IDebugCallStack::instance()->ReportBug(szBuffer);
#endif
}

@ -0,0 +1,137 @@
/*
* 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.
*
*/
// Original file Copyright Crytek GMBH or its affiliates, used under license.
// Description : Support for Windows Error Reporting (WER)
#include "CrySystem_precompiled.h"
#ifdef WIN32
#include "System.h"
#include <windows.h>
#include <tchar.h>
#include "errorrep.h"
#include "ISystem.h"
#include <DbgHelp.h>
static WCHAR szPath[MAX_PATH + 1];
static WCHAR szFR[] = L"\\System32\\FaultRep.dll";
WCHAR* GetFullPathToFaultrepDll(void)
{
UINT rc = GetSystemWindowsDirectoryW(szPath, ARRAYSIZE(szPath));
if (rc == 0 || rc > ARRAYSIZE(szPath) - ARRAYSIZE(szFR) - 1)
{
return NULL;
}
wcscat_s(szPath, szFR);
return szPath;
}
typedef BOOL (WINAPI * MINIDUMPWRITEDUMP)(HANDLE hProcess, DWORD dwPid, HANDLE hFile, MINIDUMP_TYPE DumpType,
CONST PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam,
CONST PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam,
CONST PMINIDUMP_CALLBACK_INFORMATION CallbackParam
);
//////////////////////////////////////////////////////////////////////////
LONG WINAPI CryEngineExceptionFilterMiniDump(struct _EXCEPTION_POINTERS* pExceptionPointers, const char* szDumpPath, MINIDUMP_TYPE DumpType)
{
// note: In debug mode, this dll is loaded on startup anyway, so this should not incur an additional load unless it crashes
// very early during startup.
fflush(nullptr); // according to MSDN on fflush, calling fflush on null flushes all buffers.
HMODULE hndDBGHelpDLL = LoadLibraryA("DBGHELP.DLL");
if (!hndDBGHelpDLL)
{
CryLogAlways("Failed to record DMP file: Could not open DBGHELP.DLL");
return EXCEPTION_CONTINUE_SEARCH;
}
MINIDUMPWRITEDUMP dumpFnPtr = (MINIDUMPWRITEDUMP)::GetProcAddress(hndDBGHelpDLL, "MiniDumpWriteDump");
if (!dumpFnPtr)
{
CryLogAlways("Failed to record DMP file: Unable to find MiniDumpWriteDump in DBGHELP.DLL");
return EXCEPTION_CONTINUE_SEARCH;
}
HANDLE hFile = ::CreateFile(szDumpPath, GENERIC_WRITE, FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE)
{
CryLogAlways("Failed to record DMP file: could not open file '%s' for writing - error code: %d", szDumpPath, GetLastError());
return EXCEPTION_CONTINUE_SEARCH;
}
_MINIDUMP_EXCEPTION_INFORMATION ExInfo;
ExInfo.ThreadId = ::GetCurrentThreadId();
ExInfo.ExceptionPointers = pExceptionPointers;
ExInfo.ClientPointers = NULL;
BOOL bOK = dumpFnPtr(GetCurrentProcess(), GetCurrentProcessId(), hFile, DumpType, &ExInfo, NULL, NULL);
::CloseHandle(hFile);
if (bOK)
{
CryLogAlways("Successfully recorded DMP file: '%s'", szDumpPath);
return EXCEPTION_EXECUTE_HANDLER; // SUCCESS! you can execute your handlers now
}
else
{
CryLogAlways("Failed to record DMP file: '%s' - error code: %d", szDumpPath, GetLastError());
}
return EXCEPTION_CONTINUE_SEARCH;
}
//////////////////////////////////////////////////////////////////////////
LONG WINAPI CryEngineExceptionFilterWER(struct _EXCEPTION_POINTERS* pExceptionPointers)
{
if (g_cvars.sys_WER > 1)
{
char szScratch [_MAX_PATH];
const char* szDumpPath = gEnv->pCryPak->AdjustFileName("@log@/CE2Dump.dmp", szScratch, AZ_ARRAY_SIZE(szScratch), 0);
MINIDUMP_TYPE mdumpValue = (MINIDUMP_TYPE)(MiniDumpNormal);
if (g_cvars.sys_WER > 1)
{
mdumpValue = (MINIDUMP_TYPE)(g_cvars.sys_WER - 2);
}
return CryEngineExceptionFilterMiniDump(pExceptionPointers, szDumpPath, mdumpValue);
}
LONG lRet = EXCEPTION_CONTINUE_SEARCH;
WCHAR* psz = GetFullPathToFaultrepDll();
if (psz)
{
HMODULE hFaultRepDll = LoadLibraryW(psz);
if (hFaultRepDll)
{
pfn_REPORTFAULT pfn = (pfn_REPORTFAULT)GetProcAddress(hFaultRepDll, "ReportFault");
if (pfn)
{
pfn(pExceptionPointers, 0);
lRet = EXCEPTION_EXECUTE_HANDLER;
}
FreeLibrary(hFaultRepDll);
}
}
return lRet;
}
#endif // WIN32

@ -15,6 +15,8 @@ set(FILES
CmdLineArg.cpp
ConsoleBatchFile.cpp
ConsoleHelpGen.cpp
DebugCallStack.cpp
IDebugCallStack.cpp
Log.cpp
System.cpp
SystemCFG.cpp
@ -31,6 +33,8 @@ set(FILES
CmdLineArg.h
ConsoleBatchFile.h
ConsoleHelpGen.h
DebugCallStack.h
IDebugCallStack.h
Log.h
SimpleStringPool.h
CrySystem_precompiled.h
@ -72,4 +76,5 @@ set(FILES
ViewSystem/ViewSystem.cpp
ViewSystem/ViewSystem.h
CrySystem_precompiled.cpp
WindowsErrorReporting.cpp
)

@ -307,6 +307,8 @@ namespace AZ
Asset(AssetLoadBehavior loadBehavior = AssetLoadBehavior::Default);
/// Create an asset from a valid asset data (created asset), might not be loaded or currently loading.
Asset(AssetData* assetData, AssetLoadBehavior loadBehavior);
/// Create an asset from a valid asset data (created asset) and set the asset id for both, might not be loaded or currently loading.
Asset(const AZ::Data::AssetId& id, AssetData* assetData, AssetLoadBehavior loadBehavior);
/// Initialize asset pointer with id, type, and hint. No data construction will occur until QueueLoad is called.
Asset(const AZ::Data::AssetId& id, const AZ::Data::AssetType& type, const AZStd::string& hint = AZStd::string());
@ -787,6 +789,18 @@ namespace AZ
SetData(assetData);
}
//=========================================================================
template<class T>
Asset<T>::Asset(const AssetId& id, AssetData* assetData, AssetLoadBehavior loadBehavior)
: m_assetId(id)
, m_assetType(azrtti_typeid<T>())
, m_loadBehavior(loadBehavior)
{
AZ_Assert(!assetData->m_assetId.IsValid(), "Asset data already has an ID set.");
assetData->m_assetId = id;
SetData(assetData);
}
//=========================================================================
template<class T>
Asset<T>::Asset(const AssetId& id, const AZ::Data::AssetType& type, const AZStd::string& hint)

@ -21,7 +21,7 @@ namespace AzPhysics
namespace
{
const float TimestepMin = 0.001f; //1000fps
const float TimestepMax = 0.05f; //20fps
const float TimestepMax = 0.1f; //10fps
}
AZ_CLASS_ALLOCATOR_IMPL(SystemConfiguration, AZ::SystemAllocator, 0);

@ -34,7 +34,7 @@ namespace AzPhysics
static constexpr float DefaultFixedTimestep = 0.0166667f; //! Value represents 1/60th or 60 FPS.
float m_maxTimestep = 1.f / 20.f; //!< Maximum fixed timestep in seconds to run the physics update.
float m_maxTimestep = 0.1f; //!< Maximum fixed timestep in seconds to run the physics update (10FPS).
float m_fixedTimestep = DefaultFixedTimestep; //!< Timestep in seconds to run the physics update. See DefaultFixedTimestep.
AZ::u64 m_raycastBufferSize = 32; //!< Maximum number of hits that will be returned from a raycast.

@ -0,0 +1,78 @@
/*
* 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/std/string/string.h>
namespace AzFramework
{
//! SessionConnectionConfig
//! The properties for handling join session request.
struct SessionConnectionConfig
{
// A unique identifier for registered player in session.
AZStd::string m_playerSessionId;
// The DNS identifier assigned to the instance that is running the session.
AZStd::string m_dnsName;
// The IP address of the session.
AZStd::string m_ipAddress;
// The port number for the session.
uint16_t m_port;
};
//! SessionConnectionConfig
//! The properties for handling player connect/disconnect
struct PlayerConnectionConfig
{
// A unique identifier for player connection.
uint32_t m_playerConnectionId;
// A unique identifier for registered player in session.
AZStd::string m_playerSessionId;
};
//! ISessionHandlingClientRequests
//! The session handling events to invoke multiplayer component handle the work on client side
class ISessionHandlingClientRequests
{
public:
// Handle the player join session process
// @param sessionConnectionConfig The required properties to handle the player join session process
// @return The result of player join session process
virtual bool HandlePlayerJoinSession(const SessionConnectionConfig& sessionConnectionConfig) = 0;
// Handle the player leave session process
virtual void HandlePlayerLeaveSession() = 0;
};
//! ISessionHandlingServerRequests
//! The session handling events to invoke server provider handle the work on server side
class ISessionHandlingServerRequests
{
public:
// Handle the destroy session process
virtual void HandleDestroySession() = 0;
// Validate the player join session process
// @param playerConnectionConfig The required properties to validate the player join session process
// @return The result of player join session validation
virtual bool ValidatePlayerJoinSession(const PlayerConnectionConfig& playerConnectionConfig) = 0;
// Handle the player leave session process
// @param playerConnectionConfig The required properties to handle the player leave session process
virtual void HandlePlayerLeaveSession(const PlayerConnectionConfig& playerConnectionConfig) = 0;
};
} // namespace AzFramework

@ -0,0 +1,130 @@
/*
* 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/Serialization/EditContext.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzFramework/Session/ISessionRequests.h>
#include <AzFramework/Session/SessionConfig.h>
namespace AzFramework
{
void CreateSessionRequest::Reflect(AZ::ReflectContext* context)
{
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<CreateSessionRequest>()
->Version(0)
->Field("creatorId", &CreateSessionRequest::m_creatorId)
->Field("sessionProperties", &CreateSessionRequest::m_sessionProperties)
->Field("sessionName", &CreateSessionRequest::m_sessionName)
->Field("maxPlayer", &CreateSessionRequest::m_maxPlayer)
;
if (AZ::EditContext* editContext = serializeContext->GetEditContext())
{
editContext->Class<CreateSessionRequest>("CreateSessionRequest", "The container for CreateSession request parameters")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly)
->DataElement(AZ::Edit::UIHandlers::Default, &CreateSessionRequest::m_creatorId,
"CreatorId", "A unique identifier for a player or entity creating the session")
->DataElement(AZ::Edit::UIHandlers::Default, &CreateSessionRequest::m_sessionProperties,
"SessionProperties", "A collection of custom properties for a session")
->DataElement(AZ::Edit::UIHandlers::Default, &CreateSessionRequest::m_sessionName,
"SessionName", "A descriptive label that is associated with a session")
->DataElement(AZ::Edit::UIHandlers::Default, &CreateSessionRequest::m_maxPlayer,
"MaxPlayer", "The maximum number of players that can be connected simultaneously to the session")
;
}
}
}
void SearchSessionsRequest::Reflect(AZ::ReflectContext* context)
{
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<SearchSessionsRequest>()
->Version(0)
->Field("filterExpression", &SearchSessionsRequest::m_filterExpression)
->Field("sortExpression", &SearchSessionsRequest::m_sortExpression)
->Field("maxResult", &SearchSessionsRequest::m_maxResult)
->Field("nextToken", &SearchSessionsRequest::m_nextToken)
;
if (AZ::EditContext* editContext = serializeContext->GetEditContext())
{
editContext->Class<SearchSessionsRequest>("SearchSessionsRequest", "The container for SearchSessions request parameters")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly)
->DataElement(AZ::Edit::UIHandlers::Default, &SearchSessionsRequest::m_filterExpression,
"FilterExpression", "String containing the search criteria for the session search")
->DataElement(AZ::Edit::UIHandlers::Default, &SearchSessionsRequest::m_sortExpression,
"SortExpression", "Instructions on how to sort the search results")
->DataElement(AZ::Edit::UIHandlers::Default, &SearchSessionsRequest::m_maxResult,
"MaxResult", "The maximum number of results to return")
->DataElement(AZ::Edit::UIHandlers::Default, &SearchSessionsRequest::m_nextToken,
"NextToken", "A token that indicates the start of the next sequential page of results")
;
}
}
}
void SearchSessionsResponse::Reflect(AZ::ReflectContext* context)
{
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<SearchSessionsResponse>()
->Version(0)
->Field("sessionConfigs", &SearchSessionsResponse::m_sessionConfigs)
->Field("nextToken", &SearchSessionsResponse::m_nextToken)
;
if (AZ::EditContext* editContext = serializeContext->GetEditContext())
{
editContext->Class<SearchSessionsResponse>("SearchSessionsResponse", "The container for SearchSession request results")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly)
->DataElement(AZ::Edit::UIHandlers::Default, &SearchSessionsResponse::m_sessionConfigs,
"SessionConfigs", "A collection of sessions that match the search criteria and sorted in specific order")
->DataElement(AZ::Edit::UIHandlers::Default, &SearchSessionsResponse::m_nextToken,
"NextToken", "A token that indicates the start of the next sequential page of results")
;
}
}
}
void JoinSessionRequest::Reflect(AZ::ReflectContext* context)
{
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<JoinSessionRequest>()
->Version(0)
->Field("sessionId", &JoinSessionRequest::m_sessionId)
->Field("playerId", &JoinSessionRequest::m_playerId)
->Field("playerData", &JoinSessionRequest::m_playerData)
;
if (AZ::EditContext* editContext = serializeContext->GetEditContext())
{
editContext->Class<JoinSessionRequest>("JoinSessionRequest", "The container for JoinSession request parameters")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly)
->DataElement(AZ::Edit::UIHandlers::Default, &JoinSessionRequest::m_sessionId,
"SessionId", "A unique identifier for the session")
->DataElement(AZ::Edit::UIHandlers::Default, &JoinSessionRequest::m_playerId,
"PlayerId", "A unique identifier for a player. Player IDs are developer-defined")
->DataElement(AZ::Edit::UIHandlers::Default, &JoinSessionRequest::m_playerData,
"PlayerData", "Developer-defined information related to a player")
;
}
}
}
} // namespace AzFramework

@ -0,0 +1,192 @@
/*
* 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/EBus/EBus.h>
#include <AzCore/RTTI/ReflectContext.h>
#include <AzCore/std/containers/unordered_map.h>
#include <AzCore/std/string/string.h>
#include <AzCore/Outcome/Outcome.h>
namespace AzFramework
{
struct SessionConfig;
//! CreateSessionRequest
//! The container for CreateSession request parameters.
struct CreateSessionRequest
{
AZ_RTTI(CreateSessionRequest, "{E39C2A45-89C9-4CFB-B337-9734DC798930}");
static void Reflect(AZ::ReflectContext* context);
CreateSessionRequest() = default;
virtual ~CreateSessionRequest() = default;
// A unique identifier for a player or entity creating the session.
AZStd::string m_creatorId;
// A collection of custom properties for a session.
AZStd::unordered_map<AZStd::string, AZStd::string> m_sessionProperties;
// A descriptive label that is associated with a session.
AZStd::string m_sessionName;
// The maximum number of players that can be connected simultaneously to the session.
uint64_t m_maxPlayer;
};
//! SearchSessionsRequest
//! The container for SearchSessions request parameters.
struct SearchSessionsRequest
{
AZ_RTTI(SearchSessionsRequest, "{B49207A8-8549-4ADB-B7D9-D7A4932F9B4B}");
static void Reflect(AZ::ReflectContext* context);
SearchSessionsRequest() = default;
virtual ~SearchSessionsRequest() = default;
// String containing the search criteria for the session search. If no filter expression is included, the request returns results
// for all active sessions.
AZStd::string m_filterExpression;
// Instructions on how to sort the search results. If no sort expression is included, the request returns results in random order.
AZStd::string m_sortExpression;
// The maximum number of results to return.
uint8_t m_maxResult;
// A token that indicates the start of the next sequential page of results.
AZStd::string m_nextToken;
};
//! SearchSessionsResponse
//! The container for SearchSession request results.
struct SearchSessionsResponse
{
AZ_RTTI(SearchSessionsResponse, "{F93DE7DC-D381-4E08-8A3B-0B08F7C38714}");
static void Reflect(AZ::ReflectContext* context);
SearchSessionsResponse() = default;
virtual ~SearchSessionsResponse() = default;
// A collection of sessions that match the search criteria and sorted in specific order.
AZStd::vector<SessionConfig> m_sessionConfigs;
// A token that indicates the start of the next sequential page of results.
AZStd::string m_nextToken;
};
//! JoinSessionRequest
//! The container for JoinSession request parameters.
struct JoinSessionRequest
{
AZ_RTTI(JoinSessionRequest, "{519769E8-3CDE-4385-A0D7-24DBB3685657}");
static void Reflect(AZ::ReflectContext* context);
JoinSessionRequest() = default;
virtual ~JoinSessionRequest() = default;
// A unique identifier for the session.
AZStd::string m_sessionId;
// A unique identifier for a player. Player IDs are developer-defined.
AZStd::string m_playerId;
// Developer-defined information related to a player.
AZStd::string m_playerData;
};
//! ISessionRequests
//! Pure virtual session interface class to abstract the details of session handling from application code.
class ISessionRequests
{
public:
AZ_RTTI(ISessionRequests, "{D6C41A71-DD8D-47FE-8515-FAF90670AE2F}");
ISessionRequests() = default;
virtual ~ISessionRequests() = default;
// Create a session for players to find and join.
// @param createSessionRequest The request of CreateSession operation
// @return The request id if session creation request succeeds; empty if it fails
virtual AZStd::string CreateSession(const CreateSessionRequest& createSessionRequest) = 0;
// Retrieve all active sessions that match the given search criteria and sorted in specific order.
// @param searchSessionsRequest The request of SearchSessions operation
// @return The response of SearchSessions operation
virtual SearchSessionsResponse SearchSessions(const SearchSessionsRequest& searchSessionsRequest) const = 0;
// Reserve an open player slot in a session, and perform connection from client to server.
// @param joinSessionRequest The request of JoinSession operation
// @return True if joining session succeeds; False otherwise
virtual bool JoinSession(const JoinSessionRequest& joinSessionRequest) = 0;
// Disconnect player from session.
virtual void LeaveSession() = 0;
};
//! ISessionAsyncRequests
//! Async version of ISessionRequests
class ISessionAsyncRequests
{
public:
AZ_RTTI(ISessionAsyncRequests, "{471542AF-96B9-4930-82FE-242A4E68432D}");
ISessionAsyncRequests() = default;
virtual ~ISessionAsyncRequests() = default;
// CreateSession Async
// @param createSessionRequest The request of CreateSession operation
virtual void CreateSessionAsync(const CreateSessionRequest& createSessionRequest) = 0;
// SearchSessions Async
// @param searchSessionsRequest The request of SearchSessions operation
virtual void SearchSessionsAsync(const SearchSessionsRequest& searchSessionsRequest) const = 0;
// JoinSession Async
// @param joinSessionRequest The request of JoinSession operation
virtual void JoinSessionAsync(const JoinSessionRequest& joinSessionRequest) = 0;
// LeaveSession Async
virtual void LeaveSessionAsync() = 0;
};
//! SessionAsyncRequestNotifications
//! The notifications correspond to session async requests
class SessionAsyncRequestNotifications
: public AZ::EBusTraits
{
public:
//////////////////////////////////////////////////////////////////////////
// EBusTraits overrides
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple;
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
//////////////////////////////////////////////////////////////////////////
// OnCreateSessionAsyncComplete is fired once CreateSessionAsync completes
// @param createSessionResponse The request id if session creation request succeeds; empty if it fails
virtual void OnCreateSessionAsyncComplete(const AZStd::string& createSessionReponse) = 0;
// OnSearchSessionsAsyncComplete is fired once SearchSessionsAsync completes
// @param searchSessionsResponse The response of SearchSessions call
virtual void OnSearchSessionsAsyncComplete(const SearchSessionsResponse& searchSessionsResponse) = 0;
// OnJoinSessionAsyncComplete is fired once JoinSessionAsync completes
// @param joinSessionsResponse True if joining session succeeds; False otherwise
virtual void OnJoinSessionAsyncComplete(bool joinSessionsResponse) = 0;
// OnLeaveSessionAsyncComplete is fired once LeaveSessionAsync completes
virtual void OnLeaveSessionAsyncComplete() = 0;
};
using SessionAsyncRequestNotificationBus = AZ::EBus<SessionAsyncRequestNotifications>;
} // namespace AzFramework

@ -0,0 +1,74 @@
/*
* 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/Serialization/EditContext.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzFramework/Session/SessionConfig.h>
namespace AzFramework
{
void SessionConfig::Reflect(AZ::ReflectContext* context)
{
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<SessionConfig>()
->Version(0)
->Field("creationTime", &SessionConfig::m_creationTime)
->Field("terminationTime", &SessionConfig::m_terminationTime)
->Field("creatorId", &SessionConfig::m_creatorId)
->Field("sessionProperties", &SessionConfig::m_sessionProperties)
->Field("sessionId", &SessionConfig::m_sessionId)
->Field("sessionName", &SessionConfig::m_sessionName)
->Field("dnsName", &SessionConfig::m_dnsName)
->Field("ipAddress", &SessionConfig::m_ipAddress)
->Field("port", &SessionConfig::m_port)
->Field("maxPlayer", &SessionConfig::m_maxPlayer)
->Field("currentPlayer", &SessionConfig::m_currentPlayer)
->Field("status", &SessionConfig::m_status)
->Field("statusReason", &SessionConfig::m_statusReason)
;
if (AZ::EditContext* editContext = serializeContext->GetEditContext())
{
editContext->Class<SessionConfig>("SessionConfig", "Properties describing a session")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly)
->DataElement(AZ::Edit::UIHandlers::Default, &AzFramework::SessionConfig::m_creationTime,
"CreationTime", "A time stamp indicating when this session was created. Format is a number expressed in Unix time as milliseconds.")
->DataElement(AZ::Edit::UIHandlers::Default, &AzFramework::SessionConfig::m_terminationTime,
"TerminationTime", "A time stamp indicating when this data object was terminated. Same format as creation time.")
->DataElement(AZ::Edit::UIHandlers::Default, &AzFramework::SessionConfig::m_creatorId,
"CreatorId", "A unique identifier for a player or entity creating the session.")
->DataElement(AZ::Edit::UIHandlers::Default, &AzFramework::SessionConfig::m_sessionProperties,
"SessionProperties", "A collection of custom properties for a session.")
->DataElement(AZ::Edit::UIHandlers::Default, &AzFramework::SessionConfig::m_sessionId,
"SessionId", "A unique identifier for the session.")
->DataElement(AZ::Edit::UIHandlers::Default, &AzFramework::SessionConfig::m_sessionName,
"SessionName", "A descriptive label that is associated with a session.")
->DataElement(AZ::Edit::UIHandlers::Default, &AzFramework::SessionConfig::m_dnsName,
"DnsName", "The DNS identifier assigned to the instance that is running the session.")
->DataElement(AZ::Edit::UIHandlers::Default, &AzFramework::SessionConfig::m_ipAddress,
"IpAddress", "The IP address of the session.")
->DataElement(AZ::Edit::UIHandlers::Default, &AzFramework::SessionConfig::m_port,
"Port", "The port number for the session.")
->DataElement(AZ::Edit::UIHandlers::Default, &AzFramework::SessionConfig::m_maxPlayer,
"MaxPlayer", "The maximum number of players that can be connected simultaneously to the session.")
->DataElement(AZ::Edit::UIHandlers::Default, &AzFramework::SessionConfig::m_currentPlayer,
"CurrentPlayer", "Number of players currently in the session.")
->DataElement(AZ::Edit::UIHandlers::Default, &AzFramework::SessionConfig::m_status,
"Status", "Current status of the session.")
->DataElement(AZ::Edit::UIHandlers::Default, &AzFramework::SessionConfig::m_statusReason,
"StatusReason", "Provides additional information about session status.");
}
}
}
} // namespace AzFramework

@ -0,0 +1,70 @@
/*
* 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/RTTI/ReflectContext.h>
#include <AzCore/std/containers/unordered_map.h>
#include <AzCore/std/string/string.h>
namespace AzFramework
{
//! SessionConfig
//! Properties describing a session.
struct SessionConfig
{
AZ_RTTI(SessionConfig, "{992DD4BE-8BA5-4071-8818-B99FD2952086}");
static void Reflect(AZ::ReflectContext* context);
SessionConfig() = default;
virtual ~SessionConfig() = default;
// A time stamp indicating when this session was created. Format is a number expressed in Unix time as milliseconds.
uint64_t m_creationTime;
// A time stamp indicating when this data object was terminated. Same format as creation time.
uint64_t m_terminationTime;
// A unique identifier for a player or entity creating the session.
AZStd::string m_creatorId;
// A collection of custom properties for a session.
AZStd::unordered_map<AZStd::string, AZStd::string> m_sessionProperties;
// A unique identifier for the session.
AZStd::string m_sessionId;
// A descriptive label that is associated with a session.
AZStd::string m_sessionName;
// The DNS identifier assigned to the instance that is running the session.
AZStd::string m_dnsName;
// The IP address of the session.
AZStd::string m_ipAddress;
// The port number for the session.
uint16_t m_port;
// The maximum number of players that can be connected simultaneously to the session.
uint64_t m_maxPlayer;
// Number of players currently in the session.
uint64_t m_currentPlayer;
// Current status of the session.
AZStd::string m_status;
// Provides additional information about session status.
AZStd::string m_statusReason;
};
} // namespace AzFramework

@ -0,0 +1,47 @@
/*
* 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/EBus/EBus.h>
namespace AzFramework
{
struct SessionConfig;
//! SessionNotifications
//! The session notifications to listen for performing required operations
class SessionNotifications
: public AZ::EBusTraits
{
public:
//////////////////////////////////////////////////////////////////////////
// EBusTraits overrides
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple;
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
//////////////////////////////////////////////////////////////////////////
// OnSessionHealthCheck is fired in health check process
// @return The result of all OnSessionHealthCheck
virtual bool OnSessionHealthCheck() = 0;
// OnCreateSessionBegin is fired at the beginning of session creation
// @param sessionConfig The properties to describe a session
// @return The result of all OnCreateSessionBegin notifications
virtual bool OnCreateSessionBegin(const SessionConfig& sessionConfig) = 0;
// OnDestroySessionBegin is fired at the beginning of session termination
// @return The result of all OnDestroySessionBegin notifications
virtual bool OnDestroySessionBegin() = 0;
};
using SessionNotificationBus = AZ::EBus<SessionNotifications>;
} // namespace AzFramework

@ -11,6 +11,7 @@
*/
#include <AzCore/Component/ComponentApplicationBus.h>
#include <AzCore/Serialization/IdUtils.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/std/parallel/scoped_lock.h>
#include <AzCore/std/smart_ptr/make_shared.h>
@ -215,6 +216,13 @@ namespace AzFramework
return clone;
}
AZ::Entity* SpawnableEntitiesManager::CloneSingleEntity(const AZ::Entity& entityTemplate,
EntityIdMap& templateToCloneEntityIdMap, AZ::SerializeContext& serializeContext)
{
return AZ::IdUtils::Remapper<AZ::EntityId>::CloneObjectAndGenerateNewIdsAndFixRefs(
&entityTemplate, templateToCloneEntityIdMap, &serializeContext);
}
bool SpawnableEntitiesManager::ProcessRequest(SpawnAllEntitiesCommand& request, AZ::SerializeContext& serializeContext)
{
Ticket& ticket = GetTicketPayload<Ticket>(*request.m_ticket);
@ -230,48 +238,37 @@ namespace AzFramework
const Spawnable::EntityList& entitiesToSpawn = ticket.m_spawnable->GetEntities();
size_t entitiesToSpawnSize = entitiesToSpawn.size();
// Map keeps track of ids from template (spawnable) to clone (instance)
// Allowing patch ups of fields referring to entityIds outside of a given entity
EntityIdMap templateToCloneEntityIdMap;
// Reserve buffers
spawnedEntities.reserve(spawnedEntities.size() + entitiesToSpawnSize);
ticket.m_spawnedEntityIndices.reserve(ticket.m_spawnedEntityIndices.size() + entitiesToSpawnSize);
spawnedEntityIndices.reserve(spawnedEntityIndices.size() + entitiesToSpawnSize);
templateToCloneEntityIdMap.reserve(entitiesToSpawnSize);
// TEMP: To be replaced by IdUtils::Remapper
using EntityIdMap = AZStd::unordered_map<AZ::EntityId, AZ::EntityId>;
EntityIdMap templateToCloneIdMap;
// \TEMP
// Clone the entities from Spawnable
// Mark all indices as spawned
for (size_t i = 0; i < entitiesToSpawnSize; ++i)
{
const AZ::Entity& entityTemplate = *entitiesToSpawn[i];
AZ::Entity* clone = serializeContext.CloneObject(&entityTemplate);
AZ::Entity* clone = CloneSingleEntity(entityTemplate, templateToCloneEntityIdMap, serializeContext);
AZ_Assert(clone != nullptr, "Failed to clone spawnable entity.");
clone->SetId(AZ::Entity::MakeId());
spawnedEntities.push_back(clone);
spawnedEntities.emplace_back(clone);
spawnedEntityIndices.push_back(i);
}
// TEMP: To be replaced by IdUtils::Remapper
templateToCloneIdMap[entityTemplate.GetId()] = clone->GetId();
// Update TransformComponent parent Id. It is guaranteed for the entities array to be sorted from parent->child here.
auto* transformComponent = clone->FindComponent<AzFramework::TransformComponent>();
AZ::EntityId parentId = transformComponent->GetParentId();
if (parentId.IsValid())
{
auto it = templateToCloneIdMap.find(parentId);
if (it != templateToCloneIdMap.end())
{
transformComponent->SetParentRelative(it->second);
}
else
{
AZ_Warning(
"SpawnableEntitiesManager", false, "Entity %s doesn't have the parent entity %s present in the spawnable",
clone->GetName().c_str(), parentId.ToString().data());
}
}
// \TEMP
// loadAll is true if every entity has been spawned only once
if (spawnedEntities.size() == entitiesToSpawnSize)
{
ticket.m_loadAll = true;
}
else
{
// Case where there were already spawns from a previous request
ticket.m_loadAll = false;
}
// Let other systems know about newly spawned entities for any pre-processing before adding to the scene/game context.
@ -437,10 +434,23 @@ namespace AzFramework
// to load every, simply start over.
ticket.m_spawnedEntityIndices.clear();
size_t entitiesSize = entities.size();
for (size_t i = 0; i < entitiesSize; ++i)
size_t entitiesToSpawnSize = entities.size();
// Map keeps track of ids from template (spawnable) to clone (instance)
// Allowing patch ups of fields referring to entityIds outside of a given entity
EntityIdMap templateToCloneEntityIdMap;
templateToCloneEntityIdMap.reserve(entitiesToSpawnSize);
// Mark all indices as spawned
for (size_t i = 0; i < entitiesToSpawnSize; ++i)
{
ticket.m_spawnedEntities.push_back(SpawnSingleEntity(*entities[i], serializeContext));
const AZ::Entity& entityTemplate = *entities[i];
AZ::Entity* clone = CloneSingleEntity(entityTemplate, templateToCloneEntityIdMap, serializeContext);
AZ_Assert(clone != nullptr, "Failed to clone spawnable entity.");
ticket.m_spawnedEntities.emplace_back(clone);
ticket.m_spawnedEntityIndices.push_back(i);
}
}

@ -29,6 +29,8 @@ namespace AZ
namespace AzFramework
{
using EntityIdMap = AZStd::unordered_map<AZ::EntityId, AZ::EntityId>;
class SpawnableEntitiesManager
: public SpawnableEntitiesInterface::Registrar
{
@ -142,7 +144,11 @@ namespace AzFramework
using Requests = AZStd::variant<SpawnAllEntitiesCommand, SpawnEntitiesCommand, DespawnAllEntitiesCommand, ReloadSpawnableCommand,
ListEntitiesCommand, ClaimEntitiesCommand, BarrierCommand, DestroyTicketCommand>;
AZ::Entity* SpawnSingleEntity(const AZ::Entity& entityTemplate, AZ::SerializeContext& serializeContext);
AZ::Entity* SpawnSingleEntity(const AZ::Entity& entityTemplate,
AZ::SerializeContext& serializeContext);
AZ::Entity* CloneSingleEntity(const AZ::Entity& entityTemplate,
EntityIdMap& templateToCloneEntityIdMap, AZ::SerializeContext& serializeContext);
bool ProcessRequest(SpawnAllEntitiesCommand& request, AZ::SerializeContext& serializeContext);
bool ProcessRequest(SpawnEntitiesCommand& request, AZ::SerializeContext& serializeContext);

@ -188,6 +188,12 @@ set(FILES
Script/ScriptDebugMsgReflection.h
Script/ScriptRemoteDebugging.cpp
Script/ScriptRemoteDebugging.h
Session/ISessionHandlingRequests.h
Session/ISessionRequests.cpp
Session/ISessionRequests.h
Session/SessionConfig.cpp
Session/SessionConfig.h
Session/SessionNotifications.h
StreamingInstall/StreamingInstall.h
StreamingInstall/StreamingInstall.cpp
StreamingInstall/StreamingInstallRequests.h

@ -116,8 +116,8 @@ namespace AzNetworking
int32_t TcpSocket::Receive(uint8_t* outData, uint32_t size) const
{
AZ_Assert(size > 0, "Invalid data size for send");
AZ_Assert(outData != nullptr, "NULL data pointer passed to send");
AZ_Assert(size > 0, "Invalid data size for receive");
AZ_Assert(outData != nullptr, "NULL data pointer passed to receive");
if (!IsOpen())
{
return SocketOpResultErrorNotOpen;
@ -176,7 +176,7 @@ namespace AzNetworking
if (::bind(aznumeric_cast<int32_t>(m_socketFd), (const sockaddr*)&hints, sizeof(hints)) != 0)
{
const int32_t error = GetLastNetworkError();
AZLOG_ERROR("Failed to bind socket (%d:%s)", error, GetNetworkErrorDesc(error));
AZLOG_ERROR("Failed to bind TCP socket to port %u (%d:%s)", uint32_t(port), error, GetNetworkErrorDesc(error));
return false;
}

@ -162,7 +162,7 @@ namespace AzNetworking
const UdpReaderThread::ReceivedPackets* packets = m_readerThread.GetReceivedPackets(m_socket.get());
if (packets == nullptr)
{
AZ_Assert(false, "nullptr was retrieved for the received packet buffer, check that the socket has been registered with the reader thread");
// Socket is not yet registered with the reader thread and is likely still pending, try again later
return;
}

@ -82,7 +82,7 @@ namespace AzNetworking
if (::bind(static_cast<int32_t>(m_socketFd), (const sockaddr *)&hints, sizeof(hints)) != 0)
{
const int32_t error = GetLastNetworkError();
AZLOG_ERROR("Failed to bind socket to port %u (%d:%s)", uint32_t(port), error, GetNetworkErrorDesc(error));
AZLOG_ERROR("Failed to bind UDP socket to port %u (%d:%s)", uint32_t(port), error, GetNetworkErrorDesc(error));
return false;
}
}

@ -12,6 +12,7 @@
#pragma once
#include <AzCore/Asset/AssetCommon.h>
#include <AzCore/Component/Entity.h>
#include <AzCore/IO/GenericStreams.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
@ -46,6 +47,10 @@ namespace AzToolsFramework
virtual Prefab::InstanceOptionalReference GetRootPrefabInstance() = 0;
//! Get all Assets generated by Prefab processing when entering Play-In Editor mode (Ctrl+G)
//! /return The vector of Assets generated by Prefab processing
virtual const AZStd::vector<AZ::Data::Asset<AZ::Data::AssetData>>& GetPlayInEditorAssetData() = 0;
virtual bool LoadFromStream(AZ::IO::GenericStream& stream, AZStd::string_view filename) = 0;
virtual bool SaveToStream(AZ::IO::GenericStream& stream, AZStd::string_view filename) = 0;

@ -314,6 +314,11 @@ namespace AzToolsFramework
return *m_rootInstance;
}
const AZStd::vector<AZ::Data::Asset<AZ::Data::AssetData>>& PrefabEditorEntityOwnershipService::GetPlayInEditorAssetData()
{
return m_playInEditorData.m_assets;
}
void PrefabEditorEntityOwnershipService::OnEntityRemoved(AZ::EntityId entityId)
{
AzFramework::SliceEntityRequestBus::MultiHandler::BusDisconnect(entityId);

@ -195,6 +195,8 @@ namespace AzToolsFramework
AZ::IO::PathView filePath, Prefab::InstanceOptionalReference instanceToParentUnder) override;
Prefab::InstanceOptionalReference GetRootPrefabInstance() override;
const AZStd::vector<AZ::Data::Asset<AZ::Data::AssetData>>& GetPlayInEditorAssetData() override;
//////////////////////////////////////////////////////////////////////////
void OnEntityRemoved(AZ::EntityId entityId);

@ -27,6 +27,7 @@ AZ_PUSH_DISABLE_WARNING(4244 4251 4800, "-Wunknown-warning-option") // 4244: con
#include <QtGui/QTextLayout>
#include <QtGui/QPainter>
#include <QMessageBox>
#include <QStylePainter>
AZ_POP_DISABLE_WARNING
static const int LabelColumnStretch = 2;
@ -121,6 +122,19 @@ namespace AzToolsFramework
setLayout(m_mainLayout);
}
void PropertyRowWidget::paintEvent(QPaintEvent* event)
{
QStylePainter p(this);
if (CanBeReordered())
{
const QPen linePen(QColor(0x3B3E3F));
p.setPen(linePen);
int indent = m_treeDepth * m_treeIndentation;
p.drawLine(event->rect().topLeft() + QPoint(indent, 0), event->rect().topRight());
}
}
bool PropertyRowWidget::HasChildWidgetAlready() const
{
return m_childWidget != nullptr;
@ -1661,6 +1675,20 @@ namespace AzToolsFramework
m_nameLabel->setFilter(m_currentFilterString);
}
bool PropertyRowWidget::CanChildrenBeReordered() const
{
return m_containerEditable;
}
bool PropertyRowWidget::CanBeReordered() const
{
if (!m_parentRow)
{
return false;
}
return m_parentRow->CanChildrenBeReordered();
}
}
#include "UI/PropertyEditor/moc_PropertyRowWidget.cpp"

@ -44,6 +44,7 @@ namespace AzToolsFramework
Q_PROPERTY(bool hasChildRows READ HasChildRows);
Q_PROPERTY(bool isTopLevel READ IsTopLevel);
Q_PROPERTY(int getLevel READ GetLevel);
Q_PROPERTY(bool canBeReordered READ CanBeReordered);
Q_PROPERTY(bool appendDefaultLabelToName READ GetAppendDefaultLabelToName WRITE AppendDefaultLabelToName)
public:
AZ_CLASS_ALLOCATOR(PropertyRowWidget, AZ::SystemAllocator, 0)
@ -126,6 +127,7 @@ namespace AzToolsFramework
void SetSelectionEnabled(bool selectionEnabled);
void SetSelected(bool selected);
bool eventFilter(QObject *watched, QEvent *event) override;
void paintEvent(QPaintEvent*) override;
/// Apply tooltip to widget and some of its children.
void SetDescription(const QString& text);
@ -146,6 +148,9 @@ namespace AzToolsFramework
QLabel* GetNameLabel() { return m_nameLabel; }
void SetIndentSize(int w);
void SetAsCustom(bool custom) { m_custom = custom; }
bool CanChildrenBeReordered() const;
bool CanBeReordered() const;
protected:
int CalculateLabelWidth() const;

@ -38,6 +38,11 @@ AzToolsFramework--ComponentPaletteWidget > QTreeView
background-color: #222222;
}
AzToolsFramework--PropertyRowWidget[canBeReordered="true"] QLabel#Name
{
font-weight: bold;
}
/* Style for visualizing property values overridden from their prefab values */
AzToolsFramework--PropertyRowWidget[IsOverridden=true] #Name QLabel,
AzToolsFramework--ComponentEditorHeader #Title[IsOverridden="true"]

@ -101,7 +101,7 @@ struct VSOutput
float2 m_uv[UvSetCount] : UV1;
float2 m_detailUv : UV3;
float4 m_blendMask : UV8;
float4 m_wrinkleBlendFactors : UV8;
};
#include <Atom/Features/Vertex/VertexHelper.azsli>
@ -132,11 +132,11 @@ VSOutput SkinVS(VSInput IN)
if(o_blendMask_isBound)
{
OUT.m_blendMask = IN.m_optional_blendMask;
OUT.m_wrinkleBlendFactors = IN.m_optional_blendMask;
}
else
{
OUT.m_blendMask = float4(0,1,0,0);
OUT.m_wrinkleBlendFactors = float4(0,0,0,0);
}
VertexHelper(IN, OUT, worldPosition, false);
@ -214,7 +214,22 @@ PbrLightingOutput SkinPS_Common(VSOutput IN)
float2 normalUv = IN.m_uv[MaterialSrg::m_normalMapUvIndex];
float detailLayerNormalFactor = MaterialSrg::m_detail_normal_factor * detailLayerBlendFactor;
// ------- Wrinkle Map Setup -------
// Combine the optional per-morph target wrinkle masks
float4 wrinkleBlendFactors = float4(0.0, 0.0, 0.0, 0.0);
for(uint wrinkleMaskIndex = 0; wrinkleMaskIndex < ObjectSrg::m_wrinkle_mask_count; ++wrinkleMaskIndex)
{
wrinkleBlendFactors += ObjectSrg::m_wrinkle_masks[wrinkleMaskIndex].Sample(MaterialSrg::m_sampler, normalUv) * ObjectSrg::GetWrinkleMaskWeight(wrinkleMaskIndex);
}
// If texture based morph target driven masks are being used, use those values instead of the per-vertex colors
if(ObjectSrg::m_wrinkle_mask_count)
{
IN.m_wrinkleBlendFactors = saturate(wrinkleBlendFactors);
}
// Since the wrinkle normal maps should all be in the same tangent space as the main normal map, we should be able to blend the raw normal map
// texture values before doing all the tangent space transforms, so we only have to do the transforms once, for better performance.
@ -223,12 +238,12 @@ PbrLightingOutput SkinPS_Common(VSOutput IN)
{
normalMapSample = SampleNormalXY(MaterialSrg::m_normalMap, MaterialSrg::m_sampler, normalUv, MaterialSrg::m_flipNormalX, MaterialSrg::m_flipNormalY);
}
if(o_wrinkleLayers_enabled && o_blendMask_isBound && o_wrinkleLayers_normal_enabled)
if(o_wrinkleLayers_enabled && o_wrinkleLayers_normal_enabled)
{
normalMapSample = ApplyNormalWrinkleMap(o_wrinkleLayers_normal_useTexture1, normalMapSample, MaterialSrg::m_wrinkle_normal_texture1, MaterialSrg::m_sampler, normalUv, MaterialSrg::m_flipNormalX, MaterialSrg::m_flipNormalY, IN.m_blendMask.r);
normalMapSample = ApplyNormalWrinkleMap(o_wrinkleLayers_normal_useTexture2, normalMapSample, MaterialSrg::m_wrinkle_normal_texture2, MaterialSrg::m_sampler, normalUv, MaterialSrg::m_flipNormalX, MaterialSrg::m_flipNormalY, IN.m_blendMask.g);
normalMapSample = ApplyNormalWrinkleMap(o_wrinkleLayers_normal_useTexture3, normalMapSample, MaterialSrg::m_wrinkle_normal_texture3, MaterialSrg::m_sampler, normalUv, MaterialSrg::m_flipNormalX, MaterialSrg::m_flipNormalY, IN.m_blendMask.b);
normalMapSample = ApplyNormalWrinkleMap(o_wrinkleLayers_normal_useTexture4, normalMapSample, MaterialSrg::m_wrinkle_normal_texture4, MaterialSrg::m_sampler, normalUv, MaterialSrg::m_flipNormalX, MaterialSrg::m_flipNormalY, IN.m_blendMask.a);
normalMapSample = ApplyNormalWrinkleMap(o_wrinkleLayers_normal_useTexture1, normalMapSample, MaterialSrg::m_wrinkle_normal_texture1, MaterialSrg::m_sampler, normalUv, MaterialSrg::m_flipNormalX, MaterialSrg::m_flipNormalY, IN.m_wrinkleBlendFactors.r);
normalMapSample = ApplyNormalWrinkleMap(o_wrinkleLayers_normal_useTexture2, normalMapSample, MaterialSrg::m_wrinkle_normal_texture2, MaterialSrg::m_sampler, normalUv, MaterialSrg::m_flipNormalX, MaterialSrg::m_flipNormalY, IN.m_wrinkleBlendFactors.g);
normalMapSample = ApplyNormalWrinkleMap(o_wrinkleLayers_normal_useTexture3, normalMapSample, MaterialSrg::m_wrinkle_normal_texture3, MaterialSrg::m_sampler, normalUv, MaterialSrg::m_flipNormalX, MaterialSrg::m_flipNormalY, IN.m_wrinkleBlendFactors.b);
normalMapSample = ApplyNormalWrinkleMap(o_wrinkleLayers_normal_useTexture4, normalMapSample, MaterialSrg::m_wrinkle_normal_texture4, MaterialSrg::m_sampler, normalUv, MaterialSrg::m_flipNormalX, MaterialSrg::m_flipNormalY, IN.m_wrinkleBlendFactors.a);
}
if(o_detail_normal_useTexture)
@ -255,7 +270,7 @@ PbrLightingOutput SkinPS_Common(VSOutput IN)
float3 baseColor = GetBaseColorInput(MaterialSrg::m_baseColorMap, MaterialSrg::m_sampler, baseColorUv, MaterialSrg::m_baseColor, o_baseColor_useTexture);
bool useSampledBaseColor = o_baseColor_useTexture;
if(o_wrinkleLayers_enabled && o_blendMask_isBound && o_wrinkleLayers_baseColor_enabled)
if(o_wrinkleLayers_enabled && o_wrinkleLayers_baseColor_enabled)
{
// If any of the wrinkle maps are applied, we will use the Base Color blend settings to apply the MaterialSrg::m_baseColor tint to the wrinkle maps,
// even if the main base color map is not used.
@ -272,10 +287,10 @@ PbrLightingOutput SkinPS_Common(VSOutput IN)
baseColor = float3(1,1,1);
}
baseColor = ApplyBaseColorWrinkleMap(o_wrinkleLayers_baseColor_useTexture1, baseColor, MaterialSrg::m_wrinkle_baseColor_texture1, MaterialSrg::m_sampler, baseColorUv, IN.m_blendMask.r);
baseColor = ApplyBaseColorWrinkleMap(o_wrinkleLayers_baseColor_useTexture2, baseColor, MaterialSrg::m_wrinkle_baseColor_texture2, MaterialSrg::m_sampler, baseColorUv, IN.m_blendMask.g);
baseColor = ApplyBaseColorWrinkleMap(o_wrinkleLayers_baseColor_useTexture3, baseColor, MaterialSrg::m_wrinkle_baseColor_texture3, MaterialSrg::m_sampler, baseColorUv, IN.m_blendMask.b);
baseColor = ApplyBaseColorWrinkleMap(o_wrinkleLayers_baseColor_useTexture4, baseColor, MaterialSrg::m_wrinkle_baseColor_texture4, MaterialSrg::m_sampler, baseColorUv, IN.m_blendMask.a);
baseColor = ApplyBaseColorWrinkleMap(o_wrinkleLayers_baseColor_useTexture1, baseColor, MaterialSrg::m_wrinkle_baseColor_texture1, MaterialSrg::m_sampler, baseColorUv, IN.m_wrinkleBlendFactors.r);
baseColor = ApplyBaseColorWrinkleMap(o_wrinkleLayers_baseColor_useTexture2, baseColor, MaterialSrg::m_wrinkle_baseColor_texture2, MaterialSrg::m_sampler, baseColorUv, IN.m_wrinkleBlendFactors.g);
baseColor = ApplyBaseColorWrinkleMap(o_wrinkleLayers_baseColor_useTexture3, baseColor, MaterialSrg::m_wrinkle_baseColor_texture3, MaterialSrg::m_sampler, baseColorUv, IN.m_wrinkleBlendFactors.b);
baseColor = ApplyBaseColorWrinkleMap(o_wrinkleLayers_baseColor_useTexture4, baseColor, MaterialSrg::m_wrinkle_baseColor_texture4, MaterialSrg::m_sampler, baseColorUv, IN.m_wrinkleBlendFactors.a);
}
@ -283,13 +298,13 @@ PbrLightingOutput SkinPS_Common(VSOutput IN)
baseColor = ApplyTextureOverlay(o_detail_baseColor_useTexture, baseColor, MaterialSrg::m_detail_baseColor_texture, MaterialSrg::m_sampler, IN.m_detailUv, detailLayerBaseColorFactor);
if(o_wrinkleLayers_enabled && o_wrinkleLayers_showBlendMaskValues && o_blendMask_isBound)
if(o_wrinkleLayers_enabled && o_wrinkleLayers_showBlendMaskValues)
{
// Overlay debug colors to highlight the different blend weights coming from the vertex color stream.
if(o_wrinkleLayers_count > 0) { baseColor = lerp(baseColor, float3(1,0,0), IN.m_blendMask.r); }
if(o_wrinkleLayers_count > 1) { baseColor = lerp(baseColor, float3(0,1,0), IN.m_blendMask.g); }
if(o_wrinkleLayers_count > 2) { baseColor = lerp(baseColor, float3(0,0,1), IN.m_blendMask.b); }
if(o_wrinkleLayers_count > 3) { baseColor = lerp(baseColor, float3(1,1,1), IN.m_blendMask.a); }
if(o_wrinkleLayers_count > 0) { baseColor = lerp(baseColor, float3(1,0,0), IN.m_wrinkleBlendFactors.r); }
if(o_wrinkleLayers_count > 1) { baseColor = lerp(baseColor, float3(0,1,0), IN.m_wrinkleBlendFactors.g); }
if(o_wrinkleLayers_count > 2) { baseColor = lerp(baseColor, float3(0,0,1), IN.m_wrinkleBlendFactors.b); }
if(o_wrinkleLayers_count > 3) { baseColor = lerp(baseColor, float3(1,1,1), IN.m_wrinkleBlendFactors.a); }
}
// ------- Specular -------

@ -987,15 +987,6 @@
{
"file": "Shaders/MotionVector/SkinnedMeshMotionVector.shader",
"tag": "SkinnedMeshMotionVector"
},
// Used by the light culling system to produce accurate depth bounds for this object when it uses blended transparency
{
"file": "Shaders/Depth/DepthPassTransparentMin.shader",
"tag": "DepthPassTransparentMin"
},
{
"file": "Shaders/Depth/DepthPassTransparentMax.shader",
"tag": "DepthPassTransparentMax"
}
],
"functors": [

@ -1199,6 +1199,14 @@
"file": "./StandardPBR_ForwardPass_EDS.shader",
"tag": "ForwardPass_EDS"
},
{
"file": "./StandardPBR_LowEndForward.shader",
"tag": "LowEndForward"
},
{
"file": "./StandardPBR_LowEndForward_EDS.shader",
"tag": "LowEndForward_EDS"
},
{
"file": "Shaders/Shadow/Shadowmap.shader",
"tag": "Shadowmap"
@ -1289,10 +1297,6 @@
"textureProperty": "baseColor.textureMap",
"useTextureProperty": "baseColor.useTexture",
"dependentProperties": ["baseColor.textureMapUv", "baseColor.textureBlendMode"],
"shaderTags": [
"ForwardPass",
"ForwardPass_EDS"
],
"shaderOption": "o_baseColor_useTexture"
}
},
@ -1302,10 +1306,6 @@
"textureProperty": "metallic.textureMap",
"useTextureProperty": "metallic.useTexture",
"dependentProperties": ["metallic.textureMapUv"],
"shaderTags": [
"ForwardPass",
"ForwardPass_EDS"
],
"shaderOption": "o_metallic_useTexture"
}
},
@ -1315,10 +1315,6 @@
"textureProperty": "specularF0.textureMap",
"useTextureProperty": "specularF0.useTexture",
"dependentProperties": ["specularF0.textureMapUv"],
"shaderTags": [
"ForwardPass",
"ForwardPass_EDS"
],
"shaderOption": "o_specularF0_useTexture"
}
},
@ -1328,10 +1324,6 @@
"textureProperty": "normal.textureMap",
"useTextureProperty": "normal.useTexture",
"dependentProperties": ["normal.textureMapUv", "normal.factor", "normal.flipX", "normal.flipY"],
"shaderTags": [
"ForwardPass",
"ForwardPass_EDS"
],
"shaderOption": "o_normal_useTexture"
}
},

@ -10,6 +10,8 @@
*
*/
#include "Atom/Features/ShaderQualityOptions.azsli"
#include "StandardPBR_Common.azsli"
// SRGs
@ -317,13 +319,18 @@ ForwardPassOutputWithDepth StandardPbr_ForwardPassPS(VSOutput IN, bool isFrontFa
PbrLightingOutput lightingOutput = ForwardPassPS_Common(IN, isFrontFace, depth);
#ifdef UNIFIED_FORWARD_OUTPUT
OUT.m_color.rgb = lightingOutput.m_diffuseColor.rgb + lightingOutput.m_specularColor.rgb;
OUT.m_color.a = lightingOutput.m_diffuseColor.a;
OUT.m_depth = depth;
#else
OUT.m_diffuseColor = lightingOutput.m_diffuseColor;
OUT.m_specularColor = lightingOutput.m_specularColor;
OUT.m_specularF0 = lightingOutput.m_specularF0;
OUT.m_albedo = lightingOutput.m_albedo;
OUT.m_normal = lightingOutput.m_normal;
OUT.m_depth = depth;
#endif
return OUT;
}
@ -335,12 +342,16 @@ ForwardPassOutput StandardPbr_ForwardPassPS_EDS(VSOutput IN, bool isFrontFace :
PbrLightingOutput lightingOutput = ForwardPassPS_Common(IN, isFrontFace, depth);
#ifdef UNIFIED_FORWARD_OUTPUT
OUT.m_color.rgb = lightingOutput.m_diffuseColor.rgb + lightingOutput.m_specularColor.rgb;
OUT.m_color.a = lightingOutput.m_diffuseColor.a;
#else
OUT.m_diffuseColor = lightingOutput.m_diffuseColor;
OUT.m_specularColor = lightingOutput.m_specularColor;
OUT.m_specularF0 = lightingOutput.m_specularF0;
OUT.m_albedo = lightingOutput.m_albedo;
OUT.m_normal = lightingOutput.m_normal;
#endif
return OUT;
}

@ -0,0 +1,17 @@
/*
* 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.
*
*/
// NOTE: This file is a temporary workaround until .shader files can #define macros for their .azsl files
#define QUALITY_LOW_END 1
#include "StandardPBR_ForwardPass.azsl"

@ -0,0 +1,59 @@
{
// Note: "LowEnd" shaders are for supporting the low end pipeline
// These shaders can be safely added to materials without incurring additional runtime draw
// items as draw items for shaders are only created if the scene has a pass with a matching
// DrawListTag. If your pipeline doesn't have a "lowEndForward" DrawListTag, no draw items
// for this shader will be created.
"Source" : "./StandardPBR_LowEndForward.azsl",
"DepthStencilState" :
{
"Depth" :
{
"Enable" : true,
"CompareFunc" : "GreaterEqual"
},
"Stencil" :
{
"Enable" : true,
"ReadMask" : "0x00",
"WriteMask" : "0xFF",
"FrontFace" :
{
"Func" : "Always",
"DepthFailOp" : "Keep",
"FailOp" : "Keep",
"PassOp" : "Replace"
},
"BackFace" :
{
"Func" : "Always",
"DepthFailOp" : "Keep",
"FailOp" : "Keep",
"PassOp" : "Replace"
}
}
},
"CompilerHints" : {
"DisableOptimizations" : false
},
"ProgramSettings":
{
"EntryPoints":
[
{
"name": "StandardPbr_ForwardPassVS",
"type": "Vertex"
},
{
"name": "StandardPbr_ForwardPassPS",
"type": "Fragment"
}
]
},
"DrawList" : "lowEndForward"
}

@ -0,0 +1,59 @@
{
// Note: "LowEnd" shaders are for supporting the low end pipeline
// These shaders can be safely added to materials without incurring additional runtime draw
// items as draw items for shaders are only created if the scene has a pass with a matching
// DrawListTag. If your pipeline doesn't have a "lowEndForward" DrawListTag, no draw items
// for this shader will be created.
"Source" : "./StandardPBR_LowEndForward.azsl",
"DepthStencilState" :
{
"Depth" :
{
"Enable" : true,
"CompareFunc" : "GreaterEqual"
},
"Stencil" :
{
"Enable" : true,
"ReadMask" : "0x00",
"WriteMask" : "0xFF",
"FrontFace" :
{
"Func" : "Always",
"DepthFailOp" : "Keep",
"FailOp" : "Keep",
"PassOp" : "Replace"
},
"BackFace" :
{
"Func" : "Always",
"DepthFailOp" : "Keep",
"FailOp" : "Keep",
"PassOp" : "Replace"
}
}
},
"CompilerHints" : {
"DisableOptimizations" : false
},
"ProgramSettings":
{
"EntryPoints":
[
{
"name": "StandardPbr_ForwardPassVS",
"type": "Vertex"
},
{
"name": "StandardPbr_ForwardPassPS_EDS",
"type": "Fragment"
}
]
},
"DrawList" : "lowEndForward"
}

@ -29,26 +29,33 @@ function Process(context)
local depthPass = context:GetShaderByTag("DepthPass")
local shadowMap = context:GetShaderByTag("Shadowmap")
local forwardPassEDS = context:GetShaderByTag("ForwardPass_EDS")
local lowEndForwardEDS = context:GetShaderByTag("LowEndForward_EDS")
local depthPassWithPS = context:GetShaderByTag("DepthPass_WithPS")
local shadowMapWitPS = context:GetShaderByTag("Shadowmap_WithPS")
local forwardPass = context:GetShaderByTag("ForwardPass")
local lowEndForward = context:GetShaderByTag("LowEndForward")
if parallaxEnabled and parallaxPdoEnabled then
depthPass:SetEnabled(false)
shadowMap:SetEnabled(false)
forwardPassEDS:SetEnabled(false)
lowEndForwardEDS:SetEnabled(false)
depthPassWithPS:SetEnabled(true)
shadowMapWitPS:SetEnabled(true)
forwardPass:SetEnabled(true)
lowEndForward:SetEnabled(true)
else
depthPass:SetEnabled(opacityMode == OpacityMode_Opaque)
shadowMap:SetEnabled(opacityMode == OpacityMode_Opaque)
forwardPassEDS:SetEnabled((opacityMode == OpacityMode_Opaque) or (opacityMode == OpacityMode_Blended) or (opacityMode == OpacityMode_TintedTransparent))
lowEndForwardEDS:SetEnabled((opacityMode == OpacityMode_Opaque) or (opacityMode == OpacityMode_Blended) or (opacityMode == OpacityMode_TintedTransparent))
depthPassWithPS:SetEnabled(opacityMode == OpacityMode_Cutout)
shadowMapWitPS:SetEnabled(opacityMode == OpacityMode_Cutout)
forwardPass:SetEnabled(opacityMode == OpacityMode_Cutout)
lowEndForward:SetEnabled(opacityMode == OpacityMode_Cutout)
end
context:GetShaderByTag("DepthPassTransparentMin"):SetEnabled((opacityMode == OpacityMode_Blended) or (opacityMode == OpacityMode_TintedTransparent))

@ -148,22 +148,6 @@
},
"LoadAction": "Clear"
}
},
{
"Name": "ScatterDistanceOutput",
"SlotType": "Output",
"ScopeAttachmentUsage": "RenderTarget",
"LoadStoreAction": {
"ClearValue": {
"Value": [
0.0,
0.0,
0.0,
0.0
]
},
"LoadAction": "Clear"
}
}
],
"ImageAttachments": [
@ -238,19 +222,6 @@
"AssetRef": {
"FilePath": "Textures/BRDFTexture.attimage"
}
},
{
"Name": "ScatterDistanceImage",
"SizeSource": {
"Source": {
"Pass": "Parent",
"Attachment": "SwapChainOutput"
}
},
"ImageDescriptor": {
"Format": "R11G11B10_FLOAT",
"SharedQueueMask": "Graphics"
}
}
],
"Connections": [
@ -295,13 +266,6 @@
"Pass": "This",
"Attachment": "BRDFTexture"
}
},
{
"LocalSlot": "ScatterDistanceOutput",
"AttachmentRef": {
"Pass": "This",
"Attachment": "ScatterDistanceImage"
}
}
]
}

@ -0,0 +1,146 @@
{
"Type": "JsonSerialization",
"Version": 1,
"ClassName": "PassAsset",
"ClassData": {
"PassTemplate": {
"Name": "LightAdaptationParentTemplate",
"PassClass": "ParentPass",
"Slots": [
// Inputs...
{
"Name": "LightingInput",
"SlotType": "Input"
},
// SwapChain here is only used to reference the frame height and format
{
"Name": "SwapChainOutput",
"SlotType": "InputOutput"
},
// Outputs...
{
"Name": "Output",
"SlotType": "Output"
},
// Debug Outputs...
{
"Name": "LuminanceMipChainOutput",
"SlotType": "Output"
}
],
"Connections": [
{
"LocalSlot": "Output",
"AttachmentRef": {
"Pass": "DisplayMapperPass",
"Attachment": "Output"
}
},
{
"LocalSlot": "LuminanceMipChainOutput",
"AttachmentRef": {
"Pass": "DownsampleLuminanceMipChain",
"Attachment": "MipChainInputOutput"
}
}
],
"PassRequests": [
{
"Name": "DownsampleLuminanceMinAvgMax",
"TemplateName": "DownsampleLuminanceMinAvgMaxCS",
"Connections": [
{
"LocalSlot": "Input",
"AttachmentRef": {
"Pass": "Parent",
"Attachment": "LightingInput"
}
}
]
},
{
"Name": "DownsampleLuminanceMipChain",
"TemplateName": "DownsampleMipChainTemplate",
"Connections": [
{
"LocalSlot": "MipChainInputOutput",
"AttachmentRef": {
"Pass": "DownsampleLuminanceMinAvgMax",
"Attachment": "Output"
}
}
],
"PassData": {
"$type": "DownsampleMipChainPassData",
"ShaderAsset": {
"FilePath": "Shaders/PostProcessing/DownsampleMinAvgMaxCS.shader"
}
}
},
{
"Name": "EyeAdaptationPass",
"TemplateName": "EyeAdaptationTemplate",
"Enabled": false,
"Connections": [
{
"LocalSlot": "SceneLuminanceInput",
"AttachmentRef": {
"Pass": "DownsampleLuminanceMipChain",
"Attachment": "MipChainInputOutput"
}
}
]
},
{
"Name": "LookModificationTransformPass",
"TemplateName": "LookModificationTransformTemplate",
"Enabled": true,
"Connections": [
{
"LocalSlot": "Input",
"AttachmentRef": {
"Pass": "Parent",
"Attachment": "LightingInput"
}
},
{
"LocalSlot": "EyeAdaptationDataInput",
"AttachmentRef": {
"Pass": "EyeAdaptationPass",
"Attachment": "EyeAdaptationDataInputOutput"
}
},
{
"LocalSlot": "SwapChainOutput",
"AttachmentRef": {
"Pass": "Parent",
"Attachment": "SwapChainOutput"
}
}
]
},
{
"Name": "DisplayMapperPass",
"TemplateName": "DisplayMapperTemplate",
"Enabled": true,
"Connections": [
{
"LocalSlot": "Input",
"AttachmentRef": {
"Pass": "LookModificationTransformPass",
"Attachment": "Output"
}
},
{
"LocalSlot": "SwapChainOutput",
"AttachmentRef": {
"Pass": "Parent",
"Attachment": "SwapChainOutput"
}
}
]
}
]
}
}
}

@ -0,0 +1,133 @@
{
"Type": "JsonSerialization",
"Version": 1,
"ClassName": "PassAsset",
"ClassData": {
"PassTemplate": {
"Name": "LowEndForwardPassTemplate",
"PassClass": "RasterPass",
"Slots": [
// Inputs...
{
"Name": "BRDFTextureInput",
"ShaderInputName": "m_brdfMap",
"SlotType": "Input",
"ScopeAttachmentUsage": "Shader"
},
{
"Name": "DirectionalLightShadowmap",
"ShaderInputName": "m_directionalLightShadowmap",
"SlotType": "Input",
"ScopeAttachmentUsage": "Shader",
"ImageViewDesc": {
"IsArray": 1
}
},
{
"Name": "ExponentialShadowmapDirectional",
"ShaderInputName": "m_directionalLightExponentialShadowmap",
"SlotType": "Input",
"ScopeAttachmentUsage": "Shader",
"ImageViewDesc": {
"IsArray": 1
}
},
{
"Name": "ProjectedShadowmap",
"ShaderInputName": "m_projectedShadowmaps",
"SlotType": "Input",
"ScopeAttachmentUsage": "Shader",
"ImageViewDesc": {
"IsArray": 1
}
},
{
"Name": "ExponentialShadowmapProjected",
"ShaderInputName": "m_projectedExponentialShadowmap",
"SlotType": "Input",
"ScopeAttachmentUsage": "Shader",
"ImageViewDesc": {
"IsArray": 1
}
},
{
"Name": "TileLightData",
"SlotType": "Input",
"ShaderInputName": "m_tileLightData",
"ScopeAttachmentUsage": "Shader"
},
{
"Name": "LightListRemapped",
"SlotType": "Input",
"ShaderInputName": "m_lightListRemapped",
"ScopeAttachmentUsage": "Shader"
},
// Input/Outputs...
{
"Name": "DepthStencilInputOutput",
"SlotType": "InputOutput",
"ScopeAttachmentUsage": "DepthStencil"
},
// Outputs...
{
"Name": "LightingOutput",
"SlotType": "Output",
"ScopeAttachmentUsage": "RenderTarget",
"LoadStoreAction": {
"ClearValue": {
"Value": [
0.0,
0.0,
0.0,
0.0
]
},
"LoadAction": "Clear"
}
}
],
"ImageAttachments": [
{
"Name": "LightingAttachment",
"SizeSource": {
"Source": {
"Pass": "Parent",
"Attachment": "SwapChainOutput"
}
},
"MultisampleSource": {
"Pass": "This",
"Attachment": "DepthStencilInputOutput"
},
"ImageDescriptor": {
"Format": "R16G16B16A16_FLOAT",
"SharedQueueMask": "Graphics"
}
},
{
"Name": "BRDFTexture",
"Lifetime": "Imported",
"AssetRef": {
"FilePath": "Textures/BRDFTexture.attimage"
}
}
],
"Connections": [
{
"LocalSlot": "LightingOutput",
"AttachmentRef": {
"Pass": "This",
"Attachment": "LightingAttachment"
}
},
{
"LocalSlot": "BRDFTextureInput",
"AttachmentRef": {
"Pass": "This",
"Attachment": "BRDFTexture"
}
}
]
}
}
}

@ -0,0 +1,344 @@
{
"Type": "JsonSerialization",
"Version": 1,
"ClassName": "PassAsset",
"ClassData": {
"PassTemplate": {
"Name": "LowEndPipelineTemplate",
"PassClass": "ParentPass",
"Slots": [
{
"Name": "SwapChainOutput",
"SlotType": "InputOutput",
"ScopeAttachmentUsage": "RenderTarget"
}
],
"PassRequests": [
{
"Name": "MorphTargetPass",
"TemplateName": "MorphTargetPassTemplate"
},
{
"Name": "SkinningPass",
"TemplateName": "SkinningPassTemplate",
"Connections": [
{
"LocalSlot": "SkinnedMeshOutputStream",
"AttachmentRef": {
"Pass": "MorphTargetPass",
"Attachment": "MorphTargetDeltaOutput"
}
}
]
},
{
"Name": "DepthPrePass",
"TemplateName": "DepthMSAAParentTemplate",
"Connections": [
{
"LocalSlot": "SkinnedMeshes",
"AttachmentRef": {
"Pass": "SkinningPass",
"Attachment": "SkinnedMeshOutputStream"
}
},
{
"LocalSlot": "SwapChainOutput",
"AttachmentRef": {
"Pass": "Parent",
"Attachment": "SwapChainOutput"
}
}
]
},
{
"Name": "LightCullingPass",
"TemplateName": "LightCullingParentTemplate",
"Connections": [
{
"LocalSlot": "SkinnedMeshes",
"AttachmentRef": {
"Pass": "SkinningPass",
"Attachment": "SkinnedMeshOutputStream"
}
},
{
"LocalSlot": "DepthMSAA",
"AttachmentRef": {
"Pass": "DepthPrePass",
"Attachment": "DepthMSAA"
}
},
{
"LocalSlot": "SwapChainOutput",
"AttachmentRef": {
"Pass": "Parent",
"Attachment": "SwapChainOutput"
}
}
]
},
{
"Name": "ShadowPass",
"TemplateName": "ShadowParentTemplate",
"Connections": [
{
"LocalSlot": "SkinnedMeshes",
"AttachmentRef": {
"Pass": "SkinningPass",
"Attachment": "SkinnedMeshOutputStream"
}
},
{
"LocalSlot": "SwapChainOutput",
"AttachmentRef": {
"Pass": "Parent",
"Attachment": "SwapChainOutput"
}
}
]
},
{
"Name": "ForwardPass",
"TemplateName": "LowEndForwardPassTemplate",
"Connections": [
// Inputs...
{
"LocalSlot": "DirectionalLightShadowmap",
"AttachmentRef": {
"Pass": "ShadowPass",
"Attachment": "DirectionalShadowmap"
}
},
{
"LocalSlot": "ExponentialShadowmapDirectional",
"AttachmentRef": {
"Pass": "ShadowPass",
"Attachment": "DirectionalESM"
}
},
{
"LocalSlot": "ProjectedShadowmap",
"AttachmentRef": {
"Pass": "ShadowPass",
"Attachment": "ProjectedShadowmap"
}
},
{
"LocalSlot": "ExponentialShadowmapProjected",
"AttachmentRef": {
"Pass": "ShadowPass",
"Attachment": "ProjectedESM"
}
},
{
"LocalSlot": "TileLightData",
"AttachmentRef": {
"Pass": "LightCullingPass",
"Attachment": "TileLightData"
}
},
{
"LocalSlot": "LightListRemapped",
"AttachmentRef": {
"Pass": "LightCullingPass",
"Attachment": "LightListRemapped"
}
},
// Input/Outputs...
{
"LocalSlot": "DepthStencilInputOutput",
"AttachmentRef": {
"Pass": "DepthPrePass",
"Attachment": "DepthMSAA"
}
}
],
"PassData": {
"$type": "RasterPassData",
"DrawListTag": "lowEndForward",
"PipelineViewTag": "MainCamera",
"PassSrgAsset": {
"FilePath": "shaderlib/atom/features/pbr/forwardpasssrg.azsli:PassSrg"
}
}
},
{
"Name": "SkyBoxPass",
"TemplateName": "SkyBoxTemplate",
"Enabled": true,
"Connections": [
{
"LocalSlot": "SpecularInputOutput",
"AttachmentRef": {
"Pass": "ForwardPass",
"Attachment": "LightingOutput"
}
},
{
"LocalSlot": "SkyBoxDepth",
"AttachmentRef": {
"Pass": "ForwardPass",
"Attachment": "DepthStencilInputOutput"
}
}
]
},
{
"Name": "MSAAResolvePass",
"TemplateName": "MSAAResolveColorTemplate",
"Connections": [
{
"LocalSlot": "Input",
"AttachmentRef": {
"Pass": "SkyBoxPass",
"Attachment": "SpecularInputOutput"
}
}
]
},
{
"Name": "TransparentPass",
"TemplateName": "TransparentParentTemplate",
"Connections": [
{
"LocalSlot": "DirectionalShadowmap",
"AttachmentRef": {
"Pass": "ShadowPass",
"Attachment": "DirectionalShadowmap"
}
},
{
"LocalSlot": "DirectionalESM",
"AttachmentRef": {
"Pass": "ShadowPass",
"Attachment": "DirectionalESM"
}
},
{
"LocalSlot": "ProjectedShadowmap",
"AttachmentRef": {
"Pass": "ShadowPass",
"Attachment": "ProjectedShadowmap"
}
},
{
"LocalSlot": "ProjectedESM",
"AttachmentRef": {
"Pass": "ShadowPass",
"Attachment": "ProjectedESM"
}
},
{
"LocalSlot": "TileLightData",
"AttachmentRef": {
"Pass": "LightCullingPass",
"Attachment": "TileLightData"
}
},
{
"LocalSlot": "LightListRemapped",
"AttachmentRef": {
"Pass": "LightCullingPass",
"Attachment": "LightListRemapped"
}
},
{
"LocalSlot": "DepthStencil",
"AttachmentRef": {
"Pass": "DepthPrePass",
"Attachment": "Depth"
}
},
{
"LocalSlot": "InputOutput",
"AttachmentRef": {
"Pass": "MSAAResolvePass",
"Attachment": "Output"
}
}
]
},
{
"Name": "LightAdaptation",
"TemplateName": "LightAdaptationParentTemplate",
"Connections": [
{
"LocalSlot": "LightingInput",
"AttachmentRef": {
"Pass": "TransparentPass",
"Attachment": "InputOutput"
}
},
{
"LocalSlot": "SwapChainOutput",
"AttachmentRef": {
"Pass": "Parent",
"Attachment": "SwapChainOutput"
}
}
]
},
{
"Name": "AuxGeomPass",
"TemplateName": "AuxGeomPassTemplate",
"Enabled": true,
"Connections": [
{
"LocalSlot": "ColorInputOutput",
"AttachmentRef": {
"Pass": "LightAdaptation",
"Attachment": "Output"
}
},
{
"LocalSlot": "DepthInputOutput",
"AttachmentRef": {
"Pass": "DepthPrePass",
"Attachment": "Depth"
}
}
],
"PassData": {
"$type": "RasterPassData",
"DrawListTag": "auxgeom",
"PipelineViewTag": "MainCamera"
}
},
{
"Name": "UIPass",
"TemplateName": "UIParentTemplate",
"Connections": [
{
"LocalSlot": "InputOutput",
"AttachmentRef": {
"Pass": "AuxGeomPass",
"Attachment": "ColorInputOutput"
}
}
]
},
{
"Name": "CopyToSwapChain",
"TemplateName": "FullscreenCopyTemplate",
"Connections": [
{
"LocalSlot": "Input",
"AttachmentRef": {
"Pass": "UIPass",
"Attachment": "InputOutput"
}
},
{
"LocalSlot": "Output",
"AttachmentRef": {
"Pass": "Parent",
"Attachment": "SwapChainOutput"
}
}
]
}
]
}
}
}

@ -305,7 +305,7 @@
},
{
"Name": "SkyBoxPass",
"TemplateName": "SkyBoxTemplate",
"TemplateName": "SkyBoxTwoOutputsTemplate",
"Enabled": true,
"Connections": [
{

@ -92,6 +92,10 @@
"Name": "SkyBoxTemplate",
"Path": "Passes/SkyBox.pass"
},
{
"Name": "SkyBoxTwoOutputsTemplate",
"Path": "Passes/SkyBox_TwoOutputs.pass"
},
{
"Name": "UIPassTemplate",
"Path": "Passes/UI.pass"
@ -483,6 +487,18 @@
{
"Name": "UIParentTemplate",
"Path": "Passes/UIParent.pass"
},
{
"Name": "LightAdaptationParentTemplate",
"Path": "Passes/LightAdaptationParent.pass"
},
{
"Name": "LowEndForwardPassTemplate",
"Path": "Passes/LowEndForward.pass"
},
{
"Name": "LowEndPipelineTemplate",
"Path": "Passes/LowEndPipeline.pass"
}
]
}

@ -40,7 +40,7 @@
{
"LocalSlot": "Output",
"AttachmentRef": {
"Pass": "DisplayMapperPass",
"Pass": "LightAdaptation",
"Attachment": "Output"
}
},
@ -54,8 +54,8 @@
{
"LocalSlot": "LuminanceMipChainOutput",
"AttachmentRef": {
"Pass": "DownsampleLuminanceMipChain",
"Attachment": "MipChainInputOutput"
"Pass": "LightAdaptation",
"Attachment": "LuminanceMipChainOutput"
}
}
],
@ -115,94 +115,16 @@
}
]
},
// Everything before this point deals in raw lighting values
// ---------------------------------------------------------
// Everything after starts to map to values we see on screen
{
"Name": "DownsampleLuminanceMinAvgMax",
"TemplateName": "DownsampleLuminanceMinAvgMaxCS",
"Name": "LightAdaptation",
"TemplateName": "LightAdaptationParentTemplate",
"Connections": [
{
"LocalSlot": "Input",
"LocalSlot": "LightingInput",
"AttachmentRef": {
"Pass": "BloomPass",
"Attachment": "InputOutput"
}
}
]
},
{
"Name": "DownsampleLuminanceMipChain",
"TemplateName": "DownsampleMipChainTemplate",
"Connections": [
{
"LocalSlot": "MipChainInputOutput",
"AttachmentRef": {
"Pass": "DownsampleLuminanceMinAvgMax",
"Attachment": "Output"
}
}
],
"PassData": {
"$type": "DownsampleMipChainPassData",
"ShaderAsset": {
"FilePath": "Shaders/PostProcessing/DownsampleMinAvgMaxCS.shader"
}
}
},
{
"Name": "EyeAdaptationPass",
"TemplateName": "EyeAdaptationTemplate",
"Enabled": false,
"Connections": [
{
"LocalSlot": "SceneLuminanceInput",
"AttachmentRef": {
"Pass": "DownsampleLuminanceMipChain",
"Attachment": "MipChainInputOutput"
}
}
]
},
{
"Name": "LookModificationTransformPass",
"TemplateName": "LookModificationTransformTemplate",
"Enabled": true,
"Connections": [
{
"LocalSlot": "Input",
"AttachmentRef": {
"Pass": "BloomPass",
"Attachment": "InputOutput"
}
},
{
"LocalSlot": "EyeAdaptationDataInput",
"AttachmentRef": {
"Pass": "EyeAdaptationPass",
"Attachment": "EyeAdaptationDataInputOutput"
}
},
{
"LocalSlot": "SwapChainOutput",
"AttachmentRef": {
"Pass": "Parent",
"Attachment": "SwapChainOutput"
}
}
]
},
{
"Name": "DisplayMapperPass",
"TemplateName": "DisplayMapperTemplate",
"Enabled": true,
"Connections": [
{
"LocalSlot": "Input",
"AttachmentRef": {
"Pass": "LookModificationTransformPass",
"Attachment": "Output"
}
},
{
"LocalSlot": "SwapChainOutput",

@ -12,11 +12,6 @@
"SlotType": "InputOutput",
"ScopeAttachmentUsage": "RenderTarget"
},
{
"Name": "ReflectionInputOutput",
"SlotType": "InputOutput",
"ScopeAttachmentUsage": "RenderTarget"
},
{
"Name": "SkyBoxDepth",
"SlotType": "InputOutput",

@ -0,0 +1,43 @@
{
"Type": "JsonSerialization",
"Version": 1,
"ClassName": "PassAsset",
"ClassData": {
"PassTemplate": {
"Name": "SkyBoxTwoOutputsTemplate",
"PassClass": "FullScreenTriangle",
"Slots": [
{
"Name": "SpecularInputOutput",
"SlotType": "InputOutput",
"ScopeAttachmentUsage": "RenderTarget"
},
{
"Name": "ReflectionInputOutput",
"SlotType": "InputOutput",
"ScopeAttachmentUsage": "RenderTarget"
},
{
"Name": "SkyBoxDepth",
"SlotType": "InputOutput",
"ScopeAttachmentUsage": "DepthStencil"
}
],
"PassData": {
"$type": "FullscreenTrianglePassData",
"ShaderAsset": {
"FilePath": "shaders/skybox/skybox_twooutputs.shader"
},
"PipelineViewTag": "MainCamera",
"ShaderDataMappings": {
"FloatMappings": [
{
"Name": "m_sunIntensityMultiplier",
"Value": 1.0
}
]
}
}
}
}
}

@ -31,6 +31,16 @@ ShaderResourceGroup ObjectSrg : SRG_PerObject
return SceneSrg::GetObjectToWorldInverseTransposeMatrix(m_objectId);
}
//[GFX TODO][ATOM-15280] Move wrinkle mask data from the default object srg into something specific to the Skin shader
uint m_wrinkle_mask_count;
float4 m_wrinkle_mask_weights[4];
Texture2D m_wrinkle_masks[16];
float GetWrinkleMaskWeight(uint index)
{
return m_wrinkle_mask_weights[index / 4][index % 4];
}
//! Reflection Probe (smallest probe volume that overlaps the object position)
struct ReflectionProbeData
{

@ -10,6 +10,21 @@
*
*/
#ifdef UNIFIED_FORWARD_OUTPUT
struct ForwardPassOutput
{
float4 m_color : SV_Target0;
};
struct ForwardPassOutputWithDepth
{
float4 m_color : SV_Target0;
float m_depth : SV_Depth;
};
#else
struct ForwardPassOutput
{
float4 m_diffuseColor : SV_Target0; //!< RGB = Diffuse Lighting, A = Blend Alpha (for blended surfaces) OR A = special encoding of surfaceScatteringFactor, m_subsurfaceScatteringQuality, o_enableSubsurfaceScattering
@ -30,3 +45,5 @@ struct ForwardPassOutputWithDepth
float4 m_normal : SV_Target4;
float m_depth : SV_Depth;
};
#endif

@ -12,38 +12,39 @@
#pragma once
// --- Static Options Available ---
// FORCE_IBL_IN_FORWARD_PASS - forces IBL lighting to be run in the forward pass, used in pipelines that don't have a reflection pass
#include <Atom/Features/PBR/LightingOptions.azsli>
#include <Atom/RPI/Math.azsli>
#include <Atom/Features/PBR/Lights/LightTypesCommon.azsli>
#include <Atom/Features/PBR/LightingUtils.azsli>
void ApplyIblDiffuse(
float3 GetIblDiffuse(
float3 normal,
float3 albedo,
float3 diffuseResponse,
out float3 outDiffuse)
float3 diffuseResponse)
{
float3 irradianceDir = MultiplyVectorQuaternion(normal, SceneSrg::m_iblOrientation);
float3 diffuseSample = SceneSrg::m_diffuseEnvMap.Sample(SceneSrg::m_samplerEnv, GetCubemapCoords(irradianceDir)).rgb;
outDiffuse = diffuseResponse * albedo * diffuseSample;
return diffuseResponse * albedo * diffuseSample;
}
void ApplyIblSpecular(
float3 GetIblSpecular(
float3 position,
float3 normal,
float3 specularF0,
float roughnessLinear,
float3 dirToCamera,
float2 brdf,
out float3 outSpecular)
float2 brdf)
{
float3 reflectDir = reflect(-dirToCamera, normal);
reflectDir = MultiplyVectorQuaternion(reflectDir, SceneSrg::m_iblOrientation);
// global
outSpecular = SceneSrg::m_specularEnvMap.SampleLevel(SceneSrg::m_samplerEnv, GetCubemapCoords(reflectDir), GetRoughnessMip(roughnessLinear)).rgb;
float3 outSpecular = SceneSrg::m_specularEnvMap.SampleLevel(SceneSrg::m_samplerEnv, GetCubemapCoords(reflectDir), GetRoughnessMip(roughnessLinear)).rgb;
outSpecular *= (specularF0 * brdf.x + brdf.y);
// reflection probe
@ -72,86 +73,55 @@ void ApplyIblSpecular(
outSpecular = lerp(outSpecular, probeSpecular, blendAmount);
}
return outSpecular;
}
void ApplyIBL(Surface surface, inout LightingData lightingData)
{
if (o_opacity_mode == OpacityMode::Blended || o_opacity_mode == OpacityMode::TintedTransparent)
#ifdef FORCE_IBL_IN_FORWARD_PASS
bool useDiffuseIbl = true;
bool useSpecularIbl = true;
bool useIbl = o_enableIBL;
#else
bool isTransparent = (o_opacity_mode == OpacityMode::Blended || o_opacity_mode == OpacityMode::TintedTransparent);
bool useDiffuseIbl = isTransparent;
bool useSpecularIbl = (isTransparent || o_meshUseForwardPassIBLSpecular || o_materialUseForwardPassIBLSpecular);
bool useIbl = o_enableIBL && (useDiffuseIbl || useSpecularIbl);
#endif
if(useIbl)
{
// transparencies currently require IBL in the forward pass
if (o_enableIBL)
float iblExposureFactor = pow(2.0, SceneSrg::m_iblExposure);
if(useDiffuseIbl)
{
float3 iblDiffuse = 0.0f;
ApplyIblDiffuse(
surface.normal,
surface.albedo,
lightingData.diffuseResponse,
iblDiffuse);
float3 iblSpecular = 0.0f;
ApplyIblSpecular(
surface.position,
surface.normal,
surface.specularF0,
surface.roughnessLinear,
lightingData.dirToCamera,
lightingData.brdf,
iblSpecular);
// Adjust IBL lighting by exposure.
float iblExposureFactor = pow(2.0, SceneSrg::m_iblExposure);
float3 iblDiffuse = GetIblDiffuse(surface.normal, surface.albedo, lightingData.diffuseResponse);
lightingData.diffuseLighting += (iblDiffuse * iblExposureFactor * lightingData.diffuseAmbientOcclusion);
lightingData.specularLighting += (iblSpecular * iblExposureFactor);
}
}
else if (o_meshUseForwardPassIBLSpecular || o_materialUseForwardPassIBLSpecular)
{
if (o_enableIBL)
{
float3 iblSpecular = 0.0f;
ApplyIblSpecular(
surface.position,
surface.normal,
surface.specularF0,
surface.roughnessLinear,
lightingData.dirToCamera,
lightingData.brdf,
iblSpecular);
if(useSpecularIbl)
{
float3 iblSpecular = GetIblSpecular(surface.position, surface.normal, surface.specularF0, surface.roughnessLinear, lightingData.dirToCamera, lightingData.brdf);
iblSpecular *= lightingData.multiScatterCompensation;
if (o_clearCoat_feature_enabled)
if (o_clearCoat_feature_enabled && surface.clearCoat.factor > 0.0f)
{
if (surface.clearCoat.factor > 0.0f)
{
float clearCoatNdotV = saturate(dot(surface.clearCoat.normal, lightingData.dirToCamera));
clearCoatNdotV = max(clearCoatNdotV, 0.01f); // [GFX TODO][ATOM-4466] This is a current band-aid for specular noise at grazing angles.
float2 clearCoatBrdf = PassSrg::m_brdfMap.Sample(PassSrg::LinearSampler, GetBRDFTexCoords(surface.clearCoat.roughness, clearCoatNdotV)).rg;
// clear coat uses fixed IOR = 1.5 represents polyurethane which is the most common material for gloss clear coat
// coat layer assumed to be dielectric thus don't need multiple scattering compensation
float3 clearCoatSpecularF0 = float3(0.04f, 0.04f, 0.04f);
float3 clearCoatIblSpecular = 0.0f;
ApplyIblSpecular(
surface.position,
surface.clearCoat.normal,
clearCoatSpecularF0,
surface.clearCoat.roughness,
lightingData.dirToCamera,
clearCoatBrdf,
clearCoatIblSpecular);
clearCoatIblSpecular *= surface.clearCoat.factor;
float clearCoatNdotV = saturate(dot(surface.clearCoat.normal, lightingData.dirToCamera));
clearCoatNdotV = max(clearCoatNdotV, 0.01f); // [GFX TODO][ATOM-4466] This is a current band-aid for specular noise at grazing angles.
float2 clearCoatBrdf = PassSrg::m_brdfMap.Sample(PassSrg::LinearSampler, GetBRDFTexCoords(surface.clearCoat.roughness, clearCoatNdotV)).rg;
// clear coat uses fixed IOR = 1.5 represents polyurethane which is the most common material for gloss clear coat
// coat layer assumed to be dielectric thus don't need multiple scattering compensation
float3 clearCoatSpecularF0 = float3(0.04f, 0.04f, 0.04f);
float3 clearCoatIblSpecular = GetIblSpecular(surface.position, surface.clearCoat.normal, clearCoatSpecularF0, surface.clearCoat.roughness, lightingData.dirToCamera, clearCoatBrdf);
clearCoatIblSpecular *= surface.clearCoat.factor;
// attenuate base layer energy
float3 clearCoatResponse = FresnelSchlickWithRoughness(clearCoatNdotV, clearCoatSpecularF0, surface.clearCoat.roughness) * surface.clearCoat.factor;
iblSpecular = iblSpecular * (1.0 - clearCoatResponse) * (1.0 - clearCoatResponse) + clearCoatIblSpecular;
}
// attenuate base layer energy
float3 clearCoatResponse = FresnelSchlickWithRoughness(clearCoatNdotV, clearCoatSpecularF0, surface.clearCoat.roughness) * surface.clearCoat.factor;
iblSpecular = iblSpecular * (1.0 - clearCoatResponse) * (1.0 - clearCoatResponse) + clearCoatIblSpecular;
}
float iblExposureFactor = pow(2.0f, SceneSrg::m_iblExposure);
lightingData.specularLighting += (iblSpecular * iblExposureFactor);
}
}

@ -0,0 +1,26 @@
/*
* 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
// This file translates quality option macros like QUALITY_LOW_END to their relevant settings
#ifdef QUALITY_LOW_END
// Unifies the forward output into a single lighting buffer instead of splitting it into a GBuffer
#define UNIFIED_FORWARD_OUTPUT 1
// Forces IBL lighting to be executed in the forward pass instead of subsequent refleciton passes
#define FORCE_IBL_IN_FORWARD_PASS 1
#endif

@ -10,6 +10,9 @@
*
*/
// --- Static Options Available ---
// SKYBOX_TWO_OUTPUTS - Skybox renders to two rendertargets instead of one (SkyBox_TwoOutputs.pass writes to specular and reflection targets)
#include <Atom/Features/ColorManagement/TransformColor.azsli>
#include <Atom/Features/PostProcessing/FullscreenVertexUtil.azsli>
#include <Atom/Features/MatrixUtility.azsli>
@ -102,7 +105,9 @@ float3 GetCubemapCoords(float3 original)
struct PSOutput
{
float4 m_specular : SV_Target0;
#ifdef SKYBOX_TWO_OUTPUTS
float4 m_reflection : SV_Target1;
#endif
};
PSOutput MainPS(VSOutput input)
@ -163,6 +168,8 @@ PSOutput MainPS(VSOutput input)
PSOutput OUT;
OUT.m_specular = float4(color, 1.0);
#ifdef SKYBOX_TWO_OUTPUTS
OUT.m_reflection = float4(color, 1.0);
#endif
return OUT;
}

@ -0,0 +1,17 @@
/*
* 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.
*
*/
// NOTE: This file is a temporary workaround until .shader files can #define macros for their .azsl files
#define SKYBOX_TWO_OUTPUTS
#include "SkyBox.azsl"

@ -0,0 +1,22 @@
{
"Source" : "SkyBox_TwoOutputs",
"DepthStencilState" : {
"Depth" : { "Enable" : true, "CompareFunc" : "GreaterEqual" }
},
"ProgramSettings":
{
"EntryPoints":
[
{
"name": "MainVS",
"type": "Vertex"
},
{
"name": "MainPS",
"type": "Fragment"
}
]
}
}

@ -38,6 +38,7 @@ set(FILES
Materials/Types/StandardMultilayerPBR_ForwardPass_EDS.shader
Materials/Types/StandardMultilayerPBR_Parallax.lua
Materials/Types/StandardMultilayerPBR_ParallaxPerLayer.lua
Materials/Types/StandardMultilayerPBR_ShaderEnable.lua
Materials/Types/StandardMultilayerPBR_Shadowmap_WithPS.azsl
Materials/Types/StandardMultilayerPBR_Shadowmap_WithPS.shader
Materials/Types/StandardPBR.materialtype
@ -52,6 +53,9 @@ set(FILES
Materials/Types/StandardPBR_ForwardPass_EDS.shader
Materials/Types/StandardPBR_HandleOpacityDoubleSided.lua
Materials/Types/StandardPBR_HandleOpacityMode.lua
Materials/Types/StandardPBR_LowEndForward.azsl
Materials/Types/StandardPBR_LowEndForward.shader
Materials/Types/StandardPBR_LowEndForward_EDS.shader
Materials/Types/StandardPBR_ParallaxState.lua
Materials/Types/StandardPBR_Roughness.lua
Materials/Types/StandardPBR_ShaderEnable.lua
@ -116,6 +120,7 @@ set(FILES
Passes/DiffuseProbeGridBlendDistance.pass
Passes/DiffuseProbeGridBlendIrradiance.pass
Passes/DiffuseProbeGridBorderUpdate.pass
Passes/DiffuseProbeGridClassification.pass
Passes/DiffuseProbeGridDownsample.pass
Passes/DiffuseProbeGridRayTracing.pass
Passes/DiffuseProbeGridRelocation.pass
@ -144,6 +149,7 @@ set(FILES
Passes/FullscreenCopy.pass
Passes/FullscreenOutputOnly.pass
Passes/ImGui.pass
Passes/LightAdaptationParent.pass
Passes/LightCulling.pass
Passes/LightCullingHeatmap.pass
Passes/LightCullingParent.pass
@ -152,6 +158,8 @@ set(FILES
Passes/LightCullingTilePrepareMSAA.pass
Passes/LookModificationComposite.pass
Passes/LookModificationTransform.pass
Passes/LowEndForward.pass
Passes/LowEndPipeline.pass
Passes/LuminanceHeatmap.pass
Passes/LuminanceHistogramGenerator.pass
Passes/MainPipeline.pass
@ -179,13 +187,16 @@ set(FILES
Passes/ReflectionScreenSpace.pass
Passes/ReflectionScreenSpaceBlur.pass
Passes/ReflectionScreenSpaceBlurHorizontal.pass
Passes/ReflectionScreenSpaceBlurMobile.pass
Passes/ReflectionScreenSpaceBlurVertical.pass
Passes/ReflectionScreenSpaceComposite.pass
Passes/ReflectionScreenSpaceMobile.pass
Passes/ReflectionScreenSpaceTrace.pass
Passes/Reflections_nomsaa.pass
Passes/ShadowParent.pass
Passes/Skinning.pass
Passes/SkyBox.pass
Passes/SkyBox_TwoOutputs.pass
Passes/SMAA1xApplyLinearHDRColor.pass
Passes/SMAA1xApplyPerceptualColor.pass
Passes/SMAABlendingWeightCalculation.pass
@ -205,6 +216,7 @@ set(FILES
ShaderLib/Atom/Features/IndirectRendering.azsli
ShaderLib/Atom/Features/MatrixUtility.azsli
ShaderLib/Atom/Features/ParallaxMapping.azsli
ShaderLib/Atom/Features/ShaderQualityOptions.azsli
ShaderLib/Atom/Features/SphericalHarmonicsUtility.azsli
ShaderLib/Atom/Features/SrgSemantics.azsli
ShaderLib/Atom/Features/ColorManagement/TransformColor.azsli
@ -272,6 +284,7 @@ set(FILES
ShaderLib/Atom/Features/PostProcessing/GlyphData.azsli
ShaderLib/Atom/Features/PostProcessing/GlyphRender.azsli
ShaderLib/Atom/Features/PostProcessing/PostProcessUtil.azsli
ShaderLib/Atom/Features/RayTracing/RayTracingSceneSrg.azsli
ShaderLib/Atom/Features/ScreenSpace/ScreenSpaceUtil.azsli
ShaderLib/Atom/Features/Shadow/BicubicPcfFilters.azsli
ShaderLib/Atom/Features/Shadow/DirectionalLightShadow.azsli
@ -471,4 +484,6 @@ set(FILES
Shaders/SkinnedMesh/LinearSkinningPassSRG.azsli
Shaders/SkyBox/SkyBox.azsl
Shaders/SkyBox/SkyBox.shader
Shaders/SkyBox/SkyBox_TwoOutputs.azsl
Shaders/SkyBox/SkyBox_TwoOutputs.shader
)

@ -148,6 +148,8 @@ namespace AZ
Data::Instance<RPI::Model> GetModel(const MeshHandle& meshHandle) const override;
Data::Asset<RPI::ModelAsset> GetModelAsset(const MeshHandle& meshHandle) const override;
Data::Instance<RPI::ShaderResourceGroup> GetObjectSrg(const MeshHandle& meshHandle) const override;
void QueueObjectSrgForCompile(const MeshHandle& meshHandle) const override;
void SetMaterialAssignmentMap(const MeshHandle& meshHandle, const Data::Instance<RPI::Material>& material) override;
void SetMaterialAssignmentMap(const MeshHandle& meshHandle, const MaterialAssignmentMap& materials) override;
const MaterialAssignmentMap& GetMaterialAssignmentMap(const MeshHandle& meshHandle) const override;

@ -61,6 +61,14 @@ namespace AZ
virtual Data::Instance<RPI::Model> GetModel(const MeshHandle& meshHandle) const = 0;
//! Gets the underlying RPI::ModelAsset for a meshHandle.
virtual Data::Asset<RPI::ModelAsset> GetModelAsset(const MeshHandle& meshHandle) const = 0;
//! Gets the ObjectSrg for a meshHandle.
//! Updating the ObjectSrg should be followed by a call to QueueObjectSrgForCompile,
//! instead of compiling the srg directly. This way, if the srg has already been queued for compile,
//! it will not be queued twice in the same frame. The ObjectSrg should not be updated during
//! Simulate, or it will create a race between updating the data and the call to Compile
virtual Data::Instance<RPI::ShaderResourceGroup> GetObjectSrg(const MeshHandle& meshHandle) const = 0;
//! Queues the object srg for compile.
virtual void QueueObjectSrgForCompile(const MeshHandle& meshHandle) const = 0;
//! Sets the MaterialAssignmentMap for a meshHandle, using just a single material for the DefaultMaterialAssignmentId.
//! Note if there is already a material assignment map, this will replace the entire map with just a single material.
virtual void SetMaterialAssignmentMap(const MeshHandle& meshHandle, const Data::Instance<RPI::Material>& material) = 0;

@ -23,6 +23,8 @@ namespace UnitTest
MOCK_METHOD1(CloneMesh, MeshHandle(const MeshHandle&));
MOCK_CONST_METHOD1(GetModel, AZStd::intrusive_ptr<AZ::RPI::Model>(const MeshHandle&));
MOCK_CONST_METHOD1(GetModelAsset, AZ::Data::Asset<AZ::RPI::ModelAsset>(const MeshHandle&));
MOCK_CONST_METHOD1(GetObjectSrg, AZStd::intrusive_ptr<AZ::RPI::ShaderResourceGroup>(const MeshHandle&));
MOCK_CONST_METHOD1(QueueObjectSrgForCompile, void(const MeshHandle&));
MOCK_CONST_METHOD1(GetMaterialAssignmentMap, const AZ::Render::MaterialAssignmentMap&(const MeshHandle&));
MOCK_METHOD2(ConnectModelChangeEventHandler, void(const MeshHandle&, ModelChangedEvent::Handler&));
MOCK_METHOD3(SetTransform, void(const MeshHandle&, const AZ::Transform&, const AZ::Vector3&));

@ -231,6 +231,19 @@ namespace AZ
return {};
}
Data::Instance<RPI::ShaderResourceGroup> MeshFeatureProcessor::GetObjectSrg(const MeshHandle& meshHandle) const
{
return meshHandle.IsValid() ? meshHandle->m_shaderResourceGroup : nullptr;
}
void MeshFeatureProcessor::QueueObjectSrgForCompile(const MeshHandle& meshHandle) const
{
if (meshHandle.IsValid())
{
meshHandle->m_objectSrgNeedsUpdate = true;
}
}
void MeshFeatureProcessor::SetMaterialAssignmentMap(const MeshHandle& meshHandle, const Data::Instance<RPI::Material>& material)
{
Render::MaterialAssignmentMap materials;

@ -12,6 +12,7 @@
#include <MorphTargets/MorphTargetComputePass.h>
#include <SkinnedMesh/SkinnedMeshFeatureProcessor.h>
#include <Atom/Feature/SkinnedMesh/SkinnedMeshOutputStreamManagerInterface.h>
#include <Atom/RPI.Public/Shader/Shader.h>
@ -38,6 +39,11 @@ namespace AZ
return m_shader;
}
void MorphTargetComputePass::SetFeatureProcessor(SkinnedMeshFeatureProcessor* skinnedMeshFeatureProcessor)
{
m_skinnedMeshFeatureProcessor = skinnedMeshFeatureProcessor;
}
void MorphTargetComputePass::BuildAttachmentsInternal()
{
// The same buffer that skinning writes to is used to manage the computed vertex deltas that are passed from the
@ -45,30 +51,16 @@ namespace AZ
AttachBufferToSlot(Name{ "MorphTargetDeltaOutput" }, SkinnedMeshOutputStreamManagerInterface::Get()->GetBuffer());
}
void MorphTargetComputePass::AddDispatchItem(const RHI::DispatchItem* dispatchItem)
{
AZ_Assert(dispatchItem != nullptr, "invalid dispatchItem");
AZStd::lock_guard<AZStd::mutex> lock(m_mutex);
//using an unordered_set here to prevent redundantly adding the same dispatchItem to the submission queue
//(i.e. if the same morph target exists in multiple views, it can call AddDispatchItem multiple times with the same item)
m_dispatches.insert(dispatchItem);
}
void MorphTargetComputePass::BuildCommandListInternal(const RHI::FrameGraphExecuteContext& context)
{
RHI::CommandList* commandList = context.GetCommandList();
if (m_skinnedMeshFeatureProcessor)
{
RHI::CommandList* commandList = context.GetCommandList();
SetSrgsForDispatch(commandList);
SetSrgsForDispatch(commandList);
AZStd::lock_guard<AZStd::mutex> lock(m_mutex);
for (const RHI::DispatchItem* dispatchItem : m_dispatches)
{
commandList->Submit(*dispatchItem);
m_skinnedMeshFeatureProcessor->SubmitMorphTargetDispatchItems(commandList);
}
// Clear the dispatch items. They will need to be re-populated next frame
m_dispatches.clear();
}
} // namespace Render
} // namespace AZ

@ -18,6 +18,8 @@ namespace AZ
{
namespace Render
{
class SkinnedMeshFeatureProcessor;
//! The morph target compute pass submits dispatch items for morph targets. The dispatch items are cleared every frame, so it needs to be re-populated.
class MorphTargetComputePass
: public RPI::ComputePass
@ -31,16 +33,14 @@ namespace AZ
static RPI::Ptr<MorphTargetComputePass> Create(const RPI::PassDescriptor& descriptor);
//! Thread-safe function for adding a dispatch item to the current frame.
void AddDispatchItem(const RHI::DispatchItem* dispatchItem);
Data::Instance<RPI::Shader> GetShader() const;
void SetFeatureProcessor(SkinnedMeshFeatureProcessor* m_skinnedMeshFeatureProcessor);
private:
void BuildAttachmentsInternal() override;
void BuildCommandListInternal(const RHI::FrameGraphExecuteContext& context) override;
AZStd::mutex m_mutex;
AZStd::unordered_set<const RHI::DispatchItem*> m_dispatches;
SkinnedMeshFeatureProcessor* m_skinnedMeshFeatureProcessor = nullptr;
};
}
}

@ -11,7 +11,7 @@
*/
#include <MorphTargets/MorphTargetDispatchItem.h>
#include <MorphTargets/MorphTargetComputePass.h>
#include <SkinnedMesh/SkinnedMeshFeatureProcessor.h>
#include <Atom/RPI.Public/Shader/ShaderResourceGroup.h>
#include <Atom/RPI.Public/Shader/Shader.h>
@ -30,7 +30,7 @@ namespace AZ
MorphTargetDispatchItem::MorphTargetDispatchItem(
const AZStd::intrusive_ptr<MorphTargetInputBuffers> inputBuffers,
const MorphTargetMetaData& morphTargetMetaData,
RPI::Ptr<MorphTargetComputePass> morphTargetComputePass,
SkinnedMeshFeatureProcessor* skinnedMeshFeatureProcessor,
MorphTargetInstanceMetaData morphInstanceMetaData,
float morphDeltaIntegerEncoding)
: m_inputBuffers(inputBuffers)
@ -38,7 +38,7 @@ namespace AZ
, m_morphInstanceMetaData(morphInstanceMetaData)
, m_accumulatedDeltaIntegerEncoding(morphDeltaIntegerEncoding)
{
m_morphTargetShader = morphTargetComputePass->GetShader();
m_morphTargetShader = skinnedMeshFeatureProcessor->GetMorphTargetShader();
RPI::ShaderReloadNotificationBus::Handler::BusConnect(m_morphTargetShader->GetAssetId());
}

@ -37,7 +37,7 @@ namespace AZ
namespace Render
{
class MorphTargetComputePass;
class SkinnedMeshFeatureProcessor;
//! Holds and manages an RHI DispatchItem for a specific morph target, and the resources that are needed to build and maintain it.
class MorphTargetDispatchItem
@ -51,7 +51,7 @@ namespace AZ
explicit MorphTargetDispatchItem(
const AZStd::intrusive_ptr<MorphTargetInputBuffers> inputBuffers,
const MorphTargetMetaData& morphTargetMetaData,
RPI::Ptr<MorphTargetComputePass> morphTargetComputePass,
SkinnedMeshFeatureProcessor* skinnedMeshFeatureProcessor,
MorphTargetInstanceMetaData morphInstanceMetaData,
float accumulatedDeltaRange
);

@ -12,6 +12,7 @@
#include <SkinnedMesh/SkinnedMeshComputePass.h>
#include <SkinnedMesh/SkinnedMeshFeatureProcessor.h>
#include <Atom/Feature/SkinnedMesh/SkinnedMeshOutputStreamManagerInterface.h>
#include <Atom/RPI.Public/Shader/Shader.h>
@ -22,11 +23,9 @@ namespace AZ
{
namespace Render
{
SkinnedMeshComputePass::SkinnedMeshComputePass(const RPI::PassDescriptor& descriptor)
: RPI::ComputePass(descriptor)
{
m_cachedShaderOptions.SetShader(m_shader);
}
RPI::Ptr<SkinnedMeshComputePass> SkinnedMeshComputePass::Create(const RPI::PassDescriptor& descriptor)
@ -40,42 +39,30 @@ namespace AZ
return m_shader;
}
RPI::ShaderOptionGroup SkinnedMeshComputePass::CreateShaderOptionGroup(const SkinnedMeshShaderOptions shaderOptions, SkinnedMeshShaderOptionNotificationBus::Handler& shaderReinitializedHandler)
void SkinnedMeshComputePass::SetFeatureProcessor(SkinnedMeshFeatureProcessor* skinnedMeshFeatureProcessor)
{
m_cachedShaderOptions.ConnectToShaderReinitializedEvent(shaderReinitializedHandler);
return m_cachedShaderOptions.CreateShaderOptionGroup(shaderOptions);
}
void SkinnedMeshComputePass::AddDispatchItem(const RHI::DispatchItem* dispatchItem)
{
AZ_Assert(dispatchItem != nullptr, "invalid dispatchItem");
AZStd::lock_guard<AZStd::mutex> lock(m_mutex);
//using an unordered_set here to prevent redundantly adding the same dispatchItem to the submission queue
//(i.e. if the same skinnedMesh exists in multiple views, it can call AddDispatchItem multiple times with the same item)
m_dispatches.insert(dispatchItem);
m_skinnedMeshFeatureProcessor = skinnedMeshFeatureProcessor;
}
void SkinnedMeshComputePass::BuildCommandListInternal(const RHI::FrameGraphExecuteContext& context)
{
RHI::CommandList* commandList = context.GetCommandList();
if (m_skinnedMeshFeatureProcessor)
{
RHI::CommandList* commandList = context.GetCommandList();
SetSrgsForDispatch(commandList);
SetSrgsForDispatch(commandList);
AZStd::lock_guard<AZStd::mutex> lock(m_mutex);
for (const RHI::DispatchItem* dispatchItem : m_dispatches)
{
commandList->Submit(*dispatchItem);
m_skinnedMeshFeatureProcessor->SubmitSkinningDispatchItems(commandList);
}
// Clear the dispatch items. They will need to be re-populated next frame
m_dispatches.clear();
}
void SkinnedMeshComputePass::OnShaderReinitialized(const RPI::Shader& shader)
{
ComputePass::OnShaderReinitialized(shader);
m_cachedShaderOptions.SetShader(m_shader);
if (m_skinnedMeshFeatureProcessor)
{
m_skinnedMeshFeatureProcessor->OnSkinningShaderReinitialized(m_shader);
}
}
void SkinnedMeshComputePass::OnShaderVariantReinitialized(const RPI::Shader& shader, const RPI::ShaderVariantId&, RPI::ShaderVariantStableId)

@ -20,6 +20,8 @@ namespace AZ
{
namespace Render
{
class SkinnedMeshFeatureProcessor;
//! The skinned mesh compute pass submits dispatch items for skinning. The dispatch items are cleared every frame, so it needs to be re-populated.
class SkinnedMeshComputePass
: public RPI::ComputePass
@ -33,10 +35,9 @@ namespace AZ
static RPI::Ptr<SkinnedMeshComputePass> Create(const RPI::PassDescriptor& descriptor);
//! Thread-safe function for adding a dispatch item to the current frame.
void AddDispatchItem(const RHI::DispatchItem* dispatchItem);
Data::Instance<RPI::Shader> GetShader() const;
RPI::ShaderOptionGroup CreateShaderOptionGroup(const SkinnedMeshShaderOptions shaderOptions, SkinnedMeshShaderOptionNotificationBus::Handler& shaderReinitializedHandler);
void SetFeatureProcessor(SkinnedMeshFeatureProcessor* m_skinnedMeshFeatureProcessor);
private:
void BuildCommandListInternal(const RHI::FrameGraphExecuteContext& context) override;
@ -45,9 +46,7 @@ namespace AZ
void OnShaderReinitialized(const RPI::Shader& shader) override;
void OnShaderVariantReinitialized(const RPI::Shader& shader, const RPI::ShaderVariantId& shaderVariantId, RPI::ShaderVariantStableId shaderVariantStableId) override;
AZStd::mutex m_mutex;
AZStd::unordered_set<const RHI::DispatchItem*> m_dispatches;
CachedSkinnedMeshShaderOptions m_cachedShaderOptions;
SkinnedMeshFeatureProcessor* m_skinnedMeshFeatureProcessor = nullptr;
};
}
}

@ -12,7 +12,7 @@
#include <SkinnedMesh/SkinnedMeshDispatchItem.h>
#include <SkinnedMesh/SkinnedMeshOutputStreamManager.h>
#include <SkinnedMesh/SkinnedMeshComputePass.h>
#include <SkinnedMesh/SkinnedMeshFeatureProcessor.h>
#include <Atom/RPI.Public/Shader/ShaderResourceGroup.h>
#include <Atom/RPI.Public/Shader/Shader.h>
@ -34,7 +34,7 @@ namespace AZ
size_t lodIndex,
Data::Instance<RPI::Buffer> boneTransforms,
const SkinnedMeshShaderOptions& shaderOptions,
RPI::Ptr<SkinnedMeshComputePass> skinnedMeshComputePass,
SkinnedMeshFeatureProcessor* skinnedMeshFeatureProcessor,
MorphTargetInstanceMetaData morphTargetInstanceMetaData,
float morphTargetDeltaIntegerEncoding)
: m_inputBuffers(inputBuffers)
@ -45,7 +45,7 @@ namespace AZ
, m_morphTargetInstanceMetaData(morphTargetInstanceMetaData)
, m_morphTargetDeltaIntegerEncoding(morphTargetDeltaIntegerEncoding)
{
m_skinningShader = skinnedMeshComputePass->GetShader();
m_skinningShader = skinnedMeshFeatureProcessor->GetSkinningShader();
// Shader options are generally set per-skinned mesh instance, but morph targets may only exist on some lods. Override the option for applying morph targets here
if (m_morphTargetInstanceMetaData.m_accumulatedPositionDeltaOffsetInBytes != MorphTargetConstants::s_invalidDeltaOffset)
@ -58,7 +58,7 @@ namespace AZ
}
// CreateShaderOptionGroup will also connect to the SkinnedMeshShaderOptionNotificationBus
m_shaderOptionGroup = skinnedMeshComputePass->CreateShaderOptionGroup(m_shaderOptions, *this);
m_shaderOptionGroup = skinnedMeshFeatureProcessor->CreateSkinningShaderOptionGroup(m_shaderOptions, *this);
}
SkinnedMeshDispatchItem::~SkinnedMeshDispatchItem()

@ -38,7 +38,7 @@ namespace AZ
namespace Render
{
class SkinnedMeshComputePass;
class SkinnedMeshFeatureProcessor;
//! Holds and manages an RHI DispatchItem for a specific skinned mesh, and the resources that are needed to build and maintain it.
class SkinnedMeshDispatchItem
@ -55,7 +55,7 @@ namespace AZ
size_t lodIndex,
Data::Instance<RPI::Buffer> skinningMatrices,
const SkinnedMeshShaderOptions& shaderOptions,
RPI::Ptr<SkinnedMeshComputePass> skinnedMeshComputePass,
SkinnedMeshFeatureProcessor* skinnedMeshFeatureProcessor,
MorphTargetInstanceMetaData morphTargetInstanceMetaData,
float morphTargetDeltaIntegerEncoding
);

@ -24,8 +24,10 @@
#include <Atom/RPI.Public/Pass/PassSystemInterface.h>
#include <Atom/RPI.Public/RPIUtils.h>
#include <Atom/RPI.Public/Shader/Shader.h>
#include <Atom/RPI.Public/RenderPipeline.h>
#include <Atom/RHI/CpuProfiler.h>
#include <Atom/RHI/CommandList.h>
#include <AzCore/Debug/EventTrace.h>
#include <AzCore/Jobs/JobCompletion.h>
@ -69,26 +71,11 @@ namespace AZ
}
void SkinnedMeshFeatureProcessor::Simulate(const FeatureProcessor::SimulatePacket& packet)
{
AZ_PROFILE_FUNCTION(Debug::ProfileCategory::AzRender);
AZ_ATOM_PROFILE_FUNCTION("SkinnedMesh", "SkinnedMeshFeatureProcessor: Simulate");
AZ_UNUSED(packet);
SkinnedMeshFeatureProcessorNotificationBus::Broadcast(&SkinnedMeshFeatureProcessorNotificationBus::Events::OnUpdateSkinningMatrices);
}
void SkinnedMeshFeatureProcessor::Render(const FeatureProcessor::RenderPacket& packet)
{
AZ_PROFILE_FUNCTION(Debug::ProfileCategory::AzRender);
AZ_ATOM_PROFILE_FUNCTION("SkinnedMesh", "SkinnedMeshFeatureProcessor: Render");
if (!m_skinningPass)
{
return;
}
#if 0 //[GFX_TODO][ATOM-13564] Temporarily disable skinning culling until we figure out how to hook up visibility & lod selection with skinning:
//Setup the culling workgroup (it will be re-used for each view)
{
@ -132,7 +119,7 @@ namespace AZ
//Dispatch the workgroup to each view
for (const RPI::ViewPtr& viewPtr : packet.m_views)
{
Job *processWorkgroupJob = AZ::CreateJobFunction(
Job* processWorkgroupJob = AZ::CreateJobFunction(
[this, cullingSystem, viewPtr](AZ::Job& thisJob)
{
AZ_PROFILE_SCOPE_DYNAMIC(Debug::ProfileCategory::AzRender, "skinningMeshFP processWorkgroupJob - View: %s", viewPtr->GetName().GetCStr());
@ -167,7 +154,16 @@ namespace AZ
float maxScreenPercentage(lod.m_range.m_max);
if (approxScreenPercentage >= minScreenPercentage && approxScreenPercentage <= maxScreenPercentage)
{
m_skinningPass->AddDispatchItem(&renderProxy->m_dispatchItemsByLod[lodIndex]->GetRHIDispatchItem());
AZStd::lock_guard lock(m_dispatchItemMutex);
m_skinningDispatches.insert(&renderProxy->m_dispatchItemsByLod[lodIndex]->GetRHIDispatchItem());
for (size_t morphTargetIndex = 0; morphTargetIndex < renderProxy->m_morphTargetDispatchItemsByLod[lodIndex].size(); morphTargetIndex++)
{
const MorphTargetDispatchItem* dispatchItem = renderProxy->m_morphTargetDispatchItemsByLod[lodIndex][morphTargetIndex].get();
if (dispatchItem && dispatchItem->GetWeight() > AZ::Constants::FloatEpsilon)
{
m_morphTargetDispatches.insert(&dispatchItem->GetRHIDispatchItem());
}
}
}
}
}
@ -232,13 +228,14 @@ namespace AZ
//Note that this supports overlapping lod ranges (to support cross-fading lods, for example)
if (approxScreenPercentage >= lod.m_screenCoverageMin && approxScreenPercentage <= lod.m_screenCoverageMax)
{
m_skinningPass->AddDispatchItem(&renderProxy.m_dispatchItemsByLod[lodIndex]->GetRHIDispatchItem());
AZStd::lock_guard lock(m_dispatchItemMutex);
m_skinningDispatches.insert(&renderProxy.m_dispatchItemsByLod[lodIndex]->GetRHIDispatchItem());
for (size_t morphTargetIndex = 0; morphTargetIndex < renderProxy.m_morphTargetDispatchItemsByLod[lodIndex].size(); morphTargetIndex++)
{
const MorphTargetDispatchItem* dispatchItem = renderProxy.m_morphTargetDispatchItemsByLod[lodIndex][morphTargetIndex].get();
if (dispatchItem && dispatchItem->GetWeight() > AZ::Constants::FloatEpsilon)
{
m_morphTargetPass->AddDispatchItem(&dispatchItem->GetRHIDispatchItem());
m_morphTargetDispatches.insert(&dispatchItem->GetRHIDispatchItem());
}
}
}
@ -248,29 +245,32 @@ namespace AZ
#endif
}
void SkinnedMeshFeatureProcessor::OnRenderPipelineAdded([[maybe_unused]] RPI::RenderPipelinePtr pipeline)
void SkinnedMeshFeatureProcessor::OnRenderPipelineAdded(RPI::RenderPipelinePtr pipeline)
{
InitSkinningAndMorphPass();
InitSkinningAndMorphPass(pipeline->GetRootPass());
}
void SkinnedMeshFeatureProcessor::OnRenderPipelineRemoved([[maybe_unused]] RPI::RenderPipeline* pipeline)
void SkinnedMeshFeatureProcessor::OnRenderPipelinePassesChanged(RPI::RenderPipeline* renderPipeline)
{
InitSkinningAndMorphPass();
}
void SkinnedMeshFeatureProcessor::OnRenderPipelinePassesChanged([[maybe_unused]] RPI::RenderPipeline* renderPipeline)
{
InitSkinningAndMorphPass();
InitSkinningAndMorphPass(renderPipeline->GetRootPass());
}
void SkinnedMeshFeatureProcessor::OnBeginPrepareRender()
{
m_renderProxiesChecker.soft_lock();
SkinnedMeshFeatureProcessorNotificationBus::Broadcast(&SkinnedMeshFeatureProcessorNotificationBus::Events::OnUpdateSkinningMatrices);
}
void SkinnedMeshFeatureProcessor::OnEndPrepareRender()
void SkinnedMeshFeatureProcessor::OnRenderEnd()
{
m_renderProxiesChecker.soft_unlock();
// Clear any dispatch items that were added but never submitted
// in case there were no passes that submitted this frame
// because they execute at a lower frequency
m_skinningDispatches.clear();
m_morphTargetDispatches.clear();
}
SkinnedMeshRenderProxyHandle SkinnedMeshFeatureProcessor::AcquireRenderProxy(const SkinnedMeshRenderProxyDesc& desc)
@ -295,61 +295,73 @@ namespace AZ
return false;
}
void SkinnedMeshFeatureProcessor::InitSkinningAndMorphPass()
void SkinnedMeshFeatureProcessor::InitSkinningAndMorphPass(const RPI::Ptr<RPI::ParentPass> pipelineRootPass)
{
m_skinningPass = nullptr; //reset it to null, just in case it fails to load the assets properly
m_morphTargetPass = nullptr;
RPI::PassSystemInterface* passSystem = RPI::PassSystemInterface::Get();
if (passSystem->HasPassesForTemplateName(AZ::Name{ "SkinningPassTemplate" }))
RPI::Ptr<RPI::Pass> skinningPass = pipelineRootPass->FindPassByNameRecursive(AZ::Name{ "SkinningPass" });
if (skinningPass)
{
auto& skinningPasses = passSystem->GetPassesForTemplateName(AZ::Name{ "SkinningPassTemplate" });
SkinnedMeshComputePass* skinnedMeshComputePass = azdynamic_cast<SkinnedMeshComputePass*>(skinningPass.get());
skinnedMeshComputePass->SetFeatureProcessor(this);
// For now, assume one skinning pass
if (!skinningPasses.empty() && skinningPasses[0])
{
m_skinningPass = static_cast<SkinnedMeshComputePass*>(skinningPasses[0]);
const Data::Instance<RPI::Shader> shader = m_skinningPass->GetShader();
// There may be multiple skinning passes in the scene due to multiple pipelines, but there is only one skinning shader
m_skinningShader = skinnedMeshComputePass->GetShader();
if (!shader)
{
AZ_Error(s_featureProcessorName, false, "Failed to get skinning pass shader. It may need to finish processing.");
}
if (!m_skinningShader)
{
AZ_Error(s_featureProcessorName, false, "Failed to get skinning pass shader. It may need to finish processing.");
}
else
{
AZ_Error(s_featureProcessorName, false, "\"SkinningPassTemplate\" does not have any valid passes. Check your game project's .pass assets.");
m_cachedSkinningShaderOptions.SetShader(m_skinningShader);
}
}
else
{
AZ_Error(s_featureProcessorName, false, "Failed to find passes for \"SkinningPassTemplate\". Check your game project's .pass assets.");
}
if (passSystem->HasPassesForTemplateName(AZ::Name{ "MorphTargetPassTemplate" }))
RPI::Ptr<RPI::Pass> morphTargetPass = pipelineRootPass->FindPassByNameRecursive(AZ::Name{ "MorphTargetPass" });
if (morphTargetPass)
{
auto& morphTargetPasses = passSystem->GetPassesForTemplateName(AZ::Name{ "MorphTargetPassTemplate" });
MorphTargetComputePass* morphTargetComputePass = azdynamic_cast<MorphTargetComputePass*>(morphTargetPass.get());
morphTargetComputePass->SetFeatureProcessor(this);
// For now, assume one skinning pass
if (!morphTargetPasses.empty() && morphTargetPasses[0])
{
m_morphTargetPass = static_cast<MorphTargetComputePass*>(morphTargetPasses[0]);
const Data::Instance<RPI::Shader> shader = m_morphTargetPass->GetShader();
// There may be multiple morph target passes in the scene due to multiple pipelines, but there is only one morph target shader
m_morphTargetShader = morphTargetComputePass->GetShader();
if (!shader)
{
AZ_Error(s_featureProcessorName, false, "Failed to get morph target pass shader. It may need to finish processing.");
}
}
else
if (!m_morphTargetShader)
{
AZ_Error(s_featureProcessorName, false, "\"MorphTargetPassTemplate\" does not have any valid passes. Check your game project's .pass assets.");
AZ_Error(s_featureProcessorName, false, "Failed to get morph target pass shader. It may need to finish processing.");
}
}
else
}
RPI::ShaderOptionGroup SkinnedMeshFeatureProcessor::CreateSkinningShaderOptionGroup(const SkinnedMeshShaderOptions shaderOptions, SkinnedMeshShaderOptionNotificationBus::Handler& shaderReinitializedHandler)
{
m_cachedSkinningShaderOptions.ConnectToShaderReinitializedEvent(shaderReinitializedHandler);
return m_cachedSkinningShaderOptions.CreateShaderOptionGroup(shaderOptions);
}
void SkinnedMeshFeatureProcessor::OnSkinningShaderReinitialized(const Data::Instance<RPI::Shader> skinningShader)
{
m_skinningShader = skinningShader;
m_cachedSkinningShaderOptions.SetShader(m_skinningShader);
}
void SkinnedMeshFeatureProcessor::SubmitSkinningDispatchItems(RHI::CommandList* commandList)
{
AZStd::lock_guard lock(m_dispatchItemMutex);
for (const RHI::DispatchItem* dispatchItem : m_skinningDispatches)
{
commandList->Submit(*dispatchItem);
}
m_skinningDispatches.clear();
}
void SkinnedMeshFeatureProcessor::SubmitMorphTargetDispatchItems(RHI::CommandList* commandList)
{
AZStd::lock_guard lock(m_dispatchItemMutex);
for (const RHI::DispatchItem* dispatchItem : m_morphTargetDispatches)
{
AZ_Error(s_featureProcessorName, false, "Failed to find passes for \"MorphTargetPassTemplate\". Check your game project's .pass assets.");
commandList->Submit(*dispatchItem);
}
m_morphTargetDispatches.clear();
}
SkinnedMeshRenderProxyInterfaceHandle SkinnedMeshFeatureProcessor::AcquireRenderProxyInterface(const SkinnedMeshRenderProxyDesc& desc)
@ -363,14 +375,14 @@ namespace AZ
return ReleaseRenderProxy(handle);
}
RPI::Ptr<SkinnedMeshComputePass> SkinnedMeshFeatureProcessor::GetSkinningPass() const
Data::Instance<RPI::Shader> SkinnedMeshFeatureProcessor::GetSkinningShader() const
{
return m_skinningPass;
return m_skinningShader;
}
RPI::Ptr<MorphTargetComputePass> SkinnedMeshFeatureProcessor::GetMorphTargetPass() const
Data::Instance<RPI::Shader> SkinnedMeshFeatureProcessor::GetMorphTargetShader() const
{
return m_morphTargetPass;
return m_morphTargetShader;
}
} // namespace Render
} // namespace AZ

@ -49,37 +49,47 @@ namespace AZ
// FeatureProcessor overrides ...
void Activate() override;
void Deactivate() override;
void Simulate(const FeatureProcessor::SimulatePacket& packet) override;
void Render(const FeatureProcessor::RenderPacket& packet) override;
void OnRenderEnd() override;
// RPI::SceneNotificationBus overrides ...
void OnRenderPipelineAdded(RPI::RenderPipelinePtr pipeline) override;
void OnRenderPipelineRemoved(RPI::RenderPipeline* pipeline) override;
void OnRenderPipelinePassesChanged(RPI::RenderPipeline* renderPipeline) override;
void OnBeginPrepareRender() override;
void OnEndPrepareRender() override;
SkinnedMeshRenderProxyHandle AcquireRenderProxy(const SkinnedMeshRenderProxyDesc& desc);
bool ReleaseRenderProxy(SkinnedMeshRenderProxyHandle& handle);
RPI::Ptr<SkinnedMeshComputePass> GetSkinningPass() const;
RPI::Ptr<MorphTargetComputePass> GetMorphTargetPass() const;
Data::Instance<RPI::Shader> GetSkinningShader() const;
RPI::ShaderOptionGroup CreateSkinningShaderOptionGroup(const SkinnedMeshShaderOptions shaderOptions, SkinnedMeshShaderOptionNotificationBus::Handler& shaderReinitializedHandler);
void OnSkinningShaderReinitialized(const Data::Instance<RPI::Shader> skinningShader);
void SubmitSkinningDispatchItems(RHI::CommandList* commandList);
Data::Instance<RPI::Shader> GetMorphTargetShader() const;
void SubmitMorphTargetDispatchItems(RHI::CommandList* commandList);
private:
AZ_DISABLE_COPY_MOVE(SkinnedMeshFeatureProcessor);
void InitSkinningAndMorphPass();
void InitSkinningAndMorphPass(const RPI::Ptr<RPI::ParentPass> pipelineRootPass);
SkinnedMeshRenderProxyInterfaceHandle AcquireRenderProxyInterface(const SkinnedMeshRenderProxyDesc& desc) override;
bool ReleaseRenderProxyInterface(SkinnedMeshRenderProxyInterfaceHandle& handle) override;
static const char* s_featureProcessorName;
RPI::Ptr<SkinnedMeshComputePass> m_skinningPass;
RPI::Ptr<MorphTargetComputePass> m_morphTargetPass;
Data::Instance<RPI::Shader> m_skinningShader;
CachedSkinnedMeshShaderOptions m_cachedSkinningShaderOptions;
Data::Instance<RPI::Shader> m_morphTargetShader;
AZStd::concurrency_checker m_renderProxiesChecker;
StableDynamicArray<SkinnedMeshRenderProxy> m_renderProxies;
AZStd::unique_ptr<SkinnedMeshStatsCollector> m_statsCollector;
MeshFeatureProcessor* m_meshFeatureProcessor = nullptr;
AZStd::unordered_set<const RHI::DispatchItem*> m_skinningDispatches;
AZStd::unordered_set<const RHI::DispatchItem*> m_morphTargetDispatches;
AZStd::mutex m_dispatchItemMutex;
};
} // namespace Render

@ -60,13 +60,7 @@ namespace AZ
bool SkinnedMeshRenderProxy::BuildDispatchItem([[maybe_unused]] const RPI::Scene& scene, size_t modelLodIndex, [[maybe_unused]] const SkinnedMeshShaderOptions& shaderOptions)
{
if (!m_featureProcessor->GetSkinningPass())
{
AZ_Error("Skinned Mesh Feature Processor", false, "Failed to get Skinning Pass. Make sure the project has a skinning pass.");
return false;
}
Data::Instance<RPI::Shader> skinningShader = m_featureProcessor->GetSkinningPass()->GetShader();
Data::Instance<RPI::Shader> skinningShader = m_featureProcessor->GetSkinningShader();
if (!skinningShader)
{
AZ_Error("Skinned Mesh Feature Processor", false, "Failed to get skinning shader from skinning pass");
@ -89,7 +83,7 @@ namespace AZ
m_instance->m_outputStreamOffsetsInBytes[modelLodIndex],
modelLodIndex, m_boneTransforms,
m_shaderOptions,
m_featureProcessor->GetSkinningPass(),
m_featureProcessor,
m_instance->m_morphTargetInstanceMetaData[modelLodIndex],
morphDeltaIntegerEncoding });
@ -100,7 +94,7 @@ namespace AZ
}
// Get the data needed to create a morph target dispatch item
Data::Instance<RPI::Shader> morphTargetShader = m_featureProcessor->GetMorphTargetPass()->GetShader();
Data::Instance<RPI::Shader> morphTargetShader = m_featureProcessor->GetMorphTargetShader();
const AZStd::vector<AZStd::intrusive_ptr<MorphTargetInputBuffers>>& morphTargetInputBuffersVector = m_inputBuffers->GetMorphTargetInputBuffers(modelLodIndex);
AZ_Assert(morphTargetMetaDatas.size() == morphTargetInputBuffersVector.size(), "Skinned Mesh Feature Processor - Mismatch in morph target metadata count and morph target input buffer count");
@ -118,7 +112,7 @@ namespace AZ
aznew MorphTargetDispatchItem{
morphTargetInputBuffersVector[morphTargetIndex],
morphTargetMetaDatas[morphTargetIndex],
m_featureProcessor->GetMorphTargetPass(),
m_featureProcessor,
m_instance->m_morphTargetInstanceMetaData[modelLodIndex],
morphDeltaIntegerEncoding });

@ -381,6 +381,7 @@ namespace AZ
uint64_t m_createdByPassRequest : 1;
uint64_t m_initialized : 1;
uint64_t m_enabled : 1;
uint64_t m_parentEnabled : 1;
uint64_t m_alreadyCreated : 1;
uint64_t m_alreadyReset : 1;
uint64_t m_alreadyPrepared : 1;

@ -15,6 +15,7 @@
#include <AzCore/Asset/AssetCommon.h>
#include <AzCore/std/containers/unordered_map.h>
#include <Atom/RPI.Reflect/Asset/AssetHandler.h>
#include <Atom/RPI.Reflect/Image/StreamingImageAsset.h>
namespace AZ::RPI
{
@ -56,6 +57,9 @@ namespace AZ::RPI
float m_minPositionDelta;
float m_maxPositionDelta;
//! Reference to the wrinkle mask, if it exists
AZ::Data::Asset<AZ::RPI::StreamingImageAsset> m_wrinkleMask;
//! Boolean to indicate the presence or absence of color deltas
bool m_hasColorDeltas = false;

@ -18,6 +18,9 @@
#include <SceneAPI/SceneCore/Containers/Views/PairIterator.h>
#include <SceneAPI/SceneCore/Containers/Views/SceneGraphDownwardsIterator.h>
#include <AzToolsFramework/API/EditorAssetSystemAPI.h>
#include <AzCore/Asset/AssetManagerBus.h>
namespace AZ::RPI
{
using namespace AZ::SceneAPI;
@ -114,7 +117,7 @@ namespace AZ::RPI
meshNodeName, sourceMesh.m_name.GetCStr());
const DataTypes::MatrixType globalTransform = Utilities::BuildWorldTransform(sceneGraph, sceneNodeIndex);
BuildMorphTargetMesh(vertexOffset, sourceMesh, productMesh, metaAssetCreator, blendShapeName, blendShapeData, globalTransform, coordSysConverter);
BuildMorphTargetMesh(vertexOffset, sourceMesh, productMesh, metaAssetCreator, blendShapeName, blendShapeData, globalTransform, coordSysConverter, scene.GetSourceFilename());
}
}
}
@ -157,7 +160,8 @@ namespace AZ::RPI
const AZStd::string& blendShapeName,
const AZStd::shared_ptr<const DataTypes::IBlendShapeData>& blendShapeData,
const DataTypes::MatrixType& globalTransform,
const AZ::SceneAPI::CoordinateSystemConverter& coordSysConverter)
const AZ::SceneAPI::CoordinateSystemConverter& coordSysConverter,
const AZStd::string& sourceSceneFilename)
{
const float tolerance = CalcPositionDeltaTolerance(sourceMesh);
AZ::Aabb deltaPositionAabb = AZ::Aabb::CreateNull();
@ -288,6 +292,8 @@ namespace AZ::RPI
metaData.m_maxPositionDelta = maxValue;
}
metaData.m_wrinkleMask = GetWrinkleMask(sourceSceneFilename, blendShapeName);
metaAssetCreator.AddMorphTarget(metaData);
AZ_Assert(uncompressedPositionDeltas.size() == compressedDeltas.size(), "Number of uncompressed (%d) and compressed position delta components (%d) do not match.",
@ -312,4 +318,47 @@ namespace AZ::RPI
AZ_Assert((packedCompressedMorphTargetVertexData.size() - metaData.m_startIndex) == numMorphedVertices, "Vertex index range (%d) in morph target meta data does not match number of morphed vertices (%d).",
packedCompressedMorphTargetVertexData.size() - metaData.m_startIndex, numMorphedVertices);
}
Data::Asset<RPI::StreamingImageAsset> MorphTargetExporter::GetWrinkleMask(const AZStd::string& sourceSceneFullFilePath, const AZStd::string& blendShapeName) const
{
AZ::Data::Asset<AZ::RPI::StreamingImageAsset> imageAsset;
// See if there is a wrinkle map mask for this mesh
AZStd::string sceneRelativeFilePath;
bool relativePathFound = true;
AzToolsFramework::AssetSystemRequestBus::BroadcastResult(relativePathFound, &AzToolsFramework::AssetSystemRequestBus::Events::GetRelativeProductPathFromFullSourceOrProductPath, sourceSceneFullFilePath, sceneRelativeFilePath);
if (relativePathFound)
{
AZ::StringFunc::Path::StripFullName(sceneRelativeFilePath);
// Get the folder the masks are supposed to be in
AZStd::string folderName;
AZ::StringFunc::Path::GetFileName(sourceSceneFullFilePath.c_str(), folderName);
folderName += "_wrinklemasks";
// Note: for now, we're assuming the mask is always authored as a .tif
AZStd::string blendMaskFileName = blendShapeName + "_wrinklemask.tif.streamingimage";
AZStd::string maskFolderAndFile;
AZ::StringFunc::Path::Join(folderName.c_str(), blendMaskFileName.c_str(), maskFolderAndFile);
AZStd::string maskRelativePath;
AZ::StringFunc::Path::Join(sceneRelativeFilePath.c_str(), maskFolderAndFile.c_str(), maskRelativePath);
AZ::StringFunc::Path::Normalize(maskRelativePath);
// Now see if the file exists
AZ::Data::AssetId maskAssetId;
Data::AssetCatalogRequestBus::BroadcastResult(maskAssetId, &Data::AssetCatalogRequests::GetAssetIdByPath, maskRelativePath.c_str(), AZ::Data::s_invalidAssetType, false);
if (maskAssetId.IsValid())
{
// Flush asset manager events to ensure no asset references are held by closures queued on Ebuses.
AZ::Data::AssetManager::Instance().DispatchEvents();
imageAsset.Create(maskAssetId, AZ::Data::AssetLoadBehavior::PreLoad, false);
}
}
return imageAsset;
}
} // namespace AZ::RPI

@ -64,7 +64,11 @@ namespace AZ
const AZStd::string& blendShapeName,
const AZStd::shared_ptr<const AZ::SceneAPI::DataTypes::IBlendShapeData>& blendShapeData,
const AZ::SceneAPI::DataTypes::MatrixType& globalTransform,
const AZ::SceneAPI::CoordinateSystemConverter& coordSysConverter);
const AZ::SceneAPI::CoordinateSystemConverter& coordSysConverter,
const AZStd::string& sourceSceneFilename);
// Find a wrinkle mask for this morph target, if it exists
Data::Asset<RPI::StreamingImageAsset> GetWrinkleMask(const AZStd::string& sourceSceneFullFilePath, const AZStd::string& blendShapeName) const;
};
} // namespace RPI
} // namespace AZ

@ -422,7 +422,7 @@ namespace AZ
const MaterialPropertyDescriptor* propertyDescriptor = materialTypeAssetCreator.GetMaterialPropertiesLayout()->GetPropertyDescriptor(propertyIndex);
AZ::Name enumName = AZ::Name(property.m_value.GetValue<AZStd::string>());
uint32_t enumValue = propertyDescriptor->GetEnumValue(enumName);
uint32_t enumValue = propertyDescriptor ? propertyDescriptor->GetEnumValue(enumName) : MaterialPropertyDescriptor::InvalidEnumValue;
if (enumValue == MaterialPropertyDescriptor::InvalidEnumValue)
{
materialTypeAssetCreator.ReportError("Enum value '%s' couldn't be found in the 'enumValues' list", enumName.GetCStr());

@ -93,11 +93,12 @@ namespace AZ
void Pass::SetEnabled(bool enabled)
{
m_flags.m_enabled = enabled;
OnHierarchyChange();
}
bool Pass::IsEnabled() const
{
return m_flags.m_enabled;
return m_flags.m_enabled && (m_flags.m_parentEnabled || m_parent == nullptr);
}
// --- Error Logging ---
@ -140,6 +141,7 @@ namespace AZ
}
// Set new tree depth and path
m_flags.m_parentEnabled = m_parent->m_flags.m_enabled && (m_parent->m_flags.m_parentEnabled || m_parent->m_parent == nullptr);
m_treeDepth = m_parent->m_treeDepth + 1;
m_path = ConcatPassName(m_parent->m_path, m_name);
m_flags.m_partOfHierarchy = m_parent->m_flags.m_partOfHierarchy;

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save