Merge branch 'stabilization/2106' of https://github.com/aws-lumberyard/o3de into carlito/stabilization/2106

main
chcurran 5 years ago
commit 9f7f6e84a4

@ -49,7 +49,7 @@ class TestViewMenuOptions(EditorTestHelper):
view_menu_options = [ view_menu_options = [
("Center on Selection",), ("Center on Selection",),
("Show Quick Access Bar",), ("Show Quick Access Bar",),
("Viewport", "Wireframe"), ("Viewport", "Configure Layout"),
("Viewport", "Go to Position"), ("Viewport", "Go to Position"),
("Viewport", "Center on Selection"), ("Viewport", "Center on Selection"),
("Viewport", "Go to Location"), ("Viewport", "Go to Location"),

@ -89,7 +89,7 @@ class TestMenus(object):
expected_lines = [ expected_lines = [
"Center on Selection Action triggered", "Center on Selection Action triggered",
"Show Quick Access Bar Action triggered", "Show Quick Access Bar Action triggered",
"Wireframe Action triggered", "Configure Layout Action triggered",
"Go to Position Action triggered", "Go to Position Action triggered",
"Center on Selection Action triggered", "Center on Selection Action triggered",
"Go to Location Action triggered", "Go to Location Action triggered",

@ -105,24 +105,25 @@ endforeach()
# Post-processing # Post-processing
################################################################################ ################################################################################
# The following steps have to be done after all targets are registered: # 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. Add any dependencies registered via ly_enable_gems # 1. Add any dependencies registered via ly_enable_gems
ly_enable_gems_delayed() ly_enable_gems_delayed()
# 2. generate a settings registry .setreg file for all ly_add_project_dependencies() and ly_add_target_dependencies() calls # 2. 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 and all the gems are enabled
ly_delayed_generate_static_modules_inl()
# 3. 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 # 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 # This must be done before ly_delayed_target_link_libraries() as that inserts BUILD_DEPENDENCIES as MANUALLY_ADDED_DEPENDENCIES
# if the build dependency is a MODULE_LIBRARY. That would cause a false load dependency to be generated # if the build dependency is a MODULE_LIBRARY. That would cause a false load dependency to be generated
ly_delayed_generate_settings_registry() ly_delayed_generate_settings_registry()
# 3. link targets where the dependency was yet not declared, we need to have the declaration so we do different # 4. link targets where the dependency was yet not declared, we need to have the declaration so we do different
# linking logic depending on the type of target # linking logic depending on the type of target
ly_delayed_target_link_libraries() ly_delayed_target_link_libraries()
# 4. generate a registry file for unit testing for platforms that support unit testing # 5. generate a registry file for unit testing for platforms that support unit testing
if(PAL_TRAIT_BUILD_TESTS_SUPPORTED) if(PAL_TRAIT_BUILD_TESTS_SUPPORTED)
ly_delayed_generate_unit_test_module_registry() ly_delayed_generate_unit_test_module_registry()
endif() endif()

@ -292,8 +292,13 @@ namespace AZ
const typename VecType::FloatType cmp2 = VecType::AndNot(cmp0, cmp1); const typename VecType::FloatType cmp2 = VecType::AndNot(cmp0, cmp1);
// -1/x // -1/x
// this step is calculated for all values of x, but only used if x > Sqrt(2) + 1
// in order to avoid a division by zero, detect if xabs is zero here and replace it with an arbitrary value
// if xabs does equal zero, the value here doesn't matter because the result will be thrown away
typename VecType::FloatType xabsSafe =
VecType::Add(xabs, VecType::And(VecType::CmpEq(xabs, VecType::ZeroFloat()), FastLoadConstant<VecType>(Simd::g_vec1111)));
const typename VecType::FloatType y0 = VecType::And(cmp0, FastLoadConstant<VecType>(Simd::g_HalfPi)); const typename VecType::FloatType y0 = VecType::And(cmp0, FastLoadConstant<VecType>(Simd::g_HalfPi));
typename VecType::FloatType x0 = VecType::Div(FastLoadConstant<VecType>(Simd::g_vec1111), xabs); typename VecType::FloatType x0 = VecType::Div(FastLoadConstant<VecType>(Simd::g_vec1111), xabsSafe);
x0 = VecType::Xor(x0, VecType::CastToFloat(FastLoadConstant<VecType>(Simd::g_negateMask))); x0 = VecType::Xor(x0, VecType::CastToFloat(FastLoadConstant<VecType>(Simd::g_negateMask)));
const typename VecType::FloatType y1 = VecType::And(cmp2, FastLoadConstant<VecType>(Simd::g_QuarterPi)); const typename VecType::FloatType y1 = VecType::And(cmp2, FastLoadConstant<VecType>(Simd::g_QuarterPi));
@ -368,8 +373,12 @@ namespace AZ
typename VecType::FloatType offset = VecType::And(x_lt_0, offset1); typename VecType::FloatType offset = VecType::And(x_lt_0, offset1);
// the result of this part of the computation is thrown away if x equals 0,
// but if x does equal 0, it will cause a division by zero
// so replace zero by an arbitrary value here in that case
typename VecType::FloatType xSafe = VecType::Add(x, VecType::And(x_eq_0, FastLoadConstant<VecType>(Simd::g_vec1111)));
const typename VecType::FloatType atan_mask = VecType::Not(VecType::Or(x_eq_0, y_eq_0)); const typename VecType::FloatType atan_mask = VecType::Not(VecType::Or(x_eq_0, y_eq_0));
const typename VecType::FloatType atan_arg = VecType::Div(y, x); const typename VecType::FloatType atan_arg = VecType::Div(y, xSafe);
typename VecType::FloatType atan_result = VecType::Atan(atan_arg); typename VecType::FloatType atan_result = VecType::Atan(atan_arg);
atan_result = VecType::Add(atan_result, offset); atan_result = VecType::Add(atan_result, offset);
atan_result = VecType::AndNot(pio2_mask, atan_result); atan_result = VecType::AndNot(pio2_mask, atan_result);

@ -471,6 +471,7 @@ namespace AZ
AZ_MATH_INLINE Vec2::FloatType Vec2::Reciprocal(FloatArgType value) AZ_MATH_INLINE Vec2::FloatType Vec2::Reciprocal(FloatArgType value)
{ {
value = Sse::ReplaceFourth(Sse::ReplaceThird(value, 1.0f), 1.0f);
return Sse::Reciprocal(value); return Sse::Reciprocal(value);
} }
@ -513,6 +514,7 @@ namespace AZ
AZ_MATH_INLINE Vec2::FloatType Vec2::SqrtInv(FloatArgType value) AZ_MATH_INLINE Vec2::FloatType Vec2::SqrtInv(FloatArgType value)
{ {
value = Sse::ReplaceFourth(Sse::ReplaceThird(value, 1.0f), 1.0f);
return Sse::SqrtInv(value); return Sse::SqrtInv(value);
} }

@ -507,6 +507,7 @@ namespace AZ
AZ_MATH_INLINE Vec3::FloatType Vec3::Reciprocal(FloatArgType value) AZ_MATH_INLINE Vec3::FloatType Vec3::Reciprocal(FloatArgType value)
{ {
value = Sse::ReplaceFourth(value, 1.0f);
return Sse::Reciprocal(value); return Sse::Reciprocal(value);
} }
@ -549,6 +550,7 @@ namespace AZ
AZ_MATH_INLINE Vec3::FloatType Vec3::SqrtInv(FloatArgType value) AZ_MATH_INLINE Vec3::FloatType Vec3::SqrtInv(FloatArgType value)
{ {
value = Sse::ReplaceFourth(value, 1.0f);
return Sse::SqrtInv(value); return Sse::SqrtInv(value);
} }

@ -607,10 +607,12 @@ namespace AZ
} }
else if (source.Size() > target.Size()) else if (source.Size() > target.Size())
{ {
rapidjson::SizeType sourceCount = source.Size(); // Loop backwards through the removals so that each removal has a valid index when processing in order.
for (rapidjson::SizeType i = count; i < sourceCount; ++i) for (rapidjson::SizeType i = source.Size(); i > count; --i)
{ {
ScopedStackedString entryName(element, i); // (We use "i - 1" here instead of in the loop to ensure we don't wrap around our unsigned numbers in the case
// where count is 0.)
ScopedStackedString entryName(element, i - 1);
patch.PushBack(CreatePatchInternal_Remove(allocator, element), allocator); patch.PushBack(CreatePatchInternal_Remove(allocator, element), allocator);
resultCode.Combine(settings.m_reporting("Removed member from array in JSON Patch.", resultCode.Combine(settings.m_reporting("Removed member from array in JSON Patch.",
ResultCode(Tasks::CreatePatch, Outcomes::Success), element)); ResultCode(Tasks::CreatePatch, Outcomes::Success), element));

@ -175,4 +175,11 @@ namespace AZ::Utils
path /= ".o3de"; path /= ".o3de";
return path.Native(); return path.Native();
} }
AZ::IO::FixedMaxPathString GetO3deLogsDirectory()
{
AZ::IO::FixedMaxPath path = GetO3deManifestDirectory();
path /= "Logs";
return path.Native();
}
} }

@ -97,6 +97,9 @@ namespace AZ
//! Retrieves the full path where the manifest file lives, i.e. "<userhome>/.o3de/o3de_manifest.json" //! Retrieves the full path where the manifest file lives, i.e. "<userhome>/.o3de/o3de_manifest.json"
AZ::IO::FixedMaxPathString GetEngineManifestPath(); AZ::IO::FixedMaxPathString GetEngineManifestPath();
//! Retrieves the full directory to the O3DE logs directory, i.e. "<userhome>/.o3de/Logs"
AZ::IO::FixedMaxPathString GetO3deLogsDirectory();
//! Retrieves the App root path to use on the current platform //! Retrieves the App root path to use on the current platform
//! If the optional is not engaged the AppRootPath should be calculated based //! If the optional is not engaged the AppRootPath should be calculated based
//! on the location of the bootstrap.cfg file //! on the location of the bootstrap.cfg file

@ -303,6 +303,29 @@ namespace JsonSerializationTests
R"( { "foo": [ "bar", "baz" ] })"); R"( { "foo": [ "bar", "baz" ] })");
} }
TEST_F(JsonPatchingSerializationTests, ApplyPatch_UseJsonPatchRemoveArrayMembersInCorrectOrder_ReportsSuccess)
{
CheckApplyPatch(
R"( { "foo": [ "bar", "qux", "baz" ] })",
R"( [
{ "op": "remove", "path": "/foo/2" },
{ "op": "remove", "path": "/foo/1" }
])",
R"( { "foo": [ "bar" ] })");
}
TEST_F(JsonPatchingSerializationTests, ApplyPatch_UseJsonPatchRemoveArrayMembersInWrongOrder_ReportsError)
{
using namespace AZ::JsonSerializationResult;
CheckApplyPatchOutcome(
R"( { "foo": [ "bar", "qux", "baz" ] })",
R"( [
{ "op": "remove", "path": "/foo/1" },
{ "op": "remove", "path": "/foo/2" }
])",
Outcomes::Invalid, Processing::Halted);
}
TEST_F(JsonPatchingSerializationTests, ApplyPatch_UseJsonPatchRemoveOperationInvalidParent_ReportError) TEST_F(JsonPatchingSerializationTests, ApplyPatch_UseJsonPatchRemoveOperationInvalidParent_ReportError)
{ {
using namespace AZ::JsonSerializationResult; using namespace AZ::JsonSerializationResult;
@ -949,6 +972,27 @@ namespace JsonSerializationTests
); );
} }
TEST_F(JsonPatchingSerializationTests, CreatePatch_UseJsonPatchRemoveLastArrayEntries_MultipleOperationsInCorrectOrder)
{
CheckCreatePatch(
R"( [ "foo", "hello", "bar" ])", R"( [ "foo" ])",
R"( [
{ "op": "remove", "path": "/2" },
{ "op": "remove", "path": "/1" }
])");
}
TEST_F(JsonPatchingSerializationTests, CreatePatch_UseJsonPatchRemoveAllArrayEntries_MultipleOperationsInCorrectOrder)
{
CheckCreatePatch(
R"( [ "foo", "hello", "bar" ])", R"( [])",
R"( [
{ "op": "remove", "path": "/2" },
{ "op": "remove", "path": "/1" },
{ "op": "remove", "path": "/0" }
])");
}
TEST_F(JsonPatchingSerializationTests, CreatePatch_UseJsonPatchRemoveObjectFromArrayInMiddle_OperationToUpdateMember) TEST_F(JsonPatchingSerializationTests, CreatePatch_UseJsonPatchRemoveObjectFromArrayInMiddle_OperationToUpdateMember)
{ {
CheckCreatePatch( CheckCreatePatch(

@ -8,25 +8,27 @@
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, # 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. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# #
ly_get_list_relative_pal_filename(pal_dir ${CMAKE_CURRENT_LIST_DIR}/AzTest/Platform/${PAL_PLATFORM_NAME})
ly_add_target( if(NOT LY_MONOLITHIC_GAME)
NAME AzTest STATIC ly_get_list_relative_pal_filename(pal_dir ${CMAKE_CURRENT_LIST_DIR}/AzTest/Platform/${PAL_PLATFORM_NAME})
NAMESPACE AZ
FILES_CMAKE ly_add_target(
AzTest/aztest_files.cmake NAME AzTest STATIC
${pal_dir}/platform_${PAL_PLATFORM_NAME_LOWERCASE}_files.cmake NAMESPACE AZ
INCLUDE_DIRECTORIES FILES_CMAKE
PUBLIC AzTest/aztest_files.cmake
. ${pal_dir}/platform_${PAL_PLATFORM_NAME_LOWERCASE}_files.cmake
${pal_dir} INCLUDE_DIRECTORIES
BUILD_DEPENDENCIES PUBLIC
PUBLIC .
3rdParty::googletest::GMock ${pal_dir}
3rdParty::googletest::GTest BUILD_DEPENDENCIES
3rdParty::GoogleBenchmark PUBLIC
AZ::AzCore 3rdParty::googletest::GMock
PLATFORM_INCLUDE_FILES 3rdParty::googletest::GTest
3rdParty::GoogleBenchmark
AZ::AzCore
PLATFORM_INCLUDE_FILES
${pal_dir}/platform_${PAL_PLATFORM_NAME_LOWERCASE}.cmake ${pal_dir}/platform_${PAL_PLATFORM_NAME_LOWERCASE}.cmake
) )
endif()

@ -159,12 +159,23 @@ namespace AzToolsFramework
void PrefabEditorEntityOwnershipService::GetNonPrefabEntities(EntityList& entities) void PrefabEditorEntityOwnershipService::GetNonPrefabEntities(EntityList& entities)
{ {
m_rootInstance->GetEntities(entities, false); m_rootInstance->GetEntities(
[&entities](const AZStd::unique_ptr<AZ::Entity>& entity)
{
entities.emplace_back(entity.get());
return true;
});
} }
bool PrefabEditorEntityOwnershipService::GetAllEntities(EntityList& entities) bool PrefabEditorEntityOwnershipService::GetAllEntities(EntityList& entities)
{ {
m_rootInstance->GetEntities(entities, true); m_rootInstance->GetAllEntitiesInHierarchy(
[&entities](const AZStd::unique_ptr<AZ::Entity>& entity)
{
entities.emplace_back(entity.get());
return true;
});
return true; return true;
} }
@ -252,13 +263,20 @@ namespace AzToolsFramework
} }
AZStd::string out; AZStd::string out;
if (m_loaderInterface->SaveTemplateToString(m_rootInstance->GetTemplateId(), out))
if (!m_loaderInterface->SaveTemplateToString(m_rootInstance->GetTemplateId(), out))
{ {
const size_t bytesToWrite = out.size(); return false;
const size_t bytesWritten = stream.Write(bytesToWrite, out.data());
return bytesWritten == bytesToWrite;
} }
return false;
const size_t bytesToWrite = out.size();
const size_t bytesWritten = stream.Write(bytesToWrite, out.data());
if(bytesWritten != bytesToWrite)
{
return false;
}
m_prefabSystemComponent->SetTemplateDirtyFlag(templateId, false);
return true;
} }
void PrefabEditorEntityOwnershipService::CreateNewLevelPrefab(AZStd::string_view filename, const AZStd::string& templateFilename) void PrefabEditorEntityOwnershipService::CreateNewLevelPrefab(AZStd::string_view filename, const AZStd::string& templateFilename)
@ -544,7 +562,7 @@ namespace AzToolsFramework
return; return;
} }
m_rootInstance->GetNestedEntities([this](AZStd::unique_ptr<AZ::Entity>& entity) m_rootInstance->GetAllEntitiesInHierarchy([this](AZStd::unique_ptr<AZ::Entity>& entity)
{ {
AZ_Assert(entity, "Invalid entity found in root instance while starting play in editor."); AZ_Assert(entity, "Invalid entity found in root instance while starting play in editor.");
if (entity->GetState() == AZ::Entity::State::Active) if (entity->GetState() == AZ::Entity::State::Active)

@ -373,17 +373,25 @@ namespace AzToolsFramework
} }
} }
void Instance::GetConstNestedEntities(const AZStd::function<bool(const AZ::Entity&)>& callback) bool Instance::GetEntities_Impl(const AZStd::function<bool(AZStd::unique_ptr<AZ::Entity>&)>& callback)
{ {
GetConstEntities(callback); for (auto& [entityAlias, entity] : m_entities)
for (const auto& [instanceAlias, instance] : m_nestedInstances)
{ {
instance->GetConstNestedEntities(callback); if (!entity)
{
continue;
}
if (!callback(entity))
{
return false;
}
} }
return true;
} }
void Instance::GetConstEntities(const AZStd::function<bool(const AZ::Entity&)>& callback) bool Instance::GetConstEntities_Impl(const AZStd::function<bool(const AZ::Entity&)>& callback) const
{ {
for (const auto& [entityAlias, entity] : m_entities) for (const auto& [entityAlias, entity] : m_entities)
{ {
@ -394,65 +402,91 @@ namespace AzToolsFramework
if (!callback(*entity)) if (!callback(*entity))
{ {
break; return false;
} }
} }
return true;
} }
void Instance::GetNestedEntities(const AZStd::function<bool(AZStd::unique_ptr<AZ::Entity>&)>& callback) bool Instance::GetAllEntitiesInHierarchy_Impl(const AZStd::function<bool(AZStd::unique_ptr<AZ::Entity>&)>& callback)
{ {
GetEntities(callback); if (HasContainerEntity())
for (auto& [instanceAlias, instance] : m_nestedInstances)
{ {
instance->GetNestedEntities(callback); if (!callback(m_containerEntity))
{
return false;
}
} }
}
void Instance::GetNestedInstances(const AZStd::function<void(AZStd::unique_ptr<Instance>&)>& callback) if (!GetEntities_Impl(callback))
{
for (auto& [instanceAlias, instance] : m_nestedInstances)
{ {
callback(instance); return false;
} }
}
void Instance::GetEntities(const AZStd::function<bool(AZStd::unique_ptr<AZ::Entity>&)>& callback) for (auto& [instanceAlias, instance] : m_nestedInstances)
{
for (auto& [entityAlias, entity] : m_entities)
{ {
if (!callback(entity)) if (!instance->GetAllEntitiesInHierarchy_Impl(callback))
{ {
break; return false;
} }
} }
return true;
} }
void Instance::GetEntities(EntityList& entities, bool includeNestedEntities) bool Instance::GetAllEntitiesInHierarchyConst_Impl(const AZStd::function<bool(const AZ::Entity&)>& callback) const
{ {
// Non-recursive traversal of instances if (HasContainerEntity())
AZStd::vector<Instance*> instancesToTraverse = { this };
while (!instancesToTraverse.empty())
{ {
Instance* currentInstance = instancesToTraverse.back(); if (!callback(*m_containerEntity))
instancesToTraverse.pop_back();
if (includeNestedEntities)
{ {
instancesToTraverse.reserve(instancesToTraverse.size() + currentInstance->m_nestedInstances.size()); return false;
for (const auto& instanceByAlias : currentInstance->m_nestedInstances)
{
instancesToTraverse.push_back(instanceByAlias.second.get());
}
} }
}
// Size increases by 1 for each instance because we have to count the container entity also. if (!GetConstEntities_Impl(callback))
entities.reserve(entities.size() + currentInstance->m_entities.size() + 1); {
entities.push_back(m_containerEntity.get()); return false;
for (const auto& entityByAlias : currentInstance->m_entities) }
for (const auto& [instanceAlias, instance] : m_nestedInstances)
{
if (!instance->GetAllEntitiesInHierarchyConst_Impl(callback))
{ {
entities.push_back(entityByAlias.second.get()); return false;
} }
} }
return true;
}
void Instance::GetEntities(const AZStd::function<bool(AZStd::unique_ptr<AZ::Entity>&)>& callback)
{
GetEntities_Impl(callback);
}
void Instance::GetConstEntities(const AZStd::function<bool(const AZ::Entity&)>& callback) const
{
GetConstEntities_Impl(callback);
}
void Instance::GetAllEntitiesInHierarchy(const AZStd::function<bool(AZStd::unique_ptr<AZ::Entity>&)>& callback)
{
GetAllEntitiesInHierarchy_Impl(callback);
}
void Instance::GetAllEntitiesInHierarchyConst(const AZStd::function<bool(const AZ::Entity&)>& callback) const
{
GetAllEntitiesInHierarchyConst_Impl(callback);
}
void Instance::GetNestedInstances(const AZStd::function<void(AZStd::unique_ptr<Instance>&)>& callback)
{
for (auto& [instanceAlias, instance] : m_nestedInstances)
{
callback(instance);
}
} }
EntityAliasOptionalReference Instance::GetEntityAlias(const AZ::EntityId& id) EntityAliasOptionalReference Instance::GetEntityAlias(const AZ::EntityId& id)

