Merge branch 'development' of https://github.com/o3de/o3de into jckand/DynVegXfailFixes

This commit is contained in:
jckand-amzn
2021-07-20 17:19:15 -05:00
106 changed files with 13836 additions and 13958 deletions
@@ -8,58 +8,43 @@ SPDX-License-Identifier: Apache-2.0 OR MIT
import logging
import os
import pytest
import time
import typing
from datetime import datetime
import ly_test_tools.log.log_monitor
# fixture imports
from AWS.Windows.resource_mappings.resource_mappings import resource_mappings
from assetpipeline.ap_fixtures.asset_processor_fixture import asset_processor
from .aws_metrics_utils import aws_metrics_utils
from .aws_metrics_custom_thread import AWSMetricsThread
AWS_METRICS_FEATURE_NAME = 'AWSMetrics'
GAME_LOG_NAME = 'Game.log'
CONTEXT_VARIABLE = ['-c', 'batch_processing=true']
logger = logging.getLogger(__name__)
def setup(launcher: ly_test_tools.launchers.Launcher,
cdk: pytest.fixture,
asset_processor: asset_processor,
resource_mappings: resource_mappings,
context_variable: str = '') -> typing.Tuple[ly_test_tools.log.log_monitor.LogMonitor, str, str]:
def setup(launcher: pytest.fixture,
asset_processor: pytest.fixture) -> pytest.fixture:
"""
Set up the CDK application and start the log monitor.
Set up the resource mapping configuration and start the log monitor.
:param launcher: Client launcher for running the test level.
:param cdk: CDK application for deploying the AWS resources.
:param asset_processor: asset_processor fixture.
:param resource_mappings: resource_mappings fixture.
:param context_variable: context_variable for enable optional CDK feature.
:return log monitor object, metrics file path and the metrics stack name.
"""
logger.info(f'Cdk stack names:\n{cdk.list()}')
stacks = cdk.deploy(context_variable=context_variable)
resource_mappings.populate_output_keys(stacks)
asset_processor.start()
asset_processor.wait_for_idle()
metrics_file_path = os.path.join(launcher.workspace.paths.project(), 'user',
AWS_METRICS_FEATURE_NAME, 'metrics.json')
remove_file(metrics_file_path)
file_to_monitor = os.path.join(launcher.workspace.paths.project_log(), GAME_LOG_NAME)
remove_file(file_to_monitor)
# Initialize the log monitor.
log_monitor = ly_test_tools.log.log_monitor.LogMonitor(launcher=launcher, log_file_path=file_to_monitor)
return log_monitor, metrics_file_path, stacks[0]
return log_monitor
def monitor_metrics_submission(log_monitor: ly_test_tools.log.log_monitor.LogMonitor) -> None:
def monitor_metrics_submission(log_monitor: pytest.fixture) -> None:
"""
Monitor the messages and notifications for submitting metrics.
:param log_monitor: Log monitor to check the log messages.
@@ -67,6 +52,7 @@ def monitor_metrics_submission(log_monitor: ly_test_tools.log.log_monitor.LogMon
expected_lines = [
'(Script) - Submitted metrics without buffer.',
'(Script) - Submitted metrics with buffer.',
'(Script) - Flushed the buffered metrics.',
'(Script) - Metrics is sent successfully.'
]
@@ -87,87 +73,132 @@ def monitor_metrics_submission(log_monitor: ly_test_tools.log.log_monitor.LogMon
f'unexpected_lines values: {unexpected_lines}')
def remove_file(file_path: str) -> None:
def query_metrics_from_s3(aws_metrics_utils: pytest.fixture, stack_name: str) -> None:
"""
Remove a local file and its directory.
:param file_path: Path to the local file.
Verify that the metrics events are delivered to the S3 bucket and can be queried.
aws_metrics_utils: aws_metrics_utils fixture.
stack_name: name of the CloudFormation stack.
"""
if os.path.exists(file_path):
os.remove(file_path)
analytics_bucket_name = aws_metrics_utils.get_analytics_bucket_name(stack_name)
aws_metrics_utils.verify_s3_delivery(analytics_bucket_name)
logger.info('Metrics are sent to S3.')
file_dir = os.path.dirname(file_path)
if os.path.exists(file_dir) and len(os.listdir(file_dir)) == 0:
os.rmdir(file_dir)
aws_metrics_utils.run_glue_crawler(f'{stack_name}-EventsCrawler')
aws_metrics_utils.run_named_queries(f'{stack_name}-AthenaWorkGroup')
logger.info('Query metrics from S3 successfully.')
# Empty the S3 bucket. S3 buckets can only be deleted successfully when it doesn't contain any object.
aws_metrics_utils.empty_batch_analytics_bucket(analytics_bucket_name)
def verify_operational_metrics(aws_metrics_utils: pytest.fixture, stack_name: str, start_time: datetime) -> None:
"""
Verify that operational health metrics are delivered to CloudWatch.
aws_metrics_utils: aws_metrics_utils fixture.
stack_name: name of the CloudFormation stack.
start_time: Time when the game launcher starts.
"""
aws_metrics_utils.verify_cloud_watch_delivery(
'AWS/Lambda',
'Invocations',
[{'Name': 'FunctionName',
'Value': f'{stack_name}-AnalyticsProcessingLambdaName'}],
start_time)
logger.info('AnalyticsProcessingLambda metrics are sent to CloudWatch.')
aws_metrics_utils.verify_cloud_watch_delivery(
'AWS/Lambda',
'Invocations',
[{'Name': 'FunctionName',
'Value': f'{stack_name}-EventsProcessingLambda'}],
start_time)
logger.info('EventsProcessingLambda metrics are sent to CloudWatch.')
def start_kinesis_analytics_application(aws_metrics_utils: pytest.fixture, stack_name: str) -> None:
"""
Start the Kinesis analytics application for real-time analytics.
aws_metrics_utils: aws_metrics_utils fixture.
stack_name: name of the CloudFormation stack.
"""
analytics_application_name = f'{stack_name}-AnalyticsApplication'
aws_metrics_utils.start_kinesis_data_analytics_application(analytics_application_name)
@pytest.mark.SUITE_periodic
@pytest.mark.usefixtures('automatic_process_killer')
@pytest.mark.parametrize('project', ['AutomatedTesting'])
@pytest.mark.parametrize('level', ['AWS/Metrics'])
@pytest.mark.parametrize('feature_name', [AWS_METRICS_FEATURE_NAME])
@pytest.mark.usefixtures('resource_mappings')
@pytest.mark.parametrize('resource_mappings_filename', ['default_aws_resource_mappings.json'])
@pytest.mark.usefixtures('aws_credentials')
@pytest.mark.parametrize('profile_name', ['AWSAutomationTest'])
@pytest.mark.parametrize('region_name', ['us-west-2'])
@pytest.mark.parametrize('assume_role_arn', ['arn:aws:iam::645075835648:role/o3de-automation-tests'])
@pytest.mark.usefixtures('cdk')
@pytest.mark.parametrize('session_name', ['o3de-Automation-session'])
@pytest.mark.parametrize('deployment_params', [CONTEXT_VARIABLE])
class TestAWSMetricsWindows(object):
def test_realtime_analytics_metrics_sent_to_cloudwatch(self,
level: str,
launcher: ly_test_tools.launchers.Launcher,
asset_processor: pytest.fixture,
workspace: pytest.fixture,
aws_utils: pytest.fixture,
aws_credentials: pytest.fixture,
resource_mappings: pytest.fixture,
cdk: pytest.fixture,
aws_metrics_utils: aws_metrics_utils,
):
"""
Tests that the submitted metrics are sent to CloudWatch for real-time analytics.
"""
log_monitor, metrics_file_path, stack_name = setup(launcher, cdk, asset_processor, resource_mappings)
"""
Test class to verify the real-time and batch analytics for metrics.
"""
# Start the Kinesis Data Analytics application for real-time analytics.
analytics_application_name = f'{stack_name}-AnalyticsApplication'
aws_metrics_utils.start_kinesis_data_analytics_application(analytics_application_name)
@pytest.mark.parametrize('destroy_stacks_on_teardown', [False])
def test_realtime_and_batch_analytics(self,
level: str,
launcher: pytest.fixture,
asset_processor: pytest.fixture,
workspace: pytest.fixture,
aws_utils: pytest.fixture,
cdk: pytest.fixture,
aws_metrics_utils: pytest.fixture):
"""
Verify that the metrics events are sent to CloudWatch and S3 for analytics.
"""
# Start Kinesis analytics application on a separate thread to avoid blocking the test.
kinesis_analytics_application_thread = AWSMetricsThread(target=start_kinesis_analytics_application,
args=(aws_metrics_utils, cdk.stacks[0]))
kinesis_analytics_application_thread.start()
log_monitor = setup(launcher, asset_processor)
# Kinesis analytics application needs to be in the running state before we start the game launcher.
kinesis_analytics_application_thread.join()
launcher.args = ['+LoadLevel', level]
launcher.args.extend(['-rhi=null'])
start_time = datetime.utcnow()
with launcher.start(launch_ap=False):
start_time = datetime.utcnow()
monitor_metrics_submission(log_monitor)
# Verify that operational health metrics are delivered to CloudWatch.
aws_metrics_utils.verify_cloud_watch_delivery(
'AWS/Lambda',
'Invocations',
[{'Name': 'FunctionName',
'Value': f'{stack_name}-AnalyticsProcessingLambdaName'}],
start_time)
logger.info('Operational health metrics sent to CloudWatch.')
# Verify that real-time analytics metrics are delivered to CloudWatch.
aws_metrics_utils.verify_cloud_watch_delivery(
AWS_METRICS_FEATURE_NAME,
'TotalLogins',
[],
start_time)
logger.info('Real-time metrics sent to CloudWatch.')
logger.info('Real-time metrics are sent to CloudWatch.')
# Stop the Kinesis Data Analytics application.
aws_metrics_utils.stop_kinesis_data_analytics_application(analytics_application_name)
# Run time-consuming verifications on separate threads to avoid blocking the test.
verification_threads = list()
verification_threads.append(
AWSMetricsThread(target=query_metrics_from_s3, args=(aws_metrics_utils, cdk.stacks[0])))
verification_threads.append(
AWSMetricsThread(target=verify_operational_metrics, args=(aws_metrics_utils, cdk.stacks[0], start_time)))
for thread in verification_threads:
thread.start()
for thread in verification_threads:
thread.join()
@pytest.mark.parametrize('destroy_stacks_on_teardown', [True])
def test_unauthorized_user_request_rejected(self,
level: str,
launcher: ly_test_tools.launchers.Launcher,
cdk: pytest.fixture,
aws_credentials: pytest.fixture,
launcher: pytest.fixture,
asset_processor: pytest.fixture,
resource_mappings: pytest.fixture,
workspace: pytest.fixture):
"""
Tests that unauthorized users cannot send metrics events to the AWS backed backend.
Verify that unauthorized users cannot send metrics events to the AWS backed backend.
"""
log_monitor, metrics_file_path, stack_name = setup(launcher, cdk, asset_processor, resource_mappings)
log_monitor = setup(launcher, asset_processor)
# Set invalid AWS credentials.
launcher.args = ['+LoadLevel', level, '+cl_awsAccessKey', 'AKIAIOSFODNN7EXAMPLE',
'+cl_awsSecretKey', 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY']
@@ -180,51 +211,3 @@ class TestAWSMetricsWindows(object):
halt_on_unexpected=True)
assert result, 'Metrics events are sent successfully by unauthorized user'
logger.info('Unauthorized user is rejected to send metrics.')
def test_batch_analytics_metrics_delivered_to_s3(self,
level: str,
launcher: ly_test_tools.launchers.Launcher,
cdk: pytest.fixture,
aws_credentials: pytest.fixture,
asset_processor: pytest.fixture,
resource_mappings: pytest.fixture,
aws_utils: pytest.fixture,
aws_metrics_utils: aws_metrics_utils,
workspace: pytest.fixture):
"""
Tests that the submitted metrics are sent to the data lake for batch analytics.
"""
log_monitor, metrics_file_path, stack_name = setup(launcher, cdk, asset_processor, resource_mappings,
context_variable='batch_processing=true')
analytics_bucket_name = aws_metrics_utils.get_analytics_bucket_name(stack_name)
launcher.args = ['+LoadLevel', level]
launcher.args.extend(['-rhi=null'])
with launcher.start(launch_ap=False):
start_time = datetime.utcnow()
monitor_metrics_submission(log_monitor)
# Verify that operational health metrics are delivered to CloudWatch.
aws_metrics_utils.verify_cloud_watch_delivery(
'AWS/Lambda',
'Invocations',
[{'Name': 'FunctionName',
'Value': f'{stack_name}-EventsProcessingLambda'}],
start_time)
logger.info('Operational health metrics sent to CloudWatch.')
aws_metrics_utils.verify_s3_delivery(analytics_bucket_name)
logger.info('Metrics sent to S3.')
# Run the glue crawler to populate the AWS Glue Data Catalog with tables.
aws_metrics_utils.run_glue_crawler(f'{stack_name}-EventsCrawler')
# Run named queries on the table to verify the batch analytics.
aws_metrics_utils.run_named_queries(f'{stack_name}-AthenaWorkGroup')
logger.info('Query metrics from S3 successfully.')
# Kinesis Data Firehose buffers incoming data before it delivers it to Amazon S3. Sleep for the
# default interval (60s) to make sure that all the metrics are sent to the bucket before cleanup.
time.sleep(60)
# Empty the S3 bucket. S3 buckets can only be deleted successfully when it doesn't contain any object.
aws_metrics_utils.empty_s3_bucket(analytics_bucket_name)
@@ -0,0 +1,29 @@
"""
Copyright (c) Contributors to the Open 3D Engine Project.
For complete copyright and license terms please see the LICENSE at the root of this distribution.
SPDX-License-Identifier: Apache-2.0 OR MIT
"""
from threading import Thread
class AWSMetricsThread(Thread):
"""
Custom thread for raising assertion errors on the main thread.
"""
def __init__(self, **kwargs):
super().__init__(**kwargs)
self._error = None
def run(self) -> None:
try:
super().run()
except AssertionError as e:
self._error = e
def join(self, **kwargs) -> None:
super().join(**kwargs)
if self._error:
raise AssertionError(self._error)
@@ -13,7 +13,6 @@ import typing
from datetime import datetime
from botocore.exceptions import WaiterError
from AWS.common.aws_utils import AwsUtils
from .aws_metrics_waiters import KinesisAnalyticsApplicationUpdatedWaiter, \
CloudWatchMetricsDeliveredWaiter, DataLakeMetricsDeliveredWaiter, GlueCrawlerReadyWaiter
@@ -29,7 +28,7 @@ class AWSMetricsUtils:
Provide utils functions for the AWSMetrics gem to interact with the deployed resources.
"""
def __init__(self, aws_utils: AwsUtils):
def __init__(self, aws_utils: pytest.fixture):
self._aws_util = aws_utils
def start_kinesis_data_analytics_application(self, application_name: str) -> None:
@@ -199,14 +198,13 @@ class AWSMetricsUtils:
assert state == 'SUCCEEDED', f'Failed to run the named query {named_query.get("Name", {})}'
def empty_s3_bucket(self, bucket_name: str) -> None:
def empty_batch_analytics_bucket(self, bucket_name: str) -> None:
"""
Empty the S3 bucket following:
https://boto3.amazonaws.com/v1/documentation/api/latest/guide/migrations3.html
:param bucket_name: Name of the S3 bucket.
"""
s3 = self._aws_util.resource('s3')
bucket = s3.Bucket(bucket_name)
@@ -72,15 +72,14 @@ class Cdk:
f'\nError:{error.stderr}')
def setup(self, cdk_path: str, project: str, account_id: str,
workspace: pytest.fixture, session: boto3.session.Session, bootstrap_required: bool):
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.
:param workspace: bootstrap_required deploys bootstrap stack.
:param session: Current boto3 session, provides credentials and region.
"""
self._cdk_env = os.environ.copy()
unique_id = uuid.uuid4().hex[-4:]
self._cdk_env['O3DE_AWS_PROJECT_NAME'] = project[:4] + unique_id if len(project) > 4 else project + unique_id
@@ -104,8 +103,7 @@ class Cdk:
logger.info(f'Installing cdk python dependencies: {output}')
if bootstrap_required:
self.bootstrap()
self.bootstrap()
def bootstrap(self) -> None:
"""
@@ -124,15 +122,19 @@ class Cdk:
logger.warning(f'Failed creating Bootstrap stack {BOOTSTRAP_STACK_NAME} not found. '
f'\nError:{clientError["Error"]["Message"]}')
def list(self) -> List[str]:
def list(self, deployment_params: List[str] = None) -> List[str]:
"""
lists cdk stack names
:return List of cdk stack names
lists cdk stack names.
:param deployment_params: Deployment parameters like --all can be passed in this way.
:return List of cdk stack names.
"""
if not self._cdk_path:
return []
list_cdk_application_cmd = ['cdk', 'list']
if deployment_params:
list_cdk_application_cmd.extend(deployment_params)
output = process_utils.check_output(
list_cdk_application_cmd,
cwd=self._cdk_path,
@@ -141,36 +143,36 @@ class Cdk:
return output.splitlines()
def synthesize(self) -> None:
def synthesize(self, deployment_params: List[str] = None) -> None:
"""
Synthesizes all cdk stacks
Synthesizes all cdk stacks.
:param deployment_params: Deployment parameters like --all can be passed in this way.
"""
if not self._cdk_path:
return
list_cdk_application_cmd = ['cdk', 'synth']
synth_cdk_application_cmd = ['cdk', 'synth']
if deployment_params:
synth_cdk_application_cmd.extend(deployment_params)
process_utils.check_output(
list_cdk_application_cmd,
synth_cdk_application_cmd,
cwd=self._cdk_path,
env=self._cdk_env,
shell=True)
def deploy(self, context_variable: str = '', additonal_params: List[str] = None) -> List[str]:
def deploy(self, deployment_params: List[str] = None) -> List[str]:
"""
Deploys all the CDK stacks.
:param context_variable: Context variable for enabling optional features.
:param additonal_params: Additonal parameters like --all can be passed in this way.
:param deployment_params: Deployment parameters like --all can be passed in this way.
:return List of deployed stack arns.
"""
if not self._cdk_path:
return []
deploy_cdk_application_cmd = ['cdk', 'deploy', '--require-approval', 'never']
if additonal_params:
deploy_cdk_application_cmd.extend(additonal_params)
if context_variable:
deploy_cdk_application_cmd.extend(['-c', f'{context_variable}'])
if deployment_params:
deploy_cdk_application_cmd.extend(deployment_params)
output = process_utils.check_output(
deploy_cdk_application_cmd,
@@ -178,21 +180,23 @@ class Cdk:
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])
self._stacks.append(line.split('/')[-2])
return stacks
return self._stacks
def destroy(self) -> None:
def destroy(self, deployment_params: List[str] = None) -> None:
"""
Destroys the cdk application.
:param deployment_params: Deployment parameters like --all can be passed in this way.
"""
logger.info(f'CDK Path {self._cdk_path}')
destroy_cdk_application_cmd = ['cdk', 'destroy', '--all', '-f']
destroy_cdk_application_cmd = ['cdk', 'destroy', '-f']
if deployment_params:
destroy_cdk_application_cmd.extend(deployment_params)
try:
process_utils.check_output(
@@ -238,3 +242,7 @@ class Cdk:
# self._session.client('cloudformation').delete_stack(
# StackName=BOOTSTRAP_STACK_NAME
# )
@property
def stacks(self):
return self._stacks
@@ -10,10 +10,7 @@ import logging
import ly_test_tools.log.log_monitor
# fixture imports
from AWS.Windows.resource_mappings.resource_mappings import resource_mappings
from AWS.Windows.cdk.cdk_utils import Cdk
from AWS.common.aws_utils import AwsUtils
from assetpipeline.ap_fixtures.asset_processor_fixture import asset_processor as asset_processor
from assetpipeline.ap_fixtures.asset_processor_fixture import asset_processor
AWS_PROJECT_NAME = 'AWS-AutomationTest'
AWS_CLIENT_AUTH_FEATURE_NAME = 'AWSClientAuth'
@@ -37,11 +34,47 @@ logger = logging.getLogger(__name__)
@pytest.mark.parametrize('region_name', ['us-west-2'])
@pytest.mark.parametrize('assume_role_arn', ['arn:aws:iam::645075835648:role/o3de-automation-tests'])
@pytest.mark.parametrize('session_name', ['o3de-Automation-session'])
class TestAWSClientAuthPasswordSignIn(object):
@pytest.mark.usefixtures('cdk')
@pytest.mark.parametrize('deployment_params', [[]])
class TestAWSClientAuthWindows(object):
"""
Test class to verify AWS Cognito IDP Password sign in and Cognito Identity pool authenticated authorization.
Test class to verify AWS Client Auth gem features on Windows.
"""
@pytest.mark.parametrize('level', ['AWS/ClientAuth'])
@pytest.mark.parametrize('destroy_stacks_on_teardown', [False])
def test_anonymous_credentials(self,
level: str,
launcher: pytest.fixture,
resource_mappings: pytest.fixture,
workspace: pytest.fixture,
asset_processor: pytest.fixture
):
"""
Test to verify AWS Cognito Identity pool anonymous authorization.
Setup: Deploys cdk and updates resource mapping file.
Tests: Getting credentials when no credentials are configured
Verification: Log monitor looks for success credentials log.
"""
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]
launcher.args.extend(['-rhi=null'])
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.'
@pytest.mark.parametrize('destroy_stacks_on_teardown', [True])
def test_password_signin_credentials(self,
launcher: pytest.fixture,
cdk: pytest.fixture,
@@ -51,13 +84,12 @@ class TestAWSClientAuthPasswordSignIn(object):
aws_utils: pytest.fixture
):
"""
Test to verify AWS Cognito IDP Password sign in and Cognito Identity pool authenticated authorization.
Setup: Deploys cdk and updates resource mapping file.
Tests: Sign up new test user, admin confirm the user, sign in and get aws credentials.
Verification: Log monitor looks for success credentials log.
"""
logger.info(f'Cdk stack names:\n{cdk.list()}')
stacks = cdk.deploy()
resource_mappings.populate_output_keys(stacks)
asset_processor.start()
asset_processor.wait_for_idle()
@@ -1,77 +0,0 @@
"""
Copyright (c) Contributors to the Open 3D Engine Project.
For complete copyright and license terms please see the LICENSE at the root of this distribution.
SPDX-License-Identifier: Apache-2.0 OR MIT
"""
import pytest
import os
import logging
import ly_test_tools.log.log_monitor
# fixture imports
from AWS.Windows.resource_mappings.resource_mappings import resource_mappings
from AWS.Windows.cdk.cdk_utils import Cdk
from AWS.common.aws_utils import AwsUtils
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', ['default_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]
launcher.args.extend(['-rhi=null'])
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.'
@@ -7,7 +7,8 @@ SPDX-License-Identifier: Apache-2.0 OR MIT
import os
import logging
import time
import typing
import shutil
import pytest
import ly_test_tools
@@ -16,7 +17,6 @@ import ly_test_tools.environment.process_utils as process_utils
import ly_test_tools.o3de.asset_processor_utils as asset_processor_utils
from botocore.exceptions import ClientError
from AWS.Windows.resource_mappings.resource_mappings import resource_mappings
from assetpipeline.ap_fixtures.asset_processor_fixture import asset_processor
AWS_CORE_FEATURE_NAME = 'AWSCore'
@@ -29,18 +29,49 @@ GAME_LOG_NAME = 'Game.log'
logger = logging.getLogger(__name__)
def setup(launcher: pytest.fixture, cdk: pytest.fixture, resource_mappings: pytest.fixture, asset_processor: pytest.fixture):
def setup(launcher: pytest.fixture, asset_processor: pytest.fixture) -> typing.Tuple[pytest.fixture, str]:
"""
Set up the resource mapping configuration and start the log monitor.
:param launcher: Client launcher for running the test level.
:param asset_processor: asset_processor fixture.
:return log monitor object, metrics file path and the metrics stack name.
"""
# Create the temporary directory for downloading test file from S3.
user_dir = os.path.join(launcher.workspace.paths.project(), 'user')
s3_download_dir = os.path.join(user_dir, 's3_download')
if not os.path.exists(s3_download_dir):
os.makedirs(s3_download_dir)
asset_processor_utils.kill_asset_processor()
logger.info(f'Cdk stack names:\n{cdk.list()}')
stacks = cdk.deploy(additonal_params=['--all'])
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)
return log_monitor
return log_monitor, s3_download_dir
def write_test_data_to_dynamodb_table(resource_mappings: pytest.fixture, aws_utils: pytest.fixture) -> None:
"""
Write test data to the DynamoDB table created by the CDK application.
:param resource_mappings: resource_mappings fixture.
:param aws_utils: aws_utils fixture.
"""
table_name = resource_mappings.get_resource_name_id("AWSCore.ExampleDynamoTableOutput")
try:
aws_utils.client('dynamodb').put_item(
TableName=table_name,
Item={
'id': {
'S': 'Item1'
}
}
)
logger.info(f'Loaded data into table {table_name}')
except ClientError:
logger.exception(f'Failed to load data into table {table_name}')
raise
@pytest.mark.SUITE_periodic
@@ -58,130 +89,57 @@ def setup(launcher: pytest.fixture, cdk: pytest.fixture, resource_mappings: pyte
@pytest.mark.parametrize('resource_mappings_filename', [AWS_RESOURCE_MAPPING_FILE_NAME])
@pytest.mark.usefixtures('aws_credentials')
@pytest.mark.parametrize('profile_name', ['AWSAutomationTest'])
@pytest.mark.usefixtures('cdk')
@pytest.mark.parametrize('deployment_params', [['--all']])
@pytest.mark.parametrize('destroy_stacks_on_teardown', [True])
class TestAWSCoreAWSResourceInteraction(object):
"""
Test class to verify AWSCore can downloading a file from S3.
Test class to verify the scripting behavior for the AWSCore gem.
"""
def test_download_from_s3(self,
level: str,
launcher: pytest.fixture,
cdk: pytest.fixture,
workspace: pytest.fixture,
asset_processor: pytest.fixture,
resource_mappings: pytest.fixture
):
@pytest.mark.parametrize('expected_lines', [
['(Script) - [S3] Head object request is done',
'(Script) - [S3] Head object success: Object example.txt is found.',
'(Script) - [S3] Get object success: Object example.txt is downloaded.',
'(Script) - [Lambda] Completed Invoke',
'(Script) - [Lambda] Invoke success: {"statusCode": 200, "body": {}}',
'(Script) - [DynamoDB] Results finished']])
@pytest.mark.parametrize('unexpected_lines', [
['(Script) - [S3] Head object error: No response body.',
'(Script) - [S3] Get object error: Request validation failed, output file directory doesn\'t exist.',
'(Script) - Request validation failed, output file miss full path.',
'(Script) - ']])
def test_scripting_behavior(self,
level: str,
launcher: pytest.fixture,
workspace: pytest.fixture,
asset_processor: pytest.fixture,
resource_mappings: pytest.fixture,
aws_utils: pytest.fixture,
expected_lines: typing.List[str],
unexpected_lines: typing.List[str]):
"""
Setup: Deploys cdk and updates resource mapping file.
Tests: Getting AWS credentials for no signed in user.
Verification: Log monitor looks for success download. The existence and contents of the file are also verified.
Tests: Interact with AWS S3, DynamoDB and Lambda services.
Verification: Script canvas nodes can communicate with AWS services successfully.
"""
log_monitor = setup(launcher, cdk, resource_mappings, asset_processor)
log_monitor, s3_download_dir = setup(launcher, asset_processor)
write_test_data_to_dynamodb_table(resource_mappings, aws_utils)
launcher.args = ['+LoadLevel', level]
launcher.args.extend(['-rhi=null'])
user_dir = os.path.join(workspace.paths.project(), 'user')
download_dir = os.path.join(user_dir, 's3_download')
if not os.path.exists(download_dir):
os.makedirs(download_dir)
with launcher.start(launch_ap=False):
result = log_monitor.monitor_log_for_lines(
expected_lines=['(Script) - [S3] Head object request is done',
'(Script) - [S3] Head object success: Object example.txt is found.',
'(Script) - [S3] Get object success: Object example.txt is downloaded.'],
unexpected_lines=['(Script) - [S3] Head object error: No response body.',
'(Script) - [S3] Get object error: Request validation failed, output file directory doesn\'t exist.'],
expected_lines=expected_lines,
unexpected_lines=unexpected_lines,
halt_on_unexpected=True
)
assert result, "Expected lines weren't found."
download_path = os.path.join(download_dir, 'output.txt')
file_was_downloaded = os.path.exists(download_path)
assert os.path.exists(os.path.join(s3_download_dir, 'output.txt')), \
'The expected file wasn\'t successfully downloaded.'
# clean up the file directories.
if file_was_downloaded:
os.remove(download_path)
os.rmdir(download_dir)
assert file_was_downloaded, 'The expected file wasn\'t successfully downloaded'
def test_invoke_lambda(self,
level: str,
launcher: pytest.fixture,
cdk: pytest.fixture,
resource_mappings: pytest.fixture,
workspace: pytest.fixture,
asset_processor: pytest.fixture
):
"""
Setup: Deploys the CDK.
Tests: Runs the test level.
Verification: Searches the logs for the expected output from the example lambda.
"""
log_monitor = setup(launcher, cdk, resource_mappings, asset_processor)
launcher.args = ['+LoadLevel', level]
launcher.args.extend(['-rhi=null'])
with launcher.start(launch_ap=False):
result = log_monitor.monitor_log_for_lines(
expected_lines=['(Script) - [Lambda] Completed Invoke',
'(Script) - [Lambda] Invoke success: {"statusCode": 200, "body": {}}'],
unexpected_lines=['(Script) - Request validation failed, output file miss full path.',
'(Script) - '],
halt_on_unexpected=True
)
assert result
def test_get_dynamodb_value(self,
level: str,
launcher: pytest.fixture,
cdk: pytest.fixture,
resource_mappings: pytest.fixture,
workspace: pytest.fixture,
asset_processor: pytest.fixture,
aws_utils: pytest.fixture,
):
"""
Setup: Deploys the CDK application
Test: Runs a launcher with a level that loads a scriptcanvas that pulls a DynamoDB table value.
Verification: The value is output in the logs and verified by the test.
"""
def write_test_table_data():
client = aws_utils.client('dynamodb')
table_name = resource_mappings.get_resource_name_id("AWSCore.ExampleDynamoTableOutput")
try:
client.put_item(
TableName=table_name,
Item={
'id': {
'S': 'Item1'
}
}
)
logger.info(f'Loaded data into table {table_name}')
except ClientError:
logger.exception(f'Failed to load data into table {table_name}')
raise
log_monitor = setup(launcher, cdk, resource_mappings, asset_processor)
write_test_table_data()
launcher.args = ['+LoadLevel', level]
launcher.args.extend(['-rhi=null'])
with launcher.start(launch_ap=False):
result = log_monitor.monitor_log_for_lines(
expected_lines=['(Script) - [DynamoDB] Results finished'],
unexpected_lines=['(Script) - Request validation failed, output file miss full path.',
'(Script) - '],
halt_on_unexpected=True
)
assert result
shutil.rmtree(s3_download_dir)
@@ -6,7 +6,6 @@ SPDX-License-Identifier: Apache-2.0 OR MIT
"""
import os
from os.path import abspath
import pytest
import json
import logging
@@ -107,38 +106,3 @@ class ResourceMappings:
def get_resource_name_id(self, resource_key: str):
return self._resource_mappings[AWS_RESOURCE_MAPPINGS_KEY][resource_key]['Name/ID']
@pytest.fixture(scope='function')
def resource_mappings(
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}'
logger.info(f'Resource mapping path : {path}')
logger.info(f'Resource mapping resolved path : {abspath(path)}')
resource_mappings_obj = ResourceMappings(abspath(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
@@ -4,11 +4,15 @@ For complete copyright and license terms please see the LICENSE at the root of t
SPDX-License-Identifier: Apache-2.0 OR MIT
"""
import pytest
import logging
from os.path import abspath
import pytest
import typing
from AWS.common.aws_utils import AwsUtils
from AWS.common.aws_credentials import AwsCredentials
from AWS.Windows.cdk.cdk_utils import Cdk
from AWS.Windows.resource_mappings.resource_mappings import ResourceMappings
logger = logging.getLogger(__name__)
@@ -42,6 +46,41 @@ def aws_utils(
pytest.cdk_obj = 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}'
logger.info(f'Resource mapping path : {path}')
logger.info(f'Resource mapping resolved path : {abspath(path)}')
resource_mappings_obj = ResourceMappings(abspath(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
@pytest.fixture(scope='function')
def cdk(
request: pytest.fixture,
@@ -49,8 +88,9 @@ def cdk(
feature_name: str,
workspace: pytest.fixture,
aws_utils: pytest.fixture,
bootstrap_required: bool = True,
destroy_stacks_on_teardown: bool = True) -> Cdk:
resource_mappings: pytest.fixture,
deployment_params: typing.List[str],
destroy_stacks_on_teardown: bool) -> Cdk:
"""
Fixture for setting up a Cdk
:param request: _pytest.fixtures.SubRequest class that handles getting
@@ -59,8 +99,8 @@ def cdk(
: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 bootstrap_required: Whether the bootstrap stack needs to be created to
provision resources the AWS CDK needs to perform the deployment.
:param resource_mappings: resource_mappings fixture.
:param deployment_params: Parameters for the CDK application deployment.
:param destroy_stacks_on_teardown: option to control calling destroy ot the end of test.
:return Cdk class object.
"""
@@ -70,22 +110,27 @@ def cdk(
if pytest.cdk_obj is None:
pytest.cdk_obj = Cdk()
pytest.cdk_obj.setup(cdk_path, project, aws_utils.assume_account_id(), workspace, aws_utils.assume_session())
stacks = pytest.cdk_obj.deploy(deployment_params=deployment_params)
logger.info(f'Cdk stack names:\n{stacks}')
resource_mappings.populate_output_keys(stacks)
pytest.cdk_obj.setup(cdk_path, project, aws_utils.assume_account_id(), workspace, aws_utils.assume_session(),
bootstrap_required)
def teardown():
if destroy_stacks_on_teardown:
pytest.cdk_obj.destroy()
pytest.cdk_obj.destroy(deployment_params=deployment_params)
# Enable after https://github.com/aws/aws-cdk/issues/986 is fixed.
# Until then clean the bootstrap bucket manually.
# cdk_obj.remove_bootstrap_stack()
# pytest.cdk_obj.remove_bootstrap_stack()
pytest.cdk_obj = None
request.addfinalizer(teardown)
return pytest.cdk_obj
@pytest.fixture(scope='function')
def aws_credentials(request: pytest.fixture, aws_utils: pytest.fixture, profile_name: str):
"""
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:f0f4d4e0155feaa76c80a14128000a0fd9570ab76e79f4847eaef9006324a4d2
size 9084
oid sha256:19f2c4454bb395cdc0a36d1e45e6a384bbd23037af1a2fb93e088ecfa0f10e5b
size 9343
File diff suppressed because it is too large Load Diff
@@ -1 +0,0 @@
<Environment />
@@ -1 +0,0 @@
<TimeOfDay Time="12"/>
@@ -1,6 +1,6 @@
<download name="ClientAuth" type="Map">
<index src="filelist.xml" dest="filelist.xml"/>
<files>
<file src="level.pak" dest="level.pak" size="ED0" md5="dbf5115226e4b0ea38ebdc3967ba3aa9"/>
<file src="level.pak" dest="level.pak" size="E1A" md5="fecbc160ebc2186184504482e3e2eba3"/>
</files>
</download>
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:da041115014f11696d5878d5c21247c17b8d694fa9674e30692259261a7223a2
size 3792
oid sha256:8a674e05824e5ceec13a0487b318923568710bc8269e5be84adad59c495a7ceb
size 3610
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:43b1a23b62fe2ffa05545ac99524f40b6fff49d6e35925b9d6138c00d8082e86
size 9073
oid sha256:a1c0b621525b8e88c3775ea4c60c2197d1e1b060ace9bad9d6efcb0532817e44
size 9356
File diff suppressed because it is too large Load Diff
@@ -1,6 +1,6 @@
<download name="ClientAuthPasswordSignIn" type="Map">
<index src="filelist.xml" dest="filelist.xml"/>
<files>
<file src="level.pak" dest="level.pak" size="DDF" md5="ebe91ae5f1ea1ec735b6650b14f3f95b"/>
<file src="level.pak" dest="level.pak" size="E53" md5="12728d49c7efe0c8e83e3651a1d13ca6"/>
</files>
</download>
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a58292341785cb260dc0ccf346259e35e2817ee48fc401a21ab528f6afb97b52
size 3551
oid sha256:f318a1787069385de291660f79e350cea2ca2c3ef3b5e0576686066bd9c49395
size 3667
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:f3d5121b26608b02747e245071ccff29ac57358cb6349ec9495a7a003ac12467
size 8942
oid sha256:afc5d665128738e6bea09e78a16ee38acc923a8ecefff90d987858ce72c395fa
size 9360
File diff suppressed because it is too large Load Diff
@@ -1,6 +1,6 @@
<download name="ClientAuthPasswordSignUp" type="Map">
<index src="filelist.xml" dest="filelist.xml"/>
<files>
<file src="level.pak" dest="level.pak" size="DDA" md5="aa6df891d505d6d9175beee4b55626db"/>
<file src="level.pak" dest="level.pak" size="E44" md5="acfaa325178533f135cd683d3782cc8e"/>
</files>
</download>
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:605391d415b828b100bada11d108099520c0b6a020f17588887b610475805d90
size 3546
oid sha256:87882b64688a77815d93c6973929fa21b89dc6c13d4866c710124ce2cd0f411e
size 3652
@@ -30,9 +30,6 @@ function metrics:OnSendMetricsFailure(requestId, errorMessage)
end
function metrics:OnDeactivate()
AWSMetricsRequestBus.Broadcast.FlushMetrics()
Debug.Log("Stop generating new test events and flushed the buffered metrics.")
self.tickBusHandler:Disconnect()
self.metricsNotificationHandler:Disconnect()
end
@@ -40,7 +37,7 @@ end
function metrics:OnTick(deltaTime, timePoint)
self.tickTime = self.tickTime + deltaTime
if self.tickTime > 2.0 then
if self.tickTime > 5.0 then
defaultAttribute = AWSMetrics_MetricsAttribute()
defaultAttribute:SetName("event_name")
defaultAttribute:SetStrValue("login")
@@ -57,6 +54,9 @@ function metrics:OnTick(deltaTime, timePoint)
if self.numSubmittedMetricsEvents % 2 == 0 then
if AWSMetricsRequestBus.Broadcast.SubmitMetrics(attributeList.attributes, 0, "lua", false) then
Debug.Log("Submitted metrics without buffer.")
AWSMetricsRequestBus.Broadcast.FlushMetrics()
Debug.Log("Flushed the buffered metrics.")
else
Debug.Log("Failed to Submit metrics without buffer.")
end
@@ -67,7 +67,7 @@ function metrics:OnTick(deltaTime, timePoint)
Debug.Log("Failed to Submit metrics with buffer.")
end
end
self.numSubmittedMetricsEvents = self.numSubmittedMetricsEvents + 1
self.tickTime = 0
end
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -8,7 +8,7 @@ To contribute, please review our [Code of Conduct](https://github.com/o3de/o3de/
## Making contributions with the Developer Certificate of Origin (DCO)
When contributing, your pull requests will require that you have agreed to our DCO found here: [Devloper Certificate of Origin](https://developercertificate.org/)
When contributing, your pull requests will require that you have agreed to our DCO found here: [Developer Certificate of Origin](https://developercertificate.org/). All commits require the --signoff flag to show DCO compliance.
You can do this by using the -s option in git.
Example: ```git commit -s -m 'my commit message'```
+2 -1
View File
@@ -9,11 +9,12 @@
#pragma once
#include <AzCore/base.h>
#include <AzCore/RTTI/TypeInfo.h>
#include <AzCore/Casting/numeric_cast.h>
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/std/containers/vector.h>
#include <AzCore/std/containers/stack.h>
#include <AzCore/std/function/function_fwd.h>
#include <AzCore/std/function/function_template.h>
namespace AZ
{
@@ -14,6 +14,7 @@
#include <AzCore/Math/Transform.h>
#include <AzCore/Math/Plane.h>
#include <AzCore/Math/SimdMath.h>
#include <AzCore/std/containers/array.h>
namespace AZ
{
@@ -13,9 +13,32 @@
#include <AzCore/Math/Obb.h>
#include <AzCore/Math/Vector2.h>
#include <AzCore/Math/MathScriptHelpers.h>
#include <AzCore/Serialization/SerializeContext.h>
namespace AZ
{
namespace
{
class TransformSerializer
: public SerializeContext::IDataSerializer
{
public:
// number of floats in the serialized representation, 4 for rotation, 1 for scale and 3 for translation
static constexpr int NumFloats = 8;
// number of floats in version 1, which used 4 for rotation, 3 for scale and 3 for translation
static constexpr int NumFloatsVersion1 = 10;
// number of floats in version 0, which stored a 3x4 matrix
static constexpr int NumFloatsVersion0 = 12;
size_t Save(const void* classPtr, IO::GenericStream& stream, bool isDataBigEndian) override;
size_t DataToText(IO::GenericStream& in, IO::GenericStream& out, bool isDataBigEndian) override;
size_t TextToData(const char* text, unsigned int textVersion, IO::GenericStream& stream, bool isDataBigEndian) override;
bool Load(void* classPtr, IO::GenericStream& stream, unsigned int version, bool isDataBigEndian) override;
bool CompareValueData(const void* lhs, const void* rhs) override;
};
}
namespace Internal
{
void TransformDefaultConstructor(Transform* thisPtr)
@@ -13,30 +13,9 @@
#include <AzCore/Math/Vector4.h>
#include <AzCore/Math/Quaternion.h>
#include <AzCore/Math/MathUtils.h>
#include <AzCore/Serialization/SerializeContext.h>
namespace AZ
{
class TransformSerializer
: public SerializeContext::IDataSerializer
{
public:
// number of floats in the serialized representation, 4 for rotation, 1 for scale and 3 for translation
static constexpr int NumFloats = 8;
// number of floats in version 1, which used 4 for rotation, 3 for scale and 3 for translation
static constexpr int NumFloatsVersion1 = 10;
// number of floats in version 0, which stored a 3x4 matrix
static constexpr int NumFloatsVersion0 = 12;
size_t Save(const void* classPtr, IO::GenericStream& stream, bool isDataBigEndian) override;
size_t DataToText(IO::GenericStream& in, IO::GenericStream& out, bool isDataBigEndian) override;
size_t TextToData(const char* text, unsigned int textVersion, IO::GenericStream& stream, bool isDataBigEndian) override;
bool Load(void* classPtr, IO::GenericStream& stream, unsigned int version, bool isDataBigEndian) override;
bool CompareValueData(const void* lhs, const void* rhs) override;
};
//! Limits for transform scale values.
//! The scale should not be zero to avoid problems with inverting.
//! @{
@@ -10,7 +10,6 @@
#include <AzCore/Math/Internal/MathTypes.h>
#include <AzCore/RTTI/TypeInfo.h>
#include <AzCore/std/algorithm.h>
namespace AZ
{
@@ -7,6 +7,7 @@
*/
#pragma once
#include <AzCore/Component/EntityId.h>
#include <AzCore/Math/Transform.h>
#include <AzCore/Math/Vector3.h>
#include <AzCore/Memory/Memory.h>
@@ -7,6 +7,8 @@
*/
#pragma once
#include <AzCore/std/containers/stack.h>
#include <AzFramework/Entity/EntityDebugDisplayBus.h>
namespace UnitTest
@@ -8,6 +8,7 @@
#include <AzFramework/Visibility/OctreeSystemComponent.h>
#include <AzCore/Math/ShapeIntersection.h>
#include <AzCore/Serialization/SerializeContext.h>
namespace AzFramework
{
@@ -9,7 +9,12 @@
#pragma once
#include <AzCore/base.h>
#include <AzCore/RTTI/BehaviorContext.h>
#include <AzCore/std/string/string.h>
namespace AZ
{
class ReflectContext;
} // namespace Az
////////////////////////////////////////////////////////////////////////////////////////////////////
namespace AzFramework
@@ -6,6 +6,7 @@
*
*/
#include <AzCore/Console/IConsole.h>
#include <AzCore/PlatformIncl.h>
#include <AzCore/IO/Path/Path.h>
#include <AzCore/IO/SystemFile.h>
@@ -13,6 +14,9 @@
#include <Psapi.h>
AZ_CVAR(bool, ap_tether_lifetime, false, nullptr, AZ::ConsoleFunctorFlags::Null,
"If enabled, a parent process that launches the AP will terminate the AP on exit");
namespace AzFramework::AssetSystem::Platform
{
void AllowAssetProcessorToForeground()
@@ -100,6 +104,21 @@ namespace AzFramework::AssetSystem::Platform
fullLaunchCommand += '"';
}
// Create or retrieve the job handle associated with the asset processor
HANDLE apJob = nullptr;
if (ap_tether_lifetime)
{
apJob = ::CreateJobObjectA(nullptr, "AssetProcessorJob");
if (apJob && GetLastError() != ERROR_ALREADY_EXISTS)
{
// We're creating the job for the first time. Configure it to close child processes when this process exits.
JOBOBJECT_EXTENDED_LIMIT_INFORMATION info = {};
info.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
::SetInformationJobObject(apJob, JobObjectExtendedLimitInformation, &info, sizeof(info));
}
}
STARTUPINFO si;
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
@@ -107,6 +126,14 @@ namespace AzFramework::AssetSystem::Platform
si.wShowWindow = SW_MINIMIZE;
PROCESS_INFORMATION pi;
return ::CreateProcessA(nullptr, fullLaunchCommand.data(), nullptr, nullptr, FALSE, 0, nullptr, AZ::IO::FixedMaxPathString{ executableDirectory }.c_str(), &si, &pi) != 0;
bool createResult = ::CreateProcessA(nullptr, fullLaunchCommand.data(), nullptr, nullptr, FALSE, 0, nullptr, AZ::IO::FixedMaxPathString{ executableDirectory }.c_str(), &si, &pi) != 0;
if (ap_tether_lifetime && apJob && createResult)
{
// Save process and thread handle to terminate AP when the parent process exits
::AssignProcessToJobObject(apJob, pi.hProcess);
}
return createResult;
}
}
@@ -17,6 +17,9 @@ class QString;
namespace AzToolsFramework::AssetUtils
{
static constexpr const char* AssetImporterSettingsKey{ "/O3DE/SceneAPI/AssetImporter" };
static constexpr const char* AssetImporterSupportedFileTypeKey{ "SupportedFileTypeExtensions" };
//! Reads the "/Amazon/AssetProcessor/Settings/Platforms" entry from the settings registry
//! to retrieve all enabled platforms
void ReadEnabledPlatformsFromSettingsRegistry(AZ::SettingsRegistryInterface& settingsRegistry,
@@ -8,6 +8,7 @@
#pragma once
#include <AzCore/Math/Transform.h>
#include <AzCore/Memory/SystemAllocator.h>
namespace AZ
{
@@ -7,6 +7,7 @@
*/
#include <Prefab/PrefabTestComponent.h>
#include <AzCore/Serialization/SerializeContext.h>
namespace UnitTest
{
+8
View File
@@ -238,6 +238,14 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED)
OUTPUT_SUBDIRECTORY
testdata/DummyProject
)
ly_add_target_files(
TARGETS
AssetProcessor.Tests
FILES
${CMAKE_CURRENT_SOURCE_DIR}/testdata/config_metadata/AssetProcessorPlatformConfig.setreg
OUTPUT_SUBDIRECTORY
testdata/config_metadata
)
# Have the AssetProcessorTest use the LY_CMAKE_TARGET define of AssetProcessorBatch for the purpose
# of looking up the generated cmake build dependencies settings registry .setreg file
@@ -12,6 +12,7 @@ set(FILES
testdata/config_broken_noscans/AssetProcessorPlatformConfig.setreg
testdata/config_broken_recognizers/AssetProcessorPlatformConfig.setreg
testdata/config_regular/AssetProcessorPlatformConfig.setreg
testdata/config_metadata/AssetProcessorPlatformConfig.setreg
testdata/config_regular_platform_scanfolder/AssetProcessorPlatformConfig.setreg
testdata/EmptyDummyProject/AssetProcessorGamePlatformConfig.setreg
testdata/DummyProject/AssetProcessorGamePlatformConfig.setreg
@@ -263,9 +263,15 @@ namespace AssetProcessor
return;
}
using FixedValueString = AZ::SettingsRegistryInterface::FixedValueString;
// Placeholder Key used by the local Settings Registry for storing all Gems SourcePaths
// array entries.
constexpr auto PlaceholderGemKey = FixedValueString(AZ::SettingsRegistryMergeUtils::OrganizationRootKey)
+ "/Gems/__SettingsRegistryBuilderPlaceholder";
AZ::SettingsRegistryImpl registry;
// Seed the local settings registry using the AssetProcessor settings registry
// Seed the local settings registry using the AssetProcessor Settings Registry
if (auto settingsRegistry = AZ::Interface<AZ::SettingsRegistryInterface>::Get(); settingsRegistry != nullptr)
{
AZStd::array settingsToCopy{
@@ -279,18 +285,55 @@ namespace AssetProcessor
for (const auto& settingsKey : settingsToCopy)
{
AZ::SettingsRegistryInterface::FixedValueString settingsValue;
FixedValueString settingsValue;
[[maybe_unused]] bool settingsCopied = settingsRegistry->Get(settingsValue, settingsKey)
&& registry.Set(settingsKey, settingsValue);
AZ_Warning("Settings Registry Builder", settingsCopied, "Unable to copy setting %s from AssetProcessor settings registry"
" to local settings registry", settingsKey.c_str());
}
// The purpose of this section is to copy the Gem's SourcePaths from the Global Settings Registry
// the local SettingsRegistry. The reason this is needed is so that the call to
// `MergeSettingsToRegistry_GemRegistries` below is able to locate each gem's "<gem-root>/Registry" folder
// that will be merged into the bootstrap.game.<configuration>.<platform>.setreg file
// This is used by the GameLauncher applications to read from a single merged .setreg file
// containing the settings needed to run a game/simulation without have access to the source code base registry
AZStd::vector<AzFramework::GemInfo> gemInfos;
size_t pathIndex{};
if (AzFramework::GetGemsInfo(gemInfos, *settingsRegistry))
{
AZStd::vector<AZ::IO::PathView> sourcePaths;
for (const AzFramework::GemInfo& gemInfo : gemInfos)
{
for (const AZ::IO::Path& absoluteSourcePath : gemInfo.m_absoluteSourcePaths)
{
if (auto foundIt = AZStd::find(sourcePaths.begin(), sourcePaths.end(), absoluteSourcePath);
foundIt == sourcePaths.end())
{
sourcePaths.emplace_back(absoluteSourcePath);
}
}
}
for (const AZ::IO::Path& sourcePath : sourcePaths)
{
// Use JSON Pointer to append elements to the SourcePaths array
registry.Set(FixedValueString::format("%s/SourcePaths/%zu", PlaceholderGemKey.c_str(), pathIndex++),
sourcePath.Native());
}
}
}
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_EngineRegistry(registry, platform, specialization, &scratchBuffer);
// This function iterates over each path for each the "/Amazon/Gems/<gem-name>/SourcePaths" key and attempts
// to merge the "Registry" directory in each path.
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_GemRegistries(registry, platform, specialization, &scratchBuffer);
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_ProjectRegistry(registry, platform, specialization, &scratchBuffer);
// The Placeholder Key is removed now that each gem's "<gem-root>/Registry" directory have been merged to
// the local Settings Registry instance via `MergeSettingsToRegistry_GemRegistries`
registry.Remove(PlaceholderGemKey);
// Merge the Project User and User home settings registry only in non-release builds
constexpr bool executeRegDumpCommands = false;
AZ::CommandLine* commandLine{};
@@ -76,6 +76,7 @@ public:
friend class GTEST_TEST_CLASS_NAME_(ModtimeScanningTest, ModtimeSkipping_ModifyMetadataFile);
friend class GTEST_TEST_CLASS_NAME_(ModtimeScanningTest, ModtimeSkipping_DeleteFile);
friend class GTEST_TEST_CLASS_NAME_(DeleteTest, DeleteFolderSharedAcrossTwoScanFolders_CorrectFileAndFolderAreDeletedFromCache);
friend class GTEST_TEST_CLASS_NAME_(MetadataFileTest, MetadataFile_SourceFileExtensionDifferentCase);
friend class AssetProcessorManagerTest;
friend struct ModtimeScanningTest;
@@ -5241,3 +5242,59 @@ void DuplicateProcessTest::SetUp()
m_sharedConnection = m_assetProcessorManager->m_stateData.get();
ASSERT_TRUE(m_sharedConnection);
}
void MetadataFileTest::SetUp()
{
AssetProcessorManagerTest::SetUp();
m_config->AddMetaDataType("foo", "txt");
}
TEST_F(MetadataFileTest, MetadataFile_SourceFileExtensionDifferentCase)
{
using namespace AzToolsFramework::AssetSystem;
using namespace AssetProcessor;
QDir tempPath(m_tempDir.path());
QString relFileName("Dummy.TXT");
QString absPath(tempPath.absoluteFilePath("subfolder1/Dummy.TXT"));
QString watchFolder = tempPath.absoluteFilePath("subfolder1");
UnitTestUtils::CreateDummyFile(absPath, "dummy");
JobEntry entry;
entry.m_watchFolderPath = watchFolder;
entry.m_databaseSourceName = entry.m_pathRelativeToWatchFolder = relFileName;
entry.m_jobKey = "txt";
entry.m_platformInfo = { "pc", {"host", "renderer", "desktop"} };
entry.m_jobRunKey = 1;
QString productPath(m_normalizedCacheRootDir.absoluteFilePath("outputfile.TXT"));
UnitTestUtils::CreateDummyFile(productPath);
AssetBuilderSDK::ProcessJobResponse jobResponse;
jobResponse.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
jobResponse.m_outputProducts.push_back(AssetBuilderSDK::JobProduct(productPath.toUtf8().data()));
QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssetProcessed", Qt::QueuedConnection, Q_ARG(JobEntry, entry), Q_ARG(AssetBuilderSDK::ProcessJobResponse, jobResponse));
ASSERT_TRUE(BlockUntilIdle(5000));
// Creating a metadata file for the source assets
// APM should process the source asset if a metadafile is detected
// We are intentionally having a source file with a different file extension casing than the one specified in the metadata rule.
QString metadataFile(tempPath.absoluteFilePath("subfolder1/Dummy.foo"));
UnitTestUtils::CreateDummyFile(metadataFile, "dummy");
// Capture the job details as the APM inspects the file.
JobDetails jobDetails;
auto connection = QObject::connect(m_assetProcessorManager.get(), &AssetProcessorManager::AssetToProcess, [&jobDetails](JobDetails job)
{
jobDetails = job;
});
m_assetProcessorManager->AssessAddedFile(tempPath.absoluteFilePath(metadataFile));
ASSERT_TRUE(BlockUntilIdle(5000));
ASSERT_EQ(jobDetails.m_jobEntry.m_pathRelativeToWatchFolder, relFileName);
}
@@ -179,6 +179,13 @@ struct ModtimeScanningTest
AZStd::unique_ptr<StaticData> m_data;
};
struct MetadataFileTest
: public AssetProcessorManagerTest
{
void SetUp() override;
};
struct FingerprintTest
: public AssetProcessorManagerTest
{
@@ -21,6 +21,7 @@ class UnitTestPlatformConfiguration : public AssetProcessor::PlatformConfigurati
{
friend class GTEST_TEST_CLASS_NAME_(PlatformConfigurationUnitTests, Test_GemHandling);
friend class GTEST_TEST_CLASS_NAME_(PlatformConfigurationUnitTests, Test_MetaFileTypes);
friend class GTEST_TEST_CLASS_NAME_(PlatformConfigurationUnitTests, Test_MetaFileTypes_AssetImporterExtensions);
protected:
};
@@ -665,3 +666,24 @@ TEST_F(PlatformConfigurationUnitTests, PlatformConfigFile_IsPresent_Found)
ASSERT_TRUE(config.AddPlatformConfigFilePaths(platformConfigList));
ASSERT_EQ(platformConfigList.size(), 1);
}
TEST_F(PlatformConfigurationUnitTests, Test_MetaFileTypes_AssetImporterExtensions)
{
using namespace AssetProcessor;
const auto testExeFolder = AZ::IO::FileIOBase::GetInstance()->ResolvePath(TestAppRoot);
auto configRoot = AZ::IO::FileIOBase::GetInstance()->ResolvePath("@exefolder@/testdata/config_metadata");
ASSERT_TRUE(configRoot);
UnitTestPlatformConfiguration config;
m_absorber.Clear();
ASSERT_FALSE(config.InitializeFromConfigFiles(configRoot->c_str(), testExeFolder->c_str(), EmptyDummyProjectName, false, false));
ASSERT_GT(m_absorber.m_numErrorsAbsorbed, 0);
ASSERT_TRUE(config.MetaDataFileTypesCount() == 2);
QStringList entriesToTest{ "aaa", "bbb" };
for (int idx = 0; idx < entriesToTest.size(); idx++)
{
ASSERT_EQ(config.GetMetaDataFileTypeAt(idx).first, QString("%1.assetinfo").arg(entriesToTest[idx]));
ASSERT_EQ(config.GetMetaDataFileTypeAt(idx).second, QString("%1").arg(entriesToTest[idx]));
}
}
@@ -14,6 +14,7 @@
#include <AzCore/Utils/Utils.h>
#include <AzFramework/API/ApplicationAPI.h>
#include <AzFramework/Gem/GemInfo.h>
#include <AzToolsFramework/Asset/AssetUtils.h>
namespace
{
@@ -23,6 +24,21 @@ namespace
namespace AssetProcessor
{
void AssetImporterPathsVisitor::Visit([[maybe_unused]] AZStd::string_view path, AZStd::string_view, AZ::SettingsRegistryInterface::Type,
AZStd::string_view value)
{
auto found = value.find('.');
if (found != AZStd::string::npos)
{
m_supportedFileExtensions.emplace_back(value.substr(found + 1));
}
else
{
m_supportedFileExtensions.emplace_back(value);
}
}
struct PlatformsInfoVisitor
: AZ::SettingsRegistryInterface::Visitor
{
@@ -1126,6 +1142,17 @@ namespace AssetProcessor
MetaDataTypesVisitor visitor;
settingsRegistry->Visit(visitor, AZ::SettingsRegistryInterface::FixedValueString(AssetProcessorSettingsKey) + "/MetaDataTypes");
using namespace AzToolsFramework::AssetUtils;
AZStd::vector<AZStd::string> supportedFileExtensions;
AssetImporterPathsVisitor assetImporterVisitor{ settingsRegistry, supportedFileExtensions };
settingsRegistry->Visit(assetImporterVisitor, AZ::SettingsRegistryInterface::FixedValueString(AssetImporterSettingsKey) + "/" + AssetImporterSupportedFileTypeKey);
for (auto& entry : assetImporterVisitor.m_supportedFileExtensions)
{
visitor.m_metaDataTypes.push_back({ AZStd::string::format("%s.assetinfo", entry.c_str()), entry });
}
for (const auto& metaDataType : visitor.m_metaDataTypes)
{
QString fileType = AssetUtilities::NormalizeFilePath(QString::fromUtf8(metaDataType.m_fileType.c_str(),
@@ -40,6 +40,21 @@ namespace AssetProcessor
extern const char AssetConfigPlatformDir[];
extern const char AssetProcessorPlatformConfigFileName[];
struct AssetImporterPathsVisitor
: AZ::SettingsRegistryInterface::Visitor
{
AssetImporterPathsVisitor(AZ::SettingsRegistryInterface* settingsRegistry, AZStd::vector<AZStd::string>& supportedExtension)
: m_settingsRegistry(settingsRegistry)
, m_supportedFileExtensions(supportedExtension)
{
}
void Visit(AZStd::string_view path, AZStd::string_view, AZ::SettingsRegistryInterface::Type, AZStd::string_view value) override;
AZ::SettingsRegistryInterface* m_settingsRegistry;
AZStd::vector<AZStd::string> m_supportedFileExtensions;
};
//! Information for a given recognizer, on a specific platform
//! essentially a plain data holder, but with helper funcs
class AssetPlatformSpec
@@ -0,0 +1,16 @@
{
"O3DE":
{
"SceneAPI":
{
"AssetImporter":
{
"SupportedFileTypeExtensions":
[
".aaa",
".bbb"
]
}
}
}
}
@@ -12,6 +12,7 @@
#include <AzCore/Serialization/Json/JsonSerialization.h>
#include <AzCore/Settings/SettingsRegistry.h>
#include <AzCore/StringFunc/StringFunc.h>
#include <AzToolsFramework/Asset/AssetUtils.h>
#include <SceneAPI/SceneBuilder/SceneImportRequestHandler.h>
#include <SceneAPI/SceneCore/Containers/Scene.h>
#include <SceneAPI/SceneCore/Events/CallProcessorBus.h>
@@ -23,19 +24,21 @@ namespace AZ
{
void SceneImporterSettings::Reflect(AZ::ReflectContext* context)
{
using namespace AzToolsFramework::AssetUtils;
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context); serializeContext)
{
serializeContext->Class<SceneImporterSettings>()
->Version(2)
->Field("SupportedFileTypeExtensions", &SceneImporterSettings::m_supportedFileTypeExtensions);
->Field(AssetImporterSupportedFileTypeKey, &SceneImporterSettings::m_supportedFileTypeExtensions);
}
}
void SceneImportRequestHandler::Activate()
{
using namespace AzToolsFramework::AssetUtils;
if (auto* settingsRegistry = AZ::SettingsRegistry::Get())
{
settingsRegistry->GetObject(m_settings, "/O3DE/SceneAPI/AssetImporter");
settingsRegistry->GetObject(m_settings, AssetImporterSettingsKey);
}
BusConnect();
@@ -11,6 +11,7 @@
#include <AzCore/Math/Transform.h>
#include <AzCore/Math/Vector3.h>
#include <AzCore/Math/Quaternion.h>
#include <AzCore/RTTI/RTTI.h>
#include <SceneAPI/SceneCore/SceneCoreConfiguration.h>
namespace AZ::SceneAPI
@@ -7,6 +7,7 @@
*/
#include "DebugOutput.h"
#include <AzCore/std/optional.h>
namespace AZ::SceneAPI::Utilities
{
@@ -11,6 +11,8 @@
#include <SceneAPI/SceneCore/SceneCoreConfiguration.h>
#include <SceneAPI/SceneCore/DataTypes/MatrixType.h>
#include <SceneAPI/SceneCore/Utilities/HashHelper.h>
#include <AzCore/std/string/string.h>
#include <AzCore/std/containers/vector.h>
#include <cinttypes>
namespace AZ::SceneAPI::Utilities
@@ -305,6 +305,11 @@ namespace AZ::SceneAPI::Behaviors
{
using namespace AzToolsFramework;
// This behavior persists on the same AssetBuilder. Clear the script file name so that if
// this builder processes a scene file with a script file name, and then later processes
// a scene without a script file name, it won't run the old script on the new scene.
m_scriptFilename.clear();
if (action != ManifestAction::Update)
{
return Events::ProcessingResult::Ignored;
@@ -1,51 +0,0 @@
{
"Type": "JsonSerialization",
"Version": 1,
"ClassName": "PassAsset",
"ClassData": {
"PassTemplate": {
"Name": "DiffuseGlobalFullscreenPass_nomsaaTemplate",
"PassClass": "FullScreenTriangle",
"Slots": [
{
"Name": "AlbedoInput",
"SlotType": "Input",
"ScopeAttachmentUsage": "Shader"
},
{
"Name": "NormalInput",
"SlotType": "Input",
"ScopeAttachmentUsage": "Shader"
},
{
"Name": "DepthStencilTextureInput",
"SlotType": "Input",
"ScopeAttachmentUsage": "Shader",
"ImageViewDesc": {
"AspectFlags": [
"Depth"
]
}
},
{
"Name": "DiffuseInputOutput",
"SlotType": "InputOutput",
"ScopeAttachmentUsage": "RenderTarget"
},
{
"Name": "DepthStencilInputOutput",
"SlotType": "InputOutput",
"ScopeAttachmentUsage": "DepthStencil"
}
],
"PassData": {
"$type": "FullscreenTrianglePassData",
"ShaderAsset": {
"FilePath": "Shaders/DiffuseGlobalIllumination/DiffuseGlobalFullscreen_nomsaa.shader"
},
"StencilRef": 128, // See RenderCommon.h and DiffuseGlobalFullscreen.shader
"PipelineViewTag": "MainCamera"
}
}
}
}
@@ -1,63 +0,0 @@
{
"Type": "JsonSerialization",
"Version": 1,
"ClassName": "PassAsset",
"ClassData": {
"PassTemplate": {
"Name": "LightCullingTilePrepareTemplate",
"PassClass": "LightCullingTilePreparePass",
"Slots": [
{
"Name": "TileLightData",
"SlotType": "Output",
"ShaderInputName": "m_tileLightData",
"ScopeAttachmentUsage": "Shader"
},
{
"Name": "Depth",
"SlotType": "Input",
"ShaderInputName": "m_depthBuffer",
"ScopeAttachmentUsage": "Shader",
"ImageViewDesc": {
"AspectFlags": [
"Depth"
]
}
}
],
"ImageAttachments": [
{
"Name": "TileLightData",
"SizeSource": {
"Source": {
"Pass": "This",
"Attachment": "Depth"
},
"Multipliers": {
"WidthMultiplier": 0.0625,
"HeightMultiplier": 0.0625
}
},
"ImageDescriptor": {
"Format": "R32G32B32A32_UINT"
}
}
],
"Connections": [
{
"LocalSlot": "TileLightData",
"AttachmentRef": {
"Pass": "This",
"Attachment": "TileLightData"
}
}
],
"PassData": {
"$type": "ComputePassData",
"ShaderAsset": {
"FilePath": "Shaders/LightCulling/LightCullingTilePrepare.shader"
}
}
}
}
}
@@ -212,10 +212,6 @@
"Name": "LightCullingTemplate",
"Path": "Passes/LightCulling.pass"
},
{
"Name": "LightCullingTilePrepareTemplate",
"Path": "Passes/LightCullingTilePrepare.pass"
},
{
"Name": "LightCullingTilePrepareMSAATemplate",
"Path": "Passes/LightCullingTilePrepareMSAA.pass"
@@ -1,119 +0,0 @@
{
"Type": "JsonSerialization",
"Version": 1,
"ClassName": "PassAsset",
"ClassData": {
"PassTemplate": {
"Name": "ReflectionGlobalFullscreenPass_nomsaaTemplate",
"PassClass": "FullScreenTriangle",
"Slots": [
{
"Name": "DepthStencilTextureInput",
"SlotType": "Input",
"ScopeAttachmentUsage": "Shader",
"ImageViewDesc": {
"AspectFlags": [
"Depth"
]
}
},
{
"Name": "NormalInput",
"SlotType": "Input",
"ScopeAttachmentUsage": "Shader"
},
{
"Name": "SpecularF0Input",
"SlotType": "Input",
"ScopeAttachmentUsage": "Shader"
},
{
"Name": "ReflectionBlendWeightInput",
"SlotType": "Input",
"ScopeAttachmentUsage": "Shader"
},
{
"Name": "BRDFTextureInput",
"ShaderInputName": "m_brdfMap",
"SlotType": "Input",
"ScopeAttachmentUsage": "Shader"
},
{
"Name": "DepthStencilInput",
"SlotType": "Input",
"ScopeAttachmentUsage": "DepthStencil",
"ImageViewDesc": {
"AspectFlags": [
"Stencil"
]
}
},
{
"Name": "ReflectionOutput",
"SlotType": "Output",
"ScopeAttachmentUsage": "RenderTarget",
"LoadStoreAction": {
"ClearValue": {
"Value": [
0.4000000059604645,
0.4000000059604645,
0.4000000059604645,
0.0
]
},
"LoadAction": "Clear"
}
}
],
"ImageAttachments": [
{
"Name": "ReflectionImage",
"SizeSource": {
"Source": {
"Pass": "This",
"Attachment": "SpecularF0Input"
}
},
"MultisampleSource": {
"Pass": "This",
"Attachment": "SpecularF0Input"
},
"ImageDescriptor": {
"Format": "R16G16B16A16_FLOAT",
"SharedQueueMask": "Graphics"
}
},
{
"Name": "BRDFTexture",
"Lifetime": "Imported",
"AssetRef": {
"FilePath": "Textures/BRDFTexture.attimage"
}
}
],
"Connections": [
{
"LocalSlot": "ReflectionOutput",
"AttachmentRef": {
"Pass": "This",
"Attachment": "ReflectionImage"
}
},
{
"LocalSlot": "BRDFTextureInput",
"AttachmentRef": {
"Pass": "This",
"Attachment": "BRDFTexture"
}
}
],
"PassData": {
"$type": "FullscreenTrianglePassData",
"ShaderAsset": {
"FilePath": "Shaders/Reflections/ReflectionGlobalFullscreen_nomsaa.shader"
},
"PipelineViewTag": "MainCamera"
}
}
}
}
@@ -1,56 +0,0 @@
{
"Source" : "DiffuseComposite_nomsaa",
"RasterState" :
{
"CullMode" : "Back"
},
"DepthStencilState" :
{
"Depth" :
{
"Enable" : false
},
"Stencil" :
{
"Enable" : true,
"ReadMask" : "0x80",
"WriteMask" : "0x00",
"FrontFace" :
{
"Func" : "Equal",
"DepthFailOp" : "Keep",
"FailOp" : "Keep",
"PassOp" : "Keep"
}
}
},
"BlendState" : {
"Enable" : true,
"BlendSource" : "One",
"BlendDest" : "One",
"BlendOp" : "Add",
"BlendAlphaSource" : "Zero",
"BlendAlphaDest" : "One",
"BlendAlphaOp" : "Add"
},
"DrawList" : "forward",
"ProgramSettings":
{
"EntryPoints":
[
{
"name": "MainVS",
"type": "Vertex"
},
{
"name": "MainPS",
"type": "Fragment"
}
]
}
}
@@ -1,56 +0,0 @@
{
"Source" : "DiffuseGlobalFullscreen_nomsaa",
"RasterState" :
{
"CullMode" : "Back"
},
"DepthStencilState" :
{
"Depth" :
{
"Enable" : false
},
"Stencil" :
{
"Enable" : true,
"ReadMask" : "0x80",
"WriteMask" : "0x00",
"FrontFace" :
{
"Func" : "Equal",
"DepthFailOp" : "Keep",
"FailOp" : "Keep",
"PassOp" : "Keep"
}
}
},
"BlendState" : {
"Enable" : true,
"BlendSource" : "One",
"BlendDest" : "One",
"BlendOp" : "Add",
"BlendAlphaSource" : "Zero",
"BlendAlphaDest" : "One",
"BlendAlphaOp" : "Add"
},
"DrawList" : "forward",
"ProgramSettings":
{
"EntryPoints":
[
{
"name": "MainVS",
"type": "Vertex"
},
{
"name": "MainPS",
"type": "Fragment"
}
]
}
}
@@ -1,34 +0,0 @@
{
"Source" : "DiffuseProbeGridDownsample_nomsaa",
"RasterState" :
{
"CullMode" : "Back"
},
"DepthStencilState" :
{
"Depth" :
{
"Enable" : true, // required to bind the depth buffer SRV
"CompareFunc" : "Always"
}
},
"DrawList" : "forward",
"ProgramSettings":
{
"EntryPoints":
[
{
"name": "MainVS",
"type": "Vertex"
},
{
"name": "MainPS",
"type": "Fragment"
}
]
}
}
@@ -66,9 +66,7 @@ void StoreTransparentMinMaxIntoSharedMemory(float2 data)
ShaderResourceGroup PassSrg : SRG_PerPass
{
// We will use one or the other depending upon if MSAA is enabled or not
Texture2D<float4> m_depthBuffer;
Texture2DMS<float4> m_depthBufferMSAA;
Texture2DMS<float4> m_depthBufferMSAA;
// Depth buffer where we rendered the nearest pixels of transparent objects
Texture2D<float4> m_depthBufferTransparentMin;
@@ -282,9 +280,19 @@ void MainCS(
float2 minmaxDepth_transparent = ReadTransparentMinMaxMSAA(dispatchThreadID.xy);
if (o_msaaMode == MsaaMode::Msaa2x)
if (o_msaaMode == MsaaMode::None || o_msaaMode == MsaaMode::Msaa2x)
{
float2 opaqueDepthSamples = ReadOpaqueDepthSamples2xMSAA(dispatchThreadID.xy);
float2 opaqueDepthSamples;
if (o_msaaMode == MsaaMode::None)
{
float depth = PassSrg::m_depthBufferMSAA.Load(dispatchThreadID.xy, 0).x;
opaqueDepthSamples = float2(depth, depth);
}
else if (o_msaaMode == MsaaMode::Msaa2x)
{
opaqueDepthSamples = ReadOpaqueDepthSamples2xMSAA(dispatchThreadID.xy);
}
// Transparent geometry can't be behind opaque geometry. Just pick the first MSAA sample since we are trying to reduce reading from MSAA buffer
minmaxDepth_transparent = DEPTH_MIN(minmaxDepth_transparent, opaqueDepthSamples.x);
opaqueDepthSamples = ReplaceSkyPixelsWithFurthestPixelsFromTransparentObjects2x(opaqueDepthSamples, minmaxDepth_transparent);
@@ -1,53 +0,0 @@
{
"Source" : "ReflectionComposite_nomsaa",
"RasterState" :
{
"CullMode" : "Back"
},
"DepthStencilState" :
{
"Depth" :
{
"Enable" : false,
"WriteMask" : "Zero",
"CompareFunc" : "GreaterEqual"
},
"Stencil" :
{
"Enable" : true,
"ReadMask" : "0xFF",
"WriteMask" : "0x00",
"FrontFace" :
{
"Func" : "LessEqual",
"DepthFailOp" : "Keep",
"FailOp" : "Keep",
"PassOp" : "Keep"
}
}
},
"BlendState" : {
"Enable" : true,
"BlendSource" : "One",
"BlendDest" : "One",
"BlendOp" : "Add"
},
"ProgramSettings":
{
"EntryPoints":
[
{
"name": "MainVS",
"type": "Vertex"
},
{
"name": "MainPS",
"type": "Fragment"
}
]
}
}
@@ -1,44 +0,0 @@
{
"Source" : "ReflectionGlobalFullscreen_nomsaa",
"RasterState" :
{
"CullMode" : "Back"
},
"DepthStencilState" :
{
"Depth" :
{
"Enable" : false
},
"Stencil" :
{
"Enable" : true,
"ReadMask" : "0xFF",
"WriteMask" : "0x00",
"FrontFace" :
{
"Func" : "Equal",
"DepthFailOp" : "Keep",
"FailOp" : "Keep",
"PassOp" : "Keep"
}
}
},
"ProgramSettings":
{
"EntryPoints":
[
{
"name": "MainVS",
"type": "Vertex"
},
{
"name": "MainPS",
"type": "Fragment"
}
]
}
}
@@ -113,7 +113,6 @@ set(FILES
Passes/DepthUpsample.pass
Passes/DiffuseComposite.pass
Passes/DiffuseGlobalFullscreen.pass
Passes/DiffuseGlobalFullscreen_nomsaa.pass
Passes/DiffuseGlobalIllumination.pass
Passes/DiffuseProbeGridBlendDistance.pass
Passes/DiffuseProbeGridBlendIrradiance.pass
@@ -152,7 +151,6 @@ set(FILES
Passes/LightCullingHeatmap.pass
Passes/LightCullingParent.pass
Passes/LightCullingRemap.pass
Passes/LightCullingTilePrepare.pass
Passes/LightCullingTilePrepareMSAA.pass
Passes/LookModificationComposite.pass
Passes/LookModificationTransform.pass
@@ -176,7 +174,6 @@ set(FILES
Passes/ReflectionComposite.pass
Passes/ReflectionCopyFrameBuffer.pass
Passes/ReflectionGlobalFullscreen.pass
Passes/ReflectionGlobalFullscreen_nomsaa.pass
Passes/ReflectionProbeBlendWeight.pass
Passes/ReflectionProbeRenderInner.pass
Passes/ReflectionProbeRenderOuter.pass
@@ -190,7 +187,6 @@ set(FILES
Passes/ReflectionScreenSpaceComposite.pass
Passes/ReflectionScreenSpaceMobile.pass
Passes/ReflectionScreenSpaceTrace.pass
Passes/Reflections_nomsaa.pass
Passes/ShadowParent.pass
Passes/Skinning.pass
Passes/SkyBox.pass
@@ -320,16 +316,10 @@ set(FILES
Shaders/Depth/DepthPassTransparentMin.shader
Shaders/DiffuseGlobalIllumination/DiffuseComposite.azsl
Shaders/DiffuseGlobalIllumination/DiffuseComposite.shader
Shaders/DiffuseGlobalIllumination/DiffuseComposite_nomsaa.azsl
Shaders/DiffuseGlobalIllumination/DiffuseComposite_nomsaa.shader
Shaders/DiffuseGlobalIllumination/DiffuseGlobalFullscreen.azsl
Shaders/DiffuseGlobalIllumination/DiffuseGlobalFullscreen.shader
Shaders/DiffuseGlobalIllumination/DiffuseGlobalFullscreen_nomsaa.azsl
Shaders/DiffuseGlobalIllumination/DiffuseGlobalFullscreen_nomsaa.shader
Shaders/DiffuseGlobalIllumination/DiffuseProbeGridDownsample.azsl
Shaders/DiffuseGlobalIllumination/DiffuseProbeGridDownsample.shader
Shaders/DiffuseGlobalIllumination/DiffuseProbeGridDownsample_nomsaa.azsl
Shaders/DiffuseGlobalIllumination/DiffuseProbeGridDownsample_nomsaa.shader
Shaders/ImGui/ImGui.azsl
Shaders/ImGui/ImGui.shader
Shaders/LightCulling/LightCulling.azsl
@@ -442,12 +432,8 @@ set(FILES
Shaders/Reflections/ReflectionCommon.azsli
Shaders/Reflections/ReflectionComposite.azsl
Shaders/Reflections/ReflectionComposite.shader
Shaders/Reflections/ReflectionComposite_nomsaa.azsl
Shaders/Reflections/ReflectionComposite_nomsaa.shader
Shaders/Reflections/ReflectionGlobalFullscreen.azsl
Shaders/Reflections/ReflectionGlobalFullscreen.shader
Shaders/Reflections/ReflectionGlobalFullscreen_nomsaa.azsl
Shaders/Reflections/ReflectionGlobalFullscreen_nomsaa.shader
Shaders/Reflections/ReflectionProbeBlendWeight.azsl
Shaders/Reflections/ReflectionProbeBlendWeight.shader
Shaders/Reflections/ReflectionProbeRenderCommon.azsli
@@ -38,7 +38,7 @@ namespace AZ
static Name GetLightCullingTilePreparePassTemplateName()
{
return AZ::Name("LightCullingTilePrepareTemplate");
return AZ::Name("LightCullingTilePrepareMSAATemplate");
}
private:
@@ -10,6 +10,8 @@
#include <Atom/RHI.Reflect/Format.h>
#include <Atom/RHI.Reflect/AttachmentEnums.h>
#include <AzCore/Preprocessor/Enum.h>
#include <AzCore/Utils/TypeHash.h>
namespace AZ
@@ -22,47 +24,44 @@ namespace AZ
* A set of combinable flags which inform the system how a buffer is to be
* bound to the pipeline at all stages of its lifetime.
*/
enum class BufferBindFlags : uint32_t
{
None = 0,
AZ_ENUM_CLASS_WITH_UNDERLYING_TYPE(BufferBindFlags, uint32_t,
(None , 0),
/// Supports input assembly access through a IndexBufferView or StreamBufferView. This flag is for buffers that are not updated often
InputAssembly = AZ_BIT(0),
(InputAssembly , AZ_BIT(0)),
/// Supports input assembly access through a IndexBufferView or StreamBufferView. This flag is for buffers that are updated frequently
DynamicInputAssembly = AZ_BIT(1),
(DynamicInputAssembly , AZ_BIT(1)),
/// Supports constant access through a ShaderResourceGroup.
Constant = AZ_BIT(2),
(Constant , AZ_BIT(2)),
/// Supports read access through a ShaderResourceGroup.
ShaderRead = AZ_BIT(3),
(ShaderRead , AZ_BIT(3)),
/// Supports write access through ShaderResourceGroup.
ShaderWrite = AZ_BIT(4),
(ShaderWrite , AZ_BIT(4)),
/// Supports read-write access through a ShaderResourceGroup.
ShaderReadWrite = ShaderRead | ShaderWrite,
(ShaderReadWrite , ShaderRead | ShaderWrite),
/// Supports read access for GPU copy operations.
CopyRead = AZ_BIT(5),
(CopyRead , AZ_BIT(5)),
/// Supports write access for GPU copy operations.
CopyWrite = AZ_BIT(6),
(CopyWrite , AZ_BIT(6)),
/// Supports predication access for conditional rendering.
Predication = AZ_BIT(7),
(Predication , AZ_BIT(7)),
/// Supports indirect buffer access for indirect draw/dispatch.
Indirect = AZ_BIT(8),
(Indirect , AZ_BIT(8)),
/// Supports ray tracing acceleration structure usage.
RayTracingAccelerationStructure = AZ_BIT(9),
(RayTracingAccelerationStructure , AZ_BIT(9)),
/// Supports ray tracing shader table usage.
RayTracingShaderTable = AZ_BIT(10)
};
(RayTracingShaderTable , AZ_BIT(10)));
AZ_DEFINE_ENUM_BITWISE_OPERATORS(AZ::RHI::BufferBindFlags);
@@ -9,43 +9,43 @@
#include <Atom/RHI.Reflect/Base.h>
#include <AzCore/Preprocessor/Enum.h>
namespace AZ
{
namespace RHI
{
//! A set of combinable flags which inform the system how an image is to be
//! bound to the pipeline at all stages of its lifetime.
enum class ImageBindFlags : uint32_t
{
None = 0,
AZ_ENUM_CLASS_WITH_UNDERLYING_TYPE(ImageBindFlags, uint32_t,
(None, 0),
/// Supports read access through a ShaderResourceGroup.
ShaderRead = AZ_BIT(0),
(ShaderRead, AZ_BIT(0)),
/// Supports write access through a ShaderResourceGroup.
ShaderWrite = AZ_BIT(1),
(ShaderWrite, AZ_BIT(1)),
/// Supports read-write access through a ShaderResourceGroup.
ShaderReadWrite = ShaderRead | ShaderWrite,
(ShaderReadWrite, ShaderRead | ShaderWrite),
/// Supports use as a color attachment on a scope.
Color = AZ_BIT(2),
(Color, AZ_BIT(2)),
/// Supports use as depth attachment on a scope.
Depth = AZ_BIT(3),
(Depth, AZ_BIT(3)),
/// Supports use as stencil attachment on a scope.
Stencil = AZ_BIT(4),
(Stencil, AZ_BIT(4)),
/// Supports use as a depth stencil attachment on a scope.
DepthStencil = Depth | Stencil,
(DepthStencil, Depth | Stencil),
/// Supports read access for GPU copy operations.
CopyRead = AZ_BIT(5),
(CopyRead, AZ_BIT(5)),
/// Supports write access for GPU copy operations.
CopyWrite = AZ_BIT(6),
};
(CopyWrite, AZ_BIT(6)));
AZ_DEFINE_ENUM_BITWISE_OPERATORS(AZ::RHI::ImageBindFlags);
@@ -7,7 +7,6 @@
*/
#pragma once
#include <Atom/RHI.Reflect/Handle.h>
#include <Atom/RHI.Reflect/Limits.h>
#include <Atom/RHI/StreamBufferView.h>
#include <Atom/RHI/IndexBufferView.h>
@@ -23,6 +22,10 @@ namespace AZ
class ShaderResourceGroup;
struct Scissor;
struct Viewport;
struct DefaultNamespaceType;
// Forward declaration to
template <typename T , typename NamespaceType>
struct Handle;
struct DrawLinear
{
@@ -149,13 +152,13 @@ namespace AZ
};
using DrawItemSortKey = int64_t;
// A filter associate to a DrawItem which can be used to filter the DrawItem when submitting to command list
using DrawFilterTag = Handle<uint8_t>;
using DrawFilterTag = Handle<uint8_t, DefaultNamespaceType>;
using DrawFilterMask = uint32_t; // AZStd::bitset's impelmentation is too expensive.
constexpr uint32_t DrawFilterMaskDefaultValue = uint32_t(-1); // Default all bit to 1.
static_assert(sizeof(DrawFilterMask) * 8 >= Limits::Pipeline::DrawFilterTagCountMax, "DrawFilterMask doesn't have enough bits for maximum tag count");
struct DrawItemProperties
{
DrawItemProperties() = default;
@@ -51,6 +51,7 @@ namespace AZ
void ModifyFrameSchedulerStatisticsFlags(RHI::FrameSchedulerStatisticsFlags statisticsFlags, bool enableFlags) override;
const RHI::CpuTimingStatistics* GetCpuTimingStatistics() const override;
const RHI::TransientAttachmentStatistics* GetTransientAttachmentStatistics() const override;
const RHI::MemoryStatistics* GetMemoryStatistics() const override;
const RHI::TransientAttachmentPoolDescriptor* GetTransientAttachmentPoolDescriptor() const override;
ConstPtr<PlatformLimitsDescriptor> GetPlatformLimitsDescriptor() const override;
void QueueRayTracingShaderTableForBuild(RayTracingShaderTable* rayTracingShaderTable) override;
@@ -11,6 +11,7 @@
#include <AzCore/Name/Name.h>
#include <AzCore/EBus/EBus.h>
#include <Atom/RHI.Reflect/FrameSchedulerEnums.h>
#include <Atom/RHI.Reflect/MemoryStatistics.h>
#include <Atom/RHI/DrawListTagRegistry.h>
namespace AZ
@@ -55,6 +56,8 @@ namespace AZ
virtual const RHI::TransientAttachmentStatistics* GetTransientAttachmentStatistics() const = 0;
virtual const RHI::MemoryStatistics* GetMemoryStatistics() const = 0;
virtual const RHI::TransientAttachmentPoolDescriptor* GetTransientAttachmentPoolDescriptor() const = 0;
virtual ConstPtr<PlatformLimitsDescriptor> GetPlatformLimitsDescriptor() const = 0;
@@ -281,6 +281,11 @@ namespace AZ
return m_frameScheduler.GetTransientAttachmentStatistics();
}
const RHI::MemoryStatistics* RHISystem::GetMemoryStatistics() const
{
return m_frameScheduler.GetMemoryStatistics();
}
const AZ::RHI::TransientAttachmentPoolDescriptor* RHISystem::GetTransientAttachmentPoolDescriptor() const
{
return m_frameScheduler.GetTransientAttachmentPoolDescriptor();
@@ -122,11 +122,6 @@ namespace Platform
return reinterpret_cast<RHIMetalView*>([nativeWindow.contentViewController view]);
}
void ApplyTileDimentions(MTLRenderPassDescriptor* mtlRenderPassDescriptor)
{
AZ_UNUSED(mtlRenderPassDescriptor);
}
void SynchronizeBufferOnCPU(id<MTLBuffer> mtlBuffer, size_t bufferOffset, size_t bufferSize)
{
if(mtlBuffer.storageMode == MTLStorageModeManaged)
@@ -88,17 +88,6 @@ namespace Platform
return reinterpret_cast<RHIMetalView*>([nativeWindow.rootViewController view]);
}
void ApplyTileDimentions(MTLRenderPassDescriptor* mtlRenderPassDescriptor)
{
//Metal driver has a bug where if the tile dimensions changes between passes it will
//generate incorrect vertex positions (possible vertex invariance). For example vertex invariance was
//observed between Depth pass and forward pass. Hence for now we are setting global tile dimentions across all passes.
//For performance sake we should eventually remove this once this bug is addressed.
//[GFX_TODO][ATOM-13440] - Remove once driver bug is addressed.
mtlRenderPassDescriptor.tileWidth = 16;
mtlRenderPassDescriptor.tileHeight = 16;
}
void SynchronizeBufferOnCPU(id<MTLBuffer> mtlBuffer, size_t bufferOffset, size_t bufferSize)
{
//No synchronization needed as ios uses shared memory and does not support MTLStorageModeManaged
@@ -17,10 +17,6 @@
#include <RHI/Scope.h>
#include <Atom/RHI/SwapChainFrameAttachment.h>
namespace Platform
{
void ApplyTileDimentions(MTLRenderPassDescriptor* mtlRenderPassDescriptor);
}
namespace AZ
{
@@ -88,7 +84,6 @@ namespace AZ
{
AZ_Assert(m_renderPassDescriptor == nil, "m_renderPassDescriptor should be null");
m_renderPassDescriptor = [MTLRenderPassDescriptor renderPassDescriptor];
Platform::ApplyTileDimentions(m_renderPassDescriptor);
}
if(GetEstimatedItemCount())
@@ -815,11 +815,12 @@ namespace AZ
createInfo.size = descriptor.m_byteCount;
createInfo.usage = GetBufferUsageFlagBitsUnderRestrictions(descriptor.m_bindFlags);
// Trying to guess here if the buffers are going to be used as attachments. Maybe it would be better to add an explicit flag in the descriptor.
createInfo.sharingMode =
RHI::CheckBitsAny(
descriptor.m_bindFlags,
RHI::BufferBindFlags::ShaderWrite | RHI::BufferBindFlags::Predication | RHI::BufferBindFlags::Indirect)
? VK_SHARING_MODE_EXCLUSIVE
createInfo.sharingMode =
(RHI::CheckBitsAny(
descriptor.m_bindFlags,
RHI::BufferBindFlags::ShaderWrite | RHI::BufferBindFlags::Predication | RHI::BufferBindFlags::Indirect) ||
(queueFamilies.size()) <= 1)
? VK_SHARING_MODE_EXCLUSIVE
: VK_SHARING_MODE_CONCURRENT;
createInfo.queueFamilyIndexCount = static_cast<uint32_t>(queueFamilies.size());
createInfo.pQueueFamilyIndices = queueFamilies.empty() ? nullptr : queueFamilies.data();
@@ -23,7 +23,11 @@ namespace AZ
namespace RHI
{
class ScopeAttachment;
enum class BufferBindFlags : uint32_t;
// NOTE: see BufferDescriptor.h, AZ_ENUM... macro wraps enum within an outer inline namespace.
inline namespace BufferBindFlagsNamespace
{
enum class BufferBindFlags : uint32_t;
}
class BufferView;
class ImageView;
struct BufferSubresourceRange;
@@ -98,7 +98,7 @@ namespace AZ
{
return RHI::ResultCode::Fail;
}
m_shaderResourceGroup->SetName(srgName);
m_shaderResourceGroup->SetName(m_pool->GetRHIPool()->GetName());
m_data = RHI::ShaderResourceGroupData(m_layout);
m_asset = { &shaderAsset, AZ::Data::AssetLoadBehavior::PreLoad };
@@ -8,9 +8,12 @@
#pragma once
#include <Atom/RHI.Reflect/MemoryStatistics.h>
#include <Atom/RPI.Public/GpuQuery/GpuQueryTypes.h>
#include <AzCore/Name/Name.h>
#include <AzCore/std/containers/array.h>
#include <AzCore/std/containers/variant.h>
#include <AzCore/std/string/string.h>
namespace AZ
@@ -250,6 +253,44 @@ namespace AZ
};
class ImGuiGpuMemoryView
{
public:
// Draw the overall GPU memory profiling window.
void DrawGpuMemoryWindow(bool& draw);
private:
// Draw the heap usage pie chart
void DrawPieChart(const AZ::RHI::MemoryStatistics::Heap& heap);
// Update the saved pointers in m_tableRows according to new data/filters
void UpdateTableRows();
void DrawTable();
// Sort the table according to the appropriate column.
void SortTable(ImGuiTableSortSpecs* sortSpecs);
struct TableRow
{
Name m_parentPoolName;
Name m_bufImgName;
size_t m_sizeInBytes = 0;
AZStd::string m_bindFlags;
};
// Table settings
bool m_includeBuffers = true;
bool m_includeImages = true;
bool m_includeTransientAttachments = true;
ImGuiTextFilter m_nameFilter;
AZStd::vector<TableRow> m_tableRows;
AZStd::vector<AZ::RHI::MemoryStatistics::Pool> m_savedPools;
AZStd::vector<AZ::RHI::MemoryStatistics::Heap> m_savedHeaps;
};
class ImGuiGpuProfiler
{
public:
@@ -275,9 +316,11 @@ namespace AZ
bool m_drawTimestampView = false;
bool m_drawPipelineStatisticsView = false;
bool m_drawGpuMemoryView = false;
ImGuiTimestampView m_timestampView;
ImGuiPipelineStatisticsView m_pipelineStatisticsView;
ImGuiGpuMemoryView m_gpuMemoryView;
};
} //namespace Render
} // namespace AZ
@@ -6,12 +6,15 @@
*
*/
#include <Atom/RHI/RHISystemInterface.h>
#include <Atom/RPI.Public/Pass/ParentPass.h>
#include <Atom/RPI.Public/Pass/RenderPass.h>
#include <Atom/RPI.Public/RenderPipeline.h>
#include <Atom/RPI.Public/RPISystemInterface.h>
#include <Atom/RPI.Public/Scene.h>
#include <AzCore/std/sort.h>
#include <inttypes.h>
namespace AZ
@@ -87,6 +90,38 @@ namespace AZ
}
drawList->AddText(font, font->FontSize, pos, ImGui::GetColorU32(ImGuiCol_Text), text, nullptr, size.x);
}
inline static AZStd::string GetImageBindStrings(AZ::RHI::ImageBindFlags imageBindFlags)
{
AZStd::string imageBindStrings;
for (const auto& flag : AZ::RHI::ImageBindFlagsMembers)
{
if (flag.m_value != AZ::RHI::ImageBindFlags::None && AZ::RHI::CheckBitsAll(imageBindFlags, flag.m_value))
{
imageBindStrings.append(flag.m_string);
imageBindStrings.append(", ");
}
}
return imageBindStrings;
}
inline static AZStd::string GetBufferBindStrings(AZ::RHI::BufferBindFlags bufferBindFlags)
{
AZStd::string bufferBindStrings;
for (const auto& flag : AZ::RHI::BufferBindFlagsMembers)
{
if (flag.m_value != AZ::RHI::BufferBindFlags::None && AZ::RHI::CheckBitsAll(bufferBindFlags, flag.m_value))
{
bufferBindStrings.append(flag.m_string);
bufferBindStrings.append(", ");
}
}
return bufferBindStrings;
}
static constexpr u64 KB = 1024;
static constexpr u64 MB = 1024 * KB;
static constexpr u64 GB = 1024 * MB;
} // namespace GpuProfilerImGuiHelper
// --- PassEntry ---
@@ -1025,6 +1060,219 @@ namespace AZ
}
}
// --- ImGuiGpuMemoryView ---
inline void ImGuiGpuMemoryView::SortTable(ImGuiTableSortSpecs* sortSpecs)
{
const bool ascending = sortSpecs->Specs->SortDirection == ImGuiSortDirection_Ascending;
const ImS16 columnToSort = sortSpecs->Specs->ColumnIndex;
// Sort by the appropriate column in the table
switch (columnToSort)
{
case (0): // Sorting by parent pool name
AZStd::sort(m_tableRows.begin(), m_tableRows.end(),
[ascending](const TableRow& lhs, const TableRow& rhs)
{
const auto lhsParentPool = lhs.m_parentPoolName.GetStringView();
const auto rhsParentPool = rhs.m_parentPoolName.GetStringView();
return ascending ? lhsParentPool < rhsParentPool : lhsParentPool > rhsParentPool;
});
break;
case (1): // Sort by buffer/image name
AZStd::sort(m_tableRows.begin(), m_tableRows.end(),
[ascending](const TableRow& lhs, const TableRow& rhs)
{
const auto lhsName = lhs.m_bufImgName.GetStringView();
const auto rhsName = rhs.m_bufImgName.GetStringView();
return ascending ? lhsName < rhsName : lhsName > rhsName;
});
break;
case (2): // Sort by memory usage
AZStd::sort(m_tableRows.begin(), m_tableRows.end(),
[ascending](const TableRow& lhs, const TableRow& rhs)
{
const float lhsSize = lhs.m_sizeInBytes;
const float rhsSize = rhs.m_sizeInBytes;
return ascending ? lhsSize < rhsSize : lhsSize > rhsSize;
});
break;
}
sortSpecs->SpecsDirty = false;
}
inline void ImGuiGpuMemoryView::DrawTable()
{
if (ImGui::BeginTable("Table", 4, ImGuiTableFlags_Borders | ImGuiTableFlags_Sortable | ImGuiTableFlags_Resizable))
{
ImGui::TableSetupColumn("Parent pool");
ImGui::TableSetupColumn("Name");
ImGui::TableSetupColumn("Size (MB)", 0, 100.0f);
ImGui::TableSetupColumn("BindFlags", ImGuiTableColumnFlags_NoSort);
ImGui::TableHeadersRow();
ImGui::TableNextColumn();
ImGuiTableSortSpecs* sortSpecs = ImGui::TableGetSortSpecs();
if (sortSpecs && sortSpecs->SpecsDirty)
{
SortTable(sortSpecs);
}
// Draw each row in the table
for (const auto& tableRow : m_tableRows)
{
// Don't draw the row if none of the row's text fields pass the filter
if (!m_nameFilter.PassFilter(tableRow.m_parentPoolName.GetCStr())
&& !m_nameFilter.PassFilter(tableRow.m_bufImgName.GetCStr())
&& !m_nameFilter.PassFilter(tableRow.m_bindFlags.c_str()))
{
continue;
}
ImGui::Text(tableRow.m_parentPoolName.GetCStr());
ImGui::TableNextColumn();
ImGui::Text(tableRow.m_bufImgName.GetCStr());
ImGui::TableNextColumn();
ImGui::Text("%.2f", 1.0f * tableRow.m_sizeInBytes / GpuProfilerImGuiHelper::MB);
ImGui::TableNextColumn();
ImGui::Text(tableRow.m_bindFlags.c_str());
ImGui::TableNextColumn();
}
}
ImGui::EndTable();
}
inline void ImGuiGpuMemoryView::UpdateTableRows()
{
// Update the table according to the latest filters applied
m_tableRows.clear();
for (const auto& pool : m_savedPools)
{
Name poolName = pool.m_name.IsEmpty() ? Name("Unnamed pool") : pool.m_name;
// Ignore transient pools
if (!m_includeTransientAttachments && pool.m_name.GetStringView().contains("Transient"))
{
continue;
}
if (m_includeBuffers)
{
for (const auto& buf : pool.m_buffers)
{
const Name bufName = buf.m_name.IsEmpty() ? Name("Unnamed Buffer") : buf.m_name;
const AZStd::string flags = GpuProfilerImGuiHelper::GetBufferBindStrings(buf.m_bindFlags);
m_tableRows.push_back({ poolName, bufName, buf.m_sizeInBytes, flags });
}
}
if (m_includeImages)
{
for (const auto& img : pool.m_images)
{
const Name imgName = img.m_name.IsEmpty() ? Name("Unnamed Image") : img.m_name;
const AZStd::string flags = GpuProfilerImGuiHelper::GetImageBindStrings(img.m_bindFlags);
m_tableRows.push_back({ poolName, imgName, img.m_sizeInBytes, flags });
}
}
}
}
inline void ImGuiGpuMemoryView::DrawPieChart(const AZ::RHI::MemoryStatistics::Heap& heap)
{
if (ImGui::BeginChild("PieChart", {150, 150}, true))
{
ImDrawList* drawList = ImGui::GetWindowDrawList();
const auto [wx, wy] = ImGui::GetWindowPos();
const auto [windowWidth, windowHeight] = ImGui::GetWindowSize();
const ImVec2 center = { wx + windowWidth / 2, wy + windowHeight / 2 };
const float radius = windowWidth / 2 - 10;
// Draw the pie chart
drawList->AddCircleFilled(center, radius, ImGui::GetColorU32({.3, .3, .3, 1}));
const float usagePercent = 1.0f * heap.m_memoryUsage.m_residentInBytes / heap.m_memoryUsage.m_budgetInBytes;
drawList->PathArcTo(center, radius, 0, AZ::Constants::TwoPi * usagePercent); // Clockwise starting from rightmost point
drawList->PathArcTo(center, 0, 0, 0); // To center
drawList->PathArcTo(center, radius, 0, 0); // Back to starting position
drawList->PathFillConvex(ImGui::GetColorU32({ .039, .8, 0.556, 1 }));
ImGui::Text("%.2f%%", usagePercent * 100);
}
ImGui::EndChild();
}
inline void ImGuiGpuMemoryView::DrawGpuMemoryWindow(bool& draw)
{
// Enable GPU memory instrumentation while the window is open. Called every draw frame, but just a bitwise operation so overhead should be low.
auto* rhiSystem = AZ::RHI::RHISystemInterface::Get();
AZ_Assert(rhiSystem != nullptr, "Error in drawing GPU memory window: RHI System Interface was nullptr");
rhiSystem->ModifyFrameSchedulerStatisticsFlags(AZ::RHI::FrameSchedulerStatisticsFlags::GatherMemoryStatistics, draw);
if (!draw)
{
return;
}
ImGui::SetNextWindowSize({ 600, 600 }, ImGuiCond_Once);
if (ImGui::Begin("Gpu Memory Profiler", &draw, ImGuiViewportFlags_None))
{
if (ImGui::Button("Capture"))
{
// Collect and save new GPU memory usage data
const auto* memoryStatistics = rhiSystem->GetMemoryStatistics();
if (memoryStatistics)
{
m_tableRows.clear();
m_savedPools = memoryStatistics->m_pools;
m_savedHeaps = memoryStatistics->m_heaps;
// Collect the data into TableRows, ignoring depending on flags
UpdateTableRows();
}
}
if (ImGui::Checkbox("Show buffers", &m_includeBuffers)
|| ImGui::Checkbox("Show images", &m_includeImages)
|| ImGui::Checkbox("Show transient attachments", &m_includeTransientAttachments))
{
UpdateTableRows();
}
ImGui::Text("Overall heap usage:");
for (const auto& savedHeap : m_savedHeaps)
{
if (ImGui::BeginChild(savedHeap.m_name.GetCStr(), { ImGui::GetWindowWidth() / m_savedHeaps.size(), 250 }), ImGuiWindowFlags_NoScrollbar)
{
ImGui::Text(savedHeap.m_name.GetCStr());
ImGui::Columns(2, "HeapData", true);
ImGui::Text("Resident (MB): ");
ImGui::NextColumn();
ImGui::Text("%.2f", 1.0 * savedHeap.m_memoryUsage.m_residentInBytes.load() / GpuProfilerImGuiHelper::MB);
ImGui::NextColumn();
ImGui::Text("Reserved (MB): ");
ImGui::NextColumn();
ImGui::Text("%.2f", 1.0 * savedHeap.m_memoryUsage.m_reservedInBytes.load() / GpuProfilerImGuiHelper::MB);
ImGui::NextColumn();
ImGui::Text("Budget (MB): ");
ImGui::NextColumn();
ImGui::Text("%.2f", 1.0 * savedHeap.m_memoryUsage.m_budgetInBytes / GpuProfilerImGuiHelper::MB);
ImGui::Columns(1, "PieChartColumn");
DrawPieChart(savedHeap);
}
ImGui::EndChild();
ImGui::SameLine(ImGui::GetWindowWidth() / m_savedHeaps.size());
}
ImGui::NewLine();
ImGui::Separator();
m_nameFilter.Draw("Search");
DrawTable();
}
}
// --- ImGuiGpuProfiler ---
inline void ImGuiGpuProfiler::Draw(bool& draw, RHI::Ptr<RPI::ParentPass> rootPass)
@@ -1045,6 +1293,8 @@ namespace AZ
{
rootPass->SetPipelineStatisticsQueryEnabled(m_drawPipelineStatisticsView);
}
ImGui::Spacing();
ImGui::Checkbox("Enable GpuMemoryView", &m_drawGpuMemoryView);
});
// Draw the PipelineStatistics window.
@@ -1053,6 +1303,9 @@ namespace AZ
// Draw the PipelineStatistics window.
m_pipelineStatisticsView.DrawPipelineStatisticsWindow(m_drawPipelineStatisticsView, rootPassEntryRef, m_passEntryDatabase, rootPass);
// Draw the GpuMemory window.
m_gpuMemoryView.DrawGpuMemoryWindow(m_drawGpuMemoryView);
//closing window
if (wasDraw && !draw)
{
@@ -6,7 +6,9 @@
*
*/
#include <PostProcess/GradientWeightModifier/GradientWeightModifierController.h>
#include <AzCore/Serialization/SerializeContext.h>
namespace AZ
{
@@ -7,6 +7,7 @@
*/
#include <PostProcess/RadiusWeightModifier/RadiusWeightModifierComponentController.h>
#include <AzCore/RTTI/BehaviorContext.h>
namespace AZ
{
@@ -8,6 +8,7 @@
#include <PostProcess/ShapeWeightModifier/ShapeWeightModifierComponentController.h>
#include <LmbrCentral/Shape/ShapeComponentBus.h>
#include <AzCore/Serialization/SerializeContext.h>
namespace AZ
{
@@ -7,6 +7,7 @@
*/
#include <Scripting/EntityReferenceComponentController.h>
#include <AzCore/Serialization/SerializeContext.h>
namespace AZ
{
@@ -9,6 +9,7 @@
#include <AudioAllocators.h>
#include <AudioLogger.h>
#include <SoundCVars.h>
#include <AzCore/Memory/OSAllocator.h>
namespace Audio::Platform
{
@@ -10,6 +10,7 @@
#include "EMotionFXConfig.h"
#include <AzCore/std/containers/vector.h>
#include <AzCore/Asset/AssetCommon.h>
#include <AzCore/std/string/string.h>
#include "BaseObject.h"
#include "VertexAttributeLayer.h"
@@ -14,6 +14,7 @@
#include <AzCore/Asset/AssetCommon.h>
#include <AzCore/Asset/AssetManagerBus.h>
#include <AzCore/Component/TickBus.h>
#include <AzCore/std/containers/map.h>
namespace ImGui
{
@@ -12,6 +12,7 @@
#include "AzCore/Asset/AssetManager.h"
#include <AzCore/Console/IConsole.h>
#include <AzCore/Jobs/JobFunction.h>
#include <AzCore/Serialization/SerializeContext.h>
namespace LmbrCentral
{
@@ -12,6 +12,7 @@
#include <AzCore/Component/ComponentApplicationBus.h>
#include <AzCore/Component/Entity.h>
#include <AzCore/RTTI/BehaviorContext.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <LyShine/Bus/UiGameEntityContextBus.h>
#include <LyShine/Bus/UiElementBus.h>
@@ -130,6 +131,29 @@ public:
}
};
////////////////////////////////////////////////////////////////////////////////////////////////////
static bool UiFlipbookAnimationComponentVersionConverter(AZ::SerializeContext& context,
AZ::SerializeContext::DataElementNode& classElement)
{
// conversion from version 2:
// - Rename "frame delay" to "framerate"
// - Set "framerate unit" to seconds (default moving forward is FPS, but we use seconds for legacy compatibility)
if (classElement.GetVersion() <= 2)
{
if (!ConvertFrameDelayToFramerate(context, classElement))
{
return false;
}
if (!ConvertFramerateUnitToSeconds(context, classElement))
{
return false;
}
}
return true;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void UiFlipbookAnimationComponent::Reflect(AZ::ReflectContext* context)
@@ -138,7 +162,7 @@ void UiFlipbookAnimationComponent::Reflect(AZ::ReflectContext* context)
if (serializeContext)
{
serializeContext->Class<UiFlipbookAnimationComponent, AZ::Component>()
->Version(3, &VersionConverter)
->Version(3, &UiFlipbookAnimationComponentVersionConverter)
->Field("Start Frame", &UiFlipbookAnimationComponent::m_startFrame)
->Field("End Frame", &UiFlipbookAnimationComponent::m_endFrame)
->Field("Loop Start Frame", &UiFlipbookAnimationComponent::m_loopStartFrame)
@@ -268,29 +292,6 @@ void UiFlipbookAnimationComponent::Reflect(AZ::ReflectContext* context)
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
bool UiFlipbookAnimationComponent::VersionConverter(AZ::SerializeContext& context,
AZ::SerializeContext::DataElementNode& classElement)
{
// conversion from version 2:
// - Rename "frame delay" to "framerate"
// - Set "framerate unit" to seconds (default moving forward is FPS, but we use seconds for legacy compatibility)
if (classElement.GetVersion() <= 2)
{
if (!ConvertFrameDelayToFramerate(context, classElement))
{
return false;
}
if (!ConvertFramerateUnitToSeconds(context, classElement))
{
return false;
}
}
return true;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
AZ::u32 UiFlipbookAnimationComponent::GetMaxFrame() const
{
@@ -107,9 +107,6 @@ protected: // static functions
static void Reflect(AZ::ReflectContext* context);
//////////////////////////////////////////////////////////////////////////
static bool VersionConverter(AZ::SerializeContext& context,
AZ::SerializeContext::DataElementNode& classElement);
protected: // functions
//! Returns a string representation of the indices used to index sprite-sheet types.
@@ -30,7 +30,7 @@ namespace Multiplayer
virtual bool ReplicationSetUpdateReady() = 0;
virtual const ReplicationSet& GetReplicationSet() const = 0;
//! Max number of entities we can send updates for in one frame
virtual uint32_t GetMaxEntityReplicatorSendCount() const = 0;
virtual uint32_t GetMaxProxyEntityReplicatorSendCount() const = 0;
virtual bool IsInWindow(const ConstNetworkEntityHandle& entityPtr, NetEntityRole& outNetworkRole) const = 0;
virtual void UpdateWindow() = 0;
virtual void DebugDraw() const = 0;
@@ -459,6 +459,7 @@ namespace {{ Component.attrib['Namespace'] }}
{{ DeclareNetworkPropertyGetters(Component, 'Authority', 'Server', false)|indent(8) -}}
{{ DeclareNetworkPropertyGetters(Component, 'Authority', 'Client', false)|indent(8) -}}
{{ DeclareNetworkPropertyGetters(Component, 'Autonomous', 'Authority', false)|indent(8) -}}
{{ DeclareArchetypePropertyGetters(Component)|indent(8) -}}
{{ DeclareRpcInvocations(Component, 'Server', 'Authority', false)|indent(8) -}}
{{ AutoComponentMacros.DeclareRpcEventGetters(Component, 'Authority', 'Client')|indent(8) -}}
@@ -483,6 +484,7 @@ namespace {{ Component.attrib['Namespace'] }}
{{ DeclareNetworkPropertyGetters(Component, 'Authority', 'Server', true)|indent(8) -}}
{{ DeclareNetworkPropertyGetters(Component, 'Authority', 'Client', true)|indent(8) -}}
{{ DeclareNetworkPropertyGetters(Component, 'Autonomous', 'Authority', true)|indent(8) -}}
{{ DeclareRpcInvocations(Component, 'Server', 'Authority', true)|indent(8) -}}
{{ AutoComponentMacros.DeclareRpcHandlers(Component, 'Authority', 'Client', false)|indent(8) -}}
{{ AutoComponentMacros.DeclareRpcSignals(Component, 'Authority', 'Client')|indent(8) -}}
@@ -1548,8 +1548,10 @@ namespace {{ Component.attrib['Namespace'] }}
{{ DefineNetworkPropertyGets(Component, 'Authority', 'Server', false, ComponentBaseName)|indent(4) -}}
{{ DefineNetworkPropertyGets(Component, 'Authority', 'Client', false, ComponentBaseName)|indent(4) -}}
{{ DefineNetworkPropertyGets(Component, 'Autonomous', 'Authority', false, ComponentBaseName)|indent(4) -}}
{{ DefineNetworkPropertyGets(Component, 'Authority', 'Server', true, ComponentBaseName)|indent(4) -}}
{{ DefineNetworkPropertyGets(Component, 'Authority', 'Client', true, ComponentBaseName)|indent(4) }}
{{ DefineNetworkPropertyGets(Component, 'Autonomous', 'Authority', true, ComponentBaseName)|indent(4) }}
{{ DefineArchetypePropertyGets(Component, ClassType, ComponentBaseName)|indent(4) -}}
{{ DefineRpcInvocations(Component, ComponentBaseName, 'Server', 'Authority', false)|indent(4) -}}
{{ DefineRpcInvocations(Component, ComponentBaseName, 'Server', 'Authority', true)|indent(4) }}
@@ -660,6 +660,7 @@ namespace Multiplayer
AZStd::unique_ptr<IReplicationWindow> window = AZStd::make_unique<NullReplicationWindow>();
reinterpret_cast<ClientToServerConnectionData*>(connection->GetUserData())->GetReplicationManager().SetEntityActivationTimeSliceMs(cl_defaultNetworkEntityActivationTimeSliceMs);
reinterpret_cast<ClientToServerConnectionData*>(connection->GetUserData())->GetReplicationManager().SetReplicationWindow(AZStd::move(window));
}
}
@@ -185,16 +185,14 @@ namespace Multiplayer
// Generate a list of all our entities that need updates
EntityReplicatorList toSendList;
uint32_t elementsAdded = 0;
for (auto iter = m_replicatorsPendingSend.begin(); iter != m_replicatorsPendingSend.end() && elementsAdded < m_replicationWindow->GetMaxEntityReplicatorSendCount(); )
uint32_t proxySendCount = 0;
for (auto iter = m_replicatorsPendingSend.begin(); iter != m_replicatorsPendingSend.end();)
{
EntityReplicator* replicator = GetEntityReplicator(*iter);
bool clearPendingSend = true;
if (replicator)
if (EntityReplicator* replicator = GetEntityReplicator(*iter))
{
NetEntityId entityId = replicator->GetEntityHandle().GetNetEntityId();
PropertyPublisher* propPublisher = replicator->GetPropertyPublisher();
if (propPublisher)
if (PropertyPublisher* propPublisher = replicator->GetPropertyPublisher())
{
// don't have too many replicators pending creation outstanding at a time
bool canSend = true;
@@ -220,19 +218,17 @@ namespace Multiplayer
m_remoteEntitiesPendingCreation.insert(entityId);
}
if (replicator->GetRemoteNetworkRole() == NetEntityRole::Autonomous)
if (replicator->GetRemoteNetworkRole() == NetEntityRole::Autonomous ||
replicator->GetBoundLocalNetworkRole() == NetEntityRole::Autonomous)
{
toSendList.push_back(replicator);
}
else
else if (proxySendCount < m_replicationWindow->GetMaxProxyEntityReplicatorSendCount())
{
if (elementsAdded < m_replicationWindow->GetMaxEntityReplicatorSendCount())
{
toSendList.push_back(replicator);
}
toSendList.push_back(replicator);
++proxySendCount;
}
}
++elementsAdded;
}
}
@@ -305,16 +305,12 @@ namespace Multiplayer
bool EntityReplicator::RemoteManagerOwnsEntityLifetime() const
{
bool ret(false);
bool isServer = (GetBoundLocalNetworkRole() == NetEntityRole::Server)
&& (GetRemoteNetworkRole() == NetEntityRole::Authority);
bool isClient = (GetBoundLocalNetworkRole() == NetEntityRole::Client)
|| (GetBoundLocalNetworkRole() == NetEntityRole::Autonomous);
if (isServer || isClient)
{
ret = true;
}
return ret;
return isServer || isClient;
}
void EntityReplicator::MarkForRemoval()
@@ -22,6 +22,14 @@ namespace Multiplayer
, m_pendingRecord(remoteNetworkRole)
, m_sentRecords(net_EntityReplicatorRecordsMax)
{
if ( ownsLifetime == OwnsLifetime::False )
{
// This entity is owned by some other authority; this publisher will only be used for updating (not creating).
// Since this replicator does not own it's lifetime, the remote replicator must exist (otherwise, we would never have created a replicator that doesn't own its lifetime).
m_remoteReplicatorEstablished = true;
m_replicatorState = EntityReplicatorState::Updating;
}
AZ_Assert(m_netBindComponent, "NetBindComponent is nullptr");
m_pendingRecord.SetRemoteNetworkRole(remoteNetworkRole);
}
@@ -20,7 +20,7 @@ namespace Multiplayer
return m_emptySet;
}
uint32_t NullReplicationWindow::GetMaxEntityReplicatorSendCount() const
uint32_t NullReplicationWindow::GetMaxProxyEntityReplicatorSendCount() const
{
return 0;
}
@@ -22,7 +22,7 @@ namespace Multiplayer
//! @{
bool ReplicationSetUpdateReady() override;
const ReplicationSet& GetReplicationSet() const override;
uint32_t GetMaxEntityReplicatorSendCount() const override;
uint32_t GetMaxProxyEntityReplicatorSendCount() const override;
bool IsInWindow(const ConstNetworkEntityHandle& entityPtr, NetEntityRole& outNetworkRole) const override;
void UpdateWindow() override;
void DebugDraw() const override;

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