Merge branch 'o3de:development' into development

This commit is contained in:
Pinfel
2021-08-18 21:37:51 -04:00
committed by GitHub
52 changed files with 1230 additions and 1420 deletions
@@ -7,10 +7,14 @@ SPDX-License-Identifier: Apache-2.0 OR MIT
# fmt:off
class Tests():
create_new_entity = ("Entity: 'CreateNewEntity' passed", "Entity: 'CreateNewEntity' failed")
create_prefab = ("Prefab: 'CreatePrefab' passed", "Prefab: 'CreatePrefab' failed")
instantiate_prefab = ("Prefab: 'InstantiatePrefab' passed", "Prefab: 'InstantiatePrefab' failed")
new_prefab_position = ("Prefab: new prefab's position is at the expected position", "Prefab: new prefab's position is *not* at the expected position")
create_new_entity = ("'CreateNewEntity' passed", "'CreateNewEntity' failed")
create_prefab = ("'CreatePrefab' passed", "'CreatePrefab' failed")
instantiate_prefab = ("'InstantiatePrefab' passed", "'InstantiatePrefab' failed")
has_one_child = ("instantiated prefab contains only one child as expected", "instantiated prefab does *not* contain only one child as expected")
instantiated_prefab_position = ("instantiated prefab's position is at the expected position", "instantiated prefab's position is *not* at the expected position")
delete_prefab = ("'DeleteEntitiesAndAllDescendantsInInstance' passed", "'DeleteEntitiesAndAllDescendantsInInstance' failed")
instantiated_prefab_removed = ("instantiated prefab's container entity has been removed", "instantiated prefab's container entity has *not* been removed")
instantiated_child_removed = ("instantiated prefab's child entity has been removed", "instantiated prefab's child entity has *not* been removed")
# fmt:on
def PrefabLevel_BasicWorkflow():
@@ -18,6 +22,7 @@ def PrefabLevel_BasicWorkflow():
This test will help verify if the following functions related to Prefab work as expected:
- CreatePrefab
- InstantiatePrefab
- DeleteEntitiesAndAllDescendantsInInstance
"""
import os
@@ -35,31 +40,68 @@ def PrefabLevel_BasicWorkflow():
from azlmbr.math import Vector3
import azlmbr.legacy.general as general
EXPECTED_NEW_PREFAB_POSITION = Vector3(10.00, 20.0, 30.0)
NEW_PREFAB_NAME = "new_prefab"
NEW_PREFAB_FILE_NAME = NEW_PREFAB_NAME + ".prefab"
NEW_PREFAB_FILE_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), NEW_PREFAB_FILE_NAME)
INSTANTIATED_PREFAB_POSITION = Vector3(10.00, 20.0, 30.0)
INSTANTIATED_PREFAB_NAME = "instantiated_prefab"
INSTANTIATED_CHILD_ENTITY_NAME = "child_1"
TEST_LEVEL_FOLDER = "Prefab"
TEST_LEVEL_NAME = "Base"
def find_entity_by_name(entity_name):
searchFilter = entity.SearchFilter()
searchFilter.names = [entity_name]
entityIds = entity.SearchBus(bus.Broadcast, 'SearchEntities', searchFilter)
if entityIds and entityIds[0].IsValid():
return entityIds[0]
return None
def print_error_if_failed(prefab_operation_result):
if not prefab_operation_result.IsSuccess():
Report.info(f'Error message: {prefab_operation_result.GetError()}')
# Open the test level
helper.init_idle()
helper.open_level("Prefab", "Base")
helper.open_level(TEST_LEVEL_FOLDER, TEST_LEVEL_NAME)
# Create a new Entity at the root level
new_entity_id = editor.ToolsApplicationRequestBus(bus.Broadcast, 'CreateNewEntity', EntityId())
Report.result(Tests.create_new_entity, new_entity_id.IsValid())
# Checks for prefab creation passed or not
new_prefab_file_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'new_prefab.prefab')
create_prefab_result = prefab.PrefabPublicRequestBus(bus.Broadcast, 'CreatePrefabInMemory', [new_entity_id], new_prefab_file_path)
Report.result(Tests.create_prefab, create_prefab_result)
create_prefab_result = prefab.PrefabPublicRequestBus(bus.Broadcast, 'CreatePrefabInMemory', [new_entity_id], NEW_PREFAB_FILE_PATH)
Report.result(Tests.create_prefab, create_prefab_result.IsSuccess())
print_error_if_failed(create_prefab_result)
# Checks for prefab instantiation passed or not
container_entity_id = prefab.PrefabPublicRequestBus(bus.Broadcast, 'InstantiatePrefab', new_prefab_file_path, EntityId(), EXPECTED_NEW_PREFAB_POSITION)
Report.result(Tests.instantiate_prefab, container_entity_id.IsValid())
instantiate_prefab_result = prefab.PrefabPublicRequestBus(bus.Broadcast, 'InstantiatePrefab', NEW_PREFAB_FILE_PATH, EntityId(), INSTANTIATED_PREFAB_POSITION)
Report.result(Tests.instantiate_prefab, instantiate_prefab_result.IsSuccess() and instantiate_prefab_result.GetValue().IsValid())
print_error_if_failed(instantiate_prefab_result)
container_entity_id = instantiate_prefab_result.GetValue()
editor.EditorEntityAPIBus(bus.Event, 'SetName', container_entity_id, INSTANTIATED_PREFAB_NAME)
children_entity_ids = editor.EditorEntityInfoRequestBus(bus.Event, 'GetChildren', container_entity_id)
Report.result(Tests.has_one_child, len(children_entity_ids) is 1)
child_entity_id = children_entity_ids[0]
editor.EditorEntityAPIBus(bus.Event, 'SetName', child_entity_id, INSTANTIATED_CHILD_ENTITY_NAME)
# Checks if the new prefab is at the correct position and if it fails, it will provide the expected postion and the actual postion of the entity in the Editor log
new_prefab_position = azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldTranslation", container_entity_id)
is_at_position = new_prefab_position.IsClose(EXPECTED_NEW_PREFAB_POSITION)
Report.result(Tests.new_prefab_position, is_at_position)
actual_prefab_position = azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldTranslation", container_entity_id)
is_at_position = actual_prefab_position.IsClose(INSTANTIATED_PREFAB_POSITION)
Report.result(Tests.instantiated_prefab_position, is_at_position)
if not is_at_position:
Report.info(f'Expected position: {EXPECTED_NEW_PREFAB_POSITION.ToString()}, actual position: {new_prefab_position.ToString()}')
Report.info(f'Expected position: {INSTANTIATED_PREFAB_POSITION.ToString()}, actual position: {actual_prefab_position.ToString()}')
# Checks for prefab deletion passed or not
delete_prefab_result = prefab.PrefabPublicRequestBus(bus.Broadcast, 'DeleteEntitiesAndAllDescendantsInInstance', [container_entity_id])
Report.result(Tests.delete_prefab, delete_prefab_result.IsSuccess())
print_error_if_failed(delete_prefab_result)
Report.result(Tests.instantiated_prefab_removed, find_entity_by_name(INSTANTIATED_PREFAB_NAME) is None)
Report.result(Tests.instantiated_child_removed, find_entity_by_name(INSTANTIATED_CHILD_ENTITY_NAME) is None)
if __name__ == "__main__":
from editor_python_test_tools.utils import Report
@@ -77,7 +77,7 @@ namespace UnitTest
class ViewportManipulatorControllerFixture : public AllocatorsTestFixture
{
public:
static const AzFramework::ViewportId TestViewportId = AzFramework::ViewportId(0);
static const AzFramework::ViewportId TestViewportId;
void SetUp() override
{
@@ -108,6 +108,8 @@ namespace UnitTest
AZStd::unique_ptr<AzToolsFramework::QtEventToAzInputMapper> m_inputChannelMapper;
};
const AzFramework::ViewportId ViewportManipulatorControllerFixture::TestViewportId = AzFramework::ViewportId(0);
TEST_F(ViewportManipulatorControllerFixture, An_event_is_not_propagated_to_the_viewport_when_a_manipulator_handles_it_first)
{
// forward input events to our controller list
@@ -646,16 +646,27 @@ void SandboxIntegrationManager::PopulateEditorGlobalContextMenu(QMenu* menu, con
QAction* action = nullptr;
action = menu->addAction(QObject::tr("Create entity"));
QObject::connect(action, &QAction::triggered, action, [this] { ContextMenu_NewEntity(); });
if (selected.size() == 1)
// when nothing is selected, entity is created at root level
if (selected.size() == 0)
{
action = menu->addAction(QObject::tr("Create child entity"));
QObject::connect(action, &QAction::triggered, action, [selected]
{
EBUS_EVENT(AzToolsFramework::EditorRequests::Bus, CreateNewEntityAsChild, selected.front());
});
action = menu->addAction(QObject::tr("Create entity"));
QObject::connect(
action, &QAction::triggered, action,
[this]
{
ContextMenu_NewEntity();
});
}
// when a single entity is selected, entity is created as its child
else if (selected.size() == 1)
{
action = menu->addAction(QObject::tr("Create entity"));
QObject::connect(
action, &QAction::triggered, action,
[selected]
{
EBUS_EVENT(AzToolsFramework::EditorRequests::Bus, CreateNewEntityAsChild, selected.front());
});
}
bool prefabSystemEnabled = false;
@@ -61,7 +61,7 @@ namespace AzToolsFramework
m_prefabUndoCache.Destroy();
}
PrefabOperationResult PrefabPublicHandler::CreatePrefabInMemory(const AZStd::vector<AZ::EntityId>& entityIds, AZ::IO::PathView filePath)
PrefabOperationResult PrefabPublicHandler::CreatePrefabInMemory(const EntityIdList& entityIds, AZ::IO::PathView filePath)
{
EntityList inputEntityList, topLevelEntities;
AZ::EntityId commonRootEntityId;
@@ -264,7 +264,7 @@ namespace AzToolsFramework
return AZ::Success();
}
PrefabOperationResult PrefabPublicHandler::CreatePrefabInDisk(const AZStd::vector<AZ::EntityId>& entityIds, AZ::IO::PathView filePath)
PrefabOperationResult PrefabPublicHandler::CreatePrefabInDisk(const EntityIdList& entityIds, AZ::IO::PathView filePath)
{
auto result = CreatePrefabInMemory(entityIds, filePath);
if (result.IsSuccess())
@@ -43,9 +43,9 @@ namespace AzToolsFramework
// PrefabPublicInterface...
PrefabOperationResult CreatePrefabInDisk(
const AZStd::vector<AZ::EntityId>& entityIds, AZ::IO::PathView filePath) override;
const EntityIdList& entityIds, AZ::IO::PathView filePath) override;
PrefabOperationResult CreatePrefabInMemory(
const AZStd::vector<AZ::EntityId>& entityIds, AZ::IO::PathView filePath) override;
const EntityIdList& entityIds, AZ::IO::PathView filePath) override;
InstantiatePrefabResult InstantiatePrefab(AZStd::string_view filePath, AZ::EntityId parent, const AZ::Vector3& position) override;
PrefabOperationResult SavePrefab(AZ::IO::Path filePath) override;
PrefabEntityResult CreateEntity(AZ::EntityId parentId, const AZ::Vector3& position) override;
@@ -47,7 +47,7 @@ namespace AzToolsFramework
* @return An outcome object; on failure, it comes with an error message detailing the cause of the error.
*/
virtual PrefabOperationResult CreatePrefabInDisk(
const AZStd::vector<AZ::EntityId>& entityIds, AZ::IO::PathView filePath) = 0;
const EntityIdList& entityIds, AZ::IO::PathView filePath) = 0;
/**
* Create a prefab out of the entities provided, at the path provided, and keep it in memory.
@@ -57,7 +57,7 @@ namespace AzToolsFramework
* @return An outcome object; on failure, it comes with an error message detailing the cause of the error.
*/
virtual PrefabOperationResult CreatePrefabInMemory(
const AZStd::vector<AZ::EntityId>& entityIds, AZ::IO::PathView filePath) = 0;
const EntityIdList& entityIds, AZ::IO::PathView filePath) = 0;
/**
* Instantiate a prefab from a prefab file.
@@ -11,13 +11,20 @@
#include <AzCore/Component/EntityId.h>
#include <AzCore/EBus/EBus.h>
#include <AzCore/Math/Vector3.h>
#include <AzCore/Outcome/Outcome.h>
#include <AzCore/std/containers/vector.h>
#include <AzCore/std/string/string.h>
#include <AzCore/std/string/string_view.h>
namespace AzToolsFramework
{
using EntityIdList = AZStd::vector<AZ::EntityId>;
namespace Prefab
{
using PrefabOperationResult = AZ::Outcome<void, AZStd::string>;
using InstantiatePrefabResult = AZ::Outcome<AZ::EntityId, AZStd::string>;
/**
* The primary purpose of this bus is to facilitate writing automated tests for prefabs.
* It calls PrefabPublicInterface internally to talk to the prefab system.
@@ -40,14 +47,25 @@ namespace AzToolsFramework
/**
* Create a prefab out of the entities provided, at the path provided, and keep it in memory.
* Automatically detects descendants of entities, and discerns between entities and child instances.
* Return whether the creation succeeded or not.
*/
virtual bool CreatePrefabInMemory(
const AZStd::vector<AZ::EntityId>& entityIds, AZStd::string_view filePath) = 0;
virtual PrefabOperationResult CreatePrefabInMemory(
const EntityIdList& entityIds, AZStd::string_view filePath) = 0;
/**
* Instantiate a prefab from a prefab file.
* Return the container entity id of the prefab instantiated if instantiation succeeded.
*/
virtual AZ::EntityId InstantiatePrefab(AZStd::string_view filePath, AZ::EntityId parent, const AZ::Vector3& position) = 0;
virtual InstantiatePrefabResult InstantiatePrefab(
AZStd::string_view filePath, AZ::EntityId parent, const AZ::Vector3& position) = 0;
/**
* Deletes all entities and their descendants from the owning instance. Bails if the entities don't
* all belong to the same instance.
* Return whether the deletion succeeded or not.
*/
virtual PrefabOperationResult DeleteEntitiesAndAllDescendantsInInstance(const EntityIdList& entityIds) = 0;
};
using PrefabPublicRequestBus = AZ::EBus<PrefabPublicRequests>;
@@ -25,6 +25,7 @@ namespace AzToolsFramework
->Attribute(AZ::Script::Attributes::Module, "prefab")
->Event("CreatePrefabInMemory", &PrefabPublicRequests::CreatePrefabInMemory)
->Event("InstantiatePrefab", &PrefabPublicRequests::InstantiatePrefab)
->Event("DeleteEntitiesAndAllDescendantsInInstance", &PrefabPublicRequests::DeleteEntitiesAndAllDescendantsInInstance)
;
}
}
@@ -44,36 +45,20 @@ namespace AzToolsFramework
m_prefabPublicInterface = nullptr;
}
bool PrefabPublicRequestHandler::CreatePrefabInMemory(const AZStd::vector<AZ::EntityId>& entityIds, AZStd::string_view filePath)
PrefabOperationResult PrefabPublicRequestHandler::CreatePrefabInMemory(const EntityIdList& entityIds, AZStd::string_view filePath)
{
auto createPrefabOutcome = m_prefabPublicInterface->CreatePrefabInMemory(entityIds, filePath);
if (!createPrefabOutcome.IsSuccess())
{
AZ_Error("CreatePrefabInMemory", false,
"Failed to create Prefab on file path '%.*s'. Error message: %s.",
AZ_STRING_ARG(filePath),
createPrefabOutcome.GetError().c_str());
return false;
}
return true;
return m_prefabPublicInterface->CreatePrefabInMemory(entityIds, filePath);
}
AZ::EntityId PrefabPublicRequestHandler::InstantiatePrefab(AZStd::string_view filePath, AZ::EntityId parent, const AZ::Vector3& position)
InstantiatePrefabResult PrefabPublicRequestHandler::InstantiatePrefab(AZStd::string_view filePath, AZ::EntityId parent, const AZ::Vector3& position)
{
auto instantiatePrefabOutcome = m_prefabPublicInterface->InstantiatePrefab(filePath, parent, position);
if (!instantiatePrefabOutcome.IsSuccess())
{
AZ_Error("InstantiatePrefab", false,
"Failed to instantiate Prefab on file path '%.*s'. Error message: %s.",
AZ_STRING_ARG(filePath),
instantiatePrefabOutcome.GetError().c_str());
return AZ::EntityId();
}
return instantiatePrefabOutcome.GetValue();
return m_prefabPublicInterface->InstantiatePrefab(filePath, parent, position);
}
PrefabOperationResult PrefabPublicRequestHandler::DeleteEntitiesAndAllDescendantsInInstance(const EntityIdList& entityIds)
{
return m_prefabPublicInterface->DeleteEntitiesAndAllDescendantsInInstance(entityIds);
}
} // namespace Prefab
} // namespace AzToolsFramework
@@ -31,8 +31,9 @@ namespace AzToolsFramework
void Connect();
void Disconnect();
bool CreatePrefabInMemory(const AZStd::vector<AZ::EntityId>& entityIds, AZStd::string_view filePath) override;
AZ::EntityId InstantiatePrefab(AZStd::string_view filePath, AZ::EntityId parent, const AZ::Vector3& position) override;
PrefabOperationResult CreatePrefabInMemory(const EntityIdList& entityIds, AZStd::string_view filePath) override;
InstantiatePrefabResult InstantiatePrefab(AZStd::string_view filePath, AZ::EntityId parent, const AZ::Vector3& position) override;
PrefabOperationResult DeleteEntitiesAndAllDescendantsInInstance(const EntityIdList& entityIds) override;
private:
PrefabPublicInterface* m_prefabPublicInterface = nullptr;
@@ -11,6 +11,7 @@
#include <AzCore/IO/FileIO.h>
#include <AzCore/IO/SystemFile.h>
#include <AzCore/StringFunc/StringFunc.h>
#include <AzCore/Component/TransformBus.h>
#include <AzFramework/API/ApplicationAPI.h>
#include <AzFramework/Asset/AssetSystemBus.h>
@@ -363,12 +364,25 @@ namespace AzToolsFramework
if (hasUserSelectedValidSourceFile)
{
// Get position (center of viewport). If no viewport is available, (0,0,0) will be used.
AZ::Vector3 viewportCenterPosition = AZ::Vector3::CreateZero();
EditorRequestBus::BroadcastResult(viewportCenterPosition, &EditorRequestBus::Events::GetWorldPositionAtViewportCenter);
AZ::EntityId parentId;
AZ::Vector3 position = AZ::Vector3::CreateZero();
EntityIdList selectedEntities;
ToolsApplicationRequestBus::BroadcastResult(selectedEntities, &ToolsApplicationRequests::GetSelectedEntities);
// if one entity is selected, instantiate prefab as its child and place it at same position as parent
if (selectedEntities.size() == 1)
{
parentId = selectedEntities.front();
AZ::TransformBus::EventResult(position, parentId, &AZ::TransformInterface::GetWorldTranslation);
}
// otherwise instantiate it at root level and center of viewport
else
{
EditorRequestBus::BroadcastResult(position, &EditorRequestBus::Events::GetWorldPositionAtViewportCenter);
}
// Instantiating from context menu always puts the instance at the root level
auto createPrefabOutcome = s_prefabPublicInterface->InstantiatePrefab(prefabFilePath, AZ::EntityId(), viewportCenterPosition);
auto createPrefabOutcome = s_prefabPublicInterface->InstantiatePrefab(prefabFilePath, parentId, position);
if (!createPrefabOutcome.IsSuccess())
{
-1
View File
@@ -15,6 +15,5 @@ add_subdirectory(AzTest)
add_subdirectory(AzToolsFramework)
add_subdirectory(AzManipulatorTestFramework)
add_subdirectory(AzNetworking)
add_subdirectory(Crcfix)
add_subdirectory(GFxFramework)
add_subdirectory(GridMate)
-32
View File
@@ -1,32 +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
#
#
if (NOT PAL_TRAIT_BUILD_HOST_TOOLS)
return()
endif()
include(Platform/${PAL_PLATFORM_NAME}/PAL_${PAL_PLATFORM_NAME_LOWERCASE}.cmake)
if (NOT PAL_TRAIT_BUILD_CRCFIX)
return()
endif()
ly_add_target(
NAME Crcfix EXECUTABLE
NAMESPACE AZ
FILES_CMAKE
crcfix_files.cmake
BUILD_DEPENDENCIES
PRIVATE
AZ::AzCore
)
ly_add_source_properties(
SOURCES crcfix.cpp
PROPERTY COMPILE_DEFINITIONS
VALUES _CRT_SECURE_NO_WARNINGS
)
@@ -1,9 +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
#
#
set(PAL_TRAIT_BUILD_CRCFIX FALSE)
@@ -1,9 +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
#
#
set(PAL_TRAIT_BUILD_CRCFIX FALSE)
@@ -1,9 +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
#
#
set(PAL_TRAIT_BUILD_CRCFIX TRUE)
-538
View File
@@ -1,538 +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 <AzCore/PlatformIncl.h>
#include <AzCore/Math/Crc.h>
#include <AzCore/std/string/string.h>
#include <AzCore/std/containers/vector.h>
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/Memory/AllocationRecords.h>
#include <AzCore/std/chrono/chrono.h>
#include <AzCore/std/string/conversions.h>
#include <AzCore/Utils/Utils.h>
#include <stdio.h>
#include <stdlib.h>
#include <io.h>
#include <sys/stat.h>
int g_totalFixTimeMs = 0;
int g_longestFixTimeMs = 0;
class Filename
{
wchar_t fullpath[MAX_PATH];
wchar_t drive[_MAX_DRIVE];
wchar_t dir[_MAX_DIR];
wchar_t fname[_MAX_FNAME];
wchar_t ext[_MAX_EXT];
public:
Filename()
{
fullpath[0] = drive[0] = dir[0] = fname[0] = ext[0] = 0;
}
Filename(const AZStd::wstring& filename)
{
_wsplitpath(filename.c_str(), drive, dir, fname, ext);
wcscpy(fullpath, filename.c_str());
}
void SetExt(const wchar_t* pExt) { wcscpy(ext, pExt); _wmakepath(fullpath, drive, dir, fname, pExt); }
const wchar_t* GetFullPath() const { return fullpath; }
bool Exists() const { return _waccess(fullpath, 0) == 0; }
bool IsReadOnly() const { return _waccess(fullpath, 6) == -1; }
bool SetReadOnly() const { return _wchmod(fullpath, _S_IREAD) == 0; }
bool SetWritable() const { return _wchmod(fullpath, _S_IREAD | _S_IWRITE) == 0; }
bool Delete() const { return _wremove(fullpath) == 0; }
bool Rename(const wchar_t* fn2) const{ return MoveFileEx(fullpath, fn2, MOVEFILE_COPY_ALLOWED|MOVEFILE_REPLACE_EXISTING) == 0; }
bool Copy(const wchar_t* dest) const { return ::CopyFile(fullpath, dest, FALSE) == TRUE; }
};
class CRCfix
{
int lastchar;
int linenum;
public:
void SkipToEOL(FILE* infile);
char* GetToken(FILE* infile, FILE* outfile);
void GetPreviousCRC(char* token, FILE* infile);
int Fix(Filename srce);
};
void FixFiles(const AZStd::wstring& dir, const AZStd::wstring& files, FILETIME* pLastRun, bool verbose, int& nFound, int& nProcessed, int& nFixed, int& nFailed)
{
CRCfix fixer;
WIN32_FIND_DATA wfd;
HANDLE hFind;
hFind = FindFirstFile((dir + files).c_str(), &wfd);
if (hFind != INVALID_HANDLE_VALUE)
{
do
{
if ((wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0)
{
if (verbose)
{
AZ_TracePrintf("CrcFix", "\tProcessing %ls ...", wfd.cFileName);
}
nFound++;
int n = 0;
if ((wfd.dwFileAttributes & FILE_ATTRIBUTE_READONLY) == 0 && (!pLastRun || CompareFileTime(pLastRun, &wfd.ftLastWriteTime) <= 0))
{
n = fixer.Fix(Filename(dir + L"\\" + wfd.cFileName));
nProcessed++;
}
if (n < 0)
{
nFailed++;
if (verbose)
{
AZ_TracePrintf("CrcFix", "Failed\n");
}
}
else
{
if (verbose)
{
AZ_TracePrintf("CrcFix", n > 0 ? "Done\n" : (wfd.dwFileAttributes & FILE_ATTRIBUTE_READONLY) != 0 ? "ReadOnly\n" : "Unchanged\n");
}
nFixed += n;
}
}
} while (FindNextFile(hFind, &wfd));
}
FindClose(hFind);
}
void FixDirectories(const AZStd::wstring& dirs, const AZStd::wstring& files, FILETIME* pLastRun, bool verbose, int& nFound, int& nProcessed, int& nFixed, int& nFailed)
{
if (verbose)
{
AZ_TracePrintf("CrcFix", "Processing %ls ...\n", dirs.c_str());
}
// do files
FixFiles(dirs, files, pLastRun, verbose, nFound, nProcessed, nFixed, nFailed);
// do folders
WIN32_FIND_DATA wfd;
HANDLE hFind;
hFind = FindFirstFile((dirs + L"\\*").c_str(), &wfd);
if (hFind != INVALID_HANDLE_VALUE)
{
do
{
if ((wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == FILE_ATTRIBUTE_DIRECTORY)
{
if (wfd.cFileName[0] == '.')
{
continue;
}
FixDirectories(AZStd::wstring(dirs + L"\\" + wfd.cFileName), files, pLastRun, verbose, nFound, nProcessed, nFixed, nFailed);
}
} while (FindNextFile(hFind, &wfd));
}
FindClose(hFind);
}
int main(int argc, char* argv[])
{
AZStd::chrono::system_clock::time_point startTime = AZStd::chrono::system_clock::now();
AZ::SystemAllocator::Descriptor desc;
//desc.m_stackRecordLevels = 15;
AZ::AllocatorInstance<AZ::SystemAllocator>::Create(desc);
//if (AZ::AllocatorInstance<AZ::SystemAllocator>::Get().GetRecords()) {
// AZ::AllocatorInstance<AZ::SystemAllocator>::Get().GetRecords()->SetMode(AZ::Debug::AllocationRecords::RECORD_FULL);
//}
{
if (argc < 2)
{
AZ_TracePrintf("CrcFix", "Usage:\n crcfix [-v(erbose)] [-log:logfile] {path[\\*][\\*.*]}\n");
AZ_TracePrintf("CrcFix", "\n Ex:\n crcfix -v -log:timestamp.log src\\*\\*.cpp src\\*\\*.h ..\\scripts\\*.*\n\n");
}
char root[MAX_PATH];
AZ::Utils::GetExecutableDirectory(root, MAX_PATH);
AZStd::vector<AZStd::wstring> entries;
AZStd::wstring logfilename;
FILETIME lastRun;
FILETIME* pLastRun = NULL;
bool verbose = false;
for (int iArg = 1; iArg < argc; ++iArg)
{
const char* pArg = argv[iArg];
if (!pArg)
{
continue;
}
AZStd::wstring pArgW;
AZStd::to_wstring(pArgW, pArg);
if (_strnicmp(pArg, "-log:", 5) == 0)
{
logfilename.assign(pArgW.begin() + 5, pArgW.end());
HANDLE hFile = CreateFile(logfilename.data(), 0, 0, NULL, OPEN_EXISTING, 0, NULL);
if (hFile != INVALID_HANDLE_VALUE)
{
pLastRun = &lastRun;
GetFileTime(hFile, NULL, NULL, pLastRun);
CloseHandle(hFile);
}
}
else if (_stricmp(pArg, "-v") == 0)
{
verbose = true;
}
else
{
entries.emplace_back(AZStd::move(pArgW));
}
}
// for each entry from the command line...
int nFound = 0;
int nProcessed = 0;
int nFixed = 0;
int nFailed = 0;
for (AZStd::vector<AZStd::wstring>::const_iterator iEntry = entries.begin(); iEntry != entries.end(); ++iEntry)
{
AZStd::wstring entry = (iEntry->at(0) == L'\\' || iEntry->find(L":") != iEntry->npos) ? *iEntry : AZStd::wstring(root) + L"\\" + *iEntry;
AZStd::wstring::size_type split = entry.find(L"*\\");
bool doSubdirs = split != entry.npos;
if (doSubdirs)
{
FixDirectories(entry.substr(0, split), entry.substr(split + 1), pLastRun, verbose, nFound, nProcessed, nFixed, nFailed);
}
else
{
split = entry.rfind(L"\\");
if (split == entry.npos)
{
split = 0;
}
FixFiles(entry.substr(0, split), entry.substr(split), pLastRun, verbose, nFound, nProcessed, nFixed, nFailed);
}
}
// update timestamp
if (!logfilename.empty())
{
HANDLE hFile = CreateFile(logfilename.data(), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL);
GetSystemTimeAsFileTime(&lastRun);
SetFileTime(hFile, NULL, NULL, &lastRun);
char log[1024];
DWORD oCount;
sprintf(log, "Batches processed: %zu\n\tFiles found: %d\n\tFiles processed: %d\n\tFiles fixed: %d\n\tFiles failed: %d\n", entries.size(), nFound, nProcessed, nFixed, nFailed);
WriteFile(hFile, log, static_cast<DWORD>(strlen(log)), &oCount, NULL);
AZStd::chrono::system_clock::time_point endTime = AZStd::chrono::system_clock::now();
sprintf(log, "Total running time: %.2f secs.\n\tTotal processing time: %.2f secs.\n\tLongest processing time: %.2f secs.\n", (float)AZStd::chrono::milliseconds(endTime - startTime).count() / 1000.f, (float)g_totalFixTimeMs / 1000.f, (float)g_longestFixTimeMs / 1000.f);
WriteFile(hFile, log, static_cast<DWORD>(strlen(log)), &oCount, NULL);
CloseHandle(hFile);
}
}
AZ::AllocatorInstance<AZ::SystemAllocator>::Destroy();
return 0;
}
//-----------------------------------------------------------------------------
// CRCfix
//-----------------------------------------------------------------------------
void CRCfix::SkipToEOL(FILE* infile)
{
int c;
for (c = lastchar; c != EOF && c != '\n'; c = fgetc(infile))
{
;
}
lastchar = fgetc(infile);
linenum++;
}
//-----------------------------------------------------------------------------
char* CRCfix::GetToken(FILE* infile, FILE* outfile)
{
static char token[512];
bool commentline = false;
bool commentblock = false;
bool doublequote = false;
bool singlequote = false;
int i = 0;
int c;
if (lastchar == EOF)
{
return NULL;
}
for (c = lastchar; c != EOF; c = fgetc(infile))
{
if (!commentline && !commentblock && !doublequote && !singlequote)
{
if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '#' || c == '_')
{
token[i++] = c;
continue;
}
else
{
if (i)
{
lastchar = c;
token[i] = 0;
i = 0;
return token;
}
}
}
if (commentline)
{
if (c == '\n')
{
commentline = false;
}
}
else if (commentblock)
{
while (c == '*')
{
c = fgetc(infile);
if (c == '/')
{
commentblock = false;
}
fputc('*', outfile);
}
}
if (!commentline && !commentblock)
{
if (c == '"' && !singlequote)
{
doublequote = !doublequote;
}
else if (c == '\'' && !doublequote)
{
singlequote = !singlequote;
}
else if (!singlequote && !doublequote)
{
if (c == '/')
{
c = fgetc(infile);
if (c == '/')
{
commentline = true;
}
else if (c == '*')
{
commentblock = true;
}
if (c == '\'')
{
singlequote = true;
}
else if (c == '"')
{
doublequote = true;
}
fputc('/', outfile);
}
}
else if (c == '\\')
{
fputc(c, outfile);
c = fgetc(infile);
}
}
fputc(c, outfile);
if (c == '\n')
{
linenum++;
}
}
lastchar = c;
token[i] = 0;
return i ? token : NULL;
}
//-----------------------------------------------------------------------------
void CRCfix::GetPreviousCRC(char* token, FILE* infile)
{
int c;
while ((c = fgetc(infile)) != ')')
{
*token++ = c;
}
*token = 0;
}
//-----------------------------------------------------------------------------
int CRCfix::Fix(Filename srce)
{
AZStd::chrono::system_clock::time_point startTime = AZStd::chrono::system_clock::now();
bool changed = false;
Filename dest(srce);
dest.SetExt(L"xxx");
linenum = 0;
FILE* infile = _wfopen(srce.GetFullPath(), L"r");
FILE* outfile = _wfopen(dest.GetFullPath(), L"w");
if (!infile || !outfile)
{
if (infile)
{
fclose(infile);
infile = nullptr;
}
if (outfile)
{
fclose(outfile);
outfile = nullptr;
}
int dt = static_cast<int>(AZStd::chrono::milliseconds(AZStd::chrono::system_clock::now() - startTime).count());
g_totalFixTimeMs += dt;
if (dt > g_longestFixTimeMs)
{
g_longestFixTimeMs = dt;
}
return -1;
}
lastchar = fgetc(infile);
while (char* token = GetToken(infile, outfile))
{
bool got = false;
if (strcmp(token, "AZ_CRC") == 0 && lastchar == '(')
{
size_t i = strlen(token);
token[i++] = lastchar;
int c = fgetc(infile);
if (c == '"')
{
size_t j = i + 1;
do
{
token[i++] = c;
c = fgetc(infile);
} while (c != '"');
token[i++] = c;
c = fgetc(infile);
int oldcrc = 0, newcrc;
if (c == ',')
{
GetPreviousCRC(token + i, infile);
sscanf(token + i, "%i", &oldcrc);
c = ')';
}
if (c == ')')
{
token[i] = 0;
c = fgetc(infile);
got = true;
newcrc = AZ::Crc32(token + j, i - j - 1, true);
fprintf(outfile, "%s, 0x%08x)", token, newcrc);
if (newcrc != oldcrc)
{
changed = true;
}
}
}
lastchar = c;
token[i] = 0;
}
if (!got)
{
fwrite(token, 1, strlen(token), outfile);
}
}
fclose(infile);
fclose(outfile);
if (changed)
{
Filename backup(srce);
backup.SetExt(L"crcfix_old");
if (backup.Exists())
{
backup.SetWritable();
[[maybe_unused]] bool deleted = backup.Delete();
AZ_Assert(deleted, "failed to delete");
}
if (!srce.Copy(backup.GetFullPath()))
{
AZ_TracePrintf("CrcFix", "Failed to copy %ls to %ls\n", srce, backup);
int dt = static_cast<int>(AZStd::chrono::milliseconds(AZStd::chrono::system_clock::now() - startTime).count());
g_totalFixTimeMs += dt;
if (dt > g_longestFixTimeMs)
{
g_longestFixTimeMs = dt;
}
return -1;
}
if (!dest.Rename(srce.GetFullPath()))
{
AZ_TracePrintf("CrcFix", "Failed to rename %ls to %ls\n", dest, srce);
int dt = static_cast<int>(AZStd::chrono::milliseconds(AZStd::chrono::system_clock::now() - startTime).count());
g_totalFixTimeMs += dt;
if (dt > g_longestFixTimeMs)
{
g_longestFixTimeMs = dt;
}
return -1;
}
if (!backup.Delete())
{
AZ_TracePrintf("CrcFix", "Failed to delete %ls\n", backup);
}
}
else
{
dest.Delete();
}
int dt = static_cast<int>(AZStd::chrono::milliseconds(AZStd::chrono::system_clock::now() - startTime).count());
g_totalFixTimeMs += dt;
if (dt > g_longestFixTimeMs)
{
g_longestFixTimeMs = dt;
}
return changed ? 1 : 0;
}
//-----------------------------------------------------------------------------
-11
View File
@@ -1,11 +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
#
#
set(FILES crcfix_files.cmake
crcfix.cpp
)
@@ -10,6 +10,7 @@
#include <QPushButton>
#include <QHBoxLayout>
#include <QKeyEvent>
namespace O3DE::ProjectManager
{
@@ -27,4 +28,14 @@ namespace O3DE::ProjectManager
: FormBrowseEditWidget(labelText, "", parent)
{
}
void FormBrowseEditWidget::keyPressEvent(QKeyEvent* event)
{
int key = event->key();
if (key == Qt::Key_Return || key == Qt::Key_Enter)
{
HandleBrowseButton();
}
}
} // namespace O3DE::ProjectManager
@@ -24,6 +24,9 @@ namespace O3DE::ProjectManager
explicit FormBrowseEditWidget(const QString& labelText = "", QWidget* parent = nullptr);
~FormBrowseEditWidget() = default;
protected:
void keyPressEvent(QKeyEvent* event) override;
protected slots:
virtual void HandleBrowseButton() = 0;
};
@@ -34,7 +34,6 @@ namespace O3DE::ProjectManager
{
setText(directory);
}
}
void FormFolderBrowseEditWidget::setText(const QString& text)
@@ -42,4 +41,5 @@ namespace O3DE::ProjectManager
QString path = QDir::toNativeSeparators(text);
FormBrowseEditWidget::setText(path);
}
} // namespace O3DE::ProjectManager
@@ -9,6 +9,15 @@
#include "DebugOutput.h"
#include <AzCore/std/optional.h>
#include <AzCore/IO/SystemFile.h>
#include <AzFramework/StringFunc/StringFunc.h>
#include <SceneAPI/SceneCore/Containers/Scene.h>
#include <SceneAPI/SceneCore/Containers/Views/PairIterator.h>
#include <SceneAPI/SceneCore/Containers/Views/SceneGraphDownwardsIterator.h>
#include <SceneAPI/SceneCore/DataTypes/IGraphObject.h>
#include <SceneAPI/SceneCore/Events/ExportProductList.h>
#include <SceneAPI/SceneCore/Utilities/Reporting.h>
namespace AZ::SceneAPI::Utilities
{
void DebugOutput::Write(const char* name, const char* data)
@@ -118,4 +127,63 @@ namespace AZ::SceneAPI::Utilities
{
return m_output;
}
void WriteAndLog(AZ::IO::SystemFile& dbgFile, const char* strToWrite)
{
AZ_TracePrintf(AZ::SceneAPI::Utilities::LogWindow, "%s", strToWrite);
dbgFile.Write(strToWrite, strlen(strToWrite));
dbgFile.Write("\n", strlen("\n"));
}
void DebugOutput::BuildDebugSceneGraph(const char* outputFolder, AZ::SceneAPI::Events::ExportProductList& productList, const AZStd::shared_ptr<AZ::SceneAPI::Containers::Scene>& scene, AZStd::string productName)
{
const int debugSceneGraphVersion = 1;
AZStd::string debugSceneFile;
AzFramework::StringFunc::Path::ConstructFull(outputFolder, productName.c_str(), debugSceneFile);
AZ_TracePrintf(AZ::SceneAPI::Utilities::LogWindow, "outputFolder %s, name %s.\n", outputFolder, productName.c_str());
AZ::IO::SystemFile dbgFile;
if (dbgFile.Open(debugSceneFile.c_str(), AZ::IO::SystemFile::SF_OPEN_CREATE | AZ::IO::SystemFile::SF_OPEN_WRITE_ONLY))
{
WriteAndLog(dbgFile, AZStd::string::format("ProductName: %s", productName.c_str()).c_str());
WriteAndLog(dbgFile, AZStd::string::format("debugSceneGraphVersion: %d", debugSceneGraphVersion).c_str());
WriteAndLog(dbgFile, scene->GetName().c_str());
const AZ::SceneAPI::Containers::SceneGraph& sceneGraph = scene->GetGraph();
auto names = sceneGraph.GetNameStorage();
auto content = sceneGraph.GetContentStorage();
auto pairView = AZ::SceneAPI::Containers::Views::MakePairView(names, content);
auto view = AZ::SceneAPI::Containers::Views::MakeSceneGraphDownwardsView<
AZ::SceneAPI::Containers::Views::BreadthFirst>(
sceneGraph, sceneGraph.GetRoot(), pairView.cbegin(), true);
for (auto&& viewIt : view)
{
if (viewIt.second == nullptr)
{
continue;
}
AZ::SceneAPI::DataTypes::IGraphObject* graphObject = const_cast<AZ::SceneAPI::DataTypes::IGraphObject*>(viewIt.second.get());
WriteAndLog(dbgFile, AZStd::string::format("Node Name: %s", viewIt.first.GetName()).c_str());
WriteAndLog(dbgFile, AZStd::string::format("Node Path: %s", viewIt.first.GetPath()).c_str());
WriteAndLog(dbgFile, AZStd::string::format("Node Type: %s", graphObject->RTTI_GetTypeName()).c_str());
AZ::SceneAPI::Utilities::DebugOutput debugOutput;
viewIt.second->GetDebugOutput(debugOutput);
if (!debugOutput.GetOutput().empty())
{
WriteAndLog(dbgFile, debugOutput.GetOutput().c_str());
}
}
dbgFile.Close();
static const AZ::Data::AssetType dbgSceneGraphAssetType("{07F289D1-4DC7-4C40-94B4-0A53BBCB9F0B}");
productList.AddProduct(productName, AZ::Uuid::CreateName(productName.c_str()), dbgSceneGraphAssetType,
AZStd::nullopt, AZStd::nullopt);
}
}
}
@@ -16,6 +16,22 @@
#include <AzCore/std/optional.h>
#include <cinttypes>
namespace AZ
{
namespace SceneAPI
{
namespace Containers
{
class Scene;
}
namespace Events
{
struct ExportProduct;
class ExportProductList;
}
}
}
namespace AZ::SceneAPI::Utilities
{
class DebugOutput
@@ -42,6 +58,8 @@ namespace AZ::SceneAPI::Utilities
SCENE_CORE_API const AZStd::string& GetOutput() const;
SCENE_CORE_API static void BuildDebugSceneGraph(const char* outputFolder, AZ::SceneAPI::Events::ExportProductList& productList, const AZStd::shared_ptr<AZ::SceneAPI::Containers::Scene>& scene, AZStd::string productName);
protected:
AZStd::string m_output;
};
@@ -75,6 +75,8 @@ UNDISCLOSED.
////////////////////////////////////////////////////////////////////////////////
// Constants
#pragma once
#include <Atom/RPI/Math.azsli>
static const float HALF_MAX = 65504.0f;
@@ -90,7 +92,8 @@ static const float DIM_SURROUND_GAMMA = 0.9811;
enum class ShaperType
{
ShaperLinear,
ShaperLog2
ShaperLog2,
PqSmpteSt2084,
};
////////////////////////////////////////////////////////////////////////////////
@@ -0,0 +1,52 @@
/*
* 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/Features/PostProcessing/Aces.azsli>
// Perceptual quantizer coefficients
static const float PqM1 = 1305.0 / 8192.0;
static const float PqM2 = 2533.0 / 32.0;
static const float PqC1 = 102.0 / 128.0;
static const float PqC2 = 2413.0 / 128.0;
static const float PqC3 = 2392.0 / 128.0;
static const float PqMaxNits = 10000.0;
float3 ShaperToLinear(float3 shaperColor, ShaperType shaperType, float shaperBias, float shaperScale)
{
// Apply the inverse of the shaper function to give the color in the working color space
switch (shaperType)
{
case ShaperType::ShaperLinear:
return (shaperColor - shaperBias) / shaperScale;
case ShaperType::ShaperLog2:
return pow(2.0, (shaperColor - shaperBias) / shaperScale);
case ShaperType::PqSmpteSt2084:
shaperColor = min(shaperColor, 1.0);
return PqMaxNits * pow(max(pow(shaperColor, 1.0 / PqM2) - PqC1, 0.0) / (PqC2 - PqC3 * pow(shaperColor, 1.0 / PqM2)), 1.0 / PqM1);
}
return shaperColor;
}
float3 LinearToShaper(float3 linearColor, ShaperType shaperType, float shaperBias, float shaperScale)
{
// Convert from working color space to lut coordinates by applying the shaper function
switch (shaperType)
{
case ShaperType::ShaperLinear:
return linearColor * shaperScale + shaperBias;
case ShaperType::ShaperLog2:
return log2(linearColor) * shaperScale + shaperBias;
case ShaperType::PqSmpteSt2084:
linearColor = min(linearColor, PqMaxNits);
linearColor = linearColor / PqMaxNits;
return pow((PqC1 + PqC2 * pow(linearColor, PqM1)) / (1.0 + PqC3 * pow(linearColor, PqM1)), PqM2);
}
return linearColor;
}
@@ -11,9 +11,7 @@
#include <Atom/Features/PostProcessing/FullscreenPixelInfo.azsli>
#include <Atom/Features/PostProcessing/FullscreenVertex.azsli>
#include <Atom/Features/PostProcessing/PostProcessUtil.azsli>
static const int SHAPER_LINEAR = 0;
static const int SHAPER_LOG2 = 1;
#include <Atom/Features/PostProcessing/Shapers.azsli>
ShaderResourceGroup PassSrg : SRG_PerPass
{
@@ -41,36 +39,22 @@ PSOutput MainPS(VSOutput IN)
float2 uvCoord = float2(IN.m_texCoord.x, IN.m_texCoord.y);
float3 color = PassSrg::m_colorTexture.Sample(PassSrg::LinearSampler, uvCoord).rgb;
ShaperType shaperType = (ShaperType)PassSrg::m_shaperType;
// Convert from working color space to lut coordinates by applying the shaper function
float3 lutCoordinate = color;
if (PassSrg::m_shaperType == SHAPER_LINEAR)
{
lutCoordinate = color * PassSrg::m_shaperScale + PassSrg::m_shaperBias;
}
else if (PassSrg::m_shaperType == SHAPER_LOG2)
{
lutCoordinate = log2(color) * PassSrg::m_shaperScale + PassSrg::m_shaperBias;
}
float3 lutCoordinate = LinearToShaper(color, shaperType, PassSrg::m_shaperBias, PassSrg::m_shaperScale);
// Adjust coordinate to the domain excluding the outer half texel in all directions
uint3 outputDimensions;
PassSrg::m_lut.GetDimensions(outputDimensions.x, outputDimensions.y, outputDimensions.z);
float3 coordBias = 1.0/(2.0 * outputDimensions);
float3 coordScale = (outputDimensions-1.0)/outputDimensions;
float3 coordBias = 1.0 / (2.0 * outputDimensions);
float3 coordScale = (outputDimensions - 1.0) / outputDimensions;
lutCoordinate = (lutCoordinate * coordScale) + coordBias;
float3 lutColor = PassSrg::m_lut.Sample(PassSrg::LinearSampler, lutCoordinate).rgb;
// Apply the inverse of the shaper function to give the color in the working color space
float3 finalColor = lutColor;
if (PassSrg::m_shaperType == SHAPER_LINEAR)
{
finalColor = (lutColor - PassSrg::m_shaperBias)/PassSrg::m_shaperScale;
}
else if (PassSrg::m_shaperType == SHAPER_LOG2)
{
finalColor = pow(2.0, (lutColor - PassSrg::m_shaperBias)/PassSrg::m_shaperScale);
}
float3 finalColor = ShaperToLinear(lutColor, shaperType, PassSrg::m_shaperBias, PassSrg::m_shaperScale);
OUT.m_color.rgb = finalColor;
OUT.m_color.a = 1.0;
@@ -8,6 +8,7 @@
#include <Atom/Features/SrgSemantics.azsli>
#include <Atom/Features/PostProcessing/Aces.azsli>
#include <Atom/Features/PostProcessing/Shapers.azsli>
ShaderResourceGroup PassSrg : SRG_PerPass_WithFallback
{
@@ -62,40 +63,18 @@ ShaderResourceGroup PassSrg : SRG_PerPass_WithFallback
[[range(0, 4)]]
option uint o_numSourceLuts = 0;
float3 ShaperToLinear(float3 shaperColor, ShaperType shaperType, float shaperBias, float shaperScale)
{
// Apply the inverse of the shaper function to give the color in the working color space
float3 linearColor = shaperColor;
if (shaperType == ShaperType::ShaperLinear)
{
linearColor = (shaperColor - shaperBias)/shaperScale;
}
else if (shaperType == ShaperType::ShaperLog2)
{
linearColor = pow(2.0, (shaperColor - shaperBias)/shaperScale);
}
return linearColor;
}
float3 LinearToShaper(float3 linearColor, ShaperType shaperType, float shaperBias, float shaperScale)
{
// Convert from working color space to lut coordinates by applying the shaper function
float3 shaperColor = linearColor;
if (shaperType == ShaperType::ShaperLinear)
{
shaperColor = linearColor * shaperScale + shaperBias;
}
else if (shaperType == ShaperType::ShaperLog2)
{
shaperColor = log2(linearColor) * shaperScale + shaperBias;
}
return shaperColor;
}
float3 GetSourceLutLinearColor(float3 baseColor, Texture3D<float4> sourceLut, ShaperType shaperType, float shaperBias, float shaperScale)
{
// Convert from reference linearColor to the lutCoordinate for this Lut
float3 lutCoord = LinearToShaper(baseColor, shaperType, shaperBias, shaperScale);
// Adjust coordinate to the domain excluding the outer half texel in all directions
uint3 outputDimensions;
sourceLut.GetDimensions(outputDimensions.x, outputDimensions.y, outputDimensions.z);
float3 coordBias = 1.0 / (2.0 * outputDimensions);
float3 coordScale = (outputDimensions - 1.0) / outputDimensions;
lutCoord = (lutCoord * coordScale) + coordBias;
float3 lutColor = sourceLut.SampleLevel(PassSrg::LinearSampler, lutCoord, 0).rgb;
// Convert to linear
float3 linearColor = ShaperToLinear(lutColor, shaperType, shaperBias, shaperScale);
@@ -115,11 +94,7 @@ void MainCS(uint3 dispatch_id: SV_DispatchThreadID)
}
// Get coordinates within the blended LUT 3D texture
float3 baseCoord = float3 (
(float)(dispatch_id.x)/(float)PassSrg::m_blendedLutDimensions.x,
(float)(dispatch_id.y)/(float)PassSrg::m_blendedLutDimensions.y,
(float)(dispatch_id.z)/(float)PassSrg::m_blendedLutDimensions.z
);
float3 baseCoord = float3(outPixel) / float3(PassSrg::m_blendedLutDimensions - 1.0);
// Convert to the base linear color (this is the color of the identity LUT)
float3 baseColor = ShaperToLinear(baseCoord, (ShaperType)PassSrg::m_blendedLutShaperType, PassSrg::m_blendedLutShaperBias, PassSrg::m_blendedLutShaperScale);
@@ -12,6 +12,7 @@
#include <Atom/Features/PostProcessing/FullscreenPixelInfo.azsli>
#include <Atom/Features/PostProcessing/FullscreenVertex.azsli>
#include <Atom/Features/PostProcessing/PostProcessUtil.azsli>
#include <Atom/Features/PostProcessing/Shapers.azsli>
#include "EyeAdaptationUtil.azsli"
ShaderResourceGroup PassSrg : SRG_PerPass_WithFallback
@@ -42,12 +43,111 @@ option bool o_enableExposureControlFeature = false;
// Option shader variable to enable color grading LUT.
option bool o_enableColorGradingLut = false;
// Controls the sampling quality of the blended LUT. Setting this higher can improve the quality of particularly tricky luts.
// 0 - linear
// 1 - 7 tap b-spline
// 2 - 19 tap b-spline
[[range(0, 2)]]
option uint o_lutSampleQuality = 0;
// Sample a 3dtexture with a 7 or 19 tap B-Spline. Consider ripping this out and putting in a more general location.
// This function samples a 4x4x4 neighborhood around the uv. Normally this would take 64 samples, but by taking
// advantage of bilinear filtering this can be done with 27 taps on the edges between pixels. The cost is further
// reduced by dropping either the 8 corners (19 total taps) or also dropping the 12 edges (7 total taps).
float4 SampleBSpline3D(Texture3D<float4> texture, SamplerState linearSampler, float3 uv, float3 textureSize, float3 rcpTextureSize)
{
// Think of sample locations in the 4x4 neighborhood as having a top left coordinate of 0,0 and
// a bottom right coordinate of 3,3.
// Find the position in texture space then round it to get the center of the 1,1 pixel (tc1)
float3 texelPos = uv * textureSize;
float3 tc1= floor(texelPos - 0.5) + 0.5;
// Offset from center position to texel
float3 f = texelPos - tc1;
// Compute B-Spline weights based on the offset
float3 OneMinusF = (1.0 - f);
float3 OneMinusF2 = OneMinusF * OneMinusF;
float3 OneMinusF3 = OneMinusF2 * OneMinusF;
float3 w0 = OneMinusF3;
float3 w1 = 4.0 + 3.0 * f * f * f - 6.0 * f * f;
float3 w2 = 4.0 + 3.0 * OneMinusF3 - 6.0 * OneMinusF2;
float3 w3 = f * f * f;
float3 w12 = w1 + w2;
// Compute uv coordinates for sampling the texture
float3 tc0 = (tc1 - 1.0f) * rcpTextureSize;
float3 tc3 = (tc1 + 2.0f) * rcpTextureSize;
float3 tc12 = (tc1 + w2 / w12) * rcpTextureSize;
// Compute sample weights
float sw0 = w12.x * w0.y * w12.z;
float sw1 = w0.x * w12.y * w12.z;
float sw2 = w12.x * w12.y * w12.z;
float sw3 = w3.x * w12.y * w12.z;
float sw4 = w12.x * w3.y * w12.z;
float sw5 = w12.x * w12.y * w0.z;
float sw6 = w12.x * w12.y * w3.z;
// total weight of samples to normalize result.
float totalWeight = sw0 + sw1 + sw2 + sw3 + sw4 + sw5 + sw6;
float4 result = 0.0f;
result += texture.SampleLevel(linearSampler, float3(tc12.x, tc0.y, tc12.z), 0.0) * sw0;
result += texture.SampleLevel(linearSampler, float3( tc0.x, tc12.y, tc12.z), 0.0) * sw1;
result += texture.SampleLevel(linearSampler, float3(tc12.x, tc12.y, tc12.z), 0.0) * sw2;
result += texture.SampleLevel(linearSampler, float3( tc3.x, tc12.y, tc12.z), 0.0) * sw3;
result += texture.SampleLevel(linearSampler, float3(tc12.x, tc3.y, tc12.z), 0.0) * sw4;
result += texture.SampleLevel(linearSampler, float3(tc12.x, tc12.y, tc0.z), 0.0) * sw5;
result += texture.SampleLevel(linearSampler, float3(tc12.x, tc12.y, tc3.z), 0.0) * sw6;
if (o_lutSampleQuality == 2)
{
// Extra 12 taps for Diagonals to increase the quality further.
float sw7 = w0.x * w0.y * w12.z;
float sw8 = w0.x * w3.y * w12.z;
float sw9 = w3.x * w0.y * w12.z;
float sw10 = w3.x * w3.y * w12.z;
float sw11 = w12.x * w0.y * w0.z;
float sw12 = w12.x * w0.y * w3.z;
float sw13 = w12.x * w3.y * w0.z;
float sw14 = w12.x * w3.y * w3.z;
float sw15 = w0.x * w12.y * w0.z;
float sw16 = w0.x * w12.y * w3.z;
float sw17 = w3.x * w12.y * w0.z;
float sw18 = w3.x * w12.y * w3.z;
totalWeight += sw7 + sw8 + sw9 + sw10 + sw11 + sw12 + sw13 + sw14 + sw15 + sw16 + sw17 + sw18;
result += texture.SampleLevel(linearSampler, float3(tc0.x, tc0.y, tc12.z), 0.0) * sw7;
result += texture.SampleLevel(linearSampler, float3(tc0.x, tc3.y, tc12.z), 0.0) * sw8;
result += texture.SampleLevel(linearSampler, float3(tc3.x, tc0.y, tc12.z), 0.0) * sw9;
result += texture.SampleLevel(linearSampler, float3(tc3.x, tc3.y, tc12.z), 0.0) * sw10;
result += texture.SampleLevel(linearSampler, float3(tc12.x, tc0.y, tc0.z), 0.0) * sw11;
result += texture.SampleLevel(linearSampler, float3(tc12.x, tc0.y, tc3.z), 0.0) * sw12;
result += texture.SampleLevel(linearSampler, float3(tc12.x, tc3.y, tc0.z), 0.0) * sw13;
result += texture.SampleLevel(linearSampler, float3(tc12.x, tc3.y, tc3.z), 0.0) * sw14;
result += texture.SampleLevel(linearSampler, float3(tc0.x, tc12.y, tc0.z), 0.0) * sw15;
result += texture.SampleLevel(linearSampler, float3(tc0.x, tc12.y, tc3.z), 0.0) * sw16;
result += texture.SampleLevel(linearSampler, float3(tc3.x, tc12.y, tc0.z), 0.0) * sw17;
result += texture.SampleLevel(linearSampler, float3(tc3.x, tc12.y, tc3.z), 0.0) * sw18;
}
return result / totalWeight;
}
PSOutput MainPS(VSOutput IN)
{
PSOutput OUT;
// Fetch the pixel color from the input texture
float3 color = PassSrg::m_framebuffer.Sample(PassSrg::LinearSampler, IN.m_texCoord).rgb;
float3 color = PassSrg::m_framebuffer.SampleLevel(PassSrg::LinearSampler, IN.m_texCoord, 0.0).rgb;
if (o_enableExposureControlFeature)
{
@@ -63,36 +163,28 @@ PSOutput MainPS(VSOutput IN)
if (o_enableColorGradingLut)
{
// Convert from working color space to lut coordinates by applying the shaper function
float3 lutCoordinate = color;
if (shaperType == ShaperType::ShaperLinear)
{
lutCoordinate = color * PassSrg::m_shaperScale + PassSrg::m_shaperBias;
}
else if (shaperType == ShaperType::ShaperLog2)
{
lutCoordinate = log2(color) * PassSrg::m_shaperScale + PassSrg::m_shaperBias;
}
float3 lutCoordinate = LinearToShaper(color, shaperType, PassSrg::m_shaperBias, PassSrg::m_shaperScale);
// Adjust coordinate to the domain excluding the outer half texel in all directions
uint3 outputDimensions;
PassSrg::m_gradingLut.GetDimensions(outputDimensions.x, outputDimensions.y, outputDimensions.z);
float3 coordBias = 0.5f / outputDimensions;
float3 coordScale = (outputDimensions-1.0)/outputDimensions;
float3 sizeMinusOne = outputDimensions - 1.0;
float3 coordScale = sizeMinusOne / outputDimensions;
lutCoordinate = (lutCoordinate * coordScale) + coordBias;
float3 lutColor = PassSrg::m_gradingLut.Sample(PassSrg::LinearSampler, lutCoordinate).rgb;
float3 lutColor = float3(0.0, 0.0, 0.0);
if (o_lutSampleQuality == 0)
{
lutColor = PassSrg::m_gradingLut.SampleLevel(PassSrg::LinearSampler, lutCoordinate, 0.0).rgb;
}
else
{
lutColor = SampleBSpline3D(PassSrg::m_gradingLut, PassSrg::LinearSampler, lutCoordinate, float3(outputDimensions), 1.0 / float3(outputDimensions)).rgb;
}
// Apply the inverse of the shaper function to give the color in the working color space
float3 finalColor = lutColor;
if (shaperType == ShaperType::ShaperLinear)
{
finalColor = (lutColor - PassSrg::m_shaperBias)/PassSrg::m_shaperScale;
}
else if (shaperType == ShaperType::ShaperLog2)
{
finalColor = pow(2.0, (lutColor - PassSrg::m_shaperBias)/PassSrg::m_shaperScale);
}
color = finalColor;
color = ShaperToLinear(lutColor, shaperType, PassSrg::m_shaperBias, PassSrg::m_shaperScale);
}
OUT.m_color.rgb = color;
@@ -1,9 +1,13 @@
{
"Shader" : "LookModificationTransform.shader",
"Variants" : [
{ "StableId": 1, "Options" : { "o_enableExposureControlFeature": "true", "o_enableColorGradingLut": "true" } },
{ "StableId": 2, "Options" : { "o_enableExposureControlFeature": "true", "o_enableColorGradingLut": "false" } },
{ "StableId": 3, "Options" : { "o_enableExposureControlFeature": "false", "o_enableColorGradingLut": "true" } },
{ "StableId": 4, "Options" : { "o_enableExposureControlFeature": "false", "o_enableColorGradingLut": "false" } }
{ "StableId": 1, "Options" : { "o_enableExposureControlFeature": "true", "o_enableColorGradingLut": "false" } },
{ "StableId": 2, "Options" : { "o_enableExposureControlFeature": "false", "o_enableColorGradingLut": "false" } },
{ "StableId": 3, "Options" : { "o_enableExposureControlFeature": "true", "o_enableColorGradingLut": "true", "o_lutSampleQuality": 0 } },
{ "StableId": 4, "Options" : { "o_enableExposureControlFeature": "false", "o_enableColorGradingLut": "true", "o_lutSampleQuality": 0 } },
{ "StableId": 5, "Options" : { "o_enableExposureControlFeature": "true", "o_enableColorGradingLut": "true", "o_lutSampleQuality": 1 } },
{ "StableId": 6, "Options" : { "o_enableExposureControlFeature": "false", "o_enableColorGradingLut": "true", "o_lutSampleQuality": 1 } },
{ "StableId": 7, "Options" : { "o_enableExposureControlFeature": "true", "o_enableColorGradingLut": "true", "o_lutSampleQuality": 2 } },
{ "StableId": 8, "Options" : { "o_enableExposureControlFeature": "false", "o_enableColorGradingLut": "true", "o_lutSampleQuality": 2 } }
]
}
@@ -1135,13 +1135,13 @@ float4 SMAABlendingWeightCalculationPS(float2 texcoord,
if (!o_enableDiagonalDetectionFeature ||
weights.r == -weights.g) // weights.r + weights.g == 0.0
{
float2 d;
// NOTE: using separate floats for (dx, dy) and (sqrt_d_x, sqrt_d_y) instead of float2 due to android Mali driver problem crashing the device
// Find the distance to the left:
float3 coords;
coords.x = SMAASearchXLeft(SMAATexturePass2D(edgesTex), SMAATexturePass2D(searchTex), offset[0].xy, offset[2].x);
coords.y = offset[1].y; // offset[1].y = texcoord.y - 0.25 * SMAA_RT_METRICS.y (@CROSSING_OFFSET)
d.x = coords.x;
float dx = coords.x;
// Now fetch the left crossing edges, two at a time using bilinear
// filtering. Sampling at -0.25 (see @CROSSING_OFFSET) enables to
@@ -1150,26 +1150,29 @@ float4 SMAABlendingWeightCalculationPS(float2 texcoord,
// Find the distance to the right:
coords.z = SMAASearchXRight(SMAATexturePass2D(edgesTex), SMAATexturePass2D(searchTex), offset[0].zw, offset[2].y);
d.y = coords.z;
float dy = coords.z;
// We want the distances to be in pixel units (doing this here allow to
// better interleave arithmetic and memory accesses):
d = abs(round(mad(SMAA_RT_METRICS.zz, d, -pixcoord.xx)));
dx = abs(round(mad(SMAA_RT_METRICS.z, dx, -pixcoord.x)));
dy = abs(round(mad(SMAA_RT_METRICS.z, dy, -pixcoord.x)));
// SMAAArea below needs a sqrt, as the areas texture is compressed
// quadratically:
float2 sqrt_d = sqrt(d);
float sqrt_d_x = sqrt(dx);
float sqrt_d_y = sqrt(dy);
// Fetch the right crossing edges:
float e2 = SMAASampleLevelZeroOffset(edgesTex, coords.zy, int2(1, 0)).r;
// Ok, we know how this pattern looks like, now it is time for getting
// the actual area:
weights.rg = SMAAArea(SMAATexturePass2D(areaTex), sqrt_d, e1, e2, subsampleIndices.y);
weights.rg = SMAAArea(SMAATexturePass2D(areaTex), float2(sqrt_d_x, sqrt_d_y), e1, e2, subsampleIndices.y);
// Fix corners:
coords.y = texcoord.y;
SMAADetectHorizontalCornerPattern(SMAATexturePass2D(edgesTex), weights.rg, coords.xyzy, d);
SMAADetectHorizontalCornerPattern(SMAATexturePass2D(edgesTex), weights.rg, coords.xyzy, float2(dx, dy));
}
else
{
@@ -1180,37 +1183,37 @@ float4 SMAABlendingWeightCalculationPS(float2 texcoord,
SMAA_BRANCH
if (e.r > 0.0) // Edge at west
{
float2 d;
// Find the distance to the top:
float3 coords;
coords.y = SMAASearchYUp(SMAATexturePass2D(edgesTex), SMAATexturePass2D(searchTex), offset[1].xy, offset[2].z);
coords.x = offset[0].x; // offset[1].x = texcoord.x - 0.25 * SMAA_RT_METRICS.x;
d.x = coords.y;
float dx = coords.y;
// Fetch the top crossing edges:
float e1 = SMAASampleLevelZero(edgesTex, coords.xy).g;
// Find the distance to the bottom:
coords.z = SMAASearchYDown(SMAATexturePass2D(edgesTex), SMAATexturePass2D(searchTex), offset[1].zw, offset[2].w);
d.y = coords.z;
float dy = coords.z;
// We want the distances to be in pixel units:
d = abs(round(mad(SMAA_RT_METRICS.ww, d, -pixcoord.yy)));
dx = abs(round(mad(SMAA_RT_METRICS.w, dx, -pixcoord.y)));
dy = abs(round(mad(SMAA_RT_METRICS.w, dy, -pixcoord.y)));
// SMAAArea below needs a sqrt, as the areas texture is compressed
// quadratically:
float2 sqrt_d = sqrt(d);
float sqrt_d_x = sqrt(dx);
float sqrt_d_y = sqrt(dy);
// Fetch the bottom crossing edges:
float e2 = SMAASampleLevelZeroOffset(edgesTex, coords.xz, int2(0, 1)).g;
// Get the area for this direction:
weights.ba = SMAAArea(SMAATexturePass2D(areaTex), sqrt_d, e1, e2, subsampleIndices.x);
weights.ba = SMAAArea(SMAATexturePass2D(areaTex), float2(sqrt_d_x, sqrt_d_y), e1, e2, subsampleIndices.x);
// Fix corners:
coords.x = texcoord.x;
SMAADetectVerticalCornerPattern(SMAATexturePass2D(edgesTex), weights.ba, coords.xyxz, d);
SMAADetectVerticalCornerPattern(SMAATexturePass2D(edgesTex), weights.ba, coords.xyxz, float2(dx, dy));
}
return weights;
+17 -27
View File
@@ -203,47 +203,37 @@ namespace AZ
return ODT_48nits;
}
ShaperParams GetLog2ShaperParameters(float minStops, float maxStops)
{
ShaperParams shaperParams;
constexpr float Log2MediumGray = -2.47393118833f; // log2f(0.18f);
shaperParams.m_type = ShaperType::Log2;
shaperParams.m_scale = 1.0f / (maxStops - minStops);
shaperParams.m_bias = -((minStops + Log2MediumGray) * shaperParams.m_scale);
return shaperParams;
}
ShaperParams GetAcesShaperParameters(OutputDeviceTransformType odtType)
{
AZ_Assert(static_cast<uint32_t>(odtType) < static_cast<uint32_t>(NumOutputDeviceTransformTypes), "Invalid ODT type specified.");
ShaperParams shaperParams;
// These values represent and low and high end of the dynamic range in terms of stops from middle grey (0.18)
float lowerDynamicRangeInStops;
float higherDynamicRangeInStops;
const float MIDDLE_GREY = 0.18f;
switch (odtType)
{
case OutputDeviceTransformType_48Nits:
lowerDynamicRangeInStops = -6.5f;
higherDynamicRangeInStops = 6.5f;
break;
return GetLog2ShaperParameters(-6.5f, 6.5f);
case OutputDeviceTransformType_1000Nits:
lowerDynamicRangeInStops = -12.f;
higherDynamicRangeInStops = 10.f;
break;
return GetLog2ShaperParameters(-12.0f, 10.0f);
case OutputDeviceTransformType_2000Nits:
lowerDynamicRangeInStops = -12.f;
higherDynamicRangeInStops = 11.f;
break;
return GetLog2ShaperParameters(-12.0f, 11.0f);
case OutputDeviceTransformType_4000Nits:
lowerDynamicRangeInStops = -12.f;
higherDynamicRangeInStops = 12.f;
break;
return GetLog2ShaperParameters(-12.0f, 12.0f);
default:
AZ_Assert(false, "Invalid output device transform type.");
return shaperParams;
break;
}
float logMin = log2(MIDDLE_GREY * exp2(lowerDynamicRangeInStops));
float logMax = log2(MIDDLE_GREY * exp2(higherDynamicRangeInStops));
shaperParams.scale = 1.0f / (logMax - logMin);
shaperParams.bias = -shaperParams.scale * logMin;
shaperParams.type = ShaperType::Log2;
return shaperParams;
return ShaperParams();
}
Matrix3x3 GetColorConvertionMatrix(ColorConvertionMatrixType type)
+14 -8
View File
@@ -124,18 +124,19 @@ namespace AZ
NumColorConvertionMatrixTypes
};
enum ShaperType
enum class ShaperType : uint32_t
{
Linear = 0,
Log2 = 1,
PqSmpteSt2084 = 2,
NumShaperTypes
};
struct ShaperParams
{
ShaperType type = ShaperType::Linear;
float bias = 0.f;
float scale = 1.f;
ShaperType m_type = ShaperType::Linear;
float m_bias = 0.0f;
float m_scale = 1.0f;
};
enum class DisplayMapperOperationType : uint32_t
@@ -151,10 +152,14 @@ namespace AZ
enum class ShaperPresetType
{
None = 0,
Log2_48_nits,
Log2_1000_nits,
Log2_2000_nits,
Log2_4000_nits
LinearCustomRange,
Log2_48Nits,
Log2_1000Nits,
Log2_2000Nits,
Log2_4000Nits,
Log2CustomRange,
PqSmpteSt2084,
NumShaperTypes
};
enum class ToneMapperType
@@ -171,6 +176,7 @@ namespace AZ
};
SegmentedSplineParamsC9 GetAcesODTParameters(OutputDeviceTransformType odtType);
ShaperParams GetLog2ShaperParameters(float minStops, float maxStops);
ShaperParams GetAcesShaperParameters(OutputDeviceTransformType odtType);
Matrix3x3 GetColorConvertionMatrix(ColorConvertionMatrixType type);
@@ -78,7 +78,7 @@ namespace AZ
static OutputDeviceTransformType GetOutputDeviceTransformType(RHI::Format bufferFormat);
static void GetAcesDisplayMapperParameters(DisplayMapperParameters* displayMapperParameters, OutputDeviceTransformType odtType);
static ShaperParams GetShaperParameters(ShaperPresetType shaperPreset);
static ShaperParams GetShaperParameters(ShaperPresetType shaperPreset, float customMinEv = 0.0f, float customMaxEv = 0.0f);
static void GetDefaultDisplayMapperConfiguration(DisplayMapperConfigurationDescriptor& config);
// DisplayMapperFeatureProcessorInteface overrides...
@@ -102,8 +102,6 @@ namespace AZ
static constexpr const char* FeatureProcessorName = "AcesDisplayMapperFeatureProcessor";
static const int LutSize = 32;
static const RHI::Format LutFormat = RHI::Format::R16G16B16A16_FLOAT;
static const int ImagePoolBudget = 1 << 20; // 1 Megabyte
// LUTs that are baked through shaders
@@ -10,11 +10,9 @@
// PARAM(NAME, MEMBER_NAME, DEFAULT_VALUE, ...)
AZ_GFX_BOOL_PARAM(Enabled, m_enabled, false)
AZ_GFX_COMMON_PARAM(Data::Asset<RPI::AnyAsset>, ColorGradingLut, m_colorGradingLut, {})
AZ_GFX_COMMON_PARAM(AZ::Render::ShaperPresetType, ShaperPresetType, m_shaperPresetType, AZ::Render::ShaperPresetType::Log2_48_nits)
AZ_GFX_COMMON_PARAM(AZ::Render::ShaperPresetType, ShaperPresetType, m_shaperPresetType, AZ::Render::ShaperPresetType::Log2_48Nits)
AZ_GFX_COMMON_PARAM(float, CustomMinExposure, m_customMinExposure, -6.5)
AZ_GFX_COMMON_PARAM(float, CustomMaxExposure, m_customMaxExposure, 6.5)
AZ_GFX_FLOAT_PARAM(ColorGradingLutIntensity, m_colorGradingLutIntensity, 1.0)
AZ_GFX_FLOAT_PARAM(ColorGradingLutOverride, m_colorGradingLutOverride, 1.0)
@@ -21,6 +21,7 @@
namespace
{
static const AZ::RHI::Format LutFormat = AZ::RHI::Format::R16G16B16A16_FLOAT;
uint16_t ConvertFloatToHalf(const float Value)
{
@@ -56,395 +57,409 @@ namespace
}
}
namespace AZ
namespace AZ::Render
{
namespace Render
void AcesDisplayMapperFeatureProcessor::Reflect(ReflectContext* context)
{
void AcesDisplayMapperFeatureProcessor::Reflect(ReflectContext* context)
if (auto* serializeContext = azrtti_cast<SerializeContext*>(context))
{
if (auto* serializeContext = azrtti_cast<SerializeContext*>(context))
{
serializeContext
->Class<AcesDisplayMapperFeatureProcessor, FeatureProcessor>()
->Version(0);
}
serializeContext
->Class<AcesDisplayMapperFeatureProcessor, FeatureProcessor>()
->Version(0);
}
}
void AcesDisplayMapperFeatureProcessor::Activate()
{
GetDefaultDisplayMapperConfiguration(m_displayMapperConfiguration);
}
void AcesDisplayMapperFeatureProcessor::Deactivate()
{
m_ownedLuts.clear();
}
void AcesDisplayMapperFeatureProcessor::Simulate(const FeatureProcessor::SimulatePacket& packet)
{
AZ_TRACE_METHOD();
AZ_UNUSED(packet);
}
void AcesDisplayMapperFeatureProcessor::Render([[maybe_unused]] const FeatureProcessor::RenderPacket& packet)
{
}
void AcesDisplayMapperFeatureProcessor::ApplyLdrOdtParameters(DisplayMapperParameters* displayMapperParameters)
{
AZ_Assert(displayMapperParameters != nullptr, "The pOutParameters must not to be null pointer.");
if (displayMapperParameters == nullptr)
{
return;
}
void AcesDisplayMapperFeatureProcessor::Activate()
// These values in the ODT parameter are taken from the reference ACES transform.
//
// The original ACES references.
// Common:
// https://github.com/ampas/aces-dev/blob/master/transforms/ctl/lib/ACESlib.ODT_Common.ctl
// For sRGB:
// https://github.com/ampas/aces-dev/tree/master/transforms/ctl/odt/sRGB
displayMapperParameters->m_cinemaLimits[0] = 0.02f;
displayMapperParameters->m_cinemaLimits[1] = 48.0f;
displayMapperParameters->m_acesSplineParams = GetAcesODTParameters(OutputDeviceTransformType_48Nits);
displayMapperParameters->m_OutputDisplayTransformFlags = AlterSurround | ApplyDesaturation | ApplyCATD60toD65;
displayMapperParameters->m_OutputDisplayTransformMode = Srgb;
ColorConvertionMatrixType colorMatrixType = XYZ_To_Rec709;
switch (displayMapperParameters->m_OutputDisplayTransformMode)
{
GetDefaultDisplayMapperConfiguration(m_displayMapperConfiguration);
case Srgb:
colorMatrixType = XYZ_To_Rec709;
break;
case PerceptualQuantizer:
case Ldr:
colorMatrixType = XYZ_To_Bt2020;
break;
default:
break;
}
displayMapperParameters->m_XYZtoDisplayPrimaries = GetColorConvertionMatrix(colorMatrixType);
displayMapperParameters->m_surroundGamma = 0.9811f;
displayMapperParameters->m_gamma = 2.2f;
}
void AcesDisplayMapperFeatureProcessor::ApplyHdrOdtParameters(DisplayMapperParameters* displayMapperParameters, const OutputDeviceTransformType& odtType)
{
AZ_Assert(displayMapperParameters != nullptr, "The pOutParameters must not to be null pointer.");
if (displayMapperParameters == nullptr)
{
return;
}
void AcesDisplayMapperFeatureProcessor::Deactivate()
// Dynamic range limit values taken from NVIDIA HDR sample.
// These values represent and low and high end of the dynamic range in terms of stops from middle grey (0.18)
float lowerDynamicRangeInStops = -12.f;
float higherDynamicRangeInStops = 10.f;
const float MIDDLE_GREY = 0.18f;
switch (odtType)
{
m_ownedLuts.clear();
case OutputDeviceTransformType_1000Nits:
higherDynamicRangeInStops = 10.f;
break;
case OutputDeviceTransformType_2000Nits:
higherDynamicRangeInStops = 11.f;
break;
case OutputDeviceTransformType_4000Nits:
higherDynamicRangeInStops = 12.f;
break;
default:
AZ_Assert(false, "Invalid output device transform type.");
break;
}
void AcesDisplayMapperFeatureProcessor::Simulate(const FeatureProcessor::SimulatePacket& packet)
displayMapperParameters->m_cinemaLimits[0] = MIDDLE_GREY * exp2(lowerDynamicRangeInStops);
displayMapperParameters->m_cinemaLimits[1] = MIDDLE_GREY * exp2(higherDynamicRangeInStops);
displayMapperParameters->m_acesSplineParams = GetAcesODTParameters(odtType);
displayMapperParameters->m_OutputDisplayTransformFlags = AlterSurround | ApplyDesaturation | ApplyCATD60toD65;
displayMapperParameters->m_OutputDisplayTransformMode = PerceptualQuantizer;
ColorConvertionMatrixType colorMatrixType = XYZ_To_Bt2020;
displayMapperParameters->m_XYZtoDisplayPrimaries = GetColorConvertionMatrix(colorMatrixType);
// Surround gamma value is from the dim surround gamma from the ACES reference transforms.
// https://github.com/ampas/aces-dev/blob/master/transforms/ctl/lib/ACESlib.ODT_Common.ctl
displayMapperParameters->m_surroundGamma = 0.9811f;
displayMapperParameters->m_gamma = 1.0f; // gamma not used with perceptual quantizer, but just set to 1.0 anyways
}
OutputDeviceTransformType AcesDisplayMapperFeatureProcessor::GetOutputDeviceTransformType(RHI::Format bufferFormat)
{
OutputDeviceTransformType outputDeviceTransformType = OutputDeviceTransformType_48Nits;
if (bufferFormat == RHI::Format::R8G8B8A8_UNORM ||
bufferFormat == RHI::Format::B8G8R8A8_UNORM)
{
AZ_TRACE_METHOD();
AZ_UNUSED(packet);
outputDeviceTransformType = OutputDeviceTransformType_48Nits;
}
else if (bufferFormat == RHI::Format::R10G10B10A2_UNORM)
{
outputDeviceTransformType = OutputDeviceTransformType_1000Nits;
}
else
{
AZ_Assert(false, "Not yet supported.");
// To work normally on unsupported environment, initialize the display parameters by OutputDeviceTransformType_48Nits.
outputDeviceTransformType = OutputDeviceTransformType_48Nits;
}
return outputDeviceTransformType;
}
void AcesDisplayMapperFeatureProcessor::GetAcesDisplayMapperParameters(DisplayMapperParameters* displayMapperParameters, OutputDeviceTransformType odtType)
{
switch (odtType)
{
case OutputDeviceTransformType_48Nits:
ApplyLdrOdtParameters(displayMapperParameters);
break;
case OutputDeviceTransformType_1000Nits:
case OutputDeviceTransformType_2000Nits:
case OutputDeviceTransformType_4000Nits:
ApplyHdrOdtParameters(displayMapperParameters, odtType);
break;
default:
AZ_Assert(false, "This ODT type[%d] is not supported.", odtType);
break;
}
}
void AcesDisplayMapperFeatureProcessor::GetOwnedLut(DisplayMapperLut& displayMapperLut, const AZ::Name& lutName)
{
auto it = m_ownedLuts.find(lutName);
if (it == m_ownedLuts.end())
{
InitializeLutImage(lutName);
it = m_ownedLuts.find(lutName);
AZ_Assert(it != m_ownedLuts.end(), "AcesDisplayMapperFeatureProcessor unable to create LUT %s", lutName.GetCStr());
}
displayMapperLut = it->second;
}
void AcesDisplayMapperFeatureProcessor::GetDisplayMapperLut(DisplayMapperLut& displayMapperLut)
{
const AZ::Name acesLutName("AcesLutImage");
auto it = m_ownedLuts.find(acesLutName);
if (it == m_ownedLuts.end())
{
InitializeLutImage(acesLutName);
it = m_ownedLuts.find(acesLutName);
AZ_Assert(it != m_ownedLuts.end(), "AcesDisplayMapperFeatureProcessor unable to create ACES LUT image");
}
displayMapperLut = it->second;
}
void AcesDisplayMapperFeatureProcessor::GetLutFromAssetLocation(DisplayMapperAssetLut& displayMapperAssetLut, const AZStd::string& assetPath)
{
Data::AssetId assetId = RPI::AssetUtils::GetAssetIdForProductPath(assetPath.c_str(), RPI::AssetUtils::TraceLevel::Error);
GetLutFromAssetId(displayMapperAssetLut, assetId);
}
void AcesDisplayMapperFeatureProcessor::GetLutFromAssetId(DisplayMapperAssetLut& displayMapperAssetLut, const AZ::Data::AssetId assetId)
{
if (!assetId.IsValid())
{
return;
}
void AcesDisplayMapperFeatureProcessor::Render([[maybe_unused]] const FeatureProcessor::RenderPacket& packet)
// Check first if this already exists
auto it = m_assetLuts.find(assetId.ToString<AZStd::string>());
if (it != m_assetLuts.end())
{
displayMapperAssetLut = it->second;
return;
}
void AcesDisplayMapperFeatureProcessor::ApplyLdrOdtParameters(DisplayMapperParameters* displayMapperParameters)
// Read the lut which is a .3dl file embedded within an azasset file.
Data::Asset<RPI::AnyAsset> asset = RPI::AssetUtils::LoadAssetById<RPI::AnyAsset>(assetId, RPI::AssetUtils::TraceLevel::Error);
const LookupTableAsset* lutAsset = RPI::GetDataFromAnyAsset<LookupTableAsset>(asset);
if (lutAsset == nullptr)
{
AZ_Assert(displayMapperParameters != nullptr, "The pOutParameters must not to be null pointer.");
if (displayMapperParameters == nullptr)
{
return;
}
// These values in the ODT parameter are taken from the reference ACES transform.
//
// The original ACES references.
// Common:
// https://github.com/ampas/aces-dev/blob/master/transforms/ctl/lib/ACESlib.ODT_Common.ctl
// For sRGB:
// https://github.com/ampas/aces-dev/tree/master/transforms/ctl/odt/sRGB
displayMapperParameters->m_cinemaLimits[0] = 0.02f;
displayMapperParameters->m_cinemaLimits[1] = 48.0f;
displayMapperParameters->m_acesSplineParams = GetAcesODTParameters(OutputDeviceTransformType_48Nits);
displayMapperParameters->m_OutputDisplayTransformFlags = AlterSurround | ApplyDesaturation | ApplyCATD60toD65;
displayMapperParameters->m_OutputDisplayTransformMode = Srgb;
ColorConvertionMatrixType colorMatrixType = XYZ_To_Rec709;
switch (displayMapperParameters->m_OutputDisplayTransformMode)
{
case Srgb:
colorMatrixType = XYZ_To_Rec709;
break;
case PerceptualQuantizer:
case Ldr:
colorMatrixType = XYZ_To_Bt2020;
break;
default:
break;
}
displayMapperParameters->m_XYZtoDisplayPrimaries = GetColorConvertionMatrix(colorMatrixType);
displayMapperParameters->m_surroundGamma = 0.9811f;
displayMapperParameters->m_gamma = 2.2f;
AZ_Error("AcesDisplayMapperFeatureProcessor", false, "Unable to read LUT from asset.");
asset.Release();
return;
}
void AcesDisplayMapperFeatureProcessor::ApplyHdrOdtParameters(DisplayMapperParameters* displayMapperParameters, const OutputDeviceTransformType& odtType)
// The first row of numbers in a 3dl file is a number of vertices that partition the space from [0,..1023]
// This assumes that the vertices are evenly spaced apart. Non-uniform spacing is supported by the format,
// but haven't been encountered yet.
const size_t lutSize = lutAsset->m_intervals.size();
if (lutSize == 0)
{
AZ_Assert(displayMapperParameters != nullptr, "The pOutParameters must not to be null pointer.");
if (displayMapperParameters == nullptr)
{
return;
}
// Dynamic range limit values taken from NVIDIA HDR sample.
// These values represent and low and high end of the dynamic range in terms of stops from middle grey (0.18)
float lowerDynamicRangeInStops = -12.f;
float higherDynamicRangeInStops = 10.f;
const float MIDDLE_GREY = 0.18f;
switch (odtType)
{
case OutputDeviceTransformType_1000Nits:
higherDynamicRangeInStops = 10.f;
break;
case OutputDeviceTransformType_2000Nits:
higherDynamicRangeInStops = 11.f;
break;
case OutputDeviceTransformType_4000Nits:
higherDynamicRangeInStops = 12.f;
break;
default:
AZ_Assert(false, "Invalid output device transform type.");
break;
}
displayMapperParameters->m_cinemaLimits[0] = MIDDLE_GREY * exp2(lowerDynamicRangeInStops);
displayMapperParameters->m_cinemaLimits[1] = MIDDLE_GREY * exp2(higherDynamicRangeInStops);
displayMapperParameters->m_acesSplineParams = GetAcesODTParameters(odtType);
displayMapperParameters->m_OutputDisplayTransformFlags = AlterSurround | ApplyDesaturation | ApplyCATD60toD65;
displayMapperParameters->m_OutputDisplayTransformMode = PerceptualQuantizer;
ColorConvertionMatrixType colorMatrixType = XYZ_To_Bt2020;
displayMapperParameters->m_XYZtoDisplayPrimaries = GetColorConvertionMatrix(colorMatrixType);
// Surround gamma value is from the dim surround gamma from the ACES reference transforms.
// https://github.com/ampas/aces-dev/blob/master/transforms/ctl/lib/ACESlib.ODT_Common.ctl
displayMapperParameters->m_surroundGamma = 0.9811f;
displayMapperParameters->m_gamma = 1.0f; // gamma not used with perceptual quantizer, but just set to 1.0 anyways
AZ_Error("AcesDisplayMapperFeatureProcessor", false, "Lut asset has invalid size.");
asset.Release();
return;
}
OutputDeviceTransformType AcesDisplayMapperFeatureProcessor::GetOutputDeviceTransformType(RHI::Format bufferFormat)
// Create a buffer of half floats from the LUT and use it to initialize a 3d texture.
const size_t kChannels = 4;
const size_t kChannelBytes = 2;
const size_t bytesPerRow = lutSize * kChannels * kChannelBytes;
const size_t bytesPerSlice = bytesPerRow * lutSize;
AZStd::vector<uint16_t> u16Buffer;
const size_t bufferSize = lutSize * lutSize * lutSize * kChannels;
u16Buffer.resize(bufferSize);
for (size_t slice = 0; slice < lutSize; slice++)
{
OutputDeviceTransformType outputDeviceTransformType = OutputDeviceTransformType_48Nits;
if (bufferFormat == RHI::Format::R8G8B8A8_UNORM ||
bufferFormat == RHI::Format::B8G8R8A8_UNORM)
for (size_t column = 0; column < lutSize; column++)
{
outputDeviceTransformType = OutputDeviceTransformType_48Nits;
}
else if (bufferFormat == RHI::Format::R10G10B10A2_UNORM)
{
outputDeviceTransformType = OutputDeviceTransformType_1000Nits;
}
else
{
AZ_Assert(false, "Not yet supported.");
// To work normally on unsupported environment, initialize the display parameters by OutputDeviceTransformType_48Nits.
outputDeviceTransformType = OutputDeviceTransformType_48Nits;
}
return outputDeviceTransformType;
}
void AcesDisplayMapperFeatureProcessor::GetAcesDisplayMapperParameters(DisplayMapperParameters* displayMapperParameters, OutputDeviceTransformType odtType)
{
switch (odtType)
{
case OutputDeviceTransformType_48Nits:
ApplyLdrOdtParameters(displayMapperParameters);
break;
case OutputDeviceTransformType_1000Nits:
case OutputDeviceTransformType_2000Nits:
case OutputDeviceTransformType_4000Nits:
ApplyHdrOdtParameters(displayMapperParameters, odtType);
break;
default:
AZ_Assert(false, "This ODT type[%d] is not supported.", odtType);
break;
}
}
void AcesDisplayMapperFeatureProcessor::GetOwnedLut(DisplayMapperLut& displayMapperLut, const AZ::Name& lutName)
{
auto it = m_ownedLuts.find(lutName);
if (it == m_ownedLuts.end())
{
InitializeLutImage(lutName);
it = m_ownedLuts.find(lutName);
AZ_Assert(it != m_ownedLuts.end(), "AcesDisplayMapperFeatureProcessor unable to create LUT %s", lutName.GetCStr());
}
displayMapperLut = it->second;
}
void AcesDisplayMapperFeatureProcessor::GetDisplayMapperLut(DisplayMapperLut& displayMapperLut)
{
const AZ::Name acesLutName("AcesLutImage");
auto it = m_ownedLuts.find(acesLutName);
if (it == m_ownedLuts.end())
{
InitializeLutImage(acesLutName);
it = m_ownedLuts.find(acesLutName);
AZ_Assert(it != m_ownedLuts.end(), "AcesDisplayMapperFeatureProcessor unable to create ACES LUT image");
}
displayMapperLut = it->second;
}
void AcesDisplayMapperFeatureProcessor::GetLutFromAssetLocation(DisplayMapperAssetLut& displayMapperAssetLut, const AZStd::string& assetPath)
{
Data::AssetId assetId = RPI::AssetUtils::GetAssetIdForProductPath(assetPath.c_str(), RPI::AssetUtils::TraceLevel::Error);
GetLutFromAssetId(displayMapperAssetLut, assetId);
}
void AcesDisplayMapperFeatureProcessor::GetLutFromAssetId(DisplayMapperAssetLut& displayMapperAssetLut, const AZ::Data::AssetId assetId)
{
if (!assetId.IsValid())
{
return;
}
// Check first if this already exists
auto it = m_assetLuts.find(assetId.ToString<AZStd::string>());
if (it != m_assetLuts.end())
{
displayMapperAssetLut = it->second;
return;
}
// Read the lut which is a .3dl file embedded within an azasset file.
Data::Asset<RPI::AnyAsset> asset = RPI::AssetUtils::LoadAssetById<RPI::AnyAsset>(assetId, RPI::AssetUtils::TraceLevel::Error);
const LookupTableAsset* lutAsset = RPI::GetDataFromAnyAsset<LookupTableAsset>(asset);
if (lutAsset == nullptr)
{
AZ_Error("AcesDisplayMapperFeatureProcessor", false, "Unable to read LUT from asset.");
asset.Release();
return;
}
// The first row of numbers in a 3dl file is a number of vertices that partition the space from [0,..1023]
// This assumes that the vertices are evenly spaced apart. Non-uniform spacing is supported by the format,
// but haven't been encountered yet.
uint32_t lutSize = static_cast<uint32_t>(lutAsset->m_intervals.size());
if (lutSize == 0)
{
AZ_Error("AcesDisplayMapperFeatureProcessor", false, "Lut asset has invalid size.");
asset.Release();
return;
}
// The vertices in the file are given as a positive integer value in [0,..4095] and need to be normalized
// and stored into a linear unaligned buffer used to initialize the streaming image.
const float normalizeValue = 4095.0f;
const int kChannels = 4;
const int kChannelBytes = 2;
int bytesPerRow = lutSize * kChannels * kChannelBytes;
int bytesPerSlice = bytesPerRow * lutSize;
AZStd::vector<uint16_t> u16Buffer;
size_t bufferSize = (bytesPerSlice * lutSize) / sizeof(uint16_t);
u16Buffer.resize(bufferSize);
uint16_t* data = u16Buffer.data();
for (int slice = 0; slice < (int)lutSize; slice++)
{
for (int column = 0; column < (int)lutSize; column++)
for (size_t row = 0; row < lutSize; row++)
{
for (int row = 0; row < (int)lutSize; row++)
{
// Index in the LUT texture data
int idx = (column * kChannels) +
(bytesPerRow * row / sizeof(uint16_t)) +
((bytesPerSlice * slice) / sizeof(uint16_t));
// Index in the LUT texture data
size_t idx = (column * kChannels) +
((bytesPerRow * row) / kChannelBytes) +
((bytesPerSlice * slice) / kChannelBytes);
// Vertices the .3dl file are listed first by increasing slice, then row, and finally column coordinate
// This corresponds to blue, green, and red channels, respectively.
int assetIdx = slice + lutSize * row + (lutSize * lutSize * column);
// Vertices the .3dl file are listed first by increasing slice, then row, and finally column coordinate
// This corresponds to blue, green, and red channels, respectively.
size_t assetIdx = slice + lutSize * row + (lutSize * lutSize * column);
AZ::u64 red = lutAsset->m_values[assetIdx * 3 + 0];
AZ::u64 green = lutAsset->m_values[assetIdx * 3 + 1];
AZ::u64 blue = lutAsset->m_values[assetIdx * 3 + 2];
data[idx + 0] = ConvertFloatToHalf(static_cast<float>(red) / normalizeValue);
data[idx + 1] = ConvertFloatToHalf(static_cast<float>(green) / normalizeValue);
data[idx + 2] = ConvertFloatToHalf(static_cast<float>(blue) / normalizeValue);
data[idx + 3] = 0x3b00; // 1.0 in half
}
AZ::u64 red = lutAsset->m_values[assetIdx * 3 + 0];
AZ::u64 green = lutAsset->m_values[assetIdx * 3 + 1];
AZ::u64 blue = lutAsset->m_values[assetIdx * 3 + 2];
// The vertices in the file are given as a positive integer value in [0,..4095] and need to be normalized
constexpr float NormalizeValue = 4095.0f;
u16Buffer[idx + 0] = ConvertFloatToHalf(static_cast<float>(red) / NormalizeValue);
u16Buffer[idx + 1] = ConvertFloatToHalf(static_cast<float>(green) / NormalizeValue);
u16Buffer[idx + 2] = ConvertFloatToHalf(static_cast<float>(blue) / NormalizeValue);
u16Buffer[idx + 3] = 0x3b00; // 1.0 in half
}
}
asset.Release();
Data::Instance<RPI::StreamingImagePool> streamingImagePool = RPI::ImageSystemInterface::Get()->GetSystemStreamingPool();
RHI::Size imageSize;
imageSize.m_width = static_cast<uint32_t>(lutSize);
imageSize.m_height = static_cast<uint32_t>(lutSize);
imageSize.m_depth = static_cast<uint32_t>(lutSize);
size_t imageDataSize = bytesPerSlice * lutSize;
Data::Instance<RPI::StreamingImage> lutStreamingImage = RPI::StreamingImage::CreateFromCpuData(
*streamingImagePool, RHI::ImageDimension::Image3D, imageSize, LutFormat, data, imageDataSize);
AZ_Error("AcesDisplayMapperFeatureProcessor", lutStreamingImage, "Failed to initialize the lut assetId %s.", assetId.ToString<AZStd::string>().c_str());
DisplayMapperAssetLut assetLut;
assetLut.m_lutStreamingImage = lutStreamingImage;
// Add to the list of LUT asset resources
m_assetLuts.insert(AZStd::pair<AZStd::string, DisplayMapperAssetLut>(assetId.ToString<AZStd::string>(), assetLut));
displayMapperAssetLut = assetLut;
}
void AcesDisplayMapperFeatureProcessor::InitializeImagePool()
asset.Release();
Data::Instance<RPI::StreamingImagePool> streamingImagePool = RPI::ImageSystemInterface::Get()->GetSystemStreamingPool();
RHI::Size imageSize;
imageSize.m_width = static_cast<uint32_t>(lutSize);
imageSize.m_height = static_cast<uint32_t>(lutSize);
imageSize.m_depth = static_cast<uint32_t>(lutSize);
size_t imageDataSize = bytesPerSlice * lutSize;
Data::Instance<RPI::StreamingImage> lutStreamingImage = RPI::StreamingImage::CreateFromCpuData(
*streamingImagePool, RHI::ImageDimension::Image3D, imageSize, LutFormat, u16Buffer.data(), imageDataSize);
AZ_Error("AcesDisplayMapperFeatureProcessor", lutStreamingImage, "Failed to initialize the lut assetId %s.", assetId.ToString<AZStd::string>().c_str());
DisplayMapperAssetLut assetLut;
assetLut.m_lutStreamingImage = lutStreamingImage;
// Add to the list of LUT asset resources
m_assetLuts.insert(AZStd::pair<AZStd::string, DisplayMapperAssetLut>(assetId.ToString<AZStd::string>(), assetLut));
displayMapperAssetLut = assetLut;
}
void AcesDisplayMapperFeatureProcessor::InitializeImagePool()
{
AZ::RHI::Factory& factory = RHI::Factory::Get();
m_displayMapperImagePool = factory.CreateImagePool();
m_displayMapperImagePool->SetName(Name("DisplayMapperImagePool"));
RHI::ImagePoolDescriptor imagePoolDesc = {};
imagePoolDesc.m_bindFlags = RHI::ImageBindFlags::ShaderReadWrite;
imagePoolDesc.m_budgetInBytes = ImagePoolBudget;
RHI::Device* device = RHI::RHISystemInterface::Get()->GetDevice();
RHI::ResultCode resultCode = m_displayMapperImagePool->Init(*device, imagePoolDesc);
if (resultCode != RHI::ResultCode::Success)
{
AZ::RHI::Factory& factory = RHI::Factory::Get();
m_displayMapperImagePool = factory.CreateImagePool();
m_displayMapperImagePool->SetName(Name("DisplayMapperImagePool"));
RHI::ImagePoolDescriptor imagePoolDesc = {};
imagePoolDesc.m_bindFlags = RHI::ImageBindFlags::ShaderReadWrite;
imagePoolDesc.m_budgetInBytes = ImagePoolBudget;
RHI::Device* device = RHI::RHISystemInterface::Get()->GetDevice();
RHI::ResultCode resultCode = m_displayMapperImagePool->Init(*device, imagePoolDesc);
if (resultCode != RHI::ResultCode::Success)
{
AZ_Error("AcesDisplayMapperFeatureProcessor", false, "Failed to initialize image pool.");
return;
}
AZ_Error("AcesDisplayMapperFeatureProcessor", false, "Failed to initialize image pool.");
return;
}
}
void AcesDisplayMapperFeatureProcessor::InitializeLutImage(const AZ::Name& lutName)
void AcesDisplayMapperFeatureProcessor::InitializeLutImage(const AZ::Name& lutName)
{
if (!m_displayMapperImagePool)
{
if (!m_displayMapperImagePool)
{
InitializeImagePool();
}
DisplayMapperLut lutResource;
lutResource.m_lutImage = RHI::Factory::Get().CreateImage();
lutResource.m_lutImage->SetName(lutName);
RHI::ImageInitRequest imageRequest;
imageRequest.m_image = lutResource.m_lutImage.get();
imageRequest.m_descriptor = RHI::ImageDescriptor::Create3D(RHI::ImageBindFlags::ShaderReadWrite, LutSize, LutSize, LutSize, LutFormat);
RHI::ResultCode resultCode = m_displayMapperImagePool->InitImage(imageRequest);
if (resultCode != RHI::ResultCode::Success)
{
AZ_Error("AcesDisplayMapperFeatureProcessor", false, "Failed to initialize LUT image.");
return;
}
lutResource.m_lutImageViewDescriptor = RHI::ImageViewDescriptor::Create(LutFormat, 0, 0);
lutResource.m_lutImageView = lutResource.m_lutImage->GetImageView(lutResource.m_lutImageViewDescriptor);
if (!lutResource.m_lutImageView.get())
{
AZ_Error("AcesDisplayMapperFeatureProcessor", false, "Failed to initialize LUT image view.");
return;
}
// Add to the list of lut resources
lutResource.m_lutImageView->SetName(lutName);
m_ownedLuts[lutName] = lutResource;
InitializeImagePool();
}
ShaperParams AcesDisplayMapperFeatureProcessor::GetShaperParameters(ShaperPresetType shaperPreset)
DisplayMapperLut lutResource;
lutResource.m_lutImage = RHI::Factory::Get().CreateImage();
lutResource.m_lutImage->SetName(lutName);
RHI::ImageInitRequest imageRequest;
imageRequest.m_image = lutResource.m_lutImage.get();
static const int LutSize = 32;
imageRequest.m_descriptor = RHI::ImageDescriptor::Create3D(RHI::ImageBindFlags::ShaderReadWrite, LutSize, LutSize, LutSize, LutFormat);
RHI::ResultCode resultCode = m_displayMapperImagePool->InitImage(imageRequest);
if (resultCode != RHI::ResultCode::Success)
{
// Default is a linear shaper with bias 0.0 and scale 1.0. That is, fx = x*1.0 + 0.0
ShaperParams shaperParams = { ShaperType::Linear, 0.0, 1.f };
OutputDeviceTransformType outputDeviceTransformType = OutputDeviceTransformType::NumOutputDeviceTransformTypes;
switch (shaperPreset)
{
case ShaperPresetType::None:
break;
case ShaperPresetType::Log2_48_nits:
outputDeviceTransformType = OutputDeviceTransformType::OutputDeviceTransformType_48Nits;
break;
case ShaperPresetType::Log2_1000_nits:
outputDeviceTransformType = OutputDeviceTransformType::OutputDeviceTransformType_1000Nits;
break;
case ShaperPresetType::Log2_2000_nits:
outputDeviceTransformType = OutputDeviceTransformType::OutputDeviceTransformType_2000Nits;
break;
case ShaperPresetType::Log2_4000_nits:
outputDeviceTransformType = OutputDeviceTransformType::OutputDeviceTransformType_4000Nits;
break;
default:
AZ_Error("DisplayMapperPass", false, "Invalid shaper preset type.");
break;
}
if (outputDeviceTransformType < OutputDeviceTransformType::NumOutputDeviceTransformTypes)
{
shaperParams = GetAcesShaperParameters(outputDeviceTransformType);
}
return shaperParams;
AZ_Error("AcesDisplayMapperFeatureProcessor", false, "Failed to initialize LUT image.");
return;
}
void AcesDisplayMapperFeatureProcessor::GetDefaultDisplayMapperConfiguration(DisplayMapperConfigurationDescriptor& config)
lutResource.m_lutImageViewDescriptor = RHI::ImageViewDescriptor::Create(LutFormat, 0, 0);
lutResource.m_lutImageView = lutResource.m_lutImage->GetImageView(lutResource.m_lutImageViewDescriptor);
if (!lutResource.m_lutImageView.get())
{
// Default configuration is ACES with LDR color grading LUT disabled.
config.m_operationType = DisplayMapperOperationType::Aces;
config.m_ldrGradingLutEnabled = false;
config.m_ldrColorGradingLut.Release();
AZ_Error("AcesDisplayMapperFeatureProcessor", false, "Failed to initialize LUT image view.");
return;
}
void AcesDisplayMapperFeatureProcessor::RegisterDisplayMapperConfiguration(const DisplayMapperConfigurationDescriptor& config)
{
m_displayMapperConfiguration = config;
}
// Add to the list of lut resources
lutResource.m_lutImageView->SetName(lutName);
m_ownedLuts[lutName] = lutResource;
}
DisplayMapperConfigurationDescriptor AcesDisplayMapperFeatureProcessor::GetDisplayMapperConfiguration()
ShaperParams AcesDisplayMapperFeatureProcessor::GetShaperParameters(ShaperPresetType shaperPreset, float customMinEv, float customMaxEv)
{
// Default is a linear shaper with bias 0.0 and scale 1.0. That is, fx = x*1.0 + 0.0
ShaperParams shaperParams = { ShaperType::Linear, 0.0, 1.f };
switch (shaperPreset)
{
return m_displayMapperConfiguration;
case ShaperPresetType::None:
break;
case ShaperPresetType::Log2_48Nits:
shaperParams = GetAcesShaperParameters(OutputDeviceTransformType::OutputDeviceTransformType_48Nits);
break;
case ShaperPresetType::Log2_1000Nits:
shaperParams = GetAcesShaperParameters(OutputDeviceTransformType::OutputDeviceTransformType_1000Nits);
break;
case ShaperPresetType::Log2_2000Nits:
shaperParams = GetAcesShaperParameters(OutputDeviceTransformType::OutputDeviceTransformType_2000Nits);
break;
case ShaperPresetType::Log2_4000Nits:
shaperParams = GetAcesShaperParameters(OutputDeviceTransformType::OutputDeviceTransformType_4000Nits);
break;
case ShaperPresetType::LinearCustomRange:
{
// Map the range min exposure - max exposure to 0-1. Convert EV values to linear values here to avoid that work in the shader.
// Shader equation becomes (x - bias) / scale;
constexpr float MediumGray = 0.18f;
const float minValue = MediumGray * powf(2, customMinEv);
const float maxValue = MediumGray * powf(2, customMaxEv);
shaperParams.m_type = ShaperType::Linear;
shaperParams.m_scale = 1.0f / (maxValue - minValue);
shaperParams.m_bias = -minValue * shaperParams.m_scale;
break;
}
} // namespace Render
} // namespace AZ
case ShaperPresetType::Log2CustomRange:
shaperParams = GetLog2ShaperParameters(customMinEv, customMaxEv);
break;
case ShaperPresetType::PqSmpteSt2084:
shaperParams.m_type = ShaperType::PqSmpteSt2084;
break;
default:
AZ_Error("DisplayMapperPass", false, "Invalid shaper preset type.");
break;
}
return shaperParams;
}
void AcesDisplayMapperFeatureProcessor::GetDefaultDisplayMapperConfiguration(DisplayMapperConfigurationDescriptor& config)
{
// Default configuration is ACES with LDR color grading LUT disabled.
config.m_operationType = DisplayMapperOperationType::Aces;
config.m_ldrGradingLutEnabled = false;
config.m_ldrColorGradingLut.Release();
}
void AcesDisplayMapperFeatureProcessor::RegisterDisplayMapperConfiguration(const DisplayMapperConfigurationDescriptor& config)
{
m_displayMapperConfiguration = config;
}
DisplayMapperConfigurationDescriptor AcesDisplayMapperFeatureProcessor::GetDisplayMapperConfiguration()
{
return m_displayMapperConfiguration;
}
} // namespace AZ::Render
@@ -91,8 +91,8 @@ namespace AZ
m_shaderResourceGroup->SetImageView(m_shaderInputLutImageIndex, m_displayMapperLut.m_lutImageView.get());
}
m_shaderResourceGroup->SetConstant(m_shaderInputShaperBiasIndex, m_shaperParams.bias);
m_shaderResourceGroup->SetConstant(m_shaderInputShaperScaleIndex, m_shaperParams.scale);
m_shaderResourceGroup->SetConstant(m_shaderInputShaperBiasIndex, m_shaperParams.m_bias);
m_shaderResourceGroup->SetConstant(m_shaderInputShaperScaleIndex, m_shaperParams.m_scale);
}
BindPassSrg(context, m_shaderResourceGroup);
@@ -109,9 +109,9 @@ namespace AZ
{
m_shaderResourceGroup->SetImageView(m_shaderInputLutImageIndex, m_lutResource.m_lutStreamingImage->GetImageView());
m_shaderResourceGroup->SetConstant(m_shaderShaperTypeIndex, m_shaperParams.type);
m_shaderResourceGroup->SetConstant(m_shaderShaperBiasIndex, m_shaperParams.bias);
m_shaderResourceGroup->SetConstant(m_shaderShaperScaleIndex, m_shaperParams.scale);
m_shaderResourceGroup->SetConstant(m_shaderShaperTypeIndex, m_shaperParams.m_type);
m_shaderResourceGroup->SetConstant(m_shaderShaperBiasIndex, m_shaperParams.m_bias);
m_shaderResourceGroup->SetConstant(m_shaderShaperScaleIndex, m_shaperParams.m_scale);
}
}
}
@@ -94,8 +94,8 @@ namespace AZ
m_shaderResourceGroup->SetImageView(m_shaderInputLutImageIndex, m_displayMapperLut.m_lutImageView.get());
m_shaderResourceGroup->SetConstant(m_shaderInputShaperBiasIndex, m_shaperParams.bias);
m_shaderResourceGroup->SetConstant(m_shaderInputShaperScaleIndex, m_shaperParams.scale);
m_shaderResourceGroup->SetConstant(m_shaderInputShaperBiasIndex, m_shaperParams.m_bias);
m_shaderResourceGroup->SetConstant(m_shaderInputShaperScaleIndex, m_shaperParams.m_scale);
}
BindPassSrg(context, m_shaderResourceGroup);
@@ -26,6 +26,8 @@ namespace AZ
seed = TypeHash64(m_overrideStrength, seed);
seed = TypeHash64(m_assetId.GetId(), seed);
seed = TypeHash64(m_shaperPreset, seed);
seed = TypeHash64(m_customMinExposure, seed);
seed = TypeHash64(m_customMaxExposure, seed);
return seed;
}
@@ -50,6 +52,9 @@ namespace AZ
lutBlend.m_intensity = GetColorGradingLutIntensity();
lutBlend.m_overrideStrength = GetColorGradingLutOverride() * alpha;
lutBlend.m_assetId = lutAssetId;
lutBlend.m_shaperPreset = GetShaperPresetType();
lutBlend.m_customMinExposure = GetCustomMinExposure();
lutBlend.m_customMaxExposure = GetCustomMaxExposure();
target->AddLutBlend(lutBlend);
}
}
@@ -87,6 +92,9 @@ namespace AZ
blendItem.m_intensity = GetColorGradingLutIntensity();
blendItem.m_overrideStrength = GetColorGradingLutOverride();
blendItem.m_assetId = GetColorGradingLut();
blendItem.m_shaperPreset = GetShaperPresetType();
blendItem.m_customMinExposure = GetCustomMinExposure();
blendItem.m_customMaxExposure = GetCustomMaxExposure();
m_lutBlendStack.insert(m_lutBlendStack.begin(), blendItem);
}
}
@@ -33,7 +33,10 @@ namespace AZ
//! Asset ID of LUT
Data::Asset<RPI::AnyAsset> m_assetId;
//! Shaper preset type
ShaperPresetType m_shaperPreset = AZ::Render::ShaperPresetType::Log2_48_nits;
ShaperPresetType m_shaperPreset = AZ::Render::ShaperPresetType::Log2_48Nits;
//! When shaper preset is custom, these values set min and max exposure.
float m_customMinExposure = -6.5;
float m_customMaxExposure = 6.5;
HashValue64 GetHash(HashValue64 seed) const;
};
@@ -21,7 +21,7 @@ namespace AZ
namespace Render
{
static const char* const NumSourceLutsShaderVariantOptionName{ "o_numSourceLuts" };
RPI::Ptr<BlendColorGradingLutsPass> BlendColorGradingLutsPass::Create(const RPI::PassDescriptor& descriptor)
{
RPI::Ptr<BlendColorGradingLutsPass> pass = aznew BlendColorGradingLutsPass(descriptor);
@@ -151,9 +151,9 @@ namespace AZ
{
m_shaderResourceGroup->SetImageView(m_shaderInputBlendedLutImageIndex, m_blendedLut.m_lutImageView.get());
m_shaderResourceGroup->SetConstant(m_shaderInputBlendedLutDimensionsIndex, m_blendedLutDimensions);
m_shaderResourceGroup->SetConstant(m_shaderInputBlendedLutShaperTypeIndex, m_blendedLutShaperParams.type);
m_shaderResourceGroup->SetConstant(m_shaderInputBlendededLutShaperBiasIndex, m_blendedLutShaperParams.bias);
m_shaderResourceGroup->SetConstant(m_shaderInputBlendededLutShaperScaleIndex, m_blendedLutShaperParams.scale);
m_shaderResourceGroup->SetConstant(m_shaderInputBlendedLutShaperTypeIndex, m_blendedLutShaperParams.m_type);
m_shaderResourceGroup->SetConstant(m_shaderInputBlendededLutShaperBiasIndex, m_blendedLutShaperParams.m_bias);
m_shaderResourceGroup->SetConstant(m_shaderInputBlendededLutShaperScaleIndex, m_blendedLutShaperParams.m_scale);
m_shaderResourceGroup->SetConstant(m_shaderInputWeight0Index, m_weights[0]);
m_shaderResourceGroup->SetConstant(m_shaderInputWeight1Index, m_weights[1]);
m_shaderResourceGroup->SetConstant(m_shaderInputWeight2Index, m_weights[2]);
@@ -163,33 +163,33 @@ namespace AZ
if (m_colorGradingLuts[0].m_lutStreamingImage)
{
m_shaderResourceGroup->SetImageView(m_shaderInputSourceLut1ImageIndex, m_colorGradingLuts[0].m_lutStreamingImage->GetImageView());
m_shaderResourceGroup->SetConstant(m_shaderInputSourceLut1ShaperTypeIndex, m_colorGradingShaperParams[0].type);
m_shaderResourceGroup->SetConstant(m_shaderInputSourceLut1ShaperBiasIndex, m_colorGradingShaperParams[0].bias);
m_shaderResourceGroup->SetConstant(m_shaderInputSourceLut1ShaperScaleIndex, m_colorGradingShaperParams[0].scale);
m_shaderResourceGroup->SetConstant(m_shaderInputSourceLut1ShaperTypeIndex, m_colorGradingShaperParams[0].m_type);
m_shaderResourceGroup->SetConstant(m_shaderInputSourceLut1ShaperBiasIndex, m_colorGradingShaperParams[0].m_bias);
m_shaderResourceGroup->SetConstant(m_shaderInputSourceLut1ShaperScaleIndex, m_colorGradingShaperParams[0].m_scale);
}
if (m_colorGradingLuts[1].m_lutStreamingImage)
{
m_shaderResourceGroup->SetImageView(m_shaderInputSourceLut2ImageIndex, m_colorGradingLuts[1].m_lutStreamingImage->GetImageView());
m_shaderResourceGroup->SetConstant(m_shaderInputSourceLut2ShaperTypeIndex, m_colorGradingShaperParams[1].type);
m_shaderResourceGroup->SetConstant(m_shaderInputSourceLut2ShaperBiasIndex, m_colorGradingShaperParams[1].bias);
m_shaderResourceGroup->SetConstant(m_shaderInputSourceLut2ShaperScaleIndex, m_colorGradingShaperParams[1].scale);
m_shaderResourceGroup->SetConstant(m_shaderInputSourceLut2ShaperTypeIndex, m_colorGradingShaperParams[1].m_type);
m_shaderResourceGroup->SetConstant(m_shaderInputSourceLut2ShaperBiasIndex, m_colorGradingShaperParams[1].m_bias);
m_shaderResourceGroup->SetConstant(m_shaderInputSourceLut2ShaperScaleIndex, m_colorGradingShaperParams[1].m_scale);
}
if (m_colorGradingLuts[2].m_lutStreamingImage)
{
m_shaderResourceGroup->SetImageView(m_shaderInputSourceLut3ImageIndex, m_colorGradingLuts[2].m_lutStreamingImage->GetImageView());
m_shaderResourceGroup->SetConstant(m_shaderInputSourceLut3ShaperTypeIndex, m_colorGradingShaperParams[2].type);
m_shaderResourceGroup->SetConstant(m_shaderInputSourceLut3ShaperBiasIndex, m_colorGradingShaperParams[2].bias);
m_shaderResourceGroup->SetConstant(m_shaderInputSourceLut3ShaperScaleIndex, m_colorGradingShaperParams[2].scale);
m_shaderResourceGroup->SetConstant(m_shaderInputSourceLut3ShaperTypeIndex, m_colorGradingShaperParams[2].m_type);
m_shaderResourceGroup->SetConstant(m_shaderInputSourceLut3ShaperBiasIndex, m_colorGradingShaperParams[2].m_bias);
m_shaderResourceGroup->SetConstant(m_shaderInputSourceLut3ShaperScaleIndex, m_colorGradingShaperParams[2].m_scale);
}
if (m_colorGradingLuts[3].m_lutStreamingImage)
{
m_shaderResourceGroup->SetImageView(m_shaderInputSourceLut4ImageIndex, m_colorGradingLuts[3].m_lutStreamingImage->GetImageView());
m_shaderResourceGroup->SetConstant(m_shaderInputSourceLut4ShaperTypeIndex, m_colorGradingShaperParams[3].type);
m_shaderResourceGroup->SetConstant(m_shaderInputSourceLut4ShaperBiasIndex, m_colorGradingShaperParams[3].bias);
m_shaderResourceGroup->SetConstant(m_shaderInputSourceLut4ShaperScaleIndex, m_colorGradingShaperParams[3].scale);
m_shaderResourceGroup->SetConstant(m_shaderInputSourceLut4ShaperTypeIndex, m_colorGradingShaperParams[3].m_type);
m_shaderResourceGroup->SetConstant(m_shaderInputSourceLut4ShaperBiasIndex, m_colorGradingShaperParams[3].m_bias);
m_shaderResourceGroup->SetConstant(m_shaderInputSourceLut4ShaperScaleIndex, m_colorGradingShaperParams[3].m_scale);
}
if (m_shaderResourceGroup->HasShaderVariantKeyFallbackEntry())
@@ -244,7 +244,170 @@ namespace AZ
m_blendedLutShaperParams = shaperParams;
}
AZStd::optional<ShaperParams> BlendColorGradingLutsPass::GetCommonShaperParams() const
{
LookModificationSettings* settings = GetLookModificationSettings();
if (settings)
{
settings->PrepareLutBlending();
ShaperPresetType type = ShaperPresetType::NumShaperTypes;
float customMinExposure = 0.0;
float customMaxExposure = 0.0;
for (size_t lutIndex = 0; lutIndex < settings->GetLutBlendStackSize(); lutIndex++)
{
LutBlendItem& lutBlendItem = settings->GetLutBlendItem(lutIndex);
if (lutIndex == 0)
{
type = lutBlendItem.m_shaperPreset;
customMinExposure = lutBlendItem.m_customMinExposure;
customMaxExposure = lutBlendItem.m_customMaxExposure;
}
else if (type != lutBlendItem.m_shaperPreset)
{
// Shapers are different
return AZStd::nullopt;
}
else if (type == ShaperPresetType::LinearCustomRange || type == ShaperPresetType::Log2CustomRange)
{
if (lutBlendItem.m_customMinExposure != customMinExposure ||
lutBlendItem.m_customMaxExposure != customMaxExposure)
{
// Shapers are same, but custom exposure for custom type is different.
return AZStd::nullopt;
}
}
}
// Only calculate shaper params when there's at least one lut blend.
if (settings->GetLutBlendStackSize() > 0)
{
return AcesDisplayMapperFeatureProcessor::GetShaperParameters(type, customMinExposure, customMaxExposure);
}
}
return AZStd::nullopt;
}
void BlendColorGradingLutsPass::CheckLutBlendSettings()
{
LookModificationSettings* settings = GetLookModificationSettings();
if (settings)
{
settings->PrepareLutBlending();
// Early out if the settings have not chanced
HashValue64 hash = settings->GetHash();
if (hash == m_lutBlendHash)
{
return;
}
m_lutBlendHash = hash;
m_needToUpdateLut = true;
// Calculate all the weights and LUT assets and check if there has been a change
// Only the top N LUTs will be blended where N = LookModificationSettings::MaxBlendLuts
// Weight 0 is used for the base color, and the other weights are for the LUTs in increasing priority
size_t numLuts = settings->GetLutBlendStackSize();
float intensity[LookModificationSettings::MaxBlendLuts];
float one_intensity[LookModificationSettings::MaxBlendLuts];
float over[LookModificationSettings::MaxBlendLuts];
float one_over[LookModificationSettings::MaxBlendLuts];
for (int curLutIndex = 0; curLutIndex < LookModificationSettings::MaxBlendLuts; curLutIndex++)
{
intensity[curLutIndex] = 0.f;
one_intensity[curLutIndex] = 1.f;
over[curLutIndex] = 0.f;
one_over[curLutIndex] = 1.f;
}
int current = 0;
for (size_t lutIndex = 0; lutIndex < numLuts; lutIndex++)
{
LutBlendItem& lutBlendItem = settings->GetLutBlendItem(lutIndex);
const auto assetId = lutBlendItem.m_assetId.GetId();
if (assetId.IsValid())
{
AcesDisplayMapperFeatureProcessor* dmfp = GetScene()->GetFeatureProcessor<AcesDisplayMapperFeatureProcessor>();
dmfp->GetLutFromAssetId(m_colorGradingLuts[current], assetId);
if (!m_colorGradingLuts[current].m_lutStreamingImage)
{
AZ_Warning("BlendColorGradingLutsPass", false, "Unable to load grading LUT from asset %s",
lutBlendItem.m_assetId.ToString<AZStd::string>().c_str());
// Skip this LUT
continue;
}
}
intensity[current] = lutBlendItem.m_intensity;
one_intensity[current] = 1.0f - lutBlendItem.m_intensity;
over[current] = lutBlendItem.m_overrideStrength;
one_over[current] = 1.0f - lutBlendItem.m_overrideStrength;
m_colorGradingShaperParams[current] = AcesDisplayMapperFeatureProcessor::GetShaperParameters(
lutBlendItem.m_shaperPreset,
lutBlendItem.m_customMinExposure,
lutBlendItem.m_customMaxExposure
);
++current;
if (current == LookModificationSettings::MaxBlendLuts)
{
break;
}
}
m_weights[0] = 0.f;
// Handle the case where there are no LUTs to be blended, and hence an identity LUT will be generated
if (current == 0)
{
m_weights[0] = 1.f;
// These weights would not be used in the shader in this case, but setting to zero anyways.
for (int lutIndex = 1; lutIndex < LookModificationSettings::MaxBlendLuts + 1; lutIndex++)
{
m_weights[lutIndex] = 0.f;
}
}
else
{
// Compute all the weights
// First compute the weight of the ungraded color value
for (int lutIndex = 0; lutIndex < current; lutIndex++)
{
float weight = one_intensity[lutIndex] * over[lutIndex];
for (int overrideLutIndex = lutIndex + 1; overrideLutIndex < LookModificationSettings::MaxBlendLuts; overrideLutIndex++)
{
weight *= one_over[overrideLutIndex];
}
m_weights[0] += weight;
}
// Then compute the weights for the LUTs
for (int weightIndex = 0; weightIndex < current; weightIndex++)
{
m_weights[weightIndex + 1] = intensity[weightIndex] * over[weightIndex];
for (int lutIndex = weightIndex + 1; lutIndex < LookModificationSettings::MaxBlendLuts; lutIndex++)
{
m_weights[weightIndex + 1] *= one_over[lutIndex];
}
}
}
// If the number of source LUTs have changed, the shader variant will need to be updated
if (m_numSourceLuts != current)
{
m_numSourceLuts = current;
m_needToUpdateShaderVariant = true;
}
}
}
LookModificationSettings* BlendColorGradingLutsPass::GetLookModificationSettings() const
{
AZ::RPI::Scene* scene = GetScene();
if (scene)
@@ -259,109 +422,12 @@ namespace AZ
LookModificationSettings* settings = postProcessSettings->GetLookModificationSettings();
if (settings)
{
settings->PrepareLutBlending();
// Early out if the settings have not chanced
HashValue64 hash = settings->GetHash();
if (hash == m_lutBlendHash)
{
return;
}
m_lutBlendHash = hash;
m_needToUpdateLut = true;
// Calculate all the weights and LUT assets and check if there has been a change
// Only the top N LUTs will be blended where N = LookModificationSettings::MaxBlendLuts
// Weight 0 is used for the base color, and the other weights are for the LUTs in increasing priority
size_t numLuts = settings->GetLutBlendStackSize();
float intensity[LookModificationSettings::MaxBlendLuts];
float one_intensity[LookModificationSettings::MaxBlendLuts];
float over[LookModificationSettings::MaxBlendLuts];
float one_over[LookModificationSettings::MaxBlendLuts];
for (int curLutIndex = 0; curLutIndex < LookModificationSettings::MaxBlendLuts; curLutIndex++)
{
intensity[curLutIndex] = 0.f;
one_intensity[curLutIndex] = 1.f;
over[curLutIndex] = 0.f;
one_over[curLutIndex] = 1.f;
}
int current = 0;
for (size_t lutIndex = 0; lutIndex < numLuts; lutIndex++)
{
LutBlendItem& lutBlendItem = settings->GetLutBlendItem(lutIndex);
auto assetId = lutBlendItem.m_assetId.GetId();
if (assetId.IsValid())
{
AcesDisplayMapperFeatureProcessor* dmfp = scene->GetFeatureProcessor<AcesDisplayMapperFeatureProcessor>();
dmfp->GetLutFromAssetId(m_colorGradingLuts[lutIndex], assetId);
if (!m_colorGradingLuts[lutIndex].m_lutStreamingImage)
{
AZ_Warning("BlendColorGradingLutsPass", false, "Unable to load grading LUT from asset %s", lutBlendItem.m_assetId.ToString<AZStd::string>().c_str());
// Skip this LUT
continue;
}
}
intensity[current] = lutBlendItem.m_intensity;
one_intensity[current] = 1.f - intensity[lutIndex];
over[current] = lutBlendItem.m_overrideStrength;
one_over[current] = 1.f - over[lutIndex];
m_colorGradingLutAssets[current] = lutBlendItem.m_assetId;
m_colorGradingShaperPresets[current] = lutBlendItem.m_shaperPreset;
m_colorGradingShaperParams[current] = AcesDisplayMapperFeatureProcessor::GetShaperParameters(m_colorGradingShaperPresets[lutIndex]);
current++;
if (current == LookModificationSettings::MaxBlendLuts)
{
break;
}
}
m_weights[0] = 0.f;
// Handle the case where there are no LUTs to be blended, and hence an identity LUT will be generated
if (current == 0)
{
m_weights[0] = 1.f;
// These weights would not be used in the shader in this case, but setting to zero anyways.
for (int lutIndex = 1; lutIndex < LookModificationSettings::MaxBlendLuts + 1; lutIndex++)
{
m_weights[lutIndex] = 0.f;
}
}
else
{
// Compute all the weights
// First compute the weight of the ungraded color value
for (int lutIndex = 0; lutIndex < LookModificationSettings::MaxBlendLuts; lutIndex++)
{
float weight = one_intensity[lutIndex] * over[lutIndex];
for (int overrideLutIndex = lutIndex + 1; overrideLutIndex < LookModificationSettings::MaxBlendLuts; overrideLutIndex++)
{
weight *= one_over[overrideLutIndex];
}
m_weights[0] += weight;
}
// Then compute the weights for the LUTs
for (int weightIndex = 0; weightIndex < LookModificationSettings::MaxBlendLuts; weightIndex++)
{
m_weights[weightIndex + 1] = intensity[weightIndex] * over[weightIndex];
for (int lutIndex = weightIndex + 1; lutIndex < LookModificationSettings::MaxBlendLuts; lutIndex++)
{
m_weights[weightIndex + 1] *= one_over[lutIndex];
}
}
}
// If the number of source LUTs have changed, the shader variant will need to be updated
if (m_numSourceLuts != current)
{
m_numSourceLuts = current;
m_needToUpdateShaderVariant = true;
}
return settings;
}
}
}
}
return nullptr;
}
} // namespace Render
} // namespace AZ
@@ -47,6 +47,7 @@ namespace AZ
static RPI::Ptr<BlendColorGradingLutsPass> Create(const RPI::PassDescriptor& descriptor);
void SetShaperParameters(const ShaperParams& shaperParams);
AZStd::optional<ShaperParams> GetCommonShaperParams() const;
private:
explicit BlendColorGradingLutsPass(const RPI::PassDescriptor& descriptor);
@@ -66,6 +67,7 @@ namespace AZ
void ReleaseLutImage();
void CheckLutBlendSettings();
LookModificationSettings* GetLookModificationSettings() const;
bool m_resourcesInitialized = false;
@@ -111,8 +113,6 @@ namespace AZ
AZStd::array<u32, 3> m_blendedLutDimensions;
float m_weights[LookModificationSettings::MaxBlendLuts + 1]; // The first index is reserved for the weight of the non color graded value
Data::Asset<RPI::AnyAsset> m_colorGradingLutAssets[LookModificationSettings::MaxBlendLuts];
ShaperPresetType m_colorGradingShaperPresets[LookModificationSettings::MaxBlendLuts];
Render::ShaperParams m_colorGradingShaperParams[LookModificationSettings::MaxBlendLuts];
Render::DisplayMapperAssetLut m_colorGradingLuts[LookModificationSettings::MaxBlendLuts];
@@ -9,11 +9,15 @@
#include <Atom/RPI.Public/RenderPipeline.h>
#include <Atom/RPI.Public/View.h>
#include <Atom/RPI.Public/Scene.h>
#include <Atom/RPI.Public/Pass/PassFilter.h>
#include <Atom/RPI.Public/Pass/PassSystem.h>
#include <Atom/RPI.Public/Shader/ShaderVariant.h>
#include <Atom/RHI/FrameScheduler.h>
#include <Atom/RHI/PipelineState.h>
#include <AzCore/Console/Console.h>
#include <PostProcessing/LookModificationCompositePass.h>
#include <PostProcess/PostProcessFeatureProcessor.h>
#include <PostProcess/ExposureControl/ExposureControlSettings.h>
@@ -22,6 +26,22 @@ namespace AZ
{
namespace Render
{
AZ_CVAR(uint8_t,
r_lutSampleQuality,
0,
[](const uint8_t& value)
{
auto passes = RPI::PassSystem::Get()->FindPasses(RPI::PassClassFilter<LookModificationCompositePass>());
for (auto* pass : passes)
{
LookModificationCompositePass* lookModPass = azrtti_cast<LookModificationCompositePass*>(pass);
lookModPass->SetSampleQuality(LookModificationCompositePass::SampleQuality(value));
}
},
ConsoleFunctorFlags::Null,
"This can be increased to deal with particularly tricky luts. Range (0-2). 0 (default) - Standard linear sampling. 1 - 7 tap b-spline sampling. 2 - 19 tap b-spline sampling."
);
RPI::Ptr<LookModificationCompositePass> LookModificationCompositePass::Create(const RPI::PassDescriptor& descriptor)
{
RPI::Ptr<LookModificationCompositePass> pass = aznew LookModificationCompositePass(descriptor);
@@ -30,8 +50,6 @@ namespace AZ
LookModificationCompositePass::LookModificationCompositePass(const RPI::PassDescriptor& descriptor)
: AZ::RPI::FullscreenTrianglePass(descriptor)
, m_exposureShaderVariantOptionName(ExposureShaderVariantOptionName)
, m_colorGradingShaderVariantOptionName(ColorGradingShaderVariantOptionName)
{
}
@@ -60,19 +78,38 @@ namespace AZ
{
AZ_Assert(m_shader != nullptr, "LookModificationCompositePass %s has a null shader when calling InitializeShaderVariant.", GetPathName().GetCStr());
AZStd::vector<AZ::Name> exposureVariationTypes = { AZ::Name("true"), AZ::Name("false") };
AZStd::vector<AZ::Name> colorGradingVariationTypes = { AZ::Name("true"), AZ::Name("false") };
struct OptionSettings
{
AZ::Name m_enableExposureControl;
AZ::Name m_enableColorGrading;
RPI::ShaderOptionValue m_lutSampleQuality;
auto exposureVariationTypeCount = exposureVariationTypes.size();
auto totalVariationCount = exposureVariationTypes.size() * colorGradingVariationTypes.size();
OptionSettings(const char* enableExposureControl, const char* enableColorGrading, SampleQuality sampleQuality)
: m_enableExposureControl(Name(enableExposureControl))
, m_enableColorGrading(Name(enableColorGrading))
, m_lutSampleQuality(RPI::ShaderOptionValue(sampleQuality))
{}
};
AZStd::vector<OptionSettings> options =
{
{ "false", "false", SampleQuality::Linear },
{ "true", "false", SampleQuality::Linear },
{ "false", "true", SampleQuality::Linear },
{ "false", "true", SampleQuality::BSpline7Tap },
{ "false", "true", SampleQuality::BSpline19Tap },
{ "true", "true", SampleQuality::Linear },
{ "true", "true", SampleQuality::BSpline7Tap },
{ "true", "true", SampleQuality::BSpline19Tap },
};
// Caching all pipeline state for each shader variation for performance reason.
for (auto shaderVariantIndex = 0; shaderVariantIndex < totalVariationCount; ++shaderVariantIndex)
for (auto shaderVariantIndex = 0; shaderVariantIndex < options.size(); ++shaderVariantIndex)
{
auto shaderOption = m_shader->CreateShaderOptionGroup();
shaderOption.SetValue(m_exposureShaderVariantOptionName, exposureVariationTypes[shaderVariantIndex % exposureVariationTypeCount]);
shaderOption.SetValue(m_colorGradingShaderVariantOptionName, colorGradingVariationTypes[shaderVariantIndex / exposureVariationTypeCount]);
shaderOption.SetValue(m_exposureShaderVariantOptionName, options.at(shaderVariantIndex).m_enableExposureControl);
shaderOption.SetValue(m_colorGradingShaderVariantOptionName, options.at(shaderVariantIndex).m_enableColorGrading);
shaderOption.SetValue(m_lutSampleQualityShaderVariantOptionName, options.at(shaderVariantIndex).m_lutSampleQuality);
PreloadShaderVariant(m_shader, shaderOption, GetRenderAttachmentConfiguration(), GetMultisampleState());
}
@@ -173,9 +210,9 @@ namespace AZ
{
m_shaderResourceGroup->SetImageView(m_shaderColorGradingLutImageIndex, m_blendedColorGradingLut.m_lutImageView.get());
m_shaderResourceGroup->SetConstant(m_shaderColorGradingShaperTypeIndex, m_colorGradingShaperParams.type);
m_shaderResourceGroup->SetConstant(m_shaderColorGradingShaperBiasIndex, m_colorGradingShaperParams.bias);
m_shaderResourceGroup->SetConstant(m_shaderColorGradingShaperScaleIndex, m_colorGradingShaperParams.scale);
m_shaderResourceGroup->SetConstant(m_shaderColorGradingShaperTypeIndex, m_colorGradingShaperParams.m_type);
m_shaderResourceGroup->SetConstant(m_shaderColorGradingShaperBiasIndex, m_colorGradingShaperParams.m_bias);
m_shaderResourceGroup->SetConstant(m_shaderColorGradingShaperScaleIndex, m_colorGradingShaperParams.m_scale);
}
}
@@ -192,7 +229,8 @@ namespace AZ
// Decide which shader to use.
shaderOption.SetValue(m_exposureShaderVariantOptionName, m_exposureControlEnabled ? AZ::Name("true") : AZ::Name("false"));
shaderOption.SetValue(m_colorGradingShaderVariantOptionName, m_colorGradingLutEnabled ? AZ::Name("true") : AZ::Name("false"));
shaderOption.SetValue(m_lutSampleQualityShaderVariantOptionName, RPI::ShaderOptionValue(m_sampleQuality));
UpdateShaderVariant(shaderOption);
m_needToUpdateShaderVariant = false;
@@ -218,5 +256,12 @@ namespace AZ
{
m_colorGradingShaperParams = shaperParams;
}
void LookModificationCompositePass::SetSampleQuality(SampleQuality sampleQuality)
{
m_sampleQuality = sampleQuality;
m_needToUpdateShaderVariant = true;
}
} // namespace Render
} // namespace AZ
@@ -30,8 +30,6 @@ namespace AZ
namespace Render
{
static const char* const LookModificationTransformPassTemplateName{ "LookModificationTransformTemplate" };
static const char* const ExposureShaderVariantOptionName{ "o_enableExposureControlFeature" };
static const char* const ColorGradingShaderVariantOptionName{ "o_enableColorGradingLut" };
/**
* The look modification composite pass. If color grading LUTs are enabled, this pass will apply the blended LUT.
@@ -43,6 +41,14 @@ namespace AZ
public:
AZ_RTTI(LookModificationCompositePass, "{D7DF3E8A-B642-4D51-ABC2-ADB2B60FCE1D}", AZ::RPI::FullscreenTrianglePass);
AZ_CLASS_ALLOCATOR(LookModificationCompositePass, SystemAllocator, 0);
enum class SampleQuality : uint8_t
{
Linear = 0,
BSpline7Tap = 1,
BSpline19Tap = 2,
};
virtual ~LookModificationCompositePass() = default;
//! Creates a LookModificationPass
@@ -54,6 +60,8 @@ namespace AZ
//! Set shaper parameters
void SetShaperParameters(const ShaperParams& shaperParams);
void SetSampleQuality(SampleQuality sampleQuality);
protected:
LookModificationCompositePass(const RPI::PassDescriptor& descriptor);
@@ -76,11 +84,15 @@ namespace AZ
bool m_exposureControlEnabled = false;
bool m_colorGradingLutEnabled = false;
SampleQuality m_sampleQuality = SampleQuality::Linear;
Render::DisplayMapperLut m_blendedColorGradingLut;
Render::ShaperParams m_colorGradingShaperParams;
const AZ::Name m_exposureShaderVariantOptionName;
const AZ::Name m_colorGradingShaderVariantOptionName;
const AZ::Name m_exposureShaderVariantOptionName{ "o_enableExposureControlFeature" };
const AZ::Name m_colorGradingShaderVariantOptionName{ "o_enableColorGradingLut" };
const AZ::Name m_lutSampleQualityShaderVariantOptionName{ "o_lutSampleQuality" };
bool m_needToUpdateShaderVariant = true;
RHI::ShaderInputNameIndex m_shaderColorGradingLutImageIndex = "m_gradingLut";
@@ -47,29 +47,32 @@ namespace AZ
swapChainFormat = m_swapChainAttachmentBinding->m_attachment->GetTransientImageDescriptor().m_imageDescriptor.m_format;
}
if (m_displayBufferFormat != swapChainFormat)
// Update the children passes
RPI::Ptr<BlendColorGradingLutsPass> blendPass = FindChildPass<BlendColorGradingLutsPass>();
if (blendPass)
{
m_displayBufferFormat = swapChainFormat;
m_outputDeviceTransformType = AcesDisplayMapperFeatureProcessor::GetOutputDeviceTransformType(m_displayBufferFormat);
m_shaperParams = GetAcesShaperParameters(m_outputDeviceTransformType);
// Update the children passes
for (const AZ::RPI::Ptr<Pass>& child : m_children)
auto commonShaperParams = blendPass->GetCommonShaperParams();
if (commonShaperParams)
{
BlendColorGradingLutsPass* blendPass = azrtti_cast<BlendColorGradingLutsPass*>(child.get());
if (blendPass)
{
blendPass->SetShaperParameters(m_shaperParams);
continue;
}
LookModificationCompositePass* compositePass = azrtti_cast<LookModificationCompositePass*>(child.get());
if (compositePass)
{
compositePass->SetShaperParameters(m_shaperParams);
continue;
}
m_shaperParams = *commonShaperParams;
}
else
{
// Mix of shapers used, so shape them based on the output transform type.
m_displayBufferFormat = swapChainFormat;
m_outputDeviceTransformType = AcesDisplayMapperFeatureProcessor::GetOutputDeviceTransformType(m_displayBufferFormat);
m_shaperParams = GetAcesShaperParameters(m_outputDeviceTransformType);
}
blendPass->SetShaperParameters(m_shaperParams);
RPI::Ptr<LookModificationCompositePass> compositePass = FindChildPass<LookModificationCompositePass>();
if (compositePass)
{
compositePass->SetShaperParameters(m_shaperParams);
}
}
ParentPass::FrameBeginInternal(params);
}
} // namespace Render
@@ -64,6 +64,9 @@ namespace AZ
//! Find a child pass with a matching name and returns it. Return nullptr if none found.
Ptr<Pass> FindChildPass(const Name& passName) const;
template<typename PassType>
Ptr<PassType> FindChildPass() const;
//! Searches the tree for the first pass that has same pass name (Depth-first search). Return nullptr if none found.
Ptr<Pass> FindPassByNameRecursive(const Name& passName) const;
@@ -132,5 +135,20 @@ namespace AZ
// Generates child passes from source PassTemplate
void CreatePassesFromTemplate();
};
template<typename PassType>
inline Ptr<PassType> ParentPass::FindChildPass() const
{
for (const Ptr<Pass>& child : m_children)
{
PassType* pass = azrtti_cast<PassType*>(child.get());
if (pass)
{
return pass;
}
}
return {};
}
} // namespace RPI
} // namespace AZ
@@ -39,6 +39,11 @@ namespace AZ
void CopySettingsTo(LookModificationSettingsInterface* settings);
bool ArePropertiesReadOnly() const { return !m_enabled; }
bool IsUsingCustomShaper() const {
return m_shaperPresetType == ShaperPresetType::LinearCustomRange
|| m_shaperPresetType == ShaperPresetType::Log2CustomRange;
}
};
}
}
@@ -48,33 +48,44 @@ namespace AZ
&LookModificationComponentConfig::m_enabled,
"Enable look modification",
"Enable look modification.")
->Attribute(Edit::Attributes::ChangeNotify, Edit::PropertyRefreshLevels::ValuesOnly)
->Attribute(Edit::Attributes::ChangeNotify, Edit::PropertyRefreshLevels::ValuesOnly)
->DataElement(AZ::Edit::UIHandlers::Default, &LookModificationComponentConfig::m_colorGradingLut, "Color Grading LUT", "Color grading LUT")
->ClassElement(Edit::ClassElements::EditorData, "")
->DataElement(Edit::UIHandlers::ComboBox,
&LookModificationComponentConfig::m_shaperPresetType,
"Shaper Type",
"Shaper Type.")
->EnumAttribute(ShaperPresetType::None, "None")
->EnumAttribute(ShaperPresetType::Log2_48_nits, "Log2_48_nits")
->EnumAttribute(ShaperPresetType::Log2_1000_nits, "Log2_1000_nits")
->EnumAttribute(ShaperPresetType::Log2_2000_nits, "Log2_2000_nits")
->EnumAttribute(ShaperPresetType::Log2_4000_nits, "Log2_4000_nits")
->Attribute(Edit::Attributes::ChangeNotify, Edit::PropertyRefreshLevels::ValuesOnly)
&LookModificationComponentConfig::m_shaperPresetType, "Shaper Type", "Shaper Type.")
->EnumAttribute(ShaperPresetType::None, "None")
->EnumAttribute(ShaperPresetType::LinearCustomRange, "Linear Custom Range")
->EnumAttribute(ShaperPresetType::Log2_48Nits, "Log2 48 nits")
->EnumAttribute(ShaperPresetType::Log2_1000Nits, "Log2 1000 nits")
->EnumAttribute(ShaperPresetType::Log2_2000Nits, "Log2 2000 nits")
->EnumAttribute(ShaperPresetType::Log2_4000Nits, "Log2 4000 nits")
->EnumAttribute(ShaperPresetType::Log2CustomRange, "Log2 Custom Range")
->EnumAttribute(ShaperPresetType::PqSmpteSt2084, "PQ (SMPTE ST 2084)")
->Attribute(Edit::Attributes::ChangeNotify, Edit::PropertyRefreshLevels::EntireTree)
->DataElement(AZ::Edit::UIHandlers::Slider, &LookModificationComponentConfig::m_customMinExposure, "Minimum Exposure", "The minimum exposure this LUT supports. Values smaller than this will be clamped to 0.")
->Attribute(AZ::Edit::Attributes::Min, -50.0f)
->Attribute(AZ::Edit::Attributes::Max, 0.0f)
->Attribute(AZ::Edit::Attributes::SoftMin, -20.0f)
->Attribute(AZ::Edit::Attributes::SoftMax, 0.0f)
->Attribute(Edit::Attributes::Visibility, &LookModificationComponentConfig::IsUsingCustomShaper)
->Attribute(AZ::Edit::Attributes::ChangeNotify, Edit::PropertyRefreshLevels::ValuesOnly)
->DataElement(AZ::Edit::UIHandlers::Slider, &LookModificationComponentConfig::m_customMaxExposure, "Maximum Exposure", "The maximum exposure this LUT supports. Values larger than this will be clamped.")
->Attribute(AZ::Edit::Attributes::Min, 0.0f)
->Attribute(AZ::Edit::Attributes::Max, 50.0f)
->Attribute(AZ::Edit::Attributes::SoftMin, 0.0f)
->Attribute(AZ::Edit::Attributes::SoftMax, 20.0f)
->Attribute(Edit::Attributes::Visibility, &LookModificationComponentConfig::IsUsingCustomShaper)
->Attribute(AZ::Edit::Attributes::ChangeNotify, Edit::PropertyRefreshLevels::ValuesOnly)
->DataElement(AZ::Edit::UIHandlers::Slider, &LookModificationComponentConfig::m_colorGradingLutIntensity, "LUT Intensity", "Blend intensity of this LUT.")
->Attribute(AZ::Edit::Attributes::Min, 0.0f)
->Attribute(AZ::Edit::Attributes::Max, 1.0f)
->Attribute(AZ::Edit::Attributes::ChangeNotify, Edit::PropertyRefreshLevels::ValuesOnly)
->Attribute(Edit::Attributes::ReadOnly, &LookModificationComponentConfig::ArePropertiesReadOnly)
->Attribute(AZ::Edit::Attributes::Min, 0.0f)
->Attribute(AZ::Edit::Attributes::Max, 1.0f)
->Attribute(AZ::Edit::Attributes::ChangeNotify, Edit::PropertyRefreshLevels::ValuesOnly)
->Attribute(Edit::Attributes::ReadOnly, &LookModificationComponentConfig::ArePropertiesReadOnly)
->DataElement(AZ::Edit::UIHandlers::Slider, &LookModificationComponentConfig::m_colorGradingLutOverride, "LUT Override", "Blend intensity of this LUT.")
->Attribute(AZ::Edit::Attributes::Min, 0.0f)
->Attribute(AZ::Edit::Attributes::Max, 1.0f)
->Attribute(AZ::Edit::Attributes::ChangeNotify, Edit::PropertyRefreshLevels::ValuesOnly)
->Attribute(Edit::Attributes::ReadOnly, &LookModificationComponentConfig::ArePropertiesReadOnly)
->Attribute(AZ::Edit::Attributes::Min, 0.0f)
->Attribute(AZ::Edit::Attributes::Max, 1.0f)
->Attribute(AZ::Edit::Attributes::ChangeNotify, Edit::PropertyRefreshLevels::ValuesOnly)
->Attribute(Edit::Attributes::ReadOnly, &LookModificationComponentConfig::ArePropertiesReadOnly)
// Overrides
->ClassElement(AZ::Edit::ClassElements::Group, "Overrides")
@@ -10,9 +10,7 @@
#include <AzCore/IO/FileIO.h>
#include <AzCore/IO/SystemFile.h>
#include <AzCore/Serialization/Utils.h>
#include <SceneAPI/SceneCore/Containers/Views/PairIterator.h>
#include <SceneAPI/SceneCore/DataTypes/IGraphObject.h>
#include <SceneAPI/SceneCore/Containers/Views/SceneGraphDownwardsIterator.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/std/containers/set.h>
#include <AzFramework/Application/Application.h>
@@ -306,7 +304,10 @@ namespace SceneBuilder
if (itr != request.m_jobDescription.m_jobParameters.end() && itr->second == "true")
{
BuildDebugSceneGraph(outputFolder.c_str(), productList, scene);
AZStd::string productName;
AzFramework::StringFunc::Path::GetFullFileName(scene->GetSourceFilename().c_str(), productName);
AzFramework::StringFunc::Path::ReplaceExtension(productName, "dbgsg");
AZ::SceneAPI::Utilities::DebugOutput::BuildDebugSceneGraph(outputFolder.c_str(), productList, scene, productName);
}
AZ_TracePrintf(Utilities::LogWindow, "Collecting and registering products.\n");
@@ -371,66 +372,4 @@ namespace SceneBuilder
return id;
}
void WriteAndLog(AZ::IO::SystemFile& dbgFile, const char* strToWrite)
{
AZ_TracePrintf(AZ::SceneAPI::Utilities::LogWindow, "%s", strToWrite);
dbgFile.Write(strToWrite, strlen(strToWrite));
dbgFile.Write("\n", strlen("\n"));
}
void SceneBuilderWorker::BuildDebugSceneGraph(const char* outputFolder, AZ::SceneAPI::Events::ExportProductList& productList, const AZStd::shared_ptr<AZ::SceneAPI::Containers::Scene>& scene) const
{
const int debugSceneGraphVersion = 1;
AZStd::string productName, debugSceneFile;
AzFramework::StringFunc::Path::GetFullFileName(scene->GetSourceFilename().c_str(), productName);
AzFramework::StringFunc::Path::ReplaceExtension(productName, "dbgsg");
AzFramework::StringFunc::Path::ConstructFull(outputFolder, productName.c_str(), debugSceneFile);
AZ_TracePrintf(AZ::SceneAPI::Utilities::LogWindow, "outputFolder %s, name %s.\n", outputFolder, productName.c_str());
AZ::IO::SystemFile dbgFile;
if (dbgFile.Open(debugSceneFile.c_str(), AZ::IO::SystemFile::SF_OPEN_CREATE | AZ::IO::SystemFile::SF_OPEN_WRITE_ONLY))
{
WriteAndLog(dbgFile, AZStd::string::format("ProductName: %s", productName.c_str()).c_str());
WriteAndLog(dbgFile, AZStd::string::format("debugSceneGraphVersion: %d", debugSceneGraphVersion).c_str());
WriteAndLog(dbgFile, scene->GetName().c_str());
const AZ::SceneAPI::Containers::SceneGraph& sceneGraph = scene->GetGraph();
auto names = sceneGraph.GetNameStorage();
auto content = sceneGraph.GetContentStorage();
auto pairView = AZ::SceneAPI::Containers::Views::MakePairView(names, content);
auto view = AZ::SceneAPI::Containers::Views::MakeSceneGraphDownwardsView<
AZ::SceneAPI::Containers::Views::BreadthFirst>(
sceneGraph, sceneGraph.GetRoot(), pairView.cbegin(), true);
for (auto&& viewIt : view)
{
if (viewIt.second == nullptr)
{
continue;
}
AZ::SceneAPI::DataTypes::IGraphObject* graphObject = const_cast<AZ::SceneAPI::DataTypes::IGraphObject*>(viewIt.second.get());
WriteAndLog(dbgFile, AZStd::string::format("Node Name: %s", viewIt.first.GetName()).c_str());
WriteAndLog(dbgFile, AZStd::string::format("Node Path: %s", viewIt.first.GetPath()).c_str());
WriteAndLog(dbgFile, AZStd::string::format("Node Type: %s", graphObject->RTTI_GetTypeName()).c_str());
AZ::SceneAPI::Utilities::DebugOutput debugOutput;
viewIt.second->GetDebugOutput(debugOutput);
if (!debugOutput.GetOutput().empty())
{
WriteAndLog(dbgFile, debugOutput.GetOutput().c_str());
}
}
dbgFile.Close();
static const AZ::Data::AssetType dbgSceneGraphAssetType("{07F289D1-4DC7-4C40-94B4-0A53BBCB9F0B}");
productList.AddProduct(productName, AZ::Uuid::CreateName(productName.c_str()), dbgSceneGraphAssetType,
AZStd::nullopt, AZStd::nullopt);
}
}
} // namespace SceneBuilder
@@ -55,9 +55,6 @@ namespace SceneBuilder
void PopulateProductDependencies(const AZ::SceneAPI::Events::ExportProduct& exportProduct, const char* watchFolder, AssetBuilderSDK::JobProduct& jobProduct) const;
protected:
void BuildDebugSceneGraph(const char* outputFolder, AZ::SceneAPI::Events::ExportProductList& productList, const AZStd::shared_ptr<AZ::SceneAPI::Containers::Scene>& scene) const;
bool LoadScene(AZStd::shared_ptr<AZ::SceneAPI::Containers::Scene>& result,
const AssetBuilderSDK::ProcessJobRequest& request, AssetBuilderSDK::ProcessJobResponse& response);
@@ -77,7 +77,7 @@ public: \
{% if Class.attrib['GraphEntryPoint'] is defined %} bool IsEntryPoint() const override { return {%if Class.attrib['GraphEntryPoint'] == "True" %}true{%else%}false{%endif%}; } \
{% endif %}
public: \
friend struct ::{{ className | replace(' ','') }}Property;
friend struct {% if attribute_Namespace is not defined %}::{% endif %}{{ className | replace(' ','') }}Property;
// Helpers for easily accessing properties and slots
struct {{ className | replace(' ','') }}Property
@@ -26,6 +26,20 @@ SPDX-License-Identifier: Apache-2.0 OR MIT
#include "{{ xml.attrib['Include'] }}"
{% for Class in xml.iter('Class') %}
{% set attribute_Namespace = undefined %}
{%- if Class.attrib['Namespace'] is defined %}
{% if Class.attrib['Namespace'] != "None" %}
{% set attribute_Namespace = Class.attrib['Namespace'] %}
{% endif %}
{% endif %}
{% if attribute_Namespace is defined %}
namespace {{attribute_Namespace}}
{
{% endif %}
void {{ Class.attrib['QualifiedName'] }}::ConfigureSlots()
{
{% if Class.attrib['Base'] is defined %}
@@ -269,7 +283,12 @@ void {{ Class.attrib['QualifiedName'] }}::Reflect(AZ::ReflectContext* context)
return datumValue ? *datumValue : {{ Property.attrib['Type'] }}();
}
{% endfor %}
{% if attribute_Namespace is defined %}
}
{% endif %}
{% endfor %}
{% endfor %}
+1 -1
View File
@@ -643,7 +643,7 @@ try {
defaultValue = jenkinsParameter['default_value']
// Use last run's value as default value so we can save values in different Jenkins environment
if (jenkinsParameter['use_last_run_value']?.toBoolean()) {
defaultValue = params."$jenkinsParameter['parameter_name']" ?: jenkinsParameter['default_value']
defaultValue = params."${jenkinsParameter['parameter_name']}" ?: jenkinsParameter['default_value']
}
switch (jenkinsParameter['parameter_type']) {
case 'string':