@ -121,10 +121,10 @@ namespace AzToolsFramework
/** /**
* Gets the entities in the Instance DOM. Can recursively trace all nested instances. * Gets the entities in the Instance DOM. Can recursively trace all nested instances.
*/ */
void GetConstNestedEntities(const AZStd::function<bool(const AZ::Entity&)>& callback);
void GetConstEntities(const AZStd::function<bool(const AZ::Entity&)>& callback);
void GetNestedEntities(const AZStd::function<bool(AZStd::unique_ptr<AZ::Entity>&)>& callback);
void GetEntities(const AZStd::function<bool(AZStd::unique_ptr<AZ::Entity>&)>& callback); void GetEntities(const AZStd::function<bool(AZStd::unique_ptr<AZ::Entity>&)>& callback);
void GetConstEntities(const AZStd::function<bool(const AZ::Entity&)>& callback) const;
void GetAllEntitiesInHierarchy(const AZStd::function<bool(AZStd::unique_ptr<AZ::Entity>&)>& callback);
void GetAllEntitiesInHierarchyConst(const AZStd::function<bool(const AZ::Entity&)>& callback) const;
void GetNestedInstances(const AZStd::function<void(AZStd::unique_ptr<Instance>&)>& callback); void GetNestedInstances(const AZStd::function<void(AZStd::unique_ptr<Instance>&)>& callback);
/** /**
@ -184,12 +184,6 @@ namespace AzToolsFramework
static InstanceAlias GenerateInstanceAlias(); static InstanceAlias GenerateInstanceAlias();
protected:
/**
* Gets the entities owned by this instance
*/
void GetEntities(EntityList& entities, bool includeNestedEntities = false);
private: private:
static constexpr const char s_aliasPathSeparator = '/'; static constexpr const char s_aliasPathSeparator = '/';
@ -197,6 +191,11 @@ namespace AzToolsFramework
void RemoveEntities(const AZStd::function<bool(const AZStd::unique_ptr<AZ::Entity>&)>& filter); void RemoveEntities(const AZStd::function<bool(const AZStd::unique_ptr<AZ::Entity>&)>& filter);
bool GetEntities_Impl(const AZStd::function<bool(AZStd::unique_ptr<AZ::Entity>&)>& callback);
bool GetConstEntities_Impl(const AZStd::function<bool(const AZ::Entity&)>& callback) const;
bool GetAllEntitiesInHierarchy_Impl(const AZStd::function<bool(AZStd::unique_ptr<AZ::Entity>&)>& callback);
bool GetAllEntitiesInHierarchyConst_Impl(const AZStd::function<bool(const AZ::Entity&)>& callback) const;
bool RegisterEntity(const AZ::EntityId& entityId, const EntityAlias& entityAlias); bool RegisterEntity(const AZ::EntityId& entityId, const EntityAlias& entityAlias);
AZStd::unique_ptr<AZ::Entity> DetachEntity(const EntityAlias& entityAlias); AZStd::unique_ptr<AZ::Entity> DetachEntity(const EntityAlias& entityAlias);

@ -62,25 +62,16 @@ namespace AzToolsFramework::Prefab::PrefabConversionUtils
} }
} }
AZStd::vector<AZ::Entity*> EditorInfoRemover::GetEntitiesFromInstance(AZStd::unique_ptr<Instance>& instance) void EditorInfoRemover::GetEntitiesFromInstance(
AZStd::unique_ptr<AzToolsFramework::Prefab::Instance>& instance, EntityList& hierarchyEntities)
{ {
AZStd::vector<AZ::Entity*> result; instance->GetAllEntitiesInHierarchy(
[&hierarchyEntities](const AZStd::unique_ptr<AZ::Entity>& entity)
instance->GetNestedEntities(
[&result](const AZStd::unique_ptr<AZ::Entity>& entity)
{ {
result.emplace_back(entity.get()); hierarchyEntities.emplace_back(entity.get());
return true; return true;
} }
); );
if (instance->HasContainerEntity())
{
auto containerEntityReference = instance->GetContainerEntity();
result.emplace_back(&containerEntityReference->get());
}
return result;
} }
void EditorInfoRemover::SetEditorOnlyEntityHandlerFromCandidates(const EntityList& entities) void EditorInfoRemover::SetEditorOnlyEntityHandlerFromCandidates(const EntityList& entities)
@ -543,7 +534,9 @@ exportComponent, prefabProcessorContext);
} }
// grab all nested entities from the Instance as source entities. // grab all nested entities from the Instance as source entities.
EntityList sourceEntities = GetEntitiesFromInstance(instance); EntityList sourceEntities;
GetEntitiesFromInstance(instance, sourceEntities);
EntityList exportEntities; EntityList exportEntities;
// prepare for validation of component requirements. // prepare for validation of component requirements.
@ -616,7 +609,7 @@ exportComponent, prefabProcessorContext);
); );
// replace entities of instance with exported ones. // replace entities of instance with exported ones.
instance->GetNestedEntities( instance->GetAllEntitiesInHierarchy(
[&exportEntitiesMap](AZStd::unique_ptr<AZ::Entity>& entity) [&exportEntitiesMap](AZStd::unique_ptr<AZ::Entity>& entity)
{ {
auto entityId = entity->GetId(); auto entityId = entity->GetId();
@ -625,14 +618,6 @@ exportComponent, prefabProcessorContext);
} }
); );
if (instance->HasContainerEntity())
{
if (auto found = exportEntitiesMap.find(instance->GetContainerEntityId()); found != exportEntitiesMap.end())
{
instance->SetContainerEntity(*found->second);
}
}
// save the final result in the target Prefab DOM. // save the final result in the target Prefab DOM.
PrefabDom filteredPrefab; PrefabDom filteredPrefab;
if (!PrefabDomUtils::StoreInstanceInPrefabDom(*instance, filteredPrefab)) if (!PrefabDomUtils::StoreInstanceInPrefabDom(*instance, filteredPrefab))

