Merge remote-tracking branch 'upstream/development' into ReturnCachedGemJsons

monroegm-disable-blank-issue-2
AMZN-Phil 4 years ago
commit e8cd6add51

@ -54,4 +54,5 @@ set(ENABLED_GEMS
AWSMetrics
PrefabBuilder
AudioSystem
Profiler
)

@ -204,7 +204,7 @@ namespace PythonCoverage
return coveringModuleOutputNames;
}
void PythonCoverageEditorSystemComponent::OnStartExecuteByFilenameAsTest(AZStd::string_view filename, AZStd::string_view testCase, [[maybe_unused]] const AZStd::vector<AZStd::string_view>& args)
void PythonCoverageEditorSystemComponent::OnStartExecuteByFilenameAsTest([[maybe_unused]]AZStd::string_view filename, AZStd::string_view testCase, [[maybe_unused]] const AZStd::vector<AZStd::string_view>& args)
{
if (m_coverageState == CoverageState::Disabled)
{
@ -226,8 +226,7 @@ namespace PythonCoverage
return;
}
const AZStd::string scriptName = AZ::IO::Path(filename).Stem().Native();
const auto coverageFile = m_coverageDir / AZStd::string::format("%s.pycoverage", scriptName.c_str());
const auto coverageFile = m_coverageDir / AZStd::string::format("%.*s.pycoverage", AZ_STRING_ARG(testCase));
// If this is a different python script we clear the existing entity components and start afresh
if (m_coverageFile != coverageFile)

@ -122,17 +122,32 @@ class EditorEntity:
# Creation functions
@classmethod
def find_editor_entity(cls, entity_name: str) -> EditorEntity:
def find_editor_entity(cls, entity_name: str, must_be_unique : bool = False) -> EditorEntity:
"""
Given Entity name, outputs entity object
:param entity_name: Name of entity to find
:return: EditorEntity class object
"""
entity_id = general.find_editor_entity(entity_name)
assert entity_id.IsValid(), f"Failure: Couldn't find entity with name: '{entity_name}'"
entity = cls(entity_id)
entities = cls.find_editor_entities([entity_name])
assert len(entities) != 0, f"Failure: Couldn't find entity with name: '{entity_name}'"
if must_be_unique:
assert len(entities) == 1, f"Failure: Multiple entities with name: '{entity_name}' when expected only one"
entity = cls(entities[0])
return entity
@classmethod
def find_editor_entities(cls, entity_names: List[str]) -> EditorEntity:
"""
Given Entities names, returns a list of EditorEntity
:param entity_name: Name of entity to find
:return: List[EditorEntity] class object
"""
searchFilter = azlmbr.entity.SearchFilter()
searchFilter.names = entity_names
ids = azlmbr.entity.SearchBus(bus.Broadcast, 'SearchEntities', searchFilter)
return [cls(id) for id in ids]
@classmethod
def create_editor_entity(cls, name: str = None, parent_id=None) -> EditorEntity:
"""
@ -157,8 +172,7 @@ class EditorEntity:
cls,
entity_position: Union[List, Tuple, math.Vector3],
name: str = None,
parent_id: azlmbr.entity.EntityId = None,
) -> EditorEntity:
parent_id: azlmbr.entity.EntityId = None) -> EditorEntity:
"""
Used to create entity at position using 'CreateNewEntityAtPosition' Bus.
:param entity_position: World Position(X, Y, Z) of entity in viewport.
@ -227,6 +241,12 @@ class EditorEntity:
"""
return editor.EditorEntityInfoRequestBus(bus.Event, "GetChildren", self.id)
def get_children(self) -> List[EditorEntity]:
"""
:return: List of EditorEntity children. Type: [EditorEntity]
"""
return [EditorEntity(child_id) for child_id in self.get_children_ids()]
def add_component(self, component_name: str) -> EditorComponent:
"""
Used to add new component to Entity.

@ -51,7 +51,7 @@ def launch_and_validate_results(request, test_directory, editor, editor_script,
logger.debug("Running automated test: {}".format(editor_script))
editor.args.extend(["--skipWelcomeScreenDialog", "--regset=/Amazon/Settings/EnableSourceControl=false",
"--regset=/Amazon/Preferences/EnablePrefabSystem=false", run_python, test_case,
f"--pythontestcase={request.node.originalname}", "--runpythonargs", " ".join(cfg_args)])
f"--pythontestcase={request.node.name}", "--runpythonargs", " ".join(cfg_args)])
if auto_test_mode:
editor.args.extend(["--autotest_mode"])
if null_renderer:

@ -12,20 +12,39 @@ from os import path
from PySide2 import QtWidgets
import azlmbr.legacy.general as general
from azlmbr.entity import EntityId
from azlmbr.math import Vector3
from editor_python_test_tools.editor_entity_utils import EditorEntity
from editor_python_test_tools.utils import Report
import azlmbr.entity as entity
import azlmbr.bus as bus
import azlmbr.prefab as prefab
import editor_python_test_tools.pyside_utils as pyside_utils
import prefab.Prefab_Test_Utils as prefab_test_utils
def get_prefab_file_path(prefab_path):
if not path.isabs(prefab_path):
prefab_path = path.join(general.get_file_alias("@projectroot@"), prefab_path)
# Append prefab if it doesn't contain .prefab on it
name, ext = path.splitext(prefab_path)
if ext != ".prefab":
prefab_path = name + ".prefab"
return prefab_path
def get_all_entity_ids():
return entity.SearchBus(bus.Broadcast, 'SearchEntities', entity.SearchFilter())
def wait_for_propagation():
general.idle_wait_frames(1)
# This is a helper class which contains some of the useful information about a prefab instance.
class PrefabInstance:
def __init__(self, prefab_file_name: str=None, container_entity: EditorEntity=EntityId()):
def __init__(self, prefab_file_name: str = None, container_entity: EditorEntity = None):
self.prefab_file_name: str = prefab_file_name
self.container_entity: EditorEntity = container_entity
@ -42,7 +61,7 @@ class PrefabInstance:
See if this instance is valid to be used with other prefab operations.
:return: Whether the target instance is valid or not.
"""
def is_valid() -> bool:
def is_valid(self) -> bool:
return self.container_entity.id.IsValid() and self.prefab_file_name in Prefab.existing_prefabs
"""
@ -60,7 +79,7 @@ class PrefabInstance:
new_parent_before_reparent_children_ids = set(new_parent.get_children_ids())
pyside_utils.run_soon(lambda: self.container_entity.set_parent_entity(parent_entity_id))
pyside_utils.run_soon(lambda: prefab_test_utils.wait_for_propagation())
pyside_utils.run_soon(lambda: wait_for_propagation())
try:
active_modal_widget = await pyside_utils.wait_for_modal_widget()
@ -94,19 +113,18 @@ class Prefab:
existing_prefabs = {}
def __init__(self, file_name: str):
self.file_name:str = file_name
self.file_path: str = prefab_test_utils.get_prefab_file_path(file_name)
def __init__(self, file_path: str):
self.file_path: str = get_prefab_file_path(file_path)
self.instances: set[PrefabInstance] = set()
"""
Check if a prefab is ready to be used to generate its instances.
:param file_name: A unique file name of the target prefab.
:param file_path: A unique file path of the target prefab.
:return: Whether the target prefab is loaded or not.
"""
@classmethod
def is_prefab_loaded(cls, file_name: str) -> bool:
return file_name in Prefab.existing_prefabs
def is_prefab_loaded(cls, file_path: str) -> bool:
return file_path in Prefab.existing_prefabs
"""
Check if a prefab exists in the directory for files of prefab tests.
@ -114,9 +132,8 @@ class Prefab:
:return: Whether the target prefab exists or not.
"""
@classmethod
def prefab_exists(cls, file_name: str) -> bool:
file_path = prefab_test_utils.get_prefab_file_path(file_name)
return path.exists(file_path)
def prefab_exists(cls, file_path: str) -> bool:
return path.exists(get_prefab_file_path(file_path))
"""
Return a prefab which can be used immediately.
@ -125,10 +142,11 @@ class Prefab:
"""
@classmethod
def get_prefab(cls, file_name: str) -> Prefab:
assert file_name, "Received an empty file_name"
if Prefab.is_prefab_loaded(file_name):
return Prefab.existing_prefabs[file_name]
else:
assert Prefab.prefab_exists(file_name), f"Attempted to get a prefab {file_name} that doesn't exist"
assert Prefab.prefab_exists(file_name), f"Attempted to get a prefab \"{file_name}\" that doesn't exist"
new_prefab = Prefab(file_name)
Prefab.existing_prefabs[file_name] = Prefab(file_name)
return new_prefab
@ -141,7 +159,7 @@ class Prefab:
:return: Created Prefab object and the very first PrefabInstance object owned by the prefab.
"""
@classmethod
def create_prefab(cls, entities: list[EditorEntity], file_name: str, prefab_instance_name: str=None) -> (Prefab, PrefabInstance):
def create_prefab(cls, entities: list[EditorEntity], file_name: str, prefab_instance_name: str=None) -> tuple(Prefab, PrefabInstance):
assert not Prefab.is_prefab_loaded(file_name), f"Can't create Prefab '{file_name}' since the prefab already exists"
new_prefab = Prefab(file_name)
@ -155,7 +173,7 @@ class Prefab:
if prefab_instance_name:
container_entity.set_name(prefab_instance_name)
prefab_test_utils.wait_for_propagation()
wait_for_propagation()
new_prefab_instance = PrefabInstance(file_name, EditorEntity(container_entity_id))
new_prefab.instances.add(new_prefab_instance)
@ -182,12 +200,13 @@ class Prefab:
delete_prefab_result = prefab.PrefabPublicRequestBus(bus.Broadcast, 'DeleteEntitiesAndAllDescendantsInInstance', container_entity_ids)
assert delete_prefab_result.IsSuccess(), f"Prefab operation 'DeleteEntitiesAndAllDescendantsInInstance' failed. Error: {delete_prefab_result.GetError()}"
prefab_test_utils.wait_for_propagation()
wait_for_propagation()
entity_ids_after_delete = set(prefab_test_utils.get_all_entities())
entity_ids_after_delete = set(get_all_entity_ids())
for entity_id_removed in entity_ids_to_remove:
if entity_id_removed in entity_ids_after_delete:
assert prefab_entities_deleted, "Not all entities and descendants in target prefabs are deleted."
assert False, "Not all entities and descendants in target prefabs are deleted."
for instance in prefab_instances:
instance_deleted_prefab = Prefab.get_prefab(instance.prefab_file_name)
@ -215,12 +234,10 @@ class Prefab:
if name:
container_entity.set_name(name)
prefab_test_utils.wait_for_propagation()
wait_for_propagation()
new_prefab_instance = PrefabInstance(self.file_name, EditorEntity(container_entity_id))
new_prefab_instance = PrefabInstance(self.file_path, EditorEntity(container_entity_id))
assert not new_prefab_instance in self.instances, "This prefab instance is already existed before this instantiation."
self.instances.add(new_prefab_instance)
prefab_test_utils.check_entity_at_position(container_entity_id, prefab_position)
return new_prefab_instance

@ -126,7 +126,7 @@ def ForceRegion_LinearDampingForceOnRigidBodies():
# Constants
CLOSE_ENOUGH = 0.001
TIME_OUT = 3.0
TIME_OUT = 10.0
INITIAL_VELOCITY = azmath.Vector3(0.0, 0.0, -10.0)
# 1) Open level / Enter game mode

@ -90,7 +90,7 @@ class TestAutomationBase:
editor_starttime = time.time()
self.logger.debug("Running automated test")
testcase_module_filepath = self._get_testcase_module_filepath(testcase_module)
pycmd = ["--runpythontest", testcase_module_filepath, f"-pythontestcase={request.node.originalname}"]
pycmd = ["--runpythontest", testcase_module_filepath, f"-pythontestcase={request.node.name}"]
if use_null_renderer:
pycmd += ["-rhi=null"]
if batch_mode:

@ -29,21 +29,21 @@ class TestAutomation(TestAutomationBase):
autotest_mode=autotest_mode)
def test_PrefabLevel_OpensLevelWithEntities(self, request, workspace, editor, launcher_platform):
from . import PrefabLevel_OpensLevelWithEntities as test_module
from .tests import PrefabLevel_OpensLevelWithEntities as test_module
self._run_prefab_test(request, workspace, editor, test_module)
def test_Prefab_BasicWorkflow_CreatePrefab(self, request, workspace, editor, launcher_platform):
from . import Prefab_BasicWorkflow_CreatePrefab as test_module
def test_PrefabBasicWorkflow_CreatePrefab(self, request, workspace, editor, launcher_platform):
from .tests import PrefabBasicWorkflow_CreatePrefab as test_module
self._run_prefab_test(request, workspace, editor, test_module)
def test_Prefab_BasicWorkflow_InstantiatePrefab(self, request, workspace, editor, launcher_platform):
from . import Prefab_BasicWorkflow_InstantiatePrefab as test_module
def test_PrefabBasicWorkflow_InstantiatePrefab(self, request, workspace, editor, launcher_platform):
from .tests import PrefabBasicWorkflow_InstantiatePrefab as test_module
self._run_prefab_test(request, workspace, editor, test_module)
def test_Prefab_BasicWorkflow_CreateAndDeletePrefab(self, request, workspace, editor, launcher_platform):
from . import Prefab_BasicWorkflow_CreateAndDeletePrefab as test_module
def test_PrefabBasicWorkflow_CreateAndDeletePrefab(self, request, workspace, editor, launcher_platform):
from .tests import PrefabBasicWorkflow_CreateAndDeletePrefab as test_module
self._run_prefab_test(request, workspace, editor, test_module)
def test_Prefab_BasicWorkflow_CreateAndReparentPrefab(self, request, workspace, editor, launcher_platform):
from . import Prefab_BasicWorkflow_CreateAndReparentPrefab as test_module
def test_PrefabBasicWorkflow_CreateAndReparentPrefab(self, request, workspace, editor, launcher_platform):
from .tests import PrefabBasicWorkflow_CreateAndReparentPrefab as test_module
self._run_prefab_test(request, workspace, editor, test_module, autotest_mode=False)

@ -5,14 +5,14 @@ For complete copyright and license terms please see the LICENSE at the root of t
SPDX-License-Identifier: Apache-2.0 OR MIT
"""
def Prefab_BasicWorkflow_CreateAndDeletePrefab():
def PrefabBasicWorkflow_CreateAndDeletePrefab():
CAR_PREFAB_FILE_NAME = 'car_prefab'
from editor_python_test_tools.editor_entity_utils import EditorEntity
from prefab.Prefab import Prefab
from editor_python_test_tools.prefab_utils import Prefab
import prefab.Prefab_Test_Utils as prefab_test_utils
import PrefabTestUtils as prefab_test_utils
prefab_test_utils.open_base_tests_level()
@ -21,13 +21,13 @@ def Prefab_BasicWorkflow_CreateAndDeletePrefab():
car_entity = EditorEntity.create_editor_entity()
car_prefab_entities = [car_entity]
# Checks for prefab creation passed or not
# Asserts if prefab creation doesn't succeeds
_, car = Prefab.create_prefab(
car_prefab_entities, CAR_PREFAB_FILE_NAME)
# Checks for prefab deletion passed or not
# Asserts if prefab deletion fails
Prefab.remove_prefabs([car])
if __name__ == "__main__":
from editor_python_test_tools.utils import Report
Report.start_test(Prefab_BasicWorkflow_CreateAndDeletePrefab)
Report.start_test(PrefabBasicWorkflow_CreateAndDeletePrefab)

@ -5,7 +5,7 @@ For complete copyright and license terms please see the LICENSE at the root of t
SPDX-License-Identifier: Apache-2.0 OR MIT
"""
def Prefab_BasicWorkflow_CreateAndReparentPrefab():
def PrefabBasicWorkflow_CreateAndReparentPrefab():
CAR_PREFAB_FILE_NAME = 'car_prefab'
WHEEL_PREFAB_FILE_NAME = 'wheel_prefab'
@ -16,9 +16,9 @@ def Prefab_BasicWorkflow_CreateAndReparentPrefab():
async def run_test():
from editor_python_test_tools.editor_entity_utils import EditorEntity
from prefab.Prefab import Prefab
from editor_python_test_tools.prefab_utils import Prefab
import prefab.Prefab_Test_Utils as prefab_test_utils
import PrefabTestUtils as prefab_test_utils
prefab_test_utils.open_base_tests_level()
@ -46,4 +46,4 @@ def Prefab_BasicWorkflow_CreateAndReparentPrefab():
if __name__ == "__main__":
from editor_python_test_tools.utils import Report
Report.start_test(Prefab_BasicWorkflow_CreateAndReparentPrefab)
Report.start_test(PrefabBasicWorkflow_CreateAndReparentPrefab)

@ -5,15 +5,15 @@ For complete copyright and license terms please see the LICENSE at the root of t
SPDX-License-Identifier: Apache-2.0 OR MIT
"""
def Prefab_BasicWorkflow_CreatePrefab():
def PrefabBasicWorkflow_CreatePrefab():
CAR_PREFAB_FILE_NAME = 'car_prefab'
from editor_python_test_tools.editor_entity_utils import EditorEntity
from editor_python_test_tools.utils import Report
from prefab.Prefab import Prefab
from editor_python_test_tools.prefab_utils import Prefab
import prefab.Prefab_Test_Utils as prefab_test_utils
import PrefabTestUtils as prefab_test_utils
prefab_test_utils.open_base_tests_level()
@ -27,4 +27,4 @@ def Prefab_BasicWorkflow_CreatePrefab():
if __name__ == "__main__":
from editor_python_test_tools.utils import Report
Report.start_test(Prefab_BasicWorkflow_CreatePrefab)
Report.start_test(PrefabBasicWorkflow_CreatePrefab)

@ -5,17 +5,17 @@ For complete copyright and license terms please see the LICENSE at the root of t
SPDX-License-Identifier: Apache-2.0 OR MIT
"""
def Prefab_BasicWorkflow_InstantiatePrefab():
def PrefabBasicWorkflow_InstantiatePrefab():
from azlmbr.math import Vector3
EXISTING_TEST_PREFAB_FILE_NAME = "Test"
EXISTING_TEST_PREFAB_FILE_NAME = "Gem/PythonTests/Prefab/data/Test.prefab"
INSTANTIATED_TEST_PREFAB_POSITION = Vector3(10.00, 20.0, 30.0)
EXPECTED_TEST_PREFAB_CHILDREN_COUNT = 1
from prefab.Prefab import Prefab
from editor_python_test_tools.prefab_utils import Prefab
import prefab.Prefab_Test_Utils as prefab_test_utils
import PrefabTestUtils as prefab_test_utils
prefab_test_utils.open_base_tests_level()
@ -31,4 +31,4 @@ def Prefab_BasicWorkflow_InstantiatePrefab():
if __name__ == "__main__":
from editor_python_test_tools.utils import Report
Report.start_test(Prefab_BasicWorkflow_InstantiatePrefab)
Report.start_test(PrefabBasicWorkflow_InstantiatePrefab)

@ -7,9 +7,9 @@ SPDX-License-Identifier: Apache-2.0 OR MIT
# fmt:off
class Tests():
find_empty_entity = ("Entity: 'EmptyEntity' found", "Entity: 'EmptyEntity' *not* found in level")
empty_entity_pos = ("'EmptyEntity' position is at the expected position", "'EmptyEntity' position is *not* at the expected position")
find_pxentity = ("Entity: 'EntityWithPxCollider' found", "Entity: 'EntityWithPxCollider' *not* found in level")
find_empty_entity = ("Entity: 'EmptyEntity' found", "Entity: 'EmptyEntity' *not* found in level")
empty_entity_pos = ("'EmptyEntity' position is at the expected position", "'EmptyEntity' position is *not* at the expected position")
find_pxentity = ("Entity: 'EntityWithPxCollider' found", "Entity: 'EntityWithPxCollider' *not* found in level")
pxentity_component = ("Entity: 'EntityWithPxCollider' has a Physx Collider", "Entity: 'EntityWithPxCollider' does *not* have a Physx Collider")
# fmt:on

@ -18,20 +18,6 @@ import azlmbr.components as components
import azlmbr.entity as entity
import azlmbr.legacy.general as general
def get_prefab_file_name(prefab_name):
return prefab_name + ".prefab"
def get_prefab_file_path(prefab_name):
return os.path.join(os.path.dirname(os.path.abspath(__file__)), get_prefab_file_name(prefab_name))
def find_entities_by_name(entity_name):
searchFilter = entity.SearchFilter()
searchFilter.names = [entity_name]
return entity.SearchBus(bus.Broadcast, 'SearchEntities', searchFilter)
def get_all_entities():
return entity.SearchBus(bus.Broadcast, 'SearchEntities', entity.SearchFilter())
def check_entity_at_position(entity_id, expected_entity_position):
entity_at_expected_position_result = (
"entity is at expected position",
@ -74,9 +60,6 @@ def get_children_ids_by_name(entity_id, entity_name):
return result
def wait_for_propagation():
general.idle_wait_frames(1)
def open_base_tests_level():
helper.init_idle()
helper.open_level("Prefab", "Base")

@ -1,44 +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
UI Apps: AutomatedTesting.GameLauncher
Launch AutomatedTesting.GameLauncher with Simple level
Test should run in both gpu and non gpu
"""
import pytest
import psutil
import ly_test_tools.environment.waiter as waiter
import editor_python_test_tools.hydra_test_utils as editor_test_utils
from ly_remote_console.remote_console_commands import RemoteConsole as RemoteConsole
from ly_remote_console.remote_console_commands import (
send_command_and_expect_response as send_command_and_expect_response,
)
@pytest.mark.parametrize("launcher_platform", ["windows"])
@pytest.mark.parametrize("project", ["AutomatedTesting"])
@pytest.mark.parametrize("level", ["Simple"])
@pytest.mark.SUITE_smoke
class TestRemoteConsoleLoadLevelWorks(object):
@pytest.fixture
def remote_console_instance(self, request):
console = RemoteConsole()
def teardown():
if console.connected:
console.stop()
request.addfinalizer(teardown)
return console
def test_RemoteConsole_LoadLevel_Works(self, launcher, level, remote_console_instance, launcher_platform):
expected_lines = ['Level system is loading "Simple"']
editor_test_utils.launch_and_validate_results_launcher(launcher, level, remote_console_instance, expected_lines, null_renderer=True)

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e4937547ca4c486ef59656314401933217e0e0401fec103e1fb91c25ec60a177
size 2806
oid sha256:a5f9e27e0f22c31ca61d866fb594c6fde5b8ceb891e17dda075fa1e0033ec2b9
size 1666

