Merge remote-tracking branch 'upstream/development' into Atom/santorac/RemixableMaterialTypes4_Layering
Signed-off-by: santorac <55155825+santorac@users.noreply.github.com>
This commit is contained in:
@@ -20,9 +20,14 @@ TEST_DIRECTORY = os.path.join(os.path.dirname(__file__), "tests")
|
||||
@pytest.mark.parametrize("launcher_platform", ['windows_editor'])
|
||||
class TestAutomation(EditorTestSuite):
|
||||
|
||||
enable_prefab_system = False
|
||||
enable_prefab_system = True
|
||||
|
||||
# this test is intermittently timing out without ever having executed. sandboxing while we investigate cause.
|
||||
@pytest.mark.test_case_id("C36525660")
|
||||
class AtomEditorComponents_DisplayMapperAdded(EditorSharedTest):
|
||||
from Atom.tests import hydra_AtomEditorComponents_DisplayMapperAdded as test_module
|
||||
|
||||
# The "Sponza" level is failing with a hard lock 4-12% of the time, needs root causing and fixing.
|
||||
@pytest.mark.test_case_id("C36529679")
|
||||
class AtomLevelLoadTest_Editor_Sandbox(EditorSharedTest):
|
||||
from Atom.tests import hydra_Atom_LevelLoadTest_Sandbox as test_module
|
||||
|
||||
@@ -33,7 +33,9 @@ GLOBAL_ILLUMINATION_QUALITY = {
|
||||
}
|
||||
|
||||
# Level list used in Editor Level Load Test
|
||||
LEVEL_LIST = ["hermanubis", "hermanubis_high", "macbeth_shaderballs", "PbrMaterialChart", "ShadowTest", "Sponza"]
|
||||
# WARNING: "Sponza" level is sandboxed due to an intermittent failure.
|
||||
LEVEL_LIST = ["hermanubis", "hermanubis_high", "macbeth_shaderballs", "PbrMaterialChart", "ShadowTest"]
|
||||
|
||||
|
||||
class AtomComponentProperties:
|
||||
"""
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
"""
|
||||
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
|
||||
"""
|
||||
|
||||
|
||||
def Atom_LevelLoadTest():
|
||||
"""
|
||||
Summary:
|
||||
Loads all graphics levels within the AutomatedTesting project in editor. For each level this script will verify that
|
||||
the level loads, and can enter/exit gameplay without crashing the editor.
|
||||
|
||||
Test setup:
|
||||
- Store all available levels in a list.
|
||||
- Set up a for loop to run all checks for each level.
|
||||
|
||||
Expected Behavior:
|
||||
Test verifies that each level loads, enters/exits game mode, and reports success for all test actions.
|
||||
|
||||
Test Steps for each level:
|
||||
1) Create tuple with level load success and failure messages
|
||||
2) Open the level using the python test tools command
|
||||
3) Verify level is loaded using a separate command, and report success/failure
|
||||
4) Enter gameplay and report result using a tuple
|
||||
5) Exit Gameplay and report result using a tuple
|
||||
6) Look for errors or asserts.
|
||||
|
||||
:return: None
|
||||
"""
|
||||
SANDBOX_LEVEL_LIST = ["Sponza"]
|
||||
|
||||
import azlmbr.legacy.general as general
|
||||
|
||||
from editor_python_test_tools.utils import Report, Tracer, TestHelper
|
||||
|
||||
with Tracer() as error_tracer:
|
||||
|
||||
for level in SANDBOX_LEVEL_LIST:
|
||||
|
||||
# 1. Create tuple with level load success and failure messages
|
||||
level_check_tuple = (f"loaded {level}", f"failed to load {level}")
|
||||
|
||||
# 2. Open the level using the python test tools command
|
||||
TestHelper.init_idle()
|
||||
TestHelper.open_level("Graphics", level)
|
||||
|
||||
# 3. Verify level is loaded using a separate command, and report success/failure
|
||||
Report.result(level_check_tuple, level == general.get_current_level_name())
|
||||
|
||||
# 4. Enter gameplay and report result using a tuple
|
||||
enter_game_mode_tuple = (f"{level} entered gameplay successfully ", f"{level} failed to enter gameplay")
|
||||
TestHelper.enter_game_mode(enter_game_mode_tuple)
|
||||
general.idle_wait_frames(1)
|
||||
|
||||
# 5. Exit gameplay and report result using a tuple
|
||||
exit_game_mode_tuple = (f"{level} exited gameplay successfully ", f"{level} failed to exit gameplay")
|
||||
TestHelper.exit_game_mode(exit_game_mode_tuple)
|
||||
|
||||
# 6. Look for errors or asserts.
|
||||
TestHelper.wait_for_condition(lambda: error_tracer.has_errors or error_tracer.has_asserts, 1.0)
|
||||
for error_info in error_tracer.errors:
|
||||
Report.info(f"Error: {error_info.filename} {error_info.function} | {error_info.message}")
|
||||
for assert_info in error_tracer.asserts:
|
||||
Report.info(f"Assert: {assert_info.filename} {assert_info.function} | {assert_info.message}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from editor_python_test_tools.utils import Report
|
||||
Report.start_test(Atom_LevelLoadTest)
|
||||
@@ -54,7 +54,7 @@ class EditorSingleTest_WithFileOverrides(EditorSingleTest):
|
||||
fm._restore_file(f, file_list[f])
|
||||
|
||||
|
||||
# @pytest.mark.xfail(reason="Optimized tests are experimental, we will enable xfail and monitor them temporarily.")
|
||||
@pytest.mark.xfail(reason="Optimized tests are experimental, we will enable xfail and monitor them temporarily.")
|
||||
@pytest.mark.SUITE_main
|
||||
@pytest.mark.parametrize("launcher_platform", ['windows_editor'])
|
||||
@pytest.mark.parametrize("project", ["AutomatedTesting"])
|
||||
@@ -113,6 +113,9 @@ class TestAutomationWithPrefabSystemEnabled(EditorTestSuite):
|
||||
|
||||
class C14861504_RenderMeshAsset_WithNoPxAsset(EditorSharedTest):
|
||||
from .tests.collider import Collider_PxMeshNotAutoAssignedWhenNoPhysicsFbx as test_module
|
||||
|
||||
class C4976236_AddPhysxColliderComponent(EditorSharedTest):
|
||||
from .tests.collider import Collider_AddColliderComponent as test_module
|
||||
|
||||
|
||||
@pytest.mark.xfail(reason="Optimized tests are experimental, we will enable xfail and monitor them temporarily.")
|
||||
@@ -346,9 +349,6 @@ class TestAutomation(EditorTestSuite):
|
||||
class C5959809_ForceRegion_RotationalOffset(EditorSharedTest):
|
||||
from .tests.force_region import ForceRegion_RotationalOffset as test_module
|
||||
|
||||
class C4976236_AddPhysxColliderComponent(EditorSharedTest):
|
||||
from .tests.collider import Collider_AddColliderComponent as test_module
|
||||
|
||||
class C100000_RigidBody_EnablingGravityWorksPoC(EditorSharedTest):
|
||||
from .tests.rigid_body import RigidBody_EnablingGravityWorksPoC as test_module
|
||||
|
||||
|
||||
@@ -440,13 +440,9 @@ class TestAutomation(TestAutomationBase):
|
||||
from .tests.material import Material_LibraryClearingAssignsDefault as test_module
|
||||
self._run_test(request, workspace, editor, test_module)
|
||||
|
||||
@pytest.mark.xfail(reason=
|
||||
"Test failed due to an error message shown while in game mode: "
|
||||
"'(Prefab) - Invalid asset found referenced in scene while entering game mode. "
|
||||
"The asset was stored in an instance of Asset.'")
|
||||
def test_Collider_AddColliderComponent(self, request, workspace, editor, launcher_platform):
|
||||
from .tests.collider import Collider_AddColliderComponent as test_module
|
||||
self._run_test(request, workspace, editor, test_module, enable_prefab_system=False)
|
||||
self._run_test(request, workspace, editor, test_module)
|
||||
|
||||
@pytest.mark.xfail(
|
||||
reason="This will fail due to this issue ATOM-15487.")
|
||||
|
||||
+3
-2
@@ -49,9 +49,10 @@ def Collider_AddColliderComponent():
|
||||
from editor_python_test_tools.utils import Tracer
|
||||
from editor_python_test_tools.asset_utils import Asset
|
||||
|
||||
helper.init_idle()
|
||||
import editor_python_test_tools.hydra_editor_utils as hydra
|
||||
|
||||
# 1) Load the level
|
||||
helper.open_level("Physics", "Base")
|
||||
hydra.open_base_level()
|
||||
|
||||
# 2) Create test entity
|
||||
test_entity = EditorEntity.create_editor_entity("TestEntity")
|
||||
|
||||
@@ -49,9 +49,11 @@ class TestAutomationBase:
|
||||
time_info_str += f"{testcase_name}: (Full:{t} sec, Editor:{editor_t} sec)\n"
|
||||
|
||||
logger.info(time_info_str)
|
||||
if cls.asset_processor is not None:
|
||||
cls.asset_processor.teardown()
|
||||
|
||||
# Kill all ly processes
|
||||
cls.asset_processor.teardown()
|
||||
cls._kill_ly_processes()
|
||||
cls._kill_ly_processes(include_asset_processor=True)
|
||||
|
||||
def _run_test(self, request, workspace, editor, testcase_module, extra_cmdline_args=[], batch_mode=True,
|
||||
autotest_mode=True, use_null_renderer=True, enable_prefab_system=True):
|
||||
@@ -62,14 +64,16 @@ class TestAutomationBase:
|
||||
|
||||
#########
|
||||
# Setup #
|
||||
|
||||
if self.asset_processor is None:
|
||||
self._kill_ly_processes(include_asset_processor=True)
|
||||
self.__class__.asset_processor = AssetProcessor(workspace)
|
||||
self.asset_processor.backup_ap_settings()
|
||||
|
||||
self._kill_ly_processes(include_asset_processor=False)
|
||||
self.asset_processor.start()
|
||||
self.asset_processor.wait_for_idle()
|
||||
else:
|
||||
self._kill_ly_processes(include_asset_processor=False)
|
||||
|
||||
if not self.asset_processor.process_exists():
|
||||
self.asset_processor.start()
|
||||
self.asset_processor.wait_for_idle()
|
||||
|
||||
def teardown():
|
||||
if os.path.exists(workspace.paths.editor_log()):
|
||||
|
||||
@@ -0,0 +1,178 @@
|
||||
/*
|
||||
* 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/DOM/DomComparison.h>
|
||||
#include <AzCore/std/containers/queue.h>
|
||||
#include <AzCore/std/containers/unordered_set.h>
|
||||
|
||||
namespace AZ::Dom
|
||||
{
|
||||
PatchUndoRedoInfo GenerateHierarchicalDeltaPatch(
|
||||
const Value& beforeState, const Value& afterState, const DeltaPatchGenerationParameters& params)
|
||||
{
|
||||
PatchUndoRedoInfo patches;
|
||||
|
||||
auto AddPatch = [&patches](PatchOperation op, PatchOperation inverse)
|
||||
{
|
||||
patches.m_forwardPatches.PushBack(AZStd::move(op));
|
||||
patches.m_inversePatches.PushFront(AZStd::move(inverse));
|
||||
};
|
||||
|
||||
AZStd::function<void(const Path&, const Value&, const Value&)> compareValues;
|
||||
|
||||
struct PendingComparison
|
||||
{
|
||||
Path m_path;
|
||||
const Value& m_before;
|
||||
const Value& m_after;
|
||||
|
||||
PendingComparison(Path path, const Value& before, const Value& after)
|
||||
: m_path(AZStd::move(path))
|
||||
, m_before(before)
|
||||
, m_after(after)
|
||||
{
|
||||
}
|
||||
};
|
||||
AZStd::queue<PendingComparison> entriesToCompare;
|
||||
|
||||
AZStd::unordered_set<AZ::Name::Hash> desiredKeys;
|
||||
auto compareObjects = [&](const Path& path, const Value& before, const Value& after)
|
||||
{
|
||||
desiredKeys.clear();
|
||||
Path subPath = path;
|
||||
for (auto it = after.MemberBegin(); it != after.MemberEnd(); ++it)
|
||||
{
|
||||
desiredKeys.insert(it->first.GetHash());
|
||||
subPath.Push(it->first);
|
||||
auto beforeIt = before.FindMember(it->first);
|
||||
if (beforeIt == before.MemberEnd())
|
||||
{
|
||||
AddPatch(PatchOperation::AddOperation(subPath, it->second), PatchOperation::RemoveOperation(subPath));
|
||||
}
|
||||
else
|
||||
{
|
||||
entriesToCompare.emplace(subPath, beforeIt->second, it->second);
|
||||
}
|
||||
subPath.Pop();
|
||||
}
|
||||
|
||||
for (auto it = before.MemberBegin(); it != before.MemberEnd(); ++it)
|
||||
{
|
||||
if (!desiredKeys.contains(it->first.GetHash()))
|
||||
{
|
||||
subPath.Push(it->first);
|
||||
AddPatch(PatchOperation::RemoveOperation(subPath), PatchOperation::AddOperation(subPath, it->second));
|
||||
subPath.Pop();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
auto compareArrays = [&](const Path& path, const Value& before, const Value& after)
|
||||
{
|
||||
const size_t beforeSize = before.ArraySize();
|
||||
const size_t afterSize = after.ArraySize();
|
||||
|
||||
// If more than replaceThreshold values differ, do a replace operation instead
|
||||
if (params.m_replaceThreshold != DeltaPatchGenerationParameters::NoReplace)
|
||||
{
|
||||
size_t changedValueCount = 0;
|
||||
const size_t entriesToEnumerate = AZStd::min(beforeSize, afterSize);
|
||||
for (size_t i = 0; i < entriesToEnumerate; ++i)
|
||||
{
|
||||
if (before[i] != after[i])
|
||||
{
|
||||
++changedValueCount;
|
||||
if (changedValueCount >= params.m_replaceThreshold)
|
||||
{
|
||||
AddPatch(PatchOperation::ReplaceOperation(path, after), PatchOperation::ReplaceOperation(path, before));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Path subPath = path;
|
||||
for (size_t i = 0; i < afterSize; ++i)
|
||||
{
|
||||
if (i >= beforeSize)
|
||||
{
|
||||
subPath.Push(PathEntry(PathEntry::EndOfArrayIndex));
|
||||
AddPatch(PatchOperation::AddOperation(subPath, after[i]), PatchOperation::RemoveOperation(subPath));
|
||||
subPath.Pop();
|
||||
}
|
||||
else
|
||||
{
|
||||
subPath.Push(PathEntry(i));
|
||||
entriesToCompare.emplace(subPath, before[i], after[i]);
|
||||
subPath.Pop();
|
||||
}
|
||||
}
|
||||
|
||||
if (beforeSize > afterSize)
|
||||
{
|
||||
subPath.Push(PathEntry(PathEntry::EndOfArrayIndex));
|
||||
for (size_t i = beforeSize; i > afterSize; --i)
|
||||
{
|
||||
AddPatch(PatchOperation::RemoveOperation(subPath), PatchOperation::AddOperation(subPath, before[i - 1]));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
auto compareNodes = [&](const Path& path, const Value& before, const Value& after)
|
||||
{
|
||||
if (before.GetNodeName() != after.GetNodeName())
|
||||
{
|
||||
AddPatch(PatchOperation::ReplaceOperation(path, after), PatchOperation::ReplaceOperation(path, before));
|
||||
}
|
||||
else
|
||||
{
|
||||
compareObjects(path, before, after);
|
||||
compareArrays(path, before, after);
|
||||
}
|
||||
};
|
||||
|
||||
compareValues = [&](const Path& path, const Value& before, const Value& after)
|
||||
{
|
||||
if (before.GetType() != after.GetType())
|
||||
{
|
||||
AddPatch(PatchOperation::ReplaceOperation(path, after), PatchOperation::ReplaceOperation(path, before));
|
||||
}
|
||||
else if (before == after)
|
||||
{
|
||||
// If a shallow comparison succeeds we're pointing to an identical value or container
|
||||
// and don't need to drill down.
|
||||
return;
|
||||
}
|
||||
else if (before.IsObject())
|
||||
{
|
||||
compareObjects(path, before, after);
|
||||
}
|
||||
else if (before.IsArray())
|
||||
{
|
||||
compareArrays(path, before, after);
|
||||
}
|
||||
else if (before.IsNode())
|
||||
{
|
||||
compareNodes(path, before, after);
|
||||
}
|
||||
else
|
||||
{
|
||||
AddPatch(PatchOperation::ReplaceOperation(path, after), PatchOperation::ReplaceOperation(path, before));
|
||||
}
|
||||
};
|
||||
|
||||
entriesToCompare.emplace(Path(), beforeState, afterState);
|
||||
while (!entriesToCompare.empty())
|
||||
{
|
||||
PendingComparison& comparison = entriesToCompare.front();
|
||||
compareValues(comparison.m_path, comparison.m_before, comparison.m_after);
|
||||
entriesToCompare.pop();
|
||||
}
|
||||
return patches;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AzCore/DOM/DomPatch.h>
|
||||
|
||||
namespace AZ::Dom
|
||||
{
|
||||
//! A set of patches for applying a change and doing the inverse operation.
|
||||
struct PatchUndoRedoInfo
|
||||
{
|
||||
Patch m_forwardPatches;
|
||||
Patch m_inversePatches;
|
||||
};
|
||||
|
||||
//! Parameters for GenerateHierarchicalDeltaPatch.
|
||||
struct DeltaPatchGenerationParameters
|
||||
{
|
||||
static constexpr size_t NoReplace = AZStd::numeric_limits<size_t>::max();
|
||||
static constexpr size_t AlwaysFullReplace = 0;
|
||||
|
||||
//! The threshold of changed values in a node or array which, if exceeded, will cause the generation to create an
|
||||
//! entire "replace" oepration instead. If set to NoReplace, no replacement will occur.
|
||||
size_t m_replaceThreshold = 3;
|
||||
};
|
||||
|
||||
//! Generates a set of patches such that m_forwardPatches.Apply(beforeState) shall produce a document equivalent to afterState, and
|
||||
//! a subsequent m_inversePatches.Apply(beforeState) shall produce the original document. This patch generation strategy does a
|
||||
//! hierarchical comparison and is not guaranteed to create the minimal set of patches required to transform between the two states.
|
||||
PatchUndoRedoInfo GenerateHierarchicalDeltaPatch(const Value& beforeState, const Value& afterState, const DeltaPatchGenerationParameters& params = {});
|
||||
} // namespace AZ::Dom
|
||||
@@ -0,0 +1,799 @@
|
||||
/*
|
||||
* 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/DOM/DomPatch.h>
|
||||
#include <AzCore/DOM/DomUtils.h>
|
||||
|
||||
namespace AZ::Dom
|
||||
{
|
||||
PatchOperation::PatchOperation(Path destinationPath, Type type, Value value)
|
||||
: m_domPath(AZStd::move(destinationPath))
|
||||
, m_type(type)
|
||||
, m_value(AZStd::move(value))
|
||||
{
|
||||
}
|
||||
|
||||
PatchOperation::PatchOperation(Path destinationPath, Type type, Path sourcePath)
|
||||
: m_domPath(AZStd::move(destinationPath))
|
||||
, m_type(type)
|
||||
, m_value(AZStd::move(sourcePath))
|
||||
{
|
||||
}
|
||||
|
||||
PatchOperation::PatchOperation(Path destinationPath, Type type)
|
||||
: m_domPath(AZStd::move(destinationPath))
|
||||
, m_type(type)
|
||||
{
|
||||
}
|
||||
|
||||
bool PatchOperation::operator==(const PatchOperation& rhs) const
|
||||
{
|
||||
if (m_type != rhs.m_type)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (m_type)
|
||||
{
|
||||
case Type::Add:
|
||||
return m_domPath == rhs.m_domPath && Utils::DeepCompareIsEqual(GetValue(), rhs.GetValue());
|
||||
case Type::Remove:
|
||||
return m_domPath == rhs.m_domPath;
|
||||
case Type::Replace:
|
||||
return m_domPath == rhs.m_domPath && Utils::DeepCompareIsEqual(GetValue(), rhs.GetValue());
|
||||
case Type::Copy:
|
||||
return m_domPath == rhs.m_domPath && GetSourcePath() == rhs.GetSourcePath();
|
||||
case Type::Move:
|
||||
return m_domPath == rhs.m_domPath && GetSourcePath() == rhs.GetSourcePath();
|
||||
case Type::Test:
|
||||
return m_domPath == rhs.m_domPath && Utils::DeepCompareIsEqual(GetValue(), rhs.GetValue());
|
||||
default:
|
||||
AZ_Assert(false, "PatchOperation::GetDomRepresentation: invalid patch type specified");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool PatchOperation::operator!=(const PatchOperation& rhs) const
|
||||
{
|
||||
return !operator==(rhs);
|
||||
}
|
||||
|
||||
PatchOperation::Type PatchOperation::GetType() const
|
||||
{
|
||||
return m_type;
|
||||
}
|
||||
|
||||
void PatchOperation::SetType(Type type)
|
||||
{
|
||||
m_type = type;
|
||||
}
|
||||
|
||||
const Path& PatchOperation::GetDestinationPath() const
|
||||
{
|
||||
return m_domPath;
|
||||
}
|
||||
|
||||
void PatchOperation::SetDestinationPath(Path path)
|
||||
{
|
||||
m_domPath = path;
|
||||
}
|
||||
|
||||
const Value& PatchOperation::GetValue() const
|
||||
{
|
||||
return AZStd::get<Value>(m_value);
|
||||
}
|
||||
|
||||
void PatchOperation::SetValue(Value value)
|
||||
{
|
||||
m_value = AZStd::move(value);
|
||||
}
|
||||
|
||||
const Path& PatchOperation::GetSourcePath() const
|
||||
{
|
||||
return AZStd::get<Path>(m_value);
|
||||
}
|
||||
|
||||
void PatchOperation::SetSourcePath(Path path)
|
||||
{
|
||||
m_value = AZStd::move(path);
|
||||
}
|
||||
|
||||
AZ::Outcome<Value, AZStd::string> PatchOperation::Apply(Value rootElement) const
|
||||
{
|
||||
PatchOutcome outcome = ApplyInPlace(rootElement);
|
||||
if (!outcome.IsSuccess())
|
||||
{
|
||||
return AZ::Failure(outcome.TakeError());
|
||||
}
|
||||
return AZ::Success(AZStd::move(rootElement));
|
||||
}
|
||||
|
||||
PatchOperation::PatchOutcome PatchOperation::ApplyInPlace(Value& rootElement) const
|
||||
{
|
||||
switch (m_type)
|
||||
{
|
||||
case Type::Add:
|
||||
return ApplyAdd(rootElement);
|
||||
case Type::Remove:
|
||||
return ApplyRemove(rootElement);
|
||||
case Type::Replace:
|
||||
return ApplyReplace(rootElement);
|
||||
case Type::Copy:
|
||||
return ApplyCopy(rootElement);
|
||||
case Type::Move:
|
||||
return ApplyMove(rootElement);
|
||||
case Type::Test:
|
||||
return ApplyTest(rootElement);
|
||||
}
|
||||
return AZ::Failure<AZStd::string>("Unsupported DOM patch operation specified");
|
||||
}
|
||||
|
||||
Value PatchOperation::GetDomRepresentation() const
|
||||
{
|
||||
Value serializedPatch(Dom::Type::Object);
|
||||
switch (m_type)
|
||||
{
|
||||
case Type::Add:
|
||||
serializedPatch["op"].SetString("add");
|
||||
serializedPatch["path"].CopyFromString(GetDestinationPath().ToString());
|
||||
serializedPatch["value"] = GetValue();
|
||||
break;
|
||||
case Type::Remove:
|
||||
serializedPatch["op"].SetString("remove");
|
||||
serializedPatch["path"].CopyFromString(GetDestinationPath().ToString());
|
||||
break;
|
||||
case Type::Replace:
|
||||
serializedPatch["op"].SetString("replace");
|
||||
serializedPatch["path"].CopyFromString(GetDestinationPath().ToString());
|
||||
serializedPatch["value"] = GetValue();
|
||||
break;
|
||||
case Type::Copy:
|
||||
serializedPatch["op"].SetString("copy");
|
||||
serializedPatch["from"].CopyFromString(GetSourcePath().ToString());
|
||||
serializedPatch["path"].CopyFromString(GetDestinationPath().ToString());
|
||||
break;
|
||||
case Type::Move:
|
||||
serializedPatch["op"].SetString("move");
|
||||
serializedPatch["from"].CopyFromString(GetSourcePath().ToString());
|
||||
serializedPatch["path"].CopyFromString(GetDestinationPath().ToString());
|
||||
break;
|
||||
case Type::Test:
|
||||
serializedPatch["op"].SetString("test");
|
||||
serializedPatch["path"].CopyFromString(GetDestinationPath().ToString());
|
||||
serializedPatch["value"] = GetValue();
|
||||
break;
|
||||
default:
|
||||
AZ_Assert(false, "PatchOperation::GetDomRepresentation: invalid patch type specified");
|
||||
}
|
||||
return serializedPatch;
|
||||
}
|
||||
|
||||
AZ::Outcome<PatchOperation, AZStd::string> PatchOperation::CreateFromDomRepresentation(Value domValue)
|
||||
{
|
||||
if (!domValue.IsObject())
|
||||
{
|
||||
return AZ::Failure<AZStd::string>("PatchOperation failed to load: PatchOperation must be specified as an Object");
|
||||
}
|
||||
|
||||
auto loadField = [&](const char* field, AZStd::optional<Dom::Type> type = {}) -> AZ::Outcome<Value, AZStd::string>
|
||||
{
|
||||
auto it = domValue.FindMember(field);
|
||||
if (it == domValue.MemberEnd())
|
||||
{
|
||||
return AZ::Failure(AZStd::string::format("PatchOperation failed to load: no \"%s\" specified", field));
|
||||
}
|
||||
|
||||
if (type.has_value() && it->second.GetType() != type)
|
||||
{
|
||||
return AZ::Failure(AZStd::string::format("PatchOperation failed to load: \"%s\" is invalid", field));
|
||||
}
|
||||
|
||||
return AZ::Success(it->second);
|
||||
};
|
||||
|
||||
auto opLoad = loadField("op", Dom::Type::String);
|
||||
if (!opLoad.IsSuccess())
|
||||
{
|
||||
return AZ::Failure(opLoad.TakeError());
|
||||
}
|
||||
AZStd::string_view op = opLoad.GetValue().GetString();
|
||||
if (op == "add")
|
||||
{
|
||||
auto pathLoad = loadField("path", Dom::Type::String);
|
||||
if (!pathLoad.IsSuccess())
|
||||
{
|
||||
return AZ::Failure(pathLoad.TakeError());
|
||||
}
|
||||
auto valueLoad = loadField("value");
|
||||
if (!valueLoad.IsSuccess())
|
||||
{
|
||||
return AZ::Failure(valueLoad.TakeError());
|
||||
}
|
||||
|
||||
return AZ::Success(PatchOperation::AddOperation(Path(pathLoad.GetValue().GetString()), valueLoad.TakeValue()));
|
||||
}
|
||||
else if (op == "remove")
|
||||
{
|
||||
auto pathLoad = loadField("path", Dom::Type::String);
|
||||
if (!pathLoad.IsSuccess())
|
||||
{
|
||||
return AZ::Failure(pathLoad.TakeError());
|
||||
}
|
||||
|
||||
return AZ::Success(PatchOperation::RemoveOperation(Path(pathLoad.GetValue().GetString())));
|
||||
}
|
||||
else if (op == "replace")
|
||||
{
|
||||
auto pathLoad = loadField("path", Dom::Type::String);
|
||||
if (!pathLoad.IsSuccess())
|
||||
{
|
||||
return AZ::Failure(pathLoad.TakeError());
|
||||
}
|
||||
auto valueLoad = loadField("value");
|
||||
if (!valueLoad.IsSuccess())
|
||||
{
|
||||
return AZ::Failure(valueLoad.TakeError());
|
||||
}
|
||||
|
||||
return AZ::Success(PatchOperation::ReplaceOperation(Path(pathLoad.GetValue().GetString()), valueLoad.TakeValue()));
|
||||
}
|
||||
else if (op == "copy")
|
||||
{
|
||||
auto destLoad = loadField("path", Dom::Type::String);
|
||||
if (!destLoad.IsSuccess())
|
||||
{
|
||||
return AZ::Failure(destLoad.TakeError());
|
||||
}
|
||||
auto sourceLoad = loadField("from", Dom::Type::String);
|
||||
if (!sourceLoad.IsSuccess())
|
||||
{
|
||||
return AZ::Failure(sourceLoad.TakeError());
|
||||
}
|
||||
|
||||
return AZ::Success(
|
||||
PatchOperation::CopyOperation(Path(destLoad.GetValue().GetString()), Path(sourceLoad.GetValue().GetString())));
|
||||
}
|
||||
else if (op == "move")
|
||||
{
|
||||
auto destLoad = loadField("path", Dom::Type::String);
|
||||
if (!destLoad.IsSuccess())
|
||||
{
|
||||
return AZ::Failure(destLoad.TakeError());
|
||||
}
|
||||
auto sourceLoad = loadField("from", Dom::Type::String);
|
||||
if (!sourceLoad.IsSuccess())
|
||||
{
|
||||
return AZ::Failure(sourceLoad.TakeError());
|
||||
}
|
||||
|
||||
return AZ::Success(
|
||||
PatchOperation::MoveOperation(Path(destLoad.GetValue().GetString()), Path(sourceLoad.GetValue().GetString())));
|
||||
}
|
||||
else if (op == "test")
|
||||
{
|
||||
auto pathLoad = loadField("path", Dom::Type::String);
|
||||
if (!pathLoad.IsSuccess())
|
||||
{
|
||||
return AZ::Failure(pathLoad.TakeError());
|
||||
}
|
||||
auto valueLoad = loadField("value");
|
||||
if (!valueLoad.IsSuccess())
|
||||
{
|
||||
return AZ::Failure(valueLoad.TakeError());
|
||||
}
|
||||
|
||||
return AZ::Success(PatchOperation::TestOperation(Path(pathLoad.GetValue().GetString()), valueLoad.TakeValue()));
|
||||
}
|
||||
else
|
||||
{
|
||||
return AZ::Failure<AZStd::string>("PatchOperation failed to create DOM representation: invalid \"op\" specified");
|
||||
}
|
||||
}
|
||||
|
||||
AZ::Outcome<AZStd::fixed_vector<PatchOperation, 2>, AZStd::string> PatchOperation::GetInverse(Value stateBeforeApplication) const
|
||||
{
|
||||
switch (m_type)
|
||||
{
|
||||
case Type::Add:
|
||||
{
|
||||
// Add -> Replace (if value already existed in an object) otherwise
|
||||
// Add -> Remove
|
||||
if (m_domPath.Size() > 0 && m_domPath[m_domPath.Size() - 1].IsKey())
|
||||
{
|
||||
const Value* existingValue = stateBeforeApplication.FindChild(m_domPath);
|
||||
if (existingValue != nullptr)
|
||||
{
|
||||
return AZ::Success<InversePatches>({PatchOperation::ReplaceOperation(m_domPath, *existingValue)});
|
||||
}
|
||||
}
|
||||
return AZ::Success<InversePatches>({PatchOperation::RemoveOperation(m_domPath)});
|
||||
}
|
||||
case Type::Remove:
|
||||
{
|
||||
// Remove -> Add
|
||||
const Value* existingValue = stateBeforeApplication.FindChild(m_domPath);
|
||||
if (existingValue == nullptr)
|
||||
{
|
||||
AZStd::string errorMessage = "Unable to invert DOM remove patch, source path not found: ";
|
||||
m_domPath.AppendToString(errorMessage);
|
||||
return AZ::Failure(AZStd::move(errorMessage));
|
||||
}
|
||||
return AZ::Success<InversePatches>({PatchOperation::AddOperation(m_domPath, *existingValue)});
|
||||
}
|
||||
case Type::Replace:
|
||||
{
|
||||
// Replace -> Replace (with old value)
|
||||
const Value* existingValue = stateBeforeApplication.FindChild(m_domPath);
|
||||
if (existingValue == nullptr)
|
||||
{
|
||||
AZStd::string errorMessage = "Unable to invert DOM replace patch, source path not found: ";
|
||||
m_domPath.AppendToString(errorMessage);
|
||||
return AZ::Failure(AZStd::move(errorMessage));
|
||||
}
|
||||
return AZ::Success<InversePatches>({PatchOperation::ReplaceOperation(m_domPath, *existingValue)});
|
||||
}
|
||||
case Type::Copy:
|
||||
{
|
||||
// Copy -> Replace (with old value)
|
||||
const Value* existingValue = stateBeforeApplication.FindChild(m_domPath);
|
||||
if (existingValue == nullptr)
|
||||
{
|
||||
AZStd::string errorMessage = "Unable to invert DOM copy patch, source path not found: ";
|
||||
m_domPath.AppendToString(errorMessage);
|
||||
return AZ::Failure(AZStd::move(errorMessage));
|
||||
}
|
||||
return AZ::Success<InversePatches>({PatchOperation::ReplaceOperation(m_domPath, *existingValue)});
|
||||
}
|
||||
case Type::Move:
|
||||
{
|
||||
const Value* sourceValue = stateBeforeApplication.FindChild(GetSourcePath());
|
||||
if (sourceValue == nullptr)
|
||||
{
|
||||
AZStd::string errorMessage = "Unable to invert DOM copy patch, source path not found: ";
|
||||
m_domPath.AppendToString(errorMessage);
|
||||
return AZ::Failure(AZStd::move(errorMessage));
|
||||
}
|
||||
|
||||
// If there was a value at the destination path, invert with an add / replace
|
||||
const Value* destinationValue = stateBeforeApplication.FindChild(GetDestinationPath());
|
||||
if (destinationValue != nullptr)
|
||||
{
|
||||
InversePatches result({PatchOperation::AddOperation(GetSourcePath(), *sourceValue)});
|
||||
result.push_back(PatchOperation::ReplaceOperation(GetDestinationPath(), *destinationValue));
|
||||
return AZ::Success<InversePatches>({
|
||||
PatchOperation::AddOperation(GetSourcePath(), *sourceValue),
|
||||
PatchOperation::ReplaceOperation(GetDestinationPath(), *destinationValue),
|
||||
});
|
||||
}
|
||||
// Otherwise, just do a move
|
||||
return AZ::Success<InversePatches>({PatchOperation::MoveOperation(GetDestinationPath(), GetSourcePath())});
|
||||
}
|
||||
case Type::Test:
|
||||
{
|
||||
// Test -> Test (no change)
|
||||
// When inverting a sequence of patches, applying them in reverse order should allow the test to continue to succeed
|
||||
return AZ::Success<InversePatches>({*this});
|
||||
}
|
||||
}
|
||||
return AZ::Failure<AZStd::string>("Unable to invert DOM patch, unknown type specified");
|
||||
}
|
||||
|
||||
AZ::Outcome<PatchOperation::PathContext, AZStd::string> PatchOperation::LookupPath(
|
||||
Value& rootElement, const Path& path, ExistenceCheckFlags flags)
|
||||
{
|
||||
const bool verifyFullPath = (flags & ExistenceCheckFlags::VerifyFullPath) != ExistenceCheckFlags::DefaultExistenceCheck;
|
||||
const bool allowEndOfArray = (flags & ExistenceCheckFlags::AllowEndOfArray) != ExistenceCheckFlags::DefaultExistenceCheck;
|
||||
|
||||
Path target = path;
|
||||
if (target.IsEmpty())
|
||||
{
|
||||
Value wrapper(Dom::Type::Array);
|
||||
wrapper.ArrayPushBack(rootElement);
|
||||
return AZ::Success<PathContext>({ wrapper, PathEntry(0) });
|
||||
}
|
||||
|
||||
if (verifyFullPath || !allowEndOfArray)
|
||||
{
|
||||
for (size_t i = 0; i < path.Size(); ++i)
|
||||
{
|
||||
const PathEntry& entry = path[i];
|
||||
if (entry.IsEndOfArray() && (!allowEndOfArray || i != path.Size() - 1))
|
||||
{
|
||||
return AZ::Failure<AZStd::string>("Append to array index (\"-\") specified for path that must already exist");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PathEntry destinationIndex = target[target.Size() - 1];
|
||||
target.Pop();
|
||||
|
||||
Value* targetValue = rootElement.FindMutableChild(target);
|
||||
if (targetValue == nullptr)
|
||||
{
|
||||
AZStd::string errorMessage = "Path not found: ";
|
||||
target.AppendToString(errorMessage);
|
||||
return AZ::Failure(AZStd::move(errorMessage));
|
||||
}
|
||||
|
||||
if (destinationIndex.IsIndex() || destinationIndex.IsEndOfArray())
|
||||
{
|
||||
if (!targetValue->IsArray() && !targetValue->IsNode())
|
||||
{
|
||||
return AZ::Failure<AZStd::string>("Array index specified for a value that is not an array or node");
|
||||
}
|
||||
|
||||
if (destinationIndex.IsIndex() && destinationIndex.GetIndex() >= targetValue->ArraySize())
|
||||
{
|
||||
return AZ::Failure<AZStd::string>("Array index out bounds");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!targetValue->IsObject() && !targetValue->IsNode())
|
||||
{
|
||||
return AZ::Failure<AZStd::string>("Key specified for a value that is not an object or node");
|
||||
}
|
||||
|
||||
if (verifyFullPath)
|
||||
{
|
||||
if (auto it = targetValue->FindMember(destinationIndex.GetKey()); it == targetValue->MemberEnd())
|
||||
{
|
||||
return AZ::Failure<AZStd::string>("Key not found in container");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return AZ::Success<PathContext>({ *targetValue, AZStd::move(destinationIndex) });
|
||||
}
|
||||
|
||||
PatchOperation::PatchOutcome PatchOperation::ApplyAdd(Value& rootElement) const
|
||||
{
|
||||
auto pathLookup = LookupPath(rootElement, m_domPath, ExistenceCheckFlags::AllowEndOfArray);
|
||||
if (!pathLookup.IsSuccess())
|
||||
{
|
||||
return AZ::Failure(pathLookup.TakeError());
|
||||
}
|
||||
const PathContext& context = pathLookup.GetValue();
|
||||
const PathEntry& destinationIndex = context.m_key;
|
||||
Value& targetValue = context.m_value;
|
||||
|
||||
if (destinationIndex.IsIndex() || destinationIndex.IsEndOfArray())
|
||||
{
|
||||
if (destinationIndex.IsEndOfArray())
|
||||
{
|
||||
targetValue.ArrayPushBack(GetValue());
|
||||
}
|
||||
else
|
||||
{
|
||||
const size_t index = destinationIndex.GetIndex();
|
||||
auto& arrayToChange = targetValue.GetMutableArray();
|
||||
arrayToChange.insert(arrayToChange.begin() + index, GetValue());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
targetValue[destinationIndex] = GetValue();
|
||||
}
|
||||
return AZ::Success();
|
||||
}
|
||||
|
||||
PatchOperation::PatchOutcome PatchOperation::ApplyRemove(Value& rootElement) const
|
||||
{
|
||||
auto pathLookup = LookupPath(rootElement, m_domPath, ExistenceCheckFlags::VerifyFullPath | ExistenceCheckFlags::AllowEndOfArray);
|
||||
if (!pathLookup.IsSuccess())
|
||||
{
|
||||
return AZ::Failure(pathLookup.TakeError());
|
||||
}
|
||||
const PathContext& context = pathLookup.GetValue();
|
||||
const PathEntry& destinationIndex = context.m_key;
|
||||
Value& targetValue = context.m_value;
|
||||
|
||||
if (destinationIndex.IsIndex() || destinationIndex.IsEndOfArray())
|
||||
{
|
||||
size_t index = destinationIndex.IsEndOfArray() ? targetValue.ArraySize() - 1 : destinationIndex.GetIndex();
|
||||
targetValue.ArrayErase(targetValue.MutableArrayBegin() + index);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto it = targetValue.FindMutableMember(destinationIndex.GetKey());
|
||||
targetValue.EraseMember(it);
|
||||
}
|
||||
return AZ::Success();
|
||||
}
|
||||
|
||||
PatchOperation::PatchOutcome PatchOperation::ApplyReplace(Value& rootElement) const
|
||||
{
|
||||
auto pathLookup = LookupPath(rootElement, m_domPath, ExistenceCheckFlags::VerifyFullPath);
|
||||
if (!pathLookup.IsSuccess())
|
||||
{
|
||||
return AZ::Failure(pathLookup.TakeError());
|
||||
}
|
||||
|
||||
rootElement[m_domPath] = GetValue();
|
||||
return AZ::Success();
|
||||
}
|
||||
|
||||
PatchOperation::PatchOutcome PatchOperation::ApplyCopy(Value& rootElement) const
|
||||
{
|
||||
auto sourceLookup = LookupPath(rootElement, GetSourcePath(), ExistenceCheckFlags::VerifyFullPath);
|
||||
if (!sourceLookup.IsSuccess())
|
||||
{
|
||||
return AZ::Failure(sourceLookup.TakeError());
|
||||
}
|
||||
|
||||
auto destLookup = LookupPath(rootElement, m_domPath, ExistenceCheckFlags::AllowEndOfArray);
|
||||
if (!destLookup.IsSuccess())
|
||||
{
|
||||
return AZ::Failure(destLookup.TakeError());
|
||||
}
|
||||
|
||||
rootElement[m_domPath] = rootElement[GetSourcePath()];
|
||||
return AZ::Success();
|
||||
}
|
||||
|
||||
PatchOperation::PatchOutcome PatchOperation::ApplyMove(Value& rootElement) const
|
||||
{
|
||||
auto sourceLookup = LookupPath(rootElement, GetSourcePath(), ExistenceCheckFlags::VerifyFullPath);
|
||||
if (!sourceLookup.IsSuccess())
|
||||
{
|
||||
return AZ::Failure(sourceLookup.TakeError());
|
||||
}
|
||||
|
||||
auto destLookup = LookupPath(rootElement, m_domPath, ExistenceCheckFlags::AllowEndOfArray);
|
||||
if (!destLookup.IsSuccess())
|
||||
{
|
||||
return AZ::Failure(destLookup.TakeError());
|
||||
}
|
||||
|
||||
Value valueToMove = rootElement[GetSourcePath()];
|
||||
const PathContext& sourceContext = sourceLookup.GetValue();
|
||||
if (sourceContext.m_key.IsEndOfArray())
|
||||
{
|
||||
sourceContext.m_value.ArrayPopBack();
|
||||
}
|
||||
else if (sourceContext.m_key.IsIndex())
|
||||
{
|
||||
sourceContext.m_value.ArrayErase(sourceContext.m_value.MutableArrayBegin() + sourceContext.m_key.GetIndex());
|
||||
}
|
||||
else
|
||||
{
|
||||
sourceContext.m_value.EraseMember(sourceContext.m_key.GetKey());
|
||||
}
|
||||
|
||||
rootElement[m_domPath] = AZStd::move(valueToMove);
|
||||
return AZ::Success();
|
||||
}
|
||||
|
||||
PatchOperation::PatchOutcome PatchOperation::ApplyTest(Value& rootElement) const
|
||||
{
|
||||
auto pathLookup = LookupPath(rootElement, m_domPath, ExistenceCheckFlags::VerifyFullPath);
|
||||
if (!pathLookup.IsSuccess())
|
||||
{
|
||||
return AZ::Failure(pathLookup.TakeError());
|
||||
}
|
||||
|
||||
if (!Utils::DeepCompareIsEqual(rootElement[m_domPath], GetValue()))
|
||||
{
|
||||
return AZ::Failure<AZStd::string>("Test failed, values don't match");
|
||||
}
|
||||
|
||||
return AZ::Success();
|
||||
}
|
||||
|
||||
namespace PatchApplicationStrategy
|
||||
{
|
||||
void HaltOnFailure(PatchApplicationState& state)
|
||||
{
|
||||
if (!state.m_outcome.IsSuccess())
|
||||
{
|
||||
state.m_shouldContinue = false;
|
||||
}
|
||||
}
|
||||
|
||||
void IgnoreFailureAndContinue([[maybe_unused]] PatchApplicationState& state)
|
||||
{
|
||||
}
|
||||
} // namespace PatchApplicationStrategy
|
||||
|
||||
Patch::Patch(AZStd::initializer_list<PatchOperation> init)
|
||||
: m_operations(init)
|
||||
{
|
||||
}
|
||||
|
||||
bool Patch::operator==(const Patch& rhs) const
|
||||
{
|
||||
if (m_operations.size() != rhs.m_operations.size())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < m_operations.size(); ++i)
|
||||
{
|
||||
if (m_operations[i] != rhs.m_operations[i])
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Patch::operator!=(const Patch& rhs) const
|
||||
{
|
||||
return !operator==(rhs);
|
||||
}
|
||||
|
||||
const Patch::OperationsContainer& Patch::GetOperations() const
|
||||
{
|
||||
return m_operations;
|
||||
}
|
||||
|
||||
void Patch::PushBack(PatchOperation op)
|
||||
{
|
||||
m_operations.push_back(AZStd::move(op));
|
||||
}
|
||||
|
||||
void Patch::PushFront(PatchOperation op)
|
||||
{
|
||||
m_operations.insert(m_operations.begin(), AZStd::move(op));
|
||||
}
|
||||
|
||||
void Patch::Pop()
|
||||
{
|
||||
m_operations.pop_back();
|
||||
}
|
||||
|
||||
void Patch::Clear()
|
||||
{
|
||||
m_operations.clear();
|
||||
}
|
||||
|
||||
const PatchOperation& Patch::At(size_t index) const
|
||||
{
|
||||
return m_operations[index];
|
||||
}
|
||||
|
||||
size_t Patch::Size() const
|
||||
{
|
||||
return m_operations.size();
|
||||
}
|
||||
|
||||
PatchOperation& Patch::operator[](size_t index)
|
||||
{
|
||||
return m_operations[index];
|
||||
}
|
||||
|
||||
const PatchOperation& Patch::operator[](size_t index) const
|
||||
{
|
||||
return m_operations[index];
|
||||
}
|
||||
|
||||
auto Patch::begin() -> OperationsContainer::iterator
|
||||
{
|
||||
return m_operations.begin();
|
||||
}
|
||||
|
||||
auto Patch::end() -> OperationsContainer::iterator
|
||||
{
|
||||
return m_operations.end();
|
||||
}
|
||||
|
||||
auto Patch::begin() const -> OperationsContainer::const_iterator
|
||||
{
|
||||
return m_operations.begin();
|
||||
}
|
||||
|
||||
auto Patch::end() const -> OperationsContainer::const_iterator
|
||||
{
|
||||
return m_operations.end();
|
||||
}
|
||||
|
||||
auto Patch::cbegin() const -> OperationsContainer::const_iterator
|
||||
{
|
||||
return m_operations.begin();
|
||||
}
|
||||
|
||||
auto Patch::cend() const -> OperationsContainer::const_iterator
|
||||
{
|
||||
return m_operations.end();
|
||||
}
|
||||
|
||||
size_t Patch::size() const
|
||||
{
|
||||
return m_operations.size();
|
||||
}
|
||||
|
||||
AZ::Outcome<Value, AZStd::string> Patch::Apply(Value rootElement, StrategyFunctor strategy) const
|
||||
{
|
||||
auto result = ApplyInPlace(rootElement, strategy);
|
||||
if (!result.IsSuccess())
|
||||
{
|
||||
return AZ::Failure(result.TakeError());
|
||||
}
|
||||
return AZ::Success(AZStd::move(rootElement));
|
||||
}
|
||||
|
||||
AZ::Outcome<void, AZStd::string> Patch::ApplyInPlace(Value& rootElement, StrategyFunctor strategy) const
|
||||
{
|
||||
PatchApplicationState state;
|
||||
state.m_currentState = &rootElement;
|
||||
state.m_patch = this;
|
||||
|
||||
for (const PatchOperation& operation : m_operations)
|
||||
{
|
||||
state.m_lastOperation = &operation;
|
||||
state.m_outcome = operation.ApplyInPlace(rootElement);
|
||||
strategy(state);
|
||||
if (!state.m_shouldContinue)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
return state.m_outcome;
|
||||
}
|
||||
|
||||
Value Patch::GetDomRepresentation() const
|
||||
{
|
||||
Value domValue(Dom::Type::Array);
|
||||
for (const PatchOperation& operation : m_operations)
|
||||
{
|
||||
domValue.ArrayPushBack(operation.GetDomRepresentation());
|
||||
}
|
||||
return domValue;
|
||||
}
|
||||
|
||||
AZ::Outcome<Patch, AZStd::string> Patch::CreateFromDomRepresentation(Value domValue)
|
||||
{
|
||||
if (!domValue.IsArray())
|
||||
{
|
||||
return AZ::Failure<AZStd::string>("Patch must be an array");
|
||||
}
|
||||
|
||||
Patch patch;
|
||||
for (auto it = domValue.ArrayBegin(); it != domValue.ArrayEnd(); ++it)
|
||||
{
|
||||
auto operationLoadResult = PatchOperation::CreateFromDomRepresentation(*it);
|
||||
if (!operationLoadResult.IsSuccess())
|
||||
{
|
||||
return AZ::Failure(operationLoadResult.TakeError());
|
||||
}
|
||||
patch.PushBack(operationLoadResult.TakeValue());
|
||||
}
|
||||
return AZ::Success(AZStd::move(patch));
|
||||
}
|
||||
|
||||
PatchOperation PatchOperation::AddOperation(Path destinationPath, Value value)
|
||||
{
|
||||
return PatchOperation(AZStd::move(destinationPath), PatchOperation::Type::Add, AZStd::move(value));
|
||||
}
|
||||
|
||||
PatchOperation PatchOperation::RemoveOperation(Path pathToRemove)
|
||||
{
|
||||
return PatchOperation(AZStd::move(pathToRemove), PatchOperation::Type::Remove);
|
||||
}
|
||||
|
||||
PatchOperation PatchOperation::ReplaceOperation(Path destinationPath, Value value)
|
||||
{
|
||||
return PatchOperation(AZStd::move(destinationPath), PatchOperation::Type::Replace, AZStd::move(value));
|
||||
}
|
||||
|
||||
PatchOperation PatchOperation::CopyOperation(Path destinationPath, Path sourcePath)
|
||||
{
|
||||
return PatchOperation(AZStd::move(destinationPath), PatchOperation::Type::Copy, AZStd::move(sourcePath));
|
||||
}
|
||||
|
||||
PatchOperation PatchOperation::MoveOperation(Path destinationPath, Path sourcePath)
|
||||
{
|
||||
return PatchOperation(AZStd::move(destinationPath), PatchOperation::Type::Move, AZStd::move(sourcePath));
|
||||
}
|
||||
|
||||
PatchOperation PatchOperation::TestOperation(Path testPath, Value value)
|
||||
{
|
||||
return PatchOperation(AZStd::move(testPath), PatchOperation::Type::Test, AZStd::move(value));
|
||||
}
|
||||
} // namespace AZ::Dom
|
||||
@@ -0,0 +1,186 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AzCore/DOM/DomPath.h>
|
||||
#include <AzCore/DOM/DomValue.h>
|
||||
#include <AzCore/std/containers/deque.h>
|
||||
|
||||
namespace AZ::Dom
|
||||
{
|
||||
//! A patch operation that represents an atomic operation for mutating or validating a Value.
|
||||
//! PatchOperations can be created with helper methods in Patch. /see Patch
|
||||
class PatchOperation final
|
||||
{
|
||||
public:
|
||||
using PatchOutcome = AZ::Outcome<void, AZStd::string>;
|
||||
|
||||
//! The operation to perform.
|
||||
enum class Type
|
||||
{
|
||||
Add, //!< Inserts or replaces the value at DestinationPath with Value
|
||||
Remove, //!< Removes the entry at DestinationPath
|
||||
Replace, //!< Replaces the value at DestinationPath with Value
|
||||
Copy, //!< Copies the contents of SourcePath to DestinationPath
|
||||
Move, //!< Moves the contents of SourcePath to DestinationPath
|
||||
Test //!< Ensures the contents of DestinationPath match Value or fails, performs no mutations
|
||||
};
|
||||
|
||||
PatchOperation() = default;
|
||||
PatchOperation(const PatchOperation&) = default;
|
||||
PatchOperation(PatchOperation&&) = default;
|
||||
|
||||
PatchOperation(Path destionationPath, Type type, Value value);
|
||||
PatchOperation(Path destionationPath, Type type, Path sourcePath);
|
||||
PatchOperation(Path path, Type type);
|
||||
|
||||
static PatchOperation AddOperation(Path destinationPath, Value value);
|
||||
static PatchOperation RemoveOperation(Path pathToRemove);
|
||||
static PatchOperation ReplaceOperation(Path destinationPath, Value value);
|
||||
static PatchOperation CopyOperation(Path destinationPath, Path sourcePath);
|
||||
static PatchOperation MoveOperation(Path destinationPath, Path sourcePath);
|
||||
static PatchOperation TestOperation(Path testPath, Value value);
|
||||
|
||||
PatchOperation& operator=(const PatchOperation&) = default;
|
||||
PatchOperation& operator=(PatchOperation&&) = default;
|
||||
|
||||
bool operator==(const PatchOperation& rhs) const;
|
||||
bool operator!=(const PatchOperation& rhs) const;
|
||||
|
||||
Type GetType() const;
|
||||
void SetType(Type type);
|
||||
|
||||
const Path& GetDestinationPath() const;
|
||||
void SetDestinationPath(Path path);
|
||||
|
||||
const Value& GetValue() const;
|
||||
void SetValue(Value value);
|
||||
|
||||
const Path& GetSourcePath() const;
|
||||
void SetSourcePath(Path path);
|
||||
|
||||
AZ::Outcome<Value, AZStd::string> Apply(Value rootElement) const;
|
||||
PatchOutcome ApplyInPlace(Value& rootElement) const;
|
||||
|
||||
Value GetDomRepresentation() const;
|
||||
static AZ::Outcome<PatchOperation, AZStd::string> CreateFromDomRepresentation(Value domValue);
|
||||
|
||||
using InversePatches = AZStd::fixed_vector<PatchOperation, 2>;
|
||||
AZ::Outcome<AZStd::fixed_vector<PatchOperation, 2>, AZStd::string> GetInverse(Value stateBeforeApplication) const;
|
||||
|
||||
enum class ExistenceCheckFlags : AZ::u8
|
||||
{
|
||||
DefaultExistenceCheck = 0x0,
|
||||
VerifyFullPath = 0x1,
|
||||
AllowEndOfArray = 0x2,
|
||||
};
|
||||
|
||||
private:
|
||||
struct PathContext
|
||||
{
|
||||
Value& m_value;
|
||||
PathEntry m_key;
|
||||
};
|
||||
|
||||
static AZ::Outcome<PathContext, AZStd::string> LookupPath(
|
||||
Value& rootElement, const Path& path, ExistenceCheckFlags existenceCheckFlags = ExistenceCheckFlags::DefaultExistenceCheck);
|
||||
|
||||
PatchOutcome ApplyAdd(Value& rootElement) const;
|
||||
PatchOutcome ApplyRemove(Value& rootElement) const;
|
||||
PatchOutcome ApplyReplace(Value& rootElement) const;
|
||||
PatchOutcome ApplyCopy(Value& rootElement) const;
|
||||
PatchOutcome ApplyMove(Value& rootElement) const;
|
||||
PatchOutcome ApplyTest(Value& rootElement) const;
|
||||
|
||||
AZStd::variant<AZStd::monostate, Value, Path> m_value;
|
||||
Path m_domPath;
|
||||
Type m_type;
|
||||
};
|
||||
|
||||
AZ_DEFINE_ENUM_BITWISE_OPERATORS(PatchOperation::ExistenceCheckFlags);
|
||||
|
||||
class Patch;
|
||||
|
||||
//! The current state of a Patch application operation.
|
||||
struct PatchApplicationState
|
||||
{
|
||||
//! The outcome of the last operation, may be overridden to produce a different failure outcome.
|
||||
PatchOperation::PatchOutcome m_outcome;
|
||||
//! The patch being applied.
|
||||
const Patch* m_patch = nullptr;
|
||||
//! The last operation attempted.
|
||||
const PatchOperation* m_lastOperation = nullptr;
|
||||
//! The current state of the value being patched, will be returned if the patch operation succeeds.
|
||||
Value* m_currentState = nullptr;
|
||||
//! If set to false, the patch operation should halt.
|
||||
bool m_shouldContinue = true;
|
||||
};
|
||||
|
||||
namespace PatchApplicationStrategy
|
||||
{
|
||||
//! The default patching strategy. Applies all operations in a patch, but halts if any one operation fails.
|
||||
void HaltOnFailure(PatchApplicationState& state);
|
||||
//! Patching strategy that attemps to apply all operations in a patch, but ignores operation failures and continues.
|
||||
void IgnoreFailureAndContinue(PatchApplicationState& state);
|
||||
} // namespace PatchApplicationStrategy
|
||||
|
||||
//! A set of operations that can be applied to a Value to produce a new Value.
|
||||
//! \see PatchOperation
|
||||
class Patch final
|
||||
{
|
||||
public:
|
||||
using StrategyFunctor = AZStd::function<void(PatchApplicationState&)>;
|
||||
using OperationsContainer = AZStd::deque<PatchOperation>;
|
||||
|
||||
Patch() = default;
|
||||
Patch(const Patch&) = default;
|
||||
Patch(Patch&&) = default;
|
||||
Patch(AZStd::initializer_list<PatchOperation> init);
|
||||
|
||||
template<class InputIterator>
|
||||
Patch(InputIterator first, InputIterator last)
|
||||
: m_operations(first, last)
|
||||
{
|
||||
}
|
||||
|
||||
Patch& operator=(const Patch&) = default;
|
||||
Patch& operator=(Patch&&) = default;
|
||||
|
||||
bool operator==(const Patch& rhs) const;
|
||||
bool operator!=(const Patch& rhs) const;
|
||||
|
||||
const OperationsContainer& GetOperations() const;
|
||||
void PushBack(PatchOperation op);
|
||||
void PushFront(PatchOperation op);
|
||||
void Pop();
|
||||
void Clear();
|
||||
const PatchOperation& At(size_t index) const;
|
||||
size_t Size() const;
|
||||
|
||||
PatchOperation& operator[](size_t index);
|
||||
const PatchOperation& operator[](size_t index) const;
|
||||
|
||||
OperationsContainer::iterator begin();
|
||||
OperationsContainer::iterator end();
|
||||
OperationsContainer::const_iterator begin() const;
|
||||
OperationsContainer::const_iterator end() const;
|
||||
OperationsContainer::const_iterator cbegin() const;
|
||||
OperationsContainer::const_iterator cend() const;
|
||||
size_t size() const;
|
||||
|
||||
AZ::Outcome<Value, AZStd::string> Apply(Value rootElement, StrategyFunctor strategy = PatchApplicationStrategy::HaltOnFailure) const;
|
||||
AZ::Outcome<void, AZStd::string> ApplyInPlace(Value& rootElement, StrategyFunctor strategy = PatchApplicationStrategy::HaltOnFailure) const;
|
||||
|
||||
Value GetDomRepresentation() const;
|
||||
static AZ::Outcome<Patch, AZStd::string> CreateFromDomRepresentation(Value domValue);
|
||||
|
||||
private:
|
||||
OperationsContainer m_operations;
|
||||
};
|
||||
} // namespace AZ::Dom
|
||||
@@ -53,17 +53,20 @@ namespace AZ::Dom
|
||||
|
||||
bool PathEntry::operator==(size_t value) const
|
||||
{
|
||||
return IsIndex() && GetIndex() == value;
|
||||
const size_t* internalValue = AZStd::get_if<size_t>(&m_value);
|
||||
return internalValue != nullptr && *internalValue == value;
|
||||
}
|
||||
|
||||
bool PathEntry::operator==(const AZ::Name& key) const
|
||||
{
|
||||
return IsKey() && GetKey() == key;
|
||||
const AZ::Name* internalValue = AZStd::get_if<AZ::Name>(&m_value);
|
||||
return internalValue != nullptr && *internalValue == key;
|
||||
}
|
||||
|
||||
bool PathEntry::operator==(AZStd::string_view key) const
|
||||
{
|
||||
return IsKey() && GetKey() == AZ::Name(key);
|
||||
const AZ::Name* internalValue = AZStd::get_if<AZ::Name>(&m_value);
|
||||
return internalValue != nullptr && *internalValue == AZ::Name(key);
|
||||
}
|
||||
|
||||
bool PathEntry::operator!=(const PathEntry& other) const
|
||||
@@ -73,17 +76,17 @@ namespace AZ::Dom
|
||||
|
||||
bool PathEntry::operator!=(size_t value) const
|
||||
{
|
||||
return !IsIndex() || GetIndex() != value;
|
||||
return !operator==(value);
|
||||
}
|
||||
|
||||
bool PathEntry::operator!=(const AZ::Name& key) const
|
||||
{
|
||||
return !IsKey() || GetKey() != key;
|
||||
return !operator==(key);
|
||||
}
|
||||
|
||||
bool PathEntry::operator!=(AZStd::string_view key) const
|
||||
{
|
||||
return !IsKey() || GetKey() != AZ::Name(key);
|
||||
return !operator==(key);
|
||||
}
|
||||
|
||||
void PathEntry::SetEndOfArray()
|
||||
@@ -243,6 +246,11 @@ namespace AZ::Dom
|
||||
return m_entries.size();
|
||||
}
|
||||
|
||||
bool Path::IsEmpty() const
|
||||
{
|
||||
return m_entries.empty();
|
||||
}
|
||||
|
||||
PathEntry& Path::operator[](size_t index)
|
||||
{
|
||||
return m_entries[index];
|
||||
@@ -320,13 +328,13 @@ namespace AZ::Dom
|
||||
return size;
|
||||
}
|
||||
|
||||
void Path::FormatString(char* stringBuffer, size_t bufferSize) const
|
||||
size_t Path::FormatString(char* stringBuffer, size_t bufferSize) const
|
||||
{
|
||||
size_t bufferIndex = 0;
|
||||
|
||||
auto putChar = [&](char c)
|
||||
{
|
||||
if (bufferIndex == bufferSize)
|
||||
if (bufferIndex >= bufferSize)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -357,6 +365,11 @@ namespace AZ::Dom
|
||||
|
||||
for (const PathEntry& entry : m_entries)
|
||||
{
|
||||
if (bufferIndex >= bufferSize)
|
||||
{
|
||||
return bufferIndex;
|
||||
}
|
||||
|
||||
putChar(PathSeparator);
|
||||
if (entry.IsEndOfArray())
|
||||
{
|
||||
@@ -372,7 +385,10 @@ namespace AZ::Dom
|
||||
}
|
||||
}
|
||||
|
||||
size_t bytesWritten = bufferIndex;
|
||||
putChar('\0');
|
||||
|
||||
return bytesWritten;
|
||||
}
|
||||
|
||||
AZStd::string Path::ToString() const
|
||||
|
||||
@@ -111,6 +111,7 @@ namespace AZ::Dom
|
||||
void Clear();
|
||||
PathEntry At(size_t index) const;
|
||||
size_t Size() const;
|
||||
bool IsEmpty() const;
|
||||
|
||||
PathEntry& operator[](size_t index);
|
||||
const PathEntry& operator[](size_t index) const;
|
||||
@@ -128,10 +129,19 @@ namespace AZ::Dom
|
||||
size_t GetStringLength() const;
|
||||
//! Formats a JSON-pointer style path string into the target buffer.
|
||||
//! This operation will fail if bufferSize < GetStringLength() + 1
|
||||
void FormatString(char* stringBuffer, size_t bufferSize) const;
|
||||
//! \return The number of bytes written, excepting the null terminator.
|
||||
size_t FormatString(char* stringBuffer, size_t bufferSize) const;
|
||||
//! Returns a JSON-pointer style path string for this path.
|
||||
AZStd::string ToString() const;
|
||||
|
||||
void AppendToString(AZStd::string& output) const;
|
||||
template <class T>
|
||||
void AppendToString(T& output) const
|
||||
{
|
||||
const size_t startIndex = output.length();
|
||||
output.resize_no_construct(startIndex + FormatString(output.data() + startIndex, output.capacity() - startIndex));
|
||||
}
|
||||
|
||||
//! Reads a JSON-pointer style path from pathString and replaces this path's contents.
|
||||
//! Paths are accepted in the following forms:
|
||||
//! "/path/to/foo/0"
|
||||
|
||||
@@ -116,6 +116,8 @@ set(FILES
|
||||
Debug/TraceReflection.h
|
||||
DOM/DomBackend.cpp
|
||||
DOM/DomBackend.h
|
||||
DOM/DomPatch.cpp
|
||||
DOM/DomPatch.h
|
||||
DOM/DomPath.cpp
|
||||
DOM/DomPath.h
|
||||
DOM/DomUtils.cpp
|
||||
@@ -126,6 +128,8 @@ set(FILES
|
||||
DOM/DomValueWriter.h
|
||||
DOM/DomVisitor.cpp
|
||||
DOM/DomVisitor.h
|
||||
DOM/DomComparison.cpp
|
||||
DOM/DomComparison.h
|
||||
DOM/Backends/JSON/JsonBackend.h
|
||||
DOM/Backends/JSON/JsonSerializationUtils.cpp
|
||||
DOM/Backends/JSON/JsonSerializationUtils.h
|
||||
|
||||
@@ -0,0 +1,180 @@
|
||||
/*
|
||||
* 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/DOM/DomPatch.h>
|
||||
#include <AzCore/DOM/DomUtils.h>
|
||||
#include <AzCore/DOM/DomValue.h>
|
||||
#include <AzCore/DOM/DomComparison.h>
|
||||
#include <AzCore/Name/NameDictionary.h>
|
||||
#include <AzCore/UnitTest/TestTypes.h>
|
||||
#include <Tests/DOM/DomFixtures.h>
|
||||
|
||||
namespace AZ::Dom::Benchmark
|
||||
{
|
||||
class DomPatchBenchmark : public Tests::DomBenchmarkFixture
|
||||
{
|
||||
public:
|
||||
void TearDownHarness() override
|
||||
{
|
||||
m_before = {};
|
||||
m_after = {};
|
||||
Tests::DomBenchmarkFixture::TearDownHarness();
|
||||
}
|
||||
|
||||
void SimpleReplace(benchmark::State& state, bool deepCopy, bool apply)
|
||||
{
|
||||
m_before = GenerateDomBenchmarkPayload(state.range(0), state.range(1));
|
||||
m_after = deepCopy ? Utils::DeepCopy(m_before) : m_before;
|
||||
m_after["entries"]["Key0"] = Value("replacement string", true);
|
||||
|
||||
RunBenchmarkInternal(state, apply);
|
||||
}
|
||||
|
||||
void TopLevelReplace(benchmark::State& state, bool apply)
|
||||
{
|
||||
m_before = GenerateDomBenchmarkPayload(state.range(0), state.range(1));
|
||||
m_after = Value(Type::Object);
|
||||
m_after["UnrelatedKey"] = Value(42);
|
||||
|
||||
RunBenchmarkInternal(state, apply);
|
||||
}
|
||||
|
||||
void KeyRemove(benchmark::State& state, bool deepCopy, bool apply)
|
||||
{
|
||||
m_before = GenerateDomBenchmarkPayload(state.range(0), state.range(1));
|
||||
m_after = deepCopy ? Utils::DeepCopy(m_before) : m_before;
|
||||
m_after["entries"].RemoveMember("Key1");
|
||||
|
||||
RunBenchmarkInternal(state, apply);
|
||||
}
|
||||
|
||||
void ArrayAppend(benchmark::State& state, bool deepCopy, bool apply)
|
||||
{
|
||||
m_before = GenerateDomBenchmarkPayload(state.range(0), state.range(1));
|
||||
m_after = deepCopy ? Utils::DeepCopy(m_before) : m_before;
|
||||
m_after["entries"]["Key2"].ArrayPushBack(Value(0));
|
||||
|
||||
RunBenchmarkInternal(state, apply);
|
||||
}
|
||||
|
||||
void ArrayPrepend(benchmark::State& state, bool deepCopy, bool apply)
|
||||
{
|
||||
m_before = GenerateDomBenchmarkPayload(state.range(0), state.range(1));
|
||||
m_after = deepCopy ? Utils::DeepCopy(m_before) : m_before;
|
||||
auto& arr = m_after["entries"]["Key2"].GetMutableArray();
|
||||
arr.insert(arr.begin(), Value(42));
|
||||
|
||||
RunBenchmarkInternal(state, apply);
|
||||
}
|
||||
|
||||
private:
|
||||
void RunBenchmarkInternal(benchmark::State& state, bool apply)
|
||||
{
|
||||
if (apply)
|
||||
{
|
||||
auto patchInfo = GenerateHierarchicalDeltaPatch(m_before, m_after);
|
||||
for (auto _ : state)
|
||||
{
|
||||
auto patchResult = patchInfo.m_forwardPatches.Apply(m_before);
|
||||
benchmark::DoNotOptimize(patchResult);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (auto _ : state)
|
||||
{
|
||||
auto patchInfo = GenerateHierarchicalDeltaPatch(m_before, m_after);
|
||||
benchmark::DoNotOptimize(patchInfo);
|
||||
}
|
||||
}
|
||||
|
||||
state.SetItemsProcessed(state.iterations());
|
||||
}
|
||||
|
||||
Value m_before;
|
||||
Value m_after;
|
||||
};
|
||||
|
||||
BENCHMARK_DEFINE_F(DomPatchBenchmark, AzDomPatch_Generate_SimpleReplace_ShallowCopy)(benchmark::State& state)
|
||||
{
|
||||
SimpleReplace(state, false, false);
|
||||
}
|
||||
DOM_REGISTER_SERIALIZATION_BENCHMARK_MS(DomPatchBenchmark, AzDomPatch_Generate_SimpleReplace_ShallowCopy)
|
||||
|
||||
BENCHMARK_DEFINE_F(DomPatchBenchmark, AzDomPatch_Generate_SimpleReplace_DeepCopy)(benchmark::State& state)
|
||||
{
|
||||
SimpleReplace(state, true, false);
|
||||
}
|
||||
DOM_REGISTER_SERIALIZATION_BENCHMARK_MS(DomPatchBenchmark, AzDomPatch_Generate_SimpleReplace_DeepCopy)
|
||||
|
||||
BENCHMARK_DEFINE_F(DomPatchBenchmark, AzDomPatch_Generate_TopLevelReplace)(benchmark::State& state)
|
||||
{
|
||||
TopLevelReplace(state, false);
|
||||
}
|
||||
DOM_REGISTER_SERIALIZATION_BENCHMARK_MS(DomPatchBenchmark, AzDomPatch_Generate_TopLevelReplace)
|
||||
|
||||
BENCHMARK_DEFINE_F(DomPatchBenchmark, AzDomPatch_Generate_KeyRemove_ShallowCopy)(benchmark::State& state)
|
||||
{
|
||||
KeyRemove(state, false, false);
|
||||
}
|
||||
DOM_REGISTER_SERIALIZATION_BENCHMARK_MS(DomPatchBenchmark, AzDomPatch_Generate_KeyRemove_ShallowCopy)
|
||||
|
||||
BENCHMARK_DEFINE_F(DomPatchBenchmark, AzDomPatch_Generate_KeyRemove_DeepCopy)(benchmark::State& state)
|
||||
{
|
||||
KeyRemove(state, true, false);
|
||||
}
|
||||
DOM_REGISTER_SERIALIZATION_BENCHMARK_MS(DomPatchBenchmark, AzDomPatch_Generate_KeyRemove_DeepCopy)
|
||||
|
||||
BENCHMARK_DEFINE_F(DomPatchBenchmark, AzDomPatch_Generate_ArrayAppend_ShallowCopy)(benchmark::State& state)
|
||||
{
|
||||
ArrayAppend(state, false, false);
|
||||
}
|
||||
DOM_REGISTER_SERIALIZATION_BENCHMARK_MS(DomPatchBenchmark, AzDomPatch_Generate_ArrayAppend_ShallowCopy)
|
||||
|
||||
BENCHMARK_DEFINE_F(DomPatchBenchmark, AzDomPatch_Generate_ArrayAppend_DeepCopy)(benchmark::State& state)
|
||||
{
|
||||
ArrayAppend(state, true, false);
|
||||
}
|
||||
DOM_REGISTER_SERIALIZATION_BENCHMARK_MS(DomPatchBenchmark, AzDomPatch_Generate_ArrayAppend_DeepCopy)
|
||||
|
||||
BENCHMARK_DEFINE_F(DomPatchBenchmark, AzDomPatch_Generate_ArrayPrepend)(benchmark::State& state)
|
||||
{
|
||||
ArrayPrepend(state, true, false);
|
||||
}
|
||||
DOM_REGISTER_SERIALIZATION_BENCHMARK_MS(DomPatchBenchmark, AzDomPatch_Generate_ArrayPrepend)
|
||||
|
||||
BENCHMARK_DEFINE_F(DomPatchBenchmark, AzDomPatch_Apply_SimpleReplace)(benchmark::State& state)
|
||||
{
|
||||
SimpleReplace(state, true, true);
|
||||
}
|
||||
DOM_REGISTER_SERIALIZATION_BENCHMARK_MS(DomPatchBenchmark, AzDomPatch_Apply_SimpleReplace)
|
||||
|
||||
BENCHMARK_DEFINE_F(DomPatchBenchmark, AzDomPatch_Apply_TopLevelReplace)(benchmark::State& state)
|
||||
{
|
||||
TopLevelReplace(state, true);
|
||||
}
|
||||
DOM_REGISTER_SERIALIZATION_BENCHMARK_MS(DomPatchBenchmark, AzDomPatch_Apply_TopLevelReplace)
|
||||
|
||||
BENCHMARK_DEFINE_F(DomPatchBenchmark, AzDomPatch_Apply_KeyRemove)(benchmark::State& state)
|
||||
{
|
||||
KeyRemove(state, true, true);
|
||||
}
|
||||
DOM_REGISTER_SERIALIZATION_BENCHMARK_MS(DomPatchBenchmark, AzDomPatch_Apply_KeyRemove)
|
||||
|
||||
BENCHMARK_DEFINE_F(DomPatchBenchmark, AzDomPatch_Apply_ArrayAppend)(benchmark::State& state)
|
||||
{
|
||||
ArrayAppend(state, true, true);
|
||||
}
|
||||
DOM_REGISTER_SERIALIZATION_BENCHMARK_MS(DomPatchBenchmark, AzDomPatch_Apply_ArrayAppend)
|
||||
|
||||
BENCHMARK_DEFINE_F(DomPatchBenchmark, AzDomPatch_Apply_ArrayPrepend)(benchmark::State& state)
|
||||
{
|
||||
ArrayPrepend(state, true, true);
|
||||
}
|
||||
DOM_REGISTER_SERIALIZATION_BENCHMARK_MS(DomPatchBenchmark, AzDomPatch_Apply_ArrayPrepend)
|
||||
} // namespace AZ::Dom::Benchmark
|
||||
@@ -0,0 +1,563 @@
|
||||
/*
|
||||
* 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/DOM/DomPatch.h>
|
||||
#include <AzCore/DOM/DomComparison.h>
|
||||
#include <Tests/DOM/DomFixtures.h>
|
||||
|
||||
namespace AZ::Dom::Tests
|
||||
{
|
||||
class DomPatchTests : public DomTestFixture
|
||||
{
|
||||
public:
|
||||
void SetUp() override
|
||||
{
|
||||
DomTestFixture::SetUp();
|
||||
|
||||
m_dataset = Value(Type::Object);
|
||||
m_dataset["arr"].SetArray();
|
||||
|
||||
m_dataset["node"].SetNode("SomeNode");
|
||||
m_dataset["node"]["int"] = 5;
|
||||
m_dataset["node"]["null"] = Value();
|
||||
|
||||
for (int i = 0; i < 5; ++i)
|
||||
{
|
||||
m_dataset["arr"].ArrayPushBack(Value(i));
|
||||
m_dataset["node"].ArrayPushBack(Value(i * 2));
|
||||
}
|
||||
|
||||
m_dataset["obj"].SetObject();
|
||||
m_dataset["obj"]["foo"] = true;
|
||||
m_dataset["obj"]["bar"] = false;
|
||||
|
||||
m_deltaDataset = m_dataset;
|
||||
}
|
||||
|
||||
void TearDown() override
|
||||
{
|
||||
m_dataset = m_deltaDataset = Value();
|
||||
|
||||
DomTestFixture::TearDown();
|
||||
}
|
||||
|
||||
PatchUndoRedoInfo GenerateAndVerifyDelta()
|
||||
{
|
||||
PatchUndoRedoInfo info = GenerateHierarchicalDeltaPatch(m_dataset, m_deltaDataset);
|
||||
|
||||
auto result = info.m_forwardPatches.Apply(m_dataset);
|
||||
EXPECT_TRUE(result.IsSuccess());
|
||||
EXPECT_TRUE(Utils::DeepCompareIsEqual(result.GetValue(), m_deltaDataset));
|
||||
|
||||
result = info.m_inversePatches.Apply(result.GetValue());
|
||||
EXPECT_TRUE(result.IsSuccess());
|
||||
EXPECT_TRUE(Utils::DeepCompareIsEqual(result.GetValue(), m_dataset));
|
||||
|
||||
// Verify serialization of the patches
|
||||
auto VerifySerialization = [](const Patch& patch)
|
||||
{
|
||||
Value serializedPatch = patch.GetDomRepresentation();
|
||||
auto deserializePatchResult = Patch::CreateFromDomRepresentation(serializedPatch);
|
||||
EXPECT_TRUE(deserializePatchResult.IsSuccess());
|
||||
EXPECT_EQ(deserializePatchResult.GetValue(), patch);
|
||||
};
|
||||
VerifySerialization(info.m_forwardPatches);
|
||||
VerifySerialization(info.m_inversePatches);
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
Value m_dataset;
|
||||
Value m_deltaDataset;
|
||||
};
|
||||
|
||||
TEST_F(DomPatchTests, AddOperation_InsertInObject_Succeeds)
|
||||
{
|
||||
Path p("/obj/baz");
|
||||
PatchOperation op = PatchOperation::AddOperation(p, Value(42));
|
||||
auto result = op.Apply(m_dataset);
|
||||
ASSERT_TRUE(result.IsSuccess());
|
||||
EXPECT_EQ(result.GetValue()[p].GetInt64(), 42);
|
||||
}
|
||||
|
||||
TEST_F(DomPatchTests, AddOperation_ReplaceInObject_Succeeds)
|
||||
{
|
||||
Path p("/obj/foo");
|
||||
PatchOperation op = PatchOperation::AddOperation(p, Value(false));
|
||||
auto result = op.Apply(m_dataset);
|
||||
ASSERT_TRUE(result.IsSuccess());
|
||||
EXPECT_EQ(result.GetValue()[p].GetBool(), false);
|
||||
}
|
||||
|
||||
TEST_F(DomPatchTests, AddOperation_InsertObjectKeyInArray_Fails)
|
||||
{
|
||||
Path p("/arr/key");
|
||||
PatchOperation op = PatchOperation::AddOperation(p, Value(999));
|
||||
auto result = op.Apply(m_dataset);
|
||||
ASSERT_FALSE(result.IsSuccess());
|
||||
}
|
||||
|
||||
TEST_F(DomPatchTests, AddOperation_AppendInArray_Succeeds)
|
||||
{
|
||||
Path p("/arr/-");
|
||||
PatchOperation op = PatchOperation::AddOperation(p, Value(42));
|
||||
auto result = op.Apply(m_dataset);
|
||||
ASSERT_TRUE(result.IsSuccess());
|
||||
EXPECT_EQ(result.GetValue()["arr"][5].GetInt64(), 42);
|
||||
}
|
||||
|
||||
TEST_F(DomPatchTests, AddOperation_InsertKeyInNode_Succeeds)
|
||||
{
|
||||
Path p("/node/attr");
|
||||
PatchOperation op = PatchOperation::AddOperation(p, Value(500));
|
||||
auto result = op.Apply(m_dataset);
|
||||
ASSERT_TRUE(result.IsSuccess());
|
||||
EXPECT_EQ(result.GetValue()[p].GetInt64(), 500);
|
||||
}
|
||||
|
||||
TEST_F(DomPatchTests, AddOperation_ReplaceIndexInNode_Succeeds)
|
||||
{
|
||||
Path p("/node/0");
|
||||
PatchOperation op = PatchOperation::AddOperation(p, Value(42));
|
||||
auto result = op.Apply(m_dataset);
|
||||
ASSERT_TRUE(result.IsSuccess());
|
||||
EXPECT_EQ(result.GetValue()[p].GetInt64(), 42);
|
||||
}
|
||||
|
||||
TEST_F(DomPatchTests, AddOperation_AppendInNode_Succeeds)
|
||||
{
|
||||
Path p("/node/-");
|
||||
PatchOperation op = PatchOperation::AddOperation(p, Value(42));
|
||||
auto result = op.Apply(m_dataset);
|
||||
ASSERT_TRUE(result.IsSuccess());
|
||||
EXPECT_EQ(result.GetValue()["node"][5].GetInt64(), 42);
|
||||
}
|
||||
|
||||
TEST_F(DomPatchTests, AddOperation_InvalidPath_Fails)
|
||||
{
|
||||
Path p("/non/existent/path");
|
||||
PatchOperation op = PatchOperation::AddOperation(p, Value(0));
|
||||
auto result = op.Apply(m_dataset);
|
||||
ASSERT_FALSE(result.IsSuccess());
|
||||
}
|
||||
|
||||
TEST_F(DomPatchTests, RemoveOperation_RemoveKeyFromObject_Succeeds)
|
||||
{
|
||||
Path p("/obj/foo");
|
||||
PatchOperation op = PatchOperation::RemoveOperation(p);
|
||||
auto result = op.Apply(m_dataset);
|
||||
ASSERT_TRUE(result.IsSuccess());
|
||||
EXPECT_FALSE(result.GetValue()["obj"].HasMember("foo"));
|
||||
}
|
||||
|
||||
TEST_F(DomPatchTests, RemoveOperation_RemoveIndexFromArray_Succeeds)
|
||||
{
|
||||
Path p("/arr/0");
|
||||
PatchOperation op = PatchOperation::RemoveOperation(p);
|
||||
auto result = op.Apply(m_dataset);
|
||||
ASSERT_TRUE(result.IsSuccess());
|
||||
EXPECT_EQ(result.GetValue()["arr"].ArraySize(), 4);
|
||||
EXPECT_EQ(result.GetValue()["arr"][0].GetInt64(), 1);
|
||||
}
|
||||
|
||||
TEST_F(DomPatchTests, RemoveOperation_PopArray_Succeeds)
|
||||
{
|
||||
Path p("/arr/-");
|
||||
PatchOperation op = PatchOperation::RemoveOperation(p);
|
||||
auto result = op.Apply(m_dataset);
|
||||
EXPECT_EQ(result.GetValue()["arr"].ArraySize(), 4);
|
||||
}
|
||||
|
||||
TEST_F(DomPatchTests, RemoveOperation_RemoveKeyFromNode_Succeeds)
|
||||
{
|
||||
Path p("/node/int");
|
||||
PatchOperation op = PatchOperation::RemoveOperation(p);
|
||||
auto result = op.Apply(m_dataset);
|
||||
ASSERT_TRUE(result.IsSuccess());
|
||||
EXPECT_FALSE(result.GetValue()["node"].HasMember("int"));
|
||||
}
|
||||
|
||||
TEST_F(DomPatchTests, RemoveOperation_RemoveIndexFromNode_Succeeds)
|
||||
{
|
||||
Path p("/node/1");
|
||||
PatchOperation op = PatchOperation::RemoveOperation(p);
|
||||
auto result = op.Apply(m_dataset);
|
||||
ASSERT_TRUE(result.IsSuccess());
|
||||
EXPECT_EQ(result.GetValue()["node"].ArraySize(), 4);
|
||||
EXPECT_EQ(result.GetValue()["node"][1].GetInt64(), 4);
|
||||
}
|
||||
|
||||
TEST_F(DomPatchTests, RemoveOperation_PopIndexFromNode_Succeeds)
|
||||
{
|
||||
Path p("/node/-");
|
||||
PatchOperation op = PatchOperation::RemoveOperation(p);
|
||||
auto result = op.Apply(m_dataset);
|
||||
ASSERT_TRUE(result.IsSuccess());
|
||||
EXPECT_EQ(result.GetValue()["node"].ArraySize(), 4);
|
||||
}
|
||||
|
||||
TEST_F(DomPatchTests, RemoveOperation_RemoveKeyFromArray_Fails)
|
||||
{
|
||||
Path p("/arr/foo");
|
||||
PatchOperation op = PatchOperation::RemoveOperation(p);
|
||||
auto result = op.Apply(m_dataset);
|
||||
ASSERT_FALSE(result.IsSuccess());
|
||||
}
|
||||
|
||||
TEST_F(DomPatchTests, RemoveOperation_InvalidPath_Fails)
|
||||
{
|
||||
Path p("/non/existent/path");
|
||||
PatchOperation op = PatchOperation::RemoveOperation(p);
|
||||
auto result = op.Apply(m_dataset);
|
||||
ASSERT_FALSE(result.IsSuccess());
|
||||
}
|
||||
|
||||
TEST_F(DomPatchTests, ReplaceOperation_InsertInObject_Fails)
|
||||
{
|
||||
Path p("/obj/baz");
|
||||
PatchOperation op = PatchOperation::ReplaceOperation(p, Value(42));
|
||||
auto result = op.Apply(m_dataset);
|
||||
ASSERT_FALSE(result.IsSuccess());
|
||||
}
|
||||
|
||||
TEST_F(DomPatchTests, ReplaceOperation_ReplaceInObject_Succeeds)
|
||||
{
|
||||
Path p("/obj/foo");
|
||||
PatchOperation op = PatchOperation::ReplaceOperation(p, Value(false));
|
||||
auto result = op.Apply(m_dataset);
|
||||
ASSERT_TRUE(result.IsSuccess());
|
||||
EXPECT_EQ(result.GetValue()[p].GetBool(), false);
|
||||
}
|
||||
|
||||
TEST_F(DomPatchTests, ReplaceOperation_InsertObjectKeyInArray_Fails)
|
||||
{
|
||||
Path p("/arr/key");
|
||||
PatchOperation op = PatchOperation::ReplaceOperation(p, Value(999));
|
||||
auto result = op.Apply(m_dataset);
|
||||
ASSERT_FALSE(result.IsSuccess());
|
||||
}
|
||||
|
||||
TEST_F(DomPatchTests, ReplaceOperation_AppendInArray_Fails)
|
||||
{
|
||||
Path p("/arr/-");
|
||||
PatchOperation op = PatchOperation::ReplaceOperation(p, Value(42));
|
||||
auto result = op.Apply(m_dataset);
|
||||
ASSERT_FALSE(result.IsSuccess());
|
||||
}
|
||||
|
||||
TEST_F(DomPatchTests, ReplaceOperation_InsertKeyInNode_Fails)
|
||||
{
|
||||
Path p("/node/attr");
|
||||
PatchOperation op = PatchOperation::ReplaceOperation(p, Value(500));
|
||||
auto result = op.Apply(m_dataset);
|
||||
ASSERT_FALSE(result.IsSuccess());
|
||||
}
|
||||
|
||||
TEST_F(DomPatchTests, ReplaceOperation_ReplaceIndexInNode_Succeeds)
|
||||
{
|
||||
Path p("/node/0");
|
||||
PatchOperation op = PatchOperation::ReplaceOperation(p, Value(42));
|
||||
auto result = op.Apply(m_dataset);
|
||||
ASSERT_TRUE(result.IsSuccess());
|
||||
EXPECT_EQ(result.GetValue()[p].GetInt64(), 42);
|
||||
}
|
||||
|
||||
TEST_F(DomPatchTests, ReplaceOperation_AppendInNode_Fails)
|
||||
{
|
||||
Path p("/node/-");
|
||||
PatchOperation op = PatchOperation::ReplaceOperation(p, Value(42));
|
||||
auto result = op.Apply(m_dataset);
|
||||
ASSERT_FALSE(result.IsSuccess());
|
||||
}
|
||||
|
||||
TEST_F(DomPatchTests, ReplaceOperation_InvalidPath_Fails)
|
||||
{
|
||||
Path p("/non/existent/path");
|
||||
PatchOperation op = PatchOperation::ReplaceOperation(p, Value(0));
|
||||
auto result = op.Apply(m_dataset);
|
||||
ASSERT_FALSE(result.IsSuccess());
|
||||
}
|
||||
|
||||
TEST_F(DomPatchTests, CopyOperation_ArrayToObject_Succeeds)
|
||||
{
|
||||
Path dest("/obj/arr");
|
||||
Path src("/arr");
|
||||
PatchOperation op = PatchOperation::CopyOperation(dest, src);
|
||||
auto result = op.Apply(m_dataset);
|
||||
ASSERT_TRUE(result.IsSuccess());
|
||||
EXPECT_TRUE(Utils::DeepCompareIsEqual(m_dataset[src], result.GetValue()[dest]));
|
||||
EXPECT_TRUE(Utils::DeepCompareIsEqual(result.GetValue()[src], result.GetValue()[dest]));
|
||||
}
|
||||
|
||||
TEST_F(DomPatchTests, CopyOperation_ObjectToArrayInRange_Succeeds)
|
||||
{
|
||||
Path dest("/arr/0");
|
||||
Path src("/obj");
|
||||
PatchOperation op = PatchOperation::CopyOperation(dest, src);
|
||||
auto result = op.Apply(m_dataset);
|
||||
ASSERT_TRUE(result.IsSuccess());
|
||||
EXPECT_TRUE(Utils::DeepCompareIsEqual(m_dataset[src], result.GetValue()[dest]));
|
||||
EXPECT_TRUE(Utils::DeepCompareIsEqual(result.GetValue()[src], result.GetValue()[dest]));
|
||||
}
|
||||
|
||||
TEST_F(DomPatchTests, CopyOperation_ObjectToArrayOutOfRange_Fails)
|
||||
{
|
||||
Path dest("/arr/5");
|
||||
Path src("/obj");
|
||||
PatchOperation op = PatchOperation::CopyOperation(dest, src);
|
||||
auto result = op.Apply(m_dataset);
|
||||
ASSERT_FALSE(result.IsSuccess());
|
||||
}
|
||||
|
||||
TEST_F(DomPatchTests, CopyOperation_ObjectToNodeChildInRange_Succeeds)
|
||||
{
|
||||
Path dest("/node/0");
|
||||
Path src("/obj");
|
||||
PatchOperation op = PatchOperation::CopyOperation(dest, src);
|
||||
auto result = op.Apply(m_dataset);
|
||||
ASSERT_TRUE(result.IsSuccess());
|
||||
EXPECT_TRUE(Utils::DeepCompareIsEqual(m_dataset[src], result.GetValue()[dest]));
|
||||
EXPECT_TRUE(Utils::DeepCompareIsEqual(result.GetValue()[src], result.GetValue()[dest]));
|
||||
}
|
||||
|
||||
TEST_F(DomPatchTests, CopyOperation_ObjectToNodeChildOutOfRange_Fails)
|
||||
{
|
||||
Path dest("/node/5");
|
||||
Path src("/obj");
|
||||
PatchOperation op = PatchOperation::CopyOperation(dest, src);
|
||||
auto result = op.Apply(m_dataset);
|
||||
ASSERT_FALSE(result.IsSuccess());
|
||||
}
|
||||
|
||||
TEST_F(DomPatchTests, CopyOperation_InvalidSourcePath_Fails)
|
||||
{
|
||||
Path dest("/node/0");
|
||||
Path src("/invalid/path");
|
||||
PatchOperation op = PatchOperation::CopyOperation(dest, src);
|
||||
auto result = op.Apply(m_dataset);
|
||||
ASSERT_FALSE(result.IsSuccess());
|
||||
}
|
||||
|
||||
TEST_F(DomPatchTests, CopyOperation_InvalidDestinationPath_Fails)
|
||||
{
|
||||
Path dest("/invalid/path");
|
||||
Path src("/arr/0");
|
||||
PatchOperation op = PatchOperation::CopyOperation(dest, src);
|
||||
auto result = op.Apply(m_dataset);
|
||||
ASSERT_FALSE(result.IsSuccess());
|
||||
}
|
||||
|
||||
TEST_F(DomPatchTests, MoveOperation_ArrayToObject_Succeeds)
|
||||
{
|
||||
Path dest("/obj/arr");
|
||||
Path src("/arr");
|
||||
PatchOperation op = PatchOperation::MoveOperation(dest, src);
|
||||
auto result = op.Apply(m_dataset);
|
||||
ASSERT_TRUE(result.IsSuccess());
|
||||
EXPECT_TRUE(Utils::DeepCompareIsEqual(m_dataset[src], result.GetValue()[dest]));
|
||||
EXPECT_FALSE(result.GetValue().HasMember("arr"));
|
||||
}
|
||||
|
||||
TEST_F(DomPatchTests, MoveOperation_ObjectToArrayInRange_Succeeds)
|
||||
{
|
||||
Path dest("/arr/0");
|
||||
Path src("/obj");
|
||||
PatchOperation op = PatchOperation::MoveOperation(dest, src);
|
||||
auto result = op.Apply(m_dataset);
|
||||
ASSERT_TRUE(result.IsSuccess());
|
||||
EXPECT_TRUE(Utils::DeepCompareIsEqual(m_dataset[src], result.GetValue()[dest]));
|
||||
EXPECT_FALSE(result.GetValue().HasMember("obj"));
|
||||
}
|
||||
|
||||
TEST_F(DomPatchTests, MoveOperation_ObjectToArrayOutOfRange_Fails)
|
||||
{
|
||||
Path dest("/arr/5");
|
||||
Path src("/obj");
|
||||
PatchOperation op = PatchOperation::MoveOperation(dest, src);
|
||||
auto result = op.Apply(m_dataset);
|
||||
ASSERT_FALSE(result.IsSuccess());
|
||||
}
|
||||
|
||||
TEST_F(DomPatchTests, MoveOperation_ObjectToNodeChildInRange_Succeeds)
|
||||
{
|
||||
Path dest("/node/0");
|
||||
Path src("/obj");
|
||||
PatchOperation op = PatchOperation::MoveOperation(dest, src);
|
||||
auto result = op.Apply(m_dataset);
|
||||
ASSERT_TRUE(result.IsSuccess());
|
||||
EXPECT_TRUE(Utils::DeepCompareIsEqual(m_dataset[src], result.GetValue()[dest]));
|
||||
EXPECT_FALSE(result.GetValue().HasMember("obj"));
|
||||
}
|
||||
|
||||
TEST_F(DomPatchTests, MoveOperation_ObjectToNodeChildOutOfRange_Fails)
|
||||
{
|
||||
Path dest("/node/5");
|
||||
Path src("/obj");
|
||||
PatchOperation op = PatchOperation::MoveOperation(dest, src);
|
||||
auto result = op.Apply(m_dataset);
|
||||
ASSERT_FALSE(result.IsSuccess());
|
||||
}
|
||||
|
||||
TEST_F(DomPatchTests, MoveOperation_InvalidSourcePath_Fails)
|
||||
{
|
||||
Path dest("/node/0");
|
||||
Path src("/invalid/path");
|
||||
PatchOperation op = PatchOperation::MoveOperation(dest, src);
|
||||
auto result = op.Apply(m_dataset);
|
||||
ASSERT_FALSE(result.IsSuccess());
|
||||
}
|
||||
|
||||
TEST_F(DomPatchTests, MoveOperation_InvalidDestinationPath_Fails)
|
||||
{
|
||||
Path dest("/invalid/path");
|
||||
Path src("/arr/0");
|
||||
PatchOperation op = PatchOperation::MoveOperation(dest, src);
|
||||
auto result = op.Apply(m_dataset);
|
||||
ASSERT_FALSE(result.IsSuccess());
|
||||
}
|
||||
|
||||
TEST_F(DomPatchTests, TestOperation_TestCorrectValue_Succeeds)
|
||||
{
|
||||
Path path("/arr/1");
|
||||
Value value(1);
|
||||
PatchOperation op = PatchOperation::TestOperation(path, value);
|
||||
auto result = op.Apply(m_dataset);
|
||||
ASSERT_TRUE(result.IsSuccess());
|
||||
}
|
||||
|
||||
TEST_F(DomPatchTests, TestOperation_TestIncorrectValue_Fails)
|
||||
{
|
||||
Path path("/arr/1");
|
||||
Value value(55);
|
||||
PatchOperation op = PatchOperation::TestOperation(path, value);
|
||||
auto result = op.Apply(m_dataset);
|
||||
ASSERT_FALSE(result.IsSuccess());
|
||||
}
|
||||
|
||||
TEST_F(DomPatchTests, TestOperation_TestCorrectComplexValue_Succeeds)
|
||||
{
|
||||
Path path;
|
||||
Value value = m_dataset;
|
||||
PatchOperation op = PatchOperation::TestOperation(path, value);
|
||||
auto result = op.Apply(m_dataset);
|
||||
ASSERT_TRUE(result.IsSuccess());
|
||||
}
|
||||
|
||||
TEST_F(DomPatchTests, TestOperation_TestIncorrectComplexValue_Fails)
|
||||
{
|
||||
Path path;
|
||||
Value value = m_dataset;
|
||||
value["arr"][4] = 9;
|
||||
PatchOperation op = PatchOperation::TestOperation(path, value);
|
||||
auto result = op.Apply(m_dataset);
|
||||
ASSERT_FALSE(result.IsSuccess());
|
||||
}
|
||||
|
||||
TEST_F(DomPatchTests, TestOperation_TestInvalidPath_Fails)
|
||||
{
|
||||
Path path("/invalid/path");
|
||||
Value value;
|
||||
PatchOperation op = PatchOperation::TestOperation(path, value);
|
||||
auto result = op.Apply(m_dataset);
|
||||
ASSERT_FALSE(result.IsSuccess());
|
||||
}
|
||||
|
||||
TEST_F(DomPatchTests, TestOperation_TestInsertArrayPath_Fails)
|
||||
{
|
||||
Path path("/arr/-");
|
||||
Value value(4);
|
||||
PatchOperation op = PatchOperation::TestOperation(path, value);
|
||||
auto result = op.Apply(m_dataset);
|
||||
ASSERT_FALSE(result.IsSuccess());
|
||||
}
|
||||
|
||||
TEST_F(DomPatchTests, TestPatch_ReplaceArrayValue)
|
||||
{
|
||||
m_deltaDataset["arr"][0] = 5;
|
||||
GenerateAndVerifyDelta();
|
||||
}
|
||||
|
||||
TEST_F(DomPatchTests, TestPatch_AppendArrayValue)
|
||||
{
|
||||
m_deltaDataset["arr"].ArrayPushBack(Value(7));
|
||||
auto result = GenerateAndVerifyDelta();
|
||||
|
||||
// Ensure the generated patch uses the array append operation
|
||||
ASSERT_EQ(result.m_forwardPatches.Size(), 1);
|
||||
EXPECT_TRUE(result.m_forwardPatches[0].GetDestinationPath()[1].IsEndOfArray());
|
||||
}
|
||||
|
||||
TEST_F(DomPatchTests, TestPatch_AppendArrayValues)
|
||||
{
|
||||
m_deltaDataset["arr"].ArrayPushBack(Value(7));
|
||||
m_deltaDataset["arr"].ArrayPushBack(Value(8));
|
||||
m_deltaDataset["arr"].ArrayPushBack(Value(9));
|
||||
GenerateAndVerifyDelta();
|
||||
}
|
||||
|
||||
TEST_F(DomPatchTests, TestPatch_InsertArrayValue)
|
||||
{
|
||||
auto& arr = m_deltaDataset["arr"].GetMutableArray();
|
||||
arr.insert(arr.begin(), Value(42));
|
||||
GenerateAndVerifyDelta();
|
||||
}
|
||||
|
||||
TEST_F(DomPatchTests, TestPatch_InsertObjectKey)
|
||||
{
|
||||
m_deltaDataset["obj"]["newKey"].CopyFromString("test");
|
||||
GenerateAndVerifyDelta();
|
||||
}
|
||||
|
||||
TEST_F(DomPatchTests, TestPatch_DeleteObjectKey)
|
||||
{
|
||||
m_deltaDataset["obj"].RemoveMember("foo");
|
||||
GenerateAndVerifyDelta();
|
||||
}
|
||||
|
||||
TEST_F(DomPatchTests, TestPatch_AppendNodeValues)
|
||||
{
|
||||
m_deltaDataset["node"].ArrayPushBack(Value(7));
|
||||
m_deltaDataset["node"].ArrayPushBack(Value(8));
|
||||
m_deltaDataset["node"].ArrayPushBack(Value(9));
|
||||
GenerateAndVerifyDelta();
|
||||
}
|
||||
|
||||
TEST_F(DomPatchTests, TestPatch_InsertNodeValue)
|
||||
{
|
||||
auto& node = m_deltaDataset["node"].GetMutableNode();
|
||||
node.GetChildren().insert(node.GetChildren().begin(), Value(42));
|
||||
GenerateAndVerifyDelta();
|
||||
}
|
||||
|
||||
TEST_F(DomPatchTests, TestPatch_InsertNodeKey)
|
||||
{
|
||||
m_deltaDataset["node"]["newKey"].CopyFromString("test");
|
||||
GenerateAndVerifyDelta();
|
||||
}
|
||||
|
||||
TEST_F(DomPatchTests, TestPatch_DeleteNodeKey)
|
||||
{
|
||||
m_deltaDataset["node"].RemoveMember("int");
|
||||
GenerateAndVerifyDelta();
|
||||
}
|
||||
|
||||
TEST_F(DomPatchTests, TestPatch_RenameNode)
|
||||
{
|
||||
m_deltaDataset["node"].SetNodeName("RenamedNode");
|
||||
GenerateAndVerifyDelta();
|
||||
}
|
||||
|
||||
TEST_F(DomPatchTests, TestPatch_ReplaceRoot)
|
||||
{
|
||||
m_deltaDataset = Value(Type::Array);
|
||||
m_deltaDataset.ArrayPushBack(Value(2));
|
||||
m_deltaDataset.ArrayPushBack(Value(4));
|
||||
m_deltaDataset.ArrayPushBack(Value(6));
|
||||
GenerateAndVerifyDelta();
|
||||
}
|
||||
} // namespace AZ::Dom::Tests
|
||||
@@ -96,4 +96,25 @@ namespace AZ::Dom::Benchmark
|
||||
state.SetItemsProcessed(3 * state.iterations());
|
||||
}
|
||||
BENCHMARK_REGISTER_F(DomPathBenchmark, DomPathEntry_IsEndOfArray);
|
||||
|
||||
BENCHMARK_DEFINE_F(DomPathBenchmark, DomPathEntry_Comparison)(benchmark::State& state)
|
||||
{
|
||||
PathEntry name("name");
|
||||
PathEntry index(0);
|
||||
PathEntry endOfArray;
|
||||
endOfArray.SetEndOfArray();
|
||||
|
||||
for (auto _ : state)
|
||||
{
|
||||
benchmark::DoNotOptimize(name == name);
|
||||
benchmark::DoNotOptimize(name == index);
|
||||
benchmark::DoNotOptimize(name == endOfArray);
|
||||
benchmark::DoNotOptimize(index == index);
|
||||
benchmark::DoNotOptimize(index == endOfArray);
|
||||
benchmark::DoNotOptimize(endOfArray == endOfArray);
|
||||
}
|
||||
|
||||
state.SetItemsProcessed(6 * state.iterations());
|
||||
}
|
||||
BENCHMARK_REGISTER_F(DomPathBenchmark, DomPathEntry_Comparison);
|
||||
}
|
||||
|
||||
@@ -174,4 +174,23 @@ namespace AZ::Dom::Tests
|
||||
p.AppendToString(s);
|
||||
EXPECT_EQ(s, "/foo/0/foo/0");
|
||||
}
|
||||
|
||||
TEST_F(DomPathTests, MixedPath_AppendToFixedString)
|
||||
{
|
||||
Path p("/foo/0");
|
||||
|
||||
{
|
||||
AZStd::fixed_string<7> s;
|
||||
p.AppendToString(s);
|
||||
EXPECT_EQ(s, "/foo/0");
|
||||
}
|
||||
|
||||
{
|
||||
AZStd::fixed_string<9> s;
|
||||
p.AppendToString(s);
|
||||
EXPECT_EQ(s, "/foo/0");
|
||||
p.AppendToString(s);
|
||||
EXPECT_EQ(s, "/foo/0/fo");
|
||||
}
|
||||
}
|
||||
} // namespace AZ::Dom::Tests
|
||||
|
||||
@@ -224,6 +224,8 @@ set(FILES
|
||||
DOM/DomJsonBenchmarks.cpp
|
||||
DOM/DomPathTests.cpp
|
||||
DOM/DomPathBenchmarks.cpp
|
||||
DOM/DomPatchTests.cpp
|
||||
DOM/DomPatchBenchmarks.cpp
|
||||
DOM/DomValueTests.cpp
|
||||
DOM/DomValueBenchmarks.cpp
|
||||
)
|
||||
|
||||
@@ -292,7 +292,7 @@ namespace AzToolsFramework::Prefab
|
||||
return false;
|
||||
}
|
||||
|
||||
const InstanceOptionalConstReference instance = m_instanceEntityMapperInterface->FindOwningInstance(entityId);
|
||||
InstanceOptionalReference instance = m_instanceEntityMapperInterface->FindOwningInstance(entityId);
|
||||
if (!instance.has_value())
|
||||
{
|
||||
return false;
|
||||
@@ -308,7 +308,7 @@ namespace AzToolsFramework::Prefab
|
||||
return false;
|
||||
}
|
||||
|
||||
InstanceOptionalConstReference instance = m_instanceEntityMapperInterface->FindOwningInstance(entityId);
|
||||
InstanceOptionalReference instance = m_instanceEntityMapperInterface->FindOwningInstance(entityId);
|
||||
while (instance.has_value())
|
||||
{
|
||||
if (instance->get().GetAbsoluteInstanceAliasPath() == m_rootAliasFocusPath)
|
||||
|
||||
+1
-4
@@ -235,10 +235,7 @@ namespace AzToolsFramework::Prefab::PrefabConversionUtils
|
||||
|
||||
if (!asset->GetId().IsValid())
|
||||
{
|
||||
AZ_Error(
|
||||
"Prefab", false,
|
||||
"Invalid asset found referenced in scene while entering game mode. The asset was stored in an instance of %s.",
|
||||
classData->m_name);
|
||||
// Invalid asset found referenced in scene while entering game mode.
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -49,8 +49,7 @@ namespace ImageProcessingAtom
|
||||
static const unsigned int s_MinReduceLevel = 0;
|
||||
static const unsigned int s_MaxReduceLevel = 5;
|
||||
|
||||
static const int s_TotalSupportedImageExtensions = 10;
|
||||
static const char* s_SupportedImageExtensions[s_TotalSupportedImageExtensions] = {
|
||||
static const char* s_SupportedImageExtensions[] = {
|
||||
"*.tif",
|
||||
"*.tiff",
|
||||
"*.png",
|
||||
@@ -62,6 +61,7 @@ namespace ImageProcessingAtom
|
||||
"*.dds",
|
||||
"*.exr"
|
||||
};
|
||||
static constexpr int s_TotalSupportedImageExtensions = AZ_ARRAY_SIZE(s_SupportedImageExtensions);
|
||||
|
||||
enum class RGBWeight : AZ::u32
|
||||
{
|
||||
+1
-1
@@ -8,12 +8,12 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <BuilderSettings/ImageProcessingDefines.h>
|
||||
#include <BuilderSettings/BuilderSettings.h>
|
||||
#include <AzCore/base.h>
|
||||
#include <AzCore/IO/Path/Path.h>
|
||||
#include <AzCore/std/containers/set.h>
|
||||
#include <Atom/ImageProcessing/ImageObject.h>
|
||||
#include <Atom/ImageProcessing/ImageProcessingDefines.h>
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QFileSystemWatcher>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
#include <AzCore/RTTI/TypeInfo.h>
|
||||
#include <AzCore/RTTI/ReflectContext.h>
|
||||
#include <AzCore/Memory/Memory.h>
|
||||
#include <BuilderSettings/ImageProcessingDefines.h>
|
||||
#include <Atom/ImageProcessing/ImageProcessingDefines.h>
|
||||
#include <Atom/RPI.Reflect/Image/StreamingImageAsset.h>
|
||||
|
||||
namespace ImageProcessingAtom
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <BuilderSettings/ImageProcessingDefines.h>
|
||||
#include <Atom/ImageProcessing/ImageProcessingDefines.h>
|
||||
#include <AzCore/RTTI/TypeInfo.h>
|
||||
#include <AzCore/RTTI/ReflectContext.h>
|
||||
#include <AzCore/Memory/Memory.h>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
#include <AzCore/RTTI/TypeInfo.h>
|
||||
#include <AzCore/Memory/SystemAllocator.h>
|
||||
#include <BuilderSettings/ImageProcessingDefines.h>
|
||||
#include <Atom/ImageProcessing/ImageProcessingDefines.h>
|
||||
#include <Atom/ImageProcessing/PixelFormats.h>
|
||||
|
||||
namespace ImageProcessingAtom
|
||||
|
||||
@@ -11,9 +11,9 @@
|
||||
#include <AzCore/RTTI/TypeInfo.h>
|
||||
#include <AzCore/Asset/AssetCommon.h>
|
||||
|
||||
#include <BuilderSettings/ImageProcessingDefines.h>
|
||||
#include <BuilderSettings/CubemapSettings.h>
|
||||
#include <BuilderSettings/MipmapSettings.h>
|
||||
#include <Atom/ImageProcessing/ImageProcessingDefines.h>
|
||||
#include <Atom/ImageProcessing/PixelFormats.h>
|
||||
|
||||
namespace ImageProcessingAtom
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <BuilderSettings/ImageProcessingDefines.h>
|
||||
#include <Atom/ImageProcessing/ImageProcessingDefines.h>
|
||||
#include <AzCore/Serialization/SerializeContext.h>
|
||||
#include <AzCore/RTTI/ReflectContext.h>
|
||||
#include <AzCore/Serialization/DataPatch.h>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <BuilderSettings/ImageProcessingDefines.h>
|
||||
#include <Atom/ImageProcessing/ImageProcessingDefines.h>
|
||||
#include <Atom/ImageProcessing/PixelFormats.h>
|
||||
#include <Atom/ImageProcessing/ImageObject.h>
|
||||
|
||||
|
||||
@@ -10,10 +10,10 @@
|
||||
|
||||
#include <QRect>
|
||||
|
||||
#include <BuilderSettings/ImageProcessingDefines.h>
|
||||
#include <BuilderSettings/PresetSettings.h>
|
||||
#include <BuilderSettings/TextureSettings.h>
|
||||
#include <Atom/ImageProcessing/ImageObject.h>
|
||||
#include <Atom/ImageProcessing/ImageProcessingDefines.h>
|
||||
#include <Compressors/Compressor.h>
|
||||
|
||||
#include <AzCore/Jobs/Job.h>
|
||||
|
||||
@@ -8,10 +8,10 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <BuilderSettings/ImageProcessingDefines.h>
|
||||
#include <BuilderSettings/PresetSettings.h>
|
||||
#include <BuilderSettings/TextureSettings.h>
|
||||
#include <Atom/ImageProcessing/ImageObject.h>
|
||||
#include <Atom/ImageProcessing/ImageProcessingDefines.h>
|
||||
#include <AzCore/Jobs/Job.h>
|
||||
|
||||
namespace ImageProcessingAtom
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
#include <AzFramework/IO/LocalFileIO.h>
|
||||
|
||||
#include <Atom/ImageProcessing/ImageObject.h>
|
||||
#include <Atom/ImageProcessing/ImageProcessingDefines.h>
|
||||
#include <Processing/PixelFormatInfo.h>
|
||||
#include <Processing/ImageConvert.h>
|
||||
#include <Processing/ImageToProcess.h>
|
||||
@@ -45,7 +46,6 @@
|
||||
|
||||
#include <BuilderSettings/BuilderSettingManager.h>
|
||||
#include <BuilderSettings/CubemapSettings.h>
|
||||
#include <BuilderSettings/ImageProcessingDefines.h>
|
||||
#include <BuilderSettings/PresetSettings.h>
|
||||
|
||||
#include <Editor/EditorCommon.h>
|
||||
|
||||
@@ -13,6 +13,7 @@ set(FILES
|
||||
Include/Atom/ImageProcessing/ImageProcessingEditorBus.h
|
||||
Include/Atom/ImageProcessing/PixelFormats.h
|
||||
Include/Atom/ImageProcessing/ImageObject.h
|
||||
Include/Atom/ImageProcessing/ImageProcessingDefines.h
|
||||
../Assets/Editor/Resources.qrc
|
||||
../Assets/Editor/Backward.png
|
||||
../Assets/Editor/Forward.png
|
||||
@@ -28,7 +29,6 @@ set(FILES
|
||||
Source/BuilderSettings/BuilderSettings.h
|
||||
Source/BuilderSettings/CubemapSettings.cpp
|
||||
Source/BuilderSettings/CubemapSettings.h
|
||||
Source/BuilderSettings/ImageProcessingDefines.h
|
||||
Source/BuilderSettings/MipmapSettings.cpp
|
||||
Source/BuilderSettings/MipmapSettings.h
|
||||
Source/BuilderSettings/PlatformSettings.h
|
||||
|
||||
@@ -7,4 +7,5 @@
|
||||
#
|
||||
|
||||
set(FILES
|
||||
Include/Atom/ImageProcessing/ImageProcessingDefines.h
|
||||
)
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
* 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
|
||||
*
|
||||
*/
|
||||
|
||||
#ifdef SHADOWS
|
||||
#include <Atom/Features/Shadow/Shadow.azsli>
|
||||
#endif
|
||||
|
||||
#ifndef MULTILAYER
|
||||
#define MULTILAYER 0
|
||||
#endif
|
||||
|
||||
#ifndef ENABLE_ALPHA_CLIP
|
||||
#define ENABLE_ALPHA_CLIP 0
|
||||
#endif
|
||||
|
||||
#ifndef SHADOWS
|
||||
#define SHADOWS 0
|
||||
#endif
|
||||
|
||||
struct VSInput
|
||||
{
|
||||
float3 m_position : POSITION;
|
||||
float2 m_uv0 : UV0;
|
||||
float2 m_uv1 : UV1;
|
||||
|
||||
// only used for parallax depth calculation
|
||||
float3 m_normal : NORMAL;
|
||||
float4 m_tangent : TANGENT;
|
||||
float3 m_bitangent : BITANGENT;
|
||||
|
||||
#if MULTILAYER
|
||||
// This gets set automatically by the system at runtime only if it's available.
|
||||
// There is a soft naming convention that associates this with o_blendMask_isBound, which will be set to true whenever m_optional_blendMask is available.
|
||||
// (search "m_optional_" in ShaderVariantAssetBuilder for details on the naming convention).
|
||||
// [GFX TODO][ATOM-14475]: Come up with a more elegant way to associate the isBound flag with the input stream.
|
||||
float4 m_optional_blendMask : COLOR0;
|
||||
#endif
|
||||
};
|
||||
|
||||
struct VSDepthOutput
|
||||
{
|
||||
// "centroid" is needed for SV_Depth to compile
|
||||
precise linear centroid float4 m_position : SV_Position;
|
||||
float2 m_uv[UvSetCount] : UV1;
|
||||
|
||||
// only used for parallax depth calculation
|
||||
float3 m_normal : NORMAL;
|
||||
float3 m_tangent : TANGENT;
|
||||
float3 m_bitangent : BITANGENT;
|
||||
float3 m_worldPosition : UV0;
|
||||
|
||||
#if MULTILAYER
|
||||
float3 m_blendMask : UV3;
|
||||
#endif
|
||||
};
|
||||
|
||||
VSDepthOutput MainVS(VSInput IN)
|
||||
{
|
||||
VSDepthOutput OUT;
|
||||
|
||||
float4x4 objectToWorld = GetObjectToWorld();
|
||||
float4 worldPosition = mul(objectToWorld, float4(IN.m_position, 1.0));
|
||||
|
||||
OUT.m_position = mul(ViewSrg::m_viewProjectionMatrix, worldPosition);
|
||||
|
||||
float2 uvs[UvSetCount] = { IN.m_uv0, IN.m_uv1 };
|
||||
TransformUvs(uvs, OUT.m_uv);
|
||||
|
||||
if(ShouldHandleParallaxInDepthShaders())
|
||||
{
|
||||
OUT.m_worldPosition = worldPosition.xyz;
|
||||
|
||||
float3x3 objectToWorldIT = GetNormalToWorld();
|
||||
ConstructTBN(IN.m_normal, IN.m_tangent, IN.m_bitangent, objectToWorld, objectToWorldIT, OUT.m_normal, OUT.m_tangent, OUT.m_bitangent);
|
||||
}
|
||||
|
||||
#if MULTILAYER
|
||||
if(o_blendMask_isBound)
|
||||
{
|
||||
OUT.m_blendMask = IN.m_optional_blendMask.rgb;
|
||||
}
|
||||
else
|
||||
{
|
||||
OUT.m_blendMask = float3(0,0,0);
|
||||
}
|
||||
#endif
|
||||
|
||||
return OUT;
|
||||
}
|
||||
|
||||
struct PSDepthOutput
|
||||
{
|
||||
precise float m_depth : SV_Depth;
|
||||
};
|
||||
|
||||
PSDepthOutput MainPS(VSDepthOutput IN, bool isFrontFace : SV_IsFrontFace)
|
||||
{
|
||||
PSDepthOutput OUT;
|
||||
|
||||
OUT.m_depth = IN.m_position.z;
|
||||
|
||||
if(ShouldHandleParallaxInDepthShaders())
|
||||
{
|
||||
float3 tangents[UvSetCount] = { IN.m_tangent, IN.m_tangent };
|
||||
float3 bitangents[UvSetCount] = { IN.m_bitangent, IN.m_bitangent };
|
||||
|
||||
for (int i = 0; i != UvSetCount; ++i)
|
||||
{
|
||||
EvaluateTangentFrame(
|
||||
IN.m_normal,
|
||||
IN.m_worldPosition,
|
||||
isFrontFace,
|
||||
IN.m_uv[i],
|
||||
i,
|
||||
IN.m_tangent,
|
||||
IN.m_bitangent,
|
||||
tangents[i],
|
||||
bitangents[i]);
|
||||
}
|
||||
|
||||
#if MULTILAYER
|
||||
MultilayerSetPixelDepth(IN.m_blendMask, IN.m_worldPosition, IN.m_normal, tangents, bitangents, IN.m_uv, isFrontFace, OUT.m_depth);
|
||||
#else
|
||||
SetPixelDepth(IN.m_worldPosition, IN.m_normal, tangents, bitangents, IN.m_uv, isFrontFace, OUT.m_depth);
|
||||
#endif
|
||||
|
||||
#if SHADOWS
|
||||
OUT.m_depth += PdoShadowMapBias;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if ENABLE_ALPHA_CLIP
|
||||
GetAlphaAndClip(IN.m_uv);
|
||||
#endif
|
||||
|
||||
return OUT;
|
||||
}
|
||||
@@ -8,88 +8,13 @@
|
||||
|
||||
#include "./EnhancedPBR_Common.azsli"
|
||||
#include <Atom/Features/PBR/DefaultObjectSrg.azsli>
|
||||
#include <Atom/Features/ParallaxMapping.azsli>
|
||||
#include <Atom/Features/MatrixUtility.azsli>
|
||||
|
||||
#include "MaterialInputs/AlphaInput.azsli"
|
||||
#include "MaterialInputs/ParallaxInput.azsli"
|
||||
#include "MaterialFunctions/StandardGetObjectToWorld.azsli"
|
||||
#include "MaterialFunctions/StandardGetNormalToWorld.azsli"
|
||||
#include "MaterialFunctions/StandardTransformUvs.azsli"
|
||||
#include "MaterialFunctions/EvaluateTangentFrame.azsli"
|
||||
#include "MaterialFunctions/ParallaxDepth.azsli"
|
||||
#include "MaterialFunctions/StandardGetAlphaAndClip.azsli"
|
||||
|
||||
struct VSInput
|
||||
{
|
||||
float3 m_position : POSITION;
|
||||
float2 m_uv0 : UV0;
|
||||
float2 m_uv1 : UV1;
|
||||
|
||||
// only used for parallax depth calculation
|
||||
float3 m_normal : NORMAL;
|
||||
float4 m_tangent : TANGENT;
|
||||
float3 m_bitangent : BITANGENT;
|
||||
};
|
||||
|
||||
struct VSDepthOutput
|
||||
{
|
||||
precise linear centroid float4 m_position : SV_Position;
|
||||
float2 m_uv[UvSetCount] : UV1;
|
||||
|
||||
// only used for parallax depth calculation
|
||||
float3 m_normal : NORMAL;
|
||||
float3 m_tangent : TANGENT;
|
||||
float3 m_bitangent : BITANGENT;
|
||||
float3 m_worldPosition : UV0;
|
||||
};
|
||||
|
||||
VSDepthOutput MainVS(VSInput IN)
|
||||
{
|
||||
VSDepthOutput OUT;
|
||||
|
||||
float4x4 objectToWorld = ObjectSrg::GetWorldMatrix();
|
||||
float4 worldPosition = mul(objectToWorld, float4(IN.m_position, 1.0));
|
||||
|
||||
OUT.m_position = mul(ViewSrg::m_viewProjectionMatrix, worldPosition);
|
||||
// By design, only UV0 is allowed to apply transforms.
|
||||
OUT.m_uv[0] = mul(MaterialSrg::m_uvMatrix, float3(IN.m_uv0, 1.0)).xy;
|
||||
OUT.m_uv[1] = IN.m_uv1;
|
||||
|
||||
if(ShouldHandleParallaxInDepthShaders())
|
||||
{
|
||||
OUT.m_worldPosition = worldPosition.xyz;
|
||||
|
||||
float3x3 objectToWorldIT = ObjectSrg::GetWorldMatrixInverseTranspose();
|
||||
ConstructTBN(IN.m_normal, IN.m_tangent, IN.m_bitangent, objectToWorld, objectToWorldIT, OUT.m_normal, OUT.m_tangent, OUT.m_bitangent);
|
||||
}
|
||||
return OUT;
|
||||
}
|
||||
|
||||
struct PSDepthOutput
|
||||
{
|
||||
precise float m_depth : SV_Depth;
|
||||
};
|
||||
|
||||
PSDepthOutput MainPS(VSDepthOutput IN, bool isFrontFace : SV_IsFrontFace)
|
||||
{
|
||||
PSDepthOutput OUT;
|
||||
|
||||
OUT.m_depth = IN.m_position.z;
|
||||
|
||||
if(ShouldHandleParallaxInDepthShaders())
|
||||
{
|
||||
float3 tangents[UvSetCount] = { IN.m_tangent.xyz, IN.m_tangent.xyz };
|
||||
float3 bitangents[UvSetCount] = { IN.m_bitangent.xyz, IN.m_bitangent.xyz };
|
||||
PrepareGeneratedTangent(IN.m_normal, IN.m_worldPosition, isFrontFace, IN.m_uv, UvSetCount, tangents, bitangents);
|
||||
|
||||
float3x3 uvMatrix = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrix : CreateIdentity3x3();
|
||||
float3x3 uvMatrixInverse = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrixInverse : CreateIdentity3x3();
|
||||
|
||||
GetParallaxInput(IN.m_normal, tangents[MaterialSrg::m_parallaxUvIndex], bitangents[MaterialSrg::m_parallaxUvIndex], MaterialSrg::m_heightmapScale, MaterialSrg::m_heightmapOffset,
|
||||
ObjectSrg::GetWorldMatrix(), uvMatrix, uvMatrixInverse,
|
||||
IN.m_uv[MaterialSrg::m_parallaxUvIndex], IN.m_worldPosition, OUT.m_depth);
|
||||
}
|
||||
|
||||
// Clip Alpha
|
||||
float2 baseColorUV = IN.m_uv[MaterialSrg::m_baseColorMapUvIndex];
|
||||
float2 opacityUV = IN.m_uv[MaterialSrg::m_opacityMapUvIndex];
|
||||
float alpha = SampleAlpha(MaterialSrg::m_baseColorMap, MaterialSrg::m_opacityMap, baseColorUV, opacityUV, MaterialSrg::m_sampler, o_opacity_source);
|
||||
CheckClipping(alpha, MaterialSrg::m_opacityFactor);
|
||||
|
||||
return OUT;
|
||||
}
|
||||
#define ENABLE_ALPHA_CLIP 1
|
||||
#include "DepthPass_WithPS.azsli"
|
||||
|
||||
@@ -10,21 +10,6 @@
|
||||
|
||||
// SRGs
|
||||
#include <Atom/Features/PBR/DefaultObjectSrg.azsli>
|
||||
#include <Atom/Features/PBR/ForwardPassSrg.azsli>
|
||||
|
||||
// Pass Output
|
||||
#include <Atom/Features/PBR/ForwardSubsurfacePassOutput.azsli>
|
||||
|
||||
// Utility
|
||||
#include <Atom/Features/ColorManagement/TransformColor.azsli>
|
||||
#include <Atom/Features/PBR/AlphaUtils.azsli>
|
||||
|
||||
// Custom Surface & Lighting
|
||||
#include <Atom/Features/PBR/Lighting/EnhancedLighting.azsli>
|
||||
|
||||
// Decals
|
||||
#include <Atom/Features/PBR/Decals.azsli>
|
||||
|
||||
|
||||
// ---------- Material Parameters ----------
|
||||
|
||||
@@ -49,397 +34,13 @@ COMMON_OPTIONS_DETAIL_MAPS()
|
||||
#include "MaterialInputs/TransmissionInput.azsli"
|
||||
|
||||
|
||||
// ---------- Vertex Shader ----------
|
||||
|
||||
struct VSInput
|
||||
{
|
||||
// Base fields (required by the template azsli file)...
|
||||
float3 m_position : POSITION;
|
||||
float3 m_normal : NORMAL;
|
||||
float4 m_tangent : TANGENT;
|
||||
float3 m_bitangent : BITANGENT;
|
||||
|
||||
// Extended fields (only referenced in this azsl file)...
|
||||
float2 m_uv0 : UV0;
|
||||
float2 m_uv1 : UV1;
|
||||
};
|
||||
|
||||
struct VSOutput
|
||||
{
|
||||
// Base fields (required by the template azsli file)...
|
||||
precise linear centroid float4 m_position : SV_Position;
|
||||
float3 m_normal: NORMAL;
|
||||
float3 m_tangent : TANGENT;
|
||||
float3 m_bitangent : BITANGENT;
|
||||
float3 m_worldPosition : UV0;
|
||||
float3 m_shadowCoords[ViewSrg::MaxCascadeCount] : UV5;
|
||||
|
||||
// Extended fields (only referenced in this azsl file)...
|
||||
float2 m_uv[UvSetCount] : UV1;
|
||||
float2 m_detailUv[UvSetCount] : UV3;
|
||||
};
|
||||
|
||||
#include <Atom/Features/Vertex/VertexHelper.azsli>
|
||||
|
||||
VSOutput EnhancedPbr_ForwardPassVS(VSInput IN)
|
||||
{
|
||||
VSOutput OUT;
|
||||
|
||||
float3 worldPosition = mul(ObjectSrg::GetWorldMatrix(), float4(IN.m_position, 1.0)).xyz;
|
||||
|
||||
// By design, only UV0 is allowed to apply transforms.
|
||||
OUT.m_uv[0] = mul(MaterialSrg::m_uvMatrix, float3(IN.m_uv0, 1.0)).xy;
|
||||
OUT.m_uv[1] = IN.m_uv1;
|
||||
|
||||
// As seen above our standard practice is to only transform the first UV as that's the one we expect to be used for
|
||||
// tiling. But for detail maps you could actually use either UV stream for tiling. There is no concern about applying
|
||||
// the same transform to both UV sets because the detail map feature forces the same UV set to be used for all detail maps.
|
||||
// Note we might be able to combine these into a single UV similar to what Skin.materialtype does,
|
||||
// but we would need to address how it works with the parallax code below that indexes into the m_detailUV array.
|
||||
OUT.m_detailUv[0] = mul(MaterialSrg::m_detailUvMatrix, float3(IN.m_uv0, 1.0)).xy;
|
||||
OUT.m_detailUv[1] = mul(MaterialSrg::m_detailUvMatrix, float3(IN.m_uv1, 1.0)).xy;
|
||||
|
||||
// Shadow coords will be calculated in the pixel shader in this case
|
||||
bool skipShadowCoords = ShouldHandleParallax() && o_parallax_enablePixelDepthOffset;
|
||||
|
||||
VertexHelper(IN, OUT, worldPosition, skipShadowCoords);
|
||||
|
||||
return OUT;
|
||||
}
|
||||
|
||||
|
||||
// ---------- Pixel Shader ----------
|
||||
|
||||
PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace, out float depth)
|
||||
{
|
||||
const float3 vertexNormal = normalize(IN.m_normal);
|
||||
|
||||
// ------- Tangents & Bitangets -------
|
||||
float3 tangents[UvSetCount] = { IN.m_tangent.xyz, IN.m_tangent.xyz };
|
||||
float3 bitangents[UvSetCount] = { IN.m_bitangent.xyz, IN.m_bitangent.xyz };
|
||||
|
||||
if ((o_parallax_feature_enabled && !o_enableSubsurfaceScattering) || o_normal_useTexture || (o_clearCoat_enabled && o_clearCoat_normal_useTexture) || o_detail_normal_useTexture)
|
||||
{
|
||||
PrepareGeneratedTangent(vertexNormal, IN.m_worldPosition, isFrontFace, IN.m_uv, UvSetCount, tangents, bitangents);
|
||||
}
|
||||
|
||||
// ------- Depth & Parallax -------
|
||||
|
||||
depth = IN.m_position.z;
|
||||
|
||||
bool displacementIsClipped = false;
|
||||
|
||||
// Parallax mapping's non uniform uv transformations break screen space subsurface scattering, disable it when subsurface scatteirng is enabled
|
||||
if(ShouldHandleParallax())
|
||||
{
|
||||
// GetParallaxInput applies an tangent offset to the UV. We want to apply the same offset to the detailUv (note: this needs to be tested with content)
|
||||
// The math is: offset = newUv - oldUv; detailUv += offset;
|
||||
// This is the same as: detailUv -= oldUv; detailUv += newUv;
|
||||
IN.m_detailUv[MaterialSrg::m_parallaxUvIndex] -= IN.m_uv[MaterialSrg::m_parallaxUvIndex];
|
||||
float3x3 uvMatrix = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrix : CreateIdentity3x3();
|
||||
float3x3 uvMatrixInverse = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrixInverse : CreateIdentity3x3();
|
||||
|
||||
GetParallaxInput(vertexNormal, tangents[MaterialSrg::m_parallaxUvIndex], bitangents[MaterialSrg::m_parallaxUvIndex], MaterialSrg::m_heightmapScale, MaterialSrg::m_heightmapOffset,
|
||||
ObjectSrg::GetWorldMatrix(), uvMatrix, uvMatrixInverse,
|
||||
IN.m_uv[MaterialSrg::m_parallaxUvIndex], IN.m_worldPosition, depth, IN.m_position.w, displacementIsClipped);
|
||||
|
||||
// Apply second part of the offset to the detail UV (see comment above)
|
||||
IN.m_detailUv[MaterialSrg::m_parallaxUvIndex] -= IN.m_uv[MaterialSrg::m_parallaxUvIndex];
|
||||
|
||||
// Adjust directional light shadow coorinates for parallax correction
|
||||
if(o_parallax_enablePixelDepthOffset)
|
||||
{
|
||||
const uint shadowIndex = ViewSrg::m_shadowIndexDirectionalLight;
|
||||
if (o_enableShadows && shadowIndex < SceneSrg::m_directionalLightCount)
|
||||
{
|
||||
DirectionalLightShadow::GetShadowCoords(shadowIndex, IN.m_worldPosition, vertexNormal, IN.m_shadowCoords);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Surface surface;
|
||||
surface.position = IN.m_worldPosition;
|
||||
|
||||
// ------- Alpha & Clip -------
|
||||
|
||||
float2 baseColorUv = IN.m_uv[MaterialSrg::m_baseColorMapUvIndex];
|
||||
float2 opacityUv = IN.m_uv[MaterialSrg::m_opacityMapUvIndex];
|
||||
float alpha = GetAlphaInputAndClip(MaterialSrg::m_baseColorMap, MaterialSrg::m_opacityMap, baseColorUv, opacityUv, MaterialSrg::m_sampler, MaterialSrg::m_opacityFactor, o_opacity_source);
|
||||
|
||||
// ------- Detail Layer Setup -------
|
||||
|
||||
const float2 detailUv = IN.m_detailUv[MaterialSrg::m_detail_allMapsUvIndex];
|
||||
|
||||
// When the detail maps and the detail blend mask are on the same UV, they both use the transformed detail UVs because they are 'attached' to each other
|
||||
const float2 detailBlendMaskUv = (MaterialSrg::m_detail_blendMask_uvIndex == MaterialSrg::m_detail_allMapsUvIndex) ?
|
||||
IN.m_detailUv[MaterialSrg::m_detail_blendMask_uvIndex] :
|
||||
IN.m_uv[MaterialSrg::m_detail_blendMask_uvIndex];
|
||||
|
||||
const float detailLayerBlendFactor = GetDetailLayerBlendFactor(
|
||||
MaterialSrg::m_detail_blendMask_texture,
|
||||
MaterialSrg::m_sampler,
|
||||
detailBlendMaskUv,
|
||||
o_detail_blendMask_useTexture,
|
||||
MaterialSrg::m_detail_blendFactor);
|
||||
|
||||
// ------- Normal -------
|
||||
|
||||
float2 normalUv = IN.m_uv[MaterialSrg::m_normalMapUvIndex];
|
||||
float3x3 uvMatrix = MaterialSrg::m_normalMapUvIndex == 0 ? MaterialSrg::m_uvMatrix : CreateIdentity3x3(); // By design, only UV0 is allowed to apply transforms.
|
||||
float detailLayerNormalFactor = MaterialSrg::m_detail_normal_factor * detailLayerBlendFactor;
|
||||
surface.vertexNormal = vertexNormal;
|
||||
surface.normal = GetDetailedNormalInputWS(
|
||||
isFrontFace, IN.m_normal,
|
||||
tangents[MaterialSrg::m_normalMapUvIndex], bitangents[MaterialSrg::m_normalMapUvIndex], MaterialSrg::m_normalMap, MaterialSrg::m_sampler, normalUv, MaterialSrg::m_normalFactor, MaterialSrg::m_flipNormalX, MaterialSrg::m_flipNormalY, uvMatrix, o_normal_useTexture,
|
||||
tangents[MaterialSrg::m_detail_allMapsUvIndex], bitangents[MaterialSrg::m_detail_allMapsUvIndex], MaterialSrg::m_detail_normal_texture, MaterialSrg::m_sampler, detailUv, detailLayerNormalFactor, MaterialSrg::m_detail_normal_flipX, MaterialSrg::m_detail_normal_flipY, MaterialSrg::m_detailUvMatrix, o_detail_normal_useTexture);
|
||||
|
||||
//--------------------- Base Color ----------------------
|
||||
|
||||
// [GFX TODO][ATOM-1761] Figure out how we want our base material to expect channels to be encoded, and apply that to the way we pack alpha.
|
||||
|
||||
float detailLayerBaseColorFactor = MaterialSrg::m_detail_baseColor_factor * detailLayerBlendFactor;
|
||||
|
||||
float3 baseColor = GetDetailedBaseColorInput(
|
||||
MaterialSrg::m_baseColorMap, MaterialSrg::m_sampler, baseColorUv, o_baseColor_useTexture, MaterialSrg::m_baseColor, MaterialSrg::m_baseColorFactor, o_baseColorTextureBlendMode,
|
||||
MaterialSrg::m_detail_baseColor_texture, MaterialSrg::m_sampler, detailUv, o_detail_baseColor_useTexture, detailLayerBaseColorFactor);
|
||||
|
||||
if(o_parallax_highlightClipping && displacementIsClipped)
|
||||
{
|
||||
ApplyParallaxClippingHighlight(baseColor);
|
||||
}
|
||||
|
||||
// ------- Metallic -------
|
||||
|
||||
float metallic = 0;
|
||||
if(!o_enableSubsurfaceScattering) // If subsurface scattering is enabled skip texture lookup for metallic, as this quantity won't be used anyway
|
||||
{
|
||||
float2 metallicUv = IN.m_uv[MaterialSrg::m_metallicMapUvIndex];
|
||||
metallic = GetMetallicInput(MaterialSrg::m_metallicMap, MaterialSrg::m_sampler, metallicUv, MaterialSrg::m_metallicFactor, o_metallic_useTexture);
|
||||
}
|
||||
|
||||
// ------- Specular -------
|
||||
|
||||
float2 specularUv = IN.m_uv[MaterialSrg::m_specularF0MapUvIndex];
|
||||
float specularF0Factor = GetSpecularInput(MaterialSrg::m_specularF0Map, MaterialSrg::m_sampler, specularUv, MaterialSrg::m_specularF0Factor, o_specularF0_useTexture);
|
||||
|
||||
surface.SetAlbedoAndSpecularF0(baseColor, specularF0Factor, metallic);
|
||||
|
||||
// ------- Roughness -------
|
||||
|
||||
float2 roughnessUv = IN.m_uv[MaterialSrg::m_roughnessMapUvIndex];
|
||||
surface.roughnessLinear = GetRoughnessInput(MaterialSrg::m_roughnessMap, MaterialSrg::m_sampler, roughnessUv, MaterialSrg::m_roughnessFactor,
|
||||
MaterialSrg::m_roughnessLowerBound, MaterialSrg::m_roughnessUpperBound, o_roughness_useTexture);
|
||||
surface.CalculateRoughnessA();
|
||||
|
||||
// ------- Subsurface -------
|
||||
|
||||
float2 subsurfaceUv = IN.m_uv[MaterialSrg::m_subsurfaceScatteringInfluenceMapUvIndex];
|
||||
float surfaceScatteringFactor = GetSubsurfaceInput(MaterialSrg::m_subsurfaceScatteringInfluenceMap, MaterialSrg::m_sampler, subsurfaceUv, MaterialSrg::m_subsurfaceScatteringFactor);
|
||||
|
||||
// ------- Transmission -------
|
||||
|
||||
float2 transmissionUv = IN.m_uv[MaterialSrg::m_transmissionThicknessMapUvIndex];
|
||||
float4 transmissionTintThickness = GeTransmissionInput(MaterialSrg::m_transmissionThicknessMap, MaterialSrg::m_sampler, transmissionUv, MaterialSrg::m_transmissionTintThickness);
|
||||
surface.transmission.tint = transmissionTintThickness.rgb;
|
||||
surface.transmission.thickness = transmissionTintThickness.w;
|
||||
surface.transmission.transmissionParams = MaterialSrg::m_transmissionParams;
|
||||
surface.transmission.scatterDistance = MaterialSrg::m_scatterDistance;
|
||||
|
||||
// ------- Anisotropy -------
|
||||
|
||||
if (o_enableAnisotropy)
|
||||
{
|
||||
// Convert the angle from [0..1] = [0 .. 180 degrees] to radians [0 .. PI]
|
||||
const float anisotropyAngle = MaterialSrg::m_anisotropicAngle * PI;
|
||||
const float anisotropyFactor = MaterialSrg::m_anisotropicFactor;
|
||||
surface.anisotropy.Init(surface.normal, IN.m_tangent, IN.m_bitangent, anisotropyAngle, anisotropyFactor, surface.roughnessA);
|
||||
}
|
||||
|
||||
// ------- Lighting Data -------
|
||||
|
||||
LightingData lightingData;
|
||||
|
||||
// Light iterator
|
||||
lightingData.tileIterator.Init(IN.m_position, PassSrg::m_lightListRemapped, PassSrg::m_tileLightData);
|
||||
lightingData.Init(surface.position, surface.normal, surface.roughnessLinear);
|
||||
|
||||
// Directional light shadow coordinates
|
||||
lightingData.shadowCoords = IN.m_shadowCoords;
|
||||
|
||||
// ------- Emissive -------
|
||||
|
||||
float2 emissiveUv = IN.m_uv[MaterialSrg::m_emissiveMapUvIndex];
|
||||
lightingData.emissiveLighting = GetEmissiveInput(MaterialSrg::m_emissiveMap, MaterialSrg::m_sampler, emissiveUv, MaterialSrg::m_emissiveIntensity, MaterialSrg::m_emissiveColor.rgb, o_emissiveEnabled, o_emissive_useTexture);
|
||||
|
||||
// ------- Occlusion -------
|
||||
|
||||
lightingData.diffuseAmbientOcclusion = GetOcclusionInput(MaterialSrg::m_diffuseOcclusionMap, MaterialSrg::m_sampler, IN.m_uv[MaterialSrg::m_diffuseOcclusionMapUvIndex], MaterialSrg::m_diffuseOcclusionFactor, o_diffuseOcclusion_useTexture);
|
||||
lightingData.specularOcclusion = GetOcclusionInput(MaterialSrg::m_specularOcclusionMap, MaterialSrg::m_sampler, IN.m_uv[MaterialSrg::m_specularOcclusionMapUvIndex], MaterialSrg::m_specularOcclusionFactor, o_specularOcclusion_useTexture);
|
||||
|
||||
// ------- Thin Object Light Transmission -------
|
||||
|
||||
// Shrink (absolute) offset towards the normal opposite direction to ensure correct shadow map projection
|
||||
lightingData.shrinkFactor = surface.transmission.transmissionParams.x;
|
||||
|
||||
// Angle offset for subsurface scattering through thin objects
|
||||
lightingData.transmissionNdLBias = surface.transmission.transmissionParams.y;
|
||||
|
||||
// Attenuation applied to hide artifacts due to low-res shadow maps
|
||||
lightingData.distanceAttenuation = surface.transmission.transmissionParams.z;
|
||||
|
||||
// ------- Clearcoat -------
|
||||
|
||||
// [GFX TODO][ATOM-14603]: Clean up the double uses of these clear coat flags
|
||||
if(o_clearCoat_feature_enabled)
|
||||
{
|
||||
if(o_clearCoat_enabled)
|
||||
{
|
||||
float3x3 uvMatrix = MaterialSrg::m_clearCoatNormalMapUvIndex == 0 ? MaterialSrg::m_uvMatrix : CreateIdentity3x3();
|
||||
GetClearCoatInputs(MaterialSrg::m_clearCoatInfluenceMap, IN.m_uv[MaterialSrg::m_clearCoatInfluenceMapUvIndex], MaterialSrg::m_clearCoatFactor, o_clearCoat_factor_useTexture,
|
||||
MaterialSrg::m_clearCoatRoughnessMap, IN.m_uv[MaterialSrg::m_clearCoatRoughnessMapUvIndex], MaterialSrg::m_clearCoatRoughness, o_clearCoat_roughness_useTexture,
|
||||
MaterialSrg::m_clearCoatNormalMap, IN.m_uv[MaterialSrg::m_clearCoatNormalMapUvIndex], IN.m_normal, o_clearCoat_normal_useTexture, MaterialSrg::m_clearCoatNormalStrength,
|
||||
uvMatrix, tangents[MaterialSrg::m_clearCoatNormalMapUvIndex], bitangents[MaterialSrg::m_clearCoatNormalMapUvIndex],
|
||||
MaterialSrg::m_sampler, isFrontFace,
|
||||
surface.clearCoat.factor, surface.clearCoat.roughness, surface.clearCoat.normal);
|
||||
}
|
||||
|
||||
// manipulate base layer f0 if clear coat is enabled
|
||||
// modify base layer's normal incidence reflectance
|
||||
// for the derivation of the following equation please refer to:
|
||||
// https://google.github.io/filament/Filament.md.html#materialsystem/clearcoatmodel/baselayermodification
|
||||
float3 f0 = (1.0 - 5.0 * sqrt(surface.specularF0)) / (5.0 - sqrt(surface.specularF0));
|
||||
surface.specularF0 = lerp(surface.specularF0, f0 * f0, surface.clearCoat.factor);
|
||||
}
|
||||
|
||||
// Diffuse and Specular response (used in IBL calculations)
|
||||
lightingData.specularResponse = FresnelSchlickWithRoughness(lightingData.NdotV, surface.specularF0, surface.roughnessLinear);
|
||||
lightingData.diffuseResponse = 1.0 - lightingData.specularResponse;
|
||||
|
||||
if(o_clearCoat_feature_enabled)
|
||||
{
|
||||
// Clear coat layer has fixed IOR = 1.5 and transparent => F0 = (1.5 - 1)^2 / (1.5 + 1)^2 = 0.04
|
||||
lightingData.diffuseResponse *= 1.0 - (FresnelSchlickWithRoughness(lightingData.NdotV, float3(0.04, 0.04, 0.04), surface.clearCoat.roughness) * surface.clearCoat.factor);
|
||||
}
|
||||
|
||||
// ------- Multiscatter -------
|
||||
|
||||
lightingData.CalculateMultiscatterCompensation(surface.specularF0, o_specularF0_enableMultiScatterCompensation);
|
||||
|
||||
// ------- Lighting Calculation -------
|
||||
|
||||
// Apply Decals
|
||||
ApplyDecals(lightingData.tileIterator, surface);
|
||||
|
||||
// Apply Direct Lighting
|
||||
ApplyDirectLighting(surface, lightingData);
|
||||
|
||||
// Apply Image Based Lighting (IBL)
|
||||
ApplyIBL(surface, lightingData);
|
||||
|
||||
// Finalize Lighting
|
||||
lightingData.FinalizeLighting(surface.transmission.tint);
|
||||
|
||||
PbrLightingOutput lightingOutput = GetPbrLightingOutput(surface, lightingData, alpha);
|
||||
|
||||
// ------- Opacity -------
|
||||
|
||||
if (o_opacity_mode == OpacityMode::Blended || o_opacity_mode == OpacityMode::TintedTransparent)
|
||||
{
|
||||
// Increase opacity at grazing angles for surfaces with a low m_opacityAffectsSpecularFactor.
|
||||
// For m_opacityAffectsSpecularFactor values close to 0, that indicates a transparent surface
|
||||
// like glass, so it becomes less transparent at grazing angles. For m_opacityAffectsSpecularFactor
|
||||
// values close to 1.0, that indicates the absence of a surface entirely, so this effect should
|
||||
// not apply.
|
||||
float fresnelAlpha = FresnelSchlickWithRoughness(lightingData.NdotV, alpha, surface.roughnessLinear).x;
|
||||
alpha = lerp(fresnelAlpha, alpha, MaterialSrg::m_opacityAffectsSpecularFactor);
|
||||
}
|
||||
|
||||
// Note: lightingOutput rendertargets are not always used as named, particularly m_diffuseColor (target 0) and
|
||||
// m_specularColor (target 1). Comments below describe the differences when appropriate.
|
||||
|
||||
if (o_opacity_mode == OpacityMode::Blended)
|
||||
{
|
||||
// [GFX_TODO ATOM-13187] PbrLighting shouldn't be writing directly to render targets. It's confusing when
|
||||
// specular is being added to diffuse just because we're calling render target 0 "diffuse".
|
||||
|
||||
// For blended mode, we do (dest * alpha) + (source * 1.0). This allows the specular
|
||||
// to be added on top of the diffuse, but then the diffuse must be pre-multiplied.
|
||||
// It's done this way because surface transparency doesn't really change specular response (eg, glass).
|
||||
|
||||
lightingOutput.m_diffuseColor.rgb *= lightingOutput.m_diffuseColor.w; // pre-multiply diffuse
|
||||
|
||||
// Add specular. m_opacityAffectsSpecularFactor controls how much the alpha masks out specular contribution.
|
||||
float3 specular = lightingOutput.m_specularColor.rgb;
|
||||
specular = lerp(specular, specular * lightingOutput.m_diffuseColor.w, MaterialSrg::m_opacityAffectsSpecularFactor);
|
||||
lightingOutput.m_diffuseColor.rgb += specular;
|
||||
|
||||
lightingOutput.m_diffuseColor.w = alpha;
|
||||
}
|
||||
else if (o_opacity_mode == OpacityMode::TintedTransparent)
|
||||
{
|
||||
// See OpacityMode::Blended above for the basic method. TintedTransparent adds onto the above concept by supporting
|
||||
// colored alpha. This is currently a very basic calculation that uses the baseColor as a multiplier with strength
|
||||
// determined by the alpha. We'll modify this later to be more physically accurate and allow surface depth,
|
||||
// absorption, and interior color to be specified.
|
||||
//
|
||||
// The technique uses dual source blending to allow two separate sources to be part of the blending equation
|
||||
// even though ultimately only a single render target is being written to. m_diffuseColor is render target 0 and
|
||||
// m_specularColor render target 1, and the blend mode is (dest * source1color) + (source * 1.0).
|
||||
//
|
||||
// This means that m_specularColor.rgb (source 1) is multiplied against the destination, then
|
||||
// m_diffuseColor.rgb (source) is added to that, and the final result is stored in render target 0.
|
||||
|
||||
lightingOutput.m_diffuseColor.rgb *= lightingOutput.m_diffuseColor.w; // pre-multiply diffuse
|
||||
|
||||
// Add specular. m_opacityAffectsSpecularFactor controls how much the alpha masks out specular contribution.
|
||||
float3 specular = lightingOutput.m_specularColor.rgb;
|
||||
specular = lerp(specular, specular * lightingOutput.m_diffuseColor.w, MaterialSrg::m_opacityAffectsSpecularFactor);
|
||||
lightingOutput.m_diffuseColor.rgb += specular;
|
||||
|
||||
lightingOutput.m_specularColor.rgb = baseColor * (1.0 - alpha);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Pack factor and quality, drawback: because of precision limit of float16 cannot represent exact 1, maximum representable value is 0.9961
|
||||
uint factorAndQuality = dot(round(float2(saturate(surfaceScatteringFactor), MaterialSrg::m_subsurfaceScatteringQuality) * 255), float2(256, 1));
|
||||
lightingOutput.m_diffuseColor.w = factorAndQuality * (o_enableSubsurfaceScattering ? 1.0 : -1.0);
|
||||
lightingOutput.m_scatterDistance = MaterialSrg::m_scatterDistance;
|
||||
}
|
||||
|
||||
return lightingOutput;
|
||||
}
|
||||
|
||||
ForwardPassOutputWithDepth EnhancedPbr_ForwardPassPS(VSOutput IN, bool isFrontFace : SV_IsFrontFace)
|
||||
{
|
||||
ForwardPassOutputWithDepth OUT;
|
||||
float depth;
|
||||
|
||||
PbrLightingOutput lightingOutput = ForwardPassPS_Common(IN, isFrontFace, depth);
|
||||
|
||||
OUT.m_diffuseColor = lightingOutput.m_diffuseColor;
|
||||
OUT.m_specularColor = lightingOutput.m_specularColor;
|
||||
OUT.m_specularF0 = lightingOutput.m_specularF0;
|
||||
OUT.m_albedo = lightingOutput.m_albedo;
|
||||
OUT.m_normal = lightingOutput.m_normal;
|
||||
OUT.m_scatterDistance = lightingOutput.m_scatterDistance;
|
||||
OUT.m_depth = depth;
|
||||
return OUT;
|
||||
}
|
||||
|
||||
[earlydepthstencil]
|
||||
ForwardPassOutput EnhancedPbr_ForwardPassPS_EDS(VSOutput IN, bool isFrontFace : SV_IsFrontFace)
|
||||
{
|
||||
ForwardPassOutput OUT;
|
||||
float depth;
|
||||
|
||||
PbrLightingOutput lightingOutput = ForwardPassPS_Common(IN, isFrontFace, depth);
|
||||
|
||||
OUT.m_diffuseColor = lightingOutput.m_diffuseColor;
|
||||
OUT.m_specularColor = lightingOutput.m_specularColor;
|
||||
OUT.m_specularF0 = lightingOutput.m_specularF0;
|
||||
OUT.m_albedo = lightingOutput.m_albedo;
|
||||
OUT.m_normal = lightingOutput.m_normal;
|
||||
OUT.m_scatterDistance = lightingOutput.m_scatterDistance;
|
||||
|
||||
return OUT;
|
||||
}
|
||||
#include "MaterialFunctions/EvaluateEnhancedSurface.azsli"
|
||||
#include "MaterialFunctions/EvaluateTangentFrame.azsli"
|
||||
#include "MaterialFunctions/EnhancedParallaxDepth.azsli"
|
||||
#include "MaterialFunctions/StandardGetNormalToWorld.azsli"
|
||||
#include "MaterialFunctions/StandardGetObjectToWorld.azsli"
|
||||
#include "MaterialFunctions/StandardGetAlphaAndClip.azsli"
|
||||
#include "MaterialFunctions/StandardTransformDetailUvs.azsli"
|
||||
#include "MaterialFunctions/StandardTransformUvs.azsli"
|
||||
|
||||
#include "EnhancedSurface_ForwardPass.azsli"
|
||||
|
||||
@@ -16,85 +16,13 @@
|
||||
#include "MaterialInputs/AlphaInput.azsli"
|
||||
#include "MaterialInputs/ParallaxInput.azsli"
|
||||
|
||||
struct VertexInput
|
||||
{
|
||||
float3 m_position : POSITION;
|
||||
float2 m_uv0 : UV0;
|
||||
float2 m_uv1 : UV1;
|
||||
#include "MaterialFunctions/StandardGetObjectToWorld.azsli"
|
||||
#include "MaterialFunctions/StandardGetNormalToWorld.azsli"
|
||||
#include "MaterialFunctions/StandardTransformUvs.azsli"
|
||||
#include "MaterialFunctions/EvaluateTangentFrame.azsli"
|
||||
#include "MaterialFunctions/ParallaxDepth.azsli"
|
||||
#include "MaterialFunctions/StandardGetAlphaAndClip.azsli"
|
||||
|
||||
// only used for parallax depth calculation
|
||||
float3 m_normal : NORMAL;
|
||||
float4 m_tangent : TANGENT;
|
||||
float3 m_bitangent : BITANGENT;
|
||||
};
|
||||
|
||||
struct VertexOutput
|
||||
{
|
||||
float4 m_position : SV_Position;
|
||||
float2 m_uv[UvSetCount] : UV1;
|
||||
|
||||
// only used for parallax depth calculation
|
||||
float3 m_normal : NORMAL;
|
||||
float3 m_tangent : TANGENT;
|
||||
float3 m_bitangent : BITANGENT;
|
||||
float3 m_worldPosition : UV0;
|
||||
};
|
||||
|
||||
VertexOutput MainVS(VertexInput IN)
|
||||
{
|
||||
const float4x4 objectToWorld = ObjectSrg::GetWorldMatrix();
|
||||
VertexOutput OUT;
|
||||
|
||||
const float3 worldPosition = mul(objectToWorld, float4(IN.m_position, 1.0)).xyz;
|
||||
OUT.m_position = mul(ViewSrg::m_viewProjectionMatrix, float4(worldPosition, 1.0));
|
||||
// By design, only UV0 is allowed to apply transforms.
|
||||
OUT.m_uv[0] = mul(MaterialSrg::m_uvMatrix, float3(IN.m_uv0, 1.0)).xy;
|
||||
OUT.m_uv[1] = IN.m_uv1;
|
||||
|
||||
if(ShouldHandleParallaxInDepthShaders())
|
||||
{
|
||||
OUT.m_worldPosition = worldPosition.xyz;
|
||||
|
||||
float3x3 objectToWorldIT = ObjectSrg::GetWorldMatrixInverseTranspose();
|
||||
ConstructTBN(IN.m_normal, IN.m_tangent, IN.m_bitangent, objectToWorld, objectToWorldIT, OUT.m_normal, OUT.m_tangent, OUT.m_bitangent);
|
||||
}
|
||||
|
||||
return OUT;
|
||||
}
|
||||
|
||||
struct PSDepthOutput
|
||||
{
|
||||
float m_depth : SV_Depth;
|
||||
};
|
||||
|
||||
PSDepthOutput MainPS(VertexOutput IN, bool isFrontFace : SV_IsFrontFace)
|
||||
{
|
||||
PSDepthOutput OUT;
|
||||
|
||||
OUT.m_depth = IN.m_position.z;
|
||||
|
||||
if(ShouldHandleParallaxInDepthShaders())
|
||||
{
|
||||
float3 tangents[UvSetCount] = { IN.m_tangent.xyz, IN.m_tangent.xyz };
|
||||
float3 bitangents[UvSetCount] = { IN.m_bitangent.xyz, IN.m_bitangent.xyz };
|
||||
|
||||
PrepareGeneratedTangent(IN.m_normal, IN.m_worldPosition, isFrontFace, IN.m_uv, UvSetCount, tangents, bitangents);
|
||||
|
||||
float3x3 uvMatrix = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrix : CreateIdentity3x3();
|
||||
float3x3 uvMatrixInverse = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrixInverse : CreateIdentity3x3();
|
||||
|
||||
GetParallaxInput(IN.m_normal, tangents[MaterialSrg::m_parallaxUvIndex], bitangents[MaterialSrg::m_parallaxUvIndex], MaterialSrg::m_heightmapScale, MaterialSrg::m_heightmapOffset,
|
||||
ObjectSrg::GetWorldMatrix(), uvMatrix, uvMatrixInverse,
|
||||
IN.m_uv[MaterialSrg::m_parallaxUvIndex], IN.m_worldPosition, OUT.m_depth);
|
||||
|
||||
OUT.m_depth += PdoShadowMapBias;
|
||||
}
|
||||
|
||||
// Clip Alpha
|
||||
float2 baseColorUV = IN.m_uv[MaterialSrg::m_baseColorMapUvIndex];
|
||||
float2 opacityUV = IN.m_uv[MaterialSrg::m_opacityMapUvIndex];
|
||||
float alpha = SampleAlpha(MaterialSrg::m_baseColorMap, MaterialSrg::m_opacityMap, baseColorUV, opacityUV, MaterialSrg::m_sampler, o_opacity_source);
|
||||
CheckClipping(alpha, MaterialSrg::m_opacityFactor);
|
||||
|
||||
return OUT;
|
||||
}
|
||||
#define SHADOWS 1
|
||||
#define ENABLE_ALPHA_CLIP 1
|
||||
#include "DepthPass_WithPS.azsli"
|
||||
|
||||
@@ -0,0 +1,317 @@
|
||||
/*
|
||||
* 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
|
||||
*
|
||||
*/
|
||||
|
||||
// SRGs
|
||||
#include <Atom/Features/PBR/ForwardPassSrg.azsli>
|
||||
|
||||
// Pass Output
|
||||
#include <Atom/Features/PBR/ForwardSubsurfacePassOutput.azsli>
|
||||
|
||||
// Utility
|
||||
#include <Atom/Features/ColorManagement/TransformColor.azsli>
|
||||
|
||||
// Custom Surface & Lighting
|
||||
#include <Atom/Features/PBR/Lighting/EnhancedLighting.azsli>
|
||||
|
||||
// Decals
|
||||
#include <Atom/Features/PBR/Decals.azsli>
|
||||
|
||||
// ---------- Vertex Shader ----------
|
||||
|
||||
struct VSInput
|
||||
{
|
||||
// Base fields (required by the template azsli file)...
|
||||
float3 m_position : POSITION;
|
||||
float3 m_normal : NORMAL;
|
||||
float4 m_tangent : TANGENT;
|
||||
float3 m_bitangent : BITANGENT;
|
||||
|
||||
// Extended fields (only referenced in this azsl file)...
|
||||
float2 m_uv0 : UV0;
|
||||
float2 m_uv1 : UV1;
|
||||
};
|
||||
|
||||
struct VSOutput
|
||||
{
|
||||
// Base fields (required by the template azsli file)...
|
||||
precise linear centroid float4 m_position : SV_Position;
|
||||
float3 m_normal: NORMAL;
|
||||
float3 m_tangent : TANGENT;
|
||||
float3 m_bitangent : BITANGENT;
|
||||
float3 m_worldPosition : UV0;
|
||||
float3 m_shadowCoords[ViewSrg::MaxCascadeCount] : UV5;
|
||||
|
||||
// Extended fields (only referenced in this azsl file)...
|
||||
float2 m_uv[UvSetCount] : UV1;
|
||||
float2 m_detailUv[UvSetCount] : UV3;
|
||||
};
|
||||
|
||||
VSOutput EnhancedPbr_ForwardPassVS(VSInput IN)
|
||||
{
|
||||
VSOutput OUT;
|
||||
|
||||
float4x4 objectToWorld = GetObjectToWorld();
|
||||
float4 worldPosition = mul(objectToWorld, float4(IN.m_position, 1.0));
|
||||
OUT.m_worldPosition = worldPosition.xyz;
|
||||
OUT.m_position = mul(ViewSrg::m_viewProjectionMatrix, worldPosition);
|
||||
|
||||
float2 uv[UvSetCount] = { IN.m_uv0, IN.m_uv1 };
|
||||
TransformUvs(uv, OUT.m_uv);
|
||||
|
||||
float2 detailUv[UvSetCount] = { IN.m_uv0, IN.m_uv1 };
|
||||
TransformDetailUvs(detailUv, OUT.m_detailUv);
|
||||
|
||||
// Shadow coords will be calculated in the pixel shader in this case
|
||||
bool skipShadowCoords = ShouldHandleParallax() && o_parallax_enablePixelDepthOffset;
|
||||
|
||||
float3x3 objectToWorldIT = GetNormalToWorld();
|
||||
ConstructTBN(IN.m_normal, IN.m_tangent, IN.m_bitangent, objectToWorld, objectToWorldIT, OUT.m_normal, OUT.m_tangent, OUT.m_bitangent);
|
||||
|
||||
// directional light shadow
|
||||
const uint shadowIndex = ViewSrg::m_shadowIndexDirectionalLight;
|
||||
if (o_enableShadows && !skipShadowCoords && shadowIndex < SceneSrg::m_directionalLightCount)
|
||||
{
|
||||
DirectionalLightShadow::GetShadowCoords(
|
||||
shadowIndex,
|
||||
worldPosition,
|
||||
OUT.m_normal,
|
||||
OUT.m_shadowCoords);
|
||||
}
|
||||
|
||||
return OUT;
|
||||
}
|
||||
|
||||
|
||||
// ---------- Pixel Shader ----------
|
||||
|
||||
PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace, out float depth)
|
||||
{
|
||||
const float3 vertexNormal = normalize(IN.m_normal);
|
||||
|
||||
// ------- Tangents & Bitangets -------
|
||||
float3 tangents[UvSetCount] = { IN.m_tangent.xyz, IN.m_tangent.xyz };
|
||||
float3 bitangents[UvSetCount] = { IN.m_bitangent.xyz, IN.m_bitangent.xyz };
|
||||
|
||||
if ((o_parallax_feature_enabled && !o_enableSubsurfaceScattering) || o_normal_useTexture || (o_clearCoat_enabled && o_clearCoat_normal_useTexture) || o_detail_normal_useTexture)
|
||||
{
|
||||
for (int i = 0; i != UvSetCount; ++i)
|
||||
{
|
||||
EvaluateTangentFrame(
|
||||
IN.m_normal,
|
||||
IN.m_worldPosition,
|
||||
isFrontFace,
|
||||
IN.m_uv[i],
|
||||
i,
|
||||
IN.m_tangent,
|
||||
IN.m_bitangent,
|
||||
tangents[i],
|
||||
bitangents[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// ------- Depth & Parallax -------
|
||||
|
||||
depth = IN.m_position.z;
|
||||
|
||||
bool displacementIsClipped = false;
|
||||
|
||||
// Parallax mapping's non uniform uv transformations break screen space subsurface scattering, disable it when subsurface scatteirng is enabled
|
||||
if(ShouldHandleParallax())
|
||||
{
|
||||
EnhancedSetPixelDepth(
|
||||
IN.m_worldPosition,
|
||||
IN.m_normal,
|
||||
tangents,
|
||||
bitangents,
|
||||
IN.m_uv,
|
||||
isFrontFace,
|
||||
IN.m_detailUv,
|
||||
IN.m_position.w,
|
||||
depth,
|
||||
displacementIsClipped);
|
||||
|
||||
// Adjust directional light shadow coorinates for parallax correction
|
||||
if(o_parallax_enablePixelDepthOffset)
|
||||
{
|
||||
const uint shadowIndex = ViewSrg::m_shadowIndexDirectionalLight;
|
||||
if (o_enableShadows && shadowIndex < SceneSrg::m_directionalLightCount)
|
||||
{
|
||||
DirectionalLightShadow::GetShadowCoords(shadowIndex, IN.m_worldPosition, vertexNormal, IN.m_shadowCoords);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SurfaceSettings surfaceSettings;
|
||||
Surface surface;
|
||||
surface.vertexNormal = vertexNormal;
|
||||
surface.position = IN.m_worldPosition;
|
||||
|
||||
// ------- Alpha & Clip -------
|
||||
// TODO: this often invokes a separate sample of the base color texture which is wasteful
|
||||
float alpha = GetAlphaAndClip(IN.m_uv);
|
||||
|
||||
EvaluateEnhancedSurface(IN.m_normal, IN.m_uv, IN.m_detailUv, tangents, bitangents, isFrontFace, displacementIsClipped, surface, surfaceSettings);
|
||||
|
||||
// ------- Lighting Data -------
|
||||
|
||||
LightingData lightingData;
|
||||
|
||||
// Light iterator
|
||||
lightingData.tileIterator.Init(IN.m_position, PassSrg::m_lightListRemapped, PassSrg::m_tileLightData);
|
||||
lightingData.Init(surface.position, surface.normal, surface.roughnessLinear);
|
||||
|
||||
// Directional light shadow coordinates
|
||||
lightingData.shadowCoords = IN.m_shadowCoords;
|
||||
|
||||
lightingData.emissiveLighting = surface.emissiveLighting;
|
||||
lightingData.diffuseAmbientOcclusion = surface.diffuseAmbientOcclusion;
|
||||
lightingData.specularOcclusion = surface.specularOcclusion;
|
||||
|
||||
// Diffuse and Specular response (used in IBL calculations)
|
||||
lightingData.specularResponse = FresnelSchlickWithRoughness(lightingData.NdotV, surface.specularF0, surface.roughnessLinear);
|
||||
lightingData.diffuseResponse = 1.0 - lightingData.specularResponse;
|
||||
|
||||
// ------- Thin Object Light Transmission -------
|
||||
|
||||
// Shrink (absolute) offset towards the normal opposite direction to ensure correct shadow map projection
|
||||
lightingData.shrinkFactor = surface.transmission.transmissionParams.x;
|
||||
|
||||
// Angle offset for subsurface scattering through thin objects
|
||||
lightingData.transmissionNdLBias = surface.transmission.transmissionParams.y;
|
||||
|
||||
// Attenuation applied to hide artifacts due to low-res shadow maps
|
||||
lightingData.distanceAttenuation = surface.transmission.transmissionParams.z;
|
||||
|
||||
if(o_clearCoat_feature_enabled)
|
||||
{
|
||||
// Clear coat layer has fixed IOR = 1.5 and transparent => F0 = (1.5 - 1)^2 / (1.5 + 1)^2 = 0.04
|
||||
lightingData.diffuseResponse *= 1.0 - (FresnelSchlickWithRoughness(lightingData.NdotV, float3(0.04, 0.04, 0.04), surface.clearCoat.roughness) * surface.clearCoat.factor);
|
||||
}
|
||||
|
||||
// ------- Multiscatter -------
|
||||
|
||||
lightingData.CalculateMultiscatterCompensation(surface.specularF0, o_specularF0_enableMultiScatterCompensation);
|
||||
|
||||
// ------- Lighting Calculation -------
|
||||
|
||||
// Apply Decals
|
||||
ApplyDecals(lightingData.tileIterator, surface);
|
||||
|
||||
// Apply Direct Lighting
|
||||
ApplyDirectLighting(surface, lightingData);
|
||||
|
||||
// Apply Image Based Lighting (IBL)
|
||||
ApplyIBL(surface, lightingData);
|
||||
|
||||
// Finalize Lighting
|
||||
lightingData.FinalizeLighting(surface.transmission.tint);
|
||||
|
||||
PbrLightingOutput lightingOutput = GetPbrLightingOutput(surface, lightingData, alpha);
|
||||
|
||||
// ------- Opacity -------
|
||||
|
||||
if (o_opacity_mode == OpacityMode::Blended || o_opacity_mode == OpacityMode::TintedTransparent)
|
||||
{
|
||||
// Increase opacity at grazing angles for surfaces with a low m_opacityAffectsSpecularFactor.
|
||||
// For m_opacityAffectsSpecularFactor values close to 0, that indicates a transparent surface
|
||||
// like glass, so it becomes less transparent at grazing angles. For m_opacityAffectsSpecularFactor
|
||||
// values close to 1.0, that indicates the absence of a surface entirely, so this effect should
|
||||
// not apply.
|
||||
float fresnelAlpha = FresnelSchlickWithRoughness(lightingData.NdotV, alpha, surface.roughnessLinear).x;
|
||||
alpha = lerp(fresnelAlpha, alpha, surfaceSettings.opacityAffectsSpecularFactor);
|
||||
}
|
||||
|
||||
// Note: lightingOutput rendertargets are not always used as named, particularly m_diffuseColor (target 0) and
|
||||
// m_specularColor (target 1). Comments below describe the differences when appropriate.
|
||||
|
||||
if (o_opacity_mode == OpacityMode::Blended)
|
||||
{
|
||||
// [GFX_TODO ATOM-13187] PbrLighting shouldn't be writing directly to render targets. It's confusing when
|
||||
// specular is being added to diffuse just because we're calling render target 0 "diffuse".
|
||||
|
||||
// For blended mode, we do (dest * alpha) + (source * 1.0). This allows the specular
|
||||
// to be added on top of the diffuse, but then the diffuse must be pre-multiplied.
|
||||
// It's done this way because surface transparency doesn't really change specular response (eg, glass).
|
||||
|
||||
lightingOutput.m_diffuseColor.rgb *= lightingOutput.m_diffuseColor.w; // pre-multiply diffuse
|
||||
|
||||
// Add specular. m_opacityAffectsSpecularFactor controls how much the alpha masks out specular contribution.
|
||||
float3 specular = lightingOutput.m_specularColor.rgb;
|
||||
specular = lerp(specular, specular * lightingOutput.m_diffuseColor.w, surfaceSettings.opacityAffectsSpecularFactor);
|
||||
lightingOutput.m_diffuseColor.rgb += specular;
|
||||
|
||||
lightingOutput.m_diffuseColor.w = alpha;
|
||||
}
|
||||
else if (o_opacity_mode == OpacityMode::TintedTransparent)
|
||||
{
|
||||
// See OpacityMode::Blended above for the basic method. TintedTransparent adds onto the above concept by supporting
|
||||
// colored alpha. This is currently a very basic calculation that uses the baseColor as a multiplier with strength
|
||||
// determined by the alpha. We'll modify this later to be more physically accurate and allow surface depth,
|
||||
// absorption, and interior color to be specified.
|
||||
//
|
||||
// The technique uses dual source blending to allow two separate sources to be part of the blending equation
|
||||
// even though ultimately only a single render target is being written to. m_diffuseColor is render target 0 and
|
||||
// m_specularColor render target 1, and the blend mode is (dest * source1color) + (source * 1.0).
|
||||
//
|
||||
// This means that m_specularColor.rgb (source 1) is multiplied against the destination, then
|
||||
// m_diffuseColor.rgb (source) is added to that, and the final result is stored in render target 0.
|
||||
|
||||
lightingOutput.m_diffuseColor.rgb *= lightingOutput.m_diffuseColor.w; // pre-multiply diffuse
|
||||
|
||||
// Add specular. m_opacityAffectsSpecularFactor controls how much the alpha masks out specular contribution.
|
||||
float3 specular = lightingOutput.m_specularColor.rgb;
|
||||
specular = lerp(specular, specular * lightingOutput.m_diffuseColor.w, surfaceSettings.opacityAffectsSpecularFactor);
|
||||
lightingOutput.m_diffuseColor.rgb += specular;
|
||||
|
||||
lightingOutput.m_specularColor.rgb = surface.baseColor * (1.0 - alpha);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Pack factor and quality, drawback: because of precision limit of float16 cannot represent exact 1, maximum representable value is 0.9961
|
||||
uint factorAndQuality = dot(round(float2(saturate(surface.subsurfaceScatteringFactor), surfaceSettings.subsurfaceScatteringQuality) * 255), float2(256, 1));
|
||||
lightingOutput.m_diffuseColor.w = factorAndQuality * (o_enableSubsurfaceScattering ? 1.0 : -1.0);
|
||||
lightingOutput.m_scatterDistance = surfaceSettings.scatterDistance;
|
||||
}
|
||||
|
||||
return lightingOutput;
|
||||
}
|
||||
|
||||
ForwardPassOutputWithDepth EnhancedPbr_ForwardPassPS(VSOutput IN, bool isFrontFace : SV_IsFrontFace)
|
||||
{
|
||||
ForwardPassOutputWithDepth OUT;
|
||||
float depth;
|
||||
|
||||
PbrLightingOutput lightingOutput = ForwardPassPS_Common(IN, isFrontFace, depth);
|
||||
|
||||
OUT.m_diffuseColor = lightingOutput.m_diffuseColor;
|
||||
OUT.m_specularColor = lightingOutput.m_specularColor;
|
||||
OUT.m_specularF0 = lightingOutput.m_specularF0;
|
||||
OUT.m_albedo = lightingOutput.m_albedo;
|
||||
OUT.m_normal = lightingOutput.m_normal;
|
||||
OUT.m_scatterDistance = lightingOutput.m_scatterDistance;
|
||||
OUT.m_depth = depth;
|
||||
return OUT;
|
||||
}
|
||||
|
||||
[earlydepthstencil]
|
||||
ForwardPassOutput EnhancedPbr_ForwardPassPS_EDS(VSOutput IN, bool isFrontFace : SV_IsFrontFace)
|
||||
{
|
||||
ForwardPassOutput OUT;
|
||||
float depth;
|
||||
|
||||
PbrLightingOutput lightingOutput = ForwardPassPS_Common(IN, isFrontFace, depth);
|
||||
|
||||
OUT.m_diffuseColor = lightingOutput.m_diffuseColor;
|
||||
OUT.m_specularColor = lightingOutput.m_specularColor;
|
||||
OUT.m_specularF0 = lightingOutput.m_specularF0;
|
||||
OUT.m_albedo = lightingOutput.m_albedo;
|
||||
OUT.m_normal = lightingOutput.m_normal;
|
||||
OUT.m_scatterDistance = lightingOutput.m_scatterDistance;
|
||||
|
||||
return OUT;
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#include <Atom/Features/ParallaxMapping.azsli>
|
||||
#include "../MaterialInputs/ParallaxInput.azsli"
|
||||
#include <Atom/Features/MatrixUtility.azsli>
|
||||
|
||||
void EnhancedSetPixelDepth(
|
||||
float3 worldPosition,
|
||||
float3 normal,
|
||||
float3 tangents[UvSetCount],
|
||||
float3 bitangents[UvSetCount],
|
||||
float2 uvs[UvSetCount],
|
||||
bool isFrontFace,
|
||||
inout float2 detailUv[UvSetCount],
|
||||
inout float depthCS,
|
||||
out float depth,
|
||||
out bool isClipped)
|
||||
{
|
||||
// GetParallaxInput applies an tangent offset to the UV. We want to apply the same offset to the detailUv (note: this needs to be tested with content)
|
||||
// The math is: offset = newUv - oldUv; detailUv += offset;
|
||||
// This is the same as: detailUv -= oldUv; detailUv += newUv;
|
||||
detailUv[MaterialSrg::m_parallaxUvIndex] -= uvs[MaterialSrg::m_parallaxUvIndex];
|
||||
|
||||
float3x3 uvMatrix = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrix : CreateIdentity3x3();
|
||||
float3x3 uvMatrixInverse = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrixInverse : CreateIdentity3x3();
|
||||
|
||||
GetParallaxInput(
|
||||
normal, tangents[MaterialSrg::m_parallaxUvIndex], bitangents[MaterialSrg::m_parallaxUvIndex],
|
||||
MaterialSrg::m_heightmapScale, MaterialSrg::m_heightmapOffset,
|
||||
ObjectSrg::GetWorldMatrix(), uvMatrix, uvMatrixInverse,
|
||||
uvs[MaterialSrg::m_parallaxUvIndex], worldPosition, depth, depthCS, isClipped);
|
||||
|
||||
// Apply second part of the offset to the detail UV (see comment above)
|
||||
detailUv[MaterialSrg::m_parallaxUvIndex] -= uvs[MaterialSrg::m_parallaxUvIndex];
|
||||
}
|
||||
+149
@@ -0,0 +1,149 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#include <Atom/Features/PBR/Surfaces/EnhancedSurface.azsli>
|
||||
#include <Atom/Features/MatrixUtility.azsli>
|
||||
|
||||
void EvaluateEnhancedSurface(
|
||||
float3 normal,
|
||||
float2 uvs[UvSetCount],
|
||||
float2 detailUvs[UvSetCount],
|
||||
float3 tangents[UvSetCount],
|
||||
float3 bitangents[UvSetCount],
|
||||
bool isFrontFace,
|
||||
bool displacementIsClipped,
|
||||
inout Surface surface,
|
||||
out SurfaceSettings surfaceSettings)
|
||||
{
|
||||
// ------- Detail Layer Setup -------
|
||||
|
||||
const float2 detailUv = detailUvs[MaterialSrg::m_detail_allMapsUvIndex];
|
||||
|
||||
// When the detail maps and the detail blend mask are on the same UV, they both use the transformed detail UVs because they are 'attached' to each other
|
||||
const float2 detailBlendMaskUv = (MaterialSrg::m_detail_blendMask_uvIndex == MaterialSrg::m_detail_allMapsUvIndex) ?
|
||||
detailUvs[MaterialSrg::m_detail_blendMask_uvIndex] :
|
||||
uvs[MaterialSrg::m_detail_blendMask_uvIndex];
|
||||
|
||||
const float detailLayerBlendFactor = GetDetailLayerBlendFactor(
|
||||
MaterialSrg::m_detail_blendMask_texture,
|
||||
MaterialSrg::m_sampler,
|
||||
detailBlendMaskUv,
|
||||
o_detail_blendMask_useTexture,
|
||||
MaterialSrg::m_detail_blendFactor);
|
||||
|
||||
// ------- Normal -------
|
||||
|
||||
float2 normalUv = uvs[MaterialSrg::m_normalMapUvIndex];
|
||||
float3x3 uvMatrix = MaterialSrg::m_normalMapUvIndex == 0 ? MaterialSrg::m_uvMatrix : CreateIdentity3x3(); // By design, only UV0 is allowed to apply transforms.
|
||||
float detailLayerNormalFactor = MaterialSrg::m_detail_normal_factor * detailLayerBlendFactor;
|
||||
surface.normal = GetDetailedNormalInputWS(
|
||||
isFrontFace, normal,
|
||||
tangents[MaterialSrg::m_normalMapUvIndex], bitangents[MaterialSrg::m_normalMapUvIndex], MaterialSrg::m_normalMap, MaterialSrg::m_sampler, normalUv, MaterialSrg::m_normalFactor, MaterialSrg::m_flipNormalX, MaterialSrg::m_flipNormalY, uvMatrix, o_normal_useTexture,
|
||||
tangents[MaterialSrg::m_detail_allMapsUvIndex], bitangents[MaterialSrg::m_detail_allMapsUvIndex], MaterialSrg::m_detail_normal_texture, MaterialSrg::m_sampler, detailUv, detailLayerNormalFactor, MaterialSrg::m_detail_normal_flipX, MaterialSrg::m_detail_normal_flipY, MaterialSrg::m_detailUvMatrix, o_detail_normal_useTexture);
|
||||
|
||||
//--------------------- Base Color ----------------------
|
||||
|
||||
// [GFX TODO][ATOM-1761] Figure out how we want our base material to expect channels to be encoded, and apply that to the way we pack alpha.
|
||||
|
||||
float detailLayerBaseColorFactor = MaterialSrg::m_detail_baseColor_factor * detailLayerBlendFactor;
|
||||
float2 baseColorUv = uvs[MaterialSrg::m_baseColorMapUvIndex];
|
||||
|
||||
float3 baseColor = GetDetailedBaseColorInput(
|
||||
MaterialSrg::m_baseColorMap, MaterialSrg::m_sampler, baseColorUv, o_baseColor_useTexture, MaterialSrg::m_baseColor, MaterialSrg::m_baseColorFactor, o_baseColorTextureBlendMode,
|
||||
MaterialSrg::m_detail_baseColor_texture, MaterialSrg::m_sampler, detailUv, o_detail_baseColor_useTexture, detailLayerBaseColorFactor);
|
||||
|
||||
if(o_parallax_highlightClipping && displacementIsClipped)
|
||||
{
|
||||
ApplyParallaxClippingHighlight(baseColor);
|
||||
}
|
||||
|
||||
// ------- Metallic -------
|
||||
|
||||
float metallic = 0;
|
||||
if(!o_enableSubsurfaceScattering) // If subsurface scattering is enabled skip texture lookup for metallic, as this quantity won't be used anyway
|
||||
{
|
||||
float2 metallicUv = uvs[MaterialSrg::m_metallicMapUvIndex];
|
||||
metallic = GetMetallicInput(MaterialSrg::m_metallicMap, MaterialSrg::m_sampler, metallicUv, MaterialSrg::m_metallicFactor, o_metallic_useTexture);
|
||||
}
|
||||
|
||||
// ------- Specular -------
|
||||
|
||||
float2 specularUv = uvs[MaterialSrg::m_specularF0MapUvIndex];
|
||||
float specularF0Factor = GetSpecularInput(MaterialSrg::m_specularF0Map, MaterialSrg::m_sampler, specularUv, MaterialSrg::m_specularF0Factor, o_specularF0_useTexture);
|
||||
|
||||
surface.SetAlbedoAndSpecularF0(baseColor, specularF0Factor, metallic);
|
||||
|
||||
// ------- Roughness -------
|
||||
|
||||
float2 roughnessUv = uvs[MaterialSrg::m_roughnessMapUvIndex];
|
||||
surface.roughnessLinear = GetRoughnessInput(MaterialSrg::m_roughnessMap, MaterialSrg::m_sampler, roughnessUv, MaterialSrg::m_roughnessFactor,
|
||||
MaterialSrg::m_roughnessLowerBound, MaterialSrg::m_roughnessUpperBound, o_roughness_useTexture);
|
||||
surface.CalculateRoughnessA();
|
||||
|
||||
// ------- Subsurface -------
|
||||
|
||||
float2 subsurfaceUv = uvs[MaterialSrg::m_subsurfaceScatteringInfluenceMapUvIndex];
|
||||
surface.subsurfaceScatteringFactor = GetSubsurfaceInput(MaterialSrg::m_subsurfaceScatteringInfluenceMap, MaterialSrg::m_sampler, subsurfaceUv, MaterialSrg::m_subsurfaceScatteringFactor);
|
||||
surfaceSettings.subsurfaceScatteringQuality = MaterialSrg::m_subsurfaceScatteringQuality;
|
||||
surfaceSettings.scatterDistance = MaterialSrg::m_scatterDistance;
|
||||
|
||||
// ------- Transmission -------
|
||||
|
||||
float2 transmissionUv = uvs[MaterialSrg::m_transmissionThicknessMapUvIndex];
|
||||
float4 transmissionTintThickness = GeTransmissionInput(MaterialSrg::m_transmissionThicknessMap, MaterialSrg::m_sampler, transmissionUv, MaterialSrg::m_transmissionTintThickness);
|
||||
surface.transmission.tint = transmissionTintThickness.rgb;
|
||||
surface.transmission.thickness = transmissionTintThickness.w;
|
||||
surface.transmission.transmissionParams = MaterialSrg::m_transmissionParams;
|
||||
surface.transmission.scatterDistance = MaterialSrg::m_scatterDistance;
|
||||
|
||||
// ------- Anisotropy -------
|
||||
|
||||
if (o_enableAnisotropy)
|
||||
{
|
||||
// Convert the angle from [0..1] = [0 .. 180 degrees] to radians [0 .. PI]
|
||||
const float anisotropyAngle = MaterialSrg::m_anisotropicAngle * PI;
|
||||
const float anisotropyFactor = MaterialSrg::m_anisotropicFactor;
|
||||
surface.anisotropy.Init(surface.normal, tangents[0], bitangents[0], anisotropyAngle, anisotropyFactor, surface.roughnessA);
|
||||
}
|
||||
|
||||
// ------- Emissive -------
|
||||
|
||||
float2 emissiveUv = uvs[MaterialSrg::m_emissiveMapUvIndex];
|
||||
surface.emissiveLighting = GetEmissiveInput(MaterialSrg::m_emissiveMap, MaterialSrg::m_sampler, emissiveUv, MaterialSrg::m_emissiveIntensity, MaterialSrg::m_emissiveColor.rgb, o_emissiveEnabled, o_emissive_useTexture);
|
||||
|
||||
// ------- Occlusion -------
|
||||
|
||||
surface.diffuseAmbientOcclusion = GetOcclusionInput(MaterialSrg::m_diffuseOcclusionMap, MaterialSrg::m_sampler, uvs[MaterialSrg::m_diffuseOcclusionMapUvIndex], MaterialSrg::m_diffuseOcclusionFactor, o_diffuseOcclusion_useTexture);
|
||||
surface.specularOcclusion = GetOcclusionInput(MaterialSrg::m_specularOcclusionMap, MaterialSrg::m_sampler, uvs[MaterialSrg::m_specularOcclusionMapUvIndex], MaterialSrg::m_specularOcclusionFactor, o_specularOcclusion_useTexture);
|
||||
|
||||
// ------- Clearcoat -------
|
||||
|
||||
// [GFX TODO][ATOM-14603]: Clean up the double uses of these clear coat flags
|
||||
if(o_clearCoat_feature_enabled)
|
||||
{
|
||||
if(o_clearCoat_enabled)
|
||||
{
|
||||
float3x3 uvMatrix = MaterialSrg::m_clearCoatNormalMapUvIndex == 0 ? MaterialSrg::m_uvMatrix : CreateIdentity3x3();
|
||||
GetClearCoatInputs(MaterialSrg::m_clearCoatInfluenceMap, uvs[MaterialSrg::m_clearCoatInfluenceMapUvIndex], MaterialSrg::m_clearCoatFactor, o_clearCoat_factor_useTexture,
|
||||
MaterialSrg::m_clearCoatRoughnessMap, uvs[MaterialSrg::m_clearCoatRoughnessMapUvIndex], MaterialSrg::m_clearCoatRoughness, o_clearCoat_roughness_useTexture,
|
||||
MaterialSrg::m_clearCoatNormalMap, uvs[MaterialSrg::m_clearCoatNormalMapUvIndex], normal, o_clearCoat_normal_useTexture, MaterialSrg::m_clearCoatNormalStrength,
|
||||
uvMatrix, tangents[MaterialSrg::m_clearCoatNormalMapUvIndex], bitangents[MaterialSrg::m_clearCoatNormalMapUvIndex],
|
||||
MaterialSrg::m_sampler, isFrontFace,
|
||||
surface.clearCoat.factor, surface.clearCoat.roughness, surface.clearCoat.normal);
|
||||
}
|
||||
|
||||
// manipulate base layer f0 if clear coat is enabled
|
||||
// modify base layer's normal incidence reflectance
|
||||
// for the derivation of the following equation please refer to:
|
||||
// https://google.github.io/filament/Filament.md.html#materialsystem/clearcoatmodel/baselayermodification
|
||||
float3 f0 = (1.0 - 5.0 * sqrt(surface.specularF0)) / (5.0 - sqrt(surface.specularF0));
|
||||
surface.specularF0 = lerp(surface.specularF0, f0 * f0, surface.clearCoat.factor);
|
||||
}
|
||||
|
||||
surfaceSettings.opacityAffectsSpecularFactor = MaterialSrg::m_opacityAffectsSpecularFactor;
|
||||
}
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#include <Atom/Features/PBR/Surfaces/StandardSurface.azsli>
|
||||
#include <Atom/Features/MatrixUtility.azsli>
|
||||
|
||||
void EvaluateStandardSurface(
|
||||
float3 normal,
|
||||
float2 uv[UvSetCount],
|
||||
float3 tangents[UvSetCount],
|
||||
float3 bitangents[UvSetCount],
|
||||
bool isFrontFace,
|
||||
bool displacementIsClipped,
|
||||
inout Surface surface,
|
||||
out SurfaceSettings surfaceSettings)
|
||||
{
|
||||
// ------- Normal -------
|
||||
|
||||
float2 normalUv = uv[MaterialSrg::m_normalMapUvIndex];
|
||||
float3x3 uvMatrix = MaterialSrg::m_normalMapUvIndex == 0 ? MaterialSrg::m_uvMatrix : CreateIdentity3x3(); // By design, only UV0 is allowed to apply transforms.
|
||||
surface.normal = GetNormalInputWS(MaterialSrg::m_normalMap, MaterialSrg::m_sampler, normalUv, MaterialSrg::m_flipNormalX, MaterialSrg::m_flipNormalY, isFrontFace, normal,
|
||||
tangents[MaterialSrg::m_normalMapUvIndex], bitangents[MaterialSrg::m_normalMapUvIndex], uvMatrix, o_normal_useTexture, MaterialSrg::m_normalFactor);
|
||||
|
||||
// ------- Base Color -------
|
||||
|
||||
float2 baseColorUv = uv[MaterialSrg::m_baseColorMapUvIndex];
|
||||
float3 sampledColor = GetBaseColorInput(MaterialSrg::m_baseColorMap, MaterialSrg::m_sampler, baseColorUv, MaterialSrg::m_baseColor.rgb, o_baseColor_useTexture);
|
||||
float3 baseColor = BlendBaseColor(sampledColor, MaterialSrg::m_baseColor.rgb, MaterialSrg::m_baseColorFactor, o_baseColorTextureBlendMode, o_baseColor_useTexture);
|
||||
|
||||
if(o_parallax_highlightClipping && displacementIsClipped)
|
||||
{
|
||||
ApplyParallaxClippingHighlight(baseColor);
|
||||
}
|
||||
|
||||
// ------- Metallic -------
|
||||
|
||||
float2 metallicUv = uv[MaterialSrg::m_metallicMapUvIndex];
|
||||
float metallic = GetMetallicInput(MaterialSrg::m_metallicMap, MaterialSrg::m_sampler, metallicUv, MaterialSrg::m_metallicFactor, o_metallic_useTexture);
|
||||
|
||||
// ------- Specular -------
|
||||
|
||||
float2 specularUv = uv[MaterialSrg::m_specularF0MapUvIndex];
|
||||
float specularF0Factor = GetSpecularInput(MaterialSrg::m_specularF0Map, MaterialSrg::m_sampler, specularUv, MaterialSrg::m_specularF0Factor, o_specularF0_useTexture);
|
||||
|
||||
surface.SetAlbedoAndSpecularF0(baseColor, specularF0Factor, metallic);
|
||||
|
||||
// ------- Roughness -------
|
||||
|
||||
float2 roughnessUv = uv[MaterialSrg::m_roughnessMapUvIndex];
|
||||
surface.roughnessLinear = GetRoughnessInput(MaterialSrg::m_roughnessMap, MaterialSrg::m_sampler, roughnessUv, MaterialSrg::m_roughnessFactor,
|
||||
MaterialSrg::m_roughnessLowerBound, MaterialSrg::m_roughnessUpperBound, o_roughness_useTexture);
|
||||
surface.CalculateRoughnessA();
|
||||
|
||||
// ------- Emissive -------
|
||||
|
||||
float2 emissiveUv = uv[MaterialSrg::m_emissiveMapUvIndex];
|
||||
surface.emissiveLighting = GetEmissiveInput(MaterialSrg::m_emissiveMap, MaterialSrg::m_sampler, emissiveUv, MaterialSrg::m_emissiveIntensity, MaterialSrg::m_emissiveColor.rgb, o_emissiveEnabled, o_emissive_useTexture);
|
||||
|
||||
// ------- Occlusion -------
|
||||
|
||||
surface.diffuseAmbientOcclusion = GetOcclusionInput(MaterialSrg::m_diffuseOcclusionMap, MaterialSrg::m_sampler, uv[MaterialSrg::m_diffuseOcclusionMapUvIndex], MaterialSrg::m_diffuseOcclusionFactor, o_diffuseOcclusion_useTexture);
|
||||
surface.specularOcclusion = GetOcclusionInput(MaterialSrg::m_specularOcclusionMap, MaterialSrg::m_sampler, uv[MaterialSrg::m_specularOcclusionMapUvIndex], MaterialSrg::m_specularOcclusionFactor, o_specularOcclusion_useTexture);
|
||||
|
||||
// ------- Clearcoat -------
|
||||
|
||||
// [GFX TODO][ATOM-14603]: Clean up the double uses of these clear coat flags
|
||||
if(o_clearCoat_feature_enabled)
|
||||
{
|
||||
if(o_clearCoat_enabled)
|
||||
{
|
||||
float3x3 uvMatrix = MaterialSrg::m_clearCoatNormalMapUvIndex == 0 ? MaterialSrg::m_uvMatrix : CreateIdentity3x3();
|
||||
GetClearCoatInputs(MaterialSrg::m_clearCoatInfluenceMap, uv[MaterialSrg::m_clearCoatInfluenceMapUvIndex], MaterialSrg::m_clearCoatFactor, o_clearCoat_factor_useTexture,
|
||||
MaterialSrg::m_clearCoatRoughnessMap, uv[MaterialSrg::m_clearCoatRoughnessMapUvIndex], MaterialSrg::m_clearCoatRoughness, o_clearCoat_roughness_useTexture,
|
||||
MaterialSrg::m_clearCoatNormalMap, uv[MaterialSrg::m_clearCoatNormalMapUvIndex], normal, o_clearCoat_normal_useTexture, MaterialSrg::m_clearCoatNormalStrength,
|
||||
uvMatrix, tangents[MaterialSrg::m_clearCoatNormalMapUvIndex], bitangents[MaterialSrg::m_clearCoatNormalMapUvIndex],
|
||||
MaterialSrg::m_sampler, isFrontFace,
|
||||
surface.clearCoat.factor, surface.clearCoat.roughness, surface.clearCoat.normal);
|
||||
}
|
||||
|
||||
// manipulate base layer f0 if clear coat is enabled
|
||||
// modify base layer's normal incidence reflectance
|
||||
// for the derivation of the following equation please refer to:
|
||||
// https://google.github.io/filament/Filament.md.html#materialsystem/clearcoatmodel/baselayermodification
|
||||
float3 f0 = (1.0 - 5.0 * sqrt(surface.specularF0)) / (5.0 - sqrt(surface.specularF0));
|
||||
surface.specularF0 = lerp(surface.specularF0, f0 * f0, surface.clearCoat.factor);
|
||||
}
|
||||
|
||||
// ------- Opacity -------
|
||||
surfaceSettings.opacityAffectsSpecularFactor = MaterialSrg::m_opacityAffectsSpecularFactor;
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* 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
|
||||
*
|
||||
*/
|
||||
|
||||
// The built-in tangent frame evaluation forwards the tangent frame interpolanted from the vertex
|
||||
// data streams for UV-index 0. For UV-index 1, the tangent frame is computed from UV surface gradients.
|
||||
void EvaluateTangentFrame(
|
||||
float3 normal,
|
||||
float3 worldPosition,
|
||||
bool isFrontFace,
|
||||
float2 uv,
|
||||
int uvIndex,
|
||||
// The input tangent and bitangent vectors are optional and used to forward data from interpolants
|
||||
float3 IN_tangent,
|
||||
float3 IN_bitangent,
|
||||
out float3 OUT_tangent,
|
||||
out float3 OUT_bitangent)
|
||||
{
|
||||
if (DrawSrg::GetTangentAtUv(uvIndex) == 0)
|
||||
{
|
||||
OUT_tangent = IN_tangent;
|
||||
OUT_bitangent = IN_bitangent;
|
||||
}
|
||||
else
|
||||
{
|
||||
SurfaceGradientNormalMapping_Init(normal, worldPosition, !isFrontFace);
|
||||
SurfaceGradientNormalMapping_GenerateTB(uv, OUT_tangent, OUT_bitangent);
|
||||
}
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#include <Atom/Features/ParallaxMapping.azsli>
|
||||
#include "../MaterialInputs/ParallaxInput.azsli"
|
||||
#include <Atom/Features/MatrixUtility.azsli>
|
||||
|
||||
void MultilayerSetPixelDepth(
|
||||
float3 blendMask,
|
||||
float3 worldPosition,
|
||||
float3 normal,
|
||||
float3 tangents[UvSetCount],
|
||||
float3 bitangents[UvSetCount],
|
||||
float2 uvs[UvSetCount],
|
||||
bool isFrontFace,
|
||||
out float depth)
|
||||
{
|
||||
s_blendMaskFromVertexStream = blendMask;
|
||||
|
||||
float3x3 uvMatrix = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrix : CreateIdentity3x3();
|
||||
float3x3 uvMatrixInverse = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrixInverse : CreateIdentity3x3();
|
||||
|
||||
float parallaxOverallOffset = MaterialSrg::m_displacementMax;
|
||||
float parallaxOverallFactor = MaterialSrg::m_displacementMax - MaterialSrg::m_displacementMin;
|
||||
GetParallaxInput(
|
||||
normal, tangents[MaterialSrg::m_parallaxUvIndex], bitangents[MaterialSrg::m_parallaxUvIndex],
|
||||
parallaxOverallFactor, parallaxOverallOffset,
|
||||
ObjectSrg::GetWorldMatrix(), uvMatrix, uvMatrixInverse,
|
||||
uvs[MaterialSrg::m_parallaxUvIndex], worldPosition, depth);
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#include <Atom/Features/ParallaxMapping.azsli>
|
||||
#include "../MaterialInputs/ParallaxInput.azsli"
|
||||
#include <Atom/Features/MatrixUtility.azsli>
|
||||
|
||||
void SetPixelDepth(
|
||||
inout float3 worldPosition,
|
||||
float3 normal,
|
||||
float3 tangents[UvSetCount],
|
||||
float3 bitangents[UvSetCount],
|
||||
inout float2 uvs[UvSetCount],
|
||||
bool isFrontFace,
|
||||
inout float depthNDC)
|
||||
{
|
||||
float3x3 uvMatrix = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrix : CreateIdentity3x3();
|
||||
float3x3 uvMatrixInverse = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrixInverse : CreateIdentity3x3();
|
||||
|
||||
GetParallaxInput(
|
||||
normal, tangents[MaterialSrg::m_parallaxUvIndex], bitangents[MaterialSrg::m_parallaxUvIndex],
|
||||
MaterialSrg::m_heightmapScale, MaterialSrg::m_heightmapOffset,
|
||||
ObjectSrg::GetWorldMatrix(), uvMatrix, uvMatrixInverse,
|
||||
uvs[MaterialSrg::m_parallaxUvIndex], worldPosition, depthNDC);
|
||||
}
|
||||
|
||||
void SetPixelDepth(
|
||||
inout float3 worldPosition,
|
||||
float3 normal,
|
||||
float3 tangents[UvSetCount],
|
||||
float3 bitangents[UvSetCount],
|
||||
inout float2 uvs[UvSetCount],
|
||||
bool isFrontFace,
|
||||
inout float depthCS,
|
||||
inout float depthNDC,
|
||||
out bool isClipped)
|
||||
{
|
||||
float3x3 uvMatrix = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrix : CreateIdentity3x3();
|
||||
float3x3 uvMatrixInverse = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrixInverse : CreateIdentity3x3();
|
||||
|
||||
GetParallaxInput(
|
||||
normal, tangents[MaterialSrg::m_parallaxUvIndex], bitangents[MaterialSrg::m_parallaxUvIndex],
|
||||
MaterialSrg::m_heightmapScale, MaterialSrg::m_heightmapOffset,
|
||||
ObjectSrg::GetWorldMatrix(), uvMatrix, uvMatrixInverse,
|
||||
uvs[MaterialSrg::m_parallaxUvIndex], worldPosition, depthNDC, depthCS, isClipped);
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* 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 "../MaterialInputs/AlphaInput.azsli"
|
||||
|
||||
float GetAlphaAndClip(float2 uvs[UvSetCount])
|
||||
{
|
||||
// Alpha
|
||||
float2 baseColorUV = uvs[MaterialSrg::m_baseColorMapUvIndex];
|
||||
float2 opacityUV = uvs[MaterialSrg::m_opacityMapUvIndex];
|
||||
float alpha = SampleAlpha(MaterialSrg::m_baseColorMap, MaterialSrg::m_opacityMap, baseColorUV, opacityUV, MaterialSrg::m_sampler, o_opacity_source);
|
||||
CheckClipping(alpha, MaterialSrg::m_opacityFactor);
|
||||
return MaterialSrg::m_opacityFactor * alpha;
|
||||
}
|
||||
+4
-5
@@ -6,8 +6,7 @@
|
||||
*
|
||||
*/
|
||||
|
||||
// NOTE: This file is a temporary workaround until .shader files can #define macros for their .azsl files
|
||||
|
||||
#define QUALITY_LOW_END 1
|
||||
|
||||
#include "StandardPBR_ForwardPass.azsl"
|
||||
float3x3 GetNormalToWorld()
|
||||
{
|
||||
return ObjectSrg::GetWorldMatrixInverseTranspose();
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
/*
|
||||
* 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
|
||||
*
|
||||
*/
|
||||
|
||||
float4x4 GetObjectToWorld()
|
||||
{
|
||||
return ObjectSrg::GetWorldMatrix();
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* 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
|
||||
*
|
||||
*/
|
||||
|
||||
void TransformDetailUvs(in float2 IN[UvSetCount], out float2 OUT[UvSetCount])
|
||||
{
|
||||
// Our standard practice is to only transform the first UV as that's the one we expect to be used for
|
||||
// tiling. But for detail maps you could actually use either UV stream for tiling. There is no concern about applying
|
||||
// the same transform to both UV sets because the detail map feature forces the same UV set to be used for all detail maps.
|
||||
// Note we might be able to combine these into a single UV similar to what Skin.materialtype does,
|
||||
// but we would need to address how it works with the parallax code below that indexes into the m_detailUV array.
|
||||
OUT[0] = mul(MaterialSrg::m_detailUvMatrix, float3(IN[0], 1.0)).xy;
|
||||
OUT[1] = mul(MaterialSrg::m_detailUvMatrix, float3(IN[1], 1.0)).xy;
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
* 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
|
||||
*
|
||||
*/
|
||||
|
||||
void TransformUvs(in float2 IN[UvSetCount], out float2 OUT[UvSetCount])
|
||||
{
|
||||
// By design, only UV0 is allowed to apply transforms.
|
||||
OUT[0] = mul(MaterialSrg::m_uvMatrix, float3(IN[0], 1.0)).xy;
|
||||
OUT[1] = IN[1];
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
# Upcoming material system changes
|
||||
|
||||
Currently, `.materialtype` files specify a set of shaders for each pass in the rendering pipeline.
|
||||
For example, the `StandardPBR.materialtype` asset specifies the following shaders:
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"file": "./StandardPBR_ForwardPass.shader",
|
||||
"tag": "ForwardPass"
|
||||
},
|
||||
{
|
||||
"file": "./StandardPBR_ForwardPass_EDS.shader",
|
||||
"tag": "ForwardPass_EDS"
|
||||
},
|
||||
{
|
||||
"file": "./StandardPBR_LowEndForward.shader",
|
||||
"tag": "LowEndForward"
|
||||
},
|
||||
{
|
||||
"file": "./StandardPBR_LowEndForward_EDS.shader",
|
||||
"tag": "LowEndForward_EDS"
|
||||
},
|
||||
{
|
||||
"file": "Shaders/Shadow/Shadowmap.shader",
|
||||
"tag": "Shadowmap"
|
||||
},
|
||||
{
|
||||
"file": "./StandardPBR_Shadowmap_WithPS.shader",
|
||||
"tag": "Shadowmap_WithPS"
|
||||
},
|
||||
{
|
||||
"file": "Shaders/Depth/DepthPass.shader",
|
||||
"tag": "DepthPass"
|
||||
},
|
||||
{
|
||||
"file": "./StandardPBR_DepthPass_WithPS.shader",
|
||||
"tag": "DepthPass_WithPS"
|
||||
},
|
||||
{
|
||||
"file": "Shaders/MotionVector/MeshMotionVector.shader",
|
||||
"tag": "MeshMotionVector"
|
||||
},
|
||||
{
|
||||
"file": "Shaders/Depth/DepthPassTransparentMin.shader",
|
||||
"tag": "DepthPassTransparentMin"
|
||||
},
|
||||
{
|
||||
"file": "Shaders/Depth/DepthPassTransparentMax.shader",
|
||||
"tag": "DepthPassTransparentMax"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
**This will be changing in a future release** to a material type description that specifies shader snippets (aka material functions)
|
||||
instead of explicit shaders.
|
||||
|
||||
## Why is it changing?
|
||||
|
||||
There are two primary reasons to move to a different scheme.
|
||||
|
||||
1. Material types are strongly coupled to the rendering pipeline. If a user wants to change the pipeline, or the engine wants to use, for example, a custom pipeline for mobile, or VR, this isn't possible today without cloning existing material types and changing the shader array.
|
||||
2. The material canvas work that has been prioritized to allow artist-driven material customization benefits from a more modular construction of materials. For example, we'd like to apply a "wind graph" and mix and match that with a "foliage graph" to describe the appearance of some foliage. The current material type description couples all the geometric passes with the material and lighting passes, which makes this sort of decomposition difficult.
|
||||
|
||||
## What is it changing to?
|
||||
|
||||
The best way to understand how this is changing is to inspect the current structure of `EnhancedPBR_ForwardPass.azsl` and `StandardPBR_ForwardPass.azsl`.
|
||||
These shaders start with a number of includes to specify the SRG as follows:
|
||||
|
||||
```hlsl
|
||||
#include "StandardPBR_Common.azsli"
|
||||
#include <Atom/Features/PBR/DefaultObjectSrg.azsli>
|
||||
```
|
||||
|
||||
Later, it includes a number of material functions, for example:
|
||||
|
||||
```hlsl
|
||||
#include "MaterialFunctions/EvaluateStandardSurface.azsli"
|
||||
#include "MaterialFunctions/EvaluateTangentFrame.azsli"
|
||||
#include "MaterialFunctions/ParallaxDepth.azsli"
|
||||
#include "MaterialFunctions/StandardGetNormalToWorld.azsli"
|
||||
#include "MaterialFunctions/StandardGetObjectToWorld.azsli"
|
||||
#include "MaterialFunctions/StandardGetAlphaAndClip.azsli"
|
||||
#include "MaterialFunctions/StandardTransformUvs.azsli"
|
||||
```
|
||||
|
||||
The material function headers define functions that may later be overridden using material graphs.
|
||||
|
||||
Finally, in the case of the standard surface shader, it includes an implementation file:
|
||||
|
||||
```hlsl
|
||||
#include "StandardSurface_ForwardPass.azsli"
|
||||
```
|
||||
|
||||
This file, if you inspect it, _makes no reference to `MaterialSrg`_, and furthermore, does not include files needed to implement any of the material functions.
|
||||
In other words, the structure of the standard pbr forward shader is such that it can be assembled with different components, specifing the SRG, material functions, and implementation.
|
||||
|
||||
In the future, a material pipeline abstraction will allow the `materialtype` asset to specify _only_ the material function files, and the tuple of `materialtype` and `materialpipeline` will allow the material builder to assemble the shader on behalf of the user. The final piece to the puzzle is that (again, in the future), material canvas (in active development) can produce material functions to replace the built-in ones.
|
||||
+8
-100
@@ -21,104 +21,12 @@ COMMON_OPTIONS_PARALLAX(o_layer3_)
|
||||
|
||||
#include "./StandardMultilayerPBR_Common.azsli"
|
||||
|
||||
struct VSInput
|
||||
{
|
||||
float3 m_position : POSITION;
|
||||
float2 m_uv0 : UV0;
|
||||
float2 m_uv1 : UV1;
|
||||
#include "MaterialFunctions/StandardGetObjectToWorld.azsli"
|
||||
#include "MaterialFunctions/StandardGetNormalToWorld.azsli"
|
||||
#include "MaterialFunctions/EvaluateTangentFrame.azsli"
|
||||
#include "MaterialFunctions/StandardTransformUvs.azsli"
|
||||
#include "MaterialFunctions/MultilayerParallaxDepth.azsli"
|
||||
|
||||
// only used for parallax depth calculation
|
||||
float3 m_normal : NORMAL;
|
||||
float4 m_tangent : TANGENT;
|
||||
float3 m_bitangent : BITANGENT;
|
||||
|
||||
// This gets set automatically by the system at runtime only if it's available.
|
||||
// There is a soft naming convention that associates this with o_blendMask_isBound, which will be set to true whenever m_optional_blendMask is available.
|
||||
// (search "m_optional_" in ShaderVariantAssetBuilder for details on the naming convention).
|
||||
// [GFX TODO][ATOM-14475]: Come up with a more elegant way to associate the isBound flag with the input stream.
|
||||
float4 m_optional_blendMask : COLOR0;
|
||||
};
|
||||
|
||||
struct VSDepthOutput
|
||||
{
|
||||
precise linear centroid float4 m_position : SV_Position;
|
||||
float2 m_uv[UvSetCount] : UV1;
|
||||
|
||||
// only used for parallax depth calculation
|
||||
float3 m_normal : NORMAL;
|
||||
float3 m_tangent : TANGENT;
|
||||
float3 m_bitangent : BITANGENT;
|
||||
float3 m_worldPosition : UV0;
|
||||
float3 m_blendMask : UV3;
|
||||
};
|
||||
|
||||
VSDepthOutput MainVS(VSInput IN)
|
||||
{
|
||||
VSDepthOutput OUT;
|
||||
|
||||
float4x4 objectToWorld = ObjectSrg::GetWorldMatrix();
|
||||
float4 worldPosition = mul(objectToWorld, float4(IN.m_position, 1.0));
|
||||
|
||||
OUT.m_position = mul(ViewSrg::m_viewProjectionMatrix, worldPosition);
|
||||
|
||||
// By design, only UV0 is allowed to apply transforms.
|
||||
// Note there are additional UV transforms that happen for each layer, but we defer that step to the pixel shader to avoid bloating the vertex output buffer.
|
||||
OUT.m_uv[0] = mul(MaterialSrg::m_uvMatrix, float3(IN.m_uv0, 1.0)).xy;
|
||||
OUT.m_uv[1] = IN.m_uv1;
|
||||
|
||||
if(ShouldHandleParallaxInDepthShaders())
|
||||
{
|
||||
OUT.m_worldPosition = worldPosition.xyz;
|
||||
|
||||
float3x3 objectToWorldIT = ObjectSrg::GetWorldMatrixInverseTranspose();
|
||||
ConstructTBN(IN.m_normal, IN.m_tangent, IN.m_bitangent, objectToWorld, objectToWorldIT, OUT.m_normal, OUT.m_tangent, OUT.m_bitangent);
|
||||
}
|
||||
|
||||
if(o_blendMask_isBound)
|
||||
{
|
||||
OUT.m_blendMask = IN.m_optional_blendMask.rgb;
|
||||
}
|
||||
else
|
||||
{
|
||||
OUT.m_blendMask = float3(0,0,0);
|
||||
}
|
||||
|
||||
return OUT;
|
||||
}
|
||||
|
||||
struct PSDepthOutput
|
||||
{
|
||||
precise float m_depth : SV_Depth;
|
||||
};
|
||||
|
||||
PSDepthOutput MainPS(VSDepthOutput IN, bool isFrontFace : SV_IsFrontFace)
|
||||
{
|
||||
PSDepthOutput OUT;
|
||||
|
||||
OUT.m_depth = IN.m_position.z;
|
||||
|
||||
if(ShouldHandleParallaxInDepthShaders())
|
||||
{
|
||||
float3 tangents[UvSetCount] = { IN.m_tangent.xyz, IN.m_tangent.xyz };
|
||||
float3 bitangents[UvSetCount] = { IN.m_bitangent.xyz, IN.m_bitangent.xyz };
|
||||
|
||||
PrepareGeneratedTangent(IN.m_normal, IN.m_worldPosition, isFrontFace, IN.m_uv, UvSetCount, tangents, bitangents);
|
||||
|
||||
s_blendMaskFromVertexStream = IN.m_blendMask;
|
||||
|
||||
float depth;
|
||||
|
||||
float3x3 uvMatrix = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrix : CreateIdentity3x3();
|
||||
float3x3 uvMatrixInverse = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrixInverse : CreateIdentity3x3();
|
||||
|
||||
float parallaxOverallOffset = MaterialSrg::m_displacementMax;
|
||||
float parallaxOverallFactor = MaterialSrg::m_displacementMax - MaterialSrg::m_displacementMin;
|
||||
GetParallaxInput(IN.m_normal, tangents[MaterialSrg::m_parallaxUvIndex], bitangents[MaterialSrg::m_parallaxUvIndex], parallaxOverallFactor, parallaxOverallOffset,
|
||||
ObjectSrg::GetWorldMatrix(), uvMatrix, uvMatrixInverse,
|
||||
IN.m_uv[MaterialSrg::m_parallaxUvIndex], IN.m_worldPosition, depth);
|
||||
|
||||
OUT.m_depth = depth;
|
||||
}
|
||||
|
||||
return OUT;
|
||||
}
|
||||
#define MULTILAYER 1
|
||||
#define ENABLE_ALPHA_CLIP 0
|
||||
#include "DepthPass_WithPS.azsli"
|
||||
|
||||
+2
-2
@@ -451,8 +451,8 @@ PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace, out float
|
||||
// ------- Combine Albedo, roughness, specular, roughness ---------
|
||||
|
||||
float3 baseColor = BlendLayers(lightingInputLayer1.m_baseColor, lightingInputLayer2.m_baseColor, lightingInputLayer3.m_baseColor, blendWeights);
|
||||
float3 specularF0Factor = BlendLayers(lightingInputLayer1.m_specularF0Factor, lightingInputLayer2.m_specularF0Factor, lightingInputLayer3.m_specularF0Factor, blendWeights);
|
||||
float3 metallic = BlendLayers(lightingInputLayer1.m_metallic, lightingInputLayer2.m_metallic, lightingInputLayer3.m_metallic, blendWeights);
|
||||
float specularF0Factor = BlendLayers(lightingInputLayer1.m_specularF0Factor, lightingInputLayer2.m_specularF0Factor, lightingInputLayer3.m_specularF0Factor, blendWeights);
|
||||
float metallic = BlendLayers(lightingInputLayer1.m_metallic, lightingInputLayer2.m_metallic, lightingInputLayer3.m_metallic, blendWeights);
|
||||
|
||||
if(o_parallax_highlightClipping && displacementIsClipped)
|
||||
{
|
||||
|
||||
+10
-100
@@ -5,7 +5,7 @@
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#include <scenesrg.srgi>
|
||||
#include <viewsrg.srgi>
|
||||
#include <Atom/Features/PBR/DefaultObjectSrg.azsli>
|
||||
@@ -21,103 +21,13 @@ COMMON_OPTIONS_PARALLAX(o_layer3_)
|
||||
|
||||
#include "StandardMultilayerPBR_Common.azsli"
|
||||
|
||||
struct VertexInput
|
||||
{
|
||||
float3 m_position : POSITION;
|
||||
float2 m_uv0 : UV0;
|
||||
float2 m_uv1 : UV1;
|
||||
#include "MaterialFunctions/StandardGetObjectToWorld.azsli"
|
||||
#include "MaterialFunctions/StandardGetNormalToWorld.azsli"
|
||||
#include "MaterialFunctions/StandardTransformUvs.azsli"
|
||||
#include "MaterialFunctions/EvaluateTangentFrame.azsli"
|
||||
#include "MaterialFunctions/MultilayerParallaxDepth.azsli"
|
||||
|
||||
// only used for parallax depth calculation
|
||||
float3 m_normal : NORMAL;
|
||||
float4 m_tangent : TANGENT;
|
||||
float3 m_bitangent : BITANGENT;
|
||||
|
||||
// This gets set automatically by the system at runtime only if it's available.
|
||||
// There is a soft naming convention that associates this with o_blendMask_isBound, which will be set to true whenever m_optional_blendMask is available.
|
||||
// (search "m_optional_" in ShaderVariantAssetBuilder for details on the naming convention).
|
||||
// [GFX TODO][ATOM-14475]: Come up with a more elegant way to associate the isBound flag with the input stream.
|
||||
float4 m_optional_blendMask : COLOR0;
|
||||
};
|
||||
|
||||
struct VertexOutput
|
||||
{
|
||||
float4 m_position : SV_Position;
|
||||
float2 m_uv[UvSetCount] : UV1;
|
||||
|
||||
// only used for parallax depth calculation
|
||||
float3 m_normal : NORMAL;
|
||||
float3 m_tangent : TANGENT;
|
||||
float3 m_bitangent : BITANGENT;
|
||||
float3 m_worldPosition : UV0;
|
||||
float3 m_blendMask : UV3;
|
||||
};
|
||||
|
||||
VertexOutput MainVS(VertexInput IN)
|
||||
{
|
||||
const float4x4 objectToWorld = ObjectSrg::GetWorldMatrix();
|
||||
VertexOutput OUT;
|
||||
|
||||
const float3 worldPosition = mul(objectToWorld, float4(IN.m_position, 1.0)).xyz;
|
||||
OUT.m_position = mul(ViewSrg::m_viewProjectionMatrix, float4(worldPosition, 1.0));
|
||||
|
||||
// By design, only UV0 is allowed to apply transforms.
|
||||
// Note there are additional UV transforms that happen for each layer, but we defer that step to the pixel shader to avoid bloating the vertex output buffer.
|
||||
OUT.m_uv[0] = mul(MaterialSrg::m_uvMatrix, float3(IN.m_uv0, 1.0)).xy;
|
||||
OUT.m_uv[1] = IN.m_uv1;
|
||||
|
||||
if(ShouldHandleParallaxInDepthShaders())
|
||||
{
|
||||
OUT.m_worldPosition = worldPosition.xyz;
|
||||
|
||||
float3x3 objectToWorldIT = ObjectSrg::GetWorldMatrixInverseTranspose();
|
||||
ConstructTBN(IN.m_normal, IN.m_tangent, IN.m_bitangent, objectToWorld, objectToWorldIT, OUT.m_normal, OUT.m_tangent, OUT.m_bitangent);
|
||||
}
|
||||
|
||||
if(o_blendMask_isBound)
|
||||
{
|
||||
OUT.m_blendMask = IN.m_optional_blendMask.rgb;
|
||||
}
|
||||
else
|
||||
{
|
||||
OUT.m_blendMask = float3(0,0,0);
|
||||
}
|
||||
|
||||
return OUT;
|
||||
}
|
||||
|
||||
struct PSDepthOutput
|
||||
{
|
||||
float m_depth : SV_Depth;
|
||||
};
|
||||
|
||||
PSDepthOutput MainPS(VertexOutput IN, bool isFrontFace : SV_IsFrontFace)
|
||||
{
|
||||
PSDepthOutput OUT;
|
||||
|
||||
OUT.m_depth = IN.m_position.z;
|
||||
|
||||
if(ShouldHandleParallaxInDepthShaders())
|
||||
{
|
||||
float3 tangents[UvSetCount] = { IN.m_tangent.xyz, IN.m_tangent.xyz };
|
||||
float3 bitangents[UvSetCount] = { IN.m_bitangent.xyz, IN.m_bitangent.xyz };
|
||||
|
||||
PrepareGeneratedTangent(IN.m_normal, IN.m_worldPosition, isFrontFace, IN.m_uv, UvSetCount, tangents, bitangents);
|
||||
|
||||
s_blendMaskFromVertexStream = IN.m_blendMask;
|
||||
|
||||
float depthNDC;
|
||||
|
||||
float3x3 uvMatrix = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrix : CreateIdentity3x3();
|
||||
float3x3 uvMatrixInverse = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrixInverse : CreateIdentity3x3();
|
||||
|
||||
float parallaxOverallOffset = MaterialSrg::m_displacementMax;
|
||||
float parallaxOverallFactor = MaterialSrg::m_displacementMax - MaterialSrg::m_displacementMin;
|
||||
GetParallaxInput(IN.m_normal, tangents[MaterialSrg::m_parallaxUvIndex], bitangents[MaterialSrg::m_parallaxUvIndex], parallaxOverallFactor, parallaxOverallOffset,
|
||||
ObjectSrg::GetWorldMatrix(), uvMatrix, uvMatrixInverse,
|
||||
IN.m_uv[MaterialSrg::m_parallaxUvIndex], IN.m_worldPosition, depthNDC);
|
||||
|
||||
OUT.m_depth = depthNDC;
|
||||
}
|
||||
|
||||
return OUT;
|
||||
}
|
||||
#define MULTILAYER 1
|
||||
#define ENABLE_ALPHA_CLIP 0
|
||||
#define SHADOWS 1
|
||||
#include "DepthPass_WithPS.azsli"
|
||||
|
||||
@@ -8,91 +8,13 @@
|
||||
|
||||
#include "./StandardPBR_Common.azsli"
|
||||
#include <Atom/Features/PBR/DefaultObjectSrg.azsli>
|
||||
#include <Atom/Features/ParallaxMapping.azsli>
|
||||
#include <Atom/Features/MatrixUtility.azsli>
|
||||
|
||||
#include "MaterialInputs/AlphaInput.azsli"
|
||||
#include "MaterialInputs/ParallaxInput.azsli"
|
||||
#include "MaterialFunctions/StandardGetObjectToWorld.azsli"
|
||||
#include "MaterialFunctions/StandardGetNormalToWorld.azsli"
|
||||
#include "MaterialFunctions/StandardGetAlphaAndClip.azsli"
|
||||
#include "MaterialFunctions/EvaluateTangentFrame.azsli"
|
||||
#include "MaterialFunctions/StandardTransformUvs.azsli"
|
||||
#include "MaterialFunctions/ParallaxDepth.azsli"
|
||||
|
||||
struct VSInput
|
||||
{
|
||||
float3 m_position : POSITION;
|
||||
float2 m_uv0 : UV0;
|
||||
float2 m_uv1 : UV1;
|
||||
|
||||
// only used for parallax depth calculation
|
||||
float3 m_normal : NORMAL;
|
||||
float4 m_tangent : TANGENT;
|
||||
float3 m_bitangent : BITANGENT;
|
||||
};
|
||||
|
||||
struct VSDepthOutput
|
||||
{
|
||||
// "centroid" is needed for SV_Depth to compile
|
||||
precise linear centroid float4 m_position : SV_Position;
|
||||
float2 m_uv[UvSetCount] : UV1;
|
||||
|
||||
// only used for parallax depth calculation
|
||||
float3 m_normal : NORMAL;
|
||||
float3 m_tangent : TANGENT;
|
||||
float3 m_bitangent : BITANGENT;
|
||||
float3 m_worldPosition : UV0;
|
||||
};
|
||||
|
||||
VSDepthOutput MainVS(VSInput IN)
|
||||
{
|
||||
VSDepthOutput OUT;
|
||||
|
||||
float4x4 objectToWorld = ObjectSrg::GetWorldMatrix();
|
||||
float4 worldPosition = mul(objectToWorld, float4(IN.m_position, 1.0));
|
||||
|
||||
OUT.m_position = mul(ViewSrg::m_viewProjectionMatrix, worldPosition);
|
||||
// By design, only UV0 is allowed to apply transforms.
|
||||
OUT.m_uv[0] = mul(MaterialSrg::m_uvMatrix, float3(IN.m_uv0, 1.0)).xy;
|
||||
OUT.m_uv[1] = IN.m_uv1;
|
||||
|
||||
if(ShouldHandleParallaxInDepthShaders())
|
||||
{
|
||||
OUT.m_worldPosition = worldPosition.xyz;
|
||||
|
||||
float3x3 objectToWorldIT = ObjectSrg::GetWorldMatrixInverseTranspose();
|
||||
ConstructTBN(IN.m_normal, IN.m_tangent, IN.m_bitangent, objectToWorld, objectToWorldIT, OUT.m_normal, OUT.m_tangent, OUT.m_bitangent);
|
||||
}
|
||||
return OUT;
|
||||
}
|
||||
|
||||
struct PSDepthOutput
|
||||
{
|
||||
precise float m_depth : SV_Depth;
|
||||
};
|
||||
|
||||
PSDepthOutput MainPS(VSDepthOutput IN, bool isFrontFace : SV_IsFrontFace)
|
||||
{
|
||||
PSDepthOutput OUT;
|
||||
|
||||
OUT.m_depth = IN.m_position.z;
|
||||
|
||||
if(ShouldHandleParallaxInDepthShaders())
|
||||
{
|
||||
float3 tangents[UvSetCount] = { IN.m_tangent.xyz, IN.m_tangent.xyz };
|
||||
float3 bitangents[UvSetCount] = { IN.m_bitangent.xyz, IN.m_bitangent.xyz };
|
||||
|
||||
PrepareGeneratedTangent(IN.m_normal, IN.m_worldPosition, isFrontFace, IN.m_uv, UvSetCount, tangents, bitangents);
|
||||
|
||||
float3x3 uvMatrix = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrix : CreateIdentity3x3();
|
||||
float3x3 uvMatrixInverse = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrixInverse : CreateIdentity3x3();
|
||||
|
||||
GetParallaxInput(IN.m_normal, tangents[MaterialSrg::m_parallaxUvIndex], bitangents[MaterialSrg::m_parallaxUvIndex], MaterialSrg::m_heightmapScale, MaterialSrg::m_heightmapOffset,
|
||||
ObjectSrg::GetWorldMatrix(), uvMatrix, uvMatrixInverse,
|
||||
IN.m_uv[MaterialSrg::m_parallaxUvIndex], IN.m_worldPosition, OUT.m_depth);
|
||||
}
|
||||
|
||||
// Alpha
|
||||
float2 baseColorUV = IN.m_uv[MaterialSrg::m_baseColorMapUvIndex];
|
||||
float2 opacityUV = IN.m_uv[MaterialSrg::m_opacityMapUvIndex];
|
||||
float alpha = SampleAlpha(MaterialSrg::m_baseColorMap, MaterialSrg::m_opacityMap, baseColorUV, opacityUV, MaterialSrg::m_sampler, o_opacity_source);
|
||||
|
||||
CheckClipping(alpha, MaterialSrg::m_opacityFactor);
|
||||
|
||||
return OUT;
|
||||
}
|
||||
#define ENABLE_ALPHA_CLIP 1
|
||||
#include "DepthPass_WithPS.azsli"
|
||||
|
||||
@@ -10,23 +10,7 @@
|
||||
|
||||
#include "StandardPBR_Common.azsli"
|
||||
|
||||
// SRGs
|
||||
#include <Atom/Features/PBR/DefaultObjectSrg.azsli>
|
||||
#include <Atom/Features/PBR/ForwardPassSrg.azsli>
|
||||
|
||||
// Pass Output
|
||||
#include <Atom/Features/PBR/ForwardPassOutput.azsli>
|
||||
|
||||
// Utility
|
||||
#include <Atom/Features/ColorManagement/TransformColor.azsli>
|
||||
#include <Atom/Features/PBR/AlphaUtils.azsli>
|
||||
|
||||
// Custom Surface & Lighting
|
||||
#include <Atom/Features/PBR/Lighting/StandardLighting.azsli>
|
||||
|
||||
// Decals
|
||||
#include <Atom/Features/PBR/Decals.azsli>
|
||||
|
||||
|
||||
// ---------- Material Parameters ----------
|
||||
|
||||
@@ -43,320 +27,12 @@ COMMON_OPTIONS_EMISSIVE()
|
||||
// Alpha
|
||||
#include "MaterialInputs/AlphaInput.azsli"
|
||||
|
||||
// ---------- Vertex Shader ----------
|
||||
|
||||
struct VSInput
|
||||
{
|
||||
// Base fields (required by the template azsli file)...
|
||||
float3 m_position : POSITION;
|
||||
float3 m_normal : NORMAL;
|
||||
float4 m_tangent : TANGENT;
|
||||
float3 m_bitangent : BITANGENT;
|
||||
|
||||
// Extended fields (only referenced in this azsl file)...
|
||||
float2 m_uv0 : UV0;
|
||||
float2 m_uv1 : UV1;
|
||||
};
|
||||
|
||||
struct VSOutput
|
||||
{
|
||||
// Base fields (required by the template azsli file)...
|
||||
// "centroid" is needed for SV_Depth to compile
|
||||
precise linear centroid float4 m_position : SV_Position;
|
||||
float3 m_normal: NORMAL;
|
||||
float3 m_tangent : TANGENT;
|
||||
float3 m_bitangent : BITANGENT;
|
||||
float3 m_worldPosition : UV0;
|
||||
float3 m_shadowCoords[ViewSrg::MaxCascadeCount] : UV3;
|
||||
|
||||
// Extended fields (only referenced in this azsl file)...
|
||||
float2 m_uv[UvSetCount] : UV1;
|
||||
};
|
||||
|
||||
#include <Atom/Features/Vertex/VertexHelper.azsli>
|
||||
|
||||
VSOutput StandardPbr_ForwardPassVS(VSInput IN)
|
||||
{
|
||||
VSOutput OUT;
|
||||
|
||||
float3 worldPosition = mul(ObjectSrg::GetWorldMatrix(), float4(IN.m_position, 1.0)).xyz;
|
||||
|
||||
// By design, only UV0 is allowed to apply transforms.
|
||||
OUT.m_uv[0] = mul(MaterialSrg::m_uvMatrix, float3(IN.m_uv0, 1.0)).xy;
|
||||
OUT.m_uv[1] = IN.m_uv1;
|
||||
|
||||
// Shadow coords will be calculated in the pixel shader in this case
|
||||
bool skipShadowCoords = ShouldHandleParallax() && o_parallax_enablePixelDepthOffset;
|
||||
|
||||
VertexHelper(IN, OUT, worldPosition, skipShadowCoords);
|
||||
|
||||
return OUT;
|
||||
}
|
||||
|
||||
|
||||
// ---------- Pixel Shader ----------
|
||||
|
||||
PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace, out float depthNDC)
|
||||
{
|
||||
const float3 vertexNormal = normalize(IN.m_normal);
|
||||
|
||||
// ------- Tangents & Bitangets -------
|
||||
float3 tangents[UvSetCount] = { IN.m_tangent.xyz, IN.m_tangent.xyz };
|
||||
float3 bitangents[UvSetCount] = { IN.m_bitangent.xyz, IN.m_bitangent.xyz };
|
||||
|
||||
if (ShouldHandleParallax() || o_normal_useTexture || (o_clearCoat_enabled && o_clearCoat_normal_useTexture))
|
||||
{
|
||||
PrepareGeneratedTangent(IN.m_normal, IN.m_worldPosition, isFrontFace, IN.m_uv, UvSetCount, tangents, bitangents);
|
||||
}
|
||||
|
||||
// ------- Depth & Parallax -------
|
||||
|
||||
depthNDC = IN.m_position.z;
|
||||
|
||||
bool displacementIsClipped = false;
|
||||
|
||||
if(ShouldHandleParallax())
|
||||
{
|
||||
|
||||
float3x3 uvMatrix = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrix : CreateIdentity3x3();
|
||||
float3x3 uvMatrixInverse = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrixInverse : CreateIdentity3x3();
|
||||
GetParallaxInput(IN.m_normal, tangents[MaterialSrg::m_parallaxUvIndex], bitangents[MaterialSrg::m_parallaxUvIndex], MaterialSrg::m_heightmapScale, MaterialSrg::m_heightmapOffset,
|
||||
ObjectSrg::GetWorldMatrix(), uvMatrix, uvMatrixInverse,
|
||||
IN.m_uv[MaterialSrg::m_parallaxUvIndex], IN.m_worldPosition, depthNDC, IN.m_position.w, displacementIsClipped);
|
||||
|
||||
// Adjust directional light shadow coordinates for parallax correction
|
||||
if(o_parallax_enablePixelDepthOffset)
|
||||
{
|
||||
const uint shadowIndex = ViewSrg::m_shadowIndexDirectionalLight;
|
||||
if (o_enableShadows && shadowIndex < SceneSrg::m_directionalLightCount)
|
||||
{
|
||||
DirectionalLightShadow::GetShadowCoords(shadowIndex, IN.m_worldPosition, vertexNormal, IN.m_shadowCoords);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Surface surface;
|
||||
surface.position = IN.m_worldPosition.xyz;
|
||||
|
||||
// ------- Alpha & Clip -------
|
||||
|
||||
float2 baseColorUv = IN.m_uv[MaterialSrg::m_baseColorMapUvIndex];
|
||||
float2 opacityUv = IN.m_uv[MaterialSrg::m_opacityMapUvIndex];
|
||||
float alpha = GetAlphaInputAndClip(MaterialSrg::m_baseColorMap, MaterialSrg::m_opacityMap, baseColorUv, opacityUv, MaterialSrg::m_sampler, MaterialSrg::m_opacityFactor, o_opacity_source);
|
||||
|
||||
// ------- Normal -------
|
||||
|
||||
float2 normalUv = IN.m_uv[MaterialSrg::m_normalMapUvIndex];
|
||||
float3x3 uvMatrix = MaterialSrg::m_normalMapUvIndex == 0 ? MaterialSrg::m_uvMatrix : CreateIdentity3x3(); // By design, only UV0 is allowed to apply transforms.
|
||||
surface.vertexNormal = vertexNormal;
|
||||
surface.normal = GetNormalInputWS(MaterialSrg::m_normalMap, MaterialSrg::m_sampler, normalUv, MaterialSrg::m_flipNormalX, MaterialSrg::m_flipNormalY, isFrontFace, IN.m_normal,
|
||||
tangents[MaterialSrg::m_normalMapUvIndex], bitangents[MaterialSrg::m_normalMapUvIndex], uvMatrix, o_normal_useTexture, MaterialSrg::m_normalFactor);
|
||||
|
||||
// ------- Base Color -------
|
||||
|
||||
float3 sampledColor = GetBaseColorInput(MaterialSrg::m_baseColorMap, MaterialSrg::m_sampler, baseColorUv, MaterialSrg::m_baseColor.rgb, o_baseColor_useTexture);
|
||||
float3 baseColor = BlendBaseColor(sampledColor, MaterialSrg::m_baseColor.rgb, MaterialSrg::m_baseColorFactor, o_baseColorTextureBlendMode, o_baseColor_useTexture);
|
||||
|
||||
if(o_parallax_highlightClipping && displacementIsClipped)
|
||||
{
|
||||
ApplyParallaxClippingHighlight(baseColor);
|
||||
}
|
||||
|
||||
// ------- Metallic -------
|
||||
|
||||
float2 metallicUv = IN.m_uv[MaterialSrg::m_metallicMapUvIndex];
|
||||
float metallic = GetMetallicInput(MaterialSrg::m_metallicMap, MaterialSrg::m_sampler, metallicUv, MaterialSrg::m_metallicFactor, o_metallic_useTexture);
|
||||
|
||||
// ------- Specular -------
|
||||
|
||||
float2 specularUv = IN.m_uv[MaterialSrg::m_specularF0MapUvIndex];
|
||||
float specularF0Factor = GetSpecularInput(MaterialSrg::m_specularF0Map, MaterialSrg::m_sampler, specularUv, MaterialSrg::m_specularF0Factor, o_specularF0_useTexture);
|
||||
|
||||
surface.SetAlbedoAndSpecularF0(baseColor, specularF0Factor, metallic);
|
||||
|
||||
// ------- Roughness -------
|
||||
|
||||
float2 roughnessUv = IN.m_uv[MaterialSrg::m_roughnessMapUvIndex];
|
||||
surface.roughnessLinear = GetRoughnessInput(MaterialSrg::m_roughnessMap, MaterialSrg::m_sampler, roughnessUv, MaterialSrg::m_roughnessFactor,
|
||||
MaterialSrg::m_roughnessLowerBound, MaterialSrg::m_roughnessUpperBound, o_roughness_useTexture);
|
||||
surface.CalculateRoughnessA();
|
||||
|
||||
// ------- Lighting Data -------
|
||||
|
||||
LightingData lightingData;
|
||||
|
||||
// Light iterator
|
||||
lightingData.tileIterator.Init(IN.m_position, PassSrg::m_lightListRemapped, PassSrg::m_tileLightData);
|
||||
lightingData.Init(surface.position, surface.normal, surface.roughnessLinear);
|
||||
|
||||
// Directional light shadow coordinates
|
||||
lightingData.shadowCoords = IN.m_shadowCoords;
|
||||
|
||||
// ------- Emissive -------
|
||||
|
||||
float2 emissiveUv = IN.m_uv[MaterialSrg::m_emissiveMapUvIndex];
|
||||
lightingData.emissiveLighting = GetEmissiveInput(MaterialSrg::m_emissiveMap, MaterialSrg::m_sampler, emissiveUv, MaterialSrg::m_emissiveIntensity, MaterialSrg::m_emissiveColor.rgb, o_emissiveEnabled, o_emissive_useTexture);
|
||||
|
||||
// ------- Occlusion -------
|
||||
|
||||
lightingData.diffuseAmbientOcclusion = GetOcclusionInput(MaterialSrg::m_diffuseOcclusionMap, MaterialSrg::m_sampler, IN.m_uv[MaterialSrg::m_diffuseOcclusionMapUvIndex], MaterialSrg::m_diffuseOcclusionFactor, o_diffuseOcclusion_useTexture);
|
||||
lightingData.specularOcclusion = GetOcclusionInput(MaterialSrg::m_specularOcclusionMap, MaterialSrg::m_sampler, IN.m_uv[MaterialSrg::m_specularOcclusionMapUvIndex], MaterialSrg::m_specularOcclusionFactor, o_specularOcclusion_useTexture);
|
||||
|
||||
// ------- Clearcoat -------
|
||||
|
||||
// [GFX TODO][ATOM-14603]: Clean up the double uses of these clear coat flags
|
||||
if(o_clearCoat_feature_enabled)
|
||||
{
|
||||
if(o_clearCoat_enabled)
|
||||
{
|
||||
float3x3 uvMatrix = MaterialSrg::m_clearCoatNormalMapUvIndex == 0 ? MaterialSrg::m_uvMatrix : CreateIdentity3x3();
|
||||
GetClearCoatInputs(MaterialSrg::m_clearCoatInfluenceMap, IN.m_uv[MaterialSrg::m_clearCoatInfluenceMapUvIndex], MaterialSrg::m_clearCoatFactor, o_clearCoat_factor_useTexture,
|
||||
MaterialSrg::m_clearCoatRoughnessMap, IN.m_uv[MaterialSrg::m_clearCoatRoughnessMapUvIndex], MaterialSrg::m_clearCoatRoughness, o_clearCoat_roughness_useTexture,
|
||||
MaterialSrg::m_clearCoatNormalMap, IN.m_uv[MaterialSrg::m_clearCoatNormalMapUvIndex], IN.m_normal, o_clearCoat_normal_useTexture, MaterialSrg::m_clearCoatNormalStrength,
|
||||
uvMatrix, tangents[MaterialSrg::m_clearCoatNormalMapUvIndex], bitangents[MaterialSrg::m_clearCoatNormalMapUvIndex],
|
||||
MaterialSrg::m_sampler, isFrontFace,
|
||||
surface.clearCoat.factor, surface.clearCoat.roughness, surface.clearCoat.normal);
|
||||
}
|
||||
|
||||
// manipulate base layer f0 if clear coat is enabled
|
||||
// modify base layer's normal incidence reflectance
|
||||
// for the derivation of the following equation please refer to:
|
||||
// https://google.github.io/filament/Filament.md.html#materialsystem/clearcoatmodel/baselayermodification
|
||||
float3 f0 = (1.0 - 5.0 * sqrt(surface.specularF0)) / (5.0 - sqrt(surface.specularF0));
|
||||
surface.specularF0 = lerp(surface.specularF0, f0 * f0, surface.clearCoat.factor);
|
||||
}
|
||||
|
||||
// Diffuse and Specular response (used in IBL calculations)
|
||||
lightingData.specularResponse = FresnelSchlickWithRoughness(lightingData.NdotV, surface.specularF0, surface.roughnessLinear);
|
||||
lightingData.diffuseResponse = 1.0 - lightingData.specularResponse;
|
||||
|
||||
if(o_clearCoat_feature_enabled)
|
||||
{
|
||||
// Clear coat layer has fixed IOR = 1.5 and transparent => F0 = (1.5 - 1)^2 / (1.5 + 1)^2 = 0.04
|
||||
lightingData.diffuseResponse *= 1.0 - (FresnelSchlickWithRoughness(lightingData.NdotV, float3(0.04, 0.04, 0.04), surface.clearCoat.roughness) * surface.clearCoat.factor);
|
||||
}
|
||||
|
||||
// ------- Multiscatter -------
|
||||
|
||||
lightingData.CalculateMultiscatterCompensation(surface.specularF0, o_specularF0_enableMultiScatterCompensation);
|
||||
|
||||
// ------- Lighting Calculation -------
|
||||
|
||||
// Apply Decals
|
||||
ApplyDecals(lightingData.tileIterator, surface);
|
||||
|
||||
// Apply Direct Lighting
|
||||
ApplyDirectLighting(surface, lightingData);
|
||||
|
||||
// Apply Image Based Lighting (IBL)
|
||||
ApplyIBL(surface, lightingData);
|
||||
|
||||
// Finalize Lighting
|
||||
lightingData.FinalizeLighting();
|
||||
|
||||
PbrLightingOutput lightingOutput = GetPbrLightingOutput(surface, lightingData, alpha);
|
||||
|
||||
// ------- Opacity -------
|
||||
|
||||
if (o_opacity_mode == OpacityMode::Blended || o_opacity_mode == OpacityMode::TintedTransparent)
|
||||
{
|
||||
// Increase opacity at grazing angles for surfaces with a low m_opacityAffectsSpecularFactor.
|
||||
// For m_opacityAffectsSpecularFactor values close to 0, that indicates a transparent surface
|
||||
// like glass, so it becomes less transparent at grazing angles. For m_opacityAffectsSpecularFactor
|
||||
// values close to 1.0, that indicates the absence of a surface entirely, so this effect should
|
||||
// not apply.
|
||||
float fresnelAlpha = FresnelSchlickWithRoughness(lightingData.NdotV, alpha, surface.roughnessLinear).x;
|
||||
alpha = lerp(fresnelAlpha, alpha, MaterialSrg::m_opacityAffectsSpecularFactor);
|
||||
}
|
||||
|
||||
if (o_opacity_mode == OpacityMode::Blended)
|
||||
{
|
||||
// [GFX_TODO ATOM-13187] PbrLighting shouldn't be writing directly to render targets. It's confusing when
|
||||
// specular is being added to diffuse just because we're calling render target 0 "diffuse".
|
||||
|
||||
// For blended mode, we do (dest * alpha) + (source * 1.0). This allows the specular
|
||||
// to be added on top of the diffuse, but then the diffuse must be pre-multiplied.
|
||||
// It's done this way because surface transparency doesn't really change specular response (eg, glass).
|
||||
|
||||
lightingOutput.m_diffuseColor.rgb *= lightingOutput.m_diffuseColor.w; // pre-multiply diffuse
|
||||
|
||||
// Add specular. m_opacityAffectsSpecularFactor controls how much the alpha masks out specular contribution.
|
||||
float3 specular = lightingOutput.m_specularColor.rgb;
|
||||
specular = lerp(specular, specular * lightingOutput.m_diffuseColor.w, MaterialSrg::m_opacityAffectsSpecularFactor);
|
||||
lightingOutput.m_diffuseColor.rgb += specular;
|
||||
|
||||
lightingOutput.m_diffuseColor.w = alpha;
|
||||
}
|
||||
else if (o_opacity_mode == OpacityMode::TintedTransparent)
|
||||
{
|
||||
// See OpacityMode::Blended above for the basic method. TintedTransparent adds onto the above concept by supporting
|
||||
// colored alpha. This is currently a very basic calculation that uses the baseColor as a multiplier with strength
|
||||
// determined by the alpha. We'll modify this later to be more physically accurate and allow surface depth,
|
||||
// absorption, and interior color to be specified.
|
||||
//
|
||||
// The technique uses dual source blending to allow two separate sources to be part of the blending equation
|
||||
// even though ultimately only a single render target is being written to. m_diffuseColor is render target 0 and
|
||||
// m_specularColor render target 1, and the blend mode is (dest * source1color) + (source * 1.0).
|
||||
//
|
||||
// This means that m_specularColor.rgb (source 1) is multiplied against the destination, then
|
||||
// m_diffuseColor.rgb (source) is added to that, and the final result is stored in render target 0.
|
||||
|
||||
lightingOutput.m_diffuseColor.rgb *= lightingOutput.m_diffuseColor.w; // pre-multiply diffuse
|
||||
|
||||
// Add specular. m_opacityAffectsSpecularFactor controls how much the alpha masks out specular contribution.
|
||||
float3 specular = lightingOutput.m_specularColor.rgb;
|
||||
specular = lerp(specular, specular * lightingOutput.m_diffuseColor.w, MaterialSrg::m_opacityAffectsSpecularFactor);
|
||||
lightingOutput.m_diffuseColor.rgb += specular;
|
||||
|
||||
lightingOutput.m_specularColor.rgb = baseColor * (1.0 - alpha);
|
||||
}
|
||||
else
|
||||
{
|
||||
lightingOutput.m_diffuseColor.w = -1; // Disable subsurface scattering
|
||||
}
|
||||
|
||||
return lightingOutput;
|
||||
}
|
||||
|
||||
ForwardPassOutputWithDepth StandardPbr_ForwardPassPS(VSOutput IN, bool isFrontFace : SV_IsFrontFace)
|
||||
{
|
||||
ForwardPassOutputWithDepth OUT;
|
||||
float depth;
|
||||
|
||||
PbrLightingOutput lightingOutput = ForwardPassPS_Common(IN, isFrontFace, depth);
|
||||
|
||||
#ifdef UNIFIED_FORWARD_OUTPUT
|
||||
OUT.m_color.rgb = lightingOutput.m_diffuseColor.rgb + lightingOutput.m_specularColor.rgb;
|
||||
OUT.m_color.a = lightingOutput.m_diffuseColor.a;
|
||||
OUT.m_depth = depth;
|
||||
#else
|
||||
OUT.m_diffuseColor = lightingOutput.m_diffuseColor;
|
||||
OUT.m_specularColor = lightingOutput.m_specularColor;
|
||||
OUT.m_specularF0 = lightingOutput.m_specularF0;
|
||||
OUT.m_albedo = lightingOutput.m_albedo;
|
||||
OUT.m_normal = lightingOutput.m_normal;
|
||||
OUT.m_depth = depth;
|
||||
#endif
|
||||
return OUT;
|
||||
}
|
||||
|
||||
[earlydepthstencil]
|
||||
ForwardPassOutput StandardPbr_ForwardPassPS_EDS(VSOutput IN, bool isFrontFace : SV_IsFrontFace)
|
||||
{
|
||||
ForwardPassOutput OUT;
|
||||
float depth;
|
||||
|
||||
PbrLightingOutput lightingOutput = ForwardPassPS_Common(IN, isFrontFace, depth);
|
||||
|
||||
#ifdef UNIFIED_FORWARD_OUTPUT
|
||||
OUT.m_color.rgb = lightingOutput.m_diffuseColor.rgb + lightingOutput.m_specularColor.rgb;
|
||||
OUT.m_color.a = lightingOutput.m_diffuseColor.a;
|
||||
#else
|
||||
OUT.m_diffuseColor = lightingOutput.m_diffuseColor;
|
||||
OUT.m_specularColor = lightingOutput.m_specularColor;
|
||||
OUT.m_specularF0 = lightingOutput.m_specularF0;
|
||||
OUT.m_albedo = lightingOutput.m_albedo;
|
||||
OUT.m_normal = lightingOutput.m_normal;
|
||||
#endif
|
||||
return OUT;
|
||||
}
|
||||
#include "MaterialFunctions/EvaluateStandardSurface.azsli"
|
||||
#include "MaterialFunctions/EvaluateTangentFrame.azsli"
|
||||
#include "MaterialFunctions/ParallaxDepth.azsli"
|
||||
#include "MaterialFunctions/StandardGetNormalToWorld.azsli"
|
||||
#include "MaterialFunctions/StandardGetObjectToWorld.azsli"
|
||||
#include "MaterialFunctions/StandardGetAlphaAndClip.azsli"
|
||||
#include "MaterialFunctions/StandardTransformUvs.azsli"
|
||||
|
||||
#include "StandardSurface_ForwardPass.azsli"
|
||||
|
||||
@@ -5,7 +5,9 @@
|
||||
// DrawListTag. If your pipeline doesn't have a "lowEndForward" DrawListTag, no draw items
|
||||
// for this shader will be created.
|
||||
|
||||
"Source" : "./StandardPBR_LowEndForward.azsl",
|
||||
"Source" : "./StandardPBR_ForwardPass.azsl",
|
||||
|
||||
"Definitions": [ "QUALITY_LOW_END=1" ],
|
||||
|
||||
"DepthStencilState" :
|
||||
{
|
||||
|
||||
+3
-1
@@ -5,7 +5,9 @@
|
||||
// DrawListTag. If your pipeline doesn't have a "lowEndForward" DrawListTag, no draw items
|
||||
// for this shader will be created.
|
||||
|
||||
"Source" : "./StandardPBR_LowEndForward.azsl",
|
||||
"Source" : "./StandardPBR_ForwardPass.azsl",
|
||||
|
||||
"Definitions": [ "QUALITY_LOW_END=1" ],
|
||||
|
||||
"DepthStencilState" :
|
||||
{
|
||||
|
||||
@@ -9,93 +9,17 @@
|
||||
#include <scenesrg.srgi>
|
||||
#include "StandardPBR_Common.azsli"
|
||||
#include <Atom/Features/PBR/DefaultObjectSrg.azsli>
|
||||
#include <Atom/Features/ParallaxMapping.azsli>
|
||||
#include <Atom/Features/MatrixUtility.azsli>
|
||||
#include <Atom/Features/Shadow/Shadow.azsli>
|
||||
|
||||
#include "MaterialInputs/AlphaInput.azsli"
|
||||
#include "MaterialInputs/ParallaxInput.azsli"
|
||||
|
||||
struct VertexInput
|
||||
{
|
||||
float3 m_position : POSITION;
|
||||
float2 m_uv0 : UV0;
|
||||
float2 m_uv1 : UV1;
|
||||
#include "MaterialFunctions/StandardGetObjectToWorld.azsli"
|
||||
#include "MaterialFunctions/StandardGetNormalToWorld.azsli"
|
||||
#include "MaterialFunctions/StandardGetAlphaAndClip.azsli"
|
||||
#include "MaterialFunctions/StandardTransformUvs.azsli"
|
||||
#include "MaterialFunctions/EvaluateTangentFrame.azsli"
|
||||
#include "MaterialFunctions/ParallaxDepth.azsli"
|
||||
|
||||
// only used for parallax depth calculation
|
||||
float3 m_normal : NORMAL;
|
||||
float4 m_tangent : TANGENT;
|
||||
float3 m_bitangent : BITANGENT;
|
||||
};
|
||||
|
||||
struct VertexOutput
|
||||
{
|
||||
// "centroid" is needed for SV_Depth to compile
|
||||
linear centroid float4 m_position : SV_Position;
|
||||
float2 m_uv[UvSetCount] : UV1;
|
||||
|
||||
// only used for parallax depth calculation
|
||||
float3 m_normal : NORMAL;
|
||||
float3 m_tangent : TANGENT;
|
||||
float3 m_bitangent : BITANGENT;
|
||||
float3 m_worldPosition : UV0;
|
||||
};
|
||||
|
||||
VertexOutput MainVS(VertexInput IN)
|
||||
{
|
||||
const float4x4 objectToWorld = ObjectSrg::GetWorldMatrix();
|
||||
VertexOutput OUT;
|
||||
|
||||
const float3 worldPosition = mul(objectToWorld, float4(IN.m_position, 1.0)).xyz;
|
||||
OUT.m_position = mul(ViewSrg::m_viewProjectionMatrix, float4(worldPosition, 1.0));
|
||||
// By design, only UV0 is allowed to apply transforms.
|
||||
OUT.m_uv[0] = mul(MaterialSrg::m_uvMatrix, float3(IN.m_uv0, 1.0)).xy;
|
||||
OUT.m_uv[1] = IN.m_uv1;
|
||||
|
||||
if(ShouldHandleParallaxInDepthShaders())
|
||||
{
|
||||
OUT.m_worldPosition = worldPosition.xyz;
|
||||
|
||||
float3x3 objectToWorldIT = ObjectSrg::GetWorldMatrixInverseTranspose();
|
||||
ConstructTBN(IN.m_normal, IN.m_tangent, IN.m_bitangent, objectToWorld, objectToWorldIT, OUT.m_normal, OUT.m_tangent, OUT.m_bitangent);
|
||||
}
|
||||
|
||||
return OUT;
|
||||
}
|
||||
|
||||
struct PSDepthOutput
|
||||
{
|
||||
float m_depth : SV_Depth;
|
||||
};
|
||||
|
||||
PSDepthOutput MainPS(VertexOutput IN, bool isFrontFace : SV_IsFrontFace)
|
||||
{
|
||||
PSDepthOutput OUT;
|
||||
|
||||
OUT.m_depth = IN.m_position.z;
|
||||
|
||||
if(ShouldHandleParallaxInDepthShaders())
|
||||
{
|
||||
float3 tangents[UvSetCount] = { IN.m_tangent.xyz, IN.m_tangent.xyz };
|
||||
float3 bitangents[UvSetCount] = { IN.m_bitangent.xyz, IN.m_bitangent.xyz };
|
||||
PrepareGeneratedTangent(IN.m_normal, IN.m_worldPosition, isFrontFace, IN.m_uv, UvSetCount, tangents, bitangents);
|
||||
|
||||
float3x3 uvMatrix = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrix : CreateIdentity3x3();
|
||||
float3x3 uvMatrixInverse = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrixInverse : CreateIdentity3x3();
|
||||
|
||||
GetParallaxInput(IN.m_normal, tangents[MaterialSrg::m_parallaxUvIndex], bitangents[MaterialSrg::m_parallaxUvIndex], MaterialSrg::m_heightmapScale, MaterialSrg::m_heightmapOffset,
|
||||
ObjectSrg::GetWorldMatrix(), uvMatrix, uvMatrixInverse,
|
||||
IN.m_uv[MaterialSrg::m_parallaxUvIndex], IN.m_worldPosition, OUT.m_depth);
|
||||
|
||||
OUT.m_depth += PdoShadowMapBias;
|
||||
}
|
||||
|
||||
// Alpha
|
||||
float2 baseColorUV = IN.m_uv[MaterialSrg::m_baseColorMapUvIndex];
|
||||
float2 opacityUV = IN.m_uv[MaterialSrg::m_opacityMapUvIndex];
|
||||
float alpha = SampleAlpha(MaterialSrg::m_baseColorMap, MaterialSrg::m_opacityMap, baseColorUV, opacityUV, MaterialSrg::m_sampler, o_opacity_source);
|
||||
|
||||
CheckClipping(alpha, MaterialSrg::m_opacityFactor);
|
||||
|
||||
return OUT;
|
||||
}
|
||||
#define SHADOWS 1
|
||||
#define ENABLE_ALPHA_CLIP 1
|
||||
#include "DepthPass_WithPS.azsli"
|
||||
|
||||
@@ -0,0 +1,306 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#include <Atom/Features/PBR/ForwardPassSrg.azsli>
|
||||
|
||||
// Pass Output
|
||||
#include <Atom/Features/PBR/ForwardPassOutput.azsli>
|
||||
|
||||
// Utility
|
||||
#include <Atom/Features/ColorManagement/TransformColor.azsli>
|
||||
#include <Atom/Features/PBR/AlphaUtils.azsli>
|
||||
|
||||
// Custom Surface & Lighting
|
||||
#include <Atom/Features/PBR/Lighting/StandardLighting.azsli>
|
||||
|
||||
// Decals
|
||||
#include <Atom/Features/PBR/Decals.azsli>
|
||||
|
||||
|
||||
// ---------- Vertex Shader ----------
|
||||
|
||||
struct VSInput
|
||||
{
|
||||
// Base fields (required by the template azsli file)...
|
||||
float3 m_position : POSITION;
|
||||
float3 m_normal : NORMAL;
|
||||
float4 m_tangent : TANGENT;
|
||||
float3 m_bitangent : BITANGENT;
|
||||
|
||||
// Extended fields (only referenced in this azsl file)...
|
||||
float2 m_uv0 : UV0;
|
||||
float2 m_uv1 : UV1;
|
||||
};
|
||||
|
||||
struct VSOutput
|
||||
{
|
||||
// Base fields (required by the template azsli file)...
|
||||
// "centroid" is needed for SV_Depth to compile
|
||||
precise linear centroid float4 m_position : SV_Position;
|
||||
float3 m_normal: NORMAL;
|
||||
float3 m_tangent : TANGENT;
|
||||
float3 m_bitangent : BITANGENT;
|
||||
float3 m_worldPosition : UV0;
|
||||
float3 m_shadowCoords[ViewSrg::MaxCascadeCount] : UV3;
|
||||
|
||||
// Extended fields (only referenced in this azsl file)...
|
||||
float2 m_uv[UvSetCount] : UV1;
|
||||
};
|
||||
|
||||
#include <Atom/Features/Vertex/VertexHelper.azsli>
|
||||
|
||||
VSOutput StandardPbr_ForwardPassVS(VSInput IN)
|
||||
{
|
||||
VSOutput OUT;
|
||||
|
||||
float4x4 objectToWorld = GetObjectToWorld();
|
||||
float4 worldPosition = mul(objectToWorld, float4(IN.m_position, 1.0));
|
||||
OUT.m_worldPosition = worldPosition.xyz;
|
||||
OUT.m_position = mul(ViewSrg::m_viewProjectionMatrix, worldPosition);
|
||||
|
||||
float2 uvs[UvSetCount] = { IN.m_uv0, IN.m_uv1 };
|
||||
TransformUvs(uvs, OUT.m_uv);
|
||||
|
||||
// Shadow coords will be calculated in the pixel shader in this case
|
||||
bool skipShadowCoords = ShouldHandleParallax() && o_parallax_enablePixelDepthOffset;
|
||||
|
||||
float3x3 objectToWorldIT = GetNormalToWorld();
|
||||
ConstructTBN(IN.m_normal, IN.m_tangent, IN.m_bitangent, objectToWorld, objectToWorldIT, OUT.m_normal, OUT.m_tangent, OUT.m_bitangent);
|
||||
|
||||
// directional light shadow
|
||||
const uint shadowIndex = ViewSrg::m_shadowIndexDirectionalLight;
|
||||
if (o_enableShadows && !skipShadowCoords && shadowIndex < SceneSrg::m_directionalLightCount)
|
||||
{
|
||||
DirectionalLightShadow::GetShadowCoords(
|
||||
shadowIndex,
|
||||
worldPosition,
|
||||
OUT.m_normal,
|
||||
OUT.m_shadowCoords);
|
||||
}
|
||||
|
||||
return OUT;
|
||||
}
|
||||
|
||||
|
||||
// ---------- Pixel Shader ----------
|
||||
|
||||
PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace, out float depthNDC)
|
||||
{
|
||||
const float3 vertexNormal = normalize(IN.m_normal);
|
||||
|
||||
// ------- Tangents & Bitangents -------
|
||||
float3 tangents[UvSetCount] = { IN.m_tangent, IN.m_tangent };
|
||||
float3 bitangents[UvSetCount] = { IN.m_bitangent, IN.m_bitangent };
|
||||
|
||||
if (ShouldHandleParallax() || o_normal_useTexture || (o_clearCoat_enabled && o_clearCoat_normal_useTexture))
|
||||
{
|
||||
for (int i = 0; i != UvSetCount; ++i)
|
||||
{
|
||||
EvaluateTangentFrame(
|
||||
IN.m_normal,
|
||||
IN.m_worldPosition,
|
||||
isFrontFace,
|
||||
IN.m_uv[i],
|
||||
i,
|
||||
IN.m_tangent,
|
||||
IN.m_bitangent,
|
||||
tangents[i],
|
||||
bitangents[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// ------- Depth & Parallax -------
|
||||
|
||||
depthNDC = IN.m_position.z;
|
||||
bool displacementIsClipped = false;
|
||||
|
||||
if(ShouldHandleParallax())
|
||||
{
|
||||
SetPixelDepth(
|
||||
IN.m_worldPosition,
|
||||
IN.m_normal,
|
||||
tangents,
|
||||
bitangents,
|
||||
IN.m_uv,
|
||||
isFrontFace,
|
||||
IN.m_position.w,
|
||||
depthNDC,
|
||||
displacementIsClipped);
|
||||
|
||||
// Adjust directional light shadow coordinates for parallax correction
|
||||
if(o_parallax_enablePixelDepthOffset)
|
||||
{
|
||||
const uint shadowIndex = ViewSrg::m_shadowIndexDirectionalLight;
|
||||
if (o_enableShadows && shadowIndex < SceneSrg::m_directionalLightCount)
|
||||
{
|
||||
DirectionalLightShadow::GetShadowCoords(shadowIndex, IN.m_worldPosition, vertexNormal, IN.m_shadowCoords);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SurfaceSettings surfaceSettings;
|
||||
Surface surface;
|
||||
surface.vertexNormal = vertexNormal;
|
||||
surface.position = IN.m_worldPosition.xyz;
|
||||
|
||||
// ------- Alpha & Clip -------
|
||||
// TODO: this often invokes a separate sample of the base color texture which is wasteful
|
||||
float alpha = GetAlphaAndClip(IN.m_uv);
|
||||
|
||||
EvaluateStandardSurface(IN.m_normal, IN.m_uv, tangents, bitangents, isFrontFace, displacementIsClipped, surface, surfaceSettings);
|
||||
|
||||
// ------- Lighting Data -------
|
||||
|
||||
LightingData lightingData;
|
||||
|
||||
// Light iterator
|
||||
lightingData.tileIterator.Init(IN.m_position, PassSrg::m_lightListRemapped, PassSrg::m_tileLightData);
|
||||
lightingData.Init(surface.position, surface.normal, surface.roughnessLinear);
|
||||
|
||||
// Directional light shadow coordinates
|
||||
lightingData.shadowCoords = IN.m_shadowCoords;
|
||||
|
||||
// Surface lighting properties
|
||||
lightingData.emissiveLighting = surface.emissiveLighting;
|
||||
lightingData.diffuseAmbientOcclusion = surface.diffuseAmbientOcclusion;
|
||||
lightingData.specularOcclusion = surface.specularOcclusion;
|
||||
|
||||
// Diffuse and Specular response (used in IBL calculations)
|
||||
lightingData.specularResponse = FresnelSchlickWithRoughness(lightingData.NdotV, surface.specularF0, surface.roughnessLinear);
|
||||
lightingData.diffuseResponse = 1.0 - lightingData.specularResponse;
|
||||
|
||||
if(o_clearCoat_feature_enabled)
|
||||
{
|
||||
// Clear coat layer has fixed IOR = 1.5 and transparent => F0 = (1.5 - 1)^2 / (1.5 + 1)^2 = 0.04
|
||||
lightingData.diffuseResponse *= 1.0 - (FresnelSchlickWithRoughness(lightingData.NdotV, float3(0.04, 0.04, 0.04), surface.clearCoat.roughness) * surface.clearCoat.factor);
|
||||
}
|
||||
|
||||
// ------- Multiscatter -------
|
||||
|
||||
lightingData.CalculateMultiscatterCompensation(surface.specularF0, o_specularF0_enableMultiScatterCompensation);
|
||||
|
||||
// ------- Lighting Calculation -------
|
||||
|
||||
// Apply Decals
|
||||
ApplyDecals(lightingData.tileIterator, surface);
|
||||
|
||||
// Apply Direct Lighting
|
||||
ApplyDirectLighting(surface, lightingData);
|
||||
|
||||
// Apply Image Based Lighting (IBL)
|
||||
ApplyIBL(surface, lightingData);
|
||||
|
||||
// Finalize Lighting
|
||||
lightingData.FinalizeLighting();
|
||||
|
||||
PbrLightingOutput lightingOutput = GetPbrLightingOutput(surface, lightingData, alpha);
|
||||
|
||||
// ------- Opacity -------
|
||||
|
||||
if (o_opacity_mode == OpacityMode::Blended || o_opacity_mode == OpacityMode::TintedTransparent)
|
||||
{
|
||||
// Increase opacity at grazing angles for surfaces with a low m_opacityAffectsSpecularFactor.
|
||||
// For m_opacityAffectsSpecularFactor values close to 0, that indicates a transparent surface
|
||||
// like glass, so it becomes less transparent at grazing angles. For m_opacityAffectsSpecularFactor
|
||||
// values close to 1.0, that indicates the absence of a surface entirely, so this effect should
|
||||
// not apply.
|
||||
float fresnelAlpha = FresnelSchlickWithRoughness(lightingData.NdotV, alpha, surface.roughnessLinear).x;
|
||||
alpha = lerp(fresnelAlpha, alpha, surfaceSettings.opacityAffectsSpecularFactor);
|
||||
}
|
||||
|
||||
if (o_opacity_mode == OpacityMode::Blended)
|
||||
{
|
||||
// [GFX_TODO ATOM-13187] PbrLighting shouldn't be writing directly to render targets. It's confusing when
|
||||
// specular is being added to diffuse just because we're calling render target 0 "diffuse".
|
||||
|
||||
// For blended mode, we do (dest * alpha) + (source * 1.0). This allows the specular
|
||||
// to be added on top of the diffuse, but then the diffuse must be pre-multiplied.
|
||||
// It's done this way because surface transparency doesn't really change specular response (eg, glass).
|
||||
|
||||
lightingOutput.m_diffuseColor.rgb *= lightingOutput.m_diffuseColor.w; // pre-multiply diffuse
|
||||
|
||||
// Add specular. m_opacityAffectsSpecularFactor controls how much the alpha masks out specular contribution.
|
||||
float3 specular = lightingOutput.m_specularColor.rgb;
|
||||
specular = lerp(specular, specular * lightingOutput.m_diffuseColor.w, surfaceSettings.opacityAffectsSpecularFactor);
|
||||
lightingOutput.m_diffuseColor.rgb += specular;
|
||||
|
||||
lightingOutput.m_diffuseColor.w = alpha;
|
||||
}
|
||||
else if (o_opacity_mode == OpacityMode::TintedTransparent)
|
||||
{
|
||||
// See OpacityMode::Blended above for the basic method. TintedTransparent adds onto the above concept by supporting
|
||||
// colored alpha. This is currently a very basic calculation that uses the baseColor as a multiplier with strength
|
||||
// determined by the alpha. We'll modify this later to be more physically accurate and allow surface depth,
|
||||
// absorption, and interior color to be specified.
|
||||
//
|
||||
// The technique uses dual source blending to allow two separate sources to be part of the blending equation
|
||||
// even though ultimately only a single render target is being written to. m_diffuseColor is render target 0 and
|
||||
// m_specularColor render target 1, and the blend mode is (dest * source1color) + (source * 1.0).
|
||||
//
|
||||
// This means that m_specularColor.rgb (source 1) is multiplied against the destination, then
|
||||
// m_diffuseColor.rgb (source) is added to that, and the final result is stored in render target 0.
|
||||
|
||||
lightingOutput.m_diffuseColor.rgb *= lightingOutput.m_diffuseColor.w; // pre-multiply diffuse
|
||||
|
||||
// Add specular. m_opacityAffectsSpecularFactor controls how much the alpha masks out specular contribution.
|
||||
float3 specular = lightingOutput.m_specularColor.rgb;
|
||||
specular = lerp(specular, specular * lightingOutput.m_diffuseColor.w, surfaceSettings.opacityAffectsSpecularFactor);
|
||||
lightingOutput.m_diffuseColor.rgb += specular;
|
||||
|
||||
lightingOutput.m_specularColor.rgb = surface.baseColor * (1.0 - alpha);
|
||||
}
|
||||
else
|
||||
{
|
||||
lightingOutput.m_diffuseColor.w = -1; // Disable subsurface scattering
|
||||
}
|
||||
|
||||
return lightingOutput;
|
||||
}
|
||||
|
||||
ForwardPassOutputWithDepth StandardPbr_ForwardPassPS(VSOutput IN, bool isFrontFace : SV_IsFrontFace)
|
||||
{
|
||||
ForwardPassOutputWithDepth OUT;
|
||||
float depth;
|
||||
|
||||
PbrLightingOutput lightingOutput = ForwardPassPS_Common(IN, isFrontFace, depth);
|
||||
|
||||
#ifdef UNIFIED_FORWARD_OUTPUT
|
||||
OUT.m_color.rgb = lightingOutput.m_diffuseColor.rgb + lightingOutput.m_specularColor.rgb;
|
||||
OUT.m_color.a = lightingOutput.m_diffuseColor.a;
|
||||
OUT.m_depth = depth;
|
||||
#else
|
||||
OUT.m_diffuseColor = lightingOutput.m_diffuseColor;
|
||||
OUT.m_specularColor = lightingOutput.m_specularColor;
|
||||
OUT.m_specularF0 = lightingOutput.m_specularF0;
|
||||
OUT.m_albedo = lightingOutput.m_albedo;
|
||||
OUT.m_normal = lightingOutput.m_normal;
|
||||
OUT.m_depth = depth;
|
||||
#endif
|
||||
return OUT;
|
||||
}
|
||||
|
||||
[earlydepthstencil]
|
||||
ForwardPassOutput StandardPbr_ForwardPassPS_EDS(VSOutput IN, bool isFrontFace : SV_IsFrontFace)
|
||||
{
|
||||
ForwardPassOutput OUT;
|
||||
float depth;
|
||||
|
||||
PbrLightingOutput lightingOutput = ForwardPassPS_Common(IN, isFrontFace, depth);
|
||||
|
||||
#ifdef UNIFIED_FORWARD_OUTPUT
|
||||
OUT.m_color.rgb = lightingOutput.m_diffuseColor.rgb + lightingOutput.m_specularColor.rgb;
|
||||
OUT.m_color.a = lightingOutput.m_diffuseColor.a;
|
||||
#else
|
||||
OUT.m_diffuseColor = lightingOutput.m_diffuseColor;
|
||||
OUT.m_specularColor = lightingOutput.m_specularColor;
|
||||
OUT.m_specularF0 = lightingOutput.m_specularF0;
|
||||
OUT.m_albedo = lightingOutput.m_albedo;
|
||||
OUT.m_normal = lightingOutput.m_normal;
|
||||
#endif
|
||||
return OUT;
|
||||
}
|
||||
+28
-4
@@ -13,6 +13,21 @@
|
||||
#include <Atom/Features/PBR/Surfaces/ClearCoatSurfaceData.azsli>
|
||||
#include <Atom/Features/PBR/Surfaces/TransmissionSurfaceData.azsli>
|
||||
|
||||
// This data varies across different surfaces, but is uniform within a surface
|
||||
class SurfaceSettings
|
||||
{
|
||||
//! Subsurface scattering parameters
|
||||
float subsurfaceScatteringQuality;
|
||||
float3 scatterDistance;
|
||||
|
||||
// Increase opacity at grazing angles for surfaces with a low m_opacityAffectsSpecularFactor.
|
||||
// For m_opacityAffectsSpecularFactor values close to 0, that indicates a transparent surface
|
||||
// like glass, so it becomes less transparent at grazing angles. For m_opacityAffectsSpecularFactor
|
||||
// values close to 1.0, that indicates the absence of a surface entirely, so this effect should
|
||||
// not apply.
|
||||
float opacityAffectsSpecularFactor;
|
||||
};
|
||||
|
||||
class Surface
|
||||
{
|
||||
AnisotropicSurfaceData anisotropy;
|
||||
@@ -24,11 +39,19 @@ class Surface
|
||||
float3 position; //!< Position in world-space
|
||||
float3 normal; //!< Normal in world-space
|
||||
float3 vertexNormal; //!< Vertex normal in world-space
|
||||
float3 albedo; //!< Albedo color of the non-metallic material, will be multiplied against the diffuse lighting value
|
||||
float3 specularF0; //!< Fresnel f0 spectral value of the surface
|
||||
float3 baseColor; //!< Surface base color
|
||||
float metallic; //!< Surface metallic property
|
||||
float roughnessLinear; //!< Perceptually linear roughness value authored by artists. Must be remapped to roughnessA before use
|
||||
float roughnessA; //!< Actual roughness value ( a.k.a. "alpha roughness") to be used in microfacet calculations
|
||||
float roughnessA2; //!< Alpha roughness ^ 2 (i.e. roughnessA * roughnessA), used in GGX, cached here for perfromance
|
||||
float subsurfaceScatteringFactor;
|
||||
|
||||
//! Surface lighting inputs
|
||||
float3 albedo; //!< Albedo color of the non-metallic material, will be multiplied against the diffuse lighting value
|
||||
float3 specularF0; //!< Fresnel f0 spectral value of the surface
|
||||
float3 emissiveLighting; //!< Emissive lighting
|
||||
float diffuseAmbientOcclusion; //!< Diffuse ambient occlusion factor - [0, 1] :: [Dark, Bright]
|
||||
float specularOcclusion; //!< Specular occlusion factor - [0, 1] :: [Dark, Bright]
|
||||
|
||||
//! Applies specular anti-aliasing to roughnessA2
|
||||
void ApplySpecularAA();
|
||||
@@ -76,12 +99,13 @@ void Surface::CalculateRoughnessA()
|
||||
}
|
||||
}
|
||||
|
||||
void Surface::SetAlbedoAndSpecularF0(float3 baseColor, float specularF0Factor, float metallic)
|
||||
void Surface::SetAlbedoAndSpecularF0(float3 newBaseColor, float specularF0Factor, float newMetallic)
|
||||
{
|
||||
baseColor = newBaseColor;
|
||||
metallic = newMetallic;
|
||||
float3 dielectricSpecularF0 = MaxDielectricSpecularF0 * specularF0Factor;
|
||||
|
||||
// Compute albedo and specularF0 based on metalness
|
||||
albedo = lerp(baseColor, float3(0.0f, 0.0f, 0.0f), metallic);
|
||||
specularF0 = lerp(dielectricSpecularF0, baseColor, metallic);
|
||||
}
|
||||
|
||||
|
||||
+32
-6
@@ -8,11 +8,21 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Atom/Features/PBR/Surfaces/AnisotropicSurfaceData.azsli>
|
||||
#include <Atom/Features/PBR/Surfaces/BasePbrSurfaceData.azsli>
|
||||
#include <Atom/Features/PBR/Surfaces/ClearCoatSurfaceData.azsli>
|
||||
#include <Atom/Features/PBR/Surfaces/TransmissionSurfaceData.azsli>
|
||||
|
||||
// This data varies across different surfaces, but is uniform within a surface
|
||||
class SurfaceSettings
|
||||
{
|
||||
// Increase opacity at grazing angles for surfaces with a low m_opacityAffectsSpecularFactor.
|
||||
// For m_opacityAffectsSpecularFactor values close to 0, that indicates a transparent surface
|
||||
// like glass, so it becomes less transparent at grazing angles. For m_opacityAffectsSpecularFactor
|
||||
// values close to 1.0, that indicates the absence of a surface entirely, so this effect should
|
||||
// not apply.
|
||||
float opacityAffectsSpecularFactor;
|
||||
};
|
||||
|
||||
class Surface
|
||||
{
|
||||
|
||||
@@ -25,19 +35,33 @@ class Surface
|
||||
precise float3 position; //!< Position in world-space
|
||||
float3 normal; //!< Normal in world-space
|
||||
float3 vertexNormal; //!< Vertex normal in world-space
|
||||
float3 albedo; //!< Albedo color of the non-metallic material, will be multiplied against the diffuse lighting value
|
||||
float3 specularF0; //!< Fresnel f0 spectral value of the surface
|
||||
float3 baseColor; //!< Surface base color
|
||||
float metallic; //!< Surface metallic property
|
||||
float roughnessLinear; //!< Perceptually linear roughness value authored by artists. Must be remapped to roughnessA before use
|
||||
float roughnessA; //!< Actual roughness value ( a.k.a. "alpha roughness") to be used in microfacet calculations
|
||||
float roughnessA2; //!< Alpha roughness ^ 2 (i.e. roughnessA * roughnessA), used in GGX, cached here for perfromance
|
||||
|
||||
// Increase opacity at grazing angles for surfaces with a low m_opacityAffectsSpecularFactor.
|
||||
// For m_opacityAffectsSpecularFactor values close to 0, that indicates a transparent surface
|
||||
// like glass, so it becomes less transparent at grazing angles. For m_opacityAffectsSpecularFactor
|
||||
// values close to 1.0, that indicates the absence of a surface entirely, so this effect should
|
||||
// not apply.
|
||||
float opacityAffectsSpecularFactor;
|
||||
|
||||
//! Surface lighting data
|
||||
float3 albedo; //!< Albedo color of the non-metallic material, will be multiplied against the diffuse lighting value
|
||||
float3 specularF0; //!< Fresnel f0 spectral value of the surface
|
||||
float3 emissiveLighting; //!< Emissive lighting
|
||||
float diffuseAmbientOcclusion; //!< Diffuse ambient occlusion factor - [0, 1] :: [Dark, Bright]
|
||||
float specularOcclusion; //!< Specular occlusion factor - [0, 1] :: [Dark, Bright]
|
||||
|
||||
//! Applies specular anti-aliasing to roughnessA2
|
||||
void ApplySpecularAA();
|
||||
|
||||
//! Calculates roughnessA and roughnessA2 after roughness has been set
|
||||
void CalculateRoughnessA();
|
||||
|
||||
//! Sets albedo and specularF0 using metallic workflow
|
||||
//! Sets albedo, base color, specularF0, and metallic properties using metallic workflow
|
||||
void SetAlbedoAndSpecularF0(float3 baseColor, float specularF0Factor, float metallic);
|
||||
};
|
||||
|
||||
@@ -75,12 +99,14 @@ void Surface::CalculateRoughnessA()
|
||||
}
|
||||
}
|
||||
|
||||
void Surface::SetAlbedoAndSpecularF0(float3 baseColor, float specularF0Factor, float metallic)
|
||||
void Surface::SetAlbedoAndSpecularF0(float3 newBaseColor, float specularF0Factor, float newMetallic)
|
||||
{
|
||||
baseColor = newBaseColor;
|
||||
metallic = newMetallic;
|
||||
|
||||
float3 dielectricSpecularF0 = MaxDielectricSpecularF0 * specularF0Factor;
|
||||
|
||||
// Compute albedo and specularF0 based on metalness
|
||||
albedo = lerp(baseColor, float3(0.0f, 0.0f, 0.0f), metallic);
|
||||
specularF0 = lerp(dielectricSpecularF0, baseColor, metallic);
|
||||
}
|
||||
|
||||
|
||||
@@ -10,12 +10,24 @@ set(FILES
|
||||
Materials/Special/ShadowCatcher.azsl
|
||||
Materials/Special/ShadowCatcher.materialtype
|
||||
Materials/Special/ShadowCatcher.shader
|
||||
Materials/Types/MaterialFunctions/EnhancedParallaxDepth.azsli
|
||||
Materials/Types/MaterialFunctions/EvaluateEnhancedSurface.azsli
|
||||
Materials/Types/MaterialFunctions/EvaluateStandardSurface.azsli
|
||||
Materials/Types/MaterialFunctions/EvaluateTangentFrame.azsli
|
||||
Materials/Types/MaterialFunctions/MultilayerParallaxDepth.azsli
|
||||
Materials/Types/MaterialFunctions/ParallaxDepth.azsli
|
||||
Materials/Types/MaterialFunctions/StandardGetAlphaAndClip.azsli
|
||||
Materials/Types/MaterialFunctions/StandardGetNormalToWorld.azsli
|
||||
Materials/Types/MaterialFunctions/StandardGetObjectToWorld.azsli
|
||||
Materials/Types/MaterialFunctions/StandardTransformDetailUvs.azsli
|
||||
Materials/Types/MaterialFunctions/StandardTransformUvs.azsli
|
||||
Materials/Types/BasePBR.materialtype
|
||||
Materials/Types/BasePBR_Common.azsli
|
||||
Materials/Types/BasePBR_ForwardPass.azsl
|
||||
Materials/Types/BasePBR_ForwardPass.shader
|
||||
Materials/Types/BasePBR_LowEndForward.azsl
|
||||
Materials/Types/BasePBR_LowEndForward.shader
|
||||
Materials/Types/DepthPass_WithPS.azsli
|
||||
Materials/Types/EnhancedPBR.materialtype
|
||||
Materials/Types/EnhancedPBR_Common.azsli
|
||||
Materials/Types/EnhancedPBR_DepthPass_WithPS.azsl
|
||||
@@ -26,6 +38,7 @@ set(FILES
|
||||
Materials/Types/EnhancedPBR_Shadowmap_WithPS.azsl
|
||||
Materials/Types/EnhancedPBR_Shadowmap_WithPS.shader
|
||||
Materials/Types/EnhancedPBR_SubsurfaceState.lua
|
||||
Materials/Types/EnhancedSurface_ForwardPass.azsli
|
||||
Materials/Types/Skin.azsl
|
||||
Materials/Types/Skin.materialtype
|
||||
Materials/Types/Skin.shader
|
||||
@@ -56,7 +69,6 @@ set(FILES
|
||||
Materials/Types/StandardPBR_ForwardPass_EDS.shader
|
||||
Materials/Types/StandardPBR_HandleOpacityDoubleSided.lua
|
||||
Materials/Types/StandardPBR_HandleOpacityMode.lua
|
||||
Materials/Types/StandardPBR_LowEndForward.azsl
|
||||
Materials/Types/StandardPBR_LowEndForward.shader
|
||||
Materials/Types/StandardPBR_LowEndForward_EDS.shader
|
||||
Materials/Types/StandardPBR_Metallic.lua
|
||||
@@ -65,6 +77,7 @@ set(FILES
|
||||
Materials/Types/StandardPBR_ShaderEnable.lua
|
||||
Materials/Types/StandardPBR_Shadowmap_WithPS.azsl
|
||||
Materials/Types/StandardPBR_Shadowmap_WithPS.shader
|
||||
Materials/Types/StandardSurface_ForwardPass.azsli
|
||||
Materials/Types/MaterialInputs/AlphaInput.azsli
|
||||
Materials/Types/MaterialInputs/BaseColorInput.azsli
|
||||
Materials/Types/MaterialInputs/ClearCoatInput.azsli
|
||||
|
||||
+2
-1
@@ -100,7 +100,8 @@ namespace AZ
|
||||
//! Sets all of the the disk data for the provided LightHandle.
|
||||
virtual void SetDiskData(LightHandle handle, const DiskLightData& data) = 0;
|
||||
|
||||
|
||||
//! Get a read only copy of a disk lights data, useful for debug rendering
|
||||
virtual const DiskLightData& GetDiskData(LightHandle handle) const = 0;
|
||||
};
|
||||
} // namespace Render
|
||||
} // namespace AZ
|
||||
|
||||
@@ -255,6 +255,13 @@ namespace AZ
|
||||
UpdateShadow(handle);
|
||||
}
|
||||
|
||||
const DiskLightData& DiskLightFeatureProcessor::GetDiskData(LightHandle handle) const
|
||||
{
|
||||
AZ_Assert(handle.IsValid(), "Invalid LightHandle passed to DiskLightFeatureProcessor::GetDiskData().");
|
||||
|
||||
return m_diskLightData.GetData(handle.GetIndex());
|
||||
}
|
||||
|
||||
const Data::Instance<RPI::Buffer> DiskLightFeatureProcessor::GetLightBuffer()const
|
||||
{
|
||||
return m_lightBufferHandler.GetBuffer();
|
||||
|
||||
@@ -58,6 +58,7 @@ namespace AZ
|
||||
void SetEsmExponent(LightHandle handle, float esmExponent) override;
|
||||
|
||||
void SetDiskData(LightHandle handle, const DiskLightData& data) override;
|
||||
const DiskLightData& GetDiskData(LightHandle handle) const override;
|
||||
|
||||
const Data::Instance<RPI::Buffer> GetLightBuffer()const;
|
||||
uint32_t GetLightCount()const;
|
||||
|
||||
@@ -184,8 +184,8 @@ namespace AZ
|
||||
virtual void DrawDisk(const AZ::Vector3& center, const AZ::Vector3& direction, float radius, const AZ::Color& color, DrawStyle style = DrawStyle::Shaded, DepthTest depthTest = DepthTest::On, DepthWrite depthWrite = DepthWrite::On, FaceCullMode faceCull = FaceCullMode::Back, int32_t viewProjOverrideIndex = -1) = 0;
|
||||
|
||||
//! Draw a cone.
|
||||
//! @param center The center of the base circle.
|
||||
//! @param direction The direction vector. The tip of the cone will point along this vector.
|
||||
//! @param center The center of the cone base.
|
||||
//! @param direction The direction vector. This is the vector from the center of the base to the point at the tip.
|
||||
//! @param radius The radius.
|
||||
//! @param height The height of the cone (the distance from the base center to the tip).
|
||||
//! @param color The color to draw the cone.
|
||||
|
||||
@@ -133,7 +133,8 @@ namespace AZ
|
||||
case AZ::RHI::Format::R16G16_UNORM:
|
||||
case AZ::RHI::Format::R16G16B16A16_UNORM:
|
||||
{
|
||||
return mem[index] / static_cast<float>(std::numeric_limits<AZ::u16>::max());
|
||||
auto actualMem = reinterpret_cast<const AZ::u16*>(mem);
|
||||
return actualMem[index] / static_cast<float>(std::numeric_limits<AZ::u16>::max());
|
||||
}
|
||||
case AZ::RHI::Format::R16_SNORM:
|
||||
case AZ::RHI::Format::R16G16_SNORM:
|
||||
@@ -480,14 +481,13 @@ namespace AZ
|
||||
const AZ::RHI::ImageDescriptor imageDescriptor = imageAsset->GetImageDescriptor();
|
||||
auto width = imageDescriptor.m_size.m_width;
|
||||
const uint32_t numComponents = AZ::RHI::GetFormatComponentCount(imageDescriptor.m_format);
|
||||
const uint32_t pixelSize = AZ::RHI::GetFormatSize(imageDescriptor.m_format) / numComponents;
|
||||
|
||||
size_t outValuesIndex = 0;
|
||||
for (uint32_t y = topLeft.second; y < bottomRight.second; ++y)
|
||||
{
|
||||
for (uint32_t x = topLeft.first; x < bottomRight.first; ++x)
|
||||
{
|
||||
size_t imageDataIndex = (y * width + x) * pixelSize + componentIndex;
|
||||
size_t imageDataIndex = (y * width + x) * numComponents + componentIndex;
|
||||
|
||||
auto& outValue = outValues[outValuesIndex++];
|
||||
outValue = Internal::RetrieveFloatValue(imageData.data(), imageDataIndex, imageDescriptor.m_format);
|
||||
@@ -513,14 +513,13 @@ namespace AZ
|
||||
const AZ::RHI::ImageDescriptor imageDescriptor = imageAsset->GetImageDescriptor();
|
||||
auto width = imageDescriptor.m_size.m_width;
|
||||
const uint32_t numComponents = AZ::RHI::GetFormatComponentCount(imageDescriptor.m_format);
|
||||
const uint32_t pixelSize = AZ::RHI::GetFormatSize(imageDescriptor.m_format) / numComponents;
|
||||
|
||||
size_t outValuesIndex = 0;
|
||||
for (uint32_t y = topLeft.second; y < bottomRight.second; ++y)
|
||||
{
|
||||
for (uint32_t x = topLeft.first; x < bottomRight.first; ++x)
|
||||
{
|
||||
size_t imageDataIndex = (y * width + x) * pixelSize + componentIndex;
|
||||
size_t imageDataIndex = (y * width + x) * numComponents + componentIndex;
|
||||
|
||||
auto& outValue = outValues[outValuesIndex++];
|
||||
outValue = Internal::RetrieveUintValue(imageData.data(), imageDataIndex, imageDescriptor.m_format);
|
||||
@@ -546,14 +545,13 @@ namespace AZ
|
||||
const AZ::RHI::ImageDescriptor imageDescriptor = imageAsset->GetImageDescriptor();
|
||||
auto width = imageDescriptor.m_size.m_width;
|
||||
const uint32_t numComponents = AZ::RHI::GetFormatComponentCount(imageDescriptor.m_format);
|
||||
const uint32_t pixelSize = AZ::RHI::GetFormatSize(imageDescriptor.m_format) / numComponents;
|
||||
|
||||
size_t outValuesIndex = 0;
|
||||
for (uint32_t y = topLeft.second; y < bottomRight.second; ++y)
|
||||
{
|
||||
for (uint32_t x = topLeft.first; x < bottomRight.first; ++x)
|
||||
{
|
||||
size_t imageDataIndex = (y * width + x) * pixelSize + componentIndex;
|
||||
size_t imageDataIndex = (y * width + x) * numComponents + componentIndex;
|
||||
|
||||
auto& outValue = outValues[outValuesIndex++];
|
||||
outValue = Internal::RetrieveIntValue(imageData.data(), imageDataIndex, imageDescriptor.m_format);
|
||||
|
||||
@@ -68,8 +68,10 @@ ForwardPassOutput MinimalPBR_MainPassPS(VSOutput IN)
|
||||
surface.CalculateRoughnessA();
|
||||
|
||||
// Albedo, SpecularF0
|
||||
const float specularF0Factor = 0.5f;
|
||||
surface.SetAlbedoAndSpecularF0(MinimalPBRSrg::m_baseColor, specularF0Factor, MinimalPBRSrg::m_metallic);
|
||||
float3 baseColor = MinimalPBRSrg::m_baseColor;
|
||||
float metallic = MinimalPBRSrg::m_metallic;
|
||||
float specularF0Factor = 0.5f;
|
||||
surface.SetAlbedoAndSpecularF0(baseColor, specularF0Factor, metallic);
|
||||
|
||||
// Clear Coat
|
||||
surface.clearCoat.InitializeToZero();
|
||||
|
||||
@@ -102,7 +102,7 @@ TEST_F(FastNoiseTest, FastNoise_VerifyGetValueAndGetValuesMatch)
|
||||
noiseEntity->Activate();
|
||||
|
||||
// Create a gradient sampler and run through a series of points to see if they match expectations.
|
||||
UnitTest::GradientSignalTestHelpers::CompareGetValueAndGetValues(noiseEntity->GetId(), shapeHalfBounds);
|
||||
UnitTest::GradientSignalTestHelpers::CompareGetValueAndGetValues(noiseEntity->GetId(), -shapeHalfBounds, shapeHalfBounds);
|
||||
}
|
||||
|
||||
// This uses custom test / benchmark hooks so that we can load LmbrCentral and GradientSignal Gems.
|
||||
|
||||
@@ -17,9 +17,12 @@ ly_add_target(
|
||||
PUBLIC
|
||||
Include
|
||||
BUILD_DEPENDENCIES
|
||||
PRIVATE
|
||||
AZ::AtomCore
|
||||
PUBLIC
|
||||
AZ::AzCore
|
||||
AZ::AzFramework
|
||||
Gem::Atom_RPI.Public
|
||||
Gem::SurfaceData
|
||||
Gem::ImageProcessingAtom.Headers
|
||||
Gem::LmbrCentral
|
||||
|
||||
+17
-1
@@ -8,8 +8,11 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Atom/RPI.Reflect/Image/StreamingImageAsset.h>
|
||||
#include <AzCore/Asset/AssetCommon.h>
|
||||
#include <AzCore/Component/Component.h>
|
||||
#include <AzCore/Serialization/Json/BaseJsonSerializer.h>
|
||||
#include <AzCore/Serialization/Json/RegistrationContext.h>
|
||||
#include <GradientSignal/Ebuses/GradientRequestBus.h>
|
||||
#include <GradientSignal/Ebuses/GradientTransformRequestBus.h>
|
||||
#include <GradientSignal/Ebuses/ImageGradientRequestBus.h>
|
||||
@@ -25,6 +28,19 @@ namespace LmbrCentral
|
||||
|
||||
namespace GradientSignal
|
||||
{
|
||||
// Custom JSON serializer for ImageGradientConfig to handle version conversion
|
||||
class JsonImageGradientConfigSerializer
|
||||
: public AZ::BaseJsonSerializer
|
||||
{
|
||||
public:
|
||||
AZ_RTTI(GradientSignal::JsonImageGradientConfigSerializer, "{C5B982C8-2E81-45C3-8932-B6F54B28F493}", AZ::BaseJsonSerializer);
|
||||
AZ_CLASS_ALLOCATOR_DECL;
|
||||
|
||||
AZ::JsonSerializationResult::Result Load(
|
||||
void* outputValue, const AZ::Uuid& outputValueTypeId, const rapidjson::Value& inputValue,
|
||||
AZ::JsonDeserializerContext& context) override;
|
||||
};
|
||||
|
||||
class ImageGradientConfig
|
||||
: public AZ::ComponentConfig
|
||||
{
|
||||
@@ -32,7 +48,7 @@ namespace GradientSignal
|
||||
AZ_CLASS_ALLOCATOR(ImageGradientConfig, AZ::SystemAllocator, 0);
|
||||
AZ_RTTI(ImageGradientConfig, "{1BDB5DA4-A4A8-452B-BE6D-6BD451D4E7CD}", AZ::ComponentConfig);
|
||||
static void Reflect(AZ::ReflectContext* context);
|
||||
AZ::Data::Asset<ImageAsset> m_imageAsset = { AZ::Data::AssetLoadBehavior::QueueLoad };
|
||||
AZ::Data::Asset<AZ::RPI::StreamingImageAsset> m_imageAsset = { AZ::Data::AssetLoadBehavior::QueueLoad };
|
||||
float m_tilingX = 1.0f;
|
||||
float m_tilingY = 1.0f;
|
||||
};
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include <AzCore/RTTI/ReflectContext.h>
|
||||
#include <AzFramework/Asset/GenericAssetHandler.h>
|
||||
#include <Atom/ImageProcessing/PixelFormats.h>
|
||||
#include <Atom/RPI.Reflect/Image/StreamingImageAsset.h>
|
||||
|
||||
namespace AZ
|
||||
{
|
||||
@@ -61,6 +62,6 @@ namespace GradientSignal
|
||||
}
|
||||
};
|
||||
|
||||
float GetValueFromImageAsset(const AZ::Data::Asset<ImageAsset>& imageAsset, const AZ::Vector3& uvw, float tilingX, float tilingY, float defaultValue);
|
||||
float GetValueFromImageAsset(const AZ::Data::Asset<AZ::RPI::StreamingImageAsset>& imageAsset, const AZ::Vector3& uvw, float tilingX, float tilingY, float defaultValue);
|
||||
|
||||
} // namespace GradientSignal
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
*/
|
||||
|
||||
#include <GradientSignal/Components/ImageGradientComponent.h>
|
||||
#include <Atom/ImageProcessing/ImageProcessingDefines.h>
|
||||
#include <AzCore/Asset/AssetManager.h>
|
||||
#include <AzCore/Asset/AssetSerializer.h>
|
||||
#include <AzCore/Debug/Profiler.h>
|
||||
@@ -18,16 +19,103 @@
|
||||
|
||||
namespace GradientSignal
|
||||
{
|
||||
AZ::JsonSerializationResult::Result JsonImageGradientConfigSerializer::Load(
|
||||
void* outputValue, [[maybe_unused]] const AZ::Uuid& outputValueTypeId,
|
||||
const rapidjson::Value& inputValue, AZ::JsonDeserializerContext& context)
|
||||
{
|
||||
// We can distinguish between version 1 and 2 by the presence of the "ImageAsset" field,
|
||||
// which is only in version 1.
|
||||
// For version 2, we don't need to do any special processing, so just let the base class
|
||||
// load the JSON if we don't find the "ImageAsset" field.
|
||||
rapidjson::Value::ConstMemberIterator itr = inputValue.FindMember("ImageAsset");
|
||||
if (itr == inputValue.MemberEnd())
|
||||
{
|
||||
return AZ::BaseJsonSerializer::Load(outputValue, outputValueTypeId, inputValue, context);
|
||||
}
|
||||
|
||||
namespace JSR = AZ::JsonSerializationResult;
|
||||
|
||||
auto configInstance = reinterpret_cast<ImageGradientConfig*>(outputValue);
|
||||
AZ_Assert(configInstance, "Output value for JsonImageGradientConfigSerializer can't be null.");
|
||||
|
||||
JSR::ResultCode result(JSR::Tasks::ReadField);
|
||||
|
||||
result.Combine(ContinueLoadingFromJsonObjectField(
|
||||
&configInstance->m_tilingX, azrtti_typeid<decltype(configInstance->m_tilingX)>(), inputValue, "TilingX", context));
|
||||
|
||||
result.Combine(ContinueLoadingFromJsonObjectField(
|
||||
&configInstance->m_tilingY, azrtti_typeid<decltype(configInstance->m_tilingY)>(), inputValue, "TilingY", context));
|
||||
|
||||
// Version 1 stored a custom GradientSignal::ImageAsset as the image asset.
|
||||
// In Version 2, we changed the image asset to use the generic AZ::RPI::StreamingImageAsset,
|
||||
// so they are both AZ::Data::Asset but reference different types.
|
||||
// Using the assetHint, which will be something like "my_test_image.gradimage",
|
||||
// we need to find the valid streaming image asset product from the same source,
|
||||
// which will be something like "my_test_image.png.streamingimage"
|
||||
AZStd::string assetHint;
|
||||
AZ::Data::AssetId fixedAssetId;
|
||||
auto it = itr->value.FindMember("assetHint");
|
||||
if (it != itr->value.MemberEnd())
|
||||
{
|
||||
AZ::ScopedContextPath subPath(context, "assetHint");
|
||||
result.Combine(ContinueLoading(&assetHint, azrtti_typeid<AZStd::string>(), it->value, context));
|
||||
|
||||
if (assetHint.ends_with(".gradimage"))
|
||||
{
|
||||
// We don't know what image format the original source was, so we need to loop through
|
||||
// all the supported image extensions to check if they have a valid corresponding
|
||||
// streaming image asset
|
||||
for (auto& supportedImageExtension : ImageProcessingAtom::s_SupportedImageExtensions)
|
||||
{
|
||||
AZStd::string imageExtension(supportedImageExtension);
|
||||
|
||||
// The image extensions are stored with a wildcard (e.g. *.png) so we need to strip that off first
|
||||
AZ::StringFunc::Replace(imageExtension, "*", "");
|
||||
|
||||
// Form potential streaming image path (e.g. my_test_image.png.streamingimage)
|
||||
AZStd::string potentialStreamingImagePath(assetHint);
|
||||
AZ::StringFunc::Replace(potentialStreamingImagePath, ".gradimage", "");
|
||||
potentialStreamingImagePath += imageExtension + ".streamingimage";
|
||||
|
||||
// Check if there is a valid streaming image asset for this path
|
||||
AZ::Data::AssetCatalogRequestBus::BroadcastResult(fixedAssetId, &AZ::Data::AssetCatalogRequestBus::Events::GetAssetIdByPath, potentialStreamingImagePath.c_str(), azrtti_typeid<AZ::Data::Asset<AZ::RPI::StreamingImageAsset>>(), false);
|
||||
if (fixedAssetId.IsValid())
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Replace the old gradimage with new AssetId for streaming image asset
|
||||
if (fixedAssetId.IsValid())
|
||||
{
|
||||
configInstance->m_imageAsset = AZ::Data::AssetManager::Instance().GetAsset<AZ::RPI::StreamingImageAsset>(fixedAssetId, AZ::Data::AssetLoadBehavior::QueueLoad);
|
||||
}
|
||||
|
||||
return context.Report(result,
|
||||
result.GetProcessing() != JSR::Processing::Halted ?
|
||||
"Successfully loaded ImageGradientConfig information." :
|
||||
"Failed to load ImageGradientConfig information.");
|
||||
}
|
||||
|
||||
AZ_CLASS_ALLOCATOR_IMPL(JsonImageGradientConfigSerializer, AZ::SystemAllocator, 0);
|
||||
|
||||
void ImageGradientConfig::Reflect(AZ::ReflectContext* context)
|
||||
{
|
||||
if (auto jsonContext = azrtti_cast<AZ::JsonRegistrationContext*>(context))
|
||||
{
|
||||
jsonContext->Serializer<JsonImageGradientConfigSerializer>()->HandlesType<ImageGradientConfig>();
|
||||
}
|
||||
|
||||
AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context);
|
||||
if (serialize)
|
||||
{
|
||||
serialize->Class<ImageGradientConfig, AZ::ComponentConfig>()
|
||||
->Version(1)
|
||||
->Field("ImageAsset", &ImageGradientConfig::m_imageAsset)
|
||||
->Version(2)
|
||||
->Field("TilingX", &ImageGradientConfig::m_tilingX)
|
||||
->Field("TilingY", &ImageGradientConfig::m_tilingY)
|
||||
->Field("StreamingImageAsset", &ImageGradientConfig::m_imageAsset)
|
||||
;
|
||||
|
||||
AZ::EditContext* edit = serialize->GetEditContext();
|
||||
@@ -265,7 +353,7 @@ namespace GradientSignal
|
||||
|
||||
{
|
||||
AZStd::unique_lock<decltype(m_imageMutex)> imageLock(m_imageMutex);
|
||||
m_configuration.m_imageAsset = AZ::Data::AssetManager::Instance().FindOrCreateAsset(assetId, azrtti_typeid<ImageAsset>(), m_configuration.m_imageAsset.GetAutoLoadBehavior());
|
||||
m_configuration.m_imageAsset = AZ::Data::AssetManager::Instance().FindOrCreateAsset(assetId, azrtti_typeid<AZ::RPI::StreamingImageAsset>(), m_configuration.m_imageAsset.GetAutoLoadBehavior());
|
||||
}
|
||||
|
||||
SetupDependencies();
|
||||
|
||||
@@ -15,84 +15,10 @@
|
||||
#include <AzCore/Debug/Profiler.h>
|
||||
|
||||
#include <Atom/ImageProcessing/PixelFormats.h>
|
||||
#include <Atom/RPI.Public/RPIUtils.h>
|
||||
#include <GradientSignal/Util.h>
|
||||
#include <numeric>
|
||||
|
||||
namespace
|
||||
{
|
||||
template <ImageProcessingAtom::EPixelFormat>
|
||||
float RetrieveValue(const AZ::u8* mem, size_t index)
|
||||
{
|
||||
AZ_Assert(false, "Unimplemented!");
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
template <>
|
||||
float RetrieveValue<ImageProcessingAtom::EPixelFormat::ePixelFormat_Unknown>([[maybe_unused]] const AZ::u8* mem, [[maybe_unused]] size_t index)
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
template <>
|
||||
float RetrieveValue<ImageProcessingAtom::EPixelFormat::ePixelFormat_R8>(const AZ::u8* mem, size_t index)
|
||||
{
|
||||
return mem[index] / static_cast<float>(std::numeric_limits<AZ::u8>::max());
|
||||
}
|
||||
|
||||
template <>
|
||||
float RetrieveValue<ImageProcessingAtom::EPixelFormat::ePixelFormat_R16>(const AZ::u8* mem, size_t index)
|
||||
{
|
||||
// 16 bits per channel
|
||||
auto actualMem = reinterpret_cast<const AZ::u16*>(mem);
|
||||
actualMem += index;
|
||||
|
||||
return *actualMem / static_cast<float>(std::numeric_limits<AZ::u16>::max());
|
||||
}
|
||||
|
||||
template <>
|
||||
float RetrieveValue<ImageProcessingAtom::EPixelFormat::ePixelFormat_R32>(const AZ::u8* mem, size_t index)
|
||||
{
|
||||
// 32 bits per channel
|
||||
auto actualMem = reinterpret_cast<const AZ::u32*>(mem);
|
||||
actualMem += index;
|
||||
|
||||
return *actualMem / static_cast<float>(std::numeric_limits<AZ::u32>::max());
|
||||
}
|
||||
|
||||
template <>
|
||||
float RetrieveValue<ImageProcessingAtom::EPixelFormat::ePixelFormat_R32F>(const AZ::u8* mem, size_t index)
|
||||
{
|
||||
// 32 bits per channel
|
||||
auto actualMem = reinterpret_cast<const float*>(mem);
|
||||
actualMem += index;
|
||||
|
||||
return *actualMem;
|
||||
}
|
||||
|
||||
float RetrieveValue(const AZ::u8* mem, size_t index, ImageProcessingAtom::EPixelFormat format)
|
||||
{
|
||||
using namespace ImageProcessingAtom;
|
||||
|
||||
switch (format)
|
||||
{
|
||||
case ePixelFormat_R8:
|
||||
return RetrieveValue<ePixelFormat_R8>(mem, index);
|
||||
|
||||
case ePixelFormat_R16:
|
||||
return RetrieveValue<ePixelFormat_R16>(mem, index);
|
||||
|
||||
case ePixelFormat_R32:
|
||||
return RetrieveValue<ePixelFormat_R32>(mem, index);
|
||||
|
||||
case ePixelFormat_R32F:
|
||||
return RetrieveValue<ePixelFormat_R32F>(mem, index);
|
||||
|
||||
default:
|
||||
return RetrieveValue<ePixelFormat_Unknown>(mem, index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace GradientSignal
|
||||
{
|
||||
void ImageAsset::Reflect(AZ::ReflectContext* context)
|
||||
@@ -152,17 +78,15 @@ namespace GradientSignal
|
||||
return true;
|
||||
}
|
||||
|
||||
float GetValueFromImageAsset(const AZ::Data::Asset<ImageAsset>& imageAsset, const AZ::Vector3& uvw, float tilingX, float tilingY, float defaultValue)
|
||||
float GetValueFromImageAsset(const AZ::Data::Asset<AZ::RPI::StreamingImageAsset>& imageAsset, const AZ::Vector3& uvw, float tilingX, float tilingY, float defaultValue)
|
||||
{
|
||||
if (imageAsset.IsReady())
|
||||
{
|
||||
const auto& image = imageAsset.Get();
|
||||
AZStd::size_t imageSize = image->m_imageWidth * image->m_imageHeight *
|
||||
static_cast<AZ::u32>(image->m_bytesPerPixel);
|
||||
|
||||
if (image->m_imageWidth > 0 &&
|
||||
image->m_imageHeight > 0 &&
|
||||
image->m_imageData.size() == imageSize)
|
||||
auto imageDescriptor = imageAsset->GetImageDescriptor();
|
||||
auto width = imageDescriptor.m_size.m_width;
|
||||
auto height = imageDescriptor.m_size.m_height;
|
||||
|
||||
if (width > 0 && height > 0)
|
||||
{
|
||||
// When "rasterizing" from uvs, a range of 0-1 has slightly different meanings depending on the sampler state.
|
||||
// For repeating states (Unbounded/None, Repeat), a uv value of 1 should wrap around back to our 0th pixel.
|
||||
@@ -185,8 +109,8 @@ namespace GradientSignal
|
||||
// A 16x16 pixel image and tilingX = tilingY = 1 maps the uv range of 0-1 to 0-16 pixels.
|
||||
// A 16x16 pixel image and tilingX = tilingY = 1.5 maps the uv range of 0-1 to 0-24 pixels.
|
||||
|
||||
const AZ::Vector3 tiledDimensions((image->m_imageWidth * tilingX),
|
||||
(image->m_imageHeight * tilingY),
|
||||
const AZ::Vector3 tiledDimensions((width * tilingX),
|
||||
(height * tilingY),
|
||||
0.0f);
|
||||
|
||||
// Convert from uv space back to pixel space
|
||||
@@ -195,13 +119,13 @@ namespace GradientSignal
|
||||
// UVs outside the 0-1 range are treated as infinitely tiling, so that we behave the same as the
|
||||
// other gradient generators. As mentioned above, if clamping is desired, we expect it to be applied
|
||||
// outside of this function.
|
||||
size_t x = static_cast<size_t>(pixelLookup.GetX()) % image->m_imageWidth;
|
||||
size_t y = static_cast<size_t>(pixelLookup.GetY()) % image->m_imageHeight;
|
||||
auto x = aznumeric_cast<AZ::u32>(pixelLookup.GetX()) % width;
|
||||
auto y = aznumeric_cast<AZ::u32>(pixelLookup.GetY()) % height;
|
||||
|
||||
// Flip the y because images are stored in reverse of our world axes
|
||||
size_t index = ((image->m_imageHeight - 1) - y) * image->m_imageWidth + x;
|
||||
y = (height - 1) - y;
|
||||
|
||||
return RetrieveValue(image->m_imageData.data(), index, image->m_imageFormat);
|
||||
return AZ::RPI::GetSubImagePixelValue<float>(imageAsset, x, y);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -140,27 +140,18 @@ namespace UnitTest
|
||||
|
||||
BENCHMARK_DEFINE_F(GradientGetValues, BM_SurfaceAltitudeGradient)(benchmark::State& state)
|
||||
{
|
||||
auto mockSurfaceDataSystem =
|
||||
CreateMockSurfaceDataSystem(AZ::Aabb::CreateFromMinMax(AZ::Vector3(-TestShapeHalfBounds), AZ::Vector3(TestShapeHalfBounds)));
|
||||
|
||||
auto entity = BuildTestSurfaceAltitudeGradient(TestShapeHalfBounds);
|
||||
GradientSignalTestHelpers::RunGetValueOrGetValuesBenchmark(state, entity->GetId());
|
||||
}
|
||||
|
||||
BENCHMARK_DEFINE_F(GradientGetValues, BM_SurfaceMaskGradient)(benchmark::State& state)
|
||||
{
|
||||
auto mockSurfaceDataSystem =
|
||||
CreateMockSurfaceDataSystem(AZ::Aabb::CreateFromMinMax(AZ::Vector3(-TestShapeHalfBounds), AZ::Vector3(TestShapeHalfBounds)));
|
||||
|
||||
auto entity = BuildTestSurfaceMaskGradient(TestShapeHalfBounds);
|
||||
GradientSignalTestHelpers::RunGetValueOrGetValuesBenchmark(state, entity->GetId());
|
||||
}
|
||||
|
||||
BENCHMARK_DEFINE_F(GradientGetValues, BM_SurfaceSlopeGradient)(benchmark::State& state)
|
||||
{
|
||||
auto mockSurfaceDataSystem =
|
||||
CreateMockSurfaceDataSystem(AZ::Aabb::CreateFromMinMax(AZ::Vector3(-TestShapeHalfBounds), AZ::Vector3(TestShapeHalfBounds)));
|
||||
|
||||
auto entity = BuildTestSurfaceSlopeGradient(TestShapeHalfBounds);
|
||||
GradientSignalTestHelpers::RunGetValueOrGetValuesBenchmark(state, entity->GetId());
|
||||
}
|
||||
|
||||
@@ -24,31 +24,33 @@ namespace UnitTest
|
||||
TEST_F(GradientSignalGetValuesTestsFixture, ImageGradientComponent_VerifyGetValueAndGetValuesMatch)
|
||||
{
|
||||
auto entity = BuildTestImageGradient(TestShapeHalfBounds);
|
||||
GradientSignalTestHelpers::CompareGetValueAndGetValues(entity->GetId(), TestShapeHalfBounds);
|
||||
GradientSignalTestHelpers::CompareGetValueAndGetValues(entity->GetId(), 0.0f, TestShapeHalfBounds * 2.0f);
|
||||
}
|
||||
|
||||
TEST_F(GradientSignalGetValuesTestsFixture, PerlinGradientComponent_VerifyGetValueAndGetValuesMatch)
|
||||
{
|
||||
auto entity = BuildTestPerlinGradient(TestShapeHalfBounds);
|
||||
GradientSignalTestHelpers::CompareGetValueAndGetValues(entity->GetId(), TestShapeHalfBounds);
|
||||
GradientSignalTestHelpers::CompareGetValueAndGetValues(entity->GetId(), 0.0f, TestShapeHalfBounds * 2.0f);
|
||||
}
|
||||
|
||||
TEST_F(GradientSignalGetValuesTestsFixture, RandomGradientComponent_VerifyGetValueAndGetValuesMatch)
|
||||
{
|
||||
auto entity = BuildTestRandomGradient(TestShapeHalfBounds);
|
||||
GradientSignalTestHelpers::CompareGetValueAndGetValues(entity->GetId(), TestShapeHalfBounds);
|
||||
GradientSignalTestHelpers::CompareGetValueAndGetValues(entity->GetId(), 0.0f, TestShapeHalfBounds * 2.0f);
|
||||
}
|
||||
|
||||
TEST_F(GradientSignalGetValuesTestsFixture, ConstantGradientComponent_VerifyGetValueAndGetValuesMatch)
|
||||
{
|
||||
auto entity = BuildTestConstantGradient(TestShapeHalfBounds);
|
||||
GradientSignalTestHelpers::CompareGetValueAndGetValues(entity->GetId(), TestShapeHalfBounds);
|
||||
GradientSignalTestHelpers::CompareGetValueAndGetValues(entity->GetId(), 0.0f, TestShapeHalfBounds * 2.0f);
|
||||
}
|
||||
|
||||
TEST_F(GradientSignalGetValuesTestsFixture, ShapeAreaFalloffGradientComponent_VerifyGetValueAndGetValuesMatch)
|
||||
{
|
||||
auto entity = BuildTestShapeAreaFalloffGradient(TestShapeHalfBounds);
|
||||
GradientSignalTestHelpers::CompareGetValueAndGetValues(entity->GetId(), TestShapeHalfBounds);
|
||||
|
||||
// Use a query range larger than our shape to ensure that we're getting falloff values within our query bounds.
|
||||
GradientSignalTestHelpers::CompareGetValueAndGetValues(entity->GetId(), -TestShapeHalfBounds, TestShapeHalfBounds * 3.0f);
|
||||
}
|
||||
|
||||
TEST_F(GradientSignalGetValuesTestsFixture, DitherGradientComponent_VerifyGetValueAndGetValuesMatch)
|
||||
@@ -56,21 +58,21 @@ namespace UnitTest
|
||||
auto baseEntity = BuildTestRandomGradient(TestShapeHalfBounds);
|
||||
|
||||
auto entity = BuildTestDitherGradient(TestShapeHalfBounds, baseEntity->GetId());
|
||||
GradientSignalTestHelpers::CompareGetValueAndGetValues(entity->GetId(), TestShapeHalfBounds);
|
||||
GradientSignalTestHelpers::CompareGetValueAndGetValues(entity->GetId(), 0.0f, TestShapeHalfBounds * 2.0f);
|
||||
}
|
||||
|
||||
TEST_F(GradientSignalGetValuesTestsFixture, InvertGradientComponent_VerifyGetValueAndGetValuesMatch)
|
||||
{
|
||||
auto baseEntity = BuildTestRandomGradient(TestShapeHalfBounds);
|
||||
auto entity = BuildTestInvertGradient(TestShapeHalfBounds, baseEntity->GetId());
|
||||
GradientSignalTestHelpers::CompareGetValueAndGetValues(entity->GetId(), TestShapeHalfBounds);
|
||||
GradientSignalTestHelpers::CompareGetValueAndGetValues(entity->GetId(), 0.0f, TestShapeHalfBounds * 2.0f);
|
||||
}
|
||||
|
||||
TEST_F(GradientSignalGetValuesTestsFixture, LevelsGradientComponent_VerifyGetValueAndGetValuesMatch)
|
||||
{
|
||||
auto baseEntity = BuildTestRandomGradient(TestShapeHalfBounds);
|
||||
auto entity = BuildTestLevelsGradient(TestShapeHalfBounds, baseEntity->GetId());
|
||||
GradientSignalTestHelpers::CompareGetValueAndGetValues(entity->GetId(), TestShapeHalfBounds);
|
||||
GradientSignalTestHelpers::CompareGetValueAndGetValues(entity->GetId(), 0.0f, TestShapeHalfBounds * 2.0f);
|
||||
}
|
||||
|
||||
TEST_F(GradientSignalGetValuesTestsFixture, MixedGradientComponent_VerifyGetValueAndGetValuesMatch)
|
||||
@@ -78,62 +80,53 @@ namespace UnitTest
|
||||
auto baseEntity = BuildTestRandomGradient(TestShapeHalfBounds);
|
||||
auto mixedEntity = BuildTestConstantGradient(TestShapeHalfBounds);
|
||||
auto entity = BuildTestMixedGradient(TestShapeHalfBounds, baseEntity->GetId(), mixedEntity->GetId());
|
||||
GradientSignalTestHelpers::CompareGetValueAndGetValues(entity->GetId(), TestShapeHalfBounds);
|
||||
GradientSignalTestHelpers::CompareGetValueAndGetValues(entity->GetId(), 0.0f, TestShapeHalfBounds * 2.0f);
|
||||
}
|
||||
|
||||
TEST_F(GradientSignalGetValuesTestsFixture, PosterizeGradientComponent_VerifyGetValueAndGetValuesMatch)
|
||||
{
|
||||
auto baseEntity = BuildTestRandomGradient(TestShapeHalfBounds);
|
||||
auto entity = BuildTestPosterizeGradient(TestShapeHalfBounds, baseEntity->GetId());
|
||||
GradientSignalTestHelpers::CompareGetValueAndGetValues(entity->GetId(), TestShapeHalfBounds);
|
||||
GradientSignalTestHelpers::CompareGetValueAndGetValues(entity->GetId(), 0.0f, TestShapeHalfBounds * 2.0f);
|
||||
}
|
||||
|
||||
TEST_F(GradientSignalGetValuesTestsFixture, ReferenceGradientComponent_VerifyGetValueAndGetValuesMatch)
|
||||
{
|
||||
auto baseEntity = BuildTestRandomGradient(TestShapeHalfBounds);
|
||||
auto entity = BuildTestReferenceGradient(TestShapeHalfBounds, baseEntity->GetId());
|
||||
GradientSignalTestHelpers::CompareGetValueAndGetValues(entity->GetId(), TestShapeHalfBounds);
|
||||
GradientSignalTestHelpers::CompareGetValueAndGetValues(entity->GetId(), 0.0f, TestShapeHalfBounds * 2.0f);
|
||||
}
|
||||
|
||||
TEST_F(GradientSignalGetValuesTestsFixture, SmoothStepGradientComponent_VerifyGetValueAndGetValuesMatch)
|
||||
{
|
||||
auto baseEntity = BuildTestRandomGradient(TestShapeHalfBounds);
|
||||
auto entity = BuildTestSmoothStepGradient(TestShapeHalfBounds, baseEntity->GetId());
|
||||
GradientSignalTestHelpers::CompareGetValueAndGetValues(entity->GetId(), TestShapeHalfBounds);
|
||||
GradientSignalTestHelpers::CompareGetValueAndGetValues(entity->GetId(), 0.0f, TestShapeHalfBounds * 2.0f);
|
||||
}
|
||||
|
||||
TEST_F(GradientSignalGetValuesTestsFixture, ThresholdGradientComponent_VerifyGetValueAndGetValuesMatch)
|
||||
{
|
||||
auto baseEntity = BuildTestRandomGradient(TestShapeHalfBounds);
|
||||
auto entity = BuildTestThresholdGradient(TestShapeHalfBounds, baseEntity->GetId());
|
||||
GradientSignalTestHelpers::CompareGetValueAndGetValues(entity->GetId(), TestShapeHalfBounds);
|
||||
GradientSignalTestHelpers::CompareGetValueAndGetValues(entity->GetId(), 0.0f, TestShapeHalfBounds * 2.0f);
|
||||
}
|
||||
|
||||
TEST_F(GradientSignalGetValuesTestsFixture, SurfaceAltitudeGradientComponent_VerifyGetValueAndGetValuesMatch)
|
||||
{
|
||||
auto mockSurfaceDataSystem =
|
||||
CreateMockSurfaceDataSystem(AZ::Aabb::CreateFromMinMax(AZ::Vector3(-TestShapeHalfBounds), AZ::Vector3(TestShapeHalfBounds)));
|
||||
|
||||
auto entity = BuildTestSurfaceAltitudeGradient(TestShapeHalfBounds);
|
||||
GradientSignalTestHelpers::CompareGetValueAndGetValues(entity->GetId(), TestShapeHalfBounds);
|
||||
GradientSignalTestHelpers::CompareGetValueAndGetValues(entity->GetId(), 0.0f, TestShapeHalfBounds * 2.0f);
|
||||
}
|
||||
|
||||
TEST_F(GradientSignalGetValuesTestsFixture, SurfaceMaskGradientComponent_VerifyGetValueAndGetValuesMatch)
|
||||
{
|
||||
auto mockSurfaceDataSystem =
|
||||
CreateMockSurfaceDataSystem(AZ::Aabb::CreateFromMinMax(AZ::Vector3(-TestShapeHalfBounds), AZ::Vector3(TestShapeHalfBounds)));
|
||||
|
||||
auto entity = BuildTestSurfaceMaskGradient(TestShapeHalfBounds);
|
||||
GradientSignalTestHelpers::CompareGetValueAndGetValues(entity->GetId(), TestShapeHalfBounds);
|
||||
GradientSignalTestHelpers::CompareGetValueAndGetValues(entity->GetId(), 0.0f, TestShapeHalfBounds * 2.0f);
|
||||
}
|
||||
|
||||
TEST_F(GradientSignalGetValuesTestsFixture, SurfaceSlopeGradientComponent_VerifyGetValueAndGetValuesMatch)
|
||||
{
|
||||
auto mockSurfaceDataSystem =
|
||||
CreateMockSurfaceDataSystem(AZ::Aabb::CreateFromMinMax(AZ::Vector3(-TestShapeHalfBounds), AZ::Vector3(TestShapeHalfBounds)));
|
||||
|
||||
auto entity = BuildTestSurfaceSlopeGradient(TestShapeHalfBounds);
|
||||
GradientSignalTestHelpers::CompareGetValueAndGetValues(entity->GetId(), TestShapeHalfBounds);
|
||||
GradientSignalTestHelpers::CompareGetValueAndGetValues(entity->GetId(), 0.0f, TestShapeHalfBounds * 2.0f);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
|
||||
#include <Tests/GradientSignalTestFixtures.h>
|
||||
#include <Tests/GradientSignalTestHelpers.h>
|
||||
|
||||
#include <AzTest/AzTest.h>
|
||||
#include <AzCore/Asset/AssetManager.h>
|
||||
@@ -88,7 +89,7 @@ namespace UnitTest
|
||||
|
||||
// Create the Image Gradient Component.
|
||||
GradientSignal::ImageGradientConfig config;
|
||||
config.m_imageAsset = ImageAssetMockAssetHandler::CreateSpecificPixelImageAsset(
|
||||
config.m_imageAsset = UnitTest::CreateSpecificPixelImageAsset(
|
||||
test.m_imageSize, test.m_imageSize, static_cast<AZ::u32>(test.m_pixel.GetX()), static_cast<AZ::u32>(test.m_pixel.GetY()));
|
||||
config.m_tilingX = test.m_tiling;
|
||||
config.m_tilingY = test.m_tiling;
|
||||
@@ -379,7 +380,7 @@ namespace UnitTest
|
||||
|
||||
// Create an ImageGradient with a 3x3 asset with the center pixel set.
|
||||
GradientSignal::ImageGradientConfig gradientConfig;
|
||||
gradientConfig.m_imageAsset = ImageAssetMockAssetHandler::CreateSpecificPixelImageAsset(3, 3, 1, 1);
|
||||
gradientConfig.m_imageAsset = UnitTest::CreateSpecificPixelImageAsset(3, 3, 1, 1);
|
||||
entity->CreateComponent<GradientSignal::ImageGradientComponent>(gradientConfig);
|
||||
|
||||
// Create the test GradientTransform
|
||||
|
||||
@@ -65,7 +65,11 @@ namespace UnitTest
|
||||
float slopeMin, float slopeMax, GradientSignal::SurfaceSlopeGradientConfig::RampType rampType,
|
||||
float falloffMidpoint, float falloffRange, float falloffStrength)
|
||||
{
|
||||
MockSurfaceDataSystem mockSurfaceDataSystem;
|
||||
auto surfaceEntity = CreateEntity();
|
||||
auto mockSurface = surfaceEntity->CreateComponent<MockSurfaceProviderComponent>();
|
||||
mockSurface->m_bounds = AZ::Aabb::CreateFromMinMax(AZ::Vector3(0.0f), AZ::Vector3(aznumeric_cast<float>(dataSize)));
|
||||
mockSurface->m_tags.emplace_back("test_mask");
|
||||
|
||||
AzFramework::SurfaceData::SurfacePoint point;
|
||||
|
||||
// Fill our mock surface with the correct normal value for each point based on our test angle set.
|
||||
@@ -75,9 +79,10 @@ namespace UnitTest
|
||||
{
|
||||
float angle = AZ::DegToRad(inputAngles[(y * dataSize) + x]);
|
||||
point.m_normal = AZ::Vector3(sinf(angle), 0.0f, cosf(angle));
|
||||
mockSurfaceDataSystem.m_GetSurfacePoints[AZStd::make_pair(static_cast<float>(x), static_cast<float>(y))] = { { point } };
|
||||
mockSurface->m_surfacePoints[AZStd::make_pair(static_cast<float>(x), static_cast<float>(y))] = { { point } };
|
||||
}
|
||||
}
|
||||
ActivateEntity(surfaceEntity.get());
|
||||
|
||||
GradientSignal::SurfaceSlopeGradientConfig config;
|
||||
config.m_slopeMin = slopeMin;
|
||||
@@ -538,11 +543,14 @@ namespace UnitTest
|
||||
mockShapeComponentHandler.m_GetEncompassingAabb = AZ::Aabb::CreateFromMinMax(AZ::Vector3::CreateZero(), AZ::Vector3(10.0f));
|
||||
|
||||
// Set a different altitude for each point we're going to test. We'll use 0, 2, 5, 10 to test various points along the range.
|
||||
MockSurfaceDataSystem mockSurfaceDataSystem;
|
||||
mockSurfaceDataSystem.m_GetSurfacePoints[AZStd::make_pair(0.0f, 0.0f)] = { { AZ::Vector3(0.0f, 0.0f, 0.0f), AZ::Vector3::CreateZero() } };
|
||||
mockSurfaceDataSystem.m_GetSurfacePoints[AZStd::make_pair(1.0f, 0.0f)] = { { AZ::Vector3(0.0f, 0.0f, 2.0f), AZ::Vector3::CreateZero() } };
|
||||
mockSurfaceDataSystem.m_GetSurfacePoints[AZStd::make_pair(0.0f, 1.0f)] = { { AZ::Vector3(0.0f, 0.0f, 5.0f), AZ::Vector3::CreateZero() } };
|
||||
mockSurfaceDataSystem.m_GetSurfacePoints[AZStd::make_pair(1.0f, 1.0f)] = { { AZ::Vector3(0.0f, 0.0f, 10.0f), AZ::Vector3::CreateZero() } };
|
||||
auto surfaceEntity = CreateEntity();
|
||||
auto mockSurface = surfaceEntity->CreateComponent<MockSurfaceProviderComponent>();
|
||||
mockSurface->m_bounds = mockShapeComponentHandler.m_GetEncompassingAabb;
|
||||
mockSurface->m_surfacePoints[AZStd::make_pair(0.0f, 0.0f)] = { { AZ::Vector3(0.0f, 0.0f, 0.0f), AZ::Vector3::CreateZero() } };
|
||||
mockSurface->m_surfacePoints[AZStd::make_pair(1.0f, 0.0f)] = { { AZ::Vector3(0.0f, 0.0f, 2.0f), AZ::Vector3::CreateZero() } };
|
||||
mockSurface->m_surfacePoints[AZStd::make_pair(0.0f, 1.0f)] = { { AZ::Vector3(0.0f, 0.0f, 5.0f), AZ::Vector3::CreateZero() } };
|
||||
mockSurface->m_surfacePoints[AZStd::make_pair(1.0f, 1.0f)] = { { AZ::Vector3(0.0f, 0.0f, 10.0f), AZ::Vector3::CreateZero() } };
|
||||
ActivateEntity(surfaceEntity.get());
|
||||
|
||||
// We set the min/max to values other than 0-10 to help validate that they aren't used in the case of the pinned shape.
|
||||
GradientSignal::SurfaceAltitudeGradientConfig config;
|
||||
@@ -572,11 +580,14 @@ namespace UnitTest
|
||||
auto entityShape = CreateEntity();
|
||||
|
||||
// Set a different altitude for each point we're going to test. We'll use 0, 2, 5, 10 to test various points along the range.
|
||||
MockSurfaceDataSystem mockSurfaceDataSystem;
|
||||
mockSurfaceDataSystem.m_GetSurfacePoints[AZStd::make_pair(0.0f, 0.0f)] = { { AZ::Vector3(0.0f, 0.0f, 0.0f), AZ::Vector3::CreateZero() } };
|
||||
mockSurfaceDataSystem.m_GetSurfacePoints[AZStd::make_pair(1.0f, 0.0f)] = { { AZ::Vector3(0.0f, 0.0f, 2.0f), AZ::Vector3::CreateZero() } };
|
||||
mockSurfaceDataSystem.m_GetSurfacePoints[AZStd::make_pair(0.0f, 1.0f)] = { { AZ::Vector3(0.0f, 0.0f, 5.0f), AZ::Vector3::CreateZero() } };
|
||||
mockSurfaceDataSystem.m_GetSurfacePoints[AZStd::make_pair(1.0f, 1.0f)] = { { AZ::Vector3(0.0f, 0.0f, 10.0f), AZ::Vector3::CreateZero() } };
|
||||
auto surfaceEntity = CreateEntity();
|
||||
auto mockSurface = surfaceEntity->CreateComponent<MockSurfaceProviderComponent>();
|
||||
mockSurface->m_bounds = AZ::Aabb::CreateFromMinMax(AZ::Vector3(0.0f), AZ::Vector3(1.0f));
|
||||
mockSurface->m_surfacePoints[AZStd::make_pair(0.0f, 0.0f)] = { { AZ::Vector3(0.0f, 0.0f, 0.0f), AZ::Vector3::CreateZero() } };
|
||||
mockSurface->m_surfacePoints[AZStd::make_pair(1.0f, 0.0f)] = { { AZ::Vector3(0.0f, 0.0f, 2.0f), AZ::Vector3::CreateZero() } };
|
||||
mockSurface->m_surfacePoints[AZStd::make_pair(0.0f, 1.0f)] = { { AZ::Vector3(0.0f, 0.0f, 5.0f), AZ::Vector3::CreateZero() } };
|
||||
mockSurface->m_surfacePoints[AZStd::make_pair(1.0f, 1.0f)] = { { AZ::Vector3(0.0f, 0.0f, 10.0f), AZ::Vector3::CreateZero() } };
|
||||
ActivateEntity(surfaceEntity.get());
|
||||
|
||||
// We set the min/max to 0-10, but don't set a shape.
|
||||
GradientSignal::SurfaceAltitudeGradientConfig config;
|
||||
@@ -603,9 +614,6 @@ namespace UnitTest
|
||||
|
||||
auto entityShape = CreateEntity();
|
||||
|
||||
// Don't set any points.
|
||||
MockSurfaceDataSystem mockSurfaceDataSystem;
|
||||
|
||||
// We set the min/max to -5 - 15 so that a height of 0 would produce a non-zero value.
|
||||
GradientSignal::SurfaceAltitudeGradientConfig config;
|
||||
config.m_altitudeMin = -5.0f;
|
||||
@@ -631,16 +639,18 @@ namespace UnitTest
|
||||
|
||||
auto entityShape = CreateEntity();
|
||||
|
||||
MockSurfaceDataSystem mockSurfaceDataSystem;
|
||||
|
||||
auto surfaceEntity = CreateEntity();
|
||||
auto mockSurface = surfaceEntity->CreateComponent<MockSurfaceProviderComponent>();
|
||||
mockSurface->m_bounds = AZ::Aabb::CreateFromMinMax(AZ::Vector3(0.0f), AZ::Vector3(1.0f));
|
||||
// Altitude value below min - should result in 0.0f.
|
||||
mockSurfaceDataSystem.m_GetSurfacePoints[AZStd::make_pair(0.0f, 0.0f)] = { { AZ::Vector3(0.0f, 0.0f, -10.0f), AZ::Vector3::CreateZero() } };
|
||||
mockSurface->m_surfacePoints[AZStd::make_pair(0.0f, 0.0f)] = { { AZ::Vector3(0.0f, 0.0f, -10.0f), AZ::Vector3::CreateZero() } };
|
||||
// Altitude value at exactly min - should result in 0.0f.
|
||||
mockSurfaceDataSystem.m_GetSurfacePoints[AZStd::make_pair(1.0f, 0.0f)] = { { AZ::Vector3(0.0f, 0.0f, -5.0f), AZ::Vector3::CreateZero() } };
|
||||
mockSurface->m_surfacePoints[AZStd::make_pair(1.0f, 0.0f)] = { { AZ::Vector3(0.0f, 0.0f, -5.0f), AZ::Vector3::CreateZero() } };
|
||||
// Altitude value at exactly max - should result in 1.0f.
|
||||
mockSurfaceDataSystem.m_GetSurfacePoints[AZStd::make_pair(0.0f, 1.0f)] = { { AZ::Vector3(0.0f, 0.0f, 15.0f), AZ::Vector3::CreateZero() } };
|
||||
mockSurface->m_surfacePoints[AZStd::make_pair(0.0f, 1.0f)] = { { AZ::Vector3(0.0f, 0.0f, 15.0f), AZ::Vector3::CreateZero() } };
|
||||
// Altitude value above max - should result in 1.0f.
|
||||
mockSurfaceDataSystem.m_GetSurfacePoints[AZStd::make_pair(1.0f, 1.0f)] = { { AZ::Vector3(0.0f, 0.0f, 20.0f), AZ::Vector3::CreateZero() } };
|
||||
mockSurface->m_surfacePoints[AZStd::make_pair(1.0f, 1.0f)] = { { AZ::Vector3(0.0f, 0.0f, 20.0f), AZ::Vector3::CreateZero() } };
|
||||
ActivateEntity(surfaceEntity.get());
|
||||
|
||||
// We set the min/max to -5 - 15. By using a range without 0 at either end, and not having 0 as the midpoint,
|
||||
// it should be easier to verify that we're successfully clamping to 0 and 1.
|
||||
@@ -667,7 +677,11 @@ namespace UnitTest
|
||||
0.5f, 1.0f,
|
||||
};
|
||||
|
||||
MockSurfaceDataSystem mockSurfaceDataSystem;
|
||||
auto surfaceEntity = CreateEntity();
|
||||
auto mockSurface = surfaceEntity->CreateComponent<MockSurfaceProviderComponent>();
|
||||
mockSurface->m_bounds = AZ::Aabb::CreateFromMinMax(AZ::Vector3(0.0f), AZ::Vector3(aznumeric_cast<float>(dataSize)));
|
||||
mockSurface->m_tags.emplace_back("test_mask");
|
||||
|
||||
AzFramework::SurfaceData::SurfacePoint point;
|
||||
|
||||
// Fill our mock surface with the test_mask set and the expected gradient value at each point.
|
||||
@@ -677,9 +691,10 @@ namespace UnitTest
|
||||
{
|
||||
point.m_surfaceTags.clear();
|
||||
point.m_surfaceTags.emplace_back(AZ_CRC_CE("test_mask"), expectedOutput[(y * dataSize) + x]);
|
||||
mockSurfaceDataSystem.m_GetSurfacePoints[AZStd::make_pair(static_cast<float>(x), static_cast<float>(y))] = { { point } };
|
||||
mockSurface->m_surfacePoints[AZStd::make_pair(static_cast<float>(x), static_cast<float>(y))] = { { point } };
|
||||
}
|
||||
}
|
||||
ActivateEntity(surfaceEntity.get());
|
||||
|
||||
GradientSignal::SurfaceMaskGradientConfig config;
|
||||
config.m_surfaceTagList.push_back(AZ_CRC("test_mask", 0x7a16e9ff));
|
||||
@@ -706,8 +721,6 @@ namespace UnitTest
|
||||
0.0f, 0.0f,
|
||||
};
|
||||
|
||||
MockSurfaceDataSystem mockSurfaceDataSystem;
|
||||
|
||||
GradientSignal::SurfaceMaskGradientConfig config;
|
||||
config.m_surfaceTagList.push_back(AZ_CRC("test_mask", 0x7a16e9ff));
|
||||
|
||||
|
||||
@@ -67,9 +67,6 @@ namespace UnitTest
|
||||
const AzFramework::SurfaceData::SurfacePoint& input,
|
||||
const AzFramework::SurfaceData::SurfacePoint& expectedOutput)
|
||||
{
|
||||
// This lets our component register with surfaceData successfully.
|
||||
MockSurfaceDataSystem mockSurfaceDataSystem;
|
||||
|
||||
// Create a mock shape entity in case our gradient test uses shape constraints.
|
||||
// The mock shape is a cube that goes from -0.5 to 0.5 in space.
|
||||
auto mockShapeEntity = CreateTestEntity(0.5f);
|
||||
@@ -105,7 +102,9 @@ namespace UnitTest
|
||||
ActivateEntity(entity.get());
|
||||
|
||||
// Get our registered modifier handle (and verify that it's valid)
|
||||
auto modifierHandle = mockSurfaceDataSystem.GetSurfaceModifierHandle(entity->GetId());
|
||||
SurfaceData::SurfaceDataRegistryHandle modifierHandle = SurfaceData::InvalidSurfaceDataRegistryHandle;
|
||||
SurfaceData::SurfaceDataSystemRequestBus::BroadcastResult(
|
||||
modifierHandle, &SurfaceData::SurfaceDataSystemRequestBus::Events::GetSurfaceDataModifierHandle, entity->GetId());
|
||||
EXPECT_TRUE(modifierHandle != SurfaceData::InvalidSurfaceDataRegistryHandle);
|
||||
|
||||
// Call ModifySurfacePoints and verify the results
|
||||
|
||||
@@ -8,11 +8,17 @@
|
||||
|
||||
|
||||
#include <Tests/GradientSignalTestFixtures.h>
|
||||
#include <Tests/GradientSignalTestHelpers.h>
|
||||
|
||||
#include <Atom/RPI.Reflect/Image/ImageMipChainAsset.h>
|
||||
#include <Atom/RPI.Reflect/Image/StreamingImageAssetHandler.h>
|
||||
#include <AzFramework/Components/TransformComponent.h>
|
||||
#include <GradientSignal/Components/GradientSurfaceDataComponent.h>
|
||||
#include <GradientSignal/Components/GradientTransformComponent.h>
|
||||
#include <LmbrCentral/Shape/BoxShapeComponentBus.h>
|
||||
#include <LmbrCentral/Shape/SphereShapeComponentBus.h>
|
||||
#include <SurfaceData/Components/SurfaceDataShapeComponent.h>
|
||||
#include <SurfaceData/Components/SurfaceDataSystemComponent.h>
|
||||
|
||||
// Base gradient components
|
||||
#include <GradientSignal/Components/ConstantGradientComponent.h>
|
||||
@@ -40,7 +46,7 @@ namespace UnitTest
|
||||
{
|
||||
void GradientSignalTestEnvironment::AddGemsAndComponents()
|
||||
{
|
||||
AddDynamicModulePaths({ "LmbrCentral" });
|
||||
AddDynamicModulePaths({ "LmbrCentral", "SurfaceData" });
|
||||
|
||||
AddComponentDescriptors({
|
||||
AzFramework::TransformComponent::CreateDescriptor(),
|
||||
@@ -65,52 +71,26 @@ namespace UnitTest
|
||||
GradientSignal::ThresholdGradientComponent::CreateDescriptor(),
|
||||
|
||||
MockShapeComponent::CreateDescriptor(),
|
||||
MockSurfaceProviderComponent::CreateDescriptor(),
|
||||
});
|
||||
}
|
||||
|
||||
void GradientSignalBaseFixture::SetupCoreSystems()
|
||||
{
|
||||
m_mockHandler = new UnitTest::ImageAssetMockAssetHandler();
|
||||
AZ::Data::AssetManager::Instance().RegisterHandler(m_mockHandler, azrtti_typeid<GradientSignal::ImageAsset>());
|
||||
// Using the AZ::RPI::MakeAssetHandler will both create the asset handlers,
|
||||
// and register them with the AssetManager
|
||||
m_assetHandlers.emplace_back(AZ::RPI::MakeAssetHandler<AZ::RPI::ImageMipChainAssetHandler>());
|
||||
m_assetHandlers.emplace_back(AZ::RPI::MakeAssetHandler<AZ::RPI::StreamingImageAssetHandler>());
|
||||
}
|
||||
|
||||
void GradientSignalBaseFixture::TearDownCoreSystems()
|
||||
{
|
||||
AZ::Data::AssetManager::Instance().UnregisterHandler(m_mockHandler);
|
||||
delete m_mockHandler; // delete after removing from the asset manager
|
||||
// This will delete the asset handlers, which will unregister themselves on deletion
|
||||
m_assetHandlers.clear();
|
||||
|
||||
AzFramework::LegacyAssetEventBus::ClearQueuedEvents();
|
||||
}
|
||||
|
||||
AZStd::unique_ptr<MockSurfaceDataSystem> GradientSignalBaseFixture::CreateMockSurfaceDataSystem(const AZ::Aabb& spawnerBox)
|
||||
{
|
||||
AzFramework::SurfaceData::SurfacePoint point;
|
||||
AZStd::unique_ptr<MockSurfaceDataSystem> mockSurfaceDataSystem = AZStd::make_unique<MockSurfaceDataSystem>();
|
||||
|
||||
// Give the mock surface data a bunch of fake point values to return.
|
||||
for (float y = spawnerBox.GetMin().GetY(); y < spawnerBox.GetMax().GetY(); y+= 1.0f)
|
||||
{
|
||||
for (float x = spawnerBox.GetMin().GetX(); x < spawnerBox.GetMax().GetX(); x += 1.0f)
|
||||
{
|
||||
// Use our x distance into the spawnerBox as an arbitrary percentage value that we'll use to calculate
|
||||
// our other arbitrary values below.
|
||||
float arbitraryPercentage = AZStd::abs(x / spawnerBox.GetExtents().GetX());
|
||||
|
||||
// Create a position that's between min and max Z of the box.
|
||||
point.m_position = AZ::Vector3(x, y, AZ::Lerp(spawnerBox.GetMin().GetZ(), spawnerBox.GetMax().GetZ(), arbitraryPercentage));
|
||||
// Create an arbitrary normal value.
|
||||
point.m_normal = point.m_position.GetNormalized();
|
||||
// Create an arbitrary surface value.
|
||||
point.m_surfaceTags.clear();
|
||||
point.m_surfaceTags.emplace_back(AZ_CRC_CE("test_mask"), arbitraryPercentage);
|
||||
|
||||
mockSurfaceDataSystem->m_GetSurfacePoints[AZStd::make_pair(x, y)] = { { point } };
|
||||
}
|
||||
}
|
||||
|
||||
return mockSurfaceDataSystem;
|
||||
}
|
||||
|
||||
AZStd::unique_ptr<AZ::Entity> GradientSignalBaseFixture::CreateTestEntity(float shapeHalfBounds)
|
||||
{
|
||||
// Create the base entity
|
||||
@@ -120,7 +100,7 @@ namespace UnitTest
|
||||
auto boxComponent = testEntity->CreateComponent(LmbrCentral::AxisAlignedBoxShapeComponentTypeId);
|
||||
boxComponent->SetConfiguration(boxConfig);
|
||||
|
||||
// Create a transform that locates our gradient in the center of our desired mock Shape.
|
||||
// Create a transform that locates our gradient in the center of our desired Shape.
|
||||
auto transform = testEntity->CreateComponent<AzFramework::TransformComponent>();
|
||||
transform->SetLocalTM(AZ::Transform::CreateTranslation(AZ::Vector3(shapeHalfBounds)));
|
||||
transform->SetWorldTM(AZ::Transform::CreateTranslation(AZ::Vector3(shapeHalfBounds)));
|
||||
@@ -128,6 +108,23 @@ namespace UnitTest
|
||||
return testEntity;
|
||||
}
|
||||
|
||||
AZStd::unique_ptr<AZ::Entity> GradientSignalBaseFixture::CreateTestSphereEntity(float shapeRadius)
|
||||
{
|
||||
// Create the base entity
|
||||
AZStd::unique_ptr<AZ::Entity> testEntity = CreateEntity();
|
||||
|
||||
LmbrCentral::SphereShapeConfig sphereConfig(shapeRadius);
|
||||
auto sphereComponent = testEntity->CreateComponent(LmbrCentral::SphereShapeComponentTypeId);
|
||||
sphereComponent->SetConfiguration(sphereConfig);
|
||||
|
||||
// Create a transform that locates our gradient in the center of our desired Shape.
|
||||
auto transform = testEntity->CreateComponent<AzFramework::TransformComponent>();
|
||||
transform->SetLocalTM(AZ::Transform::CreateTranslation(AZ::Vector3(shapeRadius)));
|
||||
transform->SetWorldTM(AZ::Transform::CreateTranslation(AZ::Vector3(shapeRadius)));
|
||||
|
||||
return testEntity;
|
||||
}
|
||||
|
||||
AZStd::unique_ptr<AZ::Entity> GradientSignalBaseFixture::BuildTestConstantGradient(float shapeHalfBounds)
|
||||
{
|
||||
// Create a Constant Gradient Component with arbitrary parameters.
|
||||
@@ -147,7 +144,7 @@ namespace UnitTest
|
||||
GradientSignal::ImageGradientConfig config;
|
||||
const uint32_t imageSize = 4096;
|
||||
const int32_t imageSeed = 12345;
|
||||
config.m_imageAsset = ImageAssetMockAssetHandler::CreateImageAsset(imageSize, imageSize, imageSeed);
|
||||
config.m_imageAsset = UnitTest::CreateImageAsset(imageSize, imageSize, imageSeed);
|
||||
config.m_tilingX = 1.0f;
|
||||
config.m_tilingY = 1.0f;
|
||||
entity->CreateComponent<GradientSignal::ImageGradientComponent>(config);
|
||||
@@ -349,12 +346,18 @@ namespace UnitTest
|
||||
AZStd::unique_ptr<AZ::Entity> GradientSignalBaseFixture::BuildTestSurfaceAltitudeGradient(float shapeHalfBounds)
|
||||
{
|
||||
// Create a Surface Altitude Gradient Component with arbitrary parameters.
|
||||
auto entity = CreateTestEntity(shapeHalfBounds);
|
||||
auto entity = CreateTestSphereEntity(shapeHalfBounds);
|
||||
GradientSignal::SurfaceAltitudeGradientConfig config;
|
||||
config.m_altitudeMin = -5.0f;
|
||||
config.m_altitudeMax = 15.0f;
|
||||
config.m_altitudeMax = 15.0f + (shapeHalfBounds * 2.0f);
|
||||
entity->CreateComponent<GradientSignal::SurfaceAltitudeGradientComponent>(config);
|
||||
|
||||
// Create a SurfaceDataShape component to provide surface points from this component.
|
||||
SurfaceData::SurfaceDataShapeConfig shapeConfig;
|
||||
shapeConfig.m_providerTags.emplace_back("test_mask");
|
||||
auto surfaceShapeComponent = entity->CreateComponent(azrtti_typeid<SurfaceData::SurfaceDataShapeComponent>());
|
||||
surfaceShapeComponent->SetConfiguration(shapeConfig);
|
||||
|
||||
ActivateEntity(entity.get());
|
||||
return entity;
|
||||
}
|
||||
@@ -362,11 +365,17 @@ namespace UnitTest
|
||||
AZStd::unique_ptr<AZ::Entity> GradientSignalBaseFixture::BuildTestSurfaceMaskGradient(float shapeHalfBounds)
|
||||
{
|
||||
// Create a Surface Mask Gradient Component with arbitrary parameters.
|
||||
auto entity = CreateTestEntity(shapeHalfBounds);
|
||||
auto entity = CreateTestSphereEntity(shapeHalfBounds);
|
||||
GradientSignal::SurfaceMaskGradientConfig config;
|
||||
config.m_surfaceTagList.push_back(AZ_CRC_CE("test_mask"));
|
||||
entity->CreateComponent<GradientSignal::SurfaceMaskGradientComponent>(config);
|
||||
|
||||
// Create a SurfaceDataShape component to provide surface points from this component.
|
||||
SurfaceData::SurfaceDataShapeConfig shapeConfig;
|
||||
shapeConfig.m_providerTags.emplace_back("test_mask");
|
||||
auto surfaceShapeComponent = entity->CreateComponent(azrtti_typeid<SurfaceData::SurfaceDataShapeComponent>());
|
||||
surfaceShapeComponent->SetConfiguration(shapeConfig);
|
||||
|
||||
ActivateEntity(entity.get());
|
||||
return entity;
|
||||
}
|
||||
@@ -374,7 +383,7 @@ namespace UnitTest
|
||||
AZStd::unique_ptr<AZ::Entity> GradientSignalBaseFixture::BuildTestSurfaceSlopeGradient(float shapeHalfBounds)
|
||||
{
|
||||
// Create a Surface Slope Gradient Component with arbitrary parameters.
|
||||
auto entity = CreateTestEntity(shapeHalfBounds);
|
||||
auto entity = CreateTestSphereEntity(shapeHalfBounds);
|
||||
GradientSignal::SurfaceSlopeGradientConfig config;
|
||||
config.m_slopeMin = 5.0f;
|
||||
config.m_slopeMax = 50.0f;
|
||||
@@ -384,6 +393,12 @@ namespace UnitTest
|
||||
config.m_smoothStep.m_falloffStrength = 0.25f;
|
||||
entity->CreateComponent<GradientSignal::SurfaceSlopeGradientComponent>(config);
|
||||
|
||||
// Create a SurfaceDataShape component to provide surface points from this component.
|
||||
SurfaceData::SurfaceDataShapeConfig shapeConfig;
|
||||
shapeConfig.m_providerTags.emplace_back("test_mask");
|
||||
auto surfaceShapeComponent = entity->CreateComponent(azrtti_typeid<SurfaceData::SurfaceDataShapeComponent>());
|
||||
surfaceShapeComponent->SetConfiguration(shapeConfig);
|
||||
|
||||
ActivateEntity(entity.get());
|
||||
return entity;
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
#include <Tests/GradientSignalTestMocks.h>
|
||||
#include <LmbrCentral/Shape/MockShapes.h>
|
||||
#include <Atom/RPI.Reflect/Asset/AssetHandler.h>
|
||||
#include <AzTest/GemTestEnvironment.h>
|
||||
|
||||
namespace UnitTest
|
||||
@@ -60,14 +61,14 @@ namespace UnitTest
|
||||
entity->Activate();
|
||||
}
|
||||
|
||||
// Create a mock SurfaceDataSystem that will respond to requests for surface points with mock responses for points inside
|
||||
// the given input box.
|
||||
AZStd::unique_ptr<MockSurfaceDataSystem> CreateMockSurfaceDataSystem(const AZ::Aabb& spawnerBox);
|
||||
|
||||
// Create an entity with a mock shape and a transform. It won't be activated yet though, because we expect a gradient component
|
||||
// Create an entity with a box shape and a transform. It won't be activated yet though, because we expect a gradient component
|
||||
// to also get added to it first before activation.
|
||||
AZStd::unique_ptr<AZ::Entity> CreateTestEntity(float shapeHalfBounds);
|
||||
|
||||
// Create an entity with a sphere shape and a transform. It won't be activated yet though, because we expect a gradient component
|
||||
// to also get added to it first before activation.
|
||||
AZStd::unique_ptr<AZ::Entity> CreateTestSphereEntity(float shapeRadius);
|
||||
|
||||
// Create and activate an entity with a gradient component of the requested type, initialized with test data.
|
||||
AZStd::unique_ptr<AZ::Entity> BuildTestConstantGradient(float shapeHalfBounds);
|
||||
AZStd::unique_ptr<AZ::Entity> BuildTestImageGradient(float shapeHalfBounds);
|
||||
@@ -89,7 +90,7 @@ namespace UnitTest
|
||||
AZStd::unique_ptr<AZ::Entity> BuildTestSurfaceMaskGradient(float shapeHalfBounds);
|
||||
AZStd::unique_ptr<AZ::Entity> BuildTestSurfaceSlopeGradient(float shapeHalfBounds);
|
||||
|
||||
UnitTest::ImageAssetMockAssetHandler* m_mockHandler = nullptr;
|
||||
AZ::RPI::AssetHandlerPtrList m_assetHandlers;
|
||||
};
|
||||
|
||||
struct GradientSignalTest
|
||||
|
||||
@@ -8,16 +8,194 @@
|
||||
|
||||
|
||||
#include <Tests/GradientSignalTestHelpers.h>
|
||||
#include <Atom/RPI.Reflect/Image/ImageMipChainAssetCreator.h>
|
||||
#include <Atom/RPI.Reflect/Image/StreamingImageAssetCreator.h>
|
||||
#include <AzCore/Math/Aabb.h>
|
||||
#include <GradientSignal/GradientSampler.h>
|
||||
|
||||
namespace UnitTest
|
||||
{
|
||||
void GradientSignalTestHelpers::CompareGetValueAndGetValues(AZ::EntityId gradientEntityId, float shapeHalfBounds)
|
||||
AZ::RHI::ImageSubresourceLayout BuildSubImageLayout(AZ::u32 width, AZ::u32 height, AZ::u32 pixelSize)
|
||||
{
|
||||
AZ::RHI::ImageSubresourceLayout layout;
|
||||
layout.m_size = AZ::RHI::Size{ width, height, 1 };
|
||||
layout.m_rowCount = width;
|
||||
layout.m_bytesPerRow = width * pixelSize;
|
||||
layout.m_bytesPerImage = width * height * pixelSize;
|
||||
return layout;
|
||||
}
|
||||
|
||||
AZStd::vector<uint8_t> BuildBasicImageData(AZ::u32 width, AZ::u32 height, AZ::u32 pixelSize, AZ::s32 seed)
|
||||
{
|
||||
const size_t imageSize = width * height * pixelSize;
|
||||
|
||||
AZStd::vector<uint8_t> image;
|
||||
image.reserve(imageSize);
|
||||
|
||||
size_t value = 0;
|
||||
AZStd::hash_combine(value, seed);
|
||||
|
||||
for (AZ::u32 x = 0; x < width; ++x)
|
||||
{
|
||||
for (AZ::u32 y = 0; y < height; ++y)
|
||||
{
|
||||
AZStd::hash_combine(value, x);
|
||||
AZStd::hash_combine(value, y);
|
||||
image.push_back(static_cast<AZ::u8>(value));
|
||||
}
|
||||
}
|
||||
|
||||
EXPECT_EQ(image.size(), imageSize);
|
||||
return image;
|
||||
}
|
||||
|
||||
AZ::Data::Asset<AZ::RPI::ImageMipChainAsset> BuildBasicMipChainAsset(AZ::u16 mipLevels, AZ::u16 arraySize, AZ::u32 width, AZ::u32 height, AZ::u32 pixelSize, AZ::s32 seed)
|
||||
{
|
||||
using namespace AZ;
|
||||
|
||||
RPI::ImageMipChainAssetCreator assetCreator;
|
||||
|
||||
assetCreator.Begin(Data::AssetId(AZ::Uuid::CreateRandom()), mipLevels, arraySize);
|
||||
|
||||
RHI::ImageSubresourceLayout layout = BuildSubImageLayout(width, height, pixelSize);
|
||||
|
||||
assetCreator.BeginMip(layout);
|
||||
|
||||
for (AZ::u32 arrayIndex = 0; arrayIndex < arraySize; ++arrayIndex)
|
||||
{
|
||||
AZStd::vector<uint8_t> data = BuildBasicImageData(width, height, pixelSize, seed);
|
||||
assetCreator.AddSubImage(data.data(), data.size());
|
||||
}
|
||||
|
||||
assetCreator.EndMip();
|
||||
|
||||
Data::Asset<RPI::ImageMipChainAsset> asset;
|
||||
EXPECT_TRUE(assetCreator.End(asset));
|
||||
EXPECT_TRUE(asset.IsReady());
|
||||
EXPECT_NE(asset.Get(), nullptr);
|
||||
|
||||
return asset;
|
||||
}
|
||||
|
||||
AZStd::vector<uint8_t> BuildSpecificPixelImageData(AZ::u32 width, AZ::u32 height, AZ::u32 pixelSize, AZ::u32 pixelX, AZ::u32 pixelY)
|
||||
{
|
||||
const size_t imageSize = width * height * pixelSize;
|
||||
|
||||
AZStd::vector<uint8_t> image;
|
||||
image.reserve(imageSize);
|
||||
|
||||
const AZ::u8 pixelValue = 255;
|
||||
|
||||
// Image data should be stored inverted on the y axis relative to our engine, so loop backwards through y.
|
||||
for (int y = static_cast<int>(height) - 1; y >= 0; --y)
|
||||
{
|
||||
for (AZ::u32 x = 0; x < width; ++x)
|
||||
{
|
||||
if ((x == static_cast<int>(pixelX)) && (y == static_cast<int>(pixelY)))
|
||||
{
|
||||
image.push_back(pixelValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
image.push_back(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
EXPECT_EQ(image.size(), imageSize);
|
||||
return image;
|
||||
}
|
||||
|
||||
AZ::Data::Asset<AZ::RPI::ImageMipChainAsset> BuildSpecificPixelMipChainAsset(AZ::u16 mipLevels, AZ::u16 arraySize, AZ::u32 width, AZ::u32 height, AZ::u32 pixelSize, AZ::u32 pixelX, AZ::u32 pixelY)
|
||||
{
|
||||
using namespace AZ;
|
||||
|
||||
RPI::ImageMipChainAssetCreator assetCreator;
|
||||
|
||||
assetCreator.Begin(Data::AssetId(AZ::Uuid::CreateRandom()), mipLevels, arraySize);
|
||||
|
||||
RHI::ImageSubresourceLayout layout = BuildSubImageLayout(width, height, pixelSize);
|
||||
|
||||
assetCreator.BeginMip(layout);
|
||||
|
||||
for (AZ::u32 arrayIndex = 0; arrayIndex < arraySize; ++arrayIndex)
|
||||
{
|
||||
AZStd::vector<uint8_t> data = BuildSpecificPixelImageData(width, height, pixelSize, pixelX, pixelY);
|
||||
assetCreator.AddSubImage(data.data(), data.size());
|
||||
}
|
||||
|
||||
assetCreator.EndMip();
|
||||
|
||||
Data::Asset<RPI::ImageMipChainAsset> asset;
|
||||
EXPECT_TRUE(assetCreator.End(asset));
|
||||
EXPECT_TRUE(asset.IsReady());
|
||||
EXPECT_NE(asset.Get(), nullptr);
|
||||
|
||||
return asset;
|
||||
}
|
||||
|
||||
AZ::Data::Asset<AZ::RPI::StreamingImageAsset> CreateImageAsset(AZ::u32 width, AZ::u32 height, AZ::s32 seed)
|
||||
{
|
||||
auto randomAssetId = AZ::Data::AssetId(AZ::Uuid::CreateRandom());
|
||||
auto imageAsset = AZ::Data::AssetManager::Instance().CreateAsset<AZ::RPI::StreamingImageAsset>(
|
||||
randomAssetId, AZ::Data::AssetLoadBehavior::Default);
|
||||
|
||||
const AZ::u32 arraySize = 1;
|
||||
const AZ::u32 mipCountTotal = 1;
|
||||
const auto format = AZ::RHI::Format::R8_UNORM;
|
||||
const AZ::u32 pixelSize = AZ::RHI::GetFormatComponentCount(format);
|
||||
|
||||
AZ::Data::Asset<AZ::RPI::ImageMipChainAsset> mipChain = BuildBasicMipChainAsset(mipCountTotal, arraySize, width, height, pixelSize, seed);
|
||||
|
||||
AZ::RPI::StreamingImageAssetCreator assetCreator;
|
||||
assetCreator.Begin(randomAssetId);
|
||||
|
||||
AZ::RHI::ImageDescriptor imageDesc = AZ::RHI::ImageDescriptor::Create2DArray(AZ::RHI::ImageBindFlags::ShaderRead, width, height, arraySize, format);
|
||||
imageDesc.m_mipLevels = static_cast<AZ::u16>(mipCountTotal);
|
||||
|
||||
assetCreator.SetImageDescriptor(imageDesc);
|
||||
assetCreator.AddMipChainAsset(*mipChain.Get());
|
||||
|
||||
EXPECT_TRUE(assetCreator.End(imageAsset));
|
||||
EXPECT_TRUE(imageAsset.IsReady());
|
||||
EXPECT_NE(imageAsset.Get(), nullptr);
|
||||
|
||||
return imageAsset;
|
||||
}
|
||||
|
||||
AZ::Data::Asset<AZ::RPI::StreamingImageAsset> CreateSpecificPixelImageAsset(AZ::u32 width, AZ::u32 height, AZ::u32 pixelX, AZ::u32 pixelY)
|
||||
{
|
||||
auto randomAssetId = AZ::Data::AssetId(AZ::Uuid::CreateRandom());
|
||||
auto imageAsset = AZ::Data::AssetManager::Instance().CreateAsset<AZ::RPI::StreamingImageAsset>(
|
||||
randomAssetId, AZ::Data::AssetLoadBehavior::Default);
|
||||
|
||||
const AZ::u32 arraySize = 1;
|
||||
const AZ::u32 mipCountTotal = 1;
|
||||
const auto format = AZ::RHI::Format::R8_UNORM;
|
||||
const AZ::u32 pixelSize = AZ::RHI::GetFormatComponentCount(format);
|
||||
|
||||
AZ::Data::Asset<AZ::RPI::ImageMipChainAsset> mipChain = BuildSpecificPixelMipChainAsset(mipCountTotal, arraySize, width, height, pixelSize, pixelX, pixelY);
|
||||
|
||||
AZ::RPI::StreamingImageAssetCreator assetCreator;
|
||||
assetCreator.Begin(randomAssetId);
|
||||
|
||||
AZ::RHI::ImageDescriptor imageDesc = AZ::RHI::ImageDescriptor::Create2DArray(AZ::RHI::ImageBindFlags::ShaderRead, width, height, arraySize, format);
|
||||
imageDesc.m_mipLevels = static_cast<AZ::u16>(mipCountTotal);
|
||||
|
||||
assetCreator.SetImageDescriptor(imageDesc);
|
||||
assetCreator.AddMipChainAsset(*mipChain.Get());
|
||||
|
||||
EXPECT_TRUE(assetCreator.End(imageAsset));
|
||||
EXPECT_TRUE(imageAsset.IsReady());
|
||||
EXPECT_NE(imageAsset.Get(), nullptr);
|
||||
return imageAsset;
|
||||
}
|
||||
|
||||
void GradientSignalTestHelpers::CompareGetValueAndGetValues(AZ::EntityId gradientEntityId, float queryMin, float queryMax)
|
||||
{
|
||||
// Create a gradient sampler and run through a series of points to see if they match expectations.
|
||||
|
||||
const AZ::Aabb queryRegion = AZ::Aabb::CreateFromMinMax(AZ::Vector3(-shapeHalfBounds), AZ::Vector3(shapeHalfBounds));
|
||||
const AZ::Aabb queryRegion = AZ::Aabb::CreateFromMinMax(AZ::Vector3(queryMin), AZ::Vector3(queryMax));
|
||||
const AZ::Vector2 stepSize(1.0f, 1.0f);
|
||||
|
||||
GradientSignal::GradientSampler gradientSampler;
|
||||
@@ -118,6 +296,7 @@ namespace UnitTest
|
||||
AZStd::vector<float> results(totalQueryPoints);
|
||||
GradientSignal::GradientRequestBus::Event(
|
||||
gradientId, &GradientSignal::GradientRequestBus::Events::GetValues, positions, results);
|
||||
benchmark::DoNotOptimize(results);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -174,6 +353,7 @@ namespace UnitTest
|
||||
// Query and get the results.
|
||||
AZStd::vector<float> results(totalQueryPoints);
|
||||
gradientSampler.GetValues(positions, results);
|
||||
benchmark::DoNotOptimize(results);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,12 +12,76 @@
|
||||
#include <AzCore/std/containers/vector.h>
|
||||
#include <AzTest/AzTest.h>
|
||||
|
||||
#include <Atom/RHI.Reflect/ImageSubresource.h>
|
||||
#include <Atom/RPI.Reflect/Image/ImageMipChainAsset.h>
|
||||
#include <Atom/RPI.Reflect/Image/StreamingImageAsset.h>
|
||||
|
||||
namespace UnitTest
|
||||
{
|
||||
//! Helper method to build a AZ::RHI::ImageSubresourceLayout
|
||||
//! @param width The width of the image
|
||||
//! @param height The height of the image
|
||||
//! @param pixelSize Number of bytes per pixel
|
||||
//! @return The AZ::RHI::ImageSubresourceLayout that has been filled out appropriately
|
||||
AZ::RHI::ImageSubresourceLayout BuildSubImageLayout(AZ::u32 width, AZ::u32 height, AZ::u32 pixelSize);
|
||||
|
||||
//! Build a deterministic random set of image pixel data
|
||||
//! @param width Width of the image
|
||||
//! @param height Height of the image
|
||||
//! @param pixelSize Number of bytes per pixel
|
||||
//! @param seed The random seed for generating the data
|
||||
//! @return A vector of bytes for the image data
|
||||
AZStd::vector<uint8_t> BuildBasicImageData(AZ::u32 width, AZ::u32 height, AZ::u32 pixelSize, AZ::s32 seed);
|
||||
|
||||
//! Build a mip chain asset that contains the basic image data from BuildBasicImageData
|
||||
//! @param mipLevels Number of mip levels in the chain
|
||||
//! @param arraySize Number of sub images within a mip level
|
||||
//! @param width The width of the image
|
||||
//! @param height The height of the image
|
||||
//! @param pixelSize The number of bytes per pixel
|
||||
//! @param seed The random seed for generating the data
|
||||
//! @return A mip chain asset with the specified basic image data
|
||||
AZ::Data::Asset<AZ::RPI::ImageMipChainAsset> BuildBasicMipChainAsset(AZ::u16 mipLevels, AZ::u16 arraySize, AZ::u32 width, AZ::u32 height, AZ::u32 pixelSize, AZ::s32 seed);
|
||||
|
||||
//! Construct an array of image data where all the pixels are 0 except for one at the given coordinate
|
||||
//! @param width Width of the image
|
||||
//! @param height Height of the image
|
||||
//! @param pixelSize Number of bytes per pixel
|
||||
//! @param pixelX The X coordinate of the pixel to set to 1
|
||||
//! @param pixelY The Y coordinate of the pixel to set to 1
|
||||
//! @return A vector of bytes for the image data
|
||||
AZStd::vector<uint8_t> BuildSpecificPixelImageData(AZ::u32 width, AZ::u32 height, AZ::u32 pixelSize, AZ::u32 pixelX, AZ::u32 pixelY);
|
||||
|
||||
//! Build a mip chain asset that contains the specific image data from BuildSpecificPixelImageData
|
||||
//! @param mipLevels Number of mip levels in the chain
|
||||
//! @param arraySize Number of sub images within a mip level
|
||||
//! @param width The width of the image
|
||||
//! @param height The height of the image
|
||||
//! @param pixelSize The number of bytes per pixel
|
||||
//! @param pixelX The X coordinate of the pixel to set to 1
|
||||
//! @param pixelY The Y coordinate of the pixel to set to 1
|
||||
//! @return A mip chain asset with the specific pixel image data
|
||||
AZ::Data::Asset<AZ::RPI::ImageMipChainAsset> BuildSpecificPixelMipChainAsset(AZ::u16 mipLevels, AZ::u16 arraySize, AZ::u32 width, AZ::u32 height, AZ::u32 pixelSize, AZ::u32 pixelX, AZ::u32 pixelY);
|
||||
|
||||
//! Creates a deterministically random set of pixel data as an AZ::RPI::StreamingImageAsset.
|
||||
//! \param width The width of the AZ::RPI::StreamingImageAsset
|
||||
//! \param height The height of the AZ::RPI::StreamingImageAsset
|
||||
//! \param seed The random seed to use for generating the random data
|
||||
//! \return The AZ::RPI::StreamingImageAsset in a loaded ready state
|
||||
AZ::Data::Asset<AZ::RPI::StreamingImageAsset> CreateImageAsset(AZ::u32 width, AZ::u32 height, AZ::s32 seed);
|
||||
|
||||
//! Creates an AZ::RPI::StreamingImageAsset where all the pixels are 0 except for the one pixel at the given coordinates, which is set to 1.
|
||||
//! \param width The width of the AZ::RPI::StreamingImageAsset
|
||||
//! \param height The height of the AZ::RPI::StreamingImageAsset
|
||||
//! \param pixelX The X coordinate of the pixel to set to 1
|
||||
//! \param pixelY The Y coordinate of the pixel to set to 1
|
||||
//! \return The AZ::RPI::StreamingImageAsset in a loaded ready state
|
||||
AZ::Data::Asset<AZ::RPI::StreamingImageAsset> CreateSpecificPixelImageAsset(AZ::u32 width, AZ::u32 height, AZ::u32 pixelX, AZ::u32 pixelY);
|
||||
|
||||
class GradientSignalTestHelpers
|
||||
{
|
||||
public:
|
||||
static void CompareGetValueAndGetValues(AZ::EntityId gradientEntityId, float shapeHalfBounds);
|
||||
static void CompareGetValueAndGetValues(AZ::EntityId gradientEntityId, float queryMin, float queryMax);
|
||||
|
||||
#ifdef HAVE_BENCHMARK
|
||||
// We use an enum to list out the different types of GetValue() benchmarks to run so that way we can condense our test cases
|
||||
|
||||
@@ -1,74 +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 <Tests/GradientSignalTestMocks.h>
|
||||
|
||||
namespace UnitTest
|
||||
{
|
||||
AZ::Data::Asset<GradientSignal::ImageAsset> ImageAssetMockAssetHandler::CreateImageAsset(AZ::u32 width, AZ::u32 height, AZ::s32 seed)
|
||||
{
|
||||
auto imageAsset = AZ::Data::AssetManager::Instance().CreateAsset<GradientSignal::ImageAsset>(
|
||||
AZ::Data::AssetId(AZ::Uuid::CreateRandom()), AZ::Data::AssetLoadBehavior::Default);
|
||||
|
||||
imageAsset->m_imageWidth = width;
|
||||
imageAsset->m_imageHeight = height;
|
||||
imageAsset->m_bytesPerPixel = 1;
|
||||
imageAsset->m_imageFormat = ImageProcessingAtom::EPixelFormat::ePixelFormat_R8;
|
||||
imageAsset->m_imageData.reserve(width * height);
|
||||
|
||||
size_t value = 0;
|
||||
AZStd::hash_combine(value, seed);
|
||||
|
||||
for (AZ::u32 x = 0; x < width; ++x)
|
||||
{
|
||||
for (AZ::u32 y = 0; y < height; ++y)
|
||||
{
|
||||
AZStd::hash_combine(value, x);
|
||||
AZStd::hash_combine(value, y);
|
||||
imageAsset->m_imageData.push_back(static_cast<AZ::u8>(value));
|
||||
}
|
||||
}
|
||||
|
||||
return imageAsset;
|
||||
}
|
||||
|
||||
AZ::Data::Asset<GradientSignal::ImageAsset> ImageAssetMockAssetHandler::CreateSpecificPixelImageAsset(
|
||||
AZ::u32 width, AZ::u32 height, AZ::u32 pixelX, AZ::u32 pixelY)
|
||||
{
|
||||
auto imageAsset = AZ::Data::AssetManager::Instance().CreateAsset<GradientSignal::ImageAsset>(
|
||||
AZ::Data::AssetId(AZ::Uuid::CreateRandom()), AZ::Data::AssetLoadBehavior::Default);
|
||||
|
||||
imageAsset->m_imageWidth = width;
|
||||
imageAsset->m_imageHeight = height;
|
||||
imageAsset->m_bytesPerPixel = 1;
|
||||
imageAsset->m_imageFormat = ImageProcessingAtom::EPixelFormat::ePixelFormat_R8;
|
||||
imageAsset->m_imageData.reserve(width * height);
|
||||
|
||||
const AZ::u8 pixelValue = 255;
|
||||
|
||||
// Image data should be stored inverted on the y axis relative to our engine, so loop backwards through y.
|
||||
for (int y = static_cast<int>(height) - 1; y >= 0; --y)
|
||||
{
|
||||
for (AZ::u32 x = 0; x < width; ++x)
|
||||
{
|
||||
if ((x == static_cast<int>(pixelX)) && (y == static_cast<int>(pixelY)))
|
||||
{
|
||||
imageAsset->m_imageData.push_back(pixelValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
imageAsset->m_imageData.push_back(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return imageAsset;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,58 +22,12 @@
|
||||
#include <GradientSignal/GradientSampler.h>
|
||||
#include <GradientSignal/ImageAsset.h>
|
||||
#include <LmbrCentral/Shape/ShapeComponentBus.h>
|
||||
#include <SurfaceData/SurfaceDataProviderRequestBus.h>
|
||||
#include <SurfaceData/SurfaceDataSystemRequestBus.h>
|
||||
#include <SurfaceData/Tests/SurfaceDataTestMocks.h>
|
||||
|
||||
namespace UnitTest
|
||||
{
|
||||
// Mock asset handler for GradientSignal::ImageAsset that we can use in unit tests to pretend to load an image asset with.
|
||||
// Also includes utility functions for creating image assets with specific testable patterns.
|
||||
struct ImageAssetMockAssetHandler : public AZ::Data::AssetHandler
|
||||
{
|
||||
//! Creates a deterministically random set of pixel data as an ImageAsset.
|
||||
//! \param width The width of the ImageAsset
|
||||
//! \param height The height of the ImageAsset
|
||||
//! \param seed The random seed to use for generating the random data
|
||||
//! \return The ImageAsset in a loaded ready state
|
||||
static AZ::Data::Asset<GradientSignal::ImageAsset> CreateImageAsset(AZ::u32 width, AZ::u32 height, AZ::s32 seed);
|
||||
|
||||
//! Creates an ImageAsset where all the pixels are 0 except for the one pixel at the given coordinates, which is set to 1.
|
||||
//! \param width The width of the ImageAsset
|
||||
//! \param height The height of the ImageAsset
|
||||
//! \param pixelX The X coordinate of the pixel to set to 1
|
||||
//! \param pixelY The Y coordinate of the pixel to set to 1
|
||||
//! \return The ImageAsset in a loaded ready state
|
||||
static AZ::Data::Asset<GradientSignal::ImageAsset> CreateSpecificPixelImageAsset(
|
||||
AZ::u32 width, AZ::u32 height, AZ::u32 pixelX, AZ::u32 pixelY);
|
||||
|
||||
AZ::Data::AssetPtr CreateAsset(const AZ::Data::AssetId& id, [[maybe_unused]] const AZ::Data::AssetType& type) override
|
||||
{
|
||||
// For our mock handler, always mark our assets as immediately ready.
|
||||
return aznew GradientSignal::ImageAsset(id, AZ::Data::AssetData::AssetStatus::Ready);
|
||||
}
|
||||
|
||||
void DestroyAsset(AZ::Data::AssetPtr ptr) override
|
||||
{
|
||||
if (ptr)
|
||||
{
|
||||
delete ptr;
|
||||
}
|
||||
}
|
||||
|
||||
void GetHandledAssetTypes([[maybe_unused]] AZStd::vector<AZ::Data::AssetType>& assetTypes) override
|
||||
{
|
||||
}
|
||||
|
||||
AZ::Data::AssetHandler::LoadResult LoadAssetData(
|
||||
[[maybe_unused]] const AZ::Data::Asset<AZ::Data::AssetData>& asset,
|
||||
[[maybe_unused]] AZStd::shared_ptr<AZ::Data::AssetDataStream> stream,
|
||||
[[maybe_unused]] const AZ::Data::AssetFilterCB& assetLoadFilterCB) override
|
||||
{
|
||||
return AZ::Data::AssetHandler::LoadResult::LoadComplete;
|
||||
}
|
||||
};
|
||||
|
||||
struct MockGradientRequestsBus
|
||||
: public GradientSignal::GradientRequestBus::Handler
|
||||
{
|
||||
@@ -162,4 +116,64 @@ namespace UnitTest
|
||||
bool m_constrainToShape;
|
||||
};
|
||||
|
||||
// Mock out a SurfaceProvider component so that we can control exactly what surface weights get returned
|
||||
// at which points for our unit tests.
|
||||
struct MockSurfaceProviderComponent
|
||||
: public AZ::Component
|
||||
, public SurfaceData::SurfaceDataProviderRequestBus::Handler
|
||||
{
|
||||
public:
|
||||
AZ_COMPONENT(MockSurfaceProviderComponent, "{18C71877-DB29-4CEC-B34C-B4B44E05203D}", AZ::Component);
|
||||
|
||||
void Activate() override
|
||||
{
|
||||
SurfaceData::SurfaceDataRegistryEntry providerRegistryEntry;
|
||||
providerRegistryEntry.m_entityId = GetEntityId();
|
||||
providerRegistryEntry.m_bounds = m_bounds;
|
||||
providerRegistryEntry.m_tags = m_tags;
|
||||
|
||||
SurfaceData::SurfaceDataSystemRequestBus::BroadcastResult(
|
||||
m_providerHandle, &SurfaceData::SurfaceDataSystemRequestBus::Events::RegisterSurfaceDataProvider, providerRegistryEntry);
|
||||
SurfaceData::SurfaceDataProviderRequestBus::Handler::BusConnect(m_providerHandle);
|
||||
}
|
||||
|
||||
void Deactivate() override
|
||||
{
|
||||
SurfaceData::SurfaceDataSystemRequestBus::Broadcast(
|
||||
&SurfaceData::SurfaceDataSystemRequestBus::Events::UnregisterSurfaceDataProvider, m_providerHandle);
|
||||
m_providerHandle = SurfaceData::InvalidSurfaceDataRegistryHandle;
|
||||
SurfaceData::SurfaceDataProviderRequestBus::Handler::BusDisconnect();
|
||||
}
|
||||
|
||||
static void Reflect([[maybe_unused]] AZ::ReflectContext* reflect)
|
||||
{
|
||||
}
|
||||
|
||||
static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
|
||||
{
|
||||
provided.push_back(AZ_CRC_CE("SurfaceDataProviderService"));
|
||||
}
|
||||
|
||||
void GetSurfacePoints(const AZ::Vector3& inPosition, SurfaceData::SurfacePointList& surfacePointList) const override
|
||||
{
|
||||
auto surfacePoints = m_surfacePoints.find(AZStd::make_pair(inPosition.GetX(), inPosition.GetY()));
|
||||
|
||||
if (surfacePoints != m_surfacePoints.end())
|
||||
{
|
||||
surfacePointList = surfacePoints->second;
|
||||
}
|
||||
}
|
||||
|
||||
// m_surfacePoints is a mapping of locations to surface tags / weights that should be returned.
|
||||
AZStd::unordered_map<AZStd::pair<float, float>, SurfaceData::SurfacePointList> m_surfacePoints;
|
||||
|
||||
// m_bounds is the AABB to use for our mock surface provider.
|
||||
AZ::Aabb m_bounds;
|
||||
|
||||
// m_tags are the possible set of tags that this provider will return.
|
||||
SurfaceData::SurfaceTagVector m_tags;
|
||||
|
||||
SurfaceData::SurfaceDataRegistryHandle m_providerHandle = SurfaceData::InvalidSurfaceDataRegistryHandle;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -11,6 +11,5 @@ set(FILES
|
||||
Tests/GradientSignalTestHelpers.h
|
||||
Tests/GradientSignalTestFixtures.cpp
|
||||
Tests/GradientSignalTestFixtures.h
|
||||
Tests/GradientSignalTestMocks.cpp
|
||||
Tests/GradientSignalTestMocks.h
|
||||
)
|
||||
|
||||
@@ -153,7 +153,7 @@ namespace AZ::SceneAPI::Behaviors
|
||||
// all mesh data nodes left in the meshIndexContainer do not have a matching TransformData node
|
||||
// since the nodes have an identity transform, so map the MeshData index with an Invalid mesh index to
|
||||
// indicate the transform should not be set to a default value
|
||||
for( const auto meshIndex : meshIndexContainer)
|
||||
for( const auto& meshIndex : meshIndexContainer)
|
||||
{
|
||||
MeshTransformPair pair{ meshIndex, Containers::SceneGraph::NodeIndex{} };
|
||||
meshTransformMap.emplace(MeshTransformEntry{ graph.GetNodeParent(meshIndex), AZStd::move(pair) });
|
||||
|
||||
@@ -1098,7 +1098,7 @@ protected:
|
||||
slotConfiguration.SetType(Data::FromAZType<AZStd::decay_t<ResultType>>());
|
||||
slotConfiguration.SetConnectionType(ConnectionType::Output);
|
||||
|
||||
node.AddSlot(slotConfiguration);
|
||||
AZ_VerifyError("ScriptCanvas", node.AddSlot(slotConfiguration).IsValid(), "Node failed to add a required Data Out slot");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1115,12 +1115,11 @@ protected:
|
||||
static void CreateDataSlot(Node& node, ConnectionType connectionType)
|
||||
{
|
||||
DataSlotConfiguration slotConfiguration;
|
||||
|
||||
slotConfiguration.m_name = t_Traits::GetResultName(Index);
|
||||
slotConfiguration.SetType(Data::FromAZType<AZStd::decay_t<AZStd::tuple_element_t<Index, ResultType>>>());
|
||||
|
||||
slotConfiguration.SetConnectionType(connectionType);
|
||||
node.AddSlot(slotConfiguration);
|
||||
|
||||
AZ_VerifyError("ScriptCanvas", node.AddSlot(slotConfiguration).IsValid(), "Node failed to add a required Data Out slot");
|
||||
}
|
||||
|
||||
template<AZStd::size_t... Is>
|
||||
|
||||
@@ -82,15 +82,30 @@ namespace ScriptCanvas
|
||||
static const size_t s_numNames = SCRIPT_CANVAS_FUNCTION_VAR_ARGS(__VA_ARGS__);\
|
||||
/*static const size_t s_numResults = ScriptCanvas::Internal::extended_tuple_size<ResultType>::value;*/\
|
||||
\
|
||||
static const char* GetArgName(size_t i)\
|
||||
static AZStd::string GetArgName(size_t i)\
|
||||
{\
|
||||
return GetName(i).data();\
|
||||
AZStd::string_view argName = GetName(i);\
|
||||
if (!argName.empty())\
|
||||
{\
|
||||
return argName;\
|
||||
}\
|
||||
else\
|
||||
{\
|
||||
return AZStd::string::format("Input [%zu]", i);\
|
||||
}\
|
||||
}\
|
||||
\
|
||||
static const char* GetResultName(size_t i)\
|
||||
static AZStd::string GetResultName(size_t i)\
|
||||
{\
|
||||
AZStd::string_view result = GetName(i + s_numArgs);\
|
||||
return !result.empty() ? result.data() : "Result";\
|
||||
AZStd::string_view resultName = GetName(i + s_numArgs);\
|
||||
if (!resultName.empty())\
|
||||
{\
|
||||
return resultName;\
|
||||
}\
|
||||
else\
|
||||
{\
|
||||
return AZStd::string::format("Result [%zu]", i);\
|
||||
}\
|
||||
}\
|
||||
\
|
||||
static const char* GetDependency() { return CATEGORY; }\
|
||||
@@ -260,7 +275,7 @@ namespace ScriptCanvas
|
||||
slotConfiguration.ConfigureDatum(AZStd::move(Datum(Data::FromAZType(Data::Traits<ArgType>::GetAZType()), Datum::eOriginality::Copy)));
|
||||
|
||||
slotConfiguration.SetConnectionType(connectionType);
|
||||
AddSlot(slotConfiguration);
|
||||
AZ_VerifyError("ScriptCanvas", AddSlot(slotConfiguration).IsValid(), "NodeFunctionGenericMultiReturn failed to add a required data slot");
|
||||
}
|
||||
|
||||
template<typename... t_Args, AZStd::size_t... Is>
|
||||
@@ -278,12 +293,12 @@ namespace ScriptCanvas
|
||||
{
|
||||
{
|
||||
ExecutionSlotConfiguration slotConfiguration("In", ConnectionType::Input);
|
||||
AddSlot(slotConfiguration);
|
||||
AZ_VerifyError("ScriptCanvas", AddSlot(slotConfiguration).IsValid(), "NodeFunctionGenericMultiReturn failed to add a required Execution In slot");
|
||||
}
|
||||
|
||||
{
|
||||
ExecutionSlotConfiguration slotConfiguration("Out", ConnectionType::Output);
|
||||
AddSlot(slotConfiguration);
|
||||
AZ_VerifyError("ScriptCanvas", AddSlot(slotConfiguration).IsValid(), "NodeFunctionGenericMultiReturn failed to add a required Execution Out slot");
|
||||
}
|
||||
|
||||
AddInputDatumSlotHelper(typename AZStd::function_traits<t_Func>::arg_sequence{}, AZStd::make_index_sequence<AZStd::function_traits<t_Func>::arity>{});
|
||||
|
||||
@@ -257,7 +257,7 @@ namespace ScriptCanvas
|
||||
r.SetLength(static_cast<float>(optionalScale));
|
||||
return std::make_tuple(r, length);
|
||||
}
|
||||
SCRIPT_CANVAS_GENERIC_FUNCTION_NODE_WITH_DEFAULTS(DirectionTo, DirectionToDefaults, k_categoryName, "{49A2D7F6-6CD3-420E-8A79-D46B00DB6CED}", "Returns a direction vector between two points and the distance between them, by default the direction will be normalized, but it may be optionally scaled using the Scale parameter if different from 1.0", "From", "To", "Scale");
|
||||
SCRIPT_CANVAS_GENERIC_FUNCTION_NODE_WITH_DEFAULTS(DirectionTo, DirectionToDefaults, k_categoryName, "{49A2D7F6-6CD3-420E-8A79-D46B00DB6CED}", "Returns a direction vector between two points and the distance between them, by default the direction will be normalized, but it may be optionally scaled using the Scale parameter if different from 1.0", "From", "To", "Scale", "Direction", "Length");
|
||||
|
||||
using Registrar = RegistrarGeneric <
|
||||
AbsoluteNode
|
||||
|
||||
@@ -343,8 +343,17 @@ namespace ScriptCanvas
|
||||
r.SetLength(static_cast<float>(optionalScale));
|
||||
return std::make_tuple(r, length);
|
||||
}
|
||||
SCRIPT_CANVAS_GENERIC_FUNCTION_NODE_WITH_DEFAULTS(DirectionTo, DirectionToDefaults, k_categoryName, "{28FBD529-4C9A-4E34-B8A0-A13B5DB3C331}", "Returns a direction vector between two points and the distance between them, by default the direction will be normalized, but it may be optionally scaled using the Scale parameter if different from 1.0", "From", "To", "Scale");
|
||||
|
||||
SCRIPT_CANVAS_GENERIC_FUNCTION_NODE_WITH_DEFAULTS
|
||||
( DirectionTo
|
||||
, DirectionToDefaults
|
||||
, k_categoryName, "{28FBD529-4C9A-4E34-B8A0-A13B5DB3C331}"
|
||||
, "Returns a direction vector between two points and the distance between them, by default the direction will be normalized, but it may be optionally scaled using the Scale parameter if different from 1.0"
|
||||
, "From"
|
||||
, "To"
|
||||
, "Scale"
|
||||
, "Direction"
|
||||
, "Length");
|
||||
|
||||
using Registrar = RegistrarGeneric <
|
||||
AbsoluteNode
|
||||
|
||||
@@ -228,7 +228,7 @@ namespace ScriptCanvas
|
||||
r.SetLength(static_cast<float>(optionalScale));
|
||||
return std::make_tuple(r, length);
|
||||
}
|
||||
SCRIPT_CANVAS_GENERIC_FUNCTION_NODE_WITH_DEFAULTS(DirectionTo, DirectionToDefaults, k_categoryName, "{463762DE-E541-4AFE-80C2-FED1C5273319}", "Returns a direction vector between two points and the distance between them, by default the direction will be normalized, but it may be optionally scaled using the Scale parameter if different from 1.0", "From", "To", "Scale");
|
||||
SCRIPT_CANVAS_GENERIC_FUNCTION_NODE_WITH_DEFAULTS(DirectionTo, DirectionToDefaults, k_categoryName, "{463762DE-E541-4AFE-80C2-FED1C5273319}", "Returns a direction vector between two points and the distance between them, by default the direction will be normalized, but it may be optionally scaled using the Scale parameter if different from 1.0", "From", "To", "Scale", "Direction", "Length");
|
||||
|
||||
using Registrar = RegistrarGeneric <
|
||||
AbsoluteNode,
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
|
||||
#include "GraphToLuaUtility.h"
|
||||
|
||||
#include <clocale>
|
||||
|
||||
#include <AzCore/Outcome/Outcome.h>
|
||||
#include <AzCore/RTTI/BehaviorContextUtilities.h>
|
||||
#include <ScriptCanvas/Core/Node.h>
|
||||
@@ -23,6 +25,23 @@
|
||||
|
||||
namespace GraphToLuaUtilityCpp
|
||||
{
|
||||
class ScopedLocale
|
||||
{
|
||||
public:
|
||||
ScopedLocale()
|
||||
{
|
||||
m_previousLocale = std::setlocale(LC_NUMERIC, "en_US.UTF-8");
|
||||
}
|
||||
|
||||
~ScopedLocale()
|
||||
{
|
||||
std::setlocale(LC_NUMERIC, m_previousLocale);
|
||||
}
|
||||
|
||||
private:
|
||||
char* m_previousLocale = nullptr;
|
||||
};
|
||||
|
||||
AZStd::string EqualSigns(size_t numEqualSignsRequired)
|
||||
{
|
||||
AZStd::string equalSigns = "";
|
||||
@@ -183,6 +202,8 @@ namespace ScriptCanvas
|
||||
|
||||
AZStd::string ToValueString(const Datum& datum, const Configuration& config)
|
||||
{
|
||||
GraphToLuaUtilityCpp::ScopedLocale scopedLocal;
|
||||
|
||||
switch (datum.GetType().GetType())
|
||||
{
|
||||
case Data::eType::AABB:
|
||||
|
||||
+4
@@ -57,6 +57,10 @@ namespace SurfaceData
|
||||
void UpdateSurfaceDataModifier(const SurfaceDataRegistryHandle& handle, const SurfaceDataRegistryEntry& entry) override;
|
||||
|
||||
void RefreshSurfaceData(const AZ::Aabb& dirtyArea) override;
|
||||
|
||||
SurfaceDataRegistryHandle GetSurfaceDataProviderHandle(const AZ::EntityId& providerEntityId) override;
|
||||
SurfaceDataRegistryHandle GetSurfaceDataModifierHandle(const AZ::EntityId& modifierEntityId) override;
|
||||
|
||||
private:
|
||||
SurfaceDataRegistryHandle RegisterSurfaceDataProviderInternal(const SurfaceDataRegistryEntry& entry);
|
||||
SurfaceDataRegistryEntry UnregisterSurfaceDataProviderInternal(const SurfaceDataRegistryHandle& handle);
|
||||
@@ -58,6 +58,10 @@ namespace SurfaceData
|
||||
|
||||
// Notify any dependent systems that they need to refresh their surface data for the provided area.
|
||||
virtual void RefreshSurfaceData(const AZ::Aabb& dirtyArea) = 0;
|
||||
|
||||
// Get the SurfaceDataRegistryHandle for a given entityId.
|
||||
virtual SurfaceDataRegistryHandle GetSurfaceDataProviderHandle(const AZ::EntityId& providerEntityId) = 0;
|
||||
virtual SurfaceDataRegistryHandle GetSurfaceDataModifierHandle(const AZ::EntityId& modifierEntityId) = 0;
|
||||
};
|
||||
|
||||
typedef AZ::EBus<SurfaceDataSystemRequests> SurfaceDataSystemRequestBus;
|
||||
|
||||
@@ -250,6 +250,16 @@ namespace UnitTest
|
||||
{
|
||||
}
|
||||
|
||||
SurfaceData::SurfaceDataRegistryHandle GetSurfaceDataProviderHandle(const AZ::EntityId& providerEntityId) override
|
||||
{
|
||||
return GetSurfaceProviderHandle(providerEntityId);
|
||||
}
|
||||
|
||||
SurfaceData::SurfaceDataRegistryHandle GetSurfaceDataModifierHandle(const AZ::EntityId& modifierEntityId) override
|
||||
{
|
||||
return GetSurfaceModifierHandle(modifierEntityId);
|
||||
}
|
||||
|
||||
SurfaceData::SurfaceDataRegistryHandle GetSurfaceProviderHandle(AZ::EntityId id)
|
||||
{
|
||||
return GetEntryHandle(id, m_providers);
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#include "SurfaceDataColliderComponent.h"
|
||||
#include <SurfaceData/Components/SurfaceDataColliderComponent.h>
|
||||
|
||||
#include <AzCore/Debug/Profiler.h>
|
||||
#include <AzCore/RTTI/BehaviorContext.h>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user