@ -55,8 +55,8 @@ namespace AzToolsFramework::Prefab::PrefabConversionUtils
protected: protected:
using EntityList = AZStd::vector<AZ::Entity*>; using EntityList = AZStd::vector<AZ::Entity*>;
static EntityList GetEntitiesFromInstance( static void GetEntitiesFromInstance(
AZStd::unique_ptr<AzToolsFramework::Prefab::Instance>& instance); AZStd::unique_ptr<AzToolsFramework::Prefab::Instance>& instance, EntityList& hierarchyEntities);
static bool ReadComponentAttribute( static bool ReadComponentAttribute(
AZ::Component* component, AZ::Component* component,

@ -58,8 +58,17 @@ namespace AzToolsFramework
if (!path.empty()) if (!path.empty())
{ {
infoString = QString saveFlag = "";
QObject::tr("<span style=\"font-style: italic; font-weight: 400;\">(%1)</span>").arg(path.Filename().Native().data()); auto dirtyOutcome = m_prefabPublicInterface->HasUnsavedChanges(path);
if (dirtyOutcome.IsSuccess() && dirtyOutcome.GetValue() == true)
{
saveFlag = "*";
}
infoString = QObject::tr("<span style=\"font-style: italic; font-weight: 400;\">(%1%2)</span>")
.arg(path.Filename().Native().data())
.arg(saveFlag);
} }
return infoString; return infoString;

@ -28,6 +28,7 @@
#include <AzToolsFramework/ToolsComponents/EditorLayerComponentBus.h> #include <AzToolsFramework/ToolsComponents/EditorLayerComponentBus.h>
#include <AzToolsFramework/UI/EditorEntityUi/EditorEntityUiInterface.h> #include <AzToolsFramework/UI/EditorEntityUi/EditorEntityUiInterface.h>
#include <AzToolsFramework/UI/Prefab/PrefabIntegrationInterface.h> #include <AzToolsFramework/UI/Prefab/PrefabIntegrationInterface.h>
#include <AzToolsFramework/UI/UICore/WidgetHelpers.h>
#include <QApplication> #include <QApplication>
#include <QFileDialog> #include <QFileDialog>
@ -588,15 +589,6 @@ namespace AzToolsFramework
bool PrefabIntegrationManager::QueryUserForPrefabFilePath(AZStd::string& outPrefabFilePath) bool PrefabIntegrationManager::QueryUserForPrefabFilePath(AZStd::string& outPrefabFilePath)
{ {
QWidget* mainWindow = nullptr;
EditorRequests::Bus::BroadcastResult(mainWindow, &EditorRequests::Bus::Events::GetMainWindow);
if (mainWindow == nullptr)
{
AZ_Assert(false, "Prefab - Could not detect Editor main window to generate the asset picker.");
return false;
}
AssetSelectionModel selection; AssetSelectionModel selection;
// Note, stringfilter will match every source file CONTAINING ".prefab". // Note, stringfilter will match every source file CONTAINING ".prefab".
@ -624,7 +616,7 @@ namespace AzToolsFramework
selection.SetDisplayFilter(compositeFilterPtr); selection.SetDisplayFilter(compositeFilterPtr);
selection.SetSelectionFilter(compositeFilterPtr); selection.SetSelectionFilter(compositeFilterPtr);
AssetBrowserComponentRequestBus::Broadcast(&AssetBrowserComponentRequests::PickAssets, selection, mainWindow); AssetBrowserComponentRequestBus::Broadcast(&AssetBrowserComponentRequests::PickAssets, selection, AzToolsFramework::GetActiveWindow());
if (!selection.IsValid()) if (!selection.IsValid())
{ {
@ -983,12 +975,7 @@ namespace AzToolsFramework
includedEntities.c_str(), includedEntities.c_str(),
referencedEntities.c_str()); referencedEntities.c_str());
QWidget* mainWindow = nullptr; QMessageBox msgBox(AzToolsFramework::GetActiveWindow());
AzToolsFramework::EditorRequests::Bus::BroadcastResult(
mainWindow,
&AzToolsFramework::EditorRequests::Bus::Events::GetMainWindow);
QMessageBox msgBox(mainWindow);
msgBox.setWindowTitle("External Entity References"); msgBox.setWindowTitle("External Entity References");
msgBox.setText("The prefab contains references to external entities that are not selected."); msgBox.setText("The prefab contains references to external entities that are not selected.");
msgBox.setInformativeText("You can move the referenced entities into this prefab or retain the external references."); msgBox.setInformativeText("You can move the referenced entities into this prefab or retain the external references.");

@ -93,7 +93,7 @@ namespace UnitTest
// Retrieve the entity pointer from the component application bus. // Retrieve the entity pointer from the component application bus.
AZ::Entity* wheelEntityUnderAxle = nullptr; AZ::Entity* wheelEntityUnderAxle = nullptr;
axleInstance->GetNestedEntities([&wheelEntityUnderAxle, wheelEntityIdUnderAxle](AZStd::unique_ptr<AZ::Entity>& entity) axleInstance->GetAllEntitiesInHierarchy([&wheelEntityUnderAxle, wheelEntityIdUnderAxle](AZStd::unique_ptr<AZ::Entity>& entity)
{ {
if (entity->GetId() == wheelEntityIdUnderAxle) if (entity->GetId() == wheelEntityIdUnderAxle)
{ {

@ -196,6 +196,16 @@ function(ly_delayed_generate_static_modules_inl)
ly_get_gem_load_dependencies(all_game_gem_dependencies ${project_name}.GameLauncher) ly_get_gem_load_dependencies(all_game_gem_dependencies ${project_name}.GameLauncher)
foreach(game_gem_dependency ${all_game_gem_dependencies}) foreach(game_gem_dependency ${all_game_gem_dependencies})
# Sometimes, a gem's Client variant may be an interface library
# which dependes on multiple gem targets. The interface libraries
# should be skipped; the real dependencies of the interface will be processed
if(TARGET ${game_gem_dependency})
get_target_property(target_type ${game_gem_dependency} TYPE)
if(${target_type} STREQUAL "INTERFACE_LIBRARY")
continue()
endif()
endif()
# To match the convention on how gems targets vs gem modules are named, # To match the convention on how gems targets vs gem modules are named,
# we remove the ".Static" from the suffix # we remove the ".Static" from the suffix
# Replace "." with "_" # Replace "." with "_"
@ -224,6 +234,14 @@ function(ly_delayed_generate_static_modules_inl)
list(APPEND all_server_gem_dependencies ${server_gem_load_dependencies} ${server_gem_dependency}) list(APPEND all_server_gem_dependencies ${server_gem_load_dependencies} ${server_gem_dependency})
endforeach() endforeach()
foreach(server_gem_dependency ${all_server_gem_dependencies}) foreach(server_gem_dependency ${all_server_gem_dependencies})
# Skip interface libraries
if(TARGET ${server_gem_dependency})
get_target_property(target_type ${server_gem_dependency} TYPE)
if(${target_type} STREQUAL "INTERFACE_LIBRARY")
continue()
endif()
endif()
# Replace "." with "_" # Replace "." with "_"
string(REPLACE "." "_" server_gem_dependency ${server_gem_dependency}) string(REPLACE "." "_" server_gem_dependency ${server_gem_dependency})

@ -715,11 +715,11 @@ QMenu* LevelEditorMenuHandler::CreateViewMenu()
{ {
return view.IsViewportPane(); return view.IsViewportPane();
}); });
#endif
viewportViewsMenuWrapper.AddAction(ID_WIREFRAME);
viewportViewsMenuWrapper.AddSeparator(); viewportViewsMenuWrapper.AddSeparator();
#endif
if (CViewManager::IsMultiViewportEnabled()) if (CViewManager::IsMultiViewportEnabled())
{ {
viewportViewsMenuWrapper.AddAction(ID_VIEW_CONFIGURELAYOUT); viewportViewsMenuWrapper.AddAction(ID_VIEW_CONFIGURELAYOUT);

@ -2891,7 +2891,7 @@ void CCryEditApp::OpenProjectManager(const AZStd::string& screen)
{ {
// provide the current project path for in case we want to update the project // provide the current project path for in case we want to update the project
AZ::IO::FixedMaxPathString projectPath = AZ::Utils::GetProjectPath(); AZ::IO::FixedMaxPathString projectPath = AZ::Utils::GetProjectPath();
const AZStd::string commandLineOptions = AZStd::string::format(" --screen %s --project_path %s", screen.c_str(), projectPath.c_str()); const AZStd::string commandLineOptions = AZStd::string::format(" --screen %s --project-path %s", screen.c_str(), projectPath.c_str());
bool launchSuccess = AzFramework::ProjectManager::LaunchProjectManager(commandLineOptions); bool launchSuccess = AzFramework::ProjectManager::LaunchProjectManager(commandLineOptions);
if (!launchSuccess) if (!launchSuccess)
{ {

@ -514,18 +514,28 @@ void EditorViewportWidget::Update()
// Disable rendering to avoid recursion into Update() // Disable rendering to avoid recursion into Update()
PushDisableRendering(); PushDisableRendering();
//get debug display interface for the viewport
AzFramework::DebugDisplayRequestBus::BusPtr debugDisplayBus;
AzFramework::DebugDisplayRequestBus::Bind(debugDisplayBus, GetViewportId());
AZ_Assert(debugDisplayBus, "Invalid DebugDisplayRequestBus.");
AzFramework::DebugDisplayRequests* debugDisplay =
AzFramework::DebugDisplayRequestBus::FindFirstHandler(debugDisplayBus);
// draw debug visualizations // draw debug visualizations
if (m_debugDisplay) if (debugDisplay)
{ {
const AZ::u32 prevState = m_debugDisplay->GetState(); const AZ::u32 prevState = debugDisplay->GetState();
m_debugDisplay->SetState( debugDisplay->SetState(
e_Mode3D | e_AlphaBlended | e_FillModeSolid | e_CullModeBack | e_DepthWriteOn | e_DepthTestOn); e_Mode3D | e_AlphaBlended | e_FillModeSolid | e_CullModeBack | e_DepthWriteOn | e_DepthTestOn);
AzFramework::EntityDebugDisplayEventBus::Broadcast( AzFramework::EntityDebugDisplayEventBus::Broadcast(
&AzFramework::EntityDebugDisplayEvents::DisplayEntityViewport, &AzFramework::EntityDebugDisplayEvents::DisplayEntityViewport,
AzFramework::ViewportInfo{ GetViewportId() }, *m_debugDisplay); AzFramework::ViewportInfo{ GetViewportId() }, *debugDisplay);
m_debugDisplay->SetState(prevState); debugDisplay->SetState(prevState);
} }
QtViewport::Update(); QtViewport::Update();

@ -154,11 +154,23 @@ SandboxIntegrationManager::SandboxIntegrationManager()
{ {
// Required to receive events from the Cry Engine undo system // Required to receive events from the Cry Engine undo system
GetIEditor()->GetUndoManager()->AddListener(this); GetIEditor()->GetUndoManager()->AddListener(this);
// Only create the PrefabIntegrationManager if prefabs are enabled
bool prefabSystemEnabled = false;
AzFramework::ApplicationRequests::Bus::BroadcastResult(
prefabSystemEnabled, &AzFramework::ApplicationRequests::IsPrefabSystemEnabled);
if (prefabSystemEnabled)
{
m_prefabIntegrationManager = aznew AzToolsFramework::Prefab::PrefabIntegrationManager();
}
} }
SandboxIntegrationManager::~SandboxIntegrationManager() SandboxIntegrationManager::~SandboxIntegrationManager()
{ {
GetIEditor()->GetUndoManager()->RemoveListener(this); GetIEditor()->GetUndoManager()->RemoveListener(this);
delete m_prefabIntegrationManager;
m_prefabIntegrationManager = nullptr;
} }
void SandboxIntegrationManager::Setup() void SandboxIntegrationManager::Setup()
@ -187,11 +199,16 @@ void SandboxIntegrationManager::Setup()
AZ_Assert((m_editorEntityUiInterface != nullptr), AZ_Assert((m_editorEntityUiInterface != nullptr),
"SandboxIntegrationManager requires a EditorEntityUiInterface instance to be present on Setup()."); "SandboxIntegrationManager requires a EditorEntityUiInterface instance to be present on Setup().");
m_prefabIntegrationInterface = AZ::Interface<AzToolsFramework::Prefab::PrefabIntegrationInterface>::Get(); bool prefabSystemEnabled = false;
AzFramework::ApplicationRequests::Bus::BroadcastResult(
AZ_Assert( prefabSystemEnabled, &AzFramework::ApplicationRequests::IsPrefabSystemEnabled);
(m_prefabIntegrationInterface != nullptr), if (prefabSystemEnabled)
"SandboxIntegrationManager requires a PrefabIntegrationInterface instance to be present on Setup()."); {
m_prefabIntegrationInterface = AZ::Interface<AzToolsFramework::Prefab::PrefabIntegrationInterface>::Get();
AZ_Assert(
(m_prefabIntegrationInterface != nullptr),
"SandboxIntegrationManager requires a PrefabIntegrationInterface instance to be present on Setup().");
}
m_editorEntityAPI = AZ::Interface<AzToolsFramework::EditorEntityAPI>::Get(); m_editorEntityAPI = AZ::Interface<AzToolsFramework::EditorEntityAPI>::Get();
AZ_Assert(m_editorEntityAPI, "SandboxIntegrationManager requires an EditorEntityAPI instance to be present on Setup()."); AZ_Assert(m_editorEntityAPI, "SandboxIntegrationManager requires an EditorEntityAPI instance to be present on Setup().");

@ -305,7 +305,7 @@ private:
bool m_debugDisplayBusImplementationActive = false; bool m_debugDisplayBusImplementationActive = false;
AzToolsFramework::Prefab::PrefabIntegrationManager m_prefabIntegrationManager; AzToolsFramework::Prefab::PrefabIntegrationManager* m_prefabIntegrationManager = nullptr;
AzToolsFramework::EditorEntityUiInterface* m_editorEntityUiInterface = nullptr; AzToolsFramework::EditorEntityUiInterface* m_editorEntityUiInterface = nullptr;
AzToolsFramework::Prefab::PrefabIntegrationInterface* m_prefabIntegrationInterface = nullptr; AzToolsFramework::Prefab::PrefabIntegrationInterface* m_prefabIntegrationInterface = nullptr;

@ -19,6 +19,7 @@ set(FILES
native/AssetManager/AssetRequestHandler.cpp native/AssetManager/AssetRequestHandler.cpp
native/AssetManager/AssetRequestHandler.h native/AssetManager/AssetRequestHandler.h
native/AssetManager/assetScanFolderInfo.h native/AssetManager/assetScanFolderInfo.h
native/AssetManager/assetScanFolderInfo.cpp
native/AssetManager/assetScanner.cpp native/AssetManager/assetScanner.cpp
native/AssetManager/assetScanner.h native/AssetManager/assetScanner.h
native/AssetManager/assetScannerWorker.cpp native/AssetManager/assetScannerWorker.cpp

@ -0,0 +1,42 @@
/*
* 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 <native/AssetManager/assetScanFolderInfo.h>
#include <native/utilities/assetUtils.h>
namespace AssetProcessor
{
ScanFolderInfo::ScanFolderInfo(
QString path,
QString displayName,
QString portableKey,
bool isRoot,
bool recurseSubFolders,
AZStd::vector<AssetBuilderSDK::PlatformInfo> platforms,
int order,
AZ::s64 scanFolderID,
bool canSaveNewAssets)
: m_scanPath(path)
, m_displayName(displayName)
, m_portableKey (portableKey)
, m_isRoot(isRoot)
, m_recurseSubFolders(recurseSubFolders)
, m_order(order)
, m_scanFolderID(scanFolderID)
, m_platforms(platforms)
, m_canSaveNewAssets(canSaveNewAssets)
{
m_scanPath = AssetUtilities::NormalizeFilePath(m_scanPath);
// note that m_scanFolderID is 0 unless its filled in from the DB.
}
} // end namespace AssetProcessor

@ -33,19 +33,7 @@ namespace AssetProcessor
AZStd::vector<AssetBuilderSDK::PlatformInfo> platforms = AZStd::vector<AssetBuilderSDK::PlatformInfo>{}, AZStd::vector<AssetBuilderSDK::PlatformInfo> platforms = AZStd::vector<AssetBuilderSDK::PlatformInfo>{},
int order = 0, int order = 0,
AZ::s64 scanFolderID = 0, AZ::s64 scanFolderID = 0,
bool canSaveNewAssets = false) bool canSaveNewAssets = false);
: m_scanPath(path)
, m_displayName(displayName)
, m_portableKey (portableKey)
, m_isRoot(isRoot)
, m_recurseSubFolders(recurseSubFolders)
, m_order(order)
, m_scanFolderID(scanFolderID)
, m_platforms(platforms)
, m_canSaveNewAssets(canSaveNewAssets)
{
// note that m_scanFolderID is 0 unless its filled in from the DB.
}
ScanFolderInfo() = default; ScanFolderInfo() = default;
ScanFolderInfo(const ScanFolderInfo& other) = default; ScanFolderInfo(const ScanFolderInfo& other) = default;

@ -13,7 +13,7 @@ ly_get_list_relative_pal_filename(pal_dir ${CMAKE_CURRENT_LIST_DIR}/Platform/${P
include(${pal_dir}/platform_traits_${PAL_PLATFORM_NAME_LOWERCASE}.cmake) include(${pal_dir}/platform_traits_${PAL_PLATFORM_NAME_LOWERCASE}.cmake)
if(PAL_TRAIT_AZTESTRUNNER_SUPPORTED) if(PAL_TRAIT_AZTESTRUNNER_SUPPORTED AND NOT LY_MONOLITHIC_GAME)
ly_add_target( ly_add_target(
NAME AzTestRunner ${PAL_TRAIT_AZTESTRUNNER_LAUNCHER_TYPE} NAME AzTestRunner ${PAL_TRAIT_AZTESTRUNNER_LAUNCHER_TYPE}

@ -20,12 +20,11 @@ if (NOT python_package_name)
message(WARNING "Python was not found in the package assocation list. Did someone call ly_associate_package(xxxxxxx Python) ?") message(WARNING "Python was not found in the package assocation list. Did someone call ly_associate_package(xxxxxxx Python) ?")
endif() endif()
ly_add_target( ly_add_target(
NAME ProjectManager APPLICATION NAME ProjectManager.Static STATIC
OUTPUT_NAME o3de
NAMESPACE AZ NAMESPACE AZ
AUTOMOC AUTOMOC
AUTORCC
FILES_CMAKE FILES_CMAKE
project_manager_files.cmake project_manager_files.cmake
Platform/${PAL_PLATFORM_NAME}/PAL_${PAL_PLATFORM_NAME_LOWERCASE}_files.cmake Platform/${PAL_PLATFORM_NAME}/PAL_${PAL_PLATFORM_NAME_LOWERCASE}_files.cmake
@ -47,6 +46,60 @@ ly_add_target(
3rdParty::pybind11 3rdParty::pybind11
AZ::AzCore AZ::AzCore
AZ::AzFramework AZ::AzFramework
AZ::AzToolsFramework
AZ::AzQtComponents AZ::AzQtComponents
) )
ly_add_target(
NAME ProjectManager APPLICATION
OUTPUT_NAME o3de
NAMESPACE AZ
AUTORCC
FILES_CMAKE
project_manager_app_files.cmake
INCLUDE_DIRECTORIES
PRIVATE
Source
BUILD_DEPENDENCIES
PRIVATE
3rdParty::Qt::Core
3rdParty::Qt::Concurrent
3rdParty::Qt::Widgets
3rdParty::Python
3rdParty::pybind11
AZ::AzCore
AZ::AzFramework
AZ::AzQtComponents
AZ::ProjectManager.Static
)
if(PAL_TRAIT_BUILD_TESTS_SUPPORTED)
ly_add_target(
NAME ProjectManager.Tests EXECUTABLE
NAMESPACE AZ
AUTORCC
FILES_CMAKE
project_manager_tests_files.cmake
Platform/${PAL_PLATFORM_NAME}/PAL_${PAL_PLATFORM_NAME_LOWERCASE}_tests_files.cmake
INCLUDE_DIRECTORIES
PRIVATE
Source
Platform/${PAL_PLATFORM_NAME}
BUILD_DEPENDENCIES
PRIVATE
3rdParty::Qt::Core
3rdParty::Qt::Concurrent
3rdParty::Qt::Widgets
3rdParty::Python
3rdParty::pybind11
AZ::AzTest
AZ::AzFramework
AZ::AzFrameworkTestShared
AZ::ProjectManager.Static
)
ly_add_googletest(
NAME AZ::ProjectManager.Tests
TEST_COMMAND $<TARGET_FILE:AZ::ProjectManager.Tests> --unittest
)
endif()

@ -0,0 +1,15 @@
#
# 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
ProjectManager_Test_Traits_Platform.h
ProjectManager_Test_Traits_Linux.h
)

@ -0,0 +1,15 @@
/*
* 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
#define AZ_TRAIT_DISABLE_FAILED_PROJECT_MANAGER_TESTS true

@ -0,0 +1,15 @@
/*
* 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 <ProjectManager_Test_Traits_Linux.h>

@ -0,0 +1,15 @@
#
# 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
ProjectManager_Test_Traits_Platform.h
ProjectManager_Test_Traits_Mac.h
)

@ -0,0 +1,15 @@
/*
* 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
#define AZ_TRAIT_DISABLE_FAILED_PROJECT_MANAGER_TESTS false

@ -0,0 +1,15 @@
/*
* 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 <ProjectManager_Test_Traits_Mac.h>

@ -0,0 +1,15 @@
#
# 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
ProjectManager_Test_Traits_Platform.h
ProjectManager_Test_Traits_Windows.h
)

@ -0,0 +1,15 @@
/*
* 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 <ProjectManager_Test_Traits_Windows.h>

@ -0,0 +1,15 @@
/*
* 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
#define AZ_TRAIT_DISABLE_FAILED_PROJECT_MANAGER_TESTS false

@ -7,6 +7,11 @@ QMainWindow {
margin:0; margin:0;
} }
#ScreensCtrl {
min-width:1200px;
min-height:800px;
}
QPushButton:focus { QPushButton:focus {
outline: none; outline: none;
border:1px solid #1e70eb; border:1px solid #1e70eb;

@ -0,0 +1,186 @@
/*
* 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 <Application.h>
#include <ProjectUtils.h>
#include <AzCore/IO/FileIO.h>
#include <AzCore/Utils/Utils.h>
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
#include <AzFramework/Logging/LoggingComponent.h>
#include <AzQtComponents/Utilities/HandleDpiAwareness.h>
#include <AzQtComponents/Components/StyleManager.h>
#include <AzQtComponents/Components/WindowDecorationWrapper.h>
#include <QApplication>
#include <QDir>
#include <QMessageBox>
namespace O3DE::ProjectManager
{
Application::~Application()
{
TearDown();
}
bool Application::Init(bool interactive)
{
constexpr const char* applicationName { "O3DE" };
QApplication::setOrganizationName(applicationName);
QApplication::setOrganizationDomain("o3de.org");
QCoreApplication::setApplicationName(applicationName);
QCoreApplication::setApplicationVersion("1.0");
// Use the LogComponent for non-dev logging log
RegisterComponentDescriptor(AzFramework::LogComponent::CreateDescriptor());
// set the log alias to .o3de/Logs instead of the default user/logs
AZ::IO::FixedMaxPath path = AZ::Utils::GetO3deLogsDirectory();
// DevWriteStorage is where the event log is written during development
m_settingsRegistry->Set(AZ::SettingsRegistryMergeUtils::FilePathKey_DevWriteStorage, path.LexicallyNormal().Native());
// Save event logs to .o3de/Logs/eventlogger/EventLogO3DE.azsl
m_settingsRegistry->Set(AZ::SettingsRegistryMergeUtils::BuildTargetNameKey, applicationName);
Start(AzFramework::Application::Descriptor());
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
QCoreApplication::setAttribute(Qt::AA_DontCreateNativeWidgetSiblings);
QLocale::setDefault(QLocale(QLocale::English, QLocale::UnitedStates));
QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough);
AzQtComponents::Utilities::HandleDpiAwareness(AzQtComponents::Utilities::SystemDpiAware);
// Create the actual Qt Application - this needs to happen before using QMessageBox
m_app.reset(new QApplication(*GetArgC(), *GetArgV()));
if(!InitLog(applicationName))
{
AZ_Warning("ProjectManager", false, "Failed to init logging");
}
m_pythonBindings = AZStd::make_unique<PythonBindings>(GetEngineRoot());
if (!m_pythonBindings || !m_pythonBindings->PythonStarted())
{
if (interactive)
{
QMessageBox::critical(nullptr, QObject::tr("Failed to start Python"),
QObject::tr("This tool requires an O3DE engine with a Python runtime, "
"but either Python is missing or mis-configured. Please rename "
"your python/runtime folder to python/runtime_bak, then run "
"python/get_python.bat to restore the Python runtime folder."));
}
return false;
}
const AZ::CommandLine* commandLine = GetCommandLine();
AZ_Assert(commandLine, "Failed to get command line");
ProjectManagerScreen startScreen = ProjectManagerScreen::Projects;
if (size_t screenSwitchCount = commandLine->GetNumSwitchValues("screen"); screenSwitchCount > 0)
{
QString screenOption = commandLine->GetSwitchValue("screen", screenSwitchCount - 1).c_str();
ProjectManagerScreen screen = ProjectUtils::GetProjectManagerScreen(screenOption);
if (screen != ProjectManagerScreen::Invalid)
{
startScreen = screen;
}
}
AZ::IO::FixedMaxPath projectPath;
if (size_t projectSwitchCount = commandLine->GetNumSwitchValues("project-path"); projectSwitchCount > 0)
{
projectPath = commandLine->GetSwitchValue("project-path", projectSwitchCount - 1).c_str();
}
m_mainWindow.reset(new ProjectManagerWindow(nullptr, projectPath, startScreen));
return true;
}
bool Application::InitLog(const char* logName)
{
if (!m_entity)
{
// override the log alias to the O3de Logs directory instead of the default project user/Logs folder
AZ::IO::FixedMaxPath path = AZ::Utils::GetO3deLogsDirectory();
AZ::IO::FileIOBase* fileIO = AZ::IO::FileIOBase::GetInstance();
AZ_Assert(fileIO, "Failed to get FileIOBase instance");
fileIO->SetAlias("@log@", path.LexicallyNormal().Native().c_str());
// this entity exists because we need a home for LogComponent
// and cannot use the system entity because we need to be able to call SetLogFileBaseName
// so the log will be named O3DE.log
m_entity = aznew AZ::Entity("Application Entity");
if (m_entity)
{
AzFramework::LogComponent* logger = aznew AzFramework::LogComponent();
AZ_Assert(logger, "Failed to create LogComponent");
logger->SetLogFileBaseName(logName);
m_entity->AddComponent(logger);
m_entity->Init();
m_entity->Activate();
}
}
return m_entity != nullptr;
}
void Application::TearDown()
{
if (m_entity)
{
m_entity->Deactivate();
delete m_entity;
m_entity = nullptr;
}
m_pythonBindings.reset();
m_mainWindow.reset();
m_app.reset();
}
bool Application::Run()
{
// Set up the Style Manager
AzQtComponents::StyleManager styleManager(qApp);
styleManager.initialize(qApp, GetEngineRoot());
// setup stylesheets and hot reloading
AZ::IO::FixedMaxPath engineRoot(GetEngineRoot());
QDir rootDir(engineRoot.c_str());
const auto pathOnDisk = rootDir.absoluteFilePath("Code/Tools/ProjectManager/Resources");
const auto qrcPath = QStringLiteral(":/ProjectManager/style");
AzQtComponents::StyleManager::addSearchPaths("style", pathOnDisk, qrcPath, engineRoot);
// set stylesheet after creating the main window or their styles won't get updated
AzQtComponents::StyleManager::setStyleSheet(m_mainWindow.data(), QStringLiteral("style:ProjectManager.qss"));
// the decoration wrapper is intended to remember window positioning and sizing
auto wrapper = new AzQtComponents::WindowDecorationWrapper();
wrapper->setGuest(m_mainWindow.data());
wrapper->show();
m_mainWindow->show();
qApp->setQuitOnLastWindowClosed(true);
// Run the application
return qApp->exec();
}
}

@ -0,0 +1,48 @@
/*
* 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
#if !defined(Q_MOC_RUN)
#include <AzFramework/Application/Application.h>
#include <QCoreApplication>
#include <PythonBindings.h>
#include <ProjectManagerWindow.h>
#endif
namespace AZ
{
class Entity;
}
namespace O3DE::ProjectManager
{
class Application
: public AzFramework::Application
{
public:
using AzFramework::Application::Application;
virtual ~Application();
bool Init(bool interactive = true);
bool Run();
void TearDown();
private:
bool InitLog(const char* logName);
AZStd::unique_ptr<PythonBindings> m_pythonBindings;
QSharedPointer<QCoreApplication> m_app;
QSharedPointer<ProjectManagerWindow> m_mainWindow;
AZ::Entity* m_entity = nullptr;
};
}

@ -25,7 +25,7 @@ namespace O3DE::ProjectManager
EngineSettingsScreen::EngineSettingsScreen(QWidget* parent) EngineSettingsScreen::EngineSettingsScreen(QWidget* parent)
: ScreenWidget(parent) : ScreenWidget(parent)
{ {
auto* layout = new QVBoxLayout(this); auto* layout = new QVBoxLayout();
layout->setAlignment(Qt::AlignTop); layout->setAlignment(Qt::AlignTop);
setObjectName("engineSettingsScreen"); setObjectName("engineSettingsScreen");

@ -82,6 +82,8 @@ namespace O3DE::ProjectManager
m_headerWidget->ReinitForProject(); m_headerWidget->ReinitForProject();
connect(m_gemModel, &GemModel::dataChanged, m_filterWidget, &GemFilterWidget::ResetGemStatusFilter);
// Select the first entry after everything got correctly sized // Select the first entry after everything got correctly sized
QTimer::singleShot(200, [=]{ QTimer::singleShot(200, [=]{
QModelIndex firstModelIndex = m_gemListView->model()->index(0,0); QModelIndex firstModelIndex = m_gemListView->model()->index(0,0);

@ -26,6 +26,7 @@ namespace O3DE::ProjectManager
const QVector<QString>& elementNames, const QVector<QString>& elementNames,
const QVector<int>& elementCounts, const QVector<int>& elementCounts,
bool showAllLessButton, bool showAllLessButton,
bool collapsed,
int defaultShowCount, int defaultShowCount,
QWidget* parent) QWidget* parent)
: QWidget(parent) : QWidget(parent)
@ -40,6 +41,7 @@ namespace O3DE::ProjectManager
QHBoxLayout* collapseLayout = new QHBoxLayout(); QHBoxLayout* collapseLayout = new QHBoxLayout();
m_collapseButton = new QPushButton(); m_collapseButton = new QPushButton();
m_collapseButton->setCheckable(true); m_collapseButton->setCheckable(true);
m_collapseButton->setChecked(collapsed);
m_collapseButton->setFlat(true); m_collapseButton->setFlat(true);
m_collapseButton->setFocusPolicy(Qt::NoFocus); m_collapseButton->setFocusPolicy(Qt::NoFocus);
m_collapseButton->setFixedWidth(s_collapseButtonSize); m_collapseButton->setFixedWidth(s_collapseButtonSize);
@ -178,6 +180,11 @@ namespace O3DE::ProjectManager
return m_buttonGroup; return m_buttonGroup;
} }
bool FilterCategoryWidget::IsCollapsed()
{
return m_collapseButton->isChecked();
}
GemFilterWidget::GemFilterWidget(GemSortFilterProxyModel* filterProxyModel, QWidget* parent) GemFilterWidget::GemFilterWidget(GemSortFilterProxyModel* filterProxyModel, QWidget* parent)
: QScrollArea(parent) : QScrollArea(parent)
, m_filterProxyModel(filterProxyModel) , m_filterProxyModel(filterProxyModel)
@ -193,20 +200,106 @@ namespace O3DE::ProjectManager
QWidget* mainWidget = new QWidget(); QWidget* mainWidget = new QWidget();
setWidget(mainWidget); setWidget(mainWidget);
m_mainLayout = new QVBoxLayout(); QVBoxLayout* mainLayout = new QVBoxLayout();
m_mainLayout->setAlignment(Qt::AlignTop); mainLayout->setAlignment(Qt::AlignTop);
mainWidget->setLayout(m_mainLayout); mainWidget->setLayout(mainLayout);
QLabel* filterByLabel = new QLabel("Filter by"); QLabel* filterByLabel = new QLabel("Filter by");
filterByLabel->setStyleSheet("font-size: 16px;"); filterByLabel->setStyleSheet("font-size: 16px;");
m_mainLayout->addWidget(filterByLabel); mainLayout->addWidget(filterByLabel);
QWidget* filterSection = new QWidget(this);
mainLayout->addWidget(filterSection);
m_filterLayout = new QVBoxLayout();
m_filterLayout->setAlignment(Qt::AlignTop);
m_filterLayout->setContentsMargins(0, 0, 0, 0);
filterSection->setLayout(m_filterLayout);
ResetGemStatusFilter();
AddGemOriginFilter(); AddGemOriginFilter();
AddTypeFilter(); AddTypeFilter();
AddPlatformFilter(); AddPlatformFilter();
AddFeatureFilter(); AddFeatureFilter();
} }
void GemFilterWidget::ResetGemStatusFilter()
{
QVector<QString> elementNames;
QVector<int> elementCounts;
const int totalGems = m_gemModel->rowCount();
const int selectedGemTotal = m_gemModel->TotalAddedGems();
elementNames.push_back(GemSortFilterProxyModel::GetGemStatusString(GemSortFilterProxyModel::GemStatus::Unselected));
elementCounts.push_back(totalGems - selectedGemTotal);
elementNames.push_back(GemSortFilterProxyModel::GetGemStatusString(GemSortFilterProxyModel::GemStatus::Selected));
elementCounts.push_back(selectedGemTotal);
bool wasCollapsed = false;
if (m_statusFilter)
{
wasCollapsed = m_statusFilter->IsCollapsed();
}
FilterCategoryWidget* filterWidget =
new FilterCategoryWidget("Status", elementNames, elementCounts, /*showAllLessButton=*/false, /*collapsed*/wasCollapsed);
if (m_statusFilter)
{
m_filterLayout->replaceWidget(m_statusFilter, filterWidget);
}
else
{
m_filterLayout->addWidget(filterWidget);
}
m_statusFilter->deleteLater();
m_statusFilter = filterWidget;
const GemSortFilterProxyModel::GemStatus currentFilterState = m_filterProxyModel->GetGemStatus();
const QList<QAbstractButton*> buttons = m_statusFilter->GetButtonGroup()->buttons();
for (int statusFilterIndex = 0; statusFilterIndex < buttons.size(); ++statusFilterIndex)
{
const GemSortFilterProxyModel::GemStatus gemStatus = static_cast<GemSortFilterProxyModel::GemStatus>(statusFilterIndex);
QAbstractButton* button = buttons[statusFilterIndex];
if (static_cast<GemSortFilterProxyModel::GemStatus>(statusFilterIndex) == currentFilterState)
{
button->setChecked(true);
}
connect(
button, &QAbstractButton::toggled, this,
[=](bool checked)
{
GemSortFilterProxyModel::GemStatus filterStatus = m_filterProxyModel->GetGemStatus();
if (checked)
{
if (filterStatus == GemSortFilterProxyModel::GemStatus::NoFilter)
{
filterStatus = gemStatus;
}
else
{
filterStatus = GemSortFilterProxyModel::GemStatus::NoFilter;
}
}
else
{
if (filterStatus != gemStatus)
{
filterStatus = static_cast<GemSortFilterProxyModel::GemStatus>(!gemStatus);
}
else
{
filterStatus = GemSortFilterProxyModel::GemStatus::NoFilter;
}
}
m_filterProxyModel->SetGemStatus(filterStatus);
});
}
}
void GemFilterWidget::AddGemOriginFilter() void GemFilterWidget::AddGemOriginFilter()
{ {
QVector<QString> elementNames; QVector<QString> elementNames;
@ -233,7 +326,7 @@ namespace O3DE::ProjectManager
} }
FilterCategoryWidget* filterWidget = new FilterCategoryWidget("Provider", elementNames, elementCounts, /*showAllLessButton=*/false); FilterCategoryWidget* filterWidget = new FilterCategoryWidget("Provider", elementNames, elementCounts, /*showAllLessButton=*/false);
m_mainLayout->addWidget(filterWidget); m_filterLayout->addWidget(filterWidget);
const QList<QAbstractButton*> buttons = filterWidget->GetButtonGroup()->buttons(); const QList<QAbstractButton*> buttons = filterWidget->GetButtonGroup()->buttons();
for (int i = 0; i < buttons.size(); ++i) for (int i = 0; i < buttons.size(); ++i)
@ -283,7 +376,7 @@ namespace O3DE::ProjectManager
} }
FilterCategoryWidget* filterWidget = new FilterCategoryWidget("Type", elementNames, elementCounts, /*showAllLessButton=*/false); FilterCategoryWidget* filterWidget = new FilterCategoryWidget("Type", elementNames, elementCounts, /*showAllLessButton=*/false);
m_mainLayout->addWidget(filterWidget); m_filterLayout->addWidget(filterWidget);
const QList<QAbstractButton*> buttons = filterWidget->GetButtonGroup()->buttons(); const QList<QAbstractButton*> buttons = filterWidget->GetButtonGroup()->buttons();
for (int i = 0; i < buttons.size(); ++i) for (int i = 0; i < buttons.size(); ++i)
@ -333,7 +426,7 @@ namespace O3DE::ProjectManager
} }
FilterCategoryWidget* filterWidget = new FilterCategoryWidget("Supported Platforms", elementNames, elementCounts, /*showAllLessButton=*/false); FilterCategoryWidget* filterWidget = new FilterCategoryWidget("Supported Platforms", elementNames, elementCounts, /*showAllLessButton=*/false);
m_mainLayout->addWidget(filterWidget); m_filterLayout->addWidget(filterWidget);
const QList<QAbstractButton*> buttons = filterWidget->GetButtonGroup()->buttons(); const QList<QAbstractButton*> buttons = filterWidget->GetButtonGroup()->buttons();
for (int i = 0; i < buttons.size(); ++i) for (int i = 0; i < buttons.size(); ++i)
@ -388,8 +481,8 @@ namespace O3DE::ProjectManager
} }
FilterCategoryWidget* filterWidget = new FilterCategoryWidget("Features", elementNames, elementCounts, FilterCategoryWidget* filterWidget = new FilterCategoryWidget("Features", elementNames, elementCounts,
/*showAllLessButton=*/true, /*defaultShowCount=*/5); /*showAllLessButton=*/true, false, /*defaultShowCount=*/5);
m_mainLayout->addWidget(filterWidget); m_filterLayout->addWidget(filterWidget);
const QList<QAbstractButton*> buttons = filterWidget->GetButtonGroup()->buttons(); const QList<QAbstractButton*> buttons = filterWidget->GetButtonGroup()->buttons();
for (int i = 0; i < buttons.size(); ++i) for (int i = 0; i < buttons.size(); ++i)

@ -37,11 +37,14 @@ namespace O3DE::ProjectManager
const QVector<QString>& elementNames, const QVector<QString>& elementNames,
const QVector<int>& elementCounts, const QVector<int>& elementCounts,
bool showAllLessButton = true, bool showAllLessButton = true,
bool collapsed = false,
int defaultShowCount = 4, int defaultShowCount = 4,
QWidget* parent = nullptr); QWidget* parent = nullptr);
QButtonGroup* GetButtonGroup(); QButtonGroup* GetButtonGroup();
bool IsCollapsed();
private: private:
void UpdateCollapseState(); void UpdateCollapseState();
void UpdateSeeMoreLess(); void UpdateSeeMoreLess();
@ -66,14 +69,18 @@ namespace O3DE::ProjectManager
explicit GemFilterWidget(GemSortFilterProxyModel* filterProxyModel, QWidget* parent = nullptr); explicit GemFilterWidget(GemSortFilterProxyModel* filterProxyModel, QWidget* parent = nullptr);
~GemFilterWidget() = default; ~GemFilterWidget() = default;
public slots:
void ResetGemStatusFilter();
private: private:
void AddGemOriginFilter(); void AddGemOriginFilter();
void AddTypeFilter(); void AddTypeFilter();
void AddPlatformFilter(); void AddPlatformFilter();
void AddFeatureFilter(); void AddFeatureFilter();
QVBoxLayout* m_mainLayout = nullptr; QVBoxLayout* m_filterLayout = nullptr;
GemModel* m_gemModel = nullptr; GemModel* m_gemModel = nullptr;
GemSortFilterProxyModel* m_filterProxyModel = nullptr; GemSortFilterProxyModel* m_filterProxyModel = nullptr;
FilterCategoryWidget* m_statusFilter = nullptr;
}; };
} // namespace O3DE::ProjectManager } // namespace O3DE::ProjectManager

@ -204,7 +204,6 @@ namespace O3DE::ProjectManager
painter->save(); painter->save();
const QRect buttonRect = CalcButtonRect(contentRect); const QRect buttonRect = CalcButtonRect(contentRect);
QPoint circleCenter; QPoint circleCenter;
QString buttonText;
const bool isAdded = GemModel::IsAdded(modelIndex); const bool isAdded = GemModel::IsAdded(modelIndex);
if (isAdded) if (isAdded)
@ -213,34 +212,15 @@ namespace O3DE::ProjectManager
painter->setPen(m_buttonEnabledColor); painter->setPen(m_buttonEnabledColor);
circleCenter = buttonRect.center() + QPoint(buttonRect.width() / 2 - s_buttonBorderRadius + 1, 1); circleCenter = buttonRect.center() + QPoint(buttonRect.width() / 2 - s_buttonBorderRadius + 1, 1);
buttonText = "Added";
} }
else else
{ {
circleCenter = buttonRect.center() + QPoint(-buttonRect.width() / 2 + s_buttonBorderRadius, 1); circleCenter = buttonRect.center() + QPoint(-buttonRect.width() / 2 + s_buttonBorderRadius, 1);
buttonText = "Get";
} }
// Rounded rect // Rounded rect
painter->drawRoundedRect(buttonRect, s_buttonBorderRadius, s_buttonBorderRadius); painter->drawRoundedRect(buttonRect, s_buttonBorderRadius, s_buttonBorderRadius);
// Text
QFont font;
QRect textRect = GetTextRect(font, buttonText, s_buttonFontSize);
if (isAdded)
{
textRect = QRect(buttonRect.left(), buttonRect.top(), buttonRect.width() - s_buttonCircleRadius * 2.0, buttonRect.height());
}
else
{
textRect = QRect(buttonRect.left() + s_buttonCircleRadius * 2.0, buttonRect.top(), buttonRect.width() - s_buttonCircleRadius * 2.0, buttonRect.height());
}
font.setPixelSize(s_buttonFontSize);
painter->setFont(font);
painter->setPen(m_textColor);
painter->drawText(textRect, Qt::AlignCenter, buttonText);
// Circle // Circle
painter->setBrush(m_textColor); painter->setBrush(m_textColor);
painter->drawEllipse(circleCenter, s_buttonCircleRadius, s_buttonCircleRadius); painter->drawEllipse(circleCenter, s_buttonCircleRadius, s_buttonCircleRadius);