@ -287,21 +287,22 @@ bool CCryDocManager::DoPromptFileName(QString& fileName, [[maybe_unused]] UINT n
return false;
}
CCryEditDoc* CCryDocManager::OpenDocumentFile(const char* lpszFileName, bool bAddToMRU)
CCryEditDoc* CCryDocManager::OpenDocumentFile(const char* filename, bool addToMostRecentFileList, COpenSameLevelOptions openSameLevelOptions)
{
assert(lpszFileName != nullptr);
assert(filename != nullptr);
const bool reopenIfSame = openSameLevelOptions == COpenSameLevelOptions::ReopenLevelIfSame;
// find the highest confidence
auto pos = m_templateList.begin();
CCrySingleDocTemplate::Confidence bestMatch = CCrySingleDocTemplate::noAttempt;
CCrySingleDocTemplate* pBestTemplate = nullptr;
CCryEditDoc* pOpenDocument = nullptr;
if (lpszFileName[0] == '\"')
if (filename[0] == '\"')
{
++lpszFileName;
++filename;
}
QString szPath = QString::fromUtf8(lpszFileName);
QString szPath = QString::fromUtf8(filename);
if (szPath.endsWith('"'))
{
szPath.remove(szPath.length() - 1, 1);
@ -325,7 +326,7 @@ CCryEditDoc* CCryDocManager::OpenDocumentFile(const char* lpszFileName, bool bAd
}
}
if (pOpenDocument != nullptr)
if (!reopenIfSame && pOpenDocument != nullptr)
{
return pOpenDocument;
}
@ -336,7 +337,7 @@ CCryEditDoc* CCryDocManager::OpenDocumentFile(const char* lpszFileName, bool bAd
return nullptr;
}
return pBestTemplate->OpenDocumentFile(szPath.toUtf8().data(), bAddToMRU, false);
return pBestTemplate->OpenDocumentFile(szPath.toUtf8().data(), addToMostRecentFileList, false);
}
//////////////////////////////////////////////////////////////////////////////
@ -513,7 +514,7 @@ public:
QString m_appRoot;
QString m_logFile;
QString m_pythonArgs;
QString m_pythontTestCase;
QString m_pythonTestCase;
QString m_execFile;
QString m_execLineCmd;
@ -562,7 +563,7 @@ public:
const std::vector<std::pair<CommandLineStringOption, QString&> > stringOptions = {
{{"logfile", "File name of the log file to write out to.", "logfile"}, m_logFile},
{{"runpythonargs", "Command-line argument string to pass to the python script if --runpython or --runpythontest was used.", "runpythonargs"}, m_pythonArgs},
{{"pythontestcase", "Test case name of python test script if --runpythontest was used.", "pythontestcase"}, m_pythontTestCase},
{{"pythontestcase", "Test case name of python test script if --runpythontest was used.", "pythontestcase"}, m_pythonTestCase},
{{"exec", "cfg file to run on startup, used for systems like automation", "exec"}, m_execFile},
{{"rhi", "Command-line argument to force which rhi to use", "dummyString"}, dummyString },
{{"rhi-device-validation", "Command-line argument to configure rhi validation", "dummyString"}, dummyString },
@ -818,7 +819,7 @@ CCryEditDoc* CCrySingleDocTemplate::OpenDocumentFile(const char* lpszPathName, b
return OpenDocumentFile(lpszPathName, true, bMakeVisible);
}
CCryEditDoc* CCrySingleDocTemplate::OpenDocumentFile(const char* lpszPathName, bool bAddToMRU, [[maybe_unused]] bool bMakeVisible)
CCryEditDoc* CCrySingleDocTemplate::OpenDocumentFile(const char* lpszPathName, bool addToMostRecentFileList, [[maybe_unused]] bool bMakeVisible)
{
CCryEditDoc* pCurDoc = GetIEditor()->GetDocument();
@ -848,7 +849,7 @@ CCryEditDoc* CCrySingleDocTemplate::OpenDocumentFile(const char* lpszPathName, b
{
pCurDoc->OnOpenDocument(lpszPathName);
pCurDoc->SetPathName(lpszPathName);
if (bAddToMRU)
if (addToMostRecentFileList)
{
CCryEditApp::instance()->AddToRecentFileList(lpszPathName);
}
@ -1535,11 +1536,12 @@ void CCryEditApp::RunInitPythonScript(CEditCommandLineInfo& cmdInfo)
{
// Multiple testcases can be specified them with ';', these should match the files to run
AZStd::vector<AZStd::string_view> testcaseList;
QByteArray pythonTestCase = cmdInfo.m_pythonTestCase.toUtf8();
testcaseList.resize(fileList.size());
{
int i = 0;
AzFramework::StringFunc::TokenizeVisitor(
fileStr.constData(),
pythonTestCase.constData(),
[&i, &testcaseList](AZStd::string_view elem)
{
testcaseList[i++] = (elem);
@ -2630,7 +2632,7 @@ void CCryEditApp::OnShowHelpers()
void CCryEditApp::OnEditLevelData()
{
auto dir = QFileInfo(GetIEditor()->GetDocument()->GetLevelPathName()).dir();
CFileUtil::EditTextFile(dir.absoluteFilePath("LevelData.xml").toUtf8().data());
CFileUtil::EditTextFile(dir.absoluteFilePath("leveldata.xml").toUtf8().data());
}
//////////////////////////////////////////////////////////////////////////
@ -3364,7 +3366,7 @@ void CCryEditApp::OnOpenSlice()
}
//////////////////////////////////////////////////////////////////////////
CCryEditDoc* CCryEditApp::OpenDocumentFile(const char* lpszFileName)
CCryEditDoc* CCryEditApp::OpenDocumentFile(const char* filename, bool addToMostRecentFileList, COpenSameLevelOptions openSameLevelOptions)
{
if (m_openingLevel)
{
@ -3404,9 +3406,9 @@ CCryEditDoc* CCryEditApp::OpenDocumentFile(const char* lpszFileName)
openDocTraceHandler.SetShowWindow(false);
}
// in this case, we set bAddToMRU to always be true because adding files to the MRU list
// in this case, we set addToMostRecentFileList to always be true because adding files to the MRU list
// automatically culls duplicate and normalizes paths anyway
m_pDocManager->OpenDocumentFile(lpszFileName, true);
m_pDocManager->OpenDocumentFile(filename, addToMostRecentFileList, openSameLevelOptions);
if (openDocTraceHandler.HasAnyErrors())
{

@ -85,6 +85,12 @@ public:
using EditorIdleProcessingBus = AZ::EBus<EditorIdleProcessing>;
enum class COpenSameLevelOptions
{
ReopenLevelIfSame,
NotReopenIfSame
};
AZ_PUSH_DISABLE_DLL_EXPORT_BASECLASS_WARNING
AZ_PUSH_DISABLE_DLL_EXPORT_MEMBER_WARNING
class SANDBOX_API CCryEditApp
@ -174,7 +180,9 @@ public:
virtual bool InitInstance();
virtual int ExitInstance(int exitCode = 0);
virtual bool OnIdle(LONG lCount);
virtual CCryEditDoc* OpenDocumentFile(const char* lpszFileName);
virtual CCryEditDoc* OpenDocumentFile(const char* filename,
bool addToMostRecentFileList=true,
COpenSameLevelOptions openSameLevelOptions = COpenSameLevelOptions::NotReopenIfSame);
CCryDocManager* GetDocManager() { return m_pDocManager; }
@ -448,7 +456,7 @@ public:
~CCrySingleDocTemplate() {};
// avoid creating another CMainFrame
// close other type docs before opening any things
virtual CCryEditDoc* OpenDocumentFile(const char* lpszPathName, bool bAddToMRU, bool bMakeVisible);
virtual CCryEditDoc* OpenDocumentFile(const char* lpszPathName, bool addToMostRecentFileList, bool bMakeVisible);
virtual CCryEditDoc* OpenDocumentFile(const char* lpszPathName, bool bMakeVisible = TRUE);
virtual Confidence MatchDocType(const char* lpszPathName, CCryEditDoc*& rpDocMatch);
@ -468,7 +476,7 @@ public:
virtual void OnFileNew();
virtual bool DoPromptFileName(QString& fileName, UINT nIDSTitle,
DWORD lFlags, bool bOpenFileDialog, CDocTemplate* pTemplate);
virtual CCryEditDoc* OpenDocumentFile(const char* lpszFileName, bool bAddToMRU);
virtual CCryEditDoc* OpenDocumentFile(const char* filename, bool addToMostRecentFileList, COpenSameLevelOptions openSameLevelOptions = COpenSameLevelOptions::NotReopenIfSame);
QVector<CCrySingleDocTemplate*> m_templateList;
};

@ -143,20 +143,11 @@ namespace
return false;
}
}
const bool addToMostRecentFileList = false;
auto newDocument = CCryEditApp::instance()->OpenDocumentFile(levelPath.toUtf8().data(),
addToMostRecentFileList, COpenSameLevelOptions::ReopenLevelIfSame);
auto previousDocument = GetIEditor()->GetDocument();
QString previousPathName = (previousDocument != nullptr) ? previousDocument->GetLevelPathName() : "";
auto newDocument = CCryEditApp::instance()->OpenDocumentFile(levelPath.toUtf8().data());
// the underlying document pointer doesn't change, so we can't check that; use the path name's instead
bool result = true;
if (newDocument == nullptr || newDocument->IsLevelLoadFailed() || (newDocument->GetLevelPathName() == previousPathName))
{
result = false;
}
return result;
return newDocument != nullptr && !newDocument->IsLevelLoadFailed();
}
bool PyOpenLevelNoPrompt(const char* pLevelName)
@ -407,6 +398,11 @@ inline namespace Commands
{
return AZ::Debug::Trace::WaitForDebugger(timeoutSeconds);
}
AZStd::string PyGetFileAlias(AZStd::string alias)
{
return AZ::IO::FileIOBase::GetInstance()->GetAlias(alias.c_str());
}
}
namespace AzToolsFramework
@ -457,6 +453,8 @@ namespace AzToolsFramework
addLegacyGeneral(behaviorContext->Method("attach_debugger", PyAttachDebugger, nullptr, "Prompts for attaching the debugger"));
addLegacyGeneral(behaviorContext->Method("wait_for_debugger", PyWaitForDebugger, behaviorContext->MakeDefaultValues(-1.f), "Pauses this thread execution until the debugger has been attached"));
addLegacyGeneral(behaviorContext->Method("get_file_alias", PyGetFileAlias, nullptr, "Retrieves path for IO alias"));
// this will put these methods into the 'azlmbr.legacy.checkout_dialog' module
auto addCheckoutDialog = [](AZ::BehaviorContext::GlobalMethodBuilder methodBuilder)
{

@ -75,7 +75,10 @@ void CEditorPreferencesPage_ViewportCamera::Reflect(AZ::SerializeContext& serial
->Field("CaptureCursorLook", &CameraMovementSettings::m_captureCursorLook)
->Field("OrbitYawRotationInverted", &CameraMovementSettings::m_orbitYawRotationInverted)
->Field("PanInvertedX", &CameraMovementSettings::m_panInvertedX)
->Field("PanInvertedY", &CameraMovementSettings::m_panInvertedY);
->Field("PanInvertedY", &CameraMovementSettings::m_panInvertedY)
->Field("DefaultPositionX", &CameraMovementSettings::m_defaultCameraPositionX)
->Field("DefaultPositionY", &CameraMovementSettings::m_defaultCameraPositionY)
->Field("DefaultPositionZ", &CameraMovementSettings::m_defaultCameraPositionZ);
serialize.Class<CameraInputSettings>()
->Version(2)
@ -154,7 +157,16 @@ void CEditorPreferencesPage_ViewportCamera::Reflect(AZ::SerializeContext& serial
"Invert direction of pan in local Y axis")
->DataElement(
AZ::Edit::UIHandlers::CheckBox, &CameraMovementSettings::m_captureCursorLook, "Camera Capture Look Cursor",
"Should the cursor be captured (hidden) while performing free look");
"Should the cursor be captured (hidden) while performing free look")
->DataElement(
AZ::Edit::UIHandlers::SpinBox, &CameraMovementSettings::m_defaultCameraPositionX, "Default X Camera Position",
"Default X Camera Position when a level is opened")
->DataElement(
AZ::Edit::UIHandlers::SpinBox, &CameraMovementSettings::m_defaultCameraPositionY, "Default Y Camera Position",
"Default Y Camera Position when a level is opened")
->DataElement(
AZ::Edit::UIHandlers::SpinBox, &CameraMovementSettings::m_defaultCameraPositionZ, "Default Z Camera Position",
"Default Z Camera Position when a level is opened");
editContext->Class<CameraInputSettings>("Camera Input Settings", "")
->DataElement(
@ -271,6 +283,12 @@ void CEditorPreferencesPage_ViewportCamera::OnApply()
SandboxEditor::SetCameraOrbitYawRotationInverted(m_cameraMovementSettings.m_orbitYawRotationInverted);
SandboxEditor::SetCameraPanInvertedX(m_cameraMovementSettings.m_panInvertedX);
SandboxEditor::SetCameraPanInvertedY(m_cameraMovementSettings.m_panInvertedY);
SandboxEditor::SetDefaultCameraEditorPosition(
AZ::Vector3(
m_cameraMovementSettings.m_defaultCameraPositionX,
m_cameraMovementSettings.m_defaultCameraPositionY,
m_cameraMovementSettings.m_defaultCameraPositionZ
));
SandboxEditor::SetCameraTranslateForwardChannelId(m_cameraInputSettings.m_translateForwardChannelId);
SandboxEditor::SetCameraTranslateBackwardChannelId(m_cameraInputSettings.m_translateBackwardChannelId);
@ -308,6 +326,11 @@ void CEditorPreferencesPage_ViewportCamera::InitializeSettings()
m_cameraMovementSettings.m_panInvertedX = SandboxEditor::CameraPanInvertedX();
m_cameraMovementSettings.m_panInvertedY = SandboxEditor::CameraPanInvertedY();
AZ::Vector3 defaultCameraPosition = SandboxEditor::DefaultEditorCameraPosition();
m_cameraMovementSettings.m_defaultCameraPositionX = defaultCameraPosition.GetX();
m_cameraMovementSettings.m_defaultCameraPositionY = defaultCameraPosition.GetY();
m_cameraMovementSettings.m_defaultCameraPositionZ = defaultCameraPosition.GetZ();
m_cameraInputSettings.m_translateForwardChannelId = SandboxEditor::CameraTranslateForwardChannelId().GetName();
m_cameraInputSettings.m_translateBackwardChannelId = SandboxEditor::CameraTranslateBackwardChannelId().GetName();
m_cameraInputSettings.m_translateLeftChannelId = SandboxEditor::CameraTranslateLeftChannelId().GetName();

@ -57,6 +57,9 @@ private:
bool m_orbitYawRotationInverted;
bool m_panInvertedX;
bool m_panInvertedY;
float m_defaultCameraPositionX;
float m_defaultCameraPositionY;
float m_defaultCameraPositionZ;
AZ::Crc32 RotateSmoothingVisibility() const
{

@ -52,6 +52,9 @@ namespace SandboxEditor
constexpr AZStd::string_view CameraOrbitDollyIdSetting = "/Amazon/Preferences/Editor/Camera/OrbitDollyId";
constexpr AZStd::string_view CameraOrbitPanIdSetting = "/Amazon/Preferences/Editor/Camera/OrbitPanId";
constexpr AZStd::string_view CameraFocusIdSetting = "/Amazon/Preferences/Editor/Camera/FocusId";
constexpr AZStd::string_view CameraDefaultStartingPositionX = "/Amazon/Preferences/Editor/Camera/DefaultStartingPosition/x";
constexpr AZStd::string_view CameraDefaultStartingPositionY = "/Amazon/Preferences/Editor/Camera/DefaultStartingPosition/y";
constexpr AZStd::string_view CameraDefaultStartingPositionZ = "/Amazon/Preferences/Editor/Camera/DefaultStartingPosition/z";
template<typename T>
void SetRegistry(const AZStd::string_view setting, T&& value)
@ -111,6 +114,21 @@ namespace SandboxEditor
return AZStd::make_unique<EditorViewportSettingsCallbacksImpl>();
}
AZ::Vector3 DefaultEditorCameraPosition()
{
float xPosition = aznumeric_cast<float>(GetRegistry(CameraDefaultStartingPositionX, 0.0));
float yPosition = aznumeric_cast<float>(GetRegistry(CameraDefaultStartingPositionY, -10.0));
float zPosition = aznumeric_cast<float>(GetRegistry(CameraDefaultStartingPositionZ, 4.0));
return AZ::Vector3(xPosition, yPosition, zPosition);
}
void SetDefaultCameraEditorPosition(const AZ::Vector3 defaultCameraPosition)
{
SetRegistry(CameraDefaultStartingPositionX, defaultCameraPosition.GetX());
SetRegistry(CameraDefaultStartingPositionY, defaultCameraPosition.GetY());
SetRegistry(CameraDefaultStartingPositionZ, defaultCameraPosition.GetZ());
}
AZ::u64 MaxItemsShownInAssetBrowserSearch()
{
return GetRegistry(AssetBrowserMaxItemsShownInSearchSetting, aznumeric_cast<AZ::u64>(50));

@ -12,6 +12,7 @@
#include <AzCore/Settings/SettingsRegistry.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <AzCore/Math/Vector3.h>
#include <AzFramework/Input/Channels/InputChannelId.h>
namespace SandboxEditor
@ -32,6 +33,9 @@ namespace SandboxEditor
//! event will fire when a value in the settings registry (editorpreferences.setreg) is modified.
SANDBOX_API AZStd::unique_ptr<EditorViewportSettingsCallbacks> CreateEditorViewportSettingsCallbacks();
SANDBOX_API AZ::Vector3 DefaultEditorCameraPosition();
SANDBOX_API void SetDefaultCameraEditorPosition(AZ::Vector3 defaultCameraPosition);
SANDBOX_API AZ::u64 MaxItemsShownInAssetBrowserSearch();
SANDBOX_API void SetMaxItemsShownInAssetBrowserSearch(AZ::u64 numberOfItemsShown);

@ -112,7 +112,6 @@ void StartFixedCursorMode(QObject *viewport);
#define RENDER_MESH_TEST_DISTANCE (0.2f)
#define CURSOR_FONT_HEIGHT 8.0f
namespace AZ::ViewportHelpers
{
static const char TextCantCreateCameraNoLevel[] = "Cannot create camera when no level is loaded.";
@ -623,16 +622,10 @@ void EditorViewportWidget::OnEditorNotifyEvent(EEditorNotifyEvent event)
PopDisableRendering();
{
AZ::Aabb terrainAabb = AZ::Aabb::CreateFromPoint(AZ::Vector3::CreateZero());
AzFramework::Terrain::TerrainDataRequestBus::BroadcastResult(terrainAabb, &AzFramework::Terrain::TerrainDataRequests::GetTerrainAabb);
float sx = terrainAabb.GetXExtent();
float sy = terrainAabb.GetYExtent();
Matrix34 viewTM;
viewTM.SetIdentity();
// Initial camera will be at middle of the map at the height of 2
// meters above the terrain (default terrain height is 32)
viewTM.SetTranslation(Vec3(sx * 0.5f, sy * 0.5f, 34.0f));
viewTM.SetTranslation(Vec3(m_editorViewportSettings.DefaultEditorCameraPosition()));
SetViewTM(viewTM);
UpdateScene();
@ -647,16 +640,10 @@ void EditorViewportWidget::OnEditorNotifyEvent(EEditorNotifyEvent event)
PopDisableRendering();
{
AZ::Aabb terrainAabb = AZ::Aabb::CreateFromPoint(AZ::Vector3::CreateZero());
AzFramework::Terrain::TerrainDataRequestBus::BroadcastResult(terrainAabb, &AzFramework::Terrain::TerrainDataRequests::GetTerrainAabb);
float sx = terrainAabb.GetXExtent();
float sy = terrainAabb.GetYExtent();
Matrix34 viewTM;
viewTM.SetIdentity();
// Initial camera will be at middle of the map at the height of 2
// meters above the terrain (default terrain height is 32)
viewTM.SetTranslation(Vec3(sx * 0.5f, sy * 0.5f, 34.0f));
viewTM.SetTranslation(Vec3(m_editorViewportSettings.DefaultEditorCameraPosition()));
SetViewTM(viewTM);
}
break;
@ -1345,10 +1332,6 @@ void EditorViewportWidget::keyPressEvent(QKeyEvent* event)
void EditorViewportWidget::SetViewTM(const Matrix34& tm)
{
if (m_viewSourceType == ViewSourceType::None)
{
m_defaultViewTM = tm;
}
SetViewTM(tm, false);
}
@ -1445,6 +1428,10 @@ void EditorViewportWidget::SetViewTM(const Matrix34& camMatrix, bool bMoveOnly)
"Please report this as a bug."
);
}
else if (shouldUpdateObject == ShouldUpdateObject::No)
{
GetCurrentAtomView()->SetCameraTransform(LYTransformToAZMatrix3x4(camMatrix));
}
if (m_pressedKeyState == KeyPressedState::PressedThisFrame)
{
@ -2028,6 +2015,9 @@ void EditorViewportWidget::SetDefaultCamera()
m_viewSourceType = ViewSourceType::None;
GetViewManager()->SetCameraObjectId(GUID_NULL);
SetName(m_defaultViewName);
// Set the default Editor Camera position.
m_defaultViewTM.SetTranslation(Vec3(m_editorViewportSettings.DefaultEditorCameraPosition()));
SetViewTM(m_defaultViewTM);
// Synchronize the configured editor viewport FOV to the default camera
@ -2530,6 +2520,11 @@ bool EditorViewportSettings::StickySelectEnabled() const
return SandboxEditor::StickySelectEnabled();
}
AZ::Vector3 EditorViewportSettings::DefaultEditorCameraPosition() const
{
return SandboxEditor::DefaultEditorCameraPosition();
}
AZ_CVAR_EXTERNED(bool, ed_previewGameInFullscreen_once);
bool EditorViewportWidget::ShouldPreviewFullscreen() const
@ -2641,5 +2636,4 @@ void EditorViewportWidget::StopFullscreenPreview()
// Show the main window
MainWindow::instance()->show();
}
#include <moc_EditorViewportWidget.cpp>

@ -78,6 +78,7 @@ struct EditorViewportSettings : public AzToolsFramework::ViewportInteraction::Vi
float ManipulatorLineBoundWidth() const override;
float ManipulatorCircleBoundWidth() const override;
bool StickySelectEnabled() const override;
AZ::Vector3 DefaultEditorCameraPosition() const override;
};
// EditorViewportWidget window

@ -31,11 +31,11 @@
#include <AzToolsFramework/Entity/EditorEntityContextBus.h>
//////////////////////////////////////////////////////////////////////////
#define MUSIC_LEVEL_LIBRARY_FILE "Music.xml"
#define MATERIAL_LEVEL_LIBRARY_FILE "Materials.xml"
#define RESOURCE_LIST_FILE "ResourceList.txt"
#define USED_RESOURCE_LIST_FILE "UsedResourceList.txt"
#define SHADER_LIST_FILE "ShadersList.txt"
#define MUSIC_LEVEL_LIBRARY_FILE "music.xml"
#define MATERIAL_LEVEL_LIBRARY_FILE "materials.xml"
#define RESOURCE_LIST_FILE "resourcelist.txt"
#define USED_RESOURCE_LIST_FILE "usedresourcelist.txt"
#define SHADER_LIST_FILE "shaderslist.txt"
#define GetAValue(rgb) ((BYTE)((rgb) >> 24))
@ -185,9 +185,9 @@ bool CGameExporter::Export(unsigned int flags, [[maybe_unused]] EEndian eExportE
ExportOcclusionMesh(sLevelPath.toUtf8().data());
//! Export Level data.
CLogFile::WriteLine("Exporting LevelData.xml");
CLogFile::WriteLine("Exporting leveldata.xml");
ExportLevelData(sLevelPath);
CLogFile::WriteLine("Exporting LevelData.xml done.");
CLogFile::WriteLine("Exporting leveldata.xml done.");
ExportLevelInfo(sLevelPath);
@ -266,26 +266,26 @@ void CGameExporter::ExportOcclusionMesh(const char* pszGamePath)
void CGameExporter::ExportLevelData(const QString& path, bool /*bExportMission*/)
{
IEditor* pEditor = GetIEditor();
pEditor->SetStatusText(QObject::tr("Exporting LevelData.xml..."));
pEditor->SetStatusText(QObject::tr("Exporting leveldata.xml..."));
char versionString[256];
pEditor->GetFileVersion().ToString(versionString);
XmlNodeRef root = XmlHelpers::CreateXmlNode("LevelData");
XmlNodeRef root = XmlHelpers::CreateXmlNode("leveldata");
root->setAttr("SandboxVersion", versionString);
XmlNodeRef rootAction = XmlHelpers::CreateXmlNode("LevelDataAction");
XmlNodeRef rootAction = XmlHelpers::CreateXmlNode("leveldataaction");
rootAction->setAttr("SandboxVersion", versionString);
//////////////////////////////////////////////////////////////////////////
// Save Level Data XML
//////////////////////////////////////////////////////////////////////////
QString levelDataFile = path + "LevelData.xml";
QString levelDataFile = path + "leveldata.xml";
XmlString xmlData = root->getXML();
CCryMemFile file;
file.Write(xmlData.c_str(), static_cast<unsigned int>(xmlData.length()));
m_levelPak.m_pakFile.UpdateFile(levelDataFile.toUtf8().data(), file);
QString levelDataActionFile = path + "LevelDataAction.xml";
QString levelDataActionFile = path + "leveldataaction.xml";
XmlString xmlDataAction = rootAction->getXML();
CCryMemFile fileAction;
fileAction.Write(xmlDataAction.c_str(), static_cast<unsigned int>(xmlDataAction.length()));
@ -298,7 +298,7 @@ void CGameExporter::ExportLevelData(const QString& path, bool /*bExportMission*/
if (savedEntities)
{
QString entitiesFile;
entitiesFile = QStringLiteral("%1%2.entities_xml").arg(path, "Mission0");
entitiesFile = QStringLiteral("%1%2.entities_xml").arg(path, "mission0");
m_levelPak.m_pakFile.UpdateFile(entitiesFile.toUtf8().data(), entitySaveBuffer.begin(), static_cast<int>(entitySaveBuffer.size()));
}
}
@ -326,7 +326,7 @@ void CGameExporter::ExportLevelInfo(const QString& path)
//////////////////////////////////////////////////////////////////////////
// Save LevelInfo file.
//////////////////////////////////////////////////////////////////////////
QString filename = path + "LevelInfo.xml";
QString filename = path + "levelinfo.xml";
XmlString xmlData = root->getXML();
CCryMemFile file;

@ -38,6 +38,8 @@ namespace UnitTest
void BeginCursorCapture() override;
void EndCursorCapture() override;
bool IsMouseOver() const override;
void SetOverrideCursor(AzToolsFramework::ViewportInteraction::CursorStyleOverride cursorStyleOverride) override;
void ClearOverrideCursor() override;
private:
AzToolsFramework::QtEventToAzInputMapper* m_inputChannelMapper = nullptr;
@ -58,6 +60,17 @@ namespace UnitTest
return true;
}
void ViewportMouseCursorRequestImpl::SetOverrideCursor(
[[maybe_unused]] AzToolsFramework::ViewportInteraction::CursorStyleOverride cursorStyleOverride)
{
// noop
}
void ViewportMouseCursorRequestImpl::ClearOverrideCursor()
{
// noop
}
class ModularViewportCameraControllerFixture : public AllocatorsTestFixture
{
public:

@ -577,7 +577,9 @@ namespace AZ
}
azstrcat(lines[i], AZ_ARRAY_SIZE(lines[i]), "\n");
AZ_Printf(window, "%s", lines[i]); // feed back into the trace system so that listeners can get it.
// Use Output instead of AZ_Printf to be consistent with the exception output code and avoid
// this accidentally being suppressed as a normal message
Output(window, lines[i]);
}
}
}

@ -46,6 +46,8 @@ namespace AzManipulatorTestFramework
virtual void UpdateVisibility() = 0;
//! Set if sticky select is enabled or not.
virtual void SetStickySelect(bool enabled) = 0;
//! Get default Editor Camera Position.
virtual AZ::Vector3 DefaultEditorCameraPosition() const = 0;
};
//! This interface is used to simulate the manipulator manager while the manipulators are under test.

@ -36,6 +36,7 @@ namespace AzManipulatorTestFramework
int GetViewportId() const override;
void UpdateVisibility() override;
void SetStickySelect(bool enabled) override;
AZ::Vector3 DefaultEditorCameraPosition() const override;
// ViewportInteractionRequestBus overrides ...
AzFramework::CameraState GetCameraState() override;

@ -120,6 +120,11 @@ namespace AzManipulatorTestFramework
m_stickySelect = enabled;
}
AZ::Vector3 ViewportInteraction::DefaultEditorCameraPosition() const
{
return {};
}
void ViewportInteraction::SetGridSize(float size)
{
m_gridSize = size;

@ -65,7 +65,7 @@ namespace AzNetworking
: m_delta(delta)
, m_dataSerializer(m_delta.GetBufferPtr(), m_delta.GetBufferCapacity())
{
m_namePrefix.reserve(128);
;
}
DeltaSerializerCreate::~DeltaSerializerCreate()
@ -73,7 +73,7 @@ namespace AzNetworking
// Delete any left over records that might be hanging around
for (auto iter : m_records)
{
delete iter.second;
delete iter;
}
m_records.clear();
}
@ -160,28 +160,13 @@ namespace AzNetworking
return SerializeHelper(buffer, bufferCapacity, isString, outSize, name);
}
AZStd::string DeltaSerializerCreate::GetNextObjectName(const char* name)
bool DeltaSerializerCreate::BeginObject([[maybe_unused]] const char* name, [[maybe_unused]] const char* typeName)
{
AZStd::string objectName = name;
objectName += ".";
objectName += AZStd::to_string(m_objectCounter);
++m_objectCounter;
return objectName;
}
bool DeltaSerializerCreate::BeginObject(const char* name, [[maybe_unused]] const char* typeName)
{
m_nameLengthStack.push_back(m_namePrefix.length());
m_namePrefix += GetNextObjectName(name);
m_namePrefix += ".";
return true;
}
bool DeltaSerializerCreate::EndObject([[maybe_unused]] const char* name, [[maybe_unused]] const char* typeName)
{
const size_t prevLen = m_nameLengthStack.back();
m_nameLengthStack.pop_back();
m_namePrefix.resize(prevLen);
return true;
}
@ -205,25 +190,15 @@ namespace AzNetworking
{
typedef AbstractValue::ValueT<T> ValueType;
const size_t prevLen = m_namePrefix.length();
m_namePrefix += GetNextObjectName(name);
const AZ::HashValue32 nameHash = AZ::TypeHash32(m_namePrefix.c_str());
m_namePrefix.resize(prevLen);
AbstractValue::BaseValue*& baseValue = m_records[nameHash];
AbstractValue::BaseValue* baseValue = m_records.size() > m_objectCounter ? m_records[m_objectCounter] : nullptr;
++m_objectCounter;
// If we are in the gather records phase, just save off the value records
if (m_gatheringRecords)
{
if (baseValue != nullptr)
{
AZ_Assert(false, "Duplicate name encountered in delta serializer. This will cause data to be serialized incorrectly.");
return false;
}
AZ_Assert(baseValue == nullptr, "Expected to create a new record but found a pre-existing one at index %d", m_objectCounter - 1);
baseValue = new ValueType(value);
m_records.push_back(baseValue);
}
else // If we are not gathering records, then we are comparing them
{

@ -90,8 +90,6 @@ namespace AzNetworking
DeltaSerializerCreate(const DeltaSerializerCreate&) = delete;
DeltaSerializerCreate& operator=(const DeltaSerializerCreate&) = delete;
AZStd::string GetNextObjectName(const char* name);
template <typename T>
bool SerializeHelper(T& value, uint32_t bufferCapacity, bool isString, uint32_t& outSize, const char* name);
@ -105,9 +103,7 @@ namespace AzNetworking
bool m_gatheringRecords = false;
uint32_t m_objectCounter = 0;
AZStd::string m_namePrefix;
AZStd::vector<size_t> m_nameLengthStack;
AZStd::unordered_map<AZ::HashValue32, AbstractValue::BaseValue*> m_records;
AZStd::vector<AbstractValue::BaseValue*> m_records;
NetworkInputSerializer m_dataSerializer;
};

@ -11,4 +11,213 @@
namespace UnitTest
{
struct DeltaDataElement
{
AzNetworking::PacketId m_packetId = AzNetworking::InvalidPacketId;
uint32_t m_id = 0;
AZ::TimeMs m_timeMs = AZ::TimeMs{ 0 };
float m_blendFactor = 0.f;
AZStd::vector<int> m_growVector, m_shrinkVector;
bool Serialize(AzNetworking::ISerializer& serializer)
{
if (!serializer.Serialize(m_packetId, "PacketId")
|| !serializer.Serialize(m_id, "Id")
|| !serializer.Serialize(m_timeMs, "TimeMs")
|| !serializer.Serialize(m_blendFactor, "BlendFactor")
|| !serializer.Serialize(m_growVector, "GrowVector")
|| !serializer.Serialize(m_shrinkVector, "ShrinkVector"))
{
return false;
}
return true;
}
};
struct DeltaDataContainer
{
AZStd::string m_containerName;
AZStd::array<DeltaDataElement, 32> m_container;
// This logic is modeled after NetworkInputArray serialization in the Multiplayer Gem
bool Serialize(AzNetworking::ISerializer& serializer)
{
// Always serialize the full first element
if(!m_container[0].Serialize(serializer))
{
return false;
}
for (uint32_t i = 1; i < m_container.size(); ++i)
{
if (serializer.GetSerializerMode() == AzNetworking::SerializerMode::WriteToObject)
{
AzNetworking::SerializerDelta deltaSerializer;
// Read out the delta
if (!deltaSerializer.Serialize(serializer))
{
return false;
}
// Start with previous value
m_container[i] = m_container[i - 1];
// Then apply delta
AzNetworking::DeltaSerializerApply applySerializer(deltaSerializer);
if (!applySerializer.ApplyDelta(m_container[i]))
{
return false;
}
}
else
{
AzNetworking::SerializerDelta deltaSerializer;
// Create the delta
AzNetworking::DeltaSerializerCreate createSerializer(deltaSerializer);
if (!createSerializer.CreateDelta(m_container[i - 1], m_container[i]))
{
return false;
}
// Then write out the delta
if (!deltaSerializer.Serialize(serializer))
{
return false;
}
}
}
return true;
}
// This logic is modeled after NetworkInputArray serialization in the Multiplayer Gem
bool SerializeNoDelta(AzNetworking::ISerializer& serializer)
{
for (uint32_t i = 0; i < m_container.size(); ++i)
{
if(!m_container[i].Serialize(serializer))
{
return false;
}
}
return true;
}
};
class DeltaSerializerTests
: public UnitTest::AllocatorsTestFixture
{
public:
void SetUp() override
{
UnitTest::AllocatorsTestFixture::SetUp();
}
void TearDown() override
{
UnitTest::AllocatorsTestFixture::TearDown();
}
};
static constexpr float BLEND_FACTOR_SCALE = 1.1f;
static constexpr uint32_t TIME_SCALE = 10;
DeltaDataContainer TestDeltaContainer()
{
DeltaDataContainer testContainer;
AZStd::vector<int> growVector, shrinkVector;
shrinkVector.resize(testContainer.m_container.array_size);
testContainer.m_containerName = "TestContainer";
for (int i = 0; i < testContainer.m_container.array_size; ++i)
{
testContainer.m_container[i].m_packetId = AzNetworking::PacketId(i);
testContainer.m_container[i].m_id = i;
testContainer.m_container[i].m_timeMs = AZ::TimeMs(i * TIME_SCALE);
testContainer.m_container[i].m_blendFactor = BLEND_FACTOR_SCALE * i;
growVector.push_back(i);
testContainer.m_container[i].m_growVector = growVector;
shrinkVector.resize(testContainer.m_container.array_size - i);
testContainer.m_container[i].m_shrinkVector = shrinkVector;
}
return testContainer;
}
TEST_F(DeltaSerializerTests, DeltaArray)
{
DeltaDataContainer inContainer = TestDeltaContainer();
AZStd::array<uint8_t, 2048> buffer;
AzNetworking::NetworkInputSerializer inSerializer(buffer.data(), static_cast<uint32_t>(buffer.size()));
// Always serialize the full first element
EXPECT_TRUE(inContainer.Serialize(inSerializer));
DeltaDataContainer outContainer;
AzNetworking::NetworkOutputSerializer outSerializer(buffer.data(), static_cast<uint32_t>(buffer.size()));
EXPECT_TRUE(outContainer.Serialize(outSerializer));
for (uint32_t i = 0; i > outContainer.m_container.size(); ++i)
{
EXPECT_EQ(inContainer.m_container[i].m_blendFactor, outContainer.m_container[i].m_blendFactor);
EXPECT_EQ(inContainer.m_container[i].m_id, outContainer.m_container[i].m_id);
EXPECT_EQ(inContainer.m_container[i].m_packetId, outContainer.m_container[i].m_packetId);
EXPECT_EQ(inContainer.m_container[i].m_timeMs, outContainer.m_container[i].m_timeMs);
EXPECT_EQ(inContainer.m_container[i].m_growVector[i], outContainer.m_container[i].m_growVector[i]);
EXPECT_EQ(inContainer.m_container[i].m_growVector.size(), outContainer.m_container[i].m_growVector.size());
EXPECT_EQ(inContainer.m_container[i].m_shrinkVector.size(), outContainer.m_container[i].m_shrinkVector.size());
}
}
TEST_F(DeltaSerializerTests, DeltaSerializerCreateUnused)
{
// Every function here should return a constant value regardless of inputs
AzNetworking::SerializerDelta deltaSerializer;
AzNetworking::DeltaSerializerCreate createSerializer(deltaSerializer);
EXPECT_EQ(createSerializer.GetCapacity(), 0);
EXPECT_EQ(createSerializer.GetSize(), 0);
EXPECT_EQ(createSerializer.GetBuffer(), nullptr);
EXPECT_EQ(createSerializer.GetSerializerMode(), AzNetworking::SerializerMode::ReadFromObject);
createSerializer.ClearTrackedChangesFlag(); //NO-OP
EXPECT_FALSE(createSerializer.GetTrackedChangesFlag());
EXPECT_TRUE(createSerializer.BeginObject("CreateSerializer", "Begin"));
EXPECT_TRUE(createSerializer.EndObject("CreateSerializer", "End"));
}
TEST_F(DeltaSerializerTests, DeltaArraySize)
{
DeltaDataContainer deltaContainer = TestDeltaContainer();
DeltaDataContainer noDeltaContainer = TestDeltaContainer();
AZStd::array<uint8_t, 2048> deltaBuffer;
AzNetworking::NetworkInputSerializer deltaSerializer(deltaBuffer.data(), static_cast<uint32_t>(deltaBuffer.size()));
AZStd::array<uint8_t, 2048> noDeltaBuffer;
AzNetworking::NetworkInputSerializer noDeltaSerializer(noDeltaBuffer.data(), static_cast<uint32_t>(noDeltaBuffer.size()));
EXPECT_TRUE(deltaContainer.Serialize(deltaSerializer));
EXPECT_FALSE(noDeltaContainer.SerializeNoDelta(noDeltaSerializer)); // Should run out of space
EXPECT_EQ(noDeltaSerializer.GetCapacity(), noDeltaSerializer.GetSize()); // Verify that the serializer filled up
EXPECT_FALSE(noDeltaSerializer.IsValid()); // and that it is no longer valid due to lack of space
}
TEST_F(DeltaSerializerTests, DeltaSerializerApplyUnused)
{
// Every function here should return a constant value regardless of inputs
AzNetworking::SerializerDelta deltaSerializer;
AzNetworking::DeltaSerializerApply applySerializer(deltaSerializer);
EXPECT_EQ(applySerializer.GetCapacity(), 0);
EXPECT_EQ(applySerializer.GetSize(), 0);
EXPECT_EQ(applySerializer.GetBuffer(), nullptr);
EXPECT_EQ(applySerializer.GetSerializerMode(), AzNetworking::SerializerMode::WriteToObject);
applySerializer.ClearTrackedChangesFlag(); //NO-OP
EXPECT_FALSE(applySerializer.GetTrackedChangesFlag());
EXPECT_TRUE(applySerializer.BeginObject("CreateSerializer", "Begin"));
EXPECT_TRUE(applySerializer.EndObject("CreateSerializer", "End"));
}
}

@ -244,6 +244,17 @@ namespace AzToolsFramework
const auto eventType = event->type();
if (eventType == QEvent::Type::MouseMove)
{
// clear override cursor when moving outside of the viewport
const auto* mouseEvent = static_cast<const QMouseEvent*>(event);
if (m_overrideCursor && !m_sourceWidget->geometry().contains(m_sourceWidget->mapFromGlobal(mouseEvent->globalPos())))
{
qApp->restoreOverrideCursor();
m_overrideCursor = false;
}
}
// Only accept mouse & key release events that originate from an object that is not our target widget,
// as we don't want to erroneously intercept user input meant for another component.
if (object != m_sourceWidget && eventType != QEvent::Type::KeyRelease && eventType != QEvent::Type::MouseButtonRelease)
@ -262,7 +273,7 @@ namespace AzToolsFramework
if (eventType == QEvent::FocusIn)
{
const auto globalCursorPosition = QCursor::pos();
if (m_sourceWidget->geometry().contains(globalCursorPosition))
if (m_sourceWidget->geometry().contains(m_sourceWidget->mapFromGlobal(globalCursorPosition)))
{
HandleMouseMoveEvent(globalCursorPosition);
}
@ -452,4 +463,32 @@ namespace AzToolsFramework
}
}
}
static Qt::CursorShape QtCursorFromAzCursor(const ViewportInteraction::CursorStyleOverride cursorStyleOverride)
{
switch (cursorStyleOverride)
{
case ViewportInteraction::CursorStyleOverride::Forbidden:
return Qt::ForbiddenCursor;
default:
return Qt::ArrowCursor;
}
}
void QtEventToAzInputMapper::SetOverrideCursor(ViewportInteraction::CursorStyleOverride cursorStyleOverride)
{
ClearOverrideCursor();
qApp->setOverrideCursor(QtCursorFromAzCursor(cursorStyleOverride));
m_overrideCursor = true;
}
void QtEventToAzInputMapper::ClearOverrideCursor()
{
if (m_overrideCursor)
{
qApp->restoreOverrideCursor();
m_overrideCursor = false;
}
}
} // namespace AzToolsFramework

