From e06b8782cc9781660dda860daef76bdf546fe10a Mon Sep 17 00:00:00 2001 From: Nicholas Van Sickle Date: Fri, 21 Jan 2022 15:55:37 -0800 Subject: [PATCH 01/29] Add AZ::Dom::Patch, a Generic DOM analog to JSON patch - Currently supports JSON patch operations (add/remove/replace/copy/move/test) - `GenerateHierarchicalDeltaPatch` provides a patch generation mechanism that produces forward and inverse patches - Patch application comes with a `PatchApplicationStrategy` functor that allows customizing behavior for patch failure that may be useful for the prefab system - Serialization to/from JSON patch by way of `AZ::Dom::Value` is supported Benchmarks provided, split into three categories on the Dom value benchmark payload (payloads with up to 10k entries populated with strings up to 100 characters in length): - Patch generation based on a deep copy of the affected data ``` DomPatchBenchmark/AzDomPatch_Generate_SimpleReplace_DeepCopy/10/5 0.024 ms 0.024 ms 29867 items_per_second=41.5541k/s DomPatchBenchmark/AzDomPatch_Generate_SimpleReplace_DeepCopy/10/500 0.024 ms 0.024 ms 29867 items_per_second=41.5541k/s DomPatchBenchmark/AzDomPatch_Generate_SimpleReplace_DeepCopy/100/5 0.346 ms 0.345 ms 2036 items_per_second=2.89564k/s DomPatchBenchmark/AzDomPatch_Generate_SimpleReplace_DeepCopy/100/500 0.375 ms 0.377 ms 1867 items_per_second=2.65529k/s DomPatchBenchmark/AzDomPatch_Generate_TopLevelReplace/10/5 0.003 ms 0.003 ms 203636 items_per_second=289.616k/s DomPatchBenchmark/AzDomPatch_Generate_TopLevelReplace/10/500 0.004 ms 0.004 ms 194783 items_per_second=283.321k/s DomPatchBenchmark/AzDomPatch_Generate_TopLevelReplace/100/5 0.003 ms 0.003 ms 203636 items_per_second=289.616k/s DomPatchBenchmark/AzDomPatch_Generate_TopLevelReplace/100/500 0.004 ms 0.004 ms 194783 items_per_second=271.002k/s DomPatchBenchmark/AzDomPatch_Generate_KeyRemove_DeepCopy/10/5 0.023 ms 0.024 ms 29867 items_per_second=42.4775k/s DomPatchBenchmark/AzDomPatch_Generate_KeyRemove_DeepCopy/10/500 0.023 ms 0.023 ms 28000 items_per_second=42.6667k/s DomPatchBenchmark/AzDomPatch_Generate_KeyRemove_DeepCopy/100/5 0.341 ms 0.337 ms 2133 items_per_second=2.96765k/s DomPatchBenchmark/AzDomPatch_Generate_KeyRemove_DeepCopy/100/500 0.365 ms 0.361 ms 1948 items_per_second=2.77049k/s DomPatchBenchmark/AzDomPatch_Generate_ArrayAppend_DeepCopy/10/5 0.023 ms 0.023 ms 29867 items_per_second=43.4429k/s DomPatchBenchmark/AzDomPatch_Generate_ArrayAppend_DeepCopy/10/500 0.023 ms 0.024 ms 29867 items_per_second=42.4775k/s DomPatchBenchmark/AzDomPatch_Generate_ArrayAppend_DeepCopy/100/5 0.330 ms 0.330 ms 2133 items_per_second=3.0336k/s DomPatchBenchmark/AzDomPatch_Generate_ArrayAppend_DeepCopy/100/500 0.359 ms 0.360 ms 1867 items_per_second=2.77879k/s DomPatchBenchmark/AzDomPatch_Generate_ArrayPrepend_DeepCopy/10/5 0.023 ms 0.022 ms 29867 items_per_second=44.4532k/s DomPatchBenchmark/AzDomPatch_Generate_ArrayPrepend_DeepCopy/10/500 0.023 ms 0.023 ms 32000 items_per_second=43.5745k/s DomPatchBenchmark/AzDomPatch_Generate_ArrayPrepend_DeepCopy/100/5 0.329 ms 0.330 ms 2133 items_per_second=3.0336k/s DomPatchBenchmark/AzDomPatch_Generate_ArrayPrepend_DeepCopy/100/500 0.357 ms 0.361 ms 1948 items_per_second=2.77049k/s ``` - Patch generation based on a shallow copy of the affected data (this is faster because when using Dom::Value to copy and mutate, we can bypass expensive array and object comparisons for identical values) ``` DomPatchBenchmark/AzDomPatch_Generate_SimpleReplace_ShallowCopy/10/5 0.010 ms 0.010 ms 74667 items_per_second=99.556k/s DomPatchBenchmark/AzDomPatch_Generate_SimpleReplace_ShallowCopy/10/500 0.010 ms 0.010 ms 74667 items_per_second=97.5242k/s DomPatchBenchmark/AzDomPatch_Generate_SimpleReplace_ShallowCopy/100/5 0.079 ms 0.078 ms 8960 items_per_second=12.7431k/s DomPatchBenchmark/AzDomPatch_Generate_SimpleReplace_ShallowCopy/100/500 0.087 ms 0.087 ms 8960 items_per_second=11.4688k/s DomPatchBenchmark/AzDomPatch_Generate_KeyRemove_ShallowCopy/10/5 0.009 ms 0.009 ms 74667 items_per_second=116.553k/s DomPatchBenchmark/AzDomPatch_Generate_KeyRemove_ShallowCopy/10/500 0.009 ms 0.009 ms 74667 items_per_second=113.778k/s DomPatchBenchmark/AzDomPatch_Generate_KeyRemove_ShallowCopy/100/5 0.072 ms 0.071 ms 8960 items_per_second=13.9863k/s DomPatchBenchmark/AzDomPatch_Generate_KeyRemove_ShallowCopy/100/500 0.087 ms 0.088 ms 7467 items_per_second=11.3783k/s DomPatchBenchmark/AzDomPatch_Generate_ArrayAppend_ShallowCopy/10/5 0.014 ms 0.014 ms 49778 items_per_second=72.4044k/s DomPatchBenchmark/AzDomPatch_Generate_ArrayAppend_ShallowCopy/10/500 0.014 ms 0.014 ms 56000 items_per_second=70.2745k/s DomPatchBenchmark/AzDomPatch_Generate_ArrayAppend_ShallowCopy/100/5 0.118 ms 0.117 ms 5600 items_per_second=8.53333k/s DomPatchBenchmark/AzDomPatch_Generate_ArrayAppend_ShallowCopy/100/500 0.140 ms 0.141 ms 4978 items_per_second=7.07982k/s DomPatchBenchmark/AzDomPatch_Generate_ArrayPrepend_ShallowCopy/10/5 0.009 ms 0.009 ms 89600 items_per_second=108.196k/s DomPatchBenchmark/AzDomPatch_Generate_ArrayPrepend_ShallowCopy/10/500 0.009 ms 0.009 ms 74667 items_per_second=108.607k/s DomPatchBenchmark/AzDomPatch_Generate_ArrayPrepend_ShallowCopy/100/5 0.068 ms 0.068 ms 11200 items_per_second=14.6286k/s DomPatchBenchmark/AzDomPatch_Generate_ArrayPrepend_ShallowCopy/100/500 0.082 ms 0.082 ms 8960 items_per_second=12.2009k/s ``` - Patch application ``` DomPatchBenchmark/AzDomPatch_Apply_SimpleReplace_ShallowCopy/10/5 0.001 ms 0.001 ms 560000 items_per_second=874.146k/s DomPatchBenchmark/AzDomPatch_Apply_SimpleReplace_ShallowCopy/10/500 0.001 ms 0.001 ms 560000 items_per_second=874.146k/s DomPatchBenchmark/AzDomPatch_Apply_SimpleReplace_ShallowCopy/100/5 0.004 ms 0.004 ms 172308 items_per_second=250.63k/s DomPatchBenchmark/AzDomPatch_Apply_SimpleReplace_ShallowCopy/100/500 0.004 ms 0.004 ms 179200 items_per_second=260.655k/s DomPatchBenchmark/AzDomPatch_Apply_SimpleReplace_DeepCopy/10/5 0.005 ms 0.005 ms 112000 items_per_second=193.73k/s DomPatchBenchmark/AzDomPatch_Apply_SimpleReplace_DeepCopy/10/500 0.005 ms 0.005 ms 112000 items_per_second=193.73k/s DomPatchBenchmark/AzDomPatch_Apply_SimpleReplace_DeepCopy/100/5 0.052 ms 0.052 ms 10000 items_per_second=19.3939k/s DomPatchBenchmark/AzDomPatch_Apply_SimpleReplace_DeepCopy/100/500 0.052 ms 0.052 ms 11200 items_per_second=19.373k/s DomPatchBenchmark/AzDomPatch_Apply_TopLevelReplace/10/5 0.001 ms 0.001 ms 640000 items_per_second=910.222k/s DomPatchBenchmark/AzDomPatch_Apply_TopLevelReplace/10/500 0.001 ms 0.001 ms 640000 items_per_second=910.222k/s DomPatchBenchmark/AzDomPatch_Apply_TopLevelReplace/100/5 0.001 ms 0.001 ms 640000 items_per_second=910.222k/s DomPatchBenchmark/AzDomPatch_Apply_TopLevelReplace/100/500 0.001 ms 0.001 ms 640000 items_per_second=910.222k/s DomPatchBenchmark/AzDomPatch_Apply_KeyRemove_ShallowCopy/10/5 0.001 ms 0.001 ms 560000 items_per_second=896k/s DomPatchBenchmark/AzDomPatch_Apply_KeyRemove_ShallowCopy/10/500 0.001 ms 0.001 ms 640000 items_per_second=871.489k/s DomPatchBenchmark/AzDomPatch_Apply_KeyRemove_ShallowCopy/100/5 0.004 ms 0.004 ms 160000 items_per_second=232.727k/s DomPatchBenchmark/AzDomPatch_Apply_KeyRemove_ShallowCopy/100/500 0.004 ms 0.004 ms 165926 items_per_second=235.984k/s DomPatchBenchmark/AzDomPatch_Apply_KeyRemove_DeepCopy/10/5 0.005 ms 0.005 ms 112000 items_per_second=193.73k/s DomPatchBenchmark/AzDomPatch_Apply_KeyRemove_DeepCopy/10/500 0.005 ms 0.005 ms 112000 items_per_second=193.73k/s DomPatchBenchmark/AzDomPatch_Apply_KeyRemove_DeepCopy/100/5 0.053 ms 0.053 ms 10000 items_per_second=18.8235k/s DomPatchBenchmark/AzDomPatch_Apply_KeyRemove_DeepCopy/100/500 0.051 ms 0.052 ms 10000 items_per_second=19.3939k/s DomPatchBenchmark/AzDomPatch_Apply_ArrayAppend_ShallowCopy/10/5 0.001 ms 0.001 ms 497778 items_per_second=692.561k/s DomPatchBenchmark/AzDomPatch_Apply_ArrayAppend_ShallowCopy/10/500 0.001 ms 0.001 ms 497778 items_per_second=692.561k/s DomPatchBenchmark/AzDomPatch_Apply_ArrayAppend_ShallowCopy/100/5 0.006 ms 0.006 ms 112000 items_per_second=174.829k/s DomPatchBenchmark/AzDomPatch_Apply_ArrayAppend_ShallowCopy/100/500 0.006 ms 0.006 ms 100000 items_per_second=177.778k/s DomPatchBenchmark/AzDomPatch_Apply_ArrayAppend_DeepCopy/10/5 0.005 ms 0.005 ms 112000 items_per_second=193.73k/s DomPatchBenchmark/AzDomPatch_Apply_ArrayAppend_DeepCopy/10/500 0.005 ms 0.005 ms 100000 items_per_second=188.235k/s DomPatchBenchmark/AzDomPatch_Apply_ArrayAppend_DeepCopy/100/5 0.053 ms 0.052 ms 11200 items_per_second=19.373k/s DomPatchBenchmark/AzDomPatch_Apply_ArrayAppend_DeepCopy/100/500 0.052 ms 0.053 ms 11200 items_per_second=18.8632k/s DomPatchBenchmark/AzDomPatch_Apply_ArrayPrepend_ShallowCopy/10/5 0.001 ms 0.001 ms 560000 items_per_second=874.146k/s DomPatchBenchmark/AzDomPatch_Apply_ArrayPrepend_ShallowCopy/10/500 0.001 ms 0.001 ms 560000 items_per_second=874.146k/s DomPatchBenchmark/AzDomPatch_Apply_ArrayPrepend_ShallowCopy/100/5 0.004 ms 0.004 ms 179200 items_per_second=260.655k/s DomPatchBenchmark/AzDomPatch_Apply_ArrayPrepend_ShallowCopy/100/500 0.004 ms 0.004 ms 179200 items_per_second=260.655k/s DomPatchBenchmark/AzDomPatch_Apply_ArrayPrepend_DeepCopy/10/5 0.005 ms 0.005 ms 100000 items_per_second=193.939k/s DomPatchBenchmark/AzDomPatch_Apply_ArrayPrepend_DeepCopy/10/500 0.005 ms 0.005 ms 100000 items_per_second=193.939k/s DomPatchBenchmark/AzDomPatch_Apply_ArrayPrepend_DeepCopy/100/5 0.052 ms 0.052 ms 11200 items_per_second=19.373k/s DomPatchBenchmark/AzDomPatch_Apply_ArrayPrepend_DeepCopy/100/500 0.053 ms 0.053 ms 10000 items_per_second=18.8235k/s ``` At a glance, patch generation using `GenerateHierarchicalDeltaPatch` is slower than applying its created patches, but not prohibitively so. Ideally patches shouldn't be recreated unnecessarily, but especially when diffing `Value`s that have been copied and then mutated, a generate + apply operation similar to what prefabs currently do is reasonably fast. Signed-off-by: Nicholas Van Sickle --- Code/Framework/AzCore/AzCore/DOM/DomPatch.cpp | 959 ++++++++++++++++++ Code/Framework/AzCore/AzCore/DOM/DomPatch.h | 191 ++++ Code/Framework/AzCore/AzCore/DOM/DomPath.cpp | 15 +- .../AzCore/AzCore/azcore_files.cmake | 2 + .../AzCore/Tests/DOM/DomPatchBenchmarks.cpp | 179 ++++ .../AzCore/Tests/DOM/DomPatchTests.cpp | 562 ++++++++++ .../AzCore/Tests/azcoretests_files.cmake | 2 + 7 files changed, 1904 insertions(+), 6 deletions(-) create mode 100644 Code/Framework/AzCore/AzCore/DOM/DomPatch.cpp create mode 100644 Code/Framework/AzCore/AzCore/DOM/DomPatch.h create mode 100644 Code/Framework/AzCore/Tests/DOM/DomPatchBenchmarks.cpp create mode 100644 Code/Framework/AzCore/Tests/DOM/DomPatchTests.cpp diff --git a/Code/Framework/AzCore/AzCore/DOM/DomPatch.cpp b/Code/Framework/AzCore/AzCore/DOM/DomPatch.cpp new file mode 100644 index 0000000000..963308e9c8 --- /dev/null +++ b/Code/Framework/AzCore/AzCore/DOM/DomPatch.cpp @@ -0,0 +1,959 @@ +/* + * 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 +#include +#include +#include + +namespace AZ::Dom +{ + PatchOperation::PatchOperation(Path destinationPath, Type type, Value value) + : m_domPath(destinationPath) + , m_type(type) + , m_value(value) + { + } + + PatchOperation::PatchOperation(Path destinationPath, Type type, Path sourcePath) + : m_domPath(destinationPath) + , m_type(type) + , m_value(sourcePath) + { + } + + PatchOperation::PatchOperation(Path destinationPath, Type type) + : m_domPath(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(m_value); + } + + void PatchOperation::SetValue(Value value) + { + m_value = AZStd::move(value); + } + + const Path& PatchOperation::GetSourcePath() const + { + return AZStd::get(m_value); + } + + void PatchOperation::SetSourcePath(Path path) + { + m_value = AZStd::move(path); + } + + AZ::Outcome 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("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::CreateFromDomRepresentation(Value domValue) + { + if (!domValue.IsObject()) + { + return AZ::Failure("PatchOperation failed to load: PatchOperation must be specified as an Object"); + } + + auto loadField = [&](const char* field, AZStd::optional type = {}) -> AZ::Outcome + { + 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("PatchOperation failed to create DOM representation: invalid \"op\" specified"); + } + } + + AZ::Outcome 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(PatchOperation::ReplaceOperation(m_domPath, *existingValue)); + } + } + return AZ::Success(PatchOperation::RemoveOperation(m_domPath)); + } + case Type::Remove: + { + // Remove -> Add + const Value* existingValue = stateBeforeApplication.FindChild(m_domPath); + if (existingValue == nullptr) + { + return AZ::Failure( + AZStd::string::format("Unable to invert DOM remove patch, source path not found: %s", m_domPath.ToString().data())); + } + return AZ::Success(PatchOperation::AddOperation(m_domPath, *existingValue)); + } + case Type::Replace: + { + // Replace -> Replace (with old value) + const Value* existingValue = stateBeforeApplication.FindChild(m_domPath); + if (existingValue == nullptr) + { + return AZ::Failure(AZStd::string::format( + "Unable to invert DOM replace patch, source path not found: %s", m_domPath.ToString().data())); + } + return AZ::Success(PatchOperation::ReplaceOperation(m_domPath, *existingValue)); + } + case Type::Copy: + { + // Copy -> Replace (with old value) + const Value* existingValue = stateBeforeApplication.FindChild(m_domPath); + if (existingValue == nullptr) + { + return AZ::Failure( + AZStd::string::format("Unable to invert DOM copy patch, source path not found: %s", m_domPath.ToString().data())); + } + return AZ::Success(PatchOperation::ReplaceOperation(m_domPath, *existingValue)); + } + case Type::Move: + { + // Move -> Replace, using the common ancestor of the two paths as the replacement + // This is not a minimal inverse, which would be two replace operations at each path + const Path& destPath = m_domPath; + const Path& sourcePath = GetSourcePath(); + + Path commonAncestor; + for (size_t i = 0; i < destPath.Size() && i < sourcePath.Size(); ++i) + { + if (destPath[i] != sourcePath[i]) + { + break; + } + + commonAncestor.Push(destPath[i]); + } + + const Value* existingValue = stateBeforeApplication.FindChild(commonAncestor); + if (existingValue == nullptr) + { + return AZ::Failure(AZStd::string::format( + "Unable to invert DOM move patch, common ancestor path not found: %s", commonAncestor.ToString().data())); + } + return AZ::Success(PatchOperation::ReplaceOperation(commonAncestor, *existingValue)); + } + 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(*this); + } + } + return AZ::Failure("Unable to invert DOM patch, unknown type specified"); + } + + AZ::Outcome PatchOperation::LookupPath( + Value& rootElement, const Path& path, AZ::u8 existenceCheckFlags) + { + const bool verifyFullPath = existenceCheckFlags & VerifyFullPath; + const bool allowEndOfArray = existenceCheckFlags & AllowEndOfArray; + + Path target = path; + if (target.Size() == 0) + { + Value wrapper(Dom::Type::Array); + wrapper.ArrayPushBack(rootElement); + return AZ::Success({ 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("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) + { + return AZ::Failure(AZStd::string::format("Path not found (%s)", target.ToString().data())); + } + + if (destinationIndex.IsIndex() || destinationIndex.IsEndOfArray()) + { + if (!targetValue->IsArray() && !targetValue->IsNode()) + { + return AZ::Failure("Array index specified for a value that is not an array or node"); + } + + if (destinationIndex.IsIndex() && destinationIndex.GetIndex() >= targetValue->ArraySize()) + { + return AZ::Failure("Array index out bounds"); + } + } + else + { + if (!targetValue->IsObject() && !targetValue->IsNode()) + { + return AZ::Failure("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("Key not found in container"); + } + } + } + + return AZ::Success({ *targetValue, AZStd::move(destinationIndex) }); + } + + PatchOperation::PatchOutcome PatchOperation::ApplyAdd(Value& rootElement) const + { + auto pathLookup = LookupPath(rootElement, m_domPath, 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, VerifyFullPath | 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, 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(), VerifyFullPath); + if (!sourceLookup.IsSuccess()) + { + return AZ::Failure(sourceLookup.TakeError()); + } + + auto destLookup = LookupPath(rootElement, m_domPath, 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(), VerifyFullPath); + if (!sourceLookup.IsSuccess()) + { + return AZ::Failure(sourceLookup.TakeError()); + } + + auto destLookup = LookupPath(rootElement, m_domPath, 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, VerifyFullPath); + if (!pathLookup.IsSuccess()) + { + return AZ::Failure(pathLookup.TakeError()); + } + + if (!Utils::DeepCompareIsEqual(rootElement[m_domPath], GetValue())) + { + return AZ::Failure("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 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]; + } + + Patch::OperationsContainer::iterator Patch::begin() + { + return m_operations.begin(); + } + + Patch::OperationsContainer::iterator Patch::end() + { + return m_operations.end(); + } + + Patch::OperationsContainer::const_iterator Patch::begin() const + { + return m_operations.cbegin(); + } + + Patch::OperationsContainer::const_iterator Patch::end() const + { + return m_operations.cend(); + } + + Patch::OperationsContainer::const_iterator Patch::cbegin() const + { + return m_operations.cbegin(); + } + + Patch::OperationsContainer::const_iterator Patch::cend() const + { + return m_operations.cend(); + } + + size_t Patch::size() const + { + return m_operations.size(); + } + + AZ::Outcome 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 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::CreateFromDomRepresentation(Value domValue) + { + if (!domValue.IsArray()) + { + return AZ::Failure("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)); + } + + PatchInfo GenerateHierarchicalDeltaPatch(const Value& beforeState, const Value& afterState) + { + PatchInfo patches; + + auto AddPatch = [&patches](PatchOperation op, PatchOperation inverse) + { + patches.m_forwardPatches.PushBack(AZStd::move(op)); + patches.m_inversePatches.PushFront(AZStd::move(inverse)); + }; + + AZStd::function 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 entriesToCompare; + + AZStd::unordered_set 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 + constexpr size_t replaceThreshold = 3; + size_t changedValueCount = 0; + for (size_t i = 0; i < afterSize; ++i) + { + if (i < beforeSize) + { + if (before[i] != after[i]) + { + ++changedValueCount; + if (changedValueCount >= 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; + } +} // namespace AZ::Dom diff --git a/Code/Framework/AzCore/AzCore/DOM/DomPatch.h b/Code/Framework/AzCore/AzCore/DOM/DomPatch.h new file mode 100644 index 0000000000..9d9ad7560a --- /dev/null +++ b/Code/Framework/AzCore/AzCore/DOM/DomPatch.h @@ -0,0 +1,191 @@ +/* + * 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 +#include + +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; + + //! 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; + + explicit PatchOperation(Path destinationPath, Type type, Value value); + explicit PatchOperation(Path destionationPath, Type type, Path sourcePath); + explicit 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 Apply(Value rootElement) const; + PatchOutcome ApplyInPlace(Value& rootElement) const; + + Value GetDomRepresentation() const; + static AZ::Outcome CreateFromDomRepresentation(Value domValue); + + AZ::Outcome GetInverse(Value stateBeforeApplication) const; + + private: + struct PathContext + { + Value& m_value; + PathEntry m_key; + }; + + static constexpr AZ::u8 DefaultExistenceCheck = 0x0; + static constexpr AZ::u8 VerifyFullPath = 0x1; + static constexpr AZ::u8 AllowEndOfArray = 0x2; + + static AZ::Outcome LookupPath( + Value& rootElement, const Path& path, AZ::u8 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; + + Path m_domPath; + Type m_type; + AZStd::variant m_value; + }; + + class Patch; + + //! The current state of a Patch application operation. + struct PatchApplicationState + { + //! The patch being applied. + const Patch* m_patch = nullptr; + //! The last operation attempted. + const PatchOperation* m_lastOperation = nullptr; + //! The outcome of the last operation, may be overridden to produce a different failure outcome. + PatchOperation::PatchOutcome m_outcome; + //! 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; + using OperationsContainer = AZStd::vector; + + Patch() = default; + Patch(const Patch&) = default; + Patch(Patch&&) = default; + Patch(AZStd::initializer_list init); + + template + 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 Apply(Value rootElement, StrategyFunctor strategy = PatchApplicationStrategy::HaltOnFailure) const; + AZ::Outcome ApplyInPlace(Value& rootElement, StrategyFunctor strategy = PatchApplicationStrategy::HaltOnFailure) const; + + Value GetDomRepresentation() const; + static AZ::Outcome CreateFromDomRepresentation(Value domValue); + + private: + OperationsContainer m_operations; + }; + + //! A set of patches for applying a change and doing the inverse operation (i.e. undoing it). + struct PatchInfo + { + Patch m_forwardPatches; + Patch m_inversePatches; + }; + + //! 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. + PatchInfo GenerateHierarchicalDeltaPatch(const Value& beforeState, const Value& afterState); +} // namespace AZ::Dom diff --git a/Code/Framework/AzCore/AzCore/DOM/DomPath.cpp b/Code/Framework/AzCore/AzCore/DOM/DomPath.cpp index bc50a8513c..a5d130d48c 100644 --- a/Code/Framework/AzCore/AzCore/DOM/DomPath.cpp +++ b/Code/Framework/AzCore/AzCore/DOM/DomPath.cpp @@ -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(&m_value); + return internalValue == nullptr ? false : (*internalValue) == value; } bool PathEntry::operator==(const AZ::Name& key) const { - return IsKey() && GetKey() == key; + const AZ::Name* internalValue = AZStd::get_if(&m_value); + return internalValue == nullptr ? false : (*internalValue) == key; } bool PathEntry::operator==(AZStd::string_view key) const { - return IsKey() && GetKey() == AZ::Name(key); + const AZ::Name* internalValue = AZStd::get_if(&m_value); + return internalValue == nullptr ? false : (*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() diff --git a/Code/Framework/AzCore/AzCore/azcore_files.cmake b/Code/Framework/AzCore/AzCore/azcore_files.cmake index 2c0c318648..c5928f1c84 100644 --- a/Code/Framework/AzCore/AzCore/azcore_files.cmake +++ b/Code/Framework/AzCore/AzCore/azcore_files.cmake @@ -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 diff --git a/Code/Framework/AzCore/Tests/DOM/DomPatchBenchmarks.cpp b/Code/Framework/AzCore/Tests/DOM/DomPatchBenchmarks.cpp new file mode 100644 index 0000000000..de19ee1783 --- /dev/null +++ b/Code/Framework/AzCore/Tests/DOM/DomPatchBenchmarks.cpp @@ -0,0 +1,179 @@ +/* + * 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 +#include +#include +#include +#include +#include + +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 diff --git a/Code/Framework/AzCore/Tests/DOM/DomPatchTests.cpp b/Code/Framework/AzCore/Tests/DOM/DomPatchTests.cpp new file mode 100644 index 0000000000..a0e4f349a5 --- /dev/null +++ b/Code/Framework/AzCore/Tests/DOM/DomPatchTests.cpp @@ -0,0 +1,562 @@ +/* + * 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 +#include + +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(); + } + + PatchInfo GenerateAndVerifyDelta() + { + PatchInfo 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 diff --git a/Code/Framework/AzCore/Tests/azcoretests_files.cmake b/Code/Framework/AzCore/Tests/azcoretests_files.cmake index 24a6601cf0..86bf943a5a 100644 --- a/Code/Framework/AzCore/Tests/azcoretests_files.cmake +++ b/Code/Framework/AzCore/Tests/azcoretests_files.cmake @@ -221,6 +221,8 @@ set(FILES DOM/DomJsonBenchmarks.cpp DOM/DomPathTests.cpp DOM/DomPathBenchmarks.cpp + DOM/DomPatchTests.cpp + DOM/DomPatchBenchmarks.cpp DOM/DomValueTests.cpp DOM/DomValueBenchmarks.cpp ) From 73f166241e33edd33d2211c8c91a1dcf786fb3e5 Mon Sep 17 00:00:00 2001 From: Nicholas Van Sickle Date: Thu, 27 Jan 2022 13:15:30 -0800 Subject: [PATCH 02/29] Address review feedback Signed-off-by: Nicholas Van Sickle --- Code/Framework/AzCore/AzCore/DOM/DomPatch.cpp | 101 ++++++++++-------- Code/Framework/AzCore/AzCore/DOM/DomPatch.h | 35 ++++-- Code/Framework/AzCore/AzCore/DOM/DomPath.cpp | 18 +++- Code/Framework/AzCore/AzCore/DOM/DomPath.h | 11 +- .../AzCore/Tests/DOM/DomPathBenchmarks.cpp | 21 ++++ .../AzCore/Tests/DOM/DomPathTests.cpp | 19 ++++ 6 files changed, 143 insertions(+), 62 deletions(-) diff --git a/Code/Framework/AzCore/AzCore/DOM/DomPatch.cpp b/Code/Framework/AzCore/AzCore/DOM/DomPatch.cpp index 963308e9c8..6e53de0689 100644 --- a/Code/Framework/AzCore/AzCore/DOM/DomPatch.cpp +++ b/Code/Framework/AzCore/AzCore/DOM/DomPatch.cpp @@ -8,8 +8,8 @@ #include #include -#include #include +#include namespace AZ::Dom { @@ -257,7 +257,8 @@ namespace AZ::Dom return AZ::Failure(sourceLoad.TakeError()); } - return AZ::Success(PatchOperation::CopyOperation(Path(destLoad.GetValue().GetString()), Path(sourceLoad.GetValue().GetString()))); + return AZ::Success( + PatchOperation::CopyOperation(Path(destLoad.GetValue().GetString()), Path(sourceLoad.GetValue().GetString()))); } else if (op == "move") { @@ -272,7 +273,8 @@ namespace AZ::Dom return AZ::Failure(sourceLoad.TakeError()); } - return AZ::Success(PatchOperation::MoveOperation(Path(destLoad.GetValue().GetString()), Path(sourceLoad.GetValue().GetString()))); + return AZ::Success( + PatchOperation::MoveOperation(Path(destLoad.GetValue().GetString()), Path(sourceLoad.GetValue().GetString()))); } else if (op == "test") { @@ -319,8 +321,9 @@ namespace AZ::Dom const Value* existingValue = stateBeforeApplication.FindChild(m_domPath); if (existingValue == nullptr) { - return AZ::Failure( - AZStd::string::format("Unable to invert DOM remove patch, source path not found: %s", m_domPath.ToString().data())); + 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(PatchOperation::AddOperation(m_domPath, *existingValue)); } @@ -330,8 +333,9 @@ namespace AZ::Dom const Value* existingValue = stateBeforeApplication.FindChild(m_domPath); if (existingValue == nullptr) { - return AZ::Failure(AZStd::string::format( - "Unable to invert DOM replace patch, source path not found: %s", m_domPath.ToString().data())); + 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(PatchOperation::ReplaceOperation(m_domPath, *existingValue)); } @@ -341,8 +345,9 @@ namespace AZ::Dom const Value* existingValue = stateBeforeApplication.FindChild(m_domPath); if (existingValue == nullptr) { - return AZ::Failure( - AZStd::string::format("Unable to invert DOM copy patch, source path not found: %s", m_domPath.ToString().data())); + 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(PatchOperation::ReplaceOperation(m_domPath, *existingValue)); } @@ -367,8 +372,9 @@ namespace AZ::Dom const Value* existingValue = stateBeforeApplication.FindChild(commonAncestor); if (existingValue == nullptr) { - return AZ::Failure(AZStd::string::format( - "Unable to invert DOM move patch, common ancestor path not found: %s", commonAncestor.ToString().data())); + AZStd::string errorMessage = "Unable to invert DOM copy patch, common ancestor path not found: "; + commonAncestor.AppendToString(errorMessage); + return AZ::Failure(AZStd::move(errorMessage)); } return AZ::Success(PatchOperation::ReplaceOperation(commonAncestor, *existingValue)); } @@ -383,10 +389,10 @@ namespace AZ::Dom } AZ::Outcome PatchOperation::LookupPath( - Value& rootElement, const Path& path, AZ::u8 existenceCheckFlags) + Value& rootElement, const Path& path, ExistenceCheckFlags flags) { - const bool verifyFullPath = existenceCheckFlags & VerifyFullPath; - const bool allowEndOfArray = existenceCheckFlags & AllowEndOfArray; + const bool verifyFullPath = (flags & ExistenceCheckFlags::VerifyFullPath) != ExistenceCheckFlags::DefaultExistenceCheck; + const bool allowEndOfArray = (flags & ExistenceCheckFlags::AllowEndOfArray) != ExistenceCheckFlags::DefaultExistenceCheck; Path target = path; if (target.Size() == 0) @@ -414,7 +420,9 @@ namespace AZ::Dom Value* targetValue = rootElement.FindMutableChild(target); if (targetValue == nullptr) { - return AZ::Failure(AZStd::string::format("Path not found (%s)", target.ToString().data())); + AZStd::string errorMessage = "Path not found: "; + target.AppendToString(errorMessage); + return AZ::Failure(AZStd::move(errorMessage)); } if (destinationIndex.IsIndex() || destinationIndex.IsEndOfArray()) @@ -450,7 +458,7 @@ namespace AZ::Dom PatchOperation::PatchOutcome PatchOperation::ApplyAdd(Value& rootElement) const { - auto pathLookup = LookupPath(rootElement, m_domPath, AllowEndOfArray); + auto pathLookup = LookupPath(rootElement, m_domPath, ExistenceCheckFlags::AllowEndOfArray); if (!pathLookup.IsSuccess()) { return AZ::Failure(pathLookup.TakeError()); @@ -481,7 +489,7 @@ namespace AZ::Dom PatchOperation::PatchOutcome PatchOperation::ApplyRemove(Value& rootElement) const { - auto pathLookup = LookupPath(rootElement, m_domPath, VerifyFullPath | AllowEndOfArray); + auto pathLookup = LookupPath(rootElement, m_domPath, ExistenceCheckFlags::VerifyFullPath | ExistenceCheckFlags::AllowEndOfArray); if (!pathLookup.IsSuccess()) { return AZ::Failure(pathLookup.TakeError()); @@ -505,7 +513,7 @@ namespace AZ::Dom PatchOperation::PatchOutcome PatchOperation::ApplyReplace(Value& rootElement) const { - auto pathLookup = LookupPath(rootElement, m_domPath, VerifyFullPath); + auto pathLookup = LookupPath(rootElement, m_domPath, ExistenceCheckFlags::VerifyFullPath); if (!pathLookup.IsSuccess()) { return AZ::Failure(pathLookup.TakeError()); @@ -517,13 +525,13 @@ namespace AZ::Dom PatchOperation::PatchOutcome PatchOperation::ApplyCopy(Value& rootElement) const { - auto sourceLookup = LookupPath(rootElement, GetSourcePath(), VerifyFullPath); + auto sourceLookup = LookupPath(rootElement, GetSourcePath(), ExistenceCheckFlags::VerifyFullPath); if (!sourceLookup.IsSuccess()) { return AZ::Failure(sourceLookup.TakeError()); } - auto destLookup = LookupPath(rootElement, m_domPath, AllowEndOfArray); + auto destLookup = LookupPath(rootElement, m_domPath, ExistenceCheckFlags::AllowEndOfArray); if (!destLookup.IsSuccess()) { return AZ::Failure(destLookup.TakeError()); @@ -535,13 +543,13 @@ namespace AZ::Dom PatchOperation::PatchOutcome PatchOperation::ApplyMove(Value& rootElement) const { - auto sourceLookup = LookupPath(rootElement, GetSourcePath(), VerifyFullPath); + auto sourceLookup = LookupPath(rootElement, GetSourcePath(), ExistenceCheckFlags::VerifyFullPath); if (!sourceLookup.IsSuccess()) { return AZ::Failure(sourceLookup.TakeError()); } - auto destLookup = LookupPath(rootElement, m_domPath, AllowEndOfArray); + auto destLookup = LookupPath(rootElement, m_domPath, ExistenceCheckFlags::AllowEndOfArray); if (!destLookup.IsSuccess()) { return AZ::Failure(destLookup.TakeError()); @@ -568,7 +576,7 @@ namespace AZ::Dom PatchOperation::PatchOutcome PatchOperation::ApplyTest(Value& rootElement) const { - auto pathLookup = LookupPath(rootElement, m_domPath, VerifyFullPath); + auto pathLookup = LookupPath(rootElement, m_domPath, ExistenceCheckFlags::VerifyFullPath); if (!pathLookup.IsSuccess()) { return AZ::Failure(pathLookup.TakeError()); @@ -670,32 +678,32 @@ namespace AZ::Dom return m_operations[index]; } - Patch::OperationsContainer::iterator Patch::begin() + auto Patch::begin() -> OperationsContainer::iterator { return m_operations.begin(); } - Patch::OperationsContainer::iterator Patch::end() + auto Patch::end() -> OperationsContainer::iterator { return m_operations.end(); } - Patch::OperationsContainer::const_iterator Patch::begin() const + auto Patch::begin() const -> OperationsContainer::const_iterator { return m_operations.cbegin(); } - Patch::OperationsContainer::const_iterator Patch::end() const + auto Patch::end() const -> OperationsContainer::const_iterator { return m_operations.cend(); } - Patch::OperationsContainer::const_iterator Patch::cbegin() const + auto Patch::cbegin() const -> OperationsContainer::const_iterator { return m_operations.cbegin(); } - Patch::OperationsContainer::const_iterator Patch::cend() const + auto Patch::cend() const -> OperationsContainer::const_iterator { return m_operations.cend(); } @@ -794,7 +802,8 @@ namespace AZ::Dom return PatchOperation(AZStd::move(testPath), PatchOperation::Type::Test, AZStd::move(value)); } - PatchInfo GenerateHierarchicalDeltaPatch(const Value& beforeState, const Value& afterState) + PatchInfo GenerateHierarchicalDeltaPatch( + const Value& beforeState, const Value& afterState, const DeltaPatchGenerationParameters& params) { PatchInfo patches; @@ -804,7 +813,7 @@ namespace AZ::Dom patches.m_inversePatches.PushFront(AZStd::move(inverse)); }; - AZStd::function CompareValues; + AZStd::function compareValues; struct PendingComparison { @@ -822,7 +831,7 @@ namespace AZ::Dom AZStd::queue entriesToCompare; AZStd::unordered_set desiredKeys; - auto CompareObjects = [&](const Path& path, const Value& before, const Value& after) + auto compareObjects = [&](const Path& path, const Value& before, const Value& after) { desiredKeys.clear(); Path subPath = path; @@ -853,22 +862,22 @@ namespace AZ::Dom } }; - auto CompareArrays = [&](const Path& path, const Value& before, const Value& after) + 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 - constexpr size_t replaceThreshold = 3; - size_t changedValueCount = 0; - for (size_t i = 0; i < afterSize; ++i) + if (params.m_replaceThreshold != DeltaPatchGenerationParameters::NoReplace) { - if (i < beforeSize) + 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 >= replaceThreshold) + if (changedValueCount >= params.m_replaceThreshold) { AddPatch(PatchOperation::ReplaceOperation(path, after), PatchOperation::ReplaceOperation(path, before)); return; @@ -904,7 +913,7 @@ namespace AZ::Dom } }; - auto CompareNodes = [&](const Path& path, const Value& before, const Value& after) + auto compareNodes = [&](const Path& path, const Value& before, const Value& after) { if (before.GetNodeName() != after.GetNodeName()) { @@ -912,12 +921,12 @@ namespace AZ::Dom } else { - CompareObjects(path, before, after); - CompareArrays(path, before, after); + compareObjects(path, before, after); + compareArrays(path, before, after); } }; - CompareValues = [&](const Path& path, const Value& before, const Value& after) + compareValues = [&](const Path& path, const Value& before, const Value& after) { if (before.GetType() != after.GetType()) { @@ -931,15 +940,15 @@ namespace AZ::Dom } else if (before.IsObject()) { - CompareObjects(path, before, after); + compareObjects(path, before, after); } else if (before.IsArray()) { - CompareArrays(path, before, after); + compareArrays(path, before, after); } else if (before.IsNode()) { - CompareNodes(path, before, after); + compareNodes(path, before, after); } else { @@ -951,7 +960,7 @@ namespace AZ::Dom while (!entriesToCompare.empty()) { PendingComparison& comparison = entriesToCompare.front(); - CompareValues(comparison.m_path, comparison.m_before, comparison.m_after); + compareValues(comparison.m_path, comparison.m_before, comparison.m_after); entriesToCompare.pop(); } return patches; diff --git a/Code/Framework/AzCore/AzCore/DOM/DomPatch.h b/Code/Framework/AzCore/AzCore/DOM/DomPatch.h index 9d9ad7560a..633a611c73 100644 --- a/Code/Framework/AzCore/AzCore/DOM/DomPatch.h +++ b/Code/Framework/AzCore/AzCore/DOM/DomPatch.h @@ -35,9 +35,9 @@ namespace AZ::Dom PatchOperation(const PatchOperation&) = default; PatchOperation(PatchOperation&&) = default; - explicit PatchOperation(Path destinationPath, Type type, Value value); - explicit PatchOperation(Path destionationPath, Type type, Path sourcePath); - explicit PatchOperation(Path path, Type type); + 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); @@ -72,6 +72,13 @@ namespace AZ::Dom AZ::Outcome GetInverse(Value stateBeforeApplication) const; + enum class ExistenceCheckFlags : AZ::u8 + { + DefaultExistenceCheck = 0x0, + VerifyFullPath = 0x1, + AllowEndOfArray = 0x2, + }; + private: struct PathContext { @@ -79,12 +86,8 @@ namespace AZ::Dom PathEntry m_key; }; - static constexpr AZ::u8 DefaultExistenceCheck = 0x0; - static constexpr AZ::u8 VerifyFullPath = 0x1; - static constexpr AZ::u8 AllowEndOfArray = 0x2; - static AZ::Outcome LookupPath( - Value& rootElement, const Path& path, AZ::u8 existenceCheckFlags = DefaultExistenceCheck); + Value& rootElement, const Path& path, ExistenceCheckFlags existenceCheckFlags = ExistenceCheckFlags::DefaultExistenceCheck); PatchOutcome ApplyAdd(Value& rootElement) const; PatchOutcome ApplyRemove(Value& rootElement) const; @@ -93,11 +96,13 @@ namespace AZ::Dom PatchOutcome ApplyMove(Value& rootElement) const; PatchOutcome ApplyTest(Value& rootElement) const; + AZStd::variant m_value; Path m_domPath; Type m_type; - AZStd::variant m_value; }; + AZ_DEFINE_ENUM_BITWISE_OPERATORS(PatchOperation::ExistenceCheckFlags); + class Patch; //! The current state of a Patch application operation. @@ -184,8 +189,18 @@ namespace AZ::Dom Patch m_inversePatches; }; + //! Parameters for GenerateHierarchicalDeltaPatch. + struct DeltaPatchGenerationParameters + { + static constexpr size_t NoReplace = AZStd::numeric_limits::max(); + + //! 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. - PatchInfo GenerateHierarchicalDeltaPatch(const Value& beforeState, const Value& afterState); + PatchInfo GenerateHierarchicalDeltaPatch(const Value& beforeState, const Value& afterState, const DeltaPatchGenerationParameters& params = {}); } // namespace AZ::Dom diff --git a/Code/Framework/AzCore/AzCore/DOM/DomPath.cpp b/Code/Framework/AzCore/AzCore/DOM/DomPath.cpp index a5d130d48c..f98dad4adb 100644 --- a/Code/Framework/AzCore/AzCore/DOM/DomPath.cpp +++ b/Code/Framework/AzCore/AzCore/DOM/DomPath.cpp @@ -54,19 +54,19 @@ namespace AZ::Dom bool PathEntry::operator==(size_t value) const { const size_t* internalValue = AZStd::get_if(&m_value); - return internalValue == nullptr ? false : (*internalValue) == value; + return internalValue != nullptr && *internalValue == value; } bool PathEntry::operator==(const AZ::Name& key) const { const AZ::Name* internalValue = AZStd::get_if(&m_value); - return internalValue == nullptr ? false : (*internalValue) == key; + return internalValue != nullptr && *internalValue == key; } bool PathEntry::operator==(AZStd::string_view key) const { const AZ::Name* internalValue = AZStd::get_if(&m_value); - return internalValue == nullptr ? false : (*internalValue) == AZ::Name(key); + return internalValue != nullptr && *internalValue == AZ::Name(key); } bool PathEntry::operator!=(const PathEntry& other) const @@ -323,13 +323,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; } @@ -360,6 +360,11 @@ namespace AZ::Dom for (const PathEntry& entry : m_entries) { + if (bufferIndex >= bufferSize) + { + return bufferIndex; + } + putChar(PathSeparator); if (entry.IsEndOfArray()) { @@ -375,7 +380,10 @@ namespace AZ::Dom } } + size_t bytesWritten = bufferIndex; putChar('\0'); + + return bytesWritten; } AZStd::string Path::ToString() const diff --git a/Code/Framework/AzCore/AzCore/DOM/DomPath.h b/Code/Framework/AzCore/AzCore/DOM/DomPath.h index 39f7c7da98..f1a95b4e70 100644 --- a/Code/Framework/AzCore/AzCore/DOM/DomPath.h +++ b/Code/Framework/AzCore/AzCore/DOM/DomPath.h @@ -128,10 +128,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 + 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" diff --git a/Code/Framework/AzCore/Tests/DOM/DomPathBenchmarks.cpp b/Code/Framework/AzCore/Tests/DOM/DomPathBenchmarks.cpp index 624b24cd20..c3c8139f6c 100644 --- a/Code/Framework/AzCore/Tests/DOM/DomPathBenchmarks.cpp +++ b/Code/Framework/AzCore/Tests/DOM/DomPathBenchmarks.cpp @@ -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) + { + name == name; + name == index; + name == endOfArray; + index == index; + index == endOfArray; + endOfArray == endOfArray; + } + + state.SetItemsProcessed(6 * state.iterations()); + } + BENCHMARK_REGISTER_F(DomPathBenchmark, DomPathEntry_Comparison); } diff --git a/Code/Framework/AzCore/Tests/DOM/DomPathTests.cpp b/Code/Framework/AzCore/Tests/DOM/DomPathTests.cpp index 54bff9f29b..5f3aa946b2 100644 --- a/Code/Framework/AzCore/Tests/DOM/DomPathTests.cpp +++ b/Code/Framework/AzCore/Tests/DOM/DomPathTests.cpp @@ -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 From 155cc5845b656b1a9c31b100ac6e5379e24e74eb Mon Sep 17 00:00:00 2001 From: Nicholas Van Sickle Date: Thu, 27 Jan 2022 13:44:35 -0800 Subject: [PATCH 03/29] Add Path::IsEmpty Signed-off-by: Nicholas Van Sickle --- Code/Framework/AzCore/AzCore/DOM/DomPatch.cpp | 2 +- Code/Framework/AzCore/AzCore/DOM/DomPath.cpp | 5 +++++ Code/Framework/AzCore/AzCore/DOM/DomPath.h | 1 + 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/Code/Framework/AzCore/AzCore/DOM/DomPatch.cpp b/Code/Framework/AzCore/AzCore/DOM/DomPatch.cpp index 6e53de0689..44c16baa16 100644 --- a/Code/Framework/AzCore/AzCore/DOM/DomPatch.cpp +++ b/Code/Framework/AzCore/AzCore/DOM/DomPatch.cpp @@ -395,7 +395,7 @@ namespace AZ::Dom const bool allowEndOfArray = (flags & ExistenceCheckFlags::AllowEndOfArray) != ExistenceCheckFlags::DefaultExistenceCheck; Path target = path; - if (target.Size() == 0) + if (target.IsEmpty()) { Value wrapper(Dom::Type::Array); wrapper.ArrayPushBack(rootElement); diff --git a/Code/Framework/AzCore/AzCore/DOM/DomPath.cpp b/Code/Framework/AzCore/AzCore/DOM/DomPath.cpp index f98dad4adb..5f6438518f 100644 --- a/Code/Framework/AzCore/AzCore/DOM/DomPath.cpp +++ b/Code/Framework/AzCore/AzCore/DOM/DomPath.cpp @@ -246,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]; diff --git a/Code/Framework/AzCore/AzCore/DOM/DomPath.h b/Code/Framework/AzCore/AzCore/DOM/DomPath.h index f1a95b4e70..7a031a2c68 100644 --- a/Code/Framework/AzCore/AzCore/DOM/DomPath.h +++ b/Code/Framework/AzCore/AzCore/DOM/DomPath.h @@ -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; From 6322dd5397d2a91c86bed2f2b8b1c378da549c03 Mon Sep 17 00:00:00 2001 From: Chris Galvan Date: Thu, 3 Feb 2022 12:29:18 -0600 Subject: [PATCH 04/29] Updated the image gradient component to use a streaming image asset Signed-off-by: Chris Galvan --- .../BuilderSettings/ImageProcessingDefines.h | 14 --- .../Code/Source/ImageBuilderComponent.cpp | 5 +- .../Source/ImageProcessingSystemComponent.cpp | 5 +- .../Include/Atom/RPI.Reflect/Image/Image.h | 14 +++ .../RPI/Code/Source/RPI.Public/RPIUtils.cpp | 12 +-- Gems/GradientSignal/Code/CMakeLists.txt | 3 + .../Components/ImageGradientComponent.h | 18 +++- .../Code/Include/GradientSignal/ImageAsset.h | 3 +- .../Components/ImageGradientComponent.cpp | 92 ++++++++++++++++++- .../GradientSignal/Code/Source/ImageAsset.cpp | 27 +++--- 10 files changed, 150 insertions(+), 43 deletions(-) diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/BuilderSettings/ImageProcessingDefines.h b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/BuilderSettings/ImageProcessingDefines.h index 7c35a0634e..dcd95c7fb6 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/BuilderSettings/ImageProcessingDefines.h +++ b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/BuilderSettings/ImageProcessingDefines.h @@ -49,20 +49,6 @@ 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] = { - "*.tif", - "*.tiff", - "*.png", - "*.bmp", - "*.jpg", - "*.jpeg", - "*.tga", - "*.gif", - "*.dds", - "*.exr" - }; - enum class RGBWeight : AZ::u32 { uniform, // uniform weights (1.0, 1.0, 1.0) (default) diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/ImageBuilderComponent.cpp b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/ImageBuilderComponent.cpp index 55a6c1ea8f..8c2e61ff6d 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/ImageBuilderComponent.cpp +++ b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/ImageBuilderComponent.cpp @@ -27,6 +27,7 @@ #include #include +#include #include namespace ImageProcessingAtom @@ -66,9 +67,9 @@ namespace ImageProcessingAtom AssetBuilderSDK::AssetBuilderDesc builderDescriptor; builderDescriptor.m_name = "Atom Image Builder"; - for (int i = 0; i < s_TotalSupportedImageExtensions; i++) + for (int i = 0; i < AZ::RPI::s_TotalSupportedImageExtensions; i++) { - builderDescriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern(s_SupportedImageExtensions[i], AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard)); + builderDescriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern(AZ::RPI::s_SupportedImageExtensions[i], AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard)); } builderDescriptor.m_busId = azrtti_typeid(); diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/ImageProcessingSystemComponent.cpp b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/ImageProcessingSystemComponent.cpp index 665d08b3ae..f0d881c416 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/ImageProcessingSystemComponent.cpp +++ b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/ImageProcessingSystemComponent.cpp @@ -7,6 +7,7 @@ */ +#include #include #include @@ -223,9 +224,9 @@ namespace ImageProcessingAtom { AZStd::string targetExtension = entry->GetExtension(); - for (int i = 0; i < s_TotalSupportedImageExtensions; i++) + for (int i = 0; i < AZ::RPI::s_TotalSupportedImageExtensions; i++) { - if (AZStd::wildcard_match(s_SupportedImageExtensions[i], targetExtension.c_str())) + if (AZStd::wildcard_match(AZ::RPI::s_SupportedImageExtensions[i], targetExtension.c_str())) { return true; } diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Image/Image.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Image/Image.h index 185666626c..66c91e92cf 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Image/Image.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Image/Image.h @@ -22,6 +22,20 @@ namespace AZ { class ImageAsset; + static const int s_TotalSupportedImageExtensions = 10; + static const char* s_SupportedImageExtensions[s_TotalSupportedImageExtensions] = { + "*.tif", + "*.tiff", + "*.png", + "*.bmp", + "*.jpg", + "*.jpeg", + "*.tga", + "*.gif", + "*.dds", + "*.exr" + }; + //! A base class for images providing access to common image information. class Image : public Data::InstanceData diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/RPIUtils.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/RPIUtils.cpp index e13db253f3..9ff26f0d3a 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/RPIUtils.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/RPIUtils.cpp @@ -133,7 +133,8 @@ namespace AZ case AZ::RHI::Format::R16G16_UNORM: case AZ::RHI::Format::R16G16B16A16_UNORM: { - return mem[index] / static_cast(std::numeric_limits::max()); + auto actualMem = reinterpret_cast(mem); + return actualMem[index] / static_cast(std::numeric_limits::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); diff --git a/Gems/GradientSignal/Code/CMakeLists.txt b/Gems/GradientSignal/Code/CMakeLists.txt index d2d8f8c696..febf7d9cd4 100644 --- a/Gems/GradientSignal/Code/CMakeLists.txt +++ b/Gems/GradientSignal/Code/CMakeLists.txt @@ -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 diff --git a/Gems/GradientSignal/Code/Include/GradientSignal/Components/ImageGradientComponent.h b/Gems/GradientSignal/Code/Include/GradientSignal/Components/ImageGradientComponent.h index 044e9d91b1..0cbd8bd4c7 100644 --- a/Gems/GradientSignal/Code/Include/GradientSignal/Components/ImageGradientComponent.h +++ b/Gems/GradientSignal/Code/Include/GradientSignal/Components/ImageGradientComponent.h @@ -8,8 +8,11 @@ #pragma once +#include #include #include +#include +#include #include #include #include @@ -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 m_imageAsset = { AZ::Data::AssetLoadBehavior::QueueLoad }; + AZ::Data::Asset m_imageAsset = { AZ::Data::AssetLoadBehavior::QueueLoad }; float m_tilingX = 1.0f; float m_tilingY = 1.0f; }; diff --git a/Gems/GradientSignal/Code/Include/GradientSignal/ImageAsset.h b/Gems/GradientSignal/Code/Include/GradientSignal/ImageAsset.h index 4ffab0b4b4..811d74082d 100644 --- a/Gems/GradientSignal/Code/Include/GradientSignal/ImageAsset.h +++ b/Gems/GradientSignal/Code/Include/GradientSignal/ImageAsset.h @@ -12,6 +12,7 @@ #include #include #include +#include namespace AZ { @@ -61,6 +62,6 @@ namespace GradientSignal } }; - float GetValueFromImageAsset(const AZ::Data::Asset& imageAsset, const AZ::Vector3& uvw, float tilingX, float tilingY, float defaultValue); + float GetValueFromImageAsset(const AZ::Data::Asset& imageAsset, const AZ::Vector3& uvw, float tilingX, float tilingY, float defaultValue); } // namespace GradientSignal diff --git a/Gems/GradientSignal/Code/Source/Components/ImageGradientComponent.cpp b/Gems/GradientSignal/Code/Source/Components/ImageGradientComponent.cpp index 3639d5c6df..e3f128ec95 100644 --- a/Gems/GradientSignal/Code/Source/Components/ImageGradientComponent.cpp +++ b/Gems/GradientSignal/Code/Source/Components/ImageGradientComponent.cpp @@ -7,6 +7,7 @@ */ #include +#include #include #include #include @@ -18,13 +19,100 @@ namespace GradientSignal { + AZ::JsonSerializationResult::Result JsonImageGradientConfigSerializer::Load( + void* outputValue, [[maybe_unused]] const AZ::Uuid& outputValueTypeId, + const rapidjson::Value& inputValue, AZ::JsonDeserializerContext& context) + { + namespace JSR = AZ::JsonSerializationResult; + + auto configInstance = reinterpret_cast(outputValue); + AZ_Assert(configInstance, "Output value for JsonImageGradientConfigSerializer can't be null."); + + JSR::ResultCode result(JSR::Tasks::ReadField); + + rapidjson::Value::ConstMemberIterator itr = inputValue.FindMember("ImageAsset"); + if (itr != inputValue.MemberEnd()) + { + // 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(), 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 (int i = 0; i < AZ::RPI::s_TotalSupportedImageExtensions; i++) + { + AZStd::string imageExtension(AZ::RPI::s_SupportedImageExtensions[i]); + + // 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>(), false); + if (fixedAssetId.IsValid()) + { + break; + } + } + } + } + + // Replace the old gradimage with new AssetId for streaming image asset (if we needed to replace) + if (fixedAssetId.IsValid()) + { + configInstance->m_imageAsset = AZ::Data::AssetManager::Instance().GetAsset(fixedAssetId, AZ::Data::AssetLoadBehavior::QueueLoad); + } + // Otherwise, this was already a streaming image asset, so just load it like normal + else + { + result.Combine(ContinueLoadingFromJsonObjectField( + &configInstance->m_imageAsset, azrtti_typeidm_imageAsset)>(), inputValue, "ImageAsset", context)); + } + } + + result.Combine(ContinueLoadingFromJsonObjectField( + &configInstance->m_tilingX, azrtti_typeidm_tilingX)>(), inputValue, "TilingX", context)); + + result.Combine(ContinueLoadingFromJsonObjectField( + &configInstance->m_tilingY, azrtti_typeidm_tilingY)>(), inputValue, "TilingY", context)); + + 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(context)) + { + jsonContext->Serializer()->HandlesType(); + } + AZ::SerializeContext* serialize = azrtti_cast(context); if (serialize) { serialize->Class() - ->Version(1) + ->Version(2) ->Field("ImageAsset", &ImageGradientConfig::m_imageAsset) ->Field("TilingX", &ImageGradientConfig::m_tilingX) ->Field("TilingY", &ImageGradientConfig::m_tilingY) @@ -265,7 +353,7 @@ namespace GradientSignal { AZStd::unique_lock imageLock(m_imageMutex); - m_configuration.m_imageAsset = AZ::Data::AssetManager::Instance().FindOrCreateAsset(assetId, azrtti_typeid(), m_configuration.m_imageAsset.GetAutoLoadBehavior()); + m_configuration.m_imageAsset = AZ::Data::AssetManager::Instance().FindOrCreateAsset(assetId, azrtti_typeid(), m_configuration.m_imageAsset.GetAutoLoadBehavior()); } SetupDependencies(); diff --git a/Gems/GradientSignal/Code/Source/ImageAsset.cpp b/Gems/GradientSignal/Code/Source/ImageAsset.cpp index 7e5667a5be..6ccd9e0a2a 100644 --- a/Gems/GradientSignal/Code/Source/ImageAsset.cpp +++ b/Gems/GradientSignal/Code/Source/ImageAsset.cpp @@ -15,6 +15,7 @@ #include #include +#include #include #include @@ -152,17 +153,15 @@ namespace GradientSignal return true; } - float GetValueFromImageAsset(const AZ::Data::Asset& imageAsset, const AZ::Vector3& uvw, float tilingX, float tilingY, float defaultValue) + float GetValueFromImageAsset(const AZ::Data::Asset& 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(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 +184,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 +194,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(pixelLookup.GetX()) % image->m_imageWidth; - size_t y = static_cast(pixelLookup.GetY()) % image->m_imageHeight; + auto x = aznumeric_cast(pixelLookup.GetX()) % width; + auto y = aznumeric_cast(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(imageAsset, x, y); } } From 4e15cef2fca68d34b77256da8fc68d474ac97b03 Mon Sep 17 00:00:00 2001 From: Chris Galvan Date: Thu, 3 Feb 2022 13:49:54 -0600 Subject: [PATCH 05/29] Removed unused methods to fix compile error on linux Signed-off-by: Chris Galvan --- .../GradientSignal/Code/Source/ImageAsset.cpp | 75 ------------------- 1 file changed, 75 deletions(-) diff --git a/Gems/GradientSignal/Code/Source/ImageAsset.cpp b/Gems/GradientSignal/Code/Source/ImageAsset.cpp index 6ccd9e0a2a..0f91a3ea08 100644 --- a/Gems/GradientSignal/Code/Source/ImageAsset.cpp +++ b/Gems/GradientSignal/Code/Source/ImageAsset.cpp @@ -19,81 +19,6 @@ #include #include -namespace -{ - template - float RetrieveValue(const AZ::u8* mem, size_t index) - { - AZ_Assert(false, "Unimplemented!"); - return 0.0f; - } - - template <> - float RetrieveValue([[maybe_unused]] const AZ::u8* mem, [[maybe_unused]] size_t index) - { - return 0.0f; - } - - template <> - float RetrieveValue(const AZ::u8* mem, size_t index) - { - return mem[index] / static_cast(std::numeric_limits::max()); - } - - template <> - float RetrieveValue(const AZ::u8* mem, size_t index) - { - // 16 bits per channel - auto actualMem = reinterpret_cast(mem); - actualMem += index; - - return *actualMem / static_cast(std::numeric_limits::max()); - } - - template <> - float RetrieveValue(const AZ::u8* mem, size_t index) - { - // 32 bits per channel - auto actualMem = reinterpret_cast(mem); - actualMem += index; - - return *actualMem / static_cast(std::numeric_limits::max()); - } - - template <> - float RetrieveValue(const AZ::u8* mem, size_t index) - { - // 32 bits per channel - auto actualMem = reinterpret_cast(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(mem, index); - - case ePixelFormat_R16: - return RetrieveValue(mem, index); - - case ePixelFormat_R32: - return RetrieveValue(mem, index); - - case ePixelFormat_R32F: - return RetrieveValue(mem, index); - - default: - return RetrieveValue(mem, index); - } - } -} - namespace GradientSignal { void ImageAsset::Reflect(AZ::ReflectContext* context) From 22f018c310b67c4a8c7fc54cde1feb5149454672 Mon Sep 17 00:00:00 2001 From: Chris Galvan Date: Thu, 3 Feb 2022 15:05:01 -0600 Subject: [PATCH 06/29] Moved the ImageProcessingDefines to the Include folder and added it to the header only target Signed-off-by: Chris Galvan --- .../Atom/ImageProcessing}/ImageProcessingDefines.h | 14 ++++++++++++++ .../Source/BuilderSettings/BuilderSettingManager.h | 2 +- .../Code/Source/BuilderSettings/CubemapSettings.h | 2 +- .../Code/Source/BuilderSettings/MipmapSettings.h | 2 +- .../Code/Source/BuilderSettings/PlatformSettings.h | 2 +- .../Code/Source/BuilderSettings/PresetSettings.h | 2 +- .../Code/Source/BuilderSettings/TextureSettings.h | 2 +- .../Code/Source/Compressors/Compressor.h | 2 +- .../Code/Source/ImageBuilderComponent.cpp | 5 ++--- .../Code/Source/ImageProcessingSystemComponent.cpp | 5 ++--- .../Code/Source/Processing/ImageConvert.h | 2 +- .../Code/Source/Processing/ImageConvertJob.h | 2 +- .../Code/Tests/ImageProcessing_Test.cpp | 2 +- .../Code/imageprocessing_files.cmake | 2 +- .../Code/imageprocessingatom_headers_files.cmake | 1 + .../Code/Include/Atom/RPI.Reflect/Image/Image.h | 14 -------------- .../Source/Components/ImageGradientComponent.cpp | 6 +++--- 17 files changed, 33 insertions(+), 34 deletions(-) rename Gems/Atom/Asset/ImageProcessingAtom/Code/{Source/BuilderSettings => Include/Atom/ImageProcessing}/ImageProcessingDefines.h (92%) diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/BuilderSettings/ImageProcessingDefines.h b/Gems/Atom/Asset/ImageProcessingAtom/Code/Include/Atom/ImageProcessing/ImageProcessingDefines.h similarity index 92% rename from Gems/Atom/Asset/ImageProcessingAtom/Code/Source/BuilderSettings/ImageProcessingDefines.h rename to Gems/Atom/Asset/ImageProcessingAtom/Code/Include/Atom/ImageProcessing/ImageProcessingDefines.h index dcd95c7fb6..38f6e4a488 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/BuilderSettings/ImageProcessingDefines.h +++ b/Gems/Atom/Asset/ImageProcessingAtom/Code/Include/Atom/ImageProcessing/ImageProcessingDefines.h @@ -49,6 +49,20 @@ namespace ImageProcessingAtom static const unsigned int s_MinReduceLevel = 0; static const unsigned int s_MaxReduceLevel = 5; + static const char* s_SupportedImageExtensions[] = { + "*.tif", + "*.tiff", + "*.png", + "*.bmp", + "*.jpg", + "*.jpeg", + "*.tga", + "*.gif", + "*.dds", + "*.exr" + }; + static constexpr int s_TotalSupportedImageExtensions = AZ_ARRAY_SIZE(s_SupportedImageExtensions); + enum class RGBWeight : AZ::u32 { uniform, // uniform weights (1.0, 1.0, 1.0) (default) diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/BuilderSettings/BuilderSettingManager.h b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/BuilderSettings/BuilderSettingManager.h index 443b91bc07..65fc0aa2a7 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/BuilderSettings/BuilderSettingManager.h +++ b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/BuilderSettings/BuilderSettingManager.h @@ -8,12 +8,12 @@ #pragma once -#include #include #include #include #include #include +#include #include #include diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/BuilderSettings/CubemapSettings.h b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/BuilderSettings/CubemapSettings.h index 77f804b646..fbfc95ac46 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/BuilderSettings/CubemapSettings.h +++ b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/BuilderSettings/CubemapSettings.h @@ -11,7 +11,7 @@ #include #include #include -#include +#include #include namespace ImageProcessingAtom diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/BuilderSettings/MipmapSettings.h b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/BuilderSettings/MipmapSettings.h index c9abc82b09..c5fa48e10f 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/BuilderSettings/MipmapSettings.h +++ b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/BuilderSettings/MipmapSettings.h @@ -8,7 +8,7 @@ #pragma once -#include +#include #include #include #include diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/BuilderSettings/PlatformSettings.h b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/BuilderSettings/PlatformSettings.h index 10f76db1dd..014b2c7cfb 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/BuilderSettings/PlatformSettings.h +++ b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/BuilderSettings/PlatformSettings.h @@ -10,7 +10,7 @@ #include #include -#include +#include #include namespace ImageProcessingAtom diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/BuilderSettings/PresetSettings.h b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/BuilderSettings/PresetSettings.h index 348fff989d..c0228cef79 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/BuilderSettings/PresetSettings.h +++ b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/BuilderSettings/PresetSettings.h @@ -11,9 +11,9 @@ #include #include -#include #include #include +#include #include namespace ImageProcessingAtom diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/BuilderSettings/TextureSettings.h b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/BuilderSettings/TextureSettings.h index 24a2f07bfb..1767b5d9e8 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/BuilderSettings/TextureSettings.h +++ b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/BuilderSettings/TextureSettings.h @@ -8,7 +8,7 @@ #pragma once -#include +#include #include #include #include diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Compressors/Compressor.h b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Compressors/Compressor.h index 6920ae0cc1..ae4217764d 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Compressors/Compressor.h +++ b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Compressors/Compressor.h @@ -8,7 +8,7 @@ #pragma once -#include +#include #include #include diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/ImageBuilderComponent.cpp b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/ImageBuilderComponent.cpp index 8c2e61ff6d..55a6c1ea8f 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/ImageBuilderComponent.cpp +++ b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/ImageBuilderComponent.cpp @@ -27,7 +27,6 @@ #include #include -#include #include namespace ImageProcessingAtom @@ -67,9 +66,9 @@ namespace ImageProcessingAtom AssetBuilderSDK::AssetBuilderDesc builderDescriptor; builderDescriptor.m_name = "Atom Image Builder"; - for (int i = 0; i < AZ::RPI::s_TotalSupportedImageExtensions; i++) + for (int i = 0; i < s_TotalSupportedImageExtensions; i++) { - builderDescriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern(AZ::RPI::s_SupportedImageExtensions[i], AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard)); + builderDescriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern(s_SupportedImageExtensions[i], AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard)); } builderDescriptor.m_busId = azrtti_typeid(); diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/ImageProcessingSystemComponent.cpp b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/ImageProcessingSystemComponent.cpp index f0d881c416..665d08b3ae 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/ImageProcessingSystemComponent.cpp +++ b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/ImageProcessingSystemComponent.cpp @@ -7,7 +7,6 @@ */ -#include #include #include @@ -224,9 +223,9 @@ namespace ImageProcessingAtom { AZStd::string targetExtension = entry->GetExtension(); - for (int i = 0; i < AZ::RPI::s_TotalSupportedImageExtensions; i++) + for (int i = 0; i < s_TotalSupportedImageExtensions; i++) { - if (AZStd::wildcard_match(AZ::RPI::s_SupportedImageExtensions[i], targetExtension.c_str())) + if (AZStd::wildcard_match(s_SupportedImageExtensions[i], targetExtension.c_str())) { return true; } diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Processing/ImageConvert.h b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Processing/ImageConvert.h index ba3d21191f..a59c3471b3 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Processing/ImageConvert.h +++ b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Processing/ImageConvert.h @@ -10,10 +10,10 @@ #include -#include #include #include #include +#include #include #include diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Processing/ImageConvertJob.h b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Processing/ImageConvertJob.h index ac15d47806..2a5aef9902 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Processing/ImageConvertJob.h +++ b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Processing/ImageConvertJob.h @@ -8,10 +8,10 @@ #pragma once -#include #include #include #include +#include #include namespace ImageProcessingAtom diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Code/Tests/ImageProcessing_Test.cpp b/Gems/Atom/Asset/ImageProcessingAtom/Code/Tests/ImageProcessing_Test.cpp index 91b0952a06..f73446653f 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Code/Tests/ImageProcessing_Test.cpp +++ b/Gems/Atom/Asset/ImageProcessingAtom/Code/Tests/ImageProcessing_Test.cpp @@ -32,6 +32,7 @@ #include #include +#include #include #include #include @@ -45,7 +46,6 @@ #include #include -#include #include #include diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Code/imageprocessing_files.cmake b/Gems/Atom/Asset/ImageProcessingAtom/Code/imageprocessing_files.cmake index a6fe09bfa6..3f7cb0a79d 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Code/imageprocessing_files.cmake +++ b/Gems/Atom/Asset/ImageProcessingAtom/Code/imageprocessing_files.cmake @@ -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 diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Code/imageprocessingatom_headers_files.cmake b/Gems/Atom/Asset/ImageProcessingAtom/Code/imageprocessingatom_headers_files.cmake index c2c5a11c4c..51864a8442 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Code/imageprocessingatom_headers_files.cmake +++ b/Gems/Atom/Asset/ImageProcessingAtom/Code/imageprocessingatom_headers_files.cmake @@ -7,4 +7,5 @@ # set(FILES + Include/Atom/ImageProcessing/ImageProcessingDefines.h ) diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Image/Image.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Image/Image.h index 66c91e92cf..185666626c 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Image/Image.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Image/Image.h @@ -22,20 +22,6 @@ namespace AZ { class ImageAsset; - static const int s_TotalSupportedImageExtensions = 10; - static const char* s_SupportedImageExtensions[s_TotalSupportedImageExtensions] = { - "*.tif", - "*.tiff", - "*.png", - "*.bmp", - "*.jpg", - "*.jpeg", - "*.tga", - "*.gif", - "*.dds", - "*.exr" - }; - //! A base class for images providing access to common image information. class Image : public Data::InstanceData diff --git a/Gems/GradientSignal/Code/Source/Components/ImageGradientComponent.cpp b/Gems/GradientSignal/Code/Source/Components/ImageGradientComponent.cpp index e3f128ec95..c06792c1f1 100644 --- a/Gems/GradientSignal/Code/Source/Components/ImageGradientComponent.cpp +++ b/Gems/GradientSignal/Code/Source/Components/ImageGradientComponent.cpp @@ -7,7 +7,7 @@ */ #include -#include +#include #include #include #include @@ -52,9 +52,9 @@ namespace GradientSignal // 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 (int i = 0; i < AZ::RPI::s_TotalSupportedImageExtensions; i++) + for (int i = 0; i < ImageProcessingAtom::s_TotalSupportedImageExtensions; i++) { - AZStd::string imageExtension(AZ::RPI::s_SupportedImageExtensions[i]); + AZStd::string imageExtension(ImageProcessingAtom::s_SupportedImageExtensions[i]); // The image extensions are stored with a wildcard (e.g. *.png) so we need to strip that off first AZ::StringFunc::Replace(imageExtension, "*", ""); From 2c54b48a9e726906aa6b39780a1ea68af1dcc2ac Mon Sep 17 00:00:00 2001 From: Chris Galvan Date: Thu, 3 Feb 2022 16:20:55 -0600 Subject: [PATCH 07/29] Modified image gradient config json serializer to be able to detect version difference earlier Signed-off-by: Chris Galvan --- .../Components/ImageGradientComponent.cpp | 116 +++++++++--------- 1 file changed, 58 insertions(+), 58 deletions(-) diff --git a/Gems/GradientSignal/Code/Source/Components/ImageGradientComponent.cpp b/Gems/GradientSignal/Code/Source/Components/ImageGradientComponent.cpp index c06792c1f1..468b96e5ad 100644 --- a/Gems/GradientSignal/Code/Source/Components/ImageGradientComponent.cpp +++ b/Gems/GradientSignal/Code/Source/Components/ImageGradientComponent.cpp @@ -23,6 +23,16 @@ namespace GradientSignal 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(outputValue); @@ -30,69 +40,59 @@ namespace GradientSignal JSR::ResultCode result(JSR::Tasks::ReadField); - rapidjson::Value::ConstMemberIterator itr = inputValue.FindMember("ImageAsset"); - if (itr != inputValue.MemberEnd()) - { - // 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(), 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 (int i = 0; i < ImageProcessingAtom::s_TotalSupportedImageExtensions; i++) - { - AZStd::string imageExtension(ImageProcessingAtom::s_SupportedImageExtensions[i]); - - // 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>(), false); - if (fixedAssetId.IsValid()) - { - break; - } - } - } - } - - // Replace the old gradimage with new AssetId for streaming image asset (if we needed to replace) - if (fixedAssetId.IsValid()) - { - configInstance->m_imageAsset = AZ::Data::AssetManager::Instance().GetAsset(fixedAssetId, AZ::Data::AssetLoadBehavior::QueueLoad); - } - // Otherwise, this was already a streaming image asset, so just load it like normal - else - { - result.Combine(ContinueLoadingFromJsonObjectField( - &configInstance->m_imageAsset, azrtti_typeidm_imageAsset)>(), inputValue, "ImageAsset", context)); - } - } - result.Combine(ContinueLoadingFromJsonObjectField( &configInstance->m_tilingX, azrtti_typeidm_tilingX)>(), inputValue, "TilingX", context)); result.Combine(ContinueLoadingFromJsonObjectField( &configInstance->m_tilingY, azrtti_typeidm_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(), 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>(), 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(fixedAssetId, AZ::Data::AssetLoadBehavior::QueueLoad); + } + return context.Report(result, result.GetProcessing() != JSR::Processing::Halted ? "Successfully loaded ImageGradientConfig information." : @@ -113,9 +113,9 @@ namespace GradientSignal { serialize->Class() ->Version(2) - ->Field("ImageAsset", &ImageGradientConfig::m_imageAsset) ->Field("TilingX", &ImageGradientConfig::m_tilingX) ->Field("TilingY", &ImageGradientConfig::m_tilingY) + ->Field("StreamingImageAsset", &ImageGradientConfig::m_imageAsset) ; AZ::EditContext* edit = serialize->GetEditContext(); From c9a84d08a8e4298d5b7e57af05a3717d26008acb Mon Sep 17 00:00:00 2001 From: scspaldi Date: Fri, 4 Feb 2022 17:16:33 -0800 Subject: [PATCH 08/29] Added server launcher sanity test. Signed-off-by: scspaldi --- Tools/LyTestTools/tests/integ/sanity_tests.py | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/Tools/LyTestTools/tests/integ/sanity_tests.py b/Tools/LyTestTools/tests/integ/sanity_tests.py index 2e46d822c7..f1d39891d3 100755 --- a/Tools/LyTestTools/tests/integ/sanity_tests.py +++ b/Tools/LyTestTools/tests/integ/sanity_tests.py @@ -63,6 +63,35 @@ class TestAutomatedTestingProject(object): # Clean up processes after the test is finished process_utils.kill_processes_named(names=process_utils.LY_PROCESS_KILL_LIST, ignore_extensions=True) + def test_StartServerLauncher_Sanity(self, project): + """ + The `test_StartServerLauncher_Sanity` test function verifies that the O3DE game client launches successfully. + Start the test by utilizing the `kill_processes_named` function to close any open O3DE processes that may + interfere with the test. The Workspace object emulates the O3DE package by locating the engine and project + directories. The Launcher object controls the O3DE game client and requires a Workspace object for + initialization. Add the `-rhi=Null` arg to the executable call to disable GPU rendering. This allows the + test to run on instances without a GPU. We launch the game client executable and wait for the process to exist. + A try/finally block ensures proper test cleanup if issues occur during the test. + """ + # Kill processes that may interfere with the test + process_utils.kill_processes_named(names=process_utils.LY_PROCESS_KILL_LIST, ignore_extensions=True) + + try: + # Create the Workspace object + workspace = helpers.create_builtin_workspace(project=project) + + # Create the Launcher object and add args + launcher = launcher_helper.create_dedicated_launcher(workspace) + launcher.args.extend(['-rhi=Null']) + + # Call the game client executable + with launcher.start(): + # Wait for the process to exist + waiter.wait_for(lambda: process_utils.process_exists(f"{project}.ServerLauncher.exe", ignore_extensions=True)) + finally: + # Clean up processes after the test is finished + process_utils.kill_processes_named(names=process_utils.LY_PROCESS_KILL_LIST, ignore_extensions=True) + def test_StartEditor_Sanity(self, project): """ The `test_StartEditor_Sanity` test function is similar to the previous example with minor adjustments. A From e66d55189e175864c40c2d1d776c615ffd11c0ed Mon Sep 17 00:00:00 2001 From: Chris Galvan Date: Mon, 7 Feb 2022 12:06:11 -0600 Subject: [PATCH 09/29] Updated the gradient signal unit tests for the image gradient now using a StreamingImageAsset Signed-off-by: Chris Galvan --- .../Code/Tests/GradientSignalImageTests.cpp | 5 +- .../Code/Tests/GradientSignalTestFixtures.cpp | 15 +- .../Code/Tests/GradientSignalTestFixtures.h | 3 +- .../Code/Tests/GradientSignalTestHelpers.cpp | 178 ++++++++++++++++++ .../Code/Tests/GradientSignalTestHelpers.h | 64 +++++++ .../Code/Tests/GradientSignalTestMocks.cpp | 74 -------- .../Code/Tests/GradientSignalTestMocks.h | 47 ----- .../gradientsignal_shared_tests_files.cmake | 1 - 8 files changed, 257 insertions(+), 130 deletions(-) delete mode 100644 Gems/GradientSignal/Code/Tests/GradientSignalTestMocks.cpp diff --git a/Gems/GradientSignal/Code/Tests/GradientSignalImageTests.cpp b/Gems/GradientSignal/Code/Tests/GradientSignalImageTests.cpp index 8ec190000d..c6656d589e 100644 --- a/Gems/GradientSignal/Code/Tests/GradientSignalImageTests.cpp +++ b/Gems/GradientSignal/Code/Tests/GradientSignalImageTests.cpp @@ -8,6 +8,7 @@ #include +#include #include #include @@ -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(test.m_pixel.GetX()), static_cast(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(gradientConfig); // Create the test GradientTransform diff --git a/Gems/GradientSignal/Code/Tests/GradientSignalTestFixtures.cpp b/Gems/GradientSignal/Code/Tests/GradientSignalTestFixtures.cpp index 402438ee88..d07d2aae50 100644 --- a/Gems/GradientSignal/Code/Tests/GradientSignalTestFixtures.cpp +++ b/Gems/GradientSignal/Code/Tests/GradientSignalTestFixtures.cpp @@ -8,7 +8,10 @@ #include +#include +#include +#include #include #include #include @@ -70,14 +73,16 @@ namespace UnitTest void GradientSignalBaseFixture::SetupCoreSystems() { - m_mockHandler = new UnitTest::ImageAssetMockAssetHandler(); - AZ::Data::AssetManager::Instance().RegisterHandler(m_mockHandler, azrtti_typeid()); + // Using the AZ::RPI::MakeAssetHandler will both create the asset handlers, + // and register them with the AssetManager + m_assetHandlers.emplace_back(AZ::RPI::MakeAssetHandler()); + m_assetHandlers.emplace_back(AZ::RPI::MakeAssetHandler()); } 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(); } @@ -147,7 +152,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(config); diff --git a/Gems/GradientSignal/Code/Tests/GradientSignalTestFixtures.h b/Gems/GradientSignal/Code/Tests/GradientSignalTestFixtures.h index 5fda88ea27..1c703964e7 100644 --- a/Gems/GradientSignal/Code/Tests/GradientSignalTestFixtures.h +++ b/Gems/GradientSignal/Code/Tests/GradientSignalTestFixtures.h @@ -9,6 +9,7 @@ #include #include +#include #include namespace UnitTest @@ -89,7 +90,7 @@ namespace UnitTest AZStd::unique_ptr BuildTestSurfaceMaskGradient(float shapeHalfBounds); AZStd::unique_ptr BuildTestSurfaceSlopeGradient(float shapeHalfBounds); - UnitTest::ImageAssetMockAssetHandler* m_mockHandler = nullptr; + AZ::RPI::AssetHandlerPtrList m_assetHandlers; }; struct GradientSignalTest diff --git a/Gems/GradientSignal/Code/Tests/GradientSignalTestHelpers.cpp b/Gems/GradientSignal/Code/Tests/GradientSignalTestHelpers.cpp index 46cc3475e8..560aa09f48 100644 --- a/Gems/GradientSignal/Code/Tests/GradientSignalTestHelpers.cpp +++ b/Gems/GradientSignal/Code/Tests/GradientSignalTestHelpers.cpp @@ -8,11 +8,189 @@ #include +#include +#include #include #include namespace UnitTest { + 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 BuildBasicImageData(AZ::u32 width, AZ::u32 height, AZ::u32 pixelSize, AZ::s32 seed) + { + const size_t imageSize = width * height * pixelSize; + + AZStd::vector 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(value)); + } + } + + EXPECT_EQ(image.size(), imageSize); + return image; + } + + AZ::Data::Asset 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 data = BuildBasicImageData(width, height, pixelSize, seed); + assetCreator.AddSubImage(data.data(), data.size()); + } + + assetCreator.EndMip(); + + Data::Asset asset; + EXPECT_TRUE(assetCreator.End(asset)); + EXPECT_TRUE(asset.IsReady()); + EXPECT_NE(asset.Get(), nullptr); + + return asset; + } + + AZStd::vector 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 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(height) - 1; y >= 0; --y) + { + for (AZ::u32 x = 0; x < width; ++x) + { + if ((x == static_cast(pixelX)) && (y == static_cast(pixelY))) + { + image.push_back(pixelValue); + } + else + { + image.push_back(0); + } + } + } + + EXPECT_EQ(image.size(), imageSize); + return image; + } + + AZ::Data::Asset 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 data = BuildSpecificPixelImageData(width, height, pixelSize, pixelX, pixelY); + assetCreator.AddSubImage(data.data(), data.size()); + } + + assetCreator.EndMip(); + + Data::Asset asset; + EXPECT_TRUE(assetCreator.End(asset)); + EXPECT_TRUE(asset.IsReady()); + EXPECT_NE(asset.Get(), nullptr); + + return asset; + } + + AZ::Data::Asset 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( + 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 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(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 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( + 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 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(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 shapeHalfBounds) { // Create a gradient sampler and run through a series of points to see if they match expectations. diff --git a/Gems/GradientSignal/Code/Tests/GradientSignalTestHelpers.h b/Gems/GradientSignal/Code/Tests/GradientSignalTestHelpers.h index 8a175939ee..5a13167975 100644 --- a/Gems/GradientSignal/Code/Tests/GradientSignalTestHelpers.h +++ b/Gems/GradientSignal/Code/Tests/GradientSignalTestHelpers.h @@ -12,8 +12,72 @@ #include #include +#include +#include +#include + 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 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 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 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 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 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 CreateSpecificPixelImageAsset(AZ::u32 width, AZ::u32 height, AZ::u32 pixelX, AZ::u32 pixelY); + class GradientSignalTestHelpers { public: diff --git a/Gems/GradientSignal/Code/Tests/GradientSignalTestMocks.cpp b/Gems/GradientSignal/Code/Tests/GradientSignalTestMocks.cpp deleted file mode 100644 index 7ac6ef1ecc..0000000000 --- a/Gems/GradientSignal/Code/Tests/GradientSignalTestMocks.cpp +++ /dev/null @@ -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 - -namespace UnitTest -{ - AZ::Data::Asset ImageAssetMockAssetHandler::CreateImageAsset(AZ::u32 width, AZ::u32 height, AZ::s32 seed) - { - auto imageAsset = AZ::Data::AssetManager::Instance().CreateAsset( - 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(value)); - } - } - - return imageAsset; - } - - AZ::Data::Asset ImageAssetMockAssetHandler::CreateSpecificPixelImageAsset( - AZ::u32 width, AZ::u32 height, AZ::u32 pixelX, AZ::u32 pixelY) - { - auto imageAsset = AZ::Data::AssetManager::Instance().CreateAsset( - 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(height) - 1; y >= 0; --y) - { - for (AZ::u32 x = 0; x < width; ++x) - { - if ((x == static_cast(pixelX)) && (y == static_cast(pixelY))) - { - imageAsset->m_imageData.push_back(pixelValue); - } - else - { - imageAsset->m_imageData.push_back(0); - } - } - } - - return imageAsset; - } -} - diff --git a/Gems/GradientSignal/Code/Tests/GradientSignalTestMocks.h b/Gems/GradientSignal/Code/Tests/GradientSignalTestMocks.h index 30f262d9b4..997e88ce17 100644 --- a/Gems/GradientSignal/Code/Tests/GradientSignalTestMocks.h +++ b/Gems/GradientSignal/Code/Tests/GradientSignalTestMocks.h @@ -27,53 +27,6 @@ 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 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 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& assetTypes) override - { - } - - AZ::Data::AssetHandler::LoadResult LoadAssetData( - [[maybe_unused]] const AZ::Data::Asset& asset, - [[maybe_unused]] AZStd::shared_ptr stream, - [[maybe_unused]] const AZ::Data::AssetFilterCB& assetLoadFilterCB) override - { - return AZ::Data::AssetHandler::LoadResult::LoadComplete; - } - }; - struct MockGradientRequestsBus : public GradientSignal::GradientRequestBus::Handler { diff --git a/Gems/GradientSignal/Code/gradientsignal_shared_tests_files.cmake b/Gems/GradientSignal/Code/gradientsignal_shared_tests_files.cmake index 98ab57b7b0..5e11406bae 100644 --- a/Gems/GradientSignal/Code/gradientsignal_shared_tests_files.cmake +++ b/Gems/GradientSignal/Code/gradientsignal_shared_tests_files.cmake @@ -11,6 +11,5 @@ set(FILES Tests/GradientSignalTestHelpers.h Tests/GradientSignalTestFixtures.cpp Tests/GradientSignalTestFixtures.h - Tests/GradientSignalTestMocks.cpp Tests/GradientSignalTestMocks.h ) From d4df57e9106edccadeccb3a846d3d9cad3cbf810 Mon Sep 17 00:00:00 2001 From: scspaldi Date: Mon, 7 Feb 2022 15:56:24 -0800 Subject: [PATCH 10/29] Applied PR feedback. Signed-off-by: scspaldi --- Tools/LyTestTools/tests/integ/sanity_tests.py | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/Tools/LyTestTools/tests/integ/sanity_tests.py b/Tools/LyTestTools/tests/integ/sanity_tests.py index f1d39891d3..481349dfda 100755 --- a/Tools/LyTestTools/tests/integ/sanity_tests.py +++ b/Tools/LyTestTools/tests/integ/sanity_tests.py @@ -64,23 +64,15 @@ class TestAutomatedTestingProject(object): process_utils.kill_processes_named(names=process_utils.LY_PROCESS_KILL_LIST, ignore_extensions=True) def test_StartServerLauncher_Sanity(self, project): - """ - The `test_StartServerLauncher_Sanity` test function verifies that the O3DE game client launches successfully. - Start the test by utilizing the `kill_processes_named` function to close any open O3DE processes that may - interfere with the test. The Workspace object emulates the O3DE package by locating the engine and project - directories. The Launcher object controls the O3DE game client and requires a Workspace object for - initialization. Add the `-rhi=Null` arg to the executable call to disable GPU rendering. This allows the - test to run on instances without a GPU. We launch the game client executable and wait for the process to exist. - A try/finally block ensures proper test cleanup if issues occur during the test. - """ # Kill processes that may interfere with the test process_utils.kill_processes_named(names=process_utils.LY_PROCESS_KILL_LIST, ignore_extensions=True) try: - # Create the Workspace object + # Create the Workspace object, this locates the engine and project workspace = helpers.create_builtin_workspace(project=project) - # Create the Launcher object and add args + # Create the Launcher object and add args, such as `-rhi=Null` which disables GPU rendering and allows the + # test to run on nodes without a GPU launcher = launcher_helper.create_dedicated_launcher(workspace) launcher.args.extend(['-rhi=Null']) From 5b8176e99bba504a8c11488deac0f06f7a5b67b1 Mon Sep 17 00:00:00 2001 From: Nicholas Van Sickle Date: Mon, 7 Feb 2022 16:01:29 -0800 Subject: [PATCH 11/29] Address some review feedback Signed-off-by: Nicholas Van Sickle --- Code/Framework/AzCore/AzCore/DOM/DomPatch.cpp | 18 +++++++++--------- Code/Framework/AzCore/AzCore/DOM/DomPatch.h | 8 +++++--- .../AzCore/Tests/DOM/DomPathBenchmarks.cpp | 12 ++++++------ 3 files changed, 20 insertions(+), 18 deletions(-) diff --git a/Code/Framework/AzCore/AzCore/DOM/DomPatch.cpp b/Code/Framework/AzCore/AzCore/DOM/DomPatch.cpp index 44c16baa16..f89cb313e7 100644 --- a/Code/Framework/AzCore/AzCore/DOM/DomPatch.cpp +++ b/Code/Framework/AzCore/AzCore/DOM/DomPatch.cpp @@ -14,21 +14,21 @@ namespace AZ::Dom { PatchOperation::PatchOperation(Path destinationPath, Type type, Value value) - : m_domPath(destinationPath) + : m_domPath(AZStd::move(destinationPath)) , m_type(type) - , m_value(value) + , m_value(AZStd::move(value)) { } PatchOperation::PatchOperation(Path destinationPath, Type type, Path sourcePath) - : m_domPath(destinationPath) + : m_domPath(AZStd::move(destinationPath)) , m_type(type) - , m_value(sourcePath) + , m_value(AZStd::move(sourcePath)) { } PatchOperation::PatchOperation(Path destinationPath, Type type) - : m_domPath(destinationPath) + : m_domPath(AZStd::move(destinationPath)) , m_type(type) { } @@ -690,22 +690,22 @@ namespace AZ::Dom auto Patch::begin() const -> OperationsContainer::const_iterator { - return m_operations.cbegin(); + return m_operations.begin(); } auto Patch::end() const -> OperationsContainer::const_iterator { - return m_operations.cend(); + return m_operations.end(); } auto Patch::cbegin() const -> OperationsContainer::const_iterator { - return m_operations.cbegin(); + return m_operations.begin(); } auto Patch::cend() const -> OperationsContainer::const_iterator { - return m_operations.cend(); + return m_operations.end(); } size_t Patch::size() const diff --git a/Code/Framework/AzCore/AzCore/DOM/DomPatch.h b/Code/Framework/AzCore/AzCore/DOM/DomPatch.h index 633a611c73..40ef725b57 100644 --- a/Code/Framework/AzCore/AzCore/DOM/DomPatch.h +++ b/Code/Framework/AzCore/AzCore/DOM/DomPatch.h @@ -10,6 +10,7 @@ #include #include +#include namespace AZ::Dom { @@ -108,12 +109,12 @@ namespace AZ::Dom //! 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 outcome of the last operation, may be overridden to produce a different failure outcome. - PatchOperation::PatchOutcome m_outcome; //! 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. @@ -134,7 +135,7 @@ namespace AZ::Dom { public: using StrategyFunctor = AZStd::function; - using OperationsContainer = AZStd::vector; + using OperationsContainer = AZStd::deque; Patch() = default; Patch(const Patch&) = default; @@ -193,6 +194,7 @@ namespace AZ::Dom struct DeltaPatchGenerationParameters { static constexpr size_t NoReplace = AZStd::numeric_limits::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. diff --git a/Code/Framework/AzCore/Tests/DOM/DomPathBenchmarks.cpp b/Code/Framework/AzCore/Tests/DOM/DomPathBenchmarks.cpp index c3c8139f6c..4741900aa1 100644 --- a/Code/Framework/AzCore/Tests/DOM/DomPathBenchmarks.cpp +++ b/Code/Framework/AzCore/Tests/DOM/DomPathBenchmarks.cpp @@ -106,12 +106,12 @@ namespace AZ::Dom::Benchmark for (auto _ : state) { - name == name; - name == index; - name == endOfArray; - index == index; - index == endOfArray; - endOfArray == endOfArray; + 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()); From 48313cd6dee676e90775b744e0625e54837a9b34 Mon Sep 17 00:00:00 2001 From: Danilo Aimini <82231674+AMZN-daimini@users.noreply.github.com> Date: Tue, 8 Feb 2022 12:40:10 -0800 Subject: [PATCH 12/29] Remove const reference to prevent issues in nightly builds. (#7491) Signed-off-by: Danilo Aimini <82231674+AMZN-daimini@users.noreply.github.com> --- .../AzToolsFramework/Prefab/PrefabFocusHandler.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusHandler.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusHandler.cpp index 62c6518d4c..800464a336 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusHandler.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusHandler.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) From c46c55803860cec45f2bb197c159116e90604ec6 Mon Sep 17 00:00:00 2001 From: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> Date: Tue, 8 Feb 2022 15:00:19 -0600 Subject: [PATCH 13/29] Switched Gradient Surface benchmarks to use actual surface components. (#7468) * Switched Gradient Surface benchmarks to use actual surface components. The gradient unit tests and benchmarks were previously using a mock surface data system, which led to misleading benchmark results. Now, the actual SurfaceData system gets constructed, and the tests use a mock provider, but the benchmarks use actual shape providers for more realistic benchmarking. Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> * Fixed unit tests to have better query ranges. Half of each previous range was querying outside the surface provider's data. Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> --- Gems/FastNoise/Code/Tests/FastNoiseTest.cpp | 2 +- .../Code/Tests/GradientSignalBenchmarks.cpp | 9 --- .../Tests/GradientSignalGetValuesTests.cpp | 43 +++++----- .../Tests/GradientSignalReferencesTests.cpp | 63 +++++++++------ .../Code/Tests/GradientSignalSurfaceTests.cpp | 7 +- .../Code/Tests/GradientSignalTestFixtures.cpp | 80 +++++++++++-------- .../Code/Tests/GradientSignalTestFixtures.h | 10 +-- .../Code/Tests/GradientSignalTestHelpers.cpp | 6 +- .../Code/Tests/GradientSignalTestHelpers.h | 2 +- .../Code/Tests/GradientSignalTestMocks.h | 61 ++++++++++++++ .../Components/SurfaceDataColliderComponent.h | 0 .../Components/SurfaceDataShapeComponent.h | 0 .../Components}/SurfaceDataSystemComponent.h | 4 + .../SurfaceData/SurfaceDataSystemRequestBus.h | 4 + .../SurfaceData/Tests/SurfaceDataTestMocks.h | 10 +++ .../SurfaceDataColliderComponent.cpp | 2 +- .../Components/SurfaceDataShapeComponent.cpp | 2 +- .../EditorSurfaceDataColliderComponent.h | 2 +- .../Editor/EditorSurfaceDataShapeComponent.h | 2 +- .../Code/Source/SurfaceDataEditorModule.cpp | 2 +- .../Code/Source/SurfaceDataModule.cpp | 6 +- .../Source/SurfaceDataSystemComponent.cpp | 30 ++++++- .../Code/Source/SurfaceDataTypes.cpp | 4 +- .../Code/Tests/SurfaceDataBenchmarks.cpp | 4 +- .../SurfaceDataColliderComponentTest.cpp | 2 +- .../Code/Tests/SurfaceDataTest.cpp | 2 +- .../Code/Tests/SurfaceDataTestFixtures.cpp | 6 +- Gems/SurfaceData/Code/surfacedata_files.cmake | 6 +- Gems/Vegetation/Code/Tests/VegetationMocks.h | 10 +++ 29 files changed, 252 insertions(+), 129 deletions(-) rename Gems/SurfaceData/Code/{Source => Include/SurfaceData}/Components/SurfaceDataColliderComponent.h (100%) rename Gems/SurfaceData/Code/{Source => Include/SurfaceData}/Components/SurfaceDataShapeComponent.h (100%) rename Gems/SurfaceData/Code/{Source => Include/SurfaceData/Components}/SurfaceDataSystemComponent.h (95%) diff --git a/Gems/FastNoise/Code/Tests/FastNoiseTest.cpp b/Gems/FastNoise/Code/Tests/FastNoiseTest.cpp index 1ac5e8ddca..e82409d599 100644 --- a/Gems/FastNoise/Code/Tests/FastNoiseTest.cpp +++ b/Gems/FastNoise/Code/Tests/FastNoiseTest.cpp @@ -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. diff --git a/Gems/GradientSignal/Code/Tests/GradientSignalBenchmarks.cpp b/Gems/GradientSignal/Code/Tests/GradientSignalBenchmarks.cpp index 6ffb4a31c4..d8194dcc9a 100644 --- a/Gems/GradientSignal/Code/Tests/GradientSignalBenchmarks.cpp +++ b/Gems/GradientSignal/Code/Tests/GradientSignalBenchmarks.cpp @@ -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()); } diff --git a/Gems/GradientSignal/Code/Tests/GradientSignalGetValuesTests.cpp b/Gems/GradientSignal/Code/Tests/GradientSignalGetValuesTests.cpp index f0ad53ee64..e217dbb103 100644 --- a/Gems/GradientSignal/Code/Tests/GradientSignalGetValuesTests.cpp +++ b/Gems/GradientSignal/Code/Tests/GradientSignalGetValuesTests.cpp @@ -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); } } diff --git a/Gems/GradientSignal/Code/Tests/GradientSignalReferencesTests.cpp b/Gems/GradientSignal/Code/Tests/GradientSignalReferencesTests.cpp index 6f90c5022f..2e602e8c80 100644 --- a/Gems/GradientSignal/Code/Tests/GradientSignalReferencesTests.cpp +++ b/Gems/GradientSignal/Code/Tests/GradientSignalReferencesTests.cpp @@ -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(); + mockSurface->m_bounds = AZ::Aabb::CreateFromMinMax(AZ::Vector3(0.0f), AZ::Vector3(aznumeric_cast(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(x), static_cast(y))] = { { point } }; + mockSurface->m_surfacePoints[AZStd::make_pair(static_cast(x), static_cast(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(); + 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(); + 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(); + 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(); + mockSurface->m_bounds = AZ::Aabb::CreateFromMinMax(AZ::Vector3(0.0f), AZ::Vector3(aznumeric_cast(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(x), static_cast(y))] = { { point } }; + mockSurface->m_surfacePoints[AZStd::make_pair(static_cast(x), static_cast(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)); diff --git a/Gems/GradientSignal/Code/Tests/GradientSignalSurfaceTests.cpp b/Gems/GradientSignal/Code/Tests/GradientSignalSurfaceTests.cpp index 9f4346e09e..4ab4c76d56 100644 --- a/Gems/GradientSignal/Code/Tests/GradientSignalSurfaceTests.cpp +++ b/Gems/GradientSignal/Code/Tests/GradientSignalSurfaceTests.cpp @@ -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 diff --git a/Gems/GradientSignal/Code/Tests/GradientSignalTestFixtures.cpp b/Gems/GradientSignal/Code/Tests/GradientSignalTestFixtures.cpp index 402438ee88..3ca145b7c3 100644 --- a/Gems/GradientSignal/Code/Tests/GradientSignalTestFixtures.cpp +++ b/Gems/GradientSignal/Code/Tests/GradientSignalTestFixtures.cpp @@ -13,6 +13,9 @@ #include #include #include +#include +#include +#include // Base gradient components #include @@ -40,7 +43,7 @@ namespace UnitTest { void GradientSignalTestEnvironment::AddGemsAndComponents() { - AddDynamicModulePaths({ "LmbrCentral" }); + AddDynamicModulePaths({ "LmbrCentral", "SurfaceData" }); AddComponentDescriptors({ AzFramework::TransformComponent::CreateDescriptor(), @@ -65,6 +68,7 @@ namespace UnitTest GradientSignal::ThresholdGradientComponent::CreateDescriptor(), MockShapeComponent::CreateDescriptor(), + MockSurfaceProviderComponent::CreateDescriptor(), }); } @@ -82,35 +86,6 @@ namespace UnitTest AzFramework::LegacyAssetEventBus::ClearQueuedEvents(); } - AZStd::unique_ptr GradientSignalBaseFixture::CreateMockSurfaceDataSystem(const AZ::Aabb& spawnerBox) - { - AzFramework::SurfaceData::SurfacePoint point; - AZStd::unique_ptr mockSurfaceDataSystem = AZStd::make_unique(); - - // 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 GradientSignalBaseFixture::CreateTestEntity(float shapeHalfBounds) { // Create the base entity @@ -120,7 +95,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(); transform->SetLocalTM(AZ::Transform::CreateTranslation(AZ::Vector3(shapeHalfBounds))); transform->SetWorldTM(AZ::Transform::CreateTranslation(AZ::Vector3(shapeHalfBounds))); @@ -128,6 +103,23 @@ namespace UnitTest return testEntity; } + AZStd::unique_ptr GradientSignalBaseFixture::CreateTestSphereEntity(float shapeRadius) + { + // Create the base entity + AZStd::unique_ptr 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(); + transform->SetLocalTM(AZ::Transform::CreateTranslation(AZ::Vector3(shapeRadius))); + transform->SetWorldTM(AZ::Transform::CreateTranslation(AZ::Vector3(shapeRadius))); + + return testEntity; + } + AZStd::unique_ptr GradientSignalBaseFixture::BuildTestConstantGradient(float shapeHalfBounds) { // Create a Constant Gradient Component with arbitrary parameters. @@ -349,12 +341,18 @@ namespace UnitTest AZStd::unique_ptr 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(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()); + surfaceShapeComponent->SetConfiguration(shapeConfig); + ActivateEntity(entity.get()); return entity; } @@ -362,11 +360,17 @@ namespace UnitTest AZStd::unique_ptr 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(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()); + surfaceShapeComponent->SetConfiguration(shapeConfig); + ActivateEntity(entity.get()); return entity; } @@ -374,7 +378,7 @@ namespace UnitTest AZStd::unique_ptr 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 +388,12 @@ namespace UnitTest config.m_smoothStep.m_falloffStrength = 0.25f; entity->CreateComponent(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()); + surfaceShapeComponent->SetConfiguration(shapeConfig); + ActivateEntity(entity.get()); return entity; } diff --git a/Gems/GradientSignal/Code/Tests/GradientSignalTestFixtures.h b/Gems/GradientSignal/Code/Tests/GradientSignalTestFixtures.h index 5fda88ea27..9a173e7df1 100644 --- a/Gems/GradientSignal/Code/Tests/GradientSignalTestFixtures.h +++ b/Gems/GradientSignal/Code/Tests/GradientSignalTestFixtures.h @@ -60,14 +60,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 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 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 CreateTestSphereEntity(float shapeRadius); + // Create and activate an entity with a gradient component of the requested type, initialized with test data. AZStd::unique_ptr BuildTestConstantGradient(float shapeHalfBounds); AZStd::unique_ptr BuildTestImageGradient(float shapeHalfBounds); diff --git a/Gems/GradientSignal/Code/Tests/GradientSignalTestHelpers.cpp b/Gems/GradientSignal/Code/Tests/GradientSignalTestHelpers.cpp index 46cc3475e8..0b5432abe9 100644 --- a/Gems/GradientSignal/Code/Tests/GradientSignalTestHelpers.cpp +++ b/Gems/GradientSignal/Code/Tests/GradientSignalTestHelpers.cpp @@ -13,11 +13,11 @@ namespace UnitTest { - void GradientSignalTestHelpers::CompareGetValueAndGetValues(AZ::EntityId gradientEntityId, float shapeHalfBounds) + 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 +118,7 @@ namespace UnitTest AZStd::vector results(totalQueryPoints); GradientSignal::GradientRequestBus::Event( gradientId, &GradientSignal::GradientRequestBus::Events::GetValues, positions, results); + benchmark::DoNotOptimize(results); } } @@ -174,6 +175,7 @@ namespace UnitTest // Query and get the results. AZStd::vector results(totalQueryPoints); gradientSampler.GetValues(positions, results); + benchmark::DoNotOptimize(results); } } diff --git a/Gems/GradientSignal/Code/Tests/GradientSignalTestHelpers.h b/Gems/GradientSignal/Code/Tests/GradientSignalTestHelpers.h index 8a175939ee..15b436105e 100644 --- a/Gems/GradientSignal/Code/Tests/GradientSignalTestHelpers.h +++ b/Gems/GradientSignal/Code/Tests/GradientSignalTestHelpers.h @@ -17,7 +17,7 @@ namespace UnitTest 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 diff --git a/Gems/GradientSignal/Code/Tests/GradientSignalTestMocks.h b/Gems/GradientSignal/Code/Tests/GradientSignalTestMocks.h index 30f262d9b4..f436045b4e 100644 --- a/Gems/GradientSignal/Code/Tests/GradientSignalTestMocks.h +++ b/Gems/GradientSignal/Code/Tests/GradientSignalTestMocks.h @@ -22,6 +22,7 @@ #include #include #include +#include #include #include @@ -162,4 +163,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, 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; + }; + } diff --git a/Gems/SurfaceData/Code/Source/Components/SurfaceDataColliderComponent.h b/Gems/SurfaceData/Code/Include/SurfaceData/Components/SurfaceDataColliderComponent.h similarity index 100% rename from Gems/SurfaceData/Code/Source/Components/SurfaceDataColliderComponent.h rename to Gems/SurfaceData/Code/Include/SurfaceData/Components/SurfaceDataColliderComponent.h diff --git a/Gems/SurfaceData/Code/Source/Components/SurfaceDataShapeComponent.h b/Gems/SurfaceData/Code/Include/SurfaceData/Components/SurfaceDataShapeComponent.h similarity index 100% rename from Gems/SurfaceData/Code/Source/Components/SurfaceDataShapeComponent.h rename to Gems/SurfaceData/Code/Include/SurfaceData/Components/SurfaceDataShapeComponent.h diff --git a/Gems/SurfaceData/Code/Source/SurfaceDataSystemComponent.h b/Gems/SurfaceData/Code/Include/SurfaceData/Components/SurfaceDataSystemComponent.h similarity index 95% rename from Gems/SurfaceData/Code/Source/SurfaceDataSystemComponent.h rename to Gems/SurfaceData/Code/Include/SurfaceData/Components/SurfaceDataSystemComponent.h index bb554cec78..3e1291e524 100644 --- a/Gems/SurfaceData/Code/Source/SurfaceDataSystemComponent.h +++ b/Gems/SurfaceData/Code/Include/SurfaceData/Components/SurfaceDataSystemComponent.h @@ -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); diff --git a/Gems/SurfaceData/Code/Include/SurfaceData/SurfaceDataSystemRequestBus.h b/Gems/SurfaceData/Code/Include/SurfaceData/SurfaceDataSystemRequestBus.h index 616f069184..d4e84c5974 100644 --- a/Gems/SurfaceData/Code/Include/SurfaceData/SurfaceDataSystemRequestBus.h +++ b/Gems/SurfaceData/Code/Include/SurfaceData/SurfaceDataSystemRequestBus.h @@ -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 SurfaceDataSystemRequestBus; diff --git a/Gems/SurfaceData/Code/Include/SurfaceData/Tests/SurfaceDataTestMocks.h b/Gems/SurfaceData/Code/Include/SurfaceData/Tests/SurfaceDataTestMocks.h index 82a321756d..bafe215402 100644 --- a/Gems/SurfaceData/Code/Include/SurfaceData/Tests/SurfaceDataTestMocks.h +++ b/Gems/SurfaceData/Code/Include/SurfaceData/Tests/SurfaceDataTestMocks.h @@ -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); diff --git a/Gems/SurfaceData/Code/Source/Components/SurfaceDataColliderComponent.cpp b/Gems/SurfaceData/Code/Source/Components/SurfaceDataColliderComponent.cpp index 86a165b7d8..35f377bfe0 100644 --- a/Gems/SurfaceData/Code/Source/Components/SurfaceDataColliderComponent.cpp +++ b/Gems/SurfaceData/Code/Source/Components/SurfaceDataColliderComponent.cpp @@ -6,7 +6,7 @@ * */ -#include "SurfaceDataColliderComponent.h" +#include #include #include diff --git a/Gems/SurfaceData/Code/Source/Components/SurfaceDataShapeComponent.cpp b/Gems/SurfaceData/Code/Source/Components/SurfaceDataShapeComponent.cpp index 481372b7dc..973fca232a 100644 --- a/Gems/SurfaceData/Code/Source/Components/SurfaceDataShapeComponent.cpp +++ b/Gems/SurfaceData/Code/Source/Components/SurfaceDataShapeComponent.cpp @@ -6,7 +6,7 @@ * */ -#include "SurfaceDataShapeComponent.h" +#include #include #include diff --git a/Gems/SurfaceData/Code/Source/Editor/EditorSurfaceDataColliderComponent.h b/Gems/SurfaceData/Code/Source/Editor/EditorSurfaceDataColliderComponent.h index 211d819b4d..a25061c245 100644 --- a/Gems/SurfaceData/Code/Source/Editor/EditorSurfaceDataColliderComponent.h +++ b/Gems/SurfaceData/Code/Source/Editor/EditorSurfaceDataColliderComponent.h @@ -11,7 +11,7 @@ #include #include #include -#include +#include #include namespace SurfaceData diff --git a/Gems/SurfaceData/Code/Source/Editor/EditorSurfaceDataShapeComponent.h b/Gems/SurfaceData/Code/Source/Editor/EditorSurfaceDataShapeComponent.h index 79218b9317..202511c4c7 100644 --- a/Gems/SurfaceData/Code/Source/Editor/EditorSurfaceDataShapeComponent.h +++ b/Gems/SurfaceData/Code/Source/Editor/EditorSurfaceDataShapeComponent.h @@ -11,7 +11,7 @@ #include #include #include -#include +#include #include namespace SurfaceData diff --git a/Gems/SurfaceData/Code/Source/SurfaceDataEditorModule.cpp b/Gems/SurfaceData/Code/Source/SurfaceDataEditorModule.cpp index 936cac5954..5f1ead9873 100644 --- a/Gems/SurfaceData/Code/Source/SurfaceDataEditorModule.cpp +++ b/Gems/SurfaceData/Code/Source/SurfaceDataEditorModule.cpp @@ -7,7 +7,7 @@ */ #include -#include +#include #include #include #include diff --git a/Gems/SurfaceData/Code/Source/SurfaceDataModule.cpp b/Gems/SurfaceData/Code/Source/SurfaceDataModule.cpp index 35064bf4a4..36b8936e23 100644 --- a/Gems/SurfaceData/Code/Source/SurfaceDataModule.cpp +++ b/Gems/SurfaceData/Code/Source/SurfaceDataModule.cpp @@ -7,9 +7,9 @@ */ #include -#include -#include -#include +#include +#include +#include namespace SurfaceData { diff --git a/Gems/SurfaceData/Code/Source/SurfaceDataSystemComponent.cpp b/Gems/SurfaceData/Code/Source/SurfaceDataSystemComponent.cpp index 66b8c83a58..7dc6711b72 100644 --- a/Gems/SurfaceData/Code/Source/SurfaceDataSystemComponent.cpp +++ b/Gems/SurfaceData/Code/Source/SurfaceDataSystemComponent.cpp @@ -12,7 +12,7 @@ #include #include -#include "SurfaceDataSystemComponent.h" +#include #include #include #include @@ -175,6 +175,34 @@ namespace SurfaceData SurfaceDataSystemNotificationBus::Broadcast(&SurfaceDataSystemNotificationBus::Events::OnSurfaceChanged, AZ::EntityId(), dirtyBounds, dirtyBounds); } + SurfaceDataRegistryHandle SurfaceDataSystemComponent::GetSurfaceDataProviderHandle(const AZ::EntityId& providerEntityId) + { + AZStd::shared_lock registrationLock(m_registrationMutex); + + for (auto& [providerHandle, providerEntry] : m_registeredSurfaceDataProviders) + { + if (providerEntry.m_entityId == providerEntityId) + { + return providerHandle; + } + } + return {}; + } + + SurfaceDataRegistryHandle SurfaceDataSystemComponent::GetSurfaceDataModifierHandle(const AZ::EntityId& modifierEntityId) + { + AZStd::shared_lock registrationLock(m_registrationMutex); + + for (auto& [modifierHandle, modifierEntry] : m_registeredSurfaceDataModifiers) + { + if (modifierEntry.m_entityId == modifierEntityId) + { + return modifierHandle; + } + } + return {}; + } + void SurfaceDataSystemComponent::GetSurfacePoints(const AZ::Vector3& inPosition, const SurfaceTagVector& desiredTags, SurfacePointList& surfacePointList) const { const bool useTagFilters = HasValidTags(desiredTags); diff --git a/Gems/SurfaceData/Code/Source/SurfaceDataTypes.cpp b/Gems/SurfaceData/Code/Source/SurfaceDataTypes.cpp index 04c1ada677..a25273896e 100644 --- a/Gems/SurfaceData/Code/Source/SurfaceDataTypes.cpp +++ b/Gems/SurfaceData/Code/Source/SurfaceDataTypes.cpp @@ -226,9 +226,7 @@ namespace SurfaceData void SurfacePointList::ReserveSpace(size_t maxPointsPerInput) { - AZ_Assert( - m_surfacePositionList.size() < maxPointsPerInput, - "Trying to reserve space on a list that is already using more points than requested."); + AZ_Assert(m_surfacePositionList.empty(), "Trying to reserve space on a list that is already being used."); m_surfaceCreatorIdList.reserve(maxPointsPerInput); m_surfacePositionList.reserve(maxPointsPerInput); diff --git a/Gems/SurfaceData/Code/Tests/SurfaceDataBenchmarks.cpp b/Gems/SurfaceData/Code/Tests/SurfaceDataBenchmarks.cpp index f8b4c675a5..df011113da 100644 --- a/Gems/SurfaceData/Code/Tests/SurfaceDataBenchmarks.cpp +++ b/Gems/SurfaceData/Code/Tests/SurfaceDataBenchmarks.cpp @@ -21,8 +21,8 @@ #include #include #include -#include -#include +#include +#include namespace UnitTest { diff --git a/Gems/SurfaceData/Code/Tests/SurfaceDataColliderComponentTest.cpp b/Gems/SurfaceData/Code/Tests/SurfaceDataColliderComponentTest.cpp index 884c831393..aca5a53f4d 100644 --- a/Gems/SurfaceData/Code/Tests/SurfaceDataColliderComponentTest.cpp +++ b/Gems/SurfaceData/Code/Tests/SurfaceDataColliderComponentTest.cpp @@ -14,7 +14,7 @@ #include #include -#include +#include #include #include diff --git a/Gems/SurfaceData/Code/Tests/SurfaceDataTest.cpp b/Gems/SurfaceData/Code/Tests/SurfaceDataTest.cpp index f4ff0cb04c..8500e557bf 100644 --- a/Gems/SurfaceData/Code/Tests/SurfaceDataTest.cpp +++ b/Gems/SurfaceData/Code/Tests/SurfaceDataTest.cpp @@ -15,7 +15,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/Gems/SurfaceData/Code/Tests/SurfaceDataTestFixtures.cpp b/Gems/SurfaceData/Code/Tests/SurfaceDataTestFixtures.cpp index 2ec531d2e4..28f7ea6b03 100644 --- a/Gems/SurfaceData/Code/Tests/SurfaceDataTestFixtures.cpp +++ b/Gems/SurfaceData/Code/Tests/SurfaceDataTestFixtures.cpp @@ -11,9 +11,9 @@ #include #include -#include -#include -#include +#include +#include +#include namespace UnitTest diff --git a/Gems/SurfaceData/Code/surfacedata_files.cmake b/Gems/SurfaceData/Code/surfacedata_files.cmake index 1c36ca43a0..8e7435abb3 100644 --- a/Gems/SurfaceData/Code/surfacedata_files.cmake +++ b/Gems/SurfaceData/Code/surfacedata_files.cmake @@ -7,6 +7,9 @@ # set(FILES + Include/SurfaceData/Components/SurfaceDataColliderComponent.h + Include/SurfaceData/Components/SurfaceDataShapeComponent.h + Include/SurfaceData/Components/SurfaceDataSystemComponent.h Include/SurfaceData/SurfaceDataConstants.h Include/SurfaceData/SurfaceDataTypes.h Include/SurfaceData/SurfaceDataSystemRequestBus.h @@ -18,12 +21,9 @@ set(FILES Include/SurfaceData/SurfaceTag.h Include/SurfaceData/Utility/SurfaceDataUtility.h Source/SurfaceDataSystemComponent.cpp - Source/SurfaceDataSystemComponent.h Source/SurfaceDataTypes.cpp Source/SurfaceTag.cpp Source/Components/SurfaceDataColliderComponent.cpp - Source/Components/SurfaceDataColliderComponent.h Source/Components/SurfaceDataShapeComponent.cpp - Source/Components/SurfaceDataShapeComponent.h Source/SurfaceDataUtility.cpp ) diff --git a/Gems/Vegetation/Code/Tests/VegetationMocks.h b/Gems/Vegetation/Code/Tests/VegetationMocks.h index d77862d578..33a320b03e 100644 --- a/Gems/Vegetation/Code/Tests/VegetationMocks.h +++ b/Gems/Vegetation/Code/Tests/VegetationMocks.h @@ -384,6 +384,16 @@ namespace UnitTest { ++m_count; } + + SurfaceData::SurfaceDataRegistryHandle GetSurfaceDataProviderHandle([[maybe_unused]] const AZ::EntityId& providerEntityId) override + { + return {}; + } + + SurfaceData::SurfaceDataRegistryHandle GetSurfaceDataModifierHandle([[maybe_unused]] const AZ::EntityId& modifierEntityId) override + { + return {}; + } }; struct MockMeshAsset From 742ea34d442ceefe14a8219119a8612b9c704c64 Mon Sep 17 00:00:00 2001 From: rgba16f <82187279+rgba16f@users.noreply.github.com> Date: Tue, 8 Feb 2022 15:24:37 -0600 Subject: [PATCH 14/29] Add a function to get the internal data for a disk light from it's feature processor (#7450) * Add a function to get the internal data for a disk light from it's feature processor Signed-off-by: rgba16f <82187279+rgba16f@users.noreply.github.com> * Address PR comments. Fixed assert message, made function const. Signed-off-by: rgba16f <82187279+rgba16f@users.noreply.github.com> --- .../CoreLights/DiskLightFeatureProcessorInterface.h | 3 ++- .../Code/Source/CoreLights/DiskLightFeatureProcessor.cpp | 7 +++++++ .../Code/Source/CoreLights/DiskLightFeatureProcessor.h | 1 + .../RPI/Code/Include/Atom/RPI.Public/AuxGeom/AuxGeomDraw.h | 4 ++-- 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/CoreLights/DiskLightFeatureProcessorInterface.h b/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/CoreLights/DiskLightFeatureProcessorInterface.h index 98220fae15..9622b5aa40 100644 --- a/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/CoreLights/DiskLightFeatureProcessorInterface.h +++ b/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/CoreLights/DiskLightFeatureProcessorInterface.h @@ -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 diff --git a/Gems/Atom/Feature/Common/Code/Source/CoreLights/DiskLightFeatureProcessor.cpp b/Gems/Atom/Feature/Common/Code/Source/CoreLights/DiskLightFeatureProcessor.cpp index 168a7ea00a..9c626fccd8 100644 --- a/Gems/Atom/Feature/Common/Code/Source/CoreLights/DiskLightFeatureProcessor.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/CoreLights/DiskLightFeatureProcessor.cpp @@ -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 DiskLightFeatureProcessor::GetLightBuffer()const { return m_lightBufferHandler.GetBuffer(); diff --git a/Gems/Atom/Feature/Common/Code/Source/CoreLights/DiskLightFeatureProcessor.h b/Gems/Atom/Feature/Common/Code/Source/CoreLights/DiskLightFeatureProcessor.h index bafddacc65..d742b1fbc0 100644 --- a/Gems/Atom/Feature/Common/Code/Source/CoreLights/DiskLightFeatureProcessor.h +++ b/Gems/Atom/Feature/Common/Code/Source/CoreLights/DiskLightFeatureProcessor.h @@ -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 GetLightBuffer()const; uint32_t GetLightCount()const; diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/AuxGeom/AuxGeomDraw.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/AuxGeom/AuxGeomDraw.h index 29b127407e..b3e898c4dd 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/AuxGeom/AuxGeomDraw.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/AuxGeom/AuxGeomDraw.h @@ -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. From d1bb5a0543268ec73223aa24eea093f864a830f4 Mon Sep 17 00:00:00 2001 From: Nicholas Van Sickle Date: Tue, 8 Feb 2022 13:44:08 -0800 Subject: [PATCH 15/29] Move DOM delta comparison to its own file, enhance inverting moves Signed-off-by: Nicholas Van Sickle --- .../AzCore/AzCore/DOM/DomComparison.cpp | 178 ++++++++++++++ .../AzCore/AzCore/DOM/DomComparison.h | 37 +++ Code/Framework/AzCore/AzCore/DOM/DomPatch.cpp | 219 ++---------------- Code/Framework/AzCore/AzCore/DOM/DomPatch.h | 26 +-- .../AzCore/AzCore/azcore_files.cmake | 2 + .../AzCore/Tests/DOM/DomPatchTests.cpp | 5 +- 6 files changed, 247 insertions(+), 220 deletions(-) create mode 100644 Code/Framework/AzCore/AzCore/DOM/DomComparison.cpp create mode 100644 Code/Framework/AzCore/AzCore/DOM/DomComparison.h diff --git a/Code/Framework/AzCore/AzCore/DOM/DomComparison.cpp b/Code/Framework/AzCore/AzCore/DOM/DomComparison.cpp new file mode 100644 index 0000000000..dc21761670 --- /dev/null +++ b/Code/Framework/AzCore/AzCore/DOM/DomComparison.cpp @@ -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 +#include +#include + +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 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 entriesToCompare; + + AZStd::unordered_set 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; + } +} diff --git a/Code/Framework/AzCore/AzCore/DOM/DomComparison.h b/Code/Framework/AzCore/AzCore/DOM/DomComparison.h new file mode 100644 index 0000000000..f882887cba --- /dev/null +++ b/Code/Framework/AzCore/AzCore/DOM/DomComparison.h @@ -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 + +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::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 diff --git a/Code/Framework/AzCore/AzCore/DOM/DomPatch.cpp b/Code/Framework/AzCore/AzCore/DOM/DomPatch.cpp index f89cb313e7..dc0bff4e7c 100644 --- a/Code/Framework/AzCore/AzCore/DOM/DomPatch.cpp +++ b/Code/Framework/AzCore/AzCore/DOM/DomPatch.cpp @@ -8,8 +8,6 @@ #include #include -#include -#include namespace AZ::Dom { @@ -297,7 +295,7 @@ namespace AZ::Dom } } - AZ::Outcome PatchOperation::GetInverse(Value stateBeforeApplication) const + AZ::Outcome, AZStd::string> PatchOperation::GetInverse(Value stateBeforeApplication) const { switch (m_type) { @@ -310,10 +308,10 @@ namespace AZ::Dom const Value* existingValue = stateBeforeApplication.FindChild(m_domPath); if (existingValue != nullptr) { - return AZ::Success(PatchOperation::ReplaceOperation(m_domPath, *existingValue)); + return AZ::Success({PatchOperation::ReplaceOperation(m_domPath, *existingValue)}); } } - return AZ::Success(PatchOperation::RemoveOperation(m_domPath)); + return AZ::Success({PatchOperation::RemoveOperation(m_domPath)}); } case Type::Remove: { @@ -325,7 +323,7 @@ namespace AZ::Dom m_domPath.AppendToString(errorMessage); return AZ::Failure(AZStd::move(errorMessage)); } - return AZ::Success(PatchOperation::AddOperation(m_domPath, *existingValue)); + return AZ::Success({PatchOperation::AddOperation(m_domPath, *existingValue)}); } case Type::Replace: { @@ -337,7 +335,7 @@ namespace AZ::Dom m_domPath.AppendToString(errorMessage); return AZ::Failure(AZStd::move(errorMessage)); } - return AZ::Success(PatchOperation::ReplaceOperation(m_domPath, *existingValue)); + return AZ::Success({PatchOperation::ReplaceOperation(m_domPath, *existingValue)}); } case Type::Copy: { @@ -349,40 +347,37 @@ namespace AZ::Dom m_domPath.AppendToString(errorMessage); return AZ::Failure(AZStd::move(errorMessage)); } - return AZ::Success(PatchOperation::ReplaceOperation(m_domPath, *existingValue)); + return AZ::Success({PatchOperation::ReplaceOperation(m_domPath, *existingValue)}); } case Type::Move: { - // Move -> Replace, using the common ancestor of the two paths as the replacement - // This is not a minimal inverse, which would be two replace operations at each path - const Path& destPath = m_domPath; - const Path& sourcePath = GetSourcePath(); - - Path commonAncestor; - for (size_t i = 0; i < destPath.Size() && i < sourcePath.Size(); ++i) + const Value* sourceValue = stateBeforeApplication.FindChild(GetSourcePath()); + if (sourceValue == nullptr) { - if (destPath[i] != sourcePath[i]) - { - break; - } - - commonAncestor.Push(destPath[i]); - } - - const Value* existingValue = stateBeforeApplication.FindChild(commonAncestor); - if (existingValue == nullptr) - { - AZStd::string errorMessage = "Unable to invert DOM copy patch, common ancestor path not found: "; - commonAncestor.AppendToString(errorMessage); + 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(PatchOperation::ReplaceOperation(commonAncestor, *existingValue)); + + // 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({ + PatchOperation::AddOperation(GetSourcePath(), *sourceValue), + PatchOperation::ReplaceOperation(GetDestinationPath(), *destinationValue), + }); + } + // Otherwise, just do a move + return AZ::Success({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(*this); + return AZ::Success({*this}); } } return AZ::Failure("Unable to invert DOM patch, unknown type specified"); @@ -801,168 +796,4 @@ namespace AZ::Dom { return PatchOperation(AZStd::move(testPath), PatchOperation::Type::Test, AZStd::move(value)); } - - PatchInfo GenerateHierarchicalDeltaPatch( - const Value& beforeState, const Value& afterState, const DeltaPatchGenerationParameters& params) - { - PatchInfo patches; - - auto AddPatch = [&patches](PatchOperation op, PatchOperation inverse) - { - patches.m_forwardPatches.PushBack(AZStd::move(op)); - patches.m_inversePatches.PushFront(AZStd::move(inverse)); - }; - - AZStd::function 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 entriesToCompare; - - AZStd::unordered_set 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; - } } // namespace AZ::Dom diff --git a/Code/Framework/AzCore/AzCore/DOM/DomPatch.h b/Code/Framework/AzCore/AzCore/DOM/DomPatch.h index 40ef725b57..2d591a8079 100644 --- a/Code/Framework/AzCore/AzCore/DOM/DomPatch.h +++ b/Code/Framework/AzCore/AzCore/DOM/DomPatch.h @@ -71,7 +71,8 @@ namespace AZ::Dom Value GetDomRepresentation() const; static AZ::Outcome CreateFromDomRepresentation(Value domValue); - AZ::Outcome GetInverse(Value stateBeforeApplication) const; + using InversePatches = AZStd::fixed_vector; + AZ::Outcome, AZStd::string> GetInverse(Value stateBeforeApplication) const; enum class ExistenceCheckFlags : AZ::u8 { @@ -182,27 +183,4 @@ namespace AZ::Dom private: OperationsContainer m_operations; }; - - //! A set of patches for applying a change and doing the inverse operation (i.e. undoing it). - struct PatchInfo - { - Patch m_forwardPatches; - Patch m_inversePatches; - }; - - //! Parameters for GenerateHierarchicalDeltaPatch. - struct DeltaPatchGenerationParameters - { - static constexpr size_t NoReplace = AZStd::numeric_limits::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. - PatchInfo GenerateHierarchicalDeltaPatch(const Value& beforeState, const Value& afterState, const DeltaPatchGenerationParameters& params = {}); } // namespace AZ::Dom diff --git a/Code/Framework/AzCore/AzCore/azcore_files.cmake b/Code/Framework/AzCore/AzCore/azcore_files.cmake index ad7d7cbdef..b7821f1d65 100644 --- a/Code/Framework/AzCore/AzCore/azcore_files.cmake +++ b/Code/Framework/AzCore/AzCore/azcore_files.cmake @@ -128,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 diff --git a/Code/Framework/AzCore/Tests/DOM/DomPatchTests.cpp b/Code/Framework/AzCore/Tests/DOM/DomPatchTests.cpp index a0e4f349a5..b60c09330f 100644 --- a/Code/Framework/AzCore/Tests/DOM/DomPatchTests.cpp +++ b/Code/Framework/AzCore/Tests/DOM/DomPatchTests.cpp @@ -7,6 +7,7 @@ */ #include +#include #include namespace AZ::Dom::Tests @@ -45,9 +46,9 @@ namespace AZ::Dom::Tests DomTestFixture::TearDown(); } - PatchInfo GenerateAndVerifyDelta() + PatchUndoRedoInfo GenerateAndVerifyDelta() { - PatchInfo info = GenerateHierarchicalDeltaPatch(m_dataset, m_deltaDataset); + PatchUndoRedoInfo info = GenerateHierarchicalDeltaPatch(m_dataset, m_deltaDataset); auto result = info.m_forwardPatches.Apply(m_dataset); EXPECT_TRUE(result.IsSuccess()); From 61f915366a92a02f57e3a3b53b7f30ada76eddbf Mon Sep 17 00:00:00 2001 From: Jeremy Ong Date: Fri, 4 Feb 2022 11:53:37 -0700 Subject: [PATCH 16/29] Shader refactor (no functional changes) The purpose of this shader refactor is to split various material type shaders into disparate pieces: 1. The original material type shaders now include an external file with the actual shader entry points and structure of the algorithm (e.g. depth pass, shadow pass, forward pass) but continue to specify the SRGs used 2. Common functionality used across multiple shaders was consolidated into routines implemented in the MaterialFunctions folder (Materials/Types/MaterialFunctions) 3. The implementation shaders rely on common routines to be included/imported prior to inclusion, and by design, make no references to any Draw, Object, or Material SRG. This refactor only includes the Standard and Enhanced material types, and is, for the most part, a non-functional change. However, the Surface definition needed to be augmented to include information needed by lighting later. Modifying the Surface structure enables the lighting loops to avoid any references to the Material SRG. This completes the decoupling needed to support future Material canvas work, as well as a future Material pipeline abstraction (where by the implementation shaders can be injected by the user, customized per platform, and in general, are simply decoupled from the materials themselves). Signed-off-by: Jeremy Ong --- .../Materials/Types/BasePBR_ForwardPass.azsl | 6 +- .../Materials/Types/DepthPass_WithPS.azsl | 124 ++++++ .../Types/EnhancedPBR_DepthPass_WithPS.azsl | 91 +--- .../Types/EnhancedPBR_ForwardPass.azsl | 419 +----------------- .../Types/EnhancedPBR_Shadowmap_WithPS.azsl | 89 +--- .../Types/EnhancedSurface_ForwardPass.azsl | 317 +++++++++++++ .../EnhancedParallaxDepth.azsli | 41 ++ .../EvaluateEnhancedSurface.azsli | 149 +++++++ .../EvaluateStandardSurface.azsli | 95 ++++ .../EvaluateTangentFrame.azsli | 33 ++ .../MultilayerParallaxDepth.azsli | 35 ++ .../MaterialFunctions/ParallaxDepth.azsli | 51 +++ .../MaterialFunctions/StandardGetAlpha.azsli | 17 + .../StandardGetNormalToWorld.azsli | 12 + .../StandardGetObjectToWorld.azsli | 12 + .../MaterialFunctions/StandardMaybeClip.azsli | 14 + .../StandardTransformDetailUvs.azsli | 18 + .../StandardTransformUvs.azsli | 14 + .../Materials/Types/ShadowMap_WithPS.azsl | 127 ++++++ ...tandardMultilayerPBR_DepthPass_WithPS.azsl | 107 +---- .../StandardMultilayerPBR_ForwardPass.azsl | 10 +- ...tandardMultilayerPBR_Shadowmap_WithPS.azsl | 108 +---- .../Types/StandardPBR_DepthPass_WithPS.azsl | 94 +--- .../Types/StandardPBR_ForwardPass.azsl | 342 +------------- .../Types/StandardPBR_Shadowmap_WithPS.azsl | 93 +--- .../Types/StandardSurface_ForwardPass.azsl | 306 +++++++++++++ .../PBR/Surfaces/EnhancedSurface.azsli | 27 +- .../PBR/Surfaces/StandardSurface.azsli | 22 +- .../Types/AutoBrick_ForwardPass.azsl | 16 +- .../Types/MinimalPBR_ForwardPass.azsl | 6 +- 30 files changed, 1492 insertions(+), 1303 deletions(-) create mode 100644 Gems/Atom/Feature/Common/Assets/Materials/Types/DepthPass_WithPS.azsl create mode 100644 Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedSurface_ForwardPass.azsl create mode 100644 Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/EnhancedParallaxDepth.azsli create mode 100644 Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/EvaluateEnhancedSurface.azsli create mode 100644 Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/EvaluateStandardSurface.azsli create mode 100644 Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/EvaluateTangentFrame.azsli create mode 100644 Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/MultilayerParallaxDepth.azsli create mode 100644 Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/ParallaxDepth.azsli create mode 100644 Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/StandardGetAlpha.azsli create mode 100644 Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/StandardGetNormalToWorld.azsli create mode 100644 Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/StandardGetObjectToWorld.azsli create mode 100644 Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/StandardMaybeClip.azsli create mode 100644 Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/StandardTransformDetailUvs.azsli create mode 100644 Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/StandardTransformUvs.azsli create mode 100644 Gems/Atom/Feature/Common/Assets/Materials/Types/ShadowMap_WithPS.azsl create mode 100644 Gems/Atom/Feature/Common/Assets/Materials/Types/StandardSurface_ForwardPass.azsl diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/BasePBR_ForwardPass.azsl b/Gems/Atom/Feature/Common/Assets/Materials/Types/BasePBR_ForwardPass.azsl index 96318f2284..b887761917 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/BasePBR_ForwardPass.azsl +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/BasePBR_ForwardPass.azsl @@ -116,19 +116,19 @@ PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace) float2 baseColorUv = IN.m_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); + surface.baseColor = BlendBaseColor(sampledColor, MaterialSrg::m_baseColor.rgb, MaterialSrg::m_baseColorFactor, o_baseColorTextureBlendMode, o_baseColor_useTexture); // ------- 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); + surface.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); + surface.SetAlbedoAndSpecularF0(specularF0Factor); // ------- Roughness ------- diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/DepthPass_WithPS.azsl b/Gems/Atom/Feature/Common/Assets/Materials/Types/DepthPass_WithPS.azsl new file mode 100644 index 0000000000..9b017222b1 --- /dev/null +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/DepthPass_WithPS.azsl @@ -0,0 +1,124 @@ +/* + * 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 + * + */ + +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; + +#ifdef 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; + +#ifdef 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); + } + +#ifdef 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]); + } + +#ifdef 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 + + } + +#ifndef MULTILAYER + float alpha = GetAlpha(IN.m_uv); + MaybeClip(alpha, IN.m_uv); +#endif + + return OUT; +} diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_DepthPass_WithPS.azsl b/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_DepthPass_WithPS.azsl index 08642decc0..2f7a5b94f9 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_DepthPass_WithPS.azsl +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_DepthPass_WithPS.azsl @@ -8,88 +8,13 @@ #include "./EnhancedPBR_Common.azsli" #include -#include -#include -#include "MaterialInputs/AlphaInput.azsli" -#include "MaterialInputs/ParallaxInput.azsli" +#include "MaterialFunctions/StandardGetObjectToWorld.azsli" +#include "MaterialFunctions/StandardGetNormalToWorld.azsli" +#include "MaterialFunctions/StandardGetAlpha.azsli" +#include "MaterialFunctions/StandardTransformUvs.azsli" +#include "MaterialFunctions/EvaluateTangentFrame.azsli" +#include "MaterialFunctions/ParallaxDepth.azsli" +#include "MaterialFunctions/StandardMaybeClip.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; -} +#include "DepthPass_WithPS.azsl" diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_ForwardPass.azsl b/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_ForwardPass.azsl index 8458ca11a2..bac97fed64 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_ForwardPass.azsl +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_ForwardPass.azsl @@ -10,21 +10,6 @@ // SRGs #include -#include - -// Pass Output -#include - -// Utility -#include -#include - -// Custom Surface & Lighting -#include - -// Decals -#include - // ---------- 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 - -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/StandardMaybeClip.azsli" +#include "MaterialFunctions/StandardTransformDetailUvs.azsli" +#include "MaterialFunctions/StandardTransformUvs.azsli" + +#include "EnhancedSurface_ForwardPass.azsl" \ No newline at end of file diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_Shadowmap_WithPS.azsl b/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_Shadowmap_WithPS.azsl index 0f2457fcd8..6c01a63a7c 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_Shadowmap_WithPS.azsl +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_Shadowmap_WithPS.azsl @@ -16,85 +16,12 @@ #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/StandardGetAlpha.azsli" +#include "MaterialFunctions/StandardTransformUvs.azsli" +#include "MaterialFunctions/EvaluateTangentFrame.azsli" +#include "MaterialFunctions/ParallaxDepth.azsli" +#include "MaterialFunctions/StandardMaybeClip.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; -} +#include "ShadowMap_WithPS.azsl" \ No newline at end of file diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedSurface_ForwardPass.azsl b/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedSurface_ForwardPass.azsl new file mode 100644 index 0000000000..ceb9379f28 --- /dev/null +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedSurface_ForwardPass.azsl @@ -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 + +// Pass Output +#include + +// Utility +#include + +// Custom Surface & Lighting +#include + +// Decals +#include + +// ---------- 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); + } + } + } + + 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 = GetAlpha(IN.m_uv); + MaybeClip(alpha, IN.m_uv); + + EvaluateEnhancedSurface(IN.m_normal, IN.m_uv, IN.m_detailUv, tangents, bitangents, isFrontFace, displacementIsClipped, surface); + + // ------- 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, surface.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, surface.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, surface.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), surface.subsurfaceScatteringQuality) * 255), float2(256, 1)); + lightingOutput.m_diffuseColor.w = factorAndQuality * (o_enableSubsurfaceScattering ? 1.0 : -1.0); + lightingOutput.m_scatterDistance = surface.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; +} diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/EnhancedParallaxDepth.azsli b/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/EnhancedParallaxDepth.azsli new file mode 100644 index 0000000000..e89afd6f37 --- /dev/null +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/EnhancedParallaxDepth.azsli @@ -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 +#include "../MaterialInputs/ParallaxInput.azsli" +#include + + 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]; +} \ No newline at end of file diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/EvaluateEnhancedSurface.azsli b/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/EvaluateEnhancedSurface.azsli new file mode 100644 index 0000000000..9cc3e05789 --- /dev/null +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/EvaluateEnhancedSurface.azsli @@ -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 +#include +#include "StandardGetAlpha.azsli" + +void EvaluateEnhancedSurface( + float3 normal, + float2 uvs[UvSetCount], + float2 detailUvs[UvSetCount], + float3 tangents[UvSetCount], + float3 bitangents[UvSetCount], + bool isFrontFace, + bool displacementIsClipped, + inout Surface surface) +{ + // ------- 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]; + + surface.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(surface.baseColor); + } + + // ------- Metallic ------- + + surface.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]; + surface.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(specularF0Factor); + + // ------- 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); + surface.subsurfaceScatteringQuality = MaterialSrg::m_subsurfaceScatteringQuality; + surface.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); + } + + surface.opacityAffectsSpecularFactor = MaterialSrg::m_opacityAffectsSpecularFactor; +} diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/EvaluateStandardSurface.azsli b/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/EvaluateStandardSurface.azsli new file mode 100644 index 0000000000..54a4a2462a --- /dev/null +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/EvaluateStandardSurface.azsli @@ -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 +#include +#include "StandardGetAlpha.azsli" + +void EvaluateStandardSurface( + float3 normal, + float2 uv[UvSetCount], + float3 tangents[UvSetCount], + float3 bitangents[UvSetCount], + bool isFrontFace, + bool displacementIsClipped, + inout Surface surface) +{ + // ------- 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); + surface.baseColor = BlendBaseColor(sampledColor, MaterialSrg::m_baseColor.rgb, MaterialSrg::m_baseColorFactor, o_baseColorTextureBlendMode, o_baseColor_useTexture); + + if(o_parallax_highlightClipping && displacementIsClipped) + { + ApplyParallaxClippingHighlight(surface.baseColor); + } + + // ------- Metallic ------- + + float2 metallicUv = uv[MaterialSrg::m_metallicMapUvIndex]; + surface.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(specularF0Factor); + + // ------- 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 ------- + surface.opacityAffectsSpecularFactor = MaterialSrg::m_opacityAffectsSpecularFactor; +} \ No newline at end of file diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/EvaluateTangentFrame.azsli b/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/EvaluateTangentFrame.azsli new file mode 100644 index 0000000000..2aad5f257d --- /dev/null +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/EvaluateTangentFrame.azsli @@ -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, + float3 OUT_tangent, + 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); \ + } +} \ No newline at end of file diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/MultilayerParallaxDepth.azsli b/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/MultilayerParallaxDepth.azsli new file mode 100644 index 0000000000..5ee527b9a4 --- /dev/null +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/MultilayerParallaxDepth.azsli @@ -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 +#include "../MaterialInputs/ParallaxInput.azsli" +#include + + void MultilayerSetPixelDepth( + float 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); +} \ No newline at end of file diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/ParallaxDepth.azsli b/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/ParallaxDepth.azsli new file mode 100644 index 0000000000..0298656375 --- /dev/null +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/ParallaxDepth.azsli @@ -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 +#include "../MaterialInputs/ParallaxInput.azsli" +#include + + 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); +} \ No newline at end of file diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/StandardGetAlpha.azsli b/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/StandardGetAlpha.azsli new file mode 100644 index 0000000000..90f8e417e1 --- /dev/null +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/StandardGetAlpha.azsli @@ -0,0 +1,17 @@ +/* + * 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 GetAlpha(float2 uvs[UvSetCount]) +{ + // Alpha + float2 baseColorUV = uvs[MaterialSrg::m_baseColorMapUvIndex]; + float2 opacityUV = uvs[MaterialSrg::m_opacityMapUvIndex]; + return MaterialSrg::m_opacityFactor * SampleAlpha(MaterialSrg::m_baseColorMap, MaterialSrg::m_opacityMap, baseColorUV, opacityUV, MaterialSrg::m_sampler, o_opacity_source); +} \ No newline at end of file diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/StandardGetNormalToWorld.azsli b/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/StandardGetNormalToWorld.azsli new file mode 100644 index 0000000000..b79b5b3418 --- /dev/null +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/StandardGetNormalToWorld.azsli @@ -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 + * + */ + +float3x3 GetNormalToWorld() +{ + return ObjectSrg::GetWorldMatrixInverseTranspose(); +} \ No newline at end of file diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/StandardGetObjectToWorld.azsli b/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/StandardGetObjectToWorld.azsli new file mode 100644 index 0000000000..435326215b --- /dev/null +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/StandardGetObjectToWorld.azsli @@ -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(); +} \ No newline at end of file diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/StandardMaybeClip.azsli b/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/StandardMaybeClip.azsli new file mode 100644 index 0000000000..2dabf9ba1e --- /dev/null +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/StandardMaybeClip.azsli @@ -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 + * + */ + +#include + +void MaybeClip(float alpha, float2 uvs[UvSetCount]) +{ + CheckClipping(alpha, MaterialSrg::m_opacityFactor); +} \ No newline at end of file diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/StandardTransformDetailUvs.azsli b/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/StandardTransformDetailUvs.azsli new file mode 100644 index 0000000000..60ab575a2d --- /dev/null +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/StandardTransformDetailUvs.azsli @@ -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; +} \ No newline at end of file diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/StandardTransformUvs.azsli b/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/StandardTransformUvs.azsli new file mode 100644 index 0000000000..dd77f99ac3 --- /dev/null +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/StandardTransformUvs.azsli @@ -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]; +} \ No newline at end of file diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/ShadowMap_WithPS.azsl b/Gems/Atom/Feature/Common/Assets/Materials/Types/ShadowMap_WithPS.azsl new file mode 100644 index 0000000000..dd0665efe7 --- /dev/null +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/ShadowMap_WithPS.azsl @@ -0,0 +1,127 @@ +/* + * 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 +#include + +struct VertexInput +{ + 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; + +#ifdef 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 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; + +#ifdef MULTILAYER + float3 m_blendMask : UV3; +#endif +}; + +VertexOutput MainVS(VertexInput IN) +{ + const float4x4 objectToWorld = GetObjectToWorld(); + 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)); + + float2 uv[UvSetCount] = { IN.m_uv0, IN.m_uv1 }; + TransformUvs(uv, 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); + } + +#ifdef 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 +{ + 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, 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]); + } + +#ifdef 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 + + OUT.m_depth += PdoShadowMapBias; + } + +#ifndef MULTILAYER + float alpha = GetAlpha(IN.m_uv); + MaybeClip(alpha, IN.m_uv); +#endif + + return OUT; +} diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_DepthPass_WithPS.azsl b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_DepthPass_WithPS.azsl index 7875457489..d1a488ec0a 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_DepthPass_WithPS.azsl +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_DepthPass_WithPS.azsl @@ -21,104 +21,11 @@ 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 +#include "DepthPass_WithPS.azsl" diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_ForwardPass.azsl b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_ForwardPass.azsl index 3617b7fb04..2fe9990862 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_ForwardPass.azsl +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_ForwardPass.azsl @@ -450,16 +450,16 @@ 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); + surface.baseColor = BlendLayers(lightingInputLayer1.m_baseColor, lightingInputLayer2.m_baseColor, lightingInputLayer3.m_baseColor, blendWeights); + float specularF0Factor = BlendLayers(lightingInputLayer1.m_specularF0Factor, lightingInputLayer2.m_specularF0Factor, lightingInputLayer3.m_specularF0Factor, blendWeights); + surface.metallic = BlendLayers(lightingInputLayer1.m_metallic, lightingInputLayer2.m_metallic, lightingInputLayer3.m_metallic, blendWeights); if(o_parallax_highlightClipping && displacementIsClipped) { - ApplyParallaxClippingHighlight(baseColor); + ApplyParallaxClippingHighlight(surface.baseColor); } - surface.SetAlbedoAndSpecularF0(baseColor, specularF0Factor, metallic); + surface.SetAlbedoAndSpecularF0(specularF0Factor); surface.roughnessLinear = BlendLayers(lightingInputLayer1.m_roughness, lightingInputLayer2.m_roughness, lightingInputLayer3.m_roughness, blendWeights); surface.CalculateRoughnessA(); diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_Shadowmap_WithPS.azsl b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_Shadowmap_WithPS.azsl index 45d8ed94b3..84b44512f0 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_Shadowmap_WithPS.azsl +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_Shadowmap_WithPS.azsl @@ -5,7 +5,7 @@ * SPDX-License-Identifier: Apache-2.0 OR MIT * */ - + #include #include #include @@ -21,103 +21,11 @@ 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 +#include "ShadowMap_WithPS.azsl" \ No newline at end of file diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_DepthPass_WithPS.azsl b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_DepthPass_WithPS.azsl index 04fd104407..fe46d9bfba 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_DepthPass_WithPS.azsl +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_DepthPass_WithPS.azsl @@ -8,91 +8,13 @@ #include "./StandardPBR_Common.azsli" #include -#include -#include -#include "MaterialInputs/AlphaInput.azsli" -#include "MaterialInputs/ParallaxInput.azsli" +#include "MaterialFunctions/StandardGetObjectToWorld.azsli" +#include "MaterialFunctions/StandardGetNormalToWorld.azsli" +#include "MaterialFunctions/StandardGetAlpha.azsli" +#include "MaterialFunctions/EvaluateTangentFrame.azsli" +#include "MaterialFunctions/StandardTransformUvs.azsli" +#include "MaterialFunctions/ParallaxDepth.azsli" +#include "MaterialFunctions/StandardMaybeClip.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; -} +#include "DepthPass_WithPS.azsl" diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_ForwardPass.azsl b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_ForwardPass.azsl index b99ea734af..1a5e92b1d3 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_ForwardPass.azsl +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_ForwardPass.azsl @@ -10,23 +10,7 @@ #include "StandardPBR_Common.azsli" -// SRGs #include -#include - -// Pass Output -#include - -// Utility -#include -#include - -// Custom Surface & Lighting -#include - -// Decals -#include - // ---------- 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 - -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/StandardMaybeClip.azsli" +#include "MaterialFunctions/StandardTransformUvs.azsli" + +#include "StandardSurface_ForwardPass.azsl" \ No newline at end of file diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_Shadowmap_WithPS.azsl b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_Shadowmap_WithPS.azsl index 02f1cd0389..e8aeb20871 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_Shadowmap_WithPS.azsl +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_Shadowmap_WithPS.azsl @@ -9,93 +9,16 @@ #include #include "StandardPBR_Common.azsli" #include -#include -#include -#include #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/StandardGetAlpha.azsli" +#include "MaterialFunctions/StandardTransformUvs.azsli" +#include "MaterialFunctions/EvaluateTangentFrame.azsli" +#include "MaterialFunctions/ParallaxDepth.azsli" +#include "MaterialFunctions/StandardMaybeClip.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; -} +#include "ShadowMap_WithPS.azsl" \ No newline at end of file diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardSurface_ForwardPass.azsl b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardSurface_ForwardPass.azsl new file mode 100644 index 0000000000..1d3fa29991 --- /dev/null +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardSurface_ForwardPass.azsl @@ -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 + +// Pass Output +#include + +// Utility +#include +#include + +// Custom Surface & Lighting +#include + +// Decals +#include + + +// ---------- 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 + +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); + } + } + } + + 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 = GetAlpha(IN.m_uv); + MaybeClip(alpha, IN.m_uv); + + EvaluateStandardSurface(IN.m_normal, IN.m_uv, tangents, bitangents, isFrontFace, displacementIsClipped, surface); + + // ------- 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, surface.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, surface.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, surface.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; +} diff --git a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Surfaces/EnhancedSurface.azsli b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Surfaces/EnhancedSurface.azsli index eb8c33cdce..21f55ea259 100644 --- a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Surfaces/EnhancedSurface.azsli +++ b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Surfaces/EnhancedSurface.azsli @@ -24,12 +24,31 @@ 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 + float3 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 + //! Subsurface scattering parameters + float subsurfaceScatteringFactor; + 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; + + //! 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(); @@ -37,7 +56,7 @@ class Surface void CalculateRoughnessA(); //! Sets albedo and specularF0 using metallic workflow - void SetAlbedoAndSpecularF0(float3 baseColor, float specularF0Factor, float metallic); + void SetAlbedoAndSpecularF0(float specularF0Factor); }; @@ -76,7 +95,7 @@ void Surface::CalculateRoughnessA() } } -void Surface::SetAlbedoAndSpecularF0(float3 baseColor, float specularF0Factor, float metallic) +void Surface::SetAlbedoAndSpecularF0(float specularF0Factor) { float3 dielectricSpecularF0 = MaxDielectricSpecularF0 * specularF0Factor; diff --git a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Surfaces/StandardSurface.azsli b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Surfaces/StandardSurface.azsli index cf4edba923..9d4abeb0f6 100644 --- a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Surfaces/StandardSurface.azsli +++ b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Surfaces/StandardSurface.azsli @@ -25,12 +25,26 @@ 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(); @@ -38,7 +52,7 @@ class Surface void CalculateRoughnessA(); //! Sets albedo and specularF0 using metallic workflow - void SetAlbedoAndSpecularF0(float3 baseColor, float specularF0Factor, float metallic); + void SetAlbedoAndSpecularF0(float specularF0Factor); }; // Specular Anti-Aliasing technique from this paper: @@ -75,7 +89,7 @@ void Surface::CalculateRoughnessA() } } -void Surface::SetAlbedoAndSpecularF0(float3 baseColor, float specularF0Factor, float metallic) +void Surface::SetAlbedoAndSpecularF0(float specularF0Factor) { float3 dielectricSpecularF0 = MaxDielectricSpecularF0 * specularF0Factor; diff --git a/Gems/Atom/TestData/TestData/Materials/Types/AutoBrick_ForwardPass.azsl b/Gems/Atom/TestData/TestData/Materials/Types/AutoBrick_ForwardPass.azsl index 13f1e33ef7..3c4388350e 100644 --- a/Gems/Atom/TestData/TestData/Materials/Types/AutoBrick_ForwardPass.azsl +++ b/Gems/Atom/TestData/TestData/Materials/Types/AutoBrick_ForwardPass.azsl @@ -144,18 +144,20 @@ ForwardPassOutput AutoBrick_ForwardPassPS(VSOutput IN) identityUvMatrix); IN.m_uv += tangentOffset.m_offsetTS.xy; + + Surface surface; - float3 baseColor = float3(1,1,1); + surface.baseColor = float3(1,1,1); const float noise = AutoBrickSrg::m_noise.Sample(AutoBrickSrg::m_sampler, IN.m_uv).r; float distanceFromBrick = GetNormalizedDistanceFromBrick(IN.m_uv); if(distanceFromBrick > AutoBrickSrg::m_brickColorBleed) { - baseColor = AutoBrickSrg::m_lineColor * lerp(1.0, noise, AutoBrickSrg::m_lineNoiseFactor); + surface.baseColor = AutoBrickSrg::m_lineColor * lerp(1.0, noise, AutoBrickSrg::m_lineNoiseFactor); } else { - baseColor = AutoBrickSrg::m_brickColor * lerp(1.0, noise, AutoBrickSrg::m_brickNoiseFactor); + surface.baseColor = AutoBrickSrg::m_brickColor * lerp(1.0, noise, AutoBrickSrg::m_brickNoiseFactor); } float surfaceDepth; @@ -164,8 +166,6 @@ ForwardPassOutput AutoBrick_ForwardPassPS(VSOutput IN) const float3 normal = TangentSpaceToWorld(surfaceNormal, normalize(IN.m_normal), normalize(IN.m_tangent), normalize(IN.m_bitangent)); // ------- Surface ------- - - Surface surface; // Position, Normal, Roughness surface.position = IN.m_worldPosition.xyz; @@ -175,9 +175,9 @@ ForwardPassOutput AutoBrick_ForwardPassPS(VSOutput IN) surface.CalculateRoughnessA(); // Albedo, SpecularF0 - const float metallic = 0.0f; - const float specularF0Factor = 0.5f; - surface.SetAlbedoAndSpecularF0(baseColor, specularF0Factor, metallic); + surface.metallic = 0.0f; + float specularF0Factor = 0.5f; + surface.SetAlbedoAndSpecularF0(specularF0Factor); // Clear Coat surface.clearCoat.InitializeToZero(); diff --git a/Gems/Atom/TestData/TestData/Materials/Types/MinimalPBR_ForwardPass.azsl b/Gems/Atom/TestData/TestData/Materials/Types/MinimalPBR_ForwardPass.azsl index 140fc57961..503c0107db 100644 --- a/Gems/Atom/TestData/TestData/Materials/Types/MinimalPBR_ForwardPass.azsl +++ b/Gems/Atom/TestData/TestData/Materials/Types/MinimalPBR_ForwardPass.azsl @@ -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); + surface.baseColor = MinimalPBRSrg::m_baseColor; + surface.metallic = MinimalPBRSrg::m_metallic; + float specularF0Factor = 0.5f; + surface.SetAlbedoAndSpecularF0(specularF0Factor); // Clear Coat surface.clearCoat.InitializeToZero(); From 7aace39c2fe63757c3ac966452b030842511e734 Mon Sep 17 00:00:00 2001 From: Jeremy Ong Date: Fri, 4 Feb 2022 13:37:42 -0700 Subject: [PATCH 17/29] Consolidate alpha retrieval and clip Signed-off-by: Jeremy Ong --- .../Assets/Materials/Types/DepthPass_WithPS.azsl | 3 +-- .../Types/EnhancedPBR_DepthPass_WithPS.azsl | 3 +-- .../Materials/Types/EnhancedPBR_ForwardPass.azsl | 2 +- .../Types/EnhancedPBR_Shadowmap_WithPS.azsl | 3 +-- .../Types/EnhancedSurface_ForwardPass.azsl | 3 +-- .../EvaluateEnhancedSurface.azsli | 1 - .../EvaluateStandardSurface.azsli | 1 - .../MaterialFunctions/EvaluateTangentFrame.azsli | 4 ++-- ...etAlpha.azsli => StandardGetAlphaAndClip.azsli} | 6 ++++-- .../MaterialFunctions/StandardMaybeClip.azsli | 14 -------------- .../Assets/Materials/Types/ShadowMap_WithPS.azsl | 3 +-- .../Types/StandardPBR_DepthPass_WithPS.azsl | 3 +-- .../Materials/Types/StandardPBR_ForwardPass.azsl | 2 +- .../Types/StandardPBR_Shadowmap_WithPS.azsl | 3 +-- .../Types/StandardSurface_ForwardPass.azsl | 3 +-- 15 files changed, 16 insertions(+), 38 deletions(-) rename Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/{StandardGetAlpha.azsli => StandardGetAlphaAndClip.azsli} (57%) delete mode 100644 Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/StandardMaybeClip.azsli diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/DepthPass_WithPS.azsl b/Gems/Atom/Feature/Common/Assets/Materials/Types/DepthPass_WithPS.azsl index 9b017222b1..59d10f28f9 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/DepthPass_WithPS.azsl +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/DepthPass_WithPS.azsl @@ -116,8 +116,7 @@ PSDepthOutput MainPS(VSDepthOutput IN, bool isFrontFace : SV_IsFrontFace) } #ifndef MULTILAYER - float alpha = GetAlpha(IN.m_uv); - MaybeClip(alpha, IN.m_uv); + GetAlphaAndClip(IN.m_uv); #endif return OUT; diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_DepthPass_WithPS.azsl b/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_DepthPass_WithPS.azsl index 2f7a5b94f9..f5ca5c5cde 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_DepthPass_WithPS.azsl +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_DepthPass_WithPS.azsl @@ -11,10 +11,9 @@ #include "MaterialFunctions/StandardGetObjectToWorld.azsli" #include "MaterialFunctions/StandardGetNormalToWorld.azsli" -#include "MaterialFunctions/StandardGetAlpha.azsli" #include "MaterialFunctions/StandardTransformUvs.azsli" #include "MaterialFunctions/EvaluateTangentFrame.azsli" #include "MaterialFunctions/ParallaxDepth.azsli" -#include "MaterialFunctions/StandardMaybeClip.azsli" +#include "MaterialFunctions/StandardGetAlphaAndClip.azsli" #include "DepthPass_WithPS.azsl" diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_ForwardPass.azsl b/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_ForwardPass.azsl index bac97fed64..3e2bd4588b 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_ForwardPass.azsl +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_ForwardPass.azsl @@ -39,7 +39,7 @@ COMMON_OPTIONS_DETAIL_MAPS() #include "MaterialFunctions/EnhancedParallaxDepth.azsli" #include "MaterialFunctions/StandardGetNormalToWorld.azsli" #include "MaterialFunctions/StandardGetObjectToWorld.azsli" -#include "MaterialFunctions/StandardMaybeClip.azsli" +#include "MaterialFunctions/StandardGetAlphaAndClip.azsli" #include "MaterialFunctions/StandardTransformDetailUvs.azsli" #include "MaterialFunctions/StandardTransformUvs.azsli" diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_Shadowmap_WithPS.azsl b/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_Shadowmap_WithPS.azsl index 6c01a63a7c..3a9f8a7417 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_Shadowmap_WithPS.azsl +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_Shadowmap_WithPS.azsl @@ -18,10 +18,9 @@ #include "MaterialFunctions/StandardGetObjectToWorld.azsli" #include "MaterialFunctions/StandardGetNormalToWorld.azsli" -#include "MaterialFunctions/StandardGetAlpha.azsli" #include "MaterialFunctions/StandardTransformUvs.azsli" #include "MaterialFunctions/EvaluateTangentFrame.azsli" #include "MaterialFunctions/ParallaxDepth.azsli" -#include "MaterialFunctions/StandardMaybeClip.azsli" +#include "MaterialFunctions/StandardGetAlphaAndClip.azsli" #include "ShadowMap_WithPS.azsl" \ No newline at end of file diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedSurface_ForwardPass.azsl b/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedSurface_ForwardPass.azsl index ceb9379f28..ef08b928cf 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedSurface_ForwardPass.azsl +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedSurface_ForwardPass.azsl @@ -152,8 +152,7 @@ PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace, out float // ------- Alpha & Clip ------- // TODO: this often invokes a separate sample of the base color texture which is wasteful - float alpha = GetAlpha(IN.m_uv); - MaybeClip(alpha, IN.m_uv); + float alpha = GetAlphaAndClip(IN.m_uv); EvaluateEnhancedSurface(IN.m_normal, IN.m_uv, IN.m_detailUv, tangents, bitangents, isFrontFace, displacementIsClipped, surface); diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/EvaluateEnhancedSurface.azsli b/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/EvaluateEnhancedSurface.azsli index 9cc3e05789..c8d59363ea 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/EvaluateEnhancedSurface.azsli +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/EvaluateEnhancedSurface.azsli @@ -8,7 +8,6 @@ #include #include -#include "StandardGetAlpha.azsli" void EvaluateEnhancedSurface( float3 normal, diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/EvaluateStandardSurface.azsli b/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/EvaluateStandardSurface.azsli index 54a4a2462a..c58f0534b3 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/EvaluateStandardSurface.azsli +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/EvaluateStandardSurface.azsli @@ -8,7 +8,6 @@ #include #include -#include "StandardGetAlpha.azsli" void EvaluateStandardSurface( float3 normal, diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/EvaluateTangentFrame.azsli b/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/EvaluateTangentFrame.azsli index 2aad5f257d..643a2d29ca 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/EvaluateTangentFrame.azsli +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/EvaluateTangentFrame.azsli @@ -17,8 +17,8 @@ void EvaluateTangentFrame( // The input tangent and bitangent vectors are optional and used to forward data from interpolants float3 IN_tangent, float3 IN_bitangent, - float3 OUT_tangent, - float3 OUT_bitangent) + out float3 OUT_tangent, + out float3 OUT_bitangent) { if (DrawSrg::GetTangentAtUv(uvIndex) == 0) { diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/StandardGetAlpha.azsli b/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/StandardGetAlphaAndClip.azsli similarity index 57% rename from Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/StandardGetAlpha.azsli rename to Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/StandardGetAlphaAndClip.azsli index 90f8e417e1..81380e8c07 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/StandardGetAlpha.azsli +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/StandardGetAlphaAndClip.azsli @@ -8,10 +8,12 @@ #include "../MaterialInputs/AlphaInput.azsli" -float GetAlpha(float2 uvs[UvSetCount]) +float GetAlphaAndClip(float2 uvs[UvSetCount]) { // Alpha float2 baseColorUV = uvs[MaterialSrg::m_baseColorMapUvIndex]; float2 opacityUV = uvs[MaterialSrg::m_opacityMapUvIndex]; - return MaterialSrg::m_opacityFactor * SampleAlpha(MaterialSrg::m_baseColorMap, MaterialSrg::m_opacityMap, baseColorUV, opacityUV, MaterialSrg::m_sampler, o_opacity_source); + 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; } \ No newline at end of file diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/StandardMaybeClip.azsli b/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/StandardMaybeClip.azsli deleted file mode 100644 index 2dabf9ba1e..0000000000 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/StandardMaybeClip.azsli +++ /dev/null @@ -1,14 +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 - -void MaybeClip(float alpha, float2 uvs[UvSetCount]) -{ - CheckClipping(alpha, MaterialSrg::m_opacityFactor); -} \ No newline at end of file diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/ShadowMap_WithPS.azsl b/Gems/Atom/Feature/Common/Assets/Materials/Types/ShadowMap_WithPS.azsl index dd0665efe7..3dc2cf5d77 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/ShadowMap_WithPS.azsl +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/ShadowMap_WithPS.azsl @@ -119,8 +119,7 @@ PSDepthOutput MainPS(VertexOutput IN, bool isFrontFace : SV_IsFrontFace) } #ifndef MULTILAYER - float alpha = GetAlpha(IN.m_uv); - MaybeClip(alpha, IN.m_uv); + GetAlphaAndClip(IN.m_uv); #endif return OUT; diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_DepthPass_WithPS.azsl b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_DepthPass_WithPS.azsl index fe46d9bfba..62dcddf2a6 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_DepthPass_WithPS.azsl +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_DepthPass_WithPS.azsl @@ -11,10 +11,9 @@ #include "MaterialFunctions/StandardGetObjectToWorld.azsli" #include "MaterialFunctions/StandardGetNormalToWorld.azsli" -#include "MaterialFunctions/StandardGetAlpha.azsli" +#include "MaterialFunctions/StandardGetAlphaAndClip.azsli" #include "MaterialFunctions/EvaluateTangentFrame.azsli" #include "MaterialFunctions/StandardTransformUvs.azsli" #include "MaterialFunctions/ParallaxDepth.azsli" -#include "MaterialFunctions/StandardMaybeClip.azsli" #include "DepthPass_WithPS.azsl" diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_ForwardPass.azsl b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_ForwardPass.azsl index 1a5e92b1d3..5dc1148370 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_ForwardPass.azsl +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_ForwardPass.azsl @@ -32,7 +32,7 @@ COMMON_OPTIONS_EMISSIVE() #include "MaterialFunctions/ParallaxDepth.azsli" #include "MaterialFunctions/StandardGetNormalToWorld.azsli" #include "MaterialFunctions/StandardGetObjectToWorld.azsli" -#include "MaterialFunctions/StandardMaybeClip.azsli" +#include "MaterialFunctions/StandardGetAlphaAndClip.azsli" #include "MaterialFunctions/StandardTransformUvs.azsli" #include "StandardSurface_ForwardPass.azsl" \ No newline at end of file diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_Shadowmap_WithPS.azsl b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_Shadowmap_WithPS.azsl index e8aeb20871..7acc121036 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_Shadowmap_WithPS.azsl +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_Shadowmap_WithPS.azsl @@ -15,10 +15,9 @@ #include "MaterialFunctions/StandardGetObjectToWorld.azsli" #include "MaterialFunctions/StandardGetNormalToWorld.azsli" -#include "MaterialFunctions/StandardGetAlpha.azsli" +#include "MaterialFunctions/StandardGetAlphaAndClip.azsli" #include "MaterialFunctions/StandardTransformUvs.azsli" #include "MaterialFunctions/EvaluateTangentFrame.azsli" #include "MaterialFunctions/ParallaxDepth.azsli" -#include "MaterialFunctions/StandardMaybeClip.azsli" #include "ShadowMap_WithPS.azsl" \ No newline at end of file diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardSurface_ForwardPass.azsl b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardSurface_ForwardPass.azsl index 1d3fa29991..b60f789cf5 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardSurface_ForwardPass.azsl +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardSurface_ForwardPass.azsl @@ -149,8 +149,7 @@ PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace, out float // ------- Alpha & Clip ------- // TODO: this often invokes a separate sample of the base color texture which is wasteful - float alpha = GetAlpha(IN.m_uv); - MaybeClip(alpha, IN.m_uv); + float alpha = GetAlphaAndClip(IN.m_uv); EvaluateStandardSurface(IN.m_normal, IN.m_uv, tangents, bitangents, isFrontFace, displacementIsClipped, surface); From 4bcc83e7ac66f7c8631aa45fb9b6796d80e634c2 Mon Sep 17 00:00:00 2001 From: Jeremy Ong Date: Fri, 4 Feb 2022 23:59:45 -0700 Subject: [PATCH 18/29] Address PR feedback, ensure all ASV tests pass Signed-off-by: Jeremy Ong --- .../Materials/Types/BasePBR_ForwardPass.azsl | 6 +- ...ass_WithPS.azsl => DepthPass_WithPS.azsli} | 9 +- .../Types/EnhancedPBR_DepthPass_WithPS.azsl | 2 +- .../Types/EnhancedPBR_ForwardPass.azsl | 2 +- .../Types/EnhancedPBR_Shadowmap_WithPS.azsl | 3 +- ...azsl => EnhancedSurface_ForwardPass.azsli} | 13 +- .../EnhancedParallaxDepth.azsli | 2 +- .../EvaluateEnhancedSurface.azsli | 19 +-- .../EvaluateStandardSurface.azsli | 15 ++- .../EvaluateTangentFrame.azsli | 6 +- .../MultilayerParallaxDepth.azsli | 6 +- .../MaterialFunctions/ParallaxDepth.azsli | 2 +- .../StandardGetAlphaAndClip.azsli | 2 +- .../StandardGetNormalToWorld.azsli | 2 +- .../StandardGetObjectToWorld.azsli | 2 +- .../StandardTransformDetailUvs.azsli | 2 +- .../StandardTransformUvs.azsli | 2 +- .../Materials/Types/ShadowMap_WithPS.azsl | 126 ------------------ ...tandardMultilayerPBR_DepthPass_WithPS.azsl | 3 +- .../StandardMultilayerPBR_ForwardPass.azsl | 8 +- ...tandardMultilayerPBR_Shadowmap_WithPS.azsl | 3 +- .../Types/StandardPBR_DepthPass_WithPS.azsl | 2 +- .../Types/StandardPBR_ForwardPass.azsl | 2 +- .../Types/StandardPBR_LowEndForward.azsl | 13 -- .../Types/StandardPBR_LowEndForward.shader | 4 +- .../StandardPBR_LowEndForward_EDS.shader | 4 +- .../Types/StandardPBR_Shadowmap_WithPS.azsl | 3 +- ...azsl => StandardSurface_ForwardPass.azsli} | 9 +- .../PBR/Surfaces/EnhancedSurface.azsli | 33 +++-- .../PBR/Surfaces/StandardSurface.azsli | 22 ++- .../atom_feature_common_asset_files.cmake | 15 ++- .../Types/AutoBrick_ForwardPass.azsl | 16 +-- .../Types/MinimalPBR_ForwardPass.azsl | 6 +- 33 files changed, 137 insertions(+), 227 deletions(-) rename Gems/Atom/Feature/Common/Assets/Materials/Types/{DepthPass_WithPS.azsl => DepthPass_WithPS.azsli} (95%) rename Gems/Atom/Feature/Common/Assets/Materials/Types/{EnhancedSurface_ForwardPass.azsl => EnhancedSurface_ForwardPass.azsli} (96%) delete mode 100644 Gems/Atom/Feature/Common/Assets/Materials/Types/ShadowMap_WithPS.azsl delete mode 100644 Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_LowEndForward.azsl rename Gems/Atom/Feature/Common/Assets/Materials/Types/{StandardSurface_ForwardPass.azsl => StandardSurface_ForwardPass.azsli} (97%) diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/BasePBR_ForwardPass.azsl b/Gems/Atom/Feature/Common/Assets/Materials/Types/BasePBR_ForwardPass.azsl index b887761917..96318f2284 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/BasePBR_ForwardPass.azsl +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/BasePBR_ForwardPass.azsl @@ -116,19 +116,19 @@ PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace) float2 baseColorUv = IN.m_uv[MaterialSrg::m_baseColorMapUvIndex]; float3 sampledColor = GetBaseColorInput(MaterialSrg::m_baseColorMap, MaterialSrg::m_sampler, baseColorUv, MaterialSrg::m_baseColor.rgb, o_baseColor_useTexture); - surface.baseColor = BlendBaseColor(sampledColor, MaterialSrg::m_baseColor.rgb, MaterialSrg::m_baseColorFactor, o_baseColorTextureBlendMode, o_baseColor_useTexture); + float3 baseColor = BlendBaseColor(sampledColor, MaterialSrg::m_baseColor.rgb, MaterialSrg::m_baseColorFactor, o_baseColorTextureBlendMode, o_baseColor_useTexture); // ------- Metallic ------- float2 metallicUv = IN.m_uv[MaterialSrg::m_metallicMapUvIndex]; - surface.metallic = GetMetallicInput(MaterialSrg::m_metallicMap, MaterialSrg::m_sampler, metallicUv, MaterialSrg::m_metallicFactor, o_metallic_useTexture); + 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(specularF0Factor); + surface.SetAlbedoAndSpecularF0(baseColor, specularF0Factor, metallic); // ------- Roughness ------- diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/DepthPass_WithPS.azsl b/Gems/Atom/Feature/Common/Assets/Materials/Types/DepthPass_WithPS.azsli similarity index 95% rename from Gems/Atom/Feature/Common/Assets/Materials/Types/DepthPass_WithPS.azsl rename to Gems/Atom/Feature/Common/Assets/Materials/Types/DepthPass_WithPS.azsli index 59d10f28f9..70d7b65b9b 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/DepthPass_WithPS.azsl +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/DepthPass_WithPS.azsli @@ -6,6 +6,10 @@ * */ +#ifdef SHADOWS +#include +#endif + struct VSInput { float3 m_position : POSITION; @@ -113,9 +117,12 @@ PSDepthOutput MainPS(VSDepthOutput IN, bool isFrontFace : SV_IsFrontFace) SetPixelDepth(IN.m_worldPosition, IN.m_normal, tangents, bitangents, IN.m_uv, isFrontFace, OUT.m_depth); #endif +#ifdef SHADOW + OUT.m_depth += PdoShadowMapBias; +#endif } -#ifndef MULTILAYER +#ifndef DEACTIVATE_ALPHA_CLIP GetAlphaAndClip(IN.m_uv); #endif diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_DepthPass_WithPS.azsl b/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_DepthPass_WithPS.azsl index f5ca5c5cde..a13d04bdb8 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_DepthPass_WithPS.azsl +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_DepthPass_WithPS.azsl @@ -16,4 +16,4 @@ #include "MaterialFunctions/ParallaxDepth.azsli" #include "MaterialFunctions/StandardGetAlphaAndClip.azsli" -#include "DepthPass_WithPS.azsl" +#include "DepthPass_WithPS.azsli" diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_ForwardPass.azsl b/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_ForwardPass.azsl index 3e2bd4588b..640ada8bd8 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_ForwardPass.azsl +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_ForwardPass.azsl @@ -43,4 +43,4 @@ COMMON_OPTIONS_DETAIL_MAPS() #include "MaterialFunctions/StandardTransformDetailUvs.azsli" #include "MaterialFunctions/StandardTransformUvs.azsli" -#include "EnhancedSurface_ForwardPass.azsl" \ No newline at end of file +#include "EnhancedSurface_ForwardPass.azsli" diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_Shadowmap_WithPS.azsl b/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_Shadowmap_WithPS.azsl index 3a9f8a7417..e5a7b3eb3c 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_Shadowmap_WithPS.azsl +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_Shadowmap_WithPS.azsl @@ -23,4 +23,5 @@ #include "MaterialFunctions/ParallaxDepth.azsli" #include "MaterialFunctions/StandardGetAlphaAndClip.azsli" -#include "ShadowMap_WithPS.azsl" \ No newline at end of file +#define SHADOWS +#include "DepthPass_WithPS.azsli" diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedSurface_ForwardPass.azsl b/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedSurface_ForwardPass.azsli similarity index 96% rename from Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedSurface_ForwardPass.azsl rename to Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedSurface_ForwardPass.azsli index ef08b928cf..8bc9802443 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedSurface_ForwardPass.azsl +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedSurface_ForwardPass.azsli @@ -146,6 +146,7 @@ PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace, out float } } + SurfaceSettings surfaceSettings; Surface surface; surface.vertexNormal = vertexNormal; surface.position = IN.m_worldPosition; @@ -154,7 +155,7 @@ PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace, out float // 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); + EvaluateEnhancedSurface(IN.m_normal, IN.m_uv, IN.m_detailUv, tangents, bitangents, isFrontFace, displacementIsClipped, surface, surfaceSettings); // ------- Lighting Data ------- @@ -222,7 +223,7 @@ PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace, out float // 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, surface.opacityAffectsSpecularFactor); + alpha = lerp(fresnelAlpha, alpha, surfaceSettings.opacityAffectsSpecularFactor); } // Note: lightingOutput rendertargets are not always used as named, particularly m_diffuseColor (target 0) and @@ -241,7 +242,7 @@ PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace, out float // 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, surface.opacityAffectsSpecularFactor); + specular = lerp(specular, specular * lightingOutput.m_diffuseColor.w, surfaceSettings.opacityAffectsSpecularFactor); lightingOutput.m_diffuseColor.rgb += specular; lightingOutput.m_diffuseColor.w = alpha; @@ -264,7 +265,7 @@ PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace, out float // 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, surface.opacityAffectsSpecularFactor); + specular = lerp(specular, specular * lightingOutput.m_diffuseColor.w, surfaceSettings.opacityAffectsSpecularFactor); lightingOutput.m_diffuseColor.rgb += specular; lightingOutput.m_specularColor.rgb = surface.baseColor * (1.0 - alpha); @@ -272,9 +273,9 @@ PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace, out float 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), surface.subsurfaceScatteringQuality) * 255), float2(256, 1)); + 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 = surface.scatterDistance; + lightingOutput.m_scatterDistance = surfaceSettings.scatterDistance; } return lightingOutput; diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/EnhancedParallaxDepth.azsli b/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/EnhancedParallaxDepth.azsli index e89afd6f37..449c789d01 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/EnhancedParallaxDepth.azsli +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/EnhancedParallaxDepth.azsli @@ -38,4 +38,4 @@ // Apply second part of the offset to the detail UV (see comment above) detailUv[MaterialSrg::m_parallaxUvIndex] -= uvs[MaterialSrg::m_parallaxUvIndex]; -} \ No newline at end of file +} diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/EvaluateEnhancedSurface.azsli b/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/EvaluateEnhancedSurface.azsli index c8d59363ea..d1cb52004a 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/EvaluateEnhancedSurface.azsli +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/EvaluateEnhancedSurface.azsli @@ -17,7 +17,8 @@ void EvaluateEnhancedSurface( float3 bitangents[UvSetCount], bool isFrontFace, bool displacementIsClipped, - inout Surface surface) + inout Surface surface, + out SurfaceSettings surfaceSettings) { // ------- Detail Layer Setup ------- @@ -52,22 +53,22 @@ void EvaluateEnhancedSurface( float detailLayerBaseColorFactor = MaterialSrg::m_detail_baseColor_factor * detailLayerBlendFactor; float2 baseColorUv = uvs[MaterialSrg::m_baseColorMapUvIndex]; - surface.baseColor = GetDetailedBaseColorInput( + 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(surface.baseColor); + ApplyParallaxClippingHighlight(baseColor); } // ------- Metallic ------- - surface.metallic = 0; + 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]; - surface.metallic = GetMetallicInput(MaterialSrg::m_metallicMap, MaterialSrg::m_sampler, metallicUv, MaterialSrg::m_metallicFactor, o_metallic_useTexture); + metallic = GetMetallicInput(MaterialSrg::m_metallicMap, MaterialSrg::m_sampler, metallicUv, MaterialSrg::m_metallicFactor, o_metallic_useTexture); } // ------- Specular ------- @@ -75,7 +76,7 @@ void EvaluateEnhancedSurface( float2 specularUv = uvs[MaterialSrg::m_specularF0MapUvIndex]; float specularF0Factor = GetSpecularInput(MaterialSrg::m_specularF0Map, MaterialSrg::m_sampler, specularUv, MaterialSrg::m_specularF0Factor, o_specularF0_useTexture); - surface.SetAlbedoAndSpecularF0(specularF0Factor); + surface.SetAlbedoAndSpecularF0(baseColor, specularF0Factor, metallic); // ------- Roughness ------- @@ -88,8 +89,8 @@ void EvaluateEnhancedSurface( float2 subsurfaceUv = uvs[MaterialSrg::m_subsurfaceScatteringInfluenceMapUvIndex]; surface.subsurfaceScatteringFactor = GetSubsurfaceInput(MaterialSrg::m_subsurfaceScatteringInfluenceMap, MaterialSrg::m_sampler, subsurfaceUv, MaterialSrg::m_subsurfaceScatteringFactor); - surface.subsurfaceScatteringQuality = MaterialSrg::m_subsurfaceScatteringQuality; - surface.scatterDistance = MaterialSrg::m_scatterDistance; + surfaceSettings.subsurfaceScatteringQuality = MaterialSrg::m_subsurfaceScatteringQuality; + surfaceSettings.scatterDistance = MaterialSrg::m_scatterDistance; // ------- Transmission ------- @@ -144,5 +145,5 @@ void EvaluateEnhancedSurface( surface.specularF0 = lerp(surface.specularF0, f0 * f0, surface.clearCoat.factor); } - surface.opacityAffectsSpecularFactor = MaterialSrg::m_opacityAffectsSpecularFactor; + surfaceSettings.opacityAffectsSpecularFactor = MaterialSrg::m_opacityAffectsSpecularFactor; } diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/EvaluateStandardSurface.azsli b/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/EvaluateStandardSurface.azsli index c58f0534b3..da24bb3937 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/EvaluateStandardSurface.azsli +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/EvaluateStandardSurface.azsli @@ -16,7 +16,8 @@ void EvaluateStandardSurface( float3 bitangents[UvSetCount], bool isFrontFace, bool displacementIsClipped, - inout Surface surface) + inout Surface surface, + out SurfaceSettings surfaceSettings) { // ------- Normal ------- @@ -29,24 +30,24 @@ void EvaluateStandardSurface( float2 baseColorUv = uv[MaterialSrg::m_baseColorMapUvIndex]; float3 sampledColor = GetBaseColorInput(MaterialSrg::m_baseColorMap, MaterialSrg::m_sampler, baseColorUv, MaterialSrg::m_baseColor.rgb, o_baseColor_useTexture); - surface.baseColor = BlendBaseColor(sampledColor, MaterialSrg::m_baseColor.rgb, MaterialSrg::m_baseColorFactor, o_baseColorTextureBlendMode, 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(surface.baseColor); + ApplyParallaxClippingHighlight(baseColor); } // ------- Metallic ------- float2 metallicUv = uv[MaterialSrg::m_metallicMapUvIndex]; - surface.metallic = GetMetallicInput(MaterialSrg::m_metallicMap, MaterialSrg::m_sampler, metallicUv, MaterialSrg::m_metallicFactor, o_metallic_useTexture); + 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(specularF0Factor); + surface.SetAlbedoAndSpecularF0(baseColor, specularF0Factor, metallic); // ------- Roughness ------- @@ -90,5 +91,5 @@ void EvaluateStandardSurface( } // ------- Opacity ------- - surface.opacityAffectsSpecularFactor = MaterialSrg::m_opacityAffectsSpecularFactor; -} \ No newline at end of file + surfaceSettings.opacityAffectsSpecularFactor = MaterialSrg::m_opacityAffectsSpecularFactor; +} diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/EvaluateTangentFrame.azsli b/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/EvaluateTangentFrame.azsli index 643a2d29ca..0045d18e47 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/EvaluateTangentFrame.azsli +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/EvaluateTangentFrame.azsli @@ -27,7 +27,7 @@ void EvaluateTangentFrame( } else { - SurfaceGradientNormalMapping_Init(normal, worldPosition, !isFrontFace); \ - SurfaceGradientNormalMapping_GenerateTB(uv, OUT_tangent, OUT_bitangent); \ + SurfaceGradientNormalMapping_Init(normal, worldPosition, !isFrontFace); + SurfaceGradientNormalMapping_GenerateTB(uv, OUT_tangent, OUT_bitangent); } -} \ No newline at end of file +} diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/MultilayerParallaxDepth.azsli b/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/MultilayerParallaxDepth.azsli index 5ee527b9a4..3f8b87c942 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/MultilayerParallaxDepth.azsli +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/MultilayerParallaxDepth.azsli @@ -11,7 +11,7 @@ #include void MultilayerSetPixelDepth( - float blendMask, + float3 blendMask, float3 worldPosition, float3 normal, float3 tangents[UvSetCount], @@ -21,7 +21,7 @@ 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(); @@ -32,4 +32,4 @@ parallaxOverallFactor, parallaxOverallOffset, ObjectSrg::GetWorldMatrix(), uvMatrix, uvMatrixInverse, uvs[MaterialSrg::m_parallaxUvIndex], worldPosition, depth); -} \ No newline at end of file +} diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/ParallaxDepth.azsli b/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/ParallaxDepth.azsli index 0298656375..645e3a4c2d 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/ParallaxDepth.azsli +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/ParallaxDepth.azsli @@ -48,4 +48,4 @@ MaterialSrg::m_heightmapScale, MaterialSrg::m_heightmapOffset, ObjectSrg::GetWorldMatrix(), uvMatrix, uvMatrixInverse, uvs[MaterialSrg::m_parallaxUvIndex], worldPosition, depthNDC, depthCS, isClipped); -} \ No newline at end of file +} diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/StandardGetAlphaAndClip.azsli b/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/StandardGetAlphaAndClip.azsli index 81380e8c07..c588b4423b 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/StandardGetAlphaAndClip.azsli +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/StandardGetAlphaAndClip.azsli @@ -16,4 +16,4 @@ float GetAlphaAndClip(float2 uvs[UvSetCount]) 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; -} \ No newline at end of file +} diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/StandardGetNormalToWorld.azsli b/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/StandardGetNormalToWorld.azsli index b79b5b3418..f55f1b2078 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/StandardGetNormalToWorld.azsli +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/StandardGetNormalToWorld.azsli @@ -9,4 +9,4 @@ float3x3 GetNormalToWorld() { return ObjectSrg::GetWorldMatrixInverseTranspose(); -} \ No newline at end of file +} diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/StandardGetObjectToWorld.azsli b/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/StandardGetObjectToWorld.azsli index 435326215b..f02677658d 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/StandardGetObjectToWorld.azsli +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/StandardGetObjectToWorld.azsli @@ -9,4 +9,4 @@ float4x4 GetObjectToWorld() { return ObjectSrg::GetWorldMatrix(); -} \ No newline at end of file +} diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/StandardTransformDetailUvs.azsli b/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/StandardTransformDetailUvs.azsli index 60ab575a2d..e6bb26ddc5 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/StandardTransformDetailUvs.azsli +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/StandardTransformDetailUvs.azsli @@ -15,4 +15,4 @@ void TransformDetailUvs(in float2 IN[UvSetCount], out float2 OUT[UvSetCount]) // 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; -} \ No newline at end of file +} diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/StandardTransformUvs.azsli b/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/StandardTransformUvs.azsli index dd77f99ac3..39d77c7a93 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/StandardTransformUvs.azsli +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialFunctions/StandardTransformUvs.azsli @@ -11,4 +11,4 @@ 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]; -} \ No newline at end of file +} diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/ShadowMap_WithPS.azsl b/Gems/Atom/Feature/Common/Assets/Materials/Types/ShadowMap_WithPS.azsl deleted file mode 100644 index 3dc2cf5d77..0000000000 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/ShadowMap_WithPS.azsl +++ /dev/null @@ -1,126 +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 -#include - -struct VertexInput -{ - 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; - -#ifdef 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 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; - -#ifdef MULTILAYER - float3 m_blendMask : UV3; -#endif -}; - -VertexOutput MainVS(VertexInput IN) -{ - const float4x4 objectToWorld = GetObjectToWorld(); - 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)); - - float2 uv[UvSetCount] = { IN.m_uv0, IN.m_uv1 }; - TransformUvs(uv, 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); - } - -#ifdef 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 -{ - 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, 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]); - } - -#ifdef 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 - - OUT.m_depth += PdoShadowMapBias; - } - -#ifndef MULTILAYER - GetAlphaAndClip(IN.m_uv); -#endif - - return OUT; -} diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_DepthPass_WithPS.azsl b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_DepthPass_WithPS.azsl index d1a488ec0a..a185aae106 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_DepthPass_WithPS.azsl +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_DepthPass_WithPS.azsl @@ -28,4 +28,5 @@ COMMON_OPTIONS_PARALLAX(o_layer3_) #include "MaterialFunctions/MultilayerParallaxDepth.azsli" #define MULTILAYER -#include "DepthPass_WithPS.azsl" +#define DEACTIVATE_ALPHA_CLIP +#include "DepthPass_WithPS.azsli" diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_ForwardPass.azsl b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_ForwardPass.azsl index 2fe9990862..48d3cb4739 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_ForwardPass.azsl +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_ForwardPass.azsl @@ -450,16 +450,16 @@ PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace, out float // ------- Combine Albedo, roughness, specular, roughness --------- - surface.baseColor = BlendLayers(lightingInputLayer1.m_baseColor, lightingInputLayer2.m_baseColor, lightingInputLayer3.m_baseColor, blendWeights); + float3 baseColor = BlendLayers(lightingInputLayer1.m_baseColor, lightingInputLayer2.m_baseColor, lightingInputLayer3.m_baseColor, blendWeights); float specularF0Factor = BlendLayers(lightingInputLayer1.m_specularF0Factor, lightingInputLayer2.m_specularF0Factor, lightingInputLayer3.m_specularF0Factor, blendWeights); - surface.metallic = BlendLayers(lightingInputLayer1.m_metallic, lightingInputLayer2.m_metallic, lightingInputLayer3.m_metallic, blendWeights); + float metallic = BlendLayers(lightingInputLayer1.m_metallic, lightingInputLayer2.m_metallic, lightingInputLayer3.m_metallic, blendWeights); if(o_parallax_highlightClipping && displacementIsClipped) { - ApplyParallaxClippingHighlight(surface.baseColor); + ApplyParallaxClippingHighlight(baseColor); } - surface.SetAlbedoAndSpecularF0(specularF0Factor); + surface.SetAlbedoAndSpecularF0(baseColor, specularF0Factor, metallic); surface.roughnessLinear = BlendLayers(lightingInputLayer1.m_roughness, lightingInputLayer2.m_roughness, lightingInputLayer3.m_roughness, blendWeights); surface.CalculateRoughnessA(); diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_Shadowmap_WithPS.azsl b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_Shadowmap_WithPS.azsl index 84b44512f0..331fa76b16 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_Shadowmap_WithPS.azsl +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_Shadowmap_WithPS.azsl @@ -28,4 +28,5 @@ COMMON_OPTIONS_PARALLAX(o_layer3_) #include "MaterialFunctions/MultilayerParallaxDepth.azsli" #define MULTILAYER -#include "ShadowMap_WithPS.azsl" \ No newline at end of file +#define DEACTIVATE_ALPHA_CLIP +#include "DepthPass_WithPS.azsli" diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_DepthPass_WithPS.azsl b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_DepthPass_WithPS.azsl index 62dcddf2a6..cf0b7b7311 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_DepthPass_WithPS.azsl +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_DepthPass_WithPS.azsl @@ -16,4 +16,4 @@ #include "MaterialFunctions/StandardTransformUvs.azsli" #include "MaterialFunctions/ParallaxDepth.azsli" -#include "DepthPass_WithPS.azsl" +#include "DepthPass_WithPS.azsli" diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_ForwardPass.azsl b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_ForwardPass.azsl index 5dc1148370..4240c291da 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_ForwardPass.azsl +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_ForwardPass.azsl @@ -35,4 +35,4 @@ COMMON_OPTIONS_EMISSIVE() #include "MaterialFunctions/StandardGetAlphaAndClip.azsli" #include "MaterialFunctions/StandardTransformUvs.azsli" -#include "StandardSurface_ForwardPass.azsl" \ No newline at end of file +#include "StandardSurface_ForwardPass.azsli" diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_LowEndForward.azsl b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_LowEndForward.azsl deleted file mode 100644 index 02e9e93ba2..0000000000 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_LowEndForward.azsl +++ /dev/null @@ -1,13 +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 - * - */ - -// 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" diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_LowEndForward.shader b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_LowEndForward.shader index 44139608ca..511749c6ae 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_LowEndForward.shader +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_LowEndForward.shader @@ -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" : { diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_LowEndForward_EDS.shader b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_LowEndForward_EDS.shader index 9faa1d3698..6dc4004b07 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_LowEndForward_EDS.shader +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_LowEndForward_EDS.shader @@ -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" : { diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_Shadowmap_WithPS.azsl b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_Shadowmap_WithPS.azsl index 7acc121036..665c85c02e 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_Shadowmap_WithPS.azsl +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_Shadowmap_WithPS.azsl @@ -20,4 +20,5 @@ #include "MaterialFunctions/EvaluateTangentFrame.azsli" #include "MaterialFunctions/ParallaxDepth.azsli" -#include "ShadowMap_WithPS.azsl" \ No newline at end of file +#define SHADOWS +#include "DepthPass_WithPS.azsli" diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardSurface_ForwardPass.azsl b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardSurface_ForwardPass.azsli similarity index 97% rename from Gems/Atom/Feature/Common/Assets/Materials/Types/StandardSurface_ForwardPass.azsl rename to Gems/Atom/Feature/Common/Assets/Materials/Types/StandardSurface_ForwardPass.azsli index b60f789cf5..5f89fc704b 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardSurface_ForwardPass.azsl +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardSurface_ForwardPass.azsli @@ -143,6 +143,7 @@ PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace, out float } } + SurfaceSettings surfaceSettings; Surface surface; surface.vertexNormal = vertexNormal; surface.position = IN.m_worldPosition.xyz; @@ -151,7 +152,7 @@ PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace, out float // 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); + EvaluateStandardSurface(IN.m_normal, IN.m_uv, tangents, bitangents, isFrontFace, displacementIsClipped, surface, surfaceSettings); // ------- Lighting Data ------- @@ -209,7 +210,7 @@ PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace, out float // 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, surface.opacityAffectsSpecularFactor); + alpha = lerp(fresnelAlpha, alpha, surfaceSettings.opacityAffectsSpecularFactor); } if (o_opacity_mode == OpacityMode::Blended) @@ -225,7 +226,7 @@ PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace, out float // 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, surface.opacityAffectsSpecularFactor); + specular = lerp(specular, specular * lightingOutput.m_diffuseColor.w, surfaceSettings.opacityAffectsSpecularFactor); lightingOutput.m_diffuseColor.rgb += specular; lightingOutput.m_diffuseColor.w = alpha; @@ -248,7 +249,7 @@ PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace, out float // 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, surface.opacityAffectsSpecularFactor); + specular = lerp(specular, specular * lightingOutput.m_diffuseColor.w, surfaceSettings.opacityAffectsSpecularFactor); lightingOutput.m_diffuseColor.rgb += specular; lightingOutput.m_specularColor.rgb = surface.baseColor * (1.0 - alpha); diff --git a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Surfaces/EnhancedSurface.azsli b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Surfaces/EnhancedSurface.azsli index 21f55ea259..c12cd2834b 100644 --- a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Surfaces/EnhancedSurface.azsli +++ b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Surfaces/EnhancedSurface.azsli @@ -13,6 +13,21 @@ #include #include +// 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; @@ -29,18 +44,7 @@ class Surface 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 - - //! Subsurface scattering parameters float subsurfaceScatteringFactor; - 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; //! Surface lighting inputs float3 albedo; //!< Albedo color of the non-metallic material, will be multiplied against the diffuse lighting value @@ -56,7 +60,7 @@ class Surface void CalculateRoughnessA(); //! Sets albedo and specularF0 using metallic workflow - void SetAlbedoAndSpecularF0(float specularF0Factor); + void SetAlbedoAndSpecularF0(float3 baseColor, float specularF0Factor, float metallic); }; @@ -95,12 +99,13 @@ void Surface::CalculateRoughnessA() } } -void Surface::SetAlbedoAndSpecularF0(float specularF0Factor) +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); } - diff --git a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Surfaces/StandardSurface.azsli b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Surfaces/StandardSurface.azsli index 9d4abeb0f6..9a8d5cfd54 100644 --- a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Surfaces/StandardSurface.azsli +++ b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Surfaces/StandardSurface.azsli @@ -8,11 +8,21 @@ #pragma once -#include #include #include #include +// 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 { @@ -51,8 +61,8 @@ class Surface //! Calculates roughnessA and roughnessA2 after roughness has been set void CalculateRoughnessA(); - //! Sets albedo and specularF0 using metallic workflow - void SetAlbedoAndSpecularF0(float specularF0Factor); + //! Sets albedo, base color, specularF0, and metallic properties using metallic workflow + void SetAlbedoAndSpecularF0(float3 baseColor, float specularF0Factor, float metallic); }; // Specular Anti-Aliasing technique from this paper: @@ -89,12 +99,14 @@ void Surface::CalculateRoughnessA() } } -void Surface::SetAlbedoAndSpecularF0(float specularF0Factor) +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); } - diff --git a/Gems/Atom/Feature/Common/Assets/atom_feature_common_asset_files.cmake b/Gems/Atom/Feature/Common/Assets/atom_feature_common_asset_files.cmake index e6ad71bde3..00d437acd1 100644 --- a/Gems/Atom/Feature/Common/Assets/atom_feature_common_asset_files.cmake +++ b/Gems/Atom/Feature/Common/Assets/atom_feature_common_asset_files.cmake @@ -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 diff --git a/Gems/Atom/TestData/TestData/Materials/Types/AutoBrick_ForwardPass.azsl b/Gems/Atom/TestData/TestData/Materials/Types/AutoBrick_ForwardPass.azsl index 3c4388350e..13f1e33ef7 100644 --- a/Gems/Atom/TestData/TestData/Materials/Types/AutoBrick_ForwardPass.azsl +++ b/Gems/Atom/TestData/TestData/Materials/Types/AutoBrick_ForwardPass.azsl @@ -144,20 +144,18 @@ ForwardPassOutput AutoBrick_ForwardPassPS(VSOutput IN) identityUvMatrix); IN.m_uv += tangentOffset.m_offsetTS.xy; - - Surface surface; - surface.baseColor = float3(1,1,1); + float3 baseColor = float3(1,1,1); const float noise = AutoBrickSrg::m_noise.Sample(AutoBrickSrg::m_sampler, IN.m_uv).r; float distanceFromBrick = GetNormalizedDistanceFromBrick(IN.m_uv); if(distanceFromBrick > AutoBrickSrg::m_brickColorBleed) { - surface.baseColor = AutoBrickSrg::m_lineColor * lerp(1.0, noise, AutoBrickSrg::m_lineNoiseFactor); + baseColor = AutoBrickSrg::m_lineColor * lerp(1.0, noise, AutoBrickSrg::m_lineNoiseFactor); } else { - surface.baseColor = AutoBrickSrg::m_brickColor * lerp(1.0, noise, AutoBrickSrg::m_brickNoiseFactor); + baseColor = AutoBrickSrg::m_brickColor * lerp(1.0, noise, AutoBrickSrg::m_brickNoiseFactor); } float surfaceDepth; @@ -166,6 +164,8 @@ ForwardPassOutput AutoBrick_ForwardPassPS(VSOutput IN) const float3 normal = TangentSpaceToWorld(surfaceNormal, normalize(IN.m_normal), normalize(IN.m_tangent), normalize(IN.m_bitangent)); // ------- Surface ------- + + Surface surface; // Position, Normal, Roughness surface.position = IN.m_worldPosition.xyz; @@ -175,9 +175,9 @@ ForwardPassOutput AutoBrick_ForwardPassPS(VSOutput IN) surface.CalculateRoughnessA(); // Albedo, SpecularF0 - surface.metallic = 0.0f; - float specularF0Factor = 0.5f; - surface.SetAlbedoAndSpecularF0(specularF0Factor); + const float metallic = 0.0f; + const float specularF0Factor = 0.5f; + surface.SetAlbedoAndSpecularF0(baseColor, specularF0Factor, metallic); // Clear Coat surface.clearCoat.InitializeToZero(); diff --git a/Gems/Atom/TestData/TestData/Materials/Types/MinimalPBR_ForwardPass.azsl b/Gems/Atom/TestData/TestData/Materials/Types/MinimalPBR_ForwardPass.azsl index 503c0107db..71c1621f7f 100644 --- a/Gems/Atom/TestData/TestData/Materials/Types/MinimalPBR_ForwardPass.azsl +++ b/Gems/Atom/TestData/TestData/Materials/Types/MinimalPBR_ForwardPass.azsl @@ -68,10 +68,10 @@ ForwardPassOutput MinimalPBR_MainPassPS(VSOutput IN) surface.CalculateRoughnessA(); // Albedo, SpecularF0 - surface.baseColor = MinimalPBRSrg::m_baseColor; - surface.metallic = MinimalPBRSrg::m_metallic; + float3 baseColor = MinimalPBRSrg::m_baseColor; + float metallic = MinimalPBRSrg::m_metallic; float specularF0Factor = 0.5f; - surface.SetAlbedoAndSpecularF0(specularF0Factor); + surface.SetAlbedoAndSpecularF0(baseColor, specularF0Factor, metallic); // Clear Coat surface.clearCoat.InitializeToZero(); From 9ed9bcbb2f3324949bf774f56cb37c7ed91159cf Mon Sep 17 00:00:00 2001 From: Jeremy Ong Date: Sat, 5 Feb 2022 12:31:03 -0700 Subject: [PATCH 19/29] Fix incorrect include header casing Signed-off-by: Jeremy Ong --- .../Materials/Types/StandardMultilayerPBR_DepthPass_WithPS.azsl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_DepthPass_WithPS.azsl b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_DepthPass_WithPS.azsl index a185aae106..7edea547ac 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_DepthPass_WithPS.azsl +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_DepthPass_WithPS.azsl @@ -24,7 +24,7 @@ COMMON_OPTIONS_PARALLAX(o_layer3_) #include "MaterialFunctions/StandardGetObjectToWorld.azsli" #include "MaterialFunctions/StandardGetNormalToWorld.azsli" #include "MaterialFunctions/EvaluateTangentFrame.azsli" -#include "MaterialFunctions/StandardTransformUVs.azsli" +#include "MaterialFunctions/StandardTransformUvs.azsli" #include "MaterialFunctions/MultilayerParallaxDepth.azsli" #define MULTILAYER From 86d8ececfbf1e1b197acc4c2157f59b5b31f4b02 Mon Sep 17 00:00:00 2001 From: Jeremy Ong Date: Mon, 7 Feb 2022 10:54:31 -0700 Subject: [PATCH 20/29] Resolve incorrect SHADOWS definitions Signed-off-by: Jeremy Ong --- .../Common/Assets/Materials/Types/DepthPass_WithPS.azsli | 2 +- .../Materials/Types/StandardMultilayerPBR_Shadowmap_WithPS.azsl | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/DepthPass_WithPS.azsli b/Gems/Atom/Feature/Common/Assets/Materials/Types/DepthPass_WithPS.azsli index 70d7b65b9b..3dd3792102 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/DepthPass_WithPS.azsli +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/DepthPass_WithPS.azsli @@ -117,7 +117,7 @@ PSDepthOutput MainPS(VSDepthOutput IN, bool isFrontFace : SV_IsFrontFace) SetPixelDepth(IN.m_worldPosition, IN.m_normal, tangents, bitangents, IN.m_uv, isFrontFace, OUT.m_depth); #endif -#ifdef SHADOW +#ifdef SHADOWS OUT.m_depth += PdoShadowMapBias; #endif } diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_Shadowmap_WithPS.azsl b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_Shadowmap_WithPS.azsl index 331fa76b16..8d7314640b 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_Shadowmap_WithPS.azsl +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_Shadowmap_WithPS.azsl @@ -29,4 +29,5 @@ COMMON_OPTIONS_PARALLAX(o_layer3_) #define MULTILAYER #define DEACTIVATE_ALPHA_CLIP +#define SHADOWS #include "DepthPass_WithPS.azsli" From faa65bca1bee49a6ae607d209d391788e668540a Mon Sep 17 00:00:00 2001 From: Jeremy Ong Date: Tue, 8 Feb 2022 11:51:16 -0700 Subject: [PATCH 21/29] Refactor shader definitions to be more consistent with other shaders Signed-off-by: Jeremy Ong --- .../Materials/Types/DepthPass_WithPS.azsli | 24 ++++++++++++++----- .../Types/EnhancedPBR_DepthPass_WithPS.azsl | 1 + .../Types/EnhancedPBR_Shadowmap_WithPS.azsl | 3 ++- ...tandardMultilayerPBR_DepthPass_WithPS.azsl | 4 ++-- ...tandardMultilayerPBR_Shadowmap_WithPS.azsl | 6 ++--- .../Types/StandardPBR_DepthPass_WithPS.azsl | 1 + .../Types/StandardPBR_Shadowmap_WithPS.azsl | 3 ++- .../PBR/Surfaces/EnhancedSurface.azsli | 2 +- 8 files changed, 30 insertions(+), 14 deletions(-) diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/DepthPass_WithPS.azsli b/Gems/Atom/Feature/Common/Assets/Materials/Types/DepthPass_WithPS.azsli index 3dd3792102..a1318bf383 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/DepthPass_WithPS.azsli +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/DepthPass_WithPS.azsli @@ -10,6 +10,18 @@ #include #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; @@ -21,7 +33,7 @@ struct VSInput float4 m_tangent : TANGENT; float3 m_bitangent : BITANGENT; -#ifdef MULTILAYER +#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). @@ -42,7 +54,7 @@ struct VSDepthOutput float3 m_bitangent : BITANGENT; float3 m_worldPosition : UV0; -#ifdef MULTILAYER +#if MULTILAYER float3 m_blendMask : UV3; #endif }; @@ -67,7 +79,7 @@ VSDepthOutput MainVS(VSInput IN) ConstructTBN(IN.m_normal, IN.m_tangent, IN.m_bitangent, objectToWorld, objectToWorldIT, OUT.m_normal, OUT.m_tangent, OUT.m_bitangent); } -#ifdef MULTILAYER +#if MULTILAYER if(o_blendMask_isBound) { OUT.m_blendMask = IN.m_optional_blendMask.rgb; @@ -111,18 +123,18 @@ PSDepthOutput MainPS(VSDepthOutput IN, bool isFrontFace : SV_IsFrontFace) bitangents[i]); } -#ifdef MULTILAYER +#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 -#ifdef SHADOWS +#if SHADOWS OUT.m_depth += PdoShadowMapBias; #endif } -#ifndef DEACTIVATE_ALPHA_CLIP +#if ENABLE_ALPHA_CLIP GetAlphaAndClip(IN.m_uv); #endif diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_DepthPass_WithPS.azsl b/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_DepthPass_WithPS.azsl index a13d04bdb8..eb220faec6 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_DepthPass_WithPS.azsl +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_DepthPass_WithPS.azsl @@ -16,4 +16,5 @@ #include "MaterialFunctions/ParallaxDepth.azsli" #include "MaterialFunctions/StandardGetAlphaAndClip.azsli" +#define ENABLE_ALPHA_CLIP 1 #include "DepthPass_WithPS.azsli" diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_Shadowmap_WithPS.azsl b/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_Shadowmap_WithPS.azsl index e5a7b3eb3c..be350a3ce7 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_Shadowmap_WithPS.azsl +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_Shadowmap_WithPS.azsl @@ -23,5 +23,6 @@ #include "MaterialFunctions/ParallaxDepth.azsli" #include "MaterialFunctions/StandardGetAlphaAndClip.azsli" -#define SHADOWS +#define SHADOWS 1 +#define ENABLE_ALPHA_CLIP 1 #include "DepthPass_WithPS.azsli" diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_DepthPass_WithPS.azsl b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_DepthPass_WithPS.azsl index 7edea547ac..0f000fba98 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_DepthPass_WithPS.azsl +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_DepthPass_WithPS.azsl @@ -27,6 +27,6 @@ COMMON_OPTIONS_PARALLAX(o_layer3_) #include "MaterialFunctions/StandardTransformUvs.azsli" #include "MaterialFunctions/MultilayerParallaxDepth.azsli" -#define MULTILAYER -#define DEACTIVATE_ALPHA_CLIP +#define MULTILAYER 1 +#define ENABLE_ALPHA_CLIP 0 #include "DepthPass_WithPS.azsli" diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_Shadowmap_WithPS.azsl b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_Shadowmap_WithPS.azsl index 8d7314640b..9d64916444 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_Shadowmap_WithPS.azsl +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_Shadowmap_WithPS.azsl @@ -27,7 +27,7 @@ COMMON_OPTIONS_PARALLAX(o_layer3_) #include "MaterialFunctions/EvaluateTangentFrame.azsli" #include "MaterialFunctions/MultilayerParallaxDepth.azsli" -#define MULTILAYER -#define DEACTIVATE_ALPHA_CLIP -#define SHADOWS +#define MULTILAYER 1 +#define ENABLE_ALPHA_CLIP 0 +#define SHADOWS 1 #include "DepthPass_WithPS.azsli" diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_DepthPass_WithPS.azsl b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_DepthPass_WithPS.azsl index cf0b7b7311..b848ba03ba 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_DepthPass_WithPS.azsl +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_DepthPass_WithPS.azsl @@ -16,4 +16,5 @@ #include "MaterialFunctions/StandardTransformUvs.azsli" #include "MaterialFunctions/ParallaxDepth.azsli" +#define ENABLE_ALPHA_CLIP 1 #include "DepthPass_WithPS.azsli" diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_Shadowmap_WithPS.azsl b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_Shadowmap_WithPS.azsl index 665c85c02e..634d23daa1 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_Shadowmap_WithPS.azsl +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_Shadowmap_WithPS.azsl @@ -20,5 +20,6 @@ #include "MaterialFunctions/EvaluateTangentFrame.azsli" #include "MaterialFunctions/ParallaxDepth.azsli" -#define SHADOWS +#define SHADOWS 1 +#define ENABLE_ALPHA_CLIP 1 #include "DepthPass_WithPS.azsli" diff --git a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Surfaces/EnhancedSurface.azsli b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Surfaces/EnhancedSurface.azsli index c12cd2834b..961fbd49f9 100644 --- a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Surfaces/EnhancedSurface.azsli +++ b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Surfaces/EnhancedSurface.azsli @@ -40,7 +40,7 @@ class Surface float3 normal; //!< Normal in world-space float3 vertexNormal; //!< Vertex normal in world-space float3 baseColor; //!< Surface base color - float3 metallic; //!< Surface metallic property + 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 From 5f914e8e1aa1da1235784b26cb355a7189859b98 Mon Sep 17 00:00:00 2001 From: Jeremy Ong Date: Tue, 8 Feb 2022 12:03:28 -0700 Subject: [PATCH 22/29] Add README to describe upcoming material type changes Signed-off-by: Jeremy Ong --- .../Common/Assets/Materials/Types/README.md | 98 +++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 Gems/Atom/Feature/Common/Assets/Materials/Types/README.md diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/README.md b/Gems/Atom/Feature/Common/Assets/Materials/Types/README.md new file mode 100644 index 0000000000..e176f6d7dc --- /dev/null +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/README.md @@ -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 +``` + +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. From 42c2243eaac0ac8174ffef52808b2f5f08d49352 Mon Sep 17 00:00:00 2001 From: Nicholas Van Sickle Date: Tue, 8 Feb 2022 14:34:04 -0800 Subject: [PATCH 23/29] Fix benchmark non-unity build Signed-off-by: Nicholas Van Sickle --- Code/Framework/AzCore/Tests/DOM/DomPatchBenchmarks.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Code/Framework/AzCore/Tests/DOM/DomPatchBenchmarks.cpp b/Code/Framework/AzCore/Tests/DOM/DomPatchBenchmarks.cpp index de19ee1783..e44ba3ce67 100644 --- a/Code/Framework/AzCore/Tests/DOM/DomPatchBenchmarks.cpp +++ b/Code/Framework/AzCore/Tests/DOM/DomPatchBenchmarks.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include From 90503f2bef683e864ec35b836b757eb3050891ad Mon Sep 17 00:00:00 2001 From: chiyenteng <82238204+chiyenteng@users.noreply.github.com> Date: Tue, 8 Feb 2022 15:25:35 -0800 Subject: [PATCH 24/29] Remove error message from InMemorySpawnableAssetContainer (#7499) * Remove error message from InMemorySpawnableAssetContainer Signed-off-by: chiyenteng <82238204+chiyenteng@users.noreply.github.com> * Fix nits Signed-off-by: chiyenteng <82238204+chiyenteng@users.noreply.github.com> --- .../Gem/PythonTests/Physics/TestSuite_Main_Optimized.py | 8 ++++---- .../Gem/PythonTests/Physics/TestSuite_Periodic.py | 6 +----- .../tests/collider/Collider_AddColliderComponent.py | 5 +++-- .../Prefab/Spawnable/InMemorySpawnableAssetContainer.cpp | 5 +---- 4 files changed, 9 insertions(+), 15 deletions(-) diff --git a/AutomatedTesting/Gem/PythonTests/Physics/TestSuite_Main_Optimized.py b/AutomatedTesting/Gem/PythonTests/Physics/TestSuite_Main_Optimized.py index a40b9065d6..3242735345 100644 --- a/AutomatedTesting/Gem/PythonTests/Physics/TestSuite_Main_Optimized.py +++ b/AutomatedTesting/Gem/PythonTests/Physics/TestSuite_Main_Optimized.py @@ -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 diff --git a/AutomatedTesting/Gem/PythonTests/Physics/TestSuite_Periodic.py b/AutomatedTesting/Gem/PythonTests/Physics/TestSuite_Periodic.py index d75328d8f7..d8fa6a30cd 100755 --- a/AutomatedTesting/Gem/PythonTests/Physics/TestSuite_Periodic.py +++ b/AutomatedTesting/Gem/PythonTests/Physics/TestSuite_Periodic.py @@ -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.") diff --git a/AutomatedTesting/Gem/PythonTests/Physics/tests/collider/Collider_AddColliderComponent.py b/AutomatedTesting/Gem/PythonTests/Physics/tests/collider/Collider_AddColliderComponent.py index 4172e66962..d03cc7fff5 100644 --- a/AutomatedTesting/Gem/PythonTests/Physics/tests/collider/Collider_AddColliderComponent.py +++ b/AutomatedTesting/Gem/PythonTests/Physics/tests/collider/Collider_AddColliderComponent.py @@ -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") diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Spawnable/InMemorySpawnableAssetContainer.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Spawnable/InMemorySpawnableAssetContainer.cpp index 567ccc3619..dcb17d02b4 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Spawnable/InMemorySpawnableAssetContainer.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Spawnable/InMemorySpawnableAssetContainer.cpp @@ -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; } From b2206be14d48622811f8c324ca66f75f0e44fb65 Mon Sep 17 00:00:00 2001 From: moraaar Date: Wed, 9 Feb 2022 13:37:34 +0000 Subject: [PATCH 25/29] Fixed range-loop-analysis warninig reported as error on mac/ios (#7511) Signed-off-by: moraaar --- Gems/Prefab/PrefabBuilder/PrefabGroup/PrefabGroupBehavior.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gems/Prefab/PrefabBuilder/PrefabGroup/PrefabGroupBehavior.cpp b/Gems/Prefab/PrefabBuilder/PrefabGroup/PrefabGroupBehavior.cpp index 751c04952c..aac758b96e 100644 --- a/Gems/Prefab/PrefabBuilder/PrefabGroup/PrefabGroupBehavior.cpp +++ b/Gems/Prefab/PrefabBuilder/PrefabGroup/PrefabGroupBehavior.cpp @@ -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) }); From fee95f3f5eb6c7d62ea97158a74f071f9e80a7de Mon Sep 17 00:00:00 2001 From: jromnoa <80134229+jromnoa@users.noreply.github.com> Date: Wed, 9 Feb 2022 08:10:33 -0800 Subject: [PATCH 26/29] sandboxing the sponza level from the level test because it causes a hardlock 4%-12% of the time (#7513) Signed-off-by: jromnoa <80134229+jromnoa@users.noreply.github.com> --- .../Gem/PythonTests/Atom/TestSuite_Sandbox.py | 7 +- .../Atom/atom_utils/atom_constants.py | 4 +- .../tests/hydra_Atom_LevelLoadTest_Sandbox.py | 71 +++++++++++++++++++ 3 files changed, 80 insertions(+), 2 deletions(-) create mode 100644 AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_Atom_LevelLoadTest_Sandbox.py diff --git a/AutomatedTesting/Gem/PythonTests/Atom/TestSuite_Sandbox.py b/AutomatedTesting/Gem/PythonTests/Atom/TestSuite_Sandbox.py index 5299e55a2b..54e3d8cb41 100644 --- a/AutomatedTesting/Gem/PythonTests/Atom/TestSuite_Sandbox.py +++ b/AutomatedTesting/Gem/PythonTests/Atom/TestSuite_Sandbox.py @@ -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 diff --git a/AutomatedTesting/Gem/PythonTests/Atom/atom_utils/atom_constants.py b/AutomatedTesting/Gem/PythonTests/Atom/atom_utils/atom_constants.py index 6525536f96..778f9bd3fb 100644 --- a/AutomatedTesting/Gem/PythonTests/Atom/atom_utils/atom_constants.py +++ b/AutomatedTesting/Gem/PythonTests/Atom/atom_utils/atom_constants.py @@ -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: """ diff --git a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_Atom_LevelLoadTest_Sandbox.py b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_Atom_LevelLoadTest_Sandbox.py new file mode 100644 index 0000000000..6cee7b2840 --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_Atom_LevelLoadTest_Sandbox.py @@ -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) From 8cd8638316682dcb2738cc8d3c928f7d1a3c8bc0 Mon Sep 17 00:00:00 2001 From: carlitosan <82187351+carlitosan@users.noreply.github.com> Date: Wed, 9 Feb 2022 10:19:39 -0800 Subject: [PATCH 27/29] initial locale safety for translating to Lua on multiple platforms (#7490) * initial locale safety for translating to Lua on multiple platforms Signed-off-by: carlitosan <82187351+carlitosan@users.noreply.github.com> * fix android build error Signed-off-by: carlitosan <82187351+carlitosan@users.noreply.github.com> --- .../Translation/GraphToLuaUtility.cpp | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Translation/GraphToLuaUtility.cpp b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Translation/GraphToLuaUtility.cpp index f0eae04383..08bd3883ca 100644 --- a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Translation/GraphToLuaUtility.cpp +++ b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Translation/GraphToLuaUtility.cpp @@ -8,6 +8,8 @@ #include "GraphToLuaUtility.h" +#include + #include #include #include @@ -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: From f74e980659f12361337f37df722abc47948b7932 Mon Sep 17 00:00:00 2001 From: carlitosan <82187351+carlitosan@users.noreply.github.com> Date: Wed, 9 Feb 2022 10:23:16 -0800 Subject: [PATCH 28/29] fix errors when generic nodes fail to add slots (#7508) Signed-off-by: carlitosan <82187351+carlitosan@users.noreply.github.com> --- .../Code/Include/ScriptCanvas/Core/Node.h | 7 ++--- .../ScriptCanvas/Core/NodeFunctionGeneric.h | 31 ++++++++++++++----- .../Libraries/Math/Vector2Nodes.h | 2 +- .../Libraries/Math/Vector3Nodes.h | 11 ++++++- .../Libraries/Math/Vector4Nodes.h | 2 +- 5 files changed, 38 insertions(+), 15 deletions(-) diff --git a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Core/Node.h b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Core/Node.h index f8b4ca7264..4cafd97237 100644 --- a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Core/Node.h +++ b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Core/Node.h @@ -1098,7 +1098,7 @@ protected: slotConfiguration.SetType(Data::FromAZType>()); 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>>()); - slotConfiguration.SetConnectionType(connectionType); - node.AddSlot(slotConfiguration); + + AZ_VerifyError("ScriptCanvas", node.AddSlot(slotConfiguration).IsValid(), "Node failed to add a required Data Out slot"); } template diff --git a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Core/NodeFunctionGeneric.h b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Core/NodeFunctionGeneric.h index 549c56d07d..102b255cda 100644 --- a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Core/NodeFunctionGeneric.h +++ b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Core/NodeFunctionGeneric.h @@ -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::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::GetAZType()), Datum::eOriginality::Copy))); slotConfiguration.SetConnectionType(connectionType); - AddSlot(slotConfiguration); + AZ_VerifyError("ScriptCanvas", AddSlot(slotConfiguration).IsValid(), "NodeFunctionGenericMultiReturn failed to add a required data slot"); } template @@ -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::arg_sequence{}, AZStd::make_index_sequence::arity>{}); diff --git a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Math/Vector2Nodes.h b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Math/Vector2Nodes.h index 9a389404a2..760610b10e 100644 --- a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Math/Vector2Nodes.h +++ b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Math/Vector2Nodes.h @@ -257,7 +257,7 @@ namespace ScriptCanvas r.SetLength(static_cast(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 diff --git a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Math/Vector3Nodes.h b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Math/Vector3Nodes.h index f5e09ef78f..30e6643d44 100644 --- a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Math/Vector3Nodes.h +++ b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Math/Vector3Nodes.h @@ -343,8 +343,17 @@ namespace ScriptCanvas r.SetLength(static_cast(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 diff --git a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Math/Vector4Nodes.h b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Math/Vector4Nodes.h index 30e1b691bf..39e9718824 100644 --- a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Math/Vector4Nodes.h +++ b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Math/Vector4Nodes.h @@ -228,7 +228,7 @@ namespace ScriptCanvas r.SetLength(static_cast(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, From f5463bd9031e1b9d8453057c19ab96d713c84b38 Mon Sep 17 00:00:00 2001 From: sweeneys Date: Wed, 9 Feb 2022 10:40:11 -0800 Subject: [PATCH 29/29] Prevent unsafe calls to AssetProcessor Signed-off-by: sweeneys --- .../automatedtesting_shared/base.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/AutomatedTesting/Gem/PythonTests/automatedtesting_shared/base.py b/AutomatedTesting/Gem/PythonTests/automatedtesting_shared/base.py index e59282c97b..034930484e 100755 --- a/AutomatedTesting/Gem/PythonTests/automatedtesting_shared/base.py +++ b/AutomatedTesting/Gem/PythonTests/automatedtesting_shared/base.py @@ -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()):