@ -15,6 +15,7 @@
#include <QStandardItemModel> #include <QStandardItemModel>
#include <QLabel> #include <QLabel>
#include <QVBoxLayout> #include <QVBoxLayout>
#include <QSpacerItem>
namespace O3DE::ProjectManager namespace O3DE::ProjectManager
{ {
@ -74,6 +75,15 @@ namespace O3DE::ProjectManager
gemSummaryLabel->setStyleSheet("font-size: 12px;"); gemSummaryLabel->setStyleSheet("font-size: 12px;");
columnHeaderLayout->addWidget(gemSummaryLabel); columnHeaderLayout->addWidget(gemSummaryLabel);
QSpacerItem* horizontalSpacer = new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Minimum);
columnHeaderLayout->addSpacerItem(horizontalSpacer);
QLabel* gemSelectedLabel = new QLabel(tr("Selected"));
gemSelectedLabel->setStyleSheet("font-size: 12px;");
columnHeaderLayout->addWidget(gemSelectedLabel);
columnHeaderLayout->addSpacing(60);
vLayout->addLayout(columnHeaderLayout); vLayout->addLayout(columnHeaderLayout);
} }
} // namespace O3DE::ProjectManager } // namespace O3DE::ProjectManager

@ -235,4 +235,19 @@ namespace O3DE::ProjectManager
} }
return result; return result;
} }
int GemModel::TotalAddedGems() const
{
int result = 0;
for (int row = 0; row < rowCount(); ++row)
{
const QModelIndex modelIndex = index(row, 0);
if (IsAdded(modelIndex))
{
++result;
}
}
return result;
}
} // namespace O3DE::ProjectManager } // namespace O3DE::ProjectManager

@ -63,6 +63,8 @@ namespace O3DE::ProjectManager
QVector<QModelIndex> GatherGemsToBeAdded() const; QVector<QModelIndex> GatherGemsToBeAdded() const;
QVector<QModelIndex> GatherGemsToBeRemoved() const; QVector<QModelIndex> GatherGemsToBeRemoved() const;
int TotalAddedGems() const;
private: private:
enum UserRole enum UserRole
{ {

@ -37,6 +37,16 @@ namespace O3DE::ProjectManager
return false; return false;
} }
// Gem status
if (m_gemStatusFilter != GemStatus::NoFilter)
{
const GemStatus sourceGemStatus = static_cast<GemStatus>(GemModel::IsAdded(sourceIndex));
if (m_gemStatusFilter != sourceGemStatus)
{
return false;
}
}
// Gem origins // Gem origins
if (m_gemOriginFilter) if (m_gemOriginFilter)
{ {
@ -125,6 +135,19 @@ namespace O3DE::ProjectManager
return true; return true;
} }
QString GemSortFilterProxyModel::GetGemStatusString(GemStatus status)
{
switch (status)
{
case Unselected:
return "Unselected";
case Selected:
return "Selected";
default:
return "<Unknown Gem Status>";
}
}
void GemSortFilterProxyModel::InvalidateFilter() void GemSortFilterProxyModel::InvalidateFilter()
{ {
invalidate(); invalidate();

@ -29,8 +29,17 @@ namespace O3DE::ProjectManager
Q_OBJECT // AUTOMOC Q_OBJECT // AUTOMOC
public: public:
enum GemStatus
{
NoFilter = -1,
Unselected,
Selected
};
GemSortFilterProxyModel(GemModel* sourceModel, QObject* parent = nullptr); GemSortFilterProxyModel(GemModel* sourceModel, QObject* parent = nullptr);
static QString GetGemStatusString(GemStatus status);
bool filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const override; bool filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const override;
GemModel* GetSourceModel() const { return m_sourceModel; } GemModel* GetSourceModel() const { return m_sourceModel; }
@ -38,6 +47,9 @@ namespace O3DE::ProjectManager
void SetSearchString(const QString& searchString) { m_searchString = searchString; InvalidateFilter(); } void SetSearchString(const QString& searchString) { m_searchString = searchString; InvalidateFilter(); }
GemStatus GetGemStatus() const { return m_gemStatusFilter; }
void SetGemStatus(GemStatus gemStatus) { m_gemStatusFilter = gemStatus; InvalidateFilter(); }
GemInfo::GemOrigins GetGemOrigins() const { return m_gemOriginFilter; } GemInfo::GemOrigins GetGemOrigins() const { return m_gemOriginFilter; }
void SetGemOrigins(const GemInfo::GemOrigins& gemOrigins) { m_gemOriginFilter = gemOrigins; InvalidateFilter(); } void SetGemOrigins(const GemInfo::GemOrigins& gemOrigins) { m_gemOriginFilter = gemOrigins; InvalidateFilter(); }
@ -61,6 +73,7 @@ namespace O3DE::ProjectManager
AzQtComponents::SelectionProxyModel* m_selectionProxyModel = nullptr; AzQtComponents::SelectionProxyModel* m_selectionProxyModel = nullptr;
QString m_searchString; QString m_searchString;
GemStatus m_gemStatusFilter = GemStatus::NoFilter;
GemInfo::GemOrigins m_gemOriginFilter = {}; GemInfo::GemOrigins m_gemOriginFilter = {};
GemInfo::Platforms m_platformFilter = {}; GemInfo::Platforms m_platformFilter = {};
GemInfo::Types m_typeFilter = {}; GemInfo::Types m_typeFilter = {};

@ -33,7 +33,7 @@ namespace O3DE::ProjectManager
{ {
setObjectName("labelButton"); setObjectName("labelButton");
QVBoxLayout* vLayout = new QVBoxLayout(this); QVBoxLayout* vLayout = new QVBoxLayout();
vLayout->setContentsMargins(0, 0, 0, 0); vLayout->setContentsMargins(0, 0, 0, 0);
vLayout->setSpacing(5); vLayout->setSpacing(5);

@ -13,21 +13,11 @@
#include <ProjectManagerWindow.h> #include <ProjectManagerWindow.h>
#include <ScreensCtrl.h> #include <ScreensCtrl.h>
#include <AzQtComponents/Components/StyleManager.h>
#include <AzCore/IO/FileIO.h>
#include <AzCore/IO/Path/Path.h>
#include <AzFramework/CommandLine/CommandLine.h>
#include <AzFramework/Application/Application.h>
#include <QDir>
namespace O3DE::ProjectManager namespace O3DE::ProjectManager
{ {
ProjectManagerWindow::ProjectManagerWindow(QWidget* parent, const AZ::IO::PathView& engineRootPath, const AZ::IO::PathView& projectPath, ProjectManagerScreen startScreen) ProjectManagerWindow::ProjectManagerWindow(QWidget* parent, const AZ::IO::PathView& projectPath, ProjectManagerScreen startScreen)
: QMainWindow(parent) : QMainWindow(parent)
{ {
m_pythonBindings = AZStd::make_unique<PythonBindings>(engineRootPath);
setWindowTitle(tr("O3DE Project Manager")); setWindowTitle(tr("O3DE Project Manager"));
ScreensCtrl* screensCtrl = new ScreensCtrl(); ScreensCtrl* screensCtrl = new ScreensCtrl();
@ -44,15 +34,6 @@ namespace O3DE::ProjectManager
setCentralWidget(screensCtrl); setCentralWidget(screensCtrl);
// setup stylesheets and hot reloading
QDir rootDir = QString::fromUtf8(engineRootPath.Native().data(), aznumeric_cast<int>(engineRootPath.Native().size()));
const auto pathOnDisk = rootDir.absoluteFilePath("Code/Tools/ProjectManager/Resources");
const auto qrcPath = QStringLiteral(":/ProjectManager/style");
AzQtComponents::StyleManager::addSearchPaths("style", pathOnDisk, qrcPath, engineRootPath);
// set stylesheet after creating the screens or their styles won't get updated
AzQtComponents::StyleManager::setStyleSheet(this, QStringLiteral("style:ProjectManager.qss"));
// always push the projects screen first so we have something to come back to // always push the projects screen first so we have something to come back to
if (startScreen != ProjectManagerScreen::Projects) if (startScreen != ProjectManagerScreen::Projects)
{ {
@ -66,10 +47,4 @@ namespace O3DE::ProjectManager
emit screensCtrl->NotifyCurrentProject(path); emit screensCtrl->NotifyCurrentProject(path);
} }
} }
ProjectManagerWindow::~ProjectManagerWindow()
{
m_pythonBindings.reset();
}
} // namespace O3DE::ProjectManager } // namespace O3DE::ProjectManager

@ -13,7 +13,7 @@
#if !defined(Q_MOC_RUN) #if !defined(Q_MOC_RUN)
#include <QMainWindow> #include <QMainWindow>
#include <PythonBindings.h> #include <AzCore/IO/Path/Path.h>
#include <ScreenDefs.h> #include <ScreenDefs.h>
#endif #endif
@ -25,12 +25,8 @@ namespace O3DE::ProjectManager
Q_OBJECT Q_OBJECT
public: public:
explicit ProjectManagerWindow(QWidget* parent, const AZ::IO::PathView& engineRootPath, const AZ::IO::PathView& projectPath, explicit ProjectManagerWindow(QWidget* parent, const AZ::IO::PathView& projectPath,
ProjectManagerScreen startScreen = ProjectManagerScreen::Projects); ProjectManagerScreen startScreen = ProjectManagerScreen::Projects);
~ProjectManagerWindow();
private:
AZStd::unique_ptr<PythonBindings> m_pythonBindings;
}; };
} // namespace O3DE::ProjectManager } // namespace O3DE::ProjectManager

@ -37,7 +37,7 @@ namespace O3DE::ProjectManager
// if we don't set this in a frame (just use a sub-layout) all the content will align incorrectly horizontally // if we don't set this in a frame (just use a sub-layout) all the content will align incorrectly horizontally
QFrame* projectSettingsFrame = new QFrame(this); QFrame* projectSettingsFrame = new QFrame(this);
projectSettingsFrame->setObjectName("projectSettings"); projectSettingsFrame->setObjectName("projectSettings");
m_verticalLayout = new QVBoxLayout(this); m_verticalLayout = new QVBoxLayout();
// you cannot remove content margins in qss // you cannot remove content margins in qss
m_verticalLayout->setContentsMargins(0, 0, 0, 0); m_verticalLayout->setContentsMargins(0, 0, 0, 0);

@ -85,7 +85,7 @@ namespace O3DE::ProjectManager
QFrame* frame = new QFrame(this); QFrame* frame = new QFrame(this);
frame->setObjectName("firstTimeContent"); frame->setObjectName("firstTimeContent");
{ {
QVBoxLayout* layout = new QVBoxLayout(this); QVBoxLayout* layout = new QVBoxLayout();
layout->setContentsMargins(0, 0, 0, 0); layout->setContentsMargins(0, 0, 0, 0);
layout->setAlignment(Qt::AlignTop); layout->setAlignment(Qt::AlignTop);
frame->setLayout(layout); frame->setLayout(layout);
@ -100,7 +100,7 @@ namespace O3DE::ProjectManager
"available by downloading our sample project.")); "available by downloading our sample project."));
layout->addWidget(introLabel); layout->addWidget(introLabel);
QHBoxLayout* buttonLayout = new QHBoxLayout(this); QHBoxLayout* buttonLayout = new QHBoxLayout();
buttonLayout->setAlignment(Qt::AlignLeft); buttonLayout->setAlignment(Qt::AlignLeft);
buttonLayout->setSpacing(s_spacerSize); buttonLayout->setSpacing(s_spacerSize);