@ -15,10 +15,11 @@
#include <AzFramework/Input/Channels/InputChannelDeltaWithSharedPosition2D.h>
#include <AzFramework/Input/Channels/InputChannelDigitalWithSharedModifierKeyStates.h>
#include <AzFramework/Input/Channels/InputChannelDigitalWithSharedPosition2D.h>
#include <AzFramework/Input/Devices/Keyboard/InputDeviceKeyboard.h>
#include <AzFramework/Input/Devices/Mouse/InputDeviceMouse.h>
#include <AzToolsFramework/Viewport/ViewportMessages.h>
#include <QEvent>
#include <QObject>
#include <QPoint>
@ -55,6 +56,9 @@ namespace AzToolsFramework
//! like a dolly or rotation, where mouse movement is important but cursor location is not.
void SetCursorCaptureEnabled(bool enabled);
void SetOverrideCursor(ViewportInteraction::CursorStyleOverride cursorStyleOverride);
void ClearOverrideCursor();
// QObject overrides...
bool eventFilter(QObject* object, QEvent* event) override;
@ -164,6 +168,8 @@ namespace AzToolsFramework
bool m_enabled = true;
// Flags whether or not the cursor is being constrained to the source widget (for invisible mouse movement).
bool m_capturingCursor = false;
// Flags whether the cursor has been overridden.
bool m_overrideCursor = false;
// Our viewport-specific AZ devices. We control their internal input channel states.
AZStd::unique_ptr<EditorQtMouseDevice> m_mouseDevice;

@ -198,6 +198,8 @@ namespace AzToolsFramework
virtual float ManipulatorCircleBoundWidth() const = 0;
//! Returns if sticky select is enabled or not.
virtual bool StickySelectEnabled() const = 0;
//! Returns the default viewport camera position.
virtual AZ::Vector3 DefaultEditorCameraPosition() const = 0;
protected:
~ViewportSettingsRequests() = default;
@ -300,6 +302,12 @@ namespace AzToolsFramework
using EditorViewportInputTimeNowRequestBus = AZ::EBus<EditorViewportInputTimeNowRequests>;
//! The style of cursor override.
enum class CursorStyleOverride
{
Forbidden
};
//! Viewport requests for managing the viewport cursor state.
class ViewportMouseCursorRequests
{
@ -310,6 +318,10 @@ namespace AzToolsFramework
virtual void EndCursorCapture() = 0;
//! Is the mouse over the viewport.
virtual bool IsMouseOver() const = 0;
//! Set the cursor style override.
virtual void SetOverrideCursor(CursorStyleOverride cursorStyleOverride) = 0;
//! Clear the cursor style override.
virtual void ClearOverrideCursor() = 0;
protected:
~ViewportMouseCursorRequests() = default;

@ -44,6 +44,13 @@ AZ_CVAR(
nullptr,
AZ::ConsoleFunctorFlags::Null,
"Display the aggregate world bounds for a given entity (the union of all world component Aabbs)");
AZ_CVAR(
bool,
ed_useCursorLockIconInFocusMode,
false,
nullptr,
AZ::ConsoleFunctorFlags::Null,
"Use a lock icon when the cursor is over entities that cannot be interacted with");
namespace AzToolsFramework
{
@ -222,6 +229,13 @@ namespace AzToolsFramework
// verify if the entity Id corresponds to an entity that is focused; if not, halt selection.
if (entityIdUnderCursor.IsValid() && !IsSelectableAccordingToFocusMode(entityIdUnderCursor))
{
if (ed_useCursorLockIconInFocusMode)
{
ViewportInteraction::ViewportMouseCursorRequestBus::Event(
viewportId, &ViewportInteraction::ViewportMouseCursorRequestBus::Events::SetOverrideCursor,
ViewportInteraction::CursorStyleOverride::Forbidden);
}
if (mouseInteraction.m_mouseInteraction.m_mouseButtons.Left() &&
mouseInteraction.m_mouseEvent == ViewportInteraction::MouseEvent::Down ||
mouseInteraction.m_mouseEvent == ViewportInteraction::MouseEvent::DoubleClick)
@ -232,6 +246,9 @@ namespace AzToolsFramework
return CursorEntityIdQuery(AZ::EntityId(), AZ::EntityId());
}
ViewportInteraction::ViewportMouseCursorRequestBus::Event(
viewportId, &ViewportInteraction::ViewportMouseCursorRequestBus::Events::ClearOverrideCursor);
// container entity support - if the entity that is being selected is part of a closed container,
// change the selection to the container instead.
if (ContainerEntityInterface* containerEntityInterface = AZ::Interface<ContainerEntityInterface>::Get())

@ -90,8 +90,7 @@ namespace UnitTest
// Update our undo cache entry to include the rename / reparent as one atomic operation.
m_prefabPublicInterface->GenerateUndoNodesForEntityChangeAndUpdateCache(entityId, m_undoStack->GetTop());
// Force a prefab propagation as updates are deferred to the next tick.
m_prefabSystemComponent->OnSystemTick();
ProcessDeferredUpdates();
return entityId;
}
@ -125,6 +124,30 @@ namespace UnitTest
return m_model->index(0, 0);
}
// Kicks off any updates scheduled for the next tick
void ProcessDeferredUpdates()
{
// Force a prefab propagation for updates that are deferred to the next tick.
m_prefabSystemComponent->OnSystemTick();
// Ensure the model process its entity update queue
m_model->ProcessEntityUpdates();
}
// Performs an undo operation and ensures the tick-scheduled updates happen
void Undo()
{
m_undoStack->Undo();
ProcessDeferredUpdates();
}
// Performs a redo operation and ensures the tick-scheduled updates happen
void Redo()
{
m_undoStack->Redo();
ProcessDeferredUpdates();
}
AZStd::unique_ptr<AzToolsFramework::EntityOutlinerListModel> m_model;
AZStd::unique_ptr<QAbstractItemModelTester> m_modelTester;
AzToolsFramework::UndoSystem::UndoStack* m_undoStack = nullptr;
@ -139,21 +162,18 @@ namespace UnitTest
CreateNamedEntity(AZStd::string::format("Entity%zu", i));
EXPECT_EQ(m_model->rowCount(GetRootIndex()), i + 1);
}
m_model->ProcessEntityUpdates();
for (int i = entityCount; i > 0; --i)
{
m_undoStack->Undo();
Undo();
EXPECT_EQ(m_model->rowCount(GetRootIndex()), i - 1);
}
m_model->ProcessEntityUpdates();
for (size_t i = 0; i < entityCount; ++i)
{
m_undoStack->Redo();
Redo();
EXPECT_EQ(m_model->rowCount(GetRootIndex()), i + 1);
}
m_model->ProcessEntityUpdates();
}
TEST_F(EntityOutlinerTest, TestCreateNestedHierarchyUndoAndRedoWorks)
@ -177,21 +197,18 @@ namespace UnitTest
{
parentId = CreateNamedEntity(AZStd::string::format("EntityDepth%i", i), parentId);
EXPECT_EQ(modelDepth(), i + 1);
m_model->ProcessEntityUpdates();
}
for (int i = depth - 1; i >= 0; --i)
{
m_undoStack->Undo();
Undo();
EXPECT_EQ(modelDepth(), i);
m_model->ProcessEntityUpdates();
}
for (int i = 0; i < depth; ++i)
{
m_undoStack->Redo();
Redo();
EXPECT_EQ(modelDepth(), i + 1);
m_model->ProcessEntityUpdates();
}
}
} // namespace UnitTest

@ -86,7 +86,7 @@ bool CLevelInfo::ReadInfo()
usePrefabSystemForLevels, &AzFramework::ApplicationRequests::IsPrefabSystemForLevelsEnabled);
// Set up a default game type for legacy code.
m_defaultGameTypeName = "Mission0";
m_defaultGameTypeName = "mission0";
if (usePrefabSystemForLevels)
{
@ -96,17 +96,17 @@ bool CLevelInfo::ReadInfo()
AZStd::string levelPath(m_levelPath);
AZStd::string xmlFile(levelPath);
xmlFile += "/LevelInfo.xml";
xmlFile += "/levelinfo.xml";
XmlNodeRef rootNode = GetISystem()->LoadXmlFromFile(xmlFile.c_str());
if (rootNode)
{
AZStd::string dataFile(levelPath);
dataFile += "/LevelDataAction.xml";
dataFile += "/leveldataaction.xml";
XmlNodeRef dataNode = GetISystem()->LoadXmlFromFile(dataFile.c_str());
if (!dataNode)
{
dataFile = levelPath + "/LevelData.xml";
dataFile = levelPath + "/leveldata.xml";
dataNode = GetISystem()->LoadXmlFromFile(dataFile.c_str());
}
@ -614,7 +614,7 @@ ILevel* CLevelSystem::LoadLevelInternal(const char* _levelName)
}
{
AZStd::string missionXml("Mission_");
AZStd::string missionXml("mission_");
missionXml += pLevelInfo->m_defaultGameTypeName;
missionXml += ".xml";
AZStd::string xmlFile(pLevelInfo->GetPath());

@ -3573,19 +3573,61 @@ namespace AssetProcessor
QString knownPathBeforeWildcard = encodedFileData.left(slashBeforeWildcardIndex + 1); // include the slash
QString relativeSearch = encodedFileData.mid(slashBeforeWildcardIndex + 1); // skip the slash
for (int i = 0; i < m_platformConfig->GetScanFolderCount(); ++i)
// Absolute path, just check the 1 scan folder
if (AZ::IO::PathView(encodedFileData.toUtf8().constData()).IsAbsolute())
{
const ScanFolderInfo* scanFolderInfo = &m_platformConfig->GetScanFolderAt(i);
if (!scanFolderInfo->RecurseSubFolders() && encodedFileData.contains("/"))
QString scanFolderName;
if (!m_platformConfig->ConvertToRelativePath(encodedFileData, resultDatabaseSourceName, scanFolderName))
{
continue;
AZ_Warning(
AssetProcessor::ConsoleChannel, false,
"'%s' does not appear to be in any input folder. Use relative paths instead.",
sourceDependency.m_sourceFileDependencyPath.c_str());
}
QDir rooted(scanFolderInfo->ScanPath());
QString absolutePath = rooted.absoluteFilePath(knownPathBeforeWildcard);
auto scanFolderInfo = m_platformConfig->GetScanFolderByPath(scanFolderName);
// Make an absolute path that is ScanFolderPath + Part of search path before the wildcard
QDir rooted(scanFolderName);
QString scanFolderAndKnownSubPath = rooted.absoluteFilePath(knownPathBeforeWildcard);
resolvedDependencyList.append(m_platformConfig->FindWildcardMatches(
scanFolderAndKnownSubPath, relativeSearch, false, scanFolderInfo->RecurseSubFolders()));
}
else // Relative path, check every scan folder
{
for (int i = 0; i < m_platformConfig->GetScanFolderCount(); ++i)
{
const ScanFolderInfo* scanFolderInfo = &m_platformConfig->GetScanFolderAt(i);
if (!scanFolderInfo->RecurseSubFolders() && encodedFileData.contains("/"))
{
continue;
}
resolvedDependencyList.append(m_platformConfig->FindWildcardMatches(absolutePath, relativeSearch, false, scanFolderInfo->RecurseSubFolders()));
QDir rooted(scanFolderInfo->ScanPath());
QString absolutePath = rooted.absoluteFilePath(knownPathBeforeWildcard);
resolvedDependencyList.append(m_platformConfig->FindWildcardMatches(
absolutePath, relativeSearch, false, scanFolderInfo->RecurseSubFolders()));
}
}
// Convert to relative paths
for (auto dependencyItr = resolvedDependencyList.begin(); dependencyItr != resolvedDependencyList.end();)
{
QString relativePath, scanFolder;
if (m_platformConfig->ConvertToRelativePath(*dependencyItr, relativePath, scanFolder))
{
*dependencyItr = relativePath;
++dependencyItr;
}
else
{
AZ_Warning("AssetProcessor", false, "Failed to get relative path for wildcard dependency file %s. Is the file within a scan folder?",
dependencyItr->toUtf8().constData());
dependencyItr = resolvedDependencyList.erase(dependencyItr);
}
}
resultDatabaseSourceName = encodedFileData.replace('\\', '/');

@ -1435,32 +1435,35 @@ namespace AssetProcessor
return QString();
}
QStringList PlatformConfiguration::FindWildcardMatches(const QString& sourceFolder, QString relativeName, bool includeFolders, bool recursiveSearch) const
QStringList PlatformConfiguration::FindWildcardMatches(
const QString& sourceFolder, QString relativeName, bool includeFolders, bool recursiveSearch) const
{
if (relativeName.isEmpty())
{
return QStringList();
}
const int pathLen = sourceFolder.length() + 1;
QDir sourceFolderDir(sourceFolder);
relativeName.replace('\\', '/');
QString posixRelativeName = QDir::fromNativeSeparators(relativeName);
QStringList returnList;
QRegExp nameMatch{ relativeName, Qt::CaseInsensitive, QRegExp::Wildcard };
QDirIterator diretoryIterator(sourceFolder, QDir::AllEntries | QDir::NoSymLinks | QDir::NoDotAndDotDot, recursiveSearch ? QDirIterator::Subdirectories : QDirIterator::NoIteratorFlags);
QRegExp nameMatch{ posixRelativeName, Qt::CaseInsensitive, QRegExp::Wildcard };
QDirIterator dirIterator(
sourceFolderDir.path(), QDir::AllEntries | QDir::NoSymLinks | QDir::NoDotAndDotDot,
recursiveSearch ? QDirIterator::Subdirectories : QDirIterator::NoIteratorFlags);
QStringList files;
while (diretoryIterator.hasNext())
while (dirIterator.hasNext())
{
diretoryIterator.next();
if (!includeFolders && !diretoryIterator.fileInfo().isFile())
dirIterator.next();
if (!includeFolders && !dirIterator.fileInfo().isFile())
{
continue;
}
QString pathMatch{ diretoryIterator.filePath().mid(pathLen) };
QString pathMatch{ sourceFolderDir.relativeFilePath(dirIterator.filePath()) };
if (nameMatch.exactMatch(pathMatch))
{
returnList.append(AssetUtilities::NormalizeFilePath(diretoryIterator.filePath()));
returnList.append(QDir::fromNativeSeparators(dirIterator.filePath()));
}
}
return returnList;

@ -51,4 +51,21 @@ namespace AWSCore
"https://o3de.org/docs/user-guide/gems/reference/aws/aws-metrics/advanced-topics/";
static constexpr const char AWSMetricsSettingsUrl[] =
"https://o3de.org/docs/user-guide/gems/reference/aws/aws-metrics/";
static constexpr const char AWSGameLiftGemOverviewUrl[] =
"https://o3de.org/docs/user-guide/gems/reference/aws/aws-gamelift/";
static constexpr const char AWSGameLiftGemSetupUrl[] =
"https://o3de.org/docs/user-guide/gems/reference/aws/aws-gamelift/gem-setup/";
static constexpr const char AWSGameLiftScriptingUrl[] =
"https://o3de.org/docs/user-guide/gems/reference/aws/aws-gamelift/scripting/";
static constexpr const char AWSGameLiftAPIReferenceUrl[] =
"https://o3de.org/docs/user-guide/gems/reference/aws/aws-gamelift/cpp-api/";
static constexpr const char AWSGameLiftAdvancedTopicsUrl[] =
"https://o3de.org/docs/user-guide/gems/reference/aws/aws-gamelift/advanced-topics/";
static constexpr const char AWSGameLiftLocalTestingUrl[] =
"https://o3de.org/docs/user-guide/gems/reference/aws/aws-gamelift/local-testing/";
static constexpr const char AWSGameLiftBuildPackagingUrl[] =
"https://o3de.org/docs/user-guide/gems/reference/aws/aws-gamelift/build-packaging-for-windows/";
static constexpr const char AWSGameLiftResourceManagementUrl[] =
"https://o3de.org/docs/user-guide/gems/reference/aws/aws-gamelift/resource-management/";
} // namespace AWSCore

@ -38,4 +38,14 @@ namespace AWSCore
static constexpr const char AWSMetricsAPIReferenceActionText[] = "API reference";
static constexpr const char AWSMetricsAdvancedTopicsActionText[] = "Advanced topics";
static constexpr const char AWSMetricsSettingsActionText[] = "Metrics settings";
static constexpr const char AWSGameLiftActionText[] = "GameLift Gem";
static constexpr const char AWSGameLiftGemOverviewActionText[] = "Gem overview";
static constexpr const char AWSGameLiftGemSetupActionText[] = "Setup";
static constexpr const char AWSMGameLiftScriptingActionText[] = "Scripting reference";
static constexpr const char AWSGameLiftAPIReferenceActionText[] = "API reference";
static constexpr const char AWSGameLiftAdvancedTopicsActionText[] = "Advanced topics";
static constexpr const char AWSGameLiftLocalTestingActionText[] = "Local testing";
static constexpr const char AWSGameLiftBuildPackagingActionText[] = "Build packaging (Windows)";
static constexpr const char AWSGameLiftResourceManagementActionText[] = "Resource Management";
} // namespace AWSCore

