Merge remote-tracking branch 'upstream/main' into gallowj/main/DCCsi
This commit is contained in:
@@ -39,4 +39,19 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS AND PAL_TRAIT_
|
||||
COMPONENT
|
||||
Editor
|
||||
)
|
||||
|
||||
ly_add_pytest(
|
||||
NAME AutomatedTesting::EditorTests_Sandbox
|
||||
TEST_SUITE sandbox
|
||||
TEST_SERIAL
|
||||
PATH ${CMAKE_CURRENT_LIST_DIR}
|
||||
PYTEST_MARKS "SUITE_sandbox"
|
||||
TIMEOUT 1500
|
||||
RUNTIME_DEPENDENCIES
|
||||
Legacy::Editor
|
||||
AZ::AssetProcessor
|
||||
AutomatedTesting.Assets
|
||||
COMPONENT
|
||||
Editor
|
||||
)
|
||||
endif()
|
||||
|
||||
@@ -39,7 +39,7 @@ class TestDocking(object):
|
||||
file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", level)], True, True)
|
||||
|
||||
@pytest.mark.test_case_id("C6376081")
|
||||
@pytest.mark.SUITE_periodic
|
||||
@pytest.mark.SUITE_sandbox
|
||||
def test_Docking_BasicDockedTools(self, request, editor, level, launcher_platform):
|
||||
expected_lines = [
|
||||
"The tools are all docked together in a tabbed widget",
|
||||
|
||||
@@ -39,7 +39,7 @@ class TestMenus(object):
|
||||
file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", level)], True, True)
|
||||
|
||||
@pytest.mark.test_case_id("C16780783", "C2174438")
|
||||
@pytest.mark.SUITE_periodic
|
||||
@pytest.mark.SUITE_sandbox
|
||||
def test_Menus_EditMenuOptions_Work(self, request, editor, level, launcher_platform):
|
||||
expected_lines = [
|
||||
"Undo Action triggered",
|
||||
@@ -113,7 +113,7 @@ class TestMenus(object):
|
||||
)
|
||||
|
||||
@pytest.mark.test_case_id("C16780778")
|
||||
@pytest.mark.SUITE_periodic
|
||||
@pytest.mark.SUITE_sandbox
|
||||
def test_Menus_FileMenuOptions_Work(self, request, editor, level, launcher_platform):
|
||||
expected_lines = [
|
||||
"New Level Action triggered",
|
||||
|
||||
@@ -128,6 +128,10 @@ foreach(external_directory ${LY_EXTERNAL_SUBDIRS})
|
||||
endforeach()
|
||||
|
||||
# The following steps have to be done after all targets are registered:
|
||||
# Defer generation of the StaticModules.inl file which is needed to create the AZ::Module derived class in monolithic
|
||||
# builds until after all the targets are known
|
||||
ly_delayed_generate_static_modules_inl()
|
||||
|
||||
# 1. generate a settings registry .setreg file for all ly_add_project_dependencies() and ly_add_target_dependencies() calls
|
||||
# to provide applications with the filenames of gem modules to load
|
||||
# This must be done before ly_delayed_target_link_libraries() as that inserts BUILD_DEPENDENCIES as MANUALLY_ADDED_DEPENDENCIES
|
||||
|
||||
@@ -244,7 +244,7 @@ public class LumberyardActivity extends NativeActivity
|
||||
boolean useMainObb = GetBooleanResource("use_main_obb");
|
||||
boolean usePatchObb = GetBooleanResource("use_patch_obb");
|
||||
|
||||
if (IsBootstrapInAPK() && (useMainObb || usePatchObb))
|
||||
if (AreAssetsInAPK() && (useMainObb || usePatchObb))
|
||||
{
|
||||
Log.d(TAG, "Using OBB expansion files for game assets");
|
||||
|
||||
@@ -421,12 +421,12 @@ public class LumberyardActivity extends NativeActivity
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
private boolean IsBootstrapInAPK()
|
||||
private boolean AreAssetsInAPK()
|
||||
{
|
||||
try
|
||||
{
|
||||
InputStream bootstrap = getAssets().open("bootstrap.cfg", AssetManager.ACCESS_UNKNOWN);
|
||||
bootstrap.close();
|
||||
InputStream engine = getAssets().open("engine.json", AssetManager.ACCESS_UNKNOWN);
|
||||
engine.close();
|
||||
return true;
|
||||
}
|
||||
catch (IOException exception)
|
||||
|
||||
@@ -148,7 +148,7 @@ namespace AZ
|
||||
}
|
||||
}
|
||||
|
||||
AZ_Assert(false, "Failed to locate the bootstrap.cfg path");
|
||||
AZ_Assert(false, "Failed to locate the engine.json path");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
@@ -73,8 +73,8 @@ namespace AZ
|
||||
//! \return The pointer position of the relative asset path
|
||||
AZ::IO::FixedMaxPath StripApkPrefix(const char* filePath);
|
||||
|
||||
//! Searches application storage and the APK for bootstrap.cfg. Will return nullptr
|
||||
//! if bootstrap.cfg is not found.
|
||||
//! Searches application storage and the APK for engine.json. Will return nullptr
|
||||
//! if engine.json is not found.
|
||||
const char* FindAssetsDirectory();
|
||||
|
||||
//! Calls into Java to show the splash screen on the main UI (Java) thread
|
||||
|
||||
@@ -462,8 +462,6 @@ namespace AZ
|
||||
// for the application root.
|
||||
CalculateAppRoot();
|
||||
|
||||
// Merge the bootstrap.cfg file into the Settings Registry as soon as the OSAllocator has been created.
|
||||
SettingsRegistryMergeUtils::MergeSettingsToRegistry_Bootstrap(*m_settingsRegistry);
|
||||
SettingsRegistryMergeUtils::MergeSettingsToRegistry_O3deUserRegistry(*m_settingsRegistry, AZ_TRAIT_OS_PLATFORM_CODENAME, {});
|
||||
SettingsRegistryMergeUtils::MergeSettingsToRegistry_CommandLine(*m_settingsRegistry, m_commandLine, executeRegDumpCommands);
|
||||
SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(*m_settingsRegistry);
|
||||
|
||||
@@ -126,17 +126,16 @@ namespace AZ
|
||||
m_offsets.fill(1); // Halton sequences start at index 1.
|
||||
m_increments.fill(1); // By default increment by 1 between each number.
|
||||
}
|
||||
|
||||
//! Returns a Halton sequence in an array of N length
|
||||
template<uint32_t N>
|
||||
AZStd::array<AZStd::array<float, Dimensions>, N> GetHaltonSequence()
|
||||
|
||||
//! Fills a provided container from begin to end with a Halton sequence.
|
||||
//! Entries are expected to be, or implicitly converted to, AZStd::array<float, Dimensions>.
|
||||
template<typename Iterator>
|
||||
void FillHaltonSequence(Iterator begin, Iterator end)
|
||||
{
|
||||
AZStd::array<AZStd::array<float, Dimensions>, N> result;
|
||||
|
||||
AZStd::array<uint32_t, Dimensions> indices = m_offsets;
|
||||
|
||||
// Generator that returns the Halton number for all bases for a single entry.
|
||||
auto f = [&] ()
|
||||
auto f = [&]()
|
||||
{
|
||||
AZStd::array<float, Dimensions> item;
|
||||
for (auto d = 0; d < Dimensions; ++d)
|
||||
@@ -147,12 +146,20 @@ namespace AZ
|
||||
return item;
|
||||
};
|
||||
|
||||
AZStd::generate(result.begin(), result.end(), f);
|
||||
AZStd::generate(begin, end, f);
|
||||
}
|
||||
|
||||
//! Returns a Halton sequence in an array of N length.
|
||||
template<uint32_t N>
|
||||
AZStd::array<AZStd::array<float, Dimensions>, N> GetHaltonSequence()
|
||||
{
|
||||
AZStd::array<AZStd::array<float, Dimensions>, N> result;
|
||||
FillHaltonSequence(result.begin(), result.end());
|
||||
return result;
|
||||
}
|
||||
|
||||
//! Sets the offsets per dimension to start generating a sequence from.
|
||||
//! By default, there is no offset (offset of 0 corresponds to starting at index 1)
|
||||
//! By default, there is no offset (offset of 0 corresponds to starting at index 1).
|
||||
void SetOffsets(AZStd::array<uint32_t, Dimensions> offsets)
|
||||
{
|
||||
m_offsets = offsets;
|
||||
|
||||
@@ -494,13 +494,6 @@ namespace AZ::SettingsRegistryMergeUtils
|
||||
return configFileParsed;
|
||||
}
|
||||
|
||||
void MergeSettingsToRegistry_Bootstrap(SettingsRegistryInterface& registry)
|
||||
{
|
||||
ConfigParserSettings parserSettings;
|
||||
parserSettings.m_registryRootPointerPath = BootstrapSettingsRootKey;
|
||||
MergeSettingsToRegistry_ConfigFile(registry, "bootstrap.cfg", parserSettings);
|
||||
}
|
||||
|
||||
void MergeSettingsToRegistry_AddRuntimeFilePaths(SettingsRegistryInterface& registry)
|
||||
{
|
||||
using FixedValueString = AZ::SettingsRegistryInterface::FixedValueString;
|
||||
|
||||
@@ -172,9 +172,6 @@ namespace AZ::SettingsRegistryMergeUtils
|
||||
bool MergeSettingsToRegistry_ConfigFile(SettingsRegistryInterface& registry, AZStd::string_view filePath,
|
||||
const ConfigParserSettings& configParserSettings);
|
||||
|
||||
//! Loads bootstrap.cfg into the Settings Registry. This file does not support specializations.
|
||||
void MergeSettingsToRegistry_Bootstrap(SettingsRegistryInterface& registry);
|
||||
|
||||
//! Extracts file path information from the environment and bootstrap to calculate the various file paths and adds those
|
||||
//! to the Settings Registry under the FilePathsRootKey.
|
||||
void MergeSettingsToRegistry_AddRuntimeFilePaths(SettingsRegistryInterface& registry);
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace UnitTest
|
||||
EXPECT_FLOAT_EQ(5981.0f / 15625.0f, GetHaltonNumber(4321, 5));
|
||||
}
|
||||
|
||||
TEST(MATH_Random, HaltonSequence)
|
||||
TEST(MATH_Random, HaltonSequenceStandard)
|
||||
{
|
||||
HaltonSequence<3> sequence({ 2, 3, 5 });
|
||||
auto regularSequence = sequence.GetHaltonSequence<5>();
|
||||
@@ -48,7 +48,11 @@ namespace UnitTest
|
||||
EXPECT_FLOAT_EQ(5.0f / 8.0f, regularSequence[4][0]);
|
||||
EXPECT_FLOAT_EQ(7.0f / 9.0f, regularSequence[4][1]);
|
||||
EXPECT_FLOAT_EQ(1.0f / 25.0f, regularSequence[4][2]);
|
||||
|
||||
}
|
||||
|
||||
TEST(MATH_Random, HaltonSequenceOffsets)
|
||||
{
|
||||
HaltonSequence<3> sequence({ 2, 3, 5 });
|
||||
sequence.SetOffsets({ 1, 2, 3 });
|
||||
auto offsetSequence = sequence.GetHaltonSequence<2>();
|
||||
|
||||
@@ -59,10 +63,15 @@ namespace UnitTest
|
||||
EXPECT_FLOAT_EQ(3.0f / 4.0f, offsetSequence[1][0]);
|
||||
EXPECT_FLOAT_EQ(4.0f / 9.0f, offsetSequence[1][1]);
|
||||
EXPECT_FLOAT_EQ(1.0f / 25.0f, offsetSequence[1][2]);
|
||||
|
||||
}
|
||||
|
||||
TEST(MATH_Random, HaltonSequenceIncrements)
|
||||
{
|
||||
HaltonSequence<3> sequence({ 2, 3, 5 });
|
||||
sequence.SetOffsets({ 1, 2, 3 });
|
||||
sequence.SetIncrements({ 1, 2, 3 });
|
||||
auto incrementedSequence = sequence.GetHaltonSequence<2>();
|
||||
|
||||
|
||||
EXPECT_FLOAT_EQ(1.0f / 4.0f, incrementedSequence[0][0]);
|
||||
EXPECT_FLOAT_EQ(1.0f / 9.0f, incrementedSequence[0][1]);
|
||||
EXPECT_FLOAT_EQ(4.0f / 5.0f, incrementedSequence[0][2]);
|
||||
@@ -71,4 +80,35 @@ namespace UnitTest
|
||||
EXPECT_FLOAT_EQ(7.0f / 9.0f, incrementedSequence[1][1]);
|
||||
EXPECT_FLOAT_EQ(11.0f / 25.0f, incrementedSequence[1][2]);
|
||||
}
|
||||
|
||||
TEST(MATH_Random, FillHaltonSequence)
|
||||
{
|
||||
HaltonSequence<3> sequence({ 2, 3, 5 });
|
||||
auto regularSequence = sequence.GetHaltonSequence<5>();
|
||||
|
||||
struct Point
|
||||
{
|
||||
Point() = default;
|
||||
Point(AZStd::array<float, 3> arr)
|
||||
:x(arr[0])
|
||||
,y(arr[1])
|
||||
,z(arr[2])
|
||||
{}
|
||||
|
||||
float x = 0.0f;
|
||||
float y = 0.0f;
|
||||
float z = 0.0f;
|
||||
};
|
||||
|
||||
AZStd::array<Point, 5> ownedContainer;
|
||||
sequence.FillHaltonSequence(ownedContainer.begin(), ownedContainer.end());
|
||||
|
||||
for (size_t i = 0; i < regularSequence.size(); ++i)
|
||||
{
|
||||
EXPECT_FLOAT_EQ(regularSequence[i][0], ownedContainer[i].x);
|
||||
EXPECT_FLOAT_EQ(regularSequence[i][1], ownedContainer[i].y);
|
||||
EXPECT_FLOAT_EQ(regularSequence[i][2], ownedContainer[i].z);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -679,8 +679,6 @@ namespace AzFramework
|
||||
{
|
||||
auto fileIoBase = m_archiveFileIO.get();
|
||||
// Set up the default file aliases based on the settings registry
|
||||
fileIoBase->SetAlias("@assets@", "");
|
||||
fileIoBase->SetAlias("@root@", GetEngineRoot());
|
||||
fileIoBase->SetAlias("@engroot@", GetEngineRoot());
|
||||
fileIoBase->SetAlias("@projectroot@", GetEngineRoot());
|
||||
fileIoBase->SetAlias("@exefolder@", GetExecutableFolder());
|
||||
@@ -694,8 +692,8 @@ namespace AzFramework
|
||||
pathAliases.clear();
|
||||
if (m_settingsRegistry->Get(pathAliases.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_CacheRootFolder))
|
||||
{
|
||||
fileIoBase->SetAlias("@projectplatformcache@", pathAliases.c_str());
|
||||
fileIoBase->SetAlias("@assets@", pathAliases.c_str());
|
||||
fileIoBase->SetAlias("@projectplatformcache@", pathAliases.c_str());
|
||||
fileIoBase->SetAlias("@root@", pathAliases.c_str()); // Deprecated Use @projectplatformcache@
|
||||
}
|
||||
pathAliases.clear();
|
||||
|
||||
@@ -308,6 +308,56 @@ namespace AzFramework
|
||||
}
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
GenerateRelativeSourcePathRequest::GenerateRelativeSourcePathRequest(const AZ::OSString& sourcePath)
|
||||
{
|
||||
AZ_Assert(!sourcePath.empty(), "GenerateRelativeSourcePathRequest: asset path is empty");
|
||||
m_sourcePath = sourcePath;
|
||||
}
|
||||
|
||||
unsigned int GenerateRelativeSourcePathRequest::GetMessageType() const
|
||||
{
|
||||
return MessageType;
|
||||
}
|
||||
|
||||
void GenerateRelativeSourcePathRequest::Reflect(AZ::ReflectContext* context)
|
||||
{
|
||||
auto serialize = azrtti_cast<AZ::SerializeContext*>(context);
|
||||
if (serialize)
|
||||
{
|
||||
serialize->Class<GenerateRelativeSourcePathRequest, BaseAssetProcessorMessage>()
|
||||
->Version(1)
|
||||
->Field("SourcePath", &GenerateRelativeSourcePathRequest::m_sourcePath);
|
||||
}
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
GenerateRelativeSourcePathResponse::GenerateRelativeSourcePathResponse(
|
||||
bool resolved, const AZ::OSString& relativeSourcePath, const AZ::OSString& rootFolder)
|
||||
{
|
||||
m_relativeSourcePath = relativeSourcePath;
|
||||
m_resolved = resolved;
|
||||
m_rootFolder = rootFolder;
|
||||
}
|
||||
|
||||
unsigned int GenerateRelativeSourcePathResponse::GetMessageType() const
|
||||
{
|
||||
return GenerateRelativeSourcePathRequest::MessageType;
|
||||
}
|
||||
|
||||
void GenerateRelativeSourcePathResponse::Reflect(AZ::ReflectContext* context)
|
||||
{
|
||||
auto serialize = azrtti_cast<AZ::SerializeContext*>(context);
|
||||
if (serialize)
|
||||
{
|
||||
serialize->Class<GenerateRelativeSourcePathResponse, BaseAssetProcessorMessage>()
|
||||
->Version(1)
|
||||
->Field("RelativeSourcePath", &GenerateRelativeSourcePathResponse::m_relativeSourcePath)
|
||||
->Field("RootFolder", &GenerateRelativeSourcePathResponse::m_rootFolder)
|
||||
->Field("Resolved", &GenerateRelativeSourcePathResponse::m_resolved);
|
||||
}
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
GetFullSourcePathFromRelativeProductPathRequest::GetFullSourcePathFromRelativeProductPathRequest(const AZ::OSString& relativeProductPath)
|
||||
{
|
||||
|
||||
@@ -288,6 +288,45 @@ namespace AzFramework
|
||||
bool m_resolved;
|
||||
};
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
class GenerateRelativeSourcePathRequest : public BaseAssetProcessorMessage
|
||||
{
|
||||
public:
|
||||
AZ_CLASS_ALLOCATOR(GenerateRelativeSourcePathRequest, AZ::OSAllocator, 0);
|
||||
AZ_RTTI(GenerateRelativeSourcePathRequest, "{B3865033-F5A3-4749-8147-7B1AB04D5F6D}",
|
||||
BaseAssetProcessorMessage);
|
||||
static void Reflect(AZ::ReflectContext* context);
|
||||
|
||||
// For people that are debugging the network messages and just see MessageType as a value,
|
||||
// the CRC value below is 739777771 (0x2C181CEB)
|
||||
static constexpr unsigned int MessageType =
|
||||
AZ_CRC_CE("AssetSystem::GenerateRelativeSourcePathRequest");
|
||||
|
||||
GenerateRelativeSourcePathRequest() = default;
|
||||
GenerateRelativeSourcePathRequest(const AZ::OSString& sourcePath);
|
||||
unsigned int GetMessageType() const override;
|
||||
|
||||
AZ::OSString m_sourcePath;
|
||||
};
|
||||
|
||||
class GenerateRelativeSourcePathResponse : public BaseAssetProcessorMessage
|
||||
{
|
||||
public:
|
||||
AZ_CLASS_ALLOCATOR(GenerateRelativeSourcePathResponse, AZ::OSAllocator, 0);
|
||||
AZ_RTTI(GenerateRelativeSourcePathResponse, "{938D33DB-C8F6-4FA4-BC81-2F139A9BE1D7}",
|
||||
BaseAssetProcessorMessage);
|
||||
static void Reflect(AZ::ReflectContext* context);
|
||||
|
||||
GenerateRelativeSourcePathResponse() = default;
|
||||
GenerateRelativeSourcePathResponse(
|
||||
bool resolved, const AZ::OSString& relativeSourcePath, const AZ::OSString& rootFolder);
|
||||
unsigned int GetMessageType() const override;
|
||||
|
||||
AZ::OSString m_relativeSourcePath;
|
||||
AZ::OSString m_rootFolder; ///< This is the folder it was found in (the watched/scanned folder, such as gems /assets/ folder)
|
||||
bool m_resolved;
|
||||
};
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
class GetFullSourcePathFromRelativeProductPathRequest
|
||||
: public BaseAssetProcessorMessage
|
||||
|
||||
@@ -202,6 +202,7 @@ namespace AzFramework
|
||||
// Requests
|
||||
GetUnresolvedDependencyCountsRequest::Reflect(context);
|
||||
GetRelativeProductPathFromFullSourceOrProductPathRequest::Reflect(context);
|
||||
GenerateRelativeSourcePathRequest::Reflect(context);
|
||||
GetFullSourcePathFromRelativeProductPathRequest::Reflect(context);
|
||||
SourceAssetInfoRequest::Reflect(context);
|
||||
AssetInfoRequest::Reflect(context);
|
||||
@@ -234,6 +235,7 @@ namespace AzFramework
|
||||
// Responses
|
||||
GetUnresolvedDependencyCountsResponse::Reflect(context);
|
||||
GetRelativeProductPathFromFullSourceOrProductPathResponse::Reflect(context);
|
||||
GenerateRelativeSourcePathResponse::Reflect(context);
|
||||
GetFullSourcePathFromRelativeProductPathResponse::Reflect(context);
|
||||
SourceAssetInfoResponse::Reflect(context);
|
||||
AssetInfoResponse::Reflect(context);
|
||||
|
||||
@@ -46,7 +46,6 @@ namespace AzFramework::ProjectManager
|
||||
// Store the Command line to the Setting Registry
|
||||
AZ::SettingsRegistryImpl settingsRegistry;
|
||||
AZ::SettingsRegistryMergeUtils::StoreCommandLineToRegistry(settingsRegistry, commandLine);
|
||||
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_Bootstrap(settingsRegistry);
|
||||
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_O3deUserRegistry(settingsRegistry, AZ_TRAIT_OS_PLATFORM_CODENAME, {});
|
||||
// Retrieve Command Line from Settings Registry, it may have been updated by the call to FindEngineRoot()
|
||||
// in MergeSettingstoRegistry_ConfigFile
|
||||
|
||||
@@ -19,10 +19,13 @@
|
||||
#include <AzCore/std/smart_ptr/unique_ptr.h>
|
||||
#include <AzFramework/Spawnable/SpawnableMetaData.h>
|
||||
|
||||
namespace AzFramework
|
||||
namespace AZ
|
||||
{
|
||||
class ReflectContext;
|
||||
}
|
||||
|
||||
namespace AzFramework
|
||||
{
|
||||
class Spawnable final
|
||||
: public AZ::Data::AssetData
|
||||
{
|
||||
|
||||
@@ -14,6 +14,10 @@
|
||||
|
||||
namespace AzFramework
|
||||
{
|
||||
//
|
||||
// SpawnableEntityContainerView
|
||||
//
|
||||
|
||||
SpawnableEntityContainerView::SpawnableEntityContainerView(AZ::Entity** begin, size_t length)
|
||||
: m_begin(begin)
|
||||
, m_end(begin + length)
|
||||
@@ -52,6 +56,9 @@ namespace AzFramework
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// SpawnableConstEntityContainerView
|
||||
//
|
||||
|
||||
SpawnableConstEntityContainerView::SpawnableConstEntityContainerView(AZ::Entity** begin, size_t length)
|
||||
: m_begin(begin)
|
||||
@@ -91,6 +98,136 @@ namespace AzFramework
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// SpawnableIndexEntityPair
|
||||
//
|
||||
|
||||
SpawnableIndexEntityPair::SpawnableIndexEntityPair(AZ::Entity** entityIterator, size_t* indexIterator)
|
||||
: m_entity(entityIterator)
|
||||
, m_index(indexIterator)
|
||||
{
|
||||
}
|
||||
|
||||
AZ::Entity* SpawnableIndexEntityPair::GetEntity()
|
||||
{
|
||||
return *m_entity;
|
||||
}
|
||||
|
||||
const AZ::Entity* SpawnableIndexEntityPair::GetEntity() const
|
||||
{
|
||||
return *m_entity;
|
||||
}
|
||||
|
||||
size_t SpawnableIndexEntityPair::GetIndex() const
|
||||
{
|
||||
return *m_index;
|
||||
}
|
||||
|
||||
//
|
||||
// SpawnableIndexEntityIterator
|
||||
//
|
||||
|
||||
SpawnableIndexEntityIterator::SpawnableIndexEntityIterator(AZ::Entity** entityIterator, size_t* indexIterator)
|
||||
: m_value(entityIterator, indexIterator)
|
||||
{
|
||||
}
|
||||
|
||||
SpawnableIndexEntityIterator& SpawnableIndexEntityIterator::operator++()
|
||||
{
|
||||
++m_value.m_entity;
|
||||
++m_value.m_index;
|
||||
return *this;
|
||||
}
|
||||
|
||||
SpawnableIndexEntityIterator SpawnableIndexEntityIterator::operator++(int)
|
||||
{
|
||||
SpawnableIndexEntityIterator result = *this;
|
||||
++m_value.m_entity;
|
||||
++m_value.m_index;
|
||||
return result;
|
||||
}
|
||||
|
||||
SpawnableIndexEntityIterator& SpawnableIndexEntityIterator::operator--()
|
||||
{
|
||||
--m_value.m_entity;
|
||||
--m_value.m_index;
|
||||
return *this;
|
||||
}
|
||||
|
||||
SpawnableIndexEntityIterator SpawnableIndexEntityIterator::operator--(int)
|
||||
{
|
||||
SpawnableIndexEntityIterator result = *this;
|
||||
--m_value.m_entity;
|
||||
--m_value.m_index;
|
||||
return result;
|
||||
}
|
||||
|
||||
bool SpawnableIndexEntityIterator::operator==(const SpawnableIndexEntityIterator& rhs)
|
||||
{
|
||||
return m_value.m_entity == rhs.m_value.m_entity && m_value.m_index == rhs.m_value.m_index;
|
||||
}
|
||||
|
||||
bool SpawnableIndexEntityIterator::operator!=(const SpawnableIndexEntityIterator& rhs)
|
||||
{
|
||||
return m_value.m_entity != rhs.m_value.m_entity || m_value.m_index != rhs.m_value.m_index;
|
||||
}
|
||||
|
||||
SpawnableIndexEntityPair& SpawnableIndexEntityIterator::operator*()
|
||||
{
|
||||
return m_value;
|
||||
}
|
||||
|
||||
const SpawnableIndexEntityPair& SpawnableIndexEntityIterator::operator*() const
|
||||
{
|
||||
return m_value;
|
||||
}
|
||||
|
||||
SpawnableIndexEntityPair* SpawnableIndexEntityIterator::operator->()
|
||||
{
|
||||
return &m_value;
|
||||
}
|
||||
|
||||
const SpawnableIndexEntityPair* SpawnableIndexEntityIterator::operator->() const
|
||||
{
|
||||
return &m_value;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// SpawnableConstIndexEntityContainerView
|
||||
//
|
||||
|
||||
SpawnableConstIndexEntityContainerView::SpawnableConstIndexEntityContainerView(
|
||||
AZ::Entity** beginEntity, size_t* beginIndices, size_t length)
|
||||
: m_begin(beginEntity, beginIndices)
|
||||
, m_end(beginEntity + length, beginIndices + length)
|
||||
{
|
||||
}
|
||||
|
||||
const SpawnableIndexEntityIterator& SpawnableConstIndexEntityContainerView::begin()
|
||||
{
|
||||
return m_begin;
|
||||
}
|
||||
|
||||
const SpawnableIndexEntityIterator& SpawnableConstIndexEntityContainerView::end()
|
||||
{
|
||||
return m_end;
|
||||
}
|
||||
|
||||
const SpawnableIndexEntityIterator& SpawnableConstIndexEntityContainerView::cbegin()
|
||||
{
|
||||
return m_begin;
|
||||
}
|
||||
|
||||
const SpawnableIndexEntityIterator& SpawnableConstIndexEntityContainerView::cend()
|
||||
{
|
||||
return m_end;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// EntitySpawnTicket
|
||||
//
|
||||
|
||||
EntitySpawnTicket::EntitySpawnTicket(EntitySpawnTicket&& rhs)
|
||||
: m_payload(rhs.m_payload)
|
||||
|
||||
@@ -58,6 +58,72 @@ namespace AzFramework
|
||||
AZ::Entity** m_end;
|
||||
};
|
||||
|
||||
class SpawnableIndexEntityPair
|
||||
{
|
||||
public:
|
||||
friend class SpawnableIndexEntityIterator;
|
||||
|
||||
AZ::Entity* GetEntity();
|
||||
const AZ::Entity* GetEntity() const;
|
||||
size_t GetIndex() const;
|
||||
|
||||
private:
|
||||
SpawnableIndexEntityPair() = default;
|
||||
SpawnableIndexEntityPair(const SpawnableIndexEntityPair&) = default;
|
||||
SpawnableIndexEntityPair(SpawnableIndexEntityPair&&) = default;
|
||||
SpawnableIndexEntityPair(AZ::Entity** entityIterator, size_t* indexIterator);
|
||||
|
||||
SpawnableIndexEntityPair& operator=(const SpawnableIndexEntityPair&) = default;
|
||||
SpawnableIndexEntityPair& operator=(SpawnableIndexEntityPair&&) = default;
|
||||
|
||||
AZ::Entity** m_entity { nullptr };
|
||||
size_t* m_index { nullptr };
|
||||
};
|
||||
|
||||
class SpawnableIndexEntityIterator
|
||||
{
|
||||
public:
|
||||
// Limited to bidirectional iterator as there's no use case for extending it further, but can be extended if a use case is found.
|
||||
using iterator_category = AZStd::bidirectional_iterator_tag;
|
||||
using value_type = SpawnableIndexEntityPair;
|
||||
using difference_type = size_t;
|
||||
using pointer = SpawnableIndexEntityPair*;
|
||||
using reference = SpawnableIndexEntityPair&;
|
||||
|
||||
SpawnableIndexEntityIterator(AZ::Entity** entityIterator, size_t* indexIterator);
|
||||
|
||||
SpawnableIndexEntityIterator& operator++();
|
||||
SpawnableIndexEntityIterator operator++(int);
|
||||
SpawnableIndexEntityIterator& operator--();
|
||||
SpawnableIndexEntityIterator operator--(int);
|
||||
|
||||
bool operator==(const SpawnableIndexEntityIterator& rhs);
|
||||
bool operator!=(const SpawnableIndexEntityIterator& rhs);
|
||||
|
||||
SpawnableIndexEntityPair& operator*();
|
||||
const SpawnableIndexEntityPair& operator*() const;
|
||||
SpawnableIndexEntityPair* operator->();
|
||||
const SpawnableIndexEntityPair* operator->() const;
|
||||
|
||||
private:
|
||||
SpawnableIndexEntityPair m_value;
|
||||
};
|
||||
|
||||
class SpawnableConstIndexEntityContainerView
|
||||
{
|
||||
public:
|
||||
SpawnableConstIndexEntityContainerView(AZ::Entity** beginEntity, size_t* beginIndices, size_t length);
|
||||
|
||||
const SpawnableIndexEntityIterator& begin();
|
||||
const SpawnableIndexEntityIterator& end();
|
||||
const SpawnableIndexEntityIterator& cbegin();
|
||||
const SpawnableIndexEntityIterator& cend();
|
||||
|
||||
private:
|
||||
SpawnableIndexEntityIterator m_begin;
|
||||
SpawnableIndexEntityIterator m_end;
|
||||
};
|
||||
|
||||
//! Requests to the SpawnableEntitiesInterface require a ticket with a valid spawnable that be used as a template. A ticket can
|
||||
//! be reused for multiple calls on the same spawnable and is safe to use by multiple threads at the same time. Entities created
|
||||
//! from the spawnable may be tracked by the ticket and so using the same ticket is needed to despawn the exact entities created
|
||||
@@ -88,6 +154,7 @@ namespace AzFramework
|
||||
using EntityDespawnCallback = AZStd::function<void(EntitySpawnTicket&)>;
|
||||
using ReloadSpawnableCallback = AZStd::function<void(EntitySpawnTicket&, SpawnableConstEntityContainerView)>;
|
||||
using ListEntitiesCallback = AZStd::function<void(EntitySpawnTicket&, SpawnableConstEntityContainerView)>;
|
||||
using ListIndicesEntitiesCallback = AZStd::function<void(EntitySpawnTicket&, SpawnableConstIndexEntityContainerView)>;
|
||||
using ClaimEntitiesCallback = AZStd::function<void(EntitySpawnTicket&, SpawnableEntityContainerView)>;
|
||||
using BarrierCallback = AZStd::function<void(EntitySpawnTicket&)>;
|
||||
|
||||
@@ -140,6 +207,15 @@ namespace AzFramework
|
||||
//! @param ticket Only the entities associated with this ticket will be listed.
|
||||
//! @param listCallback Required callback that will be called to list the entities on.
|
||||
virtual void ListEntities(EntitySpawnTicket& ticket, ListEntitiesCallback listCallback) = 0;
|
||||
//! List all entities that are spawned using this ticket with their spawnable index.
|
||||
//! Spawnables contain a flat list of entities, which are used as templates to spawn entities from. For every spawned entity
|
||||
//! the index of the entity in the spawnable that was used as a template is stored. This version of ListEntities will return
|
||||
//! both the entities and this index. The index can be used with SpawnEntities to create the same entities again. Note that
|
||||
//! the same index may appear multiple times as there are no restriction on how many instance of a specific entity can be
|
||||
//! created.
|
||||
//! @param ticket Only the entities associated with this ticket will be listed.
|
||||
//! @param listCallback Required callback that will be called to list the entities and indices on.
|
||||
virtual void ListIndicesAndEntities(EntitySpawnTicket& ticket, ListIndicesEntitiesCallback listCallback) = 0;
|
||||
//! Claim all entities that are spawned using this ticket. Ownership of the entities is transferred from the ticket to the
|
||||
//! caller through the callback. After this call the ticket will have no entities associated with it. The caller of
|
||||
//! this function will need to manage the entities after this call.
|
||||
|
||||
@@ -25,6 +25,8 @@ namespace AzFramework
|
||||
void SpawnableEntitiesManager::SpawnAllEntities(EntitySpawnTicket& ticket, EntityPreInsertionCallback preInsertionCallback,
|
||||
EntitySpawnCallback completionCallback)
|
||||
{
|
||||
AZ_Assert(ticket.IsValid(), "Ticket provided to SpawnAllEntities hasn't been initialized.");
|
||||
|
||||
SpawnAllEntitiesCommand queueEntry;
|
||||
queueEntry.m_ticket = &ticket;
|
||||
queueEntry.m_completionCallback = AZStd::move(completionCallback);
|
||||
@@ -40,6 +42,8 @@ namespace AzFramework
|
||||
EntitySpawnTicket& ticket, AZStd::vector<size_t> entityIndices,
|
||||
EntityPreInsertionCallback preInsertionCallback, EntitySpawnCallback completionCallback)
|
||||
{
|
||||
AZ_Assert(ticket.IsValid(), "Ticket provided to SpawnEntities hasn't been initialized.");
|
||||
|
||||
SpawnEntitiesCommand queueEntry;
|
||||
queueEntry.m_ticket = &ticket;
|
||||
queueEntry.m_entityIndices = AZStd::move(entityIndices);
|
||||
@@ -54,6 +58,8 @@ namespace AzFramework
|
||||
|
||||
void SpawnableEntitiesManager::DespawnAllEntities(EntitySpawnTicket& ticket, EntityDespawnCallback completionCallback)
|
||||
{
|
||||
AZ_Assert(ticket.IsValid(), "Ticket provided to DespawnAllEntities hasn't been initialized.");
|
||||
|
||||
DespawnAllEntitiesCommand queueEntry;
|
||||
queueEntry.m_ticket = &ticket;
|
||||
queueEntry.m_completionCallback = AZStd::move(completionCallback);
|
||||
@@ -67,6 +73,8 @@ namespace AzFramework
|
||||
void SpawnableEntitiesManager::ReloadSpawnable(EntitySpawnTicket& ticket, AZ::Data::Asset<Spawnable> spawnable,
|
||||
ReloadSpawnableCallback completionCallback)
|
||||
{
|
||||
AZ_Assert(ticket.IsValid(), "Ticket provided to ReloadSpawnable hasn't been initialized.");
|
||||
|
||||
ReloadSpawnableCommand queueEntry;
|
||||
queueEntry.m_ticket = &ticket;
|
||||
queueEntry.m_spawnable = AZStd::move(spawnable);
|
||||
@@ -81,6 +89,7 @@ namespace AzFramework
|
||||
void SpawnableEntitiesManager::ListEntities(EntitySpawnTicket& ticket, ListEntitiesCallback listCallback)
|
||||
{
|
||||
AZ_Assert(listCallback, "ListEntities called on spawnable entities without a valid callback to use.");
|
||||
AZ_Assert(ticket.IsValid(), "Ticket provided to ListEntities hasn't been initialized.");
|
||||
|
||||
ListEntitiesCommand queueEntry;
|
||||
queueEntry.m_ticket = &ticket;
|
||||
@@ -92,9 +101,25 @@ namespace AzFramework
|
||||
}
|
||||
}
|
||||
|
||||
void SpawnableEntitiesManager::ListIndicesAndEntities(EntitySpawnTicket& ticket, ListIndicesEntitiesCallback listCallback)
|
||||
{
|
||||
AZ_Assert(listCallback, "ListEntities called on spawnable entities without a valid callback to use.");
|
||||
AZ_Assert(ticket.IsValid(), "Ticket provided to ListEntities hasn't been initialized.");
|
||||
|
||||
ListIndicesEntitiesCommand queueEntry;
|
||||
queueEntry.m_ticket = &ticket;
|
||||
queueEntry.m_listCallback = AZStd::move(listCallback);
|
||||
{
|
||||
AZStd::scoped_lock queueLock(m_pendingRequestQueueMutex);
|
||||
queueEntry.m_ticketId = GetTicketPayload<Ticket>(ticket).m_nextTicketId++;
|
||||
m_pendingRequestQueue.push(AZStd::move(queueEntry));
|
||||
}
|
||||
}
|
||||
|
||||
void SpawnableEntitiesManager::ClaimEntities(EntitySpawnTicket& ticket, ClaimEntitiesCallback listCallback)
|
||||
{
|
||||
AZ_Assert(listCallback, "ClaimEntities called on spawnable entities without a valid callback to use.");
|
||||
AZ_Assert(ticket.IsValid(), "Ticket provided to ClaimEntities hasn't been initialized.");
|
||||
|
||||
ClaimEntitiesCommand queueEntry;
|
||||
queueEntry.m_ticket = &ticket;
|
||||
@@ -109,6 +134,7 @@ namespace AzFramework
|
||||
void SpawnableEntitiesManager::Barrier(EntitySpawnTicket& ticket, BarrierCallback completionCallback)
|
||||
{
|
||||
AZ_Assert(completionCallback, "Barrier on spawnable entities called without a valid callback to use.");
|
||||
AZ_Assert(ticket.IsValid(), "Ticket provided to Barrier hasn't been initialized.");
|
||||
|
||||
BarrierCommand queueEntry;
|
||||
queueEntry.m_ticket = &ticket;
|
||||
@@ -499,6 +525,27 @@ namespace AzFramework
|
||||
}
|
||||
}
|
||||
|
||||
bool SpawnableEntitiesManager::ProcessRequest(ListIndicesEntitiesCommand& request, [[maybe_unused]] AZ::SerializeContext& serializeContext)
|
||||
{
|
||||
Ticket& ticket = GetTicketPayload<Ticket>(*request.m_ticket);
|
||||
if (request.m_ticketId == ticket.m_currentTicketId)
|
||||
{
|
||||
AZ_Assert(
|
||||
ticket.m_spawnedEntities.size() == ticket.m_spawnedEntityIndices.size(),
|
||||
"Entities and indices on spawnable ticket have gone out of sync.");
|
||||
request.m_listCallback(
|
||||
*request.m_ticket,
|
||||
SpawnableConstIndexEntityContainerView(
|
||||
ticket.m_spawnedEntities.begin(), ticket.m_spawnedEntityIndices.begin(), ticket.m_spawnedEntities.size()));
|
||||
ticket.m_currentTicketId++;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool SpawnableEntitiesManager::ProcessRequest(ClaimEntitiesCommand& request, [[maybe_unused]] AZ::SerializeContext& serializeContext)
|
||||
{
|
||||
Ticket& ticket = GetTicketPayload<Ticket>(*request.m_ticket);
|
||||
|
||||
@@ -36,6 +36,7 @@ namespace AzFramework
|
||||
{
|
||||
public:
|
||||
AZ_RTTI(AzFramework::SpawnableEntitiesManager, "{6E14333F-128C-464C-94CA-A63B05A5E51C}");
|
||||
AZ_CLASS_ALLOCATOR(SpawnableEntitiesManager, AZ::SystemAllocator, 0);
|
||||
|
||||
enum class CommandQueueStatus : bool
|
||||
{
|
||||
@@ -58,6 +59,7 @@ namespace AzFramework
|
||||
ReloadSpawnableCallback completionCallback = {}) override;
|
||||
|
||||
void ListEntities(EntitySpawnTicket& ticket, ListEntitiesCallback listCallback) override;
|
||||
void ListIndicesAndEntities(EntitySpawnTicket& ticket, ListIndicesEntitiesCallback listCallback) override;
|
||||
void ClaimEntities(EntitySpawnTicket& ticket, ClaimEntitiesCallback listCallback) override;
|
||||
|
||||
void Barrier(EntitySpawnTicket& spawnInfo, BarrierCallback completionCallback) override;
|
||||
@@ -123,6 +125,12 @@ namespace AzFramework
|
||||
EntitySpawnTicket* m_ticket;
|
||||
uint32_t m_ticketId;
|
||||
};
|
||||
struct ListIndicesEntitiesCommand
|
||||
{
|
||||
ListIndicesEntitiesCallback m_listCallback;
|
||||
EntitySpawnTicket* m_ticket;
|
||||
uint32_t m_ticketId;
|
||||
};
|
||||
struct ClaimEntitiesCommand
|
||||
{
|
||||
ClaimEntitiesCallback m_listCallback;
|
||||
@@ -141,8 +149,9 @@ namespace AzFramework
|
||||
uint32_t m_ticketId;
|
||||
};
|
||||
|
||||
using Requests = AZStd::variant<SpawnAllEntitiesCommand, SpawnEntitiesCommand, DespawnAllEntitiesCommand, ReloadSpawnableCommand,
|
||||
ListEntitiesCommand, ClaimEntitiesCommand, BarrierCommand, DestroyTicketCommand>;
|
||||
using Requests = AZStd::variant<
|
||||
SpawnAllEntitiesCommand, SpawnEntitiesCommand, DespawnAllEntitiesCommand, ReloadSpawnableCommand, ListEntitiesCommand,
|
||||
ListIndicesEntitiesCommand, ClaimEntitiesCommand, BarrierCommand, DestroyTicketCommand>;
|
||||
|
||||
AZ::Entity* SpawnSingleEntity(const AZ::Entity& entityTemplate,
|
||||
AZ::SerializeContext& serializeContext);
|
||||
@@ -155,6 +164,7 @@ namespace AzFramework
|
||||
bool ProcessRequest(DespawnAllEntitiesCommand& request, AZ::SerializeContext& serializeContext);
|
||||
bool ProcessRequest(ReloadSpawnableCommand& request, AZ::SerializeContext& serializeContext);
|
||||
bool ProcessRequest(ListEntitiesCommand& request, AZ::SerializeContext& serializeContext);
|
||||
bool ProcessRequest(ListIndicesEntitiesCommand& request, AZ::SerializeContext& serializeContext);
|
||||
bool ProcessRequest(ClaimEntitiesCommand& request, AZ::SerializeContext& serializeContext);
|
||||
bool ProcessRequest(BarrierCommand& request, AZ::SerializeContext& serializeContext);
|
||||
bool ProcessRequest(DestroyTicketCommand& request, AZ::SerializeContext& serializeContext);
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>Icon / Local</title>
|
||||
<g id="Icon-/-Local" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<rect id="Rectangle" x="0" y="0" width="24" height="24"></rect>
|
||||
<polygon id="L" fill="#FFFFFF" fill-rule="nonzero" points="16.9658203 19 16.9658203 16.5 11.1748047 16.5 11.1748047 4.72265625 8.14746094 4.72265625 8.14746094 19"></polygon>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 575 B |
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>Icon / Parent</title>
|
||||
<g id="Icon-/-Parent" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<rect id="Rectangle" x="0" y="0" width="24" height="24"></rect>
|
||||
<path d="M10.5449219,19 L10.5449219,13.921875 L11.84375,13.921875 C13.6145833,13.921875 14.9801432,13.514974 15.9404297,12.7011719 C16.9007161,11.8873698 17.3808594,10.7122396 17.3808594,9.17578125 C17.3808594,7.69791667 16.930013,6.58626302 16.0283203,5.84082031 C15.1266276,5.0953776 13.8098958,4.72265625 12.078125,4.72265625 L12.078125,4.72265625 L7.51757812,4.72265625 L7.51757812,19 L10.5449219,19 Z M11.5410156,11.4414062 L10.5449219,11.4414062 L10.5449219,7.203125 L11.921875,7.203125 C12.7486979,7.203125 13.3557943,7.37239583 13.7431641,7.7109375 C14.1305339,8.04947917 14.3242188,8.57356771 14.3242188,9.28320312 C14.3242188,9.98632812 14.093099,10.5218099 13.6308594,10.8896484 C13.1686198,11.257487 12.4720052,11.4414062 11.5410156,11.4414062 L11.5410156,11.4414062 Z" id="P" fill="#FFFFFF" fill-rule="nonzero"></path>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>Icon / World</title>
|
||||
<g id="Icon-/-World" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<rect id="Rectangle" x="0" y="0" width="24" height="24"></rect>
|
||||
<path d="M9.41210938,19 L11.3359375,11.5195312 C11.4205729,11.1875 11.5410156,10.632487 11.6972656,9.85449219 C11.8535156,9.0764974 11.9511719,8.51171875 11.9902344,8.16015625 C12.016276,8.44661458 12.1155599,9.00813802 12.2880859,9.84472656 C12.460612,10.6813151 12.5826823,11.2330729 12.6542969,11.5 L12.6542969,11.5 L14.5878906,19 L18.0351562,19 L21.6679688,4.72265625 L18.6894531,4.72265625 L16.8730469,12.515625 C16.7558594,12.984375 16.625651,13.5979818 16.4824219,14.3564453 C16.3391927,15.1149089 16.235026,15.7480469 16.1699219,16.2558594 C16.0983073,15.7285156 15.9941406,15.0970052 15.8574219,14.3613281 C15.7207031,13.625651 15.6067708,13.078776 15.515625,12.7207031 L15.515625,12.7207031 L13.4355469,4.72265625 L10.5742188,4.72265625 L8.49414062,12.7207031 C8.37044271,13.1503906 8.23860677,13.7542318 8.09863281,14.5322266 C7.95865885,15.3102214 7.86914062,15.8847656 7.83007812,16.2558594 C7.68684896,15.2011719 7.45572917,13.9544271 7.13671875,12.515625 L7.13671875,12.515625 L5.31054688,4.72265625 L2.33203125,4.72265625 L5.97460938,19 L9.41210938,19 Z" id="W" fill="#FFFFFF" fill-rule="nonzero"></path>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -354,6 +354,7 @@
|
||||
<file>img/UI20/toolbar/Grid.svg</file>
|
||||
<file>img/UI20/toolbar/Lighting.svg</file>
|
||||
<file>img/UI20/toolbar/Load.svg</file>
|
||||
<file>img/UI20/toolbar/Local.svg</file>
|
||||
<file>img/UI20/toolbar/Locked.svg</file>
|
||||
<file>img/UI20/toolbar/LUA.svg</file>
|
||||
<file>img/UI20/toolbar/Material.svg</file>
|
||||
@@ -362,6 +363,7 @@
|
||||
<file>img/UI20/toolbar/Object_follow_terrain.svg</file>
|
||||
<file>img/UI20/toolbar/Object_height.svg</file>
|
||||
<file>img/UI20/toolbar/Object_list.svg</file>
|
||||
<file>img/UI20/toolbar/Parent.svg</file>
|
||||
<file>img/UI20/toolbar/particle.svg</file>
|
||||
<file>img/UI20/toolbar/Play.svg</file>
|
||||
<file>img/UI20/toolbar/Redo.svg</file>
|
||||
@@ -380,6 +382,7 @@
|
||||
<file>img/UI20/toolbar/undo.svg</file>
|
||||
<file>img/UI20/toolbar/Unlocked.svg</file>
|
||||
<file>img/UI20/toolbar/Vertex_snapping.svg</file>
|
||||
<file>img/UI20/toolbar/World.svg</file>
|
||||
<file>img/UI20/toolbar/X_axis.svg</file>
|
||||
<file>img/UI20/toolbar/Y_axis.svg</file>
|
||||
<file>img/UI20/toolbar/Z_axis.svg</file>
|
||||
|
||||
@@ -60,10 +60,20 @@ namespace AzToolsFramework
|
||||
//! and is generally checked into source control.
|
||||
virtual const char* GetAbsoluteDevRootFolderPath() = 0;
|
||||
|
||||
/// Convert a full source path like "c:\\dev\gamename\\blah\\test.tga" into a relative product path.
|
||||
/// Convert a full source path like "c:\\dev\\gamename\\blah\\test.tga" into a relative product path.
|
||||
/// asset paths never mention their alias and are relative to the asset cache root
|
||||
virtual bool GetRelativeProductPathFromFullSourceOrProductPath(const AZStd::string& fullPath, AZStd::string& relativeProductPath) = 0;
|
||||
|
||||
/** Convert a source path like "c:\\dev\\gamename\\blah\\test.tga" into a relative source path, like "blah/test.tga".
|
||||
* If no valid relative path could be created, the input source path will be returned in relativePath.
|
||||
* @param sourcePath partial or full path to a source file. (The file doesn't need to exist)
|
||||
* @param relativePath the output relative path for the source file, if a valid one could be created
|
||||
* @param rootFilePath the root path that relativePath is relative to
|
||||
* @return true if a valid relative path was created, false if it wasn't
|
||||
*/
|
||||
virtual bool GenerateRelativeSourcePath(
|
||||
const AZStd::string& sourcePath, AZStd::string& relativePath, AZStd::string& rootFilePath) = 0;
|
||||
|
||||
/// Convert a relative asset path like "blah/test.tga" to a full source path path.
|
||||
/// Once the asset processor has finished building, this function is capable of handling even when the extension changes
|
||||
/// or when the source is in a different folder or in a different location (such as inside gems)
|
||||
|
||||
@@ -239,6 +239,11 @@ namespace AzToolsFramework
|
||||
*/
|
||||
virtual int RemoveDirtyEntity(AZ::EntityId target) = 0;
|
||||
|
||||
/*!
|
||||
* Clears the dirty entity set.
|
||||
*/
|
||||
virtual void ClearDirtyEntities() = 0;
|
||||
|
||||
/*!
|
||||
* \return true if an undo/redo operation is in progress.
|
||||
*/
|
||||
|
||||
@@ -1354,6 +1354,11 @@ namespace AzToolsFramework
|
||||
return static_cast<int>(m_dirtyEntities.erase(entityId));
|
||||
}
|
||||
|
||||
void ToolsApplication::ClearDirtyEntities()
|
||||
{
|
||||
m_dirtyEntities.clear();
|
||||
}
|
||||
|
||||
void ToolsApplication::UndoPressed()
|
||||
{
|
||||
if (m_undoStack)
|
||||
|
||||
@@ -85,6 +85,7 @@ namespace AzToolsFramework
|
||||
|
||||
void AddDirtyEntity(AZ::EntityId entityId) override;
|
||||
int RemoveDirtyEntity(AZ::EntityId entityId) override;
|
||||
void ClearDirtyEntities() override;
|
||||
bool IsDuringUndoRedo() override { return m_isDuringUndoRedo; }
|
||||
void UndoPressed() override;
|
||||
void RedoPressed() override;
|
||||
|
||||
@@ -265,6 +265,30 @@ namespace AzToolsFramework
|
||||
return response.m_resolved;
|
||||
}
|
||||
|
||||
bool AssetSystemComponent::GenerateRelativeSourcePath(
|
||||
const AZStd::string& sourcePath, AZStd::string& relativePath, AZStd::string& rootFilePath)
|
||||
{
|
||||
AzFramework::SocketConnection* engineConnection = AzFramework::SocketConnection::GetInstance();
|
||||
if (!engineConnection || !engineConnection->IsConnected())
|
||||
{
|
||||
relativePath = sourcePath;
|
||||
return false;
|
||||
}
|
||||
|
||||
AzFramework::AssetSystem::GenerateRelativeSourcePathRequest request(sourcePath);
|
||||
AzFramework::AssetSystem::GenerateRelativeSourcePathResponse response;
|
||||
if (!SendRequest(request, response))
|
||||
{
|
||||
AZ_Error("Editor", false, "Failed to send GenerateRelativeSourcePath request for %s", sourcePath.c_str());
|
||||
relativePath = sourcePath;
|
||||
return false;
|
||||
}
|
||||
|
||||
relativePath = response.m_relativeSourcePath;
|
||||
rootFilePath = response.m_rootFolder;
|
||||
return response.m_resolved;
|
||||
}
|
||||
|
||||
bool AssetSystemComponent::GetFullSourcePathFromRelativeProductPath(const AZStd::string& relPath, AZStd::string& fullPath)
|
||||
{
|
||||
auto foundIt = m_assetSourceRelativePathToFullPathCache.find(relPath);
|
||||
|
||||
@@ -63,6 +63,8 @@ namespace AzToolsFramework
|
||||
const char* GetAbsoluteDevGameFolderPath() override;
|
||||
const char* GetAbsoluteDevRootFolderPath() override;
|
||||
bool GetRelativeProductPathFromFullSourceOrProductPath(const AZStd::string& fullPath, AZStd::string& outputPath) override;
|
||||
bool GenerateRelativeSourcePath(
|
||||
const AZStd::string& sourcePath, AZStd::string& outputPath, AZStd::string& watchFolder) override;
|
||||
bool GetFullSourcePathFromRelativeProductPath(const AZStd::string& relPath, AZStd::string& fullPath) override;
|
||||
bool GetAssetInfoById(const AZ::Data::AssetId& assetId, const AZ::Data::AssetType& assetType, const AZStd::string& platformName, AZ::Data::AssetInfo& assetInfo, AZStd::string& rootFilePath) override;
|
||||
bool GetSourceInfoBySourcePath(const char* sourcePath, AZ::Data::AssetInfo& assetInfo, AZStd::string& watchFolder) override;
|
||||
|
||||
+8
-12
@@ -276,18 +276,14 @@ namespace AzToolsFramework
|
||||
PrefabDomValueReference linkPatchesReference =
|
||||
PrefabDomUtils::FindPrefabDomValue(linkDom, PrefabDomUtils::PatchesName);
|
||||
|
||||
// This logic only covers addition of patches. If patches already exists, the given list of patches must be appended to them.
|
||||
if (!linkPatchesReference.has_value())
|
||||
{
|
||||
/*
|
||||
If the original allocator the patches were created with gets destroyed, then the patches would become garbage in the
|
||||
linkDom. Since we cannot guarantee the lifecycle of the patch allocators, we are doing a copy of the patches here to
|
||||
associate them with the linkDom's allocator.
|
||||
*/
|
||||
PrefabDom patchesCopy;
|
||||
patchesCopy.CopyFrom(patches, linkDom.GetAllocator());
|
||||
linkDom.AddMember(rapidjson::StringRef(PrefabDomUtils::PatchesName), patchesCopy, linkDom.GetAllocator());
|
||||
}
|
||||
/*
|
||||
If the original allocator the patches were created with gets destroyed, then the patches would become garbage in the
|
||||
linkDom. Since we cannot guarantee the lifecycle of the patch allocators, we are doing a copy of the patches here to
|
||||
associate them with the linkDom's allocator.
|
||||
*/
|
||||
PrefabDom patchesCopy;
|
||||
patchesCopy.CopyFrom(patches, linkDom.GetAllocator());
|
||||
linkDom.AddMember(rapidjson::StringRef(PrefabDomUtils::PatchesName), patchesCopy, linkDom.GetAllocator());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -234,5 +234,10 @@ namespace AzToolsFramework
|
||||
}
|
||||
}
|
||||
|
||||
PrefabDomValueReference Link::GetLinkPatches()
|
||||
{
|
||||
return PrefabDomUtils::FindPrefabDomValue(m_linkDom, PrefabDomUtils::PatchesName);
|
||||
}
|
||||
|
||||
} // namespace Prefab
|
||||
} // namespace AzToolsFramework
|
||||
|
||||
@@ -79,6 +79,8 @@ namespace AzToolsFramework
|
||||
*/
|
||||
void AddLinkIdToInstanceDom(PrefabDomValue& instanceDomValue);
|
||||
|
||||
PrefabDomValueReference GetLinkPatches();
|
||||
|
||||
private:
|
||||
|
||||
/**
|
||||
|
||||
@@ -41,7 +41,7 @@ namespace AzToolsFramework
|
||||
|
||||
[[maybe_unused]] bool result =
|
||||
settingsRegistry->Get(m_projectPathWithOsSeparator.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_ProjectPath);
|
||||
AZ_Assert(result, "Couldn't retrieve project root path");
|
||||
AZ_Warning("Prefab", result, "Couldn't retrieve project root path");
|
||||
m_projectPathWithSlashSeparator = AZ::IO::Path(m_projectPathWithOsSeparator.Native(), '/').MakePreferred();
|
||||
|
||||
AZ::Interface<PrefabLoaderInterface>::Register(this);
|
||||
|
||||
@@ -10,8 +10,6 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#include <AzToolsFramework/Prefab/PrefabPublicHandler.h>
|
||||
|
||||
#include <AzCore/Component/TransformBus.h>
|
||||
#include <AzCore/JSON/stringbuffer.h>
|
||||
#include <AzCore/JSON/writer.h>
|
||||
@@ -28,6 +26,7 @@
|
||||
#include <AzToolsFramework/Prefab/Instance/InstanceToTemplateInterface.h>
|
||||
#include <AzToolsFramework/Prefab/PrefabDomUtils.h>
|
||||
#include <AzToolsFramework/Prefab/PrefabLoaderInterface.h>
|
||||
#include <AzToolsFramework/Prefab/PrefabPublicHandler.h>
|
||||
#include <AzToolsFramework/Prefab/PrefabSystemComponentInterface.h>
|
||||
#include <AzToolsFramework/Prefab/PrefabUndo.h>
|
||||
#include <AzToolsFramework/Prefab/PrefabUndoHelpers.h>
|
||||
@@ -98,9 +97,13 @@ namespace AzToolsFramework
|
||||
AZStd::string("Could not create a new prefab out of the entities provided - invalid selection."));
|
||||
}
|
||||
|
||||
AZStd::unordered_map<AZ::EntityId, AZStd::string> oldEntityAliases;
|
||||
|
||||
// Detach the retrieved entities
|
||||
for (AZ::Entity* entity : entities)
|
||||
{
|
||||
AZ::EntityId entityId = entity->GetId();
|
||||
oldEntityAliases.emplace(entityId, commonRootEntityOwningInstance->get().GetEntityAlias(entityId)->get());
|
||||
commonRootEntityOwningInstance->get().DetachEntity(entity->GetId()).release();
|
||||
}
|
||||
|
||||
@@ -110,15 +113,18 @@ namespace AzToolsFramework
|
||||
{
|
||||
AZStd::unique_ptr<Instance> outInstance = commonRootEntityOwningInstance->get().DetachNestedInstance(nestedInstance->GetInstanceAlias());
|
||||
|
||||
auto linkRef = m_prefabSystemComponentInterface->FindLink(nestedInstance->GetLinkId());
|
||||
LinkId detachingInstanceLinkId = nestedInstance->GetLinkId();
|
||||
auto linkRef = m_prefabSystemComponentInterface->FindLink(detachingInstanceLinkId);
|
||||
AZ_Assert(linkRef.has_value(), "Unable to find link with id '%llu' during prefab creation.", detachingInstanceLinkId);
|
||||
|
||||
if (linkRef.has_value())
|
||||
{
|
||||
PrefabDom oldLinkPatches;
|
||||
oldLinkPatches.CopyFrom(linkRef->get().GetLinkDom(), oldLinkPatches.GetAllocator());
|
||||
PrefabDomValueReference linkPatches = linkRef->get().GetLinkPatches();
|
||||
AZ_Assert(
|
||||
linkPatches.has_value(), "Unable to get patches on link with id '%llu' during prefab creation.",
|
||||
detachingInstanceLinkId);
|
||||
|
||||
nestedInstanceLinkPatchesMap.emplace(nestedInstance, AZStd::move(oldLinkPatches));
|
||||
}
|
||||
PrefabDom linkPatchesCopy;
|
||||
linkPatchesCopy.CopyFrom(linkPatches->get(), linkPatchesCopy.GetAllocator());
|
||||
nestedInstanceLinkPatchesMap.emplace(nestedInstance, AZStd::move(linkPatchesCopy));
|
||||
|
||||
RemoveLink(outInstance, commonRootEntityOwningInstance->get().GetTemplateId(), undoBatch.GetUndoBatch());
|
||||
|
||||
@@ -182,6 +188,24 @@ namespace AzToolsFramework
|
||||
if (nestedInstanceLinkPatchesMap.contains(nestedInstance.get()))
|
||||
{
|
||||
previousPatch = AZStd::move(nestedInstanceLinkPatchesMap[nestedInstance.get()]);
|
||||
rapidjson::StringBuffer buffer;
|
||||
rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
|
||||
previousPatch.Accept(writer);
|
||||
QString previousPatchString(buffer.GetString());
|
||||
|
||||
for (AZ::Entity* entity : entities)
|
||||
{
|
||||
AZ::EntityId entityId = entity->GetId();
|
||||
AZStd::string oldEntityAlias = oldEntityAliases[entityId];
|
||||
EntityAliasOptionalReference newEntityAlias = instanceToCreate->get().GetEntityAlias(entityId);
|
||||
AZ_Assert(
|
||||
newEntityAlias.has_value(),
|
||||
"Could not fetch entity alias for entity with id '%llu' during prefab creation.",
|
||||
static_cast<AZ::u64>(entityId));
|
||||
ReplaceOldAliases(previousPatchString, oldEntityAlias, newEntityAlias->get());
|
||||
}
|
||||
|
||||
previousPatch.Parse(previousPatchString.toUtf8().constData());
|
||||
}
|
||||
|
||||
// These link creations shouldn't be undone because that would put the template in a non-usable state if a user
|
||||
@@ -203,36 +227,23 @@ namespace AzToolsFramework
|
||||
m_instanceToTemplateInterface->GeneratePatch(reparentPatch, containerEntityDomBefore, containerEntityDomAfter);
|
||||
m_instanceToTemplateInterface->AppendEntityAliasToPatchPaths(reparentPatch, nestedInstanceContainerEntityId);
|
||||
|
||||
// Update the cache - this prevents these changes from being stored in the regular undo/redo nodes as a separate step
|
||||
m_prefabUndoCache.Store(nestedInstanceContainerEntityId, AZStd::move(containerEntityDomAfter));
|
||||
|
||||
// Save these changes as patches to the link
|
||||
PrefabUndoLinkUpdate* linkUpdate = aznew PrefabUndoLinkUpdate(AZStd::to_string(static_cast<AZ::u64>(nestedInstanceContainerEntityId)));
|
||||
linkUpdate->SetParent(undoBatch.GetUndoBatch());
|
||||
linkUpdate->Capture(reparentPatch, nestedInstance->GetLinkId());
|
||||
|
||||
linkUpdate->Redo();
|
||||
// We won't parent this undo node to the undo batch so that the newly created template and link will remain
|
||||
// unaffected by undo actions. This is needed so that any future instantiations of the template will work.
|
||||
PrefabUndoLinkUpdate linkUpdate = PrefabUndoLinkUpdate(AZStd::to_string(static_cast<AZ::u64>(nestedInstanceContainerEntityId)));
|
||||
linkUpdate.Capture(reparentPatch, nestedInstance->GetLinkId());
|
||||
linkUpdate.Redo();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// Create a link between the templates of the newly created instance and the instance it's being parented under.
|
||||
CreateLink(
|
||||
instanceToCreate->get(), commonRootEntityOwningInstance->get().GetTemplateId(), undoBatch.GetUndoBatch(),
|
||||
AZStd::move(patch));
|
||||
|
||||
for (AZ::Entity* topLevelEntity : topLevelEntities)
|
||||
{
|
||||
AZ::EntityId topLevelEntityId = topLevelEntity->GetId();
|
||||
if (topLevelEntityId.IsValid())
|
||||
{
|
||||
m_prefabUndoCache.UpdateCache(topLevelEntity->GetId());
|
||||
|
||||
// Parenting entities would mark entities as dirty. But we want to unmark the top level entities as dirty because
|
||||
// if we don't, the template created would be updated and cause issues with undo operation followed by instantiation.
|
||||
ToolsApplicationRequests::Bus::Broadcast(
|
||||
&ToolsApplicationRequests::Bus::Events::RemoveDirtyEntity, topLevelEntity->GetId());
|
||||
}
|
||||
}
|
||||
// This clears any entities marked as dirty due to reparenting of entities during the process of creating a prefab.
|
||||
// We are doing this so that the changes in those enities are not queued up twice for propagation.
|
||||
AzToolsFramework::ToolsApplicationRequestBus::Broadcast(
|
||||
&AzToolsFramework::ToolsApplicationRequestBus::Events::ClearDirtyEntities);
|
||||
|
||||
// Select Container Entity
|
||||
{
|
||||
@@ -824,15 +835,7 @@ namespace AzToolsFramework
|
||||
// This will cover both cases where an alias could be used in a normal entity vs. an instance
|
||||
for (auto aliasMapIter : oldAliasToNewAliasMap)
|
||||
{
|
||||
QString oldAliasQuotes = QString("\"%1\"").arg(aliasMapIter.first.c_str());
|
||||
QString newAliasQuotes = QString("\"%1\"").arg(aliasMapIter.second.c_str());
|
||||
|
||||
newEntityDomString.replace(oldAliasQuotes, newAliasQuotes);
|
||||
|
||||
QString oldAliasPathRef = QString("/%1").arg(aliasMapIter.first.c_str());
|
||||
QString newAliasPathRef = QString("/%1").arg(aliasMapIter.second.c_str());
|
||||
|
||||
newEntityDomString.replace(oldAliasPathRef, newAliasPathRef);
|
||||
ReplaceOldAliases(newEntityDomString, aliasMapIter.first, aliasMapIter.second);
|
||||
}
|
||||
|
||||
// Create the new Entity DOM from parsing the JSON string
|
||||
@@ -1233,5 +1236,18 @@ namespace AzToolsFramework
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void PrefabPublicHandler::ReplaceOldAliases(QString& stringToReplace, AZStd::string_view oldAlias, AZStd::string_view newAlias)
|
||||
{
|
||||
QString oldAliasQuotes = QString("\"%1\"").arg(oldAlias.data());
|
||||
QString newAliasQuotes = QString("\"%1\"").arg(newAlias.data());
|
||||
|
||||
stringToReplace.replace(oldAliasQuotes, newAliasQuotes);
|
||||
|
||||
QString oldAliasPathRef = QString("/%1").arg(oldAlias.data());
|
||||
QString newAliasPathRef = QString("/%1").arg(newAlias.data());
|
||||
|
||||
stringToReplace.replace(oldAliasPathRef, newAliasPathRef);
|
||||
}
|
||||
} // namespace Prefab
|
||||
} // namespace AzToolsFramework
|
||||
|
||||
@@ -14,12 +14,15 @@
|
||||
|
||||
#include <AzCore/Math/Vector3.h>
|
||||
#include <AzCore/Memory/SystemAllocator.h>
|
||||
#include <AzCore/std/string/string_view.h>
|
||||
|
||||
#include <AzToolsFramework/Prefab/Instance/Instance.h>
|
||||
#include <AzToolsFramework/Prefab/PrefabPublicInterface.h>
|
||||
#include <AzToolsFramework/Prefab/PrefabSystemComponentInterface.h>
|
||||
#include <AzToolsFramework/Prefab/PrefabUndoCache.h>
|
||||
|
||||
class QString;
|
||||
|
||||
namespace AzToolsFramework
|
||||
{
|
||||
using EntityList = AZStd::vector<AZ::Entity*>;
|
||||
@@ -27,7 +30,6 @@ namespace AzToolsFramework
|
||||
namespace Prefab
|
||||
{
|
||||
class Instance;
|
||||
|
||||
class InstanceEntityMapperInterface;
|
||||
class InstanceToTemplateInterface;
|
||||
class PrefabLoaderInterface;
|
||||
@@ -130,6 +132,8 @@ namespace AzToolsFramework
|
||||
bool IsCyclicalDependencyFound(
|
||||
InstanceOptionalConstReference instance, const AZStd::unordered_set<AZ::IO::Path>& templateSourcePaths);
|
||||
|
||||
void ReplaceOldAliases(QString& stringToReplace, AZStd::string_view oldAlias, AZStd::string_view newAlias);
|
||||
|
||||
static Instance* GetParentInstance(Instance* instance);
|
||||
static Instance* GetAncestorOfInstanceThatIsChildOfRoot(const Instance* ancestor, Instance* descendant);
|
||||
static void GenerateContainerEntityTransform(const EntityList& topLevelEntities, AZ::Vector3& translation, AZ::Quaternion& rotation);
|
||||
|
||||
+99
-12
@@ -435,7 +435,7 @@ namespace AzToolsFramework
|
||||
}
|
||||
}
|
||||
|
||||
static void DestroyTransformModeSelectionCluster(const ViewportUi::ClusterId clusterId)
|
||||
static void DestroyCluster(const ViewportUi::ClusterId clusterId)
|
||||
{
|
||||
ViewportUi::ViewportUiRequestBus::Event(
|
||||
ViewportUi::DefaultViewportId,
|
||||
@@ -483,6 +483,26 @@ namespace AzToolsFramework
|
||||
return worldFromLocal.TransformPoint(CalculateCenterOffset(entityId, pivot));
|
||||
}
|
||||
|
||||
void EditorTransformComponentSelection::UpdateSpaceCluster(const ReferenceFrame referenceFrame)
|
||||
{
|
||||
auto buttonIdFromFrameFn = [this](const ReferenceFrame referenceFrame) {
|
||||
switch (referenceFrame)
|
||||
{
|
||||
case ReferenceFrame::Local:
|
||||
return m_spaceCluster.m_localButtonId;
|
||||
case ReferenceFrame::Parent:
|
||||
return m_spaceCluster.m_parentButtonId;
|
||||
case ReferenceFrame::World:
|
||||
return m_spaceCluster.m_worldButtonId;
|
||||
}
|
||||
return m_spaceCluster.m_parentButtonId;
|
||||
};
|
||||
|
||||
ViewportUi::ViewportUiRequestBus::Event(
|
||||
ViewportUi::DefaultViewportId, &ViewportUi::ViewportUiRequestBus::Events::SetClusterActiveButton, m_spaceCluster.m_spaceClusterId,
|
||||
buttonIdFromFrameFn(referenceFrame));
|
||||
}
|
||||
|
||||
namespace ETCS
|
||||
{
|
||||
PivotOrientationResult CalculatePivotOrientation(
|
||||
@@ -789,13 +809,13 @@ namespace AzToolsFramework
|
||||
EntityIdManipulators& entityIdManipulators,
|
||||
OptionalFrame& pivotOverrideFrame,
|
||||
ViewportInteraction::KeyboardModifiers& prevModifiers,
|
||||
bool& transformChangedInternally)
|
||||
bool& transformChangedInternally, SpaceCluster spaceCluster)
|
||||
{
|
||||
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzToolsFramework);
|
||||
|
||||
entityIdManipulators.m_manipulators->SetLocalPosition(action.LocalPosition());
|
||||
|
||||
const ReferenceFrame referenceFrame = ReferenceFrameFromModifiers(action.m_modifiers);
|
||||
const ReferenceFrame referenceFrame = spaceCluster.m_spaceLock ? spaceCluster.m_currentSpace : ReferenceFrameFromModifiers(action.m_modifiers);
|
||||
|
||||
if (action.m_modifiers.Ctrl())
|
||||
{
|
||||
@@ -1027,6 +1047,7 @@ namespace AzToolsFramework
|
||||
EditorManipulatorCommandUndoRedoRequestBus::Handler::BusConnect(entityContextId);
|
||||
|
||||
CreateTransformModeSelectionCluster();
|
||||
CreateSpaceSelectionCluster();
|
||||
RegisterActions();
|
||||
SetupBoxSelect();
|
||||
RefreshSelectedEntityIdsAndRegenerateManipulators();
|
||||
@@ -1037,7 +1058,9 @@ namespace AzToolsFramework
|
||||
m_selectedEntityIds.clear();
|
||||
DestroyManipulators(m_entityIdManipulators);
|
||||
|
||||
DestroyTransformModeSelectionCluster(m_transformModeClusterId);
|
||||
DestroyCluster(m_transformModeClusterId);
|
||||
DestroyCluster(m_spaceCluster.m_spaceClusterId);
|
||||
|
||||
UnregisterActions();
|
||||
|
||||
m_pivotOverrideFrame.Reset();
|
||||
@@ -1274,8 +1297,8 @@ namespace AzToolsFramework
|
||||
[this, prevModifiers, manipulatorEntityIds](const LinearManipulator::Action& action) mutable -> void
|
||||
{
|
||||
UpdateTranslationManipulator(
|
||||
action, manipulatorEntityIds->m_entityIds, m_entityIdManipulators,
|
||||
m_pivotOverrideFrame, prevModifiers, m_transformChangedInternally);
|
||||
action, manipulatorEntityIds->m_entityIds, m_entityIdManipulators, m_pivotOverrideFrame, prevModifiers,
|
||||
m_transformChangedInternally, m_spaceCluster);
|
||||
});
|
||||
|
||||
translationManipulators->InstallLinearManipulatorMouseUpCallback(
|
||||
@@ -1305,8 +1328,8 @@ namespace AzToolsFramework
|
||||
[this, prevModifiers, manipulatorEntityIds](const PlanarManipulator::Action& action) mutable -> void
|
||||
{
|
||||
UpdateTranslationManipulator(
|
||||
action, manipulatorEntityIds->m_entityIds, m_entityIdManipulators,
|
||||
m_pivotOverrideFrame, prevModifiers, m_transformChangedInternally);
|
||||
action, manipulatorEntityIds->m_entityIds, m_entityIdManipulators, m_pivotOverrideFrame, prevModifiers,
|
||||
m_transformChangedInternally, m_spaceCluster);
|
||||
});
|
||||
|
||||
translationManipulators->InstallPlanarManipulatorMouseUpCallback(
|
||||
@@ -1335,8 +1358,8 @@ namespace AzToolsFramework
|
||||
[this, prevModifiers, manipulatorEntityIds](const SurfaceManipulator::Action& action) mutable -> void
|
||||
{
|
||||
UpdateTranslationManipulator(
|
||||
action, manipulatorEntityIds->m_entityIds, m_entityIdManipulators,
|
||||
m_pivotOverrideFrame, prevModifiers, m_transformChangedInternally);
|
||||
action, manipulatorEntityIds->m_entityIds, m_entityIdManipulators, m_pivotOverrideFrame, prevModifiers,
|
||||
m_transformChangedInternally, m_spaceCluster);
|
||||
});
|
||||
|
||||
translationManipulators->InstallSurfaceManipulatorMouseUpCallback(
|
||||
@@ -1414,7 +1437,7 @@ namespace AzToolsFramework
|
||||
[this, prevModifiers, sharedRotationState]
|
||||
(const AngularManipulator::Action& action) mutable -> void
|
||||
{
|
||||
const ReferenceFrame referenceFrame = ReferenceFrameFromModifiers(action.m_modifiers);
|
||||
const ReferenceFrame referenceFrame = m_spaceCluster.m_spaceLock ? m_spaceCluster.m_currentSpace : ReferenceFrameFromModifiers(action.m_modifiers);
|
||||
|
||||
const AZ::Quaternion manipulatorOrientation = action.m_start.m_rotation * action.m_current.m_delta;
|
||||
// store the pivot override frame when positioning the manipulator manually (ctrl)
|
||||
@@ -2566,6 +2589,67 @@ namespace AzToolsFramework
|
||||
m_transformModeSelectionHandler);
|
||||
}
|
||||
|
||||
void EditorTransformComponentSelection::CreateSpaceSelectionCluster()
|
||||
{
|
||||
// create the cluster for switching spaces/reference frames
|
||||
ViewportUi::ViewportUiRequestBus::EventResult(
|
||||
m_spaceCluster.m_spaceClusterId, ViewportUi::DefaultViewportId, &ViewportUi::ViewportUiRequestBus::Events::CreateCluster,
|
||||
ViewportUi::Alignment::TopRight);
|
||||
|
||||
// create and register the buttons (strings correspond to icons even if the values appear different)
|
||||
m_spaceCluster.m_worldButtonId = RegisterClusterButton(m_spaceCluster.m_spaceClusterId, "World");
|
||||
m_spaceCluster.m_parentButtonId = RegisterClusterButton(m_spaceCluster.m_spaceClusterId, "Parent");
|
||||
m_spaceCluster.m_localButtonId = RegisterClusterButton(m_spaceCluster.m_spaceClusterId, "Local");
|
||||
|
||||
auto onButtonClicked = [this](ViewportUi::ButtonId buttonId) {
|
||||
if (buttonId == m_spaceCluster.m_localButtonId)
|
||||
{
|
||||
// Unlock
|
||||
if (m_spaceCluster.m_spaceLock && m_spaceCluster.m_currentSpace == ReferenceFrame::Local)
|
||||
{
|
||||
m_spaceCluster.m_spaceLock = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_spaceCluster.m_spaceLock = true;
|
||||
m_spaceCluster.m_currentSpace = ReferenceFrame::Local;
|
||||
}
|
||||
}
|
||||
else if (buttonId == m_spaceCluster.m_parentButtonId)
|
||||
{
|
||||
// Unlock
|
||||
if (m_spaceCluster.m_spaceLock && m_spaceCluster.m_currentSpace == ReferenceFrame::Parent)
|
||||
{
|
||||
m_spaceCluster.m_spaceLock = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_spaceCluster.m_spaceLock = true;
|
||||
m_spaceCluster.m_currentSpace = ReferenceFrame::Parent;
|
||||
}
|
||||
}
|
||||
else if (buttonId == m_spaceCluster.m_worldButtonId)
|
||||
{
|
||||
// Unlock
|
||||
if (m_spaceCluster.m_spaceLock && m_spaceCluster.m_currentSpace == ReferenceFrame::World)
|
||||
{
|
||||
m_spaceCluster.m_spaceLock = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_spaceCluster.m_spaceLock = true;
|
||||
m_spaceCluster.m_currentSpace = ReferenceFrame::World;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
m_spaceCluster.m_spaceSelectionHandler = AZ::Event<ViewportUi::ButtonId>::Handler(onButtonClicked);
|
||||
|
||||
ViewportUi::ViewportUiRequestBus::Event(
|
||||
ViewportUi::DefaultViewportId, &ViewportUi::ViewportUiRequestBus::Events::RegisterClusterEventHandler,
|
||||
m_spaceCluster.m_spaceClusterId, m_spaceCluster.m_spaceSelectionHandler);
|
||||
}
|
||||
|
||||
EditorTransformComponentSelectionRequests::Mode EditorTransformComponentSelection::GetTransformMode()
|
||||
{
|
||||
return m_mode;
|
||||
@@ -3277,7 +3361,10 @@ namespace AzToolsFramework
|
||||
ViewportInteraction::BuildMouseButtons(
|
||||
QGuiApplication::mouseButtons()), m_boxSelect.Active());
|
||||
|
||||
const ReferenceFrame referenceFrame = ReferenceFrameFromModifiers(modifiers);
|
||||
const ReferenceFrame referenceFrame =
|
||||
m_spaceCluster.m_spaceLock ? m_spaceCluster.m_currentSpace : ReferenceFrameFromModifiers(modifiers);
|
||||
|
||||
UpdateSpaceCluster(referenceFrame);
|
||||
|
||||
bool refresh = false;
|
||||
if (referenceFrame != m_referenceFrame)
|
||||
|
||||
+15
@@ -106,6 +106,17 @@ namespace AzToolsFramework
|
||||
World, //!< World space (space aligned to world axes - identity).
|
||||
};
|
||||
|
||||
struct SpaceCluster
|
||||
{
|
||||
ViewportUi::ClusterId m_spaceClusterId;
|
||||
ViewportUi::ButtonId m_localButtonId;
|
||||
ViewportUi::ButtonId m_parentButtonId;
|
||||
ViewportUi::ButtonId m_worldButtonId;
|
||||
AZ::Event<ViewportUi::ButtonId>::Handler m_spaceSelectionHandler;
|
||||
ReferenceFrame m_currentSpace = ReferenceFrame::Parent;
|
||||
bool m_spaceLock = false;
|
||||
};
|
||||
|
||||
//! Entity selection/interaction handling.
|
||||
//! Provide a suite of functionality for manipulating entities, primarily through their TransformComponent.
|
||||
class EditorTransformComponentSelection
|
||||
@@ -160,6 +171,7 @@ namespace AzToolsFramework
|
||||
void RegenerateManipulators();
|
||||
|
||||
void CreateTransformModeSelectionCluster();
|
||||
void CreateSpaceSelectionCluster();
|
||||
|
||||
void ClearManipulatorTranslationOverride();
|
||||
void ClearManipulatorOrientationOverride();
|
||||
@@ -285,6 +297,9 @@ namespace AzToolsFramework
|
||||
AZ::Event<ViewportUi::ButtonId>::Handler m_transformModeSelectionHandler; //!< Event handler for the Viewport UI cluster.
|
||||
AzFramework::ClickDetector m_clickDetector; //!< Detect different types of mouse click.
|
||||
AzFramework::CursorState m_cursorState; //!< Track the mouse position and delta movement each frame.
|
||||
|
||||
SpaceCluster m_spaceCluster;
|
||||
void UpdateSpaceCluster(ReferenceFrame referenceFrame);
|
||||
};
|
||||
|
||||
//! The ETCS (EntityTransformComponentSelection) namespace contains functions and data used exclusively by
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include <AzToolsFramework/Asset/AssetSeedManager.h>
|
||||
#include <AzFramework/Asset/AssetRegistry.h>
|
||||
#include <AzCore/IO/FileIO.h>
|
||||
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
|
||||
#include <AzFramework/IO/LocalFileIO.h>
|
||||
#include <AzFramework/Asset/AssetCatalog.h>
|
||||
#include <AzFramework/StringFunc/StringFunc.h>
|
||||
@@ -62,6 +63,12 @@ namespace UnitTest
|
||||
m_assetSeedManager = new AzToolsFramework::AssetSeedManager();
|
||||
m_assetRegistry = new AzFramework::AssetRegistry();
|
||||
|
||||
AZ::SettingsRegistryInterface* registry = AZ::SettingsRegistry::Get();
|
||||
auto projectPathKey =
|
||||
AZ::SettingsRegistryInterface::FixedValueString(AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey) + "/project_path";
|
||||
registry->Set(projectPathKey, "AutomatedTesting");
|
||||
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(*registry);
|
||||
|
||||
m_application->Start(AzFramework::Application::Descriptor());
|
||||
|
||||
for (int idx = 0; idx < s_totalAssets; idx++)
|
||||
|
||||
@@ -25,6 +25,8 @@ namespace UnitTests
|
||||
MOCK_METHOD0(GetAbsoluteDevGameFolderPath, const char* ());
|
||||
MOCK_METHOD0(GetAbsoluteDevRootFolderPath, const char* ());
|
||||
MOCK_METHOD2(GetRelativeProductPathFromFullSourceOrProductPath, bool(const AZStd::string& fullPath, AZStd::string& relativeProductPath));
|
||||
MOCK_METHOD3(GenerateRelativeSourcePath,
|
||||
bool(const AZStd::string& sourcePath, AZStd::string& relativePath, AZStd::string& watchFolder));
|
||||
MOCK_METHOD2(GetFullSourcePathFromRelativeProductPath, bool(const AZStd::string& relPath, AZStd::string& fullSourcePath));
|
||||
MOCK_METHOD5(GetAssetInfoById, bool(const AZ::Data::AssetId& assetId, const AZ::Data::AssetType& assetType, const AZStd::string& platformName, AZ::Data::AssetInfo& assetInfo, AZStd::string& rootFilePath));
|
||||
MOCK_METHOD3(GetSourceInfoBySourcePath, bool(const char* sourcePath, AZ::Data::AssetInfo& assetInfo, AZStd::string& watchFolder));
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#include <AzCore/Settings/SettingsRegistryImpl.h>
|
||||
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
|
||||
#include <AzCore/UnitTest/TestTypes.h>
|
||||
#include <AzToolsFramework/AssetCatalog/PlatformAddressedAssetCatalogManager.h>
|
||||
#include <AzFramework/Asset/AssetRegistry.h>
|
||||
@@ -49,6 +51,14 @@ namespace UnitTest
|
||||
using namespace AZ::Data;
|
||||
m_application = new ToolsTestApplication("AddressedAssetCatalogManager"); // Shorter name because Setting Registry
|
||||
// specialization are 32 characters max.
|
||||
|
||||
AZ::SettingsRegistryInterface* registry = AZ::SettingsRegistry::Get();
|
||||
|
||||
auto projectPathKey =
|
||||
AZ::SettingsRegistryInterface::FixedValueString(AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey) + "/project_path";
|
||||
registry->Set(projectPathKey, "AutomatedTesting");
|
||||
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(*registry);
|
||||
|
||||
m_application->Start(AzFramework::Application::Descriptor());
|
||||
// Without this, the user settings component would attempt to save on finalize/shutdown. Since the file is
|
||||
// shared across the whole engine, if multiple tests are run in parallel, the saving could cause a crash
|
||||
|
||||
+3
@@ -149,6 +149,9 @@ namespace UnitTest
|
||||
const char* GetAbsoluteDevGameFolderPath() override { return ""; }
|
||||
const char* GetAbsoluteDevRootFolderPath() override { return ""; }
|
||||
bool GetRelativeProductPathFromFullSourceOrProductPath([[maybe_unused]] const AZStd::string& fullPath, [[maybe_unused]] AZStd::string& relativeProductPath) override { return false; }
|
||||
bool GenerateRelativeSourcePath(
|
||||
[[maybe_unused]] const AZStd::string& sourcePath, [[maybe_unused]] AZStd::string& relativePath,
|
||||
[[maybe_unused]] AZStd::string& watchFolder) override { return false; }
|
||||
bool GetFullSourcePathFromRelativeProductPath([[maybe_unused]] const AZStd::string& relPath, [[maybe_unused]] AZStd::string& fullSourcePath) override { return false; }
|
||||
bool GetAssetInfoById([[maybe_unused]] const AZ::Data::AssetId& assetId, [[maybe_unused]] const AZ::Data::AssetType& assetType, [[maybe_unused]] const AZStd::string& platformName, [[maybe_unused]] AZ::Data::AssetInfo& assetInfo, [[maybe_unused]] AZStd::string& rootFilePath) override { return false; }
|
||||
bool GetSourceInfoBySourcePath(const char* sourcePath, AZ::Data::AssetInfo& assetInfo, AZStd::string& watchFolder) override;
|
||||
|
||||
@@ -488,18 +488,17 @@ void TransformCompressor::Marshal(WriteBuffer& wb, const AZ::Transform& value) c
|
||||
{
|
||||
AZ::u8 flags = 0;
|
||||
auto flagsMarker = wb.InsertMarker(flags);
|
||||
AZ::Matrix3x3 m33 = AZ::Matrix3x3::CreateFromTransform(value);
|
||||
AZ::Vector3 scale = m33.ExtractScale();
|
||||
AZ::Quaternion rot = AZ::Quaternion::CreateFromMatrix3x3(m33.GetOrthogonalized());
|
||||
float scale = value.GetUniformScale();
|
||||
AZ::Quaternion rot = value.GetRotation();
|
||||
if (!rot.IsIdentity())
|
||||
{
|
||||
flags |= HAS_ROT;
|
||||
wb.Write(rot, QuatCompMarshaler());
|
||||
}
|
||||
if (!scale.IsClose(AZ::Vector3::CreateOne()))
|
||||
if (!AZ::IsClose(scale, 1.0f, AZ::Constants::Tolerance))
|
||||
{
|
||||
flags |= HAS_SCALE;
|
||||
wb.Write(scale, Vec3CompMarshaler());
|
||||
wb.Write(scale, HalfMarshaler());
|
||||
}
|
||||
AZ::Vector3 pos = value.GetTranslation();
|
||||
if (!pos.IsZero())
|
||||
@@ -527,9 +526,9 @@ void TransformCompressor::Unmarshal(AZ::Transform& value, ReadBuffer& rb) const
|
||||
}
|
||||
if (flags & HAS_SCALE)
|
||||
{
|
||||
AZ::Vector3 scale;
|
||||
rb.Read(scale, Vec3CompMarshaler());
|
||||
xform.MultiplyByScale(scale);
|
||||
float scale;
|
||||
rb.Read(scale, HalfMarshaler());
|
||||
xform.MultiplyByUniformScale(scale);
|
||||
}
|
||||
if (flags & HAS_POS)
|
||||
{
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
*/
|
||||
|
||||
#include <AzTest/AzTest.h>
|
||||
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
|
||||
#include <AzCore/UnitTest/TestTypes.h>
|
||||
#include <AzCore/UnitTest/UnitTest.h>
|
||||
#include <AzCore/UserSettings/UserSettingsComponent.h>
|
||||
@@ -40,6 +41,13 @@ namespace UnitTest
|
||||
|
||||
void SetUp() override
|
||||
{
|
||||
AZ::SettingsRegistryInterface* registry = AZ::SettingsRegistry::Get();
|
||||
|
||||
auto projectPathKey =
|
||||
AZ::SettingsRegistryInterface::FixedValueString(AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey) + "/project_path";
|
||||
registry->Set(projectPathKey, "AutomatedTesting");
|
||||
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(*registry);
|
||||
|
||||
m_application->Start({});
|
||||
// Without this, the user settings component would attempt to save on finalize/shutdown. Since the file is
|
||||
// shared across the whole engine, if multiple tests are run in parallel, the saving could cause a crash
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include <AzCore/UnitTest/UnitTest.h>
|
||||
|
||||
#include <AzCore/IO/SystemFile.h> // for max path decl
|
||||
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
|
||||
#include <AzCore/std/parallel/thread.h>
|
||||
#include <AzCore/std/parallel/semaphore.h>
|
||||
#include <AzCore/std/functional.h> // for function<> in the find files callback.
|
||||
@@ -42,6 +43,14 @@ namespace UnitTest
|
||||
{
|
||||
AZ::ComponentApplication::Descriptor descriptor;
|
||||
descriptor.m_stackRecordLevels = 30;
|
||||
|
||||
AZ::SettingsRegistryInterface* registry = AZ::SettingsRegistry::Get();
|
||||
|
||||
auto projectPathKey =
|
||||
AZ::SettingsRegistryInterface::FixedValueString(AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey) + "/project_path";
|
||||
registry->Set(projectPathKey, "AutomatedTesting");
|
||||
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(*registry);
|
||||
|
||||
m_application->Start(descriptor);
|
||||
// Without this, the user settings component would attempt to save on finalize/shutdown. Since the file is
|
||||
// shared across the whole engine, if multiple tests are run in parallel, the saving could cause a crash
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include <AzCore/Math/Uuid.h>
|
||||
#include <AzCore/Memory/Memory.h>
|
||||
#include <AzCore/Memory/PoolAllocator.h>
|
||||
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
|
||||
#include <AzCore/std/containers/vector.h>
|
||||
#include <AzCore/UnitTest/TestTypes.h>
|
||||
#include <AzCore/UserSettings/UserSettingsComponent.h>
|
||||
@@ -304,6 +305,13 @@ namespace UnitTest
|
||||
m_app.reset(aznew AzFramework::Application());
|
||||
AZ::ComponentApplication::Descriptor desc;
|
||||
desc.m_useExistingAllocator = true;
|
||||
|
||||
AZ::SettingsRegistryInterface* registry = AZ::SettingsRegistry::Get();
|
||||
auto projectPathKey =
|
||||
AZ::SettingsRegistryInterface::FixedValueString(AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey) + "/project_path";
|
||||
registry->Set(projectPathKey, "AutomatedTesting");
|
||||
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(*registry);
|
||||
|
||||
m_app->Start(desc);
|
||||
|
||||
// Without this, the user settings component would attempt to save on finalize/shutdown. Since the file is
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
|
||||
#include <AzCore/Component/ComponentApplicationBus.h>
|
||||
#include <AzCore/Outcome/Outcome.h>
|
||||
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
|
||||
#include <AzCore/UnitTest/TestTypes.h>
|
||||
#include <AzCore/UserSettings/UserSettingsComponent.h>
|
||||
|
||||
@@ -572,6 +573,12 @@ namespace UnitTest
|
||||
{
|
||||
AllocatorsTestFixture::SetUp();
|
||||
|
||||
AZ::SettingsRegistryInterface* registry = AZ::SettingsRegistry::Get();
|
||||
auto projectPathKey =
|
||||
AZ::SettingsRegistryInterface::FixedValueString(AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey) + "/project_path";
|
||||
registry->Set(projectPathKey, "AutomatedTesting");
|
||||
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(*registry);
|
||||
|
||||
AzFramework::Application::Descriptor descriptor;
|
||||
descriptor.m_enableDrilling = false;
|
||||
m_app.Start(descriptor);
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include <AzCore/Serialization/Json/JsonSerialization.h>
|
||||
#include <AzCore/Serialization/Json/JsonSystemComponent.h>
|
||||
#include <AzCore/Serialization/Json/RegistrationContext.h>
|
||||
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
|
||||
#include <AzCore/UnitTest/TestTypes.h>
|
||||
#include <AzCore/Utils/Utils.h>
|
||||
#include <AzFramework/FileFunc/FileFunc.h>
|
||||
@@ -278,6 +279,12 @@ namespace UnitTest
|
||||
{
|
||||
FrameworkApplicationFixture::SetUp();
|
||||
|
||||
AZ::SettingsRegistryInterface* registry = AZ::SettingsRegistry::Get();
|
||||
auto projectPathKey =
|
||||
AZ::SettingsRegistryInterface::FixedValueString(AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey) + "/project_path";
|
||||
registry->Set(projectPathKey, "AutomatedTesting");
|
||||
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(*registry);
|
||||
|
||||
m_serializeContext = AZStd::make_unique<AZ::SerializeContext>();
|
||||
m_jsonRegistrationContext = AZStd::make_unique<AZ::JsonRegistrationContext>();
|
||||
m_jsonSystemComponent = AZStd::make_unique<AZ::JsonSystemComponent>();
|
||||
|
||||
@@ -83,6 +83,7 @@ namespace UnitTest
|
||||
void SetUp() override
|
||||
{
|
||||
AllocatorsFixture::SetUp();
|
||||
|
||||
m_data = AZStd::make_unique<StaticData>();
|
||||
using namespace AzFramework::FileTag;
|
||||
AZ::ComponentApplication::Descriptor desc;
|
||||
|
||||
@@ -54,7 +54,7 @@ namespace UnitTest
|
||||
};
|
||||
|
||||
void SetUp() override
|
||||
{
|
||||
{
|
||||
m_appDescriptor.m_allocationRecords = true;
|
||||
m_appDescriptor.m_allocationRecordsSaveNames = true;
|
||||
m_appDescriptor.m_recordingMode = AZ::Debug::AllocationRecords::Mode::RECORD_FULL;
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include <AzTest/AzTest.h>
|
||||
#include <AzCore/Slice/SliceComponent.h>
|
||||
#include <AzCore/Serialization/Utils.h>
|
||||
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
|
||||
#include <AzCore/UserSettings/UserSettingsComponent.h>
|
||||
#include <AzToolsFramework/ToolsComponents/GenericComponentWrapper.h>
|
||||
#include <AzToolsFramework/Application/ToolsApplication.h>
|
||||
@@ -59,6 +60,12 @@ class WrappedEditorComponentTest
|
||||
protected:
|
||||
void SetUp() override
|
||||
{
|
||||
AZ::SettingsRegistryInterface* registry = AZ::SettingsRegistry::Get();
|
||||
auto projectPathKey =
|
||||
AZ::SettingsRegistryInterface::FixedValueString(AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey) + "/project_path";
|
||||
registry->Set(projectPathKey, "AutomatedTesting");
|
||||
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(*registry);
|
||||
|
||||
m_app.Start(AZ::ComponentApplication::Descriptor());
|
||||
|
||||
// Without this, the user settings component would attempt to save on finalize/shutdown. Since the file is
|
||||
@@ -178,6 +185,12 @@ class FindWrappedComponentsTest
|
||||
public:
|
||||
void SetUp() override
|
||||
{
|
||||
AZ::SettingsRegistryInterface* registry = AZ::SettingsRegistry::Get();
|
||||
auto projectPathKey =
|
||||
AZ::SettingsRegistryInterface::FixedValueString(AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey) + "/project_path";
|
||||
registry->Set(projectPathKey, "AutomatedTesting");
|
||||
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(*registry);
|
||||
|
||||
m_app.Start(AzFramework::Application::Descriptor());
|
||||
|
||||
// Without this, the user settings component would attempt to save on finalize/shutdown. Since the file is
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include <AzCore/Slice/SliceAssetHandler.h>
|
||||
#include <AzCore/Script/ScriptSystemComponent.h>
|
||||
#include <AzCore/Script/ScriptAsset.h>
|
||||
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
|
||||
#include <AzCore/std/chrono/chrono.h>
|
||||
#include <AzCore/RTTI/TypeInfo.h>
|
||||
#include <AzCore/UnitTest/TestTypes.h>
|
||||
@@ -1059,6 +1060,12 @@ namespace UnitTest
|
||||
|
||||
void SetUp() override
|
||||
{
|
||||
AZ::SettingsRegistryInterface* registry = AZ::SettingsRegistry::Get();
|
||||
auto projectPathKey =
|
||||
AZ::SettingsRegistryInterface::FixedValueString(AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey) + "/project_path";
|
||||
registry->Set(projectPathKey, "AutomatedTesting");
|
||||
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(*registry);
|
||||
|
||||
m_app.Start(AzFramework::Application::Descriptor());
|
||||
|
||||
// Without this, the user settings component would attempt to save on finalize/shutdown. Since the file is
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
/*
|
||||
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
* its licensors.
|
||||
*
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this
|
||||
* distribution (the "License"). All use of this software is governed by the License,
|
||||
* or, if provided, by the license below or the license accompanying this file. Do not
|
||||
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <AzCore/UnitTest/TestTypes.h>
|
||||
#include <AzFramework/Application/Application.h>
|
||||
#include <AzFramework/Spawnable/SpawnableAssetHandler.h>
|
||||
#include <AzFramework/Spawnable/SpawnableEntitiesManager.h>
|
||||
#include <AzTest/AzTest.h>
|
||||
|
||||
namespace UnitTest
|
||||
{
|
||||
class TestApplication : public AzFramework::Application
|
||||
{
|
||||
public:
|
||||
// ComponentApplication
|
||||
void SetSettingsRegistrySpecializations(AZ::SettingsRegistryInterface::Specializations& specializations) override
|
||||
{
|
||||
Application::SetSettingsRegistrySpecializations(specializations);
|
||||
specializations.Append("test");
|
||||
specializations.Append("spawnable");
|
||||
}
|
||||
};
|
||||
|
||||
class SpawnableEntitiesManagerTest : public AllocatorsFixture
|
||||
{
|
||||
public:
|
||||
void SetUp() override
|
||||
{
|
||||
AllocatorsFixture::SetUp();
|
||||
|
||||
m_application = new TestApplication();
|
||||
AZ::ComponentApplication::Descriptor descriptor;
|
||||
m_application->Start(descriptor);
|
||||
|
||||
m_spawnable = aznew AzFramework::Spawnable(
|
||||
AZ::Data::AssetId::CreateString("{EB2E8A2B-F253-4A90-BBF4-55F2EED786B8}:0"), AZ::Data::AssetData::AssetStatus::Ready);
|
||||
m_spawnableAsset = new AZ::Data::Asset<AzFramework::Spawnable>(m_spawnable, AZ::Data::AssetLoadBehavior::Default);
|
||||
m_ticket = new AzFramework::EntitySpawnTicket(*m_spawnableAsset);
|
||||
|
||||
auto managerInterface = AzFramework::SpawnableEntitiesInterface::Get();
|
||||
m_manager = azrtti_cast<AzFramework::SpawnableEntitiesManager*>(managerInterface);
|
||||
}
|
||||
|
||||
void TearDown() override
|
||||
{
|
||||
delete m_ticket;
|
||||
m_ticket = nullptr;
|
||||
// One more tick on the spawnable entities manager in order to delete the ticket fully.
|
||||
m_manager->ProcessQueue();
|
||||
|
||||
delete m_spawnableAsset;
|
||||
m_spawnableAsset = nullptr;
|
||||
// This will also delete m_spawnable.
|
||||
|
||||
delete m_application;
|
||||
m_application = nullptr;
|
||||
|
||||
AllocatorsFixture::TearDown();
|
||||
}
|
||||
|
||||
void FillSpawnable(size_t numElements)
|
||||
{
|
||||
AzFramework::Spawnable::EntityList& entities = m_spawnable->GetEntities();
|
||||
entities.reserve(numElements);
|
||||
for (size_t i=0; i<numElements; ++i)
|
||||
{
|
||||
entities.push_back(AZStd::make_unique<AZ::Entity>());
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
AZ::Data::Asset<AzFramework::Spawnable>* m_spawnableAsset { nullptr };
|
||||
AzFramework::SpawnableEntitiesManager* m_manager { nullptr };
|
||||
AzFramework::EntitySpawnTicket* m_ticket { nullptr };
|
||||
AzFramework::Spawnable* m_spawnable { nullptr };
|
||||
TestApplication* m_application { nullptr };
|
||||
};
|
||||
|
||||
TEST_F(SpawnableEntitiesManagerTest, SpawnAllEntities_Call_AllEntitiesSpawned)
|
||||
{
|
||||
static constexpr size_t NumEntities = 4;
|
||||
FillSpawnable(NumEntities);
|
||||
|
||||
size_t spawnedEntitiesCount = 0;
|
||||
auto callback =
|
||||
[&spawnedEntitiesCount](AzFramework::EntitySpawnTicket&, AzFramework::SpawnableConstEntityContainerView entities)
|
||||
{
|
||||
spawnedEntitiesCount += entities.size();
|
||||
};
|
||||
m_manager->SpawnAllEntities(*m_ticket, {}, AZStd::move(callback));
|
||||
m_manager->ProcessQueue();
|
||||
|
||||
EXPECT_EQ(NumEntities, spawnedEntitiesCount);
|
||||
}
|
||||
|
||||
TEST_F(SpawnableEntitiesManagerTest, ListEntities_Call_AllEntitiesAreReported)
|
||||
{
|
||||
static constexpr size_t NumEntities = 4;
|
||||
FillSpawnable(NumEntities);
|
||||
|
||||
bool allValidEntityIds = true;
|
||||
size_t spawnedEntitiesCount = 0;
|
||||
auto callback = [&allValidEntityIds, &spawnedEntitiesCount]
|
||||
(AzFramework::EntitySpawnTicket&, AzFramework::SpawnableConstEntityContainerView entities)
|
||||
{
|
||||
for (auto&& entity : entities)
|
||||
{
|
||||
allValidEntityIds = entity->GetId().IsValid() && allValidEntityIds;
|
||||
}
|
||||
spawnedEntitiesCount += entities.size();
|
||||
};
|
||||
|
||||
m_manager->SpawnAllEntities(*m_ticket);
|
||||
m_manager->ListEntities(*m_ticket, AZStd::move(callback));
|
||||
m_manager->ProcessQueue();
|
||||
|
||||
EXPECT_TRUE(allValidEntityIds);
|
||||
EXPECT_EQ(NumEntities, spawnedEntitiesCount);
|
||||
}
|
||||
|
||||
TEST_F(SpawnableEntitiesManagerTest, ListIndicesAndEntities_Call_AllEntitiesAreReportedAndIncrementByOne)
|
||||
{
|
||||
static constexpr size_t NumEntities = 4;
|
||||
FillSpawnable(NumEntities);
|
||||
|
||||
bool allValidEntityIds = true;
|
||||
size_t spawnedEntitiesCount = 0;
|
||||
auto callback = [&allValidEntityIds, &spawnedEntitiesCount]
|
||||
(AzFramework::EntitySpawnTicket&, AzFramework::SpawnableConstIndexEntityContainerView entities)
|
||||
{
|
||||
for (auto&& indexEntityPair : entities)
|
||||
{
|
||||
// Since all entities are spawned a single time, the indices should be 0..NumEntities.
|
||||
if (indexEntityPair.GetIndex() == spawnedEntitiesCount)
|
||||
{
|
||||
spawnedEntitiesCount++;
|
||||
}
|
||||
allValidEntityIds = indexEntityPair.GetEntity()->GetId().IsValid() && allValidEntityIds;
|
||||
}
|
||||
};
|
||||
|
||||
m_manager->SpawnAllEntities(*m_ticket);
|
||||
m_manager->ListIndicesAndEntities(*m_ticket, AZStd::move(callback));
|
||||
m_manager->ProcessQueue();
|
||||
|
||||
EXPECT_TRUE(allValidEntityIds);
|
||||
EXPECT_EQ(NumEntities, spawnedEntitiesCount);
|
||||
}
|
||||
} // namespace UnitTest
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
set(FILES
|
||||
../AzCore/Tests/Main.cpp
|
||||
Spawnable/SpawnableEntitiesManagerTests.cpp
|
||||
ArchiveCompressionTests.cpp
|
||||
ArchiveTests.cpp
|
||||
BehaviorEntityTests.cpp
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
#
|
||||
|
||||
|
||||
set_property(GLOBAL PROPERTY LAUNCHER_UNIFIED_BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR})
|
||||
# Launcher targets for a project need to be generated when configuring a project.
|
||||
# When building the engine source, this file will be included by LauncherUnified's CMakeLists.txt
|
||||
# When using an installed engine, this file will be included by the FindLauncherGenerator.cmake script
|
||||
@@ -40,28 +42,8 @@ foreach(project_name project_path IN ZIP_LISTS LY_PROJECTS_TARGET_NAME LY_PROJEC
|
||||
|
||||
# In the monolithic case, we need to register the gem modules, to do so we will generate a StaticModules.inl
|
||||
# file from StaticModules.in
|
||||
|
||||
set_property(GLOBAL APPEND PROPERTY LY_STATIC_MODULE_PROJECTS_NAME ${project_name})
|
||||
get_property(game_gem_dependencies GLOBAL PROPERTY LY_DELAYED_DEPENDENCIES_${project_name}.GameLauncher)
|
||||
|
||||
unset(extern_module_declarations)
|
||||
unset(module_invocations)
|
||||
|
||||
foreach(game_gem_dependency ${game_gem_dependencies})
|
||||
# To match the convention on how gems targets vs gem modules are named, we remove the "Gem::" from prefix
|
||||
# and remove the ".Static" from the suffix
|
||||
string(REGEX REPLACE "^Gem::" "Gem_" game_gem_dependency ${game_gem_dependency})
|
||||
string(REGEX REPLACE "^Project::" "Project_" game_gem_dependency ${game_gem_dependency})
|
||||
# Replace "." with "_"
|
||||
string(REPLACE "." "_" game_gem_dependency ${game_gem_dependency})
|
||||
|
||||
string(APPEND extern_module_declarations "extern \"C\" AZ::Module* CreateModuleClass_${game_gem_dependency}();\n")
|
||||
string(APPEND module_invocations " modulesOut.push_back(CreateModuleClass_${game_gem_dependency}());\n")
|
||||
|
||||
endforeach()
|
||||
|
||||
configure_file(StaticModules.in
|
||||
${CMAKE_CURRENT_BINARY_DIR}/${project_name}.GameLauncher/Includes/StaticModules.inl
|
||||
)
|
||||
|
||||
set(game_build_dependencies
|
||||
${game_gem_dependencies}
|
||||
@@ -70,29 +52,9 @@ foreach(project_name project_path IN ZIP_LISTS LY_PROJECTS_TARGET_NAME LY_PROJEC
|
||||
|
||||
if(PAL_TRAIT_BUILD_SERVER_SUPPORTED)
|
||||
get_property(server_gem_dependencies GLOBAL PROPERTY LY_DELAYED_DEPENDENCIES_${project_name}.ServerLauncher)
|
||||
|
||||
unset(extern_module_declarations)
|
||||
unset(module_invocations)
|
||||
|
||||
foreach(server_gem_dependency ${server_gem_dependencies})
|
||||
# To match the convention on how gems targets vs gem modules are named, we remove the "Gem::" from prefix
|
||||
# and remove the ".Static" from the suffix
|
||||
string(REGEX REPLACE "^Gem::" "Gem_" server_gem_dependency ${server_gem_dependency})
|
||||
string(REGEX REPLACE "^Project::" "Project_" server_gem_dependency ${server_gem_dependency})
|
||||
# Replace "." with "_"
|
||||
string(REPLACE "." "_" server_gem_dependency ${server_gem_dependency})
|
||||
|
||||
string(APPEND extern_module_declarations "extern \"C\" AZ::Module* CreateModuleClass_${server_gem_dependency}();\n")
|
||||
string(APPEND module_invocations " modulesOut.push_back(CreateModuleClass_${server_gem_dependency}());\n")
|
||||
|
||||
endforeach()
|
||||
|
||||
configure_file(StaticModules.in
|
||||
${CMAKE_CURRENT_BINARY_DIR}/${project_name}.ServerLauncher/Includes/StaticModules.inl
|
||||
)
|
||||
|
||||
set(server_build_dependencies
|
||||
${game_gem_dependencies}
|
||||
${server_gem_dependencies}
|
||||
Legacy::CrySystem
|
||||
)
|
||||
endif()
|
||||
@@ -186,3 +148,63 @@ foreach(project_name project_path IN ZIP_LISTS LY_PROJECTS_TARGET_NAME LY_PROJEC
|
||||
endif()
|
||||
|
||||
endforeach()
|
||||
|
||||
#! Defer generation of the StaticModules.inl file needed in monolithic builds until after all the CMake targets are known
|
||||
# This is that the GEM_MODULE target runtime dependencies can be parsed to discover the list of dependent modules
|
||||
# to load
|
||||
function(ly_delayed_generate_static_modules_inl)
|
||||
if(LY_MONOLITHIC_GAME)
|
||||
get_property(launcher_unified_binary_dir GLOBAL PROPERTY LAUNCHER_UNIFIED_BINARY_DIR)
|
||||
get_property(project_names GLOBAL PROPERTY LY_STATIC_MODULE_PROJECTS_NAME)
|
||||
foreach(project_name ${project_names})
|
||||
|
||||
unset(extern_module_declarations)
|
||||
unset(module_invocations)
|
||||
|
||||
unset(all_game_gem_dependencies)
|
||||
ly_get_gem_load_dependencies(all_game_gem_dependencies ${project_name}.GameLauncher)
|
||||
|
||||
foreach(game_gem_dependency ${all_game_gem_dependencies})
|
||||
# To match the convention on how gems targets vs gem modules are named,
|
||||
# we remove the ".Static" from the suffix
|
||||
# Replace "." with "_"
|
||||
string(REPLACE "." "_" game_gem_dependency ${game_gem_dependency})
|
||||
|
||||
string(APPEND extern_module_declarations "extern \"C\" AZ::Module* CreateModuleClass_Gem_${game_gem_dependency}();\n")
|
||||
string(APPEND module_invocations " modulesOut.push_back(CreateModuleClass_Gem_${game_gem_dependency}());\n")
|
||||
|
||||
endforeach()
|
||||
|
||||
configure_file(${CMAKE_CURRENT_FUNCTION_LIST_DIR}/StaticModules.in
|
||||
${launcher_unified_binary_dir}/${project_name}.GameLauncher/Includes/StaticModules.inl
|
||||
)
|
||||
|
||||
if(PAL_TRAIT_BUILD_SERVER_SUPPORTED)
|
||||
get_property(server_gem_dependencies GLOBAL PROPERTY LY_STATIC_MODULE_PROJECTS_DEPENDENCIES_${project_name}.ServerLauncher)
|
||||
|
||||
unset(extern_module_declarations)
|
||||
unset(module_invocations)
|
||||
|
||||
unset(all_server_gem_dependencies)
|
||||
ly_get_gem_load_dependencies(all_server_gem_dependencies ${project_name}.ServerLauncher)
|
||||
foreach(server_gem_dependency ${server_gem_dependencies})
|
||||
ly_get_gem_load_dependencies(server_gem_load_dependencies ${server_gem_dependency})
|
||||
list(APPEND all_server_gem_dependencies ${server_gem_load_dependencies} ${server_gem_dependency})
|
||||
endforeach()
|
||||
foreach(server_gem_dependency ${all_server_gem_dependencies})
|
||||
# Replace "." with "_"
|
||||
string(REPLACE "." "_" server_gem_dependency ${server_gem_dependency})
|
||||
|
||||
string(APPEND extern_module_declarations "extern \"C\" AZ::Module* CreateModuleClass_Gem_${server_gem_dependency}();\n")
|
||||
string(APPEND module_invocations " modulesOut.push_back(CreateModuleClass_Gem_${server_gem_dependency}());\n")
|
||||
|
||||
endforeach()
|
||||
|
||||
configure_file(${CMAKE_CURRENT_FUNCTION_LIST_DIR}/StaticModules.in
|
||||
${launcher_unified_binary_dir}/${project_name}.ServerLauncher/Includes/StaticModules.inl
|
||||
)
|
||||
|
||||
endif()
|
||||
endforeach()
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include <AzToolsFramework/Application/ToolsApplication.h>
|
||||
#include <AzToolsFramework/Asset/AssetBundler.h>
|
||||
#include <AzCore/IO/Path/Path.h>
|
||||
#include <AzCore/Settings/SettingsRegistryImpl.h>
|
||||
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
|
||||
#include <AzCore/UserSettings/UserSettingsComponent.h>
|
||||
|
||||
@@ -57,6 +58,22 @@ namespace AssetBundler
|
||||
UnitTest::ScopedAllocatorSetupFixture::SetUp();
|
||||
m_data = AZStd::make_unique<StaticData>();
|
||||
|
||||
AZ::SettingsRegistryInterface* registry = nullptr;
|
||||
if (!AZ::SettingsRegistry::Get())
|
||||
{
|
||||
AZ::SettingsRegistry::Register(&m_registry);
|
||||
registry = &m_registry;
|
||||
}
|
||||
else
|
||||
{
|
||||
registry = AZ::SettingsRegistry::Get();
|
||||
}
|
||||
auto projectPathKey = AZ::SettingsRegistryInterface::FixedValueString(AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey)
|
||||
+ "/project_path";
|
||||
registry->Set(projectPathKey, "AutomatedTesting");
|
||||
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(*registry);
|
||||
|
||||
|
||||
m_data->m_applicationManager.reset(aznew MockApplicationManagerTest(0, 0));
|
||||
m_data->m_applicationManager->Start(AzFramework::Application::Descriptor());
|
||||
|
||||
@@ -84,6 +101,12 @@ namespace AssetBundler
|
||||
delete m_data->m_localFileIO;
|
||||
AZ::IO::FileIOBase::SetInstance(m_data->m_priorFileIO);
|
||||
|
||||
auto settingsRegistry = AZ::SettingsRegistry::Get();
|
||||
if(settingsRegistry == &m_registry)
|
||||
{
|
||||
AZ::SettingsRegistry::Unregister(settingsRegistry);
|
||||
}
|
||||
|
||||
m_data->m_applicationManager->Stop();
|
||||
m_data->m_applicationManager.reset();
|
||||
m_data.reset();
|
||||
@@ -99,6 +122,7 @@ namespace AssetBundler
|
||||
};
|
||||
|
||||
AZStd::unique_ptr<StaticData> m_data;
|
||||
AZ::SettingsRegistryImpl m_registry;
|
||||
};
|
||||
|
||||
TEST_F(ApplicationManagerTest, ValidatePlatformFlags_ReadConfigFiles_OK)
|
||||
|
||||
@@ -97,7 +97,28 @@ namespace AssetBundler
|
||||
{
|
||||
public:
|
||||
void SetUp() override
|
||||
{
|
||||
{
|
||||
AZ::SettingsRegistryInterface* registry = nullptr;
|
||||
if (!AZ::SettingsRegistry::Get())
|
||||
{
|
||||
AZ::SettingsRegistry::Register(&m_registry);
|
||||
registry = &m_registry;
|
||||
}
|
||||
else
|
||||
{
|
||||
registry = AZ::SettingsRegistry::Get();
|
||||
}
|
||||
auto projectPathKey = AZ::SettingsRegistryInterface::FixedValueString(AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey)
|
||||
+ "/project_path";
|
||||
registry->Set(projectPathKey, "AutomatedTesting");
|
||||
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(*registry);
|
||||
|
||||
AZ::IO::FixedMaxPath engineRoot = AZ::Utils::GetEnginePath();
|
||||
if (engineRoot.empty())
|
||||
{
|
||||
GTEST_FATAL_FAILURE_(AZStd::string::format("Unable to locate engine root.\n").c_str());
|
||||
}
|
||||
|
||||
m_data = AZStd::make_unique<StaticData>();
|
||||
m_data->m_application.reset(aznew AzToolsFramework::ToolsApplication());
|
||||
m_data->m_application.get()->Start(AzFramework::Application::Descriptor());
|
||||
@@ -107,19 +128,6 @@ namespace AssetBundler
|
||||
// in the unit tests.
|
||||
AZ::UserSettingsComponentRequestBus::Broadcast(&AZ::UserSettingsComponentRequests::DisableSaveOnFinalize);
|
||||
|
||||
if (!AZ::SettingsRegistry::Get())
|
||||
{
|
||||
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_Bootstrap(m_registry);
|
||||
AZ::SettingsRegistry::Register(&m_registry);
|
||||
}
|
||||
|
||||
AZ::IO::FixedMaxPath engineRoot = AZ::Utils::GetEnginePath();
|
||||
if (engineRoot.empty())
|
||||
{
|
||||
GTEST_FATAL_FAILURE_(AZStd::string::format("Unable to locate engine root.\n").c_str());
|
||||
}
|
||||
|
||||
|
||||
m_data->m_testEngineRoot = (engineRoot / RelativeTestFolder).LexicallyNormal().String();
|
||||
|
||||
m_data->m_localFileIO = aznew AZ::IO::LocalFileIO();
|
||||
@@ -144,14 +152,24 @@ namespace AssetBundler
|
||||
}
|
||||
void TearDown() override
|
||||
{
|
||||
AZ::IO::FileIOBase::SetInstance(nullptr);
|
||||
delete m_data->m_localFileIO;
|
||||
AZ::IO::FileIOBase::SetInstance(m_data->m_priorFileIO);
|
||||
if (m_data)
|
||||
{
|
||||
AZ::IO::FileIOBase::SetInstance(nullptr);
|
||||
delete m_data->m_localFileIO;
|
||||
AZ::IO::FileIOBase::SetInstance(m_data->m_priorFileIO);
|
||||
|
||||
m_data->m_gemInfoList.set_capacity(0);
|
||||
m_data->m_gemSeedFilePairList.set_capacity(0);
|
||||
m_data->m_application.get()->Stop();
|
||||
m_data->m_application.reset();
|
||||
}
|
||||
|
||||
if(auto settingsRegistry = AZ::SettingsRegistry::Get();
|
||||
settingsRegistry == &m_registry)
|
||||
{
|
||||
AZ::SettingsRegistry::Unregister(settingsRegistry);
|
||||
}
|
||||
|
||||
m_data->m_gemInfoList.set_capacity(0);
|
||||
m_data->m_gemSeedFilePairList.set_capacity(0);
|
||||
m_data->m_application.get()->Stop();
|
||||
m_data->m_application.reset();
|
||||
}
|
||||
|
||||
void AddGemData(const char* engineRoot, const char* gemName, bool seedFileExists = true)
|
||||
|
||||
@@ -655,6 +655,80 @@ namespace AssetProcessor
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AssetCatalog::GenerateRelativeSourcePath(
|
||||
const AZStd::string& sourcePath, AZStd::string& relativePath, AZStd::string& rootFolder)
|
||||
{
|
||||
QString normalizedSourcePath = AssetUtilities::NormalizeFilePath(sourcePath.c_str());
|
||||
QDir inputPath(normalizedSourcePath);
|
||||
QString scanFolder;
|
||||
QString relativeName;
|
||||
|
||||
bool validResult = false;
|
||||
|
||||
AZ_TracePrintf(AssetProcessor::DebugChannel, "ProcessGenerateRelativeSourcePathRequest: %s...\n", sourcePath.c_str());
|
||||
|
||||
if (sourcePath.empty())
|
||||
{
|
||||
// For an empty input path, do nothing, we'll return an empty, invalid result.
|
||||
// (We check fullPath instead of inputPath, because an empty fullPath actually produces "." for inputPath)
|
||||
}
|
||||
else if (inputPath.isAbsolute())
|
||||
{
|
||||
// For an absolute path, try to convert it to a relative path, based on the existing scan folders.
|
||||
// To get the inputPath, we use absolutePath() instead of path() so that any . or .. entries get collapsed.
|
||||
validResult = m_platformConfig->ConvertToRelativePath(inputPath.absolutePath(), relativeName, scanFolder);
|
||||
}
|
||||
else if (inputPath.isRelative())
|
||||
{
|
||||
// For a relative path, concatenate it with each scan folder, and see if a valid relative path emerges.
|
||||
int scanFolders = m_platformConfig->GetScanFolderCount();
|
||||
for (int scanIdx = 0; scanIdx < scanFolders; scanIdx++)
|
||||
{
|
||||
auto& scanInfo = m_platformConfig->GetScanFolderAt(scanIdx);
|
||||
QDir possibleRoot(scanInfo.ScanPath());
|
||||
QDir possibleAbsolutePath = possibleRoot.filePath(normalizedSourcePath);
|
||||
// To get the inputPath, we use absolutePath() instead of path() so that any . or .. entries get collapsed.
|
||||
if (m_platformConfig->ConvertToRelativePath(possibleAbsolutePath.absolutePath(), relativeName, scanFolder))
|
||||
{
|
||||
validResult = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The input has produced a valid relative path. However, the path might match multiple nested scan folders,
|
||||
// so look to see if a higher-priority folder has a better match.
|
||||
if (validResult)
|
||||
{
|
||||
QString overridingFile = m_platformConfig->GetOverridingFile(relativeName, scanFolder);
|
||||
|
||||
if (!overridingFile.isEmpty())
|
||||
{
|
||||
overridingFile = AssetUtilities::NormalizeFilePath(overridingFile);
|
||||
validResult = m_platformConfig->ConvertToRelativePath(overridingFile, relativeName, scanFolder);
|
||||
}
|
||||
}
|
||||
|
||||
if (!validResult)
|
||||
{
|
||||
// if we are here it means we have failed to determine the relativePath, so we will send back the original path
|
||||
AZ_TracePrintf(AssetProcessor::DebugChannel,
|
||||
"GenerateRelativeSourcePath found no valid result, returning original path: %s...\n", sourcePath.c_str());
|
||||
|
||||
rootFolder.clear();
|
||||
relativePath.clear();
|
||||
relativePath = sourcePath;
|
||||
return false;
|
||||
}
|
||||
|
||||
relativePath = relativeName.toUtf8().data();
|
||||
rootFolder = scanFolder.toUtf8().data();
|
||||
|
||||
AZ_Assert(!relativePath.empty(), "ConvertToRelativePath returned true, but relativePath is empty");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AssetCatalog::GetFullSourcePathFromRelativeProductPath(const AZStd::string& relPath, AZStd::string& fullSourcePath)
|
||||
{
|
||||
ProcessGetFullSourcePathFromRelativeProductPathRequest(relPath, fullSourcePath);
|
||||
|
||||
@@ -95,6 +95,12 @@ namespace AssetProcessor
|
||||
const char* GetAbsoluteDevGameFolderPath() override;
|
||||
const char* GetAbsoluteDevRootFolderPath() override;
|
||||
bool GetRelativeProductPathFromFullSourceOrProductPath(const AZStd::string& fullPath, AZStd::string& relativeProductPath) override;
|
||||
|
||||
//! Given a partial or full source file path, respond with its relative path and the watch folder it is relative to.
|
||||
//! The input source path does not need to exist, so this can be used for new files that haven't been saved yet.
|
||||
bool GenerateRelativeSourcePath(
|
||||
const AZStd::string& sourcePath, AZStd::string& relativePath, AZStd::string& watchFolder) override;
|
||||
|
||||
bool GetFullSourcePathFromRelativeProductPath(const AZStd::string& relPath, AZStd::string& fullSourcePath) override;
|
||||
bool GetAssetInfoById(const AZ::Data::AssetId& assetId, const AZ::Data::AssetType& assetType, const AZStd::string& platformName, AZ::Data::AssetInfo& assetInfo, AZStd::string& rootFilePath) override;
|
||||
bool GetSourceInfoBySourcePath(const char* sourcePath, AZ::Data::AssetInfo& assetInfo, AZStd::string& watchFolder) override;
|
||||
|
||||
@@ -104,6 +104,27 @@ namespace
|
||||
return GetRelativeProductPathFromFullSourceOrProductPathResponse(relPathFound, relProductPath);
|
||||
}
|
||||
|
||||
GenerateRelativeSourcePathResponse HandleGenerateRelativeSourcePathRequest(
|
||||
MessageData<GenerateRelativeSourcePathRequest> messageData)
|
||||
{
|
||||
bool relPathFound = false;
|
||||
AZStd::string relPath;
|
||||
AZStd::string watchFolder;
|
||||
|
||||
AzToolsFramework::AssetSystemRequestBus::BroadcastResult(
|
||||
relPathFound, &AzToolsFramework::AssetSystemRequestBus::Events::GenerateRelativeSourcePath,
|
||||
messageData.m_message->m_sourcePath, relPath, watchFolder);
|
||||
|
||||
if (!relPathFound)
|
||||
{
|
||||
AZ_TracePrintf(
|
||||
AssetProcessor::ConsoleChannel, "Could not find relative source path for the source file (%s).",
|
||||
messageData.m_message->m_sourcePath.c_str());
|
||||
}
|
||||
|
||||
return GenerateRelativeSourcePathResponse(relPathFound, relPath, watchFolder);
|
||||
}
|
||||
|
||||
SourceAssetInfoResponse HandleSourceAssetInfoRequest(MessageData<SourceAssetInfoRequest> messageData)
|
||||
{
|
||||
SourceAssetInfoResponse response;
|
||||
@@ -407,6 +428,7 @@ AssetRequestHandler::AssetRequestHandler()
|
||||
|
||||
m_requestRouter.RegisterMessageHandler(&HandleGetFullSourcePathFromRelativeProductPathRequest);
|
||||
m_requestRouter.RegisterMessageHandler(&HandleGetRelativeProductPathFromFullSourceOrProductPathRequest);
|
||||
m_requestRouter.RegisterMessageHandler(&HandleGenerateRelativeSourcePathRequest);
|
||||
m_requestRouter.RegisterMessageHandler(&HandleSourceAssetInfoRequest);
|
||||
m_requestRouter.RegisterMessageHandler(&HandleSourceAssetProductsInfoRequest);
|
||||
m_requestRouter.RegisterMessageHandler(&HandleGetScanFoldersRequest);
|
||||
|
||||
@@ -57,6 +57,9 @@ namespace AzFramework
|
||||
class GetRelativeProductPathFromFullSourceOrProductPathRequest;
|
||||
class GetRelativeProductPathFromFullSourceOrProductPathResponse;
|
||||
|
||||
class GenerateRelativeSourcePathRequest;
|
||||
class GenerateRelativeSourcePathResponse;
|
||||
|
||||
class GetFullSourcePathFromRelativeProductPathRequest;
|
||||
class GetFullSourcePathFromRelativeProductPathResponse;
|
||||
class AssetNotificationMessage;
|
||||
@@ -104,6 +107,8 @@ namespace AssetProcessor
|
||||
using GetAbsoluteAssetDatabaseLocationResponse = AzToolsFramework::AssetSystem::GetAbsoluteAssetDatabaseLocationResponse;
|
||||
using GetRelativeProductPathFromFullSourceOrProductPathRequest = AzFramework::AssetSystem::GetRelativeProductPathFromFullSourceOrProductPathRequest;
|
||||
using GetRelativeProductPathFromFullSourceOrProductPathResponse = AzFramework::AssetSystem::GetRelativeProductPathFromFullSourceOrProductPathResponse;
|
||||
using GenerateRelativeSourcePathRequest = AzFramework::AssetSystem::GenerateRelativeSourcePathRequest;
|
||||
using GenerateRelativeSourcePathResponse = AzFramework::AssetSystem::GenerateRelativeSourcePathResponse;
|
||||
using GetFullSourcePathFromRelativeProductPathRequest = AzFramework::AssetSystem::GetFullSourcePathFromRelativeProductPathRequest;
|
||||
using GetFullSourcePathFromRelativeProductPathResponse = AzFramework::AssetSystem::GetFullSourcePathFromRelativeProductPathResponse;
|
||||
|
||||
|
||||
@@ -291,7 +291,6 @@ namespace AssetProcessor
|
||||
}
|
||||
}
|
||||
|
||||
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_Bootstrap(registry);
|
||||
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_EngineRegistry(registry, platform, specialization, &scratchBuffer);
|
||||
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_GemRegistries(registry, platform, specialization, &scratchBuffer);
|
||||
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_ProjectRegistry(registry, platform, specialization, &scratchBuffer);
|
||||
|
||||
@@ -130,6 +130,9 @@ namespace AssetProcessor
|
||||
auto cacheRootKey =
|
||||
AZ::SettingsRegistryInterface::FixedValueString(AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey) + "/project_cache_path";
|
||||
settingsRegistry->Set(cacheRootKey, m_data->m_temporarySourceDir.absoluteFilePath("Cache").toUtf8().constData());
|
||||
auto projectPathKey =
|
||||
AZ::SettingsRegistryInterface::FixedValueString(AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey) + "/project_path";
|
||||
settingsRegistry->Set(projectPathKey, "AutomatedTesting");
|
||||
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(*settingsRegistry);
|
||||
AssetUtilities::ComputeProjectCacheRoot(m_data->m_cacheRootDir);
|
||||
QString normalizedCacheRoot = AssetUtilities::NormalizeDirectoryPath(m_data->m_cacheRootDir.absolutePath());
|
||||
@@ -221,6 +224,18 @@ namespace AssetProcessor
|
||||
dbConn->SetScanFolder(newScanFolder);
|
||||
}
|
||||
|
||||
virtual void AddScanFolders(
|
||||
const QDir& tempPath, AssetDatabaseConnection* dbConn, PlatformConfiguration& config,
|
||||
const AZStd::vector<AssetBuilderSDK::PlatformInfo>& platforms)
|
||||
{
|
||||
// PATH DisplayName PortKey root recurse platforms order
|
||||
AddScanFolder(ScanFolderInfo(tempPath.filePath("subfolder4"), "subfolder4", "subfolder4", false, false, platforms, -6), config, dbConn); // subfolder 4 overrides subfolder3
|
||||
AddScanFolder(ScanFolderInfo(tempPath.filePath("subfolder3"), "subfolder3", "subfolder3", false, false, platforms, -5), config, dbConn); // subfolder 3 overrides subfolder2
|
||||
AddScanFolder(ScanFolderInfo(tempPath.filePath("subfolder2"), "subfolder2", "subfolder2", false, true, platforms, -2), config, dbConn); // subfolder 2 overrides subfolder1
|
||||
AddScanFolder(ScanFolderInfo(tempPath.filePath("subfolder1"), "subfolder1", "subfolder1", false, true, platforms, -1), config, dbConn); // subfolder1 overrides root
|
||||
AddScanFolder(ScanFolderInfo(tempPath.absolutePath(), "temp", "tempfolder", true, false, platforms, 0), config, dbConn); // add the root
|
||||
}
|
||||
|
||||
// build some default configs.
|
||||
void BuildConfig(const QDir& tempPath, AssetDatabaseConnection* dbConn, PlatformConfiguration& config)
|
||||
{
|
||||
@@ -229,12 +244,8 @@ namespace AssetProcessor
|
||||
config.EnablePlatform({ "fandango" ,{ "console", "renderer" } }, false);
|
||||
AZStd::vector<AssetBuilderSDK::PlatformInfo> platforms;
|
||||
config.PopulatePlatformsForScanFolder(platforms);
|
||||
// PATH DisplayName PortKey root recurse platforms order
|
||||
AddScanFolder(ScanFolderInfo(tempPath.filePath("subfolder4"), "subfolder4", "subfolder4", false, false, platforms, -6), config, dbConn); // subfolder 4 overrides subfolder3
|
||||
AddScanFolder(ScanFolderInfo(tempPath.filePath("subfolder3"), "subfolder3", "subfolder3", false, false, platforms, -5), config, dbConn); // subfolder 3 overrides subfolder2
|
||||
AddScanFolder(ScanFolderInfo(tempPath.filePath("subfolder2"), "subfolder2", "subfolder2", false, true, platforms, -2), config, dbConn); // subfolder 2 overrides subfolder1
|
||||
AddScanFolder(ScanFolderInfo(tempPath.filePath("subfolder1"), "subfolder1", "subfolder1", false, true, platforms, -1), config, dbConn); // subfolder1 overrides root
|
||||
AddScanFolder(ScanFolderInfo(tempPath.absolutePath(), "temp", "tempfolder", true, false, platforms, 0), config, dbConn); // add the root
|
||||
|
||||
AddScanFolders(tempPath, dbConn, config, platforms);
|
||||
|
||||
config.AddMetaDataType("exportsettings", QString());
|
||||
|
||||
@@ -356,7 +367,8 @@ namespace AssetProcessor
|
||||
return false;
|
||||
}
|
||||
|
||||
// Calls the GetFullSourcePathFromRelativeProductPath function and checks the return results, returning true if it matches both of the expected results
|
||||
// Calls the GetFullSourcePathFromRelativeProductPath function and checks the return results, returning true if it matches both of
|
||||
// the expected results
|
||||
bool TestGetFullSourcePath(const QString& fileToCheck, const QDir& tempPath, bool expectToFind, const char* expectedPath)
|
||||
{
|
||||
bool fullPathfound = false;
|
||||
@@ -528,6 +540,177 @@ namespace AssetProcessor
|
||||
ASSERT_TRUE(TestGetRelativeProductPath(fileToCheck, true, { "aaa/basefile.txt" }));
|
||||
}
|
||||
|
||||
class AssetCatalogTestRelativeSourcePath : public AssetCatalogTest
|
||||
{
|
||||
public:
|
||||
QDir GetRoot()
|
||||
{
|
||||
// Return an OS-friendly absolute root directory for our tests ("C:/sourceRoot" or "/sourceRoot"). It doesn't
|
||||
// need to exist, it just needs to be an absolute path.
|
||||
return QDir::root().filePath("sourceRoot");
|
||||
}
|
||||
|
||||
// Set up custom scan folders for the "relative source path" tests, so that we can try out specific combinations of watch folders
|
||||
void AddScanFolders(
|
||||
[[maybe_unused]] const QDir& tempPath, AssetDatabaseConnection* dbConn, PlatformConfiguration& config,
|
||||
const AZStd::vector<AssetBuilderSDK::PlatformInfo>& platforms) override
|
||||
{
|
||||
QDir root = GetRoot();
|
||||
|
||||
// This will set up the following watch folders, in highest to lowest priority:
|
||||
|
||||
// /sourceRoot/recurseNested/nested (recurse)
|
||||
// /sourceRoot/noRecurse (no recurse)
|
||||
// /sourceRoot/recurseNotNested (recurse)
|
||||
// /sourceRoot/recurseNested (recurse)
|
||||
|
||||
AddScanFolder(
|
||||
ScanFolderInfo(root.filePath("recurseNested/nested"), "nested", "nested", false, true, platforms, -4), config, dbConn);
|
||||
AddScanFolder(
|
||||
ScanFolderInfo(root.filePath("noRecurse"), "noRecurse", "noRecurse", false, false, platforms, -3), config, dbConn);
|
||||
AddScanFolder(
|
||||
ScanFolderInfo(root.filePath("recurseNotNested"), "recurseNotNested", "recurseNotNested", false, true, platforms, -2),
|
||||
config, dbConn);
|
||||
AddScanFolder(
|
||||
ScanFolderInfo(root.filePath("recurseNested"), "recurseNested", "recurseNested", false, true, platforms, -1),
|
||||
config, dbConn);
|
||||
}
|
||||
|
||||
// Calls the GenerateRelativeSourcePath function and validates that the results match the expected inputs.
|
||||
void TestGetRelativeSourcePath(
|
||||
const AZStd::string& sourcePath, bool expectedToFind, const AZStd::string& expectedPath, const AZStd::string& expectedRoot)
|
||||
{
|
||||
bool relPathFound = false;
|
||||
AZStd::string relPath;
|
||||
AZStd::string rootFolder;
|
||||
|
||||
AzToolsFramework::AssetSystemRequestBus::BroadcastResult(
|
||||
relPathFound, &AzToolsFramework::AssetSystem::AssetSystemRequest::GenerateRelativeSourcePath, sourcePath,
|
||||
relPath, rootFolder);
|
||||
|
||||
EXPECT_EQ(relPathFound, expectedToFind);
|
||||
EXPECT_EQ(relPath, expectedPath);
|
||||
EXPECT_EQ(rootFolder, expectedRoot);
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(AssetCatalogTestRelativeSourcePath, GenerateRelativeSourcePath_EmptySourcePath_ReturnsNoMatch)
|
||||
{
|
||||
// Test passes in an empty source path, which shouldn't produce a valid result.
|
||||
// Input: empty source path
|
||||
// Output: empty, not found result
|
||||
TestGetRelativeSourcePath("", false, "", "");
|
||||
}
|
||||
|
||||
TEST_F(AssetCatalogTestRelativeSourcePath, GenerateRelativeSourcePath_AbsolutePathOutsideWatchFolders_ReturnsNoMatch)
|
||||
{
|
||||
// Test passes in an invalid absolute source path, which shouldn't produce a valid result.
|
||||
// Input: "/sourceRoot/noWatchFolder/test.txt"
|
||||
// Output: not found result, which also returns the input as the relative file name
|
||||
QDir watchFolder = GetRoot().filePath("noWatchFolder/");
|
||||
QString fileToCheck = watchFolder.filePath("test.txt");
|
||||
|
||||
TestGetRelativeSourcePath(fileToCheck.toUtf8().constData(), false, fileToCheck.toUtf8().constData(), "");
|
||||
}
|
||||
|
||||
TEST_F(AssetCatalogTestRelativeSourcePath, GenerateRelativeSourcePath_AbsolutePathUnderWatchFolder_ReturnsRelativePath)
|
||||
{
|
||||
// Test passes in a valid absolute source path, which should produce a valid relative path
|
||||
// Input: "/sourceRoot/noRecurse/test.txt"
|
||||
// Output: "test.txt" in folder "/sourceRoot/noRecurse/"
|
||||
QDir watchFolder = GetRoot().filePath("noRecurse/");
|
||||
QString fileToCheck = watchFolder.filePath("test.txt");
|
||||
|
||||
TestGetRelativeSourcePath(fileToCheck.toUtf8().constData(), true, "test.txt", watchFolder.path().toUtf8().constData());
|
||||
}
|
||||
|
||||
TEST_F(AssetCatalogTestRelativeSourcePath, GenerateRelativeSourcePath_AbsolutePathUnderNestedWatchFolders_ReturnsRelativePath)
|
||||
{
|
||||
// Test passes in a valid absolute source path that matches a watch folder and a nested watch folder.
|
||||
// The output relative path should match the nested folder, because the nested folder has a higher priority registered with the AP.
|
||||
// Input: "/sourceRoot/recurseNested/nested/test.txt"
|
||||
// Output: "test.txt" in folder "/sourceRoot/recurseNested/nested/"
|
||||
QDir watchFolder = GetRoot().filePath("recurseNested/nested/");
|
||||
QString fileToCheck = watchFolder.filePath("test.txt");
|
||||
|
||||
TestGetRelativeSourcePath(fileToCheck.toUtf8().constData(), true, "test.txt", watchFolder.path().toUtf8().constData());
|
||||
}
|
||||
|
||||
TEST_F(AssetCatalogTestRelativeSourcePath, GenerateRelativeSourcePath_BareFileNameValidInWatchFolder_ReturnsHighestPriorityWatchFolder)
|
||||
{
|
||||
// Test passes in a simple file name. The output should be relative to the highest-priority watch folder.
|
||||
// Input: "test.txt"
|
||||
// Output: "test.txt" in folder "/sourceRoot/recurseNested/nested/"
|
||||
QDir watchFolder = GetRoot().filePath("recurseNested/nested/");
|
||||
|
||||
TestGetRelativeSourcePath("test.txt", true, "test.txt", watchFolder.path().toUtf8().constData());
|
||||
}
|
||||
|
||||
TEST_F(AssetCatalogTestRelativeSourcePath, GenerateRelativeSourcePath_RelativePathValidInWatchFolder_ReturnsHighestPriorityWatchFolder)
|
||||
{
|
||||
// Test passes in a relative path. The output should preserve the relative path, but list it as relative to the highest-priority
|
||||
// watch folder.
|
||||
// Input: "a/b/c/test.txt"
|
||||
// Output: "a/b/c/test.txt" in folder "/sourceRoot/recurseNested/nested/"
|
||||
QDir watchFolder = GetRoot().filePath("recurseNested/nested/");
|
||||
|
||||
TestGetRelativeSourcePath("a/b/c/test.txt", true, "a/b/c/test.txt", watchFolder.path().toUtf8().constData());
|
||||
}
|
||||
|
||||
TEST_F(AssetCatalogTestRelativeSourcePath, GenerateRelativeSourcePath_RelativePathNotInWatchFolder_ReturnsNoMatch)
|
||||
{
|
||||
// Test passes in a relative path that "backs up" two directories. This will be invalid, because no matter which watch directory
|
||||
// we start at, the result will be outside of any watch directory.
|
||||
// Input: "../../test.txt"
|
||||
// Output: not found result, which also returns the input as the relative file name
|
||||
TestGetRelativeSourcePath("../../test.txt", false, "../../test.txt", "");
|
||||
}
|
||||
|
||||
TEST_F(AssetCatalogTestRelativeSourcePath, GenerateRelativeSourcePath_RelativePathValidFromNestedWatchFolder_ReturnsOuterFolder)
|
||||
{
|
||||
// Test passes in a relative path that "backs up" one directory. This will produce a valid result, because we can back up from
|
||||
// the "recurseNested/nested/" watch folder to "recurseNested", which is also a valid watch folder.
|
||||
// Input: "../test.txt"
|
||||
// Output: "test.txt" in folder "/sourceRoot/recurseNested"
|
||||
QDir watchFolder = GetRoot().filePath("recurseNested/");
|
||||
TestGetRelativeSourcePath("../test.txt", true, "test.txt", watchFolder.path().toUtf8().constData());
|
||||
}
|
||||
|
||||
TEST_F(AssetCatalogTestRelativeSourcePath, GenerateRelativeSourcePath_RelativePathMovesToParentWatchFolder_ReturnsOuterFolder)
|
||||
{
|
||||
// Test passes in a relative path that backs up one directory and then forward into a directory. This will produce a valid
|
||||
// result, because it can validly start in the highest-priority watch folder (recurseNested/nested), move back one into the
|
||||
// outer watch folder (recurseNested), and then have a subdirectory within it.
|
||||
// Note that it would also be valid to move from recurseNested to recurseNotNested, but that won't be the result of this test
|
||||
// because that's a lower-priority match.
|
||||
// Input: "../recurseNotNested/test.txt"
|
||||
// Output: "recurseNotNested/test.txt" in folder "/sourceRoot/recurseNested/"
|
||||
QDir watchFolder = GetRoot().filePath("recurseNested/");
|
||||
|
||||
TestGetRelativeSourcePath("../recurseNotNested/test.txt", true, "recurseNotNested/test.txt", watchFolder.path().toUtf8().constData());
|
||||
}
|
||||
|
||||
TEST_F(AssetCatalogTestRelativeSourcePath, GenerateRelativeSourcePath_RelativePathMovesToSiblingWatchFolder_ReturnsSiblingFolder)
|
||||
{
|
||||
// Test passes in a relative path that backs up two directories and then forward into a directory. This will produce a valid
|
||||
// result, because it can validly start in the recurseNested/nested folder, move back two folders, then forward into the sibling
|
||||
// recurseNotNested folder. The result will be a relative path to the sibling folder.
|
||||
// Input: "../../recurseNotNested/test.txt"
|
||||
// Output: "test.txt" in folder "/sourceRoot/recurseNotNested/"
|
||||
QDir watchFolder = GetRoot().filePath("recurseNotNested/");
|
||||
|
||||
TestGetRelativeSourcePath("../../recurseNotNested/test.txt", true, "test.txt", watchFolder.path().toUtf8().constData());
|
||||
}
|
||||
|
||||
TEST_F(AssetCatalogTestRelativeSourcePath, GenerateRelativeSourcePath_RelativePathBacksOutOfWatchFolder_ReturnsNoMatch)
|
||||
{
|
||||
// Test passes in a relative path that adds a directory, then "backs up" three directories. This will be invalid, because no
|
||||
// matter which watch directory we start at, the result will be outside of any watch directory.
|
||||
// Input: "../test.txt"
|
||||
// Output: "test.txt" in folder "/sourceRoot/recurseNested"
|
||||
TestGetRelativeSourcePath("a/../../../test.txt", false, "a/../../../test.txt", "");
|
||||
}
|
||||
|
||||
class AssetCatalogTest_GetFullSourcePath
|
||||
: public AssetCatalogTest
|
||||
{
|
||||
|
||||
@@ -265,6 +265,9 @@ namespace AssetProcessorMessagesTests
|
||||
|
||||
addPairFunc(new GetFullSourcePathFromRelativeProductPathRequest(), new GetFullSourcePathFromRelativeProductPathResponse());
|
||||
addPairFunc(new GetRelativeProductPathFromFullSourceOrProductPathRequest(), new GetRelativeProductPathFromFullSourceOrProductPathResponse());
|
||||
addPairFunc(
|
||||
new GenerateRelativeSourcePathRequest(),
|
||||
new GenerateRelativeSourcePathResponse());
|
||||
addPairFunc(new SourceAssetInfoRequest(), new SourceAssetInfoResponse());
|
||||
addPairFunc(new SourceAssetProductsInfoRequest(), new SourceAssetProductsInfoResponse());
|
||||
addPairFunc(new GetScanFoldersRequest(), new GetScanFoldersResponse());
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
#include "AssetProcessorTest.h"
|
||||
|
||||
|
||||
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
|
||||
#include <AzCore/UserSettings/UserSettingsComponent.h>
|
||||
|
||||
#include "BaseAssetProcessorTest.h"
|
||||
@@ -67,6 +67,12 @@ namespace AssetProcessor
|
||||
static char processName[] = {"AssetProcessorBatch"};
|
||||
static char* namePtr = &processName[0];
|
||||
static char** paramStringArray = &namePtr;
|
||||
|
||||
auto registry = AZ::SettingsRegistry::Get();
|
||||
auto projectPathKey =
|
||||
AZ::SettingsRegistryInterface::FixedValueString(AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey) + "/project_path";
|
||||
registry->Set(projectPathKey, "AutomatedTesting");
|
||||
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(*registry);
|
||||
|
||||
m_application.reset(new UnitTestAppManager(&numParams, ¶mStringArray));
|
||||
ASSERT_EQ(m_application->BeforeRun(), ApplicationManager::Status_Success);
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include <AzTest/AzTest.h>
|
||||
#include <AzCore/Memory/Memory.h>
|
||||
#include <AzCore/Memory/SystemAllocator.h>
|
||||
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
|
||||
#include <AzFramework/Application/Application.h>
|
||||
#include <native/utilities/assetUtils.h>
|
||||
#include <native/unittests/UnitTestRunner.h> // for the assert absorber.
|
||||
@@ -44,7 +45,18 @@ namespace AssetProcessor
|
||||
AZ::AllocatorInstance<AZ::SystemAllocator>::Create();
|
||||
}
|
||||
m_errorAbsorber = new UnitTestUtils::AssertAbsorber();
|
||||
|
||||
m_application = AZStd::make_unique<AzFramework::Application>();
|
||||
|
||||
// Inject the AutomatedTesting project as a project path into test fixture
|
||||
using FixedValueString = AZ::SettingsRegistryInterface::FixedValueString;
|
||||
constexpr auto projectPathKey = FixedValueString(AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey)
|
||||
+ "/project_path";
|
||||
if(auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr)
|
||||
{
|
||||
settingsRegistry->Set(projectPathKey, "AutomatedTesting");
|
||||
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(*settingsRegistry);
|
||||
}
|
||||
}
|
||||
|
||||
void TearDown() override
|
||||
|
||||
@@ -204,6 +204,9 @@ void AssetProcessorManagerTest::SetUp()
|
||||
auto cacheRootKey =
|
||||
AZ::SettingsRegistryInterface::FixedValueString(AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey) + "/project_cache_path";
|
||||
registry->Set(cacheRootKey, tempPath.absoluteFilePath("Cache").toUtf8().constData());
|
||||
auto projectPathKey =
|
||||
AZ::SettingsRegistryInterface::FixedValueString(AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey) + "/project_path";
|
||||
registry->Set(projectPathKey, "AutomatedTesting");
|
||||
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(*registry);
|
||||
|
||||
m_data->m_databaseLocationListener.BusConnect();
|
||||
|
||||
-1
@@ -39,7 +39,6 @@ void PlatformConfigurationUnitTests::SetUp()
|
||||
m_qApp = new QCoreApplication(m_argc, m_argv);
|
||||
AssetProcessorTest::SetUp();
|
||||
AssetUtilities::ResetAssetRoot();
|
||||
|
||||
}
|
||||
|
||||
void PlatformConfigurationUnitTests::TearDown()
|
||||
|
||||
-1
@@ -37,6 +37,5 @@ private:
|
||||
int m_argc;
|
||||
char** m_argv;
|
||||
QCoreApplication* m_qApp;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -51,6 +51,8 @@ namespace AssetProcessor
|
||||
public:
|
||||
using GetRelativeProductPathFromFullSourceOrProductPathRequest = AzFramework::AssetSystem::GetRelativeProductPathFromFullSourceOrProductPathRequest;
|
||||
using GetRelativeProductPathFromFullSourceOrProductPathResponse = AzFramework::AssetSystem::GetRelativeProductPathFromFullSourceOrProductPathResponse;
|
||||
using GenerateRelativeSourcePathRequest = AzFramework::AssetSystem::GenerateRelativeSourcePathRequest;
|
||||
using GenerateRelativeSourcePathResponse = AzFramework::AssetSystem::GenerateRelativeSourcePathResponse;
|
||||
using GetFullSourcePathFromRelativeProductPathRequest = AzFramework::AssetSystem::GetFullSourcePathFromRelativeProductPathRequest;
|
||||
using GetFullSourcePathFromRelativeProductPathResponse = AzFramework::AssetSystem::GetFullSourcePathFromRelativeProductPathResponse;
|
||||
};
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include <AzTest/AzTest.h>
|
||||
#include <AzCore/Memory/OSAllocator.h>
|
||||
#include <AzCore/Memory/SystemAllocator.h>
|
||||
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
|
||||
#include <AzCore/std/containers/vector.h>
|
||||
#include <AzCore/std/string/string.h>
|
||||
#include <AzFramework/Asset/AssetBundleManifest.h>
|
||||
@@ -45,6 +46,12 @@ public:
|
||||
protected:
|
||||
void SetUp() override
|
||||
{
|
||||
AZ::SettingsRegistryInterface* registry = AZ::SettingsRegistry::Get();
|
||||
auto projectPathKey =
|
||||
AZ::SettingsRegistryInterface::FixedValueString(AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey) + "/project_path";
|
||||
registry->Set(projectPathKey, "AutomatedTesting");
|
||||
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(*registry);
|
||||
|
||||
AZ::ComponentApplication::Descriptor desc;
|
||||
desc.m_useExistingAllocator = true;
|
||||
desc.m_enableDrilling = false; // we already created a memory driller for the test (AllocatorsFixture)
|
||||
|
||||
@@ -71,3 +71,10 @@ QPushButton:focus {
|
||||
margin: 10px 0 10px 30px;
|
||||
}
|
||||
|
||||
#labelButtonOverlay {
|
||||
background-color: rgba(50,50,50,200);
|
||||
min-width:210px;
|
||||
max-width:210px;;
|
||||
min-height:278px;
|
||||
max-height:278px;
|
||||
}
|
||||
|
||||
@@ -31,11 +31,30 @@ namespace O3DE::ProjectManager
|
||||
LabelButton::LabelButton(QWidget* parent)
|
||||
: QLabel(parent)
|
||||
{
|
||||
m_overlayLabel = new QLabel("", this);
|
||||
m_overlayLabel->setObjectName("labelButtonOverlay");
|
||||
m_overlayLabel->setWordWrap(true);
|
||||
m_overlayLabel->setAlignment(Qt::AlignCenter);
|
||||
m_overlayLabel->setVisible(false);
|
||||
}
|
||||
|
||||
void LabelButton::mousePressEvent([[maybe_unused]] QMouseEvent* event)
|
||||
{
|
||||
emit triggered();
|
||||
if(m_enabled)
|
||||
{
|
||||
emit triggered();
|
||||
}
|
||||
}
|
||||
|
||||
void LabelButton::SetEnabled(bool enabled)
|
||||
{
|
||||
m_enabled = enabled;
|
||||
m_overlayLabel->setVisible(!enabled);
|
||||
}
|
||||
|
||||
void LabelButton::SetOverlayText(const QString& text)
|
||||
{
|
||||
m_overlayLabel->setText(text);
|
||||
}
|
||||
|
||||
ProjectButton::ProjectButton(const QString& projectName, QWidget* parent)
|
||||
@@ -99,4 +118,13 @@ namespace O3DE::ProjectManager
|
||||
#endif
|
||||
}
|
||||
|
||||
void ProjectButton::SetButtonEnabled(bool enabled)
|
||||
{
|
||||
m_projectImageLabel->SetEnabled(enabled);
|
||||
}
|
||||
|
||||
void ProjectButton::SetButtonOverlayText(const QString& text)
|
||||
{
|
||||
m_projectImageLabel->SetOverlayText(text);
|
||||
}
|
||||
} // namespace O3DE::ProjectManager
|
||||
|
||||
@@ -32,11 +32,18 @@ namespace O3DE::ProjectManager
|
||||
explicit LabelButton(QWidget* parent = nullptr);
|
||||
~LabelButton() = default;
|
||||
|
||||
void SetEnabled(bool enabled);
|
||||
void SetOverlayText(const QString& text);
|
||||
|
||||
signals:
|
||||
void triggered();
|
||||
|
||||
public slots:
|
||||
void mousePressEvent(QMouseEvent* event) override;
|
||||
|
||||
private:
|
||||
QLabel* m_overlayLabel;
|
||||
bool m_enabled = true;
|
||||
};
|
||||
|
||||
class ProjectButton
|
||||
@@ -49,6 +56,9 @@ namespace O3DE::ProjectManager
|
||||
explicit ProjectButton(const QString& projectName, const QString& projectImage, QWidget* parent = nullptr);
|
||||
~ProjectButton() = default;
|
||||
|
||||
void SetButtonEnabled(bool enabled);
|
||||
void SetButtonOverlayText(const QString& text);
|
||||
|
||||
signals:
|
||||
void OpenProject(const QString& projectName);
|
||||
void EditProject(const QString& projectName);
|
||||
|
||||
@@ -14,6 +14,12 @@
|
||||
|
||||
#include <ProjectButtonWidget.h>
|
||||
#include <PythonBindingsInterface.h>
|
||||
#include <AzCore/Platform.h>
|
||||
#include <AzCore/IO/SystemFile.h>
|
||||
#include <AzFramework/AzFramework_Traits_Platform.h>
|
||||
#include <AzFramework/Process/ProcessCommon.h>
|
||||
#include <AzFramework/Process/ProcessWatcher.h>
|
||||
#include <AzCore/Utils/Utils.h>
|
||||
|
||||
#include <QVBoxLayout>
|
||||
#include <QHBoxLayout>
|
||||
@@ -27,6 +33,8 @@
|
||||
#include <QListWidgetItem>
|
||||
#include <QFileInfo>
|
||||
#include <QScrollArea>
|
||||
#include <QMessageBox>
|
||||
#include <QTimer>
|
||||
|
||||
namespace O3DE::ProjectManager
|
||||
{
|
||||
@@ -127,8 +135,48 @@ namespace O3DE::ProjectManager
|
||||
}
|
||||
void ProjectsHomeScreen::HandleOpenProject(const QString& projectPath)
|
||||
{
|
||||
// Open the editor with this project open
|
||||
emit NotifyCurrentProject(projectPath);
|
||||
if (!projectPath.isEmpty())
|
||||
{
|
||||
AZ::IO::FixedMaxPath executableDirectory = AZ::Utils::GetExecutableDirectory();
|
||||
AZStd::string executableFilename = "Editor";
|
||||
AZ::IO::FixedMaxPath editorExecutablePath = executableDirectory / (executableFilename + AZ_TRAIT_OS_EXECUTABLE_EXTENSION);
|
||||
auto cmdPath = AZ::IO::FixedMaxPathString::format("%s -regset=\"/Amazon/AzCore/Bootstrap/project_path=%s\"", editorExecutablePath.c_str(), projectPath.toStdString().c_str());
|
||||
|
||||
AzFramework::ProcessLauncher::ProcessLaunchInfo processLaunchInfo;
|
||||
processLaunchInfo.m_commandlineParameters = cmdPath;
|
||||
bool launchSucceeded = AzFramework::ProcessLauncher::LaunchUnwatchedProcess(processLaunchInfo);
|
||||
if (!launchSucceeded)
|
||||
{
|
||||
AZ_Error("ProjectManager", false, "Failed to launch editor");
|
||||
QMessageBox::critical( this, tr("Error"), tr("Failed to launch the Editor, please verify the project settings are valid."));
|
||||
}
|
||||
else
|
||||
{
|
||||
// prevent the user from accidentally pressing the button while the editor is launching
|
||||
// and let them know what's happening
|
||||
ProjectButton* button = qobject_cast<ProjectButton*>(sender());
|
||||
if (button)
|
||||
{
|
||||
button->SetButtonEnabled(false);
|
||||
button->SetButtonOverlayText(tr("Opening Editor..."));
|
||||
}
|
||||
|
||||
// enable the button after 3 seconds
|
||||
constexpr int waitTimeInMs = 3000;
|
||||
QTimer::singleShot(waitTimeInMs, this, [this, button] {
|
||||
if (button)
|
||||
{
|
||||
button->SetButtonEnabled(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AZ_Error("ProjectManager", false, "Cannot open editor because an empty project path was provided");
|
||||
QMessageBox::critical( this, tr("Error"), tr("Failed to launch the Editor because the project path is invalid."));
|
||||
}
|
||||
|
||||
}
|
||||
void ProjectsHomeScreen::HandleEditProject(const QString& projectPath)
|
||||
{
|
||||
|
||||
@@ -152,8 +152,11 @@ struct AssetValidationTest
|
||||
{
|
||||
AZ::SettingsRegistry::Register(&m_registry);
|
||||
|
||||
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_Bootstrap(m_registry);
|
||||
auto projectPathKey = AZ::SettingsRegistryInterface::FixedValueString(AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey)
|
||||
+ "/project_path";
|
||||
m_registry.Set(projectPathKey, "AutomatedTesting");
|
||||
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(m_registry);
|
||||
|
||||
// Set the engine root to the temporary directory and re-update the runtime file paths
|
||||
auto enginePathKey = AZ::SettingsRegistryInterface::FixedValueString(AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey)
|
||||
+ "/engine_path";
|
||||
|
||||
@@ -101,3 +101,36 @@ ly_add_target(
|
||||
3rdParty::SPIRVCross
|
||||
3rdParty::azslc
|
||||
)
|
||||
|
||||
################################################################################
|
||||
# Tests
|
||||
################################################################################
|
||||
if(PAL_TRAIT_BUILD_TESTS_SUPPORTED)
|
||||
|
||||
ly_add_target(
|
||||
NAME Atom_Asset_Shader.Tests ${PAL_TRAIT_TEST_TARGET_TYPE}
|
||||
NAMESPACE Gem
|
||||
FILES_CMAKE
|
||||
atom_asset_shader_builders_tests_files.cmake
|
||||
INCLUDE_DIRECTORIES
|
||||
PRIVATE
|
||||
.
|
||||
Source/Editor
|
||||
Tests
|
||||
BUILD_DEPENDENCIES
|
||||
PRIVATE
|
||||
AZ::AtomCore
|
||||
AZ::AzTest
|
||||
AZ::AzFramework
|
||||
AZ::AzToolsFramework
|
||||
Legacy::CryCommon
|
||||
Gem::Atom_RPI.Public
|
||||
Gem::Atom_RHI.Public
|
||||
Gem::Atom_RPI.Edit
|
||||
Gem::Atom_Asset_Shader.Static
|
||||
)
|
||||
ly_add_googletest(
|
||||
NAME Gem::Atom_Asset_Shader.Tests
|
||||
)
|
||||
|
||||
endif()
|
||||
|
||||
@@ -60,33 +60,31 @@ namespace AZ
|
||||
|
||||
void PreprocessorOptions::RemovePredefinedMacros(const AZStd::vector<AZStd::string>& macroNames)
|
||||
{
|
||||
for (const auto& macroName : macroNames)
|
||||
{
|
||||
m_predefinedMacros.erase(
|
||||
AZStd::remove_if(
|
||||
m_predefinedMacros.begin(), m_predefinedMacros.end(),
|
||||
[&](const AZStd::string& predefinedMacro)
|
||||
{
|
||||
for (const auto& macroName : macroNames)
|
||||
[&](const AZStd::string& predefinedMacro) {
|
||||
// Haystack, needle, bCaseSensitive
|
||||
if (!AzFramework::StringFunc::StartsWith(predefinedMacro, macroName, true))
|
||||
{
|
||||
// Haystack, needle, bCaseSensitive
|
||||
if (!AzFramework::StringFunc::StartsWith(predefinedMacro, macroName, true))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// If found, let's make sure it is not just a substring.
|
||||
if (predefinedMacro.size() == macroName.size())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
// The predefinedMacro can be a string like "macro=value". If we find '=' it is a match.
|
||||
if (predefinedMacro.c_str()[macroName.size()] == '=')
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
// If found, let's make sure it is not just a substring.
|
||||
if (predefinedMacro.size() == macroName.size())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
// The predefinedMacro can be a string like "macro=value". If we find '=' it is a match.
|
||||
if (predefinedMacro.c_str()[macroName.size()] == '=')
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}),
|
||||
m_predefinedMacros.end());
|
||||
}
|
||||
}
|
||||
|
||||
//! Binder helper to Matsui C-Pre-Processor library
|
||||
|
||||
@@ -344,8 +344,7 @@ namespace AZ
|
||||
AZStd::string prependedAzslFilePath = RHI::PrependFile(args);
|
||||
if (prependedAzslFilePath == azslFullPath)
|
||||
{
|
||||
// For some reason the combined azsl file was not created in the temporary
|
||||
// directory assigned to this job.
|
||||
// The specific error is already reported by RHI::PrependFile().
|
||||
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
* its licensors.
|
||||
*
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this
|
||||
* distribution (the "License"). All use of this software is governed by the License,
|
||||
* or, if provided, by the license below or the license accompanying this file. Do not
|
||||
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "ShaderBuilderTestFixture.h"
|
||||
|
||||
#include <AzCore/Memory/PoolAllocator.h>
|
||||
#include <AzCore/Name/NameDictionary.h>
|
||||
|
||||
namespace UnitTest
|
||||
{
|
||||
void ShaderBuilderTestFixture::SetUp()
|
||||
{
|
||||
AllocatorsTestFixture::SetUp();
|
||||
|
||||
AZ::AllocatorInstance<AZ::PoolAllocator>::Create();
|
||||
AZ::AllocatorInstance<AZ::ThreadPoolAllocator>::Create();
|
||||
|
||||
AZ::NameDictionary::Create();
|
||||
}
|
||||
|
||||
void ShaderBuilderTestFixture::TearDown()
|
||||
{
|
||||
AZ::NameDictionary::Destroy();
|
||||
|
||||
AZ::AllocatorInstance<AZ::ThreadPoolAllocator>::Destroy();
|
||||
AZ::AllocatorInstance<AZ::PoolAllocator>::Destroy();
|
||||
|
||||
AllocatorsTestFixture::TearDown();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
* its licensors.
|
||||
*
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this
|
||||
* distribution (the "License"). All use of this software is governed by the License,
|
||||
* or, if provided, by the license below or the license accompanying this file. Do not
|
||||
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AzCore/UnitTest/TestTypes.h>
|
||||
|
||||
namespace UnitTest
|
||||
{
|
||||
/**
|
||||
* Unit test fixture for setting up memory allocation pools and the AZ::Name dictionary.
|
||||
* In the future will be extended as needed.
|
||||
*/
|
||||
class ShaderBuilderTestFixture
|
||||
: public AllocatorsTestFixture
|
||||
{
|
||||
protected:
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
// AllocatorsTestFixture overrides
|
||||
void SetUp() override;
|
||||
void TearDown() override;
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
};
|
||||
} // namespace UnitTest
|
||||
|
||||
@@ -0,0 +1,523 @@
|
||||
/*
|
||||
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
* its licensors.
|
||||
*
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this
|
||||
* distribution (the "License"). All use of this software is governed by the License,
|
||||
* or, if provided, by the license below or the license accompanying this file. Do not
|
||||
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <AzTest/AzTest.h>
|
||||
#include <AzCore/UnitTest/TestTypes.h>
|
||||
#include <AzCore/std/algorithm.h>
|
||||
|
||||
#include <Atom/RPI.Edit/Common/JsonUtils.h>
|
||||
#include <Atom/RPI.Edit/Shader/ShaderSourceData.h>
|
||||
#include <Atom/RHI.Edit/Utils.h>
|
||||
|
||||
#include <CommonFiles/GlobalBuildOptions.h>
|
||||
|
||||
#include "Common/ShaderBuilderTestFixture.h"
|
||||
|
||||
namespace UnitTest
|
||||
{
|
||||
using namespace AZ;
|
||||
|
||||
struct KeyValueView
|
||||
{
|
||||
AZStd::string_view m_key;
|
||||
AZStd::string_view m_value;
|
||||
};
|
||||
|
||||
class SupervariantCmdArgumentTests : public ShaderBuilderTestFixture
|
||||
{
|
||||
protected:
|
||||
static constexpr char MCPP_MACRO1[] = "MACRO1";
|
||||
static constexpr char MCPP_VALUE1[] = "VALUE1a";
|
||||
static constexpr char MCPP_NEW_VALUE1[] = "VALUE1b"; // Missing A is not a typo
|
||||
|
||||
static constexpr char MCPP_MACRO2[] = "MACRO2";
|
||||
static constexpr char MCPP_VALUE2[] = "VALUE2";
|
||||
|
||||
static constexpr char MCPP_MACRO3[] = "MACRO3";
|
||||
static constexpr char MCPP_VALUE3[] = "VALUE3a";
|
||||
static constexpr char MCPP_NEW_VALUE3[] = "VALUE3b";
|
||||
|
||||
static constexpr char MCPP_MACRO4[] = "MACRO4";
|
||||
|
||||
static constexpr char MCPP_MACRO5[] = "MACRO5";
|
||||
|
||||
static constexpr char MCPP_MACRO6[] = "MACRO6";
|
||||
static constexpr char MCPP_VALUE6[] = "VALUE6";
|
||||
|
||||
static constexpr char AZSLC_ARG1[] = "--azsl1";
|
||||
|
||||
static constexpr char AZSLC_ARG2[] = "--azsl2";
|
||||
static constexpr char AZSLC_VAL2[] = "open,source";
|
||||
static constexpr char AZSLC_NEW_VAL2a[] = "closed,binary";
|
||||
static constexpr char AZSLC_NEW_VAL2b[] = "closed,source";
|
||||
|
||||
static constexpr char AZSLC_ARG3[] = "--azsl3";
|
||||
static constexpr char AZSLC_VAL3[] = "blue";
|
||||
|
||||
static constexpr char AZSLC_ARG4[] = "-azsl4";
|
||||
|
||||
static constexpr char AZSLC_ARG5[] = "--azsl5";
|
||||
static constexpr char AZSLC_VAL5[] = "smith,wick,john,45,-1,-1";
|
||||
static constexpr char AZSLC_NEW_VAL5[] = "apple,seed,crisp,-1,2,0";
|
||||
|
||||
static constexpr char AZSLC_ARG6[] = "--azsl6";
|
||||
|
||||
static constexpr char AZSLC_ARG7[] = "--azsl7";
|
||||
|
||||
//! Helper function.
|
||||
//! Given an input list of {Key, Value} pairs returns a list of strings where each string is of the form: "Key=Value".
|
||||
AZStd::vector<AZStd::string> CreateListOfStringsFromListOfKeyValues(AZStd::array_view<KeyValueView> listOfKeyValues) const
|
||||
{
|
||||
AZStd::vector<AZStd::string> listOfStrings;
|
||||
for (const auto& keyValue : listOfKeyValues)
|
||||
{
|
||||
if (keyValue.m_value.empty())
|
||||
{
|
||||
listOfStrings.push_back(keyValue.m_key);
|
||||
}
|
||||
else
|
||||
{
|
||||
listOfStrings.push_back(AZStd::string::format("%s=%s", keyValue.m_key.data(), keyValue.m_value.data()));
|
||||
}
|
||||
}
|
||||
return listOfStrings;
|
||||
}
|
||||
|
||||
//! Helper function.
|
||||
//! Given an input list of {Key, Value} pairs returns a list of strings where each string is of the form: "Key1", "Value1", "Key2", "Value2".
|
||||
AZStd::vector<AZStd::string> CreateListOfSingleStringsFromListOfKeyValues(AZStd::array_view<KeyValueView> listOfKeyValues) const
|
||||
{
|
||||
AZStd::vector<AZStd::string> listOfStrings;
|
||||
for (const auto& keyValue : listOfKeyValues)
|
||||
{
|
||||
listOfStrings.push_back(keyValue.m_key);
|
||||
if (!keyValue.m_value.empty())
|
||||
{
|
||||
listOfStrings.push_back(keyValue.m_value);
|
||||
}
|
||||
}
|
||||
return listOfStrings;
|
||||
}
|
||||
|
||||
//! Helper function.
|
||||
//! @param outputString: [out] The string " @argName" gets appended to it (The space is intentional).
|
||||
//! Alternatively, if @argValue is NOT empty, then the string " @argName=@argValue" is
|
||||
//! appended to it.
|
||||
//! @param argName: A typical command line argument. "-p" or "--some".
|
||||
//! @param argValue: A string representing the value that should be appended to @argName.
|
||||
void AppendCmdLineArgument(AZStd::string& outputString, AZStd::string_view argName, AZStd::string_view argValue) const
|
||||
{
|
||||
if (argValue.empty())
|
||||
{
|
||||
outputString += AZStd::string::format(" %s", argName.data());
|
||||
}
|
||||
else
|
||||
{
|
||||
outputString += AZStd::string::format(" %s=%s", argName.data(), argValue.data());
|
||||
}
|
||||
}
|
||||
|
||||
//! Helper function.
|
||||
//! Similar to above, but assumes that @argName refers to just the name of a macro definition so the appended string will always start
|
||||
//! with "-D".
|
||||
void AppendMacroDefinitionArgument(AZStd::string& outputString, AZStd::string_view argName, AZStd::string_view argValue) const
|
||||
{
|
||||
AppendCmdLineArgument(outputString, AZStd::string::format("-D%s", argName.data()), argValue);
|
||||
}
|
||||
|
||||
//! A helper made of helpers.
|
||||
//! Returns a command line string that results of concatenating the input list of {Key, Value} pairs (with '=').
|
||||
//! Example of a returned string:
|
||||
//! "key1=value1 key2 key3 key4=value"
|
||||
AZStd::string CreateCmdLineStringFromListOfKeyValues(AZStd::array_view<KeyValueView> listOfKeyValues) const
|
||||
{
|
||||
AZStd::string cmdLineString;
|
||||
for (const auto& keyValueView : listOfKeyValues)
|
||||
{
|
||||
AppendCmdLineArgument(cmdLineString, keyValueView.m_key, keyValueView.m_value);
|
||||
}
|
||||
return cmdLineString;
|
||||
}
|
||||
|
||||
//! A helper made of helpers.
|
||||
//! Returns a command line string of macro definitions that results of concatenating the input list of {Key, Value} pairs.
|
||||
//! Example of a returned string:
|
||||
//! "-Dkey1=value1 -Dkey2 -Dkey3 -Dkey4=value"
|
||||
AZStd::string CreateMacroDefinitionCmdLineStringFromListOfKeyValues(AZStd::array_view<KeyValueView> listOfKeyValues) const
|
||||
{
|
||||
AZStd::string cmdLineString;
|
||||
for (const auto& keyValueView : listOfKeyValues)
|
||||
{
|
||||
AppendMacroDefinitionArgument(cmdLineString, keyValueView.m_key, keyValueView.m_value);
|
||||
}
|
||||
return cmdLineString;
|
||||
}
|
||||
|
||||
//! @param includePaths A List of folder paths
|
||||
//! @param predefinedMacros A List of strings with format: "name[=value]"
|
||||
ShaderBuilder::PreprocessorOptions CreatePreprocessorOptions(
|
||||
AZStd::array_view<AZStd::string> includePaths, AZStd::array_view<AZStd::string> predefinedMacros) const
|
||||
{
|
||||
ShaderBuilder::PreprocessorOptions preprocessorOptions;
|
||||
|
||||
preprocessorOptions.m_projectIncludePaths.reserve(includePaths.size());
|
||||
for (const auto& path : includePaths)
|
||||
{
|
||||
preprocessorOptions.m_projectIncludePaths.push_back(path);
|
||||
}
|
||||
|
||||
preprocessorOptions.m_predefinedMacros.reserve(predefinedMacros.size());
|
||||
for (const auto& macro : predefinedMacros)
|
||||
{
|
||||
preprocessorOptions.m_predefinedMacros.push_back(macro);
|
||||
}
|
||||
|
||||
return preprocessorOptions;
|
||||
}
|
||||
|
||||
//! @param azslcAdditionalFreeArguments: A string representing series of command line arguments for AZSLc.
|
||||
//! @param dxcAdditionalFreeArguments: A string representing series of command line arguments for DXC.
|
||||
RHI::ShaderCompilerArguments CreateShaderCompilerArguments(
|
||||
AZStd::string_view azslcAdditionalFreeArguments, AZStd::string_view dxcAdditionalFreeArguments) const
|
||||
{
|
||||
RHI::ShaderCompilerArguments shaderCompilerArguments;
|
||||
shaderCompilerArguments.m_azslcWarningLevel = 1;
|
||||
shaderCompilerArguments.m_azslcAdditionalFreeArguments = azslcAdditionalFreeArguments;
|
||||
shaderCompilerArguments.m_defaultMatrixOrder = RHI::MatrixOrder::Row;
|
||||
shaderCompilerArguments.m_dxcAdditionalFreeArguments = dxcAdditionalFreeArguments;
|
||||
|
||||
return shaderCompilerArguments;
|
||||
}
|
||||
|
||||
|
||||
//! @param includePaths A List of folder paths
|
||||
//! @param predefinedMacros A List of strings with format: "name[=value]"
|
||||
//! @param azslcAdditionalFreeArguments A string representing series of command line arguments for AZSLc.
|
||||
//! @param dxcAdditionalFreeArguments: A string representing series of command line arguments for DXC.
|
||||
ShaderBuilder::GlobalBuildOptions CreateGlobalBuildOptions(
|
||||
AZStd::array_view<AZStd::string> includePaths,
|
||||
AZStd::array_view<AZStd::string> predefinedMacros,
|
||||
AZStd::string_view azslcAdditionalFreeArguments,
|
||||
AZStd::string_view dxcAdditionalFreeArguments) const
|
||||
{
|
||||
ShaderBuilder::GlobalBuildOptions globalBuildOptions;
|
||||
globalBuildOptions.m_preprocessorSettings = CreatePreprocessorOptions(includePaths, predefinedMacros);
|
||||
globalBuildOptions.m_compilerArguments =
|
||||
CreateShaderCompilerArguments(azslcAdditionalFreeArguments, dxcAdditionalFreeArguments);
|
||||
return globalBuildOptions;
|
||||
}
|
||||
|
||||
//! @param name Name of the supervariant.
|
||||
//! @param plusArguments A string with command line arguments that contains both C-preprocessor macro definitions
|
||||
//! and other command line arguments for AZSLc.
|
||||
//! @param minusArguments A string with command line arguments that should be removed from the finalized command line arguments.
|
||||
//! it can contain both, C-preprocessor macro definitions and other command line arguments for AZSLc.
|
||||
RPI::ShaderSourceData::SupervariantInfo CreateSupervariantInfo(
|
||||
AZStd::string_view name, AZStd::string_view plusArguments, AZStd::string_view minusArguments) const
|
||||
{
|
||||
RPI::ShaderSourceData::SupervariantInfo supervariantInfo;
|
||||
supervariantInfo.m_name = name;
|
||||
supervariantInfo.m_plusArguments = plusArguments;
|
||||
supervariantInfo.m_minusArguments = minusArguments;
|
||||
return supervariantInfo;
|
||||
}
|
||||
|
||||
bool StringContainsAllSubstrings(AZStd::string_view haystack, AZStd::array_view<AZStd::string> substrings)
|
||||
{
|
||||
return AZStd::all_of(AZ_BEGIN_END(substrings),
|
||||
[&](AZStd::string_view needle) -> bool
|
||||
{
|
||||
return (haystack.find(needle) != AZStd::string::npos);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
bool StringDoesNotContainAnyOneOfTheSubstrings(AZStd::string_view haystack, AZStd::array_view<AZStd::string> substrings)
|
||||
{
|
||||
return AZStd::all_of(AZ_BEGIN_END(substrings), [&](AZStd::string_view needle) -> bool {
|
||||
return (haystack.find(needle) == AZStd::string::npos);
|
||||
});
|
||||
}
|
||||
|
||||
//! @returns: True if all strings in @substring appear in @vectorOfString.
|
||||
//! @remark: Keep in mind that this is not the same as saying that all strings in @vectorOfStrings appear in @substrings.
|
||||
bool VectorContainsAllSubstrings(
|
||||
AZStd::array_view<AZStd::string> vectorOfStrings, AZStd::array_view<AZStd::string> substrings)
|
||||
{
|
||||
return AZStd::all_of(
|
||||
AZ_BEGIN_END(substrings),
|
||||
[&](AZStd::string_view needle) -> bool {
|
||||
bool res = AZStd::any_of(AZ_BEGIN_END(vectorOfStrings),
|
||||
[&](AZStd::string_view haystack) -> bool
|
||||
{
|
||||
return haystack.find(needle) != AZStd::string::npos;
|
||||
}
|
||||
);
|
||||
return res;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
//! @returns: True only if None of the strings in @vectorOfStrings contains any of the strings in @substrings.
|
||||
bool VectorDoesNotContainAnyOneOfTheSubstrings(AZStd::array_view<AZStd::string> vectorOfStrings, AZStd::array_view<AZStd::string> substrings)
|
||||
{
|
||||
return AZStd::all_of(AZ_BEGIN_END(vectorOfStrings), [&](AZStd::string_view haystack) -> bool {
|
||||
return StringDoesNotContainAnyOneOfTheSubstrings(haystack, substrings);
|
||||
});
|
||||
}
|
||||
|
||||
}; // class SupervariantCmdArgumentTests
|
||||
|
||||
|
||||
TEST_F(SupervariantCmdArgumentTests, CommandLineArgumentUtils_ValidateHelperFunctions)
|
||||
{
|
||||
// In this test the idea is to validate the static helper functions in AZ::RHI::ShaderCompilerArguments class
|
||||
// that are useful for command line argument manipulation, etc.
|
||||
AZStd::vector<KeyValueView> argumentList = {
|
||||
{AZSLC_ARG1, ""}, {AZSLC_ARG2, AZSLC_VAL2}, {AZSLC_ARG3, AZSLC_VAL3}, {AZSLC_ARG4, ""}, {AZSLC_ARG5, AZSLC_VAL5},
|
||||
};
|
||||
|
||||
auto argumentsAsString = CreateCmdLineStringFromListOfKeyValues(argumentList);
|
||||
auto listOfArgumentNames = AZ::RHI::CommandLineArgumentUtils::GetListOfArgumentNames(argumentsAsString);
|
||||
|
||||
EXPECT_TRUE(AZStd::all_of(AZ_BEGIN_END(argumentList), [&](const KeyValueView& needle) -> bool {
|
||||
return (AZStd::find(AZ_BEGIN_END(listOfArgumentNames), needle.m_key) != listOfArgumentNames.end()) &&
|
||||
// Make sure the values did not make into the expected list of keys.
|
||||
(AZStd::find(AZ_BEGIN_END(listOfArgumentNames), needle.m_value) == listOfArgumentNames.end());
|
||||
}));
|
||||
|
||||
AZStd::vector<AZStd::string> listOfArgumentsToRemove = { AZSLC_ARG4, AZSLC_ARG2 };
|
||||
auto stringWithRemovedArguments =
|
||||
AZ::RHI::CommandLineArgumentUtils::RemoveArgumentsFromCommandLineString(listOfArgumentsToRemove, argumentsAsString);
|
||||
EXPECT_TRUE(AZStd::all_of(AZ_BEGIN_END(listOfArgumentsToRemove), [&](const AZStd::string& needle) -> bool {
|
||||
return stringWithRemovedArguments.find(needle) == AZStd::string::npos;
|
||||
}));
|
||||
|
||||
AZStd::vector<AZStd::string> listOfSurvivingArguments = {AZSLC_ARG1, AZSLC_ARG3, AZSLC_ARG5};
|
||||
EXPECT_TRUE(AZStd::all_of(AZ_BEGIN_END(listOfSurvivingArguments), [&](const AZStd::string& needle) -> bool {
|
||||
return stringWithRemovedArguments.find(needle) != AZStd::string::npos;
|
||||
}));
|
||||
|
||||
auto stringWithoutExtraSpaces =
|
||||
AZ::RHI::CommandLineArgumentUtils::RemoveExtraSpaces(" --arg1 -arg2 --arg3=foo --arg4=bar ");
|
||||
EXPECT_EQ(stringWithoutExtraSpaces, AZStd::string("--arg1 -arg2 --arg3=foo --arg4=bar"));
|
||||
|
||||
auto stringAsMergedArguments =
|
||||
AZ::RHI::CommandLineArgumentUtils::MergeCommandLineArguments("--arg1 -arg2 --arg3=foo", "--arg3=bar --arg4");
|
||||
EXPECT_EQ(stringAsMergedArguments, AZStd::string("--arg1 -arg2 --arg3=bar --arg4"));
|
||||
|
||||
EXPECT_TRUE(AZ::RHI::CommandLineArgumentUtils::HasMacroDefinitions("-DMACRO"));
|
||||
EXPECT_TRUE(AZ::RHI::CommandLineArgumentUtils::HasMacroDefinitions("-D MACRO"));
|
||||
EXPECT_TRUE(AZ::RHI::CommandLineArgumentUtils::HasMacroDefinitions("--help -D MACRO"));
|
||||
EXPECT_TRUE(AZ::RHI::CommandLineArgumentUtils::HasMacroDefinitions("--help -p -DMACRO --more"));
|
||||
EXPECT_TRUE(AZ::RHI::CommandLineArgumentUtils::HasMacroDefinitions("--help -p -D MACRO=VALUE --more"));
|
||||
EXPECT_FALSE(AZ::RHI::CommandLineArgumentUtils::HasMacroDefinitions("--help -p --more"));
|
||||
EXPECT_FALSE(AZ::RHI::CommandLineArgumentUtils::HasMacroDefinitions("--help -p --more --DFAKE"));
|
||||
EXPECT_FALSE(AZ::RHI::CommandLineArgumentUtils::HasMacroDefinitions("--DFAKE1 --help -p --more --D FAKE2"));
|
||||
}
|
||||
|
||||
TEST_F(SupervariantCmdArgumentTests, ShaderCompilerArguments_ValidateCommandLineArgumentsMerge)
|
||||
{
|
||||
// In this test we validate that AZ::RHI::ShaderCompilerArguments::Merge() works as expected
|
||||
// by merging AZSLC & DXC arguments giving higher priority to the arguments in the "right".
|
||||
|
||||
auto shaderCompilerArgumentsLeft = CreateShaderCompilerArguments(
|
||||
"--azsl1 --azsl2=avalue2a -azsl3 --azsl4=avalue4a",
|
||||
"--dxc1=dvalue1a -dxc2 --dxc3=dvalue3a --dxc4");
|
||||
auto shaderCompilerArgumentsRight = CreateShaderCompilerArguments(
|
||||
"--azsl1 --azsl2=avalue2b -azsl3 --azsl4=avalue4a --azsl5",
|
||||
"--dxc1=dvalue1a -dxc2 --dxc3=dvalue3b --dxc4 --dxc5=dvalue5a");
|
||||
|
||||
shaderCompilerArgumentsLeft.Merge(shaderCompilerArgumentsRight);
|
||||
EXPECT_EQ(shaderCompilerArgumentsLeft.m_azslcAdditionalFreeArguments, "--azsl1 --azsl2=avalue2b -azsl3 --azsl4=avalue4a --azsl5");
|
||||
EXPECT_EQ(shaderCompilerArgumentsLeft.m_dxcAdditionalFreeArguments, "--dxc1=dvalue1a -dxc2 --dxc3=dvalue3b --dxc4 --dxc5=dvalue5a");
|
||||
}
|
||||
|
||||
|
||||
TEST_F(SupervariantCmdArgumentTests, SupervariantInfo_ValidateMemberFunctions)
|
||||
{
|
||||
// In this test all member functions of the ShaderSourceData::SupervariantInfo class
|
||||
// are validated.
|
||||
|
||||
AZStd::vector<KeyValueView> mcppMacrosList = {
|
||||
{MCPP_MACRO1, MCPP_VALUE1},
|
||||
{MCPP_MACRO2, MCPP_VALUE2},
|
||||
{MCPP_MACRO3, MCPP_VALUE3},
|
||||
{MCPP_MACRO4, ""},
|
||||
};
|
||||
|
||||
AZStd::string argumentsToAddOrReplace;
|
||||
AppendMacroDefinitionArgument(argumentsToAddOrReplace, MCPP_MACRO3, MCPP_NEW_VALUE3);
|
||||
AppendCmdLineArgument(argumentsToAddOrReplace, AZSLC_ARG2, AZSLC_NEW_VAL2a);
|
||||
AppendMacroDefinitionArgument(argumentsToAddOrReplace, MCPP_MACRO1, MCPP_NEW_VALUE1);
|
||||
AppendCmdLineArgument(argumentsToAddOrReplace, AZSLC_ARG5, AZSLC_NEW_VAL5);
|
||||
AppendMacroDefinitionArgument(argumentsToAddOrReplace, MCPP_MACRO5, "");
|
||||
AppendCmdLineArgument(argumentsToAddOrReplace, AZSLC_ARG6, "");
|
||||
|
||||
AZStd::string argumentsToRemove;
|
||||
AppendCmdLineArgument(argumentsToRemove, AZSLC_ARG3, "");
|
||||
AppendMacroDefinitionArgument(argumentsToRemove, MCPP_MACRO2, "");
|
||||
AppendCmdLineArgument(argumentsToRemove, AZSLC_ARG4, "");
|
||||
AppendMacroDefinitionArgument(argumentsToRemove, MCPP_MACRO4, "");
|
||||
|
||||
auto supervariantInfo = CreateSupervariantInfo("Dummy", argumentsToAddOrReplace, argumentsToRemove);
|
||||
|
||||
auto macroListToRemove = supervariantInfo.GetCombinedListOfMacroDefinitionNamesToRemove();
|
||||
AZStd::vector<AZStd::string> macroNamesToRemoveThatMustBePresent = { MCPP_MACRO1, MCPP_MACRO2, MCPP_MACRO3, MCPP_MACRO4, MCPP_MACRO5 };
|
||||
EXPECT_EQ(macroListToRemove.size(), macroNamesToRemoveThatMustBePresent.size());
|
||||
EXPECT_TRUE(
|
||||
VectorContainsAllSubstrings(macroListToRemove, macroNamesToRemoveThatMustBePresent)
|
||||
);
|
||||
|
||||
auto macroListToAdd = supervariantInfo.GetMacroDefinitionsToAdd();
|
||||
AZStd::vector<AZStd::string> macroNamesToAddThatMustBePresent = {MCPP_MACRO1, MCPP_MACRO3, MCPP_MACRO5};
|
||||
EXPECT_EQ(macroListToAdd.size(), macroNamesToAddThatMustBePresent.size());
|
||||
EXPECT_TRUE(VectorContainsAllSubstrings(macroListToAdd, macroNamesToAddThatMustBePresent));
|
||||
|
||||
// The result of GetCustomizedArgumentsForAzslc() is the most important value to test
|
||||
AZStd::vector<KeyValueView> freeAzslcArgumentList = {
|
||||
{AZSLC_ARG1, ""}, {AZSLC_ARG2, AZSLC_VAL2}, {AZSLC_ARG3, AZSLC_VAL3}, {AZSLC_ARG4, ""}, {AZSLC_ARG5, AZSLC_VAL5},
|
||||
};
|
||||
AZStd::string azslcArgs = CreateCmdLineStringFromListOfKeyValues(freeAzslcArgumentList);
|
||||
AZStd::string customizedAzslcArgs = supervariantInfo.GetCustomizedArgumentsForAzslc(azslcArgs);
|
||||
|
||||
AZStd::vector<AZStd::string> stringsThatMustBePresent = {
|
||||
AZSLC_ARG1, AZSLC_ARG2, AZSLC_NEW_VAL2a, AZSLC_ARG5, AZSLC_NEW_VAL5, AZSLC_ARG6};
|
||||
EXPECT_TRUE(StringContainsAllSubstrings(customizedAzslcArgs, stringsThatMustBePresent));
|
||||
|
||||
AZStd::vector<AZStd::string> stringsThatCanNotBePresent = { AZSLC_ARG3, AZSLC_VAL3, AZSLC_ARG4,
|
||||
// Because GetCustomizedArgumentsForAzslc() only returns arguments for AZSLc, none of the macro related
|
||||
// arguments can be present
|
||||
MCPP_MACRO1, MCPP_VALUE1, MCPP_NEW_VALUE1,
|
||||
MCPP_MACRO2, MCPP_VALUE2,
|
||||
MCPP_MACRO3, MCPP_VALUE3, MCPP_NEW_VALUE3,
|
||||
MCPP_MACRO4,
|
||||
MCPP_MACRO5
|
||||
};
|
||||
|
||||
EXPECT_TRUE(
|
||||
StringDoesNotContainAnyOneOfTheSubstrings(customizedAzslcArgs, stringsThatCanNotBePresent)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
TEST_F(SupervariantCmdArgumentTests, ShaderAssetBuilder_ValidateInfluenceOfSupervariantInfoOnGlobalBuildOptions)
|
||||
{
|
||||
// In this test we validate how the ShaderAssetBuilder configure the commmand line arguments it passes
|
||||
// to MCPP, AZSLc & DXC. It basically starts with a GlobalBuildOptions, that gets further customized by
|
||||
// the ShaderCompilerArguments from ShaderSourceData(.shader file) and later further customized
|
||||
// by each SupervariantInfo in ShaderSourceData.
|
||||
|
||||
// The first step is to define the initial values of the GlobalBuildOptions.
|
||||
AZStd::vector<KeyValueView> globalMcppMacrosList = {
|
||||
{MCPP_MACRO1, MCPP_VALUE1},
|
||||
{MCPP_MACRO2, MCPP_VALUE2},
|
||||
{MCPP_MACRO3, MCPP_VALUE3},
|
||||
{MCPP_MACRO4, ""},
|
||||
};
|
||||
|
||||
AZStd::vector<KeyValueView> globalAzslArguments = {
|
||||
{AZSLC_ARG1, ""},
|
||||
{AZSLC_ARG2, AZSLC_VAL2},
|
||||
{AZSLC_ARG3, AZSLC_VAL3},
|
||||
{AZSLC_ARG4, ""},
|
||||
{AZSLC_ARG5, AZSLC_VAL5},
|
||||
};
|
||||
|
||||
auto globalBuildOptions = CreateGlobalBuildOptions(
|
||||
AZStd::vector<AZStd::string>(), CreateListOfStringsFromListOfKeyValues(globalMcppMacrosList),
|
||||
CreateCmdLineStringFromListOfKeyValues(globalAzslArguments),
|
||||
"" /* Don't care about DXC in this test */);
|
||||
|
||||
// The second step is to load the Shader Compiler Arguments from the .shader file.
|
||||
// These arguments will be merged in @globalBuildOptions, but the .shader arguments have
|
||||
// higher priority.
|
||||
AZStd::vector<KeyValueView> shaderAzslArguments = {
|
||||
{AZSLC_ARG2, AZSLC_NEW_VAL2a},
|
||||
{AZSLC_ARG6, ""},
|
||||
};
|
||||
auto shaderCompilerArguments = CreateShaderCompilerArguments(
|
||||
CreateCmdLineStringFromListOfKeyValues(shaderAzslArguments), "" /* Don't care about DXC in this test */);
|
||||
globalBuildOptions.m_compilerArguments.Merge(shaderCompilerArguments);
|
||||
|
||||
// Let's create the dummy supervariant. It will have some MCPP & AZSLc arguments to be added/replaced AND other MCPP & AZSLc arguments to be removed.
|
||||
AZStd::vector<KeyValueView> supervariantAzslArgumentsToAdd = {
|
||||
{AZSLC_ARG2, AZSLC_NEW_VAL2b},
|
||||
{AZSLC_ARG7, ""},
|
||||
};
|
||||
AZStd::vector<KeyValueView> supervariantMacroDefinitionsToAdd = {
|
||||
{MCPP_MACRO1, MCPP_NEW_VALUE1},
|
||||
{MCPP_MACRO3, MCPP_NEW_VALUE3},
|
||||
{MCPP_MACRO5, ""},
|
||||
};
|
||||
auto supervariantArgumentsToAdd = CreateCmdLineStringFromListOfKeyValues(supervariantAzslArgumentsToAdd) +
|
||||
CreateMacroDefinitionCmdLineStringFromListOfKeyValues(supervariantMacroDefinitionsToAdd);
|
||||
|
||||
AZStd::vector<KeyValueView> supervariantAzslArgumentsToRemove = {
|
||||
{AZSLC_ARG4, ""},
|
||||
{AZSLC_ARG1, ""},
|
||||
};
|
||||
AZStd::vector<KeyValueView> supervariantMacrosToRemove = {
|
||||
{MCPP_MACRO2, ""},
|
||||
{MCPP_MACRO4, ""},
|
||||
};
|
||||
auto supervariantArgumentsToRemove = CreateCmdLineStringFromListOfKeyValues(supervariantAzslArgumentsToRemove) +
|
||||
CreateMacroDefinitionCmdLineStringFromListOfKeyValues(supervariantMacrosToRemove);
|
||||
|
||||
//CreateMacroDefinitionCmdLineStringFromListOfKeyValues
|
||||
auto supervariantInfo = CreateSupervariantInfo("Dummy",
|
||||
supervariantArgumentsToAdd, // These arguments will be added or replace existing ones.
|
||||
supervariantArgumentsToRemove); // These arguments must be removed.
|
||||
|
||||
AZStd::vector<AZStd::string> macroDefinitionNamesToRemove = supervariantInfo.GetCombinedListOfMacroDefinitionNamesToRemove();
|
||||
globalBuildOptions.m_preprocessorSettings.RemovePredefinedMacros(macroDefinitionNamesToRemove);
|
||||
AZStd::vector<AZStd::string> macroDefinitionsToAdd = supervariantInfo.GetMacroDefinitionsToAdd();
|
||||
globalBuildOptions.m_preprocessorSettings.m_predefinedMacros.insert(
|
||||
globalBuildOptions.m_preprocessorSettings.m_predefinedMacros.end(), macroDefinitionsToAdd.begin(), macroDefinitionsToAdd.end());
|
||||
|
||||
// Validate macro definitions that must be present.
|
||||
EXPECT_TRUE(
|
||||
VectorContainsAllSubstrings(
|
||||
globalBuildOptions.m_preprocessorSettings.m_predefinedMacros,
|
||||
AZStd::vector<AZStd::string>({MCPP_MACRO1, MCPP_NEW_VALUE1, MCPP_MACRO3, MCPP_NEW_VALUE3, MCPP_MACRO5}))
|
||||
);
|
||||
|
||||
// Validate macro definitions that can't be present.
|
||||
EXPECT_TRUE(
|
||||
VectorDoesNotContainAnyOneOfTheSubstrings(
|
||||
globalBuildOptions.m_preprocessorSettings.m_predefinedMacros,
|
||||
AZStd::vector<AZStd::string>({MCPP_MACRO2, MCPP_VALUE3, MCPP_MACRO4}))
|
||||
);
|
||||
|
||||
AZStd::string azslcArgsFromGlobalBuildOptions = globalBuildOptions.m_compilerArguments.MakeAdditionalAzslcCommandLineString();
|
||||
|
||||
// The result of GetCustomizedArgumentsForAzslc() is the most important value to test
|
||||
AZStd::string customizedAzslcArgs = supervariantInfo.GetCustomizedArgumentsForAzslc(azslcArgsFromGlobalBuildOptions);
|
||||
|
||||
EXPECT_TRUE(
|
||||
StringContainsAllSubstrings(customizedAzslcArgs, CreateListOfSingleStringsFromListOfKeyValues(supervariantAzslArgumentsToAdd))
|
||||
);
|
||||
|
||||
EXPECT_TRUE(
|
||||
StringDoesNotContainAnyOneOfTheSubstrings(customizedAzslcArgs, CreateListOfSingleStringsFromListOfKeyValues(supervariantAzslArgumentsToRemove))
|
||||
);
|
||||
|
||||
EXPECT_TRUE(
|
||||
StringContainsAllSubstrings(customizedAzslcArgs, AZStd::vector<AZStd::string>({AZSLC_ARG3, AZSLC_VAL3, AZSLC_ARG5, AZSLC_VAL5}))
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
} //namespace UnitTest
|
||||
|
||||
AZ_UNIT_TEST_HOOK(DEFAULT_UNIT_TEST_ENV);
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
#
|
||||
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
# its licensors.
|
||||
#
|
||||
# For complete copyright and license terms please see the LICENSE at the root of this
|
||||
# distribution (the "License"). All use of this software is governed by the License,
|
||||
# or, if provided, by the license below or the license accompanying this file. Do not
|
||||
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
#
|
||||
|
||||
set(FILES
|
||||
Tests/Common/ShaderBuilderTestFixture.h
|
||||
Tests/Common/ShaderBuilderTestFixture.cpp
|
||||
Tests/SupervariantCmdArgumentTests.cpp
|
||||
)
|
||||
@@ -77,10 +77,9 @@ PSDepthOutput MainPS(VSDepthOutput IN, bool isFrontFace : SV_IsFrontFace)
|
||||
|
||||
if(ShouldHandleParallaxInDepthShaders())
|
||||
{
|
||||
// We support two UV streams, but only a single stream of tangent/bitangent. So for UV[1+] we generated the tangent/bitangent in screen-space.
|
||||
float3 tangents[UvSetCount] = { IN.m_tangent.xyz, float3(0, 0, 0) };
|
||||
float3 bitangents[UvSetCount] = { IN.m_bitangent.xyz, float3(0, 0, 0) };
|
||||
PrepareGeneratedTangent(IN.m_normal, IN.m_worldPosition, isFrontFace, IN.m_uv, UvSetCount, tangents, bitangents, 1);
|
||||
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();
|
||||
|
||||
@@ -117,19 +117,12 @@ VSOutput EnhancedPbr_ForwardPassVS(VSInput IN)
|
||||
PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace, out float depth)
|
||||
{
|
||||
// ------- Tangents & Bitangets -------
|
||||
float3 tangents[UvSetCount] = { IN.m_tangent.xyz, IN.m_tangent.xyz };
|
||||
float3 bitangents[UvSetCount] = { IN.m_bitangent.xyz, IN.m_bitangent.xyz };
|
||||
|
||||
// We support two UV streams, but only a single stream of tangent/bitangent. So for UV[1+] we generated the tangent/bitangent in screen-space.
|
||||
float3 tangents[UvSetCount] = { IN.m_tangent.xyz, float3(0, 0, 0) };
|
||||
float3 bitangents[UvSetCount] = { IN.m_bitangent.xyz, float3(0, 0, 0) };
|
||||
|
||||
if ((o_parallax_feature_enabled && !o_enableSubsurfaceScattering && MaterialSrg::m_parallaxUvIndex != 0)
|
||||
|| (o_normal_useTexture && MaterialSrg::m_normalMapUvIndex != 0)
|
||||
|| (o_clearCoat_enabled && o_clearCoat_normal_useTexture && MaterialSrg::m_clearCoatNormalMapUvIndex != 0)
|
||||
|| (o_detail_normal_useTexture && MaterialSrg::m_detail_allMapsUvIndex != 0))
|
||||
if ((o_parallax_feature_enabled && !o_enableSubsurfaceScattering) || o_normal_useTexture || (o_clearCoat_enabled && o_clearCoat_normal_useTexture) || o_detail_normal_useTexture)
|
||||
{
|
||||
// Generate the tangent/bitangent for UV[1+]
|
||||
const int startIndex = 1;
|
||||
PrepareGeneratedTangent(IN.m_normal, IN.m_worldPosition, isFrontFace, IN.m_uv, UvSetCount, tangents, bitangents, startIndex);
|
||||
PrepareGeneratedTangent(IN.m_normal, IN.m_worldPosition, isFrontFace, IN.m_uv, UvSetCount, tangents, bitangents);
|
||||
}
|
||||
|
||||
// ------- Depth & Parallax -------
|
||||
@@ -260,7 +253,7 @@ PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace, out float
|
||||
// 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);
|
||||
surface.anisotropy.Init(surface.normal, IN.m_tangent, IN.m_bitangent, anisotropyAngle, anisotropyFactor, surface.roughnessA);
|
||||
}
|
||||
|
||||
// ------- Lighting Data -------
|
||||
|
||||
@@ -80,10 +80,10 @@ PSDepthOutput MainPS(VertexOutput IN, bool isFrontFace : SV_IsFrontFace)
|
||||
{
|
||||
static const float ShadowMapDepthBias = 0.000001;
|
||||
|
||||
// We support two UV streams, but only a single stream of tangent/bitangent. So for UV[1+] we generated the tangent/bitangent in screen-space.
|
||||
float3 tangents[UvSetCount] = { IN.m_tangent.xyz, float3(0, 0, 0) };
|
||||
float3 bitangents[UvSetCount] = { IN.m_bitangent.xyz, float3(0, 0, 0) };
|
||||
PrepareGeneratedTangent(IN.m_normal, IN.m_worldPosition, isFrontFace, IN.m_uv, UvSetCount, tangents, bitangents, 1);
|
||||
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();
|
||||
|
||||
@@ -180,17 +180,12 @@ PbrLightingOutput SkinPS_Common(VSOutput IN)
|
||||
float3x3 uvMatrix = CreateIdentity3x3();
|
||||
|
||||
// ------- Tangents & Bitangets -------
|
||||
float3 tangents[UvSetCount] = { IN.m_tangent.xyz, IN.m_tangent.xyz };
|
||||
float3 bitangents[UvSetCount] = { IN.m_bitangent.xyz, IN.m_bitangent.xyz };
|
||||
|
||||
// We support two UV streams, but only a single stream of tangent/bitangent. So for UV[1+] we generated the tangent/bitangent in screen-space.
|
||||
float3 tangents[UvSetCount] = { IN.m_tangent.xyz, float3(0, 0, 0) };
|
||||
float3 bitangents[UvSetCount] = { IN.m_bitangent.xyz, float3(0, 0, 0) };
|
||||
|
||||
if ( (o_normal_useTexture && MaterialSrg::m_normalMapUvIndex != 0) ||
|
||||
(o_detail_normal_useTexture && MaterialSrg::m_detail_allMapsUvIndex != 0))
|
||||
if (o_normal_useTexture || o_detail_normal_useTexture)
|
||||
{
|
||||
// Generate the tangent/bitangent for UV[1+]
|
||||
const int startIndex = 1;
|
||||
PrepareGeneratedTangent(IN.m_normal, IN.m_worldPosition, isFrontFace, IN.m_uv, UvSetCount, tangents, bitangents, startIndex);
|
||||
PrepareGeneratedTangent(IN.m_normal, IN.m_worldPosition, isFrontFace, IN.m_uv, UvSetCount, tangents, bitangents);
|
||||
}
|
||||
|
||||
Surface surface;
|
||||
|
||||
+126
-108
@@ -3,11 +3,6 @@
|
||||
"propertyLayout": {
|
||||
"version": 3,
|
||||
"groups": [
|
||||
{
|
||||
"id": "general",
|
||||
"displayName": "General",
|
||||
"description": "General settings."
|
||||
},
|
||||
{
|
||||
"id": "blend",
|
||||
"displayName": "Blend Settings",
|
||||
@@ -29,6 +24,11 @@
|
||||
"displayName": "Irradiance",
|
||||
"description": "Properties for configuring the irradiance used in global illumination."
|
||||
},
|
||||
{
|
||||
"id": "general",
|
||||
"displayName": "General",
|
||||
"description": "General settings."
|
||||
},
|
||||
//##############################################################################################
|
||||
// Layer 1 Groups
|
||||
//##############################################################################################
|
||||
@@ -74,8 +74,8 @@
|
||||
},
|
||||
{
|
||||
"id": "layer1_parallax",
|
||||
"displayName": "Layer 1: Parallax Mapping",
|
||||
"description": "Properties for parallax effect produced by depthmap."
|
||||
"displayName": "Layer 1: Displacement",
|
||||
"description": "Properties for surface displacement, which can be used for displacement-based blending and/or a parallax effect."
|
||||
},
|
||||
{
|
||||
"id": "layer1_uv",
|
||||
@@ -127,8 +127,8 @@
|
||||
},
|
||||
{
|
||||
"id": "layer2_parallax",
|
||||
"displayName": "Layer 2: Parallax Mapping",
|
||||
"description": "Properties for parallax effect produced by depthmap."
|
||||
"displayName": "Layer 2: Displacement",
|
||||
"description": "Properties for surface displacement, which can be used for displacement-based blending and/or a parallax effect."
|
||||
},
|
||||
{
|
||||
"id": "layer2_uv",
|
||||
@@ -180,8 +180,8 @@
|
||||
},
|
||||
{
|
||||
"id": "layer3_parallax",
|
||||
"displayName": "Layer 3: Parallax Mapping",
|
||||
"description": "Properties for parallax effect produced by depthmap."
|
||||
"displayName": "Layer 3: Displacement",
|
||||
"description": "Properties for surface displacement, which can be used for displacement-based blending and/or a parallax effect."
|
||||
},
|
||||
{
|
||||
"id": "layer3_uv",
|
||||
@@ -194,18 +194,6 @@
|
||||
// General Properties
|
||||
//##############################################################################################
|
||||
"general": [
|
||||
{
|
||||
"id": "debugDrawMode",
|
||||
"displayName": "Debug Draw Mode",
|
||||
"description": "Enables various debug view features.",
|
||||
"type": "Enum",
|
||||
"enumValues": [ "None", "BlendSource", "DepthMaps" ],
|
||||
"defaultValue": "None",
|
||||
"connection": {
|
||||
"type": "ShaderOption",
|
||||
"id": "o_debugDrawMode"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "applySpecularAA",
|
||||
"displayName": "Apply Specular AA",
|
||||
@@ -322,16 +310,16 @@
|
||||
"displayName": "Blend Source",
|
||||
"description": "The source to use for defining the blend mask. Note VertexColors mode will still use the texture as a fallback if the mesh does not have a COLOR0 stream.",
|
||||
"type": "Enum",
|
||||
"enumValues": ["TextureMap", "VertexColors"],
|
||||
"defaultValue": "TextureMap",
|
||||
"enumValues": ["BlendMaskTexture", "BlendMaskVertexColors", "Displacement", "Displacement_With_BlendMaskTexture", "Displacement_With_BlendMaskVertexColors"],
|
||||
"defaultValue": "BlendMaskTexture",
|
||||
"connection": {
|
||||
"type": "ShaderOption",
|
||||
"id": "o_blendSource"
|
||||
"id": "o_layerBlendSource"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "textureMap",
|
||||
"displayName": "Blend Mask",
|
||||
"displayName": "Blend Mask Texture",
|
||||
"description": "RGB image where each channel is the blend mask for one of the three available layers.",
|
||||
"type": "Image",
|
||||
"defaultValue": "Textures/DefaultBlendMask_layers.png",
|
||||
@@ -351,6 +339,32 @@
|
||||
"type": "ShaderInput",
|
||||
"id": "m_blendMaskUvIndex"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "displacementBlendDistance",
|
||||
"displayName": "Blend Distance",
|
||||
"description": "Adjusts how smoothly to transition between layers when displacement blending is enabled.",
|
||||
"type": "Float",
|
||||
"defaultValue": 0.0,
|
||||
"min": 0.0,
|
||||
"softMax": 0.1,
|
||||
"step": 0.001,
|
||||
"connection": {
|
||||
"type": "ShaderInput",
|
||||
"id": "m_displacementBlendDistance"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "debugDrawMode",
|
||||
"displayName": "Debug Draw Mode",
|
||||
"description": "Enables various debug view features.",
|
||||
"type": "Enum",
|
||||
"enumValues": [ "None", "BlendMask", "Displacement", "FinalBlendWeights" ],
|
||||
"defaultValue": "None",
|
||||
"connection": {
|
||||
"type": "ShaderOption",
|
||||
"id": "o_debugDrawMode"
|
||||
}
|
||||
}
|
||||
],
|
||||
"parallax": [
|
||||
@@ -359,7 +373,11 @@
|
||||
"displayName": "Enable",
|
||||
"description": "Whether to enable the parallax feature for this material.",
|
||||
"type": "Bool",
|
||||
"defaultValue": false
|
||||
"defaultValue": false,
|
||||
"connection": {
|
||||
"type": "ShaderOption",
|
||||
"id": "o_parallax_feature_enabled"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "parallaxUv",
|
||||
@@ -1090,27 +1108,38 @@
|
||||
}
|
||||
],
|
||||
"layer1_parallax": [
|
||||
{
|
||||
"id": "enable",
|
||||
"displayName": "Enable",
|
||||
"description": "Whether to enable the parallax feature.",
|
||||
"type": "Bool",
|
||||
"defaultValue": false
|
||||
},
|
||||
{
|
||||
"id": "textureMap",
|
||||
"displayName": "Texture Map",
|
||||
"description": "Depthmap to create parallax effect.",
|
||||
"description": "Displacement texture map, which can be used for layer blending and/or a parallax effect.",
|
||||
"type": "Image",
|
||||
"connection": {
|
||||
"type": "ShaderInput",
|
||||
"id": "m_layer1_m_depthMap"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "useTexture",
|
||||
"displayName": "Use Texture",
|
||||
"description": "Whether to use the texture map.",
|
||||
"type": "Bool",
|
||||
"defaultValue": true
|
||||
},
|
||||
{
|
||||
"id": "invert",
|
||||
"displayName": "Invert",
|
||||
"description": "Invert the displacement map",
|
||||
"type": "Bool",
|
||||
"defaultValue": true,
|
||||
"connection": {
|
||||
"type": "ShaderInput",
|
||||
"id": "m_layer1_m_depthInverted"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "factor",
|
||||
"displayName": "Heightmap Scale",
|
||||
"description": "The total height of the heightmap in local model units.",
|
||||
"displayName": "Scale",
|
||||
"description": "The total height of the displacement texture map in local model units.",
|
||||
"type": "Float",
|
||||
"defaultValue": 0.0,
|
||||
"min": 0.0,
|
||||
@@ -1132,17 +1161,6 @@
|
||||
"type": "ShaderInput",
|
||||
"id": "m_layer1_m_depthOffset"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "invert",
|
||||
"displayName": "Invert",
|
||||
"description": "Invert to depthmap if the texture is heightmap",
|
||||
"type": "Bool",
|
||||
"defaultValue": true,
|
||||
"connection": {
|
||||
"type": "ShaderInput",
|
||||
"id": "m_layer1_m_depthInverted"
|
||||
}
|
||||
}
|
||||
],
|
||||
"layer1_uv": [
|
||||
@@ -1796,27 +1814,38 @@
|
||||
}
|
||||
],
|
||||
"layer2_parallax": [
|
||||
{
|
||||
"id": "enable",
|
||||
"displayName": "Enable",
|
||||
"description": "Whether to enable the parallax feature.",
|
||||
"type": "Bool",
|
||||
"defaultValue": false
|
||||
},
|
||||
{
|
||||
"id": "textureMap",
|
||||
"displayName": "Texture Map",
|
||||
"description": "Depthmap to create parallax effect.",
|
||||
"description": "Displacement texture map, which can be used for layer blending and/or a parallax effect.",
|
||||
"type": "Image",
|
||||
"connection": {
|
||||
"type": "ShaderInput",
|
||||
"id": "m_layer2_m_depthMap"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "useTexture",
|
||||
"displayName": "Use Texture",
|
||||
"description": "Whether to use the texture map.",
|
||||
"type": "Bool",
|
||||
"defaultValue": true
|
||||
},
|
||||
{
|
||||
"id": "invert",
|
||||
"displayName": "Invert",
|
||||
"description": "Invert the displacement map",
|
||||
"type": "Bool",
|
||||
"defaultValue": true,
|
||||
"connection": {
|
||||
"type": "ShaderInput",
|
||||
"id": "m_layer2_m_depthInverted"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "factor",
|
||||
"displayName": "Heightmap Scale",
|
||||
"description": "The total height of the heightmap in local model units.",
|
||||
"displayName": "Scale",
|
||||
"description": "The total height of the displacement texture map in local model units.",
|
||||
"type": "Float",
|
||||
"defaultValue": 0.0,
|
||||
"min": 0.0,
|
||||
@@ -1838,17 +1867,6 @@
|
||||
"type": "ShaderInput",
|
||||
"id": "m_layer2_m_depthOffset"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "invert",
|
||||
"displayName": "Invert",
|
||||
"description": "Invert to depthmap if the texture is heightmap",
|
||||
"type": "Bool",
|
||||
"defaultValue": true,
|
||||
"connection": {
|
||||
"type": "ShaderInput",
|
||||
"id": "m_layer2_m_depthInverted"
|
||||
}
|
||||
}
|
||||
],
|
||||
"layer2_uv": [
|
||||
@@ -2502,27 +2520,38 @@
|
||||
}
|
||||
],
|
||||
"layer3_parallax": [
|
||||
{
|
||||
"id": "enable",
|
||||
"displayName": "Enable",
|
||||
"description": "Whether to enable the parallax feature.",
|
||||
"type": "Bool",
|
||||
"defaultValue": false
|
||||
},
|
||||
{
|
||||
"id": "textureMap",
|
||||
"displayName": "Texture Map",
|
||||
"description": "Depthmap to create parallax effect.",
|
||||
"description": "Displacement texture map, which can be used for layer blending and/or a parallax effect.",
|
||||
"type": "Image",
|
||||
"connection": {
|
||||
"type": "ShaderInput",
|
||||
"id": "m_layer3_m_depthMap"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "useTexture",
|
||||
"displayName": "Use Texture",
|
||||
"description": "Whether to use the texture map.",
|
||||
"type": "Bool",
|
||||
"defaultValue": true
|
||||
},
|
||||
{
|
||||
"id": "invert",
|
||||
"displayName": "Invert",
|
||||
"description": "Invert the displacement map",
|
||||
"type": "Bool",
|
||||
"defaultValue": true,
|
||||
"connection": {
|
||||
"type": "ShaderInput",
|
||||
"id": "m_layer3_m_depthInverted"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "factor",
|
||||
"displayName": "Heightmap Scale",
|
||||
"description": "The total height of the heightmap in local model units.",
|
||||
"displayName": "Scale",
|
||||
"description": "The total height of the displacement texture map in local model units.",
|
||||
"type": "Float",
|
||||
"defaultValue": 0.0,
|
||||
"min": 0.0,
|
||||
@@ -2544,17 +2573,6 @@
|
||||
"type": "ShaderInput",
|
||||
"id": "m_layer3_m_depthOffset"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "invert",
|
||||
"displayName": "Invert",
|
||||
"description": "Invert to depthmap if the texture is heightmap",
|
||||
"type": "Bool",
|
||||
"defaultValue": true,
|
||||
"connection": {
|
||||
"type": "ShaderInput",
|
||||
"id": "m_layer3_m_depthInverted"
|
||||
}
|
||||
}
|
||||
],
|
||||
"layer3_uv": [
|
||||
@@ -2699,7 +2717,7 @@
|
||||
{
|
||||
"type": "Lua",
|
||||
"args": {
|
||||
"file": "StandardMultilayerPBR_Parallax.lua"
|
||||
"file": "StandardMultilayerPBR_Displacement.lua"
|
||||
}
|
||||
},
|
||||
//##############################################################################################
|
||||
@@ -2816,12 +2834,12 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Lua",
|
||||
"type": "UseTexture",
|
||||
"args": {
|
||||
"file": "StandardMultilayerPBR_ParallaxPerLayer.lua",
|
||||
"propertyNamePrefix": "layer1_",
|
||||
"srgNamePrefix": "m_layer1_",
|
||||
"optionsNamePrefix": "o_layer1_"
|
||||
"textureProperty": "layer1_parallax.textureMap",
|
||||
"useTextureProperty": "layer1_parallax.useTexture",
|
||||
"dependentProperties": ["layer1_parallax.factor", "layer1_parallax.invert"],
|
||||
"shaderOption": "o_layer1_o_useDepthMap"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -2953,12 +2971,12 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Lua",
|
||||
"type": "UseTexture",
|
||||
"args": {
|
||||
"file": "StandardMultilayerPBR_ParallaxPerLayer.lua",
|
||||
"propertyNamePrefix": "layer2_",
|
||||
"srgNamePrefix": "m_layer2_",
|
||||
"optionsNamePrefix": "o_layer2_"
|
||||
"textureProperty": "layer2_parallax.textureMap",
|
||||
"useTextureProperty": "layer2_parallax.useTexture",
|
||||
"dependentProperties": ["layer2_parallax.factor", "layer2_parallax.invert"],
|
||||
"shaderOption": "o_layer2_o_useDepthMap"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -3090,14 +3108,14 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Lua",
|
||||
"type": "UseTexture",
|
||||
"args": {
|
||||
"file": "StandardMultilayerPBR_ParallaxPerLayer.lua",
|
||||
"propertyNamePrefix": "layer3_",
|
||||
"srgNamePrefix": "m_layer3_",
|
||||
"optionsNamePrefix": "o_layer3_"
|
||||
"textureProperty": "layer3_parallax.textureMap",
|
||||
"useTextureProperty": "layer3_parallax.useTexture",
|
||||
"dependentProperties": ["layer3_parallax.factor", "layer3_parallax.invert"],
|
||||
"shaderOption": "o_layer3_o_useDepthMap"
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
// Maps 2D scale, offset, and rotate properties into a float3x3 transform matrix.
|
||||
"type": "Transform2D",
|
||||
|
||||
+272
-64
@@ -15,6 +15,7 @@
|
||||
#include <Atom/Features/SrgSemantics.azsli>
|
||||
#include <Atom/RPI/ShaderResourceGroups/DefaultDrawSrg.azsli>
|
||||
#include <Atom/Features/PBR/LightingOptions.azsli>
|
||||
#include <Atom/RPI/Math.azsli>
|
||||
|
||||
#include "MaterialInputs/BaseColorInput.azsli"
|
||||
#include "MaterialInputs/RoughnessInput.azsli"
|
||||
@@ -42,9 +43,19 @@ COMMON_SRG_INPUTS_PARALLAX(prefix)
|
||||
|
||||
ShaderResourceGroup MaterialSrg : SRG_PerMaterial
|
||||
{
|
||||
Texture2D m_blendMaskTexture;
|
||||
Texture2D m_blendMaskTexture;
|
||||
uint m_blendMaskUvIndex;
|
||||
|
||||
// When parallax mapping is used, these limit the heightmap intersection search range to the narrowest band possible, to give the best quality result.
|
||||
// These are also support other calculations related to displacement-based blending, even when parallax is not used.
|
||||
float m_displacementMin; // The lowest displacement value possible from all layers combined (negative values are below the surface)
|
||||
float m_displacementMax; // The highest displacement value possible from all layers combined (negative values are below the surface)
|
||||
|
||||
// When displacement-based blending is used, this is the height range where the surface properties of different layers will be blended together.
|
||||
// We use an absolute value rather than a relative factor because disconnecting this property from the influence of other properties makes per-layer
|
||||
// displacement adjustments feel more natural if they don't impact the blend distance.
|
||||
float m_displacementBlendDistance;
|
||||
|
||||
// Auto-generate material SRG fields for common inputs for each layer
|
||||
DEFINE_LAYER_SRG_INPUTS(m_layer1_)
|
||||
DEFINE_LAYER_SRG_INPUTS(m_layer2_)
|
||||
@@ -61,10 +72,6 @@ ShaderResourceGroup MaterialSrg : SRG_PerMaterial
|
||||
|
||||
uint m_parallaxUvIndex;
|
||||
|
||||
// These are used to limit the heightmap intersection search range to the narrowest band possible, to give the best quality result.
|
||||
float m_displacementMin; // The lowest displacement value possible from all layers combined
|
||||
float m_displacementMax; // The highest displacement value possible from all layers combined
|
||||
|
||||
float3x3 m_uvMatrix;
|
||||
float4 m_pad4; // [GFX TODO][ATOM-14595] This is a workaround for a data stomping bug. Remove once it's fixed.
|
||||
float3x3 m_uvMatrixInverse;
|
||||
@@ -98,11 +105,11 @@ ShaderResourceGroup MaterialSrg : SRG_PerMaterial
|
||||
option bool o_layer2_enabled;
|
||||
option bool o_layer3_enabled;
|
||||
|
||||
enum class DebugDrawMode { None, BlendSource, DepthMaps };
|
||||
enum class DebugDrawMode { None, BlendMask, Displacement, FinalBlendWeights };
|
||||
option DebugDrawMode o_debugDrawMode;
|
||||
|
||||
enum class BlendMaskSource { TextureMap, VertexColors, Fallback };
|
||||
option BlendMaskSource o_blendSource;
|
||||
enum class LayerBlendSource { BlendMaskTexture, BlendMaskVertexColors, Displacement, Displacement_With_BlendMaskTexture, Displacement_With_BlendMaskVertexColors, Fallback };
|
||||
option LayerBlendSource o_layerBlendSource;
|
||||
|
||||
// Indicates whether the vertex input struct's "m_optional_blendMask" is bound. If false, it is not safe to read from m_optional_blendMask.
|
||||
// This option gets set automatically by the system at runtime; there is a soft naming convention that associates it with m_optional_blendMask.
|
||||
@@ -111,53 +118,83 @@ option BlendMaskSource o_blendSource;
|
||||
option bool o_blendMask_isBound;
|
||||
|
||||
// ------ Blend Utilities ----------------------------------------
|
||||
|
||||
// This is mainly used to pass extra data to the GetDepth callback function during the parallax depth search.
|
||||
// But since we have it, we use it in some other functions as well rather than passing it around.
|
||||
static float3 s_blendMaskFromVertexStream;
|
||||
|
||||
//! Returns the BlendMaskSource that will actually be used when rendering (not necessarily the same BlendMaskSource specified by the user)
|
||||
BlendMaskSource GetFinalBlendMaskSource()
|
||||
// TODO: Consider storing the result of GetFinalLayerBlendSource() in a static similar to s_blendMaskFromVertexStream. That might give better performance when variants aren't used.
|
||||
|
||||
//! Returns the LayerBlendSource that will actually be used when rendering (not necessarily the same LayerBlendSource specified by the user)
|
||||
LayerBlendSource GetFinalLayerBlendSource()
|
||||
{
|
||||
if(o_blendSource == BlendMaskSource::TextureMap)
|
||||
if(o_layerBlendSource == LayerBlendSource::BlendMaskTexture)
|
||||
{
|
||||
return BlendMaskSource::TextureMap;
|
||||
return o_layerBlendSource;
|
||||
}
|
||||
else if(o_blendSource == BlendMaskSource::VertexColors)
|
||||
else if(o_layerBlendSource == LayerBlendSource::BlendMaskVertexColors)
|
||||
{
|
||||
if(o_blendMask_isBound)
|
||||
{
|
||||
return BlendMaskSource::VertexColors;
|
||||
return o_layerBlendSource;
|
||||
}
|
||||
else
|
||||
{
|
||||
return BlendMaskSource::TextureMap;
|
||||
return LayerBlendSource::Fallback;
|
||||
}
|
||||
}
|
||||
else if(o_layerBlendSource == LayerBlendSource::Displacement)
|
||||
{
|
||||
return o_layerBlendSource;
|
||||
}
|
||||
else if(o_layerBlendSource == LayerBlendSource::Displacement_With_BlendMaskTexture)
|
||||
{
|
||||
return o_layerBlendSource;
|
||||
}
|
||||
else if(o_layerBlendSource == LayerBlendSource::Displacement_With_BlendMaskVertexColors)
|
||||
{
|
||||
if(o_blendMask_isBound)
|
||||
{
|
||||
return o_layerBlendSource;
|
||||
}
|
||||
else
|
||||
{
|
||||
return LayerBlendSource::Displacement;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return BlendMaskSource::Fallback;
|
||||
return LayerBlendSource::Fallback;
|
||||
}
|
||||
}
|
||||
|
||||
//! Return the raw blend source values directly from the blend mask or vertex colors, depending on the available data and configuration.
|
||||
//! Return the applicable blend mask values from the blend mask texture or vertex colors, and filters out any that don't apply.
|
||||
//! layer1 is an implicit base layer
|
||||
//! layer2 is weighted by r
|
||||
//! layer3 is weighted by g
|
||||
//! layer2 mask is in the r channel
|
||||
//! layer3 mask is in the g channel
|
||||
//! b is reserved for perhaps a dedicated puddle layer
|
||||
float3 GetBlendSourceValues(float2 uv)
|
||||
//! @param blendSource indicates where to get the blend mask from
|
||||
//! @param blendMaskUv for sampling a blend mask texture, if that's the blend source
|
||||
//! @param blendMaskVertexColors the vertex color values to use for the blend mask, if that's the blend source
|
||||
//! @return the blend mask values, or 0 if there is no blend mask
|
||||
float3 GetApplicableBlendMaskValues(LayerBlendSource blendSource, float2 blendMaskUv, float3 blendMaskVertexColors)
|
||||
{
|
||||
float3 blendSourceValues = float3(0,0,0);
|
||||
|
||||
if(o_layer2_enabled || o_layer3_enabled)
|
||||
{
|
||||
switch(GetFinalBlendMaskSource())
|
||||
switch(blendSource)
|
||||
{
|
||||
case BlendMaskSource::TextureMap:
|
||||
blendSourceValues = MaterialSrg::m_blendMaskTexture.Sample(MaterialSrg::m_sampler, uv).rgb;
|
||||
case LayerBlendSource::Displacement:
|
||||
// In this case the blend mask has no effect, returning (1,1,1) disables any impact of the mask.
|
||||
blendSourceValues = float3(1,1,1);
|
||||
break;
|
||||
case BlendMaskSource::VertexColors:
|
||||
blendSourceValues = s_blendMaskFromVertexStream;
|
||||
case LayerBlendSource::BlendMaskTexture:
|
||||
case LayerBlendSource::Displacement_With_BlendMaskTexture:
|
||||
blendSourceValues = MaterialSrg::m_blendMaskTexture.Sample(MaterialSrg::m_sampler, blendMaskUv).rgb;
|
||||
break;
|
||||
case LayerBlendSource::BlendMaskVertexColors:
|
||||
case LayerBlendSource::Displacement_With_BlendMaskVertexColors:
|
||||
blendSourceValues = blendMaskVertexColors;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -175,29 +212,96 @@ float3 GetBlendSourceValues(float2 uv)
|
||||
return blendSourceValues;
|
||||
}
|
||||
|
||||
//! Return the final blend mask values to be used for rendering, based on the available data and configuration.
|
||||
//! When dealing with masks for displacement-based blending, we sometimes need to push the value below the min displacement to make it disappear.
|
||||
float GetSubMinDisplacement()
|
||||
{
|
||||
return MaterialSrg::m_displacementMin - 0.001;
|
||||
//return MaterialSrg::m_displacementMin - max(MaterialSrg::m_displacementBlendDistance, 0.001);
|
||||
}
|
||||
|
||||
float3 ApplyBlendMaskToDepthValues(float3 blendMaskValues, float3 layerDepthValues, float zeroMaskDisplacement);
|
||||
|
||||
//! Return the final blend weights to be used for rendering, based on the available data and configuration.
|
||||
//! @param blendSource - indicates where to get the blend mask from
|
||||
//! @param blendMaskValues - blend mask values as returned by GetApplicableBlendMaskValues()
|
||||
//! @param layerDepthValues - the depth values for each layer, used if the blend source includes displacement. See GetLayerDepthValues().
|
||||
//! Note the blendMaskValues will not be applied here, those should have already been applied to layerDepthValues.
|
||||
//! @param layerDepthBlendDistance - controls how smoothly to blend layers 2 and 3 with the base layer, when the blend source includes displacement.
|
||||
//! When layers are close together their weights will be blended together, otherwise the highest layer will have the full weight.
|
||||
//! @return The blend weights for each layer.
|
||||
//! Even though layer1 not explicitly specified in the blend source data, it is explicitly included with the returned values.
|
||||
//! Even though layer1 not explicitly specified in the blend mask data, it is explicitly included with the returned values.
|
||||
//! layer1 = r
|
||||
//! layer2 = g
|
||||
//! layer3 = b
|
||||
float3 GetBlendWeights(float2 uv)
|
||||
float3 GetBlendWeights(LayerBlendSource blendSource, float3 blendMaskValues, float3 layerDepthValues, float layerDepthBlendDistance)
|
||||
{
|
||||
float3 blendWeights;
|
||||
|
||||
if(o_layer2_enabled || o_layer3_enabled)
|
||||
{
|
||||
float3 blendSourceValues = GetBlendSourceValues(uv);
|
||||
if(LayerBlendSource::Displacement == blendSource ||
|
||||
LayerBlendSource::Displacement_With_BlendMaskTexture == blendSource ||
|
||||
LayerBlendSource::Displacement_With_BlendMaskVertexColors == blendSource)
|
||||
{
|
||||
// Calculate the blend weights based on displacement values...
|
||||
|
||||
// Calculate blend weights such that multiplying and adding them with layer data is equivalent
|
||||
// to lerping between each layer.
|
||||
// final = lerp(final, layer1, blendWeights.r)
|
||||
// final = lerp(final, layer2, blendWeights.g)
|
||||
// final = lerp(final, layer3, blendWeights.b)
|
||||
// The inputs are depth values, but we change them to height values to make the code a bit more intuitive.
|
||||
float3 layerHeightValues = -layerDepthValues;
|
||||
|
||||
float highestPoint = layerHeightValues.x;
|
||||
if(o_layer2_enabled)
|
||||
{
|
||||
highestPoint = max(highestPoint, layerHeightValues.y);
|
||||
}
|
||||
if(o_layer3_enabled)
|
||||
{
|
||||
highestPoint = max(highestPoint, layerHeightValues.z);
|
||||
}
|
||||
|
||||
if(layerDepthBlendDistance > 0.0001)
|
||||
{
|
||||
float lowestVisiblePoint = highestPoint - layerDepthBlendDistance;
|
||||
blendWeights = smoothstep(lowestVisiblePoint, highestPoint, layerHeightValues);
|
||||
|
||||
if(!o_layer2_enabled)
|
||||
{
|
||||
blendWeights.y = 0.0;
|
||||
}
|
||||
|
||||
if(!o_layer3_enabled)
|
||||
{
|
||||
blendWeights.z = 0.0;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
blendWeights = float3(layerHeightValues.x >= highestPoint ? 1.0 : 0.0,
|
||||
layerHeightValues.y >= highestPoint && o_layer2_enabled ? 1.0 : 0.0,
|
||||
layerHeightValues.z >= highestPoint && o_layer3_enabled ? 1.0 : 0.0);
|
||||
}
|
||||
|
||||
// Calculate blend weights such that multiplying and adding them with layer data is equivalent
|
||||
// to lerping between each layer.
|
||||
// final = lerp(final, layer1, blendWeights.r)
|
||||
// final = lerp(final, layer2, blendWeights.g)
|
||||
// final = lerp(final, layer3, blendWeights.b)
|
||||
|
||||
blendWeights.y = (1 - blendWeights.z) * blendWeights.y;
|
||||
blendWeights.x = 1 - blendWeights.y - blendWeights.z;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Calculate blend weights such that multiplying and adding them with layer data is equivalent
|
||||
// to lerping between each layer.
|
||||
// final = lerp(final, layer1, blendWeights.r)
|
||||
// final = lerp(final, layer2, blendWeights.g)
|
||||
// final = lerp(final, layer3, blendWeights.b)
|
||||
|
||||
blendWeights.b = blendMaskValues.g;
|
||||
blendWeights.g = (1.0 - blendMaskValues.g) * blendMaskValues.r;
|
||||
blendWeights.r = (1.0 - blendMaskValues.g) * (1.0 - blendMaskValues.r);
|
||||
}
|
||||
|
||||
blendWeights.b = blendSourceValues.g;
|
||||
blendWeights.g = (1.0 - blendSourceValues.g) * blendSourceValues.r;
|
||||
blendWeights.r = (1.0 - blendSourceValues.g) * (1.0 - blendSourceValues.r);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -207,6 +311,38 @@ float3 GetBlendWeights(float2 uv)
|
||||
return blendWeights;
|
||||
}
|
||||
|
||||
float3 GetLayerDepthValues(float2 uv, float2 uv_ddx, float2 uv_ddy);
|
||||
|
||||
//! Return the final blend weights to be used for rendering, based on the available data and configuration.
|
||||
//! Note this will sample the displacement maps in the case of LayerBlendSource::Displacement. If you have already
|
||||
//! the layer depth values, use the GetBlendWeights() overload that takes layerDepthValues instead.
|
||||
float3 GetBlendWeights(LayerBlendSource blendSource, float2 uv, float3 blendMaskVertexColors)
|
||||
{
|
||||
float3 layerDepthValues = float3(0,0,0);
|
||||
|
||||
float3 blendMaskValues = GetApplicableBlendMaskValues(blendSource, uv, blendMaskVertexColors);
|
||||
|
||||
if(blendSource == LayerBlendSource::Displacement ||
|
||||
blendSource == LayerBlendSource::Displacement_With_BlendMaskTexture ||
|
||||
blendSource == LayerBlendSource::Displacement_With_BlendMaskVertexColors)
|
||||
{
|
||||
bool useBlendMask =
|
||||
LayerBlendSource::Displacement_With_BlendMaskTexture == blendSource ||
|
||||
LayerBlendSource::Displacement_With_BlendMaskVertexColors == blendSource;
|
||||
|
||||
layerDepthValues = GetLayerDepthValues(uv, ddx_fine(uv), ddy_fine(uv));
|
||||
|
||||
if(useBlendMask)
|
||||
{
|
||||
// Unlike the GetDepth() callback used for parallax, we don't just shift the values toward GetSubMinDisplacement(),
|
||||
// we shift extra to ensure that completely masked-out layers are not blended onto upper layers.
|
||||
layerDepthValues = ApplyBlendMaskToDepthValues(blendMaskValues, layerDepthValues, GetSubMinDisplacement() - MaterialSrg::m_displacementBlendDistance);
|
||||
}
|
||||
}
|
||||
|
||||
return GetBlendWeights(blendSource, blendMaskValues, layerDepthValues, MaterialSrg::m_displacementBlendDistance);
|
||||
}
|
||||
|
||||
float BlendLayers(float layer1, float layer2, float layer3, float3 blendWeights)
|
||||
{
|
||||
return dot(float3(layer1, layer2, layer3), blendWeights);
|
||||
@@ -236,55 +372,127 @@ bool ShouldHandleParallaxInDepthShaders()
|
||||
return ShouldHandleParallax() && o_parallax_enablePixelDepthOffset;
|
||||
}
|
||||
|
||||
// Callback function for ParallaxMapping.azsli
|
||||
DepthResult GetDepth(float2 uv, float2 uv_ddx, float2 uv_ddy)
|
||||
//! Returns the depth values for each layer.
|
||||
float3 GetLayerDepthValues(float2 uv, float2 uv_ddx, float2 uv_ddy)
|
||||
{
|
||||
float3 layerDepthValues = float3(0,0,0);
|
||||
|
||||
if(o_layer1_o_useDepthMap)
|
||||
|
||||
// layer1
|
||||
{
|
||||
float2 layerUv = uv;
|
||||
if(MaterialSrg::m_parallaxUvIndex == 0)
|
||||
if(o_layer1_o_useDepthMap)
|
||||
{
|
||||
layerUv = mul(MaterialSrg::m_layer1_m_uvMatrix, float3(uv, 1.0)).xy;
|
||||
float2 layerUv = uv;
|
||||
if(MaterialSrg::m_parallaxUvIndex == 0)
|
||||
{
|
||||
layerUv = mul(MaterialSrg::m_layer1_m_uvMatrix, float3(uv, 1.0)).xy;
|
||||
}
|
||||
|
||||
layerDepthValues.r = SampleDepthOrHeightMap(MaterialSrg::m_layer1_m_depthInverted, MaterialSrg::m_layer1_m_depthMap, MaterialSrg::m_sampler, layerUv, uv_ddx, uv_ddy).m_depth;
|
||||
layerDepthValues.r *= MaterialSrg::m_layer1_m_depthFactor;
|
||||
}
|
||||
|
||||
layerDepthValues.r = SampleDepthOrHeightMap(MaterialSrg::m_layer1_m_depthInverted, MaterialSrg::m_layer1_m_depthMap, MaterialSrg::m_sampler, layerUv, uv_ddx, uv_ddy).m_depth;
|
||||
layerDepthValues.r *= MaterialSrg::m_layer1_m_depthFactor;
|
||||
layerDepthValues.r -= MaterialSrg::m_layer1_m_depthOffset;
|
||||
}
|
||||
|
||||
if(o_layer2_enabled && o_layer2_o_useDepthMap)
|
||||
if(o_layer2_enabled)
|
||||
{
|
||||
float2 layerUv = uv;
|
||||
if(MaterialSrg::m_parallaxUvIndex == 0)
|
||||
if(o_layer2_o_useDepthMap)
|
||||
{
|
||||
layerUv = mul(MaterialSrg::m_layer2_m_uvMatrix, float3(uv, 1.0)).xy;
|
||||
float2 layerUv = uv;
|
||||
if(MaterialSrg::m_parallaxUvIndex == 0)
|
||||
{
|
||||
layerUv = mul(MaterialSrg::m_layer2_m_uvMatrix, float3(uv, 1.0)).xy;
|
||||
}
|
||||
|
||||
layerDepthValues.g = SampleDepthOrHeightMap(MaterialSrg::m_layer2_m_depthInverted, MaterialSrg::m_layer2_m_depthMap, MaterialSrg::m_sampler, layerUv, uv_ddx, uv_ddy).m_depth;
|
||||
layerDepthValues.g *= MaterialSrg::m_layer2_m_depthFactor;
|
||||
}
|
||||
|
||||
layerDepthValues.g = SampleDepthOrHeightMap(MaterialSrg::m_layer2_m_depthInverted, MaterialSrg::m_layer2_m_depthMap, MaterialSrg::m_sampler, layerUv, uv_ddx, uv_ddy).m_depth;
|
||||
layerDepthValues.g *= MaterialSrg::m_layer2_m_depthFactor;
|
||||
layerDepthValues.g -= MaterialSrg::m_layer2_m_depthOffset;
|
||||
|
||||
}
|
||||
|
||||
if(o_layer3_enabled && o_layer3_o_useDepthMap)
|
||||
if(o_layer3_enabled)
|
||||
{
|
||||
float2 layerUv = uv;
|
||||
if(MaterialSrg::m_parallaxUvIndex == 0)
|
||||
if(o_layer3_o_useDepthMap)
|
||||
{
|
||||
layerUv = mul(MaterialSrg::m_layer3_m_uvMatrix, float3(uv, 1.0)).xy;
|
||||
float2 layerUv = uv;
|
||||
if(MaterialSrg::m_parallaxUvIndex == 0)
|
||||
{
|
||||
layerUv = mul(MaterialSrg::m_layer3_m_uvMatrix, float3(uv, 1.0)).xy;
|
||||
}
|
||||
|
||||
layerDepthValues.b = SampleDepthOrHeightMap(MaterialSrg::m_layer3_m_depthInverted, MaterialSrg::m_layer3_m_depthMap, MaterialSrg::m_sampler, layerUv, uv_ddx, uv_ddy).m_depth;
|
||||
layerDepthValues.b *= MaterialSrg::m_layer3_m_depthFactor;
|
||||
}
|
||||
|
||||
layerDepthValues.b = SampleDepthOrHeightMap(MaterialSrg::m_layer3_m_depthInverted, MaterialSrg::m_layer3_m_depthMap, MaterialSrg::m_sampler, layerUv, uv_ddx, uv_ddy).m_depth;
|
||||
layerDepthValues.b *= MaterialSrg::m_layer3_m_depthFactor;
|
||||
layerDepthValues.b -= MaterialSrg::m_layer3_m_depthOffset;
|
||||
|
||||
}
|
||||
|
||||
// Note, when the blend source is BlendMaskSource::VertexColors, parallax will not be able to blend correctly between layers. It will end up using the same blend mask values
|
||||
// for every UV position when searching for the intersection. This leads to smearing artifacts at the transition point, but these won't be so noticeable as long as
|
||||
// you have a small depth factor relative to the size of the blend transition.
|
||||
float3 blendWeights = GetBlendWeights(uv);
|
||||
return layerDepthValues;
|
||||
}
|
||||
|
||||
|
||||
//! Uses a layer blend mask to further displace each layer's surface so that it disappears beyond the other surfaces.
|
||||
//! Note the blend mask does not apply to the first layer, it is the implicit base layer. Layers 2 and 3 are masked by the r and g channels of the mask.
|
||||
//! @param blendMaskValues layer mask values as returned by GetApplicableBlendMaskValues()
|
||||
//! @param layerDepthValues layer depth values as returned by GetLayerDepthValues()
|
||||
//! @param zeroMaskDisplacement the target displacement value that corresponds to a mask value of 0
|
||||
//! @return new layer depth values that have been adjusted according to the layerMaskValues
|
||||
float3 ApplyBlendMaskToDepthValues(float3 blendMaskValues, float3 layerDepthValues, float zeroMaskDisplacement)
|
||||
{
|
||||
if(o_layer2_enabled || o_layer3_enabled)
|
||||
{
|
||||
// We add to the depth value rather than lerp toward m_displacementMin to avoid squashing the topology, but instead lower it out of sight.
|
||||
|
||||
if(o_layer2_enabled)
|
||||
{
|
||||
float dropoffRange = MaterialSrg::m_layer2_m_depthOffset - zeroMaskDisplacement;
|
||||
layerDepthValues.g += dropoffRange * (1-blendMaskValues.r);
|
||||
}
|
||||
|
||||
if(o_layer3_enabled)
|
||||
{
|
||||
float dropoffRange = MaterialSrg::m_layer3_m_depthOffset - zeroMaskDisplacement;
|
||||
layerDepthValues.b += dropoffRange * (1-blendMaskValues.g);
|
||||
}
|
||||
}
|
||||
|
||||
return layerDepthValues;
|
||||
}
|
||||
|
||||
//! Callback function for ParallaxMapping.azsli
|
||||
DepthResult GetDepth(float2 uv, float2 uv_ddx, float2 uv_ddy)
|
||||
{
|
||||
LayerBlendSource blendSource = GetFinalLayerBlendSource();
|
||||
|
||||
float3 layerDepthValues = GetLayerDepthValues(uv, uv_ddx, uv_ddy);
|
||||
|
||||
// Note, when the blend source uses the blend mask from the vertex colors, parallax will not be able to blend correctly between layers. It will end up using the same blend mask values
|
||||
// for every UV position when searching for the intersection. This leads to smearing artifacts at the transition point, but these won't be as noticeable if
|
||||
// you have a small depth factor relative to the size of the blend transition.
|
||||
float3 blendMaskValues = GetApplicableBlendMaskValues(blendSource, uv, s_blendMaskFromVertexStream);
|
||||
|
||||
bool useBlendMask =
|
||||
LayerBlendSource::Displacement_With_BlendMaskTexture == blendSource ||
|
||||
LayerBlendSource::Displacement_With_BlendMaskVertexColors == blendSource;
|
||||
|
||||
if(useBlendMask)
|
||||
{
|
||||
// Regarding GetSubMinDisplacement(), when a mask of 0 pushes the surface all the way to the bottom, we want that
|
||||
// to go a little below the min so it will disappear if there is something else right at the min.
|
||||
|
||||
layerDepthValues = ApplyBlendMaskToDepthValues(blendMaskValues, layerDepthValues, GetSubMinDisplacement());
|
||||
}
|
||||
|
||||
// When blending the depth together, we don't use MaterialSrg::m_displacementBlendDistance. The intention is that m_displacementBlendDistance
|
||||
// is for transitioning the appearance of the surface itself, but we still want a distinct change in the heightmap. If someday we want to
|
||||
// support smoothly blending the depth as well, there is a bit more work to do to get it to play nice with the blend mask code in GetLayerDepthValues().
|
||||
float layerDepthBlendDistance = 0.0f;
|
||||
float3 blendWeightValues = GetBlendWeights(blendSource, blendMaskValues, layerDepthValues, layerDepthBlendDistance);
|
||||
|
||||
float depth = BlendLayers(layerDepthValues.r, layerDepthValues.g, layerDepthValues.b, blendWeightValues);
|
||||
|
||||
float depth = BlendLayers(layerDepthValues.r, layerDepthValues.g, layerDepthValues.b, blendWeights);
|
||||
return DepthResultAbsolute(depth);
|
||||
}
|
||||
|
||||
+6
-6
@@ -84,7 +84,7 @@ VSDepthOutput MainVS(VSInput IN)
|
||||
}
|
||||
else
|
||||
{
|
||||
OUT.m_blendMask = float3(1,1,1);
|
||||
OUT.m_blendMask = float3(0,0,0);
|
||||
}
|
||||
|
||||
return OUT;
|
||||
@@ -103,11 +103,11 @@ PSDepthOutput MainPS(VSDepthOutput IN, bool isFrontFace : SV_IsFrontFace)
|
||||
|
||||
if(ShouldHandleParallaxInDepthShaders())
|
||||
{
|
||||
// We support two UV streams, but only a single stream of tangent/bitangent. So for UV[1+] we generated the tangent/bitangent in screen-space.
|
||||
float3 tangents[UvSetCount] = { IN.m_tangent.xyz, float3(0, 0, 0) };
|
||||
float3 bitangents[UvSetCount] = { IN.m_bitangent.xyz, float3(0, 0, 0) };
|
||||
PrepareGeneratedTangent(IN.m_normal, IN.m_worldPosition, isFrontFace, IN.m_uv, UvSetCount, tangents, bitangents, 1);
|
||||
|
||||
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;
|
||||
|
||||
+147
@@ -0,0 +1,147 @@
|
||||
--------------------------------------------------------------------------------------
|
||||
--
|
||||
-- All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
-- its licensors.
|
||||
--
|
||||
-- For complete copyright and license terms please see the LICENSE at the root of this
|
||||
-- distribution (the "License"). All use of this software is governed by the License,
|
||||
-- or, if provided, by the license below or the license accompanying this file. Do not
|
||||
-- remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
--
|
||||
--
|
||||
----------------------------------------------------------------------------------------------------
|
||||
|
||||
-- This functor handles general parallax properties that affect the entire material.
|
||||
|
||||
function GetMaterialPropertyDependencies()
|
||||
return {
|
||||
"blend.blendSource",
|
||||
"blend.enableLayer2",
|
||||
"blend.enableLayer3",
|
||||
"parallax.enable",
|
||||
"layer1_parallax.textureMap",
|
||||
"layer2_parallax.textureMap",
|
||||
"layer3_parallax.textureMap",
|
||||
"layer1_parallax.useTexture",
|
||||
"layer2_parallax.useTexture",
|
||||
"layer3_parallax.useTexture",
|
||||
"layer1_parallax.factor",
|
||||
"layer2_parallax.factor",
|
||||
"layer3_parallax.factor",
|
||||
"layer1_parallax.offset",
|
||||
"layer2_parallax.offset",
|
||||
"layer3_parallax.offset"
|
||||
}
|
||||
end
|
||||
|
||||
-- These values must align with LayerBlendSource in StandardMultilayerPBR_Common.azsli.
|
||||
LayerBlendSource_BlendMaskTexture = 0
|
||||
LayerBlendSource_BlendMaskVertexColors = 1
|
||||
LayerBlendSource_Displacement = 2
|
||||
LayerBlendSource_Displacement_With_BlendMaskTexture = 3
|
||||
LayerBlendSource_Displacement_With_BlendMaskVertexColors = 4
|
||||
|
||||
function BlendSourceUsesDisplacement(context)
|
||||
local blendSource = context:GetMaterialPropertyValue_enum("blend.blendSource")
|
||||
local blendSourceIncludesDisplacement = (blendSource == LayerBlendSource_Displacement or
|
||||
blendSource == LayerBlendSource_Displacement_With_BlendMaskTexture or
|
||||
blendSource == LayerBlendSource_Displacement_With_BlendMaskVertexColors)
|
||||
return blendSourceIncludesDisplacement
|
||||
end
|
||||
|
||||
-- Calculates the min and max displacement height values encompassing all enabled layers.
|
||||
-- @return a table with two values {min,max}. Negative values are below the surface and positive values are above the surface.
|
||||
function CalcOverallHeightRange(context)
|
||||
|
||||
local heightMinMax = {nil, nil}
|
||||
|
||||
local function GetMergedHeightRange(heightMinMax, offset, factor)
|
||||
top = offset
|
||||
bottom = offset - factor
|
||||
|
||||
if(heightMinMax[1] == nil) then
|
||||
heightMinMax[1] = top
|
||||
else
|
||||
heightMinMax[1] = math.max(heightMinMax[1], top)
|
||||
end
|
||||
|
||||
if(heightMinMax[0] == nil) then
|
||||
heightMinMax[0] = bottom
|
||||
else
|
||||
heightMinMax[0] = math.min(heightMinMax[0], bottom)
|
||||
end
|
||||
end
|
||||
|
||||
local enableParallax = context:GetMaterialPropertyValue_bool("parallax.enable")
|
||||
|
||||
if(enableParallax or BlendSourceUsesDisplacement(context)) then
|
||||
local hasTextureLayer1 = nil ~= context:GetMaterialPropertyValue_Image("layer1_parallax.textureMap")
|
||||
local hasTextureLayer2 = nil ~= context:GetMaterialPropertyValue_Image("layer2_parallax.textureMap")
|
||||
local hasTextureLayer3 = nil ~= context:GetMaterialPropertyValue_Image("layer3_parallax.textureMap")
|
||||
|
||||
local useTextureLayer1 = context:GetMaterialPropertyValue_bool("layer1_parallax.useTexture")
|
||||
local useTextureLayer2 = context:GetMaterialPropertyValue_bool("layer2_parallax.useTexture")
|
||||
local useTextureLayer3 = context:GetMaterialPropertyValue_bool("layer3_parallax.useTexture")
|
||||
|
||||
local factorLayer1 = context:GetMaterialPropertyValue_float("layer1_parallax.factor")
|
||||
local factorLayer2 = context:GetMaterialPropertyValue_float("layer2_parallax.factor")
|
||||
local factorLayer3 = context:GetMaterialPropertyValue_float("layer3_parallax.factor")
|
||||
|
||||
if not hasTextureLayer1 or not useTextureLayer1 then factorLayer1 = 0 end
|
||||
if not hasTextureLayer2 or not useTextureLayer2 then factorLayer2 = 0 end
|
||||
if not hasTextureLayer3 or not useTextureLayer3 then factorLayer3 = 0 end
|
||||
|
||||
local offsetLayer1 = context:GetMaterialPropertyValue_float("layer1_parallax.offset")
|
||||
local offsetLayer2 = context:GetMaterialPropertyValue_float("layer2_parallax.offset")
|
||||
local offsetLayer3 = context:GetMaterialPropertyValue_float("layer3_parallax.offset")
|
||||
|
||||
local enableLayer2 = context:GetMaterialPropertyValue_bool("blend.enableLayer2")
|
||||
local enableLayer3 = context:GetMaterialPropertyValue_bool("blend.enableLayer3")
|
||||
|
||||
GetMergedHeightRange(heightMinMax, offsetLayer1, factorLayer1)
|
||||
if(enableLayer2) then GetMergedHeightRange(heightMinMax, offsetLayer2, factorLayer2) end
|
||||
if(enableLayer3) then GetMergedHeightRange(heightMinMax, offsetLayer3, factorLayer3) end
|
||||
|
||||
else
|
||||
heightMinMax = {0,0}
|
||||
end
|
||||
|
||||
return heightMinMax
|
||||
end
|
||||
|
||||
function Process(context)
|
||||
local heightMinMax = CalcOverallHeightRange(context)
|
||||
context:SetShaderConstant_float("m_displacementMin", heightMinMax[0])
|
||||
context:SetShaderConstant_float("m_displacementMax", heightMinMax[1])
|
||||
end
|
||||
|
||||
function ProcessEditor(context)
|
||||
local enable = context:GetMaterialPropertyValue_bool("parallax.enable")
|
||||
|
||||
local visibility = MaterialPropertyVisibility_Enabled
|
||||
if(not enable) then
|
||||
visibility = MaterialPropertyVisibility_Hidden
|
||||
end
|
||||
|
||||
context:SetMaterialPropertyVisibility("parallax.parallaxUv", visibility)
|
||||
context:SetMaterialPropertyVisibility("parallax.algorithm", visibility)
|
||||
context:SetMaterialPropertyVisibility("parallax.quality", visibility)
|
||||
context:SetMaterialPropertyVisibility("parallax.pdo", visibility)
|
||||
context:SetMaterialPropertyVisibility("parallax.showClipping", visibility)
|
||||
|
||||
if BlendSourceUsesDisplacement(context) then
|
||||
context:SetMaterialPropertyVisibility("blend.displacementBlendDistance", MaterialPropertyVisibility_Enabled)
|
||||
else
|
||||
context:SetMaterialPropertyVisibility("blend.displacementBlendDistance", MaterialPropertyVisibility_Hidden)
|
||||
end
|
||||
|
||||
-- We set the displacementBlendDistance slider range to match the range of displacement, so the slider will feel good
|
||||
-- regardless of how big the overall displacement is. Using a soft max allows the user to exceed the limit if desired,
|
||||
-- but the main reason for the *soft* max is to avoid impacting the value of displacementBlendDistance which could
|
||||
-- otherwise lead to edge cases.
|
||||
local heightMinMax = CalcOverallHeightRange(context)
|
||||
local totalDisplacementRange = heightMinMax[1] - heightMinMax[0]
|
||||
context:SetMaterialPropertySoftMaxValue_float("blend.displacementBlendDistance", math.max(totalDisplacementRange, 0.001))
|
||||
end
|
||||
|
||||
+46
-27
@@ -116,7 +116,7 @@ VSOutput ForwardPassVS(VSInput IN)
|
||||
}
|
||||
else
|
||||
{
|
||||
OUT.m_blendMask = float3(1,1,1);
|
||||
OUT.m_blendMask = float3(0,0,0);
|
||||
}
|
||||
|
||||
// Shadow coords will be calculated in the pixel shader in this case
|
||||
@@ -310,38 +310,45 @@ PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace, out float
|
||||
|
||||
s_blendMaskFromVertexStream = IN.m_blendMask;
|
||||
|
||||
LayerBlendSource blendSource = GetFinalLayerBlendSource();
|
||||
|
||||
// ------- Tangents & Bitangets -------
|
||||
float3 tangents[UvSetCount] = { IN.m_tangent.xyz, IN.m_tangent.xyz };
|
||||
float3 bitangents[UvSetCount] = { IN.m_bitangent.xyz, IN.m_bitangent.xyz };
|
||||
|
||||
// We support two UV streams, but only a single stream of tangent/bitangent. So for UV[1+] we generated the tangent/bitangent in screen-space.
|
||||
float3 tangents[UvSetCount] = { IN.m_tangent.xyz, float3(0, 0, 0) };
|
||||
float3 bitangents[UvSetCount] = { IN.m_bitangent.xyz, float3(0, 0, 0) };
|
||||
|
||||
if ((o_parallax_feature_enabled && !o_enableSubsurfaceScattering && MaterialSrg::m_parallaxUvIndex != 0)
|
||||
|| (o_layer1_o_normal_useTexture && MaterialSrg::m_layer1_m_normalMapUvIndex != 0)
|
||||
|| (o_layer2_o_normal_useTexture && MaterialSrg::m_layer2_m_normalMapUvIndex != 0)
|
||||
|| (o_layer3_o_normal_useTexture && MaterialSrg::m_layer3_m_normalMapUvIndex != 0)
|
||||
|| (o_layer1_o_clearCoat_normal_useTexture && MaterialSrg::m_layer1_m_clearCoatNormalMapUvIndex != 0)
|
||||
|| (o_layer2_o_clearCoat_normal_useTexture && MaterialSrg::m_layer2_m_clearCoatNormalMapUvIndex != 0)
|
||||
|| (o_layer3_o_clearCoat_normal_useTexture && MaterialSrg::m_layer3_m_clearCoatNormalMapUvIndex != 0)
|
||||
if ((o_parallax_feature_enabled && !o_enableSubsurfaceScattering)
|
||||
|| o_layer1_o_normal_useTexture
|
||||
|| o_layer2_o_normal_useTexture
|
||||
|| o_layer3_o_normal_useTexture
|
||||
|| o_layer1_o_clearCoat_normal_useTexture
|
||||
|| o_layer2_o_clearCoat_normal_useTexture
|
||||
|| o_layer3_o_clearCoat_normal_useTexture
|
||||
)
|
||||
{
|
||||
// Generate the tangent/bitangent for UV[1+]
|
||||
const int startIndex = 1;
|
||||
PrepareGeneratedTangent(IN.m_normal, IN.m_worldPosition, isFrontFace, IN.m_uv, UvSetCount, tangents, bitangents, startIndex);
|
||||
PrepareGeneratedTangent(IN.m_normal, IN.m_worldPosition, isFrontFace, IN.m_uv, UvSetCount, tangents, bitangents);
|
||||
}
|
||||
|
||||
// ------- Debug Modes -------
|
||||
|
||||
if(o_debugDrawMode == DebugDrawMode::BlendSource)
|
||||
if(o_debugDrawMode == DebugDrawMode::BlendMask)
|
||||
{
|
||||
float3 blendSource = GetBlendSourceValues(IN.m_uv[MaterialSrg::m_blendMaskUvIndex]);
|
||||
return DebugOutput(blendSource);
|
||||
float3 blendMaskValues = GetApplicableBlendMaskValues(blendSource, IN.m_uv[MaterialSrg::m_blendMaskUvIndex], IN.m_blendMask);
|
||||
return DebugOutput(blendMaskValues);
|
||||
}
|
||||
|
||||
if(o_debugDrawMode == DebugDrawMode::DepthMaps)
|
||||
if(o_debugDrawMode == DebugDrawMode::Displacement)
|
||||
{
|
||||
float depth = GetNormalizedDepth(-MaterialSrg::m_displacementMax, -MaterialSrg::m_displacementMin, IN.m_uv[MaterialSrg::m_parallaxUvIndex], float2(0,0), float2(0,0));
|
||||
return DebugOutput(float3(depth,depth,depth));
|
||||
float startDepth = -MaterialSrg::m_displacementMax;
|
||||
float stopDepth = -MaterialSrg::m_displacementMin;
|
||||
float depth = GetNormalizedDepth(startDepth, stopDepth, IN.m_uv[MaterialSrg::m_parallaxUvIndex], float2(0,0), float2(0,0));
|
||||
float height = 1 - saturate(depth);
|
||||
return DebugOutput(float3(height,height,height));
|
||||
}
|
||||
|
||||
if(o_debugDrawMode == DebugDrawMode::FinalBlendWeights)
|
||||
{
|
||||
float3 blendWeights = GetBlendWeights(blendSource, IN.m_uv[MaterialSrg::m_blendMaskUvIndex], IN.m_blendMask);
|
||||
return DebugOutput(blendWeights);
|
||||
}
|
||||
|
||||
// ------- Parallax -------
|
||||
@@ -373,21 +380,27 @@ PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace, out float
|
||||
// ------- Calculate Layer Blend Mask Values -------
|
||||
|
||||
// Now that any parallax has been calculated, we calculate the blend factors for any layers that are impacted by the parallax.
|
||||
float3 blendWeights = GetBlendWeights(IN.m_uv[MaterialSrg::m_blendMaskUvIndex]);
|
||||
float3 blendWeights = GetBlendWeights(blendSource, IN.m_uv[MaterialSrg::m_blendMaskUvIndex], IN.m_blendMask);
|
||||
|
||||
// ------- Layer 1 (base layer) -----------
|
||||
|
||||
ProcessedMaterialInputs lightingInputLayer1;
|
||||
if(blendWeights.r > 0)
|
||||
{
|
||||
StandardMaterialInputs inputs;
|
||||
FILL_STANDARD_MATERIAL_INPUTS(inputs, MaterialSrg::m_layer1_, o_layer1_, blendWeights.r)
|
||||
lightingInputLayer1 = ProcessStandardMaterialInputs(inputs);
|
||||
}
|
||||
else
|
||||
{
|
||||
lightingInputLayer1.InitializeToZero();
|
||||
blendWeights.r = 0;
|
||||
}
|
||||
|
||||
// ----------- Layer 2 -----------
|
||||
|
||||
ProcessedMaterialInputs lightingInputLayer2;
|
||||
if(o_layer2_enabled)
|
||||
if(o_layer2_enabled && blendWeights.g > 0)
|
||||
{
|
||||
StandardMaterialInputs inputs;
|
||||
FILL_STANDARD_MATERIAL_INPUTS(inputs, MaterialSrg::m_layer2_, o_layer2_, blendWeights.g)
|
||||
@@ -396,12 +409,13 @@ PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace, out float
|
||||
else
|
||||
{
|
||||
lightingInputLayer2.InitializeToZero();
|
||||
blendWeights.g = 0;
|
||||
}
|
||||
|
||||
// ----------- Layer 3 -----------
|
||||
|
||||
ProcessedMaterialInputs lightingInputLayer3;
|
||||
if(o_layer3_enabled)
|
||||
if(o_layer3_enabled && blendWeights.b > 0)
|
||||
{
|
||||
StandardMaterialInputs inputs;
|
||||
FILL_STANDARD_MATERIAL_INPUTS(inputs, MaterialSrg::m_layer3_, o_layer3_, blendWeights.b)
|
||||
@@ -410,6 +424,7 @@ PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace, out float
|
||||
else
|
||||
{
|
||||
lightingInputLayer3.InitializeToZero();
|
||||
blendWeights.b = 0;
|
||||
}
|
||||
|
||||
// ------- Combine all layers ---------
|
||||
@@ -420,12 +435,16 @@ PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace, out float
|
||||
|
||||
// ------- Combine Normals ---------
|
||||
|
||||
float3 normalTS = lightingInputLayer1.m_normalTS;
|
||||
if(o_layer2_enabled)
|
||||
float3 normalTS = float3(0,0,1);
|
||||
if(blendWeights.r > 0)
|
||||
{
|
||||
normalTS = lightingInputLayer1.m_normalTS;
|
||||
}
|
||||
if(o_layer2_enabled && blendWeights.g > 0)
|
||||
{
|
||||
normalTS = ReorientTangentSpaceNormal(normalTS, lightingInputLayer2.m_normalTS);
|
||||
}
|
||||
if(o_layer3_enabled)
|
||||
if(o_layer3_enabled && blendWeights.b > 0)
|
||||
{
|
||||
normalTS = ReorientTangentSpaceNormal(normalTS, lightingInputLayer3.m_normalTS);
|
||||
}
|
||||
|
||||
@@ -1,97 +0,0 @@
|
||||
--------------------------------------------------------------------------------------
|
||||
--
|
||||
-- All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
-- its licensors.
|
||||
--
|
||||
-- For complete copyright and license terms please see the LICENSE at the root of this
|
||||
-- distribution (the "License"). All use of this software is governed by the License,
|
||||
-- or, if provided, by the license below or the license accompanying this file. Do not
|
||||
-- remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
--
|
||||
--
|
||||
----------------------------------------------------------------------------------------------------
|
||||
|
||||
-- This functor handles general parallax properties that affect the entire material.
|
||||
|
||||
function GetMaterialPropertyDependencies()
|
||||
return {
|
||||
"parallax.enable",
|
||||
"layer1_parallax.enable",
|
||||
"layer2_parallax.enable",
|
||||
"layer3_parallax.enable",
|
||||
"layer1_parallax.factor",
|
||||
"layer2_parallax.factor",
|
||||
"layer3_parallax.factor",
|
||||
"layer1_parallax.offset",
|
||||
"layer2_parallax.offset",
|
||||
"layer3_parallax.offset"
|
||||
}
|
||||
end
|
||||
|
||||
function GetShaderOptionDependencies()
|
||||
return {"o_parallax_feature_enabled"}
|
||||
end
|
||||
|
||||
function MergeRange(heightMinMax, offset, factor)
|
||||
top = offset
|
||||
bottom = offset - factor
|
||||
|
||||
if(heightMinMax[1] == nil) then
|
||||
heightMinMax[1] = top
|
||||
else
|
||||
heightMinMax[1] = math.max(heightMinMax[1], top)
|
||||
end
|
||||
|
||||
if(heightMinMax[0] == nil) then
|
||||
heightMinMax[0] = bottom
|
||||
else
|
||||
heightMinMax[0] = math.min(heightMinMax[0], bottom)
|
||||
end
|
||||
end
|
||||
|
||||
function Process(context)
|
||||
local enableParallax = context:GetMaterialPropertyValue_bool("parallax.enable")
|
||||
local enable1 = context:GetMaterialPropertyValue_bool("layer1_parallax.enable")
|
||||
local enable2 = context:GetMaterialPropertyValue_bool("layer2_parallax.enable")
|
||||
local enable3 = context:GetMaterialPropertyValue_bool("layer3_parallax.enable")
|
||||
enableParallax = enableParallax and (enable1 or enable2 or enable3)
|
||||
context:SetShaderOptionValue_bool("o_parallax_feature_enabled", enableParallax)
|
||||
|
||||
if(enableParallax) then
|
||||
local factorLayer1 = context:GetMaterialPropertyValue_float("layer1_parallax.factor")
|
||||
local factorLayer2 = context:GetMaterialPropertyValue_float("layer2_parallax.factor")
|
||||
local factorLayer3 = context:GetMaterialPropertyValue_float("layer3_parallax.factor")
|
||||
|
||||
local offsetLayer1 = context:GetMaterialPropertyValue_float("layer1_parallax.offset")
|
||||
local offsetLayer2 = context:GetMaterialPropertyValue_float("layer2_parallax.offset")
|
||||
local offsetLayer3 = context:GetMaterialPropertyValue_float("layer3_parallax.offset")
|
||||
|
||||
local heightMinMax = {nil, nil}
|
||||
if(enable1) then MergeRange(heightMinMax, offsetLayer1, factorLayer1) end
|
||||
if(enable2) then MergeRange(heightMinMax, offsetLayer2, factorLayer2) end
|
||||
if(enable3) then MergeRange(heightMinMax, offsetLayer3, factorLayer3) end
|
||||
|
||||
if(heightMinMax[1] - heightMinMax[0] < 0.0001) then
|
||||
context:SetShaderOptionValue_bool("o_parallax_feature_enabled", false)
|
||||
else
|
||||
context:SetShaderConstant_float("m_displacementMin", heightMinMax[0])
|
||||
context:SetShaderConstant_float("m_displacementMax", heightMinMax[1])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function ProcessEditor(context)
|
||||
local enable = context:GetMaterialPropertyValue_bool("parallax.enable")
|
||||
|
||||
local visibility = MaterialPropertyVisibility_Enabled
|
||||
if(not enable) then
|
||||
visibility = MaterialPropertyVisibility_Hidden
|
||||
end
|
||||
|
||||
context:SetMaterialPropertyVisibility("parallax.parallaxUv", visibility)
|
||||
context:SetMaterialPropertyVisibility("parallax.algorithm", visibility)
|
||||
context:SetMaterialPropertyVisibility("parallax.quality", visibility)
|
||||
context:SetMaterialPropertyVisibility("parallax.pdo", visibility)
|
||||
context:SetMaterialPropertyVisibility("parallax.showClipping", visibility)
|
||||
end
|
||||
-49
@@ -1,49 +0,0 @@
|
||||
--------------------------------------------------------------------------------------
|
||||
--
|
||||
-- All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
-- its licensors.
|
||||
--
|
||||
-- For complete copyright and license terms please see the LICENSE at the root of this
|
||||
-- distribution (the "License"). All use of this software is governed by the License,
|
||||
-- or, if provided, by the license below or the license accompanying this file. Do not
|
||||
-- remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
--
|
||||
--
|
||||
----------------------------------------------------------------------------------------------------
|
||||
|
||||
-- This functor handles parallax properties that are specific to a single layer.
|
||||
|
||||
function GetMaterialPropertyDependencies()
|
||||
return {"parallax.enable", "parallax.textureMap"}
|
||||
end
|
||||
|
||||
function GetShaderOptionDependencies()
|
||||
return {"o_useDepthMap"}
|
||||
end
|
||||
|
||||
function Process(context)
|
||||
local enable = context:GetMaterialPropertyValue_bool("parallax.enable")
|
||||
local textureMap = context:GetMaterialPropertyValue_Image("parallax.textureMap")
|
||||
context:SetShaderOptionValue_bool("o_useDepthMap", enable and textureMap ~= nil)
|
||||
end
|
||||
|
||||
function ProcessEditor(context)
|
||||
local enable = context:GetMaterialPropertyValue_bool("parallax.enable")
|
||||
|
||||
if enable then
|
||||
context:SetMaterialPropertyVisibility("parallax.textureMap", MaterialPropertyVisibility_Enabled)
|
||||
else
|
||||
context:SetMaterialPropertyVisibility("parallax.textureMap", MaterialPropertyVisibility_Hidden)
|
||||
end
|
||||
|
||||
local textureMap = context:GetMaterialPropertyValue_Image("parallax.textureMap")
|
||||
local visibility = MaterialPropertyVisibility_Enabled
|
||||
if(not enable or textureMap == nil) then
|
||||
visibility = MaterialPropertyVisibility_Hidden
|
||||
end
|
||||
|
||||
context:SetMaterialPropertyVisibility("parallax.factor", visibility)
|
||||
context:SetMaterialPropertyVisibility("parallax.offset", visibility)
|
||||
context:SetMaterialPropertyVisibility("parallax.invert", visibility)
|
||||
end
|
||||
+5
-5
@@ -83,7 +83,7 @@ VertexOutput MainVS(VertexInput IN)
|
||||
}
|
||||
else
|
||||
{
|
||||
OUT.m_blendMask = float3(1,1,1);
|
||||
OUT.m_blendMask = float3(0,0,0);
|
||||
}
|
||||
|
||||
return OUT;
|
||||
@@ -102,10 +102,10 @@ PSDepthOutput MainPS(VertexOutput IN, bool isFrontFace : SV_IsFrontFace)
|
||||
|
||||
if(ShouldHandleParallaxInDepthShaders())
|
||||
{
|
||||
// We support two UV streams, but only a single stream of tangent/bitangent. So for UV[1+] we generated the tangent/bitangent in screen-space.
|
||||
float3 tangents[UvSetCount] = { IN.m_tangent.xyz, float3(0, 0, 0) };
|
||||
float3 bitangents[UvSetCount] = { IN.m_bitangent.xyz, float3(0, 0, 0) };
|
||||
PrepareGeneratedTangent(IN.m_normal, IN.m_worldPosition, isFrontFace, IN.m_uv, UvSetCount, tangents, bitangents, 1);
|
||||
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;
|
||||
|
||||
|
||||
@@ -78,10 +78,10 @@ PSDepthOutput MainPS(VSDepthOutput IN, bool isFrontFace : SV_IsFrontFace)
|
||||
|
||||
if(ShouldHandleParallaxInDepthShaders())
|
||||
{
|
||||
// We support two UV streams, but only a single stream of tangent/bitangent. So for UV[1+] we generated the tangent/bitangent in screen-space.
|
||||
float3 tangents[UvSetCount] = { IN.m_tangent.xyz, float3(0, 0, 0) };
|
||||
float3 bitangents[UvSetCount] = { IN.m_bitangent.xyz, float3(0, 0, 0) };
|
||||
PrepareGeneratedTangent(IN.m_normal, IN.m_worldPosition, isFrontFace, IN.m_uv, UvSetCount, tangents, bitangents, 1);
|
||||
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();
|
||||
|
||||
@@ -110,19 +110,12 @@ VSOutput StandardPbr_ForwardPassVS(VSInput IN)
|
||||
PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace, out float depthNDC)
|
||||
{
|
||||
// ------- Tangents & Bitangets -------
|
||||
float3 tangents[UvSetCount] = { IN.m_tangent.xyz, IN.m_tangent.xyz };
|
||||
float3 bitangents[UvSetCount] = { IN.m_bitangent.xyz, IN.m_bitangent.xyz };
|
||||
|
||||
// We support two UV streams, but only a single stream of tangent/bitangent. So for UV[1+] we generated the tangent/bitangent in screen-space.
|
||||
float3 tangents[UvSetCount] = { IN.m_tangent.xyz, float3(0, 0, 0) };
|
||||
float3 bitangents[UvSetCount] = { IN.m_bitangent.xyz, float3(0, 0, 0) };
|
||||
|
||||
if ((o_parallax_feature_enabled && !o_enableSubsurfaceScattering && MaterialSrg::m_parallaxUvIndex != 0)
|
||||
|| (o_normal_useTexture && MaterialSrg::m_normalMapUvIndex != 0)
|
||||
|| (o_clearCoat_enabled && o_clearCoat_normal_useTexture && MaterialSrg::m_clearCoatNormalMapUvIndex != 0)
|
||||
)
|
||||
if ((o_parallax_feature_enabled && !o_enableSubsurfaceScattering) || o_normal_useTexture || (o_clearCoat_enabled && o_clearCoat_normal_useTexture))
|
||||
{
|
||||
// Generate the tangent/bitangent for UV[1+]
|
||||
const int startIndex = 1;
|
||||
PrepareGeneratedTangent(IN.m_normal, IN.m_worldPosition, isFrontFace, IN.m_uv, UvSetCount, tangents, bitangents, startIndex);
|
||||
PrepareGeneratedTangent(IN.m_normal, IN.m_worldPosition, isFrontFace, IN.m_uv, UvSetCount, tangents, bitangents);
|
||||
}
|
||||
|
||||
// ------- Depth & Parallax -------
|
||||
|
||||
@@ -81,10 +81,9 @@ PSDepthOutput MainPS(VertexOutput IN, bool isFrontFace : SV_IsFrontFace)
|
||||
{
|
||||
static const float ShadowMapDepthBias = 0.000001;
|
||||
|
||||
// We support two UV streams, but only a single stream of tangent/bitangent. So for UV[1+] we generated the tangent/bitangent in screen-space.
|
||||
float3 tangents[UvSetCount] = { IN.m_tangent.xyz, float3(0, 0, 0) };
|
||||
float3 bitangents[UvSetCount] = { IN.m_bitangent.xyz, float3(0, 0, 0) };
|
||||
PrepareGeneratedTangent(IN.m_normal, IN.m_worldPosition, isFrontFace, IN.m_uv, UvSetCount, tangents, bitangents, 1);
|
||||
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();
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user