@ -226,7 +226,7 @@ namespace O3DE::ProjectManager
PythonBindings::PythonBindings(const AZ::IO::PathView& enginePath) PythonBindings::PythonBindings(const AZ::IO::PathView& enginePath)
: m_enginePath(enginePath) : m_enginePath(enginePath)
{ {
StartPython(); m_pythonStarted = StartPython();
} }
PythonBindings::~PythonBindings() PythonBindings::~PythonBindings()
@ -234,6 +234,11 @@ namespace O3DE::ProjectManager
StopPython(); StopPython();
} }
bool PythonBindings::PythonStarted()
{
return m_pythonStarted && Py_IsInitialized();
}
bool PythonBindings::StartPython() bool PythonBindings::StartPython()
{ {
if (Py_IsInitialized()) if (Py_IsInitialized())
@ -246,7 +251,7 @@ namespace O3DE::ProjectManager
AZStd::string pyBasePath = Platform::GetPythonHomePath(PY_PACKAGE, m_enginePath.c_str()); AZStd::string pyBasePath = Platform::GetPythonHomePath(PY_PACKAGE, m_enginePath.c_str());
if (!AZ::IO::SystemFile::Exists(pyBasePath.c_str())) if (!AZ::IO::SystemFile::Exists(pyBasePath.c_str()))
{ {
AZ_Assert(false, "Python home path must exist. path:%s", pyBasePath.c_str()); AZ_Error("python", false, "Python home path does not exist: %s", pyBasePath.c_str());
return false; return false;
} }
@ -351,6 +356,11 @@ namespace O3DE::ProjectManager
AZ::Outcome<void, AZStd::string> PythonBindings::ExecuteWithLockErrorHandling(AZStd::function<void()> executionCallback) AZ::Outcome<void, AZStd::string> PythonBindings::ExecuteWithLockErrorHandling(AZStd::function<void()> executionCallback)
{ {
if (!Py_IsInitialized())
{
return AZ::Failure<AZStd::string>("Python is not initialized");
}
AZStd::lock_guard<decltype(m_lock)> lock(m_lock); AZStd::lock_guard<decltype(m_lock)> lock(m_lock);
pybind11::gil_scoped_release release; pybind11::gil_scoped_release release;
pybind11::gil_scoped_acquire acquire; pybind11::gil_scoped_acquire acquire;

@ -34,6 +34,8 @@ namespace O3DE::ProjectManager
~PythonBindings() override; ~PythonBindings() override;
// PythonBindings overrides // PythonBindings overrides
bool PythonStarted() override;
// Engine // Engine
AZ::Outcome<EngineInfo> GetEngineInfo() override; AZ::Outcome<EngineInfo> GetEngineInfo() override;
bool SetEngineInfo(const EngineInfo& engineInfo) override; bool SetEngineInfo(const EngineInfo& engineInfo) override;
@ -70,6 +72,8 @@ namespace O3DE::ProjectManager
bool StopPython(); bool StopPython();
bool m_pythonStarted = false;
AZ::IO::FixedMaxPath m_enginePath; AZ::IO::FixedMaxPath m_enginePath;
pybind11::handle m_engineTemplate; pybind11::handle m_engineTemplate;
AZStd::recursive_mutex m_lock; AZStd::recursive_mutex m_lock;

@ -34,6 +34,12 @@ namespace O3DE::ProjectManager
IPythonBindings() = default; IPythonBindings() = default;
virtual ~IPythonBindings() = default; virtual ~IPythonBindings() = default;
/**
* Get whether Python was started or not. All Python functionality will fail if Python
* failed to start.
* @return true if Python was started successfully, false on failure
*/
virtual bool PythonStarted() = 0;
// Engine // Engine

@ -189,11 +189,13 @@ namespace O3DE::ProjectManager
{ {
if (m_stack->currentIndex() == ScreenOrder::Gems) if (m_stack->currentIndex() == ScreenOrder::Gems)
{ {
m_header->setSubTitle(QString(tr("Configure Gems for \"%1\"")).arg(m_projectInfo.m_projectName)); m_header->setTitle(QString(tr("Edit Project Settings: \"%1\"")).arg(m_projectInfo.m_projectName));
m_nextButton->setText(tr("Confirm")); m_header->setSubTitle(QString(tr("Configure Gems")));
m_nextButton->setText(tr("Finalize"));
} }
else else
{ {
m_header->setTitle("");
m_header->setSubTitle(QString(tr("Edit Project Settings: \"%1\"")).arg(m_projectInfo.m_projectName)); m_header->setSubTitle(QString(tr("Edit Project Settings: \"%1\"")).arg(m_projectInfo.m_projectName));
m_nextButton->setText(tr("Save")); m_nextButton->setText(tr("Save"));
} }

@ -10,85 +10,26 @@
* *
*/ */
#include <AzQtComponents/Utilities/HandleDpiAwareness.h> #include <AzQtComponents/Utilities/QtPluginPaths.h>
#include <AzQtComponents/Components/StyleManager.h> #include <Application.h>
#include <AzCore/Component/ComponentApplication.h>
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
#include <AzCore/IO/Path/Path.h>
#include <AzFramework/CommandLine/CommandLine.h>
#include <ProjectManagerWindow.h>
#include <ProjectUtils.h>
#include <QApplication>
#include <QCoreApplication>
#include <QGuiApplication>
using namespace O3DE::ProjectManager;
int main(int argc, char* argv[]) int main(int argc, char* argv[])
{ {
QApplication::setOrganizationName("O3DE");
QApplication::setOrganizationDomain("o3de.org");
QCoreApplication::setApplicationName("ProjectManager");
QCoreApplication::setApplicationVersion("1.0");
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
QCoreApplication::setAttribute(Qt::AA_DontCreateNativeWidgetSiblings);
QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough);
AzQtComponents::Utilities::HandleDpiAwareness(AzQtComponents::Utilities::SystemDpiAware);
AZ::AllocatorInstance<AZ::SystemAllocator>::Create();
int runSuccess = 0; int runSuccess = 0;
{
QApplication app(argc, argv);
// Need to use settings registry to get EngineRootFolder
AZ::IO::FixedMaxPath engineRootPath;
{
AZ::ComponentApplication componentApplication;
auto settingsRegistry = AZ::SettingsRegistry::Get();
settingsRegistry->Get(engineRootPath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder);
}
AzQtComponents::StyleManager styleManager(&app);
styleManager.initialize(&app, engineRootPath);
// Get the initial start screen if one is provided via command line // Call before using any Qt, or the app may not be able to locate Qt libs
constexpr char optionPrefix[] = "--"; AzQtComponents::PrepareQtPaths();
AZ::CommandLine commandLine(optionPrefix);
commandLine.Parse(argc, argv);
ProjectManagerScreen startScreen = ProjectManagerScreen::Projects; O3DE::ProjectManager::Application application(&argc, &argv);
if(commandLine.HasSwitch("screen")) if (!application.Init())
{ {
QString screenOption = commandLine.GetSwitchValue("screen", 0).c_str(); AZ_Error("ProjectManager", false, "Failed to initialize");
ProjectManagerScreen screen = ProjectUtils::GetProjectManagerScreen(screenOption); runSuccess = 1;
if (screen != ProjectManagerScreen::Invalid) }
{ else
startScreen = screen; {
} runSuccess = application.Run() ? 0 : 1;
}
AZ::IO::FixedMaxPath projectPath;
if (commandLine.HasSwitch("project-path"))
{
projectPath = commandLine.GetSwitchValue("project-path", 0).c_str();
}
ProjectManagerWindow window(nullptr, engineRootPath, projectPath, startScreen);
window.show();
// somethings is preventing us from moving the window to the center of the
// primary screen - likely an Az style or component helper
constexpr int width = 1200;
constexpr int height = 800;
window.resize(width, height);
runSuccess = app.exec();
} }
AZ::AllocatorInstance<AZ::SystemAllocator>::Destroy();
return runSuccess; return runSuccess;
} }

@ -0,0 +1,17 @@
#
# 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
Resources/ProjectManager.rc
Resources/ProjectManager.qrc
Resources/ProjectManager.qss
Source/main.cpp
)

@ -1,4 +1,5 @@
# #
#
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or # All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
# its licensors. # its licensors.
# #
@ -10,10 +11,8 @@
# #
set(FILES set(FILES
Resources/ProjectManager.rc Source/Application.h
Resources/ProjectManager.qrc Source/Application.cpp
Resources/ProjectManager.qss
Source/main.cpp
Source/ScreenDefs.h Source/ScreenDefs.h
Source/ScreenFactory.h Source/ScreenFactory.h
Source/ScreenFactory.cpp Source/ScreenFactory.cpp

@ -0,0 +1,17 @@
#
# 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
Resources/ProjectManager.qrc
Resources/ProjectManager.qss
tests/ApplicationTests.cpp
tests/main.cpp
)

@ -0,0 +1,47 @@
/*
* 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 <AzCore/std/smart_ptr/make_shared.h>
#include <Application.h>
#include <ProjectManager_Test_Traits_Platform.h>
namespace O3DE::ProjectManager
{
class ProjectManagerApplicationTests
: public ::UnitTest::ScopedAllocatorSetupFixture
{
public:
ProjectManagerApplicationTests()
{
m_application = AZStd::make_unique<ProjectManager::Application>();
}
~ProjectManagerApplicationTests()
{
m_application.reset();
}
AZStd::unique_ptr<ProjectManager::Application> m_application;
};
#if AZ_TRAIT_DISABLE_FAILED_PROJECT_MANAGER_TESTS
TEST_F(ProjectManagerApplicationTests, DISABLED_Application_Init_Succeeds)
#else
TEST_F(ProjectManagerApplicationTests, Application_Init_Succeeds)
#endif // !AZ_TRAIT_DISABLE_FAILED_PROJECT_MANAGER_TESTS
{
// we don't want to interact with actual GUI or display it
EXPECT_TRUE(m_application->Init(/*interactive=*/false));
}
}

@ -0,0 +1,35 @@
/*
* 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>
DECLARE_AZ_UNIT_TEST_MAIN();
int runDefaultRunner(int argc, char* argv[])
{
INVOKE_AZ_UNIT_TEST_MAIN(nullptr)
return 0;
}
int main(int argc, char* argv[])
{
if (argc == 1)
{
// if no parameters are provided, add the --unittests parameter
constexpr int defaultArgc = 2;
char unittest_arg[] = "--unittests"; // Conversion from string literal to char* is not allowed per ISO C++11
char* defaultArgv[defaultArgc] = { argv[0], unittest_arg };
return runDefaultRunner(defaultArgc, defaultArgv);
}
INVOKE_AZ_UNIT_TEST_MAIN(nullptr);
return 0;
}

@ -46,22 +46,69 @@ namespace AZ
serializeContext->Class<AssImpTransformImporter, SceneCore::LoadingComponent>()->Version(1); serializeContext->Class<AssImpTransformImporter, SceneCore::LoadingComponent>()->Version(1);
} }
} }
void GetAllBones(const aiScene* scene, AZStd::unordered_map<AZStd::string, const aiBone*>& boneLookup)
{
for (unsigned meshIndex = 0; meshIndex < scene->mNumMeshes; ++meshIndex)
{
const aiMesh* mesh = scene->mMeshes[meshIndex];
for (unsigned boneIndex = 0; boneIndex < mesh->mNumBones; ++boneIndex)
{
const aiBone* bone = mesh->mBones[boneIndex];
boneLookup[bone->mName.C_Str()] = bone;
}
}
}
Events::ProcessingResult AssImpTransformImporter::ImportTransform(AssImpSceneNodeAppendedContext& context) Events::ProcessingResult AssImpTransformImporter::ImportTransform(AssImpSceneNodeAppendedContext& context)
{ {
AZ_TraceContext("Importer", "transform"); AZ_TraceContext("Importer", "transform");
const aiNode* currentNode = context.m_sourceNode.GetAssImpNode(); const aiNode* currentNode = context.m_sourceNode.GetAssImpNode();
const aiScene* scene = context.m_sourceScene.GetAssImpScene(); const aiScene* scene = context.m_sourceScene.GetAssImpScene();
if (currentNode == scene->mRootNode || IsPivotNode(currentNode->mName)) if (currentNode == scene->mRootNode || IsPivotNode(currentNode->mName))
{ {
return Events::ProcessingResult::Ignored; return Events::ProcessingResult::Ignored;
} }
aiMatrix4x4 combinedTransform = GetConcatenatedLocalTransform(currentNode); AZStd::unordered_map<AZStd::string, const aiBone*> boneLookup;
GetAllBones(scene, boneLookup);
auto boneIterator = boneLookup.find(currentNode->mName.C_Str());
const bool isBone = boneIterator != boneLookup.end();
aiMatrix4x4 combinedTransform;
if (isBone)
{
auto parentNode = currentNode->mParent;
aiMatrix4x4 offsetMatrix = boneIterator->second->mOffsetMatrix;
aiMatrix4x4 parentOffset {};
auto parentBoneIterator = boneLookup.find(parentNode->mName.C_Str());
if (parentNode && parentBoneIterator != boneLookup.end())
{
const auto& parentBone = parentBoneIterator->second;
parentOffset = parentBone->mOffsetMatrix;
}
auto inverseOffset = offsetMatrix;
inverseOffset.Inverse();
combinedTransform = parentOffset * inverseOffset;
}
else
{
combinedTransform = GetConcatenatedLocalTransform(currentNode);
}
DataTypes::MatrixType localTransform = AssImpSDKWrapper::AssImpTypeConverter::ToTransform(combinedTransform); DataTypes::MatrixType localTransform = AssImpSDKWrapper::AssImpTypeConverter::ToTransform(combinedTransform);
context.m_sourceSceneSystem.SwapTransformForUpAxis(localTransform); context.m_sourceSceneSystem.SwapTransformForUpAxis(localTransform);
context.m_sourceSceneSystem.ConvertUnit(localTransform); context.m_sourceSceneSystem.ConvertUnit(localTransform);
@ -105,9 +152,7 @@ namespace AZ
} }
else else
{ {
bool addedData = context.m_scene.GetGraph().SetContent( bool addedData = context.m_scene.GetGraph().SetContent(context.m_currentGraphPosition, transformData);
context.m_currentGraphPosition,
transformData);
AZ_Error(SceneAPI::Utilities::ErrorWindow, addedData, "Failed to add node data"); AZ_Error(SceneAPI::Utilities::ErrorWindow, addedData, "Failed to add node data");
return addedData ? Events::ProcessingResult::Success : Events::ProcessingResult::Failure; return addedData ? Events::ProcessingResult::Success : Events::ProcessingResult::Failure;

@ -69,13 +69,14 @@ namespace AZ
// aiProcess_JoinIdenticalVertices is not enabled because O3DE has a mesh optimizer that also does this, // aiProcess_JoinIdenticalVertices is not enabled because O3DE has a mesh optimizer that also does this,
// this flag is disabled to keep AssImp output similar to FBX SDK to reduce downstream bugs for the initial AssImp release. // this flag is disabled to keep AssImp output similar to FBX SDK to reduce downstream bugs for the initial AssImp release.
// There's currently a minimum of properties and flags set to maximize compatibility with the existing node graph. // There's currently a minimum of properties and flags set to maximize compatibility with the existing node graph.
// aiProcess_LimitBoneWeights is not enabled because it will remove bones which are not associated with a mesh.
// This results in the loss of the offset matrix data for nodes without a mesh which is required for the Transform Importer.
m_importer.SetPropertyBool(AI_CONFIG_IMPORT_FBX_PRESERVE_PIVOTS, false); m_importer.SetPropertyBool(AI_CONFIG_IMPORT_FBX_PRESERVE_PIVOTS, false);
m_importer.SetPropertyBool(AI_CONFIG_IMPORT_FBX_OPTIMIZE_EMPTY_ANIMATION_CURVES, false); m_importer.SetPropertyBool(AI_CONFIG_IMPORT_FBX_OPTIMIZE_EMPTY_ANIMATION_CURVES, false);
m_sceneFileName = fileName; m_sceneFileName = fileName;
m_assImpScene = m_importer.ReadFile(fileName, m_assImpScene = m_importer.ReadFile(fileName,
aiProcess_Triangulate //Triangulates all faces of all meshes aiProcess_Triangulate //Triangulates all faces of all meshes
| aiProcess_LimitBoneWeights //Limits the number of bones that can affect a vertex to a maximum value
//dropping the least important and re-normalizing
| aiProcess_GenNormals); //Generate normals for meshes | aiProcess_GenNormals); //Generate normals for meshes
#if AZ_TRAIT_COMPILER_SUPPORT_CSIGNAL #if AZ_TRAIT_COMPILER_SUPPORT_CSIGNAL

@ -177,9 +177,10 @@ namespace AZ
AZ::Entity* rootEntity = reinterpret_cast<AZ::Entity*>(classPtr); AZ::Entity* rootEntity = reinterpret_cast<AZ::Entity*>(classPtr);
bool convertResult = ConvertSliceToPrefab(context, outputPath, isDryRun, rootEntity); bool convertResult = ConvertSliceToPrefab(context, outputPath, isDryRun, rootEntity);
// Clear out the references to any nested slices so that the nested assets get unloaded correctly at the end of
// the conversion. // Delete the root entity pointer. Otherwise, it will leak itself along with all of the slice asset references held
ClearSliceAssetReferences(rootEntity); // within it.
delete rootEntity;
return convertResult; return convertResult;
}; };
@ -229,8 +230,12 @@ namespace AZ
return false; return false;
} }
// Get all of the entities from the slice. // Get all of the entities from the slice. We're taking ownership of them, so we also remove them from the slice component
// without deleting them.
constexpr bool deleteEntities = false;
constexpr bool removeEmptyInstances = true;
SliceComponent::EntityList sliceEntities = sliceComponent->GetNewEntities(); SliceComponent::EntityList sliceEntities = sliceComponent->GetNewEntities();
sliceComponent->RemoveAllEntities(deleteEntities, removeEmptyInstances);
AZ_Printf("Convert-Slice", " Slice contains %zu entities.\n", sliceEntities.size()); AZ_Printf("Convert-Slice", " Slice contains %zu entities.\n", sliceEntities.size());
// Create the Prefab with the entities from the slice. // Create the Prefab with the entities from the slice.
@ -273,6 +278,12 @@ namespace AZ
} }
} }
// Save off a mapping of the slice's metadata entity ID as well, even though we never converted the entity itself.
// This will help us better detect entity ID mapping errors for nested slice instances.
AZ::Entity* metadataEntity = sliceComponent->GetMetadataEntity();
constexpr bool isMetadataEntity = true;
m_aliasIdMapper.emplace(metadataEntity->GetId(), SliceEntityMappingInfo(templateId, "MetadataEntity", isMetadataEntity));
// Update the prefab template with the fixed-up data in our prefab instance. // Update the prefab template with the fixed-up data in our prefab instance.
AzToolsFramework::Prefab::PrefabDom prefabDom; AzToolsFramework::Prefab::PrefabDom prefabDom;
bool storeResult = AzToolsFramework::Prefab::PrefabDomUtils::StoreInstanceInPrefabDom(*sourceInstance, prefabDom); bool storeResult = AzToolsFramework::Prefab::PrefabDomUtils::StoreInstanceInPrefabDom(*sourceInstance, prefabDom);
@ -374,24 +385,36 @@ namespace AZ
return false; return false;
} }
// Now, convert the nested slice to a prefab. // Check to see if we've already converted this slice at a higher level of slice nesting, or if this is our first
bool nestedSliceResult = ConvertSliceFile(serializeContext, assetPath, isDryRun); // occurrence and we need to convert it now.
if (!nestedSliceResult)
{
AZ_Warning("Convert-Slice", nestedSliceResult, " Nested slice '%s' could not be converted.", assetPath.c_str());
return false;
}
// Find the prefab template we created for the newly-created nested prefab. // First, take our absolute slice path and turn it into a project-relative prefab path.
// To get the template, we need to take our absolute slice path and turn it into a project-relative prefab path.
AZ::IO::Path nestedPrefabPath = assetPath; AZ::IO::Path nestedPrefabPath = assetPath;
nestedPrefabPath.ReplaceExtension("prefab"); nestedPrefabPath.ReplaceExtension("prefab");
auto prefabLoaderInterface = AZ::Interface<AzToolsFramework::Prefab::PrefabLoaderInterface>::Get(); auto prefabLoaderInterface = AZ::Interface<AzToolsFramework::Prefab::PrefabLoaderInterface>::Get();
nestedPrefabPath = prefabLoaderInterface->GenerateRelativePath(nestedPrefabPath); nestedPrefabPath = prefabLoaderInterface->GenerateRelativePath(nestedPrefabPath);
// Now, see if we already have a template ID in memory for it.
AzToolsFramework::Prefab::TemplateId nestedTemplateId = AzToolsFramework::Prefab::TemplateId nestedTemplateId =
prefabSystemComponentInterface->GetTemplateIdFromFilePath(nestedPrefabPath); prefabSystemComponentInterface->GetTemplateIdFromFilePath(nestedPrefabPath);
// If we don't have a template ID yet, convert the nested slice to a prefab and get the template ID.
if (nestedTemplateId == AzToolsFramework::Prefab::InvalidTemplateId)
{
bool nestedSliceResult = ConvertSliceFile(serializeContext, assetPath, isDryRun);
if (!nestedSliceResult)
{
AZ_Warning("Convert-Slice", nestedSliceResult, " Nested slice '%s' could not be converted.", assetPath.c_str());
return false;
}
nestedTemplateId = prefabSystemComponentInterface->GetTemplateIdFromFilePath(nestedPrefabPath);
AZ_Assert(nestedTemplateId != AzToolsFramework::Prefab::InvalidTemplateId,
"Template ID for %s is invalid", nestedPrefabPath.c_str());
}
// Get the nested prefab template.
AzToolsFramework::Prefab::TemplateReference nestedTemplate = AzToolsFramework::Prefab::TemplateReference nestedTemplate =
prefabSystemComponentInterface->FindTemplate(nestedTemplateId); prefabSystemComponentInterface->FindTemplate(nestedTemplateId);
@ -402,6 +425,21 @@ namespace AZ
"Convert-Slice", " Attaching %zu instances of nested slice '%s'.\n", instances.size(), "Convert-Slice", " Attaching %zu instances of nested slice '%s'.\n", instances.size(),
nestedPrefabPath.Native().c_str()); nestedPrefabPath.Native().c_str());
// Before processing any further, save off all the known entity IDs from all the instances and how they map back to
// the base nested prefab that they've come from (i.e. this one). As we proceed up the chain of nesting, this will
// build out a hierarchical list of owning instances for each entity that we can trace upwards to know where to add
// the entity into our nested prefab instance.
// This step needs to occur *before* converting the instances themselves, because while converting instances, they
// might have entity ID references that point to other instances. By having the full instance entity ID map in place
// before conversion, we'll be able to fix them up appropriately.
for (auto& instance : instances)
{
AZStd::string instanceAlias = GetInstanceAlias(instance);
UpdateSliceEntityInstanceMappings(instance.GetEntityIdToBaseMap(), instanceAlias);
}
// Now that we have all the entity ID mappings, convert all the instances.
for (auto& instance : instances) for (auto& instance : instances)
{ {
bool instanceConvertResult = ConvertSliceInstance(instance, sliceAsset, nestedTemplate, sourceInstance); bool instanceConvertResult = ConvertSliceInstance(instance, sliceAsset, nestedTemplate, sourceInstance);
@ -415,6 +453,28 @@ namespace AZ
return true; return true;
} }
AZStd::string SliceConverter::GetInstanceAlias(const AZ::SliceComponent::SliceInstance& instance)
{
// When creating the new instance, we would like to have deterministic instance aliases. Prefabs that depend on this one
// will have patches that reference the alias, so if we reconvert this slice a second time, we would like it to produce
// the same results. To get a deterministic and unique alias, we rely on the slice instance. The slice instance contains
// a map of slice entity IDs to unique instance entity IDs. We'll just consistently use the first entry in the map as the
// unique instance ID.
AZStd::string instanceAlias;
auto entityIdMap = instance.GetEntityIdMap();
if (!entityIdMap.empty())
{
instanceAlias = AZStd::string::format("Instance_%s", entityIdMap.begin()->second.ToString().c_str());
}
else
{
AZ_Error("Convert-Slice", false, " Couldn't create deterministic instance alias.");
instanceAlias = AZStd::string::format("Instance_%s", AZ::Entity::MakeId().ToString().c_str());
}
return instanceAlias;
}
bool SliceConverter::ConvertSliceInstance( bool SliceConverter::ConvertSliceInstance(
AZ::SliceComponent::SliceInstance& instance, AZ::SliceComponent::SliceInstance& instance,
AZ::Data::Asset<AZ::SliceAsset>& sliceAsset, AZ::Data::Asset<AZ::SliceAsset>& sliceAsset,
@ -438,27 +498,7 @@ namespace AZ
auto instanceToTemplateInterface = AZ::Interface<AzToolsFramework::Prefab::InstanceToTemplateInterface>::Get(); auto instanceToTemplateInterface = AZ::Interface<AzToolsFramework::Prefab::InstanceToTemplateInterface>::Get();
auto prefabSystemComponentInterface = AZ::Interface<AzToolsFramework::Prefab::PrefabSystemComponentInterface>::Get(); auto prefabSystemComponentInterface = AZ::Interface<AzToolsFramework::Prefab::PrefabSystemComponentInterface>::Get();
// When creating the new instance, we would like to have deterministic instance aliases. Prefabs that depend on this one AZStd::string instanceAlias = GetInstanceAlias(instance);
// will have patches that reference the alias, so if we reconvert this slice a second time, we would like it to produce
// the same results. To get a deterministic and unique alias, we rely on the slice instance. The slice instance contains
// a map of slice entity IDs to unique instance entity IDs. We'll just consistently use the first entry in the map as the
// unique instance ID.
AZStd::string instanceAlias;
auto entityIdMap = instance.GetEntityIdMap();
if (!entityIdMap.empty())
{
instanceAlias = AZStd::string::format("Instance_%s", entityIdMap.begin()->second.ToString().c_str());
}
else
{
instanceAlias = AZStd::string::format("Instance_%s", AZ::Entity::MakeId().ToString().c_str());
}
// Before processing any further, save off all the known entity IDs from this instance and how they map back to the base
// nested prefab that they've come from (i.e. this one). As we proceed up the chain of nesting, this will build out a
// hierarchical list of owning instances for each entity that we can trace upwards to know where to add the entity into
// our nested prefab instance.
UpdateSliceEntityInstanceMappings(instance.GetEntityIdToBaseMap(), instanceAlias);
// Create a new unmodified prefab Instance for the nested slice instance. // Create a new unmodified prefab Instance for the nested slice instance.
auto nestedInstance = AZStd::make_unique<AzToolsFramework::Prefab::Instance>(); auto nestedInstance = AZStd::make_unique<AzToolsFramework::Prefab::Instance>();
@ -619,6 +659,10 @@ namespace AZ
SetParentEntity(containerEntity->get(), topLevelInstance->GetContainerEntityId(), onlySetIfInvalid); SetParentEntity(containerEntity->get(), topLevelInstance->GetContainerEntityId(), onlySetIfInvalid);
} }
// After doing all of the above, run through entity references in any of the patched entities, and fix up the entity IDs to
// match the new ones in our prefabs.
RemapIdReferences(m_aliasIdMapper, topLevelInstance, nestedInstance.get(), instantiated, dependentSlice->GetSerializeContext());
// Add the nested instance itself to the top-level prefab. To do this, we need to add it to our top-level instance, // Add the nested instance itself to the top-level prefab. To do this, we need to add it to our top-level instance,
// create a patch out of it, and patch the top-level prefab template. // create a patch out of it, and patch the top-level prefab template.
@ -750,17 +794,6 @@ namespace AZ
AZ_Error("Convert-Slice", disconnected, "Asset Processor failed to disconnect successfully."); AZ_Error("Convert-Slice", disconnected, "Asset Processor failed to disconnect successfully.");
} }
void SliceConverter::ClearSliceAssetReferences(AZ::Entity* rootEntity)
{
SliceComponent* sliceComponent = AZ::EntityUtils::FindFirstDerivedComponent<SliceComponent>(rootEntity);
// Make a copy of the slice list and remove all of them from the loaded component.
AZ::SliceComponent::SliceList slices = sliceComponent->GetSlices();
for (auto& slice : slices)
{
sliceComponent->RemoveSlice(&slice);
}
}
void SliceConverter::UpdateSliceEntityInstanceMappings( void SliceConverter::UpdateSliceEntityInstanceMappings(
const AZ::SliceComponent::EntityIdToEntityIdMap& sliceEntityIdMap, const AZStd::string& currentInstanceAlias) const AZ::SliceComponent::EntityIdToEntityIdMap& sliceEntityIdMap, const AZStd::string& currentInstanceAlias)
{ {
@ -789,9 +822,108 @@ namespace AZ
AZ_Assert(oldId == newId, "The same entity instance ID has unexpectedly appeared twice in the same nested prefab."); AZ_Assert(oldId == newId, "The same entity instance ID has unexpectedly appeared twice in the same nested prefab.");
} }
} }
else
{
AZ_Warning("Convert-Slice", false, " Couldn't find an entity ID conversion for %s.", oldId.ToString().c_str());
}
} }
} }
void SliceConverter::RemapIdReferences(
const AZStd::unordered_map<AZ::EntityId, SliceEntityMappingInfo>& idMapper,
AzToolsFramework::Prefab::Instance* topLevelInstance,
AzToolsFramework::Prefab::Instance* nestedInstance,
SliceComponent::InstantiatedContainer* instantiatedEntities,
SerializeContext* context)
{
// Given a set of instantiated entities, run through all of them, look for entity references, and replace the entity IDs with
// new ones that match up with our prefabs.
IdUtils::Remapper<EntityId>::ReplaceIdsAndIdRefs(
instantiatedEntities,
[idMapper, &topLevelInstance, &nestedInstance](
const EntityId& sourceId, bool isEntityId, [[maybe_unused]] const AZStd::function<EntityId()>& idGenerator) -> EntityId
{
EntityId newId = sourceId;
// Only convert valid entity references. Actual entity IDs have already been taken care of elsewhere, so ignore them.
if (!isEntityId && sourceId.IsValid())
{
auto entityEntry = idMapper.find(sourceId);
// Since we've already remapped transform hierarchies to include container entities, it's possible that our entity
// reference is pointing to a container, which means it won't be in our slice mapping table. In that case, just
// return it as-is.
if (entityEntry == idMapper.end())
{
return sourceId;
}
// We've got a slice->prefab mapping entry, so now we need to use it.
auto& mappingStruct = entityEntry->second;
if (mappingStruct.m_nestedInstanceAliases.empty())
{
// If we don't have a chain of nested instance aliases, then this entity reference is either within the
// current nested instance or it's pointing to an entity in the top-level instance. We'll try them both
// to look for a match.
EntityId prefabId = nestedInstance->GetEntityId(mappingStruct.m_entityAlias);
if (!prefabId.IsValid())
{
prefabId = topLevelInstance->GetEntityId(mappingStruct.m_entityAlias);
}
if (prefabId.IsValid())
{
newId = prefabId;
}
else
{
AZ_Error("Convert-Slice", false, " Couldn't find source ID %s", sourceId.ToString().c_str());
}
}
else
{
// We *do* have a chain of nested instance aliases. This chain could either be relative to the nested instance
// or the top-level instance. We can tell which one it is by which one can find the first nested instance
// alias.
AzToolsFramework::Prefab::Instance* entityInstance = nestedInstance;
auto it = mappingStruct.m_nestedInstanceAliases.rbegin();
if (!entityInstance->FindNestedInstance(*it).has_value())
{
entityInstance = topLevelInstance;
}
// Now that we've got a starting point, iterate through the chain of nested instance aliases to find the
// correct instance to get the entity ID for. We have to go from slice IDs -> entity aliases -> entity IDs
// because prefab instance creation can change some of our entity IDs along the way.
for (; it != mappingStruct.m_nestedInstanceAliases.rend(); it++)
{
auto foundInstance = entityInstance->FindNestedInstance(*it);
if (foundInstance.has_value())
{
entityInstance = &(foundInstance->get());
}
else
{
AZ_Assert(false, "Couldn't find nested instance %s", it->c_str());
}
}
EntityId prefabId = entityInstance->GetEntityId(mappingStruct.m_entityAlias);
if (prefabId.IsValid())
{
newId = prefabId;
}
}
}
return newId;
},
context);
}
} // namespace SerializeContextTools } // namespace SerializeContextTools
} // namespace AZ } // namespace AZ

@ -42,6 +42,28 @@ namespace AZ
bool ConvertSliceFiles(Application& application); bool ConvertSliceFiles(Application& application);
private: private:
// When converting slice entities, especially for nested slices, we need to keep track of the original
// entity ID, the entity alias it uses in the prefab, and which template and nested instance path it maps to.
// As we encounter each instanced entity ID, we can look it up in this structure and use this to determine how to properly
// add it to the correct place in the hierarchy.
struct SliceEntityMappingInfo
{
SliceEntityMappingInfo(
AzToolsFramework::Prefab::TemplateId templateId,
AzToolsFramework::Prefab::EntityAlias entityAlias,
bool isMetadataEntity = false)
: m_templateId(templateId)
, m_entityAlias(entityAlias)
, m_isMetadataEntity(isMetadataEntity)
{
}
AzToolsFramework::Prefab::TemplateId m_templateId;
AzToolsFramework::Prefab::EntityAlias m_entityAlias;
AZStd::vector<AzToolsFramework::Prefab::InstanceAlias> m_nestedInstanceAliases;
bool m_isMetadataEntity{ false };
};
bool ConnectToAssetProcessor(); bool ConnectToAssetProcessor();
void DisconnectFromAssetProcessor(); void DisconnectFromAssetProcessor();
@ -58,27 +80,17 @@ namespace AZ
void SetParentEntity(const AZ::Entity& entity, const AZ::EntityId& parentId, bool onlySetIfInvalid); void SetParentEntity(const AZ::Entity& entity, const AZ::EntityId& parentId, bool onlySetIfInvalid);
void PrintPrefab(AzToolsFramework::Prefab::TemplateId templateId); void PrintPrefab(AzToolsFramework::Prefab::TemplateId templateId);
bool SavePrefab(AZ::IO::PathView outputPath, AzToolsFramework::Prefab::TemplateId templateId); bool SavePrefab(AZ::IO::PathView outputPath, AzToolsFramework::Prefab::TemplateId templateId);
void ClearSliceAssetReferences(AZ::Entity* rootEntity);
void UpdateSliceEntityInstanceMappings( void UpdateSliceEntityInstanceMappings(
const AZ::SliceComponent::EntityIdToEntityIdMap& sliceEntityIdMap, const AZ::SliceComponent::EntityIdToEntityIdMap& sliceEntityIdMap,
const AZStd::string& currentInstanceAlias); const AZStd::string& currentInstanceAlias);
AZStd::string GetInstanceAlias(const AZ::SliceComponent::SliceInstance& instance);
// When converting slice entities, especially for nested slices, we need to keep track of the original void RemapIdReferences(
// entity ID, the entity alias it uses in the prefab, and which template and nested instance path it maps to. const AZStd::unordered_map<AZ::EntityId, SliceEntityMappingInfo>& idMapper,
// As we encounter each instanced entity ID, we can look it up in this structure and use this to determine how to properly AzToolsFramework::Prefab::Instance* topLevelInstance,
// add it to the correct place in the hierarchy. AzToolsFramework::Prefab::Instance* nestedInstance,
struct SliceEntityMappingInfo SliceComponent::InstantiatedContainer* instantiatedEntities,
{ SerializeContext* context);
SliceEntityMappingInfo(AzToolsFramework::Prefab::TemplateId templateId, AzToolsFramework::Prefab::EntityAlias entityAlias)
: m_templateId(templateId)
, m_entityAlias(entityAlias)
{
}
AzToolsFramework::Prefab::TemplateId m_templateId;
AzToolsFramework::Prefab::EntityAlias m_entityAlias;
AZStd::vector<AzToolsFramework::Prefab::InstanceAlias> m_nestedInstanceAliases;
};
// Track all of the entity IDs created and associate them with enough conversion information to know how to place the // Track all of the entity IDs created and associate them with enough conversion information to know how to place the
// entities in the correct place in the prefab hierarchy and fix up parent entity ID mappings to work with the nested // entities in the correct place in the prefab hierarchy and fix up parent entity ID mappings to work with the nested

@ -122,29 +122,22 @@ namespace AWSMetrics
//! @return Outcome of the operation. //! @return Outcome of the operation.
AZ::Outcome<void, AZStd::string> SendMetricsToFile(AZStd::shared_ptr<MetricsQueue> metricsQueue); AZ::Outcome<void, AZStd::string> SendMetricsToFile(AZStd::shared_ptr<MetricsQueue> metricsQueue);
//! Check whether the consumer should flush the metrics queue.
//! @return whether the limit is hit.
bool ShouldSendMetrics();
//! Push metrics events to the front of the queue for retry. //! Push metrics events to the front of the queue for retry.
//! @param metricsEventsForRetry Metrics events for retry. //! @param metricsEventsForRetry Metrics events for retry.
void PushMetricsForRetry(MetricsQueue& metricsEventsForRetry); void PushMetricsForRetry(MetricsQueue& metricsEventsForRetry);
void SubmitLocalMetricsAsync(); void SubmitLocalMetricsAsync();
//////////////////////////////////////////// AZStd::mutex m_metricsMutex; //!< Mutex to protect the metrics queue
// These data are protected by m_metricsMutex. MetricsQueue m_metricsQueue; //!< Queue fo buffering the metrics events
AZStd::mutex m_metricsMutex;
AZStd::chrono::system_clock::time_point m_lastSendMetricsTime;
MetricsQueue m_metricsQueue;
////////////////////////////////////////////
AZStd::mutex m_metricsFileMutex; //!< Local metrics file is protected by m_metricsFileMutex AZStd::mutex m_metricsFileMutex; //!< Mutex to protect the local metrics file
AZStd::atomic<int> m_sendMetricsId;//!< Request ID for sending metrics AZStd::atomic<int> m_sendMetricsId;//!< Request ID for sending metrics
AZStd::thread m_consumerThread; //!< Thread to monitor and consume the metrics queue AZStd::thread m_monitorThread; //!< Thread to monitor and consume the metrics queue
AZStd::atomic<bool> m_consumerTerminated; AZStd::atomic<bool> m_monitorTerminated;
AZStd::binary_semaphore m_waitEvent;
// Client Configurations. // Client Configurations.
AZStd::unique_ptr<ClientConfiguration> m_clientConfiguration; AZStd::unique_ptr<ClientConfiguration> m_clientConfiguration;

@ -29,7 +29,7 @@ namespace AWSMetrics
MetricsManager::MetricsManager() MetricsManager::MetricsManager()
: m_clientConfiguration(AZStd::make_unique<ClientConfiguration>()) : m_clientConfiguration(AZStd::make_unique<ClientConfiguration>())
, m_clientIdProvider(IdentityProvider::CreateIdentityProvider()) , m_clientIdProvider(IdentityProvider::CreateIdentityProvider())
, m_consumerTerminated(true) , m_monitorTerminated(true)
, m_sendMetricsId(0) , m_sendMetricsId(0)
{ {
} }
@ -53,31 +53,27 @@ namespace AWSMetrics
void MetricsManager::StartMetrics() void MetricsManager::StartMetrics()
{ {
if (!m_consumerTerminated) if (!m_monitorTerminated)
{ {
// The background thread has been started. // The background thread has been started.
return; return;
} }
m_monitorTerminated = false;
m_consumerTerminated = false;
AZStd::lock_guard<AZStd::mutex> lock(m_metricsMutex);
m_lastSendMetricsTime = AZStd::chrono::system_clock::now();
// Start a separate thread to monitor and consume the metrics queue. // Start a separate thread to monitor and consume the metrics queue.
// Avoid using the job system since the worker is long-running over multiple frames // Avoid using the job system since the worker is long-running over multiple frames
m_consumerThread = AZStd::thread(AZStd::bind(&MetricsManager::MonitorMetricsQueue, this)); m_monitorThread = AZStd::thread(AZStd::bind(&MetricsManager::MonitorMetricsQueue, this));
} }
void MetricsManager::MonitorMetricsQueue() void MetricsManager::MonitorMetricsQueue()
{ {
while (!m_consumerTerminated) // Continue to loop until the monitor is terminated.
while (!m_monitorTerminated)
{ {
if (ShouldSendMetrics()) // The thread will wake up either when the metrics event queue is full (try_acquire_for call returns true),
{ // or the flush period limit is hit (try_acquire_for call returns false).
// Flush the metrics queue when the accumulated metrics size or time period hits the limit m_waitEvent.try_acquire_for(AZStd::chrono::seconds(m_clientConfiguration->GetQueueFlushPeriodInSeconds()));
FlushMetricsAsync(); FlushMetricsAsync();
}
} }
} }
@ -114,6 +110,12 @@ namespace AWSMetrics
AZStd::lock_guard<AZStd::mutex> lock(m_metricsMutex); AZStd::lock_guard<AZStd::mutex> lock(m_metricsMutex);
m_metricsQueue.AddMetrics(metricsEvent); m_metricsQueue.AddMetrics(metricsEvent);
if (m_metricsQueue.GetSizeInBytes() >= m_clientConfiguration->GetMaxQueueSizeInBytes())
{
// Flush the metrics queue when the accumulated metrics size hits the limit
m_waitEvent.release();
}
return true; return true;
} }
@ -348,9 +350,6 @@ namespace AWSMetrics
void MetricsManager::FlushMetricsAsync() void MetricsManager::FlushMetricsAsync()
{ {
AZStd::lock_guard<AZStd::mutex> lock(m_metricsMutex); AZStd::lock_guard<AZStd::mutex> lock(m_metricsMutex);
m_lastSendMetricsTime = AZStd::chrono::system_clock::now();
if (m_metricsQueue.GetNumMetrics() == 0) if (m_metricsQueue.GetNumMetrics() == 0)
{ {
return; return;
@ -363,34 +362,20 @@ namespace AWSMetrics
SendMetricsAsync(metricsToFlush); SendMetricsAsync(metricsToFlush);
} }
bool MetricsManager::ShouldSendMetrics()
{
AZStd::lock_guard<AZStd::mutex> lock(m_metricsMutex);
auto secondsSinceLastFlush = AZStd::chrono::duration_cast<AZStd::chrono::seconds>(AZStd::chrono::system_clock::now() - m_lastSendMetricsTime);
if (secondsSinceLastFlush >= AZStd::chrono::seconds(m_clientConfiguration->GetQueueFlushPeriodInSeconds()) ||
m_metricsQueue.GetSizeInBytes() >= m_clientConfiguration->GetMaxQueueSizeInBytes())
{
return true;
}
return false;
}
void MetricsManager::ShutdownMetrics() void MetricsManager::ShutdownMetrics()
{ {
if (m_consumerTerminated) if (m_monitorTerminated)
{ {
return; return;
} }
// Terminate the consumer thread // Terminate the monitor thread
m_consumerTerminated = true; m_monitorTerminated = true;
FlushMetricsAsync(); m_waitEvent.release();
if (m_consumerThread.joinable()) if (m_monitorThread.joinable())
{ {
m_consumerThread.join(); m_monitorThread.join();
} }
} }
@ -449,6 +434,12 @@ namespace AWSMetrics
{ {
AZStd::lock_guard<AZStd::mutex> lock(m_metricsMutex); AZStd::lock_guard<AZStd::mutex> lock(m_metricsMutex);
m_metricsQueue.AddMetrics(offlineRecords[index]); m_metricsQueue.AddMetrics(offlineRecords[index]);
if (m_metricsQueue.GetSizeInBytes() >= m_clientConfiguration->GetMaxQueueSizeInBytes())
{
// Flush the metrics queue when the accumulated metrics size hits the limit
m_waitEvent.release();
}
} }
// Remove the local metrics file after reading all its content. // Remove the local metrics file after reading all its content.

@ -355,6 +355,9 @@ namespace AWSMetrics
TEST_F(MetricsManagerTest, FlushMetrics_NonEmptyQueue_Success) TEST_F(MetricsManagerTest, FlushMetrics_NonEmptyQueue_Success)
{ {
ResetClientConfig(true, (double)TestMetricsEventSizeInBytes * (MaxNumMetricsEvents + 1) / MbToBytes,
DefaultFlushPeriodInSeconds, 1);
for (int index = 0; index < MaxNumMetricsEvents; ++index) for (int index = 0; index < MaxNumMetricsEvents; ++index)
{ {
AZStd::vector<MetricsAttribute> metricsAttributes; AZStd::vector<MetricsAttribute> metricsAttributes;
@ -377,7 +380,7 @@ namespace AWSMetrics
TEST_F(MetricsManagerTest, ResetOfflineRecordingStatus_ResubmitLocalMetrics_Success) TEST_F(MetricsManagerTest, ResetOfflineRecordingStatus_ResubmitLocalMetrics_Success)
{ {
// Disable offline recording in the config file. // Disable offline recording in the config file.
ResetClientConfig(false, 0.0, 0, 0); ResetClientConfig(false, (double)TestMetricsEventSizeInBytes * 2 / MbToBytes, 0, 0);
// Enable offline recording after initialize the metric manager. // Enable offline recording after initialize the metric manager.
m_metricsManager->UpdateOfflineRecordingStatus(true); m_metricsManager->UpdateOfflineRecordingStatus(true);

@ -8,7 +8,7 @@
"$type": "DX12::PlatformLimitsDescriptor", "$type": "DX12::PlatformLimitsDescriptor",
"m_descriptorHeapLimits": { "m_descriptorHeapLimits": {
"DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV": [16384, 262144], "DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV": [1000000, 1000000],
"DESCRIPTOR_HEAP_TYPE_SAMPLER": [2048, 2048], "DESCRIPTOR_HEAP_TYPE_SAMPLER": [2048, 2048],
"DESCRIPTOR_HEAP_TYPE_RTV": [2048, 0], "DESCRIPTOR_HEAP_TYPE_RTV": [2048, 0],
"DESCRIPTOR_HEAP_TYPE_DSV": [2048, 0] "DESCRIPTOR_HEAP_TYPE_DSV": [2048, 0]

@ -24,9 +24,9 @@
}, },
"ImageDescriptor": { "ImageDescriptor": {
"Format": "R16G16B16A16_FLOAT", "Format": "R16G16B16A16_FLOAT",
"MipLevels": "8",
"SharedQueueMask": "Graphics" "SharedQueueMask": "Graphics"
} },
"GenerateFullMipChain": true
} }
], ],
"Connections": [ "Connections": [

@ -5,7 +5,7 @@
"ClassData": { "ClassData": {
"PassTemplate": { "PassTemplate": {
"Name": "ReflectionScreenSpaceCompositePassTemplate", "Name": "ReflectionScreenSpaceCompositePassTemplate",
"PassClass": "FullScreenTriangle", "PassClass": "ReflectionScreenSpaceCompositePass",
"Slots": [ "Slots": [
{ {
"Name": "TraceInput", "Name": "TraceInput",

@ -15,9 +15,6 @@
#include <Atom/Features/PBR/Lights/LightTypesCommon.azsli> #include <Atom/Features/PBR/Lights/LightTypesCommon.azsli>
#include <Atom/Features/Shadow/ProjectedShadow.azsli> #include <Atom/Features/Shadow/ProjectedShadow.azsli>
// The order should match m_pointShadowTransforms in PointLightFeatureProcessor.h/.cpp
static const float3 PointLightShadowCubemapDirections[6] = {float3(-1,0,0), float3(1,0,0), float3(0,-1,0), float3(0,1,0), float3(0,0,-1), float3(0,0,1)};
int GetPointLightShadowCubemapFace(const float3 targetPos, const float3 lightPos) int GetPointLightShadowCubemapFace(const float3 targetPos, const float3 lightPos)
{ {
const float3 toPoint = targetPos - lightPos; const float3 toPoint = targetPos - lightPos;
@ -83,12 +80,13 @@ void ApplyPointLight(ViewSrg::PointLight light, Surface surface, inout LightingD
{ {
const int shadowCubemapFace = GetPointLightShadowCubemapFace(surface.position, light.m_position); const int shadowCubemapFace = GetPointLightShadowCubemapFace(surface.position, light.m_position);
const int shadowIndex = UnpackPointLightShadowIndex(light, shadowCubemapFace); const int shadowIndex = UnpackPointLightShadowIndex(light, shadowCubemapFace);
const float3 lightDir = normalize(light.m_position - surface.position);
litRatio *= ProjectedShadow::GetVisibility( litRatio *= ProjectedShadow::GetVisibility(
shadowIndex, shadowIndex,
light.m_position, light.m_position,
surface.position, surface.position,
PointLightShadowCubemapDirections[shadowCubemapFace], lightDir,
surface.normal); surface.normal);
// Use backShadowRatio to carry thickness from shadow map for thick mode // Use backShadowRatio to carry thickness from shadow map for thick mode

@ -37,6 +37,9 @@ ShaderResourceGroup PassSrg : SRG_PerPass
AddressV = Clamp; AddressV = Clamp;
AddressW = Clamp; AddressW = Clamp;
}; };
// the max roughness mip level for sampling the previous frame image
uint m_maxMipLevel;
} }
#include <Atom/RPI/ShaderResourceGroups/DefaultDrawSrg.azsli> #include <Atom/RPI/ShaderResourceGroups/DefaultDrawSrg.azsli>
@ -69,10 +72,6 @@ PSOutput MainPS(VSOutput IN, in uint sampleIndex : SV_SampleIndex)
float4 positionWS = mul(ViewSrg::m_viewProjectionInverseMatrix, projectedPos); float4 positionWS = mul(ViewSrg::m_viewProjectionInverseMatrix, projectedPos);
positionWS /= positionWS.w; positionWS /= positionWS.w;
//float4 positionVS = mul(ViewSrg::m_projectionMatrixInverse, projectedPos);
//positionVS /= positionVS.w;
//float4 positionWS = mul(ViewSrg::m_viewMatrixInverse, positionVS);
// compute ray from camera to surface position // compute ray from camera to surface position
float3 cameraToPositionWS = normalize(positionWS.xyz - ViewSrg::m_worldPosition); float3 cameraToPositionWS = normalize(positionWS.xyz - ViewSrg::m_worldPosition);
@ -103,8 +102,7 @@ PSOutput MainPS(VSOutput IN, in uint sampleIndex : SV_SampleIndex)
// compute the roughness mip to use in the previous frame image // compute the roughness mip to use in the previous frame image
// remap the roughness mip into a lower range to more closely match the material roughness values // remap the roughness mip into a lower range to more closely match the material roughness values
const float MaxRoughness = 0.5f; const float MaxRoughness = 0.5f;
const float MaxRoughnessMip = 7; float mip = saturate(roughness / MaxRoughness) * PassSrg::m_maxMipLevel;
float mip = saturate(roughness / MaxRoughness) * MaxRoughnessMip;
// sample reflection value from the roughness mip // sample reflection value from the roughness mip
float4 reflectionColor = float4(PassSrg::m_previousFrame.SampleLevel(PassSrg::LinearSampler, tracePrevUV, mip).rgb, 1.0f); float4 reflectionColor = float4(PassSrg::m_previousFrame.SampleLevel(PassSrg::LinearSampler, tracePrevUV, mip).rgb, 1.0f);

@ -23,7 +23,9 @@ namespace AZ
{ {
Low, Low,
Medium, Medium,
High High,
Count
}; };
//! This class provides general features and configuration for the diffuse global illumination environment, //! This class provides general features and configuration for the diffuse global illumination environment,

@ -103,6 +103,7 @@
#include <DiffuseGlobalIllumination/DiffuseGlobalIlluminationFeatureProcessor.h> #include <DiffuseGlobalIllumination/DiffuseGlobalIlluminationFeatureProcessor.h>
#include <ReflectionScreenSpace/ReflectionScreenSpaceBlurPass.h> #include <ReflectionScreenSpace/ReflectionScreenSpaceBlurPass.h>
#include <ReflectionScreenSpace/ReflectionScreenSpaceBlurChildPass.h> #include <ReflectionScreenSpace/ReflectionScreenSpaceBlurChildPass.h>
#include <ReflectionScreenSpace/ReflectionScreenSpaceCompositePass.h>
#include <ReflectionScreenSpace/ReflectionCopyFrameBufferPass.h> #include <ReflectionScreenSpace/ReflectionCopyFrameBufferPass.h>
#include <OcclusionCullingPlane/OcclusionCullingPlaneFeatureProcessor.h> #include <OcclusionCullingPlane/OcclusionCullingPlaneFeatureProcessor.h>
@ -283,6 +284,7 @@ namespace AZ
// Add Reflection passes // Add Reflection passes
passSystem->AddPassCreator(Name("ReflectionScreenSpaceBlurPass"), &Render::ReflectionScreenSpaceBlurPass::Create); passSystem->AddPassCreator(Name("ReflectionScreenSpaceBlurPass"), &Render::ReflectionScreenSpaceBlurPass::Create);
passSystem->AddPassCreator(Name("ReflectionScreenSpaceBlurChildPass"), &Render::ReflectionScreenSpaceBlurChildPass::Create); passSystem->AddPassCreator(Name("ReflectionScreenSpaceBlurChildPass"), &Render::ReflectionScreenSpaceBlurChildPass::Create);
passSystem->AddPassCreator(Name("ReflectionScreenSpaceCompositePass"), &Render::ReflectionScreenSpaceCompositePass::Create);
passSystem->AddPassCreator(Name("ReflectionCopyFrameBufferPass"), &Render::ReflectionCopyFrameBufferPass::Create); passSystem->AddPassCreator(Name("ReflectionCopyFrameBufferPass"), &Render::ReflectionCopyFrameBufferPass::Create);
// Add RayTracing pas // Add RayTracing pas

@ -20,6 +20,8 @@
#include <Atom/RPI.Public/AuxGeom/AuxGeomDraw.h> #include <Atom/RPI.Public/AuxGeom/AuxGeomDraw.h>
#include <Atom/RPI.Public/ColorManagement/TransformColor.h> #include <Atom/RPI.Public/ColorManagement/TransformColor.h>
#include <Atom/RPI.Public/Pass/PassSystemInterface.h> #include <Atom/RPI.Public/Pass/PassSystemInterface.h>
#include <Atom/RPI.Public/Pass/PassFilter.h>
#include <Atom/RPI.Public/Pass/Specific/EnvironmentCubeMapPass.h>
#include <Atom/RPI.Public/RenderPipeline.h> #include <Atom/RPI.Public/RenderPipeline.h>
#include <Atom/RPI.Public/Scene.h> #include <Atom/RPI.Public/Scene.h>
#include <Atom/RPI.Public/View.h> #include <Atom/RPI.Public/View.h>
@ -1070,7 +1072,18 @@ namespace AZ
segment.m_pipelineViewTag = viewTag; segment.m_pipelineViewTag = viewTag;
if (!segment.m_view || segment.m_view->GetName() != viewName) if (!segment.m_view || segment.m_view->GetName() != viewName)
{ {
segment.m_view = RPI::View::CreateView(viewName, RPI::View::UsageShadow); RPI::View::UsageFlags usageFlags = RPI::View::UsageShadow;
// if the shadow is rendering in an EnvironmentCubeMapPass it also needs to be a ReflectiveCubeMap view,
// to filter out shadows from objects that are excluded from the cubemap
RPI::PassClassFilter<RPI::EnvironmentCubeMapPass> passFilter;
AZStd::vector<AZ::RPI::Pass*> cubeMapPasses = AZ::RPI::PassSystemInterface::Get()->FindPasses(passFilter);
if (!cubeMapPasses.empty())
{
usageFlags |= RPI::View::UsageReflectiveCubeMap;
}
segment.m_view = RPI::View::CreateView(viewName, usageFlags);
} }
} }
} }

@ -43,6 +43,12 @@ namespace AZ
void DiffuseGlobalIlluminationFeatureProcessor::SetQualityLevel(DiffuseGlobalIlluminationQualityLevel qualityLevel) void DiffuseGlobalIlluminationFeatureProcessor::SetQualityLevel(DiffuseGlobalIlluminationQualityLevel qualityLevel)
{ {
if (qualityLevel >= DiffuseGlobalIlluminationQualityLevel::Count)
{
AZ_Assert(false, "SetQualityLevel called with invalid quality level [%d]", qualityLevel);
return;
}
m_qualityLevel = qualityLevel; m_qualityLevel = qualityLevel;
UpdatePasses(); UpdatePasses();

@ -37,6 +37,9 @@ namespace AZ
//! to store the previous frame image //! to store the previous frame image
Data::Instance<RPI::AttachmentImage>& GetFrameBufferImageAttachment() { return m_frameBufferImageAttachment; } Data::Instance<RPI::AttachmentImage>& GetFrameBufferImageAttachment() { return m_frameBufferImageAttachment; }
//! Returns the number of mip levels in the blur
uint32_t GetNumBlurMips() const { return m_numBlurMips; }
private: private:
explicit ReflectionScreenSpaceBlurPass(const RPI::PassDescriptor& descriptor); explicit ReflectionScreenSpaceBlurPass(const RPI::PassDescriptor& descriptor);

@ -0,0 +1,58 @@
/*
* 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 "ReflectionScreenSpaceCompositePass.h"
#include "ReflectionScreenSpaceBlurPass.h"
#include <Atom/RPI.Public/Pass/PassSystemInterface.h>
#include <Atom/RPI.Public/Pass/PassFilter.h>
namespace AZ
{
namespace Render
{
RPI::Ptr<ReflectionScreenSpaceCompositePass> ReflectionScreenSpaceCompositePass::Create(const RPI::PassDescriptor& descriptor)
{
RPI::Ptr<ReflectionScreenSpaceCompositePass> pass = aznew ReflectionScreenSpaceCompositePass(descriptor);
return AZStd::move(pass);
}
ReflectionScreenSpaceCompositePass::ReflectionScreenSpaceCompositePass(const RPI::PassDescriptor& descriptor)
: RPI::FullscreenTrianglePass(descriptor)
{
}
void ReflectionScreenSpaceCompositePass::CompileResources([[maybe_unused]] const RHI::FrameGraphCompileContext& context)
{
if (!m_shaderResourceGroup)
{
return;
}
RPI::PassHierarchyFilter passFilter(AZ::Name("ReflectionScreenSpaceBlurPass"));
const AZStd::vector<RPI::Pass*>& passes = RPI::PassSystemInterface::Get()->FindPasses(passFilter);
if (!passes.empty())
{
Render::ReflectionScreenSpaceBlurPass* blurPass = azrtti_cast<ReflectionScreenSpaceBlurPass*>(passes.front());
// compute the max mip level based on the available mips in the previous frame image, and capping it
// to stay within a range that has reasonable data
const uint32_t MaxNumRoughnessMips = 8;
uint32_t maxMipLevel = AZStd::min(MaxNumRoughnessMips, blurPass->GetNumBlurMips()) - 1;
auto constantIndex = m_shaderResourceGroup->FindShaderInputConstantIndex(Name("m_maxMipLevel"));
m_shaderResourceGroup->SetConstant(constantIndex, maxMipLevel);
}
FullscreenTrianglePass::CompileResources(context);
}
} // namespace RPI
} // namespace AZ

@ -0,0 +1,43 @@
/*
* 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 <Atom/RPI.Public/Pass/Pass.h>
#include <Atom/RPI.Public/Pass/FullscreenTrianglePass.h>
#include <Atom/RPI.Public/Shader/ShaderResourceGroup.h>
#include <Atom/RPI.Public/Shader/Shader.h>
namespace AZ
{
namespace Render
{
//! This pass composites the screenspace reflection trace onto the reflection buffer.
class ReflectionScreenSpaceCompositePass
: public RPI::FullscreenTrianglePass
{
AZ_RPI_PASS(ReflectionScreenSpaceCompositePass);
public:
AZ_RTTI(Render::ReflectionScreenSpaceCompositePass, "{88739CC9-C3F1-413A-A527-9916C697D93A}", FullscreenTrianglePass);
AZ_CLASS_ALLOCATOR(Render::ReflectionScreenSpaceCompositePass, SystemAllocator, 0);
//! Creates a new pass without a PassTemplate
static RPI::Ptr<ReflectionScreenSpaceCompositePass> Create(const RPI::PassDescriptor& descriptor);
private:
explicit ReflectionScreenSpaceCompositePass(const RPI::PassDescriptor& descriptor);
// Pass Overrides...
void CompileResources(const RHI::FrameGraphCompileContext& context) override;
};
} // namespace RPI
} // namespace AZ

@ -629,6 +629,11 @@ namespace AZ
Data::Asset<RPI::ModelLodAsset> lodAsset; Data::Asset<RPI::ModelLodAsset> lodAsset;
modelLodCreator.End(lodAsset); modelLodCreator.End(lodAsset);
if (!lodAsset.IsReady())
{
// [GFX TODO] During mesh reload the modelLodCreator could report errors and result in the lodAsset not ready.
return nullptr;
}
modelCreator.AddLodAsset(AZStd::move(lodAsset)); modelCreator.AddLodAsset(AZStd::move(lodAsset));
lodIndex++; lodIndex++;

@ -267,6 +267,8 @@ set(FILES
Source/ReflectionScreenSpace/ReflectionScreenSpaceBlurPass.h Source/ReflectionScreenSpace/ReflectionScreenSpaceBlurPass.h
Source/ReflectionScreenSpace/ReflectionScreenSpaceBlurChildPass.cpp Source/ReflectionScreenSpace/ReflectionScreenSpaceBlurChildPass.cpp
Source/ReflectionScreenSpace/ReflectionScreenSpaceBlurChildPass.h Source/ReflectionScreenSpace/ReflectionScreenSpaceBlurChildPass.h
Source/ReflectionScreenSpace/ReflectionScreenSpaceCompositePass.cpp
Source/ReflectionScreenSpace/ReflectionScreenSpaceCompositePass.h
Source/ReflectionScreenSpace/ReflectionCopyFrameBufferPass.cpp Source/ReflectionScreenSpace/ReflectionCopyFrameBufferPass.cpp
Source/ReflectionScreenSpace/ReflectionCopyFrameBufferPass.h Source/ReflectionScreenSpace/ReflectionCopyFrameBufferPass.h
Source/ScreenSpace/DeferredFogSettings.cpp Source/ScreenSpace/DeferredFogSettings.cpp

@ -400,15 +400,13 @@ namespace AZ
id<MTLResource> mtlconstantBufferResource = m_constantBuffer.GetGpuAddress<id<MTLResource>>(); id<MTLResource> mtlconstantBufferResource = m_constantBuffer.GetGpuAddress<id<MTLResource>>();
if(RHI::CheckBitsAny(srgResourcesVisInfo.m_constantDataStageMask, RHI::ShaderStageMask::Compute)) if(RHI::CheckBitsAny(srgResourcesVisInfo.m_constantDataStageMask, RHI::ShaderStageMask::Compute))
{ {
uint16_t arrayIndex = resourcesToMakeResidentCompute[MTLResourceUsageRead].m_resourceArrayLen++; resourcesToMakeResidentCompute[MTLResourceUsageRead].emplace(mtlconstantBufferResource);
resourcesToMakeResidentCompute[MTLResourceUsageRead].m_resourceArray[arrayIndex] = mtlconstantBufferResource;
} }
else else
{ {
MTLRenderStages mtlRenderStages = GetRenderStages(srgResourcesVisInfo.m_constantDataStageMask); MTLRenderStages mtlRenderStages = GetRenderStages(srgResourcesVisInfo.m_constantDataStageMask);
AZStd::pair <MTLResourceUsage,MTLRenderStages> key = AZStd::make_pair(MTLResourceUsageRead, mtlRenderStages); AZStd::pair <MTLResourceUsage,MTLRenderStages> key = AZStd::make_pair(MTLResourceUsageRead, mtlRenderStages);
uint16_t arrayIndex = resourcesToMakeResidentGraphics[key].m_resourceArrayLen++; resourcesToMakeResidentGraphics[key].emplace(mtlconstantBufferResource);
resourcesToMakeResidentGraphics[key].m_resourceArray[arrayIndex] = mtlconstantBufferResource;
} }
} }
} }
@ -440,16 +438,18 @@ namespace AZ
//Call UseResource on all resources for Compute stage //Call UseResource on all resources for Compute stage
for (const auto& key : resourcesToMakeResidentCompute) for (const auto& key : resourcesToMakeResidentCompute)
{ {
[static_cast<id<MTLComputeCommandEncoder>>(commandEncoder) useResources: key.second.m_resourceArray.data() AZStd::vector<id <MTLResource>> resourcesToProcessVec(key.second.begin(), key.second.end());
count: key.second.m_resourceArrayLen [static_cast<id<MTLComputeCommandEncoder>>(commandEncoder) useResources: &resourcesToProcessVec[0]
count: resourcesToProcessVec.size()
usage: key.first]; usage: key.first];
} }
//Call UseResource on all resources for Vertex and Fragment stages //Call UseResource on all resources for Vertex and Fragment stages
for (const auto& key : resourcesToMakeResidentGraphics) for (const auto& key : resourcesToMakeResidentGraphics)
{ {
[static_cast<id<MTLRenderCommandEncoder>>(commandEncoder) useResources: key.second.m_resourceArray.data() AZStd::vector<id <MTLResource>> resourcesToProcessVec(key.second.begin(), key.second.end());
count: key.second.m_resourceArrayLen [static_cast<id<MTLRenderCommandEncoder>>(commandEncoder) useResources: &resourcesToProcessVec[0]
count: resourcesToProcessVec.size()
usage: key.first.first usage: key.first.first
stages: key.first.second]; stages: key.first.second];
} }
@ -480,9 +480,9 @@ namespace AZ
AZ_Assert(false, "Undefined Resource type"); AZ_Assert(false, "Undefined Resource type");
} }
} }
uint16_t arrayIndex = resourcesToMakeResidentMap[resourceUsage].m_resourceArrayLen++;
id<MTLResource> mtlResourceToBind = resourceBindingData.m_resourcPtr->GetGpuAddress<id<MTLResource>>(); id<MTLResource> mtlResourceToBind = resourceBindingData.m_resourcPtr->GetGpuAddress<id<MTLResource>>();
resourcesToMakeResidentMap[resourceUsage].m_resourceArray[arrayIndex] = mtlResourceToBind; resourcesToMakeResidentMap[resourceUsage].emplace(mtlResourceToBind);
} }
} }
@ -516,9 +516,8 @@ namespace AZ
} }
AZStd::pair <MTLResourceUsage, MTLRenderStages> key = AZStd::make_pair(resourceUsage, mtlRenderStages); AZStd::pair <MTLResourceUsage, MTLRenderStages> key = AZStd::make_pair(resourceUsage, mtlRenderStages);
uint16_t arrayIndex = resourcesToMakeResidentMap[key].m_resourceArrayLen++;
id<MTLResource> mtlResourceToBind = resourceBindingData.m_resourcPtr->GetGpuAddress<id<MTLResource>>(); id<MTLResource> mtlResourceToBind = resourceBindingData.m_resourcPtr->GetGpuAddress<id<MTLResource>>();
resourcesToMakeResidentMap[key].m_resourceArray[arrayIndex] = mtlResourceToBind; resourcesToMakeResidentMap[key].emplace(mtlResourceToBind);
} }
} }
} }

@ -120,15 +120,10 @@ namespace AZ
ResourceBindingsMap m_resourceBindings; ResourceBindingsMap m_resourceBindings;
static const int MaxEntriesInArgTable = 31; static const int MaxEntriesInArgTable = 31;
struct MetalResourceArray //Map to cache all the resources based on the usage as we can batch all the resources for a given usage.
{ using ComputeResourcesToMakeResidentMap = AZStd::unordered_map<MTLResourceUsage, AZStd::unordered_set<id <MTLResource>>>;
AZStd::array<id <MTLResource>, MaxEntriesInArgTable> m_resourceArray; //Map to cache all the resources based on the usage and shader stage as we can batch all the resources for a given usage/shader usage.
uint16_t m_resourceArrayLen = 0; using GraphicsResourcesToMakeResidentMap = AZStd::unordered_map<AZStd::pair<MTLResourceUsage,MTLRenderStages>, AZStd::unordered_set<id <MTLResource>>>;
};
//Map to cache all the resources based on the usage as we can batch all the resources for a given usage
using ComputeResourcesToMakeResidentMap = AZStd::unordered_map<MTLResourceUsage, MetalResourceArray>;
//Map to cache all the resources based on the usage and shader stage as we can batch all the resources for a given usage/shader usage
using GraphicsResourcesToMakeResidentMap = AZStd::unordered_map<AZStd::pair<MTLResourceUsage,MTLRenderStages>, MetalResourceArray>;
void CollectResourcesForCompute(id<MTLCommandEncoder> encoder, void CollectResourcesForCompute(id<MTLCommandEncoder> encoder,
const ResourceBindingsSet& resourceBindingData, const ResourceBindingsSet& resourceBindingData,

@ -85,16 +85,36 @@ namespace AZ
uint64_t AsyncUploadQueue::QueueUpload(const RHI::BufferStreamRequest& uploadRequest) uint64_t AsyncUploadQueue::QueueUpload(const RHI::BufferStreamRequest& uploadRequest)
{ {
uint64_t queueValue = m_uploadFence.Increment(); Buffer& destBuffer = static_cast<Buffer&>(*uploadRequest.m_buffer);
const MemoryView& destMemoryView = destBuffer.GetMemoryView();
MTLStorageMode mtlStorageMode = destBuffer.GetMemoryView().GetStorageMode();
RHI::BufferPool& bufferPool = static_cast<RHI::BufferPool&>(*destBuffer.GetPool());
// No need to use staging buffers since it's host memory.
// We just map, copy and then unmap.
if(mtlStorageMode == MTLStorageModeShared || mtlStorageMode == GetCPUGPUMemoryMode())
{
RHI::BufferMapRequest mapRequest;
mapRequest.m_buffer = uploadRequest.m_buffer;
mapRequest.m_byteCount = uploadRequest.m_byteCount;
mapRequest.m_byteOffset = uploadRequest.m_byteOffset;
RHI::BufferMapResponse mapResponse;
bufferPool.MapBuffer(mapRequest, mapResponse);
::memcpy(mapResponse.m_data, uploadRequest.m_sourceData, uploadRequest.m_byteCount);
bufferPool.UnmapBuffer(*uploadRequest.m_buffer);
if (uploadRequest.m_fenceToSignal)
{
uploadRequest.m_fenceToSignal->SignalOnCpu();
}
return m_uploadFence.GetPendingValue();
}
const MemoryView& memoryView = static_cast<Buffer&>(*uploadRequest.m_buffer).GetMemoryView();
RHI::Ptr<Memory> buffer = memoryView.GetMemory();
Fence* fenceToSignal = nullptr; Fence* fenceToSignal = nullptr;
uint64_t fenceToSignalValue = 0; uint64_t fenceToSignalValue = 0;
size_t byteCount = uploadRequest.m_byteCount; size_t byteCount = uploadRequest.m_byteCount;
size_t byteOffset = memoryView.GetOffset() + uploadRequest.m_byteOffset; size_t byteOffset = destMemoryView.GetOffset() + uploadRequest.m_byteOffset;
uint64_t queueValue = m_uploadFence.Increment();
const uint8_t* sourceData = reinterpret_cast<const uint8_t*>(uploadRequest.m_sourceData); const uint8_t* sourceData = reinterpret_cast<const uint8_t*>(uploadRequest.m_sourceData);
if (uploadRequest.m_fenceToSignal) if (uploadRequest.m_fenceToSignal)
@ -125,11 +145,11 @@ namespace AZ
} }
id<MTLBlitCommandEncoder> blitEncoder = [framePacket->m_mtlCommandBuffer blitCommandEncoder]; id<MTLBlitCommandEncoder> blitEncoder = [framePacket->m_mtlCommandBuffer blitCommandEncoder];
[blitEncoder copyFromBuffer:framePacket->m_stagingResource [blitEncoder copyFromBuffer: framePacket->m_stagingResource
sourceOffset:0 sourceOffset: 0
toBuffer:buffer->GetGpuAddress<id<MTLBuffer>>() toBuffer: destMemoryView.GetGpuAddress<id<MTLBuffer>>()
destinationOffset:byteOffset + pendingByteOffset destinationOffset: byteOffset + pendingByteOffset
size:bytesToCopy]; size: bytesToCopy];
[blitEncoder endEncoding]; [blitEncoder endEncoding];
blitEncoder = nil; blitEncoder = nil;

@ -40,9 +40,8 @@ namespace AZ
buffer->m_pendingResolves++; buffer->m_pendingResolves++;
uploadRequest.m_attachmentBuffer = buffer; uploadRequest.m_attachmentBuffer = buffer;
uploadRequest.m_byteOffset = request.m_byteOffset; uploadRequest.m_byteOffset = buffer->GetMemoryView().GetOffset() + request.m_byteOffset;
uploadRequest.m_stagingBuffer = stagingBuffer; uploadRequest.m_stagingBuffer = stagingBuffer;
uploadRequest.m_byteSize = request.m_byteCount;
return stagingBuffer->GetMemoryView().GetCpuAddress(); return stagingBuffer->GetMemoryView().GetCpuAddress();
} }
@ -64,12 +63,15 @@ namespace AZ
AZ_Assert(stagingBuffer, "Staging Buffer is null."); AZ_Assert(stagingBuffer, "Staging Buffer is null.");
AZ_Assert(destBuffer, "Attachment Buffer is null."); AZ_Assert(destBuffer, "Attachment Buffer is null.");
//Inform the GPU that the CPU has modified the staging buffer.
Platform::SynchronizeBufferOnCPU(stagingBuffer->GetMemoryView().GetGpuAddress<id<MTLBuffer>>(), stagingBuffer->GetMemoryView().GetOffset(), stagingBuffer->GetMemoryView().GetSize());
RHI::CopyBufferDescriptor copyDescriptor; RHI::CopyBufferDescriptor copyDescriptor;
copyDescriptor.m_sourceBuffer = stagingBuffer; copyDescriptor.m_sourceBuffer = stagingBuffer;
copyDescriptor.m_sourceOffset = 0; copyDescriptor.m_sourceOffset = stagingBuffer->GetMemoryView().GetOffset();
copyDescriptor.m_destinationBuffer = destBuffer; copyDescriptor.m_destinationBuffer = destBuffer;
copyDescriptor.m_destinationOffset = static_cast<uint32_t>(packet.m_byteOffset); copyDescriptor.m_destinationOffset = static_cast<uint32_t>(packet.m_byteOffset);
copyDescriptor.m_size = static_cast<uint32_t>(packet.m_byteSize); copyDescriptor.m_size = stagingBuffer->GetMemoryView().GetSize();
commandList.Submit(RHI::CopyItem(copyDescriptor)); commandList.Submit(RHI::CopyItem(copyDescriptor));
device.QueueForRelease(stagingBuffer->GetMemoryView()); device.QueueForRelease(stagingBuffer->GetMemoryView());

@ -54,7 +54,6 @@ namespace AZ
Buffer* m_attachmentBuffer = nullptr; Buffer* m_attachmentBuffer = nullptr;
RHI::Ptr<Buffer> m_stagingBuffer; RHI::Ptr<Buffer> m_stagingBuffer;
size_t m_byteOffset = 0; size_t m_byteOffset = 0;
size_t m_byteSize = 0;
}; };
AZStd::mutex m_uploadPacketsLock; AZStd::mutex m_uploadPacketsLock;

@ -44,7 +44,7 @@ namespace AZ
if (memoryView.IsValid()) if (memoryView.IsValid())
{ {
heapMemoryUsage.m_residentInBytes += m_descriptor.m_pageSizeInBytes; heapMemoryUsage.m_residentInBytes += m_descriptor.m_pageSizeInBytes;
memoryView.SetName("BufferPage"); memoryView.SetName(AZStd::string::format("BufferPage_%s", AZ::Uuid::CreateRandom().ToString<AZStd::string>().c_str()));
} }
else else
{ {

@ -55,7 +55,11 @@ namespace AZ
const auto& image = static_cast<const Image&>(resourceBase); const auto& image = static_cast<const Image&>(resourceBase);
const RHI::ImageViewDescriptor& descriptor = GetDescriptor(); const RHI::ImageViewDescriptor& descriptor = GetDescriptor();
AZ_Assert(image.GetNativeImage() != VK_NULL_HANDLE, "Image has not been initialized."); // this can happen when image has been invalidated/released right before re-compiling the image
if (image.GetNativeImage() == VK_NULL_HANDLE)
{
return RHI::ResultCode::Fail;
}
RHI::Format viewFormat = descriptor.m_overrideFormat; RHI::Format viewFormat = descriptor.m_overrideFormat;
// If an image is not owner of native image, it is a swapchain image. // If an image is not owner of native image, it is a swapchain image.

@ -12691,8 +12691,7 @@ static int glad_vk_find_extensions_vulkan( VkPhysicalDevice physical_device) {
#endif #endif
GLAD_VK_KHR_push_descriptor = glad_vk_has_extension("VK_KHR_push_descriptor", extension_count, extensions); GLAD_VK_KHR_push_descriptor = glad_vk_has_extension("VK_KHR_push_descriptor", extension_count, extensions);
GLAD_VK_KHR_ray_tracing = (glad_vk_has_extension("VK_KHR_acceleration_structure", extension_count, extensions) GLAD_VK_KHR_ray_tracing = (glad_vk_has_extension("VK_KHR_acceleration_structure", extension_count, extensions)
&& glad_vk_has_extension("VK_KHR_ray_tracing_pipeline", extension_count, extensions) && glad_vk_has_extension("VK_KHR_ray_tracing_pipeline", extension_count, extensions));
&& glad_vk_has_extension("VK_KHR_ray_query", extension_count, extensions));
GLAD_VK_KHR_relaxed_block_layout = glad_vk_has_extension("VK_KHR_relaxed_block_layout", extension_count, extensions); GLAD_VK_KHR_relaxed_block_layout = glad_vk_has_extension("VK_KHR_relaxed_block_layout", extension_count, extensions);
GLAD_VK_KHR_sampler_mirror_clamp_to_edge = glad_vk_has_extension("VK_KHR_sampler_mirror_clamp_to_edge", extension_count, extensions); GLAD_VK_KHR_sampler_mirror_clamp_to_edge = glad_vk_has_extension("VK_KHR_sampler_mirror_clamp_to_edge", extension_count, extensions);
GLAD_VK_KHR_sampler_ycbcr_conversion = glad_vk_has_extension("VK_KHR_sampler_ycbcr_conversion", extension_count, extensions); GLAD_VK_KHR_sampler_ycbcr_conversion = glad_vk_has_extension("VK_KHR_sampler_ycbcr_conversion", extension_count, extensions);

@ -265,6 +265,12 @@ namespace AZ
// Update all bindings on this pass that are connected to bindings on other passes // Update all bindings on this pass that are connected to bindings on other passes
void UpdateConnectedBindings(); void UpdateConnectedBindings();
// Update input and input/output bindings on this pass that are connected to bindings on other passes
void UpdateConnectedInputBindings();
// Update output bindings on this pass that are connected to bindings on other passes
void UpdateConnectedOutputBindings();
protected: protected:
explicit Pass(const PassDescriptor& descriptor); explicit Pass(const PassDescriptor& descriptor);

@ -57,6 +57,20 @@ namespace AZ
} }
#endif #endif
} }
//! Prints a generic message at the appropriate indent level.
template<typename ... Args>
static void Printf([[maybe_unused]] const char* format, [[maybe_unused]] Args... args)
{
#ifdef AZ_ENABLE_SHADER_RELOAD_DEBUG_TRACKER
if (IsEnabled())
{
const AZStd::string message = AZStd::string::format(format, args...);
AZ_TracePrintf("ShaderReloadDebug", "%*s %s \n", s_indent, "", message.c_str());
}
#endif
}
//! Use this utility to call BeginSection(), and automatically call EndSection() when the object goes out of scope. //! Use this utility to call BeginSection(), and automatically call EndSection() when the object goes out of scope.
class ScopedSection final class ScopedSection final

@ -97,6 +97,7 @@ namespace AZ
ShaderReloadNotificationBus::MultiHandler::BusDisconnect(); ShaderReloadNotificationBus::MultiHandler::BusDisconnect();
for (auto& shaderItem : m_shaderCollection) for (auto& shaderItem : m_shaderCollection)
{ {
ShaderReloadDebugTracker::Printf("(Material has ShaderAsset %p)", shaderItem.GetShaderAsset().Get());
ShaderReloadNotificationBus::MultiHandler::BusConnect(shaderItem.GetShaderAsset().GetId()); ShaderReloadNotificationBus::MultiHandler::BusConnect(shaderItem.GetShaderAsset().GetId());
} }
@ -226,7 +227,7 @@ namespace AZ
// AssetBus overrides... // AssetBus overrides...
void Material::OnAssetReloaded(Data::Asset<Data::AssetData> asset) void Material::OnAssetReloaded(Data::Asset<Data::AssetData> asset)
{ {
ShaderReloadDebugTracker::ScopedSection reloadSection("Material::OnAssetReloaded %s", asset.GetHint().c_str()); ShaderReloadDebugTracker::ScopedSection reloadSection("{%p}->Material::OnAssetReloaded %s", this, asset.GetHint().c_str());
Data::Asset<MaterialAsset> newMaterialAsset = { asset.GetAs<MaterialAsset>(), AZ::Data::AssetLoadBehavior::PreLoad }; Data::Asset<MaterialAsset> newMaterialAsset = { asset.GetAs<MaterialAsset>(), AZ::Data::AssetLoadBehavior::PreLoad };
@ -241,7 +242,7 @@ namespace AZ
// MaterialReloadNotificationBus overrides... // MaterialReloadNotificationBus overrides...
void Material::OnMaterialAssetReinitialized(const Data::Asset<MaterialAsset>& materialAsset) void Material::OnMaterialAssetReinitialized(const Data::Asset<MaterialAsset>& materialAsset)
{ {
ShaderReloadDebugTracker::ScopedSection reloadSection("Material::OnMaterialAssetReinitialized %s", materialAsset.GetHint().c_str()); ShaderReloadDebugTracker::ScopedSection reloadSection("{%p}->Material::OnMaterialAssetReinitialized %s", this, materialAsset.GetHint().c_str());
OnAssetReloaded(materialAsset); OnAssetReloaded(materialAsset);
} }
@ -249,7 +250,7 @@ namespace AZ
// ShaderReloadNotificationBus overrides... // ShaderReloadNotificationBus overrides...
void Material::OnShaderReinitialized([[maybe_unused]] const Shader& shader) void Material::OnShaderReinitialized([[maybe_unused]] const Shader& shader)
{ {
ShaderReloadDebugTracker::ScopedSection reloadSection("Material::OnShaderReinitialized %s", shader.GetAsset().GetHint().c_str()); ShaderReloadDebugTracker::ScopedSection reloadSection("{%p}->Material::OnShaderReinitialized %s", this, shader.GetAsset().GetHint().c_str());
// Note that it might not be strictly necessary to reinitialize the entire material, we might be able to get away with // Note that it might not be strictly necessary to reinitialize the entire material, we might be able to get away with
// just bumping the m_currentChangeId or some other minor updates. But it's pretty hard to know what exactly needs to be // just bumping the m_currentChangeId or some other minor updates. But it's pretty hard to know what exactly needs to be
// updated to correctly handle the reload, so it's safer to just reinitialize the whole material. // updated to correctly handle the reload, so it's safer to just reinitialize the whole material.
@ -260,7 +261,7 @@ namespace AZ
{ {
// TODO: I think we should make Shader handle OnShaderAssetReinitialized and treat it just like the shader reloaded. // TODO: I think we should make Shader handle OnShaderAssetReinitialized and treat it just like the shader reloaded.
ShaderReloadDebugTracker::ScopedSection reloadSection("Material::OnShaderAssetReinitialized %s", shaderAsset.GetHint().c_str()); ShaderReloadDebugTracker::ScopedSection reloadSection("{%p}->Material::OnShaderAssetReinitialized %s", this, shaderAsset.GetHint().c_str());
// Note that it might not be strictly necessary to reinitialize the entire material, we might be able to get away with // Note that it might not be strictly necessary to reinitialize the entire material, we might be able to get away with
// just bumping the m_currentChangeId or some other minor updates. But it's pretty hard to know what exactly needs to be // just bumping the m_currentChangeId or some other minor updates. But it's pretty hard to know what exactly needs to be
// updated to correctly handle the reload, so it's safer to just reinitialize the whole material. // updated to correctly handle the reload, so it's safer to just reinitialize the whole material.
@ -269,7 +270,7 @@ namespace AZ
void Material::OnShaderVariantReinitialized(const Shader& shader, const ShaderVariantId& /*shaderVariantId*/, ShaderVariantStableId shaderVariantStableId) void Material::OnShaderVariantReinitialized(const Shader& shader, const ShaderVariantId& /*shaderVariantId*/, ShaderVariantStableId shaderVariantStableId)
{ {
ShaderReloadDebugTracker::ScopedSection reloadSection("Material::OnShaderVariantReinitialized %s variant %u", shader.GetAsset().GetHint().c_str(), shaderVariantStableId.GetIndex()); ShaderReloadDebugTracker::ScopedSection reloadSection("{%p}->Material::OnShaderVariantReinitialized %s variant %u", this, shader.GetAsset().GetHint().c_str(), shaderVariantStableId.GetIndex());
// Note that it would be better to check the shaderVariantId to see if that variant is relevant to this particular material before reinitializing it. // Note that it would be better to check the shaderVariantId to see if that variant is relevant to this particular material before reinitializing it.
// There could be hundreds or even thousands of variants for a shader, but only one of those variants will be used by any given material. So we could // There could be hundreds or even thousands of variants for a shader, but only one of those variants will be used by any given material. So we could

@ -1036,6 +1036,26 @@ namespace AZ
} }
} }
void Pass::UpdateConnectedInputBindings()
{
for (uint8_t idx : m_inputBindingIndices)
{
UpdateConnectedBinding(m_attachmentBindings[idx]);
}
for (uint8_t idx : m_inputOutputBindingIndices)
{
UpdateConnectedBinding(m_attachmentBindings[idx]);
}
}
void Pass::UpdateConnectedOutputBindings()
{
for (uint8_t idx : m_outputBindingIndices)
{
UpdateConnectedBinding(m_attachmentBindings[idx]);
}
}
// --- Queuing functions with PassSystem --- // --- Queuing functions with PassSystem ---
void Pass::QueueForBuildAndInitialization() void Pass::QueueForBuildAndInitialization()
@ -1264,7 +1284,7 @@ namespace AZ
AZ_Assert(m_state == PassState::Idle, "Pass::FrameBegin - Pass [%s] is attempting to render, but is not in the Idle state.", m_path.GetCStr()); AZ_Assert(m_state == PassState::Idle, "Pass::FrameBegin - Pass [%s] is attempting to render, but is not in the Idle state.", m_path.GetCStr());
m_state = PassState::Rendering; m_state = PassState::Rendering;
UpdateConnectedBindings(); UpdateConnectedInputBindings();
UpdateOwnedAttachments(); UpdateOwnedAttachments();
CreateTransientAttachments(params.m_frameGraphBuilder->GetAttachmentDatabase()); CreateTransientAttachments(params.m_frameGraphBuilder->GetAttachmentDatabase());
@ -1273,6 +1293,8 @@ namespace AZ
// FrameBeginInternal needs to be the last function be called in FrameBegin because its implementation expects // FrameBeginInternal needs to be the last function be called in FrameBegin because its implementation expects
// all the attachments are imported to database (for example, ImageAttachmentPreview) // all the attachments are imported to database (for example, ImageAttachmentPreview)
FrameBeginInternal(params); FrameBeginInternal(params);
UpdateConnectedOutputBindings();
} }
void Pass::FrameEnd() void Pass::FrameEnd()

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

Loading…
Cancel
Save