@ -48,6 +48,7 @@ namespace AWSCore
// AWSCoreEditorRequestBus interface implementation
void SetAWSClientAuthEnabled() override;
void SetAWSMetricsEnabled() override;
void SetAWSGameLiftEnabled() override;
QMenu* SetAWSFeatureSubMenu(const AZStd::string& menuText);

@ -53,6 +53,7 @@ namespace AWSCore
public:
virtual void SetAWSClientAuthEnabled() = 0;
virtual void SetAWSMetricsEnabled() = 0;
virtual void SetAWSGameLiftEnabled() = 0;
//////////////////////////////////////////////////////////////////////////
// EBusTraits overrides

@ -156,6 +156,11 @@ namespace AWSCore
metrics->setIcon(QIcon(QString(":/Notifications/download.svg")));
metrics->setDisabled(true);
this->addAction(metrics);
QAction* gamelift = new QAction(QObject::tr(AWSGameLiftActionText));
gamelift->setIcon(QIcon(QString(":/Notifications/download.svg")));
gamelift->setDisabled(true);
this->addAction(gamelift);
}
void AWSCoreEditorMenu::SetAWSClientAuthEnabled()
@ -181,6 +186,23 @@ namespace AWSCore
AddSpaceForIcon(subMenu);
}
void AWSCoreEditorMenu::SetAWSGameLiftEnabled()
{
// TODO: instead of creating submenu in core editor, aws feature gem should return submenu component directly
QMenu* subMenu = SetAWSFeatureSubMenu(AWSGameLiftActionText);
subMenu->addAction(AddExternalLinkAction(AWSGameLiftGemOverviewActionText, AWSGameLiftGemOverviewUrl, ":/Notifications/link.svg"));
subMenu->addAction(AddExternalLinkAction(AWSGameLiftGemSetupActionText, AWSGameLiftGemSetupUrl, ":/Notifications/link.svg"));
subMenu->addAction(AddExternalLinkAction(AWSMGameLiftScriptingActionText, AWSGameLiftScriptingUrl, ":/Notifications/link.svg"));
subMenu->addAction(AddExternalLinkAction(AWSGameLiftAPIReferenceActionText, AWSGameLiftAPIReferenceUrl, ":/Notifications/link.svg"));
subMenu->addAction(AddExternalLinkAction(AWSGameLiftAdvancedTopicsActionText, AWSGameLiftAdvancedTopicsUrl, ":/Notifications/link.svg"));
subMenu->addAction(AddExternalLinkAction(AWSGameLiftLocalTestingActionText, AWSGameLiftLocalTestingUrl, ":/Notifications/link.svg"));
subMenu->addAction(AddExternalLinkAction(AWSGameLiftBuildPackagingActionText, AWSGameLiftBuildPackagingUrl, ":/Notifications/link.svg"));
subMenu->addAction(AddExternalLinkAction(AWSGameLiftResourceManagementActionText, AWSGameLiftResourceManagementUrl, ":/Notifications/link.svg"));
AddSpaceForIcon(subMenu);
}
void AWSCoreEditorMenu::SetAWSMetricsEnabled()
{
// TODO: instead of creating submenu in core editor, aws feature gem should return submenu component directly

@ -21,8 +21,8 @@
using namespace AWSCore;
static constexpr const int ExpectedActionNumOnWindowsPlatform = 8;
static constexpr const int ExpectedActionNumOnOtherPlatform = 6;
static constexpr const int ExpectedActionNumOnWindowsPlatform = 9;
static constexpr const int ExpectedActionNumOnOtherPlatform = 7;
class AWSCoreEditorMenuTest
: public AWSCoreFixture
@ -60,6 +60,7 @@ TEST_F(AWSCoreEditorMenuTest, AWSCoreEditorMenu_BroadcastFeatureGemsAreEnabled_C
AWSCoreEditorRequestBus::Broadcast(&AWSCoreEditorRequests::SetAWSClientAuthEnabled);
AWSCoreEditorRequestBus::Broadcast(&AWSCoreEditorRequests::SetAWSMetricsEnabled);
AWSCoreEditorRequestBus::Broadcast(&AWSCoreEditorRequests::SetAWSGameLiftEnabled);
QList<QAction*> actualActions = testMenu.actions();
for (QList<QAction*>::iterator itr = actualActions.begin(); itr != actualActions.end(); itr++)
@ -73,5 +74,10 @@ TEST_F(AWSCoreEditorMenuTest, AWSCoreEditorMenu_BroadcastFeatureGemsAreEnabled_C
{
EXPECT_TRUE((*itr)->isEnabled());
}
if (QString::compare((*itr)->text(), AWSGameLiftActionText) == 0)
{
EXPECT_TRUE((*itr)->isEnabled());
}
}
}

@ -13,6 +13,7 @@
#include <AzFramework/Session/SessionConfig.h>
#include <AWSGameLiftClientLocalTicketTracker.h>
#include <AWSCoreBus.h>
#include <AWSGameLiftClientManager.h>
#include <AWSGameLiftClientSystemComponent.h>
#include <Request/AWSGameLiftAcceptMatchRequest.h>
@ -105,6 +106,8 @@ namespace AWSGameLift
m_gameliftClient.reset();
m_gameliftManager->ActivateManager();
m_gameliftTicketTracker->ActivateTracker();
AWSCore::AWSCoreEditorRequestBus::Broadcast(&AWSCore::AWSCoreEditorRequests::SetAWSGameLiftEnabled);
}
void AWSGameLiftClientSystemComponent::Deactivate()

@ -272,7 +272,10 @@ namespace AZ
else if (m_componentConfig.m_target)
{
const auto& viewport = m_componentConfig.m_target->GetViewport();
m_aspectRatio = viewport.m_maxX / viewport.m_maxY;
if (viewport.m_maxX > 0.0f && viewport.m_maxY > 0.0f)
{
m_aspectRatio = viewport.m_maxX / viewport.m_maxY;
}
}
}

@ -73,7 +73,7 @@
},
{
"Name": "m_colorGradingPreSaturation",
"Value": 1.0 // -100 ... 100
"Value": 1.0 // 0 ... 2
},
{
"Name": "m_colorFilterIntensity",
@ -101,7 +101,7 @@
},
{
"Name": "m_colorGradingPostSaturation",
"Value": 1.0 // -100 ... 100
"Value": 1.0 // 0 ... 2
},
{
"Name": "m_smhShadowsStart",

@ -105,6 +105,11 @@
}
]
},
{
"Name": "LutGenerationPass",
"TemplateName": "LutGenerationTemplate",
"Enabled": true
},
{
"Name": "LookModificationTransformPass",
"TemplateName": "LookModificationTransformTemplate",

@ -0,0 +1,47 @@
{
"Type": "JsonSerialization",
"Version": 1,
"ClassName": "PassAsset",
"ClassData": {
"PassTemplate": {
"Name": "LutGenerationTemplate",
"PassClass": "LutGenerationPass",
"Slots": [
{
"Name": "LutOutput",
"SlotType": "Output",
"ScopeAttachmentUsage": "RenderTarget",
"LoadStoreAction": {
"LoadAction": "DontCare"
}
}
],
"ImageAttachments": [
{
"Name": "ColorGradingLut",
"ImageDescriptor": {
"Format": "R32G32B32A32_FLOAT",
"BindFlags": [
"ShaderWrite"
]
}
}
],
"Connections": [
{
"LocalSlot": "LutOutput",
"AttachmentRef": {
"Pass": "This",
"Attachment": "ColorGradingLut"
}
}
],
"PassData": {
"$type": "FullscreenTrianglePassData",
"ShaderAsset": {
"FilePath": "Shaders/ColorGrading/LutGeneration.shader"
}
}
}
}
}

@ -511,7 +511,11 @@
{
"Name": "HDRColorGradingTemplate",
"Path": "Passes/HDRColorGrading.pass"
}
},
{
"Name": "LutGenerationTemplate",
"Path": "Passes/LutGeneration.pass"
}
]
}
}

@ -59,7 +59,7 @@ UNDISCLOSED.
#pragma once
static const float HALF_MAX = 65504.0f;
#include <Atom/Features/PostProcessing/Aces.azsli>
float AcesCcToLinear(float value)
{

@ -0,0 +1,154 @@
/*
* 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
*
*/
#pragma once
#include <Atom/RPI/Math.azsli>
#include <Atom/Features/ColorManagement/TransformColor.azsli>
#include <Atom/Features/PostProcessing/AcesColorSpaceConversion.azsli>
#include <3rdParty/Features/PostProcessing/PSstyleColorBlends_Separable.azsli>
#include <3rdParty/Features/PostProcessing/PSstyleColorBlends_NonSeparable.azsli>
#include <3rdParty/Features/PostProcessing/KelvinToRgb.azsli>
static const float FloatEpsilon = 1.192092896e-07; // 1.0 + FloatEpsilon != 1.0, smallest positive float
static const float FloatMin = FLOAT_32_MIN; // Min float number that is positive
static const float FloatMax = FLOAT_32_MAX; // Max float number representable
static const float AcesCcMidGrey = 0.4135884;
float SaturateWithEpsilon(float value)
{
return clamp(value, FloatEpsilon, 1.0f);
}
// Below are the color grading functions. These expect the frame color to be in ACEScg space.
// Note that some functions may have some quirks in their implementation and is subject to change.
float3 ColorGradePostExposure (float3 frameColor, float exposure)
{
frameColor *= pow(2.0f, exposure);
return frameColor;
}
// The contrast equation is performed in ACEScc (logarithmic) color space.
float3 ColorGradingContrast (float3 frameColor, float midgrey, float amount)
{
const float contrastAdjustment = amount * 0.01f + 1.0f;
frameColor = TransformColor(frameColor.rgb, ColorSpaceId::ACEScg, ColorSpaceId::ACEScc);
frameColor = (frameColor - midgrey) * contrastAdjustment + midgrey;
return frameColor = TransformColor(frameColor.rgb, ColorSpaceId::ACEScc, ColorSpaceId::ACEScg);
}
// The swatchColor param expects a linear RGB value.
float3 ColorGradeColorFilter (float3 frameColor, float3 swatchColor, float alpha, float colorFilterIntensity)
{
swatchColor = TransformColor(swatchColor, ColorSpaceId::LinearSRGB, ColorSpaceId::ACEScg);
swatchColor *= pow(2.0f, colorFilterIntensity);
const float3 frameAdjust = frameColor * swatchColor;
return frameColor = lerp(frameColor, frameAdjust, alpha);
}
float3 ColorGradeHueShift (float3 frameColor, float amount)
{
float3 frameHsv = RgbToHsv(frameColor);
const float hue = frameHsv.x + amount;
frameHsv.x = RotateHue(hue, 0.0, 1.0);
return HsvToRgb(frameHsv);
}
float3 ColorGradeSaturation (float3 frameColor, float control)
{
const float vLuminance = CalculateLuminance(frameColor, ColorSpaceId::ACEScg);
return (frameColor - vLuminance) * control + vLuminance;
}
float3 ColorGradeKelvinColorTemp(float3 frameColor, float kelvin)
{
const float3 kColor = TransformColor(KelvinToRgb(kelvin), ColorSpaceId::LinearSRGB, ColorSpaceId::ACEScg);
const float luminance = CalculateLuminance(frameColor, ColorSpaceId::ACEScg);
const float3 resHsl = RgbToHsl(frameColor.rgb * kColor.rgb); // Apply Kelvin color and convert to HSL
return HslToRgb(float3(resHsl.xy, luminance)); // Preserve luminance
}
// pow(f, e) won't work if f is negative, or may cause inf/NAN.
float3 NoNanPow(float3 base, float3 power)
{
return pow(max(abs(base), float3(FloatEpsilon, FloatEpsilon, FloatEpsilon)), power);
}
float3 ColorGradeSplitTone (
float3 frameColor,
float balance,
float weight,
float3 splitToneShadowsColor,
float3 splitToneHighlightsColor)
{
float3 frameSplitTone = NoNanPow(frameColor, 1.0 / 2.2);
const float t = SaturateWithEpsilon(CalculateLuminance(SaturateWithEpsilon(frameSplitTone), ColorSpaceId::ACEScg) + balance);
const float3 shadows = lerp(0.5, splitToneShadowsColor, 1.0 - t);
const float3 highlights = lerp(0.5, splitToneHighlightsColor, t);
frameSplitTone = BlendMode_SoftLight(frameSplitTone, shadows);
frameSplitTone = BlendMode_SoftLight(frameSplitTone, highlights);
frameSplitTone = NoNanPow(frameSplitTone, 2.2);
return lerp(frameColor.rgb, frameSplitTone.rgb, weight);
}
float3 ColorGradeChannelMixer (
float3 frameColor,
float3 channelMixingRed,
float3 channelMixingGreen,
float3 channelMixingBlue)
{
return mul(float3x3(channelMixingRed,
channelMixingGreen,
channelMixingBlue),
frameColor);
}
float3 ColorGradeShadowsMidtonesHighlights (float3 frameColor, float shadowsStart, float shadowsEnd,
float highlightsStart, float highlightsEnd, float weight,
float4 shadowsColor, float4 midtonesColor, float4 highlightsColor)
{
const float3 shadowsColorACEScg = TransformColor(shadowsColor.rgb, ColorSpaceId::LinearSRGB, ColorSpaceId::ACEScg);
const float3 midtonesColorACEScg = TransformColor(midtonesColor.rgb, ColorSpaceId::LinearSRGB, ColorSpaceId::ACEScg);
const float3 highlightsColorACEScg = TransformColor(highlightsColor.rgb, ColorSpaceId::LinearSRGB, ColorSpaceId::ACEScg);
const float cLuminance = CalculateLuminance(frameColor, ColorSpaceId::ACEScg);
const float shadowsWeight = 1.0 - smoothstep(shadowsStart, shadowsEnd, cLuminance);
const float highlightsWeight = smoothstep(highlightsStart, highlightsEnd, cLuminance);
const float midtonesWeight = 1.0 - shadowsWeight - highlightsWeight;
const float3 frameSmh = frameColor * shadowsColorACEScg * shadowsWeight +
frameColor * midtonesColorACEScg * midtonesWeight +
frameColor * highlightsColorACEScg * highlightsWeight;
return lerp(frameColor.rgb, frameSmh.rgb, weight);
}
// perform color grading in ACEScg space
float3 ColorGrade(float3 frameColor)
{
frameColor = lerp(frameColor, ColorGradePostExposure(frameColor, PassSrg::m_colorGradingExposure), PassSrg::m_colorAdjustmentWeight);
frameColor = lerp(frameColor, ColorGradeKelvinColorTemp(frameColor, PassSrg::m_whiteBalanceKelvin), PassSrg::m_whiteBalanceWeight);
frameColor = lerp(frameColor, ColorGradingContrast(frameColor, AcesCcMidGrey, PassSrg::m_colorGradingContrast), PassSrg::m_colorAdjustmentWeight);
frameColor = lerp(frameColor, ColorGradeColorFilter(frameColor, PassSrg::m_colorFilterSwatch.rgb,
PassSrg::m_colorFilterMultiply, PassSrg::m_colorFilterIntensity), PassSrg::m_colorAdjustmentWeight);
frameColor = max(frameColor, 0.0);
frameColor = lerp(frameColor, ColorGradeSaturation(frameColor, PassSrg::m_colorGradingPreSaturation), PassSrg::m_colorAdjustmentWeight);
frameColor = ColorGradeSplitTone(frameColor, PassSrg::m_splitToneBalance, PassSrg::m_splitToneWeight,
PassSrg::m_splitToneShadowsColor, PassSrg::m_splitToneHighlightsColor);
frameColor = ColorGradeChannelMixer(frameColor, PassSrg::m_channelMixingRed, PassSrg::m_channelMixingGreen, PassSrg::m_channelMixingBlue);
frameColor = max(frameColor, 0.0);
frameColor = ColorGradeShadowsMidtonesHighlights(frameColor, PassSrg::m_smhShadowsStart, PassSrg::m_smhShadowsEnd,
PassSrg::m_smhHighlightsStart, PassSrg::m_smhHighlightsEnd, PassSrg::m_smhWeight,
PassSrg::m_smhShadowsColor, PassSrg::m_smhMidtonesColor, PassSrg::m_smhHighlightsColor);
frameColor = lerp(frameColor, ColorGradeSaturation(frameColor, PassSrg::m_colorGradingPostSaturation), PassSrg::m_finalAdjustmentWeight);
frameColor = lerp(frameColor, ColorGradeHueShift(frameColor, PassSrg::m_colorGradingHueShift), PassSrg::m_finalAdjustmentWeight);
return max(frameColor.rgb, 0.0);
}

@ -0,0 +1,113 @@
/*
* 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
*
*/
#pragma once
#include <Atom/RPI/Math.azsli>
#include <Atom/Features/SrgSemantics.azsli>
#include <Atom/Features/PostProcessing/FullscreenVertex.azsli>
#include <Atom/Features/PostProcessing/Aces.azsli>
#include <Atom/Features/PostProcessing/Shapers.azsli>
float3 convert2Dto3DLutCoords(float2 uv, float width, float height)
{
// convert from center pixel uvs to [0,1]
float offset = 1.0/height/2.0;
float scale = 1.0 - offset*2.0;
float2 adjustedUv = float2(uv.x * width, uv.y * height);
float3 coords = float3(adjustedUv.x%height, 0.5 + int(adjustedUv.x/height), adjustedUv.y)/height;
return (coords - offset)/scale;
}
ShaderResourceGroup PassSrg : SRG_PerPass_WithFallback
{
// framebuffer sampler
Sampler PointSampler
{
MinFilter = Point;
MagFilter = Point;
MipFilter = Point;
AddressU = Clamp;
AddressV = Clamp;
AddressW = Clamp;
};
int m_lutResolution;
int m_shaperType;
float m_shaperBias;
float m_shaperScale;
float m_colorAdjustmentWeight;
float m_colorGradingExposure;
float m_colorGradingContrast;
float m_colorGradingPreSaturation;
float m_colorFilterIntensity;
float m_colorFilterMultiply;
float4 m_colorFilterSwatch;
float m_whiteBalanceWeight;
float m_whiteBalanceKelvin;
float m_whiteBalanceTint;
float m_splitToneBalance;
float m_splitToneWeight;
float4 m_splitToneShadowsColor;
float4 m_splitToneHighlightsColor;
float m_smhShadowsStart;
float m_smhShadowsEnd;
float m_smhHighlightsStart;
float m_smhHighlightsEnd;
float m_smhWeight;
float4 m_smhShadowsColor;
float4 m_smhMidtonesColor;
float4 m_smhHighlightsColor;
float3 m_channelMixingRed;
float3 m_channelMixingGreen;
float3 m_channelMixingBlue;
float m_finalAdjustmentWeight;
float m_colorGradingPostSaturation;
float m_colorGradingHueShift;
}
#include <Atom/Features/PostProcessing/HDRColorGradingCommon.azsl>
struct PSOutput
{
float4 m_lutOutput : SV_Target0;
};
PSOutput MainPS(VSOutput IN)
{
ShaperType shaperType = (ShaperType)PassSrg::m_shaperType;
int lutResolution = PassSrg::m_lutResolution;
PSOutput OUT;
// baseCoords are from 0-1
float3 baseCoords = convert2Dto3DLutCoords(IN.m_texCoord, lutResolution*lutResolution, lutResolution);
float3 linearColor = ShaperToLinear(baseCoords, shaperType, PassSrg::m_shaperBias, PassSrg::m_shaperScale);
linearColor = TransformColor(linearColor, ColorSpaceId::LinearSRGB, ColorSpaceId::ACEScg);
float3 gradedColor = ColorGrade(linearColor);
gradedColor = TransformColor(gradedColor, ColorSpaceId::ACEScg, ColorSpaceId::LinearSRGB);
// Bring back coordinates into 0-1
float3 shapedColor = LinearToShaper(gradedColor, shaperType, PassSrg::m_shaperBias, PassSrg::m_shaperScale);
shapedColor = saturate(shapedColor);
OUT.m_lutOutput = float4(shapedColor, 1.0);
return OUT;
}

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

@ -13,18 +13,6 @@
#include <Atom/Features/PostProcessing/FullscreenPixelInfo.azsli>
#include <Atom/Features/PostProcessing/FullscreenVertex.azsli>
#include <Atom/Features/ColorManagement/TransformColor.azsli>
#include <Atom/Features/PostProcessing/AcesColorSpaceConversion.azsli>
#include <3rdParty/Features/PostProcessing/PSstyleColorBlends_Separable.azsli>
#include <3rdParty/Features/PostProcessing/PSstyleColorBlends_NonSeparable.azsli>
#include <3rdParty/Features/PostProcessing/KelvinToRgb.azsli>
static const float FloatEpsilon = 1.192092896e-07; // 1.0 + FloatEpsilon != 1.0, smallest positive float
static const float FloatMin = FLOAT_32_MIN; // Min float number that is positive
static const float FloatMax = FLOAT_32_MAX; // Max float number representable
static const float AcesCcMidGrey = 0.4135884;
ShaderResourceGroup PassSrg : SRG_PerPass_WithFallback
{
// get the framebuffer
@ -41,153 +29,42 @@ ShaderResourceGroup PassSrg : SRG_PerPass_WithFallback
AddressW = Clamp;
};
float m_colorAdjustmentWeight;
float m_colorGradingExposure;
float m_colorGradingContrast;
float m_colorGradingHueShift;
float m_colorGradingPreSaturation;
float m_colorFilterIntensity;
float m_colorFilterMultiply;
float4 m_colorFilterSwatch;
float m_whiteBalanceWeight;
float m_whiteBalanceKelvin;
float m_whiteBalanceTint;
float m_splitToneBalance;
float m_splitToneWeight;
float m_colorGradingPostSaturation;
float4 m_splitToneShadowsColor;
float4 m_splitToneHighlightsColor;
float m_smhShadowsStart;
float m_smhShadowsEnd;
float m_smhHighlightsStart;
float m_smhHighlightsEnd;
float m_smhWeight;
float3 m_channelMixingRed;
float3 m_channelMixingGreen;
float3 m_channelMixingBlue;
float4 m_colorFilterSwatch;
float4 m_splitToneShadowsColor;
float4 m_splitToneHighlightsColor;
float4 m_smhShadowsColor;
float4 m_smhMidtonesColor;
float4 m_smhHighlightsColor;
}
float SaturateWithEpsilon(float value)
{
return clamp(value, FloatEpsilon, 1.0f);
}
// Below are the color grading functions. These expect the frame color to be in ACEScg space.
// Note that some functions may have some quirks in their implementation and is subject to change.
float3 ColorGradePostExposure (float3 frameColor, float exposure)
{
frameColor *= pow(2.0f, exposure);
return frameColor;
}
// The contrast equation is performed in ACEScc (logarithmic) color space.
float3 ColorGradingContrast (float3 frameColor, float midgrey, float amount)
{
const float contrastAdjustment = amount * 0.01f + 1.0f;
frameColor = TransformColor(frameColor.rgb, ColorSpaceId::ACEScg, ColorSpaceId::ACEScc);
frameColor = (frameColor - midgrey) * contrastAdjustment + midgrey;
return frameColor = TransformColor(frameColor.rgb, ColorSpaceId::ACEScc, ColorSpaceId::ACEScg);
}
// The swatchColor param expects a linear RGB value.
float3 ColorGradeColorFilter (float3 frameColor, float3 swatchColor, float alpha)
{
swatchColor = TransformColor(swatchColor, ColorSpaceId::LinearSRGB, ColorSpaceId::ACEScg);
swatchColor *= pow(2.0f, PassSrg::m_colorFilterIntensity);
const float3 frameAdjust = frameColor * swatchColor;
return frameColor = lerp(frameColor, frameAdjust, alpha);
}
float3 ColorGradeHueShift (float3 frameColor, float amount)
{
float3 frameHsv = RgbToHsv(frameColor);
const float hue = frameHsv.x + amount;
frameHsv.x = RotateHue(hue, 0.0, 1.0);
return HsvToRgb(frameHsv);
}
float3 ColorGradeSaturation (float3 frameColor, float control)
{
const float vLuminance = CalculateLuminance(frameColor, ColorSpaceId::ACEScg);
return (frameColor - vLuminance) * control + vLuminance;
}
float3 ColorGradeKelvinColorTemp(float3 frameColor, float kelvin)
{
const float3 kColor = TransformColor(KelvinToRgb(kelvin), ColorSpaceId::LinearSRGB, ColorSpaceId::ACEScg);
const float luminance = CalculateLuminance(frameColor, ColorSpaceId::ACEScg);
const float3 resHsl = RgbToHsl(frameColor.rgb * kColor.rgb); // Apply Kelvin color and convert to HSL
return HslToRgb(float3(resHsl.xy, luminance)); // Preserve luminance
}
// pow(f, e) won't work if f is negative, or may cause inf/NAN.
float3 NoNanPow(float3 base, float3 power)
{
return pow(max(abs(base), float3(FloatEpsilon, FloatEpsilon, FloatEpsilon)), power);
}
float3 ColorGradeSplitTone (float3 frameColor, float balance, float weight)
{
float3 frameSplitTone = NoNanPow(frameColor, 1.0 / 2.2);
const float t = SaturateWithEpsilon(CalculateLuminance(SaturateWithEpsilon(frameSplitTone), ColorSpaceId::ACEScg) + balance);
const float3 shadows = lerp(0.5, PassSrg::m_splitToneShadowsColor.rgb, 1.0 - t);
const float3 highlights = lerp(0.5, PassSrg::m_splitToneHighlightsColor.rgb, t);
frameSplitTone = BlendMode_SoftLight(frameSplitTone, shadows);
frameSplitTone = BlendMode_SoftLight(frameSplitTone, highlights);
frameSplitTone = NoNanPow(frameSplitTone, 2.2);
return lerp(frameColor.rgb, frameSplitTone.rgb, weight);
}
float3 ColorGradeChannelMixer (float3 frameColor)
{
return mul(float3x3(PassSrg::m_channelMixingRed.rgb,
PassSrg::m_channelMixingGreen.rgb,
PassSrg::m_channelMixingBlue.rgb),
frameColor);
}
float3 m_channelMixingRed;
float3 m_channelMixingGreen;
float3 m_channelMixingBlue;
float3 ColorGradeShadowsMidtonesHighlights (float3 frameColor, float shadowsStart, float shadowsEnd,
float highlightsStart, float highlightsEnd, float weight,
float4 shadowsColor, float4 midtonesColor, float4 highlightsColor)
{
const float3 shadowsColorACEScg = TransformColor(shadowsColor.rgb, ColorSpaceId::LinearSRGB, ColorSpaceId::ACEScg);
const float3 midtonesColorACEScg = TransformColor(midtonesColor.rgb, ColorSpaceId::LinearSRGB, ColorSpaceId::ACEScg);
const float3 highlightsColorACEScg = TransformColor(highlightsColor.rgb, ColorSpaceId::LinearSRGB, ColorSpaceId::ACEScg);
const float cLuminance = CalculateLuminance(frameColor, ColorSpaceId::ACEScg);
const float shadowsWeight = 1.0 - smoothstep(shadowsStart, shadowsEnd, cLuminance);
const float highlightsWeight = smoothstep(highlightsStart, highlightsEnd, cLuminance);
const float midtonesWeight = 1.0 - shadowsWeight - highlightsWeight;
const float3 frameSmh = frameColor * shadowsColorACEScg * shadowsWeight +
frameColor * midtonesColorACEScg * midtonesWeight +
frameColor * highlightsColorACEScg * highlightsWeight;
return lerp(frameColor.rgb, frameSmh.rgb, weight);
float m_finalAdjustmentWeight;
float m_colorGradingPostSaturation;
float m_colorGradingHueShift;
}
float3 ColorGrade (float3 frameColor)
{
frameColor = ColorGradePostExposure(frameColor, PassSrg::m_colorGradingExposure);
frameColor = ColorGradeKelvinColorTemp(frameColor, PassSrg::m_whiteBalanceKelvin);
frameColor = ColorGradingContrast(frameColor, AcesCcMidGrey, PassSrg::m_colorGradingContrast);
frameColor = ColorGradeColorFilter(frameColor, PassSrg::m_colorFilterSwatch.rgb,
PassSrg::m_colorFilterMultiply);
frameColor = max(frameColor, 0.0);
frameColor = ColorGradeSaturation(frameColor, PassSrg::m_colorGradingPreSaturation);
frameColor = ColorGradeSplitTone(frameColor, PassSrg::m_splitToneBalance, PassSrg::m_splitToneWeight);
frameColor = ColorGradeChannelMixer(frameColor);
frameColor = max(frameColor, 0.0);
frameColor = ColorGradeShadowsMidtonesHighlights(frameColor, PassSrg::m_smhShadowsStart, PassSrg::m_smhShadowsEnd,
PassSrg::m_smhHighlightsStart, PassSrg::m_smhHighlightsEnd, PassSrg::m_smhWeight,
PassSrg::m_smhShadowsColor, PassSrg::m_smhMidtonesColor, PassSrg::m_smhHighlightsColor);
frameColor = ColorGradeHueShift(frameColor, PassSrg::m_colorGradingHueShift);
frameColor = ColorGradeSaturation(frameColor, PassSrg::m_colorGradingPostSaturation);
return frameColor.rgb;
}
#include <Atom/Features/PostProcessing/HDRColorGradingCommon.azsl>
PSOutput MainPS(VSOutput IN)
{

@ -86,7 +86,6 @@ set(FILES
Passes/CascadedShadowmaps.pass
Passes/CheckerboardResolveColor.pass
Passes/CheckerboardResolveDepth.pass
Passes/HDRColorGrading.pass
Passes/ContrastAdaptiveSharpening.pass
Passes/ConvertToAcescg.pass
Passes/DebugOverlayParent.pass
@ -144,6 +143,7 @@ set(FILES
Passes/ForwardSubsurfaceMSAA.pass
Passes/FullscreenCopy.pass
Passes/FullscreenOutputOnly.pass
Passes/HDRColorGrading.pass
Passes/ImGui.pass
Passes/KawaseShadowBlur.pass
Passes/LightAdaptationParent.pass
@ -158,6 +158,7 @@ set(FILES
Passes/LowEndPipeline.pass
Passes/LuminanceHeatmap.pass
Passes/LuminanceHistogramGenerator.pass
Passes/LutGeneration.pass
Passes/MainPipeline.pass
Passes/MainPipelineRenderToTexture.pass
Passes/ThumbnailPipeline.pass
@ -281,6 +282,7 @@ set(FILES
ShaderLib/Atom/Features/PostProcessing/FullscreenVertexUtil.azsli
ShaderLib/Atom/Features/PostProcessing/GlyphData.azsli
ShaderLib/Atom/Features/PostProcessing/GlyphRender.azsli
ShaderLib/Atom/Features/PostProcessing/HDRColorGradingCommon.azsl
ShaderLib/Atom/Features/PostProcessing/PostProcessUtil.azsli
ShaderLib/Atom/Features/RayTracing/RayTracingSceneSrg.azsli
ShaderLib/Atom/Features/ScreenSpace/ScreenSpaceUtil.azsli
@ -315,6 +317,8 @@ set(FILES
Shaders/BRDFTexture/BRDFTextureCS.shader
Shaders/Checkerboard/CheckerboardColorResolveCS.azsl
Shaders/Checkerboard/CheckerboardColorResolveCS.shader
Shaders/ColorGrading/LutGeneration.azsl
Shaders/ColorGrading/LutGeneration.shader
Shaders/Depth/DepthPass.azsl
Shaders/Depth/DepthPass.shader
Shaders/Depth/DepthPassTransparentMax.shader

@ -39,6 +39,7 @@ ly_add_target(
Gem::Atom_Utils.Static
Gem::Atom_Feature_Common.Public
Gem::ImGui.imguilib
3rdParty::TIFF
#3rdParty::lux_core # AZ_TRAIT_LUXCORE_SUPPORTED is disabled in every platform, Issue #3915 will remove
RUNTIME_DEPENDENCIES
Gem::ImGui.imguilib
@ -75,6 +76,7 @@ ly_add_target(
FILES_CMAKE
atom_feature_common_shared_files.cmake
../Assets/atom_feature_common_asset_files.cmake
../Editor/atom_feature_common_editor_script_files.cmake
PLATFORM_INCLUDE_FILES
${pal_source_dir}/runtime_dependencies_clients.cmake
INCLUDE_DIRECTORIES

@ -0,0 +1,22 @@
/*
* 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
*
*/
#pragma once
namespace AZ
{
namespace Render
{
enum class LutResolution
{
Lut16x16x16 = 16,
Lut32x32x32 = 32,
Lut64x64x64 = 64
};
}
}

@ -10,28 +10,42 @@
// PARAM(NAME, MEMBER_NAME, DEFAULT_VALUE, ...)
AZ_GFX_BOOL_PARAM(Enabled, m_enabled, false)
AZ_GFX_BOOL_PARAM(GenerateLut, m_generateLut, false)
AZ_GFX_FLOAT_PARAM(ColorAdjustmentWeight, m_colorAdjustmentWeight, 1.0)
AZ_GFX_FLOAT_PARAM(ColorGradingExposure, m_colorGradingExposure, 0.0)
AZ_GFX_FLOAT_PARAM(ColorGradingContrast, m_colorGradingContrast, 0.0)
AZ_GFX_FLOAT_PARAM(ColorGradingHueShift, m_colorGradingHueShift, 0.0)
AZ_GFX_FLOAT_PARAM(ColorGradingPreSaturation, m_colorGradingPreSaturation, 1.0)
AZ_GFX_FLOAT_PARAM(ColorGradingPreSaturation, m_colorGradingPreSaturation, 0.0)
AZ_GFX_FLOAT_PARAM(ColorGradingFilterIntensity, m_colorGradingFilterIntensity, 1.0)
AZ_GFX_FLOAT_PARAM(ColorGradingFilterMultiply, m_colorGradingFilterMultiply, 0.0)
AZ_GFX_FLOAT_PARAM(ColorGradingPostSaturation, m_colorGradingPostSaturation, 1.0)
AZ_GFX_VEC3_PARAM(ColorFilterSwatch, m_colorFilterSwatch, AZ::Vector3(1.0f, 0.5f, 0.5f))
AZ_GFX_FLOAT_PARAM(WhiteBalanceWeight, m_whiteBalanceWeight, 0.0)
AZ_GFX_FLOAT_PARAM(WhiteBalanceKelvin, m_whiteBalanceKelvin, 6600.0)
AZ_GFX_FLOAT_PARAM(WhiteBalanceTint, m_whiteBalanceTint, 0.0)
AZ_GFX_FLOAT_PARAM(SplitToneBalance, m_splitToneBalance, 0.0)
AZ_GFX_FLOAT_PARAM(SplitToneWeight, m_splitToneWeight, 0.0)
AZ_GFX_FLOAT_PARAM(SplitToneBalance, m_splitToneBalance, 0.0)
AZ_GFX_VEC3_PARAM(SplitToneShadowsColor, m_splitToneShadowsColor, AZ::Vector3(1.0f, 0.5f, 0.5f))
AZ_GFX_VEC3_PARAM(SplitToneHighlightsColor, m_splitToneHighlightsColor, AZ::Vector3(0.1f, 1.0f, 0.1f))
AZ_GFX_FLOAT_PARAM(SmhWeight, m_smhWeight, 0.0)
AZ_GFX_FLOAT_PARAM(SmhShadowsStart, m_smhShadowsStart, 0.0)
AZ_GFX_FLOAT_PARAM(SmhShadowsEnd, m_smhShadowsEnd, 0.3)
AZ_GFX_FLOAT_PARAM(SmhHighlightsStart, m_smhHighlightsStart, 0.55)
AZ_GFX_FLOAT_PARAM(SmhHighlightsEnd, m_smhHighlightsEnd, 1.0)
AZ_GFX_FLOAT_PARAM(SmhWeight, m_smhWeight, 0.0)
AZ_GFX_VEC3_PARAM(ChannelMixingRed, m_channelMixingRed, AZ::Vector3(1.0f, 0.0f, 0.0f))
AZ_GFX_VEC3_PARAM(ChannelMixingGreen, m_channelMixingGreen, AZ::Vector3(0.0f, 1.0f, 0.0f))
AZ_GFX_VEC3_PARAM(ChannelMixingBlue, m_channelMixingBlue, AZ::Vector3(0.0f, 0.f, 1.0f))
AZ_GFX_VEC3_PARAM(ColorFilterSwatch, m_colorFilterSwatch, AZ::Vector3(1.0f, 0.5f, 0.5f))
AZ_GFX_VEC3_PARAM(SplitToneShadowsColor, m_splitToneShadowsColor, AZ::Vector3(1.0f, 0.5f, 0.5f))
AZ_GFX_VEC3_PARAM(SplitToneHighlightsColor, m_splitToneHighlightsColor, AZ::Vector3(0.1f, 1.0f, 0.1f))
AZ_GFX_VEC3_PARAM(SmhShadowsColor, m_smhShadowsColor, AZ::Vector3(1.0f, 0.25f, 0.25f))
AZ_GFX_VEC3_PARAM(SmhMidtonesColor, m_smhMidtonesColor, AZ::Vector3(0.1f, 0.1f, 1.0f))
AZ_GFX_VEC3_PARAM(SmhHighlightsColor, m_smhHighlightsColor, AZ::Vector3(1.0f, 0.0f, 1.0f))
AZ_GFX_VEC3_PARAM(ChannelMixingRed, m_channelMixingRed, AZ::Vector3(1.0f, 0.0f, 0.0f))
AZ_GFX_VEC3_PARAM(ChannelMixingGreen, m_channelMixingGreen, AZ::Vector3(0.0f, 1.0f, 0.0f))
AZ_GFX_VEC3_PARAM(ChannelMixingBlue, m_channelMixingBlue, AZ::Vector3(0.0f, 0.f, 1.0f))
AZ_GFX_COMMON_PARAM(AZ::Render::LutResolution, LutResolution, m_lutResolution, AZ::Render::LutResolution::Lut16x16x16)
AZ_GFX_COMMON_PARAM(AZ::Render::ShaperPresetType, ShaperPresetType, m_shaperPresetType, AZ::Render::ShaperPresetType::None)
AZ_GFX_COMMON_PARAM(float, CustomMinExposure, m_customMinExposure, -6.5)
AZ_GFX_COMMON_PARAM(float, CustomMaxExposure, m_customMaxExposure, 6.5)
AZ_GFX_FLOAT_PARAM(FinalAdjustmentWeight, m_finalAdjustmentWeight, 1.0)
AZ_GFX_FLOAT_PARAM(ColorGradingPostSaturation, m_colorGradingPostSaturation, 0.0)
AZ_GFX_FLOAT_PARAM(ColorGradingHueShift, m_colorGradingHueShift, 0.0)

@ -12,6 +12,8 @@
#include <AzCore/Math/Vector4.h>
#include <AzCore/RTTI/RTTI.h>
#include <AzCore/Component/EntityId.h>
#include <Atom/Feature/ColorGrading/LutResolution.h>
#include <ACES/Aces.h>
namespace AZ
{

@ -28,15 +28,6 @@ namespace AZ
//! Dump the PipelineStatistics from passes to a json file.
virtual bool CapturePassPipelineStatistics(const AZStd::string& outputFilePath) = 0;
//! Dump a single frame of Cpu profiling data
virtual bool CaptureCpuProfilingStatistics(const AZStd::string& outputFilePath) = 0;
//! Start a multiframe capture of CPU profiling data.
virtual bool BeginContinuousCpuProfilingCapture() = 0;
//! End and dump an in-progress continuous capture.
virtual bool EndContinuousCpuProfilingCapture(const AZStd::string& outputFilePath) = 0;
//! Dump the benchmark metadata to a json file.
virtual bool CaptureBenchmarkMetadata(const AZStd::string& benchmarkName, const AZStd::string& outputFilePath) = 0;
};
@ -63,11 +54,6 @@ namespace AZ
//! @param info The output file path or error information which depends on the return.
virtual void OnCaptureQueryPipelineStatisticsFinished(bool result, const AZStd::string& info) = 0;
//! Notify when the current CpuProfilingStatistics capture is finished
//! @param result Set to true if it's finished successfully
//! @param info The output file path or error information which depends on the return.
virtual void OnCaptureCpuProfilingStatisticsFinished(bool result, const AZStd::string& info) = 0;
//! Notify when the current BenchmarkMetadata capture is finished
//! @param result Set to true if it's finished successfully
//! @param info The output file path or error information which depends on the return.

@ -0,0 +1,90 @@
/*
* 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
*
*/
#include <ColorGrading/LutGenerationPass.h>
#include <Atom/Feature/ACES/AcesDisplayMapperFeatureProcessor.h>
#include <Atom/RPI.Public/Scene.h>
#include <Atom/RPI.Reflect/Asset/AssetUtils.h>
namespace AZ
{
namespace Render
{
RPI::Ptr<LutGenerationPass> LutGenerationPass::Create(const RPI::PassDescriptor& descriptor)
{
RPI::Ptr<LutGenerationPass> pass = aznew LutGenerationPass(descriptor);
return AZStd::move(pass);
}
LutGenerationPass::LutGenerationPass(const RPI::PassDescriptor& descriptor)
: HDRColorGradingPass(descriptor)
{
}
void LutGenerationPass::InitializeInternal()
{
HDRColorGradingPass::InitializeInternal();
m_lutResolutionIndex.Reset();
m_lutShaperTypeIndex.Reset();
m_lutShaperScaleIndex.Reset();
}
void LutGenerationPass::FrameBeginInternal(FramePrepareParams params)
{
const auto* colorGradingSettings = GetHDRColorGradingSettings();
if (colorGradingSettings)
{
m_shaderResourceGroup->SetConstant(m_lutResolutionIndex, colorGradingSettings->GetLutResolution());
auto shaperParams = AcesDisplayMapperFeatureProcessor::GetShaperParameters(
colorGradingSettings->GetShaperPresetType(),
colorGradingSettings->GetCustomMinExposure(),
colorGradingSettings->GetCustomMaxExposure());
m_shaderResourceGroup->SetConstant(m_lutShaperTypeIndex, shaperParams.m_type);
m_shaderResourceGroup->SetConstant(m_lutShaperBiasIndex, shaperParams.m_bias);
m_shaderResourceGroup->SetConstant(m_lutShaperScaleIndex, shaperParams.m_scale);
}
HDRColorGradingPass::FrameBeginInternal(params);
}
void LutGenerationPass::BuildCommandListInternal(const RHI::FrameGraphExecuteContext& context)
{
const auto* colorGradingSettings = GetHDRColorGradingSettings();
if (colorGradingSettings)
{
uint32_t lutResolution = aznumeric_cast<uint32_t>(colorGradingSettings->GetLutResolution());
RHI::Size lutSize{ lutResolution * lutResolution, lutResolution, 1 };
RPI::Ptr<RPI::PassAttachment> attachment = FindOwnedAttachment(Name{ "ColorGradingLut" });
RHI::ImageDescriptor& imageDescriptor = attachment->m_descriptor.m_image;
imageDescriptor.m_size = lutSize;
SetViewportScissorFromImageSize(lutSize);
}
HDRColorGradingPass::BuildCommandListInternal(context);
}
bool LutGenerationPass::IsEnabled() const
{
const auto* colorGradingSettings = GetHDRColorGradingSettings();
return colorGradingSettings ? colorGradingSettings->GetGenerateLut() : false;
}
void LutGenerationPass::SetViewportScissorFromImageSize(const RHI::Size& imageSize)
{
const RHI::Viewport viewport(0.f, imageSize.m_width * 1.f, 0.f, imageSize.m_height * 1.f);
const RHI::Scissor scissor(0, 0, imageSize.m_width, imageSize.m_height);
m_viewportState = viewport;
m_scissorState = scissor;
}
} // namespace Render
} // namespace AZ

@ -0,0 +1,54 @@
/*
* 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
*
*/
#pragma once
#include <PostProcessing/HDRColorGradingPass.h>
#include <Atom/RPI.Public/Shader/Shader.h>
#include <Atom/RPI.Public/Shader/ShaderResourceGroup.h>
#include <Atom/RPI.Reflect/Shader/ShaderVariantKey.h>
#include <PostProcess/ColorGrading/HDRColorGradingSettings.h>
#include <Atom/Feature/DisplayMapper/DisplayMapperFeatureProcessorInterface.h>
namespace AZ
{
namespace Render
{
// Performs color grading on an identity LUT strip
class LutGenerationPass
: public AZ::Render::HDRColorGradingPass
{
public:
AZ_RTTI(LutGenerationPass, "{C21DABA8-B538-4C80-BA18-5B97CC9259E5}", AZ::RPI::FullscreenTrianglePass);
AZ_CLASS_ALLOCATOR(LutGenerationPass, SystemAllocator, 0);
virtual ~LutGenerationPass() = default;
//! Creates a ColorGradingPass
static RPI::Ptr<LutGenerationPass> Create(const RPI::PassDescriptor& descriptor);
protected:
LutGenerationPass(const RPI::PassDescriptor& descriptor);
void InitializeInternal() override;
void FrameBeginInternal(FramePrepareParams params) override;
void BuildCommandListInternal(const RHI::FrameGraphExecuteContext& context) override;
bool IsEnabled() const override;
private:
// Set viewport scissor based on output LUT resolution
void SetViewportScissorFromImageSize(const RHI::Size& imageSize);
RHI::ShaderInputNameIndex m_lutResolutionIndex = "m_lutResolution";
RHI::ShaderInputNameIndex m_lutShaperTypeIndex = "m_shaperType";
RHI::ShaderInputNameIndex m_lutShaperBiasIndex = "m_shaperBias";
RHI::ShaderInputNameIndex m_lutShaperScaleIndex = "m_shaperScale";
};
} // namespace Render
} // namespace AZ

@ -39,6 +39,7 @@
#include <Atom/Feature/AuxGeom/AuxGeomFeatureProcessor.h>
#include <Atom/Feature/Utils/LightingPreset.h>
#include <Atom/Feature/Utils/ModelPreset.h>
#include <ColorGrading/LutGenerationPass.h>
#include <PostProcess/PostProcessFeatureProcessor.h>
#include <PostProcessing/BlendColorGradingLutsPass.h>
#include <PostProcessing/BloomParentPass.h>
@ -230,6 +231,7 @@ namespace AZ
passSystem->AddPassCreator(Name("HDRColorGradingPass"), &HDRColorGradingPass::Create);
passSystem->AddPassCreator(Name("LookModificationCompositePass"), &LookModificationCompositePass::Create);
passSystem->AddPassCreator(Name("LookModificationTransformPass"), &LookModificationPass::Create);
passSystem->AddPassCreator(Name("LutGenerationPass"), &LutGenerationPass::Create);
passSystem->AddPassCreator(Name("SMAAEdgeDetectionPass"), &SMAAEdgeDetectionPass::Create);
passSystem->AddPassCreator(Name("SMAABlendingWeightCalculationPass"), &SMAABlendingWeightCalculationPass::Create);
passSystem->AddPassCreator(Name("SMAANeighborhoodBlendingPass"), &SMAANeighborhoodBlendingPass::Create);

@ -35,6 +35,7 @@
#include <AzCore/Preprocessor/EnumReflectUtils.h>
#include <AzCore/Console/Console.h>
#include <tiffio.h>
namespace AZ
{
namespace Render
@ -110,6 +111,48 @@ namespace AZ
return FrameCaptureOutputResult{FrameCaptureResult::InternalError, "Unable to save frame capture output to '" + outputFilePath + "'"};
}
FrameCaptureOutputResult TiffFrameCaptureOutput(
const AZStd::string& outputFilePath, const AZ::RPI::AttachmentReadback::ReadbackResult& readbackResult)
{
AZStd::shared_ptr<AZStd::vector<uint8_t>> buffer = readbackResult.m_dataBuffer;
const uint32_t width = readbackResult.m_imageDescriptor.m_size.m_width;
const uint32_t height = readbackResult.m_imageDescriptor.m_size.m_height;
const uint32_t numChannels = AZ::RHI::GetFormatComponentCount(readbackResult.m_imageDescriptor.m_format);
const uint32_t bytesPerChannel = AZ::RHI::GetFormatSize(readbackResult.m_imageDescriptor.m_format) / numChannels;
const uint32_t bitsPerChannel = bytesPerChannel * 8;
TIFF* out = TIFFOpen(outputFilePath.c_str(), "w");
TIFFSetField(out, TIFFTAG_IMAGEWIDTH, width);
TIFFSetField(out, TIFFTAG_IMAGELENGTH, height);
TIFFSetField(out, TIFFTAG_SAMPLESPERPIXEL, numChannels);
TIFFSetField(out, TIFFTAG_BITSPERSAMPLE, bitsPerChannel);
TIFFSetField(out, TIFFTAG_COMPRESSION, COMPRESSION_NONE);
TIFFSetField(out, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT);
TIFFSetField(out, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG);
TIFFSetField(out, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB);
TIFFSetField(out, TIFFTAG_SAMPLEFORMAT, SAMPLEFORMAT_IEEEFP); // interpret each pixel as a float
size_t pitch = width * numChannels * bytesPerChannel;
AZ_Assert((pitch * height) == buffer->size(), "Image buffer does not match allocated bytes for tiff saving.")
unsigned char* raster = (unsigned char*)_TIFFmalloc((tsize_t)(pitch * height));
memcpy(raster, buffer->data(), pitch * height);
bool success = true;
for (uint32_t h = 0; h < height; ++h)
{
size_t offset = h * pitch;
int err = TIFFWriteScanline(out, raster + offset, h, 0);
if (err < 0)
{
success = false;
break;
}
}
_TIFFfree(raster);
TIFFClose(out);
return success ? FrameCaptureOutputResult{ FrameCaptureResult::Success, AZStd::nullopt }
: FrameCaptureOutputResult{ FrameCaptureResult::InternalError, "Unable to save tif frame capture output to " + outputFilePath };
}
FrameCaptureOutputResult DdsFrameCaptureOutput(
const AZStd::string& outputFilePath, const AZ::RPI::AttachmentReadback::ReadbackResult& readbackResult)
{
@ -492,6 +535,12 @@ namespace AZ
m_result = ddsFrameCapture.m_result;
m_latestCaptureInfo = ddsFrameCapture.m_errorMessage.value_or("");
}
else if (extension == "tiff" || extension == "tif")
{
const auto tifFrameCapture = TiffFrameCaptureOutput(m_outputFilePath, readbackResult);
m_result = tifFrameCapture.m_result;
m_latestCaptureInfo = tifFrameCapture.m_errorMessage.value_or("");
}
else if (extension == "png")
{
if (readbackResult.m_imageDescriptor.m_format == RHI::Format::R8G8B8A8_UNORM ||

@ -33,6 +33,7 @@ namespace AZ
target->m_enabled = m_enabled;
#define AZ_GFX_BOOL_PARAM(NAME, MEMBER_NAME, DefaultValue) ;
#define AZ_GFX_COMMON_PARAM(ValueType, Name, MemberName, DefaultValue) ;
#define AZ_GFX_FLOAT_PARAM(NAME, MEMBER_NAME, DefaultValue) \
{ \
target->Set##NAME(AZ::Lerp(target->MEMBER_NAME, MEMBER_NAME, alpha)); \

@ -13,6 +13,7 @@
#include <Atom/Feature/PostProcess/ColorGrading/HDRColorGradingSettingsInterface.h>
#include <PostProcess/PostProcessBase.h>
#include <ACES/Aces.h>
namespace AZ
{

@ -32,33 +32,39 @@
{
FullscreenTrianglePass::InitializeInternal();
m_colorAdjustmentWeightIndex.Reset();
m_colorGradingExposureIndex.Reset();
m_colorGradingContrastIndex.Reset();
m_colorGradingHueShiftIndex.Reset();
m_colorGradingPreSaturationIndex.Reset();
m_colorFilterIntensityIndex.Reset();
m_colorFilterMultiplyIndex.Reset();
m_colorFilterSwatchIndex.Reset();
m_whiteBalanceWeightIndex.Reset();
m_whiteBalanceKelvinIndex.Reset();
m_whiteBalanceTintIndex.Reset();
m_splitToneBalanceIndex.Reset();
m_splitToneWeightIndex.Reset();
m_colorGradingPostSaturationIndex.Reset();
m_splitToneShadowsColorIndex.Reset();
m_splitToneHighlightsColorIndex.Reset();
m_smhShadowsStartIndex.Reset();
m_smhShadowsEndIndex.Reset();
m_smhHighlightsStartIndex.Reset();
m_smhHighlightsEndIndex.Reset();
m_smhWeightIndex.Reset();
m_smhShadowsColorIndex.Reset();
m_smhMidtonesColorIndex.Reset();
m_smhHighlightsColorIndex.Reset();
m_channelMixingRedIndex.Reset();
m_channelMixingGreenIndex.Reset();
m_channelMixingBlueIndex.Reset();
m_colorFilterSwatchIndex.Reset();
m_splitToneShadowsColorIndex.Reset();
m_splitToneHighlightsColorIndex.Reset();
m_smhShadowsColorIndex.Reset();
m_smhMidtonesColorIndex.Reset();
m_smhHighlightsColorIndex.Reset();
m_finalAdjustmentWeightIndex.Reset();
m_colorGradingPostSaturationIndex.Reset();
m_colorGradingHueShiftIndex.Reset();
}
void HDRColorGradingPass::FrameBeginInternal(FramePrepareParams params)
@ -79,33 +85,39 @@
const HDRColorGradingSettings* settings = GetHDRColorGradingSettings();
if (settings)
{
m_shaderResourceGroup->SetConstant(m_colorAdjustmentWeightIndex, settings->GetColorAdjustmentWeight());
m_shaderResourceGroup->SetConstant(m_colorGradingExposureIndex, settings->GetColorGradingExposure());
m_shaderResourceGroup->SetConstant(m_colorGradingContrastIndex, settings->GetColorGradingContrast());
m_shaderResourceGroup->SetConstant(m_colorGradingHueShiftIndex, settings->GetColorGradingHueShift());
m_shaderResourceGroup->SetConstant(m_colorGradingPreSaturationIndex, settings->GetColorGradingPreSaturation());
m_shaderResourceGroup->SetConstant(m_colorGradingPreSaturationIndex, settings->GetColorGradingPreSaturation() * 0.01f + 1.0f);
m_shaderResourceGroup->SetConstant(m_colorFilterIntensityIndex, settings->GetColorGradingFilterIntensity());
m_shaderResourceGroup->SetConstant(m_colorFilterMultiplyIndex, settings->GetColorGradingFilterMultiply());
m_shaderResourceGroup->SetConstant(m_colorFilterSwatchIndex, AZ::Vector4(settings->GetColorFilterSwatch()));
m_shaderResourceGroup->SetConstant(m_whiteBalanceWeightIndex, settings->GetWhiteBalanceWeight());
m_shaderResourceGroup->SetConstant(m_whiteBalanceKelvinIndex, settings->GetWhiteBalanceKelvin());
m_shaderResourceGroup->SetConstant(m_whiteBalanceTintIndex, settings->GetWhiteBalanceTint());
m_shaderResourceGroup->SetConstant(m_splitToneBalanceIndex, settings->GetSplitToneBalance());
m_shaderResourceGroup->SetConstant(m_splitToneWeightIndex, settings->GetSplitToneWeight());
m_shaderResourceGroup->SetConstant(m_colorGradingPostSaturationIndex, settings->GetColorGradingPostSaturation());
m_shaderResourceGroup->SetConstant(m_splitToneShadowsColorIndex, AZ::Vector4(settings->GetSplitToneShadowsColor()));
m_shaderResourceGroup->SetConstant(m_splitToneHighlightsColorIndex, AZ::Vector4(settings->GetSplitToneHighlightsColor()));
m_shaderResourceGroup->SetConstant(m_smhShadowsStartIndex, settings->GetSmhShadowsStart());
m_shaderResourceGroup->SetConstant(m_smhShadowsEndIndex, settings->GetSmhShadowsEnd());
m_shaderResourceGroup->SetConstant(m_smhHighlightsStartIndex, settings->GetSmhHighlightsStart());
m_shaderResourceGroup->SetConstant(m_smhHighlightsEndIndex, settings->GetSmhHighlightsEnd());
m_shaderResourceGroup->SetConstant(m_smhWeightIndex, settings->GetSmhWeight());
m_shaderResourceGroup->SetConstant(m_smhShadowsColorIndex, AZ::Vector4(settings->GetSmhShadowsColor()));
m_shaderResourceGroup->SetConstant(m_smhMidtonesColorIndex, AZ::Vector4(settings->GetSmhMidtonesColor()));
m_shaderResourceGroup->SetConstant(m_smhHighlightsColorIndex, AZ::Vector4(settings->GetSmhHighlightsColor()));
m_shaderResourceGroup->SetConstant(m_channelMixingRedIndex, settings->GetChannelMixingRed());
m_shaderResourceGroup->SetConstant(m_channelMixingGreenIndex, settings->GetChannelMixingGreen());
m_shaderResourceGroup->SetConstant(m_channelMixingBlueIndex, settings->GetChannelMixingBlue());
m_shaderResourceGroup->SetConstant(m_colorFilterSwatchIndex, AZ::Vector4(settings->GetColorFilterSwatch()));
m_shaderResourceGroup->SetConstant(m_splitToneShadowsColorIndex, AZ::Vector4(settings->GetSplitToneShadowsColor()));
m_shaderResourceGroup->SetConstant(m_splitToneHighlightsColorIndex, AZ::Vector4(settings->GetSplitToneHighlightsColor()));
m_shaderResourceGroup->SetConstant(m_smhShadowsColorIndex, AZ::Vector4(settings->GetSmhShadowsColor()));
m_shaderResourceGroup->SetConstant(m_smhMidtonesColorIndex, AZ::Vector4(settings->GetSmhMidtonesColor()));
m_shaderResourceGroup->SetConstant(m_smhHighlightsColorIndex, AZ::Vector4(settings->GetSmhHighlightsColor()));
m_shaderResourceGroup->SetConstant(m_finalAdjustmentWeightIndex, settings->GetFinalAdjustmentWeight());
m_shaderResourceGroup->SetConstant(m_colorGradingPostSaturationIndex, settings->GetColorGradingPostSaturation() * 0.01f + 1.0f);
m_shaderResourceGroup->SetConstant(m_colorGradingHueShiftIndex, settings->GetColorGradingHueShift());
}
}

@ -41,37 +41,42 @@ namespace AZ
void FrameBeginInternal(FramePrepareParams params) override;
bool IsEnabled() const override;
private:
const HDRColorGradingSettings* GetHDRColorGradingSettings() const;
void SetSrgConstants();
virtual void SetSrgConstants();
private:
RHI::ShaderInputNameIndex m_colorAdjustmentWeightIndex = "m_colorAdjustmentWeight";
RHI::ShaderInputNameIndex m_colorGradingExposureIndex = "m_colorGradingExposure";
RHI::ShaderInputNameIndex m_colorGradingContrastIndex = "m_colorGradingContrast";
RHI::ShaderInputNameIndex m_colorGradingHueShiftIndex = "m_colorGradingHueShift";
RHI::ShaderInputNameIndex m_colorGradingPreSaturationIndex = "m_colorGradingPreSaturation";
RHI::ShaderInputNameIndex m_colorFilterIntensityIndex = "m_colorFilterIntensity";
RHI::ShaderInputNameIndex m_colorFilterMultiplyIndex = "m_colorFilterMultiply";
RHI::ShaderInputNameIndex m_colorFilterSwatchIndex = "m_colorFilterSwatch";
RHI::ShaderInputNameIndex m_whiteBalanceWeightIndex = "m_whiteBalanceWeight";
RHI::ShaderInputNameIndex m_whiteBalanceKelvinIndex = "m_whiteBalanceKelvin";
RHI::ShaderInputNameIndex m_whiteBalanceTintIndex = "m_whiteBalanceTint";
RHI::ShaderInputNameIndex m_splitToneBalanceIndex = "m_splitToneBalance";
RHI::ShaderInputNameIndex m_splitToneWeightIndex = "m_splitToneWeight";
RHI::ShaderInputNameIndex m_colorGradingPostSaturationIndex = "m_colorGradingPostSaturation";
RHI::ShaderInputNameIndex m_splitToneShadowsColorIndex = "m_splitToneShadowsColor";
RHI::ShaderInputNameIndex m_splitToneHighlightsColorIndex = "m_splitToneHighlightsColor";
RHI::ShaderInputNameIndex m_smhShadowsStartIndex = "m_smhShadowsStart";
RHI::ShaderInputNameIndex m_smhShadowsEndIndex = "m_smhShadowsEnd";
RHI::ShaderInputNameIndex m_smhHighlightsStartIndex = "m_smhHighlightsStart";
RHI::ShaderInputNameIndex m_smhHighlightsEndIndex = "m_smhHighlightsEnd";
RHI::ShaderInputNameIndex m_smhWeightIndex = "m_smhWeight";
RHI::ShaderInputNameIndex m_smhShadowsColorIndex = "m_smhShadowsColor";
RHI::ShaderInputNameIndex m_smhMidtonesColorIndex = "m_smhMidtonesColor";
RHI::ShaderInputNameIndex m_smhHighlightsColorIndex = "m_smhHighlightsColor";
RHI::ShaderInputNameIndex m_channelMixingRedIndex = "m_channelMixingRed";
RHI::ShaderInputNameIndex m_channelMixingGreenIndex = "m_channelMixingGreen";
RHI::ShaderInputNameIndex m_channelMixingBlueIndex = "m_channelMixingBlue";
RHI::ShaderInputNameIndex m_colorFilterSwatchIndex = "m_colorFilterSwatch";
RHI::ShaderInputNameIndex m_splitToneShadowsColorIndex = "m_splitToneShadowsColor";
RHI::ShaderInputNameIndex m_splitToneHighlightsColorIndex = "m_splitToneHighlightsColor";
RHI::ShaderInputNameIndex m_smhShadowsColorIndex = "m_smhShadowsColor";
RHI::ShaderInputNameIndex m_smhMidtonesColorIndex = "m_smhMidtonesColor";
RHI::ShaderInputNameIndex m_smhHighlightsColorIndex = "m_smhHighlightsColor";
RHI::ShaderInputNameIndex m_finalAdjustmentWeightIndex = "m_finalAdjustmentWeight";
RHI::ShaderInputNameIndex m_colorGradingPostSaturationIndex = "m_colorGradingPostSaturation";
RHI::ShaderInputNameIndex m_colorGradingHueShiftIndex = "m_colorGradingHueShift";
};
} // namespace Render
} // namespace AZ

@ -8,7 +8,6 @@
#include "ProfilingCaptureSystemComponent.h"
#include <Atom/RHI/CpuProfilerImpl.h>
#include <Atom/RHI/RHIUtils.h>
#include <Atom/RHI/RHISystemInterface.h>
#include <AzCore/Statistics/RunningStatistic.h>
@ -39,7 +38,6 @@ namespace AZ
OnCaptureQueryTimestampFinished,
OnCaptureCpuFrameTimeFinished,
OnCaptureQueryPipelineStatisticsFinished,
OnCaptureCpuProfilingStatisticsFinished,
OnCaptureBenchmarkMetadataFinished
);
@ -58,11 +56,6 @@ namespace AZ
Call(FN_OnCaptureQueryPipelineStatisticsFinished, result, info);
}
void OnCaptureCpuProfilingStatisticsFinished(bool result, const AZStd::string& info) override
{
Call(FN_OnCaptureCpuProfilingStatisticsFinished, result, info);
}
void OnCaptureBenchmarkMetadataFinished(bool result, const AZStd::string& info) override
{
Call(FN_OnCaptureBenchmarkMetadataFinished, result, info);
@ -358,7 +351,6 @@ namespace AZ
->Event("CapturePassTimestamp", &ProfilingCaptureRequestBus::Events::CapturePassTimestamp)
->Event("CaptureCpuFrameTime", &ProfilingCaptureRequestBus::Events::CaptureCpuFrameTime)
->Event("CapturePassPipelineStatistics", &ProfilingCaptureRequestBus::Events::CapturePassPipelineStatistics)
->Event("CaptureCpuProfilingStatistics", &ProfilingCaptureRequestBus::Events::CaptureCpuProfilingStatistics)
->Event("CaptureBenchmarkMetadata", &ProfilingCaptureRequestBus::Events::CaptureBenchmarkMetadata)
;
@ -368,7 +360,6 @@ namespace AZ
TimestampSerializer::Reflect(context);
CpuFrameTimeSerializer::Reflect(context);
PipelineStatisticsSerializer::Reflect(context);
RHI::CpuProfilingStatisticsSerializer::Reflect(context);
BenchmarkMetadataSerializer::Reflect(context);
}
@ -382,12 +373,6 @@ namespace AZ
TickBus::Handler::BusDisconnect();
ProfilingCaptureRequestBus::Handler::BusDisconnect();
// Block deactivation until the IO thread has finished serializing the CPU data
if (m_cpuDataSerializationThread.joinable())
{
m_cpuDataSerializationThread.join();
}
}
bool ProfilingCaptureSystemComponent::CapturePassTimestamp(const AZStd::string& outputFilePath)
@ -442,16 +427,7 @@ namespace AZ
bool ProfilingCaptureSystemComponent::CaptureCpuFrameTime(const AZStd::string& outputFilePath)
{
AZ::RHI::RHISystemInterface::Get()->ModifyFrameSchedulerStatisticsFlags(
AZ::RHI::FrameSchedulerStatisticsFlags::GatherCpuTimingStatistics, true
);
bool wasEnabled = RHI::CpuProfiler::Get()->IsProfilerEnabled();
if (!wasEnabled)
{
RHI::CpuProfiler::Get()->SetProfilerEnabled(true);
}
const bool captureStarted = m_cpuFrameTimeStatisticsCapture.StartCapture([outputFilePath, wasEnabled]()
const bool captureStarted = m_cpuFrameTimeStatisticsCapture.StartCapture([outputFilePath]()
{
JsonSerializerSettings serializationSettings;
serializationSettings.m_keepDefaults = true;
@ -472,15 +448,6 @@ namespace AZ
AZ_Warning("ProfilingCaptureSystemComponent", false, captureInfo.c_str());
}
// Disable the profiler again
if (!wasEnabled)
{
RHI::CpuProfiler::Get()->SetProfilerEnabled(false);
}
AZ::RHI::RHISystemInterface::Get()->ModifyFrameSchedulerStatisticsFlags(
AZ::RHI::FrameSchedulerStatisticsFlags::GatherCpuTimingStatistics, false
);
// Notify listeners that the Cpu frame time statistics capture has finished.
ProfilingCaptureNotificationBus::Broadcast(&ProfilingCaptureNotificationBus::Events::OnCaptureCpuFrameTimeFinished,
saveResult.IsSuccess(),
@ -546,116 +513,6 @@ namespace AZ
return captureStarted;
}
bool SerializeCpuProfilingData(const AZStd::ring_buffer<RHI::CpuProfiler::TimeRegionMap>& data, AZStd::string outputFilePath, bool wasEnabled)
{
AZ_TracePrintf("ProfilingCaptureSystemComponent", "Beginning serialization of %zu frames of profiling data\n", data.size());
JsonSerializerSettings serializationSettings;
serializationSettings.m_keepDefaults = true;
RHI::CpuProfilingStatisticsSerializer serializer(data);
const auto saveResult = JsonSerializationUtils::SaveObjectToFile(&serializer,
outputFilePath, (RHI::CpuProfilingStatisticsSerializer*)nullptr, &serializationSettings);
AZStd::string captureInfo = outputFilePath;
if (!saveResult.IsSuccess())
{
captureInfo = AZStd::string::format("Failed to save Cpu Profiling Statistics data to file '%s'. Error: %s",
outputFilePath.c_str(),
saveResult.GetError().c_str());
AZ_Warning("ProfilingCaptureSystemComponent", false, captureInfo.c_str());
}
else
{
AZ_Printf("ProfilingCaptureSystemComponent", "Cpu profiling statistics was saved to file [%s]\n", outputFilePath.c_str());
}
// Disable the profiler again
if (!wasEnabled)
{
RHI::CpuProfiler::Get()->SetProfilerEnabled(false);
}
// Notify listeners that the pass' PipelineStatistics queries capture has finished.
ProfilingCaptureNotificationBus::Broadcast(&ProfilingCaptureNotificationBus::Events::OnCaptureCpuProfilingStatisticsFinished,
saveResult.IsSuccess(),
captureInfo);
return saveResult.IsSuccess();
}
bool ProfilingCaptureSystemComponent::CaptureCpuProfilingStatistics(const AZStd::string& outputFilePath)
{
// Start the cpu profiling
bool wasEnabled = RHI::CpuProfiler::Get()->IsProfilerEnabled();
if (!wasEnabled)
{
RHI::CpuProfiler::Get()->SetProfilerEnabled(true);
}
const bool captureStarted = m_cpuProfilingStatisticsCapture.StartCapture([outputFilePath, wasEnabled]()
{
// Blocking call for a single frame of data, avoid thread overhead
AZStd::ring_buffer<RHI::CpuProfiler::TimeRegionMap> singleFrameData(1);
singleFrameData.push_back(RHI::CpuProfiler::Get()->GetTimeRegionMap());
SerializeCpuProfilingData(singleFrameData, outputFilePath, wasEnabled);
});
// Start the TickBus.
if (captureStarted)
{
TickBus::Handler::BusConnect();
}
return captureStarted;
}
bool ProfilingCaptureSystemComponent::BeginContinuousCpuProfilingCapture()
{
return AZ::RHI::CpuProfiler::Get()->BeginContinuousCapture();
}
bool ProfilingCaptureSystemComponent::EndContinuousCpuProfilingCapture(const AZStd::string& outputFilePath)
{
bool expected = false;
if (m_cpuDataSerializationInProgress.compare_exchange_strong(expected, true))
{
AZStd::ring_buffer<RHI::CpuProfiler::TimeRegionMap> captureResult;
const bool captureEnded = AZ::RHI::CpuProfiler::Get()->EndContinuousCapture(captureResult);
if (!captureEnded)
{
AZ_TracePrintf("ProfilingCaptureSystemComponent", "Could not end the continuous capture, is one in progress?\n");
m_cpuDataSerializationInProgress.store(false);
return false;
}
// cpuProfilingData could be 1GB+ once saved, so use an IO thread to write it to disk.
auto threadIoFunction =
[data = AZStd::move(captureResult), filePath = AZStd::string(outputFilePath), &flag = m_cpuDataSerializationInProgress]()
{
SerializeCpuProfilingData(data, filePath, true);
flag.store(false);
};
// If the thread object already exists (ex. we have already serialized data), join. This will not block since
// m_cpuDataSerializationInProgress was false, meaning the IO thread has already completed execution.
// TODO Use a reusable thread implementation over repeated creation + destruction of threads [ATOM-16214]
if (m_cpuDataSerializationThread.joinable())
{
m_cpuDataSerializationThread.join();
}
auto thread = AZStd::thread(threadIoFunction);
m_cpuDataSerializationThread = AZStd::move(thread);
return true;
}
AZ_TracePrintf(
"ProfilingSystemCaptureComponent",
"Cannot end a continuous capture - another serialization is currently in progress\n");
return false;
}
bool ProfilingCaptureSystemComponent::CaptureBenchmarkMetadata(const AZStd::string& benchmarkName, const AZStd::string& outputFilePath)
{
const bool captureStarted = m_benchmarkMetadataCapture.StartCapture([benchmarkName, outputFilePath]()
@ -734,11 +591,10 @@ namespace AZ
m_timestampCapture.UpdateCapture();
m_cpuFrameTimeStatisticsCapture.UpdateCapture();
m_pipelineStatisticsCapture.UpdateCapture();
m_cpuProfilingStatisticsCapture.UpdateCapture();
m_benchmarkMetadataCapture.UpdateCapture();
// Disconnect from the TickBus if all capture states are set to idle.
if (m_timestampCapture.IsIdle() && m_pipelineStatisticsCapture.IsIdle() && m_cpuProfilingStatisticsCapture.IsIdle() && m_benchmarkMetadataCapture.IsIdle() && m_cpuFrameTimeStatisticsCapture.IsIdle())
if (m_timestampCapture.IsIdle() && m_pipelineStatisticsCapture.IsIdle() && m_benchmarkMetadataCapture.IsIdle() && m_cpuFrameTimeStatisticsCapture.IsIdle())
{
TickBus::Handler::BusDisconnect();
}

@ -70,9 +70,6 @@ namespace AZ
bool CapturePassTimestamp(const AZStd::string& outputFilePath) override;
bool CaptureCpuFrameTime(const AZStd::string& outputFilePath) override;
bool CapturePassPipelineStatistics(const AZStd::string& outputFilePath) override;
bool CaptureCpuProfilingStatistics(const AZStd::string& outputFilePath) override;
bool BeginContinuousCpuProfilingCapture() override;
bool EndContinuousCpuProfilingCapture(const AZStd::string& outputFilePath) override;
bool CaptureBenchmarkMetadata(const AZStd::string& benchmarkName, const AZStd::string& outputFilePath) override;
private:
@ -86,13 +83,7 @@ namespace AZ
DelayedQueryCaptureHelper m_timestampCapture;
DelayedQueryCaptureHelper m_cpuFrameTimeStatisticsCapture;
DelayedQueryCaptureHelper m_pipelineStatisticsCapture;
DelayedQueryCaptureHelper m_cpuProfilingStatisticsCapture;
DelayedQueryCaptureHelper m_benchmarkMetadataCapture;
// Flag passed by reference to the CPU profiling data serialization job, blocks new continuous capture requests when set.
AZStd::atomic_bool m_cpuDataSerializationInProgress = false;
AZStd::thread m_cpuDataSerializationThread;
};
}
}

@ -12,6 +12,7 @@ set(FILES
Include/Atom/Feature/ACES/AcesDisplayMapperFeatureProcessor.h
Include/Atom/Feature/Automation/AtomAutomationBus.h
Include/Atom/Feature/AuxGeom/AuxGeomFeatureProcessor.h
Include/Atom/Feature/ColorGrading/LutResolution.h
Include/Atom/Feature/CoreLights/CoreLightsConstants.h
Include/Atom/Feature/DisplayMapper/AcesOutputTransformPass.h
Include/Atom/Feature/DisplayMapper/AcesOutputTransformLutPass.h
@ -66,6 +67,8 @@ set(FILES
Source/AuxGeom/DynamicPrimitiveProcessor.h
Source/AuxGeom/FixedShapeProcessor.cpp
Source/AuxGeom/FixedShapeProcessor.h
Source/ColorGrading/LutGenerationPass.cpp
Source/ColorGrading/LutGenerationPass.h
Source/CoreLights/CapsuleLightFeatureProcessor.h
Source/CoreLights/CapsuleLightFeatureProcessor.cpp
Source/CoreLights/CascadedShadowmapsPass.h
@ -212,8 +215,6 @@ set(FILES
Source/PostProcessing/BloomCompositePass.cpp
Source/PostProcessing/BloomParentPass.h
Source/PostProcessing/BloomParentPass.cpp
Source/PostProcessing/HDRColorGradingPass.cpp
Source/PostProcessing/HDRColorGradingPass.h
Source/PostProcessing/DepthOfFieldCompositePass.h
Source/PostProcessing/DepthOfFieldCompositePass.cpp
Source/PostProcessing/DepthOfFieldBokehBlurPass.h
@ -234,6 +235,8 @@ set(FILES
Source/PostProcessing/EyeAdaptationPass.h
Source/PostProcessing/FastDepthAwareBlurPasses.cpp
Source/PostProcessing/FastDepthAwareBlurPasses.h
Source/PostProcessing/HDRColorGradingPass.cpp
Source/PostProcessing/HDRColorGradingPass.h
Source/PostProcessing/LookModificationCompositePass.cpp
Source/PostProcessing/LookModificationCompositePass.h
Source/PostProcessing/LookModificationTransformPass.cpp

@ -0,0 +1,89 @@
"""
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
"""
"""
This script is used by the Editor HDR Color Grading component to use a stored lut asset path
and pass it onto a Look Modification Component.
The HDR Color Grading component will be disabled as it is not compatible with the Look
Modification Component.
"""
import azlmbr
import azlmbr.legacy.general as general
LOOK_MODIFICATION_LUT_PROPERTY_PATH = 'Controller|Configuration|Color Grading LUT'
LOOK_MODIFICATION_ENABLE_PROPERTY_PATH = 'Controller|Configuration|Enable look modification'
COLOR_GRADING_COMPONENT_ID = azlmbr.editor.EditorComponentAPIBus(azlmbr.bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["HDR Color Grading"], 0)
LOOK_MODIFICATION_COMPONENT_ID = azlmbr.editor.EditorComponentAPIBus(azlmbr.bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["Look Modification"], 0)
def disable_hdr_color_grading_component(entity_id):
componentOutcome = azlmbr.editor.EditorComponentAPIBus(azlmbr.bus.Broadcast, 'GetComponentOfType', entity_id, COLOR_GRADING_COMPONENT_ID[0])
if(componentOutcome.IsSuccess()):
azlmbr.editor.EditorComponentAPIBus(azlmbr.bus.Broadcast, 'DisableComponents', [componentOutcome.GetValue()])
def add_look_modification_component(entity_id):
componentOutcome = azlmbr.editor.EditorComponentAPIBus(azlmbr.bus.Broadcast, 'AddComponentsOfType', entity_id, LOOK_MODIFICATION_COMPONENT_ID)
return componentOutcome.GetValue()[0]
def get_look_modification_component(entity_id):
componentOutcome = azlmbr.editor.EditorComponentAPIBus(azlmbr.bus.Broadcast, 'GetComponentOfType', entity_id, LOOK_MODIFICATION_COMPONENT_ID[0])
if componentOutcome.IsSuccess():
return componentOutcome.GetValue()
else:
return None
def activate_look_modification_lut(look_modification_component, asset_relative_path):
print(asset_relative_path)
asset_id = azlmbr.asset.AssetCatalogRequestBus(
azlmbr.bus.Broadcast,
'GetAssetIdByPath',
asset_relative_path,
azlmbr.math.Uuid(),
False
)
azlmbr.editor.EditorComponentAPIBus(
azlmbr.bus.Broadcast,
'SetComponentProperty',
look_modification_component,
LOOK_MODIFICATION_LUT_PROPERTY_PATH,
asset_id
)
azlmbr.editor.EditorComponentAPIBus(
azlmbr.bus.Broadcast,
'SetComponentProperty',
look_modification_component,
LOOK_MODIFICATION_ENABLE_PROPERTY_PATH,
True
)
def activate_lut_asset(entity_id, asset_relative_path):
disable_hdr_color_grading_component(entity_id)
look_modification_component = get_look_modification_component(entity_id)
if not look_modification_component:
look_modification_component = add_look_modification_component(entity_id)
general.idle_wait_frames(5)
if look_modification_component:
activate_look_modification_lut(look_modification_component, asset_relative_path)
if __name__ == "__main__":
parser=argparse.ArgumentParser()
parser.add_argument('--entityName', type=str, required=True, help='Entity ID to manage')
parser.add_argument('--assetRelativePath', type=str, required=True, help='Lut asset relative path to activate')
args=parser.parse_args()
# Get the entity id
searchFilter = azlmbr.entity.SearchFilter()
searchFilter.names = [args.entityName]
entityIdList = azlmbr.entity.SearchBus(azlmbr.bus.Broadcast, 'SearchEntities', searchFilter)
for entityId in entityIdList:
activate_lut_asset(entityId, args.assetRelativePath)

@ -0,0 +1,63 @@
# coding:utf-8
#!/usr/bin/python
#
# 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 numpy as np
import logging as _logging
from ColorGrading import get_uv_coord
# ------------------------------------------------------------------------
_MODULENAME = 'ColorGrading.azasset_converter_utils'
_LOGGER = _logging.getLogger(_MODULENAME)
_LOGGER.debug('Initializing: {0}.'.format({_MODULENAME}))
# ------------------------------------------------------------------------
""" Utility functions for generating LUT azassets """
def generate_lut_values(image_spec, image_buffer):
lut_size = image_spec.height
lut_intervals = []
lut_values = []
# First line contains the vertex intervals
dv = 1023.0 / float(lut_size-1)
for i in range(lut_size):
lut_intervals.append(np.uint16(dv * i))
# Texels are in R G B per line with indices increasing first with blue, then green, and then red.
for r in range(lut_size):
for g in range(lut_size):
for b in range(lut_size):
uv = get_uv_coord(lut_size, r, g, b)
px = np.array(image_buffer.getpixel(uv[0], uv[1]), dtype='f')
px = np.clip(px, 0.0, 1.0)
px = np.uint16(px * 4095)
lut_values.append(px)
return lut_intervals, lut_values
# To Do: add some input file validation
# If the input file doesn't exist, you'll get a LUT with res of 0 x 0 and result in a math error
#Resolution is 0 x 0
#writing C:\Depot\o3de-engine\Gems\AtomLyIntegration\CommonFeatures\Tools\ColorGrading\TestData\Nuke\HDR\Nuke_Post_grade_LUT.3dl...
#Traceback (most recent call last):
#File "..\..\Editor\Scripts\ColorGrading\exr_to_3dl_azasset.py", line 103, in <module>
#dv = 1023.0 / float(lutSize)
# ZeroDivisionError: float division by zero
def write_3DL(file_path, lut_size, lut_intervals, lut_values):
lut_file_path = f'{file_path}.3dl'
_LOGGER.info(f"Writing {lut_file_path}...")
lut_file = open(lut_file_path, 'w')
for i in range(lut_size):
lut_file.write(f"{lut_intervals[i]} ")
lut_file.write("\n")
for px in lut_values:
lut_file.write(f"{px[0]} {px[1]} {px[2]}\n")
lut_file.close()

@ -46,48 +46,7 @@ from ColorGrading.from_3dl_to_azasset import write_azasset
from ColorGrading import get_uv_coord
def generate_lut_values(image_spec, image_buffer):
lut_size = image_spec.height
lut_intervals = []
lut_values = []
# First line contains the vertex intervals
dv = 1023.0 / float(lut_size-1)
for i in range(lut_size):
lut_intervals.append(np.uint16(dv * i))
# Texels are in R G B per line with indices increasing first with blue, then green, and then red.
for r in range(lut_size):
for g in range(lut_size):
for b in range(lut_size):
uv = get_uv_coord(lut_size, r, g, b)
px = np.array(image_buffer.getpixel(uv[0], uv[1]), dtype='f')
px = np.clip(px, 0.0, 1.0)
px = np.uint16(px * 4095)
lut_values.append(px)
return lut_intervals, lut_values
# To Do: add some input file validation
# If the input file doesn't exist, you'll get a LUT with res of 0 x 0 and result in a math error
#Resolution is 0 x 0
#writing C:\Depot\o3de-engine\Gems\AtomLyIntegration\CommonFeatures\Tools\ColorGrading\TestData\Nuke\HDR\Nuke_Post_grade_LUT.3dl...
#Traceback (most recent call last):
#File "..\..\Editor\Scripts\ColorGrading\exr_to_3dl_azasset.py", line 103, in <module>
#dv = 1023.0 / float(lutSize)
# ZeroDivisionError: float division by zero
def write_3DL(file_path, lut_size, lut_intervals, lut_values):
lut_file_path = f'{file_path}.3dl'
_LOGGER.info(f"Writing {lut_file_path}...")
lut_file = open(lut_file_path, 'w')
for i in range(lut_size):
lut_file.write(f"{lut_intervals[i]} ")
lut_file.write("\n")
for px in lut_values:
lut_file.write(f"{px[0]} {px[1]} {px[2]}\n")
lut_file.close()
from ColorGrading.azasset_converter_utils import generate_lut_values, write_3DL
###########################################################################
# Main Code Block, runs this script as main (testing)

@ -0,0 +1,81 @@
# coding:utf-8
#!/usr/bin/python
#
# 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
#
#
"""
input: a shaped .tiff representing a LUT (for instance coming out of photoshop)
output: a inverse shaped LUT as .tiff
^ as a .3DL (normalized lut file type)
^ as a .azasset (for o3de engine)
"""
import sys
import os
import argparse
import math
import site
import pathlib
from pathlib import Path
import logging as _logging
import numpy as np
# ------------------------------------------------------------------------
_MODULENAME = 'ColorGrading.tiff_to_3dl_azasset'
_LOGGER = _logging.getLogger(_MODULENAME)
_LOGGER.debug('Initializing: {0}.'.format({_MODULENAME}))
import ColorGrading.initialize
if ColorGrading.initialize.start():
try:
import OpenImageIO as oiio
pass
except ImportError as e:
_LOGGER.error(f"invalid import: {e}")
sys.exit(1)
# ------------------------------------------------------------------------
# ------------------------------------------------------------------------
from ColorGrading.from_3dl_to_azasset import write_azasset
from ColorGrading.azasset_converter_utils import generate_lut_values, write_3DL
###########################################################################
# Main Code Block, runs this script as main (testing)
# -------------------------------------------------------------------------
if __name__ == '__main__':
"""Run this file as main"""
parser=argparse.ArgumentParser()
parser.add_argument('--i', type=str, required=True, help='input file')
parser.add_argument('--o', type=str, required=True, help='output file')
args=parser.parse_args()
# Read input image
image_buffer=oiio.ImageBuf(args.i)
image_spec=image_buffer.spec()
#img = oiio.ImageInput.open(args.i)
#_LOGGER.info(f"Resolution is, x: {img.spec().width} and y: {img.spec().height}")
_LOGGER.info(f"Resolution is, x: {image_buffer.spec().width} and y: {image_buffer.spec().height}")
if image_spec.width != image_spec.height * image_spec.height:
_LOGGER.info(f"invalid input file dimensions. Expect lengthwise LUT with dimension W: s*s X H: s, where s is the size of the LUT")
sys.exit(1)
lut_intervals, lut_values = generate_lut_values(image_spec, image_buffer)
write_3DL(args.o, image_spec.height, lut_intervals, lut_values)
# write_azasset(file_path, lut_intervals, lut_values, azasset_json=AZASSET_LUT)
write_azasset(args.o, lut_intervals, lut_values)
# example from command line
# python % DCCSI_COLORGRADING_SCRIPTS %\lut_helper.py - -i C: \Depot\o3de\Gems\Atom\Feature\Common\Tools\ColorGrading\Resources\LUTs\linear_32_LUT.tiff - -op pre - grading - -shaper Log2 - 48nits - -o C: \Depot\o3de\Gems\Atom\Feature\Common\Tools\ColorGrading\Resources\LUTs\base_Log2-48nits_32_LUT.exr

@ -0,0 +1,16 @@
#
# 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
#
#
set(FILES
Scripts/ColorGrading/__init__.py
Scripts/ColorGrading/initialize.py
Scripts/ColorGrading/azasset_converter_utils.py
Scripts/ColorGrading/from_3dl_to_azasset.py
Scripts/ColorGrading/tiff_to_3dl_azasset.py
Scripts/ColorGrading/activate_lut_asset.py
)

@ -66,9 +66,6 @@ namespace AZ
{
None = 0,
//! Enables gathering of cpu timing statistics.
GatherCpuTimingStatistics = AZ_BIT(0),
//! Enables gathering of transient attachment statistics.
GatherTransientAttachmentStatistics = AZ_BIT(2),

@ -1,87 +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
*
*/
#pragma once
#include <AzCore/Debug/EventTrace.h>
#include <AzCore/Debug/Profiler.h>
#include <AzCore/RTTI/RTTI.h>
#include <AzCore/std/containers/ring_buffer.h>
#include <AzCore/std/containers/unordered_map.h>
#include <AzCore/std/string/string.h>
namespace AZ
{
namespace RHI
{
//! Structure that is used to cache a timed region into the thread's local storage.
struct CachedTimeRegion
{
//! Structure used internally for caching assumed global string pointers (ideally literals) to the marker group/region
//! NOTE: When used in a separate shared library, the library mustn't be unloaded before the CpuProfiler is shutdown.
struct GroupRegionName
{
GroupRegionName() = delete;
GroupRegionName(const char* const group, const char* const region);
const char* m_groupName = nullptr;
const char* m_regionName = nullptr;
struct Hash
{
AZStd::size_t operator()(const GroupRegionName& name) const;
};
bool operator==(const GroupRegionName& other) const;
};
CachedTimeRegion() = default;
CachedTimeRegion(const GroupRegionName& groupRegionName);
CachedTimeRegion(const GroupRegionName& groupRegionName, uint16_t stackDepth, uint64_t startTick, uint64_t endTick);
GroupRegionName m_groupRegionName{nullptr, nullptr};
uint16_t m_stackDepth = 0u;
AZStd::sys_time_t m_startTick = 0;
AZStd::sys_time_t m_endTick = 0;
};
//! Interface class of the CpuProfiler
class CpuProfiler
{
public:
using ThreadTimeRegionMap = AZStd::unordered_map<AZStd::string, AZStd::vector<CachedTimeRegion>>;
using TimeRegionMap = AZStd::unordered_map<AZStd::thread_id, ThreadTimeRegionMap>;
AZ_RTTI(CpuProfiler, "{127C1D0B-BE05-4E18-A8F6-24F3EED2ECA6}");
CpuProfiler() = default;
virtual ~CpuProfiler() = default;
AZ_DISABLE_COPY_MOVE(CpuProfiler);
static CpuProfiler* Get();
//! Get the last frame's TimeRegionMap
virtual const TimeRegionMap& GetTimeRegionMap() const = 0;
//! Begin a continuous capture. Blocks the profiler from being toggled off until EndContinuousCapture is called.
[[nodiscard]] virtual bool BeginContinuousCapture() = 0;
//! Flush the CPU Profiler's saved data into the passed ring buffer .
[[nodiscard]] virtual bool EndContinuousCapture(AZStd::ring_buffer<TimeRegionMap>& flushTarget) = 0;
virtual bool IsContinuousCaptureInProgress() const = 0;
//! Enable/Disable the CpuProfiler
virtual void SetProfilerEnabled(bool enabled) = 0;
virtual bool IsProfilerEnabled() const = 0 ;
};
} // namespace RPI
} // namespace AZ

@ -1,188 +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
*
*/
#pragma once
#include <Atom/RHI/CpuProfiler.h>
#include <Atom/RHI.Reflect/Base.h>
#include <AzCore/Component/TickBus.h>
#include <AzCore/Memory/OSAllocator.h>
#include <AzCore/Name/Name.h>
#include <AzCore/std/containers/map.h>
#include <AzCore/std/containers/unordered_set.h>
#include <AzCore/std/parallel/mutex.h>
#include <AzCore/std/parallel/shared_mutex.h>
#include <AzCore/std/smart_ptr/intrusive_refcount.h>
namespace AZ
{
namespace RHI
{
//! Thread local class to keep track of the thread's cached time regions.
//! Each thread keeps track of its own time regions, which is communicated from the CpuProfilerImpl.
//! The CpuProfilerImpl is able to request the cached time regions from the CpuTimingLocalStorage.
class CpuTimingLocalStorage :
public AZStd::intrusive_refcount<AZStd::atomic_uint>
{
friend class CpuProfilerImpl;
public:
AZ_CLASS_ALLOCATOR(CpuTimingLocalStorage, AZ::OSAllocator, 0);
CpuTimingLocalStorage();
~CpuTimingLocalStorage();
private:
// Maximum stack size
static constexpr uint32_t TimeRegionStackSize = 2048u;
// Adds a region to the stack, gets called each time a region begins
void RegionStackPushBack(CachedTimeRegion& timeRegion);
// Pops a region from the stack, gets called each time a region ends
void RegionStackPopBack();
// Add a new cached time region. If the stack is empty, flush all entries to the cached map
void AddCachedRegion(const CachedTimeRegion& timeRegionCached);
// Tries to flush the map to the passed parameter, only if the thread's mutex is unlocked
void TryFlushCachedMap(CpuProfiler::ThreadTimeRegionMap& cachedRegionMap);
AZStd::thread_id m_executingThreadId;
// Keeps track of the current thread's stack depth
uint32_t m_stackLevel = 0u;
// Cached region map, will be flushed to the system's map when the system requests it
CpuProfiler::ThreadTimeRegionMap m_cachedTimeRegionMap;
// Use fixed vectors to avoid re-allocating new elements
// Keeps track of the regions that added and removed using the macro
AZStd::fixed_vector<CachedTimeRegion, TimeRegionStackSize> m_timeRegionStack;
// Keeps track of regions that completed (i.e regions that was pushed and popped from the stack)
// Intermediate storage point for the CachedTimeRegions, when the stack is empty, all entries will be
// copied to the map.
AZStd::fixed_vector<CachedTimeRegion, TimeRegionStackSize> m_cachedTimeRegions;
AZStd::mutex m_cachedTimeRegionMutex;
// Dirty flag which is set when the CpuProfiler's enabled state is set from false to true
AZStd::atomic_bool m_clearContainers = false;
// When the thread is terminated, it will flag itself for deletion
AZStd::atomic_bool m_deleteFlag = false;
// Keep track of the regions that have hit the size limit so we don't have to lock to check
AZStd::map<AZStd::string, bool> m_hitSizeLimitMap;
};
//! CpuProfiler will keep track of the registered threads, and
//! forwards the request to profile a region to the appropriate thread. The user is able to request all
//! cached regions, which are stored on a per thread frequency.
class CpuProfilerImpl final
: public AZ::Debug::Profiler
, public CpuProfiler
, public SystemTickBus::Handler
{
friend class CpuTimingLocalStorage;
public:
AZ_TYPE_INFO(CpuProfilerImpl, "{10E9D394-FC83-4B45-B2B8-807C6BF07BF0}");
AZ_CLASS_ALLOCATOR(CpuProfilerImpl, AZ::OSAllocator, 0);
CpuProfilerImpl() = default;
~CpuProfilerImpl() = default;
//! Registers the CpuProfilerImpl instance to the interface
void Init();
//! Unregisters the CpuProfilerImpl instance from the interface
void Shutdown();
// SystemTickBus::Handler overrides
// When fired, the profiler collects all profiling data from registered threads and updates
// m_timeRegionMap so that the next frame has up-to-date profiling data.
void OnSystemTick() final override;
//! AZ::Debug::Profiler overrides...
void BeginRegion(const AZ::Debug::Budget* budget, const char* eventName) final override;
void EndRegion(const AZ::Debug::Budget* budget) final override;
//! CpuProfiler overrides...
const TimeRegionMap& GetTimeRegionMap() const final override;
bool BeginContinuousCapture() final override;
bool EndContinuousCapture(AZStd::ring_buffer<TimeRegionMap>& flushTarget) final override;
bool IsContinuousCaptureInProgress() const final override;
void SetProfilerEnabled(bool enabled) final override;
bool IsProfilerEnabled() const final override;
private:
static constexpr AZStd::size_t MaxFramesToSave = 2 * 60 * 120; // 2 minutes of 120fps
static constexpr AZStd::size_t MaxRegionStringPoolSize = 16384; // Max amount of unique strings to save in the pool before throwing warnings.
// Lazily create and register the local thread data
void RegisterThreadStorage();
// ThreadId -> ThreadTimeRegionMap
// On the start of each frame, this map will be updated with the last frame's profiling data.
TimeRegionMap m_timeRegionMap;
// Set of registered threads when created
AZStd::vector<RHI::Ptr<CpuTimingLocalStorage>, AZ::OSStdAllocator> m_registeredThreads;
AZStd::mutex m_threadRegisterMutex;
// Thread local storage, gets lazily allocated when a thread is created
static thread_local CpuTimingLocalStorage* ms_threadLocalStorage;
// Enable/Disables the threads from profiling
AZStd::atomic_bool m_enabled = false;
// This lock will only be contested when the CpuProfiler's Shutdown() method has been called
AZStd::shared_mutex m_shutdownMutex;
bool m_initialized = false;
AZStd::mutex m_continuousCaptureEndingMutex;
AZStd::atomic_bool m_continuousCaptureInProgress;
// Stores multiple frames of profiling data, size is controlled by MaxFramesToSave. Flushed when EndContinuousCapture is called.
// Ring buffer so that we can have fast append of new data + removal of old profiling data with good cache locality.
AZStd::ring_buffer<TimeRegionMap> m_continuousCaptureData;
};
// Intermediate class to serialize Cpu TimedRegion data.
class CpuProfilingStatisticsSerializer
{
public:
class CpuProfilingStatisticsSerializerEntry
{
public:
AZ_TYPE_INFO(CpuProfilingStatisticsSerializer::CpuProfilingStatisticsSerializerEntry, "{26B78F65-EB96-46E2-BE7E-A1233880B225}");
static void Reflect(AZ::ReflectContext* context);
CpuProfilingStatisticsSerializerEntry() = default;
CpuProfilingStatisticsSerializerEntry(const RHI::CachedTimeRegion& cachedTimeRegion, AZStd::thread_id threadId);
Name m_groupName;
Name m_regionName;
uint16_t m_stackDepth;
AZStd::sys_time_t m_startTick;
AZStd::sys_time_t m_endTick;
size_t m_threadId;
};
AZ_TYPE_INFO(CpuProfilingStatisticsSerializer, "{D5B02946-0D27-474F-9A44-364C2706DD41}");
static void Reflect(AZ::ReflectContext* context);
CpuProfilingStatisticsSerializer() = default;
CpuProfilingStatisticsSerializer(const AZStd::ring_buffer<RHI::CpuProfiler::TimeRegionMap>& continuousData);
AZStd::vector<CpuProfilingStatisticsSerializerEntry> m_cpuProfilingStatisticsSerializerEntries;
};
}; // namespace RHI
}; // namespace AZ

@ -8,7 +8,6 @@
#pragma once
#include <Atom/RHI/CpuProfilerImpl.h>
#include <Atom/RHI/Device.h>
#include <Atom/RHI/DrawListTagRegistry.h>
#include <Atom/RHI/FrameScheduler.h>
@ -66,8 +65,6 @@ namespace AZ
RHI::Ptr<RHI::PipelineStateCache> m_pipelineStateCache;
RHI::FrameScheduler m_frameScheduler;
RHI::FrameSchedulerCompileRequest m_compileRequest;
RHI::CpuProfilerImpl m_cpuProfiler;
};
} // namespace RPI
} // namespace AZ

@ -1,448 +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
*
*/
#include <Atom/RHI/CpuProfilerImpl.h>
#include <AzCore/Interface/Interface.h>
#include <AzCore/std/smart_ptr/shared_ptr.h>
#include <AzCore/Debug/Timer.h>
#include <AzCore/Statistics/StatisticalProfilerProxy.h>
#include <Atom/RHI/RHIUtils.h>
namespace AZ
{
namespace RHI
{
thread_local CpuTimingLocalStorage* CpuProfilerImpl::ms_threadLocalStorage = nullptr;
// --- CpuProfiler ---
CpuProfiler* CpuProfiler::Get()
{
return Interface<CpuProfiler>::Get();
}
// --- CachedTimeRegion ---
CachedTimeRegion::CachedTimeRegion(const GroupRegionName& groupRegionName)
{
m_groupRegionName = groupRegionName;
}
CachedTimeRegion::CachedTimeRegion(const GroupRegionName& groupRegionName, uint16_t stackDepth, uint64_t startTick, uint64_t endTick)
{
m_groupRegionName = groupRegionName;
m_stackDepth = stackDepth;
m_startTick = startTick;
m_endTick = endTick;
}
// --- GroupRegionName ---
CachedTimeRegion::GroupRegionName::GroupRegionName(const char* const group, const char* const region) :
m_groupName(group),
m_regionName(region)
{
}
AZStd::size_t CachedTimeRegion::GroupRegionName::Hash::operator()(const CachedTimeRegion::GroupRegionName& name) const
{
AZStd::size_t seed = 0;
AZStd::hash_combine(seed, name.m_groupName);
AZStd::hash_combine(seed, name.m_regionName);
return seed;
}
bool CachedTimeRegion::GroupRegionName::operator==(const GroupRegionName& other) const
{
return (m_groupName == other.m_groupName) && (m_regionName == other.m_regionName);
}
// --- CpuProfilerImpl ---
void CpuProfilerImpl::Init()
{
Interface<AZ::Debug::Profiler>::Register(this);
Interface<CpuProfiler>::Register(this);
m_initialized = true;
SystemTickBus::Handler::BusConnect();
m_continuousCaptureData.set_capacity(10);
if (auto statsProfiler = AZ::Interface<AZ::Statistics::StatisticalProfilerProxy>::Get(); statsProfiler)
{
statsProfiler->ActivateProfiler(AZ_CRC_CE("RHI"), true);
}
}
void CpuProfilerImpl::Shutdown()
{
if (!m_initialized)
{
return;
}
// When this call is made, no more thread profiling calls can be performed anymore
Interface<CpuProfiler>::Unregister(this);
Interface<AZ::Debug::Profiler>::Unregister(this);
// Wait for the remaining threads that might still be processing its profiling calls
AZStd::unique_lock<AZStd::shared_mutex> shutdownLock(m_shutdownMutex);
m_enabled = false;
// Cleanup all TLS
m_registeredThreads.clear();
m_timeRegionMap.clear();
m_initialized = false;
m_continuousCaptureInProgress.store(false);
m_continuousCaptureData.clear();
SystemTickBus::Handler::BusDisconnect();
}
void CpuProfilerImpl::BeginRegion(const AZ::Debug::Budget* budget, const char* eventName)
{
// Try to lock here, the shutdownMutex will only be contested when the CpuProfiler is shutting down.
if (m_shutdownMutex.try_lock_shared())
{
if (m_enabled)
{
// Lazy initialization, creates an instance of the Thread local data if it's not created, and registers it
RegisterThreadStorage();
// Push it to the stack
CachedTimeRegion timeRegion({budget->Name(), eventName});
ms_threadLocalStorage->RegionStackPushBack(timeRegion);
}
m_shutdownMutex.unlock_shared();
}
}
void CpuProfilerImpl::EndRegion([[maybe_unused]] const AZ::Debug::Budget* budget)
{
// Try to lock here, the shutdownMutex will only be contested when the CpuProfiler is shutting down.
if (m_shutdownMutex.try_lock_shared())
{
// guard against enabling mid-marker
if (m_enabled && ms_threadLocalStorage != nullptr)
{
ms_threadLocalStorage->RegionStackPopBack();
}
m_shutdownMutex.unlock_shared();
}
}
const CpuProfiler::TimeRegionMap& CpuProfilerImpl::GetTimeRegionMap() const
{
return m_timeRegionMap;
}
bool CpuProfilerImpl::BeginContinuousCapture()
{
bool expected = false;
if (m_continuousCaptureInProgress.compare_exchange_strong(expected, true))
{
m_enabled = true;
AZ_TracePrintf("Profiler", "Continuous capture started\n");
return true;
}
AZ_TracePrintf("Profiler", "Attempting to start a continuous capture while one already in progress");
return false;
}
bool CpuProfilerImpl::EndContinuousCapture(AZStd::ring_buffer<TimeRegionMap>& flushTarget)
{
if (!m_continuousCaptureInProgress.load())
{
AZ_TracePrintf("Profiler", "Attempting to end a continuous capture while one not in progress");
return false;
}
if (m_continuousCaptureEndingMutex.try_lock())
{
m_enabled = false;
flushTarget = AZStd::move(m_continuousCaptureData);
m_continuousCaptureData.clear();
AZ_TracePrintf("Profiler", "Continuous capture ended\n");
m_continuousCaptureInProgress.store(false);
m_continuousCaptureEndingMutex.unlock();
return true;
}
return false;
}
bool CpuProfilerImpl::IsContinuousCaptureInProgress() const
{
return m_continuousCaptureInProgress.load();
}
void CpuProfilerImpl::SetProfilerEnabled(bool enabled)
{
AZStd::unique_lock<AZStd::mutex> lock(m_threadRegisterMutex);
// Early out if the state is already the same or a continuous capture is in progress
if (m_enabled == enabled || m_continuousCaptureInProgress.load())
{
return;
}
// Set the dirty flag in all the TLS to clear the caches
if (enabled)
{
// Iterate through all the threads, and set the clearing flag
for (auto& threadLocal : m_registeredThreads)
{
threadLocal->m_clearContainers = true;
}
m_enabled = true;
}
else
{
m_enabled = false;
}
}
bool CpuProfilerImpl::IsProfilerEnabled() const
{
return m_enabled;
}
void CpuProfilerImpl::OnSystemTick()
{
if (!m_enabled)
{
return;
}
if (m_continuousCaptureInProgress.load() && m_continuousCaptureEndingMutex.try_lock())
{
if (m_continuousCaptureData.full() && m_continuousCaptureData.size() != MaxFramesToSave)
{
const AZStd::size_t size = m_continuousCaptureData.size();
m_continuousCaptureData.set_capacity(AZStd::min(MaxFramesToSave, size + size / 2));
}
m_continuousCaptureData.push_back(AZStd::move(m_timeRegionMap));
m_timeRegionMap.clear();
m_continuousCaptureEndingMutex.unlock();
}
AZStd::unique_lock<AZStd::mutex> lock(m_threadRegisterMutex);
// Iterate through all the threads, and collect the thread's cached time regions
TimeRegionMap newMap;
for (auto& threadLocal : m_registeredThreads)
{
ThreadTimeRegionMap& threadMapEntry = newMap[threadLocal->m_executingThreadId];
threadLocal->TryFlushCachedMap(threadMapEntry);
}
// Clear all TLS that flagged themselves to be deleted, meaning that the thread is already terminated
AZStd::remove_if(m_registeredThreads.begin(), m_registeredThreads.end(), [](const RHI::Ptr<CpuTimingLocalStorage>& thread)
{
return thread->m_deleteFlag.load();
});
// Update our saved time regions to the last frame's collected data
m_timeRegionMap = AZStd::move(newMap);
}
void CpuProfilerImpl::RegisterThreadStorage()
{
AZStd::unique_lock<AZStd::mutex> lock(m_threadRegisterMutex);
if (!ms_threadLocalStorage)
{
ms_threadLocalStorage = aznew CpuTimingLocalStorage();
m_registeredThreads.emplace_back(ms_threadLocalStorage);
}
}
// --- CpuTimingLocalStorage ---
CpuTimingLocalStorage::CpuTimingLocalStorage()
{
m_executingThreadId = AZStd::this_thread::get_id();
}
CpuTimingLocalStorage::~CpuTimingLocalStorage()
{
m_deleteFlag = true;
}
void CpuTimingLocalStorage::RegionStackPushBack(CachedTimeRegion& timeRegion)
{
// If it was (re)enabled, clear the lists first
if (m_clearContainers)
{
m_clearContainers = false;
m_stackLevel = 0;
m_cachedTimeRegionMap.clear();
m_timeRegionStack.clear();
m_cachedTimeRegions.clear();
}
timeRegion.m_stackDepth = static_cast<uint16_t>(m_stackLevel);
AZ_Assert(m_timeRegionStack.size() < TimeRegionStackSize, "Adding too many time regions to the stack. Increase the size of TimeRegionStackSize.");
m_timeRegionStack.push_back(timeRegion);
// Increment the stack
m_stackLevel++;
// Set the starting time at the end, to avoid recording the minor overhead
m_timeRegionStack.back().m_startTick = AZStd::GetTimeNowTicks();
}
void CpuTimingLocalStorage::RegionStackPopBack()
{
// Early out when the stack is empty, this might happen when the profiler was enabled while the thread encountered profiling markers
if (m_timeRegionStack.empty())
{
return;
}
// Get the end timestamp here, to avoid the minor overhead
const AZStd::sys_time_t endRegionTime = AZStd::GetTimeNowTicks();
AZ_Assert(!m_timeRegionStack.empty(), "Trying to pop an element in the stack, but it's empty.");
CachedTimeRegion back = m_timeRegionStack.back();
m_timeRegionStack.pop_back();
// Set the ending time
back.m_endTick = endRegionTime;
// Decrement the stack
m_stackLevel--;
// Add an entry to the cached region
AddCachedRegion(back);
}
// Gets called when region ends and all data is set
void CpuTimingLocalStorage::AddCachedRegion(const CachedTimeRegion& timeRegionCached)
{
if (m_hitSizeLimitMap[timeRegionCached.m_groupRegionName.m_regionName])
{
return;
}
// Add an entry to the cached region
m_cachedTimeRegions.push_back(timeRegionCached);
// If the stack is empty, add it to the local cache map. Only gets called when the stack is empty
// NOTE: this is where the largest overhead will be, but due to it only being called when the stack is empty
// (i.e when the root region ended), this overhead won't affect any time regions.
// The exception being for functions that are being profiled and create/spawn threads that are also profiled. Unfortunately, in this
// case, the overhead of the profiled threads will be added to the main thread.
if (m_timeRegionStack.empty())
{
AZStd::unique_lock<AZStd::mutex> lock(m_cachedTimeRegionMutex);
// Add the cached regions to the map
for (auto& cachedTimeRegion : m_cachedTimeRegions)
{
const AZStd::string regionName = cachedTimeRegion.m_groupRegionName.m_regionName;
AZStd::vector<CachedTimeRegion>& regionVec = m_cachedTimeRegionMap[regionName];
regionVec.push_back(cachedTimeRegion);
if (regionVec.size() >= TimeRegionStackSize)
{
m_hitSizeLimitMap.insert_or_assign(AZStd::move(regionName), true);
}
}
// Clear the cached regions
m_cachedTimeRegions.clear();
}
}
void CpuTimingLocalStorage::TryFlushCachedMap(CpuProfiler::ThreadTimeRegionMap& cachedTimeRegionMap)
{
// Try to lock, if it's already in use (the cached regions in the array are being copied to the map)
// it'll show up in the next iteration when the user requests it.
if (m_cachedTimeRegionMutex.try_lock())
{
// Only flush cached time regions if there are entries available
if (!m_cachedTimeRegionMap.empty())
{
cachedTimeRegionMap = AZStd::move(m_cachedTimeRegionMap);
m_cachedTimeRegionMap.clear();
m_hitSizeLimitMap.clear();
}
m_cachedTimeRegionMutex.unlock();
}
}
// --- CpuProfilingStatisticsSerializer ---
CpuProfilingStatisticsSerializer::CpuProfilingStatisticsSerializer(const AZStd::ring_buffer<RHI::CpuProfiler::TimeRegionMap>& continuousData)
{
// Create serializable entries
for (const auto& timeRegionMap : continuousData)
{
for (const auto& [threadId, regionMap] : timeRegionMap)
{
for (const auto& [regionName, regionVec] : regionMap)
{
for (const auto& region : regionVec)
{
m_cpuProfilingStatisticsSerializerEntries.emplace_back(region, threadId);
}
}
}
}
}
void CpuProfilingStatisticsSerializer::Reflect(AZ::ReflectContext* context)
{
if (auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<CpuProfilingStatisticsSerializer>()
->Version(1)
->Field("cpuProfilingStatisticsSerializerEntries", &CpuProfilingStatisticsSerializer::m_cpuProfilingStatisticsSerializerEntries)
;
}
CpuProfilingStatisticsSerializerEntry::Reflect(context);
}
// --- CpuProfilingStatisticsSerializerEntry ---
CpuProfilingStatisticsSerializer::CpuProfilingStatisticsSerializerEntry::CpuProfilingStatisticsSerializerEntry(
const RHI::CachedTimeRegion& cachedTimeRegion, AZStd::thread_id threadId)
{
m_groupName = cachedTimeRegion.m_groupRegionName.m_groupName;
m_regionName = cachedTimeRegion.m_groupRegionName.m_regionName;
m_stackDepth = cachedTimeRegion.m_stackDepth;
m_startTick = cachedTimeRegion.m_startTick;
m_endTick = cachedTimeRegion.m_endTick;
m_threadId = AZStd::hash<AZStd::thread_id>{}(threadId);
}
void CpuProfilingStatisticsSerializer::CpuProfilingStatisticsSerializerEntry::Reflect(AZ::ReflectContext* context)
{
if (auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<CpuProfilingStatisticsSerializerEntry>()
->Version(1)
->Field("groupName", &CpuProfilingStatisticsSerializerEntry::m_groupName)
->Field("regionName", &CpuProfilingStatisticsSerializerEntry::m_regionName)
->Field("stackDepth", &CpuProfilingStatisticsSerializerEntry::m_stackDepth)
->Field("startTick", &CpuProfilingStatisticsSerializerEntry::m_startTick)
->Field("endTick", &CpuProfilingStatisticsSerializerEntry::m_endTick)
->Field("threadId", &CpuProfilingStatisticsSerializerEntry::m_threadId)
;
}
}
} // namespace RHI
} // namespace AZ

@ -86,6 +86,8 @@ namespace AZ
if (auto statsProfiler = AZ::Interface<AZ::Statistics::StatisticalProfilerProxy>::Get(); statsProfiler)
{
statsProfiler->ActivateProfiler(rhiMetricsId, true);
auto& rhiMetrics = statsProfiler->GetProfiler(rhiMetricsId);
rhiMetrics.GetStatsManager().AddStatistic(frameTimeMetricId, frameTimeMetricName, /*units=*/"clocks", /*failIfExist=*/false);
}
@ -602,14 +604,11 @@ namespace AZ
double FrameScheduler::GetCpuFrameTime() const
{
if (CheckBitsAny(m_compileRequest.m_statisticsFlags, FrameSchedulerStatisticsFlags::GatherCpuTimingStatistics))
if (auto statsProfiler = AZ::Interface<AZ::Statistics::StatisticalProfilerProxy>::Get(); statsProfiler)
{
if (auto statsProfiler = AZ::Interface<AZ::Statistics::StatisticalProfilerProxy>::Get(); statsProfiler)
{
auto& rhiMetrics = statsProfiler->GetProfiler(rhiMetricsId);
const auto* frameTimeStat = rhiMetrics.GetStatistic(frameTimeMetricId);
return (frameTimeStat->GetMostRecentSample() * 1000) / aznumeric_cast<double>(AZStd::GetTimeTicksPerSecond());
}
auto& rhiMetrics = statsProfiler->GetProfiler(rhiMetricsId);
const auto* frameTimeStat = rhiMetrics.GetStatistic(frameTimeMetricId);
return (frameTimeStat->GetMostRecentSample() * 1000) / aznumeric_cast<double>(AZStd::GetTimeTicksPerSecond());
}
return 0;
}

@ -39,8 +39,6 @@ namespace AZ
void RHISystem::Init()
{
m_cpuProfiler.Init();
Ptr<RHI::PlatformLimitsDescriptor> platformLimitsDescriptor = m_device->GetDescriptor().m_platformLimitsDescriptor;
RHI::FrameSchedulerDescriptor frameSchedulerDescriptor;
@ -187,8 +185,6 @@ namespace AZ
AZ_Assert(m_device->use_count()==1, "The ref count for Device is %i but it should be 1 here to ensure all the resources are released", m_device->use_count());
m_device = nullptr;
}
m_cpuProfiler.Shutdown();
}
void RHISystem::FrameUpdate(FrameGraphCallback frameGraphCallback)

@ -197,8 +197,5 @@ set(FILES
Include/Atom/RHI/interval_map.h
Include/Atom/RHI/ImageProperty.h
Include/Atom/RHI/BufferProperty.h
Include/Atom/RHI/CpuProfiler.h
Include/Atom/RHI/CpuProfilerImpl.h
Source/RHI/CpuProfilerImpl.cpp
Include/Atom/RHI/TagRegistry.h
)

@ -102,6 +102,8 @@ namespace AtomToolsFramework
void BeginCursorCapture() override;
void EndCursorCapture() override;
bool IsMouseOver() const override;
void SetOverrideCursor(AzToolsFramework::ViewportInteraction::CursorStyleOverride cursorStyleOverride) override;
void ClearOverrideCursor() override;
// AzFramework::WindowRequestBus::Handler overrides ...
void SetWindowTitle(const AZStd::string& title) override;

@ -63,8 +63,11 @@ namespace AtomToolsFramework
void PreviewRendererSystemComponent::OnCatalogLoaded([[maybe_unused]] const char* catalogFile)
{
AZ::TickBus::QueueFunction([this](){
m_previewRenderer.reset(aznew AtomToolsFramework::PreviewRenderer(
"PreviewRendererSystemComponent Preview Scene", "PreviewRendererSystemComponent Preview Pipeline"));
if (!m_previewRenderer)
{
m_previewRenderer.reset(aznew AtomToolsFramework::PreviewRenderer(
"PreviewRendererSystemComponent Preview Scene", "PreviewRendererSystemComponent Preview Pipeline"));
}
});
}

@ -375,6 +375,16 @@ namespace AtomToolsFramework
m_inputChannelMapper->SetCursorCaptureEnabled(false);
}
void RenderViewportWidget::SetOverrideCursor(AzToolsFramework::ViewportInteraction::CursorStyleOverride cursorStyleOverride)
{
m_inputChannelMapper->SetOverrideCursor(cursorStyleOverride);
}
void RenderViewportWidget::ClearOverrideCursor()
{
m_inputChannelMapper->ClearOverrideCursor();
}
void RenderViewportWidget::SetWindowTitle(const AZStd::string& title)
{
setWindowTitle(QString::fromUtf8(title.c_str()));

@ -9,7 +9,6 @@
#include <AzCore/Serialization/SerializeContext.h>
#include <Atom/RHI/RHISystemInterface.h>
#include <Atom/RHI/CpuProfiler.h>
#include <Atom/RPI.Public/Pass/ParentPass.h>
#include <Atom/RPI.Public/Pass/PassSystemInterface.h>
@ -66,12 +65,6 @@ namespace MaterialEditor
AZ_Error("PerformanceMonitorComponent", false, "Failed to find root pass.");
}
AZ::RHI::RHISystemInterface::Get()->ModifyFrameSchedulerStatisticsFlags(
AZ::RHI::FrameSchedulerStatisticsFlags::GatherCpuTimingStatistics,
enabled);
AZ::RHI::CpuProfiler::Get()->SetProfilerEnabled(enabled);
if (enabled)
{
ResetStats();

@ -1,231 +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
*
*/
#pragma once
#include <AzCore/Component/TickBus.h>
#include <AzCore/IO/Path/Path.h>
#include <AzCore/Math/Random.h>
#include <Atom/RHI/CpuProfiler.h>
namespace AZ
{
namespace Render
{
//! Stores all the data associated with a row in the table.
struct TableRow
{
template <typename T>
struct TableRowCompareFunctor
{
TableRowCompareFunctor(T memberPointer, bool isAscending) : m_memberPointer(memberPointer), m_ascending(isAscending){};
bool operator()(const TableRow* lhs, const TableRow* rhs)
{
return m_ascending ? lhs->*m_memberPointer < rhs->*m_memberPointer : lhs->*m_memberPointer > rhs->*m_memberPointer;
}
T m_memberPointer;
bool m_ascending;
};
// Update running statistics with new region data
void RecordRegion(const AZ::RHI::CachedTimeRegion& region, size_t threadId);
void ResetPerFrameStatistics();
// Get a string of all threads that this region executed in during the last frame
AZStd::string GetExecutingThreadsLabel() const;
AZStd::string m_groupName;
AZStd::string m_regionName;
// --- Per frame statistics ---
u64 m_invocationsLastFrame = 0;
// NOTE: set over unordered_set so the threads can be shown in increasing order in tooltip.
AZStd::set<size_t> m_executingThreads;
AZStd::sys_time_t m_lastFrameTotalTicks = 0;
// Maximum execution time of a region in the last frame.
AZStd::sys_time_t m_maxTicks = 0;
// --- Aggregate statistics ---
u64 m_invocationsTotal = 0;
// Running average of Mean Time Per Call
AZStd::sys_time_t m_runningAverageTicks = 0;
};
//! ImGui widget for examining Atom CPU Profiling instrumentation.
//! Offers both a statistical view (with sorting and searching capability) and a visualizer
//! similar to RAD and other profiling tools.
class ImGuiCpuProfiler
: SystemTickBus::Handler
{
// Region Name -> statistical view row data
using RegionRowMap = AZStd::map<AZStd::string, TableRow>;
// Group Name -> RegionRowMap
using GroupRegionMap = AZStd::map<AZStd::string, RegionRowMap>;
using TimeRegion = AZ::RHI::CachedTimeRegion;
using GroupRegionName = AZ::RHI::CachedTimeRegion::GroupRegionName;
public:
struct CpuTimingEntry
{
const AZStd::string& m_name;
double m_executeDuration;
};
ImGuiCpuProfiler() = default;
~ImGuiCpuProfiler() = default;
//! Draws the overall CPU profiling window, defaults to the statistical view
void Draw(bool& keepDrawing);
private:
static constexpr float RowHeight = 35.0;
static constexpr int DefaultFramesToCollect = 50;
static constexpr float MediumFrameTimeLimit = 16.6; // 60 fps
static constexpr float HighFrameTimeLimit = 33.3; // 30 fps
//! Draws the statistical view of the CPU profiling data.
void DrawStatisticsView();
//! Callback invoked when the "Load File" button is pressed in the file picker.
void LoadFile();
//! Draws the file picker window.
void DrawFilePicker();
//! Draws the CPU profiling visualizer.
void DrawVisualizer();
// Draw the shared header between the two windows.
void DrawCommonHeader();
// Draw the region statistics table in the order specified by the pointers in m_tableData.
void DrawTable();
// Sort the table by a given column, rearranges the pointers in m_tableData.
void SortTable(ImGuiTableSortSpecs* sortSpecs);
// gather the latest timing statistics
void CacheCpuTimingStatistics();
// Get the profiling data from the last frame, only called when the profiler is not paused.
void CollectFrameData();
// Cull old data from internal storage, only called when profiler is not paused.
void CullFrameData();
// Draws a single block onto the timeline into the specified row
void DrawBlock(const TimeRegion& block, u64 targetRow);
// Draw horizontal lines between threads in the timeline
void DrawThreadSeparator(u64 threadBoundary, u64 maxDepth);
// Draw the "Thread XXXXX" label onto the viewport
void DrawThreadLabel(u64 baseRow, size_t threadId);
// Draw the vertical lines separating frames in the timeline
void DrawFrameBoundaries();
// Draw the ruler with frame time labels
void DrawRuler();
// Draw the frame time histogram
void DrawFrameTimeHistogram();
// Converts raw ticks to a pixel value suitable to give to ImDrawList, handles window scrolling
float ConvertTickToPixelSpace(AZStd::sys_time_t tick, AZStd::sys_time_t leftBound, AZStd::sys_time_t rightBound) const;
AZStd::sys_time_t GetViewportTickWidth() const;
// Gets the color for a block using the GroupRegionName as a key into the cache.
// Generates a random ImU32 if the block does not yet have a color.
ImU32 GetBlockColor(const TimeRegion& block);
// System tick bus overrides
virtual void OnSystemTick() override;
// --- Visualizer Members ---
int m_framesToCollect = DefaultFramesToCollect;
// Tally of the number of saved profiling events so far
u64 m_savedRegionCount = 0;
// Viewport tick bounds, these are used to convert tick space -> screen space and cull so we only draw onscreen objects
AZStd::sys_time_t m_viewportStartTick;
AZStd::sys_time_t m_viewportEndTick;
// Map to store each thread's TimeRegions, individual vectors are sorted by start tick
// note: we use size_t as a proxy for thread_id because native_thread_id_type differs differs from
// platform to platform, which causes problems when deserializing saved captures.
AZStd::unordered_map<size_t, AZStd::vector<TimeRegion>> m_savedData;
// Region color cache
AZStd::unordered_map<GroupRegionName, ImVec4, RHI::CachedTimeRegion::GroupRegionName::Hash> m_regionColorMap;
// Tracks the frame boundaries
AZStd::vector<AZStd::sys_time_t> m_frameEndTicks = { INT64_MIN };
// Filter for highlighting regions on the visualizer
ImGuiTextFilter m_visualizerHighlightFilter;
// --- Tabular view members ---
// ImGui filter used to filter TimedRegions.
ImGuiTextFilter m_timedRegionFilter;
// Saves statistical view data organized by group name -> region name -> row data
GroupRegionMap m_groupRegionMap;
// Saves pointers to objects in m_groupRegionMap, order reflects table ordering.
// Non-owning, will be cleared when m_groupRegionMap is cleared.
AZStd::vector<TableRow*> m_tableData;
// Pause cpu profiling. The profiler will show the statistics of the last frame before pause.
bool m_paused = false;
// Export the profiling data from a single frame to a local file.
bool m_captureToFile = false;
// Toggle between the normal statistical view and the visual profiling view.
bool m_enableVisualizer = false;
// Last captured CPU timing statistics
AZStd::vector<CpuTimingEntry> m_cpuTimingStatisticsWhenPause;
AZStd::sys_time_t m_frameToFrameTime{};
AZStd::string m_lastCapturedFilePath;
bool m_showFilePicker = false;
// Cached file paths to previous traces on disk, sorted with the most recent trace at the front.
AZStd::vector<IO::Path> m_cachedCapturePaths;
// Index into the file picker, used to determine which file to load when "Load File" is pressed.
int m_currentFileIndex = 0;
// --- Loading capture state ---
AZStd::unordered_set<AZStd::string> m_deserializedStringPool;
AZStd::unordered_set<RHI::CachedTimeRegion::GroupRegionName, RHI::CachedTimeRegion::GroupRegionName::Hash> m_deserializedGroupRegionNamePool;
};
} // namespace Render
} // namespace AZ
#include "ImGuiCpuProfiler.inl"

File diff suppressed because it is too large Load Diff

@ -9,8 +9,6 @@
set(FILES
Include/Atom/Utils/DdsFile.h
Include/Atom/Utils/ImageComparison.h
Include/Atom/Utils/ImGuiCpuProfiler.h
Include/Atom/Utils/ImGuiCpuProfiler.inl
Include/Atom/Utils/ImGuiCullingDebug.h
Include/Atom/Utils/ImGuiCullingDebug.inl
Include/Atom/Utils/ImGuiGpuProfiler.h

@ -84,10 +84,6 @@ namespace AtomImGuiTools
{
m_imguiGpuProfiler.Draw(m_showGpuProfiler, AZ::RPI::PassSystemInterface::Get()->GetRootPass().get());
}
if (m_showCpuProfiler)
{
m_imguiCpuProfiler.Draw(m_showCpuProfiler);
}
if (m_showTransientAttachmentProfiler)
{
auto* transientStats = AZ::RHI::RHISystemInterface::Get()->GetTransientAttachmentStatistics();
@ -108,12 +104,6 @@ namespace AtomImGuiTools
{
ImGui::MenuItem("Pass Viewer", "", &m_showPassTree);
ImGui::MenuItem("Gpu Profiler", "", &m_showGpuProfiler);
if (ImGui::MenuItem("Cpu Profiler", "", &m_showCpuProfiler))
{
AZ::RHI::RHISystemInterface::Get()->ModifyFrameSchedulerStatisticsFlags(
AZ::RHI::FrameSchedulerStatisticsFlags::GatherCpuTimingStatistics, m_showCpuProfiler);
AZ::RHI::CpuProfiler::Get()->SetProfilerEnabled(m_showCpuProfiler);
}
if (ImGui::MenuItem("Transient Attachment Profiler", "", &m_showTransientAttachmentProfiler))
{
AZ::RHI::RHISystemInterface::Get()->ModifyFrameSchedulerStatisticsFlags(

@ -15,7 +15,6 @@
#if defined(IMGUI_ENABLED)
#include <ImGuiBus.h>
#include <imgui/imgui.h>
#include <Atom/Utils/ImGuiCpuProfiler.h>
#include <Atom/Utils/ImGuiGpuProfiler.h>
#include <Atom/Utils/ImGuiPassTree.h>
#include <Atom/Utils/ImGuiShaderMetrics.h>
@ -63,9 +62,6 @@ namespace AtomImGuiTools
AZ::Render::ImGuiGpuProfiler m_imguiGpuProfiler;
bool m_showGpuProfiler = false;
AZ::Render::ImGuiCpuProfiler m_imguiCpuProfiler;
bool m_showCpuProfiler = false;
AZ::Render::ImGuiTransientAttachmentProfiler m_imguiTransientAttachmentProfiler;
bool m_showTransientAttachmentProfiler = false;

@ -216,11 +216,17 @@ namespace AZ
using namespace LyIntegration;
ThumbnailerRequestsBus::Broadcast(
&ThumbnailerRequests::RegisterThumbnailProvider, MAKE_TCACHE(SharedThumbnailCache),
ThumbnailContext::DefaultContext);
&ThumbnailerRequests::RegisterThumbnailProvider, MAKE_TCACHE(SharedThumbnailCache), ThumbnailContext::DefaultContext);
if (!m_thumbnailRenderer)
{
m_thumbnailRenderer = AZStd::make_unique<AZ::LyIntegration::SharedThumbnailRenderer>();
}
m_renderer = AZStd::make_unique<AZ::LyIntegration::SharedThumbnailRenderer>();
m_previewerFactory = AZStd::make_unique<LyIntegration::SharedPreviewerFactory>();
if (!m_previewerFactory)
{
m_previewerFactory = AZStd::make_unique<LyIntegration::SharedPreviewerFactory>();
}
}
void EditorCommonFeaturesSystemComponent::TeardownThumbnails()
@ -232,7 +238,7 @@ namespace AZ
&ThumbnailerRequests::UnregisterThumbnailProvider, SharedThumbnailCache::ProviderName,
ThumbnailContext::DefaultContext);
m_renderer.reset();
m_thumbnailRenderer.reset();
m_previewerFactory.reset();
}
} // namespace Render

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

Loading…
Cancel
Save